Subject: upgrade AST

From: William Joye <[email-masked]>
Date: Fri, 8 Dec 2017 13:59:21 -0500

---
 ast/AST_ERR                             |   639 +
 ast/COPYING                             |   674 +
 ast/COPYING.LESSER                      |   165 +
 ast/COPYING.LIB                         |   481 +
 ast/Ers.h                               |   245 +
 ast/GRF_PAR                             |   124 +
 ast/Makefile.am                         |   822 +
 ast/Makefile.in                         |  3968 +++
 ast/acinclude.m4                        |     1 +
 ast/aclocal.m4                          | 11530 +++++++
 ast/ast.news                            |  1122 +
 ast/ast_cpp.in                          |    11 +
 ast/ast_err.h                           |   486 +
 ast/ast_link.in                         |   463 +
 ast/ast_link_adam                       |   406 +
 ast/ast_link_adam.in                    |   406 +
 ast/ast_par.source                      |   727 +
 ast/ast_test.c                          |   115 +
 ast/astbad.c                            |   181 +
 ast/axis.c                              |  3500 ++
 ast/axis.h                              |   625 +
 ast/box.c                               |  5062 +++
 ast/box.h                               |   234 +
 ast/build-aux/compile                   |   347 +
 ast/build-aux/config.guess              |  1568 +
 ast/build-aux/config.sub                |  1793 +
 ast/build-aux/depcomp                   |   791 +
 ast/build-aux/install-sh                |   527 +
 ast/build-aux/ltmain.sh                 |  9655 ++++++
 ast/build-aux/missing                   |   215 +
 ast/build-aux/test-driver               |   139 +
 ast/c2f77.c                             |   125 +
 ast/c2f77.h                             |   166 +
 ast/channel.c                           |  6458 ++++
 ast/channel.h                           |   687 +
 ast/circle.c                            |  2900 ++
 ast/circle.h                            |   241 +
 ast/cminpack/.deps/libast_la-dpmpar.Plo |     1 +
 ast/cminpack/.deps/libast_la-enorm.Plo  |     1 +
 ast/cminpack/.deps/libast_la-lmder.Plo  |     1 +
 ast/cminpack/.deps/libast_la-lmder1.Plo |     1 +
 ast/cminpack/.deps/libast_la-lmpar.Plo  |     1 +
 ast/cminpack/.deps/libast_la-qrfac.Plo  |     1 +
 ast/cminpack/.deps/libast_la-qrsolv.Plo |     1 +
 ast/cminpack/CopyrightMINPACK.txt       |    52 +
 ast/cminpack/README.md                  |   128 +
 ast/cminpack/cminpack.h                 |   370 +
 ast/cminpack/cminpackP.h                |    62 +
 ast/cminpack/dpmpar.c                   |   201 +
 ast/cminpack/enorm.c                    |   157 +
 ast/cminpack/lmder.c                    |   526 +
 ast/cminpack/lmder1.c                   |   167 +
 ast/cminpack/lmpar.c                    |   338 +
 ast/cminpack/qrfac.c                    |   285 +
 ast/cminpack/qrsolv.c                   |   218 +
 ast/cmpframe.c                          | 10654 ++++++
 ast/cmpframe.h                          |   428 +
 ast/cmpmap.c                            |  4739 +++
 ast/cmpmap.h                            |   300 +
 ast/cmpregion.c                         |  5127 +++
 ast/cmpregion.h                         |   251 +
 ast/component.xml.in                    |    44 +
 ast/config.h.in                         |   136 +
 ast/configure                           | 19453 +++++++++++
 ast/configure.ac                        |   215 +
 ast/dsbspecframe.c                      |  3266 ++
 ast/dsbspecframe.h                      |   298 +
 ast/dssmap.c                            |  2283 ++
 ast/dssmap.h                            |   401 +
 ast/ellipse.c                           |  3055 ++
 ast/ellipse.h                           |   244 +
 ast/ems.h                               |   185 +
 ast/erfa.h                              |    72 +
 ast/erfa/INFO                           |    19 +
 ast/erfa/LICENSE                        |    53 +
 ast/erfa/Makefile.am                    |    47 +
 ast/erfa/README.rst                     |    93 +
 ast/erfa/RELEASE.rst                    |   180 +
 ast/erfa/a2af.c                         |   129 +
 ast/erfa/a2tf.c                         |   125 +
 ast/erfa/ab.c                           |   137 +
 ast/erfa/af2a.c                         |   116 +
 ast/erfa/anp.c                          |    91 +
 ast/erfa/anpm.c                         |    91 +
 ast/erfa/apcg.c                         |   181 +
 ast/erfa/apcg13.c                       |   184 +
 ast/erfa/apci.c                         |   190 +
 ast/erfa/apci13.c                       |   202 +
 ast/erfa/apco.c                         |   264 +
 ast/erfa/apco13.c                       |   287 +
 ast/erfa/apcs.c                         |   233 +
 ast/erfa/apcs13.c                       |   191 +
 ast/erfa/aper.c                         |   162 +
 ast/erfa/aper13.c                       |   181 +
 ast/erfa/apio.c                         |   213 +
 ast/erfa/apio13.c                       |   259 +
 ast/erfa/atci13.c                       |   159 +
 ast/erfa/atciq.c                        |   154 +
 ast/erfa/atciqn.c                       |   191 +
 ast/erfa/atciqz.c                       |   153 +
 ast/erfa/atco13.c                       |   243 +
 ast/erfa/atic13.c                       |   152 +
 ast/erfa/aticq.c                        |   199 +
 ast/erfa/aticqn.c                       |   237 +
 ast/erfa/atio13.c                       |   222 +
 ast/erfa/atioq.c                        |   243 +
 ast/erfa/atoc13.c                       |   233 +
 ast/erfa/atoi13.c                       |   228 +
 ast/erfa/atoiq.c                        |   260 +
 ast/erfa/bi00.c                         |   125 +
 ast/erfa/bp00.c                         |   181 +
 ast/erfa/bp06.c                         |   152 +
 ast/erfa/bpn2xy.c                       |   109 +
 ast/erfa/c2i00a.c                       |   148 +
 ast/erfa/c2i00b.c                       |   148 +
 ast/erfa/c2i06a.c                       |   145 +
 ast/erfa/c2ibpn.c                       |   151 +
 ast/erfa/c2ixy.c                        |   140 +
 ast/erfa/c2ixys.c                       |   132 +
 ast/erfa/c2s.c                          |   105 +
 ast/erfa/c2t00a.c                       |   163 +
 ast/erfa/c2t00b.c                       |   159 +
 ast/erfa/c2t06a.c                       |   161 +
 ast/erfa/c2tcio.c                       |   131 +
 ast/erfa/c2teqx.c                       |   131 +
 ast/erfa/c2tpe.c                        |   176 +
 ast/erfa/c2txy.c                        |   168 +
 ast/erfa/cal2jd.c                       |   148 +
 ast/erfa/cp.c                           |    89 +
 ast/erfa/cpv.c                          |    91 +
 ast/erfa/cr.c                           |    92 +
 ast/erfa/d2dtf.c                        |   245 +
 ast/erfa/d2tf.c                         |   169 +
 ast/erfa/dat.c                          |   306 +
 ast/erfa/dtdb.c                         |  1222 +
 ast/erfa/dtf2d.c                        |   212 +
 ast/erfa/eceq06.c                       |   141 +
 ast/erfa/ecm06.c                        |   144 +
 ast/erfa/ee00.c                         |   137 +
 ast/erfa/ee00a.c                        |   144 +
 ast/erfa/ee00b.c                        |   150 +
 ast/erfa/ee06a.c                        |   131 +
 ast/erfa/eect00.c                       |   291 +
 ast/erfa/eform.c                        |   155 +
 ast/erfa/eo06a.c                        |   140 +
 ast/erfa/eors.c                         |   117 +
 ast/erfa/epb.c                          |   100 +
 ast/erfa/epb2jd.c                       |   100 +
 ast/erfa/epj.c                          |   102 +
 ast/erfa/epj2jd.c                       |   100 +
 ast/erfa/epv00.c                        |  2598 ++
 ast/erfa/eqec06.c                       |   142 +
 ast/erfa/eqeq94.c                       |   141 +
 ast/erfa/era00.c                        |   145 +
 ast/erfa/erfa.h                         |   517 +
 ast/erfa/erfam.h                        |   208 +
 ast/erfa/fad03.c                        |   112 +
 ast/erfa/fae03.c                        |   111 +
 ast/erfa/faf03.c                        |   115 +
 ast/erfa/faju03.c                       |   111 +
 ast/erfa/fal03.c                        |   112 +
 ast/erfa/falp03.c                       |   112 +
 ast/erfa/fama03.c                       |   111 +
 ast/erfa/fame03.c                       |   111 +
 ast/erfa/fane03.c                       |   108 +
 ast/erfa/faom03.c                       |   113 +
 ast/erfa/fapa03.c                       |   112 +
 ast/erfa/fasa03.c                       |   111 +
 ast/erfa/faur03.c                       |   108 +
 ast/erfa/fave03.c                       |   111 +
 ast/erfa/fk52h.c                        |   152 +
 ast/erfa/fk5hip.c                       |   135 +
 ast/erfa/fk5hz.c                        |   169 +
 ast/erfa/fw2m.c                         |   143 +
 ast/erfa/fw2xy.c                        |   130 +
 ast/erfa/g2icrs.c                       |   170 +
 ast/erfa/gc2gd.c                        |   143 +
 ast/erfa/gc2gde.c                       |   208 +
 ast/erfa/gd2gc.c                        |   142 +
 ast/erfa/gd2gce.c                       |   146 +
 ast/erfa/gmst00.c                       |   154 +
 ast/erfa/gmst06.c                       |   145 +
 ast/erfa/gmst82.c                       |   160 +
 ast/erfa/gst00a.c                       |   147 +
 ast/erfa/gst00b.c                       |   155 +
 ast/erfa/gst06.c                        |   149 +
 ast/erfa/gst06a.c                       |   140 +
 ast/erfa/gst94.c                        |   140 +
 ast/erfa/h2fk5.c                        |   157 +
 ast/erfa/hfk5z.c                        |   184 +
 ast/erfa/icrs2g.c                       |   170 +
 ast/erfa/ir.c                           |    92 +
 ast/erfa/jd2cal.c                       |   164 +
 ast/erfa/jdcalf.c                       |   170 +
 ast/erfa/ld.c                           |   161 +
 ast/erfa/ldn.c                          |   183 +
 ast/erfa/ldsun.c                        |   115 +
 ast/erfa/lteceq.c                       |   138 +
 ast/erfa/ltecm.c                        |   157 +
 ast/erfa/lteqec.c                       |   139 +
 ast/erfa/ltp.c                          |   140 +
 ast/erfa/ltpb.c                         |   133 +
 ast/erfa/ltpecl.c                       |   177 +
 ast/erfa/ltpequ.c                       |   177 +
 ast/erfa/num00a.c                       |   130 +
 ast/erfa/num00b.c                       |   130 +
 ast/erfa/num06a.c                       |   134 +
 ast/erfa/numat.c                        |   118 +
 ast/erfa/nut00a.c                       |  2056 ++
 ast/erfa/nut00b.c                       |   381 +
 ast/erfa/nut06a.c                       |   162 +
 ast/erfa/nut80.c                        |   334 +
 ast/erfa/nutm80.c                       |   126 +
 ast/erfa/obl06.c                        |   127 +
 ast/erfa/obl80.c                        |   127 +
 ast/erfa/p06e.c                         |   330 +
 ast/erfa/p2pv.c                         |    92 +
 ast/erfa/p2s.c                          |   100 +
 ast/erfa/pap.c                          |   148 +
 ast/erfa/pas.c                          |   105 +
 ast/erfa/pb06.c                         |   153 +
 ast/erfa/pdp.c                          |    93 +
 ast/erfa/pfw06.c                        |   174 +
 ast/erfa/plan94.c                       |   523 +
 ast/erfa/pm.c                           |    85 +
 ast/erfa/pmat00.c                       |   127 +
 ast/erfa/pmat06.c                       |   131 +
 ast/erfa/pmat76.c                       |   150 +
 ast/erfa/pmp.c                          |    94 +
 ast/erfa/pmpx.c                         |   153 +
 ast/erfa/pmsafe.c                       |   206 +
 ast/erfa/pn.c                           |   118 +
 ast/erfa/pn00.c                         |   186 +
 ast/erfa/pn00a.c                        |   172 +
 ast/erfa/pn00b.c                        |   172 +
 ast/erfa/pn06.c                         |   196 +
 ast/erfa/pn06a.c                        |   162 +
 ast/erfa/pnm00a.c                       |   130 +
 ast/erfa/pnm00b.c                       |   130 +
 ast/erfa/pnm06a.c                       |   133 +
 ast/erfa/pnm80.c                        |   135 +
 ast/erfa/pom00.c                        |   124 +
 ast/erfa/ppp.c                          |    94 +
 ast/erfa/ppsp.c                         |   103 +
 ast/erfa/pr00.c                         |   151 +
 ast/erfa/prec76.c                       |   157 +
 ast/erfa/pv2p.c                         |    90 +
 ast/erfa/pv2s.c                         |   153 +
 ast/erfa/pvdpv.c                        |   111 +
 ast/erfa/pvm.c                          |    95 +
 ast/erfa/pvmpv.c                        |    96 +
 ast/erfa/pvppv.c                        |    96 +
 ast/erfa/pvstar.c                       |   216 +
 ast/erfa/pvtob.c                        |   162 +
 ast/erfa/pvu.c                          |   102 +
 ast/erfa/pvup.c                         |    97 +
 ast/erfa/pvxpv.c                        |   116 +
 ast/erfa/pxp.c                          |   103 +
 ast/erfa/refco.c                        |   262 +
 ast/erfa/rm2v.c                         |   120 +
 ast/erfa/rv2m.c                         |   127 +
 ast/erfa/rx.c                           |   119 +
 ast/erfa/rxp.c                          |   108 +
 ast/erfa/rxpv.c                         |    95 +
 ast/erfa/rxr.c                          |   108 +
 ast/erfa/ry.c                           |   119 +
 ast/erfa/rz.c                           |   119 +
 ast/erfa/s00.c                          |   380 +
 ast/erfa/s00a.c                         |   152 +
 ast/erfa/s00b.c                         |   152 +
 ast/erfa/s06.c                          |   377 +
 ast/erfa/s06a.c                         |   154 +
 ast/erfa/s2c.c                          |    94 +
 ast/erfa/s2p.c                          |    97 +
 ast/erfa/s2pv.c                         |   112 +
 ast/erfa/s2xpv.c                        |    96 +
 ast/erfa/sepp.c                         |   114 +
 ast/erfa/seps.c                         |   102 +
 ast/erfa/sp00.c                         |   127 +
 ast/erfa/starpm.c                       |   214 +
 ast/erfa/starpv.c                       |   273 +
 ast/erfa/sxp.c                          |    93 +
 ast/erfa/sxpv.c                         |    94 +
 ast/erfa/t_erfa_c.c                     |  9742 ++++++
 ast/erfa/taitt.c                        |   119 +
 ast/erfa/taiut1.c                       |   120 +
 ast/erfa/taiutc.c                       |   168 +
 ast/erfa/tcbtdb.c                       |   141 +
 ast/erfa/tcgtt.c                        |   118 +
 ast/erfa/tdbtcb.c                       |   146 +
 ast/erfa/tdbtt.c                        |   130 +
 ast/erfa/tf2a.c                         |   116 +
 ast/erfa/tf2d.c                         |   116 +
 ast/erfa/tr.c                           |   102 +
 ast/erfa/trxp.c                         |   102 +
 ast/erfa/trxpv.c                        |   102 +
 ast/erfa/tttai.c                        |   119 +
 ast/erfa/tttcg.c                        |   121 +
 ast/erfa/tttdb.c                        |   130 +
 ast/erfa/ttut1.c                        |   119 +
 ast/erfa/ut1tai.c                       |   120 +
 ast/erfa/ut1tt.c                        |   119 +
 ast/erfa/ut1utc.c                       |   202 +
 ast/erfa/utctai.c                       |   186 +
 ast/erfa/utcut1.c                       |   156 +
 ast/erfa/xy06.c                         |  2767 ++
 ast/erfa/xys00a.c                       |   142 +
 ast/erfa/xys00b.c                       |   142 +
 ast/erfa/xys06a.c                       |   142 +
 ast/erfa/zp.c                           |    86 +
 ast/erfa/zpv.c                          |    88 +
 ast/erfa/zr.c                           |    92 +
 ast/erfa2ast.h                          |   248 +
 ast/erfam.h                             |    61 +
 ast/err.h                               |    66 +
 ast/err_drama.c                         |   122 +
 ast/err_ems.c                           |   108 +
 ast/err_null.c                          |   108 +
 ast/error.c                             |  1024 +
 ast/error.h                             |   327 +
 ast/f77.h                               |  1096 +
 ast/f77.h.in                            |  1096 +
 ast/fac_1521_err                        |   160 +
 ast/fbox.c                              |   110 +
 ast/fchannel.c                          |   473 +
 ast/fcircle.c                           |   128 +
 ast/fcmpframe.c                         |   104 +
 ast/fcmpmap.c                           |   106 +
 ast/fcmpregion.c                        |   107 +
 ast/fdsbspecframe.c                     |   100 +
 ast/fdssmap.c                           |    75 +
 ast/fellipse.c                          |   136 +
 ast/ferror.c                            |    58 +
 ast/ffitschan.c                         |  1022 +
 ast/ffitstable.c                        |   234 +
 ast/ffluxframe.c                        |   104 +
 ast/fframe.c                            |   514 +
 ast/fframeset.c                         |   214 +
 ast/fgrismmap.c                         |    99 +
 ast/finterval.c                         |   109 +
 ast/fintramap.c                         |   332 +
 ast/fitschan.c                          | 42623 ++++++++++++++++++++++++
 ast/fitschan.h                          |   871 +
 ast/fitstable.c                         |  3006 ++
 ast/fitstable.h                         |   235 +
 ast/fkeymap.c                           |  1400 +
 ast/flutmap.c                           |   107 +
 ast/fluxframe.c                         |  4490 +++
 ast/fluxframe.h                         |   267 +
 ast/fmapping.c                          |   771 +
 ast/fmathmap.c                          |   122 +
 ast/fmatrixmap.c                        |   110 +
 ast/fnormmap.c                          |   101 +
 ast/fnullregion.c                       |   104 +
 ast/fobject.c                           |   674 +
 ast/fpcdmap.c                           |   103 +
 ast/fpermmap.c                          |   110 +
 ast/fplot.c                             |   683 +
 ast/fplot3d.c                           |   107 +
 ast/fpointlist.c                        |   117 +
 ast/fpolygon.c                          |   226 +
 ast/fpolymap.c                          |   140 +
 ast/fprism.c                            |   105 +
 ast/frame.c                             | 15978 +++++++++
 ast/frame.h                             |  1437 +
 ast/frameset.c                          | 13096 ++++++++
 ast/frameset.h                          |   709 +
 ast/fratemap.c                          |   106 +
 ast/fregion.c                           |   297 +
 ast/fselectormap.c                      |   115 +
 ast/fshiftmap.c                         |   103 +
 ast/fskyframe.c                         |   112 +
 ast/fslamap.c                           |   122 +
 ast/fspecfluxframe.c                    |   104 +
 ast/fspecframe.c                        |   134 +
 ast/fspecmap.c                          |   124 +
 ast/fsphmap.c                           |    99 +
 ast/fstc.c                              |   114 +
 ast/fstccatalogentrylocation.c          |   117 +
 ast/fstcobsdatalocation.c               |   117 +
 ast/fstcresourceprofile.c               |   118 +
 ast/fstcschan.c                         |   131 +
 ast/fstcsearchlocation.c                |   117 +
 ast/fswitchmap.c                        |   118 +
 ast/ftable.c                            |   330 +
 ast/ftimeframe.c                        |   114 +
 ast/ftimemap.c                          |   122 +
 ast/ftranmap.c                          |   104 +
 ast/funitmap.c                          |   101 +
 ast/funitnormmap.c                      |   105 +
 ast/fwcsmap.c                           |   108 +
 ast/fwinmap.c                           |   110 +
 ast/fxmlchan.c                          |   130 +
 ast/fzoommap.c                          |   103 +
 ast/globals.c                           |   252 +
 ast/globals.h                           |   245 +
 ast/grf.h                               |   110 +
 ast/grf3d.c                             |   102 +
 ast/grf3d.h                             |    69 +
 ast/grf3d_pgplot.c                      |  3196 ++
 ast/grf_2.0.c                           |   101 +
 ast/grf_3.2.c                           |    74 +
 ast/grf_5.6.c                           |    77 +
 ast/grf_pgplot.c                        |  1494 +
 ast/grismmap.c                          |  2596 ++
 ast/grismmap.h                          |   353 +
 ast/interval.c                          |  4686 +++
 ast/interval.h                          |   236 +
 ast/intramap.c                          |  2942 ++
 ast/intramap.h                          |   344 +
 ast/keymap.c                            | 10786 ++++++
 ast/keymap.h                            |   566 +
 ast/loader.c                            |   197 +
 ast/loader.h                            |    49 +
 ast/lutmap.c                            |  2629 ++
 ast/lutmap.h                            |   335 +
 ast/makeh                               |   313 +
 ast/mapping.c                           | 24616 ++++++++++++++
 ast/mapping.h                           |   856 +
 ast/mathmap.c                           |  7421 +++++
 ast/mathmap.h                           |   410 +
 ast/matrixmap.c                         |  5501 ++++
 ast/matrixmap.h                         |   318 +
 ast/memory.c                            |  5483 ++++
 ast/memory.h                            |   347 +
 ast/normmap.c                           |  1720 +
 ast/normmap.h                           |   217 +
 ast/nullregion.c                        |  2093 ++
 ast/nullregion.h                        |   221 +
 ast/object.c                            |  8657 +++++
 ast/object.h                            |  1934 ++
 ast/object.h.in                         |  1934 ++
 ast/pal.h                               |   582 +
 ast/pal/pal.h                           |   551 +
 ast/pal/pal1sofa.h                      |   142 +
 ast/pal/palAddet.c                      |   112 +
 ast/pal/palAmpqk.c                      |   159 +
 ast/pal/palCaldj.c                      |    99 +
 ast/pal/palDat.c                        |    95 +
 ast/pal/palDe2h.c                       |   142 +
 ast/pal/palDeuler.c                     |   141 +
 ast/pal/palDh2e.c                       |   133 +
 ast/pal/palDjcal.c                      |    97 +
 ast/pal/palDmat.c                       |   182 +
 ast/pal/palDrange.c                     |    77 +
 ast/pal/palDs2tp.c                      |   127 +
 ast/pal/palDtp2s.c                      |    95 +
 ast/pal/palDtps2c.c                     |   151 +
 ast/pal/palDtt.c                        |    77 +
 ast/pal/palEcmat.c                      |    82 +
 ast/pal/palEqgal.c                      |   118 +
 ast/pal/palEtrms.c                      |   106 +
 ast/pal/palEvp.c                        |   110 +
 ast/pal/palFk45z.c                      |   186 +
 ast/pal/palFk524.c                      |   259 +
 ast/pal/palFk54z.c                      |   113 +
 ast/pal/palGaleq.c                      |   118 +
 ast/pal/palGalsup.c                     |   116 +
 ast/pal/palGeoc.c                       |    83 +
 ast/pal/palMappa.c                      |   129 +
 ast/pal/palMapqkz.c                     |   150 +
 ast/pal/palOne2One.c                    |  1482 +
 ast/pal/palPrebn.c                      |    98 +
 ast/pal/palPrec.c                       |   107 +
 ast/pal/palPrenut.c                     |   111 +
 ast/pal/palPvobs.c                      |   108 +
 ast/pal/palRvgalc.c                     |   111 +
 ast/pal/palRvlg.c                       |   106 +
 ast/pal/palRvlsrd.c                     |   116 +
 ast/pal/palRvlsrk.c                     |   116 +
 ast/pal/palSubet.c                      |   112 +
 ast/pal/palSupgal.c                     |   116 +
 ast/pal/palmac.h                        |   136 +
 ast/pal2ast.h                           |   134 +
 ast/palwrap.c                           |   301 +
 ast/pcdmap.c                            |  3218 ++
 ast/pcdmap.h                            |   357 +
 ast/permmap.c                           |  3204 ++
 ast/permmap.h                           |   322 +
 ast/pg3d.h                              |    68 +
 ast/plot.c                              | 32074 ++++++++++++++++++
 ast/plot.h                              |  1417 +
 ast/plot3d.c                            |  8587 +++++
 ast/plot3d.h                            |   258 +
 ast/pointlist.c                         |  3407 ++
 ast/pointlist.h                         |   239 +
 ast/pointset.c                          |  3285 ++
 ast/pointset.h                          |   711 +
 ast/polygon.c                           |  7087 ++++
 ast/polygon.h                           |   353 +
 ast/polymap.c                           |  5511 ++++
 ast/polymap.h                           |   354 +
 ast/prism.c                             |  4448 +++
 ast/prism.h                             |   238 +
 ast/proj.c                              |  4840 +++
 ast/proj.h                              |   181 +
 ast/ratemap.c                           |  2011 ++
 ast/ratemap.h                           |   276 +
 ast/region.c                            | 13502 ++++++++
 ast/region.h                            |   515 +
 ast/selectormap.c                       |  1838 ++
 ast/selectormap.h                       |   277 +
 ast/shiftmap.c                          |  1617 +
 ast/shiftmap.h                          |   290 +
 ast/skyaxis.c                           |  5150 +++
 ast/skyaxis.h                           |   428 +
 ast/skyframe.c                          | 12440 +++++++
 ast/skyframe.h                          |   505 +
 ast/slamap.c                            |  5027 +++
 ast/slamap.h                            |   330 +
 ast/specfluxframe.c                     |  2189 ++
 ast/specfluxframe.h                     |   215 +
 ast/specframe.c                         |  7437 +++++
 ast/specframe.h                         |   430 +
 ast/specmap.c                           |  4696 +++
 ast/specmap.h                           |   282 +
 ast/sphmap.c                            |  2061 ++
 ast/sphmap.h                            |   374 +
 ast/sst.sty                             |   234 +
 ast/starabbrev.sty                      |   284 +
 ast/starlink.cls                        |   510 +
 ast/starstyle.sty                       |   162 +
 ast/stc.c                               |  3703 +++
 ast/stc.h                               |   240 +
 ast/stccatalogentrylocation.c           |   804 +
 ast/stccatalogentrylocation.h           |   223 +
 ast/stcobsdatalocation.c                |  1051 +
 ast/stcobsdatalocation.h                |   236 +
 ast/stcresourceprofile.c                |   807 +
 ast/stcresourceprofile.h                |   223 +
 ast/stcschan.c                          |  8732 +++++
 ast/stcschan.h                          |   308 +
 ast/stcsearchlocation.c                 |   806 +
 ast/stcsearchlocation.h                 |   222 +
 ast/sun210.htx_tar                      |   Bin 0 -> 16230400 bytes
 ast/sun210.pdf                          |   Bin 0 -> 2642881 bytes
 ast/sun210.tex                          | 50985 ++++++++++++++++++++++++++++
 ast/sun210_figures/cmpframe.pdf         |   Bin 0 -> 7432 bytes
 ast/sun210_figures/complex.pdf          |   Bin 0 -> 15323 bytes
 ast/sun210_figures/frames.pdf           |   Bin 0 -> 7196 bytes
 ast/sun210_figures/frameset.pdf         |   Bin 0 -> 28443 bytes
 ast/sun210_figures/fronta.pdf           |   Bin 0 -> 22394 bytes
 ast/sun210_figures/fronta_bw.pdf        |   Bin 0 -> 22635 bytes
 ast/sun210_figures/frontb.pdf           |   Bin 0 -> 57273 bytes
 ast/sun210_figures/frontb_bw.pdf        |   Bin 0 -> 66247 bytes
 ast/sun210_figures/frontc.pdf           |   Bin 0 -> 57629 bytes
 ast/sun210_figures/frontc_bw.pdf        |   Bin 0 -> 43186 bytes
 ast/sun210_figures/fsalign.pdf          |   Bin 0 -> 51413 bytes
 ast/sun210_figures/fsconvert.pdf        |   Bin 0 -> 18693 bytes
 ast/sun210_figures/fsexample.pdf        |   Bin 0 -> 17716 bytes
 ast/sun210_figures/fsmerge.pdf          |   Bin 0 -> 47115 bytes
 ast/sun210_figures/fsremap.pdf          |   Bin 0 -> 24970 bytes
 ast/sun210_figures/gridplot.pdf         |   Bin 0 -> 50536 bytes
 ast/sun210_figures/gridplot_bw.pdf      |   Bin 0 -> 20421 bytes
 ast/sun210_figures/mapping.pdf          |   Bin 0 -> 8013 bytes
 ast/sun210_figures/overgrid.pdf         |   Bin 0 -> 26963 bytes
 ast/sun210_figures/overgrid_bw.pdf      |   Bin 0 -> 25865 bytes
 ast/sun210_figures/parallel.pdf         |   Bin 0 -> 10441 bytes
 ast/sun210_figures/series.pdf           |   Bin 0 -> 10149 bytes
 ast/sun210_figures/simpexamp.pdf        |   Bin 0 -> 7842 bytes
 ast/sun211.htx_tar                      |   Bin 0 -> 16578560 bytes
 ast/sun211.pdf                          |   Bin 0 -> 2644871 bytes
 ast/sun211.tex                          | 52927 ++++++++++++++++++++++++++++++
 ast/sun211_figures/cmpframe.pdf         |   Bin 0 -> 7432 bytes
 ast/sun211_figures/complex.pdf          |   Bin 0 -> 15323 bytes
 ast/sun211_figures/frames.pdf           |   Bin 0 -> 7196 bytes
 ast/sun211_figures/frameset.pdf         |   Bin 0 -> 28443 bytes
 ast/sun211_figures/fronta.pdf           |   Bin 0 -> 22394 bytes
 ast/sun211_figures/fronta_bw.pdf        |   Bin 0 -> 22635 bytes
 ast/sun211_figures/frontb.pdf           |   Bin 0 -> 57273 bytes
 ast/sun211_figures/frontb_bw.pdf        |   Bin 0 -> 66247 bytes
 ast/sun211_figures/frontc.pdf           |   Bin 0 -> 57629 bytes
 ast/sun211_figures/frontc_bw.pdf        |   Bin 0 -> 43186 bytes
 ast/sun211_figures/fsalign.pdf          |   Bin 0 -> 51413 bytes
 ast/sun211_figures/fsconvert.pdf        |   Bin 0 -> 18693 bytes
 ast/sun211_figures/fsexample.pdf        |   Bin 0 -> 17716 bytes
 ast/sun211_figures/fsmerge.pdf          |   Bin 0 -> 47115 bytes
 ast/sun211_figures/fsremap.pdf          |   Bin 0 -> 24962 bytes
 ast/sun211_figures/gridplot.pdf         |   Bin 0 -> 50536 bytes
 ast/sun211_figures/gridplot_bw.pdf      |   Bin 0 -> 20421 bytes
 ast/sun211_figures/mapping.pdf          |   Bin 0 -> 8013 bytes
 ast/sun211_figures/overgrid.pdf         |   Bin 0 -> 26963 bytes
 ast/sun211_figures/overgrid_bw.pdf      |   Bin 0 -> 25865 bytes
 ast/sun211_figures/parallel.pdf         |   Bin 0 -> 10441 bytes
 ast/sun211_figures/series.pdf           |   Bin 0 -> 10149 bytes
 ast/sun211_figures/simpexamp.pdf        |   Bin 0 -> 7842 bytes
 ast/switchmap.c                         |  2875 ++
 ast/switchmap.h                         |   289 +
 ast/table.c                             |  5246 +++
 ast/table.h                             |   309 +
 ast/timeframe.c                         |  7481 +++++
 ast/timeframe.h                         |   324 +
 ast/timemap.c                           |  5219 +++
 ast/timemap.h                           |   285 +
 ast/tpn.c                               |   393 +
 ast/tranmap.c                           |  2327 ++
 ast/tranmap.h                           |   276 +
 ast/unit.c                              |  6218 ++++
 ast/unit.h                              |    83 +
 ast/unitmap.c                           |  1425 +
 ast/unitmap.h                           |   288 +
 ast/unitnormmap.c                       |  1637 +
 ast/unitnormmap.h                       |   299 +
 ast/version.h                           |    73 +
 ast/wcsmap.c                            |  6063 ++++
 ast/wcsmap.h                            |   591 +
 ast/wcsmath.h                           |    67 +
 ast/wcstrig.c                           |   189 +
 ast/wcstrig.h                           |    63 +
 ast/winmap.c                            |  4389 +++
 ast/winmap.h                            |   300 +
 ast/xml.c                               |  7119 ++++
 ast/xml.h                               |   392 +
 ast/xmlchan.c                           | 14120 ++++++++
 ast/xmlchan.h                           |   300 +
 ast/zoommap.c                           |  2074 ++
 ast/zoommap.h                           |   322 +
 617 files changed, 701374 insertions(+)
 create mode 100644 ast/AST_ERR
 create mode 100644 ast/COPYING
 create mode 100644 ast/COPYING.LESSER
 create mode 100644 ast/COPYING.LIB
 create mode 100644 ast/Ers.h
 create mode 100644 ast/GRF_PAR
 create mode 100644 ast/Makefile.am
 create mode 100644 ast/Makefile.in
 create mode 100644 ast/acinclude.m4
 create mode 100644 ast/aclocal.m4
 create mode 100644 ast/ast.news
 create mode 100644 ast/ast_cpp.in
 create mode 100644 ast/ast_err.h
 create mode 100644 ast/ast_link.in
 create mode 100644 ast/ast_link_adam
 create mode 100644 ast/ast_link_adam.in
 create mode 100644 ast/ast_par.source
 create mode 100644 ast/ast_test.c
 create mode 100644 ast/astbad.c
 create mode 100644 ast/axis.c
 create mode 100644 ast/axis.h
 create mode 100644 ast/box.c
 create mode 100644 ast/box.h
 create mode 100755 ast/build-aux/compile
 create mode 100755 ast/build-aux/config.guess
 create mode 100755 ast/build-aux/config.sub
 create mode 100755 ast/build-aux/depcomp
 create mode 100755 ast/build-aux/install-sh
 create mode 100644 ast/build-aux/ltmain.sh
 create mode 100755 ast/build-aux/missing
 create mode 100755 ast/build-aux/test-driver
 create mode 100644 ast/c2f77.c
 create mode 100644 ast/c2f77.h
 create mode 100644 ast/channel.c
 create mode 100644 ast/channel.h
 create mode 100644 ast/circle.c
 create mode 100644 ast/circle.h
 create mode 100644 ast/cminpack/.deps/libast_la-dpmpar.Plo
 create mode 100644 ast/cminpack/.deps/libast_la-enorm.Plo
 create mode 100644 ast/cminpack/.deps/libast_la-lmder.Plo
 create mode 100644 ast/cminpack/.deps/libast_la-lmder1.Plo
 create mode 100644 ast/cminpack/.deps/libast_la-lmpar.Plo
 create mode 100644 ast/cminpack/.deps/libast_la-qrfac.Plo
 create mode 100644 ast/cminpack/.deps/libast_la-qrsolv.Plo
 create mode 100644 ast/cminpack/CopyrightMINPACK.txt
 create mode 100644 ast/cminpack/README.md
 create mode 100644 ast/cminpack/cminpack.h
 create mode 100644 ast/cminpack/cminpackP.h
 create mode 100644 ast/cminpack/dpmpar.c
 create mode 100644 ast/cminpack/enorm.c
 create mode 100644 ast/cminpack/lmder.c
 create mode 100644 ast/cminpack/lmder1.c
 create mode 100644 ast/cminpack/lmpar.c
 create mode 100644 ast/cminpack/qrfac.c
 create mode 100644 ast/cminpack/qrsolv.c
 create mode 100644 ast/cmpframe.c
 create mode 100644 ast/cmpframe.h
 create mode 100644 ast/cmpmap.c
 create mode 100644 ast/cmpmap.h
 create mode 100644 ast/cmpregion.c
 create mode 100644 ast/cmpregion.h
 create mode 100644 ast/component.xml.in
 create mode 100644 ast/config.h.in
 create mode 100755 ast/configure
 create mode 100644 ast/configure.ac
 create mode 100644 ast/dsbspecframe.c
 create mode 100644 ast/dsbspecframe.h
 create mode 100644 ast/dssmap.c
 create mode 100644 ast/dssmap.h
 create mode 100644 ast/ellipse.c
 create mode 100644 ast/ellipse.h
 create mode 100644 ast/ems.h
 create mode 100644 ast/erfa.h
 create mode 100644 ast/erfa/INFO
 create mode 100644 ast/erfa/LICENSE
 create mode 100644 ast/erfa/Makefile.am
 create mode 100644 ast/erfa/README.rst
 create mode 100644 ast/erfa/RELEASE.rst
 create mode 100644 ast/erfa/a2af.c
 create mode 100644 ast/erfa/a2tf.c
 create mode 100644 ast/erfa/ab.c
 create mode 100644 ast/erfa/af2a.c
 create mode 100644 ast/erfa/anp.c
 create mode 100644 ast/erfa/anpm.c
 create mode 100644 ast/erfa/apcg.c
 create mode 100644 ast/erfa/apcg13.c
 create mode 100644 ast/erfa/apci.c
 create mode 100644 ast/erfa/apci13.c
 create mode 100644 ast/erfa/apco.c
 create mode 100644 ast/erfa/apco13.c
 create mode 100644 ast/erfa/apcs.c
 create mode 100644 ast/erfa/apcs13.c
 create mode 100644 ast/erfa/aper.c
 create mode 100644 ast/erfa/aper13.c
 create mode 100644 ast/erfa/apio.c
 create mode 100644 ast/erfa/apio13.c
 create mode 100644 ast/erfa/atci13.c
 create mode 100644 ast/erfa/atciq.c
 create mode 100644 ast/erfa/atciqn.c
 create mode 100644 ast/erfa/atciqz.c
 create mode 100644 ast/erfa/atco13.c
 create mode 100644 ast/erfa/atic13.c
 create mode 100644 ast/erfa/aticq.c
 create mode 100644 ast/erfa/aticqn.c
 create mode 100644 ast/erfa/atio13.c
 create mode 100644 ast/erfa/atioq.c
 create mode 100644 ast/erfa/atoc13.c
 create mode 100644 ast/erfa/atoi13.c
 create mode 100644 ast/erfa/atoiq.c
 create mode 100644 ast/erfa/bi00.c
 create mode 100644 ast/erfa/bp00.c
 create mode 100644 ast/erfa/bp06.c
 create mode 100644 ast/erfa/bpn2xy.c
 create mode 100644 ast/erfa/c2i00a.c
 create mode 100644 ast/erfa/c2i00b.c
 create mode 100644 ast/erfa/c2i06a.c
 create mode 100644 ast/erfa/c2ibpn.c
 create mode 100644 ast/erfa/c2ixy.c
 create mode 100644 ast/erfa/c2ixys.c
 create mode 100644 ast/erfa/c2s.c
 create mode 100644 ast/erfa/c2t00a.c
 create mode 100644 ast/erfa/c2t00b.c
 create mode 100644 ast/erfa/c2t06a.c
 create mode 100644 ast/erfa/c2tcio.c
 create mode 100644 ast/erfa/c2teqx.c
 create mode 100644 ast/erfa/c2tpe.c
 create mode 100644 ast/erfa/c2txy.c
 create mode 100644 ast/erfa/cal2jd.c
 create mode 100644 ast/erfa/cp.c
 create mode 100644 ast/erfa/cpv.c
 create mode 100644 ast/erfa/cr.c
 create mode 100644 ast/erfa/d2dtf.c
 create mode 100644 ast/erfa/d2tf.c
 create mode 100644 ast/erfa/dat.c
 create mode 100644 ast/erfa/dtdb.c
 create mode 100644 ast/erfa/dtf2d.c
 create mode 100644 ast/erfa/eceq06.c
 create mode 100644 ast/erfa/ecm06.c
 create mode 100644 ast/erfa/ee00.c
 create mode 100644 ast/erfa/ee00a.c
 create mode 100644 ast/erfa/ee00b.c
 create mode 100644 ast/erfa/ee06a.c
 create mode 100644 ast/erfa/eect00.c
 create mode 100644 ast/erfa/eform.c
 create mode 100644 ast/erfa/eo06a.c
 create mode 100644 ast/erfa/eors.c
 create mode 100644 ast/erfa/epb.c
 create mode 100644 ast/erfa/epb2jd.c
 create mode 100644 ast/erfa/epj.c
 create mode 100644 ast/erfa/epj2jd.c
 create mode 100644 ast/erfa/epv00.c
 create mode 100644 ast/erfa/eqec06.c
 create mode 100644 ast/erfa/eqeq94.c
 create mode 100644 ast/erfa/era00.c
 create mode 100644 ast/erfa/erfa.h
 create mode 100644 ast/erfa/erfam.h
 create mode 100644 ast/erfa/fad03.c
 create mode 100644 ast/erfa/fae03.c
 create mode 100644 ast/erfa/faf03.c
 create mode 100644 ast/erfa/faju03.c
 create mode 100644 ast/erfa/fal03.c
 create mode 100644 ast/erfa/falp03.c
 create mode 100644 ast/erfa/fama03.c
 create mode 100644 ast/erfa/fame03.c
 create mode 100644 ast/erfa/fane03.c
 create mode 100644 ast/erfa/faom03.c
 create mode 100644 ast/erfa/fapa03.c
 create mode 100644 ast/erfa/fasa03.c
 create mode 100644 ast/erfa/faur03.c
 create mode 100644 ast/erfa/fave03.c
 create mode 100644 ast/erfa/fk52h.c
 create mode 100644 ast/erfa/fk5hip.c
 create mode 100644 ast/erfa/fk5hz.c
 create mode 100644 ast/erfa/fw2m.c
 create mode 100644 ast/erfa/fw2xy.c
 create mode 100644 ast/erfa/g2icrs.c
 create mode 100644 ast/erfa/gc2gd.c
 create mode 100644 ast/erfa/gc2gde.c
 create mode 100644 ast/erfa/gd2gc.c
 create mode 100644 ast/erfa/gd2gce.c
 create mode 100644 ast/erfa/gmst00.c
 create mode 100644 ast/erfa/gmst06.c
 create mode 100644 ast/erfa/gmst82.c
 create mode 100644 ast/erfa/gst00a.c
 create mode 100644 ast/erfa/gst00b.c
 create mode 100644 ast/erfa/gst06.c
 create mode 100644 ast/erfa/gst06a.c
 create mode 100644 ast/erfa/gst94.c
 create mode 100644 ast/erfa/h2fk5.c
 create mode 100644 ast/erfa/hfk5z.c
 create mode 100644 ast/erfa/icrs2g.c
 create mode 100644 ast/erfa/ir.c
 create mode 100644 ast/erfa/jd2cal.c
 create mode 100644 ast/erfa/jdcalf.c
 create mode 100644 ast/erfa/ld.c
 create mode 100644 ast/erfa/ldn.c
 create mode 100644 ast/erfa/ldsun.c
 create mode 100644 ast/erfa/lteceq.c
 create mode 100644 ast/erfa/ltecm.c
 create mode 100644 ast/erfa/lteqec.c
 create mode 100644 ast/erfa/ltp.c
 create mode 100644 ast/erfa/ltpb.c
 create mode 100644 ast/erfa/ltpecl.c
 create mode 100644 ast/erfa/ltpequ.c
 create mode 100644 ast/erfa/num00a.c
 create mode 100644 ast/erfa/num00b.c
 create mode 100644 ast/erfa/num06a.c
 create mode 100644 ast/erfa/numat.c
 create mode 100644 ast/erfa/nut00a.c
 create mode 100644 ast/erfa/nut00b.c
 create mode 100644 ast/erfa/nut06a.c
 create mode 100644 ast/erfa/nut80.c
 create mode 100644 ast/erfa/nutm80.c
 create mode 100644 ast/erfa/obl06.c
 create mode 100644 ast/erfa/obl80.c
 create mode 100644 ast/erfa/p06e.c
 create mode 100644 ast/erfa/p2pv.c
 create mode 100644 ast/erfa/p2s.c
 create mode 100644 ast/erfa/pap.c
 create mode 100644 ast/erfa/pas.c
 create mode 100644 ast/erfa/pb06.c
 create mode 100644 ast/erfa/pdp.c
 create mode 100644 ast/erfa/pfw06.c
 create mode 100644 ast/erfa/plan94.c
 create mode 100644 ast/erfa/pm.c
 create mode 100644 ast/erfa/pmat00.c
 create mode 100644 ast/erfa/pmat06.c
 create mode 100644 ast/erfa/pmat76.c
 create mode 100644 ast/erfa/pmp.c
 create mode 100644 ast/erfa/pmpx.c
 create mode 100644 ast/erfa/pmsafe.c
 create mode 100644 ast/erfa/pn.c
 create mode 100644 ast/erfa/pn00.c
 create mode 100644 ast/erfa/pn00a.c
 create mode 100644 ast/erfa/pn00b.c
 create mode 100644 ast/erfa/pn06.c
 create mode 100644 ast/erfa/pn06a.c
 create mode 100644 ast/erfa/pnm00a.c
 create mode 100644 ast/erfa/pnm00b.c
 create mode 100644 ast/erfa/pnm06a.c
 create mode 100644 ast/erfa/pnm80.c
 create mode 100644 ast/erfa/pom00.c
 create mode 100644 ast/erfa/ppp.c
 create mode 100644 ast/erfa/ppsp.c
 create mode 100644 ast/erfa/pr00.c
 create mode 100644 ast/erfa/prec76.c
 create mode 100644 ast/erfa/pv2p.c
 create mode 100644 ast/erfa/pv2s.c
 create mode 100644 ast/erfa/pvdpv.c
 create mode 100644 ast/erfa/pvm.c
 create mode 100644 ast/erfa/pvmpv.c
 create mode 100644 ast/erfa/pvppv.c
 create mode 100644 ast/erfa/pvstar.c
 create mode 100644 ast/erfa/pvtob.c
 create mode 100644 ast/erfa/pvu.c
 create mode 100644 ast/erfa/pvup.c
 create mode 100644 ast/erfa/pvxpv.c
 create mode 100644 ast/erfa/pxp.c
 create mode 100644 ast/erfa/refco.c
 create mode 100644 ast/erfa/rm2v.c
 create mode 100644 ast/erfa/rv2m.c
 create mode 100644 ast/erfa/rx.c
 create mode 100644 ast/erfa/rxp.c
 create mode 100644 ast/erfa/rxpv.c
 create mode 100644 ast/erfa/rxr.c
 create mode 100644 ast/erfa/ry.c
 create mode 100644 ast/erfa/rz.c
 create mode 100644 ast/erfa/s00.c
 create mode 100644 ast/erfa/s00a.c
 create mode 100644 ast/erfa/s00b.c
 create mode 100644 ast/erfa/s06.c
 create mode 100644 ast/erfa/s06a.c
 create mode 100644 ast/erfa/s2c.c
 create mode 100644 ast/erfa/s2p.c
 create mode 100644 ast/erfa/s2pv.c
 create mode 100644 ast/erfa/s2xpv.c
 create mode 100644 ast/erfa/sepp.c
 create mode 100644 ast/erfa/seps.c
 create mode 100644 ast/erfa/sp00.c
 create mode 100644 ast/erfa/starpm.c
 create mode 100644 ast/erfa/starpv.c
 create mode 100644 ast/erfa/sxp.c
 create mode 100644 ast/erfa/sxpv.c
 create mode 100644 ast/erfa/t_erfa_c.c
 create mode 100644 ast/erfa/taitt.c
 create mode 100644 ast/erfa/taiut1.c
 create mode 100644 ast/erfa/taiutc.c
 create mode 100644 ast/erfa/tcbtdb.c
 create mode 100644 ast/erfa/tcgtt.c
 create mode 100644 ast/erfa/tdbtcb.c
 create mode 100644 ast/erfa/tdbtt.c
 create mode 100644 ast/erfa/tf2a.c
 create mode 100644 ast/erfa/tf2d.c
 create mode 100644 ast/erfa/tr.c
 create mode 100644 ast/erfa/trxp.c
 create mode 100644 ast/erfa/trxpv.c
 create mode 100644 ast/erfa/tttai.c
 create mode 100644 ast/erfa/tttcg.c
 create mode 100644 ast/erfa/tttdb.c
 create mode 100644 ast/erfa/ttut1.c
 create mode 100644 ast/erfa/ut1tai.c
 create mode 100644 ast/erfa/ut1tt.c
 create mode 100644 ast/erfa/ut1utc.c
 create mode 100644 ast/erfa/utctai.c
 create mode 100644 ast/erfa/utcut1.c
 create mode 100644 ast/erfa/xy06.c
 create mode 100644 ast/erfa/xys00a.c
 create mode 100644 ast/erfa/xys00b.c
 create mode 100644 ast/erfa/xys06a.c
 create mode 100644 ast/erfa/zp.c
 create mode 100644 ast/erfa/zpv.c
 create mode 100644 ast/erfa/zr.c
 create mode 100644 ast/erfa2ast.h
 create mode 100644 ast/erfam.h
 create mode 100644 ast/err.h
 create mode 100644 ast/err_drama.c
 create mode 100644 ast/err_ems.c
 create mode 100644 ast/err_null.c
 create mode 100644 ast/error.c
 create mode 100644 ast/error.h
 create mode 100644 ast/f77.h
 create mode 100644 ast/f77.h.in
 create mode 100644 ast/fac_1521_err
 create mode 100644 ast/fbox.c
 create mode 100644 ast/fchannel.c
 create mode 100644 ast/fcircle.c
 create mode 100644 ast/fcmpframe.c
 create mode 100644 ast/fcmpmap.c
 create mode 100644 ast/fcmpregion.c
 create mode 100644 ast/fdsbspecframe.c
 create mode 100644 ast/fdssmap.c
 create mode 100644 ast/fellipse.c
 create mode 100644 ast/ferror.c
 create mode 100644 ast/ffitschan.c
 create mode 100644 ast/ffitstable.c
 create mode 100644 ast/ffluxframe.c
 create mode 100644 ast/fframe.c
 create mode 100644 ast/fframeset.c
 create mode 100644 ast/fgrismmap.c
 create mode 100644 ast/finterval.c
 create mode 100644 ast/fintramap.c
 create mode 100644 ast/fitschan.c
 create mode 100644 ast/fitschan.h
 create mode 100644 ast/fitstable.c
 create mode 100644 ast/fitstable.h
 create mode 100644 ast/fkeymap.c
 create mode 100644 ast/flutmap.c
 create mode 100644 ast/fluxframe.c
 create mode 100644 ast/fluxframe.h
 create mode 100644 ast/fmapping.c
 create mode 100644 ast/fmathmap.c
 create mode 100644 ast/fmatrixmap.c
 create mode 100644 ast/fnormmap.c
 create mode 100644 ast/fnullregion.c
 create mode 100644 ast/fobject.c
 create mode 100644 ast/fpcdmap.c
 create mode 100644 ast/fpermmap.c
 create mode 100644 ast/fplot.c
 create mode 100644 ast/fplot3d.c
 create mode 100644 ast/fpointlist.c
 create mode 100644 ast/fpolygon.c
 create mode 100644 ast/fpolymap.c
 create mode 100644 ast/fprism.c
 create mode 100644 ast/frame.c
 create mode 100644 ast/frame.h
 create mode 100644 ast/frameset.c
 create mode 100644 ast/frameset.h
 create mode 100644 ast/fratemap.c
 create mode 100644 ast/fregion.c
 create mode 100644 ast/fselectormap.c
 create mode 100644 ast/fshiftmap.c
 create mode 100644 ast/fskyframe.c
 create mode 100644 ast/fslamap.c
 create mode 100644 ast/fspecfluxframe.c
 create mode 100644 ast/fspecframe.c
 create mode 100644 ast/fspecmap.c
 create mode 100644 ast/fsphmap.c
 create mode 100644 ast/fstc.c
 create mode 100644 ast/fstccatalogentrylocation.c
 create mode 100644 ast/fstcobsdatalocation.c
 create mode 100644 ast/fstcresourceprofile.c
 create mode 100644 ast/fstcschan.c
 create mode 100644 ast/fstcsearchlocation.c
 create mode 100644 ast/fswitchmap.c
 create mode 100644 ast/ftable.c
 create mode 100644 ast/ftimeframe.c
 create mode 100644 ast/ftimemap.c
 create mode 100644 ast/ftranmap.c
 create mode 100644 ast/funitmap.c
 create mode 100644 ast/funitnormmap.c
 create mode 100644 ast/fwcsmap.c
 create mode 100644 ast/fwinmap.c
 create mode 100644 ast/fxmlchan.c
 create mode 100644 ast/fzoommap.c
 create mode 100644 ast/globals.c
 create mode 100644 ast/globals.h
 create mode 100644 ast/grf.h
 create mode 100644 ast/grf3d.c
 create mode 100644 ast/grf3d.h
 create mode 100644 ast/grf3d_pgplot.c
 create mode 100644 ast/grf_2.0.c
 create mode 100644 ast/grf_3.2.c
 create mode 100644 ast/grf_5.6.c
 create mode 100644 ast/grf_pgplot.c
 create mode 100644 ast/grismmap.c
 create mode 100644 ast/grismmap.h
 create mode 100644 ast/interval.c
 create mode 100644 ast/interval.h
 create mode 100644 ast/intramap.c
 create mode 100644 ast/intramap.h
 create mode 100644 ast/keymap.c
 create mode 100644 ast/keymap.h
 create mode 100644 ast/loader.c
 create mode 100644 ast/loader.h
 create mode 100644 ast/lutmap.c
 create mode 100644 ast/lutmap.h
 create mode 100644 ast/makeh
 create mode 100644 ast/mapping.c
 create mode 100644 ast/mapping.h
 create mode 100644 ast/mathmap.c
 create mode 100644 ast/mathmap.h
 create mode 100644 ast/matrixmap.c
 create mode 100644 ast/matrixmap.h
 create mode 100644 ast/memory.c
 create mode 100644 ast/memory.h
 create mode 100644 ast/normmap.c
 create mode 100644 ast/normmap.h
 create mode 100644 ast/nullregion.c
 create mode 100644 ast/nullregion.h
 create mode 100644 ast/object.c
 create mode 100644 ast/object.h
 create mode 100644 ast/object.h.in
 create mode 100644 ast/pal.h
 create mode 100644 ast/pal/pal.h
 create mode 100644 ast/pal/pal1sofa.h
 create mode 100644 ast/pal/palAddet.c
 create mode 100644 ast/pal/palAmpqk.c
 create mode 100644 ast/pal/palCaldj.c
 create mode 100644 ast/pal/palDat.c
 create mode 100644 ast/pal/palDe2h.c
 create mode 100644 ast/pal/palDeuler.c
 create mode 100644 ast/pal/palDh2e.c
 create mode 100644 ast/pal/palDjcal.c
 create mode 100644 ast/pal/palDmat.c
 create mode 100644 ast/pal/palDrange.c
 create mode 100644 ast/pal/palDs2tp.c
 create mode 100644 ast/pal/palDtp2s.c
 create mode 100644 ast/pal/palDtps2c.c
 create mode 100644 ast/pal/palDtt.c
 create mode 100644 ast/pal/palEcmat.c
 create mode 100644 ast/pal/palEqgal.c
 create mode 100644 ast/pal/palEtrms.c
 create mode 100644 ast/pal/palEvp.c
 create mode 100644 ast/pal/palFk45z.c
 create mode 100644 ast/pal/palFk524.c
 create mode 100644 ast/pal/palFk54z.c
 create mode 100644 ast/pal/palGaleq.c
 create mode 100644 ast/pal/palGalsup.c
 create mode 100644 ast/pal/palGeoc.c
 create mode 100644 ast/pal/palMappa.c
 create mode 100644 ast/pal/palMapqkz.c
 create mode 100644 ast/pal/palOne2One.c
 create mode 100644 ast/pal/palPrebn.c
 create mode 100644 ast/pal/palPrec.c
 create mode 100644 ast/pal/palPrenut.c
 create mode 100644 ast/pal/palPvobs.c
 create mode 100644 ast/pal/palRvgalc.c
 create mode 100644 ast/pal/palRvlg.c
 create mode 100644 ast/pal/palRvlsrd.c
 create mode 100644 ast/pal/palRvlsrk.c
 create mode 100644 ast/pal/palSubet.c
 create mode 100644 ast/pal/palSupgal.c
 create mode 100644 ast/pal/palmac.h
 create mode 100644 ast/pal2ast.h
 create mode 100644 ast/palwrap.c
 create mode 100644 ast/pcdmap.c
 create mode 100644 ast/pcdmap.h
 create mode 100644 ast/permmap.c
 create mode 100644 ast/permmap.h
 create mode 100644 ast/pg3d.h
 create mode 100644 ast/plot.c
 create mode 100644 ast/plot.h
 create mode 100644 ast/plot3d.c
 create mode 100644 ast/plot3d.h
 create mode 100644 ast/pointlist.c
 create mode 100644 ast/pointlist.h
 create mode 100644 ast/pointset.c
 create mode 100644 ast/pointset.h
 create mode 100644 ast/polygon.c
 create mode 100644 ast/polygon.h
 create mode 100644 ast/polymap.c
 create mode 100644 ast/polymap.h
 create mode 100644 ast/prism.c
 create mode 100644 ast/prism.h
 create mode 100644 ast/proj.c
 create mode 100644 ast/proj.h
 create mode 100644 ast/ratemap.c
 create mode 100644 ast/ratemap.h
 create mode 100644 ast/region.c
 create mode 100644 ast/region.h
 create mode 100644 ast/selectormap.c
 create mode 100644 ast/selectormap.h
 create mode 100644 ast/shiftmap.c
 create mode 100644 ast/shiftmap.h
 create mode 100644 ast/skyaxis.c
 create mode 100644 ast/skyaxis.h
 create mode 100644 ast/skyframe.c
 create mode 100644 ast/skyframe.h
 create mode 100644 ast/slamap.c
 create mode 100644 ast/slamap.h
 create mode 100644 ast/specfluxframe.c
 create mode 100644 ast/specfluxframe.h
 create mode 100644 ast/specframe.c
 create mode 100644 ast/specframe.h
 create mode 100644 ast/specmap.c
 create mode 100644 ast/specmap.h
 create mode 100644 ast/sphmap.c
 create mode 100644 ast/sphmap.h
 create mode 100644 ast/sst.sty
 create mode 100644 ast/starabbrev.sty
 create mode 100644 ast/starlink.cls
 create mode 100644 ast/starstyle.sty
 create mode 100644 ast/stc.c
 create mode 100644 ast/stc.h
 create mode 100644 ast/stccatalogentrylocation.c
 create mode 100644 ast/stccatalogentrylocation.h
 create mode 100644 ast/stcobsdatalocation.c
 create mode 100644 ast/stcobsdatalocation.h
 create mode 100644 ast/stcresourceprofile.c
 create mode 100644 ast/stcresourceprofile.h
 create mode 100644 ast/stcschan.c
 create mode 100644 ast/stcschan.h
 create mode 100644 ast/stcsearchlocation.c
 create mode 100644 ast/stcsearchlocation.h
 create mode 100644 ast/sun210.htx_tar
 create mode 100644 ast/sun210.pdf
 create mode 100644 ast/sun210.tex
 create mode 100644 ast/sun210_figures/cmpframe.pdf
 create mode 100644 ast/sun210_figures/complex.pdf
 create mode 100644 ast/sun210_figures/frames.pdf
 create mode 100644 ast/sun210_figures/frameset.pdf
 create mode 100644 ast/sun210_figures/fronta.pdf
 create mode 100644 ast/sun210_figures/fronta_bw.pdf
 create mode 100644 ast/sun210_figures/frontb.pdf
 create mode 100644 ast/sun210_figures/frontb_bw.pdf
 create mode 100644 ast/sun210_figures/frontc.pdf
 create mode 100644 ast/sun210_figures/frontc_bw.pdf
 create mode 100644 ast/sun210_figures/fsalign.pdf
 create mode 100644 ast/sun210_figures/fsconvert.pdf
 create mode 100644 ast/sun210_figures/fsexample.pdf
 create mode 100644 ast/sun210_figures/fsmerge.pdf
 create mode 100644 ast/sun210_figures/fsremap.pdf
 create mode 100644 ast/sun210_figures/gridplot.pdf
 create mode 100644 ast/sun210_figures/gridplot_bw.pdf
 create mode 100644 ast/sun210_figures/mapping.pdf
 create mode 100644 ast/sun210_figures/overgrid.pdf
 create mode 100644 ast/sun210_figures/overgrid_bw.pdf
 create mode 100644 ast/sun210_figures/parallel.pdf
 create mode 100644 ast/sun210_figures/series.pdf
 create mode 100644 ast/sun210_figures/simpexamp.pdf
 create mode 100644 ast/sun211.htx_tar
 create mode 100644 ast/sun211.pdf
 create mode 100644 ast/sun211.tex
 create mode 100644 ast/sun211_figures/cmpframe.pdf
 create mode 100644 ast/sun211_figures/complex.pdf
 create mode 100644 ast/sun211_figures/frames.pdf
 create mode 100644 ast/sun211_figures/frameset.pdf
 create mode 100644 ast/sun211_figures/fronta.pdf
 create mode 100644 ast/sun211_figures/fronta_bw.pdf
 create mode 100644 ast/sun211_figures/frontb.pdf
 create mode 100644 ast/sun211_figures/frontb_bw.pdf
 create mode 100644 ast/sun211_figures/frontc.pdf
 create mode 100644 ast/sun211_figures/frontc_bw.pdf
 create mode 100644 ast/sun211_figures/fsalign.pdf
 create mode 100644 ast/sun211_figures/fsconvert.pdf
 create mode 100644 ast/sun211_figures/fsexample.pdf
 create mode 100644 ast/sun211_figures/fsmerge.pdf
 create mode 100644 ast/sun211_figures/fsremap.pdf
 create mode 100644 ast/sun211_figures/gridplot.pdf
 create mode 100644 ast/sun211_figures/gridplot_bw.pdf
 create mode 100644 ast/sun211_figures/mapping.pdf
 create mode 100644 ast/sun211_figures/overgrid.pdf
 create mode 100644 ast/sun211_figures/overgrid_bw.pdf
 create mode 100644 ast/sun211_figures/parallel.pdf
 create mode 100644 ast/sun211_figures/series.pdf
 create mode 100644 ast/sun211_figures/simpexamp.pdf
 create mode 100644 ast/switchmap.c
 create mode 100644 ast/switchmap.h
 create mode 100644 ast/table.c
 create mode 100644 ast/table.h
 create mode 100644 ast/timeframe.c
 create mode 100644 ast/timeframe.h
 create mode 100644 ast/timemap.c
 create mode 100644 ast/timemap.h
 create mode 100644 ast/tpn.c
 create mode 100644 ast/tranmap.c
 create mode 100644 ast/tranmap.h
 create mode 100644 ast/unit.c
 create mode 100644 ast/unit.h
 create mode 100644 ast/unitmap.c
 create mode 100644 ast/unitmap.h
 create mode 100644 ast/unitnormmap.c
 create mode 100644 ast/unitnormmap.h
 create mode 100644 ast/version.h
 create mode 100644 ast/wcsmap.c
 create mode 100644 ast/wcsmap.h
 create mode 100644 ast/wcsmath.h
 create mode 100644 ast/wcstrig.c
 create mode 100644 ast/wcstrig.h
 create mode 100644 ast/winmap.c
 create mode 100644 ast/winmap.h
 create mode 100644 ast/xml.c
 create mode 100644 ast/xml.h
 create mode 100644 ast/xmlchan.c
 create mode 100644 ast/xmlchan.h
 create mode 100644 ast/zoommap.c
 create mode 100644 ast/zoommap.h

diff --git a/ast/AST_ERR b/ast/AST_ERR
new file mode 100644
index 0000000..a7ecd20
--- /dev/null
+++ b/ast/AST_ERR
@@ -0,0 +1,639 @@
+* FORTRAN error include file for facility AST (Code 1521)
+* Generated by the MESSGEN utility
+
+*     attribute getting error
+      INTEGER AST__ATGER
+      PARAMETER ( AST__ATGER = 233933154 )
+
+*     attribute setting error
+      INTEGER AST__ATSER
+      PARAMETER ( AST__ATSER = 233933162 )
+
+*     attribute value invalid
+      INTEGER AST__ATTIN
+      PARAMETER ( AST__ATTIN = 233933170 )
+
+*     axis index invalid
+      INTEGER AST__AXIIN
+      PARAMETER ( AST__AXIIN = 233933178 )
+
+*     bad attribute name
+      INTEGER AST__BADAT
+      PARAMETER ( AST__BADAT = 233933186 )
+
+*     zero-sized box given
+      INTEGER AST__BADBX
+      PARAMETER ( AST__BADBX = 233933194 )
+
+*     bad input data
+      INTEGER AST__BADIN
+      PARAMETER ( AST__BADIN = 233933202 )
+
+*     bad number of input coordinates
+      INTEGER AST__BADNI
+      PARAMETER ( AST__BADNI = 233933210 )
+
+*     bad number of output coordinates
+      INTEGER AST__BADNO
+      PARAMETER ( AST__BADNO = 233933218 )
+
+*     PolyMap contains illegal power value
+      INTEGER AST__BADPW
+      PARAMETER ( AST__BADPW = 233933226 )
+
+*     ShiftMap contains no shift information
+      INTEGER AST__BADSM
+      PARAMETER ( AST__BADSM = 233933234 )
+
+*     WinMap contains no bounds information
+      INTEGER AST__BADWM
+      PARAMETER ( AST__BADWM = 233933242 )
+
+*     bad break index
+      INTEGER AST__BDBRK
+      PARAMETER ( AST__BDBRK = 233933250 )
+
+*     bad field specifier
+      INTEGER AST__BDFMT
+      PARAMETER ( AST__BDFMT = 233933258 )
+
+*     invalid FITS keyword value found
+      INTEGER AST__BDFTS
+      PARAMETER ( AST__BDFTS = 233933266 )
+
+*     inappropriate Object supplied
+      INTEGER AST__BDOBJ
+      PARAMETER ( AST__BDOBJ = 233933274 )
+
+*     wrong number of clipping axes
+      INTEGER AST__CLPAX
+      PARAMETER ( AST__CLPAX = 233933282 )
+
+*     range of coordinates invalid
+      INTEGER AST__CORNG
+      PARAMETER ( AST__CORNG = 233933290 )
+
+*     too many breaks in a curve
+      INTEGER AST__CVBRK
+      PARAMETER ( AST__CVBRK = 233933298 )
+
+*     array dimensions invalid
+      INTEGER AST__DIMIN
+      PARAMETER ( AST__DIMIN = 233933306 )
+
+*     date/time error
+      INTEGER AST__DTERR
+      PARAMETER ( AST__DTERR = 233933314 )
+
+*     invalid use of astEnd
+      INTEGER AST__ENDIN
+      PARAMETER ( AST__ENDIN = 233933322 )
+
+*     end of input Channel encountered
+      INTEGER AST__EOCHN
+      PARAMETER ( AST__EOCHN = 233933330 )
+
+*     attempt to export Object pointer from level zero
+      INTEGER AST__EXPIN
+      PARAMETER ( AST__EXPIN = 233933338 )
+
+*     corrupted FitsChan supplied
+      INTEGER AST__FCRPT
+      PARAMETER ( AST__FCRPT = 233933346 )
+
+*     error while formatting coordinate value
+      INTEGER AST__FMTER
+      PARAMETER ( AST__FMTER = 233933354 )
+
+*     Frame index invalid
+      INTEGER AST__FRMIN
+      PARAMETER ( AST__FRMIN = 233933362 )
+
+*     FrameSet invalid
+      INTEGER AST__FRSIN
+      PARAMETER ( AST__FRSIN = 233933370 )
+
+*     cannot convert FITS data value type
+      INTEGER AST__FTCNV
+      PARAMETER ( AST__FTCNV = 233933378 )
+
+*     low level graphics error
+      INTEGER AST__GRFER
+      PARAMETER ( AST__GRFER = 233933386 )
+
+*     invalid Handle
+      INTEGER AST__INHAN
+      PARAMETER ( AST__INHAN = 233933394 )
+
+*     incompatible numbers of coordinates
+      INTEGER AST__INNCO
+      PARAMETER ( AST__INNCO = 233933402 )
+
+*     internal programming error
+      INTEGER AST__INTER
+      PARAMETER ( AST__INTER = 233933410 )
+
+*     incompatible transformation directions
+      INTEGER AST__INTRD
+      PARAMETER ( AST__INTRD = 233933418 )
+
+*     circular dependency between KeyMaps
+      INTEGER AST__KYCIR
+      PARAMETER ( AST__KYCIR = 233933426 )
+
+*     class loader error
+      INTEGER AST__LDERR
+      PARAMETER ( AST__LDERR = 233933434 )
+
+*     invalid lookup table increment
+      INTEGER AST__LUTII
+      PARAMETER ( AST__LUTII = 233933442 )
+
+*     invalid number of lookup table elements
+      INTEGER AST__LUTIN
+      PARAMETER ( AST__LUTIN = 233933450 )
+
+*     requested memory size invalid
+      INTEGER AST__MEMIN
+      PARAMETER ( AST__MEMIN = 233933458 )
+
+*     not a 2d or 3d MatrixMap
+      INTEGER AST__MTR23
+      PARAMETER ( AST__MTR23 = 233933466 )
+
+*     null rotation axis supplied
+      INTEGER AST__MTRAX
+      PARAMETER ( AST__MTRAX = 233933474 )
+
+*     bad matrix shapes for multiplication
+      INTEGER AST__MTRML
+      PARAMETER ( AST__MTRML = 233933482 )
+
+*     null matrix supplied
+      INTEGER AST__MTRMT
+      PARAMETER ( AST__MTRMT = 233933490 )
+
+*     number of axes invalid
+      INTEGER AST__NAXIN
+      PARAMETER ( AST__NAXIN = 233933498 )
+
+*     number of characters invalid
+      INTEGER AST__NCHIN
+      PARAMETER ( AST__NCHIN = 233933506 )
+
+*     number of coordinates invalid
+      INTEGER AST__NCOIN
+      PARAMETER ( AST__NCOIN = 233933514 )
+
+*     number of coordinates per point invalid
+      INTEGER AST__NCPIN
+      PARAMETER ( AST__NCPIN = 233933522 )
+
+*     number of array elements invalid
+      INTEGER AST__NELIN
+      PARAMETER ( AST__NELIN = 233933530 )
+
+*     number of output coordinates too small
+      INTEGER AST__NOCTS
+      PARAMETER ( AST__NOCTS = 233933538 )
+
+*     transformation not defined
+      INTEGER AST__NODEF
+      PARAMETER ( AST__NODEF = 233933546 )
+
+*     required FITS keywords missing
+      INTEGER AST__NOFTS
+      PARAMETER ( AST__NOFTS = 233933554 )
+
+*     unable to allocate memory
+      INTEGER AST__NOMEM
+      PARAMETER ( AST__NOMEM = 233933562 )
+
+*     number of output points too small
+      INTEGER AST__NOPTS
+      PARAMETER ( AST__NOPTS = 233933570 )
+
+*     attribute is read-only
+      INTEGER AST__NOWRT
+      PARAMETER ( AST__NOWRT = 233933578 )
+
+*     number of points invalid
+      INTEGER AST__NPTIN
+      PARAMETER ( AST__NPTIN = 233933586 )
+
+*     Object invalid
+      INTEGER AST__OBJIN
+      PARAMETER ( AST__OBJIN = 233933594 )
+
+*     invalid Plot option
+      INTEGER AST__OPT
+      PARAMETER ( AST__OPT = 233933602 )
+
+*     points data structure invalid
+      INTEGER AST__PDSIN
+      PARAMETER ( AST__PDSIN = 233933610 )
+
+*     no numerical labels can be produced
+      INTEGER AST__PLFMT
+      PARAMETER ( AST__PLFMT = 233933618 )
+
+*     permutation invalid
+      INTEGER AST__PRMIN
+      PARAMETER ( AST__PRMIN = 233933626 )
+
+*     pointer invalid
+      INTEGER AST__PTRIN
+      PARAMETER ( AST__PTRIN = 233933634 )
+
+*     range of points invalid
+      INTEGER AST__PTRNG
+      PARAMETER ( AST__PTRNG = 233933642 )
+
+*     read error
+      INTEGER AST__RDERR
+      PARAMETER ( AST__RDERR = 233933650 )
+
+*     invalid or corrupted Region structure supplied
+      INTEGER AST__REGIN
+      PARAMETER ( AST__REGIN = 233933658 )
+
+*     invalid attempt to remove last Frame
+      INTEGER AST__REMIN
+      PARAMETER ( AST__REMIN = 233933666 )
+
+*     sky coordinate system invalid
+      INTEGER AST__SCSIN
+      PARAMETER ( AST__SCSIN = 233933674 )
+
+*     axis selection invalid
+      INTEGER AST__SELIN
+      PARAMETER ( AST__SELIN = 233933682 )
+
+*     bad SLALIB transformation type
+      INTEGER AST__SLAIN
+      PARAMETER ( AST__SLAIN = 233933690 )
+
+*     coordinate transformation not defined
+      INTEGER AST__TRNND
+      PARAMETER ( AST__TRNND = 233933698 )
+
+*     unmatched quotes
+      INTEGER AST__UNMQT
+      PARAMETER ( AST__UNMQT = 233933706 )
+
+*     valid area too small
+      INTEGER AST__VSMAL
+      PARAMETER ( AST__VSMAL = 233933714 )
+
+*     non-existent longitude or latitude axis
+      INTEGER AST__WCSAX
+      PARAMETER ( AST__WCSAX = 233933722 )
+
+*     too few mapping coordinates
+      INTEGER AST__WCSNC
+      PARAMETER ( AST__WCSNC = 233933730 )
+
+*     invalid projection parameters
+      INTEGER AST__WCSPA
+      PARAMETER ( AST__WCSPA = 233933738 )
+
+*     unknown projection type
+      INTEGER AST__WCSTY
+      PARAMETER ( AST__WCSTY = 233933746 )
+
+*     too many Objects in use at once
+      INTEGER AST__XSOBJ
+      PARAMETER ( AST__XSOBJ = 233933754 )
+
+*     zoom factor invalid
+      INTEGER AST__ZOOMI
+      PARAMETER ( AST__ZOOMI = 233933762 )
+
+*     bad coordinate index
+      INTEGER AST__BADCI
+      PARAMETER ( AST__BADCI = 233933770 )
+
+*     FrameSet integrity lost
+      INTEGER AST__ILOST
+      PARAMETER ( AST__ILOST = 233933778 )
+
+*     error in IntraMap transformation function
+      INTEGER AST__ITFER
+      PARAMETER ( AST__ITFER = 233933786 )
+
+*     IntraMap transformation function name invalid
+      INTEGER AST__ITFNI
+      PARAMETER ( AST__ITFNI = 233933794 )
+
+*     Mapping bounding box not found
+      INTEGER AST__MBBNF
+      PARAMETER ( AST__MBBNF = 233933802 )
+
+*     multiple registration of IntraMap transformation function
+      INTEGER AST__MRITF
+      PARAMETER ( AST__MRITF = 233933810 )
+
+*     Object class unknown
+      INTEGER AST__OCLUK
+      PARAMETER ( AST__OCLUK = 233933818 )
+
+*     error while unformatting a coordinate value
+      INTEGER AST__UNFER
+      PARAMETER ( AST__UNFER = 233933826 )
+
+*     unregistered IntraMap transformation function
+      INTEGER AST__URITF
+      PARAMETER ( AST__URITF = 233933834 )
+
+*     grid bounds invalid
+      INTEGER AST__GBDIN
+      PARAMETER ( AST__GBDIN = 233933842 )
+
+*     number of grid dimensions invalid
+      INTEGER AST__NGDIN
+      PARAMETER ( AST__NGDIN = 233933850 )
+
+*     positional accuracy tolerance invalid
+      INTEGER AST__PATIN
+      PARAMETER ( AST__PATIN = 233933858 )
+
+*     sub-pixel interpolation scheme invalid
+      INTEGER AST__SISIN
+      PARAMETER ( AST__SISIN = 233933866 )
+
+*     scale size in pixels invalid
+      INTEGER AST__SSPIN
+      PARAMETER ( AST__SSPIN = 233933874 )
+
+*     error in user-supplied sub-pixel interpolation function
+      INTEGER AST__UINER
+      PARAMETER ( AST__UINER = 233933882 )
+
+*     error in user-supplied 1-d sub-pixel interpolation kernel
+      INTEGER AST__UK1ER
+      PARAMETER ( AST__UK1ER = 233933890 )
+
+*     invalid comma in expression
+      INTEGER AST__COMIN
+      PARAMETER ( AST__COMIN = 233933898 )
+
+*     invalid constant in expression
+      INTEGER AST__CONIN
+      PARAMETER ( AST__CONIN = 233933906 )
+
+*     duplicate variable name
+      INTEGER AST__DUVAR
+      PARAMETER ( AST__DUVAR = 233933914 )
+
+*     invalid number of transformation functions
+      INTEGER AST__INNTF
+      PARAMETER ( AST__INNTF = 233933922 )
+
+*     missing or invalid operand in expression
+      INTEGER AST__MIOPA
+      PARAMETER ( AST__MIOPA = 233933930 )
+
+*     missing or invalid operator in expression
+      INTEGER AST__MIOPR
+      PARAMETER ( AST__MIOPR = 233933938 )
+
+*     missing variable name
+      INTEGER AST__MISVN
+      PARAMETER ( AST__MISVN = 233933946 )
+
+*     missing left parenthesis in expression
+      INTEGER AST__MLPAR
+      PARAMETER ( AST__MLPAR = 233933954 )
+
+*     missing right parenthesis in expression
+      INTEGER AST__MRPAR
+      PARAMETER ( AST__MRPAR = 233933962 )
+
+*     missing right hand side in function
+      INTEGER AST__NORHS
+      PARAMETER ( AST__NORHS = 233933970 )
+
+*     undefined variable or function in expression
+      INTEGER AST__UDVOF
+      PARAMETER ( AST__UDVOF = 233933978 )
+
+*     variable name invalid
+      INTEGER AST__VARIN
+      PARAMETER ( AST__VARIN = 233933986 )
+
+*     wrong number of function arguments in expression
+      INTEGER AST__WRNFA
+      PARAMETER ( AST__WRNFA = 233933994 )
+
+*     invalid units specification
+      INTEGER AST__BADUN
+      PARAMETER ( AST__BADUN = 233934002 )
+
+*     no rest frequency is defined
+      INTEGER AST__NORSF
+      PARAMETER ( AST__NORSF = 233934010 )
+
+*     no standard of rest is defined
+      INTEGER AST__NOSOR
+      PARAMETER ( AST__NOSOR = 233934018 )
+
+*     invalid SpecMap
+      INTEGER AST__SPCIN
+      PARAMETER ( AST__SPCIN = 233934026 )
+
+*     invalid XML name or prefix
+      INTEGER AST__XMLNM
+      PARAMETER ( AST__XMLNM = 233934034 )
+
+*     invalid XML comment text
+      INTEGER AST__XMLCM
+      PARAMETER ( AST__XMLCM = 233934042 )
+
+*     invalid XML processing instruction target text
+      INTEGER AST__XMLPT
+      PARAMETER ( AST__XMLPT = 233934050 )
+
+*     invalid XML content item index
+      INTEGER AST__XMLIT
+      PARAMETER ( AST__XMLIT = 233934058 )
+
+*     supplied XML document is not well formed
+      INTEGER AST__XMLWF
+      PARAMETER ( AST__XMLWF = 233934066 )
+
+*     Range of log axis scale includes zero
+      INTEGER AST__ZERAX
+      PARAMETER ( AST__ZERAX = 233934074 )
+
+*     Invalid parameters for offset sky coordinate system
+      INTEGER AST__BADOC
+      PARAMETER ( AST__BADOC = 233934082 )
+
+*     error getting a named value from a KeyMap
+      INTEGER AST__MPGER
+      PARAMETER ( AST__MPGER = 233934090 )
+
+*     invalid integer index supplied for a KeyMap entry
+      INTEGER AST__MPIND
+      PARAMETER ( AST__MPIND = 233934098 )
+
+*     region cannot be re-centred
+      INTEGER AST__REGCN
+      PARAMETER ( AST__REGCN = 233934106 )
+
+*     attribute has no usable value
+      INTEGER AST__NOVAL
+      PARAMETER ( AST__NOVAL = 233934114 )
+
+*     incompatible time scales
+      INTEGER AST__INCTS
+      PARAMETER ( AST__INCTS = 233934122 )
+
+*     invalid TimeMap
+      INTEGER AST__TIMIN
+      PARAMETER ( AST__TIMIN = 233934130 )
+
+*     cannot use supplied AstroCoords info
+      INTEGER AST__STCKEY
+      PARAMETER ( AST__STCKEY = 233934138 )
+
+*     invalid AstroCoords index
+      INTEGER AST__STCIND
+      PARAMETER ( AST__STCIND = 233934146 )
+
+*     cannot conserve flux whilst resampling an array of data
+      INTEGER AST__CNFLX
+      PARAMETER ( AST__CNFLX = 233934154 )
+
+*     Unknown AST tuning parameter name supplied
+      INTEGER AST__TUNAM
+      PARAMETER ( AST__TUNAM = 233934162 )
+
+*     Bad value supplied for a public function parameter
+      INTEGER AST__BDPAR
+      PARAMETER ( AST__BDPAR = 233934170 )
+
+*     Supplied FrameSet does not contain any independent axes
+      INTEGER AST__3DFSET
+      PARAMETER ( AST__3DFSET = 233934178 )
+
+*     Attempt to delete original Plot3D base Frame
+      INTEGER AST__PXFRRM
+      PARAMETER ( AST__PXFRRM = 233934186 )
+
+*     Illegal syntax for string substitution template
+      INTEGER AST__BADSUB
+      PARAMETER ( AST__BADSUB = 233934194 )
+
+*     Incompatible flags for re-sampling or re-binning
+      INTEGER AST__BADFLG
+      PARAMETER ( AST__BADFLG = 233934202 )
+
+*     Error locking or unlocking an AST Object
+      INTEGER AST__LCKERR
+      PARAMETER ( AST__LCKERR = 233934210 )
+
+*     FITS keyword had undefined value
+      INTEGER AST__FUNDEF
+      PARAMETER ( AST__FUNDEF = 233934218 )
+
+*     invalid integer index supplied for a KeyMap vector element
+      INTEGER AST__MPVIN
+      PARAMETER ( AST__MPVIN = 233934226 )
+
+*     operation specifier invalid
+      INTEGER AST__OPRIN
+      PARAMETER ( AST__OPRIN = 233934234 )
+
+*     no inside point found
+      INTEGER AST__NONIN
+      PARAMETER ( AST__NONIN = 233934242 )
+
+*     requested key not found in KeyMap
+      INTEGER AST__MPKER
+      PARAMETER ( AST__MPKER = 233934250 )
+
+*     error putting a named value into a KeyMap
+      INTEGER AST__MPPER
+      PARAMETER ( AST__MPPER = 233934258 )
+
+*     Attempt made to add an entry to a locked KeyMap
+      INTEGER AST__BADKEY
+      PARAMETER ( AST__BADKEY = 233934266 )
+
+*     Bad data type
+      INTEGER AST__BADTYP
+      PARAMETER ( AST__BADTYP = 233934274 )
+
+*     Column already exists with different properties
+      INTEGER AST__OLDCOL
+      PARAMETER ( AST__OLDCOL = 233934282 )
+
+*     Bad null value for a FITS table column
+      INTEGER AST__BADNULL
+      PARAMETER ( AST__BADNULL = 233934290 )
+
+*     Key string is too long
+      INTEGER AST__BIGKEY
+      PARAMETER ( AST__BIGKEY = 233934298 )
+
+*     No such column exists in the table
+      INTEGER AST__BADCOL
+      PARAMETER ( AST__BADCOL = 233934306 )
+
+*     Table is too large
+      INTEGER AST__BIGTAB
+      PARAMETER ( AST__BIGTAB = 233934314 )
+
+*     Invalid array size
+      INTEGER AST__BADSIZ
+      PARAMETER ( AST__BADSIZ = 233934322 )
+
+*     Error reading WCS from FITS binary table
+      INTEGER AST__BADTAB
+      PARAMETER ( AST__BADTAB = 233934330 )
+
+*     Cannot access FITS binary table
+      INTEGER AST__NOTAB
+      PARAMETER ( AST__NOTAB = 233934338 )
+
+*     Error in levmar Levenberg-Marquardt code
+      INTEGER AST__LEVMAR
+      PARAMETER ( AST__LEVMAR = 233934346 )
+
+*     Fit failed
+      INTEGER AST__NOFIT
+      PARAMETER ( AST__NOFIT = 233934354 )
+
+*     A transformation generated one or more NaN values
+      INTEGER AST__ISNAN
+      PARAMETER ( AST__ISNAN = 233934362 )
+
+*     write error
+      INTEGER AST__WRERR
+      PARAMETER ( AST__WRERR = 233934370 )
+
+*     Bad variant Mapping name
+      INTEGER AST__BDVNM
+      PARAMETER ( AST__BDVNM = 233934378 )
+
+*     Attempt to add a variant Mapping to a mirror Frame
+      INTEGER AST__MIRRO
+      PARAMETER ( AST__MIRRO = 233934386 )
+
+*     Error in cminpack Levenberg-Marquardt code
+      INTEGER AST__MNPCK
+      PARAMETER ( AST__MNPCK = 233934394 )
+
+*     Supplied array has too many pixels
+      INTEGER AST__EXSPIX
+      PARAMETER ( AST__EXSPIX = 233934402 )
+
+*     No mapping found between coordinate systems
+      INTEGER AST__NOCNV
+      PARAMETER ( AST__NOCNV = 233934410 )
+
+*     Attempt to change an immutable attribute
+      INTEGER AST__IMMUT
+      PARAMETER ( AST__IMMUT = 233934418 )
+
diff --git a/ast/COPYING b/ast/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/ast/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/ast/COPYING.LESSER b/ast/COPYING.LESSER
new file mode 100644
index 0000000..65c5ca8
--- /dev/null
+++ b/ast/COPYING.LESSER
@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/ast/COPYING.LIB b/ast/COPYING.LIB
new file mode 100644
index 0000000..eb685a5
--- /dev/null
+++ b/ast/COPYING.LIB
@@ -0,0 +1,481 @@
+		  GNU LIBRARY GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+                    675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL.  It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it.  You can use it for
+your libraries, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the library, or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library.  If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software.  To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+  Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs.  This
+license, the GNU Library General Public License, applies to certain
+designated libraries.  This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+  The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it.  Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program.  However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+  Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries.  We
+concluded that weaker conditions might promote sharing better.
+
+  However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves.  This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them.  (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.)  The hope is that this
+will lead to faster development of free libraries.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+  Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+
+		  GNU LIBRARY GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License").  Each licensee is
+addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    c) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    d) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Library General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+     Appendix: How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public
+    License along with this library; if not, write to the Free
+    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/ast/Ers.h b/ast/Ers.h
new file mode 100644
index 0000000..a66fa82
--- /dev/null
+++ b/ast/Ers.h
@@ -0,0 +1,245 @@
+#ifndef ERSINC
+#define ERSINC
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/*			E r s . h
+
+ *  Module name:
+      Ers.h
+
+ *  Function:
+	Function header for the Ers routines
+
+ *  Description:
+     Should be included by all files using the Ers routines.
+
+ *  Language:
+      C
+
+ *  Support: Tony Farrell, AAO
+
+ *  Copyright (c) Anglo-Australian Telescope Board, 1995.
+    Not to be used for commercial purposes without AATB permission.
+
+ *     @(#) $Id: Ers.h,v 1.3 2005/05/17 22:21:19 rkackley Exp $
+
+
+ *  History:
+      04-Aug-1992 - TJF - Original version
+      25-Sep-1992 - TJF - Update comments
+      06-Oct-1992 - TJF - Rewrite for complete Ers package.
+      04-Aug-1993 - TJF - maxsize argument to ErsSPrintf needs a type
+      28-Sep-1993 - TJF - Use GNUC attribute to flag the ers calls
+			  as printf style.  Use drama.h for configuration
+			  stuff.
+
+      29-Sep-1993 - TJF - Add Sccs id
+      06-Mar-1994 - TJF - Add Task Id stuff.
+      05-Feb-1995 - TJF - Add BROADCAST flag
+      06-Aug-1996 - TJF - Add const to strings arguments of ErsVSPrintf
+      30-May-2001 - TJF - Add ErsSetLogRoutine.
+      15-Jun-2001 - TJF - Add ErsGetTaskId.
+      {@change entry@}
+
+
+ */
+
+#ifdef ERS_STANDALONE
+/*
+ *  DRAMA macros and types used by Ers.  They are defined here when we
+ *  are building ers standalone.
+ */
+#define DVOID void
+#define DVOIDP void *
+#define DPUBLIC extern
+#define DPRIVATE static
+#define DCONSTV const
+#define DCONSTR const
+#define STATUS__OK 0
+#define DPROTOTYPES_OK
+#define DFLOAT_OK
+#define DCONST_I
+typedef long int StatusType;
+#define StatusOkP(_value_)  (*(_value_) == STATUS__OK)
+#else
+/*
+ *  Include the drama.h file for configuration macros.
+ */
+
+#include "drama.h"
+
+#include "status.h"		/* For StatusType etc	*/
+#endif
+
+/*
+ *  Get around problems in Sparc include files, they are not ANSI compatible
+ */
+#if defined(__sparc__) && !defined(sparc)
+#define sparc 1
+#endif
+
+/*
+ *  Floating point stuff.  Only used in ErsVSPrintf.
+ */
+#ifdef DFLOAT_OK
+/*
+ *  These values taken from bsd floatio.h
+ */
+#    define ERS_MAXEXP 308
+#    define ERS_MAXFRACT 39
+#endif
+
+/*
+ *  Constants
+ */
+
+#define ERS_C_LEN 200		/* Maximum length of reported messages */
+#define ERS_C_MAXMSG 30		/* Maximum number of reported messages */
+
+#define ERS_M_NOFMT (1<<0)	/* Message flag masks		*/
+#define ERS_M_HIGHLIGHT (1<<1)
+#define ERS_M_BELL (1<<2)
+#define ERS_M_ALARM (1<<3)
+#define ERS_M_BROADCAST (1<<4)
+
+
+/*
+ *  This structure is used to store details of a message
+ */
+typedef struct {
+	    StatusType mesStatus;	    /* Status of message    */
+	    unsigned int context;	    /* Context message was written at */
+	    int    flags;		    /* Message flags	    */
+	    char   message[ERS_C_LEN];	    /* The formated message */
+	    } ErsMessageType;
+
+typedef DVOIDP ErsTaskIdType;
+
+#ifdef DPROTOTYPES_OK
+/*
+ *  This type is that required for log routines - called on each call to
+ *  ErsRep with details of a single message.
+ *
+ *  The argument "logArg" is a user value supplied when ErsStart is called.
+ *  It enables the user to pass any appropriate value to the log routine.
+ */
+typedef DVOID (*ErsLogRoutineType)(
+		    DVOIDP logArg,	    /* Supplied to ErsStart  */
+		    DCONSTV ErsMessageType * message,/* The message  */
+		    StatusType * status);
+/*
+ *  The type is that requried for the output routine - called to output
+ *  the messages to the user.  An array of message may be output by one
+ *  call, with count being the number of message to output.
+ *
+ *  The argument "outArg" is a user value supplied when ErsStart is called.
+ *  It enables the user to pass any appropriate value to the log routine.
+ */
+typedef DVOID (*ErsOutRoutineType)(
+		    DVOIDP outArg,	    /* Supplied to ErsStart */
+		    unsigned int count,	    /* Number of messages   */
+		    DCONSTV ErsMessageType messages[],/* Array of messages */
+		    StatusType * status);
+
+
+/*
+ *  Function prototypes.
+ *
+ *
+ *  We can't define these prorotype in the Ers main module unless we have
+ *  stdarg.h.
+ */
+#if !defined(ERS_MAIN) || defined(DSTDARG__OK)
+    DPUBLIC DVOID ErsRep(DCONSTV int flags, StatusType * status,
+		       DCONSTV char * string , ...)
+#ifdef __GNUC__
+	__attribute__ ((format (printf, 3, 4)))
+#endif
+		;
+    DPUBLIC DVOID ErsOut(DCONSTV int flags, StatusType * status,
+		          DCONSTV char * string, ...)
+#ifdef __GNUC__
+	__attribute__ ((format (printf, 3, 4)))
+#endif
+		;
+    DPUBLIC int  ErsSPrintf(DCONSTV int maxLength,
+			char *string,
+			DCONSTV char * fmt,...)
+#ifdef __GNUC__
+	__attribute__ ((format (printf, 3, 4)))
+#endif
+		;
+
+#endif /* DSTDARG_OK */
+
+DPUBLIC ErsTaskIdType ErsStart(
+		ErsOutRoutineType outRoutine,
+		DVOIDP outArg,
+		ErsLogRoutineType logRoutine,
+		DVOIDP logArg,
+		StatusType * status);
+DPUBLIC DVOID ErsStop(StatusType * status);
+DPUBLIC DVOID ErsPush(void);
+DPUBLIC DVOID ErsAnnul(StatusType * status);
+DPUBLIC DVOID ErsFlush(StatusType * status);
+DPUBLIC DVOID ErsClear(StatusType * status);
+DPUBLIC DVOID ErsPop(void);
+DPUBLIC DVOID ErsSetLogRoutine(
+    ErsLogRoutineType logRoutine,
+    DVOIDP logArg,
+    ErsLogRoutineType *oldLogRoutine,
+    DVOIDP *oldLogArg,
+    StatusType * status);
+
+DPUBLIC ErsTaskIdType ErsGetTaskId(StatusType *status);
+DPUBLIC DVOID ErsEnableTask(ErsTaskIdType TaskId,
+			    ErsTaskIdType * SavedTaskId);
+DPUBLIC DVOID ErsRestoreTask(ErsTaskIdType TaskId);
+
+
+#ifdef DSTDARG_OK
+#   include <stdarg.h>
+#else
+#   include <varargs.h>
+#endif
+DPUBLIC int ErsVSPrintf(
+		 int maxLength,
+		 char *string ,
+		 DCONSTV char * fmt0,
+		 va_list ap);
+#else
+/* Don't use prorotypes */
+typedef DVOID (*ErsLogRoutineType)();
+typedef DVOID (*ErsOutRoutineType)();
+
+DPUBLIC DVOID ErsRep();
+DPUBLIC DVOID ErsOut();
+
+DPUBLIC DVOID ErsStart();
+DPUBLIC DVOID ErsStop();
+DPUBLIC DVOID ErsPush();
+DPUBLIC DVOID ErsPop();
+DPUBLIC DVOID ErsAnnul();
+DPUBLIC DVOID ErsFlush();
+DPUBLIC DVOID ErsClear();
+DPUBLIC DVOID ErsSetLogRoutine();
+DPUBLIC ErsTaskIdType ErsGetTaskId();
+
+DPUBLIC int ErsVSPrintf();
+DPUBLIC int ErsSPrintf();
+
+DPUBLIC DVOID ErsEnableTask();
+DPUBLIC DVOID ErsRestoreTask();
+
+
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/ast/GRF_PAR b/ast/GRF_PAR
new file mode 100644
index 0000000..0c1d9aa
--- /dev/null
+++ b/ast/GRF_PAR
@@ -0,0 +1,124 @@
+*+
+*  Name:
+*     GRF_PAR
+
+*  Purpose:
+*     Define the constants needed to implement Fortran GRF routines.
+
+*  Language:
+*     Fortran 77
+
+*  Type of Module:
+*     Include file.
+
+*  Description:
+*     This file contains definitions which are required by Fortran 77
+*     programs which implement their own grf routines (routines for
+*     drawing graphics primitive used by the AST Plot class).
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     13-JUN-2001 (DSB):
+*        Original version.
+*-
+
+*  Values identifying different graphics attributes.
+      INTEGER GRF__STYLE
+      PARAMETER ( GRF__STYLE  = 0 )
+
+      INTEGER GRF__WIDTH
+      PARAMETER ( GRF__WIDTH  = 1 )
+
+      INTEGER GRF__SIZE
+      PARAMETER ( GRF__SIZE   = 2 )
+
+      INTEGER GRF__FONT
+      PARAMETER ( GRF__FONT   = 3 )
+
+      INTEGER GRF__COLOUR
+      PARAMETER ( GRF__COLOUR = 4 )
+
+*  Values identifying different graphics primatives.
+      INTEGER GRF__TEXT
+      PARAMETER ( GRF__TEXT = 0 )
+
+      INTEGER GRF__LINE
+      PARAMETER ( GRF__LINE = 1 )
+
+      INTEGER GRF__MARK
+      PARAMETER ( GRF__MARK = 2 )
+
+*  The number of different graphics attributes.
+      INTEGER GRF__NATTR
+      PARAMETER ( GRF__NATTR = 5 )
+
+*  Values identifying capabilities.
+      INTEGER GRF__ESC
+      PARAMETER ( GRF__ESC = 0 )
+
+      INTEGER GRF__MJUST
+      PARAMETER ( GRF__MJUST = 1 )
+
+      INTEGER GRF__SCALES
+      PARAMETER ( GRF__SCALES = 2 )
+
+* Values identifying types of graphics escape sequence
+      INTEGER GRF__ESPER
+      PARAMETER ( GRF__ESPER = 1 )
+
+      INTEGER GRF__ESSUP
+      PARAMETER ( GRF__ESSUP = 2 )
+
+      INTEGER GRF__ESSUB
+      PARAMETER ( GRF__ESSUB = 3 )
+
+      INTEGER GRF__ESGAP
+      PARAMETER ( GRF__ESGAP = 4 )
+
+      INTEGER GRF__ESBAC
+      PARAMETER ( GRF__ESBAC = 5 )
+
+      INTEGER GRF__ESSIZ
+      PARAMETER ( GRF__ESSIZ = 6 )
+
+      INTEGER GRF__ESWID
+      PARAMETER ( GRF__ESWID = 7 )
+
+      INTEGER GRF__ESFON
+      PARAMETER ( GRF__ESFON = 8 )
+
+      INTEGER GRF__ESCOL
+      PARAMETER ( GRF__ESCOL = 9 )
+
+      INTEGER GRF__ESSTY
+      PARAMETER ( GRF__ESSTY = 10 )
+
+      INTEGER GRF__ESPOP
+      PARAMETER ( GRF__ESPOP = 11 )
+
+      INTEGER GRF__ESPSH
+      PARAMETER ( GRF__ESPSH = 12 )
+
+
diff --git a/ast/Makefile.am b/ast/Makefile.am
new file mode 100644
index 0000000..2739ab4
--- /dev/null
+++ b/ast/Makefile.am
@@ -0,0 +1,822 @@
+## Process this file with automake to produce Makefile.in
+
+# First declare various groups of files.  These were initially extracted
+# from the grp.make file, as constructed by the SDT newdev command
+GRP_C_ROUTINES = \
+    axis.c \
+    box.c \
+    channel.c \
+    circle.c \
+    cmpframe.c \
+    cmpmap.c \
+    cmpregion.c \
+    dsbspecframe.c \
+    dssmap.c \
+    ellipse.c \
+    error.c \
+    fitschan.c \
+    fitstable.c \
+    fluxframe.c \
+    frame.c \
+    frameset.c \
+    globals.c \
+    grismmap.c \
+    interval.c \
+    intramap.c \
+    keymap.c \
+    loader.c \
+    lutmap.c \
+    mapping.c \
+    mathmap.c \
+    matrixmap.c \
+    memory.c \
+    normmap.c \
+    nullregion.c \
+    object.c \
+    pcdmap.c \
+    permmap.c \
+    plot.c \
+    plot3d.c \
+    pointlist.c \
+    pointset.c \
+    polygon.c \
+    polymap.c \
+    prism.c \
+    ratemap.c \
+    region.c \
+    selectormap.c \
+    shiftmap.c \
+    skyaxis.c \
+    skyframe.c \
+    slamap.c \
+    specfluxframe.c \
+    specframe.c \
+    specmap.c \
+    sphmap.c \
+    stc.c \
+    stccatalogentrylocation.c \
+    stcobsdatalocation.c \
+    stcresourceprofile.c \
+    stcschan.c \
+    stcsearchlocation.c \
+    switchmap.c \
+    table.c \
+    timeframe.c \
+    timemap.c \
+    tranmap.c \
+    unit.c \
+    unitmap.c \
+    unitnormmap.c \
+    wcsmap.c \
+    winmap.c \
+    xml.c \
+    xmlchan.c \
+    zoommap.c
+
+
+# The C source files required for the Fortran interface
+if !NOFORTRAN
+F_C_ROUTINES = \
+    c2f77.c \
+    fbox.c \
+    fchannel.c \
+    fcircle.c \
+    fcmpframe.c \
+    fcmpmap.c \
+    fcmpregion.c \
+    fdsbspecframe.c \
+    fdssmap.c \
+    fellipse.c \
+    ferror.c \
+    ffitschan.c \
+    ffitstable.c \
+    ffluxframe.c \
+    fframe.c \
+    fframeset.c \
+    fgrismmap.c \
+    finterval.c \
+    fintramap.c \
+    fkeymap.c \
+    flutmap.c \
+    fmapping.c \
+    fmathmap.c \
+    fmatrixmap.c \
+    fnormmap.c \
+    fnullregion.c \
+    fobject.c \
+    fpcdmap.c \
+    fpermmap.c \
+    fplot.c \
+    fplot3d.c \
+    fpointlist.c \
+    fpolygon.c \
+    fpolymap.c \
+    fprism.c \
+    fratemap.c \
+    fregion.c \
+    fselectormap.c \
+    fshiftmap.c \
+    fskyframe.c \
+    fslamap.c \
+    fspecfluxframe.c \
+    fspecframe.c \
+    fspecmap.c \
+    fsphmap.c \
+    fstc.c \
+    fstccatalogentrylocation.c \
+    fstcobsdatalocation.c \
+    fstcresourceprofile.c \
+    fstcschan.c \
+    fstcsearchlocation.c \
+    fswitchmap.c \
+    ftable.c \
+    ftimeframe.c \
+    ftimemap.c \
+    ftranmap.c \
+    funitmap.c \
+    funitnormmap.c \
+    fwcsmap.c \
+    fwinmap.c \
+    fxmlchan.c \
+    fzoommap.c
+else
+F_C_ROUTINES =
+endif
+
+# Header files which contribute to the "ast.h" file, organised to correspond
+# with the class hierarchy.
+AST_H_FILES = \
+    xml.h \
+    wcstrig.h \
+    proj.h \
+    memory.h \
+    error.h \
+    globals.h \
+    unit.h \
+    ast_err.h \
+    version.h \
+    object.h \
+       keymap.h \
+          table.h \
+             fitstable.h \
+       pointset.h \
+       axis.h \
+          skyaxis.h \
+       mapping.h \
+          cmpmap.h \
+          dssmap.h \
+          grismmap.h \
+          intramap.h \
+          lutmap.h \
+          mathmap.h \
+          matrixmap.h \
+          pcdmap.h \
+          permmap.h \
+          polymap.h \
+          ratemap.h \
+          normmap.h \
+          shiftmap.h \
+          slamap.h \
+          specmap.h \
+          sphmap.h \
+          timemap.h \
+          selectormap.h \
+          switchmap.h \
+          tranmap.h \
+          unitmap.h \
+          unitnormmap.h \
+          wcsmap.h \
+          winmap.h \
+          zoommap.h \
+          frame.h \
+             cmpframe.h \
+                specfluxframe.h \
+             fluxframe.h \
+             frameset.h \
+                plot.h \
+                   plot3d.h \
+             skyframe.h \
+             specframe.h \
+                dsbspecframe.h \
+             region.h \
+                box.h \
+                circle.h \
+                cmpregion.h \
+                ellipse.h \
+                interval.h \
+                nullregion.h \
+                pointlist.h \
+                polygon.h \
+                prism.h \
+                stc.h \
+                    stcresourceprofile.h \
+                    stcsearchlocation.h \
+                    stccatalogentrylocation.h \
+                    stcobsdatalocation.h \
+             timeframe.h \
+       channel.h \
+          fitschan.h \
+          stcschan.h \
+          xmlchan.h
+
+# All the (C) include files required to build the library.
+GRP_C_INCLUDE_FILES = \
+    $(AST_H_FILES) \
+    ems.h \
+    err.h \
+    Ers.h \
+    f77.h \
+    grf.h \
+    grf3d.h \
+    pg3d.h \
+    loader.h \
+    pal2ast.h \
+    skyaxis.h \
+    erfa2ast.h \
+    stc.h \
+    stcresourceprofile.h \
+    stcsearchlocation.h \
+    stccatalogentrylocation.h \
+    stcobsdatalocation.h \
+    wcsmath.h \
+    wcstrig.h \
+    xmlchan.h
+
+if !NOFORTRAN
+F_C_INCLUDE_FILES = \
+    c2f77.h
+
+# The following list should include AST_PAR, but that must not be
+# distributed, and so it is listed separately in
+# nodist_libast_la_SOURCES below.
+GRP_F_INCLUDE_FILES = \
+    GRF_PAR \
+    AST_ERR
+
+else
+F_C_INCLUDE_FILES =
+GRP_F_INCLUDE_FILES =
+endif
+
+# If we have no Fortran we are not building f77.h and we probably
+# do not want a Fortran runtime. This requires that PGPLOT is disabled.
+# We replace it with the stub GRF interface. An alternative would
+# be to have the PGPLOT wrappers use a preprocessor symbol to build
+# the pgplot to always error if used.
+if !NOFORTRAN
+GRF_PGPLOT_SOURCES = \
+    grf_pgplot.c
+GRF3D_PGPLOT_SOURCES = \
+    grf3d_pgplot.c
+else
+GRF_PGPLOT_SOURCES = \
+    grf_5.6.c
+GRF3D_PGPLOT_SOURCES = \
+    grf3d.c
+endif
+
+
+## Following declaration isn't used
+## LATEX_DOCUMENTATION_FILES = \
+##     sun210.tex \
+##     sun211.tex
+
+DOCUMENTATION_PRODUCTS = $(PAPER_DOCUMENTATION) $(HYPER_DOCUMENTATION)
+PAPER_DOCUMENTATION = sun210.tex sun211.tex sun210.pdf sun211.pdf
+HYPER_DOCUMENTATION = sun210.htx_tar sun211.htx_tar
+
+PDF_FIGURES = \
+    cmpframe.pdf \
+    complex.pdf \
+    frames.pdf \
+    frameset.pdf \
+    fronta.pdf \
+    fronta_bw.pdf \
+    frontb.pdf \
+    frontb_bw.pdf \
+    frontc.pdf \
+    frontc_bw.pdf \
+    fsalign.pdf \
+    fsconvert.pdf \
+    fsexample.pdf \
+    fsmerge.pdf \
+    fsremap.pdf \
+    gridplot.pdf \
+    gridplot_bw.pdf \
+    mapping.pdf \
+    overgrid.pdf \
+    overgrid_bw.pdf \
+    parallel.pdf \
+    series.pdf \
+    simpexamp.pdf
+
+WCSLIB_FILES = \
+    proj.c \
+    tpn.c \
+    proj.h \
+    wcstrig.c \
+    wcsmath.h \
+    wcstrig.h
+
+STAR_PAL_FILES = \
+    pal/pal.h \
+    pal/palAddet.c \
+    pal/palAmpqk.c \
+    pal/palCaldj.c \
+    pal/palDat.c \
+    pal/palDe2h.c \
+    pal/palDeuler.c \
+    pal/palDh2e.c \
+    pal/palDjcal.c \
+    pal/palDmat.c \
+    pal/palDrange.c \
+    pal/palDs2tp.c \
+    pal/palDtp2s.c \
+    pal/palDtps2c.c \
+    pal/palDtt.c \
+    pal/palEcmat.c \
+    pal/palEqgal.c \
+    pal/palEtrms.c \
+    pal/palEvp.c \
+    pal/palFk45z.c \
+    pal/palFk524.c \
+    pal/palFk54z.c \
+    pal/palGaleq.c \
+    pal/palGalsup.c \
+    pal/palMappa.c \
+    pal/palMapqkz.c \
+    pal/palOne2One.c \
+    pal/palPrebn.c \
+    pal/palPrec.c \
+    pal/palPrenut.c \
+    pal/palPvobs.c \
+    pal/palRvgalc.c \
+    pal/palRvlg.c \
+    pal/palRvlsrd.c \
+    pal/palRvlsrk.c \
+    pal/palSubet.c \
+    pal/palSupgal.c \
+    pal/pal1sofa.h \
+    pal/palmac.h
+
+ERFA_FILES = \
+    erfa/00READ.ME \
+    erfa/erfa.h \
+    erfa/erfam.h \
+    erfa/a2af.c \
+    erfa/a2tf.c \
+    erfa/af2a.c \
+    erfa/anp.c \
+    erfa/anpm.c \
+    erfa/bi00.c \
+    erfa/bp00.c \
+    erfa/bp06.c \
+    erfa/bpn2xy.c \
+    erfa/c2i00a.c \
+    erfa/c2i00b.c \
+    erfa/c2i06a.c \
+    erfa/c2ibpn.c \
+    erfa/c2ixy.c \
+    erfa/c2ixys.c \
+    erfa/c2s.c \
+    erfa/c2t00a.c \
+    erfa/c2t00b.c \
+    erfa/c2t06a.c \
+    erfa/c2tcio.c \
+    erfa/c2teqx.c \
+    erfa/c2tpe.c \
+    erfa/c2txy.c \
+    erfa/cal2jd.c \
+    erfa/cp.c \
+    erfa/cpv.c \
+    erfa/cr.c \
+    erfa/d2dtf.c \
+    erfa/d2tf.c \
+    erfa/dat.c \
+    erfa/dtdb.c \
+    erfa/dtf2d.c \
+    erfa/ee00.c \
+    erfa/ee00a.c \
+    erfa/ee00b.c \
+    erfa/ee06a.c \
+    erfa/eect00.c \
+    erfa/eform.c \
+    erfa/eo06a.c \
+    erfa/eors.c \
+    erfa/epb.c \
+    erfa/epb2jd.c \
+    erfa/epj.c \
+    erfa/epj2jd.c \
+    erfa/epv00.c \
+    erfa/eqeq94.c \
+    erfa/era00.c \
+    erfa/fad03.c \
+    erfa/fae03.c \
+    erfa/faf03.c \
+    erfa/faju03.c \
+    erfa/fal03.c \
+    erfa/falp03.c \
+    erfa/fama03.c \
+    erfa/fame03.c \
+    erfa/fane03.c \
+    erfa/faom03.c \
+    erfa/fapa03.c \
+    erfa/fasa03.c \
+    erfa/faur03.c \
+    erfa/fave03.c \
+    erfa/fk52h.c \
+    erfa/fk5hip.c \
+    erfa/fk5hz.c \
+    erfa/fw2m.c \
+    erfa/fw2xy.c \
+    erfa/gc2gd.c \
+    erfa/gc2gde.c \
+    erfa/gd2gc.c \
+    erfa/gd2gce.c \
+    erfa/gmst00.c \
+    erfa/gmst06.c \
+    erfa/gmst82.c \
+    erfa/gst00a.c \
+    erfa/gst00b.c \
+    erfa/gst06.c \
+    erfa/gst06a.c \
+    erfa/gst94.c \
+    erfa/h2fk5.c \
+    erfa/hfk5z.c \
+    erfa/ir.c \
+    erfa/jd2cal.c \
+    erfa/jdcalf.c \
+    erfa/num00a.c \
+    erfa/num00b.c \
+    erfa/num06a.c \
+    erfa/numat.c \
+    erfa/nut00a.c \
+    erfa/nut00b.c \
+    erfa/nut06a.c \
+    erfa/nut80.c \
+    erfa/nutm80.c \
+    erfa/obl06.c \
+    erfa/obl80.c \
+    erfa/p06e.c \
+    erfa/p2pv.c \
+    erfa/p2s.c \
+    erfa/pap.c \
+    erfa/pas.c \
+    erfa/pb06.c \
+    erfa/pdp.c \
+    erfa/pfw06.c \
+    erfa/plan94.c \
+    erfa/pm.c \
+    erfa/pmat00.c \
+    erfa/pmat06.c \
+    erfa/pmat76.c \
+    erfa/pmp.c \
+    erfa/pn.c \
+    erfa/pn00.c \
+    erfa/pn00a.c \
+    erfa/pn00b.c \
+    erfa/pn06.c \
+    erfa/pn06a.c \
+    erfa/pnm00a.c \
+    erfa/pnm00b.c \
+    erfa/pnm06a.c \
+    erfa/pnm80.c \
+    erfa/pom00.c \
+    erfa/ppp.c \
+    erfa/ppsp.c \
+    erfa/pr00.c \
+    erfa/prec76.c \
+    erfa/pv2p.c \
+    erfa/pv2s.c \
+    erfa/pvdpv.c \
+    erfa/pvm.c \
+    erfa/pvmpv.c \
+    erfa/pvppv.c \
+    erfa/pvstar.c \
+    erfa/pvu.c \
+    erfa/pvup.c \
+    erfa/pvxpv.c \
+    erfa/pxp.c \
+    erfa/refco.c \
+    erfa/rm2v.c \
+    erfa/rv2m.c \
+    erfa/rx.c \
+    erfa/rxp.c \
+    erfa/rxpv.c \
+    erfa/rxr.c \
+    erfa/ry.c \
+    erfa/rz.c \
+    erfa/s00.c \
+    erfa/s00a.c \
+    erfa/s00b.c \
+    erfa/s06.c \
+    erfa/s06a.c \
+    erfa/s2c.c \
+    erfa/s2p.c \
+    erfa/s2pv.c \
+    erfa/s2xpv.c \
+    erfa/sepp.c \
+    erfa/seps.c \
+    erfa/sp00.c \
+    erfa/starpm.c \
+    erfa/starpv.c \
+    erfa/sxp.c \
+    erfa/sxpv.c \
+    erfa/taitt.c \
+    erfa/taiut1.c \
+    erfa/taiutc.c \
+    erfa/tcbtdb.c \
+    erfa/tcgtt.c \
+    erfa/tdbtcb.c \
+    erfa/tdbtt.c \
+    erfa/tf2a.c \
+    erfa/tf2d.c \
+    erfa/tr.c \
+    erfa/trxp.c \
+    erfa/trxpv.c \
+    erfa/tttai.c \
+    erfa/tttcg.c \
+    erfa/tttdb.c \
+    erfa/ttut1.c \
+    erfa/ut1tai.c \
+    erfa/ut1tt.c \
+    erfa/ut1utc.c \
+    erfa/utctai.c \
+    erfa/utcut1.c \
+    erfa/xy06.c \
+    erfa/xys00a.c \
+    erfa/xys00b.c \
+    erfa/xys06a.c \
+    erfa/zp.c \
+    erfa/zpv.c \
+    erfa/zr.c
+
+PAL_FILES = \
+    palwrap.c \
+    pal.h \
+    erfa.h \
+    erfam.h
+
+CMINPACK_FILES = \
+    cminpack/cminpack.h \
+    cminpack/cminpackP.h \
+    cminpack/lmder1.c \
+    cminpack/lmder.c \
+    cminpack/dpmpar.c \
+    cminpack/enorm.c \
+    cminpack/qrfac.c \
+    cminpack/lmpar.c \
+    cminpack/qrsolv.c
+
+bin_SCRIPTS = ast_link
+dist_bin_SCRIPTS = ast_link_adam
+noinst_SCRIPTS = ast_cpp
+dist_noinst_SCRIPTS = makeh
+# Scripts are not distributed by default (since they might be derived objects)
+# Add these to the distribution below.  In fact, it would be useful
+# and straightforward  to make ast_link{,_adam} derived, since they
+# could then have installation directories painlessly edited in to
+# them.  This might be a requirement for scripts which supported
+# linking against shared libraries.
+
+# Headers required by library users.  Both of the following lines
+# indicate headers which are installed.
+include_HEADERS = GRF_PAR grf.h grf3d.h
+# Following are generated, so should not be distributed.
+nodist_include_HEADERS = ast.h AST_PAR
+include_MESSAGES = AST_ERR ast_err.h
+
+if EXTERNAL_PAL
+PAL_LIB =
+else
+PAL_LIB = libast_pal.la
+endif
+
+lib_LTLIBRARIES = \
+    $(PAL_LIB) \
+    libast.la \
+    libast_err.la \
+    libast_ems.la \
+    libast_drama.la \
+    libast_grf3d.la \
+    libast_grf_2.0.la \
+    libast_grf_3.2.la \
+    libast_grf_5.6.la \
+    libast_pgplot.la \
+    libast_pgplot3d.la
+
+stardocs_DATA = @STAR_LATEX_DOCUMENTATION@
+dist_starnews_DATA = ast.news
+dist_pkgdata_DATA = COPYING COPYING.LESSER COPYING.LIB
+
+# Make all library code position independent by default. This is handy for
+# creating shareable libraries from the static ones (Java JNI libraries).
+# Note we do not simply set the AM_CFLAGS variable, as this would also
+# apply to programs compiled without using libtool, possibly causing the
+# compilation to fail.
+
+if !NOTHREADS
+
+if !NOPIC
+libast_la_CFLAGS = $(AM_CFLAGS) -prefer-pic -DTHREAD_SAFE
+else
+libast_la_CFLAGS = $(AM_CFLAGS) -DTHREAD_SAFE
+endif
+
+else
+libast_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+
+endif
+
+if !NOPIC
+libast_err_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+libast_ems_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+libast_drama_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+libast_grf3d_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+libast_grf_2_0_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+libast_grf_3_2_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+libast_grf_5_6_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+libast_pgplot_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+libast_pgplot3d_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+libast_pal_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+endif
+
+#  The module containing the main AST classes
+libast_la_SOURCES = \
+    $(GRP_C_ROUTINES) \
+    $(F_C_ROUTINES) \
+    $(GRP_C_INCLUDE_FILES) \
+    $(F_C_INCLUDE_FILES) \
+    $(GRP_F_INCLUDE_FILES) \
+    $(CMINPACK_FILES) \
+    $(WCSLIB_FILES) \
+    ast_err.h
+
+# Ensure libast links against libraries containing functions used within
+# libast. If AST is configured --with-external-pal, then the internal
+# libast_pal library will be empty, and we link to an external PAL
+# library instead.
+if EXTERNAL_PAL
+libast_la_LIBADD =  $(libdir)/libpal.la
+else
+libast_la_LIBADD = libast_pal.la
+endif
+
+# AST_PAR is really part of GRP_F_INCLUDE_FILES, but it must not be
+# distributed, so list it separately.
+nodist_libast_la_SOURCES = \
+    ast.h \
+    AST_PAR
+
+#  The default error reporting module.
+libast_err_la_SOURCES = err_null.c
+
+#  The error reporting module that uses EMS to deliver errors.
+libast_ems_la_SOURCES = err_ems.c
+
+#  The error reporting module that uses DRAMA Ers to deliver errors.
+libast_drama_la_SOURCES = err_drama.c
+
+#  The module containing null implementations of the 3D graphics routines
+#  required by AST
+libast_grf3d_la_SOURCES = grf3d.c
+
+#  The module containing null implementations of the graphics routines
+#  required by AST V2.0
+libast_grf_2_0_la_SOURCES = grf_2.0.c
+
+#  The module containing null implementations of the graphics routines
+#  added by AST V3.2 and not present in V2.0
+libast_grf_3_2_la_SOURCES = grf_3.2.c
+
+#  The module containing null implementations of the graphics routines
+#  added by AST V5.6 and not present in V3.2
+libast_grf_5_6_la_SOURCES = grf_5.6.c
+
+#  The graphics module that uses PGPLOT for 2D graphical output.
+libast_pgplot_la_SOURCES = $(GRF_PGPLOT_SOURCES)
+
+#  The graphics module that uses PGPLOT for 3D graphical output.
+libast_pgplot3d_la_SOURCES = $(GRF3D_PGPLOT_SOURCES)
+
+#  Positional astronomy libraries.
+libast_pal_la_SOURCES = $(PAL_FILES)
+
+# The following files are built by the targets in this makefile.
+MAINTAINERCLEANFILES = version.h builddocs addversion \
+	ast.h $(DOCUMENTATION_PRODUCTS)
+CLEANFILES = AST_PAR ast.h
+
+# Special cases start here
+
+# The AST_PAR include file is produced by compiling the astbad.c
+# program and editing its output into the ast_par.source file (while
+# also changing the "E" exponent character to a "D").  The astbad.c
+# and ast_par.source must be distributed (the generation of the
+# AST__BAD value must be done on the build host) but not installed.
+noinst_PROGRAMS = astbad
+astbad_SOURCES = astbad.c pointset.h
+AST_PAR: ast_par.source astbad
+	sed -e 's/<AST__BAD>/'`./astbad AST__BAD | tr 'E' 'D'`'/' \
+	    -e 's/<AST__NAN>/'`./astbad AST__NAN | tr 'E' 'D'`'/' \
+	    -e 's/<AST__NANR>/'`./astbad AST__NANF | tr 'E' 'D'`'/' \
+	    ast_par.source >$@
+
+# ast_link is generated from ast_link.in; ast_link_adam does not
+# need configuration, and so is not mentioned in AC_CONFIG_FILES within
+# configure.ac, and so is not distributed automatically.
+#
+# makeh is required in order to build ast.h after distribution (see below).
+EXTRA_DIST = ast_par.source sun210_figures sun211_figures pal erfa cminpack
+
+# ast.h depends on the error-facility files.  ast.h _could_ be made
+# before distribution, but since it's generated from other distributed
+# files, it's more robust to distribute makeh and make ast.h on the
+# build host.
+ast.h: $(AST_H_FILES) ast_err.h makeh config.h
+	@PERL@ ./makeh -s $(srcdir) $(AST_H_FILES) >$@
+
+# AST_ERR and ast_err.h are `generated source files', and so must be
+# generated before any other compilations are done.  Note that these
+# files are generated on the distribution host, and so this
+# declaration has no effect post-distribution.
+#
+# AST_PAR is also a generated source file, but it should _not_ be
+# included in the list of BUILT_SOURCES, otherwise `make' tries to make
+# it before it makes the `astbad' program it depends on.  Instead,
+# just rely on the dependencies expressed in the main body above to
+# have AST_PAR built before it is needed.
+#
+# version.h is included by object.h, and thus indirectly by most modules.
+# It's most straightforward to build it at the beginning.
+BUILT_SOURCES = AST_ERR ast_err.h version.h
+
+#  Form a second link to the main object library (static and shared). This
+#  is used when a second pass through the library is needed during linking
+#  to resolve references made within other AST libraries (e.g. the grf
+#  modules, etc), to functions defined within libast (e.g. memory management
+#  and error reporting functions). Do not forget to change the contents of
+#  the libast_pass2.la file to refer to libast_pass2.* rather than libast.*.
+#  Use target install-exec-hook rather than install-exec-local so that the
+#  soft links and files are created *after* the main library has been
+#  installed.
+install-exec-hook: libast.la
+	$(mkdir_p) $(DESTDIR)$(libdir)
+	cd $(DESTDIR)$(libdir); \
+	for f in `ls libast.*`; do \
+	   ff=`echo $$f | sed -e 's/libast/libast_pass2/'`; \
+	   if test -f "$$ff"; then rm "$$ff"; fi; \
+	   $(LN_S) $$f $$ff; \
+	   $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(libdir)/$$ff" || :; \
+	done; \
+	if test -f "libast.la"; then \
+	   if test -f "libast_pass2.la"; then rm "libast_pass2.la"; fi; \
+	   sed -e 's/libast\./libast_pass2\./g' libast.la > libast_pass2.la; \
+	fi
+
+# Make pre-distribution files.  These are files which are required for
+# building the distribution, but are not themselves distributed.
+# The source files here should be mentioned in STAR_PREDIST_SOURCES in
+# configure.ac
+@PREDIST@predist_subs = sed \
+@PREDIST@    -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),' \
+@PREDIST@    -e 's,@PACKAGE_VERSION_MAJOR\@,$(PACKAGE_VERSION_MAJOR),' \
+@PREDIST@    -e 's,@PACKAGE_VERSION_MINOR\@,$(PACKAGE_VERSION_MINOR),' \
+@PREDIST@    -e 's,@PACKAGE_VERSION_RELEASE\@,$(PACKAGE_VERSION_RELEASE),' \
+@PREDIST@    -e 's,@PERL\@,$(PERL),' \
+@PREDIST@    -e 's,@STARLINK\@,$(STARLINK),'
+
+@[email-masked]: version.h.in configure.ac
+@PREDIST@	rm -f $@; $(predist_subs) version.h.in >$@
+@PREDIST@builddocs: builddocs.in configure.ac
+@PREDIST@	rm -f $@; $(predist_subs) builddocs.in >$@; chmod +x $@
+@PREDIST@addversion: addversion.in configure.ac
+@PREDIST@	rm -f $@; $(predist_subs) addversion.in >$@; chmod +x $@
+
+# Documentation
+@PREDIST@$(PAPER_DOCUMENTATION): sun211_figures builddocs addversion
+@PREDIST@	./builddocs
+
+# The contents of the sun211_figures directory is identical to that
+# sun210_figures
+sun211_figures: sun210_figures
+	$(LN_S) sun210_figures sun211_figures
+
+# Installation check
+
+TESTS = ast_test
+check_PROGRAMS = ast_test
+ast_test_SOURCES = ast_test.c
+
+#ast_test_LDADD = `ast_link`
+# Expand ast_link to avoid libast_pass2, which causes problems for Solaris
+ast_test_LDADD = @LIBPAL@ libast.la libast_pal.la libast_grf_3.2.la libast_grf_5.6.la libast_grf_2.0.la libast_grf3d.la libast_err.la -lm
+
+#  Need to include latex support files in the distribution tar ball so
+#  that the docs can be built from the tex source files. Requires environment
+#  variable STARLATEXSUPPORT to be deined. Is there a better way to do this?
+dist-hook:
+	cp -p $(STARLATEXSUPPORT)/starlink.cls $(distdir)/
+	cp -p $(STARLATEXSUPPORT)/starabbrev.sty $(distdir)/
+	cp -p $(STARLATEXSUPPORT)/starstyle.sty $(distdir)/
+	cp -p $(STARLATEXSUPPORT)/sst.sty $(distdir)/
diff --git a/ast/Makefile.in b/ast/Makefile.in
new file mode 100644
index 0000000..dd29055
--- /dev/null
+++ b/ast/Makefile.in
@@ -0,0 +1,3968 @@
+# Makefile.in generated by automake 1.14.1-starlink from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \	]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs	]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = astbad$(EXEEXT)
+TESTS = ast_test$(EXEEXT)
+check_PROGRAMS = ast_test$(EXEEXT)
+subdir = .
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+	$(top_srcdir)/configure $(am__configure_deps) \
+	$(srcdir)/config.h.in $(srcdir)/component.xml.in \
+	$(srcdir)/ast_link.in $(srcdir)/ast_link_adam.in \
+	$(srcdir)/object.h.in $(srcdir)/f77.h.in $(srcdir)/ast_cpp.in \
+	$(dist_bin_SCRIPTS) $(dist_noinst_SCRIPTS) \
+	$(top_srcdir)/build-aux/depcomp $(include_MESSAGES) \
+	$(srcdir)/AST_ERR $(srcdir)/ast_err.h $(srcdir)/fac_1521_err \
+	$(dist_pkgdata_DATA) $(dist_starnews_DATA) $(include_HEADERS) \
+	$(top_srcdir)/build-aux/test-driver $(stardocs_DATA) COPYING \
+	COPYING.LIB COPYING.LESSER build-aux/compile \
+	build-aux/config.guess build-aux/config.sub build-aux/depcomp \
+	build-aux/install-sh build-aux/missing build-aux/ltmain.sh \
+	$(top_srcdir)/build-aux/compile \
+	$(top_srcdir)/build-aux/config.guess \
+	$(top_srcdir)/build-aux/config.sub \
+	$(top_srcdir)/build-aux/install-sh \
+	$(top_srcdir)/build-aux/ltmain.sh \
+	$(top_srcdir)/build-aux/missing
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \
+	$(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
+ configure.lineno config.status.lineno
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = config.h
+CONFIG_CLEAN_FILES = component.xml ast_link ast_link_adam object.h \
+	f77.h ast_cpp
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" \
+	"$(DESTDIR)$(bindir)" "$(DESTDIR)$(includedir)" \
+	"$(DESTDIR)$(pkgdatadir)" "$(DESTDIR)$(starnewsdir)" \
+	$(DESTDIR)$(stardocsdir) "$(DESTDIR)$(starfacsdir)" \
+	"$(DESTDIR)$(includedir)" "$(DESTDIR)$(includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+@EXTERNAL_PAL_FALSE@libast_la_DEPENDENCIES = libast_pal.la
+@EXTERNAL_PAL_TRUE@libast_la_DEPENDENCIES = $(libdir)/libpal.la
+am__libast_la_SOURCES_DIST = axis.c box.c channel.c circle.c \
+	cmpframe.c cmpmap.c cmpregion.c dsbspecframe.c dssmap.c \
+	ellipse.c error.c fitschan.c fitstable.c fluxframe.c frame.c \
+	frameset.c globals.c grismmap.c interval.c intramap.c keymap.c \
+	loader.c lutmap.c mapping.c mathmap.c matrixmap.c memory.c \
+	normmap.c nullregion.c object.c pcdmap.c permmap.c plot.c \
+	plot3d.c pointlist.c pointset.c polygon.c polymap.c prism.c \
+	ratemap.c region.c selectormap.c shiftmap.c skyaxis.c \
+	skyframe.c slamap.c specfluxframe.c specframe.c specmap.c \
+	sphmap.c stc.c stccatalogentrylocation.c stcobsdatalocation.c \
+	stcresourceprofile.c stcschan.c stcsearchlocation.c \
+	switchmap.c table.c timeframe.c timemap.c tranmap.c unit.c \
+	unitmap.c unitnormmap.c wcsmap.c winmap.c xml.c xmlchan.c \
+	zoommap.c c2f77.c fbox.c fchannel.c fcircle.c fcmpframe.c \
+	fcmpmap.c fcmpregion.c fdsbspecframe.c fdssmap.c fellipse.c \
+	ferror.c ffitschan.c ffitstable.c ffluxframe.c fframe.c \
+	fframeset.c fgrismmap.c finterval.c fintramap.c fkeymap.c \
+	flutmap.c fmapping.c fmathmap.c fmatrixmap.c fnormmap.c \
+	fnullregion.c fobject.c fpcdmap.c fpermmap.c fplot.c fplot3d.c \
+	fpointlist.c fpolygon.c fpolymap.c fprism.c fratemap.c \
+	fregion.c fselectormap.c fshiftmap.c fskyframe.c fslamap.c \
+	fspecfluxframe.c fspecframe.c fspecmap.c fsphmap.c fstc.c \
+	fstccatalogentrylocation.c fstcobsdatalocation.c \
+	fstcresourceprofile.c fstcschan.c fstcsearchlocation.c \
+	fswitchmap.c ftable.c ftimeframe.c ftimemap.c ftranmap.c \
+	funitmap.c funitnormmap.c fwcsmap.c fwinmap.c fxmlchan.c \
+	fzoommap.c xml.h wcstrig.h proj.h memory.h error.h globals.h \
+	unit.h ast_err.h version.h object.h keymap.h table.h \
+	fitstable.h pointset.h axis.h skyaxis.h mapping.h cmpmap.h \
+	dssmap.h grismmap.h intramap.h lutmap.h mathmap.h matrixmap.h \
+	pcdmap.h permmap.h polymap.h ratemap.h normmap.h shiftmap.h \
+	slamap.h specmap.h sphmap.h timemap.h selectormap.h \
+	switchmap.h tranmap.h unitmap.h unitnormmap.h wcsmap.h \
+	winmap.h zoommap.h frame.h cmpframe.h specfluxframe.h \
+	fluxframe.h frameset.h plot.h plot3d.h skyframe.h specframe.h \
+	dsbspecframe.h region.h box.h circle.h cmpregion.h ellipse.h \
+	interval.h nullregion.h pointlist.h polygon.h prism.h stc.h \
+	stcresourceprofile.h stcsearchlocation.h \
+	stccatalogentrylocation.h stcobsdatalocation.h timeframe.h \
+	channel.h fitschan.h stcschan.h xmlchan.h ems.h err.h Ers.h \
+	f77.h grf.h grf3d.h pg3d.h loader.h pal2ast.h erfa2ast.h \
+	wcsmath.h c2f77.h GRF_PAR AST_ERR cminpack/cminpack.h \
+	cminpack/cminpackP.h cminpack/lmder1.c cminpack/lmder.c \
+	cminpack/dpmpar.c cminpack/enorm.c cminpack/qrfac.c \
+	cminpack/lmpar.c cminpack/qrsolv.c proj.c tpn.c wcstrig.c
+am__objects_1 = libast_la-axis.lo libast_la-box.lo \
+	libast_la-channel.lo libast_la-circle.lo libast_la-cmpframe.lo \
+	libast_la-cmpmap.lo libast_la-cmpregion.lo \
+	libast_la-dsbspecframe.lo libast_la-dssmap.lo \
+	libast_la-ellipse.lo libast_la-error.lo libast_la-fitschan.lo \
+	libast_la-fitstable.lo libast_la-fluxframe.lo \
+	libast_la-frame.lo libast_la-frameset.lo libast_la-globals.lo \
+	libast_la-grismmap.lo libast_la-interval.lo \
+	libast_la-intramap.lo libast_la-keymap.lo libast_la-loader.lo \
+	libast_la-lutmap.lo libast_la-mapping.lo libast_la-mathmap.lo \
+	libast_la-matrixmap.lo libast_la-memory.lo \
+	libast_la-normmap.lo libast_la-nullregion.lo \
+	libast_la-object.lo libast_la-pcdmap.lo libast_la-permmap.lo \
+	libast_la-plot.lo libast_la-plot3d.lo libast_la-pointlist.lo \
+	libast_la-pointset.lo libast_la-polygon.lo \
+	libast_la-polymap.lo libast_la-prism.lo libast_la-ratemap.lo \
+	libast_la-region.lo libast_la-selectormap.lo \
+	libast_la-shiftmap.lo libast_la-skyaxis.lo \
+	libast_la-skyframe.lo libast_la-slamap.lo \
+	libast_la-specfluxframe.lo libast_la-specframe.lo \
+	libast_la-specmap.lo libast_la-sphmap.lo libast_la-stc.lo \
+	libast_la-stccatalogentrylocation.lo \
+	libast_la-stcobsdatalocation.lo \
+	libast_la-stcresourceprofile.lo libast_la-stcschan.lo \
+	libast_la-stcsearchlocation.lo libast_la-switchmap.lo \
+	libast_la-table.lo libast_la-timeframe.lo libast_la-timemap.lo \
+	libast_la-tranmap.lo libast_la-unit.lo libast_la-unitmap.lo \
+	libast_la-unitnormmap.lo libast_la-wcsmap.lo \
+	libast_la-winmap.lo libast_la-xml.lo libast_la-xmlchan.lo \
+	libast_la-zoommap.lo
+@NOFORTRAN_FALSE@am__objects_2 = libast_la-c2f77.lo libast_la-fbox.lo \
+@NOFORTRAN_FALSE@	libast_la-fchannel.lo libast_la-fcircle.lo \
+@NOFORTRAN_FALSE@	libast_la-fcmpframe.lo libast_la-fcmpmap.lo \
+@NOFORTRAN_FALSE@	libast_la-fcmpregion.lo \
+@NOFORTRAN_FALSE@	libast_la-fdsbspecframe.lo \
+@NOFORTRAN_FALSE@	libast_la-fdssmap.lo libast_la-fellipse.lo \
+@NOFORTRAN_FALSE@	libast_la-ferror.lo libast_la-ffitschan.lo \
+@NOFORTRAN_FALSE@	libast_la-ffitstable.lo \
+@NOFORTRAN_FALSE@	libast_la-ffluxframe.lo libast_la-fframe.lo \
+@NOFORTRAN_FALSE@	libast_la-fframeset.lo libast_la-fgrismmap.lo \
+@NOFORTRAN_FALSE@	libast_la-finterval.lo libast_la-fintramap.lo \
+@NOFORTRAN_FALSE@	libast_la-fkeymap.lo libast_la-flutmap.lo \
+@NOFORTRAN_FALSE@	libast_la-fmapping.lo libast_la-fmathmap.lo \
+@NOFORTRAN_FALSE@	libast_la-fmatrixmap.lo libast_la-fnormmap.lo \
+@NOFORTRAN_FALSE@	libast_la-fnullregion.lo libast_la-fobject.lo \
+@NOFORTRAN_FALSE@	libast_la-fpcdmap.lo libast_la-fpermmap.lo \
+@NOFORTRAN_FALSE@	libast_la-fplot.lo libast_la-fplot3d.lo \
+@NOFORTRAN_FALSE@	libast_la-fpointlist.lo libast_la-fpolygon.lo \
+@NOFORTRAN_FALSE@	libast_la-fpolymap.lo libast_la-fprism.lo \
+@NOFORTRAN_FALSE@	libast_la-fratemap.lo libast_la-fregion.lo \
+@NOFORTRAN_FALSE@	libast_la-fselectormap.lo \
+@NOFORTRAN_FALSE@	libast_la-fshiftmap.lo libast_la-fskyframe.lo \
+@NOFORTRAN_FALSE@	libast_la-fslamap.lo \
+@NOFORTRAN_FALSE@	libast_la-fspecfluxframe.lo \
+@NOFORTRAN_FALSE@	libast_la-fspecframe.lo libast_la-fspecmap.lo \
+@NOFORTRAN_FALSE@	libast_la-fsphmap.lo libast_la-fstc.lo \
+@NOFORTRAN_FALSE@	libast_la-fstccatalogentrylocation.lo \
+@NOFORTRAN_FALSE@	libast_la-fstcobsdatalocation.lo \
+@NOFORTRAN_FALSE@	libast_la-fstcresourceprofile.lo \
+@NOFORTRAN_FALSE@	libast_la-fstcschan.lo \
+@NOFORTRAN_FALSE@	libast_la-fstcsearchlocation.lo \
+@NOFORTRAN_FALSE@	libast_la-fswitchmap.lo libast_la-ftable.lo \
+@NOFORTRAN_FALSE@	libast_la-ftimeframe.lo libast_la-ftimemap.lo \
+@NOFORTRAN_FALSE@	libast_la-ftranmap.lo libast_la-funitmap.lo \
+@NOFORTRAN_FALSE@	libast_la-funitnormmap.lo \
+@NOFORTRAN_FALSE@	libast_la-fwcsmap.lo libast_la-fwinmap.lo \
+@NOFORTRAN_FALSE@	libast_la-fxmlchan.lo libast_la-fzoommap.lo
+am__objects_3 =
+am__objects_4 = $(am__objects_3)
+am__dirstamp = $(am__leading_dot)dirstamp
+am__objects_5 = cminpack/libast_la-lmder1.lo \
+	cminpack/libast_la-lmder.lo cminpack/libast_la-dpmpar.lo \
+	cminpack/libast_la-enorm.lo cminpack/libast_la-qrfac.lo \
+	cminpack/libast_la-lmpar.lo cminpack/libast_la-qrsolv.lo
+am__objects_6 = libast_la-proj.lo libast_la-tpn.lo \
+	libast_la-wcstrig.lo
+am_libast_la_OBJECTS = $(am__objects_1) $(am__objects_2) \
+	$(am__objects_4) $(am__objects_3) $(am__objects_3) \
+	$(am__objects_5) $(am__objects_6)
+nodist_libast_la_OBJECTS =
+libast_la_OBJECTS = $(am_libast_la_OBJECTS) \
+	$(nodist_libast_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 = 
+libast_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(libast_la_CFLAGS) \
+	$(CFLAGS) $(STAR_LDFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+libast_drama_la_LIBADD =
+am_libast_drama_la_OBJECTS = libast_drama_la-err_drama.lo
+libast_drama_la_OBJECTS = $(am_libast_drama_la_OBJECTS)
+libast_drama_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+	$(libast_drama_la_CFLAGS) $(CFLAGS) $(STAR_LDFLAGS) \
+	$(AM_LDFLAGS) $(LDFLAGS) -o $@
+libast_ems_la_LIBADD =
+am_libast_ems_la_OBJECTS = libast_ems_la-err_ems.lo
+libast_ems_la_OBJECTS = $(am_libast_ems_la_OBJECTS)
+libast_ems_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(libast_ems_la_CFLAGS) \
+	$(CFLAGS) $(STAR_LDFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+libast_err_la_LIBADD =
+am_libast_err_la_OBJECTS = libast_err_la-err_null.lo
+libast_err_la_OBJECTS = $(am_libast_err_la_OBJECTS)
+libast_err_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(libast_err_la_CFLAGS) \
+	$(CFLAGS) $(STAR_LDFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+libast_grf3d_la_LIBADD =
+am_libast_grf3d_la_OBJECTS = libast_grf3d_la-grf3d.lo
+libast_grf3d_la_OBJECTS = $(am_libast_grf3d_la_OBJECTS)
+libast_grf3d_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+	$(libast_grf3d_la_CFLAGS) $(CFLAGS) $(STAR_LDFLAGS) \
+	$(AM_LDFLAGS) $(LDFLAGS) -o $@
+libast_grf_2_0_la_LIBADD =
+am_libast_grf_2_0_la_OBJECTS = libast_grf_2_0_la-grf_2.0.lo
+libast_grf_2_0_la_OBJECTS = $(am_libast_grf_2_0_la_OBJECTS)
+libast_grf_2_0_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+	$(libast_grf_2_0_la_CFLAGS) $(CFLAGS) $(STAR_LDFLAGS) \
+	$(AM_LDFLAGS) $(LDFLAGS) -o $@
+libast_grf_3_2_la_LIBADD =
+am_libast_grf_3_2_la_OBJECTS = libast_grf_3_2_la-grf_3.2.lo
+libast_grf_3_2_la_OBJECTS = $(am_libast_grf_3_2_la_OBJECTS)
+libast_grf_3_2_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+	$(libast_grf_3_2_la_CFLAGS) $(CFLAGS) $(STAR_LDFLAGS) \
+	$(AM_LDFLAGS) $(LDFLAGS) -o $@
+libast_grf_5_6_la_LIBADD =
+am_libast_grf_5_6_la_OBJECTS = libast_grf_5_6_la-grf_5.6.lo
+libast_grf_5_6_la_OBJECTS = $(am_libast_grf_5_6_la_OBJECTS)
+libast_grf_5_6_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+	$(libast_grf_5_6_la_CFLAGS) $(CFLAGS) $(STAR_LDFLAGS) \
+	$(AM_LDFLAGS) $(LDFLAGS) -o $@
+libast_pal_la_LIBADD =
+am__objects_7 = libast_pal_la-palwrap.lo
+am_libast_pal_la_OBJECTS = $(am__objects_7)
+libast_pal_la_OBJECTS = $(am_libast_pal_la_OBJECTS)
+libast_pal_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(libast_pal_la_CFLAGS) \
+	$(CFLAGS) $(STAR_LDFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+@EXTERNAL_PAL_FALSE@am_libast_pal_la_rpath = -rpath $(libdir)
+libast_pgplot_la_LIBADD =
+am__libast_pgplot_la_SOURCES_DIST = grf_pgplot.c grf_5.6.c
+@NOFORTRAN_FALSE@am__objects_8 = libast_pgplot_la-grf_pgplot.lo
+@NOFORTRAN_TRUE@am__objects_8 = libast_pgplot_la-grf_5.6.lo
+am_libast_pgplot_la_OBJECTS = $(am__objects_8)
+libast_pgplot_la_OBJECTS = $(am_libast_pgplot_la_OBJECTS)
+libast_pgplot_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+	$(libast_pgplot_la_CFLAGS) $(CFLAGS) $(STAR_LDFLAGS) \
+	$(AM_LDFLAGS) $(LDFLAGS) -o $@
+libast_pgplot3d_la_LIBADD =
+am__libast_pgplot3d_la_SOURCES_DIST = grf3d_pgplot.c grf3d.c
+@NOFORTRAN_FALSE@am__objects_9 = libast_pgplot3d_la-grf3d_pgplot.lo
+@NOFORTRAN_TRUE@am__objects_9 = libast_pgplot3d_la-grf3d.lo
+am_libast_pgplot3d_la_OBJECTS = $(am__objects_9)
+libast_pgplot3d_la_OBJECTS = $(am_libast_pgplot3d_la_OBJECTS)
+libast_pgplot3d_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+	$(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+	$(libast_pgplot3d_la_CFLAGS) $(CFLAGS) $(STAR_LDFLAGS) \
+	$(AM_LDFLAGS) $(LDFLAGS) -o $@
+PROGRAMS = $(noinst_PROGRAMS)
+am_ast_test_OBJECTS = ast_test.$(OBJEXT)
+ast_test_OBJECTS = $(am_ast_test_OBJECTS)
+ast_test_DEPENDENCIES = libast.la libast_pal.la libast_grf_3.2.la \
+	libast_grf_5.6.la libast_grf_2.0.la libast_grf3d.la \
+	libast_err.la
+am_astbad_OBJECTS = astbad.$(OBJEXT)
+astbad_OBJECTS = $(am_astbad_OBJECTS)
+astbad_LDADD = $(LDADD)
+SCRIPTS = $(bin_SCRIPTS) $(dist_bin_SCRIPTS) $(dist_noinst_SCRIPTS) \
+	$(noinst_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+DEFAULT_INCLUDES = -I.@am__isrc@
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+	$(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) \
+	$(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+	$(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) \
+	$(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo "  CC      " $@;
+am__v_CC_1 = 
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+	$(STAR_LDFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo "  CCLD    " $@;
+am__v_CCLD_1 = 
+SOURCES = $(libast_la_SOURCES) $(nodist_libast_la_SOURCES) \
+	$(libast_drama_la_SOURCES) $(libast_ems_la_SOURCES) \
+	$(libast_err_la_SOURCES) $(libast_grf3d_la_SOURCES) \
+	$(libast_grf_2_0_la_SOURCES) $(libast_grf_3_2_la_SOURCES) \
+	$(libast_grf_5_6_la_SOURCES) $(libast_pal_la_SOURCES) \
+	$(libast_pgplot_la_SOURCES) $(libast_pgplot3d_la_SOURCES) \
+	$(ast_test_SOURCES) $(astbad_SOURCES)
+DIST_SOURCES = $(am__libast_la_SOURCES_DIST) \
+	$(libast_drama_la_SOURCES) $(libast_ems_la_SOURCES) \
+	$(libast_err_la_SOURCES) $(libast_grf3d_la_SOURCES) \
+	$(libast_grf_2_0_la_SOURCES) $(libast_grf_3_2_la_SOURCES) \
+	$(libast_grf_5_6_la_SOURCES) $(libast_pal_la_SOURCES) \
+	$(am__libast_pgplot_la_SOURCES_DIST) \
+	$(am__libast_pgplot3d_la_SOURCES_DIST) $(ast_test_SOURCES) \
+	$(astbad_SOURCES)
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+MESSAGES = $(include_MESSAGES)
+INSTALL_MESSAGE = $(INSTALL_DATA)
+stardocsDATA_INSTALL = $(INSTALL_DATA)
+DATA = $(dist_pkgdata_DATA) $(dist_starnews_DATA) $(stardocs_DATA) \
+	$(starfacs_DATA)
+HEADERS = $(include_HEADERS) $(nodist_include_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) \
+	$(LISP)config.h.in
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates.  Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+  BEGIN { nonempty = 0; } \
+  { items[$$0] = 1; nonempty = 1; } \
+  END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique.  This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+  list='$(am__tagged_files)'; \
+  unique=`for i in $$list; do \
+    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+  done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+CSCOPE = cscope
+AM_RECURSIVE_TARGETS = cscope check recheck
+am__tty_colors_dummy = \
+  mgn= red= grn= lgn= blu= brg= std=; \
+  am__color_tests=no
+am__tty_colors = { \
+  $(am__tty_colors_dummy); \
+  if test "X$(AM_COLOR_TESTS)" = Xno; then \
+    am__color_tests=no; \
+  elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+    am__color_tests=yes; \
+  elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+    am__color_tests=yes; \
+  fi; \
+  if test $$am__color_tests = yes; then \
+    red='[0;31m'; \
+    grn='[0;32m'; \
+    lgn='[1;32m'; \
+    blu='[1;34m'; \
+    mgn='[0;35m'; \
+    brg='[1m'; \
+    std='[m'; \
+  fi; \
+}
+am__recheck_rx = ^[ 	]*:recheck:[ 	]*
+am__global_test_result_rx = ^[ 	]*:global-test-result:[ 	]*
+am__copy_in_global_log_rx = ^[ 	]*:copy-in-global-log:[ 	]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+  recheck = 1; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+        { \
+          if ((getline line2 < ($$0 ".log")) < 0) \
+	    recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+        { \
+          recheck = 0; \
+          break; \
+        } \
+      else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+        { \
+          break; \
+        } \
+    }; \
+  if (recheck) \
+    print $$0; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+  print "fatal: making $@: " msg | "cat >&2"; \
+  exit 1; \
+} \
+function rst_section(header) \
+{ \
+  print header; \
+  len = length(header); \
+  for (i = 1; i <= len; i = i + 1) \
+    printf "="; \
+  printf "\n\n"; \
+} \
+{ \
+  copy_in_global_log = 1; \
+  global_test_result = "RUN"; \
+  while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+    { \
+      if (rc < 0) \
+         fatal("failed to read from " $$0 ".trs"); \
+      if (line ~ /$(am__global_test_result_rx)/) \
+        { \
+          sub("$(am__global_test_result_rx)", "", line); \
+          sub("[ 	]*$$", "", line); \
+          global_test_result = line; \
+        } \
+      else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+        copy_in_global_log = 0; \
+    }; \
+  if (copy_in_global_log) \
+    { \
+      rst_section(global_test_result ": " $$0); \
+      while ((rc = (getline line < ($$0 ".log"))) != 0) \
+      { \
+        if (rc < 0) \
+          fatal("failed to read from " $$0 ".log"); \
+        print line; \
+      }; \
+      printf "\n"; \
+    }; \
+  close ($$0 ".trs"); \
+  close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/   &   /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this.  Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+  --color-tests "$$am__color_tests" \
+  --enable-hard-errors "$$am__enable_hard_errors" \
+  --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test.  Creates the
+# directory for the log if needed.  Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log.  Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT.  Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup);					\
+$(am__vpath_adj_setup) $(am__vpath_adj)			\
+$(am__tty_colors);					\
+srcdir=$(srcdir); export srcdir;			\
+case "$@" in						\
+  */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;;	\
+    *) am__odir=.;; 					\
+esac;							\
+test "x$$am__odir" = x"." || test -d "$$am__odir" 	\
+  || $(MKDIR_P) "$$am__odir" || exit $$?;		\
+if test -f "./$$f"; then dir=./;			\
+elif test -f "$$f"; then dir=;				\
+else dir="$(srcdir)/"; fi;				\
+tst=$$dir$$f; log='$@'; 				\
+if test -n '$(DISABLE_HARD_ERRORS)'; then		\
+  am__enable_hard_errors=no; 				\
+else							\
+  am__enable_hard_errors=yes; 				\
+fi; 							\
+case " $(XFAIL_TESTS) " in				\
+  *[\ \	]$$f[\ \	]* | *[\ \	]$$dir$$f[\ \	]*) \
+    am__expect_failure=yes;;				\
+  *)							\
+    am__expect_failure=no;;				\
+esac; 							\
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed).  The result is saved in the shell variable
+# '$bases'.  This honors runtime overriding of TESTS and TEST_LOGS.  Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+  bases='$(TEST_LOGS)'; \
+  bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+  bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+  case '$@' in \
+    */*) \
+      case '$*' in \
+        */*) b='$*';; \
+          *) b=`echo '$@' | sed 's/\.log$$//'`; \
+       esac;; \
+    *) \
+      b='$*';; \
+  esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+	$(TEST_LOG_FLAGS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+distdir = $(PACKAGE)-$(VERSION)
+top_distdir = $(distdir)
+am__remove_distdir = \
+  if test -d "$(distdir)"; then \
+    find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
+      && rm -rf "$(distdir)" \
+      || { sleep 5 && rm -rf "$(distdir)"; }; \
+  else :; fi
+am__post_remove_distdir = $(am__remove_distdir)
+DIST_ARCHIVES = $(distdir).tar.gz
+GZIP_ENV = --best
+DIST_TARGETS = dist-gzip
+distuninstallcheck_listfiles = find . -type f -print
+am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \
+  | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$'
+distcleancheck_listfiles = find . -type f -print
+# Use AM_MAKEFLAGS to pass the value of the MANIFEST variable into
+# submakes (not all makes do this automatically).  This can still be
+# overridden with a setting of this variable on the $(MAKE) command
+# line.  I think we really shouldn't be setting this variable -- if
+# it causes a problem for anyone, we should edit a new variable in to
+# the .am templates.
+AM_MAKEFLAGS = MANIFEST=$(MANIFEST)
+REAL_INSTALL = install-am
+# Set to : to emit manifest lines, too
+# (don't actually do this here -- it's done within install-manifest below).
+MANIFEST = false
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+EXTERNAL_PAL = @EXTERNAL_PAL@
+FC = @FC@
+FCFLAGS = @FCFLAGS@
+FCLIBS = @FCLIBS@
+FGREP = @FGREP@
+FORTRAN = @FORTRAN@
+GIT = @GIT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LATEX2DVI = @LATEX2DVI@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBPAL = @LIBPAL@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MESSGEN = @MESSGEN@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_INTEGER = @PACKAGE_VERSION_INTEGER@
+PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@
+PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@
+PACKAGE_VERSION_RELEASE = @PACKAGE_VERSION_RELEASE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PAX = @PAX@
+PERL = @PERL@
+PREDIST = @PREDIST@
+PROLAT = @PROLAT@
+RANLIB = @RANLIB@
+REAL_FUNCTION_TYPE = @REAL_FUNCTION_TYPE@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STAR2HTML = @STAR2HTML@
+STARLINK = @STARLINK@
+STAR_CPPFLAGS = @STAR_CPPFLAGS@
+STAR_DEPENDENCIES_ATTRIBUTES = @STAR_DEPENDENCIES_ATTRIBUTES@
+STAR_DEPENDENCIES_CHILDREN = @STAR_DEPENDENCIES_CHILDREN@
+STAR_DOCUMENTATION = @STAR_DOCUMENTATION@
+STAR_FCFLAGS = @STAR_FCFLAGS@
+STAR_FFLAGS = @STAR_FFLAGS@
+STAR_LATEX_DOCUMENTATION = @STAR_LATEX_DOCUMENTATION@
+STAR_LDFLAGS = @STAR_LDFLAGS@
+STAR_MANIFEST_DIR = @STAR_MANIFEST_DIR@
+STAR_SOURCE_ROOT_DIR = @STAR_SOURCE_ROOT_DIR@
+STRIP = @STRIP@
+TAR = @TAR@
+THREADS = @THREADS@
+TRAIL_TYPE = @TRAIL_TYPE@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_FC = @ac_ct_FC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+stardocsdir = @stardocsdir@
+staretcdir = @staretcdir@
+starexamplesdir = @starexamplesdir@
+starfacsdir = @starfacsdir@
+starhelpdir = @starhelpdir@
+starnewsdir = @starnewsdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+
+# First declare various groups of files.  These were initially extracted
+# from the grp.make file, as constructed by the SDT newdev command
+GRP_C_ROUTINES = \
+    axis.c \
+    box.c \
+    channel.c \
+    circle.c \
+    cmpframe.c \
+    cmpmap.c \
+    cmpregion.c \
+    dsbspecframe.c \
+    dssmap.c \
+    ellipse.c \
+    error.c \
+    fitschan.c \
+    fitstable.c \
+    fluxframe.c \
+    frame.c \
+    frameset.c \
+    globals.c \
+    grismmap.c \
+    interval.c \
+    intramap.c \
+    keymap.c \
+    loader.c \
+    lutmap.c \
+    mapping.c \
+    mathmap.c \
+    matrixmap.c \
+    memory.c \
+    normmap.c \
+    nullregion.c \
+    object.c \
+    pcdmap.c \
+    permmap.c \
+    plot.c \
+    plot3d.c \
+    pointlist.c \
+    pointset.c \
+    polygon.c \
+    polymap.c \
+    prism.c \
+    ratemap.c \
+    region.c \
+    selectormap.c \
+    shiftmap.c \
+    skyaxis.c \
+    skyframe.c \
+    slamap.c \
+    specfluxframe.c \
+    specframe.c \
+    specmap.c \
+    sphmap.c \
+    stc.c \
+    stccatalogentrylocation.c \
+    stcobsdatalocation.c \
+    stcresourceprofile.c \
+    stcschan.c \
+    stcsearchlocation.c \
+    switchmap.c \
+    table.c \
+    timeframe.c \
+    timemap.c \
+    tranmap.c \
+    unit.c \
+    unitmap.c \
+    unitnormmap.c \
+    wcsmap.c \
+    winmap.c \
+    xml.c \
+    xmlchan.c \
+    zoommap.c
+
+
+# The C source files required for the Fortran interface
+@NOFORTRAN_FALSE@F_C_ROUTINES = \
+@NOFORTRAN_FALSE@    c2f77.c \
+@NOFORTRAN_FALSE@    fbox.c \
+@NOFORTRAN_FALSE@    fchannel.c \
+@NOFORTRAN_FALSE@    fcircle.c \
+@NOFORTRAN_FALSE@    fcmpframe.c \
+@NOFORTRAN_FALSE@    fcmpmap.c \
+@NOFORTRAN_FALSE@    fcmpregion.c \
+@NOFORTRAN_FALSE@    fdsbspecframe.c \
+@NOFORTRAN_FALSE@    fdssmap.c \
+@NOFORTRAN_FALSE@    fellipse.c \
+@NOFORTRAN_FALSE@    ferror.c \
+@NOFORTRAN_FALSE@    ffitschan.c \
+@NOFORTRAN_FALSE@    ffitstable.c \
+@NOFORTRAN_FALSE@    ffluxframe.c \
+@NOFORTRAN_FALSE@    fframe.c \
+@NOFORTRAN_FALSE@    fframeset.c \
+@NOFORTRAN_FALSE@    fgrismmap.c \
+@NOFORTRAN_FALSE@    finterval.c \
+@NOFORTRAN_FALSE@    fintramap.c \
+@NOFORTRAN_FALSE@    fkeymap.c \
+@NOFORTRAN_FALSE@    flutmap.c \
+@NOFORTRAN_FALSE@    fmapping.c \
+@NOFORTRAN_FALSE@    fmathmap.c \
+@NOFORTRAN_FALSE@    fmatrixmap.c \
+@NOFORTRAN_FALSE@    fnormmap.c \
+@NOFORTRAN_FALSE@    fnullregion.c \
+@NOFORTRAN_FALSE@    fobject.c \
+@NOFORTRAN_FALSE@    fpcdmap.c \
+@NOFORTRAN_FALSE@    fpermmap.c \
+@NOFORTRAN_FALSE@    fplot.c \
+@NOFORTRAN_FALSE@    fplot3d.c \
+@NOFORTRAN_FALSE@    fpointlist.c \
+@NOFORTRAN_FALSE@    fpolygon.c \
+@NOFORTRAN_FALSE@    fpolymap.c \
+@NOFORTRAN_FALSE@    fprism.c \
+@NOFORTRAN_FALSE@    fratemap.c \
+@NOFORTRAN_FALSE@    fregion.c \
+@NOFORTRAN_FALSE@    fselectormap.c \
+@NOFORTRAN_FALSE@    fshiftmap.c \
+@NOFORTRAN_FALSE@    fskyframe.c \
+@NOFORTRAN_FALSE@    fslamap.c \
+@NOFORTRAN_FALSE@    fspecfluxframe.c \
+@NOFORTRAN_FALSE@    fspecframe.c \
+@NOFORTRAN_FALSE@    fspecmap.c \
+@NOFORTRAN_FALSE@    fsphmap.c \
+@NOFORTRAN_FALSE@    fstc.c \
+@NOFORTRAN_FALSE@    fstccatalogentrylocation.c \
+@NOFORTRAN_FALSE@    fstcobsdatalocation.c \
+@NOFORTRAN_FALSE@    fstcresourceprofile.c \
+@NOFORTRAN_FALSE@    fstcschan.c \
+@NOFORTRAN_FALSE@    fstcsearchlocation.c \
+@NOFORTRAN_FALSE@    fswitchmap.c \
+@NOFORTRAN_FALSE@    ftable.c \
+@NOFORTRAN_FALSE@    ftimeframe.c \
+@NOFORTRAN_FALSE@    ftimemap.c \
+@NOFORTRAN_FALSE@    ftranmap.c \
+@NOFORTRAN_FALSE@    funitmap.c \
+@NOFORTRAN_FALSE@    funitnormmap.c \
+@NOFORTRAN_FALSE@    fwcsmap.c \
+@NOFORTRAN_FALSE@    fwinmap.c \
+@NOFORTRAN_FALSE@    fxmlchan.c \
+@NOFORTRAN_FALSE@    fzoommap.c
+
+@NOFORTRAN_TRUE@F_C_ROUTINES = 
+
+# Header files which contribute to the "ast.h" file, organised to correspond
+# with the class hierarchy.
+AST_H_FILES = \
+    xml.h \
+    wcstrig.h \
+    proj.h \
+    memory.h \
+    error.h \
+    globals.h \
+    unit.h \
+    ast_err.h \
+    version.h \
+    object.h \
+       keymap.h \
+          table.h \
+             fitstable.h \
+       pointset.h \
+       axis.h \
+          skyaxis.h \
+       mapping.h \
+          cmpmap.h \
+          dssmap.h \
+          grismmap.h \
+          intramap.h \
+          lutmap.h \
+          mathmap.h \
+          matrixmap.h \
+          pcdmap.h \
+          permmap.h \
+          polymap.h \
+          ratemap.h \
+          normmap.h \
+          shiftmap.h \
+          slamap.h \
+          specmap.h \
+          sphmap.h \
+          timemap.h \
+          selectormap.h \
+          switchmap.h \
+          tranmap.h \
+          unitmap.h \
+          unitnormmap.h \
+          wcsmap.h \
+          winmap.h \
+          zoommap.h \
+          frame.h \
+             cmpframe.h \
+                specfluxframe.h \
+             fluxframe.h \
+             frameset.h \
+                plot.h \
+                   plot3d.h \
+             skyframe.h \
+             specframe.h \
+                dsbspecframe.h \
+             region.h \
+                box.h \
+                circle.h \
+                cmpregion.h \
+                ellipse.h \
+                interval.h \
+                nullregion.h \
+                pointlist.h \
+                polygon.h \
+                prism.h \
+                stc.h \
+                    stcresourceprofile.h \
+                    stcsearchlocation.h \
+                    stccatalogentrylocation.h \
+                    stcobsdatalocation.h \
+             timeframe.h \
+       channel.h \
+          fitschan.h \
+          stcschan.h \
+          xmlchan.h
+
+
+# All the (C) include files required to build the library.
+GRP_C_INCLUDE_FILES = \
+    $(AST_H_FILES) \
+    ems.h \
+    err.h \
+    Ers.h \
+    f77.h \
+    grf.h \
+    grf3d.h \
+    pg3d.h \
+    loader.h \
+    pal2ast.h \
+    skyaxis.h \
+    erfa2ast.h \
+    stc.h \
+    stcresourceprofile.h \
+    stcsearchlocation.h \
+    stccatalogentrylocation.h \
+    stcobsdatalocation.h \
+    wcsmath.h \
+    wcstrig.h \
+    xmlchan.h
+
+@NOFORTRAN_FALSE@F_C_INCLUDE_FILES = \
+@NOFORTRAN_FALSE@    c2f77.h
+
+@NOFORTRAN_TRUE@F_C_INCLUDE_FILES = 
+
+# The following list should include AST_PAR, but that must not be
+# distributed, and so it is listed separately in
+# nodist_libast_la_SOURCES below.
+@NOFORTRAN_FALSE@GRP_F_INCLUDE_FILES = \
+@NOFORTRAN_FALSE@    GRF_PAR \
+@NOFORTRAN_FALSE@    AST_ERR
+
+@NOFORTRAN_TRUE@GRP_F_INCLUDE_FILES = 
+
+# If we have no Fortran we are not building f77.h and we probably
+# do not want a Fortran runtime. This requires that PGPLOT is disabled.
+# We replace it with the stub GRF interface. An alternative would
+# be to have the PGPLOT wrappers use a preprocessor symbol to build
+# the pgplot to always error if used.
+@NOFORTRAN_FALSE@GRF_PGPLOT_SOURCES = \
+@NOFORTRAN_FALSE@    grf_pgplot.c
+
+@NOFORTRAN_TRUE@GRF_PGPLOT_SOURCES = \
+@NOFORTRAN_TRUE@    grf_5.6.c
+
+@NOFORTRAN_FALSE@GRF3D_PGPLOT_SOURCES = \
+@NOFORTRAN_FALSE@    grf3d_pgplot.c
+
+@NOFORTRAN_TRUE@GRF3D_PGPLOT_SOURCES = \
+@NOFORTRAN_TRUE@    grf3d.c
+
+DOCUMENTATION_PRODUCTS = $(PAPER_DOCUMENTATION) $(HYPER_DOCUMENTATION)
+PAPER_DOCUMENTATION = sun210.tex sun211.tex sun210.pdf sun211.pdf
+HYPER_DOCUMENTATION = sun210.htx_tar sun211.htx_tar
+PDF_FIGURES = \
+    cmpframe.pdf \
+    complex.pdf \
+    frames.pdf \
+    frameset.pdf \
+    fronta.pdf \
+    fronta_bw.pdf \
+    frontb.pdf \
+    frontb_bw.pdf \
+    frontc.pdf \
+    frontc_bw.pdf \
+    fsalign.pdf \
+    fsconvert.pdf \
+    fsexample.pdf \
+    fsmerge.pdf \
+    fsremap.pdf \
+    gridplot.pdf \
+    gridplot_bw.pdf \
+    mapping.pdf \
+    overgrid.pdf \
+    overgrid_bw.pdf \
+    parallel.pdf \
+    series.pdf \
+    simpexamp.pdf
+
+WCSLIB_FILES = \
+    proj.c \
+    tpn.c \
+    proj.h \
+    wcstrig.c \
+    wcsmath.h \
+    wcstrig.h
+
+STAR_PAL_FILES = \
+    pal/pal.h \
+    pal/palAddet.c \
+    pal/palAmpqk.c \
+    pal/palCaldj.c \
+    pal/palDat.c \
+    pal/palDe2h.c \
+    pal/palDeuler.c \
+    pal/palDh2e.c \
+    pal/palDjcal.c \
+    pal/palDmat.c \
+    pal/palDrange.c \
+    pal/palDs2tp.c \
+    pal/palDtp2s.c \
+    pal/palDtps2c.c \
+    pal/palDtt.c \
+    pal/palEcmat.c \
+    pal/palEqgal.c \
+    pal/palEtrms.c \
+    pal/palEvp.c \
+    pal/palFk45z.c \
+    pal/palFk524.c \
+    pal/palFk54z.c \
+    pal/palGaleq.c \
+    pal/palGalsup.c \
+    pal/palMappa.c \
+    pal/palMapqkz.c \
+    pal/palOne2One.c \
+    pal/palPrebn.c \
+    pal/palPrec.c \
+    pal/palPrenut.c \
+    pal/palPvobs.c \
+    pal/palRvgalc.c \
+    pal/palRvlg.c \
+    pal/palRvlsrd.c \
+    pal/palRvlsrk.c \
+    pal/palSubet.c \
+    pal/palSupgal.c \
+    pal/pal1sofa.h \
+    pal/palmac.h
+
+ERFA_FILES = \
+    erfa/00READ.ME \
+    erfa/erfa.h \
+    erfa/erfam.h \
+    erfa/a2af.c \
+    erfa/a2tf.c \
+    erfa/af2a.c \
+    erfa/anp.c \
+    erfa/anpm.c \
+    erfa/bi00.c \
+    erfa/bp00.c \
+    erfa/bp06.c \
+    erfa/bpn2xy.c \
+    erfa/c2i00a.c \
+    erfa/c2i00b.c \
+    erfa/c2i06a.c \
+    erfa/c2ibpn.c \
+    erfa/c2ixy.c \
+    erfa/c2ixys.c \
+    erfa/c2s.c \
+    erfa/c2t00a.c \
+    erfa/c2t00b.c \
+    erfa/c2t06a.c \
+    erfa/c2tcio.c \
+    erfa/c2teqx.c \
+    erfa/c2tpe.c \
+    erfa/c2txy.c \
+    erfa/cal2jd.c \
+    erfa/cp.c \
+    erfa/cpv.c \
+    erfa/cr.c \
+    erfa/d2dtf.c \
+    erfa/d2tf.c \
+    erfa/dat.c \
+    erfa/dtdb.c \
+    erfa/dtf2d.c \
+    erfa/ee00.c \
+    erfa/ee00a.c \
+    erfa/ee00b.c \
+    erfa/ee06a.c \
+    erfa/eect00.c \
+    erfa/eform.c \
+    erfa/eo06a.c \
+    erfa/eors.c \
+    erfa/epb.c \
+    erfa/epb2jd.c \
+    erfa/epj.c \
+    erfa/epj2jd.c \
+    erfa/epv00.c \
+    erfa/eqeq94.c \
+    erfa/era00.c \
+    erfa/fad03.c \
+    erfa/fae03.c \
+    erfa/faf03.c \
+    erfa/faju03.c \
+    erfa/fal03.c \
+    erfa/falp03.c \
+    erfa/fama03.c \
+    erfa/fame03.c \
+    erfa/fane03.c \
+    erfa/faom03.c \
+    erfa/fapa03.c \
+    erfa/fasa03.c \
+    erfa/faur03.c \
+    erfa/fave03.c \
+    erfa/fk52h.c \
+    erfa/fk5hip.c \
+    erfa/fk5hz.c \
+    erfa/fw2m.c \
+    erfa/fw2xy.c \
+    erfa/gc2gd.c \
+    erfa/gc2gde.c \
+    erfa/gd2gc.c \
+    erfa/gd2gce.c \
+    erfa/gmst00.c \
+    erfa/gmst06.c \
+    erfa/gmst82.c \
+    erfa/gst00a.c \
+    erfa/gst00b.c \
+    erfa/gst06.c \
+    erfa/gst06a.c \
+    erfa/gst94.c \
+    erfa/h2fk5.c \
+    erfa/hfk5z.c \
+    erfa/ir.c \
+    erfa/jd2cal.c \
+    erfa/jdcalf.c \
+    erfa/num00a.c \
+    erfa/num00b.c \
+    erfa/num06a.c \
+    erfa/numat.c \
+    erfa/nut00a.c \
+    erfa/nut00b.c \
+    erfa/nut06a.c \
+    erfa/nut80.c \
+    erfa/nutm80.c \
+    erfa/obl06.c \
+    erfa/obl80.c \
+    erfa/p06e.c \
+    erfa/p2pv.c \
+    erfa/p2s.c \
+    erfa/pap.c \
+    erfa/pas.c \
+    erfa/pb06.c \
+    erfa/pdp.c \
+    erfa/pfw06.c \
+    erfa/plan94.c \
+    erfa/pm.c \
+    erfa/pmat00.c \
+    erfa/pmat06.c \
+    erfa/pmat76.c \
+    erfa/pmp.c \
+    erfa/pn.c \
+    erfa/pn00.c \
+    erfa/pn00a.c \
+    erfa/pn00b.c \
+    erfa/pn06.c \
+    erfa/pn06a.c \
+    erfa/pnm00a.c \
+    erfa/pnm00b.c \
+    erfa/pnm06a.c \
+    erfa/pnm80.c \
+    erfa/pom00.c \
+    erfa/ppp.c \
+    erfa/ppsp.c \
+    erfa/pr00.c \
+    erfa/prec76.c \
+    erfa/pv2p.c \
+    erfa/pv2s.c \
+    erfa/pvdpv.c \
+    erfa/pvm.c \
+    erfa/pvmpv.c \
+    erfa/pvppv.c \
+    erfa/pvstar.c \
+    erfa/pvu.c \
+    erfa/pvup.c \
+    erfa/pvxpv.c \
+    erfa/pxp.c \
+    erfa/refco.c \
+    erfa/rm2v.c \
+    erfa/rv2m.c \
+    erfa/rx.c \
+    erfa/rxp.c \
+    erfa/rxpv.c \
+    erfa/rxr.c \
+    erfa/ry.c \
+    erfa/rz.c \
+    erfa/s00.c \
+    erfa/s00a.c \
+    erfa/s00b.c \
+    erfa/s06.c \
+    erfa/s06a.c \
+    erfa/s2c.c \
+    erfa/s2p.c \
+    erfa/s2pv.c \
+    erfa/s2xpv.c \
+    erfa/sepp.c \
+    erfa/seps.c \
+    erfa/sp00.c \
+    erfa/starpm.c \
+    erfa/starpv.c \
+    erfa/sxp.c \
+    erfa/sxpv.c \
+    erfa/taitt.c \
+    erfa/taiut1.c \
+    erfa/taiutc.c \
+    erfa/tcbtdb.c \
+    erfa/tcgtt.c \
+    erfa/tdbtcb.c \
+    erfa/tdbtt.c \
+    erfa/tf2a.c \
+    erfa/tf2d.c \
+    erfa/tr.c \
+    erfa/trxp.c \
+    erfa/trxpv.c \
+    erfa/tttai.c \
+    erfa/tttcg.c \
+    erfa/tttdb.c \
+    erfa/ttut1.c \
+    erfa/ut1tai.c \
+    erfa/ut1tt.c \
+    erfa/ut1utc.c \
+    erfa/utctai.c \
+    erfa/utcut1.c \
+    erfa/xy06.c \
+    erfa/xys00a.c \
+    erfa/xys00b.c \
+    erfa/xys06a.c \
+    erfa/zp.c \
+    erfa/zpv.c \
+    erfa/zr.c
+
+PAL_FILES = \
+    palwrap.c \
+    pal.h \
+    erfa.h \
+    erfam.h
+
+CMINPACK_FILES = \
+    cminpack/cminpack.h \
+    cminpack/cminpackP.h \
+    cminpack/lmder1.c \
+    cminpack/lmder.c \
+    cminpack/dpmpar.c \
+    cminpack/enorm.c \
+    cminpack/qrfac.c \
+    cminpack/lmpar.c \
+    cminpack/qrsolv.c
+
+bin_SCRIPTS = ast_link
+dist_bin_SCRIPTS = ast_link_adam
+noinst_SCRIPTS = ast_cpp
+dist_noinst_SCRIPTS = makeh
+# Scripts are not distributed by default (since they might be derived objects)
+# Add these to the distribution below.  In fact, it would be useful
+# and straightforward  to make ast_link{,_adam} derived, since they
+# could then have installation directories painlessly edited in to
+# them.  This might be a requirement for scripts which supported
+# linking against shared libraries.
+
+# Headers required by library users.  Both of the following lines
+# indicate headers which are installed.
+include_HEADERS = GRF_PAR grf.h grf3d.h
+# Following are generated, so should not be distributed.
+nodist_include_HEADERS = ast.h AST_PAR
+include_MESSAGES = AST_ERR ast_err.h
+@EXTERNAL_PAL_FALSE@PAL_LIB = libast_pal.la
+@EXTERNAL_PAL_TRUE@PAL_LIB = 
+lib_LTLIBRARIES = \
+    $(PAL_LIB) \
+    libast.la \
+    libast_err.la \
+    libast_ems.la \
+    libast_drama.la \
+    libast_grf3d.la \
+    libast_grf_2.0.la \
+    libast_grf_3.2.la \
+    libast_grf_5.6.la \
+    libast_pgplot.la \
+    libast_pgplot3d.la
+
+stardocs_DATA = @STAR_LATEX_DOCUMENTATION@
+dist_starnews_DATA = ast.news
+dist_pkgdata_DATA = COPYING COPYING.LESSER COPYING.LIB
+
+# Make all library code position independent by default. This is handy for
+# creating shareable libraries from the static ones (Java JNI libraries).
+# Note we do not simply set the AM_CFLAGS variable, as this would also
+# apply to programs compiled without using libtool, possibly causing the
+# compilation to fail.
+@NOPIC_FALSE@@NOTHREADS_FALSE@libast_la_CFLAGS = $(AM_CFLAGS) -prefer-pic -DTHREAD_SAFE
+@NOPIC_TRUE@@NOTHREADS_FALSE@libast_la_CFLAGS = $(AM_CFLAGS) -DTHREAD_SAFE
+@NOTHREADS_TRUE@libast_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+@NOPIC_FALSE@libast_err_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+@NOPIC_FALSE@libast_ems_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+@NOPIC_FALSE@libast_drama_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+@NOPIC_FALSE@libast_grf3d_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+@NOPIC_FALSE@libast_grf_2_0_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+@NOPIC_FALSE@libast_grf_3_2_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+@NOPIC_FALSE@libast_grf_5_6_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+@NOPIC_FALSE@libast_pgplot_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+@NOPIC_FALSE@libast_pgplot3d_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+@NOPIC_FALSE@libast_pal_la_CFLAGS = $(AM_CFLAGS) -prefer-pic
+
+#  The module containing the main AST classes
+libast_la_SOURCES = \
+    $(GRP_C_ROUTINES) \
+    $(F_C_ROUTINES) \
+    $(GRP_C_INCLUDE_FILES) \
+    $(F_C_INCLUDE_FILES) \
+    $(GRP_F_INCLUDE_FILES) \
+    $(CMINPACK_FILES) \
+    $(WCSLIB_FILES) \
+    ast_err.h
+
+@EXTERNAL_PAL_FALSE@libast_la_LIBADD = libast_pal.la
+
+# Ensure libast links against libraries containing functions used within
+# libast. If AST is configured --with-external-pal, then the internal
+# libast_pal library will be empty, and we link to an external PAL
+# library instead.
+@EXTERNAL_PAL_TRUE@libast_la_LIBADD = $(libdir)/libpal.la
+
+# AST_PAR is really part of GRP_F_INCLUDE_FILES, but it must not be
+# distributed, so list it separately.
+nodist_libast_la_SOURCES = \
+    ast.h \
+    AST_PAR
+
+
+#  The default error reporting module.
+libast_err_la_SOURCES = err_null.c
+
+#  The error reporting module that uses EMS to deliver errors.
+libast_ems_la_SOURCES = err_ems.c
+
+#  The error reporting module that uses DRAMA Ers to deliver errors.
+libast_drama_la_SOURCES = err_drama.c
+
+#  The module containing null implementations of the 3D graphics routines
+#  required by AST
+libast_grf3d_la_SOURCES = grf3d.c
+
+#  The module containing null implementations of the graphics routines
+#  required by AST V2.0
+libast_grf_2_0_la_SOURCES = grf_2.0.c
+
+#  The module containing null implementations of the graphics routines
+#  added by AST V3.2 and not present in V2.0
+libast_grf_3_2_la_SOURCES = grf_3.2.c
+
+#  The module containing null implementations of the graphics routines
+#  added by AST V5.6 and not present in V3.2
+libast_grf_5_6_la_SOURCES = grf_5.6.c
+
+#  The graphics module that uses PGPLOT for 2D graphical output.
+libast_pgplot_la_SOURCES = $(GRF_PGPLOT_SOURCES)
+
+#  The graphics module that uses PGPLOT for 3D graphical output.
+libast_pgplot3d_la_SOURCES = $(GRF3D_PGPLOT_SOURCES)
+
+#  Positional astronomy libraries.
+libast_pal_la_SOURCES = $(PAL_FILES)
+
+# The following files are built by the targets in this makefile.
+MAINTAINERCLEANFILES = version.h builddocs addversion \
+	ast.h $(DOCUMENTATION_PRODUCTS)
+
+CLEANFILES = AST_PAR ast.h
+astbad_SOURCES = astbad.c pointset.h
+
+# ast_link is generated from ast_link.in; ast_link_adam does not
+# need configuration, and so is not mentioned in AC_CONFIG_FILES within
+# configure.ac, and so is not distributed automatically.
+#
+# makeh is required in order to build ast.h after distribution (see below).
+EXTRA_DIST = ast_par.source sun210_figures sun211_figures pal erfa cminpack
+
+# AST_ERR and ast_err.h are `generated source files', and so must be
+# generated before any other compilations are done.  Note that these
+# files are generated on the distribution host, and so this
+# declaration has no effect post-distribution.
+#
+# AST_PAR is also a generated source file, but it should _not_ be
+# included in the list of BUILT_SOURCES, otherwise `make' tries to make
+# it before it makes the `astbad' program it depends on.  Instead,
+# just rely on the dependencies expressed in the main body above to
+# have AST_PAR built before it is needed.
+#
+# version.h is included by object.h, and thus indirectly by most modules.
+# It's most straightforward to build it at the beginning.
+BUILT_SOURCES = AST_ERR ast_err.h version.h
+
+# Make pre-distribution files.  These are files which are required for
+# building the distribution, but are not themselves distributed.
+# The source files here should be mentioned in STAR_PREDIST_SOURCES in
+# configure.ac
+@PREDIST@predist_subs = sed \
+@PREDIST@    -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),' \
+@PREDIST@    -e 's,@PACKAGE_VERSION_MAJOR\@,$(PACKAGE_VERSION_MAJOR),' \
+@PREDIST@    -e 's,@PACKAGE_VERSION_MINOR\@,$(PACKAGE_VERSION_MINOR),' \
+@PREDIST@    -e 's,@PACKAGE_VERSION_RELEASE\@,$(PACKAGE_VERSION_RELEASE),' \
+@PREDIST@    -e 's,@PERL\@,$(PERL),' \
+@PREDIST@    -e 's,@STARLINK\@,$(STARLINK),'
+
+ast_test_SOURCES = ast_test.c
+
+#ast_test_LDADD = `ast_link`
+# Expand ast_link to avoid libast_pass2, which causes problems for Solaris
+ast_test_LDADD = @LIBPAL@ libast.la libast_pal.la libast_grf_3.2.la libast_grf_5.6.la libast_grf_2.0.la libast_grf3d.la libast_err.la -lm
+starfacs_DATA = fac_1521_err
+all: $(BUILT_SOURCES) config.h
+	$(MAKE) $(AM_MAKEFLAGS) all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .dvi .htx_tar .lo .log .o .obj .pdf .ps .test .test$(EXEEXT) .tex .trs
+am--refresh: Makefile
+	@:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      echo ' cd $(srcdir) && $(AUTOMAKE) --startree'; \
+	      $(am__cd) $(srcdir) && $(AUTOMAKE) --startree \
+		&& exit 0; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --startree Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --startree Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    echo ' $(SHELL) ./config.status'; \
+	    $(SHELL) ./config.status;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	$(SHELL) ./config.status --recheck
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	$(am__cd) $(srcdir) && $(AUTOCONF)
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	$(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
+$(am__aclocal_m4_deps):
+
+config.h: stamp-h1
+	@test -f $@ || rm -f stamp-h1
+	@test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1
+
+stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status
+	@rm -f stamp-h1
+	cd $(top_builddir) && $(SHELL) ./config.status config.h
+$(srcdir)/config.h.in:  $(am__configure_deps) 
+	($(am__cd) $(top_srcdir) && $(AUTOHEADER))
+	rm -f stamp-h1
+	touch $@
+
+distclean-hdr:
+	-rm -f config.h stamp-h1
+component.xml: $(top_builddir)/config.status $(srcdir)/component.xml.in
+	cd $(top_builddir) && $(SHELL) ./config.status $@
+ast_link: $(top_builddir)/config.status $(srcdir)/ast_link.in
+	cd $(top_builddir) && $(SHELL) ./config.status $@
+ast_link_adam: $(top_builddir)/config.status $(srcdir)/ast_link_adam.in
+	cd $(top_builddir) && $(SHELL) ./config.status $@
+object.h: $(top_builddir)/config.status $(srcdir)/object.h.in
+	cd $(top_builddir) && $(SHELL) ./config.status $@
+f77.h: $(top_builddir)/config.status $(srcdir)/f77.h.in
+	cd $(top_builddir) && $(SHELL) ./config.status $@
+ast_cpp: $(top_builddir)/config.status $(srcdir)/ast_cpp.in
+	cd $(top_builddir) && $(SHELL) ./config.status $@
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+	@$(NORMAL_INSTALL)
+	@list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+	list2=; for p in $$list; do \
+	  if test -f $$p; then \
+	    list2="$$list2 $$p"; \
+	  else :; fi; \
+	done; \
+	test -z "$$list2" || { \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+	  echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+	  $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+	    if $(MANIFEST); then \
+	      for p in $$list2; do \
+	         echo "MANIFEST:$(DESTDIR)$(libdir)/$$p"; \
+	         expr $$p : '.*\.la$$' >/dev/null || \
+		   { echo "Installing non-la file $$p!"; exit 1; }; \
+	         (. ./$$p; \
+		    if test -n "$$library_names"; then \
+		     for l in $$library_names; do \
+		       echo "MANIFEST:$(DESTDIR)$$libdir/$$l"; \
+		     done; \
+		   fi; \
+		   if test -n "$$old_library"; then \
+		     echo "MANIFEST:$(DESTDIR)$$libdir/$$old_library"; \
+		   else :; fi; \
+                 ); \
+               done; \
+	    else :; fi; \
+	}
+
+uninstall-libLTLIBRARIES:
+	@$(NORMAL_UNINSTALL)
+	@list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+	for p in $$list; do \
+	  $(am__strip_dir) \
+	  echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+	  $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+	done
+
+clean-libLTLIBRARIES:
+	-test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+	@list='$(lib_LTLIBRARIES)'; \
+	locs=`for p in $$list; do echo $$p; done | \
+	      sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+	      sort -u`; \
+	test -z "$$locs" || { \
+	  echo rm -f $${locs}; \
+	  rm -f $${locs}; \
+	}
+cminpack/$(am__dirstamp):
+	@$(MKDIR_P) cminpack
+	@: > cminpack/$(am__dirstamp)
+cminpack/$(DEPDIR)/$(am__dirstamp):
+	@$(MKDIR_P) cminpack/$(DEPDIR)
+	@: > cminpack/$(DEPDIR)/$(am__dirstamp)
+cminpack/libast_la-lmder1.lo: cminpack/$(am__dirstamp) \
+	cminpack/$(DEPDIR)/$(am__dirstamp)
+cminpack/libast_la-lmder.lo: cminpack/$(am__dirstamp) \
+	cminpack/$(DEPDIR)/$(am__dirstamp)
+cminpack/libast_la-dpmpar.lo: cminpack/$(am__dirstamp) \
+	cminpack/$(DEPDIR)/$(am__dirstamp)
+cminpack/libast_la-enorm.lo: cminpack/$(am__dirstamp) \
+	cminpack/$(DEPDIR)/$(am__dirstamp)
+cminpack/libast_la-qrfac.lo: cminpack/$(am__dirstamp) \
+	cminpack/$(DEPDIR)/$(am__dirstamp)
+cminpack/libast_la-lmpar.lo: cminpack/$(am__dirstamp) \
+	cminpack/$(DEPDIR)/$(am__dirstamp)
+cminpack/libast_la-qrsolv.lo: cminpack/$(am__dirstamp) \
+	cminpack/$(DEPDIR)/$(am__dirstamp)
+libast.la: $(libast_la_OBJECTS) $(libast_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_la_LINK) -rpath $(libdir) $(libast_la_LDFLAGS) $(libast_la_OBJECTS) $(libast_la_LIBADD)  $(LIBS)
+libast_drama.la: $(libast_drama_la_OBJECTS) $(libast_drama_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_drama_la_LINK) -rpath $(libdir) $(libast_drama_la_LDFLAGS) $(libast_drama_la_OBJECTS) $(libast_drama_la_LIBADD)  $(LIBS)
+libast_ems.la: $(libast_ems_la_OBJECTS) $(libast_ems_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_ems_la_LINK) -rpath $(libdir) $(libast_ems_la_LDFLAGS) $(libast_ems_la_OBJECTS) $(libast_ems_la_LIBADD)  $(LIBS)
+libast_err.la: $(libast_err_la_OBJECTS) $(libast_err_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_err_la_LINK) -rpath $(libdir) $(libast_err_la_LDFLAGS) $(libast_err_la_OBJECTS) $(libast_err_la_LIBADD)  $(LIBS)
+libast_grf3d.la: $(libast_grf3d_la_OBJECTS) $(libast_grf3d_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_grf3d_la_LINK) -rpath $(libdir) $(libast_grf3d_la_LDFLAGS) $(libast_grf3d_la_OBJECTS) $(libast_grf3d_la_LIBADD)  $(LIBS)
+libast_grf_2.0.la: $(libast_grf_2_0_la_OBJECTS) $(libast_grf_2_0_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_grf_2_0_la_LINK) -rpath $(libdir) $(libast_grf_2_0_la_LDFLAGS) $(libast_grf_2_0_la_OBJECTS) $(libast_grf_2_0_la_LIBADD)  $(LIBS)
+libast_grf_3.2.la: $(libast_grf_3_2_la_OBJECTS) $(libast_grf_3_2_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_grf_3_2_la_LINK) -rpath $(libdir) $(libast_grf_3_2_la_LDFLAGS) $(libast_grf_3_2_la_OBJECTS) $(libast_grf_3_2_la_LIBADD)  $(LIBS)
+libast_grf_5.6.la: $(libast_grf_5_6_la_OBJECTS) $(libast_grf_5_6_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_grf_5_6_la_LINK) -rpath $(libdir) $(libast_grf_5_6_la_LDFLAGS) $(libast_grf_5_6_la_OBJECTS) $(libast_grf_5_6_la_LIBADD)  $(LIBS)
+libast_pal.la: $(libast_pal_la_OBJECTS) $(libast_pal_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_pal_la_LINK) $(am_libast_pal_la_rpath) $(libast_pal_la_LDFLAGS) $(libast_pal_la_OBJECTS) $(libast_pal_la_LIBADD)  $(LIBS)
+libast_pgplot.la: $(libast_pgplot_la_OBJECTS) $(libast_pgplot_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_pgplot_la_LINK) -rpath $(libdir) $(libast_pgplot_la_LDFLAGS) $(libast_pgplot_la_OBJECTS) $(libast_pgplot_la_LIBADD)  $(LIBS)
+libast_pgplot3d.la: $(libast_pgplot3d_la_OBJECTS) $(libast_pgplot3d_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libast_pgplot3d_la_LINK) -rpath $(libdir) $(libast_pgplot3d_la_LDFLAGS) $(libast_pgplot3d_la_OBJECTS) $(libast_pgplot3d_la_LIBADD)  $(LIBS)
+
+clean-checkPROGRAMS:
+	@list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \
+	echo " rm -f" $$list; \
+	rm -f $$list || exit $$?; \
+	test -n "$(EXEEXT)" || exit 0; \
+	list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+	echo " rm -f" $$list; \
+	rm -f $$list
+
+clean-noinstPROGRAMS:
+	@list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+	echo " rm -f" $$list; \
+	rm -f $$list || exit $$?; \
+	test -n "$(EXEEXT)" || exit 0; \
+	list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+	echo " rm -f" $$list; \
+	rm -f $$list
+
+ast_test$(EXEEXT): $(ast_test_OBJECTS) $(ast_test_DEPENDENCIES) $(EXTRA_ast_test_DEPENDENCIES) 
+	@rm -f ast_test$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(ast_test_LDFLAGS) $(ast_test_OBJECTS) $(ast_test_LDADD) $(LIBS) 
+
+astbad$(EXEEXT): $(astbad_OBJECTS) $(astbad_DEPENDENCIES) $(EXTRA_astbad_DEPENDENCIES) 
+	@rm -f astbad$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(astbad_LDFLAGS) $(astbad_OBJECTS) $(astbad_LDADD) $(LIBS) 
+install-binSCRIPTS: $(bin_SCRIPTS)
+	@$(NORMAL_INSTALL)
+	@list='$(bin_SCRIPTS)'; test -n "$(bindir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+	fi; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n' \
+	    -e 'h;s|.*|.|' \
+	    -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+	      if (++n[d] == $(am__install_max)) { \
+		print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+	    else { print "f", d "/" $$4, $$1 } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	     if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	     test -z "$$files" || { \
+	       echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	       $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	       for f in $$files; do \
+	           $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(bindir)$$dir/$$f" || :; \
+	       done; \
+	     } \
+	done
+
+uninstall-binSCRIPTS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_SCRIPTS)'; test -n "$(bindir)" || exit 0; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	       sed -e 's,.*/,,;$(transform)'`; \
+	dir='$(DESTDIR)$(bindir)'; $(am__uninstall_files_from_dir)
+install-dist_binSCRIPTS: $(dist_bin_SCRIPTS)
+	@$(NORMAL_INSTALL)
+	@list='$(dist_bin_SCRIPTS)'; test -n "$(bindir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+	fi; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n' \
+	    -e 'h;s|.*|.|' \
+	    -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+	      if (++n[d] == $(am__install_max)) { \
+		print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+	    else { print "f", d "/" $$4, $$1 } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	     if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	     test -z "$$files" || { \
+	       echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	       $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	       for f in $$files; do \
+	           $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(bindir)$$dir/$$f" || :; \
+	       done; \
+	     } \
+	done
+
+uninstall-dist_binSCRIPTS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(dist_bin_SCRIPTS)'; test -n "$(bindir)" || exit 0; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	       sed -e 's,.*/,,;$(transform)'`; \
+	dir='$(DESTDIR)$(bindir)'; $(am__uninstall_files_from_dir)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+	-rm -f cminpack/*.$(OBJEXT)
+	-rm -f cminpack/*.lo
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ast_test.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/astbad.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_drama_la-err_drama.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_ems_la-err_ems.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_err_la-err_null.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_grf3d_la-grf3d.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_grf_2_0_la-grf_2.0.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_grf_3_2_la-grf_3.2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_grf_5_6_la-grf_5.6.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-axis.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-box.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-c2f77.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-channel.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-circle.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-cmpframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-cmpmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-cmpregion.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-dsbspecframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-dssmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-ellipse.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-error.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fbox.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fchannel.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fcircle.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fcmpframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fcmpmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fcmpregion.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fdsbspecframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fdssmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fellipse.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-ferror.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-ffitschan.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-ffitstable.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-ffluxframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fframeset.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fgrismmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-finterval.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fintramap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fitschan.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fitstable.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fkeymap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-flutmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fluxframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fmapping.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fmathmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fmatrixmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fnormmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fnullregion.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fobject.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fpcdmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fpermmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fplot.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fplot3d.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fpointlist.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fpolygon.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fpolymap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fprism.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-frame.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-frameset.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fratemap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fregion.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fselectormap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fshiftmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fskyframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fslamap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fspecfluxframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fspecframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fspecmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fsphmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fstc.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fstccatalogentrylocation.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fstcobsdatalocation.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fstcresourceprofile.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fstcschan.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fstcsearchlocation.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fswitchmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-ftable.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-ftimeframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-ftimemap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-ftranmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-funitmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-funitnormmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fwcsmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fwinmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fxmlchan.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-fzoommap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-globals.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-grismmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-interval.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-intramap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-keymap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-loader.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-lutmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-mapping.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-mathmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-matrixmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-memory.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-normmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-nullregion.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-object.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-pcdmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-permmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-plot.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-plot3d.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-pointlist.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-pointset.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-polygon.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-polymap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-prism.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-proj.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-ratemap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-region.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-selectormap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-shiftmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-skyaxis.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-skyframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-slamap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-specfluxframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-specframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-specmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-sphmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-stc.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-stccatalogentrylocation.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-stcobsdatalocation.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-stcresourceprofile.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-stcschan.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-stcsearchlocation.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-switchmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-table.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-timeframe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-timemap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-tpn.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-tranmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-unit.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-unitmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-unitnormmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-wcsmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-wcstrig.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-winmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-xml.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-xmlchan.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_la-zoommap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_pal_la-palwrap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_pgplot3d_la-grf3d.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_pgplot3d_la-grf3d_pgplot.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_pgplot_la-grf_5.6.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libast_pgplot_la-grf_pgplot.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@cminpack/$(DEPDIR)/libast_la-dpmpar.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@cminpack/$(DEPDIR)/libast_la-enorm.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@cminpack/$(DEPDIR)/libast_la-lmder.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@cminpack/$(DEPDIR)/libast_la-lmder1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@cminpack/$(DEPDIR)/libast_la-lmpar.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@cminpack/$(DEPDIR)/libast_la-qrfac.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@cminpack/$(DEPDIR)/libast_la-qrsolv.Plo@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@	$(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+libast_la-axis.lo: axis.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-axis.lo -MD -MP -MF $(DEPDIR)/libast_la-axis.Tpo -c -o libast_la-axis.lo `test -f 'axis.c' || echo '$(srcdir)/'`axis.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-axis.Tpo $(DEPDIR)/libast_la-axis.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='axis.c' object='libast_la-axis.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-axis.lo `test -f 'axis.c' || echo '$(srcdir)/'`axis.c
+
+libast_la-box.lo: box.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-box.lo -MD -MP -MF $(DEPDIR)/libast_la-box.Tpo -c -o libast_la-box.lo `test -f 'box.c' || echo '$(srcdir)/'`box.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-box.Tpo $(DEPDIR)/libast_la-box.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='box.c' object='libast_la-box.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-box.lo `test -f 'box.c' || echo '$(srcdir)/'`box.c
+
+libast_la-channel.lo: channel.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-channel.lo -MD -MP -MF $(DEPDIR)/libast_la-channel.Tpo -c -o libast_la-channel.lo `test -f 'channel.c' || echo '$(srcdir)/'`channel.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-channel.Tpo $(DEPDIR)/libast_la-channel.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='channel.c' object='libast_la-channel.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-channel.lo `test -f 'channel.c' || echo '$(srcdir)/'`channel.c
+
+libast_la-circle.lo: circle.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-circle.lo -MD -MP -MF $(DEPDIR)/libast_la-circle.Tpo -c -o libast_la-circle.lo `test -f 'circle.c' || echo '$(srcdir)/'`circle.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-circle.Tpo $(DEPDIR)/libast_la-circle.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='circle.c' object='libast_la-circle.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-circle.lo `test -f 'circle.c' || echo '$(srcdir)/'`circle.c
+
+libast_la-cmpframe.lo: cmpframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-cmpframe.lo -MD -MP -MF $(DEPDIR)/libast_la-cmpframe.Tpo -c -o libast_la-cmpframe.lo `test -f 'cmpframe.c' || echo '$(srcdir)/'`cmpframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-cmpframe.Tpo $(DEPDIR)/libast_la-cmpframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cmpframe.c' object='libast_la-cmpframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-cmpframe.lo `test -f 'cmpframe.c' || echo '$(srcdir)/'`cmpframe.c
+
+libast_la-cmpmap.lo: cmpmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-cmpmap.lo -MD -MP -MF $(DEPDIR)/libast_la-cmpmap.Tpo -c -o libast_la-cmpmap.lo `test -f 'cmpmap.c' || echo '$(srcdir)/'`cmpmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-cmpmap.Tpo $(DEPDIR)/libast_la-cmpmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cmpmap.c' object='libast_la-cmpmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-cmpmap.lo `test -f 'cmpmap.c' || echo '$(srcdir)/'`cmpmap.c
+
+libast_la-cmpregion.lo: cmpregion.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-cmpregion.lo -MD -MP -MF $(DEPDIR)/libast_la-cmpregion.Tpo -c -o libast_la-cmpregion.lo `test -f 'cmpregion.c' || echo '$(srcdir)/'`cmpregion.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-cmpregion.Tpo $(DEPDIR)/libast_la-cmpregion.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cmpregion.c' object='libast_la-cmpregion.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-cmpregion.lo `test -f 'cmpregion.c' || echo '$(srcdir)/'`cmpregion.c
+
+libast_la-dsbspecframe.lo: dsbspecframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-dsbspecframe.lo -MD -MP -MF $(DEPDIR)/libast_la-dsbspecframe.Tpo -c -o libast_la-dsbspecframe.lo `test -f 'dsbspecframe.c' || echo '$(srcdir)/'`dsbspecframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-dsbspecframe.Tpo $(DEPDIR)/libast_la-dsbspecframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='dsbspecframe.c' object='libast_la-dsbspecframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-dsbspecframe.lo `test -f 'dsbspecframe.c' || echo '$(srcdir)/'`dsbspecframe.c
+
+libast_la-dssmap.lo: dssmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-dssmap.lo -MD -MP -MF $(DEPDIR)/libast_la-dssmap.Tpo -c -o libast_la-dssmap.lo `test -f 'dssmap.c' || echo '$(srcdir)/'`dssmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-dssmap.Tpo $(DEPDIR)/libast_la-dssmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='dssmap.c' object='libast_la-dssmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-dssmap.lo `test -f 'dssmap.c' || echo '$(srcdir)/'`dssmap.c
+
+libast_la-ellipse.lo: ellipse.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-ellipse.lo -MD -MP -MF $(DEPDIR)/libast_la-ellipse.Tpo -c -o libast_la-ellipse.lo `test -f 'ellipse.c' || echo '$(srcdir)/'`ellipse.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-ellipse.Tpo $(DEPDIR)/libast_la-ellipse.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='ellipse.c' object='libast_la-ellipse.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-ellipse.lo `test -f 'ellipse.c' || echo '$(srcdir)/'`ellipse.c
+
+libast_la-error.lo: error.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-error.lo -MD -MP -MF $(DEPDIR)/libast_la-error.Tpo -c -o libast_la-error.lo `test -f 'error.c' || echo '$(srcdir)/'`error.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-error.Tpo $(DEPDIR)/libast_la-error.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='error.c' object='libast_la-error.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-error.lo `test -f 'error.c' || echo '$(srcdir)/'`error.c
+
+libast_la-fitschan.lo: fitschan.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fitschan.lo -MD -MP -MF $(DEPDIR)/libast_la-fitschan.Tpo -c -o libast_la-fitschan.lo `test -f 'fitschan.c' || echo '$(srcdir)/'`fitschan.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fitschan.Tpo $(DEPDIR)/libast_la-fitschan.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fitschan.c' object='libast_la-fitschan.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fitschan.lo `test -f 'fitschan.c' || echo '$(srcdir)/'`fitschan.c
+
+libast_la-fitstable.lo: fitstable.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fitstable.lo -MD -MP -MF $(DEPDIR)/libast_la-fitstable.Tpo -c -o libast_la-fitstable.lo `test -f 'fitstable.c' || echo '$(srcdir)/'`fitstable.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fitstable.Tpo $(DEPDIR)/libast_la-fitstable.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fitstable.c' object='libast_la-fitstable.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fitstable.lo `test -f 'fitstable.c' || echo '$(srcdir)/'`fitstable.c
+
+libast_la-fluxframe.lo: fluxframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fluxframe.lo -MD -MP -MF $(DEPDIR)/libast_la-fluxframe.Tpo -c -o libast_la-fluxframe.lo `test -f 'fluxframe.c' || echo '$(srcdir)/'`fluxframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fluxframe.Tpo $(DEPDIR)/libast_la-fluxframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fluxframe.c' object='libast_la-fluxframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fluxframe.lo `test -f 'fluxframe.c' || echo '$(srcdir)/'`fluxframe.c
+
+libast_la-frame.lo: frame.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-frame.lo -MD -MP -MF $(DEPDIR)/libast_la-frame.Tpo -c -o libast_la-frame.lo `test -f 'frame.c' || echo '$(srcdir)/'`frame.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-frame.Tpo $(DEPDIR)/libast_la-frame.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='frame.c' object='libast_la-frame.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-frame.lo `test -f 'frame.c' || echo '$(srcdir)/'`frame.c
+
+libast_la-frameset.lo: frameset.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-frameset.lo -MD -MP -MF $(DEPDIR)/libast_la-frameset.Tpo -c -o libast_la-frameset.lo `test -f 'frameset.c' || echo '$(srcdir)/'`frameset.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-frameset.Tpo $(DEPDIR)/libast_la-frameset.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='frameset.c' object='libast_la-frameset.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-frameset.lo `test -f 'frameset.c' || echo '$(srcdir)/'`frameset.c
+
+libast_la-globals.lo: globals.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-globals.lo -MD -MP -MF $(DEPDIR)/libast_la-globals.Tpo -c -o libast_la-globals.lo `test -f 'globals.c' || echo '$(srcdir)/'`globals.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-globals.Tpo $(DEPDIR)/libast_la-globals.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='globals.c' object='libast_la-globals.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-globals.lo `test -f 'globals.c' || echo '$(srcdir)/'`globals.c
+
+libast_la-grismmap.lo: grismmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-grismmap.lo -MD -MP -MF $(DEPDIR)/libast_la-grismmap.Tpo -c -o libast_la-grismmap.lo `test -f 'grismmap.c' || echo '$(srcdir)/'`grismmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-grismmap.Tpo $(DEPDIR)/libast_la-grismmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='grismmap.c' object='libast_la-grismmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-grismmap.lo `test -f 'grismmap.c' || echo '$(srcdir)/'`grismmap.c
+
+libast_la-interval.lo: interval.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-interval.lo -MD -MP -MF $(DEPDIR)/libast_la-interval.Tpo -c -o libast_la-interval.lo `test -f 'interval.c' || echo '$(srcdir)/'`interval.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-interval.Tpo $(DEPDIR)/libast_la-interval.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='interval.c' object='libast_la-interval.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-interval.lo `test -f 'interval.c' || echo '$(srcdir)/'`interval.c
+
+libast_la-intramap.lo: intramap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-intramap.lo -MD -MP -MF $(DEPDIR)/libast_la-intramap.Tpo -c -o libast_la-intramap.lo `test -f 'intramap.c' || echo '$(srcdir)/'`intramap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-intramap.Tpo $(DEPDIR)/libast_la-intramap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='intramap.c' object='libast_la-intramap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-intramap.lo `test -f 'intramap.c' || echo '$(srcdir)/'`intramap.c
+
+libast_la-keymap.lo: keymap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-keymap.lo -MD -MP -MF $(DEPDIR)/libast_la-keymap.Tpo -c -o libast_la-keymap.lo `test -f 'keymap.c' || echo '$(srcdir)/'`keymap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-keymap.Tpo $(DEPDIR)/libast_la-keymap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='keymap.c' object='libast_la-keymap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-keymap.lo `test -f 'keymap.c' || echo '$(srcdir)/'`keymap.c
+
+libast_la-loader.lo: loader.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-loader.lo -MD -MP -MF $(DEPDIR)/libast_la-loader.Tpo -c -o libast_la-loader.lo `test -f 'loader.c' || echo '$(srcdir)/'`loader.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-loader.Tpo $(DEPDIR)/libast_la-loader.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='loader.c' object='libast_la-loader.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-loader.lo `test -f 'loader.c' || echo '$(srcdir)/'`loader.c
+
+libast_la-lutmap.lo: lutmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-lutmap.lo -MD -MP -MF $(DEPDIR)/libast_la-lutmap.Tpo -c -o libast_la-lutmap.lo `test -f 'lutmap.c' || echo '$(srcdir)/'`lutmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-lutmap.Tpo $(DEPDIR)/libast_la-lutmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='lutmap.c' object='libast_la-lutmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-lutmap.lo `test -f 'lutmap.c' || echo '$(srcdir)/'`lutmap.c
+
+libast_la-mapping.lo: mapping.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-mapping.lo -MD -MP -MF $(DEPDIR)/libast_la-mapping.Tpo -c -o libast_la-mapping.lo `test -f 'mapping.c' || echo '$(srcdir)/'`mapping.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-mapping.Tpo $(DEPDIR)/libast_la-mapping.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='mapping.c' object='libast_la-mapping.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-mapping.lo `test -f 'mapping.c' || echo '$(srcdir)/'`mapping.c
+
+libast_la-mathmap.lo: mathmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-mathmap.lo -MD -MP -MF $(DEPDIR)/libast_la-mathmap.Tpo -c -o libast_la-mathmap.lo `test -f 'mathmap.c' || echo '$(srcdir)/'`mathmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-mathmap.Tpo $(DEPDIR)/libast_la-mathmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='mathmap.c' object='libast_la-mathmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-mathmap.lo `test -f 'mathmap.c' || echo '$(srcdir)/'`mathmap.c
+
+libast_la-matrixmap.lo: matrixmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-matrixmap.lo -MD -MP -MF $(DEPDIR)/libast_la-matrixmap.Tpo -c -o libast_la-matrixmap.lo `test -f 'matrixmap.c' || echo '$(srcdir)/'`matrixmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-matrixmap.Tpo $(DEPDIR)/libast_la-matrixmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='matrixmap.c' object='libast_la-matrixmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-matrixmap.lo `test -f 'matrixmap.c' || echo '$(srcdir)/'`matrixmap.c
+
+libast_la-memory.lo: memory.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-memory.lo -MD -MP -MF $(DEPDIR)/libast_la-memory.Tpo -c -o libast_la-memory.lo `test -f 'memory.c' || echo '$(srcdir)/'`memory.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-memory.Tpo $(DEPDIR)/libast_la-memory.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='memory.c' object='libast_la-memory.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-memory.lo `test -f 'memory.c' || echo '$(srcdir)/'`memory.c
+
+libast_la-normmap.lo: normmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-normmap.lo -MD -MP -MF $(DEPDIR)/libast_la-normmap.Tpo -c -o libast_la-normmap.lo `test -f 'normmap.c' || echo '$(srcdir)/'`normmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-normmap.Tpo $(DEPDIR)/libast_la-normmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='normmap.c' object='libast_la-normmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-normmap.lo `test -f 'normmap.c' || echo '$(srcdir)/'`normmap.c
+
+libast_la-nullregion.lo: nullregion.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-nullregion.lo -MD -MP -MF $(DEPDIR)/libast_la-nullregion.Tpo -c -o libast_la-nullregion.lo `test -f 'nullregion.c' || echo '$(srcdir)/'`nullregion.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-nullregion.Tpo $(DEPDIR)/libast_la-nullregion.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='nullregion.c' object='libast_la-nullregion.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-nullregion.lo `test -f 'nullregion.c' || echo '$(srcdir)/'`nullregion.c
+
+libast_la-object.lo: object.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-object.lo -MD -MP -MF $(DEPDIR)/libast_la-object.Tpo -c -o libast_la-object.lo `test -f 'object.c' || echo '$(srcdir)/'`object.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-object.Tpo $(DEPDIR)/libast_la-object.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='object.c' object='libast_la-object.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-object.lo `test -f 'object.c' || echo '$(srcdir)/'`object.c
+
+libast_la-pcdmap.lo: pcdmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-pcdmap.lo -MD -MP -MF $(DEPDIR)/libast_la-pcdmap.Tpo -c -o libast_la-pcdmap.lo `test -f 'pcdmap.c' || echo '$(srcdir)/'`pcdmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-pcdmap.Tpo $(DEPDIR)/libast_la-pcdmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='pcdmap.c' object='libast_la-pcdmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-pcdmap.lo `test -f 'pcdmap.c' || echo '$(srcdir)/'`pcdmap.c
+
+libast_la-permmap.lo: permmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-permmap.lo -MD -MP -MF $(DEPDIR)/libast_la-permmap.Tpo -c -o libast_la-permmap.lo `test -f 'permmap.c' || echo '$(srcdir)/'`permmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-permmap.Tpo $(DEPDIR)/libast_la-permmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='permmap.c' object='libast_la-permmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-permmap.lo `test -f 'permmap.c' || echo '$(srcdir)/'`permmap.c
+
+libast_la-plot.lo: plot.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-plot.lo -MD -MP -MF $(DEPDIR)/libast_la-plot.Tpo -c -o libast_la-plot.lo `test -f 'plot.c' || echo '$(srcdir)/'`plot.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-plot.Tpo $(DEPDIR)/libast_la-plot.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='plot.c' object='libast_la-plot.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-plot.lo `test -f 'plot.c' || echo '$(srcdir)/'`plot.c
+
+libast_la-plot3d.lo: plot3d.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-plot3d.lo -MD -MP -MF $(DEPDIR)/libast_la-plot3d.Tpo -c -o libast_la-plot3d.lo `test -f 'plot3d.c' || echo '$(srcdir)/'`plot3d.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-plot3d.Tpo $(DEPDIR)/libast_la-plot3d.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='plot3d.c' object='libast_la-plot3d.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-plot3d.lo `test -f 'plot3d.c' || echo '$(srcdir)/'`plot3d.c
+
+libast_la-pointlist.lo: pointlist.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-pointlist.lo -MD -MP -MF $(DEPDIR)/libast_la-pointlist.Tpo -c -o libast_la-pointlist.lo `test -f 'pointlist.c' || echo '$(srcdir)/'`pointlist.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-pointlist.Tpo $(DEPDIR)/libast_la-pointlist.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='pointlist.c' object='libast_la-pointlist.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-pointlist.lo `test -f 'pointlist.c' || echo '$(srcdir)/'`pointlist.c
+
+libast_la-pointset.lo: pointset.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-pointset.lo -MD -MP -MF $(DEPDIR)/libast_la-pointset.Tpo -c -o libast_la-pointset.lo `test -f 'pointset.c' || echo '$(srcdir)/'`pointset.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-pointset.Tpo $(DEPDIR)/libast_la-pointset.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='pointset.c' object='libast_la-pointset.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-pointset.lo `test -f 'pointset.c' || echo '$(srcdir)/'`pointset.c
+
+libast_la-polygon.lo: polygon.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-polygon.lo -MD -MP -MF $(DEPDIR)/libast_la-polygon.Tpo -c -o libast_la-polygon.lo `test -f 'polygon.c' || echo '$(srcdir)/'`polygon.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-polygon.Tpo $(DEPDIR)/libast_la-polygon.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='polygon.c' object='libast_la-polygon.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-polygon.lo `test -f 'polygon.c' || echo '$(srcdir)/'`polygon.c
+
+libast_la-polymap.lo: polymap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-polymap.lo -MD -MP -MF $(DEPDIR)/libast_la-polymap.Tpo -c -o libast_la-polymap.lo `test -f 'polymap.c' || echo '$(srcdir)/'`polymap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-polymap.Tpo $(DEPDIR)/libast_la-polymap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='polymap.c' object='libast_la-polymap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-polymap.lo `test -f 'polymap.c' || echo '$(srcdir)/'`polymap.c
+
+libast_la-prism.lo: prism.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-prism.lo -MD -MP -MF $(DEPDIR)/libast_la-prism.Tpo -c -o libast_la-prism.lo `test -f 'prism.c' || echo '$(srcdir)/'`prism.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-prism.Tpo $(DEPDIR)/libast_la-prism.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='prism.c' object='libast_la-prism.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-prism.lo `test -f 'prism.c' || echo '$(srcdir)/'`prism.c
+
+libast_la-ratemap.lo: ratemap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-ratemap.lo -MD -MP -MF $(DEPDIR)/libast_la-ratemap.Tpo -c -o libast_la-ratemap.lo `test -f 'ratemap.c' || echo '$(srcdir)/'`ratemap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-ratemap.Tpo $(DEPDIR)/libast_la-ratemap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='ratemap.c' object='libast_la-ratemap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-ratemap.lo `test -f 'ratemap.c' || echo '$(srcdir)/'`ratemap.c
+
+libast_la-region.lo: region.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-region.lo -MD -MP -MF $(DEPDIR)/libast_la-region.Tpo -c -o libast_la-region.lo `test -f 'region.c' || echo '$(srcdir)/'`region.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-region.Tpo $(DEPDIR)/libast_la-region.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='region.c' object='libast_la-region.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-region.lo `test -f 'region.c' || echo '$(srcdir)/'`region.c
+
+libast_la-selectormap.lo: selectormap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-selectormap.lo -MD -MP -MF $(DEPDIR)/libast_la-selectormap.Tpo -c -o libast_la-selectormap.lo `test -f 'selectormap.c' || echo '$(srcdir)/'`selectormap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-selectormap.Tpo $(DEPDIR)/libast_la-selectormap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='selectormap.c' object='libast_la-selectormap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-selectormap.lo `test -f 'selectormap.c' || echo '$(srcdir)/'`selectormap.c
+
+libast_la-shiftmap.lo: shiftmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-shiftmap.lo -MD -MP -MF $(DEPDIR)/libast_la-shiftmap.Tpo -c -o libast_la-shiftmap.lo `test -f 'shiftmap.c' || echo '$(srcdir)/'`shiftmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-shiftmap.Tpo $(DEPDIR)/libast_la-shiftmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='shiftmap.c' object='libast_la-shiftmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-shiftmap.lo `test -f 'shiftmap.c' || echo '$(srcdir)/'`shiftmap.c
+
+libast_la-skyaxis.lo: skyaxis.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-skyaxis.lo -MD -MP -MF $(DEPDIR)/libast_la-skyaxis.Tpo -c -o libast_la-skyaxis.lo `test -f 'skyaxis.c' || echo '$(srcdir)/'`skyaxis.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-skyaxis.Tpo $(DEPDIR)/libast_la-skyaxis.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='skyaxis.c' object='libast_la-skyaxis.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-skyaxis.lo `test -f 'skyaxis.c' || echo '$(srcdir)/'`skyaxis.c
+
+libast_la-skyframe.lo: skyframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-skyframe.lo -MD -MP -MF $(DEPDIR)/libast_la-skyframe.Tpo -c -o libast_la-skyframe.lo `test -f 'skyframe.c' || echo '$(srcdir)/'`skyframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-skyframe.Tpo $(DEPDIR)/libast_la-skyframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='skyframe.c' object='libast_la-skyframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-skyframe.lo `test -f 'skyframe.c' || echo '$(srcdir)/'`skyframe.c
+
+libast_la-slamap.lo: slamap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-slamap.lo -MD -MP -MF $(DEPDIR)/libast_la-slamap.Tpo -c -o libast_la-slamap.lo `test -f 'slamap.c' || echo '$(srcdir)/'`slamap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-slamap.Tpo $(DEPDIR)/libast_la-slamap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='slamap.c' object='libast_la-slamap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-slamap.lo `test -f 'slamap.c' || echo '$(srcdir)/'`slamap.c
+
+libast_la-specfluxframe.lo: specfluxframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-specfluxframe.lo -MD -MP -MF $(DEPDIR)/libast_la-specfluxframe.Tpo -c -o libast_la-specfluxframe.lo `test -f 'specfluxframe.c' || echo '$(srcdir)/'`specfluxframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-specfluxframe.Tpo $(DEPDIR)/libast_la-specfluxframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='specfluxframe.c' object='libast_la-specfluxframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-specfluxframe.lo `test -f 'specfluxframe.c' || echo '$(srcdir)/'`specfluxframe.c
+
+libast_la-specframe.lo: specframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-specframe.lo -MD -MP -MF $(DEPDIR)/libast_la-specframe.Tpo -c -o libast_la-specframe.lo `test -f 'specframe.c' || echo '$(srcdir)/'`specframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-specframe.Tpo $(DEPDIR)/libast_la-specframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='specframe.c' object='libast_la-specframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-specframe.lo `test -f 'specframe.c' || echo '$(srcdir)/'`specframe.c
+
+libast_la-specmap.lo: specmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-specmap.lo -MD -MP -MF $(DEPDIR)/libast_la-specmap.Tpo -c -o libast_la-specmap.lo `test -f 'specmap.c' || echo '$(srcdir)/'`specmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-specmap.Tpo $(DEPDIR)/libast_la-specmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='specmap.c' object='libast_la-specmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-specmap.lo `test -f 'specmap.c' || echo '$(srcdir)/'`specmap.c
+
+libast_la-sphmap.lo: sphmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-sphmap.lo -MD -MP -MF $(DEPDIR)/libast_la-sphmap.Tpo -c -o libast_la-sphmap.lo `test -f 'sphmap.c' || echo '$(srcdir)/'`sphmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-sphmap.Tpo $(DEPDIR)/libast_la-sphmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='sphmap.c' object='libast_la-sphmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-sphmap.lo `test -f 'sphmap.c' || echo '$(srcdir)/'`sphmap.c
+
+libast_la-stc.lo: stc.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-stc.lo -MD -MP -MF $(DEPDIR)/libast_la-stc.Tpo -c -o libast_la-stc.lo `test -f 'stc.c' || echo '$(srcdir)/'`stc.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-stc.Tpo $(DEPDIR)/libast_la-stc.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='stc.c' object='libast_la-stc.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-stc.lo `test -f 'stc.c' || echo '$(srcdir)/'`stc.c
+
+libast_la-stccatalogentrylocation.lo: stccatalogentrylocation.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-stccatalogentrylocation.lo -MD -MP -MF $(DEPDIR)/libast_la-stccatalogentrylocation.Tpo -c -o libast_la-stccatalogentrylocation.lo `test -f 'stccatalogentrylocation.c' || echo '$(srcdir)/'`stccatalogentrylocation.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-stccatalogentrylocation.Tpo $(DEPDIR)/libast_la-stccatalogentrylocation.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='stccatalogentrylocation.c' object='libast_la-stccatalogentrylocation.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-stccatalogentrylocation.lo `test -f 'stccatalogentrylocation.c' || echo '$(srcdir)/'`stccatalogentrylocation.c
+
+libast_la-stcobsdatalocation.lo: stcobsdatalocation.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-stcobsdatalocation.lo -MD -MP -MF $(DEPDIR)/libast_la-stcobsdatalocation.Tpo -c -o libast_la-stcobsdatalocation.lo `test -f 'stcobsdatalocation.c' || echo '$(srcdir)/'`stcobsdatalocation.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-stcobsdatalocation.Tpo $(DEPDIR)/libast_la-stcobsdatalocation.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='stcobsdatalocation.c' object='libast_la-stcobsdatalocation.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-stcobsdatalocation.lo `test -f 'stcobsdatalocation.c' || echo '$(srcdir)/'`stcobsdatalocation.c
+
+libast_la-stcresourceprofile.lo: stcresourceprofile.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-stcresourceprofile.lo -MD -MP -MF $(DEPDIR)/libast_la-stcresourceprofile.Tpo -c -o libast_la-stcresourceprofile.lo `test -f 'stcresourceprofile.c' || echo '$(srcdir)/'`stcresourceprofile.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-stcresourceprofile.Tpo $(DEPDIR)/libast_la-stcresourceprofile.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='stcresourceprofile.c' object='libast_la-stcresourceprofile.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-stcresourceprofile.lo `test -f 'stcresourceprofile.c' || echo '$(srcdir)/'`stcresourceprofile.c
+
+libast_la-stcschan.lo: stcschan.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-stcschan.lo -MD -MP -MF $(DEPDIR)/libast_la-stcschan.Tpo -c -o libast_la-stcschan.lo `test -f 'stcschan.c' || echo '$(srcdir)/'`stcschan.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-stcschan.Tpo $(DEPDIR)/libast_la-stcschan.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='stcschan.c' object='libast_la-stcschan.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-stcschan.lo `test -f 'stcschan.c' || echo '$(srcdir)/'`stcschan.c
+
+libast_la-stcsearchlocation.lo: stcsearchlocation.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-stcsearchlocation.lo -MD -MP -MF $(DEPDIR)/libast_la-stcsearchlocation.Tpo -c -o libast_la-stcsearchlocation.lo `test -f 'stcsearchlocation.c' || echo '$(srcdir)/'`stcsearchlocation.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-stcsearchlocation.Tpo $(DEPDIR)/libast_la-stcsearchlocation.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='stcsearchlocation.c' object='libast_la-stcsearchlocation.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-stcsearchlocation.lo `test -f 'stcsearchlocation.c' || echo '$(srcdir)/'`stcsearchlocation.c
+
+libast_la-switchmap.lo: switchmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-switchmap.lo -MD -MP -MF $(DEPDIR)/libast_la-switchmap.Tpo -c -o libast_la-switchmap.lo `test -f 'switchmap.c' || echo '$(srcdir)/'`switchmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-switchmap.Tpo $(DEPDIR)/libast_la-switchmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='switchmap.c' object='libast_la-switchmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-switchmap.lo `test -f 'switchmap.c' || echo '$(srcdir)/'`switchmap.c
+
+libast_la-table.lo: table.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-table.lo -MD -MP -MF $(DEPDIR)/libast_la-table.Tpo -c -o libast_la-table.lo `test -f 'table.c' || echo '$(srcdir)/'`table.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-table.Tpo $(DEPDIR)/libast_la-table.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='table.c' object='libast_la-table.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-table.lo `test -f 'table.c' || echo '$(srcdir)/'`table.c
+
+libast_la-timeframe.lo: timeframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-timeframe.lo -MD -MP -MF $(DEPDIR)/libast_la-timeframe.Tpo -c -o libast_la-timeframe.lo `test -f 'timeframe.c' || echo '$(srcdir)/'`timeframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-timeframe.Tpo $(DEPDIR)/libast_la-timeframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='timeframe.c' object='libast_la-timeframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-timeframe.lo `test -f 'timeframe.c' || echo '$(srcdir)/'`timeframe.c
+
+libast_la-timemap.lo: timemap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-timemap.lo -MD -MP -MF $(DEPDIR)/libast_la-timemap.Tpo -c -o libast_la-timemap.lo `test -f 'timemap.c' || echo '$(srcdir)/'`timemap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-timemap.Tpo $(DEPDIR)/libast_la-timemap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='timemap.c' object='libast_la-timemap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-timemap.lo `test -f 'timemap.c' || echo '$(srcdir)/'`timemap.c
+
+libast_la-tranmap.lo: tranmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-tranmap.lo -MD -MP -MF $(DEPDIR)/libast_la-tranmap.Tpo -c -o libast_la-tranmap.lo `test -f 'tranmap.c' || echo '$(srcdir)/'`tranmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-tranmap.Tpo $(DEPDIR)/libast_la-tranmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='tranmap.c' object='libast_la-tranmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-tranmap.lo `test -f 'tranmap.c' || echo '$(srcdir)/'`tranmap.c
+
+libast_la-unit.lo: unit.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-unit.lo -MD -MP -MF $(DEPDIR)/libast_la-unit.Tpo -c -o libast_la-unit.lo `test -f 'unit.c' || echo '$(srcdir)/'`unit.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-unit.Tpo $(DEPDIR)/libast_la-unit.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='unit.c' object='libast_la-unit.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-unit.lo `test -f 'unit.c' || echo '$(srcdir)/'`unit.c
+
+libast_la-unitmap.lo: unitmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-unitmap.lo -MD -MP -MF $(DEPDIR)/libast_la-unitmap.Tpo -c -o libast_la-unitmap.lo `test -f 'unitmap.c' || echo '$(srcdir)/'`unitmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-unitmap.Tpo $(DEPDIR)/libast_la-unitmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='unitmap.c' object='libast_la-unitmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-unitmap.lo `test -f 'unitmap.c' || echo '$(srcdir)/'`unitmap.c
+
+libast_la-unitnormmap.lo: unitnormmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-unitnormmap.lo -MD -MP -MF $(DEPDIR)/libast_la-unitnormmap.Tpo -c -o libast_la-unitnormmap.lo `test -f 'unitnormmap.c' || echo '$(srcdir)/'`unitnormmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-unitnormmap.Tpo $(DEPDIR)/libast_la-unitnormmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='unitnormmap.c' object='libast_la-unitnormmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-unitnormmap.lo `test -f 'unitnormmap.c' || echo '$(srcdir)/'`unitnormmap.c
+
+libast_la-wcsmap.lo: wcsmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-wcsmap.lo -MD -MP -MF $(DEPDIR)/libast_la-wcsmap.Tpo -c -o libast_la-wcsmap.lo `test -f 'wcsmap.c' || echo '$(srcdir)/'`wcsmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-wcsmap.Tpo $(DEPDIR)/libast_la-wcsmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='wcsmap.c' object='libast_la-wcsmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-wcsmap.lo `test -f 'wcsmap.c' || echo '$(srcdir)/'`wcsmap.c
+
+libast_la-winmap.lo: winmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-winmap.lo -MD -MP -MF $(DEPDIR)/libast_la-winmap.Tpo -c -o libast_la-winmap.lo `test -f 'winmap.c' || echo '$(srcdir)/'`winmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-winmap.Tpo $(DEPDIR)/libast_la-winmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='winmap.c' object='libast_la-winmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-winmap.lo `test -f 'winmap.c' || echo '$(srcdir)/'`winmap.c
+
+libast_la-xml.lo: xml.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-xml.lo -MD -MP -MF $(DEPDIR)/libast_la-xml.Tpo -c -o libast_la-xml.lo `test -f 'xml.c' || echo '$(srcdir)/'`xml.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-xml.Tpo $(DEPDIR)/libast_la-xml.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='xml.c' object='libast_la-xml.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-xml.lo `test -f 'xml.c' || echo '$(srcdir)/'`xml.c
+
+libast_la-xmlchan.lo: xmlchan.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-xmlchan.lo -MD -MP -MF $(DEPDIR)/libast_la-xmlchan.Tpo -c -o libast_la-xmlchan.lo `test -f 'xmlchan.c' || echo '$(srcdir)/'`xmlchan.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-xmlchan.Tpo $(DEPDIR)/libast_la-xmlchan.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='xmlchan.c' object='libast_la-xmlchan.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-xmlchan.lo `test -f 'xmlchan.c' || echo '$(srcdir)/'`xmlchan.c
+
+libast_la-zoommap.lo: zoommap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-zoommap.lo -MD -MP -MF $(DEPDIR)/libast_la-zoommap.Tpo -c -o libast_la-zoommap.lo `test -f 'zoommap.c' || echo '$(srcdir)/'`zoommap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-zoommap.Tpo $(DEPDIR)/libast_la-zoommap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='zoommap.c' object='libast_la-zoommap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-zoommap.lo `test -f 'zoommap.c' || echo '$(srcdir)/'`zoommap.c
+
+libast_la-c2f77.lo: c2f77.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-c2f77.lo -MD -MP -MF $(DEPDIR)/libast_la-c2f77.Tpo -c -o libast_la-c2f77.lo `test -f 'c2f77.c' || echo '$(srcdir)/'`c2f77.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-c2f77.Tpo $(DEPDIR)/libast_la-c2f77.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='c2f77.c' object='libast_la-c2f77.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-c2f77.lo `test -f 'c2f77.c' || echo '$(srcdir)/'`c2f77.c
+
+libast_la-fbox.lo: fbox.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fbox.lo -MD -MP -MF $(DEPDIR)/libast_la-fbox.Tpo -c -o libast_la-fbox.lo `test -f 'fbox.c' || echo '$(srcdir)/'`fbox.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fbox.Tpo $(DEPDIR)/libast_la-fbox.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fbox.c' object='libast_la-fbox.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fbox.lo `test -f 'fbox.c' || echo '$(srcdir)/'`fbox.c
+
+libast_la-fchannel.lo: fchannel.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fchannel.lo -MD -MP -MF $(DEPDIR)/libast_la-fchannel.Tpo -c -o libast_la-fchannel.lo `test -f 'fchannel.c' || echo '$(srcdir)/'`fchannel.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fchannel.Tpo $(DEPDIR)/libast_la-fchannel.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fchannel.c' object='libast_la-fchannel.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fchannel.lo `test -f 'fchannel.c' || echo '$(srcdir)/'`fchannel.c
+
+libast_la-fcircle.lo: fcircle.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fcircle.lo -MD -MP -MF $(DEPDIR)/libast_la-fcircle.Tpo -c -o libast_la-fcircle.lo `test -f 'fcircle.c' || echo '$(srcdir)/'`fcircle.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fcircle.Tpo $(DEPDIR)/libast_la-fcircle.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fcircle.c' object='libast_la-fcircle.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fcircle.lo `test -f 'fcircle.c' || echo '$(srcdir)/'`fcircle.c
+
+libast_la-fcmpframe.lo: fcmpframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fcmpframe.lo -MD -MP -MF $(DEPDIR)/libast_la-fcmpframe.Tpo -c -o libast_la-fcmpframe.lo `test -f 'fcmpframe.c' || echo '$(srcdir)/'`fcmpframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fcmpframe.Tpo $(DEPDIR)/libast_la-fcmpframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fcmpframe.c' object='libast_la-fcmpframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fcmpframe.lo `test -f 'fcmpframe.c' || echo '$(srcdir)/'`fcmpframe.c
+
+libast_la-fcmpmap.lo: fcmpmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fcmpmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fcmpmap.Tpo -c -o libast_la-fcmpmap.lo `test -f 'fcmpmap.c' || echo '$(srcdir)/'`fcmpmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fcmpmap.Tpo $(DEPDIR)/libast_la-fcmpmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fcmpmap.c' object='libast_la-fcmpmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fcmpmap.lo `test -f 'fcmpmap.c' || echo '$(srcdir)/'`fcmpmap.c
+
+libast_la-fcmpregion.lo: fcmpregion.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fcmpregion.lo -MD -MP -MF $(DEPDIR)/libast_la-fcmpregion.Tpo -c -o libast_la-fcmpregion.lo `test -f 'fcmpregion.c' || echo '$(srcdir)/'`fcmpregion.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fcmpregion.Tpo $(DEPDIR)/libast_la-fcmpregion.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fcmpregion.c' object='libast_la-fcmpregion.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fcmpregion.lo `test -f 'fcmpregion.c' || echo '$(srcdir)/'`fcmpregion.c
+
+libast_la-fdsbspecframe.lo: fdsbspecframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fdsbspecframe.lo -MD -MP -MF $(DEPDIR)/libast_la-fdsbspecframe.Tpo -c -o libast_la-fdsbspecframe.lo `test -f 'fdsbspecframe.c' || echo '$(srcdir)/'`fdsbspecframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fdsbspecframe.Tpo $(DEPDIR)/libast_la-fdsbspecframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fdsbspecframe.c' object='libast_la-fdsbspecframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fdsbspecframe.lo `test -f 'fdsbspecframe.c' || echo '$(srcdir)/'`fdsbspecframe.c
+
+libast_la-fdssmap.lo: fdssmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fdssmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fdssmap.Tpo -c -o libast_la-fdssmap.lo `test -f 'fdssmap.c' || echo '$(srcdir)/'`fdssmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fdssmap.Tpo $(DEPDIR)/libast_la-fdssmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fdssmap.c' object='libast_la-fdssmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fdssmap.lo `test -f 'fdssmap.c' || echo '$(srcdir)/'`fdssmap.c
+
+libast_la-fellipse.lo: fellipse.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fellipse.lo -MD -MP -MF $(DEPDIR)/libast_la-fellipse.Tpo -c -o libast_la-fellipse.lo `test -f 'fellipse.c' || echo '$(srcdir)/'`fellipse.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fellipse.Tpo $(DEPDIR)/libast_la-fellipse.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fellipse.c' object='libast_la-fellipse.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fellipse.lo `test -f 'fellipse.c' || echo '$(srcdir)/'`fellipse.c
+
+libast_la-ferror.lo: ferror.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-ferror.lo -MD -MP -MF $(DEPDIR)/libast_la-ferror.Tpo -c -o libast_la-ferror.lo `test -f 'ferror.c' || echo '$(srcdir)/'`ferror.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-ferror.Tpo $(DEPDIR)/libast_la-ferror.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='ferror.c' object='libast_la-ferror.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-ferror.lo `test -f 'ferror.c' || echo '$(srcdir)/'`ferror.c
+
+libast_la-ffitschan.lo: ffitschan.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-ffitschan.lo -MD -MP -MF $(DEPDIR)/libast_la-ffitschan.Tpo -c -o libast_la-ffitschan.lo `test -f 'ffitschan.c' || echo '$(srcdir)/'`ffitschan.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-ffitschan.Tpo $(DEPDIR)/libast_la-ffitschan.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='ffitschan.c' object='libast_la-ffitschan.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-ffitschan.lo `test -f 'ffitschan.c' || echo '$(srcdir)/'`ffitschan.c
+
+libast_la-ffitstable.lo: ffitstable.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-ffitstable.lo -MD -MP -MF $(DEPDIR)/libast_la-ffitstable.Tpo -c -o libast_la-ffitstable.lo `test -f 'ffitstable.c' || echo '$(srcdir)/'`ffitstable.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-ffitstable.Tpo $(DEPDIR)/libast_la-ffitstable.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='ffitstable.c' object='libast_la-ffitstable.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-ffitstable.lo `test -f 'ffitstable.c' || echo '$(srcdir)/'`ffitstable.c
+
+libast_la-ffluxframe.lo: ffluxframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-ffluxframe.lo -MD -MP -MF $(DEPDIR)/libast_la-ffluxframe.Tpo -c -o libast_la-ffluxframe.lo `test -f 'ffluxframe.c' || echo '$(srcdir)/'`ffluxframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-ffluxframe.Tpo $(DEPDIR)/libast_la-ffluxframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='ffluxframe.c' object='libast_la-ffluxframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-ffluxframe.lo `test -f 'ffluxframe.c' || echo '$(srcdir)/'`ffluxframe.c
+
+libast_la-fframe.lo: fframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fframe.lo -MD -MP -MF $(DEPDIR)/libast_la-fframe.Tpo -c -o libast_la-fframe.lo `test -f 'fframe.c' || echo '$(srcdir)/'`fframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fframe.Tpo $(DEPDIR)/libast_la-fframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fframe.c' object='libast_la-fframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fframe.lo `test -f 'fframe.c' || echo '$(srcdir)/'`fframe.c
+
+libast_la-fframeset.lo: fframeset.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fframeset.lo -MD -MP -MF $(DEPDIR)/libast_la-fframeset.Tpo -c -o libast_la-fframeset.lo `test -f 'fframeset.c' || echo '$(srcdir)/'`fframeset.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fframeset.Tpo $(DEPDIR)/libast_la-fframeset.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fframeset.c' object='libast_la-fframeset.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fframeset.lo `test -f 'fframeset.c' || echo '$(srcdir)/'`fframeset.c
+
+libast_la-fgrismmap.lo: fgrismmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fgrismmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fgrismmap.Tpo -c -o libast_la-fgrismmap.lo `test -f 'fgrismmap.c' || echo '$(srcdir)/'`fgrismmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fgrismmap.Tpo $(DEPDIR)/libast_la-fgrismmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fgrismmap.c' object='libast_la-fgrismmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fgrismmap.lo `test -f 'fgrismmap.c' || echo '$(srcdir)/'`fgrismmap.c
+
+libast_la-finterval.lo: finterval.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-finterval.lo -MD -MP -MF $(DEPDIR)/libast_la-finterval.Tpo -c -o libast_la-finterval.lo `test -f 'finterval.c' || echo '$(srcdir)/'`finterval.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-finterval.Tpo $(DEPDIR)/libast_la-finterval.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='finterval.c' object='libast_la-finterval.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-finterval.lo `test -f 'finterval.c' || echo '$(srcdir)/'`finterval.c
+
+libast_la-fintramap.lo: fintramap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fintramap.lo -MD -MP -MF $(DEPDIR)/libast_la-fintramap.Tpo -c -o libast_la-fintramap.lo `test -f 'fintramap.c' || echo '$(srcdir)/'`fintramap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fintramap.Tpo $(DEPDIR)/libast_la-fintramap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fintramap.c' object='libast_la-fintramap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fintramap.lo `test -f 'fintramap.c' || echo '$(srcdir)/'`fintramap.c
+
+libast_la-fkeymap.lo: fkeymap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fkeymap.lo -MD -MP -MF $(DEPDIR)/libast_la-fkeymap.Tpo -c -o libast_la-fkeymap.lo `test -f 'fkeymap.c' || echo '$(srcdir)/'`fkeymap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fkeymap.Tpo $(DEPDIR)/libast_la-fkeymap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fkeymap.c' object='libast_la-fkeymap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fkeymap.lo `test -f 'fkeymap.c' || echo '$(srcdir)/'`fkeymap.c
+
+libast_la-flutmap.lo: flutmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-flutmap.lo -MD -MP -MF $(DEPDIR)/libast_la-flutmap.Tpo -c -o libast_la-flutmap.lo `test -f 'flutmap.c' || echo '$(srcdir)/'`flutmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-flutmap.Tpo $(DEPDIR)/libast_la-flutmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='flutmap.c' object='libast_la-flutmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-flutmap.lo `test -f 'flutmap.c' || echo '$(srcdir)/'`flutmap.c
+
+libast_la-fmapping.lo: fmapping.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fmapping.lo -MD -MP -MF $(DEPDIR)/libast_la-fmapping.Tpo -c -o libast_la-fmapping.lo `test -f 'fmapping.c' || echo '$(srcdir)/'`fmapping.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fmapping.Tpo $(DEPDIR)/libast_la-fmapping.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fmapping.c' object='libast_la-fmapping.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fmapping.lo `test -f 'fmapping.c' || echo '$(srcdir)/'`fmapping.c
+
+libast_la-fmathmap.lo: fmathmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fmathmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fmathmap.Tpo -c -o libast_la-fmathmap.lo `test -f 'fmathmap.c' || echo '$(srcdir)/'`fmathmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fmathmap.Tpo $(DEPDIR)/libast_la-fmathmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fmathmap.c' object='libast_la-fmathmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fmathmap.lo `test -f 'fmathmap.c' || echo '$(srcdir)/'`fmathmap.c
+
+libast_la-fmatrixmap.lo: fmatrixmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fmatrixmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fmatrixmap.Tpo -c -o libast_la-fmatrixmap.lo `test -f 'fmatrixmap.c' || echo '$(srcdir)/'`fmatrixmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fmatrixmap.Tpo $(DEPDIR)/libast_la-fmatrixmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fmatrixmap.c' object='libast_la-fmatrixmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fmatrixmap.lo `test -f 'fmatrixmap.c' || echo '$(srcdir)/'`fmatrixmap.c
+
+libast_la-fnormmap.lo: fnormmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fnormmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fnormmap.Tpo -c -o libast_la-fnormmap.lo `test -f 'fnormmap.c' || echo '$(srcdir)/'`fnormmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fnormmap.Tpo $(DEPDIR)/libast_la-fnormmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fnormmap.c' object='libast_la-fnormmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fnormmap.lo `test -f 'fnormmap.c' || echo '$(srcdir)/'`fnormmap.c
+
+libast_la-fnullregion.lo: fnullregion.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fnullregion.lo -MD -MP -MF $(DEPDIR)/libast_la-fnullregion.Tpo -c -o libast_la-fnullregion.lo `test -f 'fnullregion.c' || echo '$(srcdir)/'`fnullregion.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fnullregion.Tpo $(DEPDIR)/libast_la-fnullregion.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fnullregion.c' object='libast_la-fnullregion.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fnullregion.lo `test -f 'fnullregion.c' || echo '$(srcdir)/'`fnullregion.c
+
+libast_la-fobject.lo: fobject.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fobject.lo -MD -MP -MF $(DEPDIR)/libast_la-fobject.Tpo -c -o libast_la-fobject.lo `test -f 'fobject.c' || echo '$(srcdir)/'`fobject.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fobject.Tpo $(DEPDIR)/libast_la-fobject.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fobject.c' object='libast_la-fobject.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fobject.lo `test -f 'fobject.c' || echo '$(srcdir)/'`fobject.c
+
+libast_la-fpcdmap.lo: fpcdmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fpcdmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fpcdmap.Tpo -c -o libast_la-fpcdmap.lo `test -f 'fpcdmap.c' || echo '$(srcdir)/'`fpcdmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fpcdmap.Tpo $(DEPDIR)/libast_la-fpcdmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fpcdmap.c' object='libast_la-fpcdmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fpcdmap.lo `test -f 'fpcdmap.c' || echo '$(srcdir)/'`fpcdmap.c
+
+libast_la-fpermmap.lo: fpermmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fpermmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fpermmap.Tpo -c -o libast_la-fpermmap.lo `test -f 'fpermmap.c' || echo '$(srcdir)/'`fpermmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fpermmap.Tpo $(DEPDIR)/libast_la-fpermmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fpermmap.c' object='libast_la-fpermmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fpermmap.lo `test -f 'fpermmap.c' || echo '$(srcdir)/'`fpermmap.c
+
+libast_la-fplot.lo: fplot.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fplot.lo -MD -MP -MF $(DEPDIR)/libast_la-fplot.Tpo -c -o libast_la-fplot.lo `test -f 'fplot.c' || echo '$(srcdir)/'`fplot.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fplot.Tpo $(DEPDIR)/libast_la-fplot.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fplot.c' object='libast_la-fplot.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fplot.lo `test -f 'fplot.c' || echo '$(srcdir)/'`fplot.c
+
+libast_la-fplot3d.lo: fplot3d.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fplot3d.lo -MD -MP -MF $(DEPDIR)/libast_la-fplot3d.Tpo -c -o libast_la-fplot3d.lo `test -f 'fplot3d.c' || echo '$(srcdir)/'`fplot3d.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fplot3d.Tpo $(DEPDIR)/libast_la-fplot3d.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fplot3d.c' object='libast_la-fplot3d.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fplot3d.lo `test -f 'fplot3d.c' || echo '$(srcdir)/'`fplot3d.c
+
+libast_la-fpointlist.lo: fpointlist.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fpointlist.lo -MD -MP -MF $(DEPDIR)/libast_la-fpointlist.Tpo -c -o libast_la-fpointlist.lo `test -f 'fpointlist.c' || echo '$(srcdir)/'`fpointlist.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fpointlist.Tpo $(DEPDIR)/libast_la-fpointlist.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fpointlist.c' object='libast_la-fpointlist.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fpointlist.lo `test -f 'fpointlist.c' || echo '$(srcdir)/'`fpointlist.c
+
+libast_la-fpolygon.lo: fpolygon.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fpolygon.lo -MD -MP -MF $(DEPDIR)/libast_la-fpolygon.Tpo -c -o libast_la-fpolygon.lo `test -f 'fpolygon.c' || echo '$(srcdir)/'`fpolygon.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fpolygon.Tpo $(DEPDIR)/libast_la-fpolygon.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fpolygon.c' object='libast_la-fpolygon.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fpolygon.lo `test -f 'fpolygon.c' || echo '$(srcdir)/'`fpolygon.c
+
+libast_la-fpolymap.lo: fpolymap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fpolymap.lo -MD -MP -MF $(DEPDIR)/libast_la-fpolymap.Tpo -c -o libast_la-fpolymap.lo `test -f 'fpolymap.c' || echo '$(srcdir)/'`fpolymap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fpolymap.Tpo $(DEPDIR)/libast_la-fpolymap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fpolymap.c' object='libast_la-fpolymap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fpolymap.lo `test -f 'fpolymap.c' || echo '$(srcdir)/'`fpolymap.c
+
+libast_la-fprism.lo: fprism.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fprism.lo -MD -MP -MF $(DEPDIR)/libast_la-fprism.Tpo -c -o libast_la-fprism.lo `test -f 'fprism.c' || echo '$(srcdir)/'`fprism.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fprism.Tpo $(DEPDIR)/libast_la-fprism.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fprism.c' object='libast_la-fprism.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fprism.lo `test -f 'fprism.c' || echo '$(srcdir)/'`fprism.c
+
+libast_la-fratemap.lo: fratemap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fratemap.lo -MD -MP -MF $(DEPDIR)/libast_la-fratemap.Tpo -c -o libast_la-fratemap.lo `test -f 'fratemap.c' || echo '$(srcdir)/'`fratemap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fratemap.Tpo $(DEPDIR)/libast_la-fratemap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fratemap.c' object='libast_la-fratemap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fratemap.lo `test -f 'fratemap.c' || echo '$(srcdir)/'`fratemap.c
+
+libast_la-fregion.lo: fregion.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fregion.lo -MD -MP -MF $(DEPDIR)/libast_la-fregion.Tpo -c -o libast_la-fregion.lo `test -f 'fregion.c' || echo '$(srcdir)/'`fregion.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fregion.Tpo $(DEPDIR)/libast_la-fregion.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fregion.c' object='libast_la-fregion.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fregion.lo `test -f 'fregion.c' || echo '$(srcdir)/'`fregion.c
+
+libast_la-fselectormap.lo: fselectormap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fselectormap.lo -MD -MP -MF $(DEPDIR)/libast_la-fselectormap.Tpo -c -o libast_la-fselectormap.lo `test -f 'fselectormap.c' || echo '$(srcdir)/'`fselectormap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fselectormap.Tpo $(DEPDIR)/libast_la-fselectormap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fselectormap.c' object='libast_la-fselectormap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fselectormap.lo `test -f 'fselectormap.c' || echo '$(srcdir)/'`fselectormap.c
+
+libast_la-fshiftmap.lo: fshiftmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fshiftmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fshiftmap.Tpo -c -o libast_la-fshiftmap.lo `test -f 'fshiftmap.c' || echo '$(srcdir)/'`fshiftmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fshiftmap.Tpo $(DEPDIR)/libast_la-fshiftmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fshiftmap.c' object='libast_la-fshiftmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fshiftmap.lo `test -f 'fshiftmap.c' || echo '$(srcdir)/'`fshiftmap.c
+
+libast_la-fskyframe.lo: fskyframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fskyframe.lo -MD -MP -MF $(DEPDIR)/libast_la-fskyframe.Tpo -c -o libast_la-fskyframe.lo `test -f 'fskyframe.c' || echo '$(srcdir)/'`fskyframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fskyframe.Tpo $(DEPDIR)/libast_la-fskyframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fskyframe.c' object='libast_la-fskyframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fskyframe.lo `test -f 'fskyframe.c' || echo '$(srcdir)/'`fskyframe.c
+
+libast_la-fslamap.lo: fslamap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fslamap.lo -MD -MP -MF $(DEPDIR)/libast_la-fslamap.Tpo -c -o libast_la-fslamap.lo `test -f 'fslamap.c' || echo '$(srcdir)/'`fslamap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fslamap.Tpo $(DEPDIR)/libast_la-fslamap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fslamap.c' object='libast_la-fslamap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fslamap.lo `test -f 'fslamap.c' || echo '$(srcdir)/'`fslamap.c
+
+libast_la-fspecfluxframe.lo: fspecfluxframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fspecfluxframe.lo -MD -MP -MF $(DEPDIR)/libast_la-fspecfluxframe.Tpo -c -o libast_la-fspecfluxframe.lo `test -f 'fspecfluxframe.c' || echo '$(srcdir)/'`fspecfluxframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fspecfluxframe.Tpo $(DEPDIR)/libast_la-fspecfluxframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fspecfluxframe.c' object='libast_la-fspecfluxframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fspecfluxframe.lo `test -f 'fspecfluxframe.c' || echo '$(srcdir)/'`fspecfluxframe.c
+
+libast_la-fspecframe.lo: fspecframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fspecframe.lo -MD -MP -MF $(DEPDIR)/libast_la-fspecframe.Tpo -c -o libast_la-fspecframe.lo `test -f 'fspecframe.c' || echo '$(srcdir)/'`fspecframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fspecframe.Tpo $(DEPDIR)/libast_la-fspecframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fspecframe.c' object='libast_la-fspecframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fspecframe.lo `test -f 'fspecframe.c' || echo '$(srcdir)/'`fspecframe.c
+
+libast_la-fspecmap.lo: fspecmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fspecmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fspecmap.Tpo -c -o libast_la-fspecmap.lo `test -f 'fspecmap.c' || echo '$(srcdir)/'`fspecmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fspecmap.Tpo $(DEPDIR)/libast_la-fspecmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fspecmap.c' object='libast_la-fspecmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fspecmap.lo `test -f 'fspecmap.c' || echo '$(srcdir)/'`fspecmap.c
+
+libast_la-fsphmap.lo: fsphmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fsphmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fsphmap.Tpo -c -o libast_la-fsphmap.lo `test -f 'fsphmap.c' || echo '$(srcdir)/'`fsphmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fsphmap.Tpo $(DEPDIR)/libast_la-fsphmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fsphmap.c' object='libast_la-fsphmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fsphmap.lo `test -f 'fsphmap.c' || echo '$(srcdir)/'`fsphmap.c
+
+libast_la-fstc.lo: fstc.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fstc.lo -MD -MP -MF $(DEPDIR)/libast_la-fstc.Tpo -c -o libast_la-fstc.lo `test -f 'fstc.c' || echo '$(srcdir)/'`fstc.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fstc.Tpo $(DEPDIR)/libast_la-fstc.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fstc.c' object='libast_la-fstc.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fstc.lo `test -f 'fstc.c' || echo '$(srcdir)/'`fstc.c
+
+libast_la-fstccatalogentrylocation.lo: fstccatalogentrylocation.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fstccatalogentrylocation.lo -MD -MP -MF $(DEPDIR)/libast_la-fstccatalogentrylocation.Tpo -c -o libast_la-fstccatalogentrylocation.lo `test -f 'fstccatalogentrylocation.c' || echo '$(srcdir)/'`fstccatalogentrylocation.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fstccatalogentrylocation.Tpo $(DEPDIR)/libast_la-fstccatalogentrylocation.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fstccatalogentrylocation.c' object='libast_la-fstccatalogentrylocation.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fstccatalogentrylocation.lo `test -f 'fstccatalogentrylocation.c' || echo '$(srcdir)/'`fstccatalogentrylocation.c
+
+libast_la-fstcobsdatalocation.lo: fstcobsdatalocation.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fstcobsdatalocation.lo -MD -MP -MF $(DEPDIR)/libast_la-fstcobsdatalocation.Tpo -c -o libast_la-fstcobsdatalocation.lo `test -f 'fstcobsdatalocation.c' || echo '$(srcdir)/'`fstcobsdatalocation.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fstcobsdatalocation.Tpo $(DEPDIR)/libast_la-fstcobsdatalocation.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fstcobsdatalocation.c' object='libast_la-fstcobsdatalocation.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fstcobsdatalocation.lo `test -f 'fstcobsdatalocation.c' || echo '$(srcdir)/'`fstcobsdatalocation.c
+
+libast_la-fstcresourceprofile.lo: fstcresourceprofile.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fstcresourceprofile.lo -MD -MP -MF $(DEPDIR)/libast_la-fstcresourceprofile.Tpo -c -o libast_la-fstcresourceprofile.lo `test -f 'fstcresourceprofile.c' || echo '$(srcdir)/'`fstcresourceprofile.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fstcresourceprofile.Tpo $(DEPDIR)/libast_la-fstcresourceprofile.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fstcresourceprofile.c' object='libast_la-fstcresourceprofile.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fstcresourceprofile.lo `test -f 'fstcresourceprofile.c' || echo '$(srcdir)/'`fstcresourceprofile.c
+
+libast_la-fstcschan.lo: fstcschan.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fstcschan.lo -MD -MP -MF $(DEPDIR)/libast_la-fstcschan.Tpo -c -o libast_la-fstcschan.lo `test -f 'fstcschan.c' || echo '$(srcdir)/'`fstcschan.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fstcschan.Tpo $(DEPDIR)/libast_la-fstcschan.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fstcschan.c' object='libast_la-fstcschan.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fstcschan.lo `test -f 'fstcschan.c' || echo '$(srcdir)/'`fstcschan.c
+
+libast_la-fstcsearchlocation.lo: fstcsearchlocation.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fstcsearchlocation.lo -MD -MP -MF $(DEPDIR)/libast_la-fstcsearchlocation.Tpo -c -o libast_la-fstcsearchlocation.lo `test -f 'fstcsearchlocation.c' || echo '$(srcdir)/'`fstcsearchlocation.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fstcsearchlocation.Tpo $(DEPDIR)/libast_la-fstcsearchlocation.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fstcsearchlocation.c' object='libast_la-fstcsearchlocation.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fstcsearchlocation.lo `test -f 'fstcsearchlocation.c' || echo '$(srcdir)/'`fstcsearchlocation.c
+
+libast_la-fswitchmap.lo: fswitchmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fswitchmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fswitchmap.Tpo -c -o libast_la-fswitchmap.lo `test -f 'fswitchmap.c' || echo '$(srcdir)/'`fswitchmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fswitchmap.Tpo $(DEPDIR)/libast_la-fswitchmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fswitchmap.c' object='libast_la-fswitchmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fswitchmap.lo `test -f 'fswitchmap.c' || echo '$(srcdir)/'`fswitchmap.c
+
+libast_la-ftable.lo: ftable.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-ftable.lo -MD -MP -MF $(DEPDIR)/libast_la-ftable.Tpo -c -o libast_la-ftable.lo `test -f 'ftable.c' || echo '$(srcdir)/'`ftable.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-ftable.Tpo $(DEPDIR)/libast_la-ftable.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='ftable.c' object='libast_la-ftable.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-ftable.lo `test -f 'ftable.c' || echo '$(srcdir)/'`ftable.c
+
+libast_la-ftimeframe.lo: ftimeframe.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-ftimeframe.lo -MD -MP -MF $(DEPDIR)/libast_la-ftimeframe.Tpo -c -o libast_la-ftimeframe.lo `test -f 'ftimeframe.c' || echo '$(srcdir)/'`ftimeframe.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-ftimeframe.Tpo $(DEPDIR)/libast_la-ftimeframe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='ftimeframe.c' object='libast_la-ftimeframe.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-ftimeframe.lo `test -f 'ftimeframe.c' || echo '$(srcdir)/'`ftimeframe.c
+
+libast_la-ftimemap.lo: ftimemap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-ftimemap.lo -MD -MP -MF $(DEPDIR)/libast_la-ftimemap.Tpo -c -o libast_la-ftimemap.lo `test -f 'ftimemap.c' || echo '$(srcdir)/'`ftimemap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-ftimemap.Tpo $(DEPDIR)/libast_la-ftimemap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='ftimemap.c' object='libast_la-ftimemap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-ftimemap.lo `test -f 'ftimemap.c' || echo '$(srcdir)/'`ftimemap.c
+
+libast_la-ftranmap.lo: ftranmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-ftranmap.lo -MD -MP -MF $(DEPDIR)/libast_la-ftranmap.Tpo -c -o libast_la-ftranmap.lo `test -f 'ftranmap.c' || echo '$(srcdir)/'`ftranmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-ftranmap.Tpo $(DEPDIR)/libast_la-ftranmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='ftranmap.c' object='libast_la-ftranmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-ftranmap.lo `test -f 'ftranmap.c' || echo '$(srcdir)/'`ftranmap.c
+
+libast_la-funitmap.lo: funitmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-funitmap.lo -MD -MP -MF $(DEPDIR)/libast_la-funitmap.Tpo -c -o libast_la-funitmap.lo `test -f 'funitmap.c' || echo '$(srcdir)/'`funitmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-funitmap.Tpo $(DEPDIR)/libast_la-funitmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='funitmap.c' object='libast_la-funitmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-funitmap.lo `test -f 'funitmap.c' || echo '$(srcdir)/'`funitmap.c
+
+libast_la-funitnormmap.lo: funitnormmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-funitnormmap.lo -MD -MP -MF $(DEPDIR)/libast_la-funitnormmap.Tpo -c -o libast_la-funitnormmap.lo `test -f 'funitnormmap.c' || echo '$(srcdir)/'`funitnormmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-funitnormmap.Tpo $(DEPDIR)/libast_la-funitnormmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='funitnormmap.c' object='libast_la-funitnormmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-funitnormmap.lo `test -f 'funitnormmap.c' || echo '$(srcdir)/'`funitnormmap.c
+
+libast_la-fwcsmap.lo: fwcsmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fwcsmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fwcsmap.Tpo -c -o libast_la-fwcsmap.lo `test -f 'fwcsmap.c' || echo '$(srcdir)/'`fwcsmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fwcsmap.Tpo $(DEPDIR)/libast_la-fwcsmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fwcsmap.c' object='libast_la-fwcsmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fwcsmap.lo `test -f 'fwcsmap.c' || echo '$(srcdir)/'`fwcsmap.c
+
+libast_la-fwinmap.lo: fwinmap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fwinmap.lo -MD -MP -MF $(DEPDIR)/libast_la-fwinmap.Tpo -c -o libast_la-fwinmap.lo `test -f 'fwinmap.c' || echo '$(srcdir)/'`fwinmap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fwinmap.Tpo $(DEPDIR)/libast_la-fwinmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fwinmap.c' object='libast_la-fwinmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fwinmap.lo `test -f 'fwinmap.c' || echo '$(srcdir)/'`fwinmap.c
+
+libast_la-fxmlchan.lo: fxmlchan.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fxmlchan.lo -MD -MP -MF $(DEPDIR)/libast_la-fxmlchan.Tpo -c -o libast_la-fxmlchan.lo `test -f 'fxmlchan.c' || echo '$(srcdir)/'`fxmlchan.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fxmlchan.Tpo $(DEPDIR)/libast_la-fxmlchan.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fxmlchan.c' object='libast_la-fxmlchan.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fxmlchan.lo `test -f 'fxmlchan.c' || echo '$(srcdir)/'`fxmlchan.c
+
+libast_la-fzoommap.lo: fzoommap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-fzoommap.lo -MD -MP -MF $(DEPDIR)/libast_la-fzoommap.Tpo -c -o libast_la-fzoommap.lo `test -f 'fzoommap.c' || echo '$(srcdir)/'`fzoommap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-fzoommap.Tpo $(DEPDIR)/libast_la-fzoommap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='fzoommap.c' object='libast_la-fzoommap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-fzoommap.lo `test -f 'fzoommap.c' || echo '$(srcdir)/'`fzoommap.c
+
+cminpack/libast_la-lmder1.lo: cminpack/lmder1.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT cminpack/libast_la-lmder1.lo -MD -MP -MF cminpack/$(DEPDIR)/libast_la-lmder1.Tpo -c -o cminpack/libast_la-lmder1.lo `test -f 'cminpack/lmder1.c' || echo '$(srcdir)/'`cminpack/lmder1.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) cminpack/$(DEPDIR)/libast_la-lmder1.Tpo cminpack/$(DEPDIR)/libast_la-lmder1.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cminpack/lmder1.c' object='cminpack/libast_la-lmder1.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o cminpack/libast_la-lmder1.lo `test -f 'cminpack/lmder1.c' || echo '$(srcdir)/'`cminpack/lmder1.c
+
+cminpack/libast_la-lmder.lo: cminpack/lmder.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT cminpack/libast_la-lmder.lo -MD -MP -MF cminpack/$(DEPDIR)/libast_la-lmder.Tpo -c -o cminpack/libast_la-lmder.lo `test -f 'cminpack/lmder.c' || echo '$(srcdir)/'`cminpack/lmder.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) cminpack/$(DEPDIR)/libast_la-lmder.Tpo cminpack/$(DEPDIR)/libast_la-lmder.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cminpack/lmder.c' object='cminpack/libast_la-lmder.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o cminpack/libast_la-lmder.lo `test -f 'cminpack/lmder.c' || echo '$(srcdir)/'`cminpack/lmder.c
+
+cminpack/libast_la-dpmpar.lo: cminpack/dpmpar.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT cminpack/libast_la-dpmpar.lo -MD -MP -MF cminpack/$(DEPDIR)/libast_la-dpmpar.Tpo -c -o cminpack/libast_la-dpmpar.lo `test -f 'cminpack/dpmpar.c' || echo '$(srcdir)/'`cminpack/dpmpar.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) cminpack/$(DEPDIR)/libast_la-dpmpar.Tpo cminpack/$(DEPDIR)/libast_la-dpmpar.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cminpack/dpmpar.c' object='cminpack/libast_la-dpmpar.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o cminpack/libast_la-dpmpar.lo `test -f 'cminpack/dpmpar.c' || echo '$(srcdir)/'`cminpack/dpmpar.c
+
+cminpack/libast_la-enorm.lo: cminpack/enorm.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT cminpack/libast_la-enorm.lo -MD -MP -MF cminpack/$(DEPDIR)/libast_la-enorm.Tpo -c -o cminpack/libast_la-enorm.lo `test -f 'cminpack/enorm.c' || echo '$(srcdir)/'`cminpack/enorm.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) cminpack/$(DEPDIR)/libast_la-enorm.Tpo cminpack/$(DEPDIR)/libast_la-enorm.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cminpack/enorm.c' object='cminpack/libast_la-enorm.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o cminpack/libast_la-enorm.lo `test -f 'cminpack/enorm.c' || echo '$(srcdir)/'`cminpack/enorm.c
+
+cminpack/libast_la-qrfac.lo: cminpack/qrfac.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT cminpack/libast_la-qrfac.lo -MD -MP -MF cminpack/$(DEPDIR)/libast_la-qrfac.Tpo -c -o cminpack/libast_la-qrfac.lo `test -f 'cminpack/qrfac.c' || echo '$(srcdir)/'`cminpack/qrfac.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) cminpack/$(DEPDIR)/libast_la-qrfac.Tpo cminpack/$(DEPDIR)/libast_la-qrfac.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cminpack/qrfac.c' object='cminpack/libast_la-qrfac.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o cminpack/libast_la-qrfac.lo `test -f 'cminpack/qrfac.c' || echo '$(srcdir)/'`cminpack/qrfac.c
+
+cminpack/libast_la-lmpar.lo: cminpack/lmpar.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT cminpack/libast_la-lmpar.lo -MD -MP -MF cminpack/$(DEPDIR)/libast_la-lmpar.Tpo -c -o cminpack/libast_la-lmpar.lo `test -f 'cminpack/lmpar.c' || echo '$(srcdir)/'`cminpack/lmpar.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) cminpack/$(DEPDIR)/libast_la-lmpar.Tpo cminpack/$(DEPDIR)/libast_la-lmpar.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cminpack/lmpar.c' object='cminpack/libast_la-lmpar.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o cminpack/libast_la-lmpar.lo `test -f 'cminpack/lmpar.c' || echo '$(srcdir)/'`cminpack/lmpar.c
+
+cminpack/libast_la-qrsolv.lo: cminpack/qrsolv.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT cminpack/libast_la-qrsolv.lo -MD -MP -MF cminpack/$(DEPDIR)/libast_la-qrsolv.Tpo -c -o cminpack/libast_la-qrsolv.lo `test -f 'cminpack/qrsolv.c' || echo '$(srcdir)/'`cminpack/qrsolv.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) cminpack/$(DEPDIR)/libast_la-qrsolv.Tpo cminpack/$(DEPDIR)/libast_la-qrsolv.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cminpack/qrsolv.c' object='cminpack/libast_la-qrsolv.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o cminpack/libast_la-qrsolv.lo `test -f 'cminpack/qrsolv.c' || echo '$(srcdir)/'`cminpack/qrsolv.c
+
+libast_la-proj.lo: proj.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-proj.lo -MD -MP -MF $(DEPDIR)/libast_la-proj.Tpo -c -o libast_la-proj.lo `test -f 'proj.c' || echo '$(srcdir)/'`proj.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-proj.Tpo $(DEPDIR)/libast_la-proj.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='proj.c' object='libast_la-proj.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-proj.lo `test -f 'proj.c' || echo '$(srcdir)/'`proj.c
+
+libast_la-tpn.lo: tpn.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-tpn.lo -MD -MP -MF $(DEPDIR)/libast_la-tpn.Tpo -c -o libast_la-tpn.lo `test -f 'tpn.c' || echo '$(srcdir)/'`tpn.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-tpn.Tpo $(DEPDIR)/libast_la-tpn.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='tpn.c' object='libast_la-tpn.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-tpn.lo `test -f 'tpn.c' || echo '$(srcdir)/'`tpn.c
+
+libast_la-wcstrig.lo: wcstrig.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -MT libast_la-wcstrig.lo -MD -MP -MF $(DEPDIR)/libast_la-wcstrig.Tpo -c -o libast_la-wcstrig.lo `test -f 'wcstrig.c' || echo '$(srcdir)/'`wcstrig.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_la-wcstrig.Tpo $(DEPDIR)/libast_la-wcstrig.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='wcstrig.c' object='libast_la-wcstrig.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_la_CFLAGS) $(CFLAGS) -c -o libast_la-wcstrig.lo `test -f 'wcstrig.c' || echo '$(srcdir)/'`wcstrig.c
+
+libast_drama_la-err_drama.lo: err_drama.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_drama_la_CFLAGS) $(CFLAGS) -MT libast_drama_la-err_drama.lo -MD -MP -MF $(DEPDIR)/libast_drama_la-err_drama.Tpo -c -o libast_drama_la-err_drama.lo `test -f 'err_drama.c' || echo '$(srcdir)/'`err_drama.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_drama_la-err_drama.Tpo $(DEPDIR)/libast_drama_la-err_drama.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='err_drama.c' object='libast_drama_la-err_drama.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_drama_la_CFLAGS) $(CFLAGS) -c -o libast_drama_la-err_drama.lo `test -f 'err_drama.c' || echo '$(srcdir)/'`err_drama.c
+
+libast_ems_la-err_ems.lo: err_ems.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_ems_la_CFLAGS) $(CFLAGS) -MT libast_ems_la-err_ems.lo -MD -MP -MF $(DEPDIR)/libast_ems_la-err_ems.Tpo -c -o libast_ems_la-err_ems.lo `test -f 'err_ems.c' || echo '$(srcdir)/'`err_ems.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_ems_la-err_ems.Tpo $(DEPDIR)/libast_ems_la-err_ems.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='err_ems.c' object='libast_ems_la-err_ems.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_ems_la_CFLAGS) $(CFLAGS) -c -o libast_ems_la-err_ems.lo `test -f 'err_ems.c' || echo '$(srcdir)/'`err_ems.c
+
+libast_err_la-err_null.lo: err_null.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_err_la_CFLAGS) $(CFLAGS) -MT libast_err_la-err_null.lo -MD -MP -MF $(DEPDIR)/libast_err_la-err_null.Tpo -c -o libast_err_la-err_null.lo `test -f 'err_null.c' || echo '$(srcdir)/'`err_null.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_err_la-err_null.Tpo $(DEPDIR)/libast_err_la-err_null.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='err_null.c' object='libast_err_la-err_null.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_err_la_CFLAGS) $(CFLAGS) -c -o libast_err_la-err_null.lo `test -f 'err_null.c' || echo '$(srcdir)/'`err_null.c
+
+libast_grf3d_la-grf3d.lo: grf3d.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_grf3d_la_CFLAGS) $(CFLAGS) -MT libast_grf3d_la-grf3d.lo -MD -MP -MF $(DEPDIR)/libast_grf3d_la-grf3d.Tpo -c -o libast_grf3d_la-grf3d.lo `test -f 'grf3d.c' || echo '$(srcdir)/'`grf3d.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_grf3d_la-grf3d.Tpo $(DEPDIR)/libast_grf3d_la-grf3d.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='grf3d.c' object='libast_grf3d_la-grf3d.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_grf3d_la_CFLAGS) $(CFLAGS) -c -o libast_grf3d_la-grf3d.lo `test -f 'grf3d.c' || echo '$(srcdir)/'`grf3d.c
+
+libast_grf_2_0_la-grf_2.0.lo: grf_2.0.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_grf_2_0_la_CFLAGS) $(CFLAGS) -MT libast_grf_2_0_la-grf_2.0.lo -MD -MP -MF $(DEPDIR)/libast_grf_2_0_la-grf_2.0.Tpo -c -o libast_grf_2_0_la-grf_2.0.lo `test -f 'grf_2.0.c' || echo '$(srcdir)/'`grf_2.0.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_grf_2_0_la-grf_2.0.Tpo $(DEPDIR)/libast_grf_2_0_la-grf_2.0.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='grf_2.0.c' object='libast_grf_2_0_la-grf_2.0.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_grf_2_0_la_CFLAGS) $(CFLAGS) -c -o libast_grf_2_0_la-grf_2.0.lo `test -f 'grf_2.0.c' || echo '$(srcdir)/'`grf_2.0.c
+
+libast_grf_3_2_la-grf_3.2.lo: grf_3.2.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_grf_3_2_la_CFLAGS) $(CFLAGS) -MT libast_grf_3_2_la-grf_3.2.lo -MD -MP -MF $(DEPDIR)/libast_grf_3_2_la-grf_3.2.Tpo -c -o libast_grf_3_2_la-grf_3.2.lo `test -f 'grf_3.2.c' || echo '$(srcdir)/'`grf_3.2.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_grf_3_2_la-grf_3.2.Tpo $(DEPDIR)/libast_grf_3_2_la-grf_3.2.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='grf_3.2.c' object='libast_grf_3_2_la-grf_3.2.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_grf_3_2_la_CFLAGS) $(CFLAGS) -c -o libast_grf_3_2_la-grf_3.2.lo `test -f 'grf_3.2.c' || echo '$(srcdir)/'`grf_3.2.c
+
+libast_grf_5_6_la-grf_5.6.lo: grf_5.6.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_grf_5_6_la_CFLAGS) $(CFLAGS) -MT libast_grf_5_6_la-grf_5.6.lo -MD -MP -MF $(DEPDIR)/libast_grf_5_6_la-grf_5.6.Tpo -c -o libast_grf_5_6_la-grf_5.6.lo `test -f 'grf_5.6.c' || echo '$(srcdir)/'`grf_5.6.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_grf_5_6_la-grf_5.6.Tpo $(DEPDIR)/libast_grf_5_6_la-grf_5.6.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='grf_5.6.c' object='libast_grf_5_6_la-grf_5.6.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_grf_5_6_la_CFLAGS) $(CFLAGS) -c -o libast_grf_5_6_la-grf_5.6.lo `test -f 'grf_5.6.c' || echo '$(srcdir)/'`grf_5.6.c
+
+libast_pal_la-palwrap.lo: palwrap.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_pal_la_CFLAGS) $(CFLAGS) -MT libast_pal_la-palwrap.lo -MD -MP -MF $(DEPDIR)/libast_pal_la-palwrap.Tpo -c -o libast_pal_la-palwrap.lo `test -f 'palwrap.c' || echo '$(srcdir)/'`palwrap.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_pal_la-palwrap.Tpo $(DEPDIR)/libast_pal_la-palwrap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='palwrap.c' object='libast_pal_la-palwrap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_pal_la_CFLAGS) $(CFLAGS) -c -o libast_pal_la-palwrap.lo `test -f 'palwrap.c' || echo '$(srcdir)/'`palwrap.c
+
+libast_pgplot_la-grf_pgplot.lo: grf_pgplot.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_pgplot_la_CFLAGS) $(CFLAGS) -MT libast_pgplot_la-grf_pgplot.lo -MD -MP -MF $(DEPDIR)/libast_pgplot_la-grf_pgplot.Tpo -c -o libast_pgplot_la-grf_pgplot.lo `test -f 'grf_pgplot.c' || echo '$(srcdir)/'`grf_pgplot.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_pgplot_la-grf_pgplot.Tpo $(DEPDIR)/libast_pgplot_la-grf_pgplot.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='grf_pgplot.c' object='libast_pgplot_la-grf_pgplot.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_pgplot_la_CFLAGS) $(CFLAGS) -c -o libast_pgplot_la-grf_pgplot.lo `test -f 'grf_pgplot.c' || echo '$(srcdir)/'`grf_pgplot.c
+
+libast_pgplot_la-grf_5.6.lo: grf_5.6.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_pgplot_la_CFLAGS) $(CFLAGS) -MT libast_pgplot_la-grf_5.6.lo -MD -MP -MF $(DEPDIR)/libast_pgplot_la-grf_5.6.Tpo -c -o libast_pgplot_la-grf_5.6.lo `test -f 'grf_5.6.c' || echo '$(srcdir)/'`grf_5.6.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_pgplot_la-grf_5.6.Tpo $(DEPDIR)/libast_pgplot_la-grf_5.6.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='grf_5.6.c' object='libast_pgplot_la-grf_5.6.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_pgplot_la_CFLAGS) $(CFLAGS) -c -o libast_pgplot_la-grf_5.6.lo `test -f 'grf_5.6.c' || echo '$(srcdir)/'`grf_5.6.c
+
+libast_pgplot3d_la-grf3d_pgplot.lo: grf3d_pgplot.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_pgplot3d_la_CFLAGS) $(CFLAGS) -MT libast_pgplot3d_la-grf3d_pgplot.lo -MD -MP -MF $(DEPDIR)/libast_pgplot3d_la-grf3d_pgplot.Tpo -c -o libast_pgplot3d_la-grf3d_pgplot.lo `test -f 'grf3d_pgplot.c' || echo '$(srcdir)/'`grf3d_pgplot.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_pgplot3d_la-grf3d_pgplot.Tpo $(DEPDIR)/libast_pgplot3d_la-grf3d_pgplot.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='grf3d_pgplot.c' object='libast_pgplot3d_la-grf3d_pgplot.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_pgplot3d_la_CFLAGS) $(CFLAGS) -c -o libast_pgplot3d_la-grf3d_pgplot.lo `test -f 'grf3d_pgplot.c' || echo '$(srcdir)/'`grf3d_pgplot.c
+
+libast_pgplot3d_la-grf3d.lo: grf3d.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_pgplot3d_la_CFLAGS) $(CFLAGS) -MT libast_pgplot3d_la-grf3d.lo -MD -MP -MF $(DEPDIR)/libast_pgplot3d_la-grf3d.Tpo -c -o libast_pgplot3d_la-grf3d.lo `test -f 'grf3d.c' || echo '$(srcdir)/'`grf3d.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/libast_pgplot3d_la-grf3d.Tpo $(DEPDIR)/libast_pgplot3d_la-grf3d.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='grf3d.c' object='libast_pgplot3d_la-grf3d.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(STAR_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) $(libast_pgplot3d_la_CFLAGS) $(CFLAGS) -c -o libast_pgplot3d_la-grf3d.lo `test -f 'grf3d.c' || echo '$(srcdir)/'`grf3d.c
+
+mostlyclean-libtool:
+	-rm -f *.lo
+
+clean-libtool:
+	-rm -rf .libs _libs
+	-rm -rf cminpack/.libs cminpack/_libs
+
+distclean-libtool:
+	-rm -f libtool config.lt
+install-includeMESSAGES: $(include_MESSAGES)
+	@$(NORMAL_INSTALL)
+	@list='$(include_MESSAGES)'; test -n "$(includedir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \
+	fi; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; \
+	done | $(am__base_list) | \
+	while read files; do \
+	  echo " $(INSTALL_MESSAGE) $$files '$(DESTDIR)$(includedir)'"; \
+	  $(INSTALL_MESSAGE) $$files "$(DESTDIR)$(includedir)" || exit $$?; \
+	  for f in $$files; do \
+	      $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(includedir)/$$f" || :; \
+	  done; \
+	done
+
+uninstall-includeMESSAGES:
+	@$(NORMAL_UNINSTALL)
+	@list='$(include_MESSAGES)'; test -n "$(includedir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+	dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir)
+
+# Compile messages for message file ast_err.msg.  This requires that the 
+# @MESSGEN@ substitution was requested in configure.ac, probably implicitly
+# by the STAR_MESSGEN macro.
+#
+# These rules are usable only in the predist state (since the .msg files
+# are typically not distributed), so should be activated only when in that
+# state.
+@PREDIST@AST_ERR: ast_err.msg
+@PREDIST@	$(MESSGEN) -F ast_err.msg
+@[email-masked]: ast_err.msg
+@PREDIST@	$(MESSGEN) -c ast_err.msg
+@PREDIST@fac_1521_err: ast_err.msg
+@PREDIST@	$(MESSGEN) -e ast_err.msg
+install-dist_pkgdataDATA: $(dist_pkgdata_DATA)
+	@$(NORMAL_INSTALL)
+	@list='$(dist_pkgdata_DATA)'; test -n "$(pkgdatadir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(pkgdatadir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(pkgdatadir)" || exit 1; \
+	fi; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; \
+	done | $(am__base_list) | \
+	while read files; do \
+	  echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgdatadir)'"; \
+	  $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgdatadir)" || exit $$?; \
+	  for f in $$files; do \
+	      $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(pkgdatadir)/$$f" || :; \
+	  done; \
+	done
+
+uninstall-dist_pkgdataDATA:
+	@$(NORMAL_UNINSTALL)
+	@list='$(dist_pkgdata_DATA)'; test -n "$(pkgdatadir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+	dir='$(DESTDIR)$(pkgdatadir)'; $(am__uninstall_files_from_dir)
+install-dist_starnewsDATA: $(dist_starnews_DATA)
+	@$(NORMAL_INSTALL)
+	@list='$(dist_starnews_DATA)'; test -n "$(starnewsdir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(starnewsdir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(starnewsdir)" || exit 1; \
+	fi; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; \
+	done | $(am__base_list) | \
+	while read files; do \
+	  echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(starnewsdir)'"; \
+	  $(INSTALL_DATA) $$files "$(DESTDIR)$(starnewsdir)" || exit $$?; \
+	  for f in $$files; do \
+	      $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(starnewsdir)/$$f" || :; \
+	  done; \
+	done
+
+uninstall-dist_starnewsDATA:
+	@$(NORMAL_UNINSTALL)
+	@list='$(dist_starnews_DATA)'; test -n "$(starnewsdir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+	dir='$(DESTDIR)$(starnewsdir)'; $(am__uninstall_files_from_dir)
+install-stardocsDATA: $(stardocs_DATA)
+	@$(NORMAL_INSTALL)
+	$(mkdir_p) $(DESTDIR)$(stardocsdir)
+	@list='$(stardocs_DATA)'; for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  f="`echo $$p | sed -e 's|^.*/||'`"; \
+	  if expr "x$$p" : 'x.*\.htx_tar$$' >/dev/null; then \
+	    if test -n "$(PAX)"; then \
+	      if $(MANIFEST); then \
+		$(PAX) -f $$d$$p | \
+		  sed 's+^+MANIFEST:$(DESTDIR)$(stardocsdir)/+'; \
+	      fi; \
+	      cat $$d$$p | (cd $(DESTDIR)$(stardocsdir); $(PAX) -r); \
+	    elif test -n "$(TAR)"; then \
+	      if $(MANIFEST); then \
+	        cat $$d$$p | (cd $(DESTDIR)$(stardocsdir); $(TAR) xBpvf -) | sed 's+^+MANIFEST:$(DESTDIR)$(stardocsdir)/+'; \
+	      else \
+	        cat $$d$$p | (cd $(DESTDIR)$(stardocsdir); $(TAR) xBpf -); \
+	      fi; \
+	    else \
+	      echo "Neither tar nor pax!!!" >&2; \
+	      exit 1; \
+	    fi; \
+	  else \
+	    echo " $(stardocsDATA_INSTALL) $$d$$p $(DESTDIR)$(stardocsdir)/$$f"; \
+	    $(stardocsDATA_INSTALL) $$d$$p $(DESTDIR)$(stardocsdir)/$$f; \
+	    $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(stardocsdir)/$$f" || :; \
+	  fi; \
+	done
+
+uninstall-stardocsDATA:
+	@$(NORMAL_UNINSTALL)
+	@list='$(stardocs_DATA)'; for p in $$list; do \
+	  f="`echo $$p | sed -e 's|^.*/||'`"; \
+	  echo " rm -f $(DESTDIR)$(stardocsdir)/$$f"; \
+	  rm -f $(DESTDIR)$(stardocsdir)/$$f; \
+	done
+install-starfacsDATA: $(starfacs_DATA)
+	@$(NORMAL_INSTALL)
+	@list='$(starfacs_DATA)'; test -n "$(starfacsdir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(starfacsdir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(starfacsdir)" || exit 1; \
+	fi; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; \
+	done | $(am__base_list) | \
+	while read files; do \
+	  echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(starfacsdir)'"; \
+	  $(INSTALL_DATA) $$files "$(DESTDIR)$(starfacsdir)" || exit $$?; \
+	  for f in $$files; do \
+	      $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(starfacsdir)/$$f" || :; \
+	  done; \
+	done
+
+uninstall-starfacsDATA:
+	@$(NORMAL_UNINSTALL)
+	@list='$(starfacs_DATA)'; test -n "$(starfacsdir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+	dir='$(DESTDIR)$(starfacsdir)'; $(am__uninstall_files_from_dir)
+install-includeHEADERS: $(include_HEADERS)
+	@$(NORMAL_INSTALL)
+	@list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \
+	fi; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; \
+	done | $(am__base_list) | \
+	while read files; do \
+	  echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(includedir)'"; \
+	  $(INSTALL_HEADER) $$files "$(DESTDIR)$(includedir)" || exit $$?; \
+	  for f in $$files; do \
+	      $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(includedir)/$$f" || :; \
+	  done; \
+	done
+
+uninstall-includeHEADERS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+	dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir)
+install-nodist_includeHEADERS: $(nodist_include_HEADERS)
+	@$(NORMAL_INSTALL)
+	@list='$(nodist_include_HEADERS)'; test -n "$(includedir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \
+	fi; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; \
+	done | $(am__base_list) | \
+	while read files; do \
+	  echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(includedir)'"; \
+	  $(INSTALL_HEADER) $$files "$(DESTDIR)$(includedir)" || exit $$?; \
+	  for f in $$files; do \
+	      $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(includedir)/$$f" || :; \
+	  done; \
+	done
+
+uninstall-nodist_includeHEADERS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(nodist_include_HEADERS)'; test -n "$(includedir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+	dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+	$(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	set x; \
+	here=`pwd`; \
+	$(am__define_uniq_tagged_files); \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	$(am__define_uniq_tagged_files); \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+cscope: cscope.files
+	test ! -s cscope.files \
+	  || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS)
+clean-cscope:
+	-rm -f cscope.files
+cscope.files: clean-cscope cscopelist
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+	list='$(am__tagged_files)'; \
+	case "$(srcdir)" in \
+	  [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+	  *) sdir=$(subdir)/$(srcdir) ;; \
+	esac; \
+	for i in $$list; do \
+	  if test -f "$$i"; then \
+	    echo "$(subdir)/$$i"; \
+	  else \
+	    echo "$$sdir/$$i"; \
+	  fi; \
+	done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+	-rm -f cscope.out cscope.in.out cscope.po.out cscope.files
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'.  Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+	rm -f $< $@
+	$(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+	@:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+	@$(am__set_TESTS_bases); \
+	am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+	redo_bases=`for i in $$bases; do \
+	              am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+	            done`; \
+	if test -n "$$redo_bases"; then \
+	  redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+	  redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+	  if $(am__make_dryrun); then :; else \
+	    rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+	  fi; \
+	fi; \
+	if test -n "$$am__remaking_logs"; then \
+	  echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+	       "recursion detected" >&2; \
+	else \
+	  am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+	fi; \
+	if $(am__make_dryrun); then :; else \
+	  st=0;  \
+	  errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+	  for i in $$redo_bases; do \
+	    test -f $$i.trs && test -r $$i.trs \
+	      || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+	    test -f $$i.log && test -r $$i.log \
+	      || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+	  done; \
+	  test $$st -eq 0 || exit 1; \
+	fi
+	@$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+	ws='[ 	]'; \
+	results=`for b in $$bases; do echo $$b.trs; done`; \
+	test -n "$$results" || results=/dev/null; \
+	all=`  grep "^$$ws*:test-result:"           $$results | wc -l`; \
+	pass=` grep "^$$ws*:test-result:$$ws*PASS"  $$results | wc -l`; \
+	fail=` grep "^$$ws*:test-result:$$ws*FAIL"  $$results | wc -l`; \
+	skip=` grep "^$$ws*:test-result:$$ws*SKIP"  $$results | wc -l`; \
+	xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+	xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+	error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+	if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+	  success=true; \
+	else \
+	  success=false; \
+	fi; \
+	br='==================='; br=$$br$$br$$br$$br; \
+	result_count () \
+	{ \
+	    if test x"$$1" = x"--maybe-color"; then \
+	      maybe_colorize=yes; \
+	    elif test x"$$1" = x"--no-color"; then \
+	      maybe_colorize=no; \
+	    else \
+	      echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+	    fi; \
+	    shift; \
+	    desc=$$1 count=$$2; \
+	    if test $$maybe_colorize = yes && test $$count -gt 0; then \
+	      color_start=$$3 color_end=$$std; \
+	    else \
+	      color_start= color_end=; \
+	    fi; \
+	    echo "$${color_start}# $$desc $$count$${color_end}"; \
+	}; \
+	create_testsuite_report () \
+	{ \
+	  result_count $$1 "TOTAL:" $$all   "$$brg"; \
+	  result_count $$1 "PASS: " $$pass  "$$grn"; \
+	  result_count $$1 "SKIP: " $$skip  "$$blu"; \
+	  result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+	  result_count $$1 "FAIL: " $$fail  "$$red"; \
+	  result_count $$1 "XPASS:" $$xpass "$$red"; \
+	  result_count $$1 "ERROR:" $$error "$$mgn"; \
+	}; \
+	{								\
+	  echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" |	\
+	    $(am__rst_title);						\
+	  create_testsuite_report --no-color;				\
+	  echo;								\
+	  echo ".. contents:: :depth: 2";				\
+	  echo;								\
+	  for b in $$bases; do echo $$b; done				\
+	    | $(am__create_global_log);					\
+	} >$(TEST_SUITE_LOG).tmp || exit 1;				\
+	mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG);			\
+	if $$success; then						\
+	  col="$$grn";							\
+	 else								\
+	  col="$$red";							\
+	  test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG);		\
+	fi;								\
+	echo "$${col}$$br$${std}"; 					\
+	echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}";	\
+	echo "$${col}$$br$${std}"; 					\
+	create_testsuite_report --maybe-color;				\
+	echo "$$col$$br$$std";						\
+	if $$success; then :; else					\
+	  echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}";		\
+	  if test -n "$(PACKAGE_BUGREPORT)"; then			\
+	    echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}";	\
+	  fi;								\
+	  echo "$$col$$br$$std";					\
+	fi;								\
+	$$success || exit 1
+
+check-TESTS:
+	@list='$(RECHECK_LOGS)';           test -z "$$list" || rm -f $$list
+	@list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+	log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+	exit $$?;
+recheck: all $(check_PROGRAMS)
+	@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+	@set +e; $(am__set_TESTS_bases); \
+	bases=`for i in $$bases; do echo $$i; done \
+	         | $(am__list_recheck_tests)` || exit 1; \
+	log_list=`for i in $$bases; do echo $$i.log; done`; \
+	log_list=`echo $$log_list`; \
+	$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+	        am__force_recheck=am--force-recheck \
+	        TEST_LOGS="$$log_list"; \
+	exit $$?
+ast_test.log: ast_test$(EXEEXT)
+	@p='ast_test$(EXEEXT)'; \
+	b='ast_test'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+	@p='$<'; \
+	$(am__set_b); \
+	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@	@p='$<'; \
+@am__EXEEXT_TRUE@	$(am__set_b); \
+@am__EXEEXT_TRUE@	$(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@	--log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@	$(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@	"$$tst" $(AM_TESTS_FD_REDIRECT)
+
+# Don't express a dependency via the directory .htx, or else make
+# tries to delete it as an intermediate (we can't specify
+# .PRECIOUS targets within this file).
+#
+# Requires that the STAR2HTML substitution was made in
+# configure.ac, probably implicitly by the STAR_LATEX_DOCUMENTATION macro.
+#
+# We do not require that star2html be available on the build system,
+# and so we distribute built HTX documentation.  Thus the following rules
+# should be invoked only in the predist state.  However there isn't a
+# file we can use to test whether we're in that state, so write the
+# test so that it will fail if star2html isn't available.
+#
+# If file $(<:.tex=.htx_tar.extras) is present, then it contains a list
+# of files, each one of which should be added to the .htx directory before
+# it is rolled up into a tarball.
+#
+# Ignore the exit status of star2html, as it can sometimes fail harmlessly.
+.tex.dvi:
+	LATEX=latex; latex2dvi () { $(LATEX2DVI); }; \
+	  latex2dvi ${<:.tex=}
+.dvi.ps:
+	dvips -o $@ $<
+.tex.ps:
+	LATEX=latex; latex2dvi () { $(LATEX2DVI); }; \
+	  latex2dvi ${<:.tex=}
+	dvips -o $@ $(<:.tex=.dvi)
+.tex.pdf:
+	LATEX=pdflatex; latex2dvi () { $(LATEX2DVI); }; \
+	  TEXINPUTS=${STARLINK}/share/latexsupport//:${TEXINPUTS} latex2dvi ${<:.tex=}
+.tex.htx_tar:
+	- @STAR2HTML@ $(STAR2HTML_FLAGS) $<
+	test -d ${<:.tex=.htx}
+	if test -f ${<:.tex=.htx_tar.extras}; then \
+	  for f in `cat ${<:.tex=.htx_tar.extras}`; do \
+	    test -f "$$f" && cp "$$f" ${<:.tex=.htx} || true; \
+	  done; else :; fi
+	tar cf $@ ${<:.tex=.htx}
+
+distdir: $(DISTFILES)
+	$(am__remove_distdir)
+	test -d "$(distdir)" || mkdir "$(distdir)"
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+	$(MAKE) $(AM_MAKEFLAGS) \
+	  top_distdir="$(top_distdir)" distdir="$(distdir)" \
+	  dist-hook
+	-test -n "$(am__skip_mode_fix)" \
+	|| find "$(distdir)" -type d ! -perm -755 \
+		-exec chmod u+rwx,go+rx {} \; -o \
+	  ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
+	  ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
+	  ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
+	|| chmod -R a+r "$(distdir)"
+dist-gzip: distdir
+	tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
+	$(am__post_remove_distdir)
+
+dist-bzip2: distdir
+	tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2
+	$(am__post_remove_distdir)
+
+dist-lzip: distdir
+	tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz
+	$(am__post_remove_distdir)
+
+dist-xz: distdir
+	tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz
+	$(am__post_remove_distdir)
+
+dist-tarZ: distdir
+	@echo WARNING: "Support for shar distribution archives is" \
+	               "deprecated." >&2
+	@echo WARNING: "It will be removed altogether in Automake 2.0" >&2
+	tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
+	$(am__post_remove_distdir)
+
+dist-shar: distdir
+	@echo WARNING: "Support for distribution archives compressed with" \
+		       "legacy program 'compress' is deprecated." >&2
+	@echo WARNING: "It will be removed altogether in Automake 2.0" >&2
+	shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz
+	$(am__post_remove_distdir)
+
+dist-zip: distdir
+	-rm -f $(distdir).zip
+	zip -rq $(distdir).zip $(distdir)
+	$(am__post_remove_distdir)
+
+dist dist-all:
+	$(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:'
+	$(am__post_remove_distdir)
+
+# This target untars the dist file and tries a VPATH configuration.  Then
+# it guarantees that the distribution is self-contained by making another
+# tarfile.
+distcheck: dist
+	case '$(DIST_ARCHIVES)' in \
+	*.tar.gz*) \
+	  GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\
+	*.tar.bz2*) \
+	  bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
+	*.tar.lz*) \
+	  lzip -dc $(distdir).tar.lz | $(am__untar) ;;\
+	*.tar.xz*) \
+	  xz -dc $(distdir).tar.xz | $(am__untar) ;;\
+	*.tar.Z*) \
+	  uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
+	*.shar.gz*) \
+	  GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\
+	*.zip*) \
+	  unzip $(distdir).zip ;;\
+	esac
+	chmod -R a-w $(distdir)
+	chmod u+w $(distdir)
+	mkdir $(distdir)/_build $(distdir)/_inst
+	chmod a-w $(distdir)
+	test -d $(distdir)/_build || exit 0; \
+	dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
+	  && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
+	  && am__cwd=`pwd` \
+	  && $(am__cd) $(distdir)/_build \
+	  && ../configure \
+	    $(AM_DISTCHECK_CONFIGURE_FLAGS) \
+	    $(DISTCHECK_CONFIGURE_FLAGS) \
+	    --srcdir=.. --prefix="$$dc_install_base" \
+	  && $(MAKE) $(AM_MAKEFLAGS) \
+	  && $(MAKE) $(AM_MAKEFLAGS) dvi \
+	  && $(MAKE) $(AM_MAKEFLAGS) check \
+	  && $(MAKE) $(AM_MAKEFLAGS) install \
+	  && $(MAKE) $(AM_MAKEFLAGS) installcheck \
+	  && $(MAKE) $(AM_MAKEFLAGS) uninstall \
+	  && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
+	        distuninstallcheck \
+	  && chmod -R a-w "$$dc_install_base" \
+	  && ({ \
+	       (cd ../.. && umask 077 && mkdir "$$dc_destdir") \
+	       && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
+	       && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
+	       && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
+	            distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
+	      } || { rm -rf "$$dc_destdir"; exit 1; }) \
+	  && rm -rf "$$dc_destdir" \
+	  && $(MAKE) $(AM_MAKEFLAGS) dist \
+	  && rm -rf $(DIST_ARCHIVES) \
+	  && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \
+	  && cd "$$am__cwd" \
+	  || exit 1
+	$(am__post_remove_distdir)
+	@(echo "$(distdir) archives ready for distribution: "; \
+	  list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
+	  sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
+distuninstallcheck:
+	@test -n '$(distuninstallcheck_dir)' || { \
+	  echo 'ERROR: trying to run $@ with an empty' \
+	       '$$(distuninstallcheck_dir)' >&2; \
+	  exit 1; \
+	}; \
+	$(am__cd) '$(distuninstallcheck_dir)' || { \
+	  echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \
+	  exit 1; \
+	}; \
+	test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \
+	   || { echo "ERROR: files left after uninstall:" ; \
+	        if test -n "$(DESTDIR)"; then \
+	          echo "  (check DESTDIR support)"; \
+	        fi ; \
+	        $(distuninstallcheck_listfiles) ; \
+	        exit 1; } >&2
+distcleancheck: distclean
+	@if test '$(srcdir)' = . ; then \
+	  echo "ERROR: distcleancheck can only run from a VPATH build" ; \
+	  exit 1 ; \
+	fi
+	@test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
+	  || { echo "ERROR: files left in build directory after distclean:" ; \
+	       $(distcleancheck_listfiles) ; \
+	       exit 1; } >&2
+check-am: all-am
+	$(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+	$(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: $(BUILT_SOURCES)
+	$(MAKE) $(AM_MAKEFLAGS) check-am
+all-am: Makefile $(LTLIBRARIES) $(PROGRAMS) $(SCRIPTS) $(MESSAGES) \
+		$(DATA) $(HEADERS) config.h
+all-am: Makefile $(LTLIBRARIES) $(PROGRAMS) $(SCRIPTS) $(MESSAGES) \
+		$(DATA) $(HEADERS) config.h
+installdirs:
+	for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" "$(DESTDIR)$(bindir)" "$(DESTDIR)$(includedir)" "$(DESTDIR)$(pkgdatadir)" "$(DESTDIR)$(starnewsdir)" $(DESTDIR)$(stardocsdir) "$(DESTDIR)$(starfacsdir)" "$(DESTDIR)$(includedir)" "$(DESTDIR)$(includedir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+# If STAR_MANIFEST_DIR is defined and the MANIFEST variable has the
+# (default) string value 'false', then invoke the install-manifest
+# target, otherwise, do the real install rule.  This means that if
+# this is being invoked from within an install-manifest rule further
+# up the process tree, we don't create another manifest, which would
+# stomp on the original.
+#
+# Any Makefile which does special installations should check the
+# $(MANIFEST) variable, which will be ':' or 'false', and if it is
+# true, emit a line to stdout, consisting of the string 'MANIFEST:'
+# followed by the full path of the file being installed.
+install:  $(BUILT_SOURCES)
+	$(MAKE) $(AM_MAKEFLAGS) all-am
+	if test -n "$(STAR_MANIFEST_DIR)" -a $(MANIFEST) = false; then \
+	    $(MAKE) $(AM_MAKEFLAGS) install-manifest; \
+	else \
+	    $(MAKE) $(AM_MAKEFLAGS) $(REAL_INSTALL); \
+	fi
+
+install-manifest:
+	$(mkdir_p) $(DESTDIR)$(STAR_MANIFEST_DIR)
+	MFX=$${TMPDIR-/tmp}/manifest-$$$$-1; rm -f $$MFX; MF_INST_OK=:; \
+	  { $(MAKE) MANIFEST=: $(REAL_INSTALL) \
+	    || MF_INST_OK=false; } \
+	    | tee $$MFX | grep -v '^MANIFEST:' || :; \
+	  if $$MF_INST_OK; then \
+	    MF=$${TMPDIR-/tmp}/manifest-$$$$-2; rm -f $$MF; \
+	    ( echo "<?xml version='1.0'?>"; \
+	      echo "<!DOCTYPE manifest SYSTEM 'componentinfo.dtd'>"; \
+	      echo "<manifest component='$(PACKAGE)'>"; \
+	      echo "<version>$(PACKAGE_VERSION)</version>"; \
+	      echo "<files>"; \
+	      sed -n 's/^MANIFEST://p;' $$MFX; \
+	      echo "</files>"; \
+	      echo "</manifest>"; \
+	    ) >$$MF; \
+	    $(INSTALL_DATA) $$MF $(DESTDIR)$(STAR_MANIFEST_DIR)/$(PACKAGE); \
+	  else \
+	    echo "Installation of component $(DESTDIR)$(STAR_MANIFEST_DIR)/$(PACKAGE) failed" >&2; \
+	  fi; \
+	  rm -f $$MFX $$MF; \
+	  $$MF_INST_OK
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	if test -z '$(STRIP)'; then \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	      install; \
+	else \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+	fi
+mostlyclean-generic:
+	-test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+	-test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+	-test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+	-test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+	-rm -f cminpack/$(DEPDIR)/$(am__dirstamp)
+	-rm -f cminpack/$(am__dirstamp)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+	-rm -f AST_ERR
+	-rm -f ast_err.h
+	-rm -f configure.log
+	-rm -f fac_1521_err
+	-rm -f make.log
+	-rm -f starconf.status
+	-test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+	-test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES)
+clean: clean-am
+
+clean-am: clean-checkPROGRAMS clean-generic clean-libLTLIBRARIES \
+	clean-libtool clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+	-rm -f $(am__CONFIG_DISTCLEAN_FILES)
+	-rm -rf ./$(DEPDIR) cminpack/$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-hdr distclean-libtool distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-dist_pkgdataDATA install-dist_starnewsDATA \
+	install-includeHEADERS install-includeMESSAGES \
+	install-nodist_includeHEADERS install-stardocsDATA \
+	install-starfacsDATA
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binSCRIPTS install-dist_binSCRIPTS \
+	install-libLTLIBRARIES
+	@$(NORMAL_INSTALL)
+	$(MAKE) $(AM_MAKEFLAGS) install-exec-hook
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -f $(am__CONFIG_DISTCLEAN_FILES)
+	-rm -rf $(top_srcdir)/autom4te.cache
+	-rm -rf ./$(DEPDIR) cminpack/$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+	mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binSCRIPTS uninstall-dist_binSCRIPTS \
+	uninstall-dist_pkgdataDATA uninstall-dist_starnewsDATA \
+	uninstall-includeHEADERS uninstall-includeMESSAGES \
+	uninstall-libLTLIBRARIES uninstall-nodist_includeHEADERS \
+	uninstall-stardocsDATA uninstall-starfacsDATA
+
+.MAKE: all check check-am install install-am install-exec-am \
+	install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--refresh check check-TESTS \
+	check-am clean clean-checkPROGRAMS clean-cscope clean-generic \
+	clean-libLTLIBRARIES clean-libtool clean-noinstPROGRAMS cscope \
+	cscopelist-am ctags ctags-am dist dist-all dist-bzip2 \
+	dist-gzip dist-hook dist-lzip dist-shar dist-tarZ dist-xz \
+	dist-zip distcheck distclean distclean-compile \
+	distclean-generic distclean-hdr distclean-libtool \
+	distclean-tags distcleancheck distdir distuninstallcheck dvi \
+	dvi-am html html-am info info-am install install-am \
+	install-binSCRIPTS install-data install-data-am \
+	install-dist_binSCRIPTS install-dist_pkgdataDATA \
+	install-dist_starnewsDATA install-dvi install-dvi-am \
+	install-exec install-exec-am install-exec-hook install-html \
+	install-html-am install-includeHEADERS install-includeMESSAGES \
+	install-info install-info-am install-libLTLIBRARIES \
+	install-man install-manifest install-nodist_includeHEADERS \
+	install-pdf install-pdf-am install-ps install-ps-am \
+	install-stardocsDATA install-starfacsDATA install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+	recheck tags tags-am uninstall uninstall-am \
+	uninstall-binSCRIPTS uninstall-dist_binSCRIPTS \
+	uninstall-dist_pkgdataDATA uninstall-dist_starnewsDATA \
+	uninstall-includeHEADERS uninstall-includeMESSAGES \
+	uninstall-libLTLIBRARIES uninstall-nodist_includeHEADERS \
+	uninstall-stardocsDATA uninstall-starfacsDATA
+
+AST_PAR: ast_par.source astbad
+	sed -e 's/<AST__BAD>/'`./astbad AST__BAD | tr 'E' 'D'`'/' \
+	    -e 's/<AST__NAN>/'`./astbad AST__NAN | tr 'E' 'D'`'/' \
+	    -e 's/<AST__NANR>/'`./astbad AST__NANF | tr 'E' 'D'`'/' \
+	    ast_par.source >$@
+
+# ast.h depends on the error-facility files.  ast.h _could_ be made
+# before distribution, but since it's generated from other distributed
+# files, it's more robust to distribute makeh and make ast.h on the
+# build host.
+ast.h: $(AST_H_FILES) ast_err.h makeh config.h
+	@PERL@ ./makeh -s $(srcdir) $(AST_H_FILES) >$@
+
+#  Form a second link to the main object library (static and shared). This
+#  is used when a second pass through the library is needed during linking
+#  to resolve references made within other AST libraries (e.g. the grf
+#  modules, etc), to functions defined within libast (e.g. memory management
+#  and error reporting functions). Do not forget to change the contents of
+#  the libast_pass2.la file to refer to libast_pass2.* rather than libast.*.
+#  Use target install-exec-hook rather than install-exec-local so that the
+#  soft links and files are created *after* the main library has been
+#  installed.
+install-exec-hook: libast.la
+	$(mkdir_p) $(DESTDIR)$(libdir)
+	cd $(DESTDIR)$(libdir); \
+	for f in `ls libast.*`; do \
+	   ff=`echo $$f | sed -e 's/libast/libast_pass2/'`; \
+	   if test -f "$$ff"; then rm "$$ff"; fi; \
+	   $(LN_S) $$f $$ff; \
+	   $(MANIFEST) && echo "MANIFEST:$(DESTDIR)$(libdir)/$$ff" || :; \
+	done; \
+	if test -f "libast.la"; then \
+	   if test -f "libast_pass2.la"; then rm "libast_pass2.la"; fi; \
+	   sed -e 's/libast\./libast_pass2\./g' libast.la > libast_pass2.la; \
+	fi
+
+@[email-masked]: version.h.in configure.ac
+@PREDIST@	rm -f $@; $(predist_subs) version.h.in >$@
+@PREDIST@builddocs: builddocs.in configure.ac
+@PREDIST@	rm -f $@; $(predist_subs) builddocs.in >$@; chmod +x $@
+@PREDIST@addversion: addversion.in configure.ac
+@PREDIST@	rm -f $@; $(predist_subs) addversion.in >$@; chmod +x $@
+
+# Documentation
+@PREDIST@$(PAPER_DOCUMENTATION): sun211_figures builddocs addversion
+@PREDIST@	./builddocs
+
+# The contents of the sun211_figures directory is identical to that
+# sun210_figures
+sun211_figures: sun210_figures
+	$(LN_S) sun210_figures sun211_figures
+
+#  Need to include latex support files in the distribution tar ball so
+#  that the docs can be built from the tex source files. Requires environment
+#  variable STARLATEXSUPPORT to be deined. Is there a better way to do this?
+dist-hook:
+	cp -p $(STARLATEXSUPPORT)/starlink.cls $(distdir)/
+	cp -p $(STARLATEXSUPPORT)/starabbrev.sty $(distdir)/
+	cp -p $(STARLATEXSUPPORT)/starstyle.sty $(distdir)/
+	cp -p $(STARLATEXSUPPORT)/sst.sty $(distdir)/
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/ast/acinclude.m4 b/ast/acinclude.m4
new file mode 100644
index 0000000..96fb878
--- /dev/null
+++ b/ast/acinclude.m4
@@ -0,0 +1 @@
+m4_define([OVERRIDE_PREFIX],[/usr/local])
diff --git a/ast/aclocal.m4 b/ast/aclocal.m4
new file mode 100644
index 0000000..d05efd5
--- /dev/null
+++ b/ast/aclocal.m4
@@ -0,0 +1,11530 @@
+# generated automatically by aclocal 1.14.1-starlink -*- Autoconf -*-
+
+# Copyright (C) 1996-2013 Free Software Foundation, Inc.
+
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
+m4_ifndef([AC_AUTOCONF_VERSION],
+  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],,
+[m4_warning([this file was generated for autoconf 2.69.
+You have another version of autoconf.  It may work, but is not guaranteed to.
+If you have problems, you may need to regenerate the build system entirely.
+To do so, use the procedure documented by the package, typically 'autoreconf'.])])
+
+# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*-
+#
+#   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
+#                 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+#                 Foundation, Inc.
+#   Written by Gordon Matzigkeit, 1996
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+m4_define([_LT_COPYING], [dnl
+#   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
+#                 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+#                 Foundation, Inc.
+#   Written by Gordon Matzigkeit, 1996
+#
+#   This file is part of GNU Libtool.
+#
+# GNU Libtool is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# As a special exception to the GNU General Public License,
+# if you distribute this file as part of a program or library that
+# is built using GNU Libtool, you may include this file under the
+# same distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Libtool; see the file COPYING.  If not, a copy
+# can be downloaded from http://www.gnu.org/licenses/gpl.html, or
+# obtained by writing to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+])
+
+# serial 57 LT_INIT
+
+
+# LT_PREREQ(VERSION)
+# ------------------
+# Complain and exit if this libtool version is less that VERSION.
+m4_defun([LT_PREREQ],
+[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1,
+       [m4_default([$3],
+		   [m4_fatal([Libtool version $1 or higher is required],
+		             63)])],
+       [$2])])
+
+
+# _LT_CHECK_BUILDDIR
+# ------------------
+# Complain if the absolute build directory name contains unusual characters
+m4_defun([_LT_CHECK_BUILDDIR],
+[case `pwd` in
+  *\ * | *\	*)
+    AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;;
+esac
+])
+
+
+# LT_INIT([OPTIONS])
+# ------------------
+AC_DEFUN([LT_INIT],
+[AC_PREREQ([2.58])dnl We use AC_INCLUDES_DEFAULT
+AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
+AC_BEFORE([$0], [LT_LANG])dnl
+AC_BEFORE([$0], [LT_OUTPUT])dnl
+AC_BEFORE([$0], [LTDL_INIT])dnl
+m4_require([_LT_CHECK_BUILDDIR])dnl
+
+dnl Autoconf doesn't catch unexpanded LT_ macros by default:
+m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl
+m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl
+dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4
+dnl unless we require an AC_DEFUNed macro:
+AC_REQUIRE([LTOPTIONS_VERSION])dnl
+AC_REQUIRE([LTSUGAR_VERSION])dnl
+AC_REQUIRE([LTVERSION_VERSION])dnl
+AC_REQUIRE([LTOBSOLETE_VERSION])dnl
+m4_require([_LT_PROG_LTMAIN])dnl
+
+_LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}])
+
+dnl Parse OPTIONS
+_LT_SET_OPTIONS([$0], [$1])
+
+# This can be used to rebuild libtool when needed
+LIBTOOL_DEPS="$ltmain"
+
+# Always use our own libtool.
+LIBTOOL='$(SHELL) $(top_builddir)/libtool'
+AC_SUBST(LIBTOOL)dnl
+
+_LT_SETUP
+
+# Only expand once:
+m4_define([LT_INIT])
+])# LT_INIT
+
+# Old names:
+AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT])
+AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_PROG_LIBTOOL], [])
+dnl AC_DEFUN([AM_PROG_LIBTOOL], [])
+
+
+# _LT_CC_BASENAME(CC)
+# -------------------
+# Calculate cc_basename.  Skip known compiler wrappers and cross-prefix.
+m4_defun([_LT_CC_BASENAME],
+[for cc_temp in $1""; do
+  case $cc_temp in
+    compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;;
+    distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;;
+    \-*) ;;
+    *) break;;
+  esac
+done
+cc_basename=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+])
+
+
+# _LT_FILEUTILS_DEFAULTS
+# ----------------------
+# It is okay to use these file commands and assume they have been set
+# sensibly after `m4_require([_LT_FILEUTILS_DEFAULTS])'.
+m4_defun([_LT_FILEUTILS_DEFAULTS],
+[: ${CP="cp -f"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+])# _LT_FILEUTILS_DEFAULTS
+
+
+# _LT_SETUP
+# ---------
+m4_defun([_LT_SETUP],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl
+AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl
+
+_LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl
+dnl
+_LT_DECL([], [host_alias], [0], [The host system])dnl
+_LT_DECL([], [host], [0])dnl
+_LT_DECL([], [host_os], [0])dnl
+dnl
+_LT_DECL([], [build_alias], [0], [The build system])dnl
+_LT_DECL([], [build], [0])dnl
+_LT_DECL([], [build_os], [0])dnl
+dnl
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([LT_PATH_LD])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+dnl
+AC_REQUIRE([AC_PROG_LN_S])dnl
+test -z "$LN_S" && LN_S="ln -s"
+_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl
+dnl
+AC_REQUIRE([LT_CMD_MAX_LEN])dnl
+_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl
+_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl
+dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_CHECK_SHELL_FEATURES])dnl
+m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl
+m4_require([_LT_CMD_RELOAD])dnl
+m4_require([_LT_CHECK_MAGIC_METHOD])dnl
+m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl
+m4_require([_LT_CMD_OLD_ARCHIVE])dnl
+m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl
+m4_require([_LT_WITH_SYSROOT])dnl
+
+_LT_CONFIG_LIBTOOL_INIT([
+# See if we are running on zsh, and set the options which allow our
+# commands through without removal of \ escapes INIT.
+if test -n "\${ZSH_VERSION+set}" ; then
+   setopt NO_GLOB_SUBST
+fi
+])
+if test -n "${ZSH_VERSION+set}" ; then
+   setopt NO_GLOB_SUBST
+fi
+
+_LT_CHECK_OBJDIR
+
+m4_require([_LT_TAG_COMPILER])dnl
+
+case $host_os in
+aix3*)
+  # AIX sometimes has problems with the GCC collect2 program.  For some
+  # reason, if we set the COLLECT_NAMES environment variable, the problems
+  # vanish in a puff of smoke.
+  if test "X${COLLECT_NAMES+set}" != Xset; then
+    COLLECT_NAMES=
+    export COLLECT_NAMES
+  fi
+  ;;
+esac
+
+# Global variables:
+ofile=libtool
+can_build_shared=yes
+
+# All known linkers require a `.a' archive for static linking (except MSVC,
+# which needs '.lib').
+libext=a
+
+with_gnu_ld="$lt_cv_prog_gnu_ld"
+
+old_CC="$CC"
+old_CFLAGS="$CFLAGS"
+
+# Set sane defaults for various variables
+test -z "$CC" && CC=cc
+test -z "$LTCC" && LTCC=$CC
+test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS
+test -z "$LD" && LD=ld
+test -z "$ac_objext" && ac_objext=o
+
+_LT_CC_BASENAME([$compiler])
+
+# Only perform the check for file, if the check method requires it
+test -z "$MAGIC_CMD" && MAGIC_CMD=file
+case $deplibs_check_method in
+file_magic*)
+  if test "$file_magic_cmd" = '$MAGIC_CMD'; then
+    _LT_PATH_MAGIC
+  fi
+  ;;
+esac
+
+# Use C for the default configuration in the libtool script
+LT_SUPPORTED_TAG([CC])
+_LT_LANG_C_CONFIG
+_LT_LANG_DEFAULT_CONFIG
+_LT_CONFIG_COMMANDS
+])# _LT_SETUP
+
+
+# _LT_PREPARE_SED_QUOTE_VARS
+# --------------------------
+# Define a few sed substitution that help us do robust quoting.
+m4_defun([_LT_PREPARE_SED_QUOTE_VARS],
+[# Backslashify metacharacters that are still active within
+# double-quoted strings.
+sed_quote_subst='s/\([["`$\\]]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\([["`\\]]\)/\\\1/g'
+
+# Sed substitution to delay expansion of an escaped shell variable in a
+# double_quote_subst'ed string.
+delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g'
+
+# Sed substitution to delay expansion of an escaped single quote.
+delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g'
+
+# Sed substitution to avoid accidental globbing in evaled expressions
+no_glob_subst='s/\*/\\\*/g'
+])
+
+# _LT_PROG_LTMAIN
+# ---------------
+# Note that this code is called both from `configure', and `config.status'
+# now that we use AC_CONFIG_COMMANDS to generate libtool.  Notably,
+# `config.status' has no value for ac_aux_dir unless we are using Automake,
+# so we pass a copy along to make sure it has a sensible value anyway.
+m4_defun([_LT_PROG_LTMAIN],
+[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl
+_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir'])
+ltmain="$ac_aux_dir/ltmain.sh"
+])# _LT_PROG_LTMAIN
+
+
+
+# So that we can recreate a full libtool script including additional
+# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS
+# in macros and then make a single call at the end using the `libtool'
+# label.
+
+
+# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS])
+# ----------------------------------------
+# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later.
+m4_define([_LT_CONFIG_LIBTOOL_INIT],
+[m4_ifval([$1],
+          [m4_append([_LT_OUTPUT_LIBTOOL_INIT],
+                     [$1
+])])])
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_INIT])
+
+
+# _LT_CONFIG_LIBTOOL([COMMANDS])
+# ------------------------------
+# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later.
+m4_define([_LT_CONFIG_LIBTOOL],
+[m4_ifval([$1],
+          [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS],
+                     [$1
+])])])
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS])
+
+
+# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS])
+# -----------------------------------------------------
+m4_defun([_LT_CONFIG_SAVE_COMMANDS],
+[_LT_CONFIG_LIBTOOL([$1])
+_LT_CONFIG_LIBTOOL_INIT([$2])
+])
+
+
+# _LT_FORMAT_COMMENT([COMMENT])
+# -----------------------------
+# Add leading comment marks to the start of each line, and a trailing
+# full-stop to the whole comment if one is not present already.
+m4_define([_LT_FORMAT_COMMENT],
+[m4_ifval([$1], [
+m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])],
+              [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.])
+)])
+
+
+
+
+
+# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?])
+# -------------------------------------------------------------------
+# CONFIGNAME is the name given to the value in the libtool script.
+# VARNAME is the (base) name used in the configure script.
+# VALUE may be 0, 1 or 2 for a computed quote escaped value based on
+# VARNAME.  Any other value will be used directly.
+m4_define([_LT_DECL],
+[lt_if_append_uniq([lt_decl_varnames], [$2], [, ],
+    [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name],
+	[m4_ifval([$1], [$1], [$2])])
+    lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3])
+    m4_ifval([$4],
+	[lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])])
+    lt_dict_add_subkey([lt_decl_dict], [$2],
+	[tagged?], [m4_ifval([$5], [yes], [no])])])
+])
+
+
+# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION])
+# --------------------------------------------------------
+m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])])
+
+
+# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...])
+# ------------------------------------------------
+m4_define([lt_decl_tag_varnames],
+[_lt_decl_filter([tagged?], [yes], $@)])
+
+
+# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..])
+# ---------------------------------------------------------
+m4_define([_lt_decl_filter],
+[m4_case([$#],
+  [0], [m4_fatal([$0: too few arguments: $#])],
+  [1], [m4_fatal([$0: too few arguments: $#: $1])],
+  [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)],
+  [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)],
+  [lt_dict_filter([lt_decl_dict], $@)])[]dnl
+])
+
+
+# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...])
+# --------------------------------------------------
+m4_define([lt_decl_quote_varnames],
+[_lt_decl_filter([value], [1], $@)])
+
+
+# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...])
+# ---------------------------------------------------
+m4_define([lt_decl_dquote_varnames],
+[_lt_decl_filter([value], [2], $@)])
+
+
+# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...])
+# ---------------------------------------------------
+m4_define([lt_decl_varnames_tagged],
+[m4_assert([$# <= 2])dnl
+_$0(m4_quote(m4_default([$1], [[, ]])),
+    m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]),
+    m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))])
+m4_define([_lt_decl_varnames_tagged],
+[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])])
+
+
+# lt_decl_all_varnames([SEPARATOR], [VARNAME1...])
+# ------------------------------------------------
+m4_define([lt_decl_all_varnames],
+[_$0(m4_quote(m4_default([$1], [[, ]])),
+     m4_if([$2], [],
+	   m4_quote(lt_decl_varnames),
+	m4_quote(m4_shift($@))))[]dnl
+])
+m4_define([_lt_decl_all_varnames],
+[lt_join($@, lt_decl_varnames_tagged([$1],
+			lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl
+])
+
+
+# _LT_CONFIG_STATUS_DECLARE([VARNAME])
+# ------------------------------------
+# Quote a variable value, and forward it to `config.status' so that its
+# declaration there will have the same value as in `configure'.  VARNAME
+# must have a single quote delimited value for this to work.
+m4_define([_LT_CONFIG_STATUS_DECLARE],
+[$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`'])
+
+
+# _LT_CONFIG_STATUS_DECLARATIONS
+# ------------------------------
+# We delimit libtool config variables with single quotes, so when
+# we write them to config.status, we have to be sure to quote all
+# embedded single quotes properly.  In configure, this macro expands
+# each variable declared with _LT_DECL (and _LT_TAGDECL) into:
+#
+#    <var>='`$ECHO "$<var>" | $SED "$delay_single_quote_subst"`'
+m4_defun([_LT_CONFIG_STATUS_DECLARATIONS],
+[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames),
+    [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])])
+
+
+# _LT_LIBTOOL_TAGS
+# ----------------
+# Output comment and list of tags supported by the script
+m4_defun([_LT_LIBTOOL_TAGS],
+[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl
+available_tags="_LT_TAGS"dnl
+])
+
+
+# _LT_LIBTOOL_DECLARE(VARNAME, [TAG])
+# -----------------------------------
+# Extract the dictionary values for VARNAME (optionally with TAG) and
+# expand to a commented shell variable setting:
+#
+#    # Some comment about what VAR is for.
+#    visible_name=$lt_internal_name
+m4_define([_LT_LIBTOOL_DECLARE],
+[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1],
+					   [description])))[]dnl
+m4_pushdef([_libtool_name],
+    m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl
+m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])),
+    [0], [_libtool_name=[$]$1],
+    [1], [_libtool_name=$lt_[]$1],
+    [2], [_libtool_name=$lt_[]$1],
+    [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl
+m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl
+])
+
+
+# _LT_LIBTOOL_CONFIG_VARS
+# -----------------------
+# Produce commented declarations of non-tagged libtool config variables
+# suitable for insertion in the LIBTOOL CONFIG section of the `libtool'
+# script.  Tagged libtool config variables (even for the LIBTOOL CONFIG
+# section) are produced by _LT_LIBTOOL_TAG_VARS.
+m4_defun([_LT_LIBTOOL_CONFIG_VARS],
+[m4_foreach([_lt_var],
+    m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)),
+    [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])])
+
+
+# _LT_LIBTOOL_TAG_VARS(TAG)
+# -------------------------
+m4_define([_LT_LIBTOOL_TAG_VARS],
+[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames),
+    [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])])
+
+
+# _LT_TAGVAR(VARNAME, [TAGNAME])
+# ------------------------------
+m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])])
+
+
+# _LT_CONFIG_COMMANDS
+# -------------------
+# Send accumulated output to $CONFIG_STATUS.  Thanks to the lists of
+# variables for single and double quote escaping we saved from calls
+# to _LT_DECL, we can put quote escaped variables declarations
+# into `config.status', and then the shell code to quote escape them in
+# for loops in `config.status'.  Finally, any additional code accumulated
+# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded.
+m4_defun([_LT_CONFIG_COMMANDS],
+[AC_PROVIDE_IFELSE([LT_OUTPUT],
+	dnl If the libtool generation code has been placed in $CONFIG_LT,
+	dnl instead of duplicating it all over again into config.status,
+	dnl then we will have config.status run $CONFIG_LT later, so it
+	dnl needs to know what name is stored there:
+        [AC_CONFIG_COMMANDS([libtool],
+            [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])],
+    dnl If the libtool generation code is destined for config.status,
+    dnl expand the accumulated commands and init code now:
+    [AC_CONFIG_COMMANDS([libtool],
+        [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])])
+])#_LT_CONFIG_COMMANDS
+
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT],
+[
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+sed_quote_subst='$sed_quote_subst'
+double_quote_subst='$double_quote_subst'
+delay_variable_subst='$delay_variable_subst'
+_LT_CONFIG_STATUS_DECLARATIONS
+LTCC='$LTCC'
+LTCFLAGS='$LTCFLAGS'
+compiler='$compiler_DEFAULT'
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+  eval 'cat <<_LTECHO_EOF
+\$[]1
+_LTECHO_EOF'
+}
+
+# Quote evaled strings.
+for var in lt_decl_all_varnames([[ \
+]], lt_decl_quote_varnames); do
+    case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+    *[[\\\\\\\`\\"\\\$]]*)
+      eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\""
+      ;;
+    *)
+      eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+      ;;
+    esac
+done
+
+# Double-quote double-evaled strings.
+for var in lt_decl_all_varnames([[ \
+]], lt_decl_dquote_varnames); do
+    case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+    *[[\\\\\\\`\\"\\\$]]*)
+      eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\""
+      ;;
+    *)
+      eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+      ;;
+    esac
+done
+
+_LT_OUTPUT_LIBTOOL_INIT
+])
+
+# _LT_GENERATED_FILE_INIT(FILE, [COMMENT])
+# ------------------------------------
+# Generate a child script FILE with all initialization necessary to
+# reuse the environment learned by the parent script, and make the
+# file executable.  If COMMENT is supplied, it is inserted after the
+# `#!' sequence but before initialization text begins.  After this
+# macro, additional text can be appended to FILE to form the body of
+# the child script.  The macro ends with non-zero status if the
+# file could not be fully written (such as if the disk is full).
+m4_ifdef([AS_INIT_GENERATED],
+[m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])],
+[m4_defun([_LT_GENERATED_FILE_INIT],
+[m4_require([AS_PREPARE])]dnl
+[m4_pushdef([AS_MESSAGE_LOG_FD])]dnl
+[lt_write_fail=0
+cat >$1 <<_ASEOF || lt_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+$2
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$1 <<\_ASEOF || lt_write_fail=1
+AS_SHELL_SANITIZE
+_AS_PREPARE
+exec AS_MESSAGE_FD>&1
+_ASEOF
+test $lt_write_fail = 0 && chmod +x $1[]dnl
+m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT
+
+# LT_OUTPUT
+# ---------
+# This macro allows early generation of the libtool script (before
+# AC_OUTPUT is called), incase it is used in configure for compilation
+# tests.
+AC_DEFUN([LT_OUTPUT],
+[: ${CONFIG_LT=./config.lt}
+AC_MSG_NOTICE([creating $CONFIG_LT])
+_LT_GENERATED_FILE_INIT(["$CONFIG_LT"],
+[# Run this file to recreate a libtool stub with the current configuration.])
+
+cat >>"$CONFIG_LT" <<\_LTEOF
+lt_cl_silent=false
+exec AS_MESSAGE_LOG_FD>>config.log
+{
+  echo
+  AS_BOX([Running $as_me.])
+} >&AS_MESSAGE_LOG_FD
+
+lt_cl_help="\
+\`$as_me' creates a local libtool stub from the current configuration,
+for use in further configure time tests before the real libtool is
+generated.
+
+Usage: $[0] [[OPTIONS]]
+
+  -h, --help      print this help, then exit
+  -V, --version   print version number, then exit
+  -q, --quiet     do not print progress messages
+  -d, --debug     don't remove temporary files
+
+Report bugs to <[email-masked]>."
+
+lt_cl_version="\
+m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl
+m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION])
+configured by $[0], generated by m4_PACKAGE_STRING.
+
+Copyright (C) 2011 Free Software Foundation, Inc.
+This config.lt script is free software; the Free Software Foundation
+gives unlimited permision to copy, distribute and modify it."
+
+while test $[#] != 0
+do
+  case $[1] in
+    --version | --v* | -V )
+      echo "$lt_cl_version"; exit 0 ;;
+    --help | --h* | -h )
+      echo "$lt_cl_help"; exit 0 ;;
+    --debug | --d* | -d )
+      debug=: ;;
+    --quiet | --q* | --silent | --s* | -q )
+      lt_cl_silent=: ;;
+
+    -*) AC_MSG_ERROR([unrecognized option: $[1]
+Try \`$[0] --help' for more information.]) ;;
+
+    *) AC_MSG_ERROR([unrecognized argument: $[1]
+Try \`$[0] --help' for more information.]) ;;
+  esac
+  shift
+done
+
+if $lt_cl_silent; then
+  exec AS_MESSAGE_FD>/dev/null
+fi
+_LTEOF
+
+cat >>"$CONFIG_LT" <<_LTEOF
+_LT_OUTPUT_LIBTOOL_COMMANDS_INIT
+_LTEOF
+
+cat >>"$CONFIG_LT" <<\_LTEOF
+AC_MSG_NOTICE([creating $ofile])
+_LT_OUTPUT_LIBTOOL_COMMANDS
+AS_EXIT(0)
+_LTEOF
+chmod +x "$CONFIG_LT"
+
+# configure is writing to config.log, but config.lt does its own redirection,
+# appending to config.log, which fails on DOS, as config.log is still kept
+# open by configure.  Here we exec the FD to /dev/null, effectively closing
+# config.log, so it can be properly (re)opened and appended to by config.lt.
+lt_cl_success=:
+test "$silent" = yes &&
+  lt_config_lt_args="$lt_config_lt_args --quiet"
+exec AS_MESSAGE_LOG_FD>/dev/null
+$SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false
+exec AS_MESSAGE_LOG_FD>>config.log
+$lt_cl_success || AS_EXIT(1)
+])# LT_OUTPUT
+
+
+# _LT_CONFIG(TAG)
+# ---------------
+# If TAG is the built-in tag, create an initial libtool script with a
+# default configuration from the untagged config vars.  Otherwise add code
+# to config.status for appending the configuration named by TAG from the
+# matching tagged config vars.
+m4_defun([_LT_CONFIG],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+_LT_CONFIG_SAVE_COMMANDS([
+  m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl
+  m4_if(_LT_TAG, [C], [
+    # See if we are running on zsh, and set the options which allow our
+    # commands through without removal of \ escapes.
+    if test -n "${ZSH_VERSION+set}" ; then
+      setopt NO_GLOB_SUBST
+    fi
+
+    cfgfile="${ofile}T"
+    trap "$RM \"$cfgfile\"; exit 1" 1 2 15
+    $RM "$cfgfile"
+
+    cat <<_LT_EOF >> "$cfgfile"
+#! $SHELL
+
+# `$ECHO "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services.
+# Generated automatically by $as_me ($PACKAGE$TIMESTAMP) $VERSION
+# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+# NOTE: Changes made to this file will be lost: look at ltmain.sh.
+#
+_LT_COPYING
+_LT_LIBTOOL_TAGS
+
+# ### BEGIN LIBTOOL CONFIG
+_LT_LIBTOOL_CONFIG_VARS
+_LT_LIBTOOL_TAG_VARS
+# ### END LIBTOOL CONFIG
+
+_LT_EOF
+
+  case $host_os in
+  aix3*)
+    cat <<\_LT_EOF >> "$cfgfile"
+# AIX sometimes has problems with the GCC collect2 program.  For some
+# reason, if we set the COLLECT_NAMES environment variable, the problems
+# vanish in a puff of smoke.
+if test "X${COLLECT_NAMES+set}" != Xset; then
+  COLLECT_NAMES=
+  export COLLECT_NAMES
+fi
+_LT_EOF
+    ;;
+  esac
+
+  _LT_PROG_LTMAIN
+
+  # We use sed instead of cat because bash on DJGPP gets confused if
+  # if finds mixed CR/LF and LF-only lines.  Since sed operates in
+  # text mode, it properly converts lines to CR/LF.  This bash problem
+  # is reportedly fixed, but why not run on old versions too?
+  sed '$q' "$ltmain" >> "$cfgfile" \
+     || (rm -f "$cfgfile"; exit 1)
+
+  _LT_PROG_REPLACE_SHELLFNS
+
+   mv -f "$cfgfile" "$ofile" ||
+    (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile")
+  chmod +x "$ofile"
+],
+[cat <<_LT_EOF >> "$ofile"
+
+dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded
+dnl in a comment (ie after a #).
+# ### BEGIN LIBTOOL TAG CONFIG: $1
+_LT_LIBTOOL_TAG_VARS(_LT_TAG)
+# ### END LIBTOOL TAG CONFIG: $1
+_LT_EOF
+])dnl /m4_if
+],
+[m4_if([$1], [], [
+    PACKAGE='$PACKAGE'
+    VERSION='$VERSION'
+    TIMESTAMP='$TIMESTAMP'
+    RM='$RM'
+    ofile='$ofile'], [])
+])dnl /_LT_CONFIG_SAVE_COMMANDS
+])# _LT_CONFIG
+
+
+# LT_SUPPORTED_TAG(TAG)
+# ---------------------
+# Trace this macro to discover what tags are supported by the libtool
+# --tag option, using:
+#    autoconf --trace 'LT_SUPPORTED_TAG:$1'
+AC_DEFUN([LT_SUPPORTED_TAG], [])
+
+
+# C support is built-in for now
+m4_define([_LT_LANG_C_enabled], [])
+m4_define([_LT_TAGS], [])
+
+
+# LT_LANG(LANG)
+# -------------
+# Enable libtool support for the given language if not already enabled.
+AC_DEFUN([LT_LANG],
+[AC_BEFORE([$0], [LT_OUTPUT])dnl
+m4_case([$1],
+  [C],			[_LT_LANG(C)],
+  [C++],		[_LT_LANG(CXX)],
+  [Go],			[_LT_LANG(GO)],
+  [Java],		[_LT_LANG(GCJ)],
+  [Fortran 77],		[_LT_LANG(F77)],
+  [Fortran],		[_LT_LANG(FC)],
+  [Windows Resource],	[_LT_LANG(RC)],
+  [m4_ifdef([_LT_LANG_]$1[_CONFIG],
+    [_LT_LANG($1)],
+    [m4_fatal([$0: unsupported language: "$1"])])])dnl
+])# LT_LANG
+
+
+# _LT_LANG(LANGNAME)
+# ------------------
+m4_defun([_LT_LANG],
+[m4_ifdef([_LT_LANG_]$1[_enabled], [],
+  [LT_SUPPORTED_TAG([$1])dnl
+  m4_append([_LT_TAGS], [$1 ])dnl
+  m4_define([_LT_LANG_]$1[_enabled], [])dnl
+  _LT_LANG_$1_CONFIG($1)])dnl
+])# _LT_LANG
+
+
+m4_ifndef([AC_PROG_GO], [
+# NOTE: This macro has been submitted for inclusion into   #
+#  GNU Autoconf as AC_PROG_GO.  When it is available in    #
+#  a released version of Autoconf we should remove this    #
+#  macro and use it instead.                               #
+m4_defun([AC_PROG_GO],
+[AC_LANG_PUSH(Go)dnl
+AC_ARG_VAR([GOC],     [Go compiler command])dnl
+AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl
+_AC_ARG_VAR_LDFLAGS()dnl
+AC_CHECK_TOOL(GOC, gccgo)
+if test -z "$GOC"; then
+  if test -n "$ac_tool_prefix"; then
+    AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo])
+  fi
+fi
+if test -z "$GOC"; then
+  AC_CHECK_PROG(GOC, gccgo, gccgo, false)
+fi
+])#m4_defun
+])#m4_ifndef
+
+
+# _LT_LANG_DEFAULT_CONFIG
+# -----------------------
+m4_defun([_LT_LANG_DEFAULT_CONFIG],
+[AC_PROVIDE_IFELSE([AC_PROG_CXX],
+  [LT_LANG(CXX)],
+  [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])])
+
+AC_PROVIDE_IFELSE([AC_PROG_F77],
+  [LT_LANG(F77)],
+  [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])])
+
+AC_PROVIDE_IFELSE([AC_PROG_FC],
+  [LT_LANG(FC)],
+  [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])])
+
+dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal
+dnl pulling things in needlessly.
+AC_PROVIDE_IFELSE([AC_PROG_GCJ],
+  [LT_LANG(GCJ)],
+  [AC_PROVIDE_IFELSE([A][M_PROG_GCJ],
+    [LT_LANG(GCJ)],
+    [AC_PROVIDE_IFELSE([LT_PROG_GCJ],
+      [LT_LANG(GCJ)],
+      [m4_ifdef([AC_PROG_GCJ],
+	[m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])])
+       m4_ifdef([A][M_PROG_GCJ],
+	[m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])])
+       m4_ifdef([LT_PROG_GCJ],
+	[m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])])
+
+AC_PROVIDE_IFELSE([AC_PROG_GO],
+  [LT_LANG(GO)],
+  [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])])
+
+AC_PROVIDE_IFELSE([LT_PROG_RC],
+  [LT_LANG(RC)],
+  [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])])
+])# _LT_LANG_DEFAULT_CONFIG
+
+# Obsolete macros:
+AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)])
+AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)])
+AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)])
+AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)])
+AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_CXX], [])
+dnl AC_DEFUN([AC_LIBTOOL_F77], [])
+dnl AC_DEFUN([AC_LIBTOOL_FC], [])
+dnl AC_DEFUN([AC_LIBTOOL_GCJ], [])
+dnl AC_DEFUN([AC_LIBTOOL_RC], [])
+
+
+# _LT_TAG_COMPILER
+# ----------------
+m4_defun([_LT_TAG_COMPILER],
+[AC_REQUIRE([AC_PROG_CC])dnl
+
+_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl
+_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl
+_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl
+_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+])# _LT_TAG_COMPILER
+
+
+# _LT_COMPILER_BOILERPLATE
+# ------------------------
+# Check for compiler boilerplate output or warnings with
+# the simple compiler test code.
+m4_defun([_LT_COMPILER_BOILERPLATE],
+[m4_require([_LT_DECL_SED])dnl
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_compile_test_code" >conftest.$ac_ext
+eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_compiler_boilerplate=`cat conftest.err`
+$RM conftest*
+])# _LT_COMPILER_BOILERPLATE
+
+
+# _LT_LINKER_BOILERPLATE
+# ----------------------
+# Check for linker boilerplate output or warnings with
+# the simple link test code.
+m4_defun([_LT_LINKER_BOILERPLATE],
+[m4_require([_LT_DECL_SED])dnl
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_link_test_code" >conftest.$ac_ext
+eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_linker_boilerplate=`cat conftest.err`
+$RM -r conftest*
+])# _LT_LINKER_BOILERPLATE
+
+# _LT_REQUIRED_DARWIN_CHECKS
+# -------------------------
+m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[
+  case $host_os in
+    rhapsody* | darwin*)
+    AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:])
+    AC_CHECK_TOOL([NMEDIT], [nmedit], [:])
+    AC_CHECK_TOOL([LIPO], [lipo], [:])
+    AC_CHECK_TOOL([OTOOL], [otool], [:])
+    AC_CHECK_TOOL([OTOOL64], [otool64], [:])
+    _LT_DECL([], [DSYMUTIL], [1],
+      [Tool to manipulate archived DWARF debug symbol files on Mac OS X])
+    _LT_DECL([], [NMEDIT], [1],
+      [Tool to change global to local symbols on Mac OS X])
+    _LT_DECL([], [LIPO], [1],
+      [Tool to manipulate fat objects and archives on Mac OS X])
+    _LT_DECL([], [OTOOL], [1],
+      [ldd/readelf like tool for Mach-O binaries on Mac OS X])
+    _LT_DECL([], [OTOOL64], [1],
+      [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4])
+
+    AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod],
+      [lt_cv_apple_cc_single_mod=no
+      if test -z "${LT_MULTI_MODULE}"; then
+	# By default we will add the -single_module flag. You can override
+	# by either setting the environment variable LT_MULTI_MODULE
+	# non-empty at configure time, or by adding -multi_module to the
+	# link flags.
+	rm -rf libconftest.dylib*
+	echo "int foo(void){return 1;}" > conftest.c
+	echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD
+	$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+	  -dynamiclib -Wl,-single_module conftest.c 2>conftest.err
+        _lt_result=$?
+	# If there is a non-empty error log, and "single_module"
+	# appears in it, assume the flag caused a linker warning
+        if test -s conftest.err && $GREP single_module conftest.err; then
+	  cat conftest.err >&AS_MESSAGE_LOG_FD
+	# Otherwise, if the output was created with a 0 exit code from
+	# the compiler, it worked.
+	elif test -f libconftest.dylib && test $_lt_result -eq 0; then
+	  lt_cv_apple_cc_single_mod=yes
+	else
+	  cat conftest.err >&AS_MESSAGE_LOG_FD
+	fi
+	rm -rf libconftest.dylib*
+	rm -f conftest.*
+      fi])
+
+    AC_CACHE_CHECK([for -exported_symbols_list linker flag],
+      [lt_cv_ld_exported_symbols_list],
+      [lt_cv_ld_exported_symbols_list=no
+      save_LDFLAGS=$LDFLAGS
+      echo "_main" > conftest.sym
+      LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym"
+      AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])],
+	[lt_cv_ld_exported_symbols_list=yes],
+	[lt_cv_ld_exported_symbols_list=no])
+	LDFLAGS="$save_LDFLAGS"
+    ])
+
+    AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load],
+      [lt_cv_ld_force_load=no
+      cat > conftest.c << _LT_EOF
+int forced_loaded() { return 2;}
+_LT_EOF
+      echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD
+      $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD
+      echo "$AR cru libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD
+      $AR cru libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD
+      echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD
+      $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD
+      cat > conftest.c << _LT_EOF
+int main() { return 0;}
+_LT_EOF
+      echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD
+      $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err
+      _lt_result=$?
+      if test -s conftest.err && $GREP force_load conftest.err; then
+	cat conftest.err >&AS_MESSAGE_LOG_FD
+      elif test -f conftest && test $_lt_result -eq 0 && $GREP forced_load conftest >/dev/null 2>&1 ; then
+	lt_cv_ld_force_load=yes
+      else
+	cat conftest.err >&AS_MESSAGE_LOG_FD
+      fi
+        rm -f conftest.err libconftest.a conftest conftest.c
+        rm -rf conftest.dSYM
+    ])
+    case $host_os in
+    rhapsody* | darwin1.[[012]])
+      _lt_dar_allow_undefined='${wl}-undefined ${wl}suppress' ;;
+    darwin1.*)
+      _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+    darwin*) # darwin 5.x on
+      # if running on 10.5 or later, the deployment target defaults
+      # to the OS version, if on x86, and 10.4, the deployment
+      # target defaults to 10.4. Don't you love it?
+      case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in
+	10.0,*86*-darwin8*|10.0,*-darwin[[91]]*)
+	  _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+	10.[[012]]*)
+	  _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+	10.*)
+	  _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+      esac
+    ;;
+  esac
+    if test "$lt_cv_apple_cc_single_mod" = "yes"; then
+      _lt_dar_single_mod='$single_module'
+    fi
+    if test "$lt_cv_ld_exported_symbols_list" = "yes"; then
+      _lt_dar_export_syms=' ${wl}-exported_symbols_list,$output_objdir/${libname}-symbols.expsym'
+    else
+      _lt_dar_export_syms='~$NMEDIT -s $output_objdir/${libname}-symbols.expsym ${lib}'
+    fi
+    if test "$DSYMUTIL" != ":" && test "$lt_cv_ld_force_load" = "no"; then
+      _lt_dsymutil='~$DSYMUTIL $lib || :'
+    else
+      _lt_dsymutil=
+    fi
+    ;;
+  esac
+])
+
+
+# _LT_DARWIN_LINKER_FEATURES([TAG])
+# ---------------------------------
+# Checks for linker and compiler features on darwin
+m4_defun([_LT_DARWIN_LINKER_FEATURES],
+[
+  m4_require([_LT_REQUIRED_DARWIN_CHECKS])
+  _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+  _LT_TAGVAR(hardcode_direct, $1)=no
+  _LT_TAGVAR(hardcode_automatic, $1)=yes
+  _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+  if test "$lt_cv_ld_force_load" = "yes"; then
+    _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience ${wl}-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`'
+    m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes],
+                  [FC],  [_LT_TAGVAR(compiler_needs_object, $1)=yes])
+  else
+    _LT_TAGVAR(whole_archive_flag_spec, $1)=''
+  fi
+  _LT_TAGVAR(link_all_deplibs, $1)=yes
+  _LT_TAGVAR(allow_undefined_flag, $1)="$_lt_dar_allow_undefined"
+  case $cc_basename in
+     ifort*) _lt_dar_can_shared=yes ;;
+     *) _lt_dar_can_shared=$GCC ;;
+  esac
+  if test "$_lt_dar_can_shared" = "yes"; then
+    output_verbose_link_cmd=func_echo_all
+    _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}"
+    _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}"
+    _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}"
+    _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}"
+    m4_if([$1], [CXX],
+[   if test "$lt_cv_apple_cc_single_mod" != "yes"; then
+      _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dsymutil}"
+      _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dar_export_syms}${_lt_dsymutil}"
+    fi
+],[])
+  else
+  _LT_TAGVAR(ld_shlibs, $1)=no
+  fi
+])
+
+# _LT_SYS_MODULE_PATH_AIX([TAGNAME])
+# ----------------------------------
+# Links a minimal program and checks the executable
+# for the system default hardcoded library path. In most cases,
+# this is /usr/lib:/lib, but when the MPI compilers are used
+# the location of the communication and MPI libs are included too.
+# If we don't find anything, use the default library path according
+# to the aix ld manual.
+# Store the results from the different compilers for each TAGNAME.
+# Allow to override them for all tags through lt_cv_aix_libpath.
+m4_defun([_LT_SYS_MODULE_PATH_AIX],
+[m4_require([_LT_DECL_SED])dnl
+if test "${lt_cv_aix_libpath+set}" = set; then
+  aix_libpath=$lt_cv_aix_libpath
+else
+  AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])],
+  [AC_LINK_IFELSE([AC_LANG_PROGRAM],[
+  lt_aix_libpath_sed='[
+      /Import File Strings/,/^$/ {
+	  /^0/ {
+	      s/^0  *\([^ ]*\) *$/\1/
+	      p
+	  }
+      }]'
+  _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  # Check for a 64-bit object if we didn't find anything.
+  if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then
+    _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  fi],[])
+  if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then
+    _LT_TAGVAR([lt_cv_aix_libpath_], [$1])="/usr/lib:/lib"
+  fi
+  ])
+  aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])
+fi
+])# _LT_SYS_MODULE_PATH_AIX
+
+
+# _LT_SHELL_INIT(ARG)
+# -------------------
+m4_define([_LT_SHELL_INIT],
+[m4_divert_text([M4SH-INIT], [$1
+])])# _LT_SHELL_INIT
+
+
+
+# _LT_PROG_ECHO_BACKSLASH
+# -----------------------
+# Find how we can fake an echo command that does not interpret backslash.
+# In particular, with Autoconf 2.60 or later we add some code to the start
+# of the generated configure script which will find a shell with a builtin
+# printf (which we can use as an echo command).
+m4_defun([_LT_PROG_ECHO_BACKSLASH],
+[ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+
+AC_MSG_CHECKING([how to print strings])
+# Test print first, because it will be a builtin if present.
+if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \
+   test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then
+  ECHO='print -r --'
+elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then
+  ECHO='printf %s\n'
+else
+  # Use this function as a fallback that always works.
+  func_fallback_echo ()
+  {
+    eval 'cat <<_LTECHO_EOF
+$[]1
+_LTECHO_EOF'
+  }
+  ECHO='func_fallback_echo'
+fi
+
+# func_echo_all arg...
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+    $ECHO "$*" 
+}
+
+case "$ECHO" in
+  printf*) AC_MSG_RESULT([printf]) ;;
+  print*) AC_MSG_RESULT([print -r]) ;;
+  *) AC_MSG_RESULT([cat]) ;;
+esac
+
+m4_ifdef([_AS_DETECT_SUGGESTED],
+[_AS_DETECT_SUGGESTED([
+  test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || (
+    ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+    ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+    ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+    PATH=/empty FPATH=/empty; export PATH FPATH
+    test "X`printf %s $ECHO`" = "X$ECHO" \
+      || test "X`print -r -- $ECHO`" = "X$ECHO" )])])
+
+_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts])
+_LT_DECL([], [ECHO], [1], [An echo program that protects backslashes])
+])# _LT_PROG_ECHO_BACKSLASH
+
+
+# _LT_WITH_SYSROOT
+# ----------------
+AC_DEFUN([_LT_WITH_SYSROOT],
+[AC_MSG_CHECKING([for sysroot])
+AC_ARG_WITH([sysroot],
+[  --with-sysroot[=DIR] Search for dependent libraries within DIR
+                        (or the compiler's sysroot if not specified).],
+[], [with_sysroot=no])
+
+dnl lt_sysroot will always be passed unquoted.  We quote it here
+dnl in case the user passed a directory name.
+lt_sysroot=
+case ${with_sysroot} in #(
+ yes)
+   if test "$GCC" = yes; then
+     lt_sysroot=`$CC --print-sysroot 2>/dev/null`
+   fi
+   ;; #(
+ /*)
+   lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"`
+   ;; #(
+ no|'')
+   ;; #(
+ *)
+   AC_MSG_RESULT([${with_sysroot}])
+   AC_MSG_ERROR([The sysroot must be an absolute path.])
+   ;;
+esac
+
+ AC_MSG_RESULT([${lt_sysroot:-no}])
+_LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl
+[dependent libraries, and in which our libraries should be installed.])])
+
+# _LT_ENABLE_LOCK
+# ---------------
+m4_defun([_LT_ENABLE_LOCK],
+[AC_ARG_ENABLE([libtool-lock],
+  [AS_HELP_STRING([--disable-libtool-lock],
+    [avoid locking (might break parallel builds)])])
+test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes
+
+# Some flags need to be propagated to the compiler or linker for good
+# libtool support.
+case $host in
+ia64-*-hpux*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    case `/usr/bin/file conftest.$ac_objext` in
+      *ELF-32*)
+	HPUX_IA64_MODE="32"
+	;;
+      *ELF-64*)
+	HPUX_IA64_MODE="64"
+	;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+*-*-irix6*)
+  # Find out which ABI we are using.
+  echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    if test "$lt_cv_prog_gnu_ld" = yes; then
+      case `/usr/bin/file conftest.$ac_objext` in
+	*32-bit*)
+	  LD="${LD-ld} -melf32bsmip"
+	  ;;
+	*N32*)
+	  LD="${LD-ld} -melf32bmipn32"
+	  ;;
+	*64-bit*)
+	  LD="${LD-ld} -melf64bmip"
+	;;
+      esac
+    else
+      case `/usr/bin/file conftest.$ac_objext` in
+	*32-bit*)
+	  LD="${LD-ld} -32"
+	  ;;
+	*N32*)
+	  LD="${LD-ld} -n32"
+	  ;;
+	*64-bit*)
+	  LD="${LD-ld} -64"
+	  ;;
+      esac
+    fi
+  fi
+  rm -rf conftest*
+  ;;
+
+x86_64-*kfreebsd*-gnu|x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*| \
+s390*-*linux*|s390*-*tpf*|sparc*-*linux*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    case `/usr/bin/file conftest.o` in
+      *32-bit*)
+	case $host in
+	  x86_64-*kfreebsd*-gnu)
+	    LD="${LD-ld} -m elf_i386_fbsd"
+	    ;;
+	  x86_64-*linux*)
+	    LD="${LD-ld} -m elf_i386"
+	    ;;
+	  ppc64-*linux*|powerpc64-*linux*)
+	    LD="${LD-ld} -m elf32ppclinux"
+	    ;;
+	  s390x-*linux*)
+	    LD="${LD-ld} -m elf_s390"
+	    ;;
+	  sparc64-*linux*)
+	    LD="${LD-ld} -m elf32_sparc"
+	    ;;
+	esac
+	;;
+      *64-bit*)
+	case $host in
+	  x86_64-*kfreebsd*-gnu)
+	    LD="${LD-ld} -m elf_x86_64_fbsd"
+	    ;;
+	  x86_64-*linux*)
+	    LD="${LD-ld} -m elf_x86_64"
+	    ;;
+	  ppc*-*linux*|powerpc*-*linux*)
+	    LD="${LD-ld} -m elf64ppc"
+	    ;;
+	  s390*-*linux*|s390*-*tpf*)
+	    LD="${LD-ld} -m elf64_s390"
+	    ;;
+	  sparc*-*linux*)
+	    LD="${LD-ld} -m elf64_sparc"
+	    ;;
+	esac
+	;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+
+*-*-sco3.2v5*)
+  # On SCO OpenServer 5, we need -belf to get full-featured binaries.
+  SAVE_CFLAGS="$CFLAGS"
+  CFLAGS="$CFLAGS -belf"
+  AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf,
+    [AC_LANG_PUSH(C)
+     AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no])
+     AC_LANG_POP])
+  if test x"$lt_cv_cc_needs_belf" != x"yes"; then
+    # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf
+    CFLAGS="$SAVE_CFLAGS"
+  fi
+  ;;
+*-*solaris*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    case `/usr/bin/file conftest.o` in
+    *64-bit*)
+      case $lt_cv_prog_gnu_ld in
+      yes*)
+        case $host in
+        i?86-*-solaris*)
+          LD="${LD-ld} -m elf_x86_64"
+          ;;
+        sparc*-*-solaris*)
+          LD="${LD-ld} -m elf64_sparc"
+          ;;
+        esac
+        # GNU ld 2.21 introduced _sol2 emulations.  Use them if available.
+        if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then
+          LD="${LD-ld}_sol2"
+        fi
+        ;;
+      *)
+	if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then
+	  LD="${LD-ld} -64"
+	fi
+	;;
+      esac
+      ;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+esac
+
+need_locks="$enable_libtool_lock"
+])# _LT_ENABLE_LOCK
+
+
+# _LT_PROG_AR
+# -----------
+m4_defun([_LT_PROG_AR],
+[AC_CHECK_TOOLS(AR, [ar], false)
+: ${AR=ar}
+: ${AR_FLAGS=cru}
+_LT_DECL([], [AR], [1], [The archiver])
+_LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive])
+
+AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file],
+  [lt_cv_ar_at_file=no
+   AC_COMPILE_IFELSE([AC_LANG_PROGRAM],
+     [echo conftest.$ac_objext > conftest.lst
+      lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD'
+      AC_TRY_EVAL([lt_ar_try])
+      if test "$ac_status" -eq 0; then
+	# Ensure the archiver fails upon bogus file names.
+	rm -f conftest.$ac_objext libconftest.a
+	AC_TRY_EVAL([lt_ar_try])
+	if test "$ac_status" -ne 0; then
+          lt_cv_ar_at_file=@
+        fi
+      fi
+      rm -f conftest.* libconftest.a
+     ])
+  ])
+
+if test "x$lt_cv_ar_at_file" = xno; then
+  archiver_list_spec=
+else
+  archiver_list_spec=$lt_cv_ar_at_file
+fi
+_LT_DECL([], [archiver_list_spec], [1],
+  [How to feed a file listing to the archiver])
+])# _LT_PROG_AR
+
+
+# _LT_CMD_OLD_ARCHIVE
+# -------------------
+m4_defun([_LT_CMD_OLD_ARCHIVE],
+[_LT_PROG_AR
+
+AC_CHECK_TOOL(STRIP, strip, :)
+test -z "$STRIP" && STRIP=:
+_LT_DECL([], [STRIP], [1], [A symbol stripping program])
+
+AC_CHECK_TOOL(RANLIB, ranlib, :)
+test -z "$RANLIB" && RANLIB=:
+_LT_DECL([], [RANLIB], [1],
+    [Commands used to install an old-style archive])
+
+# Determine commands to create old-style static archives.
+old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs'
+old_postinstall_cmds='chmod 644 $oldlib'
+old_postuninstall_cmds=
+
+if test -n "$RANLIB"; then
+  case $host_os in
+  openbsd*)
+    old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib"
+    ;;
+  *)
+    old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib"
+    ;;
+  esac
+  old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib"
+fi
+
+case $host_os in
+  darwin*)
+    lock_old_archive_extraction=yes ;;
+  *)
+    lock_old_archive_extraction=no ;;
+esac
+_LT_DECL([], [old_postinstall_cmds], [2])
+_LT_DECL([], [old_postuninstall_cmds], [2])
+_LT_TAGDECL([], [old_archive_cmds], [2],
+    [Commands used to build an old-style archive])
+_LT_DECL([], [lock_old_archive_extraction], [0],
+    [Whether to use a lock for old archive extraction])
+])# _LT_CMD_OLD_ARCHIVE
+
+
+# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS,
+#		[OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE])
+# ----------------------------------------------------------------
+# Check whether the given compiler option works
+AC_DEFUN([_LT_COMPILER_OPTION],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_SED])dnl
+AC_CACHE_CHECK([$1], [$2],
+  [$2=no
+   m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4])
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+   lt_compiler_flag="$3"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   # The option is referenced via a variable to avoid confusing sed.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD)
+   (eval "$lt_compile" 2>conftest.err)
+   ac_status=$?
+   cat conftest.err >&AS_MESSAGE_LOG_FD
+   echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+   if (exit $ac_status) && test -s "$ac_outfile"; then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings other than the usual output.
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+     $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+     if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+       $2=yes
+     fi
+   fi
+   $RM conftest*
+])
+
+if test x"[$]$2" = xyes; then
+    m4_if([$5], , :, [$5])
+else
+    m4_if([$6], , :, [$6])
+fi
+])# _LT_COMPILER_OPTION
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], [])
+
+
+# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS,
+#                  [ACTION-SUCCESS], [ACTION-FAILURE])
+# ----------------------------------------------------
+# Check whether the given linker option works
+AC_DEFUN([_LT_LINKER_OPTION],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_SED])dnl
+AC_CACHE_CHECK([$1], [$2],
+  [$2=no
+   save_LDFLAGS="$LDFLAGS"
+   LDFLAGS="$LDFLAGS $3"
+   echo "$lt_simple_link_test_code" > conftest.$ac_ext
+   if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+     # The linker can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     if test -s conftest.err; then
+       # Append any errors to the config.log.
+       cat conftest.err 1>&AS_MESSAGE_LOG_FD
+       $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+       $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+       if diff conftest.exp conftest.er2 >/dev/null; then
+         $2=yes
+       fi
+     else
+       $2=yes
+     fi
+   fi
+   $RM -r conftest*
+   LDFLAGS="$save_LDFLAGS"
+])
+
+if test x"[$]$2" = xyes; then
+    m4_if([$4], , :, [$4])
+else
+    m4_if([$5], , :, [$5])
+fi
+])# _LT_LINKER_OPTION
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], [])
+
+
+# LT_CMD_MAX_LEN
+#---------------
+AC_DEFUN([LT_CMD_MAX_LEN],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+# find the maximum length of command line arguments
+AC_MSG_CHECKING([the maximum length of command line arguments])
+AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl
+  i=0
+  teststring="ABCD"
+
+  case $build_os in
+  msdosdjgpp*)
+    # On DJGPP, this test can blow up pretty badly due to problems in libc
+    # (any single argument exceeding 2000 bytes causes a buffer overrun
+    # during glob expansion).  Even if it were fixed, the result of this
+    # check would be larger than it should be.
+    lt_cv_sys_max_cmd_len=12288;    # 12K is about right
+    ;;
+
+  gnu*)
+    # Under GNU Hurd, this test is not required because there is
+    # no limit to the length of command line arguments.
+    # Libtool will interpret -1 as no limit whatsoever
+    lt_cv_sys_max_cmd_len=-1;
+    ;;
+
+  cygwin* | mingw* | cegcc*)
+    # On Win9x/ME, this test blows up -- it succeeds, but takes
+    # about 5 minutes as the teststring grows exponentially.
+    # Worse, since 9x/ME are not pre-emptively multitasking,
+    # you end up with a "frozen" computer, even though with patience
+    # the test eventually succeeds (with a max line length of 256k).
+    # Instead, let's just punt: use the minimum linelength reported by
+    # all of the supported platforms: 8192 (on NT/2K/XP).
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  mint*)
+    # On MiNT this can take a long time and run out of memory.
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  amigaos*)
+    # On AmigaOS with pdksh, this test takes hours, literally.
+    # So we just punt and use a minimum line length of 8192.
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  netbsd* | freebsd* | openbsd* | darwin* | dragonfly*)
+    # This has been around since 386BSD, at least.  Likely further.
+    if test -x /sbin/sysctl; then
+      lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax`
+    elif test -x /usr/sbin/sysctl; then
+      lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax`
+    else
+      lt_cv_sys_max_cmd_len=65536	# usable default for all BSDs
+    fi
+    # And add a safety zone
+    lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+    lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+    ;;
+
+  interix*)
+    # We know the value 262144 and hardcode it with a safety zone (like BSD)
+    lt_cv_sys_max_cmd_len=196608
+    ;;
+
+  os2*)
+    # The test takes a long time on OS/2.
+    lt_cv_sys_max_cmd_len=8192
+    ;;
+
+  osf*)
+    # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure
+    # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not
+    # nice to cause kernel panics so lets avoid the loop below.
+    # First set a reasonable default.
+    lt_cv_sys_max_cmd_len=16384
+    #
+    if test -x /sbin/sysconfig; then
+      case `/sbin/sysconfig -q proc exec_disable_arg_limit` in
+        *1*) lt_cv_sys_max_cmd_len=-1 ;;
+      esac
+    fi
+    ;;
+  sco3.2v5*)
+    lt_cv_sys_max_cmd_len=102400
+    ;;
+  sysv5* | sco5v6* | sysv4.2uw2*)
+    kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null`
+    if test -n "$kargmax"; then
+      lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[	 ]]//'`
+    else
+      lt_cv_sys_max_cmd_len=32768
+    fi
+    ;;
+  *)
+    lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null`
+    if test -n "$lt_cv_sys_max_cmd_len"; then
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+    else
+      # Make teststring a little bigger before we do anything with it.
+      # a 1K string should be a reasonable start.
+      for i in 1 2 3 4 5 6 7 8 ; do
+        teststring=$teststring$teststring
+      done
+      SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}}
+      # If test is not a shell built-in, we'll probably end up computing a
+      # maximum length that is only half of the actual maximum length, but
+      # we can't tell.
+      while { test "X"`env echo "$teststring$teststring" 2>/dev/null` \
+	         = "X$teststring$teststring"; } >/dev/null 2>&1 &&
+	      test $i != 17 # 1/2 MB should be enough
+      do
+        i=`expr $i + 1`
+        teststring=$teststring$teststring
+      done
+      # Only check the string length outside the loop.
+      lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1`
+      teststring=
+      # Add a significant safety factor because C++ compilers can tack on
+      # massive amounts of additional arguments before passing them to the
+      # linker.  It appears as though 1/2 is a usable value.
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2`
+    fi
+    ;;
+  esac
+])
+if test -n $lt_cv_sys_max_cmd_len ; then
+  AC_MSG_RESULT($lt_cv_sys_max_cmd_len)
+else
+  AC_MSG_RESULT(none)
+fi
+max_cmd_len=$lt_cv_sys_max_cmd_len
+_LT_DECL([], [max_cmd_len], [0],
+    [What is the maximum length of a command?])
+])# LT_CMD_MAX_LEN
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], [])
+
+
+# _LT_HEADER_DLFCN
+# ----------------
+m4_defun([_LT_HEADER_DLFCN],
+[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl
+])# _LT_HEADER_DLFCN
+
+
+# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE,
+#                      ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING)
+# ----------------------------------------------------------------
+m4_defun([_LT_TRY_DLOPEN_SELF],
+[m4_require([_LT_HEADER_DLFCN])dnl
+if test "$cross_compiling" = yes; then :
+  [$4]
+else
+  lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+  lt_status=$lt_dlunknown
+  cat > conftest.$ac_ext <<_LT_EOF
+[#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+#  define LT_DLGLOBAL		RTLD_GLOBAL
+#else
+#  ifdef DL_GLOBAL
+#    define LT_DLGLOBAL		DL_GLOBAL
+#  else
+#    define LT_DLGLOBAL		0
+#  endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+   find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+#  ifdef RTLD_LAZY
+#    define LT_DLLAZY_OR_NOW		RTLD_LAZY
+#  else
+#    ifdef DL_LAZY
+#      define LT_DLLAZY_OR_NOW		DL_LAZY
+#    else
+#      ifdef RTLD_NOW
+#        define LT_DLLAZY_OR_NOW	RTLD_NOW
+#      else
+#        ifdef DL_NOW
+#          define LT_DLLAZY_OR_NOW	DL_NOW
+#        else
+#          define LT_DLLAZY_OR_NOW	0
+#        endif
+#      endif
+#    endif
+#  endif
+#endif
+
+/* When -fvisbility=hidden is used, assume the code has been annotated
+   correspondingly for the symbols needed.  */
+#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+  void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+  int status = $lt_dlunknown;
+
+  if (self)
+    {
+      if (dlsym (self,"fnord"))       status = $lt_dlno_uscore;
+      else
+        {
+	  if (dlsym( self,"_fnord"))  status = $lt_dlneed_uscore;
+          else puts (dlerror ());
+	}
+      /* dlclose (self); */
+    }
+  else
+    puts (dlerror ());
+
+  return status;
+}]
+_LT_EOF
+  if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext} 2>/dev/null; then
+    (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null
+    lt_status=$?
+    case x$lt_status in
+      x$lt_dlno_uscore) $1 ;;
+      x$lt_dlneed_uscore) $2 ;;
+      x$lt_dlunknown|x*) $3 ;;
+    esac
+  else :
+    # compilation failed
+    $3
+  fi
+fi
+rm -fr conftest*
+])# _LT_TRY_DLOPEN_SELF
+
+
+# LT_SYS_DLOPEN_SELF
+# ------------------
+AC_DEFUN([LT_SYS_DLOPEN_SELF],
+[m4_require([_LT_HEADER_DLFCN])dnl
+if test "x$enable_dlopen" != xyes; then
+  enable_dlopen=unknown
+  enable_dlopen_self=unknown
+  enable_dlopen_self_static=unknown
+else
+  lt_cv_dlopen=no
+  lt_cv_dlopen_libs=
+
+  case $host_os in
+  beos*)
+    lt_cv_dlopen="load_add_on"
+    lt_cv_dlopen_libs=
+    lt_cv_dlopen_self=yes
+    ;;
+
+  mingw* | pw32* | cegcc*)
+    lt_cv_dlopen="LoadLibrary"
+    lt_cv_dlopen_libs=
+    ;;
+
+  cygwin*)
+    lt_cv_dlopen="dlopen"
+    lt_cv_dlopen_libs=
+    ;;
+
+  darwin*)
+  # if libdl is installed we need to link against it
+    AC_CHECK_LIB([dl], [dlopen],
+		[lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],[
+    lt_cv_dlopen="dyld"
+    lt_cv_dlopen_libs=
+    lt_cv_dlopen_self=yes
+    ])
+    ;;
+
+  *)
+    AC_CHECK_FUNC([shl_load],
+	  [lt_cv_dlopen="shl_load"],
+      [AC_CHECK_LIB([dld], [shl_load],
+	    [lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld"],
+	[AC_CHECK_FUNC([dlopen],
+	      [lt_cv_dlopen="dlopen"],
+	  [AC_CHECK_LIB([dl], [dlopen],
+		[lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],
+	    [AC_CHECK_LIB([svld], [dlopen],
+		  [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"],
+	      [AC_CHECK_LIB([dld], [dld_link],
+		    [lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld"])
+	      ])
+	    ])
+	  ])
+	])
+      ])
+    ;;
+  esac
+
+  if test "x$lt_cv_dlopen" != xno; then
+    enable_dlopen=yes
+  else
+    enable_dlopen=no
+  fi
+
+  case $lt_cv_dlopen in
+  dlopen)
+    save_CPPFLAGS="$CPPFLAGS"
+    test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H"
+
+    save_LDFLAGS="$LDFLAGS"
+    wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\"
+
+    save_LIBS="$LIBS"
+    LIBS="$lt_cv_dlopen_libs $LIBS"
+
+    AC_CACHE_CHECK([whether a program can dlopen itself],
+	  lt_cv_dlopen_self, [dnl
+	  _LT_TRY_DLOPEN_SELF(
+	    lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes,
+	    lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross)
+    ])
+
+    if test "x$lt_cv_dlopen_self" = xyes; then
+      wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\"
+      AC_CACHE_CHECK([whether a statically linked program can dlopen itself],
+	  lt_cv_dlopen_self_static, [dnl
+	  _LT_TRY_DLOPEN_SELF(
+	    lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes,
+	    lt_cv_dlopen_self_static=no,  lt_cv_dlopen_self_static=cross)
+      ])
+    fi
+
+    CPPFLAGS="$save_CPPFLAGS"
+    LDFLAGS="$save_LDFLAGS"
+    LIBS="$save_LIBS"
+    ;;
+  esac
+
+  case $lt_cv_dlopen_self in
+  yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;;
+  *) enable_dlopen_self=unknown ;;
+  esac
+
+  case $lt_cv_dlopen_self_static in
+  yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;;
+  *) enable_dlopen_self_static=unknown ;;
+  esac
+fi
+_LT_DECL([dlopen_support], [enable_dlopen], [0],
+	 [Whether dlopen is supported])
+_LT_DECL([dlopen_self], [enable_dlopen_self], [0],
+	 [Whether dlopen of programs is supported])
+_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0],
+	 [Whether dlopen of statically linked programs is supported])
+])# LT_SYS_DLOPEN_SELF
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], [])
+
+
+# _LT_COMPILER_C_O([TAGNAME])
+# ---------------------------
+# Check to see if options -c and -o are simultaneously supported by compiler.
+# This macro does not hard code the compiler like AC_PROG_CC_C_O.
+m4_defun([_LT_COMPILER_C_O],
+[m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext],
+  [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)],
+  [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no
+   $RM -r conftest 2>/dev/null
+   mkdir conftest
+   cd conftest
+   mkdir out
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+   lt_compiler_flag="-o out/conftest2.$ac_objext"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD)
+   (eval "$lt_compile" 2>out/conftest.err)
+   ac_status=$?
+   cat out/conftest.err >&AS_MESSAGE_LOG_FD
+   echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+   if (exit $ac_status) && test -s out/conftest2.$ac_objext
+   then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+     $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+     if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+       _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes
+     fi
+   fi
+   chmod u+w . 2>&AS_MESSAGE_LOG_FD
+   $RM conftest*
+   # SGI C++ compiler will create directory out/ii_files/ for
+   # template instantiation
+   test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+   $RM out/* && rmdir out
+   cd ..
+   $RM -r conftest
+   $RM conftest*
+])
+_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1],
+	[Does compiler simultaneously support -c and -o options?])
+])# _LT_COMPILER_C_O
+
+
+# _LT_COMPILER_FILE_LOCKS([TAGNAME])
+# ----------------------------------
+# Check to see if we can do hard links to lock some files if needed
+m4_defun([_LT_COMPILER_FILE_LOCKS],
+[m4_require([_LT_ENABLE_LOCK])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+_LT_COMPILER_C_O([$1])
+
+hard_links="nottested"
+if test "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" = no && test "$need_locks" != no; then
+  # do not overwrite the value of need_locks provided by the user
+  AC_MSG_CHECKING([if we can lock with hard links])
+  hard_links=yes
+  $RM conftest*
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  touch conftest.a
+  ln conftest.a conftest.b 2>&5 || hard_links=no
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  AC_MSG_RESULT([$hard_links])
+  if test "$hard_links" = no; then
+    AC_MSG_WARN([`$CC' does not support `-c -o', so `make -j' may be unsafe])
+    need_locks=warn
+  fi
+else
+  need_locks=no
+fi
+_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?])
+])# _LT_COMPILER_FILE_LOCKS
+
+
+# _LT_CHECK_OBJDIR
+# ----------------
+m4_defun([_LT_CHECK_OBJDIR],
+[AC_CACHE_CHECK([for objdir], [lt_cv_objdir],
+[rm -f .libs 2>/dev/null
+mkdir .libs 2>/dev/null
+if test -d .libs; then
+  lt_cv_objdir=.libs
+else
+  # MS-DOS does not allow filenames that begin with a dot.
+  lt_cv_objdir=_libs
+fi
+rmdir .libs 2>/dev/null])
+objdir=$lt_cv_objdir
+_LT_DECL([], [objdir], [0],
+         [The name of the directory that contains temporary libtool files])dnl
+m4_pattern_allow([LT_OBJDIR])dnl
+AC_DEFINE_UNQUOTED(LT_OBJDIR, "$lt_cv_objdir/",
+  [Define to the sub-directory in which libtool stores uninstalled libraries.])
+])# _LT_CHECK_OBJDIR
+
+
+# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME])
+# --------------------------------------
+# Check hardcoding attributes.
+m4_defun([_LT_LINKER_HARDCODE_LIBPATH],
+[AC_MSG_CHECKING([how to hardcode library paths into programs])
+_LT_TAGVAR(hardcode_action, $1)=
+if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" ||
+   test -n "$_LT_TAGVAR(runpath_var, $1)" ||
+   test "X$_LT_TAGVAR(hardcode_automatic, $1)" = "Xyes" ; then
+
+  # We can hardcode non-existent directories.
+  if test "$_LT_TAGVAR(hardcode_direct, $1)" != no &&
+     # If the only mechanism to avoid hardcoding is shlibpath_var, we
+     # have to relink, otherwise we might link with an installed library
+     # when we should be linking with a yet-to-be-installed one
+     ## test "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" != no &&
+     test "$_LT_TAGVAR(hardcode_minus_L, $1)" != no; then
+    # Linking always hardcodes the temporary library directory.
+    _LT_TAGVAR(hardcode_action, $1)=relink
+  else
+    # We can link without hardcoding, and we can hardcode nonexisting dirs.
+    _LT_TAGVAR(hardcode_action, $1)=immediate
+  fi
+else
+  # We cannot hardcode anything, or else we can only hardcode existing
+  # directories.
+  _LT_TAGVAR(hardcode_action, $1)=unsupported
+fi
+AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)])
+
+if test "$_LT_TAGVAR(hardcode_action, $1)" = relink ||
+   test "$_LT_TAGVAR(inherit_rpath, $1)" = yes; then
+  # Fast installation is not supported
+  enable_fast_install=no
+elif test "$shlibpath_overrides_runpath" = yes ||
+     test "$enable_shared" = no; then
+  # Fast installation is not necessary
+  enable_fast_install=needless
+fi
+_LT_TAGDECL([], [hardcode_action], [0],
+    [How to hardcode a shared library path into an executable])
+])# _LT_LINKER_HARDCODE_LIBPATH
+
+
+# _LT_CMD_STRIPLIB
+# ----------------
+m4_defun([_LT_CMD_STRIPLIB],
+[m4_require([_LT_DECL_EGREP])
+striplib=
+old_striplib=
+AC_MSG_CHECKING([whether stripping libraries is possible])
+if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then
+  test -z "$old_striplib" && old_striplib="$STRIP --strip-debug"
+  test -z "$striplib" && striplib="$STRIP --strip-unneeded"
+  AC_MSG_RESULT([yes])
+else
+# FIXME - insert some real tests, host_os isn't really good enough
+  case $host_os in
+  darwin*)
+    if test -n "$STRIP" ; then
+      striplib="$STRIP -x"
+      old_striplib="$STRIP -S"
+      AC_MSG_RESULT([yes])
+    else
+      AC_MSG_RESULT([no])
+    fi
+    ;;
+  *)
+    AC_MSG_RESULT([no])
+    ;;
+  esac
+fi
+_LT_DECL([], [old_striplib], [1], [Commands to strip libraries])
+_LT_DECL([], [striplib], [1])
+])# _LT_CMD_STRIPLIB
+
+
+# _LT_SYS_DYNAMIC_LINKER([TAG])
+# -----------------------------
+# PORTME Fill in your ld.so characteristics
+m4_defun([_LT_SYS_DYNAMIC_LINKER],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_OBJDUMP])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_CHECK_SHELL_FEATURES])dnl
+AC_MSG_CHECKING([dynamic linker characteristics])
+m4_if([$1],
+	[], [
+if test "$GCC" = yes; then
+  case $host_os in
+    darwin*) lt_awk_arg="/^libraries:/,/LR/" ;;
+    *) lt_awk_arg="/^libraries:/" ;;
+  esac
+  case $host_os in
+    mingw* | cegcc*) lt_sed_strip_eq="s,=\([[A-Za-z]]:\),\1,g" ;;
+    *) lt_sed_strip_eq="s,=/,/,g" ;;
+  esac
+  lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq`
+  case $lt_search_path_spec in
+  *\;*)
+    # if the path contains ";" then we assume it to be the separator
+    # otherwise default to the standard path separator (i.e. ":") - it is
+    # assumed that no part of a normal pathname contains ";" but that should
+    # okay in the real world where ";" in dirpaths is itself problematic.
+    lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'`
+    ;;
+  *)
+    lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"`
+    ;;
+  esac
+  # Ok, now we have the path, separated by spaces, we can step through it
+  # and add multilib dir if necessary.
+  lt_tmp_lt_search_path_spec=
+  lt_multi_os_dir=`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null`
+  for lt_sys_path in $lt_search_path_spec; do
+    if test -d "$lt_sys_path/$lt_multi_os_dir"; then
+      lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path/$lt_multi_os_dir"
+    else
+      test -d "$lt_sys_path" && \
+	lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path"
+    fi
+  done
+  lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk '
+BEGIN {RS=" "; FS="/|\n";} {
+  lt_foo="";
+  lt_count=0;
+  for (lt_i = NF; lt_i > 0; lt_i--) {
+    if ($lt_i != "" && $lt_i != ".") {
+      if ($lt_i == "..") {
+        lt_count++;
+      } else {
+        if (lt_count == 0) {
+          lt_foo="/" $lt_i lt_foo;
+        } else {
+          lt_count--;
+        }
+      }
+    }
+  }
+  if (lt_foo != "") { lt_freq[[lt_foo]]++; }
+  if (lt_freq[[lt_foo]] == 1) { print lt_foo; }
+}'`
+  # AWK program above erroneously prepends '/' to C:/dos/paths
+  # for these hosts.
+  case $host_os in
+    mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\
+      $SED 's,/\([[A-Za-z]]:\),\1,g'` ;;
+  esac
+  sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP`
+else
+  sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+fi])
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+shrext_cmds=".so"
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+need_lib_prefix=unknown
+hardcode_into_libs=no
+
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+need_version=unknown
+
+case $host_os in
+aix3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a'
+  shlibpath_var=LIBPATH
+
+  # AIX 3 has no versioning support, so we append a major version to the name.
+  soname_spec='${libname}${release}${shared_ext}$major'
+  ;;
+
+aix[[4-9]]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  hardcode_into_libs=yes
+  if test "$host_cpu" = ia64; then
+    # AIX 5 supports IA64
+    library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}'
+    shlibpath_var=LD_LIBRARY_PATH
+  else
+    # With GCC up to 2.95.x, collect2 would create an import file
+    # for dependence libraries.  The import file would start with
+    # the line `#! .'.  This would cause the generated library to
+    # depend on `.', always an invalid library.  This was fixed in
+    # development snapshots of GCC prior to 3.0.
+    case $host_os in
+      aix4 | aix4.[[01]] | aix4.[[01]].*)
+      if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+	   echo ' yes '
+	   echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then
+	:
+      else
+	can_build_shared=no
+      fi
+      ;;
+    esac
+    # AIX (on Power*) has no versioning support, so currently we can not hardcode correct
+    # soname into executable. Probably we can add versioning support to
+    # collect2, so additional links can be useful in future.
+    if test "$aix_use_runtimelinking" = yes; then
+      # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+      # instead of lib<name>.a to let people know that these are not
+      # typical AIX shared libraries.
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    else
+      # We preserve .a as extension for shared libraries through AIX4.2
+      # and later when we are not doing run time linking.
+      library_names_spec='${libname}${release}.a $libname.a'
+      soname_spec='${libname}${release}${shared_ext}$major'
+    fi
+    shlibpath_var=LIBPATH
+  fi
+  ;;
+
+amigaos*)
+  case $host_cpu in
+  powerpc)
+    # Since July 2007 AmigaOS4 officially supports .so libraries.
+    # When compiling the executable, add -use-dynld -Lsobjs: to the compileline.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    ;;
+  m68k)
+    library_names_spec='$libname.ixlibrary $libname.a'
+    # Create ${libname}_ixlibrary.a entries in /sys/libs.
+    finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done'
+    ;;
+  esac
+  ;;
+
+beos*)
+  library_names_spec='${libname}${shared_ext}'
+  dynamic_linker="$host_os ld.so"
+  shlibpath_var=LIBRARY_PATH
+  ;;
+
+bsdi[[45]]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+  sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+  # the default ld.so.conf also contains /usr/contrib/lib and
+  # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+  # libtool to hard-code these into programs
+  ;;
+
+cygwin* | mingw* | pw32* | cegcc*)
+  version_type=windows
+  shrext_cmds=".dll"
+  need_version=no
+  need_lib_prefix=no
+
+  case $GCC,$cc_basename in
+  yes,*)
+    # gcc
+    library_names_spec='$libname.dll.a'
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname~
+      chmod a+x \$dldir/$dlname~
+      if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+        eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+      fi'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+
+    case $host_os in
+    cygwin*)
+      # Cygwin DLLs use 'cyg' prefix rather than 'lib'
+      soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+m4_if([$1], [],[
+      sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"])
+      ;;
+    mingw* | cegcc*)
+      # MinGW DLLs use traditional 'lib' prefix
+      soname_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    pw32*)
+      # pw32 DLLs use 'pw' prefix rather than 'lib'
+      library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    esac
+    dynamic_linker='Win32 ld.exe'
+    ;;
+
+  *,cl*)
+    # Native MSVC
+    libname_spec='$name'
+    soname_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+    library_names_spec='${libname}.dll.lib'
+
+    case $build_os in
+    mingw*)
+      sys_lib_search_path_spec=
+      lt_save_ifs=$IFS
+      IFS=';'
+      for lt_path in $LIB
+      do
+        IFS=$lt_save_ifs
+        # Let DOS variable expansion print the short 8.3 style file name.
+        lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"`
+        sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path"
+      done
+      IFS=$lt_save_ifs
+      # Convert to MSYS style.
+      sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'`
+      ;;
+    cygwin*)
+      # Convert to unix form, then to dos form, then back to unix form
+      # but this time dos style (no spaces!) so that the unix form looks
+      # like /cygdrive/c/PROGRA~1:/cygdr...
+      sys_lib_search_path_spec=`cygpath --path --unix "$LIB"`
+      sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null`
+      sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      ;;
+    *)
+      sys_lib_search_path_spec="$LIB"
+      if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then
+        # It is most probably a Windows format PATH.
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'`
+      else
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      fi
+      # FIXME: find the short name or the path components, as spaces are
+      # common. (e.g. "Program Files" -> "PROGRA~1")
+      ;;
+    esac
+
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+    dynamic_linker='Win32 link.exe'
+    ;;
+
+  *)
+    # Assume MSVC wrapper
+    library_names_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext} $libname.lib'
+    dynamic_linker='Win32 ld.exe'
+    ;;
+  esac
+  # FIXME: first we should search . and the directory the executable is in
+  shlibpath_var=PATH
+  ;;
+
+darwin* | rhapsody*)
+  dynamic_linker="$host_os dyld"
+  version_type=darwin
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext'
+  soname_spec='${libname}${release}${major}$shared_ext'
+  shlibpath_overrides_runpath=yes
+  shlibpath_var=DYLD_LIBRARY_PATH
+  shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`'
+m4_if([$1], [],[
+  sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"])
+  sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib'
+  ;;
+
+dgux*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+freebsd* | dragonfly*)
+  # DragonFly does not have aout.  When/if they implement a new
+  # versioning mechanism, adjust this.
+  if test -x /usr/bin/objformat; then
+    objformat=`/usr/bin/objformat`
+  else
+    case $host_os in
+    freebsd[[23]].*) objformat=aout ;;
+    *) objformat=elf ;;
+    esac
+  fi
+  version_type=freebsd-$objformat
+  case $version_type in
+    freebsd-elf*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+      need_version=no
+      need_lib_prefix=no
+      ;;
+    freebsd-*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix'
+      need_version=yes
+      ;;
+  esac
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_os in
+  freebsd2.*)
+    shlibpath_overrides_runpath=yes
+    ;;
+  freebsd3.[[01]]* | freebsdelf3.[[01]]*)
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \
+  freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1)
+    shlibpath_overrides_runpath=no
+    hardcode_into_libs=yes
+    ;;
+  *) # from 4.6 on, and DragonFly
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  esac
+  ;;
+
+gnu*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+haiku*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  dynamic_linker="$host_os runtime_loader"
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib'
+  hardcode_into_libs=yes
+  ;;
+
+hpux9* | hpux10* | hpux11*)
+  # Give a soname corresponding to the major version so that dld.sl refuses to
+  # link against other versions.
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  case $host_cpu in
+  ia64*)
+    shrext_cmds='.so'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.so"
+    shlibpath_var=LD_LIBRARY_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    if test "X$HPUX_IA64_MODE" = X32; then
+      sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib"
+    else
+      sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64"
+    fi
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  hppa*64*)
+    shrext_cmds='.sl'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64"
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  *)
+    shrext_cmds='.sl'
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=SHLIB_PATH
+    shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    ;;
+  esac
+  # HP-UX runs *really* slowly unless shared libraries are mode 555, ...
+  postinstall_cmds='chmod 555 $lib'
+  # or fails outright, so override atomically:
+  install_override_mode=555
+  ;;
+
+interix[[3-9]]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $host_os in
+    nonstopux*) version_type=nonstopux ;;
+    *)
+	if test "$lt_cv_prog_gnu_ld" = yes; then
+		version_type=linux # correct to gnu/linux during the next big refactor
+	else
+		version_type=irix
+	fi ;;
+  esac
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}'
+  case $host_os in
+  irix5* | nonstopux*)
+    libsuff= shlibsuff=
+    ;;
+  *)
+    case $LD in # libtool.m4 will add one of these switches to LD
+    *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ")
+      libsuff= shlibsuff= libmagic=32-bit;;
+    *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ")
+      libsuff=32 shlibsuff=N32 libmagic=N32;;
+    *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ")
+      libsuff=64 shlibsuff=64 libmagic=64-bit;;
+    *) libsuff= shlibsuff= libmagic=never-match;;
+    esac
+    ;;
+  esac
+  shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+  shlibpath_overrides_runpath=no
+  sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}"
+  sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}"
+  hardcode_into_libs=yes
+  ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux*oldld* | linux*aout* | linux*coff*)
+  dynamic_linker=no
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+
+  # Some binutils ld are patched to set DT_RUNPATH
+  AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath],
+    [lt_cv_shlibpath_overrides_runpath=no
+    save_LDFLAGS=$LDFLAGS
+    save_libdir=$libdir
+    eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \
+	 LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\""
+    AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])],
+      [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null],
+	 [lt_cv_shlibpath_overrides_runpath=yes])])
+    LDFLAGS=$save_LDFLAGS
+    libdir=$save_libdir
+    ])
+  shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath
+
+  # This implies no fast_install, which is unacceptable.
+  # Some rework will be needed to allow for fast_install
+  # before this can be enabled.
+  hardcode_into_libs=yes
+
+  # Append ld.so.conf contents to the search path
+  if test -f /etc/ld.so.conf; then
+    lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[	 ]*hwcap[	 ]/d;s/[:,	]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '`
+    sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra"
+  fi
+
+  # We used to test for /lib/ld.so.1 and disable shared libraries on
+  # powerpc, because MkLinux only supported shared libraries with the
+  # GNU dynamic linker.  Since this was broken with cross compilers,
+  # most powerpc-linux boxes support dynamic linking these days and
+  # people can always --disable-shared, the test was removed, and we
+  # assume the GNU/Linux dynamic linker is in use.
+  dynamic_linker='GNU/Linux ld.so'
+  ;;
+
+netbsd*)
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+    finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+    dynamic_linker='NetBSD (a.out) ld.so'
+  else
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    dynamic_linker='NetBSD ld.elf_so'
+  fi
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  ;;
+
+newsos6)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  ;;
+
+*nto* | *qnx*)
+  version_type=qnx
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  dynamic_linker='ldqnx.so'
+  ;;
+
+openbsd*)
+  version_type=sunos
+  sys_lib_dlsearch_path_spec="/usr/lib"
+  need_lib_prefix=no
+  # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs.
+  case $host_os in
+    openbsd3.3 | openbsd3.3.*)	need_version=yes ;;
+    *)				need_version=no  ;;
+  esac
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    case $host_os in
+      openbsd2.[[89]] | openbsd2.[[89]].*)
+	shlibpath_overrides_runpath=no
+	;;
+      *)
+	shlibpath_overrides_runpath=yes
+	;;
+      esac
+  else
+    shlibpath_overrides_runpath=yes
+  fi
+  ;;
+
+os2*)
+  libname_spec='$name'
+  shrext_cmds=".dll"
+  need_lib_prefix=no
+  library_names_spec='$libname${shared_ext} $libname.a'
+  dynamic_linker='OS/2 ld.exe'
+  shlibpath_var=LIBPATH
+  ;;
+
+osf3* | osf4* | osf5*)
+  version_type=osf
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+  sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec"
+  ;;
+
+rdos*)
+  dynamic_linker=no
+  ;;
+
+solaris*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  # ldd complains unless libraries are executable
+  postinstall_cmds='chmod +x $lib'
+  ;;
+
+sunos4*)
+  version_type=sunos
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  if test "$with_gnu_ld" = yes; then
+    need_lib_prefix=no
+  fi
+  need_version=yes
+  ;;
+
+sysv4 | sysv4.3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_vendor in
+    sni)
+      shlibpath_overrides_runpath=no
+      need_lib_prefix=no
+      runpath_var=LD_RUN_PATH
+      ;;
+    siemens)
+      need_lib_prefix=no
+      ;;
+    motorola)
+      need_lib_prefix=no
+      need_version=no
+      shlibpath_overrides_runpath=no
+      sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+      ;;
+  esac
+  ;;
+
+sysv4*MP*)
+  if test -d /usr/nec ;then
+    version_type=linux # correct to gnu/linux during the next big refactor
+    library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}'
+    soname_spec='$libname${shared_ext}.$major'
+    shlibpath_var=LD_LIBRARY_PATH
+  fi
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  version_type=freebsd-elf
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  if test "$with_gnu_ld" = yes; then
+    sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib'
+  else
+    sys_lib_search_path_spec='/usr/ccs/lib /usr/lib'
+    case $host_os in
+      sco3.2v5*)
+        sys_lib_search_path_spec="$sys_lib_search_path_spec /lib"
+	;;
+    esac
+  fi
+  sys_lib_dlsearch_path_spec='/usr/lib'
+  ;;
+
+tpf*)
+  # TPF is a cross-target only.  Preferred cross-host = GNU/Linux.
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+uts4*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+*)
+  dynamic_linker=no
+  ;;
+esac
+AC_MSG_RESULT([$dynamic_linker])
+test "$dynamic_linker" = no && can_build_shared=no
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test "$GCC" = yes; then
+  variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then
+  sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec"
+fi
+if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then
+  sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec"
+fi
+
+_LT_DECL([], [variables_saved_for_relink], [1],
+    [Variables whose values should be saved in libtool wrapper scripts and
+    restored at link time])
+_LT_DECL([], [need_lib_prefix], [0],
+    [Do we need the "lib" prefix for modules?])
+_LT_DECL([], [need_version], [0], [Do we need a version for libraries?])
+_LT_DECL([], [version_type], [0], [Library versioning type])
+_LT_DECL([], [runpath_var], [0],  [Shared library runtime path variable])
+_LT_DECL([], [shlibpath_var], [0],[Shared library path variable])
+_LT_DECL([], [shlibpath_overrides_runpath], [0],
+    [Is shlibpath searched before the hard-coded library search path?])
+_LT_DECL([], [libname_spec], [1], [Format of library name prefix])
+_LT_DECL([], [library_names_spec], [1],
+    [[List of archive names.  First name is the real one, the rest are links.
+    The last name is the one that the linker finds with -lNAME]])
+_LT_DECL([], [soname_spec], [1],
+    [[The coded name of the library, if different from the real name]])
+_LT_DECL([], [install_override_mode], [1],
+    [Permission mode override for installation of shared libraries])
+_LT_DECL([], [postinstall_cmds], [2],
+    [Command to use after installation of a shared archive])
+_LT_DECL([], [postuninstall_cmds], [2],
+    [Command to use after uninstallation of a shared archive])
+_LT_DECL([], [finish_cmds], [2],
+    [Commands used to finish a libtool library installation in a directory])
+_LT_DECL([], [finish_eval], [1],
+    [[As "finish_cmds", except a single script fragment to be evaled but
+    not shown]])
+_LT_DECL([], [hardcode_into_libs], [0],
+    [Whether we should hardcode library paths into libraries])
+_LT_DECL([], [sys_lib_search_path_spec], [2],
+    [Compile-time system search path for libraries])
+_LT_DECL([], [sys_lib_dlsearch_path_spec], [2],
+    [Run-time system search path for libraries])
+])# _LT_SYS_DYNAMIC_LINKER
+
+
+# _LT_PATH_TOOL_PREFIX(TOOL)
+# --------------------------
+# find a file program which can recognize shared library
+AC_DEFUN([_LT_PATH_TOOL_PREFIX],
+[m4_require([_LT_DECL_EGREP])dnl
+AC_MSG_CHECKING([for $1])
+AC_CACHE_VAL(lt_cv_path_MAGIC_CMD,
+[case $MAGIC_CMD in
+[[\\/*] |  ?:[\\/]*])
+  lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+  ;;
+*)
+  lt_save_MAGIC_CMD="$MAGIC_CMD"
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+dnl $ac_dummy forces splitting on constant user-supplied paths.
+dnl POSIX.2 word splitting is done only on the output of word expansions,
+dnl not every word.  This closes a longstanding sh security hole.
+  ac_dummy="m4_if([$2], , $PATH, [$2])"
+  for ac_dir in $ac_dummy; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$1; then
+      lt_cv_path_MAGIC_CMD="$ac_dir/$1"
+      if test -n "$file_magic_test_file"; then
+	case $deplibs_check_method in
+	"file_magic "*)
+	  file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+	  MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+	  if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+	    $EGREP "$file_magic_regex" > /dev/null; then
+	    :
+	  else
+	    cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such.  This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem.  Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** [email-masked]
+
+_LT_EOF
+	  fi ;;
+	esac
+      fi
+      break
+    fi
+  done
+  IFS="$lt_save_ifs"
+  MAGIC_CMD="$lt_save_MAGIC_CMD"
+  ;;
+esac])
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+  AC_MSG_RESULT($MAGIC_CMD)
+else
+  AC_MSG_RESULT(no)
+fi
+_LT_DECL([], [MAGIC_CMD], [0],
+	 [Used to examine libraries when file_magic_cmd begins with "file"])dnl
+])# _LT_PATH_TOOL_PREFIX
+
+# Old name:
+AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], [])
+
+
+# _LT_PATH_MAGIC
+# --------------
+# find a file program which can recognize a shared library
+m4_defun([_LT_PATH_MAGIC],
+[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH)
+if test -z "$lt_cv_path_MAGIC_CMD"; then
+  if test -n "$ac_tool_prefix"; then
+    _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH)
+  else
+    MAGIC_CMD=:
+  fi
+fi
+])# _LT_PATH_MAGIC
+
+
+# LT_PATH_LD
+# ----------
+# find the pathname to the GNU or non-GNU linker
+AC_DEFUN([LT_PATH_LD],
+[AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_PROG_ECHO_BACKSLASH])dnl
+
+AC_ARG_WITH([gnu-ld],
+    [AS_HELP_STRING([--with-gnu-ld],
+	[assume the C compiler uses GNU ld @<:@default=no@:>@])],
+    [test "$withval" = no || with_gnu_ld=yes],
+    [with_gnu_ld=no])dnl
+
+ac_prog=ld
+if test "$GCC" = yes; then
+  # Check if gcc -print-prog-name=ld gives a path.
+  AC_MSG_CHECKING([for ld used by $CC])
+  case $host in
+  *-*-mingw*)
+    # gcc leaves a trailing carriage return which upsets mingw
+    ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+  *)
+    ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+  esac
+  case $ac_prog in
+    # Accept absolute paths.
+    [[\\/]]* | ?:[[\\/]]*)
+      re_direlt='/[[^/]][[^/]]*/\.\./'
+      # Canonicalize the pathname of ld
+      ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'`
+      while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do
+	ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"`
+      done
+      test -z "$LD" && LD="$ac_prog"
+      ;;
+  "")
+    # If it fails, then pretend we aren't using GCC.
+    ac_prog=ld
+    ;;
+  *)
+    # If it is relative, then search for the first ld in PATH.
+    with_gnu_ld=unknown
+    ;;
+  esac
+elif test "$with_gnu_ld" = yes; then
+  AC_MSG_CHECKING([for GNU ld])
+else
+  AC_MSG_CHECKING([for non-GNU ld])
+fi
+AC_CACHE_VAL(lt_cv_path_LD,
+[if test -z "$LD"; then
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+  for ac_dir in $PATH; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+      lt_cv_path_LD="$ac_dir/$ac_prog"
+      # Check to see if the program is GNU ld.  I'd rather use --version,
+      # but apparently some variants of GNU ld only accept -v.
+      # Break only if it was the GNU/non-GNU ld that we prefer.
+      case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in
+      *GNU* | *'with BFD'*)
+	test "$with_gnu_ld" != no && break
+	;;
+      *)
+	test "$with_gnu_ld" != yes && break
+	;;
+      esac
+    fi
+  done
+  IFS="$lt_save_ifs"
+else
+  lt_cv_path_LD="$LD" # Let the user override the test with a path.
+fi])
+LD="$lt_cv_path_LD"
+if test -n "$LD"; then
+  AC_MSG_RESULT($LD)
+else
+  AC_MSG_RESULT(no)
+fi
+test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH])
+_LT_PATH_LD_GNU
+AC_SUBST([LD])
+
+_LT_TAGDECL([], [LD], [1], [The linker used to build libraries])
+])# LT_PATH_LD
+
+# Old names:
+AU_ALIAS([AM_PROG_LD], [LT_PATH_LD])
+AU_ALIAS([AC_PROG_LD], [LT_PATH_LD])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_PROG_LD], [])
+dnl AC_DEFUN([AC_PROG_LD], [])
+
+
+# _LT_PATH_LD_GNU
+#- --------------
+m4_defun([_LT_PATH_LD_GNU],
+[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], lt_cv_prog_gnu_ld,
+[# I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+  lt_cv_prog_gnu_ld=yes
+  ;;
+*)
+  lt_cv_prog_gnu_ld=no
+  ;;
+esac])
+with_gnu_ld=$lt_cv_prog_gnu_ld
+])# _LT_PATH_LD_GNU
+
+
+# _LT_CMD_RELOAD
+# --------------
+# find reload flag for linker
+#   -- PORTME Some linkers may need a different reload flag.
+m4_defun([_LT_CMD_RELOAD],
+[AC_CACHE_CHECK([for $LD option to reload object files],
+  lt_cv_ld_reload_flag,
+  [lt_cv_ld_reload_flag='-r'])
+reload_flag=$lt_cv_ld_reload_flag
+case $reload_flag in
+"" | " "*) ;;
+*) reload_flag=" $reload_flag" ;;
+esac
+reload_cmds='$LD$reload_flag -o $output$reload_objs'
+case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    if test "$GCC" != yes; then
+      reload_cmds=false
+    fi
+    ;;
+  darwin*)
+    if test "$GCC" = yes; then
+      reload_cmds='$LTCC $LTCFLAGS -nostdlib ${wl}-r -o $output$reload_objs'
+    else
+      reload_cmds='$LD$reload_flag -o $output$reload_objs'
+    fi
+    ;;
+esac
+_LT_TAGDECL([], [reload_flag], [1], [How to create reloadable object files])dnl
+_LT_TAGDECL([], [reload_cmds], [2])dnl
+])# _LT_CMD_RELOAD
+
+
+# _LT_CHECK_MAGIC_METHOD
+# ----------------------
+# how to check for library dependencies
+#  -- PORTME fill in with the dynamic library characteristics
+m4_defun([_LT_CHECK_MAGIC_METHOD],
+[m4_require([_LT_DECL_EGREP])
+m4_require([_LT_DECL_OBJDUMP])
+AC_CACHE_CHECK([how to recognize dependent libraries],
+lt_cv_deplibs_check_method,
+[lt_cv_file_magic_cmd='$MAGIC_CMD'
+lt_cv_file_magic_test_file=
+lt_cv_deplibs_check_method='unknown'
+# Need to set the preceding variable on all platforms that support
+# interlibrary dependencies.
+# 'none' -- dependencies not supported.
+# `unknown' -- same as none, but documents that we really don't know.
+# 'pass_all' -- all dependencies passed with no checks.
+# 'test_compile' -- check by making test program.
+# 'file_magic [[regex]]' -- check by looking for files in library path
+# which responds to the $file_magic_cmd with a given extended regex.
+# If you have `file' or equivalent on your system and you're not sure
+# whether `pass_all' will *always* work, you probably want this one.
+
+case $host_os in
+aix[[4-9]]*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+beos*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+bsdi[[45]]*)
+  lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)'
+  lt_cv_file_magic_cmd='/usr/bin/file -L'
+  lt_cv_file_magic_test_file=/shlib/libc.so
+  ;;
+
+cygwin*)
+  # func_win32_libid is a shell function defined in ltmain.sh
+  lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+  lt_cv_file_magic_cmd='func_win32_libid'
+  ;;
+
+mingw* | pw32*)
+  # Base MSYS/MinGW do not provide the 'file' command needed by
+  # func_win32_libid shell function, so use a weaker test based on 'objdump',
+  # unless we find 'file', for example because we are cross-compiling.
+  # func_win32_libid assumes BSD nm, so disallow it if using MS dumpbin.
+  if ( test "$lt_cv_nm_interface" = "BSD nm" && file / ) >/dev/null 2>&1; then
+    lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+    lt_cv_file_magic_cmd='func_win32_libid'
+  else
+    # Keep this pattern in sync with the one in func_win32_libid.
+    lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)'
+    lt_cv_file_magic_cmd='$OBJDUMP -f'
+  fi
+  ;;
+
+cegcc*)
+  # use the weaker test based on 'objdump'. See mingw*.
+  lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?'
+  lt_cv_file_magic_cmd='$OBJDUMP -f'
+  ;;
+
+darwin* | rhapsody*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+freebsd* | dragonfly*)
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+    case $host_cpu in
+    i*86 )
+      # Not sure whether the presence of OpenBSD here was a mistake.
+      # Let's accept both of them until this is cleared up.
+      lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library'
+      lt_cv_file_magic_cmd=/usr/bin/file
+      lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+      ;;
+    esac
+  else
+    lt_cv_deplibs_check_method=pass_all
+  fi
+  ;;
+
+gnu*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+haiku*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+hpux10.20* | hpux11*)
+  lt_cv_file_magic_cmd=/usr/bin/file
+  case $host_cpu in
+  ia64*)
+    lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64'
+    lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so
+    ;;
+  hppa*64*)
+    [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]']
+    lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl
+    ;;
+  *)
+    lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library'
+    lt_cv_file_magic_test_file=/usr/lib/libc.sl
+    ;;
+  esac
+  ;;
+
+interix[[3-9]]*)
+  # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here
+  lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$'
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $LD in
+  *-32|*"-32 ") libmagic=32-bit;;
+  *-n32|*"-n32 ") libmagic=N32;;
+  *-64|*"-64 ") libmagic=64-bit;;
+  *) libmagic=never-match;;
+  esac
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+netbsd*)
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$'
+  else
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$'
+  fi
+  ;;
+
+newos6*)
+  lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)'
+  lt_cv_file_magic_cmd=/usr/bin/file
+  lt_cv_file_magic_test_file=/usr/lib/libnls.so
+  ;;
+
+*nto* | *qnx*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+openbsd*)
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$'
+  else
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$'
+  fi
+  ;;
+
+osf3* | osf4* | osf5*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+rdos*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+solaris*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+sysv4 | sysv4.3*)
+  case $host_vendor in
+  motorola)
+    lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]'
+    lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*`
+    ;;
+  ncr)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  sequent)
+    lt_cv_file_magic_cmd='/bin/file'
+    lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )'
+    ;;
+  sni)
+    lt_cv_file_magic_cmd='/bin/file'
+    lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib"
+    lt_cv_file_magic_test_file=/lib/libc.so
+    ;;
+  siemens)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  pc)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  esac
+  ;;
+
+tpf*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+esac
+])
+
+file_magic_glob=
+want_nocaseglob=no
+if test "$build" = "$host"; then
+  case $host_os in
+  mingw* | pw32*)
+    if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then
+      want_nocaseglob=yes
+    else
+      file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"`
+    fi
+    ;;
+  esac
+fi
+
+file_magic_cmd=$lt_cv_file_magic_cmd
+deplibs_check_method=$lt_cv_deplibs_check_method
+test -z "$deplibs_check_method" && deplibs_check_method=unknown
+
+_LT_DECL([], [deplibs_check_method], [1],
+    [Method to check whether dependent libraries are shared objects])
+_LT_DECL([], [file_magic_cmd], [1],
+    [Command to use when deplibs_check_method = "file_magic"])
+_LT_DECL([], [file_magic_glob], [1],
+    [How to find potential files when deplibs_check_method = "file_magic"])
+_LT_DECL([], [want_nocaseglob], [1],
+    [Find potential files using nocaseglob when deplibs_check_method = "file_magic"])
+])# _LT_CHECK_MAGIC_METHOD
+
+
+# LT_PATH_NM
+# ----------
+# find the pathname to a BSD- or MS-compatible name lister
+AC_DEFUN([LT_PATH_NM],
+[AC_REQUIRE([AC_PROG_CC])dnl
+AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM,
+[if test -n "$NM"; then
+  # Let the user override the test.
+  lt_cv_path_NM="$NM"
+else
+  lt_nm_to_check="${ac_tool_prefix}nm"
+  if test -n "$ac_tool_prefix" && test "$build" = "$host"; then
+    lt_nm_to_check="$lt_nm_to_check nm"
+  fi
+  for lt_tmp_nm in $lt_nm_to_check; do
+    lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+    for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do
+      IFS="$lt_save_ifs"
+      test -z "$ac_dir" && ac_dir=.
+      tmp_nm="$ac_dir/$lt_tmp_nm"
+      if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then
+	# Check to see if the nm accepts a BSD-compat flag.
+	# Adding the `sed 1q' prevents false positives on HP-UX, which says:
+	#   nm: unknown option "B" ignored
+	# Tru64's nm complains that /dev/null is an invalid object file
+	case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in
+	*/dev/null* | *'Invalid file or object type'*)
+	  lt_cv_path_NM="$tmp_nm -B"
+	  break
+	  ;;
+	*)
+	  case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in
+	  */dev/null*)
+	    lt_cv_path_NM="$tmp_nm -p"
+	    break
+	    ;;
+	  *)
+	    lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but
+	    continue # so that we can try to find one that supports BSD flags
+	    ;;
+	  esac
+	  ;;
+	esac
+      fi
+    done
+    IFS="$lt_save_ifs"
+  done
+  : ${lt_cv_path_NM=no}
+fi])
+if test "$lt_cv_path_NM" != "no"; then
+  NM="$lt_cv_path_NM"
+else
+  # Didn't find any BSD compatible name lister, look for dumpbin.
+  if test -n "$DUMPBIN"; then :
+    # Let the user override the test.
+  else
+    AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :)
+    case `$DUMPBIN -symbols /dev/null 2>&1 | sed '1q'` in
+    *COFF*)
+      DUMPBIN="$DUMPBIN -symbols"
+      ;;
+    *)
+      DUMPBIN=:
+      ;;
+    esac
+  fi
+  AC_SUBST([DUMPBIN])
+  if test "$DUMPBIN" != ":"; then
+    NM="$DUMPBIN"
+  fi
+fi
+test -z "$NM" && NM=nm
+AC_SUBST([NM])
+_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl
+
+AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface],
+  [lt_cv_nm_interface="BSD nm"
+  echo "int some_variable = 0;" > conftest.$ac_ext
+  (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD)
+  (eval "$ac_compile" 2>conftest.err)
+  cat conftest.err >&AS_MESSAGE_LOG_FD
+  (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD)
+  (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
+  cat conftest.err >&AS_MESSAGE_LOG_FD
+  (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD)
+  cat conftest.out >&AS_MESSAGE_LOG_FD
+  if $GREP 'External.*some_variable' conftest.out > /dev/null; then
+    lt_cv_nm_interface="MS dumpbin"
+  fi
+  rm -f conftest*])
+])# LT_PATH_NM
+
+# Old names:
+AU_ALIAS([AM_PROG_NM], [LT_PATH_NM])
+AU_ALIAS([AC_PROG_NM], [LT_PATH_NM])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_PROG_NM], [])
+dnl AC_DEFUN([AC_PROG_NM], [])
+
+# _LT_CHECK_SHAREDLIB_FROM_LINKLIB
+# --------------------------------
+# how to determine the name of the shared library
+# associated with a specific link library.
+#  -- PORTME fill in with the dynamic library characteristics
+m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB],
+[m4_require([_LT_DECL_EGREP])
+m4_require([_LT_DECL_OBJDUMP])
+m4_require([_LT_DECL_DLLTOOL])
+AC_CACHE_CHECK([how to associate runtime and link libraries],
+lt_cv_sharedlib_from_linklib_cmd,
+[lt_cv_sharedlib_from_linklib_cmd='unknown'
+
+case $host_os in
+cygwin* | mingw* | pw32* | cegcc*)
+  # two different shell functions defined in ltmain.sh
+  # decide which to use based on capabilities of $DLLTOOL
+  case `$DLLTOOL --help 2>&1` in
+  *--identify-strict*)
+    lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib
+    ;;
+  *)
+    lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback
+    ;;
+  esac
+  ;;
+*)
+  # fallback: assume linklib IS sharedlib
+  lt_cv_sharedlib_from_linklib_cmd="$ECHO"
+  ;;
+esac
+])
+sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd
+test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO
+
+_LT_DECL([], [sharedlib_from_linklib_cmd], [1],
+    [Command to associate shared and link libraries])
+])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB
+
+
+# _LT_PATH_MANIFEST_TOOL
+# ----------------------
+# locate the manifest tool
+m4_defun([_LT_PATH_MANIFEST_TOOL],
+[AC_CHECK_TOOL(MANIFEST_TOOL, mt, :)
+test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt
+AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool],
+  [lt_cv_path_mainfest_tool=no
+  echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD
+  $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out
+  cat conftest.err >&AS_MESSAGE_LOG_FD
+  if $GREP 'Manifest Tool' conftest.out > /dev/null; then
+    lt_cv_path_mainfest_tool=yes
+  fi
+  rm -f conftest*])
+if test "x$lt_cv_path_mainfest_tool" != xyes; then
+  MANIFEST_TOOL=:
+fi
+_LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl
+])# _LT_PATH_MANIFEST_TOOL
+
+
+# LT_LIB_M
+# --------
+# check for math library
+AC_DEFUN([LT_LIB_M],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+LIBM=
+case $host in
+*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*)
+  # These system don't have libm, or don't need it
+  ;;
+*-ncr-sysv4.3*)
+  AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM="-lmw")
+  AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm")
+  ;;
+*)
+  AC_CHECK_LIB(m, cos, LIBM="-lm")
+  ;;
+esac
+AC_SUBST([LIBM])
+])# LT_LIB_M
+
+# Old name:
+AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_CHECK_LIBM], [])
+
+
+# _LT_COMPILER_NO_RTTI([TAGNAME])
+# -------------------------------
+m4_defun([_LT_COMPILER_NO_RTTI],
+[m4_require([_LT_TAG_COMPILER])dnl
+
+_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=
+
+if test "$GCC" = yes; then
+  case $cc_basename in
+  nvcc*)
+    _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;;
+  *)
+    _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;;
+  esac
+
+  _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions],
+    lt_cv_prog_compiler_rtti_exceptions,
+    [-fno-rtti -fno-exceptions], [],
+    [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"])
+fi
+_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1],
+	[Compiler flag to turn off builtin functions])
+])# _LT_COMPILER_NO_RTTI
+
+
+# _LT_CMD_GLOBAL_SYMBOLS
+# ----------------------
+m4_defun([_LT_CMD_GLOBAL_SYMBOLS],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+AC_REQUIRE([LT_PATH_LD])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+
+# Check for command to grab the raw symbol name followed by C symbol from nm.
+AC_MSG_CHECKING([command to parse $NM output from $compiler object])
+AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe],
+[
+# These are sane defaults that work on at least a few old systems.
+# [They come from Ultrix.  What could be older than Ultrix?!! ;)]
+
+# Character class describing NM global symbol codes.
+symcode='[[BCDEGRST]]'
+
+# Regexp to match symbols that can be accessed directly from C.
+sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)'
+
+# Define system-specific variables.
+case $host_os in
+aix*)
+  symcode='[[BCDT]]'
+  ;;
+cygwin* | mingw* | pw32* | cegcc*)
+  symcode='[[ABCDGISTW]]'
+  ;;
+hpux*)
+  if test "$host_cpu" = ia64; then
+    symcode='[[ABCDEGRST]]'
+  fi
+  ;;
+irix* | nonstopux*)
+  symcode='[[BCDEGRST]]'
+  ;;
+osf*)
+  symcode='[[BCDEGQRST]]'
+  ;;
+solaris*)
+  symcode='[[BDRT]]'
+  ;;
+sco3.2v5*)
+  symcode='[[DT]]'
+  ;;
+sysv4.2uw2*)
+  symcode='[[DT]]'
+  ;;
+sysv5* | sco5v6* | unixware* | OpenUNIX*)
+  symcode='[[ABDT]]'
+  ;;
+sysv4)
+  symcode='[[DFNSTU]]'
+  ;;
+esac
+
+# If we're using GNU nm, then use its standard symbol codes.
+case `$NM -V 2>&1` in
+*GNU* | *'with BFD'*)
+  symcode='[[ABCDGIRSTW]]' ;;
+esac
+
+# Transform an extracted symbol line into a proper C declaration.
+# Some systems (esp. on ia64) link data and code symbols differently,
+# so use this general approach.
+lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'"
+
+# Transform an extracted symbol line into symbol name and symbol address
+lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\)[[ ]]*$/  {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/  {\"\2\", (void *) \&\2},/p'"
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n -e 's/^: \([[^ ]]*\)[[ ]]*$/  {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \(lib[[^ ]]*\)$/  {\"\2\", (void *) \&\2},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/  {\"lib\2\", (void *) \&\2},/p'"
+
+# Handle CRLF in mingw tool chain
+opt_cr=
+case $build_os in
+mingw*)
+  opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp
+  ;;
+esac
+
+# Try without a prefix underscore, then with it.
+for ac_symprfx in "" "_"; do
+
+  # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol.
+  symxfrm="\\1 $ac_symprfx\\2 \\2"
+
+  # Write the raw and C identifiers.
+  if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+    # Fake it for dumpbin and say T for any non-static function
+    # and D for any global variable.
+    # Also find C++ and __fastcall symbols from MSVC++,
+    # which start with @ or ?.
+    lt_cv_sys_global_symbol_pipe="$AWK ['"\
+"     {last_section=section; section=\$ 3};"\
+"     /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\
+"     /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\
+"     \$ 0!~/External *\|/{next};"\
+"     / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\
+"     {if(hide[section]) next};"\
+"     {f=0}; \$ 0~/\(\).*\|/{f=1}; {printf f ? \"T \" : \"D \"};"\
+"     {split(\$ 0, a, /\||\r/); split(a[2], s)};"\
+"     s[1]~/^[@?]/{print s[1], s[1]; next};"\
+"     s[1]~prfx {split(s[1],t,\"@\"); print t[1], substr(t[1],length(prfx))}"\
+"     ' prfx=^$ac_symprfx]"
+  else
+    lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[	 ]]\($symcode$symcode*\)[[	 ]][[	 ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'"
+  fi
+  lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'"
+
+  # Check to see that the pipe works correctly.
+  pipe_works=no
+
+  rm -f conftest*
+  cat > conftest.$ac_ext <<_LT_EOF
+#ifdef __cplusplus
+extern "C" {
+#endif
+char nm_test_var;
+void nm_test_func(void);
+void nm_test_func(void){}
+#ifdef __cplusplus
+}
+#endif
+int main(){nm_test_var='a';nm_test_func();return(0);}
+_LT_EOF
+
+  if AC_TRY_EVAL(ac_compile); then
+    # Now try to grab the symbols.
+    nlist=conftest.nm
+    if AC_TRY_EVAL(NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) && test -s "$nlist"; then
+      # Try sorting and uniquifying the output.
+      if sort "$nlist" | uniq > "$nlist"T; then
+	mv -f "$nlist"T "$nlist"
+      else
+	rm -f "$nlist"T
+      fi
+
+      # Make sure that we snagged all the symbols we need.
+      if $GREP ' nm_test_var$' "$nlist" >/dev/null; then
+	if $GREP ' nm_test_func$' "$nlist" >/dev/null; then
+	  cat <<_LT_EOF > conftest.$ac_ext
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests.  */
+#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE)
+/* DATA imports from DLLs on WIN32 con't be const, because runtime
+   relocations are performed -- see ld's documentation on pseudo-relocs.  */
+# define LT@&t@_DLSYM_CONST
+#elif defined(__osf__)
+/* This system does not cope well with relocations in const data.  */
+# define LT@&t@_DLSYM_CONST
+#else
+# define LT@&t@_DLSYM_CONST const
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_LT_EOF
+	  # Now generate the symbol file.
+	  eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext'
+
+	  cat <<_LT_EOF >> conftest.$ac_ext
+
+/* The mapping between symbol names and symbols.  */
+LT@&t@_DLSYM_CONST struct {
+  const char *name;
+  void       *address;
+}
+lt__PROGRAM__LTX_preloaded_symbols[[]] =
+{
+  { "@PROGRAM@", (void *) 0 },
+_LT_EOF
+	  $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/  {\"\2\", (void *) \&\2},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext
+	  cat <<\_LT_EOF >> conftest.$ac_ext
+  {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+  return lt__PROGRAM__LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+_LT_EOF
+	  # Now try linking the two files.
+	  mv conftest.$ac_objext conftstm.$ac_objext
+	  lt_globsym_save_LIBS=$LIBS
+	  lt_globsym_save_CFLAGS=$CFLAGS
+	  LIBS="conftstm.$ac_objext"
+	  CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)"
+	  if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext}; then
+	    pipe_works=yes
+	  fi
+	  LIBS=$lt_globsym_save_LIBS
+	  CFLAGS=$lt_globsym_save_CFLAGS
+	else
+	  echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD
+	fi
+      else
+	echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD
+      fi
+    else
+      echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD
+    fi
+  else
+    echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD
+    cat conftest.$ac_ext >&5
+  fi
+  rm -rf conftest* conftst*
+
+  # Do not use the global_symbol_pipe unless it works.
+  if test "$pipe_works" = yes; then
+    break
+  else
+    lt_cv_sys_global_symbol_pipe=
+  fi
+done
+])
+if test -z "$lt_cv_sys_global_symbol_pipe"; then
+  lt_cv_sys_global_symbol_to_cdecl=
+fi
+if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then
+  AC_MSG_RESULT(failed)
+else
+  AC_MSG_RESULT(ok)
+fi
+
+# Response file support.
+if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+  nm_file_list_spec='@'
+elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then
+  nm_file_list_spec='@'
+fi
+
+_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1],
+    [Take the output of nm and produce a listing of raw symbols and C names])
+_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1],
+    [Transform the output of nm in a proper C declaration])
+_LT_DECL([global_symbol_to_c_name_address],
+    [lt_cv_sys_global_symbol_to_c_name_address], [1],
+    [Transform the output of nm in a C name address pair])
+_LT_DECL([global_symbol_to_c_name_address_lib_prefix],
+    [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1],
+    [Transform the output of nm in a C name address pair when lib prefix is needed])
+_LT_DECL([], [nm_file_list_spec], [1],
+    [Specify filename containing input files for $NM])
+]) # _LT_CMD_GLOBAL_SYMBOLS
+
+
+# _LT_COMPILER_PIC([TAGNAME])
+# ---------------------------
+m4_defun([_LT_COMPILER_PIC],
+[m4_require([_LT_TAG_COMPILER])dnl
+_LT_TAGVAR(lt_prog_compiler_wl, $1)=
+_LT_TAGVAR(lt_prog_compiler_pic, $1)=
+_LT_TAGVAR(lt_prog_compiler_static, $1)=
+
+m4_if([$1], [CXX], [
+  # C++ specific cases for pic, static, wl, etc.
+  if test "$GXX" = yes; then
+    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+    _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+
+    case $host_os in
+    aix*)
+      # All AIX code is PIC.
+      if test "$host_cpu" = ia64; then
+	# AIX 5 now supports IA64 processor
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+        ;;
+      m68k)
+            # FIXME: we need at least 68020 code to build shared libraries, but
+            # adding the `-m68020' flag to GCC prevents building anything better,
+            # like `-m68040'.
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4'
+        ;;
+      esac
+      ;;
+
+    beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+      # PIC is the default for these OSes.
+      ;;
+    mingw* | cygwin* | os2* | pw32* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      # Although the cygwin gcc ignores -fPIC, still need this for old-style
+      # (--disable-auto-import) libraries
+      m4_if([$1], [GCJ], [],
+	[_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+      ;;
+    darwin* | rhapsody*)
+      # PIC is the default on this platform
+      # Common symbols not allowed in MH_DYLIB files
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common'
+      ;;
+    *djgpp*)
+      # DJGPP does not support shared libraries at all
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+      ;;
+    haiku*)
+      # PIC is the default for Haiku.
+      # The "-static" flag exists, but is broken.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)=
+      ;;
+    interix[[3-9]]*)
+      # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+      # Instead, we relocate shared libraries at runtime.
+      ;;
+    sysv4*MP*)
+      if test -d /usr/nec; then
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic
+      fi
+      ;;
+    hpux*)
+      # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+      # PA HP-UX.  On IA64 HP-UX, PIC is the default but the pic flag
+      # sets the default TLS model and affects inlining.
+      case $host_cpu in
+      hppa*64*)
+	;;
+      *)
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	;;
+      esac
+      ;;
+    *qnx* | *nto*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+      ;;
+    *)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+      ;;
+    esac
+  else
+    case $host_os in
+      aix[[4-9]]*)
+	# All AIX code is PIC.
+	if test "$host_cpu" = ia64; then
+	  # AIX 5 now supports IA64 processor
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	else
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp'
+	fi
+	;;
+      chorus*)
+	case $cc_basename in
+	cxch68*)
+	  # Green Hills C++ Compiler
+	  # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a"
+	  ;;
+	esac
+	;;
+      mingw* | cygwin* | os2* | pw32* | cegcc*)
+	# This hack is so that the source file can tell whether it is being
+	# built for inclusion in a dll (and should export symbols for example).
+	m4_if([$1], [GCJ], [],
+	  [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+	;;
+      dgux*)
+	case $cc_basename in
+	  ec++*)
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	    ;;
+	  ghcx*)
+	    # Green Hills C++ Compiler
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      freebsd* | dragonfly*)
+	# FreeBSD uses GNU C++
+	;;
+      hpux9* | hpux10* | hpux11*)
+	case $cc_basename in
+	  CC*)
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive'
+	    if test "$host_cpu" != ia64; then
+	      _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+	    fi
+	    ;;
+	  aCC*)
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive'
+	    case $host_cpu in
+	    hppa*64*|ia64*)
+	      # +Z the default
+	      ;;
+	    *)
+	      _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+	      ;;
+	    esac
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      interix*)
+	# This is c89, which is MS Visual C++ (no shared libs)
+	# Anyone wants to do a port?
+	;;
+      irix5* | irix6* | nonstopux*)
+	case $cc_basename in
+	  CC*)
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+	    # CC pic flag -KPIC is the default.
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      linux* | k*bsd*-gnu | kopensolaris*-gnu)
+	case $cc_basename in
+	  KCC*)
+	    # KAI C++ Compiler
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	    ;;
+	  ecpc* )
+	    # old Intel C++ for x86_64 which still supported -KPIC.
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+	    ;;
+	  icpc* )
+	    # Intel C++, used to be incompatible with GCC.
+	    # ICC 10 doesn't accept -KPIC any more.
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+	    ;;
+	  pgCC* | pgcpp*)
+	    # Portland Group C++ compiler
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	    ;;
+	  cxx*)
+	    # Compaq C++
+	    # Make sure the PIC flag is empty.  It appears that all Alpha
+	    # Linux and Compaq Tru64 Unix objects are PIC.
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+	    ;;
+	  xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*)
+	    # IBM XL 8.0, 9.0 on PPC and BlueGene
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink'
+	    ;;
+	  *)
+	    case `$CC -V 2>&1 | sed 5q` in
+	    *Sun\ C*)
+	      # Sun C++ 5.9
+	      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+	      ;;
+	    esac
+	    ;;
+	esac
+	;;
+      lynxos*)
+	;;
+      m88k*)
+	;;
+      mvs*)
+	case $cc_basename in
+	  cxx*)
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      netbsd*)
+	;;
+      *qnx* | *nto*)
+        # QNX uses GNU C++, but need to define -shared option too, otherwise
+        # it will coredump.
+        _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+        ;;
+      osf3* | osf4* | osf5*)
+	case $cc_basename in
+	  KCC*)
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,'
+	    ;;
+	  RCC*)
+	    # Rational C++ 2.4.1
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+	    ;;
+	  cxx*)
+	    # Digital/Compaq C++
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    # Make sure the PIC flag is empty.  It appears that all Alpha
+	    # Linux and Compaq Tru64 Unix objects are PIC.
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      psos*)
+	;;
+      solaris*)
+	case $cc_basename in
+	  CC* | sunCC*)
+	    # Sun C++ 4.2, 5.x and Centerline C++
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+	    ;;
+	  gcx*)
+	    # Green Hills C++ Compiler
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      sunos4*)
+	case $cc_basename in
+	  CC*)
+	    # Sun C++ 4.x
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	    ;;
+	  lcc*)
+	    # Lucid
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+	case $cc_basename in
+	  CC*)
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	    ;;
+	esac
+	;;
+      tandem*)
+	case $cc_basename in
+	  NCC*)
+	    # NonStop-UX NCC 3.20
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      vxworks*)
+	;;
+      *)
+	_LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+	;;
+    esac
+  fi
+],
+[
+  if test "$GCC" = yes; then
+    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+    _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+
+    case $host_os in
+      aix*)
+      # All AIX code is PIC.
+      if test "$host_cpu" = ia64; then
+	# AIX 5 now supports IA64 processor
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+        ;;
+      m68k)
+            # FIXME: we need at least 68020 code to build shared libraries, but
+            # adding the `-m68020' flag to GCC prevents building anything better,
+            # like `-m68040'.
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4'
+        ;;
+      esac
+      ;;
+
+    beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+      # PIC is the default for these OSes.
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      # Although the cygwin gcc ignores -fPIC, still need this for old-style
+      # (--disable-auto-import) libraries
+      m4_if([$1], [GCJ], [],
+	[_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+      ;;
+
+    darwin* | rhapsody*)
+      # PIC is the default on this platform
+      # Common symbols not allowed in MH_DYLIB files
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common'
+      ;;
+
+    haiku*)
+      # PIC is the default for Haiku.
+      # The "-static" flag exists, but is broken.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)=
+      ;;
+
+    hpux*)
+      # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+      # PA HP-UX.  On IA64 HP-UX, PIC is the default but the pic flag
+      # sets the default TLS model and affects inlining.
+      case $host_cpu in
+      hppa*64*)
+	# +Z the default
+	;;
+      *)
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	;;
+      esac
+      ;;
+
+    interix[[3-9]]*)
+      # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+      # Instead, we relocate shared libraries at runtime.
+      ;;
+
+    msdosdjgpp*)
+      # Just because we use GCC doesn't mean we suddenly get shared libraries
+      # on systems that don't support them.
+      _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+      enable_shared=no
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic
+      fi
+      ;;
+
+    *)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+      ;;
+    esac
+
+    case $cc_basename in
+    nvcc*) # Cuda Compiler Driver 2.2
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker '
+      if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then
+        _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)"
+      fi
+      ;;
+    esac
+  else
+    # PORTME Check for flag to pass linker flags through the system compiler.
+    case $host_os in
+    aix*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      if test "$host_cpu" = ia64; then
+	# AIX 5 now supports IA64 processor
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      else
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp'
+      fi
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      m4_if([$1], [GCJ], [],
+	[_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+      ;;
+
+    hpux9* | hpux10* | hpux11*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but
+      # not for PA HP-UX.
+      case $host_cpu in
+      hppa*64*|ia64*)
+	# +Z the default
+	;;
+      *)
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+	;;
+      esac
+      # Is there a better lt_prog_compiler_static that works with the bundled CC?
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive'
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      # PIC (with -KPIC) is the default.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+      ;;
+
+    linux* | k*bsd*-gnu | kopensolaris*-gnu)
+      case $cc_basename in
+      # old Intel for x86_64 which still supported -KPIC.
+      ecc*)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+        ;;
+      # icc used to be incompatible with GCC.
+      # ICC 10 doesn't accept -KPIC any more.
+      icc* | ifort*)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+        ;;
+      # Lahey Fortran 8.1.
+      lf95*)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='--static'
+	;;
+      nagfor*)
+	# NAG Fortran compiler
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	;;
+      pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*)
+        # Portland Group compilers (*not* the Pentium gcc compiler,
+	# which looks to be a dead project)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+        ;;
+      ccc*)
+        _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+        # All Alpha code is PIC.
+        _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+        ;;
+      xl* | bgxl* | bgf* | mpixl*)
+	# IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink'
+	;;
+      *)
+	case `$CC -V 2>&1 | sed 5q` in
+	*Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*)
+	  # Sun Fortran 8.3 passes all unrecognized flags to the linker
+	  _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	  _LT_TAGVAR(lt_prog_compiler_wl, $1)=''
+	  ;;
+	*Sun\ F* | *Sun*Fortran*)
+	  _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	  _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+	  ;;
+	*Sun\ C*)
+	  # Sun C 5.9
+	  _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	  _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	  ;;
+        *Intel*\ [[CF]]*Compiler*)
+	  _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	  _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+	  ;;
+	*Portland\ Group*)
+	  _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	  _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	  ;;
+	esac
+	;;
+      esac
+      ;;
+
+    newsos6)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+      ;;
+
+    osf3* | osf4* | osf5*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      # All OSF/1 code is PIC.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+      ;;
+
+    rdos*)
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+      ;;
+
+    solaris*)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      case $cc_basename in
+      f77* | f90* | f95* | sunf77* | sunf90* | sunf95*)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';;
+      *)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';;
+      esac
+      ;;
+
+    sunos4*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    sysv4 | sysv4.2uw2* | sysv4.3*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec ;then
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      fi
+      ;;
+
+    sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    unicos*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+      ;;
+
+    uts4*)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    *)
+      _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+      ;;
+    esac
+  fi
+])
+case $host_os in
+  # For platforms which do not support PIC, -DPIC is meaningless:
+  *djgpp*)
+    _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+    ;;
+  *)
+    _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])"
+    ;;
+esac
+
+AC_CACHE_CHECK([for $compiler option to produce PIC],
+  [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)],
+  [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)])
+_LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)
+
+#
+# Check to make sure the PIC flag actually works.
+#
+if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then
+  _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works],
+    [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)],
+    [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [],
+    [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in
+     "" | " "*) ;;
+     *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;;
+     esac],
+    [_LT_TAGVAR(lt_prog_compiler_pic, $1)=
+     _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no])
+fi
+_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1],
+	[Additional compiler flags for building library objects])
+
+_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1],
+	[How to pass a linker flag through the compiler])
+#
+# Check to make sure the static flag actually works.
+#
+wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\"
+_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works],
+  _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1),
+  $lt_tmp_static_flag,
+  [],
+  [_LT_TAGVAR(lt_prog_compiler_static, $1)=])
+_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1],
+	[Compiler flag to prevent dynamic linking])
+])# _LT_COMPILER_PIC
+
+
+# _LT_LINKER_SHLIBS([TAGNAME])
+# ----------------------------
+# See if the linker supports building shared libraries.
+m4_defun([_LT_LINKER_SHLIBS],
+[AC_REQUIRE([LT_PATH_LD])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+m4_require([_LT_PATH_MANIFEST_TOOL])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries])
+m4_if([$1], [CXX], [
+  _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+  _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*']
+  case $host_os in
+  aix[[4-9]]*)
+    # If we're using GNU nm, then we don't want the "-C" option.
+    # -C means demangle to AIX nm, but means don't demangle with GNU nm
+    # Also, AIX nm treats weak defined symbols like other global defined
+    # symbols, whereas GNU nm marks them as "W".
+    if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+    else
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+    fi
+    ;;
+  pw32*)
+    _LT_TAGVAR(export_symbols_cmds, $1)="$ltdll_cmds"
+    ;;
+  cygwin* | mingw* | cegcc*)
+    case $cc_basename in
+    cl*)
+      _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+      ;;
+    *)
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols'
+      _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname']
+      ;;
+    esac
+    ;;
+  *)
+    _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+    ;;
+  esac
+], [
+  runpath_var=
+  _LT_TAGVAR(allow_undefined_flag, $1)=
+  _LT_TAGVAR(always_export_symbols, $1)=no
+  _LT_TAGVAR(archive_cmds, $1)=
+  _LT_TAGVAR(archive_expsym_cmds, $1)=
+  _LT_TAGVAR(compiler_needs_object, $1)=no
+  _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+  _LT_TAGVAR(export_dynamic_flag_spec, $1)=
+  _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+  _LT_TAGVAR(hardcode_automatic, $1)=no
+  _LT_TAGVAR(hardcode_direct, $1)=no
+  _LT_TAGVAR(hardcode_direct_absolute, $1)=no
+  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+  _LT_TAGVAR(hardcode_libdir_separator, $1)=
+  _LT_TAGVAR(hardcode_minus_L, $1)=no
+  _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+  _LT_TAGVAR(inherit_rpath, $1)=no
+  _LT_TAGVAR(link_all_deplibs, $1)=unknown
+  _LT_TAGVAR(module_cmds, $1)=
+  _LT_TAGVAR(module_expsym_cmds, $1)=
+  _LT_TAGVAR(old_archive_from_new_cmds, $1)=
+  _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)=
+  _LT_TAGVAR(thread_safe_flag_spec, $1)=
+  _LT_TAGVAR(whole_archive_flag_spec, $1)=
+  # include_expsyms should be a list of space-separated symbols to be *always*
+  # included in the symbol list
+  _LT_TAGVAR(include_expsyms, $1)=
+  # exclude_expsyms can be an extended regexp of symbols to exclude
+  # it will be wrapped by ` (' and `)$', so one must not match beginning or
+  # end of line.  Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc',
+  # as well as any symbol that contains `d'.
+  _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*']
+  # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+  # platforms (ab)use it in PIC code, but their linkers get confused if
+  # the symbol is explicitly referenced.  Since portable code cannot
+  # rely on this symbol name, it's probably fine to never include it in
+  # preloaded symbol tables.
+  # Exclude shared library initialization/finalization symbols.
+dnl Note also adjust exclude_expsyms for C++ above.
+  extract_expsyms_cmds=
+
+  case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    # FIXME: the MSVC++ port hasn't been tested in a loooong time
+    # When not using gcc, we currently assume that we are using
+    # Microsoft Visual C++.
+    if test "$GCC" != yes; then
+      with_gnu_ld=no
+    fi
+    ;;
+  interix*)
+    # we just hope/assume this is gcc and not c89 (= MSVC++)
+    with_gnu_ld=yes
+    ;;
+  openbsd*)
+    with_gnu_ld=no
+    ;;
+  esac
+
+  _LT_TAGVAR(ld_shlibs, $1)=yes
+
+  # On some targets, GNU ld is compatible enough with the native linker
+  # that we're better off using the native interface for both.
+  lt_use_gnu_ld_interface=no
+  if test "$with_gnu_ld" = yes; then
+    case $host_os in
+      aix*)
+	# The AIX port of GNU ld has always aspired to compatibility
+	# with the native linker.  However, as the warning in the GNU ld
+	# block says, versions before 2.19.5* couldn't really create working
+	# shared libraries, regardless of the interface used.
+	case `$LD -v 2>&1` in
+	  *\ \(GNU\ Binutils\)\ 2.19.5*) ;;
+	  *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;;
+	  *\ \(GNU\ Binutils\)\ [[3-9]]*) ;;
+	  *)
+	    lt_use_gnu_ld_interface=yes
+	    ;;
+	esac
+	;;
+      *)
+	lt_use_gnu_ld_interface=yes
+	;;
+    esac
+  fi
+
+  if test "$lt_use_gnu_ld_interface" = yes; then
+    # If archive_cmds runs LD, not CC, wlarc should be empty
+    wlarc='${wl}'
+
+    # Set some defaults for GNU ld with shared library support. These
+    # are reset later if shared libraries are not supported. Putting them
+    # here allows them to be overridden if necessary.
+    runpath_var=LD_RUN_PATH
+    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+    # ancient GNU ld didn't support --whole-archive et. al.
+    if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then
+      _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+    else
+      _LT_TAGVAR(whole_archive_flag_spec, $1)=
+    fi
+    supports_anon_versioning=no
+    case `$LD -v 2>&1` in
+      *GNU\ gold*) supports_anon_versioning=yes ;;
+      *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11
+      *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ...
+      *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ...
+      *\ 2.11.*) ;; # other 2.11 versions
+      *) supports_anon_versioning=yes ;;
+    esac
+
+    # See if GNU ld supports shared libraries.
+    case $host_os in
+    aix[[3-9]]*)
+      # On AIX/PPC, the GNU linker is very broken
+      if test "$host_cpu" != ia64; then
+	_LT_TAGVAR(ld_shlibs, $1)=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.19, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support.  If you
+*** really care for shared libraries, you may want to install binutils
+*** 2.20 or above, or modify your PATH so that a non-GNU linker is found.
+*** You will then need to restart the configuration process.
+
+_LT_EOF
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            _LT_TAGVAR(archive_expsym_cmds, $1)=''
+        ;;
+      m68k)
+            _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+            _LT_TAGVAR(hardcode_minus_L, $1)=yes
+        ;;
+      esac
+      ;;
+
+    beos*)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	_LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	# Joseph Beckenbach <[email-masked]> says some releases of gcc
+	# support --undefined.  This deserves some investigation.  FIXME
+	_LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      else
+	_LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless,
+      # as there is no search path for DLLs.
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-all-symbols'
+      _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+      _LT_TAGVAR(always_export_symbols, $1)=no
+      _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols'
+      _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname']
+
+      if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+	# If the export-symbols file already is a .def file (1st line
+	# is EXPORTS), use it as is; otherwise, prepend...
+	_LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	  cp $export_symbols $output_objdir/$soname.def;
+	else
+	  echo EXPORTS > $output_objdir/$soname.def;
+	  cat $export_symbols >> $output_objdir/$soname.def;
+	fi~
+	$CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+      else
+	_LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    haiku*)
+      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      ;;
+
+    interix[[3-9]]*)
+      _LT_TAGVAR(hardcode_direct, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+      # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+      # Instead, shared libraries are loaded at an image base (0x10000000 by
+      # default) and relocated if they conflict, which is a slow very memory
+      # consuming and fragmenting process.  To avoid this, we pick a random,
+      # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+      # time.  Moving up from 0x10000000 also allows more sbrk(2) space.
+      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      ;;
+
+    gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu)
+      tmp_diet=no
+      if test "$host_os" = linux-dietlibc; then
+	case $cc_basename in
+	  diet\ *) tmp_diet=yes;;	# linux-dietlibc with static linking (!diet-dyn)
+	esac
+      fi
+      if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \
+	 && test "$tmp_diet" = no
+      then
+	tmp_addflag=' $pic_flag'
+	tmp_sharedflag='-shared'
+	case $cc_basename,$host_cpu in
+        pgcc*)				# Portland Group C compiler
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  tmp_addflag=' $pic_flag'
+	  ;;
+	pgf77* | pgf90* | pgf95* | pgfortran*)
+					# Portland Group f77 and f90 compilers
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  tmp_addflag=' $pic_flag -Mnomain' ;;
+	ecc*,ia64* | icc*,ia64*)	# Intel C compiler on ia64
+	  tmp_addflag=' -i_dynamic' ;;
+	efc*,ia64* | ifort*,ia64*)	# Intel Fortran compiler on ia64
+	  tmp_addflag=' -i_dynamic -nofor_main' ;;
+	ifc* | ifort*)			# Intel Fortran compiler
+	  tmp_addflag=' -nofor_main' ;;
+	lf95*)				# Lahey Fortran 8.1
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)=
+	  tmp_sharedflag='--shared' ;;
+	xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below)
+	  tmp_sharedflag='-qmkshrobj'
+	  tmp_addflag= ;;
+	nvcc*)	# Cuda Compiler Driver 2.2
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  _LT_TAGVAR(compiler_needs_object, $1)=yes
+	  ;;
+	esac
+	case `$CC -V 2>&1 | sed 5q` in
+	*Sun\ C*)			# Sun C 5.9
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  _LT_TAGVAR(compiler_needs_object, $1)=yes
+	  tmp_sharedflag='-G' ;;
+	*Sun\ F*)			# Sun Fortran 8.3
+	  tmp_sharedflag='-G' ;;
+	esac
+	_LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+
+        if test "x$supports_anon_versioning" = xyes; then
+          _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+	    cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+	    echo "local: *; };" >> $output_objdir/$libname.ver~
+	    $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+        fi
+
+	case $cc_basename in
+	xlf* | bgf* | bgxlf* | mpixlf*)
+	  # IBM XL Fortran 10.1 on PPC cannot create shared libs itself
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive'
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib'
+	  if test "x$supports_anon_versioning" = xyes; then
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+	      cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+	      echo "local: *; };" >> $output_objdir/$libname.ver~
+	      $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib'
+	  fi
+	  ;;
+	esac
+      else
+        _LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    netbsd*)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+	_LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+	wlarc=
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      fi
+      ;;
+
+    solaris*)
+      if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then
+	_LT_TAGVAR(ld_shlibs, $1)=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+      elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+	_LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
+      case `$LD -v 2>&1` in
+        *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*)
+	_LT_TAGVAR(ld_shlibs, $1)=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not
+*** reliably create shared libraries on SCO systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.16.91.0.3 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+	;;
+	*)
+	  # For security reasons, it is highly recommended that you always
+	  # use absolute paths for naming shared libraries, and exclude the
+	  # DT_RUNPATH tag from executables and libraries.  But doing so
+	  # requires that you compile everything twice, which is a pain.
+	  if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+	  else
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	  fi
+	;;
+      esac
+      ;;
+
+    sunos4*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      wlarc=
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    *)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+	_LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+    esac
+
+    if test "$_LT_TAGVAR(ld_shlibs, $1)" = no; then
+      runpath_var=
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)=
+      _LT_TAGVAR(whole_archive_flag_spec, $1)=
+    fi
+  else
+    # PORTME fill in a description of your system's linker (not GNU ld)
+    case $host_os in
+    aix3*)
+      _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+      _LT_TAGVAR(always_export_symbols, $1)=yes
+      _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+      # Note: this linker hardcodes the directories in LIBPATH if there
+      # are no directories specified by -L.
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then
+	# Neither direct hardcoding nor static linking is supported with a
+	# broken collect2.
+	_LT_TAGVAR(hardcode_direct, $1)=unsupported
+      fi
+      ;;
+
+    aix[[4-9]]*)
+      if test "$host_cpu" = ia64; then
+	# On IA64, the linker does run time linking by default, so we don't
+	# have to do anything special.
+	aix_use_runtimelinking=no
+	exp_sym_flag='-Bexport'
+	no_entry_flag=""
+      else
+	# If we're using GNU nm, then we don't want the "-C" option.
+	# -C means demangle to AIX nm, but means don't demangle with GNU nm
+	# Also, AIX nm treats weak defined symbols like other global
+	# defined symbols, whereas GNU nm marks them as "W".
+	if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+	  _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+	else
+	  _LT_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+	fi
+	aix_use_runtimelinking=no
+
+	# Test if we are trying to use run time linking or normal
+	# AIX style linking. If -brtl is somewhere in LDFLAGS, we
+	# need to do runtime linking.
+	case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*)
+	  for ld_flag in $LDFLAGS; do
+	  if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
+	    aix_use_runtimelinking=yes
+	    break
+	  fi
+	  done
+	  ;;
+	esac
+
+	exp_sym_flag='-bexport'
+	no_entry_flag='-bnoentry'
+      fi
+
+      # When large executables or shared objects are built, AIX ld can
+      # have problems creating the table of contents.  If linking a library
+      # or program results in "error TOC overflow" add -mminimal-toc to
+      # CXXFLAGS/CFLAGS for g++/gcc.  In the cases where that is not
+      # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+      _LT_TAGVAR(archive_cmds, $1)=''
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      _LT_TAGVAR(file_list_spec, $1)='${wl}-f,'
+
+      if test "$GCC" = yes; then
+	case $host_os in aix4.[[012]]|aix4.[[012]].*)
+	# We only want to do this on AIX 4.2 and lower, the check
+	# below for broken collect2 doesn't work under 4.3+
+	  collect2name=`${CC} -print-prog-name=collect2`
+	  if test -f "$collect2name" &&
+	   strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+	  then
+	  # We have reworked collect2
+	  :
+	  else
+	  # We have old collect2
+	  _LT_TAGVAR(hardcode_direct, $1)=unsupported
+	  # It fails to find uninstalled libraries when the uninstalled
+	  # path is not listed in the libpath.  Setting hardcode_minus_L
+	  # to unsupported forces relinking
+	  _LT_TAGVAR(hardcode_minus_L, $1)=yes
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+	  _LT_TAGVAR(hardcode_libdir_separator, $1)=
+	  fi
+	  ;;
+	esac
+	shared_flag='-shared'
+	if test "$aix_use_runtimelinking" = yes; then
+	  shared_flag="$shared_flag "'${wl}-G'
+	fi
+      else
+	# not using gcc
+	if test "$host_cpu" = ia64; then
+	# VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+	# chokes on -Wl,-G. The following line is correct:
+	  shared_flag='-G'
+	else
+	  if test "$aix_use_runtimelinking" = yes; then
+	    shared_flag='${wl}-G'
+	  else
+	    shared_flag='${wl}-bM:SRE'
+	  fi
+	fi
+      fi
+
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-bexpall'
+      # It seems that -bexpall does not export symbols beginning with
+      # underscore (_), so it is better to generate a list of symbols to export.
+      _LT_TAGVAR(always_export_symbols, $1)=yes
+      if test "$aix_use_runtimelinking" = yes; then
+	# Warning - without using the other runtime loading flags (-brtl),
+	# -berok will link without error, but may produce a broken library.
+	_LT_TAGVAR(allow_undefined_flag, $1)='-berok'
+        # Determine the default libpath from the value encoded in an
+        # empty executable.
+        _LT_SYS_MODULE_PATH_AIX([$1])
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+        _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+      else
+	if test "$host_cpu" = ia64; then
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib'
+	  _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs"
+	  _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+	else
+	 # Determine the default libpath from the value encoded in an
+	 # empty executable.
+	 _LT_SYS_MODULE_PATH_AIX([$1])
+	 _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+	  # Warning - without using the other run time loading flags,
+	  # -berok will link without error, but may produce a broken library.
+	  _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok'
+	  _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok'
+	  if test "$with_gnu_ld" = yes; then
+	    # We only use this code for GNU lds that support --whole-archive.
+	    _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+	  else
+	    # Exported symbols can be pulled into shared objects from archives
+	    _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience'
+	  fi
+	  _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+	  # This is similar to how AIX traditionally builds its shared libraries.
+	  _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+	fi
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            _LT_TAGVAR(archive_expsym_cmds, $1)=''
+        ;;
+      m68k)
+            _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+            _LT_TAGVAR(hardcode_minus_L, $1)=yes
+        ;;
+      esac
+      ;;
+
+    bsdi[[45]]*)
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # When not using gcc, we currently assume that we are using
+      # Microsoft Visual C++.
+      # hardcode_libdir_flag_spec is actually meaningless, as there is
+      # no search path for DLLs.
+      case $cc_basename in
+      cl*)
+	# Native MSVC
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+	_LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	_LT_TAGVAR(always_export_symbols, $1)=yes
+	_LT_TAGVAR(file_list_spec, $1)='@'
+	# Tell ltmain to make .lib files, not .a files.
+	libext=lib
+	# Tell ltmain to make .dll files, not .so files.
+	shrext_cmds=".dll"
+	# FIXME: Setting linknames here is a bad hack.
+	_LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+	_LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	    sed -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+	  else
+	    sed -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+	  fi~
+	  $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+	  linknames='
+	# The linker will not automatically build a static lib if we build a DLL.
+	# _LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+	_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+	_LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+	_LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols'
+	# Don't use ranlib
+	_LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib'
+	_LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~
+	  lt_tool_outputfile="@TOOL_OUTPUT@"~
+	  case $lt_outputfile in
+	    *.exe|*.EXE) ;;
+	    *)
+	      lt_outputfile="$lt_outputfile.exe"
+	      lt_tool_outputfile="$lt_tool_outputfile.exe"
+	      ;;
+	  esac~
+	  if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+	    $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+	    $RM "$lt_outputfile.manifest";
+	  fi'
+	;;
+      *)
+	# Assume MSVC wrapper
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+	_LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	# Tell ltmain to make .lib files, not .a files.
+	libext=lib
+	# Tell ltmain to make .dll files, not .so files.
+	shrext_cmds=".dll"
+	# FIXME: Setting linknames here is a bad hack.
+	_LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames='
+	# The linker will automatically build a .lib file if we build a DLL.
+	_LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+	# FIXME: Should let the user specify the lib program.
+	_LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs'
+	_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+	;;
+      esac
+      ;;
+
+    darwin* | rhapsody*)
+      _LT_DARWIN_LINKER_FEATURES($1)
+      ;;
+
+    dgux*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+    # support.  Future versions do this automatically, but an explicit c++rt0.o
+    # does not break anything, and helps significantly (at the cost of a little
+    # extra space).
+    freebsd2.2*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+    freebsd2.*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+    freebsd* | dragonfly*)
+      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    hpux9*)
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+
+      # hardcode_minus_L: Not really in the search PATH,
+      # but as the default location of the library.
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+      ;;
+
+    hpux10*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      if test "$with_gnu_ld" = no; then
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+	_LT_TAGVAR(hardcode_libdir_separator, $1)=:
+	_LT_TAGVAR(hardcode_direct, $1)=yes
+	_LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+	_LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+	# hardcode_minus_L: Not really in the search PATH,
+	# but as the default location of the library.
+	_LT_TAGVAR(hardcode_minus_L, $1)=yes
+      fi
+      ;;
+
+    hpux11*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+	case $host_cpu in
+	hppa*64*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	ia64*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	esac
+      else
+	case $host_cpu in
+	hppa*64*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	ia64*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	*)
+	m4_if($1, [], [
+	  # Older versions of the 11.00 compiler do not understand -b yet
+	  # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does)
+	  _LT_LINKER_OPTION([if $CC understands -b],
+	    _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b],
+	    [_LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'],
+	    [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])],
+	  [_LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'])
+	  ;;
+	esac
+      fi
+      if test "$with_gnu_ld" = no; then
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+	_LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+	case $host_cpu in
+	hppa*64*|ia64*)
+	  _LT_TAGVAR(hardcode_direct, $1)=no
+	  _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	  ;;
+	*)
+	  _LT_TAGVAR(hardcode_direct, $1)=yes
+	  _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+	  _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+
+	  # hardcode_minus_L: Not really in the search PATH,
+	  # but as the default location of the library.
+	  _LT_TAGVAR(hardcode_minus_L, $1)=yes
+	  ;;
+	esac
+      fi
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+	# Try to use the -exported_symbol ld option, if it does not
+	# work, assume that -exports_file does not work either and
+	# implicitly export all symbols.
+	# This should be the same for all languages, so no per-tag cache variable.
+	AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol],
+	  [lt_cv_irix_exported_symbol],
+	  [save_LDFLAGS="$LDFLAGS"
+	   LDFLAGS="$LDFLAGS -shared ${wl}-exported_symbol ${wl}foo ${wl}-update_registry ${wl}/dev/null"
+	   AC_LINK_IFELSE(
+	     [AC_LANG_SOURCE(
+	        [AC_LANG_CASE([C], [[int foo (void) { return 0; }]],
+			      [C++], [[int foo (void) { return 0; }]],
+			      [Fortran 77], [[
+      subroutine foo
+      end]],
+			      [Fortran], [[
+      subroutine foo
+      end]])])],
+	      [lt_cv_irix_exported_symbol=yes],
+	      [lt_cv_irix_exported_symbol=no])
+           LDFLAGS="$save_LDFLAGS"])
+	if test "$lt_cv_irix_exported_symbol" = yes; then
+          _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations ${wl}-exports_file ${wl}$export_symbols -o $lib'
+	fi
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -exports_file $export_symbols -o $lib'
+      fi
+      _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      _LT_TAGVAR(inherit_rpath, $1)=yes
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      ;;
+
+    netbsd*)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+	_LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'  # a.out
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags'      # ELF
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    newsos6)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    *nto* | *qnx*)
+      ;;
+
+    openbsd*)
+      if test -f /usr/libexec/ld.so; then
+	_LT_TAGVAR(hardcode_direct, $1)=yes
+	_LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	_LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+	if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols'
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	  _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+	else
+	  case $host_os in
+	   openbsd[[01]].* | openbsd2.[[0-7]] | openbsd2.[[0-7]].*)
+	     _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+	     _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+	     ;;
+	   *)
+	     _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+	     _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	     ;;
+	  esac
+	fi
+      else
+	_LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    os2*)
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+      _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~echo DATA >> $output_objdir/$libname.def~echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def'
+      _LT_TAGVAR(old_archive_from_new_cmds, $1)='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def'
+      ;;
+
+    osf3*)
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+      else
+	_LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+      fi
+      _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      ;;
+
+    osf4* | osf5*)	# as osf3* with the addition of -msym flag
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $pic_flag $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      else
+	_LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~
+	$CC -shared${allow_undefined_flag} ${wl}-input ${wl}$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~$RM $lib.exp'
+
+	# Both c and cxx compiler support -rpath directly
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+      fi
+      _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      ;;
+
+    solaris*)
+      _LT_TAGVAR(no_undefined_flag, $1)=' -z defs'
+      if test "$GCC" = yes; then
+	wlarc='${wl}'
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+      else
+	case `$CC -V 2>&1` in
+	*"Compilers 5.0"*)
+	  wlarc=''
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp'
+	  ;;
+	*)
+	  wlarc='${wl}'
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $compiler_flags'
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $CC -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+	  ;;
+	esac
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      case $host_os in
+      solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+      *)
+	# The compiler driver will combine and reorder linker options,
+	# but understands `-z linker_flag'.  GCC discards it without `$wl',
+	# but is careful enough not to reorder.
+	# Supported since Solaris 2.6 (maybe 2.5.1?)
+	if test "$GCC" = yes; then
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+	else
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract'
+	fi
+	;;
+      esac
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      ;;
+
+    sunos4*)
+      if test "x$host_vendor" = xsequent; then
+	# Use $CC to link under sequent, because it throws in some extra .o
+	# files that make .init and .fini sections work.
+	_LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    sysv4)
+      case $host_vendor in
+	sni)
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true???
+	;;
+	siemens)
+	  ## LD is ld it makes a PLAMLIB
+	  ## CC just makes a GrossModule.
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+	  _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs'
+	  _LT_TAGVAR(hardcode_direct, $1)=no
+        ;;
+	motorola)
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie
+	;;
+      esac
+      runpath_var='LD_RUN_PATH'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    sysv4.3*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+	_LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	_LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	runpath_var=LD_RUN_PATH
+	hardcode_runpath_var=yes
+	_LT_TAGVAR(ld_shlibs, $1)=yes
+      fi
+      ;;
+
+    sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*)
+      _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6*)
+      # Note: We can NOT use -z defs as we might desire, because we do not
+      # link with -lc, and that would cause any symbols used from libc to
+      # always be unresolved, which means just about no library would
+      # ever link correctly.  If we're not using GNU ld we use -z text
+      # though, which does catch some bad symbols but isn't as heavy-handed
+      # as -z defs.
+      _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+      _LT_TAGVAR(allow_undefined_flag, $1)='${wl}-z,nodefs'
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R,$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport'
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    uts4*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    *)
+      _LT_TAGVAR(ld_shlibs, $1)=no
+      ;;
+    esac
+
+    if test x$host_vendor = xsni; then
+      case $host in
+      sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+	_LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Blargedynsym'
+	;;
+      esac
+    fi
+  fi
+])
+AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)])
+test "$_LT_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no
+
+_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld
+
+_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl
+_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl
+_LT_DECL([], [extract_expsyms_cmds], [2],
+    [The commands to extract the exported symbol list from a shared archive])
+
+#
+# Do we need to explicitly link libc?
+#
+case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in
+x|xyes)
+  # Assume -lc should be added
+  _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+
+  if test "$enable_shared" = yes && test "$GCC" = yes; then
+    case $_LT_TAGVAR(archive_cmds, $1) in
+    *'~'*)
+      # FIXME: we may have to deal with multi-command sequences.
+      ;;
+    '$CC '*)
+      # Test whether the compiler implicitly links with -lc since on some
+      # systems, -lgcc has to come before -lc. If gcc already passes -lc
+      # to ld, don't add -lc before -lgcc.
+      AC_CACHE_CHECK([whether -lc should be explicitly linked in],
+	[lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1),
+	[$RM conftest*
+	echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+	if AC_TRY_EVAL(ac_compile) 2>conftest.err; then
+	  soname=conftest
+	  lib=conftest
+	  libobjs=conftest.$ac_objext
+	  deplibs=
+	  wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1)
+	  pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1)
+	  compiler_flags=-v
+	  linker_flags=-v
+	  verstring=
+	  output_objdir=.
+	  libname=conftest
+	  lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1)
+	  _LT_TAGVAR(allow_undefined_flag, $1)=
+	  if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1)
+	  then
+	    lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+	  else
+	    lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+	  fi
+	  _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag
+	else
+	  cat conftest.err 1>&5
+	fi
+	$RM conftest*
+	])
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)
+      ;;
+    esac
+  fi
+  ;;
+esac
+
+_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0],
+    [Whether or not to add -lc for building shared libraries])
+_LT_TAGDECL([allow_libtool_libs_with_static_runtimes],
+    [enable_shared_with_static_runtimes], [0],
+    [Whether or not to disallow shared libs when runtime libs are static])
+_LT_TAGDECL([], [export_dynamic_flag_spec], [1],
+    [Compiler flag to allow reflexive dlopens])
+_LT_TAGDECL([], [whole_archive_flag_spec], [1],
+    [Compiler flag to generate shared objects directly from archives])
+_LT_TAGDECL([], [compiler_needs_object], [1],
+    [Whether the compiler copes with passing no objects directly])
+_LT_TAGDECL([], [old_archive_from_new_cmds], [2],
+    [Create an old-style archive from a shared archive])
+_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2],
+    [Create a temporary old-style archive to link instead of a shared archive])
+_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive])
+_LT_TAGDECL([], [archive_expsym_cmds], [2])
+_LT_TAGDECL([], [module_cmds], [2],
+    [Commands used to build a loadable module if different from building
+    a shared archive.])
+_LT_TAGDECL([], [module_expsym_cmds], [2])
+_LT_TAGDECL([], [with_gnu_ld], [1],
+    [Whether we are building with GNU ld or not])
+_LT_TAGDECL([], [allow_undefined_flag], [1],
+    [Flag that allows shared libraries with undefined symbols to be built])
+_LT_TAGDECL([], [no_undefined_flag], [1],
+    [Flag that enforces no undefined symbols])
+_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1],
+    [Flag to hardcode $libdir into a binary during linking.
+    This must work even if $libdir does not exist])
+_LT_TAGDECL([], [hardcode_libdir_separator], [1],
+    [Whether we need a single "-rpath" flag with a separated argument])
+_LT_TAGDECL([], [hardcode_direct], [0],
+    [Set to "yes" if using DIR/libNAME${shared_ext} during linking hardcodes
+    DIR into the resulting binary])
+_LT_TAGDECL([], [hardcode_direct_absolute], [0],
+    [Set to "yes" if using DIR/libNAME${shared_ext} during linking hardcodes
+    DIR into the resulting binary and the resulting library dependency is
+    "absolute", i.e impossible to change by setting ${shlibpath_var} if the
+    library is relocated])
+_LT_TAGDECL([], [hardcode_minus_L], [0],
+    [Set to "yes" if using the -LDIR flag during linking hardcodes DIR
+    into the resulting binary])
+_LT_TAGDECL([], [hardcode_shlibpath_var], [0],
+    [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR
+    into the resulting binary])
+_LT_TAGDECL([], [hardcode_automatic], [0],
+    [Set to "yes" if building a shared library automatically hardcodes DIR
+    into the library and all subsequent libraries and executables linked
+    against it])
+_LT_TAGDECL([], [inherit_rpath], [0],
+    [Set to yes if linker adds runtime paths of dependent libraries
+    to runtime path list])
+_LT_TAGDECL([], [link_all_deplibs], [0],
+    [Whether libtool must link a program against all its dependency libraries])
+_LT_TAGDECL([], [always_export_symbols], [0],
+    [Set to "yes" if exported symbols are required])
+_LT_TAGDECL([], [export_symbols_cmds], [2],
+    [The commands to list exported symbols])
+_LT_TAGDECL([], [exclude_expsyms], [1],
+    [Symbols that should not be listed in the preloaded symbols])
+_LT_TAGDECL([], [include_expsyms], [1],
+    [Symbols that must always be exported])
+_LT_TAGDECL([], [prelink_cmds], [2],
+    [Commands necessary for linking programs (against libraries) with templates])
+_LT_TAGDECL([], [postlink_cmds], [2],
+    [Commands necessary for finishing linking programs])
+_LT_TAGDECL([], [file_list_spec], [1],
+    [Specify filename containing input files])
+dnl FIXME: Not yet implemented
+dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1],
+dnl    [Compiler flag to generate thread safe objects])
+])# _LT_LINKER_SHLIBS
+
+
+# _LT_LANG_C_CONFIG([TAG])
+# ------------------------
+# Ensure that the configuration variables for a C compiler are suitably
+# defined.  These variables are subsequently used by _LT_CONFIG to write
+# the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_C_CONFIG],
+[m4_require([_LT_DECL_EGREP])dnl
+lt_save_CC="$CC"
+AC_LANG_PUSH(C)
+
+# Source file extension for C test sources.
+ac_ext=c
+
+# Object file extension for compiled C test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="int some_variable = 0;"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='int main(){return(0);}'
+
+_LT_TAG_COMPILER
+# Save the default compiler, since it gets overwritten when the other
+# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP.
+compiler_DEFAULT=$CC
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+if test -n "$compiler"; then
+  _LT_COMPILER_NO_RTTI($1)
+  _LT_COMPILER_PIC($1)
+  _LT_COMPILER_C_O($1)
+  _LT_COMPILER_FILE_LOCKS($1)
+  _LT_LINKER_SHLIBS($1)
+  _LT_SYS_DYNAMIC_LINKER($1)
+  _LT_LINKER_HARDCODE_LIBPATH($1)
+  LT_SYS_DLOPEN_SELF
+  _LT_CMD_STRIPLIB
+
+  # Report which library types will actually be built
+  AC_MSG_CHECKING([if libtool supports shared libraries])
+  AC_MSG_RESULT([$can_build_shared])
+
+  AC_MSG_CHECKING([whether to build shared libraries])
+  test "$can_build_shared" = "no" && enable_shared=no
+
+  # On AIX, shared libraries and static libraries use the same namespace, and
+  # are all built from PIC.
+  case $host_os in
+  aix3*)
+    test "$enable_shared" = yes && enable_static=no
+    if test -n "$RANLIB"; then
+      archive_cmds="$archive_cmds~\$RANLIB \$lib"
+      postinstall_cmds='$RANLIB $lib'
+    fi
+    ;;
+
+  aix[[4-9]]*)
+    if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+      test "$enable_shared" = yes && enable_static=no
+    fi
+    ;;
+  esac
+  AC_MSG_RESULT([$enable_shared])
+
+  AC_MSG_CHECKING([whether to build static libraries])
+  # Make sure either enable_shared or enable_static is yes.
+  test "$enable_shared" = yes || enable_static=yes
+  AC_MSG_RESULT([$enable_static])
+
+  _LT_CONFIG($1)
+fi
+AC_LANG_POP
+CC="$lt_save_CC"
+])# _LT_LANG_C_CONFIG
+
+
+# _LT_LANG_CXX_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for a C++ compiler are suitably
+# defined.  These variables are subsequently used by _LT_CONFIG to write
+# the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_CXX_CONFIG],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_PATH_MANIFEST_TOOL])dnl
+if test -n "$CXX" && ( test "X$CXX" != "Xno" &&
+    ( (test "X$CXX" = "Xg++" && `g++ -v >/dev/null 2>&1` ) ||
+    (test "X$CXX" != "Xg++"))) ; then
+  AC_PROG_CXXCPP
+else
+  _lt_caught_CXX_error=yes
+fi
+
+AC_LANG_PUSH(C++)
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(compiler_needs_object, $1)=no
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for C++ test sources.
+ac_ext=cpp
+
+# Object file extension for compiled C++ test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the CXX compiler isn't working.  Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_caught_CXX_error" != yes; then
+  # Code to be used in simple compile tests
+  lt_simple_compile_test_code="int some_variable = 0;"
+
+  # Code to be used in simple link tests
+  lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }'
+
+  # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+  _LT_TAG_COMPILER
+
+  # save warnings/boilerplate of simple test code
+  _LT_COMPILER_BOILERPLATE
+  _LT_LINKER_BOILERPLATE
+
+  # Allow CC to be a program name with arguments.
+  lt_save_CC=$CC
+  lt_save_CFLAGS=$CFLAGS
+  lt_save_LD=$LD
+  lt_save_GCC=$GCC
+  GCC=$GXX
+  lt_save_with_gnu_ld=$with_gnu_ld
+  lt_save_path_LD=$lt_cv_path_LD
+  if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then
+    lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx
+  else
+    $as_unset lt_cv_prog_gnu_ld
+  fi
+  if test -n "${lt_cv_path_LDCXX+set}"; then
+    lt_cv_path_LD=$lt_cv_path_LDCXX
+  else
+    $as_unset lt_cv_path_LD
+  fi
+  test -z "${LDCXX+set}" || LD=$LDCXX
+  CC=${CXX-"c++"}
+  CFLAGS=$CXXFLAGS
+  compiler=$CC
+  _LT_TAGVAR(compiler, $1)=$CC
+  _LT_CC_BASENAME([$compiler])
+
+  if test -n "$compiler"; then
+    # We don't want -fno-exception when compiling C++ code, so set the
+    # no_builtin_flag separately
+    if test "$GXX" = yes; then
+      _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin'
+    else
+      _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=
+    fi
+
+    if test "$GXX" = yes; then
+      # Set up default GNU C++ configuration
+
+      LT_PATH_LD
+
+      # Check if GNU C++ uses GNU ld as the underlying linker, since the
+      # archiving commands below assume that GNU ld is being used.
+      if test "$with_gnu_ld" = yes; then
+        _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+        _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+        _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+
+        # If archive_cmds runs LD, not CC, wlarc should be empty
+        # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to
+        #     investigate it a little bit more. (MM)
+        wlarc='${wl}'
+
+        # ancient GNU ld didn't support --whole-archive et. al.
+        if eval "`$CC -print-prog-name=ld` --help 2>&1" |
+	  $GREP 'no-whole-archive' > /dev/null; then
+          _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+        else
+          _LT_TAGVAR(whole_archive_flag_spec, $1)=
+        fi
+      else
+        with_gnu_ld=no
+        wlarc=
+
+        # A generic and very simple default shared library creation
+        # command for GNU C++ for the case where it uses the native
+        # linker, instead of GNU ld.  If possible, this setting should
+        # overridden to take advantage of the native linker features on
+        # the platform it is being used on.
+        _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib'
+      fi
+
+      # Commands to make compiler produce verbose output that lists
+      # what "hidden" libraries, object files and flags are used when
+      # linking a shared library.
+      output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+
+    else
+      GXX=no
+      with_gnu_ld=no
+      wlarc=
+    fi
+
+    # PORTME: fill in a description of your system's C++ link characteristics
+    AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries])
+    _LT_TAGVAR(ld_shlibs, $1)=yes
+    case $host_os in
+      aix3*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+      aix[[4-9]]*)
+        if test "$host_cpu" = ia64; then
+          # On IA64, the linker does run time linking by default, so we don't
+          # have to do anything special.
+          aix_use_runtimelinking=no
+          exp_sym_flag='-Bexport'
+          no_entry_flag=""
+        else
+          aix_use_runtimelinking=no
+
+          # Test if we are trying to use run time linking or normal
+          # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+          # need to do runtime linking.
+          case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*)
+	    for ld_flag in $LDFLAGS; do
+	      case $ld_flag in
+	      *-brtl*)
+	        aix_use_runtimelinking=yes
+	        break
+	        ;;
+	      esac
+	    done
+	    ;;
+          esac
+
+          exp_sym_flag='-bexport'
+          no_entry_flag='-bnoentry'
+        fi
+
+        # When large executables or shared objects are built, AIX ld can
+        # have problems creating the table of contents.  If linking a library
+        # or program results in "error TOC overflow" add -mminimal-toc to
+        # CXXFLAGS/CFLAGS for g++/gcc.  In the cases where that is not
+        # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+        _LT_TAGVAR(archive_cmds, $1)=''
+        _LT_TAGVAR(hardcode_direct, $1)=yes
+        _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+        _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+        _LT_TAGVAR(link_all_deplibs, $1)=yes
+        _LT_TAGVAR(file_list_spec, $1)='${wl}-f,'
+
+        if test "$GXX" = yes; then
+          case $host_os in aix4.[[012]]|aix4.[[012]].*)
+          # We only want to do this on AIX 4.2 and lower, the check
+          # below for broken collect2 doesn't work under 4.3+
+	  collect2name=`${CC} -print-prog-name=collect2`
+	  if test -f "$collect2name" &&
+	     strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+	  then
+	    # We have reworked collect2
+	    :
+	  else
+	    # We have old collect2
+	    _LT_TAGVAR(hardcode_direct, $1)=unsupported
+	    # It fails to find uninstalled libraries when the uninstalled
+	    # path is not listed in the libpath.  Setting hardcode_minus_L
+	    # to unsupported forces relinking
+	    _LT_TAGVAR(hardcode_minus_L, $1)=yes
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+	    _LT_TAGVAR(hardcode_libdir_separator, $1)=
+	  fi
+          esac
+          shared_flag='-shared'
+	  if test "$aix_use_runtimelinking" = yes; then
+	    shared_flag="$shared_flag "'${wl}-G'
+	  fi
+        else
+          # not using gcc
+          if test "$host_cpu" = ia64; then
+	  # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+	  # chokes on -Wl,-G. The following line is correct:
+	  shared_flag='-G'
+          else
+	    if test "$aix_use_runtimelinking" = yes; then
+	      shared_flag='${wl}-G'
+	    else
+	      shared_flag='${wl}-bM:SRE'
+	    fi
+          fi
+        fi
+
+        _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-bexpall'
+        # It seems that -bexpall does not export symbols beginning with
+        # underscore (_), so it is better to generate a list of symbols to
+	# export.
+        _LT_TAGVAR(always_export_symbols, $1)=yes
+        if test "$aix_use_runtimelinking" = yes; then
+          # Warning - without using the other runtime loading flags (-brtl),
+          # -berok will link without error, but may produce a broken library.
+          _LT_TAGVAR(allow_undefined_flag, $1)='-berok'
+          # Determine the default libpath from the value encoded in an empty
+          # executable.
+          _LT_SYS_MODULE_PATH_AIX([$1])
+          _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+
+          _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+        else
+          if test "$host_cpu" = ia64; then
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib'
+	    _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs"
+	    _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+          else
+	    # Determine the default libpath from the value encoded in an
+	    # empty executable.
+	    _LT_SYS_MODULE_PATH_AIX([$1])
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+	    # Warning - without using the other run time loading flags,
+	    # -berok will link without error, but may produce a broken library.
+	    _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok'
+	    _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok'
+	    if test "$with_gnu_ld" = yes; then
+	      # We only use this code for GNU lds that support --whole-archive.
+	      _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+	    else
+	      # Exported symbols can be pulled into shared objects from archives
+	      _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience'
+	    fi
+	    _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+	    # This is similar to how AIX traditionally builds its shared
+	    # libraries.
+	    _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+          fi
+        fi
+        ;;
+
+      beos*)
+	if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	  _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	  # Joseph Beckenbach <[email-masked]> says some releases of gcc
+	  # support --undefined.  This deserves some investigation.  FIXME
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	else
+	  _LT_TAGVAR(ld_shlibs, $1)=no
+	fi
+	;;
+
+      chorus*)
+        case $cc_basename in
+          *)
+	  # FIXME: insert proper C++ library support
+	  _LT_TAGVAR(ld_shlibs, $1)=no
+	  ;;
+        esac
+        ;;
+
+      cygwin* | mingw* | pw32* | cegcc*)
+	case $GXX,$cc_basename in
+	,cl* | no,cl*)
+	  # Native MSVC
+	  # hardcode_libdir_flag_spec is actually meaningless, as there is
+	  # no search path for DLLs.
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+	  _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	  _LT_TAGVAR(always_export_symbols, $1)=yes
+	  _LT_TAGVAR(file_list_spec, $1)='@'
+	  # Tell ltmain to make .lib files, not .a files.
+	  libext=lib
+	  # Tell ltmain to make .dll files, not .so files.
+	  shrext_cmds=".dll"
+	  # FIXME: Setting linknames here is a bad hack.
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	      $SED -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+	    else
+	      $SED -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+	    fi~
+	    $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+	    linknames='
+	  # The linker will not automatically build a static lib if we build a DLL.
+	  # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+	  _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+	  # Don't use ranlib
+	  _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib'
+	  _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~
+	    lt_tool_outputfile="@TOOL_OUTPUT@"~
+	    case $lt_outputfile in
+	      *.exe|*.EXE) ;;
+	      *)
+		lt_outputfile="$lt_outputfile.exe"
+		lt_tool_outputfile="$lt_tool_outputfile.exe"
+		;;
+	    esac~
+	    func_to_tool_file "$lt_outputfile"~
+	    if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+	      $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+	      $RM "$lt_outputfile.manifest";
+	    fi'
+	  ;;
+	*)
+	  # g++
+	  # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless,
+	  # as there is no search path for DLLs.
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+	  _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-all-symbols'
+	  _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	  _LT_TAGVAR(always_export_symbols, $1)=no
+	  _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+
+	  if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+	    # If the export-symbols file already is a .def file (1st line
+	    # is EXPORTS), use it as is; otherwise, prepend...
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	      cp $export_symbols $output_objdir/$soname.def;
+	    else
+	      echo EXPORTS > $output_objdir/$soname.def;
+	      cat $export_symbols >> $output_objdir/$soname.def;
+	    fi~
+	    $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+	  else
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	  fi
+	  ;;
+	esac
+	;;
+      darwin* | rhapsody*)
+        _LT_DARWIN_LINKER_FEATURES($1)
+	;;
+
+      dgux*)
+        case $cc_basename in
+          ec++*)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          ghcx*)
+	    # Green Hills C++ Compiler
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          *)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+        esac
+        ;;
+
+      freebsd2.*)
+        # C++ shared libraries reported to be fairly broken before
+	# switch to ELF
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+
+      freebsd-elf*)
+        _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+        ;;
+
+      freebsd* | dragonfly*)
+        # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF
+        # conventions
+        _LT_TAGVAR(ld_shlibs, $1)=yes
+        ;;
+
+      gnu*)
+        ;;
+
+      haiku*)
+        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+        _LT_TAGVAR(link_all_deplibs, $1)=yes
+        ;;
+
+      hpux9*)
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+        _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+        _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+        _LT_TAGVAR(hardcode_direct, $1)=yes
+        _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH,
+				             # but as the default
+				             # location of the library.
+
+        case $cc_basename in
+          CC*)
+            # FIXME: insert proper C++ library support
+            _LT_TAGVAR(ld_shlibs, $1)=no
+            ;;
+          aCC*)
+            _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+            # Commands to make compiler produce verbose output that lists
+            # what "hidden" libraries, object files and flags are used when
+            # linking a shared library.
+            #
+            # There doesn't appear to be a way to prevent this compiler from
+            # explicitly linking system object files so we need to strip them
+            # from the output so that they don't get included in the library
+            # dependencies.
+            output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+            ;;
+          *)
+            if test "$GXX" = yes; then
+              _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+            else
+              # FIXME: insert proper C++ library support
+              _LT_TAGVAR(ld_shlibs, $1)=no
+            fi
+            ;;
+        esac
+        ;;
+
+      hpux10*|hpux11*)
+        if test $with_gnu_ld = no; then
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+	  _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+          case $host_cpu in
+            hppa*64*|ia64*)
+              ;;
+            *)
+	      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+              ;;
+          esac
+        fi
+        case $host_cpu in
+          hppa*64*|ia64*)
+            _LT_TAGVAR(hardcode_direct, $1)=no
+            _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+            ;;
+          *)
+            _LT_TAGVAR(hardcode_direct, $1)=yes
+            _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+            _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH,
+					         # but as the default
+					         # location of the library.
+            ;;
+        esac
+
+        case $cc_basename in
+          CC*)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          aCC*)
+	    case $host_cpu in
+	      hppa*64*)
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	        ;;
+	      ia64*)
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	        ;;
+	      *)
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	        ;;
+	    esac
+	    # Commands to make compiler produce verbose output that lists
+	    # what "hidden" libraries, object files and flags are used when
+	    # linking a shared library.
+	    #
+	    # There doesn't appear to be a way to prevent this compiler from
+	    # explicitly linking system object files so we need to strip them
+	    # from the output so that they don't get included in the library
+	    # dependencies.
+	    output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+	    ;;
+          *)
+	    if test "$GXX" = yes; then
+	      if test $with_gnu_ld = no; then
+	        case $host_cpu in
+	          hppa*64*)
+	            _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	            ;;
+	          ia64*)
+	            _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	            ;;
+	          *)
+	            _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	            ;;
+	        esac
+	      fi
+	    else
+	      # FIXME: insert proper C++ library support
+	      _LT_TAGVAR(ld_shlibs, $1)=no
+	    fi
+	    ;;
+        esac
+        ;;
+
+      interix[[3-9]]*)
+	_LT_TAGVAR(hardcode_direct, $1)=no
+	_LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	_LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+	# Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+	# Instead, shared libraries are loaded at an image base (0x10000000 by
+	# default) and relocated if they conflict, which is a slow very memory
+	# consuming and fragmenting process.  To avoid this, we pick a random,
+	# 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+	# time.  Moving up from 0x10000000 also allows more sbrk(2) space.
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+	;;
+      irix5* | irix6*)
+        case $cc_basename in
+          CC*)
+	    # SGI C++
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+
+	    # Archives containing C++ object files must be created using
+	    # "CC -ar", where "CC" is the IRIX C++ compiler.  This is
+	    # necessary to make sure instantiated templates are included
+	    # in the archive.
+	    _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs'
+	    ;;
+          *)
+	    if test "$GXX" = yes; then
+	      if test "$with_gnu_ld" = no; then
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+	      else
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` -o $lib'
+	      fi
+	    fi
+	    _LT_TAGVAR(link_all_deplibs, $1)=yes
+	    ;;
+        esac
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+        _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+        _LT_TAGVAR(inherit_rpath, $1)=yes
+        ;;
+
+      linux* | k*bsd*-gnu | kopensolaris*-gnu)
+        case $cc_basename in
+          KCC*)
+	    # Kuck and Associates, Inc. (KAI) C++ Compiler
+
+	    # KCC will only create a shared library if the output file
+	    # ends with ".so" (or ".sl" for HP-UX), so rename the library
+	    # to its proper name (with version) after linking.
+	    _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib ${wl}-retain-symbols-file,$export_symbols; mv \$templib $lib'
+	    # Commands to make compiler produce verbose output that lists
+	    # what "hidden" libraries, object files and flags are used when
+	    # linking a shared library.
+	    #
+	    # There doesn't appear to be a way to prevent this compiler from
+	    # explicitly linking system object files so we need to strip them
+	    # from the output so that they don't get included in the library
+	    # dependencies.
+	    output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+
+	    # Archives containing C++ object files must be created using
+	    # "CC -Bstatic", where "CC" is the KAI C++ compiler.
+	    _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs'
+	    ;;
+	  icpc* | ecpc* )
+	    # Intel C++
+	    with_gnu_ld=yes
+	    # version 8.0 and above of icpc choke on multiply defined symbols
+	    # if we add $predep_objects and $postdep_objects, however 7.1 and
+	    # earlier do not add the objects themselves.
+	    case `$CC -V 2>&1` in
+	      *"Version 7."*)
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+		_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+		;;
+	      *)  # Version 8.0 or newer
+	        tmp_idyn=
+	        case $host_cpu in
+		  ia64*) tmp_idyn=' -i_dynamic';;
+		esac
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+		_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+		;;
+	    esac
+	    _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+	    _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+	    ;;
+          pgCC* | pgcpp*)
+            # Portland Group C++ compiler
+	    case `$CC -V` in
+	    *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*)
+	      _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~
+		rm -rf $tpldir~
+		$CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~
+		compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"'
+	      _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~
+		rm -rf $tpldir~
+		$CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~
+		$AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~
+		$RANLIB $oldlib'
+	      _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~
+		rm -rf $tpldir~
+		$CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~
+		$CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib'
+	      _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~
+		rm -rf $tpldir~
+		$CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~
+		$CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib'
+	      ;;
+	    *) # Version 6 and above use weak symbols
+	      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib'
+	      _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib'
+	      ;;
+	    esac
+
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir'
+	    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+	    _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+            ;;
+	  cxx*)
+	    # Compaq C++
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname  -o $lib ${wl}-retain-symbols-file $wl$export_symbols'
+
+	    runpath_var=LD_RUN_PATH
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+	    _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+	    # Commands to make compiler produce verbose output that lists
+	    # what "hidden" libraries, object files and flags are used when
+	    # linking a shared library.
+	    #
+	    # There doesn't appear to be a way to prevent this compiler from
+	    # explicitly linking system object files so we need to strip them
+	    # from the output so that they don't get included in the library
+	    # dependencies.
+	    output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed'
+	    ;;
+	  xl* | mpixl* | bgxl*)
+	    # IBM XL 8.0 on PPC, with GNU ld
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+	    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	    if test "x$supports_anon_versioning" = xyes; then
+	      _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+		cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+		echo "local: *; };" >> $output_objdir/$libname.ver~
+		$CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+	    fi
+	    ;;
+	  *)
+	    case `$CC -V 2>&1 | sed 5q` in
+	    *Sun\ C*)
+	      # Sun C++ 5.9
+	      _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs'
+	      _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	      _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file ${wl}$export_symbols'
+	      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+	      _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	      _LT_TAGVAR(compiler_needs_object, $1)=yes
+
+	      # Not sure whether something based on
+	      # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1
+	      # would be better.
+	      output_verbose_link_cmd='func_echo_all'
+
+	      # Archives containing C++ object files must be created using
+	      # "CC -xar", where "CC" is the Sun C++ compiler.  This is
+	      # necessary to make sure instantiated templates are included
+	      # in the archive.
+	      _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs'
+	      ;;
+	    esac
+	    ;;
+	esac
+	;;
+
+      lynxos*)
+        # FIXME: insert proper C++ library support
+	_LT_TAGVAR(ld_shlibs, $1)=no
+	;;
+
+      m88k*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+	;;
+
+      mvs*)
+        case $cc_basename in
+          cxx*)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+	  *)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+	esac
+	;;
+
+      netbsd*)
+        if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable  -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags'
+	  wlarc=
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+	  _LT_TAGVAR(hardcode_direct, $1)=yes
+	  _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	fi
+	# Workaround some broken pre-1.5 toolchains
+	output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"'
+	;;
+
+      *nto* | *qnx*)
+        _LT_TAGVAR(ld_shlibs, $1)=yes
+	;;
+
+      openbsd2*)
+        # C++ shared libraries are fairly broken
+	_LT_TAGVAR(ld_shlibs, $1)=no
+	;;
+
+      openbsd*)
+	if test -f /usr/libexec/ld.so; then
+	  _LT_TAGVAR(hardcode_direct, $1)=yes
+	  _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	  _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib'
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	  if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file,$export_symbols -o $lib'
+	    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+	    _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+	  fi
+	  output_verbose_link_cmd=func_echo_all
+	else
+	  _LT_TAGVAR(ld_shlibs, $1)=no
+	fi
+	;;
+
+      osf3* | osf4* | osf5*)
+        case $cc_basename in
+          KCC*)
+	    # Kuck and Associates, Inc. (KAI) C++ Compiler
+
+	    # KCC will only create a shared library if the output file
+	    # ends with ".so" (or ".sl" for HP-UX), so rename the library
+	    # to its proper name (with version) after linking.
+	    _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib'
+
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	    _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+	    # Archives containing C++ object files must be created using
+	    # the KAI C++ compiler.
+	    case $host in
+	      osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;;
+	      *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;;
+	    esac
+	    ;;
+          RCC*)
+	    # Rational C++ 2.4.1
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          cxx*)
+	    case $host in
+	      osf3*)
+	        _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $soname `test -n "$verstring" && func_echo_all "${wl}-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+		;;
+	      *)
+	        _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	        _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~
+	          echo "-hidden">> $lib.exp~
+	          $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname ${wl}-input ${wl}$lib.exp  `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~
+	          $RM $lib.exp'
+	        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+		;;
+	    esac
+
+	    _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+	    # Commands to make compiler produce verbose output that lists
+	    # what "hidden" libraries, object files and flags are used when
+	    # linking a shared library.
+	    #
+	    # There doesn't appear to be a way to prevent this compiler from
+	    # explicitly linking system object files so we need to strip them
+	    # from the output so that they don't get included in the library
+	    # dependencies.
+	    output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+	    ;;
+	  *)
+	    if test "$GXX" = yes && test "$with_gnu_ld" = no; then
+	      _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+	      case $host in
+	        osf3*)
+	          _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+		  ;;
+	        *)
+	          _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+		  ;;
+	      esac
+
+	      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+	      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+	      # Commands to make compiler produce verbose output that lists
+	      # what "hidden" libraries, object files and flags are used when
+	      # linking a shared library.
+	      output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+
+	    else
+	      # FIXME: insert proper C++ library support
+	      _LT_TAGVAR(ld_shlibs, $1)=no
+	    fi
+	    ;;
+        esac
+        ;;
+
+      psos*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+
+      sunos4*)
+        case $cc_basename in
+          CC*)
+	    # Sun C++ 4.x
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          lcc*)
+	    # Lucid
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          *)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+        esac
+        ;;
+
+      solaris*)
+        case $cc_basename in
+          CC* | sunCC*)
+	    # Sun C++ 4.2, 5.x and Centerline C++
+            _LT_TAGVAR(archive_cmds_need_lc,$1)=yes
+	    _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs'
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag}  -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	      $CC -G${allow_undefined_flag} ${wl}-M ${wl}$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+	    _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	    case $host_os in
+	      solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+	      *)
+		# The compiler driver will combine and reorder linker options,
+		# but understands `-z linker_flag'.
+	        # Supported since Solaris 2.6 (maybe 2.5.1?)
+		_LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract'
+	        ;;
+	    esac
+	    _LT_TAGVAR(link_all_deplibs, $1)=yes
+
+	    output_verbose_link_cmd='func_echo_all'
+
+	    # Archives containing C++ object files must be created using
+	    # "CC -xar", where "CC" is the Sun C++ compiler.  This is
+	    # necessary to make sure instantiated templates are included
+	    # in the archive.
+	    _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs'
+	    ;;
+          gcx*)
+	    # Green Hills C++ Compiler
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+
+	    # The C++ compiler must be used to create the archive.
+	    _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs'
+	    ;;
+          *)
+	    # GNU C++ compiler with Solaris linker
+	    if test "$GXX" = yes && test "$with_gnu_ld" = no; then
+	      _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-z ${wl}defs'
+	      if $CC --version | $GREP -v '^2\.7' > /dev/null; then
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+	        _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+		  $CC -shared $pic_flag -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+	        # Commands to make compiler produce verbose output that lists
+	        # what "hidden" libraries, object files and flags are used when
+	        # linking a shared library.
+	        output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+	      else
+	        # g++ 2.7 appears to require `-G' NOT `-shared' on this
+	        # platform.
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+	        _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+		  $CC -G -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+	        # Commands to make compiler produce verbose output that lists
+	        # what "hidden" libraries, object files and flags are used when
+	        # linking a shared library.
+	        output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+	      fi
+
+	      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $wl$libdir'
+	      case $host_os in
+		solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+		*)
+		  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+		  ;;
+	      esac
+	    fi
+	    ;;
+        esac
+        ;;
+
+    sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*)
+      _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      runpath_var='LD_RUN_PATH'
+
+      case $cc_basename in
+        CC*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+      esac
+      ;;
+
+      sysv5* | sco3.2v5* | sco5v6*)
+	# Note: We can NOT use -z defs as we might desire, because we do not
+	# link with -lc, and that would cause any symbols used from libc to
+	# always be unresolved, which means just about no library would
+	# ever link correctly.  If we're not using GNU ld we use -z text
+	# though, which does catch some bad symbols but isn't as heavy-handed
+	# as -z defs.
+	_LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+	_LT_TAGVAR(allow_undefined_flag, $1)='${wl}-z,nodefs'
+	_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+	_LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R,$libdir'
+	_LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+	_LT_TAGVAR(link_all_deplibs, $1)=yes
+	_LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport'
+	runpath_var='LD_RUN_PATH'
+
+	case $cc_basename in
+          CC*)
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	    _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~
+	      '"$_LT_TAGVAR(old_archive_cmds, $1)"
+	    _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~
+	      '"$_LT_TAGVAR(reload_cmds, $1)"
+	    ;;
+	  *)
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	    ;;
+	esac
+      ;;
+
+      tandem*)
+        case $cc_basename in
+          NCC*)
+	    # NonStop-UX NCC 3.20
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          *)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+        esac
+        ;;
+
+      vxworks*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+
+      *)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+    esac
+
+    AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)])
+    test "$_LT_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no
+
+    _LT_TAGVAR(GCC, $1)="$GXX"
+    _LT_TAGVAR(LD, $1)="$LD"
+
+    ## CAVEAT EMPTOR:
+    ## There is no encapsulation within the following macros, do not change
+    ## the running order or otherwise move them around unless you know exactly
+    ## what you are doing...
+    _LT_SYS_HIDDEN_LIBDEPS($1)
+    _LT_COMPILER_PIC($1)
+    _LT_COMPILER_C_O($1)
+    _LT_COMPILER_FILE_LOCKS($1)
+    _LT_LINKER_SHLIBS($1)
+    _LT_SYS_DYNAMIC_LINKER($1)
+    _LT_LINKER_HARDCODE_LIBPATH($1)
+
+    _LT_CONFIG($1)
+  fi # test -n "$compiler"
+
+  CC=$lt_save_CC
+  CFLAGS=$lt_save_CFLAGS
+  LDCXX=$LD
+  LD=$lt_save_LD
+  GCC=$lt_save_GCC
+  with_gnu_ld=$lt_save_with_gnu_ld
+  lt_cv_path_LDCXX=$lt_cv_path_LD
+  lt_cv_path_LD=$lt_save_path_LD
+  lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld
+  lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld
+fi # test "$_lt_caught_CXX_error" != yes
+
+AC_LANG_POP
+])# _LT_LANG_CXX_CONFIG
+
+
+# _LT_FUNC_STRIPNAME_CNF
+# ----------------------
+# func_stripname_cnf prefix suffix name
+# strip PREFIX and SUFFIX off of NAME.
+# PREFIX and SUFFIX must not contain globbing or regex special
+# characters, hashes, percent signs, but SUFFIX may contain a leading
+# dot (in which case that matches only a dot).
+#
+# This function is identical to the (non-XSI) version of func_stripname,
+# except this one can be used by m4 code that may be executed by configure,
+# rather than the libtool script.
+m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl
+AC_REQUIRE([_LT_DECL_SED])
+AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])
+func_stripname_cnf ()
+{
+  case ${2} in
+  .*) func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%\\\\${2}\$%%"`;;
+  *)  func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%${2}\$%%"`;;
+  esac
+} # func_stripname_cnf
+])# _LT_FUNC_STRIPNAME_CNF
+
+# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME])
+# ---------------------------------
+# Figure out "hidden" library dependencies from verbose
+# compiler output when linking a shared library.
+# Parse the compiler output and extract the necessary
+# objects, libraries and library flags.
+m4_defun([_LT_SYS_HIDDEN_LIBDEPS],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl
+# Dependencies to place before and after the object being linked:
+_LT_TAGVAR(predep_objects, $1)=
+_LT_TAGVAR(postdep_objects, $1)=
+_LT_TAGVAR(predeps, $1)=
+_LT_TAGVAR(postdeps, $1)=
+_LT_TAGVAR(compiler_lib_search_path, $1)=
+
+dnl we can't use the lt_simple_compile_test_code here,
+dnl because it contains code intended for an executable,
+dnl not a library.  It's possible we should let each
+dnl tag define a new lt_????_link_test_code variable,
+dnl but it's only used here...
+m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF
+int a;
+void foo (void) { a = 0; }
+_LT_EOF
+], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF
+class Foo
+{
+public:
+  Foo (void) { a = 0; }
+private:
+  int a;
+};
+_LT_EOF
+], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF
+      subroutine foo
+      implicit none
+      integer*4 a
+      a=0
+      return
+      end
+_LT_EOF
+], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF
+      subroutine foo
+      implicit none
+      integer a
+      a=0
+      return
+      end
+_LT_EOF
+], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF
+public class foo {
+  private int a;
+  public void bar (void) {
+    a = 0;
+  }
+};
+_LT_EOF
+], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF
+package foo
+func foo() {
+}
+_LT_EOF
+])
+
+_lt_libdeps_save_CFLAGS=$CFLAGS
+case "$CC $CFLAGS " in #(
+*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;;
+*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;;
+*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;;
+esac
+
+dnl Parse the compiler output and extract the necessary
+dnl objects, libraries and library flags.
+if AC_TRY_EVAL(ac_compile); then
+  # Parse the compiler output and extract the necessary
+  # objects, libraries and library flags.
+
+  # Sentinel used to keep track of whether or not we are before
+  # the conftest object file.
+  pre_test_object_deps_done=no
+
+  for p in `eval "$output_verbose_link_cmd"`; do
+    case ${prev}${p} in
+
+    -L* | -R* | -l*)
+       # Some compilers place space between "-{L,R}" and the path.
+       # Remove the space.
+       if test $p = "-L" ||
+          test $p = "-R"; then
+	 prev=$p
+	 continue
+       fi
+
+       # Expand the sysroot to ease extracting the directories later.
+       if test -z "$prev"; then
+         case $p in
+         -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;;
+         -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;;
+         -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;;
+         esac
+       fi
+       case $p in
+       =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;;
+       esac
+       if test "$pre_test_object_deps_done" = no; then
+	 case ${prev} in
+	 -L | -R)
+	   # Internal compiler library paths should come after those
+	   # provided the user.  The postdeps already come after the
+	   # user supplied libs so there is no need to process them.
+	   if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then
+	     _LT_TAGVAR(compiler_lib_search_path, $1)="${prev}${p}"
+	   else
+	     _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} ${prev}${p}"
+	   fi
+	   ;;
+	 # The "-l" case would never come before the object being
+	 # linked, so don't bother handling this case.
+	 esac
+       else
+	 if test -z "$_LT_TAGVAR(postdeps, $1)"; then
+	   _LT_TAGVAR(postdeps, $1)="${prev}${p}"
+	 else
+	   _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} ${prev}${p}"
+	 fi
+       fi
+       prev=
+       ;;
+
+    *.lto.$objext) ;; # Ignore GCC LTO objects
+    *.$objext)
+       # This assumes that the test object file only shows up
+       # once in the compiler output.
+       if test "$p" = "conftest.$objext"; then
+	 pre_test_object_deps_done=yes
+	 continue
+       fi
+
+       if test "$pre_test_object_deps_done" = no; then
+	 if test -z "$_LT_TAGVAR(predep_objects, $1)"; then
+	   _LT_TAGVAR(predep_objects, $1)="$p"
+	 else
+	   _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p"
+	 fi
+       else
+	 if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then
+	   _LT_TAGVAR(postdep_objects, $1)="$p"
+	 else
+	   _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p"
+	 fi
+       fi
+       ;;
+
+    *) ;; # Ignore the rest.
+
+    esac
+  done
+
+  # Clean up.
+  rm -f a.out a.exe
+else
+  echo "libtool.m4: error: problem compiling $1 test program"
+fi
+
+$RM -f confest.$objext
+CFLAGS=$_lt_libdeps_save_CFLAGS
+
+# PORTME: override above test on systems where it is broken
+m4_if([$1], [CXX],
+[case $host_os in
+interix[[3-9]]*)
+  # Interix 3.5 installs completely hosed .la files for C++, so rather than
+  # hack all around it, let's just trust "g++" to DTRT.
+  _LT_TAGVAR(predep_objects,$1)=
+  _LT_TAGVAR(postdep_objects,$1)=
+  _LT_TAGVAR(postdeps,$1)=
+  ;;
+
+linux*)
+  case `$CC -V 2>&1 | sed 5q` in
+  *Sun\ C*)
+    # Sun C++ 5.9
+
+    # The more standards-conforming stlport4 library is
+    # incompatible with the Cstd library. Avoid specifying
+    # it if it's in CXXFLAGS. Ignore libCrun as
+    # -library=stlport4 depends on it.
+    case " $CXX $CXXFLAGS " in
+    *" -library=stlport4 "*)
+      solaris_use_stlport4=yes
+      ;;
+    esac
+
+    if test "$solaris_use_stlport4" != yes; then
+      _LT_TAGVAR(postdeps,$1)='-library=Cstd -library=Crun'
+    fi
+    ;;
+  esac
+  ;;
+
+solaris*)
+  case $cc_basename in
+  CC* | sunCC*)
+    # The more standards-conforming stlport4 library is
+    # incompatible with the Cstd library. Avoid specifying
+    # it if it's in CXXFLAGS. Ignore libCrun as
+    # -library=stlport4 depends on it.
+    case " $CXX $CXXFLAGS " in
+    *" -library=stlport4 "*)
+      solaris_use_stlport4=yes
+      ;;
+    esac
+
+    # Adding this requires a known-good setup of shared libraries for
+    # Sun compiler versions before 5.6, else PIC objects from an old
+    # archive will be linked into the output, leading to subtle bugs.
+    if test "$solaris_use_stlport4" != yes; then
+      _LT_TAGVAR(postdeps,$1)='-library=Cstd -library=Crun'
+    fi
+    ;;
+  esac
+  ;;
+esac
+])
+
+case " $_LT_TAGVAR(postdeps, $1) " in
+*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;;
+esac
+ _LT_TAGVAR(compiler_lib_search_dirs, $1)=
+if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then
+ _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | ${SED} -e 's! -L! !g' -e 's!^ !!'`
+fi
+_LT_TAGDECL([], [compiler_lib_search_dirs], [1],
+    [The directories searched by this compiler when creating a shared library])
+_LT_TAGDECL([], [predep_objects], [1],
+    [Dependencies to place before and after the objects being linked to
+    create a shared library])
+_LT_TAGDECL([], [postdep_objects], [1])
+_LT_TAGDECL([], [predeps], [1])
+_LT_TAGDECL([], [postdeps], [1])
+_LT_TAGDECL([], [compiler_lib_search_path], [1],
+    [The library search path used internally by the compiler when linking
+    a shared library])
+])# _LT_SYS_HIDDEN_LIBDEPS
+
+
+# _LT_LANG_F77_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for a Fortran 77 compiler are
+# suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_F77_CONFIG],
+[AC_LANG_PUSH(Fortran 77)
+if test -z "$F77" || test "X$F77" = "Xno"; then
+  _lt_disable_F77=yes
+fi
+
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for f77 test sources.
+ac_ext=f
+
+# Object file extension for compiled f77 test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the F77 compiler isn't working.  Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_disable_F77" != yes; then
+  # Code to be used in simple compile tests
+  lt_simple_compile_test_code="\
+      subroutine t
+      return
+      end
+"
+
+  # Code to be used in simple link tests
+  lt_simple_link_test_code="\
+      program t
+      end
+"
+
+  # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+  _LT_TAG_COMPILER
+
+  # save warnings/boilerplate of simple test code
+  _LT_COMPILER_BOILERPLATE
+  _LT_LINKER_BOILERPLATE
+
+  # Allow CC to be a program name with arguments.
+  lt_save_CC="$CC"
+  lt_save_GCC=$GCC
+  lt_save_CFLAGS=$CFLAGS
+  CC=${F77-"f77"}
+  CFLAGS=$FFLAGS
+  compiler=$CC
+  _LT_TAGVAR(compiler, $1)=$CC
+  _LT_CC_BASENAME([$compiler])
+  GCC=$G77
+  if test -n "$compiler"; then
+    AC_MSG_CHECKING([if libtool supports shared libraries])
+    AC_MSG_RESULT([$can_build_shared])
+
+    AC_MSG_CHECKING([whether to build shared libraries])
+    test "$can_build_shared" = "no" && enable_shared=no
+
+    # On AIX, shared libraries and static libraries use the same namespace, and
+    # are all built from PIC.
+    case $host_os in
+      aix3*)
+        test "$enable_shared" = yes && enable_static=no
+        if test -n "$RANLIB"; then
+          archive_cmds="$archive_cmds~\$RANLIB \$lib"
+          postinstall_cmds='$RANLIB $lib'
+        fi
+        ;;
+      aix[[4-9]]*)
+	if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+	  test "$enable_shared" = yes && enable_static=no
+	fi
+        ;;
+    esac
+    AC_MSG_RESULT([$enable_shared])
+
+    AC_MSG_CHECKING([whether to build static libraries])
+    # Make sure either enable_shared or enable_static is yes.
+    test "$enable_shared" = yes || enable_static=yes
+    AC_MSG_RESULT([$enable_static])
+
+    _LT_TAGVAR(GCC, $1)="$G77"
+    _LT_TAGVAR(LD, $1)="$LD"
+
+    ## CAVEAT EMPTOR:
+    ## There is no encapsulation within the following macros, do not change
+    ## the running order or otherwise move them around unless you know exactly
+    ## what you are doing...
+    _LT_COMPILER_PIC($1)
+    _LT_COMPILER_C_O($1)
+    _LT_COMPILER_FILE_LOCKS($1)
+    _LT_LINKER_SHLIBS($1)
+    _LT_SYS_DYNAMIC_LINKER($1)
+    _LT_LINKER_HARDCODE_LIBPATH($1)
+
+    _LT_CONFIG($1)
+  fi # test -n "$compiler"
+
+  GCC=$lt_save_GCC
+  CC="$lt_save_CC"
+  CFLAGS="$lt_save_CFLAGS"
+fi # test "$_lt_disable_F77" != yes
+
+AC_LANG_POP
+])# _LT_LANG_F77_CONFIG
+
+
+# _LT_LANG_FC_CONFIG([TAG])
+# -------------------------
+# Ensure that the configuration variables for a Fortran compiler are
+# suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_FC_CONFIG],
+[AC_LANG_PUSH(Fortran)
+
+if test -z "$FC" || test "X$FC" = "Xno"; then
+  _lt_disable_FC=yes
+fi
+
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for fc test sources.
+ac_ext=${ac_fc_srcext-f}
+
+# Object file extension for compiled fc test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the FC compiler isn't working.  Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_disable_FC" != yes; then
+  # Code to be used in simple compile tests
+  lt_simple_compile_test_code="\
+      subroutine t
+      return
+      end
+"
+
+  # Code to be used in simple link tests
+  lt_simple_link_test_code="\
+      program t
+      end
+"
+
+  # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+  _LT_TAG_COMPILER
+
+  # save warnings/boilerplate of simple test code
+  _LT_COMPILER_BOILERPLATE
+  _LT_LINKER_BOILERPLATE
+
+  # Allow CC to be a program name with arguments.
+  lt_save_CC="$CC"
+  lt_save_GCC=$GCC
+  lt_save_CFLAGS=$CFLAGS
+  CC=${FC-"f95"}
+  CFLAGS=$FCFLAGS
+  compiler=$CC
+  GCC=$ac_cv_fc_compiler_gnu
+
+  _LT_TAGVAR(compiler, $1)=$CC
+  _LT_CC_BASENAME([$compiler])
+
+  if test -n "$compiler"; then
+    AC_MSG_CHECKING([if libtool supports shared libraries])
+    AC_MSG_RESULT([$can_build_shared])
+
+    AC_MSG_CHECKING([whether to build shared libraries])
+    test "$can_build_shared" = "no" && enable_shared=no
+
+    # On AIX, shared libraries and static libraries use the same namespace, and
+    # are all built from PIC.
+    case $host_os in
+      aix3*)
+        test "$enable_shared" = yes && enable_static=no
+        if test -n "$RANLIB"; then
+          archive_cmds="$archive_cmds~\$RANLIB \$lib"
+          postinstall_cmds='$RANLIB $lib'
+        fi
+        ;;
+      aix[[4-9]]*)
+	if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+	  test "$enable_shared" = yes && enable_static=no
+	fi
+        ;;
+    esac
+    AC_MSG_RESULT([$enable_shared])
+
+    AC_MSG_CHECKING([whether to build static libraries])
+    # Make sure either enable_shared or enable_static is yes.
+    test "$enable_shared" = yes || enable_static=yes
+    AC_MSG_RESULT([$enable_static])
+
+    _LT_TAGVAR(GCC, $1)="$ac_cv_fc_compiler_gnu"
+    _LT_TAGVAR(LD, $1)="$LD"
+
+    ## CAVEAT EMPTOR:
+    ## There is no encapsulation within the following macros, do not change
+    ## the running order or otherwise move them around unless you know exactly
+    ## what you are doing...
+    _LT_SYS_HIDDEN_LIBDEPS($1)
+    _LT_COMPILER_PIC($1)
+    _LT_COMPILER_C_O($1)
+    _LT_COMPILER_FILE_LOCKS($1)
+    _LT_LINKER_SHLIBS($1)
+    _LT_SYS_DYNAMIC_LINKER($1)
+    _LT_LINKER_HARDCODE_LIBPATH($1)
+
+    _LT_CONFIG($1)
+  fi # test -n "$compiler"
+
+  GCC=$lt_save_GCC
+  CC=$lt_save_CC
+  CFLAGS=$lt_save_CFLAGS
+fi # test "$_lt_disable_FC" != yes
+
+AC_LANG_POP
+])# _LT_LANG_FC_CONFIG
+
+
+# _LT_LANG_GCJ_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for the GNU Java Compiler compiler
+# are suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_GCJ_CONFIG],
+[AC_REQUIRE([LT_PROG_GCJ])dnl
+AC_LANG_SAVE
+
+# Source file extension for Java test sources.
+ac_ext=java
+
+# Object file extension for compiled Java test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="class foo {}"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }'
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC=$CC
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=yes
+CC=${GCJ-"gcj"}
+CFLAGS=$GCJFLAGS
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_TAGVAR(LD, $1)="$LD"
+_LT_CC_BASENAME([$compiler])
+
+# GCJ did not exist at the time GCC didn't implicitly link libc in.
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+
+if test -n "$compiler"; then
+  _LT_COMPILER_NO_RTTI($1)
+  _LT_COMPILER_PIC($1)
+  _LT_COMPILER_C_O($1)
+  _LT_COMPILER_FILE_LOCKS($1)
+  _LT_LINKER_SHLIBS($1)
+  _LT_LINKER_HARDCODE_LIBPATH($1)
+
+  _LT_CONFIG($1)
+fi
+
+AC_LANG_RESTORE
+
+GCC=$lt_save_GCC
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_GCJ_CONFIG
+
+
+# _LT_LANG_GO_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for the GNU Go compiler
+# are suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_GO_CONFIG],
+[AC_REQUIRE([LT_PROG_GO])dnl
+AC_LANG_SAVE
+
+# Source file extension for Go test sources.
+ac_ext=go
+
+# Object file extension for compiled Go test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="package main; func main() { }"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='package main; func main() { }'
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC=$CC
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=yes
+CC=${GOC-"gccgo"}
+CFLAGS=$GOFLAGS
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_TAGVAR(LD, $1)="$LD"
+_LT_CC_BASENAME([$compiler])
+
+# Go did not exist at the time GCC didn't implicitly link libc in.
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+
+if test -n "$compiler"; then
+  _LT_COMPILER_NO_RTTI($1)
+  _LT_COMPILER_PIC($1)
+  _LT_COMPILER_C_O($1)
+  _LT_COMPILER_FILE_LOCKS($1)
+  _LT_LINKER_SHLIBS($1)
+  _LT_LINKER_HARDCODE_LIBPATH($1)
+
+  _LT_CONFIG($1)
+fi
+
+AC_LANG_RESTORE
+
+GCC=$lt_save_GCC
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_GO_CONFIG
+
+
+# _LT_LANG_RC_CONFIG([TAG])
+# -------------------------
+# Ensure that the configuration variables for the Windows resource compiler
+# are suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_RC_CONFIG],
+[AC_REQUIRE([LT_PROG_RC])dnl
+AC_LANG_SAVE
+
+# Source file extension for RC test sources.
+ac_ext=rc
+
+# Object file extension for compiled RC test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }'
+
+# Code to be used in simple link tests
+lt_simple_link_test_code="$lt_simple_compile_test_code"
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC="$CC"
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=
+CC=${RC-"windres"}
+CFLAGS=
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_CC_BASENAME([$compiler])
+_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes
+
+if test -n "$compiler"; then
+  :
+  _LT_CONFIG($1)
+fi
+
+GCC=$lt_save_GCC
+AC_LANG_RESTORE
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_RC_CONFIG
+
+
+# LT_PROG_GCJ
+# -----------
+AC_DEFUN([LT_PROG_GCJ],
+[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ],
+  [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ],
+    [AC_CHECK_TOOL(GCJ, gcj,)
+      test "x${GCJFLAGS+set}" = xset || GCJFLAGS="-g -O2"
+      AC_SUBST(GCJFLAGS)])])[]dnl
+])
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_GCJ], [])
+
+
+# LT_PROG_GO
+# ----------
+AC_DEFUN([LT_PROG_GO],
+[AC_CHECK_TOOL(GOC, gccgo,)
+])
+
+
+# LT_PROG_RC
+# ----------
+AC_DEFUN([LT_PROG_RC],
+[AC_CHECK_TOOL(RC, windres,)
+])
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_RC], [])
+
+
+# _LT_DECL_EGREP
+# --------------
+# If we don't have a new enough Autoconf to choose the best grep
+# available, choose the one first in the user's PATH.
+m4_defun([_LT_DECL_EGREP],
+[AC_REQUIRE([AC_PROG_EGREP])dnl
+AC_REQUIRE([AC_PROG_FGREP])dnl
+test -z "$GREP" && GREP=grep
+_LT_DECL([], [GREP], [1], [A grep program that handles long lines])
+_LT_DECL([], [EGREP], [1], [An ERE matcher])
+_LT_DECL([], [FGREP], [1], [A literal string matcher])
+dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too
+AC_SUBST([GREP])
+])
+
+
+# _LT_DECL_OBJDUMP
+# --------------
+# If we don't have a new enough Autoconf to choose the best objdump
+# available, choose the one first in the user's PATH.
+m4_defun([_LT_DECL_OBJDUMP],
+[AC_CHECK_TOOL(OBJDUMP, objdump, false)
+test -z "$OBJDUMP" && OBJDUMP=objdump
+_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper])
+AC_SUBST([OBJDUMP])
+])
+
+# _LT_DECL_DLLTOOL
+# ----------------
+# Ensure DLLTOOL variable is set.
+m4_defun([_LT_DECL_DLLTOOL],
+[AC_CHECK_TOOL(DLLTOOL, dlltool, false)
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+_LT_DECL([], [DLLTOOL], [1], [DLL creation program])
+AC_SUBST([DLLTOOL])
+])
+
+# _LT_DECL_SED
+# ------------
+# Check for a fully-functional sed program, that truncates
+# as few characters as possible.  Prefer GNU sed if found.
+m4_defun([_LT_DECL_SED],
+[AC_PROG_SED
+test -z "$SED" && SED=sed
+Xsed="$SED -e 1s/^X//"
+_LT_DECL([], [SED], [1], [A sed program that does not truncate output])
+_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"],
+    [Sed that helps us avoid accidentally triggering echo(1) options like -n])
+])# _LT_DECL_SED
+
+m4_ifndef([AC_PROG_SED], [
+# NOTE: This macro has been submitted for inclusion into   #
+#  GNU Autoconf as AC_PROG_SED.  When it is available in   #
+#  a released version of Autoconf we should remove this    #
+#  macro and use it instead.                               #
+
+m4_defun([AC_PROG_SED],
+[AC_MSG_CHECKING([for a sed that does not truncate output])
+AC_CACHE_VAL(lt_cv_path_SED,
+[# Loop through the user's path and test for sed and gsed.
+# Then use that list of sed's as ones to test for truncation.
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for lt_ac_prog in sed gsed; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then
+        lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext"
+      fi
+    done
+  done
+done
+IFS=$as_save_IFS
+lt_ac_max=0
+lt_ac_count=0
+# Add /usr/xpg4/bin/sed as it is typically found on Solaris
+# along with /bin/sed that truncates output.
+for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do
+  test ! -f $lt_ac_sed && continue
+  cat /dev/null > conftest.in
+  lt_ac_count=0
+  echo $ECHO_N "0123456789$ECHO_C" >conftest.in
+  # Check for GNU sed and select it if it is found.
+  if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then
+    lt_cv_path_SED=$lt_ac_sed
+    break
+  fi
+  while true; do
+    cat conftest.in conftest.in >conftest.tmp
+    mv conftest.tmp conftest.in
+    cp conftest.in conftest.nl
+    echo >>conftest.nl
+    $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break
+    cmp -s conftest.out conftest.nl || break
+    # 10000 chars as input seems more than enough
+    test $lt_ac_count -gt 10 && break
+    lt_ac_count=`expr $lt_ac_count + 1`
+    if test $lt_ac_count -gt $lt_ac_max; then
+      lt_ac_max=$lt_ac_count
+      lt_cv_path_SED=$lt_ac_sed
+    fi
+  done
+done
+])
+SED=$lt_cv_path_SED
+AC_SUBST([SED])
+AC_MSG_RESULT([$SED])
+])#AC_PROG_SED
+])#m4_ifndef
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_SED], [])
+
+
+# _LT_CHECK_SHELL_FEATURES
+# ------------------------
+# Find out whether the shell is Bourne or XSI compatible,
+# or has some other useful features.
+m4_defun([_LT_CHECK_SHELL_FEATURES],
+[AC_MSG_CHECKING([whether the shell understands some XSI constructs])
+# Try some XSI features
+xsi_shell=no
+( _lt_dummy="a/b/c"
+  test "${_lt_dummy##*/},${_lt_dummy%/*},${_lt_dummy#??}"${_lt_dummy%"$_lt_dummy"}, \
+      = c,a/b,b/c, \
+    && eval 'test $(( 1 + 1 )) -eq 2 \
+    && test "${#_lt_dummy}" -eq 5' ) >/dev/null 2>&1 \
+  && xsi_shell=yes
+AC_MSG_RESULT([$xsi_shell])
+_LT_CONFIG_LIBTOOL_INIT([xsi_shell='$xsi_shell'])
+
+AC_MSG_CHECKING([whether the shell understands "+="])
+lt_shell_append=no
+( foo=bar; set foo baz; eval "$[1]+=\$[2]" && test "$foo" = barbaz ) \
+    >/dev/null 2>&1 \
+  && lt_shell_append=yes
+AC_MSG_RESULT([$lt_shell_append])
+_LT_CONFIG_LIBTOOL_INIT([lt_shell_append='$lt_shell_append'])
+
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+  lt_unset=unset
+else
+  lt_unset=false
+fi
+_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl
+
+# test EBCDIC or ASCII
+case `echo X|tr X '\101'` in
+ A) # ASCII based system
+    # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr
+  lt_SP2NL='tr \040 \012'
+  lt_NL2SP='tr \015\012 \040\040'
+  ;;
+ *) # EBCDIC based system
+  lt_SP2NL='tr \100 \n'
+  lt_NL2SP='tr \r\n \100\100'
+  ;;
+esac
+_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl
+_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl
+])# _LT_CHECK_SHELL_FEATURES
+
+
+# _LT_PROG_FUNCTION_REPLACE (FUNCNAME, REPLACEMENT-BODY)
+# ------------------------------------------------------
+# In `$cfgfile', look for function FUNCNAME delimited by `^FUNCNAME ()$' and
+# '^} FUNCNAME ', and replace its body with REPLACEMENT-BODY.
+m4_defun([_LT_PROG_FUNCTION_REPLACE],
+[dnl {
+sed -e '/^$1 ()$/,/^} # $1 /c\
+$1 ()\
+{\
+m4_bpatsubsts([$2], [$], [\\], [^\([	 ]\)], [\\\1])
+} # Extended-shell $1 implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+])
+
+
+# _LT_PROG_REPLACE_SHELLFNS
+# -------------------------
+# Replace existing portable implementations of several shell functions with
+# equivalent extended shell implementations where those features are available..
+m4_defun([_LT_PROG_REPLACE_SHELLFNS],
+[if test x"$xsi_shell" = xyes; then
+  _LT_PROG_FUNCTION_REPLACE([func_dirname], [dnl
+    case ${1} in
+      */*) func_dirname_result="${1%/*}${2}" ;;
+      *  ) func_dirname_result="${3}" ;;
+    esac])
+
+  _LT_PROG_FUNCTION_REPLACE([func_basename], [dnl
+    func_basename_result="${1##*/}"])
+
+  _LT_PROG_FUNCTION_REPLACE([func_dirname_and_basename], [dnl
+    case ${1} in
+      */*) func_dirname_result="${1%/*}${2}" ;;
+      *  ) func_dirname_result="${3}" ;;
+    esac
+    func_basename_result="${1##*/}"])
+
+  _LT_PROG_FUNCTION_REPLACE([func_stripname], [dnl
+    # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are
+    # positional parameters, so assign one to ordinary parameter first.
+    func_stripname_result=${3}
+    func_stripname_result=${func_stripname_result#"${1}"}
+    func_stripname_result=${func_stripname_result%"${2}"}])
+
+  _LT_PROG_FUNCTION_REPLACE([func_split_long_opt], [dnl
+    func_split_long_opt_name=${1%%=*}
+    func_split_long_opt_arg=${1#*=}])
+
+  _LT_PROG_FUNCTION_REPLACE([func_split_short_opt], [dnl
+    func_split_short_opt_arg=${1#??}
+    func_split_short_opt_name=${1%"$func_split_short_opt_arg"}])
+
+  _LT_PROG_FUNCTION_REPLACE([func_lo2o], [dnl
+    case ${1} in
+      *.lo) func_lo2o_result=${1%.lo}.${objext} ;;
+      *)    func_lo2o_result=${1} ;;
+    esac])
+
+  _LT_PROG_FUNCTION_REPLACE([func_xform], [    func_xform_result=${1%.*}.lo])
+
+  _LT_PROG_FUNCTION_REPLACE([func_arith], [    func_arith_result=$(( $[*] ))])
+
+  _LT_PROG_FUNCTION_REPLACE([func_len], [    func_len_result=${#1}])
+fi
+
+if test x"$lt_shell_append" = xyes; then
+  _LT_PROG_FUNCTION_REPLACE([func_append], [    eval "${1}+=\\${2}"])
+
+  _LT_PROG_FUNCTION_REPLACE([func_append_quoted], [dnl
+    func_quote_for_eval "${2}"
+dnl m4 expansion turns \\\\ into \\, and then the shell eval turns that into \
+    eval "${1}+=\\\\ \\$func_quote_for_eval_result"])
+
+  # Save a `func_append' function call where possible by direct use of '+='
+  sed -e 's%func_append \([[a-zA-Z_]]\{1,\}\) "%\1+="%g' $cfgfile > $cfgfile.tmp \
+    && mv -f "$cfgfile.tmp" "$cfgfile" \
+      || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+  test 0 -eq $? || _lt_function_replace_fail=:
+else
+  # Save a `func_append' function call even when '+=' is not available
+  sed -e 's%func_append \([[a-zA-Z_]]\{1,\}\) "%\1="$\1%g' $cfgfile > $cfgfile.tmp \
+    && mv -f "$cfgfile.tmp" "$cfgfile" \
+      || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+  test 0 -eq $? || _lt_function_replace_fail=:
+fi
+
+if test x"$_lt_function_replace_fail" = x":"; then
+  AC_MSG_WARN([Unable to substitute extended shell functions in $ofile])
+fi
+])
+
+# _LT_PATH_CONVERSION_FUNCTIONS
+# -----------------------------
+# Determine which file name conversion functions should be used by
+# func_to_host_file (and, implicitly, by func_to_host_path).  These are needed
+# for certain cross-compile configurations and native mingw.
+m4_defun([_LT_PATH_CONVERSION_FUNCTIONS],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+AC_MSG_CHECKING([how to convert $build file names to $host format])
+AC_CACHE_VAL(lt_cv_to_host_file_cmd,
+[case $host in
+  *-*-mingw* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32
+        ;;
+      *-*-cygwin* )
+        lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32
+        ;;
+      * ) # otherwise, assume *nix
+        lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32
+        ;;
+    esac
+    ;;
+  *-*-cygwin* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin
+        ;;
+      *-*-cygwin* )
+        lt_cv_to_host_file_cmd=func_convert_file_noop
+        ;;
+      * ) # otherwise, assume *nix
+        lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin
+        ;;
+    esac
+    ;;
+  * ) # unhandled hosts (and "normal" native builds)
+    lt_cv_to_host_file_cmd=func_convert_file_noop
+    ;;
+esac
+])
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+AC_MSG_RESULT([$lt_cv_to_host_file_cmd])
+_LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd],
+         [0], [convert $build file names to $host format])dnl
+
+AC_MSG_CHECKING([how to convert $build file names to toolchain format])
+AC_CACHE_VAL(lt_cv_to_tool_file_cmd,
+[#assume ordinary cross tools, or native build.
+lt_cv_to_tool_file_cmd=func_convert_file_noop
+case $host in
+  *-*-mingw* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32
+        ;;
+    esac
+    ;;
+esac
+])
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+AC_MSG_RESULT([$lt_cv_to_tool_file_cmd])
+_LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd],
+         [0], [convert $build files to toolchain format])dnl
+])# _LT_PATH_CONVERSION_FUNCTIONS
+
+# Helper functions for option handling.                    -*- Autoconf -*-
+#
+#   Copyright (C) 2004, 2005, 2007, 2008, 2009 Free Software Foundation,
+#   Inc.
+#   Written by Gary V. Vaughan, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 7 ltoptions.m4
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])])
+
+
+# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME)
+# ------------------------------------------
+m4_define([_LT_MANGLE_OPTION],
+[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])])
+
+
+# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME)
+# ---------------------------------------
+# Set option OPTION-NAME for macro MACRO-NAME, and if there is a
+# matching handler defined, dispatch to it.  Other OPTION-NAMEs are
+# saved as a flag.
+m4_define([_LT_SET_OPTION],
+[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl
+m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]),
+        _LT_MANGLE_DEFUN([$1], [$2]),
+    [m4_warning([Unknown $1 option `$2'])])[]dnl
+])
+
+
+# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET])
+# ------------------------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+m4_define([_LT_IF_OPTION],
+[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])])
+
+
+# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET)
+# -------------------------------------------------------
+# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME
+# are set.
+m4_define([_LT_UNLESS_OPTIONS],
+[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
+	    [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option),
+		      [m4_define([$0_found])])])[]dnl
+m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3
+])[]dnl
+])
+
+
+# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST)
+# ----------------------------------------
+# OPTION-LIST is a space-separated list of Libtool options associated
+# with MACRO-NAME.  If any OPTION has a matching handler declared with
+# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about
+# the unknown option and exit.
+m4_defun([_LT_SET_OPTIONS],
+[# Set options
+m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
+    [_LT_SET_OPTION([$1], _LT_Option)])
+
+m4_if([$1],[LT_INIT],[
+  dnl
+  dnl Simply set some default values (i.e off) if boolean options were not
+  dnl specified:
+  _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no
+  ])
+  _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no
+  ])
+  dnl
+  dnl If no reference was made to various pairs of opposing options, then
+  dnl we run the default mode handler for the pair.  For example, if neither
+  dnl `shared' nor `disable-shared' was passed, we enable building of shared
+  dnl archives by default:
+  _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED])
+  _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC])
+  _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC])
+  _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install],
+  		   [_LT_ENABLE_FAST_INSTALL])
+  ])
+])# _LT_SET_OPTIONS
+
+
+
+# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME)
+# -----------------------------------------
+m4_define([_LT_MANGLE_DEFUN],
+[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])])
+
+
+# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE)
+# -----------------------------------------------
+m4_define([LT_OPTION_DEFINE],
+[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl
+])# LT_OPTION_DEFINE
+
+
+# dlopen
+# ------
+LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes
+])
+
+AU_DEFUN([AC_LIBTOOL_DLOPEN],
+[_LT_SET_OPTION([LT_INIT], [dlopen])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the `dlopen' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], [])
+
+
+# win32-dll
+# ---------
+# Declare package support for building win32 dll's.
+LT_OPTION_DEFINE([LT_INIT], [win32-dll],
+[enable_win32_dll=yes
+
+case $host in
+*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*)
+  AC_CHECK_TOOL(AS, as, false)
+  AC_CHECK_TOOL(DLLTOOL, dlltool, false)
+  AC_CHECK_TOOL(OBJDUMP, objdump, false)
+  ;;
+esac
+
+test -z "$AS" && AS=as
+_LT_DECL([], [AS],      [1], [Assembler program])dnl
+
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl
+
+test -z "$OBJDUMP" && OBJDUMP=objdump
+_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl
+])# win32-dll
+
+AU_DEFUN([AC_LIBTOOL_WIN32_DLL],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+_LT_SET_OPTION([LT_INIT], [win32-dll])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the `win32-dll' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], [])
+
+
+# _LT_ENABLE_SHARED([DEFAULT])
+# ----------------------------
+# implement the --enable-shared flag, and supports the `shared' and
+# `disable-shared' LT_INIT options.
+# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.
+m4_define([_LT_ENABLE_SHARED],
+[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([shared],
+    [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@],
+	[build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])],
+    [p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_shared=yes ;;
+    no) enable_shared=no ;;
+    *)
+      enable_shared=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+	IFS="$lt_save_ifs"
+	if test "X$pkg" = "X$p"; then
+	  enable_shared=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [enable_shared=]_LT_ENABLE_SHARED_DEFAULT)
+
+    _LT_DECL([build_libtool_libs], [enable_shared], [0],
+	[Whether or not to build shared libraries])
+])# _LT_ENABLE_SHARED
+
+LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])])
+
+# Old names:
+AC_DEFUN([AC_ENABLE_SHARED],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared])
+])
+
+AC_DEFUN([AC_DISABLE_SHARED],
+[_LT_SET_OPTION([LT_INIT], [disable-shared])
+])
+
+AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)])
+AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_ENABLE_SHARED], [])
+dnl AC_DEFUN([AM_DISABLE_SHARED], [])
+
+
+
+# _LT_ENABLE_STATIC([DEFAULT])
+# ----------------------------
+# implement the --enable-static flag, and support the `static' and
+# `disable-static' LT_INIT options.
+# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.
+m4_define([_LT_ENABLE_STATIC],
+[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([static],
+    [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@],
+	[build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])],
+    [p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_static=yes ;;
+    no) enable_static=no ;;
+    *)
+     enable_static=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+	IFS="$lt_save_ifs"
+	if test "X$pkg" = "X$p"; then
+	  enable_static=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [enable_static=]_LT_ENABLE_STATIC_DEFAULT)
+
+    _LT_DECL([build_old_libs], [enable_static], [0],
+	[Whether or not to build static libraries])
+])# _LT_ENABLE_STATIC
+
+LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])])
+
+# Old names:
+AC_DEFUN([AC_ENABLE_STATIC],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static])
+])
+
+AC_DEFUN([AC_DISABLE_STATIC],
+[_LT_SET_OPTION([LT_INIT], [disable-static])
+])
+
+AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)])
+AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_ENABLE_STATIC], [])
+dnl AC_DEFUN([AM_DISABLE_STATIC], [])
+
+
+
+# _LT_ENABLE_FAST_INSTALL([DEFAULT])
+# ----------------------------------
+# implement the --enable-fast-install flag, and support the `fast-install'
+# and `disable-fast-install' LT_INIT options.
+# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.
+m4_define([_LT_ENABLE_FAST_INSTALL],
+[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([fast-install],
+    [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@],
+    [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])],
+    [p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_fast_install=yes ;;
+    no) enable_fast_install=no ;;
+    *)
+      enable_fast_install=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+	IFS="$lt_save_ifs"
+	if test "X$pkg" = "X$p"; then
+	  enable_fast_install=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT)
+
+_LT_DECL([fast_install], [enable_fast_install], [0],
+	 [Whether or not to optimize for fast installation])dnl
+])# _LT_ENABLE_FAST_INSTALL
+
+LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])])
+
+# Old names:
+AU_DEFUN([AC_ENABLE_FAST_INSTALL],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you put
+the `fast-install' option into LT_INIT's first parameter.])
+])
+
+AU_DEFUN([AC_DISABLE_FAST_INSTALL],
+[_LT_SET_OPTION([LT_INIT], [disable-fast-install])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you put
+the `disable-fast-install' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], [])
+dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], [])
+
+
+# _LT_WITH_PIC([MODE])
+# --------------------
+# implement the --with-pic flag, and support the `pic-only' and `no-pic'
+# LT_INIT options.
+# MODE is either `yes' or `no'.  If omitted, it defaults to `both'.
+m4_define([_LT_WITH_PIC],
+[AC_ARG_WITH([pic],
+    [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@],
+	[try to use only PIC/non-PIC objects @<:@default=use both@:>@])],
+    [lt_p=${PACKAGE-default}
+    case $withval in
+    yes|no) pic_mode=$withval ;;
+    *)
+      pic_mode=default
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for lt_pkg in $withval; do
+	IFS="$lt_save_ifs"
+	if test "X$lt_pkg" = "X$lt_p"; then
+	  pic_mode=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [pic_mode=default])
+
+test -z "$pic_mode" && pic_mode=m4_default([$1], [default])
+
+_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl
+])# _LT_WITH_PIC
+
+LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])])
+LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])])
+
+# Old name:
+AU_DEFUN([AC_LIBTOOL_PICMODE],
+[_LT_SET_OPTION([LT_INIT], [pic-only])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the `pic-only' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_PICMODE], [])
+
+
+m4_define([_LTDL_MODE], [])
+LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive],
+		 [m4_define([_LTDL_MODE], [nonrecursive])])
+LT_OPTION_DEFINE([LTDL_INIT], [recursive],
+		 [m4_define([_LTDL_MODE], [recursive])])
+LT_OPTION_DEFINE([LTDL_INIT], [subproject],
+		 [m4_define([_LTDL_MODE], [subproject])])
+
+m4_define([_LTDL_TYPE], [])
+LT_OPTION_DEFINE([LTDL_INIT], [installable],
+		 [m4_define([_LTDL_TYPE], [installable])])
+LT_OPTION_DEFINE([LTDL_INIT], [convenience],
+		 [m4_define([_LTDL_TYPE], [convenience])])
+
+# ltsugar.m4 -- libtool m4 base layer.                         -*-Autoconf-*-
+#
+# Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc.
+# Written by Gary V. Vaughan, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 6 ltsugar.m4
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])])
+
+
+# lt_join(SEP, ARG1, [ARG2...])
+# -----------------------------
+# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their
+# associated separator.
+# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier
+# versions in m4sugar had bugs.
+m4_define([lt_join],
+[m4_if([$#], [1], [],
+       [$#], [2], [[$2]],
+       [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])])
+m4_define([_lt_join],
+[m4_if([$#$2], [2], [],
+       [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])])
+
+
+# lt_car(LIST)
+# lt_cdr(LIST)
+# ------------
+# Manipulate m4 lists.
+# These macros are necessary as long as will still need to support
+# Autoconf-2.59 which quotes differently.
+m4_define([lt_car], [[$1]])
+m4_define([lt_cdr],
+[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])],
+       [$#], 1, [],
+       [m4_dquote(m4_shift($@))])])
+m4_define([lt_unquote], $1)
+
+
+# lt_append(MACRO-NAME, STRING, [SEPARATOR])
+# ------------------------------------------
+# Redefine MACRO-NAME to hold its former content plus `SEPARATOR'`STRING'.
+# Note that neither SEPARATOR nor STRING are expanded; they are appended
+# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked).
+# No SEPARATOR is output if MACRO-NAME was previously undefined (different
+# than defined and empty).
+#
+# This macro is needed until we can rely on Autoconf 2.62, since earlier
+# versions of m4sugar mistakenly expanded SEPARATOR but not STRING.
+m4_define([lt_append],
+[m4_define([$1],
+	   m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])])
+
+
+
+# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...])
+# ----------------------------------------------------------
+# Produce a SEP delimited list of all paired combinations of elements of
+# PREFIX-LIST with SUFFIX1 through SUFFIXn.  Each element of the list
+# has the form PREFIXmINFIXSUFFIXn.
+# Needed until we can rely on m4_combine added in Autoconf 2.62.
+m4_define([lt_combine],
+[m4_if(m4_eval([$# > 3]), [1],
+       [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl
+[[m4_foreach([_Lt_prefix], [$2],
+	     [m4_foreach([_Lt_suffix],
+		]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[,
+	[_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])])
+
+
+# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ])
+# -----------------------------------------------------------------------
+# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited
+# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ.
+m4_define([lt_if_append_uniq],
+[m4_ifdef([$1],
+	  [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1],
+		 [lt_append([$1], [$2], [$3])$4],
+		 [$5])],
+	  [lt_append([$1], [$2], [$3])$4])])
+
+
+# lt_dict_add(DICT, KEY, VALUE)
+# -----------------------------
+m4_define([lt_dict_add],
+[m4_define([$1($2)], [$3])])
+
+
+# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE)
+# --------------------------------------------
+m4_define([lt_dict_add_subkey],
+[m4_define([$1($2:$3)], [$4])])
+
+
+# lt_dict_fetch(DICT, KEY, [SUBKEY])
+# ----------------------------------
+m4_define([lt_dict_fetch],
+[m4_ifval([$3],
+	m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]),
+    m4_ifdef([$1($2)], [m4_defn([$1($2)])]))])
+
+
+# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE])
+# -----------------------------------------------------------------
+m4_define([lt_if_dict_fetch],
+[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4],
+	[$5],
+    [$6])])
+
+
+# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...])
+# --------------------------------------------------------------
+m4_define([lt_dict_filter],
+[m4_if([$5], [], [],
+  [lt_join(m4_quote(m4_default([$4], [[, ]])),
+           lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]),
+		      [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl
+])
+
+# ltversion.m4 -- version numbers			-*- Autoconf -*-
+#
+#   Copyright (C) 2004 Free Software Foundation, Inc.
+#   Written by Scott James Remnant, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# @configure_input@
+
+# serial 3337 ltversion.m4
+# This file is part of GNU Libtool
+
+m4_define([LT_PACKAGE_VERSION], [2.4.2])
+m4_define([LT_PACKAGE_REVISION], [1.3337])
+
+AC_DEFUN([LTVERSION_VERSION],
+[macro_version='2.4.2'
+macro_revision='1.3337'
+_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?])
+_LT_DECL(, macro_revision, 0)
+])
+
+# lt~obsolete.m4 -- aclocal satisfying obsolete definitions.    -*-Autoconf-*-
+#
+#   Copyright (C) 2004, 2005, 2007, 2009 Free Software Foundation, Inc.
+#   Written by Scott James Remnant, 2004.
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 5 lt~obsolete.m4
+
+# These exist entirely to fool aclocal when bootstrapping libtool.
+#
+# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN)
+# which have later been changed to m4_define as they aren't part of the
+# exported API, or moved to Autoconf or Automake where they belong.
+#
+# The trouble is, aclocal is a bit thick.  It'll see the old AC_DEFUN
+# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us
+# using a macro with the same name in our local m4/libtool.m4 it'll
+# pull the old libtool.m4 in (it doesn't see our shiny new m4_define
+# and doesn't know about Autoconf macros at all.)
+#
+# So we provide this file, which has a silly filename so it's always
+# included after everything else.  This provides aclocal with the
+# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything
+# because those macros already exist, or will be overwritten later.
+# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. 
+#
+# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here.
+# Yes, that means every name once taken will need to remain here until
+# we give up compatibility with versions before 1.7, at which point
+# we need to keep only those names which we still refer to.
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])])
+
+m4_ifndef([AC_LIBTOOL_LINKER_OPTION],	[AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])])
+m4_ifndef([AC_PROG_EGREP],		[AC_DEFUN([AC_PROG_EGREP])])
+m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH],	[AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])])
+m4_ifndef([_LT_AC_SHELL_INIT],		[AC_DEFUN([_LT_AC_SHELL_INIT])])
+m4_ifndef([_LT_AC_SYS_LIBPATH_AIX],	[AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])])
+m4_ifndef([_LT_PROG_LTMAIN],		[AC_DEFUN([_LT_PROG_LTMAIN])])
+m4_ifndef([_LT_AC_TAGVAR],		[AC_DEFUN([_LT_AC_TAGVAR])])
+m4_ifndef([AC_LTDL_ENABLE_INSTALL],	[AC_DEFUN([AC_LTDL_ENABLE_INSTALL])])
+m4_ifndef([AC_LTDL_PREOPEN],		[AC_DEFUN([AC_LTDL_PREOPEN])])
+m4_ifndef([_LT_AC_SYS_COMPILER],	[AC_DEFUN([_LT_AC_SYS_COMPILER])])
+m4_ifndef([_LT_AC_LOCK],		[AC_DEFUN([_LT_AC_LOCK])])
+m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE],	[AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])])
+m4_ifndef([_LT_AC_TRY_DLOPEN_SELF],	[AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])])
+m4_ifndef([AC_LIBTOOL_PROG_CC_C_O],	[AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])])
+m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])])
+m4_ifndef([AC_LIBTOOL_OBJDIR],		[AC_DEFUN([AC_LIBTOOL_OBJDIR])])
+m4_ifndef([AC_LTDL_OBJDIR],		[AC_DEFUN([AC_LTDL_OBJDIR])])
+m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])])
+m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP],	[AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])])
+m4_ifndef([AC_PATH_MAGIC],		[AC_DEFUN([AC_PATH_MAGIC])])
+m4_ifndef([AC_PROG_LD_GNU],		[AC_DEFUN([AC_PROG_LD_GNU])])
+m4_ifndef([AC_PROG_LD_RELOAD_FLAG],	[AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])])
+m4_ifndef([AC_DEPLIBS_CHECK_METHOD],	[AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])])
+m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])])
+m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])])
+m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])])
+m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS],	[AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])])
+m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP],	[AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])])
+m4_ifndef([LT_AC_PROG_EGREP],		[AC_DEFUN([LT_AC_PROG_EGREP])])
+m4_ifndef([LT_AC_PROG_SED],		[AC_DEFUN([LT_AC_PROG_SED])])
+m4_ifndef([_LT_CC_BASENAME],		[AC_DEFUN([_LT_CC_BASENAME])])
+m4_ifndef([_LT_COMPILER_BOILERPLATE],	[AC_DEFUN([_LT_COMPILER_BOILERPLATE])])
+m4_ifndef([_LT_LINKER_BOILERPLATE],	[AC_DEFUN([_LT_LINKER_BOILERPLATE])])
+m4_ifndef([_AC_PROG_LIBTOOL],		[AC_DEFUN([_AC_PROG_LIBTOOL])])
+m4_ifndef([AC_LIBTOOL_SETUP],		[AC_DEFUN([AC_LIBTOOL_SETUP])])
+m4_ifndef([_LT_AC_CHECK_DLFCN],		[AC_DEFUN([_LT_AC_CHECK_DLFCN])])
+m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER],	[AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])])
+m4_ifndef([_LT_AC_TAGCONFIG],		[AC_DEFUN([_LT_AC_TAGCONFIG])])
+m4_ifndef([AC_DISABLE_FAST_INSTALL],	[AC_DEFUN([AC_DISABLE_FAST_INSTALL])])
+m4_ifndef([_LT_AC_LANG_CXX],		[AC_DEFUN([_LT_AC_LANG_CXX])])
+m4_ifndef([_LT_AC_LANG_F77],		[AC_DEFUN([_LT_AC_LANG_F77])])
+m4_ifndef([_LT_AC_LANG_GCJ],		[AC_DEFUN([_LT_AC_LANG_GCJ])])
+m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG],	[AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])])
+m4_ifndef([_LT_AC_LANG_C_CONFIG],	[AC_DEFUN([_LT_AC_LANG_C_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG],	[AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])])
+m4_ifndef([_LT_AC_LANG_CXX_CONFIG],	[AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG],	[AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])])
+m4_ifndef([_LT_AC_LANG_F77_CONFIG],	[AC_DEFUN([_LT_AC_LANG_F77_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG],	[AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])])
+m4_ifndef([_LT_AC_LANG_GCJ_CONFIG],	[AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG],	[AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])])
+m4_ifndef([_LT_AC_LANG_RC_CONFIG],	[AC_DEFUN([_LT_AC_LANG_RC_CONFIG])])
+m4_ifndef([AC_LIBTOOL_CONFIG],		[AC_DEFUN([AC_LIBTOOL_CONFIG])])
+m4_ifndef([_LT_AC_FILE_LTDLL_C],	[AC_DEFUN([_LT_AC_FILE_LTDLL_C])])
+m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS],	[AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])])
+m4_ifndef([_LT_AC_PROG_CXXCPP],		[AC_DEFUN([_LT_AC_PROG_CXXCPP])])
+m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS],	[AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])])
+m4_ifndef([_LT_PROG_ECHO_BACKSLASH],	[AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])])
+m4_ifndef([_LT_PROG_F77],		[AC_DEFUN([_LT_PROG_F77])])
+m4_ifndef([_LT_PROG_FC],		[AC_DEFUN([_LT_PROG_FC])])
+m4_ifndef([_LT_PROG_CXX],		[AC_DEFUN([_LT_PROG_CXX])])
+
+# -*- mode: m4 -*-
+# Starlink M4 macros for autoconf
+# original starconf.m4, installed by starconf 1.3, rnum=1003000
+# DO NOT EDIT: it may be overwritten when starconf is next run
+
+#  Copyright:
+#     Copyright (C) 2003-2005 Council for the Central Laboratory of the
+#     Research Councils
+#
+#  Licence:
+#     This program is free software; you can redistribute it and/or
+#     modify it under the terms of the GNU General Public Licence as
+#     published by the Free Software Foundation; either version 2 of
+#     the Licence, or (at your option) any later version.
+#
+#     This program is distributed in the hope that it will be
+#     useful,but WITHOUT ANY WARRANTY; without even the implied
+#     warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+#     PURPOSE. See the GNU General Public Licence for more details.
+#
+#     You should have received a copy of the GNU General Public Licence
+#     along with this program; if not, write to the Free Software
+#     Foundation, Inc., 51 Franklin Street,Fifth Floor, Boston, MA
+#     02110-1301, USA
+
+# STAR_DEFAULTS(options='')
+# -------------------------
+# Defaults for Starlink configure.ac files.  The optional OPTIONS
+# argument holds a space-separated list of option keywords, of which
+# the only ones at present are `per-package-dirs', which causes
+# applications and help to be installed in a package-specific
+# directory, and 'docs-only', which indicates that the component contains
+# only documentation.
+#
+# Certain features of this macro are documented in SSN/78, in particular
+#   - Sets STARLINK
+#   - Sets AM_FCFLAGS, AM_FFLAGS, AM_CFLAGS, AM_LDFLAGS to appropriate values
+#   - Sets PACKAGE_VERSION_{MAJOR,MINOR,RELEASE,INTEGER}
+# The behaviour of these should not be changed without changing the
+# documentation, or without due consideration of the packages which use
+# the earlier behaviour.  Everything else is, in principle, private
+# (not that that's going to stop folk).
+AC_DEFUN([STAR_DEFAULTS],
+[##
+m4_ifval([$1],
+         [AC_FOREACH([Option], [$1],
+                     [m4_case(Option,
+                              [per-package-dirs], [_star_per_package_dirs=:],
+                              [docs-only], [_star_docs_only=:],
+                              [AC_FATAL([$0: unrecognised option $1])])
+                      ])],
+         [])
+
+m4_define([per_dir_PREFIX],   [m4_ifdef([OVERRIDE_PREFIX],
+                                        [OVERRIDE_PREFIX],
+                                        [/stardev/git/starlink/star])])
+m4_define([per_dir_STARLINK], [m4_ifdef([OVERRIDE_STARLINK],
+                                        [OVERRIDE_STARLINK],
+                                        [/stardev/git/starlink/star])])
+
+test -n "$_star_per_package_dirs" || _star_per_package_dirs=false
+test -n "$_star_docs_only"        || _star_docs_only=false
+
+
+# Ensure that STARLINK has a value, defaulting to
+# /stardev/git/starlink/star.  Note that this directory may be
+# different from /star, and reflects the value of
+# STARCONF_DEFAULT_STARLINK that the `starconf' package was configured
+# with before its installation. 
+#
+# We use $STARLINK as the location of any other Starlink tools we need
+# to use during the building of our packages, and for the location of
+# any manifests we need to check.  It is permissable for it to be
+# different from $(prefix): this is partly because we have no way of
+# enforcing that the two be the same, since the user can set
+# prefix=xxx on the `make install' command line, and partly so that it
+# is possible to make a test version of a new package, using tools
+# from an old installation, but installing in a new place.
+#
+# However, we install software in /stardev/git/starlink/star by
+# default.  This is so even if $STARLINK and STARCONF_DEFAULT_STARLINK
+# are different, because in this case we are planning to use a
+# previous installation in $STARLINK or $STARCONF_DEFAULT_STARLINK,
+# but install the newly built tool elsewhere. 
+#
+# In most cases, including the most important case where we are
+# building the tree from scratch, in a checked out directory,
+# STARLINK, STARCONF_DEFAULT_STARLINK and STARCONF_DEFAULT_PREFIX will
+# all be the same.  That's OK because a separate aspect of the build
+# process, respecting the various dependencies expresses in source
+# directories, ensures that we don't use (and install) any Starlink
+# tools in one component before that component has been build and
+# installed. 
+
+AC_PREFIX_DEFAULT(per_dir_PREFIX)dnl
+
+test -n "$STARLINK" || STARLINK=per_dir_STARLINK
+
+# Handle the --with-starlink option.  If --with-starlink is present
+# with no argument (the default), we do nothing as this simply
+# indicates that this is part of a Starlink tree.  If it has an
+# argument, then this overrides the location of the Starlink tree.
+# Option --without-starlink or --with-starlink=no indicates that this
+# is being built _not_ as part of a Starlink build (that is, it's
+# being distributed as something other than a Starlink package).  In
+# this case, the variable STARLINK is unset.
+AC_ARG_WITH(starlink,
+            AS_HELP_STRING([--with-starlink],
+                           [Starlink tree to use (default ${STARLINK:=per_dir_STARLINK})]),
+            [if test -z "$withval" -o "$withval" = yes; then
+                 : nothing needs to be done
+             elif test "X$withval" = Xno; then
+                 unset STARLINK
+             elif test -d "$withval"; then
+                 STARLINK="$withval"
+             else
+                 AC_MSG_WARN([--with-starlink given nonexistent directory; ignored: using default $STARLINK instead])
+             fi])
+if test -n "$STARLINK"; then
+    AC_MSG_NOTICE([Starlink tree located at $STARLINK])
+else
+    AC_MSG_NOTICE([Not being built as part of the Starlink tree])
+fi
+
+# Handle --without-stardocs.  Don't build and install documentation.
+# Default is --with-stardocs.
+_star_build_docs=:
+AC_ARG_WITH(stardocs,
+            AS_HELP_STRING([--without-stardocs],
+                           [Do not install built documentation (default --with)]),
+            [if test -z "$withval"; then
+                 _star_build_docs=: # default
+             elif test "X$withval" = Xno; then
+                 _star_build_docs=false
+             elif test "X$withval" = Xyes; then
+                 _star_build_docs=:
+             else
+                 AC_MSG_WARN([bad arg to --with-stardocs: using yes])
+                 _star_build_docs=:
+             fi])
+
+if $_star_docs_only; then
+    if $_star_build_docs; then
+        : OK
+    else
+        AC_MSG_WARN([Building without documentation in a docs-only directory])
+    fi
+fi
+
+# Everything depends on where /star is.  Declare STARLINK as a
+# `precious variable'.  Amongst other things, this will make
+# ./configure squeal if the package is re-configured with an
+# inconsistent value of this variable.
+AC_ARG_VAR(STARLINK, [Location of a current Starlink tree (used if necessary)])dnl
+
+# AC_SUBST the STARLINK variable.  Macro AC_ARG_VAR does this anyway,
+# but automake doesn't know that (in 1.6 at least): however any
+# variable that automake finds has been AC_SUBSTed, it includes in
+# Makefile.in, and we need that.
+AC_SUBST(STARLINK)
+
+# Use the above information: $STARLINK indicates a preexisting
+# Starlink tree.
+#
+# Avoid doing anything if $STARLINK was unset above.
+#
+# Add library search paths using STAR_LDFLAGS.  Do it this way, rather than
+# by defining LIBS (which is also a non-user variable): (a) these are
+# really options to the linker, rather than adjustments to the set of
+# libraries, so this makes sense; also (b) adding them to LIBS is too
+# late, since that adds -L _after_ any -l options found in *_LDADD.
+if test -n "$STARLINK"; then
+    STAR_CPPFLAGS="-I$STARLINK/include"
+    STAR_FCFLAGS="-I$STARLINK/include"
+    STAR_FFLAGS="-I$STARLINK/include"
+    STAR_LDFLAGS="-L$STARLINK/lib"
+else
+    STAR_CPPFLAGS=
+    STAR_FCFLAGS=
+    STAR_FFLAGS=
+    STAR_LDFLAGS=
+fi
+AC_SUBST(STAR_CPPFLAGS)
+AC_SUBST(STAR_FCFLAGS)
+AC_SUBST(STAR_FFLAGS)
+AC_SUBST(STAR_LDFLAGS)
+
+# Installation directory options (these are no longer handled
+# by _STAR_EXTRADIR_COMMON).  There should be an entry here for each of
+# Starlink's special installation locations.
+AC_SUBST([stardocsdir],     ['${prefix}/docs'])dnl     documentation
+AC_SUBST([staretcdir],      ['${prefix}/etc'])dnl
+AC_SUBST([starexamplesdir], ['${prefix}/examples'])dnl
+AC_SUBST([starfacsdir],     ['${prefix}/help'])dnl     facilities files
+AC_SUBST([starhelpdir],     ['${prefix}/help'])dnl     other help files
+AC_SUBST([starnewsdir],     ['${prefix}/news'])dnl
+
+# Certain directories are affected by the $_star_per_package_dir variable;
+# if it's true, then add the $PACKAGE_NAME to the directory.
+# The directories currently adjusted by this are bin and help;
+# there are others: see PWD's message of 2004-02-16
+# <http://www.jiscmail.ac.uk/cgi-bin/wa.exe?A2=ind0402&L=stardev&T=0&F=&S=&P=5153>
+if $_star_per_package_dirs; then
+    bindir="$bindir/$PACKAGE_NAME"
+    starhelpdir="$starhelpdir/$PACKAGE_NAME"
+    staretcdir="$staretcdir/$PACKAGE_NAME"
+    AC_MSG_NOTICE([[STAR_DEFAULTS] has option per-package-dirs:])
+    AC_MSG_NOTICE([    bindir=$bindir starhelpdir=$starhelpdir staretcdir=$staretcdir])
+    # Note that starfacsdir is unaffected by per-package-dirs -- facility
+    # files must always be installed in .../help (this also facilitates
+    # changing this installation location in future, to somewhere with a
+    # more logical name than .../help).
+fi
+
+
+# Dependency declarations and checks.
+# Everything is dependent on starconf, so we don't have to declare that 
+# for each package separately.
+# STAR_DEPENDENCIES_ATTRIBUTES is currently not used.
+STAR_DEPENDENCIES_ATTRIBUTES=''
+STAR_DEPENDENCIES_CHILDREN=''
+AC_SUBST(STAR_DEPENDENCIES_ATTRIBUTES)
+AC_SUBST(STAR_DEPENDENCIES_CHILDREN)
+
+# List of documentation.  See [STAR_LATEX_DOCUMENTATION].
+# STAR_DOCUMENTATION is a list of document codes,
+STAR_DOCUMENTATION=
+AC_SUBST([STAR_DOCUMENTATION])
+
+# Create a PACKAGE_VERSION_INTEGER variable, which contains the
+# package's version number as an integer major*1e6+minor*1e3+release.
+eval [`echo $VERSION | sed -e 's/\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*/PACKAGE_VERSION_MAJOR=\1; PACKAGE_VERSION_MINOR=\2; PACKAGE_VERSION_RELEASE=\3;/'`]
+test -n "$PACKAGE_VERSION_MAJOR"   || PACKAGE_VERSION_MAJOR=0
+test -n "$PACKAGE_VERSION_MINOR"   || PACKAGE_VERSION_MINOR=0
+test -n "$PACKAGE_VERSION_RELEASE" || PACKAGE_VERSION_RELEASE=0
+PACKAGE_VERSION_INTEGER=`expr $PACKAGE_VERSION_MAJOR \* 1000000 + $PACKAGE_VERSION_MINOR \* 1000 + $PACKAGE_VERSION_RELEASE`
+AC_SUBST(PACKAGE_VERSION_MAJOR)
+AC_SUBST(PACKAGE_VERSION_MINOR)
+AC_SUBST(PACKAGE_VERSION_RELEASE)
+AC_SUBST(PACKAGE_VERSION_INTEGER)
+dnl Don't put this into config.h -- subst a .h file if required.
+dnl May change this in future
+dnl AC_DEFINE_UNQUOTED([PACKAGE_VERSION_INTEGER], $PACKAGE_VERSION_INTEGER,
+dnl                    [Integer version number, in the form major*1e6+minor*1e3+release])
+
+# When we do dependency checking, using the dependencies declared in
+# the package's configure.ac, we do so by looking at what tools have
+# already been installed in the Starlink tree.  The tree in question
+# is to be found under $STARLINK (see above), so we check that a
+# package is installed by checking that its manifest can be found in
+# $STARLINK/manifests.  We don't AC_SUBST this.
+current_MANIFESTS=$STARLINK/manifests
+
+# When we install manifests, however, they should go in the
+# installation directory.  Allow this to be defaulted from the environment.
+# In particular, if it is set to null in the environment, this will
+# suppress the installation of manifests.
+: ${STAR_MANIFEST_DIR='$(prefix)/manifests'}
+AC_SUBST(STAR_MANIFEST_DIR)
+
+# Each package updates the "starlink.version" file installed into the
+# manifests directory. This tracks the last git sha1 checkin for
+# the current code state by running the git show on the HEAD.
+# Define GIT as the program to run, but allow it to be overridden 
+# (most likely by ":" to avoid the overhead).
+# Also requires that STAR_SOURCE_ROOT_DIR is defined to locate the
+# head of the source tree.
+: ${GIT='git'}
+if test "${GIT}" = "git"; then
+   AC_PATH_PROG(GIT, git)
+fi
+AC_SUBST(GIT)
+
+: ${STAR_SOURCE_ROOT_DIR=''}
+AC_SUBST(STAR_SOURCE_ROOT_DIR)
+
+# Although PACKAGE_VERSION is a default output variable, it isn't
+# added as a Makefile variable by default.  We need it below, however,
+# so add it now.
+AC_SUBST(PACKAGE_VERSION)
+
+# Initialise state of predist/postdist flags (see STAR_PREDIST_SOURCES).
+# The value of _star_predist_status must be inherited by any
+# ./configure run in a subdirectory, so that we there avoid the predist
+# test of starconf.status: in a pre-distribution state, this file must
+# be present in the component directory (where we are running
+# ./configure), but must not be present in any subdirectory.
+_star_predist_status=unknown
+PREDIST='#'  # safe default
+AC_SUBST(PREDIST)
+
+# pax and/or tar are used in some install targets.
+# Note: value-if-not-found should be blank, so this can be tested for.
+AC_PATH_PROG(PAX, pax)
+AC_PATH_PROGS(TAR, [gnutar tar])
+
+ALL_TARGET=all-am-normal
+
+# Default $prefix.  This is done by the standard autoconf configure, but at
+# a slightly later stage than this.  Doing it here, as part of STAR_[]DEFAULTS
+# means that the defaulted value of $prefix can be used within the body of
+# the configure.ac, for example to pass it to a ./configure in a subdirectory.
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+])# STAR_DEFAULTS
+
+
+
+# STAR_MESSGEN([msgfile-list])
+# ----------------------------
+#
+# Handle generating message, error, and facility files.
+#
+# Declare that we will need to use the messgen utility.  This macro
+# does not by itself cause the messgen rules to be included in the
+# makefile -- that is done by automake, when it sees a
+# 'include_MESSAGES' or 'noinst_MESSAGES' variable.
+#
+# The optional argument is a space-separated list of files, each of
+# which has a set of message declarations in it, in the format
+# prescribed by the messgen utility.  If this is present, then the
+# named files are declared as pre-distribution files (the macro calls
+# STAR_PREDIST_SOURCES on them), and so the resulting configure script
+# should expect not to find them in an unpacked distribution.  This is
+# useful as documentation or as a shortcut for calling the latter
+# macro, but recall that it is the presence of the automake
+# 'include_MESSAGES' variable which does the work.
+#
+# The macro may be called more than once if you have more than one
+# .msg file in the directory.
+#
+# The files listed in the '_MESSAGES' variable will often have to be 
+# declared as `BUILT_SOURCES'.  
+#
+# The macro also implicitly declares a `sourceset' dependency on the
+# messgen package.
+AC_DEFUN([STAR_MESSGEN],
+   [#
+    $_star_docs_only &&
+        AC_MSG_ERROR([STAR[]_MESSGEN in docs-only directory])
+    STAR_DECLARE_DEPENDENCIES([sourceset], [messgen])
+    m4_ifval([$1], [STAR_PREDIST_SOURCES($1)])
+    STAR_CHECK_PROGS(messgen)
+])# STAR_MESSGEN
+
+
+# STAR_PREDIST_SOURCES(source-files)
+# ----------------------------------
+#
+# Give a (space-separated) list of files which should exist only in
+# the pre-distribution (ie, repository checkout) state.  If one of
+# these is found, then the substitution variable PREDIST is set to a
+# blank.  We should find either all of the marker files or none of
+# them; if only some of the marker files are found, this is probably
+# an error of some type, so warn about it.  This means, by the way,
+# that it is the presence or absence of the first marker file which
+# determines whether we are in the predist or postdist state, with the
+# rest providing consistency checks.
+#
+# The macro may be called more than once.  Multiple calls are
+# equivalent to a single call with all the marker files in the list.
+# Automake checks that the files listed here are not in the list of
+# distributed files, and issues a warning if they are.
+AC_DEFUN([STAR_PREDIST_SOURCES],
+[m4_ifval([$1], [], [AC_FATAL([$0: called with no stamp file])])dnl
+_star_tmp='$1'
+for marker in $_star_tmp
+do
+    if test -f $marker; then
+        _star_predist_marker_present=:
+        AC_MSG_NOTICE([found predist marker file $marker])
+    else
+        _star_predist_marker_present=false
+    fi
+    case $_star_predist_status in
+        unknown)
+            if $_star_predist_marker_present; then
+                # we do want to build sourceset files
+                _star_predist_status=predist
+                PREDIST=
+                AC_MSG_NOTICE([in pre-distribution state])
+            else
+                _star_predist_status=postdist
+                PREDIST='#'
+                AC_MSG_NOTICE([in post-distribution state])
+            fi
+            ;;
+        predist)
+            if $_star_predist_marker_present; then
+                : OK
+            else
+                AC_MSG_WARN([Building predist, but marker file $marker is not present])
+            fi
+            ;;
+        postdist)
+            if $_star_predist_marker_present; then
+                AC_MSG_WARN([In postdistribution state, but predist marker file $marker is present])
+            fi
+            ;;
+        *)
+            AC_MSG_ERROR([impossible predist status $_star_predist_status])
+            ;;
+    esac
+done
+])# STAR_PREDIST_SOURCES
+
+
+# STAR_CNF_COMPATIBLE_SYMBOLS
+# ---------------------------
+#
+# Work out what is required to have the Fortran compiler produce
+# library symbols which are compatible with those expected by the CNF
+# package.  If you are building a library in which C code refers to
+# Fortran libraries, then you should call this macro, which possibly
+# adjusts the AM_FCFLAGS variable.  That is, if you include cnf.h, you
+# should have this macro in the configure.ac.
+#
+# This macro deals with the following issue.  The cnf.h header
+# includes a macro F77_EXTERNAL_NAME which mangles a C name into the
+# corresponding name the Fortran compiler would generate; this
+# generally means no more than appending a single underscore.  As the
+# autoconf documentation for AC_FC_WRAPPERS points out, this is less
+# general than it could be, as some Fortrans fold symbols to
+# uppercase, and some (in particular g77) add two underscores to
+# symbols which already contain one (thus mangling 'ab' to 'ab_', but
+# 'a_b' to 'a_b__').  This behaviour would break the F77_EXTERNAL_NAME
+# macro, which is used throughout the Starlink code in both cases,
+# unless we compiled all the Starlink Fortran libraries in a mode which
+# suppressed this second underscore.  Working out how to do that --
+# if it's necessary at all -- is what this macro does.
+#
+# The more restricted interface of F77_EXTERNAL_NAME is, by the way,
+# the reason why we cannot simply copy the FC_FUNC definition to the
+# cnf.h file as F77_EXTERNAL_NAME: the latter macro is used for
+# symbols both with and without an underscore.
+#
+# If we ever have to migrate the Starlink software to a Fortran which
+# does more complicated name mangling, we'll almost certainly have to
+# perform more serious surgery on cnf.h, using the results of
+# AC_FC_WRAPPERS, along with similar surgery on the code which invokes
+# it.
+#
+# This macro is designed to work with CNF, however it does _not_
+# require the cnf.h headers to be installed, because it should remain
+# callable at configuration time before _anything_ has been installed.
+# In the test code below, we therefore emulate the definition of
+# F77_EXTERNAL_NAME in cnf.h, which appends a single underscore.
+# to the end of C symbols.
+#
+AC_DEFUN([STAR_CNF_COMPATIBLE_SYMBOLS],
+   [$_star_docs_only &&
+        AC_MSG_ERROR([STAR[]_CNF_COMPATIBLE_SYMBOLS in docs-only dir])
+    AC_CACHE_CHECK([how to make Fortran and C play nicely],
+       [star_cv_cnf_compatible_symbols],
+       [dnl AC_REQUIRE([AC_PROG_FC])dnl
+        dnl AC_REQUIRE([AC_PROG_CC])dnl
+        AC_LANG_PUSH([C])
+        AC_LANG_CONFTEST([AC_LANG_SOURCE([
+void funcone_() { return; }
+void func_two_() { return; }
+])])
+        if (eval $ac_compile) 2>&5
+        then
+            mv conftest.$ac_objext c-conftest.$ac_objext
+        else
+            AC_MSG_ERROR([cannot compile a C program!])
+        fi
+        AC_LANG_POP(C)
+        AC_LANG_PUSH([Fortran])
+        AC_LANG_CONFTEST([AC_LANG_SOURCE([
+      PROGRAM conftest
+      CALL funcone
+      CALL func_two
+      END
+])])
+        star_cv_cnf_compatible_symbols=cantlink
+        # The only Fortran we (need to) handle at present is
+        # g77, which has a -fno-second-underscore option for
+        # simplifying the mangling here.  Other Fortrans we've
+        # used do only the single-underscore mangling.
+        for opt in "" "-fno-second-underscore"
+        do
+            if $FC $FCFLAGS $opt -o conftest conftest.f c-conftest.$ac_objext 2>&5
+            then
+                star_cv_cnf_compatible_symbols=$opt
+                break
+            fi
+        done
+        AC_LANG_POP([Fortran])
+        rm -f conftest* c-conftest*
+])
+    if test "$star_cv_cnf_compatible_symbols" = cantlink
+    then
+        AC_MSG_ERROR([cannot work out how])
+    else
+        STAR_FCFLAGS="$STAR_FCFLAGS $star_cv_cnf_compatible_symbols"
+        STAR_FFLAGS="$STAR_FFLAGS $star_cv_cnf_compatible_symbols"
+    fi
+])# STAR_CNF_COMPATIBLE_SYMBOLS
+
+
+# STAR_CNF_F2C_COMPATIBLE
+# -----------------------
+#
+# Work out if the compiler is using 'f2c' compatible calling conventions.
+#
+# The `f2c' calling conventions, used by GNU Fortran compilers, require
+# functions that return type REAL to actually return the C type 'double'
+# (there is also special handling of COMPLEX returns, but that's not supported
+# by CNF). When operating in 'non-f2c' mode such functions return the expected
+# C type 'float'.
+#
+# The effect of this macro is subsitute the variable REAL_FUNCTION_TYPE
+# to either float or double as required.
+#
+# This function is not infallable and will usually return float for GNU
+# compilers, as the calling convention seems to not matter on 32-bit platforms
+# for the test in use. A stronger test would be to attempt calling a intrinsic
+# function, which is supposed to fail. Non-GNU compilers should always
+# return float. However, this test is used as it is all that is required.
+#
+AC_DEFUN([STAR_CNF_F2C_COMPATIBLE],
+   [$_star_docs_only &&
+        AC_MSG_ERROR([STAR[]_CNF_F2C_SYMBOLS in docs-only dir])
+    AC_CACHE_CHECK([if $FC is in strict f2c compatible mode],
+       [star_cv_cnf_f2c_compatible],
+       [AC_REQUIRE([AC_PROG_FC])dnl
+        if test "$ac_cv_fc_compiler_gnu" = yes; then
+           AC_REQUIRE([AC_PROG_CC])dnl
+           AC_LANG_PUSH([C])
+           AC_LANG_CONFTEST([AC_LANG_SOURCE([
+float fred_() {
+   return 1.0f;
+}
+])])
+           if (eval $ac_compile) 2>&5
+           then
+               mv conftest.$ac_objext c-conftest.$ac_objext
+           else
+               AC_MSG_ERROR([cannot compile a C function!])
+           fi
+           AC_LANG_POP(C)
+           AC_LANG_PUSH([Fortran])
+           AC_LANG_CONFTEST([AC_LANG_SOURCE([
+      PROGRAM F2CTEST
+      REAL FRED
+      REAL R
+      R = FRED()
+      IF ( R .NE. 0.0 ) THEN
+         WRITE(*,*) 'no'
+      ELSE
+         WRITE(*,*) 'yes'
+      ENDIF
+      END
+])])
+           star_cv_cnf_f2c_compatible=yes
+           $FC $FCFLAGS $opt -o conftest conftest.f c-conftest.$ac_objext 2>&5
+           if test -r conftest
+           then
+              star_cv_cnf_f2c_compatible=`eval ./conftest | sed 's/\ //g'` > /dev/null
+           else
+              AC_MSG_ERROR([failed to link program]) 
+           fi
+           AC_LANG_POP([Fortran])
+           rm -f conftest* c-conftest*
+      else
+         # Not a GNU compiler.
+         star_cv_cnf_f2c_compatible=no
+      fi
+])
+    if test "$star_cv_cnf_f2c_compatible" = "yes"
+    then
+        AC_SUBST(REAL_FUNCTION_TYPE, double)
+    else
+        AC_SUBST(REAL_FUNCTION_TYPE, float)
+    fi
+])# STAR_CNF_F2C_COMPATIBLE
+
+# STAR_CNF_BLANK_COMMON
+# ---------------------
+# Define the global symbol used to access the Fortran blank common block.
+# Usually under UNIX this is _BLNK__, but gfortran uses __BLNK__, so we
+# need to check for that. Gfortran is just detected by being a GNU compiler
+# and having "Fortran (GCC) 4.x[x].x[x]" as part of its --version output.
+#
+# The effect of this macro is to substitute BLANK_COMMON_SYMBOL with
+# the expected value.
+#
+AC_DEFUN([STAR_CNF_BLANK_COMMON],
+   [AC_CACHE_CHECK([symbol used for blank common in Fortran],
+       [star_cv_blank_common_symbol],
+       [AC_REQUIRE([AC_PROG_FC])
+       star_cv_blank_common_symbol=_BLNK__
+       if test "$ac_cv_fc_compiler_gnu" = yes; then
+            if "$FC" --version 2>&1 < /dev/null | grep 'GNU Fortran.*[[4-9]]\.[[0-9]][[0-9]]*\.[[0-9]][[0-9]]*' > /dev/null; then
+                star_cv_blank_common_symbol=__BLNK__
+            fi
+       fi])
+    AC_SUBST([BLANK_COMMON_SYMBOL], $star_cv_blank_common_symbol )
+])# STAR_CNF_BLANK_COMMON
+
+# STAR_PRM_COMPATIBLE_SYMBOLS
+# ---------------------------
+#
+#  See if any special flags are required to support PRM and the use of the
+#  PRM_PAR constants. If a typeless BOZ descriptor is available (usually 'X)
+#  then this macro will have no effect, however, if there's no typeless BOZ
+#  support any special Fortran compiler flags that are required when using
+#  PRM_PAR will be defined as part of the STAR_FCFLAGS and STAR_FFLAGS
+#  variables.
+#
+#  In fact this macro is only currently used for the gfortran and Solaris f95
+#  compilers. Gfortran has no typeless BOZ support, so requires that the
+#  -fno-range-check flag is set so that assigments to integers can silently
+#  overflow (BOZ constants are replaced with their plain integer and floating
+#  point equivalents). The Solaris f95 compiler doesn't allow assignments to
+#  LOGICAL parameters, so we need to use the -f77 flag.
+#
+#  In general this macro should be used by all packages that include PRM_PAR,
+#  all monoliths are assumed to use this by default. 
+#
+AC_DEFUN([STAR_PRM_COMPATIBLE_SYMBOLS],
+   [$_star_docs_only &&
+        AC_MSG_ERROR([STAR[]_PRM_COMPATIBLE_SYMBOLS in docs-only dir])
+    AC_CACHE_CHECK([how to make compiler accept PRM constants],
+       [star_cv_prm_compatible_symbols],
+       [star_cv_prm_compatible_symbols="nocheck"
+        AC_MSG_NOTICE([ ])
+        AC_FC_HAVE_TYPELESS_BOZ 2>&5
+        if test $ac_cv_fc_have_typeless_boz = no; then
+           AC_FC_HAVE_OLD_TYPELESS_BOZ 2>&5
+           if test $ac_cv_fc_have_old_typeless_boz = no; then
+              #  Test if -f77 works. Note need to clear the cached variables
+              #  for these tests.
+              unset ac_cv_fc_have_typeless_boz
+              unset ac_cv_fc_have_old_typeless_boz
+              old_FCFLAGS="$FCFLAGS"
+              FCFLAGS="-f77 $FCFLAGS"
+              AC_FC_HAVE_TYPELESS_BOZ 2>&5
+              if test $ac_cv_fc_have_typeless_boz = no; then
+                 AC_FC_HAVE_OLD_TYPELESS_BOZ 2>&5
+                 if test $ac_cv_fc_have_old_typeless_boz = no; then
+                    star_cv_prm_compatible_symbols="nocheck"
+                 else
+                    star_cv_prm_compatible_symbols="-f77"
+                 fi
+              else
+                 star_cv_prm_compatible_symbols="-f77"
+              fi
+              FCFLAGS="$old_FCFLAGS"
+              if test "$star_cv_prm_compatible_symbols" = "nocheck"; then
+                 #  Test if "-fno-range-check" works.
+                 AC_REQUIRE([AC_PROG_FC])dnl
+                 AC_LANG_PUSH([Fortran])
+                 AC_LANG_CONFTEST([AC_LANG_SOURCE([
+      PROGRAM conftest
+      INTEGER*2 VAL__BADUW
+      PARAMETER ( VAL__BADUW = 65535 )
+      BYTE VAL__BADUB
+      PARAMETER ( VAL__BADUB = 255 )
+      END
+])])
+                 if $FC -c $FCFLAGS -fno-range-check -o conftest conftest.f 2>&5
+                 then
+                    star_cv_prm_compatible_symbols="-fno-range-check"
+                 fi              
+                 AC_LANG_POP([Fortran])
+                 rm -f conftest.f
+              fi
+           else
+              star_cv_prm_compatible_symbols=""
+           fi
+         else
+            star_cv_prm_compatible_symbols=""
+         fi])
+         if test "$star_cv_prm_compatible_symbols" = "nocheck"; then
+            AC_MSG_ERROR([cannot work out how])
+         else
+            STAR_FCFLAGS="$STAR_FCFLAGS $star_cv_prm_compatible_symbols"
+            STAR_FFLAGS="$STAR_FFLAGS $star_cv_prm_compatible_symbols"
+         fi
+])# STAR_PRM_COMPATIBLE_SYMBOLS
+
+# STAR_CNF_TRAIL_TYPE
+# -------------------
+#
+# Work out what type to use for the trailing lengths of character strings
+# passed from Fortran to C. See the "TRAIL" descriptions in SUN/209.
+#
+# For most compilers the maximum length of a string is limited to a 32bit
+# unsigned int, but for others, this can be a 64bit unsigned long. Currently
+# the only compilers with 64bit strings are 64bit Intel fortran and
+# Solaris studio12 with -m64.
+#
+# The test is only performed for 64bit compilers, all others are assumed
+# to use 32bit lengths. Various attempts to trap this issue permanently 
+# using a test program have failed (especially for the Intel compiler), so the
+# actual test is to check for a known 64 bit compiler first and then try a
+# program that has had some success. Note no GNU compilers seem to have this
+# problem so they are never tested.
+#
+# The side-effect of this macro is to substitute TRAIL_TYPE with
+# the derived value and define TRAIL_TYPE. See "f77.h" in CNF.
+#
+AC_DEFUN([STAR_CNF_TRAIL_TYPE],
+   [AC_CHECK_SIZEOF(void*)dnl
+    AC_FC_HAVE_PERCENTLOC dnl
+    AC_CACHE_CHECK([type used for Fortran string lengths],
+       [star_cv_cnf_trail_type],
+       [if test "$ac_cv_sizeof_voidp" = 8 -a "$ac_cv_fc_compiler_gnu" = no; then
+           if "$FC" -V 2>&1 < /dev/null | grep 'Intel.*64' > /dev/null; then
+              star_cv_cnf_trail_type=long
+           elif "$FC" -V 2>&1 < /dev/null | grep 'Sun.*Fortran' > /dev/null; then
+              star_cv_cnf_trail_type=long
+           else
+              AC_REQUIRE([AC_PROG_FC])dnl
+              AC_LANG_PUSH([Fortran])
+              if test "$ac_cv_fc_have_percentloc" = yes; then
+                 FORTRAN_GETLOC='%loc'
+              else 
+                 FORTRAN_GETLOC='loc'
+              fi
+              AC_LANG_CONFTEST([AC_LANG_SOURCE([
+      program conftest
+
+C  checks passing 4 byte character string lengths on 64bit compiler.
+
+      integer*8 ip1, ip2
+      integer*4 l1, l2
+      integer dummy1, dummy2
+      real dummy3, dummy4
+      double precision dummy5, dummy6
+
+      character str1*(1024)
+      character str2*(2048)
+
+      ip1 = $FORTRAN_GETLOC (str1)
+      ip2 = $FORTRAN_GETLOC (str2)
+
+      l1 = 1024
+      l2 = 2048
+
+      call report( dummy1, dummy2, %val(ip1), dummy3, dummy4,
+     :             %val(ip2), dummy5, dummy6, 
+     :             %val(l1), %val(l2) )
+
+      end
+
+      subroutine report( dummy1, dummy2, str1, dummy3, dummy4,
+     :                   str2, dummy5, dummy6 )
+      integer dummy1, dummy2
+      real dummy3, dummy4
+      double precision dummy5, dummy6
+
+      character*(*) str1
+      character*(*) str2
+
+      if ( [len(str1)] .eq. 1024 .and. [len(str2)] .eq. 2048 ) then
+         print *, 'int'
+      else
+         print *, 'long'
+      endif
+      end
+])])
+              star_cv_cnf_trail_type=int
+              $FC $FCFLAGS $opt -o conftest conftest.f 2>&5
+              if test -r conftest
+              then
+                 star_cv_cnf_trail_type=`eval ./conftest | sed 's/\ //g'` > /dev/null
+              else
+                 AC_MSG_ERROR([failed to link program]) 
+              fi
+              rm -f conftest*
+              AC_LANG_POP([Fortran])
+           fi
+        else
+dnl  sizeof(void *) != 8 or GNU so no problems.
+           star_cv_cnf_trail_type=int
+        fi
+])
+    AC_SUBST([TRAIL_TYPE], $star_cv_cnf_trail_type )
+    AC_DEFINE_UNQUOTED([TRAIL_TYPE], $star_cv_cnf_trail_type, 
+                       [Type of Fortran CNF TRAIL argument] )
+])# STAR_CNF_TRAIL_TYPE
+
+# STAR_PATH_TCLTK([minversion=0], [options=''])
+# ---------------------------------------------
+#
+# Finds a tclsh and wish, and the associated libraries.  Sets output variable
+# TCL_CFLAGS to the C compiler flags necessary to compile with Tcl, TCL_LIBS
+# to the required library flags, and TCLSH to the full path of the tclsh
+# executable, TCL_PREFIX to the installation root and TCL_LD_SEARCH_FLAGS
+# to the default search path for loading the shareable library; if Tk is  
+# requested, it similarly sets TK_CFLAGS, TK_LIBS and WISH.  Define the 
+# cpp variable TCL_MISSING to 1 if Tcl is not available.  Similar to 
+# macro AC_PATH_XTRA.
+#
+# If argument MINVERSION is present, it specifies the minimum Tcl/Tk
+# version number required.
+#
+# The macro searches first in the path, and
+# then in a selection of platform-specific standard locations.  The
+# configure option --with-tcl allows you to provide a path to a tclsh
+# binary, which is put at the head of the list of locations to search.
+# Option --without-tcl suppresses the search, and results in no
+# variables being substituted.
+#
+# If the argument OPTIONS is present, it is a space-separated list of
+# the words 'tk' or 'itcl'.  If one or both of these is present, then
+# the macro will find a Tcl location which also has Tk or itcl
+# installed (note that the itcl test doesn't do anything at present).
+AC_DEFUN([STAR_PATH_TCLTK],
+    [_star_use_tcl=:
+     AC_ARG_WITH([tcl],
+                 AS_HELP_STRING([--with-tcl],
+                                [give path to tclsh (dir which contains binary)]),
+                 [if test "X$withval" = Xno; then
+                      _star_use_tcl=false
+                  elif test "X$withval" = Xyes; then
+                      _star_use_tcl=:
+                  else
+                      _star_use_tcl=:
+                      _star_try_tcldir=$withval
+                  fi])
+     if $_star_use_tcl; then
+         _star_searchfor=Tcl
+         if expr "x m4_ifval([$2], [$2], []) " : 'x.* tk ' >/dev/null; then
+             search_tk=:
+             _star_searchfor="$_star_searchfor/Tk"
+         else
+             search_tk=false
+         fi
+         if expr "x m4_ifval([$2], [$2], []) " : 'x.* itcl ' >/dev/null; then
+             search_itcl=:
+             _star_searchfor="$_star_searchfor/itcl"
+             echo "Searching for itcl does nothing so far!"
+         else
+             search_itcl=false
+         fi
+         AC_MSG_CHECKING([where to find $_star_searchfor m4_ifval([$1], [$1+], [(any version)])])
+         AC_CACHE_VAL([star_cv_settcldir],
+             [star_cv_settcldir=unknown
+              reqversint=`echo m4_ifval([$1], [$1], 0.0)-0-0 | [sed 's/\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*/10000 \1* 100 \2*+ \3+p/']|dc`
+              tclsources=`echo $PATH | sed "s/$PATH_SEPARATOR/ /g"`
+              stdsources='
+dnl  Search in /usr and /usr/local at least
+/usr/bin
+/usr/local/bin
+dnl  /opt/local and /sw are the default installation locations for OpenDarwin
+dnl  and Fink on OSX
+/opt/local/bin
+/sw/bin'
+              for d in $_star_try_tcldir $STARCONF_DEFAULT_STARLINK/bin $tclsources $stdsources
+              do
+                  locok=:
+                  if test -d $d; then
+                      tcldir=`cd $d/..; pwd`
+                      test -f $d/tclsh -a -f $tcldir/include/tcl.h || locok=false
+                  else
+                      locok=false
+                  fi
+                  if $locok && $search_tk; then
+                      test -f $d/wish -a -f $tcldir/include/tk.h || locok=false
+                  fi
+                  if $locok && $search_itcl; then
+                      test -f $tcldir/lib/libitcl.aXXX || locok=false
+                  fi
+                  if $locok; then
+                       if test ! -f $tcldir/lib/tclConfig.sh; then
+                           echo "$tcldir/lib/tclConfig.sh unexpectedly missing"
+                           break
+                       fi
+                       if $search_tk && test ! -f $tcldir/lib/tkConfig.sh; then
+                           echo "$tcldir/lib/tkConfig.sh unexpectedly missing"
+                           break
+                       fi
+                       rm -f conftest.results
+                       # Run in a subshell, to isolate settings in tclConfig.sh
+                       # Send output to conftest.results, and return
+                       # 0 if all is ok
+                       (
+    . $tcldir/lib/tclConfig.sh
+    if $search_tk; then
+        . $tcldir/lib/tkConfig.sh
+    fi
+    tclversint=`[echo $TCL_VERSION$TCL_PATCH_LEVEL-0-0 | sed 's/\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*/10000 \1* 100 \2*+ \3+p/'|dc]`
+    if test $tclversint -gt $reqversint; then
+        # New enough version.
+
+        # Dereference the tclsh and wish links -- the "->" _is_ standard,
+        # mandated by POSIX.
+        lslink=`ls -l $tcldir/bin/tclsh`
+        tclsh_loc=`expr "x$lslink" : "x.*-> *\(.*\)"`
+        if test -n "$tclsh_loc" -a -x "$tclsh_loc"; then
+           : OK
+        elif test -x "$tcldir/bin/tclsh"; then
+            # Odd: either .../bin/tclsh isn't a link, or it doesn't point to an
+            # executable.  But .../bin/tclsh is OK, so use that.
+            tclsh_loc="$tcldir/bin/tclsh"
+        else
+            # This really shouldn't happen, since we checked above that
+            # $d/tclsh was executable.  Still, it clearly has happened,
+            # so don't go mad.
+            echo "Warning: found Tcl libraries, but not tclsh!" >&2
+            tclsh_loc=
+        fi
+
+        res="_star_tcldir=$tcldir;"
+
+        # Make the TCL version number available.
+        res="$res TCL_VERSION=\"$TCL_VERSION\";"
+
+        # Export the TCL_PREFIX value.
+        res="$res TCL_PREFIX=\"$TCL_PREFIX\";"
+
+        # Export the TCL_LD_SEARCH_FLAGS value (need LIB_RUNTIME_DIR
+        # which is part of the symbol).
+        res="$res LIB_RUNTIME_DIR=\"$TCL_PREFIX/lib\";"
+        res="$res TCL_LD_SEARCH_FLAGS=\"$TCL_LD_SEARCH_FLAGS\";"
+
+        # These envs include $TCL_DBGX -- expand this out.
+        eval "I=\"$TCL_INCLUDE_SPEC\"; L=\"$TCL_LIB_SPEC\""
+        res="$res TCL_CFLAGS=\"$I\"; TCL_LIBS=\"$L\"; TCLSH=\"$tclsh_loc\";"
+
+        if $search_tk; then
+            # Same for wish
+            lslink=`ls -l $tcldir/bin/wish`
+            wish_loc=`expr "x$lslink" : "x.*-> *\(.*\)"`
+            if test -n "$wish_loc" -a -x "$wish_loc"; then
+                : OK
+            elif test -x "$tcldir/bin/wish"; then
+                wish_loc="$tcldir/bin/wish"
+            else
+                echo "Warning: found Tk libraries, but not wish!" >&2
+                wish_loc=
+            fi
+            # These envs potentially include $TK_DBGX -- expand this out.
+            eval "I=\"$TK_XINCLUDES\"; L=\"$TK_LIB_SPEC\""
+            res="$res TK_CFLAGS=\"$I\"; TK_LIBS=\"$L\"; WISH=\"$wish_loc\";"
+        fi
+
+        # similarly for $search_itcl
+
+        echo $res >conftest.results
+        status=0
+    else
+        msg="$tcldir: found Tcl-$TCL_VERSION$TCL_PATCH_LEVEL"
+        if $search_tk; then
+            msg="$msg, Tk-$TK_VERSION$TK_PATCH_LEVEL"
+        fi
+        echo "$msg: older than required" >&2
+        status=1
+    fi
+    exit $status
+                       )
+                       teststat=$?
+                       if test $teststat = 0; then
+                           star_cv_settcldir=`cat conftest.results`
+                       fi
+                       if test "$star_cv_settcldir" != unknown; then
+                           break
+                       fi
+                  fi # $locok
+              done])
+
+        if test "$star_cv_settcldir" = unknown; then
+            AC_MSG_RESULT(unknown)
+        else
+            eval $star_cv_settcldir
+            AC_MSG_RESULT($_star_tcldir)
+        fi
+    else # $_star_use_tcl
+        AC_MSG_WARN(Compiling without Tcl/Tk)
+    fi # $_star_use_tcl
+
+    if $_star_use_tcl && test "$star_cv_settcldir" != unknown; then
+        :
+    else
+        AC_DEFINE(TCL_MISSING, 1,
+                  [Define to 1 if no Tcl/Tk libraries can be found])
+    fi
+
+    AC_SUBST(TCL_VERSION)
+
+    AC_SUBST(TCL_PREFIX)
+
+    AC_SUBST(TCL_LD_SEARCH_FLAGS)
+    AC_SUBST(TCL_CFLAGS)
+    AC_SUBST(TCL_LIBS)
+    AC_SUBST(TCLSH)
+
+    AC_SUBST(TK_CFLAGS)
+    AC_SUBST(TK_LIBS)
+    AC_SUBST(WISH)
+
+    # add itcl variables here
+
+])# STAR_PATH_TCLTK
+
+
+# STAR_LATEX_DOCUMENTATION(documentcode, [targets])
+# -------------------------------------------------
+# Generate the standard makefile targets to handle LaTeX documentation
+# source.  The parameter documentcode should be something like
+# `sun123' -- it should not include any .tex extension.
+#
+# The second, optional, argument gives an explicit list of the targets
+# which are build.  If this is _not_ specified, then a standard list
+# is used (.tex, .ps and .tar_htx) and corresponding rules added to
+# the generated makefile.  If it is specified, it must be non-null,
+# and its value is a list of files which are to be added to the
+# distribution, and no extra Makefile rules are added.  Thus if users need
+# anything complicated done, they should use this second argument and
+# provide rules for satisfying the given targets.
+#
+# In the latter case, the .tex -> htx_tar rule is still emitted, so
+# you can use it, but it requires the substitution variable
+# @STAR[]2HTML@, and so if you _do_ use it, you will have to make that
+# available, either through [STAR_CHECK_PROGS(star2html)] or otherwise.
+AC_DEFUN([STAR_LATEX_DOCUMENTATION],
+   [m4_ifval([$1], [], [AC_FATAL([$0: called with no documentcode])])dnl
+    m4_if(m4_bregexp([$1], [^ *\([a-z][a-z]*[0-9]*/? *\)*$]),
+          [0],
+          [],
+          [AC_FATAL([$0: bad doccode in $1 -- must be eg sun123 or sun123/])])
+    STAR_DOCUMENTATION="$STAR_DOCUMENTATION m4_bpatsubst([$1],[/])"
+    m4_ifval([$2],
+       [dnl non-empty second argument -- just add to variable
+        m4_if(m4_bregexp([$1], [/]), -1,
+              [],
+              [AC_FATAL([$0: do not mix non-null second argument and .../ syntax])])
+        if $_star_build_docs; then
+            STAR@&t@_LATEX_DOCUMENTATION="$2"
+        fi
+        ],
+       [dnl second arg empty -- use defaults
+        if $_star_build_docs; then
+            AC_FOREACH([DocCode], [$1],
+               [m4_if(m4_bregexp(DocCode,[/]), -1,
+                      [STAR@&t@_LATEX_DOCUMENTATION="$STAR@&t@_LATEX_DOCUMENTATION DocCode.tex DocCode.pdf DocCode.htx_tar"
+],
+                      [m4_define([_T], m4_bpatsubst(DocCode,[/]))dnl
+                       STAR_LATEX_DOCUMENTATION_[]_STAR_UPCASE(_T)="_T.tex _T.pdf _T.htx_tar"
+                       AC_SUBST(STAR_LATEX_DOCUMENTATION_[]_STAR_UPCASE(_T))])])
+        fi
+        STAR_DECLARE_DEPENDENCIES([sourceset], [star2html])
+        STAR_CHECK_PROGS([star2html])
+       ])
+    if $_star_build_docs; then
+        : ${LATEX2DVI='$$LATEX "\\batchmode\\input $$[]1" && $$LATEX "\\batchmode\\input $$[]1"'}
+        AC_SUBST(LATEX2DVI)
+    else
+        AC_MSG_WARN([not installing docs $1])
+    fi
+    AC_SUBST([STAR@&t@_LATEX_DOCUMENTATION])dnl
+])# STAR_LATEX_DOCUMENTATION
+
+# STAR_XML_DOCUMENTATION(documentcode, [targets])
+# -----------------------------------------------
+# Generate the standard makefile targets to handle XML documentation
+# source.  The parameter documentcode should be something like
+# `sun123' -- it should not include any .xml extension.  For each of the
+# documentcodes which does not end with a slash, append
+# <documentcode>.{texml_tar,htx_tar,ps} to STAR_XML_DOCUMENTATION;
+# for each which does end with a slash, define instead the
+# variable STAR_XML_DOCUMENTATION_<documentcode>.  In either case,
+# append the documentcode to STAR_DOCUMENTATION
+#
+# The second, optional, argument gives an explicit list of the targets
+# which are build.  If this is _not_ specified, then a standard list
+# is used (.texml_tar, .ps and .htx_tar) and corresponding rules added to
+# the generated makefile.  If it is specified, it must be non-null,
+# and its value is a list of files which are to be added to the
+# distribution, and no extra Makefile rules are added.  Thus if users need
+# anything complicated done, they should use this second argument and
+# provide rules for satisfying the given targets.
+#
+# In the latter case, the .tex -> htx_tar rule is still emitted, so
+# you can use it, but it requires the substitution variables JADE, SGMLNORM,
+# and SGMLKIT_HOME.  This is rather inconvenient, and it is fortunate that
+# you almost certainly won't need to use this.
+AC_DEFUN([STAR_XML_DOCUMENTATION],
+   [m4_ifval([$1], [], [AC_FATAL([$0: called with no documentcode])])dnl
+    m4_if(m4_bregexp([$1], [^ *\([a-z][a-z]*[0-9]*/? *\)*$]),
+          [0],
+          [],
+          [AC_FATAL([$0: bad doccode in $1 -- must be eg sun123 or sun123/])])
+    STAR_DOCUMENTATION="$STAR_DOCUMENTATION m4_bpatsubst([$1],[/])"
+    m4_ifval([$2],
+       [dnl non-empty second argument -- just add to variable
+        m4_if(m4_bregexp([$1], [/]), -1,
+              [],
+              [AC_FATAL([$0: do not mix non-null second argument and .../ syntax])])
+        if $_star_build_docs; then
+            STAR@&t@_XML_DOCUMENTATION="$2"
+        fi
+        ],
+       [dnl second arg empty -- use defaults
+        if $_star_build_docs; then
+            do_the_build=  # blank if we're to go ahead, string expl. otherwise
+            AC_PATH_PROGS(JADE, [openjade jade], NOJADE)
+            AC_PATH_PROGS(SGMLNORM, [osgmlnorm sgmlnorm], NOSGMLNORM)
+            STAR_CHECK_PROGS([sgml2docs])
+            if test "$JADE" = NOJADE -o "$SGMLNORM" = NOSGMLNORM -o "$SGML2DOCS" = "sgml2docs"; then
+                if $_star_docs_only; then
+                    # Building documentation is all we're supposed to do,
+                    # and we can't, so suppress further building.
+                    do_the_build=\
+"This docs-only component requires Jade, sgmlnorm and sgml2docs.  
+        All I could find were:
+        $JADE for Jade, 
+        $SGMLNORM for sgmlnorm and 
+        $SGML2DOCS for sgml2docs (requires full path).
+        Your system may have a way to install Jade and sgmlnorm as a package,
+        sgml2docs is part of the SGMLKIT package."
+                else
+                    AC_MSG_WARN([can't find (open)jade + (o)sgmlnorm + sgml2docs -- skipping XML documentation $1])
+                fi
+            else
+                # Test Jade version
+                AC_MSG_CHECKING([version of $JADE (need 1.3.2 or better)])
+                $JADE -v </dev/null >conftest.version 2>&1
+                JADEVERS=[`sed -n '/:I:.*[Jj]ade.*version/{
+    s/.*:I://
+    s/[^0-9][^0-9]*/ /gp
+}' conftest.version`]
+                # The following converts space-separated integers to a single
+                # one.  It's perhaps a leeettle funkier than necessary...
+                VERSINT=[`echo "[Ss[z0<a]x]sa $JADEVERS 0 0 0 lax Ls100* Ls+100* Ls+p" | dc`]
+                if test $VERSINT -ge 10302; then
+                  AC_MSG_RESULT([ok])
+                  AC_FOREACH([DocCode], [$1],
+                   [m4_if(m4_bregexp(DocCode,[/]), -1,
+                          [STAR@&t@_XML_DOCUMENTATION="$STAR@&t@_XML_DOCUMENTATION DocCode.texml_tar DocCode.htx_tar DocCode.ps DocCode.pdf"
+],
+                          [m4_define([_T], m4_bpatsubst(DocCode,[/]))dnl
+                           STAR_XML_DOCUMENTATION_[]_STAR_UPCASE(_T)="_T.texml_tar _T.htx_tar _T.ps _T.pdf"
+                           AC_SUBST(STAR_XML_DOCUMENTATION_[]_STAR_UPCASE(_T))])])
+                else
+                    AC_MSG_RESULT([too old])
+                    do_the_build="Your openjade is version $JADEVERS; need 1.3.2 or better"
+                fi
+                SGMLKIT_HOME=$prefix/lib/sgmlkit
+                AC_SUBST(SGMLKIT_HOME)
+            fi
+            STAR_SUPPRESS_BUILD_IF(test -n "$do_the_build", [$do_the_build])
+        fi
+        STAR_DECLARE_DEPENDENCIES([sourceset], [sgmlkit])
+       ])
+    if $_star_build_docs; then
+        : ${LATEX2DVI='$$LATEX "\\batchmode\\input $$[]1" && $$LATEX "\\batchmode\\input $$[]1"'}
+        AC_SUBST(LATEX2DVI)
+    else
+        AC_MSG_WARN([not installing docs $1])
+    fi
+    AC_SUBST([STAR@&t@_XML_DOCUMENTATION])dnl
+])# STAR_XML_DOCUMENTATION
+            
+
+
+# STAR_CHECK_PROGS(progs-to-check-for, [component=''])
+# --------------------------------------------------
+#
+# For each of the programs in PROGS-TO-CHECK-FOR, define a variable
+# whose name is the upcased version of the program name, and whose
+# value is the full path to that program, or the expected installation
+# location of that program if no absolute path can be found.  Because
+# of this default behaviour, this macro should _only_ be used for
+# locating Starlink programs such as messgen or alink, and not as a
+# general replacement for AC_CHECK_PROG.  Any characters in the
+# program outside of the set of alphanumerics and underscores are
+# normalised to underscores.
+#
+# The optional second argument gives the name of the component containing
+# the program in question.  Some packages install their binaries in
+# package-specific directories, and this argument allows this macro to
+# look there as well.
+#
+# For example:
+#     STAR_CHECK_PROGS(messgen)
+# would define the variable MESSGEN to have the full path to the
+# messgen application, and 
+#     STAR_CHECK_PROGS(prolat, sst)
+# would define the variable PROLAT to have the path to the prolat
+# application within the sst component.
+#
+# Calls AC_SUBST and AC_ARG_VAR on the generated variable name.  This
+# macro does _not_ automatically declare a configure dependency on any
+# component specified in the second argument.  These dependencies should
+# be kept to an absolute minimum, and therefore any such dependencies
+# must be declared obviously and explicitly, with rationale.
+#
+# The behaviour described below, for the result when the required program
+# is not found, is not final, and may change.  The documentation below is
+# contradictory, and should be regarded merely as a rather confused
+# discussion of the issues.  The current behaviour is that when the progam
+# is not found, the variable is defined to be the program's name without
+# any path at all.
+#
+# This is the analogue of AC_CHECK_PROG, except that: (1) the variable
+# name defaults to the program name, (2) the variable value if the
+# program is not found is the path to the anticipated installation
+# location of the program, so that the macro does not fail in this
+# case.  This is useful for locating Starlink programs, as it means we
+# can use this macro to produce absolute paths to programs, even
+# before they have been installed (in this case we are presumably
+# doing a top-level configure of the Starlink tree, and the Makefile
+# will ensure that the required files are installed before the current
+# package actually uses it.  NB: (2) is not true at present.
+#
+# The current value of the PATH variable is augmented by the location
+# of the binary installation directory, using the current default
+# value of the prefix (not ideal, since this may in principle change
+# when the component being configured is installed, but it's the best
+# we can do at configure time); and by the $STARLINK/bin directory.
+#
+# The default, if the program isn't in the augmented path, is the path
+# to the starconf-finder program if that's available, and the bare
+# program-name otherwise.  Is this the best default?  Would just 
+# program-name be better?  The program may not be in the augmented
+# path for two reasons: (1) we are doing the global configuration done
+# during bootstrapping, and noting has been installed yet; or (2) the
+# program is one of those installed in a subdirectory of the
+# `bindir'.  In case (2), there's not a lot we can do, short of
+# grubbing round manifest files at some point, but the
+# starconf-finder, which is the eventual default, knows about this
+# case, and could take care of it.
+#
+# No, I've changed my mind again.  If the program isn't found, then
+# simply have the AC_PATH_PROG default to bare ProgramName.  This is
+# probably adequate, and if so probably more robust than relying on
+# more and more layers of indirection.  We probably will need to
+# revisit this.  Again.
+AC_DEFUN([STAR_CHECK_PROGS],
+         [eval default_bindir=`echo $bindir | sed 's,\${exec_prefix},$ac_default_prefix,'`
+          AC_FOREACH([ProgramName], [$1],
+                     [m4_define([star_prog],
+                                _STAR_UPCASE(m4_bpatsubst(ProgramName,
+                                                          [[^0-9a-zA-Z_]], 
+                                                          [_])))
+                      AC_PATH_PROG(star_prog,
+                                   ProgramName,
+                                   ProgramName,
+[$STARLINK/Perl/bin:]dnl
+[$STARLINK/starjava/bin:]dnl
+[$STARLINK/bin:]m4_ifval([$2],[$STARLINK/bin/$2:],)dnl
+[$default_bindir:]m4_ifval([$2],[$default_bindir/$2:],)dnl
+[$PATH])
+                      AC_ARG_VAR(star_prog,
+                                 [Location of the ]ProgramName[ application])])
+])# STAR_CHECK_PROGS
+
+
+# STAR_SPECIAL_INSTALL_COMMAND(cmd)
+# ---------------------------------
+# Declare a special install command.  Note that we do not examine the
+# actual command here -- that is done by automake.  All we do is find
+# a way of copying a directory tree, preserving symlinks, as used by
+# the install targets in automake/lib/am/install.am.  Try 'cp -R' and
+# variants, then pax, and if both fail, collapse.
+dnl Link test is `test -h': more portable than -L, according to autoconf notes.
+dnl `cp --no-dereference' is GNU cp: -P gives a compatibility warning.
+dnl Include broken link in test: some cp fail to copy these (OSX 10.2, Sol9).
+dnl Option -f is probably a good plan, but no failures spotted so far.
+dnl In pax, `-p e' fails with broken links; `-p p' is OK.
+AC_DEFUN([STAR_SPECIAL_INSTALL_COMMAND],
+   [AC_REQUIRE([AC_PROG_LN_S])dnl
+    AC_PATH_PROG(CP, cp)dnl
+    AC_PATH_PROG(PAX, pax)
+    AC_CACHE_CHECK([how to do a recursive directory copy],
+                   [star_cv_cp_r],
+                   [rm -Rf conftest*
+                    mkdir conftest-d1 conftest-d2
+                    mkdir conftest-d1/d
+                    date >conftest-d1/d/f
+                    (cd conftest-d1/d; $LN_S f l; $LN_S x broken)
+                    if test ! -h conftest-d1/d/l; then
+                        # We don't have links!  So plain cp -R will do
+                        star_cv_cp_r="$CP -R"
+                    else
+                        star_cv_cp_r=
+                        for try in "$CP -R --no-dereference -p -f" "$CP -R -P -p -f" "$CP -R -P -p" "$CP -R -p" "${PAX-false} -r -w -p p"
+                        do
+                            rm -Rf conftest-d2/*
+                            if (cd conftest-d1; $try . ../conftest-d2 2>/dev/null); then
+                                if test -h conftest-d2/d/l -a -h conftest-d2/d/broken; then
+                                    star_cv_cp_r="$try"
+                                    break
+                                fi
+                            fi
+                        done
+                    fi
+                    rm -Rf conftest*])
+    if test -z "$star_cv_cp_r"; then
+        AC_MSG_ERROR([unable to find working cp or pax])
+    fi
+    AC_SUBST(CP_RECURSIVE, $star_cv_cp_r)dnl
+])# STAR_SPECIAL_INSTALL_COMMAND
+    
+
+# STAR_MONOLITHS
+# --------------
+# Declare that we will be creating monoliths.  This does whatever
+# configuration is necessary to handle these.
+#
+# Note that the declarations done in the Makefile.am, declaring the
+# name of the monolith and the names and source files of the tasks,
+# are slightly redundant inasmuch as some of that information could be
+# implied.  However, this is required to be explicit for clarity and
+# consistency, and so accomodate the (currently unexploited)
+# possibility that the tasks and .ifl files longer have the
+# one-task-per-file relationship they have now.
+AC_DEFUN([STAR_MONOLITHS],
+         [$_star_docs_only &&
+             AC_MSG_ERROR([STAR[]_MONOLITHS in docs-only directory])
+          dnl Installation in monoliths.am uses $(LN_S)
+          AC_REQUIRE([AC_PROG_LN_S])dnl
+
+          # To build monoliths, we need both compifl to build the .ifc
+          # files (in the parsecon component), and alink
+          # to link the monoliths (in dtask).  Both are now part of
+          # the pcs component.
+          STAR_DECLARE_DEPENDENCIES(build, [pcs])
+
+          # So try to find alink and compifl.
+          STAR_CHECK_PROGS([compifl alink])
+
+          # When we're building monoliths, we will almost certainly be
+          # using Fortran, and so we might as well include this,
+          # partly in case the user forgets, but also because this is
+          # reasonably part of the default setup required for monoliths.
+          STAR_CNF_COMPATIBLE_SYMBOLS
+          STAR_PRM_COMPATIBLE_SYMBOLS
+])# STAR_MONOLITHS
+
+
+# STAR_HELP_FILES(helpfiles)
+# --------------------------
+# Declare a list of files to be installed into the Starlink help
+# directory.  This can be used both internally and in user
+# configure.ac files.
+AC_DEFUN([STAR_HELP_FILES],
+	 [_STAR_EXTRADIR_COMMON([help], [$1])])
+
+
+# STAR_ETC_FILES(etcfiles)
+# ------------------------
+# Declare a list of files to be installed into the Starlink etc
+# directory.  This can be used both internally and in user
+# configure.ac files.
+AC_DEFUN([STAR_ETC_FILES],
+         [_STAR_EXTRADIR_COMMON([etc], [$1])])
+
+
+# STAR_DOCS_FILES(docfiles)
+# -------------------------
+# Declare a list of files to be installed into the Starlink
+# documentation directory.  This can be used both internally and in
+# user configure.ac files.
+AC_DEFUN([STAR_DOCS_FILES],
+         [_STAR_EXTRADIR_COMMON([docs], [$1])])
+
+
+# STAR_EXAMPLES_FILES(examplesfiles)
+# ----------------------------------
+# Declare a list of files to be installed into the Starlink
+# examples directory.  This can be used both internally and in
+# user configure.ac files.
+AC_DEFUN([STAR_EXAMPLES_FILES],
+         [_STAR_EXTRADIR_COMMON([examples], [$1])])
+
+
+# STAR_DECLARE_DEPENDENCIES(type, deplist, option='')
+# ---------------------------------------------------
+#
+# Declare dependencies of this component.  The TYPE is one of
+# `sourceset', `build', `link', `use', `test' or `configure', and the
+# DEPLIST is a space separated list of component names, which this
+# component depends on in the given way.
+#
+# -- Sourceset dependencies are those components which must be
+# installed in order to build the complete set of sources, either for
+# building or for distribution.  This includes documentation, so it
+# would include star2html as well as messgen.
+#
+# -- Build dependencies are those which are required in order to build
+# this component.  This typically means include files, but if part of
+# the component is an executable file (such as compifl within the
+# parsecon component), then that's a build dependence also (but see
+# the discussion of `option', below).  You may not have two components
+# which have a build dependency on each other, since that would mean
+# that each would have to be built before the other, which is
+# impossible.
+#
+# -- Link dependencies are those required to link against the
+# libraries in a component.  That means all the libraries that this
+# component's libraries use.  These are not necessarily build
+# dependencies, since if you are building a library, any called
+# libraries don't have to be present in order to build this library;
+# you can have two components which have mutual link dependencies.  If
+# you are building an application, however, then all its link
+# dependencies will actually be build dependencies and should be
+# declared as such.  In other words, the distinction between build and
+# link dependencies is important only for library components.
+#
+# -- Use dependencies are those which are required in order for the
+# component to be used by something else, after it has been built and
+# installed.  For example a library which called another application
+# as part of its functionality would have only a use dependency on the
+# component which contained that application.  If no use dependencies
+# are declared, we take the use dependencies to be the same as the
+# link dependencies.
+#
+# -- Test dependencies are those which are required in order to run
+# any regression tests which come with the component.  It's generally
+# a good idea to avoid making this a larger set than the use
+# dependencies, but sometimes this is unavoidable.  If no test
+# dependencies are declared, we take the test dependencies to be the
+# same as the use dependencies.
+#
+# -- Configure dependencies are those which must be satisfied before
+# this component can be successfully configured.  In this case, we
+# also check that the corresponding manifest files have been installed
+# in current_MANIFESTS, and if not exit with a message, and the suggestion
+# that the user runs 'make configure-deps'.
+#
+# The point of this is that different dependencies are required at
+# different times.  The set of dependencies in the master makefile is
+# composed of all the `sourceset' and `build' dependencies, but not
+# `link' or `use' dependencies, and since the core Starlink libraries
+# are closely interdependent, the set of `build' dependencies needs to
+# be kept as small as possible in order to avoid circularities (that
+# is, A depending on B, which depends, possibly indirectly, on A).
+#
+# All these relationships are transitive: if A has a build dependency
+# on B, and B has one on C, then A has a build dependency on C.  You
+# can augment this by using the final `option' argument: if, in
+# component A's declaration element you say
+# STAR_DECLARE_DEPENDENCIES(build, B, link), then you declare that A
+# has a build-time dependency on B, but that (presumably because you
+# are building an application within a component which is mostly
+# libraries) you need to link against B, so component A has a
+# dependency on all of B's _link_ dependencies, not just its build
+# dependencies.  This is (I believe) the only case where this `option'
+# attribute is useful, though it is legal for each of the dependency types.
+#
+# You need only declare direct dependencies.  If package A depends on
+# package B, which depends in turn on package C, then package A need
+# not declare a dependency on C.
+#
+# The macro may be called more than once.  The results of this macro
+# are expressed in the file component.xml in the component directory.
+AC_DEFUN([STAR_DECLARE_DEPENDENCIES],
+ [m4_ifval([$1], [], [AC_FATAL([$0: no type given])])dnl
+  m4_if(m4_bregexp([$1], 
+                   [^\(sourceset\|build\|link\|use\|test\|configure\)$]),
+        [0],
+        [],
+        [AC_FATAL([$0: unrecognised dependency type: $1])])dnl
+  m4_ifval([$2], [], [AC_FATAL([$0: no deplist given])])dnl
+  for _star_tmp in $2
+  do
+    STAR_DEPENDENCIES_CHILDREN="$STAR_DEPENDENCIES_CHILDREN<[$1]m4_ifval([$3], [ option='$3'], [])>$_star_tmp</$1>"
+  done
+  m4_if([$1], [configure], [# check that configure-deps ran...
+  for _star_tmp in $2
+  do
+    echo "$as_me:$LINENO: checking for configure-deps/$_star_tmp" >&5
+    echo $ECHO_N "checking for configure-deps/$_star_tmp... $ECHO_C" >&6
+    if test -f $current_MANIFESTS/$_star_tmp; then
+      echo "$as_me:$LINENO: result: ok" >&5
+      echo "${ECHO_T}ok" >&6
+    else
+      echo "$as_me:$LINENO: result: not found!" >&5
+      echo "${ECHO_T}not found" >&6
+      echo "*** This package has a configure dependency on $_star_tmp" >&6
+      echo "    but that component doesn't appear to be installed." >&6
+      echo "    (I can't find $current_MANIFESTS/$_star_tmp:" >&6
+      echo "    have you forgotten to run 'make configure-deps'?)" >&6
+      echo "    Giving up!" >&6
+      exit 1
+    fi
+  done
+])dnl
+])# STAR_DECLARE_DEPENDENCIES
+
+
+# STAR_PLATFORM_SOURCES(target-file-list, platform-list)
+# ------------------------------------------------------
+#
+# Generate the given target-file for each of the files in the list
+# TARGET-FILE-LIST, by selecting the appropriate element of the
+# PLATFORM-LIST based on the value of [AC_CANONICAL_BUILD].  Both
+# lists are space-separated lists.
+#
+# For each of the platforms, <p>, in platform-list, there should be a
+# file `<target-file><p>'.  There should always be a file
+# `<target-file>default', and if none of the platform-list strings
+# matches, this is the file which is used.  If the `default' file is
+# listed in the `platform-list', then it is matched in the normal run
+# of things; if it is not listed, it still matches, but a warning is
+# issued.
+#
+# If you wish no match _not_ to be an error -- perhaps because there
+# is a platform-dependent file which is redundant on unlisted platforms
+# -- then end the platform-list with `NONE'.  In this case, if no file
+# matches, then no link is made, with no error or warning.
+#
+# This macro uses the results of ./config.guess to determine the
+# current platform.  That returns a triple consisting of
+# cpu-vendor-os, such as `i686-pc-linux-gnu' (OS=linux-gnu),
+# `sparc-sun-solaris2.9', or `alphaev6-dec-osf5.1'
+#
+# The extensions <p> in platform-list should all have the form
+# `cpu_vendor[_os]', where each of the components `cpu', `vendor' and
+# `os' may be blank.  If not blank, they are matched as a prefix of
+# the corresponding part of the config.guess value.  Thus
+# `_sun_solaris' would match `sparc-sun-solaris2.9' but not
+# `sparc-sun-sunos', and `_sun' would match both.  For a <target-file>
+# file foo.c, this would result in `ln -s foo.c_sun foo.c'
+#
+# Calls AC_LIBSOURCE for each of the implied platform-specific files.
+#
+AC_DEFUN([STAR_PLATFORM_SOURCES],
+         [
+$_star_docs_only && AC_MSG_ERROR([STAR_[]PLATFORM_SOURCES in docs-only dir])
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+m4_ifval([$1], [], [AC_FATAL([$0: no target-file-list given])])dnl
+m4_ifval([$2], [], [AC_FATAL([$0: no platform-list given])])dnl
+AC_FOREACH([TargetFile], [$1],
+  [AC_FOREACH([Ext], [$2], 
+    [m4_if(Ext, [NONE], , [AC_LIBSOURCE(TargetFile[]Ext)])])])dnl
+AC_MSG_CHECKING([platform-specific source for file(s) $1])
+_star_tmp=
+for platform in $2
+do
+    if test $platform = NONE; then
+        # Special case -- no file required
+        _star_tmp=NONE
+        break;
+    fi
+    if test $platform = default; then
+        _star_tmp=default
+        break;
+    fi
+    if expr $build : `echo $platform | sed 's/_/.*-/g'` >/dev/null; then
+        _star_tmp=$platform
+        break;
+    fi
+done
+if test -z "$_star_tmp"; then
+    # Use default, but it wasn't listed in the platform-list
+    # (though it should have been)
+    AC_MSG_WARN([build platform $build does not match any of ($2): using `default'])
+    _star_tmp=default
+fi    
+if test $_star_tmp = NONE; then
+    AC_MSG_RESULT([none required])
+else
+    AC_MSG_RESULT([using $_star_tmp])
+    for _t in $1
+    do
+        if test -f $srcdir/$_t$_star_tmp; then
+            (cd $srcdir; rm -f $_t; cp -p $_t$_star_tmp $_t)
+        else
+            AC_MSG_WARN([platform $_star_tmp matched, but no file $_t$_star_tmp found])
+        fi
+    done
+fi
+])# STAR_PLATFORM_SOURCES
+
+
+# STAR_INITIALISE_FORTRAN_RTL
+# ---------------------------
+#
+# Define a macro which can be used in a C main program to initialise the
+# Fortran RTL, including, for example, doing whatever work is required so that
+# the Fortran getarg() function works.  This defines the macro
+# STAR_INITIALISE_FORTRAN(argc,argv).  The implementation necessarily uses
+# functions which are specific to the Fortran implementation, and the body of
+# this macro is basically a switch to determine the compiler and thus the
+# appropriate compiler-specific magic.  If no implementation is available,
+# then the macro should expand to nothing.
+#
+# Note that the Starlink functions wrapping getarg() are robust against
+# getarg() failing because this information is not available.  This function
+# is nonetheless necessary because some platforms have link problems otherwise
+# (specifically OSX, on which the most readily available compiler is g77,
+# cannot link properly if the getarg() function is referenced by a library,
+# but there is a C main function, so that the Fortran main function's call of
+# f_setarg is omitted).  Thus it is generally harmless to leave this function
+# unimplemented on those platforms which do not have these link problems, and
+# it is harmless that the test below is extremely compiler specific (though we
+# would probably have to add implementations for any other compilers used on
+# OSX).  It's also generally harmless not to call the defined function, or
+# invoke this macro, if your application doesn't have the link problems which
+# makeit necessary, though of course calling it will make getarg() work where
+# it otherwise wouldn't, which may be an advantage.
+#
+# All was well until g95 and gfortran, these are "gnu" compilers, but use
+# different semantics. In the case of g95 the calling the startup code is not
+# optional (docs say that heap initialisation relies on the startup call).
+#
+# The test for g95 relies on the output from `g95 --version' containing the
+# string "G95". Gfortran requires "GNU Fortran (GCC) 4+.x[x].x[x]".  Note that
+# this whole area probably needs rethinking as g95 also has a g95_runtime_stop()
+# function, that should be called.
+#
+# At gfortran 4.6 the call sequence stopped allowing a NULL argv, so a dummy
+# version had to be added. The argv[0] value is always de-referenced in an
+# an attempt to get the program name.
+#
+# Intel Fortran needs the for_rtl_init_ function, there is also a
+# for_rtl_finish_ to run during closedown. The intel compiler signature
+# is to have "IFORT" in the --version string.
+#
+# Under Solaris and the studio compilers the argc and argv values are no
+# longer automatically shared, so we test for "Sun Fortran" and have a 
+# code section that copies the given argc and argv directly to the global
+# variable __xargc and __xargv, this may need fixing from time to time.
+# Doesn't seem to be a function for doing this job.
+#
+AC_DEFUN([STAR_INITIALISE_FORTRAN_RTL],
+   [AC_CACHE_CHECK([how to initialise the Fortran RTL],
+       [star_cv_initialise_fortran],
+       [AC_REQUIRE([AC_PROG_FC])
+       if test "$ac_cv_fc_compiler_gnu" = yes; then
+            if "$FC" --version 2>&1 < /dev/null | grep 'G95' > /dev/null; then
+                star_cv_initialise_fortran=g95-start
+            elif "$FC" --version 2>&1 < /dev/null | grep 'GNU Fortran.*[[4-9]]\.[[0-9]][[0-9]]*\.[[0-9]][[0-9]]*' > /dev/null; then
+                star_cv_initialise_fortran=gfortran-setarg
+            else
+                star_cv_initialise_fortran=g77-setarg
+            fi
+        else
+            if "$FC" --version 2>&1 < /dev/null | grep 'IFORT' > /dev/null; then
+                star_cv_initialise_fortran=ifort-setarg
+            elif "$FC" -V 2>&1 < /dev/null | grep 'Sun Fortran' > /dev/null; then
+                star_cv_initialise_fortran=sunstudio-setarg
+            else
+                star_cv_initialise_fortran=
+            fi
+        fi])
+    AH_TEMPLATE([STAR_INITIALISE_FORTRAN],
+       [Define to a function call which will initialise the Fortran RTL])
+    case "$star_cv_initialise_fortran" in
+      g77-setarg)
+        AC_DEFINE([STAR_INITIALISE_FORTRAN(argc,argv)],
+                  [{extern void f_setarg(int,char**); f_setarg(argc, argv);}])
+        ;;
+      g95-start)
+        AC_DEFINE([STAR_INITIALISE_FORTRAN(argc,argv)],
+                  [{extern void g95_runtime_start(int,char**); g95_runtime_start(argc, argv);}])
+        ;;
+      gfortran-setarg)
+        AC_DEFINE([STAR_INITIALISE_FORTRAN(argc,argv)],
+                  [{extern void _gfortran_set_args(int,char**); if (argv == NULL) {static char *sc_dummy[[]]={NULL};_gfortran_set_args(0,sc_dummy);} else {_gfortran_set_args(argc,argv);}}])
+        ;;
+      ifort-setarg)
+        AC_DEFINE([STAR_INITIALISE_FORTRAN(argc,argv)],
+                  [{extern void for_rtl_init_(int*,char**); for_rtl_init_(&argc, argv);}])
+        ;;
+      sunstudio-setarg)
+        AC_DEFINE([STAR_INITIALISE_FORTRAN(argc,argv)],
+                  [{extern int __xargc; extern char **__xargv;__xargc = argc;__xargv = argv;}])
+        ;;
+      *) 
+        AC_DEFINE([STAR_INITIALISE_FORTRAN(argc,argv)],[])
+        ;;
+    esac
+dnl    AC_DEFINE_UNQUOTED([STAR_INITIALISE_FORTRAN(argc,argv)],
+dnl                       $star_cv_initialise_fortran)
+])# STAR_INITIALISE_FORTRAN
+
+
+# STAR_SUPPRESS_BUILD_IF(test, message)
+# -------------------------------------
+# Call once at the end of the configure script.
+#
+# If the given shell test evaluates to true, then suppress the build,
+# without having ./configure fail.  The test is any command which
+# returns true if the build should be suppressed, and may be shell
+# commands `true' or `false', or might be a more complicated test,
+# such as `test -n "$SOMEENV"'.
+#
+# The macro communicates with the generated Makefile.in by creating a file
+# STAR_SUPPRESS_BUILD if the test evaluates to true.  The file contains
+# the text of the explanation.
+AC_DEFUN([STAR_SUPPRESS_BUILD_IF],
+   [m4_ifval([$1], [], [AC_FATAL([$0: needs two arguments])])dnl
+    m4_ifval([$2], [], [AC_FATAL([$0: needs two arguments])])dnl
+    rm -f STAR_SUPPRESS_BUILD
+    if [$1]; then
+        AC_MSG_WARN([Build inhibited:
+        $2])
+        echo "$2" >STAR_SUPPRESS_BUILD
+        ALL_TARGET=all-am-suppress
+    else
+        ALL_TARGET=all-am-normal
+    fi
+    AC_SUBST(ALL_TARGET)
+])# STAR_SUPPRESS_BUILD_IF
+
+
+# starconf internal macros
+
+
+# _STAR_UPCASE(string)
+# --------------------
+# Expands to STRING with all letters translated to uppercase.
+AC_DEFUN([_STAR_UPCASE],
+         [m4_translit([$1], [a-z], [A-Z])])
+
+
+# _STAR_EXTRADIR_COMMON(dir-type, file-list)
+# ------------------------------------------
+#
+# Common handler for STAR_HELP_FILES, etc.  DIR-TYPE is one of
+#
+#    help, etc, docs, examples
+#
+# and `FILE-LIST' is a list of files to be installed in
+# the directory STAR_[DIR-TYPE]_DIR.  This works by defining and
+# AC_SUBSTing the variables `starX_DATA for X=dir-type (eg, `stardocs_DATA').
+#
+# This is now obsolete -- components should use star<dir-type>_DATA in
+# the Makefile.am file instead.  Don't use AC_[]DIAGNOSE([obsolete],...),
+# since those warnings aren't turned on by default.
+AC_DEFUN([_STAR_EXTRADIR_COMMON],
+   [AC_FATAL([Macro STAR_]_STAR_UPCASE($1)[_FILES($2) is obsolete -- use star$1_DATA in Makefile.am instead])
+    AC_FATAL([For STAR@&t@_LATEX_DOCUMENTATION, use stardocs_DATA=@STAR@&t@_LATEX_DOCUMENTATION@ instead])]
+)# _STAR_EXTRADIR_COMMON
+
+
+# STAR_LARGEFILE_SUPPORT
+# ----------------------
+#
+# Set C macros for compiling C routines that want to make use of large file 
+# support. This is a joining of AC_SYS_LARGEFILE and AC_FUNC_FSEEKO
+# so defines the macros _FILE_OFFSET_BITS, _LARGEFILE_SOURCE and _LARGE_FILES,
+# along with HAVE_FSEEKO. To use large file support you need to use fseeko and
+# ftello when HAVE_FSEEKO is defined (and use off_t for offsets) and compile
+# all C code with the other defines. 
+#
+# This function also gathers the values of _FILE_OFFSET_BITS, _LARGEFILE_SOURCE
+# and _LARGE_FILES and sets the STAR_LARGEFILE_CFLAGS variable (this in useful
+# for passing to packages which are not directly part of the starconf system).
+#
+AC_DEFUN([STAR_LARGEFILE_SUPPORT],
+[dnl Enable autoconf largefile support.
+AC_SYS_LARGEFILE
+AC_FUNC_FSEEKO
+
+# Gather state into a single variable for passing to other packages.
+STAR_LARGEFILE_CFLAGS=
+if test "$ac_cv_sys_file_offset_bits" != "no"; then
+   STAR_LARGEFILE_CFLAGS="-D_FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits"
+fi
+
+if test "$ac_cv_sys_large_files" != "no"; then
+   STAR_LARGEFILE_CFLAGS="-D_LARGE_FILES_=$ac_cv_sys_large_files $STAR_LARGEFILE_CFLAGS"
+fi
+
+if test "$ac_cv_sys_largefile_source" != "no"; then
+   STAR_LARGEFILE_CFLAGS="-D_LARGEFILE_SOURCE=$ac_cv_sys_largefile_source $STAR_LARGEFILE_CFLAGS"
+fi
+])# STAR_LARGEFILE_SUPPORT
+
+
+# Obsolete macros
+# ===============
+
+# STAR_HAVE_FC_OPEN_READONLY
+# ---------------------------
+#
+# Tests if the Fortran compiler supports the READONLY option on the
+# OPEN command.  If it does, it defines HAVE_FC_OPEN_READONLY to 1.
+AC_DEFUN([STAR_HAVE_FC_OPEN_READONLY],
+   [AC_FATAL([Macro STAR_HAVE_FC_OPEN_READONLY is obsolete; use AC_FC_OPEN_SPECIFIERS(readonly) instead])])
+
+
+# STAR_FC_LIBRARY_LDFLAGS
+# -----------------------
+#
+# This was once a wrapper for AC_[]FC_LIBRARY_LDFLAGS which added
+# functionality, use AC_[]FC_LIBRARY_LDFLAGS instead.
+AC_DEFUN([STAR_FC_LIBRARY_LDFLAGS],
+   [AC_FATAL([Macro STAR_FC_LIBRARY_LDFLAGS is obsolete: if necessary, use standard AC_FC_LIBRARY_LDFLAGS instead])])
+
+
+# end of starconf macros
+
+# Copyright (C) 2002-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_AUTOMAKE_VERSION(VERSION)
+# ----------------------------
+# Automake X.Y traces this macro to ensure aclocal.m4 has been
+# generated from the m4 files accompanying Automake X.Y.
+# (This private macro should not be called outside this file.)
+AC_DEFUN([AM_AUTOMAKE_VERSION],
+[am__api_version='1.14'
+dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
+dnl require some minimum version.  Point them to the right macro.
+m4_if([$1], [1.14.1-starlink], [],
+      [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
+])
+
+# _AM_AUTOCONF_VERSION(VERSION)
+# -----------------------------
+# aclocal traces this macro to find the Autoconf version.
+# This is a private macro too.  Using m4_define simplifies
+# the logic in aclocal, which can simply ignore this definition.
+m4_define([_AM_AUTOCONF_VERSION], [])
+
+# AM_SET_CURRENT_AUTOMAKE_VERSION
+# -------------------------------
+# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
+# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
+AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
+[AM_AUTOMAKE_VERSION([1.14.1-starlink])dnl
+m4_ifndef([AC_AUTOCONF_VERSION],
+  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
+
+# AM_AUX_DIR_EXPAND                                         -*- Autoconf -*-
+
+# Copyright (C) 2001-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets
+# $ac_aux_dir to '$srcdir/foo'.  In other projects, it is set to
+# '$srcdir', '$srcdir/..', or '$srcdir/../..'.
+#
+# Of course, Automake must honor this variable whenever it calls a
+# tool from the auxiliary directory.  The problem is that $srcdir (and
+# therefore $ac_aux_dir as well) can be either absolute or relative,
+# depending on how configure is run.  This is pretty annoying, since
+# it makes $ac_aux_dir quite unusable in subdirectories: in the top
+# source directory, any form will work fine, but in subdirectories a
+# relative path needs to be adjusted first.
+#
+# $ac_aux_dir/missing
+#    fails when called from a subdirectory if $ac_aux_dir is relative
+# $top_srcdir/$ac_aux_dir/missing
+#    fails if $ac_aux_dir is absolute,
+#    fails when called from a subdirectory in a VPATH build with
+#          a relative $ac_aux_dir
+#
+# The reason of the latter failure is that $top_srcdir and $ac_aux_dir
+# are both prefixed by $srcdir.  In an in-source build this is usually
+# harmless because $srcdir is '.', but things will broke when you
+# start a VPATH build or use an absolute $srcdir.
+#
+# So we could use something similar to $top_srcdir/$ac_aux_dir/missing,
+# iff we strip the leading $srcdir from $ac_aux_dir.  That would be:
+#   am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"`
+# and then we would define $MISSING as
+#   MISSING="\${SHELL} $am_aux_dir/missing"
+# This will work as long as MISSING is not called from configure, because
+# unfortunately $(top_srcdir) has no meaning in configure.
+# However there are other variables, like CC, which are often used in
+# configure, and could therefore not use this "fixed" $ac_aux_dir.
+#
+# Another solution, used here, is to always expand $ac_aux_dir to an
+# absolute PATH.  The drawback is that using absolute paths prevent a
+# configured tree to be moved without reconfiguration.
+
+AC_DEFUN([AM_AUX_DIR_EXPAND],
+[dnl Rely on autoconf to set up CDPATH properly.
+AC_PREREQ([2.50])dnl
+# expand $ac_aux_dir to an absolute path
+am_aux_dir=`cd $ac_aux_dir && pwd`
+])
+
+# AM_CONDITIONAL                                            -*- Autoconf -*-
+
+# Copyright (C) 1997-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_CONDITIONAL(NAME, SHELL-CONDITION)
+# -------------------------------------
+# Define a conditional.
+AC_DEFUN([AM_CONDITIONAL],
+[AC_PREREQ([2.52])dnl
+ m4_if([$1], [TRUE],  [AC_FATAL([$0: invalid condition: $1])],
+       [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
+AC_SUBST([$1_TRUE])dnl
+AC_SUBST([$1_FALSE])dnl
+_AM_SUBST_NOTMAKE([$1_TRUE])dnl
+_AM_SUBST_NOTMAKE([$1_FALSE])dnl
+m4_define([_AM_COND_VALUE_$1], [$2])dnl
+if $2; then
+  $1_TRUE=
+  $1_FALSE='#'
+else
+  $1_TRUE='#'
+  $1_FALSE=
+fi
+AC_CONFIG_COMMANDS_PRE(
+[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then
+  AC_MSG_ERROR([[conditional "$1" was never defined.
+Usually this means the macro was only invoked conditionally.]])
+fi])])
+
+# Copyright (C) 1999-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+
+# There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be
+# written in clear, in which case automake, when reading aclocal.m4,
+# will think it sees a *use*, and therefore will trigger all it's
+# C support machinery.  Also note that it means that autoscan, seeing
+# CC etc. in the Makefile, will ask for an AC_PROG_CC use...
+
+
+# _AM_DEPENDENCIES(NAME)
+# ----------------------
+# See how the compiler implements dependency checking.
+# NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC".
+# We try a few techniques and use that to set a single cache variable.
+#
+# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was
+# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular
+# dependency, and given that the user is not expected to run this macro,
+# just rely on AC_PROG_CC.
+AC_DEFUN([_AM_DEPENDENCIES],
+[AC_REQUIRE([AM_SET_DEPDIR])dnl
+AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl
+AC_REQUIRE([AM_MAKE_INCLUDE])dnl
+AC_REQUIRE([AM_DEP_TRACK])dnl
+
+m4_if([$1], [CC],   [depcc="$CC"   am_compiler_list=],
+      [$1], [CXX],  [depcc="$CXX"  am_compiler_list=],
+      [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'],
+      [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'],
+      [$1], [UPC],  [depcc="$UPC"  am_compiler_list=],
+      [$1], [GCJ],  [depcc="$GCJ"  am_compiler_list='gcc3 gcc'],
+                    [depcc="$$1"   am_compiler_list=])
+
+AC_CACHE_CHECK([dependency style of $depcc],
+               [am_cv_$1_dependencies_compiler_type],
+[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+  # We make a subdir and do the tests there.  Otherwise we can end up
+  # making bogus files that we don't know about and never remove.  For
+  # instance it was reported that on HP-UX the gcc test will end up
+  # making a dummy file named 'D' -- because '-MD' means "put the output
+  # in D".
+  rm -rf conftest.dir
+  mkdir conftest.dir
+  # Copy depcomp to subdir because otherwise we won't find it if we're
+  # using a relative directory.
+  cp "$am_depcomp" conftest.dir
+  cd conftest.dir
+  # We will build objects and dependencies in a subdirectory because
+  # it helps to detect inapplicable dependency modes.  For instance
+  # both Tru64's cc and ICC support -MD to output dependencies as a
+  # side effect of compilation, but ICC will put the dependencies in
+  # the current directory while Tru64 will put them in the object
+  # directory.
+  mkdir sub
+
+  am_cv_$1_dependencies_compiler_type=none
+  if test "$am_compiler_list" = ""; then
+     am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp`
+  fi
+  am__universal=false
+  m4_case([$1], [CC],
+    [case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac],
+    [CXX],
+    [case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac])
+
+  for depmode in $am_compiler_list; do
+    # Setup a source with many dependencies, because some compilers
+    # like to wrap large dependency lists on column 80 (with \), and
+    # we should not choose a depcomp mode which is confused by this.
+    #
+    # We need to recreate these files for each test, as the compiler may
+    # overwrite some of them when testing with obscure command lines.
+    # This happens at least with the AIX C compiler.
+    : > sub/conftest.c
+    for i in 1 2 3 4 5 6; do
+      echo '#include "conftst'$i'.h"' >> sub/conftest.c
+      # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
+      # Solaris 10 /bin/sh.
+      echo '/* dummy */' > sub/conftst$i.h
+    done
+    echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+    # We check with '-c' and '-o' for the sake of the "dashmstdout"
+    # mode.  It turns out that the SunPro C++ compiler does not properly
+    # handle '-M -o', and we need to detect this.  Also, some Intel
+    # versions had trouble with output in subdirs.
+    am__obj=sub/conftest.${OBJEXT-o}
+    am__minus_obj="-o $am__obj"
+    case $depmode in
+    gcc)
+      # This depmode causes a compiler race in universal mode.
+      test "$am__universal" = false || continue
+      ;;
+    nosideeffect)
+      # After this tag, mechanisms are not by side-effect, so they'll
+      # only be used when explicitly requested.
+      if test "x$enable_dependency_tracking" = xyes; then
+	continue
+      else
+	break
+      fi
+      ;;
+    msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+      # This compiler won't grok '-c -o', but also, the minuso test has
+      # not run yet.  These depmodes are late enough in the game, and
+      # so weak that their functioning should not be impacted.
+      am__obj=conftest.${OBJEXT-o}
+      am__minus_obj=
+      ;;
+    none) break ;;
+    esac
+    if depmode=$depmode \
+       source=sub/conftest.c object=$am__obj \
+       depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+       $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+         >/dev/null 2>conftest.err &&
+       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+       ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+      # icc doesn't choke on unknown options, it will just issue warnings
+      # or remarks (even with -Werror).  So we grep stderr for any message
+      # that says an option was ignored or not supported.
+      # When given -MP, icc 7.0 and 7.1 complain thusly:
+      #   icc: Command line warning: ignoring option '-M'; no argument required
+      # The diagnosis changed in icc 8.0:
+      #   icc: Command line remark: option '-MP' not supported
+      if (grep 'ignoring option' conftest.err ||
+          grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+        am_cv_$1_dependencies_compiler_type=$depmode
+        break
+      fi
+    fi
+  done
+
+  cd ..
+  rm -rf conftest.dir
+else
+  am_cv_$1_dependencies_compiler_type=none
+fi
+])
+AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type])
+AM_CONDITIONAL([am__fastdep$1], [
+  test "x$enable_dependency_tracking" != xno \
+  && test "$am_cv_$1_dependencies_compiler_type" = gcc3])
+])
+
+
+# AM_SET_DEPDIR
+# -------------
+# Choose a directory name for dependency files.
+# This macro is AC_REQUIREd in _AM_DEPENDENCIES.
+AC_DEFUN([AM_SET_DEPDIR],
+[AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl
+])
+
+
+# AM_DEP_TRACK
+# ------------
+AC_DEFUN([AM_DEP_TRACK],
+[AC_ARG_ENABLE([dependency-tracking], [dnl
+AS_HELP_STRING(
+  [--enable-dependency-tracking],
+  [do not reject slow dependency extractors])
+AS_HELP_STRING(
+  [--disable-dependency-tracking],
+  [speeds up one-time build])])
+if test "x$enable_dependency_tracking" != xno; then
+  am_depcomp="$ac_aux_dir/depcomp"
+  AMDEPBACKSLASH='\'
+  am__nodep='_no'
+fi
+AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno])
+AC_SUBST([AMDEPBACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl
+AC_SUBST([am__nodep])dnl
+_AM_SUBST_NOTMAKE([am__nodep])dnl
+])
+
+# Generate code to set up dependency tracking.              -*- Autoconf -*-
+
+# Copyright (C) 1999-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+
+# _AM_OUTPUT_DEPENDENCY_COMMANDS
+# ------------------------------
+AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
+[{
+  # Older Autoconf quotes --file arguments for eval, but not when files
+  # are listed without --file.  Let's play safe and only enable the eval
+  # if we detect the quoting.
+  case $CONFIG_FILES in
+  *\'*) eval set x "$CONFIG_FILES" ;;
+  *)   set x $CONFIG_FILES ;;
+  esac
+  shift
+  for mf
+  do
+    # Strip MF so we end up with the name of the file.
+    mf=`echo "$mf" | sed -e 's/:.*$//'`
+    # Check whether this is an Automake generated Makefile or not.
+    # We used to match only the files named 'Makefile.in', but
+    # some people rename them; so instead we look at the file content.
+    # Grep'ing the first line is not enough: some people post-process
+    # each Makefile.in and add a new line on top of each file to say so.
+    # Grep'ing the whole file is not good either: AIX grep has a line
+    # limit of 2048, but all sed's we know have understand at least 4000.
+    if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+      dirpart=`AS_DIRNAME("$mf")`
+    else
+      continue
+    fi
+    # Extract the definition of DEPDIR, am__include, and am__quote
+    # from the Makefile without running 'make'.
+    DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
+    test -z "$DEPDIR" && continue
+    am__include=`sed -n 's/^am__include = //p' < "$mf"`
+    test -z "$am__include" && continue
+    am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
+    # Find all dependency output files, they are included files with
+    # $(DEPDIR) in their names.  We invoke sed twice because it is the
+    # simplest approach to changing $(DEPDIR) to its actual value in the
+    # expansion.
+    for file in `sed -n "
+      s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
+	 sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do
+      # Make sure the directory exists.
+      test -f "$dirpart/$file" && continue
+      fdir=`AS_DIRNAME(["$file"])`
+      AS_MKDIR_P([$dirpart/$fdir])
+      # echo "creating $dirpart/$file"
+      echo '# dummy' > "$dirpart/$file"
+    done
+  done
+}
+])# _AM_OUTPUT_DEPENDENCY_COMMANDS
+
+
+# AM_OUTPUT_DEPENDENCY_COMMANDS
+# -----------------------------
+# This macro should only be invoked once -- use via AC_REQUIRE.
+#
+# This code is only required when automatic dependency tracking
+# is enabled.  FIXME.  This creates each '.P' file that we will
+# need in order to bootstrap the dependency handling code.
+AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
+[AC_CONFIG_COMMANDS([depfiles],
+     [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS],
+     [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"])
+])
+
+# Do all the work for Automake.                             -*- Autoconf -*-
+
+# Copyright (C) 1996-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This macro actually does too much.  Some checks are only needed if
+# your package does certain things.  But this isn't really a big deal.
+
+dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O.
+m4_define([AC_PROG_CC],
+m4_defn([AC_PROG_CC])
+[_AM_PROG_CC_C_O
+])
+
+# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE])
+# AM_INIT_AUTOMAKE([OPTIONS])
+# -----------------------------------------------
+# The call with PACKAGE and VERSION arguments is the old style
+# call (pre autoconf-2.50), which is being phased out.  PACKAGE
+# and VERSION should now be passed to AC_INIT and removed from
+# the call to AM_INIT_AUTOMAKE.
+# We support both call styles for the transition.  After
+# the next Automake release, Autoconf can make the AC_INIT
+# arguments mandatory, and then we can depend on a new Autoconf
+# release and drop the old call support.
+AC_DEFUN([AM_INIT_AUTOMAKE],
+[AC_PREREQ([2.65])dnl
+dnl Autoconf wants to disallow AM_ names.  We explicitly allow
+dnl the ones we care about.
+m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
+AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl
+AC_REQUIRE([AC_PROG_INSTALL])dnl
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+  # is not polluted with repeated "-I."
+  AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl
+  # test to see if srcdir already configured
+  if test -f $srcdir/config.status; then
+    AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
+  fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+  if (cygpath --version) >/dev/null 2>/dev/null; then
+    CYGPATH_W='cygpath -w'
+  else
+    CYGPATH_W=echo
+  fi
+fi
+AC_SUBST([CYGPATH_W])
+
+# Define the identity of the package.
+dnl Distinguish between old-style and new-style calls.
+m4_ifval([$2],
+[AC_DIAGNOSE([obsolete],
+             [$0: two- and three-arguments forms are deprecated.])
+m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl
+ AC_SUBST([PACKAGE], [$1])dnl
+ AC_SUBST([VERSION], [$2])],
+[_AM_SET_OPTIONS([$1])dnl
+dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
+m4_if(
+  m4_ifdef([AC_PACKAGE_NAME], [ok]):m4_ifdef([AC_PACKAGE_VERSION], [ok]),
+  [ok:ok],,
+  [m4_fatal([AC_INIT should be called with package and version arguments])])dnl
+ AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
+ AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl
+
+_AM_IF_OPTION([no-define],,
+[AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package])
+ AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl
+
+# Some tools Automake needs.
+AC_REQUIRE([AM_SANITY_CHECK])dnl
+AC_REQUIRE([AC_ARG_PROGRAM])dnl
+AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}])
+AM_MISSING_PROG([AUTOCONF], [autoconf])
+AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}])
+AM_MISSING_PROG([AUTOHEADER], [autoheader])
+AM_MISSING_PROG([MAKEINFO], [makeinfo])
+AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl
+AC_REQUIRE([AC_PROG_MKDIR_P])dnl
+# For better backward compatibility.  To be removed once Automake 1.9.x
+# dies out for good.  For more background, see:
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
+AC_SUBST([mkdir_p], ['$(MKDIR_P)'])
+# We need awk for the "check" target.  The system "awk" is bad on
+# some platforms.
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])],
+	      [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])],
+			     [_AM_PROG_TAR([v7])])])
+_AM_IF_OPTION([no-dependencies],,
+[AC_PROVIDE_IFELSE([AC_PROG_CC],
+		  [_AM_DEPENDENCIES([CC])],
+		  [m4_define([AC_PROG_CC],
+			     m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_CXX],
+		  [_AM_DEPENDENCIES([CXX])],
+		  [m4_define([AC_PROG_CXX],
+			     m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJC],
+		  [_AM_DEPENDENCIES([OBJC])],
+		  [m4_define([AC_PROG_OBJC],
+			     m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJCXX],
+		  [_AM_DEPENDENCIES([OBJCXX])],
+		  [m4_define([AC_PROG_OBJCXX],
+			     m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl
+])
+AC_REQUIRE([AM_SILENT_RULES])dnl
+dnl The testsuite driver may need to know about EXEEXT, so add the
+dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen.  This
+dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below.
+AC_CONFIG_COMMANDS_PRE(dnl
+[m4_provide_if([_AM_COMPILER_EXEEXT],
+  [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl
+
+# POSIX will say in a future version that running "rm -f" with no argument
+# is OK; and we want to be able to make that assumption in our Makefile
+# recipes.  So use an aggressive probe to check that the usage we want is
+# actually supported "in the wild" to an acceptable degree.
+# See automake bug#10828.
+# To make any issue more visible, cause the running configure to be aborted
+# by default if the 'rm' program in use doesn't match our expectations; the
+# user can still override this though.
+if rm -f && rm -fr && rm -rf; then : OK; else
+  cat >&2 <<'END'
+Oops!
+
+Your 'rm' program seems unable to run without file operands specified
+on the command line, even when the '-f' option is present.  This is contrary
+to the behaviour of most rm programs out there, and not conforming with
+the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542>
+
+Please tell [email-masked] about your system, including the value
+of your $PATH and any error possibly output before this message.  This
+can help us improve future automake versions.
+
+END
+  if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then
+    echo 'Configuration will proceed anyway, since you have set the' >&2
+    echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2
+    echo >&2
+  else
+    cat >&2 <<'END'
+Aborting the configuration process, to ensure you take notice of the issue.
+
+You can download and install GNU coreutils to get an 'rm' implementation
+that behaves properly: <http://www.gnu.org/software/coreutils/>.
+
+If you want to complete the configuration process using your problematic
+'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
+to "yes", and re-run configure.
+
+END
+    AC_MSG_ERROR([Your 'rm' program is bad, sorry.])
+  fi
+fi])
+
+dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion.  Do not
+dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further
+dnl mangled by Autoconf and run in a shell conditional statement.
+m4_define([_AC_COMPILER_EXEEXT],
+m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])])
+
+# When config.status generates a header, we must update the stamp-h file.
+# This file resides in the same directory as the config header
+# that is generated.  The stamp files are numbered to have different names.
+
+# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the
+# loop where config.status creates the headers, so we can generate
+# our stamp files there.
+AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK],
+[# Compute $1's index in $config_headers.
+_am_arg=$1
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+  case $_am_header in
+    $_am_arg | $_am_arg:* )
+      break ;;
+    * )
+      _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+  esac
+done
+echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
+
+# Copyright (C) 2001-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_SH
+# ------------------
+# Define $install_sh.
+AC_DEFUN([AM_PROG_INSTALL_SH],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+if test x"${install_sh}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\	*)
+    install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+  *)
+    install_sh="\${SHELL} $am_aux_dir/install-sh"
+  esac
+fi
+AC_SUBST([install_sh])])
+
+# Copyright (C) 2003-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# Check whether the underlying file-system supports filenames
+# with a leading dot.  For instance MS-DOS doesn't.
+AC_DEFUN([AM_SET_LEADING_DOT],
+[rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+  am__leading_dot=.
+else
+  am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+AC_SUBST([am__leading_dot])])
+
+# Check to see how 'make' treats includes.	            -*- Autoconf -*-
+
+# Copyright (C) 2001-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MAKE_INCLUDE()
+# -----------------
+# Check to see how make treats includes.
+AC_DEFUN([AM_MAKE_INCLUDE],
+[am_make=${MAKE-make}
+cat > confinc << 'END'
+am__doit:
+	@echo this is the am__doit target
+.PHONY: am__doit
+END
+# If we don't find an include directive, just comment out the code.
+AC_MSG_CHECKING([for style of include used by $am_make])
+am__include="#"
+am__quote=
+_am_result=none
+# First try GNU make style include.
+echo "include confinc" > confmf
+# Ignore all kinds of additional output from 'make'.
+case `$am_make -s -f confmf 2> /dev/null` in #(
+*the\ am__doit\ target*)
+  am__include=include
+  am__quote=
+  _am_result=GNU
+  ;;
+esac
+# Now try BSD make style include.
+if test "$am__include" = "#"; then
+   echo '.include "confinc"' > confmf
+   case `$am_make -s -f confmf 2> /dev/null` in #(
+   *the\ am__doit\ target*)
+     am__include=.include
+     am__quote="\""
+     _am_result=BSD
+     ;;
+   esac
+fi
+AC_SUBST([am__include])
+AC_SUBST([am__quote])
+AC_MSG_RESULT([$_am_result])
+rm -f confinc confmf
+])
+
+# Fake the existence of programs that GNU maintainers use.  -*- Autoconf -*-
+
+# Copyright (C) 1997-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MISSING_PROG(NAME, PROGRAM)
+# ------------------------------
+AC_DEFUN([AM_MISSING_PROG],
+[AC_REQUIRE([AM_MISSING_HAS_RUN])
+$1=${$1-"${am_missing_run}$2"}
+AC_SUBST($1)])
+
+# AM_MISSING_HAS_RUN
+# ------------------
+# Define MISSING if not defined so far and test if it is modern enough.
+# If it is, set am_missing_run to use it, otherwise, to nothing.
+AC_DEFUN([AM_MISSING_HAS_RUN],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([missing])dnl
+if test x"${MISSING+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\	*)
+    MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
+  *)
+    MISSING="\${SHELL} $am_aux_dir/missing" ;;
+  esac
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --is-lightweight"; then
+  am_missing_run="$MISSING "
+else
+  am_missing_run=
+  AC_MSG_WARN(['missing' script is too old or missing])
+fi
+])
+
+# Helper functions for option handling.                     -*- Autoconf -*-
+
+# Copyright (C) 2001-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_MANGLE_OPTION(NAME)
+# -----------------------
+AC_DEFUN([_AM_MANGLE_OPTION],
+[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])])
+
+# _AM_SET_OPTION(NAME)
+# --------------------
+# Set option NAME.  Presently that only means defining a flag for this option.
+AC_DEFUN([_AM_SET_OPTION],
+[m4_define(_AM_MANGLE_OPTION([$1]), [1])])
+
+# _AM_SET_OPTIONS(OPTIONS)
+# ------------------------
+# OPTIONS is a space-separated list of Automake options.
+AC_DEFUN([_AM_SET_OPTIONS],
+[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])])
+
+# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET])
+# -------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+AC_DEFUN([_AM_IF_OPTION],
+[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
+
+# Copyright (C) 1999-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_PROG_CC_C_O
+# ---------------
+# Like AC_PROG_CC_C_O, but changed for automake.  We rewrite AC_PROG_CC
+# to automatically call this.
+AC_DEFUN([_AM_PROG_CC_C_O],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([compile])dnl
+AC_LANG_PUSH([C])dnl
+AC_CACHE_CHECK(
+  [whether $CC understands -c and -o together],
+  [am_cv_prog_cc_c_o],
+  [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])])
+  # Make sure it works both with $CC and with simple cc.
+  # Following AC_PROG_CC_C_O, we do the test twice because some
+  # compilers refuse to overwrite an existing .o file with -o,
+  # though they will create one.
+  am_cv_prog_cc_c_o=yes
+  for am_i in 1 2; do
+    if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \
+         && test -f conftest2.$ac_objext; then
+      : OK
+    else
+      am_cv_prog_cc_c_o=no
+      break
+    fi
+  done
+  rm -f core conftest*
+  unset am_i])
+if test "$am_cv_prog_cc_c_o" != yes; then
+   # Losing compiler, so override with the script.
+   # FIXME: It is wrong to rewrite CC.
+   # But if we don't then we get into trouble of one sort or another.
+   # A longer-term fix would be to have automake use am__CC in this case,
+   # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)"
+   CC="$am_aux_dir/compile $CC"
+fi
+AC_LANG_POP([C])])
+
+# For backward compatibility.
+AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])])
+
+# Copyright (C) 2001-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_RUN_LOG(COMMAND)
+# -------------------
+# Run COMMAND, save the exit status in ac_status, and log it.
+# (This has been adapted from Autoconf's _AC_RUN_LOG macro.)
+AC_DEFUN([AM_RUN_LOG],
+[{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD
+   ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD
+   ac_status=$?
+   echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+   (exit $ac_status); }])
+
+# Check to make sure that the build environment is sane.    -*- Autoconf -*-
+
+# Copyright (C) 1996-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_SANITY_CHECK
+# ---------------
+AC_DEFUN([AM_SANITY_CHECK],
+[AC_MSG_CHECKING([whether build environment is sane])
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name.  Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+  *[[\\\"\#\$\&\'\`$am_lf]]*)
+    AC_MSG_ERROR([unsafe absolute working directory name]);;
+esac
+case $srcdir in
+  *[[\\\"\#\$\&\'\`$am_lf\ \	]]*)
+    AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);;
+esac
+
+# Do 'set' in a subshell so we don't clobber the current shell's
+# arguments.  Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+   am_has_slept=no
+   for am_try in 1 2; do
+     echo "timestamp, slept: $am_has_slept" > conftest.file
+     set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+     if test "$[*]" = "X"; then
+	# -L didn't work.
+	set X `ls -t "$srcdir/configure" conftest.file`
+     fi
+     if test "$[*]" != "X $srcdir/configure conftest.file" \
+	&& test "$[*]" != "X conftest.file $srcdir/configure"; then
+
+	# If neither matched, then we have a broken ls.  This can happen
+	# if, for instance, CONFIG_SHELL is bash and it inherits a
+	# broken ls alias from the environment.  This has actually
+	# happened.  Such a system could not be considered "sane".
+	AC_MSG_ERROR([ls -t appears to fail.  Make sure there is not a broken
+  alias in your environment])
+     fi
+     if test "$[2]" = conftest.file || test $am_try -eq 2; then
+       break
+     fi
+     # Just in case.
+     sleep 1
+     am_has_slept=yes
+   done
+   test "$[2]" = conftest.file
+   )
+then
+   # Ok.
+   :
+else
+   AC_MSG_ERROR([newly created file is older than distributed files!
+Check your system clock])
+fi
+AC_MSG_RESULT([yes])
+# If we didn't sleep, we still need to ensure time stamps of config.status and
+# generated files are strictly newer.
+am_sleep_pid=
+if grep 'slept: no' conftest.file >/dev/null 2>&1; then
+  ( sleep 1 ) &
+  am_sleep_pid=$!
+fi
+AC_CONFIG_COMMANDS_PRE(
+  [AC_MSG_CHECKING([that generated files are newer than configure])
+   if test -n "$am_sleep_pid"; then
+     # Hide warnings about reused PIDs.
+     wait $am_sleep_pid 2>/dev/null
+   fi
+   AC_MSG_RESULT([done])])
+rm -f conftest.file
+])
+
+# Copyright (C) 2009-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_SILENT_RULES([DEFAULT])
+# --------------------------
+# Enable less verbose build rules; with the default set to DEFAULT
+# ("yes" being less verbose, "no" or empty being verbose).
+AC_DEFUN([AM_SILENT_RULES],
+[AC_ARG_ENABLE([silent-rules], [dnl
+AS_HELP_STRING(
+  [--enable-silent-rules],
+  [less verbose build output (undo: "make V=1")])
+AS_HELP_STRING(
+  [--disable-silent-rules],
+  [verbose build output (undo: "make V=0")])dnl
+])
+case $enable_silent_rules in @%:@ (((
+  yes) AM_DEFAULT_VERBOSITY=0;;
+   no) AM_DEFAULT_VERBOSITY=1;;
+    *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);;
+esac
+dnl
+dnl A few 'make' implementations (e.g., NonStop OS and NextStep)
+dnl do not support nested variable expansions.
+dnl See automake bug#9928 and bug#10237.
+am_make=${MAKE-make}
+AC_CACHE_CHECK([whether $am_make supports nested variables],
+   [am_cv_make_support_nested_variables],
+   [if AS_ECHO([['TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+	@$(TRUE)
+.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then
+  am_cv_make_support_nested_variables=yes
+else
+  am_cv_make_support_nested_variables=no
+fi])
+if test $am_cv_make_support_nested_variables = yes; then
+  dnl Using '$V' instead of '$(V)' breaks IRIX make.
+  AM_V='$(V)'
+  AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+  AM_V=$AM_DEFAULT_VERBOSITY
+  AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AC_SUBST([AM_V])dnl
+AM_SUBST_NOTMAKE([AM_V])dnl
+AC_SUBST([AM_DEFAULT_V])dnl
+AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl
+AC_SUBST([AM_DEFAULT_VERBOSITY])dnl
+AM_BACKSLASH='\'
+AC_SUBST([AM_BACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
+])
+
+# Copyright (C) 2001-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_STRIP
+# ---------------------
+# One issue with vendor 'install' (even GNU) is that you can't
+# specify the program used to strip binaries.  This is especially
+# annoying in cross-compiling environments, where the build's strip
+# is unlikely to handle the host's binaries.
+# Fortunately install-sh will honor a STRIPPROG variable, so we
+# always use install-sh in "make install-strip", and initialize
+# STRIPPROG with the value of the STRIP variable (set by the user).
+AC_DEFUN([AM_PROG_INSTALL_STRIP],
+[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+# Installed binaries are usually stripped using 'strip' when the user
+# run "make install-strip".  However 'strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the 'STRIP' environment variable to overrule this program.
+dnl Don't test for $cross_compiling = yes, because it might be 'maybe'.
+if test "$cross_compiling" != no; then
+  AC_CHECK_TOOL([STRIP], [strip], :)
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+AC_SUBST([INSTALL_STRIP_PROGRAM])])
+
+# Copyright (C) 2006-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_SUBST_NOTMAKE(VARIABLE)
+# ---------------------------
+# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in.
+# This macro is traced by Automake.
+AC_DEFUN([_AM_SUBST_NOTMAKE])
+
+# AM_SUBST_NOTMAKE(VARIABLE)
+# --------------------------
+# Public sister of _AM_SUBST_NOTMAKE.
+AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
+
+# Check how to create a tarball.                            -*- Autoconf -*-
+
+# Copyright (C) 2004-2013 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_PROG_TAR(FORMAT)
+# --------------------
+# Check how to create a tarball in format FORMAT.
+# FORMAT should be one of 'v7', 'ustar', or 'pax'.
+#
+# Substitute a variable $(am__tar) that is a command
+# writing to stdout a FORMAT-tarball containing the directory
+# $tardir.
+#     tardir=directory && $(am__tar) > result.tar
+#
+# Substitute a variable $(am__untar) that extract such
+# a tarball read from stdin.
+#     $(am__untar) < result.tar
+#
+AC_DEFUN([_AM_PROG_TAR],
+[# Always define AMTAR for backward compatibility.  Yes, it's still used
+# in the wild :-(  We should find a proper way to deprecate it ...
+AC_SUBST([AMTAR], ['$${TAR-tar}'])
+
+# We'll loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none'
+
+m4_if([$1], [v7],
+  [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'],
+
+  [m4_case([$1],
+    [ustar],
+     [# The POSIX 1988 'ustar' format is defined with fixed-size fields.
+      # There is notably a 21 bits limit for the UID and the GID.  In fact,
+      # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343
+      # and bug#13588).
+      am_max_uid=2097151 # 2^21 - 1
+      am_max_gid=$am_max_uid
+      # The $UID and $GID variables are not portable, so we need to resort
+      # to the POSIX-mandated id(1) utility.  Errors in the 'id' calls
+      # below are definitely unexpected, so allow the users to see them
+      # (that is, avoid stderr redirection).
+      am_uid=`id -u || echo unknown`
+      am_gid=`id -g || echo unknown`
+      AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format])
+      if test $am_uid -le $am_max_uid; then
+         AC_MSG_RESULT([yes])
+      else
+         AC_MSG_RESULT([no])
+         _am_tools=none
+      fi
+      AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format])
+      if test $am_gid -le $am_max_gid; then
+         AC_MSG_RESULT([yes])
+      else
+        AC_MSG_RESULT([no])
+        _am_tools=none
+      fi],
+
+  [pax],
+    [],
+
+  [m4_fatal([Unknown tar format])])
+
+  AC_MSG_CHECKING([how to create a $1 tar archive])
+
+  # Go ahead even if we have the value already cached.  We do so because we
+  # need to set the values for the 'am__tar' and 'am__untar' variables.
+  _am_tools=${am_cv_prog_tar_$1-$_am_tools}
+
+  for _am_tool in $_am_tools; do
+    case $_am_tool in
+    gnutar)
+      for _am_tar in tar gnutar gtar; do
+        AM_RUN_LOG([$_am_tar --version]) && break
+      done
+      am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"'
+      am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"'
+      am__untar="$_am_tar -xf -"
+      ;;
+    plaintar)
+      # Must skip GNU tar: if it does not support --format= it doesn't create
+      # ustar tarball either.
+      (tar --version) >/dev/null 2>&1 && continue
+      am__tar='tar chf - "$$tardir"'
+      am__tar_='tar chf - "$tardir"'
+      am__untar='tar xf -'
+      ;;
+    pax)
+      am__tar='pax -L -x $1 -w "$$tardir"'
+      am__tar_='pax -L -x $1 -w "$tardir"'
+      am__untar='pax -r'
+      ;;
+    cpio)
+      am__tar='find "$$tardir" -print | cpio -o -H $1 -L'
+      am__tar_='find "$tardir" -print | cpio -o -H $1 -L'
+      am__untar='cpio -i -H $1 -d'
+      ;;
+    none)
+      am__tar=false
+      am__tar_=false
+      am__untar=false
+      ;;
+    esac
+
+    # If the value was cached, stop now.  We just wanted to have am__tar
+    # and am__untar set.
+    test -n "${am_cv_prog_tar_$1}" && break
+
+    # tar/untar a dummy directory, and stop if the command works.
+    rm -rf conftest.dir
+    mkdir conftest.dir
+    echo GrepMe > conftest.dir/file
+    AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar])
+    rm -rf conftest.dir
+    if test -s conftest.tar; then
+      AM_RUN_LOG([$am__untar <conftest.tar])
+      AM_RUN_LOG([cat conftest.dir/file])
+      grep GrepMe conftest.dir/file >/dev/null 2>&1 && break
+    fi
+  done
+  rm -rf conftest.dir
+
+  AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool])
+  AC_MSG_RESULT([$am_cv_prog_tar_$1])])
+
+AC_SUBST([am__tar])
+AC_SUBST([am__untar])
+]) # _AM_PROG_TAR
+
+m4_include([acinclude.m4])
diff --git a/ast/ast.news b/ast/ast.news
new file mode 100644
index 0000000..0891b79
--- /dev/null
+++ b/ast/ast.news
@@ -0,0 +1,1122 @@
+AST Library
+-----------
+   A new release (V8.4.0) of the Starlink AST (astrometry) library is
+now available.
+
+   AST provides a comprehensive range of facilities for attaching
+world coordinate systems (such as RA/Dec, frequency, etc) to astronomical
+data, for retrieving and interpreting that information and for generating
+graphical output based on it.
+
+   The library should be of interest to anyone writing astronomical
+software which needs to manipulate coordinate system data, especially
+celestial coordinate systems. AST is portable and
+environment-independent.
+
+
+
+
+Main Changes in this Version
+----------------------------
+
+- The PAL library files included in the AST distribution have been updated
+to PAL version 0.9.7.
+
+- Multiple identical NormMaps in series will now be simplified to a
+single NormMap.
+
+- A NormMap that encapsulates a basic Frame will now be simplified to a
+UnitMap.
+
+- The astTimeAdd (AST_TIMEADD) method of the TimeMap class now include an
+extra argument that gives the number of values supplied in the arguments
+array. Note, any existing code that uses this method will need to be
+changed.
+
+- The astSlaAdd (AST_SLAADD) method of the SlaMap class now include an
+extra argument that gives the number of values supplied in the arguments
+array. Note, any existing code that uses this method will need to be
+changed.
+
+- The astSpecAdd (AST_SPECADD) method of the SpecMap class now include an
+extra argument that gives the number of values supplied in the arguments
+array. Note, any existing code that uses this method will need to be
+changed.
+
+- If the astMapRegion (AST_MAPREGION) method is used to map a Region into
+a new Frame that has fewer axes than the original Region, and if the
+inverse transformation of the supplied Mapping does not specify a value
+for the missing axes, then those axes are removed entirely from the
+Region. Previously they were retained, but supplied with bad values. This
+affects the number of mesh points per axes for such Regions, and so
+affects the accuracy of overlap determination.
+
+
+Main Changes in V8.3.0
+----------------------
+
+- A new method called astAxNorm has been added to the Frame class that
+normalises an array of axis values. When used with SkyFrames, it allows
+longitude values to be normalised into the shortest range.
+
+- A bug has been fixed in the Fortran include file AST_PAR that caused constants
+related to PI to be defined as single rather than double precision.
+
+- A bug has been fixed in the astGetRegionBounds method that could
+cause the wrong bounds to be returned for regions spanning a longitude =
+zero singularity.
+
+Main Changes in V8.2.0
+----------------------
+
+- A new class of Mapping called UnitNormMap has been added that converts a
+vector to a unit vector relative to a specified centre, plus length. A
+UnitNormMap has N inputs and N+1 outputs.The lower N output coordinates
+represent a unit vector parallel to the supplied input vector, and the
+(N+1)'th output coordinate is the length of the input vector.
+
+- The restriction that Mappings are immutable has been extended to all
+Mapping classes. This means that attributes representing parameters of
+a Mapping's forward or inverse transformation cannot be changed after
+the Mapping has been created. In order to minimise the risk to existing
+software, this rule does not apply to Mappings that have not yet been
+included in other objects such as CmpMaps or FrameSets, or which have not
+yet been cloned. In other words, an error is reported if an attempt is
+made to change the nature of a Mapping's transformation, but only if the
+reference count of the Mapping is greater than one. The Mapping classes
+affected include: GrismMap, LutMap, PcdMap, SphMap, WcsMap and ZoomMap.
+
+
+Main Changes in V8.1.0
+----------------------
+
+- The configure script has a new option "--without-fortran" that allows
+AST to be built in situations where no Fortran compiler is available. The
+resulting library has no Fortran interface and so cannot be used within
+Fortran applications. Also, the link scripts do not attempt to include the
+fortran runtime libraries.
+
+Main Changes in V8.0.7
+----------------------
+
+- A bug in FitsChan has been fixed which could cause a small shift in
+  spectral axis value when writing out a spectral cube to FITS-WCS headers,
+  This shift occurred only if the celestial axes in the cube were not FK5
+  (RA,Dec).
+
+- Avoid some more compiler warnings.
+
+- A "BadKeyValue" warning is now issued by the FitsChan class if an illegal
+FITS keyword value is encountered. See attribute "Warnings" and function
+"astWarnings".
+
+Main Changes in V8.0.6
+----------------------
+
+- Fix bug in FitsChan that caused SIP headers to be treated as linear
+when creating a FrameSet from the headers.
+
+- Fix bug in LutMap that incorrectly allowed an inverse lutmap to be used
+even if the original LutMap was not monotonic.
+
+- Allow attributes to be set for each plane of a Plot3D.
+
+- Avoid some compiler warnings.
+
+Main Changes in V8.0.5
+----------------------
+
+- The SkyFrame class has a new attribute called SkyTol, which specifies
+the smallest significant distance within the SkyFrame. It is used to
+decide if the Mapping between two SkyFrames can be considered a unit
+transformation. The default value is 0.001 arc-seconds.
+
+- A bug has been fixed in the FitsChan class that prevented illegal
+characters within FITS keyword names (i.e. characters not allowed by the
+FITS standard) being detected. This bug could under some circumstances
+cause a subsequent segmentation violation to occur.
+
+- A "BadKeyName" warning is now issued by the FitsChan class if a FITS
+keyword name is encountered that contains any illegal characters. See
+attribute "Warnings" and function "astWarnings".
+
+Main Changes in V8.0.4
+----------------------
+
+- The behaviour of the astAddFrame method has been changed slightly.
+Previously, astAddFrame modified the FrameSet by storing references to
+the supplied Mapping and Frame objects within the FrameSet. This meant
+that any subsequent changes to the current Frame of the modified FrameSet
+also affected the supplied Frame object. Now, astAddFrame stores deep
+copies of the Mapping and Frame objects (rather than references) within
+the modified FrameSet. This means that subsequent changes to the modified
+FrameSet will now have no effect on the supplied Frame.
+
+- The choice of default tick-mark for time axes has been improved, to avoid
+previous issues which could result in no suitable gap being found, or
+inappropriate tick marks when using formatted dates.
+
+- A new method called astRegionOutline has been added to the Plot class.
+It draws the outline of a supplied AST Region.
+
+- A bug has been fixed that could cause astSimplfy to enter an infinite loop.
+
+- Some improvements have been made to the Mapping simplification process
+that allow more Mappings to be simplified.
+
+- The Frame class has a new read-only attribute called "InternalUnit",
+which gives the units used for the unformatted (i.e. floating-point) axis
+values used internally by application code. For most Frames, the
+InternalUnit value is just the same as the Unit value (i.e. formatted and
+unformatted axis values use the same units). However, the SkyFrame class
+always returns "rad" for InternalUnit, regardless of the value of Unit,
+indicating that floating-point SkyFrame axis values are always in units
+of radians.
+
+- The LutMap class has a new attribute called LutEpsilon, which specifies
+the relative error of the values in the table. It is used to decide if
+the LutMap can be simplified to a straight line.
+
+
+Main Changes in V8.0.3
+----------------------
+
+- Methods astRebin, astRebinSeq, astResample and astTranGrid now report an
+error if an array is specified that has more pixels than can be counted by
+a 32 bit integer.
+
+- The hypertext documentation is now generated using Tex4HT rather
+than latex2html. The format of the hypertext docs has changed
+significantly.
+
+- Another bug fix associated with reading CAR projections from FITS-WCS headers.
+
+- Constructor options strings of the form "..., "%s", text );" can now be
+supplied. This avoids a security issue associated with the alternative
+form "..., text );".
+
+
+Main Changes in V8.0.2
+----------------------
+
+- For security reasons, the change introduced to astAppendString in
+  V8.0.1 has been moved to a new function called astAppendStringf, and
+  astAppendString itself has been reverted to its V8.0.0 version.
+
+
+Main Changes in V8.0.1
+----------------------
+
+- The macro used to invoke the astAppendString utility function has
+  changed to allow printf-style converstions to be included in the
+  supplied text. Any code that uses this macro must be re-compiled.
+
+- The astRebin and astRebinSeq family of functions now include support
+  for arrays with char (byte) and unsigned char (unsigned byte) data types.
+
+- The Base and Current attributes of a FrameSet may now be set using the
+  Domain name or the index of the required Frame.
+
+- The FITS XPH projection is now supported.
+
+- The order of WCS axes within new FITS-WCS headers created by astWrite
+  can now be controlled using a new attribute called FitsAxisOrder.
+
+Main Changes in V8.0.0
+----------------------
+
+- AST is now distributed under the Lesser GPL licence.
+
+- Least squares fitting of N-dimensional polynomials is now done using
+files copied from the C/C++ Minpack package (see
+http://devernay.free.fr/hacks/cminpack/index.html).
+
+- Use of the IAU SOFA library has been replaced by ERFA library, which is
+a re-badged copy of SOFA distributed under a less restrictive license. A
+copy of ERFA is included within AST.
+
+Main Changes in V7.3.4
+----------------------
+
+- By default, the simplification of Polygons no longer checks that the
+edges are not bent by the simplification. A new attribute, SimpVertices,
+can be set to zero in order to re-instate this check.
+
+- The Polygon class has a new mathod, astConvex, that returns a Polygon
+representing the shortest polygon (i.e. convex hull) enclosing a
+specified set of pixel values within a supplied array.
+
+Main Changes in V7.3.3
+----------------------
+
+- The FitsChan class has new attributes CardName and CardComm, which hold
+the keyword name and comment of the current card.
+
+- When reading FITS-WCS headers that include polynomial distortion in the
+SIP format, any inverse transformation specified in the header is now
+ignored and a new inverse is created to replace it based on the supplied
+forward transformation. Previously, an inverse was created only if the
+header did not include an inverse. The accuracy of the inverse
+transformation has also been improved, although it may now be slower to
+evaluate in some circumstances.
+
+- A bug has been fixed that could over-write the FitsChan CarLin attribute
+with a non-zero value if the header contains a spectral axis.
+
+- The default options for each newly created FitsChan can now be
+specified via the environment variable FITSCHAN_OPTIONS.
+
+Main Changes in V7.3.2
+----------------------
+
+- Fix support for reading GLS projections from FITS headers.
+
+- The KeyMap class has new sorting options "KeyAgeUp" and "KeyAgeDown" that
+retain the position of an existing entry if its value is changed. See the
+SortBy attribute.
+
+- A bug has been fixed in FitsChan that caused CDELT keywords for sky
+axes to be treated as radians rather than degrees when reading a FITS
+header, if the corresponding CTYPE values included no projection code.
+
+
+Main Changes in V7.3.1
+----------------------
+
+- Fix bug that could cause a segmenatation fault when reading a FITS TNX
+header.
+
+Main Changes in V7.3.0
+----------------------
+
+- IMPORTANT! The interface for the astRebinSeq<X> (AST_REBINSEQ) family
+of functions has been changed in order to allow a greater number of
+pixels to be pasted into the output array. In C, the "nused" parameter
+is now a pointer to a "int64_t" variable, instead of a simple "int". In
+Fortran, the NUSED argument for AST_REBINSEQ<X> is now an INTEGER*8.
+
+APPLICATION CODE SHOULD BE CHANGED ACCORDINGLY TO AVOID SEGMENTATION
+FAULTS AND OTHER ERRATIC BEHAVIOUR.
+
+- Added a new facility to the FrameSet class to allow each Frame to be
+associated with multiple Mappings, any one of which can be used to
+connect the Frame to the other Frames in the FrameSet. The choice of
+which Mapping to use is controlled by the new "Variant" attribute of the
+FrameSet class.
+
+- Mappings (but not Frames) that have a value set for their Ident attribute
+are now left unchanged by the astSimplify (AST_SIMPLIFY) function.
+
+Main Changes in V7.2.0
+----------------------
+
+- A new method call astMapDefined has been added to the KeyMap class.
+It checks if a gtiven key name has a defined value in a given KeyMap.
+
+
+Main Changes in V7.1.1
+----------------------
+
+- A bug has been fixed in FitsChan that caused inappropriate CTYPE values
+to be generated when writing a FrameSet to FITS-WCS headers if the
+current Frame describes generalised spherical coordinates (i.e. a
+SkyFrame with System=Unknown).
+
+- When a FitsChan is used to write an "offset" SkyFrame (see attribute
+SkyRefIs) to a FITS-WCS encoded header, two alternate axis descriptions
+are now created - one for the offset coordinates and one for the absolute
+coordinates. If such a header is subsequently read back into AST, the
+original offset SkyFrame is recreated.
+
+
+Main Changes in V7.1.0
+----------------------
+
+- IMPORTANT! The default behaviour of astRebinSeq is now NOT to conserve
+flux. To conserve flux, the AST__CONSERVEFLUX flag should be supplied
+when calling astRebinSeq. Without this flag, each output value is a
+weighted mean of the neighbouring input values.
+
+- A new flag AST__NONORM can be used with astRebinSeq<X> to indicate that
+normalisation of the output arrays is not required. In this case no
+weights array need be supplied.
+
+- A bug has been fixed in astAddFrame (AST_ADDFRAME) method that could
+result in the incorrect inversion of Mappings within the FrameSet when
+the AST__ALLFRAMES flag is supplied for the "iframe" parameter.
+
+- The astRate method has been re-written to make it faster and more
+reliable.
+
+Main Changes in V7.0.6
+----------------------
+
+- A bug has been fixed in astRebinSeq<X> which could result in
+incorrect normalisation of the final binned data and variance values.
+
+- When reading a FrameSet from a FITS-DSS header, the keywords CNPIX1 and
+CNPIX2 now default to zero if absent. Previously an error was reported.
+
+Main Changes in V7.0.5
+----------------------
+
+- The FitsChan class can now read FITS headers that use the SAO
+convention for representing distorted TAN projections, based on the use
+of "COi_j" keywords to hold the coefficients of the distortion polynomial.
+
+
+Main Changes in V7.0.4
+----------------------
+
+- The previously private grf3d.h header file is now installed into
+prefix/include.
+
+
+Main Changes in V7.0.3
+----------------------
+
+- A bug has been fixed which could cause an incorrect axis to be used when
+accessing axis attributes within CmpFrames. This could happen if axes
+within the CmpFrame have been permuted.
+
+- A bug has been fixed in the SkyFrame class that could cause the two
+values of the SkyRef and/or SkyRefP attributes to be reversed.
+
+- Bugs have been fixed in the CmpRegion class that should allow the border
+around a compound Region to be plotted more quickly, and more accurately.
+Previously, component Regions nested deeply inside a CmpRegion may have
+been completely or partially ignored.
+
+- A bug has been fixed in the Plot3D class that caused a segmentation
+violation if the MinTick attribute was set to zero.
+
+- The astResampleX set of methods now includes astResampleK and
+astResampleUK that handles 64 bit integer data.
+
+Main Changes in V7.0.2
+----------------------
+
+- The libast_pal library is no longer built if the "--with-external_pal"
+option is used when AST is configured.
+
+
+Main Changes in V7.0.1
+----------------------
+
+- The levmar and wcslib code distributed within AST is now stored in the
+main AST library (libast.so) rather than in separate libraries.
+
+
+Main Changes in V7.0.0
+----------------------
+
+- Fundamental positional astronomy calculations are now performed
+using the IAU SOFA library where possible, and the Starlink PAL library
+otherwise (the PAL library contains a subset of the Fortran Starlink SLALIB
+library re-written in C). Copies of these libraries are bundled with AST
+and so do not need to be obtained or built separately, although external
+copies of SOFA and PAL can be used if necessary by including the
+"--with-external_pal" option when configuring AST.
+
+
+Main Changes in V6.0-1
+-----------------------
+
+- The Spitzer "-SIP" distortion code is now recognised within FITS
+headers that describe non-celestial axes, as well as celestial axes.
+
+- A bug has been fixed that could cause inappropriate equinox values to
+be used when aligning SkyFrames if the AlignSystem attribute is set.
+
+- The format of the version string for AST has changed from
+"<major>.<minor>-<release>" to "<major>.<minor>.<release>".
+
+Main Changes in V6.0
+-----------------------
+
+- This version of AST is the first that can be used with the Python
+AST wrapper module, starlink.Ast, available at http://github.com/timj/starlink-pyast.
+
+- When reading a FITS-WCS header, the FitsChan class now recognises the
+non-standard "TPV" projection code within a CTYPE keyword value. This
+code is used by SCAMP (see www.astromatic.net/software/scamp) to
+represent a distorted TAN projection.
+
+- The Plot class has been changed to remove visual anomalies (such as
+incorrectly rotated numerical axis labels) if the graphics coordinates have
+unequal scales on the X and Y axes.
+
+- The graphics escape sequences used to produce graphical sky axis labels
+can now be changed using the new function astTuneC (AST_TUNEC).
+
+Main Changes in V5.7-2
+-----------------------
+
+- The PolyMap class can now use an iterative Newton-Raphson method to
+evaluate the inverse the inverse transformation if no inverse
+transformation is defined when the PolyMap is created.
+
+- The FitsChan class has a new method astWriteFits (AST_WRITEFITS)
+which writes out all cards currently in the FitsChan to the associated
+external data sink (specified either by the SinkFile attribute or the
+sink function supplied when the FitsChan was created), and then empties
+the FitsChan.
+
+- The FitsChan class has a new method astReadFits (AST_READFITS)
+which forces the FitsChan to reads cards from the associated external
+source and appends them to the end of the FitsChan.
+
+- The FitsChan class has a new read-only attribute called "Nkey", which
+holds the number of keywords for which values are held in a FitsChan.
+
+- The FitsChan class has a new read-only attribute called "CardType", which
+holds the data type of the keyword value for the current card.
+
+- The FitsChan astGetFits<X> (AST_GETFITS<X>) methods can now be used to
+returned the value of the current card.
+
+- If the FitsChan astRead method reads a FITS header that uses the
+-SIP (Spitzer) distortion code within the CTYPE values, but which does
+not provide an inverse polynomial correction, and for which the PolyTran
+method of the PolyMap class fails to create an accurate estimate of the
+inverse polynomial correction, then an iterative method will be used to
+evaluate the inverse correction for each point transformed.
+
+- The Object class has a new function astToString (C only), which creates
+an in-memory textual serialisation of a given AST Object. A corresponding
+new function called astFromString re-creates the Object from its
+serialisation.
+
+
+Main Changes in V5.7-1
+-----------------------
+
+- All classes of Channel can now read to and write from specified text
+files, without the need to provide source and sink functions when the
+Channel is created. The files to use are specified by the new attributes
+SourceFile and SinkFile.
+
+- The FitsChan class now ignores trailing spaces in character-valued WCS
+keywords when reading a FrameSet from a FITS header.
+
+- If the FitsChan astRead method reads a FITS header that uses the -SIP
+(Spitzer) distortion code within the CTYPE values, but which does not
+provide an inverse polynomial correction, the FitsChan class will now use
+the PolyTran method of the PolyMap class to create an estimate of the
+inverse polynomial correction.
+
+
+
+Main Changes in V5.7-0
+-----------------------
+
+- The FitsChan class support for the IRAF-specific "TNX" projection has
+been extended to include reading TNX headers that use a Chebyshev
+representation for the distortion polynomial.
+
+- The FitsChan class support for the IRAF-specific "ZPX" projection has
+been extended to include reading ZPX headers that use simple or Chebyshev
+representation for the distortion polynomial.
+
+- A bug has been fixed in the FitsChan class that caused headers
+including the Spitzer "-SIP" distortion code to be read incorrectly if no
+inverse polynomial was specified in the header.
+
+- A new attribute called PolyTan has been added to the FitsChan class. It
+can be used to indicate that FITS headers that specify a TAN projection
+should be interpreted according to the "distorted TAN" convention
+included in an early draft of FITS-WCS paper II. Such headers are created
+by (for instance) the SCAMP tool (http://www.astromatic.net/software/scamp).
+
+- The PolyMap class now provides a method called astPolyTran (AST_POLYTRAN)
+that adds an inverse transformation to a PolyMap by sampling the forward
+transformation on a regular grid, and then fitting a polynomial function
+from the resulting output values to the grid of input values.
+
+
+Main Changes in V5.6-1
+-----------------------
+
+- Tables can now have any number of parameters describing the global
+properties of the Table.
+
+- Frames now interpret the unit string "A" as meaning "Ampere" rather
+than "Angstrom", as specified by FITS-WCS paper I.
+
+- A bug has been fixed in the astFindFrame (AST_FINDFRAME) method that
+allowed a template Frame of a more specialised class to match a target
+frame of a less specialised class. For example, this bug would allow a
+template SkyFrame to match a target Frame. This no longer happens.
+
+
+Main Changes in V5.6-0
+-----------------------
+
+- New functions astBBuf (AST_BBUF) and astEBuf (AST_EBUF) have been added
+to the Plot class. These control the buffering of graphical output
+produced by other Plot methods.
+
+- New functions astGBBuf and astGEBuf have been added to the interface
+defined by file grf.h. The ast_link command has been modified so that the
+-grf_v3.2 switch loads dummy versions of the new grf functions. This
+means that applications that use the -grf_v3.2 switch should continue to
+build without any change. However, the new public functions astBBuf and
+astEBuf described in the previous item will report an error unless the
+new grf functions are implemented. If you choose to implement them, you
+should modify your linking procedure to use the -grf (or -grf_v5.6)
+switch in place of the older -grf_v3.2 switch. See the description of the
+ast_link command for details of these switches.
+
+- New method astGetRegionMesh (AST_GETREGIONMESH) returns a set of
+positions covering the boundary, or volume, of a supplied Region.
+
+Main Changes in V5.5-0
+-----------------------
+
+- The FitsChan "TabOK" attribute is now an integer value rather
+than a boolean value. As in previous versions, it is used to indicate
+whether the "-TAB" algorithm should be supported by the astRead
+(AST_READ) and astWrite (AST_WRITE) methods, but in addition it is now
+also used to give the version number to assign to any table gebnerated as
+a consequence of calling astWrite (AST_WRITE).  A negative or zero value
+(the default) indicates that support for the -TAB algorithm is not
+available, where as a positive non-zero value indicates that support is
+available and also gives the table version number to use when creating
+subsequent -TAB headers.
+
+
+Main Changes in V5.4-0
+-----------------------
+
+- The FitsChan class now has an option to support reading and writing
+of FITS-WCS headers that use the -TAB algorithm described in FITS-WCS paper
+III. This option is controlled by a new FitsChan attribute called TabOK.
+See the documentation for TabOK for more information.
+
+- A new class called "Table" has been added. A Table is a KeyMap in
+which each entry represents a cell in a two-dimensional table.
+
+- A new class called "FitsTable" has been added. A FitsTable is a
+Table that has an associated FitsChan holding headers appropriate to a
+FITS binary table.
+
+- KeyMaps can now hold byte (i.e. "unsigned char" or BYTE) values.
+
+- A new method called astMapRename (AST_MAPRENAME) has been added to rename
+an existing entry in a KeyMap.
+
+- KeyMaps have a new attribute called KeyCase that can be set to zero to
+make the handling of keys case insensitive.
+
+Main Changes in V5.3-2
+-----------------------
+
+- A bug has been fixed in the FitsChan class that could cause wavelength
+axes to be assigned the units "m/s" when reading WCS information from a
+FITS header.
+
+- The astSet function (AST_SET) now allows literal commas to be included in
+string attribute values. String attribute values that include a literal
+comma should be enclosed in quotation marks.
+
+- A bug in FitsChan has been fixed that caused "-SIN" projection codes within
+FITS-WCS headers to be mis-interpreted, resulting in no FrameSet being
+read by astRead.
+
+- The KeyMap class has a new attribute called "SortBy". It controls
+the order in which keys are returned by the astMapKey (AST_MAPKEY) function.
+Keys can be sorted alphabetically or by age, or left unsorted.
+
+- Access to KeyMaps holding thousands of entries is now significantly
+faster.
+
+- KeyMaps can now hold word (i.e. "short int" or INTEGER*2) values.
+
+Main Changes in V5.3-1
+-----------------------
+
+- The KeyMap class has a new method called astMapCopy/AST_MAPCOPY that
+copies entries from one KeyMap to another KeyMap.
+
+- The KeyMap class now supports entries that have undefined values. A
+new method called astMapPutU/AST_MAPPUTU will store an entry with undefined
+value in a keymap.
+
+- The KeyMap class has a new boolean attribute called MapLocked. If true
+(non-zero), an error is reported if an attempt is made to add any new entries
+to a KeyMap (the value associated with any old entry may still be changed #
+without error). The default is false (zero).
+
+- The Object class has a new method called astHasAttribute/AST_HASATTRIBUTE
+that returns a boolean value indicating if a specified Object has a named
+attribute.
+
+- The SkyFrame class has two new read-only boolean attributes called
+IsLatAxis and IsLonAxis that can be used to determine the nature of a
+specified SkyFrame axis.
+
+- A bug has been fixed in the astRebin(Seq)/AST_REBIN(SEQ) methods
+that could cause flux to be lost from the edges of the supplied array.
+
+- A bug has been fixed in the astRebin(Seq)/AST_REBIN(SEQ) methods
+that caused the first user supplied parameter to be interpreted as the
+full width of the spreading kernel, rather than the half-width.
+
+- The StcsChan class now ignores case when reading STC-S phrases (except
+that units strings are still case sensitive).
+
+- The Channel class now has an Indent attribute that controls indentation
+in the text created by astWrite/AST_WRITE. The StcsIndent and XmlIndent
+attributes have been removed.
+
+- All classes of Channel now use the string "<bad>" to represent the
+floating point value AST__BAD, rather than the literal formatted value
+(typically "-1.79769313486232e+308" ).
+
+- The KeyMap class now uses the string "<bad>" to represent the
+floating point value AST__BAD, rather than the literal formatted value
+(typically "-1.79769313486232e+308" ).
+
+- The KeyMap class has a new method called astMapPutElem<X>/AST_MAPPUTELEM<X>
+that allows a value to be put into a single element of a vector entry in
+a KeyMap. The vector entry is extended automatically to hold the new
+element if required.
+
+- The DSBSpecFrame class now reports an error if the local oscillator
+frequency is less than the absoliute value of the intermediate frequency.
+
+- A new method astQuadApprox produces a quadratic fit to a 2D Mapping.
+
+- A new method astSkyOffsetMap produces a Mapping from absolute SkyFrame
+coordinates to offset SkyFrame coordinates.
+
+
+Main Changes in Version 5.3
+---------------------------
+
+- The details of how a Frame is aligned with another Frame by the
+astFindFrame and astConvert (AST_FINDFRAME and AST_CONVERT) functions
+have been changed. The changes mean that a Frame can now be aligned with
+an instance of a sub-class of Frame, so long as the number of axes and
+the Domain values are consistent. For instance, a basic 2-dimensional
+Frame with Domain "SKY" will now align succesfully with a SkyFrame,
+conversion between the two Frames being achieved using a UnitMap.
+
+- The arrays that supply input values to astMapPut1<X> are now declared
+"const".
+
+- Added method astMatchAxes (AST_MATCHAXES) to the Frame class. This
+allows corresponding axes in two Frames to be identified.
+
+- The astAddFrame (AST_ADDFRAME) method can now be used to append one or
+more axes to all Frames in a FrameSet.
+
+
+Main Changes in Version 5.1
+---------------------------
+
+- A new method called astSetFitsCM (AST_SETFITSCM) has been added to
+the FitsChan class. It stores a pure comment card in a FitsChan (that
+is, a card with no keyword name or equals sign).
+
+- A new attribute called ObsAlt has been added to the Frame class. It
+records the geodetic altitude of the observer, in metres. It defaults to
+zero. It is used when converting times to or from the TDB timescale, or
+converting spectral positions to or from the topocentric rest frame, or
+converting sky positions to or from horizon coordinates. The FitsChan
+class will include its effect when creating a set of values for the
+OBSGEO-X/Y/Z keywords, and will also assign a value to it when reading a
+set of OBSGEO-X/Y/Z keyword values from a FITS header.
+
+- The TimeMap conversions "TTTOTDB" and "TDBTOTT", and the SpecMap
+conversions "TPF2HL" and "HLF2TP", now have an additional argument -
+the observer's geodetic altitude.
+
+- The Polygon class has been modified to make it consistent with the
+IVOA STC definition of a Polygon. Specifically, the inside of a polygon
+is now the area to the left of each edge as the vertices are traversed in
+an anti-clockwise manner, as seen from the inside of the celestial sphere.
+Previously, AST used the anti-clockwise convention, but viewed from the
+outside of the celestial sphere instead of the inside. Any Polygon saved
+using previous versions of AST will be identified and negated automatically
+when read by AST V5.2.
+
+- A new class of Channel, called StcsChan, has been added that allows
+conversion of suitable AST Objects to and from IVOA STC-S format.
+
+- A new method called astDownsize (AST_DOWNSIZE) has been added to the
+Polygon class. It produces a new Polygon that contains a subset of the
+vertices in the supplied Polygon. The subset is chosen to retain the main
+features of the supplied Polygion, in so far as that is possible, within
+specified constraints.
+
+- A new constructor called astOutline (AST_OUTLINE) has been added to the
+Polygon class. Given a 2D data array, it identifies the boundary of a
+region within the array that holds pixels with specified values. It then
+creates a new Polygon to describe this boundary to a specified accuracy.
+
+- A new method called astRemoveRegions (AST_REMOVEREGIONS) has been added
+to the Mapping class. It removes the masking effects of any Regions found
+within a (possibly compound) Mapping or Frame. In effect, it replaces
+each Region found within the Mapping or Frame with a UnitMap or
+equivalent Frame.
+
+- A new set of methods, called astMapGetElem<X> (AST_MAPGETELEM<X>) has
+been added to the KeyMap class. They allow a single element of a vector
+valued entry to be returned.
+
+- A new attribute called KeyError has been added to the KeyMap Class. It
+controls whether the astMapGet... (AST_MAPGET...) family of functions report
+an error if an entry with the requested key does not exist in the KeyMap.
+
+Main Changes in Version 5.1
+---------------------------
+
+- The astUnlock function now has an extra parameter that controls whether
+or not an error is reported if the Object is currently locked by another
+thread.
+
+- The values of the AST__THREADSAFE macro (defined in ast.h) have
+been changed from "yes" and "no" to "1" and "0".
+
+- The PointList class has a new method, astPoints, that copies the axis
+values from the PointList into a supplied array.
+
+- The PointList class has a new (read-only) attribute, ListSize, that
+gives the number of points stored in the PointList.
+
+- A new method (astIntersect) has been added to the Frame class. It
+determines the position at which two geodesic curves intersect.
+
+- The XmlStrict attribute and astXmlWarnings function have been removed.
+The same functionality is now available via the existing Strict attribute,
+a new attribute called ReportLevel, and a new function called astWarnings.
+
+- A bug in the type-checking of Objects passed as arguments to constructor
+functions has been fixed. This bug could lead to applications crashing or
+showing strange behaviour if an inappropriate class of Object was
+supplied as an argument to a constructor.
+
+- The astPickAxes function will now return a Region, if possible, when
+applied to a Region. If this is not possible, a Frame will be returned as
+before.
+
+- The default gap size between the ISO date/time labels used by the Plot
+class when displaying an annotated axis described by a TimeFrame has been
+changed. The changes are meant to improve the labelling of calendar time
+axes that span intervals from a day to a few years.
+
+Main Changes in Version 5.0
+---------------------------
+
+-  AST is now thread-safe. Many of the macro definitions in the "ast.h"
+header file have changed, and so all source code that include "ast.h"
+should be re-compiled.
+
+-  The TimeFrame class now support Local Time as a time scale. The offset
+from UTC to Local Time is specified by a new TimeFrame attribute called
+LTOffset.
+
+-  Addition of a new class called Plot3D that provides facilities for
+producing 3-dimensional annotated coordinate grids.
+
+-  A correction for diurnal aberration is now included when
+converting between AZEL and other celestial coordinate systems. The
+correction is based on the value of the ObsLat Frame attribute (the
+geodetic latitude of the observer).
+
+-  A bug has been fixed which caused the DUT1 attribute to be ignored
+by the SkyFrame class when finding conversions between AZEL and other
+celestial coordinate systems.
+
+-  The Channel class has a new attribute called Strict which controls
+whether or not to report an error if unexpected data items are found
+within an AST Object description read from an external data source. Note,
+the default behaviour is now not to report such errors. This differs from
+previous versions of AST which always reported an error is unexpected
+input items were encountered.
+
+
+
+Main Changes in Version 4.5
+---------------------------
+
+- All FITS-CLASS headers are now created with a frequency axis. If the
+FrameSet supplied to astWrite contains a velocity axis (or any other form
+of spectral axis) it will be converted to an equivalent frequency axis
+before being used to create the FITS-CLASS header.
+
+- The value stored in the FITS-CLASS keyword "VELO-LSR" has been changed
+from the velocity of the source to the velocity of the reference channel.
+
+- Addition of a new method call astPurgeWCS (AST_PURGEWCS) to the FitsChan
+class. This method removes all WCS-related header cards from a FitsChan.
+
+- The astRebinSeq functions now have an extra parameter that is used to
+record the total number of input data val;ues added into the output
+array. This is necessary to correct a flaw in the calculation of output
+variances based on the spread of input values. NOTE, THIS CHANGE WILL
+REQUIRE EXISTING CODE THAT USES ASTREBINSEQ TO BE MODIFIED TO INCLUDE THE
+NEW PARAMETER (CALLED "NUSED").
+- The Plot class now honours the value of the LabelUp attribute even if
+numerical labels are placed around the edge of the Plot. Previously
+LabelUp was only used if the labels were drawn within the interior of
+the plot. The LabelUp attribute controls whether numerical labels are
+drawn horizontally or parallel to the axis they describe.
+- The Plot class has a new attribute called GrfContext that can be used
+to comminicate context information between an application and any
+graphics functions registered with the Plot class via the astGrfSet
+(AST_GRFSET) function.
+- Functions registered with the Plot class using astGrfSet (AST_GRFSET)
+now take a new additional integer parameter, "grfcon". The Plot class
+sets this parameter to value of the Plot's GrfContext attribute before
+calling the graphics function. NOTE, THIS CHANGE WILL REQUIRE EXISTING
+CODE THAT USES astGrfSet (AST_GRFSET) TO BE MODIFIED TO INCLUDE THE
+NEW PARAMETER.
+-  Support has been added for the FITS-WCS "HPX" projection (HEALPix).
+-  A new flag "AST__VARWGT" can be supplied to astRebinSeq. This causes
+the input data values to be weighted using the reciprocals of the input
+variances (if supplied).
+- The Frame class has a new read-only attribute called NormUnit that
+returns the normalised value of the Unit attribute for an axis. Here,
+"normalisation" means cancelling redundant units, etc. So for instance, a
+Unit value of "s*(m/s)" would result in a NormUnit value of "m".
+- A new method astShowMesh has been added to the Region class. It
+displays a mesh of points covering the surface of a Region by writing out
+a table of axis values to standard output.
+- A bug has been fixed that could segmentation violations when setting
+attribute values.
+
+Main Changes in Version 4.4
+---------------------------
+
+- The astFindFrame (AST_FINDFRAME) method can now be used to search a
+CmpFrame for an instance of a more specialised class of Frame (SkyFrame,
+TimeFrame, SpecFrame, DSBSpecFrame or FluxFrame). That is, if an instance
+of one of these classes is used as the "template" when calling
+astFindFrame, and the "target" being searched is a CmpFrame (or a
+FrameSet in which the current Frame is a CmpFrame), then the component
+Frames within the CmpFrame will be searched for an instance of the
+supplied template Frame, and, if found, a suitable Mapping (which will
+include a PermMap to select the required axes from the CmpFrame) will be
+returned by astFindFrame. Note, for this to work, the MaxAxes and MinAxes
+attributes of the template Frame must be set so that they cover a range
+that includes the number of axes in the target CmpFrame.
+
+- The DSBSpecFrame class has a new attribute called AlignSB that
+specifies whether or not to take account of the SideBand attributes when
+aligning two DSBSpecFrames using astConvert (AST_CONVERT).
+
+- The Frame class has a new attribute called Dut1 that can be used to
+store a value for the difference between the UT1 and UTC timescales at
+the epoch referred to by the Frame.
+
+- The number of digits used to format the Frame attributes ObsLat and
+ObsLon has been increased.
+
+- The use of the SkyFrame attribute AlignOffset has been changed. This
+attribute is used to control how two SkyFrames are aligned by astConvert.
+If the template and target SkyFrames both have a non-zero value for
+AlignOffset, then alignment occurs within the offset coordinate systems
+(that is, a UnitMap will always be used to align the two SkyFrames).
+
+- The Plot class has a new attribute called ForceExterior that can be
+used to force exterior (rather than interior) tick marks to be produced,
+even if this would result in less than 3 tick marks being produced.
+
+- The TimeFrame class now supports conversion between angle based
+timescales such as UT1 and atomic based timescales such as UTC.
+
+
+Main Changes in Version 4.3
+---------------------------
+
+- The SpecFrame class has a new attribute called SourceSys that specified
+whether the SourceVel attribute (which specifies the rest frame of the
+source) should be accessed as an apparent radial velocity or a redshift.
+Note, any existing software that assumes that SourceVel always represents
+a velocity in km/s should be changed to allow for the possibility of
+SourceVel representing a redshift value.
+
+-  The astGetFitsS (AST_GETFITSS) function now strips trailing white space
+from the returned string, if the original string contains 8 or fewer
+characters.
+
+
+Main Changes in Version 4.2
+---------------------------
+
+- The SideBand attribute of the DSBSpecFrame class can now take the
+option "LO" in addition to "USB" and "LSB". The new option causes the
+DSBSpecFrame to represent the offset from the local oscillator frequency,
+rather than either of the two sidebands.
+
+- The FitsChan class has been changed so that it writes out a VELOSYS
+keyword when creating a FITS-WCS encoding (VELOSYS indicates the
+topocentric apparent velocity of the standard of rest). FitsChan also
+strips out VELOSYS keywords when reading a FrameSet from a FITS-WCS
+encoding.
+
+- The FitsChan class has a new method called astRetainFits (AST_RETAINFITS)
+that indicates that the current card in the FitsChan should not be
+stripped out of the FitsChan when an AST Object is read from the FitsChan.
+Unless this method is used, all cards that were involved in the creation
+of the AST Object will be stripped from the FitsChan afte a read operation.
+
+- The ast_link_adam and ast_link scripts now ignore the -fsla and -csla
+options, and always link against the minimal cut-down version of SLALIB
+distributed as part of AST.
+
+- A problem with unaligned memory access that could cause bus errors on
+Solaris has been fixed.
+
+- A new function called astTune (or AST_TUNE) has been added which can be
+used to get and set global AST tuning parameters. At the moment there are
+only two such parameter, both of which are concerned with memory management
+within AST.
+
+- A new method called astTranGrid (AST_TRANGRID in Fortran) has been
+added to the Mapping class. This method creates a regular grid of points
+covering a rectangular region within the input space of a Mapping, and
+then transforms this set of points into the output space of the Mapping,
+using a piecewise-continuous linear approximation to the Mapping if
+appropriate in order to achive higher speed.
+
+- A new subclass of Mapping has been added called SwitchMap. A
+SwitchMap represents several alternate Mappings, each of which is used to
+transforms input positions within a different region of the input
+coordinate space.
+
+- A new subclass of Mapping has been added called SelectorMap. A
+SelectorMap tests each input position to see if it falls within one of
+several Regions. If it does, the index of the Region containing the
+input position is returned as the Mapping output.
+
+- The behaviour of the astConvert (AST_CONVERT) method when trying to
+align a CmpFrame with another Frame has been modified. If no conversion
+between positions in the Frame and CmpFrame can be found, an attempt is
+now made to find a conversion between the Frame and one of two component
+Frames contained within the CmpFrame. Thus is should now be possible to
+align a SkyFrame with a CmpFrame containing a SkyFrame and a SpecFrame
+(for instance). The returned Mapping produces bad values for the extra
+axes (i.e. for the SpecFrame axis in the above example).
+
+Main Changes in Version 4.1
+---------------------------
+
+- A new control flag has been added to the AST_RESAMPLE<X>/astResample<X>
+functions which produces approximate flux conservation.
+
+- The SkyFrame class now supports a System value of "AZEL" corresponding
+to horizon (azimuth/elevation) coordinates.
+
+- The FitsChan class allows the non-standard strings "AZ--" and "EL--" to
+be used as axis types in FITS-WCS CTYPE keyword values.
+
+- The Frame class now has attributes ObsLon and ObsLat to specify
+the geodetic longitude and latitude of the observer.
+
+- The ClockLon and ClockLat attributes have been removed from the
+TimeFrame class. Likewise, the GeoLon and GeoLat attributes have been
+removed from the SpecFrame class. Both classes now use the ObsLon and
+ObsLat attributes of the parent Frame class instead. However, the old
+attribute names can be used as synonyms for ObsLat and ObsLon. Also,
+dumps created using the old scheme can be read succesfully by AST V4.1
+and converted to the new form.
+
+- A new function astMapSplit has been added to the Mapping class. This
+splits a Mapping into two component Mappings which, when combined in
+parallel, are equivalent to the original Mapping.
+
+- The default value for the SkyRefIs attribute has been changed from
+"Origin" to "Ignored". This means that if you want to use a SkyFrame
+to represent offsets from some origin position, you must now set the
+SkyRefIs attribute explicitly to either "Pole" or "Origin", in addition
+to assigning the required origin position to the SkyRef attribute.
+
+
+Main Changes in Version 4.0
+---------------------------
+
+- Experimental support for reading IVOA Space-Time-Coordinates (STC)
+descriptions using the XmlChan class has been added. Support is included
+for a subset of V1.20 of the draft STC specification.
+
+- A new set of methods (AST_REBIN<X>/astRebin<X>) has been added to
+the Mapping class. These are accurately flux-conserving alternatives to the
+existing AST_RESAMPLE<X>/astResample<X> methods.
+
+
+Main Changes in Version 3.7
+---------------------------
+
+- Support for time coordinate systems has been introduced throught the
+addition of two new classes, TimeFrame and TimeMap. The TimeFrame is a
+1-dimensional Frame which can be used to describe moments in time (either
+absolute or relative) in various systems (MJD, Julian Epoch, etc.) and
+referred to various time scales (TAI, UTC, UT1, GMST, etc). The TimeMap
+is a Mapping which can transform time values between these various
+systems and time scales.
+
+
+Main Changes in Version 3.6
+---------------------------
+
+- If the Format attribute associated with an axis of a SkyFrame starts
+with a percent character (%), then axis values are now formatted and
+unformatted as a decimal radians value, using the Format syntax of a
+simple Frame.
+
+- The Plot class has a new attribute called Clip which controls the
+clipping performed by AST at the plot boundary.
+
+- The PolyMap class has been modified to allow a PolyMap to be written
+succesfully to a FitsChan using Native encoding.
+
+- A mimimal cut down subset of the C version of SLALIB is now included
+with the AST distribution and built as part of building AST. This means
+that it is no longer necessary to have SLALIB installed separately at
+your site. The SLALIB code included with AST is distrubuted under the
+GPL. The default behaviour of the ast_link script is now to link with
+this internal slalib subset. However, the ``-csla'' option can still be
+used to force linking with an external full C SLALIB library. A new
+option ``-fsla'' has been introduced which forces linking with the
+external full Fortran SLALIB library.
+
+
+Main Changes in Version 3.5
+---------------------------
+
+- AST now provides facilities for representing regions of various
+shapes within a coordinate system. The Region class provides general
+facilities which are independent of the specific shape of region being
+used. Various sub-classes of Region are also now available which provide
+means of creating Regions of specific shape. Facilities provided by the
+Region class include testing points to see if they are inside the
+Region, testing two Regions for overlap, transforming Regions from one
+coordinate system to another, etc.
+
+- A new class of 1-dimensional Frame called FluxFrame has been added which
+can be used to describe various systems for describing ovserved value at a
+single fixed spectral position.
+
+- A new class of 2-dimensional Frame called SpecFluxFrame has been added which
+can be used to describe a 2-d frame spanned by a spectral position axis
+and and an observed value axis.
+
+- A new class of Mapping called RateMap has been added. A RateMap encapsulates
+a previously created Mapping. The inputs of the RateMap correspond to the
+inputs of the encapsulated Mapping. All RateMaps have just a single
+output which correspond to the rate of change of a specified output of
+the encapsulated Mapping with respect to a specified input.
+
+- The SkyFrame class now supports a value of "J2000" for System. This
+system is an equatorial system based on the mean dynamical equator and
+equinox at J2000, and differs slightly from an FK5(J2000) system.
+
+- Methods have been added to the FitsChan class to allow values for named
+keywords to be changed or added.
+
+- The parameter list for the astRate method of the Mapping class has been
+modified. It no longer returns a second derivative estimate. Existing
+code which uses the astRate (AST_RATE) method will need to be changed.
diff --git a/ast/ast_cpp.in b/ast/ast_cpp.in
new file mode 100644
index 0000000..c4fb206
--- /dev/null
+++ b/ast/ast_cpp.in
@@ -0,0 +1,11 @@
+
+# Replacement for the C pre-processor command "cpp" which is not
+# always available. This uses the compiler command "cc" to do the same
+# thing.  Also, this reads from standard input (which "cc" won't do).
+#
+# The name of the CPP processor is substituted in by the ./configure script,
+# based on the result of the AC_PROG_CPP test.
+
+cat >/tmp/ast_cpp_$$.c
+@CPP@ /tmp/ast_cpp_$$.c
+rm -f /tmp/ast_cpp_$$.c
diff --git a/ast/ast_err.h b/ast/ast_err.h
new file mode 100644
index 0000000..c64a736
--- /dev/null
+++ b/ast/ast_err.h
@@ -0,0 +1,486 @@
+/*
+ * C error define file for facility AST (1521)
+ * Generated by the MESSGEN utility
+ */
+
+#ifndef AST_ERROR_DEFINED
+#define AST_ERROR_DEFINED
+
+/* attribute getting error */
+enum { AST__ATGER           	= 233933154 };	/* messid=300 */
+
+/* attribute setting error */
+enum { AST__ATSER           	= 233933162 };	/* messid=301 */
+
+/* attribute value invalid */
+enum { AST__ATTIN           	= 233933170 };	/* messid=302 */
+
+/* axis index invalid */
+enum { AST__AXIIN           	= 233933178 };	/* messid=303 */
+
+/* bad attribute name */
+enum { AST__BADAT           	= 233933186 };	/* messid=304 */
+
+/* zero-sized box given */
+enum { AST__BADBX           	= 233933194 };	/* messid=305 */
+
+/* bad input data */
+enum { AST__BADIN           	= 233933202 };	/* messid=306 */
+
+/* bad number of input coordinates */
+enum { AST__BADNI           	= 233933210 };	/* messid=307 */
+
+/* bad number of output coordinates */
+enum { AST__BADNO           	= 233933218 };	/* messid=308 */
+
+/* PolyMap contains illegal power value */
+enum { AST__BADPW           	= 233933226 };	/* messid=309 */
+
+/* ShiftMap contains no shift information */
+enum { AST__BADSM           	= 233933234 };	/* messid=310 */
+
+/* WinMap contains no bounds information */
+enum { AST__BADWM           	= 233933242 };	/* messid=311 */
+
+/* bad break index */
+enum { AST__BDBRK           	= 233933250 };	/* messid=312 */
+
+/* bad field specifier */
+enum { AST__BDFMT           	= 233933258 };	/* messid=313 */
+
+/* invalid FITS keyword value found */
+enum { AST__BDFTS           	= 233933266 };	/* messid=314 */
+
+/* inappropriate Object supplied */
+enum { AST__BDOBJ           	= 233933274 };	/* messid=315 */
+
+/* wrong number of clipping axes */
+enum { AST__CLPAX           	= 233933282 };	/* messid=316 */
+
+/* range of coordinates invalid */
+enum { AST__CORNG           	= 233933290 };	/* messid=317 */
+
+/* too many breaks in a curve */
+enum { AST__CVBRK           	= 233933298 };	/* messid=318 */
+
+/* array dimensions invalid */
+enum { AST__DIMIN           	= 233933306 };	/* messid=319 */
+
+/* date/time error */
+enum { AST__DTERR           	= 233933314 };	/* messid=320 */
+
+/* invalid use of astEnd */
+enum { AST__ENDIN           	= 233933322 };	/* messid=321 */
+
+/* end of input Channel encountered */
+enum { AST__EOCHN           	= 233933330 };	/* messid=322 */
+
+/* attempt to export Object pointer from level zero */
+enum { AST__EXPIN           	= 233933338 };	/* messid=323 */
+
+/* corrupted FitsChan supplied */
+enum { AST__FCRPT           	= 233933346 };	/* messid=324 */
+
+/* error while formatting coordinate value */
+enum { AST__FMTER           	= 233933354 };	/* messid=325 */
+
+/* Frame index invalid */
+enum { AST__FRMIN           	= 233933362 };	/* messid=326 */
+
+/* FrameSet invalid */
+enum { AST__FRSIN           	= 233933370 };	/* messid=327 */
+
+/* cannot convert FITS data value type */
+enum { AST__FTCNV           	= 233933378 };	/* messid=328 */
+
+/* low level graphics error */
+enum { AST__GRFER           	= 233933386 };	/* messid=329 */
+
+/* invalid Handle */
+enum { AST__INHAN           	= 233933394 };	/* messid=330 */
+
+/* incompatible numbers of coordinates */
+enum { AST__INNCO           	= 233933402 };	/* messid=331 */
+
+/* internal programming error */
+enum { AST__INTER           	= 233933410 };	/* messid=332 */
+
+/* incompatible transformation directions */
+enum { AST__INTRD           	= 233933418 };	/* messid=333 */
+
+/* circular dependency between KeyMaps */
+enum { AST__KYCIR           	= 233933426 };	/* messid=334 */
+
+/* class loader error */
+enum { AST__LDERR           	= 233933434 };	/* messid=335 */
+
+/* invalid lookup table increment */
+enum { AST__LUTII           	= 233933442 };	/* messid=336 */
+
+/* invalid number of lookup table elements */
+enum { AST__LUTIN           	= 233933450 };	/* messid=337 */
+
+/* requested memory size invalid */
+enum { AST__MEMIN           	= 233933458 };	/* messid=338 */
+
+/* not a 2d or 3d MatrixMap */
+enum { AST__MTR23           	= 233933466 };	/* messid=339 */
+
+/* null rotation axis supplied */
+enum { AST__MTRAX           	= 233933474 };	/* messid=340 */
+
+/* bad matrix shapes for multiplication */
+enum { AST__MTRML           	= 233933482 };	/* messid=341 */
+
+/* null matrix supplied */
+enum { AST__MTRMT           	= 233933490 };	/* messid=342 */
+
+/* number of axes invalid */
+enum { AST__NAXIN           	= 233933498 };	/* messid=343 */
+
+/* number of characters invalid */
+enum { AST__NCHIN           	= 233933506 };	/* messid=344 */
+
+/* number of coordinates invalid */
+enum { AST__NCOIN           	= 233933514 };	/* messid=345 */
+
+/* number of coordinates per point invalid */
+enum { AST__NCPIN           	= 233933522 };	/* messid=346 */
+
+/* number of array elements invalid */
+enum { AST__NELIN           	= 233933530 };	/* messid=347 */
+
+/* number of output coordinates too small */
+enum { AST__NOCTS           	= 233933538 };	/* messid=348 */
+
+/* transformation not defined */
+enum { AST__NODEF           	= 233933546 };	/* messid=349 */
+
+/* required FITS keywords missing */
+enum { AST__NOFTS           	= 233933554 };	/* messid=350 */
+
+/* unable to allocate memory */
+enum { AST__NOMEM           	= 233933562 };	/* messid=351 */
+
+/* number of output points too small */
+enum { AST__NOPTS           	= 233933570 };	/* messid=352 */
+
+/* attribute is read-only */
+enum { AST__NOWRT           	= 233933578 };	/* messid=353 */
+
+/* number of points invalid */
+enum { AST__NPTIN           	= 233933586 };	/* messid=354 */
+
+/* Object invalid */
+enum { AST__OBJIN           	= 233933594 };	/* messid=355 */
+
+/* invalid Plot option */
+enum { AST__OPT             	= 233933602 };	/* messid=356 */
+
+/* points data structure invalid */
+enum { AST__PDSIN           	= 233933610 };	/* messid=357 */
+
+/* no numerical labels can be produced */
+enum { AST__PLFMT           	= 233933618 };	/* messid=358 */
+
+/* permutation invalid */
+enum { AST__PRMIN           	= 233933626 };	/* messid=359 */
+
+/* pointer invalid */
+enum { AST__PTRIN           	= 233933634 };	/* messid=360 */
+
+/* range of points invalid */
+enum { AST__PTRNG           	= 233933642 };	/* messid=361 */
+
+/* read error */
+enum { AST__RDERR           	= 233933650 };	/* messid=362 */
+
+/* invalid or corrupted Region structure supplied */
+enum { AST__REGIN           	= 233933658 };	/* messid=363 */
+
+/* invalid attempt to remove last Frame */
+enum { AST__REMIN           	= 233933666 };	/* messid=364 */
+
+/* sky coordinate system invalid */
+enum { AST__SCSIN           	= 233933674 };	/* messid=365 */
+
+/* axis selection invalid */
+enum { AST__SELIN           	= 233933682 };	/* messid=366 */
+
+/* bad SLALIB transformation type */
+enum { AST__SLAIN           	= 233933690 };	/* messid=367 */
+
+/* coordinate transformation not defined */
+enum { AST__TRNND           	= 233933698 };	/* messid=368 */
+
+/* unmatched quotes */
+enum { AST__UNMQT           	= 233933706 };	/* messid=369 */
+
+/* valid area too small */
+enum { AST__VSMAL           	= 233933714 };	/* messid=370 */
+
+/* non-existent longitude or latitude axis */
+enum { AST__WCSAX           	= 233933722 };	/* messid=371 */
+
+/* too few mapping coordinates */
+enum { AST__WCSNC           	= 233933730 };	/* messid=372 */
+
+/* invalid projection parameters */
+enum { AST__WCSPA           	= 233933738 };	/* messid=373 */
+
+/* unknown projection type */
+enum { AST__WCSTY           	= 233933746 };	/* messid=374 */
+
+/* too many Objects in use at once */
+enum { AST__XSOBJ           	= 233933754 };	/* messid=375 */
+
+/* zoom factor invalid */
+enum { AST__ZOOMI           	= 233933762 };	/* messid=376 */
+
+/* bad coordinate index */
+enum { AST__BADCI           	= 233933770 };	/* messid=377 */
+
+/* FrameSet integrity lost */
+enum { AST__ILOST           	= 233933778 };	/* messid=378 */
+
+/* error in IntraMap transformation function */
+enum { AST__ITFER           	= 233933786 };	/* messid=379 */
+
+/* IntraMap transformation function name invalid */
+enum { AST__ITFNI           	= 233933794 };	/* messid=380 */
+
+/* Mapping bounding box not found */
+enum { AST__MBBNF           	= 233933802 };	/* messid=381 */
+
+/* multiple registration of IntraMap transformation function */
+enum { AST__MRITF           	= 233933810 };	/* messid=382 */
+
+/* Object class unknown */
+enum { AST__OCLUK           	= 233933818 };	/* messid=383 */
+
+/* error while unformatting a coordinate value */
+enum { AST__UNFER           	= 233933826 };	/* messid=384 */
+
+/* unregistered IntraMap transformation function */
+enum { AST__URITF           	= 233933834 };	/* messid=385 */
+
+/* grid bounds invalid */
+enum { AST__GBDIN           	= 233933842 };	/* messid=386 */
+
+/* number of grid dimensions invalid */
+enum { AST__NGDIN           	= 233933850 };	/* messid=387 */
+
+/* positional accuracy tolerance invalid */
+enum { AST__PATIN           	= 233933858 };	/* messid=388 */
+
+/* sub-pixel interpolation scheme invalid */
+enum { AST__SISIN           	= 233933866 };	/* messid=389 */
+
+/* scale size in pixels invalid */
+enum { AST__SSPIN           	= 233933874 };	/* messid=390 */
+
+/* error in user-supplied sub-pixel interpolation function */
+enum { AST__UINER           	= 233933882 };	/* messid=391 */
+
+/* error in user-supplied 1-d sub-pixel interpolation kernel */
+enum { AST__UK1ER           	= 233933890 };	/* messid=392 */
+
+/* invalid comma in expression */
+enum { AST__COMIN           	= 233933898 };	/* messid=393 */
+
+/* invalid constant in expression */
+enum { AST__CONIN           	= 233933906 };	/* messid=394 */
+
+/* duplicate variable name */
+enum { AST__DUVAR           	= 233933914 };	/* messid=395 */
+
+/* invalid number of transformation functions */
+enum { AST__INNTF           	= 233933922 };	/* messid=396 */
+
+/* missing or invalid operand in expression */
+enum { AST__MIOPA           	= 233933930 };	/* messid=397 */
+
+/* missing or invalid operator in expression */
+enum { AST__MIOPR           	= 233933938 };	/* messid=398 */
+
+/* missing variable name */
+enum { AST__MISVN           	= 233933946 };	/* messid=399 */
+
+/* missing left parenthesis in expression */
+enum { AST__MLPAR           	= 233933954 };	/* messid=400 */
+
+/* missing right parenthesis in expression */
+enum { AST__MRPAR           	= 233933962 };	/* messid=401 */
+
+/* missing right hand side in function */
+enum { AST__NORHS           	= 233933970 };	/* messid=402 */
+
+/* undefined variable or function in expression */
+enum { AST__UDVOF           	= 233933978 };	/* messid=403 */
+
+/* variable name invalid */
+enum { AST__VARIN           	= 233933986 };	/* messid=404 */
+
+/* wrong number of function arguments in expression */
+enum { AST__WRNFA           	= 233933994 };	/* messid=405 */
+
+/* invalid units specification */
+enum { AST__BADUN           	= 233934002 };	/* messid=406 */
+
+/* no rest frequency is defined */
+enum { AST__NORSF           	= 233934010 };	/* messid=407 */
+
+/* no standard of rest is defined */
+enum { AST__NOSOR           	= 233934018 };	/* messid=408 */
+
+/* invalid SpecMap */
+enum { AST__SPCIN           	= 233934026 };	/* messid=409 */
+
+/* invalid XML name or prefix */
+enum { AST__XMLNM           	= 233934034 };	/* messid=410 */
+
+/* invalid XML comment text */
+enum { AST__XMLCM           	= 233934042 };	/* messid=411 */
+
+/* invalid XML processing instruction target text */
+enum { AST__XMLPT           	= 233934050 };	/* messid=412 */
+
+/* invalid XML content item index */
+enum { AST__XMLIT           	= 233934058 };	/* messid=413 */
+
+/* supplied XML document is not well formed */
+enum { AST__XMLWF           	= 233934066 };	/* messid=414 */
+
+/* Range of log axis scale includes zero */
+enum { AST__ZERAX           	= 233934074 };	/* messid=415 */
+
+/* Invalid parameters for offset sky coordinate system */
+enum { AST__BADOC           	= 233934082 };	/* messid=416 */
+
+/* error getting a named value from a KeyMap */
+enum { AST__MPGER           	= 233934090 };	/* messid=417 */
+
+/* invalid integer index supplied for a KeyMap entry */
+enum { AST__MPIND           	= 233934098 };	/* messid=418 */
+
+/* region cannot be re-centred */
+enum { AST__REGCN           	= 233934106 };	/* messid=419 */
+
+/* attribute has no usable value */
+enum { AST__NOVAL           	= 233934114 };	/* messid=420 */
+
+/* incompatible time scales */
+enum { AST__INCTS           	= 233934122 };	/* messid=421 */
+
+/* invalid TimeMap */
+enum { AST__TIMIN           	= 233934130 };	/* messid=422 */
+
+/* cannot use supplied AstroCoords info */
+enum { AST__STCKEY          	= 233934138 };	/* messid=423 */
+
+/* invalid AstroCoords index */
+enum { AST__STCIND          	= 233934146 };	/* messid=424 */
+
+/* cannot conserve flux whilst resampling an array of data */
+enum { AST__CNFLX           	= 233934154 };	/* messid=425 */
+
+/* Unknown AST tuning parameter name supplied */
+enum { AST__TUNAM           	= 233934162 };	/* messid=426 */
+
+/* Bad value supplied for a public function parameter */
+enum { AST__BDPAR           	= 233934170 };	/* messid=427 */
+
+/* Supplied FrameSet does not contain any independent axes */
+enum { AST__3DFSET          	= 233934178 };	/* messid=428 */
+
+/* Attempt to delete original Plot3D base Frame */
+enum { AST__PXFRRM          	= 233934186 };	/* messid=429 */
+
+/* Illegal syntax for string substitution template */
+enum { AST__BADSUB          	= 233934194 };	/* messid=430 */
+
+/* Incompatible flags for re-sampling or re-binning */
+enum { AST__BADFLG          	= 233934202 };	/* messid=431 */
+
+/* Error locking or unlocking an AST Object */
+enum { AST__LCKERR          	= 233934210 };	/* messid=432 */
+
+/* FITS keyword had undefined value */
+enum { AST__FUNDEF          	= 233934218 };	/* messid=433 */
+
+/* invalid integer index supplied for a KeyMap vector element */
+enum { AST__MPVIN           	= 233934226 };	/* messid=434 */
+
+/* operation specifier invalid */
+enum { AST__OPRIN           	= 233934234 };	/* messid=435 */
+
+/* no inside point found */
+enum { AST__NONIN           	= 233934242 };	/* messid=436 */
+
+/* requested key not found in KeyMap */
+enum { AST__MPKER           	= 233934250 };	/* messid=437 */
+
+/* error putting a named value into a KeyMap */
+enum { AST__MPPER           	= 233934258 };	/* messid=438 */
+
+/* Attempt made to add an entry to a locked KeyMap */
+enum { AST__BADKEY          	= 233934266 };	/* messid=439 */
+
+/* Bad data type */
+enum { AST__BADTYP          	= 233934274 };	/* messid=440 */
+
+/* Column already exists with different properties */
+enum { AST__OLDCOL          	= 233934282 };	/* messid=441 */
+
+/* Bad null value for a FITS table column */
+enum { AST__BADNULL         	= 233934290 };	/* messid=442 */
+
+/* Key string is too long */
+enum { AST__BIGKEY          	= 233934298 };	/* messid=443 */
+
+/* No such column exists in the table */
+enum { AST__BADCOL          	= 233934306 };	/* messid=444 */
+
+/* Table is too large */
+enum { AST__BIGTAB          	= 233934314 };	/* messid=445 */
+
+/* Invalid array size */
+enum { AST__BADSIZ          	= 233934322 };	/* messid=446 */
+
+/* Error reading WCS from FITS binary table */
+enum { AST__BADTAB          	= 233934330 };	/* messid=447 */
+
+/* Cannot access FITS binary table */
+enum { AST__NOTAB           	= 233934338 };	/* messid=448 */
+
+/* Error in levmar Levenberg-Marquardt code */
+enum { AST__LEVMAR          	= 233934346 };	/* messid=449 */
+
+/* Fit failed */
+enum { AST__NOFIT           	= 233934354 };	/* messid=450 */
+
+/* A transformation generated one or more NaN values */
+enum { AST__ISNAN           	= 233934362 };	/* messid=451 */
+
+/* write error */
+enum { AST__WRERR           	= 233934370 };	/* messid=452 */
+
+/* Bad variant Mapping name */
+enum { AST__BDVNM           	= 233934378 };	/* messid=453 */
+
+/* Attempt to add a variant Mapping to a mirror Frame */
+enum { AST__MIRRO           	= 233934386 };	/* messid=454 */
+
+/* Error in cminpack Levenberg-Marquardt code */
+enum { AST__MNPCK           	= 233934394 };	/* messid=455 */
+
+/* Supplied array has too many pixels */
+enum { AST__EXSPIX          	= 233934402 };	/* messid=456 */
+
+/* No mapping found between coordinate systems */
+enum { AST__NOCNV           	= 233934410 };	/* messid=457 */
+
+/* Attempt to change an immutable attribute */
+enum { AST__IMMUT           	= 233934418 };	/* messid=458 */
+
+#endif	/* AST_ERROR_DEFINED */
diff --git a/ast/ast_link.in b/ast/ast_link.in
new file mode 100644
index 0000000..c5759e2
--- /dev/null
+++ b/ast/ast_link.in
@@ -0,0 +1,463 @@
+
+#  N.B. the previous line should be blank.
+#++
+#  Name:
+#     ast_link
+
+#  Purpose:
+#     Link a program with the AST library.
+
+#  Type of Module:
+#     Shell script.
+
+#  Description:
+#     This command should be used when building programs which use the AST
+#     library, in order to generate the correct arguments to allow the compiler
+#     to link your program. The arguments generated are written to standard
+#     output but may be substituted into the compiler command line in the
+#     standard UNIX way using backward quotes (see below).
+#
+#     By default, it is assumed that you are building a stand-alone program
+#     which does not produce graphical output. However, switches are provided
+#     for linking other types of program.
+
+#  Invocation:
+#c     cc program.c -L/star/lib `ast_link [switches]` -o program
+#f     f77 program.f -L/star/lib `ast_link [switches]` -o program
+
+#  Switches:
+#     The following switches may optionally be given to this command to
+#     modify its behaviour:
+#
+#
+#     - ``-csla'': Ignored. Provided for backward compatibility only.
+#
+#     - ``-fsla'': Ignored. Provided for backward compatibility only.
+#
+#     - ``-ems'': Requests that the program be linked so that error messages
+#     produced by the AST library are delivered via the Starlink EMS (Error
+#     Message Service) library (Starlink System Note SSN/4). By default,
+#     error messages are simply written to standard error.
+#
+#     - ``-drama'': Requests that the program be linked so that error messages
+#     produced by the AST library are delivered via the DRAMA Ers (Error
+#     Reporting Service) library. By default, error messages are simply
+#     written to standard error.
+#
+#     - ``-grf'': Requests that no arguments be generated to specify which
+#     2D graphics system is used to display output from the AST library. You
+#     should use this option only if you have implemented an interface to a
+#     new graphics system yourself and wish to provide your own arguments for
+#     linking with it. This switch differs from the other ``grf'' switches in
+#     that it assumes that your graphics module implements the complete
+#     interface required by the current version of AST. If future versions of
+#     AST introduce new functions to the graphics interface, this switch will
+#     cause ``unresolved symbol'' errors to occur during linking, warning you
+#     that you need to implement new functions in your graphics module. To
+#     avoid such errors, you can use one of the other, version-specific,
+#     switches in place of the ``-grf'' switch, but these will cause run-time
+#     errors to be reported if any AST function is invoked which requires
+#     facilities not in the implemented interface.
+#
+#     - ``-grf_v2.0'': This switch is equivalent to the ``-mygrf'' switch.
+#     It indicates that you want to link with your own graphics module
+#     which implements the 2D graphics interface required by V2.0 of AST.
+#
+#     - ``-grf_v3.2'': Indicates that you want to link with your own
+#     graphics module which implements the 2D graphics interface required by
+#     V3.2 of AST.
+#
+#     - ``-grf_v5.6'': Indicates that you want to link with your own
+#     graphics module which implements the 2D graphics interface required by
+#     V5.6 of AST.
+#
+#     - ``-myerr'': Requests that no arguments be generated to specify how
+#     error messages produced by the AST library should be delivered. You
+#     should use this option only if you have implemented an interface to a
+#     new error delivery system yourself and wish to provide your own
+#     arguments for linking with it.
+#
+#     - ``-mygrf'': This switch has been superceeded by the ``-grf'' switch,
+#     but is retained in order to allow applications to be linked with a
+#     graphics module which implements the 2D interface used by AST V2.0. It
+#     is equivalent to the ``-grf_v2.0'' switch.
+#
+#     - ``-pgp'': Requests that the program be linked so that 2D
+#     graphical output from the AST library is displayed via the
+#     Starlink version of the PGPLOT graphics package (which uses GKS
+#     for its output). By default, no 2D graphics package is linked and
+#     this will result in an error at run time if AST routines are
+#     invoked that attempt to generate graphical output.
+#
+#     - ``-pgplot'': Requests that the program be linked so that 2D
+#     graphical output from the AST library is displayed via
+#     the standard (or ``native'') version of the PGPLOT graphics
+#     package. By default, no 2D graphics package is linked and this will
+#     result in an error at run time if AST routines are invoked that
+#     attempt to generate graphical output.
+#
+#     - ``-grf3d'': Requests that no arguments be generated to specify which
+#     3D graphics system is used to display output from the AST library. You
+#     should use this option only if you have implemented an interface to a
+#     new 3D graphics system yourself and wish to provide your own arguments
+#     for linking with it.
+#
+#     - ``-pgp3d'': Requests that the program be linked so that 3D
+#     graphical output from the AST library is displayed via the
+#     Starlink version of the PGPLOT graphics package (which uses GKS
+#     for its output). By default, no 3D graphics package is linked and
+#     this will result in an error at run time if AST routines are
+#     invoked that attempt to generate graphical output.
+#
+#     - ``-pgplot3d'': Requests that the program be linked so that 3D
+#     graphical output from the AST library is displayed via
+#     the standard (or ``native'') version of the PGPLOT graphics
+#     package. By default, no 3D graphics package is linked and this will
+#     result in an error at run time if AST routines are invoked that
+#     attempt to generate graphical output.
+
+#  ERFA & PAL:
+#     The AST distribution includes bundled copies of the ERFA and PAL
+#     libraries. These will be used for fundamental positional astronomy
+#     calculations unless the "--with-external_pal" option was used when
+#     AST was configured. If "--with-external_pal" is used, this script
+#     will include "-lpal" in the returned list of linking options, and
+#     the user should then ensure that external copies of the PAL and
+#     ERFA libraries are available (ERFA functions are used within PAL).
+
+#  Examples:
+#c     cc display.c -L/star/lib `ast_link -pgplot` -o display
+#c        Compiles and links a C program called ``display'' which uses
+#c        the standard version of PGPLOT for graphical output.
+#c     cc plotit.c -L. -L/star/lib `ast_link -grf` -lgrf -o plotit
+#c        Compiles and links a C program ``plotit''. The ``-grf''
+#c        switch indicates that graphical output will be delivered through
+#c        a graphical interface which you have implemented yourself, which
+#c        corresponds to the interface required by the current version of AST.
+#c        Here, this interface is supplied by means of the ``-lgrf'' library
+#c        reference.
+#c     cc plotit.c -L. -L/star/lib `ast_link -grf_v2.0` -lgrf -o plotit
+#c        Compiles and links a C program ``plotit''. The ``-grf_v2.0''
+#c        switch indicates that graphical output will be delivered through
+#c        a graphical interface which you have implemented yourself, which
+#c        corresponds to the interface required by version 2.0 of AST.
+#c        Here, this interface is supplied by means of the ``-lgrf'' library
+#c        reference.
+#f     f77 display.f -L/star/lib `ast_link -pgplot` -o display
+#f        Compiles and links a Fortran program called ``display'' which uses
+#f        the standard version of PGPLOT for graphical output.
+#f     f77 plotit.f -L. -L/star/lib `ast_link -grf` -lgrf -o plotit
+#f        Compiles and links a Fortran program ``plotit''. The ``-grf''
+#f        switch indicates that graphical output will be delivered through
+#f        a graphical interface which you have implemented yourself, which
+#f        corresponds to the interface required by the current version of AST.
+#f        Here, this interface is supplied by means of the ``-lgrf'' library
+#f        reference.
+#f     f77 plotit.f -L. -L/star/lib `ast_link -grf_v2.0` -lgrf -o plotit
+#f        Compiles and links a Fortran program ``plotit''. The ``-grf_v2.0''
+#f        switch indicates that graphical output will be delivered through
+#f        a graphical interface which you have implemented yourself, which
+#f        corresponds to the interface required by version 2.0 of AST.
+#f        Here, this interface is supplied by means of the ``-lgrf'' library
+#f        reference.
+
+#  Copyright:
+#     Copyright (C) 1997-2006 Council for the Central Laboratory of the Research Councils
+#     Copyright (C) 2007-2008 Science & Technology Facilities Council.
+#     All Rights Reserved.
+
+#  Authors:
+#     RFWS: R.F. Warren-Smith (STARLINK)
+#     DSB: David S. Berry (STARLINK)
+#     TIMJ: Tim Jenness (JAC, Hawaii)
+#     {enter_new_authors_here}
+
+#  History:
+#     11-JUN-1996 (RFWS):
+#        Original version.
+#     11-NOV-1996 (RFWS):
+#        Added switches.
+#     18-NOV-1997 (RFWS):
+#        Adapted prologue for document extraction.
+#     28-SEP-1998 (RFWS):
+#        Distinguish between -pgp and -pgplot options.
+#     12-JAN-2001 (DSB):
+#        Move terminating "}" in function "find" onto a new line to
+#        avoid error when run under bash 2.04.11(1) (redhat 7).
+#     3-MAY-2001 (DSB):
+#        Added a terminating ";" to the "done" statement at the end of
+#        the "find" function, so that ast_link can be used on Debian Linux.
+#     23-JAN-2004 (DSB):
+#        Added switches to support older grf implementations.
+#     24-AUG-2004 (DSB):
+#        Removed f77='y' from -ems case.
+#     21-APR-2005 (DSB):
+#        Added "-fsla" option.
+#     16-JUN-2006 (DSB):
+#        Ignore "-fsla" and "-clsa" options, and always use PAL.
+#     26-JUN-2007 (DSB):
+#        Added "-grf3d", "-pgplot3d" and "-pgp3d" flags.
+#     13-NOV-2008 (TIMJ):
+#        Add -drama option for DRAMA Ers support.
+#     3-MAR-2011 (DSB):
+#        Added grf 5.6 options.
+#     {enter_further_changes_here}
+
+#  Bugs:
+#     {note_any_bugs_here}
+
+#--
+
+#  This line is edited during configuration of this script to define a list
+#  of the libraries that must be linked in order to resolve Fortran 77
+#  references made from within a C main program. Typically, these will arise
+#  from libraries written in Fortran which the AST library (or the C
+#  program) calls.  The value here is worked out by the autoconf macro
+#  AC_FC_LIBRARY_LDFLAGS.
+      flibs='@FCLIBS@'
+
+#  This function searches the directory path specified in PATH, looking for
+#  an executable file which is not a directory. If found, it echos the full
+#  file name to standard output. Otherwise, it outputs nothing.
+      find() { IFS=':'; for d in $PATH; do f="${d:=.}/${1}"
+                  test -x "${f}" -a ! -d "${f}" && echo "${f}" && break
+               done;
+             }
+
+#  Initialise linking options.
+      err=''
+      grf=''
+      grf3d=''
+      sla=''
+      f77=''
+
+#  Interpret command line switches.
+#  --------------------------------
+      while :; do
+         case "${1}" in
+
+#  -csla - Previously used to request C version of SLALIB. Now ignored.
+         -csla)
+#            sla='c'
+            shift;;
+
+#  -fsla - Previously used to request Fortran version of SLALIB. Now ignored.
+         -fsla)
+#            sla='f'
+            shift;;
+
+#  -ems - Requests error reporting through EMS.
+         -ems)
+            err='ems'
+            shift;;
+
+#  -drama - Requests error reporting through DRAMA Ers.
+         -drama)
+            err='drama'
+            shift;;
+
+#  -myerr - Requests no error reporting.
+         -myerr)
+            err='my'
+            shift;;
+
+#  -grf - Requests no 2D graphics.
+         -grf)
+            grf='current'
+            shift;;
+
+#  -mygrf - Requests no 2D graphics, except for null implementations of
+#  functions aded to the grf interface after AST V2.0.
+         -mygrf)
+            grf='v2.0'
+            shift;;
+
+#  -grf_v2.0 - Requests no 2D graphics, except for null implementations of
+#  functions aded to the grf interface after AST V2.0.
+         -grf_v2.0)
+            grf='v2.0'
+            shift;;
+
+#  -grf_v3.2 - Requests no 2D graphics, except for null implementations of
+#  functions aded to the grf interface after AST V3.2.
+         -grf_v3.2)
+            grf='v3.2'
+            shift;;
+
+#  -grf_v5.6 - Requests no 2D graphics, except for null implementations of
+#  functions aded to the grf interface after AST V5.6.
+         -grf_v5.6)
+            grf='v5.6'
+            shift;;
+
+#  -pgp - Requests 2D graphical output through Starlink PGPLOT.
+         -pgp)
+            grf='pgp'
+            shift;;
+
+#  -pgplot - Requests 2D graphical output through native PGPLOT.
+         -pgplot)
+            grf='pgplot'
+            shift;;
+
+#  -grf3d - Requests no 3D graphics.
+         -grf3d)
+            grf3d='current'
+            shift;;
+
+#  -pgp3d - Requests 3D graphical output through Starlink PGPLOT.
+         -pgp3d)
+            grf3d='pgp'
+            shift;;
+
+#  -pgplot3d - Requests 3D graphical output through native PGPLOT.
+         -pgplot3d)
+            grf3d='pgplot'
+            shift;;
+
+#  Once all switches have been read, continue with the rest of the script.
+         '') break;;
+
+#  Catch unrecognised arguments and report an error.
+         *)
+            echo >&2 "ast_link: unknown argument \""${1}"\" given"
+            exit 1;;
+         esac
+      done
+
+#  Link with the main AST library.
+#  -------------------------------
+#  Start forming the list of arguments with the main AST library itself.
+      args='-last '
+
+#  Generate arguments for linking PAL.
+#  -----------------------------------
+
+      case "@EXTERNAL_PAL@" in
+
+#  If we configured --with-external_pal include a link option to pick up
+#  an external PAL library.
+      1) args="${args} -lpal";;
+
+#  Otherwise, use the internal PAL & ERFA libraries.
+      *) args="${args} -last_pal";;
+
+      esac
+
+#  Generate arguments for linking the 2D graphics system.
+#  ------------------------------------------------------
+      case "${grf}" in
+
+#  If using Starlink PGPLOT, link with the AST PGPLOT interface and
+#  the Fortran library via the PGP link script (if found).
+      pgp) args="${args} -last_pgplot `\`find pgp_link\``"
+           f77='y';;
+
+#  If using native PGPLOT, link with the AST PGPLOT interface and the
+#  Fortran library via the PGPLOT link script (if found).
+      pgplot) args="${args} -last_pgplot `\`find pgplot_link\``"
+              f77='y';;
+
+#  If using own graphics which conform to the requirements of the current
+#  version of AST, do not produce any arguments.
+      current) :;;
+
+#  If using own graphics which conform to the requirements of version 5.6
+#  of AST, produce arguments which link in dummy implementations of any
+#  functions which are required by the current version of AST but which were
+#  not required by version 5.6.
+      v5.6) :;;
+
+#  If using own graphics which conform to the requirements of version 3.2
+#  of AST, produce arguments which link in dummy implementations of any
+#  functions which are required by the current version of AST but which were
+#  not required by version 3.2.
+      v3.2) args="${args} -last_grf_5.6";;
+
+#  If using own graphics which conform to the requirements of version 2.0
+#  of AST, produce arguments which link in dummy implementations of any
+#  functions which are required by the current version of AST but which were
+#  not required by version 2.0.
+      v2.0) args="${args} -last_grf_3.2 -last_grf_5.6";;
+
+#  Default graphics (none) requires linking with all the default (null) AST
+#  "grf" modules.
+      *) args="${args} -last_grf_2.0 -last_grf_3.2 -last_grf_5.6";;
+      esac
+
+
+#  Generate arguments for linking the 3D graphics system.
+#  ------------------------------------------------------
+      case "${grf3d}" in
+
+#  If using Starlink PGPLOT, link with the AST 3D PGPLOT interface and
+#  the Fortran library via the PGP link script (if found).
+      pgp) args="${args} -last_pgplot3d `\`find pgp_link\``"
+           f77='y';;
+
+#  If using native PGPLOT, link with the AST 3D PGPLOT interface and the
+#  Fortran library via the PGPLOT link script (if found).
+      pgplot) args="${args} -last_pgplot3d `\`find pgplot_link\``"
+              f77='y';;
+
+#  If using own 3D graphics which conform to the requirements of the current
+#  version of AST, do not produce any arguments.
+      current) :;;
+
+#  Default graphics (none) requires linking with all the default (null) AST
+#  "grf3d" modules.
+      *) args="${args} -last_grf3d";;
+      esac
+
+
+
+#  Make a second pass through the AST library.
+#  -------------------------------------------
+#  This library is a link to the main AST library and results in a second
+#  pass to resolve any backward references generated by the other modules
+#  used above. A different library name must be used to avoid the two passes
+#  being merged into one (either below, or by other link scripts).
+      args="${args} -last_pass2"
+
+#  Generate arguments for linking the error reporting system.
+#  ----------------------------------------------------------
+      case "${err}" in
+
+#  If using EMS, link with the AST EMS interface and the EMS library via the
+#  link script (if found).
+      ems) args="${args} -last_ems `\`find ems_link\``";;
+
+#  If using DRAMA, link with the AST DRAMA interface and the DRAMA Ers library
+#  via the link script (if found).
+      drama) args="${args} -last_drama -lers";;
+
+#  If using own error reporting, do not produce any arguments.
+      my) :;;
+
+#  Default error reporting requires linking with the default AST "err" module.
+      *) args="${args} -last_err";;
+      esac
+
+#  Link with the maths library.
+#  ----------------------------
+      args="${args} -lm"
+
+#  Link with the starmem library, if available.
+#  --------------------------------------------
+      args="${args} `\`find starmem_link\``"
+
+#  Resolve Fortran 77 references.
+#  ------------------------------
+#  If libraries written in Fortran are being linked against, then include
+#  additional libaries needed to resolve the references these will produce
+#  (in the event that the main program is not Fortran).
+      if test "${f77}" = 'y'; then args="${args} ${flibs}"; fi
+
+#  Pass the resulting argument list through an awk script which eliminates
+#  all except the last reference to each library.
+      echo "${args}" \
+           | awk 'BEGIN{RS=" ";FS="\n"}
+                  {if($1)f[i++]=$1}
+                  END{for(;i--;)if(!w[f[i]]++)l=f[i]" "l;print l}'
+
+#  End of script.
diff --git a/ast/ast_link_adam b/ast/ast_link_adam
new file mode 100644
index 0000000..f776bb7
--- /dev/null
+++ b/ast/ast_link_adam
@@ -0,0 +1,406 @@
+
+#  N.B. the previous line should be blank.
+#++
+#  Name:
+#     ast_link_adam
+
+#  Purpose:
+#     Link an ADAM program with the AST library.
+
+#  Type of Module:
+#     Shell script.
+
+#  Description:
+#     This command should only be used when building Starlink ADAM programs
+#     which use the AST library, in order to generate the correct arguments
+#     to allow the ADAM ``alink'' command to link the program. The arguments
+#     generated are written to standard output but may be substituted into
+#     the ``alink'' command line in the standard UNIX way using backward
+#     quotes (see below).
+#
+#     By default, it is assumed that you are building an ADAM program which
+#     does not produce graphical output. However, switches are provided for
+#     linking other types of program. This command should not be used when
+#     building stand-alone (non-ADAM) programs. Use the ``ast_link'' command
+#     instead.
+
+#  Invocation:
+#c     alink program.o -L/star/lib `ast_link_adam [switches]`
+#f     alink program.f -L/star/lib `ast_link_adam [switches]`
+
+#  Switches:
+#     The following switches may optionally be given to this command to
+#     modify its behaviour:
+#
+#     - ``-csla'': Ignored. Provided for backward compatibility only.
+#
+#     - ``-fsla'': Ignored. Provided for backward compatibility only.
+#
+#     - ``-grf'': Requests that no arguments be generated to specify which
+#     2D graphics system is used to display output from the AST library. You
+#     should use this option only if you have implemented an interface to a
+#     new graphics system yourself and wish to provide your own arguments for
+#     linking with it. This switch differs from the other ``grf'' switches in
+#     that it assumes that your graphics module implements the complete
+#     interface required by the current version of AST. If future versions of
+#     AST introduce new functions to the graphics interface, this switch will
+#     cause ``unresolved symbol'' errors to occur during linking, warning you
+#     that you need to implement new functions in your graphics module. To
+#     avoid such errors, you can use one of the other, version-specific,
+#     switches in place of the ``-grf'' switch, but these will cause run-time
+#     errors to be reported if any AST function is invoked which requires
+#     facilities not in the implemented interface.
+#
+#     - ``-grf_v2.0'': This switch is equivalent to the ``-mygrf'' switch.
+#     It indicates that you want to link with your own graphics module which
+#     implements the 2D graphics interface required by V2.0 of AST.
+#
+#     - ``-grf_v3.2'': Indicates that you want to link with your own graphics
+#     module which implements the 2D graphics interface required by V3.2 of AST.
+#
+#     - ``-grf_v5.6'': Indicates that you want to link with your own graphics
+#     module which implements the 2D graphics interface required by V5.6 of AST.
+#
+#     - ``-myerr'': Requests that no arguments be generated to specify how
+#     error messages produced by the AST library should be delivered. You
+#     should use this option only if you have implemented an interface to a
+#     new error delivery system yourself and wish to provide your own
+#     arguments for linking with it. By default, error messages are delivered
+#     in the standard ADAM way via the EMS Error Message Service (Starlink
+#     System Note SSN/4).
+#
+#     - ``-mygrf'': This switch has been superceeded by the ``-grf'' switch,
+#     but is retained in order to allow applications to be linked with a
+#     graphics module which implements the interface used by AST V2.0. It is
+#     equivalent to the ``-grf_v2.0'' switch.
+#
+#     - ``-pgp'': Requests that the program be linked so that 2D
+#     graphical output from the AST library is displayed via the
+#     Starlink version of the PGPLOT graphics package (which uses GKS
+#     for its output). By default, no graphics package is linked and
+#     this will result in an error at run time if AST routines are
+#     invoked that attempt to generate graphical output.
+#
+#     - ``-pgplot'': Requests that the program be linked so that 2D
+#     graphical output from the AST library is displayed via the
+#     standard (or ``native'') version of the PGPLOT graphics
+#     package. By default, no graphics package is linked and this will
+#     result in an error at run time if AST routines are invoked that
+#     attempt to generate graphical output.
+#
+#     - ``-grf3d'': Requests that no arguments be generated to specify which
+#     3D graphics system is used to display output from the AST library. You
+#     should use this option only if you have implemented an interface to a
+#     new 3D graphics system yourself and wish to provide your own arguments
+#     for linking with it.
+#
+#     - ``-pgp3d'': Requests that the program be linked so that 3D
+#     graphical output from the AST library is displayed via the
+#     Starlink version of the PGPLOT graphics package (which uses GKS
+#     for its output). By default, no 3D graphics package is linked and
+#     this will result in an error at run time if AST routines are
+#     invoked that attempt to generate graphical output.
+#
+#     - ``-pgplot3d'': Requests that the program be linked so that 3D
+#     graphical output from the AST library is displayed via
+#     the standard (or ``native'') version of the PGPLOT graphics
+#     package. By default, no 3D graphics package is linked and this will
+#     result in an error at run time if AST routines are invoked that
+#     attempt to generate graphical output.
+
+#  SLALIB:
+#     The AST distribution includes a cut down subset of the C version of
+#     the SLALIB library written by Pat Wallace. This subset contains only
+#     the functions needed by the AST library. It is built as part of the
+#     process of building AST and is distributed under GPL (and is thus
+#     compatible with the AST license). Previous version of this script
+#     allowed AST applications to be linked against external SLALIB
+#     libraries (either Fortran or C) rather than the internal version.
+#     The current version of this script does not provide this option,
+#     and always uses the internal SLALIB library. However, for backward
+#     compatibility, this script still allows the "-fsla" and "-csla" flags
+#     (previously used for selecting which version of SLALIB to use) to be
+#     specified, but they will be ignored.
+
+#  Examples:
+#c     alink display.o -L/star/lib `ast_link_adam -pgplot`
+#c        Links an ADAM program ``display'' which uses the standard
+#c        version of PGPLOT for graphical output.
+#c     alink plotit.o -L. -L/star/lib `ast_link_adam -grf` -lgrf
+#c        Links an ADAM program ``plotit'', written in C. The ``-grf''
+#c        switch indicates that graphical output will be delivered through
+#c        a graphical interface which you have implemented yourself, which
+#c        corresponds to the interface required by the current version of AST.
+#c        Here, this interface is supplied by means of the ``-lgrf'' library
+#c        reference.
+#c     alink plotit.o -L. -L/star/lib `ast_link_adam -grf_v2.0` -lgrf
+#c        Links an ADAM program ``plotit'', written in C. The ``-grf_v2.0''
+#c        switch indicates that graphical output will be delivered through
+#c        a graphical interface which you have implemented yourself, which
+#c        corresponds to the interface required by version 2.0 of AST. Here,
+#c        this interface is supplied by means of the ``-lgrf'' library
+#c        reference.
+#f     alink display.f -L/star/lib `ast_link_adam -pgplot`
+#f        Compiles and links an ADAM Fortran program called ``display'' which
+#f        uses the standard version of PGPLOT for graphical output.
+#f     alink plotit.f -L. -L/star/lib `ast_link_adam -grf` -lgrf
+#f        Compiles and links an ADAM Fortran program ``plotit''. The ``-grf''
+#f        switch indicates that graphical output will be delivered through
+#f        a graphical interface which you have implemented yourself, which
+#f        corresponds to the interface required by the current version of AST.
+#f        Here, this interface is supplied by means of the ``-lgrf'' library
+#f        reference.
+#f     alink plotit.f -L. -L/star/lib `ast_link_adam -grf_v2.0` -lgrf
+#f        Compiles and links an ADAM Fortran program ``plotit''. The ``-grf_v2.0''
+#f        switch indicates that graphical output will be delivered through
+#f        a graphical interface which you have implemented yourself, which
+#f        corresponds to the interface required by version 2.0 of AST.
+#f        Here, this interface is supplied by means of the ``-lgrf'' library
+#f        reference.
+
+#  Copyright:
+#     Copyright (C) 1997-2006 Council for the Central Laboratory of the Research Councils
+
+#  Authors:
+#     RFWS: R.F. Warren-Smith (STARLINK)
+#     {enter_new_authors_here}
+
+#  History:
+#     11-NOV-1996 (RFWS):
+#        Original version.
+#     18-NOV-1997 (RFWS):
+#        Adapted prologue for document extraction.
+#     28-SEP-1998 (RFWS):
+#        Distinguish between -pgp and -pgplot options.
+#     23-JAN-2004 (DSB):
+#        Added switches to support older grf implementations.
+#     21-APR-2005 (DSB):
+#        Added "-fsla" option.
+#     16-JUN-2006 (DSB):
+#        Ignore "-fsla" and "-clsa" options, and always use PAL.
+#     22-AUG-2007 (DSB):
+#        Added "-grf3d", "-pgplot3d" and "-pgp3d" flags.
+#     4-MAR-2011 (DSB):
+#        Added v5.6 grf options.
+#     {enter_changes_here}
+
+#  Bugs:
+#     {note_any_bugs_here}
+
+#--
+
+#  This function searches the directory path specified in PATH, looking for
+#  an executable file which is not a directory. If found, it echos the full
+#  file name to standard output. Otherwise, it outputs nothing.
+      find() { IFS=':'; for d in $PATH; do f="${d:=.}/${1}"
+                  test -x "${f}" -a ! -d "${f}" && echo "${f}" && break
+               done;
+             }
+
+#  Initialise linking options.
+      err=''
+      grf=''
+      grf3d=''
+      sla=''
+
+#  Interpret command line switches.
+#  --------------------------------
+      while :; do
+         case "${1}" in
+
+#  -csla - Previously used to request C version of SLALIB. Now ignored.
+         -csla)
+#            sla='c'
+            shift;;
+
+#  -fsla - Previously used to request Fortran version of SLALIB. Now ignored.
+         -fsla)
+#            sla='f'
+            shift;;
+
+#  -myerr - Requests no error reporting.
+         -myerr)
+            err='my'
+            shift;;
+
+#  -grf - Requests no 2D graphics.
+         -grf)
+            grf='current'
+            shift;;
+
+#  -mygrf - Requests no 2D graphics, except for null implementations of
+#  functions aded to the grf interface after AST V2.0.
+         -mygrf)
+            grf='v2.0'
+            shift;;
+
+#  -grf_v2.0 - Requests no 2D graphics, except for null implementations of
+#  functions aded to the grf interface after AST V2.0.
+         -grf_v2.0)
+            grf='v2.0'
+            shift;;
+
+#  -grf_v3.2 - Requests no 2D graphics, except for null implementations of
+#  functions aded to the grf interface after AST V3.2.
+         -grf_v3.2)
+            grf='v3.2'
+            shift;;
+
+#  -grf_v5.6 - Requests no 2D graphics, except for null implementations of
+#  functions added to the grf interface after AST V5.6.
+         -grf_v5.6)
+            grf='v5.6'
+            shift;;
+
+#  -pgp - Requests 2D graphical output through Starlink PGPLOT.
+         -pgp)
+            grf='pgp'
+            shift;;
+
+#  -pgplot - Requests 2D graphical output through native PGPLOT.
+         -pgplot)
+            grf='pgplot'
+            shift;;
+
+#  -grf3d - Requests no 3D graphics.
+         -grf3d)
+            grf3d='current'
+            shift;;
+
+#  -pgp3d - Requests 3D graphical output through Starlink PGPLOT.
+         -pgp3d)
+            grf3d='pgp'
+            shift;;
+
+#  -pgplot3d - Requests 3D graphical output through native PGPLOT.
+         -pgplot3d)
+            grf3d='pgplot'
+            shift;;
+
+#  Once all switches have been read, continue with the rest of the script.
+         '') break;;
+
+#  Catch unrecognised switches and report an error.
+         *)
+            echo >&2 "ast_link_adam: unknown argument \""${1}"\" given"
+            exit 1;;
+         esac
+      done
+
+#  Link with the main AST library.
+#  -------------------------------
+#  Start forming the list of arguments with the main AST library itself.
+      args='-last'
+
+#  Generate arguments for linking PAL.
+#  -----------------------------------
+
+      case "0" in
+
+#  If we configured --with-external_pal include a link option to pick up
+#  an external PAL library.
+      1) args="${args} -lpal";;
+
+#  Otherwise, use the internal PAL & ERFA libraries.
+      *) args="${args} -last_pal";;
+
+      esac
+
+#  Generate arguments for linking the 2D graphics system.
+#  ------------------------------------------------------
+      case "${grf}" in
+
+#  If using Starlink PGPLOT, link with the AST PGPLOT interface and
+#  the Fortran library via the PGP link script.
+      pgp) args="${args} -last_pgplot `pgp_link_adam`";;
+
+#  If using native PGPLOT, link with the AST PGPLOT interface and
+#  the Fortran library via the PGPLOT link script.
+      pgplot) args="${args} -last_pgplot `pgplot_link_adam`";;
+
+#  If using own graphics which conform to the requirements of the current
+#  version of AST, do not produce any arguments.
+      current) :;;
+
+#  If using own graphics which conform to the requirements of version 5.6
+#  of AST, produce arguments which link in dummy implementations of any
+#  functions which are required by the current version of AST but which were
+#  not required by version 5.6.
+      v5.6) :;;
+
+#  If using own graphics which conform to the requirements of version 3.2
+#  of AST, produce arguments which link in dummy implementations of any
+#  functions which are required by the current version of AST but which were
+#  not required by version 3.2.
+      v3.2) args="${args} -last_grf_5.6";;
+
+#  If using own graphics which conform to the requirements of version 2.0
+#  of AST, produce arguments which link in dummy implementations of any
+#  functions which are required by the current version of AST but which were
+#  not required by version 2.0.
+      v2.0) args="${args} -last_grf_3.2 -last_grf_5.6";;
+
+#  Default graphics (none) requires linking with all the default (null) AST
+#  "grf" modules.
+      *) args="${args} -last_grf_2.0 -last_grf_3.2 -last_grf_5.6";;
+      esac
+
+#  Generate arguments for linking the 3D graphics system.
+#  ------------------------------------------------------
+      case "${grf3d}" in
+
+#  If using Starlink PGPLOT, link with the AST 3D PGPLOT interface and
+#  the Fortran library via the PGP link script (if found).
+      pgp) args="${args} -last_pgplot3d `\`find pgp_link\``"
+           f77='y';;
+
+#  If using native PGPLOT, link with the AST 3D PGPLOT interface and the
+#  Fortran library via the PGPLOT link script (if found).
+      pgplot) args="${args} -last_pgplot3d `\`find pgplot_link\``"
+              f77='y';;
+
+#  If using own 3D graphics which conform to the requirements of the current
+#  version of AST, do not produce any arguments.
+      current) :;;
+
+#  Default graphics (none) requires linking with all the default (null) AST
+#  "grf3d" modules.
+      *) args="${args} -last_grf3d";;
+      esac
+
+#  Make a second pass through the AST library.
+#  -------------------------------------------
+#  This library is a link to the main AST library and results in a second
+#  pass to resolve any backward references generated by the other modules
+#  used above. A different library name must be used to avoid the two passes
+#  being merged into one (either below, or by other link scripts).
+      args="${args} -last_pass2"
+
+#  Generate arguments for linking the error reporting system.
+#  ----------------------------------------------------------
+      case "${err}" in
+
+#  If using own error reporting, do not produce any arguments.
+      my) :;;
+
+#  Default error reporting requires linking with the AST EMS interface and
+#  the EMS library via the link script.
+      *) args="${args} -last_ems `ems_link_adam`";;
+      esac
+
+#  Link with the maths library.
+#  ----------------------------
+      args="${args} -lm"
+
+#  Link with the starmem library, if available.
+#  --------------------------------------------
+      args="${args} `\`find starmem_link\``"
+
+#  Pass the resulting argument list through an awk script which eliminates
+#  all except the last reference to each library.
+      echo "${args}" \
+           | awk 'BEGIN{RS=" ";FS="\n"}
+                  {if($1)f[i++]=$1}
+                  END{for(;i--;)if(!w[f[i]]++)l=f[i]" "l;print l}'
+
+#  End of script.
diff --git a/ast/ast_link_adam.in b/ast/ast_link_adam.in
new file mode 100644
index 0000000..df93c6c
--- /dev/null
+++ b/ast/ast_link_adam.in
@@ -0,0 +1,406 @@
+
+#  N.B. the previous line should be blank.
+#++
+#  Name:
+#     ast_link_adam
+
+#  Purpose:
+#     Link an ADAM program with the AST library.
+
+#  Type of Module:
+#     Shell script.
+
+#  Description:
+#     This command should only be used when building Starlink ADAM programs
+#     which use the AST library, in order to generate the correct arguments
+#     to allow the ADAM ``alink'' command to link the program. The arguments
+#     generated are written to standard output but may be substituted into
+#     the ``alink'' command line in the standard UNIX way using backward
+#     quotes (see below).
+#
+#     By default, it is assumed that you are building an ADAM program which
+#     does not produce graphical output. However, switches are provided for
+#     linking other types of program. This command should not be used when
+#     building stand-alone (non-ADAM) programs. Use the ``ast_link'' command
+#     instead.
+
+#  Invocation:
+#c     alink program.o -L/star/lib `ast_link_adam [switches]`
+#f     alink program.f -L/star/lib `ast_link_adam [switches]`
+
+#  Switches:
+#     The following switches may optionally be given to this command to
+#     modify its behaviour:
+#
+#     - ``-csla'': Ignored. Provided for backward compatibility only.
+#
+#     - ``-fsla'': Ignored. Provided for backward compatibility only.
+#
+#     - ``-grf'': Requests that no arguments be generated to specify which
+#     2D graphics system is used to display output from the AST library. You
+#     should use this option only if you have implemented an interface to a
+#     new graphics system yourself and wish to provide your own arguments for
+#     linking with it. This switch differs from the other ``grf'' switches in
+#     that it assumes that your graphics module implements the complete
+#     interface required by the current version of AST. If future versions of
+#     AST introduce new functions to the graphics interface, this switch will
+#     cause ``unresolved symbol'' errors to occur during linking, warning you
+#     that you need to implement new functions in your graphics module. To
+#     avoid such errors, you can use one of the other, version-specific,
+#     switches in place of the ``-grf'' switch, but these will cause run-time
+#     errors to be reported if any AST function is invoked which requires
+#     facilities not in the implemented interface.
+#
+#     - ``-grf_v2.0'': This switch is equivalent to the ``-mygrf'' switch.
+#     It indicates that you want to link with your own graphics module which
+#     implements the 2D graphics interface required by V2.0 of AST.
+#
+#     - ``-grf_v3.2'': Indicates that you want to link with your own graphics
+#     module which implements the 2D graphics interface required by V3.2 of AST.
+#
+#     - ``-grf_v5.6'': Indicates that you want to link with your own graphics
+#     module which implements the 2D graphics interface required by V5.6 of AST.
+#
+#     - ``-myerr'': Requests that no arguments be generated to specify how
+#     error messages produced by the AST library should be delivered. You
+#     should use this option only if you have implemented an interface to a
+#     new error delivery system yourself and wish to provide your own
+#     arguments for linking with it. By default, error messages are delivered
+#     in the standard ADAM way via the EMS Error Message Service (Starlink
+#     System Note SSN/4).
+#
+#     - ``-mygrf'': This switch has been superceeded by the ``-grf'' switch,
+#     but is retained in order to allow applications to be linked with a
+#     graphics module which implements the interface used by AST V2.0. It is
+#     equivalent to the ``-grf_v2.0'' switch.
+#
+#     - ``-pgp'': Requests that the program be linked so that 2D
+#     graphical output from the AST library is displayed via the
+#     Starlink version of the PGPLOT graphics package (which uses GKS
+#     for its output). By default, no graphics package is linked and
+#     this will result in an error at run time if AST routines are
+#     invoked that attempt to generate graphical output.
+#
+#     - ``-pgplot'': Requests that the program be linked so that 2D
+#     graphical output from the AST library is displayed via the
+#     standard (or ``native'') version of the PGPLOT graphics
+#     package. By default, no graphics package is linked and this will
+#     result in an error at run time if AST routines are invoked that
+#     attempt to generate graphical output.
+#
+#     - ``-grf3d'': Requests that no arguments be generated to specify which
+#     3D graphics system is used to display output from the AST library. You
+#     should use this option only if you have implemented an interface to a
+#     new 3D graphics system yourself and wish to provide your own arguments
+#     for linking with it.
+#
+#     - ``-pgp3d'': Requests that the program be linked so that 3D
+#     graphical output from the AST library is displayed via the
+#     Starlink version of the PGPLOT graphics package (which uses GKS
+#     for its output). By default, no 3D graphics package is linked and
+#     this will result in an error at run time if AST routines are
+#     invoked that attempt to generate graphical output.
+#
+#     - ``-pgplot3d'': Requests that the program be linked so that 3D
+#     graphical output from the AST library is displayed via
+#     the standard (or ``native'') version of the PGPLOT graphics
+#     package. By default, no 3D graphics package is linked and this will
+#     result in an error at run time if AST routines are invoked that
+#     attempt to generate graphical output.
+
+#  SLALIB:
+#     The AST distribution includes a cut down subset of the C version of
+#     the SLALIB library written by Pat Wallace. This subset contains only
+#     the functions needed by the AST library. It is built as part of the
+#     process of building AST and is distributed under GPL (and is thus
+#     compatible with the AST license). Previous version of this script
+#     allowed AST applications to be linked against external SLALIB
+#     libraries (either Fortran or C) rather than the internal version.
+#     The current version of this script does not provide this option,
+#     and always uses the internal SLALIB library. However, for backward
+#     compatibility, this script still allows the "-fsla" and "-csla" flags
+#     (previously used for selecting which version of SLALIB to use) to be
+#     specified, but they will be ignored.
+
+#  Examples:
+#c     alink display.o -L/star/lib `ast_link_adam -pgplot`
+#c        Links an ADAM program ``display'' which uses the standard
+#c        version of PGPLOT for graphical output.
+#c     alink plotit.o -L. -L/star/lib `ast_link_adam -grf` -lgrf
+#c        Links an ADAM program ``plotit'', written in C. The ``-grf''
+#c        switch indicates that graphical output will be delivered through
+#c        a graphical interface which you have implemented yourself, which
+#c        corresponds to the interface required by the current version of AST.
+#c        Here, this interface is supplied by means of the ``-lgrf'' library
+#c        reference.
+#c     alink plotit.o -L. -L/star/lib `ast_link_adam -grf_v2.0` -lgrf
+#c        Links an ADAM program ``plotit'', written in C. The ``-grf_v2.0''
+#c        switch indicates that graphical output will be delivered through
+#c        a graphical interface which you have implemented yourself, which
+#c        corresponds to the interface required by version 2.0 of AST. Here,
+#c        this interface is supplied by means of the ``-lgrf'' library
+#c        reference.
+#f     alink display.f -L/star/lib `ast_link_adam -pgplot`
+#f        Compiles and links an ADAM Fortran program called ``display'' which
+#f        uses the standard version of PGPLOT for graphical output.
+#f     alink plotit.f -L. -L/star/lib `ast_link_adam -grf` -lgrf
+#f        Compiles and links an ADAM Fortran program ``plotit''. The ``-grf''
+#f        switch indicates that graphical output will be delivered through
+#f        a graphical interface which you have implemented yourself, which
+#f        corresponds to the interface required by the current version of AST.
+#f        Here, this interface is supplied by means of the ``-lgrf'' library
+#f        reference.
+#f     alink plotit.f -L. -L/star/lib `ast_link_adam -grf_v2.0` -lgrf
+#f        Compiles and links an ADAM Fortran program ``plotit''. The ``-grf_v2.0''
+#f        switch indicates that graphical output will be delivered through
+#f        a graphical interface which you have implemented yourself, which
+#f        corresponds to the interface required by version 2.0 of AST.
+#f        Here, this interface is supplied by means of the ``-lgrf'' library
+#f        reference.
+
+#  Copyright:
+#     Copyright (C) 1997-2006 Council for the Central Laboratory of the Research Councils
+
+#  Authors:
+#     RFWS: R.F. Warren-Smith (STARLINK)
+#     {enter_new_authors_here}
+
+#  History:
+#     11-NOV-1996 (RFWS):
+#        Original version.
+#     18-NOV-1997 (RFWS):
+#        Adapted prologue for document extraction.
+#     28-SEP-1998 (RFWS):
+#        Distinguish between -pgp and -pgplot options.
+#     23-JAN-2004 (DSB):
+#        Added switches to support older grf implementations.
+#     21-APR-2005 (DSB):
+#        Added "-fsla" option.
+#     16-JUN-2006 (DSB):
+#        Ignore "-fsla" and "-clsa" options, and always use PAL.
+#     22-AUG-2007 (DSB):
+#        Added "-grf3d", "-pgplot3d" and "-pgp3d" flags.
+#     4-MAR-2011 (DSB):
+#        Added v5.6 grf options.
+#     {enter_changes_here}
+
+#  Bugs:
+#     {note_any_bugs_here}
+
+#--
+
+#  This function searches the directory path specified in PATH, looking for
+#  an executable file which is not a directory. If found, it echos the full
+#  file name to standard output. Otherwise, it outputs nothing.
+      find() { IFS=':'; for d in $PATH; do f="${d:=.}/${1}"
+                  test -x "${f}" -a ! -d "${f}" && echo "${f}" && break
+               done;
+             }
+
+#  Initialise linking options.
+      err=''
+      grf=''
+      grf3d=''
+      sla=''
+
+#  Interpret command line switches.
+#  --------------------------------
+      while :; do
+         case "${1}" in
+
+#  -csla - Previously used to request C version of SLALIB. Now ignored.
+         -csla)
+#            sla='c'
+            shift;;
+
+#  -fsla - Previously used to request Fortran version of SLALIB. Now ignored.
+         -fsla)
+#            sla='f'
+            shift;;
+
+#  -myerr - Requests no error reporting.
+         -myerr)
+            err='my'
+            shift;;
+
+#  -grf - Requests no 2D graphics.
+         -grf)
+            grf='current'
+            shift;;
+
+#  -mygrf - Requests no 2D graphics, except for null implementations of
+#  functions aded to the grf interface after AST V2.0.
+         -mygrf)
+            grf='v2.0'
+            shift;;
+
+#  -grf_v2.0 - Requests no 2D graphics, except for null implementations of
+#  functions aded to the grf interface after AST V2.0.
+         -grf_v2.0)
+            grf='v2.0'
+            shift;;
+
+#  -grf_v3.2 - Requests no 2D graphics, except for null implementations of
+#  functions aded to the grf interface after AST V3.2.
+         -grf_v3.2)
+            grf='v3.2'
+            shift;;
+
+#  -grf_v5.6 - Requests no 2D graphics, except for null implementations of
+#  functions added to the grf interface after AST V5.6.
+         -grf_v5.6)
+            grf='v5.6'
+            shift;;
+
+#  -pgp - Requests 2D graphical output through Starlink PGPLOT.
+         -pgp)
+            grf='pgp'
+            shift;;
+
+#  -pgplot - Requests 2D graphical output through native PGPLOT.
+         -pgplot)
+            grf='pgplot'
+            shift;;
+
+#  -grf3d - Requests no 3D graphics.
+         -grf3d)
+            grf3d='current'
+            shift;;
+
+#  -pgp3d - Requests 3D graphical output through Starlink PGPLOT.
+         -pgp3d)
+            grf3d='pgp'
+            shift;;
+
+#  -pgplot3d - Requests 3D graphical output through native PGPLOT.
+         -pgplot3d)
+            grf3d='pgplot'
+            shift;;
+
+#  Once all switches have been read, continue with the rest of the script.
+         '') break;;
+
+#  Catch unrecognised switches and report an error.
+         *)
+            echo >&2 "ast_link_adam: unknown argument \""${1}"\" given"
+            exit 1;;
+         esac
+      done
+
+#  Link with the main AST library.
+#  -------------------------------
+#  Start forming the list of arguments with the main AST library itself.
+      args='-last'
+
+#  Generate arguments for linking PAL.
+#  -----------------------------------
+
+      case "@EXTERNAL_PAL@" in
+
+#  If we configured --with-external_pal include a link option to pick up
+#  an external PAL library.
+      1) args="${args} -lpal";;
+
+#  Otherwise, use the internal PAL & ERFA libraries.
+      *) args="${args} -last_pal";;
+
+      esac
+
+#  Generate arguments for linking the 2D graphics system.
+#  ------------------------------------------------------
+      case "${grf}" in
+
+#  If using Starlink PGPLOT, link with the AST PGPLOT interface and
+#  the Fortran library via the PGP link script.
+      pgp) args="${args} -last_pgplot `pgp_link_adam`";;
+
+#  If using native PGPLOT, link with the AST PGPLOT interface and
+#  the Fortran library via the PGPLOT link script.
+      pgplot) args="${args} -last_pgplot `pgplot_link_adam`";;
+
+#  If using own graphics which conform to the requirements of the current
+#  version of AST, do not produce any arguments.
+      current) :;;
+
+#  If using own graphics which conform to the requirements of version 5.6
+#  of AST, produce arguments which link in dummy implementations of any
+#  functions which are required by the current version of AST but which were
+#  not required by version 5.6.
+      v5.6) :;;
+
+#  If using own graphics which conform to the requirements of version 3.2
+#  of AST, produce arguments which link in dummy implementations of any
+#  functions which are required by the current version of AST but which were
+#  not required by version 3.2.
+      v3.2) args="${args} -last_grf_5.6";;
+
+#  If using own graphics which conform to the requirements of version 2.0
+#  of AST, produce arguments which link in dummy implementations of any
+#  functions which are required by the current version of AST but which were
+#  not required by version 2.0.
+      v2.0) args="${args} -last_grf_3.2 -last_grf_5.6";;
+
+#  Default graphics (none) requires linking with all the default (null) AST
+#  "grf" modules.
+      *) args="${args} -last_grf_2.0 -last_grf_3.2 -last_grf_5.6";;
+      esac
+
+#  Generate arguments for linking the 3D graphics system.
+#  ------------------------------------------------------
+      case "${grf3d}" in
+
+#  If using Starlink PGPLOT, link with the AST 3D PGPLOT interface and
+#  the Fortran library via the PGP link script (if found).
+      pgp) args="${args} -last_pgplot3d `\`find pgp_link\``"
+           f77='y';;
+
+#  If using native PGPLOT, link with the AST 3D PGPLOT interface and the
+#  Fortran library via the PGPLOT link script (if found).
+      pgplot) args="${args} -last_pgplot3d `\`find pgplot_link\``"
+              f77='y';;
+
+#  If using own 3D graphics which conform to the requirements of the current
+#  version of AST, do not produce any arguments.
+      current) :;;
+
+#  Default graphics (none) requires linking with all the default (null) AST
+#  "grf3d" modules.
+      *) args="${args} -last_grf3d";;
+      esac
+
+#  Make a second pass through the AST library.
+#  -------------------------------------------
+#  This library is a link to the main AST library and results in a second
+#  pass to resolve any backward references generated by the other modules
+#  used above. A different library name must be used to avoid the two passes
+#  being merged into one (either below, or by other link scripts).
+      args="${args} -last_pass2"
+
+#  Generate arguments for linking the error reporting system.
+#  ----------------------------------------------------------
+      case "${err}" in
+
+#  If using own error reporting, do not produce any arguments.
+      my) :;;
+
+#  Default error reporting requires linking with the AST EMS interface and
+#  the EMS library via the link script.
+      *) args="${args} -last_ems `ems_link_adam`";;
+      esac
+
+#  Link with the maths library.
+#  ----------------------------
+      args="${args} -lm"
+
+#  Link with the starmem library, if available.
+#  --------------------------------------------
+      args="${args} `\`find starmem_link\``"
+
+#  Pass the resulting argument list through an awk script which eliminates
+#  all except the last reference to each library.
+      echo "${args}" \
+           | awk 'BEGIN{RS=" ";FS="\n"}
+                  {if($1)f[i++]=$1}
+                  END{for(;i--;)if(!w[f[i]]++)l=f[i]" "l;print l}'
+
+#  End of script.
diff --git a/ast/ast_par.source b/ast/ast_par.source
new file mode 100644
index 0000000..f423374
--- /dev/null
+++ b/ast/ast_par.source
@@ -0,0 +1,727 @@
+*+
+*  Name:
+*     AST_PAR
+
+*  Purpose:
+*     Define the Fortran 77 interface to the AST library.
+
+*  Language:
+*     Fortran 77
+
+*  Type of Module:
+*     Include file.
+
+*  Description:
+*     This file contains definitions which are required by Fortran 77
+*     programs which use the AST library.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (STARLINK)
+*     MBT: Mark Taylor (STARLINK)
+*     DSB: David S. Berry
+
+*  History:
+*     12-NOV-1996 (RFWS):
+*        Original version.
+*     18-MAR-1998 (RFWS):
+*        Added definitions for the IntraMap class.
+*     21-DEC-1998 (RFWS):
+*        Added resampling definitions for the Mapping class.
+*     15-NOV-1999 (RFWS):
+*        Added definitions for PcdMap.
+*     24-NOV-2000 (MBT):
+*        Added AST__BLOCKAVE interpolation scheme.
+*     22-JUN-2001 (DSB):
+*        Added AST_OFFSET2 and AST_ANGLE to Frame class.
+*     6-SEP-2001 (DSB):
+*        Added AST_AXDISTANCE and AST_AXOFFSET to Frame class.
+*     12-SEP-2001 (DSB):
+*        Added AST_BEAR to Frame class.
+*     21-SEP-2001 (DSB):
+*        Replaced AST_BEAR by AST_AXANGLE.
+*     28-JAN-2003 (DSB):
+*        Added AST_GETACTIVEUNIT.
+*     14-FEB-2003 (DSB):
+*        Added new values for WcsMap projections.
+*     30-APR-2003 (DSB):
+*        Added AST_VERSION.
+*     15-JUL-2003 (DSB):
+*        Added AST_RATE, POLYMAP, SHIFTMAP and GRISMMAP functions.
+*     13-NOV-2003 (DSB):
+*        Added XmlChan class.
+*     9-NOV-2004 (DSB):
+*        Added all initial Region classes.
+*     19-NOV-2004 (DSB):
+*        Added KeyMap.
+*     16-JUN-2005 (DSB):
+*        Added TimeMap and TimeFrame.
+*     1-SEP-2005 (DSB):
+*        Added AST__REBININIT and AST__REBINNORM.
+*     17-FEB-2006 (DSB):
+*        Added AST_ESCAPES.
+*     9-FEB-2007 (DSB):
+*        Use a double precision constant to initialise AST__UNDEFF.
+*     4-DEC-2008 (TIMJ):
+*        Add AST_TESTFITS. Remove AST__UNDEF<X>
+*     6-FEB-2009 (DSB):
+*        Added StcsChan class.
+*     26-OCT-2016 (DSB):
+*        Make angle constants double precision.
+*-
+
+*  Length of character string returned by a character function.
+      INTEGER AST__SZCHR
+      PARAMETER ( AST__SZCHR = 200 )
+
+*  Bad coordinate value.
+      DOUBLE PRECISION AST__BAD
+      PARAMETER ( AST__BAD = <AST__BAD> )
+
+*  Double precision NaN flag (this value is not actually a NaN itself).
+      DOUBLE PRECISION AST__NAN
+      PARAMETER ( AST__NAN = <AST__NAN> )
+
+*  Single precision NaN flag (this value is not actually a NaN itself).
+      REAL AST__NANR
+      PARAMETER ( AST__NANR = <AST__NANR> )
+
+*  Error module.
+      LOGICAL AST_OK
+      INTEGER AST_STATUS
+
+*  Object class.
+      EXTERNAL AST_NULL
+      INTEGER AST__NULL
+      PARAMETER ( AST__NULL = 0 )
+
+      INTEGER AST__TUNULL
+      PARAMETER ( AST__TUNULL = -99999 )
+
+
+      CHARACTER AST__TUNULLC*11
+      PARAMETER ( AST__TUNULLC = '<NOTUNEPAR>' )
+
+      CHARACTER * ( AST__SZCHR ) AST_GETC
+      DOUBLE PRECISION AST_GETD
+      INTEGER AST_CLONE
+      INTEGER AST_COPY
+      LOGICAL AST_EQUAL
+      INTEGER AST_GETI
+      INTEGER AST_VERSION
+      LOGICAL AST_GETL
+      LOGICAL AST_ISAOBJECT
+      LOGICAL AST_TEST
+      LOGICAL AST_HASATTRIBUTE
+      LOGICAL AST_SAME
+      INTEGER AST_TUNE
+      REAL AST_GETR
+      LOGICAL AST_CHRSUB
+
+*  Channel class.
+      INTEGER AST_CHANNEL
+      INTEGER AST_READ
+      INTEGER AST_WRITE
+      LOGICAL AST_ISACHANNEL
+      INTEGER AST_WARNINGS
+
+*  FitsChan class.
+      INTEGER AST_FITSCHAN
+      LOGICAL AST_FINDFITS
+      LOGICAL AST_ISAFITSCHAN
+      LOGICAL AST_GETFITSCF
+      LOGICAL AST_GETFITSCI
+      LOGICAL AST_GETFITSF
+      LOGICAL AST_GETFITSI
+      LOGICAL AST_GETFITSL
+      LOGICAL AST_GETFITSS
+      LOGICAL AST_GETFITSCN
+      LOGICAL AST_TESTFITS
+      INTEGER AST_GETTABLES
+
+      CHARACTER AST__TABEXTNAME*7
+      PARAMETER ( AST__TABEXTNAME = 'WCS-TAB' )
+
+      INTEGER AST__NOTYPE
+      PARAMETER ( AST__NOTYPE = -1 )
+      INTEGER AST__COMMENT
+      PARAMETER ( AST__COMMENT = 0 )
+      INTEGER AST__INT
+      PARAMETER ( AST__INT = 1 )
+      INTEGER AST__FLOAT
+      PARAMETER ( AST__FLOAT = 2 )
+      INTEGER AST__STRING
+      PARAMETER ( AST__STRING = 3 )
+      INTEGER AST__COMPLEXF
+      PARAMETER ( AST__COMPLEXF = 4 )
+      INTEGER AST__COMPLEXI
+      PARAMETER ( AST__COMPLEXI = 5 )
+      INTEGER AST__LOGICAL
+      PARAMETER ( AST__LOGICAL = 6 )
+      INTEGER AST__CONTINUE
+      PARAMETER ( AST__CONTINUE = 7 )
+      INTEGER AST__UNDEF
+      PARAMETER ( AST__UNDEF = 8 )
+
+
+*  Mapping Class.
+      INTEGER AST__URESAMP1
+      PARAMETER ( AST__URESAMP1 = 1 )
+      INTEGER AST__URESAMP2
+      PARAMETER ( AST__URESAMP2 = 2 )
+      INTEGER AST__URESAMP3
+      PARAMETER ( AST__URESAMP3 = 4 )
+      INTEGER AST__URESAMP4
+      PARAMETER ( AST__URESAMP4 = 8 )
+      INTEGER AST__USEVAR
+      PARAMETER ( AST__USEVAR = 16 )
+      INTEGER AST__USEBAD
+      PARAMETER ( AST__USEBAD = 32 )
+      INTEGER AST__CONSERVEFLUX
+      PARAMETER ( AST__CONSERVEFLUX = 64 )
+      INTEGER AST__REBININIT
+      PARAMETER ( AST__REBININIT = 128 )
+      INTEGER AST__REBINEND
+      PARAMETER ( AST__REBINEND = 256 )
+      INTEGER AST__GENVAR
+      PARAMETER ( AST__GENVAR = 512 )
+      INTEGER AST__VARWGT
+      PARAMETER ( AST__VARWGT = 1024 )
+      INTEGER AST__NOBAD
+      PARAMETER ( AST__NOBAD = 2048 )
+      INTEGER AST__DISVAR
+      PARAMETER ( AST__DISVAR = 4096 )
+      INTEGER AST__NONORM
+      PARAMETER ( AST__NONORM = 8192 )
+
+      INTEGER AST__UKERN1
+      PARAMETER ( AST__UKERN1 = 1 )
+c  Not yet implemented
+c      INTEGER AST__UKERNN
+c      PARAMETER ( AST__UKERNN = 2 )
+      INTEGER AST__UINTERP
+      PARAMETER ( AST__UINTERP = 3 )
+      INTEGER AST__NEAREST
+      PARAMETER ( AST__NEAREST = 4 )
+      INTEGER AST__LINEAR
+      PARAMETER ( AST__LINEAR = 5 )
+      INTEGER AST__SINC
+      PARAMETER ( AST__SINC = 6 )
+      INTEGER AST__SINCSINC
+      PARAMETER ( AST__SINCSINC = 7 )
+      INTEGER AST__SINCCOS
+      PARAMETER ( AST__SINCCOS = 8 )
+      INTEGER AST__SINCGAUSS
+      PARAMETER ( AST__SINCGAUSS = 9 )
+      INTEGER AST__BLOCKAVE
+      PARAMETER ( AST__BLOCKAVE = 10 )
+      INTEGER AST__GAUSS
+      PARAMETER ( AST__GAUSS = 11 )
+      INTEGER AST__SOMB
+      PARAMETER ( AST__SOMB = 12 )
+      INTEGER AST__SOMBCOS
+      PARAMETER ( AST__SOMBCOS = 13 )
+
+      INTEGER AST_RESAMPLEB
+      INTEGER AST_RESAMPLED
+      INTEGER AST_RESAMPLEI
+      INTEGER AST_RESAMPLEK
+      INTEGER AST_RESAMPLER
+      INTEGER AST_RESAMPLES
+      INTEGER AST_RESAMPLEUB
+      INTEGER AST_RESAMPLEUI
+      INTEGER AST_RESAMPLEUK
+      INTEGER AST_RESAMPLEUS
+      INTEGER AST_RESAMPLEUW
+      INTEGER AST_RESAMPLEW
+      INTEGER AST_REMOVEREGIONS
+      INTEGER AST_SIMPLIFY
+      LOGICAL AST_ISAMAPPING
+      LOGICAL AST_LINEARAPPROX
+      LOGICAL AST_QUADAPPROX
+      DOUBLE PRECISION AST_RATE
+
+*  CmpMap class.
+      INTEGER AST_CMPMAP
+      LOGICAL AST_ISACMPMAP
+
+*  Frame class.
+      CHARACTER * ( AST__SZCHR ) AST_FORMAT
+      DOUBLE PRECISION AST_DISTANCE
+      INTEGER AST_CONVERT
+      INTEGER AST_FINDFRAME
+      INTEGER AST_FRAME
+      INTEGER AST_PICKAXES
+      INTEGER AST_UNFORMAT
+      LOGICAL AST_ISAFRAME
+      LOGICAL AST_GETACTIVEUNIT
+      DOUBLE PRECISION AST_ANGLE
+      DOUBLE PRECISION AST_OFFSET2
+      DOUBLE PRECISION AST_AXDISTANCE
+      DOUBLE PRECISION AST_AXOFFSET
+      DOUBLE PRECISION AST_AXANGLE
+
+*  CmpFrame class.
+      INTEGER AST_CMPFRAME
+      LOGICAL AST_ISACMPFRAME
+
+*  FrameSet class.
+      INTEGER AST__BASE
+      PARAMETER ( AST__BASE = 0 )
+      INTEGER AST__CURRENT
+      PARAMETER ( AST__CURRENT = -1 )
+      INTEGER AST__NOFRAME
+      PARAMETER ( AST__NOFRAME = -99 )
+
+      INTEGER AST_FRAMESET
+      INTEGER AST_GETFRAME
+      INTEGER AST_GETMAPPING
+      LOGICAL AST_ISAFRAMESET
+
+*  IntraMap class.
+      INTEGER AST__NOFWD
+      PARAMETER ( AST__NOFWD = 1 )
+      INTEGER AST__NOINV
+      PARAMETER ( AST__NOINV = 2 )
+      INTEGER AST__SIMPFI
+      PARAMETER ( AST__SIMPFI = 4 )
+      INTEGER AST__SIMPIF
+      PARAMETER ( AST__SIMPIF = 8 )
+      INTEGER AST__ANY
+      PARAMETER ( AST__ANY = -66 )
+
+      INTEGER AST_INTRAMAP
+      LOGICAL AST_ISAINTRAMAP
+
+*  LutMap class.
+      INTEGER AST_LUTMAP
+      LOGICAL AST_ISALUTMAP
+
+*  PcdMap class.
+      INTEGER AST_PCDMAP
+      LOGICAL AST_ISAPCDMAP
+
+*  Plot class.
+      INTEGER AST_PLOT
+      LOGICAL AST_BORDER
+      INTEGER AST_GETGRFCONTEXT
+      LOGICAL AST_ISAPLOT
+      INTEGER AST_ESCAPES
+      CHARACTER * ( AST__SZCHR ) AST_STRIPESCAPES
+
+*  SkyFrame class.
+      INTEGER AST_SKYFRAME
+      LOGICAL AST_ISASKYFRAME
+      INTEGER AST_SKYOFFSETMAP
+
+*  SpecFrame class.
+      INTEGER AST_SPECFRAME
+      LOGICAL AST_ISASPECFRAME
+
+*  DSBSpecFrame class.
+      INTEGER AST_DSBSPECFRAME
+      LOGICAL AST_ISADSBSPECFRAME
+
+*  MathMap class.
+      INTEGER AST_MATHMAP
+      LOGICAL AST_ISAMATHMAP
+
+*  MatrixMap class.
+      INTEGER AST_MATRIXMAP
+      LOGICAL AST_ISAMATRIXMAP
+
+*  PermMap class.
+      INTEGER AST_PERMMAP
+      LOGICAL AST_ISAPERMMAP
+
+*  PolyMap class.
+      INTEGER AST_POLYMAP
+      LOGICAL AST_ISAPOLYMAP
+      INTEGER AST_POLYTRAN
+
+*  SlaMap class.
+      INTEGER AST_SLAMAP
+      LOGICAL AST_ISASLAMAP
+
+*  SpecMap class.
+      INTEGER AST_SPECMAP
+      LOGICAL AST_ISASPECMAP
+
+*  SphMap class.
+      INTEGER AST_SPHMAP
+      LOGICAL AST_ISASPHMAP
+
+*  UnitMap class.
+      INTEGER AST_UNITMAP
+      LOGICAL AST_ISAUNITMAP
+
+*  WcsMap class.
+
+      INTEGER AST__WCSMX
+      PARAMETER ( AST__WCSMX = 10 )
+
+      DOUBLE PRECISION AST__DPI
+      PARAMETER ( AST__DPI = 3.1415926535897932384626433832795028842D0 )
+
+      DOUBLE PRECISION AST__DPIBY2
+      PARAMETER ( AST__DPIBY2 = 1.5707963267948966192313216916397514D0 )
+
+      DOUBLE PRECISION AST__DD2R
+      PARAMETER ( AST__DD2R = 0.017453292519943295769236907684886127D0 )
+
+      DOUBLE PRECISION AST__DR2D
+      PARAMETER ( AST__DR2D = 57.29577951308232087679815481410517033D0 )
+
+      INTEGER AST__AIR
+      PARAMETER ( AST__AIR = 9 )
+      INTEGER AST__AIT
+      PARAMETER ( AST__AIT = 17 )
+      INTEGER AST__ARC
+      PARAMETER ( AST__ARC = 6 )
+      INTEGER AST__AZP
+      PARAMETER ( AST__AZP = 1 )
+      INTEGER AST__BON
+      PARAMETER ( AST__BON = 22 )
+      INTEGER AST__CAR
+      PARAMETER ( AST__CAR = 12 )
+      INTEGER AST__CEA
+      PARAMETER ( AST__CEA = 11 )
+      INTEGER AST__COD
+      PARAMETER ( AST__COD = 20 )
+      INTEGER AST__COE
+      PARAMETER ( AST__COE = 19 )
+      INTEGER AST__COO
+      PARAMETER ( AST__COO = 21 )
+      INTEGER AST__COP
+      PARAMETER ( AST__COP = 18 )
+      INTEGER AST__CSC
+      PARAMETER ( AST__CSC = 25 )
+      INTEGER AST__CYP
+      PARAMETER ( AST__CYP = 10 )
+      INTEGER AST__GLS
+      PARAMETER ( AST__GLS = 28 )
+      INTEGER AST__HPX
+      PARAMETER ( AST__HPX = 30 )
+      INTEGER AST__MER
+      PARAMETER ( AST__MER = 13 )
+      INTEGER AST__MOL
+      PARAMETER ( AST__MOL = 16 )
+      INTEGER AST__NCP
+      PARAMETER ( AST__NCP = 27 )
+      INTEGER AST__PAR
+      PARAMETER ( AST__PAR = 15 )
+      INTEGER AST__PCO
+      PARAMETER ( AST__PCO = 23 )
+      INTEGER AST__QSC
+      PARAMETER ( AST__QSC = 26 )
+      INTEGER AST__SFL
+      PARAMETER ( AST__SFL = 14 )
+      INTEGER AST__SIN
+      PARAMETER ( AST__SIN = 5 )
+      INTEGER AST__STG
+      PARAMETER ( AST__STG = 4 )
+      INTEGER AST__SZP
+      PARAMETER ( AST__SZP = 2 )
+      INTEGER AST__TAN
+      PARAMETER ( AST__TAN = 3 )
+      INTEGER AST__TPN
+      PARAMETER ( AST__TPN = 29 )
+      INTEGER AST__TSC
+      PARAMETER ( AST__TSC = 24 )
+      INTEGER AST__XPH
+      PARAMETER ( AST__XPH = 31 )
+      INTEGER AST__ZEA
+      PARAMETER ( AST__ZEA = 8 )
+      INTEGER AST__ZPN
+      PARAMETER ( AST__ZPN = 7 )
+      INTEGER AST__WCSBAD
+      PARAMETER ( AST__WCSBAD = 32 )
+
+      INTEGER AST_WCSMAP
+      LOGICAL AST_ISAWCSMAP
+
+*  ShiftMap class.
+      INTEGER AST_SHIFTMAP
+      LOGICAL AST_ISASHIFTMAP
+
+*  WinMap class.
+      INTEGER AST_WINMAP
+      LOGICAL AST_ISAWINMAP
+
+*  ZoomMap class.
+      INTEGER AST_ZOOMMAP
+      LOGICAL AST_ISAZOOMMAP
+
+*  GrismMap class.
+      INTEGER AST_GRISMMAP
+      LOGICAL AST_ISAGRISMMAP
+
+*  XmlChan class.
+      INTEGER AST_XMLCHAN
+      LOGICAL AST_ISAXMLCHAN
+
+*  TranMap class.
+      INTEGER AST_TRANMAP
+      LOGICAL AST_ISATRANMAP
+
+*  Region class.
+      INTEGER AST_REGION
+      INTEGER AST_GETUNC
+      INTEGER AST_GETREGIONFRAME
+      LOGICAL AST_ISAREGION
+      INTEGER AST_MAPREGION
+      INTEGER AST_OVERLAP
+      INTEGER AST_MASKB
+      INTEGER AST_MASKD
+      INTEGER AST_MASKI
+      INTEGER AST_MASKR
+      INTEGER AST_MASKS
+      INTEGER AST_MASKUB
+      INTEGER AST_MASKUI
+      INTEGER AST_MASKUS
+      INTEGER AST_MASKUW
+      INTEGER AST_MASKW
+
+*  Box class.
+      INTEGER AST_BOX
+      LOGICAL AST_ISABOX
+
+*  PointList class.
+      INTEGER AST_POINTLIST
+      LOGICAL AST_ISAPOINTLIST
+
+*  Polygon class.
+      INTEGER AST_POLYGON
+      LOGICAL AST_ISAPOLYGON
+      INTEGER AST_DOWNSIZE
+      INTEGER AST_OUTLINED
+      INTEGER AST_OUTLINER
+      INTEGER AST_OUTLINEI
+      INTEGER AST_OUTLINEUI
+      INTEGER AST_OUTLINES
+      INTEGER AST_OUTLINEUS
+      INTEGER AST_OUTLINEW
+      INTEGER AST_OUTLINEUW
+      INTEGER AST_OUTLINEB
+      INTEGER AST_OUTLINEUB
+
+      INTEGER AST_CONVEXD
+      INTEGER AST_CONVEXR
+      INTEGER AST_CONVEXI
+      INTEGER AST_CONVEXUI
+      INTEGER AST_CONVEXS
+      INTEGER AST_CONVEXUS
+      INTEGER AST_CONVEXW
+      INTEGER AST_CONVEXUW
+      INTEGER AST_CONVEXB
+      INTEGER AST_CONVEXUB
+
+      INTEGER AST__LE
+      PARAMETER( AST__LE = 2 )
+
+      INTEGER AST__EQ
+      PARAMETER( AST__EQ = 3 )
+
+      INTEGER AST__GE
+      PARAMETER( AST__GE = 4 )
+
+      INTEGER AST__GT
+      PARAMETER( AST__GT = 5 )
+
+      INTEGER AST__NE
+      PARAMETER( AST__NE = 6 )
+
+*  Circle class.
+      INTEGER AST_CIRCLE
+      LOGICAL AST_ISACIRCLE
+
+*  Ellipse class.
+      INTEGER AST_ELLIPSE
+      LOGICAL AST_ISAELLIPSE
+
+*  NullRegion class.
+      INTEGER AST_NULLREGION
+      LOGICAL AST_ISANULLREGION
+
+*  Interval class.
+      INTEGER AST_INTERVAL
+      LOGICAL AST_ISAINTERVAL
+
+*  Prism class.
+      INTEGER AST_PRISM
+      LOGICAL AST_ISAPRISM
+
+*  CmpRegion class.
+      INTEGER AST_CMPREGION
+      LOGICAL AST_ISACMPREGION
+
+      INTEGER AST__AND
+      PARAMETER( AST__AND = 1 )
+
+      INTEGER AST__OR
+      PARAMETER( AST__OR = 2 )
+
+      INTEGER AST__XOR
+      PARAMETER( AST__XOR = 3 )
+
+*  KeyMap class.
+      INTEGER AST_KEYMAP
+      LOGICAL AST_ISAKEYMAP
+      LOGICAL AST_MAPGET0I
+      LOGICAL AST_MAPGET0S
+      LOGICAL AST_MAPGET0B
+      LOGICAL AST_MAPGET0D
+      LOGICAL AST_MAPGET0R
+      LOGICAL AST_MAPGET0C
+      LOGICAL AST_MAPGET0A
+      LOGICAL AST_MAPGET1I
+      LOGICAL AST_MAPGET1B
+      LOGICAL AST_MAPGET1S
+      LOGICAL AST_MAPGET1D
+      LOGICAL AST_MAPGET1R
+      LOGICAL AST_MAPGET1C
+      LOGICAL AST_MAPGET1A
+      LOGICAL AST_MAPGETELEMI
+      LOGICAL AST_MAPGETELEMS
+      LOGICAL AST_MAPGETELEMB
+      LOGICAL AST_MAPGETELEMD
+      LOGICAL AST_MAPGETELEMR
+      LOGICAL AST_MAPGETELEMC
+      LOGICAL AST_MAPGETELEMA
+      INTEGER AST_MAPSIZE
+      INTEGER AST_MAPLENGTH
+      INTEGER AST_MAPLENC
+      INTEGER AST_MAPTYPE
+      LOGICAL AST_MAPHASKEY
+      LOGICAL AST_MAPDEFINED
+      CHARACTER * ( AST__SZCHR ) AST_MAPKEY
+
+      INTEGER AST__BADTYPE
+      PARAMETER ( AST__BADTYPE = 0)
+
+      INTEGER AST__INTTYPE
+      PARAMETER ( AST__INTTYPE = 1)
+
+      INTEGER AST__DOUBLETYPE
+      PARAMETER ( AST__DOUBLETYPE = 2)
+
+      INTEGER AST__STRINGTYPE
+      PARAMETER ( AST__STRINGTYPE = 3)
+
+      INTEGER AST__OBJECTTYPE
+      PARAMETER ( AST__OBJECTTYPE = 4)
+
+      INTEGER AST__FLOATTYPE
+      PARAMETER ( AST__FLOATTYPE = 5)
+
+      INTEGER AST__SINTTYPE
+      PARAMETER ( AST__SINTTYPE = 7)
+
+      INTEGER AST__UNDEFTYPE
+      PARAMETER ( AST__UNDEFTYPE = 8)
+
+      INTEGER AST__BYTETYPE
+      PARAMETER ( AST__BYTETYPE = 9)
+
+*  FluxFrame class.
+      INTEGER AST_FLUXFRAME
+      LOGICAL AST_ISAFLUXFRAME
+
+*  SpecFluxFrame class.
+      INTEGER AST_SPECFLUXFRAME
+      LOGICAL AST_ISASPECFLUXFRAME
+
+*  NormMap class.
+      INTEGER AST_NORMMAP
+      LOGICAL AST_ISANORMMAP
+
+*  RateMap class.
+      INTEGER AST_RATEMAP
+      LOGICAL AST_ISARATEMAP
+
+*  TimeFrame class.
+      INTEGER AST_TIMEFRAME
+      LOGICAL AST_ISATIMEFRAME
+      DOUBLE PRECISION AST_CURRENTTIME
+
+      INTEGER AST__LT
+      PARAMETER( AST__LT = 11 )
+
+*  TimeMap class.
+      INTEGER AST_TIMEMAP
+      LOGICAL AST_ISATIMEMAP
+
+*  Stc class.
+      LOGICAL AST_ISASTC
+      INTEGER AST_GETSTCREGION
+      INTEGER AST_GETSTCCOORD
+      INTEGER AST_GETSTCNCOORD
+
+      CHARACTER AST__STCNAME*4
+      PARAMETER ( AST__STCNAME  = 'Name' )
+
+      CHARACTER AST__STCVALUE*5
+      PARAMETER ( AST__STCVALUE = 'Value' )
+
+      CHARACTER AST__STCERROR*5
+      PARAMETER ( AST__STCERROR = 'Error' )
+
+      CHARACTER AST__STCRES*10
+      PARAMETER ( AST__STCRES   = 'Resolution' )
+
+      CHARACTER AST__STCSIZE*4
+      PARAMETER ( AST__STCSIZE  = 'Size' )
+
+      CHARACTER AST__STCPIXSZ*7
+      PARAMETER ( AST__STCPIXSZ = 'PixSize' )
+
+*  StcSearchLocation class.
+      LOGICAL AST_ISASTCSEARCHLOCATION
+      INTEGER AST_STCSEARCHLOCATION
+
+*  StcCatalogEntryLocation class.
+      LOGICAL AST_ISASTCCATALOGENTRYLOCATION
+      INTEGER AST_STCCATALOGENTRYLOCATION
+
+*  StcResourceProfile class.
+      LOGICAL AST_ISASTCRESOURCEPROFILE
+      INTEGER AST_STCRESOURCEPROFILE
+
+*  StcObsDataLocation class.
+      LOGICAL AST_ISASTCOBSDATALOCATION
+      INTEGER AST_STCOBSDATALOCATION
+
+*  SwitchMap class.
+      INTEGER AST_SWITCHMAP
+      LOGICAL AST_ISASWITCHMAP
+
+*  SelectorMap class.
+      INTEGER AST_SELECTORMAP
+      LOGICAL AST_ISASELECTORMAP
+
+*  Plot3D class.
+      INTEGER AST_PLOT3D
+      LOGICAL AST_ISAPLOT3D
+
+*  StcsChan class.
+      INTEGER AST_STCSCHAN
+      LOGICAL AST_ISASTCSCHAN
+
+*  Table class.
+      INTEGER AST_TABLE
+      LOGICAL AST_ISATABLE
+      LOGICAL AST_HASCOLUMN
+      CHARACTER * ( AST__SZCHR ) AST_COLUMNNAME
+      LOGICAL AST_HASPARAMETER
+      CHARACTER * ( AST__SZCHR ) AST_PARAMETERNAME
+
+*  FitsTable class.
+      INTEGER AST_FITSTABLE
+      LOGICAL AST_ISAFITSTABLE
+      INTEGER AST_COLUMNNULL
+      INTEGER AST_COLUMNSIZE
+      INTEGER AST_GETTABLEHEADER
+
+*  UnitNormMap class.
+      INTEGER AST_UNITNORMMAP
+      LOGICAL AST_ISAUNITNORMMAP
+
diff --git a/ast/ast_test.c b/ast/ast_test.c
new file mode 100644
index 0000000..61e948e
--- /dev/null
+++ b/ast/ast_test.c
@@ -0,0 +1,115 @@
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+#include "ast.h"                 /* AST C interface definition */
+
+/* C header files. */
+/* --------------- */
+#include <stdio.h>
+
+/* Main function. */
+/* ============== */
+int main( int argc, char *argv[] ) {
+/*
+*+
+*  Name:
+*     ast_test
+
+*  Purpose:
+*     Test installation of the AST library.
+
+*  Type:
+*     C program.
+
+*  Description:
+*     This program performs a simple test (without using graphics) of
+*     the AST library, to check that it is correctly installed. It is
+*     not an exhaustive test of the system.
+
+*  Arguments:
+*     None.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     19-NOV-1997 (RFWS);
+*        Original version.
+*-
+*/
+
+/* Local Constants: */
+#define NCOORD 10                /* Number of coordinates to transform */
+
+/* Local Variables: */
+   AstFrameSet *cvt;             /* Pointer to conversion FrameSet */
+   AstSkyFrame *sky1;            /* Pointer to first SkyFrame */
+   AstSkyFrame *sky2;            /* Pointer to second SkyFrame */
+   double xin[ NCOORD ];         /* Input coordinate array */
+   double xout[ NCOORD ];        /* Output coordinate array */
+   double yin[ NCOORD ];         /* Input coordinate array */
+   double yout[ NCOORD ];        /* Output coordinate array */
+   int i;                        /* Loop counter for coordinates */
+
+/* Begin an AST context. */
+   astBegin;
+
+/* Create two SkyFrames. */
+   sky1 = astSkyFrame( "system = FK4_NO_E, equinox = B1920, epoch = B1958" );
+   sky2 = astSkyFrame( "system = ecliptic, equinox = J2001" );
+
+/* Create a FrameSet describing the conversion between them. */
+   cvt = astConvert( sky1, sky2, "" );
+
+/* If successful, set up some input coordinates. */
+   if ( cvt != AST__NULL ) {
+      for ( i = 0; i < NCOORD; i++ ) {
+         xin[ i ] = 0.1 * (double) i;
+         yin[ i ] = 0.2 * (double) i;
+      }
+
+/* Display the FrameSet. */
+      astShow( cvt );
+      printf( "\n");
+
+/* Activate reporting of coordinate transformations. */
+      astSet( cvt, "Report = 1" );
+
+/* Perform the forward transformation. */
+      astTran2( cvt, 10, xin, yin, 1, xout, yout );
+      printf( "\n");
+
+/* Perform the inverse transformation. */
+      astTran2( cvt, 10, xout, yout, 0, xin, yin );
+   }
+
+/* End the AST context. */
+   astEnd;
+
+/* Return an error status. */
+   return astOK ? 0 : 1;
+
+/* Undefine local macros. */
+#undef NCOORD
+}
diff --git a/ast/astbad.c b/ast/astbad.c
new file mode 100644
index 0000000..8fec052
--- /dev/null
+++ b/ast/astbad.c
@@ -0,0 +1,181 @@
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+#include "pointset.h"           /* declaration of AST__BAD etc */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Local Constants: */
+#define BUFF_LEN ( 2 * DBL_DIG + 20 ) /* Buffer length */
+#define IEEE_DIG 17                   /* Minimum number of digits required by
+                                         IEEE for conversion from binary to
+                                         string and back again to be an
+                                         identity. */
+
+/* Prototypes for local functions */
+static void printdval( double );
+static void printfval( float );
+
+/* Main function. */
+/* ============== */
+int main( int argc, char *argv[] ) {
+/*
+*+
+*  Name:
+*     astbad
+
+*  Purpose:
+*     Generate a string representing an AST floating point constant.
+
+*  Invocation:
+*     astbad <value>
+
+*  Type:
+*     C program.
+
+*  Description:
+*     This program writes a string to standard output containing
+*     a formatted decimal representation of a specified C floating point
+*     constant defined by AST. This is intended for use in defining these
+*     constants for use from languages other than C.
+*
+*     The value written should contain sufficient decimal digits so
+*     that a routine that uses it to generate a value in another
+*     language will produce exactly the same value as a C program
+*     using the same macro.
+
+*  Arguments:
+*     value = LITERAL
+*        The name of the constant to be printed: AST__BAD, AST__NAN or
+*        AST__NANF. If not supplied, AST__BAD is printed.
+
+*  Copyright:
+*     Copyright (C) 2009-2011 Science & Technology Facilities Council.
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+
+*  History:
+*     18-NOV-1997 (RFWS);
+*        Original version.
+*     24-OCT-2000 (DSB):
+*        Ensure that the number of digits used is at least the minimum
+*        required by IEEE for a conversion from binary to string and back
+*        to binary to be an identity.
+*     31-MAR-2009 (TIMJ):
+*        Does not take any arguments so don't try to read arguments.
+*     18-JAN-2011 (DSB):
+*        Extend to print other floating point constants as well as
+*        AST__BAD.
+*-
+*/
+
+/* Local Variables; */
+   const char *name;        /* Pointer to name of constant to be printed */
+
+/* Get the name of the constant to be printed. */
+   if( argc > 1 ) {
+      name = argv[1];
+   } else {
+      name = "AST__BAD";
+   }
+
+/* Print it. */
+   if( !strcmp( name, "AST__BAD" ) ) {
+      printdval( AST__BAD );
+
+   } else if( !strcmp( name, "AST__NAN" ) ) {
+      printdval( AST__NAN );
+
+   } else if( !strcmp( name, "AST__NANF" ) ) {
+      printfval( AST__NANF );
+
+/* Issue an error message if the argument is unknown. */
+   } else {
+      (void) fprintf( stderr, "astbad: Unknown constant requested: %s\n",
+                      name );
+   }
+
+/* Exit. */
+   return 0;
+}
+
+
+/* Print a double precision value to standard output */
+static void printdval( double val ){
+
+/* Local Variables: */
+   char buff[ BUFF_LEN + 1 ];    /* Buffer for formatted string */
+   double newval;                /* Value read back from string */
+   int digits;                   /* Number of digits of precision */
+
+/* Vary the precision over a reasonable range to see how many decimal
+   digits are required. The initial number of digits is the larger of
+   DBL_DIG and IEEE_DIG. */
+   for ( digits = ( DBL_DIG > IEEE_DIG )?DBL_DIG:IEEE_DIG;
+         digits <= ( 2 * DBL_DIG ); digits++ ) {
+
+/* Format the value using this precision and then read it back. */
+      (void) sprintf( buff, "%.*G", digits, val );
+      (void) sscanf( buff, "%lg", &newval );
+
+/* Quit looping when the original value is read back. */
+      if ( newval == val ) break;
+   }
+
+/* Write the value to standard output, with one extra digit for good
+   measure. */
+   (void) printf( "%.*G\n", digits + 1, val );
+}
+
+/* Print a single precision value to standard output */
+static void printfval( float val ){
+
+/* Local Variables: */
+   char buff[ BUFF_LEN + 1 ];    /* Buffer for formatted string */
+   float newval;                 /* Value read back from string */
+   int digits;                   /* Number of digits of precision */
+
+/* Vary the precision over a reasonable range to see how many decimal
+   digits are required. The initial number of digits is FLT_DIG. */
+   for ( digits = FLT_DIG; digits <= ( 2 * FLT_DIG ); digits++ ) {
+
+/* Format the value using this precision and then read it back. */
+      (void) sprintf( buff, "%.*G", digits, val );
+      (void) sscanf( buff, "%g", &newval );
+
+/* Quit looping when the original value is read back. */
+      if ( newval == val ) break;
+   }
+
+/* Write the value to standard output, with one extra digit for good
+   measure. */
+   (void) printf( "%.*G\n", digits + 1, val );
+
+}
+
diff --git a/ast/axis.c b/ast/axis.c
new file mode 100644
index 0000000..58c430a
--- /dev/null
+++ b/ast/axis.c
@@ -0,0 +1,3500 @@
+/*
+*class++
+*  Name:
+*     Axis
+
+*  Purpose:
+*     Store axis information.
+
+*  Constructor Function:
+*     None.
+
+*  Description:
+*     The Axis class is used to store information associated with a
+*     particular axis of a Frame. It is used internally by the AST
+*     library and has no constructor function. You should encounter it
+c     only within textual output (e.g. from astWrite).
+f     only within textual output (e.g. from AST_WRITE).
+
+*  Inheritance:
+*     The Axis class inherits from the Object class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: B.S. Berry (Starlink)
+
+*  History:
+*     1-MAR-1996 (RFWS):
+*        Original version.
+*     10-SEP-1996 (RFWS):
+*        Added I/O facilities.
+*     11-SEP-1996 (RFWS):
+*        Added astAxisGap (written by DSB).
+*     25-FEB-1998 (RFWS):
+*        Added astAxisUnformat.
+*     29-AUG-2001 (DSB):
+*        Added AxisDistance and AxisOffset.
+*     20-OCT-2002 (DSB):
+*        Added Top and Bottom attributes.
+*     8-JAN-2003 (DSB):
+*        - Changed private InitVtab method to protected astInitAxisVtab
+*        method.
+*        - Include descriptive label for units string within a Dump.
+*     24-JAN-2004 (DSB):
+*        - Added astAxisFields.
+*        - Added argument "fmt" to definition of AxisAbbrev.
+*     3-FEB-2004 (DSB):
+*        - Added "log" formatting using the "&" flag character in the
+*        Format string.
+*     15-SEP-2004 (DSB):
+*        - If a format string is set which includes a wildcard precision
+*        value (".*"), then use the Digits value to determine the precision
+*        to be used.
+*        - If the conversion code is of integer type (e.g. "%d") cast value
+*        to integer before printing.
+*     2-FEB-2005 (DSB):
+*        - Avoid using astStore to allocate more storage than is supplied
+*        in the "data" pointer. This can cause access violations since
+*        astStore will then read beyond the end of the "data" area.
+*     15-MAR-2005 (DSB):
+*        - Avoid exponents in log format labels which are close to zero but
+*        not quite zero.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     30-JUN-2006 (DSB):
+*        Guard against a null "str1" value in AxisAbbrev.
+*     17-APR-2015 (DSB):
+*        Added astAxisCentre.
+*     26-OCT-2016 (DSB):
+*        Added astAxisNormValues.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Axis
+
+
+/* Header files. */
+/* ============= */
+#include "ast_err.h"             /* Error code definitions */
+
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Object interface (parent class) */
+#include "pointset.h"            /* Sets of coordinates (for AST__BAD) */
+#include "channel.h"             /* I/O channels */
+#include "axis.h"                /* Interface definition for this class */
+#include "unit.h"                /* Definitions of physical units */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+/* Plain text equivalents. */
+static const char *log_txt  = "10^";
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetDefaultFormat_Buff[ 0 ] = 0; \
+   globals->AxisFormat_Buff[ 0 ] = 0; \
+   globals->GetAxisNormUnit_Buff[ 0 ] = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Axis)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Axis,Class_Init)
+#define class_vtab astGLOBAL(Axis,Class_Vtab)
+#define getdefaultformat_buff astGLOBAL(Axis,GetDefaultFormat_Buff)
+#define axisformat_buff astGLOBAL(Axis,AxisFormat_Buff)
+#define getaxisnormunit_buff astGLOBAL(Axis,GetAxisNormUnit_Buff)
+#define getattrib_buff astGLOBAL(Axis,GetAttrib_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getdefaultformat_buff[ AST__AXIS_GETDEFAULTFORMAT_BUFF_LEN + 1 ];
+static char axisformat_buff[ AST__AXIS_GETDEFAULTFORMAT_BUFF_LEN + 1 ];
+static char getaxisnormunit_buff[ AST__AXIS_GETAXISNORMUNIT_BUFF_LEN + 1 ];
+static char getattrib_buff[ AST__AXIS_GETATTRIB_BUFF_LEN + 1 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstAxisVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstAxis *astAxisId_( const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static const char *AxisAbbrev( AstAxis *, const char *, const char *, const char *, int * );
+static const char *AxisFormat( AstAxis *, double, int * );
+static int GetObjSize( AstObject *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static const char *GetAxisFormat( AstAxis *, int * );
+static const char *GetAxisLabel( AstAxis *, int * );
+static const char *GetAxisSymbol( AstAxis *, int * );
+static const char *GetAxisUnit( AstAxis *, int * );
+static const char *GetAxisInternalUnit( AstAxis *, int * );
+static const char *GetAxisNormUnit( AstAxis *, int * );
+static const char *GetDefaultFormat( AstAxis *, int * );
+static char *ParseAxisFormat( const char *, int, int *, int *, int *, int *, int * );
+static double AxisDistance( AstAxis *, double, double, int * );
+static double AxisCentre( AstAxis *, double, double, int * );
+static double AxisGap( AstAxis *, double, int *, int * );
+static double AxisOffset( AstAxis *, double, double, int * );
+static int AxisFields( AstAxis *, const char *, const char *, int, char **, int *, double *, int * );
+static int AxisIn( AstAxis *, double, double, double, int, int * );
+static int AxisUnformat( AstAxis *, const char *, double *, int * );
+static int GetAxisDigits( AstAxis *, int * );
+static int GetAxisDirection( AstAxis *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestAxisDigits( AstAxis *, int * );
+static int TestAxisDirection( AstAxis *, int * );
+static int TestAxisFormat( AstAxis *, int * );
+static int TestAxisLabel( AstAxis *, int * );
+static int TestAxisSymbol( AstAxis *, int * );
+static int TestAxisUnit( AstAxis *, int * );
+static int TestAxisInternalUnit( AstAxis *, int * );
+static int TestAxisNormUnit( AstAxis *, int * );
+static void AxisNorm( AstAxis *, double *, int * );
+static void AxisNormValues( AstAxis *, int, int, double *, int * );
+static void AxisOverlay( AstAxis *, AstAxis *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearAxisDigits( AstAxis *, int * );
+static void ClearAxisDirection( AstAxis *, int * );
+static void ClearAxisFormat( AstAxis *, int * );
+static void ClearAxisLabel( AstAxis *, int * );
+static void ClearAxisSymbol( AstAxis *, int * );
+static void ClearAxisUnit( AstAxis *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetAxisDigits( AstAxis *, int, int * );
+static void SetAxisDirection( AstAxis *, int, int * );
+static void SetAxisFormat( AstAxis *, const char *, int * );
+static void SetAxisLabel( AstAxis *, const char *, int * );
+static void SetAxisSymbol( AstAxis *, const char *, int * );
+static void SetAxisUnit( AstAxis *, const char *, int * );
+
+static double GetAxisTop( AstAxis *, int * );
+static int TestAxisTop( AstAxis *, int * );
+static void ClearAxisTop( AstAxis *, int * );
+static void SetAxisTop( AstAxis *, double, int * );
+
+static double GetAxisBottom( AstAxis *, int * );
+static int TestAxisBottom( AstAxis *, int * );
+static void ClearAxisBottom( AstAxis *, int * );
+static void SetAxisBottom( AstAxis *, double, int * );
+
+
+/* Member functions. */
+/* ================= */
+static const char *AxisAbbrev( AstAxis *this, const char *fmt,
+                               const char *str1, const char *str2, int *status ) {
+/*
+*+
+*  Name:
+*     astAxisAbbrev
+
+*  Purpose:
+*     Abbreviate a formatted Axis value by skipping leading fields.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     const char *astAxisAbbrev( AstAxis *this, const char *fmt,
+*                                const char *str1, const char *str2 )
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function compares two Axis values that have been formatted
+*     (using astAxisFormat) and determines if they have any redundant
+*     leading fields (i.e. leading fields in common which can be
+*     suppressed when tabulating the values or plotting them on the
+*     axis of a graph).
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format specifier used to format the two values.
+*     str1
+*        Pointer to a constant null-terminated string containing the
+*        first formatted value. If this is null, the returned pointer
+*        points to the start of the final field in str2.
+*     str2
+*        Pointer to a constant null-terminated string containing the
+*        second formatted value.
+
+*  Returned Value:
+*     A pointer into the "str2" string which locates the first
+*     character in the first field that differs between the two
+*     formatted values.
+*
+*     If the two values have no leading fields in common, the returned
+*     value will point at the start of string "str2". If the two
+*     values are equal, it will point at the terminating null at the
+*     end of this string.
+
+*  Notes:
+*     - This function assumes that the format specification used was
+*     the same when both values were formatted.
+*     - A pointer to the start of "str2" will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   const char *result;           /* Result pointer to return */
+
+/* Initialise. */
+   result = str2;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* In the Axis class, there is only one field in a formatted value.
+   We return the value of "str2", unless the two strings are
+   identical, in which case we return a pointer to the final null in
+   "str2". */
+   if( str1 && !strcmp( str1, str2 ) ) result += strlen( str2 );
+
+/* Return the result. */
+   return result;
+}
+
+static double AxisDistance( AstAxis *this, double v1, double v2, int *status ) {
+/*
+*+
+*  Name:
+*     astAxisDistance
+
+*  Purpose:
+*     Find the distance between two axis values.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     AxisDistance( AstAxis *this, double v1, double v2 )
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function returns a signed value representing the axis increment
+*     from axis value v1 to axis value v2.
+*
+*     For a simple Axis, this is a trivial operation. But for other
+*     derived classes of Axis (such as a SkyAxis) this is not the case.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     v1
+*        The first axis value
+*     v2
+*        The second axis value
+
+*  Returned Value:
+*     The axis increment from v1 to v2.
+
+*  Notes:
+*     - A value of AST__BAD is returned if either axis value is AST__BAD.
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   double result;                /* Returned gap size */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check both axis values are OK, and form the returned increment. */
+   if( v1 != AST__BAD && v2 != AST__BAD ) result = v2 - v1;
+
+/* Return the result. */
+   return result;
+}
+
+static int AxisFields( AstAxis *this, const char *fmt0, const char *str,
+                       int maxfld, char **fields, int *nc, double *val, int *status ) {
+/*
+*+
+*  Name:
+*     astAxisFields
+
+*  Purpose:
+*     Identify numerical fields within a formatted Axis value.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     int astAxisFields( AstAxis *this, const char *fmt0, const char *str,
+*                        int maxfld, char **fields, int *nc, double *val )
+
+*  Class Membership:
+*     Axis member function.
+
+*  Description:
+*     This function identifies the numerical fields within an Axis value
+*     that have been formatted using astAxisFormat. It assumes that the
+*     value was formatted using the supplied format string. It also
+*     returns the equivalent floating point value.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     fmt0
+*        Pointer to a constant null-terminated string containing the
+*        format used when creating "str".
+*     str
+*        Pointer to a constant null-terminated string containing the
+*        formatted value.
+*     maxfld
+*        The maximum number of fields to identify within "str".
+*     fields
+*        A pointer to an array of at least "maxfld" character pointers.
+*        Each element is returned holding a pointer to the start of the
+*        corresponding field  in "str" (in the order in which they occur
+*        within "str"), or NULL if no corresponding field can be found.
+*     nc
+*        A pointer to an array of at least "maxfld" integers. Each
+*        element is returned holding the number of characters in the
+*        corresponding field, or zero if no corresponding field can be
+*        found.
+*     val
+*        Pointer to a location at which to store the value
+*        equivalent to the returned field values. If this is NULL,
+*        it is ignored.
+
+*  Returned Value:
+*     The number of fields succesfully identified and returned.
+
+*  Notes:
+*     - Leading and trailing spaces are ignored.
+*     - If the formatted value is not consistent with the supplied format
+*     string, then a value of zero will be returned, "fields" will be
+*     returned holding NULLs, "nc" will be returned holding zeros, and
+*     "val" is returned holding VAL__BAD.
+*     - Fields are counted from the start of the formatted string. If the
+*     string contains more than "maxfld" fields, then trailing fields are
+*     ignored.
+*     - If this function is invoked with the global error status set, or
+*     if it should fail for any reason, then a value of zero will be returned
+*     as the function value, and "fields", "nc" and "val"  will be returned
+*     holding their supplied values
+*-
+*/
+
+/* Local Variables: */
+   char log_esc[ 50 ];           /* Buffer for graphical delimiter string */
+   const char *fmt;              /* Pointer to parsed Format string */
+   const char *log_del;          /* Pointer to delimiter string */
+   const char *p;                /* Pointer to next character */
+   double value;                 /* Equivalent radians value */
+   int ifld;                     /* Field index */
+   int integ;                    /* Cast axis value to integer before printing? */
+   int len;                      /* Length of formatted string */
+   int log;                      /* Format as "10**x"? */
+   int n;                        /* Number of characters read */
+   int neg;                      /* Negate final value? */
+   int result;                   /* Result fields count to return */
+   int sign;                     /* Include leading sign in front of "10**x"? */
+   int space;                    /* Include leading space in front of "10**x"? */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise. */
+   result = 0;
+   for( ifld = 0; ifld < maxfld; ifld++ ) {
+      fields[ ifld ] = NULL;
+      nc[ ifld ] = 0;
+   }
+   if( val ) *val = AST__BAD;
+
+/* Parse the Format string. This returns a collection of flags indicating
+   if any AST specific formatting features are specified in the Format
+   string. It also returns a pointer to a new Format string which is a
+   standard C printf format specifier. Currently the only flags are "log"
+   which indicates if the value should be formatted as "10**x" using
+   the graphical escape sequences defined within the Plot class to produce
+   "x" as a superscript of "10", "sign" which is used with log to indicate
+   if a sign should always be included infront of the "10", and "space"
+   which indicates if a leading space should be included infronyt of "10" if
+   no sign is included. */
+   fmt = ParseAxisFormat( fmt0, astGetAxisDigits( this ), &log, &sign,
+                          &space, &integ, status );
+   fmt = astFree( (void *) fmt );
+
+   if( astOK ) {
+
+/* Obtain the length of the formatted string. */
+      len = (int) strlen( str );
+
+/* First deal with "log" format. */
+      if( log ) {
+
+/* We need room for at least 2 fields. */
+         if( maxfld > 1 ) {
+
+/* Return a pointer to the first non-blank character. */
+            p = str;
+            while( *p == ' ' ) p++;
+            fields[ 0 ] = (char *) p;
+
+/* If the first non-blank character is a minus sign, note it and skip it. */
+            neg = 0;
+            if( *p == '-' ) {
+               neg = 1;
+               p++;
+
+/* If the first non-blank character is a plus sign, and skip it. */
+            } else if( *p == '+' ) {
+               p++;
+            }
+
+/* Select the delimter.*/
+            if(  astEscapes( -1 ) ) {
+               astTuneC( "exdel", NULL, log_esc, sizeof( log_esc ) );
+               log_del = log_esc;
+            } else {
+               log_del = log_txt;
+            }
+
+/* Check the remaining string starts with the correct delimiter. If
+   so, store the number of characters in the first field and skip over the
+   delimiter. */
+            n = 0;
+            if( strstr( p, log_del ) == p ) {
+               nc[ 0 ] = p + 2  - fields[ 0 ];
+               p += strlen( log_del );
+
+/* Attempt to read a floating point value from the start of the remaining
+   string. */
+               if( 1 == sscanf( p, "%lg%n", &value, &n ) ) {
+
+/* If succesfull, store the returned values. */
+                  result = 2;
+                  fields[ 1 ] = (char *) p;
+                  nc[ 1 ] = n;
+                  if( val ) {
+                     *val = pow( 10.0, value );
+                     if( neg ) *val = -(*val);
+                  }
+
+/* Otherwise, see if the string starts with <bad> */
+               } else if( strstr( p, "<bad>" ) == p ) {
+
+/* If succesfull, store the returned values. */
+                  result = 2;
+                  fields[ 1 ] = (char *) p;
+                  nc[ 1 ] = 5;
+                  if( val ) *val = 0.0;
+               }
+
+/* Zero is never formatted as an exponent. If the string starts with zero,
+   return a single zero field. */
+            } else if( 1 == sscanf( p, "%lg%n", &value, &n ) ) {
+               if( value == 0.0 ) {
+                  result = 1;
+                  nc[ 0 ] = p + n  - fields[ 0 ];
+                  if( val ) *val = 0.0;
+               }
+            }
+         }
+
+/* Now deal with normal decimal format */
+      } else {
+
+/* Attempt to read a floating point value from the formatted string. */
+         if ( n = 0,
+              ( 1 == sscanf( str, "%lg %n", &value, &n ) )
+              && ( n >= len ) && maxfld > 0 ) {
+
+/* If succesful, return a pointer to the first non-blank character. */
+            p = str;
+            while( *p == ' ' ) p++;
+            fields[ 0 ] = (char *) p;
+
+/* Find the last non-blank character. */
+            p += len;
+            while( p[ -1 ] == ' ' ) p--;
+
+/* Return the number of characters in the field. */
+            nc[ 0 ] = p - fields[ 0 ];
+
+/* Return the field value. */
+            if( val ) *val = value;
+
+/* Indicate that we are returning one field. */
+            result = 1;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *AxisFormat( AstAxis *this, double value, int *status ) {
+/*
+*+
+*  Name:
+*     astAxisFormat
+
+*  Purpose:
+*     Format a coordinate value for an Axis.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     const char *astAxisFormat( AstAxis *this, double value )
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function returns a pointer to a string containing the formatted
+*     (character) version of a coordinate value for an Axis. The formatting
+*     applied is that specified by a previous invocation of the
+*     astSetAxisFormat method. A suitable default format is applied if
+*     necessary.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     value
+*        The coordinate value to be formatted.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the formatted value.
+
+*  Notes:
+*     -  The returned string pointer may point at memory allocated within
+*     the Axis object, or at static memory. The contents of the string may be
+*     over-written or the pointer may become invalid following a further
+*     invocation of the same function or deletion of the Axis. A copy of the
+*     string should therefore be made if necessary.
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Constants: */
+#define ERRBUF_LEN 80
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
+   char *errstat;               /* Pointer for system error message */
+   char errbuf[ ERRBUF_LEN ];   /* Buffer for system error message */
+   char log_esc[ 50 ];          /* Buffer for graphical delimiter string */
+   const char *fmt0;            /* Pointer to original Format string */
+   const char *fmt;             /* Pointer to parsed Format string */
+   const char *log_del;         /* Pointer to delimiter string */
+   const char *result;          /* Pointer to formatted value */
+   double x;                    /* The value to be formatted by sprintf */
+   int integ;                   /* Cast axis value to integer before printing? */
+   int log;                     /* Format as "10**x"? */
+   int nc;                      /* Total number of characters written */
+   int ncc;                     /* Number of characters written */
+   int sign;                    /* Include leading sign in front of "10**x"? */
+   int space;                   /* Include leading space in front of "10**x"? */
+   int stat;                    /* Value of errno after error */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Initialise. */
+   result = NULL;
+   nc = 0;
+   x = value;
+
+/* Check if a bad coordinate value was supplied and return a pointer to an
+   appropriate string if necessary. */
+   if ( value == AST__BAD ) {
+      result = "<bad>";
+
+/* Otherwise, obtain a pointer to the Format string. Note a private member
+   function is used here in preference to an object method. This is because the
+   syntax of the Format string may be extended by derived classes and we do not
+   want to obtain a string that we cannot interpret here (where we are
+   restricted to C format specifiers capable of formatting double values).
+   Classes that extend the syntax should provide their own astAxisFormat method
+   and may need to store the string in a separate location. The original
+   location should not be re-used as the string it contains may be needed by
+   the Axis astOverlay method when overlaying attributes on to another Axis
+   object. */
+   } else {
+      fmt0 = GetAxisFormat( this, status );
+
+/* Parse the Format string. This returns a collection of flags indicating
+   if any AST specific formatting features are specified in the Format
+   string. It also returns a pointer to a new Format string which is a
+   standard C printf format specifier. Currently the only flags are "log"
+   which indicates if the value should be formatted as "10**x" using
+   the graphical escape sequences defined within the Plot class to produce
+   "x" as a superscript of "10", "sign" which is used with log to indicate
+   if a sign should always be included infront of the "10", and "space"
+   which indicates if a leading space should be included infronyt of "10"
+   if no sign is included. It also modifies ".*" precision fields by
+   replacing the "*" by the current vale of the Digits attribute. */
+      fmt = ParseAxisFormat( fmt0, astGetAxisDigits( this ), &log, &sign,
+                             &space, &integ, status );
+      if( astOK ) {
+
+/* Format zero normally. */
+         if( value == 0.0 ) log = 0;
+
+/* If log format is required, find the value of the exponent "x", and
+   initialise the returned string to hold the exponent and the graphical
+   escape sequence which produces a superscript. Otherwise just format the
+   supplied value. */
+         if( log ) {
+
+            if( sign ) {
+               axisformat_buff[ 0 ] ='+';
+               nc = 1;
+
+            } else if( space ) {
+               axisformat_buff[ 0 ] =' ';
+               nc = 1;
+            }
+
+            if( value > 0 ) {
+               x = log10( integ ? (int) value : value );
+
+            } else {
+               x = log10( integ ? (int) -value : -value );
+               axisformat_buff[ 0 ] ='-';
+               nc = 1;
+            }
+
+            if(  astEscapes( -1 ) ) {
+               astTuneC( "exdel", NULL, log_esc, sizeof( log_esc ) );
+               log_del = log_esc;
+            } else {
+               log_del = log_txt;
+            }
+
+            nc += sprintf( axisformat_buff + nc, "%s", log_del );
+
+/* Round small exponents to zero. */
+            if( fabs( x ) < 1.0E-10 ) x = 0.0;
+         }
+      }
+
+/* Clear errno and attempt to format the value as if the Format string were
+   a standard "sprintf" format. */
+      if ( astOK ) {
+         errno = 0;
+         if( integ ) {
+            ncc = sprintf( axisformat_buff + nc, fmt, (int) x );
+         } else {
+            ncc = sprintf( axisformat_buff + nc, fmt, x );
+         }
+         nc += ncc;
+
+/* If log format is being used, terminate the string with an escape
+   sequence which resets the graphical attributes to what they were at the
+   start of the string. */
+         if( log ) nc += sprintf( axisformat_buff + nc, "%%+" );
+
+/* The possibilities for error detection are limited here, but check if an
+   error value was returned and report an error. Include information from
+   errno if it was set. */
+         if ( ncc < 0 ) {
+            stat = errno;
+            if( stat ) {
+#if HAVE_STRERROR_R
+               strerror_r( stat, errbuf, ERRBUF_LEN );
+               errstat = errbuf;
+#else
+               errstat = strerror( stat );
+#endif
+            } else {
+               *errbuf = 0;
+               errstat = errbuf;
+            }
+            astError( AST__FMTER, "astAxisFormat(%s): Error formatting a "
+                      "coordinate value of %1.*G%s%s.", status, astGetClass( this ),
+                      DBL_DIG, value, stat? " - " : "", errstat );
+            astError( AST__FMTER, "The format string was \"%s\".", status, fmt );
+
+/* Also check that the result buffer did not overflow. If it did, memory will
+   probably have been corrupted but this cannot be prevented with "sprintf".
+   Report the error and abort. */
+         } else if ( nc > AST__AXIS_AXISFORMAT_BUFF_LEN ) {
+            astError( AST__FMTER, "astAxisFormat(%s): Internal buffer "
+                      "overflow while formatting a coordinate value of %1.*G "
+                      "- result exceeds %d characters.", status, astGetClass( this ),
+                      DBL_DIG, value, AST__AXIS_AXISFORMAT_BUFF_LEN );
+            astError( AST__FMTER, "The format string was \"%s\".", status, fmt );
+
+/* If succesfull, return a pointer to the buffer. */
+         } else {
+            result = axisformat_buff;
+         }
+      }
+
+/* Free resources. */
+      fmt = astFree( (void *) fmt );
+
+   }
+
+/* Return the result. */
+   return result;
+
+}
+#undef ERRBUF_LEN
+
+static double AxisCentre( AstAxis *this, double value, double gap, int *status ) {
+/*
+*+
+*  Name:
+*     astAxisCentre
+
+*  Purpose:
+*     Find a "nice" central value for tabulating Axis values.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     double astAxisCentre( AstAxis *this, double value, double gap )
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function returns an axis value which produces a nice formatted
+*     value suitable for a major tick mark on a plot axis.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     value
+*        An arbitrary axis value in the section that is being plotted.
+*     gap
+*        The gap size.
+
+*  Returned Value:
+*     The nice central axis value.
+
+*  Notes:
+*     - A value of zero is returned if the supplied gap size is zero.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   double result;                /* Returned central axis value */
+
+/* Initialise. */
+   result = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* The returned central value is an integral number of gaps away from the
+   origin and is close to the supplied axis value. This would result in
+   the origin being at a major tick mark. */
+   if( gap != 0.0 && gap != AST__BAD && value != AST__BAD ) {
+      result = gap*floor( 0.5 + value/gap );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double AxisGap( AstAxis *this, double gap, int *ntick, int *status ) {
+/*
+*+
+*  Name:
+*     astAxisGap
+
+*  Purpose:
+*     Find a "nice" gap for tabulating Axis values.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     double astAxisGap( AstAxis *this, double gap, int *ntick )
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function returns a gap size which produces a nicely spaced
+*     series of formatted Axis values, the returned gap size being as
+*     close as possible to the supplied target gap size. It also
+*     returns a convenient number of divisions into which the gap can
+*     be divided.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     gap
+*        The target gap size.
+*     ntick
+*        Address of an int in which to return a convenient number of
+*        divisions into which the gap can be divided.
+
+*  Returned Value:
+*     The nice gap size.
+
+*  Notes:
+*     - A value of zero is returned if the supplied gap size is zero.
+*     - A negative gap size is returned if the supplied gap size is negative.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   double absgap;                /* Absolute supplied gap size */
+   double b;                     /* Decimal step size */
+   double result;                /* Returned gap size */
+   int index;                    /* Index into tables */
+   int positive;                 /* Value is positive (or zero)? */
+
+/* Local Data: */
+   static double table1[ 10 ] =  /* Table of nice decimal gaps */
+            { 1.0, 2.0, 2.0, 5.0, 5.0, 5.0, 5.0, 10.0, 10.0, 10.0 };
+   static int table2[ 10 ] =     /* Table giving number of divisions */
+            {   5,   4,   4,   5,   5,   5,   5,    5,    5,    5 };
+
+/* Initialise. */
+   result = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check that the supplied gap size is not zero. */
+   if ( gap != 0.0 ) {
+
+/* Determine if the supplied gap size is positive and obtain its
+   absolute value. */
+      positive = ( gap >= 0.0 );
+      absgap = positive ? gap : -gap;
+
+/* Obtain a value which has a 1 at the position of the most
+   significant decimal digit in the target gap size and zeros at all
+   other positions. */
+      b = pow( 10.0, floor( log10( absgap ) ) );
+
+/* This value is the basic "step size". Find the nearest whole number
+   of steps in the supplied gap, and then use the look-up-table in
+   "table1" to find the closest acceptable gap size. Convert this gap
+   size back to an absolute value by multiplying by the step size. */
+      index = (int) ( absgap / b + 0.5 ) - 1;
+      result = b * table1[ index ];
+
+/* If the target gap was negative, negate the result. */
+      if( !positive ) result = -result;
+
+/* Store the number of divisions in the gap. */
+      if ( ntick ) *ntick = table2[ index ];
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int AxisIn( AstAxis *this, double lo, double hi, double val, int closed, int *status ){
+/*
+*+
+*  Name:
+*     astAxisIn
+
+*  Purpose:
+*     Test if an axis value lies within a given interval.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     int AxisIn( AstAxis *this, double lo, double hi, double val, int closed )
+
+*  Class Membership:
+*     Axis member function.
+
+*  Description:
+*     This function returns non-zero if a given axis values lies within a
+*     given axis interval.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     lo
+*        The lower axis limit of the interval.
+*     hi
+*        The upper axis limit of the interval.
+*     val
+*        The axis value to be tested.
+*     closed
+*        If non-zero, then the lo and hi axis values are themselves
+*        considered to be within the interval. Otherwise they are outside.
+
+*  Returned Value:
+*     Non-zero if the test value is inside the interval.
+
+*  Class Applicability:
+*     Axis
+*        Uses simple Euclidean test
+*     SkyAxis
+*        All angles which are numerically between "lo" and "hi" are within
+*        the interval. Angle outside this range are also within the interval
+*        if they can be brought into the range by addition or subtraction
+*        of a multiple of 2.PI.
+*-
+*/
+
+/* For speed, omit the astOK check since no pointers are being used. */
+   if( closed ) {
+      return ( lo <= val && val <= hi );
+   } else {
+      return ( lo < val && val < hi );
+   }
+}
+
+static void AxisNorm( AstAxis *this, double *value, int *status ) {
+/*
+*+
+*  Name:
+*     astAxisNorm
+
+*  Purpose:
+*     Normalise an Axis coordinate value.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     void astAxisNorm( AstAxis *this, double *value )
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function converts an Axis coordinate value which might
+*     potentially be unsuitable for display to a user (for instance,
+*     may lie outside the expected range of values) into an acceptable
+*     alternative value suitable for display.
+*
+*     Typically, for axes that represent cyclic values such as angles,
+*     this function wraps an arbitrary coordinate value so that it
+*     lies within the first cycle (say zero to 2*pi). For an ordinary
+*     linear Axis, without constraints, this function will typically
+*     return the original value unchanged.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     value
+*        Pointer to the coordinate value to be normalised, which will
+*        be modified in place.
+*-
+*/
+
+/* In the Axis class there are no constraints, so simply return
+   without action. */
+   return;
+}
+
+static void AxisNormValues( AstAxis *this, int oper, int nval,
+                            double *values, int *status ){
+/*
+*+
+*  Name:
+*     astAxisNormValues
+
+*  Purpose:
+*     Normalise an array of axis coordinate values.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     void astAxisNormValues( AstAxis *this, int oper, int nval,
+*                             double *values )
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function modifies a supplied array of axis values so that
+*     they are normalised in the manner indicated by parameter "oper".
+*
+*     For a simple axis, the supplied values are always returned
+*     unchanged regardless of the value of "oper".
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     oper
+*        Indicates the type of normalisation to be applied. If zero is
+*        supplied, the normalisation will be the same as that performed by
+*        function astAxisNorm. If 1 is supplied, the normalisation will be
+*        chosen automatically so that the resulting list has the smallest
+*        range.
+*     nval
+*        The number of points in the values array.
+*     values
+*        On entry, the axis values to be normalised. Modified on exit to
+*        hold the normalised values.
+*-
+*/
+
+/* In the Axis class there are no constraints, so simply return
+   without action. */
+   return;
+}
+
+static double AxisOffset( AstAxis *this, double v1, double dist, int *status ) {
+/*
+*+
+*  Name:
+*     astAxisOffset
+
+*  Purpose:
+*     Add an increment onto a supplied axis value.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     AxisOffset( AstAxis *this, double v1, double dist )
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function returns an axis value formed by adding a signed axis
+*     increment onto a supplied axis value.
+*
+*     For a simple Axis, this is a trivial operation. But for other
+*     derived classes of Axis (such as a SkyAxis) this is not the case.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     v1
+*        The supplied axis value
+*     dist
+*        The axis increment
+
+*  Returned Value:
+*     The axis value which is the specified increment away from v1.
+
+*  Notes:
+*     - A value of AST__BAD is returned if either axis value is AST__BAD.
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   double result;                /* Returned gap size */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check both axis values are OK, and form the returned axis value. */
+   if( v1 != AST__BAD && dist != AST__BAD ) result = v1 + dist;
+
+/* Return the result. */
+   return result;
+}
+
+static void AxisOverlay( AstAxis *template, AstAxis *result, int *status ) {
+/*
+*+
+*  Name:
+*     astAxisOverlay
+
+*  Purpose:
+*     Overlay the attributes of a template Axis on to another Axis.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     void astAxisOverlay( AstAxis *template, AstAxis *result )
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function overlays attributes of one Axis (the "template") on to
+*     another Axis, so as to over-ride selected attributes of that second
+*     Axis. Normally only those attributes which have been specifically set
+*     in the template will be transferred. This implements a form of
+*     defaulting, in which an Axis acquires attributes from the template, but
+*     retains its original attributes (as the default) if new values have not
+*     previously been explicitly set in the template.
+
+*  Parameters:
+*     template
+*        Pointer to the template Axis, for which values should have been
+*        explicitly set for any attribute which is to be transferred.
+*     result
+*        Pointer to the Axis which is to receive the new attribute values.
+
+*  Returned Value:
+*     void
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Define a macro to overlay a single attribute. This tests if the attribute
+   is set in the template Axis. If it is, its value is obtained and set in the
+   result Axis also. */
+#define OVERLAY(par) \
+   if ( astTestAxis##par( template ) ) { \
+      astSetAxis##par( result, astGetAxis##par( template ) ); \
+   }
+/* Overlay each Axis attribute in turn. */
+   OVERLAY(Digits);
+   OVERLAY(Direction);
+   OVERLAY(Label);
+   OVERLAY(Symbol);
+   OVERLAY(Unit);
+
+/* Handle the Format string slightly differently by using a private member
+   function to obtain it. This is necessary in case derived classes have
+   extended the string syntax (see the AxisFormat function for more
+   details). */
+   if ( TestAxisFormat( template, status ) ) {
+      SetAxisFormat( result, GetAxisFormat( template, status ), status );
+   }
+
+/* Undefine macros local to this function. */
+#undef OVERLAY
+}
+
+static int AxisUnformat( AstAxis *this, const char *string, double *value, int *status ) {
+/*
+*+
+*  Name:
+*     astAxisUnformat
+
+*  Purpose:
+*     Read a formatted coordinate value for an Axis.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     int astAxisUnformat( AstAxis *this, const char *string, double *value )
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function reads a formatted coordinate value for an Axis
+*     (supplied as a string) and returns the equivalent numerical
+*     value as a double. It also returns the number of characters read
+*     from the string.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     string
+*        Pointer to a constant null-terminated string containing the
+*        formatted coordinate value.
+*     value
+*        Pointer to a double in which the coordinate value read will be
+*        returned.
+
+*  Returned Value:
+*     The number of characters read from the string to obtain the
+*     coordinate value.
+
+*  Notes:
+*     - Any white space at the beginning of the string will be
+*     skipped, as also will any trailing white space following the
+*     coordinate value read. The function's return value will reflect
+*     this.
+*     - A function value of zero (and no coordinate value) will be
+*     returned, without error, if the string supplied does not contain
+*     a suitably formatted value.
+*     - The string "<bad>" is recognised as a special case and will
+*     generate the value AST__BAD, without error. The test for this
+*     string is case-insensitive and permits embedded white space.
+*     - A function result of zero will be returned and no coordinate
+*     value will be returned via the "value" pointer if this function
+*     is invoked with the global error status set, or if it should
+*     fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   double coord;                 /* Coordinate value read */
+   int nc;                       /* Number of characters read */
+
+/* Initialise. */
+   nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return nc;
+
+/* See if the string can be read as a floating point number. If so,
+   return its value. Also obtain the number of characters read,
+   including any leading and trailing white space. */
+   if ( 1 == astSscanf( string, "%lf %n", &coord, &nc ) ) {
+      *value = coord;
+
+/* Otherwise, see if the string starts with "<bad>", allowing mixed
+   case and leading, embedded and trailing white space. If so, return
+   the value AST__BAD. */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( string, " < %*1[Bb] %*1[Aa] %*1[Dd] > %n", &nc )
+               && ( nc > 0 ) ) ) {
+      *value = AST__BAD;
+
+/* If the string cannot be read, return a function result of zero. */
+   } else {
+      nc = 0;
+   }
+
+/* Return the number of characters read. */
+   return nc;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for an Axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Axis member function (over-rides the astClearAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function clears the value of a specified attribute for an
+*     Axis, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstAxis *this;                /* Pointer to the Axis structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Axis structure. */
+   this = (AstAxis *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Digits. */
+/* ------- */
+   if ( !strcmp( attrib, "digits" ) ) {
+      astClearAxisDigits( this );
+
+/* Direction. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "direction" ) ) {
+      astClearAxisDirection( this );
+
+/* Format. */
+/* ------- */
+   } else if ( !strcmp( attrib, "format" ) ) {
+      astClearAxisFormat( this );
+
+/* Label. */
+/* ------ */
+   } else if ( !strcmp( attrib, "label" ) ) {
+      astClearAxisLabel( this );
+
+/* Top. */
+/* ---- */
+   } else if ( !strcmp( attrib, "top" ) ) {
+      astClearAxisTop( this );
+
+/* Bottom. */
+/* ------- */
+   } else if ( !strcmp( attrib, "bottom" ) ) {
+      astClearAxisBottom( this );
+
+/* Symbol. */
+/* ------- */
+   } else if ( !strcmp( attrib, "symbol" ) ) {
+      astClearAxisSymbol( this );
+
+/* Unit. */
+/* ----- */
+   } else if ( !strcmp( attrib, "unit" ) ) {
+      astClearAxisUnit( this );
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Test if the attribute name matches any of the read-only attributes
+   of this class. If it does, then report an error. */
+   } else if ( !strcmp( attrib, "normunit" ) ||
+               !strcmp( attrib, "internalunit" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static const char *GetAxisInternalUnit( AstAxis *this, int *status ){
+/*
+*+
+*  Name:
+*     astGetAxisInternalUnit
+
+*  Purpose:
+*     Return the unit string for unformatted Axis values
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     const char *astGetAxisInternalUnit( AstAxis *this ){
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function returns the axis InternalUnit attribute. For basic
+*     axes, the InternalUnit and Unit attributes are the same.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the internal
+*     unit string.
+
+*  Notes:
+*     - The returned pointer points to a static memory buffer. The
+*     contents of this buffer will be over-written on each invocation of
+*     this function. A copy of the returned string should therefore be
+*     taken if it will be needed later.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+   return astGetAxisUnit( this );
+}
+
+static const char *GetAxisNormUnit( AstAxis *this, int *status ){
+/*
+*+
+*  Name:
+*     astGetAxisNormUnit
+
+*  Purpose:
+*     Return the normalised Unit attribute for an Axis.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     const char *astGetAxisNormUnit( AstAxis *this ){
+
+*  Class Membership:
+*     Axis method.
+
+*  Description:
+*     This function normalised and returns the axis Unit attribute.
+*     Normalisation refers to transformations such as "s*(m/s)" -> "m".
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the normalised
+*     unit string.
+
+*  Notes:
+*     - The returned pointer points to a static memory buffer. The
+*     contents of this buffer will be over-written on each invocation of
+*     this function. A copy of the returned string should therefore be
+*     taken if it will be needed later.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS        /* Pointer to thread-specific global data */
+   const char *result;       /* Pointer to dynamic memory holding returned text */
+   int nc;                   /* Length of normalised Unit string */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Get the Axis Unit attrribute and normalise it. */
+   result = astUnitNormaliser( astGetAxisUnit( this ) );
+
+/* If successful, check that the resulting string will fit in the buffer.
+   If not, report an error. */
+   if( result ) {
+      nc = strlen( result );
+      if( nc > AST__AXIS_GETAXISNORMUNIT_BUFF_LEN ) {
+         astError( AST__FMTER, "astGetAxisNormUnit(%s): Internal buffer "
+                      "overflow while normalising the units string '%s' "
+                      "- result exceeds %d characters.", status, astGetClass( this ),
+                      result, AST__AXIS_GETAXISNORMUNIT_BUFF_LEN );
+         result = astFree( (void *) result );
+
+/* If so, copy it into the static buffer and free the dynamic memory returned
+   by astUnitNormaliser. */
+      } else {
+         strcpy( getaxisnormunit_buff, result );
+      }
+      astFree( (void *) result );
+
+      result = getaxisnormunit_buff;
+   }
+
+/* Return the answer. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     Axis member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied Axis,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstAxis *this;             /* Pointer to Axis structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the Axis structure. */
+   this = (AstAxis *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astTSizeOf( this->label );
+   result += astTSizeOf( this->format );
+   result += astTSizeOf( this->symbol );
+   result += astTSizeOf( this->unit );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for an Axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Axis member function (over-rides the protected astGetAttrib
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for an Axis, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Axis, or at static memory. The contents of the string
+*     may be over-written or the pointer may become invalid following
+*     a further invocation of the same function or any modification of
+*     the Axis. A copy of the string should therefore be made if
+*     necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstAxis*this;                 /* Pointer to the Axis structure */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Double attribute value */
+   int digits;                   /* Digits attribute value */
+   int direction;                /* Direction attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the Axis structure. */
+   this = (AstAxis *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an
+   appropriate format.  Set "result" to point at the result string. */
+
+/* Digits. */
+/* ------- */
+   if ( !strcmp( attrib, "digits" ) ) {
+      digits = astGetAxisDigits( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", digits );
+         result = getattrib_buff;
+      }
+
+/* Direction. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "direction" ) ) {
+      direction = astGetAxisDirection( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", direction );
+         result = getattrib_buff;
+      }
+
+/* Top. */
+/* ---- */
+   } else if ( !strcmp( attrib, "top" ) ) {
+      dval = astGetAxisTop( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Bottom. */
+/* ------- */
+   } else if ( !strcmp( attrib, "bottom" ) ) {
+      dval = astGetAxisBottom( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Format. */
+/* ------- */
+   } else if ( !strcmp( attrib, "format" ) ) {
+      result = astGetAxisFormat( this );
+
+/* Label. */
+/* ------ */
+   } else if ( !strcmp( attrib, "label" ) ) {
+      result = astGetAxisLabel( this );
+
+/* Symbol. */
+/* ------- */
+   } else if ( !strcmp( attrib, "symbol" ) ) {
+      result = astGetAxisSymbol( this );
+
+/* Unit. */
+/* ----- */
+   } else if ( !strcmp( attrib, "unit" ) ) {
+      result = astGetAxisUnit( this );
+
+/* NormUnit. */
+/* --------- */
+   } else if ( !strcmp( attrib, "normunit" ) ) {
+      result = astGetAxisNormUnit( this );
+
+/* InternalUnit. */
+/* ------------- */
+   } else if ( !strcmp( attrib, "internalunit" ) ) {
+      result = astGetAxisInternalUnit( this );
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static const char *GetDefaultFormat( AstAxis *this, int *status ){
+/*
+*  Name:
+*     GetDefaultFormat
+
+*  Purpose:
+*     Return a pointer to a string holding the default Format value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     const char *GetDefaultFormat( AstAxis *this, int *status )
+
+*  Class Membership:
+*     Axis member function
+
+*  Description:
+*     This function returns a pointer to a string holding the default
+*     Format value, which is based on the current Digits value.
+
+*  Parameters:
+*     this
+*        A pointer to the Axis structure.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a static null-terminated character string containing
+*     the default Format string.
+
+*  Notes:
+*     - A null string will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS         /* Pointer to thread-specific global data */
+
+/* Check the global error status. */
+   if ( !astOK ) return "";
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Create the default format value and store it in the "format_buff"
+   static variable. */
+   (void) sprintf( getdefaultformat_buff, "%%1.%dG", astGetAxisDigits( this ) );
+
+/* Return a pointer to the "format_buff" static variable. */
+   return getdefaultformat_buff;
+}
+
+void astInitAxisVtab_(  AstAxisVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitAxisVtab
+
+*  Purpose:
+*     Initialise a virtual function table for an Axis.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     void astInitAxisVtab( AstAxisVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Axis vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Axis class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitObjectVtab( (AstObjectVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAAxis) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstObjectVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->AxisAbbrev = AxisAbbrev;
+   vtab->AxisFields = AxisFields;
+   vtab->AxisFormat = AxisFormat;
+   vtab->AxisDistance = AxisDistance;
+   vtab->AxisOffset = AxisOffset;
+   vtab->AxisCentre = AxisCentre;
+   vtab->AxisGap = AxisGap;
+   vtab->AxisIn = AxisIn;
+   vtab->AxisNorm = AxisNorm;
+   vtab->AxisNormValues = AxisNormValues;
+   vtab->AxisOverlay = AxisOverlay;
+   vtab->AxisUnformat = AxisUnformat;
+   vtab->ClearAxisDigits = ClearAxisDigits;
+   vtab->ClearAxisDirection = ClearAxisDirection;
+   vtab->ClearAxisFormat = ClearAxisFormat;
+   vtab->ClearAxisLabel = ClearAxisLabel;
+   vtab->ClearAxisSymbol = ClearAxisSymbol;
+   vtab->ClearAxisUnit = ClearAxisUnit;
+   vtab->GetAxisDigits = GetAxisDigits;
+   vtab->GetAxisDirection = GetAxisDirection;
+   vtab->GetAxisFormat = GetAxisFormat;
+   vtab->GetAxisLabel = GetAxisLabel;
+   vtab->GetAxisSymbol = GetAxisSymbol;
+   vtab->GetAxisUnit = GetAxisUnit;
+   vtab->GetAxisInternalUnit = GetAxisInternalUnit;
+   vtab->GetAxisNormUnit = GetAxisNormUnit;
+   vtab->SetAxisDigits = SetAxisDigits;
+   vtab->SetAxisDirection = SetAxisDirection;
+   vtab->SetAxisFormat = SetAxisFormat;
+   vtab->SetAxisLabel = SetAxisLabel;
+   vtab->SetAxisSymbol = SetAxisSymbol;
+   vtab->SetAxisUnit = SetAxisUnit;
+   vtab->TestAxisDigits = TestAxisDigits;
+   vtab->TestAxisDirection = TestAxisDirection;
+   vtab->TestAxisFormat = TestAxisFormat;
+   vtab->TestAxisLabel = TestAxisLabel;
+   vtab->TestAxisSymbol = TestAxisSymbol;
+   vtab->TestAxisUnit = TestAxisUnit;
+   vtab->TestAxisInternalUnit = TestAxisInternalUnit;
+   vtab->TestAxisNormUnit = TestAxisNormUnit;
+
+   vtab->ClearAxisTop = ClearAxisTop;
+   vtab->GetAxisTop = GetAxisTop;
+   vtab->SetAxisTop = SetAxisTop;
+   vtab->TestAxisTop = TestAxisTop;
+
+   vtab->ClearAxisBottom = ClearAxisBottom;
+   vtab->GetAxisBottom = GetAxisBottom;
+   vtab->SetAxisBottom = SetAxisBottom;
+   vtab->TestAxisBottom = TestAxisBottom;
+
+/* Save the inherited pointers to methods that will be extended, and replace
+   them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+/* Declare the destructor, copy constructor and dump function. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "Axis", "Coordinate axis" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static char *ParseAxisFormat( const char *fmt0, int digs, int *log, int *sign,
+                              int *lspace, int *integ, int *status ){
+/*
+*  Name:
+*     ParseAxisFormat
+
+*  Purpose:
+*     Parse the Format string for an Axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     char *ParseAxisFormat( const char *fmt0, int digs, int *log, int *sign,
+*                            int *lspace, int *integ, int *status )
+
+*  Class Membership:
+*     Axis member function
+
+*  Description:
+*     This function returns a collection of flags indicating if any AST
+*     specific formatting features are specified in the supplied Format
+*     string. It also returns a pointer to a new Format string which is a
+*     standard C printf format specifier.
+
+*  Parameters:
+*     fmt0
+*        The value of the Format attribute.
+*     digs
+*        The default number of digits of precision to use. This is used
+*        if the given format specifier includes a wildcard precision (".*").
+*        In this case, the returned format specifier will be modified to
+*        include an explicit precision value equal to the supplied value
+*        of "digs".
+*     log
+*        Pointer to an integer in which to store a flag indicating if the
+*        if the axis value should be formatted as "10**x" using the graphical
+*        escape sequences defined within the Plot class to produce "x" as a
+*        superscript of "10". A non-zero value will be returned if the
+*        supplied Format string has a '&' character in its printf <flags>
+*        field (that is, between the leading '%' sign and the optional
+*        printf field width).
+*     sign
+*        Pointer to an integer in which to store a flag indicating if a
+*        sign character ('+' or '-') should always be included in front
+*        of the "10" if "log" is returned non-zero. If "log" is returned
+*        zero, then "sign" will also be zero. If "log" is non-zero, then
+*        a non-zero value for "sign" will be returned if the supplied Format
+*        string has a '+' character in its printf <flags> field (that is,
+*        between the leading '%' sign and the optional printf field width).
+*     lspace
+*        Pointer to an integer in which to store a flag indicating if a
+*        leading space should be included in front of the "10" if "log" is
+*        returned non-zero and "sign" is returned zero. Otherwise, "lspace"
+*        will also be zero. If "log" is non-zero, then a non-zero value for
+*        "lspace" will be returned if the supplied Format string has a ' '
+*        character in its printf <flags> field (that is, between the leading
+*        '%' sign and the optional printf field width).
+*     integ
+*        Pointer to an integer in which to store a flag indicating if the
+*        returned format specifier includes an integer conversion code
+*        (e.g. %d) or floating point conversion code (e.g. "%.7G").
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a dynamically allocated null-terminated string containing
+*     the modified Format string. This will be a copy of the supplied
+*     Format string, but with any '&' flag removed. Any '+' or ' ' flag will
+*     also be removed if "log" is returned as non-zero. An explicit
+*     precision field will be included if the supplied format includes a
+*     ".*" precision field.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   char *a;             /* Pointer to next char read from original format */
+   char *b;             /* Pointer to next char to write to new format */
+   char *c;             /* Pointer to next char read from original format */
+   char *new;           /* Pointer to new returned string */
+   char *perc;          /* Pointer to percent sign */
+   char *result;        /* Pointer to the returned string */
+   int hash;            /* Was a '#' flag found? */
+   int len;             /* Used length of format string */
+   int minus;           /* Was a '-' flag found? */
+   int plus;            /* Was a '+' flag found? */
+   int rlen;            /* Length of result */
+   int space;           /* Was a ' ' flag found? */
+
+/* Initialise. */
+   result = NULL;
+   *log = 0;
+   *sign = 0;
+   *lspace = 0;
+   *integ = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Take a copy of the supplied string. Check the pointer can be used
+   safely. */
+   len = astChrLen( fmt0 );
+   result = astStore( NULL, fmt0, len + 1 );
+   if( astOK ) {
+      result[ len ] = 0;
+
+/* Find the first percent sign. Do nothing if none is found. */
+      perc = strchr( result, '%' );
+      if( perc ) {
+
+/* Check each character following the percent sign until one is found
+   which is not a legal printf flag, or a legal AST extension flag. Note
+   which ones are present. */
+         minus = 0;
+         plus = 0;
+         space = 0;
+         hash = 0;
+
+         a = perc;
+         while( ++a ){
+            if( *a == '-' ){
+               minus = 1;
+            } else if( *a == '+' ){
+               plus = 1;
+            } else if( *a == ' ' ){
+               space = 1;
+            } else if( *a == '#' ){
+               hash = 1;
+            } else if( *a == '&' ){
+               *log = 1;
+            } else {
+               break;
+            }
+         }
+
+/* If no '&' flag was found just return the unaltered copy of the
+   supplied Format string. Otherwise, remove any '+' or ' ' flag. */
+         if( *log ) {
+            if( plus ) *sign = 1;
+            if( space ) *lspace = 1;
+
+/* Append any remaining flag characters to the output string. */
+            perc++;
+            if( minus ) *(perc++) = '-';
+            if( hash ) *(perc++) = '#';
+
+/* Copy the remaining characters down to fill up the gap left by the
+   removed flags. */
+            while( *a ) *(perc++) = *(a++);
+
+/* Terminate the returned string. */
+            *perc = 0;
+
+         }
+      }
+   }
+
+/* If the format specifier being returned does include a ".*" precision,
+   replace the "*" with the value of the Digits attribute. */
+   if( result ) {
+
+/* Find the first percent sign. Do nothing if none is found. */
+      a = strchr( result, '%' );
+      if( a ) {
+
+/* Check each character following the percent sign until one is found
+   which is not a legal printf flag. */
+         while( ++a ){
+            if( *a != '-' && *a != '+' && *a != ' ' && *a != '#' ) break;
+         }
+
+/* Skip any field width (a decimal integer) following the flags. */
+         a--;
+         while( ++a ) {
+            if( !isdigit( *a ) ) break;
+         }
+
+/* Get a pointer to the next alphabetic character. This will be the
+   conversion code. If it an integer code, return *integ non-zero. */
+         c = a - 1;
+         while( ++c ) {
+            if( isalpha( *c ) ) {
+               if( *c == 'd' || *c == 'i' || *c == 'u' || *c == 'o' ||
+                   *c == 'x' || *c == 'X' || *c == 'c' ) *integ = 1;
+               break;
+            }
+         }
+
+/* Go back to the end of the field width. If the next two characters are
+   "." and "*", change the asterisk to the supplied "digs" value. */
+         if( a[ 0 ] == '.' && a[ 1 ] == '*' ) {
+
+/* Allocate memory to hold the extended format string (allowing 20
+   characters for formatting the digs value - just in case something like
+   INT_MAX is supplied by mistake), and store the existing string in it. */
+            rlen = strlen( result );
+            new = astMalloc( rlen + 22 );
+            if( new ) memcpy( new, result, rlen + 1 );
+
+/* Put the precision into the new string, following the field width. */
+            b = new + ( a - result );
+            b += sprintf( b, ".%d", digs );
+
+/* Copy the remainder of the original format string to the new format
+   string. */
+            if( a[ 2 ] != 0 ) strcpy( b, a + 2 );
+
+/* Use the new format string in place of the old.*/
+            astFree( result );
+            result = new;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for an Axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     Axis member function (over-rides the protected astSetAttrib
+*     method inherited from the Object class).
+
+*  Description:
+*     This function assigns an attribute value for an Axis, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     setting
+*        Pointer to a null terminated string specifying the new
+*        attribute value.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstAxis *this;                /* Pointer to Axis structure */
+   double dval;                  /* Double attribute value */
+   int digits;                   /* Number of digits of precision */
+   int direction;                /* Plot axis in normal direction? */
+   int format;                   /* Offset of Format string */
+   int label;                    /* Offset of Label string */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read from setting */
+   int symbol;                   /* Offset of Symbol string */
+   int unit;                     /* Offset of Unit string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Axis structure. */
+   this = (AstAxis *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* Digits. */
+/* ------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "digits= %d %n", &digits, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisDigits( this, digits );
+
+/* Direction. */
+/* ---------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "direction= %d %n", &direction, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisDirection( this, direction );
+
+/* Top. */
+/* ---- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "top= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisTop( this, dval );
+
+/* Bottom. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "bottom= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisBottom( this, dval );
+
+/* Format. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "format=%n%*[^\n]%n", &format, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisFormat( this, setting + format );
+
+/* Label. */
+/* ------ */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "label=%n%*[^\n]%n", &label, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisLabel( this, setting + label );
+
+/* Symbol. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "symbol=%n%*[^\n]%n", &symbol, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisSymbol( this, setting + symbol );
+
+/* Unit. */
+/* ----- */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "unit=%n%*[^\n]%n", &unit, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisUnit( this, setting + unit );
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* Use this macro to report an error if a read-only attribute has been
+   specified. */
+   } else if ( MATCH( "normunit" ) ||
+               MATCH( "internalunit" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Pass any unrecognised attribute setting to the parent method for further
+   interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for an Axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Axis member function (over-rides the astTestAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate
+*     whether a value has been set for one of an Axis' attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstAxis *this;                /* Pointer to the Axis structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Axis structure. */
+   this = (AstAxis *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Digits. */
+/* ------- */
+   if ( !strcmp( attrib, "digits" ) ) {
+      result = astTestAxisDigits( this );
+
+/* Direction. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "direction" ) ) {
+      result = astTestAxisDirection( this );
+
+/* Top. */
+/* ---- */
+   } else if ( !strcmp( attrib, "top" ) ) {
+      result = astTestAxisTop( this );
+
+/* Bottom. */
+/* ------- */
+   } else if ( !strcmp( attrib, "bottom" ) ) {
+      result = astTestAxisBottom( this );
+
+/* Format. */
+/* ------- */
+   } else if ( !strcmp( attrib, "format" ) ) {
+      result = astTestAxisFormat( this );
+
+/* Label. */
+/* ------ */
+   } else if ( !strcmp( attrib, "label" ) ) {
+      result = astTestAxisLabel( this );
+
+/* Symbol. */
+/* ------- */
+   } else if ( !strcmp( attrib, "symbol" ) ) {
+      result = astTestAxisSymbol( this );
+
+/* Unit. */
+/* ----- */
+   } else if ( !strcmp( attrib, "unit" ) ) {
+      result = astTestAxisUnit( this );
+
+/* InternalUnit. */
+/* --------- */
+   } else if ( !strcmp( attrib, "internalunit" ) ) {
+      result = astTestAxisInternalUnit( this );
+
+/* NormUnit. */
+/* --------- */
+   } else if ( !strcmp( attrib, "normunit" ) ) {
+      result = astTestAxisNormUnit( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static int TestAxisInternalUnit( AstAxis *this, int *status ){
+/*
+*  Name:
+*     TestAxisInternalUnit
+
+*  Purpose:
+*     Test if a InternalUnit attribute value is set for an Axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     int TestAxisInternalUnit( AstAxis *this, int *status )
+
+*  Class Membership:
+*     Axis member function
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate
+*     whether a value has been set for the InternalUnit string.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Tell the world that we know what value to use for InternalUnit if and
+   only if a value has been set for Unit. */
+   return astTestAxisUnit( this );
+}
+
+static int TestAxisNormUnit( AstAxis *this, int *status ){
+/*
+*  Name:
+*     TestAxisNormUnit
+
+*  Purpose:
+*     Test if a NormUnit attribute value is set for an Axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     int TestAxisNormUnit( AstAxis *this, int *status )
+
+*  Class Membership:
+*     Axis member function
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate
+*     whether a value has been set for the NormUnit string.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+   return astTestAxisUnit( this );
+}
+
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with this
+   class using the macros defined for this purpose in the "object.h" file. For
+   a description of each attribute, see the class interface (in the associated
+   .h file). */
+
+/* Digits. */
+/* ------- */
+/* Clear the Digits value by setting it to -INT_MAX. */
+astMAKE_CLEAR(Axis,AxisDigits,digits,-INT_MAX)
+
+/* Supply a default of 7 digits if no value has been set. */
+astMAKE_GET(Axis,AxisDigits,int,0,( this->digits != -INT_MAX ?
+                                    this->digits : 7 ))
+
+/* Constrain the Digits value being set to be at least 1. */
+astMAKE_SET(Axis,AxisDigits,int,digits,( value > 1 ? value : 1 ))
+
+/* The Digits value is set if it is not -INT_MAX. */
+astMAKE_TEST(Axis,AxisDigits,( this->digits != -INT_MAX ))
+
+/* Direction. */
+/* ---------- */
+/* Clear the Direction value by setting it to -INT_MAX. */
+astMAKE_CLEAR(Axis,AxisDirection,direction,-INT_MAX)
+
+/* Supply a default value of 1 if the Direction value is not set. */
+astMAKE_GET(Axis,AxisDirection,int,0,( this->direction != -INT_MAX ?
+                                       this->direction : 1 ))
+
+/* Set a Direction value of 1 if any non-zero value is supplied. */
+astMAKE_SET(Axis,AxisDirection,int,direction,( value != 0 ))
+
+/* The Direction value is set if it is not -INT_MAX. */
+astMAKE_TEST(Axis,AxisDirection,( this->direction != -INT_MAX ))
+
+/* Top. */
+/* -----*/
+/* Clear the Top Direction value by setting it to AST__BAD. */
+astMAKE_CLEAR(Axis,AxisTop,top,AST__BAD)
+
+/* Supply a default value of DBL_MAX if the Top value is not set.*/
+astMAKE_GET(Axis,AxisTop,double,0,( this->top != AST__BAD ? this->top : DBL_MAX))
+
+/* Set the Top value. */
+astMAKE_SET(Axis,AxisTop,double,top,(value))
+
+/* The Top value is set if it is not AST__BAD. */
+astMAKE_TEST(Axis,AxisTop,( this->top != AST__BAD ))
+
+/* Bottom. */
+/* --------*/
+/* Clear the Bottom Direction value by setting it to AST__BAD. */
+astMAKE_CLEAR(Axis,AxisBottom,bottom,AST__BAD)
+
+/* Supply a default value of -DBL_MAX if the Bottom value is not set.*/
+astMAKE_GET(Axis,AxisBottom,double,0.0,( this->bottom != AST__BAD ? this->bottom : -DBL_MAX))
+
+/* Set the Bottom value. */
+astMAKE_SET(Axis,AxisBottom,double,bottom,(value))
+
+/* The Bottom value is set if it is not AST__BAD. */
+astMAKE_TEST(Axis,AxisBottom,( this->bottom != AST__BAD ))
+
+/* Format. */
+/* ------- */
+/* Clear the Format value by freeing the allocated memory and assigning a NULL
+   pointer. */
+astMAKE_CLEAR(Axis,AxisFormat,format,astFree( this->format ))
+
+/* If the Format value is not set, return a pointer to a default Format
+   string. */
+astMAKE_GET(Axis,AxisFormat,const char *,NULL,( this->format ? this->format :
+            GetDefaultFormat( this, status ) ) )
+
+/* Set a Format value by freeing any previously allocated memory, allocating
+   new memory, storing the string and saving the pointer to the copy. */
+astMAKE_SET(Axis,AxisFormat,const char *,format,astStore( this->format, value,
+                                                strlen( value ) + (size_t) 1 ))
+
+/* The Format value is set if the pointer to it is not NULL. */
+astMAKE_TEST(Axis,AxisFormat,( this->format != NULL ))
+
+/* Label. */
+/* ------ */
+/* Clear the Label value by freeing the allocated memory and assigning a NULL
+   pointer. */
+astMAKE_CLEAR(Axis,AxisLabel,label,astFree( this->label ))
+
+/* If the Label value is not set, supply a default value by way of a pointer
+   to the constant string "Coordinate Axis". */
+astMAKE_GET(Axis,AxisLabel,const char *,NULL,( this->label ? this->label :
+                                               "Coordinate axis" ))
+
+/* Set a Label value by freeing any previously allocated memory, allocating
+   new memory, storing the string and saving the pointer to the copy. */
+astMAKE_SET(Axis,AxisLabel,const char *,label,astStore( this->label, value,
+                                              strlen( value ) + (size_t) 1 ))
+
+/* The Label value is set if the pointer to it is not NULL. */
+astMAKE_TEST(Axis,AxisLabel,( this->label != NULL ))
+
+/* Symbol. */
+/* ------- */
+/* Clear the Symbol value by freeing the allocated memory and assigning a NULL
+   pointer. */
+astMAKE_CLEAR(Axis,AxisSymbol,symbol,astFree( this->symbol ))
+
+/* If the Symbol value is not set, supply a default value by way of a pointer
+   to the constant string "x". */
+astMAKE_GET(Axis,AxisSymbol,const char *,NULL,( this->symbol ? this->symbol :
+                                                               "x" ))
+
+/* Set a Symbol value by freeing any previously allocated memory, allocating
+   new memory, storing the string and saving the pointer to the copy. */
+astMAKE_SET(Axis,AxisSymbol,const char *,symbol,astStore( this->symbol, value,
+                                                strlen( value ) + (size_t) 1 ))
+
+/* The Symbol value is set if the pointer to it is not NULL. */
+astMAKE_TEST(Axis,AxisSymbol,( this->symbol != NULL ))
+
+/* Unit. */
+/* ----- */
+/* Clear the Unit value by freeing the allocated memory and assigning a NULL
+   pointer. */
+astMAKE_CLEAR(Axis,AxisUnit,unit,astFree( this->unit ))
+
+/* If the Unit value is not set, supply a default value by way of a pointer
+   to the constant string "". */
+astMAKE_GET(Axis,AxisUnit,const char *,NULL,( this->unit ? this->unit : "" ))
+
+/* Set a Unit value by freeing any previously allocated memory, allocating
+   new memory, storing the string and saving the pointer to the copy. */
+astMAKE_SET(Axis,AxisUnit,const char *,unit,astStore( this->unit, value,
+                                            strlen( value ) + (size_t) 1 ))
+
+/* The Unit value is set if the pointer to it is not NULL. */
+astMAKE_TEST(Axis,AxisUnit,( this->unit != NULL ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Axis objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Axis objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstAxis *in;                  /* Pointer to input Axis */
+   AstAxis *out;                 /* Pointer to output Axis */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Axis structures. */
+   in = (AstAxis *) objin;
+   out = (AstAxis *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output Axis. */
+   out->format = NULL;
+   out->label = NULL;
+   out->symbol = NULL;
+   out->unit = NULL;
+
+/* Make copies of the allocated strings and Objects. */
+   if ( in->label ) out->label = astStore( NULL, in->label,
+                                 strlen( in->label ) + (size_t) 1 );
+   if ( in->format ) out->format = astStore( NULL, in->format,
+                                   strlen( in->format ) + (size_t) 1 );
+   if ( in->symbol ) out->symbol = astStore( NULL, in->symbol,
+                                   strlen( in->symbol ) + (size_t) 1 );
+   if ( in->unit ) out->unit = astStore( NULL, in->unit,
+                               strlen( in->unit ) + (size_t) 1 );
+
+/* If an error occurred, clean up by freeing all memory allocated above. */
+   if ( !astOK ) {
+      out->format = astFree( out->format );
+      out->label = astFree( out->label );
+      out->symbol = astFree( out->symbol );
+      out->unit = astFree( out->unit );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Axis objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Axis objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstAxis *this;                /* Pointer to Axis */
+
+/* Obtain a pointer to the Axis structure. */
+   this = (AstAxis *) obj;
+
+/* Free all allocated memory. */
+   this->format = astFree( this->format );
+   this->label = astFree( this->label );
+   this->symbol = astFree( this->symbol );
+   this->unit = astFree( this->unit );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Axis objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Axis class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstAxis *this;                /* Pointer to the Axis structure */
+   char comment[ 80 ];           /* Buffer for comment string */
+   const char *sval;             /* Pointer to string value */
+   const char *lab;              /* Pointer to unit label */
+   double dval;                  /* Double value */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Axis structure. */
+   this = (AstAxis *) this_object;
+
+/* Write out values representing the instance variables for the
+   Axis class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Label. */
+/* ------ */
+   set = TestAxisLabel( this, status );
+   sval = set ? GetAxisLabel( this, status ) : astGetAxisLabel( this );
+   astWriteString( channel, "Label", set, 1, sval, "Axis Label" );
+
+/* Symbol. */
+/* ------- */
+   set = TestAxisSymbol( this, status );
+   sval = set ? GetAxisSymbol( this, status ) : astGetAxisSymbol( this );
+   astWriteString( channel, "Symbol", set, 1, sval, "Axis symbol" );
+
+/* Unit. */
+/* ----- */
+   set = TestAxisUnit( this, status );
+   sval = set ? GetAxisUnit( this, status ) : astGetAxisUnit( this );
+
+/* Get any label associated with the unit string. */
+   lab = astUnitLabel( sval );
+
+/* Construct a comment including the above label (but only if it is not
+   the same as the unit string) . */
+   if( lab && strcmp( lab, sval ) ) {
+      (void) sprintf( comment, "Axis units (%s)", lab );
+   } else {
+      (void) sprintf( comment, "Axis units" );
+   }
+
+/* Write out the Unit value. */
+   astWriteString( channel, "Unit", set, 0, sval, comment );
+
+/* Digits. */
+/* ------- */
+   set = TestAxisDigits( this, status );
+   ival = set ? GetAxisDigits( this, status ) : astGetAxisDigits( this );
+   astWriteInt( channel, "Digits", set, 0, ival,
+                "Default formatting precision" );
+
+/* Format. */
+/* ------- */
+   set = TestAxisFormat( this, status );
+   sval = set ? GetAxisFormat( this, status ) : astGetAxisFormat( this );
+   astWriteString( channel, "Format", set, 0, sval, "Format specifier" );
+
+/* Direction. */
+/* ---------- */
+   set = TestAxisDirection( this, status );
+   ival = set ? GetAxisDirection( this, status ) : astGetAxisDirection( this );
+   astWriteInt( channel, "Dirn", set, 0, ival,
+                ival ? "Plot in conventional direction (hint)" :
+                       "Plot in reverse direction (hint)" );
+/* Top. */
+/* ---- */
+   set = TestAxisTop( this, status );
+   dval = set ? GetAxisTop( this, status ) : astGetAxisTop( this );
+   astWriteDouble( channel, "Top", set, 0, dval, "Maximum legal axis value" );
+
+/* Bottom. */
+/* ------- */
+   set = TestAxisBottom( this, status );
+   dval = set ? GetAxisBottom( this, status ) : astGetAxisBottom( this );
+   astWriteDouble( channel, "Bottom", set, 0, dval, "Minimum legal axis value" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAAxis and astCheckAxis functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Axis,Object)
+astMAKE_CHECK(Axis)
+
+AstAxis *astAxis_( const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astAxis
+
+*  Purpose:
+*     Create an Axis.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     AstAxis *astAxis( const char *options, int *status, ... )
+
+*  Class Membership:
+*     Axis constructor.
+
+*  Description:
+*     This function creates a new Axis and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new Axis. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new Axis.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstAxis *new;                 /* Pointer to new Axis */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the Axis, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitAxis( NULL, sizeof( AstAxis ), !class_init, &class_vtab,
+                      "Axis" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new Axis'
+   attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Axis. */
+   return new;
+}
+
+AstAxis *astAxisId_( const char *options, ... ) {
+/*
+*  Name:
+*     astAxisId_
+
+*  Purpose:
+*     Create an Axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     AstAxis *astAxisId_( const char *options, ... );
+
+*  Class Membership:
+*     Axis constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astAxis constructor function. It returns an ID value (instead of
+*     a true C pointer) to external users, and must be provided
+*     because astAxis_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astAxis_ directly, so it must be a re-implementation of
+*     it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astAxis_.
+
+*  Returned Value:
+*     The ID value associated with the new Axis.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstAxis *new;                 /* Pointer to new Axis */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the Axis, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitAxis( NULL, sizeof( AstAxis ), !class_init, &class_vtab,
+                      "Axis" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new Axis'
+   attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new Axis. */
+   return astMakeId( new );
+}
+
+AstAxis *astInitAxis_( void *mem, size_t size, int init,
+                       AstAxisVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitAxis
+
+*  Purpose:
+*     Initialise an Axis.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     AstAxis *astInitAxis( void *mem, size_t size, int init,
+*                           AstAxisVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Axis initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Axis object. It allocates memory (if necessary) to accommodate
+*     the Axis plus any additional data associated with the derived class.
+*     It then initialises an Axis structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for an Axis at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Axis is to be created. This
+*        must be of sufficient size to accommodate the Axis data
+*        (sizeof(Axis)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Axis (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Axis
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Axis's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Axis.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astClass
+*        method).
+
+*  Returned Value:
+*     A pointer to the new Axis.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstAxis *new;                 /* Pointer to new Axis */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitAxisVtab( vtab, name );
+
+/* Initialise an Object structure (the parent class) as the first component
+   within the Axis structure, allocating memory if necessary. */
+   new = (AstAxis *) astInitObject( mem, size, 0, (AstObjectVtab *) vtab,
+                                    name );
+
+   if ( astOK ) {
+
+/* Initialise the Axis data. */
+/* ------------------------- */
+/* Initialise all attributes to their "undefined" values. */
+      new->digits = -INT_MAX;
+      new->direction = -INT_MAX;
+      new->format = NULL;
+      new->label = NULL;
+      new->symbol = NULL;
+      new->unit = NULL;
+      new->top = AST__BAD;
+      new->bottom = AST__BAD;
+
+/* If an error occurred, clean up by deleting the new Axis. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Axis. */
+   return new;
+}
+
+AstAxis *astLoadAxis_( void *mem, size_t size,
+                       AstAxisVtab *vtab, const char *name,
+                       AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadAxis
+
+*  Purpose:
+*     Load an Axis.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     AstAxis *astLoadAxis( void *mem, size_t size,
+*                           AstAxisVtab *vtab, const char *name,
+*                           AstChannel *channel )
+
+*  Class Membership:
+*     Axis loader.
+
+*  Description:
+*     This function is provided to load a new Axis using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Axis structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Axis at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Axis is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Axis data (sizeof(Axis)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Axis (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Axis structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstAxis) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Axis. If this is NULL, a pointer
+*        to the (static) virtual function table for the Axis class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Axis" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Axis.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstAxis *new;                 /* Pointer to the new Axis */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Axis. In this case the
+   Axis belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstAxis );
+      vtab = &class_vtab;
+      name = "Axis";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitAxisVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Axis. */
+   new = astLoadObject( mem, size, (AstObjectVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Axis" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Label. */
+/* ------ */
+/* Note that string values do not require any additional processing. */
+      new->label = astReadString( channel, "label", NULL );
+
+/* Symbol. */
+/* ------- */
+      new->symbol = astReadString( channel, "symbol", NULL );
+
+/* Unit. */
+/* ----- */
+      new->unit = astReadString( channel, "unit", NULL );
+
+/* Digits. */
+/* ------- */
+      new->digits = astReadInt( channel, "digits", -INT_MAX );
+      if ( TestAxisDigits( new, status ) ) SetAxisDigits( new, new->digits, status );
+
+/* Format. */
+/* ------- */
+      new->format = astReadString( channel, "format", NULL );
+
+/* Direction. */
+/* ---------- */
+      new->direction = astReadInt( channel, "dirn", -INT_MAX );
+      if ( TestAxisDirection( new, status ) ) SetAxisDirection( new, new->direction, status );
+
+/* Top. */
+/* ---- */
+      new->top = astReadDouble( channel, "top", AST__BAD );
+      if ( TestAxisTop( new, status ) ) SetAxisTop( new, new->top, status );
+
+/* Bottom. */
+/* ---- */
+      new->bottom = astReadDouble( channel, "bottom", AST__BAD );
+      if ( TestAxisBottom( new, status ) ) SetAxisBottom( new, new->bottom, status );
+
+/* If an error occurred, clean up by deleting the new Axis. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Axis pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+/* External interfaces for the attribute access functions are generated
+   automatically by the macros that implement the access functions themselves.
+   Hence, we need only provide external interfaces for a few additional
+   functions here. */
+const char *astAxisAbbrev_( AstAxis *this, const char *fmt,
+                            const char *str1, const char *str2, int *status ) {
+   if ( !astOK ) return str2;
+   return (**astMEMBER(this,Axis,AxisAbbrev))( this, fmt, str1, str2, status );
+}
+const char *astAxisFormat_( AstAxis *this, double value, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Axis,AxisFormat))( this, value, status );
+}
+double astAxisDistance_( AstAxis *this, double v1, double v2, int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,Axis,AxisDistance))( this, v1, v2, status );
+}
+double astAxisOffset_( AstAxis *this, double v1, double dist, int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,Axis,AxisOffset))( this, v1, dist, status );
+}
+double astAxisCentre_( AstAxis *this, double value, double gap, int *status ) {
+   if ( !astOK ) return 0.0;
+   return (**astMEMBER(this,Axis,AxisCentre))( this, value, gap, status );
+}
+double astAxisGap_( AstAxis *this, double gap, int *ntick, int *status ) {
+   if ( !astOK ) return 0.0;
+   return (**astMEMBER(this,Axis,AxisGap))( this, gap, ntick, status );
+}
+void astAxisNorm_( AstAxis *this, double *value, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Axis,AxisNorm))( this, value, status );
+}
+void astAxisOverlay_( AstAxis *template, AstAxis *result, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(template,Axis,AxisOverlay))( template, result, status );
+}
+int astAxisUnformat_( AstAxis *this, const char *string, double *value, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Axis,AxisUnformat))( this, string, value, status );
+}
+int astAxisFields_( AstAxis *this, const char *fmt, const char *str,
+                    int maxfld, char **fields, int *nc, double *val, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Axis,AxisFields))( this, fmt, str, maxfld, fields, nc, val, status );
+}
+int astAxisIn_( AstAxis *this, double lo, double hi, double val, int closed, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Axis,AxisIn))( this, lo, hi, val, closed, status );
+}
+const char *astGetAxisNormUnit_( AstAxis *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Axis,GetAxisNormUnit))( this, status );
+}
+int astTestAxisNormUnit_( AstAxis *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Axis,TestAxisNormUnit))( this, status );
+}
+const char *astGetAxisInternalUnit_( AstAxis *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Axis,GetAxisInternalUnit))( this, status );
+}
+int astTestAxisInternalUnit_( AstAxis *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Axis,TestAxisInternalUnit))( this, status );
+}
+void astAxisNormValues_( AstAxis *this, int oper, int nval, double *values,
+                         int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Axis,AxisNormValues))( this, oper, nval, values, status );
+}
+
+
+
+
+
+
diff --git a/ast/axis.h b/ast/axis.h
new file mode 100644
index 0000000..3b053c5
--- /dev/null
+++ b/ast/axis.h
@@ -0,0 +1,625 @@
+#if !defined( AXIS_INCLUDED )    /* Include this file only once */
+#define AXIS_INCLUDED
+/*
+*+
+*  Name:
+*     axis.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Axis class.
+
+*  Invocation:
+*     #include "axis.h"
+
+*  Description:
+*     This include file defines the interface to the Axis class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The Axis class implements the basic behaviour of a coordinate
+*     axis, several of which may be assembled to represent a
+*     coordinate system.
+
+*  Inheritance:
+*     The Axis class inherits from the Object class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     Bottom (double)
+*        Lowest legal value for axis.
+*     Digits (integer)
+*        Specifies how many digits of precision are required by
+*        default when a coordinate value for an Axis is formatted
+*        (e.g. using the astAxisFormat method). The Digits value acts
+*        as a default only and is over-ridden if a Format string is
+*        specified explicitly (using the astSetAxisFormat method). The
+*        default supplied by the Axis class for the Digits attribute
+*        itself is 7.
+*     Direction (integer)
+*        Specifies how coordinate values for an Axis should be
+*        displayed. By default, it has the value one, indicating that
+*        they should be shown in the conventional sense
+*        (i.e. increasing left to right for an abscissa and bottom to
+*        top for an ordinate). If set to zero, this attribute
+*        indicates that the direction should be reversed (as would
+*        often be done for an astronomical magnitude or a right
+*        ascension axis, for example).
+*     Format (string)
+*        Specifies the format to be used to display coordinate values
+*        for an Axis (i.e. to convert them from binary to character
+*        form). The interpretation of this string (e.g. by derived
+*        classes) is left to the astAxisFormat method, but the Axis
+*        class interprets this parameter as a C "printf" format string
+*        which should be capable of formatting a single coordinate
+*        value stored as a double (e.g.  "%1.7G"). If no Format string
+*        is set, the default supplied by the Axis class is based on
+*        the value of the Digits attribute.
+*     Label (string)
+*        Specifies the label to be attached to an Axis when it is
+*        represented in (e.g.) a graph. It is intended purely for
+*        interpretation by human readers and not by software. The
+*        default supplied by the Axis class is the string "Coordinate
+*        Axis".
+*     Symbol (string)
+*        Specifies the symbol to be used to represent coordinate
+*        values for an Axis in "short form", such as in algebraic
+*        expressions where a full description of the Axis would be
+*        inappropriate. Examples include "RA" and "Dec" (for Right
+*        Ascension and Declination). The default supplied by the Axis
+*        class is the string "x".
+*     Top (double)
+*        Highest legal value for axis.
+*     Unit (string)
+*        Describes the units used to represent coordinate values on an
+*        Axis.  The default supplied by the Axis class is an empty
+*        string "".
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+
+*     Protected:
+*        astSetAttrib
+*           Set an attribute value for an Axis.
+
+*  New Methods Defined:
+*     Public:
+*        astAxisFormat
+*           Format a coordinate value for an Axis.
+*        astAxisNorm
+*           Normalise an Axis coordinate value.
+*        astAxisUnformat
+*           Read a formatted coordinate value for an Axis.
+
+*     Protected:
+*        astAxisAbbrev
+*           Abbreviate a formatted Axis value by skipping leading fields.
+*        astAxisDistance
+*           Find the distance between two axis values.
+*        astAxisFields
+*           Identify the fields within a formatted SkyAxis value.
+*        astAxisCentre
+*           Find a "nice" central axis value.
+*        astAxisGap
+*           Find a "nice" gap for tabulating Axis values.
+*        astAxisOffset
+*           Add an increment onto a supplied axis value.
+*        astAxisOverlay
+*           Overlay the attributes of a template Axis on to another Axis.
+*        astClearAxisDigits
+*           Clear the Digits attribute for an Axis.
+*        astClearAxisDirection
+*           Clear the Direction attribute for an Axis.
+*        astClearAxisFormat
+*           Clear the Format attribute for an Axis.
+*        astClearAxisLabel
+*           Clear the Label attribute for an Axis.
+*        astClearAxisSymbol
+*           Clear the Symbol attribute for an Axis.
+*        astClearAxisUnit
+*           Clear the Unit attribute for an Axis.
+*        astGetAxisDigits
+*           Get the value of the Digits attribute for an Axis.
+*        astGetAxisDirection
+*           Get the value of the Direction attribute for an Axis.
+*        astGetAxisFormat
+*           Get a pointer to the Format attribute for an Axis.
+*        astGetAxisLabel
+*           Get a pointer to the Label attribute for an Axis.
+*        astGetAxisSymbol
+*           Get a pointer to the Symbol attribute for an Axis.
+*        astGetAxisUnit
+*           Get a pointer to the Unit attribute for an Axis.
+*        astSetAxisDigits
+*           Set the value of the Digits attribute for an Axis.
+*        astSetAxisDirection
+*           Set the value of the Direction attribute for an Axis.
+*        astSetAxisFormat
+*           Set the value of the Format attribute for an Axis.
+*        astSetAxisLabel
+*           Set the value of the Label attribute for an Axis.
+*        astSetAxisSymbol
+*           Set the value of the Symbol attribute for an Axis.
+*        astSetAxisUnit
+*           Set the value of the Unit attribute for an Axis.
+*        astTestAxisDigits
+*           Test whether a value has been set for the Digits attribute of an
+*           Axis.
+*        astTestAxisDirection
+*           Test whether a value has been set for the Direction attribute of an
+*           Axis.
+*        astTestAxisFormat
+*           Test whether a value has been set for the Format attribute of an
+*           Axis.
+*        astTestAxisLabel
+*           Test whether a value has been set for the Label attribute of an
+*           Axis.
+*        astTestAxisSymbol
+*           Test whether a value has been set for the Symbol attribute of an
+*           Axis.
+*        astTestAxisUnit
+*           Test whether a value has been set for the Unit attribute of an
+*           Axis.
+
+*  Other Class Functions:
+*     Public:
+*        astAxis
+*           Create an Axis.
+*        astIsAAxis
+*           Test class membership.
+
+*     Protected:
+*        astCheckAxis
+*           Validate class membership.
+*        astInitAxis
+*           Initialise an Axis.
+*        astLoadAxis
+*           Load an Axis.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstAxis
+*           Axis object type.
+
+*     Protected:
+*        AstAxisVtab
+*           Axis virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: B.S. Berry (Starlink)
+
+*  History:
+*     1-MAR-1996 (RFWS):
+*        Original version.
+*     25-APR-1996 (RFWS):
+*        Made all attribute access functions protected.
+*     10-SEP-1996 (RFWS):
+*        Added I/O facilities.
+*     11-SEP-1996 (RFWS):
+*        Added astAxisGap (written by DSB).
+*     25-FEB-1998 (RFWS):
+*        Added astAxisUnformat.
+*     29-AUG-2001 (DSB):
+*        Added AxisDistance and AxisOffset.
+*     10-OCT-2002 (DSB):
+*        Added Top and Bottom.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitAxisVtab method.
+*     17-APR-2015 (DSB):
+*        Added astAxisCentre.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"
+#endif
+
+
+/* Macros */
+/* ====== */
+#if defined(astCLASS)
+#define AST__AXIS_GETDEFAULTFORMAT_BUFF_LEN 50
+#define AST__AXIS_AXISFORMAT_BUFF_LEN 127
+#define AST__AXIS_GETAXISNORMUNIT_BUFF_LEN 127
+#define AST__AXIS_GETATTRIB_BUFF_LEN 50
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* Axis structure. */
+/* --------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstAxis {
+
+/* Attributes inherited from the parent class. */
+   AstObject object;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   char *label;                  /* Pointer to label string */
+   char *format;                 /* Pointer to format string */
+   char *symbol;                 /* Pointer to symbol string */
+   char *unit;                   /* Pointer to unit string */
+   int digits;                   /* Default digits of precision */
+   int direction;                /* Plot in conventional direction? */
+   double top;                   /* Highest legal axis value */
+   double bottom;                /* Lowest legal axis value */
+} AstAxis;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+
+typedef struct AstAxisVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstObjectVtab object_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   const char *(* AxisAbbrev)( AstAxis *, const char *, const char *, const char *, int * );
+   const char *(* AxisFormat)( AstAxis *, double, int * );
+   const char *(* GetAxisFormat)( AstAxis *, int * );
+   const char *(* GetAxisLabel)( AstAxis *, int * );
+   const char *(* GetAxisSymbol)( AstAxis *, int * );
+   const char *(* GetAxisUnit)( AstAxis *, int * );
+   const char *(* GetAxisInternalUnit)( AstAxis *, int * );
+   const char *(* GetAxisNormUnit)( AstAxis *, int * );
+   double (* AxisCentre)( AstAxis *, double, double, int * );
+   double (* AxisGap)( AstAxis *, double, int *, int * );
+   double (* AxisDistance)( AstAxis *, double, double, int * );
+   double (* AxisOffset)( AstAxis *, double, double, int * );
+   int (* AxisIn)( AstAxis *, double, double, double, int, int * );
+   int (* AxisFields)( AstAxis *, const char *, const char *, int, char **, int *, double *, int * );
+   int (* AxisUnformat)( AstAxis *, const char *, double *, int * );
+   int (* GetAxisDigits)( AstAxis *, int * );
+   int (* GetAxisDirection)( AstAxis *, int * );
+   int (* TestAxisDigits)( AstAxis *, int * );
+   int (* TestAxisDirection)( AstAxis *, int * );
+   int (* TestAxisFormat)( AstAxis *, int * );
+   int (* TestAxisLabel)( AstAxis *, int * );
+   int (* TestAxisSymbol)( AstAxis *, int * );
+   int (* TestAxisUnit)( AstAxis *, int * );
+   int (* TestAxisInternalUnit)( AstAxis *, int * );
+   int (* TestAxisNormUnit)( AstAxis *, int * );
+   void (* AxisNorm)( AstAxis *, double *, int * );
+   void (* AxisNormValues)( AstAxis *, int, int, double *, int * );
+   void (* AxisOverlay)( AstAxis *, AstAxis *, int * );
+   void (* ClearAxisDigits)( AstAxis *, int * );
+   void (* ClearAxisDirection)( AstAxis *, int * );
+   void (* ClearAxisFormat)( AstAxis *, int * );
+   void (* ClearAxisLabel)( AstAxis *, int * );
+   void (* ClearAxisSymbol)( AstAxis *, int * );
+   void (* ClearAxisUnit)( AstAxis *, int * );
+   void (* SetAxisDigits)( AstAxis *, int, int * );
+   void (* SetAxisDirection)( AstAxis *, int, int * );
+   void (* SetAxisFormat)( AstAxis *, const char *, int * );
+   void (* SetAxisLabel)( AstAxis *, const char *, int * );
+   void (* SetAxisSymbol)( AstAxis *, const char *, int * );
+   void (* SetAxisUnit)( AstAxis *, const char *, int * );
+
+   double (* GetAxisTop)( AstAxis *, int * );
+   int (* TestAxisTop)( AstAxis *, int * );
+   void (* ClearAxisTop)( AstAxis *, int * );
+   void (* SetAxisTop)( AstAxis *, double, int * );
+
+   double (* GetAxisBottom)( AstAxis *, int * );
+   int (* TestAxisBottom)( AstAxis *, int * );
+   void (* ClearAxisBottom)( AstAxis *, int * );
+   void (* SetAxisBottom)( AstAxis *, double, int * );
+
+} AstAxisVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstAxisGlobals {
+   AstAxisVtab Class_Vtab;
+   int Class_Init;
+   char GetDefaultFormat_Buff[ AST__AXIS_GETDEFAULTFORMAT_BUFF_LEN + 1 ];
+   char AxisFormat_Buff[ AST__AXIS_AXISFORMAT_BUFF_LEN + 1 ];
+   char GetAxisNormUnit_Buff[ AST__AXIS_GETAXISNORMUNIT_BUFF_LEN + 1 ];
+   char GetAttrib_Buff[ AST__AXIS_GETATTRIB_BUFF_LEN + 1 ];
+} AstAxisGlobals;
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Axis)             /* Check class membership */
+astPROTO_ISA(Axis)               /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstAxis *astAxis_( const char *, int *, ...);
+#else
+AstAxis *astAxisId_( const char *, ... )__attribute__((format(printf,1,2)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstAxis *astInitAxis_( void *, size_t, int, AstAxisVtab *, const char *, int * );
+
+/* Vtab initialiser. */
+void astInitAxisVtab_( AstAxisVtab *, const char *, int * );
+
+/* Loader. */
+AstAxis *astLoadAxis_( void *, size_t, AstAxisVtab *, const char *,
+                       AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitAxisGlobals_( AstAxisGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+const char *astAxisFormat_( AstAxis *, double, int * );
+int astAxisUnformat_( AstAxis *, const char *, double *, int * );
+void astAxisNorm_( AstAxis *, double *, int * );
+void astAxisNormValues_( AstAxis *, int, int, double *, int * );
+
+#if defined(astCLASS)            /* Protected */
+const char *astAxisAbbrev_( AstAxis *, const char *, const char *, const char *, int * );
+const char *astGetAxisFormat_( AstAxis *, int * );
+const char *astGetAxisLabel_( AstAxis *, int * );
+const char *astGetAxisSymbol_( AstAxis *, int * );
+const char *astGetAxisUnit_( AstAxis *, int * );
+const char *astGetAxisNormUnit_( AstAxis *, int * );
+const char *astGetAxisInternalUnit_( AstAxis *, int * );
+double astAxisCentre_( AstAxis *, double, double, int * );
+double astAxisGap_( AstAxis *, double, int *, int * );
+double astAxisDistance_( AstAxis *, double, double, int * );
+double astAxisOffset_( AstAxis *, double, double, int * );
+int astGetAxisDigits_( AstAxis *, int * );
+int astGetAxisDirection_( AstAxis *, int * );
+int astTestAxisDigits_( AstAxis *, int * );
+int astTestAxisDirection_( AstAxis *, int * );
+int astAxisFields_( AstAxis *, const char *, const char *, int, char **, int *, double *, int * );
+int astAxisIn_( AstAxis *, double, double, double, int, int * );
+int astTestAxisFormat_( AstAxis *, int * );
+int astTestAxisLabel_( AstAxis *, int * );
+int astTestAxisSymbol_( AstAxis *, int * );
+int astTestAxisUnit_( AstAxis *, int * );
+int astTestAxisNormUnit_( AstAxis *, int * );
+int astTestAxisInternalUnit_( AstAxis *, int * );
+void astAxisOverlay_( AstAxis *, AstAxis *, int * );
+void astClearAxisDigits_( AstAxis *, int * );
+void astClearAxisDirection_( AstAxis *, int * );
+void astClearAxisFormat_( AstAxis *, int * );
+void astClearAxisLabel_( AstAxis *, int * );
+void astClearAxisSymbol_( AstAxis *, int * );
+void astClearAxisUnit_( AstAxis *, int * );
+void astSetAxisDigits_( AstAxis *, int, int * );
+void astSetAxisDirection_( AstAxis *, int, int * );
+void astSetAxisFormat_( AstAxis *, const char *, int * );
+void astSetAxisLabel_( AstAxis *, const char *, int * );
+void astSetAxisSymbol_( AstAxis *, const char *, int * );
+void astSetAxisUnit_( AstAxis *, const char *, int * );
+
+double astGetAxisTop_( AstAxis *, int * );
+int astTestAxisTop_( AstAxis *, int * );
+void astClearAxisTop_( AstAxis *, int * );
+void astSetAxisTop_( AstAxis *, double, int * );
+
+double astGetAxisBottom_( AstAxis *, int * );
+int astTestAxisBottom_( AstAxis *, int * );
+void astClearAxisBottom_( AstAxis *, int * );
+void astSetAxisBottom_( AstAxis *, double, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckAxis(this) astINVOKE_CHECK(Axis,this,0)
+#define astVerifyAxis(this) astINVOKE_CHECK(Axis,this,1)
+
+/* Test class membership. */
+#define astIsAAxis(this) astINVOKE_ISA(Axis,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astAxis astINVOKE(F,astAxis_)
+#else
+#define astAxis astINVOKE(F,astAxisId_)
+#endif
+
+#if defined(astCLASS)            /* Protected. */
+
+/* Initialiser. */
+#define astInitAxis(mem,size,init,vtab,name) \
+astINVOKE(O,astInitAxis_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitAxisVtab(vtab,name) astINVOKE(V,astInitAxisVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadAxis(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadAxis_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckAxis to validate Axis pointers before
+   use. This provides a contextual error report if a pointer to the
+   wrong sort of object is supplied. */
+#define astAxisFormat(this,value) \
+astINVOKE(V,astAxisFormat_(astCheckAxis(this),value,STATUS_PTR))
+#define astAxisNorm(this,value) \
+astINVOKE(V,astAxisNorm_(astCheckAxis(this),value,STATUS_PTR))
+#define astAxisNormValues(this,oper,nval,values) \
+astINVOKE(V,astAxisNormValues_(astCheckAxis(this),oper,nval,values,STATUS_PTR))
+#define astAxisUnformat(this,string,value) \
+astINVOKE(V,astAxisUnformat_(astCheckAxis(this),string,value,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#define astAxisAbbrev(this,fmt,str1,str2) \
+astINVOKE(V,astAxisAbbrev_(astCheckAxis(this),fmt,str1,str2,STATUS_PTR))
+#define astAxisCentre(this,value,gap) \
+astINVOKE(V,astAxisCentre_(astCheckAxis(this),value,gap,STATUS_PTR))
+#define astAxisGap(this,gap,ntick) \
+astINVOKE(V,astAxisGap_(astCheckAxis(this),gap,ntick,STATUS_PTR))
+#define astAxisFields(this,fmt,str,maxfld,fields,nc,val) \
+astINVOKE(V,astAxisFields_(astCheckAxis(this),fmt,str,maxfld,fields,nc,val,STATUS_PTR))
+#define astAxisIn(this,lo,hi,val,closed) \
+astINVOKE(V,astAxisIn_(astCheckAxis(this),lo,hi,val,closed,STATUS_PTR))
+#define astAxisDistance(this,v1,v2) \
+astINVOKE(V,astAxisDistance_(astCheckAxis(this),v1,v2,STATUS_PTR))
+#define astAxisOffset(this,v1,dist) \
+astINVOKE(V,astAxisOffset_(astCheckAxis(this),v1,dist,STATUS_PTR))
+#define astAxisOverlay(template,result) \
+astINVOKE(V,astAxisOverlay_(astCheckAxis(template),astCheckAxis(result),STATUS_PTR))
+#define astClearAxisDigits(this) \
+astINVOKE(V,astClearAxisDigits_(astCheckAxis(this),STATUS_PTR))
+#define astClearAxisDirection(this) \
+astINVOKE(V,astClearAxisDirection_(astCheckAxis(this),STATUS_PTR))
+#define astClearAxisFormat(this) \
+astINVOKE(V,astClearAxisFormat_(astCheckAxis(this),STATUS_PTR))
+#define astClearAxisLabel(this) \
+astINVOKE(V,astClearAxisLabel_(astCheckAxis(this),STATUS_PTR))
+#define astClearAxisSymbol(this) \
+astINVOKE(V,astClearAxisSymbol_(astCheckAxis(this),STATUS_PTR))
+#define astClearAxisUnit(this) \
+astINVOKE(V,astClearAxisUnit_(astCheckAxis(this),STATUS_PTR))
+#define astGetAxisDigits(this) \
+astINVOKE(V,astGetAxisDigits_(astCheckAxis(this),STATUS_PTR))
+#define astGetAxisDirection(this) \
+astINVOKE(V,astGetAxisDirection_(astCheckAxis(this),STATUS_PTR))
+#define astGetAxisFormat(this) \
+astINVOKE(V,astGetAxisFormat_(astCheckAxis(this),STATUS_PTR))
+#define astGetAxisLabel(this) \
+astINVOKE(V,astGetAxisLabel_(astCheckAxis(this),STATUS_PTR))
+#define astGetAxisSymbol(this) \
+astINVOKE(V,astGetAxisSymbol_(astCheckAxis(this),STATUS_PTR))
+#define astGetAxisUnit(this) \
+astINVOKE(V,astGetAxisUnit_(astCheckAxis(this),STATUS_PTR))
+#define astGetAxisNormUnit(this) \
+astINVOKE(V,astGetAxisInternalUnit_(astCheckAxis(this),STATUS_PTR))
+#define astGetAxisInternalUnit(this) \
+astINVOKE(V,astGetAxisInternalUnit_(astCheckAxis(this),STATUS_PTR))
+#define astSetAxisDigits(this,digits) \
+astINVOKE(V,astSetAxisDigits_(astCheckAxis(this),digits,STATUS_PTR))
+#define astSetAxisDirection(this,direction) \
+astINVOKE(V,astSetAxisDirection_(astCheckAxis(this),direction,STATUS_PTR))
+#define astSetAxisFormat(this,format) \
+astINVOKE(V,astSetAxisFormat_(astCheckAxis(this),format,STATUS_PTR))
+#define astSetAxisLabel(this,label) \
+astINVOKE(V,astSetAxisLabel_(astCheckAxis(this),label,STATUS_PTR))
+#define astSetAxisSymbol(this,symbol) \
+astINVOKE(V,astSetAxisSymbol_(astCheckAxis(this),symbol,STATUS_PTR))
+#define astSetAxisUnit(this,unit) \
+astINVOKE(V,astSetAxisUnit_(astCheckAxis(this),unit,STATUS_PTR))
+#define astTestAxisDigits(this) \
+astINVOKE(V,astTestAxisDigits_(astCheckAxis(this),STATUS_PTR))
+#define astTestAxisDirection(this) \
+astINVOKE(V,astTestAxisDirection_(astCheckAxis(this),STATUS_PTR))
+#define astTestAxisFormat(this) \
+astINVOKE(V,astTestAxisFormat_(astCheckAxis(this),STATUS_PTR))
+#define astTestAxisLabel(this) \
+astINVOKE(V,astTestAxisLabel_(astCheckAxis(this),STATUS_PTR))
+#define astTestAxisSymbol(this) \
+astINVOKE(V,astTestAxisSymbol_(astCheckAxis(this),STATUS_PTR))
+#define astTestAxisUnit(this) \
+astINVOKE(V,astTestAxisUnit_(astCheckAxis(this),STATUS_PTR))
+#define astTestAxisNormUnit(this) \
+astINVOKE(V,astTestAxisNormUnit_(astCheckAxis(this),STATUS_PTR))
+#define astTestAxisInternalUnit(this) \
+astINVOKE(V,astTestAxisInternalUnit_(astCheckAxis(this),STATUS_PTR))
+
+#define astClearAxisTop(this) \
+astINVOKE(V,astClearAxisTop_(astCheckAxis(this),STATUS_PTR))
+#define astGetAxisTop(this) \
+astINVOKE(V,astGetAxisTop_(astCheckAxis(this),STATUS_PTR))
+#define astSetAxisTop(this,top) \
+astINVOKE(V,astSetAxisTop_(astCheckAxis(this),top,STATUS_PTR))
+#define astTestAxisTop(this) \
+astINVOKE(V,astTestAxisTop_(astCheckAxis(this),STATUS_PTR))
+
+#define astClearAxisBottom(this) \
+astINVOKE(V,astClearAxisBottom_(astCheckAxis(this),STATUS_PTR))
+#define astGetAxisBottom(this) \
+astINVOKE(V,astGetAxisBottom_(astCheckAxis(this),STATUS_PTR))
+#define astSetAxisBottom(this,bottom) \
+astINVOKE(V,astSetAxisBottom_(astCheckAxis(this),bottom,STATUS_PTR))
+#define astTestAxisBottom(this) \
+astINVOKE(V,astTestAxisBottom_(astCheckAxis(this),STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/box.c b/ast/box.c
new file mode 100644
index 0000000..99aa780
--- /dev/null
+++ b/ast/box.c
@@ -0,0 +1,5062 @@
+/*
+*class++
+*  Name:
+*     Box
+
+*  Purpose:
+*     A box region with sides parallel to the axes of a Frame.
+
+*  Constructor Function:
+c     astBox
+f     AST_BOX
+
+*  Description:
+*     The Box class implements a Region which represents a box with sides
+*     parallel to the axes of a Frame (i.e. an area which encloses a given
+*     range of values on each axis). A Box is similar to an Interval, the
+*     only real difference being that the Interval class allows some axis
+*     limits to be unspecified. Note, a Box will only look like a box if
+*     the Frame geometry is approximately flat. For instance, a Box centred
+*     close to a pole in a SkyFrame will look more like a fan than a box
+*     (the Polygon class can be used to create a box-like region close to a
+*     pole).
+
+*  Inheritance:
+*     The Box class inherits from the Region class.
+
+*  Attributes:
+*     The Box class does not define any new attributes beyond
+*     those which are applicable to all Regions.
+
+*  Functions:
+c     The Box class does not define any new functions beyond those
+f     The Box class does not define any new routines beyond those
+*     which are applicable to all Regions.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2008-2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-MAR-2004 (DSB):
+*        Original version.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     5-JUN-2007 (DSB):
+*        Improve astSimplify algorithm.
+*     6-JUN-2007 (DSB):
+*        Change the iterating algorithm in MakeGrid so that it uses
+*        pixel index rather than axis value. This is more robust against
+*        rounding errors.
+*     9-OCT-2007 (DSB):
+*        - Fix bug in RegBaseMesh that could cause incorrect meshes for 2D
+*        Boxes.
+*        - In RegBaseMesh, use flat geometry if all axes come from simple
+*        frames or from 1-dimensional specialist frames.
+*     26-MAY-2008 (DSB):
+*        Fix bug in RegBaseMesh that caused an error to be reported if
+*        the Box occupies a single point.
+*     20-JAN-2009 (DSB):
+*        Over-ride astRegBasePick.
+*     26-JAN-2009 (DSB):
+*        Over-ride astMapMerge.
+*     12-JUL-2009 (DSB):
+*        Modify Simplify so that if a Box can be split into two
+*        simpler components and then joined together into a Prism, it
+*        does so. This is because being able to express a Region in
+*        its current Frame is more important than having the simplest
+*        possible structure.
+*     28-FEB-2011 (DSB):
+*        Do not assume the first axis value is good in function BestBox.
+*     22-MAR-2011 (DSB):
+*        Improve uniformity of points within grid produced by astRegBaseGrid.
+*     16-JUL-2013 (DSB):
+*        Use a more robust algorithm for determining the order of the
+*        vertices whan a Box is simplified to a Polygon. The old method
+*        sometimes resulted in an unbounded "inside-out" polygon.
+*     4-NOV-2013 (DSB):
+*        Modify RegPins so that it can handle uncertainty regions that straddle
+*        a discontinuity. Previously, such uncertainty Regions could have a huge
+*        bounding box resulting in matching region being far too big.
+*     10-APR-2014 (DSB):
+*        More work (in function Cache() ) on handling uncertainty regions that straddle
+*        a discontinuity. This time ensure that the extent of the box on each axis takes
+*        account of the possibly cyclic nature of the base Frame.
+*     25-4-2016 (DSB):
+*        Remove the unused box shrinking facility (a hang over from the
+*        days when the RegBaseGrid function operated by creating multiple
+*        meshes on the surface of the box, shrinking the box each time).
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Box
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "cmpframe.h"            /* Compound Frames */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "region.h"              /* Coordinate regions (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "box.h"                 /* Interface definition for this class */
+#include "polygon.h"             /* Interface definition for this class */
+#include "mapping.h"             /* Position mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "permmap.h"             /* Axis permutation Mappings */
+#include "interval.h"            /* Axis interval regions */
+#include "nullregion.h"          /* Empty regions */
+#include "pointlist.h"           /* List of points in a Frame */
+#include "prism.h"               /* Extruded regions */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstMapping *(* parent_simplify)( AstMapping *, int * );
+static void (* parent_setnegated)( AstRegion *, int, int * );
+static void (* parent_setclosed)( AstRegion *, int, int * );
+static void (* parent_clearnegated)( AstRegion *, int * );
+static void (* parent_clearclosed)( AstRegion *, int * );
+static void (* parent_setunc)( AstRegion *, AstRegion *, int * );
+static void (* parent_setregfs)( AstRegion *, AstFrame *, int * );
+static void (* parent_resetcache)( AstRegion *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Box)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Box,Class_Init)
+#define class_vtab astGLOBAL(Box,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstBoxVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstBox *astBoxId_( void *, int, const double[], const double[], void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstBox *BestBox( AstFrame *, AstPointSet *, AstRegion *, int * );
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *RegBaseGrid( AstRegion *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstRegion *MergeBox( AstBox *, AstRegion *, int, int * );
+static AstRegion *RegBasePick( AstRegion *this, int, const int *, int * );
+static double *GeoCorner( AstFrame *, int, double *, double *, double *, int * );
+static double *GeoLengths( AstFrame *, int, double *, double *, double *, int * );
+static double *RegCentre( AstRegion *this, double *, double **, int, int, int * );
+static int GetObjSize( AstObject *, int * );
+static int MakeGrid( int, double **, int, double *, double *, int *, int, int, double, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int RegPins( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+static int RegTrace( AstRegion *, int, double *, double **, int * );
+static void BoxPoints( AstBox *, double *, double *, int *);
+static void Cache( AstBox *, int, int * );
+static void ClearClosed( AstRegion *, int * );
+static void ClearNegated( AstRegion *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void RegBaseBox( AstRegion *this, double *, double *, int * );
+static void ResetCache( AstRegion *this, int * );
+static void SetClosed( AstRegion *, int, int * );
+static void SetNegated( AstRegion *, int, int * );
+static void SetRegFS( AstRegion *, AstFrame *, int * );
+static void SetUnc( AstRegion *, AstRegion *, int * );
+
+/* Member functions. */
+/* ================= */
+
+void BoxPoints( AstBox *this, double *centre, double *corner, int *status) {
+/*
+*+
+*  Name:
+*     astBoxPoints
+
+*  Purpose:
+*     Return the defining points of a Box.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "box.h"
+*     astBoxPoints( AstBox *this, double *centre, double *corner )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns the axis values at the points defining the
+*     supplied Box.
+
+*  Parameters:
+*     this
+*        Pointer to the Box.
+*     centre
+*        A pointer to an array in which to return the centre position of
+*        the Box, in the base Frame of the encapsulated FrameSet.
+*     corner
+*        A pointer to an array in which to return the position of a corner
+*        of the Box, in the base Frame of the encapsulated FrameSet.
+
+*  Notes:
+*     - It is assumed that the length of the supplied arrays is at least
+*     equal to the number of axes in the base frame of the encapsulated
+*     FrameSet.
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *pset;
+   double **ptr;
+   int nc;
+   int i;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the PointSet holding the points defining the Box. */
+   pset = ((AstRegion *) this)->points;
+
+/* Get a pointer to the PointSet's data arrays. */
+   ptr = astGetPoints( pset );
+
+/* See how many axes each point in the PointSet has. */
+   nc = astGetNcoord( pset );
+
+/* Copy the centre and corner positions form the PointSet into the
+   supplied arrays. */
+   for( i = 0; i < nc; i++ ) {
+      centre[ i ] = ptr[ i ] [ 0 ];
+      corner[ i ] = ptr[ i ] [ 1 ];
+   }
+
+}
+
+static void ClearClosed( AstRegion *this, int *status ){
+/*
+*  Name:
+*     ClearClosed
+
+*  Purpose:
+*     Clear the Closed attribute of a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     void ClearClosed( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the protected astClearClosed
+*     method inherited from the Region class).
+
+*  Description:
+*     This function clears the Closed attribute of the supplied Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int old;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the original attribute value */
+   old = astGetClosed( this );
+
+/* Invoke the clear method inherited from the parent Region class */
+   (*parent_clearclosed)( this, status );
+
+/* If the new value is not the same as the old value, inidcatethat we
+   need to re-calculate the cached information in the Box. */
+   if( astGetClosed( this ) != old ) astResetCache( this );
+}
+
+static void ClearNegated( AstRegion *this, int *status ){
+/*
+*  Name:
+*     ClearNegated
+
+*  Purpose:
+*     Clear the Negated attribute of a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     void ClearNegated( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the protected astClearNegated
+*     method inherited from the Region class).
+
+*  Description:
+*     This function clears the Negated attribute of the supplied Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int old;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the original attribute value */
+   old = astGetNegated( this );
+
+/* Invoke the clear method inherited from the parent Region class */
+   (*parent_clearnegated)( this, status );
+
+/* If the new value is not the same as the old value, inidcatethat we
+   need to re-calculate the cached information in the Box. */
+   if( astGetNegated( this ) != old ) astResetCache( this );
+}
+
+static AstBox *BestBox( AstFrame *frm, AstPointSet *mesh, AstRegion *unc, int *status ){
+/*
+*  Name:
+*     BestBox
+
+*  Purpose:
+*     Find the best fitting Box through a given mesh of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstBox *BestBox( AstFrame *frm, AstPointSet *mesh, AstRegion *unc, int *status )
+
+*  Class Membership:
+*     Box member function
+
+*  Description:
+*     This function finds the best fitting Box through a given mesh of points.
+
+*  Parameters:
+*     frm
+*        Defines the geometry of the axes.
+*     mesh
+*        Pointer to a PointSet holding the mesh of points. They are
+*        assumed to be in the Frame represented by "unc".
+*     unc
+*        A Region representing the uncertainty associated with each point
+*        on the mesh.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the best fitting Region. It will inherit the positional
+*     uncertainty and Frame represented by "unc". NULL is returned if all
+*     the supplied positions are bad.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   AstBox *result;
+   double **ptr;
+   double *axval;
+   double *blbnd;
+   double *bubnd;
+   double *lbnd;
+   double *p;
+   double *ubnd;
+   double eps;
+   double lb;
+   double lim2;
+   double lim;
+   double liml;
+   double limu;
+   double mxl;
+   double mxu;
+   double org;
+   double sxl2;
+   double sxl;
+   double sxu2;
+   double sxu;
+   double ub;
+   double p0;
+   double d;
+   double dinc;
+   int ic;
+   int ip;
+   int nc;
+   int np;
+   int nxl;
+   int nxu;
+   int ok;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get no. of points in the mesh, and the number of axis values per point. */
+   np = astGetNpoint( mesh );
+   nc = astGetNcoord( mesh );
+
+/* Get pointers to the axis values. */
+   ptr = astGetPoints( mesh );
+
+/* Allocate work space. */
+   lbnd = astMalloc( sizeof( double )*(size_t) nc );
+   ubnd = astMalloc( sizeof( double )*(size_t) nc );
+
+   blbnd = astMalloc( sizeof( double )*(size_t) nc );
+   bubnd = astMalloc( sizeof( double )*(size_t) nc );
+
+   axval = astMalloc( sizeof( double )*(size_t) np );
+
+/* Check pointers can be used safely */
+   if( axval ) {
+
+/* Get the bounding box of the uncertainty region. */
+      astGetRegionBounds( unc, lbnd, ubnd );
+
+/* We fit the box one axis at a time. */
+      ok = 1;
+      for( ic = 0; ic < nc; ic++ ) {
+
+/* Find the first good value on this axis. */
+         for( ip = 0; ip < np; ip++ ) {
+            org = ptr[ ic ][ ip ];
+            axval[ ip ] = org;
+            if( org != AST__BAD ) break;
+         }
+
+/* Abort if all the axis values werebad. */
+         if( ip >= np ) {
+            ok = 0;
+            break;
+         }
+
+/* Find the upper and lower limits of the supplied mesh on this axis. */
+         lb = 0.0;
+         ub = 0.0;
+         ip++;
+         p = ptr[ ic ] + ip;
+         p0 = org;
+         d = 0.0;
+         for( ; ip < np; ip++, p++ ) {
+            dinc = astAxDistance( frm, ic + 1, p0, *p );
+            if( dinc != AST__BAD ) {
+               d += dinc;
+               if( d < lb ) lb = d;
+               if( d > ub ) ub = d;
+            }
+            axval[ ip ] = org + d;
+            p0 = *p;
+         }
+
+/* Now convert these relative offsets to actual axis values. */
+         lb += org;
+         ub += org;
+
+/* Now scan the list of axis values again, looking for values which are
+   "close to" either lower or upper bound. These will be the points which
+   are on the faces of the box which are orthogonal to the current axis. Here
+   "close to" means within 20 times the uncertainty associated with this
+   axis, or one tenth of the box size (which ever is smaller). We find the
+   mean and standard deviation of such "close" values, and then do a
+   single sigma-clipping iteration (at 3-sigma) to get rid of any values
+   which are on one of the other faces of the box. */
+
+         lim = 20*( ubnd[ ic ] - lbnd[ ic ] );
+         lim2 = 0.1*( ub - lb );
+         if( lim2 < lim ) lim = lim2;
+
+         sxl = 0.0;
+         sxl2 = 0.0;
+         nxl = 0;
+         sxu = 0.0;
+         sxu2 = 0.0;
+         nxu = 0;
+
+         p = axval;
+         for( ip = 0; ip < np; ip++, p++ ) {
+            if( *p != AST__BAD ) {
+               if( fabs( *p - lb ) <= lim ) {
+                  sxl += *p;
+                  sxl2 += (*p)*(*p);
+                  nxl++;
+               } else if( fabs( *p - ub ) <= lim ) {
+                  sxu += *p;
+                  sxu2 += (*p)*(*p);
+                  nxu++;
+               }
+            }
+         }
+
+         if( nxl > 0 ) {
+            mxl = sxl/nxl;
+            liml = sxl2/nxl - mxl*mxl;
+            eps = 100*mxl*DBL_EPSILON;
+            if( liml < eps*eps ) {
+               liml = eps;
+            } else {
+               liml = 3.0*sqrt( liml );
+            }
+         } else {
+            mxl = lb;
+            liml = 0.0;
+         }
+
+         if( nxu > 0 ) {
+            mxu = sxu/nxu;
+            limu = sxu2/nxu - mxu*mxu;
+            eps = 100*mxu*DBL_EPSILON;
+            if( limu < eps*eps ) {
+               limu = eps;
+            } else {
+               limu = 3.0*sqrt( limu );
+            }
+
+         } else {
+            mxu = ub;
+            limu = 0.0;
+         }
+
+         sxl = 0.0;
+         nxl = 0;
+         sxu = 0.0;
+         nxu = 0;
+
+         p = axval;
+         for( ip = 0; ip < np; ip++, p++ ) {
+            if( *p != AST__BAD ) {
+               if( fabs( *p - mxl ) <= liml ) {
+                  sxl += *p;
+                  nxl++;
+               } else if( fabs( *p - mxu ) <= limu ) {
+                  sxu += *p;
+                  nxu++;
+               }
+            }
+         }
+
+         if( nxl > 0 ) {
+            mxl = sxl/nxl;
+         } else {
+            mxl = lb;
+         }
+
+         if( nxu > 0 ) {
+            mxu = sxu/nxu;
+         } else {
+            mxu = ub;
+         }
+
+/* The resulting mean axis values are the bounds of the required box on
+   the current axis.*/
+         blbnd[ ic ] = mxl;
+         bubnd[ ic ] = mxu;
+      }
+
+/* If possible, create the returned Box. */
+      if( ok ) result = astBox( unc, 1, blbnd, bubnd, unc, " ", status );
+   }
+
+/* Free resources */
+   lbnd = astFree( lbnd );
+   ubnd = astFree( ubnd );
+   blbnd = astFree( blbnd );
+   bubnd = astFree( bubnd );
+   axval = astFree( axval );
+
+/* Return NULL if anything went wrong. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result.*/
+   return result;
+}
+
+static void Cache( AstBox *this, int lohi, int *status ){
+/*
+*  Name:
+*     Cache
+
+*  Purpose:
+*     Calculate intermediate values and cache them in the Box structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     void Cache( AstRegion *this, int lohi, int *status )
+
+*  Class Membership:
+*     Box member function
+
+*  Description:
+*     This function uses the PointSet stored in the parent Region to calculate
+*     some intermediate values which are useful in other methods. These
+*     values are stored within the Box structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Box.
+*     lohi
+*        Are the lo and hi arrays to be used?
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstPointSet *pset;
+   AstRegion *unc;
+   double **ptr;
+   double *centre;
+   double *extent;
+   double *hi;
+   double *lbnd_unc;
+   double *geolen;
+   double *lo;
+   double *ubnd_unc;
+   double wid;
+   int i;
+   int nc;
+
+/* Check the global error status. Also return if the currently cached
+   information is usable. */
+   if ( !astOK || !this->stale ) return;
+
+/* Get the number of base Frame axes. */
+   nc = astGetNin( ((AstRegion *)this)->frameset );
+
+/* Allocate memory to store the half-width of the box on each axis. */
+   extent = (double *) astMalloc( sizeof( double )*(size_t) nc );
+
+/* Allocate memory to store the centre of the box on each axis. */
+   centre = (double *) astMalloc( sizeof( double )*(size_t) nc );
+
+/* Allocate memory to store the high and low bounds. */
+   hi = (double *) astMalloc( sizeof( double )*(size_t) nc );
+   lo = (double *) astMalloc( sizeof( double )*(size_t) nc );
+
+/* Memory to store the uncertainty bounding box */
+   lbnd_unc = astMalloc( sizeof( double)*(size_t) nc );
+   ubnd_unc = astMalloc( sizeof( double)*(size_t) nc );
+
+/* Memory to store the geodesic half-dimensions of the box. */
+   geolen = astMalloc( sizeof( double)*(size_t) nc );
+
+/* Get pointers to the coordinate data in the parent Region structure. */
+   pset = ((AstRegion *) this)->points;
+   ptr = astGetPoints( pset );
+
+/* Check pointers can be used safely. */
+   if( ptr ) {
+
+/* Store the centre and corner axis values. */
+      for( i = 0; i < nc; i++ ) {
+         centre[ i ] = ptr[ i ][ 0 ];
+         hi[ i ] = ptr[ i ][ 1 ];
+      }
+
+/* Calculate the geodesic half-dimensions of the box. */
+      frm = astGetFrame( ((AstRegion *) this)->frameset, AST__BASE );
+      GeoLengths( frm, nc, centre, hi, geolen, status );
+
+/* Calculate the half-width and store in the above array. Also store the
+   lower and upper bounds. */
+      for( i = 0; i < nc; i++ ) {
+         extent[ i ] = fabs( astAxDistance( frm, i + 1, ptr[ i ][ 1 ],
+                                            centre[ i ] ) );
+         lo[ i ] = centre[ i ] - extent[ i ];
+         hi[ i ] = centre[ i ] + extent[ i ];
+      }
+
+      frm = astAnnul( frm );
+
+/* Store the pointers to these arrays in the Box structure, and indicate
+   the information is usable. */
+      if( astOK ) {
+         astFree( this->extent );
+         astFree( this->centre );
+         astFree( this->lo );
+         astFree( this->hi );
+         astFree( this->geolen );
+         this->extent = extent;
+         this->centre = centre;
+         this->lo = lo;
+         this->hi = hi;
+         this->geolen = geolen;
+         this->stale = 0;
+         extent = NULL;
+         centre = NULL;
+         lo = NULL;
+         hi = NULL;
+         geolen = NULL;
+      }
+
+/* If lo and hi values are to be used, ensure they are expanded to at
+   least the width of an uncertainty box. */
+      if( lohi ) {
+
+/* If we are dealing with an unnegated closed Box or a negated open
+   Box, ensure that the box does not have zero width on any axis. We do
+   this by ensuring that the extent on all axes is at least half the
+   width  of the bounding box of the uncertainty Region. */
+         if(  astGetNegated( this ) != astGetClosed( this ) ) {
+
+/* Get the bounding box of the uncertainty Region in the base Frame of
+   the supplied Box. */
+            unc = astGetUncFrm( this, AST__BASE );
+            astGetRegionBounds( unc, lbnd_unc, ubnd_unc );
+
+/* Ensure the extents are at least half the width of the uncertainty
+   bounding box. */
+            for ( i = 0; i < nc; i++ ) {
+               wid = 0.5*( ubnd_unc[ i ] - lbnd_unc[ i ] );
+               if( this->extent[ i ] < wid ) {
+                  this->extent[ i ] = wid;
+                  this->lo[ i ] = this->centre[ i ] - wid;
+                  this->hi[ i ] = this->centre[ i ] + wid;
+               }
+            }
+
+/* Free resources. */
+            unc = astAnnul( unc );
+         }
+      }
+   }
+
+/* Annul the memory allocated above if an error occurred. */
+   if( !astOK ) {
+      extent = astFree( extent );
+      centre = astFree( centre );
+      lo = astFree( lo );
+      hi = astFree( hi );
+   }
+
+/* Free other resources */
+   lbnd_unc = astFree( lbnd_unc );
+   ubnd_unc = astFree( ubnd_unc );
+
+}
+
+static double *GeoCorner( AstFrame *frm, int nc, double *centre,
+                          double *geolen, double *corner, int *status ){
+/*
+*  Name:
+*     GeoCorner
+
+*  Purpose:
+*     Find the corner position implied by the supplied centre position
+*     and geodesic box dimensions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     double *GeoCorner( AstFrame *frm, int nc, double *centre,
+*                        double *geolen, double *corner, int *status )
+
+*  Class Membership:
+*     Box member function
+
+*  Description:
+*     This function returns the corner position that is implied by the
+*     supplied centre position and geodesic box dimensions. The returned
+*     corner position is found by offsetting away from the supplied
+*     centre position along each axis in turn, by the geodesic distance
+*     specified in "geolen".
+
+*  Parameters:
+*     frm
+*        Defines the geometry of the axes.
+*     nc
+*        The number of Frame axes.
+*     centre
+*        Pointer to an array holding the box centre axis values.
+*     geolen
+*        Pointer to an array holding the geodesic distance corresponding
+*        to each half axis of the box.
+*     corner
+*        Pointer to an array in which to store the axis values at the
+*        returned corner position. If this is NULL a new array is
+*        allocated and a pointer to it returned as the function value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the array holding the corner axis values. If a non-NULL
+*     value is supplied for parameter "corner", then the same value will
+*     be returned as the function value. Otherwise, the returned value
+*     will be a pointer to a newly allocated array that should be freed
+*     using astFree when no longer needed.
+
+*/
+
+/* Local Variables: */
+   double *p1;
+   double *p2;
+   double *p3;
+   double *pt;
+   double *result;
+   double *work1;
+   double *work2;
+   double off;
+   double off0;
+   int i;
+
+/* Initialise */
+   result = corner;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Ensure we have a results array. */
+   if( ! result ) result = astMalloc( sizeof( double )*nc );
+
+/* Also allocate two work arrays to hold a single position. */
+   work1 = astMalloc( sizeof( double )*nc );
+   work2 = astMalloc( sizeof( double )*nc );
+
+/* Check the pointers can be used safely. */
+   if( astOK ) {
+
+/* Select which array to use as the initial results array so that the
+   final results end up in the returned array. */
+      if( ( nc % 2 ) == 0 ) {
+         p1 = result;
+         p2 = work1;
+         p3 = work2;
+      } else {
+         p1 = work2;
+         p2 = work1;
+         p3 = result;
+      }
+
+/* Initialise the current corner position to be at the centre of the box. */
+      for( i = 0; i < nc; i++ ) p1[ i ] = centre[ i ];
+
+/* Loop round offsetting along each side of the box. */
+      for( i = 0; i < nc; i++ ) {
+
+/* In the p2 array put the axis values at a point which is offset
+   slightly along the current axis away from the current "corner"
+   position (p1). */
+         memcpy( p2, p1, sizeof( double )*nc );
+
+         if( geolen[ i ] != 0.0 ) {
+            off = 0.0001*fabs( geolen[ i ] );
+         } else {
+            off = 1.0E-6;
+         }
+
+         off0 = fabs( 1.0E-10*centre[ i ] );
+         if( off < off0 ) off = off0;
+         p2[ i ] += off;
+
+/* Offset away from the current corner position (p1) towards the position
+   found above (p2), moving by the geodesic distance supplied for this axis.
+   Put the resulting axis values in p3. */
+         astOffset( frm, p1, p2, geolen[ i ], p3 );
+
+/* Swap the p3 and p1 arrays so that the offset position found above (p3)
+   becomes the starting position (p1) for the next offset. */
+         pt = p1;
+         p1 = p3;
+         p3 = pt;
+      }
+   }
+
+/* Free resources */
+   work1 = astFree( work1 );
+   work2 = astFree( work2 );
+
+/* Return the result */
+   return result;
+}
+
+static double *GeoLengths( AstFrame *frm, int nc, double *centre,
+                           double *corner, double *geolen, int *status ){
+/*
+*  Name:
+*     GeoLengths
+
+*  Purpose:
+*     Find the geodesic dimensions of a box.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*      double *GeoLengths( AstFrame *frm, int nc, double *centre,
+*                          double *corner, double *geolen, int *status )
+
+*  Class Membership:
+*     Box member function
+
+*  Description:
+*     This function returns half the geodesic distance along each edge of
+*     the supplied box.
+
+*  Parameters:
+*     frm
+*        Defines the geometry of the axes.
+*     nc
+*        The number of Frame axes.
+*     centre
+*        Pointer to an array holding rhe box centre axis values.
+*     corner
+*        Pointer to an array holding the axis values at the corner
+*        position.
+*     geolen
+*        Pointer to an array in which to return the geodesic distances
+*        corresponding to each half axis of the box. If this is NULL a
+*        new array is allocated and a pointer to it returned as the
+*        function value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the array holding the geodesic half-dimensions of the box.
+*     If a non-NULL value is supplied for parameter "corner", then the same
+*     value will be returned as the function value. Otherwise, the
+*     returned value will be a pointer to a newly allocated array that
+*     should be freed using astFree when no longer needed.
+
+*/
+
+/* Local Variables: */
+   double *result;
+   double *p1;
+   double *p2;
+   int i;
+
+/* Initialise */
+   result = geolen;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Ensure we have a results array. */
+   if( ! result ) result = astMalloc( sizeof( double )*nc );
+
+/* Also allocate two work arrays to hold a single position. */
+   p1 = astMalloc( sizeof( double )*nc );
+   p2 = astMalloc( sizeof( double )*nc );
+
+/* Check the pointers can be used safely. */
+   if( astOK ) {
+
+/* Initialise the coords as the start and end of the line. */
+      memcpy( p1, centre, sizeof( double )*nc );
+      memcpy( p2, centre, sizeof( double )*nc );
+
+/* Loop round finding the geodesic half-length of each side of the box. */
+      for( i = 0; i < nc; i++ ) {
+
+/* The end of the line is the same as the start of the line, except that
+   it has the corner value for the current axis. */
+         p2[ i ] = corner[ i ];
+
+/* Find and return the geodesic distance along the line (i.e. from p1 to
+   p2). */
+         result[ i ] = astDistance( frm, p1, p2 );
+
+/* The start of the next line wil lbe at the end of the current line. */
+         p1[ i ] = corner[ i ];
+      }
+   }
+
+/* Free resources */
+   p1 = astFree( p1 );
+   p2 = astFree( p2 );
+
+/* Return the result */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied Box,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the Box.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstBox *this;         /* Pointer to Box structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the Box structure. */
+   this = (AstBox *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astTSizeOf( this->extent );
+   result += astTSizeOf( this->centre );
+   result += astTSizeOf( this->lo );
+   result += astTSizeOf( this->hi );
+   result += astTSizeOf( this->geolen );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+void astInitBoxVtab_(  AstBoxVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitBoxVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Box.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "box.h"
+*     void astInitBoxVtab( AstBoxVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Box vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Box class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstRegionVtab *region;        /* Pointer to Region component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitRegionVtab( (AstRegionVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsABox) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstRegionVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->BoxPoints = BoxPoints;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   region = (AstRegionVtab *) vtab;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_simplify = mapping->Simplify;
+   mapping->Simplify = Simplify;
+
+   parent_setnegated = region->SetNegated;
+   region->SetNegated = SetNegated;
+
+   parent_setunc = region->SetUnc;
+   region->SetUnc = SetUnc;
+
+   parent_setclosed = region->SetClosed;
+   region->SetClosed = SetClosed;
+
+   parent_clearnegated = region->ClearNegated;
+   region->ClearNegated = ClearNegated;
+
+   parent_clearclosed = region->ClearClosed;
+   region->ClearClosed = ClearClosed;
+
+   parent_setregfs = region->SetRegFS;
+   region->SetRegFS = SetRegFS;
+
+   parent_resetcache = region->ResetCache;
+   region->ResetCache = ResetCache;
+
+   mapping->MapMerge = MapMerge;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   region->RegBaseGrid = RegBaseGrid;
+   region->RegBaseMesh = RegBaseMesh;
+   region->RegBasePick = RegBasePick;
+   region->RegBaseBox = RegBaseBox;
+   region->RegPins = RegPins;
+   region->RegTrace = RegTrace;
+   region->RegCentre = RegCentre;
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "Box", "Axis intervals" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MakeGrid( int naxes, double **ptr, int ip, double *lbnd,
+                     double *ubnd, int *np_axes, int np_axis, int iaxis,
+                     double axval, int *status ){
+/*
+*  Name:
+*     MakeGrid
+
+*  Purpose:
+*     Create a grid covering the entire volume of a specified box.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     int MakeGrid( int naxes, double **ptr, int ip, double *lbnd,
+*                   double *ubnd, int *np_axes, int np_axis, int iaxis,
+*                   double axval, int *status )
+
+*  Class Membership:
+*     Box member function
+
+*  Description:
+*     This function creates an evenly sampled grid covering a given
+*     volume of n-D space, putting the coordinates at the sample points
+*     into a supplied array and returning the number of samples added.
+*     Optionally, the volume can be assumed to have a constant value on
+*     a specified axis.
+
+*  Parameters:
+*     naxes
+*        The number of axes.
+*     ptr
+*        Pointer to an array with "naxes" elements. Each element is a
+*        pointer to an array in which to store the values for the axis.
+*     ip
+*        The index of the first point to be added to the "ptr" arrays.
+*     lbnd
+*        Pointer to an array containing the lower axis bounds of the
+*        volume to be sampled.
+*     ubnd
+*        Pointer to an array containing the upper axis bounds of the
+*        volume to be sampled.
+*     np_axes
+*        Pointer to an array with one element for every axis, giving the
+*        number of samples along each axis (except, optionally, the axis
+*        specified by "iaxis"). The first sample (sample 0) for each axis
+*        will be at the lower bound given in "lbnd" and the last sample
+*        (sample "np_axes[iax]-1") will be at the upper bound given in
+*        "ubnd". If NULL, then the single scalar avalue given by
+*        "np_axis" is used for all axes.
+*     np_axis
+*        The constant number of samples along every axis (except, optionally,
+*        the axis specified by "iaxis"). The first sample (sample 0) for each
+*        axis will be at the lower bound given in "lbnd" and the last sample
+*        (sample "np_axis-1") will be at the upper bound given in "ubnd".
+*        The supplied value is only used if "np_axes" is NULL.
+*     iaxis
+*        The index of an axis which has constant value in the volume, or
+*        -1 if all axes are to span the full volume given by lbnd/ubnd.
+*        The values in "lbnd" and "ubnd" are ignored for this axis and all
+*        sample positions will have the axis value given by "axval".
+*     axval
+*        The constant value for the axis with index "iaxis". Ignored if
+*        "iaxis" is -1.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of points added to the "ptr" arrays.
+
+*/
+
+/* Local Variables: */
+   double *step;         /* Pointer to array holding axis step sizes */
+   int *maxi;            /* Pointer to array of maximum index values */
+   int *pi;              /* Pointer to array holding current sample indices */
+   int i;                /* Axis index */
+   int ipp;              /* Index of next point */
+   int nsamp;            /* Number of samples along the axis */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Allocate memory to hold the max indices on each axis. */
+   maxi = astMalloc( sizeof( int )*(size_t) naxes );
+
+/* Allocate memory to hold the indices of the current position.*/
+   pi = astMalloc( sizeof( int )*(size_t) naxes );
+
+/* Allocate memory to hold the step size for each axis. */
+   step = astMalloc( sizeof( double )*(size_t) naxes );
+   if( astOK ) {
+
+/* For every axis, set up the step size, initialise the current position to
+   the lower bound, and store a modified upper limit which includes some
+   safety marging to allow for rounding errors. */
+      for( i = 0; i < naxes; i++ ) {
+         nsamp = np_axes ? np_axes[ i ] : np_axis;
+         step[ i ] = ( ubnd[ i ] - lbnd[ i ] )/( nsamp - 1 );
+         pi[ i ] = 0;
+         maxi[ i ] = nsamp - 1;
+      }
+
+      if( iaxis >= 0 ) {
+         maxi[ iaxis ] = 0;
+         step[ iaxis ] = 0.0;
+         pi[ iaxis ] = 0;
+      }
+
+/* Initialise the index of the next position to store. */
+      ipp = ip;
+
+/* Loop round adding points to the array until the whole volume has been
+   done. */
+      i = 0;
+      while( i < naxes ) {
+
+/* Add the current point to the supplied array,and increment the index of
+   the next point to add. */
+         for( i = 0; i < naxes; i++ ) {
+            if( i == iaxis ) {
+               ptr[ i ][ ipp ] = axval;
+            } else {
+               ptr[ i ][ ipp ] = lbnd[ i ] + pi[ i ]*step[ i ];
+            }
+         }
+         ipp++;
+
+/* We now move the current position on to the next sample */
+         i = 0;
+         while( i < naxes ) {
+            pi[ i ]++;
+            if( pi[ i ] > maxi[ i ] ) {
+               pi[ i ] = 0;
+               i++;
+            } else {
+               break;
+            }
+         }
+      }
+   } else {
+      ipp = ip;
+   }
+
+/* Free resources. */
+   maxi = astFree( maxi );
+   pi = astFree( pi );
+   step = astFree( step );
+
+/* Return the result. */
+   return astOK ? ( ipp - ip ): 0 ;
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a Box.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     Box method (over-rides the protected astMapMerge method
+*     inherited from the Region class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated Box in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated Box with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated Box which is to be merged with
+*        its neighbours. This should be a cloned copy of the Box
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        Box it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated Box resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstBox *oldbox;       /* Pointer to supplied Box */
+   AstMapping *map;      /* Pointer to adjacent Mapping */
+   AstMapping *new;      /* Simplified or merged Region */
+   int i1;               /* Index of first Mapping merged */
+   int i;                /* Loop counter */
+   int result;           /* Result value to return */
+
+/* Initialise. */
+   result = -1;
+   i1 = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Box. */
+   oldbox = (AstBox *) this;
+
+/* First of all, see if the Box can be replaced by a simpler Region,
+   without reference to the neighbouring Regions in the list.           */
+/* =====================================================================*/
+
+/* Try to simplify the box. If the pointer value has changed, we assume
+   some simplification took place. */
+   new = astSimplify( oldbox );
+   if( new != (AstMapping *) oldbox ) {
+
+/* Annul the Box pointer in the list and replace it with the new Region
+   pointer, and indicate that the forward transformation of the returned
+   Region should be used (not really needed but keeps things clean). */
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = new;
+      ( *invert_list )[ where ] = 0;
+
+/* Return the index of the first modified element. */
+      result = where;
+
+/* If the Box itself could not be simplified, see if it can be merged
+   with the Regions on either side of it in the list. We can only merge
+   in parallel. */
+/* =====================================================================*/
+   } else if( ! series ){
+      new = astAnnul( new );
+
+/* Attempt to merge the Box with its lower neighbour (if any). */
+      if( where > 0 ) {
+         i1 = where - 1;
+         map = ( *map_list )[ where - 1 ];
+         if( astIsARegion( map ) ) {
+            new = (AstMapping *) MergeBox( oldbox, (AstRegion *) map, 0,
+                                           status );
+         }
+      }
+
+/* If this did not produced a merged Region, attempt to merge the Box with its
+   upper neighbour (if any). */
+      if( !new && where < *nmap - 1 ) {
+         i1 = where;
+         map = ( *map_list )[ where + 1 ];
+         if( astIsARegion( map ) ) {
+            new = (AstMapping *) MergeBox( oldbox, (AstRegion *) map, 1,
+                                           status );
+         }
+      }
+
+/* If succesfull... */
+      if( new ){
+
+/* Annul the first of the two Mappings, and replace it with the merged
+   Region. Also clear the invert flag. */
+         (void) astAnnul( ( *map_list )[ i1 ] );
+         ( *map_list )[ i1 ] = new;
+         ( *invert_list )[ i1 ] = 0;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+         (void) astAnnul( ( *map_list )[ i1 + 1 ] );
+         for ( i = i1 + 2; i < *nmap; i++ ) {
+            ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+            ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+         }
+
+/* Clear the vacated element at the end. */
+         ( *map_list )[ *nmap - 1 ] = NULL;
+         ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+         ( *nmap )--;
+         result = i1;
+      }
+
+   } else {
+      new = astAnnul( new );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstRegion *MergeBox( AstBox *this, AstRegion *reg, int boxfirst,
+                            int *status ) {
+/*
+*  Name:
+*     MergeBox
+
+*  Purpose:
+*     Attempt to merge a Box with another Region to form a Region of higher
+*     dimensionality.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstRegion *MergeBox( AstBox *this, AstRegion *reg, int boxfirst, int *status )
+
+*  Class Membership:
+*     Box member function.
+
+*  Description:
+*     This function attempts to combine the supplied Regions together
+*     into a Region of higher dimensionality.
+
+*  Parameters:
+*     this
+*        Pointer to a Box.
+*     reg
+*        Pointer to another Region.
+*     boxfirst
+*        If non-zero, then the Box axes are put first in the new Region.
+*        Otherwise, the other Region's axes are put first.
+*     status
+*        Pointer to the inherited status value.
+
+*  Returned Value:
+*     A pointer to a new region, or NULL if the supplied Regions could
+*     not be merged.
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;           /* Pointer to base Frame for "result" */
+   AstFrame *cfrm;           /* Pointer to current Frame for "result" */
+   AstFrame *frm_reg;        /* Pointer to Frame from "reg" */
+   AstFrame *frm_this;       /* Pointer to Frame from "this" */
+   AstMapping *bcmap;        /* Base->current Mapping for "result" */
+   AstMapping *map_reg;      /* Base->current Mapping from "reg" */
+   AstMapping *map_this;     /* Base->current Mapping from "this" */
+   AstMapping *sbunc;        /* Simplified uncertainty */
+   AstPointSet *pset_new;    /* PointSet holding PointList axis values for new */
+   AstPointSet *pset_reg;    /* PointSet holding PointList axis values for reg */
+   AstRegion *bunc;          /* Base Frame uncertainty Region */
+   AstRegion *new;           /* Pointer to new Interval in base Frame */
+   AstRegion *result;        /* Pointer to returned Interval in current Frame */
+   AstRegion *unc_reg;       /* Current Frame uncertainty Region from "reg" */
+   AstRegion *unc_this;      /* Current Frame uncertainty Region from "this" */
+   double **ptr_new;         /* Pointers to arrays holding new axis values */
+   double **ptr_reg;         /* Pointers to arrays holding reg axis values */
+   double *centre;           /* Array to hold box centre axis values */
+   double *corner;           /* Array to hold box corner axis values */
+   double *lbnd;             /* Array to hold lower axis bounds */
+   double *p;                /* Pointer to next input value */
+   double *q;                /* Pointer to next output value */
+   double *ubnd;             /* Array to hold upper axis bounds */
+   double fac_reg;           /* Ratio of used to default MeshSize for "reg" */
+   double fac_this;          /* Ratio of used to default MeshSize for "this" */
+   double temp;              /* Temporary storage */
+   int i;                    /* Loop count */
+   int j;                    /* Loop count */
+   int msz_reg;              /* Original MeshSize for "reg" */
+   int msz_reg_set;          /* Was MeshSize originally set for "reg"? */
+   int msz_this;             /* Original MeshSize for "this" */
+   int msz_this_set;         /* Was MeshSize originally set for "this"? */
+   int nax;                  /* Number of axes in "result" */
+   int nax_reg;              /* Number of axes in "reg" */
+   int nax_this;             /* Number of axes in "this" */
+   int neg_reg;              /* Negated attribute value for other supplied Region */
+   int neg_this;             /* Negated attribute value for supplied Box  */
+   int npnt;                 /* Number of points in PointList */
+   int ok;                   /* Can supplied Regions be merged? */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get the Closed attributes of the two Regions. They must be the same in
+   each Region if we are to merge the Regions. In addition, in order to
+   merge, either both Regions must have a defined uncertainty, or neither
+   Region must have a defined Uncertainty. */
+   if( astGetClosed( this ) == astGetClosed( reg ) &&
+       astTestUnc( this ) == astTestUnc( reg ) ) {
+
+/* Get the Nagated attributes of the two Regions. */
+      neg_this = astGetNegated( this );
+      neg_reg = astGetNegated( reg );
+
+/* Get the number of axes in the two supplied Regions. */
+      nax_reg = astGetNaxes( reg );
+      nax_this = astGetNaxes( this );
+
+/* If the Regions can be combined, get the number of axes the
+   combination will have. */
+      nax = nax_reg + nax_this;
+
+/* Get the base Frames from the two Region FrameSets, and combine them
+   into a single CmpFrame that will be used to create any new Region. */
+      frm_this = astGetFrame( ((AstRegion *) this)->frameset, AST__BASE );
+      frm_reg = astGetFrame( reg->frameset, AST__BASE );
+
+      if( boxfirst ) {
+         bfrm = (AstFrame *) astCmpFrame( frm_this, frm_reg, "", status );
+      } else {
+         bfrm = (AstFrame *) astCmpFrame( frm_reg, frm_this, "", status );
+      }
+
+      frm_this = astAnnul( frm_this );
+      frm_reg = astAnnul( frm_reg );
+
+/* Indicate we do not yet have a merged Region. */
+      new = NULL;
+
+/* First attempt to merge with another Box. The result will be a Box. Both
+   Boxes must be un-negated. */
+      if( astIsABox( reg ) && !neg_this && !neg_reg ) {
+
+/* Allocate memory to store the centre and corner of the returned Box. */
+         centre = astMalloc( sizeof( double )*(size_t) nax );
+         corner = astMalloc( sizeof( double )*(size_t) nax );
+
+/* Copy the centres and corners from the supplied Boxes into the above
+   arrays, in the requested order. */
+         if( boxfirst ) {
+            astBoxPoints( this, centre, corner );
+            astBoxPoints( reg, centre + nax_this, corner + nax_this );
+         } else {
+            astBoxPoints( reg, centre, corner );
+            astBoxPoints( this, centre + nax_reg, corner + nax_reg );
+         }
+
+/*  Create the new Box, initially with no uncertainty. */
+         new = (AstRegion *) astBox( bfrm, 0, centre, corner, NULL, "",
+                                     status );
+
+/* Free resources .*/
+         centre = astFree( centre );
+         corner = astFree( corner );
+
+/* Now attempt to merge with an Interval. The result will be an Interval.
+   Both Intervals must be un-negated. */
+      } else if( astIsAInterval( reg )  && !neg_this && !neg_reg ) {
+
+/* Allocate memory to store the bounds of the returned Interval. */
+         lbnd = astMalloc( sizeof( double )*(size_t) nax );
+         ubnd = astMalloc( sizeof( double )*(size_t) nax );
+
+/* Copy the centre and corner from the supplied Box into the required part
+   of the above arrays. */
+         if( boxfirst ) {
+            centre = lbnd;
+            corner = ubnd;
+         } else {
+            centre = lbnd + nax_reg;
+            corner = ubnd + nax_reg;
+         }
+         astBoxPoints( this, centre, corner );
+
+/* Convert these centre and corner positions into upper and lower bounds. */
+         if( astOK ) {
+            for( i = 0; i < nax_this; i++ ) {
+               centre[ i ] = 2*centre[ i ] - corner[ i ];
+               if( centre[ i ] > corner[ i ] ) {
+                  temp = centre[ i ];
+                  centre[ i ] = corner[ i ];
+                  corner[ i ] = temp;
+               }
+            }
+         }
+
+/* Get the bounds from the interval and add them into the above arrays. */
+         if( boxfirst ) {
+            astIntervalPoints( reg, lbnd + nax_this, ubnd + nax_this );
+         } else {
+            astIntervalPoints( reg, lbnd, ubnd );
+         }
+
+/*  Create the new Interval, initially with no uncertainty. */
+         new = (AstRegion *) astInterval( bfrm, lbnd, ubnd, NULL, "",
+                                          status );
+
+/* Free resources .*/
+         lbnd = astFree( lbnd );
+         ubnd = astFree( ubnd );
+
+/* Now attempt to merge with a NullRegion. The result will be an
+   Interval. The NullRegion must be negated and the Box must not. */
+      } else if( astIsANullRegion( reg ) && !neg_this && neg_reg ) {
+
+/* Allocate memory to store the bounds of the returned Interval. */
+         lbnd = astMalloc( sizeof( double )*(size_t) nax );
+         ubnd = astMalloc( sizeof( double )*(size_t) nax );
+
+/* Copy the centre and corner from the supplied Box into the required part
+   of the above arrays. */
+         if( boxfirst ) {
+            centre = lbnd;
+            corner = ubnd;
+         } else {
+            centre = lbnd + nax_reg;
+            corner = ubnd + nax_reg;
+         }
+         astBoxPoints( this, centre, corner );
+
+/* Convert these centre and corner positions into upper and lower bounds. */
+         if( astOK ) {
+            for( i = 0; i < nax_this; i++ ) {
+               centre[ i ] = 2*centre[ i ] - corner[ i ];
+               if( centre[ i ] > corner[ i ] ) {
+                  temp = centre[ i ];
+                  centre[ i ] = corner[ i ];
+                  corner[ i ] = temp;
+               }
+            }
+
+/* Fill the other axes with bad values to indicate they are unbounded. */
+            if( boxfirst ) {
+               for( i = nax_this; i < nax; i++ ) {
+                  lbnd[ i ] = AST__BAD;
+                  ubnd[ i ] = AST__BAD;
+               }
+            } else {
+               for( i = 0; i < nax_reg; i++ ) {
+                  lbnd[ i ] = AST__BAD;
+                  ubnd[ i ] = AST__BAD;
+               }
+            }
+
+/*  Create the new Interval, initially with no uncertainty. */
+            new = (AstRegion *) astInterval( bfrm, lbnd, ubnd, NULL, "",
+                                             status );
+         }
+
+/* Free resources .*/
+         lbnd = astFree( lbnd );
+         ubnd = astFree( ubnd );
+
+/* Now attempt to merge with a PointList. The result will be a PointList.
+   Both Regions must be un-negated. */
+      } else if( astIsAPointList( reg ) && !neg_this && !neg_reg ) {
+
+/* We can only do this if the Box has zero width on each axis (i.e.
+   represents a point). Get the Box centre and corner. */
+         centre = astMalloc( sizeof( double )*(size_t) nax_this );
+         corner = astMalloc( sizeof( double )*(size_t) nax_this );
+         astBoxPoints( this, centre, corner );
+
+/* Get the size of the Box's uncertainty region. */
+         lbnd = astMalloc( sizeof( double )*(size_t) nax_this );
+         ubnd = astMalloc( sizeof( double )*(size_t) nax_this );
+         bunc = astGetUncFrm( this, AST__BASE );
+         astGetRegionBounds( bunc, lbnd, ubnd );
+
+/* Set "ok" to zero if the Box does not have zero width on any axis. Here
+   "zero width" means a width less than half the uncertainty on the axis. */
+         if( astOK ) {
+
+            ok = 1;
+            for( i = 0; i < nax_this; i++ ) {
+               if( fabs( centre[ i ] - corner[ i ] ) >
+                   0.25*fabs( ubnd[ i ] - lbnd[ i ] ) ) {
+                  ok = 0;
+                  break;
+               }
+            }
+
+/* If the Box is a point, we go on to create a new PointList. */
+            if( ok ) {
+
+/* Get a PointSet holding the axis values in the supplied PointList data.
+   Also get the number of points in the PointSet and pointers to the arrays
+   holding the axis values. */
+               astPointListPoints( reg, &pset_reg );
+               npnt = astGetNpoint( pset_reg );
+               ptr_reg = astGetPoints( pset_reg );
+
+/*  Create a new PointSet with room for the same number of points, but
+    with the extra required axes. Get pointers to its axis arrays. */
+               pset_new = astPointSet( npnt, nax, "", status );
+               ptr_new = astGetPoints( pset_new );
+
+/* Copy the PointList axis values into the new PointSet, and then include
+   the extra axis values defined by the Box to each point. */
+               if( astOK ) {
+
+                  for( j = 0; j < nax_reg; j++ ) {
+                     p = ptr_reg[ j ];
+                     q = ptr_new[ boxfirst ? nax_this + j : j ];
+                     for( i = 0; i < npnt; i++ ) *(q++) = *(p++);
+                  }
+
+                  for( j = 0; j < nax_this; j++ ) {
+                     p = centre + j;
+                     q = ptr_new[ boxfirst ? j : nax_reg + j ];
+                     for( i = 0; i < npnt; i++ ) *(q++) = *p;
+                  }
+
+/*  Create the new PointList, initially with no uncertainty. */
+                  new = (AstRegion *) astPointList( bfrm, pset_new, NULL,
+                                                    "", status );
+               }
+
+/* Free resources .*/
+               pset_new = astAnnul( pset_new );
+               pset_reg = astAnnul( pset_reg );
+            }
+         }
+         centre = astFree( centre );
+         corner = astFree( corner );
+         lbnd = astFree( lbnd );
+         ubnd = astFree( ubnd );
+         bunc = astAnnul( bunc );
+
+      }
+
+/* If a new Region was created above, propagate remaining attributes of
+   the supplied Region to it. */
+      if( new ) {
+         astRegOverlay( new, this, 1 );
+
+/* The above Prism constructors create the Prism with the correct value
+   for the Nagated attribute (i.e. zero). Ensure the above call to
+   astRegOverlay has not changed this. */
+         astClearNegated( new );
+
+/* If both the supplied Regions have uncertainty, assign the new Region an
+   uncertainty. */
+         if( astTestUnc( this ) && astTestUnc( reg ) ) {
+
+/* Get the uncertainties from the two supplied Regions. */
+            unc_this = astGetUncFrm( this, AST__BASE );
+            unc_reg = astGetUncFrm( reg, AST__BASE );
+
+/* Combine them into a single Region (a Prism), in the correct order. */
+            if( boxfirst ) {
+               bunc = (AstRegion *) astPrism( unc_this, unc_reg, "", status );
+            } else {
+               bunc = (AstRegion *) astPrism( unc_reg, unc_this, "", status );
+            }
+
+/* Attempt to simplify the Prism. */
+            sbunc = astSimplify( bunc );
+
+/* Use the simplified Prism as the uncertainty for the returned Region. */
+            astSetUnc( new, sbunc );
+
+/* Free resources. */
+            sbunc = astAnnul( sbunc );
+            bunc = astAnnul( bunc );
+            unc_reg = astAnnul( unc_reg );
+            unc_this = astAnnul( unc_this );
+         }
+
+/* Get the current Frames from the two Region FrameSets, and combine them
+   into a single CmpFrame. */
+         frm_this = astGetFrame( ((AstRegion *) this)->frameset, AST__CURRENT );
+         frm_reg = astGetFrame( reg->frameset, AST__CURRENT );
+
+         if( boxfirst ) {
+            cfrm = (AstFrame *) astCmpFrame( frm_this, frm_reg, "", status );
+         } else {
+            cfrm = (AstFrame *) astCmpFrame( frm_reg, frm_this, "", status );
+         }
+
+/* Get the base -> current Mappings from the two Region FrameSets, and
+   combine them into a single parallel CmpMap that connects bfrm and cfrm. */
+         map_this = astGetMapping( ((AstRegion *) this)->frameset, AST__BASE,
+                                   AST__CURRENT );
+         map_reg = astGetMapping( reg->frameset, AST__BASE, AST__CURRENT );
+
+         if( boxfirst ) {
+            bcmap = (AstMapping *) astCmpMap( map_this, map_reg, 0, "",
+                                              status );
+         } else {
+            bcmap = (AstMapping *) astCmpMap( map_reg, map_this, 0, "",
+                                              status );
+         }
+
+/* Map the new Region into the new current Frame. */
+         result = astMapRegion( new, bcmap, cfrm );
+
+/* The filling factor in the returned is the product of the filling
+   factors for the two supplied Regions. */
+         if( astTestFillFactor( reg ) || astTestFillFactor( this ) ) {
+            astSetFillFactor( result, astGetFillFactor( reg )*
+                                      astGetFillFactor( this ) );
+         }
+
+/* If the MeshSize value is set in either supplied Region, set a value
+   for the returned Region which scales the default value by the
+   product of the scaling factors for the two supplied Regions. First see
+   if either MeshSize value is set. */
+         msz_this_set = astTestMeshSize( this );
+         msz_reg_set = astTestMeshSize( reg );
+         if( msz_this_set || msz_reg_set ) {
+
+/* If so, get the two MeshSize values (one of which may be a default
+   value), and then clear them so that the default value will be returned
+   in future. */
+            msz_this = astGetMeshSize( this );
+            msz_reg = astGetMeshSize( reg );
+            astClearMeshSize( this );
+            astClearMeshSize( reg );
+
+/* Get the ratio of the used MeshSize to the default MeshSize for both
+   Regions. */
+            fac_this = (double)msz_this/(double)astGetMeshSize( this );
+            fac_reg = (double)msz_reg/(double)astGetMeshSize( reg );
+
+/* The MeshSize of the returned Returned is the default value scaled by
+   the product of the two ratios found above. */
+            astSetMeshSize( result, fac_this*fac_reg*astGetMeshSize( result ) );
+
+/* Re-instate the original MeshSize values for the supplied Regions (if
+   set) */
+            if( msz_this_set ) astSetMeshSize( this, msz_this );
+            if( msz_reg_set ) astSetMeshSize( reg, msz_reg );
+         }
+
+/* Free remaining resources */
+         frm_this = astAnnul( frm_this );
+         frm_reg = astAnnul( frm_reg );
+         map_this = astAnnul( map_this );
+         map_reg = astAnnul( map_reg );
+         bcmap = astAnnul( bcmap );
+         new = astAnnul( new );
+         cfrm = astAnnul( cfrm );
+      }
+      bfrm = astAnnul( bfrm );
+   }
+
+/* If an error has occurred, annul the returned pointer. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void RegBaseBox( AstRegion *this_region, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     RegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the astRegBaseBox protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstBox *this;                 /* Pointer to Box structure */
+   double axcen;                 /* Central axis value */
+   double axlen;                 /* Half width of box on axis */
+   int i;                        /* Axis index */
+   int nc;                       /* No. of axes in base Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Box structure */
+   this = (AstBox *) this_region;
+
+/* Ensure cached information is up to date. */
+   Cache( this, 0, status );
+
+/* Get the number of base Frame axes in the Region. */
+   nc = astGetNin( this_region->frameset );
+
+/* The first point is the centre of the box, the second point is the half
+   size of the box on each axis.*/
+   for( i = 0; i < nc; i++ ) {
+      axcen = this->centre[ i ];
+      axlen = this->extent[ i ];
+      lbnd[ i ] = axcen - axlen;
+      ubnd[ i ] = axcen + axlen;
+   }
+}
+
+static AstPointSet *RegBaseGrid( AstRegion *this, int *status ){
+/*
+*  Name:
+*     RegBaseGrid
+
+*  Purpose:
+*     Return a PointSet containing points spread through the volume of a
+*     Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstPointSet *RegBaseGrid( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the astRegBaseGrid protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a set of points spread
+*     through the volume of the supplied Box. The points refer to the base
+*     Frame of the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. If the Region is unbounded, a NULL pointer
+*     will be returned.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;                 /* Base Frame in encapsulated FrameSet */
+   AstPointSet *result;           /* Returned pointer */
+   double *lbnd;                  /* Pointer to array of lower bounds of box */
+   double *ubnd;                  /* Pointer to array of upper bounds of box */
+   int meshsize;                  /* Requested size of grid */
+   int naxes;                     /* No. of axes in base Frame */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return NULL;
+
+/* If the Region structure contains a pointer to a PointSet holding
+   a previously created grid, return it. */
+   if( this->basegrid ) {
+      result = astClone( this->basegrid );
+
+/* Otherwise, create a new one, but only if the Box is bounded. */
+   } else if( astGetBounded( this ) ) {
+
+/* Get the base Frame in the Region's FrameSet. */
+      frm = astGetFrame( this->frameset, AST__BASE );
+
+/* Get the number of axes in the base Frame */
+      naxes = astGetNaxes( frm );
+
+/* Get the bounds of the Region in the base Frame. */
+      lbnd = astMalloc( sizeof( double )*(size_t) naxes );
+      ubnd = astMalloc( sizeof( double )*(size_t) naxes );
+      astRegBaseBox( this, lbnd, ubnd );
+
+/* Get the number of points which would be used to create a boundary
+   mesh. We use the same number to determine the number of points in the
+   grid. */
+      meshsize = astGetMeshSize( this );
+
+/* Create the PointSet holding the grid. */
+      result = astFrameGrid( frm, meshsize, lbnd, ubnd );
+
+/* Save the returned pointer in the Region structure so that it does not
+   need to be created again next time this function is called. */
+      if( astOK && result ) this->basegrid = astClone( result );
+
+/* Free remaining resources. */
+      frm = astAnnul( frm );
+      lbnd = astFree( lbnd );
+      ubnd = astFree( ubnd );
+   }
+
+/* Annul the result if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result */
+   return result;
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this, int *status ){
+/*
+*  Name:
+*     RegBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing a mesh of points on the boundary of a
+*     Region in its base Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstPointSet *RegBaseMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the astRegBaseMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. The axis values in this PointSet will have
+*     associated accuracies derived from the accuracies which were
+*     supplied when the Region was created.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*/
+
+/* Local Constants: */
+#define NP_EDGE 50                /* No. of points for determining geodesic
+                                     length of each axis. */
+
+/* Local Variables: */
+   AstFrame *frm;                 /* Base Frame in encapsulated FrameSet */
+   AstFrame *pfrm0;               /* Primary Frame defining axis 0 */
+   AstFrame *pfrm1;               /* Primary Frame defining axis 1 */
+   AstPointSet *result;           /* Returned pointer */
+   const char *class0;            /* Pointer to class string from pfrm0 */
+   const char *class1;            /* Pointer to class string from pfrm1 */
+   double **ptr;                  /* Pointers to data */
+   double *ax;                    /* Pointer to next first axis value */
+   double *ay;                    /* Pointer to next second axis value */
+   double *lbnd;                  /* Pointer to array of lower bounds of box */
+   double *ubnd;                  /* Pointer to array of lower bounds of box */
+   double c[ 5 ][ 2 ];            /* Positions of corners for 2D boxes */
+   double dx;                     /* Increment along first axis of 2D box */
+   double dy;                     /* Increment along second axis of 2D box */
+   double edge_len[ 4 ];          /* Length of each edge of 2D boundary */
+   double lax;                    /* Previous value on first axis */
+   double lay;                    /* Previous value on second axis */
+   double len;                    /* Length of edge of 2D boundary */
+   double p0[ 2 ];                /* Position in 2D Frame */
+   double p1[ 2 ];                /* Position in 2D Frame */
+   double ppd;                    /* Points per unit distance */
+   double total_len;              /* Total length of 2D boundary */
+   int flat;                      /* Assume Frame geometry is flat? */
+   int i;                         /* Point index */
+   int iaxis;                     /* Axis index */
+   int iedge;                     /* Edge index */
+   int ip;                        /* Index of next point */
+   int metric;                    /* Does Frame have a usable metric? */
+   int nax0;                      /* No. of axes in first primary Frame */
+   int nax1;                      /* No. of axes in second primary Frame */
+   int naxes;                     /* No. of axes in base Frame */
+   int np;                        /* No. of points in returned PointSet */
+   int np0;                       /* No. of points per edge */
+   int np_axis;                   /* No. of points per axis in ND box */
+   int np_edge[ 4 ];              /* No. of points per edge in 2D box */
+   int paxis;                     /* Axis index in primary Frame */
+   int single;                    /* Does the Box occupy a single point? */
+
+/* Initialise */
+   result= NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the Region structure contains a pointer to a PointSet holding
+   a previously created mesh, return it. */
+   if( this->basemesh ) {
+      result = astClone( this->basemesh );
+
+/* Otherwise, create a new mesh. */
+   } else {
+
+/* Get the base Frame in the Region's FrameSet. */
+      frm = astGetFrame( this->frameset, AST__BASE );
+
+/* Get the number of axes in the base Frame */
+      naxes = astGetNaxes( frm );
+
+/* Get the bounds of the Region in the base Frame. */
+      lbnd = astMalloc( sizeof( double )*(size_t) naxes );
+      ubnd = astMalloc( sizeof( double )*(size_t) naxes );
+      astRegBaseBox( this, lbnd, ubnd );
+
+/* Get the requested number of points to put on the mesh. */
+      np = astGetMeshSize( this );
+
+/* See if the box occupies a single point. */
+      single = 1;
+      for( i = 0; i < naxes; i++ ) {
+         if( ubnd[ i ] > lbnd[ i ] ) {
+            single = 0;
+            break;
+         }
+      }
+
+/* If so, we return a PointSet holding a single point. */
+      if( single ) {
+         result = astPointSet( 1, naxes, "", status );
+         ptr = astGetPoints( result );
+         if( astOK ) {
+            for( i = 0; i < naxes; i++ ) {
+               ptr[ i ][ 0 ] = 0.5*( lbnd[ 0 ] + ubnd[ 0 ] );
+            }
+         }
+
+/* Otherwise, first deal with 1-D boxes. */
+      } else if( naxes == 1 ) {
+
+/* The boundary of a 1-D box consists of 2 points - the two extreme values.
+   Create a PointSet to hold 2 1-D values, and store the extreme values. */
+         result = astPointSet( 2, 1, "", status );
+         ptr = astGetPoints( result );
+         if( astOK ) {
+            ptr[ 0 ][ 0 ] = lbnd[ 0 ];
+            ptr[ 0 ][ 1 ] = ubnd[ 0 ];
+         }
+
+/* Now deal with 2-D boxes. */
+      } else if( naxes == 2 ){
+
+/* Store the coords of each corner for easy access. */
+         c[ 0 ][ 0 ] = lbnd[ 0 ];
+         c[ 0 ][ 1 ] = lbnd[ 1 ];
+         c[ 1 ][ 0 ] = lbnd[ 0 ];
+         c[ 1 ][ 1 ] = ubnd[ 1 ];
+         c[ 2 ][ 0 ] = ubnd[ 0 ];
+         c[ 2 ][ 1 ] = ubnd[ 1 ];
+         c[ 3 ][ 0 ] = ubnd[ 0 ];
+         c[ 3 ][ 1 ] = lbnd[ 1 ];
+         c[ 4 ][ 0 ] = lbnd[ 0 ];
+         c[ 4 ][ 1 ] = lbnd[ 1 ];
+
+/* See if we can assume that the frame has flat geometry. This is the case
+   if both axes belong to simple Frames, or are 1D, but may not be the case
+   if either axis does not belong to a simple Frame that has more than 1
+   axis. */
+         astPrimaryFrame( frm, 0, &pfrm0, &paxis );
+         astPrimaryFrame( frm, 1, &pfrm1, &paxis );
+         class0 = astGetClass( pfrm0 );
+         class1 = astGetClass( pfrm1 );
+         nax0 = astGetNaxes( pfrm0 );
+         nax1 = astGetNaxes( pfrm1 );
+         if( astOK ) {
+            flat = ( !strcmp( class0, "Frame" ) || nax0 == 1 ) &&
+                   ( !strcmp( class1, "Frame" ) || nax1 == 1 );
+         } else {
+            flat = 0;
+         }
+
+/* Our choice of distribution of points depends on whether the axes have the
+   same units or not. If the axes have the same units, or if they share the
+   same primary Frame, then we assume that the axes span some space in which
+   a single "distance" is defined. If not, (e.g. for a Frame representing
+   frequency in Hz on one axis - typical value 1.0E10, and slit position in
+   metres on the other axis - typical value 1.0E-2) we assume that "distance"
+   is defined differently for each axis. The primary frame requirement is
+   because some classes of Frame (e.g. SkyFrame) have a defined distance,
+   even though the Units attributes for its axes may differ (since Units
+   refers to the external representation - e.g. hours and degrees for a
+   SkyFrame - rather than the internal representation). */
+         if( astOK && !strcmp( astGetUnit( frm, 0 ), astGetUnit( frm, 1 ) ) ) {
+            metric = 1;
+         } else {
+            metric = ( pfrm0 == pfrm1 );
+         }
+
+/* If we have a usable metric, distribute the points according to the aspect
+   ratio of the box (i.e. the shorter box sides gets fewer points). */
+         if( metric ){
+
+/* Find the approximate geodesic length of each edge of the box. Since
+   the edges of the box may not correspond to single geodesic (e.g. a
+   line of constant latitude is not a geodesic), we do this by testing
+   a set of points along each edge of the box and finding the total of the
+   geodesic distances between each pair of adjacent points. Initialise the
+   total length round all edges. */
+            total_len = 0.0;
+
+/* Do each edge in turn.*/
+            for( iedge = 0; iedge < 4; iedge++ ) {
+
+/* Find the increment in x and y between adjacent points along this edge.
+   We put a point at each end of the edge. Note, one of dx or dy will be
+   zero since the edges are parallel to the axes. */
+               dx = ( c[ iedge + 1 ][ 0 ] - c[ iedge ][ 0 ] )/( NP_EDGE - 1 );
+               dy = ( c[ iedge + 1 ][ 1 ] - c[ iedge ][ 1 ] )/( NP_EDGE - 1 );
+
+/* Store the coords of the first point. */
+               p0[ 0 ] = c[ iedge ][ 0 ];
+               p0[ 1 ] = c[ iedge ][ 1 ];
+
+/* Initialise the length of this edge and loop round the remaining points. */
+               len = 0.0;
+               for( i = 1; i < NP_EDGE; i++ ) {
+
+/* Save the position of the previous point. */
+                  p1[ 0 ] = p0[ 0 ];
+                  p1[ 1 ] = p0[ 1 ];
+
+/* Find the position of the new point. */
+                  p0[ 0 ] = p1[ 0 ] + dx;
+                  p0[ 1 ] = p1[ 1 ] + dy;
+
+/* Increment the length of the edge by the geodesic distance from this point
+   to the previous point. If the Frame is flat, this is simple (given that we
+   know that one of dx and dy must be zero). */
+                  if( flat ) {
+                     len += fabs( dx + dy );
+                  } else {
+                     len += astDistance( frm, p0, p1 );
+                  }
+               }
+
+/* Save the length of this edge, and also form the total length round all
+   edges. */
+               edge_len[ iedge ] = len;
+               total_len += len;
+            }
+
+/* Find the average number of points per unit geodesic distance around the
+   boundary. */
+            if( total_len > 0.0 ) {
+               ppd = np / total_len;
+
+/* Use this, with the geodesic length of each edge found above, to find the
+   number of points to put along each edge of the boundary, and update the
+   total number of points to use. In the returned boundary we put a point at
+   the start of each edge but not at the end (since the end of one edge will
+   be the start of the next edge). */
+               np = 0;
+               for( iedge = 0; iedge < 4; iedge++ ) {
+                  np_edge[ iedge ] = (int) ( edge_len[ iedge ]*ppd );
+                  if( np_edge[ iedge ] == 0 ) np_edge[ iedge ] = 1;
+                  np += np_edge[ iedge ];
+               }
+
+/* Report error if total length round box is zero. */
+            } else if( astOK ) {
+               astError( AST__INTER, "astRegBaseMesh(%s): Distance around "
+                         "box perimeter is zero (internal AST programming "
+                         "error).", status, astGetClass( this ) );
+            }
+
+/* If the Frame has no usable metric, give an equal number of points
+   (equal to a quarter of the total) to all 4 sides of the box. */
+         } else {
+            np0 = (int) ( 0.25*np );
+            np = 0;
+            for( iedge = 0; iedge < 4; iedge++ ) {
+               np_edge[ iedge ] = np0;
+               np += np_edge[ iedge ];
+            }
+         }
+
+/* Create a PointSet with enough room and get a pointer to its data arrays. */
+         result = astPointSet( np, 2, "", status );
+         ptr = astGetPoints( result );
+         if( astOK ) {
+
+/* Initialise pointers to the first element for each axis in the returned
+   PointSet. */
+            ax = ptr[ 0 ];
+            ay = ptr[ 1 ];
+
+/* Loop round each edge. */
+            for( iedge = 0; iedge < 4; iedge++ ) {
+
+/* Find the increment in x and y between adjacent points along this edge. */
+               dx = ( c[ iedge + 1 ][ 0 ] - c[ iedge ][ 0 ] )/ np_edge[ iedge ];
+               dy = ( c[ iedge + 1 ][ 1 ] - c[ iedge ][ 1 ] )/ np_edge[ iedge ];
+
+/* Store the first point. */
+               lax = c[ iedge ][ 0 ];
+               lay = c[ iedge ][ 1 ];
+               *(ax++) = lax;
+               *(ay++) = lay;
+
+/* Loop round the remaining points, incrementing the pointers at the same
+   time. */
+               for( i = 1; i < np_edge[ iedge ]; i++, ax++, ay++ ) {
+
+/* Find the position of the new point and store it. */
+                  lax += dx;
+                  lay += dy;
+                  *ax = lax;
+                  *ay = lay;
+               }
+            }
+         }
+
+/* Free resources. */
+         pfrm0 = astAnnul( pfrm0 );
+         pfrm1 = astAnnul( pfrm1 );
+
+/* Now deal with boxes with more than 2 dimensions. */
+      } else {
+
+/* Number of samples along each edge of the hyper-cube. */
+         np_axis = 1 + (int) pow( np/(2*naxes), 1.0/(naxes-1) );
+         if( np_axis < 2 ) np_axis = 2;
+
+/* Each face of the hyper-cube will have np_axis**(naxes-1) points, and there
+   are 2*naxes faces. Create a PointSet with the correct number of
+   points. */
+         np = 2*naxes;
+         for( iaxis = 1; iaxis < naxes; iaxis++ ) np *= np_axis;
+         result = astPointSet( np, naxes, "", status );
+         ptr = astGetPoints( result );
+         if( astOK ) {
+
+/* Initialise the index of the next point to add into the PointSet. */
+            ip = 0;
+
+/* Create the upper and lower faces for each axis in turn. */
+            for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+
+/* First do the upper face for this axis. */
+               ip += MakeGrid( naxes, ptr, ip, lbnd, ubnd, NULL, np_axis,
+                               iaxis, ubnd[ iaxis ], status );
+
+/* Now do the lower face for this axis. */
+               ip += MakeGrid( naxes, ptr, ip, lbnd, ubnd, NULL, np_axis,
+                               iaxis, lbnd[ iaxis ], status );
+            }
+
+/* Remove any unused space at the end of the PointSet. */
+            if( ip < np ) astSetNpoint( result, ip );
+         }
+      }
+
+/* Same the returned pointer in the Region structure so that it does not
+   need to be created again next time this function is called. */
+      if( astOK && result ) this->basemesh = astClone( result );
+
+/* Free resources. */
+      frm = astAnnul( frm );
+      lbnd = astFree( lbnd );
+      ubnd = astFree( ubnd );
+   }
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+
+/* Undefine macros local to this function. */
+#undef NP_EDGE
+
+}
+
+static AstRegion *RegBasePick( AstRegion *this_region, int naxes, const int *axes,
+                               int *status ){
+/*
+*  Name:
+*     RegBasePick
+
+*  Purpose:
+*     Return a Region formed by picking selected base Frame axes from the
+*     supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstRegion *RegBasePick( AstRegion *this, int naxes, const int *axes,
+*                             int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the astRegBasePick protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function attempts to return a Region that is spanned by selected
+*     axes from the base Frame of the encapsulated FrameSet of the supplied
+*     Region. This may or may not be possible, depending on the class of
+*     Region. If it is not possible a NULL pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     naxes
+*        The number of base Frame axes to select.
+*     axes
+*        An array holding the zero-based indices of the base Frame axes
+*        that are to be selected.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Region, or NULL if no region can be formed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;         /* The base Frame in the supplied Region */
+   AstFrame *frm;          /* The base Frame in the returned Region */
+   AstPointSet *pset;      /* Holds axis values defining the supplied Region */
+   AstRegion *bunc;        /* The uncertainty in the supplied Region */
+   AstRegion *result;      /* Returned Region */
+   AstRegion *unc;         /* The uncertainty in the returned Region */
+   double **ptr;           /* Holds axis values defining the supplied Region */
+   double *cen;            /* Base Frm axis values at centre of returned Box */
+   double *cor;            /* Base Frm axis values at a corner of returned Box */
+   int i;                  /* Index of axis within returned Region */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the base Frame of the encapsulated FrameSet. */
+   bfrm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Create a Frame by picking the selected axes from the base Frame of the
+   encapsulated FrameSet. */
+   frm = astPickAxes( bfrm, naxes, axes, NULL );
+
+/* Get the uncertainty Region (if any) within the base Frame of the supplied
+   Region, and select the required axes from it. If the resulting Object
+   is not a Region, annul it so that the returned Region will have no
+   uncertainty. */
+   if( astTestUnc( this_region ) ) {
+      bunc = astGetUncFrm( this_region, AST__BASE );
+      unc = astPickAxes( bunc, naxes, axes, NULL );
+      bunc = astAnnul( bunc );
+
+      if( ! astIsARegion( unc ) ) unc = astAnnul( unc );
+
+   } else {
+      unc = NULL;
+   }
+
+/* Get pointers to the coordinate data in the parent Region structure. */
+   pset = this_region->points;
+   ptr = astGetPoints( pset );
+
+/* Get space to hold the centre and corner of the Box in the new Frame. */
+   cen = astMalloc( sizeof( *cen )*naxes );
+   cor = astMalloc( sizeof( *cor )*naxes );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Copy the centre and corner axis values for the selected axes into the
+   arrays allocated above. */
+      for( i = 0; i < naxes; i++ ) {
+         cen[ i ] = ptr[ axes[ i ] ][ 0 ];
+         cor[ i ] = ptr[ axes[ i ] ][ 1 ];
+      }
+
+/* Create the new Box. */
+      result = (AstRegion *) astBox( frm, 0, cen, cor, unc, "", status );
+   }
+
+/* Free resources */
+   frm = astAnnul( frm );
+   bfrm = astAnnul( bfrm );
+   if( unc ) unc = astAnnul( unc );
+   cen = astFree( cen );
+   cor = astFree( cor );
+
+/* Return a NULL pointer if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static double *RegCentre( AstRegion *this_region, double *cen, double **ptr,
+                          int index, int ifrm, int *status ){
+/*
+*  Name:
+*     RegCentre
+
+*  Purpose:
+*     Re-centre a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     double *RegCentre( AstRegion *this, double *cen, double **ptr,
+*                        int index, int ifrm, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the astRegCentre protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function shifts the centre of the supplied Region to a
+*     specified position, or returns the current centre of the Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     cen
+*        Pointer to an array of axis values, giving the new centre.
+*        Supply a NULL value for this in order to use "ptr" and "index" to
+*        specify the new centre.
+*     ptr
+*        Pointer to an array of points, one for each axis in the Region.
+*        Each pointer locates an array of axis values. This is the format
+*        returned by the PointSet method astGetPoints. Only used if "cen"
+*        is NULL.
+*     index
+*        The index of the point within the arrays identified by "ptr" at
+*        which is stored the coords for the new centre position. Only used
+*        if "cen" is NULL.
+*     ifrm
+*        Should be AST__BASE or AST__CURRENT. Indicates whether the centre
+*        position is supplied and returned in the base or current Frame of
+*        the FrameSet encapsulated within "this".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If both "cen" and "ptr" are NULL then a pointer to a newly
+*     allocated dynamic array is returned which contains the centre
+*     coords of the Region. This array should be freed using astFree when
+*     no longer needed. If either of "ptr" or "cen" is not NULL, then a
+*     NULL pointer is returned.
+
+*  Notes:
+*    - Some Region sub-classes do not have a centre. Such classes will report
+*    an AST__INTER error code if this method is called with either "ptr" or
+*    "cen" not NULL. If "ptr" and "cen" are both NULL, then no error is
+*    reported if this method is invoked on a Region of an unsuitable class,
+*    but NULL is always returned.
+
+*/
+
+/* Local Variables: */
+   AstBox *this;       /* Pointer to Box structure */
+   AstFrame *frm;      /* Pointer to Box's base Frame */
+   double **rptr;      /* Data pointers for Region PointSet */
+   double *bc;         /* Base Frame centre position */
+   double *corner;     /* Array holding corner axis values */
+   double *result;     /* Returned pointer */
+   double *tmp;        /* Temporary array pointer */
+   double axval;       /* Axis value */
+   int ic;             /* Coordinate index */
+   int ncb;            /* Number of base frame coordinate values per point */
+   int ncc;            /* Number of current frame coordinate values per point */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Box structure. */
+   this = (AstBox *) this_region;
+
+/* First ensure cached information (which includes the centre coords)
+   is up to date. */
+   Cache( this, 0, status );
+
+/* Get the number of axis values per point in the base and current Frames. */
+   ncb = astGetNin( this_region->frameset );
+   ncc = astGetNout( this_region->frameset );
+
+/* If the centre coords are to be returned, return either a copy of the
+   base Frame centre coords, or transform the base Frame centre coords
+   into the current Frame. First ensure cached information (which
+   includes the centre coords) is up to date. */
+   if( !ptr && !cen ) {
+      if( ifrm == AST__CURRENT ) {
+         result = astRegTranPoint( this_region, this->centre, 1, 1 );
+      } else {
+         result = astStore( NULL, this->centre, sizeof( double )*ncb );
+      }
+
+/* Otherwise, we store the supplied new centre coords and return a NULL
+   pointer. */
+   } else {
+
+/* Get a pointer to the axis values stored in the Region structure. */
+      rptr = astGetPoints( this_region->points );
+
+/* Check pointers can be used safely */
+      if( astOK ) {
+
+/* If the centre position was supplied in the current Frame, find the
+   corresponding base Frame position... */
+         if( ifrm == AST__CURRENT ) {
+            if( cen ) {
+               bc = astRegTranPoint( this_region, cen, 1, 0 );
+            } else {
+               tmp = astMalloc( sizeof( double)*(size_t)ncc );
+               if( astOK ) {
+                  for( ic = 0; ic < ncc; ic++ ) tmp[ ic ] = ptr[ ic ][ index ];
+               }
+               bc = astRegTranPoint( this_region, tmp, 1, 0 );
+               tmp = astFree( tmp );
+            }
+
+/* Replace any bad centre values with their current values. */
+            for( ic = 0; ic < ncb; ic++ ) {
+               if( bc[ ic ] ==  AST__BAD ) bc[ ic ] = this->centre[ ic ];
+            }
+
+/* If the centre position was supplied in the base Frame, store the
+   centre coords in this->centre, skipping bad values. */
+         } else {
+            bc = this->centre;
+            for( ic = 0; ic < ncb; ic++ ) {
+               axval = cen ? cen[ ic ] : ptr[ ic ][ index ];
+               if( axval != AST__BAD ) bc[ ic ] = axval;
+            }
+         }
+
+/* Find the coordinates at the new box corner. */
+         frm = astGetFrame( this_region->frameset, AST__BASE );
+         corner = GeoCorner( frm, ncb, bc, this->geolen, NULL, status );
+         frm = astAnnul( frm );
+
+/* ... and change the coords in the parent Region structure. */
+         for( ic = 0; ic < ncb; ic++ ) {
+            rptr[ ic ][ 0 ] = bc[ ic ];
+            rptr[ ic ][ 1 ] = corner[ ic ];
+         }
+
+/* Free resources */
+         if( ifrm == AST__CURRENT ) bc = astFree( bc );
+         corner = astFree( corner );
+
+/* Indicate the cached info in the Box structure is out of date. */
+         astResetCache( this );
+
+/* Any base Frame mesh or grid is now no good, so annul it. */
+         if( this_region->basemesh ) this_region->basemesh = astAnnul( this_region->basemesh );
+         if( this_region->basegrid ) this_region->basegrid = astAnnul( this_region->basegrid );
+
+      }
+   }
+
+
+/* Return the result. */
+   return result;
+}
+
+static int RegPins( AstRegion *this_region, AstPointSet *pset, AstRegion *unc,
+                    int **mask, int *status ){
+/*
+*  Name:
+*     RegPins
+
+*  Purpose:
+*     Check if a set of points fall on the boundary of a given Box.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+*                  int **mask, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the astRegPins protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied set of
+*     points all fall on the boundary of the given Box.
+*
+*     Some tolerance is allowed, as specified by the uncertainty Region
+*     stored in the supplied Box "this", and the supplied uncertainty
+*     Region "unc" which describes the uncertainty of the supplied points.
+
+*  Parameters:
+*     this
+*        Pointer to the Box.
+*     pset
+*        Pointer to the PointSet. The points are assumed to refer to the
+*        base Frame of the FrameSet encapsulated by "this".
+*     unc
+*        Pointer to a Region representing the uncertainties in the points
+*        given by "pset". The Region is assumed to represent the base Frame
+*        of the FrameSet encapsulated by "this". Zero uncertainity is assumed
+*        if NULL is supplied.
+*     mask
+*        Pointer to location at which to return a pointer to a newly
+*        allocated dynamic array of ints. The number of elements in this
+*        array is equal to the value of the Npoint attribute of "pset".
+*        Each element in the returned array is set to 1 if the
+*        corresponding position in "pset" is on the boundary of the Region
+*        and is set to zero otherwise. A NULL value may be supplied
+*        in which case no array is created. If created, the array should
+*        be freed using astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the points all fall on the boundary of the given
+*     Region, to within the tolerance specified. Zero otherwise.
+
+*/
+
+/* Local variables: */
+   AstBox *large_box;           /* Box slightly larger than "this" */
+   AstBox *small_box;           /* Box slightly smaller than "this" */
+   AstBox *this;                /* Pointer to the Box structure. */
+   AstFrame *frm;               /* Base Frame in supplied Box */
+   AstPointSet *ps1;            /* Points masked by larger Box */
+   AstPointSet *ps2;            /* Points masked by larger and smaller Boxes */
+   AstRegion *tunc;             /* Uncertainity Region from "this" */
+   double **ptr;                /* Pointer to axis values in "ps2" */
+   double *large;               /* A corner position in the larger Box */
+   double *safe;                /* An interior point in "this" */
+   double *lbnd_tunc;           /* Lower bounds of "this" uncertainty Region */
+   double *lbnd_unc;            /* Lower bounds of supplied uncertainty Region */
+   double *p;                   /* Pointer to next axis value */
+   double *small;               /* A corner position in the smaller Box */
+   double *ubnd_tunc;           /* Upper bounds of "this" uncertainty Region */
+   double *ubnd_unc;            /* Upper bounds of supplied uncertainty Region */
+   double *wid;                 /* Widths of "this" border */
+   int i;                       /* Axis index */
+   int j;                       /* Point index */
+   int nc;                      /* No. of axes in Box base frame */
+   int np;                      /* No. of supplied points */
+   int result;                  /* Returned flag */
+
+/* Initialise */
+   result = 0;
+   if( mask ) *mask = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a pointer to the Box structure. */
+   this = (AstBox *) this_region;
+
+/* Ensure cached information is up to date. */
+   Cache( this, 0, status );
+
+/* Get the number of base Frame axes in the Box, and check the supplied
+   PointSet has the same number of axis values per point. */
+   frm = astGetFrame( this_region->frameset, AST__BASE );
+   nc = astGetNaxes( frm );
+   if( astGetNcoord( pset ) != nc && astOK ) {
+      astError( AST__INTER, "astRegPins(%s): Illegal number of axis "
+                "values per point (%d) in the supplied PointSet - should be "
+                "%d (internal AST programming error).", status, astGetClass( this ),
+                astGetNcoord( pset ), nc );
+   }
+
+/* Get the number of axes in the uncertainty Region and check it is the
+   same as above. */
+   if( unc && astGetNaxes( unc ) != nc && astOK ) {
+      astError( AST__INTER, "astRegPins(%s): Illegal number of axes (%d) "
+                "in the supplied uncertainty Region - should be "
+                "%d (internal AST programming error).", status, astGetClass( this ),
+                astGetNaxes( unc ), nc );
+   }
+
+/* Get the centre of the region in the base Frame. We use this as a "safe"
+   interior point within the region. */
+   safe = astRegCentre( this, NULL, NULL, 0, AST__BASE );
+
+/* We now find the maximum distance on each axis that a point can be from the
+   boundary of the Box for it still to be considered to be on the boundary.
+   First get the Region which defines the uncertainty within the Box being
+   checked (in its base Frame), re-centre it on the interior point found
+   above (to avoid problems if the uncertainty region straddles a
+   discontinuity), and get its bounding box. */
+   tunc = astGetUncFrm( this, AST__BASE );
+   if( safe ) astRegCentre( tunc, safe, NULL, 0, AST__CURRENT );
+   lbnd_tunc = astMalloc( sizeof( double )*(size_t) nc );
+   ubnd_tunc = astMalloc( sizeof( double )*(size_t) nc );
+   astGetRegionBounds( tunc, lbnd_tunc, ubnd_tunc );
+
+/* Also get the Region which defines the uncertainty of the supplied
+   points and get its bounding box. First re-centre the uncertainty at the
+   interior position to avoid problems from uncertainties that straddle a
+   discontinuity. */
+   if( unc ) {
+      if( safe ) astRegCentre( unc, safe, NULL, 0, AST__CURRENT );
+      lbnd_unc = astMalloc( sizeof( double )*(size_t) nc );
+      ubnd_unc = astMalloc( sizeof( double )*(size_t) nc );
+      astGetRegionBounds( unc, lbnd_unc, ubnd_unc );
+   } else {
+      lbnd_unc = NULL;
+      ubnd_unc = NULL;
+   }
+
+/* The required border width for each axis is half of the total width
+   of the two bounding boxes. Use a zero sized box "unc" if no box was
+   supplied. */
+   wid = astMalloc( sizeof( double )*(size_t) nc );
+   large = astMalloc( sizeof( double )*(size_t) nc );
+   small = astMalloc( sizeof( double )*(size_t) nc );
+   if( astOK ) {
+      if( unc ) {
+         for( i = 0; i < nc; i++ ) {
+            wid[ i ] = 0.5*( fabs( astAxDistance( frm, i + 1, lbnd_tunc[ i ], ubnd_tunc[ i ] ) )
+                           + fabs( astAxDistance( frm, i + 1, lbnd_unc[ i ], ubnd_unc[ i ] ) ) );
+         }
+      } else {
+         for( i = 0; i < nc; i++ ) {
+            wid[ i ] = fabs( 0.5*astAxDistance( frm, i + 1, lbnd_tunc[ i ], ubnd_tunc[ i ] ) );
+         }
+      }
+
+/* Create two new Boxes, one of which is larger than "this" by the widths
+   found above, and the other of which is smaller than "this" by the widths
+   found above. */
+      for( i = 0; i < nc; i++ ) {
+         large[ i ] = this->centre[ i ]  + this->extent[ i ] + wid[ i ];
+         small[ i ] = this->extent[ i ] - wid[ i ];
+         if( small[ i ] < 0.0 ) small[ i ] = 0.0;
+         small[ i ] += this->centre[ i ];
+      }
+
+      large_box = astBox( frm, 0, this->centre, large, NULL, "", status );
+      small_box = astBox( frm, 0, this->centre, small, NULL, "", status );
+
+/* Negate the smaller region.*/
+      astNegate( small_box );
+
+/* Points are on the boundary of "this" if they are inside both the large
+   box and the negated smallbox. First transform the supplied PointSet
+   using the large box, then transform them using the negated smaller Box. */
+      ps1 = astTransform( large_box, pset, 1, NULL );
+      ps2 = astTransform( small_box, ps1, 1, NULL );
+
+/* Get a point to the resulting axis values, and the number of axis
+   values per axis. */
+      ptr = astGetPoints( ps2 );
+      np = astGetNpoint( ps2 );
+
+/* If a mask array is to be returned, create one. */
+      if( mask ) {
+         *mask = astMalloc( sizeof(int)*(size_t) np );
+
+/* Check all the resulting points, setting mask values for all of them. */
+         if( astOK ) {
+
+/* Initialise the mask elements on the basis of the first axis values */
+            result = 1;
+            p = ptr[ 0 ];
+            for( j = 0; j < np; j++ ) {
+               if( *(p++) == AST__BAD ) {
+                  result = 0;
+                  (*mask)[ j ] = 0;
+               } else {
+                  (*mask)[ j ] = 1;
+               }
+            }
+
+/* Now check for bad values on other axes. */
+            for( i = 1; i < nc; i++ ) {
+               p = ptr[ i ];
+               for( j = 0; j < np; j++ ) {
+                  if( *(p++) == AST__BAD ) {
+                     result = 0;
+                     (*mask)[ j ] = 0;
+                  }
+               }
+            }
+         }
+
+/* If no output mask is to be made, we can break out of the check as soon
+   as the first bad value is found. */
+      } else if( astOK ) {
+         result = 1;
+         for( i = 0; i < nc && result; i++ ) {
+            p = ptr[ i ];
+            for( j = 0; j < np; j++ ) {
+               if( *(p++) == AST__BAD ) {
+                  result = 0;
+                  break;
+               }
+            }
+         }
+      }
+
+/* Free resources. */
+      large_box = astAnnul( large_box );
+      small_box = astAnnul( small_box );
+      ps1 = astAnnul( ps1 );
+      ps2 = astAnnul( ps2 );
+   }
+
+   tunc = astAnnul( tunc );
+   frm = astAnnul( frm );
+   lbnd_tunc = astFree( lbnd_tunc );
+   ubnd_tunc = astFree( ubnd_tunc );
+   if( unc ) lbnd_unc = astFree( lbnd_unc );
+   if( unc ) ubnd_unc = astFree( ubnd_unc );
+   wid = astFree( wid );
+   large = astFree( large );
+   small = astFree( small );
+   safe = astFree( safe );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) {
+      result = 0;
+      if( mask ) *mask = astAnnul( *mask );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int RegTrace( AstRegion *this_region, int n, double *dist, double **ptr,
+                     int *status ){
+/*
+*+
+*  Name:
+*     RegTrace
+
+*  Purpose:
+*     Return requested positions on the boundary of a 2D Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     int astTraceRegion( AstRegion *this, int n, double *dist, double **ptr );
+
+*  Class Membership:
+*     Box member function (overrides the astTraceRegion method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function returns positions on the boundary of the supplied
+*     Region, if possible. The required positions are indicated by a
+*     supplied list of scalar parameter values in the range zero to one.
+*     Zero corresponds to some arbitrary starting point on the boundary,
+*     and one corresponds to the end (which for a closed region will be
+*     the same place as the start).
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     n
+*        The number of positions to return. If this is zero, the function
+*        returns without action (but the returned function value still
+*        indicates if the method is supported or not).
+*     dist
+*        Pointer to an array of "n" scalar parameter values in the range
+*        0 to 1.0.
+*     ptr
+*        A pointer to an array of pointers. The number of elements in
+*        this array should equal tthe number of axes in the Frame spanned
+*        by the Region. Each element of the array should be a pointer to
+*        an array of "n" doubles, in which to return the "n" values for
+*        the corresponding axis. The contents of the arrays are unchanged
+*        if the supplied Region belongs to a class that does not
+*        implement this method.
+
+*  Returned Value:
+*     Non-zero if the astTraceRegion method is implemented by the class
+*     of Region supplied, and zero if not.
+
+*-
+*/
+
+/* Local Variables; */
+   AstMapping *map;
+   AstPointSet *bpset;
+   AstPointSet *cpset;
+   double **bptr;
+   double d;
+   double lbnd[ 2 ];
+   double ubnd[ 2 ];
+   int i;
+   int ncur;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check inherited status. */
+   if( ! astOK ) return result;
+
+/* Check it is 2-dimensional. */
+   if( astGetNin( this_region->frameset ) == 2 ) result = 1;
+
+/* Check we have some points to find. */
+   if( result && n > 0 ) {
+
+/* We first determine the required positions in the base Frame of the
+   Region, and then transform them into the current Frame. Get the
+   base->current Mapping, and the number of current Frame axes. */
+      map = astGetMapping( this_region->frameset, AST__BASE, AST__CURRENT );
+
+/* If it's a UnitMap we do not need to do the transformation, so put the
+   base Frame positions directly into the supplied arrays. */
+      if( astIsAUnitMap( map ) ) {
+         bpset = NULL;
+         bptr = ptr;
+         ncur = 2;
+
+/* Otherwise, create a PointSet to hold the base Frame positions. */
+      } else {
+         bpset = astPointSet( n, 2, " ", status );
+         bptr = astGetPoints( bpset );
+         ncur = astGetNout( map );
+      }
+
+/* Check the pointers can be used safely. */
+      if( astOK ) {
+
+/* Get the bounds of the Region in the base Frame. */
+         astRegBaseBox( this_region, lbnd, ubnd );
+
+/* Loop round each point. Each edge of the box covers a parameteric
+   distance of 0.25, regardless of the aspect ratio of the box. */
+         for( i = 0; i < n; i++ ) {
+
+/* The right hand edge starts at 0.75 (parameter increases top to bottom). */
+            d = 4*dist[ i ] - 3;
+            if( d > 0 ) {
+               bptr[ 0 ][ i ] = ubnd[ 0 ];
+               bptr[ 1 ][ i ] = ( 1.0 - d )*ubnd[ 1 ] + d*lbnd[ 1 ];
+
+/* The top edge starts at 0.5 (parameter increases left to right). */
+            } else {
+               d += 1.0;
+               if( d > 0 ) {
+                  bptr[ 0 ][ i ] = ( 1.0 - d )*lbnd[ 0 ] + d*ubnd[ 0 ];
+                  bptr[ 1 ][ i ] = ubnd[ 1 ];
+
+/* The left hand edge starts at 0.25 (parameter increases bottom to top). */
+               } else {
+                  d += 1.0;
+                  if( d > 0 ) {
+                     bptr[ 0 ][ i ] = lbnd[ 0 ];
+                     bptr[ 1 ][ i ] = ( 1.0 - d )*lbnd[ 1 ] + d*ubnd[ 1 ];
+
+/* The bottom edge starts at 0.0 (parameter increases right to left). */
+                  } else {
+                     d += 1.0;
+                     bptr[ 0 ][ i ] = ( 1.0 - d )*ubnd[ 0 ] + d*lbnd[ 0 ];
+                     bptr[ 1 ][ i ] = lbnd[ 1 ];
+                  }
+               }
+            }
+         }
+      }
+
+/* If required, transform the base frame positions into the current
+   Frame, storing them in the supplied array. Then free resources. */
+      if( bpset ) {
+         cpset = astPointSet( n, ncur, " ", status );
+         astSetPoints( cpset, ptr );
+
+         (void) astTransform( map, bpset, 1, cpset );
+
+         cpset = astAnnul( cpset );
+         bpset = astAnnul( bpset );
+      }
+
+/* Free remaining resources. */
+      map = astAnnul( map );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void ResetCache( AstRegion *this, int *status ){
+/*
+*  Name:
+*     ResetCache
+
+*  Purpose:
+*     Clear cached information within the supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     void ResetCache( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Region member function (overrides the astResetCache method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function clears cached information from the supplied Region
+*     structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   if( this ) {
+      ((AstBox *) this )->stale = 1;
+      (*parent_resetcache)( this, status );
+   }
+}
+
+static void SetClosed( AstRegion *this, int value, int *status ){
+/*
+*  Name:
+*     SetClosed
+
+*  Purpose:
+*     Set a value for the Closed attribute of a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     void SetClosed( AstRegion *this, int value, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the protected astSetClosed
+*     method inherited from the Region class).
+
+*  Description:
+*     This function sets a new value for the Closed attribute of a Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     value
+*        The new attribute value.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int old;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the original attribute value */
+   old = astGetClosed( this );
+
+/* Invoke the set method inherited from the parent Region class */
+   (*parent_setclosed)( this, value, status );
+
+/* If the new value is not the same as the old value, indicate that we
+   need to re-calculate the cached information in the Box. */
+   if( value != old ) astResetCache( this );
+}
+
+static void SetNegated( AstRegion *this, int value, int *status ){
+/*
+*  Name:
+*     SetNegated
+
+*  Purpose:
+*     Set a value for the Negated attribute of a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     void SetNegated( AstRegion *this, int value, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the protected astSetNegated
+*     method inherited from the Region class).
+
+*  Description:
+*     This function sets a new value for the Negated attribute of a Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     value
+*        The new attribute value.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int old;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the original attribute value */
+   old = astGetNegated( this );
+
+/* Invoke the set method inherited from the parent Region class */
+   (*parent_setnegated)( this, value, status );
+
+/* If the new value is not the same as the old value, indicate that we
+   need to re-calculate the cached information in the Box. */
+   if( value != old ) astResetCache( this );
+}
+
+static void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status ) {
+/*
+*  Name:
+*     SetRegFS
+
+*  Purpose:
+*     Stores a new FrameSet in a Region
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     Box method (over-rides the astSetRegFS method inherited from
+*     the Region class).
+
+*  Description:
+*     This function creates a new FrameSet and stores it in the supplied
+*     Region. The new FrameSet contains two copies of the supplied
+*     Frame, connected by a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     frm
+*        The Frame to use.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent method to store the FrameSet in the parent Region
+   structure. */
+   (* parent_setregfs)( this_region, frm, status );
+
+/* Indicate that we need to re-calculate the cached information in the Box. */
+   astResetCache( this_region );
+}
+
+static void SetUnc( AstRegion *this, AstRegion *unc, int *status ){
+/*
+*  Name:
+*     SetUnc
+
+*  Purpose:
+*     Store uncertainty information in a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     void SetUnc( AstRegion *this, AstRegion *unc, int *status )
+
+*  Class Membership:
+*     Box method (over-rides the astSetUnc method inherited from the
+*     Region class).
+
+*  Description:
+*     Each Region (of any class) can have an "uncertainty" which specifies
+*     the uncertainties associated with the boundary of the Region. This
+*     information is supplied in the form of a second Region. The uncertainty
+*     in any point on the boundary of a Region is found by shifting the
+*     associated "uncertainty" Region so that it is centred at the boundary
+*     point being considered. The area covered by the shifted uncertainty
+*     Region then represents the uncertainty in the boundary position.
+*     The uncertainty is assumed to be the same for all points.
+*
+*     The uncertainty is usually specified when the Region is created, but
+*     this function allows it to be changed at any time.
+
+*  Parameters:
+*     this
+*        Pointer to the Region which is to be assigned a new uncertainty.
+*     unc
+*        Pointer to the new uncertainty Region. This must be either a Box,
+*        a Circle or an Ellipse. A deep copy of the supplied Region will be
+*        taken, so subsequent changes to the uncertainty Region using the
+*        supplied pointer will have no effect on the Region "this".
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Invoke the astSetUnc method inherited from the parent Region class. */
+   (*parent_setunc)( this, unc, status );
+
+/* Indicate that we need to re-calculate the cached information in the Box. */
+   astResetCache( this );
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify the Mapping represented by a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Box method (over-rides the astSimplify method inherited
+*     from the Region class).
+
+*  Description:
+*     This function invokes the parent Region Simplify method, and then
+*     performs any further region-specific simplification.
+*
+*     If the Mapping from base to current Frame is not a UnitMap, this
+*     will include attempting to fit a new Region to the boundary defined
+*     in the current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the simplified Region. A cloned pointer to the
+*     supplied Region will be returned if no simplication could be
+*     performed.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstBox *box;                  /* Pointer to Box structure */
+   AstBox *newbox;               /* Pointer to simpler Box */
+   AstFrame *frm;                /* Pointer to current Frame */
+   AstMapping *map;              /* Base -> current Mapping */
+   AstMapping *result;           /* Result pointer to return */
+   AstPointSet *basemesh;        /* Mesh of base Frame positions */
+   AstPointSet *mesh;            /* Mesh of current Frame positions */
+   AstPointSet *ps1;             /* Box corners in base Frame */
+   AstPointSet *ps2;             /* Box corners in current Frame */
+   AstPolygon *newpoly;          /* New Polygon to replace Box */
+   AstRegion *prism;             /* Prism combining all axes */
+   AstRegion *new;               /* Pointer to simplified Region */
+   AstRegion *this;              /* Pointer to supplied Region structure */
+   AstRegion *unc;               /* Pointer to uncertainty Region */
+   double **ptr1;                /* Pointers to axis values in ps1 */
+   double **ptr2;                /* Pointers to axis values in ps2 */
+   double *constants;            /* Axis constants array */
+   double *lbnd;                 /* Lower bounds for new Box */
+   double *ubnd;                 /* Upper bounds for new Box */
+   double corners[8];            /* Box corners in current Frame */
+   double k;                     /* Axis constant value */
+   double lb;                    /* Lower axis bound */
+   double ub;                    /* Upper axis bound */
+   int *inperm;                  /* Input axis permutation array */
+   int *outperm;                 /* Output axis permutation array */
+   int closed;                   /* Was original Region closed? */
+   int feed;                     /* Source of value for current axis */
+   int right_handed;             /* Is the new Frame right handed? */
+   int ic;                       /* Axis index */
+   int isInterval;               /* Is the simplified Box an Interval */
+   int isNull;                   /* Is the simplified Box a NullRegion? */
+   int neg;                      /* Was original Region negated? */
+   int nin;                      /* No. of base Frame axes (Mapping inputs) */
+   int nout;                     /* No. of current Frame axes (Mapping outputs) */
+   int simpler;                  /* Has some simplication taken place? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_mapping;
+
+/* Invoke the parent Simplify method inherited from the Region class. This
+   will simplify the encapsulated FrameSet and uncertainty Region. */
+   new = (AstRegion *) (*parent_simplify)( this_mapping, status );
+
+/* Note if any simplification took place. This is assumed to be the case
+   if the pointer returned by the above call is different to the supplied
+   pointer. */
+   simpler = ( new != this );
+
+/* Get the Mapping from base to current Frame. */
+   map = astGetMapping( new->frameset, AST__BASE, AST__CURRENT );
+
+/* Get the number of inputs and outputs for the PermMap */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+
+/* If the Mapping from base to current Frame is a PermMap, we now explicitly
+   how to swap the axes of the Box to produce either a new Box or an
+   Interval. */
+   if( astIsAPermMap( map ) ){
+
+/* See if the new Box is Negated and/or Closed.*/
+      neg = astGetNegated( new );
+      closed = astGetClosed( new );
+
+/* Get a pointer to the Box structure. */
+      newbox = (AstBox *) new;
+
+/* Ensure cached information is up to date. */
+      Cache( newbox, 0, status );
+
+/* Get the input and output permutation arrays and the array of constants
+   from the PermMap. */
+      inperm = astGetInPerm( map );
+      outperm = astGetOutPerm( map );
+      constants = astGetConstants( map );
+
+/* Allocate memory to hold the axis bounds for the box in the current
+   Frame. */
+      lbnd = astMalloc( sizeof( double )*(size_t) nout );
+      ubnd = astMalloc( sizeof( double )*(size_t) nout );
+
+/* Check pointers can be used safely. */
+      if( astOK ) {
+
+/* Set flags indicating that the Box can be simplified, and does not result
+   in a NullRegion or an Interval. */
+         simpler = 1;
+         isNull = 0;
+         isInterval = 0;
+
+/* Check each output (which corresponds to an axis in the current Frame
+   of the Box). */
+         for( ic = 0; ic < nout; ic++ ) {
+
+/* Find the input (a base Frame axis) which feeds this output when the
+   forward transformation of the PermMap is used. */
+            feed = outperm[ ic ];
+
+/* If this output is fed a constant (i.e. does not depend on any of the
+   inputs), then the output axis limits are equal to this constant. */
+            if( feed < 0 ) {
+               lbnd[ ic ] = constants[ (-feed) - 1 ];
+               ubnd[ ic ] = constants[ (-feed) - 1 ];
+
+/* If this output is fed the constant AST__BAD (i.e. does not depend on any of
+   the inputs), then the output axis is unbounded and we will consequently
+   create an Interval rather than a Box. */
+            } else if( feed >= nin ) {
+               lbnd[ ic ] = AST__BAD;
+               ubnd[ ic ] = AST__BAD;
+
+/* If this output is fed the value of an input, then the output limits
+   are equal to the corresponding input limits. */
+            } else {
+               lbnd[ ic ] = newbox->centre[ feed ] - newbox->extent[ feed ];
+               ubnd[ ic ] = newbox->centre[ feed ] + newbox->extent[ feed ];
+            }
+
+/* If either bound is missing we will produce an Interval rather than a
+   Box. */
+            if( lbnd[ ic ] == AST__BAD || ubnd[ ic ] == AST__BAD ) {
+               isInterval = 1;
+            }
+         }
+
+/* If any base Frame axes are not present in the current Frame, we need
+   to check that the PermMap selects a slice which intersects the base
+   Frame box. If the slice does not intersect the base Frame box, then
+   the resulting Region willbe NullRegion. To do this, we check each
+   element in the input axis permutation array, "inperm". */
+         for( ic = 0; ic < nin; ic++ ) {
+
+/* Find the output (a current Frame axis) which feeds this input when the
+   inverse transformation of the PermMap is used. */
+            feed = inperm[ ic ];
+
+/* If this input is fed a constant (i.e. does not depend on any of the
+   outputs), then we must check that this constant is within the range of
+   axis values covered by the Box. If not then the simplified Box represents
+   a slice through the coordinate system which does not pass through the
+   original Box. In this case the simplified Region is a NullRegion
+   instead of a Box. */
+            if( feed < 0 ) {
+               k = constants[ (-feed) - 1 ];
+               lb = newbox->centre[ ic ] - newbox->extent[ ic ];
+               ub = newbox->centre[ ic ] + newbox->extent[ ic ];
+
+               if( closed == neg ) {
+                  isNull = ( k <= lb || k >= ub );
+               } else {
+                  isNull = ( k < lb || k > ub );
+               }
+
+/* If this input is fed the constant AST__BAD (i.e. does not depend on any of
+   the outputs), then the input axis is unbounded and will consequently
+   extend beyond the Box. In this case the simplified Region will be a
+   NullRegion. */
+            } else if( feed >= nout ) {
+               isNull = 1;
+
+/* If this input is fed the value of an output, then check that the
+   relationship is bi-directional. If it is not, we cannot simplify the
+   Box. */
+            } else if( outperm[ feed ] != ic ) {
+               simpler = 0;
+               break;
+            }
+         }
+
+/* If the Box can be simplified, create a new Region of an appropriate
+   class. */
+         if( simpler ) {
+
+/* Get any uncertainty from the supplied Box (it will already have been
+   simplified by the parent Simplify method). */
+            unc = astTestUnc( new ) ? astGetUncFrm( new, AST__CURRENT ) : NULL;
+
+/* Get the Frame represented by the Region. */
+            frm = astGetFrame( new->frameset, AST__CURRENT );
+
+/* We can now replace the original Region with the simplified Region so annul
+   the original pointer. */
+            new = astAnnul( new );
+
+/* Create a new Region of the required class. */
+            if( isNull ) {
+               new = (AstRegion *) astNullRegion( frm, unc, "", status );
+
+            } else if( isInterval ){
+               new = (AstRegion *) astInterval( frm, lbnd, ubnd, unc, "", status );
+
+            } else {
+               new = (AstRegion *) astBox( frm, 1, lbnd, ubnd, unc, "", status );
+            }
+
+/* If the original box was Negated. Negate the new one. */
+            if( neg ) astNegate( new );
+
+/* Free resources */
+            if( unc ) unc = astAnnul( unc );
+            frm = astAnnul( frm );
+
+         }
+      }
+
+/* Free resources */
+      inperm = astFree( inperm );
+      outperm = astFree( outperm );
+      constants = astFree( constants );
+      lbnd = astFree( lbnd );
+      ubnd = astFree( ubnd );
+
+/* If the Mapping from base to current Frame is not a PermMap or a UnitMap, we
+   attempt to simplify the Box by re-defining it within its current Frame.
+   Transforming the box from its base to its current FRame may result in
+   the region no longer being a box. We test this by transforming a set of
+   bounds on the box boundary. */
+   } else if( !astIsAUnitMap( map ) ){
+
+/* Get pointer to current Frame. */
+      frm = astGetFrame( new->frameset, AST__CURRENT );
+
+/* Get a mesh of points covering the Box in its current Frame. */
+      mesh = astRegMesh( new );
+
+/* Get the Region describing the positional uncertainty within the Box in
+   its current Frame. */
+      unc = astGetUncFrm( new, AST__CURRENT );
+
+/* Find the best fitting box (defined in the current Frame) through these
+   points */
+      newbox = BestBox( frm, mesh, unc, status );
+
+/* See if all points within this mesh fall on the boundary of the best
+   fitting Box, to within the uncertainty of the Region. */
+      if( newbox && astRegPins( newbox, mesh, NULL, NULL ) ) {
+
+/* If so, check that the inverse is true (we need to transform the
+   simplified boxes mesh into the base Frame of he original box for use by
+   astRegPins). */
+         (void) astAnnul( mesh );
+         mesh = astRegMesh( newbox );
+         basemesh = astTransform( map, mesh, 0, NULL );
+         if( astRegPins( new, basemesh, NULL, NULL ) ) {
+
+/* If so, use the new Box in place of the original Box. */
+            (void) astAnnul( new );
+            new = astClone( newbox );
+            simpler = 1;
+         }
+
+/* Free resources. */
+         basemesh = astAnnul( basemesh );
+
+/* If the transformed Box is not itself a Box, see if it can be
+   represented accurately by a Polygon. This is only possible for
+   2-dimensional Boxes. */
+      } else if( nin == 2 && nout == 2 ) {
+
+/* Create a PointSet holding the base Frame axis values at the four
+   corners of the Box. */
+         ps1 = astPointSet( 4, 2, "", status );
+         ptr1 = astGetPoints( ps1 );
+         if( astOK ) {
+            box = (AstBox *) new;
+            Cache( box, 0, status );
+
+/* The order in which the polygon vertices are stored determines whether
+   the interior or exterior of the polygon forms the inside of the
+   Region. We want the inside to be the interior. First create a Polygon
+   in which the vertices are stored in clockwise order within the
+   new coordinate Frame. If the new Polygon is not bounded, use
+   anti-clockwise order. */
+            for( right_handed = 0; right_handed < 2; right_handed++ ) {
+
+               if( right_handed ) {
+                  ptr1[ 0 ][ 0 ] = box->centre[ 0 ] - box->extent[ 0 ];
+                  ptr1[ 1 ][ 0 ] = box->centre[ 1 ] + box->extent[ 1 ];
+
+                  ptr1[ 0 ][ 1 ] = box->centre[ 0 ] - box->extent[ 0 ];
+                  ptr1[ 1 ][ 1 ] = box->centre[ 1 ] - box->extent[ 1 ];
+
+                  ptr1[ 0 ][ 2 ] = box->centre[ 0 ] + box->extent[ 0 ];
+                  ptr1[ 1 ][ 2 ] = box->centre[ 1 ] - box->extent[ 1 ];
+
+                  ptr1[ 0 ][ 3 ] = box->centre[ 0 ] + box->extent[ 0 ];
+                  ptr1[ 1 ][ 3 ] = box->centre[ 1 ] + box->extent[ 1 ];
+
+               } else {
+                  ptr1[ 0 ][ 3 ] = box->centre[ 0 ] - box->extent[ 0 ];
+                  ptr1[ 1 ][ 3 ] = box->centre[ 1 ] + box->extent[ 1 ];
+
+                  ptr1[ 0 ][ 2 ] = box->centre[ 0 ] - box->extent[ 0 ];
+                  ptr1[ 1 ][ 2 ] = box->centre[ 1 ] - box->extent[ 1 ];
+
+                  ptr1[ 0 ][ 1 ] = box->centre[ 0 ] + box->extent[ 0 ];
+                  ptr1[ 1 ][ 1 ] = box->centre[ 1 ] - box->extent[ 1 ];
+
+                  ptr1[ 0 ][ 0 ] = box->centre[ 0 ] + box->extent[ 0 ];
+                  ptr1[ 1 ][ 0 ] = box->centre[ 1 ] + box->extent[ 1 ];
+               }
+
+/* Transform the Box corners into the current Frame. */
+               ps2 = astTransform( map, ps1, 1, NULL );
+               ptr2 = astGetPoints( ps2 );
+               if( astOK ) {
+
+/* Create a Polygon from these points. */
+                  for( ic = 0; ic < 4; ic++ ) {
+                     corners[ ic ] = ptr2[ 0 ][ ic ];
+                     corners[ 4 + ic ] = ptr2[ 1 ][ ic ];
+                  }
+                  newpoly = astPolygon( frm, 4, 4, corners, unc, "", status );
+
+/* If the Polygon is bounded, break out of the loop. */
+                  if( astGetBounded( newpoly ) ) break;
+               }
+
+/* Free resources. */
+               newpoly = astAnnul( newpoly );
+               ps2 = astAnnul( ps2 );
+            }
+
+/* See if all points within the Box mesh fall on the boundary of this
+   Polygon, to within the uncertainty of the Region. */
+            if( astRegPins( newpoly, mesh, NULL, NULL ) ) {
+
+/* If so, use the new Polygon in place of the original Box. */
+               (void) astAnnul( new );
+               new = astClone( newpoly );
+               simpler = 1;
+            }
+
+/* Free resources. */
+            newpoly = astAnnul( newpoly );
+         }
+
+         ps1 = astAnnul( ps1 );
+      }
+
+/* If we have yet been able to produce a simpler region, we now try
+   splitting the Box into two separate Boxes defined in separate
+   coordinate Frames. If either of these two Boxes can be simplified,
+   create a Prism containing the two simplified Boxes, and attempt to
+   simplify the Prism. Otherwise a clone of "new" is returned by
+   astConvertToPrism. */
+      if( !simpler ) {
+         prism = astConvertToPrism( new );
+
+         if( prism != new ) {
+            simpler = 1;
+            (void) astAnnul( new );
+            new = prism;
+
+         } else {
+            prism = astAnnul( prism );
+         }
+      }
+
+      frm = astAnnul( frm );
+      mesh = astAnnul( mesh );
+      unc = astAnnul( unc );
+      if( newbox ) newbox = astAnnul( newbox );
+   }
+
+   map = astAnnul( map );
+
+/* If any simplification could be performed, copy Region attributes from
+   the supplied Region to the returned Region, and return a pointer to it.
+   If the supplied Region had no uncertainty, ensure the returned Region
+   has no uncertainty. Otherwise, return a clone of the supplied pointer. */
+   if( simpler ){
+      astRegOverlay( new, this, 1 );
+      result = (AstMapping *) new;
+
+   } else {
+      new = astAnnul( new );
+      result = astClone( this );
+   }
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a Box to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     Box member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a Box and a set of points encapsulated in a
+*     PointSet and transforms the points by setting axis values to
+*     AST__BAD for all points which are outside the region. Points inside
+*     the region are copied unchanged from input to output.
+
+*  Parameters:
+*     this
+*        Pointer to the Box.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  The forward and inverse transformations are identical for a
+*     Region.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of axes in the Frame represented by the Box.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstBox *box;                  /* Pointer to Box */
+   AstFrame *frm;                /* Pointer to base Frame in FrameSet */
+   AstPointSet *pset_tmp;        /* Pointer to PointSet holding base Frame positions*/
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstRegion *reg;               /* Pointer to Region */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double **ptr_tmp;             /* Pointer to base Frame coordinate data */
+   double axval;                 /* Input axis value */
+   int closed;                   /* Is the boundary part of the Region? */
+   int coord;                    /* Zero-based index for coordinates */
+   int ncoord_out;               /* No. of coordinates per output point */
+   int ncoord_tmp;               /* No. of coordinates per base Frame point */
+   int neg;                      /* Is the Box negated?*/
+   int npoint;                   /* No. of points */
+   int ok;                       /* Is the point inside the Region? */
+   int point;                    /* Loop counter for points */
+
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain pointers to the Regionand to the Box. */
+   reg = (AstRegion *) this;
+   box = (AstBox *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Region class. This function validates
+   all arguments and generates an output PointSet if necessary,
+   containing a copy of the input PointSet. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* First use the encapsulated FrameSet to transform the supplied positions
+   from the current Frame in the encapsulated FrameSet (the Frame
+   represented by the Region), to the base Frame (the Frame in which the
+   Region is defined). This call also returns a pointer to the base Frame
+   of the encapsulated FrameSet. Note, the returned pointer may be a
+   clone of the "in" pointer, and so we must be carefull not to modify the
+   contents of the returned PointSet. */
+   pset_tmp = astRegTransform( reg, in, 0, NULL, &frm );
+
+/* Determine the numbers of points and coordinates per point from the base
+   Frame PointSet and obtain pointers for accessing the base Frame and output
+   coordinate values. */
+   npoint = astGetNpoint( pset_tmp );
+   ncoord_tmp = astGetNcoord( pset_tmp );
+   ptr_tmp = astGetPoints( pset_tmp );
+   ncoord_out = astGetNcoord( result );
+   ptr_out = astGetPoints( result );
+
+/* See if the boundary is part of the Region. */
+   closed = astGetClosed( reg );
+
+/* See if the Box is negated */
+   neg = astGetNegated( reg );
+
+/* Ensire the cached information is up to date. */
+   Cache( box, 1, status );
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+
+/* The logic used to combine axis values for negated and un-negated boxes
+   is different. For negated boxes, a position is in the region if *any
+   one* axis is not "close" to the box centre. */
+      if( neg ) {
+
+/* Loop round each point */
+         for ( point = 0; point < npoint; point++ ) {
+
+/* Assume the point is outside the Region (since the Region is
+   negated, this means assuming it is within the box). */
+            ok = 0;
+
+/* Loop round each axis value at this point. We break as soon as we find
+   a bad axis value or an axis value which is outside the box. */
+            for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+
+/* The point is definiately not in the Region if any input axis value is bad. */
+               axval = ptr_tmp[ coord ][ point ];
+               if( axval == AST__BAD ) {
+                  break;
+
+/* Otherwise check the current axis value, depending on whether the
+   boundary is included in the Region or not. Break as soon as an axis
+   value is found which is outside the box limits (i.e. in the Region). */
+               } else if( !astAxIn( frm, coord, box->lo[ coord ], box->hi[ coord ],
+                                    axval, !closed ) ) {
+                  ok = 1;
+                  break;
+               }
+            }
+
+/* If this point is not inside the Region store bad output axis values. */
+            if( !ok ) {
+               for ( coord = 0; coord < ncoord_out; coord++ ) {
+                  ptr_out[ coord ][ point ] = AST__BAD;
+               }
+            }
+         }
+
+/* For un-negated boxes, a position is in the region if *all* axes are "close"
+   to the box centre. */
+      } else {
+
+/* Loop round each point */
+         for ( point = 0; point < npoint; point++ ) {
+
+/* Assume the point is within the Region (i.e.inside the box). */
+            ok = 1;
+
+/* Loop round each axis value at this point. We break when we find a bad
+   input point or if any axis value is outside the box. */
+            for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+
+/* The point is not in the Region if any input axis value is bad. */
+               axval = ptr_tmp[ coord ][ point ];
+               if( axval == AST__BAD ) {
+                  ok = 0;
+                  break;
+
+/* Otherwise check the current axis value, depending on whether the
+   boundary is included in the Region or not. Break as soon as an axis
+   value is found which is outside the box limits (i.e. outside the Region). */
+               } else if( !astAxIn( frm, coord, box->lo[ coord ], box->hi[ coord ],
+                                    axval, closed ) ) {
+                  ok = 0;
+                  break;
+               }
+            }
+
+/* If this point is outside the Region store bad output axis values. */
+            if( !ok ) {
+               for ( coord = 0; coord < ncoord_out; coord++ ) {
+                  ptr_out[ coord ][ point ] = AST__BAD;
+               }
+            }
+         }
+      }
+   }
+
+/* Free resources */
+   pset_tmp = astAnnul( pset_tmp );
+   frm = astAnnul( frm );
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Region objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Region objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstBox *in;                /* Pointer to input Box */
+   AstBox *out;               /* Pointer to output Box */
+   int nax;                   /* Number of base Frame axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Boxs. */
+   in = (AstBox *) objin;
+   out = (AstBox *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output Box. */
+   out->extent = NULL;
+   out->centre = NULL;
+   out->lo = NULL;
+   out->hi = NULL;
+   out->geolen = NULL;
+
+/* Copy dynamic memory contents */
+   nax = astGetNin( ((AstRegion *) in)->frameset );
+   out->extent = astStore( NULL, in->extent,
+                           sizeof( double )*(size_t)nax );
+   out->centre = astStore( NULL, in->centre,
+                           sizeof( double )*(size_t)nax );
+   out->lo = astStore( NULL, in->lo,
+                           sizeof( double )*(size_t)nax );
+   out->hi = astStore( NULL, in->hi,
+                           sizeof( double )*(size_t)nax );
+   out->geolen = astStore( NULL, in->geolen,
+                           sizeof( double )*(size_t)nax );
+}
+
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Box objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Box objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstBox *this;                 /* Pointer to Box */
+
+/* Obtain a pointer to the Box structure. */
+   this = (AstBox *) obj;
+
+/* Annul all resources. */
+   this->extent = astFree( this->extent );
+   this->centre = astFree( this->centre );
+   this->lo = astFree( this->lo );
+   this->hi = astFree( this->hi );
+   this->geolen = astFree( this->geolen );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Box objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Box class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Box whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Write out values representing the instance variables for the
+   Box class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* There are no values to write, so return without further action. */
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsABox and astCheckBox functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Box,Region)
+astMAKE_CHECK(Box)
+
+AstBox *astBox_( void *frame_void, int form, const double point1[],
+                 const double point2[], AstRegion *unc, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astBox
+f     AST_BOX
+
+*  Purpose:
+*     Create a Box.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "box.h"
+c     AstBox *astBox( AstFrame *frame, int form, const double point1[],
+c                     const double point2[], AstRegion *unc,
+c                     const char *options, ... )
+f     RESULT = AST_BOX( FRAME, FORM, POINT1, POINT2, UNC, OPTIONS, STATUS )
+
+*  Class Membership:
+*     Box constructor.
+
+*  Description:
+*     This function creates a new Box and optionally initialises its
+*     attributes.
+*
+*     The Box class implements a Region which represents a box with sides
+*     parallel to the axes of a Frame (i.e. an area which encloses a given
+*     range of values on each axis). A Box is similar to an Interval, the
+*     only real difference being that the Interval class allows some axis
+*     limits to be unspecified. Note, a Box will only look like a box if
+*     the Frame geometry is approximately flat. For instance, a Box centred
+*     close to a pole in a SkyFrame will look more like a fan than a box
+*     (the Polygon class can be used to create a box-like region close to a
+*     pole).
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        A pointer to the Frame in which the region is defined. A deep
+*        copy is taken of the supplied Frame. This means that any
+*        subsequent changes made to the Frame using the supplied pointer
+*        will have no effect the Region.
+c     form
+f     FORM = INTEGER (Given)
+*        Indicates how the box is described by the remaining parameters.
+*        A value of zero indicates that the box is specified by a centre
+*        position and a corner position. A value of one indicates that the
+*        box is specified by a two opposite corner positions.
+c     point1
+f     POINT1( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). If
+c        "form"
+f        FORM
+*        is zero, this array should contain the coordinates at the centre of
+*        the box.
+c        If "form"
+f        If FORM
+*        is one, it should contain the coordinates at the corner of the box
+*        which is diagonally opposite the corner specified by
+c        "point2".
+f        POINT2.
+c     point2
+f     POINT2( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates at any corner of the
+*        box.
+c     unc
+f     UNC = INTEGER (Given)
+*        An optional pointer to an existing Region which specifies the
+*        uncertainties associated with the boundary of the Box being created.
+*        The uncertainty in any point on the boundary of the Box is found by
+*        shifting the supplied "uncertainty" Region so that it is centred at
+*        the boundary point being considered. The area covered by the
+*        shifted uncertainty Region then represents the uncertainty in the
+*        boundary position. The uncertainty is assumed to be the same for
+*        all points.
+*
+*        If supplied, the uncertainty Region must be of a class for which
+*        all instances are centro-symetric (e.g. Box, Circle, Ellipse, etc.)
+*        or be a Prism containing centro-symetric component Regions. A deep
+*        copy of the supplied Region will be taken, so subsequent changes to
+*        the uncertainty Region using the supplied pointer will have no
+*        effect on the created Box. Alternatively,
+f        a null Object pointer (AST__NULL)
+c        a NULL Object pointer
+*        may be supplied, in which case a default uncertainty is used
+*        equivalent to a box 1.0E-6 of the size of the Box being created.
+*
+*        The uncertainty Region has two uses: 1) when the
+c        astOverlap
+f        AST_OVERLAP
+*        function compares two Regions for equality the uncertainty
+*        Region is used to determine the tolerance on the comparison, and 2)
+*        when a Region is mapped into a different coordinate system and
+*        subsequently simplified (using
+c        astSimplify),
+f        AST_SIMPLIFY),
+*        the uncertainties are used to determine if the transformed boundary
+*        can be accurately represented by a specific shape of Region.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Box. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Box. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astBox()
+f     AST_BOX = INTEGER
+*        A pointer to the new Box.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstBox *new;                  /* Pointer to new Box */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the supplied Frame structure. */
+   frame = astCheckFrame( frame_void );
+
+/* Initialise the Box, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitBox( NULL, sizeof( AstBox ), !class_init, &class_vtab,
+                     "Box", frame, form, point1, point2, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Box's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Box. */
+   return new;
+}
+
+AstBox *astBoxId_( void *frame_void, int form, const double point1[],
+                   const double point2[], void *unc_void, const char *options,
+                   ... ) {
+/*
+*  Name:
+*     astBoxId_
+
+*  Purpose:
+*     Create a Box.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstBox *astBoxId_( AstFrame *frame, int form, const double point1[],
+*                        const double point2[], AstRegion *unc,
+*                        const char *options, ... )
+
+*  Class Membership:
+*     Box constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astBox constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astBox_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astBox_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astBox_.
+
+*  Returned Value:
+*     The ID value associated with the new Box.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstBox *new;                  /* Pointer to new Box */
+   AstRegion *unc;               /* Pointer to Region structure */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Frame pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Frame. */
+   frame = astVerifyFrame( astMakePointer( frame_void ) );
+
+/* Obtain a Region pointer from the supplied "unc" ID and validate the
+   pointer to ensure it identifies a valid Region . */
+   unc = unc_void ? astCheckRegion( astMakePointer( unc_void ) ) : NULL;
+
+/* Initialise the Box, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitBox( NULL, sizeof( AstBox ), !class_init, &class_vtab,
+                     "Box", frame, form, point1, point2, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Box's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new Box. */
+   return astMakeId( new );
+}
+
+AstBox *astInitBox_( void *mem, size_t size, int init, AstBoxVtab *vtab,
+                     const char *name, AstFrame *frame, int form,
+                     const double point1[], const double point2[],
+                     AstRegion *unc, int *status ) {
+/*
+*+
+*  Name:
+*     astInitBox
+
+*  Purpose:
+*     Initialise a Box.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstBox *astInitBox_( void *mem, size_t size, int init, AstBoxVtab *vtab,
+*                         const char *name, AstFrame *frame, int form,
+*                         const double point1[], const double point2[],
+*                         AstRegion *unc )
+
+*  Class Membership:
+*     Box initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Box object. It allocates memory (if necessary) to accommodate
+*     the Box plus any additional data associated with the derived class.
+*     It then initialises a Box structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a Box at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Box is to be initialised.
+*        This must be of sufficient size to accommodate the Box data
+*        (sizeof(Box)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Box (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Box
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Box's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Box.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     frame
+*        A pointer to the Frame in which the region is defined.
+*     form
+*        Indicates how the box is described by the remaining parameters.
+*        A value of zero indicates that the box is specified by a centre
+*        position and a corner position. A value of one indicates that the
+*        box is specified by a two opposite corner positions.
+*     point1
+*        An array of double, with one element for each Frame axis (Naxes
+*        attribute). If "form" is zero, this array should contain the
+*        coordinates at the centre of the box. If "form" is one, it should
+*        contain the coordinates at the corner of the box which is diagonally
+*        opposite the corner specified by "point2".
+*     point2
+*        An array of double, with one element for each Frame axis (Naxes
+*        attribute) containing the coordinates at any of the corners of
+*        the box.
+*     unc
+*        A pointer to a Region which specifies the uncertainty in the
+*        supplied positions (all points on the boundary of the new Box
+*        being initialised are assumed to have the same uncertainty). A NULL
+*        pointer can be supplied, in which case default uncertainties equal to
+*        1.0E-6 of the dimensions of the new Box's bounding box are used.
+*        If an uncertainty Region is supplied, it must be either a Box, a
+*        Circle or an Ellipse, and its encapsulated Frame must be related
+*        to the Frame supplied for parameter "frame" (i.e. astConvert
+*        should be able to find a Mapping between them). Two positions
+*        the "frame" Frame are considered to be co-incident if their
+*        uncertainty Regions overlap. The centre of the supplied
+*        uncertainty Region is immaterial since it will be re-centred on the
+*        point being tested before use. A deep copy is taken of the supplied
+*        Region.
+
+*  Returned Value:
+*     A pointer to the new Box.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstBox *new;              /* Pointer to new Box */
+   AstPointSet *pset;        /* PointSet to pass to Region initialiser */
+   double **ptr;             /* Pointer to coords data in pset */
+   int i;                    /* axis index */
+   int nc;                   /* No. of axes */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitBoxVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Get the number of axis values required for each position. */
+   nc = astGetNaxes( frame );
+
+/* Create a PointSet to hold the supplied values, and get points to the
+   data arrays. */
+   pset = astPointSet( 2, nc, "", status );
+   ptr = astGetPoints( pset );
+
+/* Copy the supplied coordinates into the PointSet, checking that no bad
+   values have been supplied. */
+   for( i = 0; astOK && i < nc; i++ ) {
+      if( point1[ i ] == AST__BAD ) {
+         astError( AST__BADIN, "astInitBox(%s): The value of axis %d is "
+                   "undefined at point 1 of the box.", status, name, i + 1 );
+         break;
+      }
+      if( point2[ i ] == AST__BAD ) {
+         astError( AST__BADIN, "astInitBox(%s): The value of axis %d is "
+                   "undefined at point 2 of the box.", status, name, i + 1 );
+         break;
+      }
+      ptr[ i ][ 0 ] = point1[ i ];
+      ptr[ i ][ 1 ] = point2[ i ];
+   }
+
+/* If two corners were supplied, find and store the centre. */
+   if( form == 1 ) {
+      for( i = 0; i < nc; i++ ) {
+         ptr[ i ][ 0 ] = 0.5*( point1[ i ] + point2[ i ] );
+      }
+   }
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Initialise a Region structure (the parent class) as the first component
+   within the Box structure, allocating memory if necessary. */
+      new = (AstBox *) astInitRegion( mem, size, 0, (AstRegionVtab *) vtab,
+                                      name, frame, pset, unc );
+
+      if ( astOK ) {
+
+/* Initialise the Box data. */
+/* ------------------------ */
+         new->extent = NULL;
+         new->centre = NULL;
+         new->lo = NULL;
+         new->hi = NULL;
+         new->geolen = NULL;
+         new->stale = 1;
+
+/* If an error occurred, clean up by deleting the new Box. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Free resources. */
+   pset = astAnnul( pset );
+
+/* Return a pointer to the new Box. */
+   return new;
+}
+
+AstBox *astLoadBox_( void *mem, size_t size, AstBoxVtab *vtab,
+                     const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadBox
+
+*  Purpose:
+*     Load a Box.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstBox *astLoadBox( void *mem, size_t size, AstBoxVtab *vtab,
+*                         const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     Box loader.
+
+*  Description:
+*     This function is provided to load a new Box using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Box structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Box at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Box is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Box data (sizeof(Box)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Box (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Box structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstBox) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Box. If this is NULL, a pointer
+*        to the (static) virtual function table for the Box class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Box" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Box.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstBox *new;              /* Pointer to the new Box */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Box. In this case the
+   Box belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstBox );
+      vtab = &class_vtab;
+      name = "Box";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+          astInitBoxVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Box. */
+   new = astLoadRegion( mem, size, (AstRegionVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Box" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* There are no values to read. */
+/* ---------------------------- */
+
+/* Initialise Box data */
+      new->extent = NULL;
+      new->centre = NULL;
+      new->lo = NULL;
+      new->hi = NULL;
+      new->geolen = NULL;
+      new->stale = 1;
+
+/* If an error occurred, clean up by deleting the new Box. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Box pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+void astBoxPoints_( AstBox *this, double *centre, double *corner,
+                    int *status) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Box,BoxPoints))( this, centre, corner, status );
+   return;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ast/box.h b/ast/box.h
new file mode 100644
index 0000000..46dcf6c
--- /dev/null
+++ b/ast/box.h
@@ -0,0 +1,234 @@
+#if !defined( BOX_INCLUDED ) /* Include this file only once */
+#define BOX_INCLUDED
+/*
+*+
+*  Name:
+*     box.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Box class.
+
+*  Invocation:
+*     #include "box.h"
+
+*  Description:
+*     This include file defines the interface to the Box class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The Box class implement a Region which represents a simple interval
+*     on each axis of the encapsulated Frame
+
+*  Inheritance:
+*     The Box class inherits from the Region class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-MAR-2003 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "region.h"              /* Coordinate regions (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* Box structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstBox {
+
+/* Attributes inherited from the parent class. */
+   AstRegion region;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *extent;               /* Original axis half-widths */
+   double *centre;               /* Box centre coords */
+   double *lo;                   /* Low limits */
+   double *hi;                   /* High limits */
+   double *geolen;               /* Geodesic half-dimensions of box */
+   int stale;                    /* Is other info out of date? */
+} AstBox;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstBoxVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstRegionVtab region_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   void (* BoxPoints)( AstBox *, double *, double *, int *);
+} AstBoxVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstBoxGlobals {
+   AstBoxVtab Class_Vtab;
+   int Class_Init;
+} AstBoxGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitBoxGlobals_( AstBoxGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Box)          /* Check class membership */
+astPROTO_ISA(Box)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstBox *astBox_( void *, int, const double[], const double[], AstRegion *, const char *, int *, ...);
+#else
+AstBox *astBoxId_( void *, int, const double[], const double[], AstRegion *, const char *, ... )__attribute__((format(printf,6,7)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstBox *astInitBox_( void *, size_t, int, AstBoxVtab *,
+                     const char *, AstFrame *, int, const double[],
+                     const double[], AstRegion *, int * );
+
+/* Vtab initialiser. */
+void astInitBoxVtab_( AstBoxVtab *, const char *, int * );
+
+/* Loader. */
+AstBox *astLoadBox_( void *, size_t, AstBoxVtab *,
+                     const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+void astBoxPoints_( AstBox *, double *, double *, int *);
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckBox(this) astINVOKE_CHECK(Box,this,0)
+#define astVerifyBox(this) astINVOKE_CHECK(Box,this,1)
+
+/* Test class membership. */
+#define astIsABox(this) astINVOKE_ISA(Box,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astBox astINVOKE(F,astBox_)
+#else
+#define astBox astINVOKE(F,astBoxId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitBox(mem,size,init,vtab,name,frame,form,p1,p2,unc) \
+astINVOKE(O,astInitBox_(mem,size,init,vtab,name,frame,form,p1,p2,unc,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitBoxVtab(vtab,name) astINVOKE(V,astInitBoxVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadBox(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadBox_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckBox to validate Box pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astBoxPoints(this,centre,corner) astINVOKE(V,astBoxPoints_(astCheckBox(this),centre,corner,STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/build-aux/compile b/ast/build-aux/compile
new file mode 100755
index 0000000..531136b
--- /dev/null
+++ b/ast/build-aux/compile
@@ -0,0 +1,347 @@
+#! /bin/sh
+# Wrapper for compilers which do not understand '-c -o'.
+
+scriptversion=2012-10-14.11; # UTC
+
+# Copyright (C) 1999-2013 Free Software Foundation, Inc.
+# Written by Tom Tromey <[email-masked]>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <[email-masked]> or send patches to
+# <[email-masked]>.
+
+nl='
+'
+
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent tools from complaining about whitespace usage.
+IFS=" ""	$nl"
+
+file_conv=
+
+# func_file_conv build_file lazy
+# Convert a $build file to $host form and store it in $file
+# Currently only supports Windows hosts. If the determined conversion
+# type is listed in (the comma separated) LAZY, no conversion will
+# take place.
+func_file_conv ()
+{
+  file=$1
+  case $file in
+    / | /[!/]*) # absolute file, and not a UNC file
+      if test -z "$file_conv"; then
+	# lazily determine how to convert abs files
+	case `uname -s` in
+	  MINGW*)
+	    file_conv=mingw
+	    ;;
+	  CYGWIN*)
+	    file_conv=cygwin
+	    ;;
+	  *)
+	    file_conv=wine
+	    ;;
+	esac
+      fi
+      case $file_conv/,$2, in
+	*,$file_conv,*)
+	  ;;
+	mingw/*)
+	  file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'`
+	  ;;
+	cygwin/*)
+	  file=`cygpath -m "$file" || echo "$file"`
+	  ;;
+	wine/*)
+	  file=`winepath -w "$file" || echo "$file"`
+	  ;;
+      esac
+      ;;
+  esac
+}
+
+# func_cl_dashL linkdir
+# Make cl look for libraries in LINKDIR
+func_cl_dashL ()
+{
+  func_file_conv "$1"
+  if test -z "$lib_path"; then
+    lib_path=$file
+  else
+    lib_path="$lib_path;$file"
+  fi
+  linker_opts="$linker_opts -LIBPATH:$file"
+}
+
+# func_cl_dashl library
+# Do a library search-path lookup for cl
+func_cl_dashl ()
+{
+  lib=$1
+  found=no
+  save_IFS=$IFS
+  IFS=';'
+  for dir in $lib_path $LIB
+  do
+    IFS=$save_IFS
+    if $shared && test -f "$dir/$lib.dll.lib"; then
+      found=yes
+      lib=$dir/$lib.dll.lib
+      break
+    fi
+    if test -f "$dir/$lib.lib"; then
+      found=yes
+      lib=$dir/$lib.lib
+      break
+    fi
+    if test -f "$dir/lib$lib.a"; then
+      found=yes
+      lib=$dir/lib$lib.a
+      break
+    fi
+  done
+  IFS=$save_IFS
+
+  if test "$found" != yes; then
+    lib=$lib.lib
+  fi
+}
+
+# func_cl_wrapper cl arg...
+# Adjust compile command to suit cl
+func_cl_wrapper ()
+{
+  # Assume a capable shell
+  lib_path=
+  shared=:
+  linker_opts=
+  for arg
+  do
+    if test -n "$eat"; then
+      eat=
+    else
+      case $1 in
+	-o)
+	  # configure might choose to run compile as 'compile cc -o foo foo.c'.
+	  eat=1
+	  case $2 in
+	    *.o | *.[oO][bB][jJ])
+	      func_file_conv "$2"
+	      set x "$@" -Fo"$file"
+	      shift
+	      ;;
+	    *)
+	      func_file_conv "$2"
+	      set x "$@" -Fe"$file"
+	      shift
+	      ;;
+	  esac
+	  ;;
+	-I)
+	  eat=1
+	  func_file_conv "$2" mingw
+	  set x "$@" -I"$file"
+	  shift
+	  ;;
+	-I*)
+	  func_file_conv "${1#-I}" mingw
+	  set x "$@" -I"$file"
+	  shift
+	  ;;
+	-l)
+	  eat=1
+	  func_cl_dashl "$2"
+	  set x "$@" "$lib"
+	  shift
+	  ;;
+	-l*)
+	  func_cl_dashl "${1#-l}"
+	  set x "$@" "$lib"
+	  shift
+	  ;;
+	-L)
+	  eat=1
+	  func_cl_dashL "$2"
+	  ;;
+	-L*)
+	  func_cl_dashL "${1#-L}"
+	  ;;
+	-static)
+	  shared=false
+	  ;;
+	-Wl,*)
+	  arg=${1#-Wl,}
+	  save_ifs="$IFS"; IFS=','
+	  for flag in $arg; do
+	    IFS="$save_ifs"
+	    linker_opts="$linker_opts $flag"
+	  done
+	  IFS="$save_ifs"
+	  ;;
+	-Xlinker)
+	  eat=1
+	  linker_opts="$linker_opts $2"
+	  ;;
+	-*)
+	  set x "$@" "$1"
+	  shift
+	  ;;
+	*.cc | *.CC | *.cxx | *.CXX | *.[cC]++)
+	  func_file_conv "$1"
+	  set x "$@" -Tp"$file"
+	  shift
+	  ;;
+	*.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO])
+	  func_file_conv "$1" mingw
+	  set x "$@" "$file"
+	  shift
+	  ;;
+	*)
+	  set x "$@" "$1"
+	  shift
+	  ;;
+      esac
+    fi
+    shift
+  done
+  if test -n "$linker_opts"; then
+    linker_opts="-link$linker_opts"
+  fi
+  exec "$@" $linker_opts
+  exit 1
+}
+
+eat=
+
+case $1 in
+  '')
+     echo "$0: No command.  Try '$0 --help' for more information." 1>&2
+     exit 1;
+     ;;
+  -h | --h*)
+    cat <<\EOF
+Usage: compile [--help] [--version] PROGRAM [ARGS]
+
+Wrapper for compilers which do not understand '-c -o'.
+Remove '-o dest.o' from ARGS, run PROGRAM with the remaining
+arguments, and rename the output as expected.
+
+If you are trying to build a whole package this is not the
+right script to run: please start by reading the file 'INSTALL'.
+
+Report bugs to <[email-masked]>.
+EOF
+    exit $?
+    ;;
+  -v | --v*)
+    echo "compile $scriptversion"
+    exit $?
+    ;;
+  cl | *[/\\]cl | cl.exe | *[/\\]cl.exe )
+    func_cl_wrapper "$@"      # Doesn't return...
+    ;;
+esac
+
+ofile=
+cfile=
+
+for arg
+do
+  if test -n "$eat"; then
+    eat=
+  else
+    case $1 in
+      -o)
+	# configure might choose to run compile as 'compile cc -o foo foo.c'.
+	# So we strip '-o arg' only if arg is an object.
+	eat=1
+	case $2 in
+	  *.o | *.obj)
+	    ofile=$2
+	    ;;
+	  *)
+	    set x "$@" -o "$2"
+	    shift
+	    ;;
+	esac
+	;;
+      *.c)
+	cfile=$1
+	set x "$@" "$1"
+	shift
+	;;
+      *)
+	set x "$@" "$1"
+	shift
+	;;
+    esac
+  fi
+  shift
+done
+
+if test -z "$ofile" || test -z "$cfile"; then
+  # If no '-o' option was seen then we might have been invoked from a
+  # pattern rule where we don't need one.  That is ok -- this is a
+  # normal compilation that the losing compiler can handle.  If no
+  # '.c' file was seen then we are probably linking.  That is also
+  # ok.
+  exec "$@"
+fi
+
+# Name of file we expect compiler to create.
+cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'`
+
+# Create the lock directory.
+# Note: use '[/\\:.-]' here to ensure that we don't use the same name
+# that we are using for the .o file.  Also, base the name on the expected
+# object file name, since that is what matters with a parallel build.
+lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d
+while true; do
+  if mkdir "$lockdir" >/dev/null 2>&1; then
+    break
+  fi
+  sleep 1
+done
+# FIXME: race condition here if user kills between mkdir and trap.
+trap "rmdir '$lockdir'; exit 1" 1 2 15
+
+# Run the compile.
+"$@"
+ret=$?
+
+if test -f "$cofile"; then
+  test "$cofile" = "$ofile" || mv "$cofile" "$ofile"
+elif test -f "${cofile}bj"; then
+  test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile"
+fi
+
+rmdir "$lockdir"
+exit $ret
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/ast/build-aux/config.guess b/ast/build-aux/config.guess
new file mode 100755
index 0000000..9afd676
--- /dev/null
+++ b/ast/build-aux/config.guess
@@ -0,0 +1,1568 @@
+#! /bin/sh
+# Attempt to guess a canonical system name.
+#   Copyright 1992-2013 Free Software Foundation, Inc.
+
+timestamp='2013-11-29'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that
+# program.  This Exception is an additional permission under section 7
+# of the GNU General Public License, version 3 ("GPLv3").
+#
+# Originally written by Per Bothner.
+#
+# You can get the latest version of this script from:
+# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+#
+# Please send patches with a ChangeLog entry to [email-masked].
+
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION]
+
+Output the configuration name of the system \`$me' is run on.
+
+Operation modes:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <[email-masked]>."
+
+version="\
+GNU config.guess ($timestamp)
+
+Originally written by Per Bothner.
+Copyright 1992-2013 Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit ;;
+    --version | -v )
+       echo "$version" ; exit ;;
+    --help | --h* | -h )
+       echo "$usage"; exit ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )	# Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help" >&2
+       exit 1 ;;
+    * )
+       break ;;
+  esac
+done
+
+if test $# != 0; then
+  echo "$me: too many arguments$help" >&2
+  exit 1
+fi
+
+trap 'exit 1' 1 2 15
+
+# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
+# compiler to aid in system detection is discouraged as it requires
+# temporary files to be created and, as you can see below, it is a
+# headache to deal with in a portable fashion.
+
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
+
+# Portable tmp directory creation inspired by the Autoconf team.
+
+set_cc_for_build='
+trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ;
+trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ;
+: ${TMPDIR=/tmp} ;
+ { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+ { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } ||
+ { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+ { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ;
+dummy=$tmp/dummy ;
+tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ;
+case $CC_FOR_BUILD,$HOST_CC,$CC in
+ ,,)    echo "int x;" > $dummy.c ;
+	for c in cc gcc c89 c99 ; do
+	  if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then
+	     CC_FOR_BUILD="$c"; break ;
+	  fi ;
+	done ;
+	if test x"$CC_FOR_BUILD" = x ; then
+	  CC_FOR_BUILD=no_compiler_found ;
+	fi
+	;;
+ ,,*)   CC_FOR_BUILD=$CC ;;
+ ,*,*)  CC_FOR_BUILD=$HOST_CC ;;
+esac ; set_cc_for_build= ;'
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# ([email-masked] 1994-08-24)
+if (test -f /.attbin/uname) >/dev/null 2>&1 ; then
+	PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null`  || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+case "${UNAME_SYSTEM}" in
+Linux|GNU|GNU/*)
+	# If the system lacks a compiler, then just pick glibc.
+	# We could probably try harder.
+	LIBC=gnu
+
+	eval $set_cc_for_build
+	cat <<-EOF > $dummy.c
+	#include <features.h>
+	#if defined(__UCLIBC__)
+	LIBC=uclibc
+	#elif defined(__dietlibc__)
+	LIBC=dietlibc
+	#else
+	LIBC=gnu
+	#endif
+	EOF
+	eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC'`
+	;;
+esac
+
+# Note: order is significant - the case branches are not exclusive.
+
+case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
+    *:NetBSD:*:*)
+	# NetBSD (nbsd) targets should (where applicable) match one or
+	# more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*,
+	# *-*-netbsdecoff* and *-*-netbsd*.  For targets that recently
+	# switched to ELF, *-*-netbsd* would select the old
+	# object file format.  This provides both forward
+	# compatibility and a consistent mechanism for selecting the
+	# object file format.
+	#
+	# Note: NetBSD doesn't particularly care about the vendor
+	# portion of the name.  We always set it to "unknown".
+	sysctl="sysctl -n hw.machine_arch"
+	UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \
+	    /usr/sbin/$sysctl 2>/dev/null || echo unknown)`
+	case "${UNAME_MACHINE_ARCH}" in
+	    armeb) machine=armeb-unknown ;;
+	    arm*) machine=arm-unknown ;;
+	    sh3el) machine=shl-unknown ;;
+	    sh3eb) machine=sh-unknown ;;
+	    sh5el) machine=sh5le-unknown ;;
+	    *) machine=${UNAME_MACHINE_ARCH}-unknown ;;
+	esac
+	# The Operating System including object format, if it has switched
+	# to ELF recently, or will in the future.
+	case "${UNAME_MACHINE_ARCH}" in
+	    arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+		eval $set_cc_for_build
+		if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
+			| grep -q __ELF__
+		then
+		    # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
+		    # Return netbsd for either.  FIX?
+		    os=netbsd
+		else
+		    os=netbsdelf
+		fi
+		;;
+	    *)
+		os=netbsd
+		;;
+	esac
+	# The OS release
+	# Debian GNU/NetBSD machines have a different userland, and
+	# thus, need a distinct triplet. However, they do not need
+	# kernel version information, so it can be replaced with a
+	# suitable tag, in the style of linux-gnu.
+	case "${UNAME_VERSION}" in
+	    Debian*)
+		release='-gnu'
+		;;
+	    *)
+		release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+		;;
+	esac
+	# Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
+	# contains redundant information, the shorter form:
+	# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
+	echo "${machine}-${os}${release}"
+	exit ;;
+    *:Bitrig:*:*)
+	UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'`
+	echo ${UNAME_MACHINE_ARCH}-unknown-bitrig${UNAME_RELEASE}
+	exit ;;
+    *:OpenBSD:*:*)
+	UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'`
+	echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE}
+	exit ;;
+    *:ekkoBSD:*:*)
+	echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE}
+	exit ;;
+    *:SolidBSD:*:*)
+	echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE}
+	exit ;;
+    macppc:MirBSD:*:*)
+	echo powerpc-unknown-mirbsd${UNAME_RELEASE}
+	exit ;;
+    *:MirBSD:*:*)
+	echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE}
+	exit ;;
+    alpha:OSF1:*:*)
+	case $UNAME_RELEASE in
+	*4.0)
+		UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+		;;
+	*5.*)
+		UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'`
+		;;
+	esac
+	# According to Compaq, /usr/sbin/psrinfo has been available on
+	# OSF/1 and Tru64 systems produced since 1995.  I hope that
+	# covers most systems running today.  This code pipes the CPU
+	# types through head -n 1, so we only detect the type of CPU 0.
+	ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^  The alpha \(.*\) processor.*$/\1/p' | head -n 1`
+	case "$ALPHA_CPU_TYPE" in
+	    "EV4 (21064)")
+		UNAME_MACHINE="alpha" ;;
+	    "EV4.5 (21064)")
+		UNAME_MACHINE="alpha" ;;
+	    "LCA4 (21066/21068)")
+		UNAME_MACHINE="alpha" ;;
+	    "EV5 (21164)")
+		UNAME_MACHINE="alphaev5" ;;
+	    "EV5.6 (21164A)")
+		UNAME_MACHINE="alphaev56" ;;
+	    "EV5.6 (21164PC)")
+		UNAME_MACHINE="alphapca56" ;;
+	    "EV5.7 (21164PC)")
+		UNAME_MACHINE="alphapca57" ;;
+	    "EV6 (21264)")
+		UNAME_MACHINE="alphaev6" ;;
+	    "EV6.7 (21264A)")
+		UNAME_MACHINE="alphaev67" ;;
+	    "EV6.8CB (21264C)")
+		UNAME_MACHINE="alphaev68" ;;
+	    "EV6.8AL (21264B)")
+		UNAME_MACHINE="alphaev68" ;;
+	    "EV6.8CX (21264D)")
+		UNAME_MACHINE="alphaev68" ;;
+	    "EV6.9A (21264/EV69A)")
+		UNAME_MACHINE="alphaev69" ;;
+	    "EV7 (21364)")
+		UNAME_MACHINE="alphaev7" ;;
+	    "EV7.9 (21364A)")
+		UNAME_MACHINE="alphaev79" ;;
+	esac
+	# A Pn.n version is a patched version.
+	# A Vn.n version is a released version.
+	# A Tn.n version is a released field test version.
+	# A Xn.n version is an unreleased experimental baselevel.
+	# 1.2 uses "1.2" for uname -r.
+	echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+	# Reset EXIT trap before exiting to avoid spurious non-zero exit code.
+	exitcode=$?
+	trap '' 0
+	exit $exitcode ;;
+    Alpha\ *:Windows_NT*:*)
+	# How do we know it's Interix rather than the generic POSIX subsystem?
+	# Should we change UNAME_MACHINE based on the output of uname instead
+	# of the specific Alpha model?
+	echo alpha-pc-interix
+	exit ;;
+    21064:Windows_NT:50:3)
+	echo alpha-dec-winnt3.5
+	exit ;;
+    Amiga*:UNIX_System_V:4.0:*)
+	echo m68k-unknown-sysv4
+	exit ;;
+    *:[Aa]miga[Oo][Ss]:*:*)
+	echo ${UNAME_MACHINE}-unknown-amigaos
+	exit ;;
+    *:[Mm]orph[Oo][Ss]:*:*)
+	echo ${UNAME_MACHINE}-unknown-morphos
+	exit ;;
+    *:OS/390:*:*)
+	echo i370-ibm-openedition
+	exit ;;
+    *:z/VM:*:*)
+	echo s390-ibm-zvmoe
+	exit ;;
+    *:OS400:*:*)
+	echo powerpc-ibm-os400
+	exit ;;
+    arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+	echo arm-acorn-riscix${UNAME_RELEASE}
+	exit ;;
+    arm*:riscos:*:*|arm*:RISCOS:*:*)
+	echo arm-unknown-riscos
+	exit ;;
+    SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
+	echo hppa1.1-hitachi-hiuxmpp
+	exit ;;
+    Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+	# [email-masked] (Earle F. Ake) contributed MIS and NILE.
+	if test "`(/bin/universe) 2>/dev/null`" = att ; then
+		echo pyramid-pyramid-sysv3
+	else
+		echo pyramid-pyramid-bsd
+	fi
+	exit ;;
+    NILE*:*:*:dcosx)
+	echo pyramid-pyramid-svr4
+	exit ;;
+    DRS?6000:unix:4.0:6*)
+	echo sparc-icl-nx6
+	exit ;;
+    DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
+	case `/usr/bin/uname -p` in
+	    sparc) echo sparc-icl-nx7; exit ;;
+	esac ;;
+    s390x:SunOS:*:*)
+	echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    sun4H:SunOS:5.*:*)
+	echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+	echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*)
+	echo i386-pc-auroraux${UNAME_RELEASE}
+	exit ;;
+    i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
+	eval $set_cc_for_build
+	SUN_ARCH="i386"
+	# If there is a compiler, see if it is configured for 64-bit objects.
+	# Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
+	# This test works for both compilers.
+	if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then
+	    if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
+		(CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \
+		grep IS_64BIT_ARCH >/dev/null
+	    then
+		SUN_ARCH="x86_64"
+	    fi
+	fi
+	echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    sun4*:SunOS:6*:*)
+	# According to config.sub, this is the proper way to canonicalize
+	# SunOS6.  Hard to guess exactly what SunOS6 will be like, but
+	# it's likely to be more like Solaris than SunOS4.
+	echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    sun4*:SunOS:*:*)
+	case "`/usr/bin/arch -k`" in
+	    Series*|S4*)
+		UNAME_RELEASE=`uname -v`
+		;;
+	esac
+	# Japanese Language versions have a version number like `4.1.3-JL'.
+	echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'`
+	exit ;;
+    sun3*:SunOS:*:*)
+	echo m68k-sun-sunos${UNAME_RELEASE}
+	exit ;;
+    sun*:*:4.2BSD:*)
+	UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+	test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3
+	case "`/bin/arch`" in
+	    sun3)
+		echo m68k-sun-sunos${UNAME_RELEASE}
+		;;
+	    sun4)
+		echo sparc-sun-sunos${UNAME_RELEASE}
+		;;
+	esac
+	exit ;;
+    aushp:SunOS:*:*)
+	echo sparc-auspex-sunos${UNAME_RELEASE}
+	exit ;;
+    # The situation for MiNT is a little confusing.  The machine name
+    # can be virtually everything (everything which is not
+    # "atarist" or "atariste" at least should have a processor
+    # > m68000).  The system name ranges from "MiNT" over "FreeMiNT"
+    # to the lowercase version "mint" (or "freemint").  Finally
+    # the system name "TOS" denotes a system which is actually not
+    # MiNT.  But MiNT is downward compatible to TOS, so this should
+    # be no problem.
+    atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+	echo m68k-atari-mint${UNAME_RELEASE}
+	exit ;;
+    atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+	echo m68k-atari-mint${UNAME_RELEASE}
+	exit ;;
+    *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+	echo m68k-atari-mint${UNAME_RELEASE}
+	exit ;;
+    milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+	echo m68k-milan-mint${UNAME_RELEASE}
+	exit ;;
+    hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+	echo m68k-hades-mint${UNAME_RELEASE}
+	exit ;;
+    *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+	echo m68k-unknown-mint${UNAME_RELEASE}
+	exit ;;
+    m68k:machten:*:*)
+	echo m68k-apple-machten${UNAME_RELEASE}
+	exit ;;
+    powerpc:machten:*:*)
+	echo powerpc-apple-machten${UNAME_RELEASE}
+	exit ;;
+    RISC*:Mach:*:*)
+	echo mips-dec-mach_bsd4.3
+	exit ;;
+    RISC*:ULTRIX:*:*)
+	echo mips-dec-ultrix${UNAME_RELEASE}
+	exit ;;
+    VAX*:ULTRIX*:*:*)
+	echo vax-dec-ultrix${UNAME_RELEASE}
+	exit ;;
+    2020:CLIX:*:* | 2430:CLIX:*:*)
+	echo clipper-intergraph-clix${UNAME_RELEASE}
+	exit ;;
+    mips:*:*:UMIPS | mips:*:*:RISCos)
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+#ifdef __cplusplus
+#include <stdio.h>  /* for printf() prototype */
+	int main (int argc, char *argv[]) {
+#else
+	int main (argc, argv) int argc; char *argv[]; {
+#endif
+	#if defined (host_mips) && defined (MIPSEB)
+	#if defined (SYSTYPE_SYSV)
+	  printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0);
+	#endif
+	#if defined (SYSTYPE_SVR4)
+	  printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0);
+	#endif
+	#if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+	  printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0);
+	#endif
+	#endif
+	  exit (-1);
+	}
+EOF
+	$CC_FOR_BUILD -o $dummy $dummy.c &&
+	  dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` &&
+	  SYSTEM_NAME=`$dummy $dummyarg` &&
+	    { echo "$SYSTEM_NAME"; exit; }
+	echo mips-mips-riscos${UNAME_RELEASE}
+	exit ;;
+    Motorola:PowerMAX_OS:*:*)
+	echo powerpc-motorola-powermax
+	exit ;;
+    Motorola:*:4.3:PL8-*)
+	echo powerpc-harris-powermax
+	exit ;;
+    Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
+	echo powerpc-harris-powermax
+	exit ;;
+    Night_Hawk:Power_UNIX:*:*)
+	echo powerpc-harris-powerunix
+	exit ;;
+    m88k:CX/UX:7*:*)
+	echo m88k-harris-cxux7
+	exit ;;
+    m88k:*:4*:R4*)
+	echo m88k-motorola-sysv4
+	exit ;;
+    m88k:*:3*:R3*)
+	echo m88k-motorola-sysv3
+	exit ;;
+    AViiON:dgux:*:*)
+	# DG/UX returns AViiON for all architectures
+	UNAME_PROCESSOR=`/usr/bin/uname -p`
+	if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ]
+	then
+	    if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \
+	       [ ${TARGET_BINARY_INTERFACE}x = x ]
+	    then
+		echo m88k-dg-dgux${UNAME_RELEASE}
+	    else
+		echo m88k-dg-dguxbcs${UNAME_RELEASE}
+	    fi
+	else
+	    echo i586-dg-dgux${UNAME_RELEASE}
+	fi
+	exit ;;
+    M88*:DolphinOS:*:*)	# DolphinOS (SVR3)
+	echo m88k-dolphin-sysv3
+	exit ;;
+    M88*:*:R3*:*)
+	# Delta 88k system running SVR3
+	echo m88k-motorola-sysv3
+	exit ;;
+    XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+	echo m88k-tektronix-sysv3
+	exit ;;
+    Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+	echo m68k-tektronix-bsd
+	exit ;;
+    *:IRIX*:*:*)
+	echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'`
+	exit ;;
+    ????????:AIX?:[12].1:2)   # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+	echo romp-ibm-aix     # uname -m gives an 8 hex-code CPU id
+	exit ;;               # Note that: echo "'`uname -s`'" gives 'AIX '
+    i*86:AIX:*:*)
+	echo i386-ibm-aix
+	exit ;;
+    ia64:AIX:*:*)
+	if [ -x /usr/bin/oslevel ] ; then
+		IBM_REV=`/usr/bin/oslevel`
+	else
+		IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+	fi
+	echo ${UNAME_MACHINE}-ibm-aix${IBM_REV}
+	exit ;;
+    *:AIX:2:3)
+	if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+		eval $set_cc_for_build
+		sed 's/^		//' << EOF >$dummy.c
+		#include <sys/systemcfg.h>
+
+		main()
+			{
+			if (!__power_pc())
+				exit(1);
+			puts("powerpc-ibm-aix3.2.5");
+			exit(0);
+			}
+EOF
+		if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy`
+		then
+			echo "$SYSTEM_NAME"
+		else
+			echo rs6000-ibm-aix3.2.5
+		fi
+	elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+		echo rs6000-ibm-aix3.2.4
+	else
+		echo rs6000-ibm-aix3.2
+	fi
+	exit ;;
+    *:AIX:*:[4567])
+	IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
+	if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then
+		IBM_ARCH=rs6000
+	else
+		IBM_ARCH=powerpc
+	fi
+	if [ -x /usr/bin/oslevel ] ; then
+		IBM_REV=`/usr/bin/oslevel`
+	else
+		IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+	fi
+	echo ${IBM_ARCH}-ibm-aix${IBM_REV}
+	exit ;;
+    *:AIX:*:*)
+	echo rs6000-ibm-aix
+	exit ;;
+    ibmrt:4.4BSD:*|romp-ibm:BSD:*)
+	echo romp-ibm-bsd4.4
+	exit ;;
+    ibmrt:*BSD:*|romp-ibm:BSD:*)            # covers RT/PC BSD and
+	echo romp-ibm-bsd${UNAME_RELEASE}   # 4.3 with uname added to
+	exit ;;                             # report: romp-ibm BSD 4.3
+    *:BOSX:*:*)
+	echo rs6000-bull-bosx
+	exit ;;
+    DPX/2?00:B.O.S.:*:*)
+	echo m68k-bull-sysv3
+	exit ;;
+    9000/[34]??:4.3bsd:1.*:*)
+	echo m68k-hp-bsd
+	exit ;;
+    hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+	echo m68k-hp-bsd4.4
+	exit ;;
+    9000/[34678]??:HP-UX:*:*)
+	HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+	case "${UNAME_MACHINE}" in
+	    9000/31? )            HP_ARCH=m68000 ;;
+	    9000/[34]?? )         HP_ARCH=m68k ;;
+	    9000/[678][0-9][0-9])
+		if [ -x /usr/bin/getconf ]; then
+		    sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
+		    sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
+		    case "${sc_cpu_version}" in
+		      523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0
+		      528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1
+		      532)                      # CPU_PA_RISC2_0
+			case "${sc_kernel_bits}" in
+			  32) HP_ARCH="hppa2.0n" ;;
+			  64) HP_ARCH="hppa2.0w" ;;
+			  '') HP_ARCH="hppa2.0" ;;   # HP-UX 10.20
+			esac ;;
+		    esac
+		fi
+		if [ "${HP_ARCH}" = "" ]; then
+		    eval $set_cc_for_build
+		    sed 's/^		//' << EOF >$dummy.c
+
+		#define _HPUX_SOURCE
+		#include <stdlib.h>
+		#include <unistd.h>
+
+		int main ()
+		{
+		#if defined(_SC_KERNEL_BITS)
+		    long bits = sysconf(_SC_KERNEL_BITS);
+		#endif
+		    long cpu  = sysconf (_SC_CPU_VERSION);
+
+		    switch (cpu)
+			{
+			case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+			case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+			case CPU_PA_RISC2_0:
+		#if defined(_SC_KERNEL_BITS)
+			    switch (bits)
+				{
+				case 64: puts ("hppa2.0w"); break;
+				case 32: puts ("hppa2.0n"); break;
+				default: puts ("hppa2.0"); break;
+				} break;
+		#else  /* !defined(_SC_KERNEL_BITS) */
+			    puts ("hppa2.0"); break;
+		#endif
+			default: puts ("hppa1.0"); break;
+			}
+		    exit (0);
+		}
+EOF
+		    (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
+		    test -z "$HP_ARCH" && HP_ARCH=hppa
+		fi ;;
+	esac
+	if [ ${HP_ARCH} = "hppa2.0w" ]
+	then
+	    eval $set_cc_for_build
+
+	    # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
+	    # 32-bit code.  hppa64-hp-hpux* has the same kernel and a compiler
+	    # generating 64-bit code.  GNU and HP use different nomenclature:
+	    #
+	    # $ CC_FOR_BUILD=cc ./config.guess
+	    # => hppa2.0w-hp-hpux11.23
+	    # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
+	    # => hppa64-hp-hpux11.23
+
+	    if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) |
+		grep -q __LP64__
+	    then
+		HP_ARCH="hppa2.0w"
+	    else
+		HP_ARCH="hppa64"
+	    fi
+	fi
+	echo ${HP_ARCH}-hp-hpux${HPUX_REV}
+	exit ;;
+    ia64:HP-UX:*:*)
+	HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+	echo ia64-hp-hpux${HPUX_REV}
+	exit ;;
+    3050*:HI-UX:*:*)
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+	#include <unistd.h>
+	int
+	main ()
+	{
+	  long cpu = sysconf (_SC_CPU_VERSION);
+	  /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+	     true for CPU_PA_RISC1_0.  CPU_IS_PA_RISC returns correct
+	     results, however.  */
+	  if (CPU_IS_PA_RISC (cpu))
+	    {
+	      switch (cpu)
+		{
+		  case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+		  case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+		  case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+		  default: puts ("hppa-hitachi-hiuxwe2"); break;
+		}
+	    }
+	  else if (CPU_IS_HP_MC68K (cpu))
+	    puts ("m68k-hitachi-hiuxwe2");
+	  else puts ("unknown-hitachi-hiuxwe2");
+	  exit (0);
+	}
+EOF
+	$CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` &&
+		{ echo "$SYSTEM_NAME"; exit; }
+	echo unknown-hitachi-hiuxwe2
+	exit ;;
+    9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* )
+	echo hppa1.1-hp-bsd
+	exit ;;
+    9000/8??:4.3bsd:*:*)
+	echo hppa1.0-hp-bsd
+	exit ;;
+    *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
+	echo hppa1.0-hp-mpeix
+	exit ;;
+    hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* )
+	echo hppa1.1-hp-osf
+	exit ;;
+    hp8??:OSF1:*:*)
+	echo hppa1.0-hp-osf
+	exit ;;
+    i*86:OSF1:*:*)
+	if [ -x /usr/sbin/sysversion ] ; then
+	    echo ${UNAME_MACHINE}-unknown-osf1mk
+	else
+	    echo ${UNAME_MACHINE}-unknown-osf1
+	fi
+	exit ;;
+    parisc*:Lites*:*:*)
+	echo hppa1.1-hp-lites
+	exit ;;
+    C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+	echo c1-convex-bsd
+	exit ;;
+    C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+	if getsysinfo -f scalar_acc
+	then echo c32-convex-bsd
+	else echo c2-convex-bsd
+	fi
+	exit ;;
+    C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+	echo c34-convex-bsd
+	exit ;;
+    C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+	echo c38-convex-bsd
+	exit ;;
+    C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+	echo c4-convex-bsd
+	exit ;;
+    CRAY*Y-MP:*:*:*)
+	echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit ;;
+    CRAY*[A-Z]90:*:*:*)
+	echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \
+	| sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+	      -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
+	      -e 's/\.[^.]*$/.X/'
+	exit ;;
+    CRAY*TS:*:*:*)
+	echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit ;;
+    CRAY*T3E:*:*:*)
+	echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit ;;
+    CRAY*SV1:*:*:*)
+	echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit ;;
+    *:UNICOS/mp:*:*)
+	echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit ;;
+    F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
+	FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+	FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+	FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
+	echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+	exit ;;
+    5000:UNIX_System_V:4.*:*)
+	FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+	FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'`
+	echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+	exit ;;
+    i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
+	echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE}
+	exit ;;
+    sparc*:BSD/OS:*:*)
+	echo sparc-unknown-bsdi${UNAME_RELEASE}
+	exit ;;
+    *:BSD/OS:*:*)
+	echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE}
+	exit ;;
+    *:FreeBSD:*:*)
+	UNAME_PROCESSOR=`/usr/bin/uname -p`
+	case ${UNAME_PROCESSOR} in
+	    amd64)
+		echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+	    *)
+		echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+	esac
+	exit ;;
+    i*:CYGWIN*:*)
+	echo ${UNAME_MACHINE}-pc-cygwin
+	exit ;;
+    *:MINGW64*:*)
+	echo ${UNAME_MACHINE}-pc-mingw64
+	exit ;;
+    *:MINGW*:*)
+	echo ${UNAME_MACHINE}-pc-mingw32
+	exit ;;
+    i*:MSYS*:*)
+	echo ${UNAME_MACHINE}-pc-msys
+	exit ;;
+    i*:windows32*:*)
+	# uname -m includes "-pc" on this system.
+	echo ${UNAME_MACHINE}-mingw32
+	exit ;;
+    i*:PW*:*)
+	echo ${UNAME_MACHINE}-pc-pw32
+	exit ;;
+    *:Interix*:*)
+	case ${UNAME_MACHINE} in
+	    x86)
+		echo i586-pc-interix${UNAME_RELEASE}
+		exit ;;
+	    authenticamd | genuineintel | EM64T)
+		echo x86_64-unknown-interix${UNAME_RELEASE}
+		exit ;;
+	    IA64)
+		echo ia64-unknown-interix${UNAME_RELEASE}
+		exit ;;
+	esac ;;
+    [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*)
+	echo i${UNAME_MACHINE}-pc-mks
+	exit ;;
+    8664:Windows_NT:*)
+	echo x86_64-pc-mks
+	exit ;;
+    i*:Windows_NT*:* | Pentium*:Windows_NT*:*)
+	# How do we know it's Interix rather than the generic POSIX subsystem?
+	# It also conflicts with pre-2.0 versions of AT&T UWIN. Should we
+	# UNAME_MACHINE based on the output of uname instead of i386?
+	echo i586-pc-interix
+	exit ;;
+    i*:UWIN*:*)
+	echo ${UNAME_MACHINE}-pc-uwin
+	exit ;;
+    amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
+	echo x86_64-unknown-cygwin
+	exit ;;
+    p*:CYGWIN*:*)
+	echo powerpcle-unknown-cygwin
+	exit ;;
+    prep*:SunOS:5.*:*)
+	echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    *:GNU:*:*)
+	# the GNU system
+	echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-${LIBC}`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'`
+	exit ;;
+    *:GNU/*:*:*)
+	# other systems with GNU libc and userland
+	echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC}
+	exit ;;
+    i*86:Minix:*:*)
+	echo ${UNAME_MACHINE}-pc-minix
+	exit ;;
+    aarch64:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    aarch64_be:Linux:*:*)
+	UNAME_MACHINE=aarch64_be
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    alpha:Linux:*:*)
+	case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
+	  EV5)   UNAME_MACHINE=alphaev5 ;;
+	  EV56)  UNAME_MACHINE=alphaev56 ;;
+	  PCA56) UNAME_MACHINE=alphapca56 ;;
+	  PCA57) UNAME_MACHINE=alphapca56 ;;
+	  EV6)   UNAME_MACHINE=alphaev6 ;;
+	  EV67)  UNAME_MACHINE=alphaev67 ;;
+	  EV68*) UNAME_MACHINE=alphaev68 ;;
+	esac
+	objdump --private-headers /bin/sh | grep -q ld.so.1
+	if test "$?" = 0 ; then LIBC="gnulibc1" ; fi
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    arc:Linux:*:* | arceb:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    arm*:Linux:*:*)
+	eval $set_cc_for_build
+	if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
+	    | grep -q __ARM_EABI__
+	then
+	    echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	else
+	    if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
+		| grep -q __ARM_PCS_VFP
+	    then
+		echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabi
+	    else
+		echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabihf
+	    fi
+	fi
+	exit ;;
+    avr32*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    cris:Linux:*:*)
+	echo ${UNAME_MACHINE}-axis-linux-${LIBC}
+	exit ;;
+    crisv32:Linux:*:*)
+	echo ${UNAME_MACHINE}-axis-linux-${LIBC}
+	exit ;;
+    frv:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    hexagon:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    i*86:Linux:*:*)
+	echo ${UNAME_MACHINE}-pc-linux-${LIBC}
+	exit ;;
+    ia64:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    m32r*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    m68*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    mips:Linux:*:* | mips64:Linux:*:*)
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+	#undef CPU
+	#undef ${UNAME_MACHINE}
+	#undef ${UNAME_MACHINE}el
+	#if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+	CPU=${UNAME_MACHINE}el
+	#else
+	#if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+	CPU=${UNAME_MACHINE}
+	#else
+	CPU=
+	#endif
+	#endif
+EOF
+	eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'`
+	test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; }
+	;;
+    or1k:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    or32:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    padre:Linux:*:*)
+	echo sparc-unknown-linux-${LIBC}
+	exit ;;
+    parisc64:Linux:*:* | hppa64:Linux:*:*)
+	echo hppa64-unknown-linux-${LIBC}
+	exit ;;
+    parisc:Linux:*:* | hppa:Linux:*:*)
+	# Look for CPU level
+	case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
+	  PA7*) echo hppa1.1-unknown-linux-${LIBC} ;;
+	  PA8*) echo hppa2.0-unknown-linux-${LIBC} ;;
+	  *)    echo hppa-unknown-linux-${LIBC} ;;
+	esac
+	exit ;;
+    ppc64:Linux:*:*)
+	echo powerpc64-unknown-linux-${LIBC}
+	exit ;;
+    ppc:Linux:*:*)
+	echo powerpc-unknown-linux-${LIBC}
+	exit ;;
+    ppc64le:Linux:*:*)
+	echo powerpc64le-unknown-linux-${LIBC}
+	exit ;;
+    ppcle:Linux:*:*)
+	echo powerpcle-unknown-linux-${LIBC}
+	exit ;;
+    s390:Linux:*:* | s390x:Linux:*:*)
+	echo ${UNAME_MACHINE}-ibm-linux-${LIBC}
+	exit ;;
+    sh64*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    sh*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    sparc:Linux:*:* | sparc64:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    tile*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    vax:Linux:*:*)
+	echo ${UNAME_MACHINE}-dec-linux-${LIBC}
+	exit ;;
+    x86_64:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    xtensa*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+	exit ;;
+    i*86:DYNIX/ptx:4*:*)
+	# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
+	# earlier versions are messed up and put the nodename in both
+	# sysname and nodename.
+	echo i386-sequent-sysv4
+	exit ;;
+    i*86:UNIX_SV:4.2MP:2.*)
+	# Unixware is an offshoot of SVR4, but it has its own version
+	# number series starting with 2...
+	# I am not positive that other SVR4 systems won't match this,
+	# I just have to hope.  -- rms.
+	# Use sysv4.2uw... so that sysv4* matches it.
+	echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION}
+	exit ;;
+    i*86:OS/2:*:*)
+	# If we were able to find `uname', then EMX Unix compatibility
+	# is probably installed.
+	echo ${UNAME_MACHINE}-pc-os2-emx
+	exit ;;
+    i*86:XTS-300:*:STOP)
+	echo ${UNAME_MACHINE}-unknown-stop
+	exit ;;
+    i*86:atheos:*:*)
+	echo ${UNAME_MACHINE}-unknown-atheos
+	exit ;;
+    i*86:syllable:*:*)
+	echo ${UNAME_MACHINE}-pc-syllable
+	exit ;;
+    i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*)
+	echo i386-unknown-lynxos${UNAME_RELEASE}
+	exit ;;
+    i*86:*DOS:*:*)
+	echo ${UNAME_MACHINE}-pc-msdosdjgpp
+	exit ;;
+    i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*)
+	UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'`
+	if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+		echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL}
+	else
+		echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL}
+	fi
+	exit ;;
+    i*86:*:5:[678]*)
+	# UnixWare 7.x, OpenUNIX and OpenServer 6.
+	case `/bin/uname -X | grep "^Machine"` in
+	    *486*)	     UNAME_MACHINE=i486 ;;
+	    *Pentium)	     UNAME_MACHINE=i586 ;;
+	    *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
+	esac
+	echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+	exit ;;
+    i*86:*:3.2:*)
+	if test -f /usr/options/cb.name; then
+		UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+		echo ${UNAME_MACHINE}-pc-isc$UNAME_REL
+	elif /bin/uname -X 2>/dev/null >/dev/null ; then
+		UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
+		(/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
+		(/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
+			&& UNAME_MACHINE=i586
+		(/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
+			&& UNAME_MACHINE=i686
+		(/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
+			&& UNAME_MACHINE=i686
+		echo ${UNAME_MACHINE}-pc-sco$UNAME_REL
+	else
+		echo ${UNAME_MACHINE}-pc-sysv32
+	fi
+	exit ;;
+    pc:*:*:*)
+	# Left here for compatibility:
+	# uname -m prints for DJGPP always 'pc', but it prints nothing about
+	# the processor, so we play safe by assuming i586.
+	# Note: whatever this is, it MUST be the same as what config.sub
+	# prints for the "djgpp" host, or else GDB configury will decide that
+	# this is a cross-build.
+	echo i586-pc-msdosdjgpp
+	exit ;;
+    Intel:Mach:3*:*)
+	echo i386-pc-mach3
+	exit ;;
+    paragon:*:*:*)
+	echo i860-intel-osf1
+	exit ;;
+    i860:*:4.*:*) # i860-SVR4
+	if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+	  echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4
+	else # Add other i860-SVR4 vendors below as they are discovered.
+	  echo i860-unknown-sysv${UNAME_RELEASE}  # Unknown i860-SVR4
+	fi
+	exit ;;
+    mini*:CTIX:SYS*5:*)
+	# "miniframe"
+	echo m68010-convergent-sysv
+	exit ;;
+    mc68k:UNIX:SYSTEM5:3.51m)
+	echo m68k-convergent-sysv
+	exit ;;
+    M680?0:D-NIX:5.3:*)
+	echo m68k-diab-dnix
+	exit ;;
+    M68*:*:R3V[5678]*:*)
+	test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
+    3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
+	OS_REL=''
+	test -r /etc/.relid \
+	&& OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+	  && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+	/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+	  && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+    3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+	  && { echo i486-ncr-sysv4; exit; } ;;
+    NCR*:*:4.2:* | MPRAS*:*:4.2:*)
+	OS_REL='.3'
+	test -r /etc/.relid \
+	    && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+	    && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+	/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+	    && { echo i586-ncr-sysv4.3${OS_REL}; exit; }
+	/bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \
+	    && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+    m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
+	echo m68k-unknown-lynxos${UNAME_RELEASE}
+	exit ;;
+    mc68030:UNIX_System_V:4.*:*)
+	echo m68k-atari-sysv4
+	exit ;;
+    TSUNAMI:LynxOS:2.*:*)
+	echo sparc-unknown-lynxos${UNAME_RELEASE}
+	exit ;;
+    rs6000:LynxOS:2.*:*)
+	echo rs6000-unknown-lynxos${UNAME_RELEASE}
+	exit ;;
+    PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*)
+	echo powerpc-unknown-lynxos${UNAME_RELEASE}
+	exit ;;
+    SM[BE]S:UNIX_SV:*:*)
+	echo mips-dde-sysv${UNAME_RELEASE}
+	exit ;;
+    RM*:ReliantUNIX-*:*:*)
+	echo mips-sni-sysv4
+	exit ;;
+    RM*:SINIX-*:*:*)
+	echo mips-sni-sysv4
+	exit ;;
+    *:SINIX-*:*:*)
+	if uname -p 2>/dev/null >/dev/null ; then
+		UNAME_MACHINE=`(uname -p) 2>/dev/null`
+		echo ${UNAME_MACHINE}-sni-sysv4
+	else
+		echo ns32k-sni-sysv
+	fi
+	exit ;;
+    PENTIUM:*:4.0*:*)	# Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+			# says <[email-masked]>
+	echo i586-unisys-sysv4
+	exit ;;
+    *:UNIX_System_V:4*:FTX*)
+	# From Gerald Hewes <[email-masked]>.
+	# How about differentiating between stratus architectures? -djm
+	echo hppa1.1-stratus-sysv4
+	exit ;;
+    *:*:*:FTX*)
+	# From [email-masked].
+	echo i860-stratus-sysv4
+	exit ;;
+    i*86:VOS:*:*)
+	# From [email-masked].
+	echo ${UNAME_MACHINE}-stratus-vos
+	exit ;;
+    *:VOS:*:*)
+	# From [email-masked].
+	echo hppa1.1-stratus-vos
+	exit ;;
+    mc68*:A/UX:*:*)
+	echo m68k-apple-aux${UNAME_RELEASE}
+	exit ;;
+    news*:NEWS-OS:6*:*)
+	echo mips-sony-newsos6
+	exit ;;
+    R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+	if [ -d /usr/nec ]; then
+		echo mips-nec-sysv${UNAME_RELEASE}
+	else
+		echo mips-unknown-sysv${UNAME_RELEASE}
+	fi
+	exit ;;
+    BeBox:BeOS:*:*)	# BeOS running on hardware made by Be, PPC only.
+	echo powerpc-be-beos
+	exit ;;
+    BeMac:BeOS:*:*)	# BeOS running on Mac or Mac clone, PPC only.
+	echo powerpc-apple-beos
+	exit ;;
+    BePC:BeOS:*:*)	# BeOS running on Intel PC compatible.
+	echo i586-pc-beos
+	exit ;;
+    BePC:Haiku:*:*)	# Haiku running on Intel PC compatible.
+	echo i586-pc-haiku
+	exit ;;
+    x86_64:Haiku:*:*)
+	echo x86_64-unknown-haiku
+	exit ;;
+    SX-4:SUPER-UX:*:*)
+	echo sx4-nec-superux${UNAME_RELEASE}
+	exit ;;
+    SX-5:SUPER-UX:*:*)
+	echo sx5-nec-superux${UNAME_RELEASE}
+	exit ;;
+    SX-6:SUPER-UX:*:*)
+	echo sx6-nec-superux${UNAME_RELEASE}
+	exit ;;
+    SX-7:SUPER-UX:*:*)
+	echo sx7-nec-superux${UNAME_RELEASE}
+	exit ;;
+    SX-8:SUPER-UX:*:*)
+	echo sx8-nec-superux${UNAME_RELEASE}
+	exit ;;
+    SX-8R:SUPER-UX:*:*)
+	echo sx8r-nec-superux${UNAME_RELEASE}
+	exit ;;
+    Power*:Rhapsody:*:*)
+	echo powerpc-apple-rhapsody${UNAME_RELEASE}
+	exit ;;
+    *:Rhapsody:*:*)
+	echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
+	exit ;;
+    *:Darwin:*:*)
+	UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown
+	eval $set_cc_for_build
+	if test "$UNAME_PROCESSOR" = unknown ; then
+	    UNAME_PROCESSOR=powerpc
+	fi
+	if test `echo "$UNAME_RELEASE" | sed -e 's/\..*//'` -le 10 ; then
+	    if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then
+		if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
+		    (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \
+		    grep IS_64BIT_ARCH >/dev/null
+		then
+		    case $UNAME_PROCESSOR in
+			i386) UNAME_PROCESSOR=x86_64 ;;
+			powerpc) UNAME_PROCESSOR=powerpc64 ;;
+		    esac
+		fi
+	    fi
+	elif test "$UNAME_PROCESSOR" = i386 ; then
+	    # Avoid executing cc on OS X 10.9, as it ships with a stub
+	    # that puts up a graphical alert prompting to install
+	    # developer tools.  Any system running Mac OS X 10.7 or
+	    # later (Darwin 11 and later) is required to have a 64-bit
+	    # processor. This is not true of the ARM version of Darwin
+	    # that Apple uses in portable devices.
+	    UNAME_PROCESSOR=x86_64
+	fi
+	echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE}
+	exit ;;
+    *:procnto*:*:* | *:QNX:[0123456789]*:*)
+	UNAME_PROCESSOR=`uname -p`
+	if test "$UNAME_PROCESSOR" = "x86"; then
+		UNAME_PROCESSOR=i386
+		UNAME_MACHINE=pc
+	fi
+	echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE}
+	exit ;;
+    *:QNX:*:4*)
+	echo i386-pc-qnx
+	exit ;;
+    NEO-?:NONSTOP_KERNEL:*:*)
+	echo neo-tandem-nsk${UNAME_RELEASE}
+	exit ;;
+    NSE-*:NONSTOP_KERNEL:*:*)
+	echo nse-tandem-nsk${UNAME_RELEASE}
+	exit ;;
+    NSR-?:NONSTOP_KERNEL:*:*)
+	echo nsr-tandem-nsk${UNAME_RELEASE}
+	exit ;;
+    *:NonStop-UX:*:*)
+	echo mips-compaq-nonstopux
+	exit ;;
+    BS2000:POSIX*:*:*)
+	echo bs2000-siemens-sysv
+	exit ;;
+    DS/*:UNIX_System_V:*:*)
+	echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE}
+	exit ;;
+    *:Plan9:*:*)
+	# "uname -m" is not consistent, so use $cputype instead. 386
+	# is converted to i386 for consistency with other x86
+	# operating systems.
+	if test "$cputype" = "386"; then
+	    UNAME_MACHINE=i386
+	else
+	    UNAME_MACHINE="$cputype"
+	fi
+	echo ${UNAME_MACHINE}-unknown-plan9
+	exit ;;
+    *:TOPS-10:*:*)
+	echo pdp10-unknown-tops10
+	exit ;;
+    *:TENEX:*:*)
+	echo pdp10-unknown-tenex
+	exit ;;
+    KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
+	echo pdp10-dec-tops20
+	exit ;;
+    XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
+	echo pdp10-xkl-tops20
+	exit ;;
+    *:TOPS-20:*:*)
+	echo pdp10-unknown-tops20
+	exit ;;
+    *:ITS:*:*)
+	echo pdp10-unknown-its
+	exit ;;
+    SEI:*:*:SEIUX)
+	echo mips-sei-seiux${UNAME_RELEASE}
+	exit ;;
+    *:DragonFly:*:*)
+	echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+	exit ;;
+    *:*VMS:*:*)
+	UNAME_MACHINE=`(uname -p) 2>/dev/null`
+	case "${UNAME_MACHINE}" in
+	    A*) echo alpha-dec-vms ; exit ;;
+	    I*) echo ia64-dec-vms ; exit ;;
+	    V*) echo vax-dec-vms ; exit ;;
+	esac ;;
+    *:XENIX:*:SysV)
+	echo i386-pc-xenix
+	exit ;;
+    i*86:skyos:*:*)
+	echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//'
+	exit ;;
+    i*86:rdos:*:*)
+	echo ${UNAME_MACHINE}-pc-rdos
+	exit ;;
+    i*86:AROS:*:*)
+	echo ${UNAME_MACHINE}-pc-aros
+	exit ;;
+    x86_64:VMkernel:*:*)
+	echo ${UNAME_MACHINE}-unknown-esx
+	exit ;;
+esac
+
+eval $set_cc_for_build
+cat >$dummy.c <<EOF
+#ifdef _SEQUENT_
+# include <sys/types.h>
+# include <sys/utsname.h>
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+  /* BFD wants "bsd" instead of "newsos".  Perhaps BFD should be changed,
+     I don't know....  */
+  printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+  printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+	"4"
+#else
+	""
+#endif
+	); exit (0);
+#endif
+#endif
+
+#if defined (__arm) && defined (__acorn) && defined (__unix)
+  printf ("arm-acorn-riscix\n"); exit (0);
+#endif
+
+#if defined (hp300) && !defined (hpux)
+  printf ("m68k-hp-bsd\n"); exit (0);
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+  int version;
+  version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+  if (version < 4)
+    printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+  else
+    printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+  exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+  printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+  printf ("ns32k-encore-mach\n"); exit (0);
+#else
+  printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+  printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+  printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+  printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+    struct utsname un;
+
+    uname(&un);
+
+    if (strncmp(un.version, "V2", 2) == 0) {
+	printf ("i386-sequent-ptx2\n"); exit (0);
+    }
+    if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+	printf ("i386-sequent-ptx1\n"); exit (0);
+    }
+    printf ("i386-sequent-ptx\n"); exit (0);
+
+#endif
+
+#if defined (vax)
+# if !defined (ultrix)
+#  include <sys/param.h>
+#  if defined (BSD)
+#   if BSD == 43
+      printf ("vax-dec-bsd4.3\n"); exit (0);
+#   else
+#    if BSD == 199006
+      printf ("vax-dec-bsd4.3reno\n"); exit (0);
+#    else
+      printf ("vax-dec-bsd\n"); exit (0);
+#    endif
+#   endif
+#  else
+    printf ("vax-dec-bsd\n"); exit (0);
+#  endif
+# else
+    printf ("vax-dec-ultrix\n"); exit (0);
+# endif
+#endif
+
+#if defined (alliant) && defined (i860)
+  printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+  exit (1);
+}
+EOF
+
+$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` &&
+	{ echo "$SYSTEM_NAME"; exit; }
+
+# Apollos put the system type in the environment.
+
+test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; }
+
+# Convex versions that predate uname can use getsysinfo(1)
+
+if [ -x /usr/convex/getsysinfo ]
+then
+    case `getsysinfo -f cpu_type` in
+    c1*)
+	echo c1-convex-bsd
+	exit ;;
+    c2*)
+	if getsysinfo -f scalar_acc
+	then echo c32-convex-bsd
+	else echo c2-convex-bsd
+	fi
+	exit ;;
+    c34*)
+	echo c34-convex-bsd
+	exit ;;
+    c38*)
+	echo c38-convex-bsd
+	exit ;;
+    c4*)
+	echo c4-convex-bsd
+	exit ;;
+    esac
+fi
+
+cat >&2 <<EOF
+$0: unable to guess system type
+
+This script, last modified $timestamp, has failed to recognize
+the operating system you are using. It is advised that you
+download the most up to date version of the config scripts from
+
+  http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+and
+  http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+
+If the version you run ($0) is already up to date, please
+send the following data and any information you think might be
+pertinent to <[email-masked]> in order to provide the needed
+information to handle your system.
+
+config.guess timestamp = $timestamp
+
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null`
+
+hostinfo               = `(hostinfo) 2>/dev/null`
+/bin/universe          = `(/bin/universe) 2>/dev/null`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null`
+/bin/arch              = `(/bin/arch) 2>/dev/null`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
+
+UNAME_MACHINE = ${UNAME_MACHINE}
+UNAME_RELEASE = ${UNAME_RELEASE}
+UNAME_SYSTEM  = ${UNAME_SYSTEM}
+UNAME_VERSION = ${UNAME_VERSION}
+EOF
+
+exit 1
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/ast/build-aux/config.sub b/ast/build-aux/config.sub
new file mode 100755
index 0000000..61cb4bc
--- /dev/null
+++ b/ast/build-aux/config.sub
@@ -0,0 +1,1793 @@
+#! /bin/sh
+# Configuration validation subroutine script.
+#   Copyright 1992-2013 Free Software Foundation, Inc.
+
+timestamp='2013-10-01'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that
+# program.  This Exception is an additional permission under section 7
+# of the GNU General Public License, version 3 ("GPLv3").
+
+
+# Please send patches with a ChangeLog entry to [email-masked].
+#
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# You can get the latest version of this script from:
+# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support.  The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+#	CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+#	CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION] CPU-MFR-OPSYS
+       $0 [OPTION] ALIAS
+
+Canonicalize a configuration name.
+
+Operation modes:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <[email-masked]>."
+
+version="\
+GNU config.sub ($timestamp)
+
+Copyright 1992-2013 Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit ;;
+    --version | -v )
+       echo "$version" ; exit ;;
+    --help | --h* | -h )
+       echo "$usage"; exit ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )	# Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help"
+       exit 1 ;;
+
+    *local*)
+       # First pass through any local machine types.
+       echo $1
+       exit ;;
+
+    * )
+       break ;;
+  esac
+done
+
+case $# in
+ 0) echo "$me: missing argument$help" >&2
+    exit 1;;
+ 1) ;;
+ *) echo "$me: too many arguments$help" >&2
+    exit 1;;
+esac
+
+# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any).
+# Here we must recognize all the valid KERNEL-OS combinations.
+maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
+case $maybe_os in
+  nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \
+  linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \
+  knetbsd*-gnu* | netbsd*-gnu* | \
+  kopensolaris*-gnu* | \
+  storm-chaos* | os2-emx* | rtmk-nova*)
+    os=-$maybe_os
+    basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
+    ;;
+  android-linux)
+    os=-linux-android
+    basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown
+    ;;
+  *)
+    basic_machine=`echo $1 | sed 's/-[^-]*$//'`
+    if [ $basic_machine != $1 ]
+    then os=`echo $1 | sed 's/.*-/-/'`
+    else os=; fi
+    ;;
+esac
+
+### Let's recognize common machines as not being operating systems so
+### that things like config.sub decstation-3100 work.  We also
+### recognize some manufacturers as not being operating systems, so we
+### can provide default operating systems below.
+case $os in
+	-sun*os*)
+		# Prevent following clause from handling this invalid input.
+		;;
+	-dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \
+	-att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \
+	-unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \
+	-convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
+	-c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
+	-harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
+	-apple | -axis | -knuth | -cray | -microblaze*)
+		os=
+		basic_machine=$1
+		;;
+	-bluegene*)
+		os=-cnk
+		;;
+	-sim | -cisco | -oki | -wec | -winbond)
+		os=
+		basic_machine=$1
+		;;
+	-scout)
+		;;
+	-wrs)
+		os=-vxworks
+		basic_machine=$1
+		;;
+	-chorusos*)
+		os=-chorusos
+		basic_machine=$1
+		;;
+	-chorusrdb)
+		os=-chorusrdb
+		basic_machine=$1
+		;;
+	-hiux*)
+		os=-hiuxwe2
+		;;
+	-sco6)
+		os=-sco5v6
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco5)
+		os=-sco3.2v5
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco4)
+		os=-sco3.2v4
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco3.2.[4-9]*)
+		os=`echo $os | sed -e 's/sco3.2./sco3.2v/'`
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco3.2v[4-9]*)
+		# Don't forget version if it is 3.2v4 or newer.
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco5v6*)
+		# Don't forget version if it is 3.2v4 or newer.
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco*)
+		os=-sco3.2v2
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-udk*)
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-isc)
+		os=-isc2.2
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-clix*)
+		basic_machine=clipper-intergraph
+		;;
+	-isc*)
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-lynx*178)
+		os=-lynxos178
+		;;
+	-lynx*5)
+		os=-lynxos5
+		;;
+	-lynx*)
+		os=-lynxos
+		;;
+	-ptx*)
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'`
+		;;
+	-windowsnt*)
+		os=`echo $os | sed -e 's/windowsnt/winnt/'`
+		;;
+	-psos*)
+		os=-psos
+		;;
+	-mint | -mint[0-9]*)
+		basic_machine=m68k-atari
+		os=-mint
+		;;
+esac
+
+# Decode aliases for certain CPU-COMPANY combinations.
+case $basic_machine in
+	# Recognize the basic CPU types without company name.
+	# Some are omitted here because they have special meanings below.
+	1750a | 580 \
+	| a29k \
+	| aarch64 | aarch64_be \
+	| alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \
+	| alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \
+	| am33_2.0 \
+	| arc | arceb \
+	| arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \
+	| avr | avr32 \
+	| be32 | be64 \
+	| bfin \
+	| c4x | c8051 | clipper \
+	| d10v | d30v | dlx | dsp16xx \
+	| epiphany \
+	| fido | fr30 | frv \
+	| h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+	| hexagon \
+	| i370 | i860 | i960 | ia64 \
+	| ip2k | iq2000 \
+	| k1om \
+	| le32 | le64 \
+	| lm32 \
+	| m32c | m32r | m32rle | m68000 | m68k | m88k \
+	| maxq | mb | microblaze | microblazeel | mcore | mep | metag \
+	| mips | mipsbe | mipseb | mipsel | mipsle \
+	| mips16 \
+	| mips64 | mips64el \
+	| mips64octeon | mips64octeonel \
+	| mips64orion | mips64orionel \
+	| mips64r5900 | mips64r5900el \
+	| mips64vr | mips64vrel \
+	| mips64vr4100 | mips64vr4100el \
+	| mips64vr4300 | mips64vr4300el \
+	| mips64vr5000 | mips64vr5000el \
+	| mips64vr5900 | mips64vr5900el \
+	| mipsisa32 | mipsisa32el \
+	| mipsisa32r2 | mipsisa32r2el \
+	| mipsisa64 | mipsisa64el \
+	| mipsisa64r2 | mipsisa64r2el \
+	| mipsisa64sb1 | mipsisa64sb1el \
+	| mipsisa64sr71k | mipsisa64sr71kel \
+	| mipsr5900 | mipsr5900el \
+	| mipstx39 | mipstx39el \
+	| mn10200 | mn10300 \
+	| moxie \
+	| mt \
+	| msp430 \
+	| nds32 | nds32le | nds32be \
+	| nios | nios2 | nios2eb | nios2el \
+	| ns16k | ns32k \
+	| open8 \
+	| or1k | or32 \
+	| pdp10 | pdp11 | pj | pjl \
+	| powerpc | powerpc64 | powerpc64le | powerpcle \
+	| pyramid \
+	| rl78 | rx \
+	| score \
+	| sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \
+	| sh64 | sh64le \
+	| sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \
+	| sparcv8 | sparcv9 | sparcv9b | sparcv9v \
+	| spu \
+	| tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \
+	| ubicom32 \
+	| v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \
+	| we32k \
+	| x86 | xc16x | xstormy16 | xtensa \
+	| z8k | z80)
+		basic_machine=$basic_machine-unknown
+		;;
+	c54x)
+		basic_machine=tic54x-unknown
+		;;
+	c55x)
+		basic_machine=tic55x-unknown
+		;;
+	c6x)
+		basic_machine=tic6x-unknown
+		;;
+	m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip)
+		basic_machine=$basic_machine-unknown
+		os=-none
+		;;
+	m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k)
+		;;
+	ms1)
+		basic_machine=mt-unknown
+		;;
+
+	strongarm | thumb | xscale)
+		basic_machine=arm-unknown
+		;;
+	xgate)
+		basic_machine=$basic_machine-unknown
+		os=-none
+		;;
+	xscaleeb)
+		basic_machine=armeb-unknown
+		;;
+
+	xscaleel)
+		basic_machine=armel-unknown
+		;;
+
+	# We use `pc' rather than `unknown'
+	# because (1) that's what they normally are, and
+	# (2) the word "unknown" tends to confuse beginning users.
+	i*86 | x86_64)
+	  basic_machine=$basic_machine-pc
+	  ;;
+	# Object if more than one company name word.
+	*-*-*)
+		echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+		exit 1
+		;;
+	# Recognize the basic CPU types with company name.
+	580-* \
+	| a29k-* \
+	| aarch64-* | aarch64_be-* \
+	| alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \
+	| alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \
+	| alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \
+	| arm-*  | armbe-* | armle-* | armeb-* | armv*-* \
+	| avr-* | avr32-* \
+	| be32-* | be64-* \
+	| bfin-* | bs2000-* \
+	| c[123]* | c30-* | [cjt]90-* | c4x-* \
+	| c8051-* | clipper-* | craynv-* | cydra-* \
+	| d10v-* | d30v-* | dlx-* \
+	| elxsi-* \
+	| f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \
+	| h8300-* | h8500-* \
+	| hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
+	| hexagon-* \
+	| i*86-* | i860-* | i960-* | ia64-* \
+	| ip2k-* | iq2000-* \
+	| k1om-* \
+	| le32-* | le64-* \
+	| lm32-* \
+	| m32c-* | m32r-* | m32rle-* \
+	| m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
+	| m88110-* | m88k-* | maxq-* | mcore-* | metag-* \
+	| microblaze-* | microblazeel-* \
+	| mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \
+	| mips16-* \
+	| mips64-* | mips64el-* \
+	| mips64octeon-* | mips64octeonel-* \
+	| mips64orion-* | mips64orionel-* \
+	| mips64r5900-* | mips64r5900el-* \
+	| mips64vr-* | mips64vrel-* \
+	| mips64vr4100-* | mips64vr4100el-* \
+	| mips64vr4300-* | mips64vr4300el-* \
+	| mips64vr5000-* | mips64vr5000el-* \
+	| mips64vr5900-* | mips64vr5900el-* \
+	| mipsisa32-* | mipsisa32el-* \
+	| mipsisa32r2-* | mipsisa32r2el-* \
+	| mipsisa64-* | mipsisa64el-* \
+	| mipsisa64r2-* | mipsisa64r2el-* \
+	| mipsisa64sb1-* | mipsisa64sb1el-* \
+	| mipsisa64sr71k-* | mipsisa64sr71kel-* \
+	| mipsr5900-* | mipsr5900el-* \
+	| mipstx39-* | mipstx39el-* \
+	| mmix-* \
+	| mt-* \
+	| msp430-* \
+	| nds32-* | nds32le-* | nds32be-* \
+	| nios-* | nios2-* | nios2eb-* | nios2el-* \
+	| none-* | np1-* | ns16k-* | ns32k-* \
+	| open8-* \
+	| orion-* \
+	| pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
+	| powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \
+	| pyramid-* \
+	| rl78-* | romp-* | rs6000-* | rx-* \
+	| sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \
+	| shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
+	| sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \
+	| sparclite-* \
+	| sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx?-* \
+	| tahoe-* \
+	| tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \
+	| tile*-* \
+	| tron-* \
+	| ubicom32-* \
+	| v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \
+	| vax-* \
+	| we32k-* \
+	| x86-* | x86_64-* | xc16x-* | xps100-* \
+	| xstormy16-* | xtensa*-* \
+	| ymp-* \
+	| z8k-* | z80-*)
+		;;
+	# Recognize the basic CPU types without company name, with glob match.
+	xtensa*)
+		basic_machine=$basic_machine-unknown
+		;;
+	# Recognize the various machine names and aliases which stand
+	# for a CPU type and a company and sometimes even an OS.
+	386bsd)
+		basic_machine=i386-unknown
+		os=-bsd
+		;;
+	3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+		basic_machine=m68000-att
+		;;
+	3b*)
+		basic_machine=we32k-att
+		;;
+	a29khif)
+		basic_machine=a29k-amd
+		os=-udi
+		;;
+	abacus)
+		basic_machine=abacus-unknown
+		;;
+	adobe68k)
+		basic_machine=m68010-adobe
+		os=-scout
+		;;
+	alliant | fx80)
+		basic_machine=fx80-alliant
+		;;
+	altos | altos3068)
+		basic_machine=m68k-altos
+		;;
+	am29k)
+		basic_machine=a29k-none
+		os=-bsd
+		;;
+	amd64)
+		basic_machine=x86_64-pc
+		;;
+	amd64-*)
+		basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	amdahl)
+		basic_machine=580-amdahl
+		os=-sysv
+		;;
+	amiga | amiga-*)
+		basic_machine=m68k-unknown
+		;;
+	amigaos | amigados)
+		basic_machine=m68k-unknown
+		os=-amigaos
+		;;
+	amigaunix | amix)
+		basic_machine=m68k-unknown
+		os=-sysv4
+		;;
+	apollo68)
+		basic_machine=m68k-apollo
+		os=-sysv
+		;;
+	apollo68bsd)
+		basic_machine=m68k-apollo
+		os=-bsd
+		;;
+	aros)
+		basic_machine=i386-pc
+		os=-aros
+		;;
+	aux)
+		basic_machine=m68k-apple
+		os=-aux
+		;;
+	balance)
+		basic_machine=ns32k-sequent
+		os=-dynix
+		;;
+	blackfin)
+		basic_machine=bfin-unknown
+		os=-linux
+		;;
+	blackfin-*)
+		basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'`
+		os=-linux
+		;;
+	bluegene*)
+		basic_machine=powerpc-ibm
+		os=-cnk
+		;;
+	c54x-*)
+		basic_machine=tic54x-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	c55x-*)
+		basic_machine=tic55x-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	c6x-*)
+		basic_machine=tic6x-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	c90)
+		basic_machine=c90-cray
+		os=-unicos
+		;;
+	cegcc)
+		basic_machine=arm-unknown
+		os=-cegcc
+		;;
+	convex-c1)
+		basic_machine=c1-convex
+		os=-bsd
+		;;
+	convex-c2)
+		basic_machine=c2-convex
+		os=-bsd
+		;;
+	convex-c32)
+		basic_machine=c32-convex
+		os=-bsd
+		;;
+	convex-c34)
+		basic_machine=c34-convex
+		os=-bsd
+		;;
+	convex-c38)
+		basic_machine=c38-convex
+		os=-bsd
+		;;
+	cray | j90)
+		basic_machine=j90-cray
+		os=-unicos
+		;;
+	craynv)
+		basic_machine=craynv-cray
+		os=-unicosmp
+		;;
+	cr16 | cr16-*)
+		basic_machine=cr16-unknown
+		os=-elf
+		;;
+	crds | unos)
+		basic_machine=m68k-crds
+		;;
+	crisv32 | crisv32-* | etraxfs*)
+		basic_machine=crisv32-axis
+		;;
+	cris | cris-* | etrax*)
+		basic_machine=cris-axis
+		;;
+	crx)
+		basic_machine=crx-unknown
+		os=-elf
+		;;
+	da30 | da30-*)
+		basic_machine=m68k-da30
+		;;
+	decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn)
+		basic_machine=mips-dec
+		;;
+	decsystem10* | dec10*)
+		basic_machine=pdp10-dec
+		os=-tops10
+		;;
+	decsystem20* | dec20*)
+		basic_machine=pdp10-dec
+		os=-tops20
+		;;
+	delta | 3300 | motorola-3300 | motorola-delta \
+	      | 3300-motorola | delta-motorola)
+		basic_machine=m68k-motorola
+		;;
+	delta88)
+		basic_machine=m88k-motorola
+		os=-sysv3
+		;;
+	dicos)
+		basic_machine=i686-pc
+		os=-dicos
+		;;
+	djgpp)
+		basic_machine=i586-pc
+		os=-msdosdjgpp
+		;;
+	dpx20 | dpx20-*)
+		basic_machine=rs6000-bull
+		os=-bosx
+		;;
+	dpx2* | dpx2*-bull)
+		basic_machine=m68k-bull
+		os=-sysv3
+		;;
+	ebmon29k)
+		basic_machine=a29k-amd
+		os=-ebmon
+		;;
+	elxsi)
+		basic_machine=elxsi-elxsi
+		os=-bsd
+		;;
+	encore | umax | mmax)
+		basic_machine=ns32k-encore
+		;;
+	es1800 | OSE68k | ose68k | ose | OSE)
+		basic_machine=m68k-ericsson
+		os=-ose
+		;;
+	fx2800)
+		basic_machine=i860-alliant
+		;;
+	genix)
+		basic_machine=ns32k-ns
+		;;
+	gmicro)
+		basic_machine=tron-gmicro
+		os=-sysv
+		;;
+	go32)
+		basic_machine=i386-pc
+		os=-go32
+		;;
+	h3050r* | hiux*)
+		basic_machine=hppa1.1-hitachi
+		os=-hiuxwe2
+		;;
+	h8300hms)
+		basic_machine=h8300-hitachi
+		os=-hms
+		;;
+	h8300xray)
+		basic_machine=h8300-hitachi
+		os=-xray
+		;;
+	h8500hms)
+		basic_machine=h8500-hitachi
+		os=-hms
+		;;
+	harris)
+		basic_machine=m88k-harris
+		os=-sysv3
+		;;
+	hp300-*)
+		basic_machine=m68k-hp
+		;;
+	hp300bsd)
+		basic_machine=m68k-hp
+		os=-bsd
+		;;
+	hp300hpux)
+		basic_machine=m68k-hp
+		os=-hpux
+		;;
+	hp3k9[0-9][0-9] | hp9[0-9][0-9])
+		basic_machine=hppa1.0-hp
+		;;
+	hp9k2[0-9][0-9] | hp9k31[0-9])
+		basic_machine=m68000-hp
+		;;
+	hp9k3[2-9][0-9])
+		basic_machine=m68k-hp
+		;;
+	hp9k6[0-9][0-9] | hp6[0-9][0-9])
+		basic_machine=hppa1.0-hp
+		;;
+	hp9k7[0-79][0-9] | hp7[0-79][0-9])
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k78[0-9] | hp78[0-9])
+		# FIXME: really hppa2.0-hp
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+		# FIXME: really hppa2.0-hp
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k8[0-9][13679] | hp8[0-9][13679])
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k8[0-9][0-9] | hp8[0-9][0-9])
+		basic_machine=hppa1.0-hp
+		;;
+	hppa-next)
+		os=-nextstep3
+		;;
+	hppaosf)
+		basic_machine=hppa1.1-hp
+		os=-osf
+		;;
+	hppro)
+		basic_machine=hppa1.1-hp
+		os=-proelf
+		;;
+	i370-ibm* | ibm*)
+		basic_machine=i370-ibm
+		;;
+	i*86v32)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-sysv32
+		;;
+	i*86v4*)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-sysv4
+		;;
+	i*86v)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-sysv
+		;;
+	i*86sol2)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-solaris2
+		;;
+	i386mach)
+		basic_machine=i386-mach
+		os=-mach
+		;;
+	i386-vsta | vsta)
+		basic_machine=i386-unknown
+		os=-vsta
+		;;
+	iris | iris4d)
+		basic_machine=mips-sgi
+		case $os in
+		    -irix*)
+			;;
+		    *)
+			os=-irix4
+			;;
+		esac
+		;;
+	isi68 | isi)
+		basic_machine=m68k-isi
+		os=-sysv
+		;;
+	m68knommu)
+		basic_machine=m68k-unknown
+		os=-linux
+		;;
+	m68knommu-*)
+		basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'`
+		os=-linux
+		;;
+	m88k-omron*)
+		basic_machine=m88k-omron
+		;;
+	magnum | m3230)
+		basic_machine=mips-mips
+		os=-sysv
+		;;
+	merlin)
+		basic_machine=ns32k-utek
+		os=-sysv
+		;;
+	microblaze*)
+		basic_machine=microblaze-xilinx
+		;;
+	mingw64)
+		basic_machine=x86_64-pc
+		os=-mingw64
+		;;
+	mingw32)
+		basic_machine=i686-pc
+		os=-mingw32
+		;;
+	mingw32ce)
+		basic_machine=arm-unknown
+		os=-mingw32ce
+		;;
+	miniframe)
+		basic_machine=m68000-convergent
+		;;
+	*mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*)
+		basic_machine=m68k-atari
+		os=-mint
+		;;
+	mips3*-*)
+		basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`
+		;;
+	mips3*)
+		basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown
+		;;
+	monitor)
+		basic_machine=m68k-rom68k
+		os=-coff
+		;;
+	morphos)
+		basic_machine=powerpc-unknown
+		os=-morphos
+		;;
+	msdos)
+		basic_machine=i386-pc
+		os=-msdos
+		;;
+	ms1-*)
+		basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'`
+		;;
+	msys)
+		basic_machine=i686-pc
+		os=-msys
+		;;
+	mvs)
+		basic_machine=i370-ibm
+		os=-mvs
+		;;
+	nacl)
+		basic_machine=le32-unknown
+		os=-nacl
+		;;
+	ncr3000)
+		basic_machine=i486-ncr
+		os=-sysv4
+		;;
+	netbsd386)
+		basic_machine=i386-unknown
+		os=-netbsd
+		;;
+	netwinder)
+		basic_machine=armv4l-rebel
+		os=-linux
+		;;
+	news | news700 | news800 | news900)
+		basic_machine=m68k-sony
+		os=-newsos
+		;;
+	news1000)
+		basic_machine=m68030-sony
+		os=-newsos
+		;;
+	news-3600 | risc-news)
+		basic_machine=mips-sony
+		os=-newsos
+		;;
+	necv70)
+		basic_machine=v70-nec
+		os=-sysv
+		;;
+	next | m*-next )
+		basic_machine=m68k-next
+		case $os in
+		    -nextstep* )
+			;;
+		    -ns2*)
+		      os=-nextstep2
+			;;
+		    *)
+		      os=-nextstep3
+			;;
+		esac
+		;;
+	nh3000)
+		basic_machine=m68k-harris
+		os=-cxux
+		;;
+	nh[45]000)
+		basic_machine=m88k-harris
+		os=-cxux
+		;;
+	nindy960)
+		basic_machine=i960-intel
+		os=-nindy
+		;;
+	mon960)
+		basic_machine=i960-intel
+		os=-mon960
+		;;
+	nonstopux)
+		basic_machine=mips-compaq
+		os=-nonstopux
+		;;
+	np1)
+		basic_machine=np1-gould
+		;;
+	neo-tandem)
+		basic_machine=neo-tandem
+		;;
+	nse-tandem)
+		basic_machine=nse-tandem
+		;;
+	nsr-tandem)
+		basic_machine=nsr-tandem
+		;;
+	op50n-* | op60c-*)
+		basic_machine=hppa1.1-oki
+		os=-proelf
+		;;
+	openrisc | openrisc-*)
+		basic_machine=or32-unknown
+		;;
+	os400)
+		basic_machine=powerpc-ibm
+		os=-os400
+		;;
+	OSE68000 | ose68000)
+		basic_machine=m68000-ericsson
+		os=-ose
+		;;
+	os68k)
+		basic_machine=m68k-none
+		os=-os68k
+		;;
+	pa-hitachi)
+		basic_machine=hppa1.1-hitachi
+		os=-hiuxwe2
+		;;
+	paragon)
+		basic_machine=i860-intel
+		os=-osf
+		;;
+	parisc)
+		basic_machine=hppa-unknown
+		os=-linux
+		;;
+	parisc-*)
+		basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'`
+		os=-linux
+		;;
+	pbd)
+		basic_machine=sparc-tti
+		;;
+	pbb)
+		basic_machine=m68k-tti
+		;;
+	pc532 | pc532-*)
+		basic_machine=ns32k-pc532
+		;;
+	pc98)
+		basic_machine=i386-pc
+		;;
+	pc98-*)
+		basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentium | p5 | k5 | k6 | nexgen | viac3)
+		basic_machine=i586-pc
+		;;
+	pentiumpro | p6 | 6x86 | athlon | athlon_*)
+		basic_machine=i686-pc
+		;;
+	pentiumii | pentium2 | pentiumiii | pentium3)
+		basic_machine=i686-pc
+		;;
+	pentium4)
+		basic_machine=i786-pc
+		;;
+	pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+		basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentiumpro-* | p6-* | 6x86-* | athlon-*)
+		basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+		basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentium4-*)
+		basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pn)
+		basic_machine=pn-gould
+		;;
+	power)	basic_machine=power-ibm
+		;;
+	ppc | ppcbe)	basic_machine=powerpc-unknown
+		;;
+	ppc-* | ppcbe-*)
+		basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ppcle | powerpclittle | ppc-le | powerpc-little)
+		basic_machine=powerpcle-unknown
+		;;
+	ppcle-* | powerpclittle-*)
+		basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ppc64)	basic_machine=powerpc64-unknown
+		;;
+	ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ppc64le | powerpc64little | ppc64-le | powerpc64-little)
+		basic_machine=powerpc64le-unknown
+		;;
+	ppc64le-* | powerpc64little-*)
+		basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ps2)
+		basic_machine=i386-ibm
+		;;
+	pw32)
+		basic_machine=i586-unknown
+		os=-pw32
+		;;
+	rdos | rdos64)
+		basic_machine=x86_64-pc
+		os=-rdos
+		;;
+	rdos32)
+		basic_machine=i386-pc
+		os=-rdos
+		;;
+	rom68k)
+		basic_machine=m68k-rom68k
+		os=-coff
+		;;
+	rm[46]00)
+		basic_machine=mips-siemens
+		;;
+	rtpc | rtpc-*)
+		basic_machine=romp-ibm
+		;;
+	s390 | s390-*)
+		basic_machine=s390-ibm
+		;;
+	s390x | s390x-*)
+		basic_machine=s390x-ibm
+		;;
+	sa29200)
+		basic_machine=a29k-amd
+		os=-udi
+		;;
+	sb1)
+		basic_machine=mipsisa64sb1-unknown
+		;;
+	sb1el)
+		basic_machine=mipsisa64sb1el-unknown
+		;;
+	sde)
+		basic_machine=mipsisa32-sde
+		os=-elf
+		;;
+	sei)
+		basic_machine=mips-sei
+		os=-seiux
+		;;
+	sequent)
+		basic_machine=i386-sequent
+		;;
+	sh)
+		basic_machine=sh-hitachi
+		os=-hms
+		;;
+	sh5el)
+		basic_machine=sh5le-unknown
+		;;
+	sh64)
+		basic_machine=sh64-unknown
+		;;
+	sparclite-wrs | simso-wrs)
+		basic_machine=sparclite-wrs
+		os=-vxworks
+		;;
+	sps7)
+		basic_machine=m68k-bull
+		os=-sysv2
+		;;
+	spur)
+		basic_machine=spur-unknown
+		;;
+	st2000)
+		basic_machine=m68k-tandem
+		;;
+	stratus)
+		basic_machine=i860-stratus
+		os=-sysv4
+		;;
+	strongarm-* | thumb-*)
+		basic_machine=arm-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	sun2)
+		basic_machine=m68000-sun
+		;;
+	sun2os3)
+		basic_machine=m68000-sun
+		os=-sunos3
+		;;
+	sun2os4)
+		basic_machine=m68000-sun
+		os=-sunos4
+		;;
+	sun3os3)
+		basic_machine=m68k-sun
+		os=-sunos3
+		;;
+	sun3os4)
+		basic_machine=m68k-sun
+		os=-sunos4
+		;;
+	sun4os3)
+		basic_machine=sparc-sun
+		os=-sunos3
+		;;
+	sun4os4)
+		basic_machine=sparc-sun
+		os=-sunos4
+		;;
+	sun4sol2)
+		basic_machine=sparc-sun
+		os=-solaris2
+		;;
+	sun3 | sun3-*)
+		basic_machine=m68k-sun
+		;;
+	sun4)
+		basic_machine=sparc-sun
+		;;
+	sun386 | sun386i | roadrunner)
+		basic_machine=i386-sun
+		;;
+	sv1)
+		basic_machine=sv1-cray
+		os=-unicos
+		;;
+	symmetry)
+		basic_machine=i386-sequent
+		os=-dynix
+		;;
+	t3e)
+		basic_machine=alphaev5-cray
+		os=-unicos
+		;;
+	t90)
+		basic_machine=t90-cray
+		os=-unicos
+		;;
+	tile*)
+		basic_machine=$basic_machine-unknown
+		os=-linux-gnu
+		;;
+	tx39)
+		basic_machine=mipstx39-unknown
+		;;
+	tx39el)
+		basic_machine=mipstx39el-unknown
+		;;
+	toad1)
+		basic_machine=pdp10-xkl
+		os=-tops20
+		;;
+	tower | tower-32)
+		basic_machine=m68k-ncr
+		;;
+	tpf)
+		basic_machine=s390x-ibm
+		os=-tpf
+		;;
+	udi29k)
+		basic_machine=a29k-amd
+		os=-udi
+		;;
+	ultra3)
+		basic_machine=a29k-nyu
+		os=-sym1
+		;;
+	v810 | necv810)
+		basic_machine=v810-nec
+		os=-none
+		;;
+	vaxv)
+		basic_machine=vax-dec
+		os=-sysv
+		;;
+	vms)
+		basic_machine=vax-dec
+		os=-vms
+		;;
+	vpp*|vx|vx-*)
+		basic_machine=f301-fujitsu
+		;;
+	vxworks960)
+		basic_machine=i960-wrs
+		os=-vxworks
+		;;
+	vxworks68)
+		basic_machine=m68k-wrs
+		os=-vxworks
+		;;
+	vxworks29k)
+		basic_machine=a29k-wrs
+		os=-vxworks
+		;;
+	w65*)
+		basic_machine=w65-wdc
+		os=-none
+		;;
+	w89k-*)
+		basic_machine=hppa1.1-winbond
+		os=-proelf
+		;;
+	xbox)
+		basic_machine=i686-pc
+		os=-mingw32
+		;;
+	xps | xps100)
+		basic_machine=xps100-honeywell
+		;;
+	xscale-* | xscalee[bl]-*)
+		basic_machine=`echo $basic_machine | sed 's/^xscale/arm/'`
+		;;
+	ymp)
+		basic_machine=ymp-cray
+		os=-unicos
+		;;
+	z8k-*-coff)
+		basic_machine=z8k-unknown
+		os=-sim
+		;;
+	z80-*-coff)
+		basic_machine=z80-unknown
+		os=-sim
+		;;
+	none)
+		basic_machine=none-none
+		os=-none
+		;;
+
+# Here we handle the default manufacturer of certain CPU types.  It is in
+# some cases the only manufacturer, in others, it is the most popular.
+	w89k)
+		basic_machine=hppa1.1-winbond
+		;;
+	op50n)
+		basic_machine=hppa1.1-oki
+		;;
+	op60c)
+		basic_machine=hppa1.1-oki
+		;;
+	romp)
+		basic_machine=romp-ibm
+		;;
+	mmix)
+		basic_machine=mmix-knuth
+		;;
+	rs6000)
+		basic_machine=rs6000-ibm
+		;;
+	vax)
+		basic_machine=vax-dec
+		;;
+	pdp10)
+		# there are many clones, so DEC is not a safe bet
+		basic_machine=pdp10-unknown
+		;;
+	pdp11)
+		basic_machine=pdp11-dec
+		;;
+	we32k)
+		basic_machine=we32k-att
+		;;
+	sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele)
+		basic_machine=sh-unknown
+		;;
+	sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v)
+		basic_machine=sparc-sun
+		;;
+	cydra)
+		basic_machine=cydra-cydrome
+		;;
+	orion)
+		basic_machine=orion-highlevel
+		;;
+	orion105)
+		basic_machine=clipper-highlevel
+		;;
+	mac | mpw | mac-mpw)
+		basic_machine=m68k-apple
+		;;
+	pmac | pmac-mpw)
+		basic_machine=powerpc-apple
+		;;
+	*-unknown)
+		# Make sure to match an already-canonicalized machine name.
+		;;
+	*)
+		echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+		exit 1
+		;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $basic_machine in
+	*-digital*)
+		basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'`
+		;;
+	*-commodore*)
+		basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'`
+		;;
+	*)
+		;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if [ x"$os" != x"" ]
+then
+case $os in
+	# First match some system type aliases
+	# that might get confused with valid system types.
+	# -solaris* is a basic system type, with this one exception.
+	-auroraux)
+		os=-auroraux
+		;;
+	-solaris1 | -solaris1.*)
+		os=`echo $os | sed -e 's|solaris1|sunos4|'`
+		;;
+	-solaris)
+		os=-solaris2
+		;;
+	-svr4*)
+		os=-sysv4
+		;;
+	-unixware*)
+		os=-sysv4.2uw
+		;;
+	-gnu/linux*)
+		os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'`
+		;;
+	# First accept the basic system types.
+	# The portable systems comes first.
+	# Each alternative MUST END IN A *, to match a version number.
+	# -sysv* is not here because it comes later, after sysvr4.
+	-gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
+	      | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\
+	      | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \
+	      | -sym* | -kopensolaris* | -plan9* \
+	      | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
+	      | -aos* | -aros* \
+	      | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
+	      | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
+	      | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \
+	      | -bitrig* | -openbsd* | -solidbsd* \
+	      | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \
+	      | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
+	      | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
+	      | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
+	      | -chorusos* | -chorusrdb* | -cegcc* \
+	      | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
+	      | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \
+	      | -linux-newlib* | -linux-musl* | -linux-uclibc* \
+	      | -uxpv* | -beos* | -mpeix* | -udk* \
+	      | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
+	      | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
+	      | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
+	      | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
+	      | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
+	      | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \
+	      | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es*)
+	# Remember, each alternative MUST END IN *, to match a version number.
+		;;
+	-qnx*)
+		case $basic_machine in
+		    x86-* | i*86-*)
+			;;
+		    *)
+			os=-nto$os
+			;;
+		esac
+		;;
+	-nto-qnx*)
+		;;
+	-nto*)
+		os=`echo $os | sed -e 's|nto|nto-qnx|'`
+		;;
+	-sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \
+	      | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \
+	      | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*)
+		;;
+	-mac*)
+		os=`echo $os | sed -e 's|mac|macos|'`
+		;;
+	-linux-dietlibc)
+		os=-linux-dietlibc
+		;;
+	-linux*)
+		os=`echo $os | sed -e 's|linux|linux-gnu|'`
+		;;
+	-sunos5*)
+		os=`echo $os | sed -e 's|sunos5|solaris2|'`
+		;;
+	-sunos6*)
+		os=`echo $os | sed -e 's|sunos6|solaris3|'`
+		;;
+	-opened*)
+		os=-openedition
+		;;
+	-os400*)
+		os=-os400
+		;;
+	-wince*)
+		os=-wince
+		;;
+	-osfrose*)
+		os=-osfrose
+		;;
+	-osf*)
+		os=-osf
+		;;
+	-utek*)
+		os=-bsd
+		;;
+	-dynix*)
+		os=-bsd
+		;;
+	-acis*)
+		os=-aos
+		;;
+	-atheos*)
+		os=-atheos
+		;;
+	-syllable*)
+		os=-syllable
+		;;
+	-386bsd)
+		os=-bsd
+		;;
+	-ctix* | -uts*)
+		os=-sysv
+		;;
+	-nova*)
+		os=-rtmk-nova
+		;;
+	-ns2 )
+		os=-nextstep2
+		;;
+	-nsk*)
+		os=-nsk
+		;;
+	# Preserve the version number of sinix5.
+	-sinix5.*)
+		os=`echo $os | sed -e 's|sinix|sysv|'`
+		;;
+	-sinix*)
+		os=-sysv4
+		;;
+	-tpf*)
+		os=-tpf
+		;;
+	-triton*)
+		os=-sysv3
+		;;
+	-oss*)
+		os=-sysv3
+		;;
+	-svr4)
+		os=-sysv4
+		;;
+	-svr3)
+		os=-sysv3
+		;;
+	-sysvr4)
+		os=-sysv4
+		;;
+	# This must come after -sysvr4.
+	-sysv*)
+		;;
+	-ose*)
+		os=-ose
+		;;
+	-es1800*)
+		os=-ose
+		;;
+	-xenix)
+		os=-xenix
+		;;
+	-*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+		os=-mint
+		;;
+	-aros*)
+		os=-aros
+		;;
+	-zvmoe)
+		os=-zvmoe
+		;;
+	-dicos*)
+		os=-dicos
+		;;
+	-nacl*)
+		;;
+	-none)
+		;;
+	*)
+		# Get rid of the `-' at the beginning of $os.
+		os=`echo $os | sed 's/[^-]*-//'`
+		echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2
+		exit 1
+		;;
+esac
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system.  Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+case $basic_machine in
+	score-*)
+		os=-elf
+		;;
+	spu-*)
+		os=-elf
+		;;
+	*-acorn)
+		os=-riscix1.2
+		;;
+	arm*-rebel)
+		os=-linux
+		;;
+	arm*-semi)
+		os=-aout
+		;;
+	c4x-* | tic4x-*)
+		os=-coff
+		;;
+	c8051-*)
+		os=-elf
+		;;
+	hexagon-*)
+		os=-elf
+		;;
+	tic54x-*)
+		os=-coff
+		;;
+	tic55x-*)
+		os=-coff
+		;;
+	tic6x-*)
+		os=-coff
+		;;
+	# This must come before the *-dec entry.
+	pdp10-*)
+		os=-tops20
+		;;
+	pdp11-*)
+		os=-none
+		;;
+	*-dec | vax-*)
+		os=-ultrix4.2
+		;;
+	m68*-apollo)
+		os=-domain
+		;;
+	i386-sun)
+		os=-sunos4.0.2
+		;;
+	m68000-sun)
+		os=-sunos3
+		;;
+	m68*-cisco)
+		os=-aout
+		;;
+	mep-*)
+		os=-elf
+		;;
+	mips*-cisco)
+		os=-elf
+		;;
+	mips*-*)
+		os=-elf
+		;;
+	or1k-*)
+		os=-elf
+		;;
+	or32-*)
+		os=-coff
+		;;
+	*-tti)	# must be before sparc entry or we get the wrong os.
+		os=-sysv3
+		;;
+	sparc-* | *-sun)
+		os=-sunos4.1.1
+		;;
+	*-be)
+		os=-beos
+		;;
+	*-haiku)
+		os=-haiku
+		;;
+	*-ibm)
+		os=-aix
+		;;
+	*-knuth)
+		os=-mmixware
+		;;
+	*-wec)
+		os=-proelf
+		;;
+	*-winbond)
+		os=-proelf
+		;;
+	*-oki)
+		os=-proelf
+		;;
+	*-hp)
+		os=-hpux
+		;;
+	*-hitachi)
+		os=-hiux
+		;;
+	i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+		os=-sysv
+		;;
+	*-cbm)
+		os=-amigaos
+		;;
+	*-dg)
+		os=-dgux
+		;;
+	*-dolphin)
+		os=-sysv3
+		;;
+	m68k-ccur)
+		os=-rtu
+		;;
+	m88k-omron*)
+		os=-luna
+		;;
+	*-next )
+		os=-nextstep
+		;;
+	*-sequent)
+		os=-ptx
+		;;
+	*-crds)
+		os=-unos
+		;;
+	*-ns)
+		os=-genix
+		;;
+	i370-*)
+		os=-mvs
+		;;
+	*-next)
+		os=-nextstep3
+		;;
+	*-gould)
+		os=-sysv
+		;;
+	*-highlevel)
+		os=-bsd
+		;;
+	*-encore)
+		os=-bsd
+		;;
+	*-sgi)
+		os=-irix
+		;;
+	*-siemens)
+		os=-sysv4
+		;;
+	*-masscomp)
+		os=-rtu
+		;;
+	f30[01]-fujitsu | f700-fujitsu)
+		os=-uxpv
+		;;
+	*-rom68k)
+		os=-coff
+		;;
+	*-*bug)
+		os=-coff
+		;;
+	*-apple)
+		os=-macos
+		;;
+	*-atari*)
+		os=-mint
+		;;
+	*)
+		os=-none
+		;;
+esac
+fi
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer.  We pick the logical manufacturer.
+vendor=unknown
+case $basic_machine in
+	*-unknown)
+		case $os in
+			-riscix*)
+				vendor=acorn
+				;;
+			-sunos*)
+				vendor=sun
+				;;
+			-cnk*|-aix*)
+				vendor=ibm
+				;;
+			-beos*)
+				vendor=be
+				;;
+			-hpux*)
+				vendor=hp
+				;;
+			-mpeix*)
+				vendor=hp
+				;;
+			-hiux*)
+				vendor=hitachi
+				;;
+			-unos*)
+				vendor=crds
+				;;
+			-dgux*)
+				vendor=dg
+				;;
+			-luna*)
+				vendor=omron
+				;;
+			-genix*)
+				vendor=ns
+				;;
+			-mvs* | -opened*)
+				vendor=ibm
+				;;
+			-os400*)
+				vendor=ibm
+				;;
+			-ptx*)
+				vendor=sequent
+				;;
+			-tpf*)
+				vendor=ibm
+				;;
+			-vxsim* | -vxworks* | -windiss*)
+				vendor=wrs
+				;;
+			-aux*)
+				vendor=apple
+				;;
+			-hms*)
+				vendor=hitachi
+				;;
+			-mpw* | -macos*)
+				vendor=apple
+				;;
+			-*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+				vendor=atari
+				;;
+			-vos*)
+				vendor=stratus
+				;;
+		esac
+		basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"`
+		;;
+esac
+
+echo $basic_machine$os
+exit
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/ast/build-aux/depcomp b/ast/build-aux/depcomp
new file mode 100755
index 0000000..4ebd5b3
--- /dev/null
+++ b/ast/build-aux/depcomp
@@ -0,0 +1,791 @@
+#! /bin/sh
+# depcomp - compile a program generating dependencies as side-effects
+
+scriptversion=2013-05-30.07; # UTC
+
+# Copyright (C) 1999-2013 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Originally written by Alexandre Oliva <[email-masked]>.
+
+case $1 in
+  '')
+    echo "$0: No command.  Try '$0 --help' for more information." 1>&2
+    exit 1;
+    ;;
+  -h | --h*)
+    cat <<\EOF
+Usage: depcomp [--help] [--version] PROGRAM [ARGS]
+
+Run PROGRAMS ARGS to compile a file, generating dependencies
+as side-effects.
+
+Environment variables:
+  depmode     Dependency tracking mode.
+  source      Source file read by 'PROGRAMS ARGS'.
+  object      Object file output by 'PROGRAMS ARGS'.
+  DEPDIR      directory where to store dependencies.
+  depfile     Dependency file to output.
+  tmpdepfile  Temporary file to use when outputting dependencies.
+  libtool     Whether libtool is used (yes/no).
+
+Report bugs to <[email-masked]>.
+EOF
+    exit $?
+    ;;
+  -v | --v*)
+    echo "depcomp $scriptversion"
+    exit $?
+    ;;
+esac
+
+# Get the directory component of the given path, and save it in the
+# global variables '$dir'.  Note that this directory component will
+# be either empty or ending with a '/' character.  This is deliberate.
+set_dir_from ()
+{
+  case $1 in
+    */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;;
+      *) dir=;;
+  esac
+}
+
+# Get the suffix-stripped basename of the given path, and save it the
+# global variable '$base'.
+set_base_from ()
+{
+  base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'`
+}
+
+# If no dependency file was actually created by the compiler invocation,
+# we still have to create a dummy depfile, to avoid errors with the
+# Makefile "include basename.Plo" scheme.
+make_dummy_depfile ()
+{
+  echo "#dummy" > "$depfile"
+}
+
+# Factor out some common post-processing of the generated depfile.
+# Requires the auxiliary global variable '$tmpdepfile' to be set.
+aix_post_process_depfile ()
+{
+  # If the compiler actually managed to produce a dependency file,
+  # post-process it.
+  if test -f "$tmpdepfile"; then
+    # Each line is of the form 'foo.o: dependency.h'.
+    # Do two passes, one to just change these to
+    #   $object: dependency.h
+    # and one to simply output
+    #   dependency.h:
+    # which is needed to avoid the deleted-header problem.
+    { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile"
+      sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile"
+    } > "$depfile"
+    rm -f "$tmpdepfile"
+  else
+    make_dummy_depfile
+  fi
+}
+
+# A tabulation character.
+tab='	'
+# A newline character.
+nl='
+'
+# Character ranges might be problematic outside the C locale.
+# These definitions help.
+upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ
+lower=abcdefghijklmnopqrstuvwxyz
+digits=0123456789
+alpha=${upper}${lower}
+
+if test -z "$depmode" || test -z "$source" || test -z "$object"; then
+  echo "depcomp: Variables source, object and depmode must be set" 1>&2
+  exit 1
+fi
+
+# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po.
+depfile=${depfile-`echo "$object" |
+  sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`}
+tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
+
+rm -f "$tmpdepfile"
+
+# Avoid interferences from the environment.
+gccflag= dashmflag=
+
+# Some modes work just like other modes, but use different flags.  We
+# parameterize here, but still list the modes in the big case below,
+# to make depend.m4 easier to write.  Note that we *cannot* use a case
+# here, because this file can only contain one case statement.
+if test "$depmode" = hp; then
+  # HP compiler uses -M and no extra arg.
+  gccflag=-M
+  depmode=gcc
+fi
+
+if test "$depmode" = dashXmstdout; then
+  # This is just like dashmstdout with a different argument.
+  dashmflag=-xM
+  depmode=dashmstdout
+fi
+
+cygpath_u="cygpath -u -f -"
+if test "$depmode" = msvcmsys; then
+  # This is just like msvisualcpp but w/o cygpath translation.
+  # Just convert the backslash-escaped backslashes to single forward
+  # slashes to satisfy depend.m4
+  cygpath_u='sed s,\\\\,/,g'
+  depmode=msvisualcpp
+fi
+
+if test "$depmode" = msvc7msys; then
+  # This is just like msvc7 but w/o cygpath translation.
+  # Just convert the backslash-escaped backslashes to single forward
+  # slashes to satisfy depend.m4
+  cygpath_u='sed s,\\\\,/,g'
+  depmode=msvc7
+fi
+
+if test "$depmode" = xlc; then
+  # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information.
+  gccflag=-qmakedep=gcc,-MF
+  depmode=gcc
+fi
+
+case "$depmode" in
+gcc3)
+## gcc 3 implements dependency tracking that does exactly what
+## we want.  Yay!  Note: for some reason libtool 1.4 doesn't like
+## it if -MD -MP comes after the -MF stuff.  Hmm.
+## Unfortunately, FreeBSD c89 acceptance of flags depends upon
+## the command line argument order; so add the flags where they
+## appear in depend2.am.  Note that the slowdown incurred here
+## affects only configure: in makefiles, %FASTDEP% shortcuts this.
+  for arg
+  do
+    case $arg in
+    -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
+    *)  set fnord "$@" "$arg" ;;
+    esac
+    shift # fnord
+    shift # $arg
+  done
+  "$@"
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  mv "$tmpdepfile" "$depfile"
+  ;;
+
+gcc)
+## Note that this doesn't just cater to obsosete pre-3.x GCC compilers.
+## but also to in-use compilers like IMB xlc/xlC and the HP C compiler.
+## (see the conditional assignment to $gccflag above).
+## There are various ways to get dependency output from gcc.  Here's
+## why we pick this rather obscure method:
+## - Don't want to use -MD because we'd like the dependencies to end
+##   up in a subdir.  Having to rename by hand is ugly.
+##   (We might end up doing this anyway to support other compilers.)
+## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
+##   -MM, not -M (despite what the docs say).  Also, it might not be
+##   supported by the other compilers which use the 'gcc' depmode.
+## - Using -M directly means running the compiler twice (even worse
+##   than renaming).
+  if test -z "$gccflag"; then
+    gccflag=-MD,
+  fi
+  "$@" -Wp,"$gccflag$tmpdepfile"
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  # The second -e expression handles DOS-style file names with drive
+  # letters.
+  sed -e 's/^[^:]*: / /' \
+      -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
+## This next piece of magic avoids the "deleted header file" problem.
+## The problem is that when a header file which appears in a .P file
+## is deleted, the dependency causes make to die (because there is
+## typically no way to rebuild the header).  We avoid this by adding
+## dummy dependencies for each header file.  Too bad gcc doesn't do
+## this for us directly.
+## Some versions of gcc put a space before the ':'.  On the theory
+## that the space means something, we add a space to the output as
+## well.  hp depmode also adds that space, but also prefixes the VPATH
+## to the object.  Take care to not repeat it in the output.
+## Some versions of the HPUX 10.20 sed can't process this invocation
+## correctly.  Breaking it into two sed invocations is a workaround.
+  tr ' ' "$nl" < "$tmpdepfile" \
+    | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \
+    | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+hp)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+sgi)
+  if test "$libtool" = yes; then
+    "$@" "-Wp,-MDupdate,$tmpdepfile"
+  else
+    "$@" -MDupdate "$tmpdepfile"
+  fi
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+
+  if test -f "$tmpdepfile"; then  # yes, the sourcefile depend on other files
+    echo "$object : \\" > "$depfile"
+    # Clip off the initial element (the dependent).  Don't try to be
+    # clever and replace this with sed code, as IRIX sed won't handle
+    # lines with more than a fixed number of characters (4096 in
+    # IRIX 6.2 sed, 8192 in IRIX 6.5).  We also remove comment lines;
+    # the IRIX cc adds comments like '#:fec' to the end of the
+    # dependency line.
+    tr ' ' "$nl" < "$tmpdepfile" \
+      | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \
+      | tr "$nl" ' ' >> "$depfile"
+    echo >> "$depfile"
+    # The second pass generates a dummy entry for each header file.
+    tr ' ' "$nl" < "$tmpdepfile" \
+      | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
+      >> "$depfile"
+  else
+    make_dummy_depfile
+  fi
+  rm -f "$tmpdepfile"
+  ;;
+
+xlc)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+aix)
+  # The C for AIX Compiler uses -M and outputs the dependencies
+  # in a .u file.  In older versions, this file always lives in the
+  # current directory.  Also, the AIX compiler puts '$object:' at the
+  # start of each line; $object doesn't have directory information.
+  # Version 6 uses the directory in both cases.
+  set_dir_from "$object"
+  set_base_from "$object"
+  if test "$libtool" = yes; then
+    tmpdepfile1=$dir$base.u
+    tmpdepfile2=$base.u
+    tmpdepfile3=$dir.libs/$base.u
+    "$@" -Wc,-M
+  else
+    tmpdepfile1=$dir$base.u
+    tmpdepfile2=$dir$base.u
+    tmpdepfile3=$dir$base.u
+    "$@" -M
+  fi
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+    exit $stat
+  fi
+
+  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+  do
+    test -f "$tmpdepfile" && break
+  done
+  aix_post_process_depfile
+  ;;
+
+tcc)
+  # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26
+  # FIXME: That version still under development at the moment of writing.
+  #        Make that this statement remains true also for stable, released
+  #        versions.
+  # It will wrap lines (doesn't matter whether long or short) with a
+  # trailing '\', as in:
+  #
+  #   foo.o : \
+  #    foo.c \
+  #    foo.h \
+  #
+  # It will put a trailing '\' even on the last line, and will use leading
+  # spaces rather than leading tabs (at least since its commit 0394caf7
+  # "Emit spaces for -MD").
+  "$@" -MD -MF "$tmpdepfile"
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'.
+  # We have to change lines of the first kind to '$object: \'.
+  sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile"
+  # And for each line of the second kind, we have to emit a 'dep.h:'
+  # dummy dependency, to avoid the deleted-header problem.
+  sed -n -e 's|^  *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+## The order of this option in the case statement is important, since the
+## shell code in configure will try each of these formats in the order
+## listed in this file.  A plain '-MD' option would be understood by many
+## compilers, so we must ensure this comes after the gcc and icc options.
+pgcc)
+  # Portland's C compiler understands '-MD'.
+  # Will always output deps to 'file.d' where file is the root name of the
+  # source file under compilation, even if file resides in a subdirectory.
+  # The object file name does not affect the name of the '.d' file.
+  # pgcc 10.2 will output
+  #    foo.o: sub/foo.c sub/foo.h
+  # and will wrap long lines using '\' :
+  #    foo.o: sub/foo.c ... \
+  #     sub/foo.h ... \
+  #     ...
+  set_dir_from "$object"
+  # Use the source, not the object, to determine the base name, since
+  # that's sadly what pgcc will do too.
+  set_base_from "$source"
+  tmpdepfile=$base.d
+
+  # For projects that build the same source file twice into different object
+  # files, the pgcc approach of using the *source* file root name can cause
+  # problems in parallel builds.  Use a locking strategy to avoid stomping on
+  # the same $tmpdepfile.
+  lockdir=$base.d-lock
+  trap "
+    echo '$0: caught signal, cleaning up...' >&2
+    rmdir '$lockdir'
+    exit 1
+  " 1 2 13 15
+  numtries=100
+  i=$numtries
+  while test $i -gt 0; do
+    # mkdir is a portable test-and-set.
+    if mkdir "$lockdir" 2>/dev/null; then
+      # This process acquired the lock.
+      "$@" -MD
+      stat=$?
+      # Release the lock.
+      rmdir "$lockdir"
+      break
+    else
+      # If the lock is being held by a different process, wait
+      # until the winning process is done or we timeout.
+      while test -d "$lockdir" && test $i -gt 0; do
+        sleep 1
+        i=`expr $i - 1`
+      done
+    fi
+    i=`expr $i - 1`
+  done
+  trap - 1 2 13 15
+  if test $i -le 0; then
+    echo "$0: failed to acquire lock after $numtries attempts" >&2
+    echo "$0: check lockdir '$lockdir'" >&2
+    exit 1
+  fi
+
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  # Each line is of the form `foo.o: dependent.h',
+  # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'.
+  # Do two passes, one to just change these to
+  # `$object: dependent.h' and one to simply `dependent.h:'.
+  sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile"
+  # Some versions of the HPUX 10.20 sed can't process this invocation
+  # correctly.  Breaking it into two sed invocations is a workaround.
+  sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \
+    | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+hp2)
+  # The "hp" stanza above does not work with aCC (C++) and HP's ia64
+  # compilers, which have integrated preprocessors.  The correct option
+  # to use with these is +Maked; it writes dependencies to a file named
+  # 'foo.d', which lands next to the object file, wherever that
+  # happens to be.
+  # Much of this is similar to the tru64 case; see comments there.
+  set_dir_from  "$object"
+  set_base_from "$object"
+  if test "$libtool" = yes; then
+    tmpdepfile1=$dir$base.d
+    tmpdepfile2=$dir.libs/$base.d
+    "$@" -Wc,+Maked
+  else
+    tmpdepfile1=$dir$base.d
+    tmpdepfile2=$dir$base.d
+    "$@" +Maked
+  fi
+  stat=$?
+  if test $stat -ne 0; then
+     rm -f "$tmpdepfile1" "$tmpdepfile2"
+     exit $stat
+  fi
+
+  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
+  do
+    test -f "$tmpdepfile" && break
+  done
+  if test -f "$tmpdepfile"; then
+    sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile"
+    # Add 'dependent.h:' lines.
+    sed -ne '2,${
+               s/^ *//
+               s/ \\*$//
+               s/$/:/
+               p
+             }' "$tmpdepfile" >> "$depfile"
+  else
+    make_dummy_depfile
+  fi
+  rm -f "$tmpdepfile" "$tmpdepfile2"
+  ;;
+
+tru64)
+  # The Tru64 compiler uses -MD to generate dependencies as a side
+  # effect.  'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'.
+  # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
+  # dependencies in 'foo.d' instead, so we check for that too.
+  # Subdirectories are respected.
+  set_dir_from  "$object"
+  set_base_from "$object"
+
+  if test "$libtool" = yes; then
+    # Libtool generates 2 separate objects for the 2 libraries.  These
+    # two compilations output dependencies in $dir.libs/$base.o.d and
+    # in $dir$base.o.d.  We have to check for both files, because
+    # one of the two compilations can be disabled.  We should prefer
+    # $dir$base.o.d over $dir.libs/$base.o.d because the latter is
+    # automatically cleaned when .libs/ is deleted, while ignoring
+    # the former would cause a distcleancheck panic.
+    tmpdepfile1=$dir$base.o.d          # libtool 1.5
+    tmpdepfile2=$dir.libs/$base.o.d    # Likewise.
+    tmpdepfile3=$dir.libs/$base.d      # Compaq CCC V6.2-504
+    "$@" -Wc,-MD
+  else
+    tmpdepfile1=$dir$base.d
+    tmpdepfile2=$dir$base.d
+    tmpdepfile3=$dir$base.d
+    "$@" -MD
+  fi
+
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+    exit $stat
+  fi
+
+  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+  do
+    test -f "$tmpdepfile" && break
+  done
+  # Same post-processing that is required for AIX mode.
+  aix_post_process_depfile
+  ;;
+
+msvc7)
+  if test "$libtool" = yes; then
+    showIncludes=-Wc,-showIncludes
+  else
+    showIncludes=-showIncludes
+  fi
+  "$@" $showIncludes > "$tmpdepfile"
+  stat=$?
+  grep -v '^Note: including file: ' "$tmpdepfile"
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  # The first sed program below extracts the file names and escapes
+  # backslashes for cygpath.  The second sed program outputs the file
+  # name when reading, but also accumulates all include files in the
+  # hold buffer in order to output them again at the end.  This only
+  # works with sed implementations that can handle large buffers.
+  sed < "$tmpdepfile" -n '
+/^Note: including file:  *\(.*\)/ {
+  s//\1/
+  s/\\/\\\\/g
+  p
+}' | $cygpath_u | sort -u | sed -n '
+s/ /\\ /g
+s/\(.*\)/'"$tab"'\1 \\/p
+s/.\(.*\) \\/\1:/
+H
+$ {
+  s/.*/'"$tab"'/
+  G
+  p
+}' >> "$depfile"
+  echo >> "$depfile" # make sure the fragment doesn't end with a backslash
+  rm -f "$tmpdepfile"
+  ;;
+
+msvc7msys)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+#nosideeffect)
+  # This comment above is used by automake to tell side-effect
+  # dependency tracking mechanisms from slower ones.
+
+dashmstdout)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout, regardless of -o.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  # Remove '-o $object'.
+  IFS=" "
+  for arg
+  do
+    case $arg in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    *)
+      set fnord "$@" "$arg"
+      shift # fnord
+      shift # $arg
+      ;;
+    esac
+  done
+
+  test -z "$dashmflag" && dashmflag=-M
+  # Require at least two characters before searching for ':'
+  # in the target name.  This is to cope with DOS-style filenames:
+  # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise.
+  "$@" $dashmflag |
+    sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile"
+  rm -f "$depfile"
+  cat < "$tmpdepfile" > "$depfile"
+  # Some versions of the HPUX 10.20 sed can't process this sed invocation
+  # correctly.  Breaking it into two sed invocations is a workaround.
+  tr ' ' "$nl" < "$tmpdepfile" \
+    | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \
+    | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+dashXmstdout)
+  # This case only exists to satisfy depend.m4.  It is never actually
+  # run, as this mode is specially recognized in the preamble.
+  exit 1
+  ;;
+
+makedepend)
+  "$@" || exit $?
+  # Remove any Libtool call
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+  # X makedepend
+  shift
+  cleared=no eat=no
+  for arg
+  do
+    case $cleared in
+    no)
+      set ""; shift
+      cleared=yes ;;
+    esac
+    if test $eat = yes; then
+      eat=no
+      continue
+    fi
+    case "$arg" in
+    -D*|-I*)
+      set fnord "$@" "$arg"; shift ;;
+    # Strip any option that makedepend may not understand.  Remove
+    # the object too, otherwise makedepend will parse it as a source file.
+    -arch)
+      eat=yes ;;
+    -*|$object)
+      ;;
+    *)
+      set fnord "$@" "$arg"; shift ;;
+    esac
+  done
+  obj_suffix=`echo "$object" | sed 's/^.*\././'`
+  touch "$tmpdepfile"
+  ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@"
+  rm -f "$depfile"
+  # makedepend may prepend the VPATH from the source file name to the object.
+  # No need to regex-escape $object, excess matching of '.' is harmless.
+  sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile"
+  # Some versions of the HPUX 10.20 sed can't process the last invocation
+  # correctly.  Breaking it into two sed invocations is a workaround.
+  sed '1,2d' "$tmpdepfile" \
+    | tr ' ' "$nl" \
+    | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \
+    | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile" "$tmpdepfile".bak
+  ;;
+
+cpp)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  # Remove '-o $object'.
+  IFS=" "
+  for arg
+  do
+    case $arg in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    *)
+      set fnord "$@" "$arg"
+      shift # fnord
+      shift # $arg
+      ;;
+    esac
+  done
+
+  "$@" -E \
+    | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
+             -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
+    | sed '$ s: \\$::' > "$tmpdepfile"
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  cat < "$tmpdepfile" >> "$depfile"
+  sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+msvisualcpp)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  IFS=" "
+  for arg
+  do
+    case "$arg" in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
+        set fnord "$@"
+        shift
+        shift
+        ;;
+    *)
+        set fnord "$@" "$arg"
+        shift
+        shift
+        ;;
+    esac
+  done
+  "$@" -E 2>/dev/null |
+  sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile"
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile"
+  echo "$tab" >> "$depfile"
+  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+msvcmsys)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+none)
+  exec "$@"
+  ;;
+
+*)
+  echo "Unknown depmode $depmode" 1>&2
+  exit 1
+  ;;
+esac
+
+exit 0
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/ast/build-aux/install-sh b/ast/build-aux/install-sh
new file mode 100755
index 0000000..377bb86
--- /dev/null
+++ b/ast/build-aux/install-sh
@@ -0,0 +1,527 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2011-11-20.07; # UTC
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# 'make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+nl='
+'
+IFS=" ""	$nl"
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit=${DOITPROG-}
+if test -z "$doit"; then
+  doit_exec=exec
+else
+  doit_exec=$doit
+fi
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_glob='?'
+initialize_posix_glob='
+  test "$posix_glob" != "?" || {
+    if (set -f) 2>/dev/null; then
+      posix_glob=
+    else
+      posix_glob=:
+    fi
+  }
+'
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
+
+src=
+dst=
+dir_arg=
+dst_arg=
+
+copy_on_change=false
+no_target_directory=
+
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+   or: $0 [OPTION]... SRCFILES... DIRECTORY
+   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+   or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+     --help     display this help and exit.
+     --version  display version info and exit.
+
+  -c            (ignored)
+  -C            install only if different (preserve the last data modification time)
+  -d            create directories instead of installing files.
+  -g GROUP      $chgrpprog installed files to GROUP.
+  -m MODE       $chmodprog installed files to MODE.
+  -o USER       $chownprog installed files to USER.
+  -s            $stripprog installed files.
+  -t DIRECTORY  install into DIRECTORY.
+  -T            report an error if DSTFILE is a directory.
+
+Environment variables override the default commands:
+  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+  RMPROG STRIPPROG
+"
+
+while test $# -ne 0; do
+  case $1 in
+    -c) ;;
+
+    -C) copy_on_change=true;;
+
+    -d) dir_arg=true;;
+
+    -g) chgrpcmd="$chgrpprog $2"
+	shift;;
+
+    --help) echo "$usage"; exit $?;;
+
+    -m) mode=$2
+	case $mode in
+	  *' '* | *'	'* | *'
+'*	  | *'*'* | *'?'* | *'['*)
+	    echo "$0: invalid mode: $mode" >&2
+	    exit 1;;
+	esac
+	shift;;
+
+    -o) chowncmd="$chownprog $2"
+	shift;;
+
+    -s) stripcmd=$stripprog;;
+
+    -t) dst_arg=$2
+	# Protect names problematic for 'test' and other utilities.
+	case $dst_arg in
+	  -* | [=\(\)!]) dst_arg=./$dst_arg;;
+	esac
+	shift;;
+
+    -T) no_target_directory=true;;
+
+    --version) echo "$0 $scriptversion"; exit $?;;
+
+    --)	shift
+	break;;
+
+    -*)	echo "$0: invalid option: $1" >&2
+	exit 1;;
+
+    *)  break;;
+  esac
+  shift
+done
+
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+  # When -d is used, all remaining arguments are directories to create.
+  # When -t is used, the destination is already specified.
+  # Otherwise, the last argument is the destination.  Remove it from $@.
+  for arg
+  do
+    if test -n "$dst_arg"; then
+      # $@ is not empty: it contains at least $arg.
+      set fnord "$@" "$dst_arg"
+      shift # fnord
+    fi
+    shift # arg
+    dst_arg=$arg
+    # Protect names problematic for 'test' and other utilities.
+    case $dst_arg in
+      -* | [=\(\)!]) dst_arg=./$dst_arg;;
+    esac
+  done
+fi
+
+if test $# -eq 0; then
+  if test -z "$dir_arg"; then
+    echo "$0: no input file specified." >&2
+    exit 1
+  fi
+  # It's OK to call 'install-sh -d' without argument.
+  # This can happen when creating conditional directories.
+  exit 0
+fi
+
+if test -z "$dir_arg"; then
+  do_exit='(exit $ret); exit $ret'
+  trap "ret=129; $do_exit" 1
+  trap "ret=130; $do_exit" 2
+  trap "ret=141; $do_exit" 13
+  trap "ret=143; $do_exit" 15
+
+  # Set umask so as not to create temps with too-generous modes.
+  # However, 'strip' requires both read and write access to temps.
+  case $mode in
+    # Optimize common cases.
+    *644) cp_umask=133;;
+    *755) cp_umask=22;;
+
+    *[0-7])
+      if test -z "$stripcmd"; then
+	u_plus_rw=
+      else
+	u_plus_rw='% 200'
+      fi
+      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+    *)
+      if test -z "$stripcmd"; then
+	u_plus_rw=
+      else
+	u_plus_rw=,u+rw
+      fi
+      cp_umask=$mode$u_plus_rw;;
+  esac
+fi
+
+for src
+do
+  # Protect names problematic for 'test' and other utilities.
+  case $src in
+    -* | [=\(\)!]) src=./$src;;
+  esac
+
+  if test -n "$dir_arg"; then
+    dst=$src
+    dstdir=$dst
+    test -d "$dstdir"
+    dstdir_status=$?
+  else
+
+    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+    # might cause directories to be created, which would be especially bad
+    # if $src (and thus $dsttmp) contains '*'.
+    if test ! -f "$src" && test ! -d "$src"; then
+      echo "$0: $src does not exist." >&2
+      exit 1
+    fi
+
+    if test -z "$dst_arg"; then
+      echo "$0: no destination specified." >&2
+      exit 1
+    fi
+    dst=$dst_arg
+
+    # If destination is a directory, append the input filename; won't work
+    # if double slashes aren't ignored.
+    if test -d "$dst"; then
+      if test -n "$no_target_directory"; then
+	echo "$0: $dst_arg: Is a directory" >&2
+	exit 1
+      fi
+      dstdir=$dst
+      dst=$dstdir/`basename "$src"`
+      dstdir_status=0
+    else
+      # Prefer dirname, but fall back on a substitute if dirname fails.
+      dstdir=`
+	(dirname "$dst") 2>/dev/null ||
+	expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	     X"$dst" : 'X\(//\)[^/]' \| \
+	     X"$dst" : 'X\(//\)$' \| \
+	     X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
+	echo X"$dst" |
+	    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\/\)[^/].*/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\/\)$/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\).*/{
+		   s//\1/
+		   q
+		 }
+		 s/.*/./; q'
+      `
+
+      test -d "$dstdir"
+      dstdir_status=$?
+    fi
+  fi
+
+  obsolete_mkdir_used=false
+
+  if test $dstdir_status != 0; then
+    case $posix_mkdir in
+      '')
+	# Create intermediate dirs using mode 755 as modified by the umask.
+	# This is like FreeBSD 'install' as of 1997-10-28.
+	umask=`umask`
+	case $stripcmd.$umask in
+	  # Optimize common cases.
+	  *[2367][2367]) mkdir_umask=$umask;;
+	  .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
+
+	  *[0-7])
+	    mkdir_umask=`expr $umask + 22 \
+	      - $umask % 100 % 40 + $umask % 20 \
+	      - $umask % 10 % 4 + $umask % 2
+	    `;;
+	  *) mkdir_umask=$umask,go-w;;
+	esac
+
+	# With -d, create the new directory with the user-specified mode.
+	# Otherwise, rely on $mkdir_umask.
+	if test -n "$dir_arg"; then
+	  mkdir_mode=-m$mode
+	else
+	  mkdir_mode=
+	fi
+
+	posix_mkdir=false
+	case $umask in
+	  *[123567][0-7][0-7])
+	    # POSIX mkdir -p sets u+wx bits regardless of umask, which
+	    # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
+	    ;;
+	  *)
+	    tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+	    trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
+
+	    if (umask $mkdir_umask &&
+		exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
+	    then
+	      if test -z "$dir_arg" || {
+		   # Check for POSIX incompatibilities with -m.
+		   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+		   # other-writable bit of parent directory when it shouldn't.
+		   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+		   ls_ld_tmpdir=`ls -ld "$tmpdir"`
+		   case $ls_ld_tmpdir in
+		     d????-?r-*) different_mode=700;;
+		     d????-?--*) different_mode=755;;
+		     *) false;;
+		   esac &&
+		   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
+		     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
+		     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+		   }
+		 }
+	      then posix_mkdir=:
+	      fi
+	      rmdir "$tmpdir/d" "$tmpdir"
+	    else
+	      # Remove any dirs left behind by ancient mkdir implementations.
+	      rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
+	    fi
+	    trap '' 0;;
+	esac;;
+    esac
+
+    if
+      $posix_mkdir && (
+	umask $mkdir_umask &&
+	$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+      )
+    then :
+    else
+
+      # The umask is ridiculous, or mkdir does not conform to POSIX,
+      # or it failed possibly due to a race condition.  Create the
+      # directory the slow way, step by step, checking for races as we go.
+
+      case $dstdir in
+	/*) prefix='/';;
+	[-=\(\)!]*) prefix='./';;
+	*)  prefix='';;
+      esac
+
+      eval "$initialize_posix_glob"
+
+      oIFS=$IFS
+      IFS=/
+      $posix_glob set -f
+      set fnord $dstdir
+      shift
+      $posix_glob set +f
+      IFS=$oIFS
+
+      prefixes=
+
+      for d
+      do
+	test X"$d" = X && continue
+
+	prefix=$prefix$d
+	if test -d "$prefix"; then
+	  prefixes=
+	else
+	  if $posix_mkdir; then
+	    (umask=$mkdir_umask &&
+	     $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+	    # Don't fail if two instances are running concurrently.
+	    test -d "$prefix" || exit 1
+	  else
+	    case $prefix in
+	      *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+	      *) qprefix=$prefix;;
+	    esac
+	    prefixes="$prefixes '$qprefix'"
+	  fi
+	fi
+	prefix=$prefix/
+      done
+
+      if test -n "$prefixes"; then
+	# Don't fail if two instances are running concurrently.
+	(umask $mkdir_umask &&
+	 eval "\$doit_exec \$mkdirprog $prefixes") ||
+	  test -d "$dstdir" || exit 1
+	obsolete_mkdir_used=true
+      fi
+    fi
+  fi
+
+  if test -n "$dir_arg"; then
+    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+  else
+
+    # Make a couple of temp file names in the proper directory.
+    dsttmp=$dstdir/_inst.$$_
+    rmtmp=$dstdir/_rm.$$_
+
+    # Trap to clean up those temp files at exit.
+    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+    # Copy the file name to the temp name.
+    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+
+    # and set any options; do chmod last to preserve setuid bits.
+    #
+    # If any of these fail, we abort the whole thing.  If we want to
+    # ignore errors from any of these, just make sure not to ignore
+    # errors from the above "$doit $cpprog $src $dsttmp" command.
+    #
+    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+    # If -C, don't bother to copy if it wouldn't change the file.
+    if $copy_on_change &&
+       old=`LC_ALL=C ls -dlL "$dst"	2>/dev/null` &&
+       new=`LC_ALL=C ls -dlL "$dsttmp"	2>/dev/null` &&
+
+       eval "$initialize_posix_glob" &&
+       $posix_glob set -f &&
+       set X $old && old=:$2:$4:$5:$6 &&
+       set X $new && new=:$2:$4:$5:$6 &&
+       $posix_glob set +f &&
+
+       test "$old" = "$new" &&
+       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+    then
+      rm -f "$dsttmp"
+    else
+      # Rename the file to the real destination.
+      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+      # The rename failed, perhaps because mv can't rename something else
+      # to itself, or perhaps because mv is so ancient that it does not
+      # support -f.
+      {
+	# Now remove or move aside any old file at destination location.
+	# We try this two ways since rm can't unlink itself on some
+	# systems and the destination file might be busy for other
+	# reasons.  In this case, the final cleanup might fail but the new
+	# file should still install successfully.
+	{
+	  test ! -f "$dst" ||
+	  $doit $rmcmd -f "$dst" 2>/dev/null ||
+	  { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+	    { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
+	  } ||
+	  { echo "$0: cannot unlink or rename $dst" >&2
+	    (exit 1); exit 1
+	  }
+	} &&
+
+	# Now rename the file to the real destination.
+	$doit $mvcmd "$dsttmp" "$dst"
+      }
+    fi || exit 1
+
+    trap '' 0
+  fi
+done
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/ast/build-aux/ltmain.sh b/ast/build-aux/ltmain.sh
new file mode 100644
index 0000000..63ae69d
--- /dev/null
+++ b/ast/build-aux/ltmain.sh
@@ -0,0 +1,9655 @@
+
+# libtool (GNU libtool) 2.4.2
+# Written by Gordon Matzigkeit <[email-masked]>, 1996
+
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006,
+# 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+# This is free software; see the source for copying conditions.  There is NO
+# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# GNU Libtool is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# As a special exception to the GNU General Public License,
+# if you distribute this file as part of a program or library that
+# is built using GNU Libtool, you may include this file under the
+# same distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Libtool; see the file COPYING.  If not, a copy
+# can be downloaded from http://www.gnu.org/licenses/gpl.html,
+# or obtained by writing to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+# Usage: $progname [OPTION]... [MODE-ARG]...
+#
+# Provide generalized library-building support services.
+#
+#       --config             show all configuration variables
+#       --debug              enable verbose shell tracing
+#   -n, --dry-run            display commands without modifying any files
+#       --features           display basic configuration information and exit
+#       --mode=MODE          use operation mode MODE
+#       --preserve-dup-deps  don't remove duplicate dependency libraries
+#       --quiet, --silent    don't print informational messages
+#       --no-quiet, --no-silent
+#                            print informational messages (default)
+#       --no-warn            don't display warning messages
+#       --tag=TAG            use configuration variables from tag TAG
+#   -v, --verbose            print more informational messages than default
+#       --no-verbose         don't print the extra informational messages
+#       --version            print version information
+#   -h, --help, --help-all   print short, long, or detailed help message
+#
+# MODE must be one of the following:
+#
+#         clean              remove files from the build directory
+#         compile            compile a source file into a libtool object
+#         execute            automatically set library path, then run a program
+#         finish             complete the installation of libtool libraries
+#         install            install libraries or executables
+#         link               create a library or an executable
+#         uninstall          remove libraries from an installed directory
+#
+# MODE-ARGS vary depending on the MODE.  When passed as first option,
+# `--mode=MODE' may be abbreviated as `MODE' or a unique abbreviation of that.
+# Try `$progname --help --mode=MODE' for a more detailed description of MODE.
+#
+# When reporting a bug, please describe a test case to reproduce it and
+# include the following information:
+#
+#         host-triplet:	$host
+#         shell:		$SHELL
+#         compiler:		$LTCC
+#         compiler flags:		$LTCFLAGS
+#         linker:		$LD (gnu? $with_gnu_ld)
+#         $progname:	(GNU libtool) 2.4.2
+#         automake:	$automake_version
+#         autoconf:	$autoconf_version
+#
+# Report bugs to <[email-masked]>.
+# GNU libtool home page: <http://www.gnu.org/software/libtool/>.
+# General help using GNU software: <http://www.gnu.org/gethelp/>.
+
+PROGRAM=libtool
+PACKAGE=libtool
+VERSION=2.4.2
+TIMESTAMP=""
+package_revision=1.3337
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+  eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+
+# NLS nuisances: We save the old values to restore during execute mode.
+lt_user_locale=
+lt_safe_locale=
+for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES
+do
+  eval "if test \"\${$lt_var+set}\" = set; then
+          save_$lt_var=\$$lt_var
+          $lt_var=C
+	  export $lt_var
+	  lt_user_locale=\"$lt_var=\\\$save_\$lt_var; \$lt_user_locale\"
+	  lt_safe_locale=\"$lt_var=C; \$lt_safe_locale\"
+	fi"
+done
+LC_ALL=C
+LANGUAGE=C
+export LANGUAGE LC_ALL
+
+$lt_unset CDPATH
+
+
+# Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh
+# is ksh but when the shell is invoked as "sh" and the current value of
+# the _XPG environment variable is not equal to 1 (one), the special
+# positional parameter $0, within a function call, is the name of the
+# function.
+progpath="$0"
+
+
+
+: ${CP="cp -f"}
+test "${ECHO+set}" = set || ECHO=${as_echo-'printf %s\n'}
+: ${MAKE="make"}
+: ${MKDIR="mkdir"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+: ${SHELL="${CONFIG_SHELL-/bin/sh}"}
+: ${Xsed="$SED -e 1s/^X//"}
+
+# Global variables:
+EXIT_SUCCESS=0
+EXIT_FAILURE=1
+EXIT_MISMATCH=63  # $? = 63 is used to indicate version mismatch to missing.
+EXIT_SKIP=77	  # $? = 77 is used to indicate a skipped test to automake.
+
+exit_status=$EXIT_SUCCESS
+
+# Make sure IFS has a sensible default
+lt_nl='
+'
+IFS=" 	$lt_nl"
+
+dirname="s,/[^/]*$,,"
+basename="s,^.*/,,"
+
+# func_dirname file append nondir_replacement
+# Compute the dirname of FILE.  If nonempty, add APPEND to the result,
+# otherwise set result to NONDIR_REPLACEMENT.
+func_dirname ()
+{
+    func_dirname_result=`$ECHO "${1}" | $SED "$dirname"`
+    if test "X$func_dirname_result" = "X${1}"; then
+      func_dirname_result="${3}"
+    else
+      func_dirname_result="$func_dirname_result${2}"
+    fi
+} # func_dirname may be replaced by extended shell implementation
+
+
+# func_basename file
+func_basename ()
+{
+    func_basename_result=`$ECHO "${1}" | $SED "$basename"`
+} # func_basename may be replaced by extended shell implementation
+
+
+# func_dirname_and_basename file append nondir_replacement
+# perform func_basename and func_dirname in a single function
+# call:
+#   dirname:  Compute the dirname of FILE.  If nonempty,
+#             add APPEND to the result, otherwise set result
+#             to NONDIR_REPLACEMENT.
+#             value returned in "$func_dirname_result"
+#   basename: Compute filename of FILE.
+#             value retuned in "$func_basename_result"
+# Implementation must be kept synchronized with func_dirname
+# and func_basename. For efficiency, we do not delegate to
+# those functions but instead duplicate the functionality here.
+func_dirname_and_basename ()
+{
+    # Extract subdirectory from the argument.
+    func_dirname_result=`$ECHO "${1}" | $SED -e "$dirname"`
+    if test "X$func_dirname_result" = "X${1}"; then
+      func_dirname_result="${3}"
+    else
+      func_dirname_result="$func_dirname_result${2}"
+    fi
+    func_basename_result=`$ECHO "${1}" | $SED -e "$basename"`
+} # func_dirname_and_basename may be replaced by extended shell implementation
+
+
+# func_stripname prefix suffix name
+# strip PREFIX and SUFFIX off of NAME.
+# PREFIX and SUFFIX must not contain globbing or regex special
+# characters, hashes, percent signs, but SUFFIX may contain a leading
+# dot (in which case that matches only a dot).
+# func_strip_suffix prefix name
+func_stripname ()
+{
+    case ${2} in
+      .*) func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%\\\\${2}\$%%"`;;
+      *)  func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%${2}\$%%"`;;
+    esac
+} # func_stripname may be replaced by extended shell implementation
+
+
+# These SED scripts presuppose an absolute path with a trailing slash.
+pathcar='s,^/\([^/]*\).*$,\1,'
+pathcdr='s,^/[^/]*,,'
+removedotparts=':dotsl
+		s@/\./@/@g
+		t dotsl
+		s,/\.$,/,'
+collapseslashes='s@/\{1,\}@/@g'
+finalslash='s,/*$,/,'
+
+# func_normal_abspath PATH
+# Remove doubled-up and trailing slashes, "." path components,
+# and cancel out any ".." path components in PATH after making
+# it an absolute path.
+#             value returned in "$func_normal_abspath_result"
+func_normal_abspath ()
+{
+  # Start from root dir and reassemble the path.
+  func_normal_abspath_result=
+  func_normal_abspath_tpath=$1
+  func_normal_abspath_altnamespace=
+  case $func_normal_abspath_tpath in
+    "")
+      # Empty path, that just means $cwd.
+      func_stripname '' '/' "`pwd`"
+      func_normal_abspath_result=$func_stripname_result
+      return
+    ;;
+    # The next three entries are used to spot a run of precisely
+    # two leading slashes without using negated character classes;
+    # we take advantage of case's first-match behaviour.
+    ///*)
+      # Unusual form of absolute path, do nothing.
+    ;;
+    //*)
+      # Not necessarily an ordinary path; POSIX reserves leading '//'
+      # and for example Cygwin uses it to access remote file shares
+      # over CIFS/SMB, so we conserve a leading double slash if found.
+      func_normal_abspath_altnamespace=/
+    ;;
+    /*)
+      # Absolute path, do nothing.
+    ;;
+    *)
+      # Relative path, prepend $cwd.
+      func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath
+    ;;
+  esac
+  # Cancel out all the simple stuff to save iterations.  We also want
+  # the path to end with a slash for ease of parsing, so make sure
+  # there is one (and only one) here.
+  func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \
+        -e "$removedotparts" -e "$collapseslashes" -e "$finalslash"`
+  while :; do
+    # Processed it all yet?
+    if test "$func_normal_abspath_tpath" = / ; then
+      # If we ascended to the root using ".." the result may be empty now.
+      if test -z "$func_normal_abspath_result" ; then
+        func_normal_abspath_result=/
+      fi
+      break
+    fi
+    func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \
+        -e "$pathcar"`
+    func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \
+        -e "$pathcdr"`
+    # Figure out what to do with it
+    case $func_normal_abspath_tcomponent in
+      "")
+        # Trailing empty path component, ignore it.
+      ;;
+      ..)
+        # Parent dir; strip last assembled component from result.
+        func_dirname "$func_normal_abspath_result"
+        func_normal_abspath_result=$func_dirname_result
+      ;;
+      *)
+        # Actual path component, append it.
+        func_normal_abspath_result=$func_normal_abspath_result/$func_normal_abspath_tcomponent
+      ;;
+    esac
+  done
+  # Restore leading double-slash if one was found on entry.
+  func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result
+}
+
+# func_relative_path SRCDIR DSTDIR
+# generates a relative path from SRCDIR to DSTDIR, with a trailing
+# slash if non-empty, suitable for immediately appending a filename
+# without needing to append a separator.
+#             value returned in "$func_relative_path_result"
+func_relative_path ()
+{
+  func_relative_path_result=
+  func_normal_abspath "$1"
+  func_relative_path_tlibdir=$func_normal_abspath_result
+  func_normal_abspath "$2"
+  func_relative_path_tbindir=$func_normal_abspath_result
+
+  # Ascend the tree starting from libdir
+  while :; do
+    # check if we have found a prefix of bindir
+    case $func_relative_path_tbindir in
+      $func_relative_path_tlibdir)
+        # found an exact match
+        func_relative_path_tcancelled=
+        break
+        ;;
+      $func_relative_path_tlibdir*)
+        # found a matching prefix
+        func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir"
+        func_relative_path_tcancelled=$func_stripname_result
+        if test -z "$func_relative_path_result"; then
+          func_relative_path_result=.
+        fi
+        break
+        ;;
+      *)
+        func_dirname $func_relative_path_tlibdir
+        func_relative_path_tlibdir=${func_dirname_result}
+        if test "x$func_relative_path_tlibdir" = x ; then
+          # Have to descend all the way to the root!
+          func_relative_path_result=../$func_relative_path_result
+          func_relative_path_tcancelled=$func_relative_path_tbindir
+          break
+        fi
+        func_relative_path_result=../$func_relative_path_result
+        ;;
+    esac
+  done
+
+  # Now calculate path; take care to avoid doubling-up slashes.
+  func_stripname '' '/' "$func_relative_path_result"
+  func_relative_path_result=$func_stripname_result
+  func_stripname '/' '/' "$func_relative_path_tcancelled"
+  if test "x$func_stripname_result" != x ; then
+    func_relative_path_result=${func_relative_path_result}/${func_stripname_result}
+  fi
+
+  # Normalisation. If bindir is libdir, return empty string,
+  # else relative path ending with a slash; either way, target
+  # file name can be directly appended.
+  if test ! -z "$func_relative_path_result"; then
+    func_stripname './' '' "$func_relative_path_result/"
+    func_relative_path_result=$func_stripname_result
+  fi
+}
+
+# The name of this program:
+func_dirname_and_basename "$progpath"
+progname=$func_basename_result
+
+# Make sure we have an absolute path for reexecution:
+case $progpath in
+  [\\/]*|[A-Za-z]:\\*) ;;
+  *[\\/]*)
+     progdir=$func_dirname_result
+     progdir=`cd "$progdir" && pwd`
+     progpath="$progdir/$progname"
+     ;;
+  *)
+     save_IFS="$IFS"
+     IFS=${PATH_SEPARATOR-:}
+     for progdir in $PATH; do
+       IFS="$save_IFS"
+       test -x "$progdir/$progname" && break
+     done
+     IFS="$save_IFS"
+     test -n "$progdir" || progdir=`pwd`
+     progpath="$progdir/$progname"
+     ;;
+esac
+
+# Sed substitution that helps us do robust quoting.  It backslashifies
+# metacharacters that are still active within double-quoted strings.
+Xsed="${SED}"' -e 1s/^X//'
+sed_quote_subst='s/\([`"$\\]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\(["`\\]\)/\\\1/g'
+
+# Sed substitution that turns a string into a regex matching for the
+# string literally.
+sed_make_literal_regex='s,[].[^$\\*\/],\\&,g'
+
+# Sed substitution that converts a w32 file name or path
+# which contains forward slashes, into one that contains
+# (escaped) backslashes.  A very naive implementation.
+lt_sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g'
+
+# Re-`\' parameter expansions in output of double_quote_subst that were
+# `\'-ed in input to the same.  If an odd number of `\' preceded a '$'
+# in input to double_quote_subst, that '$' was protected from expansion.
+# Since each input `\' is now two `\'s, look for any number of runs of
+# four `\'s followed by two `\'s and then a '$'.  `\' that '$'.
+bs='\\'
+bs2='\\\\'
+bs4='\\\\\\\\'
+dollar='\$'
+sed_double_backslash="\
+  s/$bs4/&\\
+/g
+  s/^$bs2$dollar/$bs&/
+  s/\\([^$bs]\\)$bs2$dollar/\\1$bs2$bs$dollar/g
+  s/\n//g"
+
+# Standard options:
+opt_dry_run=false
+opt_help=false
+opt_quiet=false
+opt_verbose=false
+opt_warning=:
+
+# func_echo arg...
+# Echo program name prefixed message, along with the current mode
+# name if it has been set yet.
+func_echo ()
+{
+    $ECHO "$progname: ${opt_mode+$opt_mode: }$*"
+}
+
+# func_verbose arg...
+# Echo program name prefixed message in verbose mode only.
+func_verbose ()
+{
+    $opt_verbose && func_echo ${1+"$@"}
+
+    # A bug in bash halts the script if the last line of a function
+    # fails when set -e is in force, so we need another command to
+    # work around that:
+    :
+}
+
+# func_echo_all arg...
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+    $ECHO "$*"
+}
+
+# func_error arg...
+# Echo program name prefixed message to standard error.
+func_error ()
+{
+    $ECHO "$progname: ${opt_mode+$opt_mode: }"${1+"$@"} 1>&2
+}
+
+# func_warning arg...
+# Echo program name prefixed warning message to standard error.
+func_warning ()
+{
+    $opt_warning && $ECHO "$progname: ${opt_mode+$opt_mode: }warning: "${1+"$@"} 1>&2
+
+    # bash bug again:
+    :
+}
+
+# func_fatal_error arg...
+# Echo program name prefixed message to standard error, and exit.
+func_fatal_error ()
+{
+    func_error ${1+"$@"}
+    exit $EXIT_FAILURE
+}
+
+# func_fatal_help arg...
+# Echo program name prefixed message to standard error, followed by
+# a help hint, and exit.
+func_fatal_help ()
+{
+    func_error ${1+"$@"}
+    func_fatal_error "$help"
+}
+help="Try \`$progname --help' for more information."  ## default
+
+
+# func_grep expression filename
+# Check whether EXPRESSION matches any line of FILENAME, without output.
+func_grep ()
+{
+    $GREP "$1" "$2" >/dev/null 2>&1
+}
+
+
+# func_mkdir_p directory-path
+# Make sure the entire path to DIRECTORY-PATH is available.
+func_mkdir_p ()
+{
+    my_directory_path="$1"
+    my_dir_list=
+
+    if test -n "$my_directory_path" && test "$opt_dry_run" != ":"; then
+
+      # Protect directory names starting with `-'
+      case $my_directory_path in
+        -*) my_directory_path="./$my_directory_path" ;;
+      esac
+
+      # While some portion of DIR does not yet exist...
+      while test ! -d "$my_directory_path"; do
+        # ...make a list in topmost first order.  Use a colon delimited
+	# list incase some portion of path contains whitespace.
+        my_dir_list="$my_directory_path:$my_dir_list"
+
+        # If the last portion added has no slash in it, the list is done
+        case $my_directory_path in */*) ;; *) break ;; esac
+
+        # ...otherwise throw away the child directory and loop
+        my_directory_path=`$ECHO "$my_directory_path" | $SED -e "$dirname"`
+      done
+      my_dir_list=`$ECHO "$my_dir_list" | $SED 's,:*$,,'`
+
+      save_mkdir_p_IFS="$IFS"; IFS=':'
+      for my_dir in $my_dir_list; do
+	IFS="$save_mkdir_p_IFS"
+        # mkdir can fail with a `File exist' error if two processes
+        # try to create one of the directories concurrently.  Don't
+        # stop in that case!
+        $MKDIR "$my_dir" 2>/dev/null || :
+      done
+      IFS="$save_mkdir_p_IFS"
+
+      # Bail out if we (or some other process) failed to create a directory.
+      test -d "$my_directory_path" || \
+        func_fatal_error "Failed to create \`$1'"
+    fi
+}
+
+
+# func_mktempdir [string]
+# Make a temporary directory that won't clash with other running
+# libtool processes, and avoids race conditions if possible.  If
+# given, STRING is the basename for that directory.
+func_mktempdir ()
+{
+    my_template="${TMPDIR-/tmp}/${1-$progname}"
+
+    if test "$opt_dry_run" = ":"; then
+      # Return a directory name, but don't create it in dry-run mode
+      my_tmpdir="${my_template}-$$"
+    else
+
+      # If mktemp works, use that first and foremost
+      my_tmpdir=`mktemp -d "${my_template}-XXXXXXXX" 2>/dev/null`
+
+      if test ! -d "$my_tmpdir"; then
+        # Failing that, at least try and use $RANDOM to avoid a race
+        my_tmpdir="${my_template}-${RANDOM-0}$$"
+
+        save_mktempdir_umask=`umask`
+        umask 0077
+        $MKDIR "$my_tmpdir"
+        umask $save_mktempdir_umask
+      fi
+
+      # If we're not in dry-run mode, bomb out on failure
+      test -d "$my_tmpdir" || \
+        func_fatal_error "cannot create temporary directory \`$my_tmpdir'"
+    fi
+
+    $ECHO "$my_tmpdir"
+}
+
+
+# func_quote_for_eval arg
+# Aesthetically quote ARG to be evaled later.
+# This function returns two values: FUNC_QUOTE_FOR_EVAL_RESULT
+# is double-quoted, suitable for a subsequent eval, whereas
+# FUNC_QUOTE_FOR_EVAL_UNQUOTED_RESULT has merely all characters
+# which are still active within double quotes backslashified.
+func_quote_for_eval ()
+{
+    case $1 in
+      *[\\\`\"\$]*)
+	func_quote_for_eval_unquoted_result=`$ECHO "$1" | $SED "$sed_quote_subst"` ;;
+      *)
+        func_quote_for_eval_unquoted_result="$1" ;;
+    esac
+
+    case $func_quote_for_eval_unquoted_result in
+      # Double-quote args containing shell metacharacters to delay
+      # word splitting, command substitution and and variable
+      # expansion for a subsequent eval.
+      # Many Bourne shells cannot handle close brackets correctly
+      # in scan sets, so we specify it separately.
+      *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \	]*|*]*|"")
+        func_quote_for_eval_result="\"$func_quote_for_eval_unquoted_result\""
+        ;;
+      *)
+        func_quote_for_eval_result="$func_quote_for_eval_unquoted_result"
+    esac
+}
+
+
+# func_quote_for_expand arg
+# Aesthetically quote ARG to be evaled later; same as above,
+# but do not quote variable references.
+func_quote_for_expand ()
+{
+    case $1 in
+      *[\\\`\"]*)
+	my_arg=`$ECHO "$1" | $SED \
+	    -e "$double_quote_subst" -e "$sed_double_backslash"` ;;
+      *)
+        my_arg="$1" ;;
+    esac
+
+    case $my_arg in
+      # Double-quote args containing shell metacharacters to delay
+      # word splitting and command substitution for a subsequent eval.
+      # Many Bourne shells cannot handle close brackets correctly
+      # in scan sets, so we specify it separately.
+      *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \	]*|*]*|"")
+        my_arg="\"$my_arg\""
+        ;;
+    esac
+
+    func_quote_for_expand_result="$my_arg"
+}
+
+
+# func_show_eval cmd [fail_exp]
+# Unless opt_silent is true, then output CMD.  Then, if opt_dryrun is
+# not true, evaluate CMD.  If the evaluation of CMD fails, and FAIL_EXP
+# is given, then evaluate it.
+func_show_eval ()
+{
+    my_cmd="$1"
+    my_fail_exp="${2-:}"
+
+    ${opt_silent-false} || {
+      func_quote_for_expand "$my_cmd"
+      eval "func_echo $func_quote_for_expand_result"
+    }
+
+    if ${opt_dry_run-false}; then :; else
+      eval "$my_cmd"
+      my_status=$?
+      if test "$my_status" -eq 0; then :; else
+	eval "(exit $my_status); $my_fail_exp"
+      fi
+    fi
+}
+
+
+# func_show_eval_locale cmd [fail_exp]
+# Unless opt_silent is true, then output CMD.  Then, if opt_dryrun is
+# not true, evaluate CMD.  If the evaluation of CMD fails, and FAIL_EXP
+# is given, then evaluate it.  Use the saved locale for evaluation.
+func_show_eval_locale ()
+{
+    my_cmd="$1"
+    my_fail_exp="${2-:}"
+
+    ${opt_silent-false} || {
+      func_quote_for_expand "$my_cmd"
+      eval "func_echo $func_quote_for_expand_result"
+    }
+
+    if ${opt_dry_run-false}; then :; else
+      eval "$lt_user_locale
+	    $my_cmd"
+      my_status=$?
+      eval "$lt_safe_locale"
+      if test "$my_status" -eq 0; then :; else
+	eval "(exit $my_status); $my_fail_exp"
+      fi
+    fi
+}
+
+# func_tr_sh
+# Turn $1 into a string suitable for a shell variable name.
+# Result is stored in $func_tr_sh_result.  All characters
+# not in the set a-zA-Z0-9_ are replaced with '_'. Further,
+# if $1 begins with a digit, a '_' is prepended as well.
+func_tr_sh ()
+{
+  case $1 in
+  [0-9]* | *[!a-zA-Z0-9_]*)
+    func_tr_sh_result=`$ECHO "$1" | $SED 's/^\([0-9]\)/_\1/; s/[^a-zA-Z0-9_]/_/g'`
+    ;;
+  * )
+    func_tr_sh_result=$1
+    ;;
+  esac
+}
+
+
+# func_version
+# Echo version message to standard output and exit.
+func_version ()
+{
+    $opt_debug
+
+    $SED -n '/(C)/!b go
+	:more
+	/\./!{
+	  N
+	  s/\n# / /
+	  b more
+	}
+	:go
+	/^# '$PROGRAM' (GNU /,/# warranty; / {
+        s/^# //
+	s/^# *$//
+        s/\((C)\)[ 0-9,-]*\( [1-9][0-9]*\)/\1\2/
+        p
+     }' < "$progpath"
+     exit $?
+}
+
+# func_usage
+# Echo short help message to standard output and exit.
+func_usage ()
+{
+    $opt_debug
+
+    $SED -n '/^# Usage:/,/^#  *.*--help/ {
+        s/^# //
+	s/^# *$//
+	s/\$progname/'$progname'/
+	p
+    }' < "$progpath"
+    echo
+    $ECHO "run \`$progname --help | more' for full usage"
+    exit $?
+}
+
+# func_help [NOEXIT]
+# Echo long help message to standard output and exit,
+# unless 'noexit' is passed as argument.
+func_help ()
+{
+    $opt_debug
+
+    $SED -n '/^# Usage:/,/# Report bugs to/ {
+	:print
+        s/^# //
+	s/^# *$//
+	s*\$progname*'$progname'*
+	s*\$host*'"$host"'*
+	s*\$SHELL*'"$SHELL"'*
+	s*\$LTCC*'"$LTCC"'*
+	s*\$LTCFLAGS*'"$LTCFLAGS"'*
+	s*\$LD*'"$LD"'*
+	s/\$with_gnu_ld/'"$with_gnu_ld"'/
+	s/\$automake_version/'"`(${AUTOMAKE-automake} --version) 2>/dev/null |$SED 1q`"'/
+	s/\$autoconf_version/'"`(${AUTOCONF-autoconf} --version) 2>/dev/null |$SED 1q`"'/
+	p
+	d
+     }
+     /^# .* home page:/b print
+     /^# General help using/b print
+     ' < "$progpath"
+    ret=$?
+    if test -z "$1"; then
+      exit $ret
+    fi
+}
+
+# func_missing_arg argname
+# Echo program name prefixed message to standard error and set global
+# exit_cmd.
+func_missing_arg ()
+{
+    $opt_debug
+
+    func_error "missing argument for $1."
+    exit_cmd=exit
+}
+
+
+# func_split_short_opt shortopt
+# Set func_split_short_opt_name and func_split_short_opt_arg shell
+# variables after splitting SHORTOPT after the 2nd character.
+func_split_short_opt ()
+{
+    my_sed_short_opt='1s/^\(..\).*$/\1/;q'
+    my_sed_short_rest='1s/^..\(.*\)$/\1/;q'
+
+    func_split_short_opt_name=`$ECHO "$1" | $SED "$my_sed_short_opt"`
+    func_split_short_opt_arg=`$ECHO "$1" | $SED "$my_sed_short_rest"`
+} # func_split_short_opt may be replaced by extended shell implementation
+
+
+# func_split_long_opt longopt
+# Set func_split_long_opt_name and func_split_long_opt_arg shell
+# variables after splitting LONGOPT at the `=' sign.
+func_split_long_opt ()
+{
+    my_sed_long_opt='1s/^\(--[^=]*\)=.*/\1/;q'
+    my_sed_long_arg='1s/^--[^=]*=//'
+
+    func_split_long_opt_name=`$ECHO "$1" | $SED "$my_sed_long_opt"`
+    func_split_long_opt_arg=`$ECHO "$1" | $SED "$my_sed_long_arg"`
+} # func_split_long_opt may be replaced by extended shell implementation
+
+exit_cmd=:
+
+
+
+
+
+magic="%%%MAGIC variable%%%"
+magic_exe="%%%MAGIC EXE variable%%%"
+
+# Global variables.
+nonopt=
+preserve_args=
+lo2o="s/\\.lo\$/.${objext}/"
+o2lo="s/\\.${objext}\$/.lo/"
+extracted_archives=
+extracted_serial=0
+
+# If this variable is set in any of the actions, the command in it
+# will be execed at the end.  This prevents here-documents from being
+# left over by shells.
+exec_cmd=
+
+# func_append var value
+# Append VALUE to the end of shell variable VAR.
+func_append ()
+{
+    eval "${1}=\$${1}\${2}"
+} # func_append may be replaced by extended shell implementation
+
+# func_append_quoted var value
+# Quote VALUE and append to the end of shell variable VAR, separated
+# by a space.
+func_append_quoted ()
+{
+    func_quote_for_eval "${2}"
+    eval "${1}=\$${1}\\ \$func_quote_for_eval_result"
+} # func_append_quoted may be replaced by extended shell implementation
+
+
+# func_arith arithmetic-term...
+func_arith ()
+{
+    func_arith_result=`expr "${@}"`
+} # func_arith may be replaced by extended shell implementation
+
+
+# func_len string
+# STRING may not start with a hyphen.
+func_len ()
+{
+    func_len_result=`expr "${1}" : ".*" 2>/dev/null || echo $max_cmd_len`
+} # func_len may be replaced by extended shell implementation
+
+
+# func_lo2o object
+func_lo2o ()
+{
+    func_lo2o_result=`$ECHO "${1}" | $SED "$lo2o"`
+} # func_lo2o may be replaced by extended shell implementation
+
+
+# func_xform libobj-or-source
+func_xform ()
+{
+    func_xform_result=`$ECHO "${1}" | $SED 's/\.[^.]*$/.lo/'`
+} # func_xform may be replaced by extended shell implementation
+
+
+# func_fatal_configuration arg...
+# Echo program name prefixed message to standard error, followed by
+# a configuration failure hint, and exit.
+func_fatal_configuration ()
+{
+    func_error ${1+"$@"}
+    func_error "See the $PACKAGE documentation for more information."
+    func_fatal_error "Fatal configuration error."
+}
+
+
+# func_config
+# Display the configuration for all the tags in this script.
+func_config ()
+{
+    re_begincf='^# ### BEGIN LIBTOOL'
+    re_endcf='^# ### END LIBTOOL'
+
+    # Default configuration.
+    $SED "1,/$re_begincf CONFIG/d;/$re_endcf CONFIG/,\$d" < "$progpath"
+
+    # Now print the configurations for the tags.
+    for tagname in $taglist; do
+      $SED -n "/$re_begincf TAG CONFIG: $tagname\$/,/$re_endcf TAG CONFIG: $tagname\$/p" < "$progpath"
+    done
+
+    exit $?
+}
+
+# func_features
+# Display the features supported by this script.
+func_features ()
+{
+    echo "host: $host"
+    if test "$build_libtool_libs" = yes; then
+      echo "enable shared libraries"
+    else
+      echo "disable shared libraries"
+    fi
+    if test "$build_old_libs" = yes; then
+      echo "enable static libraries"
+    else
+      echo "disable static libraries"
+    fi
+
+    exit $?
+}
+
+# func_enable_tag tagname
+# Verify that TAGNAME is valid, and either flag an error and exit, or
+# enable the TAGNAME tag.  We also add TAGNAME to the global $taglist
+# variable here.
+func_enable_tag ()
+{
+  # Global variable:
+  tagname="$1"
+
+  re_begincf="^# ### BEGIN LIBTOOL TAG CONFIG: $tagname\$"
+  re_endcf="^# ### END LIBTOOL TAG CONFIG: $tagname\$"
+  sed_extractcf="/$re_begincf/,/$re_endcf/p"
+
+  # Validate tagname.
+  case $tagname in
+    *[!-_A-Za-z0-9,/]*)
+      func_fatal_error "invalid tag name: $tagname"
+      ;;
+  esac
+
+  # Don't test for the "default" C tag, as we know it's
+  # there but not specially marked.
+  case $tagname in
+    CC) ;;
+    *)
+      if $GREP "$re_begincf" "$progpath" >/dev/null 2>&1; then
+	taglist="$taglist $tagname"
+
+	# Evaluate the configuration.  Be careful to quote the path
+	# and the sed script, to avoid splitting on whitespace, but
+	# also don't use non-portable quotes within backquotes within
+	# quotes we have to do it in 2 steps:
+	extractedcf=`$SED -n -e "$sed_extractcf" < "$progpath"`
+	eval "$extractedcf"
+      else
+	func_error "ignoring unknown tag $tagname"
+      fi
+      ;;
+  esac
+}
+
+# func_check_version_match
+# Ensure that we are using m4 macros, and libtool script from the same
+# release of libtool.
+func_check_version_match ()
+{
+  if test "$package_revision" != "$macro_revision"; then
+    if test "$VERSION" != "$macro_version"; then
+      if test -z "$macro_version"; then
+        cat >&2 <<_LT_EOF
+$progname: Version mismatch error.  This is $PACKAGE $VERSION, but the
+$progname: definition of this LT_INIT comes from an older release.
+$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION
+$progname: and run autoconf again.
+_LT_EOF
+      else
+        cat >&2 <<_LT_EOF
+$progname: Version mismatch error.  This is $PACKAGE $VERSION, but the
+$progname: definition of this LT_INIT comes from $PACKAGE $macro_version.
+$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION
+$progname: and run autoconf again.
+_LT_EOF
+      fi
+    else
+      cat >&2 <<_LT_EOF
+$progname: Version mismatch error.  This is $PACKAGE $VERSION, revision $package_revision,
+$progname: but the definition of this LT_INIT comes from revision $macro_revision.
+$progname: You should recreate aclocal.m4 with macros from revision $package_revision
+$progname: of $PACKAGE $VERSION and run autoconf again.
+_LT_EOF
+    fi
+
+    exit $EXIT_MISMATCH
+  fi
+}
+
+
+# Shorthand for --mode=foo, only valid as the first argument
+case $1 in
+clean|clea|cle|cl)
+  shift; set dummy --mode clean ${1+"$@"}; shift
+  ;;
+compile|compil|compi|comp|com|co|c)
+  shift; set dummy --mode compile ${1+"$@"}; shift
+  ;;
+execute|execut|execu|exec|exe|ex|e)
+  shift; set dummy --mode execute ${1+"$@"}; shift
+  ;;
+finish|finis|fini|fin|fi|f)
+  shift; set dummy --mode finish ${1+"$@"}; shift
+  ;;
+install|instal|insta|inst|ins|in|i)
+  shift; set dummy --mode install ${1+"$@"}; shift
+  ;;
+link|lin|li|l)
+  shift; set dummy --mode link ${1+"$@"}; shift
+  ;;
+uninstall|uninstal|uninsta|uninst|unins|unin|uni|un|u)
+  shift; set dummy --mode uninstall ${1+"$@"}; shift
+  ;;
+esac
+
+
+
+# Option defaults:
+opt_debug=:
+opt_dry_run=false
+opt_config=false
+opt_preserve_dup_deps=false
+opt_features=false
+opt_finish=false
+opt_help=false
+opt_help_all=false
+opt_silent=:
+opt_warning=:
+opt_verbose=:
+opt_silent=false
+opt_verbose=false
+
+
+# Parse options once, thoroughly.  This comes as soon as possible in the
+# script to make things like `--version' happen as quickly as we can.
+{
+  # this just eases exit handling
+  while test $# -gt 0; do
+    opt="$1"
+    shift
+    case $opt in
+      --debug|-x)	opt_debug='set -x'
+			func_echo "enabling shell trace mode"
+			$opt_debug
+			;;
+      --dry-run|--dryrun|-n)
+			opt_dry_run=:
+			;;
+      --config)
+			opt_config=:
+func_config
+			;;
+      --dlopen|-dlopen)
+			optarg="$1"
+			opt_dlopen="${opt_dlopen+$opt_dlopen
+}$optarg"
+			shift
+			;;
+      --preserve-dup-deps)
+			opt_preserve_dup_deps=:
+			;;
+      --features)
+			opt_features=:
+func_features
+			;;
+      --finish)
+			opt_finish=:
+set dummy --mode finish ${1+"$@"}; shift
+			;;
+      --help)
+			opt_help=:
+			;;
+      --help-all)
+			opt_help_all=:
+opt_help=': help-all'
+			;;
+      --mode)
+			test $# = 0 && func_missing_arg $opt && break
+			optarg="$1"
+			opt_mode="$optarg"
+case $optarg in
+  # Valid mode arguments:
+  clean|compile|execute|finish|install|link|relink|uninstall) ;;
+
+  # Catch anything else as an error
+  *) func_error "invalid argument for $opt"
+     exit_cmd=exit
+     break
+     ;;
+esac
+			shift
+			;;
+      --no-silent|--no-quiet)
+			opt_silent=false
+func_append preserve_args " $opt"
+			;;
+      --no-warning|--no-warn)
+			opt_warning=false
+func_append preserve_args " $opt"
+			;;
+      --no-verbose)
+			opt_verbose=false
+func_append preserve_args " $opt"
+			;;
+      --silent|--quiet)
+			opt_silent=:
+func_append preserve_args " $opt"
+        opt_verbose=false
+			;;
+      --verbose|-v)
+			opt_verbose=:
+func_append preserve_args " $opt"
+opt_silent=false
+			;;
+      --tag)
+			test $# = 0 && func_missing_arg $opt && break
+			optarg="$1"
+			opt_tag="$optarg"
+func_append preserve_args " $opt $optarg"
+func_enable_tag "$optarg"
+			shift
+			;;
+
+      -\?|-h)		func_usage				;;
+      --help)		func_help				;;
+      --version)	func_version				;;
+
+      # Separate optargs to long options:
+      --*=*)
+			func_split_long_opt "$opt"
+			set dummy "$func_split_long_opt_name" "$func_split_long_opt_arg" ${1+"$@"}
+			shift
+			;;
+
+      # Separate non-argument short options:
+      -\?*|-h*|-n*|-v*)
+			func_split_short_opt "$opt"
+			set dummy "$func_split_short_opt_name" "-$func_split_short_opt_arg" ${1+"$@"}
+			shift
+			;;
+
+      --)		break					;;
+      -*)		func_fatal_help "unrecognized option \`$opt'" ;;
+      *)		set dummy "$opt" ${1+"$@"};	shift; break  ;;
+    esac
+  done
+
+  # Validate options:
+
+  # save first non-option argument
+  if test "$#" -gt 0; then
+    nonopt="$opt"
+    shift
+  fi
+
+  # preserve --debug
+  test "$opt_debug" = : || func_append preserve_args " --debug"
+
+  case $host in
+    *cygwin* | *mingw* | *pw32* | *cegcc*)
+      # don't eliminate duplications in $postdeps and $predeps
+      opt_duplicate_compiler_generated_deps=:
+      ;;
+    *)
+      opt_duplicate_compiler_generated_deps=$opt_preserve_dup_deps
+      ;;
+  esac
+
+  $opt_help || {
+    # Sanity checks first:
+    func_check_version_match
+
+    if test "$build_libtool_libs" != yes && test "$build_old_libs" != yes; then
+      func_fatal_configuration "not configured to build any kind of library"
+    fi
+
+    # Darwin sucks
+    eval std_shrext=\"$shrext_cmds\"
+
+    # Only execute mode is allowed to have -dlopen flags.
+    if test -n "$opt_dlopen" && test "$opt_mode" != execute; then
+      func_error "unrecognized option \`-dlopen'"
+      $ECHO "$help" 1>&2
+      exit $EXIT_FAILURE
+    fi
+
+    # Change the help message to a mode-specific one.
+    generic_help="$help"
+    help="Try \`$progname --help --mode=$opt_mode' for more information."
+  }
+
+
+  # Bail if the options were screwed
+  $exit_cmd $EXIT_FAILURE
+}
+
+
+
+
+## ----------- ##
+##    Main.    ##
+## ----------- ##
+
+# func_lalib_p file
+# True iff FILE is a libtool `.la' library or `.lo' object file.
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_lalib_p ()
+{
+    test -f "$1" &&
+      $SED -e 4q "$1" 2>/dev/null \
+        | $GREP "^# Generated by .*$PACKAGE" > /dev/null 2>&1
+}
+
+# func_lalib_unsafe_p file
+# True iff FILE is a libtool `.la' library or `.lo' object file.
+# This function implements the same check as func_lalib_p without
+# resorting to external programs.  To this end, it redirects stdin and
+# closes it afterwards, without saving the original file descriptor.
+# As a safety measure, use it only where a negative result would be
+# fatal anyway.  Works if `file' does not exist.
+func_lalib_unsafe_p ()
+{
+    lalib_p=no
+    if test -f "$1" && test -r "$1" && exec 5<&0 <"$1"; then
+	for lalib_p_l in 1 2 3 4
+	do
+	    read lalib_p_line
+	    case "$lalib_p_line" in
+		\#\ Generated\ by\ *$PACKAGE* ) lalib_p=yes; break;;
+	    esac
+	done
+	exec 0<&5 5<&-
+    fi
+    test "$lalib_p" = yes
+}
+
+# func_ltwrapper_script_p file
+# True iff FILE is a libtool wrapper script
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_ltwrapper_script_p ()
+{
+    func_lalib_p "$1"
+}
+
+# func_ltwrapper_executable_p file
+# True iff FILE is a libtool wrapper executable
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_ltwrapper_executable_p ()
+{
+    func_ltwrapper_exec_suffix=
+    case $1 in
+    *.exe) ;;
+    *) func_ltwrapper_exec_suffix=.exe ;;
+    esac
+    $GREP "$magic_exe" "$1$func_ltwrapper_exec_suffix" >/dev/null 2>&1
+}
+
+# func_ltwrapper_scriptname file
+# Assumes file is an ltwrapper_executable
+# uses $file to determine the appropriate filename for a
+# temporary ltwrapper_script.
+func_ltwrapper_scriptname ()
+{
+    func_dirname_and_basename "$1" "" "."
+    func_stripname '' '.exe' "$func_basename_result"
+    func_ltwrapper_scriptname_result="$func_dirname_result/$objdir/${func_stripname_result}_ltshwrapper"
+}
+
+# func_ltwrapper_p file
+# True iff FILE is a libtool wrapper script or wrapper executable
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_ltwrapper_p ()
+{
+    func_ltwrapper_script_p "$1" || func_ltwrapper_executable_p "$1"
+}
+
+
+# func_execute_cmds commands fail_cmd
+# Execute tilde-delimited COMMANDS.
+# If FAIL_CMD is given, eval that upon failure.
+# FAIL_CMD may read-access the current command in variable CMD!
+func_execute_cmds ()
+{
+    $opt_debug
+    save_ifs=$IFS; IFS='~'
+    for cmd in $1; do
+      IFS=$save_ifs
+      eval cmd=\"$cmd\"
+      func_show_eval "$cmd" "${2-:}"
+    done
+    IFS=$save_ifs
+}
+
+
+# func_source file
+# Source FILE, adding directory component if necessary.
+# Note that it is not necessary on cygwin/mingw to append a dot to
+# FILE even if both FILE and FILE.exe exist: automatic-append-.exe
+# behavior happens only for exec(3), not for open(2)!  Also, sourcing
+# `FILE.' does not work on cygwin managed mounts.
+func_source ()
+{
+    $opt_debug
+    case $1 in
+    */* | *\\*)	. "$1" ;;
+    *)		. "./$1" ;;
+    esac
+}
+
+
+# func_resolve_sysroot PATH
+# Replace a leading = in PATH with a sysroot.  Store the result into
+# func_resolve_sysroot_result
+func_resolve_sysroot ()
+{
+  func_resolve_sysroot_result=$1
+  case $func_resolve_sysroot_result in
+  =*)
+    func_stripname '=' '' "$func_resolve_sysroot_result"
+    func_resolve_sysroot_result=$lt_sysroot$func_stripname_result
+    ;;
+  esac
+}
+
+# func_replace_sysroot PATH
+# If PATH begins with the sysroot, replace it with = and
+# store the result into func_replace_sysroot_result.
+func_replace_sysroot ()
+{
+  case "$lt_sysroot:$1" in
+  ?*:"$lt_sysroot"*)
+    func_stripname "$lt_sysroot" '' "$1"
+    func_replace_sysroot_result="=$func_stripname_result"
+    ;;
+  *)
+    # Including no sysroot.
+    func_replace_sysroot_result=$1
+    ;;
+  esac
+}
+
+# func_infer_tag arg
+# Infer tagged configuration to use if any are available and
+# if one wasn't chosen via the "--tag" command line option.
+# Only attempt this if the compiler in the base compile
+# command doesn't match the default compiler.
+# arg is usually of the form 'gcc ...'
+func_infer_tag ()
+{
+    $opt_debug
+    if test -n "$available_tags" && test -z "$tagname"; then
+      CC_quoted=
+      for arg in $CC; do
+	func_append_quoted CC_quoted "$arg"
+      done
+      CC_expanded=`func_echo_all $CC`
+      CC_quoted_expanded=`func_echo_all $CC_quoted`
+      case $@ in
+      # Blanks in the command may have been stripped by the calling shell,
+      # but not from the CC environment variable when configure was run.
+      " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \
+      " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) ;;
+      # Blanks at the start of $base_compile will cause this to fail
+      # if we don't check for them as well.
+      *)
+	for z in $available_tags; do
+	  if $GREP "^# ### BEGIN LIBTOOL TAG CONFIG: $z$" < "$progpath" > /dev/null; then
+	    # Evaluate the configuration.
+	    eval "`${SED} -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$z'$/,/^# ### END LIBTOOL TAG CONFIG: '$z'$/p' < $progpath`"
+	    CC_quoted=
+	    for arg in $CC; do
+	      # Double-quote args containing other shell metacharacters.
+	      func_append_quoted CC_quoted "$arg"
+	    done
+	    CC_expanded=`func_echo_all $CC`
+	    CC_quoted_expanded=`func_echo_all $CC_quoted`
+	    case "$@ " in
+	    " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \
+	    " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*)
+	      # The compiler in the base compile command matches
+	      # the one in the tagged configuration.
+	      # Assume this is the tagged configuration we want.
+	      tagname=$z
+	      break
+	      ;;
+	    esac
+	  fi
+	done
+	# If $tagname still isn't set, then no tagged configuration
+	# was found and let the user know that the "--tag" command
+	# line option must be used.
+	if test -z "$tagname"; then
+	  func_echo "unable to infer tagged configuration"
+	  func_fatal_error "specify a tag with \`--tag'"
+#	else
+#	  func_verbose "using $tagname tagged configuration"
+	fi
+	;;
+      esac
+    fi
+}
+
+
+
+# func_write_libtool_object output_name pic_name nonpic_name
+# Create a libtool object file (analogous to a ".la" file),
+# but don't create it if we're doing a dry run.
+func_write_libtool_object ()
+{
+    write_libobj=${1}
+    if test "$build_libtool_libs" = yes; then
+      write_lobj=\'${2}\'
+    else
+      write_lobj=none
+    fi
+
+    if test "$build_old_libs" = yes; then
+      write_oldobj=\'${3}\'
+    else
+      write_oldobj=none
+    fi
+
+    $opt_dry_run || {
+      cat >${write_libobj}T <<EOF
+# $write_libobj - a libtool object file
+# Generated by $PROGRAM (GNU $PACKAGE$TIMESTAMP) $VERSION
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# Name of the PIC object.
+pic_object=$write_lobj
+
+# Name of the non-PIC object
+non_pic_object=$write_oldobj
+
+EOF
+      $MV "${write_libobj}T" "${write_libobj}"
+    }
+}
+
+
+##################################################
+# FILE NAME AND PATH CONVERSION HELPER FUNCTIONS #
+##################################################
+
+# func_convert_core_file_wine_to_w32 ARG
+# Helper function used by file name conversion functions when $build is *nix,
+# and $host is mingw, cygwin, or some other w32 environment. Relies on a
+# correctly configured wine environment available, with the winepath program
+# in $build's $PATH.
+#
+# ARG is the $build file name to be converted to w32 format.
+# Result is available in $func_convert_core_file_wine_to_w32_result, and will
+# be empty on error (or when ARG is empty)
+func_convert_core_file_wine_to_w32 ()
+{
+  $opt_debug
+  func_convert_core_file_wine_to_w32_result="$1"
+  if test -n "$1"; then
+    # Unfortunately, winepath does not exit with a non-zero error code, so we
+    # are forced to check the contents of stdout. On the other hand, if the
+    # command is not found, the shell will set an exit code of 127 and print
+    # *an error message* to stdout. So we must check for both error code of
+    # zero AND non-empty stdout, which explains the odd construction:
+    func_convert_core_file_wine_to_w32_tmp=`winepath -w "$1" 2>/dev/null`
+    if test "$?" -eq 0 && test -n "${func_convert_core_file_wine_to_w32_tmp}"; then
+      func_convert_core_file_wine_to_w32_result=`$ECHO "$func_convert_core_file_wine_to_w32_tmp" |
+        $SED -e "$lt_sed_naive_backslashify"`
+    else
+      func_convert_core_file_wine_to_w32_result=
+    fi
+  fi
+}
+# end: func_convert_core_file_wine_to_w32
+
+
+# func_convert_core_path_wine_to_w32 ARG
+# Helper function used by path conversion functions when $build is *nix, and
+# $host is mingw, cygwin, or some other w32 environment. Relies on a correctly
+# configured wine environment available, with the winepath program in $build's
+# $PATH. Assumes ARG has no leading or trailing path separator characters.
+#
+# ARG is path to be converted from $build format to win32.
+# Result is available in $func_convert_core_path_wine_to_w32_result.
+# Unconvertible file (directory) names in ARG are skipped; if no directory names
+# are convertible, then the result may be empty.
+func_convert_core_path_wine_to_w32 ()
+{
+  $opt_debug
+  # unfortunately, winepath doesn't convert paths, only file names
+  func_convert_core_path_wine_to_w32_result=""
+  if test -n "$1"; then
+    oldIFS=$IFS
+    IFS=:
+    for func_convert_core_path_wine_to_w32_f in $1; do
+      IFS=$oldIFS
+      func_convert_core_file_wine_to_w32 "$func_convert_core_path_wine_to_w32_f"
+      if test -n "$func_convert_core_file_wine_to_w32_result" ; then
+        if test -z "$func_convert_core_path_wine_to_w32_result"; then
+          func_convert_core_path_wine_to_w32_result="$func_convert_core_file_wine_to_w32_result"
+        else
+          func_append func_convert_core_path_wine_to_w32_result ";$func_convert_core_file_wine_to_w32_result"
+        fi
+      fi
+    done
+    IFS=$oldIFS
+  fi
+}
+# end: func_convert_core_path_wine_to_w32
+
+
+# func_cygpath ARGS...
+# Wrapper around calling the cygpath program via LT_CYGPATH. This is used when
+# when (1) $build is *nix and Cygwin is hosted via a wine environment; or (2)
+# $build is MSYS and $host is Cygwin, or (3) $build is Cygwin. In case (1) or
+# (2), returns the Cygwin file name or path in func_cygpath_result (input
+# file name or path is assumed to be in w32 format, as previously converted
+# from $build's *nix or MSYS format). In case (3), returns the w32 file name
+# or path in func_cygpath_result (input file name or path is assumed to be in
+# Cygwin format). Returns an empty string on error.
+#
+# ARGS are passed to cygpath, with the last one being the file name or path to
+# be converted.
+#
+# Specify the absolute *nix (or w32) name to cygpath in the LT_CYGPATH
+# environment variable; do not put it in $PATH.
+func_cygpath ()
+{
+  $opt_debug
+  if test -n "$LT_CYGPATH" && test -f "$LT_CYGPATH"; then
+    func_cygpath_result=`$LT_CYGPATH "$@" 2>/dev/null`
+    if test "$?" -ne 0; then
+      # on failure, ensure result is empty
+      func_cygpath_result=
+    fi
+  else
+    func_cygpath_result=
+    func_error "LT_CYGPATH is empty or specifies non-existent file: \`$LT_CYGPATH'"
+  fi
+}
+#end: func_cygpath
+
+
+# func_convert_core_msys_to_w32 ARG
+# Convert file name or path ARG from MSYS format to w32 format.  Return
+# result in func_convert_core_msys_to_w32_result.
+func_convert_core_msys_to_w32 ()
+{
+  $opt_debug
+  # awkward: cmd appends spaces to result
+  func_convert_core_msys_to_w32_result=`( cmd //c echo "$1" ) 2>/dev/null |
+    $SED -e 's/[ ]*$//' -e "$lt_sed_naive_backslashify"`
+}
+#end: func_convert_core_msys_to_w32
+
+
+# func_convert_file_check ARG1 ARG2
+# Verify that ARG1 (a file name in $build format) was converted to $host
+# format in ARG2. Otherwise, emit an error message, but continue (resetting
+# func_to_host_file_result to ARG1).
+func_convert_file_check ()
+{
+  $opt_debug
+  if test -z "$2" && test -n "$1" ; then
+    func_error "Could not determine host file name corresponding to"
+    func_error "  \`$1'"
+    func_error "Continuing, but uninstalled executables may not work."
+    # Fallback:
+    func_to_host_file_result="$1"
+  fi
+}
+# end func_convert_file_check
+
+
+# func_convert_path_check FROM_PATHSEP TO_PATHSEP FROM_PATH TO_PATH
+# Verify that FROM_PATH (a path in $build format) was converted to $host
+# format in TO_PATH. Otherwise, emit an error message, but continue, resetting
+# func_to_host_file_result to a simplistic fallback value (see below).
+func_convert_path_check ()
+{
+  $opt_debug
+  if test -z "$4" && test -n "$3"; then
+    func_error "Could not determine the host path corresponding to"
+    func_error "  \`$3'"
+    func_error "Continuing, but uninstalled executables may not work."
+    # Fallback.  This is a deliberately simplistic "conversion" and
+    # should not be "improved".  See libtool.info.
+    if test "x$1" != "x$2"; then
+      lt_replace_pathsep_chars="s|$1|$2|g"
+      func_to_host_path_result=`echo "$3" |
+        $SED -e "$lt_replace_pathsep_chars"`
+    else
+      func_to_host_path_result="$3"
+    fi
+  fi
+}
+# end func_convert_path_check
+
+
+# func_convert_path_front_back_pathsep FRONTPAT BACKPAT REPL ORIG
+# Modifies func_to_host_path_result by prepending REPL if ORIG matches FRONTPAT
+# and appending REPL if ORIG matches BACKPAT.
+func_convert_path_front_back_pathsep ()
+{
+  $opt_debug
+  case $4 in
+  $1 ) func_to_host_path_result="$3$func_to_host_path_result"
+    ;;
+  esac
+  case $4 in
+  $2 ) func_append func_to_host_path_result "$3"
+    ;;
+  esac
+}
+# end func_convert_path_front_back_pathsep
+
+
+##################################################
+# $build to $host FILE NAME CONVERSION FUNCTIONS #
+##################################################
+# invoked via `$to_host_file_cmd ARG'
+#
+# In each case, ARG is the path to be converted from $build to $host format.
+# Result will be available in $func_to_host_file_result.
+
+
+# func_to_host_file ARG
+# Converts the file name ARG from $build format to $host format. Return result
+# in func_to_host_file_result.
+func_to_host_file ()
+{
+  $opt_debug
+  $to_host_file_cmd "$1"
+}
+# end func_to_host_file
+
+
+# func_to_tool_file ARG LAZY
+# converts the file name ARG from $build format to toolchain format. Return
+# result in func_to_tool_file_result.  If the conversion in use is listed
+# in (the comma separated) LAZY, no conversion takes place.
+func_to_tool_file ()
+{
+  $opt_debug
+  case ,$2, in
+    *,"$to_tool_file_cmd",*)
+      func_to_tool_file_result=$1
+      ;;
+    *)
+      $to_tool_file_cmd "$1"
+      func_to_tool_file_result=$func_to_host_file_result
+      ;;
+  esac
+}
+# end func_to_tool_file
+
+
+# func_convert_file_noop ARG
+# Copy ARG to func_to_host_file_result.
+func_convert_file_noop ()
+{
+  func_to_host_file_result="$1"
+}
+# end func_convert_file_noop
+
+
+# func_convert_file_msys_to_w32 ARG
+# Convert file name ARG from (mingw) MSYS to (mingw) w32 format; automatic
+# conversion to w32 is not available inside the cwrapper.  Returns result in
+# func_to_host_file_result.
+func_convert_file_msys_to_w32 ()
+{
+  $opt_debug
+  func_to_host_file_result="$1"
+  if test -n "$1"; then
+    func_convert_core_msys_to_w32 "$1"
+    func_to_host_file_result="$func_convert_core_msys_to_w32_result"
+  fi
+  func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_msys_to_w32
+
+
+# func_convert_file_cygwin_to_w32 ARG
+# Convert file name ARG from Cygwin to w32 format.  Returns result in
+# func_to_host_file_result.
+func_convert_file_cygwin_to_w32 ()
+{
+  $opt_debug
+  func_to_host_file_result="$1"
+  if test -n "$1"; then
+    # because $build is cygwin, we call "the" cygpath in $PATH; no need to use
+    # LT_CYGPATH in this case.
+    func_to_host_file_result=`cygpath -m "$1"`
+  fi
+  func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_cygwin_to_w32
+
+
+# func_convert_file_nix_to_w32 ARG
+# Convert file name ARG from *nix to w32 format.  Requires a wine environment
+# and a working winepath. Returns result in func_to_host_file_result.
+func_convert_file_nix_to_w32 ()
+{
+  $opt_debug
+  func_to_host_file_result="$1"
+  if test -n "$1"; then
+    func_convert_core_file_wine_to_w32 "$1"
+    func_to_host_file_result="$func_convert_core_file_wine_to_w32_result"
+  fi
+  func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_nix_to_w32
+
+
+# func_convert_file_msys_to_cygwin ARG
+# Convert file name ARG from MSYS to Cygwin format.  Requires LT_CYGPATH set.
+# Returns result in func_to_host_file_result.
+func_convert_file_msys_to_cygwin ()
+{
+  $opt_debug
+  func_to_host_file_result="$1"
+  if test -n "$1"; then
+    func_convert_core_msys_to_w32 "$1"
+    func_cygpath -u "$func_convert_core_msys_to_w32_result"
+    func_to_host_file_result="$func_cygpath_result"
+  fi
+  func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_msys_to_cygwin
+
+
+# func_convert_file_nix_to_cygwin ARG
+# Convert file name ARG from *nix to Cygwin format.  Requires Cygwin installed
+# in a wine environment, working winepath, and LT_CYGPATH set.  Returns result
+# in func_to_host_file_result.
+func_convert_file_nix_to_cygwin ()
+{
+  $opt_debug
+  func_to_host_file_result="$1"
+  if test -n "$1"; then
+    # convert from *nix to w32, then use cygpath to convert from w32 to cygwin.
+    func_convert_core_file_wine_to_w32 "$1"
+    func_cygpath -u "$func_convert_core_file_wine_to_w32_result"
+    func_to_host_file_result="$func_cygpath_result"
+  fi
+  func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_nix_to_cygwin
+
+
+#############################################
+# $build to $host PATH CONVERSION FUNCTIONS #
+#############################################
+# invoked via `$to_host_path_cmd ARG'
+#
+# In each case, ARG is the path to be converted from $build to $host format.
+# The result will be available in $func_to_host_path_result.
+#
+# Path separators are also converted from $build format to $host format.  If
+# ARG begins or ends with a path separator character, it is preserved (but
+# converted to $host format) on output.
+#
+# All path conversion functions are named using the following convention:
+#   file name conversion function    : func_convert_file_X_to_Y ()
+#   path conversion function         : func_convert_path_X_to_Y ()
+# where, for any given $build/$host combination the 'X_to_Y' value is the
+# same.  If conversion functions are added for new $build/$host combinations,
+# the two new functions must follow this pattern, or func_init_to_host_path_cmd
+# will break.
+
+
+# func_init_to_host_path_cmd
+# Ensures that function "pointer" variable $to_host_path_cmd is set to the
+# appropriate value, based on the value of $to_host_file_cmd.
+to_host_path_cmd=
+func_init_to_host_path_cmd ()
+{
+  $opt_debug
+  if test -z "$to_host_path_cmd"; then
+    func_stripname 'func_convert_file_' '' "$to_host_file_cmd"
+    to_host_path_cmd="func_convert_path_${func_stripname_result}"
+  fi
+}
+
+
+# func_to_host_path ARG
+# Converts the path ARG from $build format to $host format. Return result
+# in func_to_host_path_result.
+func_to_host_path ()
+{
+  $opt_debug
+  func_init_to_host_path_cmd
+  $to_host_path_cmd "$1"
+}
+# end func_to_host_path
+
+
+# func_convert_path_noop ARG
+# Copy ARG to func_to_host_path_result.
+func_convert_path_noop ()
+{
+  func_to_host_path_result="$1"
+}
+# end func_convert_path_noop
+
+
+# func_convert_path_msys_to_w32 ARG
+# Convert path ARG from (mingw) MSYS to (mingw) w32 format; automatic
+# conversion to w32 is not available inside the cwrapper.  Returns result in
+# func_to_host_path_result.
+func_convert_path_msys_to_w32 ()
+{
+  $opt_debug
+  func_to_host_path_result="$1"
+  if test -n "$1"; then
+    # Remove leading and trailing path separator characters from ARG.  MSYS
+    # behavior is inconsistent here; cygpath turns them into '.;' and ';.';
+    # and winepath ignores them completely.
+    func_stripname : : "$1"
+    func_to_host_path_tmp1=$func_stripname_result
+    func_convert_core_msys_to_w32 "$func_to_host_path_tmp1"
+    func_to_host_path_result="$func_convert_core_msys_to_w32_result"
+    func_convert_path_check : ";" \
+      "$func_to_host_path_tmp1" "$func_to_host_path_result"
+    func_convert_path_front_back_pathsep ":*" "*:" ";" "$1"
+  fi
+}
+# end func_convert_path_msys_to_w32
+
+
+# func_convert_path_cygwin_to_w32 ARG
+# Convert path ARG from Cygwin to w32 format.  Returns result in
+# func_to_host_file_result.
+func_convert_path_cygwin_to_w32 ()
+{
+  $opt_debug
+  func_to_host_path_result="$1"
+  if test -n "$1"; then
+    # See func_convert_path_msys_to_w32:
+    func_stripname : : "$1"
+    func_to_host_path_tmp1=$func_stripname_result
+    func_to_host_path_result=`cygpath -m -p "$func_to_host_path_tmp1"`
+    func_convert_path_check : ";" \
+      "$func_to_host_path_tmp1" "$func_to_host_path_result"
+    func_convert_path_front_back_pathsep ":*" "*:" ";" "$1"
+  fi
+}
+# end func_convert_path_cygwin_to_w32
+
+
+# func_convert_path_nix_to_w32 ARG
+# Convert path ARG from *nix to w32 format.  Requires a wine environment and
+# a working winepath.  Returns result in func_to_host_file_result.
+func_convert_path_nix_to_w32 ()
+{
+  $opt_debug
+  func_to_host_path_result="$1"
+  if test -n "$1"; then
+    # See func_convert_path_msys_to_w32:
+    func_stripname : : "$1"
+    func_to_host_path_tmp1=$func_stripname_result
+    func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1"
+    func_to_host_path_result="$func_convert_core_path_wine_to_w32_result"
+    func_convert_path_check : ";" \
+      "$func_to_host_path_tmp1" "$func_to_host_path_result"
+    func_convert_path_front_back_pathsep ":*" "*:" ";" "$1"
+  fi
+}
+# end func_convert_path_nix_to_w32
+
+
+# func_convert_path_msys_to_cygwin ARG
+# Convert path ARG from MSYS to Cygwin format.  Requires LT_CYGPATH set.
+# Returns result in func_to_host_file_result.
+func_convert_path_msys_to_cygwin ()
+{
+  $opt_debug
+  func_to_host_path_result="$1"
+  if test -n "$1"; then
+    # See func_convert_path_msys_to_w32:
+    func_stripname : : "$1"
+    func_to_host_path_tmp1=$func_stripname_result
+    func_convert_core_msys_to_w32 "$func_to_host_path_tmp1"
+    func_cygpath -u -p "$func_convert_core_msys_to_w32_result"
+    func_to_host_path_result="$func_cygpath_result"
+    func_convert_path_check : : \
+      "$func_to_host_path_tmp1" "$func_to_host_path_result"
+    func_convert_path_front_back_pathsep ":*" "*:" : "$1"
+  fi
+}
+# end func_convert_path_msys_to_cygwin
+
+
+# func_convert_path_nix_to_cygwin ARG
+# Convert path ARG from *nix to Cygwin format.  Requires Cygwin installed in a
+# a wine environment, working winepath, and LT_CYGPATH set.  Returns result in
+# func_to_host_file_result.
+func_convert_path_nix_to_cygwin ()
+{
+  $opt_debug
+  func_to_host_path_result="$1"
+  if test -n "$1"; then
+    # Remove leading and trailing path separator characters from
+    # ARG. msys behavior is inconsistent here, cygpath turns them
+    # into '.;' and ';.', and winepath ignores them completely.
+    func_stripname : : "$1"
+    func_to_host_path_tmp1=$func_stripname_result
+    func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1"
+    func_cygpath -u -p "$func_convert_core_path_wine_to_w32_result"
+    func_to_host_path_result="$func_cygpath_result"
+    func_convert_path_check : : \
+      "$func_to_host_path_tmp1" "$func_to_host_path_result"
+    func_convert_path_front_back_pathsep ":*" "*:" : "$1"
+  fi
+}
+# end func_convert_path_nix_to_cygwin
+
+
+# func_mode_compile arg...
+func_mode_compile ()
+{
+    $opt_debug
+    # Get the compilation command and the source file.
+    base_compile=
+    srcfile="$nonopt"  #  always keep a non-empty value in "srcfile"
+    suppress_opt=yes
+    suppress_output=
+    arg_mode=normal
+    libobj=
+    later=
+    pie_flag=
+
+    for arg
+    do
+      case $arg_mode in
+      arg  )
+	# do not "continue".  Instead, add this to base_compile
+	lastarg="$arg"
+	arg_mode=normal
+	;;
+
+      target )
+	libobj="$arg"
+	arg_mode=normal
+	continue
+	;;
+
+      normal )
+	# Accept any command-line options.
+	case $arg in
+	-o)
+	  test -n "$libobj" && \
+	    func_fatal_error "you cannot specify \`-o' more than once"
+	  arg_mode=target
+	  continue
+	  ;;
+
+	-pie | -fpie | -fPIE)
+          func_append pie_flag " $arg"
+	  continue
+	  ;;
+
+	-shared | -static | -prefer-pic | -prefer-non-pic)
+	  func_append later " $arg"
+	  continue
+	  ;;
+
+	-no-suppress)
+	  suppress_opt=no
+	  continue
+	  ;;
+
+	-Xcompiler)
+	  arg_mode=arg  #  the next one goes into the "base_compile" arg list
+	  continue      #  The current "srcfile" will either be retained or
+	  ;;            #  replaced later.  I would guess that would be a bug.
+
+	-Wc,*)
+	  func_stripname '-Wc,' '' "$arg"
+	  args=$func_stripname_result
+	  lastarg=
+	  save_ifs="$IFS"; IFS=','
+	  for arg in $args; do
+	    IFS="$save_ifs"
+	    func_append_quoted lastarg "$arg"
+	  done
+	  IFS="$save_ifs"
+	  func_stripname ' ' '' "$lastarg"
+	  lastarg=$func_stripname_result
+
+	  # Add the arguments to base_compile.
+	  func_append base_compile " $lastarg"
+	  continue
+	  ;;
+
+	*)
+	  # Accept the current argument as the source file.
+	  # The previous "srcfile" becomes the current argument.
+	  #
+	  lastarg="$srcfile"
+	  srcfile="$arg"
+	  ;;
+	esac  #  case $arg
+	;;
+      esac    #  case $arg_mode
+
+      # Aesthetically quote the previous argument.
+      func_append_quoted base_compile "$lastarg"
+    done # for arg
+
+    case $arg_mode in
+    arg)
+      func_fatal_error "you must specify an argument for -Xcompile"
+      ;;
+    target)
+      func_fatal_error "you must specify a target with \`-o'"
+      ;;
+    *)
+      # Get the name of the library object.
+      test -z "$libobj" && {
+	func_basename "$srcfile"
+	libobj="$func_basename_result"
+      }
+      ;;
+    esac
+
+    # Recognize several different file suffixes.
+    # If the user specifies -o file.o, it is replaced with file.lo
+    case $libobj in
+    *.[cCFSifmso] | \
+    *.ada | *.adb | *.ads | *.asm | \
+    *.c++ | *.cc | *.ii | *.class | *.cpp | *.cxx | \
+    *.[fF][09]? | *.for | *.java | *.go | *.obj | *.sx | *.cu | *.cup)
+      func_xform "$libobj"
+      libobj=$func_xform_result
+      ;;
+    esac
+
+    case $libobj in
+    *.lo) func_lo2o "$libobj"; obj=$func_lo2o_result ;;
+    *)
+      func_fatal_error "cannot determine name of library object from \`$libobj'"
+      ;;
+    esac
+
+    func_infer_tag $base_compile
+
+    for arg in $later; do
+      case $arg in
+      -shared)
+	test "$build_libtool_libs" != yes && \
+	  func_fatal_configuration "can not build a shared library"
+	build_old_libs=no
+	continue
+	;;
+
+      -static)
+	build_libtool_libs=no
+	build_old_libs=yes
+	continue
+	;;
+
+      -prefer-pic)
+	pic_mode=yes
+	continue
+	;;
+
+      -prefer-non-pic)
+	pic_mode=no
+	continue
+	;;
+      esac
+    done
+
+    func_quote_for_eval "$libobj"
+    test "X$libobj" != "X$func_quote_for_eval_result" \
+      && $ECHO "X$libobj" | $GREP '[]~#^*{};<>?"'"'"'	 &()|`$[]' \
+      && func_warning "libobj name \`$libobj' may not contain shell special characters."
+    func_dirname_and_basename "$obj" "/" ""
+    objname="$func_basename_result"
+    xdir="$func_dirname_result"
+    lobj=${xdir}$objdir/$objname
+
+    test -z "$base_compile" && \
+      func_fatal_help "you must specify a compilation command"
+
+    # Delete any leftover library objects.
+    if test "$build_old_libs" = yes; then
+      removelist="$obj $lobj $libobj ${libobj}T"
+    else
+      removelist="$lobj $libobj ${libobj}T"
+    fi
+
+    # On Cygwin there's no "real" PIC flag so we must build both object types
+    case $host_os in
+    cygwin* | mingw* | pw32* | os2* | cegcc*)
+      pic_mode=default
+      ;;
+    esac
+    if test "$pic_mode" = no && test "$deplibs_check_method" != pass_all; then
+      # non-PIC code in shared libraries is not supported
+      pic_mode=default
+    fi
+
+    # Calculate the filename of the output object if compiler does
+    # not support -o with -c
+    if test "$compiler_c_o" = no; then
+      output_obj=`$ECHO "$srcfile" | $SED 's%^.*/%%; s%\.[^.]*$%%'`.${objext}
+      lockfile="$output_obj.lock"
+    else
+      output_obj=
+      need_locks=no
+      lockfile=
+    fi
+
+    # Lock this critical section if it is needed
+    # We use this script file to make the link, it avoids creating a new file
+    if test "$need_locks" = yes; then
+      until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do
+	func_echo "Waiting for $lockfile to be removed"
+	sleep 2
+      done
+    elif test "$need_locks" = warn; then
+      if test -f "$lockfile"; then
+	$ECHO "\
+*** ERROR, $lockfile exists and contains:
+`cat $lockfile 2>/dev/null`
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support \`-c' and \`-o' together.  If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+	$opt_dry_run || $RM $removelist
+	exit $EXIT_FAILURE
+      fi
+      func_append removelist " $output_obj"
+      $ECHO "$srcfile" > "$lockfile"
+    fi
+
+    $opt_dry_run || $RM $removelist
+    func_append removelist " $lockfile"
+    trap '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' 1 2 15
+
+    func_to_tool_file "$srcfile" func_convert_file_msys_to_w32
+    srcfile=$func_to_tool_file_result
+    func_quote_for_eval "$srcfile"
+    qsrcfile=$func_quote_for_eval_result
+
+    # Only build a PIC object if we are building libtool libraries.
+    if test "$build_libtool_libs" = yes; then
+      # Without this assignment, base_compile gets emptied.
+      fbsd_hideous_sh_bug=$base_compile
+
+      if test "$pic_mode" != no; then
+	command="$base_compile $qsrcfile $pic_flag"
+      else
+	# Don't build PIC code
+	command="$base_compile $qsrcfile"
+      fi
+
+      func_mkdir_p "$xdir$objdir"
+
+      if test -z "$output_obj"; then
+	# Place PIC objects in $objdir
+	func_append command " -o $lobj"
+      fi
+
+      func_show_eval_locale "$command"	\
+          'test -n "$output_obj" && $RM $removelist; exit $EXIT_FAILURE'
+
+      if test "$need_locks" = warn &&
+	 test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then
+	$ECHO "\
+*** ERROR, $lockfile contains:
+`cat $lockfile 2>/dev/null`
+
+but it should contain:
+$srcfile
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support \`-c' and \`-o' together.  If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+	$opt_dry_run || $RM $removelist
+	exit $EXIT_FAILURE
+      fi
+
+      # Just move the object if needed, then go on to compile the next one
+      if test -n "$output_obj" && test "X$output_obj" != "X$lobj"; then
+	func_show_eval '$MV "$output_obj" "$lobj"' \
+	  'error=$?; $opt_dry_run || $RM $removelist; exit $error'
+      fi
+
+      # Allow error messages only from the first compilation.
+      if test "$suppress_opt" = yes; then
+	suppress_output=' >/dev/null 2>&1'
+      fi
+    fi
+
+    # Only build a position-dependent object if we build old libraries.
+    if test "$build_old_libs" = yes; then
+      if test "$pic_mode" != yes; then
+	# Don't build PIC code
+	command="$base_compile $qsrcfile$pie_flag"
+      else
+	command="$base_compile $qsrcfile $pic_flag"
+      fi
+      if test "$compiler_c_o" = yes; then
+	func_append command " -o $obj"
+      fi
+
+      # Suppress compiler output if we already did a PIC compilation.
+      func_append command "$suppress_output"
+      func_show_eval_locale "$command" \
+        '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE'
+
+      if test "$need_locks" = warn &&
+	 test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then
+	$ECHO "\
+*** ERROR, $lockfile contains:
+`cat $lockfile 2>/dev/null`
+
+but it should contain:
+$srcfile
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support \`-c' and \`-o' together.  If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+	$opt_dry_run || $RM $removelist
+	exit $EXIT_FAILURE
+      fi
+
+      # Just move the object if needed
+      if test -n "$output_obj" && test "X$output_obj" != "X$obj"; then
+	func_show_eval '$MV "$output_obj" "$obj"' \
+	  'error=$?; $opt_dry_run || $RM $removelist; exit $error'
+      fi
+    fi
+
+    $opt_dry_run || {
+      func_write_libtool_object "$libobj" "$objdir/$objname" "$objname"
+
+      # Unlock the critical section if it was locked
+      if test "$need_locks" != no; then
+	removelist=$lockfile
+        $RM "$lockfile"
+      fi
+    }
+
+    exit $EXIT_SUCCESS
+}
+
+$opt_help || {
+  test "$opt_mode" = compile && func_mode_compile ${1+"$@"}
+}
+
+func_mode_help ()
+{
+    # We need to display help for each of the modes.
+    case $opt_mode in
+      "")
+        # Generic help is extracted from the usage comments
+        # at the start of this file.
+        func_help
+        ;;
+
+      clean)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=clean RM [RM-OPTION]... FILE...
+
+Remove files from the build directory.
+
+RM is the name of the program to use to delete files associated with each FILE
+(typically \`/bin/rm').  RM-OPTIONS are options (such as \`-f') to be passed
+to RM.
+
+If FILE is a libtool library, object or program, all the files associated
+with it are deleted. Otherwise, only FILE itself is deleted using RM."
+        ;;
+
+      compile)
+      $ECHO \
+"Usage: $progname [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE
+
+Compile a source file into a libtool library object.
+
+This mode accepts the following additional options:
+
+  -o OUTPUT-FILE    set the output file name to OUTPUT-FILE
+  -no-suppress      do not suppress compiler output for multiple passes
+  -prefer-pic       try to build PIC objects only
+  -prefer-non-pic   try to build non-PIC objects only
+  -shared           do not build a \`.o' file suitable for static linking
+  -static           only build a \`.o' file suitable for static linking
+  -Wc,FLAG          pass FLAG directly to the compiler
+
+COMPILE-COMMAND is a command to be used in creating a \`standard' object file
+from the given SOURCEFILE.
+
+The output file name is determined by removing the directory component from
+SOURCEFILE, then substituting the C source code suffix \`.c' with the
+library object suffix, \`.lo'."
+        ;;
+
+      execute)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=execute COMMAND [ARGS]...
+
+Automatically set library path, then run a program.
+
+This mode accepts the following additional options:
+
+  -dlopen FILE      add the directory containing FILE to the library path
+
+This mode sets the library path environment variable according to \`-dlopen'
+flags.
+
+If any of the ARGS are libtool executable wrappers, then they are translated
+into their corresponding uninstalled binary, and any of their required library
+directories are added to the library path.
+
+Then, COMMAND is executed, with ARGS as arguments."
+        ;;
+
+      finish)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=finish [LIBDIR]...
+
+Complete the installation of libtool libraries.
+
+Each LIBDIR is a directory that contains libtool libraries.
+
+The commands that this mode executes may require superuser privileges.  Use
+the \`--dry-run' option if you just want to see what would be executed."
+        ;;
+
+      install)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=install INSTALL-COMMAND...
+
+Install executables or libraries.
+
+INSTALL-COMMAND is the installation command.  The first component should be
+either the \`install' or \`cp' program.
+
+The following components of INSTALL-COMMAND are treated specially:
+
+  -inst-prefix-dir PREFIX-DIR  Use PREFIX-DIR as a staging area for installation
+
+The rest of the components are interpreted as arguments to that command (only
+BSD-compatible install options are recognized)."
+        ;;
+
+      link)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=link LINK-COMMAND...
+
+Link object files or libraries together to form another library, or to
+create an executable program.
+
+LINK-COMMAND is a command using the C compiler that you would use to create
+a program from several object files.
+
+The following components of LINK-COMMAND are treated specially:
+
+  -all-static       do not do any dynamic linking at all
+  -avoid-version    do not add a version suffix if possible
+  -bindir BINDIR    specify path to binaries directory (for systems where
+                    libraries must be found in the PATH setting at runtime)
+  -dlopen FILE      \`-dlpreopen' FILE if it cannot be dlopened at runtime
+  -dlpreopen FILE   link in FILE and add its symbols to lt_preloaded_symbols
+  -export-dynamic   allow symbols from OUTPUT-FILE to be resolved with dlsym(3)
+  -export-symbols SYMFILE
+                    try to export only the symbols listed in SYMFILE
+  -export-symbols-regex REGEX
+                    try to export only the symbols matching REGEX
+  -LLIBDIR          search LIBDIR for required installed libraries
+  -lNAME            OUTPUT-FILE requires the installed library libNAME
+  -module           build a library that can dlopened
+  -no-fast-install  disable the fast-install mode
+  -no-install       link a not-installable executable
+  -no-undefined     declare that a library does not refer to external symbols
+  -o OUTPUT-FILE    create OUTPUT-FILE from the specified objects
+  -objectlist FILE  Use a list of object files found in FILE to specify objects
+  -precious-files-regex REGEX
+                    don't remove output files matching REGEX
+  -release RELEASE  specify package release information
+  -rpath LIBDIR     the created library will eventually be installed in LIBDIR
+  -R[ ]LIBDIR       add LIBDIR to the runtime path of programs and libraries
+  -shared           only do dynamic linking of libtool libraries
+  -shrext SUFFIX    override the standard shared library file extension
+  -static           do not do any dynamic linking of uninstalled libtool libraries
+  -static-libtool-libs
+                    do not do any dynamic linking of libtool libraries
+  -version-info CURRENT[:REVISION[:AGE]]
+                    specify library version info [each variable defaults to 0]
+  -weak LIBNAME     declare that the target provides the LIBNAME interface
+  -Wc,FLAG
+  -Xcompiler FLAG   pass linker-specific FLAG directly to the compiler
+  -Wl,FLAG
+  -Xlinker FLAG     pass linker-specific FLAG directly to the linker
+  -XCClinker FLAG   pass link-specific FLAG to the compiler driver (CC)
+
+All other options (arguments beginning with \`-') are ignored.
+
+Every other argument is treated as a filename.  Files ending in \`.la' are
+treated as uninstalled libtool libraries, other files are standard or library
+object files.
+
+If the OUTPUT-FILE ends in \`.la', then a libtool library is created,
+only library objects (\`.lo' files) may be specified, and \`-rpath' is
+required, except when creating a convenience library.
+
+If OUTPUT-FILE ends in \`.a' or \`.lib', then a standard library is created
+using \`ar' and \`ranlib', or on Windows using \`lib'.
+
+If OUTPUT-FILE ends in \`.lo' or \`.${objext}', then a reloadable object file
+is created, otherwise an executable program is created."
+        ;;
+
+      uninstall)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE...
+
+Remove libraries from an installation directory.
+
+RM is the name of the program to use to delete files associated with each FILE
+(typically \`/bin/rm').  RM-OPTIONS are options (such as \`-f') to be passed
+to RM.
+
+If FILE is a libtool library, all the files associated with it are deleted.
+Otherwise, only FILE itself is deleted using RM."
+        ;;
+
+      *)
+        func_fatal_help "invalid operation mode \`$opt_mode'"
+        ;;
+    esac
+
+    echo
+    $ECHO "Try \`$progname --help' for more information about other modes."
+}
+
+# Now that we've collected a possible --mode arg, show help if necessary
+if $opt_help; then
+  if test "$opt_help" = :; then
+    func_mode_help
+  else
+    {
+      func_help noexit
+      for opt_mode in compile link execute install finish uninstall clean; do
+	func_mode_help
+      done
+    } | sed -n '1p; 2,$s/^Usage:/  or: /p'
+    {
+      func_help noexit
+      for opt_mode in compile link execute install finish uninstall clean; do
+	echo
+	func_mode_help
+      done
+    } |
+    sed '1d
+      /^When reporting/,/^Report/{
+	H
+	d
+      }
+      $x
+      /information about other modes/d
+      /more detailed .*MODE/d
+      s/^Usage:.*--mode=\([^ ]*\) .*/Description of \1 mode:/'
+  fi
+  exit $?
+fi
+
+
+# func_mode_execute arg...
+func_mode_execute ()
+{
+    $opt_debug
+    # The first argument is the command name.
+    cmd="$nonopt"
+    test -z "$cmd" && \
+      func_fatal_help "you must specify a COMMAND"
+
+    # Handle -dlopen flags immediately.
+    for file in $opt_dlopen; do
+      test -f "$file" \
+	|| func_fatal_help "\`$file' is not a file"
+
+      dir=
+      case $file in
+      *.la)
+	func_resolve_sysroot "$file"
+	file=$func_resolve_sysroot_result
+
+	# Check to see that this really is a libtool archive.
+	func_lalib_unsafe_p "$file" \
+	  || func_fatal_help "\`$lib' is not a valid libtool archive"
+
+	# Read the libtool library.
+	dlname=
+	library_names=
+	func_source "$file"
+
+	# Skip this library if it cannot be dlopened.
+	if test -z "$dlname"; then
+	  # Warn if it was a shared library.
+	  test -n "$library_names" && \
+	    func_warning "\`$file' was not linked with \`-export-dynamic'"
+	  continue
+	fi
+
+	func_dirname "$file" "" "."
+	dir="$func_dirname_result"
+
+	if test -f "$dir/$objdir/$dlname"; then
+	  func_append dir "/$objdir"
+	else
+	  if test ! -f "$dir/$dlname"; then
+	    func_fatal_error "cannot find \`$dlname' in \`$dir' or \`$dir/$objdir'"
+	  fi
+	fi
+	;;
+
+      *.lo)
+	# Just add the directory containing the .lo file.
+	func_dirname "$file" "" "."
+	dir="$func_dirname_result"
+	;;
+
+      *)
+	func_warning "\`-dlopen' is ignored for non-libtool libraries and objects"
+	continue
+	;;
+      esac
+
+      # Get the absolute pathname.
+      absdir=`cd "$dir" && pwd`
+      test -n "$absdir" && dir="$absdir"
+
+      # Now add the directory to shlibpath_var.
+      if eval "test -z \"\$$shlibpath_var\""; then
+	eval "$shlibpath_var=\"\$dir\""
+      else
+	eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\""
+      fi
+    done
+
+    # This variable tells wrapper scripts just to set shlibpath_var
+    # rather than running their programs.
+    libtool_execute_magic="$magic"
+
+    # Check if any of the arguments is a wrapper script.
+    args=
+    for file
+    do
+      case $file in
+      -* | *.la | *.lo ) ;;
+      *)
+	# Do a test to see if this is really a libtool program.
+	if func_ltwrapper_script_p "$file"; then
+	  func_source "$file"
+	  # Transform arg to wrapped name.
+	  file="$progdir/$program"
+	elif func_ltwrapper_executable_p "$file"; then
+	  func_ltwrapper_scriptname "$file"
+	  func_source "$func_ltwrapper_scriptname_result"
+	  # Transform arg to wrapped name.
+	  file="$progdir/$program"
+	fi
+	;;
+      esac
+      # Quote arguments (to preserve shell metacharacters).
+      func_append_quoted args "$file"
+    done
+
+    if test "X$opt_dry_run" = Xfalse; then
+      if test -n "$shlibpath_var"; then
+	# Export the shlibpath_var.
+	eval "export $shlibpath_var"
+      fi
+
+      # Restore saved environment variables
+      for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES
+      do
+	eval "if test \"\${save_$lt_var+set}\" = set; then
+                $lt_var=\$save_$lt_var; export $lt_var
+	      else
+		$lt_unset $lt_var
+	      fi"
+      done
+
+      # Now prepare to actually exec the command.
+      exec_cmd="\$cmd$args"
+    else
+      # Display what would be done.
+      if test -n "$shlibpath_var"; then
+	eval "\$ECHO \"\$shlibpath_var=\$$shlibpath_var\""
+	echo "export $shlibpath_var"
+      fi
+      $ECHO "$cmd$args"
+      exit $EXIT_SUCCESS
+    fi
+}
+
+test "$opt_mode" = execute && func_mode_execute ${1+"$@"}
+
+
+# func_mode_finish arg...
+func_mode_finish ()
+{
+    $opt_debug
+    libs=
+    libdirs=
+    admincmds=
+
+    for opt in "$nonopt" ${1+"$@"}
+    do
+      if test -d "$opt"; then
+	func_append libdirs " $opt"
+
+      elif test -f "$opt"; then
+	if func_lalib_unsafe_p "$opt"; then
+	  func_append libs " $opt"
+	else
+	  func_warning "\`$opt' is not a valid libtool archive"
+	fi
+
+      else
+	func_fatal_error "invalid argument \`$opt'"
+      fi
+    done
+
+    if test -n "$libs"; then
+      if test -n "$lt_sysroot"; then
+        sysroot_regex=`$ECHO "$lt_sysroot" | $SED "$sed_make_literal_regex"`
+        sysroot_cmd="s/\([ ']\)$sysroot_regex/\1/g;"
+      else
+        sysroot_cmd=
+      fi
+
+      # Remove sysroot references
+      if $opt_dry_run; then
+        for lib in $libs; do
+          echo "removing references to $lt_sysroot and \`=' prefixes from $lib"
+        done
+      else
+        tmpdir=`func_mktempdir`
+        for lib in $libs; do
+	  sed -e "${sysroot_cmd} s/\([ ']-[LR]\)=/\1/g; s/\([ ']\)=/\1/g" $lib \
+	    > $tmpdir/tmp-la
+	  mv -f $tmpdir/tmp-la $lib
+	done
+        ${RM}r "$tmpdir"
+      fi
+    fi
+
+    if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then
+      for libdir in $libdirs; do
+	if test -n "$finish_cmds"; then
+	  # Do each command in the finish commands.
+	  func_execute_cmds "$finish_cmds" 'admincmds="$admincmds
+'"$cmd"'"'
+	fi
+	if test -n "$finish_eval"; then
+	  # Do the single finish_eval.
+	  eval cmds=\"$finish_eval\"
+	  $opt_dry_run || eval "$cmds" || func_append admincmds "
+       $cmds"
+	fi
+      done
+    fi
+
+    # Exit here if they wanted silent mode.
+    $opt_silent && exit $EXIT_SUCCESS
+
+    if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then
+      echo "----------------------------------------------------------------------"
+      echo "Libraries have been installed in:"
+      for libdir in $libdirs; do
+	$ECHO "   $libdir"
+      done
+      echo
+      echo "If you ever happen to want to link against installed libraries"
+      echo "in a given directory, LIBDIR, you must either use libtool, and"
+      echo "specify the full pathname of the library, or use the \`-LLIBDIR'"
+      echo "flag during linking and do at least one of the following:"
+      if test -n "$shlibpath_var"; then
+	echo "   - add LIBDIR to the \`$shlibpath_var' environment variable"
+	echo "     during execution"
+      fi
+      if test -n "$runpath_var"; then
+	echo "   - add LIBDIR to the \`$runpath_var' environment variable"
+	echo "     during linking"
+      fi
+      if test -n "$hardcode_libdir_flag_spec"; then
+	libdir=LIBDIR
+	eval flag=\"$hardcode_libdir_flag_spec\"
+
+	$ECHO "   - use the \`$flag' linker flag"
+      fi
+      if test -n "$admincmds"; then
+	$ECHO "   - have your system administrator run these commands:$admincmds"
+      fi
+      if test -f /etc/ld.so.conf; then
+	echo "   - have your system administrator add LIBDIR to \`/etc/ld.so.conf'"
+      fi
+      echo
+
+      echo "See any operating system documentation about shared libraries for"
+      case $host in
+	solaris2.[6789]|solaris2.1[0-9])
+	  echo "more information, such as the ld(1), crle(1) and ld.so(8) manual"
+	  echo "pages."
+	  ;;
+	*)
+	  echo "more information, such as the ld(1) and ld.so(8) manual pages."
+	  ;;
+      esac
+      echo "----------------------------------------------------------------------"
+    fi
+    exit $EXIT_SUCCESS
+}
+
+test "$opt_mode" = finish && func_mode_finish ${1+"$@"}
+
+
+# func_mode_install arg...
+func_mode_install ()
+{
+    $opt_debug
+    # There may be an optional sh(1) argument at the beginning of
+    # install_prog (especially on Windows NT).
+    if test "$nonopt" = "$SHELL" || test "$nonopt" = /bin/sh ||
+       # Allow the use of GNU shtool's install command.
+       case $nonopt in *shtool*) :;; *) false;; esac; then
+      # Aesthetically quote it.
+      func_quote_for_eval "$nonopt"
+      install_prog="$func_quote_for_eval_result "
+      arg=$1
+      shift
+    else
+      install_prog=
+      arg=$nonopt
+    fi
+
+    # The real first argument should be the name of the installation program.
+    # Aesthetically quote it.
+    func_quote_for_eval "$arg"
+    func_append install_prog "$func_quote_for_eval_result"
+    install_shared_prog=$install_prog
+    case " $install_prog " in
+      *[\\\ /]cp\ *) install_cp=: ;;
+      *) install_cp=false ;;
+    esac
+
+    # We need to accept at least all the BSD install flags.
+    dest=
+    files=
+    opts=
+    prev=
+    install_type=
+    isdir=no
+    stripme=
+    no_mode=:
+    for arg
+    do
+      arg2=
+      if test -n "$dest"; then
+	func_append files " $dest"
+	dest=$arg
+	continue
+      fi
+
+      case $arg in
+      -d) isdir=yes ;;
+      -f)
+	if $install_cp; then :; else
+	  prev=$arg
+	fi
+	;;
+      -g | -m | -o)
+	prev=$arg
+	;;
+      -s)
+	stripme=" -s"
+	continue
+	;;
+      -*)
+	;;
+      *)
+	# If the previous option needed an argument, then skip it.
+	if test -n "$prev"; then
+	  if test "x$prev" = x-m && test -n "$install_override_mode"; then
+	    arg2=$install_override_mode
+	    no_mode=false
+	  fi
+	  prev=
+	else
+	  dest=$arg
+	  continue
+	fi
+	;;
+      esac
+
+      # Aesthetically quote the argument.
+      func_quote_for_eval "$arg"
+      func_append install_prog " $func_quote_for_eval_result"
+      if test -n "$arg2"; then
+	func_quote_for_eval "$arg2"
+      fi
+      func_append install_shared_prog " $func_quote_for_eval_result"
+    done
+
+    test -z "$install_prog" && \
+      func_fatal_help "you must specify an install program"
+
+    test -n "$prev" && \
+      func_fatal_help "the \`$prev' option requires an argument"
+
+    if test -n "$install_override_mode" && $no_mode; then
+      if $install_cp; then :; else
+	func_quote_for_eval "$install_override_mode"
+	func_append install_shared_prog " -m $func_quote_for_eval_result"
+      fi
+    fi
+
+    if test -z "$files"; then
+      if test -z "$dest"; then
+	func_fatal_help "no file or destination specified"
+      else
+	func_fatal_help "you must specify a destination"
+      fi
+    fi
+
+    # Strip any trailing slash from the destination.
+    func_stripname '' '/' "$dest"
+    dest=$func_stripname_result
+
+    # Check to see that the destination is a directory.
+    test -d "$dest" && isdir=yes
+    if test "$isdir" = yes; then
+      destdir="$dest"
+      destname=
+    else
+      func_dirname_and_basename "$dest" "" "."
+      destdir="$func_dirname_result"
+      destname="$func_basename_result"
+
+      # Not a directory, so check to see that there is only one file specified.
+      set dummy $files; shift
+      test "$#" -gt 1 && \
+	func_fatal_help "\`$dest' is not a directory"
+    fi
+    case $destdir in
+    [\\/]* | [A-Za-z]:[\\/]*) ;;
+    *)
+      for file in $files; do
+	case $file in
+	*.lo) ;;
+	*)
+	  func_fatal_help "\`$destdir' must be an absolute directory name"
+	  ;;
+	esac
+      done
+      ;;
+    esac
+
+    # This variable tells wrapper scripts just to set variables rather
+    # than running their programs.
+    libtool_install_magic="$magic"
+
+    staticlibs=
+    future_libdirs=
+    current_libdirs=
+    for file in $files; do
+
+      # Do each installation.
+      case $file in
+      *.$libext)
+	# Do the static libraries later.
+	func_append staticlibs " $file"
+	;;
+
+      *.la)
+	func_resolve_sysroot "$file"
+	file=$func_resolve_sysroot_result
+
+	# Check to see that this really is a libtool archive.
+	func_lalib_unsafe_p "$file" \
+	  || func_fatal_help "\`$file' is not a valid libtool archive"
+
+	library_names=
+	old_library=
+	relink_command=
+	func_source "$file"
+
+	# Add the libdir to current_libdirs if it is the destination.
+	if test "X$destdir" = "X$libdir"; then
+	  case "$current_libdirs " in
+	  *" $libdir "*) ;;
+	  *) func_append current_libdirs " $libdir" ;;
+	  esac
+	else
+	  # Note the libdir as a future libdir.
+	  case "$future_libdirs " in
+	  *" $libdir "*) ;;
+	  *) func_append future_libdirs " $libdir" ;;
+	  esac
+	fi
+
+	func_dirname "$file" "/" ""
+	dir="$func_dirname_result"
+	func_append dir "$objdir"
+
+	if test -n "$relink_command"; then
+	  # Determine the prefix the user has applied to our future dir.
+	  inst_prefix_dir=`$ECHO "$destdir" | $SED -e "s%$libdir\$%%"`
+
+	  # Don't allow the user to place us outside of our expected
+	  # location b/c this prevents finding dependent libraries that
+	  # are installed to the same prefix.
+	  # At present, this check doesn't affect windows .dll's that
+	  # are installed into $libdir/../bin (currently, that works fine)
+	  # but it's something to keep an eye on.
+	  test "$inst_prefix_dir" = "$destdir" && \
+	    func_fatal_error "error: cannot install \`$file' to a directory not ending in $libdir"
+
+	  if test -n "$inst_prefix_dir"; then
+	    # Stick the inst_prefix_dir data into the link command.
+	    relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%-inst-prefix-dir $inst_prefix_dir%"`
+	  else
+	    relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%%"`
+	  fi
+
+	  func_warning "relinking \`$file'"
+	  func_show_eval "$relink_command" \
+	    'func_fatal_error "error: relink \`$file'\'' with the above command before installing it"'
+	fi
+
+	# See the names of the shared library.
+	set dummy $library_names; shift
+	if test -n "$1"; then
+	  realname="$1"
+	  shift
+
+	  srcname="$realname"
+	  test -n "$relink_command" && srcname="$realname"T
+
+	  # Install the shared library and build the symlinks.
+	  func_show_eval "$install_shared_prog $dir/$srcname $destdir/$realname" \
+	      'exit $?'
+	  tstripme="$stripme"
+	  case $host_os in
+	  cygwin* | mingw* | pw32* | cegcc*)
+	    case $realname in
+	    *.dll.a)
+	      tstripme=""
+	      ;;
+	    esac
+	    ;;
+	  esac
+	  if test -n "$tstripme" && test -n "$striplib"; then
+	    func_show_eval "$striplib $destdir/$realname" 'exit $?'
+	  fi
+
+	  if test "$#" -gt 0; then
+	    # Delete the old symlinks, and create new ones.
+	    # Try `ln -sf' first, because the `ln' binary might depend on
+	    # the symlink we replace!  Solaris /bin/ln does not understand -f,
+	    # so we also need to try rm && ln -s.
+	    for linkname
+	    do
+	      test "$linkname" != "$realname" \
+		&& func_show_eval "(cd $destdir && { $LN_S -f $realname $linkname || { $RM $linkname && $LN_S $realname $linkname; }; })"
+	    done
+	  fi
+
+	  # Do each command in the postinstall commands.
+	  lib="$destdir/$realname"
+	  func_execute_cmds "$postinstall_cmds" 'exit $?'
+	fi
+
+	# Install the pseudo-library for information purposes.
+	func_basename "$file"
+	name="$func_basename_result"
+	instname="$dir/$name"i
+	func_show_eval "$install_prog $instname $destdir/$name" 'exit $?'
+
+	# Maybe install the static library, too.
+	test -n "$old_library" && func_append staticlibs " $dir/$old_library"
+	;;
+
+      *.lo)
+	# Install (i.e. copy) a libtool object.
+
+	# Figure out destination file name, if it wasn't already specified.
+	if test -n "$destname"; then
+	  destfile="$destdir/$destname"
+	else
+	  func_basename "$file"
+	  destfile="$func_basename_result"
+	  destfile="$destdir/$destfile"
+	fi
+
+	# Deduce the name of the destination old-style object file.
+	case $destfile in
+	*.lo)
+	  func_lo2o "$destfile"
+	  staticdest=$func_lo2o_result
+	  ;;
+	*.$objext)
+	  staticdest="$destfile"
+	  destfile=
+	  ;;
+	*)
+	  func_fatal_help "cannot copy a libtool object to \`$destfile'"
+	  ;;
+	esac
+
+	# Install the libtool object if requested.
+	test -n "$destfile" && \
+	  func_show_eval "$install_prog $file $destfile" 'exit $?'
+
+	# Install the old object if enabled.
+	if test "$build_old_libs" = yes; then
+	  # Deduce the name of the old-style object file.
+	  func_lo2o "$file"
+	  staticobj=$func_lo2o_result
+	  func_show_eval "$install_prog \$staticobj \$staticdest" 'exit $?'
+	fi
+	exit $EXIT_SUCCESS
+	;;
+
+      *)
+	# Figure out destination file name, if it wasn't already specified.
+	if test -n "$destname"; then
+	  destfile="$destdir/$destname"
+	else
+	  func_basename "$file"
+	  destfile="$func_basename_result"
+	  destfile="$destdir/$destfile"
+	fi
+
+	# If the file is missing, and there is a .exe on the end, strip it
+	# because it is most likely a libtool script we actually want to
+	# install
+	stripped_ext=""
+	case $file in
+	  *.exe)
+	    if test ! -f "$file"; then
+	      func_stripname '' '.exe' "$file"
+	      file=$func_stripname_result
+	      stripped_ext=".exe"
+	    fi
+	    ;;
+	esac
+
+	# Do a test to see if this is really a libtool program.
+	case $host in
+	*cygwin* | *mingw*)
+	    if func_ltwrapper_executable_p "$file"; then
+	      func_ltwrapper_scriptname "$file"
+	      wrapper=$func_ltwrapper_scriptname_result
+	    else
+	      func_stripname '' '.exe' "$file"
+	      wrapper=$func_stripname_result
+	    fi
+	    ;;
+	*)
+	    wrapper=$file
+	    ;;
+	esac
+	if func_ltwrapper_script_p "$wrapper"; then
+	  notinst_deplibs=
+	  relink_command=
+
+	  func_source "$wrapper"
+
+	  # Check the variables that should have been set.
+	  test -z "$generated_by_libtool_version" && \
+	    func_fatal_error "invalid libtool wrapper script \`$wrapper'"
+
+	  finalize=yes
+	  for lib in $notinst_deplibs; do
+	    # Check to see that each library is installed.
+	    libdir=
+	    if test -f "$lib"; then
+	      func_source "$lib"
+	    fi
+	    libfile="$libdir/"`$ECHO "$lib" | $SED 's%^.*/%%g'` ### testsuite: skip nested quoting test
+	    if test -n "$libdir" && test ! -f "$libfile"; then
+	      func_warning "\`$lib' has not been installed in \`$libdir'"
+	      finalize=no
+	    fi
+	  done
+
+	  relink_command=
+	  func_source "$wrapper"
+
+	  outputname=
+	  if test "$fast_install" = no && test -n "$relink_command"; then
+	    $opt_dry_run || {
+	      if test "$finalize" = yes; then
+	        tmpdir=`func_mktempdir`
+		func_basename "$file$stripped_ext"
+		file="$func_basename_result"
+	        outputname="$tmpdir/$file"
+	        # Replace the output file specification.
+	        relink_command=`$ECHO "$relink_command" | $SED 's%@OUTPUT@%'"$outputname"'%g'`
+
+	        $opt_silent || {
+	          func_quote_for_expand "$relink_command"
+		  eval "func_echo $func_quote_for_expand_result"
+	        }
+	        if eval "$relink_command"; then :
+	          else
+		  func_error "error: relink \`$file' with the above command before installing it"
+		  $opt_dry_run || ${RM}r "$tmpdir"
+		  continue
+	        fi
+	        file="$outputname"
+	      else
+	        func_warning "cannot relink \`$file'"
+	      fi
+	    }
+	  else
+	    # Install the binary that we compiled earlier.
+	    file=`$ECHO "$file$stripped_ext" | $SED "s%\([^/]*\)$%$objdir/\1%"`
+	  fi
+	fi
+
+	# remove .exe since cygwin /usr/bin/install will append another
+	# one anyway
+	case $install_prog,$host in
+	*/usr/bin/install*,*cygwin*)
+	  case $file:$destfile in
+	  *.exe:*.exe)
+	    # this is ok
+	    ;;
+	  *.exe:*)
+	    destfile=$destfile.exe
+	    ;;
+	  *:*.exe)
+	    func_stripname '' '.exe' "$destfile"
+	    destfile=$func_stripname_result
+	    ;;
+	  esac
+	  ;;
+	esac
+	func_show_eval "$install_prog\$stripme \$file \$destfile" 'exit $?'
+	$opt_dry_run || if test -n "$outputname"; then
+	  ${RM}r "$tmpdir"
+	fi
+	;;
+      esac
+    done
+
+    for file in $staticlibs; do
+      func_basename "$file"
+      name="$func_basename_result"
+
+      # Set up the ranlib parameters.
+      oldlib="$destdir/$name"
+      func_to_tool_file "$oldlib" func_convert_file_msys_to_w32
+      tool_oldlib=$func_to_tool_file_result
+
+      func_show_eval "$install_prog \$file \$oldlib" 'exit $?'
+
+      if test -n "$stripme" && test -n "$old_striplib"; then
+	func_show_eval "$old_striplib $tool_oldlib" 'exit $?'
+      fi
+
+      # Do each command in the postinstall commands.
+      func_execute_cmds "$old_postinstall_cmds" 'exit $?'
+    done
+
+    test -n "$future_libdirs" && \
+      func_warning "remember to run \`$progname --finish$future_libdirs'"
+
+    if test -n "$current_libdirs"; then
+      # Maybe just do a dry run.
+      $opt_dry_run && current_libdirs=" -n$current_libdirs"
+      exec_cmd='$SHELL $progpath $preserve_args --finish$current_libdirs'
+    else
+      exit $EXIT_SUCCESS
+    fi
+}
+
+test "$opt_mode" = install && func_mode_install ${1+"$@"}
+
+
+# func_generate_dlsyms outputname originator pic_p
+# Extract symbols from dlprefiles and create ${outputname}S.o with
+# a dlpreopen symbol table.
+func_generate_dlsyms ()
+{
+    $opt_debug
+    my_outputname="$1"
+    my_originator="$2"
+    my_pic_p="${3-no}"
+    my_prefix=`$ECHO "$my_originator" | sed 's%[^a-zA-Z0-9]%_%g'`
+    my_dlsyms=
+
+    if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
+      if test -n "$NM" && test -n "$global_symbol_pipe"; then
+	my_dlsyms="${my_outputname}S.c"
+      else
+	func_error "not configured to extract global symbols from dlpreopened files"
+      fi
+    fi
+
+    if test -n "$my_dlsyms"; then
+      case $my_dlsyms in
+      "") ;;
+      *.c)
+	# Discover the nlist of each of the dlfiles.
+	nlist="$output_objdir/${my_outputname}.nm"
+
+	func_show_eval "$RM $nlist ${nlist}S ${nlist}T"
+
+	# Parse the name list into a source file.
+	func_verbose "creating $output_objdir/$my_dlsyms"
+
+	$opt_dry_run || $ECHO > "$output_objdir/$my_dlsyms" "\
+/* $my_dlsyms - symbol resolution table for \`$my_outputname' dlsym emulation. */
+/* Generated by $PROGRAM (GNU $PACKAGE$TIMESTAMP) $VERSION */
+
+#ifdef __cplusplus
+extern \"C\" {
+#endif
+
+#if defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4)) || (__GNUC__ > 4))
+#pragma GCC diagnostic ignored \"-Wstrict-prototypes\"
+#endif
+
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests.  */
+#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE)
+/* DATA imports from DLLs on WIN32 con't be const, because runtime
+   relocations are performed -- see ld's documentation on pseudo-relocs.  */
+# define LT_DLSYM_CONST
+#elif defined(__osf__)
+/* This system does not cope well with relocations in const data.  */
+# define LT_DLSYM_CONST
+#else
+# define LT_DLSYM_CONST const
+#endif
+
+/* External symbol declarations for the compiler. */\
+"
+
+	if test "$dlself" = yes; then
+	  func_verbose "generating symbol list for \`$output'"
+
+	  $opt_dry_run || echo ': @PROGRAM@ ' > "$nlist"
+
+	  # Add our own program objects to the symbol list.
+	  progfiles=`$ECHO "$objs$old_deplibs" | $SP2NL | $SED "$lo2o" | $NL2SP`
+	  for progfile in $progfiles; do
+	    func_to_tool_file "$progfile" func_convert_file_msys_to_w32
+	    func_verbose "extracting global C symbols from \`$func_to_tool_file_result'"
+	    $opt_dry_run || eval "$NM $func_to_tool_file_result | $global_symbol_pipe >> '$nlist'"
+	  done
+
+	  if test -n "$exclude_expsyms"; then
+	    $opt_dry_run || {
+	      eval '$EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T'
+	      eval '$MV "$nlist"T "$nlist"'
+	    }
+	  fi
+
+	  if test -n "$export_symbols_regex"; then
+	    $opt_dry_run || {
+	      eval '$EGREP -e "$export_symbols_regex" "$nlist" > "$nlist"T'
+	      eval '$MV "$nlist"T "$nlist"'
+	    }
+	  fi
+
+	  # Prepare the list of exported symbols
+	  if test -z "$export_symbols"; then
+	    export_symbols="$output_objdir/$outputname.exp"
+	    $opt_dry_run || {
+	      $RM $export_symbols
+	      eval "${SED} -n -e '/^: @PROGRAM@ $/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"'
+	      case $host in
+	      *cygwin* | *mingw* | *cegcc* )
+                eval "echo EXPORTS "'> "$output_objdir/$outputname.def"'
+                eval 'cat "$export_symbols" >> "$output_objdir/$outputname.def"'
+	        ;;
+	      esac
+	    }
+	  else
+	    $opt_dry_run || {
+	      eval "${SED} -e 's/\([].[*^$]\)/\\\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$outputname.exp"'
+	      eval '$GREP -f "$output_objdir/$outputname.exp" < "$nlist" > "$nlist"T'
+	      eval '$MV "$nlist"T "$nlist"'
+	      case $host in
+	        *cygwin* | *mingw* | *cegcc* )
+	          eval "echo EXPORTS "'> "$output_objdir/$outputname.def"'
+	          eval 'cat "$nlist" >> "$output_objdir/$outputname.def"'
+	          ;;
+	      esac
+	    }
+	  fi
+	fi
+
+	for dlprefile in $dlprefiles; do
+	  func_verbose "extracting global C symbols from \`$dlprefile'"
+	  func_basename "$dlprefile"
+	  name="$func_basename_result"
+          case $host in
+	    *cygwin* | *mingw* | *cegcc* )
+	      # if an import library, we need to obtain dlname
+	      if func_win32_import_lib_p "$dlprefile"; then
+	        func_tr_sh "$dlprefile"
+	        eval "curr_lafile=\$libfile_$func_tr_sh_result"
+	        dlprefile_dlbasename=""
+	        if test -n "$curr_lafile" && func_lalib_p "$curr_lafile"; then
+	          # Use subshell, to avoid clobbering current variable values
+	          dlprefile_dlname=`source "$curr_lafile" && echo "$dlname"`
+	          if test -n "$dlprefile_dlname" ; then
+	            func_basename "$dlprefile_dlname"
+	            dlprefile_dlbasename="$func_basename_result"
+	          else
+	            # no lafile. user explicitly requested -dlpreopen <import library>.
+	            $sharedlib_from_linklib_cmd "$dlprefile"
+	            dlprefile_dlbasename=$sharedlib_from_linklib_result
+	          fi
+	        fi
+	        $opt_dry_run || {
+	          if test -n "$dlprefile_dlbasename" ; then
+	            eval '$ECHO ": $dlprefile_dlbasename" >> "$nlist"'
+	          else
+	            func_warning "Could not compute DLL name from $name"
+	            eval '$ECHO ": $name " >> "$nlist"'
+	          fi
+	          func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32
+	          eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe |
+	            $SED -e '/I __imp/d' -e 's/I __nm_/D /;s/_nm__//' >> '$nlist'"
+	        }
+	      else # not an import lib
+	        $opt_dry_run || {
+	          eval '$ECHO ": $name " >> "$nlist"'
+	          func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32
+	          eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'"
+	        }
+	      fi
+	    ;;
+	    *)
+	      $opt_dry_run || {
+	        eval '$ECHO ": $name " >> "$nlist"'
+	        func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32
+	        eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'"
+	      }
+	    ;;
+          esac
+	done
+
+	$opt_dry_run || {
+	  # Make sure we have at least an empty file.
+	  test -f "$nlist" || : > "$nlist"
+
+	  if test -n "$exclude_expsyms"; then
+	    $EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T
+	    $MV "$nlist"T "$nlist"
+	  fi
+
+	  # Try sorting and uniquifying the output.
+	  if $GREP -v "^: " < "$nlist" |
+	      if sort -k 3 </dev/null >/dev/null 2>&1; then
+		sort -k 3
+	      else
+		sort +2
+	      fi |
+	      uniq > "$nlist"S; then
+	    :
+	  else
+	    $GREP -v "^: " < "$nlist" > "$nlist"S
+	  fi
+
+	  if test -f "$nlist"S; then
+	    eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$my_dlsyms"'
+	  else
+	    echo '/* NONE */' >> "$output_objdir/$my_dlsyms"
+	  fi
+
+	  echo >> "$output_objdir/$my_dlsyms" "\
+
+/* The mapping between symbol names and symbols.  */
+typedef struct {
+  const char *name;
+  void *address;
+} lt_dlsymlist;
+extern LT_DLSYM_CONST lt_dlsymlist
+lt_${my_prefix}_LTX_preloaded_symbols[];
+LT_DLSYM_CONST lt_dlsymlist
+lt_${my_prefix}_LTX_preloaded_symbols[] =
+{\
+  { \"$my_originator\", (void *) 0 },"
+
+	  case $need_lib_prefix in
+	  no)
+	    eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$my_dlsyms"
+	    ;;
+	  *)
+	    eval "$global_symbol_to_c_name_address_lib_prefix" < "$nlist" >> "$output_objdir/$my_dlsyms"
+	    ;;
+	  esac
+	  echo >> "$output_objdir/$my_dlsyms" "\
+  {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+  return lt_${my_prefix}_LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif\
+"
+	} # !$opt_dry_run
+
+	pic_flag_for_symtable=
+	case "$compile_command " in
+	*" -static "*) ;;
+	*)
+	  case $host in
+	  # compiling the symbol table file with pic_flag works around
+	  # a FreeBSD bug that causes programs to crash when -lm is
+	  # linked before any other PIC object.  But we must not use
+	  # pic_flag when linking with -static.  The problem exists in
+	  # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1.
+	  *-*-freebsd2.*|*-*-freebsd3.0*|*-*-freebsdelf3.0*)
+	    pic_flag_for_symtable=" $pic_flag -DFREEBSD_WORKAROUND" ;;
+	  *-*-hpux*)
+	    pic_flag_for_symtable=" $pic_flag"  ;;
+	  *)
+	    if test "X$my_pic_p" != Xno; then
+	      pic_flag_for_symtable=" $pic_flag"
+	    fi
+	    ;;
+	  esac
+	  ;;
+	esac
+	symtab_cflags=
+	for arg in $LTCFLAGS; do
+	  case $arg in
+	  -pie | -fpie | -fPIE) ;;
+	  *) func_append symtab_cflags " $arg" ;;
+	  esac
+	done
+
+	# Now compile the dynamic symbol file.
+	func_show_eval '(cd $output_objdir && $LTCC$symtab_cflags -c$no_builtin_flag$pic_flag_for_symtable "$my_dlsyms")' 'exit $?'
+
+	# Clean up the generated files.
+	func_show_eval '$RM "$output_objdir/$my_dlsyms" "$nlist" "${nlist}S" "${nlist}T"'
+
+	# Transform the symbol file into the correct name.
+	symfileobj="$output_objdir/${my_outputname}S.$objext"
+	case $host in
+	*cygwin* | *mingw* | *cegcc* )
+	  if test -f "$output_objdir/$my_outputname.def"; then
+	    compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"`
+	    finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"`
+	  else
+	    compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+	    finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+	  fi
+	  ;;
+	*)
+	  compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+	  finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+	  ;;
+	esac
+	;;
+      *)
+	func_fatal_error "unknown suffix for \`$my_dlsyms'"
+	;;
+      esac
+    else
+      # We keep going just in case the user didn't refer to
+      # lt_preloaded_symbols.  The linker will fail if global_symbol_pipe
+      # really was required.
+
+      # Nullify the symbol file.
+      compile_command=`$ECHO "$compile_command" | $SED "s% @SYMFILE@%%"`
+      finalize_command=`$ECHO "$finalize_command" | $SED "s% @SYMFILE@%%"`
+    fi
+}
+
+# func_win32_libid arg
+# return the library type of file 'arg'
+#
+# Need a lot of goo to handle *both* DLLs and import libs
+# Has to be a shell function in order to 'eat' the argument
+# that is supplied when $file_magic_command is called.
+# Despite the name, also deal with 64 bit binaries.
+func_win32_libid ()
+{
+  $opt_debug
+  win32_libid_type="unknown"
+  win32_fileres=`file -L $1 2>/dev/null`
+  case $win32_fileres in
+  *ar\ archive\ import\ library*) # definitely import
+    win32_libid_type="x86 archive import"
+    ;;
+  *ar\ archive*) # could be an import, or static
+    # Keep the egrep pattern in sync with the one in _LT_CHECK_MAGIC_METHOD.
+    if eval $OBJDUMP -f $1 | $SED -e '10q' 2>/dev/null |
+       $EGREP 'file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' >/dev/null; then
+      func_to_tool_file "$1" func_convert_file_msys_to_w32
+      win32_nmres=`eval $NM -f posix -A \"$func_to_tool_file_result\" |
+	$SED -n -e '
+	    1,100{
+		/ I /{
+		    s,.*,import,
+		    p
+		    q
+		}
+	    }'`
+      case $win32_nmres in
+      import*)  win32_libid_type="x86 archive import";;
+      *)        win32_libid_type="x86 archive static";;
+      esac
+    fi
+    ;;
+  *DLL*)
+    win32_libid_type="x86 DLL"
+    ;;
+  *executable*) # but shell scripts are "executable" too...
+    case $win32_fileres in
+    *MS\ Windows\ PE\ Intel*)
+      win32_libid_type="x86 DLL"
+      ;;
+    esac
+    ;;
+  esac
+  $ECHO "$win32_libid_type"
+}
+
+# func_cygming_dll_for_implib ARG
+#
+# Platform-specific function to extract the
+# name of the DLL associated with the specified
+# import library ARG.
+# Invoked by eval'ing the libtool variable
+#    $sharedlib_from_linklib_cmd
+# Result is available in the variable
+#    $sharedlib_from_linklib_result
+func_cygming_dll_for_implib ()
+{
+  $opt_debug
+  sharedlib_from_linklib_result=`$DLLTOOL --identify-strict --identify "$1"`
+}
+
+# func_cygming_dll_for_implib_fallback_core SECTION_NAME LIBNAMEs
+#
+# The is the core of a fallback implementation of a
+# platform-specific function to extract the name of the
+# DLL associated with the specified import library LIBNAME.
+#
+# SECTION_NAME is either .idata$6 or .idata$7, depending
+# on the platform and compiler that created the implib.
+#
+# Echos the name of the DLL associated with the
+# specified import library.
+func_cygming_dll_for_implib_fallback_core ()
+{
+  $opt_debug
+  match_literal=`$ECHO "$1" | $SED "$sed_make_literal_regex"`
+  $OBJDUMP -s --section "$1" "$2" 2>/dev/null |
+    $SED '/^Contents of section '"$match_literal"':/{
+      # Place marker at beginning of archive member dllname section
+      s/.*/====MARK====/
+      p
+      d
+    }
+    # These lines can sometimes be longer than 43 characters, but
+    # are always uninteresting
+    /:[	 ]*file format pe[i]\{,1\}-/d
+    /^In archive [^:]*:/d
+    # Ensure marker is printed
+    /^====MARK====/p
+    # Remove all lines with less than 43 characters
+    /^.\{43\}/!d
+    # From remaining lines, remove first 43 characters
+    s/^.\{43\}//' |
+    $SED -n '
+      # Join marker and all lines until next marker into a single line
+      /^====MARK====/ b para
+      H
+      $ b para
+      b
+      :para
+      x
+      s/\n//g
+      # Remove the marker
+      s/^====MARK====//
+      # Remove trailing dots and whitespace
+      s/[\. \t]*$//
+      # Print
+      /./p' |
+    # we now have a list, one entry per line, of the stringified
+    # contents of the appropriate section of all members of the
+    # archive which possess that section. Heuristic: eliminate
+    # all those which have a first or second character that is
+    # a '.' (that is, objdump's representation of an unprintable
+    # character.) This should work for all archives with less than
+    # 0x302f exports -- but will fail for DLLs whose name actually
+    # begins with a literal '.' or a single character followed by
+    # a '.'.
+    #
+    # Of those that remain, print the first one.
+    $SED -e '/^\./d;/^.\./d;q'
+}
+
+# func_cygming_gnu_implib_p ARG
+# This predicate returns with zero status (TRUE) if
+# ARG is a GNU/binutils-style import library. Returns
+# with nonzero status (FALSE) otherwise.
+func_cygming_gnu_implib_p ()
+{
+  $opt_debug
+  func_to_tool_file "$1" func_convert_file_msys_to_w32
+  func_cygming_gnu_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $EGREP ' (_head_[A-Za-z0-9_]+_[ad]l*|[A-Za-z0-9_]+_[ad]l*_iname)$'`
+  test -n "$func_cygming_gnu_implib_tmp"
+}
+
+# func_cygming_ms_implib_p ARG
+# This predicate returns with zero status (TRUE) if
+# ARG is an MS-style import library. Returns
+# with nonzero status (FALSE) otherwise.
+func_cygming_ms_implib_p ()
+{
+  $opt_debug
+  func_to_tool_file "$1" func_convert_file_msys_to_w32
+  func_cygming_ms_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $GREP '_NULL_IMPORT_DESCRIPTOR'`
+  test -n "$func_cygming_ms_implib_tmp"
+}
+
+# func_cygming_dll_for_implib_fallback ARG
+# Platform-specific function to extract the
+# name of the DLL associated with the specified
+# import library ARG.
+#
+# This fallback implementation is for use when $DLLTOOL
+# does not support the --identify-strict option.
+# Invoked by eval'ing the libtool variable
+#    $sharedlib_from_linklib_cmd
+# Result is available in the variable
+#    $sharedlib_from_linklib_result
+func_cygming_dll_for_implib_fallback ()
+{
+  $opt_debug
+  if func_cygming_gnu_implib_p "$1" ; then
+    # binutils import library
+    sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$7' "$1"`
+  elif func_cygming_ms_implib_p "$1" ; then
+    # ms-generated import library
+    sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$6' "$1"`
+  else
+    # unknown
+    sharedlib_from_linklib_result=""
+  fi
+}
+
+
+# func_extract_an_archive dir oldlib
+func_extract_an_archive ()
+{
+    $opt_debug
+    f_ex_an_ar_dir="$1"; shift
+    f_ex_an_ar_oldlib="$1"
+    if test "$lock_old_archive_extraction" = yes; then
+      lockfile=$f_ex_an_ar_oldlib.lock
+      until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do
+	func_echo "Waiting for $lockfile to be removed"
+	sleep 2
+      done
+    fi
+    func_show_eval "(cd \$f_ex_an_ar_dir && $AR x \"\$f_ex_an_ar_oldlib\")" \
+		   'stat=$?; rm -f "$lockfile"; exit $stat'
+    if test "$lock_old_archive_extraction" = yes; then
+      $opt_dry_run || rm -f "$lockfile"
+    fi
+    if ($AR t "$f_ex_an_ar_oldlib" | sort | sort -uc >/dev/null 2>&1); then
+     :
+    else
+      func_fatal_error "object name conflicts in archive: $f_ex_an_ar_dir/$f_ex_an_ar_oldlib"
+    fi
+}
+
+
+# func_extract_archives gentop oldlib ...
+func_extract_archives ()
+{
+    $opt_debug
+    my_gentop="$1"; shift
+    my_oldlibs=${1+"$@"}
+    my_oldobjs=""
+    my_xlib=""
+    my_xabs=""
+    my_xdir=""
+
+    for my_xlib in $my_oldlibs; do
+      # Extract the objects.
+      case $my_xlib in
+	[\\/]* | [A-Za-z]:[\\/]*) my_xabs="$my_xlib" ;;
+	*) my_xabs=`pwd`"/$my_xlib" ;;
+      esac
+      func_basename "$my_xlib"
+      my_xlib="$func_basename_result"
+      my_xlib_u=$my_xlib
+      while :; do
+        case " $extracted_archives " in
+	*" $my_xlib_u "*)
+	  func_arith $extracted_serial + 1
+	  extracted_serial=$func_arith_result
+	  my_xlib_u=lt$extracted_serial-$my_xlib ;;
+	*) break ;;
+	esac
+      done
+      extracted_archives="$extracted_archives $my_xlib_u"
+      my_xdir="$my_gentop/$my_xlib_u"
+
+      func_mkdir_p "$my_xdir"
+
+      case $host in
+      *-darwin*)
+	func_verbose "Extracting $my_xabs"
+	# Do not bother doing anything if just a dry run
+	$opt_dry_run || {
+	  darwin_orig_dir=`pwd`
+	  cd $my_xdir || exit $?
+	  darwin_archive=$my_xabs
+	  darwin_curdir=`pwd`
+	  darwin_base_archive=`basename "$darwin_archive"`
+	  darwin_arches=`$LIPO -info "$darwin_archive" 2>/dev/null | $GREP Architectures 2>/dev/null || true`
+	  if test -n "$darwin_arches"; then
+	    darwin_arches=`$ECHO "$darwin_arches" | $SED -e 's/.*are://'`
+	    darwin_arch=
+	    func_verbose "$darwin_base_archive has multiple architectures $darwin_arches"
+	    for darwin_arch in  $darwin_arches ; do
+	      func_mkdir_p "unfat-$$/${darwin_base_archive}-${darwin_arch}"
+	      $LIPO -thin $darwin_arch -output "unfat-$$/${darwin_base_archive}-${darwin_arch}/${darwin_base_archive}" "${darwin_archive}"
+	      cd "unfat-$$/${darwin_base_archive}-${darwin_arch}"
+	      func_extract_an_archive "`pwd`" "${darwin_base_archive}"
+	      cd "$darwin_curdir"
+	      $RM "unfat-$$/${darwin_base_archive}-${darwin_arch}/${darwin_base_archive}"
+	    done # $darwin_arches
+            ## Okay now we've a bunch of thin objects, gotta fatten them up :)
+	    darwin_filelist=`find unfat-$$ -type f -name \*.o -print -o -name \*.lo -print | $SED -e "$basename" | sort -u`
+	    darwin_file=
+	    darwin_files=
+	    for darwin_file in $darwin_filelist; do
+	      darwin_files=`find unfat-$$ -name $darwin_file -print | sort | $NL2SP`
+	      $LIPO -create -output "$darwin_file" $darwin_files
+	    done # $darwin_filelist
+	    $RM -rf unfat-$$
+	    cd "$darwin_orig_dir"
+	  else
+	    cd $darwin_orig_dir
+	    func_extract_an_archive "$my_xdir" "$my_xabs"
+	  fi # $darwin_arches
+	} # !$opt_dry_run
+	;;
+      *)
+        func_extract_an_archive "$my_xdir" "$my_xabs"
+	;;
+      esac
+      my_oldobjs="$my_oldobjs "`find $my_xdir -name \*.$objext -print -o -name \*.lo -print | sort | $NL2SP`
+    done
+
+    func_extract_archives_result="$my_oldobjs"
+}
+
+
+# func_emit_wrapper [arg=no]
+#
+# Emit a libtool wrapper script on stdout.
+# Don't directly open a file because we may want to
+# incorporate the script contents within a cygwin/mingw
+# wrapper executable.  Must ONLY be called from within
+# func_mode_link because it depends on a number of variables
+# set therein.
+#
+# ARG is the value that the WRAPPER_SCRIPT_BELONGS_IN_OBJDIR
+# variable will take.  If 'yes', then the emitted script
+# will assume that the directory in which it is stored is
+# the $objdir directory.  This is a cygwin/mingw-specific
+# behavior.
+func_emit_wrapper ()
+{
+	func_emit_wrapper_arg1=${1-no}
+
+	$ECHO "\
+#! $SHELL
+
+# $output - temporary wrapper script for $objdir/$outputname
+# Generated by $PROGRAM (GNU $PACKAGE$TIMESTAMP) $VERSION
+#
+# The $output program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting.  It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='$sed_quote_subst'
+
+# Be Bourne compatible
+if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on \${1+\"\$@\"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '\${1+\"\$@\"}'='\"\$@\"'
+  setopt NO_GLOB_SUBST
+else
+  case \`(set -o) 2>/dev/null\` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=\"$relink_command\"
+
+# This environment variable determines our operation mode.
+if test \"\$libtool_install_magic\" = \"$magic\"; then
+  # install mode needs the following variables:
+  generated_by_libtool_version='$macro_version'
+  notinst_deplibs='$notinst_deplibs'
+else
+  # When we are sourced in execute mode, \$file and \$ECHO are already set.
+  if test \"\$libtool_execute_magic\" != \"$magic\"; then
+    file=\"\$0\""
+
+    qECHO=`$ECHO "$ECHO" | $SED "$sed_quote_subst"`
+    $ECHO "\
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+  eval 'cat <<_LTECHO_EOF
+\$1
+_LTECHO_EOF'
+}
+    ECHO=\"$qECHO\"
+  fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ which is used only on
+# windows platforms, and (c) all begin with the string "--lt-"
+# (application programs are unlikely to have options which match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's $0 value, followed by "$@".
+lt_option_debug=
+func_parse_lt_options ()
+{
+  lt_script_arg0=\$0
+  shift
+  for lt_opt
+  do
+    case \"\$lt_opt\" in
+    --lt-debug) lt_option_debug=1 ;;
+    --lt-dump-script)
+        lt_dump_D=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%/[^/]*$%%'\`
+        test \"X\$lt_dump_D\" = \"X\$lt_script_arg0\" && lt_dump_D=.
+        lt_dump_F=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%^.*/%%'\`
+        cat \"\$lt_dump_D/\$lt_dump_F\"
+        exit 0
+      ;;
+    --lt-*)
+        \$ECHO \"Unrecognized --lt- option: '\$lt_opt'\" 1>&2
+        exit 1
+      ;;
+    esac
+  done
+
+  # Print the debug banner immediately:
+  if test -n \"\$lt_option_debug\"; then
+    echo \"${outputname}:${output}:\${LINENO}: libtool wrapper (GNU $PACKAGE$TIMESTAMP) $VERSION\" 1>&2
+  fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+  lt_dump_args_N=1;
+  for lt_arg
+  do
+    \$ECHO \"${outputname}:${output}:\${LINENO}: newargv[\$lt_dump_args_N]: \$lt_arg\"
+    lt_dump_args_N=\`expr \$lt_dump_args_N + 1\`
+  done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+"
+  case $host in
+  # Backslashes separate directories on plain windows
+  *-*-mingw | *-*-os2* | *-cegcc*)
+    $ECHO "\
+      if test -n \"\$lt_option_debug\"; then
+        \$ECHO \"${outputname}:${output}:\${LINENO}: newargv[0]: \$progdir\\\\\$program\" 1>&2
+        func_lt_dump_args \${1+\"\$@\"} 1>&2
+      fi
+      exec \"\$progdir\\\\\$program\" \${1+\"\$@\"}
+"
+    ;;
+
+  *)
+    $ECHO "\
+      if test -n \"\$lt_option_debug\"; then
+        \$ECHO \"${outputname}:${output}:\${LINENO}: newargv[0]: \$progdir/\$program\" 1>&2
+        func_lt_dump_args \${1+\"\$@\"} 1>&2
+      fi
+      exec \"\$progdir/\$program\" \${1+\"\$@\"}
+"
+    ;;
+  esac
+  $ECHO "\
+      \$ECHO \"\$0: cannot exec \$program \$*\" 1>&2
+      exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from \$@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+  case \" \$* \" in
+  *\\ --lt-*)
+    for lt_wr_arg
+    do
+      case \$lt_wr_arg in
+      --lt-*) ;;
+      *) set x \"\$@\" \"\$lt_wr_arg\"; shift;;
+      esac
+      shift
+    done ;;
+  esac
+  func_exec_program_core \${1+\"\$@\"}
+}
+
+  # Parse options
+  func_parse_lt_options \"\$0\" \${1+\"\$@\"}
+
+  # Find the directory that this script lives in.
+  thisdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*$%%'\`
+  test \"x\$thisdir\" = \"x\$file\" && thisdir=.
+
+  # Follow symbolic links until we get to the real thisdir.
+  file=\`ls -ld \"\$file\" | $SED -n 's/.*-> //p'\`
+  while test -n \"\$file\"; do
+    destdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*\$%%'\`
+
+    # If there was a directory component, then change thisdir.
+    if test \"x\$destdir\" != \"x\$file\"; then
+      case \"\$destdir\" in
+      [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;;
+      *) thisdir=\"\$thisdir/\$destdir\" ;;
+      esac
+    fi
+
+    file=\`\$ECHO \"\$file\" | $SED 's%^.*/%%'\`
+    file=\`ls -ld \"\$thisdir/\$file\" | $SED -n 's/.*-> //p'\`
+  done
+
+  # Usually 'no', except on cygwin/mingw when embedded into
+  # the cwrapper.
+  WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=$func_emit_wrapper_arg1
+  if test \"\$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR\" = \"yes\"; then
+    # special case for '.'
+    if test \"\$thisdir\" = \".\"; then
+      thisdir=\`pwd\`
+    fi
+    # remove .libs from thisdir
+    case \"\$thisdir\" in
+    *[\\\\/]$objdir ) thisdir=\`\$ECHO \"\$thisdir\" | $SED 's%[\\\\/][^\\\\/]*$%%'\` ;;
+    $objdir )   thisdir=. ;;
+    esac
+  fi
+
+  # Try to get the absolute directory name.
+  absdir=\`cd \"\$thisdir\" && pwd\`
+  test -n \"\$absdir\" && thisdir=\"\$absdir\"
+"
+
+	if test "$fast_install" = yes; then
+	  $ECHO "\
+  program=lt-'$outputname'$exeext
+  progdir=\"\$thisdir/$objdir\"
+
+  if test ! -f \"\$progdir/\$program\" ||
+     { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | ${SED} 1q\`; \\
+       test \"X\$file\" != \"X\$progdir/\$program\"; }; then
+
+    file=\"\$\$-\$program\"
+
+    if test ! -d \"\$progdir\"; then
+      $MKDIR \"\$progdir\"
+    else
+      $RM \"\$progdir/\$file\"
+    fi"
+
+	  $ECHO "\
+
+    # relink executable if necessary
+    if test -n \"\$relink_command\"; then
+      if relink_command_output=\`eval \$relink_command 2>&1\`; then :
+      else
+	$ECHO \"\$relink_command_output\" >&2
+	$RM \"\$progdir/\$file\"
+	exit 1
+      fi
+    fi
+
+    $MV \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null ||
+    { $RM \"\$progdir/\$program\";
+      $MV \"\$progdir/\$file\" \"\$progdir/\$program\"; }
+    $RM \"\$progdir/\$file\"
+  fi"
+	else
+	  $ECHO "\
+  program='$outputname'
+  progdir=\"\$thisdir/$objdir\"
+"
+	fi
+
+	$ECHO "\
+
+  if test -f \"\$progdir/\$program\"; then"
+
+	# fixup the dll searchpath if we need to.
+	#
+	# Fix the DLL searchpath if we need to.  Do this before prepending
+	# to shlibpath, because on Windows, both are PATH and uninstalled
+	# libraries must come first.
+	if test -n "$dllsearchpath"; then
+	  $ECHO "\
+    # Add the dll search path components to the executable PATH
+    PATH=$dllsearchpath:\$PATH
+"
+	fi
+
+	# Export our shlibpath_var if we have one.
+	if test "$shlibpath_overrides_runpath" = yes && test -n "$shlibpath_var" && test -n "$temp_rpath"; then
+	  $ECHO "\
+    # Add our own library path to $shlibpath_var
+    $shlibpath_var=\"$temp_rpath\$$shlibpath_var\"
+
+    # Some systems cannot cope with colon-terminated $shlibpath_var
+    # The second colon is a workaround for a bug in BeOS R4 sed
+    $shlibpath_var=\`\$ECHO \"\$$shlibpath_var\" | $SED 's/::*\$//'\`
+
+    export $shlibpath_var
+"
+	fi
+
+	$ECHO "\
+    if test \"\$libtool_execute_magic\" != \"$magic\"; then
+      # Run the actual program with our arguments.
+      func_exec_program \${1+\"\$@\"}
+    fi
+  else
+    # The program doesn't exist.
+    \$ECHO \"\$0: error: \\\`\$progdir/\$program' does not exist\" 1>&2
+    \$ECHO \"This script is just a wrapper for \$program.\" 1>&2
+    \$ECHO \"See the $PACKAGE documentation for more information.\" 1>&2
+    exit 1
+  fi
+fi\
+"
+}
+
+
+# func_emit_cwrapperexe_src
+# emit the source code for a wrapper executable on stdout
+# Must ONLY be called from within func_mode_link because
+# it depends on a number of variable set therein.
+func_emit_cwrapperexe_src ()
+{
+	cat <<EOF
+
+/* $cwrappersource - temporary wrapper executable for $objdir/$outputname
+   Generated by $PROGRAM (GNU $PACKAGE$TIMESTAMP) $VERSION
+
+   The $output program cannot be directly executed until all the libtool
+   libraries that it depends on are installed.
+
+   This wrapper executable should never be moved out of the build directory.
+   If it is, it will not operate correctly.
+*/
+EOF
+	    cat <<"EOF"
+#ifdef _MSC_VER
+# define _CRT_SECURE_NO_DEPRECATE 1
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef _MSC_VER
+# include <direct.h>
+# include <process.h>
+# include <io.h>
+#else
+# include <unistd.h>
+# include <stdint.h>
+# ifdef __CYGWIN__
+#  include <io.h>
+# endif
+#endif
+#include <malloc.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+/* declarations of non-ANSI functions */
+#if defined(__MINGW32__)
+# ifdef __STRICT_ANSI__
+int _putenv (const char *);
+# endif
+#elif defined(__CYGWIN__)
+# ifdef __STRICT_ANSI__
+char *realpath (const char *, char *);
+int putenv (char *);
+int setenv (const char *, const char *, int);
+# endif
+/* #elif defined (other platforms) ... */
+#endif
+
+/* portability defines, excluding path handling macros */
+#if defined(_MSC_VER)
+# define setmode _setmode
+# define stat    _stat
+# define chmod   _chmod
+# define getcwd  _getcwd
+# define putenv  _putenv
+# define S_IXUSR _S_IEXEC
+# ifndef _INTPTR_T_DEFINED
+#  define _INTPTR_T_DEFINED
+#  define intptr_t int
+# endif
+#elif defined(__MINGW32__)
+# define setmode _setmode
+# define stat    _stat
+# define chmod   _chmod
+# define getcwd  _getcwd
+# define putenv  _putenv
+#elif defined(__CYGWIN__)
+# define HAVE_SETENV
+# define FOPEN_WB "wb"
+/* #elif defined (other platforms) ... */
+#endif
+
+#if defined(PATH_MAX)
+# define LT_PATHMAX PATH_MAX
+#elif defined(MAXPATHLEN)
+# define LT_PATHMAX MAXPATHLEN
+#else
+# define LT_PATHMAX 1024
+#endif
+
+#ifndef S_IXOTH
+# define S_IXOTH 0
+#endif
+#ifndef S_IXGRP
+# define S_IXGRP 0
+#endif
+
+/* path handling portability macros */
+#ifndef DIR_SEPARATOR
+# define DIR_SEPARATOR '/'
+# define PATH_SEPARATOR ':'
+#endif
+
+#if defined (_WIN32) || defined (__MSDOS__) || defined (__DJGPP__) || \
+  defined (__OS2__)
+# define HAVE_DOS_BASED_FILE_SYSTEM
+# define FOPEN_WB "wb"
+# ifndef DIR_SEPARATOR_2
+#  define DIR_SEPARATOR_2 '\\'
+# endif
+# ifndef PATH_SEPARATOR_2
+#  define PATH_SEPARATOR_2 ';'
+# endif
+#endif
+
+#ifndef DIR_SEPARATOR_2
+# define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR)
+#else /* DIR_SEPARATOR_2 */
+# define IS_DIR_SEPARATOR(ch) \
+	(((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2))
+#endif /* DIR_SEPARATOR_2 */
+
+#ifndef PATH_SEPARATOR_2
+# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR)
+#else /* PATH_SEPARATOR_2 */
+# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2)
+#endif /* PATH_SEPARATOR_2 */
+
+#ifndef FOPEN_WB
+# define FOPEN_WB "w"
+#endif
+#ifndef _O_BINARY
+# define _O_BINARY 0
+#endif
+
+#define XMALLOC(type, num)      ((type *) xmalloc ((num) * sizeof(type)))
+#define XFREE(stale) do { \
+  if (stale) { free ((void *) stale); stale = 0; } \
+} while (0)
+
+#if defined(LT_DEBUGWRAPPER)
+static int lt_debug = 1;
+#else
+static int lt_debug = 0;
+#endif
+
+const char *program_name = "libtool-wrapper"; /* in case xstrdup fails */
+
+void *xmalloc (size_t num);
+char *xstrdup (const char *string);
+const char *base_name (const char *name);
+char *find_executable (const char *wrapper);
+char *chase_symlinks (const char *pathspec);
+int make_executable (const char *path);
+int check_executable (const char *path);
+char *strendzap (char *str, const char *pat);
+void lt_debugprintf (const char *file, int line, const char *fmt, ...);
+void lt_fatal (const char *file, int line, const char *message, ...);
+static const char *nonnull (const char *s);
+static const char *nonempty (const char *s);
+void lt_setenv (const char *name, const char *value);
+char *lt_extend_str (const char *orig_value, const char *add, int to_end);
+void lt_update_exe_path (const char *name, const char *value);
+void lt_update_lib_path (const char *name, const char *value);
+char **prepare_spawn (char **argv);
+void lt_dump_script (FILE *f);
+EOF
+
+	    cat <<EOF
+volatile const char * MAGIC_EXE = "$magic_exe";
+const char * LIB_PATH_VARNAME = "$shlibpath_var";
+EOF
+
+	    if test "$shlibpath_overrides_runpath" = yes && test -n "$shlibpath_var" && test -n "$temp_rpath"; then
+              func_to_host_path "$temp_rpath"
+	      cat <<EOF
+const char * LIB_PATH_VALUE   = "$func_to_host_path_result";
+EOF
+	    else
+	      cat <<"EOF"
+const char * LIB_PATH_VALUE   = "";
+EOF
+	    fi
+
+	    if test -n "$dllsearchpath"; then
+              func_to_host_path "$dllsearchpath:"
+	      cat <<EOF
+const char * EXE_PATH_VARNAME = "PATH";
+const char * EXE_PATH_VALUE   = "$func_to_host_path_result";
+EOF
+	    else
+	      cat <<"EOF"
+const char * EXE_PATH_VARNAME = "";
+const char * EXE_PATH_VALUE   = "";
+EOF
+	    fi
+
+	    if test "$fast_install" = yes; then
+	      cat <<EOF
+const char * TARGET_PROGRAM_NAME = "lt-$outputname"; /* hopefully, no .exe */
+EOF
+	    else
+	      cat <<EOF
+const char * TARGET_PROGRAM_NAME = "$outputname"; /* hopefully, no .exe */
+EOF
+	    fi
+
+
+	    cat <<"EOF"
+
+#define LTWRAPPER_OPTION_PREFIX         "--lt-"
+
+static const char *ltwrapper_option_prefix = LTWRAPPER_OPTION_PREFIX;
+static const char *dumpscript_opt       = LTWRAPPER_OPTION_PREFIX "dump-script";
+static const char *debug_opt            = LTWRAPPER_OPTION_PREFIX "debug";
+
+int
+main (int argc, char *argv[])
+{
+  char **newargz;
+  int  newargc;
+  char *tmp_pathspec;
+  char *actual_cwrapper_path;
+  char *actual_cwrapper_name;
+  char *target_name;
+  char *lt_argv_zero;
+  intptr_t rval = 127;
+
+  int i;
+
+  program_name = (char *) xstrdup (base_name (argv[0]));
+  newargz = XMALLOC (char *, argc + 1);
+
+  /* very simple arg parsing; don't want to rely on getopt
+   * also, copy all non cwrapper options to newargz, except
+   * argz[0], which is handled differently
+   */
+  newargc=0;
+  for (i = 1; i < argc; i++)
+    {
+      if (strcmp (argv[i], dumpscript_opt) == 0)
+	{
+EOF
+	    case "$host" in
+	      *mingw* | *cygwin* )
+		# make stdout use "unix" line endings
+		echo "          setmode(1,_O_BINARY);"
+		;;
+	      esac
+
+	    cat <<"EOF"
+	  lt_dump_script (stdout);
+	  return 0;
+	}
+      if (strcmp (argv[i], debug_opt) == 0)
+	{
+          lt_debug = 1;
+          continue;
+	}
+      if (strcmp (argv[i], ltwrapper_option_prefix) == 0)
+        {
+          /* however, if there is an option in the LTWRAPPER_OPTION_PREFIX
+             namespace, but it is not one of the ones we know about and
+             have already dealt with, above (inluding dump-script), then
+             report an error. Otherwise, targets might begin to believe
+             they are allowed to use options in the LTWRAPPER_OPTION_PREFIX
+             namespace. The first time any user complains about this, we'll
+             need to make LTWRAPPER_OPTION_PREFIX a configure-time option
+             or a configure.ac-settable value.
+           */
+          lt_fatal (__FILE__, __LINE__,
+		    "unrecognized %s option: '%s'",
+                    ltwrapper_option_prefix, argv[i]);
+        }
+      /* otherwise ... */
+      newargz[++newargc] = xstrdup (argv[i]);
+    }
+  newargz[++newargc] = NULL;
+
+EOF
+	    cat <<EOF
+  /* The GNU banner must be the first non-error debug message */
+  lt_debugprintf (__FILE__, __LINE__, "libtool wrapper (GNU $PACKAGE$TIMESTAMP) $VERSION\n");
+EOF
+	    cat <<"EOF"
+  lt_debugprintf (__FILE__, __LINE__, "(main) argv[0]: %s\n", argv[0]);
+  lt_debugprintf (__FILE__, __LINE__, "(main) program_name: %s\n", program_name);
+
+  tmp_pathspec = find_executable (argv[0]);
+  if (tmp_pathspec == NULL)
+    lt_fatal (__FILE__, __LINE__, "couldn't find %s", argv[0]);
+  lt_debugprintf (__FILE__, __LINE__,
+                  "(main) found exe (before symlink chase) at: %s\n",
+		  tmp_pathspec);
+
+  actual_cwrapper_path = chase_symlinks (tmp_pathspec);
+  lt_debugprintf (__FILE__, __LINE__,
+                  "(main) found exe (after symlink chase) at: %s\n",
+		  actual_cwrapper_path);
+  XFREE (tmp_pathspec);
+
+  actual_cwrapper_name = xstrdup (base_name (actual_cwrapper_path));
+  strendzap (actual_cwrapper_path, actual_cwrapper_name);
+
+  /* wrapper name transforms */
+  strendzap (actual_cwrapper_name, ".exe");
+  tmp_pathspec = lt_extend_str (actual_cwrapper_name, ".exe", 1);
+  XFREE (actual_cwrapper_name);
+  actual_cwrapper_name = tmp_pathspec;
+  tmp_pathspec = 0;
+
+  /* target_name transforms -- use actual target program name; might have lt- prefix */
+  target_name = xstrdup (base_name (TARGET_PROGRAM_NAME));
+  strendzap (target_name, ".exe");
+  tmp_pathspec = lt_extend_str (target_name, ".exe", 1);
+  XFREE (target_name);
+  target_name = tmp_pathspec;
+  tmp_pathspec = 0;
+
+  lt_debugprintf (__FILE__, __LINE__,
+		  "(main) libtool target name: %s\n",
+		  target_name);
+EOF
+
+	    cat <<EOF
+  newargz[0] =
+    XMALLOC (char, (strlen (actual_cwrapper_path) +
+		    strlen ("$objdir") + 1 + strlen (actual_cwrapper_name) + 1));
+  strcpy (newargz[0], actual_cwrapper_path);
+  strcat (newargz[0], "$objdir");
+  strcat (newargz[0], "/");
+EOF
+
+	    cat <<"EOF"
+  /* stop here, and copy so we don't have to do this twice */
+  tmp_pathspec = xstrdup (newargz[0]);
+
+  /* do NOT want the lt- prefix here, so use actual_cwrapper_name */
+  strcat (newargz[0], actual_cwrapper_name);
+
+  /* DO want the lt- prefix here if it exists, so use target_name */
+  lt_argv_zero = lt_extend_str (tmp_pathspec, target_name, 1);
+  XFREE (tmp_pathspec);
+  tmp_pathspec = NULL;
+EOF
+
+	    case $host_os in
+	      mingw*)
+	    cat <<"EOF"
+  {
+    char* p;
+    while ((p = strchr (newargz[0], '\\')) != NULL)
+      {
+	*p = '/';
+      }
+    while ((p = strchr (lt_argv_zero, '\\')) != NULL)
+      {
+	*p = '/';
+      }
+  }
+EOF
+	    ;;
+	    esac
+
+	    cat <<"EOF"
+  XFREE (target_name);
+  XFREE (actual_cwrapper_path);
+  XFREE (actual_cwrapper_name);
+
+  lt_setenv ("BIN_SH", "xpg4"); /* for Tru64 */
+  lt_setenv ("DUALCASE", "1");  /* for MSK sh */
+  /* Update the DLL searchpath.  EXE_PATH_VALUE ($dllsearchpath) must
+     be prepended before (that is, appear after) LIB_PATH_VALUE ($temp_rpath)
+     because on Windows, both *_VARNAMEs are PATH but uninstalled
+     libraries must come first. */
+  lt_update_exe_path (EXE_PATH_VARNAME, EXE_PATH_VALUE);
+  lt_update_lib_path (LIB_PATH_VARNAME, LIB_PATH_VALUE);
+
+  lt_debugprintf (__FILE__, __LINE__, "(main) lt_argv_zero: %s\n",
+		  nonnull (lt_argv_zero));
+  for (i = 0; i < newargc; i++)
+    {
+      lt_debugprintf (__FILE__, __LINE__, "(main) newargz[%d]: %s\n",
+		      i, nonnull (newargz[i]));
+    }
+
+EOF
+
+	    case $host_os in
+	      mingw*)
+		cat <<"EOF"
+  /* execv doesn't actually work on mingw as expected on unix */
+  newargz = prepare_spawn (newargz);
+  rval = _spawnv (_P_WAIT, lt_argv_zero, (const char * const *) newargz);
+  if (rval == -1)
+    {
+      /* failed to start process */
+      lt_debugprintf (__FILE__, __LINE__,
+		      "(main) failed to launch target \"%s\": %s\n",
+		      lt_argv_zero, nonnull (strerror (errno)));
+      return 127;
+    }
+  return rval;
+EOF
+		;;
+	      *)
+		cat <<"EOF"
+  execv (lt_argv_zero, newargz);
+  return rval; /* =127, but avoids unused variable warning */
+EOF
+		;;
+	    esac
+
+	    cat <<"EOF"
+}
+
+void *
+xmalloc (size_t num)
+{
+  void *p = (void *) malloc (num);
+  if (!p)
+    lt_fatal (__FILE__, __LINE__, "memory exhausted");
+
+  return p;
+}
+
+char *
+xstrdup (const char *string)
+{
+  return string ? strcpy ((char *) xmalloc (strlen (string) + 1),
+			  string) : NULL;
+}
+
+const char *
+base_name (const char *name)
+{
+  const char *base;
+
+#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
+  /* Skip over the disk name in MSDOS pathnames. */
+  if (isalpha ((unsigned char) name[0]) && name[1] == ':')
+    name += 2;
+#endif
+
+  for (base = name; *name; name++)
+    if (IS_DIR_SEPARATOR (*name))
+      base = name + 1;
+  return base;
+}
+
+int
+check_executable (const char *path)
+{
+  struct stat st;
+
+  lt_debugprintf (__FILE__, __LINE__, "(check_executable): %s\n",
+                  nonempty (path));
+  if ((!path) || (!*path))
+    return 0;
+
+  if ((stat (path, &st) >= 0)
+      && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
+    return 1;
+  else
+    return 0;
+}
+
+int
+make_executable (const char *path)
+{
+  int rval = 0;
+  struct stat st;
+
+  lt_debugprintf (__FILE__, __LINE__, "(make_executable): %s\n",
+                  nonempty (path));
+  if ((!path) || (!*path))
+    return 0;
+
+  if (stat (path, &st) >= 0)
+    {
+      rval = chmod (path, st.st_mode | S_IXOTH | S_IXGRP | S_IXUSR);
+    }
+  return rval;
+}
+
+/* Searches for the full path of the wrapper.  Returns
+   newly allocated full path name if found, NULL otherwise
+   Does not chase symlinks, even on platforms that support them.
+*/
+char *
+find_executable (const char *wrapper)
+{
+  int has_slash = 0;
+  const char *p;
+  const char *p_next;
+  /* static buffer for getcwd */
+  char tmp[LT_PATHMAX + 1];
+  int tmp_len;
+  char *concat_name;
+
+  lt_debugprintf (__FILE__, __LINE__, "(find_executable): %s\n",
+                  nonempty (wrapper));
+
+  if ((wrapper == NULL) || (*wrapper == '\0'))
+    return NULL;
+
+  /* Absolute path? */
+#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
+  if (isalpha ((unsigned char) wrapper[0]) && wrapper[1] == ':')
+    {
+      concat_name = xstrdup (wrapper);
+      if (check_executable (concat_name))
+	return concat_name;
+      XFREE (concat_name);
+    }
+  else
+    {
+#endif
+      if (IS_DIR_SEPARATOR (wrapper[0]))
+	{
+	  concat_name = xstrdup (wrapper);
+	  if (check_executable (concat_name))
+	    return concat_name;
+	  XFREE (concat_name);
+	}
+#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
+    }
+#endif
+
+  for (p = wrapper; *p; p++)
+    if (*p == '/')
+      {
+	has_slash = 1;
+	break;
+      }
+  if (!has_slash)
+    {
+      /* no slashes; search PATH */
+      const char *path = getenv ("PATH");
+      if (path != NULL)
+	{
+	  for (p = path; *p; p = p_next)
+	    {
+	      const char *q;
+	      size_t p_len;
+	      for (q = p; *q; q++)
+		if (IS_PATH_SEPARATOR (*q))
+		  break;
+	      p_len = q - p;
+	      p_next = (*q == '\0' ? q : q + 1);
+	      if (p_len == 0)
+		{
+		  /* empty path: current directory */
+		  if (getcwd (tmp, LT_PATHMAX) == NULL)
+		    lt_fatal (__FILE__, __LINE__, "getcwd failed: %s",
+                              nonnull (strerror (errno)));
+		  tmp_len = strlen (tmp);
+		  concat_name =
+		    XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1);
+		  memcpy (concat_name, tmp, tmp_len);
+		  concat_name[tmp_len] = '/';
+		  strcpy (concat_name + tmp_len + 1, wrapper);
+		}
+	      else
+		{
+		  concat_name =
+		    XMALLOC (char, p_len + 1 + strlen (wrapper) + 1);
+		  memcpy (concat_name, p, p_len);
+		  concat_name[p_len] = '/';
+		  strcpy (concat_name + p_len + 1, wrapper);
+		}
+	      if (check_executable (concat_name))
+		return concat_name;
+	      XFREE (concat_name);
+	    }
+	}
+      /* not found in PATH; assume curdir */
+    }
+  /* Relative path | not found in path: prepend cwd */
+  if (getcwd (tmp, LT_PATHMAX) == NULL)
+    lt_fatal (__FILE__, __LINE__, "getcwd failed: %s",
+              nonnull (strerror (errno)));
+  tmp_len = strlen (tmp);
+  concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1);
+  memcpy (concat_name, tmp, tmp_len);
+  concat_name[tmp_len] = '/';
+  strcpy (concat_name + tmp_len + 1, wrapper);
+
+  if (check_executable (concat_name))
+    return concat_name;
+  XFREE (concat_name);
+  return NULL;
+}
+
+char *
+chase_symlinks (const char *pathspec)
+{
+#ifndef S_ISLNK
+  return xstrdup (pathspec);
+#else
+  char buf[LT_PATHMAX];
+  struct stat s;
+  char *tmp_pathspec = xstrdup (pathspec);
+  char *p;
+  int has_symlinks = 0;
+  while (strlen (tmp_pathspec) && !has_symlinks)
+    {
+      lt_debugprintf (__FILE__, __LINE__,
+		      "checking path component for symlinks: %s\n",
+		      tmp_pathspec);
+      if (lstat (tmp_pathspec, &s) == 0)
+	{
+	  if (S_ISLNK (s.st_mode) != 0)
+	    {
+	      has_symlinks = 1;
+	      break;
+	    }
+
+	  /* search backwards for last DIR_SEPARATOR */
+	  p = tmp_pathspec + strlen (tmp_pathspec) - 1;
+	  while ((p > tmp_pathspec) && (!IS_DIR_SEPARATOR (*p)))
+	    p--;
+	  if ((p == tmp_pathspec) && (!IS_DIR_SEPARATOR (*p)))
+	    {
+	      /* no more DIR_SEPARATORS left */
+	      break;
+	    }
+	  *p = '\0';
+	}
+      else
+	{
+	  lt_fatal (__FILE__, __LINE__,
+		    "error accessing file \"%s\": %s",
+		    tmp_pathspec, nonnull (strerror (errno)));
+	}
+    }
+  XFREE (tmp_pathspec);
+
+  if (!has_symlinks)
+    {
+      return xstrdup (pathspec);
+    }
+
+  tmp_pathspec = realpath (pathspec, buf);
+  if (tmp_pathspec == 0)
+    {
+      lt_fatal (__FILE__, __LINE__,
+		"could not follow symlinks for %s", pathspec);
+    }
+  return xstrdup (tmp_pathspec);
+#endif
+}
+
+char *
+strendzap (char *str, const char *pat)
+{
+  size_t len, patlen;
+
+  assert (str != NULL);
+  assert (pat != NULL);
+
+  len = strlen (str);
+  patlen = strlen (pat);
+
+  if (patlen <= len)
+    {
+      str += len - patlen;
+      if (strcmp (str, pat) == 0)
+	*str = '\0';
+    }
+  return str;
+}
+
+void
+lt_debugprintf (const char *file, int line, const char *fmt, ...)
+{
+  va_list args;
+  if (lt_debug)
+    {
+      (void) fprintf (stderr, "%s:%s:%d: ", program_name, file, line);
+      va_start (args, fmt);
+      (void) vfprintf (stderr, fmt, args);
+      va_end (args);
+    }
+}
+
+static void
+lt_error_core (int exit_status, const char *file,
+	       int line, const char *mode,
+	       const char *message, va_list ap)
+{
+  fprintf (stderr, "%s:%s:%d: %s: ", program_name, file, line, mode);
+  vfprintf (stderr, message, ap);
+  fprintf (stderr, ".\n");
+
+  if (exit_status >= 0)
+    exit (exit_status);
+}
+
+void
+lt_fatal (const char *file, int line, const char *message, ...)
+{
+  va_list ap;
+  va_start (ap, message);
+  lt_error_core (EXIT_FAILURE, file, line, "FATAL", message, ap);
+  va_end (ap);
+}
+
+static const char *
+nonnull (const char *s)
+{
+  return s ? s : "(null)";
+}
+
+static const char *
+nonempty (const char *s)
+{
+  return (s && !*s) ? "(empty)" : nonnull (s);
+}
+
+void
+lt_setenv (const char *name, const char *value)
+{
+  lt_debugprintf (__FILE__, __LINE__,
+		  "(lt_setenv) setting '%s' to '%s'\n",
+                  nonnull (name), nonnull (value));
+  {
+#ifdef HAVE_SETENV
+    /* always make a copy, for consistency with !HAVE_SETENV */
+    char *str = xstrdup (value);
+    setenv (name, str, 1);
+#else
+    int len = strlen (name) + 1 + strlen (value) + 1;
+    char *str = XMALLOC (char, len);
+    sprintf (str, "%s=%s", name, value);
+    if (putenv (str) != EXIT_SUCCESS)
+      {
+        XFREE (str);
+      }
+#endif
+  }
+}
+
+char *
+lt_extend_str (const char *orig_value, const char *add, int to_end)
+{
+  char *new_value;
+  if (orig_value && *orig_value)
+    {
+      int orig_value_len = strlen (orig_value);
+      int add_len = strlen (add);
+      new_value = XMALLOC (char, add_len + orig_value_len + 1);
+      if (to_end)
+        {
+          strcpy (new_value, orig_value);
+          strcpy (new_value + orig_value_len, add);
+        }
+      else
+        {
+          strcpy (new_value, add);
+          strcpy (new_value + add_len, orig_value);
+        }
+    }
+  else
+    {
+      new_value = xstrdup (add);
+    }
+  return new_value;
+}
+
+void
+lt_update_exe_path (const char *name, const char *value)
+{
+  lt_debugprintf (__FILE__, __LINE__,
+		  "(lt_update_exe_path) modifying '%s' by prepending '%s'\n",
+                  nonnull (name), nonnull (value));
+
+  if (name && *name && value && *value)
+    {
+      char *new_value = lt_extend_str (getenv (name), value, 0);
+      /* some systems can't cope with a ':'-terminated path #' */
+      int len = strlen (new_value);
+      while (((len = strlen (new_value)) > 0) && IS_PATH_SEPARATOR (new_value[len-1]))
+        {
+          new_value[len-1] = '\0';
+        }
+      lt_setenv (name, new_value);
+      XFREE (new_value);
+    }
+}
+
+void
+lt_update_lib_path (const char *name, const char *value)
+{
+  lt_debugprintf (__FILE__, __LINE__,
+		  "(lt_update_lib_path) modifying '%s' by prepending '%s'\n",
+                  nonnull (name), nonnull (value));
+
+  if (name && *name && value && *value)
+    {
+      char *new_value = lt_extend_str (getenv (name), value, 0);
+      lt_setenv (name, new_value);
+      XFREE (new_value);
+    }
+}
+
+EOF
+	    case $host_os in
+	      mingw*)
+		cat <<"EOF"
+
+/* Prepares an argument vector before calling spawn().
+   Note that spawn() does not by itself call the command interpreter
+     (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") :
+      ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+         GetVersionEx(&v);
+         v.dwPlatformId == VER_PLATFORM_WIN32_NT;
+      }) ? "cmd.exe" : "command.com").
+   Instead it simply concatenates the arguments, separated by ' ', and calls
+   CreateProcess().  We must quote the arguments since Win32 CreateProcess()
+   interprets characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a
+   special way:
+   - Space and tab are interpreted as delimiters. They are not treated as
+     delimiters if they are surrounded by double quotes: "...".
+   - Unescaped double quotes are removed from the input. Their only effect is
+     that within double quotes, space and tab are treated like normal
+     characters.
+   - Backslashes not followed by double quotes are not special.
+   - But 2*n+1 backslashes followed by a double quote become
+     n backslashes followed by a double quote (n >= 0):
+       \" -> "
+       \\\" -> \"
+       \\\\\" -> \\"
+ */
+#define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
+#define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
+char **
+prepare_spawn (char **argv)
+{
+  size_t argc;
+  char **new_argv;
+  size_t i;
+
+  /* Count number of arguments.  */
+  for (argc = 0; argv[argc] != NULL; argc++)
+    ;
+
+  /* Allocate new argument vector.  */
+  new_argv = XMALLOC (char *, argc + 1);
+
+  /* Put quoted arguments into the new argument vector.  */
+  for (i = 0; i < argc; i++)
+    {
+      const char *string = argv[i];
+
+      if (string[0] == '\0')
+	new_argv[i] = xstrdup ("\"\"");
+      else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL)
+	{
+	  int quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL);
+	  size_t length;
+	  unsigned int backslashes;
+	  const char *s;
+	  char *quoted_string;
+	  char *p;
+
+	  length = 0;
+	  backslashes = 0;
+	  if (quote_around)
+	    length++;
+	  for (s = string; *s != '\0'; s++)
+	    {
+	      char c = *s;
+	      if (c == '"')
+		length += backslashes + 1;
+	      length++;
+	      if (c == '\\')
+		backslashes++;
+	      else
+		backslashes = 0;
+	    }
+	  if (quote_around)
+	    length += backslashes + 1;
+
+	  quoted_string = XMALLOC (char, length + 1);
+
+	  p = quoted_string;
+	  backslashes = 0;
+	  if (quote_around)
+	    *p++ = '"';
+	  for (s = string; *s != '\0'; s++)
+	    {
+	      char c = *s;
+	      if (c == '"')
+		{
+		  unsigned int j;
+		  for (j = backslashes + 1; j > 0; j--)
+		    *p++ = '\\';
+		}
+	      *p++ = c;
+	      if (c == '\\')
+		backslashes++;
+	      else
+		backslashes = 0;
+	    }
+	  if (quote_around)
+	    {
+	      unsigned int j;
+	      for (j = backslashes; j > 0; j--)
+		*p++ = '\\';
+	      *p++ = '"';
+	    }
+	  *p = '\0';
+
+	  new_argv[i] = quoted_string;
+	}
+      else
+	new_argv[i] = (char *) string;
+    }
+  new_argv[argc] = NULL;
+
+  return new_argv;
+}
+EOF
+		;;
+	    esac
+
+            cat <<"EOF"
+void lt_dump_script (FILE* f)
+{
+EOF
+	    func_emit_wrapper yes |
+	      $SED -n -e '
+s/^\(.\{79\}\)\(..*\)/\1\
+\2/
+h
+s/\([\\"]\)/\\\1/g
+s/$/\\n/
+s/\([^\n]*\).*/  fputs ("\1", f);/p
+g
+D'
+            cat <<"EOF"
+}
+EOF
+}
+# end: func_emit_cwrapperexe_src
+
+# func_win32_import_lib_p ARG
+# True if ARG is an import lib, as indicated by $file_magic_cmd
+func_win32_import_lib_p ()
+{
+    $opt_debug
+    case `eval $file_magic_cmd \"\$1\" 2>/dev/null | $SED -e 10q` in
+    *import*) : ;;
+    *) false ;;
+    esac
+}
+
+# func_mode_link arg...
+func_mode_link ()
+{
+    $opt_debug
+    case $host in
+    *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*)
+      # It is impossible to link a dll without this setting, and
+      # we shouldn't force the makefile maintainer to figure out
+      # which system we are compiling for in order to pass an extra
+      # flag for every libtool invocation.
+      # allow_undefined=no
+
+      # FIXME: Unfortunately, there are problems with the above when trying
+      # to make a dll which has undefined symbols, in which case not
+      # even a static library is built.  For now, we need to specify
+      # -no-undefined on the libtool link line when we can be certain
+      # that all symbols are satisfied, otherwise we get a static library.
+      allow_undefined=yes
+      ;;
+    *)
+      allow_undefined=yes
+      ;;
+    esac
+    libtool_args=$nonopt
+    base_compile="$nonopt $@"
+    compile_command=$nonopt
+    finalize_command=$nonopt
+
+    compile_rpath=
+    finalize_rpath=
+    compile_shlibpath=
+    finalize_shlibpath=
+    convenience=
+    old_convenience=
+    deplibs=
+    old_deplibs=
+    compiler_flags=
+    linker_flags=
+    dllsearchpath=
+    lib_search_path=`pwd`
+    inst_prefix_dir=
+    new_inherited_linker_flags=
+
+    avoid_version=no
+    bindir=
+    dlfiles=
+    dlprefiles=
+    dlself=no
+    export_dynamic=no
+    export_symbols=
+    export_symbols_regex=
+    generated=
+    libobjs=
+    ltlibs=
+    module=no
+    no_install=no
+    objs=
+    non_pic_objects=
+    precious_files_regex=
+    prefer_static_libs=no
+    preload=no
+    prev=
+    prevarg=
+    release=
+    rpath=
+    xrpath=
+    perm_rpath=
+    temp_rpath=
+    thread_safe=no
+    vinfo=
+    vinfo_number=no
+    weak_libs=
+    single_module="${wl}-single_module"
+    func_infer_tag $base_compile
+
+    # We need to know -static, to get the right output filenames.
+    for arg
+    do
+      case $arg in
+      -shared)
+	test "$build_libtool_libs" != yes && \
+	  func_fatal_configuration "can not build a shared library"
+	build_old_libs=no
+	break
+	;;
+      -all-static | -static | -static-libtool-libs)
+	case $arg in
+	-all-static)
+	  if test "$build_libtool_libs" = yes && test -z "$link_static_flag"; then
+	    func_warning "complete static linking is impossible in this configuration"
+	  fi
+	  if test -n "$link_static_flag"; then
+	    dlopen_self=$dlopen_self_static
+	  fi
+	  prefer_static_libs=yes
+	  ;;
+	-static)
+	  if test -z "$pic_flag" && test -n "$link_static_flag"; then
+	    dlopen_self=$dlopen_self_static
+	  fi
+	  prefer_static_libs=built
+	  ;;
+	-static-libtool-libs)
+	  if test -z "$pic_flag" && test -n "$link_static_flag"; then
+	    dlopen_self=$dlopen_self_static
+	  fi
+	  prefer_static_libs=yes
+	  ;;
+	esac
+	build_libtool_libs=no
+	build_old_libs=yes
+	break
+	;;
+      esac
+    done
+
+    # See if our shared archives depend on static archives.
+    test -n "$old_archive_from_new_cmds" && build_old_libs=yes
+
+    # Go through the arguments, transforming them on the way.
+    while test "$#" -gt 0; do
+      arg="$1"
+      shift
+      func_quote_for_eval "$arg"
+      qarg=$func_quote_for_eval_unquoted_result
+      func_append libtool_args " $func_quote_for_eval_result"
+
+      # If the previous option needs an argument, assign it.
+      if test -n "$prev"; then
+	case $prev in
+	output)
+	  func_append compile_command " @OUTPUT@"
+	  func_append finalize_command " @OUTPUT@"
+	  ;;
+	esac
+
+	case $prev in
+	bindir)
+	  bindir="$arg"
+	  prev=
+	  continue
+	  ;;
+	dlfiles|dlprefiles)
+	  if test "$preload" = no; then
+	    # Add the symbol object into the linking commands.
+	    func_append compile_command " @SYMFILE@"
+	    func_append finalize_command " @SYMFILE@"
+	    preload=yes
+	  fi
+	  case $arg in
+	  *.la | *.lo) ;;  # We handle these cases below.
+	  force)
+	    if test "$dlself" = no; then
+	      dlself=needless
+	      export_dynamic=yes
+	    fi
+	    prev=
+	    continue
+	    ;;
+	  self)
+	    if test "$prev" = dlprefiles; then
+	      dlself=yes
+	    elif test "$prev" = dlfiles && test "$dlopen_self" != yes; then
+	      dlself=yes
+	    else
+	      dlself=needless
+	      export_dynamic=yes
+	    fi
+	    prev=
+	    continue
+	    ;;
+	  *)
+	    if test "$prev" = dlfiles; then
+	      func_append dlfiles " $arg"
+	    else
+	      func_append dlprefiles " $arg"
+	    fi
+	    prev=
+	    continue
+	    ;;
+	  esac
+	  ;;
+	expsyms)
+	  export_symbols="$arg"
+	  test -f "$arg" \
+	    || func_fatal_error "symbol file \`$arg' does not exist"
+	  prev=
+	  continue
+	  ;;
+	expsyms_regex)
+	  export_symbols_regex="$arg"
+	  prev=
+	  continue
+	  ;;
+	framework)
+	  case $host in
+	    *-*-darwin*)
+	      case "$deplibs " in
+		*" $qarg.ltframework "*) ;;
+		*) func_append deplibs " $qarg.ltframework" # this is fixed later
+		   ;;
+	      esac
+	      ;;
+	  esac
+	  prev=
+	  continue
+	  ;;
+	inst_prefix)
+	  inst_prefix_dir="$arg"
+	  prev=
+	  continue
+	  ;;
+	objectlist)
+	  if test -f "$arg"; then
+	    save_arg=$arg
+	    moreargs=
+	    for fil in `cat "$save_arg"`
+	    do
+#	      func_append moreargs " $fil"
+	      arg=$fil
+	      # A libtool-controlled object.
+
+	      # Check to see that this really is a libtool object.
+	      if func_lalib_unsafe_p "$arg"; then
+		pic_object=
+		non_pic_object=
+
+		# Read the .lo file
+		func_source "$arg"
+
+		if test -z "$pic_object" ||
+		   test -z "$non_pic_object" ||
+		   test "$pic_object" = none &&
+		   test "$non_pic_object" = none; then
+		  func_fatal_error "cannot find name of object for \`$arg'"
+		fi
+
+		# Extract subdirectory from the argument.
+		func_dirname "$arg" "/" ""
+		xdir="$func_dirname_result"
+
+		if test "$pic_object" != none; then
+		  # Prepend the subdirectory the object is found in.
+		  pic_object="$xdir$pic_object"
+
+		  if test "$prev" = dlfiles; then
+		    if test "$build_libtool_libs" = yes && test "$dlopen_support" = yes; then
+		      func_append dlfiles " $pic_object"
+		      prev=
+		      continue
+		    else
+		      # If libtool objects are unsupported, then we need to preload.
+		      prev=dlprefiles
+		    fi
+		  fi
+
+		  # CHECK ME:  I think I busted this.  -Ossama
+		  if test "$prev" = dlprefiles; then
+		    # Preload the old-style object.
+		    func_append dlprefiles " $pic_object"
+		    prev=
+		  fi
+
+		  # A PIC object.
+		  func_append libobjs " $pic_object"
+		  arg="$pic_object"
+		fi
+
+		# Non-PIC object.
+		if test "$non_pic_object" != none; then
+		  # Prepend the subdirectory the object is found in.
+		  non_pic_object="$xdir$non_pic_object"
+
+		  # A standard non-PIC object
+		  func_append non_pic_objects " $non_pic_object"
+		  if test -z "$pic_object" || test "$pic_object" = none ; then
+		    arg="$non_pic_object"
+		  fi
+		else
+		  # If the PIC object exists, use it instead.
+		  # $xdir was prepended to $pic_object above.
+		  non_pic_object="$pic_object"
+		  func_append non_pic_objects " $non_pic_object"
+		fi
+	      else
+		# Only an error if not doing a dry-run.
+		if $opt_dry_run; then
+		  # Extract subdirectory from the argument.
+		  func_dirname "$arg" "/" ""
+		  xdir="$func_dirname_result"
+
+		  func_lo2o "$arg"
+		  pic_object=$xdir$objdir/$func_lo2o_result
+		  non_pic_object=$xdir$func_lo2o_result
+		  func_append libobjs " $pic_object"
+		  func_append non_pic_objects " $non_pic_object"
+	        else
+		  func_fatal_error "\`$arg' is not a valid libtool object"
+		fi
+	      fi
+	    done
+	  else
+	    func_fatal_error "link input file \`$arg' does not exist"
+	  fi
+	  arg=$save_arg
+	  prev=
+	  continue
+	  ;;
+	precious_regex)
+	  precious_files_regex="$arg"
+	  prev=
+	  continue
+	  ;;
+	release)
+	  release="-$arg"
+	  prev=
+	  continue
+	  ;;
+	rpath | xrpath)
+	  # We need an absolute path.
+	  case $arg in
+	  [\\/]* | [A-Za-z]:[\\/]*) ;;
+	  *)
+	    func_fatal_error "only absolute run-paths are allowed"
+	    ;;
+	  esac
+	  if test "$prev" = rpath; then
+	    case "$rpath " in
+	    *" $arg "*) ;;
+	    *) func_append rpath " $arg" ;;
+	    esac
+	  else
+	    case "$xrpath " in
+	    *" $arg "*) ;;
+	    *) func_append xrpath " $arg" ;;
+	    esac
+	  fi
+	  prev=
+	  continue
+	  ;;
+	shrext)
+	  shrext_cmds="$arg"
+	  prev=
+	  continue
+	  ;;
+	weak)
+	  func_append weak_libs " $arg"
+	  prev=
+	  continue
+	  ;;
+	xcclinker)
+	  func_append linker_flags " $qarg"
+	  func_append compiler_flags " $qarg"
+	  prev=
+	  func_append compile_command " $qarg"
+	  func_append finalize_command " $qarg"
+	  continue
+	  ;;
+	xcompiler)
+	  func_append compiler_flags " $qarg"
+	  prev=
+	  func_append compile_command " $qarg"
+	  func_append finalize_command " $qarg"
+	  continue
+	  ;;
+	xlinker)
+	  func_append linker_flags " $qarg"
+	  func_append compiler_flags " $wl$qarg"
+	  prev=
+	  func_append compile_command " $wl$qarg"
+	  func_append finalize_command " $wl$qarg"
+	  continue
+	  ;;
+	*)
+	  eval "$prev=\"\$arg\""
+	  prev=
+	  continue
+	  ;;
+	esac
+      fi # test -n "$prev"
+
+      prevarg="$arg"
+
+      case $arg in
+      -all-static)
+	if test -n "$link_static_flag"; then
+	  # See comment for -static flag below, for more details.
+	  func_append compile_command " $link_static_flag"
+	  func_append finalize_command " $link_static_flag"
+	fi
+	continue
+	;;
+
+      -allow-undefined)
+	# FIXME: remove this flag sometime in the future.
+	func_fatal_error "\`-allow-undefined' must not be used because it is the default"
+	;;
+
+      -avoid-version)
+	avoid_version=yes
+	continue
+	;;
+
+      -bindir)
+	prev=bindir
+	continue
+	;;
+
+      -dlopen)
+	prev=dlfiles
+	continue
+	;;
+
+      -dlpreopen)
+	prev=dlprefiles
+	continue
+	;;
+
+      -export-dynamic)
+	export_dynamic=yes
+	continue
+	;;
+
+      -export-symbols | -export-symbols-regex)
+	if test -n "$export_symbols" || test -n "$export_symbols_regex"; then
+	  func_fatal_error "more than one -exported-symbols argument is not allowed"
+	fi
+	if test "X$arg" = "X-export-symbols"; then
+	  prev=expsyms
+	else
+	  prev=expsyms_regex
+	fi
+	continue
+	;;
+
+      -framework)
+	prev=framework
+	continue
+	;;
+
+      -inst-prefix-dir)
+	prev=inst_prefix
+	continue
+	;;
+
+      # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:*
+      # so, if we see these flags be careful not to treat them like -L
+      -L[A-Z][A-Z]*:*)
+	case $with_gcc/$host in
+	no/*-*-irix* | /*-*-irix*)
+	  func_append compile_command " $arg"
+	  func_append finalize_command " $arg"
+	  ;;
+	esac
+	continue
+	;;
+
+      -L*)
+	func_stripname "-L" '' "$arg"
+	if test -z "$func_stripname_result"; then
+	  if test "$#" -gt 0; then
+	    func_fatal_error "require no space between \`-L' and \`$1'"
+	  else
+	    func_fatal_error "need path for \`-L' option"
+	  fi
+	fi
+	func_resolve_sysroot "$func_stripname_result"
+	dir=$func_resolve_sysroot_result
+	# We need an absolute path.
+	case $dir in
+	[\\/]* | [A-Za-z]:[\\/]*) ;;
+	*)
+	  absdir=`cd "$dir" && pwd`
+	  test -z "$absdir" && \
+	    func_fatal_error "cannot determine absolute directory name of \`$dir'"
+	  dir="$absdir"
+	  ;;
+	esac
+	case "$deplibs " in
+	*" -L$dir "* | *" $arg "*)
+	  # Will only happen for absolute or sysroot arguments
+	  ;;
+	*)
+	  # Preserve sysroot, but never include relative directories
+	  case $dir in
+	    [\\/]* | [A-Za-z]:[\\/]* | =*) func_append deplibs " $arg" ;;
+	    *) func_append deplibs " -L$dir" ;;
+	  esac
+	  func_append lib_search_path " $dir"
+	  ;;
+	esac
+	case $host in
+	*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*)
+	  testbindir=`$ECHO "$dir" | $SED 's*/lib$*/bin*'`
+	  case :$dllsearchpath: in
+	  *":$dir:"*) ;;
+	  ::) dllsearchpath=$dir;;
+	  *) func_append dllsearchpath ":$dir";;
+	  esac
+	  case :$dllsearchpath: in
+	  *":$testbindir:"*) ;;
+	  ::) dllsearchpath=$testbindir;;
+	  *) func_append dllsearchpath ":$testbindir";;
+	  esac
+	  ;;
+	esac
+	continue
+	;;
+
+      -l*)
+	if test "X$arg" = "X-lc" || test "X$arg" = "X-lm"; then
+	  case $host in
+	  *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-beos* | *-cegcc* | *-*-haiku*)
+	    # These systems don't actually have a C or math library (as such)
+	    continue
+	    ;;
+	  *-*-os2*)
+	    # These systems don't actually have a C library (as such)
+	    test "X$arg" = "X-lc" && continue
+	    ;;
+	  *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*)
+	    # Do not include libc due to us having libc/libc_r.
+	    test "X$arg" = "X-lc" && continue
+	    ;;
+	  *-*-rhapsody* | *-*-darwin1.[012])
+	    # Rhapsody C and math libraries are in the System framework
+	    func_append deplibs " System.ltframework"
+	    continue
+	    ;;
+	  *-*-sco3.2v5* | *-*-sco5v6*)
+	    # Causes problems with __ctype
+	    test "X$arg" = "X-lc" && continue
+	    ;;
+	  *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*)
+	    # Compiler inserts libc in the correct place for threads to work
+	    test "X$arg" = "X-lc" && continue
+	    ;;
+	  esac
+	elif test "X$arg" = "X-lc_r"; then
+	 case $host in
+	 *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*)
+	   # Do not include libc_r directly, use -pthread flag.
+	   continue
+	   ;;
+	 esac
+	fi
+	func_append deplibs " $arg"
+	continue
+	;;
+
+      -module)
+	module=yes
+	continue
+	;;
+
+      # Tru64 UNIX uses -model [arg] to determine the layout of C++
+      # classes, name mangling, and exception handling.
+      # Darwin uses the -arch flag to determine output architecture.
+      -model|-arch|-isysroot|--sysroot)
+	func_append compiler_flags " $arg"
+	func_append compile_command " $arg"
+	func_append finalize_command " $arg"
+	prev=xcompiler
+	continue
+	;;
+
+      -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \
+      |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*)
+	func_append compiler_flags " $arg"
+	func_append compile_command " $arg"
+	func_append finalize_command " $arg"
+	case "$new_inherited_linker_flags " in
+	    *" $arg "*) ;;
+	    * ) func_append new_inherited_linker_flags " $arg" ;;
+	esac
+	continue
+	;;
+
+      -multi_module)
+	single_module="${wl}-multi_module"
+	continue
+	;;
+
+      -no-fast-install)
+	fast_install=no
+	continue
+	;;
+
+      -no-install)
+	case $host in
+	*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-darwin* | *-cegcc*)
+	  # The PATH hackery in wrapper scripts is required on Windows
+	  # and Darwin in order for the loader to find any dlls it needs.
+	  func_warning "\`-no-install' is ignored for $host"
+	  func_warning "assuming \`-no-fast-install' instead"
+	  fast_install=no
+	  ;;
+	*) no_install=yes ;;
+	esac
+	continue
+	;;
+
+      -no-undefined)
+	allow_undefined=no
+	continue
+	;;
+
+      -objectlist)
+	prev=objectlist
+	continue
+	;;
+
+      -o) prev=output ;;
+
+      -precious-files-regex)
+	prev=precious_regex
+	continue
+	;;
+
+      -release)
+	prev=release
+	continue
+	;;
+
+      -rpath)
+	prev=rpath
+	continue
+	;;
+
+      -R)
+	prev=xrpath
+	continue
+	;;
+
+      -R*)
+	func_stripname '-R' '' "$arg"
+	dir=$func_stripname_result
+	# We need an absolute path.
+	case $dir in
+	[\\/]* | [A-Za-z]:[\\/]*) ;;
+	=*)
+	  func_stripname '=' '' "$dir"
+	  dir=$lt_sysroot$func_stripname_result
+	  ;;
+	*)
+	  func_fatal_error "only absolute run-paths are allowed"
+	  ;;
+	esac
+	case "$xrpath " in
+	*" $dir "*) ;;
+	*) func_append xrpath " $dir" ;;
+	esac
+	continue
+	;;
+
+      -shared)
+	# The effects of -shared are defined in a previous loop.
+	continue
+	;;
+
+      -shrext)
+	prev=shrext
+	continue
+	;;
+
+      -static | -static-libtool-libs)
+	# The effects of -static are defined in a previous loop.
+	# We used to do the same as -all-static on platforms that
+	# didn't have a PIC flag, but the assumption that the effects
+	# would be equivalent was wrong.  It would break on at least
+	# Digital Unix and AIX.
+	continue
+	;;
+
+      -thread-safe)
+	thread_safe=yes
+	continue
+	;;
+
+      -version-info)
+	prev=vinfo
+	continue
+	;;
+
+      -version-number)
+	prev=vinfo
+	vinfo_number=yes
+	continue
+	;;
+
+      -weak)
+        prev=weak
+	continue
+	;;
+
+      -Wc,*)
+	func_stripname '-Wc,' '' "$arg"
+	args=$func_stripname_result
+	arg=
+	save_ifs="$IFS"; IFS=','
+	for flag in $args; do
+	  IFS="$save_ifs"
+          func_quote_for_eval "$flag"
+	  func_append arg " $func_quote_for_eval_result"
+	  func_append compiler_flags " $func_quote_for_eval_result"
+	done
+	IFS="$save_ifs"
+	func_stripname ' ' '' "$arg"
+	arg=$func_stripname_result
+	;;
+
+      -Wl,*)
+	func_stripname '-Wl,' '' "$arg"
+	args=$func_stripname_result
+	arg=
+	save_ifs="$IFS"; IFS=','
+	for flag in $args; do
+	  IFS="$save_ifs"
+          func_quote_for_eval "$flag"
+	  func_append arg " $wl$func_quote_for_eval_result"
+	  func_append compiler_flags " $wl$func_quote_for_eval_result"
+	  func_append linker_flags " $func_quote_for_eval_result"
+	done
+	IFS="$save_ifs"
+	func_stripname ' ' '' "$arg"
+	arg=$func_stripname_result
+	;;
+
+      -Xcompiler)
+	prev=xcompiler
+	continue
+	;;
+
+      -Xlinker)
+	prev=xlinker
+	continue
+	;;
+
+      -XCClinker)
+	prev=xcclinker
+	continue
+	;;
+
+      # -msg_* for osf cc
+      -msg_*)
+	func_quote_for_eval "$arg"
+	arg="$func_quote_for_eval_result"
+	;;
+
+      # Flags to be passed through unchanged, with rationale:
+      # -64, -mips[0-9]      enable 64-bit mode for the SGI compiler
+      # -r[0-9][0-9]*        specify processor for the SGI compiler
+      # -xarch=*, -xtarget=* enable 64-bit mode for the Sun compiler
+      # +DA*, +DD*           enable 64-bit mode for the HP compiler
+      # -q*                  compiler args for the IBM compiler
+      # -m*, -t[45]*, -txscale* architecture-specific flags for GCC
+      # -F/path              path to uninstalled frameworks, gcc on darwin
+      # -p, -pg, --coverage, -fprofile-*  profiling flags for GCC
+      # @file                GCC response files
+      # -tp=*                Portland pgcc target processor selection
+      # --sysroot=*          for sysroot support
+      # -O*, -flto*, -fwhopr*, -fuse-linker-plugin GCC link-time optimization
+      -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \
+      -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \
+      -O*|-flto*|-fwhopr*|-fuse-linker-plugin)
+        func_quote_for_eval "$arg"
+	arg="$func_quote_for_eval_result"
+        func_append compile_command " $arg"
+        func_append finalize_command " $arg"
+        func_append compiler_flags " $arg"
+        continue
+        ;;
+
+      # Some other compiler flag.
+      -* | +*)
+        func_quote_for_eval "$arg"
+	arg="$func_quote_for_eval_result"
+	;;
+
+      *.$objext)
+	# A standard object.
+	func_append objs " $arg"
+	;;
+
+      *.lo)
+	# A libtool-controlled object.
+
+	# Check to see that this really is a libtool object.
+	if func_lalib_unsafe_p "$arg"; then
+	  pic_object=
+	  non_pic_object=
+
+	  # Read the .lo file
+	  func_source "$arg"
+
+	  if test -z "$pic_object" ||
+	     test -z "$non_pic_object" ||
+	     test "$pic_object" = none &&
+	     test "$non_pic_object" = none; then
+	    func_fatal_error "cannot find name of object for \`$arg'"
+	  fi
+
+	  # Extract subdirectory from the argument.
+	  func_dirname "$arg" "/" ""
+	  xdir="$func_dirname_result"
+
+	  if test "$pic_object" != none; then
+	    # Prepend the subdirectory the object is found in.
+	    pic_object="$xdir$pic_object"
+
+	    if test "$prev" = dlfiles; then
+	      if test "$build_libtool_libs" = yes && test "$dlopen_support" = yes; then
+		func_append dlfiles " $pic_object"
+		prev=
+		continue
+	      else
+		# If libtool objects are unsupported, then we need to preload.
+		prev=dlprefiles
+	      fi
+	    fi
+
+	    # CHECK ME:  I think I busted this.  -Ossama
+	    if test "$prev" = dlprefiles; then
+	      # Preload the old-style object.
+	      func_append dlprefiles " $pic_object"
+	      prev=
+	    fi
+
+	    # A PIC object.
+	    func_append libobjs " $pic_object"
+	    arg="$pic_object"
+	  fi
+
+	  # Non-PIC object.
+	  if test "$non_pic_object" != none; then
+	    # Prepend the subdirectory the object is found in.
+	    non_pic_object="$xdir$non_pic_object"
+
+	    # A standard non-PIC object
+	    func_append non_pic_objects " $non_pic_object"
+	    if test -z "$pic_object" || test "$pic_object" = none ; then
+	      arg="$non_pic_object"
+	    fi
+	  else
+	    # If the PIC object exists, use it instead.
+	    # $xdir was prepended to $pic_object above.
+	    non_pic_object="$pic_object"
+	    func_append non_pic_objects " $non_pic_object"
+	  fi
+	else
+	  # Only an error if not doing a dry-run.
+	  if $opt_dry_run; then
+	    # Extract subdirectory from the argument.
+	    func_dirname "$arg" "/" ""
+	    xdir="$func_dirname_result"
+
+	    func_lo2o "$arg"
+	    pic_object=$xdir$objdir/$func_lo2o_result
+	    non_pic_object=$xdir$func_lo2o_result
+	    func_append libobjs " $pic_object"
+	    func_append non_pic_objects " $non_pic_object"
+	  else
+	    func_fatal_error "\`$arg' is not a valid libtool object"
+	  fi
+	fi
+	;;
+
+      *.$libext)
+	# An archive.
+	func_append deplibs " $arg"
+	func_append old_deplibs " $arg"
+	continue
+	;;
+
+      *.la)
+	# A libtool-controlled library.
+
+	func_resolve_sysroot "$arg"
+	if test "$prev" = dlfiles; then
+	  # This library was specified with -dlopen.
+	  func_append dlfiles " $func_resolve_sysroot_result"
+	  prev=
+	elif test "$prev" = dlprefiles; then
+	  # The library was specified with -dlpreopen.
+	  func_append dlprefiles " $func_resolve_sysroot_result"
+	  prev=
+	else
+	  func_append deplibs " $func_resolve_sysroot_result"
+	fi
+	continue
+	;;
+
+      # Some other compiler argument.
+      *)
+	# Unknown arguments in both finalize_command and compile_command need
+	# to be aesthetically quoted because they are evaled later.
+	func_quote_for_eval "$arg"
+	arg="$func_quote_for_eval_result"
+	;;
+      esac # arg
+
+      # Now actually substitute the argument into the commands.
+      if test -n "$arg"; then
+	func_append compile_command " $arg"
+	func_append finalize_command " $arg"
+      fi
+    done # argument parsing loop
+
+    test -n "$prev" && \
+      func_fatal_help "the \`$prevarg' option requires an argument"
+
+    if test "$export_dynamic" = yes && test -n "$export_dynamic_flag_spec"; then
+      eval arg=\"$export_dynamic_flag_spec\"
+      func_append compile_command " $arg"
+      func_append finalize_command " $arg"
+    fi
+
+    oldlibs=
+    # calculate the name of the file, without its directory
+    func_basename "$output"
+    outputname="$func_basename_result"
+    libobjs_save="$libobjs"
+
+    if test -n "$shlibpath_var"; then
+      # get the directories listed in $shlibpath_var
+      eval shlib_search_path=\`\$ECHO \"\${$shlibpath_var}\" \| \$SED \'s/:/ /g\'\`
+    else
+      shlib_search_path=
+    fi
+    eval sys_lib_search_path=\"$sys_lib_search_path_spec\"
+    eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\"
+
+    func_dirname "$output" "/" ""
+    output_objdir="$func_dirname_result$objdir"
+    func_to_tool_file "$output_objdir/"
+    tool_output_objdir=$func_to_tool_file_result
+    # Create the object directory.
+    func_mkdir_p "$output_objdir"
+
+    # Determine the type of output
+    case $output in
+    "")
+      func_fatal_help "you must specify an output file"
+      ;;
+    *.$libext) linkmode=oldlib ;;
+    *.lo | *.$objext) linkmode=obj ;;
+    *.la) linkmode=lib ;;
+    *) linkmode=prog ;; # Anything else should be a program.
+    esac
+
+    specialdeplibs=
+
+    libs=
+    # Find all interdependent deplibs by searching for libraries
+    # that are linked more than once (e.g. -la -lb -la)
+    for deplib in $deplibs; do
+      if $opt_preserve_dup_deps ; then
+	case "$libs " in
+	*" $deplib "*) func_append specialdeplibs " $deplib" ;;
+	esac
+      fi
+      func_append libs " $deplib"
+    done
+
+    if test "$linkmode" = lib; then
+      libs="$predeps $libs $compiler_lib_search_path $postdeps"
+
+      # Compute libraries that are listed more than once in $predeps
+      # $postdeps and mark them as special (i.e., whose duplicates are
+      # not to be eliminated).
+      pre_post_deps=
+      if $opt_duplicate_compiler_generated_deps; then
+	for pre_post_dep in $predeps $postdeps; do
+	  case "$pre_post_deps " in
+	  *" $pre_post_dep "*) func_append specialdeplibs " $pre_post_deps" ;;
+	  esac
+	  func_append pre_post_deps " $pre_post_dep"
+	done
+      fi
+      pre_post_deps=
+    fi
+
+    deplibs=
+    newdependency_libs=
+    newlib_search_path=
+    need_relink=no # whether we're linking any uninstalled libtool libraries
+    notinst_deplibs= # not-installed libtool libraries
+    notinst_path= # paths that contain not-installed libtool libraries
+
+    case $linkmode in
+    lib)
+	passes="conv dlpreopen link"
+	for file in $dlfiles $dlprefiles; do
+	  case $file in
+	  *.la) ;;
+	  *)
+	    func_fatal_help "libraries can \`-dlopen' only libtool libraries: $file"
+	    ;;
+	  esac
+	done
+	;;
+    prog)
+	compile_deplibs=
+	finalize_deplibs=
+	alldeplibs=no
+	newdlfiles=
+	newdlprefiles=
+	passes="conv scan dlopen dlpreopen link"
+	;;
+    *)  passes="conv"
+	;;
+    esac
+
+    for pass in $passes; do
+      # The preopen pass in lib mode reverses $deplibs; put it back here
+      # so that -L comes before libs that need it for instance...
+      if test "$linkmode,$pass" = "lib,link"; then
+	## FIXME: Find the place where the list is rebuilt in the wrong
+	##        order, and fix it there properly
+        tmp_deplibs=
+	for deplib in $deplibs; do
+	  tmp_deplibs="$deplib $tmp_deplibs"
+	done
+	deplibs="$tmp_deplibs"
+      fi
+
+      if test "$linkmode,$pass" = "lib,link" ||
+	 test "$linkmode,$pass" = "prog,scan"; then
+	libs="$deplibs"
+	deplibs=
+      fi
+      if test "$linkmode" = prog; then
+	case $pass in
+	dlopen) libs="$dlfiles" ;;
+	dlpreopen) libs="$dlprefiles" ;;
+	link) libs="$deplibs %DEPLIBS% $dependency_libs" ;;
+	esac
+      fi
+      if test "$linkmode,$pass" = "lib,dlpreopen"; then
+	# Collect and forward deplibs of preopened libtool libs
+	for lib in $dlprefiles; do
+	  # Ignore non-libtool-libs
+	  dependency_libs=
+	  func_resolve_sysroot "$lib"
+	  case $lib in
+	  *.la)	func_source "$func_resolve_sysroot_result" ;;
+	  esac
+
+	  # Collect preopened libtool deplibs, except any this library
+	  # has declared as weak libs
+	  for deplib in $dependency_libs; do
+	    func_basename "$deplib"
+            deplib_base=$func_basename_result
+	    case " $weak_libs " in
+	    *" $deplib_base "*) ;;
+	    *) func_append deplibs " $deplib" ;;
+	    esac
+	  done
+	done
+	libs="$dlprefiles"
+      fi
+      if test "$pass" = dlopen; then
+	# Collect dlpreopened libraries
+	save_deplibs="$deplibs"
+	deplibs=
+      fi
+
+      for deplib in $libs; do
+	lib=
+	found=no
+	case $deplib in
+	-mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \
+        |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*)
+	  if test "$linkmode,$pass" = "prog,link"; then
+	    compile_deplibs="$deplib $compile_deplibs"
+	    finalize_deplibs="$deplib $finalize_deplibs"
+	  else
+	    func_append compiler_flags " $deplib"
+	    if test "$linkmode" = lib ; then
+		case "$new_inherited_linker_flags " in
+		    *" $deplib "*) ;;
+		    * ) func_append new_inherited_linker_flags " $deplib" ;;
+		esac
+	    fi
+	  fi
+	  continue
+	  ;;
+	-l*)
+	  if test "$linkmode" != lib && test "$linkmode" != prog; then
+	    func_warning "\`-l' is ignored for archives/objects"
+	    continue
+	  fi
+	  func_stripname '-l' '' "$deplib"
+	  name=$func_stripname_result
+	  if test "$linkmode" = lib; then
+	    searchdirs="$newlib_search_path $lib_search_path $compiler_lib_search_dirs $sys_lib_search_path $shlib_search_path"
+	  else
+	    searchdirs="$newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path"
+	  fi
+	  for searchdir in $searchdirs; do
+	    for search_ext in .la $std_shrext .so .a; do
+	      # Search the libtool library
+	      lib="$searchdir/lib${name}${search_ext}"
+	      if test -f "$lib"; then
+		if test "$search_ext" = ".la"; then
+		  found=yes
+		else
+		  found=no
+		fi
+		break 2
+	      fi
+	    done
+	  done
+	  if test "$found" != yes; then
+	    # deplib doesn't seem to be a libtool library
+	    if test "$linkmode,$pass" = "prog,link"; then
+	      compile_deplibs="$deplib $compile_deplibs"
+	      finalize_deplibs="$deplib $finalize_deplibs"
+	    else
+	      deplibs="$deplib $deplibs"
+	      test "$linkmode" = lib && newdependency_libs="$deplib $newdependency_libs"
+	    fi
+	    continue
+	  else # deplib is a libtool library
+	    # If $allow_libtool_libs_with_static_runtimes && $deplib is a stdlib,
+	    # We need to do some special things here, and not later.
+	    if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+	      case " $predeps $postdeps " in
+	      *" $deplib "*)
+		if func_lalib_p "$lib"; then
+		  library_names=
+		  old_library=
+		  func_source "$lib"
+		  for l in $old_library $library_names; do
+		    ll="$l"
+		  done
+		  if test "X$ll" = "X$old_library" ; then # only static version available
+		    found=no
+		    func_dirname "$lib" "" "."
+		    ladir="$func_dirname_result"
+		    lib=$ladir/$old_library
+		    if test "$linkmode,$pass" = "prog,link"; then
+		      compile_deplibs="$deplib $compile_deplibs"
+		      finalize_deplibs="$deplib $finalize_deplibs"
+		    else
+		      deplibs="$deplib $deplibs"
+		      test "$linkmode" = lib && newdependency_libs="$deplib $newdependency_libs"
+		    fi
+		    continue
+		  fi
+		fi
+		;;
+	      *) ;;
+	      esac
+	    fi
+	  fi
+	  ;; # -l
+	*.ltframework)
+	  if test "$linkmode,$pass" = "prog,link"; then
+	    compile_deplibs="$deplib $compile_deplibs"
+	    finalize_deplibs="$deplib $finalize_deplibs"
+	  else
+	    deplibs="$deplib $deplibs"
+	    if test "$linkmode" = lib ; then
+		case "$new_inherited_linker_flags " in
+		    *" $deplib "*) ;;
+		    * ) func_append new_inherited_linker_flags " $deplib" ;;
+		esac
+	    fi
+	  fi
+	  continue
+	  ;;
+	-L*)
+	  case $linkmode in
+	  lib)
+	    deplibs="$deplib $deplibs"
+	    test "$pass" = conv && continue
+	    newdependency_libs="$deplib $newdependency_libs"
+	    func_stripname '-L' '' "$deplib"
+	    func_resolve_sysroot "$func_stripname_result"
+	    func_append newlib_search_path " $func_resolve_sysroot_result"
+	    ;;
+	  prog)
+	    if test "$pass" = conv; then
+	      deplibs="$deplib $deplibs"
+	      continue
+	    fi
+	    if test "$pass" = scan; then
+	      deplibs="$deplib $deplibs"
+	    else
+	      compile_deplibs="$deplib $compile_deplibs"
+	      finalize_deplibs="$deplib $finalize_deplibs"
+	    fi
+	    func_stripname '-L' '' "$deplib"
+	    func_resolve_sysroot "$func_stripname_result"
+	    func_append newlib_search_path " $func_resolve_sysroot_result"
+	    ;;
+	  *)
+	    func_warning "\`-L' is ignored for archives/objects"
+	    ;;
+	  esac # linkmode
+	  continue
+	  ;; # -L
+	-R*)
+	  if test "$pass" = link; then
+	    func_stripname '-R' '' "$deplib"
+	    func_resolve_sysroot "$func_stripname_result"
+	    dir=$func_resolve_sysroot_result
+	    # Make sure the xrpath contains only unique directories.
+	    case "$xrpath " in
+	    *" $dir "*) ;;
+	    *) func_append xrpath " $dir" ;;
+	    esac
+	  fi
+	  deplibs="$deplib $deplibs"
+	  continue
+	  ;;
+	*.la)
+	  func_resolve_sysroot "$deplib"
+	  lib=$func_resolve_sysroot_result
+	  ;;
+	*.$libext)
+	  if test "$pass" = conv; then
+	    deplibs="$deplib $deplibs"
+	    continue
+	  fi
+	  case $linkmode in
+	  lib)
+	    # Linking convenience modules into shared libraries is allowed,
+	    # but linking other static libraries is non-portable.
+	    case " $dlpreconveniencelibs " in
+	    *" $deplib "*) ;;
+	    *)
+	      valid_a_lib=no
+	      case $deplibs_check_method in
+		match_pattern*)
+		  set dummy $deplibs_check_method; shift
+		  match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"`
+		  if eval "\$ECHO \"$deplib\"" 2>/dev/null | $SED 10q \
+		    | $EGREP "$match_pattern_regex" > /dev/null; then
+		    valid_a_lib=yes
+		  fi
+		;;
+		pass_all)
+		  valid_a_lib=yes
+		;;
+	      esac
+	      if test "$valid_a_lib" != yes; then
+		echo
+		$ECHO "*** Warning: Trying to link with static lib archive $deplib."
+		echo "*** I have the capability to make that library automatically link in when"
+		echo "*** you link to this library.  But I can only do this if you have a"
+		echo "*** shared version of the library, which you do not appear to have"
+		echo "*** because the file extensions .$libext of this argument makes me believe"
+		echo "*** that it is just a static archive that I should not use here."
+	      else
+		echo
+		$ECHO "*** Warning: Linking the shared library $output against the"
+		$ECHO "*** static library $deplib is not portable!"
+		deplibs="$deplib $deplibs"
+	      fi
+	      ;;
+	    esac
+	    continue
+	    ;;
+	  prog)
+	    if test "$pass" != link; then
+	      deplibs="$deplib $deplibs"
+	    else
+	      compile_deplibs="$deplib $compile_deplibs"
+	      finalize_deplibs="$deplib $finalize_deplibs"
+	    fi
+	    continue
+	    ;;
+	  esac # linkmode
+	  ;; # *.$libext
+	*.lo | *.$objext)
+	  if test "$pass" = conv; then
+	    deplibs="$deplib $deplibs"
+	  elif test "$linkmode" = prog; then
+	    if test "$pass" = dlpreopen || test "$dlopen_support" != yes || test "$build_libtool_libs" = no; then
+	      # If there is no dlopen support or we're linking statically,
+	      # we need to preload.
+	      func_append newdlprefiles " $deplib"
+	      compile_deplibs="$deplib $compile_deplibs"
+	      finalize_deplibs="$deplib $finalize_deplibs"
+	    else
+	      func_append newdlfiles " $deplib"
+	    fi
+	  fi
+	  continue
+	  ;;
+	%DEPLIBS%)
+	  alldeplibs=yes
+	  continue
+	  ;;
+	esac # case $deplib
+
+	if test "$found" = yes || test -f "$lib"; then :
+	else
+	  func_fatal_error "cannot find the library \`$lib' or unhandled argument \`$deplib'"
+	fi
+
+	# Check to see that this really is a libtool archive.
+	func_lalib_unsafe_p "$lib" \
+	  || func_fatal_error "\`$lib' is not a valid libtool archive"
+
+	func_dirname "$lib" "" "."
+	ladir="$func_dirname_result"
+
+	dlname=
+	dlopen=
+	dlpreopen=
+	libdir=
+	library_names=
+	old_library=
+	inherited_linker_flags=
+	# If the library was installed with an old release of libtool,
+	# it will not redefine variables installed, or shouldnotlink
+	installed=yes
+	shouldnotlink=no
+	avoidtemprpath=
+
+
+	# Read the .la file
+	func_source "$lib"
+
+	# Convert "-framework foo" to "foo.ltframework"
+	if test -n "$inherited_linker_flags"; then
+	  tmp_inherited_linker_flags=`$ECHO "$inherited_linker_flags" | $SED 's/-framework \([^ $]*\)/\1.ltframework/g'`
+	  for tmp_inherited_linker_flag in $tmp_inherited_linker_flags; do
+	    case " $new_inherited_linker_flags " in
+	      *" $tmp_inherited_linker_flag "*) ;;
+	      *) func_append new_inherited_linker_flags " $tmp_inherited_linker_flag";;
+	    esac
+	  done
+	fi
+	dependency_libs=`$ECHO " $dependency_libs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+	if test "$linkmode,$pass" = "lib,link" ||
+	   test "$linkmode,$pass" = "prog,scan" ||
+	   { test "$linkmode" != prog && test "$linkmode" != lib; }; then
+	  test -n "$dlopen" && func_append dlfiles " $dlopen"
+	  test -n "$dlpreopen" && func_append dlprefiles " $dlpreopen"
+	fi
+
+	if test "$pass" = conv; then
+	  # Only check for convenience libraries
+	  deplibs="$lib $deplibs"
+	  if test -z "$libdir"; then
+	    if test -z "$old_library"; then
+	      func_fatal_error "cannot find name of link library for \`$lib'"
+	    fi
+	    # It is a libtool convenience library, so add in its objects.
+	    func_append convenience " $ladir/$objdir/$old_library"
+	    func_append old_convenience " $ladir/$objdir/$old_library"
+	  elif test "$linkmode" != prog && test "$linkmode" != lib; then
+	    func_fatal_error "\`$lib' is not a convenience library"
+	  fi
+	  tmp_libs=
+	  for deplib in $dependency_libs; do
+	    deplibs="$deplib $deplibs"
+	    if $opt_preserve_dup_deps ; then
+	      case "$tmp_libs " in
+	      *" $deplib "*) func_append specialdeplibs " $deplib" ;;
+	      esac
+	    fi
+	    func_append tmp_libs " $deplib"
+	  done
+	  continue
+	fi # $pass = conv
+
+
+	# Get the name of the library we link against.
+	linklib=
+	if test -n "$old_library" &&
+	   { test "$prefer_static_libs" = yes ||
+	     test "$prefer_static_libs,$installed" = "built,no"; }; then
+	  linklib=$old_library
+	else
+	  for l in $old_library $library_names; do
+	    linklib="$l"
+	  done
+	fi
+	if test -z "$linklib"; then
+	  func_fatal_error "cannot find name of link library for \`$lib'"
+	fi
+
+	# This library was specified with -dlopen.
+	if test "$pass" = dlopen; then
+	  if test -z "$libdir"; then
+	    func_fatal_error "cannot -dlopen a convenience library: \`$lib'"
+	  fi
+	  if test -z "$dlname" ||
+	     test "$dlopen_support" != yes ||
+	     test "$build_libtool_libs" = no; then
+	    # If there is no dlname, no dlopen support or we're linking
+	    # statically, we need to preload.  We also need to preload any
+	    # dependent libraries so libltdl's deplib preloader doesn't
+	    # bomb out in the load deplibs phase.
+	    func_append dlprefiles " $lib $dependency_libs"
+	  else
+	    func_append newdlfiles " $lib"
+	  fi
+	  continue
+	fi # $pass = dlopen
+
+	# We need an absolute path.
+	case $ladir in
+	[\\/]* | [A-Za-z]:[\\/]*) abs_ladir="$ladir" ;;
+	*)
+	  abs_ladir=`cd "$ladir" && pwd`
+	  if test -z "$abs_ladir"; then
+	    func_warning "cannot determine absolute directory name of \`$ladir'"
+	    func_warning "passing it literally to the linker, although it might fail"
+	    abs_ladir="$ladir"
+	  fi
+	  ;;
+	esac
+	func_basename "$lib"
+	laname="$func_basename_result"
+
+	# Find the relevant object directory and library name.
+	if test "X$installed" = Xyes; then
+	  if test ! -f "$lt_sysroot$libdir/$linklib" && test -f "$abs_ladir/$linklib"; then
+	    func_warning "library \`$lib' was moved."
+	    dir="$ladir"
+	    absdir="$abs_ladir"
+	    libdir="$abs_ladir"
+	  else
+	    dir="$lt_sysroot$libdir"
+	    absdir="$lt_sysroot$libdir"
+	  fi
+	  test "X$hardcode_automatic" = Xyes && avoidtemprpath=yes
+	else
+	  if test ! -f "$ladir/$objdir/$linklib" && test -f "$abs_ladir/$linklib"; then
+	    dir="$ladir"
+	    absdir="$abs_ladir"
+	    # Remove this search path later
+	    func_append notinst_path " $abs_ladir"
+	  else
+	    dir="$ladir/$objdir"
+	    absdir="$abs_ladir/$objdir"
+	    # Remove this search path later
+	    func_append notinst_path " $abs_ladir"
+	  fi
+	fi # $installed = yes
+	func_stripname 'lib' '.la' "$laname"
+	name=$func_stripname_result
+
+	# This library was specified with -dlpreopen.
+	if test "$pass" = dlpreopen; then
+	  if test -z "$libdir" && test "$linkmode" = prog; then
+	    func_fatal_error "only libraries may -dlpreopen a convenience library: \`$lib'"
+	  fi
+	  case "$host" in
+	    # special handling for platforms with PE-DLLs.
+	    *cygwin* | *mingw* | *cegcc* )
+	      # Linker will automatically link against shared library if both
+	      # static and shared are present.  Therefore, ensure we extract
+	      # symbols from the import library if a shared library is present
+	      # (otherwise, the dlopen module name will be incorrect).  We do
+	      # this by putting the import library name into $newdlprefiles.
+	      # We recover the dlopen module name by 'saving' the la file
+	      # name in a special purpose variable, and (later) extracting the
+	      # dlname from the la file.
+	      if test -n "$dlname"; then
+	        func_tr_sh "$dir/$linklib"
+	        eval "libfile_$func_tr_sh_result=\$abs_ladir/\$laname"
+	        func_append newdlprefiles " $dir/$linklib"
+	      else
+	        func_append newdlprefiles " $dir/$old_library"
+	        # Keep a list of preopened convenience libraries to check
+	        # that they are being used correctly in the link pass.
+	        test -z "$libdir" && \
+	          func_append dlpreconveniencelibs " $dir/$old_library"
+	      fi
+	    ;;
+	    * )
+	      # Prefer using a static library (so that no silly _DYNAMIC symbols
+	      # are required to link).
+	      if test -n "$old_library"; then
+	        func_append newdlprefiles " $dir/$old_library"
+	        # Keep a list of preopened convenience libraries to check
+	        # that they are being used correctly in the link pass.
+	        test -z "$libdir" && \
+	          func_append dlpreconveniencelibs " $dir/$old_library"
+	      # Otherwise, use the dlname, so that lt_dlopen finds it.
+	      elif test -n "$dlname"; then
+	        func_append newdlprefiles " $dir/$dlname"
+	      else
+	        func_append newdlprefiles " $dir/$linklib"
+	      fi
+	    ;;
+	  esac
+	fi # $pass = dlpreopen
+
+	if test -z "$libdir"; then
+	  # Link the convenience library
+	  if test "$linkmode" = lib; then
+	    deplibs="$dir/$old_library $deplibs"
+	  elif test "$linkmode,$pass" = "prog,link"; then
+	    compile_deplibs="$dir/$old_library $compile_deplibs"
+	    finalize_deplibs="$dir/$old_library $finalize_deplibs"
+	  else
+	    deplibs="$lib $deplibs" # used for prog,scan pass
+	  fi
+	  continue
+	fi
+
+
+	if test "$linkmode" = prog && test "$pass" != link; then
+	  func_append newlib_search_path " $ladir"
+	  deplibs="$lib $deplibs"
+
+	  linkalldeplibs=no
+	  if test "$link_all_deplibs" != no || test -z "$library_names" ||
+	     test "$build_libtool_libs" = no; then
+	    linkalldeplibs=yes
+	  fi
+
+	  tmp_libs=
+	  for deplib in $dependency_libs; do
+	    case $deplib in
+	    -L*) func_stripname '-L' '' "$deplib"
+	         func_resolve_sysroot "$func_stripname_result"
+	         func_append newlib_search_path " $func_resolve_sysroot_result"
+		 ;;
+	    esac
+	    # Need to link against all dependency_libs?
+	    if test "$linkalldeplibs" = yes; then
+	      deplibs="$deplib $deplibs"
+	    else
+	      # Need to hardcode shared library paths
+	      # or/and link against static libraries
+	      newdependency_libs="$deplib $newdependency_libs"
+	    fi
+	    if $opt_preserve_dup_deps ; then
+	      case "$tmp_libs " in
+	      *" $deplib "*) func_append specialdeplibs " $deplib" ;;
+	      esac
+	    fi
+	    func_append tmp_libs " $deplib"
+	  done # for deplib
+	  continue
+	fi # $linkmode = prog...
+
+	if test "$linkmode,$pass" = "prog,link"; then
+	  if test -n "$library_names" &&
+	     { { test "$prefer_static_libs" = no ||
+	         test "$prefer_static_libs,$installed" = "built,yes"; } ||
+	       test -z "$old_library"; }; then
+	    # We need to hardcode the library path
+	    if test -n "$shlibpath_var" && test -z "$avoidtemprpath" ; then
+	      # Make sure the rpath contains only unique directories.
+	      case "$temp_rpath:" in
+	      *"$absdir:"*) ;;
+	      *) func_append temp_rpath "$absdir:" ;;
+	      esac
+	    fi
+
+	    # Hardcode the library path.
+	    # Skip directories that are in the system default run-time
+	    # search path.
+	    case " $sys_lib_dlsearch_path " in
+	    *" $absdir "*) ;;
+	    *)
+	      case "$compile_rpath " in
+	      *" $absdir "*) ;;
+	      *) func_append compile_rpath " $absdir" ;;
+	      esac
+	      ;;
+	    esac
+	    case " $sys_lib_dlsearch_path " in
+	    *" $libdir "*) ;;
+	    *)
+	      case "$finalize_rpath " in
+	      *" $libdir "*) ;;
+	      *) func_append finalize_rpath " $libdir" ;;
+	      esac
+	      ;;
+	    esac
+	  fi # $linkmode,$pass = prog,link...
+
+	  if test "$alldeplibs" = yes &&
+	     { test "$deplibs_check_method" = pass_all ||
+	       { test "$build_libtool_libs" = yes &&
+		 test -n "$library_names"; }; }; then
+	    # We only need to search for static libraries
+	    continue
+	  fi
+	fi
+
+	link_static=no # Whether the deplib will be linked statically
+	use_static_libs=$prefer_static_libs
+	if test "$use_static_libs" = built && test "$installed" = yes; then
+	  use_static_libs=no
+	fi
+	if test -n "$library_names" &&
+	   { test "$use_static_libs" = no || test -z "$old_library"; }; then
+	  case $host in
+	  *cygwin* | *mingw* | *cegcc*)
+	      # No point in relinking DLLs because paths are not encoded
+	      func_append notinst_deplibs " $lib"
+	      need_relink=no
+	    ;;
+	  *)
+	    if test "$installed" = no; then
+	      func_append notinst_deplibs " $lib"
+	      need_relink=yes
+	    fi
+	    ;;
+	  esac
+	  # This is a shared library
+
+	  # Warn about portability, can't link against -module's on some
+	  # systems (darwin).  Don't bleat about dlopened modules though!
+	  dlopenmodule=""
+	  for dlpremoduletest in $dlprefiles; do
+	    if test "X$dlpremoduletest" = "X$lib"; then
+	      dlopenmodule="$dlpremoduletest"
+	      break
+	    fi
+	  done
+	  if test -z "$dlopenmodule" && test "$shouldnotlink" = yes && test "$pass" = link; then
+	    echo
+	    if test "$linkmode" = prog; then
+	      $ECHO "*** Warning: Linking the executable $output against the loadable module"
+	    else
+	      $ECHO "*** Warning: Linking the shared library $output against the loadable module"
+	    fi
+	    $ECHO "*** $linklib is not portable!"
+	  fi
+	  if test "$linkmode" = lib &&
+	     test "$hardcode_into_libs" = yes; then
+	    # Hardcode the library path.
+	    # Skip directories that are in the system default run-time
+	    # search path.
+	    case " $sys_lib_dlsearch_path " in
+	    *" $absdir "*) ;;
+	    *)
+	      case "$compile_rpath " in
+	      *" $absdir "*) ;;
+	      *) func_append compile_rpath " $absdir" ;;
+	      esac
+	      ;;
+	    esac
+	    case " $sys_lib_dlsearch_path " in
+	    *" $libdir "*) ;;
+	    *)
+	      case "$finalize_rpath " in
+	      *" $libdir "*) ;;
+	      *) func_append finalize_rpath " $libdir" ;;
+	      esac
+	      ;;
+	    esac
+	  fi
+
+	  if test -n "$old_archive_from_expsyms_cmds"; then
+	    # figure out the soname
+	    set dummy $library_names
+	    shift
+	    realname="$1"
+	    shift
+	    libname=`eval "\\$ECHO \"$libname_spec\""`
+	    # use dlname if we got it. it's perfectly good, no?
+	    if test -n "$dlname"; then
+	      soname="$dlname"
+	    elif test -n "$soname_spec"; then
+	      # bleh windows
+	      case $host in
+	      *cygwin* | mingw* | *cegcc*)
+	        func_arith $current - $age
+		major=$func_arith_result
+		versuffix="-$major"
+		;;
+	      esac
+	      eval soname=\"$soname_spec\"
+	    else
+	      soname="$realname"
+	    fi
+
+	    # Make a new name for the extract_expsyms_cmds to use
+	    soroot="$soname"
+	    func_basename "$soroot"
+	    soname="$func_basename_result"
+	    func_stripname 'lib' '.dll' "$soname"
+	    newlib=libimp-$func_stripname_result.a
+
+	    # If the library has no export list, then create one now
+	    if test -f "$output_objdir/$soname-def"; then :
+	    else
+	      func_verbose "extracting exported symbol list from \`$soname'"
+	      func_execute_cmds "$extract_expsyms_cmds" 'exit $?'
+	    fi
+
+	    # Create $newlib
+	    if test -f "$output_objdir/$newlib"; then :; else
+	      func_verbose "generating import library for \`$soname'"
+	      func_execute_cmds "$old_archive_from_expsyms_cmds" 'exit $?'
+	    fi
+	    # make sure the library variables are pointing to the new library
+	    dir=$output_objdir
+	    linklib=$newlib
+	  fi # test -n "$old_archive_from_expsyms_cmds"
+
+	  if test "$linkmode" = prog || test "$opt_mode" != relink; then
+	    add_shlibpath=
+	    add_dir=
+	    add=
+	    lib_linked=yes
+	    case $hardcode_action in
+	    immediate | unsupported)
+	      if test "$hardcode_direct" = no; then
+		add="$dir/$linklib"
+		case $host in
+		  *-*-sco3.2v5.0.[024]*) add_dir="-L$dir" ;;
+		  *-*-sysv4*uw2*) add_dir="-L$dir" ;;
+		  *-*-sysv5OpenUNIX* | *-*-sysv5UnixWare7.[01].[10]* | \
+		    *-*-unixware7*) add_dir="-L$dir" ;;
+		  *-*-darwin* )
+		    # if the lib is a (non-dlopened) module then we can not
+		    # link against it, someone is ignoring the earlier warnings
+		    if /usr/bin/file -L $add 2> /dev/null |
+			 $GREP ": [^:]* bundle" >/dev/null ; then
+		      if test "X$dlopenmodule" != "X$lib"; then
+			$ECHO "*** Warning: lib $linklib is a module, not a shared library"
+			if test -z "$old_library" ; then
+			  echo
+			  echo "*** And there doesn't seem to be a static archive available"
+			  echo "*** The link will probably fail, sorry"
+			else
+			  add="$dir/$old_library"
+			fi
+		      elif test -n "$old_library"; then
+			add="$dir/$old_library"
+		      fi
+		    fi
+		esac
+	      elif test "$hardcode_minus_L" = no; then
+		case $host in
+		*-*-sunos*) add_shlibpath="$dir" ;;
+		esac
+		add_dir="-L$dir"
+		add="-l$name"
+	      elif test "$hardcode_shlibpath_var" = no; then
+		add_shlibpath="$dir"
+		add="-l$name"
+	      else
+		lib_linked=no
+	      fi
+	      ;;
+	    relink)
+	      if test "$hardcode_direct" = yes &&
+	         test "$hardcode_direct_absolute" = no; then
+		add="$dir/$linklib"
+	      elif test "$hardcode_minus_L" = yes; then
+		add_dir="-L$absdir"
+		# Try looking first in the location we're being installed to.
+		if test -n "$inst_prefix_dir"; then
+		  case $libdir in
+		    [\\/]*)
+		      func_append add_dir " -L$inst_prefix_dir$libdir"
+		      ;;
+		  esac
+		fi
+		add="-l$name"
+	      elif test "$hardcode_shlibpath_var" = yes; then
+		add_shlibpath="$dir"
+		add="-l$name"
+	      else
+		lib_linked=no
+	      fi
+	      ;;
+	    *) lib_linked=no ;;
+	    esac
+
+	    if test "$lib_linked" != yes; then
+	      func_fatal_configuration "unsupported hardcode properties"
+	    fi
+
+	    if test -n "$add_shlibpath"; then
+	      case :$compile_shlibpath: in
+	      *":$add_shlibpath:"*) ;;
+	      *) func_append compile_shlibpath "$add_shlibpath:" ;;
+	      esac
+	    fi
+	    if test "$linkmode" = prog; then
+	      test -n "$add_dir" && compile_deplibs="$add_dir $compile_deplibs"
+	      test -n "$add" && compile_deplibs="$add $compile_deplibs"
+	    else
+	      test -n "$add_dir" && deplibs="$add_dir $deplibs"
+	      test -n "$add" && deplibs="$add $deplibs"
+	      if test "$hardcode_direct" != yes &&
+		 test "$hardcode_minus_L" != yes &&
+		 test "$hardcode_shlibpath_var" = yes; then
+		case :$finalize_shlibpath: in
+		*":$libdir:"*) ;;
+		*) func_append finalize_shlibpath "$libdir:" ;;
+		esac
+	      fi
+	    fi
+	  fi
+
+	  if test "$linkmode" = prog || test "$opt_mode" = relink; then
+	    add_shlibpath=
+	    add_dir=
+	    add=
+	    # Finalize command for both is simple: just hardcode it.
+	    if test "$hardcode_direct" = yes &&
+	       test "$hardcode_direct_absolute" = no; then
+	      add="$libdir/$linklib"
+	    elif test "$hardcode_minus_L" = yes; then
+	      add_dir="-L$libdir"
+	      add="-l$name"
+	    elif test "$hardcode_shlibpath_var" = yes; then
+	      case :$finalize_shlibpath: in
+	      *":$libdir:"*) ;;
+	      *) func_append finalize_shlibpath "$libdir:" ;;
+	      esac
+	      add="-l$name"
+	    elif test "$hardcode_automatic" = yes; then
+	      if test -n "$inst_prefix_dir" &&
+		 test -f "$inst_prefix_dir$libdir/$linklib" ; then
+		add="$inst_prefix_dir$libdir/$linklib"
+	      else
+		add="$libdir/$linklib"
+	      fi
+	    else
+	      # We cannot seem to hardcode it, guess we'll fake it.
+	      add_dir="-L$libdir"
+	      # Try looking first in the location we're being installed to.
+	      if test -n "$inst_prefix_dir"; then
+		case $libdir in
+		  [\\/]*)
+		    func_append add_dir " -L$inst_prefix_dir$libdir"
+		    ;;
+		esac
+	      fi
+	      add="-l$name"
+	    fi
+
+	    if test "$linkmode" = prog; then
+	      test -n "$add_dir" && finalize_deplibs="$add_dir $finalize_deplibs"
+	      test -n "$add" && finalize_deplibs="$add $finalize_deplibs"
+	    else
+	      test -n "$add_dir" && deplibs="$add_dir $deplibs"
+	      test -n "$add" && deplibs="$add $deplibs"
+	    fi
+	  fi
+	elif test "$linkmode" = prog; then
+	  # Here we assume that one of hardcode_direct or hardcode_minus_L
+	  # is not unsupported.  This is valid on all known static and
+	  # shared platforms.
+	  if test "$hardcode_direct" != unsupported; then
+	    test -n "$old_library" && linklib="$old_library"
+	    compile_deplibs="$dir/$linklib $compile_deplibs"
+	    finalize_deplibs="$dir/$linklib $finalize_deplibs"
+	  else
+	    compile_deplibs="-l$name -L$dir $compile_deplibs"
+	    finalize_deplibs="-l$name -L$dir $finalize_deplibs"
+	  fi
+	elif test "$build_libtool_libs" = yes; then
+	  # Not a shared library
+	  if test "$deplibs_check_method" != pass_all; then
+	    # We're trying link a shared library against a static one
+	    # but the system doesn't support it.
+
+	    # Just print a warning and add the library to dependency_libs so
+	    # that the program can be linked against the static library.
+	    echo
+	    $ECHO "*** Warning: This system can not link to static lib archive $lib."
+	    echo "*** I have the capability to make that library automatically link in when"
+	    echo "*** you link to this library.  But I can only do this if you have a"
+	    echo "*** shared version of the library, which you do not appear to have."
+	    if test "$module" = yes; then
+	      echo "*** But as you try to build a module library, libtool will still create "
+	      echo "*** a static module, that should work as long as the dlopening application"
+	      echo "*** is linked with the -dlopen flag to resolve symbols at runtime."
+	      if test -z "$global_symbol_pipe"; then
+		echo
+		echo "*** However, this would only work if libtool was able to extract symbol"
+		echo "*** lists from a program, using \`nm' or equivalent, but libtool could"
+		echo "*** not find such a program.  So, this module is probably useless."
+		echo "*** \`nm' from GNU binutils and a full rebuild may help."
+	      fi
+	      if test "$build_old_libs" = no; then
+		build_libtool_libs=module
+		build_old_libs=yes
+	      else
+		build_libtool_libs=no
+	      fi
+	    fi
+	  else
+	    deplibs="$dir/$old_library $deplibs"
+	    link_static=yes
+	  fi
+	fi # link shared/static library?
+
+	if test "$linkmode" = lib; then
+	  if test -n "$dependency_libs" &&
+	     { test "$hardcode_into_libs" != yes ||
+	       test "$build_old_libs" = yes ||
+	       test "$link_static" = yes; }; then
+	    # Extract -R from dependency_libs
+	    temp_deplibs=
+	    for libdir in $dependency_libs; do
+	      case $libdir in
+	      -R*) func_stripname '-R' '' "$libdir"
+	           temp_xrpath=$func_stripname_result
+		   case " $xrpath " in
+		   *" $temp_xrpath "*) ;;
+		   *) func_append xrpath " $temp_xrpath";;
+		   esac;;
+	      *) func_append temp_deplibs " $libdir";;
+	      esac
+	    done
+	    dependency_libs="$temp_deplibs"
+	  fi
+
+	  func_append newlib_search_path " $absdir"
+	  # Link against this library
+	  test "$link_static" = no && newdependency_libs="$abs_ladir/$laname $newdependency_libs"
+	  # ... and its dependency_libs
+	  tmp_libs=
+	  for deplib in $dependency_libs; do
+	    newdependency_libs="$deplib $newdependency_libs"
+	    case $deplib in
+              -L*) func_stripname '-L' '' "$deplib"
+                   func_resolve_sysroot "$func_stripname_result";;
+              *) func_resolve_sysroot "$deplib" ;;
+            esac
+	    if $opt_preserve_dup_deps ; then
+	      case "$tmp_libs " in
+	      *" $func_resolve_sysroot_result "*)
+                func_append specialdeplibs " $func_resolve_sysroot_result" ;;
+	      esac
+	    fi
+	    func_append tmp_libs " $func_resolve_sysroot_result"
+	  done
+
+	  if test "$link_all_deplibs" != no; then
+	    # Add the search paths of all dependency libraries
+	    for deplib in $dependency_libs; do
+	      path=
+	      case $deplib in
+	      -L*) path="$deplib" ;;
+	      *.la)
+	        func_resolve_sysroot "$deplib"
+	        deplib=$func_resolve_sysroot_result
+	        func_dirname "$deplib" "" "."
+		dir=$func_dirname_result
+		# We need an absolute path.
+		case $dir in
+		[\\/]* | [A-Za-z]:[\\/]*) absdir="$dir" ;;
+		*)
+		  absdir=`cd "$dir" && pwd`
+		  if test -z "$absdir"; then
+		    func_warning "cannot determine absolute directory name of \`$dir'"
+		    absdir="$dir"
+		  fi
+		  ;;
+		esac
+		if $GREP "^installed=no" $deplib > /dev/null; then
+		case $host in
+		*-*-darwin*)
+		  depdepl=
+		  eval deplibrary_names=`${SED} -n -e 's/^library_names=\(.*\)$/\1/p' $deplib`
+		  if test -n "$deplibrary_names" ; then
+		    for tmp in $deplibrary_names ; do
+		      depdepl=$tmp
+		    done
+		    if test -f "$absdir/$objdir/$depdepl" ; then
+		      depdepl="$absdir/$objdir/$depdepl"
+		      darwin_install_name=`${OTOOL} -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'`
+                      if test -z "$darwin_install_name"; then
+                          darwin_install_name=`${OTOOL64} -L $depdepl  | awk '{if (NR == 2) {print $1;exit}}'`
+                      fi
+		      func_append compiler_flags " ${wl}-dylib_file ${wl}${darwin_install_name}:${depdepl}"
+		      func_append linker_flags " -dylib_file ${darwin_install_name}:${depdepl}"
+		      path=
+		    fi
+		  fi
+		  ;;
+		*)
+		  path="-L$absdir/$objdir"
+		  ;;
+		esac
+		else
+		  eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $deplib`
+		  test -z "$libdir" && \
+		    func_fatal_error "\`$deplib' is not a valid libtool archive"
+		  test "$absdir" != "$libdir" && \
+		    func_warning "\`$deplib' seems to be moved"
+
+		  path="-L$absdir"
+		fi
+		;;
+	      esac
+	      case " $deplibs " in
+	      *" $path "*) ;;
+	      *) deplibs="$path $deplibs" ;;
+	      esac
+	    done
+	  fi # link_all_deplibs != no
+	fi # linkmode = lib
+      done # for deplib in $libs
+      if test "$pass" = link; then
+	if test "$linkmode" = "prog"; then
+	  compile_deplibs="$new_inherited_linker_flags $compile_deplibs"
+	  finalize_deplibs="$new_inherited_linker_flags $finalize_deplibs"
+	else
+	  compiler_flags="$compiler_flags "`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+	fi
+      fi
+      dependency_libs="$newdependency_libs"
+      if test "$pass" = dlpreopen; then
+	# Link the dlpreopened libraries before other libraries
+	for deplib in $save_deplibs; do
+	  deplibs="$deplib $deplibs"
+	done
+      fi
+      if test "$pass" != dlopen; then
+	if test "$pass" != conv; then
+	  # Make sure lib_search_path contains only unique directories.
+	  lib_search_path=
+	  for dir in $newlib_search_path; do
+	    case "$lib_search_path " in
+	    *" $dir "*) ;;
+	    *) func_append lib_search_path " $dir" ;;
+	    esac
+	  done
+	  newlib_search_path=
+	fi
+
+	if test "$linkmode,$pass" != "prog,link"; then
+	  vars="deplibs"
+	else
+	  vars="compile_deplibs finalize_deplibs"
+	fi
+	for var in $vars dependency_libs; do
+	  # Add libraries to $var in reverse order
+	  eval tmp_libs=\"\$$var\"
+	  new_libs=
+	  for deplib in $tmp_libs; do
+	    # FIXME: Pedantically, this is the right thing to do, so
+	    #        that some nasty dependency loop isn't accidentally
+	    #        broken:
+	    #new_libs="$deplib $new_libs"
+	    # Pragmatically, this seems to cause very few problems in
+	    # practice:
+	    case $deplib in
+	    -L*) new_libs="$deplib $new_libs" ;;
+	    -R*) ;;
+	    *)
+	      # And here is the reason: when a library appears more
+	      # than once as an explicit dependence of a library, or
+	      # is implicitly linked in more than once by the
+	      # compiler, it is considered special, and multiple
+	      # occurrences thereof are not removed.  Compare this
+	      # with having the same library being listed as a
+	      # dependency of multiple other libraries: in this case,
+	      # we know (pedantically, we assume) the library does not
+	      # need to be listed more than once, so we keep only the
+	      # last copy.  This is not always right, but it is rare
+	      # enough that we require users that really mean to play
+	      # such unportable linking tricks to link the library
+	      # using -Wl,-lname, so that libtool does not consider it
+	      # for duplicate removal.
+	      case " $specialdeplibs " in
+	      *" $deplib "*) new_libs="$deplib $new_libs" ;;
+	      *)
+		case " $new_libs " in
+		*" $deplib "*) ;;
+		*) new_libs="$deplib $new_libs" ;;
+		esac
+		;;
+	      esac
+	      ;;
+	    esac
+	  done
+	  tmp_libs=
+	  for deplib in $new_libs; do
+	    case $deplib in
+	    -L*)
+	      case " $tmp_libs " in
+	      *" $deplib "*) ;;
+	      *) func_append tmp_libs " $deplib" ;;
+	      esac
+	      ;;
+	    *) func_append tmp_libs " $deplib" ;;
+	    esac
+	  done
+	  eval $var=\"$tmp_libs\"
+	done # for var
+      fi
+      # Last step: remove runtime libs from dependency_libs
+      # (they stay in deplibs)
+      tmp_libs=
+      for i in $dependency_libs ; do
+	case " $predeps $postdeps $compiler_lib_search_path " in
+	*" $i "*)
+	  i=""
+	  ;;
+	esac
+	if test -n "$i" ; then
+	  func_append tmp_libs " $i"
+	fi
+      done
+      dependency_libs=$tmp_libs
+    done # for pass
+    if test "$linkmode" = prog; then
+      dlfiles="$newdlfiles"
+    fi
+    if test "$linkmode" = prog || test "$linkmode" = lib; then
+      dlprefiles="$newdlprefiles"
+    fi
+
+    case $linkmode in
+    oldlib)
+      if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
+	func_warning "\`-dlopen' is ignored for archives"
+      fi
+
+      case " $deplibs" in
+      *\ -l* | *\ -L*)
+	func_warning "\`-l' and \`-L' are ignored for archives" ;;
+      esac
+
+      test -n "$rpath" && \
+	func_warning "\`-rpath' is ignored for archives"
+
+      test -n "$xrpath" && \
+	func_warning "\`-R' is ignored for archives"
+
+      test -n "$vinfo" && \
+	func_warning "\`-version-info/-version-number' is ignored for archives"
+
+      test -n "$release" && \
+	func_warning "\`-release' is ignored for archives"
+
+      test -n "$export_symbols$export_symbols_regex" && \
+	func_warning "\`-export-symbols' is ignored for archives"
+
+      # Now set the variables for building old libraries.
+      build_libtool_libs=no
+      oldlibs="$output"
+      func_append objs "$old_deplibs"
+      ;;
+
+    lib)
+      # Make sure we only generate libraries of the form `libNAME.la'.
+      case $outputname in
+      lib*)
+	func_stripname 'lib' '.la' "$outputname"
+	name=$func_stripname_result
+	eval shared_ext=\"$shrext_cmds\"
+	eval libname=\"$libname_spec\"
+	;;
+      *)
+	test "$module" = no && \
+	  func_fatal_help "libtool library \`$output' must begin with \`lib'"
+
+	if test "$need_lib_prefix" != no; then
+	  # Add the "lib" prefix for modules if required
+	  func_stripname '' '.la' "$outputname"
+	  name=$func_stripname_result
+	  eval shared_ext=\"$shrext_cmds\"
+	  eval libname=\"$libname_spec\"
+	else
+	  func_stripname '' '.la' "$outputname"
+	  libname=$func_stripname_result
+	fi
+	;;
+      esac
+
+      if test -n "$objs"; then
+	if test "$deplibs_check_method" != pass_all; then
+	  func_fatal_error "cannot build libtool library \`$output' from non-libtool objects on this host:$objs"
+	else
+	  echo
+	  $ECHO "*** Warning: Linking the shared library $output against the non-libtool"
+	  $ECHO "*** objects $objs is not portable!"
+	  func_append libobjs " $objs"
+	fi
+      fi
+
+      test "$dlself" != no && \
+	func_warning "\`-dlopen self' is ignored for libtool libraries"
+
+      set dummy $rpath
+      shift
+      test "$#" -gt 1 && \
+	func_warning "ignoring multiple \`-rpath's for a libtool library"
+
+      install_libdir="$1"
+
+      oldlibs=
+      if test -z "$rpath"; then
+	if test "$build_libtool_libs" = yes; then
+	  # Building a libtool convenience library.
+	  # Some compilers have problems with a `.al' extension so
+	  # convenience libraries should have the same extension an
+	  # archive normally would.
+	  oldlibs="$output_objdir/$libname.$libext $oldlibs"
+	  build_libtool_libs=convenience
+	  build_old_libs=yes
+	fi
+
+	test -n "$vinfo" && \
+	  func_warning "\`-version-info/-version-number' is ignored for convenience libraries"
+
+	test -n "$release" && \
+	  func_warning "\`-release' is ignored for convenience libraries"
+      else
+
+	# Parse the version information argument.
+	save_ifs="$IFS"; IFS=':'
+	set dummy $vinfo 0 0 0
+	shift
+	IFS="$save_ifs"
+
+	test -n "$7" && \
+	  func_fatal_help "too many parameters to \`-version-info'"
+
+	# convert absolute version numbers to libtool ages
+	# this retains compatibility with .la files and attempts
+	# to make the code below a bit more comprehensible
+
+	case $vinfo_number in
+	yes)
+	  number_major="$1"
+	  number_minor="$2"
+	  number_revision="$3"
+	  #
+	  # There are really only two kinds -- those that
+	  # use the current revision as the major version
+	  # and those that subtract age and use age as
+	  # a minor version.  But, then there is irix
+	  # which has an extra 1 added just for fun
+	  #
+	  case $version_type in
+	  # correct linux to gnu/linux during the next big refactor
+	  darwin|linux|osf|windows|none)
+	    func_arith $number_major + $number_minor
+	    current=$func_arith_result
+	    age="$number_minor"
+	    revision="$number_revision"
+	    ;;
+	  freebsd-aout|freebsd-elf|qnx|sunos)
+	    current="$number_major"
+	    revision="$number_minor"
+	    age="0"
+	    ;;
+	  irix|nonstopux)
+	    func_arith $number_major + $number_minor
+	    current=$func_arith_result
+	    age="$number_minor"
+	    revision="$number_minor"
+	    lt_irix_increment=no
+	    ;;
+	  esac
+	  ;;
+	no)
+	  current="$1"
+	  revision="$2"
+	  age="$3"
+	  ;;
+	esac
+
+	# Check that each of the things are valid numbers.
+	case $current in
+	0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;;
+	*)
+	  func_error "CURRENT \`$current' must be a nonnegative integer"
+	  func_fatal_error "\`$vinfo' is not valid version information"
+	  ;;
+	esac
+
+	case $revision in
+	0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;;
+	*)
+	  func_error "REVISION \`$revision' must be a nonnegative integer"
+	  func_fatal_error "\`$vinfo' is not valid version information"
+	  ;;
+	esac
+
+	case $age in
+	0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;;
+	*)
+	  func_error "AGE \`$age' must be a nonnegative integer"
+	  func_fatal_error "\`$vinfo' is not valid version information"
+	  ;;
+	esac
+
+	if test "$age" -gt "$current"; then
+	  func_error "AGE \`$age' is greater than the current interface number \`$current'"
+	  func_fatal_error "\`$vinfo' is not valid version information"
+	fi
+
+	# Calculate the version variables.
+	major=
+	versuffix=
+	verstring=
+	case $version_type in
+	none) ;;
+
+	darwin)
+	  # Like Linux, but with the current version available in
+	  # verstring for coding it into the library header
+	  func_arith $current - $age
+	  major=.$func_arith_result
+	  versuffix="$major.$age.$revision"
+	  # Darwin ld doesn't like 0 for these options...
+	  func_arith $current + 1
+	  minor_current=$func_arith_result
+	  xlcverstring="${wl}-compatibility_version ${wl}$minor_current ${wl}-current_version ${wl}$minor_current.$revision"
+	  verstring="-compatibility_version $minor_current -current_version $minor_current.$revision"
+	  ;;
+
+	freebsd-aout)
+	  major=".$current"
+	  versuffix=".$current.$revision";
+	  ;;
+
+	freebsd-elf)
+	  major=".$current"
+	  versuffix=".$current"
+	  ;;
+
+	irix | nonstopux)
+	  if test "X$lt_irix_increment" = "Xno"; then
+	    func_arith $current - $age
+	  else
+	    func_arith $current - $age + 1
+	  fi
+	  major=$func_arith_result
+
+	  case $version_type in
+	    nonstopux) verstring_prefix=nonstopux ;;
+	    *)         verstring_prefix=sgi ;;
+	  esac
+	  verstring="$verstring_prefix$major.$revision"
+
+	  # Add in all the interfaces that we are compatible with.
+	  loop=$revision
+	  while test "$loop" -ne 0; do
+	    func_arith $revision - $loop
+	    iface=$func_arith_result
+	    func_arith $loop - 1
+	    loop=$func_arith_result
+	    verstring="$verstring_prefix$major.$iface:$verstring"
+	  done
+
+	  # Before this point, $major must not contain `.'.
+	  major=.$major
+	  versuffix="$major.$revision"
+	  ;;
+
+	linux) # correct to gnu/linux during the next big refactor
+	  func_arith $current - $age
+	  major=.$func_arith_result
+	  versuffix="$major.$age.$revision"
+	  ;;
+
+	osf)
+	  func_arith $current - $age
+	  major=.$func_arith_result
+	  versuffix=".$current.$age.$revision"
+	  verstring="$current.$age.$revision"
+
+	  # Add in all the interfaces that we are compatible with.
+	  loop=$age
+	  while test "$loop" -ne 0; do
+	    func_arith $current - $loop
+	    iface=$func_arith_result
+	    func_arith $loop - 1
+	    loop=$func_arith_result
+	    verstring="$verstring:${iface}.0"
+	  done
+
+	  # Make executables depend on our current version.
+	  func_append verstring ":${current}.0"
+	  ;;
+
+	qnx)
+	  major=".$current"
+	  versuffix=".$current"
+	  ;;
+
+	sunos)
+	  major=".$current"
+	  versuffix=".$current.$revision"
+	  ;;
+
+	windows)
+	  # Use '-' rather than '.', since we only want one
+	  # extension on DOS 8.3 filesystems.
+	  func_arith $current - $age
+	  major=$func_arith_result
+	  versuffix="-$major"
+	  ;;
+
+	*)
+	  func_fatal_configuration "unknown library version type \`$version_type'"
+	  ;;
+	esac
+
+	# Clear the version info if we defaulted, and they specified a release.
+	if test -z "$vinfo" && test -n "$release"; then
+	  major=
+	  case $version_type in
+	  darwin)
+	    # we can't check for "0.0" in archive_cmds due to quoting
+	    # problems, so we reset it completely
+	    verstring=
+	    ;;
+	  *)
+	    verstring="0.0"
+	    ;;
+	  esac
+	  if test "$need_version" = no; then
+	    versuffix=
+	  else
+	    versuffix=".0.0"
+	  fi
+	fi
+
+	# Remove version info from name if versioning should be avoided
+	if test "$avoid_version" = yes && test "$need_version" = no; then
+	  major=
+	  versuffix=
+	  verstring=""
+	fi
+
+	# Check to see if the archive will have undefined symbols.
+	if test "$allow_undefined" = yes; then
+	  if test "$allow_undefined_flag" = unsupported; then
+	    func_warning "undefined symbols not allowed in $host shared libraries"
+	    build_libtool_libs=no
+	    build_old_libs=yes
+	  fi
+	else
+	  # Don't allow undefined symbols.
+	  allow_undefined_flag="$no_undefined_flag"
+	fi
+
+      fi
+
+      func_generate_dlsyms "$libname" "$libname" "yes"
+      func_append libobjs " $symfileobj"
+      test "X$libobjs" = "X " && libobjs=
+
+      if test "$opt_mode" != relink; then
+	# Remove our outputs, but don't remove object files since they
+	# may have been created when compiling PIC objects.
+	removelist=
+	tempremovelist=`$ECHO "$output_objdir/*"`
+	for p in $tempremovelist; do
+	  case $p in
+	    *.$objext | *.gcno)
+	       ;;
+	    $output_objdir/$outputname | $output_objdir/$libname.* | $output_objdir/${libname}${release}.*)
+	       if test "X$precious_files_regex" != "X"; then
+		 if $ECHO "$p" | $EGREP -e "$precious_files_regex" >/dev/null 2>&1
+		 then
+		   continue
+		 fi
+	       fi
+	       func_append removelist " $p"
+	       ;;
+	    *) ;;
+	  esac
+	done
+	test -n "$removelist" && \
+	  func_show_eval "${RM}r \$removelist"
+      fi
+
+      # Now set the variables for building old libraries.
+      if test "$build_old_libs" = yes && test "$build_libtool_libs" != convenience ; then
+	func_append oldlibs " $output_objdir/$libname.$libext"
+
+	# Transform .lo files to .o files.
+	oldobjs="$objs "`$ECHO "$libobjs" | $SP2NL | $SED "/\.${libext}$/d; $lo2o" | $NL2SP`
+      fi
+
+      # Eliminate all temporary directories.
+      #for path in $notinst_path; do
+      #	lib_search_path=`$ECHO "$lib_search_path " | $SED "s% $path % %g"`
+      #	deplibs=`$ECHO "$deplibs " | $SED "s% -L$path % %g"`
+      #	dependency_libs=`$ECHO "$dependency_libs " | $SED "s% -L$path % %g"`
+      #done
+
+      if test -n "$xrpath"; then
+	# If the user specified any rpath flags, then add them.
+	temp_xrpath=
+	for libdir in $xrpath; do
+	  func_replace_sysroot "$libdir"
+	  func_append temp_xrpath " -R$func_replace_sysroot_result"
+	  case "$finalize_rpath " in
+	  *" $libdir "*) ;;
+	  *) func_append finalize_rpath " $libdir" ;;
+	  esac
+	done
+	if test "$hardcode_into_libs" != yes || test "$build_old_libs" = yes; then
+	  dependency_libs="$temp_xrpath $dependency_libs"
+	fi
+      fi
+
+      # Make sure dlfiles contains only unique files that won't be dlpreopened
+      old_dlfiles="$dlfiles"
+      dlfiles=
+      for lib in $old_dlfiles; do
+	case " $dlprefiles $dlfiles " in
+	*" $lib "*) ;;
+	*) func_append dlfiles " $lib" ;;
+	esac
+      done
+
+      # Make sure dlprefiles contains only unique files
+      old_dlprefiles="$dlprefiles"
+      dlprefiles=
+      for lib in $old_dlprefiles; do
+	case "$dlprefiles " in
+	*" $lib "*) ;;
+	*) func_append dlprefiles " $lib" ;;
+	esac
+      done
+
+      if test "$build_libtool_libs" = yes; then
+	if test -n "$rpath"; then
+	  case $host in
+	  *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-beos* | *-cegcc* | *-*-haiku*)
+	    # these systems don't actually have a c library (as such)!
+	    ;;
+	  *-*-rhapsody* | *-*-darwin1.[012])
+	    # Rhapsody C library is in the System framework
+	    func_append deplibs " System.ltframework"
+	    ;;
+	  *-*-netbsd*)
+	    # Don't link with libc until the a.out ld.so is fixed.
+	    ;;
+	  *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*)
+	    # Do not include libc due to us having libc/libc_r.
+	    ;;
+	  *-*-sco3.2v5* | *-*-sco5v6*)
+	    # Causes problems with __ctype
+	    ;;
+	  *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*)
+	    # Compiler inserts libc in the correct place for threads to work
+	    ;;
+	  *)
+	    # Add libc to deplibs on all other systems if necessary.
+	    if test "$build_libtool_need_lc" = "yes"; then
+	      func_append deplibs " -lc"
+	    fi
+	    ;;
+	  esac
+	fi
+
+	# Transform deplibs into only deplibs that can be linked in shared.
+	name_save=$name
+	libname_save=$libname
+	release_save=$release
+	versuffix_save=$versuffix
+	major_save=$major
+	# I'm not sure if I'm treating the release correctly.  I think
+	# release should show up in the -l (ie -lgmp5) so we don't want to
+	# add it in twice.  Is that correct?
+	release=""
+	versuffix=""
+	major=""
+	newdeplibs=
+	droppeddeps=no
+	case $deplibs_check_method in
+	pass_all)
+	  # Don't check for shared/static.  Everything works.
+	  # This might be a little naive.  We might want to check
+	  # whether the library exists or not.  But this is on
+	  # osf3 & osf4 and I'm not really sure... Just
+	  # implementing what was already the behavior.
+	  newdeplibs=$deplibs
+	  ;;
+	test_compile)
+	  # This code stresses the "libraries are programs" paradigm to its
+	  # limits. Maybe even breaks it.  We compile a program, linking it
+	  # against the deplibs as a proxy for the library.  Then we can check
+	  # whether they linked in statically or dynamically with ldd.
+	  $opt_dry_run || $RM conftest.c
+	  cat > conftest.c <<EOF
+	  int main() { return 0; }
+EOF
+	  $opt_dry_run || $RM conftest
+	  if $LTCC $LTCFLAGS -o conftest conftest.c $deplibs; then
+	    ldd_output=`ldd conftest`
+	    for i in $deplibs; do
+	      case $i in
+	      -l*)
+		func_stripname -l '' "$i"
+		name=$func_stripname_result
+		if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+		  case " $predeps $postdeps " in
+		  *" $i "*)
+		    func_append newdeplibs " $i"
+		    i=""
+		    ;;
+		  esac
+		fi
+		if test -n "$i" ; then
+		  libname=`eval "\\$ECHO \"$libname_spec\""`
+		  deplib_matches=`eval "\\$ECHO \"$library_names_spec\""`
+		  set dummy $deplib_matches; shift
+		  deplib_match=$1
+		  if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0 ; then
+		    func_append newdeplibs " $i"
+		  else
+		    droppeddeps=yes
+		    echo
+		    $ECHO "*** Warning: dynamic linker does not accept needed library $i."
+		    echo "*** I have the capability to make that library automatically link in when"
+		    echo "*** you link to this library.  But I can only do this if you have a"
+		    echo "*** shared version of the library, which I believe you do not have"
+		    echo "*** because a test_compile did reveal that the linker did not use it for"
+		    echo "*** its dynamic dependency list that programs get resolved with at runtime."
+		  fi
+		fi
+		;;
+	      *)
+		func_append newdeplibs " $i"
+		;;
+	      esac
+	    done
+	  else
+	    # Error occurred in the first compile.  Let's try to salvage
+	    # the situation: Compile a separate program for each library.
+	    for i in $deplibs; do
+	      case $i in
+	      -l*)
+		func_stripname -l '' "$i"
+		name=$func_stripname_result
+		$opt_dry_run || $RM conftest
+		if $LTCC $LTCFLAGS -o conftest conftest.c $i; then
+		  ldd_output=`ldd conftest`
+		  if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+		    case " $predeps $postdeps " in
+		    *" $i "*)
+		      func_append newdeplibs " $i"
+		      i=""
+		      ;;
+		    esac
+		  fi
+		  if test -n "$i" ; then
+		    libname=`eval "\\$ECHO \"$libname_spec\""`
+		    deplib_matches=`eval "\\$ECHO \"$library_names_spec\""`
+		    set dummy $deplib_matches; shift
+		    deplib_match=$1
+		    if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0 ; then
+		      func_append newdeplibs " $i"
+		    else
+		      droppeddeps=yes
+		      echo
+		      $ECHO "*** Warning: dynamic linker does not accept needed library $i."
+		      echo "*** I have the capability to make that library automatically link in when"
+		      echo "*** you link to this library.  But I can only do this if you have a"
+		      echo "*** shared version of the library, which you do not appear to have"
+		      echo "*** because a test_compile did reveal that the linker did not use this one"
+		      echo "*** as a dynamic dependency that programs can get resolved with at runtime."
+		    fi
+		  fi
+		else
+		  droppeddeps=yes
+		  echo
+		  $ECHO "*** Warning!  Library $i is needed by this library but I was not able to"
+		  echo "*** make it link in!  You will probably need to install it or some"
+		  echo "*** library that it depends on before this library will be fully"
+		  echo "*** functional.  Installing it before continuing would be even better."
+		fi
+		;;
+	      *)
+		func_append newdeplibs " $i"
+		;;
+	      esac
+	    done
+	  fi
+	  ;;
+	file_magic*)
+	  set dummy $deplibs_check_method; shift
+	  file_magic_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"`
+	  for a_deplib in $deplibs; do
+	    case $a_deplib in
+	    -l*)
+	      func_stripname -l '' "$a_deplib"
+	      name=$func_stripname_result
+	      if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+		case " $predeps $postdeps " in
+		*" $a_deplib "*)
+		  func_append newdeplibs " $a_deplib"
+		  a_deplib=""
+		  ;;
+		esac
+	      fi
+	      if test -n "$a_deplib" ; then
+		libname=`eval "\\$ECHO \"$libname_spec\""`
+		if test -n "$file_magic_glob"; then
+		  libnameglob=`func_echo_all "$libname" | $SED -e $file_magic_glob`
+		else
+		  libnameglob=$libname
+		fi
+		test "$want_nocaseglob" = yes && nocaseglob=`shopt -p nocaseglob`
+		for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do
+		  if test "$want_nocaseglob" = yes; then
+		    shopt -s nocaseglob
+		    potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null`
+		    $nocaseglob
+		  else
+		    potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null`
+		  fi
+		  for potent_lib in $potential_libs; do
+		      # Follow soft links.
+		      if ls -lLd "$potent_lib" 2>/dev/null |
+			 $GREP " -> " >/dev/null; then
+			continue
+		      fi
+		      # The statement above tries to avoid entering an
+		      # endless loop below, in case of cyclic links.
+		      # We might still enter an endless loop, since a link
+		      # loop can be closed while we follow links,
+		      # but so what?
+		      potlib="$potent_lib"
+		      while test -h "$potlib" 2>/dev/null; do
+			potliblink=`ls -ld $potlib | ${SED} 's/.* -> //'`
+			case $potliblink in
+			[\\/]* | [A-Za-z]:[\\/]*) potlib="$potliblink";;
+			*) potlib=`$ECHO "$potlib" | $SED 's,[^/]*$,,'`"$potliblink";;
+			esac
+		      done
+		      if eval $file_magic_cmd \"\$potlib\" 2>/dev/null |
+			 $SED -e 10q |
+			 $EGREP "$file_magic_regex" > /dev/null; then
+			func_append newdeplibs " $a_deplib"
+			a_deplib=""
+			break 2
+		      fi
+		  done
+		done
+	      fi
+	      if test -n "$a_deplib" ; then
+		droppeddeps=yes
+		echo
+		$ECHO "*** Warning: linker path does not have real file for library $a_deplib."
+		echo "*** I have the capability to make that library automatically link in when"
+		echo "*** you link to this library.  But I can only do this if you have a"
+		echo "*** shared version of the library, which you do not appear to have"
+		echo "*** because I did check the linker path looking for a file starting"
+		if test -z "$potlib" ; then
+		  $ECHO "*** with $libname but no candidates were found. (...for file magic test)"
+		else
+		  $ECHO "*** with $libname and none of the candidates passed a file format test"
+		  $ECHO "*** using a file magic. Last file checked: $potlib"
+		fi
+	      fi
+	      ;;
+	    *)
+	      # Add a -L argument.
+	      func_append newdeplibs " $a_deplib"
+	      ;;
+	    esac
+	  done # Gone through all deplibs.
+	  ;;
+	match_pattern*)
+	  set dummy $deplibs_check_method; shift
+	  match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"`
+	  for a_deplib in $deplibs; do
+	    case $a_deplib in
+	    -l*)
+	      func_stripname -l '' "$a_deplib"
+	      name=$func_stripname_result
+	      if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+		case " $predeps $postdeps " in
+		*" $a_deplib "*)
+		  func_append newdeplibs " $a_deplib"
+		  a_deplib=""
+		  ;;
+		esac
+	      fi
+	      if test -n "$a_deplib" ; then
+		libname=`eval "\\$ECHO \"$libname_spec\""`
+		for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do
+		  potential_libs=`ls $i/$libname[.-]* 2>/dev/null`
+		  for potent_lib in $potential_libs; do
+		    potlib="$potent_lib" # see symlink-check above in file_magic test
+		    if eval "\$ECHO \"$potent_lib\"" 2>/dev/null | $SED 10q | \
+		       $EGREP "$match_pattern_regex" > /dev/null; then
+		      func_append newdeplibs " $a_deplib"
+		      a_deplib=""
+		      break 2
+		    fi
+		  done
+		done
+	      fi
+	      if test -n "$a_deplib" ; then
+		droppeddeps=yes
+		echo
+		$ECHO "*** Warning: linker path does not have real file for library $a_deplib."
+		echo "*** I have the capability to make that library automatically link in when"
+		echo "*** you link to this library.  But I can only do this if you have a"
+		echo "*** shared version of the library, which you do not appear to have"
+		echo "*** because I did check the linker path looking for a file starting"
+		if test -z "$potlib" ; then
+		  $ECHO "*** with $libname but no candidates were found. (...for regex pattern test)"
+		else
+		  $ECHO "*** with $libname and none of the candidates passed a file format test"
+		  $ECHO "*** using a regex pattern. Last file checked: $potlib"
+		fi
+	      fi
+	      ;;
+	    *)
+	      # Add a -L argument.
+	      func_append newdeplibs " $a_deplib"
+	      ;;
+	    esac
+	  done # Gone through all deplibs.
+	  ;;
+	none | unknown | *)
+	  newdeplibs=""
+	  tmp_deplibs=`$ECHO " $deplibs" | $SED 's/ -lc$//; s/ -[LR][^ ]*//g'`
+	  if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+	    for i in $predeps $postdeps ; do
+	      # can't use Xsed below, because $i might contain '/'
+	      tmp_deplibs=`$ECHO " $tmp_deplibs" | $SED "s,$i,,"`
+	    done
+	  fi
+	  case $tmp_deplibs in
+	  *[!\	\ ]*)
+	    echo
+	    if test "X$deplibs_check_method" = "Xnone"; then
+	      echo "*** Warning: inter-library dependencies are not supported in this platform."
+	    else
+	      echo "*** Warning: inter-library dependencies are not known to be supported."
+	    fi
+	    echo "*** All declared inter-library dependencies are being dropped."
+	    droppeddeps=yes
+	    ;;
+	  esac
+	  ;;
+	esac
+	versuffix=$versuffix_save
+	major=$major_save
+	release=$release_save
+	libname=$libname_save
+	name=$name_save
+
+	case $host in
+	*-*-rhapsody* | *-*-darwin1.[012])
+	  # On Rhapsody replace the C library with the System framework
+	  newdeplibs=`$ECHO " $newdeplibs" | $SED 's/ -lc / System.ltframework /'`
+	  ;;
+	esac
+
+	if test "$droppeddeps" = yes; then
+	  if test "$module" = yes; then
+	    echo
+	    echo "*** Warning: libtool could not satisfy all declared inter-library"
+	    $ECHO "*** dependencies of module $libname.  Therefore, libtool will create"
+	    echo "*** a static module, that should work as long as the dlopening"
+	    echo "*** application is linked with the -dlopen flag."
+	    if test -z "$global_symbol_pipe"; then
+	      echo
+	      echo "*** However, this would only work if libtool was able to extract symbol"
+	      echo "*** lists from a program, using \`nm' or equivalent, but libtool could"
+	      echo "*** not find such a program.  So, this module is probably useless."
+	      echo "*** \`nm' from GNU binutils and a full rebuild may help."
+	    fi
+	    if test "$build_old_libs" = no; then
+	      oldlibs="$output_objdir/$libname.$libext"
+	      build_libtool_libs=module
+	      build_old_libs=yes
+	    else
+	      build_libtool_libs=no
+	    fi
+	  else
+	    echo "*** The inter-library dependencies that have been dropped here will be"
+	    echo "*** automatically added whenever a program is linked with this library"
+	    echo "*** or is declared to -dlopen it."
+
+	    if test "$allow_undefined" = no; then
+	      echo
+	      echo "*** Since this library must not contain undefined symbols,"
+	      echo "*** because either the platform does not support them or"
+	      echo "*** it was explicitly requested with -no-undefined,"
+	      echo "*** libtool will only create a static version of it."
+	      if test "$build_old_libs" = no; then
+		oldlibs="$output_objdir/$libname.$libext"
+		build_libtool_libs=module
+		build_old_libs=yes
+	      else
+		build_libtool_libs=no
+	      fi
+	    fi
+	  fi
+	fi
+	# Done checking deplibs!
+	deplibs=$newdeplibs
+      fi
+      # Time to change all our "foo.ltframework" stuff back to "-framework foo"
+      case $host in
+	*-*-darwin*)
+	  newdeplibs=`$ECHO " $newdeplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+	  new_inherited_linker_flags=`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+	  deplibs=`$ECHO " $deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+	  ;;
+      esac
+
+      # move library search paths that coincide with paths to not yet
+      # installed libraries to the beginning of the library search list
+      new_libs=
+      for path in $notinst_path; do
+	case " $new_libs " in
+	*" -L$path/$objdir "*) ;;
+	*)
+	  case " $deplibs " in
+	  *" -L$path/$objdir "*)
+	    func_append new_libs " -L$path/$objdir" ;;
+	  esac
+	  ;;
+	esac
+      done
+      for deplib in $deplibs; do
+	case $deplib in
+	-L*)
+	  case " $new_libs " in
+	  *" $deplib "*) ;;
+	  *) func_append new_libs " $deplib" ;;
+	  esac
+	  ;;
+	*) func_append new_libs " $deplib" ;;
+	esac
+      done
+      deplibs="$new_libs"
+
+      # All the library-specific variables (install_libdir is set above).
+      library_names=
+      old_library=
+      dlname=
+
+      # Test again, we may have decided not to build it any more
+      if test "$build_libtool_libs" = yes; then
+	# Remove ${wl} instances when linking with ld.
+	# FIXME: should test the right _cmds variable.
+	case $archive_cmds in
+	  *\$LD\ *) wl= ;;
+        esac
+	if test "$hardcode_into_libs" = yes; then
+	  # Hardcode the library paths
+	  hardcode_libdirs=
+	  dep_rpath=
+	  rpath="$finalize_rpath"
+	  test "$opt_mode" != relink && rpath="$compile_rpath$rpath"
+	  for libdir in $rpath; do
+	    if test -n "$hardcode_libdir_flag_spec"; then
+	      if test -n "$hardcode_libdir_separator"; then
+		func_replace_sysroot "$libdir"
+		libdir=$func_replace_sysroot_result
+		if test -z "$hardcode_libdirs"; then
+		  hardcode_libdirs="$libdir"
+		else
+		  # Just accumulate the unique libdirs.
+		  case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+		  *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+		    ;;
+		  *)
+		    func_append hardcode_libdirs "$hardcode_libdir_separator$libdir"
+		    ;;
+		  esac
+		fi
+	      else
+		eval flag=\"$hardcode_libdir_flag_spec\"
+		func_append dep_rpath " $flag"
+	      fi
+	    elif test -n "$runpath_var"; then
+	      case "$perm_rpath " in
+	      *" $libdir "*) ;;
+	      *) func_append perm_rpath " $libdir" ;;
+	      esac
+	    fi
+	  done
+	  # Substitute the hardcoded libdirs into the rpath.
+	  if test -n "$hardcode_libdir_separator" &&
+	     test -n "$hardcode_libdirs"; then
+	    libdir="$hardcode_libdirs"
+	    eval "dep_rpath=\"$hardcode_libdir_flag_spec\""
+	  fi
+	  if test -n "$runpath_var" && test -n "$perm_rpath"; then
+	    # We should set the runpath_var.
+	    rpath=
+	    for dir in $perm_rpath; do
+	      func_append rpath "$dir:"
+	    done
+	    eval "$runpath_var='$rpath\$$runpath_var'; export $runpath_var"
+	  fi
+	  test -n "$dep_rpath" && deplibs="$dep_rpath $deplibs"
+	fi
+
+	shlibpath="$finalize_shlibpath"
+	test "$opt_mode" != relink && shlibpath="$compile_shlibpath$shlibpath"
+	if test -n "$shlibpath"; then
+	  eval "$shlibpath_var='$shlibpath\$$shlibpath_var'; export $shlibpath_var"
+	fi
+
+	# Get the real and link names of the library.
+	eval shared_ext=\"$shrext_cmds\"
+	eval library_names=\"$library_names_spec\"
+	set dummy $library_names
+	shift
+	realname="$1"
+	shift
+
+	if test -n "$soname_spec"; then
+	  eval soname=\"$soname_spec\"
+	else
+	  soname="$realname"
+	fi
+	if test -z "$dlname"; then
+	  dlname=$soname
+	fi
+
+	lib="$output_objdir/$realname"
+	linknames=
+	for link
+	do
+	  func_append linknames " $link"
+	done
+
+	# Use standard objects if they are pic
+	test -z "$pic_flag" && libobjs=`$ECHO "$libobjs" | $SP2NL | $SED "$lo2o" | $NL2SP`
+	test "X$libobjs" = "X " && libobjs=
+
+	delfiles=
+	if test -n "$export_symbols" && test -n "$include_expsyms"; then
+	  $opt_dry_run || cp "$export_symbols" "$output_objdir/$libname.uexp"
+	  export_symbols="$output_objdir/$libname.uexp"
+	  func_append delfiles " $export_symbols"
+	fi
+
+	orig_export_symbols=
+	case $host_os in
+	cygwin* | mingw* | cegcc*)
+	  if test -n "$export_symbols" && test -z "$export_symbols_regex"; then
+	    # exporting using user supplied symfile
+	    if test "x`$SED 1q $export_symbols`" != xEXPORTS; then
+	      # and it's NOT already a .def file. Must figure out
+	      # which of the given symbols are data symbols and tag
+	      # them as such. So, trigger use of export_symbols_cmds.
+	      # export_symbols gets reassigned inside the "prepare
+	      # the list of exported symbols" if statement, so the
+	      # include_expsyms logic still works.
+	      orig_export_symbols="$export_symbols"
+	      export_symbols=
+	      always_export_symbols=yes
+	    fi
+	  fi
+	  ;;
+	esac
+
+	# Prepare the list of exported symbols
+	if test -z "$export_symbols"; then
+	  if test "$always_export_symbols" = yes || test -n "$export_symbols_regex"; then
+	    func_verbose "generating symbol list for \`$libname.la'"
+	    export_symbols="$output_objdir/$libname.exp"
+	    $opt_dry_run || $RM $export_symbols
+	    cmds=$export_symbols_cmds
+	    save_ifs="$IFS"; IFS='~'
+	    for cmd1 in $cmds; do
+	      IFS="$save_ifs"
+	      # Take the normal branch if the nm_file_list_spec branch
+	      # doesn't work or if tool conversion is not needed.
+	      case $nm_file_list_spec~$to_tool_file_cmd in
+		*~func_convert_file_noop | *~func_convert_file_msys_to_w32 | ~*)
+		  try_normal_branch=yes
+		  eval cmd=\"$cmd1\"
+		  func_len " $cmd"
+		  len=$func_len_result
+		  ;;
+		*)
+		  try_normal_branch=no
+		  ;;
+	      esac
+	      if test "$try_normal_branch" = yes \
+		 && { test "$len" -lt "$max_cmd_len" \
+		      || test "$max_cmd_len" -le -1; }
+	      then
+		func_show_eval "$cmd" 'exit $?'
+		skipped_export=false
+	      elif test -n "$nm_file_list_spec"; then
+		func_basename "$output"
+		output_la=$func_basename_result
+		save_libobjs=$libobjs
+		save_output=$output
+		output=${output_objdir}/${output_la}.nm
+		func_to_tool_file "$output"
+		libobjs=$nm_file_list_spec$func_to_tool_file_result
+		func_append delfiles " $output"
+		func_verbose "creating $NM input file list: $output"
+		for obj in $save_libobjs; do
+		  func_to_tool_file "$obj"
+		  $ECHO "$func_to_tool_file_result"
+		done > "$output"
+		eval cmd=\"$cmd1\"
+		func_show_eval "$cmd" 'exit $?'
+		output=$save_output
+		libobjs=$save_libobjs
+		skipped_export=false
+	      else
+		# The command line is too long to execute in one step.
+		func_verbose "using reloadable object file for export list..."
+		skipped_export=:
+		# Break out early, otherwise skipped_export may be
+		# set to false by a later but shorter cmd.
+		break
+	      fi
+	    done
+	    IFS="$save_ifs"
+	    if test -n "$export_symbols_regex" && test "X$skipped_export" != "X:"; then
+	      func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"'
+	      func_show_eval '$MV "${export_symbols}T" "$export_symbols"'
+	    fi
+	  fi
+	fi
+
+	if test -n "$export_symbols" && test -n "$include_expsyms"; then
+	  tmp_export_symbols="$export_symbols"
+	  test -n "$orig_export_symbols" && tmp_export_symbols="$orig_export_symbols"
+	  $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"'
+	fi
+
+	if test "X$skipped_export" != "X:" && test -n "$orig_export_symbols"; then
+	  # The given exports_symbols file has to be filtered, so filter it.
+	  func_verbose "filter symbol list for \`$libname.la' to tag DATA exports"
+	  # FIXME: $output_objdir/$libname.filter potentially contains lots of
+	  # 's' commands which not all seds can handle. GNU sed should be fine
+	  # though. Also, the filter scales superlinearly with the number of
+	  # global variables. join(1) would be nice here, but unfortunately
+	  # isn't a blessed tool.
+	  $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter
+	  func_append delfiles " $export_symbols $output_objdir/$libname.filter"
+	  export_symbols=$output_objdir/$libname.def
+	  $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols
+	fi
+
+	tmp_deplibs=
+	for test_deplib in $deplibs; do
+	  case " $convenience " in
+	  *" $test_deplib "*) ;;
+	  *)
+	    func_append tmp_deplibs " $test_deplib"
+	    ;;
+	  esac
+	done
+	deplibs="$tmp_deplibs"
+
+	if test -n "$convenience"; then
+	  if test -n "$whole_archive_flag_spec" &&
+	    test "$compiler_needs_object" = yes &&
+	    test -z "$libobjs"; then
+	    # extract the archives, so we have objects to list.
+	    # TODO: could optimize this to just extract one archive.
+	    whole_archive_flag_spec=
+	  fi
+	  if test -n "$whole_archive_flag_spec"; then
+	    save_libobjs=$libobjs
+	    eval libobjs=\"\$libobjs $whole_archive_flag_spec\"
+	    test "X$libobjs" = "X " && libobjs=
+	  else
+	    gentop="$output_objdir/${outputname}x"
+	    func_append generated " $gentop"
+
+	    func_extract_archives $gentop $convenience
+	    func_append libobjs " $func_extract_archives_result"
+	    test "X$libobjs" = "X " && libobjs=
+	  fi
+	fi
+
+	if test "$thread_safe" = yes && test -n "$thread_safe_flag_spec"; then
+	  eval flag=\"$thread_safe_flag_spec\"
+	  func_append linker_flags " $flag"
+	fi
+
+	# Make a backup of the uninstalled library when relinking
+	if test "$opt_mode" = relink; then
+	  $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}U && $MV $realname ${realname}U)' || exit $?
+	fi
+
+	# Do each of the archive commands.
+	if test "$module" = yes && test -n "$module_cmds" ; then
+	  if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then
+	    eval test_cmds=\"$module_expsym_cmds\"
+	    cmds=$module_expsym_cmds
+	  else
+	    eval test_cmds=\"$module_cmds\"
+	    cmds=$module_cmds
+	  fi
+	else
+	  if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then
+	    eval test_cmds=\"$archive_expsym_cmds\"
+	    cmds=$archive_expsym_cmds
+	  else
+	    eval test_cmds=\"$archive_cmds\"
+	    cmds=$archive_cmds
+	  fi
+	fi
+
+	if test "X$skipped_export" != "X:" &&
+	   func_len " $test_cmds" &&
+	   len=$func_len_result &&
+	   test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then
+	  :
+	else
+	  # The command line is too long to link in one step, link piecewise
+	  # or, if using GNU ld and skipped_export is not :, use a linker
+	  # script.
+
+	  # Save the value of $output and $libobjs because we want to
+	  # use them later.  If we have whole_archive_flag_spec, we
+	  # want to use save_libobjs as it was before
+	  # whole_archive_flag_spec was expanded, because we can't
+	  # assume the linker understands whole_archive_flag_spec.
+	  # This may have to be revisited, in case too many
+	  # convenience libraries get linked in and end up exceeding
+	  # the spec.
+	  if test -z "$convenience" || test -z "$whole_archive_flag_spec"; then
+	    save_libobjs=$libobjs
+	  fi
+	  save_output=$output
+	  func_basename "$output"
+	  output_la=$func_basename_result
+
+	  # Clear the reloadable object creation command queue and
+	  # initialize k to one.
+	  test_cmds=
+	  concat_cmds=
+	  objlist=
+	  last_robj=
+	  k=1
+
+	  if test -n "$save_libobjs" && test "X$skipped_export" != "X:" && test "$with_gnu_ld" = yes; then
+	    output=${output_objdir}/${output_la}.lnkscript
+	    func_verbose "creating GNU ld script: $output"
+	    echo 'INPUT (' > $output
+	    for obj in $save_libobjs
+	    do
+	      func_to_tool_file "$obj"
+	      $ECHO "$func_to_tool_file_result" >> $output
+	    done
+	    echo ')' >> $output
+	    func_append delfiles " $output"
+	    func_to_tool_file "$output"
+	    output=$func_to_tool_file_result
+	  elif test -n "$save_libobjs" && test "X$skipped_export" != "X:" && test "X$file_list_spec" != X; then
+	    output=${output_objdir}/${output_la}.lnk
+	    func_verbose "creating linker input file list: $output"
+	    : > $output
+	    set x $save_libobjs
+	    shift
+	    firstobj=
+	    if test "$compiler_needs_object" = yes; then
+	      firstobj="$1 "
+	      shift
+	    fi
+	    for obj
+	    do
+	      func_to_tool_file "$obj"
+	      $ECHO "$func_to_tool_file_result" >> $output
+	    done
+	    func_append delfiles " $output"
+	    func_to_tool_file "$output"
+	    output=$firstobj\"$file_list_spec$func_to_tool_file_result\"
+	  else
+	    if test -n "$save_libobjs"; then
+	      func_verbose "creating reloadable object files..."
+	      output=$output_objdir/$output_la-${k}.$objext
+	      eval test_cmds=\"$reload_cmds\"
+	      func_len " $test_cmds"
+	      len0=$func_len_result
+	      len=$len0
+
+	      # Loop over the list of objects to be linked.
+	      for obj in $save_libobjs
+	      do
+		func_len " $obj"
+		func_arith $len + $func_len_result
+		len=$func_arith_result
+		if test "X$objlist" = X ||
+		   test "$len" -lt "$max_cmd_len"; then
+		  func_append objlist " $obj"
+		else
+		  # The command $test_cmds is almost too long, add a
+		  # command to the queue.
+		  if test "$k" -eq 1 ; then
+		    # The first file doesn't have a previous command to add.
+		    reload_objs=$objlist
+		    eval concat_cmds=\"$reload_cmds\"
+		  else
+		    # All subsequent reloadable object files will link in
+		    # the last one created.
+		    reload_objs="$objlist $last_robj"
+		    eval concat_cmds=\"\$concat_cmds~$reload_cmds~\$RM $last_robj\"
+		  fi
+		  last_robj=$output_objdir/$output_la-${k}.$objext
+		  func_arith $k + 1
+		  k=$func_arith_result
+		  output=$output_objdir/$output_la-${k}.$objext
+		  objlist=" $obj"
+		  func_len " $last_robj"
+		  func_arith $len0 + $func_len_result
+		  len=$func_arith_result
+		fi
+	      done
+	      # Handle the remaining objects by creating one last
+	      # reloadable object file.  All subsequent reloadable object
+	      # files will link in the last one created.
+	      test -z "$concat_cmds" || concat_cmds=$concat_cmds~
+	      reload_objs="$objlist $last_robj"
+	      eval concat_cmds=\"\${concat_cmds}$reload_cmds\"
+	      if test -n "$last_robj"; then
+	        eval concat_cmds=\"\${concat_cmds}~\$RM $last_robj\"
+	      fi
+	      func_append delfiles " $output"
+
+	    else
+	      output=
+	    fi
+
+	    if ${skipped_export-false}; then
+	      func_verbose "generating symbol list for \`$libname.la'"
+	      export_symbols="$output_objdir/$libname.exp"
+	      $opt_dry_run || $RM $export_symbols
+	      libobjs=$output
+	      # Append the command to create the export file.
+	      test -z "$concat_cmds" || concat_cmds=$concat_cmds~
+	      eval concat_cmds=\"\$concat_cmds$export_symbols_cmds\"
+	      if test -n "$last_robj"; then
+		eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\"
+	      fi
+	    fi
+
+	    test -n "$save_libobjs" &&
+	      func_verbose "creating a temporary reloadable object file: $output"
+
+	    # Loop through the commands generated above and execute them.
+	    save_ifs="$IFS"; IFS='~'
+	    for cmd in $concat_cmds; do
+	      IFS="$save_ifs"
+	      $opt_silent || {
+		  func_quote_for_expand "$cmd"
+		  eval "func_echo $func_quote_for_expand_result"
+	      }
+	      $opt_dry_run || eval "$cmd" || {
+		lt_exit=$?
+
+		# Restore the uninstalled library and exit
+		if test "$opt_mode" = relink; then
+		  ( cd "$output_objdir" && \
+		    $RM "${realname}T" && \
+		    $MV "${realname}U" "$realname" )
+		fi
+
+		exit $lt_exit
+	      }
+	    done
+	    IFS="$save_ifs"
+
+	    if test -n "$export_symbols_regex" && ${skipped_export-false}; then
+	      func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"'
+	      func_show_eval '$MV "${export_symbols}T" "$export_symbols"'
+	    fi
+	  fi
+
+          if ${skipped_export-false}; then
+	    if test -n "$export_symbols" && test -n "$include_expsyms"; then
+	      tmp_export_symbols="$export_symbols"
+	      test -n "$orig_export_symbols" && tmp_export_symbols="$orig_export_symbols"
+	      $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"'
+	    fi
+
+	    if test -n "$orig_export_symbols"; then
+	      # The given exports_symbols file has to be filtered, so filter it.
+	      func_verbose "filter symbol list for \`$libname.la' to tag DATA exports"
+	      # FIXME: $output_objdir/$libname.filter potentially contains lots of
+	      # 's' commands which not all seds can handle. GNU sed should be fine
+	      # though. Also, the filter scales superlinearly with the number of
+	      # global variables. join(1) would be nice here, but unfortunately
+	      # isn't a blessed tool.
+	      $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter
+	      func_append delfiles " $export_symbols $output_objdir/$libname.filter"
+	      export_symbols=$output_objdir/$libname.def
+	      $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols
+	    fi
+	  fi
+
+	  libobjs=$output
+	  # Restore the value of output.
+	  output=$save_output
+
+	  if test -n "$convenience" && test -n "$whole_archive_flag_spec"; then
+	    eval libobjs=\"\$libobjs $whole_archive_flag_spec\"
+	    test "X$libobjs" = "X " && libobjs=
+	  fi
+	  # Expand the library linking commands again to reset the
+	  # value of $libobjs for piecewise linking.
+
+	  # Do each of the archive commands.
+	  if test "$module" = yes && test -n "$module_cmds" ; then
+	    if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then
+	      cmds=$module_expsym_cmds
+	    else
+	      cmds=$module_cmds
+	    fi
+	  else
+	    if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then
+	      cmds=$archive_expsym_cmds
+	    else
+	      cmds=$archive_cmds
+	    fi
+	  fi
+	fi
+
+	if test -n "$delfiles"; then
+	  # Append the command to remove temporary files to $cmds.
+	  eval cmds=\"\$cmds~\$RM $delfiles\"
+	fi
+
+	# Add any objects from preloaded convenience libraries
+	if test -n "$dlprefiles"; then
+	  gentop="$output_objdir/${outputname}x"
+	  func_append generated " $gentop"
+
+	  func_extract_archives $gentop $dlprefiles
+	  func_append libobjs " $func_extract_archives_result"
+	  test "X$libobjs" = "X " && libobjs=
+	fi
+
+	save_ifs="$IFS"; IFS='~'
+	for cmd in $cmds; do
+	  IFS="$save_ifs"
+	  eval cmd=\"$cmd\"
+	  $opt_silent || {
+	    func_quote_for_expand "$cmd"
+	    eval "func_echo $func_quote_for_expand_result"
+	  }
+	  $opt_dry_run || eval "$cmd" || {
+	    lt_exit=$?
+
+	    # Restore the uninstalled library and exit
+	    if test "$opt_mode" = relink; then
+	      ( cd "$output_objdir" && \
+	        $RM "${realname}T" && \
+		$MV "${realname}U" "$realname" )
+	    fi
+
+	    exit $lt_exit
+	  }
+	done
+	IFS="$save_ifs"
+
+	# Restore the uninstalled library and exit
+	if test "$opt_mode" = relink; then
+	  $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}T && $MV $realname ${realname}T && $MV ${realname}U $realname)' || exit $?
+
+	  if test -n "$convenience"; then
+	    if test -z "$whole_archive_flag_spec"; then
+	      func_show_eval '${RM}r "$gentop"'
+	    fi
+	  fi
+
+	  exit $EXIT_SUCCESS
+	fi
+
+	# Create links to the real library.
+	for linkname in $linknames; do
+	  if test "$realname" != "$linkname"; then
+	    func_show_eval '(cd "$output_objdir" && $RM "$linkname" && $LN_S "$realname" "$linkname")' 'exit $?'
+	  fi
+	done
+
+	# If -module or -export-dynamic was specified, set the dlname.
+	if test "$module" = yes || test "$export_dynamic" = yes; then
+	  # On all known operating systems, these are identical.
+	  dlname="$soname"
+	fi
+      fi
+      ;;
+
+    obj)
+      if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
+	func_warning "\`-dlopen' is ignored for objects"
+      fi
+
+      case " $deplibs" in
+      *\ -l* | *\ -L*)
+	func_warning "\`-l' and \`-L' are ignored for objects" ;;
+      esac
+
+      test -n "$rpath" && \
+	func_warning "\`-rpath' is ignored for objects"
+
+      test -n "$xrpath" && \
+	func_warning "\`-R' is ignored for objects"
+
+      test -n "$vinfo" && \
+	func_warning "\`-version-info' is ignored for objects"
+
+      test -n "$release" && \
+	func_warning "\`-release' is ignored for objects"
+
+      case $output in
+      *.lo)
+	test -n "$objs$old_deplibs" && \
+	  func_fatal_error "cannot build library object \`$output' from non-libtool objects"
+
+	libobj=$output
+	func_lo2o "$libobj"
+	obj=$func_lo2o_result
+	;;
+      *)
+	libobj=
+	obj="$output"
+	;;
+      esac
+
+      # Delete the old objects.
+      $opt_dry_run || $RM $obj $libobj
+
+      # Objects from convenience libraries.  This assumes
+      # single-version convenience libraries.  Whenever we create
+      # different ones for PIC/non-PIC, this we'll have to duplicate
+      # the extraction.
+      reload_conv_objs=
+      gentop=
+      # reload_cmds runs $LD directly, so let us get rid of
+      # -Wl from whole_archive_flag_spec and hope we can get by with
+      # turning comma into space..
+      wl=
+
+      if test -n "$convenience"; then
+	if test -n "$whole_archive_flag_spec"; then
+	  eval tmp_whole_archive_flags=\"$whole_archive_flag_spec\"
+	  reload_conv_objs=$reload_objs\ `$ECHO "$tmp_whole_archive_flags" | $SED 's|,| |g'`
+	else
+	  gentop="$output_objdir/${obj}x"
+	  func_append generated " $gentop"
+
+	  func_extract_archives $gentop $convenience
+	  reload_conv_objs="$reload_objs $func_extract_archives_result"
+	fi
+      fi
+
+      # If we're not building shared, we need to use non_pic_objs
+      test "$build_libtool_libs" != yes && libobjs="$non_pic_objects"
+
+      # Create the old-style object.
+      reload_objs="$objs$old_deplibs "`$ECHO "$libobjs" | $SP2NL | $SED "/\.${libext}$/d; /\.lib$/d; $lo2o" | $NL2SP`" $reload_conv_objs" ### testsuite: skip nested quoting test
+
+      output="$obj"
+      func_execute_cmds "$reload_cmds" 'exit $?'
+
+      # Exit if we aren't doing a library object file.
+      if test -z "$libobj"; then
+	if test -n "$gentop"; then
+	  func_show_eval '${RM}r "$gentop"'
+	fi
+
+	exit $EXIT_SUCCESS
+      fi
+
+      if test "$build_libtool_libs" != yes; then
+	if test -n "$gentop"; then
+	  func_show_eval '${RM}r "$gentop"'
+	fi
+
+	# Create an invalid libtool object if no PIC, so that we don't
+	# accidentally link it into a program.
+	# $show "echo timestamp > $libobj"
+	# $opt_dry_run || eval "echo timestamp > $libobj" || exit $?
+	exit $EXIT_SUCCESS
+      fi
+
+      if test -n "$pic_flag" || test "$pic_mode" != default; then
+	# Only do commands if we really have different PIC objects.
+	reload_objs="$libobjs $reload_conv_objs"
+	output="$libobj"
+	func_execute_cmds "$reload_cmds" 'exit $?'
+      fi
+
+      if test -n "$gentop"; then
+	func_show_eval '${RM}r "$gentop"'
+      fi
+
+      exit $EXIT_SUCCESS
+      ;;
+
+    prog)
+      case $host in
+	*cygwin*) func_stripname '' '.exe' "$output"
+	          output=$func_stripname_result.exe;;
+      esac
+      test -n "$vinfo" && \
+	func_warning "\`-version-info' is ignored for programs"
+
+      test -n "$release" && \
+	func_warning "\`-release' is ignored for programs"
+
+      test "$preload" = yes \
+        && test "$dlopen_support" = unknown \
+	&& test "$dlopen_self" = unknown \
+	&& test "$dlopen_self_static" = unknown && \
+	  func_warning "\`LT_INIT([dlopen])' not used. Assuming no dlopen support."
+
+      case $host in
+      *-*-rhapsody* | *-*-darwin1.[012])
+	# On Rhapsody replace the C library is the System framework
+	compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's/ -lc / System.ltframework /'`
+	finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's/ -lc / System.ltframework /'`
+	;;
+      esac
+
+      case $host in
+      *-*-darwin*)
+	# Don't allow lazy linking, it breaks C++ global constructors
+	# But is supposedly fixed on 10.4 or later (yay!).
+	if test "$tagname" = CXX ; then
+	  case ${MACOSX_DEPLOYMENT_TARGET-10.0} in
+	    10.[0123])
+	      func_append compile_command " ${wl}-bind_at_load"
+	      func_append finalize_command " ${wl}-bind_at_load"
+	    ;;
+	  esac
+	fi
+	# Time to change all our "foo.ltframework" stuff back to "-framework foo"
+	compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+	finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+	;;
+      esac
+
+
+      # move library search paths that coincide with paths to not yet
+      # installed libraries to the beginning of the library search list
+      new_libs=
+      for path in $notinst_path; do
+	case " $new_libs " in
+	*" -L$path/$objdir "*) ;;
+	*)
+	  case " $compile_deplibs " in
+	  *" -L$path/$objdir "*)
+	    func_append new_libs " -L$path/$objdir" ;;
+	  esac
+	  ;;
+	esac
+      done
+      for deplib in $compile_deplibs; do
+	case $deplib in
+	-L*)
+	  case " $new_libs " in
+	  *" $deplib "*) ;;
+	  *) func_append new_libs " $deplib" ;;
+	  esac
+	  ;;
+	*) func_append new_libs " $deplib" ;;
+	esac
+      done
+      compile_deplibs="$new_libs"
+
+
+      func_append compile_command " $compile_deplibs"
+      func_append finalize_command " $finalize_deplibs"
+
+      if test -n "$rpath$xrpath"; then
+	# If the user specified any rpath flags, then add them.
+	for libdir in $rpath $xrpath; do
+	  # This is the magic to use -rpath.
+	  case "$finalize_rpath " in
+	  *" $libdir "*) ;;
+	  *) func_append finalize_rpath " $libdir" ;;
+	  esac
+	done
+      fi
+
+      # Now hardcode the library paths
+      rpath=
+      hardcode_libdirs=
+      for libdir in $compile_rpath $finalize_rpath; do
+	if test -n "$hardcode_libdir_flag_spec"; then
+	  if test -n "$hardcode_libdir_separator"; then
+	    if test -z "$hardcode_libdirs"; then
+	      hardcode_libdirs="$libdir"
+	    else
+	      # Just accumulate the unique libdirs.
+	      case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+	      *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+		;;
+	      *)
+		func_append hardcode_libdirs "$hardcode_libdir_separator$libdir"
+		;;
+	      esac
+	    fi
+	  else
+	    eval flag=\"$hardcode_libdir_flag_spec\"
+	    func_append rpath " $flag"
+	  fi
+	elif test -n "$runpath_var"; then
+	  case "$perm_rpath " in
+	  *" $libdir "*) ;;
+	  *) func_append perm_rpath " $libdir" ;;
+	  esac
+	fi
+	case $host in
+	*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*)
+	  testbindir=`${ECHO} "$libdir" | ${SED} -e 's*/lib$*/bin*'`
+	  case :$dllsearchpath: in
+	  *":$libdir:"*) ;;
+	  ::) dllsearchpath=$libdir;;
+	  *) func_append dllsearchpath ":$libdir";;
+	  esac
+	  case :$dllsearchpath: in
+	  *":$testbindir:"*) ;;
+	  ::) dllsearchpath=$testbindir;;
+	  *) func_append dllsearchpath ":$testbindir";;
+	  esac
+	  ;;
+	esac
+      done
+      # Substitute the hardcoded libdirs into the rpath.
+      if test -n "$hardcode_libdir_separator" &&
+	 test -n "$hardcode_libdirs"; then
+	libdir="$hardcode_libdirs"
+	eval rpath=\" $hardcode_libdir_flag_spec\"
+      fi
+      compile_rpath="$rpath"
+
+      rpath=
+      hardcode_libdirs=
+      for libdir in $finalize_rpath; do
+	if test -n "$hardcode_libdir_flag_spec"; then
+	  if test -n "$hardcode_libdir_separator"; then
+	    if test -z "$hardcode_libdirs"; then
+	      hardcode_libdirs="$libdir"
+	    else
+	      # Just accumulate the unique libdirs.
+	      case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+	      *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+		;;
+	      *)
+		func_append hardcode_libdirs "$hardcode_libdir_separator$libdir"
+		;;
+	      esac
+	    fi
+	  else
+	    eval flag=\"$hardcode_libdir_flag_spec\"
+	    func_append rpath " $flag"
+	  fi
+	elif test -n "$runpath_var"; then
+	  case "$finalize_perm_rpath " in
+	  *" $libdir "*) ;;
+	  *) func_append finalize_perm_rpath " $libdir" ;;
+	  esac
+	fi
+      done
+      # Substitute the hardcoded libdirs into the rpath.
+      if test -n "$hardcode_libdir_separator" &&
+	 test -n "$hardcode_libdirs"; then
+	libdir="$hardcode_libdirs"
+	eval rpath=\" $hardcode_libdir_flag_spec\"
+      fi
+      finalize_rpath="$rpath"
+
+      if test -n "$libobjs" && test "$build_old_libs" = yes; then
+	# Transform all the library objects into standard objects.
+	compile_command=`$ECHO "$compile_command" | $SP2NL | $SED "$lo2o" | $NL2SP`
+	finalize_command=`$ECHO "$finalize_command" | $SP2NL | $SED "$lo2o" | $NL2SP`
+      fi
+
+      func_generate_dlsyms "$outputname" "@PROGRAM@" "no"
+
+      # template prelinking step
+      if test -n "$prelink_cmds"; then
+	func_execute_cmds "$prelink_cmds" 'exit $?'
+      fi
+
+      wrappers_required=yes
+      case $host in
+      *cegcc* | *mingw32ce*)
+        # Disable wrappers for cegcc and mingw32ce hosts, we are cross compiling anyway.
+        wrappers_required=no
+        ;;
+      *cygwin* | *mingw* )
+        if test "$build_libtool_libs" != yes; then
+          wrappers_required=no
+        fi
+        ;;
+      *)
+        if test "$need_relink" = no || test "$build_libtool_libs" != yes; then
+          wrappers_required=no
+        fi
+        ;;
+      esac
+      if test "$wrappers_required" = no; then
+	# Replace the output file specification.
+	compile_command=`$ECHO "$compile_command" | $SED 's%@OUTPUT@%'"$output"'%g'`
+	link_command="$compile_command$compile_rpath"
+
+	# We have no uninstalled library dependencies, so finalize right now.
+	exit_status=0
+	func_show_eval "$link_command" 'exit_status=$?'
+
+	if test -n "$postlink_cmds"; then
+	  func_to_tool_file "$output"
+	  postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'`
+	  func_execute_cmds "$postlink_cmds" 'exit $?'
+	fi
+
+	# Delete the generated files.
+	if test -f "$output_objdir/${outputname}S.${objext}"; then
+	  func_show_eval '$RM "$output_objdir/${outputname}S.${objext}"'
+	fi
+
+	exit $exit_status
+      fi
+
+      if test -n "$compile_shlibpath$finalize_shlibpath"; then
+	compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command"
+      fi
+      if test -n "$finalize_shlibpath"; then
+	finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command"
+      fi
+
+      compile_var=
+      finalize_var=
+      if test -n "$runpath_var"; then
+	if test -n "$perm_rpath"; then
+	  # We should set the runpath_var.
+	  rpath=
+	  for dir in $perm_rpath; do
+	    func_append rpath "$dir:"
+	  done
+	  compile_var="$runpath_var=\"$rpath\$$runpath_var\" "
+	fi
+	if test -n "$finalize_perm_rpath"; then
+	  # We should set the runpath_var.
+	  rpath=
+	  for dir in $finalize_perm_rpath; do
+	    func_append rpath "$dir:"
+	  done
+	  finalize_var="$runpath_var=\"$rpath\$$runpath_var\" "
+	fi
+      fi
+
+      if test "$no_install" = yes; then
+	# We don't need to create a wrapper script.
+	link_command="$compile_var$compile_command$compile_rpath"
+	# Replace the output file specification.
+	link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output"'%g'`
+	# Delete the old output file.
+	$opt_dry_run || $RM $output
+	# Link the executable and exit
+	func_show_eval "$link_command" 'exit $?'
+
+	if test -n "$postlink_cmds"; then
+	  func_to_tool_file "$output"
+	  postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'`
+	  func_execute_cmds "$postlink_cmds" 'exit $?'
+	fi
+
+	exit $EXIT_SUCCESS
+      fi
+
+      if test "$hardcode_action" = relink; then
+	# Fast installation is not supported
+	link_command="$compile_var$compile_command$compile_rpath"
+	relink_command="$finalize_var$finalize_command$finalize_rpath"
+
+	func_warning "this platform does not like uninstalled shared libraries"
+	func_warning "\`$output' will be relinked during installation"
+      else
+	if test "$fast_install" != no; then
+	  link_command="$finalize_var$compile_command$finalize_rpath"
+	  if test "$fast_install" = yes; then
+	    relink_command=`$ECHO "$compile_var$compile_command$compile_rpath" | $SED 's%@OUTPUT@%\$progdir/\$file%g'`
+	  else
+	    # fast_install is set to needless
+	    relink_command=
+	  fi
+	else
+	  link_command="$compile_var$compile_command$compile_rpath"
+	  relink_command="$finalize_var$finalize_command$finalize_rpath"
+	fi
+      fi
+
+      # Replace the output file specification.
+      link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'`
+
+      # Delete the old output files.
+      $opt_dry_run || $RM $output $output_objdir/$outputname $output_objdir/lt-$outputname
+
+      func_show_eval "$link_command" 'exit $?'
+
+      if test -n "$postlink_cmds"; then
+	func_to_tool_file "$output_objdir/$outputname"
+	postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'`
+	func_execute_cmds "$postlink_cmds" 'exit $?'
+      fi
+
+      # Now create the wrapper script.
+      func_verbose "creating $output"
+
+      # Quote the relink command for shipping.
+      if test -n "$relink_command"; then
+	# Preserve any variables that may affect compiler behavior
+	for var in $variables_saved_for_relink; do
+	  if eval test -z \"\${$var+set}\"; then
+	    relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command"
+	  elif eval var_value=\$$var; test -z "$var_value"; then
+	    relink_command="$var=; export $var; $relink_command"
+	  else
+	    func_quote_for_eval "$var_value"
+	    relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command"
+	  fi
+	done
+	relink_command="(cd `pwd`; $relink_command)"
+	relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"`
+      fi
+
+      # Only actually do things if not in dry run mode.
+      $opt_dry_run || {
+	# win32 will think the script is a binary if it has
+	# a .exe suffix, so we strip it off here.
+	case $output in
+	  *.exe) func_stripname '' '.exe' "$output"
+	         output=$func_stripname_result ;;
+	esac
+	# test for cygwin because mv fails w/o .exe extensions
+	case $host in
+	  *cygwin*)
+	    exeext=.exe
+	    func_stripname '' '.exe' "$outputname"
+	    outputname=$func_stripname_result ;;
+	  *) exeext= ;;
+	esac
+	case $host in
+	  *cygwin* | *mingw* )
+	    func_dirname_and_basename "$output" "" "."
+	    output_name=$func_basename_result
+	    output_path=$func_dirname_result
+	    cwrappersource="$output_path/$objdir/lt-$output_name.c"
+	    cwrapper="$output_path/$output_name.exe"
+	    $RM $cwrappersource $cwrapper
+	    trap "$RM $cwrappersource $cwrapper; exit $EXIT_FAILURE" 1 2 15
+
+	    func_emit_cwrapperexe_src > $cwrappersource
+
+	    # The wrapper executable is built using the $host compiler,
+	    # because it contains $host paths and files. If cross-
+	    # compiling, it, like the target executable, must be
+	    # executed on the $host or under an emulation environment.
+	    $opt_dry_run || {
+	      $LTCC $LTCFLAGS -o $cwrapper $cwrappersource
+	      $STRIP $cwrapper
+	    }
+
+	    # Now, create the wrapper script for func_source use:
+	    func_ltwrapper_scriptname $cwrapper
+	    $RM $func_ltwrapper_scriptname_result
+	    trap "$RM $func_ltwrapper_scriptname_result; exit $EXIT_FAILURE" 1 2 15
+	    $opt_dry_run || {
+	      # note: this script will not be executed, so do not chmod.
+	      if test "x$build" = "x$host" ; then
+		$cwrapper --lt-dump-script > $func_ltwrapper_scriptname_result
+	      else
+		func_emit_wrapper no > $func_ltwrapper_scriptname_result
+	      fi
+	    }
+	  ;;
+	  * )
+	    $RM $output
+	    trap "$RM $output; exit $EXIT_FAILURE" 1 2 15
+
+	    func_emit_wrapper no > $output
+	    chmod +x $output
+	  ;;
+	esac
+      }
+      exit $EXIT_SUCCESS
+      ;;
+    esac
+
+    # See if we need to build an old-fashioned archive.
+    for oldlib in $oldlibs; do
+
+      if test "$build_libtool_libs" = convenience; then
+	oldobjs="$libobjs_save $symfileobj"
+	addlibs="$convenience"
+	build_libtool_libs=no
+      else
+	if test "$build_libtool_libs" = module; then
+	  oldobjs="$libobjs_save"
+	  build_libtool_libs=no
+	else
+	  oldobjs="$old_deplibs $non_pic_objects"
+	  if test "$preload" = yes && test -f "$symfileobj"; then
+	    func_append oldobjs " $symfileobj"
+	  fi
+	fi
+	addlibs="$old_convenience"
+      fi
+
+      if test -n "$addlibs"; then
+	gentop="$output_objdir/${outputname}x"
+	func_append generated " $gentop"
+
+	func_extract_archives $gentop $addlibs
+	func_append oldobjs " $func_extract_archives_result"
+      fi
+
+      # Do each command in the archive commands.
+      if test -n "$old_archive_from_new_cmds" && test "$build_libtool_libs" = yes; then
+	cmds=$old_archive_from_new_cmds
+      else
+
+	# Add any objects from preloaded convenience libraries
+	if test -n "$dlprefiles"; then
+	  gentop="$output_objdir/${outputname}x"
+	  func_append generated " $gentop"
+
+	  func_extract_archives $gentop $dlprefiles
+	  func_append oldobjs " $func_extract_archives_result"
+	fi
+
+	# POSIX demands no paths to be encoded in archives.  We have
+	# to avoid creating archives with duplicate basenames if we
+	# might have to extract them afterwards, e.g., when creating a
+	# static archive out of a convenience library, or when linking
+	# the entirety of a libtool archive into another (currently
+	# not supported by libtool).
+	if (for obj in $oldobjs
+	    do
+	      func_basename "$obj"
+	      $ECHO "$func_basename_result"
+	    done | sort | sort -uc >/dev/null 2>&1); then
+	  :
+	else
+	  echo "copying selected object files to avoid basename conflicts..."
+	  gentop="$output_objdir/${outputname}x"
+	  func_append generated " $gentop"
+	  func_mkdir_p "$gentop"
+	  save_oldobjs=$oldobjs
+	  oldobjs=
+	  counter=1
+	  for obj in $save_oldobjs
+	  do
+	    func_basename "$obj"
+	    objbase="$func_basename_result"
+	    case " $oldobjs " in
+	    " ") oldobjs=$obj ;;
+	    *[\ /]"$objbase "*)
+	      while :; do
+		# Make sure we don't pick an alternate name that also
+		# overlaps.
+		newobj=lt$counter-$objbase
+		func_arith $counter + 1
+		counter=$func_arith_result
+		case " $oldobjs " in
+		*[\ /]"$newobj "*) ;;
+		*) if test ! -f "$gentop/$newobj"; then break; fi ;;
+		esac
+	      done
+	      func_show_eval "ln $obj $gentop/$newobj || cp $obj $gentop/$newobj"
+	      func_append oldobjs " $gentop/$newobj"
+	      ;;
+	    *) func_append oldobjs " $obj" ;;
+	    esac
+	  done
+	fi
+	func_to_tool_file "$oldlib" func_convert_file_msys_to_w32
+	tool_oldlib=$func_to_tool_file_result
+	eval cmds=\"$old_archive_cmds\"
+
+	func_len " $cmds"
+	len=$func_len_result
+	if test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then
+	  cmds=$old_archive_cmds
+	elif test -n "$archiver_list_spec"; then
+	  func_verbose "using command file archive linking..."
+	  for obj in $oldobjs
+	  do
+	    func_to_tool_file "$obj"
+	    $ECHO "$func_to_tool_file_result"
+	  done > $output_objdir/$libname.libcmd
+	  func_to_tool_file "$output_objdir/$libname.libcmd"
+	  oldobjs=" $archiver_list_spec$func_to_tool_file_result"
+	  cmds=$old_archive_cmds
+	else
+	  # the command line is too long to link in one step, link in parts
+	  func_verbose "using piecewise archive linking..."
+	  save_RANLIB=$RANLIB
+	  RANLIB=:
+	  objlist=
+	  concat_cmds=
+	  save_oldobjs=$oldobjs
+	  oldobjs=
+	  # Is there a better way of finding the last object in the list?
+	  for obj in $save_oldobjs
+	  do
+	    last_oldobj=$obj
+	  done
+	  eval test_cmds=\"$old_archive_cmds\"
+	  func_len " $test_cmds"
+	  len0=$func_len_result
+	  len=$len0
+	  for obj in $save_oldobjs
+	  do
+	    func_len " $obj"
+	    func_arith $len + $func_len_result
+	    len=$func_arith_result
+	    func_append objlist " $obj"
+	    if test "$len" -lt "$max_cmd_len"; then
+	      :
+	    else
+	      # the above command should be used before it gets too long
+	      oldobjs=$objlist
+	      if test "$obj" = "$last_oldobj" ; then
+		RANLIB=$save_RANLIB
+	      fi
+	      test -z "$concat_cmds" || concat_cmds=$concat_cmds~
+	      eval concat_cmds=\"\${concat_cmds}$old_archive_cmds\"
+	      objlist=
+	      len=$len0
+	    fi
+	  done
+	  RANLIB=$save_RANLIB
+	  oldobjs=$objlist
+	  if test "X$oldobjs" = "X" ; then
+	    eval cmds=\"\$concat_cmds\"
+	  else
+	    eval cmds=\"\$concat_cmds~\$old_archive_cmds\"
+	  fi
+	fi
+      fi
+      func_execute_cmds "$cmds" 'exit $?'
+    done
+
+    test -n "$generated" && \
+      func_show_eval "${RM}r$generated"
+
+    # Now create the libtool archive.
+    case $output in
+    *.la)
+      old_library=
+      test "$build_old_libs" = yes && old_library="$libname.$libext"
+      func_verbose "creating $output"
+
+      # Preserve any variables that may affect compiler behavior
+      for var in $variables_saved_for_relink; do
+	if eval test -z \"\${$var+set}\"; then
+	  relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command"
+	elif eval var_value=\$$var; test -z "$var_value"; then
+	  relink_command="$var=; export $var; $relink_command"
+	else
+	  func_quote_for_eval "$var_value"
+	  relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command"
+	fi
+      done
+      # Quote the link command for shipping.
+      relink_command="(cd `pwd`; $SHELL $progpath $preserve_args --mode=relink $libtool_args @inst_prefix_dir@)"
+      relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"`
+      if test "$hardcode_automatic" = yes ; then
+	relink_command=
+      fi
+
+      # Only create the output if not a dry run.
+      $opt_dry_run || {
+	for installed in no yes; do
+	  if test "$installed" = yes; then
+	    if test -z "$install_libdir"; then
+	      break
+	    fi
+	    output="$output_objdir/$outputname"i
+	    # Replace all uninstalled libtool libraries with the installed ones
+	    newdependency_libs=
+	    for deplib in $dependency_libs; do
+	      case $deplib in
+	      *.la)
+		func_basename "$deplib"
+		name="$func_basename_result"
+		func_resolve_sysroot "$deplib"
+		eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $func_resolve_sysroot_result`
+		test -z "$libdir" && \
+		  func_fatal_error "\`$deplib' is not a valid libtool archive"
+		func_append newdependency_libs " ${lt_sysroot:+=}$libdir/$name"
+		;;
+	      -L*)
+		func_stripname -L '' "$deplib"
+		func_replace_sysroot "$func_stripname_result"
+		func_append newdependency_libs " -L$func_replace_sysroot_result"
+		;;
+	      -R*)
+		func_stripname -R '' "$deplib"
+		func_replace_sysroot "$func_stripname_result"
+		func_append newdependency_libs " -R$func_replace_sysroot_result"
+		;;
+	      *) func_append newdependency_libs " $deplib" ;;
+	      esac
+	    done
+	    dependency_libs="$newdependency_libs"
+	    newdlfiles=
+
+	    for lib in $dlfiles; do
+	      case $lib in
+	      *.la)
+	        func_basename "$lib"
+		name="$func_basename_result"
+		eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $lib`
+		test -z "$libdir" && \
+		  func_fatal_error "\`$lib' is not a valid libtool archive"
+		func_append newdlfiles " ${lt_sysroot:+=}$libdir/$name"
+		;;
+	      *) func_append newdlfiles " $lib" ;;
+	      esac
+	    done
+	    dlfiles="$newdlfiles"
+	    newdlprefiles=
+	    for lib in $dlprefiles; do
+	      case $lib in
+	      *.la)
+		# Only pass preopened files to the pseudo-archive (for
+		# eventual linking with the app. that links it) if we
+		# didn't already link the preopened objects directly into
+		# the library:
+		func_basename "$lib"
+		name="$func_basename_result"
+		eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $lib`
+		test -z "$libdir" && \
+		  func_fatal_error "\`$lib' is not a valid libtool archive"
+		func_append newdlprefiles " ${lt_sysroot:+=}$libdir/$name"
+		;;
+	      esac
+	    done
+	    dlprefiles="$newdlprefiles"
+	  else
+	    newdlfiles=
+	    for lib in $dlfiles; do
+	      case $lib in
+		[\\/]* | [A-Za-z]:[\\/]*) abs="$lib" ;;
+		*) abs=`pwd`"/$lib" ;;
+	      esac
+	      func_append newdlfiles " $abs"
+	    done
+	    dlfiles="$newdlfiles"
+	    newdlprefiles=
+	    for lib in $dlprefiles; do
+	      case $lib in
+		[\\/]* | [A-Za-z]:[\\/]*) abs="$lib" ;;
+		*) abs=`pwd`"/$lib" ;;
+	      esac
+	      func_append newdlprefiles " $abs"
+	    done
+	    dlprefiles="$newdlprefiles"
+	  fi
+	  $RM $output
+	  # place dlname in correct position for cygwin
+	  # In fact, it would be nice if we could use this code for all target
+	  # systems that can't hard-code library paths into their executables
+	  # and that have no shared library path variable independent of PATH,
+	  # but it turns out we can't easily determine that from inspecting
+	  # libtool variables, so we have to hard-code the OSs to which it
+	  # applies here; at the moment, that means platforms that use the PE
+	  # object format with DLL files.  See the long comment at the top of
+	  # tests/bindir.at for full details.
+	  tdlname=$dlname
+	  case $host,$output,$installed,$module,$dlname in
+	    *cygwin*,*lai,yes,no,*.dll | *mingw*,*lai,yes,no,*.dll | *cegcc*,*lai,yes,no,*.dll)
+	      # If a -bindir argument was supplied, place the dll there.
+	      if test "x$bindir" != x ;
+	      then
+		func_relative_path "$install_libdir" "$bindir"
+		tdlname=$func_relative_path_result$dlname
+	      else
+		# Otherwise fall back on heuristic.
+		tdlname=../bin/$dlname
+	      fi
+	      ;;
+	  esac
+	  $ECHO > $output "\
+# $outputname - a libtool library file
+# Generated by $PROGRAM (GNU $PACKAGE$TIMESTAMP) $VERSION
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname='$tdlname'
+
+# Names of this library.
+library_names='$library_names'
+
+# The name of the static archive.
+old_library='$old_library'
+
+# Linker flags that can not go in dependency_libs.
+inherited_linker_flags='$new_inherited_linker_flags'
+
+# Libraries that this one depends upon.
+dependency_libs='$dependency_libs'
+
+# Names of additional weak libraries provided by this library
+weak_library_names='$weak_libs'
+
+# Version information for $libname.
+current=$current
+age=$age
+revision=$revision
+
+# Is this an already installed library?
+installed=$installed
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=$module
+
+# Files to dlopen/dlpreopen
+dlopen='$dlfiles'
+dlpreopen='$dlprefiles'
+
+# Directory that this library needs to be installed in:
+libdir='$install_libdir'"
+	  if test "$installed" = no && test "$need_relink" = yes; then
+	    $ECHO >> $output "\
+relink_command=\"$relink_command\""
+	  fi
+	done
+      }
+
+      # Do a symbolic link so that the libtool archive can be found in
+      # LD_LIBRARY_PATH before the program is installed.
+      func_show_eval '( cd "$output_objdir" && $RM "$outputname" && $LN_S "../$outputname" "$outputname" )' 'exit $?'
+      ;;
+    esac
+    exit $EXIT_SUCCESS
+}
+
+{ test "$opt_mode" = link || test "$opt_mode" = relink; } &&
+    func_mode_link ${1+"$@"}
+
+
+# func_mode_uninstall arg...
+func_mode_uninstall ()
+{
+    $opt_debug
+    RM="$nonopt"
+    files=
+    rmforce=
+    exit_status=0
+
+    # This variable tells wrapper scripts just to set variables rather
+    # than running their programs.
+    libtool_install_magic="$magic"
+
+    for arg
+    do
+      case $arg in
+      -f) func_append RM " $arg"; rmforce=yes ;;
+      -*) func_append RM " $arg" ;;
+      *) func_append files " $arg" ;;
+      esac
+    done
+
+    test -z "$RM" && \
+      func_fatal_help "you must specify an RM program"
+
+    rmdirs=
+
+    for file in $files; do
+      func_dirname "$file" "" "."
+      dir="$func_dirname_result"
+      if test "X$dir" = X.; then
+	odir="$objdir"
+      else
+	odir="$dir/$objdir"
+      fi
+      func_basename "$file"
+      name="$func_basename_result"
+      test "$opt_mode" = uninstall && odir="$dir"
+
+      # Remember odir for removal later, being careful to avoid duplicates
+      if test "$opt_mode" = clean; then
+	case " $rmdirs " in
+	  *" $odir "*) ;;
+	  *) func_append rmdirs " $odir" ;;
+	esac
+      fi
+
+      # Don't error if the file doesn't exist and rm -f was used.
+      if { test -L "$file"; } >/dev/null 2>&1 ||
+	 { test -h "$file"; } >/dev/null 2>&1 ||
+	 test -f "$file"; then
+	:
+      elif test -d "$file"; then
+	exit_status=1
+	continue
+      elif test "$rmforce" = yes; then
+	continue
+      fi
+
+      rmfiles="$file"
+
+      case $name in
+      *.la)
+	# Possibly a libtool archive, so verify it.
+	if func_lalib_p "$file"; then
+	  func_source $dir/$name
+
+	  # Delete the libtool libraries and symlinks.
+	  for n in $library_names; do
+	    func_append rmfiles " $odir/$n"
+	  done
+	  test -n "$old_library" && func_append rmfiles " $odir/$old_library"
+
+	  case "$opt_mode" in
+	  clean)
+	    case " $library_names " in
+	    *" $dlname "*) ;;
+	    *) test -n "$dlname" && func_append rmfiles " $odir/$dlname" ;;
+	    esac
+	    test -n "$libdir" && func_append rmfiles " $odir/$name $odir/${name}i"
+	    ;;
+	  uninstall)
+	    if test -n "$library_names"; then
+	      # Do each command in the postuninstall commands.
+	      func_execute_cmds "$postuninstall_cmds" 'test "$rmforce" = yes || exit_status=1'
+	    fi
+
+	    if test -n "$old_library"; then
+	      # Do each command in the old_postuninstall commands.
+	      func_execute_cmds "$old_postuninstall_cmds" 'test "$rmforce" = yes || exit_status=1'
+	    fi
+	    # FIXME: should reinstall the best remaining shared library.
+	    ;;
+	  esac
+	fi
+	;;
+
+      *.lo)
+	# Possibly a libtool object, so verify it.
+	if func_lalib_p "$file"; then
+
+	  # Read the .lo file
+	  func_source $dir/$name
+
+	  # Add PIC object to the list of files to remove.
+	  if test -n "$pic_object" &&
+	     test "$pic_object" != none; then
+	    func_append rmfiles " $dir/$pic_object"
+	  fi
+
+	  # Add non-PIC object to the list of files to remove.
+	  if test -n "$non_pic_object" &&
+	     test "$non_pic_object" != none; then
+	    func_append rmfiles " $dir/$non_pic_object"
+	  fi
+	fi
+	;;
+
+      *)
+	if test "$opt_mode" = clean ; then
+	  noexename=$name
+	  case $file in
+	  *.exe)
+	    func_stripname '' '.exe' "$file"
+	    file=$func_stripname_result
+	    func_stripname '' '.exe' "$name"
+	    noexename=$func_stripname_result
+	    # $file with .exe has already been added to rmfiles,
+	    # add $file without .exe
+	    func_append rmfiles " $file"
+	    ;;
+	  esac
+	  # Do a test to see if this is a libtool program.
+	  if func_ltwrapper_p "$file"; then
+	    if func_ltwrapper_executable_p "$file"; then
+	      func_ltwrapper_scriptname "$file"
+	      relink_command=
+	      func_source $func_ltwrapper_scriptname_result
+	      func_append rmfiles " $func_ltwrapper_scriptname_result"
+	    else
+	      relink_command=
+	      func_source $dir/$noexename
+	    fi
+
+	    # note $name still contains .exe if it was in $file originally
+	    # as does the version of $file that was added into $rmfiles
+	    func_append rmfiles " $odir/$name $odir/${name}S.${objext}"
+	    if test "$fast_install" = yes && test -n "$relink_command"; then
+	      func_append rmfiles " $odir/lt-$name"
+	    fi
+	    if test "X$noexename" != "X$name" ; then
+	      func_append rmfiles " $odir/lt-${noexename}.c"
+	    fi
+	  fi
+	fi
+	;;
+      esac
+      func_show_eval "$RM $rmfiles" 'exit_status=1'
+    done
+
+    # Try to remove the ${objdir}s in the directories where we deleted files
+    for dir in $rmdirs; do
+      if test -d "$dir"; then
+	func_show_eval "rmdir $dir >/dev/null 2>&1"
+      fi
+    done
+
+    exit $exit_status
+}
+
+{ test "$opt_mode" = uninstall || test "$opt_mode" = clean; } &&
+    func_mode_uninstall ${1+"$@"}
+
+test -z "$opt_mode" && {
+  help="$generic_help"
+  func_fatal_help "you must specify a MODE"
+}
+
+test -z "$exec_cmd" && \
+  func_fatal_help "invalid operation mode \`$opt_mode'"
+
+if test -n "$exec_cmd"; then
+  eval exec "$exec_cmd"
+  exit $EXIT_FAILURE
+fi
+
+exit $exit_status
+
+
+# The TAGs below are defined such that we never get into a situation
+# in which we disable both kinds of libraries.  Given conflicting
+# choices, we go for a static library, that is the most portable,
+# since we can't tell whether shared libraries were disabled because
+# the user asked for that or because the platform doesn't support
+# them.  This is particularly important on AIX, because we don't
+# support having both static and shared libraries enabled at the same
+# time on that platform, so we default to a shared-only configuration.
+# If a disable-shared tag is given, we'll fallback to a static-only
+# configuration.  But we'll never go from static-only to shared-only.
+
+# ### BEGIN LIBTOOL TAG CONFIG: disable-shared
+build_libtool_libs=no
+build_old_libs=yes
+# ### END LIBTOOL TAG CONFIG: disable-shared
+
+# ### BEGIN LIBTOOL TAG CONFIG: disable-static
+build_old_libs=`case $build_libtool_libs in yes) echo no;; *) echo yes;; esac`
+# ### END LIBTOOL TAG CONFIG: disable-static
+
+# Local Variables:
+# mode:shell-script
+# sh-indentation:2
+# End:
+# vi:sw=2
+
diff --git a/ast/build-aux/missing b/ast/build-aux/missing
new file mode 100755
index 0000000..db98974
--- /dev/null
+++ b/ast/build-aux/missing
@@ -0,0 +1,215 @@
+#! /bin/sh
+# Common wrapper for a few potentially missing GNU programs.
+
+scriptversion=2013-10-28.13; # UTC
+
+# Copyright (C) 1996-2013 Free Software Foundation, Inc.
+# Originally written by Fran,cois Pinard <[email-masked]>, 1996.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+if test $# -eq 0; then
+  echo 1>&2 "Try '$0 --help' for more information"
+  exit 1
+fi
+
+case $1 in
+
+  --is-lightweight)
+    # Used by our autoconf macros to check whether the available missing
+    # script is modern enough.
+    exit 0
+    ;;
+
+  --run)
+    # Back-compat with the calling convention used by older automake.
+    shift
+    ;;
+
+  -h|--h|--he|--hel|--help)
+    echo "\
+$0 [OPTION]... PROGRAM [ARGUMENT]...
+
+Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due
+to PROGRAM being missing or too old.
+
+Options:
+  -h, --help      display this help and exit
+  -v, --version   output version information and exit
+
+Supported PROGRAM values:
+  aclocal   autoconf  autoheader   autom4te  automake  makeinfo
+  bison     yacc      flex         lex       help2man
+
+Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and
+'g' are ignored when checking the name.
+
+Send bug reports to <[email-masked]>."
+    exit $?
+    ;;
+
+  -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
+    echo "missing $scriptversion (GNU Automake)"
+    exit $?
+    ;;
+
+  -*)
+    echo 1>&2 "$0: unknown '$1' option"
+    echo 1>&2 "Try '$0 --help' for more information"
+    exit 1
+    ;;
+
+esac
+
+# Run the given program, remember its exit status.
+"$@"; st=$?
+
+# If it succeeded, we are done.
+test $st -eq 0 && exit 0
+
+# Also exit now if we it failed (or wasn't found), and '--version' was
+# passed; such an option is passed most likely to detect whether the
+# program is present and works.
+case $2 in --version|--help) exit $st;; esac
+
+# Exit code 63 means version mismatch.  This often happens when the user
+# tries to use an ancient version of a tool on a file that requires a
+# minimum version.
+if test $st -eq 63; then
+  msg="probably too old"
+elif test $st -eq 127; then
+  # Program was missing.
+  msg="missing on your system"
+else
+  # Program was found and executed, but failed.  Give up.
+  exit $st
+fi
+
+perl_URL=http://www.perl.org/
+flex_URL=http://flex.sourceforge.net/
+gnu_software_URL=http://www.gnu.org/software
+
+program_details ()
+{
+  case $1 in
+    aclocal|automake)
+      echo "The '$1' program is part of the GNU Automake package:"
+      echo "<$gnu_software_URL/automake>"
+      echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:"
+      echo "<$gnu_software_URL/autoconf>"
+      echo "<$gnu_software_URL/m4/>"
+      echo "<$perl_URL>"
+      ;;
+    autoconf|autom4te|autoheader)
+      echo "The '$1' program is part of the GNU Autoconf package:"
+      echo "<$gnu_software_URL/autoconf/>"
+      echo "It also requires GNU m4 and Perl in order to run:"
+      echo "<$gnu_software_URL/m4/>"
+      echo "<$perl_URL>"
+      ;;
+  esac
+}
+
+give_advice ()
+{
+  # Normalize program name to check for.
+  normalized_program=`echo "$1" | sed '
+    s/^gnu-//; t
+    s/^gnu//; t
+    s/^g//; t'`
+
+  printf '%s\n' "'$1' is $msg."
+
+  configure_deps="'configure.ac' or m4 files included by 'configure.ac'"
+  case $normalized_program in
+    autoconf*)
+      echo "You should only need it if you modified 'configure.ac',"
+      echo "or m4 files included by it."
+      program_details 'autoconf'
+      ;;
+    autoheader*)
+      echo "You should only need it if you modified 'acconfig.h' or"
+      echo "$configure_deps."
+      program_details 'autoheader'
+      ;;
+    automake*)
+      echo "You should only need it if you modified 'Makefile.am' or"
+      echo "$configure_deps."
+      program_details 'automake'
+      ;;
+    aclocal*)
+      echo "You should only need it if you modified 'acinclude.m4' or"
+      echo "$configure_deps."
+      program_details 'aclocal'
+      ;;
+   autom4te*)
+      echo "You might have modified some maintainer files that require"
+      echo "the 'autom4te' program to be rebuilt."
+      program_details 'autom4te'
+      ;;
+    bison*|yacc*)
+      echo "You should only need it if you modified a '.y' file."
+      echo "You may want to install the GNU Bison package:"
+      echo "<$gnu_software_URL/bison/>"
+      ;;
+    lex*|flex*)
+      echo "You should only need it if you modified a '.l' file."
+      echo "You may want to install the Fast Lexical Analyzer package:"
+      echo "<$flex_URL>"
+      ;;
+    help2man*)
+      echo "You should only need it if you modified a dependency" \
+           "of a man page."
+      echo "You may want to install the GNU Help2man package:"
+      echo "<$gnu_software_URL/help2man/>"
+    ;;
+    makeinfo*)
+      echo "You should only need it if you modified a '.texi' file, or"
+      echo "any other file indirectly affecting the aspect of the manual."
+      echo "You might want to install the Texinfo package:"
+      echo "<$gnu_software_URL/texinfo/>"
+      echo "The spurious makeinfo call might also be the consequence of"
+      echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might"
+      echo "want to install GNU make:"
+      echo "<$gnu_software_URL/make/>"
+      ;;
+    *)
+      echo "You might have modified some files without having the proper"
+      echo "tools for further handling them.  Check the 'README' file, it"
+      echo "often tells you about the needed prerequisites for installing"
+      echo "this package.  You may also peek at any GNU archive site, in"
+      echo "case some other package contains this missing '$1' program."
+      ;;
+  esac
+}
+
+give_advice "$1" | sed -e '1s/^/WARNING: /' \
+                       -e '2,$s/^/         /' >&2
+
+# Propagate the correct exit status (expected to be 127 for a program
+# not found, 63 for a program that failed due to version mismatch).
+exit $st
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/ast/build-aux/test-driver b/ast/build-aux/test-driver
new file mode 100755
index 0000000..d306056
--- /dev/null
+++ b/ast/build-aux/test-driver
@@ -0,0 +1,139 @@
+#! /bin/sh
+# test-driver - basic testsuite driver script.
+
+scriptversion=2013-07-13.22; # UTC
+
+# Copyright (C) 2011-2013 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <[email-masked]> or send patches to
+# <[email-masked]>.
+
+# Make unconditional expansion of undefined variables an error.  This
+# helps a lot in preventing typo-related bugs.
+set -u
+
+usage_error ()
+{
+  echo "$0: $*" >&2
+  print_usage >&2
+  exit 2
+}
+
+print_usage ()
+{
+  cat <<END
+Usage:
+  test-driver --test-name=NAME --log-file=PATH --trs-file=PATH
+              [--expect-failure={yes|no}] [--color-tests={yes|no}]
+              [--enable-hard-errors={yes|no}] [--]
+              TEST-SCRIPT [TEST-SCRIPT-ARGUMENTS]
+The '--test-name', '--log-file' and '--trs-file' options are mandatory.
+END
+}
+
+test_name= # Used for reporting.
+log_file=  # Where to save the output of the test script.
+trs_file=  # Where to save the metadata of the test run.
+expect_failure=no
+color_tests=no
+enable_hard_errors=yes
+while test $# -gt 0; do
+  case $1 in
+  --help) print_usage; exit $?;;
+  --version) echo "test-driver $scriptversion"; exit $?;;
+  --test-name) test_name=$2; shift;;
+  --log-file) log_file=$2; shift;;
+  --trs-file) trs_file=$2; shift;;
+  --color-tests) color_tests=$2; shift;;
+  --expect-failure) expect_failure=$2; shift;;
+  --enable-hard-errors) enable_hard_errors=$2; shift;;
+  --) shift; break;;
+  -*) usage_error "invalid option: '$1'";;
+   *) break;;
+  esac
+  shift
+done
+
+missing_opts=
+test x"$test_name" = x && missing_opts="$missing_opts --test-name"
+test x"$log_file"  = x && missing_opts="$missing_opts --log-file"
+test x"$trs_file"  = x && missing_opts="$missing_opts --trs-file"
+if test x"$missing_opts" != x; then
+  usage_error "the following mandatory options are missing:$missing_opts"
+fi
+
+if test $# -eq 0; then
+  usage_error "missing argument"
+fi
+
+if test $color_tests = yes; then
+  # Keep this in sync with 'lib/am/check.am:$(am__tty_colors)'.
+  red='[0;31m' # Red.
+  grn='[0;32m' # Green.
+  lgn='[1;32m' # Light green.
+  blu='[1;34m' # Blue.
+  mgn='[0;35m' # Magenta.
+  std='[m'     # No color.
+else
+  red= grn= lgn= blu= mgn= std=
+fi
+
+do_exit='rm -f $log_file $trs_file; (exit $st); exit $st'
+trap "st=129; $do_exit" 1
+trap "st=130; $do_exit" 2
+trap "st=141; $do_exit" 13
+trap "st=143; $do_exit" 15
+
+# Test script is run here.
+"$@" >$log_file 2>&1
+estatus=$?
+if test $enable_hard_errors = no && test $estatus -eq 99; then
+  estatus=1
+fi
+
+case $estatus:$expect_failure in
+  0:yes) col=$red res=XPASS recheck=yes gcopy=yes;;
+  0:*)   col=$grn res=PASS  recheck=no  gcopy=no;;
+  77:*)  col=$blu res=SKIP  recheck=no  gcopy=yes;;
+  99:*)  col=$mgn res=ERROR recheck=yes gcopy=yes;;
+  *:yes) col=$lgn res=XFAIL recheck=no  gcopy=yes;;
+  *:*)   col=$red res=FAIL  recheck=yes gcopy=yes;;
+esac
+
+# Report outcome to console.
+echo "${col}${res}${std}: $test_name"
+
+# Register the test result, and other relevant metadata.
+echo ":test-result: $res" > $trs_file
+echo ":global-test-result: $res" >> $trs_file
+echo ":recheck: $recheck" >> $trs_file
+echo ":copy-in-global-log: $gcopy" >> $trs_file
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/ast/c2f77.c b/ast/c2f77.c
new file mode 100644
index 0000000..1801a55
--- /dev/null
+++ b/ast/c2f77.c
@@ -0,0 +1,125 @@
+/*
+*  Name:
+*     c2f77.c
+
+*  Purpose:
+*     Implement the interface between the C and FORTRAN 77 languages.
+
+*  Description:
+*     This file implements language-specific functions which support
+*     the FORTRAN 77 interface to the AST library.
+*
+*     Note that this module is not a class implementation, although it
+*     resembles one.
+
+*  Notes:
+*     - Some of the functions in this module are potentially platform
+*     dependent and may need to be re-implemented when porting the AST
+*     library to new platforms.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     15-NOV-1996 (RFWS):
+*        Original version (based on work by DSB and on code from the
+*        Starlink CNF library).
+*/
+
+/* Define the astCLASS macro (even although this is not a class
+   implementation) to obtain access to protected interfaces. */
+#define astCLASS
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "error.h"               /* Error reporting facilities */
+#include "c2f77.h"               /* Interface to this module */
+
+/* Function implementations. */
+/* ========================= */
+void astStringExport_( const char *source_c, char *dest_f, int dest_len ) {
+/*
+*+
+*  Name:
+*     astStringExport
+
+*  Purpose:
+*     Export a C string to a FORTRAN string.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "c2f77.h"
+*     void astStringExport( const char *source_c, char *dest_f, int dest_len )
+
+*  Description:
+*     This function creates a FORTRAN string from a C string, storing
+*     it in the supplied memory. If the C string is shorter than the
+*     space allocated for the FORTRAN string, then it is padded with
+*     blanks. If the C string is longer than the space allocated for
+*     the FORTRAN string, then the string is truncated.
+
+*  Parameters:
+*     source_c
+*        A pointer to the input C string.
+*     dest_f
+*        A pointer to the output FORTRAN string.
+*     dest_len
+*        The length of the output FORTRAN string.
+
+*  Notes:
+*     - This function is potentially platform-specific. For example,
+*     if FORTRAN strings were passed by descriptor, then the
+*     descriptor address would be passed as "dest_f" and this must
+*     then be used to locate the actual FORTRAN character data.
+*     - This function is described as protected but is in fact
+*     available through the public interface so that it may be used in
+*     constructing the FORTRAN 77 public interface.
+*     - This is the UNIX version of this function.
+*-
+*/
+
+/* Local Variables:*/
+   int i;                        /* Loop counter for characters */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Copy the characters of the input C string to the output FORTRAN
+   string, taking care not to go beyond the end of the FORTRAN
+   string.*/
+   for ( i = 0; source_c[ i ] && ( i < dest_len ); i++ ) {
+      dest_f[ i ] = source_c[ i ];
+   }
+
+/* Fill the rest of the output FORTRAN string with blanks. */
+   for ( ; i < dest_len; i++ ) dest_f[ i ] = ' ';
+}
diff --git a/ast/c2f77.h b/ast/c2f77.h
new file mode 100644
index 0000000..c50edac
--- /dev/null
+++ b/ast/c2f77.h
@@ -0,0 +1,166 @@
+#if !defined( C2F77_INCLUDED )   /* Include this file only once */
+#define C2F77_INCLUDED
+/*
+*+
+*  Name:
+*     c2f77.h
+
+*  Purpose:
+*     Define the interface to the c2f77 module.
+
+*  Description:
+*     This file defines language-specific functions which support the
+*     FORTRAN 77 interface to the AST library.
+*
+*     Note that this module is not a class implementation, although it
+*     resembles one.
+
+*  Functions Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astStringExport
+*           Export a C string to a FORTRAN string.
+
+*  Macros Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astWatchSTATUS
+*           Execute C code while watching a FORTRAN STATUS variable.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     15-NOV-1996 (RFWS):
+*        Original version.
+*     16-JUL-1997 (RFWS):
+*        Added astWatchSTATUS.
+*     13-JUN-2001 (DSB):
+*        Make astStringExport available to F77 interface modules as well
+*        as AST classes.
+*-
+*/
+
+/* Macros. */
+/* ======= */
+/*
+*+
+*  Name:
+*     astWatchSTATUS
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Execute C code while watching a FORTRAN STATUS variable.
+
+*  Synopsis:
+*     #include "c2f77.h"
+*     astWatchSTATUS(code)
+
+*  Description:
+*     This macro expands to code which executes the C code supplied
+*     via the "code" argument in a new C scope (delimited by
+*     {...}). The code supplied executes while the AST error status is
+*     equated to a variable called STATUS, which is an error status
+*     argument passed from a FORTRAN routine using the macros defined
+*     in the "f77.h" include file.
+*
+*     The effect of this is roughly as if the astWatch function had
+*     been used to locally declare the FORTRAN STATUS argument as a
+*     new AST error status variable, except that this macro also works
+*     if STATUS is not an int.
+
+*  Parameters:
+*     code
+*        The C code to be executed.
+
+*  Examples:
+*     F77_SUBROUTINE(ast_doit)( INTEGER(STATUS) ) {
+*        astWatchSTATUS(
+*           astDoit();
+*        )
+*     }
+*        Causes the astDoit function to be invoked as if the AST error
+*        status were equated to the STATUS argument passed from
+*        FORTRAN.  Typically, if STATUS is set to an error value,
+*        astDoit would detect this by means of the astOK macro and
+*        would not then execute.  If an error occurs in astDoit,
+*        causing the AST error status to be set, then that value is
+*        transferred to STATUS after the C code has executed (i.e. at
+*        the end of the astWatchSTATUS macro).
+
+*  Notes:
+*     - The FORTRAN argument must be called STATUS and must appear in
+*     the C function's parameter list as an argument of the INTEGER()
+*     macro defined in the "f77.h" include file.
+*     - The C code supplied executes in a new scope, in which
+*     automatic variables may be declared. However, such variables
+*     will not exist after the macro's expansion has been executed.
+*     - The AST error status variable and its value remain unchanged
+*     after the expansion of this macro has executed.
+*-
+*/
+
+/* Define the macro. */
+#define astWatchSTATUS(code) \
+\
+/* Begin a new C scope. */ \
+{ \
+\
+/* Ensure that a pointer to the STATUS argument exists. */ \
+   GENPTR_INTEGER(STATUS) \
+\
+/* Store the STATUS value in a local int. */ \
+   int ast_local_status = *STATUS; \
+   int *status = &ast_local_status; \
+\
+/* Make this int the AST error status variable, saving the address of \
+   the previous variable. */ \
+   int *ast_previous_status = astWatch( &ast_local_status ); \
+\
+/* Execute the code supplied using the new error status variable. */ \
+   code \
+\
+/* Restore the original error status variable. */ \
+   (void) astWatch( ast_previous_status ); \
+\
+/* Return the final error status to STATUS. */ \
+   *STATUS = ast_local_status; \
+}
+
+/* Function prototypes. */
+/* ==================== */
+void astStringExport_( const char *, char *, int );
+
+/* Function interfaces. */
+/* ==================== */
+/* These wrap up the functions defined by this module to make them
+   easier to use. */
+#define astStringExport astStringExport_
+#endif
diff --git a/ast/channel.c b/ast/channel.c
new file mode 100644
index 0000000..3c37636
--- /dev/null
+++ b/ast/channel.c
@@ -0,0 +1,6458 @@
+/*
+*class++
+*  Name:
+*     Channel
+
+*  Purpose:
+*     Basic (textual) I/O channel.
+
+*  Constructor Function:
+c     astChannel
+f     AST_CHANNEL
+
+*  Description:
+*     The Channel class implements low-level input/output for the AST
+*     library.  Writing an Object to a Channel will generate a textual
+*     representation of that Object, and reading from a Channel will
+*     create a new Object from its textual representation.
+*
+*     Normally, when you use a Channel, you should provide "source"
+c     and "sink" functions which connect it to an external data store
+f     and "sink" routines which connect it to an external data store
+*     by reading and writing the resulting text. By default, however,
+*     a Channel will read from standard input and write to standard
+*     output. Alternatively, a Channel can be told to read or write from
+*     specific text files using the SinkFile and SourceFile attributes,
+*     in which case no sink or source function need be supplied.
+
+*  Inheritance:
+*     The Channel class inherits from the Object class.
+
+*  Attributes:
+*     In addition to those attributes common to all Objects, every
+*     Channel also has the following attributes:
+*
+*     - Comment: Include textual comments in output?
+*     - Full: Set level of output detail
+*     - Indent: Indentation increment between objects
+*     - ReportLevel: Selects the level of error reporting
+*     - SinkFile: The path to a file to which the Channel should write
+*     - Skip: Skip irrelevant data?
+*     - SourceFile: The path to a file from which the Channel should read
+*     - Strict: Generate errors instead of warnings?
+
+*  Functions:
+c     In addition to those functions applicable to all Objects, the
+c     following functions may also be applied to all Channels:
+f     In addition to those routines applicable to all Objects, the
+f     following routines may also be applied to all Channels:
+*
+c     - astWarnings: Return warnings from the previous read or write
+c     - astPutChannelData: Store data to pass to source or sink functions
+c     - astRead: Read an Object from a Channel
+c     - astWrite: Write an Object to a Channel
+f     - AST_WARNINGS: Return warnings from the previous read or write
+f     - AST_READ: Read an Object from a Channel
+f     - AST_WRITE: Write an Object to a Channel
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     12-AUG-1996 (RFWS):
+*        Original version.
+*     6-SEP-1996:
+*        Finished initial implementation.
+*     11-DEC-1996 (RFWS):
+*        Added support for foreign language source and sink functions.
+*     28-APR-1997 (RFWS):
+*        Prevent "-0" being written (use "0" instead).
+*     27-NOV-2002 (DSB):
+*        Added astWriteInvocations.
+*     8-JAN-2003 (DSB):
+*        - Changed private InitVtab method to protected astInitChannelVtab
+*        method.
+*        - Modified to use protected Vtab initialisation methods when
+*        loading an Object.
+*     1-NOV-2003 (DSB):
+*        Change the initialiser so that it accepts source and sink
+*        wrapper functions as arguments (for use by derived classes).
+*     16-AUG-2006 (DSB):
+*        - Document non-destructive nature of unsuccessful astRead calls
+*        on a FitsChan.
+*     3-OCT-2008 (DSB):
+*        Added "Strict" attribute.
+*     11-DEC-2008 (DSB):
+*        Added astPutChannelData and astChannelData functions.
+*     16-JAN-2009 (DSB):
+*        Added astAddWarning and astWarnings.
+*     11-JUN-2009 (DSB):
+*        Enable astChannelData to be used from within astRead.
+*     7-DEC-2009 (DSB):
+*        Added Indent attribute.
+*     12-FEB-2010 (DSB):
+*        Represent AST__BAD externally using the string "<bad>".
+*     23-JUN-2011 (DSB):
+*        Added attributes SinkFile and SourceFile.
+*     2-OCT-2012 (DSB):
+*        Report an error if an Inf or NaN value is read from the external
+*        source.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Channel
+
+/* Define a string containing the maximum length of keywords used to
+   identify values in the external representation of data. This is
+   deliberately kept small so as to simplify integration with
+   standards such as FITS. */
+#define MAX_NAME "8"
+
+/* Max length of string returned by GetAttrib */
+#define GETATTRIB_BUFF_LEN 50
+
+/* String used to represent AST__BAD externally. */
+#define BAD_STRING "<bad>"
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "channel.h"             /* Interface definition for this class */
+#include "loader.h"              /* Interface to the global loader */
+#include "keymap.h"              /* Storing arbitrary data in an AST Object */
+#include "pointset.h"            /* For AST__BAD */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->AstReadClassData_Msg = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->Items_Written = 0; \
+   globals->Current_Indent = 0; \
+   globals->Nest = -1; \
+   globals->Nwrite_Invoc = 0; \
+   globals->Object_Class = NULL; \
+   globals->Values_List = NULL; \
+   globals->Values_Class = NULL; \
+   globals->Values_OK = NULL; \
+   globals->End_Of_Object = NULL; \
+   globals->Channel_Data  = NULL;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Channel)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Channel,Class_Init)
+#define class_vtab astGLOBAL(Channel,Class_Vtab)
+#define astreadclassdata_msg astGLOBAL(Channel,AstReadClassData_Msg)
+#define getattrib_buff astGLOBAL(Channel,GetAttrib_Buff)
+#define items_written  astGLOBAL(Channel,Items_Written)
+#define current_indent astGLOBAL(Channel,Current_Indent)
+#define nest           astGLOBAL(Channel,Nest)
+#define nwrite_invoc   astGLOBAL(Channel,Nwrite_Invoc)
+#define object_class   astGLOBAL(Channel,Object_Class)
+#define values_list    astGLOBAL(Channel,Values_List)
+#define values_class   astGLOBAL(Channel,Values_Class)
+#define values_ok      astGLOBAL(Channel,Values_OK)
+#define end_of_object  astGLOBAL(Channel,End_Of_Object)
+#define channel_data   astGLOBAL(Channel,Channel_Data)
+
+
+
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+static pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX3 pthread_mutex_lock( &mutex3 );
+#define UNLOCK_MUTEX3 pthread_mutex_unlock( &mutex3 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Contextual error message reported in astReadClassData? */
+static int astreadclassdata_msg = 0;
+
+/* Buffer returned by GetAttrib. */
+static char getattrib_buff[ GETATTRIB_BUFF_LEN + 1 ];
+
+/* Count of the number of output items written since the last "Begin"
+   or "IsA" item. */
+static int items_written = 0;
+
+/* Amount of indentation to be applied to the next output item. */
+static int current_indent = 0;
+
+/* Nesting level, used to keep track of data associated with building
+   Objects when they contain other Objects. */
+static int nest = -1;
+
+/* The number of times astWrite has been invoked. */
+static int nwrite_invoc = 0;
+
+/* Pointer to a user-supplied block of memory to be made available to
+   source or sink functions via the astChannelData function. */
+static void *channel_data = NULL;
+
+/***
+   The following items are all pointers to dynamically allocated
+   arrays (stacks) that grow as necessary to accommodate one element
+   for each level of nesting (one more than the value of "nest").
+***/
+
+/* Stack of pointers to null-terminated character strings giving the
+   names of the classes of the Objects being built at each nesting
+   level. */
+static char **object_class = NULL;
+
+/* Stack of pointers to the elements designated as the "heads" of
+   circular, doubly linked lists of name-value associations. */
+static AstChannelValue **values_list = NULL;
+
+/* Stack of pointers to null-terminated character strings giving the
+   names of the classes for which the values held in the values lists
+   are intended. */
+static char **values_class = NULL;
+
+/* Stack of flags indicating whether the values held in the values
+   lists are intended for the class loaders currently executing to
+   build Objects at each nesting level. */
+static int *values_ok = NULL;
+
+/* Stack of flags indicating whether "End" items have been read for
+   the Objects being built at each nesting level. */
+static int *end_of_object = NULL;
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstChannelVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+#define LOCK_MUTEX3
+#define UNLOCK_MUTEX3
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstChannel *astChannelForId_( const char *(*)( void ),
+                              char *(*)( const char *(*)( void ), int * ),
+                              void (*)( const char * ),
+                              void (*)( void (*)( const char * ),
+                                        const char *, int * ),
+                              const char *, ... );
+AstChannel *astChannelId_( const char *(*)( void ), void (*)( const char * ), const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstObject *Read( AstChannel *, int * );
+static AstObject *ReadObject( AstChannel *, const char *, AstObject *, int * );
+static AstChannelValue *FreeValue( AstChannelValue *, int * );
+static AstChannelValue *LookupValue( const char *, int * );
+static AstKeyMap *Warnings( AstChannel *, int * );
+static char *GetNextText( AstChannel *, int * );
+static char *InputTextItem( AstChannel *, int * );
+static char *ReadString( AstChannel *, const char *, const char *, int * );
+static char *SourceWrap( const char *(*)( void ), int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static double ReadDouble( AstChannel *, const char *, double, int * );
+static int GetComment( AstChannel *, int * );
+static int GetFull( AstChannel *, int * );
+static int GetSkip( AstChannel *, int * );
+static int GetStrict( AstChannel *, int * );
+static int ReadInt( AstChannel *, const char *, int, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestComment( AstChannel *, int * );
+static int TestFull( AstChannel *, int * );
+static int TestSkip( AstChannel *, int * );
+static int TestStrict( AstChannel *, int * );
+static int Use( AstChannel *, int, int, int * );
+static int Write( AstChannel *, AstObject *, int * );
+static void AddWarning( AstChannel *, int, const char *, const char *, int * );
+static void AppendValue( AstChannelValue *, AstChannelValue **, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearComment( AstChannel *, int * );
+static void ClearFull( AstChannel *, int * );
+static void ClearSkip( AstChannel *, int * );
+static void ClearStrict( AstChannel *, int * );
+static void ClearValues( AstChannel *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void GetNextData( AstChannel *, int, char **, char **, int * );
+static void OutputTextItem( AstChannel *, const char *, int * );
+static void PutChannelData( AstChannel *, void *, int * );
+static void PutNextText( AstChannel *, const char *, int * );
+static void ReadClassData( AstChannel *, const char *, int * );
+static void RemoveValue( AstChannelValue *, AstChannelValue **, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetComment( AstChannel *, int, int * );
+static void SetFull( AstChannel *, int, int * );
+static void SetSkip( AstChannel *, int, int * );
+static void SetStrict( AstChannel *, int, int * );
+static void SinkWrap( void (*)( const char * ), const char *, int * );
+static void Unquote( AstChannel *, char *, int * );
+static void WriteBegin( AstChannel *, const char *, const char *, int * );
+static void WriteDouble( AstChannel *, const char *, int, int, double, const char *, int * );
+static void WriteEnd( AstChannel *, const char *, int * );
+static void WriteInt( AstChannel *, const char *, int, int, int, const char *, int * );
+static void WriteIsA( AstChannel *, const char *, const char *, int * );
+static void WriteObject( AstChannel *, const char *, int, int, AstObject *, const char *, int * );
+static void WriteString( AstChannel *, const char *, int, int, const char *, const char *, int * );
+
+static int GetReportLevel( AstChannel *, int * );
+static int TestReportLevel( AstChannel *, int * );
+static void ClearReportLevel( AstChannel *, int * );
+static void SetReportLevel( AstChannel *, int, int * );
+
+static int GetIndent( AstChannel *, int * );
+static int TestIndent( AstChannel *, int * );
+static void ClearIndent( AstChannel *, int * );
+static void SetIndent( AstChannel *, int, int * );
+
+static const char *GetSourceFile( AstChannel *, int * );
+static int TestSourceFile( AstChannel *, int * );
+static void ClearSourceFile( AstChannel *, int * );
+static void SetSourceFile( AstChannel *, const char *, int * );
+
+static const char *GetSinkFile( AstChannel *, int * );
+static int TestSinkFile( AstChannel *, int * );
+static void ClearSinkFile( AstChannel *, int * );
+static void SetSinkFile( AstChannel *, const char *, int * );
+
+/* Member functions. */
+/* ================= */
+static void AddWarning( AstChannel *this, int level, const char *msg,
+                        const char *method, int *status ) {
+/*
+*+
+*  Name:
+*     astAddWarning
+
+*  Purpose:
+*     Add a warning to a Channel.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astAddWarning( AstChannel *this, int level, const char *msg,
+*                         const char *method, int status, ... )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function stores a warning message inside a Channel. These
+*     messages can be retirieved using astWarnings.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     level
+*        Ignore the warning if the ReportLevel attribute value is less
+*        than "level".
+*     msg
+*        The wanting message to store. It may contain printf format
+*        specifiers. If a NULL pointer is supplied, all warnings
+*        currently stored in the Channel are removed.
+*     method
+*        The method name.
+*     status
+*        Inherited status value.
+*     ...
+*        Extra values to substitute into the message string as
+*        replacements for the printf format specifiers.
+*-
+
+*  Note: The expansion of the printf format specifiers is done in the
+*     astAddWarning_ wrapper function. The AddWarning functions defined by
+*     each class receives the fully expanded message and does not have a
+*     variable argument list. The variable argument list is included in the
+*     above prologue in order to document the wrapper function.
+
+*/
+
+/* Local Variables: */
+   int i;          /* Message index */
+   char *a;        /* Pointer to copy of message */
+
+/* If a NULL pointer was supplied, free all warnings currently in the
+   Channel. Do this before checking the inherited status so that it works
+   even if an error has occurred. */
+   if( !msg ) {
+      for( i = 0; i < this->nwarn; i++ ) {
+         (this->warnings)[ i ] = astFree( (this->warnings)[ i ] );
+      }
+      this->warnings = astFree( this->warnings );
+      this->nwarn = 0;
+      return;
+   }
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Only proceed if the message level is sufficiently important. */
+   if( astGetReportLevel( this ) >= level ) {
+
+/* If we are being strict, issue an error rather than a warning. */
+      if( astGetStrict( this ) ) {
+         if( astOK ) {
+            astError( AST__BADIN, "%s(%s): %s", status, method,
+                      astGetClass( this ), msg );
+         }
+
+/* Otherwise, we store a copy of the message in the Channel. */
+      } else {
+
+/* Allocate memory and store a copy of th supplied string in it. */
+         a = astStore( NULL, msg, strlen( msg ) + 1 );
+
+/* Expand the array of warning pointers in ther Channel structure. */
+         this->warnings = astGrow( this->warnings, this->nwarn + 1,
+                                   sizeof( char * ) );
+
+/* If all is OK so far, store the new warning pointer, and increment the
+   number of warnings in the Channel. */
+         if( astOK ) {
+            (this->warnings)[ (this->nwarn)++ ] = a;
+
+/* Otherwise, attempt to free the memory holding the copy of the warning. */
+         } else {
+            a = astFree( a );
+         }
+      }
+   }
+}
+
+static void AppendValue( AstChannelValue *value, AstChannelValue **head, int *status ) {
+/*
+*  Name:
+*     AppendValue
+
+*  Purpose:
+*     Append a Value structure to a list.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void AppendValue( AstChannelValue *value, AstChannelValue **head, int *status )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function appends a Value structure to a doubly linked
+*     circular list of such structures. The new list element is
+*     inserted just in front of the element occupying the "head of
+*     list" position (i.e. it becomes the new last element in the
+*     list).
+
+*  Parameters:
+*     value
+*        Pointer to the new element. This must not already be in the
+*        list.
+*     head
+*        Address of a pointer to the element at the head of the list
+*        (this pointer should be NULL if the list is initially
+*        empty). This pointer will only be updated if a new element is
+*        being added to an empty list.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function does not perform error chacking and does not
+*     generate errors.
+*/
+
+/* If the list is initially empty, the sole new element points at
+   itself. */
+   if ( !*head ) {
+      value->flink = value;
+      value->blink = value;
+
+/* Update the list head to identify the new element. */
+      *head = value;
+
+/* Otherwise, insert the new element in front of the element at the
+   head of the list. */
+   } else {
+      value->flink = *head;
+      value->blink = ( *head )->blink;
+      ( *head )->blink = value;
+      value->blink->flink = value;
+   }
+}
+
+void *astChannelData_( void ) {
+/*
+c++
+*  Name:
+*     astChannelData
+
+*  Purpose:
+*     Return a pointer to user-supplied data stored with a Channel.
+
+*  Type:
+*     Public macro.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void *astChannelData
+
+*  Class Membership:
+*     Channel macro.
+
+*  Description:
+*     This macro is intended to be used within the source or sink
+*     functions associated with a Channel. It returns any pointer
+*     previously stored in the Channel (that is, the Channel that has
+*     invoked the source or sink function) using astPutChannelData.
+*
+*     This mechanism is a thread-safe alternative to passing file
+*     descriptors, etc, via static global variables.
+
+*  Returned Value:
+*     astChannelData
+*        The pointer previously stored with the Channel using
+*        astPutChannelData. A NULL pointer will be returned if no such
+*        pointer has been stored with the Channel.
+
+*  Applicability:
+*     Channel
+*        This macro applies to all Channels.
+
+*  Notes:
+*     - This routine is not available in the Fortran 77 interface to
+*     the AST library.
+c--
+*/
+   astDECLARE_GLOBALS
+   astGET_GLOBALS(NULL);
+   return channel_data;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Channel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Channel member function (over-rides the astClearAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     Channel, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstChannel *this;              /* Pointer to the Channel structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Channel structure. */
+   this = (AstChannel *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Comment. */
+/* -------- */
+   if ( !strcmp( attrib, "comment" ) ) {
+      astClearComment( this );
+
+/* Full. */
+/* ----- */
+   } else if ( !strcmp( attrib, "full" ) ) {
+      astClearFull( this );
+
+/* Indent. */
+/* ------- */
+   } else if ( !strcmp( attrib, "indent" ) ) {
+      astClearIndent( this );
+
+/* ReportLevel. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "reportlevel" ) ) {
+      astClearReportLevel( this );
+
+/* Skip. */
+/* ----- */
+   } else if ( !strcmp( attrib, "skip" ) ) {
+      astClearSkip( this );
+
+/* SourceFile. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "sourcefile" ) ) {
+      astClearSourceFile( this );
+
+/* SinkFile. */
+/* --------- */
+   } else if ( !strcmp( attrib, "sinkfile" ) ) {
+      astClearSinkFile( this );
+
+/* Strict. */
+/* ------- */
+   } else if ( !strcmp( attrib, "strict" ) ) {
+      astClearStrict( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearValues( AstChannel *this, int *status ) {
+/*
+*  Name:
+*     ClearValues
+
+*  Purpose:
+*     Clear the current values list.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void ClearValues( AstChannel *this, int *status )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function clears any (un-read) Value structures remaining in
+*     the current values list (i.e. at the current nesting level). It
+*     should be invoked after all required values have been read.
+*
+*     If the values list has not been read, or if any remaining values
+*     are found (i.e. the list is not empty) then this indicates an
+*     unrecognised input class or an input value that has not been
+*     read by a class loader. This implies an error in the loader, or
+*     bad input data, so an error is reported.
+*
+*     All resources used by any remaining Value structures are freed
+*     and the values list is left in an empty state.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel being read. This is only used for
+*        constructing error messages. It must not be NULL.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function attempts to execute even if the global error
+*     status is set on entry, although no further error report will be
+*     made if it subsequently fails under these circumstances.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstChannelValue **head;       /* Address of pointer to values list */
+   AstChannelValue *value;       /* Pointer to value list element */
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* If "values_class" is non-NULL, then the values list has previously
+   been filled with Values for a class. */
+   if ( values_class[ nest ] ) {
+
+/* If "values_ok" is zero, however, then these Values have not yet
+   been read by a class loader. This must be due to a bad class name
+   associated with them or because the class data are not available in
+   the correct order. If we are using strict error reporting, then report
+   an error (unless the error status is already set). */
+      if ( astGetStrict( this ) && !values_ok[ nest ] && astOK ) {
+         astError( AST__BADIN,
+                   "astRead(%s): Invalid class structure in input data.", status,
+                   astGetClass( this ) );
+         astError( AST__BADIN,
+                   "Class \"%s\" is invalid or out of order within a %s.", status,
+                   values_class[ nest ], object_class[ nest ] );
+      }
+
+/* Free the memory holding the class string. */
+      values_class[ nest ] = astFree( values_class[ nest ] );
+   }
+
+/* Reset the "values_ok" flag. */
+   values_ok[ nest ] = 0;
+
+/* Now clear any Values remaining in the values list. Obtain the
+   address of the pointer to the head of this list (at the current
+   nesting level) and loop to remove Values from the list while it is
+   not empty. */
+   head = values_list + nest;
+   while ( *head ) {
+
+/* Obtain a pointer to the first element. */
+      value = *head;
+
+/* Issue a warning. */
+      if ( value->is_object ) {
+         astAddWarning( this, 1, "The Object \"%s = <%s>\" was "
+                        "not recognised as valid input.", "astRead", status,
+                        value->name, astGetClass( value->ptr.object ) );
+      } else {
+         astAddWarning( this, 1, "The value \"%s = %s\" was not "
+                        "recognised as valid input.", "astRead", status,
+                        value->name, value->ptr.string );
+      }
+
+/* Remove the Value structure from the list (which updates the head of
+   list pointer) and free its resources. */
+      RemoveValue( value, head, status );
+      value = FreeValue( value, status );
+   }
+}
+
+static AstChannelValue *FreeValue( AstChannelValue *value, int *status ) {
+/*
+*  Name:
+*     FreeValue
+
+*  Purpose:
+*     Free a dynamically allocated Value structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     AstChannelValue *FreeValue( AstChannelValue *value, int *status )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function frees a dynamically allocated Value structure,
+*     releasing all resources used by it. The structure contents must
+*     have been correctly initialised.
+
+*  Parameters:
+*     value
+*        Pointer to the Value structure to be freed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A NULL pointer is always returned.
+
+*  Notes:
+*     - This function attempts to execute even if the global error
+*     status is set on entry, although no further error report will be
+*     made if it subsequently fails under these circumstances.
+*/
+
+/* Check that a non-NULL pointer has been supplied. */
+   if ( value ) {
+
+/* If the "name" component has been allocated, then free it. */
+      if ( value->name ) value->name = astFree( value->name );
+
+/* If the "ptr" component identifies an Object, then annul the Object
+   pointer. */
+      if ( value->is_object ) {
+         if ( value->ptr.object ) {
+            value->ptr.object = astAnnul( value->ptr.object );
+         }
+
+/* Otherwise, if it identifies a string, then free the string. */
+      } else {
+         if ( value->ptr.string ) {
+            value->ptr.string = astFree( value->ptr.string );
+         }
+      }
+
+/* Free the Value structure itself. */
+      value = astFree( value );
+   }
+
+/* Return a NULL pointer. */
+   return NULL;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a Channel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Channel member function (over-rides the protected astGetAttrib
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a Channel, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     attrib
+*        Pointer to a null terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Channel, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Channel. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstChannel *this;             /* Pointer to the Channel structure */
+   const char *result;           /* Pointer value to return */
+   int comment;                  /* Comment attribute value */
+   int full;                     /* Full attribute value */
+   int indent;                   /* Indent attribute value */
+   int report_level;             /* ReportLevel attribute value */
+   int skip;                     /* Skip attribute value */
+   int strict;                   /* Report errors insead of warnings? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the Channel structure. */
+   this = (AstChannel *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* Comment. */
+/* -------- */
+   if ( !strcmp( attrib, "comment" ) ) {
+      comment = astGetComment( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", comment );
+         result = getattrib_buff;
+      }
+
+/* Full. */
+/* ----- */
+   } else if ( !strcmp( attrib, "full" ) ) {
+      full = astGetFull( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", full );
+         result = getattrib_buff;
+      }
+
+/* Indent. */
+/* ------- */
+   } else if ( !strcmp( attrib, "indent" ) ) {
+      indent = astGetIndent( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", indent );
+         result = getattrib_buff;
+      }
+
+/* ReportLevel. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "reportlevel" ) ) {
+      report_level = astGetReportLevel( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", report_level );
+         result = getattrib_buff;
+      }
+
+/* Skip. */
+/* ----- */
+   } else if ( !strcmp( attrib, "skip" ) ) {
+      skip = astGetSkip( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", skip );
+         result = getattrib_buff;
+      }
+
+/* SourceFile. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "sourcefile" ) ) {
+      result = astGetSourceFile( this );
+
+/* SinkFile. */
+/* --------- */
+   } else if ( !strcmp( attrib, "sinkfile" ) ) {
+      result = astGetSinkFile( this );
+
+/* Strict. */
+/* ------- */
+   } else if ( !strcmp( attrib, "strict" ) ) {
+      strict = astGetStrict( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", strict );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static void GetNextData( AstChannel *this, int skip, char **name,
+                         char **val, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNextData
+
+*  Purpose:
+*     Read the next item of data from a data source.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astGetNextData( AstChannel *this, int skip, char **name,
+*                          char **val )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function reads the next item of input data from a data
+*     source associated with a Channel and returns the result.
+*
+*     It is layered conceptually on the astGetNextText method, but
+*     instead of returning the raw input text, it decodes it and
+*     returns name/value pairs ready for use. Note that in some
+*     derived classes, where the data are not stored as text, this
+*     function may not actually use astGetNextText, but will access
+*     the data directly.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     skip
+*        A non-zero value indicates that a new Object is to be read,
+*        and that all input data up to the next "Begin" item are to be
+*        skipped in order to locate it. This is useful if the data
+*        source contains AST objects interspersed with other data (but
+*        note that these other data cannot appear inside AST Objects,
+*        only between them).
+*
+*        A zero value indicates that all input data are significant
+*        and the next item will therefore be read and an attempt made
+*        to interpret it whatever it contains. Any other data
+*        inter-mixed with AST Objects will then result in an error.
+*     name
+*        An address at which to store a pointer to a null-terminated
+*        dynamically allocated string containing the name of the next
+*        item in the input data stream. This name will be in lower
+*        case with no surrounding white space.  It is the callers
+*        responsibilty to free the memory holding this string (using
+*        astFree) when it is no longer required.
+*
+*        A NULL pointer value will be returned (without error) to
+*        indicate when there are no further input data items to be
+*        read.
+*     val
+*        An address at which to store a pointer to a null-terminated
+*        dynamically allocated string containing the value associated
+*        with the next item in the input data stream. No case
+*        conversion is performed on this string and all white space is
+*        potentially significant.  It is the callers responsibilty to
+*        free the memory holding this string (using astFree) when it
+*        is no longer required.
+*
+*        The returned pointer will be NULL if an Object data item is
+*        read (see the "Data Representation" section).
+
+*  Data Representation:
+*     The returned data items fall into the following categories:
+*
+*     - Begin: Identified by the name string "begin", this indicates
+*     the start of an Object definition. The associated value string
+*     gives the class name of the Object being defined.
+*
+*     - IsA: Identified by the name string "isa", this indicates the
+*     end of the data associated with a particular class structure
+*     within the definiton of a larger Object. The associated value
+*     string gives the name of the class whose data have just been
+*     read.
+*
+*     - End: Identified by the name string "end", this indicates the
+*     end of the data associated with a complete Object
+*     definition. The associated value string gives the class name of
+*     the Object whose definition is being ended.
+*
+*     - Non-Object: Identified by any other name string plus a
+*     non-NULL "val" pointer, this gives the value of a non-Object
+*     structure component (instance variable). The name identifies
+*     which instance variable it is (within the context of the class
+*     whose data are being read) and the value is encoded as a string.
+*
+*     - Object: Identified by any other name string plus a NULL "val"
+*     pointer, this identifies the value of an Object structure
+*     component (instance variable).  The name identifies which
+*     instance variable it is (within the context of the class whose
+*     data are being read) and the value is given by subsequent data
+*     items (so the next item should be a "Begin" item).
+
+*  Notes:
+*     - NULL pointer values will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*     - This method is provided primarily so that derived classes may
+*     over-ride it in order to read from alternative data sources. It
+*     provides a higher-level interface than astGetNextText, so is
+*     suitable for classes that either need to read textual data in a
+*     different format, or to read from non-textual data sources.
+*-
+*/
+
+/* Local Variables: */
+   char *line;                   /* Pointer to input text line */
+   int done;                     /* Data item read? */
+   int i;                        /* Loop counter for string characters */
+   int len;                      /* Length of input text line */
+   int nc1;                      /* Offset to start of first field */
+   int nc2;                      /* Offset to end of first field */
+   int nc3;                      /* Offset to start of second field */
+   int nc;                       /* Number of charaters read by "astSscanf" */
+
+/* Initialise the returned values. */
+   *name = NULL;
+   *val = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Read the next input line as text (the loop is needed to allow
+   initial lines to be skipped if the "skip" flag is set). */
+   done = 0;
+   while ( !done && ( line = InputTextItem( this, status ) ) && astOK ) {
+
+/* If OK, determine the line length. */
+      len = strlen( line );
+
+/* Non-Object value. */
+/* ----------------- */
+/* Test for lines of the form " name = value" (or similar), where the
+   name is no more than MAX_NAME characters long (the presence of a
+   value on the right hand side indicates that this is a non-Object
+   value, encoded as a string). Ignore these lines if the "skip" flag
+   is set. */
+      if ( nc = 0,
+           ( !skip
+             && ( 0 == astSscanf( line,
+                               " %n%*" MAX_NAME "[^ \t=]%n = %n%*[^\n]%n",
+                               &nc1, &nc2, &nc3, &nc ) )
+             && ( nc >= len ) ) ) {
+
+/* Note we have found a data item. */
+         done = 1;
+
+/* Extract the name and value fields. */
+         *name = astString( line + nc1, nc2 - nc1 );
+         *val = astString( line + nc3, len - nc3 );
+
+/* If OK, truncate the value to remove any trailing white space. */
+         if ( astOK ) {
+            i = len - nc3 - 1;
+            while ( ( i >= 0 ) && isspace( ( *val )[ i ] ) ) i--;
+            ( *val )[ i + 1 ] = '\0';
+
+/* Also remove any quotes from the string. */
+            Unquote( this, *val, status );
+         }
+
+/* Object value. */
+/* ------------- */
+/* Test for lines of the form " name = " (or similar), where the name
+   is no more than MAX_NAME characters long (the absence of a value on
+   the right hand side indicates that this is an Object, whose
+   definition follows on subsequent input lines). Ignore these lines
+   if the "skip" flag is set. */
+      } else if ( nc = 0,
+                  ( !skip
+                    && ( 0 == astSscanf( line,
+                                      " %n%*" MAX_NAME "[^ \t=]%n = %n",
+                                      &nc1, &nc2, &nc ) )
+                    && ( nc >= len ) ) ) {
+
+/* Note we have found a data item. */
+         done = 1;
+
+/* Extract the name field but leave the value pointer as NULL. */
+         *name = astString( line + nc1, nc2 - nc1 );
+
+/* Begin. */
+/* ------ */
+/* Test for lines of the form " Begin Class " (or similar). */
+      } else if ( nc = 0,
+             ( ( 0 == astSscanf( line,
+                             " %*1[Bb]%*1[Ee]%*1[Gg]%*1[Ii]%*1[Nn] %n%*s%n %n",
+                              &nc1, &nc2, &nc ) )
+               && ( nc >= len ) ) ) {
+
+/* Note we have found a data item. */
+         done = 1;
+
+/* Set the returned name to "begin" and extract the associated class
+   name for the value. Store both of these in dynamically allocated
+   strings. */
+         *name = astString( "begin", 5 );
+         *val = astString( line + nc1, nc2 - nc1 );
+
+/* IsA. */
+/* ---- */
+/* Test for lines of the form " IsA Class " (or similar). Ignore these
+   lies if the "skip" flag is set. */
+      } else if ( nc = 0,
+                  ( !skip
+                    && ( 0 == astSscanf( line,
+                                      " %*1[Ii]%*1[Ss]%*1[Aa] %n%*s%n %n",
+                                      &nc1, &nc2, &nc ) )
+                    && ( nc >= len ) ) ) {
+
+/* Note we have found a data item. */
+         done = 1;
+
+/* Set the returned name to "isa" and extract the associated class
+   name for the value. */
+         *name = astString( "isa", 3 );
+         *val = astString( line + nc1, nc2 - nc1 );
+
+/* End. */
+/* ---- */
+/* Test for lines of the form " End Class " (or similar). Ignore these
+   lines if the "skip" flag is set. */
+      } else if ( nc = 0,
+                  ( !skip
+                    && ( 0 == astSscanf( line,
+                                      " %*1[Ee]%*1[Nn]%*1[Dd] %n%*s%n %n",
+                                      &nc1, &nc2, &nc ) )
+                    && ( nc >= len ) ) ) {
+
+/* Note we have found a data item. */
+         done = 1;
+
+/* If found, set the returned name to "end" and extract the associated
+   class name for the value. */
+         *name = astString( "end", 3 );
+         *val = astString( line + nc1, nc2 - nc1 );
+
+/* If the input line didn't match any of the above and the "skip" flag
+   is not set, then report an error. */
+      } else if ( !skip ) {
+         astError( AST__BADIN,
+                   "astRead(%s): Cannot interpret the input data: \"%s\".", status,
+                   astGetClass( this ), line );
+      }
+
+/* Free the memory holding the input data as text. */
+      line = astFree( line );
+   }
+
+/* If successful, convert the name to lower case. */
+   if ( astOK && *name ) {
+      for ( i = 0; ( *name )[ i ]; i++ ) {
+         ( *name )[ i ] = tolower( ( *name )[ i ] );
+      }
+   }
+
+/* If an error occurred, ensure that any memory allocated is freed and
+   that NULL pointer values are returned. */
+   if ( !astOK ) {
+      *name = astFree( *name );
+      *val = astFree( *val );
+   }
+}
+
+static char *GetNextText( AstChannel *this, int *status ) {
+/*
+*+
+*  Name:
+*     GetNextText
+
+*  Purpose:
+*     Read the next line of input text from a data source.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     char *astGetNextText( AstChannel *this )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function reads the next "raw" input line of text from the
+*     data source associated with a Channel.
+*
+*     Each line is returned as a pointer to a null-terminated string
+*     held in dynamic memory, and it is the caller's responsibility to
+*     free this memory (using astFree) when it is no longer
+*     required. A NULL pointer is returned if there are no more input
+*     lines to be read.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+
+*  Returned Value:
+*     Pointer to a null-terminated string containing the input line
+*     (held in dynamically allocated memory, which must be freed by
+*     the caller when no longer required). A NULL pointer is returned
+*     if there are no more input lines to be read.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*     - This method is provided primarily so that derived classes may
+*     over-ride it in order to read from alternative (textual) data
+*     sources.
+*-
+*/
+
+/* Local Constants: */
+#define MIN_CHARS 81             /* Initial size for allocating memory */
+#define ERRBUF_LEN 80
+
+/* Local Variables: */
+   FILE *fd;                     /* Input file descriptor */
+   char *errstat;                /* Pointer for system error message */
+   char *line;                   /* Pointer to line data to be returned */
+   char errbuf[ ERRBUF_LEN ];    /* Buffer for system error message */
+   const char *sink_file;        /* Path to output sink file */
+   const char *source_file;      /* Path to source file */
+   int c;                        /* Input character */
+   int len;                      /* Length of input line */
+   int readstat;                 /* "errno" value set by "getchar" */
+   int size;                     /* Size of allocated memory */
+
+/* Initialise. */
+   line = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return line;
+
+/* If the SourceFile attribute of the Channel specifies an input file,
+   but no input file has yet been opened, open it now. Report an error if
+   it is the same as the sink file. */
+   if( astTestSourceFile( this ) && !this->fd_in ) {
+      source_file = astGetSourceFile( this );
+
+      if( this->fd_out ) {
+         sink_file = astGetSinkFile( this );
+         if( astOK && !strcmp( sink_file, source_file ) ) {
+            astError( AST__RDERR, "astRead(%s): Failed to open input "
+                      "SourceFile '%s' - the file is currently being used "
+                      "as the output SinkFile.", status, astGetClass( this ),
+                      source_file );
+         }
+      }
+
+      if( astOK ) {
+         this->fd_in = fopen( source_file, "r" );
+         if( !this->fd_in ) {
+            if ( errno ) {
+#if HAVE_STRERROR_R
+               strerror_r( errno, errbuf, ERRBUF_LEN );
+               errstat = errbuf;
+#else
+               errstat = strerror( errno );
+#endif
+               astError( AST__RDERR, "astRead(%s): Failed to open input "
+                         "SourceFile '%s' - %s.", status, astGetClass( this ),
+                         source_file, errstat );
+            } else {
+               astError( AST__RDERR, "astRead(%s): Failed to open input "
+                         "SourceFile '%s'.", status, astGetClass( this ),
+                         source_file );
+            }
+         }
+
+      }
+   }
+
+/* Source function defined, but no input file. */
+/* ------------------------------------------- */
+/* If no active input file descriptor is stored in the Channel, but
+   a source function (and its wrapper function) is defined for the
+   Channel, use the wrapper function to invoke the source function to
+   read a line of input text. This is returned in a dynamically
+   allocated string. */
+   if ( !this->fd_in && this->source && this->source_wrap ) {
+
+/* About to call an externally supplied function which may not be
+   thread-safe, so lock a mutex first. Also store the channel data
+   pointer in a global variable so that it can be accessed in the source
+   function using macro astChannelData. */
+      astStoreChannelData( this );
+      LOCK_MUTEX3;
+      line = ( *this->source_wrap )( this->source, status );
+      UNLOCK_MUTEX3;
+
+/* Input file defined, or no source function. */
+/* ------------------------------------------ */
+/* Read the line from the input file or from standard input. */
+   } else if( astOK ) {
+      c = '\0';
+      len = 0;
+      size = 0;
+
+/* Choose the file descriptor to use. */
+      fd = this->fd_in ? this->fd_in : stdin;
+
+/* Loop to read input characters, saving any "errno" value that may be
+   set by "getchar" if an error occurs. Quit if an end of file (or
+   error) occurs or if a newline character is read. */
+      while ( errno = 0, c = getc( fd ), readstat = errno,
+              ( c != EOF ) && ( c != '\n' ) ) {
+
+/* If no memory has yet been allocated to hold the line, allocate some
+   now, using MIN_CHARS as the initial line length. */
+         if ( !line ) {
+            line = astMalloc( sizeof( char ) * (size_t) MIN_CHARS );
+            size = MIN_CHARS;
+
+/* If memory has previously been allocated, extend it when necessary
+   to hold the new input character (plus a terminating null) and note
+   the new size. */
+         } else if ( ( len + 2 ) > size ) {
+            line = astGrow( line, len + 2, sizeof( char ) );
+            if ( !astOK ) break;
+            size = (int) astSizeOf( line );
+         }
+
+/* Store the character just read. */
+         line[ len++ ] = c;
+      }
+
+/* If the above loop completed without setting the global error
+   status, check the last character read and use "ferror" to see if a
+   read error occurred. If so, report the error, using the saved
+   "errno" value (but only if one was set). */
+      if ( astOK && ( c == EOF ) && ferror( fd ) ) {
+         if ( readstat ) {
+#if HAVE_STRERROR_R
+            strerror_r( readstat, errbuf, ERRBUF_LEN );
+            errstat = errbuf;
+#else
+            errstat = strerror( readstat );
+#endif
+            astError( AST__RDERR,
+                      "astRead(%s): Read error on standard input - %s.", status,
+                      astGetClass( this ), errstat );
+         } else {
+            astError( AST__RDERR,
+                      "astRead(%s): Read error on standard input.", status,
+                      astGetClass( this ) );
+         }
+      }
+
+/* If an empty line has been read, allocate memory to hold an empty
+   string. */
+      if ( !line && ( c == '\n' ) ) {
+         line = astMalloc( sizeof( char ) );
+      }
+
+/* If memory has been allocated and there has been no error,
+   null-terminate the string of input characters. */
+      if ( line ) {
+         if ( astOK ) {
+            line[ len ] = '\0';
+
+/* If there has been an error, free the allocated memory. */
+         } else {
+            line = astFree( line );
+         }
+      }
+   }
+
+
+/* Return the result pointer. */
+   return line;
+
+/* Undefine macros local to this function. */
+#undef MIN_CHARS
+#undef ERRBUF_LEN
+}
+
+static AstKeyMap *Warnings( AstChannel *this, int *status ){
+/*
+*++
+*  Name:
+c     astWarnings
+f     AST_WARNINGS
+
+*  Purpose:
+*     Returns any warnings issued by the previous read or write operation.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "channel.h"
+c     AstKeyMap *astWarnings( AstChannel *this )
+f     RESULT = AST_WARNINGS( THIS, STATUS )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function returns an AST KeyMap object holding the text of any
+*     warnings issued as a result of the previous invocation of the
+c     astRead or astWrite
+f     AST_READ or AST_WRITE
+*     function on the Channel. If no warnings were issued, a
+c     a NULL value
+f     AST__NULL
+*     will be returned.
+*
+*     Such warnings are non-fatal and will not prevent the
+*     read or write operation succeeding. However, the converted object
+*     may not be identical to the original object in all respects.
+*     Differences which would usually be deemed as insignificant in most
+*     usual cases will generate a warning, whereas more significant
+*     differences will generate an error.
+*
+*     The "Strict" attribute allows this warning facility to be switched
+*     off, so that a fatal error is always reported for any conversion
+*     error.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Channel.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astWarnings()
+f     AST_WARNINGS = INTEGER
+*        A pointer to the KeyMap holding the warning messages, or
+c        NULL
+f        AST__NULL
+*        if no warnings were issued during the previous read operation.
+
+*  Applicability:
+*     Channel
+*        The basic Channel class generates a warning when ever an
+*        un-recognised item is encountered whilst reading an Object from
+*        an external data source. If Strict is zero (the default), then
+*        unexpected items in the Object description are simply ignored,
+*        and any remaining items are used to construct the returned
+*        Object. If Strict is non-zero, an error will be reported and a
+*        NULL Object pointer returned if any unexpected items are
+*        encountered.
+*
+*        As AST continues to be developed, new attributes are added
+*        occasionally to selected classes. If an older version of AST is
+*        used to read external Object descriptions created by a more
+*        recent version of AST, then the Channel class will, by default,
+*        ignore the new attributes, using the remaining attributes to
+*        construct the Object. This is usually a good thing. However,
+*        since external Object descriptions are often stored in plain
+*        text, it is possible to edit them using a text editor. This
+*        gives rise to the possibility of genuine errors in the
+*        description due to finger-slips, typos, or simple
+*        mis-understanding. Such inappropriate attributes will be ignored
+*        if Strict is left at its default zero value. This will cause the
+*        mis-spelled attribute to revert to its default value,
+*        potentially causing subtle changes in the behaviour of
+*        application software. If such an effect is suspected, the Strict
+*        attribute can be set non-zero, resulting in the erroneous
+*        attribute being identified in an error message.
+*     FitsChan
+*        The returned KeyMap will contain warnings for all conditions
+*        listed in the Warnings attribute.
+*     XmlChan
+*        Reports conversion errors that result in what are usally
+*        insignificant  changes.
+
+*  Notes:
+*     - The returned KeyMap uses keys of the form "Warning_1",
+*     "Warning_2", etc.
+*     - A value of
+c     NULL will be returned if this function is invoked with the AST
+c     error status set,
+f     AST__NULL will be returned if this function is invoked with STATUS
+f     set to an error value,
+*     or if it should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *result;
+   char key[ 20 ];
+   int i;
+
+/* Check the global status, and supplied keyword name. */
+   result = NULL;
+   if( !astOK ) return result;
+
+/* Check there are some warnings to return. */
+   if( this->nwarn && this->warnings ) {
+
+/* Create the KeyMap. */
+      result = astKeyMap( "", status );
+
+/* Loop round all warnings, adding them into the KeyMap. */
+      for( i = 0; i < this->nwarn; i++ ){
+         sprintf( key, "Warning_%d", i + 1 );
+         astMapPut0C( result, key, (this->warnings)[ i ], " " );
+      }
+   }
+
+/* Return the KeyMap. */
+   return result;
+}
+
+AstChannel *astInitChannel_( void *mem, size_t size, int init,
+                             AstChannelVtab *vtab, const char *name,
+                             const char *(* source)( void ),
+                             char *(* source_wrap)( const char *(*)( void ), int * ),
+                             void (* sink)( const char * ),
+                             void (* sink_wrap)( void (*)( const char * ),
+                                                 const char *, int * ), int *status ) {
+/*
+*+
+*  Name:
+*     astInitChannel
+
+*  Purpose:
+*     Initialise a Channel.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     AstChannel *astInitChannel( void *mem, size_t size, int init,
+*                                 AstChannelVtab *vtab, const char *name,
+*                                 const char *(* source)( void ),
+*                                 char *(* source_wrap)( const char *(*)( void ), int * ),
+*                                 void (* sink)( const char * ),
+*                                 void (* sink_wrap)( void (*)( const char * ),
+*                                                     const char *, int * ) )
+
+*  Class Membership:
+*     Channel initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new Channel object. It allocates memory (if
+*     necessary) to accommodate the Channel plus any additional data
+*     associated with the derived class.  It then initialises a
+*     Channel structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual
+*     function table for a Channel at the start of the memory passed
+*     via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Channel is to be
+*        initialised.  This must be of sufficient size to accommodate
+*        the Channel data (sizeof(Channel)) plus any data used by the
+*        derived class. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Channel (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Channel structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*     init
+*        A boolean flag indicating if the Channel's virtual function
+*        table is to be initialised. If this value is non-zero, the
+*        virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Channel.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*     source
+*        Pointer to a "source" function which will be used to obtain
+*        lines of input text. Generally, this will be obtained by
+*        casting a pointer to a source function which is compatible
+*        with the "source_wrap" wrapper function (below). The pointer
+*        should later be cast back to its original type by the
+*        "source_wrap" function before the function is invoked.
+*
+*        If "source" is NULL, the Channel will read from standard
+*        input instead.
+*     source_wrap
+*        Pointer to a function which can be used to invoke the
+*        "source" function supplied (above). This wrapper function is
+*        necessary in order to hide variations in the nature of the
+*        source function, such as may arise when it is supplied by a
+*        foreign (non-C) language interface.
+*
+*        The single parameter of the "source_wrap" function is a
+*        pointer to the "source" function, and it should cast this
+*        function pointer (as necessary) and invoke the function with
+*        appropriate arguments to obtain the next line of input
+*        text. The "source_wrap" function should then return a pointer
+*        to a dynamically allocated, null terminated string containing
+*        the text that was read. The string will be freed (using
+*        astFree) when no longer required and the "source_wrap"
+*        function need not concern itself with this. A NULL pointer
+*        should be returned if there is no more input to read.
+*
+*        If "source_wrap" is NULL, the Channel will read from standard
+*        input instead.
+*     sink
+*        Pointer to a "sink" function which will be used to deliver
+*        lines of output text. Generally, this will be obtained by
+*        casting a pointer to a sink function which is compatible with
+*        the "sink_wrap" wrapper function (below). The pointer should
+*        later be cast back to its original type by the "sink_wrap"
+*        function before the function is invoked.
+*
+*        If "sink" is NULL, the Channel will write to standard output
+*        instead.
+*     sink_wrap
+*        Pointer to a function which can be used to invoke the "sink"
+*        function supplied (above). This wrapper function is necessary
+*        in order to hide variations in the nature of the sink
+*        function, such as may arise when it is supplied by a foreign
+*        (non-C) language interface.
+*
+*        The first parameter of the "sink_wrap" function is a pointer
+*        to the "sink" function, and the second parameter is a pointer
+*        to a const, null-terminated character string containing the
+*        text to be written.  The "sink_wrap" function should cast the
+*        "sink" function pointer (as necessary) and invoke the
+*        function with appropriate arguments to deliver the line of
+*        output text. The "sink_wrap" function then returns void.
+*
+*        If "sink_wrap" is NULL, the Channel will write to standard
+*        output instead.
+
+*  Returned Value:
+*     A pointer to the new Channel.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstChannel *new;              /* Pointer to new Channel */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitChannelVtab( vtab, name );
+
+/* Initialise an Object structure (the parent class) as the first
+   component within the Channel structure, allocating memory if
+   necessary. */
+   new = (AstChannel *) astInitObject( mem, size, 0,
+                                       (AstObjectVtab *) vtab, name );
+
+   if ( astOK ) {
+
+/* Initialise the Channel data. */
+/* ---------------------------- */
+/* Save the pointers to the source and sink functions and the wrapper
+   functions that invoke them. */
+      new->source = source;
+      new->source_wrap = source_wrap;
+      new->sink = sink;
+      new->sink_wrap = sink_wrap;
+
+/* Indicate no input or output files have been associated with the
+   Channel. */
+      new->fd_in = NULL;
+      new->fn_in = NULL;
+      new->fd_out = NULL;
+      new->fn_out = NULL;
+
+/* Set all attributes to their undefined values. */
+      new->comment = -INT_MAX;
+      new->full = -INT_MAX;
+      new->indent = -INT_MAX;
+      new->report_level = -INT_MAX;
+      new->skip = -INT_MAX;
+      new->strict = -INT_MAX;
+      new->data = NULL;
+      new->warnings = NULL;
+      new->nwarn = 0;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+void astInitChannelVtab_(  AstChannelVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitChannelVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Channel.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astInitChannelVtab( AstChannelVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Channel vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Channel class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitObjectVtab( (AstObjectVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAChannel) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstObjectVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->AddWarning = AddWarning;
+   vtab->ClearComment = ClearComment;
+   vtab->ClearFull = ClearFull;
+   vtab->ClearSkip = ClearSkip;
+   vtab->ClearStrict = ClearStrict;
+   vtab->GetComment = GetComment;
+   vtab->GetFull = GetFull;
+   vtab->GetNextData = GetNextData;
+   vtab->GetNextText = GetNextText;
+   vtab->GetSkip = GetSkip;
+   vtab->GetStrict = GetStrict;
+   vtab->Warnings = Warnings;
+   vtab->PutNextText = PutNextText;
+   vtab->Read = Read;
+   vtab->ReadClassData = ReadClassData;
+   vtab->ReadDouble = ReadDouble;
+   vtab->ReadInt = ReadInt;
+   vtab->ReadObject = ReadObject;
+   vtab->ReadString = ReadString;
+   vtab->SetComment = SetComment;
+   vtab->SetFull = SetFull;
+   vtab->SetSkip = SetSkip;
+   vtab->SetStrict = SetStrict;
+   vtab->TestComment = TestComment;
+   vtab->TestFull = TestFull;
+   vtab->TestSkip = TestSkip;
+   vtab->TestStrict = TestStrict;
+   vtab->Write = Write;
+   vtab->WriteBegin = WriteBegin;
+   vtab->WriteDouble = WriteDouble;
+   vtab->WriteEnd = WriteEnd;
+   vtab->WriteInt = WriteInt;
+   vtab->WriteIsA = WriteIsA;
+   vtab->WriteObject = WriteObject;
+   vtab->WriteString = WriteString;
+   vtab->PutChannelData = PutChannelData;
+
+   vtab->ClearReportLevel = ClearReportLevel;
+   vtab->GetReportLevel = GetReportLevel;
+   vtab->SetReportLevel = SetReportLevel;
+   vtab->TestReportLevel = TestReportLevel;
+
+   vtab->ClearIndent = ClearIndent;
+   vtab->GetIndent = GetIndent;
+   vtab->SetIndent = SetIndent;
+   vtab->TestIndent = TestIndent;
+
+   vtab->ClearSourceFile = ClearSourceFile;
+   vtab->GetSourceFile = GetSourceFile;
+   vtab->SetSourceFile = SetSourceFile;
+   vtab->TestSourceFile = TestSourceFile;
+
+   vtab->ClearSinkFile = ClearSinkFile;
+   vtab->GetSinkFile = GetSinkFile;
+   vtab->SetSinkFile = SetSinkFile;
+   vtab->TestSinkFile = TestSinkFile;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+/* Declare the destructor and copy constructor. */
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+
+/* Declare the Dump function for this class. There is no destructor or
+   copy constructor. */
+   astSetDump( vtab, Dump, "Channel", "Basic I/O Channel" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static char *InputTextItem( AstChannel *this, int *status ) {
+/*
+*  Name:
+*     InputTextItem
+
+*  Purpose:
+*     Read the next item from a data source as text.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     char *InputTextItem( AstChannel *this )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function reads the next input data item as text from the
+*     data source associated with a Channel. It is similar to the
+*     astGetNextText method (which it invokes), except that it strips
+*     off any comments along with leading and trailing white
+*     space. Input lines which are empty or do not contain significant
+*     characters (e.g. all comment) are skipped, so that only
+*     significant lines are returned.
+*
+*     Each line is returned as a pointer to a null-terminated string
+*     held in dynamic memory, and it is the caller's responsibility to
+*     free this memory (using astFree) when it is no longer
+*     required. A NULL pointer is returned if there are no more input
+*     lines to be read.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+
+*  Returned Value:
+*     Pointer to a null-terminated string containing the input line
+*     (held in dynamically allocated memory, which must be freed by
+*     the caller when no longer required). A NULL pointer is returned
+*     if there are no more input lines to be read.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   char *line;                   /* Pointer to line data to be returned */
+   int i;                        /* Loop counter for line characters */
+   int j;                        /* Counter for characters */
+   int len;                      /* Length of result line */
+   int nonspace;                 /* Non-space character encountered? */
+   int quoted;                   /* Character is inside quotes? */
+
+/* Initialise. */
+   line = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return line;
+
+/* Loop to read input lines until one is found which contains useful
+   characters or end of file is reached (or a read error occurs). */
+   while ( !line && ( line = astGetNextText( this ) ) && astOK ) {
+
+/* Loop to remove comments and leading and trailing white space. */
+      len = 0;
+      nonspace = 0;
+      quoted = 0;
+      for ( i = j = 0; line[ i ]; i++ ) {
+
+/* Note quote characters and ignore all text after the first unquoted
+   comment character. */
+         if ( line[ i ] == '"' ) quoted = !quoted;
+         if ( ( line[ i ] == '#' ) && !quoted ) break;
+
+/* Note the first non-space character and ignore everything before
+   it. */
+         if ( ( nonspace = nonspace || !isspace( line[ i ] ) ) ) {
+
+/* Move each character to its new position in the string. */
+            line[ j++ ] = line[ i ];
+
+/* Note the final length of the string (ignoring trailing spaces). */
+            if ( !isspace( line[ i ] ) ) len = j;
+         }
+      }
+
+/* If the string is not empty, terminate it. */
+     if ( len ) {
+        line[ len ] = '\0';
+
+/* Otherwise, free the memory used for the string so that another
+   input line will be read. */
+      } else {
+         line = astFree( line );
+      }
+   }
+
+/* Return the result pointer. */
+   return line;
+
+/* Undefine macros local to this function. */
+#undef MIN_CHARS
+}
+
+static AstChannelValue *LookupValue( const char *name, int *status ) {
+/*
+*  Name:
+*     LookupValue
+
+*  Purpose:
+*     Look up a Value structure by name.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     AstChannelValue *LookupValue( const char *name )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function searches the current values list (i.e. at the
+*     current nesting level) to identify a Value structure with a
+*     specified name. If one is found, it is removed from the list and
+*     a pointer to it is returned. If no suitable Value can be found,
+*     a NULL pointer is returned instead.
+
+*  Parameters:
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required Value. This must be in
+*        lower case with no surrounding white space. Note that names
+*        longer than NAME_MAX characters will not match any Value.
+
+*  Returned value:
+*     Pointer to the required Value structure, or NULL if no suitable
+*     Value exists.
+
+*  Notes:
+*     - The returned pointer refers to a dynamically allocated
+*     structure and it is the callers responsibility to free this when
+*     no longer required. The FreeValue function must be used for this
+*     purpose.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstChannelValue **head;       /* Address of head of list pointer */
+   AstChannelValue *result;      /* Pointer value to return */
+   AstChannelValue *value;       /* Pointer to list element */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Check that the "values_ok" flag is set. If not, the Values in the
+   values list belong to a different class to that of the current
+   class loader, so we cannot return any Value. */
+   if ( values_ok[ nest ] ) {
+
+/* Obtain the address of the current "head of list" pointer for the
+   values list (at the current nesting level). */
+      head = values_list + nest;
+
+/* Obtain the head of list pointer itself and check the list is not
+   empty. */
+      if ( ( value = *head ) ) {
+
+/* Loop to inspect each list element. */
+         while ( 1 ) {
+
+/* If a name match is found, remove the element from the list, return
+   a pointer to it and quit searching. */
+            if ( !strcmp( name, value->name ) ) {
+               RemoveValue( value, head, status );
+               result = value;
+               break;
+            }
+
+/* Follow the list until we return to the head. */
+            value = value->flink;
+            if ( value == *head ) break;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void OutputTextItem( AstChannel *this, const char *line, int *status ) {
+/*
+*  Name:
+*     OutputTextItem
+
+*  Purpose:
+*     Output a data item formatted as text.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void OutputTextItem( AstChannel *this, const char *line, int *status )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function outputs a data item formatted as a text string to
+*     a data sink associated with a Channel. It keeps track of the
+*     number of items written.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     line
+*        Pointer to a constant null-terminated string containing the
+*        data item to be output (no newline character should be
+*        appended).
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Write out the line of text using the astPutNextText method (which
+   may be over-ridden). */
+   astPutNextText( this, line );
+
+/* If successful, increment the count of items written. */
+   if ( astOK ) items_written++;
+}
+
+static void PutChannelData( AstChannel *this, void *data, int *status ) {
+/*
+c++
+*  Name:
+*     astPutChannelData
+
+*  Purpose:
+*     Store arbitrary data to be passed to a source or sink function.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astPutChannelData( AstChannel *this, void *data )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function stores a supplied arbitrary pointer in the Channel.
+*     When a source or sink function is invoked by the Channel, the
+*     invoked function can use the astChannelData macro to retrieve the
+*     pointer. This provides a thread-safe alternative to passing file
+*     descriptors, etc, via global static variables.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     data
+*        A pointer to be made available to the source and sink functions
+*        via the astChannelData macro. May be NULL.
+
+*  Applicability:
+*     Channel
+*        All Channels have this function.
+
+*  Notes:
+*     - This routine is not available in the Fortran 77 interface to
+*     the AST library.
+c--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store the pointer. */
+   this->data = data;
+}
+
+static void PutNextText( AstChannel *this, const char *line, int *status ) {
+/*
+*+
+*  Name:
+*     astPutNextText
+
+*  Purpose:
+*     Write a line of output text to a data sink.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astPutNextText( AstChannel *this, const char *line )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function writes an output line of text to a data sink
+*     associated with a Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     line
+*        Pointer to a constant null-terminated string containing the
+*        line of output text to be written (no newline character
+*        should be appended).
+
+*  Notes:
+*     - This method is provided primarily so that derived classes may
+*     over-ride it in order to write to alternative (textual) data
+*     sinks.
+*-
+*/
+
+/* Local Constants: */
+#define ERRBUF_LEN 80
+
+/* Local Variables: */
+   char *errstat;                /* Pointer for system error message */
+   char errbuf[ ERRBUF_LEN ];    /* Buffer for system error message */
+   const char *sink_file;        /* Path to output sink file */
+   const char *source_file;      /* Path to output source file */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If the SinkFile attribute of the Channel specifies an output file,
+   but no output file has yet been opened, open it now. Report an error
+   if it is the same as the source file. */
+   if( astTestSinkFile( this ) && !this->fd_out ) {
+      sink_file = astGetSinkFile( this );
+
+      if( this->fd_out ) {
+         source_file = astGetSourceFile( this );
+         if( astOK && !strcmp( sink_file, source_file ) ) {
+            astError( AST__WRERR, "astWrite(%s): Failed to open output "
+                      "SinkFile '%s' - the file is currently being used "
+                      "as the input SourceFile.", status, astGetClass( this ),
+                      sink_file );
+         }
+      }
+
+      if( astOK ) {
+         this->fd_out = fopen( sink_file, "w" );
+         if( !this->fd_out ) {
+            if ( errno ) {
+#if HAVE_STRERROR_R
+               strerror_r( errno, errbuf, ERRBUF_LEN );
+               errstat = errbuf;
+#else
+               errstat = strerror( errno );
+#endif
+               astError( AST__WRERR, "astWrite(%s): Failed to open output "
+                         "SinkFile '%s' - %s.", status, astGetClass( this ),
+                         sink_file, errstat );
+            } else {
+               astError( AST__WRERR, "astWrite(%s): Failed to open output "
+                         "SinkFile '%s'.", status, astGetClass( this ),
+                         sink_file );
+            }
+         }
+      }
+   }
+
+/* Check no error occurred above. */
+   if( astOK ) {
+
+/* If an active output file descriptor is stored in the channel, write
+   the text to it, with a newline appended. */
+      if( this->fd_out ) {
+         (void) fprintf( this->fd_out, "%s\n", line );
+
+/* Otherwise, if a sink function (and its wrapper function) is defined for
+   the Channel, use the wrapper function to invoke the sink function to
+   output the text line. Since we are about to call an externally supplied
+   function which may not be thread-safe, lock a mutex first. Also store
+   the channel data pointer in a global variable so that it can be accessed
+   in the source function using macro astChannelData. */
+      } else if ( this->sink && this->sink_wrap ) {
+         astStoreChannelData( this );
+         LOCK_MUTEX2;
+         ( *this->sink_wrap )( *this->sink, line, status );
+         UNLOCK_MUTEX2;
+
+/* Otherwise, simply write the text to standard output with a newline
+   appended. */
+      } else {
+         (void) printf( "%s\n", line );
+      }
+   }
+}
+
+static AstObject *Read( AstChannel *this, int *status ) {
+/*
+*++
+*  Name:
+c     astRead
+f     AST_READ
+
+*  Purpose:
+*     Read an Object from a Channel.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "channel.h"
+c     AstObject *astRead( AstChannel *this )
+f     RESULT = AST_READ( THIS, STATUS )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function reads the next Object from a Channel and returns a
+*     pointer to the new Object.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Channel.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astRead()
+f     AST_READ = INTEGER
+*        A pointer to the new Object. The class to which this will
+*        belong is determined by the input data, so is not known in
+*        advance.
+
+*  Applicability:
+*     FitsChan
+c        All successful use of astRead on a FitsChan is destructive, so that
+f        All successful use of AST_READ on a FitsChan is destructive, so that
+*        FITS header cards are consumed in the process of reading an Object,
+*        and are removed from the FitsChan (this deletion can be prevented
+*        for specific cards by calling the FitsChan
+c        astRetainFits function).
+f        AST_RETAINFITS routine).
+*        An unsuccessful call of
+c        astRead
+f        AST_READ
+*        (for instance, caused by the FitsChan not containing the necessary
+*        FITS headers cards needed to create an Object) results in the
+*        contents of the FitsChan being left unchanged.
+*     StcsChan
+*        The AST Object returned by a successful use of
+c        astRead
+f        AST_READ
+*        on an StcsChan, will be either a Region or a KeyMap, depending
+*        on the values of the StcsArea, StcsCoords and StcsProps
+*        attributes. See the documentation for these attributes for further
+*        information.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned, without
+*     error, if the Channel contains no further Objects to be read.
+*     - A null Object pointer will also be returned if this function
+c     is invoked with the AST error status set, or if it should fail
+f     is invoked with STATUS set to an error value, or if it should fail
+*     for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstLoaderType *loader;        /* Pointer to loader for Object */
+   AstObject *new;               /* Pointer to new Object */
+   char *class;                  /* Pointer to Object class name string */
+   char *name;                   /* Pointer to data item name */
+   int skip;                     /* Skip non-AST data? */
+   int top;                      /* Reading top-level Object definition? */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Determine if we are reading a top-level (i.e. user-level) Object
+   definition, as opposed to the definition of an Object contained
+   within another Object. This is indicated by the current nesting
+   level. */
+   top = ( nest == -1 );
+
+/* If reading a top-level object, determine if data lying in between
+   Object definitions in the input data stream are to be skipped. */
+   skip = ( top && astGetSkip( this ) );
+
+/* Read the next input data item. If we are reading a top-level Object
+   definition, skip any unrelated data beforehand. Otherwise read the
+   data strictly as it comes (there should be no unrelated data
+   embedded within Object definitions themselves). */
+   astGetNextData( this, skip, &name, &class );
+
+/* If no suitable data item was found (and no error occurred), we have
+   reached the end of data. For a top-level Object a NULL Object
+   pointer is simply returned, but for a nested Object this indicates
+   that part of the Object definition is missing, so report an
+   error. */
+   if ( astOK ) {
+      if ( !name ) {
+         if ( !top ) {
+            astError( AST__EOCHN,
+                      "astRead(%s): End of input encountered while trying to "
+                      "read an AST Object.", status, astGetClass( this ) );
+         }
+
+/* If a data item was found, check it is a "Begin" item. If not, there
+   is a data item missing, so report an error and free all memory. */
+      } else if ( strcmp( name, "begin" ) ) {
+         astError( AST__BADIN,
+                   "astRead(%s): Missing \"Begin\" when expecting an Object.", status,
+                   astGetClass( this ) );
+         name = astFree( name );
+         if ( class ) class = astFree( class );
+
+/* If the required "Begin" item was found, free the memory used for the
+   name string. */
+      } else {
+         name = astFree( name );
+
+/* Use the associated class name to locate the loader for that
+   class. This function will then be used to build the Object. */
+         loader = astGetLoader( class, status );
+
+/* Extend all necessary stack arrays to accommodate entries for the
+   next nesting level (this allocates space if none has yet been
+   allocated). */
+         end_of_object = astGrow( end_of_object, nest + 2, sizeof( int ) );
+         object_class = astGrow( object_class, nest + 2, sizeof( char * ) );
+         values_class = astGrow( values_class, nest + 2, sizeof( char * ) );
+         values_list = astGrow( values_list, nest + 2, sizeof( AstChannelValue * ) );
+         values_ok = astGrow( values_ok, nest + 2, sizeof( int ) );
+
+/* If an error occurred, free the memory used by the class string,
+   which will not now be used. */
+         if ( !astOK ) {
+            class = astFree( class );
+
+/* Otherwise, increment the nesting level and initialise the new stack
+   elements for this new level. This includes clearing the
+   "end_of_object" flag so that ReadClassData can read more data, and
+   storing the class name of the object we are about to read. */
+         } else {
+            nest++;
+            end_of_object[ nest ] = 0;
+            object_class[ nest ] = class;
+            values_class[ nest ] = NULL;
+            values_list[ nest ] = NULL;
+            values_ok[ nest ] = 0;
+
+/* Invoke the loader, which reads the Object definition from the input
+   data stream and builds the Object. Supply NULL/zero values to the
+   loader so that it will substitute values appropriate to its own
+   class. */
+            new = (*loader)( NULL, (size_t) 0, NULL, NULL, this, status );
+
+/* Clear the values list for the current nesting level. If the list
+   has not been read or any Values remain in it, an error will
+   result. */
+            ClearValues( this, status );
+
+/* If no error has yet occurred, check that the "end_of_object" flag
+   has been set. If not, the input data were not correctly terminated,
+   so report an error. */
+            if ( astOK && !end_of_object[ nest ] ) {
+               astError( AST__BADIN,
+                         "astRead(%s): Unexpected end of input (missing end "
+                         "of %s).", status,
+                         astGetClass( this ), object_class[ nest ] );
+            }
+
+/* If an error occurred, report contextual information. Only do this
+   for top-level Objects to avoid multple messages. */
+            if ( !astOK && top ) {
+               astError( astStatus, "Error while reading a %s from a %s.", status,
+                         class, astGetClass( this ) );
+            }
+
+/* Clear the Object's class string, freeing the associated memory
+   (note this is the memory allocated for the "class" string
+   earlier). */
+            object_class[ nest ] = astFree( object_class[ nest ] );
+
+/* Restore the previous nesting level. */
+            nest--;
+         }
+
+/* Once the top-level Object has been built, free the memory used by
+   the stack arrays. */
+         if ( top ) {
+            end_of_object = astFree( end_of_object );
+            object_class = astFree( object_class );
+            values_class = astFree( values_class );
+            values_list = astFree( values_list );
+            values_ok = astFree( values_ok );
+         }
+      }
+   }
+
+/* If an error occurred, clean up by deleting the new Object and
+   return a NULL pointer. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the pointer to the new Object. */
+   return new;
+}
+
+static void ReadClassData( AstChannel *this, const char *class, int *status ) {
+/*
+*+
+*  Name:
+*     astReadClassData
+
+*  Purpose:
+*     Read values from a data source for a class loader.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astReadClassData( AstChannel *this, const char *class )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function reads the data for a class from the data source
+*     associated with a Channel, so as to provide values for
+*     initialising the instance variables of that class as part of
+*     building a complete Object. This function should be invoked by
+*     the loader for each class immediately before it attempts to read
+*     these values.
+*
+*     The values read are placed into the current values list by this
+*     function. They may then be read from this list by the class
+*     loader making calls to astReadDouble, astReadInt, astReadObject
+*     and astReadString. The order in which values are read by the
+*     loader is unimportant (although using the same order for reading
+*     as for writing will usually be more efficient) and values are
+*     removed from the list as they are read.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     class
+*        A pointer to a constant null-terminated string containing the
+*        name of the class whose loader is requesting the data (note
+*        this is not usually the same as the class name of the Object
+*        being built). This value allows the class structure of the
+*        input data to be validated.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstObject *object;            /* Pointer to new Object */
+   AstChannelValue *value;       /* Pointer to Value structure */
+   char *name;                   /* Pointer to data item name string */
+   char *val;                    /* Pointer to data item value string */
+   int done;                     /* All class data read? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* If the "values_ok" flag is set, this indicates that the values list
+   (at the current nesting level) has been filled by a previous
+   invocation of this function and has then been read by the
+   appropriate class loader. In this case, clear any entries which may
+   remain in the current values list. If any such entries are found,
+   they represent input data that were not read, so an error will
+   result. */
+   if ( values_ok[ nest ] ) ClearValues( this, status );
+
+/* If "values_class" is non-NULL, this indicates that the values list
+   (at the current nesting level) has been filled by a previous
+   invocation of this function, but that the values belong to a class
+   whose loader has not yet tried to read them. In this case, we must
+   continue to keep the values until they are needed, so we do not
+   read any more input data this time. */
+   if ( values_class[ nest ] ) {
+
+/* If the class to which the previously saved values belong matches
+   the class we now want values for, set the "values_ok" flag. This
+   then allows the values to be accessed (by LookupValue). */
+      values_ok[ nest ] = !strcmp( values_class[ nest ], class );
+
+/* If the current values list is empty, we must read in values for the
+   next class that appears in the input data. However, first check
+   that the "end_of_object" flag has not been set. If it has, we have
+   already reached the end of this Object's data, so there is some
+   kind of problem with the order in which class loaders have been
+   invoked. This will probably never happen, but report an error if
+   necessary. */
+   } else if ( end_of_object[ nest ] ) {
+      astError( AST__LDERR,
+                "astRead(%s): Invalid attempt to read further %s data "
+                "following an end of %s.", status,
+                astGetClass( this ), class, object_class[ nest ] );
+      astError( AST__LDERR,
+                "Perhaps the wrong class loader has been invoked?" , status);
+
+/* If we need new values, loop to read input data items until the end
+   of the data for a class is reached. */
+   } else {
+      done = 0;
+      while ( astOK && !done ) {
+
+/* Read the next input data item. */
+         astGetNextData( this, 0, &name, &val );
+         if ( astOK ) {
+
+/* Unexpected end of input. */
+/* ------------------------ */
+/* If no "name" value is returned, we have reached the end of the
+   input data stream without finding the required end of class
+   terminator, so report an error. */
+            if ( !name ) {
+               astError( AST__EOCHN,
+                         "astRead(%s): Unexpected end of input (missing end "
+                         "of %s).", status,
+                         astGetClass( this ), object_class[ nest ] );
+
+/* "IsA" item. */
+/* ----------- */
+/* Otherwise, if an "IsA" item was read, it indicates the end of the
+   data for a class. Store the pointer to the name of this class in
+   "values_class" and note whether this is the class whose data we
+   wanted in "values_ok". If the data we have read do not belong to
+   the class we wanted, they will simply be kept until the right class
+   comes looking for them. */
+            } else if ( !strcmp( name, "isa" ) ) {
+               values_class[ nest ] = val;
+               values_ok[ nest ] = !strcmp( val, class );
+
+/* Free the memory holding the name string. */
+               name = astFree( name );
+
+/* Note we have finished reading class data. */
+               done = 1;
+
+/* "End" item. */
+/* ----------- */
+/* If an "End" item was read, it indicates the end of the data both
+   for a class and for the current Object definition as a whole. Set
+   the "end_of_object" flag (for the current nesting level) which
+   prevents any further data being read for this Object. This flag is
+   also used (by Read) to check that an "End" item was actually
+   read. */
+            } else if ( !strcmp( name, "end" ) ) {
+               end_of_object[ nest ] = 1;
+
+/* Check that the class name in the "End" item matches that of the
+   Object being built. If so, store the pointer to the name of this
+   class in "values_class" and note whether this is the class whose
+   data we wanted in "values_ok". If the data we have read do not
+   belong to the class we wanted, they will simply be kept until the
+   right class comes looking for them. */
+               if ( !strcmp( val, object_class[ nest ] ) ) {
+                  values_class[ nest ] = val;
+                  values_ok[ nest ] = !strcmp( class, val );
+
+/* If the "End" item contains the wrong class name (i.e. not matching
+   the corresponding "Begin" item), then report an error. */
+               } else {
+                  astError( AST__BADIN,
+                            "astRead(%s): Bad class structure in input data.", status,
+                            astGetClass( this ) );
+                  astError( AST__BADIN,
+                            "End of %s read when expecting end of %s.", status,
+                            val, object_class[ nest ] );
+
+/* Free the memory used by the class string, which will not now be
+   used. */
+                  val = astFree( val );
+               }
+
+/* Free the memory holding the name string. */
+               name = astFree( name );
+
+/* Note we have finished reading class data. */
+               done = 1;
+
+/* String value. */
+/* ------------- */
+/* If any other name is obtained and "val" is not NULL, we have read a
+   non-Object value, encoded as a string. Allocate memory for a Value
+   structure to describe it. */
+            } else if ( val ) {
+               value = astMalloc( sizeof( AstChannelValue ) );
+               if ( astOK ) {
+
+/* Store pointers to the name and value string in the Value structure
+   and note this is not an Object value. */
+                  value->name = name;
+                  value->ptr.string = val;
+                  value->is_object = 0;
+
+/* Append the Value structure to the values list for the current
+   nesting level. */
+                  AppendValue( value, values_list + nest, status );
+
+/* If an error occurred, free the memory holding the "name" and "val"
+   strings. */
+               } else {
+                  name = astFree( name );
+                  val = astFree( val );
+               }
+
+/* Object value. */
+/* ------------- */
+/* If "val" is NULL, we have read an Object item, and the Object
+   definition should follow. Allocate memory for a Value structure to
+   describe it. */
+            } else {
+               value = astMalloc( sizeof( AstChannelValue ) );
+
+/* Invoke astRead to read the Object definition from subsequent data
+   items and to build the Object, returning a pointer to it. This will
+   result in recursive calls to the current function, but as these
+   will use higher nesting levels they will not interfere with the
+   current invocation. */
+               astreadclassdata_msg = 0;
+               object = astRead( this );
+               if ( astOK ) {
+
+/* Store pointers to the name and Object in the Value structure and
+   note this is an Object value. */
+                  value->name = name;
+                  value->ptr.object = object;
+                  value->is_object = 1;
+
+/* Append the Value structure to the values list for the current
+   nesting level. */
+                  AppendValue( value, values_list + nest, status );
+
+/* If an error occurred, report a contextual error maessage and set
+   the "astreadclassdata_msg" flag (this prevents multiple messages if this function is
+   invoked recursively to deal with nested Objects). */
+               } else {
+                  if ( !astreadclassdata_msg ) {
+                     astError( astStatus,
+                               "Failed to read the \"%s\" Object value.", status,
+                               name );
+                     astreadclassdata_msg = 1;
+                  }
+
+/* Free the memory holding the "name" string and any Value structure
+   that was allocated. */
+                  name = astFree( name );
+                  value = astFree( value );
+               }
+            }
+         }
+      }
+   }
+}
+
+static double ReadDouble( AstChannel *this, const char *name, double def, int *status ) {
+/*
+*+
+*  Name:
+*     astReadDouble
+
+*  Purpose:
+*     Read a double value as part of loading a class.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     double astReadDouble( AstChannel *this, const char *name, double def )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function searches the current values list of a Channel to
+*     identify a double value with a specified name. If such a value
+*     is found, it is returned, otherwise a default value is returned
+*     instead.
+*
+*     This function should only be invoked from within the loader
+*     function associated with a class, in order to return a double
+*     value to be assigned to an instance variable. It must be
+*     preceded by a call to the astReadClassData function, which loads
+*     the values associated with the class into the current values
+*     list from the input data source.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required value. This must be in
+*        lower case with no surrounding white space. Note that names
+*        longer than 6 characters will not match any value.
+*     def
+*        If no suitable value can be found (e.g. it is absent from the
+*        data stream being read), then this value will be returned
+*        instead.
+
+*  Returned Value:
+*     The required value, or the default if the value was not found.
+
+*  Notes:
+*     - A value of 0.0 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstChannelValue *value;       /* Pointer to required Value structure */
+   double result;                /* Value to be returned */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Initialise. */
+   result = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Search for a Value structure with the required name in the current
+   values list.*/
+   value = LookupValue( name, status );
+   if ( astOK ) {
+
+/* If a Value was found, check that it describes a string (as opposed
+   to an Object). */
+      if ( value ) {
+         if ( !value->is_object ) {
+
+/* If so, then attempt to decode the string to give a double value,
+   checking that the entire string is read (and checking for the magic string
+   used to represent bad values). If this fails, then the wrong name has
+   probably been given, or the input data are corrupt, so report an error. */
+            nc = 0;
+            if ( ( 0 == astSscanf( value->ptr.string, " " BAD_STRING " %n",
+                                                      &nc ) )
+                    && ( nc >= (int) strlen( value->ptr.string ) ) ) {
+               result = AST__BAD;
+
+            } else if ( !( ( 1 == astSscanf( value->ptr.string, " %lf %n",
+                                                      &result, &nc ) )
+                    && ( nc >= (int) strlen( value->ptr.string ) ) ) ) {
+               astError( AST__BADIN,
+                         "astRead(%s): The value \"%s = %s\" cannot "
+                         "be read as a double precision floating point "
+                         "number.", status, astGetClass( this ),
+                         value->name, value->ptr.string );
+
+            } else if( !astISFINITE( result ) ) {
+               astError( AST__BADIN,
+                         "astRead(%s): Illegal double precision floating "
+                         "point value \"%s\" read for \"%s\".", status,
+                         astGetClass( this ), value->ptr.string, value->name );
+
+            }
+
+/* Report a similar error if the Value does not describe a string. */
+         } else {
+            astError( AST__BADIN,
+                      "astRead(%s): The Object \"%s = <%s>\" cannot "
+                      "be read as a double precision floating point number.", status,
+                      astGetClass( this ),
+                      value->name, astGetClass( value->ptr.object ) );
+         }
+
+/* Free the Value structure and the resources it points at. */
+         value = FreeValue( value, status );
+
+/* If no suitable Value structure was found, then use the default
+   value instead. */
+      } else {
+         result = def;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int ReadInt( AstChannel *this, const char *name, int def, int *status ) {
+/*
+*+
+*  Name:
+*     astReadInt
+
+*  Purpose:
+*     Read an int value as part of loading a class.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     int astReadInt( AstChannel *this, const char *name, int def )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function searches the current values list of a Channel to
+*     identify an int value with a specified name. If such a value is
+*     found, it is returned, otherwise a default value is returned
+*     instead.
+*
+*     This function should only be invoked from within the loader
+*     function associated with a class, in order to return an int
+*     value to be assigned to an instance variable. It must be
+*     preceded by a call to the astReadClassData function, which loads
+*     the values associated with the class into the current values
+*     list from the input data source.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required value. This must be in
+*        lower case with no surrounding white space. Note that names
+*        longer than 6 characters will not match any value.
+*     def
+*        If no suitable value can be found (e.g. it is absent from the
+*        data stream being read), then this value will be returned
+*        instead.
+
+*  Returned Value:
+*     The required value, or the default if the value was not found.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstChannelValue *value;       /* Pointer to required Value structure */
+   int nc;                       /* Number of characters read by astSscanf */
+   int result;                   /* Value to be returned */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Search for a Value structure with the required name in the current
+   values list.*/
+   value = LookupValue( name, status );
+   if ( astOK ) {
+
+/* If a Value was found, check that it describes a string (as opposed
+   to an Object). */
+      if ( value ) {
+         if ( !value->is_object ) {
+
+/* If so, then attempt to decode the string to give an int value,
+   checking that the entire string is read. If this fails, then the
+   wrong name has probably been given, or the input data are corrupt,
+   so report an error. */
+            nc = 0;
+            if ( !( ( 1 == astSscanf( value->ptr.string, " %d %n",
+                                                      &result, &nc ) )
+                    && ( nc >= (int) strlen( value->ptr.string ) ) ) ) {
+               astError( AST__BADIN,
+                         "astRead(%s): The value \"%s = %s\" cannot "
+                         "be read as an integer.", status, astGetClass( this ),
+                         value->name, value->ptr.string );
+            }
+
+/* Report a similar error if the Value does not describe a string. */
+         } else {
+            astError( AST__BADIN,
+                      "astRead(%s): The Object \"%s = <%s>\" cannot "
+                      "be read as an integer.", status, astGetClass( this ),
+                      value->name, astGetClass( value->ptr.object ) );
+         }
+
+/* Free the Value structure and the resources it points at. */
+         value = FreeValue( value, status );
+
+/* If no suitable Value structure was found, then use the default
+   value instead. */
+      } else {
+         result = def;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstObject *ReadObject( AstChannel *this, const char *name,
+                              AstObject *def, int *status ) {
+/*
+*+
+*  Name:
+*     astReadObject
+
+*  Purpose:
+*     Read a (sub)Object as part of loading a class.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     AstObject *astReadObject( AstChannel *this, const char *name,
+*                               AstObject *def )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function searches the current values list of a Channel to
+*     identify an Object with a specified name. If such an Object is
+*     found, a pointer to it is returned, otherwise a default pointer
+*     is returned instead.
+*
+*     This function should only be invoked from within the loader
+*     function associated with a class, in order to return an Object
+*     pointer value to be assigned to an instance variable. It must be
+*     preceded by a call to the astReadClassData function, which loads
+*     the values associated with the class into the current values
+*     list from the input data source.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required Object. This must be in
+*        lower case with no surrounding white space. Note that names
+*        longer than 6 characters will not match any Object.
+*     def
+*        If no suitable Object can be found (e.g. the Object is absent
+*        from the data stream being read), then a clone of this
+*        default Object pointer will be returned instead (or NULL if
+*        this default pointer is NULL).
+
+*  Returned Value:
+*     A pointer to the Object, or a clone of the default pointer if
+*     the Object was not found.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstObject *result;            /* Pointer value to return */
+   AstChannelValue *value;       /* Pointer to required Value structure */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Search for a Value structure with the required name in the current
+   values list.*/
+   value = LookupValue( name, status );
+   if ( astOK ) {
+
+/* If a Value was found, check that it describes an Object (as opposed to
+   a string). */
+      if ( value ) {
+         if ( value->is_object ) {
+
+/* If so, then extract the Object pointer, replacing it with NULL. */
+            result = value->ptr.object;
+            value->ptr.object = NULL;
+
+/* If the Value does not describe an Object, then the wrong name has
+   probably been given, or the input data are corrupt, so report an
+   error. */
+         } else {
+            astError( AST__BADIN,
+                      "astRead(%s): The value \"%s = %s\" cannot be "
+                      "read as an Object.", status, astGetClass( this ),
+                      value->name, value->ptr.string );
+         }
+
+/* Free the Value structure and the resources it points at. */
+         value = FreeValue( value, status );
+
+/* If no suitable Value structure was found, clone the default
+   pointer, if given. */
+      } else if ( def ) {
+         result = astClone( def );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static char *ReadString( AstChannel *this, const char *name,
+                         const char *def, int *status ) {
+/*
+*+
+*  Name:
+*     astReadString
+
+*  Purpose:
+*     Read a string value as part of loading a class.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     char *astReadString( AstChannel *this, const char *name,
+*                          const char *def )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function searches the current values list of a Channel to
+*     identify a string value with a specified name. If such a value
+*     is found, a pointer to the string is returned, otherwise a
+*     pointer to a copy of a default string is returned instead.
+*
+*     This function should only be invoked from within the loader
+*     function associated with a class, in order to return a string
+*     pointer value to be assigned to an instance variable. It must be
+*     preceded by a call to the astReadClassData function, which loads
+*     the values associated with the class into the current values
+*     list from the input data source.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required value. This must be in
+*        lower case with no surrounding white space. Note that names
+*        longer than 6 characters will not match any value.
+*     def
+*        If no suitable string can be found (e.g. the value is absent
+*        from the data stream being read), then a dynamically
+*        allocated copy of the null-terminated string pointed at by
+*        "def" will be made, and a pointer to this copy will be
+*        returned instead (or NULL if this default pointer is NULL).
+
+*  Returned Value:
+*     A pointer to a dynamically allocated null-terminated string
+*     containing the value required, or to a copy of the default
+*     string if the value was not found (or NULL if the "def" pointer
+*     was NULL).
+
+*  Notes:
+*     - It is the caller's responsibility to arrange for the memory
+*     holding the returned string to be freed (using astFree) when it
+*     is no longer required.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstChannelValue *value;       /* Pointer to required Value structure */
+   char *result;                 /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Search for a Value structure with the required name in the current
+   values list.*/
+   value = LookupValue( name, status );
+   if ( astOK ) {
+
+/* If a Value was found, check that it describes a string (as opposed
+   to an Object). */
+      if ( value ) {
+         if ( !value->is_object ) {
+
+/* If so, then extract the string pointer, replacing it with NULL. */
+            result = value->ptr.string;
+            value->ptr.string = NULL;
+
+/* If the Value does not describe a string, then the wrong name has
+   probably been given, or the input data are corrupt, so report an
+   error. */
+         } else {
+            astError( AST__BADIN,
+                      "astRead(%s): The Object \"%s = <%s>\" cannot "
+                      "be read as a string.", status, astGetClass( this ),
+                      value->name, astGetClass( value->ptr.object ) );
+         }
+
+/* Free the Value structure and the resources it points at. */
+         value = FreeValue( value, status );
+
+/* If no suitable Value structure was found, then make a dynamic copy
+   of the default string (if given) and return a pointer to this. */
+      } else if ( def ) {
+         result = astStore( NULL, def, strlen( def ) + (size_t) 1 );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void RemoveValue( AstChannelValue *value, AstChannelValue **head, int *status ) {
+/*
+*  Name:
+*     RemoveValue
+
+*  Purpose:
+*     Remove a Value structure from a circular linked list.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void RemoveValue( AstChannelValue *value, AstChannelValue **head, int *status );
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function removes a Value structure from a doubly linked
+*     circular list of such structures. The "head of list" pointer is
+*     updated to point at the element following the one removed.
+
+*  Parameters:
+*     value
+*        Pointer to the structure to be removed (note that this must
+*        actually be in the list, although this function does not
+*        check).
+*     head
+*        Address of a pointer to the element at the head of the
+*        list. This pointer will be updated to point at the list
+*        element that follows the one removed. If the list becomes
+*        empty, the pointer will be set to NULL.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function does not perform error chacking and does not
+*     generate errors.
+*/
+
+/* Remove the Value structure from the list by re-establishing links
+   between the elements on either side of it. */
+   value->blink->flink = value->flink;
+   value->flink->blink = value->blink;
+
+/* Update the head of list pointer to identify the following
+   element. */
+   *head = value->flink;
+
+/* If the head of list identifies the removed element, then note that
+   the list is now empty. */
+   if ( *head == value ) *head = NULL;
+
+/* Make the removed element point at itself. */
+   value->flink = value;
+   value->blink = value;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Channel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     Channel member function (over-rides the astSetAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function assigns an attribute value for a Channel, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstChannel *this;             /* Pointer to the Channel structure */
+   int comment;                  /* Comment attribute value */
+   int full;                     /* Full attribute value */
+   int indent;                   /* Indent attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by "astSscanf" */
+   int report_level;             /* Skip attribute value */
+   int skip;                     /* Skip attribute value */
+   int sourcefile;               /* Offset of SourceFile string */
+   int sinkfile;                 /* Offset of SinkFile string */
+   int strict;                   /* Report errors instead of warnings? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Channel structure. */
+   this = (AstChannel *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* Comment. */
+/* ---------*/
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "comment= %d %n", &comment, &nc ) )
+        && ( nc >= len ) ) {
+      astSetComment( this, comment );
+
+/* Full. */
+/* ----- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "full= %d %n", &full, &nc ) )
+               && ( nc >= len ) ) {
+      astSetFull( this, full );
+
+/* Indent. */
+/* ------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "indent= %d %n", &indent, &nc ) )
+               && ( nc >= len ) ) {
+      astSetIndent( this, indent );
+
+/* ReportLavel. */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "reportlevel= %d %n", &report_level, &nc ) )
+               && ( nc >= len ) ) {
+      astSetReportLevel( this, report_level );
+
+/* Skip. */
+/* ----- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "skip= %d %n", &skip, &nc ) )
+               && ( nc >= len ) ) {
+      astSetSkip( this, skip );
+
+/* SinkFile. */
+/* --------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "sinkfile=%n%*[^\n]%n", &sinkfile, &nc ) )
+               && ( nc >= len ) ) {
+      astSetSinkFile( this, setting + sinkfile );
+
+/* SourceFile. */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "sourcefile=%n%*[^\n]%n", &sourcefile, &nc ) )
+               && ( nc >= len ) ) {
+      astSetSourceFile( this, setting + sourcefile );
+
+/* Strict. */
+/* ------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "strict= %d %n", &strict, &nc ) )
+               && ( nc >= len ) ) {
+      astSetStrict( this, strict );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SinkWrap( void (* sink)( const char * ), const char *line, int *status ) {
+/*
+*  Name:
+*     SinkWrap
+
+*  Purpose:
+*     Wrapper function to invoke a C Channel sink function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void SinkWrap( void (* sink)( const char * ), const char *line, int *status )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function invokes the sink function whose pointer is
+*     supplied in order to write an output line to an external data
+*     store.
+
+*  Parameters:
+*     sink
+*        Pointer to a sink function, whose single parameter is a
+*        pointer to a const, null-terminated string containing the
+*        text to be written, and which returns void. This is the form
+*        of Channel sink function employed by the C language interface
+*        to the AST library.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the sink function. */
+   ( *sink )( line );
+}
+
+static char *SourceWrap( const char *(* source)( void ), int *status ) {
+/*
+*  Name:
+*     SourceWrap
+
+*  Purpose:
+*     Wrapper function to invoke a C Channel source function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     char *SourceWrap( const char *, int *status(* source)( void ) )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function invokes the source function whose pointer is
+*     supplied in order to read the next input line from an external
+*     data store. It then returns a pointer to a dynamic string
+*     containing a copy of the text that was read.
+
+*  Parameters:
+*     source
+*        Pointer to a source function, with no parameters, that
+*        returns a pointer to a const, null-terminated string
+*        containing the text that it read. This is the form of Channel
+*        source function employed by the C language interface to the
+*        AST library.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated, null terminated string
+*     containing a copy of the text that was read. This string must be
+*     freed by the caller (using astFree) when no longer required.
+*
+*     A NULL pointer will be returned if there is no more input text
+*     to read.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   char *result;                 /* Pointer value to return */
+   const char *line;             /* Pointer to input line */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the source function to read the next input line and return a
+   pointer to the resulting string. */
+   line = ( *source )();
+
+/* If a string was obtained, make a dynamic copy of it and save the
+   resulting pointer. */
+   if ( line ) result = astString( line, (int) strlen( line ) );
+
+/* Return the result. */
+   return result;
+}
+
+void astStoreChannelData_( AstChannel *this, int *status ) {
+/*
+*+
+*  Name:
+*     astStoreChannelData
+
+*  Purpose:
+*     Store the Channel's channel-data pointer in a thread-specific
+*     global variable.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     astStoreChannelData( AstChannel *this )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function stores the Channel's channel-data pointer (if any)
+*     established by the previous call to astPutChannelData, in a
+*     thread-specific global variable from where the astChannelData macro
+*     can access it.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Store the pointer int he global variable. */
+   channel_data = this->data;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a Channel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Channel member function (over-rides the astTestAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a Channel's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstChannel *this;             /* Pointer to the Channel structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Channel structure. */
+   this = (AstChannel *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Comment. */
+/* -------- */
+   if ( !strcmp( attrib, "comment" ) ) {
+      result = astTestComment( this );
+
+/* Full. */
+/* ----- */
+   } else if ( !strcmp( attrib, "full" ) ) {
+      result = astTestFull( this );
+
+/* Indent. */
+/* ------- */
+   } else if ( !strcmp( attrib, "indent" ) ) {
+      result = astTestIndent( this );
+
+/* ReportLevel. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "reportlevel" ) ) {
+      result = astTestReportLevel( this );
+
+/* Skip. */
+/* ----- */
+   } else if ( !strcmp( attrib, "skip" ) ) {
+      result = astTestSkip( this );
+
+/* SourceFile. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "sourcefile" ) ) {
+      result = astTestSourceFile( this );
+
+/* SinkFile. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "sinkfile" ) ) {
+      result = astTestSinkFile( this );
+
+/* Strict. */
+/* ------- */
+   } else if ( !strcmp( attrib, "strict" ) ) {
+      result = astTestStrict( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static void Unquote( AstChannel *this, char *str, int *status ) {
+/*
+*  Name:
+*     Unquote
+
+*  Purpose:
+*     Remove quotes from a (possibly) quoted string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void Unquote( AstChannel *this, char *str, int *status )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function removes one layer of quote characters (") from a
+*     string which is possibly quoted. Any quotes within quotes (which
+*     should have been doubled when the string was originally quoted)
+*     are also converted back to single quotes again.
+*
+*     The quotes need not start or end at the ends of the string, and
+*     there may be any number of quoted sections within the string. No
+*     error results if the string does not contain any quotes at all
+*     (it is simply returned unchanged), but an error results if any
+*     unmatched quotes are found.
+
+*  Parameters:
+*     this
+*        Pointer to a Channel. This is only used for constructing error
+*        messages and has no influence on the string processing.
+*     str
+*        Pointer to the null-terminated string to be processed. This
+*        is modified in place. The new string starts at the same
+*        location as the original but has a new null character
+*        appended if necessary (it will usually be shorter than the
+*        original).
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int i;                        /* Loop counter for "input" characters */
+   int j;                        /* Counter for "output" characters */
+   int quoted;                   /* Inside a quoted string? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Loop to inspect each character in the string. */
+   quoted = 0;
+   for ( i = j = 0; str[ i ]; i++ ) {
+
+/* Non-quote characters are simply copied to their new location in the
+   string. */
+      if ( str[ i ] != '"' ) {
+         str[ j++ ] = str[ i ];
+
+/* If a quote character '"' is encountered and we are not already in a
+   quoted string, then note the start of a quoted string (and discard
+   the quote). */
+      } else if ( !quoted ) {
+         quoted = 1;
+
+/* If a quote character is encountered inside a quoted string, then
+   check if the next character is also a quote. If so, convert this
+   double quote to a single one. */
+      } else if ( str[ i + 1 ] == '"' ) {
+         str[ j++ ] = '"';
+         i++;
+
+/* If a single quote character is encountered inside a quoted string,
+   then note the end of the quoted string (and discard the quote). */
+      } else {
+         quoted = 0;
+      }
+   }
+
+/* Append a null to terminate the processed string. */
+   str[ j ] = '\0';
+
+/* If the "quoted" flag is still set, then there were unmatched
+   quotes, so report an error. */
+   if ( quoted ) {
+      astError( AST__UNMQT,
+                "astRead(%s): Unmatched quotes in input data: %s.", status,
+                astGetClass( this ), str );
+   }
+}
+
+static int Use( AstChannel *this, int set, int helpful, int *status ) {
+/*
+*  Name:
+*     Use
+
+*  Purpose:
+*     Decide whether to write a value to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     int Use( AstChannel *this, int set, int helpful, int *status )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function decides whether a value supplied by a class "Dump"
+*     function, via a call to one of the astWrite... protected
+*     methods, should actually be written to the data sink associated
+*     with a Channel.
+*
+*     This decision is based on the settings of the "set" and
+*     "helpful" flags supplied to the astWrite... method, plus the
+*     attribute settings of the Channel.
+
+*  Parameters:
+*     this
+*        A pointer to the Channel.
+*     set
+*        The "set" flag supplied.
+*     helpful
+*        The "helpful" value supplied.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the value should be written out, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   int full;                     /* Full attribute value */
+   int result;                   /* Result value to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* If "set" is non-zero, then so is the result ("set" values must
+   always be written out). */
+   result = ( set != 0 );
+
+/* Otherwise, obtain the value of the Channel's Full attribute. */
+   if ( !set ) {
+      full = astGetFull( this );
+
+/* If Full is positive, display all values, if zero, display only
+   "helpful" values, if negative, display no (un-"set") values. */
+      if ( astOK ) result = ( ( helpful && ( full > -1 ) ) || ( full > 0 ) );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int Write( AstChannel *this, AstObject *object, int *status ) {
+/*
+*++
+*  Name:
+c     astWrite
+f     AST_WRITE
+
+*  Purpose:
+*     Write an Object to a Channel.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "channel.h"
+c     int astWrite( AstChannel *this, AstObject *object )
+f     RESULT = AST_WRITE( THIS, OBJECT, STATUS )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function writes an Object to a Channel, appending it to any
+*     previous Objects written to that Channel.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Channel.
+c     object
+f     OBJECT = INTEGER (Given)
+*        Pointer to the Object which is to be written.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astWrite()
+f     AST_WRITE = INTEGER
+*        The number of Objects written to the Channel by this
+c        invocation of astWrite (normally, this will be one).
+f        invocation of AST_WRITE (normally, this will be one).
+
+*  Applicability:
+*     FitsChan
+*        If the FitsChan uses a foreign encoding (e.g. FITS-WCS) rather
+*        than the native AST encoding, then storing values in the
+*        FitsChan for keywords NAXIS1, NAXIS2, etc., before invoking
+c        astWrite
+f        AST_WRITE
+*        can help to produce a successful write.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+c     with the AST error status set, or if it should fail for any
+f     with STATUS set to an error value, or if it should fail for any
+*     reason.
+*     - Invoking this function will usually cause the sink function
+*     associated with the channel to be called in order to transfer a
+*     textual description of the supplied object to some external data
+*     store. However, the FitsChan class behaves differently. Invoking
+*     this function on a FitsChan causes new FITS header cards to be
+*     added to an internal buffer (the sink function is not invoked).
+*     This buffer is written out through the sink function only when the
+*     FitsChan is deleted.
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* The work of this function is actually performed by the protected
+   astDump method of the Object. The fact that this is further
+   encapsulated within the astWrite method (which belongs to the
+   Channel) is simply a trick to allow it to be over-ridden either by
+   a derived Channel, or a derived Object (or both), and hence to
+   adapt to the nature of either argument. */
+   astDump( object, this );
+
+/* Return the number of Objects written. */
+   return astOK ? 1 : 0;
+}
+
+static void WriteBegin( AstChannel *this, const char *class,
+                        const char *comment, int *status ) {
+/*
+*+
+*  Name:
+*     astWriteBegin
+
+*  Purpose:
+*     Write a "Begin" data item to a data sink.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astWriteBegin( AstChannel *this, const char *class,
+*                         const char *comment )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function writes a "Begin" data item to the data sink
+*     associated with a Channel, so as to begin the output of a new
+*     Object definition.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     class
+*        Pointer to a constant null-terminated string containing the
+*        name of the class to which the Object belongs.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the "Begin"
+*        item. Normally, this will describe the purpose of the Object.
+
+*  Notes:
+*     - The comment supplied may not actually be used, depending on
+*     the nature of the Channel supplied.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   char *line;                   /* Pointer to dynamic output string */
+   int i;                        /* Loop counter for indentation characters */
+   int nc;                       /* Number of output characters */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Start building a dynamic string with an initial space. Then add
+   further spaces to suit the current indentation level. */
+   line = astAppendString( NULL, &nc, " " );
+   for ( i = 0; i < current_indent; i++ ) {
+      line = astAppendString( line, &nc, " " );
+   }
+
+/* Append the "Begin" keyword followed by the class name. */
+   line = astAppendString( line, &nc, "Begin " );
+   line = astAppendString( line, &nc, class );
+
+/* If required, also append the comment. */
+   if ( astGetComment( this ) && *comment ) {
+      line = astAppendString( line, &nc, " \t# " );
+      line = astAppendString( line, &nc, comment );
+   }
+
+/* Write out the resulting line of text. */
+   OutputTextItem( this, line, status );
+
+/* Free the dynamic string. */
+   line = astFree( line );
+
+/* Increment the indentation level and clear the count of items written
+   for this Object. */
+   current_indent += astGetIndent( this );
+   items_written = 0;
+}
+
+static void WriteDouble( AstChannel *this, const char *name,
+                         int set, int helpful,
+                         double value, const char *comment, int *status ) {
+/*
+*+
+*  Name:
+*     astWriteDouble
+
+*  Purpose:
+*     Write a double value to a data sink.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astWriteDouble( AstChannel *this, const char *name,
+*                          int set, int helpful,
+*                          double value, const char *comment )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function writes a named double value, representing the
+*     value of a class instance variable, to the data sink associated
+*     with a Channel. It is intended for use by class "Dump" functions
+*     when writing out class information which will subsequently be
+*     re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        Channel's Full attribute is set - either to permit all values
+*        to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        The value to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the Channel supplied and the setting of its
+*        Comment attribute.
+*-
+*/
+
+/* Local Constants: */
+#define BUFF_LEN 100             /* Size of local formatting buffer */
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   char *line;                   /* Pointer to dynamic output string */
+   char buff[ BUFF_LEN + 1 ];    /* Local formatting buffer */
+   int i;                        /* Loop counter for indentation characters */
+   int nc;                       /* Number of output characters */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Use the "set" and "helpful" flags, along with the Channel's
+   attributes to decide whether this value should actually be
+   written. */
+   if ( Use( this, set, helpful, status ) ) {
+
+/* Start building a dynamic string with an initial space, or a comment
+   character if "set" is zero. Then add further spaces to suit the
+   current indentation level. */
+      line = astAppendString( NULL, &nc, set ? " " : "#" );
+      for ( i = 0; i < current_indent; i++ ) {
+         line = astAppendString( line, &nc, " " );
+      }
+
+/* Append the name string followed by " = ". */
+      line = astAppendString( line, &nc, name );
+      line = astAppendString( line, &nc, " = " );
+
+/* Format the value as a string and append this. Make sure "-0" isn't
+   produced. Use a magic string to represent bad values. */
+      if( value != AST__BAD ) {
+         (void) sprintf( buff, "%.*g", DBL_DIG, value );
+         if ( !strcmp( buff, "-0" ) ) {
+            buff[ 0 ] = '0';
+            buff[ 1 ] = '\0';
+         }
+      } else {
+         strcpy( buff, BAD_STRING );
+      }
+      line = astAppendString( line, &nc, buff );
+
+/* If required, also append the comment. */
+      if ( astGetComment( this ) && *comment ) {
+         line = astAppendString( line, &nc, " \t# " );
+         line = astAppendString( line, &nc, comment );
+      }
+
+/* Write out the resulting line of text. */
+      OutputTextItem( this, line, status );
+
+/* Free the dynamic string. */
+      line = astFree( line );
+   }
+
+/* Undefine macros local to this function. */
+#undef BUFF_LEN
+}
+
+static void WriteEnd( AstChannel *this, const char *class, int *status ) {
+/*
+*+
+*  Name:
+*     astWriteEnd
+
+*  Purpose:
+*     Write an "End" data item to a data sink.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astWriteEnd( AstChannel *this, const char *class )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function writes an "End" data item to the data sink
+*     associated with a Channel. This item delimits the end of an
+*     Object definition.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     class
+*        Pointer to a constant null-terminated string containing the
+*        class name of the Object whose definition is being terminated
+*        by the "End" item.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   char *line;                   /* Pointer to dynamic output string */
+   int i;                        /* Loop counter for indentation characters */
+   int nc;                       /* Number of output characters */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Decrement the indentation level so that the "End" item matches the
+   corresponding "Begin" item. */
+   current_indent -= astGetIndent( this );
+
+/* Start building a dynamic string with an initial space. Then add
+   further spaces to suit the current indentation level. */
+   line = astAppendString( NULL, &nc, " " );
+   for ( i = 0; i < current_indent; i++ ) {
+      line = astAppendString( line, &nc, " " );
+   }
+
+/* Append the "End" keyword followed by the class name. */
+   line = astAppendString( line, &nc, "End " );
+   line = astAppendString( line, &nc, class );
+
+/* Write out the resulting line of text. */
+   OutputTextItem( this, line, status );
+
+/* Free the dynamic string. */
+   line = astFree( line );
+}
+
+static void WriteInt( AstChannel *this, const char *name, int set, int helpful,
+                      int value, const char *comment, int *status ) {
+/*
+*+
+*  Name:
+*     astWriteInt
+
+*  Purpose:
+*     Write an integer value to a data sink.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astWriteInt( AstChannel *this, const char *name,
+*                       int set, int helpful,
+*                       int value, const char *comment )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function writes a named integer value, representing the
+*     value of a class instance variable, to the data sink associated
+*     with a Channel. It is intended for use by class "Dump" functions
+*     when writing out class information which will subsequently be
+*     re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        Channel's Full attribute is set - either to permit all values
+*        to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        The value to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the Channel supplied and the setting of its
+*        Comment attribute.
+*-
+*/
+
+/* Local Constants: */
+#define BUFF_LEN 50              /* Size of local formatting buffer */
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   char *line;                   /* Pointer to dynamic output string */
+   char buff[ BUFF_LEN + 1 ];    /* Local formatting buffer */
+   int i;                        /* Loop counter for indentation characters */
+   int nc;                       /* Number of output characters */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Use the "set" and "helpful" flags, along with the Channel's
+   attributes to decide whether this value should actually be
+   written. */
+   if ( Use( this, set, helpful, status ) ) {
+
+/* Start building a dynamic string with an initial space, or a comment
+   character if "set" is zero. Then add further spaces to suit the
+   current indentation level. */
+      line = astAppendString( NULL, &nc, set ? " " : "#" );
+      for ( i = 0; i < current_indent; i++ ) {
+         line = astAppendString( line, &nc, " " );
+      }
+
+/* Append the name string followed by " = ". */
+      line = astAppendString( line, &nc, name );
+      line = astAppendString( line, &nc, " = " );
+
+/* Format the value as a decimal string and append this. */
+      (void) sprintf( buff, "%d", value );
+      line = astAppendString( line, &nc, buff );
+
+/* If required, also append the comment. */
+      if ( astGetComment( this ) && *comment ) {
+         line = astAppendString( line, &nc, " \t# " );
+         line = astAppendString( line, &nc, comment );
+      }
+
+/* Write out the resulting line of text. */
+      OutputTextItem( this, line, status );
+
+/* Free the dynamic string. */
+      line = astFree( line );
+   }
+
+/* Undefine macros local to this function. */
+#undef BUFF_LEN
+}
+
+int astWriteInvocations_( int *status ){
+/*
+*+
+*  Name:
+*     astWriteInvocations
+
+*  Purpose:
+*     Returns the number of invocations of the astWrite method.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     int astWriteInvocations
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function returns the number of invocations of astWrite which
+*     have been made so far, excluding those made from within the
+*     astWriteObject method. An example of its use is to allow a Dump
+*     function to determine if a sub-object has already been dumped
+*     during the current invocation of astWrite. See the Dump method for
+*     the AstUnit class as an example.
+*-
+*/
+   astDECLARE_GLOBALS
+   astGET_GLOBALS(NULL);
+   return nwrite_invoc;
+}
+
+static void WriteIsA( AstChannel *this, const char *class,
+                      const char *comment, int *status ) {
+/*
+*+
+*  Name:
+*     astWriteIsA
+
+*  Purpose:
+*     Write an "IsA" data item to a data sink.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astWriteIsA( AstChannel *this, const char *class,
+*                       const char *comment )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function writes an "IsA" data item to the data sink
+*     associated with a Channel. This item delimits the end of the
+*     data associated with the instance variables of a class, as part
+*     of an overall Object definition.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     class
+*        Pointer to a constant null-terminated string containing the
+*        name of the class whose data are terminated by the "IsA"
+*        item.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the "IsA"
+*        item. Normally, this will describe the purpose of the class
+*        whose data are being terminated.
+
+*  Notes:
+*     - The comment supplied may not actually be used, depending on
+*     the nature of the Channel supplied.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   char *line;                   /* Pointer to dynamic output string */
+   int i;                        /* Loop counter for indentation characters */
+   int indent_inc;               /* Indentation increment */
+   int nc;                       /* Number of output characters */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Output an "IsA" item only if there has been at least one item
+   written since the last "Begin" or "IsA" item, or if the Full
+   attribute for the Channel is greater than zero (requesting maximum
+   information). */
+   if ( items_written || astGetFull( this ) > 0 ) {
+
+/* Start building a dynamic string with an initial space. Then add
+   further spaces to suit the current indentation level, but reduced
+   by one to allow the "IsA" item to match the "Begin" and "End" items
+   which enclose it. */
+      indent_inc = astGetIndent( this );
+      line = astAppendString( NULL, &nc, " " );
+      for ( i = 0; i < ( current_indent - indent_inc ); i++ ) {
+         line = astAppendString( line, &nc, " " );
+      }
+
+/* Append the "IsA" keyword followed by the class name. */
+      line = astAppendString( line, &nc, "IsA " );
+      line = astAppendString( line, &nc, class );
+
+/* If required, also append the comment. */
+      if ( astGetComment( this ) && *comment ) {
+         line = astAppendString( line, &nc, " \t# " );
+         line = astAppendString( line, &nc, comment );
+      }
+
+/* Write out the resulting line of text. */
+      OutputTextItem( this, line, status );
+
+/* Free the dynamic string. */
+      line = astFree( line );
+
+/* Clear the count of items written for this class. */
+      items_written = 0;
+   }
+}
+
+static void WriteObject( AstChannel *this, const char *name,
+                         int set, int helpful,
+                         AstObject *value, const char *comment, int *status ) {
+/*
+*+
+*  Name:
+*     astWriteObject
+
+*  Purpose:
+*     Write an Object as a value to a data sink.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astWriteObject( AstChannel *this, const char *name,
+*                          int set, int helpful,
+*                          AstObject *value, const char *comment )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function writes an Object as a named value, representing
+*     the value of a class instance variable, to the data sink
+*     associated with a Channel. It is intended for use by class
+*     "Dump" functions when writing out class information which will
+*     subsequently be re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        Channel's Full attribute is set - either to permit all values
+*        to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        A Pointer to the Object to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the Channel supplied and the setting of its
+*        Comment attribute.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   char *line;                   /* Pointer to dynamic output string */
+   int i;                        /* Loop counter for indentation characters */
+   int indent_inc;               /* Indentation increment */
+   int nc;                       /* Number of output characters */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Use the "set" and "helpful" flags, along with the Channel's
+   attributes to decide whether this value should actually be
+   written. */
+   if ( Use( this, set, helpful, status ) ) {
+
+/* Start building a dynamic string with an initial space, or a comment
+   character if "set" is zero. Then add further spaces to suit the
+   current indentation level. */
+      line = astAppendString( NULL, &nc, set ? " " : "#" );
+      for ( i = 0; i < current_indent; i++ ) {
+         line = astAppendString( line, &nc, " " );
+      }
+
+/* Append the name string followed by " =". The absence of a value on
+   the right hand side indicates an Object value, whose definition
+   follows. */
+      line = astAppendString( line, &nc, name );
+      line = astAppendString( line, &nc, " =" );
+
+/* If required, also append the comment. */
+      if ( astGetComment( this ) && *comment ) {
+         line = astAppendString( line, &nc, " \t# " );
+         line = astAppendString( line, &nc, comment );
+      }
+
+/* Write out the resulting line of text. */
+      OutputTextItem( this, line, status );
+
+/* Free the dynamic string. */
+      line = astFree( line );
+
+/* If the value is not a default, write the Object to the Channel as
+   well, suitably indented (this is omitted if the value is commented
+   out). */
+      if ( set ) {
+         indent_inc = astGetIndent( this );
+         current_indent += indent_inc;
+         (void) astWrite( this, value );
+         current_indent -= indent_inc;
+      }
+   }
+}
+
+static void WriteString( AstChannel *this, const char *name,
+                         int set, int helpful,
+                         const char *value, const char *comment, int *status ) {
+/*
+*+
+*  Name:
+*     astWriteString
+
+*  Purpose:
+*     Write a string value to a data sink.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void astWriteString( AstChannel *this, const char *name,
+*                          int set, int helpful,
+*                          const char *value, const char *comment )
+
+*  Class Membership:
+*     Channel method.
+
+*  Description:
+*     This function writes a named string value, representing the
+*     value of a class instance variable, to the data sink associated
+*     with a Channel. It is intended for use by class "Dump" functions
+*     when writing out class information which will subsequently be
+*     re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        Channel's Full attribute is set - either to permit all values
+*        to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        Pointer to a constant null-terminated string containing the
+*        value to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the Channel supplied and the setting of its
+*        Comment attribute.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   char *line;                   /* Pointer to dynamic output string */
+   int i;                        /* Loop counter for characters */
+   int nc;                       /* Number of output characters */
+   int quote;                    /* Quote character found? */
+   int size;                     /* Size of allocated memory */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Use the "set" and "helpful" flags, along with the Channel's
+   attributes to decide whether this value should actually be
+   written. */
+   if ( Use( this, set, helpful, status ) ) {
+
+/* Start building a dynamic string with an initial space, or a comment
+   character if "set" is zero. Then add further spaces to suit the
+   current indentation level. */
+      line = astAppendString( NULL, &nc, set ? " " : "#" );
+      for ( i = 0; i < current_indent; i++ ) {
+         line = astAppendString( line, &nc, " " );
+      }
+
+/* Append the name string followed by " = " and an opening quote
+   character (the string will be quoted to protect leading and
+   trailing spaces). */
+      line = astAppendString( line, &nc, name );
+      line = astAppendString( line, &nc, " = \"" );
+
+/* We now append the value string, but must inspect each character so
+   that quotes (appearing inside quotes) can be doubled. Determine the
+   current size of memory allocated for the dynamic string. */
+      size = (int) astSizeOf( line );
+
+/* Loop to inspect each character and see if it is a quote. */
+      for ( i = 0; value[ i ]; i++ ) {
+         quote = ( value[ i ] == '"' );
+
+/* If more memory is required, extend the dynamic string (allowing for
+   doubling of quotes and the final null) and save its new size. */
+         if ( nc + 2 + quote > size ) {
+            line = astGrow( line, nc + 2 + quote, sizeof( char ) );
+            if ( astOK ) {
+               size = (int) astSizeOf( line );
+
+/* Quit if an error occurs. */
+            } else {
+               break;
+            }
+         }
+
+/* Append the value character to the dynamic string, duplicating each
+   quote character. */
+         line[ nc++ ] = value[ i ];
+         if ( quote ) line[ nc++ ] = '"';
+      }
+
+/* Append the closing quote. */
+      line = astAppendString( line, &nc, "\"" );
+
+/* If required, also append the comment. */
+      if ( astGetComment( this ) && *comment ) {
+         line = astAppendString( line, &nc, " \t# " );
+         line = astAppendString( line, &nc, comment );
+      }
+
+/* Write out the resulting line of text. */
+      OutputTextItem( this, line, status );
+
+/* Free the dynamic string. */
+      line = astFree( line );
+   }
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. */
+
+/*
+*att++
+*  Name:
+*     SourceFile
+
+*  Purpose:
+*     Input file from which to read data.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies the name of a file from which the Channel
+*     should read data. If specified it is used in preference to any source
+*     function specified when the Channel was created.
+*
+*     Assigning a new value to this attribute will cause any previously
+*     opened SourceFile to be closed. The first subsequent call to
+c     astRead
+f     AST_READ
+*     will attempt to open the new file (an error will be reported if the
+*     file cannot be opened), and read data from it. All subsequent call to
+c     astRead
+f     AST_READ
+*     will read data from the new file, until the SourceFile attribute is
+*     cleared or changed.
+*
+*     Clearing the attribute causes any open SourceFile to be closed. All
+*     subsequent data reads will use the source function specified when the
+*     Channel was created, or will read from standard input if no source
+*     function was specified.
+*
+*     If no value has been assigned to SourceFile, a null string will be
+*     returned if an attempt is made to get the attribute value.
+
+*  Notes:
+*     - Any open SourceFile is closed when the Channel is deleted.
+*     - If the Channel is copied or dumped
+c     (using astCopy or astShow)
+f     (using AST_COPY or AST_SHOW)
+*     the SourceFile attribute is left in a cleared state in the output
+*     Channel (i.e. the value of the SourceFile attribute is not copied).
+
+*  Applicability:
+*     FitsChan
+*        In the case of a FitsChan, the specified SourceFile supplements
+*        the source function specified when the FitsChan was created,
+*        rather than replacing the source function. The source file
+*        should be a text file (not a FITS file) containing one header per
+*        line. When a value is assigned to SourceFile, the file is opened
+*        and read immediately, and all headers read from the file are
+*        appended to the end of any header already in the FitsChan. The file
+*        is then closed. Clearing the SourceFile attribute has no further
+*        effect, other than nullifying the string (i.e. the file name)
+*        associated with the attribute.
+
+*att--
+*/
+
+/* Clear the SourceFile value by closing any open file, freeing the
+   allocated memory and assigning a NULL pointer. */
+astMAKE_CLEAR(Channel,SourceFile,fn_in,((this->fd_in=(this->fd_in?(fclose(this->fd_in),NULL):NULL)),astFree(this->fn_in)))
+
+/* If the SourceFile value is not set, supply a default in the form of a
+   pointer to the constant string "". */
+astMAKE_GET(Channel,SourceFile,const char *,NULL,( this->fn_in ? this->fn_in : "" ))
+
+/* Set a SourceFile value by closing any open file, freeing any previously
+   allocated memory, allocating new memory, storing the string and saving
+   the pointer to the copy. */
+astMAKE_SET(Channel,SourceFile,const char *,fn_in,((this->fd_in=(this->fd_in?(fclose(this->fd_in),NULL):NULL)),astStore( this->fn_in, value, strlen( value ) + (size_t) 1 )))
+
+/* The SourceFile value is set if the pointer to it is not NULL. */
+astMAKE_TEST(Channel,SourceFile,( this->fn_in != NULL ))
+
+/*
+*att++
+*  Name:
+*     SinkFile
+
+*  Purpose:
+*     Output file to which to data should be written.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies the name of a file to which the Channel
+*     should write data. If specified it is used in preference to any sink
+*     function specified when the Channel was created.
+*
+*     Assigning a new value to this attribute will cause any previously
+*     opened SinkFile to be closed. The first subsequent call to
+c     astWrite
+f     AST_WRITE
+*     will attempt to open the new file (an error will be reported if the
+*     file cannot be opened), and write data to it. All subsequent call to
+c     astWrite
+f     AST_WRITE
+*     will write data to the new file, until the SinkFile attribute is
+*     cleared or changed.
+*
+*     Clearing the attribute causes any open SinkFile to be closed. All
+*     subsequent data writes will use the sink function specified when the
+*     Channel was created, or will write to standard output if no sink
+*     function was specified.
+*
+*     If no value has been assigned to SinkFile, a null string will be
+*     returned if an attempt is made to get the attribute value.
+
+*  Notes:
+*     - A new SinkFile will over-write any existing file with the same
+*     name unless the existing file is write protected, in which case an
+*     error will be reported.
+*     - Any open SinkFile is closed when the Channel is deleted.
+*     - If the Channel is copied or dumped
+c     (using astCopy or astShow)
+f     (using AST_COPY or AST_SHOW)
+*     the SinkFile attribute is left in a cleared state in the output
+*     Channel (i.e. the value of the SinkFile attribute is not copied).
+
+*  Applicability:
+*     FitsChan
+*        When the FitsChan is destroyed, any headers in the FitsChan will be
+*        written out to the sink file, if one is specified (if not, the
+*        sink function used when the FitsChan was created is used). The
+*        sink file is a text file (not a FITS file) containing one header
+*        per line.
+
+*att--
+*/
+
+/* Clear the SinkFile value by closing any open file, freeing the allocated
+   memory and assigning a NULL pointer. */
+astMAKE_CLEAR(Channel,SinkFile,fn_out,((this->fd_out=(this->fd_out?(fclose(this->fd_out),NULL):NULL)),astFree(this->fn_out)))
+
+/* If the SinkFile value is not set, supply a default in the form of a
+   pointer to the constant string "". */
+astMAKE_GET(Channel,SinkFile,const char *,NULL,( this->fn_out ? this->fn_out : "" ))
+
+/* Set a SinkFile value by closing any open file, freeing any previously
+   allocated memory, allocating new memory, storing the string and saving
+   the pointer to the copy. */
+astMAKE_SET(Channel,SinkFile,const char *,fn_out,((this->fd_out=(this->fd_out?(fclose(this->fd_out),NULL):NULL)),astStore( this->fn_out, value, strlen( value ) + (size_t) 1 )))
+
+/* The SinkFile value is set if the pointer to it is not NULL. */
+astMAKE_TEST(Channel,SinkFile,( this->fn_out != NULL ))
+
+
+/*
+*att++
+*  Name:
+*     Comment
+
+*  Purpose:
+*     Include textual comments in output?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This is a boolean attribute which controls whether textual
+*     comments are to be included in the output generated by a
+*     Channel. If included, they will describe what each item of
+*     output represents.
+*
+*     If Comment is non-zero, then comments will be included. If
+*     it is zero, comments will be omitted.
+
+*  Applicability:
+*     Channel
+*        The default value is non-zero for a normal Channel.
+*     FitsChan
+*        The default value is non-zero for a FitsChan.
+*     XmlChan
+*        The default value is zero for an XmlChan.
+
+*att--
+*/
+
+/* This is a boolean value (0 or 1) with a value of -INT_MAX when
+   undefined but yielding a default of one. */
+astMAKE_CLEAR(Channel,Comment,comment,-INT_MAX)
+astMAKE_GET(Channel,Comment,int,0,( this->comment != -INT_MAX ? this->comment : 1 ))
+astMAKE_SET(Channel,Comment,int,comment,( value != 0 ))
+astMAKE_TEST(Channel,Comment,( this->comment != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     Full
+
+*  Purpose:
+*     Set level of output detail.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute is a three-state flag and takes values of -1, 0
+*     or +1.  It controls the amount of information included in the
+*     output generated by a Channel.
+*
+*     If Full is zero, then a modest amount of
+*     non-essential but useful information will be included in the
+*     output. If Full is negative, all non-essential information will
+*     be suppressed to minimise the amount of output, while if it is
+*     positive, the output will include the maximum amount of detailed
+*     information about the Object being written.
+
+*  Applicability:
+*     Channel
+*        The default value is zero for a normal Channel.
+*     FitsChan
+*        The default value is zero for a FitsChan.
+*     XmlChan
+*        The default value is -1 for an XmlChan.
+*     StcsChan
+*        The default value is zero for an StcsChan. Set a positive value
+*        to cause default values to be included in STC-S descriptions.
+
+*  Notes:
+*     - All positive values supplied for this attribute are converted
+*     to +1 and all negative values are converted to -1.
+*att--
+*/
+
+/* This ia a 3-state value (-1, 0 or +1) with a value of -INT_MAX when
+   undefined but yielding a default of zero. */
+astMAKE_CLEAR(Channel,Full,full,-INT_MAX)
+astMAKE_GET(Channel,Full,int,0,( this->full != -INT_MAX ? this->full : 0 ))
+astMAKE_SET(Channel,Full,int,full,( value > 0 ? 1 : ( value < 0 ? -1 : 0 ) ))
+astMAKE_TEST(Channel,Full,( this->full != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     Indent
+
+*  Purpose:
+*     Specifies the indentation to use in text produced by a Channel.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the indentation within the output text produced by
+f     the AST_WRITE function.
+c     the astWrite function.
+*     It gives the increase in the indentation for each level in the object
+*     heirarchy. If it is set to zero, no indentation will be used. [3]
+
+*  Applicability:
+*     Channel
+*        The default value is zero for a basic Channel.
+*     FitsChan
+*        The FitsChan class ignores this attribute.
+*     StcsChan
+*        The default value for an StcsChan is zero, which causes the entire
+*        STC-S description is written out by a single invocation of the sink
+*        function. The text supplied to the sink function will not contain
+*        any linefeed characters, and each pair of adjacent words will be
+*        separated by a single space. The text may thus be arbitrarily large
+*        and the StcsLength attribute is ignored.
+*
+*        If Indent is non-zero, then the text is written out via multiple
+*        calls to the sink function, each call corresponding to a single
+*        "line" of text (although no line feed characters will be inserted
+*        by AST). The complete STC-S description is broken into lines so that:
+*
+*        - the line length specified by attribute StcsLength is not exceeded
+*        - each sub-phrase (time, space, etc.) starts on a new line
+*        - each argument in a compound spatial region starts on a new line
+*
+*        If this causes a sub-phrase to extend to two or more lines, then the
+*        second and subsequent lines will be indented by three spaces compared
+*        to the first line. In addition, lines within a compound spatial region
+*        will have extra indentation to highlight the nesting produced by the
+*        parentheses. Each new level of nesting will be indented by a further
+*        three spaces.
+f
+f        Note, the default value of zero is unlikely to be appropriate when
+f        an StcsChan is used within Fortran code. In this case, Indent
+f        should usually be set non-zero, and the StcsLength attribute set to
+f        the size of the CHARACTER variable used to
+f        receive the text returned by AST_GETLINE within the sink function.
+f        This avoids the possibility of long lines being truncated invisibly
+f        within AST_GETLINE.
+*     XmlChan
+*        The default value for an XmlChan is zero, which results in no
+*        linefeeds or indentation strings being added to output text.
+*        If any non-zero value is assigned to Indent, then extra linefeed and
+*        space characters will be inserted as necessary to ensure that each
+*        XML tag starts on a new line, and each tag will be indented by
+*        a further 3 spaces to show its depth in the containment hierarchy.
+*att--
+*/
+
+/* This is an integer value with a value of -INT_MAX when undefined,
+   yielding a default of 3. Sub-classes may over-ride theis default. */
+astMAKE_CLEAR(Channel,Indent,indent,-INT_MAX)
+astMAKE_GET(Channel,Indent,int,3,( this->indent != -INT_MAX ? this->indent : 3 ))
+astMAKE_SET(Channel,Indent,int,indent,value)
+astMAKE_TEST(Channel,Indent,( this->indent != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     ReportLevel
+
+*  Purpose:
+*     Determines which read/write conditions are reported.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute determines which, if any, of the conditions that occur
+*     whilst reading or writing an Object should be reported. These
+*     conditions will generate either a fatal error or a warning, as
+*     determined by attribute Strict. ReportLevel can take any of the
+*     following values:
+*
+*     0 - Do not report any conditions.
+*
+*     1 - Report only conditions where significant information content has been
+*     changed. For instance, an unsupported time-scale has been replaced by a
+*     supported near-equivalent time-scale. Another example is if a basic
+*     Channel unexpected encounters data items that may have been introduced
+*     by later versions of AST.
+*
+*     2 - Report the above, and in addition report significant default
+*     values. For instance, if no time-scale was specified when reading an
+*     Object from an external data source, report the default time-scale
+*     that is being used.
+*
+*     3 - Report the above, and in addition report any other potentially
+*     interesting conditions that have no significant effect on the
+*     conversion. For instance, report if a time-scale of "TT"
+*     (terrestrial time) is used in place of "ET" (ephemeris time). This
+*     change has no signficiant effect because ET is the predecessor of,
+*     and is continuous with, TT. Synonyms such as "IAT" and "TAI" are
+*     another example.
+*
+*     The default value is 1. Note, there are many other conditions that
+*     can occur whilst reading or writing an Object that completely
+*     prevent the conversion taking place. Such conditions will always
+*     generate errors, irrespective of the ReportLevel and Strict attributes.
+
+*  Applicability:
+*     Channel
+*        All Channels have this attribute.
+*     FitsChan
+*        All the conditions selected by the FitsChan Warnings attribute are
+*        reported at level 1.
+*att--
+*/
+
+/* This is an integer value with a value of -INT_MAX when undefined,
+   yielding a default of one. */
+astMAKE_CLEAR(Channel,ReportLevel,report_level,-INT_MAX)
+astMAKE_GET(Channel,ReportLevel,int,1,( this->report_level != -INT_MAX ? this->report_level : 1 ))
+astMAKE_SET(Channel,ReportLevel,int,report_level,value)
+astMAKE_TEST(Channel,ReportLevel,( this->report_level != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     Skip
+
+*  Purpose:
+*     Skip irrelevant data?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This is a boolean attribute which indicates whether the Object
+*     data being read through a Channel are inter-mixed with other,
+*     irrelevant, external data.
+*
+*     If Skip is zero (the default), then the source of input data is
+*     expected to contain descriptions of AST Objects and comments and
+*     nothing else (if anything else is read, an error will
+*     result). If Skip is non-zero, then any non-Object data
+*     encountered between Objects will be ignored and simply skipped
+*     over in order to reach the next Object.
+
+*  Applicability:
+*     Channel
+*        All Channels have this attribute.
+*     FitsChan
+*        The FitsChan class sets the default value of this attribute
+*        to 1, so that all irrelevant FITS headers will normally be
+*        ignored.
+*att--
+*/
+
+/* This ia a boolean value (0 or 1) with a value of -INT_MAX when
+   undefined but yielding a default of zero. */
+astMAKE_CLEAR(Channel,Skip,skip,-INT_MAX)
+astMAKE_GET(Channel,Skip,int,0,( this->skip != -INT_MAX ? this->skip : 0 ))
+astMAKE_SET(Channel,Skip,int,skip,( value != 0 ))
+astMAKE_TEST(Channel,Skip,( this->skip != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     Strict
+
+*  Purpose:
+*     Report an error if any unexpeted data items are found?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This is a boolean attribute which indicates whether a warning
+*     rather than an error should be issed for insignificant conversion
+*     problems. If it is set non-zero, then fatal errors are issued
+*     instead of warnings, resulting in the
+c     AST error status being set.
+f     inherited STATUS variable being set to an error value.
+*     If Strict is zero (the default), then execution continues after minor
+*     conversion problems, and a warning message is added to the Channel
+*     structure. Such messages can be retrieved using the
+c     astWarnings
+f     AST_WARNINGS
+*     function.
+
+*  Notes:
+*     - This attribute was introduced in AST version 5.0. Prior to this
+*     version of AST unexpected data items read by a basic Channel always
+*     caused an error to be reported. So applications linked against
+*     versions of AST prior to version 5.0 may not be able to read Object
+*     descriptions created by later versions of AST, if the Object's class
+*     description has changed.
+
+*  Applicability:
+*     Channel
+*        All Channels have this attribute.
+*att--
+*/
+
+/* This ia a boolean value (0 or 1) with a value of -INT_MAX when
+   undefined but yielding a default of zero. */
+astMAKE_CLEAR(Channel,Strict,strict,-INT_MAX)
+astMAKE_GET(Channel,Strict,int,0,( this->strict != -INT_MAX ? this->strict : 0 ))
+astMAKE_SET(Channel,Strict,int,strict,( value != 0 ))
+astMAKE_TEST(Channel,Strict,( this->strict != -INT_MAX ))
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Channel objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Channel objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstChannel *this;               /* Pointer to Channel */
+
+/* Obtain a pointer to the Channel structure. */
+   this = (AstChannel *) obj;
+
+/* Free memory used to store warnings. */
+   astAddWarning( this, 0, NULL, NULL, status );
+
+/* Close any open input or output files. */
+   if( this->fd_in ) fclose( this->fd_in );
+   if( this->fd_out ) fclose( this->fd_out );
+
+/* Free file name memory. */
+   this->fn_in = astFree( this->fn_in );
+   this->fn_out = astFree( this->fn_out );
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Channel objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Channel objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstChannel *out;                /* Pointer to output Channel */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Channels. */
+   out = (AstChannel *) objout;
+
+/* Just clear any references to the input memory from the output Channel. */
+   out->warnings = NULL;
+   out->nwarn = 0;
+   out->fd_in = NULL;
+   out->fn_in = NULL;
+   out->fd_out = NULL;
+   out->fn_out = NULL;
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Channel objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Channel class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Object whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstChannel *this;             /* Pointer to the Channel structure */
+   const char *comment;          /* Pointer to comment string */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Channel structure. */
+   this = (AstChannel *) this_object;
+
+/* Write out values representing the instance variables for the
+   Channel class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Indent */
+/* ------------ */
+   set = TestIndent( this, status );
+   ival = set ? GetIndent( this, status ) : astGetIndent( this );
+   astWriteInt( channel, "Indnt", set, 0, ival, "Indentation increment" );
+
+/* ReportLevel. */
+/* ------------ */
+   set = TestReportLevel( this, status );
+   ival = set ? GetReportLevel( this, status ) : astGetReportLevel( this );
+   astWriteInt( channel, "RpLev", set, 0, ival, "Error reporting level" );
+
+/* Skip. */
+/* ----- */
+   set = TestSkip( this, status );
+   ival = set ? GetSkip( this, status ) : astGetSkip( this );
+   astWriteInt( channel, "Skip", set, 0, ival,
+                ival ? "Ignore data between Objects" :
+                       "No data allowed between Objects" );
+
+/* Strict. */
+/* ------- */
+   set = TestStrict( this, status );
+   ival = set ? GetStrict( this, status ) : astGetStrict( this );
+   astWriteInt( channel, "Strict", set, 0, ival,
+                ival ? "Report errors insead of warnings" :
+                       "Report warnings instead of errors" );
+
+/* Full. */
+/* ----- */
+   set = TestFull( this, status );
+   ival = set ? GetFull( this, status ) : astGetFull( this );
+   if ( ival < 0 ) {
+      comment = "Suppress non-essential output";
+   }else if ( ival == 0 ) {
+      comment = "Output standard information";
+   } else {
+      comment = "Output maximum information";
+   }
+   astWriteInt( channel, "Full", set, 0, ival, comment );
+
+/* Comment. */
+/* -------- */
+   set = TestComment( this, status );
+   ival = set ? GetComment( this, status ) : astGetComment( this );
+   astWriteInt( channel, "Comm", set, 0, ival,
+                ival ? "Display comments" :
+                       "Omit comments" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAChannel and astCheckChannel functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Channel,Object)
+astMAKE_CHECK(Channel)
+
+AstChannel *astChannel_( const char *(* source)( void ),
+                         void (* sink)( const char * ),
+                         const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astChannel
+
+*  Purpose:
+*     Create a Channel.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     AstChannel *astChannel( const char *(* source)( void ),
+*                             void (* sink)( const char * ),
+*                             const char *options, ..., int *status )
+
+*  Class Membership:
+*     Channel constructor.
+
+*  Description:
+*     This function creates a new Channel and optionally initialises
+*     its attributes.
+*
+*     A Channel implements low-level input/output for the AST library.
+*     Writing an Object to a Channel (using astWrite) will generate a
+*     textual representation of that Object, and reading from a
+*     Channel (using astRead) will create a new Object from its
+*     textual representation.
+*
+*     Normally, when you use a Channel, you should provide "source"
+*     and "sink" functions which connect it to an external data store
+*     by reading and writing the resulting text. By default, however,
+*     a Channel will read from standard input and write to standard
+*     output.
+
+*  Parameters:
+*     source
+*        Pointer to a "source" function that takes no arguments and
+*        returns a pointer to a null-terminated string.
+*
+*        This function will be used by the Channel to obtain lines of
+*        input text. On each invocation, it should return a pointer to
+*        the next input line read from some external data store, and a
+*        NULL pointer when there are no more lines to read.
+*
+*        If "source" is NULL, the Channel will read from standard
+*        input instead.
+*     sink
+*        Pointer to a "sink" function that takes a pointer to a
+*        null-terminated string as an argument and returns void.
+*
+*        This function will be used by the Channel to deliver lines of
+*        output text. On each invocation, it should deliver the
+*        contents of the string supplied to some external data store.
+*
+*        If "sink" is NULL, the Channel will write to standard output
+*        instead.
+*     options
+*        Pointer to a null-terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new Channel. The syntax used is identical to
+*        that for the astSet function and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of additional arguments may follow it in
+*        order to supply values to be substituted for these
+*        specifiers. The rules for supplying these are identical to
+*        those for the astSet function (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     astChannel()
+*        A pointer to the new Channel.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic Channel constructor which
+*     is available via the protected interface to the Channel class.
+*     A public interface is provided by the astChannelId_ function.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstChannel *new;              /* Pointer to new Channel */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the Channel, allocating memory and initialising the
+   virtual function table as well if necessary. Supply pointers to
+   (local) wrapper functions that can invoke the source and sink
+   functions with appropriate arguments for the C language. */
+   new = astInitChannel( NULL, sizeof( AstChannel ), !class_init, &class_vtab,
+                         "Channel", source, SourceWrap, sink, SinkWrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   Channel's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Channel. */
+   return new;
+}
+
+AstChannel *astChannelId_( const char *(* source)( void ),
+                           void (* sink)( const char * ),
+                           const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astChannel
+f     AST_CHANNEL
+
+*  Purpose:
+*     Create a Channel.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "channel.h"
+c     AstChannel *astChannel( const char *(* source)( void ),
+c                             void (* sink)( const char * ),
+c                             const char *options, ... )
+f     RESULT = AST_CHANNEL( SOURCE, SINK, OPTIONS, STATUS )
+
+*  Class Membership:
+*     Channel constructor.
+
+*  Description:
+*     This function creates a new Channel and optionally initialises
+*     its attributes.
+*
+*     A Channel implements low-level input/output for the AST library.
+c     Writing an Object to a Channel (using astWrite) will generate a
+f     Writing an Object to a Channel (using AST_WRITE) will generate a
+*     textual representation of that Object, and reading from a
+c     Channel (using astRead) will create a new Object from its
+f     Channel (using AST_READ) will create a new Object from its
+*     textual representation.
+*
+*     Normally, when you use a Channel, you should provide "source"
+c     and "sink" functions which connect it to an external data store
+f     and "sink" routines which connect it to an external data store
+*     by reading and writing the resulting text. By default, however,
+*     a Channel will read from standard input and write to standard
+*     output. Alternatively, a Channel can be told to read or write from
+*     specific text files using the SinkFile and SourceFile attributes,
+*     in which case no sink or source function need be supplied.
+
+*  Parameters:
+c     source
+f     SOURCE = SUBROUTINE (Given)
+c        Pointer to a source function that takes no arguments and
+c        returns a pointer to a null-terminated string.  If no value
+c        has been set for the SourceFile attribute, this function
+c        will be used by the Channel to obtain lines of input text. On
+c        each invocation, it should return a pointer to the next input
+c        line read from some external data store, and a NULL pointer
+c        when there are no more lines to read.
+c
+c        If "source" is NULL and no value has been set for the SourceFile
+c        attribute, the Channel will read from standard input instead.
+f        A source routine, which is a subroutine which takes a single
+f        integer error status argument.   If no value has been set
+f        for the SourceFile attribute, this routine will be used by
+f        the Channel to obtain lines of input text. On each
+f        invocation, it should read the next input line from some
+f        external data store, and then return the resulting text to
+f        the AST library by calling AST_PUTLINE. It should supply a
+f        negative line length when there are no more lines to read.
+f        If an error occurs, it should set its own error status
+f        argument to an error value before returning.
+f
+f        If the null routine AST_NULL is suppied as the SOURCE value,
+f        and no value has been set for the SourceFile attribute,
+f        the Channel will read from standard input instead.
+c     sink
+f     SINK = SUBROUTINE (Given)
+c        Pointer to a sink function that takes a pointer to a
+c        null-terminated string as an argument and returns void.
+c        If no value has been set for the SinkFile attribute, this
+c        function will be used by the Channel to deliver lines of
+c        output text. On each invocation, it should deliver the
+c        contents of the string supplied to some external data store.
+c
+c        If "sink" is NULL, and no value has been set for the SinkFile
+c        attribute, the Channel will write to standard output instead.
+f        A sink routine, which is a subroutine which takes a single
+f        integer error status argument.  If no value has been set
+f        for the SinkFile attribute, this routine will be used by
+f        the Channel to deliver lines of output text. On each
+f        invocation, it should obtain the next output line from the
+f        AST library by calling AST_GETLINE, and then deliver the
+f        resulting text to some external data store.  If an error
+f        occurs, it should set its own error status argument to an
+f        error value before returning.
+f
+f        If the null routine AST_NULL is suppied as the SINK value,
+f        and no value has been set for the SinkFile attribute,
+f        the Channel will write to standard output instead.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Channel. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Channel. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astChannel()
+f     AST_CHANNEL = INTEGER
+*        A pointer to the new Channel.
+
+*  Notes:
+c     - Application code can pass arbitrary data (such as file
+c     descriptors, etc) to source and sink functions using the
+c     astPutChannelData function. The source or sink function should use
+c     the astChannelData macro to retrieve this data.
+f     - The names of the routines supplied for the SOURCE and SINK
+f     arguments should appear in EXTERNAL statements in the Fortran
+f     routine which invokes AST_CHANNEL. However, this is not generally
+f     necessary for the null routine AST_NULL (so long as the AST_PAR
+f     include file has been used).
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+f     - Note that the null routine AST_NULL (one underscore) is
+f     different to AST__NULL (two underscores), which is the null Object
+f     pointer.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astChannel constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astChannel_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - The variable argument list also prevents this function from
+*     invoking astChanel_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstChannel *new;              /* Pointer to new Channel */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the Channel, allocating memory and initialising the
+   virtual function table as well if necessary. Supply pointers to
+   (local) wrapper functions that can invoke the source and sink
+   functions with appropriate arguments for the C language. */
+   new = astInitChannel( NULL, sizeof( AstChannel ), !class_init, &class_vtab,
+                         "Channel", source, SourceWrap, sink, SinkWrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   Channel's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new Channel. */
+   return astMakeId( new );
+}
+
+AstChannel *astChannelForId_( const char *(* source)( void ),
+                              char *(* source_wrap)( const char *(*)( void ), int * ),
+                              void (* sink)( const char * ),
+                              void (* sink_wrap)( void (*)( const char * ),
+                                                  const char *, int * ),
+                              const char *options, ... ) {
+/*
+*+
+*  Name:
+*     astChannelFor
+
+*  Purpose:
+*     Initialise a Channel from a foreign language interface.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     AstChannel *astChannelFor( const char *(* source)( void ),
+*                                char *(* source_wrap)( const char *(*)
+*                                                       ( void ), int * ),
+*                                void (* sink)( const char * ),
+*                                void (* sink_wrap)( void (*)( const char * ),
+*                                                    const char *, int * ),
+*                                const char *options, ... )
+
+*  Class Membership:
+*     Channel constructor.
+
+*  Description:
+*     This function creates a new Channel from a foreign language
+*     interface and optionally initialises its attributes.
+*
+*     A Channel implements low-level input/output for the AST library.
+*     Writing an Object to a Channel (using astWrite) will generate a
+*     textual representation of that Object, and reading from a
+*     Channel (using astRead) will create a new Object from its
+*     textual representation.
+*
+*     Normally, when you use a Channel, you should provide "source"
+*     and "sink" functions which connect it to an external data store
+*     by reading and writing the resulting text. This function also
+*     requires you to provide "wrapper" functions which will invoke
+*     the source and sink functions. By default, however, a Channel
+*     will read from standard input and write to standard output.
+
+*  Parameters:
+*     source
+*        Pointer to a "source" function which will be used to obtain
+*        lines of input text. Generally, this will be obtained by
+*        casting a pointer to a source function which is compatible
+*        with the "source_wrap" wrapper function (below). The pointer
+*        should later be cast back to its original type by the
+*        "source_wrap" function before the function is invoked.
+*
+*        If "source" is NULL, the Channel will read from standard
+*        input instead.
+*     source_wrap
+*        Pointer to a function which can be used to invoke the
+*        "source" function supplied (above). This wrapper function is
+*        necessary in order to hide variations in the nature of the
+*        source function, such as may arise when it is supplied by a
+*        foreign (non-C) language interface.
+*
+*        The single parameter of the "source_wrap" function is a
+*        pointer to the "source" function, and it should cast this
+*        function pointer (as necessary) and invoke the function with
+*        appropriate arguments to obtain the next line of input
+*        text. The "source_wrap" function should then return a pointer
+*        to a dynamically allocated, null terminated string containing
+*        the text that was read. The string will be freed (using
+*        astFree) when no longer required and the "source_wrap"
+*        function need not concern itself with this. A NULL pointer
+*        should be returned if there is no more input to read.
+*
+*        If "source_wrap" is NULL, the Channel will read from standard
+*        input instead.
+*     sink
+*        Pointer to a "sink" function which will be used to deliver
+*        lines of output text. Generally, this will be obtained by
+*        casting a pointer to a sink function which is compatible with
+*        the "sink_wrap" wrapper function (below). The pointer should
+*        later be cast back to its original type by the "sink_wrap"
+*        function before the function is invoked.
+*
+*        If "sink" is NULL, the Channel will write to standard output
+*        instead.
+*     sink_wrap
+*        Pointer to a function which can be used to invoke the "sink"
+*        function supplied (above). This wrapper function is necessary
+*        in order to hide variations in the nature of the sink
+*        function, such as may arise when it is supplied by a foreign
+*        (non-C) language interface.
+*
+*        The first parameter of the "sink_wrap" function is a pointer
+*        to the "sink" function, and the second parameter is a pointer
+*        to a const, null-terminated character string containing the
+*        text to be written.  The "sink_wrap" function should cast the
+*        "sink" function pointer (as necessary) and invoke the
+*        function with appropriate arguments to deliver the line of
+*        output text. The "sink_wrap" function then returns void.
+*
+*        If "sink_wrap" is NULL, the Channel will write to standard
+*        output instead.
+*     options
+*        Pointer to a null-terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new Channel. The syntax used is identical to
+*        that for the astSet function and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of additional arguments may follow it in
+*        order to supply values to be substituted for these
+*        specifiers. The rules for supplying these are identical to
+*        those for the astSet function (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     astChannelFor()
+*        A pointer to the new Channel.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*     - This function is only available through the public interface
+*     to the Channel class (not the protected interface) and is
+*     intended solely for use in implementing foreign language
+*     interfaces to this class.
+*-
+
+*  Implememtation Notes:
+*     - This function behaves exactly like astChannelId_, in that it
+*     returns ID values and not true C pointers, but it has two
+*     additional arguments. These are pointers to the "wrapper
+*     functions" which are needed to accommodate foreign language
+*     interfaces.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstChannel *new;              /* Pointer to new Channel */
+   va_list args;                 /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise the Channel, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitChannel( NULL, sizeof( AstChannel ), !class_init, &class_vtab,
+                         "Channel", source, source_wrap, sink, sink_wrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   Channel's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new Channel. */
+   return astMakeId( new );
+}
+
+AstChannel *astLoadChannel_( void *mem, size_t size,
+                             AstChannelVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadChannel
+
+*  Purpose:
+*     Load a Channel.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     AstChannel *astLoadChannel( void *mem, size_t size,
+*                                 AstChannelVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     Channel loader.
+
+*  Description:
+*     This function is provided to load a new Channel using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Channel structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Channel at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Channel is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Channel data (sizeof(Channel)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Channel (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Channel structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstChannel) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Channel. If this is NULL, a pointer
+*        to the (static) virtual function table for the Channel class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Channel" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Channel.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstChannel *new;              /* Pointer to the new Channel */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Channel. In this case the
+   Channel belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstChannel );
+      vtab = &class_vtab;
+      name = "Channel";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitChannelVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Channel. */
+   new = astLoadObject( mem, size, (AstObjectVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Channel" );
+
+/* Set the pointers to the source and sink functions, and their
+   wrapper functions, to NULL (we cannot restore these since they
+   refer to process-specific addresses). */
+      new->source = NULL;
+      new->source_wrap = NULL;
+      new->sink = NULL;
+      new->sink_wrap = NULL;
+
+/* We do not have any data to pass to the source and sink functions. */
+      new->data = NULL;
+
+/* No warnings yet. */
+      new->warnings = NULL;
+      new->nwarn = 0;
+
+/* Indicate no input or output files have been associated with the
+   Channel. */
+      new->fd_in = NULL;
+      new->fn_in = NULL;
+      new->fd_out = NULL;
+      new->fn_out = NULL;
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Indent. */
+/* ------- */
+      new->indent = astReadInt( channel, "indnt", -INT_MAX );
+      if ( TestIndent( new, status ) ) SetIndent( new, new->indent, status );
+
+/* ReportLevel. */
+/* ------------ */
+      new->report_level = astReadInt( channel, "rplev", -INT_MAX );
+      if ( TestReportLevel( new, status ) ) SetReportLevel( new,
+                                                            new->report_level,
+                                                            status );
+
+/* Skip. */
+/* ----- */
+      new->skip = astReadInt( channel, "skip", -INT_MAX );
+      if ( TestSkip( new, status ) ) SetSkip( new, new->skip, status );
+
+/* Strict. */
+/* ------- */
+      new->strict = astReadInt( channel, "strict", -INT_MAX );
+      if ( TestStrict( new, status ) ) SetStrict( new, new->strict, status );
+
+/* Full. */
+/* ----- */
+      new->full = astReadInt( channel, "full", -INT_MAX );
+      if ( TestFull( new, status ) ) SetFull( new, new->full, status );
+
+/* Comment. */
+/* -------- */
+      new->comment = astReadInt( channel, "comm", -INT_MAX );
+      if ( TestComment( new, status ) ) SetComment( new, new->comment, status );
+
+/* If an error occurred, clean up by deleting the new Channel. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Channel pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions
+   defined by this class. Each simply checks the global error status
+   and then locates and executes the appropriate member function,
+   using the function pointer stored in the object's virtual function
+   table (this pointer is located using the astMEMBER macro defined in
+   "object.h").
+
+   Note that the member function may not be the one defined here, as
+   it may have been over-ridden by a derived class. However, it should
+   still have the same interface. */
+void astGetNextData_( AstChannel *this, int begin, char **name, char **val, int *status ) {
+   *name = NULL;
+   *val = NULL;
+   if ( !astOK ) return;
+   (**astMEMBER(this,Channel,GetNextData))( this, begin, name, val, status );
+}
+char *astGetNextText_( AstChannel *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Channel,GetNextText))( this, status );
+}
+void astPutNextText_( AstChannel *this, const char *line, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Channel,PutNextText))( this, line, status );
+}
+AstObject *astRead_( AstChannel *this, int *status ) {
+   if ( !astOK ) return NULL;
+   astAddWarning( this, 0, NULL, NULL, status );
+   return (**astMEMBER(this,Channel,Read))( this, status );
+}
+void astReadClassData_( AstChannel *this, const char *class, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Channel,ReadClassData))( this, class, status );
+}
+double astReadDouble_( AstChannel *this, const char *name, double def, int *status ) {
+   if ( !astOK ) return 0.0;
+   return (**astMEMBER(this,Channel,ReadDouble))( this, name, def, status );
+}
+int astReadInt_( AstChannel *this, const char *name, int def, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Channel,ReadInt))( this, name, def, status );
+}
+AstObject *astReadObject_( AstChannel *this, const char *name,
+                           AstObject *def, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Channel,ReadObject))( this, name, def, status );
+}
+char *astReadString_( AstChannel *this, const char *name, const char *def, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Channel,ReadString))( this, name, def, status );
+}
+void astWriteBegin_( AstChannel *this, const char *class,
+                     const char *comment, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Channel,WriteBegin))( this, class, comment, status );
+}
+void astWriteDouble_( AstChannel *this, const char *name, int set, int helpful,
+                      double value, const char *comment, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Channel,WriteDouble))( this, name, set, helpful, value,
+                                            comment, status );
+}
+void astWriteEnd_( AstChannel *this, const char *class, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Channel,WriteEnd))( this, class, status );
+}
+void astWriteInt_( AstChannel *this, const char *name, int set, int helpful,
+                   int value, const char *comment, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Channel,WriteInt))( this, name, set, helpful, value,
+                                         comment, status );
+}
+void astWriteIsA_( AstChannel *this, const char *class, const char *comment, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Channel,WriteIsA))( this, class, comment, status );
+}
+void astWriteString_( AstChannel *this, const char *name, int set, int helpful,
+                      const char *value, const char *comment, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Channel,WriteString))( this, name, set, helpful, value,
+                                            comment, status );
+}
+void astPutChannelData_( AstChannel *this, void *data, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Channel,PutChannelData))( this, data, status );
+}
+
+AstKeyMap *astWarnings_( AstChannel *this, int *status ){
+   if( !astOK ) return NULL;
+   return (**astMEMBER(this,Channel,Warnings))( this, status );
+}
+
+/* Because of the variable argument list, we need to work a bit harder on
+   astAddWarning. Functions that provide implementations of the
+   astAddWarning method recieve the fully expanded message and so do not
+   need a variable argument list. */
+
+void astAddWarning_( void *this_void, int level, const char *fmt,
+                     const char *method, int *status, ... ) {
+   AstChannel *this;
+   char buff[ 201 ];
+   va_list args;
+   int nc;
+
+   this = astCheckChannel( this_void );
+
+   if( fmt ) {
+      if( astOK ) {
+         va_start( args, status );
+         nc = vsprintf( buff, fmt, args );
+         va_end( args );
+         if( nc > 200 ) {
+            astError( AST__INTER, "astAddWarning(%s): Message buffer size "
+                      "exceeded (internal AST programming error).",
+                      status, astGetClass( this ) );
+         } else {
+            (**astMEMBER(this,Channel,AddWarning))( this, level, buff, method, status );
+         }
+      }
+   } else {
+      (**astMEMBER(this,Channel,AddWarning))( this, level, NULL, method, status );
+   }
+}
+
+/* Count the number of times astWrite is invoked (excluding invocations
+   made from within the astWriteObject method - see below). The count is
+   done here so that invocations of astWrite within a sub-class will be
+   included. */
+int astWrite_( AstChannel *this, AstObject *object, int *status ) {
+   astDECLARE_GLOBALS
+   if ( !astOK ) return 0;
+   astGET_GLOBALS(this);
+   nwrite_invoc++;
+   astAddWarning( this, 0, NULL, NULL, status );
+   return (**astMEMBER(this,Channel,Write))( this, object, status );
+}
+
+/* We do not want to count invocations of astWrite made from within the
+   astWriteObject method. So decrement the number of invocations first
+   (this assumes that each invocation of astWriteObject will only invoke
+   astWrite once). */
+void astWriteObject_( AstChannel *this, const char *name, int set,
+                      int helpful, AstObject *value, const char *comment, int *status ) {
+   astDECLARE_GLOBALS
+   if ( !astOK ) return;
+   astGET_GLOBALS(this);
+   nwrite_invoc--;
+   (**astMEMBER(this,Channel,WriteObject))( this, name, set, helpful, value,
+                                            comment, status );
+}
+
+
+
+
+
diff --git a/ast/channel.h b/ast/channel.h
new file mode 100644
index 0000000..1969ea3
--- /dev/null
+++ b/ast/channel.h
@@ -0,0 +1,687 @@
+/*
+*+
+*  Name:
+*     channel.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Channel class.
+
+*  Invocation:
+*     #include "channel.h"
+
+*  Description:
+*     This include file defines the interface to the Channel class and
+*     provides the type definitions, function prototypes and macros,
+*     etc. needed to use this class.
+*
+*     A Channel is the basic form of AST I/O channel, through which
+*     Objects may be written and later read back. It causes I/O to
+*     take place using a textual format via standard input and
+*     standard output.
+*
+*     Writing to a Channel will result in a textual representation of
+*     an Object being produced on standard output. Reading from a
+*     Channel will causes a textual description of an Object to be
+*     read from standard input, and that Object to be
+*     re-created. Channel I/O is stream based, and multiple objects
+*     may be written or read in succession through the same Channel. A
+*     null Object pointer is returned if there is no more input to
+*     read.
+
+*  Inheritance:
+*     The Channel class inherits from the Object class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     Comment (integer)
+*        A boolean value (0 or 1) which controls whether comments are
+*        to be included in textual output generated by a Channel. If
+*        this value is non-zero (the default), then comments will be
+*        included. If it is zero, comments will be omitted.
+*     Full (integer)
+*        A three-state flag (taking values -1, 0 or +1) which controls
+*        the amount of information included in textual output
+*        generated by a Channel. If this value is zero (the default),
+*        then a modest amount of non-essential but useful information
+*        will be included along with the output. If Full is negative,
+*        all non-essential information will be suppressed, while if it
+*        is positive, the output will include the maximum amount of
+*        information about the Object being written.
+*     Skip (integer)
+*        A boolean value which indicates whether the Objects being
+*        read through a Channel are inter-mixed with other external
+*        data. If this value is zero (the default), then the source of
+*        input data is expected to contain descriptions of AST Objects
+*        and comments and nothing else (if anything else is read, an
+*        error will result). If Skip is non-zero, then any non-Object
+*        data encountered between Objects will simply be skipped over
+*        in order to reach the next Object.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearAttrib
+*           Clear an attribute value for a Mapping.
+*        astGetAttrib
+*           Get an attribute value for a Mapping.
+*        astSetAttrib
+*           Set an attribute value for a Mapping.
+*        astTestAttrib
+*           Test if an attribute value has been set for a Mapping.
+
+*  New Methods Defined:
+*     Public:
+*        astRead
+*           Read an Object from a Channel.
+*        astWrite
+*           Write an Object to a Channel.
+*
+*     Protected:
+*        astClearComment
+*           Clear the Comment attribute for a Channel.
+*        astClearFull
+*           Clear the Full attribute for a Channel.
+*        astClearSkip
+*           Clear the Skip attribute for a Channel.
+*        astGetComment
+*           Get the value of the Comment attribute for a Channel.
+*        astGetFull
+*           Get the value of the Full attribute for a Channel.
+*        astGetNextData
+*           Read the next item of data from a data source.
+*        astGetNextText
+*           Read the next line of input text from a data source.
+*        astGetSkip
+*           Get the value of the Skip attribute for a Channel.
+*        astPutNextText
+*           Write a line of output text to a data sink.
+*        astReadClassData
+*           Read values from a data source for a class loader.
+*        astReadDouble
+*           Read a double value as part of loading a class.
+*        astReadInt
+*           Read an int value as part of loading a class.
+*        astReadObject
+*           Read a (sub)Object as part of loading a class.
+*        astReadString
+*           Read a string value as part of loading a class.
+*        astSetComment
+*           Set the value of the Comment attribute for a Channel.
+*        astSetFull
+*           Set the value of the Full attribute for a Channel.
+*        astSetSkip
+*           Set the value of the Skip attribute for a Channel.
+*        astTestComment
+*           Test whether a value has been set for the Comment attribute of a
+*           Channel.
+*        astTestFull
+*           Test whether a value has been set for the Full attribute of a
+*           Channel.
+*        astTestSkip
+*           Test whether a value has been set for the Skip attribute of a
+*           Channel.
+*        astWriteBegin
+*           Write a "Begin" data item to a data sink.
+*        astWriteDouble
+*           Write a double value to a data sink.
+*        astWriteEnd
+*           Write an "End" data item to a data sink.
+*        astWriteInt
+*           Write an integer value to a data sink.
+*        astWriteIsA
+*           Write an "IsA" data item to a data sink.
+*        astWriteObject
+*           Write an Object as a value to a data sink.
+*        astWriteString
+*           Write a string value to a data sink.
+
+*  Other Class Functions:
+*     Public:
+*        astChannel
+*           Create a Channel.
+*        astChannelFor
+*           Create a Channel from a foreign language interface.
+*        astIsAChannel
+*           Test class membership.
+*
+*     Protected:
+*        astCheckChannel
+*           Validate class membership.
+*        astInitChannel
+*           Initialise a Channel.
+*        astInitChannelVtab
+*           Initialise the virtual function table for the Channel class.
+*        astLoadChannel
+*           Load a Channel.
+
+*  Type Definitions:
+*     Public:
+*        AstChannel
+*           Channel object type.
+*
+*     Protected:
+*        AstChannelVtab
+*           Channel virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     12-AUG-1996 (RFWS):
+*        Original version.
+*     12-DEC-1996 (RFWS):
+*        Added the astChannelFor function.
+*     11-NOV-2002 (DSB):
+*        Added astWriteInvocations.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitAxisVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+
+/* Note that the usual setting of the CHANNEL_INCLUDED flag, which
+   prevents this file being included more than once, must be deferred
+   until after including the "object.h" file. This is because
+   "object.h" needs to include the present interface definition (as a
+   form of "forward reference") in order to have access to I/O
+   Channels itself. */
+#if !defined( CHANNEL_INCLUDED )
+#define CHANNEL_INCLUDED
+
+/* C header files. */
+/* --------------- */
+#include <stddef.h>
+
+/* Macros */
+/* ====== */
+/* Define constants used to size global arrays in this module. */
+#define AST__CHANNEL_GETATTRIB_BUFF_LEN 50
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+
+/* The astWarnings function returns a KeyMap pointer, but we cannot
+   include keymap.h here to define the AstKeyMap type since keymap.h
+   itself include sthis file. So we make a temporary declaration which
+   will be replaced by the full one when the keymap.h file is included. */
+struct AstKeyMap;
+
+/* Channel structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstChannel {
+
+/* Attributes inherited from the parent class. */
+   AstObject object;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   const char *(* source)( void ); /* Pointer to source function */
+   char *(* source_wrap)( const char *(*)(void), int * );
+                                 /* Source wrapper function pointer */
+   void (* sink)( const char * ); /* Pointer to sink function */
+   void (* sink_wrap)( void (*)( const char *), const char *, int * );
+                                 /* Sink wrapper function pointer */
+   int comment;                  /* Output comments? */
+   int full;                     /* Set max/normal/min information level */
+   int skip;                     /* Skip data between Objects? */
+   int indent;                   /* Indentation increment in astWrite output */
+   int report_level;             /* Skip data between Objects? */
+   int strict;                   /* Report unexpected data items? */
+   void *data;                   /* Data to pass to source/sink functions */
+   char **warnings;              /* Array of warning strings */
+   int nwarn;                    /* Size of warnings array */
+   FILE *fd_in;                  /* Descriptor for source text file */
+   char *fn_in;                  /* Full path for source text file */
+   FILE *fd_out;                 /* Descriptor for sink text file */
+   char *fn_out;                 /* Full path for sink text file */
+} AstChannel;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstChannelVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstObjectVtab object_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   struct AstKeyMap *(* Warnings)( AstChannel *, int * );
+   AstObject *(* Read)( AstChannel *, int * );
+   AstObject *(* ReadObject)( AstChannel *, const char *, AstObject *, int * );
+   char *(* GetNextText)( AstChannel *, int * );
+   char *(* ReadString)( AstChannel *, const char *, const char *, int * );
+   double (* ReadDouble)( AstChannel *, const char *, double, int * );
+   int (* GetComment)( AstChannel *, int * );
+   int (* GetFull)( AstChannel *, int * );
+   int (* GetStrict)( AstChannel *, int * );
+   int (* ReadInt)( AstChannel *, const char *, int, int * );
+   int (* TestComment)( AstChannel *, int * );
+   int (* TestFull)( AstChannel *, int * );
+   int (* TestStrict)( AstChannel *, int * );
+   int (* Write)( AstChannel *, AstObject *, int * );
+   void (* AddWarning)( AstChannel *, int, const char *, const char *, int * );
+   void (* ClearComment)( AstChannel *, int * );
+   void (* ClearFull)( AstChannel *, int * );
+   void (* ClearStrict)( AstChannel *, int * );
+   void (* GetNextData)( AstChannel *, int, char **, char **, int * );
+   void (* PutChannelData)( AstChannel *, void *, int * );
+   void (* PutNextText)( AstChannel *, const char *, int * );
+   void (* ReadClassData)( AstChannel *, const char *, int * );
+   void (* SetComment)( AstChannel *, int, int * );
+   void (* SetFull)( AstChannel *, int, int * );
+   void (* SetStrict)( AstChannel *, int, int * );
+   void (* WriteBegin)( AstChannel *, const char *, const char *, int * );
+   void (* WriteDouble)( AstChannel *, const char *, int, int, double, const char *, int * );
+   void (* WriteEnd)( AstChannel *, const char *, int * );
+   void (* WriteInt)( AstChannel *, const char *, int, int, int, const char *, int * );
+   void (* WriteIsA)( AstChannel *, const char *, const char *, int * );
+   void (* WriteObject)( AstChannel *, const char *, int, int, AstObject *, const char *, int * );
+   void (* WriteString)( AstChannel *, const char *, int, int, const char *, const char *, int * );
+
+   int (* GetSkip)( AstChannel *, int * );
+   int (* TestSkip)( AstChannel *, int * );
+   void (* ClearSkip)( AstChannel *, int * );
+   void (* SetSkip)( AstChannel *, int, int * );
+
+   int (* GetReportLevel)( AstChannel *, int * );
+   int (* TestReportLevel)( AstChannel *, int * );
+   void (* ClearReportLevel)( AstChannel *, int * );
+   void (* SetReportLevel)( AstChannel *, int, int * );
+
+   int (* GetIndent)( AstChannel *, int * );
+   int (* TestIndent)( AstChannel *, int * );
+   void (* ClearIndent)( AstChannel *, int * );
+   void (* SetIndent)( AstChannel *, int, int * );
+
+   const char *(* GetSourceFile)( AstChannel *, int * );
+   int (* TestSourceFile)( AstChannel *, int * );
+   void (* ClearSourceFile)( AstChannel *, int * );
+   void (* SetSourceFile)( AstChannel *, const char *, int * );
+
+   const char *(* GetSinkFile)( AstChannel *, int * );
+   int (* TestSinkFile)( AstChannel *, int * );
+   void (* ClearSinkFile)( AstChannel *, int * );
+   void (* SetSinkFile)( AstChannel *, const char *, int * );
+} AstChannelVtab;
+
+/* Define a private structure type used to store linked lists of
+   name-value associations. */
+typedef struct AstChannelValue {
+   struct AstChannelValue *flink; /* Link to next element */
+   struct AstChannelValue *blink; /* Link to previous element */
+   char *name;                    /* Pointer to name string */
+   union {                        /* Holds pointer to value */
+      char *string;               /* Pointer to string value */
+      AstObject *object;          /* Pointer to Object value */
+   } ptr;
+   int is_object;                 /* Whether value is an Object (else string) */
+} AstChannelValue;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstChannelGlobals {
+   AstChannelVtab Class_Vtab;
+   int Class_Init;
+   int AstReadClassData_Msg;
+   char GetAttrib_Buff[ AST__CHANNEL_GETATTRIB_BUFF_LEN + 1 ];
+   int Items_Written;
+   int Current_Indent;
+   int Nest;
+   int Nwrite_Invoc;
+   char **Object_Class;
+   AstChannelValue **Values_List;
+   char **Values_Class;
+   int *Values_OK;
+   int *End_Of_Object;
+   void *Channel_Data;
+} AstChannelGlobals;
+
+#endif
+
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Channel)          /* Check class membership */
+astPROTO_ISA(Channel)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstChannel *astChannel_( const char *(*)( void ), void (*)( const char * ),
+                         const char *, int *, ...);
+#else
+AstChannel *astChannelId_( const char *(*)( void ), void (*)( const char * ),
+                           const char *, ... )__attribute__((format(printf,3,4)));
+AstChannel *astChannelForId_( const char *(*)( void ),
+                              char *(*)( const char *(*)( void ), int * ),
+                              void (*)( const char * ),
+                              void (*)( void (*)( const char * ),
+                                        const char *, int * ),
+                              const char *, ... );
+#endif
+
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstChannel *astInitChannel_( void *, size_t, int, AstChannelVtab *,
+                             const char *, const char *(*)( void ),
+                             char *(*)( const char *(*)( void ), int * ),
+                             void (*)( const char * ),
+                             void (*)( void (*)( const char * ),
+                             const char *, int * ), int * );
+
+
+/* Vtab initialiser. */
+void astInitChannelVtab_( AstChannelVtab *, const char *, int * );
+
+/* Loader. */
+AstChannel *astLoadChannel_( void *, size_t, AstChannelVtab *,
+                             const char *, AstChannel *channel, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitChannelGlobals_( AstChannelGlobals * );
+#endif
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+AstObject *astRead_( AstChannel *, int * );
+int astWrite_( AstChannel *, AstObject *, int * );
+void astPutChannelData_( AstChannel *, void *, int * );
+void *astChannelData_( void );
+struct AstKeyMap *astWarnings_( AstChannel *, int * );
+
+char *astSourceWrap_( const char *(*)( void ), int * );
+void astSinkWrap_( void (*)( const char * ), const char *, int * );
+
+# if defined(astCLASS)           /* Protected */
+void astStoreChannelData_( AstChannel *, int * );
+AstObject *astReadObject_( AstChannel *, const char *, AstObject *, int * );
+char *astGetNextText_( AstChannel *, int * );
+char *astReadString_( AstChannel *, const char *, const char *, int * );
+double astReadDouble_( AstChannel *, const char *, double, int * );
+int astGetComment_( AstChannel *, int * );
+int astGetFull_( AstChannel *, int * );
+int astGetStrict_( AstChannel *, int * );
+int astReadInt_( AstChannel *, const char *, int, int * );
+int astTestComment_( AstChannel *, int * );
+int astTestFull_( AstChannel *, int * );
+int astTestStrict_( AstChannel *, int * );
+void astAddWarning_( void *, int, const char *, const char *, int *, ... )__attribute__((format(printf,3,6)));
+void astClearComment_( AstChannel *, int * );
+void astClearFull_( AstChannel *, int * );
+void astClearStrict_( AstChannel *, int * );
+void astGetNextData_( AstChannel *, int, char **, char **, int * );
+void astPutNextText_( AstChannel *, const char *, int * );
+void astReadClassData_( AstChannel *, const char *, int * );
+void astSetComment_( AstChannel *, int, int * );
+void astSetFull_( AstChannel *, int, int * );
+void astSetStrict_( AstChannel *, int, int * );
+void astWriteBegin_( AstChannel *, const char *, const char *, int * );
+void astWriteDouble_( AstChannel *, const char *, int, int, double, const char *, int * );
+void astWriteEnd_( AstChannel *, const char *, int * );
+void astWriteInt_( AstChannel *, const char *, int, int, int, const char *, int * );
+int astWriteInvocations_( int * );
+void astWriteIsA_( AstChannel *, const char *, const char *, int * );
+void astWriteObject_( AstChannel *, const char *, int, int, AstObject *, const char *, int * );
+void astWriteString_( AstChannel *, const char *, int, int, const char *, const char *, int * );
+
+int astGetSkip_( AstChannel *, int * );
+int astTestSkip_( AstChannel *, int * );
+void astClearSkip_( AstChannel *, int * );
+void astSetSkip_( AstChannel *, int, int * );
+
+int astGetReportLevel_( AstChannel *, int * );
+int astTestReportLevel_( AstChannel *, int * );
+void astClearReportLevel_( AstChannel *, int * );
+void astSetReportLevel_( AstChannel *, int, int * );
+
+int astGetIndent_( AstChannel *, int * );
+int astTestIndent_( AstChannel *, int * );
+void astClearIndent_( AstChannel *, int * );
+void astSetIndent_( AstChannel *, int, int * );
+
+const char *astGetSourceFile_( AstChannel *, int * );
+int astTestSourceFile_( AstChannel *, int * );
+void astClearSourceFile_( AstChannel *, int * );
+void astSetSourceFile_( AstChannel *, const char *, int * );
+
+const char *astGetSinkFile_( AstChannel *, int * );
+int astTestSinkFile_( AstChannel *, int * );
+void astClearSinkFile_( AstChannel *, int * );
+void astSetSinkFile_( AstChannel *, const char *, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them to
+   validate their own arguments. We must use a cast when passing object
+   pointers (so that they can accept objects from derived classes). */
+
+/* Check class membership. */
+#define astCheckChannel(this) astINVOKE_CHECK(Channel,this,0)
+#define astVerifyChannel(this) astINVOKE_CHECK(Channel,this,1)
+
+/* Test class membership. */
+#define astIsAChannel(this) astINVOKE_ISA(Channel,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astChannel astINVOKE(F,astChannel_)
+#else
+#define astChannel astINVOKE(F,astChannelId_)
+#define astChannelFor astINVOKE(F,astChannelForId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitChannel(mem,size,init,vtab,name,source,source_wrap,sink,sink_wrap) \
+astINVOKE(O,astInitChannel_(mem,size,init,vtab,name,source,source_wrap,sink,sink_wrap,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitChannelVtab(vtab,name) astINVOKE(V,astInitChannelVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadChannel(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadChannel_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to member functions. */
+/* ------------------------------- */
+/* Here we make use of astCheckChannel to validate Channel pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#define astRead(this) \
+astINVOKE(O,astRead_(astCheckChannel(this),STATUS_PTR))
+#define astWrite(this,object) \
+astINVOKE(V,astWrite_(astCheckChannel(this),astCheckObject(object),STATUS_PTR))
+#define astPutChannelData(this,data) \
+astINVOKE(V,astPutChannelData_(astCheckChannel(this),data,STATUS_PTR))
+#define astWarnings(this) \
+astINVOKE(O,astWarnings_(astCheckChannel(this),STATUS_PTR))
+
+#define astSourceWrap astSourceWrap_
+#define astSinkWrap astSinkWrap_
+#define astChannelData astChannelData_()
+
+#if defined(astCLASS)            /* Protected */
+#define astAddWarning astAddWarning_
+#define astStoreChannelData(this) \
+astStoreChannelData_(astCheckChannel(this),STATUS_PTR)
+
+#define astClearComment(this) \
+astINVOKE(V,astClearComment_(astCheckChannel(this),STATUS_PTR))
+#define astClearFull(this) \
+astINVOKE(V,astClearFull_(astCheckChannel(this),STATUS_PTR))
+#define astClearStrict(this) \
+astINVOKE(V,astClearStrict_(astCheckChannel(this),STATUS_PTR))
+#define astGetComment(this) \
+astINVOKE(V,astGetComment_(astCheckChannel(this),STATUS_PTR))
+#define astGetFull(this) \
+astINVOKE(V,astGetFull_(astCheckChannel(this),STATUS_PTR))
+#define astGetNextData(this,begin,name,val) \
+astINVOKE(V,astGetNextData_(astCheckChannel(this),begin,name,val,STATUS_PTR))
+#define astGetNextText(this) \
+astINVOKE(V,astGetNextText_(astCheckChannel(this),STATUS_PTR))
+#define astGetStrict(this) \
+astINVOKE(V,astGetStrict_(astCheckChannel(this),STATUS_PTR))
+#define astPutNextText(this,line) \
+astINVOKE(V,astPutNextText_(astCheckChannel(this),line,STATUS_PTR))
+#define astReadClassData(this,class) \
+astINVOKE(V,astReadClassData_(astCheckChannel(this),class,STATUS_PTR))
+#define astReadDouble(this,name,def) \
+astINVOKE(V,astReadDouble_(astCheckChannel(this),name,def,STATUS_PTR))
+#define astReadInt(this,name,def) \
+astINVOKE(V,astReadInt_(astCheckChannel(this),name,def,STATUS_PTR))
+#define astReadObject(this,name,def) \
+astINVOKE(O,astReadObject_(astCheckChannel(this),name,(def)?astCheckObject(def):NULL,STATUS_PTR))
+#define astReadString(this,name,def) \
+astINVOKE(V,astReadString_(astCheckChannel(this),name,def,STATUS_PTR))
+#define astSetComment(this,value) \
+astINVOKE(V,astSetComment_(astCheckChannel(this),value,STATUS_PTR))
+#define astSetFull(this,value) \
+astINVOKE(V,astSetFull_(astCheckChannel(this),value,STATUS_PTR))
+#define astSetStrict(this,value) \
+astINVOKE(V,astSetStrict_(astCheckChannel(this),value,STATUS_PTR))
+#define astTestComment(this) \
+astINVOKE(V,astTestComment_(astCheckChannel(this),STATUS_PTR))
+#define astTestFull(this) \
+astINVOKE(V,astTestFull_(astCheckChannel(this),STATUS_PTR))
+#define astTestStrict(this) \
+astINVOKE(V,astTestStrict_(astCheckChannel(this),STATUS_PTR))
+#define astWriteBegin(this,class,comment) \
+astINVOKE(V,astWriteBegin_(astCheckChannel(this),class,comment,STATUS_PTR))
+#define astWriteDouble(this,name,set,helpful,value,comment) \
+astINVOKE(V,astWriteDouble_(astCheckChannel(this),name,set,helpful,value,comment,STATUS_PTR))
+#define astWriteEnd(this,class) \
+astINVOKE(V,astWriteEnd_(astCheckChannel(this),class,STATUS_PTR))
+#define astWriteInt(this,name,set,helpful,value,comment) \
+astINVOKE(V,astWriteInt_(astCheckChannel(this),name,set,helpful,value,comment,STATUS_PTR))
+#define astWriteIsA(this,class,comment) \
+astINVOKE(V,astWriteIsA_(astCheckChannel(this),class,comment,STATUS_PTR))
+#define astWriteObject(this,name,set,helpful,value,comment) \
+astINVOKE(V,astWriteObject_(astCheckChannel(this),name,set,helpful,astCheckObject(value),comment,STATUS_PTR))
+#define astWriteString(this,name,set,helpful,value,comment) \
+astINVOKE(V,astWriteString_(astCheckChannel(this),name,set,helpful,value,comment,STATUS_PTR))
+
+#define astWriteInvocations astWriteInvocations_(STATUS_PTR)
+
+#define astClearSkip(this) \
+astINVOKE(V,astClearSkip_(astCheckChannel(this),STATUS_PTR))
+#define astGetSkip(this) \
+astINVOKE(V,astGetSkip_(astCheckChannel(this),STATUS_PTR))
+#define astSetSkip(this,value) \
+astINVOKE(V,astSetSkip_(astCheckChannel(this),value,STATUS_PTR))
+#define astTestSkip(this) \
+astINVOKE(V,astTestSkip_(astCheckChannel(this),STATUS_PTR))
+
+#define astClearReportLevel(this) \
+astINVOKE(V,astClearReportLevel_(astCheckChannel(this),STATUS_PTR))
+#define astGetReportLevel(this) \
+astINVOKE(V,astGetReportLevel_(astCheckChannel(this),STATUS_PTR))
+#define astSetReportLevel(this,value) \
+astINVOKE(V,astSetReportLevel_(astCheckChannel(this),value,STATUS_PTR))
+#define astTestReportLevel(this) \
+astINVOKE(V,astTestReportLevel_(astCheckChannel(this),STATUS_PTR))
+
+#define astClearIndent(this) \
+astINVOKE(V,astClearIndent_(astCheckChannel(this),STATUS_PTR))
+#define astGetIndent(this) \
+astINVOKE(V,astGetIndent_(astCheckChannel(this),STATUS_PTR))
+#define astSetIndent(this,value) \
+astINVOKE(V,astSetIndent_(astCheckChannel(this),value,STATUS_PTR))
+#define astTestIndent(this) \
+astINVOKE(V,astTestIndent_(astCheckChannel(this),STATUS_PTR))
+
+#define astClearSourceFile(this) \
+astINVOKE(V,astClearSourceFile_(astCheckChannel(this),STATUS_PTR))
+#define astGetSourceFile(this) \
+astINVOKE(V,astGetSourceFile_(astCheckChannel(this),STATUS_PTR))
+#define astSetSourceFile(this,value) \
+astINVOKE(V,astSetSourceFile_(astCheckChannel(this),value,STATUS_PTR))
+#define astTestSourceFile(this) \
+astINVOKE(V,astTestSourceFile_(astCheckChannel(this),STATUS_PTR))
+
+#define astClearSinkFile(this) \
+astINVOKE(V,astClearSinkFile_(astCheckChannel(this),STATUS_PTR))
+#define astGetSinkFile(this) \
+astINVOKE(V,astGetSinkFile_(astCheckChannel(this),STATUS_PTR))
+#define astSetSinkFile(this,value) \
+astINVOKE(V,astSetSinkFile_(astCheckChannel(this),value,STATUS_PTR))
+#define astTestSinkFile(this) \
+astINVOKE(V,astTestSinkFile_(astCheckChannel(this),STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/circle.c b/ast/circle.c
new file mode 100644
index 0000000..c795b98
--- /dev/null
+++ b/ast/circle.c
@@ -0,0 +1,2900 @@
+/*
+*class++
+*  Name:
+*     Circle
+
+*  Purpose:
+*     A circular or spherical region within a Frame.
+
+*  Constructor Function:
+c     astCircle
+f     AST_CIRCLE
+
+*  Description:
+*     The Circle class implements a Region which represents a circle or
+*     sphere within a Frame.
+
+*  Inheritance:
+*     The Circle class inherits from the Region class.
+
+*  Attributes:
+*     The Circle class does not define any new attributes beyond
+*     those which are applicable to all Regions.
+
+*  Functions:
+c     In addition to those functions applicable to all Regions, the
+c     following functions may also be applied to all Circles:
+f     In addition to those routines applicable to all Regions, the
+f     following routines may also be applied to all Circles:
+*
+c     - astCirclePars: Get the geometric parameters of the Circle
+f     - AST_CIRCLEPARS: Get the geometric parameters of the Circle
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     31-AUG-2004 (DSB):
+*        Original version.
+*     4-NOV-2013 (DSB):
+*        Modify RegPins so that it can handle uncertainty regions that straddle
+*        a discontinuity. Previously, such uncertainty Regions could have a huge
+*        bounding box resulting in matching region being far too big.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Circle
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "region.h"              /* Coordinate regions (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "box.h"                 /* Box Regions */
+#include "wcsmap.h"              /* Definitons of AST__DPI etc */
+#include "circle.h"              /* Interface definition for this class */
+#include "ellipse.h"             /* Interface definition for ellipse class */
+#include "mapping.h"             /* Position mappings */
+#include "unitmap.h"             /* Unit Mapping */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstMapping *(* parent_simplify)( AstMapping *, int * );
+static void (* parent_setregfs)( AstRegion *, AstFrame *, int * );
+static void (* parent_resetcache)( AstRegion *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Circle)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Circle,Class_Init)
+#define class_vtab astGLOBAL(Circle,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstCircleVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstCircle *astCircleId_( void *, int, const double[], const double[], void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double *CircumPoint( AstFrame *, int, const double *, double, int * );
+static double *RegCentre( AstRegion *this, double *, double **, int, int, int * );
+static int RegPins( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+static int RegTrace( AstRegion *, int, double *, double **, int * );
+static void Cache( AstCircle *, int * );
+static void CalcPars( AstFrame *, AstPointSet *, double *, double *, double *, int * );
+static void CirclePars( AstCircle *, double *, double *, double *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void RegBaseBox( AstRegion *this, double *, double *, int * );
+static void ResetCache( AstRegion *this, int * );
+static void SetRegFS( AstRegion *, AstFrame *, int * );
+
+/* Member functions. */
+/* ================= */
+
+AstRegion *astBestCircle_( AstPointSet *mesh, double *cen, AstRegion *unc, int *status ){
+/*
+*+
+*  Name:
+*     astBestCircle
+
+*  Purpose:
+*     Find the best fitting Circle through a given mesh of points.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     AstRegion *astBestCircle( AstPointSet *mesh, double *cen, AstRegion *unc )
+
+*  Class Membership:
+*     Circle member function
+
+*  Description:
+*     This function finds the best fitting Circle through a given mesh of
+*     points.
+
+*  Parameters:
+*     mesh
+*        Pointer to a PointSet holding the mesh of points. They are
+*        assumed to be in the Frame represented by "unc".
+*     cen
+*        Pointer to an array holding the coordinates of the new Circle
+*        centre.
+*     unc
+*        A Region representing the uncertainty associated with each point
+*        on the mesh.
+
+*  Returned Value:
+*     Pointer to the best fitting Circle. It will inherit the positional
+*     uncertainty and Frame represented by "unc".
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*-
+*/
+
+/* Local Variables: */
+   AstRegion *result;
+   double *p;
+   double rad;
+   double **ptr;
+   double d;
+   double s2r;
+   double p0;
+   int ic;
+   int ip;
+   int n;
+   int nc;
+   int np;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get no. of points in the mesh, and the number of axis values per point. */
+   np = astGetNpoint( mesh );
+   nc = astGetNcoord( mesh );
+
+/* Get pointers to the axis values. */
+   ptr = astGetPoints( mesh );
+
+/* Check pointers can be used safely */
+   if( astOK ) {
+
+/* We find ther sum of the squared axis increments from the supplied
+   centre to each of the supplied points. Initialise the sum to zero. */
+      s2r = 0.0;
+      n = 0;
+
+/* Loop round all axes. */
+      for( ic = 0; ic < nc; ic++ ) {
+         p = ptr[ ic ];
+         p0 = cen[ ic ];
+
+/* Loop round all values for this axis. */
+         for( ip = 0; ip < np; ip++, p++ ) {
+            if( *p != AST__BAD ) {
+
+/* Increment the sums */
+               d = *p - p0;
+               s2r += d*d;
+               n++;
+
+            }
+         }
+      }
+
+/* Find the RMS distance of the points from the supplied centre. This is
+   the radius of the best fitting circle. */
+      if( n > 0 ) {
+         rad = sqrt( nc*s2r/n );
+
+/* Create the returned Region. */
+         result = (AstRegion *) astCircle( unc, 1, cen, &rad, unc, "", status );
+      }
+   }
+
+/* Return NULL if anything went wrong. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result.*/
+   return result;
+}
+
+static void Cache( AstCircle *this, int *status ){
+/*
+*  Name:
+*     Cache
+
+*  Purpose:
+*     Calculate intermediate values and cache them in the Circle structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     void Cache( AstCircle *this, int *status )
+
+*  Class Membership:
+*     Circle member function
+
+*  Description:
+*     This function uses the PointSet stored in the parent Region to calculate
+*     some intermediate values which are useful in other methods. These
+*     values are stored within the Circle structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Circle.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   double *centre;
+   double *lb;
+   double *ub;
+   double radius;
+   int i;
+   int nc;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Do Nothing if the cached information is up to date. */
+   if( this->stale ) {
+
+/* Get a pointer to the base Frame and the number of base axes. */
+      frm = astGetFrame( ((AstRegion *) this)->frameset, AST__BASE );
+      nc = astGetNaxes( frm );
+
+/* Allocate memory to hold the centre coords. */
+      centre = astMalloc( sizeof( double )*astGetNaxes( frm ) );
+
+/* Get the radius and centre of the Circle in the base Frame, using the
+   centre and circumference positions stored in the parent Region structure. */
+      CalcPars( frm, ( (AstRegion *) this)->points, centre, &radius, NULL,
+                status );
+
+/* Allocate memory to store the base frame bounding box. This is just
+   initialised here. It is set properly when the astRegBaseMesh
+   function is called. This box should not be used unless the "basemesh"
+   component of the parent Region structure is set to a non-null value. */
+      lb = (double *) astMalloc( sizeof( double )*(size_t) nc );
+      ub = (double *) astMalloc( sizeof( double )*(size_t) nc );
+
+/* Initialise the bounding box. */
+      for( i = 0; astOK && i < nc; i++ ) {
+         lb[ i ] = -DBL_MAX;
+         ub[ i ] = DBL_MAX;
+      }
+
+/* If everything went OK, store these values in the Circle structure. */
+      if( astOK ) {
+         this->radius = radius;
+
+         astFree( this->centre );
+         this->centre = centre;
+         centre = NULL;
+
+         astFree( this->lb );
+         this->lb = lb;
+         lb = NULL;
+
+         astFree( this->ub );
+         this->ub = ub;
+         ub = NULL;
+      }
+
+/* Free resources */
+      frm = astAnnul( frm );
+      if( centre ) centre = astFree( centre );
+
+/* Indicate cached information is up to date. */
+      this->stale = 0;
+   }
+}
+
+static void CalcPars( AstFrame *frm, AstPointSet *pset, double *centre,
+                      double *radius, double *p1, int *status ){
+/*
+*  Name:
+*     CalcPars
+
+*  Purpose:
+*     Calculate the geometric parameters of the supplied Circle.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     double *CalcPars( AstFrame *frm, AstPointSet *pset, double *centre,
+*                       double *radius, double *p1, int *status )
+
+*  Class Membership:
+*     Circle member function
+
+*  Description:
+*     This function uses the supplied PointSet to calculate the geometric
+*     parameters that describe the a crcle. These values are returned in
+*     a newly allocated dynamic array.
+
+*  Parameters:
+*     frm
+*        Pointer to the Frame in which the circle is defined.
+*     pset
+*        Pointer to a PointSet. The first point should be the circle
+*        centre, and the second point should be a point on the circle
+*        circumference.
+*     centre
+*        An array in which to return the axis values at the circle centre.
+*        The length of this array should be no less than the number of
+*        axes in "frm".
+*     radius
+*        Pointer to a double in which to return the circle radius,
+*        expressed as a geodesic distance in the supplied Frame.
+*     p1
+*        An array in which to return the coordinates of a point on the
+*        circumference of the circle. The length of this array should be
+*        no less than the number of axes in "frm". Can be NULL if the
+*        circumference position is not needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double **ptr;
+   double *circum;
+   int i;
+   int nc;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get and the number of axes. */
+   nc = astGetNaxes( frm );
+
+/* Get pointers to the coordinate data in the supplied PointSet. */
+   ptr = astGetPoints( pset );
+
+/* If no p1 array was supplied, create a temporary work array to hold the
+   circumference position. */
+   if( !p1 ) {
+      circum = astMalloc( sizeof( double )*nc );
+   } else {
+      circum = p1;
+   }
+
+/* Check pointers can be used safely. */
+   if( ptr ) {
+
+/* Copy the two points in to the allocated memory. */
+      for( i = 0; i < nc; i++ ) {
+         centre[ i ] = ptr[ i ][ 0 ];
+         circum[ i ] = ptr[ i ][ 1 ];
+      }
+
+/* Return the geodesic distance between these two points as the radius. */
+      *radius = astDistance( frm, centre, circum );
+   }
+
+/* Free any work array. */
+   if( !p1 ) circum = astFree( circum );
+}
+
+static void CirclePars( AstCircle *this, double *centre, double *radius,
+                        double *p1, int *status ){
+/*
+*++
+*  Name:
+c     astCirclePars
+f     AST_CIRCLEPARS
+
+*  Purpose:
+*     Returns the geometric parameters of an Circle.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "circle.h"
+c     void astCirclePars( AstCircle *this, double *centre, double *radius,
+c                         double *p1 )
+f     CALL AST_CIRCLEPARS( THIS, CENTRE, RADIUS, P1, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns the geometric parameters describing the supplied Circle.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+c     centre
+f     CENTRE( * ) = DOUBLE PRECISION (Returned)
+c        Pointer to an array
+f        An array
+*        in which to return the coordinates of the Circle centre.
+*        The length of this array should be no less than the number of
+*        axes in the associated coordinate system.
+c     radius
+f     RADIUS = DOUBLE PRECISION (Returned)
+*        Returned holding the radius of the Circle, as an geodesic
+*        distance in the associated coordinate system.
+c     p1
+f     P1( * ) = DOUBLE PRECISION (Returned)
+c        Pointer to an array
+f        An array
+*        in which to return the coordinates of a point on the
+*        circumference of the Circle. The length of this array should be
+*        no less than the number of axes in the associated coordinate system.
+c        A NULL pointer can be supplied if the circumference position is
+c        not needed.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - If the coordinate system represented by the Circle has been
+*     changed since it was first created, the returned parameters refer
+*     to the new (changed) coordinate system, rather than the original
+*     coordinate system. Note however that if the transformation from
+*     original to new coordinate system is non-linear, the shape
+*     represented by the supplied Circle object may not be an accurate
+*     circle.
+*--
+*/
+
+/* Local Variables: */
+   AstRegion *this_region;  /* Parent Region pointer */
+   AstFrame *frm;           /* Current Frame represented by the Circle */
+   AstPointSet *pset;       /* PointSet holding PointList axis values */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Store a pointer to the parent region structure. */
+   this_region = (AstRegion *) this;
+
+/* Transform the base Frame axis values into the current Frame. */
+   pset = astTransform( this_region->frameset, this_region->points, 1, NULL );
+
+/* Get the Circle frame. */
+   frm = astGetFrame( this_region->frameset, AST__CURRENT );
+
+/* Calculate the required parameters. */
+   CalcPars( frm, pset, centre, radius, p1, status );
+
+/* Free resources */
+   frm = astAnnul( frm );
+   pset = astAnnul( pset );
+}
+
+static double *CircumPoint( AstFrame *frm, int nax, const double *centre,
+                            double radius, int *status ){
+/*
+*  Name:
+*     CircumPoint
+
+*  Purpose:
+*     Find a point on the circumference of the circle.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     double *CircumPoint( AstFrame *frm, int nax, const double *centre,
+*                          double radius, int *status )
+
+*  Class Membership:
+*     Circle member function
+
+*  Description:
+*     This function returns a dynamically allocated array containing the
+*     axis values at a point on the circumference of the circle specified
+*     by a given centre and radius. The returned point is the point at
+*     which the circle crosses the first axis.
+
+*  Parameters:
+*     frm
+*        Pointer to the Frame in which the circle is defined.
+*     nax
+*        The number of axes in the Frame.
+*     centre
+*        An array holding the axis values at the circle centre.
+*     radius
+*        The circle radius, expressed as a geodesic distance in the
+*        supplied Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a 1D array holding the axis values at the point where
+*     the circle crosses the first frame axis. The length of this array
+*     will equal the number of axes in the supsplied Frame. It should be
+*     freed using astFree when no longer needed.
+
+*/
+
+/* Local Variables: */
+   double *circum;
+   double *work;
+   int i;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Allocate the returned array. */
+   circum = astMalloc( sizeof( double)*(size_t) nax );
+
+/* Allocate work space */
+   work = astMalloc( sizeof( double)*(size_t) nax );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Find the coords of a point that is offset away from the centre
+   position along the first axis. We use the supplied radius value as a
+   convenient offset length, but the actual length used is not critical. */
+      for( i = 0; i < nax; i++ ) work[ i ] = centre[ i ];
+      work[ 0 ] = astAxOffset( frm, 1, work[ 0 ], radius );
+
+/* Offset away from the centre position, towards the position found
+   above, going the distance specified by the supplied radius. */
+      astOffset( frm, centre, work, radius, (double *) circum );
+   }
+
+/* Free resources. */
+   work = astFree( work );
+
+/* Return the result. */
+   return circum;
+}
+
+void astInitCircleVtab_(  AstCircleVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitCircleVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Circle.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     void astInitCircleVtab( AstCircleVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Circle vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Circle class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstRegionVtab *region;        /* Pointer to Region component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitRegionVtab( (AstRegionVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsACircle) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstRegionVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->CirclePars = CirclePars;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   mapping = (AstMappingVtab *) vtab;
+   region = (AstRegionVtab *) vtab;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_simplify = mapping->Simplify;
+   mapping->Simplify = Simplify;
+
+   parent_setregfs = region->SetRegFS;
+   region->SetRegFS = SetRegFS;
+
+   parent_resetcache = region->ResetCache;
+   region->ResetCache = ResetCache;
+
+   region->RegPins = RegPins;
+   region->RegTrace = RegTrace;
+   region->RegBaseMesh = RegBaseMesh;
+   region->RegBaseBox = RegBaseBox;
+   region->RegCentre = RegCentre;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "Circle", "Circular or spherical region" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void RegBaseBox( AstRegion *this_region, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     RegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     Circle member function (over-rides the astRegBaseBox protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCircle *this;              /* Pointer to Circle structure */
+   AstFrame *frm;                /* Pointer to base Frame */
+   const char *class;            /* Pointer to class name */
+   int i;                        /* Axis index */
+   int nb;                       /* No. of axes in base Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Circle structure */
+   this = (AstCircle *) this_region;
+
+/* Ensure cached information is available. */
+   Cache( this, status );
+
+/* Get a pointer to the base Frame in the Region, and get the number of
+   axes. */
+   frm = astGetFrame( this_region->frameset, AST__BASE );
+   nb = astGetNaxes( frm );
+
+/* If the Frame is a simple Frame, we can assume plane geometry. */
+   class = astGetClass( frm );
+   if( class && !strcmp( class, "Frame" ) ) {
+      for( i = 0; i < nb; i++ ) {
+         lbnd[ i ] = ( this->centre )[ i ] - this->radius;
+         ubnd[ i ] = ( this->centre )[ i ] + this->radius;
+      }
+
+/* If the Frame is not a simple Frame we cannot assume plane geometry. */
+   } else {
+
+/* The bounding box of the mesh returned by astRegBaseMesh is used as the
+   bounding box of the Circle. These bounds are cached in the Circle
+   structure by astRegBaseMesh. Ensure astRegBaseMesh has been invoked,
+   so that it is safe to use the cached bounding box. */
+      if( !this_region->basemesh ) (void) astAnnul( astRegBaseMesh( this ) );
+
+/* Store the bounding box. */
+      for( i = 0; i < nb; i++ ) {
+         lbnd[ i ] = this->lb[ i ];
+         ubnd[ i ] = this->ub[ i ];
+      }
+   }
+
+/* Free resources. */
+   frm = astAnnul( frm );
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this_region, int *status ){
+/*
+*  Name:
+*     RegBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing a mesh of points on the boundary of a
+*     Region in its base Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     AstPointSet *astRegBaseMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Circle member function (over-rides the astRegBaseMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. The axis values in this PointSet will have
+*     associated accuracies derived from the accuracies which were
+*     supplied when the Region was created.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*/
+
+/* Local Constants: */
+#define NP_EDGE 50                /* No. of points for determining geodesic */
+
+/* Local Variables: */
+   AstBox *box;                   /* Bounding box for this Circle */
+   AstCircle *this;               /* The Circle structure */
+   AstRegion *reg;                /* Copy of supplied Circle */
+   AstFrame *frm;                 /* Base Frame in encapsulated FrameSet */
+   AstPointSet *result;           /* Returned pointer */
+   double **ptr;                  /* Pointers to data */
+   double *p1;                    /* Pointer to array holding a single point */
+   double *p2;                    /* Pointer to array holding a single point */
+   double angle;                  /* Angular position of point */
+   double delta;                  /* Angular separation of points */
+   double dist;                   /* Offset along an axis */
+   double lbx;                    /* Lower x bound of mesh bounding box */
+   double lby;                    /* Lower y bound of mesh bounding box */
+   double p[ 2 ];                 /* Position in 2D Frame */
+   double ubx;                    /* Upper x bound of mesh bounding box */
+   double uby;                    /* Upper y bound of mesh bounding box */
+   int i;                         /* Point index */
+   int j;                         /* Axis index */
+   int naxes;                     /* No. of axes in base Frame */
+   int np;                        /* No. of points in returned PointSet */
+
+/* Initialise */
+   result= NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the Region structure contains a pointer to a PointSet holding
+   a previously created mesh, return it. */
+   if( this_region->basemesh ) {
+      result = astClone( this_region->basemesh );
+
+/* Otherwise, create a new mesh. */
+   } else {
+
+/* Get a pointer to the Circle structure. */
+      this = (AstCircle *) this_region;
+
+/* Get a pointer to the base Frame in the encapsulated FrameSet. */
+      frm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Get the number of axes in the base Frame */
+      naxes = astGetNaxes( frm );
+
+/* Get the requested number of points to put on the mesh. */
+      np = astGetMeshSize( this );
+
+/* Ensure cached information is available. */
+      Cache( (AstCircle *) this, status );
+
+/* First deal with 1-D "circles" (where we ignore MeshSize). */
+      if( naxes == 1 ) {
+
+/* The boundary of a 1-D circle consists of 2 points - the two extreme values.
+   Create a PointSet to hold 2 1-D values, and store the extreme values. */
+         result = astPointSet( 2, 1, "", status );
+         ptr = astGetPoints( result );
+         if( astOK ) {
+            ptr[ 0 ][ 0 ] = ( this->centre )[ 0 ] - this->radius;
+            ptr[ 0 ][ 1 ] = ( this->centre )[ 0 ] + this->radius;
+         }
+
+/* Store the bounding box in the Circle structure. */
+         this->lb[ 0 ] = ptr[ 0 ][ 0 ];
+         this->ub[ 0 ] = ptr[ 0 ][ 1 ];
+
+/* Now deal with 2-D circles. */
+      } else if( naxes == 2 ){
+
+/* Store the angular increment between points. */
+         delta = 2*AST__DPI/np;
+
+/* Create a suitable PointSet to hold the returned positions. */
+         result = astPointSet( np, 2, "", status );
+         ptr = astGetPoints( result );
+         if( astOK ) {
+
+/* Initialise the bounding box of the mesh points. */
+            lbx = DBL_MAX;
+            ubx = -DBL_MAX;
+            lby = DBL_MAX;
+            uby = -DBL_MAX;
+
+/* Loop round each point. */
+            angle = 0.0;
+            for( i = 0; i < np; i++ ) {
+
+/* Work out where the end of the radius vector at this angle is, and
+   store in the returned PointSet. */
+               astOffset2( frm, this->centre, angle, this->radius, p );
+               ptr[ 0 ][ i ] = p[ 0 ];
+               ptr[ 1 ][ i ] = p[ 1 ];
+
+/* Update the bounds of the mesh bounding box. The box is expressed in
+   terms of axis offsets from the centre, in order to avoid problems with
+   boxes that cross RA=0 or RA=12h */
+               if( p[ 0 ] != AST__BAD && p[ 1 ] != AST__BAD ){
+                  dist =  astAxDistance( frm, 1, this->centre[ 0 ], p[ 0 ] );
+                  if( dist < lbx ) {
+                     lbx = dist;
+                  } else if( dist > ubx ) {
+                     ubx = dist;
+                  }
+                  dist =  astAxDistance( frm, 2, this->centre[ 1 ], p[ 1 ] );
+                  if( dist < lby ) {
+                     lby = dist;
+                  } else if( dist > uby ) {
+                     uby = dist;
+                  }
+               }
+
+/* Increment the angular position of the next mesh point. */
+               angle += delta;
+            }
+
+/* Store the bounding box in the Circle structure. */
+            this->lb[ 0 ] = this->centre[ 0 ] + lbx;
+            this->lb[ 1 ] = this->centre[ 1 ] + lby;
+            this->ub[ 0 ] = this->centre[ 0 ] + ubx;
+            this->ub[ 1 ] = this->centre[ 1 ] + uby;
+         }
+
+/* Now deal with circles with more than 2 dimensions. Producing an evenly
+   spread mesh of points over a sphere is a complex task (see e.g.
+   http://www.eso.org/science/healpix/ ). This implementation does not
+   attempt to produce a genuinely even spread. Instead it simply uses the
+   mesh for the bounding box of the sphere, and projects each point on to
+   the surface of the sphere. */
+      } else {
+
+/* Allocate memory to hold an approximation of the circle bounding box. */
+         p1 = astMalloc( sizeof( double )*(size_t) naxes );
+         p2 = astMalloc( sizeof( double )*(size_t) naxes );
+
+/* Get an approximation to the bounding box, and initialise the real
+   bounding box of the mesh points. */
+         if( astOK ) {
+            memcpy( p1, this->centre, sizeof( double )*(size_t) naxes );
+            for( j = 0; j < naxes; j++ ) {
+               p1[ j ] += this->radius;
+               astOffset( frm, this->centre, p1, this->radius, p2 );
+               p1[ j ] = this->centre[ j ];
+               this->ub[ j ] = p2[ j ];
+            }
+         }
+
+/* Create a Box region which just encompasses the circle. */
+         box = astBox( frm, 0, this->centre, this->ub, NULL, "", status );
+
+/* Get a mesh covering this box. */
+         astSetMeshSize( box, np );
+         result = astRegBaseMesh( box );
+         ptr = astGetPoints( result );
+         np = astGetNpoint( result  );
+
+/* Allocate memory for a single point */
+         if( astOK ) {
+
+/* Initialise the real bounding box of the mesh points. */
+            for( j = 0; j < naxes; j++ ) {
+               this->lb[ j ] = DBL_MAX;
+               this->ub[ j ] = -DBL_MAX;
+            }
+
+/* Move each point in this mesh radially so that its distance from the centre
+   equals the radius of this Circle. */
+            for( i = 0; i < np; i++ ) {
+               for( j = 0; j < naxes; j++ ) p1[ j ] = ptr[ j ][ i ];
+               astOffset( frm, this->centre, p1, this->radius, p2 );
+
+               for( j = 0; j < naxes; j++ ) {
+                  ptr[ j ][ i ] = p2[ j ];
+
+/* Update the bounds of the mesh bounding box. */
+                  if( p2[ j ] != AST__BAD ){
+                     if( p2[ j ] < this->lb[ j ] ) {
+                        this->lb[ j ] = p2[ j ];
+                     } else if( p2[ j ] > this->ub[ j ] ) {
+                        this->ub[ j ] = p2[ j ];
+                     }
+                  }
+               }
+            }
+         }
+
+/* Same the returned pointer in the Region structure so that it does not
+   need to be created again next time this function is called. */
+         if( astOK && result ) this_region->basemesh = astClone( result );
+
+/* Free resources. */
+         p1 = astFree( p1 );
+         p2 = astFree( p2 );
+         box = astAnnul( box );
+      }
+
+/* Extend the bounding box if it contains any singularies. The astNormBox
+   requires a Mapping which can be used to test points in the base Frame.
+   Create a copy of the Circle and then set its FrameSet so that the current
+   Frame in the copy is the same as the base Frame in the original. */
+      reg = astCopy( this );
+      astSetRegFS( reg, frm );
+      astSetNegated( reg, 0 );
+
+/* Normalise this box. */
+      astNormBox( frm, this->lb, this->ub, reg );
+
+/* Free resources. */
+      reg = astAnnul( reg );
+      frm = astAnnul( frm );
+   }
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static double *RegCentre( AstRegion *this_region, double *cen, double **ptr,
+                          int index, int ifrm, int *status ){
+/*
+*  Name:
+*     RegCentre
+
+*  Purpose:
+*     Re-centre a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     double *RegCentre( AstRegion *this, double *cen, double **ptr,
+*                        int index, int ifrm, int *status )
+
+*  Class Membership:
+*     Circle member function (over-rides the astRegCentre protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function shifts the centre of the supplied Region to a
+*     specified position, or returns the current centre of the Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     cen
+*        Pointer to an array of axis values, giving the new centre.
+*        Supply a NULL value for this in order to use "ptr" and "index" to
+*        specify the new centre.
+*     ptr
+*        Pointer to an array of pointers, one for each axis in the Region.
+*        Each pointer locates an array of axis values. This is the format
+*        returned by the PointSet method astGetPoints. Only used if "cen"
+*        is NULL.
+*     index
+*        The index of the point within the arrays identified by "ptr" at
+*        which is stored the coords for the new centre position. Only used
+*        if "cen" is NULL.
+*     ifrm
+*        Should be AST__BASE or AST__CURRENT. Indicates whether the centre
+*        position is supplied and returned in the base or current Frame of
+*        the FrameSet encapsulated within "this".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If both "cen" and "ptr" are NULL then a pointer to a newly
+*     allocated dynamic array is returned which contains the centre
+*     coords of the Region. This array should be freed using astFree when
+*     no longer needed. If either of "ptr" or "cen" is not NULL, then a
+*     NULL pointer is returned.
+
+*  Notes:
+*    - Some Region sub-classes do not have a centre. Such classes will report
+*    an AST__INTER error code if this method is called.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;      /* Pointer to base Frame */
+   AstCircle *this;    /* Pointer to Circle structure */
+   double **rptr;      /* Data pointers for Region PointSet */
+   double *bc;         /* Base Frame centre position */
+   double *circum;     /* Base frame circumference position */
+   double *result;     /* Returned pointer */
+   double *tmp;        /* Temporary array pointer */
+   double axval;       /* Axis value */
+   int ic;             /* Coordinate index */
+   int ncb;            /* Number of base frame coordinate values per point */
+   int ncc;            /* Number of current frame coordinate values per point */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Circle structure. */
+   this = (AstCircle *) this_region;
+
+/* Get the number of axis values per point in the base and current Frames. */
+   ncb = astGetNin( this_region->frameset );
+   ncc = astGetNout( this_region->frameset );
+
+/* Ensure cached information is available. */
+   Cache( this, status );
+
+/* If the centre coords are to be returned, return either a copy of the
+   base Frame centre coords, or transform the base Frame centre coords
+   into the current Frame. */
+   if( !ptr && !cen ) {
+      if( ifrm == AST__CURRENT ) {
+         result = astRegTranPoint( this_region, this->centre, 1, 1 );
+      } else {
+         result = astStore( NULL, this->centre, sizeof( double )*ncb );
+      }
+
+/* Otherwise, we store the supplied new centre coords and return a NULL
+   pointer. */
+   } else {
+
+/* Get a pointer to the base Frame in the Region's FrameSet. */
+      frm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Get a pointer to the axis values stored in the Region structure. */
+      rptr = astGetPoints( this_region->points );
+
+/* Check pointers can be used safely */
+      if( astOK ) {
+
+/* If the centre position was supplied in the current Frame, find the
+   corresponding base Frame position... */
+         if( ifrm == AST__CURRENT ) {
+            if( cen ) {
+               bc = astRegTranPoint( this_region, cen, 1, 0 );
+            } else {
+               tmp = astMalloc( sizeof( double)*(size_t)ncc );
+               if( astOK ) {
+                  for( ic = 0; ic < ncc; ic++ ) tmp[ ic ] = ptr[ ic ][ index ];
+               }
+               bc = astRegTranPoint( this_region, tmp, 1, 0 );
+               tmp = astFree( tmp );
+            }
+
+/* Replace any bad centre values with their current values. */
+            for( ic = 0; ic < ncb; ic++ ) {
+               if( bc[ ic ] ==  AST__BAD ) bc[ ic ] = this->centre[ ic ];
+            }
+
+/* ... and change the coords in the parent Region structure and the cached
+   coords in the Circle structure. */
+            circum = CircumPoint( frm, ncb, bc, this->radius, status );
+            if( circum ) {
+               for( ic = 0; ic < ncb; ic++ ) {
+                  rptr[ ic ][ 0 ] = bc[ ic ];
+                  rptr[ ic ][ 1 ] = circum[ ic ];
+                  this->centre[ ic ] = bc[ ic ];
+               }
+            }
+
+/* Free resources */
+            circum = astFree( circum );
+            bc = astFree( bc );
+
+/* If the centre position was supplied in the base Frame, use the
+   supplied "cen" or "ptr" pointer directly to change the coords in the
+   parent Region structure and the cached coords in the Circle structure. */
+         } else {
+            for( ic = 0; ic < ncb; ic++ ) {
+               axval = cen ? cen[ ic ] : ptr[ ic ][ index ];
+               if( axval != AST__BAD ) this->centre[ ic ] = axval;
+            }
+
+            circum = CircumPoint( frm, ncb, this->centre, this->radius,
+                                  status );
+            if( circum ) {
+               for( ic = 0; ic < ncb; ic++ ) {
+                  rptr[ ic ][ 0 ] = this->centre[ ic ];
+                  rptr[ ic ][ 1 ] = circum[ ic ];
+               }
+               circum = astFree( circum );
+            }
+         }
+      }
+
+/* Free resources */
+      frm = astAnnul( frm );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int RegPins( AstRegion *this_region, AstPointSet *pset, AstRegion *unc,
+                    int **mask, int *status ){
+/*
+*  Name:
+*     RegPins
+
+*  Purpose:
+*     Check if a set of points fall on the boundary of a given Circle.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+*                  int **mask, int *status ){
+
+*  Class Membership:
+*     Circle member function (over-rides the astRegPins protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied set of
+*     points all fall on the boundary of the given Circle.
+*
+*     Some tolerance is allowed, as specified by the uncertainty Region
+*     stored in the supplied Circle "this", and the supplied uncertainty
+*     Region "unc" which describes the uncertainty of the supplied points.
+
+*  Parameters:
+*     this
+*        Pointer to the Circle.
+*     pset
+*        Pointer to the PointSet. The points are assumed to refer to the
+*        base Frame of the FrameSet encapsulated by "this".
+*     unc
+*        Pointer to a Region representing the uncertainties in the points
+*        given by "pset". The Region is assumed to represent the base Frame
+*        of the FrameSet encapsulated by "this". Zero uncertainity is assumed
+*        if NULL is supplied.
+*     mask
+*        Pointer to location at which to return a pointer to a newly
+*        allocated dynamic array of ints. The number of elements in this
+*        array is equal to the value of the Npoint attribute of "pset".
+*        Each element in the returned array is set to 1 if the
+*        corresponding position in "pset" is on the boundary of the Region
+*        and is set to zero otherwise. A NULL value may be supplied
+*        in which case no array is created. If created, the array should
+*        be freed using astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the points all fall on the boundary of the given
+*     Region, to within the tolerance specified. Zero otherwise.
+
+*/
+
+/* Local variables: */
+   AstCircle *large_circle;     /* Circle slightly larger than "this" */
+   AstCircle *small_circle;     /* Circle slightly smaller than "this" */
+   AstCircle *this;             /* Pointer to the Circle structure. */
+   AstFrame *frm;               /* Base Frame in supplied Circle */
+   AstPointSet *ps1;            /* Points masked by larger Circle */
+   AstPointSet *ps2;            /* Points masked by larger and smaller Circlees */
+   AstRegion *tunc;             /* Uncertainity Region from "this" */
+   double **ptr;                /* Pointer to axis values in "ps2" */
+   double *lbnd_tunc;           /* Lower bounds of "this" uncertainty Region */
+   double *lbnd_unc;            /* Lower bounds of supplied uncertainty Region */
+   double *p;                   /* Pointer to next axis value */
+   double *safe;                /* An interior point in "this" */
+   double *ubnd_tunc;           /* Upper bounds of "this" uncertainty Region */
+   double *ubnd_unc;            /* Upper bounds of supplied uncertainty Region */
+   double drad;                 /* Radius increment corresponding to border width */
+   double l1;                   /* Length of bounding box diagonal */
+   double l2;                   /* Length of bounding box diagonal */
+   double rad;                  /* Radius of Circle */
+   int i;                       /* Axis index */
+   int j;                       /* Point index */
+   int nc;                      /* No. of axes in Circle base frame */
+   int np;                      /* No. of supplied points */
+   int result;                  /* Returned flag */
+
+/* Initialise */
+   result = 0;
+   if( mask ) *mask = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a pointer to the Circle structure. */
+   this = (AstCircle *) this_region;
+
+/* Get the number of base Frame axes in the Circle, and check the supplied
+   PointSet has the same number of axis values per point. */
+   frm = astGetFrame( this_region->frameset, AST__BASE );
+   nc = astGetNaxes( frm );
+   if( astGetNcoord( pset ) != nc && astOK ) {
+      astError( AST__INTER, "astRegPins(%s): Illegal number of axis "
+                "values per point (%d) in the supplied PointSet - should be "
+                "%d (internal AST programming error).", status, astGetClass( this ),
+                astGetNcoord( pset ), nc );
+   }
+
+/* Get the number of axes in the uncertainty Region and check it is the
+   same as above. */
+   if( unc && astGetNaxes( unc ) != nc && astOK ) {
+      astError( AST__INTER, "astRegPins(%s): Illegal number of axes (%d) "
+                "in the supplied uncertainty Region - should be "
+                "%d (internal AST programming error).", status, astGetClass( this ),
+                astGetNaxes( unc ), nc );
+   }
+
+/* Get the centre of the region in the base Frame. We use this as a "safe"
+   interior point within the region. */
+   safe = astRegCentre( this, NULL, NULL, 0, AST__BASE );
+
+/* We now find the maximum distance on each axis that a point can be from the
+   boundary of the Circle for it still to be considered to be on the boundary.
+   First get the Region which defines the uncertainty within the Circle being
+   checked (in its base Frame), re-centre it on the interior point found
+   above (to avoid problems if the uncertainty region straddles a
+   discontinuity), and get its bounding box. */
+   tunc = astGetUncFrm( this, AST__BASE );
+   if( safe ) astRegCentre( tunc, safe, NULL, 0, AST__CURRENT );
+   lbnd_tunc = astMalloc( sizeof( double )*(size_t) nc );
+   ubnd_tunc = astMalloc( sizeof( double )*(size_t) nc );
+   astGetRegionBounds( tunc, lbnd_tunc, ubnd_tunc );
+
+/* Find the geodesic length within the base Frame of "this" of the diagonal of
+   the bounding box. */
+   l1 = astDistance( frm, lbnd_tunc, ubnd_tunc );
+
+/* Also get the Region which defines the uncertainty of the supplied
+   points and get its bounding box. First re-centre the uncertainty at the
+   interior position to avoid problems from uncertainties that straddle a
+   discontinuity. */
+   if( unc ) {
+      if( safe ) astRegCentre( unc, safe, NULL, 0, AST__CURRENT );
+      lbnd_unc = astMalloc( sizeof( double )*(size_t) nc );
+      ubnd_unc = astMalloc( sizeof( double )*(size_t) nc );
+      astGetRegionBounds( unc, lbnd_unc, ubnd_unc );
+
+/* Find the geodesic length of the diagonal of this bounding box. */
+      l2 = astDistance( frm, lbnd_unc, ubnd_unc );
+
+/* Use a zero sized box "unc" if no box was supplied. */
+   } else {
+      lbnd_unc = NULL;
+      ubnd_unc = NULL;
+      l2 = 0.0;
+   }
+
+/* Ensure cached information is available. */
+   Cache( this, status );
+
+/* The required border width is half of the total diagonal of the two bounding
+   boxes. */
+   if( astOK ) {
+      drad = 0.5*( l1 + l2 );
+
+/* Create two new Circle, one of which is larger than "this" by the amount
+   found above, and the other of which is smaller than "this" by the amount
+   found above. */
+      rad = this->radius + 0.5*drad;
+      large_circle = astCircle( frm, 1, this->centre, &rad, NULL, "", status );
+      rad = this->radius - 0.5*drad;
+      small_circle = astCircle( frm, 1, this->centre, &rad, NULL, "", status );
+
+/* Negate the smaller region.*/
+      astNegate( small_circle );
+
+/* Points are on the boundary of "this" if they are inside both the large
+   Circle and the negated small Circle. First transform the supplied PointSet
+   using the large Circle, then transform them using the negated smaller
+   Circle. */
+      ps1 = astTransform( large_circle, pset, 1, NULL );
+      ps2 = astTransform( small_circle, ps1, 1, NULL );
+
+/* Get a point to the resulting axis values, and the number of axis
+   values per axis. */
+      ptr = astGetPoints( ps2 );
+      np = astGetNpoint( ps2 );
+
+/* If a mask array is to be returned, create one. */
+      if( mask ) {
+         *mask = astMalloc( sizeof(int)*(size_t) np );
+
+/* Check all the resulting points, setting mask values for all of them. */
+         if( astOK ) {
+
+/* Initialise the mask elements on the basis of the first axis values */
+            result = 1;
+            p = ptr[ 0 ];
+            for( j = 0; j < np; j++ ) {
+               if( *(p++) == AST__BAD ) {
+                  result = 0;
+                  (*mask)[ j ] = 0;
+               } else {
+                  (*mask)[ j ] = 1;
+               }
+            }
+
+/* Now check for bad values on other axes. */
+            for( i = 1; i < nc; i++ ) {
+               p = ptr[ i ];
+               for( j = 0; j < np; j++ ) {
+                  if( *(p++) == AST__BAD ) {
+                     result = 0;
+                     (*mask)[ j ] = 0;
+                  }
+               }
+            }
+         }
+
+/* If no output mask is to be made, we can break out of the check as soon
+   as the first bad value is found. */
+      } else if( astOK ) {
+         result = 1;
+         for( i = 0; i < nc && result; i++ ) {
+            p = ptr[ i ];
+            for( j = 0; j < np; j++ ) {
+               if( *(p++) == AST__BAD ) {
+                  result = 0;
+                  break;
+               }
+            }
+         }
+      }
+
+/* Free resources. */
+      large_circle = astAnnul( large_circle );
+      small_circle = astAnnul( small_circle );
+      ps1 = astAnnul( ps1 );
+      ps2 = astAnnul( ps2 );
+   }
+
+   tunc = astAnnul( tunc );
+   frm = astAnnul( frm );
+   lbnd_tunc = astFree( lbnd_tunc );
+   ubnd_tunc = astFree( ubnd_tunc );
+   if( unc ) lbnd_unc = astFree( lbnd_unc );
+   if( unc ) ubnd_unc = astFree( ubnd_unc );
+   safe = astFree( safe );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) {
+      result = 0;
+      if( mask ) *mask = astAnnul( *mask );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int RegTrace( AstRegion *this_region, int n, double *dist, double **ptr,
+                     int *status ){
+/*
+*+
+*  Name:
+*     RegTrace
+
+*  Purpose:
+*     Return requested positions on the boundary of a 2D Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     int astTraceRegion( AstRegion *this, int n, double *dist, double **ptr );
+
+*  Class Membership:
+*     Circle member function (overrides the astTraceRegion method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function returns positions on the boundary of the supplied
+*     Region, if possible. The required positions are indicated by a
+*     supplied list of scalar parameter values in the range zero to one.
+*     Zero corresponds to some arbitrary starting point on the boundary,
+*     and one corresponds to the end (which for a closed region will be
+*     the same place as the start).
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     n
+*        The number of positions to return. If this is zero, the function
+*        returns without action (but the returned function value still
+*        indicates if the method is supported or not).
+*     dist
+*        Pointer to an array of "n" scalar parameter values in the range
+*        0 to 1.0.
+*     ptr
+*        A pointer to an array of pointers. The number of elements in
+*        this array should equal tthe number of axes in the Frame spanned
+*        by the Region. Each element of the array should be a pointer to
+*        an array of "n" doubles, in which to return the "n" values for
+*        the corresponding axis. The contents of the arrays are unchanged
+*        if the supplied Region belongs to a class that does not
+*        implement this method.
+
+*  Returned Value:
+*     Non-zero if the astTraceRegion method is implemented by the class
+*     of Region supplied, and zero if not.
+
+*-
+*/
+
+/* Local Variables; */
+   AstCircle *this;
+   AstFrame *frm;
+   AstMapping *map;
+   AstPointSet *bpset;
+   AstPointSet *cpset;
+   double **bptr;
+   double angle;
+   double p[ 2 ];
+   int i;
+   int ncur;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check inherited status. */
+   if( ! astOK ) return result;
+
+/* Get a pointer to the base Frame in the encapsulated FrameSet. */
+   frm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Check it is 2-dimensional. */
+   if( astGetNaxes( frm ) == 2 ) result = 1;
+
+/* Check we have some points to find. */
+   if( result && n > 0 ) {
+
+/* Get a pointer to the Circle structure. */
+      this = (AstCircle *) this_region;
+
+/* Ensure cached information is available. */
+      Cache( this, status );
+
+/* We first determine the required positions in the base Frame of the
+   Region, and then transform them into the current Frame. Get the
+   base->current Mapping, and the number of current Frame axes. */
+      map = astGetMapping( this_region->frameset, AST__BASE, AST__CURRENT );
+
+/* If it's a UnitMap we do not need to do the transformation, so put the
+   base Frame positions directly into the supplied arrays. */
+      if( astIsAUnitMap( map ) ) {
+         bpset = NULL;
+         bptr = ptr;
+         ncur = 2;
+
+/* Otherwise, create a PointSet to hold the base Frame positions. */
+      } else {
+         bpset = astPointSet( n, 2, " ", status );
+         bptr = astGetPoints( bpset );
+         ncur = astGetNout( map );
+      }
+
+/* Check the pointers can be used safely. */
+      if( astOK ) {
+
+/* Loop round each point. Get the angle around the circle, and offset
+   along that angle to find the point that is one radius away from the
+   centre. Copy the results into the required arrays. */
+         for( i = 0; i < n; i++ ) {
+            angle = dist[ i ]*2*AST__DPI;
+            astOffset2( frm, this->centre, angle, this->radius, p );
+            bptr[ 0 ][ i ] = p[ 0 ];
+            bptr[ 1 ][ i ] = p[ 1 ];
+         }
+
+      }
+
+/* If required, transform the base frame positions into the current
+   Frame, storing them in the supplied array. Then free resources. */
+      if( bpset ) {
+         cpset = astPointSet( n, ncur, " ", status );
+         astSetPoints( cpset, ptr );
+
+         (void) astTransform( map, bpset, 1, cpset );
+
+         cpset = astAnnul( cpset );
+         bpset = astAnnul( bpset );
+      }
+
+/* Free remaining resources. */
+      map = astAnnul( map );
+   }
+   frm = astAnnul( frm );
+
+/* Return the result. */
+   return result;
+}
+
+static void ResetCache( AstRegion *this, int *status ){
+/*
+*  Name:
+*     ResetCache
+
+*  Purpose:
+*     Clear cached information within the supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     void ResetCache( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Region member function (overrides the astResetCache method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function clears cached information from the supplied Region
+*     structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   if( this ) {
+      ( (AstCircle *) this )->stale = 1;
+      (*parent_resetcache)( this, status );
+   }
+}
+
+static void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status ) {
+/*
+*  Name:
+*     SetRegFS
+
+*  Purpose:
+*     Stores a new FrameSet in a Region
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     Circle method (over-rides the astSetRegFS method inherited from
+*     the Region class).
+
+*  Description:
+*     This function creates a new FrameSet and stores it in the supplied
+*     Region. The new FrameSet contains two copies of the supplied
+*     Frame, connected by a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     frm
+*        The Frame to use.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent method to store the FrameSet in the parent Region
+   structure. */
+   (* parent_setregfs)( this_region, frm, status );
+
+/* Re-calculate cached information. */
+   astResetCache( this_region );
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify the Mapping represented by a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Circle method (over-rides the astSimplify method inherited
+*     from the Region class).
+
+*  Description:
+*     This function invokes the parent Region Simplify method, and then
+*     performs any further region-specific simplification.
+*
+*     If the Mapping from base to current Frame is not a UnitMap, this
+*     will include attempting to fit a new Region to the boundary defined
+*     in the current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the simplified Region. A cloned pointer to the
+*     supplied Region will be returned if no simplication could be
+*     performed.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map;           /* Base -> current Mapping */
+   AstMapping *result;        /* Result pointer to return */
+   AstPointSet *mesh;         /* Mesh of current Frame positions */
+   AstPointSet *ps2;          /* Circle PointSet in current Frame */
+   AstRegion *new;            /* Pointer to simplified Region */
+   AstRegion *newreg;         /* Equivalent circle or ellipse */
+   AstRegion *this;           /* Pointer to supplied Region structure */
+   AstRegion *unc;            /* Pointer to uncertainty Region */
+   double **ptr2;             /* Pointer axis values in "ps2" */
+   double *cen;               /* Pointer to array holding new centre coords */
+   int ic;                    /* Axis index */
+   int nc;                    /* No. of axis values per point */
+   int ok;                    /* Was the new centre found OK? */
+   int simpler;               /* Has some simplication taken place? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the supplied Region structure. */
+   this = (AstRegion *) this_mapping;
+
+/* Invoke the parent Simplify method inherited from the Region class. This
+   will simplify the encapsulated FrameSet and uncertainty Region. */
+   new = (AstRegion *) (*parent_simplify)( this_mapping, status );
+
+/* Note if any simplification took place. This is assumed to be the case
+   if the pointer returned by the above call is different to the supplied
+   pointer. */
+   simpler = ( new != this );
+
+/* If the Mapping from base to current Frame is not a UnitMap, we attempt
+   to simplify the Circle by re-defining it within its current Frame.
+   Transforming the Circle from its base to its current Frame may result in
+   the region no longer being a circle. We test this by transforming a set of
+   bounds on the Circle boundary. */
+   map = astGetMapping( new->frameset, AST__BASE, AST__CURRENT );
+   if( !astIsAUnitMap( map ) ){
+
+/* Get a mesh of points covering the Circle in its current Frame. */
+      mesh = astRegMesh( new );
+
+/* Get the Region describing the positional uncertainty within the Circle in
+   its current Frame. */
+      unc = astGetUncFrm( new, AST__CURRENT );
+
+/* Transform the PointSet holding the circle centre into the current
+   Frame, and copy the axis values into a new array. */
+      ps2 = astRegTransform( this, this->points, 1, NULL, NULL );
+      nc = astGetNcoord( ps2 );
+      cen = astMalloc( sizeof( double )*(size_t) nc );
+      ptr2 = astGetPoints( ps2 );
+      if( astOK ) {
+
+         ok = 1;
+         for( ic = 0; ic < nc; ic++ ) {
+            cen[ ic ] = ptr2[ ic ][ 0 ];
+            if( cen[ ic ] == AST__BAD ) ok = 0;
+         }
+
+/* Find the best fitting Circle (defined in the current Frame) through these
+   points */
+         newreg = ok ? astBestCircle( mesh, cen, unc ) : NULL;
+
+/* See if all points within this mesh fall on the boundary of the best
+   fitting Circle, to within the uncertainty of the Region. */
+         if( newreg && astRegPins( newreg, mesh, NULL, NULL ) ) {
+
+/* If so, use the new Circle in place of the original. */
+            (void) astAnnul( new );
+            new = astClone( newreg );
+
+/* Otherwise, if the region is 2-d we see if an Ellipse can represent the
+   mesh. */
+         } else if( ok && nc == 2 ){
+
+/* Find the best fitting Ellipse (defined in the current Frame) through these
+   points */
+            if( newreg ) (void) astAnnul( newreg );
+            newreg = astBestEllipse( mesh, cen, unc );
+
+/* See if all points within this mesh fall on the boundary of the best
+   fitting Ellipse, to within the uncertainty of the Region. */
+            if( newreg && astRegPins( newreg, mesh, NULL, NULL ) ) {
+
+/* If so, use the new Ellipse in place of the original. */
+               (void) astAnnul( new );
+               new = astClone( newreg );
+               simpler = 1;
+            }
+         }
+
+/* Free resources. */
+         if( newreg ) newreg = astAnnul( newreg );
+      }
+
+      ps2 = astAnnul( ps2 );
+      cen = astFree( cen );
+      mesh = astAnnul( mesh );
+      unc = astAnnul( unc );
+   }
+   map = astAnnul( map );
+
+/* If any simplification could be performed, copy Region attributes from
+   the supplied Region to the returned Region, and return a pointer to it.
+   If the supplied Region had no uncertainty, ensure the returned Region
+   has no uncertainty. Otherwise, return a clone of the supplied pointer. */
+   if( simpler ){
+      astRegOverlay( new, this, 1 );
+      result = (AstMapping *) new;
+
+   } else {
+      new = astAnnul( new );
+      result = astClone( this );
+   }
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a Circle to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     Circle member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a Circle and a set of points encapsulated in a
+*     PointSet and transforms the points by setting axis values to
+*     AST__BAD for all points which are outside the region. Points inside
+*     the region are copied unchanged from input to output.
+
+*  Parameters:
+*     this
+*        Pointer to the Circle.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  The forward and inverse transformations are identical for a
+*     Region.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of axes in the Frame represented by the Circle.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstCircle *this;            /* Pointer to Circle */
+   AstFrame *frm;                /* Pointer to base Frame in FrameSet */
+   AstPointSet *pset_tmp;        /* Pointer to PointSet holding base Frame positions*/
+   AstPointSet *result;          /* Pointer to output PointSet */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double **ptr_tmp;             /* Pointer to base Frame coordinate data */
+   double *work;                 /* Pointer to array holding single base point */
+   double d;                     /* Base-Frame distance from centre to point */
+   int closed;                   /* Is the boundary part of the Region? */
+   int coord;                    /* Zero-based index for coordinates */
+   int inside;                   /* Is the point inside the Region? */
+   int ncoord_out;               /* No. of coordinates per output point */
+   int ncoord_tmp;               /* No. of coordinates per base Frame point */
+   int neg;                      /* Has the Region been negated? */
+   int npoint;                   /* No. of points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the Circle structure. */
+   this = (AstCircle *) this_mapping;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Region class. This function validates
+   all arguments and generates an output PointSet if necessary,
+   containing a copy of the input PointSet. */
+   result = (*parent_transform)( this_mapping, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* First use the encapsulated FrameSet to transform the supplied positions
+   from the current Frame in the encapsulated FrameSet (the Frame
+   represented by the Region), to the base Frame (the Frame in which the
+   Region is defined). This call also returns a pointer to the base Frame
+   of the encapsulated FrameSet. Note, the returned pointer may be a
+   clone of the "in" pointer, and so we must be carefull not to modify the
+   contents of the returned PointSet. */
+   pset_tmp = astRegTransform( this, in, 0, NULL, &frm );
+
+/* Determine the numbers of points and coordinates per point from the base
+   Frame PointSet and obtain pointers for accessing the base Frame and output
+   coordinate values. */
+   npoint = astGetNpoint( pset_tmp );
+   ncoord_tmp = astGetNcoord( pset_tmp );
+   ptr_tmp = astGetPoints( pset_tmp );
+   ncoord_out = astGetNcoord( result );
+   ptr_out = astGetPoints( result );
+
+/* Get work space for one base Frame position */
+   work = astMalloc( sizeof( double )*(size_t) ncoord_tmp );
+
+/* See if the boundary is part of the Region. */
+   closed = astGetClosed( this );
+
+/* See if the Region has been negated. */
+   neg = astGetNegated( this );
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+
+/* Ensure cached information is available. */
+      Cache( this, status );
+
+/* Loop round each point */
+      for ( point = 0; point < npoint; point++ ) {
+
+/* Copy the base Frame position into a work array. */
+         for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+            work[ coord ] = ptr_tmp[ coord ][ point ];
+         }
+
+/* Find the geodesic distance from the centre of the Circle in the base
+   Frame. */
+         d = astDistance( frm, this->centre, work );
+
+/* Now consider whether this radius value puts the point in or out of the
+   Circle. */
+         if( d != AST__BAD ){
+            if( neg ) {
+               if( closed ) {
+                  inside = ( d >= this->radius );
+               } else {
+                  inside = ( d > this->radius );
+               }
+            } else {
+               if( closed ) {
+                  inside = ( d <= this->radius );
+               } else {
+                  inside = ( d < this->radius );
+               }
+            }
+         } else {
+            inside = 0;
+         }
+
+/* If the point is outside, store bad output values. */
+         if( !inside ) {
+            for ( coord = 0; coord < ncoord_out; coord++ ) {
+               ptr_out[ coord ][ point ] = AST__BAD;
+            }
+         }
+      }
+   }
+
+/* Free resources */
+   work = astFree( work );
+   pset_tmp = astAnnul( pset_tmp );
+   frm = astAnnul( frm );
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Region objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Region objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstCircle *in;             /* Pointer to input Circle */
+   AstCircle *out;            /* Pointer to output Circle */
+   int nax;                   /* Number of base Frame axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Circles. */
+   in = (AstCircle *) objin;
+   out = (AstCircle *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output Circle. */
+   out->centre = NULL;
+   out->lb = NULL;
+   out->ub = NULL;
+
+/* Copy dynamic memory contents */
+   nax = astGetNin( ((AstRegion *) in)->frameset );
+   out->centre = astStore( NULL, in->centre,
+                           sizeof( double )*(size_t)nax );
+   out->lb = astStore( NULL, in->lb, sizeof( double )*(size_t)nax );
+   out->ub = astStore( NULL, in->ub, sizeof( double )*(size_t)nax );
+}
+
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Circle objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Circle objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstCircle *this;                 /* Pointer to Circle */
+
+/* Obtain a pointer to the Circle structure. */
+   this = (AstCircle *) obj;
+
+/* Annul all resources. */
+   this->centre = astFree( this->centre );
+   this->lb = astFree( this->lb );
+   this->ub = astFree( this->ub );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Circle objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Circle class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Circle whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstCircle *this;                 /* Pointer to the Circle structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Circle structure. */
+   this = (AstCircle *) this_object;
+
+/* Write out values representing the instance variables for the
+   Circle class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* There are no values to write, so return without further action. */
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsACircle and astCheckCircle functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Circle,Region)
+astMAKE_CHECK(Circle)
+
+AstCircle *astCircle_( void *frame_void, int form, const double centre[],
+                       const double point[], AstRegion *unc,
+                       const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astCircle
+f     AST_CIRCLE
+
+*  Purpose:
+*     Create a Circle.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "circle.h"
+c     AstCircle *astCircle( AstFrame *frame, int form, const double centre[],
+c                           const double point[], AstRegion *unc,
+c                           const char *options, ... )
+f     RESULT = AST_CIRCLE( FRAME, FORM, CENTRE, POINT, UNC, OPTIONS, STATUS )
+
+*  Class Membership:
+*     Circle constructor.
+
+*  Description:
+*     This function creates a new Circle and optionally initialises its
+*     attributes.
+*
+*     A Circle is a Region which represents a circle or sphere within the
+*     supplied Frame.
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        A pointer to the Frame in which the region is defined. A deep
+*        copy is taken of the supplied Frame. This means that any
+*        subsequent changes made to the Frame using the supplied pointer
+*        will have no effect the Region.
+c     form
+f     FORM = INTEGER (Given)
+*        Indicates how the circle is described by the remaining parameters.
+*        A value of zero indicates that the circle is specified by a
+*        centre position and a position on the circumference. A value of one
+*        indicates that the circle is specified by a centre position and a
+*        scalar radius.
+c     centre
+f     CENTRE( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates at the centre of
+*        the circle or sphere.
+c     point
+f     POINT( * ) = DOUBLE PRECISION (Given)
+c        If "form"
+f        If FORM
+*        is zero, then this array should have one element for each Frame
+*        axis (Naxes attribute), and should be supplied holding the
+*        coordinates at a point on the circumference of the circle or sphere.
+c        If "form"
+f        If FORM
+*        is one, then this array should have one element only which should
+*        be supplied holding the scalar radius of the circle or sphere,
+*        as a geodesic distance within the Frame.
+c     unc
+f     UNC = INTEGER (Given)
+*        An optional pointer to an existing Region which specifies the
+*        uncertainties associated with the boundary of the Circle being created.
+*        The uncertainty in any point on the boundary of the Circle is found by
+*        shifting the supplied "uncertainty" Region so that it is centred at
+*        the boundary point being considered. The area covered by the
+*        shifted uncertainty Region then represents the uncertainty in the
+*        boundary position. The uncertainty is assumed to be the same for
+*        all points.
+*
+*        If supplied, the uncertainty Region must be of a class for which
+*        all instances are centro-symetric (e.g. Box, Circle, Ellipse, etc.)
+*        or be a Prism containing centro-symetric component Regions. A deep
+*        copy of the supplied Region will be taken, so subsequent changes to
+*        the uncertainty Region using the supplied pointer will have no
+*        effect on the created Circle. Alternatively,
+f        a null Object pointer (AST__NULL)
+c        a NULL Object pointer
+*        may be supplied, in which case a default uncertainty is used
+*        equivalent to a box 1.0E-6 of the size of the Circle being created.
+*
+*        The uncertainty Region has two uses: 1) when the
+c        astOverlap
+f        AST_OVERLAP
+*        function compares two Regions for equality the uncertainty
+*        Region is used to determine the tolerance on the comparison, and 2)
+*        when a Region is mapped into a different coordinate system and
+*        subsequently simplified (using
+c        astSimplify),
+f        AST_SIMPLIFY),
+*        the uncertainties are used to determine if the transformed boundary
+*        can be accurately represented by a specific shape of Region.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Circle. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Circle. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astCircle()
+f     AST_CIRCLE = INTEGER
+*        A pointer to the new Circle.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstCircle *new;               /* Pointer to new Circle */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the supplied Frame structure. */
+   frame = astCheckFrame( frame_void );
+
+/* Initialise the Circle, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitCircle( NULL, sizeof( AstCircle ), !class_init, &class_vtab,
+                     "Circle", frame, form, centre, point, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Circle's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Circle. */
+   return new;
+}
+
+AstCircle *astCircleId_( void *frame_void, int form, const double centre[],
+                         const double point[], void *unc_void,
+                         const char *options, ... ) {
+/*
+*  Name:
+*     astCircleId_
+
+*  Purpose:
+*     Create a Circle.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     AstCircle *astCircleId_( AstFrame *frame, int form, const double centre[],
+*                              const double point[], AstRegion *unc,
+*                              const char *options, ... )
+
+*  Class Membership:
+*     Circle constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astCircle constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astCircle_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astCircle_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astCircle_.
+
+*  Returned Value:
+*     The ID value associated with the new Circle.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstCircle *new;               /* Pointer to new Circle */
+   AstRegion *unc;               /* Pointer to Region structure */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Frame pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Frame. */
+   frame = astVerifyFrame( astMakePointer( frame_void ) );
+
+/* Obtain a Region pointer from the supplied "unc" ID and validate the
+   pointer to ensure it identifies a valid Region . */
+   unc = unc_void ? astCheckRegion( astMakePointer( unc_void ) ) : NULL;
+
+/* Initialise the Circle, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitCircle( NULL, sizeof( AstCircle ), !class_init, &class_vtab,
+                        "Circle", frame, form, centre, point, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Circle's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new Circle. */
+   return astMakeId( new );
+}
+
+AstCircle *astInitCircle_( void *mem, size_t size, int init, AstCircleVtab *vtab,
+                           const char *name, AstFrame *frame, int form,
+                           const double centre[], const double point[],
+                           AstRegion *unc, int *status ) {
+/*
+*+
+*  Name:
+*     astInitCircle
+
+*  Purpose:
+*     Initialise a Circle.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     AstCircle *astInitCircle_( void *mem, size_t size, int init, AstCircleVtab *vtab,
+*                                const char *name, AstFrame *frame, int form,
+*                                const double centre[], const double point[],
+*                                AstRegion *unc )
+
+*  Class Membership:
+*     Circle initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Circle object. It allocates memory (if necessary) to accommodate
+*     the Circle plus any additional data associated with the derived class.
+*     It then initialises a Circle structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a Circle at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Circle is to be initialised.
+*        This must be of sufficient size to accommodate the Circle data
+*        (sizeof(Circle)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Circle (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Circle
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Circle's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Circle.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     frame
+*        A pointer to the Frame in which the region is defined.
+*     form
+*        Indicates how the "point" parameter should be interpreted.
+*        Should be either 0 or 1.
+*     centre
+*        An array of double, with one element for each Frame axis (Naxes
+*        attribute) containing the coordinates of the circle centre.
+*     point
+*        If "form" is zero, this should be an array of double, with one
+*        element for each Frame axis (Naxes attribute) containing the
+*        coordinates at any point on the circumference. If "form" is one,
+*        this should be the address of a double containing the scalar
+*        radius of the circle or sphere.
+*     unc
+*        A pointer to a Region which specifies the uncertainty in the
+*        supplied positions (all points on the boundary of the new Circle
+*        being initialised are assumed to have the same uncertainty). A NULL
+*        pointer can be supplied, in which case default uncertainties equal to
+*        1.0E-6 of the dimensions of the new Circle's bounding box are used.
+*        If an uncertainty Region is supplied, it must be either a Box, a
+*        Circle or an Ellipse, and its encapsulated Frame must be related
+*        to the Frame supplied for parameter "frame" (i.e. astConvert
+*        should be able to find a Mapping between them). Two positions
+*        the "frame" Frame are considered to be co-incident if their
+*        uncertainty Regions overlap. The centre of the supplied
+*        uncertainty Region is immaterial since it will be re-centred on the
+*        point being tested before use. A deep copy is taken of the supplied
+*        Region.
+
+*  Returned Value:
+*     A pointer to the new Circle.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstCircle *new;           /* Pointer to new Circle */
+   AstPointSet *pset;        /* PointSet to pass to Region initialiser */
+   const double *circum;     /* Pointer to circumference position */
+   double **ptr;             /* Pointer to coords data in pset */
+   int i;                    /* Axis index */
+   int nc;                   /* No. of axes */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitCircleVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the supplied value for "form" is legal. */
+   if( form != 0 && form != 1 && astOK ) {
+      astError( AST__BADIN, "astInitCircle(%s): The value supplied for "
+                "parameter \"form\" (%d) is illegal - it should be 0 or 1 "
+                "(programming error).", status, name, form );
+   }
+
+/* Get the number of axis values required for each position. */
+   nc = astGetNaxes( frame );
+
+/* If the circle radius has been supplied, find a point on the circle
+   circumference. */
+   if( form == 1 ) {
+      circum = CircumPoint( frame, nc, centre, *point, status );
+
+/* Otherwise, use the supplied circumference point. */
+   } else {
+      circum = point;
+   }
+
+/* Create a PointSet to hold the centre position, and a point on the
+   circumference, and get points to the data arrays. */
+   pset = astPointSet( 2, nc, "", status );
+   ptr = astGetPoints( pset );
+
+/* Copy the centre and circumference coordinates into the PointSet, checking
+   that no bad values have been supplied. */
+   for( i = 0; astOK && i < nc; i++ ) {
+      if( centre[ i ] == AST__BAD ) {
+         astError( AST__BADIN, "astInitCircle(%s): The value of axis %d is "
+                   "undefined at the circle centre.", status, name, i + 1 );
+      }
+      if( astOK && circum[ i ] == AST__BAD ) {
+         astError( AST__BADIN, "astInitCircle(%s): The value of axis %d is "
+                   "undefined on the circumference of the circle.", status, name, i + 1 );
+      }
+      ptr[ i ][ 0 ] = centre[ i ];
+      ptr[ i ][ 1 ] = circum[ i ];
+   }
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Initialise a Region structure (the parent class) as the first component
+   within the Circle structure, allocating memory if necessary. */
+      new = (AstCircle *) astInitRegion( mem, size, 0, (AstRegionVtab *) vtab,
+                                         name, frame, pset, unc );
+
+      if ( astOK ) {
+
+/* Initialise the Circle data. */
+/* ------------------------ */
+         new->stale = 1;
+         new->centre = NULL;
+         new->lb = NULL;
+         new->ub = NULL;
+         Cache( new, status );
+
+/* If an error occurred, clean up by deleting the new Circle. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Free resources. */
+   pset = astAnnul( pset );
+   if( form == 1 ) circum = astFree( (void *) circum );
+
+/* Return a pointer to the new Circle. */
+   return new;
+}
+
+AstCircle *astLoadCircle_( void *mem, size_t size, AstCircleVtab *vtab,
+                           const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadCircle
+
+*  Purpose:
+*     Load a Circle.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     AstCircle *astLoadCircle( void *mem, size_t size, AstCircleVtab *vtab,
+*                               const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     Circle loader.
+
+*  Description:
+*     This function is provided to load a new Circle using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Circle structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Circle at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Circle is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Circle data (sizeof(Circle)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Circle (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Circle structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstCircle) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Circle. If this is NULL, a pointer
+*        to the (static) virtual function table for the Circle class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Circle" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Circle.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstCircle *new;              /* Pointer to the new Circle */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Circle. In this case the
+   Circle belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstCircle );
+      vtab = &class_vtab;
+      name = "Circle";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitCircleVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Circle. */
+   new = astLoadRegion( mem, size, (AstRegionVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Circle" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* There are no values to read. */
+/* ---------------------------- */
+
+/* Cache intermediate results in the Circle structure */
+      new->centre = NULL;
+      new->lb = NULL;
+      new->ub = NULL;
+      new->stale = 1;
+      Cache( new, status );
+
+/* If an error occurred, clean up by deleting the new Circle. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Circle pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+void astCirclePars_( AstCircle *this, double *centre, double *radius,
+                     double *p1, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Circle,CirclePars))( this, centre, radius,
+                                          p1, status );
+}
+
+
+
+
+
+
diff --git a/ast/circle.h b/ast/circle.h
new file mode 100644
index 0000000..a69f8ea
--- /dev/null
+++ b/ast/circle.h
@@ -0,0 +1,241 @@
+#if !defined( CIRCLE_INCLUDED ) /* Include this file only once */
+#define CIRCLE_INCLUDED
+/*
+*+
+*  Name:
+*     circle.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Circle class.
+
+*  Invocation:
+*     #include "circle.h"
+
+*  Description:
+*     This include file defines the interface to the Circle class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The Circle class implement a Region which represents a simple interval
+*     on each axis of the encapsulated Frame
+
+*  Inheritance:
+*     The Circle class inherits from the Region class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     31-AUG-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "region.h"              /* Coordinate regions (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* Circle structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstCircle {
+
+/* Attributes inherited from the parent class. */
+   AstRegion region;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *centre;               /* Circle centre coords */
+   double radius;                /* Circle radius */
+   double *lb;                   /* Lower limits of mesh bounding box */
+   double *ub;                   /* Upper limit of mesh bounding box */
+   int stale;                    /* Is Cached information stale? */
+
+} AstCircle;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstCircleVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstRegionVtab region_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   void (* CirclePars)( AstCircle *, double *, double *, double *, int * );
+} AstCircleVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstCircleGlobals {
+   AstCircleVtab Class_Vtab;
+   int Class_Init;
+} AstCircleGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitCircleGlobals_( AstCircleGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Circle)          /* Check class membership */
+astPROTO_ISA(Circle)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstCircle *astCircle_( void *, int, const double[], const double[], AstRegion *, const char *, int *, ...);
+#else
+AstCircle *astCircleId_( void *, int, const double[], const double[],
+                         AstRegion *, const char *, ... )__attribute__((format(printf,6,7)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstCircle *astInitCircle_( void *, size_t, int, AstCircleVtab *,
+                           const char *, AstFrame *, int, const double[],
+                           const double[], AstRegion *, int * );
+
+/* Vtab initialiser. */
+void astInitCircleVtab_( AstCircleVtab *, const char *, int * );
+
+/* Loader. */
+AstCircle *astLoadCircle_( void *, size_t, AstCircleVtab *,
+                           const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+void astCirclePars_( AstCircle *, double *, double *, double *, int * );
+
+# if defined(astCLASS)           /* Protected */
+AstRegion *astBestCircle_( AstPointSet *, double *, AstRegion *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckCircle(this) astINVOKE_CHECK(Circle,this,0)
+#define astVerifyCircle(this) astINVOKE_CHECK(Circle,this,1)
+
+/* Test class membership. */
+#define astIsACircle(this) astINVOKE_ISA(Circle,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astCircle astINVOKE(F,astCircle_)
+#else
+#define astCircle astINVOKE(F,astCircleId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitCircle(mem,size,init,vtab,name,frame,form,p1,p2,unc) \
+astINVOKE(O,astInitCircle_(mem,size,init,vtab,name,frame,form,p1,p2,unc,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitCircleVtab(vtab,name) astINVOKE(V,astInitCircleVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadCircle(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadCircle_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckCircle to validate Circle pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#define astCirclePars(this,centre,radius,p1) \
+astINVOKE(V,astCirclePars_(astCheckCircle(this),centre,radius,p1,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#define astBestCircle(pset,cen,unc) astBestCircle_(pset,cen,unc,STATUS_PTR)
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/cminpack/.deps/libast_la-dpmpar.Plo b/ast/cminpack/.deps/libast_la-dpmpar.Plo
new file mode 100644
index 0000000..9ce06a8
--- /dev/null
+++ b/ast/cminpack/.deps/libast_la-dpmpar.Plo
@@ -0,0 +1 @@
+# dummy
diff --git a/ast/cminpack/.deps/libast_la-enorm.Plo b/ast/cminpack/.deps/libast_la-enorm.Plo
new file mode 100644
index 0000000..9ce06a8
--- /dev/null
+++ b/ast/cminpack/.deps/libast_la-enorm.Plo
@@ -0,0 +1 @@
+# dummy
diff --git a/ast/cminpack/.deps/libast_la-lmder.Plo b/ast/cminpack/.deps/libast_la-lmder.Plo
new file mode 100644
index 0000000..9ce06a8
--- /dev/null
+++ b/ast/cminpack/.deps/libast_la-lmder.Plo
@@ -0,0 +1 @@
+# dummy
diff --git a/ast/cminpack/.deps/libast_la-lmder1.Plo b/ast/cminpack/.deps/libast_la-lmder1.Plo
new file mode 100644
index 0000000..9ce06a8
--- /dev/null
+++ b/ast/cminpack/.deps/libast_la-lmder1.Plo
@@ -0,0 +1 @@
+# dummy
diff --git a/ast/cminpack/.deps/libast_la-lmpar.Plo b/ast/cminpack/.deps/libast_la-lmpar.Plo
new file mode 100644
index 0000000..9ce06a8
--- /dev/null
+++ b/ast/cminpack/.deps/libast_la-lmpar.Plo
@@ -0,0 +1 @@
+# dummy
diff --git a/ast/cminpack/.deps/libast_la-qrfac.Plo b/ast/cminpack/.deps/libast_la-qrfac.Plo
new file mode 100644
index 0000000..9ce06a8
--- /dev/null
+++ b/ast/cminpack/.deps/libast_la-qrfac.Plo
@@ -0,0 +1 @@
+# dummy
diff --git a/ast/cminpack/.deps/libast_la-qrsolv.Plo b/ast/cminpack/.deps/libast_la-qrsolv.Plo
new file mode 100644
index 0000000..9ce06a8
--- /dev/null
+++ b/ast/cminpack/.deps/libast_la-qrsolv.Plo
@@ -0,0 +1 @@
+# dummy
diff --git a/ast/cminpack/CopyrightMINPACK.txt b/ast/cminpack/CopyrightMINPACK.txt
new file mode 100644
index 0000000..ae7984d
--- /dev/null
+++ b/ast/cminpack/CopyrightMINPACK.txt
@@ -0,0 +1,52 @@
+Minpack Copyright Notice (1999) University of Chicago.  All rights reserved
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions of source code must retain the above
+copyright notice, this list of conditions and the following
+disclaimer.
+
+2. Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following
+disclaimer in the documentation and/or other materials
+provided with the distribution.
+
+3. The end-user documentation included with the
+redistribution, if any, must include the following
+acknowledgment:
+
+   "This product includes software developed by the
+   University of Chicago, as Operator of Argonne National
+   Laboratory.
+
+Alternately, this acknowledgment may appear in the software
+itself, if and wherever such third-party acknowledgments
+normally appear.
+
+4. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS"
+WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE
+UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND
+THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE
+OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY
+OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR
+USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF
+THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4)
+DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION
+UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL
+BE CORRECTED.
+
+5. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT
+HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF
+ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT,
+INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF
+ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF
+PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER
+SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT
+(INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE,
+EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE
+POSSIBILITY OF SUCH LOSS OR DAMAGES.
+
diff --git a/ast/cminpack/README.md b/ast/cminpack/README.md
new file mode 100644
index 0000000..66bbc6f
--- /dev/null
+++ b/ast/cminpack/README.md
@@ -0,0 +1,128 @@
+C/C++ Minpack [![Build Status](https://api.travis-ci.org/devernay/cminpack.png?branch=master)](https://travis-ci.org/devernay/cminpack)
+==========
+
+This is a C version of the minpack minimization package.
+It has been derived from the fortran code using f2c and
+some limited manual editing. Note that you need to link
+against libf2c to use this version of minpack. Extern "C"
+linkage permits the package routines to be called from C++.
+Check ftp://netlib.bell-labs.com/netlib/f2c for the latest
+f2c version. For general minpack info and test programs, see
+the accompanying readme.txt and http://www.netlib.org/minpack/.
+
+Type `make` to compile and `make install` to install in /usr/local
+or modify the makefile to suit your needs.
+
+This software has been tested on a RedHat 7.3 Linux machine -
+usual 'use at your own risk' warnings apply.
+
+Manolis Lourakis -- lourakis at ics forth gr, July 2002
+	Institute of Computer Science,
+	Foundation for Research and Technology - Hellas
+	Heraklion, Crete, Greece
+
+Repackaging by Frederic Devernay -- frederic dot devernay at m4x dot org
+
+The project home page is at http://devernay.free.fr/hacks/cminpack/
+
+History
+------
+
+* version 1.3.3 (04/02/2014)::
+ - Add documentation and examples abouts how to add box constraints to the variables.
+ - continuous integration https://travis-ci.org/devernay/cminpack
+
+* version 1.3.2 (27/10/2013):
+ - Minor change in the CMake build: also set SOVERSION.
+
+* version 1.3.1 (02/10/2013):
+ - Fix CUDA examples compilation, and remove non-free files.
+
+* version 1.3.0 (09/06/2012):
+ - Optionally use LAPACK and CBLAS in lmpar, qrfac, and qrsolv. Added
+  "make lapack" to build the LAPACK-based cminpack and "make
+  checklapack" to test it (results of the test may depend on the
+  underlying LAPACK and BLAS implementations).
+  On 64-bits architectures, the preprocessor symbol __LP64__ must be
+  defined (see cminpackP.h) if the LAPACK library uses the LP64
+  interface (i.e. 32-bits integer, vhereas the ILP interface uses 64
+  bits integers).
+
+* version 1.2.2 (16/05/2012):
+ - Update Makefiles and documentation (see "Using CMinpack" above) for
+  easier building and testing.
+
+* version 1.2.1 (15/05/2012):
+ - The library can now be built as double, float or half
+  versions. Standard tests in the "examples" directory can now be
+  lauched using "make check" (to run common tests, including against
+  the float version), "make checkhalf" (to test the half version) and
+  "make checkfail" (to run all the tests, even those that fail).
+
+* version 1.2.0 (14/05/2012):
+- Added original FORTRAN sources for better testing (type "make" in
+  directory fortran, then "make" in examples and follow the
+  instructions). Added driver tests lmsdrv, chkdrv, hyjdrv,
+  hybdrv. Typing "make alltest" in the examples directory will run all
+  possible test combinations (make sure you have gfortran installed).
+
+* version 1.1.5 (04/05/2012):
+ - cminpack now works in CUDA, thanks to Jordi Bataller Mascarell, type
+   "make" in the "cuda" subdir (be careful, though: this is a
+   straightforward port from C, and each problem is solved using a
+   single thread). cminpack can now also be compiled with
+   single-precision floating point computation (define
+   __cminpack_real__ to float when compiling and using the
+   library). Fix cmake support for CMINPACK_LIB_INSTALL_DIR. Update the
+   reference files for tests.
+
+* version 1.1.4 (30/10/2011):
+ - Translated all the Levenberg-Marquardt code (lmder, lmdif, lmstr,
+     lmder1, lmdif1, lmstr1, lmpar, qrfac, qrsolv, fdjac2, chkder) to use
+     C-style indices.
+
+* version 1.1.3 (16/03/2011):
+  - Minor fix: Change non-standard strnstr() to strstr() in
+     genf77tests.c.
+
+* version 1.1.2 (07/01/2011):
+   - Fix Windows DLL building (David Graeff) and document covar in
+     cminpack.h.
+
+* version 1.1.1 (04/12/2010):
+ - Complete rewrite of the C functions (without trailing underscore in
+   the function name). Using the original FORTRAN code, the original
+   algorithms structure was recovered, and many goto's were converted
+   to if...then...else. The code should now be both more readable and
+   easier to optimize, both for humans and for compilers. Added lmddrv
+   and lmfdrv test drivers, which test a lot of difficult functions
+   (these functions are explained in Testing Unconstrained Optimization
+   Software by Moré et al.). Also added the pkg-config files to the
+   cmake build, as well as an "uninstall" target, contributed by
+   Geoffrey Biggs.
+
+* version 1.0.4 (18/10/2010):
+ - Support for shared library building using CMake, thanks to Goeffrey
+   Biggs and Radu Bogdan Rusu from Willow Garage. Shared libraries can be
+   enabled using cmake options, as in;
+ cmake -DUSE_FPIC=ON -DSHARED_LIBS=ON -DBUILD_EXAMPLES=OFF path_to_sources
+
+* version 1.0.3 (18/03/2010):
+ - Added CMake support.
+ - XCode build is now Universal.
+ - Added tfdjac2_ and tfdjac2c examples, which test the accuracy of a
+   finite-differences approximation of the Jacobian.
+ - Bug fix in tlmstr1 (signaled by Thomas Capricelli).
+
+* version 1.0.2 (27/02/2009):
+ - Added Xcode and Visual Studio project files
+
+* version 1.0.1 (17/12/2007):
+ - bug fix in covar() and covar_(), the computation of tolr caused a
+   segfault (signaled by Timo Hartmann).
+
+* version 1.0.0 (24/04/2007):
+ - Added fortran and C examples
+ - Added documentation from Debian man pages
+ - Wrote pure C version
+ - Added covar() and covar_(), and use it in tlmdef/tlmdif
diff --git a/ast/cminpack/cminpack.h b/ast/cminpack/cminpack.h
new file mode 100644
index 0000000..6d3f757
--- /dev/null
+++ b/ast/cminpack/cminpack.h
@@ -0,0 +1,370 @@
+/* Header file for cminpack, by Frederic Devernay.
+   The documentation for all functions can be found in the file
+   minpack-documentation.txt from the distribution, or in the source
+   code of each function. */
+
+#ifndef __CMINPACK_H__
+#define __CMINPACK_H__
+
+/* The default floating-point type is "double" for C/C++ and "float" for CUDA,
+   but you can change this by defining one of the following symbols when
+   compiling the library, and before including cminpack.h when using it:
+   __cminpack_double__ for double
+   __cminpack_float__ for float
+   __cminpack_half__ for half from the OpenEXR library (in this case, you must
+                     compile cminpack with a C++ compiler)
+*/
+#ifdef __cminpack_double__
+#define __cminpack_real__ double
+#endif
+
+#ifdef __cminpack_float__
+#define __cminpack_real__ float
+#endif
+
+#ifdef __cminpack_half__
+#include <OpenEXR/half.h>
+#define __cminpack_real__ half
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Cmake will define cminpack_EXPORTS on Windows when it
+configures to build a shared library. If you are going to use
+another build system on windows or create the visual studio
+projects by hand you need to define cminpack_EXPORTS when
+building a DLL on windows.
+*/
+#if defined (__GNUC__)
+#define CMINPACK_DECLSPEC_EXPORT  __declspec(__dllexport__)
+#define CMINPACK_DECLSPEC_IMPORT  __declspec(__dllimport__)
+#endif
+#if defined (_MSC_VER) || defined (__BORLANDC__)
+#define CMINPACK_DECLSPEC_EXPORT  __declspec(dllexport)
+#define CMINPACK_DECLSPEC_IMPORT  __declspec(dllimport)
+#endif
+#ifdef __WATCOMC__
+#define CMINPACK_DECLSPEC_EXPORT  __export
+#define CMINPACK_DECLSPEC_IMPORT  __import
+#endif
+#ifdef __IBMC__
+#define CMINPACK_DECLSPEC_EXPORT  _Export
+#define CMINPACK_DECLSPEC_IMPORT  _Import
+#endif
+
+#if !defined(CMINPACK_NO_DLL) && (defined(__WIN32__) || defined(WIN32) || defined (_WIN32))
+#if defined(cminpack_EXPORTS) || defined(CMINPACK_EXPORTS) || defined(CMINPACK_DLL_EXPORTS)
+    #define  CMINPACK_EXPORT CMINPACK_DECLSPEC_EXPORT
+  #else
+    #define  CMINPACK_EXPORT CMINPACK_DECLSPEC_IMPORT
+  #endif /* cminpack_EXPORTS */
+#else /* defined (_WIN32) */
+ #define CMINPACK_EXPORT
+#endif
+
+#if defined(__CUDA_ARCH__) || defined(__CUDACC__)
+#define __cminpack_attr__ __device__
+#ifndef __cminpack_real__
+#define __cminpack_float__
+#define __cminpack_real__ float
+#endif
+#define __cminpack_type_fcn_nn__        __cminpack_attr__ int fcn_nn
+#define __cminpack_type_fcnder_nn__     __cminpack_attr__ int fcnder_nn
+#define __cminpack_type_fcn_mn__        __cminpack_attr__ int fcn_mn
+#define __cminpack_type_fcnder_mn__     __cminpack_attr__ int fcnder_mn
+#define __cminpack_type_fcnderstr_mn__  __cminpack_attr__ int fcnderstr_mn
+#define __cminpack_decl_fcn_nn__
+#define __cminpack_decl_fcnder_nn__
+#define __cminpack_decl_fcn_mn__
+#define __cminpack_decl_fcnder_mn__
+#define __cminpack_decl_fcnderstr_mn__
+#define __cminpack_param_fcn_nn__
+#define __cminpack_param_fcnder_nn__
+#define __cminpack_param_fcn_mn__
+#define __cminpack_param_fcnder_mn__
+#define __cminpack_param_fcnderstr_mn__
+#else
+#define __cminpack_attr__
+#ifndef __cminpack_real__
+#define __cminpack_double__
+#define __cminpack_real__ double
+#endif
+#define __cminpack_type_fcn_nn__        typedef int (*cminpack_func_nn)
+#define __cminpack_type_fcnder_nn__     typedef int (*cminpack_funcder_nn)
+#define __cminpack_type_fcn_mn__        typedef int (*cminpack_func_mn)
+#define __cminpack_type_fcnder_mn__     typedef int (*cminpack_funcder_mn)
+#define __cminpack_type_fcnderstr_mn__  typedef int (*cminpack_funcderstr_mn)
+#define __cminpack_decl_fcn_nn__        cminpack_func_nn fcn_nn,
+#define __cminpack_decl_fcnder_nn__     cminpack_funcder_nn fcnder_nn,
+#define __cminpack_decl_fcn_mn__        cminpack_func_mn fcn_mn,
+#define __cminpack_decl_fcnder_mn__     cminpack_funcder_mn fcnder_mn,
+#define __cminpack_decl_fcnderstr_mn__  cminpack_funcderstr_mn fcnderstr_mn,
+#define __cminpack_param_fcn_nn__       fcn_nn,
+#define __cminpack_param_fcnder_nn__    fcnder_nn,
+#define __cminpack_param_fcn_mn__       fcn_mn,
+#define __cminpack_param_fcnder_mn__    fcnder_mn,
+#define __cminpack_param_fcnderstr_mn__ fcnderstr_mn,
+#endif
+
+#ifdef __cminpack_double__
+#define __cminpack_func__(func) func
+#endif
+
+#ifdef __cminpack_float__
+#define __cminpack_func__(func) s ## func
+#endif
+
+#ifdef __cminpack_half__
+#define __cminpack_func__(func) h ## func
+#endif
+
+/* Declarations for minpack */
+
+/* Function types: */
+/* The first argument can be used to store extra function parameters, thus */
+/* avoiding the use of global variables. */
+/* the iflag parameter is input-only (with respect to the FORTRAN */
+/*  version), the output iflag value is the return value of the function. */
+/* If iflag=0, the function shoulkd just print the current values (see */
+/* the nprint parameters below). */
+  
+/* for hybrd1 and hybrd: */
+/*         calculate the functions at x and */
+/*         return this vector in fvec. */
+/* return a negative value to terminate hybrd1/hybrd */
+__cminpack_type_fcn_nn__(void *p, int n, const __cminpack_real__ *x, __cminpack_real__ *fvec, int iflag );
+
+/* for hybrj1 and hybrj */
+/*         if iflag = 1 calculate the functions at x and */
+/*         return this vector in fvec. do not alter fjac. */
+/*         if iflag = 2 calculate the jacobian at x and */
+/*         return this matrix in fjac. do not alter fvec. */
+/* return a negative value to terminate hybrj1/hybrj */
+__cminpack_type_fcnder_nn__(void *p, int n, const __cminpack_real__ *x, __cminpack_real__ *fvec, __cminpack_real__ *fjac,
+                                  int ldfjac, int iflag );
+
+/* for lmdif1 and lmdif */
+/*         calculate the functions at x and */
+/*         return this vector in fvec. */
+/*         if iflag = 1 the result is used to compute the residuals. */
+/*         if iflag = 2 the result is used to compute the Jacobian by finite differences. */
+/*         Jacobian computation requires exactly n function calls with iflag = 2. */
+/* return a negative value to terminate lmdif1/lmdif */
+__cminpack_type_fcn_mn__(void *p, int m, int n, const __cminpack_real__ *x, __cminpack_real__ *fvec,
+                               int iflag );
+
+/* for lmder1 and lmder */
+/*         if iflag = 1 calculate the functions at x and */
+/*         return this vector in fvec. do not alter fjac. */
+/*         if iflag = 2 calculate the jacobian at x and */
+/*         return this matrix in fjac. do not alter fvec. */
+/* return a negative value to terminate lmder1/lmder */
+__cminpack_type_fcnder_mn__(void *p, int m, int n, const __cminpack_real__ *x, __cminpack_real__ *fvec,
+                                  __cminpack_real__ *fjac, int ldfjac, int iflag );
+
+/* for lmstr1 and lmstr */
+/*         if iflag = 1 calculate the functions at x and */
+/*         return this vector in fvec. */
+/*         if iflag = i calculate the (i-1)-st row of the */
+/*         jacobian at x and return this vector in fjrow. */
+/* return a negative value to terminate lmstr1/lmstr */
+__cminpack_type_fcnderstr_mn__(void *p, int m, int n, const __cminpack_real__ *x, __cminpack_real__ *fvec,
+                                     __cminpack_real__ *fjrow, int iflag );
+
+
+
+
+
+
+/* MINPACK functions: */
+/* the info parameter was removed from most functions: the return */
+/* value of the function is used instead. */
+/* The argument 'p' can be used to store extra function parameters, thus */
+/* avoiding the use of global variables. You can also think of it as a */
+/* 'this' pointer a la C++. */
+
+/* find a zero of a system of N nonlinear functions in N variables by
+   a modification of the Powell hybrid method (Jacobian calculated by
+   a forward-difference approximation) */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(hybrd1)( __cminpack_decl_fcn_nn__ 
+	       void *p, int n, __cminpack_real__ *x, __cminpack_real__ *fvec, __cminpack_real__ tol,
+	       __cminpack_real__ *wa, int lwa );
+
+/* find a zero of a system of N nonlinear functions in N variables by
+   a modification of the Powell hybrid method (Jacobian calculated by
+   a forward-difference approximation, more general). */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(hybrd)( __cminpack_decl_fcn_nn__
+	      void *p, int n, __cminpack_real__ *x, __cminpack_real__ *fvec, __cminpack_real__ xtol, int maxfev,
+	      int ml, int mu, __cminpack_real__ epsfcn, __cminpack_real__ *diag, int mode,
+	      __cminpack_real__ factor, int nprint, int *nfev,
+	      __cminpack_real__ *fjac, int ldfjac, __cminpack_real__ *r, int lr, __cminpack_real__ *qtf,
+	      __cminpack_real__ *wa1, __cminpack_real__ *wa2, __cminpack_real__ *wa3, __cminpack_real__ *wa4);
+  
+/* find a zero of a system of N nonlinear functions in N variables by
+   a modification of the Powell hybrid method (user-supplied Jacobian) */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(hybrj1)( __cminpack_decl_fcnder_nn__ void *p, int n, __cminpack_real__ *x,
+	       __cminpack_real__ *fvec, __cminpack_real__ *fjac, int ldfjac, __cminpack_real__ tol,
+	       __cminpack_real__ *wa, int lwa );
+          
+/* find a zero of a system of N nonlinear functions in N variables by
+   a modification of the Powell hybrid method (user-supplied Jacobian,
+   more general) */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(hybrj)( __cminpack_decl_fcnder_nn__ void *p, int n, __cminpack_real__ *x,
+	      __cminpack_real__ *fvec, __cminpack_real__ *fjac, int ldfjac, __cminpack_real__ xtol,
+	      int maxfev, __cminpack_real__ *diag, int mode, __cminpack_real__ factor,
+	      int nprint, int *nfev, int *njev, __cminpack_real__ *r,
+	      int lr, __cminpack_real__ *qtf, __cminpack_real__ *wa1, __cminpack_real__ *wa2,
+	      __cminpack_real__ *wa3, __cminpack_real__ *wa4 );
+
+/* minimize the sum of the squares of nonlinear functions in N
+   variables by a modification of the Levenberg-Marquardt algorithm
+   (Jacobian calculated by a forward-difference approximation) */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(lmdif1)( __cminpack_decl_fcn_mn__
+	       void *p, int m, int n, __cminpack_real__ *x, __cminpack_real__ *fvec, __cminpack_real__ tol,
+	       int *iwa, __cminpack_real__ *wa, int lwa );
+
+/* minimize the sum of the squares of nonlinear functions in N
+   variables by a modification of the Levenberg-Marquardt algorithm
+   (Jacobian calculated by a forward-difference approximation, more
+   general) */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(lmdif)( __cminpack_decl_fcn_mn__
+	      void *p, int m, int n, __cminpack_real__ *x, __cminpack_real__ *fvec, __cminpack_real__ ftol,
+	      __cminpack_real__ xtol, __cminpack_real__ gtol, int maxfev, __cminpack_real__ epsfcn,
+	      __cminpack_real__ *diag, int mode, __cminpack_real__ factor, int nprint,
+	      int *nfev, __cminpack_real__ *fjac, int ldfjac, int *ipvt,
+	      __cminpack_real__ *qtf, __cminpack_real__ *wa1, __cminpack_real__ *wa2, __cminpack_real__ *wa3,
+	      __cminpack_real__ *wa4 );
+
+/* minimize the sum of the squares of nonlinear functions in N
+   variables by a modification of the Levenberg-Marquardt algorithm
+   (user-supplied Jacobian) */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(lmder1)( __cminpack_decl_fcnder_mn__
+	       void *p, int m, int n, __cminpack_real__ *x, __cminpack_real__ *fvec, __cminpack_real__ *fjac,
+	       int ldfjac, __cminpack_real__ tol, int *ipvt,
+	       __cminpack_real__ *wa, int lwa );
+
+/* minimize the sum of the squares of nonlinear functions in N
+   variables by a modification of the Levenberg-Marquardt algorithm
+   (user-supplied Jacobian, more general) */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(lmder)( __cminpack_decl_fcnder_mn__
+	      void *p, int m, int n, __cminpack_real__ *x, __cminpack_real__ *fvec, __cminpack_real__ *fjac,
+	      int ldfjac, __cminpack_real__ ftol, __cminpack_real__ xtol, __cminpack_real__ gtol,
+	      int maxfev, __cminpack_real__ *diag, int mode, __cminpack_real__ factor,
+	      int nprint, int *nfev, int *njev, int *ipvt,
+	      __cminpack_real__ *qtf, __cminpack_real__ *wa1, __cminpack_real__ *wa2, __cminpack_real__ *wa3,
+	      __cminpack_real__ *wa4 );
+
+/* minimize the sum of the squares of nonlinear functions in N
+   variables by a modification of the Levenberg-Marquardt algorithm
+   (user-supplied Jacobian, minimal storage) */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(lmstr1)( __cminpack_decl_fcnderstr_mn__ void *p, int m, int n,
+	       __cminpack_real__ *x, __cminpack_real__ *fvec, __cminpack_real__ *fjac, int ldfjac,
+	       __cminpack_real__ tol, int *ipvt, __cminpack_real__ *wa, int lwa );
+
+/* minimize the sum of the squares of nonlinear functions in N
+   variables by a modification of the Levenberg-Marquardt algorithm
+   (user-supplied Jacobian, minimal storage, more general) */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(lmstr)(  __cminpack_decl_fcnderstr_mn__ void *p, int m,
+	      int n, __cminpack_real__ *x, __cminpack_real__ *fvec, __cminpack_real__ *fjac,
+	      int ldfjac, __cminpack_real__ ftol, __cminpack_real__ xtol, __cminpack_real__ gtol,
+	      int maxfev, __cminpack_real__ *diag, int mode, __cminpack_real__ factor,
+	      int nprint, int *nfev, int *njev, int *ipvt,
+	      __cminpack_real__ *qtf, __cminpack_real__ *wa1, __cminpack_real__ *wa2, __cminpack_real__ *wa3,
+	      __cminpack_real__ *wa4 );
+ 
+__cminpack_attr__
+void CMINPACK_EXPORT __cminpack_func__(chkder)( int m, int n, const __cminpack_real__ *x, __cminpack_real__ *fvec, __cminpack_real__ *fjac,
+	       int ldfjac, __cminpack_real__ *xp, __cminpack_real__ *fvecp, int mode,
+	       __cminpack_real__ *err  );
+
+__cminpack_attr__
+__cminpack_real__ CMINPACK_EXPORT __cminpack_func__(dpmpar)( int i );
+
+__cminpack_attr__
+__cminpack_real__ CMINPACK_EXPORT __cminpack_func__(enorm)( int n, const __cminpack_real__ *x );
+
+/* compute a forward-difference approximation to the m by n jacobian
+   matrix associated with a specified problem of m functions in n
+   variables. */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(fdjac2)(__cminpack_decl_fcn_mn__
+	     void *p, int m, int n, __cminpack_real__ *x, const __cminpack_real__ *fvec, __cminpack_real__ *fjac,
+	     int ldfjac, __cminpack_real__ epsfcn, __cminpack_real__ *wa);
+
+/* compute a forward-difference approximation to the n by n jacobian
+   matrix associated with a specified problem of n functions in n
+   variables. if the jacobian has a banded form, then function
+   evaluations are saved by only approximating the nonzero terms. */
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(fdjac1)(__cminpack_decl_fcn_nn__
+	     void *p, int n, __cminpack_real__ *x, const __cminpack_real__ *fvec, __cminpack_real__ *fjac, int ldfjac,
+	     int ml, int mu, __cminpack_real__ epsfcn, __cminpack_real__ *wa1,
+	     __cminpack_real__ *wa2);
+
+/* compute inverse(JtJ) after a run of lmdif or lmder. The covariance matrix is obtained
+   by scaling the result by enorm(y)**2/(m-n). If JtJ is singular and k = rank(J), the
+   pseudo-inverse is computed, and the result has to be scaled by enorm(y)**2/(m-k). */
+__cminpack_attr__
+void CMINPACK_EXPORT __cminpack_func__(covar)(int n, __cminpack_real__ *r, int ldr, 
+           const int *ipvt, __cminpack_real__ tol, __cminpack_real__ *wa);
+
+/* covar1 estimates the variance-covariance matrix:
+   C = sigma**2 (JtJ)**+
+   where (JtJ)**+ is the inverse of JtJ or the pseudo-inverse of JtJ (in case J does not have full rank),
+   and sigma**2 = fsumsq / (m - k)
+   where fsumsq is the residual sum of squares and k is the rank of J.
+   The function returns 0 if J has full rank, else the rank of J.
+*/
+__cminpack_attr__
+int CMINPACK_EXPORT __cminpack_func__(covar1)(int m, int n, __cminpack_real__ fsumsq, __cminpack_real__ *r, int ldr, 
+                           const int *ipvt, __cminpack_real__ tol, __cminpack_real__ *wa);
+
+/* internal MINPACK subroutines */
+__cminpack_attr__
+void __cminpack_func__(dogleg)(int n, const __cminpack_real__ *r, int lr, 
+             const __cminpack_real__ *diag, const __cminpack_real__ *qtb, __cminpack_real__ delta, __cminpack_real__ *x, 
+             __cminpack_real__ *wa1, __cminpack_real__ *wa2);
+__cminpack_attr__
+void __cminpack_func__(qrfac)(int m, int n, __cminpack_real__ *a, int
+            lda, int pivot, int *ipvt, int lipvt, __cminpack_real__ *rdiag,
+            __cminpack_real__ *acnorm, __cminpack_real__ *wa);
+__cminpack_attr__
+void __cminpack_func__(qrsolv)(int n, __cminpack_real__ *r, int ldr, 
+             const int *ipvt, const __cminpack_real__ *diag, const __cminpack_real__ *qtb, __cminpack_real__ *x, 
+             __cminpack_real__ *sdiag, __cminpack_real__ *wa);
+__cminpack_attr__
+void __cminpack_func__(qform)(int m, int n, __cminpack_real__ *q, int
+            ldq, __cminpack_real__ *wa);
+__cminpack_attr__
+void __cminpack_func__(r1updt)(int m, int n, __cminpack_real__ *s, int
+             ls, const __cminpack_real__ *u, __cminpack_real__ *v, __cminpack_real__ *w, int *sing);
+__cminpack_attr__
+void __cminpack_func__(r1mpyq)(int m, int n, __cminpack_real__ *a, int
+             lda, const __cminpack_real__ *v, const __cminpack_real__ *w);
+__cminpack_attr__
+void __cminpack_func__(lmpar)(int n, __cminpack_real__ *r, int ldr, 
+            const int *ipvt, const __cminpack_real__ *diag, const __cminpack_real__ *qtb, __cminpack_real__ delta, 
+            __cminpack_real__ *par, __cminpack_real__ *x, __cminpack_real__ *sdiag, __cminpack_real__ *wa1, 
+            __cminpack_real__ *wa2);
+__cminpack_attr__
+void __cminpack_func__(rwupdt)(int n, __cminpack_real__ *r, int ldr, 
+             const __cminpack_real__ *w, __cminpack_real__ *b, __cminpack_real__ *alpha, __cminpack_real__ *cos, 
+             __cminpack_real__ *sin);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __CMINPACK_H__ */
diff --git a/ast/cminpack/cminpackP.h b/ast/cminpack/cminpackP.h
new file mode 100644
index 0000000..4e8ba7b
--- /dev/null
+++ b/ast/cminpack/cminpackP.h
@@ -0,0 +1,62 @@
+/* Internal header file for cminpack, by Frederic Devernay. */
+#ifndef __CMINPACKP_H__
+#define __CMINPACKP_H__
+
+#ifndef __CMINPACK_H__
+#error "cminpackP.h in an internal cminpack header, and must be included after all other headers (including cminpack.h)"
+#endif
+
+#if (defined (USE_CBLAS) || defined (USE_LAPACK)) && !defined (__cminpack_double__)
+#error "cminpack can use cblas and lapack only in double precision mode"
+#endif
+
+#ifdef USE_CBLAS
+#ifdef __APPLE__
+#include <Accelerate/Accelerate.h>
+#else
+#include <cblas.h>
+#endif
+#define __cminpack_enorm__(n,x) cblas_dnrm2(n,x,1)
+#else
+#define __cminpack_enorm__(n,x) __cminpack_func__(enorm)(n,x)
+#endif
+
+#ifdef USE_LAPACK
+#ifdef __APPLE__
+#include <Accelerate/Accelerate.h>
+#else
+#if defined(__LP64__) /* In LP64 match sizes with the 32 bit ABI */
+typedef int 		__CLPK_integer;
+typedef int 		__CLPK_logical;
+typedef float 		__CLPK_real;
+typedef double 		__CLPK_doublereal;
+typedef __CLPK_logical 	(*__CLPK_L_fp)();
+typedef int 		__CLPK_ftnlen;
+#else
+typedef long int 	__CLPK_integer;
+typedef long int 	__CLPK_logical;
+typedef float 		__CLPK_real;
+typedef double 		__CLPK_doublereal;
+typedef __CLPK_logical 	(*__CLPK_L_fp)();
+typedef long int 	__CLPK_ftnlen;
+#endif
+//extern void dlartg_(double *f, double *g, double *cs, double *sn, double *r__);
+int dlartg_(__CLPK_doublereal *f, __CLPK_doublereal *g, __CLPK_doublereal *cs,
+            __CLPK_doublereal *sn, __CLPK_doublereal *r__)
+//extern void dgeqp3_(int *m, int *n, double *a, int *lda, int *jpvt, double *tau, double *work, int *lwork, int *info);
+int dgeqp3_(__CLPK_integer *m, __CLPK_integer *n, __CLPK_doublereal *a, __CLPK_integer *
+            lda, __CLPK_integer *jpvt, __CLPK_doublereal *tau, __CLPK_doublereal *work, __CLPK_integer *lwork,
+            __CLPK_integer *info)
+//extern void dgeqrf_(int *m, int *n, double *a, int *lda, double *tau, double *work, int *lwork, int *info);
+int dgeqrf_(__CLPK_integer *m, __CLPK_integer *n, __CLPK_doublereal *a, __CLPK_integer *
+            lda, __CLPK_doublereal *tau, __CLPK_doublereal *work, __CLPK_integer *lwork, __CLPK_integer *info)
+#endif
+#endif
+
+#define real __cminpack_real__
+#define min(a,b) ((a) <= (b) ? (a) : (b))
+#define max(a,b) ((a) >= (b) ? (a) : (b))
+#define TRUE_ (1)
+#define FALSE_ (0)
+
+#endif /* !__CMINPACKP_H__ */
diff --git a/ast/cminpack/dpmpar.c b/ast/cminpack/dpmpar.c
new file mode 100644
index 0000000..81c6fcd
--- /dev/null
+++ b/ast/cminpack/dpmpar.c
@@ -0,0 +1,201 @@
+#include "cminpack.h"
+#include <float.h>
+#include "cminpackP.h"
+
+#define double_EPSILON DBL_EPSILON
+#define double_MIN DBL_MIN
+#define double_MAX DBL_MAX
+#define float_EPSILON FLT_EPSILON
+#define float_MIN FLT_MIN
+#define float_MAX FLT_MAX
+#define half_EPSILON HALF_EPSILON
+#define half_MIN HALF_NRM_MIN
+#define half_MAX HALF_MAX
+
+#define DPMPAR(type,X) _DPMPAR(type,X)
+#define _DPMPAR(type,X) type ## _ ## X
+
+__cminpack_attr__
+real __cminpack_func__(dpmpar)(int i)
+{
+/*     ********** */
+
+/*     Function dpmpar */
+
+/*     This function provides double precision machine parameters */
+/*     when the appropriate set of data statements is activated (by */
+/*     removing the c from column 1) and all other data statements are */
+/*     rendered inactive. Most of the parameter values were obtained */
+/*     from the corresponding Bell Laboratories Port Library function. */
+
+/*     The function statement is */
+
+/*       double precision function dpmpar(i) */
+
+/*     where */
+
+/*       i is an integer input variable set to 1, 2, or 3 which */
+/*         selects the desired machine parameter. If the machine has */
+/*         t base b digits and its smallest and largest exponents are */
+/*         emin and emax, respectively, then these parameters are */
+
+/*         dpmpar(1) = b**(1 - t), the machine precision, */
+
+/*         dpmpar(2) = b**(emin - 1), the smallest magnitude, */
+
+/*         dpmpar(3) = b**emax*(1 - b**(-t)), the largest magnitude. */
+
+/*     Argonne National Laboratory. MINPACK Project. November 1996. */
+/*     Burton S. Garbow, Kenneth E. Hillstrom, Jorge J. More' */
+
+/*     ********** */
+
+/*     Machine constants for the IBM 360/370 series, */
+/*     the Amdahl 470/V6, the ICL 2900, the Itel AS/6, */
+/*     the Xerox Sigma 5/7/9 and the Sel systems 85/86. */
+
+/*     data mcheps(1),mcheps(2) / z34100000, z00000000 / */
+/*     data minmag(1),minmag(2) / z00100000, z00000000 / */
+/*     data maxmag(1),maxmag(2) / z7fffffff, zffffffff / */
+
+/*     Machine constants for the Honeywell 600/6000 series. */
+
+/*     data mcheps(1),mcheps(2) / o606400000000, o000000000000 / */
+/*     data minmag(1),minmag(2) / o402400000000, o000000000000 / */
+/*     data maxmag(1),maxmag(2) / o376777777777, o777777777777 / */
+
+/*     Machine constants for the CDC 6000/7000 series. */
+
+/*     data mcheps(1) / 15614000000000000000b / */
+/*     data mcheps(2) / 15010000000000000000b / */
+
+/*     data minmag(1) / 00604000000000000000b / */
+/*     data minmag(2) / 00000000000000000000b / */
+
+/*     data maxmag(1) / 37767777777777777777b / */
+/*     data maxmag(2) / 37167777777777777777b / */
+
+/*     Machine constants for the PDP-10 (KA processor). */
+
+/*     data mcheps(1),mcheps(2) / "114400000000, "000000000000 / */
+/*     data minmag(1),minmag(2) / "033400000000, "000000000000 / */
+/*     data maxmag(1),maxmag(2) / "377777777777, "344777777777 / */
+
+/*     Machine constants for the PDP-10 (KI processor). */
+
+/*     data mcheps(1),mcheps(2) / "104400000000, "000000000000 / */
+/*     data minmag(1),minmag(2) / "000400000000, "000000000000 / */
+/*     data maxmag(1),maxmag(2) / "377777777777, "377777777777 / */
+
+/*     Machine constants for the PDP-11. */
+
+/*     data mcheps(1),mcheps(2) /   9472,      0 / */
+/*     data mcheps(3),mcheps(4) /      0,      0 / */
+
+/*     data minmag(1),minmag(2) /    128,      0 / */
+/*     data minmag(3),minmag(4) /      0,      0 / */
+
+/*     data maxmag(1),maxmag(2) /  32767,     -1 / */
+/*     data maxmag(3),maxmag(4) /     -1,     -1 / */
+
+/*     Machine constants for the Burroughs 6700/7700 systems. */
+
+/*     data mcheps(1) / o1451000000000000 / */
+/*     data mcheps(2) / o0000000000000000 / */
+
+/*     data minmag(1) / o1771000000000000 / */
+/*     data minmag(2) / o7770000000000000 / */
+
+/*     data maxmag(1) / o0777777777777777 / */
+/*     data maxmag(2) / o7777777777777777 / */
+
+/*     Machine constants for the Burroughs 5700 system. */
+
+/*     data mcheps(1) / o1451000000000000 / */
+/*     data mcheps(2) / o0000000000000000 / */
+
+/*     data minmag(1) / o1771000000000000 / */
+/*     data minmag(2) / o0000000000000000 / */
+
+/*     data maxmag(1) / o0777777777777777 / */
+/*     data maxmag(2) / o0007777777777777 / */
+
+/*     Machine constants for the Burroughs 1700 system. */
+
+/*     data mcheps(1) / zcc6800000 / */
+/*     data mcheps(2) / z000000000 / */
+
+/*     data minmag(1) / zc00800000 / */
+/*     data minmag(2) / z000000000 / */
+
+/*     data maxmag(1) / zdffffffff / */
+/*     data maxmag(2) / zfffffffff / */
+
+/*     Machine constants for the Univac 1100 series. */
+
+/*     data mcheps(1),mcheps(2) / o170640000000, o000000000000 / */
+/*     data minmag(1),minmag(2) / o000040000000, o000000000000 / */
+/*     data maxmag(1),maxmag(2) / o377777777777, o777777777777 / */
+
+/*     Machine constants for the Data General Eclipse S/200. */
+
+/*     Note - it may be appropriate to include the following card - */
+/*     static dmach(3) */
+
+/*     data minmag/20k,3*0/,maxmag/77777k,3*177777k/ */
+/*     data mcheps/32020k,3*0/ */
+
+/*     Machine constants for the Harris 220. */
+
+/*     data mcheps(1),mcheps(2) / '20000000, '00000334 / */
+/*     data minmag(1),minmag(2) / '20000000, '00000201 / */
+/*     data maxmag(1),maxmag(2) / '37777777, '37777577 / */
+
+/*     Machine constants for the Cray-1. */
+
+/*     data mcheps(1) / 0376424000000000000000b / */
+/*     data mcheps(2) / 0000000000000000000000b / */
+
+/*     data minmag(1) / 0200034000000000000000b / */
+/*     data minmag(2) / 0000000000000000000000b / */
+
+/*     data maxmag(1) / 0577777777777777777777b / */
+/*     data maxmag(2) / 0000007777777777777776b / */
+
+/*     Machine constants for the Prime 400. */
+
+/*     data mcheps(1),mcheps(2) / :10000000000, :00000000123 / */
+/*     data minmag(1),minmag(2) / :10000000000, :00000100000 / */
+/*     data maxmag(1),maxmag(2) / :17777777777, :37777677776 / */
+
+/*     Machine constants for the VAX-11. */
+
+/*     data mcheps(1),mcheps(2) /   9472,  0 / */
+/*     data minmag(1),minmag(2) /    128,  0 / */
+/*     data maxmag(1),maxmag(2) / -32769, -1 / */
+
+/*     Machine constants for IEEE machines. */
+
+/*    data dmach(1) /2.22044604926d-16/ */
+/*    data dmach(2) /2.22507385852d-308/ */
+/*    data dmach(3) /1.79769313485d+308/ */
+
+    switch(i) {
+        case 1:
+            return DPMPAR(real,EPSILON); /* 2.2204460492503131e-16 | 1.19209290e-07F */
+        case 2:
+            return DPMPAR(real,MIN);    /* 2.2250738585072014e-308 | 1.17549435e-38F */
+        default:
+            return DPMPAR(real,MAX);    /* 1.7976931348623157e+308 | 3.40282347e+38F */
+    }
+
+/*     Last card of function dpmpar. */
+
+} /* dpmpar_ */
+
+#undef mcheps
+#undef maxmag
+#undef minmag
+#undef dmach
+
+
diff --git a/ast/cminpack/enorm.c b/ast/cminpack/enorm.c
new file mode 100644
index 0000000..ad10824
--- /dev/null
+++ b/ast/cminpack/enorm.c
@@ -0,0 +1,157 @@
+#include "cminpack.h"
+#include <math.h>
+#include "cminpackP.h"
+
+/*
+  About the values for rdwarf and rgiant.
+
+  The original values, both in signe-precision FORTRAN source code and in double-precision code were:
+#define rdwarf 3.834e-20
+#define rgiant 1.304e19
+  See for example:
+    http://www.netlib.org/slatec/src/denorm.f
+    http://www.netlib.org/slatec/src/enorm.f
+  However, rdwarf is smaller than sqrt(FLT_MIN) = 1.0842021724855044e-19, so that rdwarf**2 will
+  underflow. This contradicts the constraints expressed in the comments below.
+
+  We changed these constants to be sqrt(dpmpar(2))*0.9 and sqrt(dpmpar(3))*0.9, as proposed by the
+  implementation found in MPFIT http://cow.physics.wisc.edu/~craigm/idl/fitting.html
+*/
+
+#define double_dwarf (1.4916681462400413e-154*0.9)
+#define double_giant (1.3407807929942596e+154*0.9)
+#define float_dwarf (1.0842021724855044e-19f*0.9f)
+#define float_giant (1.8446743523953730e+19f*0.9f)
+#define half_dwarf (2.4414062505039999e-4f*0.9f)
+#define half_giant (255.93749236874225497222f*0.9f)
+
+#define dwarf(type) _dwarf(type)
+#define _dwarf(type) type ## _dwarf
+#define giant(type) _giant(type)
+#define _giant(type) type ## _giant
+
+#define rdwarf dwarf(real)
+#define rgiant giant(real)
+
+__cminpack_attr__
+real __cminpack_func__(enorm)(int n, const real *x)
+{
+#ifdef USE_CBLAS
+    return cblas_dnrm2(n, x, 1);
+#else /* !USE_CBLAS */
+    /* System generated locals */
+    real ret_val, d1;
+
+    /* Local variables */
+    int i;
+    real s1, s2, s3, xabs, x1max, x3max, agiant, floatn;
+
+/*     ********** */
+
+/*     function enorm */
+
+/*     given an n-vector x, this function calculates the */
+/*     euclidean norm of x. */
+
+/*     the euclidean norm is computed by accumulating the sum of */
+/*     squares in three different sums. the sums of squares for the */
+/*     small and large components are scaled so that no overflows */
+/*     occur. non-destructive underflows are permitted. underflows */
+/*     and overflows do not occur in the computation of the unscaled */
+/*     sum of squares for the intermediate components. */
+/*     the definitions of small, intermediate and large components */
+/*     depend on two constants, rdwarf and rgiant. the main */
+/*     restrictions on these constants are that rdwarf**2 not */
+/*     underflow and rgiant**2 not overflow. the constants */
+/*     given here are suitable for every known computer. */
+
+/*     the function statement is */
+
+/*       double precision function enorm(n,x) */
+
+/*     where */
+
+/*       n is a positive integer input variable. */
+
+/*       x is an input array of length n. */
+
+/*     subprograms called */
+
+/*       fortran-supplied ... dabs,dsqrt */
+
+/*     argonne national laboratory. minpack project. march 1980. */
+/*     burton s. garbow, kenneth e. hillstrom, jorge j. more */
+
+/*     ********** */
+
+    s1 = 0.;
+    s2 = 0.;
+    s3 = 0.;
+    x1max = 0.;
+    x3max = 0.;
+    floatn = (real) (n);
+    agiant = rgiant / floatn;
+    for (i = 0; i < n; ++i) {
+	xabs = fabs(x[i]);
+	if (xabs <= rdwarf || xabs >= agiant) {
+            if (xabs > rdwarf) {
+
+/*              sum for large components. */
+
+                if (xabs > x1max) {
+                    /* Computing 2nd power */
+                    d1 = x1max / xabs;
+                    s1 = 1. + s1 * (d1 * d1);
+                    x1max = xabs;
+                } else {
+                    /* Computing 2nd power */
+                    d1 = xabs / x1max;
+                    s1 += d1 * d1;
+                }
+            } else {
+
+/*              sum for small components. */
+
+                if (xabs > x3max) {
+                    /* Computing 2nd power */
+                    d1 = x3max / xabs;
+                    s3 = 1. + s3 * (d1 * d1);
+                    x3max = xabs;
+                } else {
+                    if (xabs != 0.) {
+                        /* Computing 2nd power */
+                        d1 = xabs / x3max;
+                        s3 += d1 * d1;
+                    }
+                }
+            }
+	} else {
+
+/*           sum for intermediate components. */
+
+            /* Computing 2nd power */
+            s2 += xabs * xabs;
+        }
+    }
+
+/*     calculation of norm. */
+
+    if (s1 != 0.) {
+        ret_val = x1max * sqrt(s1 + (s2 / x1max) / x1max);
+    } else {
+        if (s2 != 0.) {
+            if (s2 >= x3max) {
+                ret_val = sqrt(s2 * (1. + (x3max / s2) * (x3max * s3)));
+            } else {
+                ret_val = sqrt(x3max * ((s2 / x3max) + (x3max * s3)));
+            }
+        } else {
+            ret_val = x3max * sqrt(s3);
+        }
+    }
+    return ret_val;
+
+/*     last card of function enorm. */
+#endif /* !USE_CBLAS */
+} /* enorm_ */
+
diff --git a/ast/cminpack/lmder.c b/ast/cminpack/lmder.c
new file mode 100644
index 0000000..7f57428
--- /dev/null
+++ b/ast/cminpack/lmder.c
@@ -0,0 +1,526 @@
+#include "cminpack.h"
+#include <math.h>
+#include "cminpackP.h"
+
+__cminpack_attr__
+int __cminpack_func__(lmder)(__cminpack_decl_fcnder_mn__ void *p, int m, int n, real *x, 
+	real *fvec, real *fjac, int ldfjac, real ftol,
+	real xtol, real gtol, int maxfev, real *
+	diag, int mode, real factor, int nprint,
+	int *nfev, int *njev, int *ipvt, real *qtf, 
+	real *wa1, real *wa2, real *wa3, real *wa4)
+{
+    /* Initialized data */
+
+#define p1 .1
+#define p5 .5
+#define p25 .25
+#define p75 .75
+#define p0001 1e-4
+
+    /* System generated locals */
+    real d1, d2;
+
+    /* Local variables */
+    int i, j, l;
+    real par, sum;
+    int iter;
+    real temp, temp1, temp2;
+    int iflag;
+    real delta = 0.;
+    real ratio;
+    real fnorm, gnorm, pnorm, xnorm = 0., fnorm1, actred, dirder, 
+	    epsmch, prered;
+    int info;
+
+/*     ********** */
+
+/*     subroutine lmder */
+
+/*     the purpose of lmder is to minimize the sum of the squares of */
+/*     m nonlinear functions in n variables by a modification of */
+/*     the levenberg-marquardt algorithm. the user must provide a */
+/*     subroutine which calculates the functions and the jacobian. */
+
+/*     the subroutine statement is */
+
+/*       subroutine lmder(fcn,m,n,x,fvec,fjac,ldfjac,ftol,xtol,gtol, */
+/*                        maxfev,diag,mode,factor,nprint,info,nfev, */
+/*                        njev,ipvt,qtf,wa1,wa2,wa3,wa4) */
+
+/*     where */
+
+/*       fcn is the name of the user-supplied subroutine which */
+/*         calculates the functions and the jacobian. fcn must */
+/*         be declared in an external statement in the user */
+/*         calling program, and should be written as follows. */
+
+/*         subroutine fcn(m,n,x,fvec,fjac,ldfjac,iflag) */
+/*         integer m,n,ldfjac,iflag */
+/*         double precision x(n),fvec(m),fjac(ldfjac,n) */
+/*         ---------- */
+/*         if iflag = 1 calculate the functions at x and */
+/*         return this vector in fvec. do not alter fjac. */
+/*         if iflag = 2 calculate the jacobian at x and */
+/*         return this matrix in fjac. do not alter fvec. */
+/*         ---------- */
+/*         return */
+/*         end */
+
+/*         the value of iflag should not be changed by fcn unless */
+/*         the user wants to terminate execution of lmder. */
+/*         in this case set iflag to a negative integer. */
+
+/*       m is a positive integer input variable set to the number */
+/*         of functions. */
+
+/*       n is a positive integer input variable set to the number */
+/*         of variables. n must not exceed m. */
+
+/*       x is an array of length n. on input x must contain */
+/*         an initial estimate of the solution vector. on output x */
+/*         contains the final estimate of the solution vector. */
+
+/*       fvec is an output array of length m which contains */
+/*         the functions evaluated at the output x. */
+
+/*       fjac is an output m by n array. the upper n by n submatrix */
+/*         of fjac contains an upper triangular matrix r with */
+/*         diagonal elements of nonincreasing magnitude such that */
+
+/*                t     t           t */
+/*               p *(jac *jac)*p = r *r, */
+
+/*         where p is a permutation matrix and jac is the final */
+/*         calculated jacobian. column j of p is column ipvt(j) */
+/*         (see below) of the identity matrix. the lower trapezoidal */
+/*         part of fjac contains information generated during */
+/*         the computation of r. */
+
+/*       ldfjac is a positive integer input variable not less than m */
+/*         which specifies the leading dimension of the array fjac. */
+
+/*       ftol is a nonnegative input variable. termination */
+/*         occurs when both the actual and predicted relative */
+/*         reductions in the sum of squares are at most ftol. */
+/*         therefore, ftol measures the relative error desired */
+/*         in the sum of squares. */
+
+/*       xtol is a nonnegative input variable. termination */
+/*         occurs when the relative error between two consecutive */
+/*         iterates is at most xtol. therefore, xtol measures the */
+/*         relative error desired in the approximate solution. */
+
+/*       gtol is a nonnegative input variable. termination */
+/*         occurs when the cosine of the angle between fvec and */
+/*         any column of the jacobian is at most gtol in absolute */
+/*         value. therefore, gtol measures the orthogonality */
+/*         desired between the function vector and the columns */
+/*         of the jacobian. */
+
+/*       maxfev is a positive integer input variable. termination */
+/*         occurs when the number of calls to fcn with iflag = 1 */
+/*         has reached maxfev. */
+
+/*       diag is an array of length n. if mode = 1 (see */
+/*         below), diag is internally set. if mode = 2, diag */
+/*         must contain positive entries that serve as */
+/*         multiplicative scale factors for the variables. */
+
+/*       mode is an integer input variable. if mode = 1, the */
+/*         variables will be scaled internally. if mode = 2, */
+/*         the scaling is specified by the input diag. other */
+/*         values of mode are equivalent to mode = 1. */
+
+/*       factor is a positive input variable used in determining the */
+/*         initial step bound. this bound is set to the product of */
+/*         factor and the euclidean norm of diag*x if nonzero, or else */
+/*         to factor itself. in most cases factor should lie in the */
+/*         interval (.1,100.).100. is a generally recommended value. */
+
+/*       nprint is an integer input variable that enables controlled */
+/*         printing of iterates if it is positive. in this case, */
+/*         fcn is called with iflag = 0 at the beginning of the first */
+/*         iteration and every nprint iterations thereafter and */
+/*         immediately prior to return, with x, fvec, and fjac */
+/*         available for printing. fvec and fjac should not be */
+/*         altered. if nprint is not positive, no special calls */
+/*         of fcn with iflag = 0 are made. */
+
+/*       info is an integer output variable. if the user has */
+/*         terminated execution, info is set to the (negative) */
+/*         value of iflag. see description of fcn. otherwise, */
+/*         info is set as follows. */
+
+/*         info = 0  improper input parameters. */
+
+/*         info = 1  both actual and predicted relative reductions */
+/*                   in the sum of squares are at most ftol. */
+
+/*         info = 2  relative error between two consecutive iterates */
+/*                   is at most xtol. */
+
+/*         info = 3  conditions for info = 1 and info = 2 both hold. */
+
+/*         info = 4  the cosine of the angle between fvec and any */
+/*                   column of the jacobian is at most gtol in */
+/*                   absolute value. */
+
+/*         info = 5  number of calls to fcn with iflag = 1 has */
+/*                   reached maxfev. */
+
+/*         info = 6  ftol is too small. no further reduction in */
+/*                   the sum of squares is possible. */
+
+/*         info = 7  xtol is too small. no further improvement in */
+/*                   the approximate solution x is possible. */
+
+/*         info = 8  gtol is too small. fvec is orthogonal to the */
+/*                   columns of the jacobian to machine precision. */
+
+/*       nfev is an integer output variable set to the number of */
+/*         calls to fcn with iflag = 1. */
+
+/*       njev is an integer output variable set to the number of */
+/*         calls to fcn with iflag = 2. */
+
+/*       ipvt is an integer output array of length n. ipvt */
+/*         defines a permutation matrix p such that jac*p = q*r, */
+/*         where jac is the final calculated jacobian, q is */
+/*         orthogonal (not stored), and r is upper triangular */
+/*         with diagonal elements of nonincreasing magnitude. */
+/*         column j of p is column ipvt(j) of the identity matrix. */
+
+/*       qtf is an output array of length n which contains */
+/*         the first n elements of the vector (q transpose)*fvec. */
+
+/*       wa1, wa2, and wa3 are work arrays of length n. */
+
+/*       wa4 is a work array of length m. */
+
+/*     subprograms called */
+
+/*       user-supplied ...... fcn */
+
+/*       minpack-supplied ... dpmpar,enorm,lmpar,qrfac */
+
+/*       fortran-supplied ... dabs,dmax1,dmin1,dsqrt,mod */
+
+/*     argonne national laboratory. minpack project. march 1980. */
+/*     burton s. garbow, kenneth e. hillstrom, jorge j. more */
+
+/*     ********** */
+
+/*     epsmch is the machine precision. */
+
+    epsmch = __cminpack_func__(dpmpar)(1);
+
+    info = 0;
+    iflag = 0;
+    *nfev = 0;
+    *njev = 0;
+
+/*     check the input parameters for errors. */
+
+    if (n <= 0 || m < n || ldfjac < m || ftol < 0. || xtol < 0. || 
+	    gtol < 0. || maxfev <= 0 || factor <= 0.) {
+	goto TERMINATE;
+    }
+    if (mode == 2) {
+        for (j = 0; j < n; ++j) {
+            if (diag[j] <= 0.) {
+                goto TERMINATE;
+            }
+        }
+    }
+
+/*     evaluate the function at the starting point */
+/*     and calculate its norm. */
+
+    iflag = fcnder_mn(p, m, n, x, fvec, fjac, ldfjac, 1);
+    *nfev = 1;
+    if (iflag < 0) {
+	goto TERMINATE;
+    }
+    fnorm = __cminpack_enorm__(m, fvec);
+
+/*     initialize levenberg-marquardt parameter and iteration counter. */
+
+    par = 0.;
+    iter = 1;
+
+/*     beginning of the outer loop. */
+
+    for (;;) {
+
+/*        calculate the jacobian matrix. */
+
+        iflag = fcnder_mn(p, m, n, x, fvec, fjac, ldfjac, 2);
+        ++(*njev);
+        if (iflag < 0) {
+            goto TERMINATE;
+        }
+
+/*        if requested, call fcn to enable printing of iterates. */
+
+        if (nprint > 0) {
+            iflag = 0;
+            if ((iter - 1) % nprint == 0) {
+                iflag = fcnder_mn(p, m, n, x, fvec, fjac, ldfjac, 0);
+            }
+            if (iflag < 0) {
+                goto TERMINATE;
+            }
+        }
+
+/*        compute the qr factorization of the jacobian. */
+
+        __cminpack_func__(qrfac)(m, n, fjac, ldfjac, TRUE_, ipvt, n,
+              wa1, wa2, wa3);
+
+/*        on the first iteration and if mode is 1, scale according */
+/*        to the norms of the columns of the initial jacobian. */
+
+        if (iter == 1) {
+            if (mode != 2) {
+                for (j = 0; j < n; ++j) {
+                    diag[j] = wa2[j];
+                    if (wa2[j] == 0.) {
+                        diag[j] = 1.;
+                    }
+                }
+            }
+
+/*        on the first iteration, calculate the norm of the scaled x */
+/*        and initialize the step bound delta. */
+
+            for (j = 0; j < n; ++j) {
+                wa3[j] = diag[j] * x[j];
+            }
+            xnorm = __cminpack_enorm__(n, wa3);
+            delta = factor * xnorm;
+            if (delta == 0.) {
+                delta = factor;
+            }
+        }
+
+/*        form (q transpose)*fvec and store the first n components in */
+/*        qtf. */
+
+        for (i = 0; i < m; ++i) {
+            wa4[i] = fvec[i];
+        }
+        for (j = 0; j < n; ++j) {
+            if (fjac[j + j * ldfjac] != 0.) {
+                sum = 0.;
+                for (i = j; i < m; ++i) {
+                    sum += fjac[i + j * ldfjac] * wa4[i];
+                }
+                temp = -sum / fjac[j + j * ldfjac];
+                for (i = j; i < m; ++i) {
+                    wa4[i] += fjac[i + j * ldfjac] * temp;
+                }
+            }
+            fjac[j + j * ldfjac] = wa1[j];
+            qtf[j] = wa4[j];
+        }
+
+/*        compute the norm of the scaled gradient. */
+
+        gnorm = 0.;
+        if (fnorm != 0.) {
+            for (j = 0; j < n; ++j) {
+                l = ipvt[j]-1;
+                if (wa2[l] != 0.) {
+                    sum = 0.;
+                    for (i = 0; i <= j; ++i) {
+                        sum += fjac[i + j * ldfjac] * (qtf[i] / fnorm);
+                    }
+                    /* Computing MAX */
+                    d1 = fabs(sum / wa2[l]);
+                    gnorm = max(gnorm,d1);
+                }
+            }
+        }
+
+/*        test for convergence of the gradient norm. */
+
+        if (gnorm <= gtol) {
+            info = 4;
+        }
+        if (info != 0) {
+            goto TERMINATE;
+        }
+
+/*        rescale if necessary. */
+
+        if (mode != 2) {
+            for (j = 0; j < n; ++j) {
+                /* Computing MAX */
+                d1 = diag[j], d2 = wa2[j];
+                diag[j] = max(d1,d2);
+            }
+        }
+
+/*        beginning of the inner loop. */
+
+        do {
+
+/*           determine the levenberg-marquardt parameter. */
+
+            __cminpack_func__(lmpar)(n, fjac, ldfjac, ipvt, diag, qtf, delta,
+                  &par, wa1, wa2, wa3, wa4);
+
+/*           store the direction p and x + p. calculate the norm of p. */
+
+            for (j = 0; j < n; ++j) {
+                wa1[j] = -wa1[j];
+                wa2[j] = x[j] + wa1[j];
+                wa3[j] = diag[j] * wa1[j];
+            }
+            pnorm = __cminpack_enorm__(n, wa3);
+
+/*           on the first iteration, adjust the initial step bound. */
+
+            if (iter == 1) {
+                delta = min(delta,pnorm);
+            }
+
+/*           evaluate the function at x + p and calculate its norm. */
+
+            iflag = fcnder_mn(p, m, n, wa2, wa4, fjac, ldfjac, 1);
+            ++(*nfev);
+            if (iflag < 0) {
+                goto TERMINATE;
+            }
+            fnorm1 = __cminpack_enorm__(m, wa4);
+
+/*           compute the scaled actual reduction. */
+
+            actred = -1.;
+            if (p1 * fnorm1 < fnorm) {
+                /* Computing 2nd power */
+                d1 = fnorm1 / fnorm;
+                actred = 1. - d1 * d1;
+            }
+
+/*           compute the scaled predicted reduction and */
+/*           the scaled directional derivative. */
+
+            for (j = 0; j < n; ++j) {
+                wa3[j] = 0.;
+                l = ipvt[j]-1;
+                temp = wa1[l];
+                for (i = 0; i <= j; ++i) {
+                    wa3[i] += fjac[i + j * ldfjac] * temp;
+                }
+            }
+            temp1 = __cminpack_enorm__(n, wa3) / fnorm;
+            temp2 = (sqrt(par) * pnorm) / fnorm;
+            prered = temp1 * temp1 + temp2 * temp2 / p5;
+            dirder = -(temp1 * temp1 + temp2 * temp2);
+
+/*           compute the ratio of the actual to the predicted */
+/*           reduction. */
+
+            ratio = 0.;
+            if (prered != 0.) {
+                ratio = actred / prered;
+            }
+
+/*           update the step bound. */
+
+            if (ratio <= p25) {
+                if (actred >= 0.) {
+                    temp = p5;
+                } else {
+                    temp = p5 * dirder / (dirder + p5 * actred);
+                }
+                if (p1 * fnorm1 >= fnorm || temp < p1) {
+                    temp = p1;
+                }
+                /* Computing MIN */
+                d1 = pnorm / p1;
+                delta = temp * min(delta,d1);
+                par /= temp;
+            } else {
+                if (par == 0. || ratio >= p75) {
+                    delta = pnorm / p5;
+                    par = p5 * par;
+                }
+            }
+
+/*           test for successful iteration. */
+
+            if (ratio >= p0001) {
+
+/*           successful iteration. update x, fvec, and their norms. */
+
+                for (j = 0; j < n; ++j) {
+                    x[j] = wa2[j];
+                    wa2[j] = diag[j] * x[j];
+                }
+                for (i = 0; i < m; ++i) {
+                    fvec[i] = wa4[i];
+                }
+                xnorm = __cminpack_enorm__(n, wa2);
+                fnorm = fnorm1;
+                ++iter;
+            }
+
+/*           tests for convergence. */
+
+            if (fabs(actred) <= ftol && prered <= ftol && p5 * ratio <= 1.) {
+                info = 1;
+            }
+            if (delta <= xtol * xnorm) {
+                info = 2;
+            }
+            if (fabs(actred) <= ftol && prered <= ftol && p5 * ratio <= 1. && info == 2) {
+                info = 3;
+            }
+            if (info != 0) {
+                goto TERMINATE;
+            }
+
+/*           tests for termination and stringent tolerances. */
+
+            if (*nfev >= maxfev) {
+                info = 5;
+            }
+            if (fabs(actred) <= epsmch && prered <= epsmch && p5 * ratio <= 1.) {
+                info = 6;
+            }
+            if (delta <= epsmch * xnorm) {
+                info = 7;
+            }
+            if (gnorm <= epsmch) {
+                info = 8;
+            }
+            if (info != 0) {
+                goto TERMINATE;
+            }
+
+/*           end of the inner loop. repeat if iteration unsuccessful. */
+
+        } while (ratio < p0001);
+
+/*        end of the outer loop. */
+
+    }
+TERMINATE:
+
+/*     termination, either normal or user imposed. */
+
+    if (iflag < 0) {
+	info = iflag;
+    }
+    if (nprint > 0) {
+	fcnder_mn(p, m, n, x, fvec, fjac, ldfjac, 0);
+    }
+    return info;
+
+/*     last card of subroutine lmder. */
+
+} /* lmder_ */
+
diff --git a/ast/cminpack/lmder1.c b/ast/cminpack/lmder1.c
new file mode 100644
index 0000000..581462e
--- /dev/null
+++ b/ast/cminpack/lmder1.c
@@ -0,0 +1,167 @@
+#include "cminpack.h"
+#include "cminpackP.h"
+
+__cminpack_attr__
+int __cminpack_func__(lmder1)(__cminpack_decl_fcnder_mn__ void *p, int m, int n, real *x, 
+	real *fvec, real *fjac, int ldfjac, real tol, 
+	int *ipvt, real *wa, int lwa)
+{
+    /* Initialized data */
+
+    const real factor = 100.;
+
+    /* Local variables */
+    int mode, nfev, njev;
+    real ftol, gtol, xtol;
+    int maxfev, nprint;
+    int info;
+
+/*     ********** */
+
+/*     subroutine lmder1 */
+
+/*     the purpose of lmder1 is to minimize the sum of the squares of */
+/*     m nonlinear functions in n variables by a modification of the */
+/*     levenberg-marquardt algorithm. this is done by using the more */
+/*     general least-squares solver lmder. the user must provide a */
+/*     subroutine which calculates the functions and the jacobian. */
+
+/*     the subroutine statement is */
+
+/*       subroutine lmder1(fcn,m,n,x,fvec,fjac,ldfjac,tol,info, */
+/*                         ipvt,wa,lwa) */
+
+/*     where */
+
+/*       fcn is the name of the user-supplied subroutine which */
+/*         calculates the functions and the jacobian. fcn must */
+/*         be declared in an external statement in the user */
+/*         calling program, and should be written as follows. */
+
+/*         subroutine fcn(m,n,x,fvec,fjac,ldfjac,iflag) */
+/*         integer m,n,ldfjac,iflag */
+/*         double precision x(n),fvec(m),fjac(ldfjac,n) */
+/*         ---------- */
+/*         if iflag = 1 calculate the functions at x and */
+/*         return this vector in fvec. do not alter fjac. */
+/*         if iflag = 2 calculate the jacobian at x and */
+/*         return this matrix in fjac. do not alter fvec. */
+/*         ---------- */
+/*         return */
+/*         end */
+
+/*         the value of iflag should not be changed by fcn unless */
+/*         the user wants to terminate execution of lmder1. */
+/*         in this case set iflag to a negative integer. */
+
+/*       m is a positive integer input variable set to the number */
+/*         of functions. */
+
+/*       n is a positive integer input variable set to the number */
+/*         of variables. n must not exceed m. */
+
+/*       x is an array of length n. on input x must contain */
+/*         an initial estimate of the solution vector. on output x */
+/*         contains the final estimate of the solution vector. */
+
+/*       fvec is an output array of length m which contains */
+/*         the functions evaluated at the output x. */
+
+/*       fjac is an output m by n array. the upper n by n submatrix */
+/*         of fjac contains an upper triangular matrix r with */
+/*         diagonal elements of nonincreasing magnitude such that */
+
+/*                t     t           t */
+/*               p *(jac *jac)*p = r *r, */
+
+/*         where p is a permutation matrix and jac is the final */
+/*         calculated jacobian. column j of p is column ipvt(j) */
+/*         (see below) of the identity matrix. the lower trapezoidal */
+/*         part of fjac contains information generated during */
+/*         the computation of r. */
+
+/*       ldfjac is a positive integer input variable not less than m */
+/*         which specifies the leading dimension of the array fjac. */
+
+/*       tol is a nonnegative input variable. termination occurs */
+/*         when the algorithm estimates either that the relative */
+/*         error in the sum of squares is at most tol or that */
+/*         the relative error between x and the solution is at */
+/*         most tol. */
+
+/*       info is an integer output variable. if the user has */
+/*         terminated execution, info is set to the (negative) */
+/*         value of iflag. see description of fcn. otherwise, */
+/*         info is set as follows. */
+
+/*         info = 0  improper input parameters. */
+
+/*         info = 1  algorithm estimates that the relative error */
+/*                   in the sum of squares is at most tol. */
+
+/*         info = 2  algorithm estimates that the relative error */
+/*                   between x and the solution is at most tol. */
+
+/*         info = 3  conditions for info = 1 and info = 2 both hold. */
+
+/*         info = 4  fvec is orthogonal to the columns of the */
+/*                   jacobian to machine precision. */
+
+/*         info = 5  number of calls to fcn with iflag = 1 has */
+/*                   reached 100*(n+1). */
+
+/*         info = 6  tol is too small. no further reduction in */
+/*                   the sum of squares is possible. */
+
+/*         info = 7  tol is too small. no further improvement in */
+/*                   the approximate solution x is possible. */
+
+/*       ipvt is an integer output array of length n. ipvt */
+/*         defines a permutation matrix p such that jac*p = q*r, */
+/*         where jac is the final calculated jacobian, q is */
+/*         orthogonal (not stored), and r is upper triangular */
+/*         with diagonal elements of nonincreasing magnitude. */
+/*         column j of p is column ipvt(j) of the identity matrix. */
+
+/*       wa is a work array of length lwa. */
+
+/*       lwa is a positive integer input variable not less than 5*n+m. */
+
+/*     subprograms called */
+
+/*       user-supplied ...... fcn */
+
+/*       minpack-supplied ... lmder */
+
+/*     argonne national laboratory. minpack project. march 1980. */
+/*     burton s. garbow, kenneth e. hillstrom, jorge j. more */
+
+/*     ********** */
+
+/*     check the input parameters for errors. */
+
+    if (n <= 0 || m < n || ldfjac < m || tol < 0. || lwa < n * 5 + m) {
+        return 0;
+    }
+
+/*     call lmder. */
+
+    maxfev = (n + 1) * 100;
+    ftol = tol;
+    xtol = tol;
+    gtol = 0.;
+    mode = 1;
+    nprint = 0;
+    info = __cminpack_func__(lmder)(__cminpack_param_fcnder_mn__ p, m, n, x, fvec, fjac, ldfjac,
+	    ftol, xtol, gtol, maxfev, wa, mode, factor, nprint, 
+	    &nfev, &njev, ipvt, &wa[n], &wa[(n << 1)], &
+	    wa[n * 3], &wa[(n << 2)], &wa[n * 5]);
+    if (info == 8) {
+	info = 4;
+    }
+    return info;
+
+/*     last card of subroutine lmder1. */
+
+} /* lmder1_ */
+
diff --git a/ast/cminpack/lmpar.c b/ast/cminpack/lmpar.c
new file mode 100644
index 0000000..108e687
--- /dev/null
+++ b/ast/cminpack/lmpar.c
@@ -0,0 +1,338 @@
+/* lmpar.f -- translated by f2c (version 20020621).
+   You must link the resulting object file with the libraries:
+	-lf2c -lm   (in that order)
+*/
+
+#include "cminpack.h"
+#include <math.h>
+#include "cminpackP.h"
+
+__cminpack_attr__
+void __cminpack_func__(lmpar)(int n, real *r, int ldr, 
+	const int *ipvt, const real *diag, const real *qtb, real delta, 
+	real *par, real *x, real *sdiag, real *wa1, 
+	real *wa2)
+{
+    /* Initialized data */
+
+#define p1 .1
+#define p001 .001
+
+    /* System generated locals */
+    real d1, d2;
+
+    /* Local variables */
+    int j, l;
+    real fp;
+    real parc, parl;
+    int iter;
+    real temp, paru, dwarf;
+    int nsing;
+    real gnorm;
+    real dxnorm;
+
+/*     ********** */
+
+/*     subroutine lmpar */
+
+/*     given an m by n matrix a, an n by n nonsingular diagonal */
+/*     matrix d, an m-vector b, and a positive number delta, */
+/*     the problem is to determine a value for the parameter */
+/*     par such that if x solves the system */
+
+/*           a*x = b ,     sqrt(par)*d*x = 0 , */
+
+/*     in the least squares sense, and dxnorm is the euclidean */
+/*     norm of d*x, then either par is zero and */
+
+/*           (dxnorm-delta) .le. 0.1*delta , */
+
+/*     or par is positive and */
+
+/*           abs(dxnorm-delta) .le. 0.1*delta . */
+
+/*     this subroutine completes the solution of the problem */
+/*     if it is provided with the necessary information from the */
+/*     qr factorization, with column pivoting, of a. that is, if */
+/*     a*p = q*r, where p is a permutation matrix, q has orthogonal */
+/*     columns, and r is an upper triangular matrix with diagonal */
+/*     elements of nonincreasing magnitude, then lmpar expects */
+/*     the full upper triangle of r, the permutation matrix p, */
+/*     and the first n components of (q transpose)*b. on output */
+/*     lmpar also provides an upper triangular matrix s such that */
+
+/*            t   t                   t */
+/*           p *(a *a + par*d*d)*p = s *s . */
+
+/*     s is employed within lmpar and may be of separate interest. */
+
+/*     only a few iterations are generally needed for convergence */
+/*     of the algorithm. if, however, the limit of 10 iterations */
+/*     is reached, then the output par will contain the best */
+/*     value obtained so far. */
+
+/*     the subroutine statement is */
+
+/*       subroutine lmpar(n,r,ldr,ipvt,diag,qtb,delta,par,x,sdiag, */
+/*                        wa1,wa2) */
+
+/*     where */
+
+/*       n is a positive integer input variable set to the order of r. */
+
+/*       r is an n by n array. on input the full upper triangle */
+/*         must contain the full upper triangle of the matrix r. */
+/*         on output the full upper triangle is unaltered, and the */
+/*         strict lower triangle contains the strict upper triangle */
+/*         (transposed) of the upper triangular matrix s. */
+
+/*       ldr is a positive integer input variable not less than n */
+/*         which specifies the leading dimension of the array r. */
+
+/*       ipvt is an integer input array of length n which defines the */
+/*         permutation matrix p such that a*p = q*r. column j of p */
+/*         is column ipvt(j) of the identity matrix. */
+
+/*       diag is an input array of length n which must contain the */
+/*         diagonal elements of the matrix d. */
+
+/*       qtb is an input array of length n which must contain the first */
+/*         n elements of the vector (q transpose)*b. */
+
+/*       delta is a positive input variable which specifies an upper */
+/*         bound on the euclidean norm of d*x. */
+
+/*       par is a nonnegative variable. on input par contains an */
+/*         initial estimate of the levenberg-marquardt parameter. */
+/*         on output par contains the final estimate. */
+
+/*       x is an output array of length n which contains the least */
+/*         squares solution of the system a*x = b, sqrt(par)*d*x = 0, */
+/*         for the output par. */
+
+/*       sdiag is an output array of length n which contains the */
+/*         diagonal elements of the upper triangular matrix s. */
+
+/*       wa1 and wa2 are work arrays of length n. */
+
+/*     subprograms called */
+
+/*       minpack-supplied ... dpmpar,enorm,qrsolv */
+
+/*       fortran-supplied ... dabs,dmax1,dmin1,dsqrt */
+
+/*     argonne national laboratory. minpack project. march 1980. */
+/*     burton s. garbow, kenneth e. hillstrom, jorge j. more */
+
+/*     ********** */
+
+/*     dwarf is the smallest positive magnitude. */
+
+    dwarf = __cminpack_func__(dpmpar)(2);
+
+/*     compute and store in x the gauss-newton direction. if the */
+/*     jacobian is rank-deficient, obtain a least squares solution. */
+
+    nsing = n;
+    for (j = 0; j < n; ++j) {
+	wa1[j] = qtb[j];
+	if (r[j + j * ldr] == 0. && nsing == n) {
+	    nsing = j;
+	}
+	if (nsing < n) {
+	    wa1[j] = 0.;
+	}
+    }
+# ifdef USE_CBLAS
+    cblas_dtrsv(CblasColMajor, CblasUpper, CblasNoTrans, CblasNonUnit, nsing, r, ldr, wa1, 1);
+# else
+    if (nsing >= 1) {
+        int k;
+        for (k = 1; k <= nsing; ++k) {
+            j = nsing - k;
+            wa1[j] /= r[j + j * ldr];
+            temp = wa1[j];
+            if (j >= 1) {
+                int i;
+                for (i = 0; i < j; ++i) {
+                    wa1[i] -= r[i + j * ldr] * temp;
+                }
+            }
+        }
+    }
+# endif
+    for (j = 0; j < n; ++j) {
+	l = ipvt[j]-1;
+	x[l] = wa1[j];
+    }
+
+/*     initialize the iteration counter. */
+/*     evaluate the function at the origin, and test */
+/*     for acceptance of the gauss-newton direction. */
+
+    iter = 0;
+    for (j = 0; j < n; ++j) {
+	wa2[j] = diag[j] * x[j];
+    }
+    dxnorm = __cminpack_enorm__(n, wa2);
+    fp = dxnorm - delta;
+    if (fp <= p1 * delta) {
+	goto TERMINATE;
+    }
+
+/*     if the jacobian is not rank deficient, the newton */
+/*     step provides a lower bound, parl, for the zero of */
+/*     the function. otherwise set this bound to zero. */
+
+    parl = 0.;
+    if (nsing >= n) {
+        for (j = 0; j < n; ++j) {
+            l = ipvt[j]-1;
+            wa1[j] = diag[l] * (wa2[l] / dxnorm);
+        }
+#     ifdef USE_CBLAS
+        cblas_dtrsv(CblasColMajor, CblasUpper, CblasTrans, CblasNonUnit, n, r, ldr, wa1, 1);
+#     else
+        for (j = 0; j < n; ++j) {
+            real sum = 0.;
+            if (j >= 1) {
+                int i;
+                for (i = 0; i < j; ++i) {
+                    sum += r[i + j * ldr] * wa1[i];
+                }
+            }
+            wa1[j] = (wa1[j] - sum) / r[j + j * ldr];
+        }
+#     endif
+        temp = __cminpack_enorm__(n, wa1);
+        parl = fp / delta / temp / temp;
+    }
+
+/*     calculate an upper bound, paru, for the zero of the function. */
+
+    for (j = 0; j < n; ++j) {
+        real sum;
+#     ifdef USE_CBLAS
+        sum = cblas_ddot(j+1, &r[j*ldr], 1, qtb, 1);
+#     else
+        int i;
+        sum = 0.;
+        for (i = 0; i <= j; ++i) {
+            sum += r[i + j * ldr] * qtb[i];
+        }
+#     endif
+        l = ipvt[j]-1;
+        wa1[j] = sum / diag[l];
+    }
+    gnorm = __cminpack_enorm__(n, wa1);
+    paru = gnorm / delta;
+    if (paru == 0.) {
+        paru = dwarf / min(delta,(real)p1) /* / p001 ??? */;
+    }
+
+/*     if the input par lies outside of the interval (parl,paru), */
+/*     set par to the closer endpoint. */
+
+    *par = max(*par,parl);
+    *par = min(*par,paru);
+    if (*par == 0.) {
+        *par = gnorm / dxnorm;
+    }
+
+/*     beginning of an iteration. */
+
+    for (;;) {
+        ++iter;
+
+/*        evaluate the function at the current value of par. */
+
+        if (*par == 0.) {
+            /* Computing MAX */
+            d1 = dwarf, d2 = p001 * paru;
+            *par = max(d1,d2);
+        }
+        temp = sqrt(*par);
+        for (j = 0; j < n; ++j) {
+            wa1[j] = temp * diag[j];
+        }
+        __cminpack_func__(qrsolv)(n, r, ldr, ipvt, wa1, qtb, x, sdiag, wa2);
+        for (j = 0; j < n; ++j) {
+            wa2[j] = diag[j] * x[j];
+        }
+        dxnorm = __cminpack_enorm__(n, wa2);
+        temp = fp;
+        fp = dxnorm - delta;
+
+/*        if the function is small enough, accept the current value */
+/*        of par. also test for the exceptional cases where parl */
+/*        is zero or the number of iterations has reached 10. */
+
+        if (fabs(fp) <= p1 * delta || (parl == 0. && fp <= temp && temp < 0.) || iter == 10) {
+            goto TERMINATE;
+        }
+
+/*        compute the newton correction. */
+
+#     ifdef USE_CBLAS
+        for (j = 0; j < nsing; ++j) {
+            l = ipvt[j]-1;
+            wa1[j] = diag[l] * (wa2[l] / dxnorm);
+        }
+        for (j = nsing; j < n; ++j) {
+            wa1[j] = 0.;
+        }
+        /* exchange the diagonal of r with sdiag */
+        cblas_dswap(n, r, ldr+1, sdiag, 1);
+        /* solve lower(r).x = wa1, result id put in wa1 */
+        cblas_dtrsv(CblasColMajor, CblasLower, CblasNoTrans, CblasNonUnit, nsing, r, ldr, wa1, 1);
+        /* exchange the diagonal of r with sdiag */
+        cblas_dswap( n, r, ldr+1, sdiag, 1);
+#     else /* !USE_CBLAS */
+        for (j = 0; j < n; ++j) {
+            l = ipvt[j]-1;
+            wa1[j] = diag[l] * (wa2[l] / dxnorm);
+        }
+        for (j = 0; j < n; ++j) {
+            wa1[j] /= sdiag[j];
+            temp = wa1[j];
+            if (n > j+1) {
+                int i;
+                for (i = j+1; i < n; ++i) {
+                    wa1[i] -= r[i + j * ldr] * temp;
+                }
+            }
+        }
+#     endif /* !USE_CBLAS */
+        temp = __cminpack_enorm__(n, wa1);
+        parc = fp / delta / temp / temp;
+
+/*        depending on the sign of the function, update parl or paru. */
+
+        if (fp > 0.) {
+            parl = max(parl,*par);
+        }
+        if (fp < 0.) {
+            paru = min(paru,*par);
+        }
+
+/*        compute an improved estimate for par. */
+
+        /* Computing MAX */
+        d1 = parl, d2 = *par + parc;
+        *par = max(d1,d2);
+
+/*        end of an iteration. */
+
+    }
+TERMINATE:
+
+/*     termination. */
+
+    if (iter == 0) {
+	*par = 0.;
+    }
+
+/*     last card of subroutine lmpar. */
+
+} /* lmpar_ */
+
diff --git a/ast/cminpack/qrfac.c b/ast/cminpack/qrfac.c
new file mode 100644
index 0000000..1573772
--- /dev/null
+++ b/ast/cminpack/qrfac.c
@@ -0,0 +1,285 @@
+#include "cminpack.h"
+#include <math.h>
+#ifdef USE_LAPACK
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#endif
+#include "cminpackP.h"
+
+__cminpack_attr__
+void __cminpack_func__(qrfac)(int m, int n, real *a, int
+	lda, int pivot, int *ipvt, int lipvt, real *rdiag,
+	 real *acnorm, real *wa)
+{
+#ifdef USE_LAPACK
+    __CLPK_integer m_ = m;
+    __CLPK_integer n_ = n;
+    __CLPK_integer lda_ = lda;
+    __CLPK_integer *jpvt;
+
+    int i, j, k;
+    double t;
+    double* tau = wa;
+    const __CLPK_integer ltau = m > n ? n : m;
+    __CLPK_integer lwork = -1;
+    __CLPK_integer info = 0;
+    double* work;
+
+    if (pivot) {
+        assert( lipvt >= n );
+        if (sizeof(__CLPK_integer) != sizeof(ipvt[0])) {
+            jpvt = malloc(n*sizeof(__CLPK_integer));
+        } else {
+            /* __CLPK_integer is actually an int, just do a cast */
+            jpvt = (__CLPK_integer *)ipvt;
+        }
+        /* set all columns free */
+        memset(jpvt, 0, sizeof(int)*n);
+    }
+    
+    /* query optimal size of work */
+    lwork = -1;
+    if (pivot) {
+        dgeqp3_(&m_,&n_,a,&lda_,jpvt,tau,tau,&lwork,&info);
+        lwork = (int)tau[0];
+        assert( lwork >= 3*n+1  );
+    } else {
+        dgeqrf_(&m_,&n_,a,&lda_,tau,tau,&lwork,&info);
+        lwork = (int)tau[0];
+        assert( lwork >= 1 && lwork >= n );
+    }
+    
+    assert( info == 0 );
+    
+    /* alloc work area */
+    work = (double *)malloc(sizeof(double)*lwork);
+    assert(work != NULL);
+    
+    /* set acnorm first (from the doc of qrfac, acnorm may point to the same area as rdiag) */
+    if (acnorm != rdiag) {
+        for (j = 0; j < n; ++j) {
+            acnorm[j] = __cminpack_enorm__(m, &a[j * lda]);
+        }
+    }
+    
+    /* QR decomposition */
+    if (pivot) {
+        dgeqp3_(&m_,&n_,a,&lda_,jpvt,tau,work,&lwork,&info);
+    } else {
+        dgeqrf_(&m_,&n_,a,&lda_,tau,work,&lwork,&info);
+    }
+    assert(info == 0);
+    
+    /* set rdiag, before the diagonal is replaced */
+    memset(rdiag, 0, sizeof(double)*n);
+    for(i=0 ; i<n ; ++i) {
+        rdiag[i] = a[i*lda+i];
+    }
+    
+    /* modify lower trinagular part to look like qrfac's output */
+    for(i=0 ; i<ltau ; ++i) {
+        k = i*lda+i;
+        t = tau[i];
+        a[k] = t;
+        for(j=i+1 ; j<m ; j++) {
+            k++;
+            a[k] *= t;
+        }
+    }
+    
+    free(work);
+    if (pivot) {
+        /* convert back jpvt to ipvt */
+        if (sizeof(__CLPK_integer) != sizeof(ipvt[0])) {
+            for(i=0; i<n; ++i) {
+                ipvt[i] = jpvt[i];
+            }
+            free(jpvt);
+        }
+    }
+#else /* !USE_LAPACK */
+    /* Initialized data */
+
+#define p05 .05
+
+    /* System generated locals */
+    real d1;
+
+    /* Local variables */
+    int i, j, k, jp1;
+    real sum;
+    real temp;
+    int minmn;
+    real epsmch;
+    real ajnorm;
+
+/*     ********** */
+
+/*     subroutine qrfac */
+
+/*     this subroutine uses householder transformations with column */
+/*     pivoting (optional) to compute a qr factorization of the */
+/*     m by n matrix a. that is, qrfac determines an orthogonal */
+/*     matrix q, a permutation matrix p, and an upper trapezoidal */
+/*     matrix r with diagonal elements of nonincreasing magnitude, */
+/*     such that a*p = q*r. the householder transformation for */
+/*     column k, k = 1,2,...,min(m,n), is of the form */
+
+/*                           t */
+/*           i - (1/u(k))*u*u */
+
+/*     where u has zeros in the first k-1 positions. the form of */
+/*     this transformation and the method of pivoting first */
+/*     appeared in the corresponding linpack subroutine. */
+
+/*     the subroutine statement is */
+
+/*       subroutine qrfac(m,n,a,lda,pivot,ipvt,lipvt,rdiag,acnorm,wa) */
+
+/*     where */
+
+/*       m is a positive integer input variable set to the number */
+/*         of rows of a. */
+
+/*       n is a positive integer input variable set to the number */
+/*         of columns of a. */
+
+/*       a is an m by n array. on input a contains the matrix for */
+/*         which the qr factorization is to be computed. on output */
+/*         the strict upper trapezoidal part of a contains the strict */
+/*         upper trapezoidal part of r, and the lower trapezoidal */
+/*         part of a contains a factored form of q (the non-trivial */
+/*         elements of the u vectors described above). */
+
+/*       lda is a positive integer input variable not less than m */
+/*         which specifies the leading dimension of the array a. */
+
+/*       pivot is a logical input variable. if pivot is set true, */
+/*         then column pivoting is enforced. if pivot is set false, */
+/*         then no column pivoting is done. */
+
+/*       ipvt is an integer output array of length lipvt. ipvt */
+/*         defines the permutation matrix p such that a*p = q*r. */
+/*         column j of p is column ipvt(j) of the identity matrix. */
+/*         if pivot is false, ipvt is not referenced. */
+
+/*       lipvt is a positive integer input variable. if pivot is false, */
+/*         then lipvt may be as small as 1. if pivot is true, then */
+/*         lipvt must be at least n. */
+
+/*       rdiag is an output array of length n which contains the */
+/*         diagonal elements of r. */
+
+/*       acnorm is an output array of length n which contains the */
+/*         norms of the corresponding columns of the input matrix a. */
+/*         if this information is not needed, then acnorm can coincide */
+/*         with rdiag. */
+
+/*       wa is a work array of length n. if pivot is false, then wa */
+/*         can coincide with rdiag. */
+
+/*     subprograms called */
+
+/*       minpack-supplied ... dpmpar,enorm */
+
+/*       fortran-supplied ... dmax1,dsqrt,min0 */
+
+/*     argonne national laboratory. minpack project. march 1980. */
+/*     burton s. garbow, kenneth e. hillstrom, jorge j. more */
+
+/*     ********** */
+    (void)lipvt;
+
+/*     epsmch is the machine precision. */
+
+    epsmch = __cminpack_func__(dpmpar)(1);
+
+/*     compute the initial column norms and initialize several arrays. */
+
+    for (j = 0; j < n; ++j) {
+	acnorm[j] = __cminpack_enorm__(m, &a[j * lda + 0]);
+	rdiag[j] = acnorm[j];
+	wa[j] = rdiag[j];
+	if (pivot) {
+	    ipvt[j] = j+1;
+	}
+    }
+
+/*     reduce a to r with householder transformations. */
+
+    minmn = min(m,n);
+    for (j = 0; j < minmn; ++j) {
+	if (pivot) {
+
+/*        bring the column of largest norm into the pivot position. */
+
+            int kmax = j;
+            for (k = j; k < n; ++k) {
+                if (rdiag[k] > rdiag[kmax]) {
+                    kmax = k;
+                }
+            }
+            if (kmax != j) {
+                for (i = 0; i < m; ++i) {
+                    temp = a[i + j * lda];
+                    a[i + j * lda] = a[i + kmax * lda];
+                    a[i + kmax * lda] = temp;
+                }
+                rdiag[kmax] = rdiag[j];
+                wa[kmax] = wa[j];
+                k = ipvt[j];
+                ipvt[j] = ipvt[kmax];
+                ipvt[kmax] = k;
+            }
+        }
+
+/*        compute the householder transformation to reduce the */
+/*        j-th column of a to a multiple of the j-th unit vector. */
+
+	ajnorm = __cminpack_enorm__(m - (j+1) + 1, &a[j + j * lda]);
+	if (ajnorm != 0.) {
+            if (a[j + j * lda] < 0.) {
+                ajnorm = -ajnorm;
+            }
+            for (i = j; i < m; ++i) {
+                a[i + j * lda] /= ajnorm;
+            }
+            a[j + j * lda] += 1.;
+
+/*        apply the transformation to the remaining columns */
+/*        and update the norms. */
+
+            jp1 = j + 1;
+            if (n > jp1) {
+                for (k = jp1; k < n; ++k) {
+                    sum = 0.;
+                    for (i = j; i < m; ++i) {
+                        sum += a[i + j * lda] * a[i + k * lda];
+                    }
+                    temp = sum / a[j + j * lda];
+                    for (i = j; i < m; ++i) {
+                        a[i + k * lda] -= temp * a[i + j * lda];
+                    }
+                    if (pivot && rdiag[k] != 0.) {
+                        temp = a[j + k * lda] / rdiag[k];
+                        /* Computing MAX */
+                        d1 = 1. - temp * temp;
+                        rdiag[k] *= sqrt((max((real)0.,d1)));
+                        /* Computing 2nd power */
+                        d1 = rdiag[k] / wa[k];
+                        if (p05 * (d1 * d1) <= epsmch) {
+                            rdiag[k] = __cminpack_enorm__(m - (j+1), &a[jp1 + k * lda]);
+                            wa[k] = rdiag[k];
+                        }
+                    }
+                }
+            }
+        }
+	rdiag[j] = -ajnorm;
+    }
+
+/*     last card of subroutine qrfac. */
+#endif /* !USE_LAPACK */
+} /* qrfac_ */
+
diff --git a/ast/cminpack/qrsolv.c b/ast/cminpack/qrsolv.c
new file mode 100644
index 0000000..6ab9e98
--- /dev/null
+++ b/ast/cminpack/qrsolv.c
@@ -0,0 +1,218 @@
+#include "cminpack.h"
+#include <math.h>
+#include "cminpackP.h"
+
+__cminpack_attr__
+void __cminpack_func__(qrsolv)(int n, real *r, int ldr, 
+	const int *ipvt, const real *diag, const real *qtb, real *x, 
+	real *sdiag, real *wa)
+{
+    /* Initialized data */
+
+#define p5 .5
+#define p25 .25
+
+    /* Local variables */
+    int i, j, k, l;
+    real cos, sin, sum, temp;
+    int nsing;
+    real qtbpj;
+
+/*     ********** */
+
+/*     subroutine qrsolv */
+
+/*     given an m by n matrix a, an n by n diagonal matrix d, */
+/*     and an m-vector b, the problem is to determine an x which */
+/*     solves the system */
+
+/*           a*x = b ,     d*x = 0 , */
+
+/*     in the least squares sense. */
+
+/*     this subroutine completes the solution of the problem */
+/*     if it is provided with the necessary information from the */
+/*     qr factorization, with column pivoting, of a. that is, if */
+/*     a*p = q*r, where p is a permutation matrix, q has orthogonal */
+/*     columns, and r is an upper triangular matrix with diagonal */
+/*     elements of nonincreasing magnitude, then qrsolv expects */
+/*     the full upper triangle of r, the permutation matrix p, */
+/*     and the first n components of (q transpose)*b. the system */
+/*     a*x = b, d*x = 0, is then equivalent to */
+
+/*                  t       t */
+/*           r*z = q *b ,  p *d*p*z = 0 , */
+
+/*     where x = p*z. if this system does not have full rank, */
+/*     then a least squares solution is obtained. on output qrsolv */
+/*     also provides an upper triangular matrix s such that */
+
+/*            t   t               t */
+/*           p *(a *a + d*d)*p = s *s . */
+
+/*     s is computed within qrsolv and may be of separate interest. */
+
+/*     the subroutine statement is */
+
+/*       subroutine qrsolv(n,r,ldr,ipvt,diag,qtb,x,sdiag,wa) */
+
+/*     where */
+
+/*       n is a positive integer input variable set to the order of r. */
+
+/*       r is an n by n array. on input the full upper triangle */
+/*         must contain the full upper triangle of the matrix r. */
+/*         on output the full upper triangle is unaltered, and the */
+/*         strict lower triangle contains the strict upper triangle */
+/*         (transposed) of the upper triangular matrix s. */
+
+/*       ldr is a positive integer input variable not less than n */
+/*         which specifies the leading dimension of the array r. */
+
+/*       ipvt is an integer input array of length n which defines the */
+/*         permutation matrix p such that a*p = q*r. column j of p */
+/*         is column ipvt(j) of the identity matrix. */
+
+/*       diag is an input array of length n which must contain the */
+/*         diagonal elements of the matrix d. */
+
+/*       qtb is an input array of length n which must contain the first */
+/*         n elements of the vector (q transpose)*b. */
+
+/*       x is an output array of length n which contains the least */
+/*         squares solution of the system a*x = b, d*x = 0. */
+
+/*       sdiag is an output array of length n which contains the */
+/*         diagonal elements of the upper triangular matrix s. */
+
+/*       wa is a work array of length n. */
+
+/*     subprograms called */
+
+/*       fortran-supplied ... dabs,dsqrt */
+
+/*     argonne national laboratory. minpack project. march 1980. */
+/*     burton s. garbow, kenneth e. hillstrom, jorge j. more */
+
+/*     ********** */
+
+/*     copy r and (q transpose)*b to preserve input and initialize s. */
+/*     in particular, save the diagonal elements of r in x. */
+
+    for (j = 0; j < n; ++j) {
+	for (i = j; i < n; ++i) {
+	    r[i + j * ldr] = r[j + i * ldr];
+	}
+	x[j] = r[j + j * ldr];
+	wa[j] = qtb[j];
+    }
+
+/*     eliminate the diagonal matrix d using a givens rotation. */
+
+    for (j = 0; j < n; ++j) {
+
+/*        prepare the row of d to be eliminated, locating the */
+/*        diagonal element using p from the qr factorization. */
+
+	l = ipvt[j]-1;
+	if (diag[l] != 0.) {
+            for (k = j; k < n; ++k) {
+                sdiag[k] = 0.;
+            }
+            sdiag[j] = diag[l];
+
+/*        the transformations to eliminate the row of d */
+/*        modify only a single element of (q transpose)*b */
+/*        beyond the first n, which is initially zero. */
+
+            qtbpj = 0.;
+            for (k = j; k < n; ++k) {
+
+/*           determine a givens rotation which eliminates the */
+/*           appropriate element in the current row of d. */
+
+                if (sdiag[k] != 0.) {
+#                 ifdef USE_LAPACK
+                    dlartg_( &r[k + k * ldr], &sdiag[k], &cos, &sin, &temp );
+#                 else /* !USE_LAPACK */
+                    if (fabs(r[k + k * ldr]) < fabs(sdiag[k])) {
+                        real cotan;
+                        cotan = r[k + k * ldr] / sdiag[k];
+                        sin = p5 / sqrt(p25 + p25 * (cotan * cotan));
+                        cos = sin * cotan;
+                    } else {
+                        real tan;
+                        tan = sdiag[k] / r[k + k * ldr];
+                        cos = p5 / sqrt(p25 + p25 * (tan * tan));
+                        sin = cos * tan;
+                    }
+
+/*           compute the modified diagonal element of r and */
+/*           the modified element of ((q transpose)*b,0). */
+
+#                 endif /* !USE_LAPACK */
+                    temp = cos * wa[k] + sin * qtbpj;
+                    qtbpj = -sin * wa[k] + cos * qtbpj;
+                    wa[k] = temp;
+
+/*           accumulate the tranformation in the row of s. */
+#                 ifdef USE_CBLAS
+                    cblas_drot( n-k, &r[k + k * ldr], 1, &sdiag[k], 1, cos, sin );
+#                 else /* !USE_CBLAS */
+                    r[k + k * ldr] = cos * r[k + k * ldr] + sin * sdiag[k];
+                    if (n > k+1) {
+                        for (i = k+1; i < n; ++i) {
+                            temp = cos * r[i + k * ldr] + sin * sdiag[i];
+                            sdiag[i] = -sin * r[i + k * ldr] + cos * sdiag[i];
+                            r[i + k * ldr] = temp;
+                        }
+                    }
+#                 endif /* !USE_CBLAS */
+                }
+            }
+        }
+
+/*        store the diagonal element of s and restore */
+/*        the corresponding diagonal element of r. */
+
+	sdiag[j] = r[j + j * ldr];
+	r[j + j * ldr] = x[j];
+    }
+
+/*     solve the triangular system for z. if the system is */
+/*     singular, then obtain a least squares solution. */
+
+    nsing = n;
+    for (j = 0; j < n; ++j) {
+	if (sdiag[j] == 0. && nsing == n) {
+	    nsing = j;
+	}
+	if (nsing < n) {
+	    wa[j] = 0.;
+	}
+    }
+    if (nsing >= 1) {
+        for (k = 1; k <= nsing; ++k) {
+            j = nsing - k;
+            sum = 0.;
+            if (nsing > j+1) {
+                for (i = j+1; i < nsing; ++i) {
+                    sum += r[i + j * ldr] * wa[i];
+                }
+            }
+            wa[j] = (wa[j] - sum) / sdiag[j];
+        }
+    }
+
+/*     permute the components of z back to components of x. */
+
+    for (j = 0; j < n; ++j) {
+	l = ipvt[j]-1;
+	x[l] = wa[j];
+    }
+    return;
+
+/*     last card of subroutine qrsolv. */
+
+} /* qrsolv_ */
+
diff --git a/ast/cmpframe.c b/ast/cmpframe.c
new file mode 100644
index 0000000..09b5f73
--- /dev/null
+++ b/ast/cmpframe.c
@@ -0,0 +1,10654 @@
+/*
+*class++
+*  Name:
+*     CmpFrame
+
+*  Purpose:
+*     Compound Frame.
+
+*  Constructor Function:
+c     astCmpFrame
+f     AST_CMPFRAME
+
+*  Description:
+*     A CmpFrame is a compound Frame which allows two component Frames
+*     (of any class) to be merged together to form a more complex
+*     Frame. The axes of the two component Frames then appear together
+*     in the resulting CmpFrame (those of the first Frame, followed by
+*     those of the second Frame).
+*
+*     Since a CmpFrame is itself a Frame, it can be used as a
+*     component in forming further CmpFrames. Frames of arbitrary
+*     complexity may be built from simple individual Frames in this
+*     way.
+*
+*     Also since a Frame is a Mapping, a CmpFrame can also be used as a
+*     Mapping. Normally, a CmpFrame is simply equivalent to a UnitMap,
+*     but if either of the component Frames within a CmpFrame is a Region
+*     (a sub-class of Frame), then the CmpFrame will use the Region as a
+*     Mapping when transforming values for axes described by the Region.
+*     Thus input axis values corresponding to positions which are outside the
+*     Region will result in bad output axis values.
+
+*  Inheritance:
+*     The CmpFrame class inherits from the Frame class.
+
+*  Attributes:
+*     The CmpFrame class does not define any new attributes beyond
+*     those which are applicable to all Frames. However, the attributes
+*     of the component Frames can be accessed as if they were attributes
+*     of the CmpFrame. For instance, if a CmpFrame contains a SpecFrame
+*     and a SkyFrame, then the CmpFrame will recognise the "Equinox"
+*     attribute and forward access requests to the component SkyFrame.
+*     Likewise, it will recognise the "RestFreq" attribute and forward
+*     access requests to the component SpecFrame. An axis index can
+*     optionally be appended to the end of any attribute name, in which
+*     case the request to access the attribute will be forwarded to the
+*     primary Frame defining the specified axis.
+
+*  Functions:
+c     The CmpFrame class does not define any new functions beyond those
+f     The CmpFrame class does not define any new routines beyond those
+*     which are applicable to all Frames.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     4-MAR-1996 (RFWS):
+*        Original version.
+*     27-FEB-1997 (RFWS):
+*        Improved public prologues.
+*     25-FEB-1998 (RFWS):
+*        Over-ride the astUnformat method.
+*     6-APR-1998 (RFWS):
+*        Fixed bug in returned value of GenAxisSelection.
+*     22-SEP-1998 (RFWS):
+*        Fixed bug in Match function - was not checking Domain values
+*        for equality.
+*     11-JUN-1999 (RFWS):
+*        Fixed bug in GenAxisSelection-  some selections were being omitted.
+*     5-FEB-2001 (DSB):
+*        Ensure that Title and Domain values appropriate to a CmpFrame
+*        are preserved if a CmpFrame result is generated by SubFrame.
+*     27-FEB-2001 (DSB):
+*        Modified Match so that all the frames have some axes in order to
+*        match. Otherwise, null pointers are created (for zero axes),
+*        resulting in a seg vio.
+*     21-JUN-2001 (DSB):
+*        Added astAngle.
+*     7-SEP-2001 (DSB):
+*        Added astResolve.
+*     26-SEP-2001 (DSB):
+*        Over-ride the astDecompose method.
+*     20-DEC-2002 (DSB):
+*        Allows any attribute of a component frame to be accessed as though
+*        it were an attribute of the CmpFrame by including an axis index in
+*        the attribute name (e.g. "System(3)").
+*     8-JAN-2003 (DSB):
+*        - Changed private InitVtab method to protected astInitCmpFrameVtab
+*        method.
+*        - Override astGetAttrib, astClearAttrib, astTestAttrib,
+*        astSetAttrib to allow attributes to be set for individual
+*        axes.
+*        - Override astGetEpoch astGetSystem, astGetAlignSystem.
+*        astValidateSystem, astSystemString, astSystemCode.
+*     27-FEB-2003 (DSB):
+*        - Modify the default Domain name for a CmpFrame to be the
+*        domains of the two subFrames separated by a "-".
+*     24-JAN-2004 (DSB):
+*        o  Override the astFields method.
+*        o  Added argument "fmt" to Abbrev.
+*     24-MAR-2004 (DSB):
+*        Over-ride the astSimplify and astTransform methods.
+*     8-SEP-2004 (DSB):
+*        Over-ride astResolvePoints method.
+*     21-JAN-2005 (DSB):
+*        Over-ride the astGetActiveUnit and astSetActiveUnit methods.
+*     23-FEB-2005 (DSB):
+*        Modify GetDomain to avoid over-writing the static "buff" array
+*        if called recursively.
+*     29-MAR-2005 (DSB):
+*        Override astSetEpoch and astClearEpoch by implementations which
+*        propagate the changed epoch value to the component Frames.
+*     5-APR-2005 (DSB):
+*        Correct error checking in Clear/Get/Set/TestAttrib.
+*     12-MAY-2005 (DSB):
+*        Override astNormBox method.
+*     12-AUG-2005 (DSB):
+*        Override astSetObsLat/Lon and astClearObslat/Lon by implementations
+*        which propagate the changed value to the component Frames.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     3-APR-2006 (DSB):
+*        Modify Match so that an attempt is made to align the target with
+*        each of the two component Frames if the target cannot be matched
+*        with the CmpFrame as a whole.
+*     3-MAY-2006 (DSB):
+*        Fix bug in Match that could cause segvio when matching a target
+*        against the second component of a CmpFrame.
+*     31-OCT-2006 (DSB):
+*        Over-ride the SetFrameFlags method.
+*     1-NOV-2005 (DSB):
+*        Override astSetDut1, astGetDut1 and astClearDut1.
+*     15-MAR-2007 (DSB):
+*        Override astClearAlignSystem by an implementation that clears
+*        AlignSystem in the component Frames.
+*     7-FEB-2008 (DSB):
+*        Allow the MaxAxes and MinAxes attributes to be specified for a
+*        CmpFrame (rather than just being the sum of the attribute values
+*        in the component frames). This enables, for instance, a (detector
+*        index,mjd) frame to match with a ((velocity,detector index),mjd)
+*        frame.
+*     5-MAY-2009 (DSB):
+*        In GetAttrib, if an index is included in the attribute name, attempt
+*        to use the GetAttrib method of the primary frame before using the
+*        parent GetAttrib method. This is because the Frame getattrib
+*        method will dissociate axes from their parent class. Thus, a
+*        SkyAxis attribute such as AsTime will come out wrong since its
+*        value is managed by the SkyFrame class rather than the SkyAxis
+*        class.
+*     18-JUN-2009 (DSB):
+*        Override astSetObsAlt and astClearObsAlt.
+*     29-SEP-2009 (DSB):
+*        Ensure the astMatch method provided by this class honours the
+*        PreserveAxes, MaxAxes and MinAxes attribute settings.
+*     22-MAR-2011 (DSB):
+*        Override astFrameGrid method.
+*     29-APR-2011 (DSB):
+*        Prevent astFindFrame from matching a subclass template against a
+*        superclass target.
+*     10-FEB-2015 (DSB):
+*        When checking attribute settings for attribute names that end with
+*        an axis index, stop looking for the axis index when the first equals
+*        sign is encountered.
+*     26-MAR-2015 (DSB):
+*        Increase size of "buf2" buffer in SetAttrib, and trap buffer overflow.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS CmpFrame
+
+/* Define the first and last acceptable System values. */
+#define FIRST_SYSTEM AST__COMP
+#define LAST_SYSTEM AST__COMP
+
+/* Define macros to implement member functions for accessing axis
+   attributes. */
+/*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Implement a function to clear an attribute value for a CmpFrame axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     MAKE_CLEAR(attribute)
+
+*  Class Membership:
+*     Defined by the CmpFrame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member
+*     function of the form:
+*
+*        static void Clear<Attribute>( AstFrame *this, int axis )
+*
+*     which clears an attribute value for a specified axis of a CmpFrame.
+
+*  Parameters:
+*      attribute
+*         The name of the attribute to be cleared, as it appears in the
+*         function name (e.g. Label in "ClearLabel").
+
+*  Notes:
+*     -  This macro assumes the existence of a method of the form:
+*
+*           void astClear<Attribute>( AstFrame *this, int axis )
+*
+*     which clears the required attribute for a Frame object.
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attribute) \
+static void Clear##attribute( AstFrame *this_frame, int axis, int *status ) { \
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */ \
+   int naxes1;                   /* Number of axes in frame1 */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the CmpFrame structure. */ \
+   this = (AstCmpFrame *) this_frame; \
+\
+/* Validate and alidateAxispermute the axis index supplied. */ \
+   axis = astValidateAxis( this, axis, 1, "astSet" #attribute ); \
+\
+/* Determine the number of axes in the first component Frame. */ \
+   naxes1 = astGetNaxes( this->frame1 ); \
+   if ( astOK ) { \
+\
+/* Decide which Frame contains the axis and invoke its astClear... method to \
+   clear the attribute value. */ \
+      if ( axis < naxes1 ) { \
+         astClear##attribute( this->frame1, axis ); \
+      } else { \
+         astClear##attribute( this->frame2, axis - naxes1 ); \
+      } \
+   } \
+}
+
+/*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Implement a function to get an attribute value for a CmpFrame axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+#     #include "cmpframe.h"
+*     MAKE_GET(attribute,type,bad_value,default,assign_default)
+
+*  Class Membership:
+*     Defined by the CmpFrame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member
+*     function of the form:
+*
+*        static <Type> Get<Attribute>( AstFrame *this, int axis )
+*
+*     which gets an attribute value for a specified axis of a
+*     CmpFrame.
+
+*  Parameters:
+*     attribute
+*        The name of the attribute whose value is to be obtained, as
+*        it appears in the function name (e.g. Label in "GetLabel").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set,
+*        or if the function fails.
+*     default
+*        A boolean (int) value that indicates whether a new default
+*        value should be returned if the requested attribute has not
+*        been set for the appropriate axis of the appropriate
+*        component Frame. If this value is zero, the component Frame's
+*        default (for the appropriate axis) will be used instead.
+*     assign_default
+*        An expression that evaluates to the new default value to be
+*        assigned. This value is ignored if "default" is zero, but a
+*        valid (e.g. constant) value should nevertheless be supplied.
+
+*  Notes:
+*     -  This macro assumes the existence of a method of the form:
+*
+*           <Type> astGet<Attribute>( AstFrame *this, int axis )
+*
+*     which gets the required attribute for a Frame object.
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attribute,type,bad_value,default,assign_default) \
+static type Get##attribute( AstFrame *this_frame, int axis, int *status ) { \
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */ \
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */ \
+   AstFrame *frame;              /* Pointer to Frame containing axis */\
+   int axis_p;                   /* Permuted axis index */ \
+   int naxes1;                   /* Number of axes in frame1 */ \
+   int set;                      /* Digits attribute set? */ \
+   type result;                  /* Result value to return */ \
+ \
+/* Initialise. */ \
+   result = (bad_value); \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Get a pointer to the structure holding thread-specific global data. */ \
+   astGET_GLOBALS(this_frame); \
+\
+/* Obtain a pointer to the CmpFrame structure. */ \
+   this = (AstCmpFrame *) this_frame; \
+\
+/* Validate and permute the axis index supplied. */ \
+   axis_p = astValidateAxis( this, axis, 1, "astGet" #attribute ); \
+\
+/* Determine the number of axes in the first component Frame. */ \
+   naxes1 = astGetNaxes( this->frame1 ); \
+   if ( astOK ) { \
+\
+/* Decide which Frame contains the axis and adjust the axis index if \
+   necessary. */ \
+      frame = ( axis_p < naxes1 ) ? this->frame1 : this->frame2; \
+      axis_p = ( axis_p < naxes1 ) ? axis_p : axis_p - naxes1; \
+\
+/* Since the component Frame is "managed" by the enclosing CmpFrame, we next \
+   test if any Frame attributes which may affect the result are undefined \
+   (i.e. have not been explicitly set). If so, we over-ride them, giving \
+   them temporary values dictated by the CmpFrame. Only the Digits attribute \
+   is relevant here. */ \
+      set = astTestDigits( frame ); \
+      if ( !set ) astSetDigits( frame, astGetDigits( this ) ); \
+\
+/* If the default value is to be over-ridden, test if the Frame's axis \
+   attribute has been set. Then, if required, obtain the attribute value from \
+   the Frame. */ \
+      if ( !(default) || astTest##attribute( frame, axis_p ) ) { \
+         result = astGet##attribute( frame, axis_p ); \
+\
+/* If required, assign the new default value. */ \
+      } else { \
+         result = (assign_default); \
+      } \
+\
+/* Clear Frame attributes which were temporarily over-ridden. */ \
+      if ( !set ) astClearDigits( frame ); \
+   } \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Implement a function to set an attribute value for a CmpFrame axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     MAKE_SET(attribute,type)
+
+*  Class Membership:
+*     Defined by the CmpFrame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member
+*     function of the form:
+*
+*        static void Set<Attribute>( AstFrame *this, int axis, <Type> value )
+*
+*     which sets an attribute value for a specified axis of a CmpFrame.
+
+*  Parameters:
+*      attribute
+*         The name of the attribute to be set, as it appears in the
+*         function name (e.g. Label in "SetLabel").
+*      type
+*         The C type of the attribute.
+
+*  Notes:
+*     -  This macro assumes the existence of a method of the form:
+*
+*           void astSet<Attribute>( AstFrame *this, int axis, <Type> value )
+*
+*     which sets the required attribute for a Frame object.
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attribute,type) \
+static void Set##attribute( AstFrame *this_frame, int axis, type value, int *status ) { \
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */ \
+   int naxes1;                   /* Number of axes in frame1 */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the CmpFrame structure. */ \
+   this = (AstCmpFrame *) this_frame; \
+\
+/* Validate and permute the axis index supplied. */ \
+   axis = astValidateAxis( this, axis, 1, "astSet" #attribute ); \
+\
+/* Determine the number of axes in the first component Frame. */ \
+   naxes1 = astGetNaxes( this->frame1 ); \
+   if ( astOK ) { \
+\
+/* Decide which Frame contains the axis and invoke its astSet... method to \
+   set the attribute value. */ \
+      if ( axis < naxes1 ) { \
+         astSet##attribute( this->frame1, axis, value ); \
+      } else { \
+         astSet##attribute( this->frame2, axis - naxes1, value ); \
+      } \
+   } \
+}
+
+/*
+*  Name:
+*     MAKE_TEST
+
+*  Purpose:
+*     Implement a function to test if an attribute is set for a CmpFrame axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     MAKE_TEST(attribute)
+
+*  Class Membership:
+*     Defined by the CmpFrame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member
+*     function of the form:
+*
+*        static int Test<Attribute>( AstFrame *this, int axis )
+*
+*     which tests whether an attribute value is set for a specified
+*     axis of a CmpFrame.
+
+*  Parameters:
+*      attribute
+*         The name of the attribute to be tested, as it appears in the
+*         function name (e.g. Label in "TestLabel").
+
+*  Notes:
+*     -  This macro assumes the existence of a method of the form:
+*
+*           int astTest<Attribute>( AstFrame *this, int axis )
+*
+*     which tests the required attribute for a Frame object.
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_TEST(attribute) \
+static int Test##attribute( AstFrame *this_frame, int axis, int *status ) { \
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */ \
+   int naxes1;                   /* Number of axes in frame1 */ \
+   int result;                   /* Result value to return */ \
+\
+/* Initialise. */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Obtain a pointer to the CmpFrame structure. */ \
+   this = (AstCmpFrame *) this_frame; \
+\
+/* Validate and permute the axis index supplied. */ \
+   axis = astValidateAxis( this, axis, 1, "astSet" #attribute ); \
+\
+/* Determine the number of axes in the first component Frame. */ \
+   naxes1 = astGetNaxes( this->frame1 ); \
+   if ( astOK ) { \
+\
+/* Decide which Frame contains the axis and invoke its astTest... method to \
+   test the attribute. */ \
+      if ( axis < naxes1 ) { \
+         result = astTest##attribute( this->frame1, axis ); \
+      } else { \
+         result = astTest##attribute( this->frame2, axis - naxes1 ); \
+      } \
+   } \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "pointset.h"            /* Sets of points */
+#include "object.h"              /* Base Object class */
+#include "mapping.h"             /* Coordinate Mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "permmap.h"             /* Coordinate permutation Mappings */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "axis.h"                /* Coordinate axes */
+#include "frame.h"               /* Parent Frame class */
+#include "cmpframe.h"            /* Interface definition for this class */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <limits.h>
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstSystemType (* parent_getalignsystem)( AstFrame *, int * );
+static AstSystemType (* parent_getsystem)( AstFrame *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static const char *(* parent_getdomain)( AstFrame *, int * );
+static const char *(* parent_gettitle)( AstFrame *, int * );
+static double (* parent_angle)( AstFrame *, const double[], const double[], const double[], int * );
+static double (* parent_getdut1)( AstFrame *, int * );
+static double (* parent_getepoch)( AstFrame *, int * );
+static double (* parent_getobsalt)( AstFrame *, int * );
+static double (* parent_getobslat)( AstFrame *, int * );
+static double (* parent_getobslon)( AstFrame *, int * );
+static int (* parent_getactiveunit)( AstFrame *, int * );
+static int (* parent_getmaxaxes)( AstFrame *, int * );
+static int (* parent_getminaxes)( AstFrame *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+static int (* parent_getusedefs)( AstObject *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearalignsystem)( AstFrame *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_cleardut1)( AstFrame *, int * );
+static void (* parent_clearepoch)( AstFrame *, int * );
+static void (* parent_clearobsalt)( AstFrame *, int * );
+static void (* parent_clearobslat)( AstFrame *, int * );
+static void (* parent_clearobslon)( AstFrame *, int * );
+static void (* parent_overlay)( AstFrame *, const int *, AstFrame *, int * );
+static void (* parent_setactiveunit)( AstFrame *, int, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static void (* parent_setdut1)( AstFrame *, double, int * );
+static void (* parent_setepoch)( AstFrame *, double, int * );
+static void (* parent_setframeflags)( AstFrame *, int, int * );
+static void (* parent_setobsalt)( AstFrame *, double, int * );
+static void (* parent_setobslat)( AstFrame *, double, int * );
+static void (* parent_setobslon)( AstFrame *, double, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->Label_Buff[ 0 ] = 0; \
+   globals->Symbol_Buff[ 0 ] = 0; \
+   globals->GetDomain_Buff[ 0 ] = 0; \
+   globals->GetTitle_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(CmpFrame)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(CmpFrame,Class_Init)
+#define class_vtab astGLOBAL(CmpFrame,Class_Vtab)
+#define getdomain_buff astGLOBAL(CmpFrame,GetDomain_Buff)
+#define gettitle_buff astGLOBAL(CmpFrame,GetTitle_Buff)
+#define label_buff astGLOBAL(CmpFrame,Label_Buff)
+#define symbol_buff astGLOBAL(CmpFrame,Symbol_Buff)
+#define qsort_axes astGLOBAL(CmpFrame,qsort_axes)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Pointer to axis index array accessed by "qsort". */
+static int *qsort_axes;
+
+/* Default Label string buffer */
+static char label_buff[ 101 ];
+
+/* Default Symbol buffer */
+static char symbol_buff[ 51 ];
+
+/* Buffer for returned domain name in GetDomain */
+static char getdomain_buff[ 101 ];
+
+/* Buffer for returned title in GetTitle */
+static char gettitle_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstCmpFrameVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstCmpFrame *astCmpFrameId_( void *, void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstAxis *GetAxis( AstFrame *, int, int * );
+static AstMapping *RemoveRegions( AstMapping *, int * );
+static AstMapping *Simplify( AstMapping *, int * );
+static AstObject *Cast( AstObject *, AstObject *, int * );
+static AstPointSet *FrameGrid( AstFrame *, int, const double *, const double *, int * );
+static AstPointSet *ResolvePoints( AstFrame *, const double [], const double [], AstPointSet *, AstPointSet *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstSystemType GetAlignSystem( AstFrame *, int * );
+static AstSystemType GetSystem( AstFrame *, int * );
+static AstSystemType SystemCode( AstFrame *, const char *, int * );
+static AstSystemType ValidateSystem( AstFrame *, AstSystemType, const char *, int * );
+static const char *Abbrev( AstFrame *, int, const char *, const char *, const char *, int * );
+static const char *Format( AstFrame *, int, double, int * );
+static const char *GetDomain( AstFrame *, int * );
+static const char *GetFormat( AstFrame *, int, int * );
+static const char *GetLabel( AstFrame *, int, int * );
+static const char *GetSymbol( AstFrame *, int, int * );
+static const char *GetTitle( AstFrame *, int * );
+static const char *GetUnit( AstFrame *, int, int * );
+static const char *SystemString( AstFrame *, AstSystemType, int * );
+static const int *GetPerm( AstFrame *, int * );
+static double Angle( AstFrame *, const double[], const double[], const double[], int * );
+static double Distance( AstFrame *, const double[], const double[], int * );
+static double Centre( AstFrame *, int, double, double, int * );
+static double Gap( AstFrame *, int, double, int *, int * );
+static int ComponentMatch( AstCmpFrame *, AstFrame *, int, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int Fields( AstFrame *, int, const char *, const char *, int, char **, int *, double *, int * );
+static int GenAxisSelection( int, int, int [], int * );
+static int GetActiveUnit( AstFrame *, int * );
+static int GetDirection( AstFrame *, int, int * );
+static int GetMaxAxes( AstFrame *, int * );
+static int GetMinAxes( AstFrame *, int * );
+static int GetNaxes( AstFrame *, int * );
+static int GetObjSize( AstObject *, int * );
+static int GetUseDefs( AstObject *, int * );
+static int GoodPerm( int, const int [], int, const int [], int * );
+static int IsUnitFrame( AstFrame *, int * );
+static int Match( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int PartMatch( AstCmpFrame *, AstFrame *, int, int, const int [], int, const int [], int **, int **, AstMapping **, AstFrame **, int * );
+static int QsortCmpAxes( const void *, const void * );
+static int SubFrame( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int TestDirection( AstFrame *, int, int * );
+static int TestFormat( AstFrame *, int, int * );
+static int TestLabel( AstFrame *, int, int * );
+static int TestSymbol( AstFrame *, int, int * );
+static int TestUnit( AstFrame *, int, int * );
+static int Unformat( AstFrame *, int, const char *, double *, int * );
+static void AddExtraAxes( int, int [], int, int, int, int * );
+static void ClearDirection( AstFrame *, int, int * );
+static void ClearFormat( AstFrame *, int, int * );
+static void ClearLabel( AstFrame *, int, int * );
+static void ClearSymbol( AstFrame *, int, int * );
+static void ClearUnit( AstFrame *, int, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Decompose( AstMapping *, AstMapping **, AstMapping **, int *, int *, int *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void MatchAxesX( AstFrame *, AstFrame *, int *, int * );
+static void Norm( AstFrame *, double [], int * );
+static void NormBox( AstFrame *, double[], double[], AstMapping *, int * );
+static void Offset( AstFrame *, const double [], const double [], double, double [], int * );
+static void Overlay( AstFrame *, const int *, AstFrame *, int * );
+static void PartitionSelection( int, const int [], const int [], int, int, int [], int, int * );
+static void PermAxes( AstFrame *, const int[], int * );
+static void PrimaryFrame( AstFrame *, int, AstFrame **, int *, int * );
+static void RenumberAxes( int, int [], int * );
+static void Resolve( AstFrame *, const double [], const double [], const double [], double [], double *, double *, int * );
+static void SetActiveUnit( AstFrame *, int, int * );
+static void SetAxis( AstFrame *, int, AstAxis *, int * );
+static void SetDirection( AstFrame *, int, int, int * );
+static void SetFormat( AstFrame *, int, const char *, int * );
+static void SetFrameFlags( AstFrame *, int, int * );
+static void SetLabel( AstFrame *, int, const char *, int * );
+static void SetSymbol( AstFrame *, int, const char *, int * );
+static void SetUnit( AstFrame *, int, const char *, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+static double GetEpoch( AstFrame *, int * );
+static void ClearEpoch( AstFrame *, int * );
+static void SetEpoch( AstFrame *, double, int * );
+
+static double GetDut1( AstFrame *, int * );
+static void ClearDut1( AstFrame *, int * );
+static void SetDut1( AstFrame *, double, int * );
+
+static double GetObsLon( AstFrame *, int * );
+static void ClearObsLon( AstFrame *, int * );
+static void SetObsLon( AstFrame *, double, int * );
+
+static double GetObsLat( AstFrame *, int * );
+static void ClearObsLat( AstFrame *, int * );
+static void SetObsLat( AstFrame *, double, int * );
+
+static double GetObsAlt( AstFrame *, int * );
+static void ClearObsAlt( AstFrame *, int * );
+static void SetObsAlt( AstFrame *, double, int * );
+
+static void ClearAlignSystem( AstFrame *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+static const char *Abbrev( AstFrame *this_frame, int axis,  const char *fmt,
+                           const char *str1, const char *str2, int *status ) {
+/*
+*  Name:
+*     Abbrev
+
+*  Purpose:
+*     Abbreviate a formatted CmpFrame axis value by skipping leading fields.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     const char *Abbrev( AstFrame *this, int axis,  const char *fmt,
+*                         const char *str1, const char *str2, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astAbbrev
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function compares two CmpFrame axis values that have been
+*     formatted (using astFormat) and determines if they have any
+*     redundant leading fields (i.e. leading fields in common which
+*     can be suppressed when tabulating the values or plotting them on
+*     the axis of a graph).
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     axis
+*        The number of the CmpFrame axis for which the values have
+*        been formatted (axis numbering starts at zero for the first
+*        axis).
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format specification used to format the two values.
+*     str1
+*        Pointer to a constant null-terminated string containing the
+*        first formatted value. If this is null, the returned pointer
+*        points to the start of the final field in str2.
+*     str2
+*        Pointer to a constant null-terminated string containing the
+*        second formatted value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer into the "str2" string which locates the first
+*     character in the first field that differs between the two
+*     formatted values.
+*
+*     If the two values have no leading fields in common, the returned
+*     value will point at the start of string "str2". If the two
+*     values are equal, it will point at the terminating null at the
+*     end of this string.
+
+*  Notes:
+*     - This function assumes that the format specification used was
+*     the same when both values were formatted and that they both
+*     apply to the same CmpFrame axis.
+*     - A pointer to the start of "str2" will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   AstFrame *frame;              /* Pointer to Frame containing axis */
+   const char *result;           /* Pointer value to return */
+   int naxes1;                   /* Number of axes in frame1 */
+   int set;                      /* Digits attribute set? */
+
+/* Initialise. */
+   result = str2;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Validate and permute the axis index supplied. */
+   axis = astValidateAxis( this, axis, 1, "astAbbrev" );
+
+/* Determine the number of axes in the first component Frame. */
+   naxes1 = astGetNaxes( this->frame1 );
+   if ( astOK ) {
+
+/* Decide which component Frame contains the axis and adjust the axis
+   index if necessary. */
+      frame = ( axis < naxes1 ) ? this->frame1 : this->frame2;
+      axis = ( axis < naxes1 ) ? axis : axis - naxes1;
+
+/* Since the component Frame is "managed" by the enclosing CmpFrame,
+   we next test if any Frame attributes which may affect the result
+   are undefined (i.e. have not been explicitly set). If so, we
+   over-ride them, giving them temporary values dictated by the
+   CmpFrame. Only the Digits attribute is relevant here. */
+      set = astTestDigits( frame );
+      if ( !set ) astSetDigits( frame, astGetDigits( this ) );
+
+/* Invoke the Frame's astAbbrev method to perform the processing. */
+      result = astAbbrev( frame, axis, fmt, str1, str2 );
+
+/* Clear Frame attributes which were temporarily over-ridden. */
+      if ( !set ) astClearDigits( frame );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = str2;
+
+/* Return the result. */
+   return result;
+}
+
+static void AddExtraAxes( int naxes, int axes[], int i1, int i2,
+                          int following, int *status ) {
+/*
+*  Name:
+*     AddExtraAxes
+
+*  Purpose:
+*     Add extra axis indices in place of missing ones.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void AddExtraAxes( int naxes, int axes[], int i1, int i2,
+*                        int following, int *status )
+
+*  Class Membership:
+*     CmpFrame member function.
+
+*  Description:
+*     This function takes an array of axis indices that refer to the
+*     axes of a Frame, and which may have values missing (denoted by
+*     an index of -1). It replaces each occurrence of -1 by a new axis
+*     index (and re-numbers the others to avoid duplication) in such a
+*     way that the new indices introduced are "associated" with
+*     certain of the pre-existing indices, by virtue of being numbered
+*     consecutively with them.
+*
+*     The purpose of this operation is to establish the relative
+*     location of new axes in relation to the pre-existing ones.
+*
+*     Normally, each new axis will be associated with (i.e. numbered
+*     one more than) the pre-existing index which precedes
+*     it. However, if the "following" parameter is non-zero, it will
+*     instead be associated with (numbered one less than) the one
+*     which follows it. If there is no preceding (or following)
+*     pre-existing index, the following (or preceding) one is used
+*     instead. If several adjacent occurrences of -1 must be replaced,
+*     they are numbered consecutively in their order of occurrence.
+
+*  Parameters:
+*     naxes
+*        The number of axis indices in the array.
+*     axes
+*        The array containing the axis indices.
+*     i1
+*        Index of the first element of the array to be processed.
+*     i2
+*        Index of the last element of the array to be processed.
+*     following
+*        Boolean flag to determine if new indices are associated with
+*        the preceding index (if zero) or the following index (if
+*        non-zero).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The values of "i1" and "i2" dictate the range of array
+*     elements where values of -1 will be replaced, but all array
+*     elements are candidates for renumbering in order to avoid
+*     duplicate axis indices.
+*     - This function aims to establish the location of new axes only
+*     by means of the relative numerical value of the indices assigned
+*     to them. It does not constrain the actual indices assigned in
+*     any further way.
+*     - Because axis indices are always incremented (never
+*     decremented) in order to avoid duplicates, where a number of new
+*     indices have been introduced, the maximum index present in the
+*     result array may exceed the original maximum.
+*     - Some axis indices may remain unused (i.e. not present) in the
+*     result array.
+*/
+
+/* Local Variables: */
+   int end;                      /* Loop termination value */
+   int extra;                    /* Index to apply to next "extra" axis */
+   int found;                    /* Default value found? */
+   int i;                        /* Main loop counter */
+   int inc;                      /* Loop increment value */
+   int j;                        /* Loop counter for eliminating duplicates */
+   int start;                    /* Loop starting value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise the default index of the next extra axis to add. This
+   will apply only if there are no valid axis indices from which to
+   obtain a better default. */
+   extra = 0;
+
+/* Initialise loop parameters so as to scan the axis indices provided
+   in either the forward or reverse direction, according to the value
+   of "following". Start with the section of the array being processed,
+   but continue looking for a default right up to the end of the array
+   (this prevents the current section being numbered inconsistently
+   with respect to adjacent ones that may already have been
+   processed). */
+   start = following ? i2 : i1;
+   end = following ? -1 : naxes;
+   inc = following ? -1 : 1;
+
+/* Search for the first (in whichever direction this is) valid axis
+   index and use it to set a new default index for the next extra axis
+   to add. If scanning forward, use the valid axis index (causing any
+   preceding extra axis to displace it upwards). If scanning
+   backwards, use one more than the valid axis index (causing any
+   following extra axis to tag on the end). */
+   found = 0;
+   for ( i = start; i != end; i += inc ) {
+      if ( axes[ i ] != -1 ) {
+         found = 1;
+         extra = axes[ i ] + ( following ? 1 : 0 );
+         break;
+      }
+   }
+
+/* If no default has yet been found, repeat the above process by
+   scanning in the opposite direction (by inverting the "following"
+   value used). Again, this prevents inconsistency with neighbouring
+   regions. This time a default must be found unless the entire array
+   is filled with -1's (in which case a default of zero is used). */
+   if ( !found ) {
+      start = !following ? i2 : i1;
+      end = !following ? -1 : naxes;
+      inc = !following ? -1 : 1;
+      for ( i = start; i != end; i += inc ) {
+         if ( axes[ i ] != -1 ) {
+            extra = axes[ i ] + ( !following ? 1 : 0 );
+            break;
+         }
+      }
+   }
+
+/* Reset the loop parameters to scan just the region of interest in
+   the original (correct) direction. */
+   start = following ? i2 : i1;
+   end = following ? i1 - 1 : i2 + 1;
+   inc = following ? -1 : 1;
+
+/* Identify those indices which are not valid. */
+   for ( i = start; i != end; i += inc ) {
+      if ( axes[ i ] == -1 ) {
+
+/* We wish to assign the value "extra" in place of this invalid axis
+   index.  However, this may duplicate an index already present, so
+   increment by one all valid indices which are not less than the new
+   index. This eliminates any possibility duplication, although it may
+   leave an axis index value unused (if no duplication would actually
+   have occurred). */
+         for ( j = 0; j < naxes; j++ ) {
+            if ( axes[ j ] != -1 ) {
+               if ( axes[ j ] >= extra ) axes[ j ]++;
+            }
+         }
+
+/* We can now assign the new axis index. */
+         axes[ i ] = extra;
+
+/* Assign the value to be used for the next extra axis index. If
+   scanning forward, this will be one more than the last one used (so
+   it will follow it).  If scanning backwards, it is equal to the last
+   one (so that it will displace the last one upwards). */
+         extra += ( following ? 0 : 1 );
+
+/* When a valid axis index is encountered, reset the value to be used
+   for the next extra axis index. If scanning forward, this is one
+   more than the last valid index (so the extra axis will follow
+   it). If scanning backwards, it is equal to the last valid index (so
+   it will displace the valid index upwards). */
+      } else {
+         extra = axes[ i ] + ( following ? 0 : 1 );
+      }
+   }
+}
+
+static double Angle( AstFrame *this_frame, const double a[],
+                     const double b[], const double c[], int *status ) {
+/*
+*  Name:
+*     Angle
+
+*  Purpose:
+*     Calculate the angle subtended by two points at a third point.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     double Angle( AstFrame *this_frame, const double a[],
+*                   const double b[], const double c[], int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astAngle method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function finds the angle at point B between the line joining
+*     points A and B, and the line joining points C and B, in the context
+*     of a CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     a
+*        An array of double, with one element for each CmpFrame axis,
+*        containing the coordinates of the first point.
+*     b
+*        An array of double, with one element for each CmpFrame axis,
+*        containing the coordinates of the second point.
+*     c
+*        An array of double, with one element for each CmpFrame axis,
+*        containing the coordinates of the third point.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The required angle, or AST__BAD if the angle is undefined.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   AstFrame *pframe;             /* Pointer to the primary Frame for an axis */
+   const int *perm;              /* Pointer to axis permutation array */
+   double *pa;                   /* Permuted coordinates for point a */
+   double *pb;                   /* Permuted coordinates for point b */
+   double *pc;                   /* Permuted coordinates for point c */
+   double ang1;                  /* Angle between input points in frame1 */
+   double ang2;                  /* Angle between input points in frame2 */
+   double result;                /* Required angle */
+   int axis;                     /* Loop counter for axes */
+   int iscart;                   /* Is the CmpFrame a Cartesian system? */
+   int naxes1;                   /* Number of axes in frame1 */
+   int naxes;                    /* Total number of axes in CmpFrame */
+   int paxis;                    /* Axis index within primary Frame */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Obtain the number of axes in the CmpFrame. */
+   naxes = astGetNaxes( this );
+
+/* See if all axes within the CmpFrame belong to a simple Frame, in which
+   case we assume that the CmpFrame describes a Cartesian coordinate system. */
+   iscart = 1;
+   for( axis = 0; axis < naxes; axis++ ){
+      PrimaryFrame( this_frame, axis, &pframe, &paxis, status );
+      if( strcmp( astGetClass( pframe ), "Frame" ) ) {
+         iscart = 0;
+         pframe = astAnnul( pframe );
+         break;
+      }
+      pframe = astAnnul( pframe );
+   }
+
+/* If the CmpFrame describes a Cartesian coordinate system, we can use the
+   Angle method from the parent Frame class. */
+   if( iscart ) {
+      result = (*parent_angle)( this_frame, a, b, c, status );
+
+/* If the CmpFrame is not Cartesian... */
+   } else {
+
+/* Obtain a pointer to the CmpFrame's axis permutation array. */
+      perm = astGetPerm( this );
+
+/* Get workspace. */
+      pa = (double *) astMalloc( sizeof(double)*naxes );
+      pb = (double *) astMalloc( sizeof(double)*naxes );
+      pc = (double *) astMalloc( sizeof(double)*naxes );
+
+/* If OK, apply the axis permutation array to obtain the coordinates in the
+   required order. */
+      if( astOK ) {
+         for( axis = 0; axis < naxes; axis++ ) {
+            pa[ perm[ axis ] ] = a[ axis ];
+            pb[ perm[ axis ] ] = b[ axis ];
+            pc[ perm[ axis ] ] = c[ axis ];
+         }
+
+/* Obtain the number of axes in the first component Frame. */
+         naxes1 = astGetNaxes( this->frame1 );
+
+/* Project the two input points into the two component Frames and
+   determine the angle between the points in each Frame. */
+         ang1 = astAngle( this->frame1, pa, pb, pc );
+         ang2 = astAngle( this->frame2, pa + naxes1, pb + naxes1,
+                          pc + naxes1 );
+
+/* The required angle is defined if one and only one of the two component
+   frames gives a defined angle between the two points. */
+         if( ang1 == AST__BAD ) {
+            result = ang2;
+         } else if( ang2 == AST__BAD ) {
+            result = ang1;
+         }
+      }
+
+/* Free the workspace */
+      pa = (double *) astFree( (void *) pa );
+      pb = (double *) astFree( (void *) pb );
+      pc = (double *) astFree( (void *) pc );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstObject *Cast( AstObject *this_object, AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Cast
+
+*  Purpose:
+*     Cast an Object into an instance of a sub-class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstObject *Cast( AstObject *this, AstObject *obj, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astCast
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a deep copy of an ancestral component of the
+*     supplied object. The required class of the ancestral component is
+*     specified by another object. Specifically, if "this" and "new" are
+*     of the same class, a copy of "this" is returned. If "this" is an
+*     instance of a subclass of "obj", then a copy of the component
+*     of "this" that matches the class of "obj" is returned. Otherwise,
+*     a NULL pointer is returned without error.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be cast.
+*     obj
+*        Pointer to an Object that defines the class of the returned Object.
+*        The returned Object will be of the same class as "obj".
+
+*  Returned Value:
+*     A pointer to the new Object. NULL if "this" is not a sub-class of
+*     "obj", or if an error occurs.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables; */
+   AstAxis *newaxis;
+   AstFrame *cfrm;
+   AstFrame *this;
+   AstObject *new;
+   astDECLARE_GLOBALS
+   int generation_gap;
+   int i;
+   int naxes;
+
+/* Initialise */
+   new = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* See how many steps up the class inheritance ladder it is from "obj"
+   to this class (CmpFrame). A positive value is returned if CmpFrame
+   is a sub-class of "obj". A negative value is returned if "obj" is
+   a sub-class of CmpFrame. Zero is returned if "obj" is a CmpFrame.
+   AST__COUSIN is returned if "obj" is not on the same line of descent
+   as CmpFrame. */
+   generation_gap = astClassCompare( (AstObjectVtab *) &class_vtab,
+                                     astVTAB( obj ) );
+
+/* If "obj" is a CmpFrame or a sub-class of CmpFrame, we can cast by
+   truncating the vtab for "this" so that it matches the vtab of "obJ",
+   and then taking a deep copy of "this". */
+   if( generation_gap <= 0 && generation_gap != AST__COUSIN ) {
+      new = astCastCopy( this_object, obj );
+
+/* If "obj" is not a CmpFrame or a sub-class of CmpFrame (e.g. it's a
+   Frame), we create a basic Frame containing the same axes and attributes
+   as the CmpFrame, and then attempt to cast this Frame into the class
+   indicated by "obj". */
+   } else {
+      this = (AstFrame *) this_object;
+
+/* Create a basic Frame with the right number of axes. */
+      naxes = astGetNaxes( this );
+      cfrm = astFrame( naxes, " ", status );
+
+/* Replace the Axis structures in the basic Frame with those in the
+   CmpFrame. */
+      for( i = 0; i < naxes; i++ ) {
+         newaxis = astGetAxis( this, i );
+         astSetAxis( cfrm, i, newaxis );
+         newaxis = astAnnul( newaxis );
+      }
+
+/* Overlay the properties of the CmpFrame onto the basic Frame. */
+      astOverlay( this, NULL, cfrm );
+
+/* Try to cast the basic Frame to the class of "obj". */
+      new = astCast( cfrm, obj );
+
+/* Annull the basic Frame. */
+      cfrm = astAnnul( cfrm );
+   }
+
+/* Return the new pointer. */
+   return new;
+}
+
+static double Centre( AstFrame *this_frame, int axis, double value, double gap, int *status ) {
+/*
+*  Name:
+*     Centre
+
+*  Purpose:
+*     Find a "nice" central value for tabulating CmpFrame axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     double  Centre( AstFrame *this_frame, int axis, double value,
+*                     double gap, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astCentre method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns an axis value which produces a nice formatted
+*     value suitable for a major tick mark on a plot axis, close to the
+*     supplied axis value.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the axis (zero-based) for which a central value
+*        is to be found.
+*     value
+*        An arbitrary axis value in the section that is being plotted.
+*     gap
+*        The gap size.
+
+*  Returned Value:
+*     The nice central axis value.
+
+*  Notes:
+*     - A value of zero is returned if the supplied gap size is zero.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   AstFrame *frame;              /* Pointer to Frame containing axis */
+   double result;                /* Result value to return */
+   int naxes1;                   /* Number of axes in frame1 */
+   int set1;                     /* Digits attribute set? */
+   int set2;                     /* Format attribute set? */
+
+/* Initialise. */
+   result = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Validate and permute the axis index supplied. */
+   axis = astValidateAxis( this, axis, 1, "astCentre" );
+
+/* Determine the number of axes in the first component Frame. */
+   naxes1 = astGetNaxes( this->frame1 );
+   if ( astOK ) {
+
+/* Decide which component Frame contains the axis and adjust the axis
+   index if necessary. */
+      frame = ( axis < naxes1 ) ? this->frame1 : this->frame2;
+      axis = ( axis < naxes1 ) ? axis : axis - naxes1;
+
+/* Since the component Frame is "managed" by the enclosing CmpFrame,
+   we next test if any Frame attributes which may affect the result
+   are undefined (i.e. have not been explicitly set). If so, we
+   over-ride them, giving them temporary values dictated by the
+   CmpFrame. Only the Digits and Format attributes are relevant here. */
+      set1 = astTestDigits( frame );
+      if ( !set1 ) astSetDigits( frame, astGetDigits( this ) );
+
+      set2 = astTestFormat( frame, axis );
+      if ( !set2 ) astSetFormat( frame, axis, astGetFormat( this, axis ) );
+
+/* Invoke the Frame's astCentre method to find the central value. */
+      result = astCentre( frame, axis, value, gap );
+
+/* Clear Frame attributes which were temporarily over-ridden. */
+      if ( !set1 ) astClearDigits( frame );
+      if ( !set2 ) astClearFormat( frame, axis );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static void ClearAlignSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     ClearAlignSystem
+
+*  Purpose:
+*     Clear the value of the AlignSystem attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void ClearAlignSystem( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astClearAlignSystem method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the AlignSystem value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to clear the CmpFrame AlignSystem value. */
+   (*parent_clearalignsystem)( this_frame, status );
+
+/* Now clear the AlignSystem attribute in the two component Frames. */
+   astClearAlignSystem( this->frame1 );
+   astClearAlignSystem( this->frame2 );
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astClearAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     CmpFrame, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   AstFrame *pfrm;               /* Pointer to primary Frame containing axis */
+   char buf1[80];                /* For for un-indexed attribute name */
+   char buf2[80];                /* For for indexed attribute name */
+   int axis;                     /* Sipplied (1-based) axis index */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* Number of characters used so dar */
+   int oldrep;                   /* Original error reporting state */
+   int paxis;                    /* Index of primary Frame axis */
+   int ok;                       /* Has the attribute been accessed succesfully? */
+
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_object;
+
+/* Obtain the length of the "attrib" string. */
+   len = strlen( attrib );
+
+/* Indicate we have not yet acessed the attribute succesfully. */
+   ok = 0;
+
+/* First check the supplied attribute name against each of the attribute
+   names defined by this class. In fact there is nothing to do here
+   since the CmpFrame class currently defines no extra attributes, but
+   this may change in the future. */
+   if( 0 ) {
+
+
+
+/* If the attribute is not a CmpFrame specific attribute... */
+   } else if( astOK ) {
+
+/* We want to allow easy access to the attributes of the component Frames.
+   That is, we do not want it to be necessary to extract a Frame from
+   its parent CmpFrame in order to access its attributes. For this reason
+   we first temporarily switch off error reporting so that if an attempt
+   to access the attribute fails, we can try a different approach. */
+      oldrep = astReporting( 0 );
+
+/* Our first attempt is to see if the attribute is recognised by the parent
+   class (Frame). */
+      (*parent_clearattrib)( this_object, attrib, status );
+
+/* Indicate success. */
+      if( astOK ) {
+         ok = 1;
+
+/* Otherwise, clear the error condition so that we can try a different
+   approach. */
+      } else {
+         astClearStatus;
+
+/* If the attribute is qualified by an axis index, try accessing it as an
+   attribute of the primary Frame containing the specified index. */
+         if ( nc = 0,
+             ( 2 == astSscanf( attrib, "%[^(](%d)%n", buf1, &axis, &nc ) )
+             && ( nc >= len ) ) {
+
+/* Find the primary Frame containing the specified axis. */
+            astPrimaryFrame( this, axis - 1, &pfrm, &paxis );
+            if( astOK ) {
+
+/* astPrimaryFrame returns the original - unpermuted - axis index within
+   the primary Frame. So we need to take into account any axis permutation
+   which has been applied to the primary Frame when forming the attribute name
+   to use below. Find the permuted (external) axis index which corresponds to
+   the internal (unpermuted) axis index "paxis". */
+               paxis = astValidateAxis( pfrm, paxis, 0, "astClear" );
+
+/* Create a new attribute with the same name but with the axis index
+   appropriate to the primary Frame. */
+               sprintf( buf2, "%s(%d)", buf1, paxis + 1 );
+
+/* Attempt to access the attribute. */
+               astClearAttrib( pfrm, buf2 );
+
+/* Indicate success. */
+               if( astOK ) {
+                  ok = 1;
+
+/* Otherwise clear the status value, and try again without any axis index. */
+               } else {
+                  astClearStatus;
+                  astClearAttrib( pfrm, buf1 );
+
+/* Indicate success, or clear the status value. */
+                  if( astOK ) {
+                     ok = 1;
+                  } else {
+                     astClearStatus;
+                  }
+               }
+
+/* Free the primary frame pointer. */
+               pfrm = astAnnul( pfrm );
+            }
+
+/* If the attribute is not qualified by an axis index, try accessing it
+   using the primary Frame of each axis in turn. */
+         } else {
+
+/* Loop round all axes attribute. */
+	    for( axis = 0; axis < astGetNaxes( this ); axis++ ) {
+
+/* Get the primary Frame containing this axis. */
+               astPrimaryFrame( this, axis, &pfrm, &paxis );
+
+/* Attempt to access the attribute as an attribute of the primary Frame. */
+               astClearAttrib( pfrm, attrib );
+
+/* Free the primary Frame pointer. */
+               pfrm = astAnnul( pfrm );
+
+/* Indicate success, or clear the status value. */
+               if( astOK ) {
+                  ok = 1;
+               } else {
+                  astClearStatus;
+               }
+            }
+         }
+      }
+
+/* Re-instate the original error reporting state. */
+      astReporting( oldrep );
+
+   }
+
+/* Report an error if the attribute could not be accessed. */
+   if( !ok && astOK ) {
+      astError( AST__BADAT, "astClear: The %s given does not have an attribute "
+                "called \"%s\".", status, astGetClass( this ), attrib );
+   }
+
+}
+
+static void ClearDut1( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     ClearDut1
+
+*  Purpose:
+*     Clear the value of the Dut1 attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void ClearDut1( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astClearDut1 method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the Dut1 value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to clear the CmpFrame Dut1 value. */
+   (*parent_cleardut1)( this_frame, status );
+
+/* Now clear the Dut1 attribute in the two component Frames. */
+   astClearDut1( this->frame1 );
+   astClearDut1( this->frame2 );
+}
+
+static void ClearEpoch( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     ClearEpoch
+
+*  Purpose:
+*     Clear the value of the Epoch attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void ClearEpoch( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astClearEpoch method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the Epoch value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to clear the CmpFrame epoch. */
+   (*parent_clearepoch)( this_frame, status );
+
+/* Now clear the Epoch attribute in the two component Frames. */
+   astClearEpoch( this->frame1 );
+   astClearEpoch( this->frame2 );
+}
+
+static void ClearObsAlt( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     ClearObsAlt
+
+*  Purpose:
+*     Clear the value of the ObsAlt attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void ClearObsAlt( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astClearObsAlt method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the ObsAlt value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to clear the CmpFrame ObsAlt. */
+   (*parent_clearobsalt)( this_frame, status );
+
+/* Now clear the ObsAlt attribute in the two component Frames. */
+   astClearObsAlt( this->frame1 );
+   astClearObsAlt( this->frame2 );
+}
+
+static void ClearObsLat( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     ClearObsLat
+
+*  Purpose:
+*     Clear the value of the ObsLat attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void ClearObsLat( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astClearObsLat method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the ObsLat value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to clear the CmpFrame ObsLat. */
+   (*parent_clearobslat)( this_frame, status );
+
+/* Now clear the ObsLat attribute in the two component Frames. */
+   astClearObsLat( this->frame1 );
+   astClearObsLat( this->frame2 );
+}
+
+static void ClearObsLon( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     ClearObsLon
+
+*  Purpose:
+*     Clear the value of the ObsLon attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void ClearObsLon( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astClearObsLon method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the ObsLon value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to clear the CmpFrame ObsLon. */
+   (*parent_clearobslon)( this_frame, status );
+
+/* Now clear the ObsLon attribute in the two component Frames. */
+   astClearObsLon( this->frame1 );
+   astClearObsLon( this->frame2 );
+}
+
+static int ComponentMatch( AstCmpFrame *template, AstFrame *target, int matchsub,
+                           int icomp, int **template_axes, int **target_axes,
+                           AstMapping **map, AstFrame **result, int *status ) {
+/*
+*  Name:
+*     ComponentMatch
+
+*  Purpose:
+*     Determine if conversion is possible between a component Frame in a
+*     template CmpFrame and another target Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int ComponentMatch( AstCmpFrame *template, AstFrame *target, int matchsub,
+*                         int icomp, int **template_axes, int **target_axes,
+*                         AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     CmpFrame member function
+
+*  Description:
+*     This function is like astMatch, but it compares the supplied target
+*     Frame with a specified component Frame of the supplied template
+*     CmpFrame, rather than with the entire template CmpFrame. If a match
+*     is found, the returned Mapping, Frame and axis lists are adjusted so
+*     that they refer to the entire template CmpFrame.
+
+*  Parameters:
+*     template
+*        Pointer to the template CmpFrame. This describes the
+*        coordinate system (or set of possible coordinate systems)
+*        into which we wish to convert our coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate
+*        system in which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case (i.e. if the
+*        target is of a more specialised class than the template). In
+*        this latter case, the target is cast down to the class of the
+*        template.
+*     icomp
+*        The index of the component Frame to use within the template
+*        CmpFrame; 0 or 1.
+*     template_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the (zero-based) index of the
+*        template CmpFrame axis from which it is derived. If it is not
+*        derived from any template axis, a value of -1 will be
+*        returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the (zero-based) index of the
+*        target Frame axis from which it is derived. If it is not
+*        derived from any target axis, a value of -1 will be returned
+*        instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will
+*        be returned if the requested coordinate conversion is
+*        possible. If returned, the forward transformation of this
+*        Mapping may be used to convert coordinates between the
+*        "target" Frame and the "result" Frame (see below) and the
+*        inverse transformation will convert in the opposite
+*        direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be
+*        returned if the requested coordinate conversion is
+*        possible. If returned, this Frame describes the coordinate
+*        system that results from applying the returned Mapping
+*        (above) to the "target" coordinate system. In general, this
+*        Frame will combine attributes from (and will therefore be
+*        more specific than) both the target Frame and the template
+*        CmpFrame. In particular, when the template allows the
+*        possibility of transformaing to any one of a set of
+*        alternative coordinate systems, the "result" Frame will
+*        indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate
+*     conversion is possible. Otherwise zero is returned (this will
+*     not in itself result in an error condition).
+
+*  Notes:
+*     - By default, the "result" Frame will have its number of axes
+*     and axis order determined by the "template" CmpFrame. However,
+*     if the PreserveAxes attribute of the template CmpFrame is
+*     non-zero, then the axis count and axis order of the "target"
+*     Frame will be used instead.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *ctemplate;
+   AstFrame *fother;
+   AstFrame *fresult;
+   AstMapping *fmap;
+   AstPermMap *pm;
+   const int *perm;
+   int *ftarget_axes;
+   int *ftemplate_axes;
+   int *inperm;
+   int *operm;
+   int *outperm;
+   int axis;
+   int match;
+   int nax1;
+   int nax2;
+   int naxr;
+   int prax;
+   int praxo;
+   int result_naxes;
+   int template_naxes;
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Get a pointer to the requested component Frame of the template CmpFrame. */
+   ctemplate = icomp ? template->frame2 :template->frame1;
+
+/* Temporarily set the component Frame PreserveAxes value to that of the
+   template CmpFrame. PreserveAxes determines whether astMatch returns a
+   result Frame that looks like the template or the target. */
+   if( astTestPreserveAxes( ctemplate ) ) {
+      praxo = astGetPreserveAxes( ctemplate ) ? 1 : 0;
+   } else {
+      praxo = -1;
+   }
+   prax = astGetPreserveAxes( template );
+   astSetPreserveAxes( ctemplate, prax );
+
+/* Attempt to find a match between the axes of the supplied target Frame
+   and the axes of the selected component Frame in the template. */
+   match = astMatch( ctemplate, target, matchsub, &ftemplate_axes, &ftarget_axes,
+                     &fmap, &fresult );
+
+/* Restore the original PreserveAxes value in the component template
+   Frame. */
+   if( praxo == -1 ) {
+      astClearPreserveAxes( ctemplate );
+   } else {
+      astSetPreserveAxes( ctemplate, praxo );
+   }
+
+/* If a match was found, we need to adjust the Mapping, Frame and axis
+   lists returned by the above call to astMatch so that they refer to the
+   full template CmpFrame or target (depending on PreserveAxes). */
+   if( match ) {
+
+/* Get the number of axes in each component Frame and the total number of
+   axes in the template CmpFrame. */
+      nax1 = astGetNaxes( template->frame1 );
+      nax2 = astGetNaxes( template->frame2 );
+      template_naxes = nax1 + nax2;
+
+/* Get the axis permutation array from the template and get its inverse.
+   The "perm" array holds the internal axis index at each external axis
+   index. The "operm" array holds the external axis index at each
+   internal axis index. */
+      perm = astGetPerm( template );
+      operm = astMalloc( sizeof( int )*(size_t)template_naxes );
+      if( astOK) {
+         for( axis = 0; axis < template_naxes; axis++ ) {
+            operm[ perm[ axis ] ] = axis;
+         }
+
+/* The PreserveAxes attribute is used by astMatch to decide whether the
+   result Frame should inherit its axes from the target frame or the
+   template frame. First deal with cases where the order and count of axes
+   in the result frame is the same as the target. */
+         if( prax ) {
+
+/* Return the result Frame and Mapping unchanged since they already refer
+   to the full target Frame used in the above call to astMatch. */
+            *result = astClone( fresult );
+            *map = astClone( fmap );
+
+/* Also return the lists of target axes unchanged. */
+            *target_axes = ftarget_axes;
+
+/* The values in the template axes list refer to the component template
+   Frame, but we want to return values that refer to the full template
+   CmpFrame. This involve sup to two setps 1) for the second component
+   Frame only, increase the axis numbers by the number of axes in the
+   first component Frame, and 2) take account of any axis permutation in
+   the template. First allocate memory for the returned list (which,
+   because PreserveAxes is zero, will have an entry for each template axis). */
+            *template_axes = astMalloc( sizeof( int )*template_naxes );
+
+/* Now, if the second component of the template has been selected, increment
+   the template axes so that they give the internal axis indices of the
+   second component Frame within the CmpFrame. The first component axes
+   will be unchanged. */
+            result_naxes = astGetNaxes( fresult );
+            if( icomp ) {
+               for( axis = 0; axis < result_naxes; axis++ ) {
+                  ftemplate_axes[ axis ] += nax1;
+               }
+            }
+
+/* Now copy the internal axis value into the returned array, modifying them
+   in the process from internal to external axis ordering. */
+            for( axis = 0; axis < result_naxes; axis++ ) {
+               (*template_axes)[ axis ] = operm[ ftemplate_axes[ axis ] ];
+            }
+
+/* If the order and count of axes in the result frame is the same as the
+   template CmpFrame... */
+         } else {
+
+/* We need to adjust the Mapping, Frame and axis lists returned by the
+   above call to astMatch so that they refer to the supplied template
+   CmpFrame rather than to the selected component Frame. Get the number
+   of axes in the result Frame returned by astMatch (naxr) and the number
+   in the result Frame returned by this function (result-naxes). */
+            naxr = astGetNaxes( fresult );
+            result_naxes = ( icomp ? nax1 : nax2 ) + naxr;
+
+/* Create the full result Frame by combining the partial result Frame
+   returned by astMatch above with the other component Frame from the
+   template. */
+            if( icomp ) {
+               fother = astCopy( template->frame1 );
+               *result = (AstFrame *) astCmpFrame( fother, fresult, "", status );
+            } else {
+               fother = astCopy( template->frame2 );
+               *result = (AstFrame *) astCmpFrame( fresult, fother, "", status );
+            }
+            fother = astAnnul( fother );
+
+/* Modify the Mapping returned by the above call to astMatch so that it
+   produces positions within the full result Frame created above. */
+            if( icomp ) {
+               inperm = astMalloc( sizeof( int )*(size_t) naxr );
+               outperm = astMalloc( sizeof( int )*(size_t) result_naxes );
+               if( astOK ) {
+                  for( axis = 0; axis < nax1; axis++ ) outperm[ axis ] = -1;
+                  for( axis = 0; axis < naxr; axis++ ) {
+                     outperm[ axis + nax1 ] = axis;
+                     inperm[ axis ] = axis + nax1;
+                  }
+               }
+
+            } else {
+               inperm = NULL;
+               outperm = NULL;
+            }
+
+            pm = astPermMap( naxr, inperm, result_naxes, outperm, NULL, "", status );
+            *map = (AstMapping *) astCmpMap( fmap, pm, 1, "", status );
+
+/* Free resources. */
+            pm = astAnnul( pm );
+            if( inperm ) inperm = astFree( inperm );
+            if( outperm ) outperm = astFree( outperm );
+
+/* Allocate memory for the returned list of axes. */
+            *template_axes = astMalloc( sizeof( int )*(size_t)result_naxes );
+            *target_axes = astMalloc( sizeof( int )*(size_t)result_naxes );
+
+/* The axis indices returned by astMatch above will refer to the selected
+   component Frame rather than the permuted (i.e. external) axis indices for
+   the template CmpFrame. Change the template axes list so that they describe
+   the axes in the full result Frame in terms of the external template axis
+   numbering. This involves shifting the indices for the second component
+   Frame to leave room for the axes of the first component Frame, and
+   also permuting the axis indices from internal to external order. */
+            if( icomp ) {
+               for( axis = 0; axis < nax1; axis++ ) {
+                  (*template_axes)[ axis ] = operm[ axis ];
+               }
+
+               for( ; axis < result_naxes; axis++ ) {
+                  (*template_axes)[ axis ] = operm[ nax1 + ftemplate_axes[ axis - nax1 ] ];
+               }
+
+            } else {
+               for( axis = 0; axis < nax1; axis++ ) {
+                  (*template_axes)[ axis ] = operm[ ftemplate_axes[ axis ] ];
+               }
+
+               for( ; axis < result_naxes; axis++ ) {
+                  (*template_axes)[ axis ] = operm[ axis ];
+               }
+            }
+
+/* Change the target axes list so that they describe the axes in the
+   full result Frame (this just means padding with -1 to indicate that
+   the extra axes do not correspond to any axis in the target). */
+            for( axis = 0; axis < naxr; axis++ ) {
+               (*target_axes)[ axis ] = ftarget_axes[ axis ];
+            }
+
+            for( ; axis < result_naxes; axis++ ) {
+               (*target_axes)[ axis ] = -1;
+            }
+
+/* Free resources */
+            ftarget_axes = astFree( ftarget_axes );
+         }
+      }
+
+      operm = astFree( operm );
+      ftemplate_axes = astFree( ftemplate_axes );
+      fmap = astAnnul( fmap );
+      fresult = astAnnul( fresult );
+
+   }
+
+/* If an error occurred, free all allocated memory, annul the result
+   Object pointers and clear all returned values. */
+   if ( !astOK ) {
+      *template_axes = astFree( *template_axes );
+      *target_axes = astFree( *target_axes );
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static void Decompose( AstMapping *this_cmpframe, AstMapping **map1,
+                       AstMapping **map2, int *series, int *invert1,
+                       int *invert2, int *status ) {
+/*
+*
+*  Name:
+*     Decompose
+
+*  Purpose:
+*     Decompose a CmpFrame into two component CmpFrames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void Decompose( AstMapping *this, AstMapping **map1,
+*                     AstMapping **map2, int *series,
+*                     int *invert1, int *invert2, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astDecompose
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns pointers to two Mappings which, when applied
+*     either in series or parallel, are equivalent to the supplied Mapping.
+*
+*     Since the Frame class inherits from the Mapping class, Frames can
+*     be considered as special types of Mappings and so this method can
+*     be used to decompose either CmpMaps or CmpFrames.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     map1
+*        Address of a location to receive a pointer to first component
+*        Mapping.
+*     map2
+*        Address of a location to receive a pointer to second component
+*        Mapping.
+*     series
+*        Address of a location to receive a value indicating if the
+*        component Mappings are applied in series or parallel. A non-zero
+*        value means that the supplied Mapping is equivalent to applying map1
+*        followed by map2 in series. A zero value means that the supplied
+*        Mapping is equivalent to applying map1 to the lower numbered axes
+*        and map2 to the higher numbered axes, in parallel.
+*     invert1
+*        The value of the Invert attribute to be used with map1.
+*     invert2
+*        The value of the Invert attribute to be used with map2.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Any changes made to the component rames using the returned
+*     pointers will be reflected in the supplied CmpFrame.
+
+*-
+*/
+
+
+/* Local Variables: */
+   AstCmpFrame *this;              /* Pointer to CmpMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpMap structure. */
+   this = (AstCmpFrame *) this_cmpframe;
+
+/* The components Frames of a CmpFrame are considered to be parallel
+   Mappings. */
+   if( series ) *series = 0;
+
+/* The Frames are returned in their original order whether or not the
+   CmpFrame has been inverted. */
+   if( map1 ) *map1 = astClone( this->frame1 );
+   if( map2 ) *map2 = astClone( this->frame2 );
+
+/* If the CmpFrame has been inverted, return inverted Invert flags. */
+   if( astGetInvert( this ) ) {
+      if( invert1 ) *invert1 = astGetInvert( this->frame1 ) ? 0 : 1;
+      if( invert2 ) *invert2 = astGetInvert( this->frame2 ) ? 0 : 1;
+
+/* If the CmpFrame has not been inverted, return the current Invert flags. */
+   } else {
+      if( invert1 ) *invert1 = astGetInvert( this->frame1 );
+      if( invert2 ) *invert2 = astGetInvert( this->frame2 );
+   }
+}
+
+static double Distance( AstFrame *this_frame,
+                        const double point1[], const double point2[], int *status ) {
+/*
+*  Name:
+*     Distance
+
+*  Purpose:
+*     Calculate the distance between two points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     double Distance( AstFrame *this,
+*                      const double point1[], const double point2[], int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astDistance method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function finds the distance between two points whose
+*     CmpFrame coordinates are given. The distance calculated is that
+*     along the geodesic curve that joins the two points. This is
+*     computed as the Cartesian sum of the distances between the
+*     points when their coordinates are projected into each of the
+*     CmpFrame's component Frames.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     point1
+*        An array of double, with one element for each CmpFrame axis,
+*        containing the coordinates of the first point.
+*     point2
+*        An array of double, with one element for each CmpFrame axis,
+*        containing the coordinates of the second point.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The distance between the two points.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input coordinates has this value.
+*     - A "bad" value will also be returned if this function is
+*     invoked with the AST error status set or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   const int *perm;              /* Axis permutation array */
+   double *p1;                   /* Pointer to permuted point1 coordinates */
+   double *p2;                   /* Pointer to permuted point2 coordinates */
+   double dist1;                 /* Distance in frame1 */
+   double dist2;                 /* Distance in frame2 */
+   double result;                /* Value to return */
+   int axis;                     /* Loop counter for axes */
+   int naxes1;                   /* Number of axes in frame1 */
+   int naxes;                    /* Number of axes in CmpFrame */
+   int ok;                       /* No "bad" coordinates found? */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Obtain a pointer to the CmpFrame's axis permutation array. */
+   perm = astGetPerm( this );
+
+/* Obtain the number of axes in the CmpFrame and in the first
+   component Frame. */
+   naxes = astGetNaxes( this );
+   naxes1 = astGetNaxes( this->frame1 );
+
+/* Allocate memory to hold the permuted coordinates of each point. */
+   p1 = astMalloc( sizeof( double ) * (size_t) naxes );
+   p2 = astMalloc( sizeof( double ) * (size_t) naxes );
+   if ( astOK ) {
+
+/* Examine the coordinates of both points and note if any coordinate
+   value is "bad". */
+      ok = 1;
+      for ( axis = 0; axis < naxes; axis++ ) {
+         if ( ( point1[ axis ] == AST__BAD ) ||
+              ( point2[ axis ] == AST__BAD ) ) {
+            ok = 0;
+            break;
+
+/* Permute good coordinates using the CmpFrame's axis permutation
+   array to put them into the order required internally (i.e. by the
+   two component Frames). */
+         } else {
+            p1[ perm[ axis ] ] = point1[ axis ];
+            p2[ perm[ axis ] ] = point2[ axis ];
+         }
+      }
+
+/* If no "bad" coordinates were found, obtain the distance between the
+   two points when their coordinates are projected into each component
+   Frame. */
+      if ( ok ) {
+         dist1 = astDistance( this->frame1, p1, p2 );
+         dist2 = astDistance( this->frame2, p1 + naxes1, p2 + naxes1 );
+
+/* If the distances found were OK, compute the distance between the
+   two points as the Cartesian sum of the two component distances. */
+         if ( astOK && ( dist1 != AST__BAD ) && ( dist2 != AST__BAD ) ) {
+            result = sqrt( ( dist1 * dist1 ) + ( dist2 * dist2 ) );
+         }
+      }
+   }
+
+/* Free the memory used for the permuted coordinates. */
+   p1 = astFree( p1 );
+   p2 = astFree( p2 );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static int Fields( AstFrame *this_frame, int axis, const char *fmt,
+                   const char *str, int maxfld, char **fields,
+                   int *nc, double *val, int *status ) {
+/*
+*+
+*  Name:
+*     astFields
+
+*  Purpose:
+*     Identify numerical fields within a formatted CmpFrame axis value.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int astFields( AstFrame *this, int axis, const char *fmt,
+*                    const char *str, int maxfld, char **fields,
+*                    int *nc, double *val )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astFields
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function identifies the numerical fields within a CmpFrame axis
+*     value that has been formatted using astAxisFormat. It assumes that
+*     the value was formatted using the supplied format string. It also
+*     returns the equivalent floating point value.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     axis
+*        The number of the CmpFrame axis for which the values have been
+*        formatted (axis numbering starts at zero for the first axis).
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format used when creating "str".
+*     str
+*        Pointer to a constant null-terminated string containing the
+*        formatted value.
+*     maxfld
+*        The maximum number of fields to identify within "str".
+*     fields
+*        A pointer to an array of at least "maxfld" character pointers.
+*        Each element is returned holding a pointer to the start of the
+*        corresponding field  in "str" (in the order in which they occur
+*        within "str"), or NULL if no corresponding field can be found.
+*     nc
+*        A pointer to an array of at least "maxfld" integers. Each
+*        element is returned holding the number of characters in the
+*        corresponding field, or zero if no corresponding field can be
+*        found.
+*     val
+*        Pointer to a location at which to store the value
+*        equivalent to the returned field values. If this is NULL,
+*        it is ignored.
+
+*  Returned Value:
+*     The number of fields succesfully identified and returned.
+
+*  Notes:
+*     - Leading and trailing spaces are ignored.
+*     - If the formatted value is not consistent with the supplied format
+*     string, then a value of zero will be returned, "fields" will be
+*     returned holding NULLs, "nc" will be returned holding zeros, and
+*     "val" is returned holding VAL__BAD.
+*     - Fields are counted from the start of the formatted string. If the
+*     string contains more than "maxfld" fields, then trailing fields are
+*     ignored.
+*     - If this function is invoked with the global error status set, or
+*     if it should fail for any reason, then a value of zero will be returned
+*     as the function value, and "fields", "nc" and "val"  will be returned
+*     holding their supplied values
+*-
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   AstFrame *frame;              /* Pointer to Frame containing axis */
+   int naxes1;                   /* Number of axes in frame1 */
+   int result;                   /* Result field count to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Validate and permute the axis index supplied. */
+   axis = astValidateAxis( this, axis, 1, "astFields" );
+
+/* Determine the number of axes in the first component Frame. */
+   naxes1 = astGetNaxes( this->frame1 );
+   if ( astOK ) {
+
+/* Decide which component Frame contains the axis and adjust the axis
+   index if necessary. */
+      frame = ( axis < naxes1 ) ? this->frame1 : this->frame2;
+      axis = ( axis < naxes1 ) ? axis : axis - naxes1;
+
+/* Invoke the Frame's astFields method to perform the processing. */
+      result = astFields( frame, axis, fmt, str, maxfld, fields,
+                          nc, val );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *Format( AstFrame *this_frame, int axis, double value, int *status ) {
+/*
+*  Name:
+*     Format
+
+*  Purpose:
+*     Format a coordinate value for a CmpFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     const char *Format( AstFrame *this, int axis, double value, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astFormat method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to a string containing the
+*     formatted (character) version of a coordinate value for a
+*     CmpFrame axis. The formatting applied is that specified by a
+*     previous invocation of the astSetFormat method (or a default
+*     format appropriate to the axis in question).
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     axis
+*        The number of the axis (zero-based) for which formatting is
+*        to be performed.
+*     value
+*        The coordinate value to be formatted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the formatted
+*     value.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   AstFrame *frame;              /* Pointer to Frame containing axis */
+   const char *result;           /* Pointer value to return */
+   int naxes1;                   /* Number of axes in frame1 */
+   int set;                      /* Digits attribute set? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Validate and permute the axis index supplied. */
+   axis = astValidateAxis( this, axis, 1, "astFormat" );
+
+/* Determine the number of axes in the first component Frame. */
+   naxes1 = astGetNaxes( this->frame1 );
+   if ( astOK ) {
+
+/* Decide which component Frame contains the axis and adjust the axis
+   index if necessary. */
+      frame = ( axis < naxes1 ) ? this->frame1 : this->frame2;
+      axis = ( axis < naxes1 ) ? axis : axis - naxes1;
+
+/* Since the component Frame is "managed" by the enclosing CmpFrame,
+   we next test if any Frame attributes which may affect the result
+   are undefined (i.e. have not been explicitly set). If so, we
+   over-ride them, giving them temporary values dictated by the
+   CmpFrame. Only the Digits attribute is relevant here. */
+      set = astTestDigits( frame );
+      if ( !set ) astSetDigits( frame, astGetDigits( this ) );
+
+/* Invoke the Frame's astFormat method to format the value. */
+      result = astFormat( frame, axis, value );
+
+/* Clear Frame attributes which were temporarily over-ridden. */
+      if ( !set ) astClearDigits( frame );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *FrameGrid( AstFrame *this_object, int size, const double *lbnd,
+                               const double *ubnd, int *status ){
+/*
+*  Name:
+*     FrameGrid
+
+*  Purpose:
+*     Return a grid of points covering a rectangular area of a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstPointSet *FrameGrid( AstFrame *this_frame, int size,
+*                             const double *lbnd, const double *ubnd,
+*                             int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astFrameGrid
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a PointSet containing positions spread
+*     approximately evenly throughtout a specified rectangular area of
+*     the Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     size
+*        The preferred number of points in the returned PointSet. The
+*        actual number of points in the returned PointSet may be
+*        different, but an attempt is made to stick reasonably closely to
+*        the supplied value.
+*     lbnd
+*        Pointer to an array holding the lower bound of the rectangular
+*        area on each Frame axis. The array should have one element for
+*        each Frame axis.
+*     ubnd
+*        Pointer to an array holding the upper bound of the rectangular
+*        area on each Frame axis. The array should have one element for
+*        each Frame axis.
+
+*  Returned Value:
+*     A pointer to a new PointSet holding the grid of points.
+
+*  Notes:
+*     - A NULL pointer is returned if an error occurs.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;
+   AstPointSet *ps1;
+   AstPointSet *ps2;
+   AstPointSet *result;
+   const int *perm;
+   double **ptr1;
+   double **ptr2;
+   double **ptr;
+   double *lbnd1;
+   double *lbnd2;
+   double *p;
+   double *ubnd1;
+   double *ubnd2;
+   double v;
+   int axis;
+   int iax1;
+   int iax2;
+   int iaxis;
+   int ip1;
+   int ip2;
+   int nax1;
+   int nax2;
+   int naxes;
+   int npoint1;
+   int npoint2;
+   int npoint;
+   int size1;
+   int size2;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_object;
+
+/* Get the number of axes in each component Frame, and the total number
+   of axes. */
+   nax1 = astGetNaxes( this->frame1 );
+   nax2 = astGetNaxes( this->frame2 );
+   naxes = nax1 + nax2;
+
+/* Allocate memory to hold bounds for each component Frame */
+   lbnd1 = astMalloc( nax1*sizeof( double ) );
+   ubnd1 = astMalloc( nax1*sizeof( double ) );
+   lbnd2 = astMalloc( nax2*sizeof( double ) );
+   ubnd2 = astMalloc( nax2*sizeof( double ) );
+
+/* Obtain a pointer to the CmpFrame's axis permutation array. This array
+   holds the original axis index for each current Frame axis index. */
+   perm = astGetPerm( this );
+
+/* Check pointers can be used safely, and check the supplied size value
+   is good. */
+   if( astOK && size > 0 ) {
+
+/* Copy the supplied bounds into the work arrays, permuting them in the
+   process so that they use the internal axis numbering of the two
+   component Frames. */
+      for( axis = 0; axis < naxes; axis++ ) {
+         iaxis = perm[ axis ];
+         if( iaxis < nax1 ) {
+            lbnd1[ iaxis ] = lbnd[ axis ];
+            ubnd1[ iaxis ] = ubnd[ axis ];
+         } else {
+            iaxis -= nax1;
+            lbnd2[ iaxis ] = lbnd[ axis ];
+            ubnd2[ iaxis ] = ubnd[ axis ];
+         }
+      }
+
+/* Get the target number of points to be used in the grid that covers the
+   first Frame. */
+      size1 = (int)( pow( size, (double)nax1/(double)naxes ) + 0.5 );
+
+/* Get the target number of points to be used in the grid that covers the
+   second Frame. */
+      size2 = (int)( (double)size/(double)size1 + 0.5 );
+
+/* Get the grids covering the two component Frames, and get the actual sizes
+   of the resulting PointSets. */
+      ps1 = astFrameGrid( this->frame1, size1, lbnd1, ubnd1 );
+      ptr1 = astGetPoints( ps1 );
+      npoint1 = astGetNpoint( ps1 );
+
+      ps2 = astFrameGrid( this->frame2, size2, lbnd2, ubnd2 );
+      ptr2 = astGetPoints( ps2 );
+      npoint2 = astGetNpoint( ps2 );
+
+/* Get the number of points in the returned FrameSet, and then create a
+   PointSet large enough to hold them. */
+      npoint = npoint1*npoint2;
+      result = astPointSet( npoint, naxes, " ", status );
+      ptr = astGetPoints( result );
+      if( astOK ) {
+
+/* For every point in the first Frame's PointSet, duplicate the second
+   Frame's entire PointSet, using the first Frame's axis values. */
+         for( ip1 = 0; ip1 < npoint1; ip1++ ) {
+            for( iax1 = 0; iax1 < nax1; iax1++ ) {
+               p = ptr[ iax1 ];
+               v = ptr1[ iax1 ][ ip1 ];
+               for( ip2 = 0; ip2 < npoint2; ip2++ ) {
+                  *(p++) = v;
+               }
+               ptr[ iax1 ] = p;
+            }
+            for( iax2 = 0; iax2 < nax2; iax2++ ) {
+               memcpy( ptr[ iax2 + nax1 ], ptr2[ iax2 ], npoint2*sizeof( double ) );
+               ptr[ iax2 + nax1 ] += npoint2*sizeof( double );
+            }
+         }
+
+/* Permute the returned PointSet so that it uses external axis numbering. */
+         astPermPoints( result, 1, perm );
+      }
+
+/* Free resources. */
+      ps1 = astAnnul( ps1 );
+      ps2 = astAnnul( ps2 );
+
+/* Report error if supplied values were bad. */
+   } else if( astOK ) {
+      astError( AST__ATTIN, "astFrameGrid(%s): The supplied grid "
+                "size (%d) is invalid (programming error).",
+                status, astGetClass( this ), size );
+   }
+
+/* Free resources. */
+   lbnd1 = astFree( lbnd1 );
+   ubnd1 = astFree( ubnd1 );
+   lbnd2 = astFree( lbnd2 );
+   ubnd2 = astFree( ubnd2 );
+
+/* Annul the returned PointSet if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the PointSet holding the grid. */
+   return result;
+}
+
+static double Gap( AstFrame *this_frame, int axis, double gap, int *ntick, int *status ) {
+/*
+*  Name:
+*     Gap
+
+*  Purpose:
+*     Find a "nice" gap for tabulating CmpFrame axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     double Gap( AstFrame *this, int axis, double gap, int *ntick, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astGap method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a gap size which produces a nicely spaced
+*     series of formatted values for a CmpFrame axis, the returned gap
+*     size being as close as possible to the supplied target gap
+*     size. It also returns a convenient number of divisions into
+*     which the gap can be divided.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     axis
+*        The number of the axis (zero-based) for which a gap is to be found.
+*     gap
+*        The target gap size.
+*     ntick
+*        Address of an int in which to return a convenient number of
+*        divisions into which the gap can be divided.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The nice gap size.
+
+*  Notes:
+*     - A value of zero is returned if the target gap size is zero.
+*     - A negative gap size is returned if the supplied gap size is negative.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   AstFrame *frame;              /* Pointer to Frame containing axis */
+   double result;                /* Result value to return */
+   int naxes1;                   /* Number of axes in frame1 */
+   int set;                      /* Digits attribute set? */
+
+/* Initialise. */
+   result = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Validate and permute the axis index supplied. */
+   axis = astValidateAxis( this, axis, 1, "astGap" );
+
+/* Determine the number of axes in the first component Frame. */
+   naxes1 = astGetNaxes( this->frame1 );
+   if ( astOK ) {
+
+/* Decide which component Frame contains the axis and adjust the axis
+   index if necessary. */
+      frame = ( axis < naxes1 ) ? this->frame1 : this->frame2;
+      axis = ( axis < naxes1 ) ? axis : axis - naxes1;
+
+/* Since the component Frame is "managed" by the enclosing CmpFrame,
+   we next test if any Frame attributes which may affect the result
+   are undefined (i.e. have not been explicitly set). If so, we
+   over-ride them, giving them temporary values dictated by the
+   CmpFrame. Only the Digits attribute is relevant here. */
+      set = astTestDigits( frame );
+      if ( !set ) astSetDigits( frame, astGetDigits( this ) );
+
+/* Invoke the Frame's astGap method to find the gap size. */
+      result = astGap( frame, axis, gap, ntick );
+
+/* Clear Frame attributes which were temporarily over-ridden. */
+      if ( !set ) astClearDigits( frame );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied CmpFrame,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;         /* Pointer to CmpFrame structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astGetObjSize( this->frame1 );
+   result += astGetObjSize( this->frame2 );
+   result += astTSizeOf( this->perm );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static AstSystemType GetAlignSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetAlignSystem
+
+*  Purpose:
+*     Obtain the AlignSystem attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstSystemType GetAlignSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetAlignSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the AlignSystem attribute for a CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The AlignSystem value.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   AstSystemType result;         /* Value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If a AlignSystem attribute has been set, invoke the parent method to obtain
+   it. */
+   if ( astTestAlignSystem( this ) ) {
+      result = (*parent_getalignsystem)( this_frame, status );
+
+/* Otherwise, provide a suitable default. */
+   } else {
+      result = AST__COMP;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "CmpFrame.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astGetAttrib
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a CmpFrame, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - The returned string pointer may point at memory allocated
+*     within the CmpFrame, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the CmpFrame. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   AstFrame *pfrm;               /* Pointer to primary Frame containing axis */
+   char buf1[80];                /* For for un-indexed attribute name */
+   char buf2[80];                /* For for indexed attribute name */
+   const char *result;           /* Pointer value to return */
+   int axis;                     /* Supplied (1-base) axis index */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* Length of string used so far */
+   int ok;                       /* Has the attribute been accessed succesfully? */
+   int oldrep;                   /* Original error reporting state */
+   int paxis;                    /* Index of primary Frame axis */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Indicate we have not yet acessed the attribute succesfully. */
+   ok = 0;
+
+/* First check the supplied attribute name against each of the attribute
+   names defined by this class. In fact there is nothing to do here
+   since the CmpFrame class currently defines no extra attributes, but
+   this may change in the future. */
+   if( 0 ) {
+
+/* If the attribute is not a CmpFrame specific attribute... */
+   } else if( astOK ) {
+
+/* We want to allow easy access to the attributes of the component Frames.
+   That is, we do not want it to be necessary to extract a Frame from
+   its parent CmpFrame in order to access its attributes. For this reason
+   we first temporarily switch off error reporting so that if an attempt
+   to access the attribute fails, we can try a different approach. */
+      oldrep = astReporting( 0 );
+
+/* If the attribute is qualified by an axis index, try accessing it as an
+   attribute of the primary Frame containing the specified index. */
+      if ( nc = 0,
+          ( 2 == astSscanf( attrib, "%[^(](%d)%n", buf1, &axis, &nc ) )
+          && ( nc >= len ) ) {
+
+/* Find the primary Frame containing the specified axis. */
+         astPrimaryFrame( this, axis - 1, &pfrm, &paxis );
+         if( astOK ) {
+
+/* astPrimaryFrame returns the original - unpermuted - axis index within
+   the primary Frame. So we need to take into account any axis permutation
+   which has been applied to the primary Frame when forming the attribute name
+   to use below. Find the permuted (external) axis index which corresponds to
+   the internal (unpermuted) axis index "paxis". */
+            paxis = astValidateAxis( pfrm, paxis, 0, "astGet" );
+
+/* Create a new attribute with the same name but with the axis index
+   appropriate to the primary Frame. */
+            sprintf( buf2, "%s(%d)", buf1, paxis + 1 );
+
+/* Attempt to access the attribute. */
+            result = astGetAttrib( pfrm, buf2 );
+
+/* Indicate success. */
+            if( astOK ) {
+               ok = 1;
+
+/* Otherwise clear the status value, and try again without any axis index. */
+            } else {
+               astClearStatus;
+               result = astGetAttrib( pfrm, buf1 );
+
+/* Indicate success, or clear the status value. */
+               if( astOK ) {
+                  ok = 1;
+               } else {
+                  astClearStatus;
+               }
+            }
+
+/* Free the primary frame pointer. */
+            pfrm = astAnnul( pfrm );
+         }
+
+/* If the attribute is not qualified by an axis index, try accessing it
+   using the parent Frame method. */
+      } else if( astOK ){
+         result = (*parent_getattrib)( this_object, attrib, status );
+
+/* Indicate success. */
+         if( astOK ) {
+            ok = 1;
+
+/* Otherwise, clear the error condition so that we can try a different
+   approach. */
+         } else {
+            astClearStatus;
+
+/* Next try accessing it using the primary Frame of each axis in turn.
+   Loop round all axes, until one is found which defines the specified
+   attribute. */
+	    for( axis = 0; axis < astGetNaxes( this ) && !ok; axis++ ) {
+
+/* Get the primary Frame containing this axis. */
+               astPrimaryFrame( this, axis, &pfrm, &paxis );
+
+/* Attempt to access the attribute as an attribute of the primary Frame. */
+               result = astGetAttrib( pfrm, attrib );
+
+/* Indicate success, or clear the status value. */
+               if( astOK ) {
+                  ok = 1;
+               } else {
+                  astClearStatus;
+               }
+
+/* Free the primary Frame pointer. */
+               pfrm = astAnnul( pfrm );
+
+            }
+         }
+      }
+
+/* Re-instate the original error reporting state. */
+      astReporting( oldrep );
+
+   }
+
+/* Report an error if the attribute could not be accessed. */
+   if( !ok && astOK ) {
+      astError( AST__BADAT, "astGet: The %s given does not have an attribute "
+                "called \"%s\".", status, astGetClass( this ), attrib );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static int GenAxisSelection( int naxes, int nselect, int axes[], int *status ) {
+/*
+*  Name:
+*     GenAxisSelection
+
+*  Purpose:
+*     Generate a sequence of axis selections.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int GenAxisSelection( int naxes, int nselect, int axes[], int *status )
+
+*  Class Membership:
+*     CmpFrame member function.
+
+*  Description:
+*     This function generates a sequence of axis selections covering
+*     all possible ways of selecting a specified number of axes from a
+*     Frame.
+
+*  Parameters:
+*     naxes
+*        The number of axes in the Frame.
+*     nselect
+*        The number of axes to be selected (between zero and "naxes").
+*     axes
+*        An array with "nselect" elements. On entry it should contain
+*        the (zero-based) indices of the initial set of distinct axes
+*        to be selected, in increasing order (initiallly this should
+*        just be the sequence [0,1,...nselect-1]). On exit, these
+*        indices will be updated to identify the next possible axis
+*        selection.
+*
+*        By invoking the function repeatedly, and passing this array
+*        each time, all possible selections will be covered.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a new axis selection has been returned. Zero if all
+*     possible selections have already been returned (in which case
+*     the selection returned this time is not valid and should not be
+*     used).
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   int i;                        /* Loop counter for axes */
+   int iselect;                  /* Selection index */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Start with the first axis index and loop until the selection has
+   been updated. */
+   iselect = 0;
+   while ( 1 ) {
+
+/* Increment the current axis index if it is the final one or it can
+   be incremented without equalling the one which follows (this ensures
+   the indices remain in increasing order). */
+      if ( ( iselect == ( nselect - 1 ) ) ||
+           ( axes[ iselect + 1 ] > ( axes[ iselect ] + 1 ) ) ) {
+         axes[ iselect ]++;
+
+/* After incrementing an index, reset all previous indices to their
+   starting values. */
+         for ( i = 0; i < iselect; i++ ) axes[ i ] = i;
+         break;
+
+/* If this axis index can't be incremented, consider the next one.
+   Quit if we go beyond the end of the selection array. */
+      } else if ( ++iselect >= nselect ) {
+         break;
+      }
+   }
+
+/* Return a result to indicate if we've reached the final selection
+   (when the final axis index goes out of range). */
+   return ( nselect > 0 ) && ( axes[ nselect - 1 ] < naxes );
+}
+
+static AstAxis *GetAxis( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetAxis
+
+*  Purpose:
+*     Obtain a pointer to a specified Axis from a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstAxis *GetAxis( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetAxis method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Axis object associated
+*     with one of the axes of a CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     axis
+*        The number of the axis (zero-based) for which an Axis pointer
+*        is required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the requested Axis object.
+
+*  Notes:
+*     - The reference count of the requested Axis object will be
+*     incremented by one to reflect the additional pointer returned by
+*     this function.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Vaiables: */
+   AstAxis *result;              /* Pointer value to return */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   int naxes1;                   /* Number of axes for frame1 */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Validate and permute the axis index supplied. */
+   axis = astValidateAxis( this, axis, 1, "astGetAxis" );
+
+/* Obtain the number of axes for frame1. */
+   naxes1 = astGetNaxes( this->frame1 );
+
+/* Decide which Frame the axis belongs to and obtain the required
+   Axis pointer. */
+   if ( axis < naxes1 ) {
+      result = astGetAxis( this->frame1, axis );
+   } else {
+      result = astGetAxis( this->frame2, axis - naxes1 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetDomain( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetDomain
+
+*  Purpose:
+*     Obtain a pointer to the Domain attribute string for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     const char *GetDomain( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetDomain protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function returns a pointer to the Domain attribute string
+*    for a CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a constant null-terminated string containing the
+*     Domain value.
+
+*  Notes:
+*     - The returned pointer or the string it refers to may become
+*     invalid following further invocation of this function or
+*     modification of the CmpFrame.
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   char *dom1;                   /* Pointer to first sub domain */
+   char *dom2;                   /* Pointer to second sub domain */
+   const char *result;           /* Pointer value to return */
+   const char *t;                /* Temporary pointer */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_frame);
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If a Domain attribute string has been set, invoke the parent method
+   to obtain a pointer to it. */
+   if ( astTestDomain( this ) ) {
+      result = (*parent_getdomain)( this_frame, status );
+
+/* Otherwise, provide a pointer to a suitable default string. */
+   } else {
+
+/* Get the Domain value for the two component Frames and store new
+   copies of them. This is necessary because the component Frames may
+   themselves be CmpFrames, resulting in this function being called
+   recursively and so causing the static "getdomain_buff" array to be used in
+   multiple contexts. */
+      t = astGetDomain( this->frame1 );
+      dom1 = t ? astStore( NULL, t, strlen(t) + 1 ) : NULL;
+      t = astGetDomain( this->frame2 );
+      dom2 = t ? astStore( NULL, t, strlen(t) + 1 ) : NULL;
+
+      if( dom2 ) {
+         if( strlen( dom1 ) > 0 || strlen( dom2 ) > 0 ) {
+            sprintf( (char *) getdomain_buff, "%s-%s", dom1, dom2 );
+            result = getdomain_buff;
+         } else {
+            result = "CMP";
+         }
+      }
+
+      dom1 = astFree( dom1 );
+      dom2 = astFree( dom2 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetMaxAxes( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetMaxAxes
+
+*  Purpose:
+*     Get a value for the MaxAxes attribute of a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int GetMaxAxes( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetMaxAxes method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a value for the MaxAxes attribute of a
+*     CmpFrame.  A large default value is supplied that is much larger
+*     than the maximum likely number of axes in a Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The MaxAxes attribute value.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If a value has been set explicitly for the CmpFrame, return it.
+   Otherwise returned a large default value. */
+   if( astTestMaxAxes( this ) ) {
+      result = (*parent_getmaxaxes)( this_frame, status );
+   } else {
+      result = 1000000;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetMinAxes( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetMinAxes
+
+*  Purpose:
+*     Get a value for the MinAxes attribute of a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int GetMinAxes( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetMinAxes method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a value for the MinAxes attribute of a
+*     CmpFrame.  A default value of zero is used.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The MinAxes attribute value.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If a value has been set explicitly for the CmpFrame, return it.
+   Otherwise returned a default value of zero. */
+   if( astTestMinAxes( this ) ) {
+      result = (*parent_getminaxes)( this_frame, status );
+   } else {
+      result = 0;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetDut1( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetDut1
+
+*  Purpose:
+*     Get a value for the Dut1 attribute of a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     double GetDut1( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetDut1 method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a value for the Dut1 attribute of a
+*     CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Dut1 attribute value.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   double result;                /* Result value to return */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If an Dut1 attribute value has been set, invoke the parent method
+   to obtain it. */
+   if ( astTestDut1( this ) ) {
+      result = (*parent_getdut1)( this_frame, status );
+
+/* Otherwise, if the Dut1 value is set in the first component Frame,
+   return it. */
+   } else if( astTestDut1( this->frame1 ) ){
+      result = astGetDut1( this->frame1 );
+
+/* Otherwise, if the Dut1 value is set in the second component Frame,
+   return it. */
+   } else if( astTestDut1( this->frame2 ) ){
+      result = astGetDut1( this->frame2 );
+
+/* Otherwise, return the default Dut1 value from the first component
+   Frame. */
+   } else {
+      result = astGetDut1( this->frame1 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetEpoch( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetEpoch
+
+*  Purpose:
+*     Get a value for the Epoch attribute of a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     double GetEpoch( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetEpoch method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a value for the Epoch attribute of a
+*     CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Epoch attribute value.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   double result;                /* Result value to return */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If an Epoch attribute value has been set, invoke the parent method
+   to obtain it. */
+   if ( astTestEpoch( this ) ) {
+      result = (*parent_getepoch)( this_frame, status );
+
+/* Otherwise, if the Epoch value is set in the first component Frame,
+   return it. */
+   } else if( astTestEpoch( this->frame1 ) ){
+      result = astGetEpoch( this->frame1 );
+
+/* Otherwise, if the Epoch value is set in the second component Frame,
+   return it. */
+   } else if( astTestEpoch( this->frame2 ) ){
+      result = astGetEpoch( this->frame2 );
+
+/* Otherwise, return the default Epoch value from the first component
+   Frame. */
+   } else {
+      result = astGetEpoch( this->frame1 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetObsAlt( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetObsAlt
+
+*  Purpose:
+*     Get a value for the ObsAlt attribute of a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     double GetObsAlt( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetObsAlt method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a value for the ObsAlt attribute of a
+*     CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The ObsAlt attribute value.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   double result;                /* Result value to return */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If an ObsAlt attribute value has been set, invoke the parent method
+   to obtain it. */
+   if ( astTestObsAlt( this ) ) {
+      result = (*parent_getobsalt)( this_frame, status );
+
+/* Otherwise, if the ObsAlt value is set in the first component Frame,
+   return it. */
+   } else if( astTestObsAlt( this->frame1 ) ){
+      result = astGetObsAlt( this->frame1 );
+
+/* Otherwise, if the ObsAlt value is set in the second component Frame,
+   return it. */
+   } else if( astTestObsAlt( this->frame2 ) ){
+      result = astGetObsAlt( this->frame2 );
+
+/* Otherwise, return the default ObsAlt value from the first component
+   Frame. */
+   } else {
+      result = astGetObsAlt( this->frame1 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetObsLat( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetObsLat
+
+*  Purpose:
+*     Get a value for the ObsLat attribute of a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     double GetObsLat( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetObsLat method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a value for the ObsLat attribute of a
+*     CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The ObsLat attribute value.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   double result;                /* Result value to return */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If an ObsLat attribute value has been set, invoke the parent method
+   to obtain it. */
+   if ( astTestObsLat( this ) ) {
+      result = (*parent_getobslat)( this_frame, status );
+
+/* Otherwise, if the ObsLat value is set in the first component Frame,
+   return it. */
+   } else if( astTestObsLat( this->frame1 ) ){
+      result = astGetObsLat( this->frame1 );
+
+/* Otherwise, if the ObsLat value is set in the second component Frame,
+   return it. */
+   } else if( astTestObsLat( this->frame2 ) ){
+      result = astGetObsLat( this->frame2 );
+
+/* Otherwise, return the default ObsLat value from the first component
+   Frame. */
+   } else {
+      result = astGetObsLat( this->frame1 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetObsLon( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetObsLon
+
+*  Purpose:
+*     Get a value for the ObsLon attribute of a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     double GetObsLon( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetObsLon method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a value for the ObsLon attribute of a
+*     CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The ObsLon attribute value.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   double result;                /* Result value to return */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If an ObsLon attribute value has been set, invoke the parent method
+   to obtain it. */
+   if ( astTestObsLon( this ) ) {
+      result = (*parent_getobslon)( this_frame, status );
+
+/* Otherwise, if the ObsLon value is set in the first component Frame,
+   return it. */
+   } else if( astTestObsLon( this->frame1 ) ){
+      result = astGetObsLon( this->frame1 );
+
+/* Otherwise, if the ObsLon value is set in the second component Frame,
+   return it. */
+   } else if( astTestObsLon( this->frame2 ) ){
+      result = astGetObsLon( this->frame2 );
+
+/* Otherwise, return the default ObsLon value from the first component
+   Frame. */
+   } else {
+      result = astGetObsLon( this->frame1 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetNaxes( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetNaxes
+
+*  Purpose:
+*     Determine how many axes a CmpFrame has.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int GetNaxes( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetNaxes method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns the number of axes for a CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of CmpFrame axes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   int naxes1;                   /* Number of axes for frame1 */
+   int naxes2;                   /* Number of axes for frame2 */
+   int result;                   /* Number of CmpFrame axes */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Obtain the number of axes for each component Frame. */
+   naxes1 = astGetNaxes( this->frame1 );
+   naxes2 = astGetNaxes( this->frame2 );
+
+/* If OK, calculate the total number of axes. */
+   if ( astOK ) result = naxes1 + naxes2;
+
+/* Return the result. */
+   return result;
+}
+
+static const int *GetPerm( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetPerm
+
+*  Purpose:
+*     Access the axis permutation array for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     const int *astGetPerm( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astGetPerm
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the axis permutation array
+*     for a CmpFrame. This array constitutes a lookup-table that
+*     converts between an axis number supplied externally and the
+*     corresponding index in the CmpFrame's internal data.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the CmpFrame's axis permutation array (a constant
+*     array of int).  Each element of this contains the (zero-based)
+*     internal axis index to be used in place of the external index
+*     which is used to address the permutation array. If the CmpFrame
+*     has zero axes, this pointer will be NULL.
+
+*  Notes:
+*     - This protected method is provided to assist class
+*     implementations which need to implement axis-dependent
+*     extensions to CmpFrame methods, and which therefore need to know
+*     how a CmpFrames's external axis index is converted for internal
+*     use.
+*     - The pointer returned by this function gives direct access to
+*     data internal to the CmpFrame object. It remains valid only so
+*     long as the CmpFrame exists. The permutation array contents may
+*     be modified by other functions which operate on the CmpFrame and
+*     this may render the returned pointer invalid.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+
+*  Implementation Notes:
+*     - This function performs essentially the same operation as the
+*     Frame member function which it over-rides. However, it returns a
+*     pointer to the "perm" array held in the CmpFrame structure
+*     (rather than the one in the parent Frame structure). This
+*     duplication of the array is necessary because the one in the
+*     Frame structure is of zero length, the number of axes in the
+*     Frame structure having been set to zero to prevent unnecessary
+*     allocation of Axis objects which are not needed by the CmpFrame.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Return a pointer to the axis permutation array. */
+   return this->perm;
+}
+
+static AstSystemType GetSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetSystem
+
+*  Purpose:
+*     Obtain the System attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstSystemType GetSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the System attribute for a CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System value.
+
+*  Notes:
+*     - AST__BADSYSTEM is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   AstSystemType result;         /* Value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If a System attribute has been set, invoke the parent method to obtain
+   it. */
+   if ( astTestSystem( this ) ) {
+      result = (*parent_getsystem)( this_frame, status );
+
+/* Otherwise, provide a suitable default. */
+   } else {
+      result = AST__COMP;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetTitle( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetTitle
+
+*  Purpose:
+*     Obtain a pointer to the Title attribute string for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     const char *GetTitle( AstFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetTitle protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function returns a pointer to the Title attribute string for
+*    a CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a constant null-terminated string containing the
+*     Title value.
+
+*  Notes:
+*     - The returned pointer or the string it refers to may become
+*     invalid following further invocation of this function or
+*     modification of the CmpFrame.
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_frame);
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* If a Title attribute string has been set, invoke the parent method
+   to obtain a pointer to it. */
+   if ( astTestTitle( this ) ) {
+      result = (*parent_gettitle)( this_frame, status );
+
+/* Otherwise, create a suitable default string and return a pointer to
+   this. */
+   } else {
+      (void) sprintf( gettitle_buff, "%d-d compound coordinate system",
+                      astGetNaxes( this ) );
+      if ( astOK ) result = gettitle_buff;
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static int GetUseDefs( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetUseDefs
+
+*  Purpose:
+*     Get a value for the UseDefs attribute of a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int GetUseDefs( AstCmpFrame *this, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetUseDefs method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a value for the UseDefs attribute of a
+*     CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The UseDefs attribute value.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_object;
+
+/* If an UseDefs attribute value has been set, invoke the parent method
+   to obtain it. */
+   if ( astTestUseDefs( this ) ) {
+      result = (*parent_getusedefs)( this_object, status );
+
+/* Otherwise, use the UseDefs value in the first component Frame as the
+   default. */
+   } else {
+      result = (*parent_getusedefs)( (AstObject *) this->frame1, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GoodPerm( int ncoord_in, const int inperm[],
+                     int ncoord_out, const int outperm[], int *status ) {
+/*
+*  Name:
+*     GoodPerm
+
+*  Purpose:
+*     Test if a PermMap will be non-null.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int GoodPerm( int ncoord_in, const int inperm[],
+*                   int ncoord_out, const int outperm[], int *status )
+
+*  Class Membership:
+*     CmpFrame member function.
+
+*  Description:
+*     This function tests if a pair of permutation arrays will, when
+*     used to create a PermMap, result in a PermMap which has a
+*     non-null effect (i.e. one which is not simply equivalent to a
+*     unit Mapping).
+
+*  Parameters:
+*     ncoord_in
+*        The number of input coordinates for the PermMap.
+*     inperm
+*        The input permutation array for the PermMap (with "ncoord_in"
+*        elements).
+*     ncoord_out
+*        The number of output coordinates for the PermMap.
+*     outperm
+*        The output permutation array for the PermMap (with
+*        "ncoord_out" elements).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the PermMap would be equivalent to a unit Mapping,
+*     otherwise one.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   int axis;                     /* Loop counter for axes */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* First test if the number of input and output coordinates are
+   different. */
+   result = ( ncoord_in != ncoord_out );
+
+/* If they are not, examine the contents of the "inperm" array. */
+   if ( !result ) {
+      for ( axis = 0; axis < ncoord_in; axis++ ) {
+
+/* We have a non-null Mapping if any element of this array selects an
+   output axis with a different index to the input axis (or selects an
+   invalid axis or a constant). */
+         if ( inperm[ axis ] != axis ) {
+            result = 1;
+            break;
+         }
+      }
+   }
+
+/* If the Mapping still appears to be null, also examine the "outperm"
+   array in the same way. */
+   if ( !result ) {
+      for ( axis = 0; axis < ncoord_out; axis++ ) {
+         if ( outperm[ axis ] != axis ) {
+            result = 1;
+            break;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astInitCmpFrameVtab_(  AstCmpFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitCmpFrameVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a CmpFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void astInitCmpFrameVtab( AstCmpFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     CmpFrame vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the CmpFrame class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstFrameVtab *frame;          /* Pointer to Frame component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitFrameVtab( (AstFrameVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsACmpFrame) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstFrameVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   frame = (AstFrameVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+   mapping = (AstMappingVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_getusedefs = object->GetUseDefs;
+   object->GetUseDefs = GetUseDefs;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   mapping->RemoveRegions = RemoveRegions;
+   mapping->Simplify = Simplify;
+   mapping->Transform = Transform;
+
+   parent_getdomain = frame->GetDomain;
+   frame->GetDomain = GetDomain;
+
+   parent_gettitle = frame->GetTitle;
+   frame->GetTitle = GetTitle;
+
+   parent_getepoch = frame->GetEpoch;
+   frame->GetEpoch = GetEpoch;
+
+   parent_setepoch = frame->SetEpoch;
+   frame->SetEpoch = SetEpoch;
+
+   parent_clearepoch = frame->ClearEpoch;
+   frame->ClearEpoch = ClearEpoch;
+
+   parent_getdut1 = frame->GetDut1;
+   frame->GetDut1 = GetDut1;
+
+   parent_setdut1 = frame->SetDut1;
+   frame->SetDut1 = SetDut1;
+
+   parent_cleardut1 = frame->ClearDut1;
+   frame->ClearDut1 = ClearDut1;
+
+   parent_getobslon = frame->GetObsLon;
+   frame->GetObsLon = GetObsLon;
+
+   parent_setobslon = frame->SetObsLon;
+   frame->SetObsLon = SetObsLon;
+
+   parent_clearobslon = frame->ClearObsLon;
+   frame->ClearObsLon = ClearObsLon;
+
+   parent_getobslat = frame->GetObsLat;
+   frame->GetObsLat = GetObsLat;
+
+   parent_setobslat = frame->SetObsLat;
+   frame->SetObsLat = SetObsLat;
+
+   parent_clearobslat = frame->ClearObsLat;
+   frame->ClearObsLat = ClearObsLat;
+
+   parent_getobsalt = frame->GetObsAlt;
+   frame->GetObsAlt = GetObsAlt;
+
+   parent_setobsalt = frame->SetObsAlt;
+   frame->SetObsAlt = SetObsAlt;
+
+   parent_clearobsalt = frame->ClearObsAlt;
+   frame->ClearObsAlt = ClearObsAlt;
+
+   parent_angle = frame->Angle;
+   frame->Angle = Angle;
+
+   parent_getsystem = frame->GetSystem;
+   frame->GetSystem = GetSystem;
+
+   parent_getalignsystem = frame->GetAlignSystem;
+   frame->GetAlignSystem = GetAlignSystem;
+
+   parent_clearalignsystem = frame->ClearAlignSystem;
+   frame->ClearAlignSystem = ClearAlignSystem;
+
+   parent_overlay = frame->Overlay;
+   frame->Overlay = Overlay;
+
+   parent_setactiveunit = frame->SetActiveUnit;
+   frame->SetActiveUnit = SetActiveUnit;
+
+   parent_getactiveunit = frame->GetActiveUnit;
+   frame->GetActiveUnit = GetActiveUnit;
+
+   parent_setframeflags = frame->SetFrameFlags;
+   frame->SetFrameFlags = SetFrameFlags;
+
+   parent_getmaxaxes = frame->GetMaxAxes;
+   frame->GetMaxAxes = GetMaxAxes;
+
+   parent_getminaxes = frame->GetMinAxes;
+   frame->GetMinAxes = GetMinAxes;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Cast = Cast;
+   mapping->Decompose = Decompose;
+   frame->Abbrev = Abbrev;
+   frame->ClearDirection = ClearDirection;
+   frame->ClearFormat = ClearFormat;
+   frame->ClearLabel = ClearLabel;
+   frame->ClearSymbol = ClearSymbol;
+   frame->ClearUnit = ClearUnit;
+   frame->Distance = Distance;
+   frame->Fields = Fields;
+   frame->Format = Format;
+   frame->FrameGrid = FrameGrid;
+   frame->Centre = Centre;
+   frame->Gap = Gap;
+   frame->GetAxis = GetAxis;
+   frame->GetDirection = GetDirection;
+   frame->GetFormat = GetFormat;
+   frame->GetLabel = GetLabel;
+   frame->GetNaxes = GetNaxes;
+   frame->GetPerm = GetPerm;
+   frame->GetSymbol = GetSymbol;
+   frame->GetUnit = GetUnit;
+   frame->IsUnitFrame = IsUnitFrame;
+   frame->Match = Match;
+   frame->Norm = Norm;
+   frame->NormBox = NormBox;
+   frame->Offset = Offset;
+   frame->PermAxes = PermAxes;
+   frame->PrimaryFrame = PrimaryFrame;
+   frame->Resolve = Resolve;
+   frame->ResolvePoints = ResolvePoints;
+   frame->SetAxis = SetAxis;
+   frame->SetDirection = SetDirection;
+   frame->SetFormat = SetFormat;
+   frame->SetLabel = SetLabel;
+   frame->SetSymbol = SetSymbol;
+   frame->SetUnit = SetUnit;
+   frame->SubFrame = SubFrame;
+   frame->TestDirection = TestDirection;
+   frame->TestFormat = TestFormat;
+   frame->TestLabel = TestLabel;
+   frame->TestSymbol = TestSymbol;
+   frame->TestUnit = TestUnit;
+   frame->Unformat = Unformat;
+   frame->ValidateSystem = ValidateSystem;
+   frame->SystemString = SystemString;
+   frame->SystemCode = SystemCode;
+   frame->MatchAxesX = MatchAxesX;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "CmpFrame",
+               "Compound coordinate system description" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int IsUnitFrame( AstFrame *this_frame, int *status ){
+/*
+*  Name:
+*     IsUnitFrame
+
+*  Purpose:
+*     Is this Frame equivalent to a UnitMap?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int IsUnitFrame( AstFrame *this, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astIsUnitFrame
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied Frame is
+*     equivalent to a UnitMap when treated as a Mapping (note, the Frame
+*     class inherits from Mapping and therefore every Frame is also a Mapping).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the supplied Frame is equivalent to
+*     a UnitMap when treated as a Mapping.
+
+*-
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Return the result. */
+   return astIsUnitFrame( this->frame1 ) && astIsUnitFrame( this->frame2 );
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;       /* Pointer to CmpFrame structure */
+   int result;              /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->frame1, mode, extra, fail );
+   if( !result ) result = astManageLock( this->frame2, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static int Match( AstFrame *template_frame, AstFrame *target, int matchsub,
+                  int **template_axes, int **target_axes,
+                  AstMapping **map, AstFrame **result, int *status ) {
+/*
+*  Name:
+*     Match
+
+*  Purpose:
+*     Determine if conversion is possible between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int Match( AstFrame *template, AstFrame *target, int matchsub,
+*                int **template_axes, int **target_axes,
+*                AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astMatch
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function matches a "template" CmpFrame to a "target" Frame
+*     and determines whether it is possible to convert coordinates
+*     between them.  If it is, a Mapping that performs the
+*     transformation is returned along with a new Frame that describes
+*     the coordinate system that results when this Mapping is applied
+*     to the "target" coordinate system. In addition, information is
+*     returned to allow the axes in this "result" Frame to be
+*     associated with the corresponding axes in the "target" Frame and
+*     "template" CmpFrame from which they are derived.
+
+*  Parameters:
+*     template
+*        Pointer to the template CmpFrame. This describes the
+*        coordinate system (or set of possible coordinate systems)
+*        into which we wish to convert our coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate
+*        system in which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case (i.e. if the
+*        target is of a more specialised class than the template). In
+*        this latter case, the target is cast down to the class of the
+*        template. NOTE, this argument is handled by the global method
+*        wrapper function "astMatch_", rather than by the class-specific
+*        implementations of this method.
+*     template_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the (zero-based) index of the
+*        template CmpFrame axis from which it is derived. If it is not
+*        derived from any template axis, a value of -1 will be
+*        returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the (zero-based) index of the
+*        target Frame axis from which it is derived. If it is not
+*        derived from any target axis, a value of -1 will be returned
+*        instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will
+*        be returned if the requested coordinate conversion is
+*        possible. If returned, the forward transformation of this
+*        Mapping may be used to convert coordinates between the
+*        "target" Frame and the "result" Frame (see below) and the
+*        inverse transformation will convert in the opposite
+*        direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be
+*        returned if the requested coordinate conversion is
+*        possible. If returned, this Frame describes the coordinate
+*        system that results from applying the returned Mapping
+*        (above) to the "target" coordinate system. In general, this
+*        Frame will combine attributes from (and will therefore be
+*        more specific than) both the target Frame and the template
+*        CmpFrame. In particular, when the template allows the
+*        possibility of transformaing to any one of a set of
+*        alternative coordinate systems, the "result" Frame will
+*        indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate
+*     conversion is possible. Otherwise zero is returned (this will
+*     not in itself result in an error condition).
+
+*  Notes:
+*     - By default, the "result" Frame will have its number of axes
+*     and axis order determined by the "template" CmpFrame. However,
+*     if the PreserveAxes attribute of the template CmpFrame is
+*     non-zero, then the axis count and axis order of the "target"
+*     Frame will be used instead.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *template;        /* Pointer to template CmpFrame structure */
+   char *template_domain;        /* Pointer to copy of template domain */
+   const char *ptr;              /* Pointer to domain string */
+   const char *target_domain;    /* Pointer to target domain string */
+   int *axes1;                   /* Pointer to axis selection 1 */
+   int *axes2;                   /* Pointer to axis selection 2 */
+   int *used;                    /* Pointer to flags array */
+   int axis2;                    /* Index for axis selection 2 */
+   int axis;                     /* Index for axis arrays */
+   int last_target;              /* Last target axis association */
+   int last_template;            /* Last template axis associateion */
+   int match;                    /* Match obtained (returned result)? */
+   int maxax1;                   /* MaxAxes attribute for component 1 */
+   int maxax2;                   /* MaxAxes attribute for component 2 */
+   int maxax;                    /* Max axes that can be matched by template */
+   int minax1;                   /* MinAxes attribute for component 1 */
+   int minax2;                   /* MinAxes attribute for component 2 */
+   int minax;                    /* Min axes that can be matched by template */
+   int naxes1;                   /* Number of axes assigned to component 1 */
+   int naxes2;                   /* Number of axes assigned to component 2 */
+   int naxes;                    /* Total number of target axes */
+   int naxes_max1;               /* First estimate of naxes_max */
+   int naxes_max2;               /* Second estimate of naxes_max */
+   int naxes_max;                /* Max number of axes to match component 1 */
+   int naxes_min1;               /* First estimate of naxes_min */
+   int naxes_min2;               /* Second estimate of naxes_min */
+   int naxes_min;                /* Min number of axes to match component 1 */
+   int permute;                  /* Permute attribute for template */
+   int result_naxes;             /* Number of result Frame axes */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the template CmpFrame structure. */
+   template = (AstCmpFrame *) template_frame;
+
+/* Further initialisation to avoid compiler warnings. */
+   naxes_min = 0;
+   naxes_max = 0;
+
+/* Obtain the maximum number of axes that the template CmpFrame, and each
+   component Frame of the template CmpFrame, can match. If the MaxAxes
+   attribute is set for the template, use it and assume that each
+   component Frame can match any number of axes. */
+   if( astTestMaxAxes( template ) ) {
+      maxax = astGetMaxAxes( template );
+      maxax1 = 100000;
+      maxax2 = 100000;
+   } else {
+      maxax1 = astGetMaxAxes( template->frame1 );
+      maxax2 = astGetMaxAxes( template->frame2 );
+      maxax = maxax1 + maxax2;
+   }
+
+/* Do the same for the minimum number of axes that can be matched by the
+   template CmpFrame. */
+   if( astTestMinAxes( template ) ) {
+      minax = astGetMinAxes( template );
+      minax1 = 1;
+      minax2 = 1;
+   } else {
+      minax1 = astGetMinAxes( template->frame1 );
+      minax2 = astGetMinAxes( template->frame2 );
+      minax = minax1 + minax2;
+   }
+
+/* Obtain the number of axes in the target Frame and test to see if it
+   is possible for the template to match it on the basis of axis
+   counts. */
+   naxes = astGetNaxes( target );
+   match = ( naxes >= minax && naxes <= maxax );
+
+/* The next requirement is that all the frames have some axes. */
+   if( naxes == 0 || maxax1 == 0 || maxax2 == 0 ) match = 0;
+
+/* The next requirement is that if the template CmpFrame has its
+   Domain attribute defined, then the target Frame must also have the
+   same Domain (although it need not be set - the default will
+   do). First check if the template has a domain. */
+   if ( astOK && match ) {
+      if ( astTestDomain( template ) ) {
+
+/* Obtain a pointer to the template domain. Then allocate memory and
+   make a copy of it (this is necessary as we will next inquire the
+   domain of the target and may over-write the buffer holding the
+   template's domain). */
+         ptr = astGetDomain( template );
+         if ( astOK ) {
+            template_domain = astStore( NULL, ptr,
+                                        strlen( ptr ) + (size_t) 1 );
+
+/* Obtain a pointer to the target domain. */
+            target_domain = astGetDomain( target );
+
+/* Compare the domain strings for equality. Then free the memory
+   allocated above. */
+            match = astOK && !strcmp( template_domain, target_domain );
+            template_domain = astFree( template_domain );
+         }
+      }
+   }
+
+/* If a match still appears possible, determine the minimum number of
+   target axes that will have to match the first component Frame of
+   the template CmpFrame. */
+   if ( astOK && match ) {
+      naxes_min1 = minax1;
+      naxes_min2 = naxes - maxax2;
+      naxes_min = ( naxes_min1 > naxes_min2 ) ? naxes_min1 : naxes_min2;
+
+/* Also determine the maximum number of target axes that may match
+   this component of the template. */
+      naxes_max1 = maxax1;
+      naxes_max2 = naxes - minax2;
+      naxes_max = ( naxes_max1 < naxes_max2 ) ? naxes_max1 : naxes_max2;
+
+/* No match possible if the number of axes are inconsistent. */
+      if( naxes_min > naxes_max ) match = 0;
+   }
+
+/* If a match is still possible, allocate workspace. */
+   if( match ) {
+      axes1 = astMalloc( sizeof( int ) * (size_t) naxes );
+      axes2 = astMalloc( sizeof( int ) * (size_t) naxes );
+      used = astMalloc( sizeof( int ) * (size_t) naxes );
+
+/* Obtain the value of the template's Permute attribute. */
+      permute = astGetPermute( template );
+      if ( astOK ) {
+
+/* Loop to consider all possible choices of the number of template
+   axes that might match the first component Frame of the template,
+   and derive the corresponding number of axes that must match the
+   second component at the same time. */
+         for ( naxes1 = naxes_max; naxes1 >= naxes_min; naxes1-- ) {
+            naxes2 = naxes - naxes1;
+
+/* Initialise the selection of target axes that we will attempt to
+   match against the first template component (to [0,1,2,...]). */
+            for ( axis = 0; axis < naxes1; axis++ ) axes1[ axis ] = axis;
+
+/* Loop to consider all possible selections with this number of axes,
+   until a match is found. */
+            while ( 1 ) {
+
+/* Initialise an array of flags to zero for each target axis. Then set
+   the flag to 1 for each axis which is in the first selection.*/
+               for ( axis = 0; axis < naxes; axis++ ) used[ axis ] = 0;
+               for( axis = 0; axis < naxes1; axis++ ) {
+                  used[ axes1[ axis ] ] = 1;
+               }
+
+/* Generate the second selection by including all target axes that are
+   not in the first selection. */
+               axis2 = 0;
+               for ( axis = 0; axis < naxes; axis++ ) {
+                  if ( !used[ axis ] ) axes2[ axis2++ ] = axis;
+               }
+
+/* Attempt to match the target axes partitioned in this way to the two
+   template components. */
+               match = PartMatch( template, target, matchsub,
+                                  naxes1, axes1, naxes2, axes2,
+                                  template_axes, target_axes, map, result, status );
+
+/* If a match was obtained but the template's Permute attribute is zero,
+   then we must check to see if the match involves permuting the target
+   axes. */
+               if ( astOK && match && !permute ) {
+
+/* Obtain the number of result Frame axes. */
+                  result_naxes = astGetNaxes( *result );
+
+/* Loop to check the target and template axis associations for all the
+   result Frame axes. The match will only be accepted if both of these
+   are monotonically increasing (indicating no axis permutation) after
+   allowing for any absent associations . */
+                  last_template = -1;
+                  last_target = -1;
+                  for ( axis = 0; axis < result_naxes; axis++ ) {
+
+/* Check the template axis association against the previous value,
+   omitting any axes witout valid associations. */
+                     if ( ( *template_axes )[ axis ] != -1 ) {
+                        if ( ( *template_axes )[ axis ] <= last_template ) {
+                           match = 0;
+                           break;
+
+/* Update the previous association value. */
+                        } else {
+                           last_template = ( *template_axes )[ axis ];
+                        }
+                     }
+
+/* Repeat this process for the target axis associations. */
+                     if ( ( *target_axes )[ axis ] != -1 ) {
+                        if ( ( *target_axes )[ axis ] <= last_target ) {
+                           match = 0;
+                           break;
+                        } else {
+                           last_target = ( *target_axes )[ axis ];
+                        }
+                     }
+                  }
+
+/* If the match was rejected because it involves an axis permutation,
+   then free the allocated memory and annul the Object pointers
+   associated with the match. */
+                  if ( !match ) {
+                     *template_axes = astFree( *template_axes );
+                     *target_axes = astFree( *target_axes );
+                     *map = astAnnul( *map );
+                     *result = astAnnul( *result );
+                  }
+               }
+
+/* If an error occurred or a match was found, quit searching,
+   otherwise generate the next axis selection and try that
+   instead. Quit if there are no more selections to try. */
+               if ( !astOK || match ||
+                    !GenAxisSelection( naxes, naxes1, axes1, status ) ) break;
+            }
+
+/* Quit the outer loop if an error occurs or a match is found. */
+            if ( !astOK || match ) break;
+         }
+      }
+
+/* Free the workspace arrays. */
+      axes1 = astFree( axes1 );
+      axes2 = astFree( axes2 );
+      used = astFree( used );
+   }
+
+/* If the target did not match the supplied template CmpFrame, see if it
+   will match either of the component Frames. First try matching it against
+   the first component Frame. */
+   if( !match ) match = ComponentMatch( template, target, matchsub, 0,
+                                        template_axes, target_axes, map, result,
+                                        status );
+
+/* If we still dont have a mcth, try matching it against the second
+   component Frame. */
+   if( !match ) match = ComponentMatch( template, target, matchsub, 1,
+                                        template_axes, target_axes, map,
+                                        result, status );
+
+/* If an error occurred, free all allocated memory, annul the result
+   Object pointers and clear all returned values. */
+   if ( !astOK ) {
+      *template_axes = astFree( *template_axes );
+      *target_axes = astFree( *target_axes );
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static void MatchAxesX( AstFrame *frm2_frame, AstFrame *frm1, int *axes,
+                        int *status ) {
+/*
+*  Name:
+*     MatchAxesX
+
+*  Purpose:
+*     Find any corresponding axes in two Frames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void MatchAxesX( AstFrame *frm2, AstFrame *frm1, int *axes )
+*                      int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astMatchAxesX
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function looks for corresponding axes within two supplied
+*     Frames. An array of integers is returned that contains an element
+*     for each axis in the second supplied Frame. An element in this array
+*     will be set to zero if the associated axis within the second Frame
+*     has no corresponding axis within the first Frame. Otherwise, it
+*     will be set to the index (a non-zero positive integer) of the
+*     corresponding axis within the first supplied Frame.
+
+*  Parameters:
+*     frm2
+*        Pointer to the second Frame.
+*     frm1
+*        Pointer to the first Frame.
+*     axes
+*        Pointer to an integer array in which to return the indices of
+*        the axes (within the first Frame) that correspond to each axis
+*        within the second Frame. Axis indices start at 1. A value of zero
+*        will be stored in the returned array for each axis in the second
+*        Frame that has no corresponding axis in the first Frame.
+*
+*        The number of elements in this array must be greater than or
+*        equal to the number of axes in the second Frame.
+*     status
+*        Pointer to inherited status value.
+
+*  Notes:
+*     -  Corresponding axes are identified by the fact that a Mapping
+*     can be found between them using astFindFrame or astConvert. Thus,
+*     "corresponding axes" are not necessarily identical. For instance,
+*     SkyFrame axes in two Frames will match even if they describe
+*     different celestial coordinate systems
+*/
+
+/* Local Variables: */
+   AstCmpFrame *frm2;
+   const int *perm;
+   int *work;
+   int i;
+   int nax2;
+   int nax1;
+   int nax;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the CmpFrame. */
+   frm2 = (AstCmpFrame *) frm2_frame;
+
+/* Get the number of axes in the two component Frames, and the total
+   number of axes in the CmpFrame. */
+   nax2 = astGetNaxes( frm2->frame1 );
+   nax1 = astGetNaxes( frm2->frame2 );
+   nax = nax2 + nax1;
+
+/* Allocate a work array to hold the unpermuted axis indices */
+   work = astMalloc( sizeof( int )*nax );
+   if( astOK ) {
+
+/* Use the astMatchAxes method to match axes in the first component Frame
+   within CmpFrame "frm2". Write the associated axis indices into the first
+   part of the work array. */
+      astMatchAxes( frm1, frm2->frame1, work );
+
+/* Use the MatchAxes method to match axes in the second component
+   Frame. Write the associated axis indices into the work array
+   following the end of the values already in there. */
+      astMatchAxes( frm1, frm2->frame2, work + nax2 );
+
+/* Obtain a pointer to the CmpFrame's axis permutation array. The index
+   into "perm" represents the external axis index, and the value held in
+   each element of "perm" represents the corresponding internal axis index. */
+      perm = astGetPerm( frm2 );
+      if( astOK ) {
+
+/* Copy the frm2 axis indices from the work array into the returned "axes"
+   array, permuting their order into the external axis order of the
+   CmpFrame. */
+         for( i = 0; i < nax; i++ ) axes[ i ] = work[ perm[ i ] ];
+      }
+
+/* Free resources */
+      work = astFree( work );
+   }
+}
+
+static void Norm( AstFrame *this_frame, double value[], int *status ) {
+/*
+*  Name:
+*     Norm
+
+*  Purpose:
+*     Normalise a set of CmpFrame coordinates.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void Norm( AstAxis *this, double value[], int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astNorm method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a set of CmpFrame coordinate values,
+*     which might potentially be unsuitable for display to a user (for
+*     instance, may lie outside the expected range of values) into a
+*     set of acceptable alternative values suitable for display.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     value
+*        An array of double, with one element for each CmpFrame axis.
+*        This should contain the initial set of coordinate values,
+*        which will be modified in place.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   const int *perm;              /* Axis permutation array */
+   double *v;                    /* Pointer to permuted coordinates */
+   int axis;                     /* Loop counter for axes */
+   int naxes1;                   /* Number of axes in frame1 */
+   int naxes;                    /* Number of axes in CmpFrame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Obtain a pointer to the CmpFrame's axis permutation array. */
+   perm = astGetPerm( this );
+
+/* Obtain the number of axes in the CmpFrame and in the first
+   component Frame. */
+   naxes = astGetNaxes( this );
+   naxes1 = astGetNaxes( this->frame1 );
+
+/* Allocate memory to hold the permuted coordinates. */
+   v = astMalloc( sizeof( double ) * (size_t) naxes );
+   if ( astOK ) {
+
+/* Permute the coordinates using the CmpFrame's axis permutation array
+   to put them into the order required internally (i.e. by the two
+   component Frames). */
+      for ( axis = 0; axis < naxes; axis++ ) v[ perm[ axis ] ] = value[ axis ];
+
+/* Invoke the astNorm method of both component Frames, passing the
+   relevant (permuted) coordinate values for normalisation. */
+      astNorm( this->frame1, v );
+      astNorm( this->frame2, v + naxes1 );
+
+/* Copy the normalised values back into the original coordinate array,
+   un-permuting them in the process. */
+      for ( axis = 0; axis < naxes; axis++ ) value[ axis ] = v[ perm[ axis ] ];
+   }
+
+/* Free the memory used for the permuted coordinates. */
+   v = astFree( v );
+}
+
+static void NormBox( AstFrame *this_frame, double lbnd[], double ubnd[],
+                     AstMapping *reg, int *status ) {
+/*
+*  Name:
+*     NormBox
+
+*  Purpose:
+*     Extend a box to include effect of any singularities in the Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void astNormBox( AstFrame *this, double lbnd[], double ubnd[],
+*                      AstMapping *reg, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astNormBox method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function modifies a supplied box to include the effect of any
+*     singularities in the co-ordinate system represented by the Frame.
+*     For a normal Cartesian coordinate system, the box will be returned
+*     unchanged. Other classes of Frame may do other things. For instance,
+*     a SkyFrame will check to see if the box contains either the north
+*     or south pole and extend the box appropriately.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     lbnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        lower axis bounds for the box. They will be modified on exit
+*        to include the effect of any singularities within the box.
+*     ubnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        upper axis bounds for the box. They will be modified on exit
+*        to include the effect of any singularities within the box.
+*     reg
+*        A Mapping which should be used to test if any singular points are
+*        inside or outside the box. The Mapping should leave an input
+*        position unchanged if the point is inside the box, and should
+*        set all bad if the point is outside the box.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;
+   AstCmpMap *m1;
+   AstCmpMap *m2;
+   AstCmpMap *m3;
+   AstCmpMap *m4;
+   AstCmpMap *m5;
+   AstCmpMap *m6;
+   AstPermMap *pm1;
+   AstPermMap *pm2;
+   AstPermMap *pm3;
+   const int *perm;
+   double *vl;
+   double *vu;
+   int *inperm;
+   int axis;
+   int naxes1;
+   int naxes;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Obtain a pointer to the CmpFrame's axis permutation array. */
+   perm = astGetPerm( this );
+
+/* Obtain the number of axes in the CmpFrame and in the first
+   component Frame. */
+   naxes = astGetNaxes( this );
+   naxes1 = astGetNaxes( this->frame1 );
+
+/* Allocate memory to hold the permuted coordinates. */
+   vl = astMalloc( sizeof( double ) * (size_t) naxes );
+   vu = astMalloc( sizeof( double ) * (size_t) naxes );
+   inperm = astMalloc( sizeof( int ) * (size_t) naxes );
+   if( inperm ) {
+
+/* Permute the coordinates using the CmpFrame's axis permutation array
+   to put them into the order required internally (i.e. by the two
+   component Frames). */
+      for ( axis = 0; axis < naxes; axis++ ) {
+         vl[ perm[ axis ] ] = lbnd[ axis ];
+         vu[ perm[ axis ] ] = ubnd[ axis ];
+      }
+
+/* Create a PermMap with a forward transformation which reorders a position
+   which uses internal axis ordering into a position which uses external axis
+   ordering. */
+      pm1 = astPermMap( naxes, NULL, naxes, perm, NULL, "", status );
+
+/* Put it in front of the supplied Mapping. The combination transforms an
+   input internal position into an output external position.  */
+      m1 = astCmpMap( pm1, reg, 1, "", status );
+
+/* Invert it and add it to the end. This combination now transforms an
+   input internal position into an output internal position.  */
+      astInvert( pm1 );
+      m2 = astCmpMap( m1, pm1, 1, "", status );
+
+/* Create a PermMap with a forward transformation which copies the lower
+   naxes1 inputs to the same outputs, and supplies AST__BAD for the other
+   outputs. */
+      for( axis = 0; axis < naxes1; axis++ ) inperm[ axis ] = axis;
+      pm2 = astPermMap( naxes1, inperm, naxes, NULL, NULL, "", status );
+
+/* Put it in front of the Mapping created above, then invert it and add
+   it at the end. */
+      m3 = astCmpMap( pm2, m2, 1, "", status );
+      astInvert( pm2 );
+      m4 = astCmpMap( m3, pm2, 1, "", status );
+
+/* Invoke the astNormBox method of the first component Frame, passing the
+   relevant (permuted) coordinate values for normalisation. */
+      astNormBox( this->frame1, vl, vu, m4 );
+
+/* Create a PermMap with a forward transformation which copies the upper
+   inputs to the same outputs, and supplied AST__BAD for the other
+   outputs. */
+      for( axis = 0; axis < naxes - naxes1; axis++ ) inperm[ axis ] = naxes1 + axis;
+      pm3 = astPermMap( naxes1, inperm, naxes, NULL, NULL, "", status );
+
+/* Put it in front of the Mapping created above, then invert it and add
+   it at the end. */
+      m5 = astCmpMap( pm3, m2, 1, "", status );
+      astInvert( pm3 );
+      m6 = astCmpMap( m5, pm3, 1, "", status );
+
+/* Invoke the astNormBox method of the seond component Frame, passing the
+   relevant (permuted) coordinate values for normalisation. */
+      astNormBox( this->frame2, vl + naxes1, vu + naxes1, m6 );
+
+/* Copy the normalised values back into the original coordinate array,
+   un-permuting them in the process. */
+      for ( axis = 0; axis < naxes; axis++ ) {
+         lbnd[ axis ] = vl[ perm[ axis ] ];
+         ubnd[ axis ] = vu[ perm[ axis ] ];
+      }
+
+/* Free resources. */
+      pm1 = astAnnul( pm1 );
+      pm2 = astAnnul( pm2 );
+      pm3 = astAnnul( pm3 );
+      m1 = astAnnul( m1 );
+      m2 = astAnnul( m2 );
+      m3 = astAnnul( m3 );
+      m4 = astAnnul( m4 );
+      m5 = astAnnul( m5 );
+      m6 = astAnnul( m6 );
+   }
+   inperm = astFree( inperm );
+   vl = astFree( vl );
+   vu = astFree( vu );
+}
+
+static void Offset( AstFrame *this_frame, const double point1[],
+                    const double point2[], double offset, double point3[], int *status ) {
+/*
+*  Name:
+*     Offset
+
+*  Purpose:
+*     Calculate an offset along a geodesic curve.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void Offset( AstFrame *this,
+*                  const double point1[], const double point2[],
+*                  double offset, double point3[], int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astOffset method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function finds the CmpFrame coordinate values of a point
+*     which is offset a specified distance along the geodesic curve
+*     between two other points.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     point1
+*        An array of double, with one element for each CmpFrame axis.
+*        This should contain the coordinates of the point marking the
+*        start of the geodesic curve.
+*     point2
+*        An array of double, with one element for each CmpFrame axis.
+*        This should contain the coordinates of the point marking the
+*        end of the geodesic curve.
+*     offset
+*        The required offset from the first point along the geodesic
+*        curve. If this is positive, it will be towards the second
+*        point. If it is negative, it will be in the opposite
+*        direction. This offset need not imply a position lying
+*        between the two points given, as the curve will be
+*        extrapolated if necessary.
+*     point3
+*        An array of double, with one element for each CmpFrame axis
+*        in which the coordinates of the required point will be
+*        returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*     - "Bad" coordinate values will also be returned if the two
+*     points supplied are coincident (or otherwise fail to uniquely
+*     specify a geodesic curve) but the requested offset is non-zero.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   const int *perm;              /* Pointer to axis permutation array */
+   double *p1;                   /* Permuted coordinates for point1 */
+   double *p2;                   /* Permuted coordinates for point2 */
+   double *p3;                   /* Permuted coordinates for point3 */
+   double dist1;                 /* Distance between input points in frame1 */
+   double dist2;                 /* Distance between input points in frame2 */
+   double dist;                  /* Total distance between input points */
+   double offset1;               /* Offset distance required in frame1 */
+   double offset2;               /* Offset distance required in frame2 */
+   int axis;                     /* Loop counter for axes */
+   int bad;                      /* Set bad output coordinates? */
+   int naxes1;                   /* Number of axes in frame1 */
+   int naxes;                    /* Total number of axes in CmpFrame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Obtain the number of axes in the CmpFrame. */
+   naxes = astGetNaxes( this );
+
+/* Obtain a pointer to the CmpFrame's axis permutation array. */
+   perm = astGetPerm( this );
+
+/* Allocate workspace. */
+   p1 = astMalloc( sizeof( double ) * (size_t) naxes );
+   p2 = astMalloc( sizeof( double ) * (size_t) naxes );
+   p3 = astMalloc( sizeof( double ) * (size_t) naxes );
+
+/* Initialise variables to avoid compiler warnings. */
+   dist1 = 0.0;
+   dist2 = 0.0;
+   offset1 = 0.0;
+   offset2 = 0.0;
+   naxes1 = 0;
+
+/* Initialise a flag to indicate whether "bad" coordinates should be
+   returned. */
+   bad = 0;
+
+/* Check that all the coordinates of both input points are OK. If not,
+   set the "bad" flag and quit checking. */
+   if ( astOK ) {
+      for ( axis = 0; axis < naxes; axis++ ) {
+         if ( ( point1[ axis ] == AST__BAD ) ||
+              ( point2[ axis ] == AST__BAD ) ) {
+            bad = 1;
+            break;
+
+/* If the coordinates are OK, apply the axis permutation array to
+   obtain them in the required order. */
+         } else {
+            p1[ perm[ axis ] ] = point1[ axis ];
+            p2[ perm[ axis ] ] = point2[ axis ];
+         }
+      }
+   }
+
+/* If OK, obtain the number of axes in the first component Frame. */
+   if ( astOK && !bad ) {
+      naxes1 = astGetNaxes( this->frame1 );
+
+/* Project the two input points into the two component Frames and
+   determine the distance between the points in each Frame. */
+      dist1 = astDistance( this->frame1, p1, p2 );
+      dist2 = astDistance( this->frame2, p1 + naxes1, p2 + naxes1 );
+
+/* Check that the returned distances are not bad. */
+      if ( astOK ) bad = ( ( dist1 == AST__BAD ) || ( dist2 == AST__BAD ) );
+   }
+
+/* If OK, calculate the total distance between the two points. */
+   if ( astOK && !bad ) {
+      dist = sqrt( dist1 * dist1 + dist2 * dist2 );
+
+/* If the points are co-incident, but "offset" is non-zero, then set
+   the "bad" flag. */
+      if ( dist == 0.0 ) {
+         if ( offset != 0.0 ) {
+            bad = 1;
+
+/* Otherwise, set the offset distance required in each Frame to
+   zero. */
+         } else {
+            offset1 = 0.0;
+            offset2 = 0.0;
+         }
+
+/* If the points are not co-incident, divide the total offset required
+   between each component Frame in such a way that the path being
+   followed will pass through the second point. */
+      } else {
+         offset1 = offset * dist1 / dist;
+         offset2 = offset * dist2 / dist;
+      }
+   }
+
+/* If OK, apply the separate offsets to each component Frame. */
+   if ( astOK && !bad ) {
+      astOffset( this->frame1, p1, p2, offset1, p3 );
+      astOffset( this->frame2, p1 + naxes1, p2 + naxes1, offset2,
+                 p3 + naxes1 );
+
+/* Copy the resulting coordinates into the output array "point3",
+   permuting them back into the required order. */
+      if ( astOK ) {
+         for ( axis = 0; axis < naxes; axis++ ) {
+            point3[ axis ] = p3[ perm[ axis ] ];
+
+/* If any of the result coordinates is bad, set the "bad" flag and
+   quit copying. */
+            if ( point3[ axis ] == AST__BAD ) {
+               bad = 1;
+               break;
+            }
+         }
+      }
+   }
+
+/* Free the workspace arrays. */
+   p1 = astFree( p1 );
+   p2 = astFree( p2 );
+   p3 = astFree( p3 );
+
+/* If no error has occurred, but bad coordinates must be returned,
+   then set these in the output array. */
+   if ( astOK && bad ) {
+      for ( axis = 0; axis < naxes; axis++ ) point3[ axis ] = AST__BAD;
+   }
+}
+
+static void Overlay( AstFrame *template_frame, const int *template_axes,
+                     AstFrame *result, int *status ) {
+/*
+*  Name:
+*     Overlay
+
+*  Purpose:
+*     Overlay the attributes of a template CmpFrame on to another Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void Overlay( AstFrame *template, const int *template_axes,
+*                   AstFrame *result, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astOverlay
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function overlays attributes from a CmpFrame on to another Frame,
+*     so as to over-ride selected attributes of that second Frame. Normally
+*     only those attributes which have been specifically set in the template
+*     will be transferred. This implements a form of defaulting, in which
+*     a Frame acquires attributes from the template, but retains its
+*     original attributes (as the default) if new values have not previously
+*     been explicitly set in the template.
+
+*  Parameters:
+*     template
+*        Pointer to the template CmpFrame, for whose current Frame
+*        values should have been explicitly set for any attribute
+*        which is to be transferred.
+*     template_axes
+*        Pointer to an array of int, with one element for each axis of
+*        the "result" Frame (see below). For each axis in the result
+*        frame, the corresponding element of this array should contain
+*        the (zero-based) index of the axis in the current Frame of
+*        the template CmpFrame to which it corresponds. This array is
+*        used to establish from which template Frame axis any
+*        axis-dependent attributes should be obtained.
+*
+*        If any axis in the result Frame is not associated with a
+*        template Frame axis, the corresponding element of this array
+*        should be set to -1.
+*
+*        If a NULL pointer is supplied, the template and result axis
+*        indices are assumed to be identical.
+*     result
+*        Pointer to the Frame which is to receive the new attribute values.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *res;             /* Pointer to the result CmpFrame structure */
+   AstCmpFrame *template;        /* Pointer to the template CmpFrame structure */
+   AstFrame *sub1;               /* Template subframe for 1st result subframe */
+   AstFrame *sub2;               /* Template subframe for 2nd result subframe */
+   const int *perm;              /* Template axis permutation array */
+   const int *rperm;             /* Result axis permutation array */
+   int *axes1;                   /* Axis associations with template frame1 */
+   int *axes2;                   /* Axis associations with template frame2 */
+   int done;                     /* Have attributes been overlayed yet? */
+   int i;                        /* Index of result axis */
+   int icmp;                     /* Internal template axis number */
+   int isfirst;                  /* Res. subframe -> 1st template subframe? */
+   int issecond;                 /* Res. subframe -> 2nd template subframe? */
+   int j;                        /* Index of template axis */
+   int nc1;                      /* Number of axes in template frame1 */
+   int nres1;                    /* Number of axes in first result subframe */
+   int nres2;                    /* Number of axes in second result subframe */
+   int nres;                     /* Number of axes in result Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   template = (AstCmpFrame *) template_frame;
+
+/* Get the axis permutation array for the template CmpFrame. */
+   perm = astGetPerm( template );
+
+/* Get the number of axes in the first component Frame in the template
+   CmpFrame. */
+   nc1 = astGetNaxes( template->frame1 );
+
+/* Indicate we have not yet overlayed any attributes. */
+   done = 0;
+
+/* If the result Frame is a CmpFrame... */
+   if( astIsACmpFrame( result ) ) {
+
+/* Get the number of axes in the two component Frames of the result CmpFrame. */
+      res = (AstCmpFrame *) result;
+      nres1 = astGetNaxes( res->frame1 );
+      nres2 = astGetNaxes( res->frame2 );
+
+/* Get the total number of axes in the result CmpFrame. */
+      nres = nres1 + nres2;
+
+/* Get the axis permutation array for the result CmpFrame. */
+      rperm = astGetPerm( result );
+
+/* Allocate memory for two new axes arrays, one for each result sub-frame. */
+      axes1 = astMalloc( sizeof(int)*(size_t)nres1 );
+      axes2 = astMalloc( sizeof(int)*(size_t)nres2 );
+      if( astOK ) {
+
+/* Assume that there is a 1-to-1 correspondence between axes in the
+   subframes of the result and template CmpFrame. That is, all the axes
+   in each result sub-frame are overlayed from the same template sub-frame. */
+         done = 1;
+
+/* Loop round each axis in the first result sub-frame. */
+         isfirst = 0;
+         issecond = 0;
+         for( i = 0; i < nres1; i++ ) {
+
+/* Find the external result CmpFrame axis index (j) for internal axis i. */
+            for( j = 0; j < nres; j++ ) {
+               if( rperm[ j ] == i ) break;
+            }
+
+/* Get the internal axis number within the template CmpFrame which
+   provides attribute values for the current result axis. */
+            icmp = perm[ template_axes ? template_axes[ j ] : j ];
+
+/* If this template axis is in the first template subframe, store the
+   corresponding internal frame axis index in "axes1" and set a flag
+   indicating that the first result subframe corresponds to the first
+   template subframe. If the correspondance has already been established,
+   but is broken by this axis, then set "done" false and exit the axis
+   loop. */
+            if( icmp < nc1 ) {
+               if( issecond ) {
+                  done = 0;
+                  break;
+               } else {
+                  isfirst = 1;
+                  axes1[ i ] = icmp;
+               }
+
+            } else {
+               if( isfirst ) {
+                  done = 0;
+                  break;
+               } else {
+                  issecond = 1;
+                  axes1[ i ] = icmp - nc1;
+               }
+            }
+         }
+
+/* Save a pointer to the template subframe which is associated with the first
+   result subframe.*/
+         sub1 = isfirst ?  template->frame1 :template->frame2;
+
+/* Now do the same for the axes in the second result sub-frame. */
+         isfirst = 0;
+         issecond = 0;
+         for( i = 0; i < nres2; i++ ) {
+            for( j = 0; j < nres; j++ ) {
+               if( rperm[ j ] == i + nres1 ) break;
+            }
+
+            icmp = perm[ template_axes ? template_axes[ j ] : j ];
+
+            if( icmp < nc1 ) {
+               if( issecond ) {
+                  done = 0;
+                  break;
+               } else {
+                  isfirst = 1;
+                  axes2[ i ] = icmp;
+               }
+
+            } else {
+               if( isfirst ) {
+                  done = 0;
+                  break;
+               } else {
+                  issecond = 1;
+                  axes2[ i ] = icmp - nc1;
+               }
+            }
+         }
+
+/* Save a pointer to the template subframe which is associated with the
+   second result subframe.*/
+         sub2 = isfirst ?  template->frame1 :template->frame2;
+
+/* If the two used template subframes are the same, something has gone
+   wrong. */
+         if( sub1 == sub2 ) done = 0;
+
+/* If all axes within each result subframe are associated with the same
+   template subframe we continue to use the subframe astOverlay methods. */
+         if( done ) {
+
+/* Overlay the first result subframe. */
+            astOverlay( sub1, axes1, res->frame1 );
+            astOverlay( sub2, axes2, res->frame2 );
+         }
+      }
+
+/* Free the axes arrays. */
+      axes1 = astFree( axes1 );
+      axes2 = astFree( axes2 );
+   }
+
+/* If we have not yet overlayed any attributes... */
+   if( !done ) {
+
+/* Get the number of axes in the result Frame. */
+      nres = astGetNaxes( result );
+
+/* Allocate memory for two new template_axes arrays. */
+      axes1 = astMalloc( sizeof(int)*(size_t)nres );
+      axes2 = astMalloc( sizeof(int)*(size_t)nres );
+      if( astOK ) {
+
+/* Set elements to -1 in "axes1" if they do not refer to the first component
+   Frame in the template CmpFrame. Likewise, set elements to -1 in "axes2" if
+   they do not refer to the second component Frame in the template CmpFrame. */
+         for( i = 0; i < nres; i++ ) {
+
+/* Get the internal axis number within the template CmpFrame which
+   provides attribute values for the current results axis. */
+            icmp = perm[ template_axes ? template_axes[ i ] : i ];
+
+/* If this template axis is in the first component Frame, store the
+   corresponding internal frame axis index in "axes1" and set "axis2" to
+   -1. */
+            if( icmp < nc1 ) {
+               axes1[ i ] = icmp;
+               axes2[ i ] = -1;
+
+/* If this template axis is in the second component Frame, store the
+   corresponding internal frame axis index in "axes2" and set "axis1" to
+   -1. */
+            } else {
+               axes1[ i ] = -1;
+               axes2[ i ] = icmp - nc1;
+            }
+         }
+
+/* Now use the astOverlay methods of the two component Frames to overlay
+   attributes onto the appropriate axes of the results Frame. */
+         astOverlay( template->frame1, axes1, result );
+         astOverlay( template->frame2, axes2, result );
+      }
+
+/* Free the axes arrays. */
+      axes1 = astFree( axes1 );
+      axes2 = astFree( axes2 );
+   }
+}
+
+static void PartitionSelection( int nselect, const int select[],
+                                const int perm[], int naxes1, int naxes2,
+                                int iframe[], int following, int *status ) {
+/*
+*  Name:
+*     PartitionSelection
+
+*  Purpose:
+*     Partition a CmpFrame axis selection into two component Frame selections.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void PartitionSelection( int nselect, const int select[],
+*                              const int perm[], int naxes1, int naxes2,
+*                              int iframe[], int following, int *status )
+
+*  Class Membership:
+*     CmpFrame member function.
+
+*  Description:
+*     This function accepts an array containing the indices of axes
+*     which are to be selected from a CmpFrame, and partitions these
+*     indices to indicate which must be selected from each of the
+*     CmpFrame's two component Frames.
+*
+*     This operation is trivial if all the axis indices supplied refer
+*     to valid CmpFrame axes. However, if some of them do not (these
+*     should generally be set to -1), this function assigns these
+*     "extra" axes to one or other of the component Frames by
+*     associating them with the axes selected immediately before (or
+*     after). Allowance is made for the possibility that several
+*     consecutive selected axes may be "extra" ones, or even that they
+*     may all be. The CmpFrame's axis permutation array is also taken
+*     into account.
+
+*  Parameters:
+*     nselect
+*        The number of axes to be selected.
+*     select
+*        An array containing the (zero-based) indices of the CmpFrame
+*        axes to be selected, or -1 where "extra" axes are required.
+*     perm
+*        The CmpFrame's axis permutation array.
+*     naxes1
+*        The number of axes in the CmpFrame's first component Frame.
+*     naxes2
+*        The number of axes in the CmpFrame's second component Frame.
+*     iframe
+*        An array with "nselect" elements in which to return a number
+*        (either 1 or 2) to indicate to which component Frame (frame1
+*        or frame2) each selected axis belongs.
+*     following
+*        If this is zero, "extra" axes will be associated with the
+*        preceding normal selected axis which appears in the "select"
+*        array (if any), otherwise they will be associated with the
+*        following normal selected axis.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int end;                      /* Loop termination value */
+   int ifr;                      /* Choice of Frame for next "extra" axis */
+   int inc;                      /* Loop increment value */
+   int iselect;                  /* Loop counter for axis selections */
+   int naxes;                    /* Total number of CmpFrame axes */
+   int start;                    /* Loop starting value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain the total number of CmpFrame axes. */
+   naxes = naxes1 + naxes2;
+
+/* Loop through each axis selection and identify those which refer to
+   valid CmpFrame axes. */
+   for ( iselect = 0; iselect < nselect; iselect++ ) {
+      if ( ( select[ iselect ] >= 0 ) && ( select[ iselect ] < naxes ) ) {
+
+/* For these selections (only), enter a flag into the "iframe" array
+   which indicates which component Frame the selected axis resides
+   in. Permute each axis index before deciding this. */
+         iframe[ iselect ] = 1 + ( perm[ select[ iselect ] ] >= naxes1 );
+      }
+   }
+
+/* Set up a start, end, and increment value for looping through the
+   array of axis selections forward (if "following" is 0) or backwards
+   (otherwise). */
+   start = following ? nselect - 1 : 0;
+   end = following ? -1 : nselect;
+   inc = following ? -1 : 1;
+
+/* Set the default choice of component Frame. This will be used if
+   there are no normal axis selections to guide the choice at all. */
+   ifr = following ? 2 : 1;
+
+/* Search for the first normal axis selection so that we can replace
+   this default, if possible. (Here, "first" actually means "last" if
+   "following" is set, because we will then be scanning the array of
+   selections in reverse.) */
+   for ( iselect = start; iselect != end; iselect += inc ) {
+
+/* Identify normal axis selections and obtain the choice of component
+   Frame for the first one found. The resulting value "ifr" will be
+   used for initial (or final, if "following" is set) "extra"
+   selections for which no earlier normal selection exists - see
+   below. */
+      if ( ( select[ iselect ] >= 0 ) && ( select[ iselect ] < naxes ) ) {
+         ifr = iframe[ iselect ];
+         break;
+      }
+   }
+
+/* Loop through the selections again to allocate a choice of Frame to
+   the "extra" selected axes. */
+   for ( iselect = start; iselect != end; iselect += inc ) {
+
+/* Remember the component Frame used by the most recently encountered
+   normal axis selection. */
+      if ( ( select[ iselect ] >= 0 ) && ( select[ iselect ] < naxes ) ) {
+         ifr = iframe[ iselect ];
+
+/* For "extra" axes, allocate the most recent Frame choice. The
+   default choice (found above) will apply if no "most recent" choice
+   has been encountered. */
+      } else {
+         iframe[ iselect ] = ifr;
+      }
+   }
+}
+
+static int PartMatch( AstCmpFrame *template, AstFrame *target,
+                      int matchsub, int naxes1, const int axes1[],
+                      int naxes2, const int axes2[],
+                      int **template_axes, int **target_axes,
+                      AstMapping **map, AstFrame **result, int *status ) {
+/*
+*  Name:
+*     PartMatch
+
+*  Purpose:
+*     Match a CmpFrame template to partitioned target axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int PartMatch( AstCmpFrame *template, AstFrame *target,
+*                    int matchsub, int naxes1, const int axes1[],
+*                    int naxes2, const int axes2[],
+*                    int **template_axes, int **target_axes,
+*                    AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     CmpFrame member function.
+
+*  Description:
+*     This function matches a "template" CmpFrame to a "target" Frame
+*     and determines whether it is possible to convert coordinates
+*     between them.  If it is, a Mapping that performs the
+*     transformation is returned along with a new Frame that describes
+*     the coordinate system that results when this Mapping is applied
+*     to the "target" coordinate system. In addition, information is
+*     returned to allow the axes in this "result" Frame to be
+*     associated with the corresponding axes in the "target" Frame and
+*     "template" CmpFrame from which they are derived.
+*
+*     To simplify the matching process for a CmpFrame template, this
+*     function requires the caller to specify how the axes of the
+*     target Frame should be partitioned between the two component
+*     Frames of the template. The function attempts to find a match
+*     using this axis partitioning only. In general, the way in which
+*     the target axes must be partitioned is not known in advance, so
+*     this function must be invoked several times with alternative
+*     partitioning before a match will be found.
+
+*  Parameters:
+*     template
+*        Pointer to the template CmpFrame. This describes the
+*        coordinate system (or set of possible coordinate systems)
+*        into which we wish to convert our coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate
+*        system in which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case (i.e. if the
+*        target is of a more specialised class than the template). In
+*        this latter case, the target is cast down to the class of the
+*        template.
+*     naxes1
+*        The number of target axes to be matched against the first
+*        component Frame of the template CmpFrame.
+*     axes1
+*        An array with "naxes1" elements containing the (zero-based)
+*        indices of the target axes to be matched against the first
+*        component Frame. Order is not significant.
+*     naxes2
+*        The number of target axes to be matched against the second
+*        component Frame of the template CmpFrame.
+*     axes2
+*        An array with "naxes2" elements containing the (zero-based)
+*        indices of the target axes to be matched against the second
+*        component Frame. Order is not significant.
+*     template_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the (zero-based) index of the
+*        template CmpFrame axis from which it is derived. If it is not
+*        derived from any template axis, a value of -1 will be
+*        returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the (zero-based) index of the
+*        target Frame axis from which it is derived. If it is not
+*        derived from any target Frame axis, a value of -1 will be
+*        returned instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will
+*        be returned if the requested coordinate conversion is
+*        possible. If returned, the forward transformation of this
+*        Mapping may be used to convert coordinates between the
+*        "target" Frame and the "result" Frame (see below) and the
+*        inverse transformation will convert in the opposite
+*        direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be
+*        returned if the requested coordinate conversion is
+*        possible. If returned, this Frame describes the coordinate
+*        system that results from applying the returned Mapping
+*        (above) to the "target" coordinate system. In general, this
+*        Frame will combine attributes from (and will therefore be
+*        more specific than) both the target Frame and the template
+*        CmpFrame. In particular, when the template allows the
+*        possibility of transformaing to any one of a set of
+*        alternative coordinate systems, the "result" Frame will
+*        indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate
+*     conversion is possible. Otherwise zero is returned (this will
+*     not in itself result in an error condition).
+
+*  Notes:
+*     - The "axes1" and "axes2" arrays should not contain any axis
+*     indices in common and should, taken together, list all the axes
+*     of the target Frame.
+*     - By default, the "result" Frame will have its number of axes
+*     and axis order determined by the "template" CmpFrame. However,
+*     if the PreserveAxes attribute of the template is non-zero, then
+*     the axis count and axis order of the "target" Frame will be used
+*     instead.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frame1;             /* Pointer to first sub-Frame from target */
+   AstFrame *frame2;             /* Pointer to second sub-Frame from target */
+   AstFrame *result1;            /* Result Frame pointer from first match */
+   AstFrame *result2;            /* Result Frame pointer from second match */
+   AstFrame *tmp_frame;          /* Temporary Frame pointer */
+   AstMapping *junk_map;         /* Mapping pointer returned by astSubFrame */
+   AstMapping *map1;             /* Mapping pointer from first match */
+   AstMapping *map2;             /* Mapping pointer from second match */
+   AstMapping *permmap;          /* Pointer to PermMap */
+   AstMapping *tmp_map;          /* Temporary Mapping pointer */
+   const int *perm;              /* Template axis permutation array pointer */
+   int *inperm;                  /* Pointer to temporary permutation array */
+   int *invperm;                 /* Inverse axis permutation array pointer */
+   int *outperm;                 /* Pointer to temporary permutation array */
+   int *pick;                    /* Pointer to array of axis selections */
+   int *result_order;            /* Relative result axis order array pointer */
+   int *result_perm;             /* Result axis permutation array pointer */
+   int *target_assoc;            /* Target axis association array pointer */
+   int *target_axes1;            /* Target axis associations from 1st match */
+   int *target_axes2;            /* Target axis associations from 2nd match */
+   int *template_assoc;          /* Template axis association array pointer */
+   int *template_axes1;          /* Template axis associations, 1st match */
+   int *template_axes2;          /* Template axis associations, 2nd match */
+   int first;                    /* Axis in 1st component? */
+   int full_axis;                /* Result Frame axis index, before sub-set */
+   int match1;                   /* First match successful? */
+   int match2;                   /* Second match successful? */
+   int match;                    /* Both matches successful? (result) */
+   int match_end1;               /* MatchEnd attribute for component 1 */
+   int match_end2;               /* MatchEnd attribute for component 2 */
+   int match_end;                /* MatchEnd attribute for template */
+   int match_end_set;            /* Component MatchEnd attribute set? */
+   int output_axis;              /* Output axis index */
+   int part_result_axis;         /* Result Frame component axis index */
+   int part_target_axis;         /* Target Frame component axis index */
+   int part_template_axis;       /* Template CmpFrame component axis index */
+   int permute_set;              /* Component Permute attribute set? */
+   int permute_value;            /* Component Permute attribute value */
+   int preserve_axes;            /* Template PreserveAxes attribute value */
+   int preserve_axes_set;        /* Component PreserveAxes attribute set? */
+   int ref_naxes;                /* Number of reference Frame axes */
+   int result_axis;              /* Result Frame axis index */
+   int result_naxes1;            /* Number of result Frame axes, component 1 */
+   int result_naxes2;            /* Number of result Frame axes, component 2 */
+   int result_naxes;             /* Total number of result Frame axes */
+   int target_axis;              /* Target Frame axis index */
+   int target_naxes;             /* Number of target Frame axes */
+   int template_axis;            /* Template CmpFrame axis index */
+   int template_naxes1;          /* Number of template axes, component 1 */
+   int template_naxes;           /* Total number of template axes */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Initialise other variables to avoid compiler errors. */
+   ref_naxes = 0;
+
+/* Select the required sub-Frames from the target. */
+/* ----------------------------------------------- */
+/* We first create two sub-Frames (that can be matched against the two
+   template component Frames) by selecting the two specified sets of
+   axes from the target. This is done without overlaying any template
+   attributes. Annul the Mappings produced by this process, as these
+   are not needed. */
+
+   frame1 = NULL;
+   junk_map = NULL;
+   (void) astSubFrame( target, NULL, naxes1, axes1, NULL, &junk_map, &frame1 );
+   if( junk_map ) junk_map = astAnnul( junk_map );
+
+   frame2 = NULL;
+   junk_map = NULL;
+   (void) astSubFrame( target, NULL, naxes2, axes2, NULL, &junk_map, &frame2 );
+   if( junk_map ) junk_map = astAnnul( junk_map );
+
+/* Match the sub-Frames with the template component Frames. */
+/* -------------------------------------------------------- */
+/* We now have two sub-Frames obtained from the target, and will
+   attempt to match these with the component Frames contained within
+   the template CmpFrame. */
+
+/* Before using each template component Frame, see whether any of its
+   attributes that control matching is "un-set". If so, over-ride it
+   with the attribute value of the template CmpFrame as a whole. */
+   match_end_set = astTestMatchEnd( template->frame1 );
+   if ( !match_end_set ) {
+      astSetMatchEnd( template->frame1, astGetMatchEnd( template ) );
+   }
+   preserve_axes_set = astTestPreserveAxes( template->frame1 );
+   if ( !preserve_axes_set ) {
+      astSetPreserveAxes( template->frame1, astGetPreserveAxes( template ) );
+   }
+
+/* We must also temporarily set the Permute attribute to 1 (this is
+   normally the default, but might have been set otherwise). This is
+   needed so that permutations of the target axes will be considered.
+   Without this, the order in which the axes are presented is
+   significant and we would have to test all the permutations. If the
+   Permute attribute of the template CmpFrame as a whole is zero, then
+   the resulting match may still have to be rejected, but this must be
+   done at a higher level. */
+   permute_set = astTestPermute( template->frame1 );
+   permute_value = ( permute_set ) ? astGetPermute( template->frame1 ) : 0;
+   astSetPermute( template->frame1, 1 );
+
+/* Test for a match with the first template component Frame. */
+   match1 = astMatch( template->frame1, frame1, matchsub,
+                      &template_axes1, &target_axes1, &map1, &result1 );
+
+/* Clear the attribute values again afterwards if necessary. */
+   if ( !match_end_set ) astClearMatchEnd( template->frame1 );
+   if ( !preserve_axes_set ) astClearPreserveAxes( template->frame1 );
+
+/* Also restore the original Permute attribute setting. */
+   if ( permute_set ) {
+      astSetPermute( template->frame1, permute_value );
+   } else {
+      astClearPermute( template->frame1 );
+   }
+
+/* Repeat the whole process for the second component Frame. */
+   match_end_set = astTestMatchEnd( template->frame2 );
+   if ( !match_end_set ) {
+      astSetMatchEnd( template->frame2, astGetMatchEnd( template ) );
+   }
+   preserve_axes_set = astTestPreserveAxes( template->frame2 );
+   if ( !preserve_axes_set ) {
+      astSetPreserveAxes( template->frame2, astGetPreserveAxes( template ) );
+   }
+   permute_set = astTestPermute( template->frame2 );
+   if ( permute_set ) permute_value = astGetPermute( template->frame2 );
+   astSetPermute( template->frame2, 1 );
+
+   match2 = astMatch( template->frame2, frame2, matchsub,
+                      &template_axes2, &target_axes2, &map2, &result2 );
+
+   if ( !match_end_set ) astClearMatchEnd( template->frame2 );
+   if ( !preserve_axes_set ) astClearPreserveAxes( template->frame2 );
+   if ( permute_set ) {
+      astSetPermute( template->frame2, permute_value );
+   } else {
+      astClearPermute( template->frame2 );
+   }
+
+/* See if both matches were successful. */
+   if ( astOK && match1 && match2 ) {
+      match = 1;
+
+/* Obtain the number of target axes. */
+      target_naxes = astGetNaxes( target );
+
+/* Obtain the number of axes in each of the result Frames produced by
+   the matching operation. */
+      result_naxes1 = astGetNaxes( result1 );
+      result_naxes2 = astGetNaxes( result2 );
+
+/* Obtain the number of axes in the first template component Frame and
+   in the template CmpFrame as a whole. */
+      template_naxes1 = astGetNaxes( template->frame1 );
+      template_naxes = astGetNaxes( template );
+
+/* Obtain the value of the MatchEnd attribute for each of the
+   template's component Frames and for the template CmpFrame as a
+   whole. */
+      match_end1 = astGetMatchEnd( template->frame1 );
+      match_end2 = astGetMatchEnd( template->frame2 );
+      match_end = astGetMatchEnd( template );
+
+/* Obtain a pointer to the template CmpFrame's axis permutation
+   array. Allocate space for a further array and fill it with the
+   inverse of this axis permutation. */
+      perm = astGetPerm( template );
+      invperm = astMalloc( sizeof( int ) * (size_t) template_naxes );
+      if ( astOK ) {
+         for ( template_axis = 0; template_axis < template_naxes;
+                                  template_axis++ ) {
+            invperm[ perm[ template_axis ] ] = template_axis;
+         }
+      }
+
+/* Generate template and target axis associations. */
+/* ----------------------------------------------- */
+/* We now construct two arrays which identify the axis associations
+   between the result axes (in the order obtained from the matching
+   process above) and the axes of the template and target. This
+   involves tracing back through several steps. */
+
+/* First calculate the total number of result axes and allocate memory
+   for the association arrays. */
+      result_naxes = result_naxes1 + result_naxes2;
+      template_assoc = astMalloc( sizeof( int ) * (size_t) result_naxes );
+      target_assoc = astMalloc( sizeof( int ) * (size_t) result_naxes );
+      if ( astOK ) {
+
+/* Produce associations for each result axis in turn. */
+         for ( result_axis = 0; result_axis < result_naxes; result_axis++ ) {
+
+/* Decide whether this result axis is contained in the first (or
+   second) individual result Frame. */
+            first = ( result_axis < result_naxes1 );
+
+/* Obtain the index of the axis within the individual result Frame.
+   This involves adjusting for the axis numbering offset of the second
+   result Frame if necessary. */
+            part_result_axis = first ? result_axis :
+                                       result_axis - result_naxes1;
+
+/* Find the template and target axis associations for this axis by
+   looking them up in the association arrays returned from the
+   matching process. This gives axis indices that apply to the
+   individual template/target Frames supplied as input to the matching
+   process. */
+            part_template_axis = first ? template_axes1[ part_result_axis ] :
+                                         template_axes2[ part_result_axis ];
+            part_target_axis = first ? target_axes1[ part_result_axis ] :
+                                       target_axes2[ part_result_axis ];
+
+/* Check that the resulting template association identifies a valid
+   template axis. */
+            if ( part_template_axis != -1 ) {
+
+/* If so, obtain the template axis index. This involves adjusting for
+   the axis numbering offset of the second template component Frame
+   (if necessary) and then applying the inverse template axis
+   permutation to convert to the external template axis
+   numbering. Store the result in the template association array. */
+               template_assoc[ result_axis ] =
+                  invperm[ first ? part_template_axis :
+                                   part_template_axis + template_naxes1 ];
+
+/* Indicate if there is no template axis association by storing an
+   index of -1. */
+            } else {
+               template_assoc[ result_axis ] = -1;
+            }
+
+/* Similarly, check that the target association identifies a valid
+   target axis. */
+            if ( part_target_axis != -1 ) {
+
+/* If so, obtain the target axis index. This simply involves using the
+   axis selection arrays provided by the caller to look up which
+   target axes were involved in the matching process. */
+               target_assoc[ result_axis ] =
+                  first ? axes1[ part_target_axis ] :
+                          axes2[ part_target_axis ];
+
+/* Indicate if there is no target axis association by storing an index
+   of -1. */
+            } else {
+               target_assoc[ result_axis ] = -1;
+            }
+         }
+      }
+
+/* Free the inverse axis permutation array. */
+      invperm = astFree( invperm );
+
+/* Create the output Frame. */
+/* ------------------------ */
+/* Initialise. */
+      result_order = NULL;
+      result_perm = NULL;
+
+/* Construct the basis of the final result Frame by combining the two
+   individual result Frames (from the matching process) using a
+   CmpFrame. */
+      if ( astOK ) {
+         *result = (AstFrame *) astCmpFrame( result1, result2, "", status );
+
+/* The next step is to permute the result Frame's axis order so that
+   it corresponds with the axis order of the "reference Frame". The
+   reference Frame is either the template or the target, depending on
+   whether the template's PreserveAxes attribute is non-zero. Obtain
+   the value of this attribute. */
+         preserve_axes = astGetPreserveAxes( template );
+
+/* Decide how many axes the reference Frame contains. */
+         ref_naxes = preserve_axes ? target_naxes : template_naxes;
+
+/* Make a copy of the axis association array that refers to the
+   reference Frame. */
+         result_order = astStore( NULL,
+                                  preserve_axes ? target_assoc :
+                                                  template_assoc,
+                                  sizeof( int ) * (size_t) result_naxes );
+
+/* The intention is to use this axis association array to permute the
+   result axes into the same order as the reference Frame's axes. It
+   is not that simple, however, because some of the axis associations
+   may be null (i.e. result axes may exist that are not associated
+   with reference axes) and they may also be incomplete (i.e. not
+   every reference axis may be associated with a result axis).
+
+   This prevents us from permuting the result axis order using this
+   array directly, essentially because we haven't yet defined where
+   any "extra" result axes (those with no association) should appear
+   in the final axis order. */
+
+/* To overcome this, we replace all the null (-1) entries in the
+   "result_order" array with new values which define their position
+   relative to the other entries. This also involves re-numbering
+   other entries to avoid clashes. The new numbers assigned depend on
+   the MatchEnd attribute for each of the template component Frames,
+   so we handle the associations for each of these components
+   separately. */
+         AddExtraAxes( result_naxes, result_order,
+                       0, result_naxes1 - 1, match_end1, status );
+         AddExtraAxes( result_naxes, result_order,
+                       result_naxes1, result_naxes - 1, match_end2, status );
+
+/* There may now be some reference Frame axes which are not referenced
+   in this array, so we renumber the entries starting at zero (but
+   preserving their relative order) so that there are no missing
+   values due to these. */
+         RenumberAxes( result_naxes, result_order, status );
+
+/* The resulting "result_order" array no longer describes the original
+   reference Frame axis associations, but is now suitable for
+   permuting the result axes into the required order. However, we
+   require the inverse of this permutation, so allocate an array and
+   fill it with the inverse. */
+         result_perm = astMalloc( sizeof( int ) * (size_t) result_naxes );
+         if ( astOK ) {
+            for ( result_axis = 0; result_axis < result_naxes;
+                                   result_axis++ ) {
+               result_perm[ result_order[ result_axis ] ] = result_axis;
+            }
+         }
+
+/* Apply the inverse permutation to the result CmpFrame to put its
+   axes into the required order. */
+         astPermAxes( *result, result_perm );
+
+/* Check if the number of result Frame axes differs from the number of
+   reference axes. This can arise if the PreserveAxes attribute of
+   either template component Frame is set to a value that differs from
+   that of the template CmpFrame as a whole. If this is the case, we
+   must select a sub-set (or super-set) of the result axes, so that we
+   end up with the same number of axes as the reference Frame. */
+         if ( ref_naxes != result_naxes ) {
+
+/* Allocate an array to hold the indices of the axes required. */
+            pick = astMalloc( sizeof( int ) * (size_t) ref_naxes );
+            if ( astOK ) {
+
+/* Generate the axis indices, using the template CmpFrame's MatchEnd
+   attribute to decide which ones to use. */
+               for ( output_axis = 0; output_axis < ref_naxes;
+                                      output_axis++ ) {
+                  full_axis =
+                     match_end ? output_axis + ( result_naxes - ref_naxes ) :
+                                 output_axis;
+
+/* If the index is valid (i.e. the required axis is available), store
+   it. Otherwise, use an index of -1, which requests that new
+   (default) axes be supplied where needed. */
+                  if ( ( full_axis >= 0 ) && ( full_axis < result_naxes ) ) {
+                     pick[ output_axis ] = full_axis;
+                  } else {
+                     pick[ output_axis ] = -1;
+                  }
+               }
+            }
+
+/* Pick the required axes from the result Frame and replace it with
+   the new one. */
+            tmp_frame = astPickAxes( *result, ref_naxes, pick, NULL );
+            *result = astAnnul( *result );
+            *result = tmp_frame;
+
+/* Free the array of axis indices. */
+            pick = astFree( pick );
+         }
+      }
+
+/* Create output axis association arrays. */
+/* -------------------------------------- */
+/* We now construct the two arrays that are returned to identify which
+   template and target axes (if any) are associated with each final
+   result Frame axis. Allocate memory for these arrays. */
+      if ( astOK ) {
+         *target_axes = astMalloc( sizeof( int ) * (size_t) ref_naxes );
+         *template_axes = astMalloc( sizeof( int ) * (size_t) ref_naxes );
+         if ( astOK ) {
+
+/* For each output axis, obtain the original result axis index (before
+   any sub-set or super-set of the output axes was selected). */
+            for ( output_axis = 0; output_axis < ref_naxes; output_axis++ ) {
+               full_axis =
+                  match_end ? output_axis + ( result_naxes - ref_naxes ) :
+                              output_axis;
+
+/* Derive the result axis index before the axes were permuted into
+   their final order. */
+               if ( ( full_axis >= 0 ) && ( full_axis < result_naxes ) ) {
+                  result_axis = result_perm[ full_axis ];
+
+/* Use this axis index and the axis association arrays generated
+   earlier to obtain the required associations, and store these in the
+   output arrays. */
+                  ( *template_axes )[ output_axis ] =
+                     template_assoc[ result_axis ];
+                  ( *target_axes )[ output_axis ] =
+                     target_assoc[ result_axis ];
+
+/* Store a value of -1 if there is no association. */
+               } else {
+                  ( *template_axes )[ output_axis ] = -1;
+                  ( *target_axes )[ output_axis ] = -1;
+               }
+            }
+         }
+      }
+
+/* Free the original (un-permuted) axis association arrays. */
+      template_assoc = astFree( template_assoc );
+      target_assoc = astFree( target_assoc );
+
+/* Create the output Mapping. */
+/* -------------------------- */
+/* Construct the basis of the final output Mapping by combining the
+   Mappings produced by the individual matching processes in parallel,
+   using a CmpMap. */
+      *map = (AstMapping *) astCmpMap( map1, map2, 0, "", status );
+
+/* It is now necessary to prefix and suffix this CmpMap with two
+   PermMaps, which correct the input and output axis order to
+   correspond with the target and result Frame axes.
+
+   At the target end, this reflects the partitioning of the target
+   axes into two groups, as specified by the caller. At the result
+   end, it reflects the axis permutation applied (above) to put the
+   final result Frame axes into the required order, together with the
+   selection of any sub-set or super-set of these axes. */
+
+/* Allocate memory for permutation arrays to describe the prefix
+   PermMap. */
+      inperm = astMalloc( sizeof( int ) * (size_t) target_naxes );
+      outperm = astMalloc( sizeof( int ) * (size_t) target_naxes );
+      if ( astOK ) {
+
+/* Consider the target axes in the order that they were supplied to
+   the matching processes (i.e. the order that corresponds with the
+   input coordinates of the CmpMap produced above). */
+         for ( target_axis = 0; target_axis < target_naxes; target_axis++ ) {
+
+/* Decide whether each axis belongs to the first (or second) selected
+   group of target axes. */
+            first = ( target_axis < naxes1 );
+
+/* Obtain the index of the target axis within the group. This involves
+   allowing for the numbering offset of the second group if
+   necessary. */
+            part_target_axis = first ? target_axis :
+                                       target_axis - naxes1;
+
+/* Obtain the original target axis index by looking up the axis in the
+   appropriate axis selection array provided by the caller. */
+            outperm[ target_axis ] = first ? axes1[ part_target_axis ] :
+                                             axes2[ part_target_axis ];
+
+/* Fill the "inperm" array with the inverse of this permutation. */
+            inperm[ outperm[ target_axis ] ] = target_axis;
+         }
+      }
+
+/* If the permutation is not null, use these permutation arrays to
+   construct the required prefix PermMap. */
+      if ( GoodPerm( target_naxes, inperm, target_naxes, outperm, status ) ) {
+         permmap = (AstMapping *) astPermMap( target_naxes, inperm,
+                                              target_naxes, outperm,
+                                              NULL, "", status );
+
+/* Add the PermMap as a prefix to the result Mapping and then annul
+   the original Mapping pointers. */
+         tmp_map = (AstMapping *) astCmpMap( permmap, *map, 1, "", status );
+         (void) astAnnul( *map );
+         *map = tmp_map;
+         permmap = astAnnul( permmap );
+      }
+
+/* Free the permutation arrays. */
+      inperm = astFree( inperm );
+      outperm = astFree( outperm );
+
+/* Allocate memory for permutation arrays to describe the suffix
+   PermMap. */
+      inperm = astMalloc( sizeof( int ) * (size_t) result_naxes );
+      outperm = astMalloc( sizeof( int ) * (size_t) ref_naxes );
+      if ( astOK ) {
+
+/* Initialise the "inperm" array. */
+         for ( result_axis = 0; result_axis < result_naxes; result_axis++ ) {
+            inperm[ result_axis ] = -1;
+         }
+
+/* For each output axis, obtain the index of the corresponding result
+   axis before any sub-set or super-set was selected. */
+         for ( output_axis = 0; output_axis < ref_naxes; output_axis++ ) {
+            full_axis =
+               match_end ? output_axis + ( result_naxes - ref_naxes ) :
+                           output_axis;
+
+/* Store the axis index before the result axes were permuted, and also
+   construct the inverse permutation. */
+            if ( ( full_axis >= 0 ) && ( full_axis < result_naxes ) ) {
+               outperm[ output_axis ] = result_perm[ full_axis ];
+               inperm[ outperm[ output_axis ] ] = output_axis;
+
+/* Note which output axes do not exist in the result Frame
+   (e.g. because a super-set was selected). */
+            } else {
+               outperm[ output_axis ] = -1;
+            }
+         }
+      }
+
+/* If the permutation is not null, use these permutation arrays to
+   construct the required suffix PermMap. */
+      if ( GoodPerm( target_naxes, inperm, target_naxes, outperm, status ) ) {
+         permmap = (AstMapping *) astPermMap( result_naxes, inperm,
+                                              ref_naxes, outperm,
+                                              NULL, "", status );
+
+/* Add the PermMap as a suffix to the result Mapping and then annul
+   the original Mapping pointers. */
+         tmp_map = (AstMapping *) astCmpMap( *map, permmap, 1, "", status );
+         (void) astAnnul( *map );
+         *map = tmp_map;
+         permmap = astAnnul( permmap );
+      }
+
+/* Free the permutation arrays. */
+      inperm = astFree( inperm );
+      outperm = astFree( outperm );
+
+/* Free the result axis permutation arrays. */
+      result_order = astFree( result_order );
+      result_perm = astFree( result_perm );
+   }
+
+/* If necessary, free the results of the first matching process. */
+   if ( match1 ) {
+      template_axes1 = astFree( template_axes1 );
+      target_axes1 = astFree( target_axes1 );
+      map1 = astAnnul( map1 );
+      result1 = astAnnul( result1 );
+   }
+
+/* If necessary, free the results of the second matching process. */
+   if ( match2 ) {
+      template_axes2 = astFree( template_axes2 );
+      target_axes2 = astFree( target_axes2 );
+      map2 = astAnnul( map2 );
+      result2 = astAnnul( result2 );
+   }
+
+/* Annul the pointers to the sub-Frames selected from the target. */
+   frame1 = astAnnul( frame1 );
+   frame2 = astAnnul( frame2 );
+
+/* If an error occurred, free all allocated memory, annul the result
+   Object pointers and clear all returned values. */
+   if ( !astOK ) {
+      *template_axes = astFree( *template_axes );
+      *target_axes = astFree( *target_axes );
+      *map = astAnnul( *map );;
+      *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static void PermAxes( AstFrame *this_frame, const int perm[], int *status ) {
+/*
+*  Name:
+*     PermAxes
+
+*  Purpose:
+*     Permute the order of a CmpFrame's axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void astPermAxes( AstFrame *this, const int perm[], int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astPermAxes method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function permutes the order in which a CmpFrame's axes occur.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     perm
+*        An array of int (with one element for each axis of the
+*        CmpFrame) which lists the axes in their new order. Each
+*        element of this array should be a (zero-based) axis index
+*        identifying the axes according to their old (un-permuted)
+*        order.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Only genuine permutations of the axis order are permitted, so
+*     each axis must be referenced exactly once in the "perm" array.
+*     - If more than one axis permutation is applied to a CmpFrame,
+*     the effects are cumulative.
+
+*  Implementation Notes:
+*     - This function performs essentially the same operation as the
+*     Frame member function which it over-rides. However, it operates
+*     on a "perm" array held in the CmpFrame structure (rather than
+*     the one in the parent Frame structure). This duplication of the
+*     array is necessary because the one in the Frame structure is of
+*     zero length, the number of axes in the Frame structure having
+*     been set to zero to prevent unnecessary allocation of Axis
+*     objects which are not needed by the CmpFrame.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   int *old;                     /* Pointer to copy of old permutation array */
+   int axis;                     /* Loop counter for CmpFrame axes */
+   int naxes;                    /* Number of CmpFrame axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Validate the permutation array, to check that it describes a
+   genuine permutation. */
+   astCheckPerm( this, perm, "astPermAxes" );
+
+/* Obtain the number of CmpFrame axes. */
+   naxes = astGetNaxes( this );
+
+/* Allocate memory and use it to store a copy of the old permutation
+   array for the CmpFrame. */
+   old = astStore( NULL, this->perm, sizeof( int ) * (size_t) naxes );
+
+/* Apply the new axis permutation cumulatively to the old one and
+   store the result in the CmpFrame. */
+   if ( astOK ) {
+      for ( axis = 0; axis < naxes; axis++ ) {
+         this->perm[ axis ] = old[ perm[ axis ] ];
+      }
+   }
+
+/* Free the temporary copy of the old array. */
+   old = astFree( old );
+}
+
+static void PrimaryFrame( AstFrame *this_frame, int axis1,
+                          AstFrame **frame, int *axis2, int *status ) {
+/*
+*  Name:
+*     PrimaryFrame
+
+*  Purpose:
+*     Uniquely identify a primary Frame and one of its axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void astPrimaryFrame( AstFrame *this, int axis1, AstFrame **frame,
+*                           int *axis2, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected
+*     astPrimaryFrame method inherited from the Frame class).
+
+*  Description:
+*     This function returns information about the underlying (primary)
+*     Frame corresponding to a specified CmpFrame axis.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     axis1
+*        An axis index (zero-based) identifying the CmpFrame axis for
+*        which information is required.
+*     frame
+*        Address of a location to receive a pointer to the underlying
+*        (primary) Frame to which the requested axis belongs
+*        (i.e. this will not be a compound Frame).
+*     axis2
+*        Pointer to an int which is to receive the (zero-based) axis
+*        index within "frame" which identifies the axis being referred
+*        to, using the axis order that applied when the primary Frame
+*        was originally constructed (i.e. this function undoes all
+*        subsequent axis pemutations and the effects of combining
+*        Frames, in order to reveal the original underlying axis
+*        order).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This protected method is provided so that class
+*     implementations can distinguish the axes of Frames from one
+*     another (e.g. can distinguish a longitude axis as being
+*     different from a latitide axis) even after their order has been
+*     permuted and they have been combined with axes from other
+*     Frames.
+*     - The reference count of the primary Frame will be incremented
+*     by one to reflect the new pointer returned.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   int naxes1;                   /* Number of axes in frame1 */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Validate and permute the axis index supplied. */
+   axis1 = astValidateAxis( this, axis1, 1, "astPrimaryFrame" );
+
+/* Obtain the number of axes in the first component Frame. */
+   naxes1 = astGetNaxes( this->frame1 );
+   if ( astOK ) {
+
+/* Decide which Frame contains the axis and invoke its astPrimaryFrame
+   method to obtain the required information. */
+      if ( axis1 < naxes1 ) {
+         astPrimaryFrame( this->frame1, axis1, frame, axis2 );
+      } else {
+         astPrimaryFrame( this->frame2, axis1 - naxes1, frame, axis2 );
+      }
+   }
+}
+
+static int QsortCmpAxes( const void *a, const void *b ) {
+/*
+*  Name:
+*     QsortCmpAxes
+
+*  Purpose:
+*     Compare two axis indices for "qsort".
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int QsortCmpAxes( const void *a, const void *b )
+
+*  Class Membership:
+*     CmpFrame member function.
+
+*  Description:
+*     This is a service function for the C RTL routine "qsort". It
+*     takes the two values supplied and interprets them as integer
+*     indices into the static "qsort_axes" array. It compares the
+*     values of these two array elements and returns the result
+*     required by "qsort".
+*
+*     This function is used when sorting an array of indices so that
+*     they access the "qsort_axes" array in ascending order.
+
+*  Parameters:
+*     As required by "qsort".
+
+*  Returned Value:
+*     As required by "qsort".
+*/
+
+/* Local Variables. */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   int result;                   /* Result value to return */
+   int val_a;                    /* First axis index */
+   int val_b;                    /* Second axis index */
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Convert the values passed by "qsort" into integer array indices and
+   use these to access the "qsort_axes" array (this pointer to the
+   array being assigned by the caller of "qsort"). Extract the two
+   values being compared. */
+   val_a = qsort_axes[ *( (const int *) a ) ];
+   val_b = qsort_axes[ *( (const int *) b ) ];
+
+/* Compare the two values as required by "qsort". */
+   if ( val_a < val_b ) {
+      result = -1;
+   } else if ( val_a == val_b ) {
+      result = 0;
+   } else {
+      result = 1;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstMapping *RemoveRegions( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     RemoveRegions
+
+*  Purpose:
+*     Remove any Regions from a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstMapping *RemoveRegions( AstMapping *this, int *status )
+
+*  Class Membership:
+*     CmpFrame method (over-rides the astRemoveRegions method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function searches the supplied Mapping (which may be a
+*     compound Mapping such as a CmpMap) for any component Mappings
+*     that are instances of the AST Region class. It then creates a new
+*     Mapping from which all Regions have been removed. If a Region
+*     cannot simply be removed (for instance, if it is a component of a
+*     parallel CmpMap), then it is replaced with an equivalent UnitMap
+*     in the returned Mapping.
+*
+*     The implementation provided by the CmpFrame class invokes the
+*     astRemoveRegions method on the two component Frames, and joins
+*     the results together into a new CmpFrame. This replaces any Regions
+*     with their equivalent Frames.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the modified mapping.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *new;             /* Pointer to new CmpFrame */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   AstFrame *newfrm1;            /* New first component Frame */
+   AstFrame *newfrm2;            /* New second component Frame */
+   AstMapping *result;           /* Result pointer to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the CmpFrame. */
+   this = (AstCmpFrame *) this_mapping;
+
+/* Invoke the astRemoveRegions method on the two component Frames. */
+   newfrm1 = astRemoveRegions( this->frame1 );
+   newfrm2 = astRemoveRegions( this->frame2 );
+
+/* If neither component was modified, just return a clone of the supplied
+   pointer. */
+   if( this->frame1 == newfrm1 && this->frame2 == newfrm2 ) {
+      result = astClone( this );
+
+/* Annul new new Frame pointers. */
+      newfrm1 = astAnnul( newfrm1 );
+      newfrm2 = astAnnul( newfrm2 );
+
+/* Otherwise, we need to create a new CmpFrame to return. */
+   } else {
+
+/* Make a copy of the supplied CmpFrame so that the new CmpFrame retains
+   any attribute settings of the supplied CmpFrame. */
+      new = astCopy( this );
+      result = (AstMapping *) new;
+
+/* Replace the two component Frames with the simplified Frames. */
+      (void) astAnnul( new->frame1 );
+      (void) astAnnul( new->frame2 );
+      new->frame1 = (AstFrame *) newfrm1;
+      new->frame2 = (AstFrame *) newfrm2;
+   }
+
+/* Annul the returned Mapping if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void RenumberAxes( int naxes, int axes[], int *status ) {
+/*
+*  Name:
+*     RenumberAxes
+
+*  Purpose:
+*     Renumber axis indices to eliminate missing ones.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void RenumberAxes( int naxes, int axes[], int *status )
+
+*  Class Membership:
+*     CmpFrame member function.
+
+*  Description:
+*     This function takes an array containing a list of (zero-based)
+*     axis indices referring to the axes of a Frame, some of whose
+*     axes may not be referenced. It renumbers the axis indices, to
+*     eliminate any which are missing (i.e. not referenced), while
+*     preserving the original order. It does this by replacing each
+*     axis index by its rank (starting at zero) when the indices are
+*     sorted into ascending order.
+
+*  Parameters:
+*     naxes
+*        The number of axis indices present.
+*     axes
+*        An array, with "naxes" elements, containing the indices. This
+*        is modified by this function to contain the new indices.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   int *work;                    /* Pointer to workspace array */
+   int i;                        /* Loop counter */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Allocate workspace. */
+   work = astMalloc( sizeof( int ) * (size_t) naxes );
+   if ( astOK ) {
+
+/* Fill the workspace with indices which address the axis index values
+   in their natural order. */
+      for ( i = 0; i < naxes; i++ ) work[ i ] = i;
+
+/* Make the "axes" values available to the C RTL function "qsort" via
+   the static "qsort_axes" pointer. Then use "qsort" to permute the
+   contents of "work" so that it addresses the axis indices in
+   ascending order. */
+      qsort_axes = axes;
+      qsort( work, (size_t) naxes, sizeof( int ), QsortCmpAxes );
+
+/* Use the result to replace each axis index by its rank when sorted
+   into ascending order (starting with zero). */
+      for ( i = 0; i < naxes; i++ ) axes[ work[ i ] ] = i;
+   }
+
+/* Free the workspace array. */
+   work = astFree( work );
+}
+
+static void Resolve( AstFrame *this_frame, const double point1[],
+                     const double point2[], const double point3[],
+                     double point4[], double *d1, double *d2, int *status ){
+/*
+*  Name:
+*     Resolve
+
+*  Purpose:
+*     Resolve a vector into two orthogonal components
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void Resolve( AstFrame *this, const double point1[],
+*                   const double point2[], const double point3[],
+*                   double point4[], double *d1, double *d2, int *status );
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astOffset method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function resolves a vector into two perpendicular components.
+*     The vector from point 1 to point 2 is used as the basis vector.
+*     The vector from point 1 to point 3 is resolved into components
+*     parallel and perpendicular to this basis vector. The lengths of the
+*     two components are returned, together with the position of closest
+*     aproach of the basis vector to point 3.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the start of the basis vector,
+*        and of the vector to be resolved.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the basis vector.
+*     point3
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the vector to be
+*        resolved.
+*     point4
+*        An array of double, with one element for each Frame axis
+*        in which the coordinates of the point of closest approach of the
+*        basis vector to point 3 will be returned.
+*     d1
+*        The address of a location at which to return the distance from
+*        point 1 to point 4 (that is, the length of the component parallel
+*        to the basis vector). Positive values are in the same sense as
+*        movement from point 1 to point 2.
+*     d2
+*        The address of a location at which to return the distance from
+*        point 4 to point 3 (that is, the length of the component
+*        perpendicular to the basis vector). The returned value is always
+*        positive.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Each vector used in this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value, or if the required
+*     output values are undefined.
+*--
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   const int *perm;              /* Pointer to axis permutation array */
+   double *p1;                   /* Permuted coordinates for point1 */
+   double *p2;                   /* Permuted coordinates for point2 */
+   double *p3;                   /* Permuted coordinates for point3 */
+   double *p4;                   /* Permuted coordinates for point4 */
+   double d1a;                   /* Parallel distance in frame1 */
+   double d1b;                   /* Parallel distance in frame2 */
+   double d2a;                   /* Perpendicular distance in frame1 */
+   double d2b;                   /* Perpendicular distance in frame2 */
+   double d;                     /* Total length of basis vector */
+   double da;                    /* Length of basis vector in frame1 */
+   double db;                    /* Length of basis vector in frame2 */
+   int axis;                     /* Loop counter for axes */
+   int bad;                      /* Set bad output coordinates? */
+   int naxes1;                   /* Number of axes in frame1 */
+   int naxes;                    /* Total number of axes in CmpFrame */
+
+/* Check the global error status. */
+   *d1 = AST__BAD;
+   *d2 = AST__BAD;
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Obtain the number of axes in the CmpFrame. */
+   naxes = astGetNaxes( this );
+
+/* Obtain a pointer to the CmpFrame's axis permutation array. */
+   perm = astGetPerm( this );
+
+/* Allocate workspace. */
+   p1 = astMalloc( sizeof( double ) * (size_t) naxes );
+   p2 = astMalloc( sizeof( double ) * (size_t) naxes );
+   p3 = astMalloc( sizeof( double ) * (size_t) naxes );
+   p4 = astMalloc( sizeof( double ) * (size_t) naxes );
+
+/* Initialise a flag to indicate whether "bad" coordinates should be
+   returned. */
+   bad = 0;
+
+/* Initialise ther variables to avoid compiler warnings. */
+   da = 0.0;
+   db = 0.0;
+
+/* Check that all the coordinates of both input points are OK. If not,
+   set the "bad" flag and quit checking. */
+   if ( astOK ) {
+      for ( axis = 0; axis < naxes; axis++ ) {
+         if ( ( point1[ axis ] == AST__BAD ) ||
+              ( point2[ axis ] == AST__BAD ) ||
+              ( point3[ axis ] == AST__BAD ) ) {
+            bad = 1;
+            break;
+
+/* If the coordinates are OK, apply the axis permutation array to
+   obtain them in the required order. */
+         } else {
+            p1[ perm[ axis ] ] = point1[ axis ];
+            p2[ perm[ axis ] ] = point2[ axis ];
+            p3[ perm[ axis ] ] = point3[ axis ];
+         }
+      }
+   }
+
+/* If OK, obtain the number of axes in the first component Frame. */
+   if ( astOK && !bad ) {
+      naxes1 = astGetNaxes( this->frame1 );
+
+/* Find the projection of the required parallel distance into each of the
+   two Frames. */
+      astResolve( this->frame1, p1, p2, p3, p4, &d1a, &d2a );
+      astResolve( this->frame2, p1 + naxes1, p2 + naxes1, p3 + naxes1,
+                  p4 + naxes1, &d1b, &d2b );
+
+/* Project the first two input points into the two component Frames and
+   determine the length of the basis vector in each Frame. */
+      da = astDistance( this->frame1, p1, p2 );
+      db = astDistance( this->frame2, p1 + naxes1, p2 + naxes1 );
+
+/* Check that the returned distances are not bad. */
+      if ( astOK ) bad = ( bad || ( da == AST__BAD ) || ( db == AST__BAD ) );
+
+/* We can tolerate a bad parallel distance within a sub-Frame if the
+   basis vector has zero length in the sub-Frame, because the bad
+   parallel distance will have zero weight in the calculation. Set such
+   bad parallel distanced arbitrarily to zero. */
+      if( d1a == AST__BAD && da == 0.0 ) d1a = 0.0;
+      if( d1b == AST__BAD && db == 0.0 ) d1b = 0.0;
+
+/* Check that the final parallel distances are not bad. */
+      if ( astOK ) bad = ( bad || ( d1a == AST__BAD ) || ( d1b == AST__BAD ) );
+
+   }
+
+/* If OK, calculate the total distance between the two points. */
+   if ( astOK && !bad ) {
+      d = sqrt( da * da + db * db );
+
+/* If the points are co-incident, then set the "bad" flag. */
+      if ( d == 0.0 ) {
+         bad = 1;
+
+/* If the points are not co-incident, combine the parallel distances for
+   the individual Frames into a single parallel distance for the entire
+   CmpFrame. */
+      } else {
+         *d1 = ( da*d1a + db*d1b )/d;
+
+/*  Offset this distance away from point 1 towards point 2 to get point 4. */
+         astOffset( this, point1, point2, *d1, point4 );
+
+/* Now find the perpendicular distance (the distance between point4 and
+   point3). */
+         *d2 = astDistance( this, point4, point3 );
+
+      }
+   }
+
+/* Free the workspace arrays. */
+   p1 = astFree( p1 );
+   p2 = astFree( p2 );
+   p3 = astFree( p3 );
+   p4 = astFree( p4 );
+
+/* If no error has occurred, but bad coordinates must be returned,
+   then set these in the output array. */
+   if ( astOK && bad ) {
+      *d1 = AST__BAD;
+      *d2 = AST__BAD;
+      for ( axis = 0; axis < naxes; axis++ ) point4[ axis ] = AST__BAD;
+   }
+
+}
+
+static AstPointSet *ResolvePoints( AstFrame *this_frame, const double point1[],
+                                   const double point2[], AstPointSet *in,
+                                   AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     ResolvePoints
+
+*  Purpose:
+*     Resolve a set of vectors into orthogonal components
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstPointSet *ResolvePoints( AstFrame *this, const double point1[],
+*                                 const double point2[], AstPointSet *in,
+*                                 AstPointSet *out )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astResolvePoints method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function takes a CmpFrame and a set of vectors encapsulated
+*     in a PointSet, and resolves each one into two orthogonal components,
+*     returning these two components in another PointSet.
+*
+*     This is exactly the same as the public astResolve method, except
+*     that this method allows many vectors to be processed in a single call,
+*     thus reducing the computational cost of overheads of many
+*     individual calls to astResolve.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the start of the basis vector,
+*        and of the vectors to be resolved.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the basis vector.
+*     in
+*        Pointer to the PointSet holding the ends of the vectors to be
+*        resolved.
+*     out
+*        Pointer to a PointSet which will hold the length of the two
+*        resolved components. A NULL value may also be given, in which
+*        case a new PointSet will be created by this function.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet. The first axis will
+*     hold the lengths of the vector components parallel to the basis vector.
+*     These values will be signed (positive values are in the same sense as
+*     movement from point 1 to point 2. The second axis will hold the lengths
+*     of the vector components perpendicular to the basis vector. These
+*     values will always be positive.
+
+*  Notes:
+*     - The number of coordinate values per point in the input
+*     PointSet must match the number of axes in the supplied Frame.
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points and 2 coordinate values per point.
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   AstPointSet *in1;             /* Pointer to input PointSet for frame1 */
+   AstPointSet *in2;             /* Pointer to input PointSet for frame2 */
+   AstPointSet *out1;            /* Pointer to output PointSet for frame1 */
+   AstPointSet *out2;            /* Pointer to output PointSet for frame2 */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   const int *perm;              /* Pointer to axis permutation array */
+   double **ptr_in;              /* Pointers to input axis values */
+   double **ptr_out1;            /* Pointers to frame1 component lengths */
+   double **ptr_out2;            /* Pointers to frame2 component lengths */
+   double **ptr_out;             /* Pointers to returned component lengths */
+   double *d1;                   /* Pointer to next parallel component value */
+   double *d1_1;                 /* arallel distance in frame1 */
+   double *d1_2;                 /* Parallel distance in frame2 */
+   double *d2;                   /* Pointer to next perpendicular component value */
+   double *d2_1;                 /* Perpendicular distance in frame1 */
+   double *d2_2;                 /* Perpendicular distance in frame2 */
+   double *p1;                   /* Permuted coordinates for point1 */
+   double *p2;                   /* Permuted coordinates for point2 */
+   double *p3;                   /* Supplied vector */
+   double *p4;                   /* Closest approach to supplied vector */
+   double b1;                    /* Length of basis vector in frame1 */
+   double b2;                    /* Length of basis vector in frame2 */
+   double b;                     /* Length of basis vector */
+   int axis;                     /* Loop counter for axes */
+   int ipoint;                   /* Index of next point */
+   int nax;                      /* Number of Frame axes */
+   int naxes1;                   /* Number of axes in frame1 */
+   int naxes2;                   /* Number of axes in frame2 */
+   int ncoord_in;                /* Number of input PointSet coordinates */
+   int ncoord_out;               /* Number of coordinates in output PointSet */
+   int npoint;                   /* Number of points to transform */
+   int npoint_out;               /* Number of points in output PointSet */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialise to prevent compiler "uninitialised use" messages. */
+   d1 = NULL;
+   d2 = NULL;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Obtain the number of axes in the two component Frames */
+   naxes1 = astGetNaxes( this->frame1 );
+   naxes2 = astGetNaxes( this->frame2 );
+
+/* For the total number of axes. */
+   nax = naxes1 + naxes2;
+
+/* Obtain the number of input vectors to resolve and the number of coordinate
+   values per vector. */
+   npoint = astGetNpoint( in );
+   ncoord_in = astGetNcoord( in );
+
+/* If OK, check that the number of input coordinates matches the number
+   required by the Frame. Report an error if these numbers do not match. */
+   if ( astOK && ( ncoord_in != nax ) ) {
+      astError( AST__NCPIN, "astResolvePoints(%s): Bad number of coordinate "
+                "values (%d) in input %s.", status, astGetClass( this ), ncoord_in,
+                astGetClass( in ) );
+      astError( AST__NCPIN, "The %s given requires %d coordinate value(s) for "
+                "each input point.", status, astGetClass( this ), nax );
+   }
+
+/* If still OK, and a non-NULL pointer has been given for the output PointSet,
+   then obtain the number of points and number of coordinates per point for
+   this PointSet. */
+   if ( astOK && out ) {
+      npoint_out = astGetNpoint( out );
+      ncoord_out = astGetNcoord( out );
+
+/* Check that the dimensions of this PointSet are adequate to accommodate the
+   output coordinate values and report an error if they are not. */
+      if ( astOK ) {
+         if ( npoint_out < npoint ) {
+            astError( AST__NOPTS, "astResolvePoints(%s): Too few points (%d) in "
+                      "output %s.", status, astGetClass( this ), npoint_out,
+                      astGetClass( out ) );
+            astError( AST__NOPTS, "The %s needs space to hold %d transformed "
+                      "point(s).", status, astGetClass( this ), npoint );
+         } else if ( ncoord_out < 2 ) {
+            astError( AST__NOCTS, "astResolvePoints(%s): Too few coordinate "
+                      "values per point (%d) in output %s.", status,
+                      astGetClass( this ), ncoord_out, astGetClass( out ) );
+            astError( AST__NOCTS, "The %s supplied needs space to store 2 "
+                      "coordinate value(s) per transformed point.", status,
+                      astGetClass( this ) );
+         }
+      }
+   }
+
+/* If all the validation stages are passed successfully, and a NULL output
+   pointer was given, then create a new PointSet to encapsulate the output
+   coordinate data. */
+   if ( astOK ) {
+      if ( !out ) {
+         result = astPointSet( npoint, 2, "", status );
+
+/* Otherwise, use the PointSet supplied. */
+      } else {
+         result = out;
+      }
+   }
+
+/* Store points to the first two axis arrays in the returned PointSet. */
+   ptr_out = astGetPoints( result );
+   if( astOK ) {
+      d1 = ptr_out[ 0 ];
+      d2 = ptr_out[ 1 ];
+   }
+
+/* Obtain a pointer to the CmpFrame's axis permutation array. This array
+   holds the original axis index for each current Frame axis index. */
+   perm = astGetPerm( this );
+
+/* Temporarily permute the coordinates within the supplied PointSet back
+   in to the axis order which existed when the CmpFrame was created. */
+   astPermPoints( in, 0, perm );
+
+/* Extract the axis values relevant to each of the two sub-Frames from the
+   point1 and point2 arrays, at the same time undoing any axis permutation
+   applied to the CmpFrame as a whole. */
+   p1 = astMalloc( sizeof( double )*( size_t )nax );
+   p2 = astMalloc( sizeof( double )*( size_t )nax );
+   if( astOK ) {
+      for( axis = 0; axis < nax; axis++ ) {
+         p1[ perm[ axis ] ] = point1[ axis ];
+         p2[ perm[ axis ] ] = point2[ axis ];
+      }
+   }
+
+/* Project the first two input points into the two component Frames and
+   determine the length of the basis vector in each Frame. */
+   b1 = astDistance( this->frame1, p1, p2 );
+   b2 = astDistance( this->frame2, p1 + naxes1, p2 + naxes1 );
+
+/* If either of these distances is bad or if both are zero, then fill the
+   returned PointSet with bad values. */
+   if( b1 == AST__BAD || b2 == AST__BAD || ( b1 == 0.0 && b2 == 0.0 ) ) {
+     for( ipoint = 0; ipoint < npoint; ipoint++, d1++, d2++ ) {
+        *d1 = AST__BAD;
+        *d2 = AST__BAD;
+     }
+
+/* Otherwise we continue to calculate the resolved components */
+   } else if( astOK ){
+
+/* Calculate the total distance between the two points. */
+      b = sqrt( b1*b1 + b2*b2 );
+
+/* Create PointSets holding the input values which refer to each of the
+   two component Frames. */
+      in1 = astPointSet( npoint, naxes1, "", status );
+      in2 = astPointSet( npoint, naxes2, "", status );
+
+/* Associated the appropriate subset of the data in the supplied input
+   PointSet with each of these two PointSets. */
+      astSetSubPoints( in, 0, 0, in1 );
+      astSetSubPoints( in, 0, naxes1, in2 );
+
+/* Invoke the astResolvePoints method on each of the sub-Frames. These
+   invocations create two new PointSets containing the output values.  */
+      out1 = astResolvePoints( this->frame1, p1, p2, in1, NULL );
+      out2 = astResolvePoints( this->frame2, p1 + naxes1, p2 + naxes1, in2, NULL );
+
+/* Get pointers to the axis values in these pointsets. */
+      ptr_out1 = astGetPoints( out1 );
+      ptr_out2 = astGetPoints( out2 );
+
+/* More work space */
+      p3 = astMalloc( sizeof( double )*( size_t )nax );
+      p4 = astMalloc( sizeof( double )*( size_t )nax );
+
+/* Get pointers to the input axis values (these are still permuted to
+   undo any axis permutation applied to the CmpFrame). */
+      ptr_in = astGetPoints( in );
+
+/* Check pointers can be used safely. */
+      if( astOK ) {
+
+/* Get pointers to the parallel (d1) and perpendiclar (d2) components
+   within the two sub-Frames (_1 and _2). */
+         d1_1 = ptr_out1[ 0 ];
+         d2_1 = ptr_out1[ 1 ];
+         d1_2 = ptr_out2[ 0 ];
+         d2_2 = ptr_out2[ 1 ];
+
+/* Loop round each supplied vector. */
+         for( ipoint = 0; ipoint < npoint; ipoint++, d1++, d2++,
+                                                     d1_1++, d2_1++,
+                                                     d1_2++, d2_2++ ) {
+
+/* We can tolerate a bad parallel distance within a sub-Frame if the
+   basis vector has zero length in the sub-Frame, because the bad
+   parallel distance will have zero weight in the calculation. Set such
+   bad parallel distanced arbitrarily to zero. */
+            if( *d1_1 == AST__BAD && b1 == 0.0 ) *d1_1 = 0.0;
+            if( *d1_2 == AST__BAD && b2 == 0.0 ) *d1_2 = 0.0;
+
+/* Combine the parallel distances for the individual Frames into a single
+   parallel distance for the entire CmpFrame. */
+            if( *d1_1 != AST__BAD && *d1_2 != AST__BAD ) {
+               *d1 = ( b1*(*d1_1) + b2*(*d1_2) )/b;
+
+/*  Offset this distance away from point 1 towards point 2 to get point 4. */
+               astOffset( this, p1, p2, *d1, p4 );
+
+/* Now find the perpendicular distance (the distance between point4 and
+   point3). */
+               for( axis = 0; axis < nax; axis++ ) p3[ axis ] = ptr_in[ axis ][ ipoint ];
+               *d2 = astDistance( this, p4, p3 );
+
+            } else {
+               *d1 = AST__BAD;
+               *d2 = AST__BAD;
+            }
+         }
+      }
+
+/* Free resources */
+      in1 = astAnnul( in1 );
+      in2 = astAnnul( in2 );
+      out1 = astAnnul( out1 );
+      out2 = astAnnul( out2 );
+      p3 = astFree( p3 );
+      p4 = astFree( p4 );
+   }
+
+/* Free resources */
+   p1 = astFree( p1 );
+   p2 = astFree( p2 );
+
+/* Re-instate the original ordering of the coordinates within the
+   supplied PointSet. */
+   astPermPoints( in, 1, perm );
+
+/* Annul the returned PointSet if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static void SetActiveUnit( AstFrame *this_frame, int value, int *status ){
+/*
+*  Name:
+*     SetActiveUnit
+
+*  Purpose:
+*     Specify how the Unit attribute should be used.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void SetActiveUnit( AstFrame *this, int value, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astSetActiveUnit method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function sets the current value of the ActiveUnit flag for a
+*     CmpFrame, which controls how the Frame behaves when it is used (by
+*     astFindFrame) as a template to match another (target) Frame, or is
+*     used as the "to" Frame by astConvert. It determines if the Mapping
+*     between the template and target Frames should take differences in
+*     axis units into account.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     value
+*        The new value to use.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent method to set the ActiveUnitFlag for the CmpFrame,
+   then set the same value for the component Frames. */
+   (*parent_setactiveunit)( this_frame, value, status );
+   astSetActiveUnit( ((AstCmpFrame *)this_frame)->frame1, value );
+   astSetActiveUnit( ((AstCmpFrame *)this_frame)->frame2, value );
+}
+
+static void SetFrameFlags( AstFrame *this_frame, int value, int *status ){
+/*
+*  Name:
+*     SetFrameFlags
+
+*  Purpose:
+*     Set flags that control current Frame behaviour.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void SetFrameFlags( AstFrame *this, int value, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astSetFrameFlags method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function sets values for the bit mask of flags that control
+*     how the CmpFrame behaves. It ensures that both component Frames use
+*     the the same bitmask as the parent CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     value
+*        The new value to use.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent method to set the FrameFlags for the CmpFrame,
+   then set the same value for the component Frames. */
+   (*parent_setframeflags)( this_frame, value, status );
+   astSetFrameFlags( ((AstCmpFrame *)this_frame)->frame1, value );
+   astSetFrameFlags( ((AstCmpFrame *)this_frame)->frame2, value );
+}
+
+static int GetActiveUnit( AstFrame *this_frame, int *status ){
+/*
+*  Name:
+*     GetActiveUnit
+
+*  Purpose:
+*     Determines how the Unit attribute will be used.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int GetActiveUnit( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astGetActiveUnit method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns the current value of the ActiveUnit flag for a
+*     CmpFrame. See the description of the astSetActiveUnit function
+*     for a description of the ActiveUnit flag.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The current value of the ActiveUnit flag.
+
+*/
+
+/* Local Variables; */
+   int result;      /* The ActiveUnit flag for the CmpFrame */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* If the ActiveUnit value has been set for the CmpFrame use the parent
+   implementation to get its value. */
+   if( astTestActiveUnit( this_frame ) ) {
+      result = (*parent_getactiveunit)( this_frame, status );
+
+/* Otherwise, the default is determined by the component Frames. If both
+   components have active units, the default for the CmpFrame is "on" */
+   } else {
+      result = astGetActiveUnit( ((AstCmpFrame *)this_frame)->frame1 ) ||
+               astGetActiveUnit( ((AstCmpFrame *)this_frame)->frame2 );
+   }
+
+/* Return the result */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (extends the astSetAttrib method inherited from
+*     the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a CmpFrame, the attribute
+*     and its value being specified by means of a string of the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in lower
+*     case with no white space present. The value to the right of the "="
+*     should be a suitable textual representation of the value to be assigned
+*     and this will be interpreted according to the attribute's data type.
+*     White space surrounding the value is only significant for string
+*     attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This protected method is intended to be invoked by the Object astSet
+*     method and makes additional attributes accessible to it.
+*/
+
+#define BUF_LEN 1024
+
+/* Local Vaiables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   AstFrame *pfrm;               /* Pointer to primary Frame containing axis */
+   char buf1[BUF_LEN];           /* For for un-indexed attribute name */
+   char buf2[BUF_LEN];           /* For for indexed attribute name */
+   int axis;                     /* Supplied (1-base) axis index */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+   int oldrep;                   /* Original error reporting state */
+   int paxis;                    /* Index of primary Frame axis */
+   int ok;                       /* Have we accessed the attribute succesfully? */
+   int value;                    /* Offset to start fo value string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Indicate we have not yet acessed the attribute succesfully. */
+   ok = 0;
+
+/* First check the supplied attribute name against each of the attribute
+   names defined by this class. In fact there is nothing to do here
+   since the CmpFrame class currently defines no extra attributes, but
+   this may change in the future. */
+   if( 0 ) {
+
+
+
+/* If the attribute is not a CmpFrame specific attribute... */
+   } else if( astOK ) {
+
+/* We want to allow easy access to the attributes of the component Frames.
+   That is, we do not want it to be necessary to extract a Frame from
+   its parent CmpFrame in order to access its attributes. For this reason
+   we first temporarily switch off error reporting so that if an attempt
+   to access the attribute fails, we can try a different approach. */
+      oldrep = astReporting( 0 );
+
+/* Our first attempt is to see if the attribute is recognised by the parent
+   class (Frame). */
+      (*parent_setattrib)( this_object, setting, status );
+
+/* Indicate success. */
+      if( astOK ) {
+         ok = 1;
+
+/* Otherwise, clear the error condition so that we can try a different
+   approach. */
+      } else {
+         astClearStatus;
+
+/* If the attribute is qualified by an axis index, try accessing it as an
+   attribute of the primary Frame containing the specified index. */
+         if ( nc = 0,
+             ( 2 == astSscanf( setting, "%[^(=](%d)= %n%*s %n", buf1, &axis,
+                               &value, &nc ) ) && ( nc >= len ) ) {
+
+/* Find the primary Frame containing the specified axis. */
+            astPrimaryFrame( this, axis - 1, &pfrm, &paxis );
+            if( astOK ) {
+
+/* astPrimaryFrame returns the original - unpermuted - axis index within
+   the primary Frame. So we need to take into account any axis permutation
+   which has been applied to the primary Frame when forming the attribute name
+   to use below. Find the permuted (external) axis index which corresponds to
+   the internal (unpermuted) axis index "paxis". */
+               paxis = astValidateAxis( pfrm, paxis, 0, "astSet" );
+
+/* Create a new setting with the same name but with the axis index
+   appropriate to the primary Frame. */
+               nc = sprintf( buf2, "%s(%d)=%s", buf1, paxis + 1,
+                             setting+value );
+               if( nc < BUF_LEN ) {
+
+/* Attempt to access the attribute. */
+                  astSetAttrib( pfrm, buf2 );
+
+/* Indicate success. */
+                  if( astOK ) {
+                     ok = 1;
+
+/* Otherwise clear the status value, and try again without any axis index. */
+                  } else {
+                     astClearStatus;
+                     sprintf( buf2, "%s=%s", buf1, setting+value );
+                     astSetAttrib( pfrm, buf2 );
+
+/* Indicate success, or clear the status value. */
+                     if( astOK ) {
+                        ok = 1;
+                     } else {
+                        astClearStatus;
+                     }
+                  }
+
+/* Buffer overflow */
+               } else if( astOK ) {
+                  astError( AST__INTER, "SetAttrib(CmpFrame): Buffer "
+                            "over-flow (internal AST programming error).",
+                            status );
+               }
+
+/* Free the primary frame pointer. */
+               pfrm = astAnnul( pfrm );
+            }
+
+/* If the attribute is not qualified by an axis index, try accessing it
+   using the primary Frame of each axis in turn. */
+         } else {
+
+/* Loop round all axes attribute. */
+	    for( axis = 0; axis < astGetNaxes( this ); axis++ ) {
+
+/* Get the primary Frame containing this axis. */
+               astPrimaryFrame( this, axis, &pfrm, &paxis );
+
+/* Attempt to access the attribute as an attribute of the primary Frame. */
+               astSetAttrib( pfrm, setting );
+
+/* Free the primary Frame pointer. */
+               pfrm = astAnnul( pfrm );
+
+/* Indicate success, or clear the status value. */
+               if( astOK ) {
+                  ok = 1;
+               } else {
+                  astClearStatus;
+               }
+            }
+         }
+      }
+
+/* Re-instate the original error reporting state. */
+      astReporting( oldrep );
+
+   }
+
+/* Report an error if the attribute could not be accessed. */
+   if( !ok && astOK ) {
+      astError( AST__BADAT, "astSet: The attribute setting \"%s\" is invalid "
+               "for the given %s.", status, setting, astGetClass( this ) );
+   }
+
+#undef BUF_LEN
+}
+
+static void SetAxis( AstFrame *this_frame, int axis, AstAxis *newaxis, int *status ) {
+/*
+*  Name:
+*     SetAxis
+
+*  Purpose:
+*     Set a new Axis for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void astSetAxis( AstFrame *this, int axis, AstAxis *newaxis, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astSetAxis method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function allows a new Axis object to be associated with one
+*     of the axes of a CmpFrame, replacing the previous one. Each Axis
+*     object contains a description of the quantity represented along
+*     one of the CmpFrame's axes, so this function allows this
+*     description to be exchanged for another one.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     axis
+*        The index (zero-based) of the CmpFrame axis whose associated
+*        Axis object is to be replaced.
+*     newaxis
+*        Pointer to the new Axis object.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   int naxes1;                   /* Number of axes in frame1 */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Validate and permute the axis index supplied. */
+   axis = astValidateAxis( this, axis, 1, "astSetAxis" );
+
+/* Determine the number of axes in the first component Frame. */
+   naxes1 = astGetNaxes( this->frame1 );
+   if ( astOK ) {
+
+/* Decide which Frame contains the axis and invoke its astSetAxis
+   method to set the new Axis. */
+      if ( axis < naxes1 ) {
+         astSetAxis( this->frame1, axis, newaxis );
+      } else {
+         astSetAxis( this->frame2, axis - naxes1, newaxis );
+      }
+   }
+}
+
+static void SetDut1( AstFrame *this_frame, double val, int *status ) {
+/*
+*  Name:
+*     SetDut1
+
+*  Purpose:
+*     Set the value of the Dut1 attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void SetDut1( AstFrame *this, double val, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astSetDut1 method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function sets the Dut1 value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     val
+*        New Dut1 value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to set the CmpFrame Dut1 value. */
+   (*parent_setdut1)( this_frame, val, status );
+
+/* Now set the Dut1 attribute in the two component Frames. */
+   astSetDut1( this->frame1, val );
+   astSetDut1( this->frame2, val );
+}
+
+static void SetEpoch( AstFrame *this_frame, double val, int *status ) {
+/*
+*  Name:
+*     SetEpoch
+
+*  Purpose:
+*     Set the value of the Epoch attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void SetEpoch( AstFrame *this, double val, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astSetEpoch method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function sets the Epoch value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     val
+*        New Epoch value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to set the CmpFrame epoch. */
+   (*parent_setepoch)( this_frame, val, status );
+
+/* Now set the Epoch attribute in the two component Frames. */
+   astSetEpoch( this->frame1, val );
+   astSetEpoch( this->frame2, val );
+}
+
+static void SetObsAlt( AstFrame *this_frame, double val, int *status ) {
+/*
+*  Name:
+*     SetObsAlt
+
+*  Purpose:
+*     Set the value of the ObsAlt attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void SetObsAlt( AstFrame *this, double val, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astSetObsAlt method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function sets the ObsAlt value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     val
+*        New ObsAlt value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to set the CmpFrame ObsAlt. */
+   (*parent_setobsalt)( this_frame, val, status );
+
+/* Now set the ObsAlt attribute in the two component Frames. */
+   astSetObsAlt( this->frame1, val );
+   astSetObsAlt( this->frame2, val );
+}
+
+static void SetObsLat( AstFrame *this_frame, double val, int *status ) {
+/*
+*  Name:
+*     SetObsLat
+
+*  Purpose:
+*     Set the value of the ObsLat attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void SetObsLat( AstFrame *this, double val, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astSetObsLat method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function sets the ObsLat value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     val
+*        New ObsLat value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to set the CmpFrame ObsLat. */
+   (*parent_setobslat)( this_frame, val, status );
+
+/* Now set the ObsLat attribute in the two component Frames. */
+   astSetObsLat( this->frame1, val );
+   astSetObsLat( this->frame2, val );
+}
+
+static void SetObsLon( AstFrame *this_frame, double val, int *status ) {
+/*
+*  Name:
+*     SetObsLon
+
+*  Purpose:
+*     Set the value of the ObsLon attribute for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     void SetObsLon( AstFrame *this, double val, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astSetObsLon method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function sets the ObsLon value in the component Frames as
+*     well as this CmpFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     val
+*        New ObsLon value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Invoke the parent method to set the CmpFrame ObsLon. */
+   (*parent_setobslon)( this_frame, val, status );
+
+/* Now set the ObsLon attribute in the two component Frames. */
+   astSetObsLon( this->frame1, val );
+   astSetObsLon( this->frame2, val );
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify the Mapping represented by a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     CmpFrame method (over-rides the astSimplify method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function simplifies the Mapping represented by a CmpFrame,
+*     by using the astSimplify method on each of the component Frames and
+*     combining the resulting Mappings together.
+
+*  Parameters:
+*     this
+*        Pointer to the original CmpFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A new pointer to the simplified CmpFrame.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *new;         /* Pointer to new CmpFrame structure */
+   AstCmpFrame *this;        /* Pointer to original CmpFrame structure */
+   AstMapping *map1;         /* Intermediate Mapping */
+   AstMapping *map2;         /* Intermediate Mapping */
+   AstMapping *result;       /* Result pointer to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_mapping;
+
+/* Simplify each of the component Frames. */
+   map1 = astSimplify( this->frame1 );
+   map2 = astSimplify( this->frame2 );
+
+/* Did any usable simplification occur? */
+   if( astIsAFrame( map1 ) &&  astIsAFrame( map2 )  &&
+       ( map1 != (AstMapping *) this->frame1 ||
+         map2 != (AstMapping *) this->frame2 ) ) {
+
+/* Make a copy of the supplied CmpFrame. */
+      new = astCopy( this );
+      result = (AstMapping *) new;
+
+/* Replace the two component Frames with the simplified Frames. */
+      (void) astAnnul( new->frame1 );
+      (void) astAnnul( new->frame2 );
+      new->frame1 = (AstFrame *) map1;
+      new->frame2 = (AstFrame *) map2;
+
+/* If no simplication took place, annul the Mapping pointers and return a
+   clone of the supplied pointer. */
+   } else {
+       map1 = astAnnul( map1 );
+       map2 = astAnnul( map2 );
+       result= astClone( this );
+   }
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType SystemCode( AstFrame *this, const char *system, int *status ) {
+/*
+*  Name:
+*     SystemCode
+
+*  Purpose:
+*     Convert a string into a coordinate system type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstSystemType SystemCode( AstFrame *this, const char *system, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astSystemCode method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a string used for the external
+*     description of a coordinate system into a CmpFrame
+*     coordinate system type code (System attribute value). It is the
+*     inverse of the astSystemString function.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     system
+*        Pointer to a constant null-terminated string containing the
+*        external description of the coordinate system.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System type code.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM is returned if the coordinate
+*     system description was not recognised. This does not produce an
+*     error.
+*     - A value of AST__BADSYSTEM is also returned if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;      /* Result value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" string against each possibility and assign the
+   result. The CmpFrame class only supports a single system "Compound". */
+   if ( astChrMatch( "Compound", system ) ) {
+      result = AST__COMP;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *SystemString( AstFrame *this, AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SystemString
+
+*  Purpose:
+*     Convert a coordinate system type code into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     const char *SystemString( AstFrame *this, AstSystemType system, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astSystemString method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a CmpFrame coordinate system type code
+*     (System attribute value) into a string suitable for use as an
+*     external representation of the coordinate system type.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" value against each possibility and convert to a
+   string pointer. (Where possible, return the same string as would be
+   used in the FITS WCS representation of the coordinate system). A
+   CmpFrame only allows a single System value, "Compound". */
+   switch ( system ) {
+   case AST__COMP:
+      result = "Compound";
+      break;
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static int SubFrame( AstFrame *target_frame, AstFrame *template,
+                     int result_naxes, const int *target_axes,
+                     const int *template_axes, AstMapping **map,
+                     AstFrame **result, int *status ) {
+/*
+*  Name:
+*     SubFrame
+
+*  Purpose:
+*     Select axes from a CmpFrame and convert to the new coordinate system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int SubFrame( AstFrame *target, AstFrame *template,
+*                   int result_naxes, const int *target_axes,
+*                   const int *template_axes, AstMapping **map,
+*                   AstFrame **result, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the protected astSubFrame
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function selects a requested sub-set (or super-set) of the
+*     axes from a "target" CmpFrame and creates a new Frame with
+*     copies of the selected axes assembled in the requested order. It
+*     then optionally overlays the attributes of a "template" Frame on
+*     to the result. It returns both the resulting Frame and a Mapping
+*     that describes how to convert between the coordinate systems
+*     described by the target and result Frames. If necessary, this
+*     Mapping takes account of any differences in the Frames'
+*     attributes due to the influence of the template.
+
+*  Parameters:
+*     target
+*        Pointer to the target CmpFrame, from which axes are to be selected.
+*     template
+*        Pointer to the template Frame, from which new attributes for
+*        the result Frame are to be obtained. Optionally, this may be
+*        NULL, in which case no overlaying of template attributes will
+*        be performed.
+*     result_naxes
+*        Number of axes to be selected from the target Frame. This
+*        number may be greater than or less than the number of axes in
+*        this Frame (or equal).
+*     target_axes
+*        Pointer to an array of int with result_naxes elements, giving
+*        a list of the (zero-based) axis indices of the axes to be
+*        selected from the target CmpFrame. The order in which these
+*        are given determines the order in which the axes appear in
+*        the result Frame. If any of the values in this array is set
+*        to -1, the corresponding result axis will not be derived from
+*        the target Frame, but will be assigned default attributes
+*        instead.
+*     template_axes
+*        Pointer to an array of int with result_naxes elements. This
+*        should contain a list of the template axes (given as
+*        zero-based axis indices) with which the axes of the result
+*        Frame are to be associated. This array determines which axes
+*        are used when overlaying axis-dependent attributes of the
+*        template on to the result. If any element of this array is
+*        set to -1, the corresponding result axis will not receive any
+*        template attributes.
+*
+*        If the template argument is given as NULL, this array is not
+*        used and a NULL pointer may also be supplied here.
+*     map
+*        Address of a location to receive a pointer to the returned
+*        Mapping.  The forward transformation of this Mapping will
+*        describe how to convert coordinates from the coordinate
+*        system described by the target CmpFrame to that described by
+*        the result Frame. The inverse transformation will convert in
+*        the opposite direction.
+*     result
+*        Address of a location to receive a pointer to the result Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if coordinate conversion is
+*     possible between the target and the result Frame. Otherwise zero
+*     is returned and *map and *result are returned as NULL (but this
+*     will not in itself result in an error condition). In general,
+*     coordinate conversion should always be possible if no template
+*     Frame is supplied but may not always be possible otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+
+*  Implementation Deficiencies:
+*     - It is not clear that the method of handling "extra" axes is
+*     the best one, nor is the method of setting the "following" flag
+*     necessarily correct.  However, it is also not obvious that this
+*     feature will ever be needed, so improvements have been left
+*     until the requirement is clearer.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *target;          /* Pointer to target CmpFrame structure */
+   AstFrame *sub_result1;        /* Pointer to result Frame for frame1 */
+   AstFrame *sub_result2;        /* Pointer to result Frame for frame2 */
+   AstMapping *permmap_pref;     /* Pointer to PermMap used as a prefix */
+   AstMapping *permmap_suff;     /* Pointer to PermMap used as a suffix */
+   AstMapping *sub_map1;         /* Pointer to Mapping from frame1 */
+   AstMapping *sub_map2;         /* Pointer to Mapping from frame2 */
+   AstMapping *sub_map;          /* Pointer to combined component Mappings */
+   AstMapping *tmp_map;          /* Temporary Mapping pointer */
+   const int *perm;              /* Pointer to axis permutation array */
+   int *frame_choice;            /* Pointer to flag array for partitioning */
+   int *inperm_pref;             /* Pointer to prefix permutation array */
+   int *inperm_suff;             /* Pointer to suffix permutation array */
+   int *outperm_pref;            /* Pointer to prefix permutation array */
+   int *outperm_suff;            /* Pointer to suffix permutation array */
+   int *target_axes1;            /* Pointer to frame1 axis selection array */
+   int *target_axes2;            /* Pointer to frame2 axis selection array */
+   int *template_axes1;          /* Pointer to frame1 template axis array */
+   int *template_axes2;          /* Pointer to frame2 template axis array */
+   int axis_p;                   /* Permuted axis index */
+   int following;                /* Associate extra axis and following axis? */
+   int i1;                       /* Count of axes obtained from frame1 */
+   int i2;                       /* Count of axes obtained from frame2 */
+   int match;                    /* Result value to return */
+   int n1;                       /* Number of axes obtained from frame1 */
+   int n2;                       /* Number of axes obtained from frame2 */
+   int naxes1;                   /* Number of axes in frame1 */
+   int naxes2;                   /* Number of axes in frame2 */
+   int naxes;                    /* Number of axes in target */
+   int result_axis;              /* Result axis index */
+   int target_axis;              /* Target axis index */
+
+/* Initialise the returned values. */
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the target CmpFrame structure. */
+   target = (AstCmpFrame *) target_frame;
+
+/* Obtain the number of axes in the target CmpFrame and in each of its
+   component Frames. */
+   naxes = astGetNaxes( target );
+   naxes1 = astGetNaxes( target->frame1 );
+   naxes2 = astGetNaxes( target->frame2 );
+
+/* Iinitialise variables to avoid compiler warnings. */
+   template_axes1 = NULL;
+   template_axes2 = NULL;
+   n1 = 0;
+   n2 = 0;
+
+/* Obtain the axis permutation array for the target CmpFrame. */
+   perm = astGetPerm( target );
+
+/* Determine how any "extra" axes should be associated with existing
+   axes (i.e. whether to associate with the preceding or following
+   axis). */
+   following = astGetMatchEnd( target );
+
+/* Split selected axes into two groups. */
+/* ------------------------------------ */
+/* Allocate a workspace array to hold the choice of component Frame
+   for each selected target axis. */
+   frame_choice = astMalloc( sizeof( int ) * (size_t) result_naxes );
+
+/* Obtain an array of flags indicating whether each selected target
+   axis should be obtained from the first or second component
+   Frame. */
+   PartitionSelection( result_naxes, target_axes, perm, naxes1, naxes2,
+                       frame_choice, following, status );
+
+/* Allocate two arrays to hold the axis indices that refer to each of
+   the component Frames. The maximum number of indices is given by
+   "result_naxes" (if all the selected axes come from one component
+   Frame alone). */
+   target_axes1 = astMalloc( sizeof( int ) * (size_t) result_naxes );
+   target_axes2 = astMalloc( sizeof( int ) * (size_t) result_naxes );
+
+/* If a template Frame has been provided, allocate similar arrays to
+   hold the indices of the two groups of template axes. */
+   if ( template ) {
+      template_axes1 = astMalloc( sizeof( int ) * (size_t) result_naxes );
+      template_axes2 = astMalloc( sizeof( int ) * (size_t) result_naxes );
+   }
+
+/* Initialise the count of axes selected from each component Frame. */
+   if ( astOK ) {
+      n1 = n2 = 0;
+
+/* Loop through each axis index to be selected from the CmpFrame. */
+      for ( result_axis = 0; result_axis < result_naxes; result_axis++ ) {
+         target_axis = target_axes[ result_axis ];
+
+/* Determine if the index refers to a valid CmpFrame axis. If it does,
+   then permute the index, otherwise set it to -1. */
+         if ( ( target_axis >= 0 ) && ( target_axis < naxes ) ) {
+            axis_p = perm[ target_axis ];
+         } else {
+            axis_p = -1;
+         }
+
+/* If the axis is to be selected from the first component Frame, store
+   the index of the axis to be selected. Also store the associated
+   template axis index (if any). */
+         if ( frame_choice[ result_axis ] == 1 ) {
+            target_axes1[ n1 ] = axis_p;
+            if ( template ) {
+               template_axes1[ n1 ] = template_axes[ result_axis ];
+            }
+
+/* Count the axes selected from the first component Frame. */
+            n1++;
+
+/* If the axis is to be selected from the second component Frame,
+   store the index of the index to be selected (adjusting for the
+   offset in axis numbering). Also store the associated template axis
+   index (if any) and count the axes selected. */
+         } else {
+            target_axes2[ n2 ] = ( axis_p == -1 ) ? -1 : axis_p - naxes1;
+            if ( template ) {
+               template_axes2[ n2 ] = template_axes[ result_axis ];
+            }
+            n2++;
+         }
+      }
+   }
+
+/* Select from first component Frame only. */
+/* --------------------------------------- */
+/* If all the selected axes come from the first component Frame, use
+   that Frame's astSubFrame method to select them (and overlay the
+   template attributes if required). */
+   if ( astOK ) {
+      if ( n1 && !n2 ) {
+         sub_map1 = NULL;
+         match = astSubFrame( target->frame1, template, n1, target_axes1,
+                              template_axes1, &sub_map1, result );
+
+/* If this is successful, the "result" Frame will be ready to return
+   and "sub_map1" will point at a Mapping that converts from the first
+   component Frame to the "result" Frame. We must now modify this
+   mapping to account for the CmpFrame's axis permutation array
+   (i.e. make it refer back to the CmpFrame's original axis order). */
+         if ( astOK && match ) {
+
+/* To do this we must prefix the Mapping with a PermMap which converts
+   between the target CmpFrame axes and those of the first component
+   Frame. Allocate space for the permutation arrays required. */
+            inperm_pref = astMalloc( sizeof( int ) * (size_t) naxes );
+            outperm_pref = astMalloc( sizeof( int ) * (size_t) naxes1 );
+            if ( astOK ) {
+
+/* Permute each target axis index. */
+               for ( target_axis = 0; target_axis < naxes; target_axis++ ) {
+                  axis_p = perm[ target_axis ];
+
+/* Set up arrays that describe this permutation and its inverse. */
+                  if ( axis_p < naxes1 ) {
+                     inperm_pref[ target_axis ] = axis_p;
+                     outperm_pref[ axis_p ] = target_axis;
+
+/* Note which target axes do not correspond with axes in the first
+   component Frame and assign -1 (so the PermMap will assign "bad"
+   coordinate values to these axes). */
+                  } else {
+                     inperm_pref[ target_axis ] = -1;
+                  }
+               }
+
+/* Use these permutation arrays to construct the PermMap. Prefix this
+   to the Mapping obtained earlier to give the final Mapping to be
+   returned. */
+               permmap_pref =
+                  (AstMapping *) astPermMap( naxes, inperm_pref,
+                                             naxes1, outperm_pref, NULL, "", status );
+               *map = (AstMapping *) astCmpMap( permmap_pref, sub_map1, 1, "", status );
+
+/* Annul the PermMap pointer. */
+               permmap_pref = astAnnul( permmap_pref );
+            }
+
+/* Free the permutation arrays and annul the original Mapping pointer. */
+            inperm_pref = astFree( inperm_pref );
+            outperm_pref = astFree( outperm_pref );
+            sub_map1 = astAnnul( sub_map1 );
+         }
+
+/* Select from second component Frame only. */
+/* ---------------------------------------- */
+/* If all the selected axes come from the second component Frame, use
+   that Frame's astSubFrame method to select them (and overlay the
+   template attributes if required). */
+      } else if ( n2 && !n1 ) {
+         sub_map2 = NULL;
+         match = astSubFrame( target->frame2, template, n2, target_axes2,
+                              template_axes2, &sub_map2, result );
+
+/* If this is successful, the "result" Frame will be ready to return
+   and "sub_map2" will point at a Mapping that converts from the second
+   component Frame to the "result" Frame. We must now modify this
+   mapping to account for the CmpFrame's axis permutation array
+   (i.e. make it refer back to the CmpFrame's original axis order). */
+         if ( astOK && match ) {
+
+/* To do this we must prefix the Mapping with a PermMap which converts
+   between the target CmpFrame axes and those of the second component
+   Frame. Allocate space for the permutation arrays required. */
+            inperm_pref = astMalloc( sizeof( int ) * (size_t) naxes );
+            outperm_pref = astMalloc( sizeof( int ) * (size_t) naxes2 );
+            if ( astOK ) {
+
+/* Permute each target axis index. */
+               for ( target_axis = 0; target_axis < naxes; target_axis++ ) {
+                  axis_p = perm[ target_axis ];
+
+/* Set up arrays that describe this permutation and its inverse,
+   allowing for the shift in axis numbering for the second component
+   Frame. */
+                  if ( axis_p >= naxes1 ) {
+                     inperm_pref[ target_axis ] = axis_p - naxes1;
+                     outperm_pref[ axis_p - naxes1 ] = target_axis;
+
+/* Note which target axes do not correspond with axes in the second
+   component Frame and assign -1 (so the PermMap will assign "bad"
+   coordinate values to these axes). */
+                  } else {
+                     inperm_pref[ target_axis ] = -1;
+                  }
+               }
+
+/* Use these permutation arrays to construct the PermMap. Prefix this
+   to the Mapping obtained earlier to give the final Mapping to be
+   returned. */
+               permmap_pref =
+                  (AstMapping *) astPermMap( naxes, inperm_pref,
+                                             naxes2, outperm_pref, NULL, "", status );
+
+               *map = (AstMapping *) astCmpMap( permmap_pref, sub_map2, 1, "", status );
+
+/* Annul the PermMap pointer. */
+               permmap_pref = astAnnul( permmap_pref );
+            }
+
+/* Free the permutation arrays and annul the original Mapping pointer. */
+            inperm_pref = astFree( inperm_pref );
+            outperm_pref = astFree( outperm_pref );
+            sub_map2 = astAnnul( sub_map2 );
+         }
+
+/* Select from both component Frames. */
+/* ---------------------------------- */
+/* If the selected axes come from both component Frames, then use both
+   Frames' astSubFrame methods to select the required axes from each
+   of them (and overlay the template attributes if required). */
+      } else {
+         sub_map1 = NULL;
+         sub_map2 = NULL;
+         sub_result1 = NULL;
+         sub_result2 = NULL;
+         match = astSubFrame( target->frame1, template, n1, target_axes1,
+                              template_axes1, &sub_map1, &sub_result1 );
+         if ( match ) {
+            match = astSubFrame( target->frame2, template, n2, target_axes2,
+                                 template_axes2, &sub_map2, &sub_result2 );
+         }
+
+/* If this is successful, the two "result" Frames will need to be
+   combined together (in a CmpFrame) in order to produce the required
+   result, and the two accompanying Mappings will also need to be
+   applied in parallel (in a CmpMap). However, the axis order
+   resulting from this will still not match that required.
+
+   On the target side, this is because of the target's axis
+   permutation array.  On the result side, it is because the result
+   axes cannot be inter-mingled (as may be required) simply by joining
+   the Frames and Mappings in parallel. The resulting CmpFrame axes
+   will therefore need permuting into the required final order. */
+         if ( astOK && match ) {
+
+/* In addition, the Mappings will need to be both prefixed and
+   suffixed with suitable PermMaps which re-order the axes. Allocate
+   space for the permutation arrays required. */
+            inperm_pref = astMalloc( sizeof( int ) * (size_t) naxes );
+            outperm_pref = astMalloc( sizeof( int ) * (size_t) naxes );
+            inperm_suff = astMalloc( sizeof( int ) * (size_t) result_naxes );
+            outperm_suff = astMalloc( sizeof( int ) * (size_t) result_naxes );
+            if ( astOK ) {
+
+/* Set up permutation arrays to construct the prefix PermMap. This
+   simply represents the target CmpFrame's axis permutation array and
+   its inverse. */
+               for ( target_axis = 0; target_axis < naxes; target_axis++ ) {
+                  axis_p = perm[ target_axis ];
+                  inperm_pref[ target_axis ] = axis_p;
+                  outperm_pref[ axis_p ] = target_axis;
+               }
+
+/* Set up permutation arrays to construct the suffix PermMap. This
+   represents the way the original axis selections were partitioned
+   between the two component frames. */
+               i1 = i2 = 0;
+               for ( result_axis = 0; result_axis < result_naxes;
+                                      result_axis++ ) {
+
+/* For each result axis derived from the first component Frame, set up
+   permutation array elements to link the output axis with the next
+   component Frame axis. Count the number of component Frame axes
+   used. */
+                  if ( frame_choice[ result_axis ] == 1 ) {
+                     inperm_suff[ i1 ] = result_axis;
+                     outperm_suff[ result_axis ] = i1;
+                     i1++;
+
+/* Similarly link the axes derived from the second component Frame
+   with the appropriate axes of that Frame. */
+                  } else {
+                     inperm_suff[ n1 + i2 ] = result_axis;
+                     outperm_suff[ result_axis ] = n1 + i2;
+                     i2++;
+                  }
+               }
+
+/* Combine the Mappings supplied by the two component Frames in
+   parallel. */
+               sub_map = (AstMapping *) astCmpMap( sub_map1, sub_map2, 0, "", status );
+
+/* Create the PermMaps which are to be used as a prefix and a suffix. */
+               permmap_pref =
+                  (AstMapping *) astPermMap( naxes, inperm_pref,
+                                             naxes, outperm_pref, NULL, "", status );
+               permmap_suff =
+                  (AstMapping *) astPermMap( result_naxes, inperm_suff,
+                                             result_naxes, outperm_suff,
+                                             NULL, "", status );
+
+/* Add the prefix and suffix PermMaps. */
+               tmp_map = (AstMapping *) astCmpMap( permmap_pref, sub_map,
+                                                   1, "", status );
+               *map = (AstMapping *) astCmpMap( tmp_map, permmap_suff, 1, "", status );
+
+/* Annul the Mapping pointers that are no longer required. */
+               sub_map = astAnnul( sub_map );
+               permmap_pref = astAnnul( permmap_pref );
+               permmap_suff = astAnnul( permmap_suff );
+               tmp_map = astAnnul( tmp_map );
+
+/* Create the result CmpFrame by combining the two component result
+   Frames and permuting the resulting axes into the required order. */
+               *result = (AstFrame *) astCmpFrame( sub_result1, sub_result2,
+                                                   "", status );
+               astPermAxes( *result, outperm_suff );
+
+/* ADDED BY DSB (5-FEB-2001). Without this, properties of the target frame
+   (most importantly, Domain) are not transferred to the result frame.
+   This results in Frames not matching which should match.
+   =================================================================== */
+
+/* If the result CmpFrame includes all the axes of the target CmpFrame,
+   then it should inherit any Domain and Title attributes set in the target
+   CmpFrame. */
+               if( result_naxes == naxes ) {
+
+                  if( astTestDomain( target ) ) {
+                     astSetDomain( *result, astGetDomain( target ) );
+                  }
+
+                  if( astTestTitle( target ) ) {
+                     astSetTitle( *result, astGetTitle( target ) );
+                  }
+               }
+
+/* End of DSB insertion (5/2/01).
+   =================================================================== */
+            }
+
+/* Free the temporary permutation arrays. */
+            inperm_pref = astFree( inperm_pref );
+            inperm_suff = astFree( inperm_suff );
+            outperm_pref = astFree( outperm_pref );
+            outperm_suff = astFree( outperm_suff );
+         }
+
+/* Annul the Mapping and Frame pointers obtained from each component
+   Frame. */
+         if( sub_map1 ) sub_map1 = astAnnul( sub_map1 );
+         if( sub_map2 ) sub_map2 = astAnnul( sub_map2 );
+         if( sub_result1 ) sub_result1 = astAnnul( sub_result1 );
+         if( sub_result2 ) sub_result2 = astAnnul( sub_result2 );
+      }
+   }
+
+/* Free the workspace used to store the choice of component Frame and the
+   axis indices for each component Frame. */
+   frame_choice = astFree( frame_choice );
+   target_axes1 = astFree( target_axes1 );
+   target_axes2 = astFree( target_axes2 );
+
+/* If necessary, also free the memory used for the template axis
+   indices. */
+   if ( template ) {
+      template_axes1 = astFree( template_axes1 );
+      template_axes2 = astFree( template_axes2 );
+   }
+
+/* If an error occurred, clean up by annulling the result pointers and
+   returning appropriate null values. */
+   if ( !astOK ) {
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astTestAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a CmpFrame's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   AstFrame *pfrm;               /* Pointer to primary Frame containing axis */
+   char buf1[80];                /* For for un-indexed attribute name */
+   char buf2[80];                /* For for indexed attribute name */
+   int axis;                     /* Supplied (1-base) axis index */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* Length of string used so far */
+   int oldrep;                   /* Original error reporting state */
+   int paxis;                    /* Index of primary Frame axis */
+   int result;                   /* Result value to return */
+   int ok;                       /* Has the attribute been accessed succesfully? */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Indicate we have not yet acessed the attribute succesfully. */
+   ok = 0;
+
+/* First check the supplied attribute name against each of the attribute
+   names defined by this class. In fact there is nothing to do here
+   since the CmpFrame class currently defines no extra attributes, but
+   this may change in the future. */
+   if( 0 ) {
+
+
+
+/* If the attribute is not a CmpFrame specific attribute... */
+   } else if( astOK ) {
+
+/* We want to allow easy access to the attributes of the component Frames.
+   That is, we do not want it to be necessary to extract a Frame from
+   its parent CmpFrame in order to access its attributes. For this reason
+   we first temporarily switch off error reporting so that if an attempt
+   to access the attribute fails, we can try a different approach. */
+      oldrep = astReporting( 0 );
+
+/* Our first attempt is to see if the attribute is recognised by the parent
+   class (Frame). */
+      result = (*parent_testattrib)( this_object, attrib, status );
+
+/* Indicate success. */
+      if( astOK ) {
+         ok = 1;
+
+/* Otherwise, clear the error condition so that we can try a different
+   approach. */
+      } else {
+         astClearStatus;
+
+/* If the attribute is qualified by an axis index, try accessing it as an
+   attribute of the primary Frame containing the specified index. */
+         if ( nc = 0,
+             ( 2 == astSscanf( attrib, "%[^(](%d)%n", buf1, &axis, &nc ) )
+             && ( nc >= len ) ) {
+
+/* Find the primary Frame containing the specified axis. */
+            astPrimaryFrame( this, axis - 1, &pfrm, &paxis );
+            if( astOK ) {
+
+/* astPrimaryFrame returns the original - unpermuted - axis index within
+   the primary Frame. So we need to take into account any axis permutation
+   which has been applied to the primary Frame when forming the attribute name
+   to use below. Find the permuted (external) axis index which corresponds to
+   the internal (unpermuted) axis index "paxis". */
+               paxis = astValidateAxis( pfrm, paxis, 0, "astTest" );
+
+/* Create a new attribute with the same name but with the axis index
+   appropriate to the primary Frame. */
+               sprintf( buf2, "%s(%d)", buf1, paxis + 1 );
+
+/* Attempt to access the attribute. */
+               result = astTestAttrib( pfrm, buf2 );
+
+/* Indicate success. */
+               if( astOK ) {
+                  ok = 1;
+
+/* Otherwise clear the status value, and try again without any axis index. */
+               } else {
+                  astClearStatus;
+                  result = astTestAttrib( pfrm, buf1 );
+
+/* Indicate success, or clear the status value. */
+                  if( astOK ) {
+                     ok = 1;
+                  } else {
+                     astClearStatus;
+                  }
+               }
+
+/* Free the primary frame pointer. */
+               pfrm = astAnnul( pfrm );
+            }
+
+/* If the attribute is not qualified by an axis index, try accessing it
+   using the primary Frame of each axis in turn. */
+         } else {
+
+/* Loop round all axes, until one is found which defines the specified
+   attribute. */
+	    for( axis = 0; axis < astGetNaxes( this ) && !ok; axis++ ) {
+
+/* Get the primary Frame containing this axis. */
+               astPrimaryFrame( this, axis, &pfrm, &paxis );
+
+/* Attempt to access the attribute as an attribute of the primary Frame. */
+               result = astTestAttrib( pfrm, attrib );
+
+/* Indicate success, or clear the status value. */
+               if( astOK ) {
+                  ok = 1;
+               } else {
+                  astClearStatus;
+               }
+
+/* Free the primary Frame pointer. */
+               pfrm = astAnnul( pfrm );
+
+            }
+         }
+      }
+
+/* Re-instate the original error reporting state. */
+      astReporting( oldrep );
+
+   }
+
+/* Report an error if the attribute could not be accessed. */
+   if( !ok && astOK ) {
+      astError( AST__BADAT, "astTest: The %s given does not have an attribute "
+                "called \"%s\".", status, astGetClass( this ), attrib );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astTransform method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function takes a CmpFrame and a set of points encapsulated
+*     in a PointSet, and applies the coordinate transformation equivalent
+*     to the CmpFrame (this will normally be a UnitMap but may not be if
+*     the CmpFrame contains any Regions).
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     - The number of coordinate values per point in the input
+*     PointSet must match the number of axes in the CmpFrame.
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points and coordinate values per point to
+*     accommodate the result (e.g. the number of CmpFrame axes). Any
+*     excess space will be ignored.
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;        /* Pointer to original CmpFrame structure */
+   AstCmpMap *map2;          /* Intermediate Mapping */
+   AstCmpMap *map;           /* Equivalent Mapping */
+   AstPermMap *pmap;         /* Intermediate PermMap */
+   AstPointSet *result;      /* Pointer value to return */
+   const int *inperm;        /* Pointer to axis permutation array */
+   int *outperm;             /* Pointer to inverse axis permutation array */
+   int i;                    /* External axis index */
+   int naxes;                /* Number of axes in CmpFrame */
+   int perm;                 /* Is there an axis permutation to undo? */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_mapping;
+
+/* Form a parallel CmpMap from the two component Frames. */
+   map = astCmpMap( this->frame1, this->frame2, 0, "", status );
+
+/* The above CmpMap does not take into account any axis permutation
+   which has been applied to the CmpFrame as a whole (as opposed to axis
+   permutations applied to the individual component Frames, which are taken
+   care of by the Transform methods of the individual Frames). Therefore
+   we need to modify the Mapping by adding a PermMap at the start which
+   converts from external axis numbering to internal axis numbering, and a
+   corresponding PermMap at the end which converts from internal to external
+   axis numbering. Obtain the number of axes in the CmpFrame */
+   naxes = astGetNaxes( this );
+
+/* Obtain a pointer to the CmpFrame's axis permutation array. This
+   contains internal axis numbers and is indexed by external axis number. */
+   inperm = astGetPerm( this );
+
+/* Check if there is any axis permutation to be performed. */
+   perm = 0;
+   for( i = 0; i < naxes; i++ ) {
+      if( inperm[ i ] != i ) {
+         perm = 1;
+         break;
+      }
+   }
+
+/* If so, create an array holding the inverse permutation - one which
+   contains external axis numbers and is indexed by internal axis number. */
+   if( perm ) {
+      outperm = astMalloc( sizeof( int )*(size_t) naxes );
+      if( astOK ) for( i = 0; i < naxes; i++ ) outperm[ inperm[ i ] ] = i;
+
+/* Create a PermMap from these permutation arrays. The forward
+   transformation maps from external axis indices to internal axis
+   indices. */
+      pmap = astPermMap( naxes, inperm, naxes, outperm, NULL, "", status );
+      outperm = astFree( outperm );
+
+/* Combine this PermMap with the CmpMap created above, adding it in the
+   forward direction at the start and in the inverse direction at the end. */
+      map2 = astCmpMap( pmap, map, 1, "", status );
+      map = astAnnul( map );
+      astInvert( pmap );
+      map = astCmpMap( map2, pmap, 1, "", status );
+      map2 = astAnnul( map2 );
+      pmap = astAnnul( pmap );
+
+   }
+
+/* Apply the Mapping to the input PointSet. */
+   result = astTransform( map, in, forward, out );
+
+/* Annul the Mapping pointer. */
+   map = astAnnul( map );
+
+/* If an error has occurred and a new PointSet may have been created, then
+   clean up by annulling it. In any case, ensure that a NULL result is
+   returned.*/
+   if ( !astOK ) {
+      if ( !out ) result = astAnnul( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static int Unformat( AstFrame *this_frame, int axis, const char *string,
+                     double *value, int *status ) {
+/*
+*  Name:
+*     Unformat
+
+*  Purpose:
+*     Read a formatted coordinate value for a CmpFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int Unformat( AstFrame *this, int axis, const char *string,
+*                   double *value, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the public astUnformat
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function reads a formatted coordinate value for a CmpFrame
+*     axis (supplied as a string) and returns the equivalent numerical
+*     value as a double. It also returns the number of characters read
+*     from the string.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame.
+*     axis
+*        The number of the CmpFrame axis for which the coordinate
+*        value is to be read (axis numbering starts at zero for the
+*        first axis).
+*     string
+*        Pointer to a constant null-terminated string containing the
+*        formatted coordinate value.
+*     value
+*        Pointer to a double in which the coordinate value read will be
+*        returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of characters read from the string to obtain the
+*     coordinate value.
+
+*  Notes:
+*     - Any white space at the beginning of the string will be
+*     skipped, as also will any trailing white space following the
+*     coordinate value read. The function's return value will reflect
+*     this.
+*     - A function value of zero (and no coordinate value) will be
+*     returned, without error, if the string supplied does not contain
+*     a suitably formatted value.
+*     - The string "<bad>" is recognised as a special case and will
+*     generate the value AST__BAD, without error. The test for this
+*     string is case-insensitive and permits embedded white space.
+*     - A function result of zero will be returned and no coordinate
+*     value will be returned via the "value" pointer if this function
+*     is invoked with the global error status set, or if it should
+*     fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+   AstFrame *frame;              /* Pointer to Frame containing axis */
+   double coord;                 /* Coordinate value read */
+   int naxes1;                   /* Number of axes in frame1 */
+   int nc;                       /* Number of characters read */
+   int set;                      /* Digits attribute set? */
+
+/* Initialise. */
+   nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return nc;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_frame;
+
+/* Validate and permute the axis index supplied. */
+   axis = astValidateAxis( this, axis, 1, "astUnformat" );
+
+/* Determine the number of axes in the first component Frame. */
+   naxes1 = astGetNaxes( this->frame1 );
+   if ( astOK ) {
+
+/* Decide which component Frame contains the axis and adjust the axis
+   index if necessary. */
+      frame = ( axis < naxes1 ) ? this->frame1 : this->frame2;
+      axis = ( axis < naxes1 ) ? axis : axis - naxes1;
+
+/* Since the component Frame is "managed" by the enclosing CmpFrame,
+   we next test if any Frame attributes which may affect the result
+   are undefined (i.e. have not been explicitly set). If so, we
+   over-ride them, giving them temporary values dictated by the
+   CmpFrame. Only the Digits attribute is potentially relevant
+   here. */
+      set = astTestDigits( frame );
+      if ( !set ) astSetDigits( frame, astGetDigits( this ) );
+
+/* Invoke the Frame's astUnformat method to read the coordinate value. */
+      nc = astUnformat( frame, axis, string, &coord );
+
+/* Clear Frame attributes which were temporarily over-ridden. */
+      if ( !set ) astClearDigits( frame );
+   }
+
+/* If an error occurred, clear the number of characters read. */
+   if ( !astOK ) {
+      nc = 0;
+
+/* Otherwise, if characters were read, return the coordinate value. */
+   } else if ( nc ) {
+      *value = coord;
+   }
+
+/* Return the number of chracters read. */
+   return nc;
+}
+
+static int ValidateSystem( AstFrame *this, AstSystemType system, const char *method, int *status ) {
+/*
+*
+*  Name:
+*     ValidateSystem
+
+*  Purpose:
+*     Validate a value for a CmpFrame's System attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     int ValidateSystem( AstFrame *this, AstSystemType system,
+*                         const char *method, int *status )
+
+*  Class Membership:
+*     CmpFrame member function (over-rides the astValidateSystem method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of the supplied system value.
+*     If the value is valid, it is returned unchanged. Otherwise, an
+*     error is reported and a value of AST__BADSYSTEM is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The system value to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The validated system value.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;              /* Validated system value */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the value is out of bounds, report an error. */
+   if ( system < FIRST_SYSTEM || system > LAST_SYSTEM ) {
+         astError( AST__AXIIN, "%s(%s): Bad value (%d) given for the System "
+                   "or AlignSystem attribute of a %s.", status, method,
+                   astGetClass( this ), (int) system, astGetClass( this ) );
+
+/* Otherwise, return the supplied value. */
+   } else {
+      result = system;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   the axes of a CmpFrame using the private macros defined for this
+   purpose at the start of this file. */
+
+/* Direction(axis). */
+/* ---------------- */
+MAKE_CLEAR(Direction)
+MAKE_GET(Direction,int,0,0,0)
+MAKE_SET(Direction,int)
+MAKE_TEST(Direction)
+
+/* Format(axis). */
+/* ------------- */
+MAKE_CLEAR(Format)
+MAKE_GET(Format,const char *,NULL,0,NULL)
+MAKE_SET(Format,const char *)
+MAKE_TEST(Format)
+
+/* Label(axis). */
+/* ------------ */
+MAKE_CLEAR(Label)
+
+/* Over-ride the default axis labels produced by Frame class objects
+   and substitute the axis numbering of the enclosing CmpFrame
+   instead. */
+static const char *label_class;
+MAKE_GET(Label,const char *,NULL,( label_class = astGetClass( frame ),
+                                   ( astOK && !strcmp( label_class,
+                                                       "Frame" ) ) ),
+         ( (void) sprintf( label_buff, "Axis %d", axis + 1 ), label_buff ))
+MAKE_SET(Label,const char *)
+MAKE_TEST(Label)
+
+/* Symbol(axis). */
+/* ------------- */
+MAKE_CLEAR(Symbol)
+
+/* Over-ride the default axis symbols produced by Frame class objects
+   and substitute the axis numbering of the enclosing CmpFrame
+   instead. */
+static const char *symbol_class;
+MAKE_GET(Symbol,const char *,NULL,( symbol_class = astGetClass( frame ),
+                                    ( astOK && !strcmp( symbol_class,
+                                                        "Frame" ) ) ),
+         ( (void) sprintf( symbol_buff, "x%d", axis + 1 ), symbol_buff ))
+MAKE_SET(Symbol,const char *)
+MAKE_TEST(Symbol)
+
+/* Unit(axis). */
+/* ----------- */
+MAKE_CLEAR(Unit)
+MAKE_GET(Unit,const char *,NULL,0,NULL)
+MAKE_SET(Unit,const char *)
+MAKE_TEST(Unit)
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for CmpFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for CmpFrame objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *in;              /* Pointer to input CmpFrame */
+   AstCmpFrame *out;             /* Pointer to output CmpFrame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output CmpFrames. */
+   in = (AstCmpFrame *) objin;
+   out = (AstCmpFrame *) objout;
+
+/* Copy the two component Frames. */
+   out->frame1 = astCopy( in->frame1 );
+   out->frame2 = astCopy( in->frame2 );
+
+/* Determine the number of axes and copy the axis permutation
+   array. */
+   out->perm = astStore( NULL, in->perm, sizeof( int ) *
+                         (size_t) GetNaxes( (AstFrame *) in, status ) );
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for CmpFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for CmpFrame objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error
+*     status is set.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to CmpFrame structure */
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) obj;
+
+/* Annul the two component Frame pointers. */
+   if ( this->frame1 ) this->frame1 = astAnnul( this->frame1 );
+   if ( this->frame2 ) this->frame2 = astAnnul( this->frame2 );
+
+/* Free the axis permutation array. */
+   if ( this->perm ) this->perm = astFree( this->perm );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for CmpFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the CmpFrame class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpFrame whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define COMMENT_LEN 150          /* Maximum length of a comment string */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstCmpFrame *this;            /* Pointer to the CmpFrame structure */
+   char comment[ COMMENT_LEN + 1 ]; /* Buffer for comment strings */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keywords */
+   int axis;                     /* Loop counter for CmpFrame axes */
+   int full;                     /* Full attribute value */
+   int full_set;                 /* Full attribute set? */
+   int ival;                     /* Integer value */
+   int naxes;                    /* Number of CmpFrame axes */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpFrame structure. */
+   this = (AstCmpFrame *) this_object;
+
+/* Write out values representing the instance variables for the
+   CmpFrame class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Axis permutation array. */
+/* ----------------------- */
+/* Obtain the number of CmpFrame axes. */
+   naxes = GetNaxes( (AstFrame *) this, status );
+
+/* Write out the CmpFrame axis permutation array value for each axis,
+   converting to 1-based axis numbering. */
+   for ( axis = 0; axis < naxes; axis++ ) {
+      set = ( this->perm[ axis ] != axis );
+      ival = this->perm[ axis ] + 1;
+
+/* Create a keyword and comment appropriate to the axis. */
+      (void) sprintf( key, "Axp%d", axis + 1 );
+      if ( set ) {
+         (void) sprintf( comment,
+                         "Axis %d permuted to use internal axis %d",
+                         axis + 1, ival );
+      } else {
+         (void) sprintf( comment, "Axis %d not permuted", axis + 1 );
+      }
+      astWriteInt( channel, key, set, 0, ival, comment );
+   }
+
+/* Component Frames. */
+/* ----------------- */
+/* Temporarily set the Channel's Full attribute to -1 (unless it is +1
+   to start with), remembering the original setting. This prevents any
+   unnecessary "un-set" Frame values being output that would otherwise
+   simply duplicate the CmpFrame's attributes which have already been
+   written. "Set" Frame values are still written, however (and all
+   values are written if Full is set to 1). */
+   full_set = astTestFull( channel );
+   full = astGetFull( channel );
+   if ( full <= 0 ) astSetFull( channel, -1 );
+
+/* Write out Object descriptions for the two component Frames. */
+   astWriteObject( channel, "FrameA", 1, 1, this->frame1,
+                   "First component Frame" );
+   astWriteObject( channel, "FrameB", 1, 1, this->frame2,
+                   "Second component Frame" );
+
+/* Restore the Channel's original Full attribute setting. */
+   if ( full_set ) {
+      astSetFull( channel, full );
+   } else {
+      astClearFull( channel );
+   }
+
+/* Undefine macros local to this function. */
+#undef COMMENT_LEN
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsACmpFrame and astCheckCmpFrame functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(CmpFrame,Frame)
+astMAKE_CHECK(CmpFrame)
+
+AstCmpFrame *astCmpFrame_( void *frame1_void, void *frame2_void,
+                           const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astCmpFrame
+f     AST_CMPFRAME
+
+*  Purpose:
+*     Create a CmpFrame.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "cmpframe.h"
+c     AstCmpFrame *astCmpFrame( AstFrame *frame1, AstFrame *frame2,
+c                               const char *options, ... )
+f     RESULT = AST_CMPFRAME( FRAME1, FRAME2, OPTIONS, STATUS )
+
+*  Class Membership:
+*     CmpFrame constructor.
+
+*  Description:
+*     This function creates a new CmpFrame and optionally initialises
+*     its attributes.
+*
+*     A CmpFrame is a compound Frame which allows two component Frames
+*     (of any class) to be merged together to form a more complex
+*     Frame. The axes of the two component Frames then appear together
+*     in the resulting CmpFrame (those of the first Frame, followed by
+*     those of the second Frame).
+*
+*     Since a CmpFrame is itself a Frame, it can be used as a
+*     component in forming further CmpFrames. Frames of arbitrary
+*     complexity may be built from simple individual Frames in this
+*     way.
+*
+*     Also since a Frame is a Mapping, a CmpFrame can also be used as a
+*     Mapping. Normally, a CmpFrame is simply equivalent to a UnitMap,
+*     but if either of the component Frames within a CmpFrame is a Region
+*     (a sub-class of Frame), then the CmpFrame will use the Region as a
+*     Mapping when transforming values for axes described by the Region.
+*     Thus input axis values corresponding to positions which are outside the
+*     Region will result in bad output axis values.
+
+*  Parameters:
+c     frame1
+f     FRAME1 = INTEGER (Given)
+*        Pointer to the first component Frame.
+c     frame2
+f     FRAME2 = INTEGER (Given)
+*        Pointer to the second component Frame.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new CmpFrame. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new CmpFrame. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astCmpFrame()
+f     AST_CMPFRAME = INTEGER
+*        A pointer to the new CmpFrame.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+
+*  Implementation Notes:
+*     - This function implements the basic CmpFrame constructor which
+*     is available via the protected interface to the CmpFrame class.
+*     A public interface is provided by the astCmpFrameId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "frame1" and "frame2" parameters are of type (void *) and are
+*     converted and validated within the function itself.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstCmpFrame *new;             /* Pointer to new CmpFrame */
+   AstFrame *frame1;             /* Pointer to first Frame structure */
+   AstFrame *frame2;             /* Pointer to second Frame structure */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   new = NULL;
+   if ( !astOK ) return new;
+
+/* Obtain and validate pointers to the Frame structures provided. */
+   frame1 = astCheckFrame( frame1_void );
+   frame2 = astCheckFrame( frame2_void );
+   if ( astOK ) {
+
+/* Initialise the CmpFrame, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitCmpFrame( NULL, sizeof( AstCmpFrame ), !class_init,
+                             &class_vtab, "CmpFrame", frame1, frame2 );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   CmpFrame's attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new CmpFrame. */
+   return new;
+}
+
+AstCmpFrame *astCmpFrameId_( void *frame1_void, void *frame2_void,
+                             const char *options, ... ) {
+/*
+*  Name:
+*     astCmpFrameId_
+
+*  Purpose:
+*     Create a CmpFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstCmpFrame *astCmpFrameId_( void *frame1_void, void *frame2_void,
+*                                  const char *options, ... )
+
+*  Class Membership:
+*     CmpFrame constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astCmpFrame constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astCmpFrame_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur). For the same reason, the "frame1" and "frame2"
+*     parameters are of type (void *) and are converted and validated
+*     within the function itself.
+*
+*     The variable argument list also prevents this function from
+*     invoking astCmpFrame_ directly, so it must be a
+*     re-implementation of it in all respects, except for the final
+*     conversion of the result to an ID value.
+
+*  Parameters:
+*     As for astCmpFrame_.
+
+*  Returned Value:
+*     The ID value associated with the new CmpFrame.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstCmpFrame *new;             /* Pointer to new CmpFrame */
+   AstFrame *frame1;             /* Pointer to first Frame structure */
+   AstFrame *frame2;             /* Pointer to second Frame structure */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   new = NULL;
+   if ( !astOK ) return new;
+
+/* Obtain the Frame pointers from the ID's supplied and validate the
+   pointers to ensure they identify valid Frames. */
+   frame1 = astVerifyFrame( astMakePointer( frame1_void ) );
+   frame2 = astVerifyFrame( astMakePointer( frame2_void ) );
+   if ( astOK ) {
+
+/* Initialise the CmpFrame, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitCmpFrame( NULL, sizeof( AstCmpFrame ), !class_init,
+                             &class_vtab, "CmpFrame", frame1, frame2 );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   CmpFrame's attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return an ID value for the new CmpFrame. */
+   return astMakeId( new );
+}
+
+AstCmpFrame *astInitCmpFrame_( void *mem, size_t size, int init,
+                               AstCmpFrameVtab *vtab, const char *name,
+                               AstFrame *frame1, AstFrame *frame2, int *status ) {
+/*
+*+
+*  Name:
+*     astInitCmpFrame
+
+*  Purpose:
+*     Initialise a CmpFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstCmpFrame *astInitCmpFrame( void *mem, size_t size, int init,
+*                                   AstCmpFrameVtab *vtab, const char *name,
+*                                   AstFrame *frame1, AstFrame *frame2 )
+
+*  Class Membership:
+*     CmpFrame initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new CmpFrame object. It allocates memory (if
+*     necessary) to accommodate the CmpFrame plus any additional data
+*     associated with the derived class.  It then initialises a
+*     CmpFrame structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual
+*     function table for a CmpFrame at the start of the memory passed
+*     via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the CmpFrame is to be
+*        created. This must be of sufficient size to accommodate the
+*        CmpFrame data (sizeof(CmpFrame)) plus any data used by the
+*        derived class. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the CmpFrame (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the CmpFrame structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*     init
+*        A logical flag indicating if the CmpFrame's virtual function
+*        table is to be initialised. If this value is non-zero, the
+*        virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new CmpFrame.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the Object astClass function).
+*     frame1
+*        Pointer to the first Frame to be included in the new CmpFrame.
+*     frame2
+*        Pointer to the second Frame to be included in the new CmpFrame.
+
+*  Returned Value:
+*     A pointer to the new CmpFrame.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstCmpFrame *new;             /* Pointer to new CmpFrame */
+   int axis;                     /* Loop counter for axes */
+   int naxes;                    /* Number of CmpFrame axes */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitCmpFrameVtab( vtab, name );
+
+/* Initialise a Frame structure (the parent class) as the first
+   component within the CmpFrame structure, allocating memory if
+   necessary. Set the number of Frame axes to zero, since all axis
+   information is stored within the component Frames. */
+   new = (AstCmpFrame *) astInitFrame( mem, size, 0, (AstFrameVtab *) vtab,
+                                       name, 0 );
+
+   if ( astOK ) {
+
+/* Initialise the CmpFrame data. */
+/* ----------------------------- */
+/* Clone the component Frame pointers. */
+      new->frame1 = astClone( frame1 );
+      new->frame2 = astClone( frame2 );
+
+/* Determine the number of CmpFrame axes. */
+      naxes = astGetNaxes( frame1 ) + astGetNaxes( frame2 );
+
+/* Allocate memory to hold the axis permutation array and initialise
+   this array. */
+      new->perm = astMalloc( sizeof( int ) * (size_t) naxes );
+      if ( astOK ) {
+         for ( axis = 0; axis < naxes; axis++ ) new->perm[ axis ] = axis;
+      }
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstCmpFrame *astLoadCmpFrame_( void *mem, size_t size,
+                               AstCmpFrameVtab *vtab, const char *name,
+                               AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadCmpFrame
+
+*  Purpose:
+*     Load a CmpFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpframe.h"
+*     AstCmpFrame *astLoadCmpFrame( void *mem, size_t size,
+*                                   AstCmpFrameVtab *vtab, const char *name,
+*                                   AstChannel *channel )
+
+*  Class Membership:
+*     CmpFrame loader.
+
+*  Description:
+*     This function is provided to load a new CmpFrame using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     CmpFrame structure in this memory, using data read from the
+*     input Channel.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the CmpFrame is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        CmpFrame data (sizeof(CmpFrame)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the CmpFrame (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the CmpFrame structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstCmpFrame) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new CmpFrame. If this is NULL, a pointer
+*        to the (static) virtual function table for the CmpFrame class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "CmpFrame" is used instead.
+
+*  Returned Value:
+*     A pointer to the new CmpFrame.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstCmpFrame *new;             /* Pointer to the new CmpFrame */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keywords */
+   int axis;                     /* Loop counter for axes */
+   int naxes;                    /* Number of CmpFrame axes */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this CmpFrame. In this case the
+   CmpFrame belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstCmpFrame );
+      vtab = &class_vtab;
+      name = "CmpFrame";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitCmpFrameVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built CmpFrame. */
+   new = astLoadFrame( mem, size, (AstFrameVtab *) vtab, name,
+                       channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "CmpFrame" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Component Frames. */
+/* ----------------- */
+/* Read both component Frames, supplying a default 1-dimensional Frame
+   if necessary. */
+      new->frame1 = astReadObject( channel, "framea", NULL );
+      if ( !new->frame1 ) new->frame1 = astFrame( 1, "", status );
+
+      new->frame2 = astReadObject( channel, "frameb", NULL );
+      if ( !new->frame2 ) new->frame2 = astFrame( 1, "", status );
+
+/* Axis permutation array. */
+/* ----------------------- */
+/* Obtain the number of CmpFrame axes and allocate memory to hold the
+   axis permutation array. */
+      naxes = GetNaxes( (AstFrame *) new, status );
+      new->perm = astMalloc( sizeof( int ) * (size_t) naxes );
+
+/* If OK, loop to read the array value for each axis. */
+      if ( astOK ) {
+         for ( axis = 0; axis < naxes; axis++ ) {
+
+/* Convert from 1-based to zero-based axis numbering at this
+   point. The default is the "un-permuted" value. */
+            sprintf( key, "axp%d", axis + 1 );
+            new->perm[ axis ] = astReadInt( channel, key, axis + 1 ) - 1;
+
+/* Quit looping if an error occurs. */
+            if ( !astOK ) break;
+         }
+      }
+
+/* If an error occurred, clean up by deleting the new CmpFrame. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new CmpFrame pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
diff --git a/ast/cmpframe.h b/ast/cmpframe.h
new file mode 100644
index 0000000..f692aa1
--- /dev/null
+++ b/ast/cmpframe.h
@@ -0,0 +1,428 @@
+#if !defined( CMPFRAME_INCLUDED ) /* Include this file only once */
+#define CMPFRAME_INCLUDED
+/*
+*+
+*  Name:
+*     cmpframe.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the CmpFrame class.
+
+*  Invocation:
+*     #include "cmpframe.h"
+
+*  Description:
+*     This include file defines the interface to the CmpFrame class
+*     and provides the type definitions, function prototypes and
+*     macros, etc. needed to use this class.
+*
+*     A CmpFrame is a compound Frame which allows two component Frames
+*     (of any class) to be merged together to form a more complex
+*     Frame. The axes of the two component Frames then appear together
+*     in the resulting CmpFrame (those of the first Frame, followed by
+*     those of the second Frame).
+*
+*     Since a CmpFrame is itself a Frame, it can be used as a
+*     component in forming further CmpFrames. Frames of arbitrary
+*     complexity may be built from simple individual Frames in this
+*     way.
+
+*  Inheritance:
+*     The CmpFrame class inherits from the Frame class.
+
+*  Attributes Over-Ridden:
+*     Domain (string)
+*        A string which may be used to identify a CmpFrame and used as
+*        an additional key when matching a target CmpFrame with a
+*        template.  The CmpFrame class re-defines the default value to
+*        "CMP".
+*     MaxAxes (integer)
+*        Specifies the maximum number of axes in a target Frame that
+*        can be matched when using the CmpFrame as a template. The
+*        CmpFrame class sets this to be the sum of the MaxAxes
+*        attribute values for the two component Frames contained by
+*        the CmpFrame. Any attempt to alter this value (other than
+*        through the component Frames) is simply ignored.
+*     MinAxes (integer)
+*        Specifies the minimum number of axes in a target Frame that
+*        can be matched when using the CmpFrame as a template. The
+*        CmpFrame class sets this to be the sum of the MinAxes
+*        attribute values for the two component Frames contained by
+*        the CmpFrame. Any attempt to alter this value (other than
+*        through the component Frames) is simply ignored.
+*     Naxes (integer)
+*        A read-only attribute that gives the number of axes in a
+*        CmpFrame (i.e. the number of dimensions of the space which
+*        the CmpFrame describes). This value is determined when the
+*        CmpFrame is created and is equal to the sum of the Naxes
+*        attributes of the two component Frames.
+*     Title (string)
+*        Specifies a string to be used as a title on (e.g.) graphs to
+*        describe the coordinate system which the CmpFrame
+*        represents. The CmpFrame class re-defines the default value
+*        to "<n>-D Compound Coordinate System", where <n> is the
+*        number of CmpFrame axes.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        astDistance
+*           Calculate the distance between two points.
+*        astFormat
+*           Format a coordinate value for a CmpFrame axis.
+*        astNorm
+*           Normalise a set of CmpFrame coordinates.
+*        astOffset
+*           Calculate an offset along a geodesic curve.
+*        astPermAxes
+*           Permute the order of a CmpFrame's axes.
+*        astUnformat
+*           Read a formatted coordinate value for a CmpFrame axis.
+*
+*     Protected:
+*        astAbbrev
+*           Abbreviate a formatted CmpFrame axis value by skipping leading
+*           fields.
+*        astClearDirection
+*           Clear the value of the Direction attribute for a CmpFrame axis.
+*        astClearFormat
+*           Clear the value of the Format attribute for a CmpFrame axis.
+*        astClearLabel
+*           Clear the value of the Label attribute for a CmpFrame axis.
+*        astClearMaxAxes
+*           Clear the value of the MaxAxes attribute for a CmpFrame.
+*        astClearMinAxes
+*           Clear the value of the MinAxes attribute for a CmpFrame.
+*        astClearSymbol
+*           Clear the value of the Symbol attribute for a CmpFrame axis.
+*        astClearUnit
+*           Clear the value of the Unit attribute for a CmpFrame axis.
+*        astGap
+*           Find a "nice" gap for tabulating CmpFrame axis values.
+*        astGetAxis
+*           Obtain a pointer to a specified Axis from a CmpFrame.
+*        astGetDirection
+*           Obtain the value of the Direction attribute for a CmpFrame axis.
+*        astGetDomain
+*           Obtain a pointer to the Domain attribute string for a CmpFrame.
+*        astGetFormat
+*           Obtain the value of the Format attribute for a CmpFrame axis.
+*        astGetLabel
+*           Obtain the value of the Label attribute for a CmpFrame axis.
+*        astGetMaxAxes
+*           Obtain the value of the MaxAxes attribute for a CmpFrame.
+*        astGetMinAxes
+*           Obtain the value of the MinAxes attribute for a CmpFrame.
+*        astGetNaxes
+*           Obtain the value of the Naxes attribute for a CmpFrame.
+*        astGetPerm
+*           Access the axis permutation array for a CmpFrame.
+*        astGetSymbol
+*           Obtain the value of the Symbol attribute for a CmpFrame axis.
+*        astGetTitle
+*           Obtain a pointer to the Title attribute string for a CmpFrame.
+*        astGetUnit
+*           Obtain the value of the Unit attribute for a CmpFrame axis.
+*        astMatch
+*           Determine if conversion is possible between two coordinate
+*           systems.
+*        astPrimaryFrame
+*           Uniquely identify a primary Frame and one of its axes.
+*        astSetAxis
+*           Set a new Axis for a CmpFrame.
+*        astSetDirection
+*           Set the value of the Direction attribute for a CmpFrame axis.
+*        astSetFormat
+*           Set the value of the Format attribute for a CmpFrame axis.
+*        astSetLabel
+*           Set the value of the Label attribute for a CmpFrame axis.
+*        astSetMaxAxes
+*           Set a value for the MaxAxes attribute of a CmpFrame.
+*        astSetMinAxes
+*           Set a value for the MinAxes attribute of a CmpFrame.
+*        astSetSymbol
+*           Set the value of the Symbol attribute for a CmpFrame axis.
+*        astSetUnit
+*           Set the value of the Unit attribute for a CmpFrame axis.
+*        astSubFrame
+*           Select axes from a CmpFrame and convert to the new coordinate
+*           system.
+*        astTestDirection
+*           Test if a Direction attribute value has been set for a CmpFrame
+*           axis.
+*        astTestFormat
+*           Test if a Format attribute value has been set for a CmpFrame axis.
+*        astTestLabel
+*           Test if a Label attribute value has been set for a CmpFrame axis.
+*        astTestMaxAxes
+*           Test if a value has been set for the MaxAxes attribute of a
+*           CmpFrame.
+*        astTestMinAxes
+*           Test if a value has been set for the MinAxes attribute of a
+*           CmpFrame.
+*        astTestSymbol
+*           Test if a Symbol attribute value has been set for a CmpFrame axis.
+*        astTestUnit
+*           Test if a Unit attribute value has been set for a CmpFrame axis.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsACmpFrame
+*           Test class membership.
+*        astCmpFrame
+*           Create a CmpFrame.
+*
+*     Protected:
+*        astCheckCmpFrame
+*           Validate class membership.
+*        astInitCmpFrame
+*           Initialise a CmpFrame.
+*        astInitCmpFrameVtab
+*           Initialise the virtual function table for the CmpFrame class.
+*        astLoadCmpFrame
+*           Load a CmpFrame.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstCmpFrame
+*           CmpFrame object type.
+*
+*     Protected:
+*        AstCmpFrameVtab
+*           CmpFrame virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     6-MAR-1996 (RFWS):
+*        Original version.
+*     27-FEB-1997 (RFWS):
+*        Improved the prologue.
+*     25-FEB-1998 (RFWS):
+*        Over-ride the astUnformat method.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitCmpFrameVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+#include "frame.h"               /* Parent Frame class */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros. */
+/* ------- */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* The legal System values recognized by this class of Frame. */
+#define AST__COMP 0
+
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* CmpFrame structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstCmpFrame {
+
+/* Attributes inherited from the parent class. */
+   AstFrame frame;               /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstFrame *frame1;             /* First component frame */
+   AstFrame *frame2;             /* Second component Frame */
+   int *perm;                    /* Pointer to axis permutation array */
+} AstCmpFrame;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstCmpFrameVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstFrameVtab frame_vtab;      /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+
+} AstCmpFrameVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstCmpFrameGlobals {
+   AstCmpFrameVtab Class_Vtab;
+   int Class_Init;
+   int *qsort_axes;
+   char Label_Buff[ 101 ];
+   char Symbol_Buff[ 51 ];
+   char GetDomain_Buff[ 101 ];
+   char GetTitle_Buff[ 101 ];
+} AstCmpFrameGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(CmpFrame)         /* Check class membership */
+astPROTO_ISA(CmpFrame)           /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstCmpFrame *astCmpFrame_( void *, void *, const char *, int *, ...);
+#else
+AstCmpFrame *astCmpFrameId_( void *, void *, const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstCmpFrame *astInitCmpFrame_( void *, size_t, int, AstCmpFrameVtab *,
+                               const char *, AstFrame *, AstFrame *, int * );
+
+/* Vtab initialiser. */
+void astInitCmpFrameVtab_( AstCmpFrameVtab *, const char *, int * );
+
+/* Loader. */
+AstCmpFrame *astLoadCmpFrame_( void *, size_t, AstCmpFrameVtab *,
+                               const char *, AstChannel *, int * );
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitCmpFrameGlobals_( AstCmpFrameGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckCmpFrame(this) astINVOKE_CHECK(CmpFrame,this,0)
+#define astVerifyCmpFrame(this) astINVOKE_CHECK(CmpFrame,this,1)
+
+/* Test class membership. */
+#define astIsACmpFrame(this) astINVOKE_ISA(CmpFrame,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astCmpFrame astINVOKE(F,astCmpFrame_)
+#else
+#define astCmpFrame astINVOKE(F,astCmpFrameId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitCmpFrame(mem,size,init,vtab,name,frame1,frame2) \
+astINVOKE(O,astInitCmpFrame_(mem,size,init,vtab,name,astCheckFrame(frame1),astCheckFrame(frame2),STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitCmpFrameVtab(vtab,name) astINVOKE(V,astInitCmpFrameVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadCmpFrame(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadCmpFrame_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckCmpFrame to validate CmpFrame pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#endif
+
+
+
+
+
diff --git a/ast/cmpmap.c b/ast/cmpmap.c
new file mode 100644
index 0000000..d0210ec
--- /dev/null
+++ b/ast/cmpmap.c
@@ -0,0 +1,4739 @@
+/*
+*class++
+*  Name:
+*     CmpMap
+
+*  Purpose:
+*     Compound Mapping.
+
+*  Constructor Function:
+c     astCmpMap
+f     AST_CMPMAP
+
+*  Description:
+*     A CmpMap is a compound Mapping which allows two component
+*     Mappings (of any class) to be connected together to form a more
+*     complex Mapping. This connection may either be "in series"
+*     (where the first Mapping is used to transform the coordinates of
+*     each point and the second mapping is then applied to the
+*     result), or "in parallel" (where one Mapping transforms the
+*     earlier coordinates for each point and the second Mapping
+*     simultaneously transforms the later coordinates).
+*
+*     Since a CmpMap is itself a Mapping, it can be used as a
+*     component in forming further CmpMaps. Mappings of arbitrary
+*     complexity may be built from simple individual Mappings in this
+*     way.
+
+*  Inheritance:
+*     The CmpMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The CmpMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     The CmpMap class does not define any new functions beyond those
+f     The CmpMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     1-FEB-1996 (RFWS):
+*        Original version.
+*     25-SEP-1996 (RFWS):
+*        Implemented external interface and I/O facilities.
+*     12-DEC-1996 (RFWS):
+*        Over-ride the astMapList method.
+*     13-DEC-1996 (RFWS):
+*        Over-ride the astSimplify method.
+*     4-JUN-1997 (RFWS):
+*        Eliminate any simplification when MapList is used. Instead,
+*        over-ride the MapMerge method and implement all
+*        simplification in this.
+*     24-MAR-1998 (RFWS):
+*        Fixed bug in testing of simplified invert flag in Simplify.
+*     15-APR-1998 (RFWS):
+*        Improved the MapMerge method to allow parallel combinations
+*        of series CmpMaps to be replaced by series combinations of
+*        parallel CmpMaps, and vice versa.
+*     26-SEP-2001 (DSB):
+*        Over-ride the astDecompose method.
+*     8-JAN-2003 (DSB):
+*        - Changed private InitVtab method to protected astInitCmpMapVtab
+*        method.
+*     8-JAN-2003 (DSB):
+*        - Modified MapMerge so that a parallel CmpMap can swap with a
+*        suitable PermMap lower neighbour.
+*     23-APR-2004 (DSB):
+*        - Modified Simplify to avoid infinite loops.
+*     27-APR-2004 (DSB):
+*        - Correction to MapMerge to prevent segvio if CmpMap and PermMap
+*        cannot be swapped.
+*     4-OCT-2004 (DSB):
+*        Modify astMapList to return flag indicating presence of inverted
+*        CmpMaps in supplied Mapping.
+*     20-APR-2005 (DSB):
+*        Modify MapMerge so that it will attempt to merge the first
+*        and second CmpMaps in a list of series CmpMaps.
+*     8-FEB-2006 (DSB):
+*        Corrected logic within MapMerge for cases where a PermMap is
+*        followed by a parallel CmpMap.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     14-MAR-2006 (DSB):
+*        - When checking for patterns in the simplification process,
+*        require at least 30 samples in the waveform for evidence of a
+*        pattern.
+*        - Override astEqual.
+*        - The constructor no longer reports an error if the resulting
+*	 CmpMap cannot transform points in either direction. This is
+*	 because it may be possible to simplify such a CmpMap and the
+*	 simplified Mapping may have defined transformations. E.g. if a
+*	 Mapping which has only a forward transformation is combined in
+*	 series with its own inverse, the combination will simplify to a
+*	 UnitMap (usually).
+*     9-MAY-2006 (DSB):
+*        - In Simplify, remove checks for patterns in the number of atomic
+*        mappings when calling astSimplify recursively.
+*     23-AUG-2006 (DSB):
+*        - In Simplify, add checks for re-appearance of a Mapping that is
+*        already being simplified at a higher levelin the call stack.
+*     18-APR-2007 (DSB):
+*        In Simplify: if the returned Mapping is not a CmpMap, always copy
+*        the returned component Mapping (rather than cloning it) so that
+*        the returned Mapping is not affected if user code subsequently
+*        inverts the component Mapping via some other pointer.
+*     12-MAR-2008 (DSB):
+*        Modify MapSplit so that attempts to split the inverse
+*        transformation if it cannot split the forward transformation.
+*     30-JUL-2009 (DSB):
+*        Ensure the PermMap has equal number of inputs and outputs when
+*        swapping a PermMap and a CmpMap in astMapMerge.
+*     3-JAN-2011 (DSB):
+*        In MapSplit, certain classes of Mapping (e.g. PermMaps) can
+*        produce a returned Mapping with zero outputs. Consider such
+*        Mappings to be unsplitable.
+*     11-JAN-2011 (DSB):
+*        Improve simplification of serial combinations of parellel CmpMaps.
+*     25-JAN-2011 (DSB):
+*        Big improvement to the efficiency of the astMapSplit method.
+*     24-JAN-2012 (DSB):
+*        If efficient MapSplit fails to split (e.g. due to the presence
+*        of PermMaps), then revert to the older slower method.
+*     5-FEB-2013 (DSB):
+*        Take account of Invert flags when combining parallel CmpMaps in
+*        series.
+*     29-APR-2013 (DSB):
+*        In MapList, use the astDoNotSimplify method to check that it is
+*        OK to expand the CmpMap.
+*     23-APR-2015 (DSB):
+*        In Simplify, prevent mappings that are known to cause infinite
+*        loops from being nominated for simplification.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS CmpMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "permmap.h"             /* Coordinate permutation Mappings */
+#include "unitmap.h"             /* Unit transformations */
+#include "cmpmap.h"              /* Interface definition for this class */
+#include "frameset.h"            /* Interface definition for FrameSets */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int (* parent_maplist)( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int *(* parent_mapsplit)( AstMapping *, int, const int *, AstMapping **, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->Simplify_Depth = 0; \
+   globals->Simplify_Stackmaps = NULL;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(CmpMap)
+
+#define class_init astGLOBAL(CmpMap,Class_Init)
+#define class_vtab astGLOBAL(CmpMap,Class_Vtab)
+#define simplify_depth astGLOBAL(CmpMap,Simplify_Depth)
+#define simplify_stackmaps astGLOBAL(CmpMap,Simplify_Stackmaps)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static int simplify_depth  = 0;
+static AstMapping **simplify_stackmaps = NULL;
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstCmpMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstCmpMap *astCmpMapId_( void *, void *, int, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *CombineMaps( AstMapping *, int, AstMapping *, int, int, int * );
+static AstMapping *RemoveRegions( AstMapping *, int * );
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+static int *MapSplit0( AstMapping *, int, const int *, AstMapping **, int, int * );
+static int *MapSplit1( AstMapping *, int, const int *, AstMapping **, int * );
+static int *MapSplit2( AstMapping *, int, const int *, AstMapping **, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetIsLinear( AstMapping *, int * );
+static int MapList( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int PatternCheck( int, int, int **, int *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Decompose( AstMapping *, AstMapping **, AstMapping **, int *, int *, int *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static int GetObjSize( AstObject *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+/* Member functions. */
+/* ================= */
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two CmpMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     CmpMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two CmpMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a CmpMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the CmpMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpMap *that;
+   AstCmpMap *this;
+   AstMapping **that_map_list;
+   AstMapping **this_map_list;
+   int *that_invert_list;
+   int *this_invert_list;
+   int i;
+   int result;
+   int that_inv;
+   int that_nmap;
+   int this_inv;
+   int this_nmap;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two CmpMap structures. */
+   this = (AstCmpMap *) this_object;
+   that = (AstCmpMap *) that_object;
+
+/* Check the second object is a CmpMap. We know the first is a
+   CmpMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsACmpMap( that ) ) {
+
+/* Check they are both either parallel or series. */
+      if( that->series == that->series ) {
+
+/* Decompose the first CmpMap into a sequence of Mappings to be applied in
+   series or parallel, as appropriate, and an associated list of
+   Invert flags. */
+         this_nmap = 0;
+         this_map_list = NULL;
+         this_invert_list = NULL;
+         astMapList( (AstMapping *) this, this->series, astGetInvert( this ),
+                     &this_nmap, &this_map_list, &this_invert_list );
+
+/* Similarly decompose the second CmpMap. */
+         that_nmap = 0;
+         that_map_list = NULL;
+         that_invert_list = NULL;
+         astMapList( (AstMapping *) that, that->series, astGetInvert( that ),
+                     &that_nmap, &that_map_list, &that_invert_list );
+
+/* Check the decompositions yielded the same number of component
+   Mappings. */
+         if( that_nmap == this_nmap ) {
+
+/* Check equality of every component. */
+            for( i = 0; i < this_nmap; i++ ) {
+
+/* Temporarily set the Mapping Invert flags to the required values,
+   saving the original values so that they can be re-instated later.*/
+               this_inv = astGetInvert( this_map_list[ i ] );
+               astSetInvert( this_map_list[ i ], this_invert_list[ i ] );
+               that_inv = astGetInvert( that_map_list[ i ] );
+               astSetInvert( that_map_list[ i ], that_invert_list[ i ] );
+
+/* Compare the two component Mappings for equality. */
+               result = astEqual( this_map_list[ i ], that_map_list[ i ] );
+
+/* Re-instate the original Invert flags. */
+               astSetInvert( this_map_list[ i ], this_inv );
+               astSetInvert( that_map_list[ i ], that_inv );
+
+/* Leave the loop if the Mappings are not equal. */
+               if( !result ) break;
+            }
+         }
+
+/* Free resources */
+         for( i = 0; i < this_nmap; i++ ) {
+            this_map_list[ i ] = astAnnul( this_map_list[ i ] );
+         }
+         this_map_list = astFree( this_map_list );
+         this_invert_list = astFree( this_invert_list );
+
+         for( i = 0; i < that_nmap; i++ ) {
+            that_map_list[ i ] = astAnnul( that_map_list[ i ] );
+         }
+         that_map_list = astFree( that_map_list );
+         that_invert_list = astFree( that_invert_list );
+
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetIsLinear( AstMapping *this_mapping, int *status ){
+/*
+*  Name:
+*     GetIsLinear
+
+*  Purpose:
+*     Return the value of the IsLinear attribute for a CmpMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GetIsLinear( AstMapping *this, int *status )
+
+*  Class Membership:
+*     CmpMap member function (over-rides the protected astGetIsLinear
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns the value of the IsLinear attribute for a
+*     Frame, which is one if both component Mappings have a value of 1
+*     for the IsLinear attribute.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpqMap.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   AstCmpMap *this;
+   this = (AstCmpMap *) this_mapping;
+   return astGetIsLinear( this->map1 ) && astGetIsLinear( this->map2 );
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     CmpMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied CmpMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpMap *this;         /* Pointer to CmpMap structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the CmpMap structure. */
+   this = (AstCmpMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astGetObjSize( this->map1 );
+   result += astGetObjSize( this->map2 );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static AstMapping *CombineMaps( AstMapping *mapping1, int invert1,
+                                AstMapping *mapping2, int invert2,
+                                int series, int *status ) {
+/*
+*  Name:
+*     CombineMaps
+
+*  Purpose:
+*     Combine two Mappings with specified Invert flags into a CmpMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     AstMapping *CombineMaps( AstMapping *mapping1, int invert1,
+*                              AstMapping *mapping2, int invert2,
+*                              int series, int *status )
+
+*  Class Membership:
+*     CmpMap member function.
+
+*  Description:
+*     This function combines two Mappings into a CmpMap (compound
+*     Mapping) as if their Invert flags were set to specified values
+*     when the CmpMap is created. However, the individual Mappings are
+*     returned with their Invert flag values unchanged from their
+*     original state.
+
+*  Parameters:
+*     mapping1
+*        Pointer to the first Mapping.
+*     invert1
+*        The (boolean) Invert flag value required for the first Mapping.
+*     mapping2
+*        Pointer to the second Mapping.
+*     invert2
+*        The (boolean) Invert flag value required for the second Mapping.
+*     series
+*        Whether the Mappings are to be combined in series (as opposed to
+*        in parallel).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the resulting compound Mapping (a CmpMap).
+
+*  Notes:
+*     - This function is a wrap-up for the astCmpMap constructor and
+*     temporarily assigns the required Invert flag values while
+*     creating the required CmpMap. However, it also takes account of
+*     the possibility that the two Mapping pointers supplied may point
+*     at the same Mapping.
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map1;             /* First temporary Mapping pointer */
+   AstMapping *map2;             /* Second temporary Mapping pointer */
+   AstMapping *result;           /* Pointer to result Mapping */
+   int copy;                     /* Copy needed? */
+   int inv1;                     /* First original Invert flag value */
+   int inv2;                     /* Second original Invert flag value */
+   int set1;                     /* First Invert flag originally set? */
+   int set2;                     /* Second Invert flag originally set? */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Limit incoming values to 0 or 1. */
+   invert1 = ( invert1 != 0 );
+   invert2 = ( invert2 != 0 );
+
+/* Obtain the Invert flag values for each Mapping. */
+   inv1 = astGetInvert( mapping1 );
+   inv2 = astGetInvert( mapping2 );
+
+/* Also determine if these values are explicitly set. */
+   set1 = astTestInvert( mapping1 );
+   set2 = astTestInvert( mapping2 );
+
+/* If both Mappings are actually the same but we need different Invert
+   flag values to be set, then this can only be achieved by making a
+   copy. Note if this is necessary. */
+   copy = ( ( mapping1 == mapping2 ) && ( invert1 != invert2 ) );
+
+/* Clone the first Mapping pointer. Do likewise for the second but
+   make a copy instead if necessary. */
+   map1 = astClone( mapping1 );
+   map2 = copy ? astCopy( mapping2 ) : astClone( mapping2 );
+
+/* If the Invert value for the first Mapping needs changing, make the
+   change. */
+   if ( invert1 != inv1 ) {
+      if ( invert1 ) {
+         astSetInvert( map1, 1 );
+      } else {
+         astClearInvert( map1 );
+      }
+   }
+
+/* Similarly, change the Invert flag for the second Mapping if
+   necessary. */
+   if ( invert2 != inv2 ) {
+      if ( invert2 ) {
+         astSetInvert( map2, 1 );
+      } else {
+         astClearInvert( map2 );
+      }
+   }
+
+/* Combine the two Mappings into a CmpMap. */
+   result = (AstMapping *) astCmpMap( map1, map2, series, "", status );
+
+/* If the first Mapping's Invert value was changed, restore it to its
+   original state. */
+   if ( invert1 != inv1 ) {
+      if ( set1 ) {
+         astSetInvert( map1, inv1 );
+      } else {
+         astClearInvert( map1 );
+      }
+   }
+
+/* Similarly, restore the second Mapping's Invert value if
+   necessary. This step is not needed, however, if a copy was made. */
+   if ( ( invert2 != inv2 ) && !copy ) {
+      if ( set2 ) {
+         astSetInvert( map2, inv2 );
+      } else {
+         astClearInvert( map2 );
+      }
+   }
+
+/* Annul the temporary Mapping pointers. */
+   map1 = astAnnul( map1 );
+   map2 = astAnnul( map2 );
+
+/* If an error occurred, then annul the result pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void Decompose( AstMapping *this_mapping, AstMapping **map1,
+                       AstMapping **map2, int *series, int *invert1,
+                       int *invert2, int *status ) {
+/*
+*
+*  Name:
+*     Decompose
+
+*  Purpose:
+*     Decompose a Mapping into two component Mappings.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void Decompose( AstMapping *this, AstMapping **map1,
+*                     AstMapping **map2, int *series,
+*                     int *invert1, int *invert2, int *status )
+
+*  Class Membership:
+*     CmpMap member function (over-rides the protected astDecompose
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns pointers to two Mappings which, when applied
+*     either in series or parallel, are equivalent to the supplied Mapping.
+*
+*     Since the Frame class inherits from the Mapping class, Frames can
+*     be considered as special types of Mappings and so this method can
+*     be used to decompose either CmpMaps or CmpFrames.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     map1
+*        Address of a location to receive a pointer to first component
+*        Mapping.
+*     map2
+*        Address of a location to receive a pointer to second component
+*        Mapping.
+*     series
+*        Address of a location to receive a value indicating if the
+*        component Mappings are applied in series or parallel. A non-zero
+*        value means that the supplied Mapping is equivalent to applying map1
+*        followed by map2 in series. A zero value means that the supplied
+*        Mapping is equivalent to applying map1 to the lower numbered axes
+*        and map2 to the higher numbered axes, in parallel.
+*     invert1
+*        The value of the Invert attribute to be used with map1.
+*     invert2
+*        The value of the Invert attribute to be used with map2.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Any changes made to the component Mappings using the returned
+*     pointers will be reflected in the supplied Mapping.
+
+*-
+*/
+
+
+/* Local Variables: */
+   AstCmpMap *this;              /* Pointer to CmpMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpMap structure. */
+   this = (AstCmpMap *) this_mapping;
+
+/* First deal with series mappings. */
+   if( this->series ) {
+      if( series ) *series = 1;
+
+/* If the CmpMap has been inverted, return the Mappings in reverse
+   order with inverted Invert falgs. */
+      if( astGetInvert( this ) ) {
+         if( map1 ) *map1 = astClone( this->map2 );
+         if( map2 ) *map2 = astClone( this->map1 );
+         if( invert1 ) *invert1 = this->invert2 ? 0 : 1;
+         if( invert2 ) *invert2 = this->invert1 ? 0 : 1;
+
+/* If the CmpMap has not been inverted, return the Mappings in their
+   original order with their original Invert flags. */
+      } else {
+         if( map1 ) *map1 = astClone( this->map1 );
+         if( map2 ) *map2 = astClone( this->map2 );
+         if( invert1 ) *invert1 = this->invert1;
+         if( invert2 ) *invert2 = this->invert2;
+      }
+
+/* Now deal with parallel mappings. */
+   } else {
+      if( series ) *series = 0;
+
+/* The mappings are returned in their original order whether or not the
+   CmpMap has been inverted. */
+      if( map1 ) *map1 = astClone( this->map1 );
+      if( map2 ) *map2 = astClone( this->map2 );
+
+/* If the CmpMap has been inverted, return inverted Invert flags. */
+      if( astGetInvert( this ) ) {
+         if( invert1 ) *invert1 = this->invert1 ? 0 : 1;
+         if( invert2 ) *invert2 = this->invert2 ? 0 : 1;
+
+/* If the CmpMap has not been inverted, return the original Invert flags. */
+      } else {
+         if( invert1 ) *invert1 = this->invert1;
+         if( invert2 ) *invert2 = this->invert2;
+      }
+
+   }
+}
+
+void astInitCmpMapVtab_(  AstCmpMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitCmpMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a CmpMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     void astInitCmpMapVtab( AstCmpMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     CmpMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the CmpMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsACmpMap) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+/* None. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_maplist = mapping->MapList;
+   mapping->MapList = MapList;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_mapsplit = mapping->MapSplit;
+   mapping->MapSplit = MapSplit;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->Decompose = Decompose;
+   mapping->MapMerge = MapMerge;
+   mapping->Simplify = Simplify;
+   mapping->RemoveRegions = RemoveRegions;
+   mapping->GetIsLinear = GetIsLinear;
+
+/* For some reason the CmpMap implementation of astRate can be immensely
+   slow for complex Mapping, so it's currently disable until such time as
+   I have time to sort it out.
+
+   mapping->Rate = Rate;
+*/
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "CmpMap", "Compound Mapping" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     CmpMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstCmpMap *this;       /* Pointer to CmpMap structure */
+   int result;            /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the CmpMap structure. */
+   this = (AstCmpMap *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->map1, mode, extra, fail );
+   if( !result ) result = astManageLock( this->map2, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static int MapList( AstMapping *this_mapping, int series, int invert,
+                     int *nmap, AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapList
+
+*  Purpose:
+*     Decompose a CmpMap into a sequence of simpler Mappings.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapList( AstMapping *this, int series, int invert, int *nmap,
+*                  AstMapping ***map_list, int **invert_list )
+
+*  Class Membership:
+*     CmpMap member function (over-rides the protected astMapList
+*     method inherited from the Maping class).
+
+*  Description:
+*     This function decomposes a CmpMap into a sequence of simpler
+*     Mappings which may be applied in sequence to achieve the same
+*     effect. The CmpMap is decomposed as far as possible, but it is
+*     not guaranteed that this will necessarily yield any more than
+*     one Mapping, which may actually be the original CmpMap supplied.
+*
+*     This function is provided to support both the simplification of
+*     CmpMaps, and the analysis of CmpMap structure so that particular
+*     forms can be recognised.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpMap to be decomposed (the CmpMap is not
+*        actually modified by this function).
+*     series
+*        If this value is non-zero, an attempt will be made to
+*        decompose the CmpMap into a sequence of equivalent Mappings
+*        which can be applied in series (i.e. one after the other). If
+*        it is zero, the decomposition will instead yield Mappings
+*        which can be applied in parallel (i.e. on successive sub-sets
+*        of the input/output coordinates).
+*     invert
+*        The value to which the CmpMap's Invert attribute is to be
+*        (notionally) set before performing the
+*        decomposition. Normally, the value supplied here will be the
+*        actual Invert value obtained from the CmpMap (e.g. using
+*        astGetInvert).  Sometimes, however, when a CmpMap is
+*        encapsulated within another structure, that structure may
+*        retain an Invert value (in order to prevent external
+*        interference) which should be used instead.
+*
+*        Note that the actual Invert value of the CmpMap supplied is
+*        not used (or modified) by this function.
+*     nmap
+*        The address of an int which holds a count of the number of
+*        individual Mappings in the decomposition. On entry, this
+*        should count the number of Mappings already in the
+*        "*map_list" array (below). On exit, it is updated to include
+*        any new Mappings appended by this function.
+*     map_list
+*        Address of a pointer to an array of Mapping pointers. On
+*        entry, this array pointer should either be NULL (if no
+*        Mappings have yet been obtained) or should point at a
+*        dynamically allocated array containing Mapping pointers
+*        ("*nmap" in number) which have been obtained from a previous
+*        invocation of this function.
+*
+*        On exit, the dynamic array will be enlarged to contain any
+*        new Mapping pointers that result from the decomposition
+*        requested. These pointers will be appended to any previously
+*        present, and the array pointer will be updated as necessary
+*        to refer to the enlarged array (any space released by the
+*        original array will be freed automatically).
+*
+*        The new Mapping pointers returned will identify a sequence of
+*        Mappings which, when applied in order, will perform a forward
+*        transformation equivalent to that of the original CmpMap
+*        (after its Invert flag has first been set to the value
+*        requested above). The Mappings should be applied in series or
+*        in parallel according to the type of decomposition requested.
+*
+*        All the Mapping pointers returned by this function should be
+*        annulled by the caller, using astAnnul, when no longer
+*        required. The dynamic array holding these pointers should
+*        also be freed, using astFree.
+*     invert_list
+*        Address of a pointer to an array of int. On entry, this array
+*        pointer should either be NULL (if no Mappings have yet been
+*        obtained) or should point at a dynamically allocated array
+*        containing Invert attribute values ("*nmap" in number) which
+*        have been obtained from a previous invocation of this
+*        function.
+*
+*        On exit, the dynamic array will be enlarged to contain any
+*        new Invert attribute values that result from the
+*        decomposition requested. These values will be appended to any
+*        previously present, and the array pointer will be updated as
+*        necessary to refer to the enlarged array (any space released
+*        by the original array will be freed automatically).
+*
+*        The new Invert values returned identify the values which must
+*        be assigned to the Invert attributes of the corresponding
+*        Mappings (whose pointers are in the "*map_list" array) before
+*        they are applied. Note that these values may differ from the
+*        actual Invert attribute values of these Mappings, which are
+*        not relevant.
+*
+*        The dynamic array holding these values should be freed by the
+*        caller, using astFree, when no longer required.
+
+*  Returned Value:
+*     A non-zero value is returned if the supplied Mapping contained any
+*     inverted CmpMaps.
+
+*  Notes:
+*     - It is unspecified to what extent the original CmpMap and the
+*     individual (decomposed) Mappings are
+*     inter-dependent. Consequently, the individual Mappings cannot be
+*     modified without risking modification of the original CmpMap.
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then the *nmap value, the
+*     list of Mapping pointers and the list of Invert values will all
+*     be returned unchanged.
+*/
+
+/* Local Variables: */
+   AstCmpMap *this;              /* Pointer to CmpMap structure */
+   int invert1;                  /* Invert flag for first component Mapping */
+   int invert2;                  /* Invert flag for second component Mapping */
+   int r1;                       /* Value returned from first map list */
+   int r2;                       /* Value returned from second map list */
+   int result;                   /* Returned value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the CmpMap structure. */
+   this = (AstCmpMap *) this_mapping;
+
+/* Check if the CmpMap combines its component Mappings in the same way
+   (series or parallel) as the decomposition requires. Also, do not
+   expand CmpMaps that are not appropriate for simplification. */
+   if ( this->series == series && !astDoNotSimplify( this ) ) {
+
+/* If so, obtain the Invert attribute values to be applied to each
+   component Mapping. */
+      invert1 = this->invert1;
+      invert2 = this->invert2;
+
+/* If the CmpMap itself is inverted, also invert the Invert values to be
+   applied to its components. */
+      if ( invert ) {
+         invert1 = !invert1;
+         invert2 = !invert2;
+      }
+
+/* If the component Mappings are applied in series, then concatenate
+   the Mapping lists obtained from each of them. Do this in reverse
+   order if the CmpMap is inverted, since the second Mapping would be
+   applied first in this case. */
+      if ( series ) {
+         if ( !invert ) {
+            r1 = astMapList( this->map1, series, invert1,
+                             nmap, map_list, invert_list );
+            r2 = astMapList( this->map2, series, invert2,
+                             nmap, map_list, invert_list );
+         } else {
+            r1 = astMapList( this->map2, series, invert2,
+                             nmap, map_list, invert_list );
+            r2 = astMapList( this->map1, series, invert1,
+                             nmap, map_list, invert_list );
+         }
+
+/* If the component Mappings are applied in parallel, then concatenate
+   the Mapping lists obtained from each of them. In this case,
+   inverting the CmpMap has no effect on the order in which they are
+   applied. */
+      } else {
+         r1 = astMapList( this->map1, series, invert1,
+                          nmap, map_list, invert_list );
+         r2 = astMapList( this->map2, series, invert2,
+                          nmap, map_list, invert_list );
+      }
+
+/* Did we find any inverted CmpMaps? */
+      result = invert || r1 || r2;
+
+/* If the CmpMap does not combine its components in the way required
+   by the decomposition (series or parallel), then we cannot decompose
+   it. In this case it must be appended to the Mapping list as a
+   single entity. We can use the parent class method to do this. */
+   } else {
+      result = ( *parent_maplist )( this_mapping, series, invert, nmap,
+                                    map_list, invert_list, status );
+   }
+
+   return result;
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a CmpMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list )
+
+*  Class Membership:
+*     CmpMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated CmpMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated CmpMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated CmpMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the CmpMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        CmpMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated CmpMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpMap *cmpmap1;           /* Pointer to first CmpMap */
+   AstCmpMap *cmpmap2;           /* Pointer to second CmpMap */
+   AstCmpMap *cmpmap;            /* Pointer to nominated CmpMap */
+   AstCmpMap *new_cm;            /* Pointer to new CmpMap */
+   AstMapping **map_list1;       /* Pointer to list of cmpmap1 component Mappings */
+   AstMapping **map_list2;       /* Pointer to list of cmpmap2 component Mappings */
+   AstMapping **new_map_list;    /* Extended Mapping list */
+   AstMapping *map;              /* Pointer to nominated CmpMap */
+   AstMapping *new1;             /* Pointer to new CmpMap */
+   AstMapping *new2;             /* Pointer to new CmpMap */
+   AstMapping *new;              /* Pointer to replacement Mapping */
+   AstMapping *simp1;            /* Pointer to simplified Mapping */
+   AstMapping *simp2;            /* Pointer to simplified Mapping */
+   AstMapping *submap1;          /* A subset of mappings from cmpmap1 */
+   AstMapping *submap2;          /* A subset of mappings from cmpmap2 */
+   AstMapping *tmap2;            /* Temporary Mapping */
+   AstMapping *tmap;             /* Temporary Mapping */
+   AstPermMap *new_pm;           /* Pointer to new PermMap */
+   AstPermMap *permmap1;         /* Pointer to first PermMap */
+   AstUnitMap *unit;             /* UnitMap that feeds const PermMap i/p's */
+   const char *class;            /* Pointer to Mapping class string */
+   double *conperm;              /* Pointer to PermMap constants array */
+   double *const_new;            /* Pointer to new PermMap constants array */
+   double *p;                    /* Pointer to PermMap input position */
+   double *q;                    /* Pointer to PermMap output position */
+   double *qa;                   /* Pointer to 1st component output position */
+   double *qb;                   /* Pointer to 2nd component output position */
+   int *inperm;                  /* Pointer to copy of PermMap inperm array */
+   int *inperm_new;              /* Pointer to new PermMap inperm array */
+   int *invert_list1;            /* Pointer to list of cmpmap1 invert values */
+   int *invert_list2;            /* Pointer to list of cmpmap2 invert values */
+   int *new_invert_list;         /* Extended Invert flag list */
+   int *outperm;                 /* Pointer to copy of PermMap outperm array */
+   int *outperm_new;             /* Pointer to new PermMap outperm array */
+   int aconstants;               /* Are all 1st component outputs constant? */
+   int bconstants;               /* Are all 2nd component outputs constant? */
+   int canswap;                  /* Can nominated Mapping swap with lower neighbour? */
+   int i;                        /* Coordinate index */
+   int iconid;                   /* Constant identifier in supplied PermMap */
+   int imap1;                    /* Index of first Mapping */
+   int imap2;                    /* Index of second Mapping */
+   int imap;                     /* Loop counter for Mappings */
+   int invert1;                  /* Invert flag for first CmpMap */
+   int invert1a;                 /* Invert flag for sub-Mapping */
+   int invert1b;                 /* Invert flag for sub-Mapping */
+   int invert2;                  /* Invert flag for second CmpMap */
+   int invert2a;                 /* Invert flag for sub-Mapping */
+   int invert2b;                 /* Invert flag for sub-Mapping */
+   int invert;                   /* Invert attribute value */
+   int j;                        /* Coordinate index */
+   int jmap1;                    /* Index of next component Mapping in cmpmap1 */
+   int jmap2;                    /* Index of next component Mapping in cmpmap2 */
+   int new_invert;               /* New Invert attribute value */
+   int nin2a;                    /* No. input coordinates for sub-Mapping */
+   int nin2b;                    /* No. input coordinates for sub-Mapping */
+   int nmap1;                    /* Number of Mappings in cmpmap1 */
+   int nmap2;                    /* Number of Mappings in cmpmap2 */
+   int nout2a;                   /* No. of outputs for 1st component Mapping */
+   int nout2b;                   /* No. of outputs for 2nd component Mapping */
+   int npin;                     /* No. of inputs for original PermMap */
+   int npin_new;                 /* No. of inputs for new PermMap */
+   int npout;                    /* No. of outputs for original PermMap */
+   int npout_new;                /* No. of outputs for new PermMap */
+   int nunit;                    /* No. of PermMap i/p's fed by UnitMap */
+   int oconid;                   /* Constant identifier in returned PermMap */
+   int result;                   /* Result value to return */
+   int set;                      /* Invert attribute set? */
+   int simpler;                  /* Simplification possible? */
+   int subin2;                   /* Number of inputs of submap2 */
+   int subinv1;                  /* Invert attribute to use with submap1 */
+   int subinv2;                  /* Invert attribute to use with submap2 */
+   int subout1;                  /* Number of outputs of submap1 */
+
+/* Initialise.*/
+   result = -1;
+
+/* Check the inherited status. */
+   if ( !astOK ) return result;
+
+/* Simplify the CmpMap on its own. */
+/* =============================== */
+/* Obtain a pointer to the nominated Mapping (which is a CmpMap). */
+   map = ( *map_list )[ where ];
+   cmpmap = (AstCmpMap *) map;
+
+/* Determine if the Mapping's Invert attribute is set and obtain its
+   value. */
+   set = astTestInvert( map );
+   invert = astGetInvert( map );
+
+/* If necessary, change the Invert attribute to the value we want. We
+   do this so that simplification (below) has a chance to absorb a
+   non-zero Invert value into the implementation of the simplified
+   Mapping (the preference being to have an Invert value of zero after
+   simplification, if possible). */
+   if ( invert != ( *invert_list )[ where ] ) {
+      astSetInvert( map, ( *invert_list )[ where ] );
+   }
+
+/* Simplify the Mapping and obtain the new Invert value. */
+   new = astSimplify( map );
+   new_invert = astGetInvert( new );
+
+/* If necessary, restore the original Mapping's Invert attribute to
+   its initial state. */
+   if ( invert != ( *invert_list )[ where ] ) {
+      if ( set ) {
+         astSetInvert( map, invert );
+      } else {
+         astClearInvert( map );
+      }
+   }
+
+/* We must now determine if simplification has occurred. Since this is
+   internal code, we can compare the two Mapping pointers directly to
+   see whether "astSimplify" just cloned the pointer we gave it. If it
+   did, then simplification was probably not possible, but check to
+   see if the Invert attribute has changed to be sure. */
+   if ( astOK ) {
+      simpler = ( new != map ) || ( new_invert != ( *invert_list )[ where ] );
+
+/* If simplification was successful, annul the original pointer in the
+   Mapping list and replace it with the new one, together with its
+   invert flag. */
+      if ( simpler ) {
+         (void) astAnnul( ( *map_list )[ where ] );
+         ( *map_list )[ where ] = new;
+         ( *invert_list )[ where ] = new_invert;
+
+/* Return the result. */
+         result = where;
+
+/* Otherwise, annul the new Mapping pointer. */
+      } else {
+         new = astAnnul( new );
+
+/* If the nominated CmpMap is a series CmpMap and the sequence of
+   Mappings are being combined in series, or if the nominated CmpMap is
+   a parallel CmpMap and the sequence of Mappings are being combined in
+   parallel, replace the single CmpMap with the two component Mappings. */
+         if( ( series && cmpmap->series ) ||
+             ( !series && !cmpmap->series ) ) {
+
+/* We are increasing the number of Mappings in the list, so we need to create
+   new, larger, arrays to hold the list of Mapping pointers and invert flags. */
+            new_map_list = astMalloc( ( *nmap + 1 )*sizeof( AstMapping * ) );
+            new_invert_list = astMalloc( ( *nmap + 1 )*sizeof( int ) );
+            if( astOK ) {
+
+/* Copy the values prior to the nominated CmpMap. */
+               for( i = 0; i < where; i++ ) {
+                  new_map_list[ i ] = astClone( ( *map_list )[ i ] );
+                  new_invert_list[ i ] = ( *invert_list )[ i ];
+               }
+
+/* Next insert the two components of the nominated CmpMap */
+               new_map_list[ where ] = astClone( cmpmap->map1 );
+               new_invert_list[ where ] = cmpmap->invert1;
+               new_map_list[ where + 1 ] = astClone( cmpmap->map2 );
+               new_invert_list[ where + 1 ] = cmpmap->invert2;
+
+/* Now copy any values after the nominated CmpMap. */
+               for( i = where + 1; i < *nmap; i++ ) {
+                  new_map_list[ i + 1 ] = astClone( ( *map_list )[ i ] );
+                  new_invert_list[ i + 1 ] = ( *invert_list )[ i ];
+               }
+
+/* Now annul the Object pointers in the supplied map list. */
+               for( i = 0; i < *nmap; i++ ) {
+                  (* map_list )[ i ] = astAnnul( ( *map_list )[ i ] );
+               }
+
+/* Free the memory holding the supplied Mapping and invert flag lists. */
+               astFree( *map_list );
+               astFree( *invert_list );
+
+/* Return pointers to the new extended lists. */
+               *map_list = new_map_list;
+               *invert_list = new_invert_list;
+
+/* Increase the number of Mappings in the list, and the index of
+   the first modified Mapping. */
+               (*nmap)++;
+               result = where;
+
+/* Indicate some simplification has taken place */
+               simpler = 1;
+            }
+         }
+      }
+
+/* If no simplification has been done, merge adjacent CmpMaps. */
+/* ========================================================== */
+/* If the CmpMap would not simplify on its own, we now look for a
+   neighbouring CmpMap with which it might merge. We use the previous
+   Mapping, if suitable, since this will normally also have been fully
+   simplified on its own. Check if a previous Mapping exists. */
+      if( !simpler ) {
+         if ( astOK && *nmap > 1 ) {
+
+/* Obtain the indices of the two potential Mappings to be merged. imap1
+   is the first Mapping, imap2 is the second. imapc is the CmpMap, imapn is
+   the neighbouring Mapping. */
+            if( where == 0 ) {
+               imap1 = 0;
+               imap2 = 1;
+            } else {
+               imap1 = where - 1;
+               imap2 = where;
+            }
+
+/* Obtain the Class string of the neighbouring Mapping and determine if it
+   is a CmpMap. */
+            class = astGetClass( ( *map_list )[ (where>0)?where-1:1 ] );
+            if ( astOK && !strcmp( class, "CmpMap" ) ) {
+
+/* If suitable, obtain pointers to the two CmpMaps. */
+               cmpmap1 = (AstCmpMap *) ( *map_list )[ imap1 ];
+               cmpmap2 = (AstCmpMap *) ( *map_list )[ imap2 ];
+
+/* Obtain the associated invert flag values. */
+               invert1 = ( *invert_list )[ imap1 ];
+               invert2 = ( *invert_list )[ imap2 ];
+
+/* Extract the invert flags associated with each CmpMap sub-Mapping
+   and combine these with the flag values obtained above so as to give
+   the invert flag to be used with each individual sub-Mapping. */
+               invert1a = cmpmap1->invert1;
+               invert1b = cmpmap1->invert2;
+               if ( invert1 ) {
+                  invert1a = !invert1a;
+                  invert1b = !invert1b;
+               }
+               invert2a = cmpmap2->invert1;
+               invert2b = cmpmap2->invert2;
+               if ( invert2 ) {
+                  invert2a = !invert2a;
+                  invert2b = !invert2b;
+               }
+
+/* Series CmpMaps in parallel. */
+/* =========================== */
+/* Now check if the CmpMaps can be merged. This may be possible if we
+   are examining a list of Mappings combined in parallel and the two
+   adjacent CmpMaps both combine their sub-Mappings in series. */
+               if ( !series && cmpmap1->series && cmpmap2->series ) {
+
+/* Form two new parallel CmpMaps with the sub-Mappings re-arranged so
+   that when combined in series these new CmpMaps are equivalent to
+   the original ones. In doing this, we must take account of the
+   invert flags which apply to each sub-Mapping and also of the fact
+   that the order in which the sub-Mappings are applied depends on the
+   invert flags of the original CmpMaps. */
+                  new1 = CombineMaps( invert1 ? cmpmap1->map2 : cmpmap1->map1,
+                                      invert1 ? invert1b      : invert1a,
+                                      invert2 ? cmpmap2->map2 : cmpmap2->map1,
+                                      invert2 ? invert2b      : invert2a, 0, status );
+                  new2 = CombineMaps( invert1 ? cmpmap1->map1 : cmpmap1->map2,
+                                      invert1 ? invert1a      : invert1b,
+                                      invert2 ? cmpmap2->map1 : cmpmap2->map2,
+                                      invert2 ? invert2a      : invert2b, 0, status );
+
+/* Having converted the parallel combination of series CmpMaps into a
+   pair of equivalent parallel CmpMaps that can be combined in series,
+   try and simplify each of these new CmpMaps. */
+                  simp1 = astSimplify( new1 );
+                  simp2 = astSimplify( new2 );
+
+/* Test if either could be simplified by checking if its pointer value
+   has changed. Also check if the Invert attribute has changed (not
+   strictly necessary, but a useful safety feature in case of any
+   rogue code which just changes this attribute instead of issuing a
+   new pointer). */
+                  simpler = ( simp1 != new1 ) || ( simp2 != new2 ) ||
+                            astGetInvert( simp1 ) || astGetInvert( simp2 );
+
+/* If either CmpMap was simplified, then combine the resulting
+   Mappings in series to give the replacement CmpMap. */
+                  if ( simpler ) new =
+                               (AstMapping *) astCmpMap( simp1, simp2, 1, "", status );
+
+/* Annul the temporary Mapping pointers. */
+                  new1 = astAnnul( new1 );
+                  new2 = astAnnul( new2 );
+                  simp1 = astAnnul( simp1 );
+                  simp2 = astAnnul( simp2 );
+
+/* Parallel CmpMaps in series. */
+/* =========================== */
+/* A pair of adjacent CmpMaps can also potentially be merged if we are
+   examining a list of Mappings combined in series and the two
+   adjacent CmpMaps both combine their sub-Mappings in parallel. */
+               } else if ( series && !cmpmap1->series && !cmpmap2->series ) {
+
+/* Expand each of the two adjacent CmpMaps into a list of Mappings to be
+   combined in parallel. */
+                  map_list1 = map_list2 = NULL;
+                  invert_list1 = invert_list2 = NULL;
+                  nmap1 = nmap2 = 0;
+                  (void) astMapList( (AstMapping *) cmpmap1, 0, invert1,
+                                     &nmap1, &map_list1, &invert_list1 );
+                  (void) astMapList( (AstMapping *) cmpmap2, 0, invert2,
+                                     &nmap2, &map_list2, &invert_list2 );
+
+/* We want to divide each of these lists into N sub-lists so that the
+   outputs of the Mappings in the i'th sub-list from cmpmap1 can feed
+   (i.e. equal in number) the inputs of the Mappings in the i'th sub-list
+   from cmpmap2. If such a sub-list contains more than one Mapping we
+   combine them together into a parallel CmpMap. Initialise a flag to
+   indicate that we have not yet found any genuine simplification. */
+                  simpler = 0;
+
+/* Initialise the index of the next Mapping to be added into each
+   sublist. */
+                  jmap1 = jmap2 = 0;
+
+/* Indicate both sublists are currently empty. */
+                  subout1 = subin2 = 0;
+                  new = submap1 = submap2 = NULL;
+                  subinv1 = subinv2 = 0;
+
+/* Loop round untill all Mappings have been used. */
+                  while( jmap1 <= nmap1 && jmap2 <= nmap2 && astOK ) {
+
+/* Note the number of outputs from submap1 and the number of inputs to
+   submap2. If the Invert flag is not set to the required value for
+   either Mapping, then inputs become outputs and vice-versa, so swap Nin
+   and Nout. */
+                     if( !submap1 ) {
+                        subout1 = 0;
+                     } else if( subinv1 == astGetInvert( submap1 ) ) {
+                        subout1 = astGetNout( submap1 );
+                     } else {
+                        subout1 = astGetNin( submap1 );
+                     }
+
+                     if( !submap2 ) {
+                        subin2 = 0;
+                     } else if( subinv2 == astGetInvert( submap2 ) ) {
+                        subin2 = astGetNin( submap2 );
+                     } else {
+                        subin2 = astGetNout( submap2 );
+                     }
+
+/* If sublist for cmpmap1 has too few outputs, add the next Mapping from
+   the cmpmap1 list into the submap1 sublist. */
+                     if( subout1 < subin2 ) {
+                        tmap = CombineMaps( submap1, subinv1,
+                                            map_list1[ jmap1 ],
+                                            invert_list1[ jmap1 ], 0, status );
+                        (void) astAnnul( submap1 );
+                        submap1 = tmap;
+                        subinv1 = 0;
+                        jmap1++;
+
+/* If sublist for cmpmap2 has too few inputs, add the next Mapping from
+   the cmpmap2 list into the submap2 sublist. */
+                     } else if( subin2 < subout1 ) {
+                        tmap = CombineMaps( submap2, subinv2,
+                                            map_list2[ jmap2 ],
+                                            invert_list2[ jmap2 ], 0, status );
+                        (void) astAnnul( submap2 );
+                        submap2 = tmap;
+                        subinv2 = 0;
+                        jmap2++;
+
+/* If submap1 can now feed submap2, combine them in series, and attempt to
+   simplify it. */
+                     } else {
+
+/* Check this is not the first pass (when we do not have a submap1 or
+   submap2). */
+                        if( submap1 && submap2 ) {
+
+/* Combine the Mappings in series and simplify. */
+                           tmap = CombineMaps( submap1, subinv1, submap2,
+                                               subinv2, 1, status );
+                           submap1 = astAnnul( submap1 );
+                           submap2 = astAnnul( submap2 );
+                           tmap2 = astSimplify( tmap );
+                           tmap = astAnnul( tmap );
+
+/* Note if any simplification took place. */
+                           if( tmap != tmap2 ||
+                               astGetInvert( tmap ) != astGetInvert( tmap2 ) )
+                                           simpler = 1;
+
+/* Add the simplifed Mapping into the total merged Mapping (a parallel
+   CmpMap). */
+                           if( !new ) {
+                              new = tmap2;
+                           } else {
+                              tmap = (AstMapping *) astCmpMap( new, tmap2, 0,
+                                                               " ", status );
+                              tmap2 = astAnnul( tmap2 );
+                              (void) astAnnul( new );
+                              new = tmap;
+                           }
+                        }
+
+/* Reset submap1 to be the next Mapping from the cmpmap1 map list. First,
+   save its old Invert flag and set it to the required value. */
+                        if( jmap1 < nmap1 ) {
+                           submap1 = astClone( map_list1[ jmap1 ] );
+                           subinv1 = invert_list1[ jmap1 ];
+                           jmap1++;
+                        } else {
+                           break;
+                        }
+
+/* Do the same for the second list. */
+                        if( jmap2 < nmap2 ) {
+                           submap2 = astClone( map_list2[ jmap2 ] );
+                           subinv2 = invert_list2[ jmap2 ];
+                           jmap2++;
+                        } else {
+                           break;
+                        }
+                     }
+                  }
+
+/* Free the lists of Mapping pointers and invert flags. */
+                  if( map_list1 ) {
+                     for( jmap1 = 0; jmap1 < nmap1; jmap1++ ) {
+                        map_list1[ jmap1 ] = astAnnul( map_list1[ jmap1 ] );
+                     }
+                     map_list1 = astFree( map_list1 );
+                  }
+                  invert_list1 = astFree( invert_list1 );
+
+                  if( map_list2 ) {
+                     for( jmap2 = 0; jmap2 < nmap2; jmap2++ ) {
+                        map_list2[ jmap2 ] = astAnnul( map_list2[ jmap2 ] );
+                     }
+                     map_list2 = astFree( map_list2 );
+                  }
+                  invert_list2 = astFree( invert_list2 );
+
+               }
+            }
+
+/* Update Mapping list. */
+/* ==================== */
+/* If adjacent CmpMaps can be combined, then annul the original pointers. */
+            if ( astOK && simpler ) {
+               ( *map_list )[ imap1 ] = astAnnul( ( *map_list )[ imap1 ] );
+               ( *map_list )[ imap2 ] = astAnnul( ( *map_list )[ imap2 ] );
+
+/* Insert the pointer to the replacement CmpMap and initialise its
+   invert flag. */
+               ( *map_list )[ imap1 ] = new;
+               ( *invert_list )[ imap1 ] = 0;
+
+/* Loop to close the resulting gap by moving subsequent elements down
+   in the arrays. */
+               for ( imap = imap2 + 1; imap < *nmap; imap++ ) {
+                  ( *map_list )[ imap - 1 ] = ( *map_list )[ imap ];
+                  ( *invert_list )[ imap - 1 ] = ( *invert_list )[ imap ];
+               }
+
+/* Clear the vacated elements at the end. */
+               ( *map_list )[ *nmap - 1 ] = NULL;
+               ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+               ( *nmap )--;
+               result = imap1;
+            }
+         }
+      }
+   }
+
+/* If we are merging the Mappings in series, and if the nominated CmpMap
+   is a parallel CmpMap, and if the lower neighbour is a PermMap, it may
+   be possible to swap the PermMap and the CmpMap. This may allow one of
+   the two swapped Mappings to merge with its new neighbour.
+   ==================================================================== */
+
+/* Only do this if no simplification occurred above, and if the Mappings
+   are being merged in series, and if the nominated Mapping is not the
+   first in the list. */
+   if( result == -1 && where > 0 ){
+
+/* Obtain the indices of the two potential Mappings to be swapped. */
+      imap1 = where - 1;
+      imap2 = where;
+
+/* Obtain a pointer to the CmpMap. */
+      cmpmap2 = (AstCmpMap *) ( *map_list )[ imap2 ];
+
+/* Obtain the Class string of the first (previous) Mapping and
+   determine if it is a PermMap. Also check that the nominated Mapping is
+   a parallel CmpMap. */
+      class = astGetClass( ( *map_list )[ imap1 ] );
+      if ( astOK && !strcmp( class, "PermMap" ) && !cmpmap2->series) {
+
+/* Indicate we have no new Mapping to store. */
+         new = NULL;
+
+/* If suitable, obtain a pointer to the PermMap. */
+         permmap1 = (AstPermMap *) ( *map_list )[ imap1 ];
+
+/* Obtain the current values of the Invert attribute in the Mappings. */
+         invert1 = astGetInvert( permmap1 );
+         invert2 = astGetInvert( cmpmap2 );
+
+/* Temporarily set the Invert attributes of both Mappings to the values
+   supplied in the "invert_list" parameter. */
+         astSetInvert( permmap1, ( *invert_list )[ imap1 ] );
+         astSetInvert( cmpmap2, ( *invert_list )[ imap2 ] );
+
+/* Get the number of inputs and outputs for the PermMap.*/
+         npout = astGetNout( permmap1 );
+         npin = astGetNin( permmap1 );
+
+/* Get the number of inputs and outputs for the two components of the
+   nominated parallel CmpMap. */
+         nin2a = astGetNin( cmpmap2->map1 );
+         nin2b = astGetNin( cmpmap2->map2 );
+         nout2a = astGetNout( cmpmap2->map1 );
+         nout2b = astGetNout( cmpmap2->map2 );
+
+/* Get the input and output axis permutation arrays and the constants
+   array from the PermMap */
+         inperm =astGetInPerm( permmap1 );
+         outperm =astGetOutPerm( permmap1 );
+         conperm = astGetConstants( permmap1 );
+
+/* In order to swap the Mappings, the PermMap outputs which feed the
+   inputs of the first component of the parallel CmpMap must be copied
+   from a contiguous block at the end of the list of PermMap inputs, or
+   must all be assigned constant values. Likewise, the PermMap outputs which
+   feed the inputs of the second component of the parallel CmpMap must be
+   copied from a contiguous block at the beggining of the list of PermMap
+   inputs or must be assigned constant values. Also, there must be a
+   one-to-one correspondance between inputs and outputs in the PermMap.
+   Check that the first block of nin2a PermMap outputs are copied from
+   the last block of nin2a PermMap inputs (and vica-versa) or are constant. */
+         canswap = ( npin == npout );
+         aconstants = ( outperm[ 0 ] < 0 );
+
+         for( i = 0, j = npin - nin2a; i < nin2a; i++, j++ ) {
+            if( aconstants ) {
+               if( outperm[ i ] >= 0 ) {
+                  canswap = 0;
+                  break;
+               }
+
+            } else if( outperm[ i ] != j || inperm[ j ] != i ) {
+               canswap = 0;
+               break;
+            }
+         }
+
+/* Check that the first block of nin2b PermMap inputs are copied from
+   the last block of nin2b PermMap outputs, and vica-versa. */
+         bconstants = ( outperm[ nin2a ] < 0 );
+         for( i = 0, j = npout - nin2b; i < nin2b; i++, j++ ) {
+            if( bconstants ) {
+               if( outperm[ j ] >= 0 ) {
+                  canswap = 0;
+                  break;
+               }
+            } else if( inperm[ i ] != j || outperm[ j ] != i ) {
+               canswap = 0;
+               break;
+            }
+         }
+
+/* If the Mappings can be swapped.. */
+         new_pm = NULL;
+         new_cm = NULL;
+         qa = NULL;
+         qb = NULL;
+         if( canswap ) {
+
+/* Temporarily set the Invert attributes of the component Mappings to the
+   values they had when the CmpMap was created. */
+            invert2a = astGetInvert( cmpmap2->map1 );
+            invert2b = astGetInvert( cmpmap2->map2 );
+            astSetInvert( cmpmap2->map1, cmpmap2->invert1 );
+            astSetInvert( cmpmap2->map2, cmpmap2->invert2 );
+
+/* If any PermMap outputs are constant, we will need the results of
+   transforming these constants using the CmpMap which follows. */
+            if( aconstants || bconstants ) {
+
+/* Transform a set of bad inputs using the PermMap. This will assign the
+   PermMap constant to any fixed outputs. */
+               p = astMalloc( sizeof( double )*(size_t) npin );
+               q = astMalloc( sizeof( double )*(size_t) npout );
+               qa = astMalloc( sizeof( double )*(size_t) nout2a );
+               qb = astMalloc( sizeof( double )*(size_t) nout2b );
+               if( astOK ) {
+                  for( i = 0; i < npin; i++ ) p[ i ] = AST__BAD;
+                  astTranN( permmap1, 1, npin, 1, p, 1, npout, 1, q );
+
+/* Transform the PermMap outputs using the two component Mappings in the
+   CmpMap. */
+                  astTranN( cmpmap2->map1, 1, nin2a, 1, q, 1, nout2a, 1, qa );
+                  astTranN( cmpmap2->map2, 1, nin2b, 1, q + nin2a, 1, nout2b, 1, qb );
+
+               }
+               p = astFree( p );
+               q = astFree( q );
+            }
+
+/* If necessary, create a UnitMap to replace a Mapping which has constant
+   outputs. The number of axes for the UnitMap is chosen to give the
+   correct total number of inputs for the final parallel CmpMap. At the
+   same time determine the number of inputs needed by the final PermMap. */
+            if( aconstants ) {
+               nunit = npin - nin2b;
+               npin_new = nout2b + nunit;
+            } else if( bconstants ) {
+               nunit = npin - nin2a;
+               npin_new = nout2a + nunit;
+            } else {
+               nunit = 0;
+               npin_new = nout2a + nout2b;
+            }
+            unit = nunit ? astUnitMap( nunit, "", status ) : NULL;
+
+/* Determine the number of outputs for the final PermMap and allocate memory
+   for its permutation arrays. */
+            npout_new = nout2a + nout2b;
+            outperm_new = astMalloc( sizeof( int )*(size_t) npout_new );
+            inperm_new = astMalloc( sizeof( int )*(size_t) npin_new );
+            const_new = astMalloc( sizeof( double )*(size_t) ( npout_new + npin_new ) );
+            if( astOK ) {
+               oconid = 0;
+
+/* First assign permutations for the second component Mapping, if used. */
+               if( !bconstants ) {
+                  for( i = 0, j = npout_new - nout2b; i < nout2b; i++,j++ ) {
+                     inperm_new[ i ] = j;
+                     outperm_new[ j ] = i;
+                  }
+
+/* Otherwise, store constants */
+               } else {
+
+                  for( i = 0; i < nunit; i++ ){
+                     iconid = inperm[ i ];
+                     if( iconid >= npout ) {
+                        inperm_new[ i ] = npout_new;
+
+                     } else if( iconid >= 0 ) {
+                        astError( AST__INTER, "astMapMerge(CmpMap): Swapped PermMap "
+                                  "input is not constant (internal AST programming "
+                                  "error)." , status);
+                        break;
+
+                     } else {
+                        inperm_new[ i ] = --oconid;
+                        const_new[ -( oconid + 1 ) ] = conperm[ -( iconid + 1 ) ];
+                     }
+                  }
+
+                  for( i = 0, j = npout_new - nout2b; i < nout2b; i++,j++ ) {
+                     outperm_new[ j ] = --oconid;
+                     const_new[ -( oconid + 1 ) ] = qb[ i ];
+                  }
+
+               }
+
+/* Now assign permutations for the first component Mapping, if used. */
+               if( !aconstants ) {
+                  for( i = 0, j = npin_new - nout2a; i < nout2a; i++,j++ ) {
+                     inperm_new[ j ] = i;
+                     outperm_new[ i ] = j;
+                  }
+
+/* Otherwise, store constants */
+               } else {
+
+                  for( i = nout2b; i < npin_new; i++ ){
+                     iconid = inperm[ i - nout2b + nin2b ];
+                     if( iconid >= npout ) {
+                        inperm_new[ i ] = npout_new;
+
+                     } else if( iconid >= 0 ) {
+                        astError( AST__INTER, "astMapMerge(CmpMap): Swapped PermMap "
+                                  "input is not constant (internal AST programming "
+                                  "error)." , status);
+                        break;
+
+                     } else {
+                        inperm_new[ i ] = --oconid;
+                        const_new[ -( oconid + 1 ) ] = conperm[ -( iconid + 1 ) ];
+                     }
+                  }
+
+                  for( i = 0; i < nout2a; i++ ) {
+                     outperm_new[ i ] = --oconid;
+                     const_new[ -( oconid + 1 ) ] = qa[ i ];
+                  }
+
+               }
+
+/* Create the new PermMap */
+               new_pm = astPermMap( npin_new, inperm_new, npout_new,
+                                    outperm_new, const_new, "", status );
+
+/* Create the new CmpMap.*/
+               if( aconstants ) {
+                  if( unit ) {
+                     new_cm = astCmpMap( cmpmap2->map2, unit, 0, "", status );
+                  } else {
+                     new_cm = astCopy( cmpmap2->map2 );
+                  }
+
+               } else if( bconstants ) {
+                  if( unit ) {
+                     new_cm = astCmpMap( unit, cmpmap2->map1, 0, "", status );
+                  } else {
+                     new_cm = astCopy( cmpmap2->map1 );
+                  }
+
+               } else{
+                  new_cm = astCmpMap( cmpmap2->map2, cmpmap2->map1, 0, "", status );
+               }
+
+            }
+
+/* Free Memory. */
+            if( unit ) unit = astAnnul( unit );
+            outperm_new = astFree( outperm_new );
+            inperm_new = astFree( inperm_new );
+            const_new = astFree( const_new );
+            if( aconstants || bconstants ) {
+               qa = astFree( qa );
+               qb = astFree( qb );
+            }
+
+/* Re-instate the original Invert attributes in the component Mappings. */
+            astSetInvert( cmpmap2->map1, invert2a );
+            astSetInvert( cmpmap2->map2, invert2b );
+
+         }
+
+/* Release the arrays holding the input and output permutation arrays
+   and constants copied from the PermMap. */
+         inperm = astFree( inperm );
+         outperm = astFree( outperm );
+         conperm = astFree( conperm );
+
+/* Re-instate the original values of the Invert attributes of both
+   Mappings. */
+         astSetInvert( permmap1, invert1 );
+         astSetInvert( cmpmap2, invert2 );
+
+/* If the Mappings can be swapped... */
+         if( astOK && canswap ) {
+
+/* Annul the supplied pointer to the two Mappings. */
+            ( *map_list )[ imap1 ] = astAnnul( ( *map_list )[ imap1 ] );
+            ( *map_list )[ imap2 ] = astAnnul( ( *map_list )[ imap2 ] );
+
+/* Store the new PermMap pointer in the slot previously occupied by the
+   nominated CmpMap pointer. Likewise, store the invert flag. */
+            ( *map_list )[ imap2 ] = (AstMapping *) new_pm;
+            ( *invert_list )[ imap2 ] = astGetInvert( new_pm );
+
+/* Store the new PermMap pointer in the slot previously occupied by the
+   nominated CmpMap pointer. Likewise, store the invert flag. */
+            ( *map_list )[ imap1 ] = (AstMapping *) new_cm;
+            ( *invert_list )[ imap1 ] = astGetInvert( new_cm );
+
+/* Return the index of the first modified element. */
+            result = imap1;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit1( AstMapping *this, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit1
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     int *MapSplit1( AstMapping *this, int nin, const int *in, AstMapping **map )
+
+*  Class Membership:
+*     CmpMap method
+
+*  Description:
+*     This function performs the work for the astMapSplit method. It
+*     first invokes the astMapSplit method to see if the forward
+*     transformation of the supplied Mapping (not necessarily a CmpMap)
+*     can be split as requested. If this is not possible it invokes MapSplit2
+*     which attempts an inverse approach to the problem. For each possible
+*     sub-sets of the Mapping outputs it call astMapSplit to see if the
+*     sub-set of outputs are generated from the selected inputs.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be split. It is not assumed to be a CmpMap.
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied Mapping, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied Mapping has no subset of outputs which
+*        depend only on the selected inputs.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied Mapping. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   int *result;       /* Axis order to return */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* First see if the forward transformation can be split as requested. */
+   result = astMapSplit( this, nin, in, map );
+
+/* If forward transformation could not be split, we attempt to split the
+   inverse transformation by selecting every possible sub-set of Mapping
+   outputs until one is found which is fed by the requested mapping inputs. */
+   if( !result ) result = MapSplit2( this, nin, in, map, status );
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static int *MapSplit2( AstMapping *this, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit2
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     int *MapSplit2( AstMapping *this, int nin, const int *in, AstMapping **map )
+
+*  Class Membership:
+*     CmpMap method
+
+*  Description:
+*     This function attempts to split the supplied Mapping using an
+*     inverse approach to the problem. For each possible sub-sets of the
+*     Mapping outputs it call astMapSplit to see if the sub-set of outputs
+*     are generated from the selected inputs.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be split. It is not assumed to be a CmpMap.
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied Mapping, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied Mapping has no subset of outputs which
+*        depend only on the selected inputs.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied Mapping. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstMapping *map2;  /* Subset Mapping */
+   AstMapping *this2; /* Inverted copy of the supplied Mapping */
+   int *out;          /* Selected output indices */
+   int *result;       /* Axis order to return */
+   int *result2;      /* Axis order for current output subset */
+   int i;             /* Loop count */
+   int iscmp;         /* Is "this" a CmpMap? */
+   int j;             /* Loop count */
+   int mout;          /* Number of selected outputs */
+   int nin2;          /* Number of inputs fed by current outputs */
+   int nout;          /* The number of outputs from the supplied Mapping */
+   int ok;            /* Are all required inputs fed by current outputs? */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the number of Mapping outputs. */
+   nout = astGetNout( this );
+
+/* Get an inverted copy of the Mapping. We do this rather than inverting
+   the supplied Maping in case an error occurs which may leave the
+   supplied Mapping inverted. */
+   this2 = astCopy( this );
+   astInvert( this2 );
+
+/* Note if the Mapping is a CmpMap. */
+   iscmp = astIsACmpMap( this );
+
+/* Allocate memory to hold the selected output indices. */
+   out = astMalloc( nout*sizeof( int ) );
+
+/* Loop round all useful subset sizes. */
+   if( out ) {
+      for( mout = 1; mout < nout && !result; mout++ ) {
+
+/* Initialise the first subset of outputs to check at the current subset
+   size. */
+         for( i = 0; i < mout; i++ ) out[ i ] = 0;
+
+/* Loop round all ways of picking a subset of "mout" outputs from the total
+   available "nout" outputs. */
+         while( ! result ) {
+
+/* Skip this subset if it refers to any axis index more than once. */
+            ok = 1;
+            for( i = 1; i < mout && ok; i++ ) {
+               for( j = 0; j < i; j++ ) {
+                  if( out[ i ] == out[ j ] ) {
+                     ok = 0;
+                     break;
+                  }
+               }
+            }
+            if( ok ) {
+
+/* Attempt to split the inverted Mapping using the current subset of
+   outputs. Take care to avoid an infinite loop if "this" is a CmpMap. */
+               if( iscmp ) {
+                  result2 = MapSplit0( this2, mout, out, &map2, 1, status );
+               } else {
+                  result2 = astMapSplit( this2, mout, out, &map2 );
+               }
+
+/* If succesful... */
+               if( result2 ) {
+
+/* See if the inputs that feed the current subset of outputs are the same
+   as the inputs specified by the caller (and in the same order). */
+                  nin2 = astGetNout( map2 );
+                  ok = ( nin2 == nin );
+                  if( ok ) {
+                     for( i = 0; i < nin; i++ ) {
+                        if( in[ i ] != result2[ i ] ) {
+                           ok = 0;
+                           break;
+                        }
+                     }
+                  }
+
+/* If so, set up the values returned to the caller. */
+                  if( ok ) {
+                     result = astStore( result, out, mout*sizeof(int) );
+                     astInvert( map2 );
+                     *map = astClone( map2 );
+                  }
+
+/* Free resources. */
+                  result2 = astFree( result2 );
+                  map2 = astAnnul( map2 );
+               }
+            }
+
+/* Increment the first axis index. */
+            i = 0;
+            out[ i ]++;
+
+/* If the incremented axis index is now too high, reset it to zero and
+   increment the next higher axis index. Do this until an incremented axis
+   index is not too high. */
+            while( out[ i ] == nout ) {
+               out[ i++ ] = 0;
+
+               if( i < mout ) {
+                  out[ i ]++;
+               } else {
+                  break;
+               }
+            }
+
+/* If all subsets have been checked break out of the loop. */
+            if( i == mout ) break;
+
+         }
+      }
+   }
+
+/* Free resources. */
+   out = astFree( out );
+   this2 = astAnnul( this2 );
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static int *MapSplit0( AstMapping *this_mapping, int nin, const int *in,
+                       AstMapping **map, int reentry, int *status ){
+/*
+*  Name:
+*     MapSplit0
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     CmpMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     int *MapSplit0( AstMapping *this, int nin, const int *in,
+*                     AstMapping **map, int reentry, int *status )
+
+*  Class Membership:
+*     CmpMap method
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing CmpMap. This is only possible if the specified inputs
+*     correspond to some subset of the CmpMap outputs. That is, there
+*     must exist a subset of the CmpMap outputs for which each output
+*     depends only on the selected CmpMap inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied CmpMap, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpMap to be split (the CmpMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied CmpMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied CmpMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     reentry
+*        Set to zero if this is a top level entry, and non-zero if it is
+*        a recursive entry.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied CmpMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstCmpMap *this;
+   AstMapping **map_list;
+   AstMapping *amap;
+   AstMapping *bmap;
+   AstPermMap *pmap;
+   int *aout;
+   int *cin;
+   int *cout;
+   int *inp;
+   int *invert_list;
+   int *outp;
+   int *p;
+   int *result;
+   int doperm;
+   int i;
+   int ibot;
+   int ibotout;
+   int iin;
+   int imap;
+   int iout;
+   int itop;
+   int j;
+   int naout;
+   int ncin;
+   int ncout;
+   int nmap;
+   int npin;
+   int npout;
+   int ok;
+   int old_inv;
+   int t;
+
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the CmpMap structure. */
+   this = (AstCmpMap *) this_mapping;
+
+/* Get the number of inputs and outputs in the supplied CmpMap. */
+   npin = astGetNin( this );
+   npout = astGetNout( this );
+
+/* Check all input axis indices are valid. */
+   ok = 1;
+   for( i = 0; i < nin; i++ ) {
+      if( in[ i ] < 0 || in[ i ] >= npin ) {
+         ok = 0;
+         break;
+      }
+   }
+
+/* If OK, proceed. */
+   if( ok ) {
+
+/* Initialise dynamic arrays of Mapping pointers and associated Invert
+   flags. */
+      nmap = 0;
+      map_list = NULL;
+      invert_list = NULL;
+
+/* Decompose the CmpMap into a sequence of Mappings to be applied in
+   series or parallel, as appropriate, and an associated list of
+   Invert flags. */
+      (void) astMapList( this_mapping, this->series, astGetInvert( this ),
+                         &nmap, &map_list, &invert_list );
+
+/* First handle lists of Mapping in series. */
+      if( this->series ) {
+
+/* Initialise the array of inputs to be split from the next component
+   Mapping. */
+         ncin = nin;
+         cin = astStore( NULL, in, sizeof( int )*nin );
+
+/* Loop round all the component Mappings that are combined in series to form
+   the supplied CmpMap. */
+         for( imap = 0; imap < nmap && cin; imap++ ) {
+
+/* Temporarily reset the Invert attribute within the commponent Mapping back
+   to the value it had when the CmpMap was created. */
+            old_inv = astGetInvert( map_list[ imap ] );
+            astSetInvert(  map_list[ imap ], invert_list[ imap ] );
+
+/* Attempt to split the component Mapping using the current list of
+   inputs. */
+            cout = MapSplit1( map_list[ imap ], ncin, cin, &amap, status );
+
+/* If the split could be done... */
+            if( amap ) {
+
+/* The outputs that correspond to the picked inputs become the inputs to
+   be picked from the next component Mapping. */
+               (void) astFree( cin );
+               cin = cout;
+               ncin = astGetNout( amap );
+
+/* Combine the split Mapping in series with the earlier split Mappings. */
+               if( *map ) {
+                  bmap = (AstMapping *) astCmpMap( *map, amap, 1, " ", status );
+                  amap = astAnnul( amap );
+                  (void) astAnnul( *map );
+                  *map = bmap;
+               } else {
+                  *map = amap;
+               }
+
+/* If the split could not be done, free the array of Mapping inputs to
+   indicate that no more component Mappings need be checked. */
+            } else {
+               cin = astFree( cin );
+               cout = astFree( cout );
+            }
+
+/* Re-instate the original value of the Invert attribute within the
+   commponent Mapping. */
+            astSetInvert(  map_list[ imap ], old_inv );
+         }
+
+/* Return the final array of output indices. */
+         result = cin;
+
+/* Now handle lists of Mapping in parallel. */
+      } else {
+
+/* Allocate work space. */
+         outp = astMalloc( sizeof(int)*(size_t)nin );
+         inp = astMalloc( sizeof(int)*(size_t)nin );
+         cin = astMalloc( sizeof(int)*(size_t)npin );
+         cout = astMalloc( sizeof(int)*(size_t)npout );
+         if( astOK ) {
+
+/* The caller may have selected the Mapping inputs in any order, so we
+   need to create a PermMap which will permute the inputs from the
+   requested order to the order used by the CmpMap. First fill the outperm
+   work array with its own indices. */
+            for( i = 0; i < nin; i++ ) outp[ i ] = i;
+
+/* Sort the outperm work array so that it accesses the array of input indices
+   in ascending order */
+            for( j = nin - 1; j > 0; j-- ) {
+               p = outp;
+               for( i = 0; i < j; i++,p++ ) {
+                  if( in[ p[0] ] > in[ p[1] ] ) {
+                     t = p[0];
+                     p[0] = p[1];
+                     p[1] = t;
+                  }
+               }
+            }
+
+/* Create the inperm array which is the inverse of the above outperm
+   array. Note if the permutation is necessary. */
+            doperm = 0;
+            for( i = 0; i < nin; i++ ) {
+               if( outp[ i ] != i ) doperm = 1;
+               inp[ outp[ i ] ] = i;
+            }
+
+/* Create a PermMap which reorders the inputs into ascending order. */
+            pmap = doperm ? astPermMap( nin, inp, nin, outp, NULL, "", status ) : NULL;
+
+/* Store the sorted input indices in the inp work array. */
+            for( i = 0; i < nin; i++ ) {
+               inp[ i ] = in[ outp[ i ] ];
+            }
+
+/* Initialise the index within the supplied CmpMap of the last (highest)
+   input in the current component Mapping. */
+            itop = -1;
+
+/* Initialise the index within the supplied CmpMap of the first (lowest)
+   output for the current component Mapping. */
+            ibotout = 0;
+
+/* Initialise the index within the supplied CmpMap of the current picked input. */
+            iin = 0;
+
+/* Initialise the index of the next returned output index. */
+            ncout = 0;
+
+/* Loop round all the component Mappings that are combined in series to form
+   the supplied CmpMap. */
+            for( imap = 0; imap < nmap && cout; imap++ ) {
+
+/* Temporarily reset the Invert attribute within the component Mapping back
+   to the value it had when the CmpMap was created. */
+               old_inv = astGetInvert( map_list[ imap ] );
+               astSetInvert(  map_list[ imap ], invert_list[ imap ] );
+
+/* Get the index within the supplied CmpMap of the first (lowest) input in
+   the current component Mapping. */
+               ibot = itop + 1;
+
+/* Get the index within the supplied CmpMap of the last (highest) input in
+   the current component Mapping. */
+               itop += astGetNin( map_list[ imap ] );
+
+/* Get the zero-based indices of the required inputs that feed the current
+   component Mapping. */
+               ncin = 0;
+               while( iin < nin && inp[ iin ] <= itop ) {
+                  cin[ ncin++ ] = inp[ iin++ ] - ibot;
+               }
+
+/* Skip components from which no inputs are being picked. */
+               if( ncin > 0 ) {
+
+/* Attempt to split the component Mapping using the current list of inputs. */
+                  aout = MapSplit1( map_list[ imap ], ncin, cin, &amap,
+                                    status );
+
+/* If successful... */
+                  if( amap ) {
+
+/* Correct the output indices so that they refer to the numbering scheme
+   of the total CmpMap, and append to the total list of output indices. */
+                     naout = astGetNout( amap );
+                     for( iout = 0; iout < naout; iout++ ) {
+                        cout[ ncout++ ] = aout[ iout ] + ibotout;
+                     }
+
+/* Combine the split Mapping in parallel with the earlier split Mappings. */
+                     if( *map ) {
+                        bmap = (AstMapping *) astCmpMap( *map, amap, 0, " ",
+                                                         status );
+                        amap = astAnnul( amap );
+                        (void) astAnnul( *map );
+                        *map = bmap;
+                     } else {
+                        *map = amap;
+                     }
+
+/* If the component Mapping could not be split, free the cout array to
+   indicate that no more component Mappings need be considered. */
+                  } else {
+                     cout = astFree( cout );
+                  }
+
+/* Free remaining resources. */
+                  aout = astFree( aout );
+               }
+
+/* Update the index within the supplied CmpMap of the first (lowest) output in
+   the next component Mapping. */
+               ibotout += astGetNout( map_list[ imap ] );
+
+/* Re-instate the original value of the Invert attribute within the
+   commponent Mapping. */
+               astSetInvert(  map_list[ imap ], old_inv );
+            }
+
+/* If the requested inputs could be split from the total CmpMap, add in any
+   PermMap needed to re-order the inputs. */
+            if( cout && ncout ){
+               if( doperm ) {
+                  bmap = (AstMapping *) astCmpMap( pmap, *map, 1, "", status );
+                  (void) astAnnul( *map );
+                  *map = bmap;
+               }
+
+/* Also return the list of output indices. */
+               result = cout;
+               cout = NULL;
+            }
+
+/* Free remaining resources. */
+            if( pmap ) pmap = astAnnul( pmap );
+         }
+         outp = astFree( outp );
+         inp = astFree( inp );
+         cin = astFree( cin );
+         cout = astFree( cout );
+      }
+
+/* Loop to annul all the Mapping pointers in the list. */
+      for ( i = 0; i < nmap; i++ ) map_list[ i ] = astAnnul( map_list[ i ] );
+
+/* Free the dynamic arrays. */
+      map_list = astFree( map_list );
+      invert_list = astFree( invert_list );
+
+   }
+
+/* Mappings that have no outputs cannot be used. */
+   if( !result && *map ) *map = astAnnul( *map );
+
+/* If the above method failed to split the CmpMap, we attempt to split the
+   inverse transformation by selecting every possible sub-set of Mapping
+   outputs until one is found which is fed by the requested mapping inputs. */
+   if( !result && !reentry ) result = MapSplit2( this_mapping, nin, in, map,
+                                                 status );
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this, int nin, const int *in,
+                      AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     CmpMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in,
+*                    AstMapping **map, int *status )
+
+*  Class Membership:
+*     CmpMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function is the main entry point for the astMapSplit method.
+*     It is a simple wrapper for MapSplit0 which calls MapSplit0
+*     indicating that this is a top-level entry.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpMap to be split (the CmpMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied CmpMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied CmpMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied CmpMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+   return MapSplit0( this, nin, in, map, 0, status );
+}
+
+static int PatternCheck( int val, int check, int **list, int *list_len, int *status ){
+/*
+*  Name:
+*     Looping
+
+*  Purpose:
+*     Check for repeating patterns in a set of integer values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     int PatternCheck( int val, int nmap, int **mlist, int **nlist, int *list_len )
+
+*  Class Membership:
+*     CmpMap member function.
+
+*  Description:
+*     This function appends a supplied integer to a dynamic list, creating
+*     or expanding the list if necessary.It then optionally, check the
+*     list for evidence of repeating patterns. If such a pattern is
+*     found, its wavelength is returned.
+
+*  Parameters:
+*     val
+*        The integer value to add to the list.
+*     check
+*        Should a check for reating patterns be performed?
+*     list
+*        Address of a location at which is stored a pointer to an array
+*        holding the values supplied on previous invocations of this
+*        function. If a NULL pointer is supplied a new array is allocated.
+*        On exit, the supplied value is appended to the end of the array. The
+*        array is extended as necessary. The returned pointer should be
+*        freed using astFree when no longer needed.
+*     list_len
+*        Address of a location at which is stored the number of elements
+*        in the "list" array.
+
+*  Returned Value:
+*     A non-zero "wavelength" value is returned if there is a repeating
+*     pattern is found in the "list" array. Otherwise, zero is returned.
+*     The "wavelength" is the number of integer values which constitute a
+*     single instance of the pattern.
+
+*  Notes:
+*     - A value of 1 is returned if this function is invoked with the AST
+*     error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   int *wave[ 30 ];          /* Pointers to start of waves */
+   int iat;                  /* Index of elements added by this invocation */
+   int jat;                  /* Index of element condiered next */
+   int jlo;                  /* Earliest "mlist" entry to consider */
+   int k;                    /* Index of element within pattern */
+   int mxwave;               /* Max pattern length to consider */
+   int iwave;                /* Index of current wave */
+   int nwave;                /* Number of waves required to mark a pattern */
+   int result;               /* Returned flag */
+   int wavelen;              /* Current pattern length */
+
+/* Check the global status. */
+   if ( !astOK ) return 1;
+
+/* Initialise */
+   result = 0;
+
+/* If no array has been supplied, create a new array. */
+   if( !(*list) ) {
+      *list = astMalloc( 100*sizeof( int ) );
+      *list_len = 0;
+   }
+
+/* Store the new value in the array, extending it if necessary. */
+   iat = (*list_len)++;
+   *list = astGrow( *list, *list_len, sizeof( int ) );
+   if( astOK ) {
+      (*list)[ iat ] = val;
+
+/* If required, determine the maximum "wavelength" for looping patterns to be
+   checked, and store the earliest list entry to consider. We take 3 complete
+   patterns as evidence of looping, but we only do the check when the
+   list length is at least 30. */
+      if( check && *list_len > 29 ){
+         mxwave = iat/3;
+         if( mxwave > 50 ) mxwave = 50;
+         jlo = iat - 3*mxwave;
+
+/* Search backwards from the end of "list" looking for the most recent
+   occurence of the supplied "val" value. Limit the search to
+   wavelengths of no more than the above limit. */
+         jat = iat - 1;
+         while( jat >= jlo ) {
+            if( (*list)[ jat ] == val ) {
+
+/* When an earlier occurrence of "val" is found, see if the values
+   which precede it are the same as the values which precede the new
+   element if "list" added by this invocation. We use 3 complete
+   patterns as evidence of looping, unless the wavelength is 1 in which
+   case we use 30 patterns (this is because wavelengths of 1 can occur
+   in short sequences legitamately). */
+               wavelen = iat - jat;
+
+               if( wavelen == 1 ) {
+                  nwave = 30;
+                  if( nwave > iat ) nwave = iat;
+               } else {
+                  nwave = 3;
+               }
+
+               if( nwave*wavelen <= *list_len ) {
+                  result = wavelen;
+                  wave[ 0 ] = *list + *list_len - wavelen;
+                  for( iwave = 1; iwave < nwave; iwave++ ) {
+                     wave[ iwave ] = wave[ iwave - 1 ] - wavelen;
+                  }
+
+                  for( k = 0; k < wavelen; k++ ) {
+                     for( iwave = 1; iwave < nwave; iwave++ ) {
+                        if( *wave[ iwave ] != *wave[ 0 ] ) {
+                           result = 0;
+                           break;
+                        }
+                        wave[ iwave ]++;
+                     }
+                     wave[ 0 ]++;
+                  }
+               }
+
+/* Break if we have found a repeating pattern. */
+               if( result ) break;
+
+            }
+            jat--;
+         }
+      }
+   }
+
+   if( !astOK ) result= 1;
+
+/* Return the result.*/
+   return result;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     CmpMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*/
+
+/* Local Variables: */
+   AstMapping *c1;
+   AstMapping *c2;
+   AstCmpMap *map;
+   double result;
+   int old_inv1;
+   int old_inv2;
+   int nin1;
+   int nin2;
+   double *at2;
+   double r1;
+   double r2;
+   int nout1;
+   int i;
+
+/* Check inherited status */
+   if( !astOK ) return AST__BAD;
+
+/* Get a pointer to the CmpMap structure. */
+   map = (AstCmpMap *) this;
+
+/* Note the current Invert flags of the two component Mappings. */
+   old_inv1 = astGetInvert( map->map1 );
+   old_inv2 = astGetInvert( map->map2 );
+
+/* Temporarily reset them to the values they had when the CmpMap was
+   created. */
+   astSetInvert( map->map1, map->invert1 );
+   astSetInvert( map->map2, map->invert2 );
+
+/* If the CmpMap itself has been inverted, invert the component Mappings.
+   Also note the order in which the Mappings should be applied if in series. */
+   if( !astGetInvert( this ) ) {
+      c1 = map->map1;
+      c2 = map->map2;
+   } else {
+      c1 = map->map2;
+      c2 = map->map1;
+      astInvert( c1 );
+      astInvert( c2 );
+   }
+
+/* First deal with Mappings in series. */
+   if( map->series ) {
+
+/* Get the number of inputs to the two component Mappings. */
+      nin1 = astGetNin( c1 );
+      nin2 = astGetNin( c2 );
+
+/* Allocate workspace to hold the result of transforming the supplied "at"
+   position using the first component. */
+      at2 = astMalloc( sizeof( double )*(size_t) nin2 );
+
+/* Transform the supplied "at" position using the first component. */
+      astTranN( c1, 1, nin1, 1, at, 1, nin2, 1, at2 );
+
+/* The required rate of change is the sum of the products of the rate of
+   changes of the two component mappings, summed over all the output axes
+   of the first componment. */
+      result = 0.0;
+      for( i = 0; i < nin2; i++ ) {
+
+/* Find the rate of change of output "i" of the first component with
+   respect to input "ax2" at the supplied "at" position. */
+         r1 = astRate( c1, at, i, ax2 );
+
+/* Find the rate of change of output "ax1" of the second component with
+   respect to input "i" at the transformed "at2" position. */
+         r2 = astRate( c2, at2, ax1, i );
+
+/* If both are good, increment the ryunning total by the product of the
+   two rates. Otherwise, break. */
+         if( r1 != AST__BAD && r2 != AST__BAD ) {
+            result += r1*r2;
+         } else {
+            result = AST__BAD;
+            break;
+         }
+      }
+
+/* Free the workspace. */
+      at2 = astFree( at2 );
+
+/* Now deal with Mappings in parallel. */
+   } else {
+
+/* Get the number of inputs and outputs for the lower component Mappings. */
+      nin1 = astGetNin( map->map1 );
+      nout1 = astGetNout( map->map1 );
+
+/* If both input and output relate to the lower component Mappings, use its
+   astRate method. */
+      if( ax1 < nout1 && ax2 < nin1 ) {
+         result = astRate( map->map1, at, ax1, ax2 );
+
+/* If both input and output relate to the upper component Mappings, use its
+   astRate method. */
+      } else if( ax1 >= nout1 && ax2 >= nin1 ) {
+         result = astRate( map->map2, at + nin1, ax1 - nout1, ax2 - nin1 );
+
+/* If input and output relate to different component Mappings, return
+   zero. */
+      } else {
+         result = 0.0;
+      }
+   }
+
+/* Reinstate the original Invert flags of the component Mappings .*/
+   astSetInvert( map->map1, old_inv1 );
+   astSetInvert( map->map2, old_inv2 );
+
+/* Return the result. */
+   return result;
+}
+
+static AstMapping *RemoveRegions( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     RemoveRegions
+
+*  Purpose:
+*     Remove any Regions from a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     AstMapping *RemoveRegions( AstMapping *this, int *status )
+
+*  Class Membership:
+*     CmpMap method (over-rides the astRemoveRegions method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function searches the supplied Mapping (which may be a
+*     compound Mapping such as a CmpMap) for any component Mappings
+*     that are instances of the AST Region class. It then creates a new
+*     Mapping from which all Regions have been removed. If a Region
+*     cannot simply be removed (for instance, if it is a component of a
+*     parallel CmpMap), then it is replaced with an equivalent UnitMap
+*     in the returned Mapping.
+*
+*     The implementation provided by the CmpMap class invokes the
+*     astRemoveRegions method on the two component Mappings, and joins
+*     the results together into a new CmpMap.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the modified mapping.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstCmpMap *new;               /* Pointer to new CmpMap */
+   AstCmpMap *this;              /* Pointer to CmpMap structure */
+   AstMapping *newmap1;          /* New first component Mapping */
+   AstMapping *newmap2;          /* New second component Mapping */
+   AstMapping *result;           /* Result pointer to return */
+   int nax;                      /* Number of Frame axes */
+   int unit1;                    /* Is new first Mapping a UnitMap? */
+   int unit2;                    /* Is new second Mapping a UnitMap? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the CmpMap. */
+   this = (AstCmpMap *) this_mapping;
+
+/* Invoke the astRemoveRegions method on the two component Mappings. */
+   newmap1 = astRemoveRegions( this->map1 );
+   newmap2 = astRemoveRegions( this->map2 );
+
+/* If neither component was modified, just return a clone of the supplied
+   pointer. */
+   if( this->map1 == newmap1 && this->map2 == newmap2 ) {
+      result = astClone( this );
+
+/* Otherwise, we need to create a new Mapping to return. */
+   } else {
+
+/* The implementation of the astRemoveRegions method provided by the
+   Region class returns a Frame rather than a UnitMap. But we need
+   Mappings here, not Frames. So if either of these new Mappings is
+   a Frame, replace it with an equivalent UnitMap. Also, get flags
+   indicating if either Mapping is a UnitMap.*/
+      if( astIsAFrame( newmap1 ) ) {
+         nax = astGetNin( newmap1 );
+         (void) astAnnul( newmap1 );
+         newmap1 = (AstMapping *) astUnitMap( nax, " ", status );
+         unit1 = 1;
+      } else {
+         unit1 = astIsAUnitMap( newmap1 );
+      }
+
+      if( astIsAFrame( newmap2 ) ) {
+         nax = astGetNin( newmap2 );
+         (void) astAnnul( newmap2 );
+         newmap2 = (AstMapping *) astUnitMap( nax, " ", status );
+         unit2 = 1;
+      } else {
+         unit2 = astIsAUnitMap( newmap2 );
+      }
+
+/* First handle series CmpMaps. */
+      if( this->series ) {
+
+/* Otherwise, if the second new Mapping is a UnitMap, return a copy of the
+   first new Mapping (with the original Invert attribute) since the second
+   one will have no effect. */
+         if( unit1 ) {
+            result = astCopy( newmap2 );
+            astSetInvert( result, this->invert2 );
+            if( astGetInvert( this ) ) astInvert( result );
+
+/* Otherwise, if the second new Mapping is a UnitMap, return a copy of the
+   first new Mapping (with the original Invert attribute) since the second
+   one will have no effect. */
+         } else if( unit2 ) {
+            result = astCopy( newmap1 );
+            astSetInvert( result, this->invert1 );
+            if( astGetInvert( this ) ) astInvert( result );
+
+/* If neither of the new Mappings is a UnitMap, return a new CmpMap
+   containing the two new Mappings. We take a deep copy of the supplied
+   CmpMap and then modify the Mappings os that we retain any extra
+   information (such as invert flags) in the supplied CmpMap. */
+         } else {
+            new = astCopy( this );
+            (void) astAnnul( new->map1 );
+            (void) astAnnul( new->map2 );
+            new->map1 = astClone( newmap1 );
+            new->map2 = astClone( newmap2 );
+            result = (AstMapping *) new;
+         }
+
+/* Now handle parallel CmpMaps. */
+      } else {
+
+/* If both new Mappings are UnitMaps, return an equivalent UnitMap. */
+         if( unit1 && unit2 ) {
+            result = (AstMapping *) astUnitMap( astGetNin( newmap1 ) +
+                                                astGetNin( newmap2 ), " ",
+                                                status );
+
+/* Otherwise, return a new CmpMap containing the two new Mappings. */
+         } else {
+            new = astCopy( this );
+            (void) astAnnul( new->map1 );
+            (void) astAnnul( new->map2 );
+            new->map1 = astClone( newmap1 );
+            new->map2 = astClone( newmap2 );
+            result = (AstMapping *) new;
+         }
+      }
+   }
+
+/* Free resources. */
+   newmap1 = astAnnul( newmap1 );
+   newmap2 = astAnnul( newmap2 );
+
+/* Annul the returned Mapping if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     CmpMap method (over-rides the astSimplify method inherited from
+*     the Mapping class).
+
+*  Description:
+*     This function simplifies a CmpMap to eliminate redundant
+*     computational steps, or to merge separate steps which can be
+*     performed more efficiently in a single operation.
+
+*  Parameters:
+*     this
+*        Pointer to the original Mapping.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A new pointer to the (possibly simplified) Mapping.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstCmpMap *this;              /* Pointer to CmpMap structure */
+   AstMapping **map_list;        /* Mapping array pointer */
+   AstMapping *map;              /* Pointer to cloned Mapping pointer */
+   AstMapping *result;           /* Result pointer to return */
+   AstMapping *tmp;              /* Temporary Mapping pointer */
+   int *invert_list;             /* Invert array pointer */
+   int *mlist;                   /* Point to list of modified Mapping indices */
+   int *nlist;                   /* Point to list of Mapping counts */
+   int i;                        /* Loop counter for Mappings */
+   int improved;                 /* Simplification achieved? */
+   int invert;                   /* Invert attribute value */
+   int invert_n;                 /* Invert value for final Mapping */
+   int mlist_len;                /* No. of entries in mlist */
+   int nlist_len;                /* No. of entries in nlist */
+   int modified;                 /* Index of first modified Mapping */
+   int nmap;                     /* Mapping count */
+   int nominated;                /* Index of nominated Mapping */
+   int set;                      /* Invert attribute set? */
+   int set_n;                    /* Invert set for final Mapping? */
+   int simpler;                  /* Simplification possible? */
+   int t;                        /* Temporary storage */
+   int wlen1;                    /* Pattern wavelength for "modified" values */
+   int wlen2;                    /* Pattern wavelength for "nmap" values */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_mapping);
+
+/* It is possible for the astSimplify method to be called recursively from
+   within astSimplify. It is also possible that the Mapping being
+   simplified by the current invocation is the same as the Mapping being
+   simplified by some recursive invocation higher up the call stack. If
+   this happens we will get into an infinite loop, since we already know
+   that simplifying the supplied Mapping will involve (eventually) a
+   recursive call to astSimplify with the same Mapping. To avoid this
+   looping, we note the Mappings supplied at each depth and first compare
+   the supplied Mapping with the Mappings which are currently being
+   simplified higher up the call stack. If the supplied Mapping is
+   already being simplified at a higher level, then we return immediately
+   without doing any simplification. Otherwise, we record the supplied
+   Mapping pointer in a static list so that it is available to subsequent
+   recursive invocations of this function. First compare the supplied
+   Mapping with the Mappingsbeing simpliied higher up. Return without
+   action if a match is found. */
+   for( i = 0; i < simplify_depth; i++ ) {
+      if( astEqual( this_mapping, simplify_stackmaps[ i ] ) ) {
+         return astClone( this_mapping );
+      }
+   }
+
+/* We have further work to do, so increment the recursion depth, extend
+   the simplify_stackmaps array, and store the new Mapping in it for future use. */
+   simplify_depth++;
+   simplify_stackmaps = astGrow( simplify_stackmaps, simplify_depth, sizeof( AstMapping * ) );
+   if( astOK ) {
+      simplify_stackmaps[ simplify_depth - 1 ] = astClone( this_mapping );
+   }
+
+/* Obtain a pointer to the CmpMap structure. */
+   this = (AstCmpMap *) this_mapping;
+
+/* Initialise dynamic arrays of Mapping pointers and associated Invert
+   flags. */
+   nmap = 0;
+   map_list = NULL;
+   invert_list = NULL;
+
+/* Decompose the CmpMap into a sequence of Mappings to be applied in
+   series or parallel, as appropriate, and an associated list of
+   Invert flags. If any inverted CmpMaps are found in the Mapping, then
+   we can at least simplify the returned Mapping by swapping and
+   inverting the components. Set "simpler" to indicate this. */
+   simpler = astMapList( this_mapping, this->series, astGetInvert( this ), &nmap,
+                         &map_list, &invert_list );
+
+/* Each Mapping has a flag that indicates if the mapping is frozen (i.e. cannot
+   be nominated for simplification). Mappings become frozen if nominating them
+   would create an infinite loop in which neighbouring mappings argue as to
+   their form. Freezing a mapping prevents the frozen mapping contributing any
+   further to the argument, so the other Mapping "wins" the argument.
+   Ensure no Mappings are frozen to begin with. */
+   for( i = 0; i < nmap; i++ ) {
+      map_list[ i ]->flags &= ~AST__FROZEN_FLAG;
+   }
+
+/* Initialise pointers to memory used to hold lists of the modified
+   Mapping index and the number of mappings after each call of
+   astMapMerge. */
+   mlist = NULL;
+   nlist = NULL;
+
+/* Loop to simplify the sequence until a complete pass through it has
+   been made without producing any improvement. */
+   improved = 1;
+   while ( astOK && improved ) {
+      improved = 0;
+
+/* Loop to nominate each Mapping in the sequence in turn. */
+      nominated = 0;
+      while ( astOK && ( nominated < nmap ) ) {
+
+/* If the current nominated mapping has been frozen, then we do not allow
+   it to suggest changes to the mapping sequence. Instead, just increment
+   the index of the next mapping to be checked and continue on to the next
+   pass round the while loop. */
+         if( map_list[ nominated ]->flags & AST__FROZEN_FLAG ) {
+            nominated++;
+            continue;
+         }
+
+/* Clone a pointer to the nominated Mapping and attempt to merge it
+   with its neighbours. Annul the cloned pointer afterwards. */
+         map = astClone( map_list[ nominated ] );
+         modified = astMapMerge( map, nominated, this->series,
+                                 &nmap, &map_list, &invert_list );
+         map = astAnnul( map );
+
+/* Move on to nominate the next Mapping in the sequence. */
+         nominated++;
+
+/* Note if any simplification occurred above. */
+         if( modified >= 0 && astOK ) {
+
+/* Append the index of the first modified Mapping in the list and and check
+   that there is no repreating pattern in the list. If there is, we are
+   probably in a loop where one mapping class is making a change, and another
+   is undoing the change. The Looping function returns the "wavelength"
+   of any pattern found. If a pattern was discovered, we ignore it unless
+   there is also a pattern in the "nmap" values - the wavelengths of the
+   two patterns must be related by a integer factor. */
+            wlen1 = PatternCheck( modified, 1, &mlist, &mlist_len, status );
+            wlen2 = PatternCheck( nmap, wlen1, &nlist, &nlist_len, status );
+            if( wlen1 && wlen2 ) {
+
+/* Ensure wlen2 is larger than or equal to wlen1. */
+               if( wlen1 > wlen2 ) {
+                  t = wlen1;
+                  wlen1 = wlen2;
+                  wlen2 = t;
+               }
+
+/* See if wlen2 is an integer multiple of wlen1. If not, ignore the
+   patterns. */
+               if( ( wlen2 % wlen1 ) != 0 ) wlen1 = 0;
+            }
+
+/* If a repeating pattern is occurring, set the frozen flag in order to
+   prevent the modified mapping from being modified any more. */
+            if( wlen1 > 0 ) {
+               map_list[ modified ]->flags |= AST__FROZEN_FLAG;
+
+/* Otherwise, indicate we have improved the mapping and go round to test
+   the next nominated mapping. */
+            } else {
+               improved = 1;
+               simpler = 1;
+
+/* If the simplification resulted in modification of an earlier
+   Mapping than would normally be considered next, then go back to
+   consider the modified one first. */
+               if ( modified < nominated ) nominated = modified;
+            }
+         }
+      }
+   }
+
+/* Free resources */
+   mlist = astFree( mlist );
+   nlist = astFree( nlist );
+
+/* Construct the output Mapping. */
+/* ============================= */
+/* If no simplification occurred above, then simply clone a pointer to
+   the original Mapping. */
+   if ( astOK ) {
+      if ( !simpler ) {
+         result = astClone( this );
+
+/* Otherwise, we must construct the result from the contents of the
+   Mapping list. */
+      } else {
+
+/* If the simplified Mapping list has only a single element, then the
+   output Mapping will not be a CmpMap. In this case, we cannot
+   necessarily set the Invert flag of the Mapping to the value we want
+   (because we must not modify the Mapping itself. */
+         if ( nmap == 1 ) {
+
+/* We must make a copy. Cloning is no good (even if the Mapping already
+   has the Invert attribute value we want), since we want the returned
+   Mapping to be independent of the original component Mappings, so that
+   if user code inverts a component Mapping (via some other pre-existing
+   pointer), the returned simplified Mapping is not affected. */
+            result = astCopy( map_list[ 0 ] );
+
+/* Either clear the copy's Invert attribute, or set it to 1, as
+   required. */
+            if ( invert_list[ 0 ] ) {
+               astSetInvert( result, 1 );
+            } else {
+               astClearInvert( result );
+            }
+
+/* If the simplified Mapping sequence has more than one element, the
+   output Mapping will be a CmpMap. In this case, we can set each
+   individual Mapping element to have the Invert attribute value we
+   want, so long as we return these attribute values to their original
+   state again afterwards (once a Mapping is encapsulated inside a
+   CmpMap, further external changes to its Invert attribute do not
+   affect the behaviour of the CmpMap). */
+         } else {
+
+/* Determine if the Invert attribute for the last Mapping is set, and
+   obtain its value. */
+            set_n = astTestInvert( map_list[ nmap - 1 ] );
+            invert_n = astGetInvert( map_list[ nmap - 1 ] );
+
+/* Set this attribute to the value we want. */
+            astSetInvert( map_list[ nmap - 1 ], invert_list[ nmap - 1 ] );
+
+/* Loop through the Mapping sequence in reverse to merge it into an
+   equivalent CmpMap. */
+            for ( i = nmap - 1; i >= 0; i-- ) {
+
+/* Simply clone the pointer to the last Mapping in the sequence (which
+   will be encountered first). */
+              if ( !result ) {
+                  result = astClone( map_list[ i ] );
+
+/* For subsequent Mappings, test if the Invert attribute is set and
+   save its value. */
+               } else {
+                  set = astTestInvert( map_list[ i ] );
+                  invert = astGetInvert( map_list[ i ] );
+
+/* Set this attribute to the value required. */
+                  astSetInvert( map_list[ i ], invert_list[ i ] );
+
+/* Combine the Mapping with the CmpMap formed so far and replace the
+   result pointer with the new pointer this produces, annulling the
+   previous pointer. */
+                  tmp = (AstMapping *) astCmpMap( map_list[ i ], result,
+                                                  this->series, "", status );
+                  (void) astAnnul( result );
+                  result = tmp;
+
+/* Restore the Invert attribute of the Mapping to its original
+   state. */
+                  if ( !set ) {
+                     astClearInvert( map_list[ i ] );
+                  } else {
+                     astSetInvert( map_list[ i ], invert );
+                  }
+               }
+            }
+
+/* When all the Mappings have been merged into the CmpMap, restore the
+   state of the Invert attribute for the final Mapping in the
+   sequence. */
+            if ( !set_n ) {
+               astClearInvert( map_list[ nmap - 1  ] );
+            } else {
+               astSetInvert( map_list[ nmap - 1 ], invert_n );
+            }
+         }
+      }
+   }
+
+/* Clean up. */
+/* ========= */
+/* Loop to annul all the Mapping pointers in the simplified list. */
+   for ( i = 0; i < nmap; i++ ) map_list[ i ] = astAnnul( map_list[ i ] );
+
+/* Free the dynamic arrays. */
+   map_list = astFree( map_list );
+   invert_list = astFree( invert_list );
+
+/* Decrement the recursion depth and free the pointer to the supplied
+   Mapping currently stored at the end of the simplify_stackmaps array. */
+   simplify_depth--;
+   if( astOK ) {
+      simplify_stackmaps[ simplify_depth ] = astAnnul( simplify_stackmaps[ simplify_depth ] );
+   }
+
+/* If we are now at depth zero, free the simplify_stackmaps array. */
+   if( simplify_depth == 0 ) simplify_stackmaps = astFree( simplify_stackmaps );
+
+/* If an error occurred, annul the returned Mapping. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a CmpMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     CmpMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a CmpMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required Mapping.
+*     This implies applying each of the CmpMap's component Mappings in turn,
+*     either in series or in parallel.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpMap.
+*     in
+*        Pointer to the PointSet associated with the input coordinate values.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the CmpMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstCmpMap *map;               /* Pointer to CmpMap to be applied */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstPointSet *temp1;           /* Pointer to temporary PointSet */
+   AstPointSet *temp2;           /* Pointer to temporary PointSet */
+   AstPointSet *temp;            /* Pointer to temporary PointSet */
+   int forward1;                 /* Use forward direction for Mapping 1? */
+   int forward2;                 /* Use forward direction for Mapping 2? */
+   int ipoint1;                  /* Index of first point in batch */
+   int ipoint2;                  /* Index of last point in batch */
+   int nin1;                     /* No. input coordinates for Mapping 1 */
+   int nin2;                     /* No. input coordinates for Mapping 2 */
+   int nin;                      /* No. input coordinates supplied */
+   int nout1;                    /* No. output coordinates for Mapping 1 */
+   int nout2;                    /* No. output coordinates for Mapping 2 */
+   int nout;                     /* No. output coordinates supplied */
+   int np;                       /* Number of points in batch */
+   int npoint;                   /* Number of points to be transformed */
+
+/* Local Constants: */
+   const int nbatch = 2048;      /* Maximum points in a batch */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the CmpMap. */
+   map = (AstCmpMap *) this;
+
+/* Apply the parent Mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We now extend the parent astTransform method by applying the component
+   Mappings of the CmpMap to generate the output coordinate values. */
+
+/* Determine whether to apply the forward or inverse Mapping, according to the
+   direction specified and whether the Mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* Check if either component Mapping's inversion flag has changed since it was
+   used to construct the CmpMap. Set a "forward" flag for each Mapping to
+   change the direction we will use, to compensate if necessary. (Such changes
+   may have occurred if other pointers to the component Mappings are in
+   circulation). */
+   forward1 = forward;
+   forward2 = forward;
+   if ( map->invert1 != astGetInvert( map->map1 ) ) forward1 = !forward1;
+   if ( map->invert2 != astGetInvert( map->map2 ) ) forward2 = !forward2;
+
+/* Determine the number of points being transformed. */
+   npoint = astGetNpoint( in );
+
+/* Mappings in series. */
+/* ------------------- */
+/* If required, use the two component Mappings in series. To do this, we must
+   apply one Mapping followed by the other, which means storing an intermediate
+   result. Since this function may be invoked recursively and have to store an
+   intermediate result on each occasion, the memory required may become
+   excessive when transforming large numbers of points. To overcome this, we
+   split the points up into smaller batches. */
+   if ( astOK ) {
+      if ( map->series ) {
+
+/* Obtain the numbers of input and output coordinates. */
+         nin = astGetNcoord( in );
+         nout = astGetNcoord( result );
+
+/* Loop to process all the points in batches, of maximum size nbatch points. */
+         for ( ipoint1 = 0; ipoint1 < npoint; ipoint1 += nbatch ) {
+
+/* Calculate the index of the final point in the batch and deduce the number of
+   points (np) to be processed in this batch. */
+            ipoint2 = ipoint1 + nbatch - 1;
+            if ( ipoint2 > npoint - 1 ) ipoint2 = npoint - 1;
+            np = ipoint2 - ipoint1 + 1;
+
+/* Create temporary PointSets to describe the input and output points for this
+   batch. */
+            temp1 = astPointSet( np, nin, "", status );
+            temp2 = astPointSet( np, nout, "", status );
+
+/* Associate the required subsets of the input and output coordinates with the
+   two PointSets. */
+            astSetSubPoints( in, ipoint1, 0, temp1 );
+            astSetSubPoints( result, ipoint1, 0, temp2 );
+
+/* Apply the two Mappings in sequence and in the required order and direction.
+   Store the intermediate result in a temporary PointSet (temp) which is
+   created by the first Mapping applied. */
+            if ( forward ) {
+               temp = astTransform( map->map1, temp1, forward1, NULL );
+               (void) astTransform( map->map2, temp, forward2, temp2 );
+            } else {
+               temp = astTransform( map->map2, temp1, forward2, NULL );
+               (void) astTransform( map->map1, temp, forward1, temp2 );
+            }
+
+/* Delete the temporary PointSets after processing each batch of points. */
+            temp = astDelete( temp );
+            temp1 = astDelete( temp1 );
+            temp2 = astDelete( temp2 );
+
+/* Quit processing batches if an error occurs. */
+            if ( !astOK ) break;
+         }
+
+/* Mappings in parallel. */
+/* --------------------- */
+/* If required, use the two component Mappings in parallel. Since we do not
+   need to allocate any memory to hold intermediate coordinate values here,
+   there is no need to process the points in batches. */
+      } else {
+
+/* Get the effective number of input and output coordinates per point for each
+   Mapping (taking account of the direction in which each will be used to
+   transform points). */
+         nin1 = forward1 ? astGetNin( map->map1 ) : astGetNout( map->map1 );
+         nout1 = forward1 ? astGetNout( map->map1 ) : astGetNin( map->map1 );
+         nin2 = forward2 ? astGetNin( map->map2 ) : astGetNout( map->map2 );
+         nout2 = forward2 ? astGetNout( map->map2 ) : astGetNin( map->map2 );
+
+/* Create temporary PointSets to describe the input and output coordinates for
+   the first Mapping. */
+         temp1 = astPointSet( npoint, nin1, "", status );
+         temp2 = astPointSet( npoint, nout1, "", status );
+
+/* Associate the required subsets of the input and output coordinates with
+   these PointSets. */
+         astSetSubPoints( in, 0, 0, temp1 );
+         astSetSubPoints( result, 0, 0, temp2 );
+
+/* Use the astTransform method to apply the coordinate transformation described
+   by the first Mapping. */
+         (void) astTransform( map->map1, temp1, forward1, temp2 );
+
+/* Delete the temporary PointSets. */
+         temp1 = astDelete( temp1 );
+         temp2 = astDelete( temp2 );
+
+/* Create a new pair of temporary PointSets to describe the input and output
+   coordinates for the second Mapping, and associate the required subsets of
+   the input and output coordinates with these PointSets. */
+         temp1 = astPointSet( npoint, nin2, "", status );
+         temp2 = astPointSet( npoint, nout2, "", status );
+         astSetSubPoints( in, 0, nin1, temp1 );
+         astSetSubPoints( result, 0, nout1, temp2 );
+
+/* Apply the coordinate transformation described by the second Mapping. */
+         (void) astTransform( map->map2, temp1, forward2, temp2 );
+
+/* Delete the two temporary PointSets. */
+         temp1 = astDelete( temp1 );
+         temp2 = astDelete( temp2 );
+      }
+   }
+
+/* If an error occurred, clean up by deleting the output PointSet (if
+   allocated by this function) and setting a NULL result pointer. */
+   if ( !astOK ) {
+      if ( !out ) result = astDelete( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for CmpMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for CmpMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the component
+*     Mappings within the CmpMap.
+*/
+
+/* Local Variables: */
+   AstCmpMap *in;                /* Pointer to input CmpMap */
+   AstCmpMap *out;               /* Pointer to output CmpMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output CmpMaps. */
+   in = (AstCmpMap *) objin;
+   out = (AstCmpMap *) objout;
+
+/* For safety, start by clearing any references to the input component
+   Mappings from the output CmpMap. */
+   out->map1 = NULL;
+   out->map2 = NULL;
+
+/* Make copies of these Mappings and store pointers to them in the output
+   CmpMap structure. */
+   out->map1 = astCopy( in->map1 );
+   out->map2 = astCopy( in->map2 );
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for CmpMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for CmpMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstCmpMap *this;              /* Pointer to CmpMap */
+
+/* Obtain a pointer to the CmpMap structure. */
+   this = (AstCmpMap *) obj;
+
+/* Annul the pointers to the component Mappings. */
+   this->map1 = astAnnul( this->map1 );
+   this->map2 = astAnnul( this->map2 );
+
+/* Clear the remaining CmpMap variables. */
+   this->invert1 = 0;
+   this->invert2 = 0;
+   this->series = 0;
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for CmpMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the CmpMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstCmpMap *this;              /* Pointer to the CmpMap structure */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpMap structure. */
+   this = (AstCmpMap *) this_object;
+
+/* Write out values representing the instance variables for the CmpMap
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Series. */
+/* ------- */
+   ival = this->series;
+   set = ( ival == 0 );
+   astWriteInt( channel, "Series", set, 0, ival,
+                ival ? "Component Mappings applied in series" :
+                       "Component Mappings applied in parallel" );
+
+/* First Invert flag. */
+/* ------------------ */
+   ival = this->invert1;
+   set = ( ival != 0 );
+   astWriteInt( channel, "InvA", set, 0, ival,
+                ival ? "First Mapping used in inverse direction" :
+                       "First Mapping used in forward direction" );
+
+/* Second Invert flag. */
+/* ------------------- */
+   ival = this->invert2;
+   set = ( ival != 0 );
+   astWriteInt( channel, "InvB", set, 0, ival,
+                ival ? "Second Mapping used in inverse direction" :
+                       "Second Mapping used in forward direction" );
+
+/* First Mapping. */
+/* -------------- */
+   astWriteObject( channel, "MapA", 1, 1, this->map1,
+                   "First component Mapping" );
+
+/* Second Mapping. */
+/* --------------- */
+   astWriteObject( channel, "MapB", 1, 1, this->map2,
+                   "Second component Mapping" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsACmpMap and astCheckCmpMap functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(CmpMap,Mapping)
+astMAKE_CHECK(CmpMap)
+
+AstCmpMap *astCmpMap_( void *map1_void, void *map2_void, int series,
+                       const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astCmpMap
+
+*  Purpose:
+*     Create a CmpMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     AstCmpMap *astCmpMap( AstMapping *map1, AstMapping *map2, int series,
+*                           const char *options, ... )
+
+*  Class Membership:
+*     CmpMap constructor.
+
+*  Description:
+*     This function creates a new CmpMap and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     map1
+*        Pointer to the first Mapping.
+*     map2
+*        Pointer to the second Mapping.
+*     series
+*        If a non-zero value is given, the two Mappings will be connected
+*        together in series. A zero value requests that they be connected in
+*        parallel.
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new CmpMap. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new CmpMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic CmpMap constructor which is
+*     available via the protected interface to the CmpMap class.  A
+*     public interface is provided by the astCmpMapId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "map1" and "map2" parameters are of type (void *) and are
+*     converted and validated within the function itself.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstCmpMap *new;               /* Pointer to new CmpMap */
+   AstMapping *map1;             /* Pointer to first Mapping structure */
+   AstMapping *map2;             /* Pointer to second Mapping structure */
+   va_list args;                 /* Variable argument list */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain and validate pointers to the Mapping structures provided. */
+   map1 = astCheckMapping( map1_void );
+   map2 = astCheckMapping( map2_void );
+   if ( astOK ) {
+
+/* Initialise the CmpMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitCmpMap( NULL, sizeof( AstCmpMap ), !class_init, &class_vtab,
+                           "CmpMap", map1, map2, series );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new CmpMap's
+   attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new CmpMap. */
+   return new;
+}
+
+AstCmpMap *astCmpMapId_( void *map1_void, void *map2_void, int series,
+                         const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astCmpMap
+f     AST_CMPMAP
+
+*  Purpose:
+*     Create a CmpMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "cmpmap.h"
+c     AstCmpMap *astCmpMap( AstMapping *map1, AstMapping *map2, int series,
+c                           const char *options, ... )
+f     RESULT = AST_CMPMAP( MAP1, MAP2, SERIES, OPTIONS, STATUS )
+
+*  Class Membership:
+*     CmpMap constructor.
+
+*  Description:
+*     This function creates a new CmpMap and optionally initialises
+*     its attributes.
+*
+*     A CmpMap is a compound Mapping which allows two component
+*     Mappings (of any class) to be connected together to form a more
+*     complex Mapping. This connection may either be "in series"
+*     (where the first Mapping is used to transform the coordinates of
+*     each point and the second mapping is then applied to the
+*     result), or "in parallel" (where one Mapping transforms the
+*     earlier coordinates for each point and the second Mapping
+*     simultaneously transforms the later coordinates).
+*
+*     Since a CmpMap is itself a Mapping, it can be used as a
+*     component in forming further CmpMaps. Mappings of arbitrary
+*     complexity may be built from simple individual Mappings in this
+*     way.
+
+*  Parameters:
+c     map1
+f     MAP1 = INTEGER (Given)
+*        Pointer to the first component Mapping.
+c     map2
+f     MAP2 = INTEGER (Given)
+*        Pointer to the second component Mapping.
+c     series
+f     SERIES = LOGICAL (Given)
+c        If a non-zero value is given for this parameter, the two
+c        component Mappings will be connected in series. A zero
+c        value requests that they are connected in parallel.
+f        If a .TRUE. value is given for this argument, the two
+f        component Mappings will be connected in series. A
+f        .FALSE. value requests that they are connected in parallel.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new CmpMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new CmpMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astCmpMap()
+f     AST_CMPMAP = INTEGER
+*        A pointer to the new CmpMap.
+
+*  Notes:
+*     - If the component Mappings are connected in series, then using
+*     the resulting CmpMap to transform coordinates will cause the
+*     first Mapping to be applied, followed by the second Mapping. If
+*     the inverse CmpMap transformation is requested, the two
+*     component Mappings will be applied in both the reverse order and
+*     the reverse direction.
+*     - When connecting two component Mappings in series, the number
+*     of output coordinates generated by the first Mapping (its Nout
+*     attribute) must equal the number of input coordinates accepted
+*     by the second Mapping (its Nin attribute).
+*     - If the component Mappings of a CmpMap are connected in
+*     parallel, then the first Mapping will be used to transform the
+*     earlier input coordinates for each point (and to produce the
+*     earlier output coordinates) and the second Mapping will be used
+*     simultaneously to transform the remaining input coordinates (to
+*     produce the remaining output coordinates for each point). If the
+*     inverse transformation is requested, each Mapping will still be
+*     applied to the same coordinates, but in the reverse direction.
+*     - When connecting two component Mappings in parallel, there is
+*     no restriction on the number of input and output coordinates for
+*     each Mapping.
+c     - Note that the component Mappings supplied are not copied by
+c     astCmpMap (the new CmpMap simply retains a reference to
+c     them). They may continue to be used for other purposes, but
+c     should not be deleted. If a CmpMap containing a copy of its
+c     component Mappings is required, then a copy of the CmpMap should
+c     be made using astCopy.
+f     - Note that the component Mappings supplied are not copied by
+f     AST_CMPMAP (the new CmpMap simply retains a reference to
+f     them). They may continue to be used for other purposes, but
+f     should not be deleted. If a CmpMap containing a copy of its
+f     component Mappings is required, then a copy of the CmpMap should
+f     be made using AST_COPY.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astCmpMap constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astCmpMap_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - Because no checking or casting of arguments is performed
+*     before the function is invoked, the "map1" and "map2" parameters
+*     are of type (void *) and are converted from an ID value to a
+*     pointer and validated within the function itself.
+*     - The variable argument list also prevents this function from
+*     invoking astCmpMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the conversions between IDs
+*     and pointers on input/output of Objects.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstCmpMap *new;               /* Pointer to new CmpMap */
+   AstMapping *map1;             /* Pointer to first Mapping structure */
+   AstMapping *map2;             /* Pointer to second Mapping structure */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain the Mapping pointers from the ID's supplied and validate the
+   pointers to ensure they identify valid Mappings. */
+   map1 = astVerifyMapping( astMakePointer( map1_void ) );
+   map2 = astVerifyMapping( astMakePointer( map2_void ) );
+   if ( astOK ) {
+
+/* Initialise the CmpMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitCmpMap( NULL, sizeof( AstCmpMap ), !class_init, &class_vtab,
+                           "CmpMap", map1, map2, series );
+
+/* If successful, note that the virtual function table has been initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new CmpMap's
+   attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return an ID value for the new CmpMap. */
+   return astMakeId( new );
+}
+
+AstCmpMap *astInitCmpMap_( void *mem, size_t size, int init,
+                           AstCmpMapVtab *vtab, const char *name,
+                           AstMapping *map1, AstMapping *map2, int series, int *status ) {
+/*
+*+
+*  Name:
+*     astInitCmpMap
+
+*  Purpose:
+*     Initialise a CmpMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     AstCmpMap *astInitCmpMap( void *mem, size_t size, int init,
+*                               AstCmpMapVtab *vtab, const char *name,
+*                               AstMapping *map1, AstMapping *map2,
+*                               int series )
+
+*  Class Membership:
+*     CmpMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new CmpMap object. It allocates memory (if necessary) to
+*     accommodate the CmpMap plus any additional data associated with the
+*     derived class. It then initialises a CmpMap structure at the start
+*     of this memory. If the "init" flag is set, it also initialises the
+*     contents of a virtual function table for a CmpMap at the start of
+*     the memory passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the CmpMap is to be initialised.
+*        This must be of sufficient size to accommodate the CmpMap data
+*        (sizeof(CmpMap)) plus any data used by the derived class. If a
+*        value of NULL is given, this function will allocate the memory itself
+*        using the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the CmpMap (plus derived class
+*        data). This will be used to allocate memory if a value of NULL is
+*        given for the "mem" parameter. This value is also stored in the
+*        CmpMap structure, so a valid value must be supplied even if not
+*        required for allocating memory.
+*     init
+*        A logical flag indicating if the CmpMap's virtual function table
+*        is to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new CmpMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     map1
+*        Pointer to the first Mapping.
+*     map2
+*        Pointer to the second Mapping.
+*     series
+*        If a non-zero value is given, the two Mappings will be connected
+*        together in series. A zero value requests that they be connected in
+*        parallel.
+
+*  Returned Value:
+*     A pointer to the new CmpMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstCmpMap *new;               /* Pointer to new CmpMap */
+   int map_f;                    /* Forward transformation defined? */
+   int map_i;                    /* Inverse transformation defined? */
+   int nin2;                     /* No. input coordinates for Mapping 2 */
+   int nin;                      /* No. input coordinates for CmpMap */
+   int nout1;                    /* No. output coordinates for Mapping 1 */
+   int nout;                     /* No. output coordinates for CmpMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitCmpMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Determine in which directions each component Mapping is able to transform
+   coordinates. Combine these results to obtain a result for the overall
+   CmpMap. */
+   map_f = astGetTranForward( map1 ) && astGetTranForward( map2 );
+   map_i = astGetTranInverse( map1 ) && astGetTranInverse( map2 );
+   if ( astOK ) {
+
+/* If connecting the Mappings in series, check that the number of coordinates
+   are compatible and report an error if they are not. */
+      if ( series ) {
+         nout1 = astGetNout( map1 );
+         nin2 = astGetNin( map2 );
+         if ( astOK && ( nout1 != nin2 ) ) {
+            astError( AST__INNCO, "astInitCmpMap(%s): The number of output "
+                      "coordinates per point (%d) for the first Mapping "
+                      "supplied does not match the number of input "
+                      "coordinates (%d) for the second Mapping.", status, name, nout1,
+                      nin2 );
+         }
+      }
+   }
+
+/* If OK, determine the total number of input and output coordinates per point
+   for the CmpMap. */
+   if ( astOK ) {
+      if ( series ) {
+         nin = astGetNin( map1 );
+         nout = astGetNout( map2 );
+      } else {
+         nin = astGetNin( map1 ) + astGetNin( map2 );
+         nout = astGetNout( map1 ) + astGetNout( map2 );
+      }
+
+   } else {
+      nin = 0;
+      nout = 0;
+   }
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the CmpMap structure, allocating memory if necessary. Specify
+   the number of input and output coordinates and in which directions the
+   Mapping should be defined. */
+   if ( astOK ) {
+      new = (AstCmpMap *) astInitMapping( mem, size, 0,
+                                          (AstMappingVtab *) vtab, name,
+                                          nin, nout, map_f, map_i );
+
+      if ( astOK ) {
+
+/* Initialise the CmpMap data. */
+/* --------------------------- */
+/* Store pointers to the component Mappings. Extract Mappings if
+   FrameSets are provided. */
+         if( astIsAFrameSet( map1 ) ) {
+            new->map1 = astGetMapping( (AstFrameSet *) map1, AST__BASE,
+                                       AST__CURRENT );
+         } else {
+            new->map1 = astClone( map1 );
+         }
+
+         if( astIsAFrameSet( map2 ) ) {
+            new->map2 = astGetMapping( (AstFrameSet *) map2, AST__BASE,
+                                       AST__CURRENT );
+         } else {
+            new->map2 = astClone( map2 );
+         }
+
+
+/* Save the initial values of the inversion flags for these Mappings. */
+         new->invert1 = astGetInvert( new->map1 );
+         new->invert2 = astGetInvert( new->map2 );
+
+/* Note whether the Mappings are joined in series (instead of in parallel),
+   constraining this flag to be 0 or 1. */
+         new->series = ( series != 0 );
+
+/* If an error occurred, clean up by annulling the Mapping pointers and
+   deleting the new object. */
+         if ( !astOK ) {
+            new->map1 = astAnnul( new->map1 );
+            new->map2 = astAnnul( new->map2 );
+            new = astDelete( new );
+         }
+      }
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstCmpMap *astLoadCmpMap_( void *mem, size_t size,
+                           AstCmpMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadCmpMap
+
+*  Purpose:
+*     Load a CmpMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpmap.h"
+*     AstCmpMap *astLoadCmpMap( void *mem, size_t size,
+*                               AstCmpMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     CmpMap loader.
+
+*  Description:
+*     This function is provided to load a new CmpMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     CmpMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a CmpMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the CmpMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        CmpMap data (sizeof(CmpMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the CmpMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the CmpMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstCmpMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new CmpMap. If this is NULL, a pointer to
+*        the (static) virtual function table for the CmpMap class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "CmpMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new CmpMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstCmpMap *new;               /* Pointer to the new CmpMap */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this CmpMap. In this case the
+   CmpMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstCmpMap );
+      vtab = &class_vtab;
+      name = "CmpMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitCmpMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built CmpMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "CmpMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Series. */
+/* ------- */
+      new->series = astReadInt( channel, "series", 1 );
+      new->series = ( new->series != 0 );
+
+/* First Invert flag. */
+/* ------------------ */
+      new->invert1 = astReadInt( channel, "inva", 0 );
+      new->invert1 = ( new->invert1 != 0 );
+
+/* Second Invert flag. */
+/* ------------------- */
+      new->invert2 = astReadInt( channel, "invb", 0 );
+      new->invert2 = ( new->invert2 != 0 );
+
+/* First Mapping. */
+/* -------------- */
+      new->map1 = astReadObject( channel, "mapa", NULL );
+
+/* Second Mapping. */
+/* --------------- */
+      new->map2 = astReadObject( channel, "mapb", NULL );
+
+/* If an error occurred, clean up by deleting the new CmpMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new CmpMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+/* None. */
+
+
+
+
+
+
+
+
diff --git a/ast/cmpmap.h b/ast/cmpmap.h
new file mode 100644
index 0000000..ed305bb
--- /dev/null
+++ b/ast/cmpmap.h
@@ -0,0 +1,300 @@
+#if !defined( CMPMAP_INCLUDED )  /* Include this file only once */
+#define CMPMAP_INCLUDED
+/*
+*+
+*  Name:
+*     cmpmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the CmpMap class.
+
+*  Invocation:
+*     #include "cmpmap.h"
+
+*  Description:
+*     This include file defines the interface to the CmpMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc. needed to use this class.
+*
+*     A CmpMap is a compound Mapping which allows two component
+*     Mappings (of any class) to be connected together to form a more
+*     complex Mapping. This connection may either be "in series"
+*     (where the first Mapping is used to transform the coordinates of
+*     each point and the second mapping is then applied to the
+*     result), or "in parallel" (where one Mapping transforms the
+*     earlier coordinates for each point and the second Mapping
+*     simultaneously transforms the later coordinates).
+*
+*     Since a CmpMap is itself a Mapping, it can be used as a
+*     component in forming further CmpMaps. Mappings of arbitrary
+*     complexity may be built from simple individual Mappings in this
+*     way.
+
+*  Inheritance:
+*     The CmpMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        astSimplify
+*           Simplify a CmpMap.
+*
+*     Protected:
+*        astMapList
+*           Decompose a CmpMap into a sequence of simpler Mappings.
+*        astTransform
+*           Transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsACmpMap
+*           Test class membership.
+*        astCmpMap
+*           Create a CmpMap.
+*
+*     Protected:
+*        astCheckCmpMap
+*           Validate class membership.
+*        astInitCmpMap
+*           Initialise a CmpMap.
+*        astInitCmpMapVtab
+*           Initialise the virtual function table for the CmpMap class.
+*        astLoadCmpMap
+*           Load a CmpMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstCmpMap
+*           CmpMap object type.
+*
+*     Protected:
+*        AstCmpMapVtab
+*           CmpMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     6-FEB-1996 (RFWS):
+*        Original version.
+*     25-SEP-1996 (RFWS):
+*        Implemented external interface and I/O facilities.
+*     13-DEC-1996 (RFWS):
+*        Over-ride the astSimplify method.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitCmpMapVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* CmpMap structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstCmpMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstMapping *map1;              /* Pointer to first Mapping */
+   AstMapping *map2;              /* Pointer to second Mapping */
+   char invert1;                  /* Inversion flag for first Mapping */
+   char invert2;                  /* Inversion flag for second Mapping */
+   char series;                   /* Connect in series (else in parallel)? */
+} AstCmpMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstCmpMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+/* None. */
+} AstCmpMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstCmpMapGlobals {
+   AstCmpMapVtab Class_Vtab;
+   int Class_Init;
+   int Simplify_Depth;
+   AstMapping **Simplify_Stackmaps;
+} AstCmpMapGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(CmpMap)           /* Check class membership */
+astPROTO_ISA(CmpMap)             /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstCmpMap *astCmpMap_( void *, void *, int, const char *, int *, ...);
+#else
+AstCmpMap *astCmpMapId_( void *, void *, int, const char *, ... )__attribute__((format(printf,4,5)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstCmpMap *astInitCmpMap_( void *, size_t, int, AstCmpMapVtab *,
+                           const char *, AstMapping *, AstMapping *, int, int * );
+
+/* Vtab initialiser. */
+void astInitCmpMapVtab_( AstCmpMapVtab *, const char *, int * );
+
+/* Loader. */
+AstCmpMap *astLoadCmpMap_( void *, size_t, AstCmpMapVtab *,
+                           const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitCmpMapGlobals_( AstCmpMapGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+/* None. */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckCmpMap(this) astINVOKE_CHECK(CmpMap,this,0)
+#define astVerifyCmpMap(this) astINVOKE_CHECK(CmpMap,this,1)
+
+/* Test class membership. */
+#define astIsACmpMap(this) astINVOKE_ISA(CmpMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astCmpMap astINVOKE(F,astCmpMap_)
+#else
+#define astCmpMap astINVOKE(F,astCmpMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitCmpMap(mem,size,init,vtab,name,map1,map2,series) \
+astINVOKE(O,astInitCmpMap_(mem,size,init,vtab,name,astCheckMapping(map1),astCheckMapping(map2),series,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitCmpMapVtab(vtab,name) astINVOKE(V,astInitCmpMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadCmpMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadCmpMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckCmpMap to validate CmpMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+/* None. */
+#endif
+
+
+
+
+
diff --git a/ast/cmpregion.c b/ast/cmpregion.c
new file mode 100644
index 0000000..c3b5b07
--- /dev/null
+++ b/ast/cmpregion.c
@@ -0,0 +1,5127 @@
+/*
+*class++
+*  Name:
+*     CmpRegion
+
+*  Purpose:
+*     A combination of two regions within a single Frame
+
+*  Constructor Function:
+c     astCmpRegion
+f     AST_CMPREGION
+
+*  Description:
+*     A CmpRegion is a Region which allows two component
+*     Regions (of any class) to be combined to form a more complex
+*     Region. This combination may be performed a boolean AND, OR
+*     or XOR (exclusive OR) operator. If the AND operator is
+*     used, then a position is inside the CmpRegion only if it is
+*     inside both of its two component Regions. If the OR operator is
+*     used, then a position is inside the CmpRegion if it is inside
+*     either (or both) of its two component Regions. If the XOR operator
+*     is used, then a position is inside the CmpRegion if it is inside
+*     one but not both of its two component Regions. Other operators can
+*     be formed by negating one or both component Regions before using
+*     them to construct a new CmpRegion.
+*
+*     The two component Region need not refer to the same coordinate
+*     Frame, but it must be possible for the
+c     astConvert
+f     AST_CONVERT
+*     function to determine a Mapping between them (an error will be
+*     reported otherwise when the CmpRegion is created). For instance,
+*     a CmpRegion may combine a Region defined within an ICRS SkyFrame
+*     with a Region defined within a Galactic SkyFrame. This is
+*     acceptable because the SkyFrame class knows how to convert between
+*     these two systems, and consequently the
+c     astConvert
+f     AST_CONVERT
+*     function will also be able to convert between them. In such cases,
+*     the second component Region will be mapped into the coordinate Frame
+*     of the first component Region, and the Frame represented by the
+*     CmpRegion as a whole will be the Frame of the first component Region.
+*
+*     Since a CmpRegion is itself a Region, it can be used as a
+*     component in forming further CmpRegions. Regions of arbitrary
+*     complexity may be built from simple individual Regions in this
+*     way.
+
+*  Inheritance:
+*     The CmpRegion class inherits from the Region class.
+
+*  Attributes:
+*     The CmpRegion class does not define any new attributes beyond those
+*     which are applicable to all Regions.
+
+*  Functions:
+c     The CmpRegion class does not define any new functions beyond those
+f     The CmpRegion class does not define any new routines beyond those
+*     which are applicable to all Regions.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     7-OCT-2004 (DSB):
+*        Original version.
+*     28-MAY-2007 (DSB):
+*        - Corrected RegBaseMesh.
+*        - In RegBaseBox, if the CmpRegion is bounded find the box by
+*        finding the extreme position sin a mesh covering the boundary.
+*     20-JAN-2009 (DSB):
+*        Over-ride astRegBasePick.
+*     19-MAR-2009 (DSB):
+*        Over-ride the astDecompose method.
+*     8-SEP-2009 (DSB):
+*        Fix logic in RegTrace.
+*     9-SEP-2009 (DSB):
+*        - Added astCmpRegionList
+*        - Added support for XOR
+*        - Override astGetObjSize.
+*     27-APR-2012 (DSB):
+*        - Cache the bounded property.
+*        - Speed up plotting of CmpRegions by using the cached negation
+*        of a Region instead of setting the Regions's Negated flag (which
+*        causes the Region's cache to be cleared).
+*     30-APR-2012 (DSB):
+*        Use geodesic distance to measure distances around the two component
+*        Regions when tracing the border. Previously, a distance normalised
+*        from zero to one was used for both component Regions, but this gives
+*        greater priority to Regions higher in the CmpRegion nesting order,
+*        resulting in a high chance that lower Regions will not be seen.
+*     7-JUN-2012 (DSB):
+*        Override astRegSplit method.
+*     21-NOV-2012 (DSB):
+*        Map the regions returned by RegSplit into the current Frame of the
+*        CmpRegion.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS CmpRegion
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "region.h"              /* Regions (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "nullregion.h"          /* Boundless Regions */
+#include "cmpregion.h"           /* Interface definition for this class */
+#include "unitmap.h"             /* Unit Mapings */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstRegion *(* parent_getdefunc)( AstRegion *, int * );
+static void (* parent_setregfs)( AstRegion *, AstFrame *, int * );
+static AstMapping *(* parent_simplify)( AstMapping *, int * );
+static int (* parent_equal)( AstObject *, AstObject *, int * );
+static void (* parent_setclosed)( AstRegion *, int, int * );
+static void (* parent_setmeshsize)( AstRegion *, int, int * );
+static void (* parent_clearclosed)( AstRegion *, int * );
+static void (* parent_clearmeshsize)( AstRegion *, int * );
+static double (*parent_getfillfactor)( AstRegion *, int * );
+static void (*parent_regsetattrib)( AstRegion *, const char *, char **, int * );
+static void (*parent_regclearattrib)( AstRegion *, const char *, char **, int * );
+static void (* parent_resetcache)( AstRegion *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(CmpRegion)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(CmpRegion,Class_Init)
+#define class_vtab astGLOBAL(CmpRegion,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstCmpRegionVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstCmpRegion *astCmpRegionId_( void *, void *, int, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstRegion *GetDefUnc( AstRegion *, int * );
+static AstRegion *MatchRegion( AstRegion *, int, AstRegion *, const char *, int * );
+static AstRegion *RegBasePick( AstRegion *this, int, const int *, int * );
+static AstRegion **RegSplit( AstRegion *, int *, int * );
+static double GetFillFactor( AstRegion *, int * );
+static int CmpRegionList( AstCmpRegion *, int *, AstRegion ***, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetBounded( AstRegion *, int * );
+static int GetObjSize( AstObject *, int * );
+static int RegPins( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+static int RegTrace( AstRegion *, int, double *, double **, int * );
+static void ClearClosed( AstRegion *, int * );
+static void ClearMeshSize( AstRegion *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Decompose( AstMapping *, AstMapping **, AstMapping **, int *, int *, int *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void GetRegions( AstCmpRegion *, AstRegion **, AstRegion **, int *, int *, int *, int * );
+static void RegBaseBox( AstRegion *, double *, double *, int * );
+static void RegBaseBox2( AstRegion *, double *, double *, int * );
+static void RegClearAttrib( AstRegion *, const char *, char **, int * );
+static void RegSetAttrib( AstRegion *, const char *, char **, int * );
+static void ResetCache( AstRegion *this, int * );
+static void SetBreakInfo( AstCmpRegion *, int, int * );
+static void SetClosed( AstRegion *, int, int * );
+static void SetMeshSize( AstRegion *, int, int * );
+static void SetRegFS( AstRegion *, AstFrame *, int * );
+static void XORCheck( AstCmpRegion *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+/* Member functions. */
+/* ================= */
+int CmpRegionList( AstCmpRegion *this, int *nreg, AstRegion ***reg_list,
+                   int *status ) {
+/*
+*+
+*  Name:
+*     astCmpRegionList
+
+*  Purpose:
+*     Decompose a CmpRegion into a sequence of simpler Regions.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     int astCmpRegionList( AstCmpRegion *this, int *nreg,
+*                           AstRegion ***reg_list, int *status )
+
+*  Class Membership:
+*     CmpRegion method.
+
+*  Description:
+*     This function decomposes a CmpRegion into a sequence of simpler
+*     Regions which may be applied in sequence to achieve the same
+*     effect.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpRegion to be decomposed (the CmpRegion is not
+*        actually modified by this function).
+*     nreg
+*        The address of an int which holds a count of the number of
+*        individual Regions in the decomposition. On entry, this
+*        should count the number of Regions already in the
+*        "*reg_list" array (below). On exit, it is updated to include
+*        any new Regions appended by this function.
+*     reg_list
+*        Address of a pointer to an array of Region pointers. On
+*        entry, this array pointer should either be NULL (if no
+*        Regions have yet been obtained) or should point at a
+*        dynamically allocated array containing Region pointers
+*        ("*nreg" in number) which have been obtained from a previous
+*        invocation of this function.
+*
+*        On exit, the dynamic array will be enlarged to contain any
+*        new Region pointers that result from the decomposition
+*        requested. These pointers will be appended to any previously
+*        present, and the array pointer will be updated as necessary
+*        to refer to the enlarged array (any space released by the
+*        original array will be freed automatically).
+*
+*        The new Region pointers returned will identify a sequence of
+*        Region which, when applied in order, will represent an area
+*        equivalent to that of the original Region.
+*
+*        All the Region pointers returned by this function should be
+*        annulled by the caller, using astAnnul, when no longer
+*        required. The dynamic array holding these pointers should
+*        also be freed, using astFree.
+
+*  Returned Value:
+*     An integer identifying the boolean operation that should be used to
+*     combine the Regions returned in "reg_list". This will be AST__AND
+*     or AST__OR.
+
+*-
+*/
+
+/* Local Variables: */
+   AstCmpRegion *cmpreg;
+   int add;
+   int result;
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__AND;
+
+/* Check if this CmpRegion has an equivalent XOR representation. Is so,
+   store details of the XOR representation in the CmpRegion. */
+   XORCheck( this, status );
+
+/* The CmpRegion class only has full support for AND and OR operators.
+   However, it can also represent XOR operators, but it does this by
+   an equivalent set of AND and OR operators. When an XOR CmpRegion is
+   created, the original supplied argument regions are stored in
+   "this->xor1" and "this->xor2", and the component Regions placed in the
+   new CmpRegion are actually CmpRegions that implement the equivalent
+   of an XOR operation, using AND and OR operators. We want to hide this
+   to the outside world, so if the supplied CmpRegion represents an XOR
+   operation, add the XOR regions to the returned list, and return an
+   XOR operator. */
+   if( this->xor1 ) {
+      *reg_list = astGrow( *reg_list, *nreg + 2, sizeof( AstRegion * ) );
+      if( astOK ) {
+         ( *reg_list )[ (*nreg)++ ] = astClone( this->xor1 );
+         ( *reg_list )[ (*nreg)++ ] = astClone( this->xor2 );
+      }
+      result = AST__XOR;
+
+/* For AND and OR operators, we deal with the component Regions directly. */
+   } else {
+
+/* If the first component of the supplied CmpRegion is itself a CmpRegion
+   that uses the same boolean operator as "this", call this function
+   recursively to add its component Regions to the returned list. */
+      add = 1;
+      if( astIsACmpRegion( this->region1 ) ) {
+         cmpreg = (AstCmpRegion *) this->region1;
+         if( cmpreg->oper == this->oper ) {
+            (void) CmpRegionList( cmpreg, nreg, reg_list, status );
+            add = 0;
+         }
+      }
+
+/* Otherwise, add the component Region directly into the returned list of
+   Regions. */
+      if( add ) {
+         *reg_list = astGrow( *reg_list, *nreg + 1, sizeof( AstRegion * ) );
+         if( astOK ) {
+            ( *reg_list )[ *nreg ] = astClone( this->region1 );
+            ( *nreg )++;
+         }
+      }
+
+/* Do the same for the second component region */
+      add = 1;
+      if( astIsACmpRegion( this->region2 ) ) {
+         cmpreg = (AstCmpRegion *) this->region2;
+         if( cmpreg->oper == this->oper ) {
+            (void) CmpRegionList( cmpreg, nreg, reg_list, status );
+            add = 0;
+         }
+      }
+
+      if( add ) {
+         *reg_list = astGrow( *reg_list, *nreg + 1, sizeof( AstRegion * ) );
+         if( astOK ) {
+            ( *reg_list )[ *nreg ] = astClone( this->region2 );
+            ( *nreg )++;
+         }
+      }
+
+      result = this->oper;
+   }
+
+/* Return the boolean operator used to combine the regions in the
+   returned array. */
+   return result;
+}
+
+static void Decompose( AstMapping *this_mapping, AstMapping **map1,
+                       AstMapping **map2, int *series, int *invert1,
+                       int *invert2, int *status ) {
+/*
+*
+*  Name:
+*     Decompose
+
+*  Purpose:
+*     Decompose a CmpRegion into two component Regions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void Decompose( AstMapping *this, AstMapping **map1,
+*                     AstMapping **map2, int *series,
+*                     int *invert1, int *invert2, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the protected astDecompose
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns pointers to two Mappings which, when applied
+*     either in series or parallel, are equivalent to the supplied Mapping.
+*
+*     Since the Frame class inherits from the Mapping class, Frames can
+*     be considered as special types of Mappings and so this method can
+*     be used to decompose either CmpMaps, CmpFrames, CmpRegions or Prisms.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     map1
+*        Address of a location to receive a pointer to first component
+*        Mapping.
+*     map2
+*        Address of a location to receive a pointer to second component
+*        Mapping.
+*     series
+*        Address of a location to receive a value indicating if the
+*        component Mappings are applied in series or parallel. A non-zero
+*        value means that the supplied Mapping is equivalent to applying map1
+*        followed by map2 in series. A zero value means that the supplied
+*        Mapping is equivalent to applying map1 to the lower numbered axes
+*        and map2 to the higher numbered axes, in parallel.
+*     invert1
+*        The value of the Invert attribute to be used with map1.
+*     invert2
+*        The value of the Invert attribute to be used with map2.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Any changes made to the component rames using the returned
+*     pointers will be reflected in the supplied CmpFrame.
+
+*-
+*/
+
+
+/* Local Variables: */
+   AstCmpRegion *this;              /* Pointer to CmpRegion structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpMap structure. */
+   this = (AstCmpRegion *) this_mapping;
+
+/* The components Frames of a CmpRegion are considered to be series
+   Mappings. */
+   if( series ) *series = 1;
+
+/* The Frames are returned in their original order whether or not the
+   CmpRegion has been inverted. */
+   if( map1 ) *map1 = astClone( this->region1 );
+   if( map2 ) *map2 = astClone( this->region2 );
+
+/* The invert flags dont mean anything for a Region, but we return them
+   anyway. If the CmpRegion has been inverted, return inverted Invert flags. */
+   if( astGetInvert( this ) ) {
+      if( invert1 ) *invert1 = astGetInvert( this->region1 ) ? 0 : 1;
+      if( invert2 ) *invert2 = astGetInvert( this->region2 ) ? 0 : 1;
+
+/* If the CmpRegion has not been inverted, return the current Invert flags. */
+   } else {
+      if( invert1 ) *invert1 = astGetInvert( this->region1 );
+      if( invert2 ) *invert2 = astGetInvert( this->region2 );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two Objects are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     int Equal( AstObject *this_object, AstObject *that_object, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astEqual protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two CmpRegions are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first CmpRegion.
+*     that
+*        Pointer to the second CmpRegion.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the CmpRegions are equivalent, zero otherwise.
+
+*  Notes:
+*     - The CmpRegions are equivalent if their component Regions are
+*     equivalent and if they have the same boolean operation, negation
+*     and closed flags.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpRegion *that;
+   AstCmpRegion *this;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the Equal method inherited from the parent Region class. This checks
+   that the Objects are both of the same class, and have the same Negated
+   and Closed flags (amongst other things). */
+   if( (*parent_equal)( this_object, that_object, status ) ) {
+
+/* Obtain pointers to the two CmpRegion structures. */
+      this = (AstCmpRegion *) this_object;
+      that = (AstCmpRegion *) that_object;
+
+/* Test their first component Regions for equality. */
+      if( astEqual( this->region1, that->region1 ) ) {
+
+/* Test their second component Regions for equality. */
+         if( astEqual( this->region2, that->region2 ) ) {
+
+/* Test their boolean operator for equality. */
+            if( this->oper == that->oper ) result = 1;
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+/*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Define a function to set an attribute value for a CmpRegion.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     MAKE_SET(attribute,lattribute,type)
+
+*  Class Membership:
+*     Defined by the CmpRegion class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Set<Attribute>( AstRegion *this, <Type> value )
+*
+*     that sets the value of a specified Region attribute in the parent
+*     Region structure and also in the component Regions.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     lattribute
+*        Name of the attribute, all in lower case.
+*     type
+*        The C type of the attribute.
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attribute,lattribute,type) \
+static void Set##attribute( AstRegion *this_region, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstCmpRegion *this;         /* Pointer to the CmpRegion structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Use the parent method to set the value in the parent Region structure. */ \
+   (*parent_set##lattribute)( this_region, value, status ); \
+\
+/* Also set the value in the two component Regions. */ \
+   this = (AstCmpRegion *) this_region; \
+   astSet##attribute( this->region1, value ); \
+   astSet##attribute( this->region2, value ); \
+}
+
+/* Use the above macro to create accessors for the MeshSize and Closed attributes. */
+MAKE_SET(MeshSize,meshsize,int)
+MAKE_SET(Closed,closed,int)
+
+/* Undefine the macro. */
+#undef MAKE_SET
+
+/*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Define a function to clear an attribute value for a CmpRegion.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     MAKE_CLEAR(attribute,lattribute)
+
+*  Class Membership:
+*     Defined by the CmpRegion class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Clear<Attribute>( AstRegion *this )
+*
+*     that sets the value of a specified Region attribute in the parent
+*     Region structure and also in the component Regions.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     lattribute
+*        Name of the attribute, all in lower case.
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attribute,lattribute) \
+static void Clear##attribute( AstRegion *this_region, int *status ) { \
+\
+/* Local Variables: */ \
+   AstCmpRegion *this;         /* Pointer to the CmpRegion structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Use the parent method to clear the value in the parent Region structure. */ \
+   (*parent_clear##lattribute)( this_region, status ); \
+\
+/* Also clear the value in the two component Regions. */ \
+   this = (AstCmpRegion *) this_region; \
+   astClear##attribute( this->region1 ); \
+   astClear##attribute( this->region2 ); \
+}
+
+/* Use the above macro to create accessors for the MeshSize and Closed attributes. */
+MAKE_CLEAR(MeshSize,meshsize)
+MAKE_CLEAR(Closed,closed)
+
+/* Undefine the macro. */
+#undef MAKE_CLEAR
+
+static int GetBounded( AstRegion *this_region, int *status ) {
+/*
+*  Name:
+*     GetBounded
+
+*  Purpose:
+*     Is the Region bounded?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     int GetBounded( AstRegion *this, int *status )
+
+*  Class Membership:
+*     CmpRegion method (over-rides the astGetBounded method inherited from
+*     the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the Region is bounded.
+*     The implementation provided by the base Region class is suitable
+*     for Region sub-classes representing the inside of a single closed
+*     curve (e.g. Circle, Ellipse, Box, etc). Other sub-classes (such as
+*     CmpRegion, PointList, etc ) may need to provide their own
+*     implementations.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the Region is bounded. Zero otherwise.
+
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;        /* Pointer to CmpRegion structure */
+   AstRegion *reg1;           /* Pointer to first component Region */
+   AstRegion *reg2;           /* Pointer to second component Region */
+   int neg1;                  /* Negated flag to use with first component */
+   int neg2;                  /* Negated flag to use with second component */
+   int oper;                  /* Combination operator */
+   int overlap;               /* Nature of overlap between components */
+   int reg1b;                 /* Is the first component Region bounded?*/
+   int reg2b;                 /* Is the second component Region bounded?*/
+   int result;                /* Returned result */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_region;
+
+/* Only calculated a new value if there is no cached value in the Region. */
+   if( this->bounded == -INT_MAX ) {
+
+/* Get the component Regions, how they should be combined, and the
+   Negated values which should be used with them. The returned values
+   take account of whether the supplied CmpRegion has itself been Negated
+   or not. The returned Regions represent regions within the base Frame
+   of the FrameSet encapsulated by the parent Region structure. */
+      GetRegions( this, &reg1, &reg2, &oper, &neg1, &neg2, status );
+
+/* If the first component Region does not have the required value for
+   its "Negated" attribute, use the negation of "reg1" in place of "reg1"
+   itself. */
+      if( neg1 != astGetNegated( reg1 ) ) {
+         AstRegion *tmp = astGetNegation( reg1 );
+         (void) astAnnul( reg1 );
+         reg1 = tmp;
+      }
+
+/* If the second component Region does not have the required value for
+   its "Negated" attribute, use the negation of "reg2" in place of "reg2"
+   itself. */
+      if( neg2 != astGetNegated( reg2 ) ) {
+         AstRegion *tmp = astGetNegation( reg2 );
+         (void) astAnnul( reg2 );
+         reg2 = tmp;
+      }
+
+/* See if either of the component Regions is bounded. */
+      reg1b = astGetBounded( reg1 );
+      reg2b = astGetBounded( reg2 );
+
+/* If the regions are ANDed... */
+      if( oper == AST__AND ) {
+
+/* If either one of the two components are bounded, then the AND region is
+   bounded. */
+         if( reg1b || reg2b ) {
+            result = 1;
+
+/* If neither of the two components is bounded, then the AND region is
+   unbounded if there is partial or no overlap between them and is bounded
+   otherwise. */
+         } else {
+            overlap = astOverlap( reg1, reg2 );
+            if( overlap == 1 || overlap == 4 || overlap == 6 ) {
+               result = 0;
+            } else {
+               result = 1;
+            }
+         }
+
+/* If the regions are ORed... */
+      } else {
+
+/* If either one of the two components is unbounded, then the OR region is
+   unbounded. */
+         if( !reg1b || !reg2b ) {
+            result = 0;
+
+/* If both of the two components are bounded, then the OR region is also
+   bounded. */
+         } else {
+            result = 1;
+         }
+      }
+
+/* Free resources. */
+      reg1 = astAnnul( reg1 );
+      reg2 = astAnnul( reg2 );
+
+/* Cache the value in the CmpRegion. */
+      this->bounded = astOK ? result : -INT_MAX;
+   }
+
+/* Return zero if an error occurred. Otherwise, return the cached value. */
+   if( astOK ) {
+      result = ( this->bounded == -INT_MAX ) ? 0 : this->bounded;
+   } else {
+      result = 0;
+   }
+
+/* Return the required pointer. */
+   return result;
+}
+
+static double GetFillFactor( AstRegion *this_region, int *status ) {
+/*
+*  Name:
+*     GetFillFactor
+
+*  Purpose:
+*     Obtain the value of the FillFactor attribute for a CmpRegion.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     double GetFillFactor( AstRegion *this, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astGetFillFactor method inherited
+*     from the Region class).
+
+*  Description:
+*     This function returns the value of the FillFactor attribute for a
+*     CmpRegion.  A suitable default value is returned if no value has
+*     previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpRegion.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The FillFactor value to use.
+
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;
+   double result;
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Obtain a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_region;
+
+/* See if a FillFactor value has been set. If so, use the parent
+   astGetFillFactor  method to obtain it. */
+   if ( astTestFillFactor( this ) ) {
+      result = (*parent_getfillfactor)( this_region, status );
+
+/* Otherwise, we will generate a default value equal to the FillFactor values
+   of the first component Region. */
+   } else {
+      result = astGetFillFactor( this->region1 );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied CmpRegion,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpRegion.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;        /* Pointer to CmpRegion structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by this class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astGetObjSize( this->region1 );
+   result += astGetObjSize( this->region2 );
+   if( this->xor1 ) result += astGetObjSize( this->xor1 );
+   if( this->xor2 ) result += astGetObjSize( this->xor2 );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static void GetRegions( AstCmpRegion *this, AstRegion **reg1, AstRegion **reg2,
+                        int *oper, int *neg1, int *neg2, int *status ) {
+/*
+*
+*  Name:
+*     GetRegions
+
+*  Purpose:
+*     Get the component Regions of a CmpRegion.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void GetRegions( AstCmpRegion *this, AstRegion **reg1, AstRegion **reg2,
+*                      int *oper, int *neg1, int *neg2, int *status )
+
+*  Class Membership:
+*     CmpRegion member function
+
+*  Description:
+*     This function returns pointers to two Regions which, when applied
+*     using the returned boolean operator, are equivalent to the supplied
+*     Region. If the CmpRegion has been negated, then the returned operator
+*     and "negated" flags will be set such that they represent the
+*     negated CmpRegion.
+*
+*     The current Frames in both the returned component Regions will be
+*     equivalent to the base Frame in the FrameSet encapsulated by the
+*     parent Region structure.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpRegion.
+*     reg1
+*        Address of a location to receive a pointer to first component
+*        Region. The current Frame in this region will be equivalent to
+*        the base Frame in the FrameSet
+*     reg2
+*        Address of a location to receive a pointer to second component
+*        Region.
+*     oper
+*        Address of a location to receive a value indicating how the
+*        component Regions are combined together. This will be one of
+*        AST__AND or AST__OR
+*     neg1
+*        The value of the Negated attribute to be used with reg1.
+*     neg2
+*        The value of the Negated attribute to be used with reg2.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Any changes made to the component Regions using the returned
+*     pointers will be reflected in the supplied CmpRegion.
+
+*-
+*/
+
+/* Initialise */
+   if( reg1 ) *reg1 = NULL;
+   if( reg2 ) *reg2 = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Return the component Region pointers. */
+   if( reg1 ) *reg1 = astClone( this->region1 );
+   if( reg2 ) *reg2 = astClone( this->region2 );
+
+/* Initialise the other returned items. Note, the CmpRegion initialiser
+   stored a deep copy of the supplied component Regions, and so we do not
+   need to worry about attributes of the components having been changed
+   after the creation of the CmpRegion. This is different to the CmpMap
+   class which merely clones its supplied component pointers and so has
+   to save copies of the original Invert settings within the CmpMap
+   structure. */
+   if( oper ) *oper = this->oper;
+   if( neg1 ) *neg1 = astGetNegated( this->region1 );
+   if( neg2 ) *neg2 = astGetNegated( this->region2 );
+
+/* If the CmpRegion has been inverted, we modify the boolean operator and
+   negation flags so that they reflect the inverted CmpRegion. */
+   if( astGetNegated( this ) ) {
+
+/* If the component Regions are combined using AND, then the negated
+   CmpRegion combines its negated components using  OR. */
+      if( this->oper == AST__AND ){
+         if( oper ) *oper = AST__OR;
+         if( neg1 ) *neg1 = *neg1 ? 0 : 1;
+         if( neg2 ) *neg2 = *neg2 ? 0 : 1;
+
+/* If the component Regions are combined using OR, then the negated CmpRegion
+   combines its negated components using  AND. */
+      } else if( this->oper == AST__OR ){
+         if( oper ) *oper = AST__AND;
+         if( neg1 ) *neg1 = *neg1 ? 0 : 1;
+         if( neg2 ) *neg2 = *neg2 ? 0 : 1;
+
+      } else if( astOK ) {
+         astError( AST__INTER, "GetRegions(%s): The %s refers to an unknown "
+                   "boolean operator with identifier %d (internal AST "
+                   "programming error).", status, astGetClass( this ),
+                   astGetClass( this ), this->oper );
+      }
+   }
+}
+
+static AstRegion *GetDefUnc( AstRegion *this_region, int *status ) {
+/*
+*  Name:
+*     GetDefUnc
+
+*  Purpose:
+*     Obtain a pointer to the default uncertainty Region for a given Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     AstRegion *GetDefUnc( AstRegion *this )
+
+*  Class Membership:
+*     CmpRegion method (over-rides the astGetDefUnc method inherited from
+*     the Region class).
+
+*     This function returns a pointer to a Region which represents the
+*     default uncertainty associated with a position on the boundary of the
+*     given  Region. The returned Region refers to the base Frame within the
+*     FrameSet encapsulated by the supplied Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     A pointer to the Region. This should be annulled (using astAnnul)
+*     when no longer needed.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;        /* Pointer to CmpRegion structure */
+   AstRegion *result;         /* Returned pointer */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_region;
+
+/* If the first component region has non-default uncertainty, use it as
+   the default uncertainty for the CmpRegion. Note, the current Frame of
+   an uncertainty Region is assumed to be the same as the base Frame in the
+   CmpRegion. */
+   if( astTestUnc( this->region1 ) ) {
+      result = astGetUncFrm( this->region1, AST__CURRENT );
+
+/* Otherwise, if the second component region has non-default uncertainty,
+   use it as the default uncertainty for the CmpRegion. */
+   } else if( astTestUnc( this->region2 ) ) {
+      result = astGetUncFrm( this->region2, AST__CURRENT );
+
+/* Otherwise, use the parent method to determine the default uncertainty. */
+   } else {
+      result = (* parent_getdefunc)( this_region, status );
+   }
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the required pointer. */
+   return result;
+}
+
+void astInitCmpRegionVtab_(  AstCmpRegionVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitCmpRegionVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a CmpRegion.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void astInitCmpRegionVtab( AstCmpRegionVtab *vtab, const char *name )
+
+*  Class Membership:
+*     CmpRegion vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the CmpRegion class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstRegionVtab *region;        /* Pointer to Region component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitRegionVtab( (AstRegionVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsACmpRegion) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstRegionVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+   vtab->CmpRegionList = CmpRegionList;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   region = (AstRegionVtab *) vtab;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_simplify = mapping->Simplify;
+   mapping->Simplify = Simplify;
+
+   parent_getdefunc = region->GetDefUnc;
+   region->GetDefUnc = GetDefUnc;
+
+   parent_setregfs = region->SetRegFS;
+   region->SetRegFS = SetRegFS;
+
+   parent_resetcache = region->ResetCache;
+   region->ResetCache = ResetCache;
+
+   parent_equal = object->Equal;
+   object->Equal = Equal;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_clearclosed = region->ClearClosed;
+   region->ClearClosed = ClearClosed;
+
+   parent_clearmeshsize = region->ClearMeshSize;
+   region->ClearMeshSize = ClearMeshSize;
+
+   parent_setclosed = region->SetClosed;
+   region->SetClosed = SetClosed;
+
+   parent_setmeshsize = region->SetMeshSize;
+   region->SetMeshSize = SetMeshSize;
+
+   parent_getfillfactor = region->GetFillFactor;
+   region->GetFillFactor = GetFillFactor;
+
+   parent_regsetattrib = region->RegSetAttrib;
+   region->RegSetAttrib = RegSetAttrib;
+
+   parent_regclearattrib = region->RegClearAttrib;
+   region->RegClearAttrib = RegClearAttrib;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   mapping->Decompose = Decompose;
+   region->RegBaseBox = RegBaseBox;
+   region->RegBaseBox2 = RegBaseBox2;
+   region->RegBaseMesh = RegBaseMesh;
+   region->RegSplit = RegSplit;
+   region->RegPins = RegPins;
+   region->RegTrace = RegTrace;
+   region->GetBounded = GetBounded;
+   region->RegBasePick = RegBasePick;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "CmpRegion", "Combination of two Regions" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;       /* Pointer to CmpRegion structure */
+   int result;               /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->region1, mode, extra, fail );
+   if( !result ) result = astManageLock( this->region2, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static AstRegion *MatchRegion( AstRegion *this, int ifrm, AstRegion *that,
+                               const char *method, int *status ) {
+/*
+*  Name:
+*     MatchRegion
+
+*  Purpose:
+*     Map a Region into the Frame of another Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     AstRegion *MatchRegion( AstRegion *this, int ifrm, AstRegion *that,
+*                             const char *method, int *status )
+
+*  Class Membership:
+*     CmpRegion method.
+
+*  Description:
+*     This function returns a pointer to a new Region which is a copy of
+*     "that" mapped into either the base or current Frame of "this".
+
+*  Parameters:
+*     this
+*        Pointer to a Region defining the Frame of the returned Region.
+*     ifrm
+*        The index of a Frame within the FrameSet encapsulated by "this".
+*        The returned Region will refer to the requested Frame. It should
+*        be either AST__CURRENT or AST__BASE.
+*     that
+*        Pointer to a Region defining the shape and extent of the
+*        returned Region.
+*     method
+*        Pointer to a string holding the calling method.This is only used
+*        in error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a new Region. This should be annulled (using astAnnul)
+*     when no longer needed.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;             /* Current Frame from "fs" */
+   AstFrameSet *fs;           /* FrameSet connecting that to this */
+   AstMapping *map;           /* Base->Current Mapping from "fs" */
+   AstRegion *result;         /* Returned pointer */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. Also return NULL if no Regions were
+   supplied. */
+   if ( !astOK || !this || !that ) return result;
+
+/* Temporarily invert "this" if we are matching its base Frame (since the
+   astConvert method matches current Frames). */
+   if( ifrm == AST__BASE ) astInvert( this );
+
+/* Find a FrameSet connecting the current Frames of the two Regions */
+   fs = astConvert( that, this, "" );
+
+/* Re-instate the original Frame indices in "this" if required. */
+   if( ifrm == AST__BASE ) astInvert( this );
+
+/* Check a conversion path was found. */
+   if( fs ) {
+
+/* Get the Frame and Mapping form the FrameSet. */
+      frm = astGetFrame( fs, AST__CURRENT );
+      map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+
+/* Re-map the Region. */
+      result = astMapRegion( that, map, frm );
+
+/* Free resources. */
+      frm = astAnnul( frm );
+      map = astAnnul( map );
+      fs = astAnnul( fs );
+
+/* Report an error if there is no conversion between the two Frames. */
+   } else {
+      astError( AST__INTER, "%s(%s): MatchRegion cannot convert between "
+                "the two supplied coordinate Frames (internal AST "
+                "programming error).", status, method, astGetClass( this ) );
+   }
+
+/* Annul the returned pointer if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void RegBaseBox( AstRegion *this_region, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     RegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astRegBaseBox protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;          /* Pointer to CmpRegion structure */
+   AstPointSet *ps;             /* Mesh pointset */
+   AstRegion *reg1;             /* Pointer to first component Region */
+   AstRegion *reg2;             /* Pointer to second component Region */
+   double **ptr;                /* Pointer to mesh data */
+   double *clbnd1;              /* Point to 1st comp lower bounds array */
+   double *clbnd2;              /* Point to 2nd comp lower bounds array */
+   double *cubnd1;              /* Point to 1st comp upper bounds array */
+   double *cubnd2;              /* Point to 2nd comp upper bounds array */
+   double *p;                   /* Pointer to next coordinate value */
+   double lb;                   /* Lower limit */
+   double ub;                   /* Upper limit */
+   int i;                       /* Axis index */
+   int icoord;                  /* Coordinate index */
+   int inc1;                    /* First component interval is included? */
+   int inc2;                    /* Second component interval is included? */
+   int ipoint;                  /* Point index */
+   int nax;                     /* Number of axes in Frame */
+   int ncoord;                  /* Number of coords */
+   int neg1;                    /* First component negated? */
+   int neg2;                    /* Second component negated? */
+   int npoint;                  /* Number of points */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the CmpRegion structure */
+   this = (AstCmpRegion *) this_region;
+
+/* If the CmpRegion is bounded, we find the bounding box using a mesh of
+   points spread evenly over the boundary of the CmpRegion. */
+   if( astGetBounded( this ) ) {
+      ps = astRegBaseMesh( this_region );
+      ptr = astGetPoints( ps );
+      ncoord = astGetNcoord( ps );
+      npoint = astGetNpoint( ps );
+
+      if( astOK ) {
+         for( icoord = 0; icoord < ncoord; icoord++ ) {
+            lbnd[ icoord ] = DBL_MAX;
+            ubnd[ icoord ] = -DBL_MAX;
+            p = ptr[ icoord ];
+            for( ipoint = 0; ipoint < npoint; ipoint++, p++ ) {
+               if( *p != AST__BAD ) {
+                  if( *p < lbnd[ icoord ] )  lbnd[ icoord ] = *p;
+                  if( *p > ubnd[ icoord ] )  ubnd[ icoord ] = *p;
+               }
+            }
+         }
+      }
+      ps = astAnnul( ps );
+
+/* If the CmpRegion is not bounded we look at each axis individually. */
+   } else {
+
+/* Get pointers to the component Regions. */
+      reg1 = this->region1;
+      reg2 = this->region2;
+
+/* Get their negated flags */
+      neg1 = astGetNegated( reg1 );
+      neg2 = astGetNegated( reg2 );
+
+/* The base Frame of the parent Region structure is the current Frame of
+   the component Regions. Get the no. of axes in this Frame. */
+      nax = astGetNaxes( reg1 );
+
+/* Get the bounding boxes of the component Regions in this Frame. */
+      clbnd1 = astMalloc( sizeof( double )*(size_t) nax );
+      cubnd1 = astMalloc( sizeof( double )*(size_t) nax );
+      clbnd2 = astMalloc( sizeof( double )*(size_t) nax );
+      cubnd2 = astMalloc( sizeof( double )*(size_t) nax );
+      if( astOK ) {
+         astGetRegionBounds( reg1, clbnd1, cubnd1 );
+         astGetRegionBounds( reg2, clbnd2, cubnd2 );
+
+/* Loop round every axis. */
+         for( i = 0; i < nax; i++ ) {
+
+/* If the first component Region has been negated, the lower and upper
+   bounds from the first component are the bounds of an *excluded* axis
+   interval, not an included interval. If either of the bounds are
+   infinite, we can swap it to an included interval. If both bounds are
+   finite, we cannot convert to an included interval. In this case, we
+   assume that the gap will be filled at some point on another axis, if
+   there is more than 1 axis, and convert it to an unbouded included
+   interval. */
+            inc1 = 1;
+            if( neg1 ) {
+               lb = clbnd1[ i ];
+               ub = cubnd1[ i ];
+               if( lb == -DBL_MAX ) clbnd1[ i ] = ub;
+               if( ub == DBL_MAX ) cubnd1[ i ] = lb;
+               if( lb != -DBL_MAX && ub != DBL_MAX ) {
+                  if( nax == 1 ) {
+                     inc1 = 0;
+                  } else {
+                     clbnd1[ i ] = -DBL_MAX;
+                     cubnd1[ i ] = DBL_MAX;
+                  }
+               }
+            }
+
+/* Likewise attempt to convert an excluded interval into an included
+   interval for the second component Region. */
+            inc2 = 1;
+            if( neg2 ) {
+               lb = clbnd2[ i ];
+               ub = cubnd2[ i ];
+               if( lb == -DBL_MAX ) clbnd2[ i ] = ub;
+               if( ub == DBL_MAX ) cubnd2[ i ] = lb;
+               if( lb != -DBL_MAX && ub != DBL_MAX ) {
+                  if( nax == 1 ) {
+                     inc2 = 0;
+                  } else {
+                     clbnd2[ i ] = -DBL_MAX;
+                     cubnd2[ i ] = DBL_MAX;
+                  }
+               }
+            }
+
+/* If the component Regions are combined using AND, find the overlap of
+   the axis intervals. This depends on whether the intervals are included
+   or excluded. */
+            if( this->oper == AST__AND ) {
+
+               if( inc1 ) {
+                  if( inc2 ) {
+                     lbnd[ i ] = astMAX( clbnd1[ i ], clbnd2[ i ] );
+                     ubnd[ i ] = astMIN( cubnd1[ i ], cubnd2[ i ] );
+                  } else {
+                     lbnd[ i ] = clbnd1[ i ] < clbnd2[ i ] ? clbnd1[ i ] : cubnd2[ i ];
+                     ubnd[ i ] = cubnd1[ i ] > cubnd2[ i ] ? cubnd1[ i ] : clbnd2[ i ];
+                  }
+               } else {
+                  if( inc2 ) {
+                     lbnd[ i ] = clbnd2[ i ] < clbnd1[ i ] ? clbnd2[ i ] : cubnd1[ i ];
+                     ubnd[ i ] = cubnd2[ i ] > cubnd1[ i ] ? cubnd2[ i ] : clbnd1[ i ];
+                  } else {
+                     lbnd[ i ] = clbnd1[ i ] < clbnd2[ i ] ? clbnd1[ i ] : cubnd2[ i ];
+                     ubnd[ i ] = cubnd1[ i ] > cubnd2[ i ] ? cubnd1[ i ] : clbnd2[ i ];
+                  }
+               }
+
+/* If the component Regions are not combined using AND, find the union of
+   the axis intervals. */
+            } else {
+               if( inc1 && inc2 ) {
+                  lbnd[ i ] = astMIN( clbnd1[ i ], clbnd2[ i ] );
+                  ubnd[ i ] = astMAX( cubnd1[ i ], cubnd2[ i ] );
+               } else {
+                  lbnd[ i ] = -DBL_MAX;
+                  ubnd[ i ] = DBL_MAX;
+               }
+            }
+         }
+      }
+
+/* Free resources. */
+      clbnd1 = astFree( clbnd1 );
+      cubnd1 = astFree( cubnd1 );
+      clbnd2 = astFree( clbnd2 );
+      cubnd2 = astFree( cubnd2 );
+   }
+}
+
+static void RegBaseBox2( AstRegion *this_region, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     RegBaseBox2
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void RegBaseBox2( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astRegBaseBox2 protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function is similar to astRegBaseBox in that it returns the
+*     upper and lower axis bounds of a Region in the base Frame of the
+*     encapsulated FrameSet. But, in addition to assuming that the
+*     supplied Region has not been negated, it also assumes that any
+*     component Regions contained within the supplied Region have not been
+*     negated.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;          /* Pointer to CmpRegion structure */
+   AstRegion *reg1;             /* Pointer to first component Region */
+   AstRegion *reg2;             /* Pointer to second component Region */
+   double *clbnd1;              /* Point to 1st comp lower bounds array */
+   double *clbnd2;              /* Point to 2nd comp lower bounds array */
+   double *cubnd1;              /* Point to 1st comp upper bounds array */
+   double *cubnd2;              /* Point to 2nd comp upper bounds array */
+   int i;                       /* Axis index */
+   int nax;                     /* Number of axes in Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the CmpRegion structure */
+   this = (AstCmpRegion *) this_region;
+
+/* Get pointers to the component Regions. */
+   reg1 = this->region1;
+   reg2 = this->region2;
+
+/* The base Frame of the parent Region structure is the current Frame of
+   the component Regions. Get the no. of axes in this Frame. */
+   nax = astGetNaxes( reg1 );
+
+/* Get the bounding boxes of the component Regions in this Frame. */
+   clbnd1 = astMalloc( sizeof( double )*(size_t) nax );
+   cubnd1 = astMalloc( sizeof( double )*(size_t) nax );
+   clbnd2 = astMalloc( sizeof( double )*(size_t) nax );
+   cubnd2 = astMalloc( sizeof( double )*(size_t) nax );
+   if( astOK ) {
+      astGetRegionBounds2( reg1, clbnd1, cubnd1 );
+      astGetRegionBounds2( reg2, clbnd2, cubnd2 );
+
+/* How we combine the two bounding boxes depends on the boolean operator
+   associated with this CmpRegion.  For AND find the overlap of the two
+   bounding boxes. For other operators find the union. */
+      if( this->oper == AST__AND ) {
+         for( i = 0; i < nax; i++ ) {
+            lbnd[ i ]= astMAX( clbnd1[ i ], clbnd2[ i ] );
+            ubnd[ i ]= astMIN( cubnd1[ i ], cubnd2[ i ] );
+         }
+
+      } else {
+         for( i = 0; i < nax; i++ ) {
+            lbnd[ i ]= astMIN( clbnd1[ i ], clbnd2[ i ] );
+            ubnd[ i ]= astMAX( cubnd1[ i ], cubnd2[ i ] );
+         }
+      }
+   }
+
+/* Free resources. */
+   clbnd1 = astFree( clbnd1 );
+   cubnd1 = astFree( cubnd1 );
+   clbnd2 = astFree( clbnd2 );
+   cubnd2 = astFree( cubnd2 );
+
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this_region, int *status ){
+/*
+*  Name:
+*     RegBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing a mesh of points on the boundary of a
+*     Region in its base Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     AstPointSet *astRegBaseMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astRegBaseMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. Annul the pointer using astAnnul when it
+*     is no longer needed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*/
+
+
+/* Local Variables: */
+   AstCmpRegion *this;            /* The CmpRegion structure */
+   AstPointSet *mesh1;            /* PointSet holding mesh for 1st component */
+   AstPointSet *mesh1b;           /* Mesh for 1st component mapped by 2nd comp. */
+   AstPointSet *mesh2;            /* PointSet holding mesh for 2nd component */
+   AstPointSet *mesh2b;           /* Mesh for 2nd component mapped by 1st comp. */
+   AstPointSet *result;           /* Returned pointer */
+   AstRegion *reg1;               /* Pointer to first component Region */
+   AstRegion *reg2;               /* Pointer to second component Region */
+   double **ptr1;                 /* Pointer to array of mesh1b axis value pointers */
+   double **ptr2;                 /* Pointer to array of mesh2b axis value pointers */
+   double **ptr;                  /* Pointer to array of total axis value pointers */
+   double *lbnd;                  /* Pointer to array of bounding box lower bounds */
+   double *ubnd;                  /* Pointer to array of bounding box upper bounds */
+   double v;                      /* Axis value */
+   int hasMesh1;                  /* Does 1st component Region have a mesh? */
+   int hasMesh2;                  /* Does 2nd component Region have a mesh? */
+   int ic;                        /* Axis index */
+   int ip;                        /* Input point index */
+   int jp;                        /* Output point index */
+   int nc;                        /* No. of axis values per point */
+   int np1;                       /* No. of points in mesh1b */
+   int np2;                       /* No. of points in mesh2b */
+   int np;                        /* No. of points in returned PointSet */
+   int ok;                        /* Were all axis values good at this point? */
+
+/* Initialise */
+   result= NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_region;
+
+/* If the Region structure contains a pointer to a PointSet holding
+   a previously created mesh, return it. */
+   if( this_region->basemesh ) {
+      result = astClone( this_region->basemesh );
+
+/* Otherwise, create a new mesh. */
+   } else {
+
+/* Get pointers to the component regions. */
+      reg1 = this->region1;
+      reg2 = this->region2;
+
+/* A mesh can only be produced for a Region if it is bounded when either
+   negated or un-negated. See if meshes can be produced for the component
+   Regions. */
+      hasMesh1 = astGetBounded( reg1 );
+      if( !hasMesh1 ){
+         astNegate( reg1 );
+         hasMesh1 = astGetBounded( reg1 );
+         astNegate( reg1 );
+      }
+
+      hasMesh2 = astGetBounded( reg2 );
+      if( !hasMesh2 ){
+         astNegate( reg2 );
+         hasMesh2 = astGetBounded( reg2 );
+         astNegate( reg2 );
+      }
+
+/* If neither Region has a mesh we cannot produce a mesh. */
+      if( !hasMesh1 && !hasMesh2 && astOK ) {
+         astError( AST__INTER, "astRegBaseMesh(%s): No mesh can be "
+                   "produced for the %s bacause neither of its component "
+                   "Regions has a mesh (internal AST programming error).", status,
+                   astGetClass( this ), astGetClass( this ) );
+
+/* If only one Region has a mesh, we can produce a mesh so long as the
+    boolean operator is not OR. */
+      } else if( ( !hasMesh1 || !hasMesh2 ) && this->oper == AST__OR && astOK ) {
+         astError( AST__INTER, "astRegBaseMesh(%s): No mesh can be produced "
+                   "for the %s bacause one its component Regions has no "
+                   "mesh and the union of the Regions is required (internal "
+                   "AST programming error).", status, astGetClass( this ), astGetClass( this ) );
+      }
+
+/* Allocate memory to hold a bounding box in the base Frame of the CmpRegion. */
+      nc = astGetNin( this_region->frameset );
+      lbnd = astMalloc( sizeof( double )*(size_t) nc );
+      ubnd = astMalloc( sizeof( double )*(size_t) nc );
+
+/* Get current Frame meshes covering the two component Regions (the current
+   Frame of the component Regions is the same as the base Frame of the parent
+   Region). We now know that at least one Region has a mesh. If the other
+   one does not have a mesh we may be able to create a mesh by taking the
+   intersection of the Region with the bounding box of the bounded Region. */
+      if( hasMesh1 ) {
+         mesh1 = astRegMesh( reg1 );
+         if( hasMesh2 ) {
+            mesh2 = astRegMesh( reg2 );
+         } else {
+            astGetRegionBounds( reg1, lbnd, ubnd );
+            mesh2 = astBndMesh( reg2, lbnd, ubnd );
+         }
+
+      } else {
+         mesh2 = astRegMesh( reg2 );
+         astGetRegionBounds( reg2, lbnd, ubnd );
+         mesh1 = astBndMesh( reg1, lbnd, ubnd );
+      }
+
+/* If the CmpRegion represents the intersection of the two component Regions
+   (AND operator), the total mesh is the sum of the component mesh points
+   which are inside the other component region. If the CmpRegion represents
+   the union of the two component Regions (OR operator), the total mesh is
+   the sum of the component mesh points which are outside the other component
+   region. So temporarily negate the component Regions if they are
+   combined using OR. */
+      if( this->oper == AST__OR ) {
+         astNegate( reg1 );
+         astNegate( reg2 );
+      }
+
+/* Transform the mesh for the first component using the second component
+   as a Mapping. Mesh points outside (or inside if "oper" is OR) the bounds
+   of the second component will be set bad. */
+      mesh1b = astTransform( reg2, mesh1, 1, NULL );
+
+/* Transform the mesh for the second component using the first component
+   as a Mapping. Mesh points outside (or inside if "oper" is OR) the bounds
+   of the first component will be set bad. */
+      mesh2b = astTransform( reg1, mesh2, 1, NULL );
+
+/* If required, negate them again to bring them back to their original state.*/
+      if( this->oper == AST__OR ) {
+         astNegate( reg1 );
+         astNegate( reg2 );
+      }
+
+/* The required mesh contains all the good points form both mesh1b and
+   mesh2b (i.e. all boundary points which are inside -or inside if "oper"
+   is OR- the other component Region). Create a PointSet assuming that all
+   points are good. First allocate an array to hold pointers to the arrays
+   holding coordinate values for each axis. */
+      nc = astGetNcoord( mesh1b );
+      np1 = astGetNpoint( mesh1b );
+      np2 = astGetNpoint( mesh2b );
+      np = np1 + np2;
+      result = astPointSet( np, nc, "", status );
+      ptr = astGetPoints( result );
+
+/* Get points to the axis values of the mapped meshes. */
+      ptr1 = astGetPoints( mesh1b );
+      ptr2 = astGetPoints( mesh2b );
+
+/* Check pointers can be used safely. */
+      if( astOK ) {
+
+/* Initialise the index of the next point in the total mesh. */
+         jp = 0;
+
+/* Loop round all the points in the transformed mesh for the first
+   component. */
+         for( ip = 0; ip < np1; ip++ ) {
+
+/* Assume this point has good axis values */
+            ok = 1;
+
+/* Copy the axis values into the total mesh. Break if a bad axis value is
+   found. */
+            for( ic = 0; ic < nc; ic++ ) {
+               v = ptr1[ ic ][ ip ];
+               if( v != AST__BAD ) {
+                  ptr[ ic ][ jp ] = v;
+               } else {
+                  ok = 0;
+                  break;
+               }
+            }
+
+/* If no bad axis values were found, increment the index of the next
+   point in the total mesh. */
+            if( ok ) jp++;
+         }
+
+/* Now similarly copy the good values from the second transformed mesh onto
+   the end of the total mesh array. */
+         for( ip = 0; ip < np2; ip++ ) {
+            ok = 1;
+            for( ic = 0; ic < nc; ic++ ) {
+               v = ptr2[ ic ][ ip ];
+               if( v != AST__BAD ) {
+                  ptr[ ic ][ jp ] = v;
+               } else {
+                  ok = 0;
+                  break;
+               }
+            }
+            if( ok ) jp++;
+         }
+
+/* If the total mesh contains no good points, we will create a PointSet
+   holding a single bad position. */
+         if( jp == 0 ) {
+            np = 1;
+            for( ic = 0; ic < nc; ic++ ) ptr[ ic ][ 0 ] = AST__BAD;
+         } else {
+            np = jp;
+         }
+
+/* Adjust the size of the returned PointSet to exclude the extra space
+   caused by any axis values being bad in the transformed meshes. */
+         astSetNpoint( result, np );
+
+      }
+
+/* Free resources. */
+      mesh1 = astAnnul( mesh1 );
+      mesh2 = astAnnul( mesh2 );
+      mesh1b = astAnnul( mesh1b );
+      mesh2b = astAnnul( mesh2b );
+      lbnd = astFree( lbnd );
+      ubnd = astFree( ubnd );
+
+/* Save the returned pointer in the Region structure so that it does not
+   need to be created again next time this function is called. */
+      if( astOK && result ) this_region->basemesh = astClone( result );
+   }
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static AstRegion *RegBasePick( AstRegion *this_region, int naxes,
+                               const int *axes, int *status ){
+/*
+*  Name:
+*     RegBasePick
+
+*  Purpose:
+*     Return a Region formed by picking selected base Frame axes from the
+*     supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     AstRegion *RegBasePick( AstRegion *this, int naxes, const int *axes,
+*                             int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astRegBasePick protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function attempts to return a Region that is spanned by selected
+*     axes from the base Frame of the encapsulated FrameSet of the supplied
+*     Region. This may or may not be possible, depending on the class of
+*     Region. If it is not possible a NULL pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     naxes
+*        The number of base Frame axes to select.
+*     axes
+*        An array holding the zero-based indices of the base Frame axes
+*        that are to be selected.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Region, or NULL if no region can be formed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;     /* Pointer to CmpRegion structure */
+   AstFrame *frm1;         /* Axes picked from the 1st encapsulated Region */
+   AstFrame *frm2;         /* Axes picked from the 2nd encapsulated Region */
+   AstRegion *result;      /* Returned Region */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the CmpRegion information. */
+   this = (AstCmpRegion *) this_region;
+
+/* Both encapsulated regions refer to the same Frame (the base Frame of
+   the parent Region), so attempt to pick the requested axs from them.
+   If the resulting Frames are not Regions, we cannot pick the requested
+   axes so return the NULL Frame pointer initialised above. */
+   frm1 = astPickAxes( this->region1, naxes, axes, NULL );
+   if( astIsARegion( frm1 ) ) {
+      frm2 = astPickAxes( this->region2, naxes, axes, NULL );
+      if( astIsARegion( frm2 ) ) {
+
+/* Create the new CmpRegion. */
+         result = (AstRegion *) astCmpRegion( (AstRegion *) frm1,
+                                              (AstRegion *) frm2,
+                                              this->oper, "", status );
+      }
+
+/* Free resources */
+      frm2 = astAnnul( frm2 );
+   }
+   frm1 = astAnnul( frm1 );
+
+/* Return a NULL pointer if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int RegPins( AstRegion *this_region, AstPointSet *pset, AstRegion *unc,
+                    int **mask, int *status ){
+/*
+*  Name:
+*     RegPins
+
+*  Purpose:
+*     Check if a set of points fall on the boundary of a given CmpRegion.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+*                  int **mask, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astRegPins protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied set of
+*     points all fall on the boundary of the given CmpRegion.
+*
+*     Some tolerance is allowed, as specified by the uncertainty Region
+*     stored in the supplied CmpRegion "this", and the supplied uncertainty
+*     Region "unc" which describes the uncertainty of the supplied points.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpRegion.
+*     pset
+*        Pointer to the PointSet. The points are assumed to refer to the
+*        base Frame of the FrameSet encapsulated by "this".
+*     unc
+*        Pointer to a Region representing the uncertainties in the points
+*        given by "pset". The Region is assumed to represent the base Frame
+*        of the FrameSet encapsulated by "this". Zero uncertainity is assumed
+*        if NULL is supplied.
+*     mask
+*        Pointer to location at which to return a pointer to a newly
+*        allocated dynamic array of ints. The number of elements in this
+*        array is equal to the value of the Npoint attribute of "pset".
+*        Each element in the returned array is set to 1 if the
+*        corresponding position in "pset" is on the boundary of the Region
+*        and is set to zero otherwise. A NULL value may be supplied
+*        in which case no array is created. If created, the array should
+*        be freed using astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the points all fall on the boundary of the given
+*     Region, to within the tolerance specified. Zero otherwise.
+
+*/
+
+/* Local variables: */
+   AstCmpRegion *this;          /* Pointer to the CmpRegion structure. */
+   AstPointSet *pset1;          /* Points masked by 1st component Region */
+   AstPointSet *pset2;          /* Points masked by 2nd component Region */
+   AstPointSet *psetb1;         /* Points in base Frame of 1st component Region */
+   AstPointSet *psetb2;         /* Points in base Frame of 2nd component Region */
+   AstRegion *reg1;             /* Pointer to first component Region */
+   AstRegion *reg2;             /* Pointer to second component Region */
+   AstRegion *unc1;             /* Base Frame uncertainty in 1st component Region */
+   AstRegion *unc2;             /* Base Frame uncertainty in 2nd component Region */
+   double **ptr1;               /* Pointer to axis values in "pset1" */
+   double **ptr2;               /* Pointer to axis values in "pset2" */
+   double *p1;                  /* Pointer to next axis zero value for pset1 */
+   double *p2;                  /* Pointer to next axis zero value for pset2 */
+   int *mask1;                  /* Mask for first component boundary */
+   int *mask2;                  /* Mask for second component boundary */
+   int ip;                      /* Point index */
+   int np;                      /* Number of points */
+   int result;                  /* Returned flag */
+
+/* Initialise */
+   result = 0;
+   if( mask ) *mask = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_region;
+
+/* Get pointers to the two component Regions. */
+   reg1 = this->region1;
+   reg2 = this->region2;
+
+/* Get a mask which indicates if each supplied point is on or off the
+   boundary of the first component Region. astRegPins expects its "pset"
+   argument to contain positions in the base Frame of the Region, so
+   we must first transform the supplied points into the base Frame of
+   "reg1". We must also map the uncertainty into the base Frame of the
+   component Region. */
+   psetb1 = astRegTransform( reg1, pset, 0, NULL, NULL );
+   unc1 = MatchRegion( reg1, AST__BASE, unc, "astRegPins", status );
+   astRegPins( reg1, psetb1, unc1, &mask1 );
+
+/* Likewise, get a mask which indicates if each supplied point is on or off
+   the boundary of the second component Region. */
+   psetb2 = astRegTransform( reg2, pset, 0, NULL, NULL );
+   unc2 = MatchRegion( reg2, AST__BASE, unc, "astRegPins", status );
+   astRegPins( reg2, psetb2, unc2, &mask2 );
+
+/* The criteria for a point to be on the boundary of the CmpRegion depend
+   on the boolean operator being used. If component regions A and B are
+   ANDed together, then a point is on the boundary of the CmpRegion if
+   either 1) it is on the boundary of A and inside B, or 2) it is on the
+   boundary of B and inside A. If the component regions are ORed together,
+   then a point is on the boundary of the CmpRegion if either 1) it is on
+   the boundary of A and outside B, or 2) it is on the boundary of B and
+   outside A. Either we need to transform the supplied PointSet using each
+   component Region as a Mapping. But if using OR we temporarily negate
+   the Regions. */
+   if( this->oper == AST__OR ) {
+      astNegate( reg1 );
+      astNegate( reg2 );
+   }
+   pset1 = astTransform( reg1, pset, 1, NULL );
+   pset2 = astTransform( reg2, pset, 1, NULL );
+   if( this->oper == AST__OR ) {
+      astNegate( reg1 );
+      astNegate( reg2 );
+   }
+
+/* Get pointers to the axis values in these PointSets */
+   ptr1 = astGetPoints( pset1 );
+   ptr2 = astGetPoints( pset2 );
+
+/* If required, create an output mask array */
+   np = astGetNpoint( pset );
+   if( mask ) *mask = astMalloc( sizeof(int)*(size_t) np );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* We can use the values for the first axis to indicate if a point is
+   inside or outside a Region. So store pointers to the first axis arrays. */
+      p1 = ptr1[ 0 ];
+      p2 = ptr2[ 0 ];
+
+/* Assume all points are on the boundary of the CmpRegion. */
+      result = 1;
+
+/* If we are creating an output mask, we must check every point. Otherwise
+   we can stop checking when we find the first point which is not on the
+   boundary of the CmpRegion. */
+      if( mask ) {
+
+         for( ip = 0; ip < np; ip++ ) {
+            if( ( mask1[ ip ] && p2[ ip ] != AST__BAD ) ||
+                ( mask2[ ip ] && p1[ ip ] != AST__BAD ) ){
+               (*mask)[ ip ] = 1;
+            } else {
+               (*mask)[ ip ] = 0;
+               result = 0;
+            }
+         }
+
+      } else {
+
+         for( ip = 0; ip < np; ip++ ) {
+            if( ( !mask1[ ip ] || p2[ ip ] == AST__BAD ) &&
+                ( !mask2[ ip ] || p1[ ip ] == AST__BAD ) ){
+               result = 0;
+               break;
+            }
+         }
+      }
+   }
+
+/* Free resources */
+   mask1 = astFree( mask1 );
+   mask2 = astFree( mask2 );
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+   psetb1 = astAnnul( psetb1 );
+   psetb2 = astAnnul( psetb2 );
+   if( unc1 ) unc1 = astAnnul( unc1 );
+   if( unc2 ) unc2 = astAnnul( unc2 );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) {
+      result = 0;
+      if( mask ) *mask = astAnnul( *mask );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void RegSetAttrib( AstRegion *this_region, const char *setting,
+                          char **base_setting, int *status ) {
+/*
+*  Name:
+*     RegSetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void RegSetAttrib( AstRegion *this, const char *setting,
+*                        char **base_setting, int *status )
+
+*  Class Membership:
+*     CmpRegion method (over-rides the astRegSetAttrib method inherited from
+*     the Region class).
+
+*  Description:
+*     This function assigns an attribute value to both the base and
+*     current Frame in the FrameSet encapsulated within a Region, without
+*     remapping either Frame.
+*
+*     No error is reported if the attribute is not recognised by the base
+*     Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     setting
+*        Pointer to a null terminated attribute setting string. NOTE, IT
+*        SHOULD BE ENTIRELY LOWER CASE. The supplied string will be
+*        interpreted using the public interpretation implemented by
+*        astSetAttrib. This can be different to the interpretation of the
+*        protected accessor functions. For instance, the public
+*        interpretation of an unqualified floating point value for the
+*        Epoch attribute is to interpet the value as a gregorian year,
+*        but the protected interpretation is to interpret the value as an
+*        MJD.
+*     base_setting
+*        Address of a location at which to return a pointer to the null
+*        terminated attribute setting string which was applied to the
+*        base Frame of the encapsulated FrameSet. This may differ from
+*        the supplied setting if the supplied setting contains an axis
+*        index and the current->base Mapping in the FrameSet produces an
+*        axis permutation. The returned pointer should be freed using
+*        astFree when no longer needed. A NULL pointer may be supplied in
+*        which case no pointer is returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;
+   char *bset;
+   int rep;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_region;
+
+/* Use the RegSetAttrib method inherited from the parent class to apply the
+   setting to the current and base Frames in the FrameSet encapsulated by the
+   parent Region structure. */
+   (*parent_regsetattrib)( this_region, setting, &bset, status );
+
+/* Now apply the base Frame setting to the component Regions (the current
+   Frame within the component Regions is equivalent to the base Frame in the
+   parent Region structure). Annul any "attribute unknown" error that results
+   from attempting to do this. */
+   if( astOK ) {
+      rep = astReporting( 0 );
+      astRegSetAttrib( this->region1, bset, NULL );
+      astRegSetAttrib( this->region2, bset, NULL );
+      if( astStatus == AST__BADAT ) astClearStatus;
+      astReporting( rep );
+   }
+
+/* If required, return the base Frame setting string, otherwise free it. */
+   if( base_setting ) {
+      *base_setting = bset;
+   } else {
+      bset = astFree( bset );
+   }
+}
+
+static AstRegion **RegSplit( AstRegion *this_region, int *nlist, int *status ){
+/*
+*+
+*  Name:
+*     RegSplit
+
+*  Purpose:
+*     Split a Region into a list of disjoint component Regions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstRegion **astRegSplit( AstRegion *this, int *nlist )
+
+*  Class Membership:
+*     CmpRegion member function (overrides the astRegSplit method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function splits the supplied Region into a set of disjoint
+*     component Regions. If the Region cannot be split, then the returned
+*     array contains only one pointer - a clone of the supplied Region
+*     pointer.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     nlist
+*        Pointer to an int in which to return the number of elements in
+*        the returned array.
+
+*  Returned Value:
+*     Pointer to dynamically alloctaed memory holding an array of Region
+*     pointers. The length of this array is given by the value returned
+*     in "*nlist". The pointers in the returned array should be annulled
+*     using astAnnul when no longer needed, and the memory used to hold
+*     the array should be freed using astFree.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*-
+*/
+
+/* Local Variables; */
+   AstCmpRegion *new;
+   AstCmpRegion *this;
+   AstFrame *frm;
+   AstFrameSet *fs;
+   AstMapping *map;
+   AstRegion **cmplist;
+   AstRegion **result;
+   AstRegion *cmpreg;
+   AstRegion *new_reg;
+   int icomp;
+   int ifirst;
+   int ilist;
+   int iw;
+   int jcomp;
+   int ncomp;
+   int nn;
+   int unbounded;
+
+/* Initialise. */
+   result = NULL;
+   *nlist = 0;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_region;
+
+/* Indicate we have not yet found any unbounded component regions. */
+   unbounded = 0;
+
+/* Can only split non-inverted CmpRegions that combine their components
+   using the OR operator. */
+   if( this->oper == AST__OR && !astGetNegated( this->region1 ) &&
+                                !astGetNegated( this->region2 ) ) {
+
+/* Process each of the two component Regions in turn. */
+      for( icomp = 0; icomp < 2 && !unbounded; icomp++ ) {
+         cmpreg = icomp ? this->region2 : this->region1;
+
+/* Create a set of disjoint Regions that are equivalent to the current
+   component Region, and loop round them. */
+         cmplist = astRegSplit( cmpreg, &ncomp );
+         for( jcomp = 0; jcomp < ncomp; jcomp++ ) {
+
+/* If any of the components are unbounds, we cannot split the supplied
+   Region. */
+            unbounded = unbounded || !astGetBounded( cmplist[ jcomp ] );
+            if( ! unbounded ) {
+
+/* Initialise the index within the returned list of the first Region that
+   overlaps the current disjoint component Region. */
+               ifirst = -1;
+
+/* Loop round all the Regions currently in the returned list. */
+               for( ilist = 0; ilist < *nlist; ilist++ ) {
+                  if( result[ ilist ] ) {
+
+/* See if the current disjoint component overlaps the current entry in
+   the returned list. */
+                     if( astOverlap( cmplist[ jcomp ], result[ ilist ] ) > 1 ) {
+
+/* If this is the first overlap found for the current disjoint component,
+   form a CmpRegion that combines the two overlapping Regions, and use it
+   to replace the current entry in the returned list. */
+                        if( ifirst == -1 ) {
+                           new = astCmpRegion( cmplist[ jcomp ], result[ ilist ],
+                                               AST__OR, " ", status );
+                           (void) astAnnul( result[ ilist ] );
+                           result[ ilist ] = (AstRegion *) new;
+
+/* Note the index within the returned list of the first Region that overlaps
+   the current disjoint component Region. */
+                           ifirst = ilist;
+
+/* If this is the second or later overlap, add the overlapping returned Region
+   into the CmpRegion that it is stored at index "ifirsT" in the returned
+   list. */
+                        } else {
+                           new = astCmpRegion( result[ ilist ], result[ ifirst ],
+                                               AST__OR, " ", status );
+                           result[ ilist ] = astAnnul( result[ ilist ] );
+                           (void) astAnnul( result[ ifirst ] );
+                           result[ ifirst ] = (AstRegion *) new;
+                        }
+                     }
+                  }
+               }
+
+/* If the current disjoint component does not overlap any of the Regions
+   already in the returned list, append the current disjoint component to
+   the end of the returned list. */
+               if( ifirst == -1 ) {
+                  ilist = (*nlist)++;
+                  result = astGrow( result, *nlist, sizeof( *result ) );
+                  if( astOK ) result[ ilist ] = astClone( cmplist[ jcomp ] );
+               }
+            }
+
+/* Annul the pointer to the disjoint component Region. */
+            cmplist[ jcomp ] = astAnnul( cmplist[ jcomp ] );
+         }
+
+/* Free the mnemory holding the list of disjoint components. */
+         cmplist = astFree( cmplist );
+      }
+   }
+
+/* If any unbounded components were found, ensure the returned list is
+   empty. */
+   if( unbounded && result ) {
+      for( ilist = 0; ilist < *nlist; ilist++ ) {
+         if( result[ ilist ] ) result[ ilist ] = astAnnul( result[ ilist ] );
+      }
+      result = astFree( result );
+      *nlist = 0;
+
+/* Otherwise, shuffle later entries down to fill any NULL slots in the returned
+   list. */
+   } else if( result ){
+      nn = *nlist;
+      iw = 0;
+      for( ilist = 0; ilist < nn; ilist++ ) {
+         if( result[ ilist ] ) result[ iw++ ] = result[ ilist ];
+      }
+      *nlist = iw;
+   }
+
+/* If this CmpRegion cannot be split, the returned list just holds a
+   clone of the Region pointer. */
+   if( !result ) {
+      result = astMalloc( sizeof( *result ) );
+      if( astOK ) {
+         result[ 0 ] = astClone( this );
+         *nlist = 1;
+      }
+   }
+
+/* Remap any returned Regions so that they are defined within the same
+   coordinate system as the supplied Region. */
+   if( result && *nlist > 0 )  {
+      fs = this_region->frameset;
+      map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+      frm = astGetFrame( fs, AST__CURRENT );
+      for( ilist = 0; ilist < *nlist; ilist++ ) {
+         new_reg = astMapRegion( result[ ilist ], map, frm );
+         (void) astAnnul( result[ ilist ] );
+         result[ ilist ] = new_reg;
+      }
+      map = astAnnul( map );
+      frm = astAnnul( frm );
+   }
+
+/* Free all returned pointers if an error has occurred. */
+   if( !astOK && result ) {
+      for( ilist = 0; ilist < *nlist; ilist++ ) {
+         result[ ilist ] = astAnnul( result[ ilist ] );
+      }
+      result = astFree( result );
+      *nlist = 0;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int RegTrace( AstRegion *this_region, int n, double *dist, double **ptr,
+                     int *status ){
+/*
+*+
+*  Name:
+*     RegTrace
+
+*  Purpose:
+*     Return requested positions on the boundary of a 2D Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     int astRegTrace( AstRegion *this, int n, double *dist, double **ptr );
+
+*  Class Membership:
+*     CmpRegion member function (overrides the astRegTrace method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function returns positions on the boundary of the supplied
+*     Region, if possible. The required positions are indicated by a
+*     supplied list of scalar parameter values in the range zero to one.
+*     Zero corresponds to some arbitrary starting point on the boundary,
+*     and one corresponds to the end (which for a closed region will be
+*     the same place as the start).
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     n
+*        The number of positions to return. If this is zero, the function
+*        returns without action (but the returned function value still
+*        indicates if the method is supported or not).
+*     dist
+*        Pointer to an array of "n" scalar parameter values in the range
+*        0 to 1.0.
+*     ptr
+*        A pointer to an array of pointers. The number of elements in
+*        this array should equal tthe number of axes in the Frame spanned
+*        by the Region. Each element of the array should be a pointer to
+*        an array of "n" doubles, in which to return the "n" values for
+*        the corresponding axis. The contents of the arrays are unchanged
+*        if the supplied Region belongs to a class that does not
+*        implement this method.
+
+*  Returned Value:
+*     Non-zero if the astRegTrace method is implemented by the class
+*     of Region supplied, and zero if not.
+
+*  Notes:
+*    - The current algorithm results in the boundary of the CmpRegion
+*    being dis-contiguous - supplied distance values from zero up to some
+*    mid-value correspond to positions on the first component Region, and
+*    higher distance (up to 1.0) correspond to points on the second
+*    component Region.
+
+*-
+*/
+
+/* Local Variables; */
+   AstCmpRegion *this;
+   AstFrame *frm;
+   AstMapping *map;
+   AstPointSet *bpset;
+   AstPointSet *cpset;
+   AstRegion *ureg1;
+   AstRegion *ureg2;
+   double **bptr;
+   int i;
+   int j;
+   int ncur;
+   int result;
+   double *rval;
+   double *off;
+   double *r1d;
+   double *r2d;
+   double *r1ptr[ 2 ];
+   double *r2ptr[ 2 ];
+   double **r1ptrb;
+   double **r2ptrb;
+   double dbreak;
+   double dtot;
+   double x;
+   double x0;
+   int r1n;
+   int r2n;
+   AstPointSet *r1pset;
+   AstPointSet *r2pset;
+   AstPointSet *r1psetb;
+   AstPointSet *r2psetb;
+
+/* Initialise */
+   result = 0;
+
+/* Check inherited status. */
+   if( ! astOK ) return result;
+
+/* Get a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_region;
+
+/* Get a pointer to the base Frame in the encapsulated FrameSet. */
+   frm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Check it is 2-dimensional. */
+   result = 1;
+   if( astGetNaxes( frm ) != 2 ) result = 0;
+
+/* Check the component Regions can be traced. */
+   if( !astRegTrace( this->region1, 0, NULL, NULL ) ||
+       !astRegTrace( this->region1, 0, NULL, NULL ) ) result = 0;
+
+/* Check we have some points to find. */
+   if( result && n > 0 ) {
+
+/* We first determine the required positions in the base Frame of the
+   Region, and then transform them into the current Frame. Get the
+   base->current Mapping, and the number of current Frame axes. */
+      map = astGetMapping( this_region->frameset, AST__BASE, AST__CURRENT );
+
+/* If it's a UnitMap we do not need to do the transformation, so put the
+   base Frame positions directly into the supplied arrays. */
+      if( astIsAUnitMap( map ) ) {
+         bpset = NULL;
+         bptr = ptr;
+         ncur = 2;
+
+/* Otherwise, create a PointSet to hold the base Frame positions. */
+      } else {
+         bpset = astPointSet( n, 2, " ", status );
+         bptr = astGetPoints( bpset );
+         ncur = astGetNout( map );
+      }
+
+      r1d = astMalloc( sizeof( double )*n );
+      r2d = astMalloc( sizeof( double )*n );
+
+/* Ensure information about the breaks in the boundary of each component
+   region is available within the CmpRegion structure. These breaks are
+   the points at which the two boundaries cross. */
+      SetBreakInfo( this, 0, status );
+      SetBreakInfo( this, 1, status );
+
+/* Get the constants needed to convert the supplied distances (normalised
+   so that the border of the entire CmpRegion has a length of 1.0), into
+   geodesic distances around the border of each component Region. */
+      dtot = this->d0[ 0 ] + this->d0[ 1 ];
+      dbreak = this->d0[ 0 ]/dtot;
+
+/* Initialise here to avoid compiler warnings. */
+      r1n = 0;
+      r2n = 0;
+
+/* Check the pointers can be used safely. */
+      if( astOK ) {
+
+/* Loop round all supplied distances, determining if they represent a
+   position on the first or second component Region. */
+         for( i = 0; i < n; i++ ) {
+
+/* If the current distance represents a point in the second component
+   Region... */
+            if( dist[ i ] > dbreak ) {
+
+/* Find the correspond distance around the used sections of the second
+   component region (normalised so that the entire border of the
+   component region has a length of "this->d0[1]"). */
+               x0 = ( dist[ i ] - dbreak )*dtot;
+               x = x0;
+
+/* Convert this into the correspond distance around the entire border of
+   the second component region (normalised so that the entire border of the
+   component region has unit length). */
+               rval = this->rvals[ 1 ];
+               off = this->offs[ 1 ];
+
+               for( j = 0; j < this->nbreak[ 1 ]; j++,rval++,off++ ) {
+                  if( *rval >= x0 ) break;
+                  x += *off;
+               }
+
+/* Store this as the next distance to move around the second component
+   Region, normalising it to the range 0 to 1 as required by astRegTrace. */
+               r2d[ r2n++ ] = x/this->dtot[ 1 ];
+
+/* Now we do the same if the current distance corresponds to a position
+   in the first component Region. */
+            } else {
+
+               x0 = dist[ i ]*dtot;
+               x = x0;
+
+               rval = this->rvals[ 0 ];
+               off = this->offs[ 0 ];
+
+               for( j = 0; j < this->nbreak[ 0 ]; j++,rval++,off++ ) {
+                  if( *rval >= x0 ) break;
+                  x += *off;
+               }
+
+               r1d[ r1n++ ] = x/this->dtot[ 0 ];
+
+            }
+
+         }
+      }
+
+/* Allocate memory to hold the axis values at the corresponding positions
+   in the first component Region. */
+      r1ptr[ 0 ] = astMalloc( sizeof( double )*r1n );
+      r1ptr[ 1 ] = astMalloc( sizeof( double )*r1n );
+
+/* Allocate memory to hold the axis values at the corresponding positions
+   in the second component Region. */
+      r2ptr[ 0 ] = astMalloc( sizeof( double )*r2n );
+      r2ptr[ 1 ] = astMalloc( sizeof( double )*r2n );
+
+/* Check the pointers can be used safely. */
+      if( astOK ) {
+
+/* Find the axis values at each of the required positions that fall in
+   the first component Region. Negate it first if needed to ensure the
+   Region is bounded (not guaranteed, but likely). */
+         if( astGetBounded( this->region1 ) ) {
+            (void) astRegTrace( this->region1, r1n, r1d, r1ptr );
+         } else {
+            AstRegion *negation = astGetNegation( this->region1 );
+            (void) astRegTrace( negation, r1n, r1d, r1ptr );
+            negation = astAnnul( negation );
+         }
+
+/* Do the same for the second component Region. */
+         if( astGetBounded( this->region2 ) ) {
+            (void) astRegTrace( this->region2, r2n, r2d, r2ptr );
+         } else {
+            AstRegion *negation = astGetNegation( this->region2 );
+            (void) astRegTrace( negation, r2n, r2d, r2ptr );
+            negation = astAnnul( negation );
+         }
+
+/* The arrays of positions returned by the above calls to astRegTrace may
+   include points that should not be there (e.g. points on the boundary
+   of one component region that should have been blanked due to being inside
+   the second component region - if the regions are ORed together). This
+   is a consequence of the relatively low value of the "NP" local constant
+   in function SetBreakInfo. So we now refine the positions to exclude
+   any such unwanted positions.
+
+   If the two component Regions are ANDed together, we want to remove the
+   positions from the boundary of the required component Region that fall
+   outside the other region. We can do this by simply using the other Region
+   as a Mapping. If the two component Regions are ORed together, we want to
+   remove the position that fall within (rather than outside) the other
+   Region. To do this we need to negate the other region  first. */
+         if( this->oper == AST__OR ) {
+            ureg1 = astGetNegation( this->region1 );
+            ureg2 = astGetNegation( this->region2 );
+         } else {
+            ureg1 = astClone( this->region1 );
+            ureg2 = astClone( this->region2 );
+         }
+
+/* Now transform the points on the boundary of the first Region in order
+   to set invalid those positions which are not on the boundary of the
+   supplied CmpRegion. */
+         if( r1n > 0 ) {
+            r1pset = astPointSet( r1n, 2, " ", status );
+            astSetPoints( r1pset, r1ptr );
+            r1psetb = astTransform( ureg2, r1pset, 1, NULL );
+            r1ptrb = astGetPoints( r1psetb );
+         } else {
+            r1pset = NULL;
+            r1psetb = NULL;
+            r1ptrb = NULL;
+         }
+
+/* Now transform the points on the boundary of the second Region in order
+   to set invalid those positions which are not on the boundary of the
+   supplied CmpRegion. */
+         if( r2n > 0 ) {
+            r2pset = astPointSet( r2n, 2, " ", status );
+            astSetPoints( r2pset, r2ptr );
+            r2psetb = astTransform( ureg1, r2pset, 1, NULL );
+            r2ptrb = astGetPoints( r2psetb );
+         } else {
+            r2pset = NULL;
+            r2psetb = NULL;
+            r2ptrb = NULL;
+         }
+
+/* Free the begation pointers. */
+         ureg1 = astAnnul( ureg1 );
+         ureg2 = astAnnul( ureg2 );
+
+/* Check pointer can be used safely. */
+         if( astOK ) {
+
+/* Copy the boundary positions from each component Region into a single
+   PointSet. These positions are in the base Frame of the CmpRegion. */
+            r1n = 0;
+            r2n = 0;
+            for( i = 0; i < n; i++ ) {
+               if( dist[ i ] > dbreak ) {
+                  bptr[ 0 ][ i ] = r2ptrb[ 0 ][ r2n ];
+                  bptr[ 1 ][ i ] = r2ptrb[ 1 ][ r2n++ ];
+               } else {
+                  bptr[ 0 ][ i ] = r1ptrb[ 0 ][ r1n ];
+                  bptr[ 1 ][ i ] = r1ptrb[ 1 ][ r1n++ ];
+               }
+            }
+
+         }
+
+/* Free resources. */
+         if( r1pset ) r1pset = astAnnul( r1pset );
+         if( r2pset ) r2pset = astAnnul( r2pset );
+         if( r1psetb ) r1psetb = astAnnul( r1psetb );
+         if( r2psetb ) r2psetb = astAnnul( r2psetb );
+
+      }
+
+/* If required, transform the base frame positions into the current
+   Frame of the CmpRegion, storing them in the supplied array. Then
+   free resources. */
+      if( bpset ) {
+         cpset = astPointSet( n, ncur, " ", status );
+         astSetPoints( cpset, ptr );
+
+         (void) astTransform( map, bpset, 1, cpset );
+
+         cpset = astAnnul( cpset );
+         bpset = astAnnul( bpset );
+      }
+
+/* Free remaining resources. */
+      map = astAnnul( map );
+   }
+   frm = astAnnul( frm );
+
+/* Return the result. */
+   return result;
+}
+
+static void RegClearAttrib( AstRegion *this_region, const char *attrib,
+                            char **base_attrib, int *status ) {
+/*
+*  Name:
+*     RegClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void RegClearAttrib( AstRegion *this, const char *attrib,
+*                          char **base_attrib, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astRegClearAttrib method
+*     inherited from the Region class).
+
+*  Description:
+*     This function clears the value of a named attribute in both the base
+*     and current Frame in the FrameSet encapsulated within a Region, without
+*     remapping either Frame.
+*
+*     No error is reported if the attribute is not recognised by the base
+*     Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     attrib
+*        Pointer to a null terminated string holding the attribute name.
+*        NOTE, IT SHOULD BE ENTIRELY LOWER CASE.
+*     base_attrib
+*        Address of a location at which to return a pointer to the null
+*        terminated string holding the attribute name which was cleared in
+*        the base Frame of the encapsulated FrameSet. This may differ from
+*        the supplied attribute if the supplied attribute contains an axis
+*        index and the current->base Mapping in the FrameSet produces an
+*        axis permutation. The returned pointer should be freed using
+*        astFree when no longer needed. A NULL pointer may be supplied in
+*        which case no pointer is returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;
+   char *batt;
+   int rep;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_region;
+
+/* Use the RegClearAttrib method inherited from the parent class to clear the
+   attribute in the current and base Frames in the FrameSet encapsulated by
+   the parent Region structure. */
+   (*parent_regclearattrib)( this_region, attrib, &batt, status );
+
+/* Now clear the base Frame attribute to the component Regions (the current
+   Frame within the component Regions is equivalent to the base Frame in the
+   parent Region structure). Annul any "attribute unknown" error that results
+   from attempting to do this. */
+   if( astOK ) {
+      rep = astReporting( 0 );
+      astRegClearAttrib( this->region1, batt, NULL );
+      astRegClearAttrib( this->region2, batt, NULL );
+      if( astStatus == AST__BADAT ) astClearStatus;
+      astReporting( rep );
+   }
+
+/* If required, return the base Frame attribute name, otherwise free it. */
+   if( base_attrib ) {
+      *base_attrib = batt;
+   } else {
+      batt = astFree( batt );
+   }
+}
+
+static void ResetCache( AstRegion *this_region, int *status ){
+/*
+*  Name:
+*     ResetCache
+
+*  Purpose:
+*     Clear cached information within the supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void ResetCache( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Region member function (overrides the astResetCache method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function clears cached information from the supplied Region
+*     structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables *: */
+   AstCmpRegion *this;
+   int i;
+
+/* Check a Region was supplied. */
+   if( this_region ) {
+
+/* Get a pointer to the CmpRegion structure. */
+      this = (AstCmpRegion *) this_region;
+
+/* Clear information cached in the CmpRegion structure. */
+      for( i = 0; i < 2; i++ ) {
+         this->rvals[ i ] = astFree(  this->rvals[ i ] );
+         this->offs[ i ] = astFree(  this->offs[ i ] );
+         this->nbreak[ i ] = 0;
+         this->d0[ i ] = AST__BAD;
+         this->dtot[ i ] = AST__BAD;
+      }
+
+      this->bounded = -INT_MAX;
+
+/* Clear information cached in the component regions. */
+      if( this->region1 ) astResetCache( this->region1 );
+      if( this->region2 ) astResetCache( this->region2 );
+
+/* Clear information cached in the parent Region structure. */
+      (*parent_resetcache)( this_region, status );
+   }
+}
+
+static void SetBreakInfo( AstCmpRegion *this, int comp, int *status ){
+/*
+*  Name:
+*     SetBreakInfo
+
+*  Purpose:
+*     Ensure that a CmpRegion has information about the breaks in the
+*     boundaries of one of the two component Regions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void SetBreakInfo( AstCmpRegion *this, int comp, int *status )
+
+*  Class Membership:
+*     CmpRegion method.
+
+*  Description:
+*     This function returns without action if the supplied CmpRegion
+*     already contains break information for the specified component Region.
+*     Otherwise, it creates the required information and stores it in the
+*     CmpRegion.
+*
+*     Each component Region in the CmpRegion has a boundary. But in
+*     general only part of the boundary of a component Region will also
+*     be included in the CmpRegion boundary. Thus the component Region
+*     boundary can be broken up into sections; sections that form part
+*     of the CmpRegion boundary, and sections that do not. This function
+*     stores information about the breaks between these sections.
+*
+*     The complete boundary of a component Region is parameterised by a
+*     geodesic distance that goes from 0.0 to the value found by this
+*     function and stored in this->dtot (the total geodesic distance
+*     around the border). This function find the ranges of this parameter
+*     that correspond to the sections of the boundary that are also on the
+*     CmpRegion boundary, and thus finds the total length that the component
+*     boundary contributes to the CmpRegion boundary. This length is stored
+*     in "this->d0" (a two element array, one for each component Region).
+*
+*     It also find two arrays "this->rvals" and "this->offs" that allow a
+*     distance value in the range 0.0 to "this->d0" (i.e. a distance
+*     measured by skipping over the parts of the component boundary that
+*     are not on the CmpRegion boundary), to be converted into the
+*     corresponding distance value in the range 0.0 to "this->dtot" (i.e. a
+*     distance measured round the complete component boundary, including the
+*     parts not on the CmpRegion boundary).
+
+*  Parameters:
+*     this
+*        Pointer to a CmpRegion.
+*     comp
+*        Zero or one, indicating which component Region is to be checked.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* The number of points to be spread evenly over the entire boundary of the
+   component Region. */
+#define NP 101
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstPointSet *pset1;
+   AstPointSet *pset2;
+   AstRegion *other;
+   AstRegion *reg;
+   AstRegion *uother;
+   double **ptr2;
+   double **ptr1;
+   double *d;
+   double *offs;
+   double *p0;
+   double *p1;
+   double *p;
+   double *q;
+   double *rvals;
+   double delta;
+   double dist;
+   double pnt1[ 2 ];
+   double pnt2[ 2 ];
+   double rbad;
+   double rval;
+   double totdist;
+   int i;
+   int j;
+   int nn;
+   int prevgood;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* If the information describing breaks in the component boundary has not
+   yet been set up, do so now. */
+   if( this->d0[ comp ] == AST__BAD ) {
+
+/* Get a pointer to the component Region for which break information is
+   required. */
+      reg = comp ? this->region2 : this->region1;
+
+/* Check the component class implements the astRegTrace method. */
+      if( astRegTrace( reg, 0, NULL, NULL ) ) {
+
+/* Create a pointSet to hold axis values at evenly spaced positions along
+   the entire boundary of the selected component region. */
+         pset1 = astPointSet( NP, 2, " ", status );
+         ptr1 = astGetPoints( pset1 );
+
+/* Allocate memory to hold an array of corresponding scalar distances around
+   the boundary. */
+         d = astMalloc( NP*sizeof( double ) );
+
+/* Check pointers can be used safely. */
+         if( astOK ) {
+
+/* Get the distance increment between points (at this point the distances
+   are normalised so that the entire boundary has unit length, as
+   required by astRegTrace). */
+            delta = 1.0/( NP - 1 );
+
+/* Set up the array of evenly spaced distances around the boundary of the
+   component region. */
+            for( i = 0; i < NP; i++ ) d[ i ] = i*delta;
+
+/* Get the corresponding Frame positions. If the Region is unbounded
+   (e.g. a negated circle, etc), then negate it first in the hope that
+   this may produced a bounded Region. */
+            if( astGetBounded( reg ) ) {
+               (void) astRegTrace( reg, NP, d, ptr1 );
+            } else {
+               AstRegion *negation = astGetNegation( reg );
+               (void) astRegTrace( negation, NP, d, ptr1 );
+               negation = astAnnul( negation );
+            }
+
+/* Get a pointer to the other component Region. */
+            other = comp ? this->region1 : this->region2;
+
+/* If the two component Regions are ANDed together, we want to remove the
+   positions from the boundary of the required component Region that fall
+   outside the other region. We can do this by simply using the other Region
+   as a Mapping. If the two component Regions are ORed together, we want to
+   remove the position that fall within (rather than outside) the other
+   Region. To do this we need to negate the other region  first. */
+            if( this->oper == AST__OR ) {
+               uother = astGetNegation( other );
+            } else {
+               uother = astClone( other );
+            }
+
+/* Now transform the points on the boundary of the selected Region in
+   order to set invalid those positions which are not on the boundary of
+   the supplied CmpRegion. */
+            pset2 = astTransform( uother, pset1, 1, NULL );
+
+/* Annul the negation pointer */
+            uother = astAnnul( uother );
+
+/* Modify the distance array by setting invalid each element that is not
+   on the boundary of the CmpRegion. */
+            ptr2 = astGetPoints( pset2 );
+            if( astOK ) {
+               p = ptr2[ 0 ];
+               q = ptr2[ 1 ];
+               for( i = 0; i < NP; i++,p++,q++ ) {
+                  if( *p == AST__BAD || *q == AST__BAD ) d[ i ] = AST__BAD;
+               }
+
+/* At each good/bad junction in this list, extend the good section by one
+   point. This ensures that the good sections of the curve do in fact
+   touch each other (they may in fact overlap a little but that does not
+   matter). */
+               prevgood = ( d[ 0 ] != AST__BAD );
+               for( i = 1; i < NP; i++,p++,q++ ) {
+                  if( d[ i ] == AST__BAD ) {
+                     if( prevgood ) d[ i ] = i*delta;
+                     prevgood = 0;
+
+                  } else {
+                     if( !prevgood ) d[ i - 1 ] = ( i - 1 )*delta;
+                     prevgood = 1;
+                  }
+               }
+
+/* Find the total geodesic distance around the border. This is only an
+   approximation but it is only used to give a relative weight to this
+   component within the CmpFrame, and so does not need to be very accurate. */
+               frm = astGetFrame( reg->frameset, AST__CURRENT );
+               p0 = ptr1[ 0 ];
+               p1 = ptr1[ 1 ];
+               totdist = 0;
+               pnt1[ 0 ] = *(p0++);
+               pnt1[ 1 ] = *(p1++);
+               for( i = 1; i < NP; i++ ) {
+                  pnt2[ 0 ] = *(p0++);
+                  pnt2[ 1 ] = *(p1++);
+                  dist = astDistance( frm, pnt1, pnt2 );
+                  if( dist != AST__BAD ) totdist += dist;
+                  pnt1[ 0 ] = pnt2[ 0 ];
+                  pnt1[ 1 ] = pnt2[ 1 ];
+               }
+
+/* Change delta so that it represents a geodesic distance, rather than a
+   normalised distance in the range zero to one. Working in geodesic distance
+   (e.g. Radians on a SkyFrame) prevents Regions higher up in a complex nested
+   CmpRegion being given higher priority than a lower Region. */
+               delta *= totdist;
+
+/* Now create two arrays - "rvals" holds the distance travelled around
+   the used parts of the border at which breaks occur, "offs" holds the jump
+   in distance around the complete border at each break. The distance
+   around the complete border is normalised to the range [0.0,1.0].
+   Therefore the total distance around the used parts of the border will in
+   general be less than 1.0 */
+               if( d[ 0 ] == AST__BAD ) {
+                  nn = 1;
+                  j = 0;
+                  rvals = astMalloc( sizeof( double ) );
+                  offs = astMalloc( sizeof( double ) );
+                  if( astOK ) rvals[ 0 ] = -0.5*delta;
+                  rbad = 0.5;
+                  prevgood = 0;
+                  rval = -0.5*delta;
+
+               } else {
+                  nn = 0;
+                  rvals = NULL;
+                  offs = NULL;
+                  prevgood = 1;
+                  rbad = 0.0;
+                  rval = 0.0;
+               }
+
+               for( i = 1; i < NP; i++,p++,q++ ) {
+
+                  if( d[ i ] == AST__BAD ) {
+                     if( prevgood ) {
+                        j = nn++;
+                        rvals = astGrow( rvals, nn, sizeof( double ) );
+                        offs = astGrow( offs, nn, sizeof( double ) );
+                        if( astOK ) {
+                           rvals[ j ] = rval + 0.5*delta;
+                           rbad = 0.0;
+                        } else {
+                           break;
+                        }
+                        prevgood = 0;
+                     }
+
+                     rbad += 1.0;
+
+                  } else {
+                     if( !prevgood ) {
+                        offs[ j ] = rbad*delta;
+                        prevgood = 1;
+                     }
+                     rval += delta;
+                  }
+               }
+
+               if( !prevgood ) {
+                  rval += 0.5*delta;
+                  offs[ j ] = rbad*delta;
+               }
+
+/* Record the information in the CmpRegion structure. */
+               this->rvals[ comp ] = rvals;
+               this->offs[ comp ] = offs;
+               this->nbreak[ comp ] = nn;
+               this->d0[ comp ] = rval;
+               this->dtot[ comp ] = totdist;
+            }
+
+/* Free resources. */
+            pset2 = astAnnul( pset2 );
+         }
+
+         pset1 = astAnnul( pset1 );
+         d = astFree( d );
+
+      }
+   }
+}
+
+#undef NP
+
+static void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status ) {
+/*
+*  Name:
+*     SetRegFS
+
+*  Purpose:
+*     Stores a new FrameSet in a Region
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     CmpRegion method (over-rides the astSetRegFS method inherited from
+*     the Region class).
+
+*  Description:
+*     This function creates a new FrameSet and stores it in the supplied
+*     Region. The new FrameSet contains two copies of the supplied
+*     Frame, connected by a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     frm
+*        The Frame to use.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstRegion *creg;        /* Pointer to component Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent method to store the FrameSet in the parent Region
+   structure. */
+   (* parent_setregfs)( this_region, frm, status );
+
+/* If either component Region has a dummy FrameSet use this method
+   recursively to give them the same FrameSet. */
+   creg = ((AstCmpRegion *) this_region )->region1;
+   if( creg && !astGetRegionFS( creg ) ) astSetRegFS( creg, frm );
+
+   creg = ((AstCmpRegion *) this_region )->region2;
+   if( creg && !astGetRegionFS( creg ) ) astSetRegFS( creg, frm );
+
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     CmpRegion method (over-rides the astSimplify method inherited from
+*     the Region class).
+
+*  Description:
+*     This function simplifies a CmpRegion to eliminate redundant
+*     computational steps, or to merge separate steps which can be
+*     performed more efficiently in a single operation.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A new pointer to the (possibly simplified) Region.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+
+*  Deficiencies:
+*     - Currently, this function does not attempt to map the component
+*     Regions into the current Frame of the parent Region structure.
+*     Both components should be mapped into the current Frame, and if the
+*     resulting base->current Mappings in *both* remapped component Regions are
+*     UnitMaps, then a new CmpRegion should be created from the re-mapped
+*     Regions.
+*/
+
+/* Local Variables: */
+   AstCmpRegion *newb;           /* New CmpRegion defined in base Frame */
+   AstCmpRegion *newc;           /* New CmpRegion defined in current Frame */
+   AstFrame *frm;                /* Current Frame */
+   AstMapping *map;              /* Base->current Mapping */
+   AstMapping *result;           /* Result pointer to return */
+   AstRegion *csreg1;            /* Copy of simplified first component Region */
+   AstRegion *csreg2;            /* Copy of simplified second component Region */
+   AstRegion *nullreg;           /* Null or infinfite Region */
+   AstRegion *othereg;           /* Non-Null and non-infinfite Region */
+   AstRegion *reg1;              /* First component Region */
+   AstRegion *reg2;              /* Second component Region */
+   AstRegion *sreg1;             /* Simplified first component Region */
+   AstRegion *sreg2;             /* Simplified second component Region */
+   int neg1;                     /* Negated flag to use with first component */
+   int neg2;                     /* Negated flag to use with second component */
+   int oper;                     /* Boolean operator used to combine components */
+   int overlap;                  /* Nature of overlap between components */
+   int simpler;                  /* Has any simplification taken place? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the parent Simplify method inherited from the Region class. This
+   will simplify the encapsulated FrameSet and uncertainty Region. The
+   returned pointer identifies a region within the current Frame of the
+   FrameSet encapsulated by the parent Region structure. Note this by
+   storing the pointer in the "newc" ("c" for "current") variable. */
+   newc = (AstCmpRegion *) (*parent_simplify)( this_mapping, status );
+
+/* Note if any simplification took place. This is assumed to be the case
+   if the pointer returned by the above call is different to the supplied
+   pointer. */
+   simpler = ( (AstMapping *) newc != this_mapping );
+
+/* Below we may create a new simplified region which identifies a region
+   within the base Frame of the FrameSet encapsulated by the parent Region
+   structure. Such a result will need to be mapped into the current Frame
+   before being returned. The "newb" variable ("b" for "base") will be
+   used to store a pointer to such a result. Initialise this variable to
+   indicate that we do not yet have a base Frame result. */
+   newb = NULL;
+
+/* Get the component Regions, how they should be combined, and the
+   Negated values which should be used with them. The returned values
+   take account of whether the supplied CmpRegion has itself been Negated
+   or not. The returned Regions represent regions within the base Frame
+   of the FrameSet encapsulated by the parent Region structure. */
+   GetRegions( newc, &reg1, &reg2, &oper, &neg1, &neg2, status );
+
+/* If the first component Region does not have the required value for
+   its "Negated" attribute, use the negation of "reg1" in place of "reg1"
+   itself. */
+   if( neg1 != astGetNegated( reg1 ) ) {
+      AstRegion *tmp = astGetNegation( reg1 );
+      (void) astAnnul( reg1 );
+      reg1 = tmp;
+   }
+
+/* If the second component Region does not have the required value for
+   its "Negated" attribute, use the negation of "reg2" in place of "reg2"
+   itself. */
+   if( neg2 != astGetNegated( reg2 ) ) {
+      AstRegion *tmp = astGetNegation( reg2 );
+      (void) astAnnul( reg2 );
+      reg2 = tmp;
+   }
+
+/* Simplify each of the two components. */
+   sreg1 = astSimplify( reg1 );
+   sreg2 = astSimplify( reg2 );
+
+/* Note if any simplification took place. */
+   simpler = simpler || ( sreg1 != reg1 || sreg2 != reg2 );
+
+/* If either component is null or infinite we can exclude it from the
+   returned Region. */
+   if( astIsANullRegion( sreg1 ) || astIsANullRegion( sreg2 ) ) {
+
+/* Get a pointer to the non-null Region. The following is still valid
+   even if both regions are null or infinite. */
+      if( astIsANullRegion( sreg1 ) ){
+         nullreg = sreg1;
+         othereg = sreg2;
+      } else {
+         nullreg = sreg2;
+         othereg = sreg1;
+      }
+
+/* If null.. */
+      if( !astGetNegated( nullreg ) ){
+         if( oper == AST__AND ) {
+            newb = (AstCmpRegion *) astNullRegion( othereg,
+                                             astGetUnc( othereg, 0 ), "", status );
+
+         } else if( oper == AST__OR ) {
+            newb = astCopy( othereg );
+
+         } else {
+            astError( AST__INTER, "astSimplify(%s): The %s refers to an "
+                      "unknown boolean operator with identifier %d (internal "
+                      "AST programming error).", status, astGetClass( newc ),
+                      astGetClass( newc ), oper );
+         }
+
+/* If infinite.. */
+      } else {
+         if( oper == AST__AND ) {
+            newb = astCopy( othereg );
+
+         } else if( oper == AST__OR ) {
+            newb = (AstCmpRegion *) astNullRegion( othereg,
+                                      astGetUnc( othereg, 0 ), "negated=1", status );
+
+         } else {
+            astError( AST__INTER, "astSimplify(%s): The %s refers to an "
+                      "unknown boolean operator with identifier %d (internal "
+                      "AST programming error).", status, astGetClass( newc ),
+                      astGetClass( newc ), oper );
+         }
+      }
+
+/* Flag that we have done some simplication.*/
+      simpler = 1;
+
+/* If neither component is null or infinite, see if it is possible to
+   remove one or both of the components on the basis of the overlap
+   between them. */
+   } else {
+      overlap = astOverlap( sreg1, sreg2 );
+
+/* If the components have no overlap, and they are combined using AND, then
+   the CmpRegion is null. */
+      if( ( overlap == 1 || overlap == 6 ) && oper == AST__AND ) {
+         newb = (AstCmpRegion *) astNullRegion( sreg1, astGetUnc( sreg1, 0 ),
+                                                "", status );
+         simpler = 1;
+
+/* If one component is the negation of the other component, and they are
+   combined using OR, then the CmpRegion is infinite. This is represented
+   by a negated null region.*/
+      } else if( overlap == 6 && oper == AST__OR ) {
+         newb = (AstCmpRegion  *) astNullRegion( sreg1, astGetUnc( sreg1, 0 ),
+                                                 "negated=1", status );
+         simpler = 1;
+
+/* If the two components are identical... */
+      } else if( overlap == 5 ) {
+         simpler = 1;
+
+/* If combined with AND or OR, the CmpRegion can be replaced by the first
+   (or second) component Region. */
+         if( oper == AST__AND || oper == AST__OR ) {
+            newb = astCopy( sreg1 );
+         } else {
+            astError( AST__INTER, "astSimplify(%s): The %s refers to an "
+                      "unknown boolean operator with identifier %d (internal "
+                      "AST programming error).", status, astGetClass( newc ),
+                      astGetClass( newc ), oper );
+         }
+
+/* If the first component is entirely contained within the second
+   component, and they are combined using AND or OR, then the CmpRegion
+   can be replaced by the first or second component. */
+      } else if( overlap == 2 && ( oper == AST__AND || oper == AST__OR ) ){
+         newb = astCopy( ( oper == AST__AND ) ? sreg1 : sreg2 );
+         simpler = 1;
+
+/* If the second component is entirely contained within the first
+   component, and they are combined using AND or OR, then the CmpRegion
+   can be replaced by the second or first component. */
+      } else if( overlap == 3 && ( oper == AST__AND || oper == AST__OR ) ){
+         newb = astCopy( ( oper == AST__AND ) ? sreg2 : sreg1 );
+         simpler = 1;
+
+/* Otherwise, no further simplication is possible, so either create a new
+   CmpRegion or leave the "newb" pointer NULL (which will cause "newc" to
+   be used), depending on whether the components were simplified. */
+      } else if( simpler ){
+         csreg1 = astCopy( sreg1 );
+         csreg2 = astCopy( sreg2 );
+         newb = astCmpRegion( csreg1, csreg2, oper, "", status );
+         csreg1 = astAnnul( csreg1 );
+         csreg2 = astAnnul( csreg2 );
+
+      }
+   }
+
+/* If any simplification took place, decide whether to use the "newc" or
+   "newb" pointer for the returned Mapping. If "newb" is non-NULL we use
+   it, otherwise we use "newc". If "newb" is used we must first map the
+   result Region from the base Frame of the FrameSet encapsulated
+   by the parent Region structure, to the current Frame. */
+   if( simpler ) {
+      if( newb ){
+         frm = astGetFrame( ((AstRegion *) newc)->frameset, AST__CURRENT );
+         map = astGetMapping( ((AstRegion *) newc)->frameset, AST__BASE, AST__CURRENT );
+         result = astMapRegion( newb, map, frm );
+         frm = astAnnul( frm );
+         map = astAnnul( map );
+         newb = astAnnul( newb );
+      } else {
+         result = astClone( newc );
+      }
+
+/* If no simplification took place, return a clone of the supplied pointer. */
+   } else {
+      result = astClone( this_mapping );
+   }
+
+/* Free resources. */
+   reg1 = astAnnul( reg1 );
+   reg2 = astAnnul( reg2 );
+   sreg1 = astAnnul( sreg1 );
+   sreg2 = astAnnul( sreg2 );
+   newc = astAnnul( newc );
+
+/* If an error occurred, annul the returned Mapping. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a CmpRegion to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     CmpRegion member function (over-rides the astTransform method inherited
+*     from the Region class).
+
+*  Description:
+*     This function takes a CmpRegion and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required Region.
+*     This implies applying each of the CmpRegion's component Regions in turn,
+*     either in series or in parallel.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpRegion.
+*     in
+*        Pointer to the PointSet associated with the input coordinate values.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the CmpRegion being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;           /* Pointer to the CmpRegion structure */
+   AstPointSet *ps1;             /* Pointer to PointSet for first component */
+   AstPointSet *ps2;             /* Pointer to PointSet for second component */
+   AstPointSet *pset_tmp;        /* Pointer to PointSet holding base Frame positions*/
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstRegion *reg1;              /* Pointer to first component Region */
+   AstRegion *reg2;              /* Pointer to second component Region */
+   double **ptr1;                /* Pointer to first component axis values */
+   double **ptr2;                /* Pointer to second component axis values */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   int coord;                    /* Zero-based index for coordinates */
+   int good;                     /* Is the point inside the CmpRegion? */
+   int ncoord_out;               /* No. of coordinates per output point */
+   int ncoord_tmp;               /* No. of coordinates per base Frame point */
+   int neg1;                     /* Negated value for first component Region */
+   int neg2;                     /* Negated value for second component Region */
+   int npoint;                   /* No. of points */
+   int oper;                     /* Boolean operator to use */
+   int point;                    /* Loop counter for points */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a Pointer to the CmpRegion structure */
+   this = (AstCmpRegion *) this_mapping;
+
+/* Get the component Regions, how they should be combined, and the
+   Negated values which should be used with them. The returned values
+   take account of whether the supplied CmpRegion has itself been Negated
+   or not. The returned Regions represent regions within the base Frame
+   of the FrameSet encapsulated by the parent Region structure. */
+   GetRegions( this, &reg1, &reg2, &oper, &neg1, &neg2, status );
+
+/* If the first component Region does not have the required value for
+   its "Negated" attribute, use the negation of "reg1" in place of "reg1"
+   itself. */
+   if( neg1 != astGetNegated( reg1 ) ) {
+      AstRegion *tmp = astGetNegation( reg1 );
+      (void) astAnnul( reg1 );
+      reg1 = tmp;
+   }
+
+/* If the second component Region does not have the required value for
+   its "Negated" attribute, use the negation of "reg2" in place of "reg2"
+   itself. */
+   if( neg2 != astGetNegated( reg2 ) ) {
+      AstRegion *tmp = astGetNegation( reg2 );
+      (void) astAnnul( reg2 );
+      reg2 = tmp;
+   }
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Region class. This function validates
+   all arguments and generates an output PointSet if necessary, containing
+   a copy of the input PointSet. */
+   result = (*parent_transform)( this_mapping, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* First use the encapsulated FrameSet in the parent Region structure to
+   transform the supplied positions from the current Frame in the
+   encapsulated FrameSet (the Frame represented by the CmpRegion), to the
+   base Frame (the Frame in which the component Regions are defined). Note,
+   the returned pointer may be a clone of the "in" pointer, and so we
+   must be carefull not to modify the contents of the returned PointSet. */
+   pset_tmp = astRegTransform( this, in, 0, NULL, NULL );
+
+/* Now transform this PointSet using each of the two component Regions in
+   turn. */
+   ps1 = astTransform( reg1, pset_tmp, 0, NULL );
+   ps2 = astTransform( reg2, pset_tmp, 0, NULL );
+
+/* Determine the numbers of points and coordinates per point for these base
+   Frame PointSets and obtain pointers for accessing the base Frame and output
+   coordinate values. */
+   npoint = astGetNpoint( pset_tmp );
+   ncoord_tmp = astGetNcoord( pset_tmp );
+   ptr1 = astGetPoints( ps1 );
+   ptr2 = astGetPoints( ps2 );
+   ncoord_out = astGetNcoord( result );
+   ptr_out = astGetPoints( result );
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+
+/* First deal with ANDed Regions */
+      if( oper == AST__AND ) {
+         for ( point = 0; point < npoint; point++ ) {
+            good = 0;
+
+            for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+               if( ptr1[ coord ][ point ] != AST__BAD &&
+                   ptr2[ coord ][ point ] != AST__BAD ) {
+                  good = 1;
+                  break;
+               }
+            }
+
+            if( !good ) {
+               for ( coord = 0; coord < ncoord_out; coord++ ) {
+                  ptr_out[ coord ][ point ] = AST__BAD;
+               }
+            }
+         }
+
+/* Now deal with ORed Regions */
+      } else if( oper == AST__OR ) {
+         for ( point = 0; point < npoint; point++ ) {
+            good = 0;
+
+            for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+               if( ptr1[ coord ][ point ] != AST__BAD ||
+                   ptr2[ coord ][ point ] != AST__BAD ) {
+                  good = 1;
+                  break;
+               }
+            }
+
+            if( !good ) {
+               for ( coord = 0; coord < ncoord_out; coord++ ) {
+                  ptr_out[ coord ][ point ] = AST__BAD;
+               }
+            }
+         }
+
+/* Report error for any unknown operator. */
+      } else if( astOK ) {
+         astError( AST__INTER, "astTransform(%s): The %s refers to an unknown "
+                   "boolean operator with identifier %d (internal AST "
+                   "programming error).", status, astGetClass( this ),
+                    astGetClass( this ), oper );
+      }
+   }
+
+/* Free resources. */
+   reg1 = astAnnul( reg1 );
+   reg2 = astAnnul( reg2 );
+   ps1 = astAnnul( ps1 );
+   ps2 = astAnnul( ps2 );
+   pset_tmp = astAnnul( pset_tmp );
+
+/* If an error occurred, clean up by deleting the output PointSet (if
+   allocated by this function) and setting a NULL result pointer. */
+   if ( !astOK ) {
+      if ( !out ) result = astDelete( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static void XORCheck( AstCmpRegion *this, int *status ) {
+/*
+*  Name:
+*     XORCheck
+
+*  Purpose:
+*     Check if the supplied CmpRegion represents an XOR operation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*      void XORCheck( AstCmpRegion *this, int *status )
+
+*  Class Membership:
+*     CmpRegion method
+
+*  Decription:
+*     This function analyses the component Regions within the supplied
+*     CmpRegion to see if the CmpRegion is equivalent to an XOR operation
+*     on two other Regions. If it is, teh Regions that are XORed are
+*     stored in the supplied CmpRegion.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpRegion.
+
+*/
+
+/* Local Variables: */
+   AstCmpRegion *cmpreg1;
+   AstCmpRegion *cmpreg2;
+   int xor;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If the CmpRegion is already known to be an XOR operation, return
+   without action. */
+   if( this->xor1 ) return;
+
+/* To be equivalent to an XOR operation, the supplied CmpRegion must be an
+   OR operation and each component Region must be a CmpRegion. */
+   if( this->oper == AST__OR && astIsACmpRegion( this->region1 )
+                             && astIsACmpRegion( this->region2 ) ) {
+      cmpreg1 = (AstCmpRegion *) this->region1;
+      cmpreg2 = (AstCmpRegion *) this->region2;
+
+/* Each component CmpRegion must be an AND operation. */
+      if( cmpreg1->oper == AST__AND && cmpreg2->oper == AST__AND ) {
+
+/* Temporarily negate the first component of the first CmpRegion. */
+         astNegate( cmpreg1->region1 );
+
+/* Initially, assume the supplied CmpRegion is not equivalent to an XOR
+   operation. */
+         xor = 0;
+
+/* This negated region must be equal to one of the two component Regions
+   in the second component CmpRegion. Check the first. */
+         if( astEqual( cmpreg1->region1, cmpreg2->region1 ) ) {
+
+/* We now check that the other two Regions are equal (after negating the
+   first). If so, set "xor" non-zero. */
+            astNegate( cmpreg1->region2 );
+            if( astEqual( cmpreg1->region2, cmpreg2->region2 ) ) xor = 1;
+            astNegate( cmpreg1->region2 );
+
+/* Do equiovalent checks the other way round. */
+         } else if( astEqual( cmpreg1->region1, cmpreg2->region2 ) ) {
+            astNegate( cmpreg1->region2 );
+            if( astEqual( cmpreg1->region2, cmpreg2->region1 ) ) xor = 1;
+            astNegate( cmpreg1->region2 );
+         }
+
+/* Re-instate the original state of the Negated attribute in the first
+   component of the first CmpRegion. */
+         astNegate( cmpreg1->region1 );
+
+/* If the supplied CmpRegion is equivalent to an XOR operation, store
+   copies of the components in the supplied CmpRegion. */
+         if( xor ) {
+            this->xor1 = astCopy( cmpreg1->region1 );
+            this->xor2 = astCopy( cmpreg1->region2 );
+
+/* We need to negate one of these two Region (it doesn't matter which),
+   and we choose to negate which ever of them is already negated (so that
+   it becomes un-negated). */
+            if( astGetNegated( this->xor1 ) ) {
+               astNegate( this->xor1 );
+            } else {
+               astNegate( this->xor2 );
+            }
+         }
+      }
+   }
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for CmpRegion objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for CmpRegion objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the component
+*     Regions within the CmpRegion.
+*/
+
+/* Local Variables: */
+   AstCmpRegion *in;                /* Pointer to input CmpRegion */
+   AstCmpRegion *out;               /* Pointer to output CmpRegion */
+   int i;                           /* Loop count */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output CmpRegions. */
+   in = (AstCmpRegion *) objin;
+   out = (AstCmpRegion *) objout;
+
+/* For safety, start by clearing any memory references in the output
+   Region that were copied from the input Region. */
+   out->region1 = NULL;
+   out->region2 = NULL;
+   out->xor1 = NULL;
+   out->xor2 = NULL;
+
+   for( i = 0; i < 2; i++ ) {
+      out->rvals[ i ] = NULL;
+      out->offs[ i ] = NULL;
+   }
+
+/* Make copies of these Regions and store pointers to them in the output
+   CmpRegion structure. */
+   out->region1 = astCopy( in->region1 );
+   out->region2 = astCopy( in->region2 );
+   if( in->xor1 ) out->xor1 = astCopy( in->xor1 );
+   if( in->xor2 ) out->xor2 = astCopy( in->xor2 );
+
+/* Copy cached arrays. */
+   for( i = 0; i < 2; i++ ) {
+      out->rvals[ i ] = astStore( NULL, in->rvals[ i ], in->nbreak[ i ]*sizeof( **in->rvals ) );
+      out->offs[ i ] = astStore( NULL, in->offs[ i ], in->nbreak[ i ]*sizeof( **in->offs ) );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for CmpRegion objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for CmpRegion objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstCmpRegion *this;              /* Pointer to CmpRegion */
+   int i;
+
+/* Obtain a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) obj;
+
+/* Free arrays holding cached information. */
+   for( i = 0; i < 2; i++ ) {
+      this->rvals[ i ] = astFree( this->rvals[ i ] );
+      this->offs[ i ] = astFree( this->offs[ i ] );
+   }
+
+/* Annul the pointers to the component Regions. */
+   this->region1 = astAnnul( this->region1 );
+   this->region2 = astAnnul( this->region2 );
+   if( this->xor1 ) this->xor1 = astAnnul( this->xor1 );
+   if( this->xor2 ) this->xor2 = astAnnul( this->xor2 );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for CmpRegion objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the CmpRegion class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the CmpRegion whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstRegion *reg1;              /* First Region to include in dump */
+   AstRegion *reg2;              /* Second Region to include in dump */
+   AstCmpRegion *this;           /* Pointer to the CmpRegion structure */
+   const char *comment;          /* Pointer to comment string */
+   int ival;                     /* Integer value */
+   int oper;                     /* The operator to include in the dump */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpRegion structure. */
+   this = (AstCmpRegion *) this_object;
+
+/* Check if this CmpRegion has an equivalent XOR representation. Is so,
+   store details of the XOR representation in the CmpRegion. */
+   XORCheck( this, status );
+
+/* Choose the operator and component regions to include in the dump. If
+   the CmpRegion originally used an XOR operator, then save the XORed
+   regions. Otherwise, store the real component Regions. */
+   if( this->xor1 ) {
+      oper = AST__XOR;
+      reg1 = this->xor1;
+      reg2 = this->xor2;
+   } else {
+      oper = this->oper;
+      reg1 = this->region1;
+      reg2 = this->region2;
+   }
+
+/* Write out values representing the instance variables for the CmpRegion
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Oper */
+/* ------- */
+   ival = oper;
+   if( ival == AST__AND ) {
+      comment = "Regions combined using Boolean AND";
+   } else if( ival == AST__OR ) {
+      comment = "Regions combined using Boolean OR";
+   } else if( ival == AST__XOR ) {
+      comment = "Regions combined using Boolean XOR";
+   } else {
+      comment = "Regions combined using unknown operator";
+   }
+   astWriteInt( channel, "Operator", 1, 0, ival, comment );
+
+/* First Region. */
+/* -------------- */
+   astWriteObject( channel, "RegionA", 1, 1, reg1,
+                   "First component Region" );
+
+/* Second Region. */
+/* --------------- */
+   astWriteObject( channel, "RegionB", 1, 1, reg2,
+                   "Second component Region" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsACmpRegion and astCheckCmpRegion functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(CmpRegion,Region)
+astMAKE_CHECK(CmpRegion)
+
+AstCmpRegion *astCmpRegion_( void *region1_void, void *region2_void, int oper,
+                             const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astCmpRegion
+
+*  Purpose:
+*     Create a CmpRegion.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     AstCmpRegion *astCmpRegion( AstRegion *region1, AstRegion *region2,
+*                                 int oper, const char *options, ..., int *status )
+
+*  Class Membership:
+*     CmpRegion constructor.
+
+*  Description:
+*     This function creates a new CmpRegion and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     region1
+*        Pointer to the first Region.
+*     region2
+*        Pointer to the second Region.
+*     oper
+*        The boolean operator with which to combine the two Regions. Either
+*        AST__AND or AST__OR.
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new CmpRegion. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new CmpRegion.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic CmpRegion constructor which is
+*     available via the protected interface to the CmpRegion class.  A
+*     public interface is provided by the astCmpRegionId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "region1" and "region2" parameters are of type (void *) and are
+*     converted and validated within the function itself.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstCmpRegion *new;              /* Pointer to new CmpRegion */
+   AstRegion *region1;             /* Pointer to first Region structure */
+   AstRegion *region2;             /* Pointer to second Region structure */
+   va_list args;                   /* Variable argument list */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain and validate pointers to the Region structures provided. */
+   region1 = astCheckRegion( region1_void );
+   region2 = astCheckRegion( region2_void );
+   if ( astOK ) {
+
+/* Initialise the CmpRegion, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitCmpRegion( NULL, sizeof( AstCmpRegion ), !class_init,
+                              &class_vtab, "CmpRegion", region1, region2,
+                              oper );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new CmpRegion's
+   attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new CmpRegion. */
+   return new;
+}
+
+AstCmpRegion *astCmpRegionId_( void *region1_void, void *region2_void,
+                               int oper, const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astCmpRegion
+f     AST_CMPREGION
+
+*  Purpose:
+*     Create a CmpRegion.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "cmpregion.h"
+c     AstCmpRegion *astCmpRegion( AstRegion *region1, AstRegion *region2,
+c                                 int oper, const char *options, ... )
+f     RESULT = AST_CMPREGION( REGION1, REGION2, OPER, OPTIONS, STATUS )
+
+*  Class Membership:
+*     CmpRegion constructor.
+
+*  Description:
+*     This function creates a new CmpRegion and optionally initialises
+*     its attributes.
+*
+*     A CmpRegion is a Region which allows two component
+*     Regions (of any class) to be combined to form a more complex
+*     Region. This combination may be performed a boolean AND, OR
+*     or XOR (exclusive OR) operator. If the AND operator is
+*     used, then a position is inside the CmpRegion only if it is
+*     inside both of its two component Regions. If the OR operator is
+*     used, then a position is inside the CmpRegion if it is inside
+*     either (or both) of its two component Regions. If the XOR operator
+*     is used, then a position is inside the CmpRegion if it is inside
+*     one but not both of its two component Regions. Other operators can
+*     be formed by negating one or both component Regions before using
+*     them to construct a new CmpRegion.
+*
+*     The two component Region need not refer to the same coordinate
+*     Frame, but it must be possible for the
+c     astConvert
+f     AST_CONVERT
+*     function to determine a Mapping between them (an error will be
+*     reported otherwise when the CmpRegion is created). For instance,
+*     a CmpRegion may combine a Region defined within an ICRS SkyFrame
+*     with a Region defined within a Galactic SkyFrame. This is
+*     acceptable because the SkyFrame class knows how to convert between
+*     these two systems, and consequently the
+c     astConvert
+f     AST_CONVERT
+*     function will also be able to convert between them. In such cases,
+*     the second component Region will be mapped into the coordinate Frame
+*     of the first component Region, and the Frame represented by the
+*     CmpRegion as a whole will be the Frame of the first component Region.
+*
+*     Since a CmpRegion is itself a Region, it can be used as a
+*     component in forming further CmpRegions. Regions of arbitrary
+*     complexity may be built from simple individual Regions in this
+*     way.
+
+*  Parameters:
+c     region1
+f     REGION1 = INTEGER (Given)
+*        Pointer to the first component Region.
+c     region2
+f     REGION2 = INTEGER (Given)
+*        Pointer to the second component Region. This Region will be
+*        transformed into the coordinate Frame of the first region before
+*        use. An error will be reported if this is not possible.
+c     oper
+f     OPER = INTEGER (Given)
+*        The boolean operator with which to combine the two Regions. This
+*        must be one of the symbolic constants AST__AND, AST__OR or AST__XOR.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new CmpRegion. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new CmpRegion. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astCmpRegion()
+f     AST_CMPREGION = INTEGER
+*        A pointer to the new CmpRegion.
+
+*  Notes:
+*     - If one of the supplied Regions has an associated uncertainty,
+*     that uncertainty will also be used for the returned CmpRegion.
+*     If both supplied Regions have associated uncertainties, the
+*     uncertainty associated with the first Region will be used for the
+*     returned CmpRegion.
+*     - Deep copies are taken of the supplied Regions. This means that
+*     any subsequent changes made to the component Regions using the
+*     supplied pointers will have no effect on the CmpRegion.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astCmpRegion constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astCmpRegion_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - Because no checking or casting of arguments is performed
+*     before the function is invoked, the "region1" and "region2" parameters
+*     are of type (void *) and are converted from an ID value to a
+*     pointer and validated within the function itself.
+*     - The variable argument list also prevents this function from
+*     invoking astCmpRegion_ directly, so it must be a re-implementation
+*     of it in all respects, except for the conversions between IDs
+*     and pointers on input/output of Objects.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstCmpRegion *new;              /* Pointer to new CmpRegion */
+   AstRegion *region1;             /* Pointer to first Region structure */
+   AstRegion *region2;             /* Pointer to second Region structure */
+   va_list args;                   /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain the Region pointers from the ID's supplied and validate the
+   pointers to ensure they identify valid Regions. */
+   region1 = astVerifyRegion( astMakePointer( region1_void ) );
+   region2 = astVerifyRegion( astMakePointer( region2_void ) );
+   if ( astOK ) {
+
+/* Initialise the CmpRegion, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitCmpRegion( NULL, sizeof( AstCmpRegion ), !class_init,
+                              &class_vtab, "CmpRegion", region1, region2,
+                              oper );
+
+/* If successful, note that the virtual function table has been initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new CmpRegion's
+   attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return an ID value for the new CmpRegion. */
+   return astMakeId( new );
+}
+
+AstCmpRegion *astInitCmpRegion_( void *mem, size_t size, int init,
+                                 AstCmpRegionVtab *vtab, const char *name,
+                                 AstRegion *region1, AstRegion *region2,
+                                 int oper, int *status ) {
+/*
+*+
+*  Name:
+*     astInitCmpRegion
+
+*  Purpose:
+*     Initialise a CmpRegion.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     AstCmpRegion *astInitCmpRegion_( void *mem, size_t size, int init,
+*                                      AstCmpRegionVtab *vtab, const char *name,
+*                                      AstRegion *region1, AstRegion *region2,
+*                                      int oper )
+
+*  Class Membership:
+*     CmpRegion initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new CmpRegion object. It allocates memory (if necessary) to
+*     accommodate the CmpRegion plus any additional data associated with the
+*     derived class. It then initialises a CmpRegion structure at the start
+*     of this memory. If the "init" flag is set, it also initialises the
+*     contents of a virtual function table for a CmpRegion at the start of
+*     the memory passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the CmpRegion is to be initialised.
+*        This must be of sufficient size to accommodate the CmpRegion data
+*        (sizeof(CmpRegion)) plus any data used by the derived class. If a
+*        value of NULL is given, this function will allocate the memory itself
+*        using the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the CmpRegion (plus derived class
+*        data). This will be used to allocate memory if a value of NULL is
+*        given for the "mem" parameter. This value is also stored in the
+*        CmpRegion structure, so a valid value must be supplied even if not
+*        required for allocating memory.
+*     init
+*        A logical flag indicating if the CmpRegion's virtual function table
+*        is to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new CmpRegion.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     region1
+*        Pointer to the first Region.
+*     region2
+*        Pointer to the second Region.
+*     oper
+*        The boolean operator to use. Must be one of AST__AND, AST__OR or
+*        AST__XOR.
+
+*  Returned Value:
+*     A pointer to the new CmpRegion.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstCmpRegion *new;            /* Pointer to new CmpRegion */
+   AstFrame *frm;                /* Frame encapsulated by first Region */
+   AstFrameSet *fs;              /* FrameSet connecting supplied Regions */
+   AstMapping *map;              /* Mapping between two supplied Regions */
+   AstMapping *smap;             /* Simplified Mapping between two supplied Regions */
+   AstRegion *new_reg1;          /* Replacement for first region */
+   AstRegion *new_reg2;          /* Replacement for second region */
+   AstRegion *reg1;              /* First Region to store in the CmpRegion */
+   AstRegion *reg2;              /* Second Region to store in the CmpRegion */
+   AstRegion *xor1;              /* Copy of first supplied Region or NULL */
+   AstRegion *xor2;              /* Copy of second supplied Region or NULL */
+   int i;                        /* Loop count */
+   int used_oper;                /* The boolean operation actually used */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitCmpRegionVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the supplied oper value. */
+   if( oper != AST__AND && oper != AST__OR && oper != AST__XOR && astOK ) {
+      astError( AST__INTRD, "astInitCmpRegion(%s): Illegal "
+                "boolean operator value (%d) supplied.", status, name, oper );
+   }
+
+/* Take copies of the supplied Regions. */
+   reg1 = astCopy( region1 );
+   reg2 = astCopy( region2 );
+
+/* Get the Mapping from the second to the first Region. */
+   fs = astConvert( reg2, reg1, "" );
+
+/* Report an error if not possible. */
+   if( fs == NULL ) {
+      frm = NULL;
+      if( astOK ) astError( AST__INTRD, "astInitCmpRegion(%s): No Mapping can "
+                            "be found between the two supplied Regions.", status, name );
+
+/* Otherwise, map the second Region into the Frame of the first (unless
+   they are already in the same Frame). This results in both component
+   Frames having the same current Frame. This current Frame is used as the
+   encapsulated Frame within the parent Region structure. */
+   } else {
+      frm = astGetFrame( fs, AST__CURRENT );
+      map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+      smap = astSimplify( map );
+      if( !astIsAUnitMap( smap ) ) {
+         new_reg2 = astMapRegion( reg2, smap, frm );
+         (void) astAnnul( reg2 );
+         reg2 = new_reg2;
+      }
+      smap = astAnnul( smap );
+      map = astAnnul( map );
+      fs = astAnnul( fs );
+   }
+
+/* The CmpRegion class does not implement XOR directly (as it does for
+   AND and OR). Instead, when requested to create an XOR CmpRegion, it
+   creates a CmpRegion that uses AND and OR to simulate XOR. The top
+   level XOR CmpRegion actually uses AST__OR and the two component
+   regions within it are CmpRegions formed by combing the two supplied
+   Regions (one being negated first) using AND. Create the required
+   component Regions. */
+   if( oper == AST__XOR ) {
+      astNegate( reg1 );
+      new_reg1 = (AstRegion *) astCmpRegion( reg1, reg2, AST__AND, " ",
+                                             status );
+      astNegate( reg1 );
+
+      astNegate( reg2 );
+      new_reg2 = (AstRegion *) astCmpRegion( reg1, reg2, AST__AND, " ",
+                                             status );
+      astNegate( reg2 );
+
+      xor1 = reg1;
+      xor2 = reg2;
+
+      reg1 = new_reg1;
+      reg2 = new_reg2;
+
+      used_oper = AST__OR;
+
+/* For AND and OR, use the supplied operator. */
+   } else {
+      xor1 = NULL;
+      xor2 = NULL;
+      used_oper = oper;
+   }
+
+/* Initialise a Region structure (the parent class) as the first component
+   within the CmpRegion structure, allocating memory if necessary. A NULL
+   PointSet is suppled as the two component Regions will perform the function
+   of defining the Region shape. The base Frame of the FrameSet in the
+   parent Region structure will be the same as the current Frames of the
+   FrameSets in the two component Regions. */
+   if ( astOK ) {
+      new = (AstCmpRegion *) astInitRegion( mem, size, 0,
+                                          (AstRegionVtab *) vtab, name,
+                                          frm, NULL, NULL );
+
+/* Initialise the CmpRegion data. */
+/* --------------------------- */
+/* Store pointers to the component Regions. */
+      new->region1 = astClone( reg1 );
+      new->region2 = astClone( reg2 );
+
+/* Note the operator used to combine the somponent Regions. */
+      new->oper = used_oper;
+
+/* If we are creating an XOR CmpRegion, save copies of the supplied
+   Regions (i.e. the supplied Regions which are XORed). These will not
+   be the same as "reg1" and "reg2" since each of those two regions will
+   be CmpRegions that combine the supplied Regions using AST__AND. */
+      if( oper == AST__XOR ) {
+         new->xor1 = xor1;
+         new->xor2 = xor2;
+      } else {
+         new->xor1 = NULL;
+         new->xor2 = NULL;
+      }
+
+/* Initialised cached values to show they have not yet been found. */
+      for( i = 0; i < 2; i++ ) {
+         new->rvals[ i ] = NULL;
+         new->offs[ i ] = NULL;
+         new->nbreak[ i ] = 0;
+         new->d0[ i ] = AST__BAD;
+         new->dtot[ i ] = AST__BAD;
+      }
+      new->bounded = -INT_MAX;
+
+/* If the base->current Mapping in the FrameSet within each component Region
+   is a UnitMap, then the FrameSet does not need to be included in the
+   Dump of the new CmpRegion. Set the RegionFS attribute of the component
+   Region to zero to flag this. */
+      map = astGetMapping( reg1->frameset, AST__BASE, AST__CURRENT );
+      if( astIsAUnitMap( map ) ) astSetRegionFS( reg1, 0 );
+      map = astAnnul( map );
+
+      map = astGetMapping( reg2->frameset, AST__BASE, AST__CURRENT );
+      if( astIsAUnitMap( map ) ) astSetRegionFS( reg2, 0 );
+      map = astAnnul( map );
+
+/* Copy attribute values from the first component Region to the parent
+   Region. */
+      if( astTestMeshSize( new->region1 ) ) {
+         astSetMeshSize( new,  astGetMeshSize( new->region1 ) );
+      }
+      if( astTestClosed( new->region1 ) ) {
+         astSetClosed( new,  astGetClosed( new->region1 ) );
+      }
+
+/* If an error occurred, clean up by annulling the Region pointers and
+   deleting the new object. */
+      if ( !astOK ) {
+         new->region1 = astAnnul( new->region1 );
+         new->region2 = astAnnul( new->region2 );
+         new = astDelete( new );
+      }
+   }
+
+/* Free resources */
+   reg1 = astAnnul( reg1 );
+   reg2 = astAnnul( reg2 );
+   if( frm ) frm = astAnnul( frm );
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstCmpRegion *astLoadCmpRegion_( void *mem, size_t size,
+                                 AstCmpRegionVtab *vtab, const char *name,
+                                 AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadCmpRegion
+
+*  Purpose:
+*     Load a CmpRegion.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     AstCmpRegion *astLoadCmpRegion( void *mem, size_t size,
+*                                     AstCmpRegionVtab *vtab, const char *name,
+*                                     AstChannel *channel )
+
+*  Class Membership:
+*     CmpRegion loader.
+
+*  Description:
+*     This function is provided to load a new CmpRegion using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     CmpRegion structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a CmpRegion at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the CmpRegion is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        CmpRegion data (sizeof(CmpRegion)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the CmpRegion (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the CmpRegion structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstCmpRegion) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new CmpRegion. If this is NULL, a pointer to
+*        the (static) virtual function table for the CmpRegion class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "CmpRegion" is used instead.
+
+*  Returned Value:
+*     A pointer to the new CmpRegion.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstCmpRegion *new;            /* Pointer to the new CmpRegion */
+   AstRegion *reg1;              /* First Region read from dump */
+   AstRegion *reg2;              /* Second Region read from dump */
+   AstFrame *f1;                 /* Base Frame in parent Region */
+   AstRegion *creg;              /* Pointer to component Region */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   int i;                        /* Loop count */
+   int oper;                     /* The operator to include in the dump */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this CmpRegion. In this case the
+   CmpRegion belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstCmpRegion );
+      vtab = &class_vtab;
+      name = "CmpRegion";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitCmpRegionVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built CmpRegion. */
+   new = astLoadRegion( mem, size, (AstRegionVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "CmpRegion" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Operator */
+/* -------- */
+      oper = astReadInt( channel, "operator", AST__AND );
+
+/* First Region. */
+/* -------------- */
+      reg1 = astReadObject( channel, "regiona", NULL );
+
+/* Second Region. */
+/* --------------- */
+      reg2 = astReadObject( channel, "regionb", NULL );
+
+/* Initialised cached values to show they have not yet been found. */
+      for( i = 0; i < 2; i++ ) {
+         new->rvals[ i ] = NULL;
+         new->offs[ i ] = NULL;
+         new->nbreak[ i ] = 0;
+         new->d0[ i ] = AST__BAD;
+         new->dtot[ i ] = AST__BAD;
+      }
+      new->bounded = -INT_MAX;
+
+/* The CmpRegion class does not implement XOR directly (as it does for
+   AND and OR). Instead, when requested to create an XOR CmpRegion, it
+   creates a CmpRegion that uses AND and OR to simulate XOR. The top
+   level XOR CmpRegion actually uses AST__OR and the two component
+   regions within it are CmpRegions formed by combing the two supplied
+   Regions (one being negated first) using AND. Create the required
+   component Regions. */
+      if( oper == AST__XOR ) {
+         astNegate( reg1 );
+         new->region1 = (AstRegion *) astCmpRegion( reg1, reg2, AST__AND,
+                                                    " ", status );
+         astNegate( reg1 );
+
+         astNegate( reg2 );
+         new->region2 = (AstRegion *) astCmpRegion( reg1, reg2, AST__AND,
+                                                    " ", status );
+         astNegate( reg2 );
+
+         new->xor1 = reg1;
+         new->xor2 = reg2;
+
+         new->oper = AST__OR;
+
+/* For AND and OR, use the supplied Regions and operator. */
+      } else {
+         new->region1 = reg1;
+         new->region2 = reg2;
+         new->xor1 = NULL;
+         new->xor2 = NULL;
+         new->oper = oper;
+      }
+
+/* If either component Region has a dummy FrameSet rather than the correct
+   FrameSet, the correct FrameSet will have copies of the base Frame of the
+   new CmpRegion as both its current and base Frames, connected by a UnitMap
+   (this is equivalent to a FrameSet containing a single Frame). However if
+   the new CmpRegion being loaded has itself got a dummy FrameSet, then we do
+   not do this since we do not yet know what the correct FrameSet is. In this
+   case we wait until the parent Region invokes the astSetRegFS method on the
+   new CmpRegion. */
+      if( !astRegDummyFS( new ) ) {
+         f1 = astGetFrame( ((AstRegion *) new)->frameset, AST__BASE );
+         creg = new->region1;
+         if( astRegDummyFS( creg ) ) astSetRegFS( creg, f1 );
+         creg = new->region2;
+         if( astRegDummyFS( creg ) ) astSetRegFS( creg, f1 );
+         f1 = astAnnul( f1 );
+      }
+
+/* If an error occurred, clean up by deleting the new CmpRegion. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new CmpRegion pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+int astCmpRegionList_( AstCmpRegion *this, int *nreg, AstRegion ***reg_list,
+                       int *status ) {
+   if ( !astOK ) return AST__AND;
+   return (**astMEMBER(this,CmpRegion,CmpRegionList))( this, nreg, reg_list,
+                                                       status );
+}
+
+
+
+
+
+
diff --git a/ast/cmpregion.h b/ast/cmpregion.h
new file mode 100644
index 0000000..92d7898
--- /dev/null
+++ b/ast/cmpregion.h
@@ -0,0 +1,251 @@
+#if !defined( CMPREGION_INCLUDED ) /* Include this file only once */
+#define CMPREGION_INCLUDED
+/*
+*+
+*  Name:
+*     cmpregion.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the CmpRegion class.
+
+*  Invocation:
+*     #include "cmpregion.h"
+
+*  Description:
+*     This include file defines the interface to the CmpRegion class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The CmpRegion class implement a Region which represents a simple interval
+*     on each axis of the encapsulated Frame
+
+*  Inheritance:
+*     The CmpRegion class inherits from the Region class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     11-OCT-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "region.h"              /* Coordinate regions (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros. */
+/* ------- */
+/* Boolean operators */
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+#define AST__AND 1
+#define AST__OR 2
+#define AST__XOR 3
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* CmpRegion structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstCmpRegion {
+
+/* Attributes inherited from the parent class. */
+   AstRegion region;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstRegion *region1;           /* First component Region */
+   AstRegion *region2;           /* Second component Region */
+   int oper;                     /* Boolean operator */
+   double *rvals[ 2 ];           /* Used boundary length at each break */
+   double *offs[ 2 ];            /* Jump at each break */
+   int nbreak[ 2 ];              /* Number of breaks */
+   double d0[ 2 ];               /* Total used boundary length */
+   double dtot[ 2 ];             /* Total boundary length */
+   AstRegion *xor1;              /* First XORed Region */
+   AstRegion *xor2;              /* Second XORed Region */
+   int bounded;                  /* Is this CmpRegion bounded? */
+} AstCmpRegion;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstCmpRegionVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstRegionVtab region_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   int (* CmpRegionList)(  AstCmpRegion *, int *, AstRegion ***, int * );
+
+} AstCmpRegionVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstCmpRegionGlobals {
+   AstCmpRegionVtab Class_Vtab;
+   int Class_Init;
+} AstCmpRegionGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitCmpRegionGlobals_( AstCmpRegionGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(CmpRegion)          /* Check class membership */
+astPROTO_ISA(CmpRegion)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstCmpRegion *astCmpRegion_( void *, void *, int, const char *, int *, ...);
+#else
+AstCmpRegion *astCmpRegionId_( void *, void *, int, const char *, ... )__attribute__((format(printf,4,5)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstCmpRegion *astInitCmpRegion_( void *, size_t, int, AstCmpRegionVtab *,
+                     const char *, AstRegion *, AstRegion *, int, int * );
+
+/* Vtab initialiser. */
+void astInitCmpRegionVtab_( AstCmpRegionVtab *, const char *, int * );
+
+/* Loader. */
+AstCmpRegion *astLoadCmpRegion_( void *, size_t, AstCmpRegionVtab *,
+                                 const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+int astCmpRegionList_(  AstCmpRegion *, int *, AstRegion ***, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckCmpRegion(this) astINVOKE_CHECK(CmpRegion,this,0)
+#define astVerifyCmpRegion(this) astINVOKE_CHECK(CmpRegion,this,1)
+
+/* Test class membership. */
+#define astIsACmpRegion(this) astINVOKE_ISA(CmpRegion,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astCmpRegion astINVOKE(F,astCmpRegion_)
+#else
+#define astCmpRegion astINVOKE(F,astCmpRegionId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitCmpRegion(mem,size,init,vtab,name,reg1,reg2,oper) \
+astINVOKE(O,astInitCmpRegion_(mem,size,init,vtab,name,reg1,reg2,oper,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitCmpRegionVtab(vtab,name) astINVOKE(V,astInitCmpRegionVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadCmpRegion(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadCmpRegion_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckCmpRegion to validate CmpRegion pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astCmpRegionList(this,nreg,reg_list) \
+astINVOKE(V,astCmpRegionList_(this,nreg,reg_list,STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/component.xml.in b/ast/component.xml.in
new file mode 100644
index 0000000..d59bb5a
--- /dev/null
+++ b/ast/component.xml.in
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<!DOCTYPE component SYSTEM "componentinfo.dtd">
+
+<component id="@PACKAGE@" support="S">
+  <version>@PACKAGE_VERSION@</version>
+  <path>libext/ast</path>
+  <description>WCS library</description>
+  <abstract>
+    <p>The AST library provides a comprehensive range of facilities for
+      attaching world coordinate systems to astronomical data, for
+      retrieving and interpreting that information in a variety of formats,
+      including FITS-WCS, and for generating graphical output based on it.</p>
+    <p>This library should be of interest to anyone writing
+      astronomical applications which need to manipulate coordinate system
+      data, especially celestial or spectral coordinate systems. AST
+      is portable and environment-independent.</p>
+  </abstract>
+  <dependencies @STAR_DEPENDENCIES_ATTRIBUTES@>
+    @STAR_DEPENDENCIES_CHILDREN@
+  </dependencies>
+  <developers>
+    <person>
+      <name>Rodney Warren-Smith</name>
+      <uname>rfws</uname>
+    </person>
+    <person>
+      <name>David Berry</name>
+      <uname>dsb</uname>
+      <email>[email-masked]</email>
+      <role>owner</role>
+    </person>
+    <person>
+      <name>Norman Gray</name>
+      <uname>nxg</uname>
+      <email>[email-masked]</email>
+    </person>
+  </developers>
+  <documentation>@STAR_DOCUMENTATION@</documentation>
+  <bugreports>@PACKAGE_BUGREPORT@</bugreports>
+  <copyright>
+    <year></year>
+    <name>Council for the Central Laboratory of the Research Councils</name>
+  </copyright>
+</component>
diff --git a/ast/config.h.in b/ast/config.h.in
new file mode 100644
index 0000000..5e1b304
--- /dev/null
+++ b/ast/config.h.in
@@ -0,0 +1,136 @@
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* use external PAL and ERFA libraries */
+#undef EXTERNAL_PAL
+
+/* Define to 1 if you have the `backtrace' function. */
+#undef HAVE_BACKTRACE
+
+/* Define to 1 if you have the declaration of `isfinite', and to 0 if you
+   don't. */
+#undef HAVE_DECL_ISFINITE
+
+/* Define to 1 if you have the declaration of `isnan', and to 0 if you don't.
+   */
+#undef HAVE_DECL_ISNAN
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define to 1 if you have the <execinfo.h> header file. */
+#undef HAVE_EXECINFO_H
+
+/* Define to 1 if the system has the type `int64_t'. */
+#undef HAVE_INT64_T
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the `isfinite' function. */
+#undef HAVE_ISFINITE
+
+/* Define to 1 if you have the `isnan' function. */
+#undef HAVE_ISNAN
+
+/* Define to 1 if you have the `pthread' library (-lpthread). */
+#undef HAVE_LIBPTHREAD
+
+/* Define to 1 if the system has the type `long double'. */
+#undef HAVE_LONG_DOUBLE
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* The sscanf shows the non-ANSI behaviour reported by Bill Joye */
+#undef HAVE_NONANSI_SSCANF
+
+/* Define to 1 if the Fortran compiler supports the VAX %LOC extension */
+#undef HAVE_PERCENTLOC
+
+/* Use the starmem library for memory management */
+#undef HAVE_STAR_MEM_H
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#undef HAVE_STDARG_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the `strerror_r' function. */
+#undef HAVE_STRERROR_R
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the `strtok_r' function. */
+#undef HAVE_STRTOK_R
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if the system has the type `uint64_t'. */
+#undef HAVE_UINT64_T
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to 1 if you have the <varargs.h> header file. */
+#undef HAVE_VARARGS_H
+
+/* Define to 1 if you have the `vsnprintf' function. */
+#undef HAVE_VSNPRINTF
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+   */
+#undef LT_OBJDIR
+
+/* enable AST memory leak debugging functions in memory.c */
+#undef MEM_DEBUG
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* The size of `long', as computed by sizeof. */
+#undef SIZEOF_LONG
+
+/* The size of `long long', as computed by sizeof. */
+#undef SIZEOF_LONG_LONG
+
+/* The size of `void*', as computed by sizeof. */
+#undef SIZEOF_VOIDP
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Type of Fortran CNF TRAIL argument */
+#undef TRAIL_TYPE
+
+/* Version number of package */
+#undef VERSION
diff --git a/ast/configure b/ast/configure
new file mode 100755
index 0000000..988d531
--- /dev/null
+++ b/ast/configure
@@ -0,0 +1,19453 @@
+#! /bin/sh
+# From configure.ac Revision.
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by Starlink Autoconf 2.69 for ast 8.4.0.
+#
+# Report bugs to <[email-masked]>.
+#
+#
+# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+	expr "X$arg" : "X\\(.*\\)$as_nl";
+	arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""	$as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+# Use a proper internal environment variable to ensure we don't fall
+  # into an infinite loop, continuously re-executing ourselves.
+  if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
+    _as_can_reexec=no; export _as_can_reexec;
+    # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+  *v*x* | *x*v* ) as_opts=-vx ;;
+  *v* ) as_opts=-v ;;
+  *x* ) as_opts=-x ;;
+  * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+as_fn_exit 255
+  fi
+  # We don't want this to propagate to other subprocesses.
+          { _as_can_reexec=; unset _as_can_reexec;}
+if test "x$CONFIG_SHELL" = x; then
+  as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '\${1+\"\$@\"}'='\"\$@\"'
+  setopt NO_GLOB_SUBST
+else
+  case \`(set -o) 2>/dev/null\` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+"
+  as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+
+else
+  exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1
+test -x / || exit 1"
+  as_suggested="  as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+  as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+  eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+  test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
+
+  test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || (
+    ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+    ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO
+    ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO
+    PATH=/empty FPATH=/empty; export PATH FPATH
+    test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\
+      || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1
+test \$(( 1 + 1 )) = 2 || exit 1"
+  if (eval "$as_required") 2>/dev/null; then :
+  as_have_required=yes
+else
+  as_have_required=no
+fi
+  if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  as_found=:
+  case $as_dir in #(
+	 /*)
+	   for as_base in sh bash ksh sh5; do
+	     # Try only shells that exist, to save several forks.
+	     as_shell=$as_dir/$as_base
+	     if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+		    { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  CONFIG_SHELL=$as_shell as_have_required=yes
+		   if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  break 2
+fi
+fi
+	   done;;
+       esac
+  as_found=false
+done
+$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+	      { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
+  CONFIG_SHELL=$SHELL as_have_required=yes
+fi; }
+IFS=$as_save_IFS
+
+
+      if test "x$CONFIG_SHELL" != x; then :
+  export CONFIG_SHELL
+             # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+  *v*x* | *x*v* ) as_opts=-vx ;;
+  *v* ) as_opts=-v ;;
+  *x* ) as_opts=-x ;;
+  * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
+fi
+
+    if test x$as_have_required = xno; then :
+  $as_echo "$0: This script requires a shell more modern than all"
+  $as_echo "$0: the shells that I found on your system."
+  if test x${ZSH_VERSION+set} = xset ; then
+    $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+    $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+  else
+    $as_echo "$0: Please tell [email-masked] about your system,
+$0: including any error possibly output before this
+$0: message. Then install a modern shell, or manually run
+$0: the script under such a shell if you do have one."
+  fi
+  exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_dir" : 'X\(//\)[^/]' \| \
+	 X"$as_dir" : 'X\(//\)$' \| \
+	 X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+	 X"$0" : 'X\(//\)$' \| \
+	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+  as_lineno_1=$LINENO as_lineno_1a=$LINENO
+  as_lineno_2=$LINENO as_lineno_2a=$LINENO
+  eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+  test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+  # Blame Lee E. McMahon (1931-1989) for sed's syntax.  :-)
+  sed -n '
+    p
+    /[$]LINENO/=
+  ' <$as_myself |
+    sed '
+      s/[$]LINENO.*/&-/
+      t lineno
+      b
+      :lineno
+      N
+      :loop
+      s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+      t loop
+      s/-\n.*//
+    ' >$as_me.lineno &&
+  chmod +x "$as_me.lineno" ||
+    { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+  # If we had to re-execute with $CONFIG_SHELL, we're ensured to have
+  # already done that, so ensure we don't try to do so again and fall
+  # in an infinite loop.  This has already happened in practice.
+  _as_can_reexec=no; export _as_can_reexec
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensitive to this).
+  . "./$as_me.lineno"
+  # Exit status is that of the last command.
+  exit
+}
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='	';;	# ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='	';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -pR'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -pR'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -pR'
+  fi
+else
+  as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME='ast'
+PACKAGE_TARNAME='ast'
+PACKAGE_VERSION='8.4.0'
+PACKAGE_STRING='ast 8.4.0'
+PACKAGE_BUGREPORT=[email-masked]'
+PACKAGE_URL=''
+
+ac_unique_file="ast_link.in"
+ac_default_prefix=/usr/local
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='am__EXEEXT_FALSE
+am__EXEEXT_TRUE
+LTLIBOBJS
+LIBOBJS
+PROLAT
+STAR2HTML
+STAR_LATEX_DOCUMENTATION
+LATEX2DVI
+MESSGEN
+TRAIL_TYPE
+REAL_FUNCTION_TYPE
+PERL
+FCLIBS
+ac_ct_FC
+FCFLAGS
+FC
+FORTRAN
+NOFORTRAN_FALSE
+NOFORTRAN_TRUE
+THREADS
+NOTHREADS_FALSE
+NOTHREADS_TRUE
+NOPIC_FALSE
+NOPIC_TRUE
+OTOOL64
+OTOOL
+LIPO
+NMEDIT
+DSYMUTIL
+MANIFEST_TOOL
+RANLIB
+ac_ct_AR
+AR
+DLLTOOL
+OBJDUMP
+LN_S
+NM
+ac_ct_DUMPBIN
+DUMPBIN
+LD
+FGREP
+EGREP
+GREP
+SED
+host_os
+host_vendor
+host_cpu
+host
+build_os
+build_vendor
+build_cpu
+build
+LIBTOOL
+CPP
+am__fastdepCC_FALSE
+am__fastdepCC_TRUE
+CCDEPMODE
+am__nodep
+AMDEPBACKSLASH
+AMDEP_FALSE
+AMDEP_TRUE
+am__quote
+am__include
+DEPDIR
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+EXTERNAL_PAL_FALSE
+EXTERNAL_PAL_TRUE
+LIBPAL
+EXTERNAL_PAL
+TAR
+PAX
+PREDIST
+STAR_SOURCE_ROOT_DIR
+GIT
+STAR_MANIFEST_DIR
+PACKAGE_VERSION_INTEGER
+PACKAGE_VERSION_RELEASE
+PACKAGE_VERSION_MINOR
+PACKAGE_VERSION_MAJOR
+STAR_DOCUMENTATION
+STAR_DEPENDENCIES_CHILDREN
+STAR_DEPENDENCIES_ATTRIBUTES
+starnewsdir
+starhelpdir
+starfacsdir
+starexamplesdir
+staretcdir
+stardocsdir
+STAR_LDFLAGS
+STAR_FFLAGS
+STAR_FCFLAGS
+STAR_CPPFLAGS
+STARLINK
+AM_BACKSLASH
+AM_DEFAULT_VERBOSITY
+AM_DEFAULT_V
+AM_V
+am__untar
+am__tar
+AMTAR
+am__leading_dot
+SET_MAKE
+AWK
+mkdir_p
+MKDIR_P
+INSTALL_STRIP_PROGRAM
+STRIP
+install_sh
+MAKEINFO
+AUTOHEADER
+AUTOMAKE
+AUTOCONF
+ACLOCAL
+VERSION
+PACKAGE
+CYGPATH_W
+am__isrc
+INSTALL_DATA
+INSTALL_SCRIPT
+INSTALL_PROGRAM
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+enable_silent_rules
+with_starlink
+with_stardocs
+with_starmem
+with_memdebug
+with_external_pal
+enable_dependency_tracking
+enable_shared
+enable_static
+with_pic
+enable_fast_install
+with_gnu_ld
+with_sysroot
+enable_libtool_lock
+with_pthreads
+with_fortran
+'
+      ac_precious_vars='build_alias
+host_alias
+target_alias
+STARLINK
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+CPP
+FC
+FCFLAGS
+MESSGEN
+STAR2HTML
+PROLAT'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval $ac_prev=\$ac_option
+    ac_prev=
+    continue
+  fi
+
+  case $ac_option in
+  *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+  *=)   ac_optarg= ;;
+  *)    ac_optarg=yes ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case $ac_dashdash$ac_option in
+  --)
+    ac_dashdash=yes ;;
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir=$ac_optarg ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build_alias ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build_alias=$ac_optarg ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file=$ac_optarg ;;
+
+  --config-cache | -C)
+    cache_file=config.cache ;;
+
+  -datadir | --datadir | --datadi | --datad)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=*)
+    datadir=$ac_optarg ;;
+
+  -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+  | --dataroo | --dataro | --datar)
+    ac_prev=datarootdir ;;
+  -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+  | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+    datarootdir=$ac_optarg ;;
+
+  -disable-* | --disable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=no ;;
+
+  -docdir | --docdir | --docdi | --doc | --do)
+    ac_prev=docdir ;;
+  -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+    docdir=$ac_optarg ;;
+
+  -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+    ac_prev=dvidir ;;
+  -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+    dvidir=$ac_optarg ;;
+
+  -enable-* | --enable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=\$ac_optarg ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix=$ac_optarg ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he | -h)
+    ac_init_help=long ;;
+  -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+    ac_init_help=recursive ;;
+  -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+    ac_init_help=short ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host_alias ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host_alias=$ac_optarg ;;
+
+  -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+    ac_prev=htmldir ;;
+  -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+  | --ht=*)
+    htmldir=$ac_optarg ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir=$ac_optarg ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir=$ac_optarg ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir=$ac_optarg ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir=$ac_optarg ;;
+
+  -localedir | --localedir | --localedi | --localed | --locale)
+    ac_prev=localedir ;;
+  -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+    localedir=$ac_optarg ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst | --locals)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+    localstatedir=$ac_optarg ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir=$ac_optarg ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c | -n)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir=$ac_optarg ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix=$ac_optarg ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix=$ac_optarg ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix=$ac_optarg ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name=$ac_optarg ;;
+
+  -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+    ac_prev=pdfdir ;;
+  -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+    pdfdir=$ac_optarg ;;
+
+  -psdir | --psdir | --psdi | --psd | --ps)
+    ac_prev=psdir ;;
+  -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+    psdir=$ac_optarg ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir=$ac_optarg ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir=$ac_optarg ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site=$ac_optarg ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir=$ac_optarg ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir=$ac_optarg ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target_alias ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target_alias=$ac_optarg ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers | -V)
+    ac_init_version=: ;;
+
+  -with-* | --with-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=\$ac_optarg ;;
+
+  -without-* | --without-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=no ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes=$ac_optarg ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries=$ac_optarg ;;
+
+  -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+    ;;
+
+  *=*)
+    ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+    # Reject names that are not valid shell variable names.
+    case $ac_envvar in #(
+      '' | [0-9]* | *[!_$as_cr_alnum]* )
+      as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+    esac
+    eval $ac_envvar=\$ac_optarg
+    export $ac_envvar ;;
+
+  *)
+    # FIXME: should be removed in autoconf 3.0.
+    $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+    expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+    : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+  as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+  case $enable_option_checking in
+    no) ;;
+    fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+    *)     $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+  esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in	exec_prefix prefix bindir sbindir libexecdir datarootdir \
+		datadir sysconfdir sharedstatedir localstatedir includedir \
+		oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+		libdir localedir mandir
+do
+  eval ac_val=\$$ac_var
+  # Remove trailing slashes.
+  case $ac_val in
+    */ )
+      ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+      eval $ac_var=\$ac_val;;
+  esac
+  # Be sure to have absolute directory names.
+  case $ac_val in
+    [\\/$]* | ?:[\\/]* )  continue;;
+    NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+  esac
+  as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+  if test "x$build_alias" = x; then
+    cross_compiling=maybe
+  elif test "x$build_alias" != "x$host_alias"; then
+    cross_compiling=yes
+  fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+  as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+  as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then the parent directory.
+  ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_myself" : 'X\(//\)[^/]' \| \
+	 X"$as_myself" : 'X\(//\)$' \| \
+	 X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_myself" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+  srcdir=$ac_confdir
+  if test ! -r "$srcdir/$ac_unique_file"; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+  test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+  as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+	cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+	pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+  srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+  eval ac_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_env_${ac_var}_value=\$${ac_var}
+  eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+  # Omit some internal or obsolete options to make the list less imposing.
+  # This message is too long to be a string in the A/UX 3.1 sh.
+  cat <<_ACEOF
+\`configure' configures ast 8.4.0 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+  -h, --help              display this help and exit
+      --help=short        display options specific to this package
+      --help=recursive    display the short help of all the included packages
+  -V, --version           display version information and exit
+  -q, --quiet, --silent   do not print \`checking ...' messages
+      --cache-file=FILE   cache test results in FILE [disabled]
+  -C, --config-cache      alias for \`--cache-file=config.cache'
+  -n, --no-create         do not create output files
+      --srcdir=DIR        find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                          [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc.  You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --oldincludedir=DIR     C header files for non-gcc [/usr/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --docdir=DIR            documentation root [DATAROOTDIR/doc/ast]
+  --htmldir=DIR           html documentation [DOCDIR]
+  --dvidir=DIR            dvi documentation [DOCDIR]
+  --pdfdir=DIR            pdf documentation [DOCDIR]
+  --psdir=DIR             ps documentation [DOCDIR]
+_ACEOF
+
+  cat <<\_ACEOF
+
+Program names:
+  --program-prefix=PREFIX            prepend PREFIX to installed program names
+  --program-suffix=SUFFIX            append SUFFIX to installed program names
+  --program-transform-name=PROGRAM   run sed PROGRAM on installed program names
+
+System types:
+  --build=BUILD     configure for building on BUILD [guessed]
+  --host=HOST       cross-compile to build programs to run on HOST [BUILD]
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+  case $ac_init_help in
+     short | recursive ) echo "Configuration of ast 8.4.0:";;
+   esac
+  cat <<\_ACEOF
+
+Optional Features:
+  --disable-option-checking  ignore unrecognized --enable/--with options
+  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
+  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
+  --enable-silent-rules   less verbose build output (undo: "make V=1")
+  --disable-silent-rules  verbose build output (undo: "make V=0")
+  --enable-dependency-tracking
+                          do not reject slow dependency extractors
+  --disable-dependency-tracking
+                          speeds up one-time build
+  --enable-shared[=PKGS]  build shared libraries [default=yes]
+  --enable-static[=PKGS]  build static libraries [default=yes]
+  --enable-fast-install[=PKGS]
+                          optimize for fast installation [default=yes]
+  --disable-libtool-lock  avoid locking (might break parallel builds)
+
+Optional Packages:
+  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
+  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
+  --with-starlink         Starlink tree to use (default
+                          ${STARLINK:=/stardev/git/starlink/star})
+  --without-stardocs      Do not install built documentation (default --with)
+  --with-starmem          use starmem library for memory management
+  --with-memdebug         enable AST memory leak debugging functions
+ --with-external_pal   Use external PAL and ERFA libraries
+  --with-pic[=PKGS]       try to use only PIC/non-PIC objects [default=use
+                          both]
+  --with-gnu-ld           assume the C compiler uses GNU ld [default=no]
+  --with-sysroot=DIR Search for dependent libraries within DIR
+                        (or the compiler's sysroot if not specified).
+ --without-pthreads   Build package without POSIX threads support
+ --without-fortran   Build package without Fortran support
+
+Some influential environment variables:
+  STARLINK    Location of a current Starlink tree (used if necessary)
+  CC          C compiler command
+  CFLAGS      C compiler flags
+  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
+              nonstandard directory <lib dir>
+  LIBS        libraries to pass to the linker, e.g. -l<library>
+  CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+              you have headers in a nonstandard directory <include dir>
+  CPP         C preprocessor
+  FC          Fortran compiler command
+  FCFLAGS     Fortran compiler flags
+  MESSGEN     Location of the messgen application
+  STAR2HTML   Location of the star2html application
+  PROLAT      Location of the prolat application
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <[email-masked]>.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+  # If there are subdirs, report their specific --help.
+  for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+    test -d "$ac_dir" ||
+      { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+      continue
+    ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+    cd "$ac_dir" || { ac_status=$?; continue; }
+    # Check for guested configure.
+    if test -f "$ac_srcdir/configure.gnu"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+    elif test -f "$ac_srcdir/configure"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure" --help=recursive
+    else
+      $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+    fi || ac_status=$?
+    cd "$ac_pwd" || { ac_status=$?; break; }
+  done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+  cat <<\_ACEOF
+ast configure 8.4.0
+generated by Starlink Autoconf 2.69
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+  exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext
+  if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } > conftest.i && {
+	 test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+    ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext conftest$ac_exeext
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext && {
+	 test "$cross_compiling" = yes ||
+	 test -x conftest$ac_exeext
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+  # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+  # interfere with the next link command; also delete a directory that is
+  # left behind by Apple's compiler.  We do this before executing the actions.
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+
+# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_c_check_header_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_compile
+
+# ac_fn_c_try_run LINENO
+# ----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_c_try_run ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: program exited with status $ac_status" >&5
+       $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=$ac_status
+fi
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_run
+
+# ac_fn_c_check_func LINENO FUNC VAR
+# ----------------------------------
+# Tests whether FUNC exists, setting the cache variable VAR accordingly
+ac_fn_c_check_func ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
+   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
+#define $2 innocuous_$2
+
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $2 (); below.
+    Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+    <limits.h> exists even on freestanding compilers.  */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $2
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $2 ();
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined __stub_$2 || defined __stub___$2
+choke me
+#endif
+
+int
+main ()
+{
+return $2 ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_func
+
+# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists, giving a warning if it cannot be compiled using
+# the include files in INCLUDES and setting the cache variable VAR
+# accordingly.
+ac_fn_c_check_header_mongrel ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if eval \${$3+:} false; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+else
+  # Is the header compilable?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
+$as_echo_n "checking $2 usability... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_header_compiler=yes
+else
+  ac_header_compiler=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
+$as_echo "$ac_header_compiler" >&6; }
+
+# Is the header present?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
+$as_echo_n "checking $2 presence... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <$2>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  ac_header_preproc=yes
+else
+  ac_header_preproc=no
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
+$as_echo "$ac_header_preproc" >&6; }
+
+# So?  What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #((
+  yes:no: )
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
+$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+    ;;
+  no:yes:* )
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
+$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     check for missing prerequisite headers?" >&5
+$as_echo "$as_me: WARNING: $2:     check for missing prerequisite headers?" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
+$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&5
+$as_echo "$as_me: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+( $as_echo "## -------------------------------------- ##
+## Report this to [email-masked] ##
+## -------------------------------------- ##"
+     ) | sed "s/^/$as_me: WARNING:     /" >&2
+    ;;
+esac
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  eval "$3=\$ac_header_compiler"
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_mongrel
+
+# ac_fn_c_check_type LINENO TYPE VAR INCLUDES
+# -------------------------------------------
+# Tests whether TYPE exists after having included INCLUDES, setting cache
+# variable VAR accordingly.
+ac_fn_c_check_type ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  eval "$3=no"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+if (sizeof ($2))
+	 return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+if (sizeof (($2)))
+	    return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  eval "$3=yes"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_type
+
+# ac_fn_c_compute_int LINENO EXPR VAR INCLUDES
+# --------------------------------------------
+# Tries to find the compile-time value of EXPR in a program that includes
+# INCLUDES, setting VAR accordingly. Returns whether the value could be
+# computed
+ac_fn_c_compute_int ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if test "$cross_compiling" = yes; then
+    # Depending upon the size, compute the lo and hi bounds.
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+static int test_array [1 - 2 * !(($2) >= 0)];
+test_array [0] = 0;
+return test_array [0];
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_lo=0 ac_mid=0
+  while :; do
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+static int test_array [1 - 2 * !(($2) <= $ac_mid)];
+test_array [0] = 0;
+return test_array [0];
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_hi=$ac_mid; break
+else
+  as_fn_arith $ac_mid + 1 && ac_lo=$as_val
+			if test $ac_lo -le $ac_mid; then
+			  ac_lo= ac_hi=
+			  break
+			fi
+			as_fn_arith 2 '*' $ac_mid + 1 && ac_mid=$as_val
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  done
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+static int test_array [1 - 2 * !(($2) < 0)];
+test_array [0] = 0;
+return test_array [0];
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_hi=-1 ac_mid=-1
+  while :; do
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+static int test_array [1 - 2 * !(($2) >= $ac_mid)];
+test_array [0] = 0;
+return test_array [0];
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_lo=$ac_mid; break
+else
+  as_fn_arith '(' $ac_mid ')' - 1 && ac_hi=$as_val
+			if test $ac_mid -le $ac_hi; then
+			  ac_lo= ac_hi=
+			  break
+			fi
+			as_fn_arith 2 '*' $ac_mid && ac_mid=$as_val
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  done
+else
+  ac_lo= ac_hi=
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+# Binary search between lo and hi bounds.
+while test "x$ac_lo" != "x$ac_hi"; do
+  as_fn_arith '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo && ac_mid=$as_val
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+static int test_array [1 - 2 * !(($2) <= $ac_mid)];
+test_array [0] = 0;
+return test_array [0];
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_hi=$ac_mid
+else
+  as_fn_arith '(' $ac_mid ')' + 1 && ac_lo=$as_val
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+done
+case $ac_lo in #((
+?*) eval "$3=\$ac_lo"; ac_retval=0 ;;
+'') ac_retval=1 ;;
+esac
+  else
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+static long int longval () { return $2; }
+static unsigned long int ulongval () { return $2; }
+#include <stdio.h>
+#include <stdlib.h>
+int
+main ()
+{
+
+  FILE *f = fopen ("conftest.val", "w");
+  if (! f)
+    return 1;
+  if (($2) < 0)
+    {
+      long int i = longval ();
+      if (i != ($2))
+	return 1;
+      fprintf (f, "%ld", i);
+    }
+  else
+    {
+      unsigned long int i = ulongval ();
+      if (i != ($2))
+	return 1;
+      fprintf (f, "%lu", i);
+    }
+  /* Do not output a trailing newline, as this causes \r\n confusion
+     on some platforms.  */
+  return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+  echo >>conftest.val; read $3 <conftest.val; ac_retval=0
+else
+  ac_retval=1
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+rm -f conftest.val
+
+  fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_compute_int
+
+# ac_fn_fc_try_compile LINENO
+# ---------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_fc_try_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext
+  if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_fc_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_fc_try_compile
+
+# ac_fn_fc_try_link LINENO
+# ------------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_fc_try_link ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext conftest$ac_exeext
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_fc_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext && {
+	 test "$cross_compiling" = yes ||
+	 test -x conftest$ac_exeext
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+  # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+  # interfere with the next link command; also delete a directory that is
+  # left behind by Apple's compiler.  We do this before executing the actions.
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_fc_try_link
+
+# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES
+# ---------------------------------------------
+# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR
+# accordingly.
+ac_fn_c_check_decl ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  as_decl_name=`echo $2|sed 's/ *(.*//'`
+  as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'`
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5
+$as_echo_n "checking whether $as_decl_name is declared... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+#ifndef $as_decl_name
+#ifdef __cplusplus
+  (void) $as_decl_use;
+#else
+  (void) $as_decl_name;
+#endif
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_decl
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by ast $as_me 8.4.0, which was
+generated by Starlink Autoconf 2.69.  Invocation command line was
+
+  $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null     || echo unknown`
+
+/bin/arch              = `(/bin/arch) 2>/dev/null              || echo unknown`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null       || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo      = `(/usr/bin/hostinfo) 2>/dev/null      || echo unknown`
+/bin/machine           = `(/bin/machine) 2>/dev/null           || echo unknown`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null       || echo unknown`
+/bin/universe          = `(/bin/universe) 2>/dev/null          || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    $as_echo "PATH: $as_dir"
+  done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+  for ac_arg
+  do
+    case $ac_arg in
+    -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+    -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+    | -silent | --silent | --silen | --sile | --sil)
+      continue ;;
+    *\'*)
+      ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    case $ac_pass in
+    1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+    2)
+      as_fn_append ac_configure_args1 " '$ac_arg'"
+      if test $ac_must_keep_next = true; then
+	ac_must_keep_next=false # Got value, back to normal.
+      else
+	case $ac_arg in
+	  *=* | --config-cache | -C | -disable-* | --disable-* \
+	  | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+	  | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+	  | -with-* | --with-* | -without-* | --without-* | --x)
+	    case "$ac_configure_args0 " in
+	      "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+	    esac
+	    ;;
+	  -* ) ac_must_keep_next=true ;;
+	esac
+      fi
+      as_fn_append ac_configure_args " '$ac_arg'"
+      ;;
+    esac
+  done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log.  We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+  # Save into config.log some information that might help in debugging.
+  {
+    echo
+
+    $as_echo "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+    echo
+    # The following way of writing the cache mishandles newlines in values,
+(
+  for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+  (set) 2>&1 |
+    case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      sed -n \
+	"s/'\''/'\''\\\\'\'''\''/g;
+	  s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+      ;; #(
+    *)
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+)
+    echo
+
+    $as_echo "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+    echo
+    for ac_var in $ac_subst_vars
+    do
+      eval ac_val=\$$ac_var
+      case $ac_val in
+      *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+      esac
+      $as_echo "$ac_var='\''$ac_val'\''"
+    done | sort
+    echo
+
+    if test -n "$ac_subst_files"; then
+      $as_echo "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+      echo
+      for ac_var in $ac_subst_files
+      do
+	eval ac_val=\$$ac_var
+	case $ac_val in
+	*\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+	esac
+	$as_echo "$ac_var='\''$ac_val'\''"
+      done | sort
+      echo
+    fi
+
+    if test -s confdefs.h; then
+      $as_echo "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+      echo
+      cat confdefs.h
+      echo
+    fi
+    test "$ac_signal" != 0 &&
+      $as_echo "$as_me: caught signal $ac_signal"
+    $as_echo "$as_me: exit $exit_status"
+  } >&5
+  rm -f core *.core core.conftest.* &&
+    rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+    exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+  trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+$as_echo "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_URL "$PACKAGE_URL"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+ac_site_file1=NONE
+ac_site_file2=NONE
+if test -n "$CONFIG_SITE"; then
+  # We do not want a PATH search for config.site.
+  case $CONFIG_SITE in #((
+    -*)  ac_site_file1=./$CONFIG_SITE;;
+    */*) ac_site_file1=$CONFIG_SITE;;
+    *)   ac_site_file1=./$CONFIG_SITE;;
+  esac
+elif test "x$prefix" != xNONE; then
+  ac_site_file1=$prefix/share/config.site
+  ac_site_file2=$prefix/etc/config.site
+else
+  ac_site_file1=$ac_default_prefix/share/config.site
+  ac_site_file2=$ac_default_prefix/etc/config.site
+fi
+for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+do
+  test "x$ac_site_file" = xNONE && continue
+  if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+    sed 's/^/| /' "$ac_site_file" >&5
+    . "$ac_site_file" \
+      || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5; }
+  fi
+done
+
+if test -r "$cache_file"; then
+  # Some versions of bash will fail to source /dev/null (special files
+  # actually), so we avoid doing that.  DJGPP emulates it as a regular file.
+  if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+$as_echo "$as_me: loading cache $cache_file" >&6;}
+    case $cache_file in
+      [\\/]* | ?:[\\/]* ) . "$cache_file";;
+      *)                      . "./$cache_file";;
+    esac
+  fi
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+$as_echo "$as_me: creating cache $cache_file" >&6;}
+  >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+  eval ac_old_set=\$ac_cv_env_${ac_var}_set
+  eval ac_new_set=\$ac_env_${ac_var}_set
+  eval ac_old_val=\$ac_cv_env_${ac_var}_value
+  eval ac_new_val=\$ac_env_${ac_var}_value
+  case $ac_old_set,$ac_new_set in
+    set,)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,set)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,);;
+    *)
+      if test "x$ac_old_val" != "x$ac_new_val"; then
+	# differences in whitespace do not lead to failure.
+	ac_old_val_w=`echo x $ac_old_val`
+	ac_new_val_w=`echo x $ac_new_val`
+	if test "$ac_old_val_w" != "$ac_new_val_w"; then
+	  { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+	  ac_cache_corrupted=:
+	else
+	  { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+	  eval $ac_var=\$ac_old_val
+	fi
+	{ $as_echo "$as_me:${as_lineno-$LINENO}:   former value:  \`$ac_old_val'" >&5
+$as_echo "$as_me:   former value:  \`$ac_old_val'" >&2;}
+	{ $as_echo "$as_me:${as_lineno-$LINENO}:   current value: \`$ac_new_val'" >&5
+$as_echo "$as_me:   current value: \`$ac_new_val'" >&2;}
+      fi;;
+  esac
+  # Pass precious variables to config.status.
+  if test "$ac_new_set" = set; then
+    case $ac_new_val in
+    *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *) ac_arg=$ac_var=$ac_new_val ;;
+    esac
+    case " $ac_configure_args " in
+      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
+      *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+    esac
+  fi
+done
+if $ac_cache_corrupted; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+  as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ac_aux_dir=
+for ac_dir in build-aux "$srcdir"/build-aux; do
+  if test -f "$ac_dir/install-sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install-sh -c"
+    break
+  elif test -f "$ac_dir/install.sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install.sh -c"
+    break
+  elif test -f "$ac_dir/shtool"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/shtool install -c"
+    break
+  fi
+done
+if test -z "$ac_aux_dir"; then
+  as_fn_error $? "cannot find install-sh, install.sh, or shtool in build-aux \"$srcdir\"/build-aux" "$LINENO" 5
+fi
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+ac_config_guess="$SHELL $ac_aux_dir/config.guess"  # Please don't use this var.
+ac_config_sub="$SHELL $ac_aux_dir/config.sub"  # Please don't use this var.
+ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
+
+
+
+
+am__api_version='1.14'
+
+# Find a good install program.  We prefer a C program (faster),
+# so one script is as good as another.  But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# OS/2's system install, which has a completely different semantic
+# ./install, which can be erroneously created by make from ./install.sh.
+# Reject install programs that cannot install multiple files.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
+$as_echo_n "checking for a BSD-compatible install... " >&6; }
+if test -z "$INSTALL"; then
+if ${ac_cv_path_install+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in #((
+  ./ | .// | /[cC]/* | \
+  /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+  ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \
+  /usr/ucb/* ) ;;
+  *)
+    # OSF1 and SCO ODT 3.0 have their own names for install.
+    # Don't use installbsd from OSF since it installs stuff as root
+    # by default.
+    for ac_prog in ginstall scoinst install; do
+      for ac_exec_ext in '' $ac_executable_extensions; do
+	if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then
+	  if test $ac_prog = install &&
+	    grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+	    # AIX install.  It has an incompatible calling convention.
+	    :
+	  elif test $ac_prog = install &&
+	    grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+	    # program-specific install script used by HP pwplus--don't use.
+	    :
+	  else
+	    rm -rf conftest.one conftest.two conftest.dir
+	    echo one > conftest.one
+	    echo two > conftest.two
+	    mkdir conftest.dir
+	    if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" &&
+	      test -s conftest.one && test -s conftest.two &&
+	      test -s conftest.dir/conftest.one &&
+	      test -s conftest.dir/conftest.two
+	    then
+	      ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+	      break 3
+	    fi
+	  fi
+	fi
+      done
+    done
+    ;;
+esac
+
+  done
+IFS=$as_save_IFS
+
+rm -rf conftest.one conftest.two conftest.dir
+
+fi
+  if test "${ac_cv_path_install+set}" = set; then
+    INSTALL=$ac_cv_path_install
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for INSTALL within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    INSTALL=$ac_install_sh
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
+$as_echo "$INSTALL" >&6; }
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5
+$as_echo_n "checking whether build environment is sane... " >&6; }
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name.  Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+  *[\\\"\#\$\&\'\`$am_lf]*)
+    as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;;
+esac
+case $srcdir in
+  *[\\\"\#\$\&\'\`$am_lf\ \	]*)
+    as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;;
+esac
+
+# Do 'set' in a subshell so we don't clobber the current shell's
+# arguments.  Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+   am_has_slept=no
+   for am_try in 1 2; do
+     echo "timestamp, slept: $am_has_slept" > conftest.file
+     set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+     if test "$*" = "X"; then
+	# -L didn't work.
+	set X `ls -t "$srcdir/configure" conftest.file`
+     fi
+     if test "$*" != "X $srcdir/configure conftest.file" \
+	&& test "$*" != "X conftest.file $srcdir/configure"; then
+
+	# If neither matched, then we have a broken ls.  This can happen
+	# if, for instance, CONFIG_SHELL is bash and it inherits a
+	# broken ls alias from the environment.  This has actually
+	# happened.  Such a system could not be considered "sane".
+	as_fn_error $? "ls -t appears to fail.  Make sure there is not a broken
+  alias in your environment" "$LINENO" 5
+     fi
+     if test "$2" = conftest.file || test $am_try -eq 2; then
+       break
+     fi
+     # Just in case.
+     sleep 1
+     am_has_slept=yes
+   done
+   test "$2" = conftest.file
+   )
+then
+   # Ok.
+   :
+else
+   as_fn_error $? "newly created file is older than distributed files!
+Check your system clock" "$LINENO" 5
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+# If we didn't sleep, we still need to ensure time stamps of config.status and
+# generated files are strictly newer.
+am_sleep_pid=
+if grep 'slept: no' conftest.file >/dev/null 2>&1; then
+  ( sleep 1 ) &
+  am_sleep_pid=$!
+fi
+
+rm -f conftest.file
+
+test "$program_prefix" != NONE &&
+  program_transform_name="s&^&$program_prefix&;$program_transform_name"
+# Use a double $ so make ignores it.
+test "$program_suffix" != NONE &&
+  program_transform_name="s&\$&$program_suffix&;$program_transform_name"
+# Double any \ or $.
+# By default was `s,x,x', remove it if useless.
+ac_script='s/[\\$]/&&/g;s/;s,x,x,$//'
+program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"`
+
+# expand $ac_aux_dir to an absolute path
+am_aux_dir=`cd $ac_aux_dir && pwd`
+
+if test x"${MISSING+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\	*)
+    MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
+  *)
+    MISSING="\${SHELL} $am_aux_dir/missing" ;;
+  esac
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --is-lightweight"; then
+  am_missing_run="$MISSING "
+else
+  am_missing_run=
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5
+$as_echo "$as_me: WARNING: 'missing' script is too old or missing" >&2;}
+fi
+
+if test x"${install_sh}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\	*)
+    install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+  *)
+    install_sh="\${SHELL} $am_aux_dir/install-sh"
+  esac
+fi
+
+# Installed binaries are usually stripped using 'strip' when the user
+# run "make install-strip".  However 'strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the 'STRIP' environment variable to overrule this program.
+if test "$cross_compiling" != no; then
+  if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$STRIP"; then
+  ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+$as_echo "$STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+  ac_ct_STRIP=$STRIP
+  # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_STRIP"; then
+  ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_STRIP="strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+$as_echo "$ac_ct_STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_STRIP" = x; then
+    STRIP=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    STRIP=$ac_ct_STRIP
+  fi
+else
+  STRIP="$ac_cv_prog_STRIP"
+fi
+
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5
+$as_echo_n "checking for a thread-safe mkdir -p... " >&6; }
+if test -z "$MKDIR_P"; then
+  if ${ac_cv_path_mkdir+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in mkdir gmkdir; do
+	 for ac_exec_ext in '' $ac_executable_extensions; do
+	   as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue
+	   case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #(
+	     'mkdir (GNU coreutils) '* | \
+	     'mkdir (coreutils) '* | \
+	     'mkdir (fileutils) '4.1*)
+	       ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext
+	       break 3;;
+	   esac
+	 done
+       done
+  done
+IFS=$as_save_IFS
+
+fi
+
+  test -d ./--version && rmdir ./--version
+  if test "${ac_cv_path_mkdir+set}" = set; then
+    MKDIR_P="$ac_cv_path_mkdir -p"
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for MKDIR_P within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    MKDIR_P="$ac_install_sh -d"
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5
+$as_echo "$MKDIR_P" >&6; }
+
+for ac_prog in gawk mawk nawk awk
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_AWK+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$AWK"; then
+  ac_cv_prog_AWK="$AWK" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_AWK="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+AWK=$ac_cv_prog_AWK
+if test -n "$AWK"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5
+$as_echo "$AWK" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$AWK" && break
+done
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
+$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
+set x ${MAKE-make}
+ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
+if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat >conftest.make <<\_ACEOF
+SHELL = /bin/sh
+all:
+	@echo '@@@%%%=$(MAKE)=@@@%%%'
+_ACEOF
+# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
+case `${MAKE-make} -f conftest.make 2>/dev/null` in
+  *@@@%%%=?*=@@@%%%*)
+    eval ac_cv_prog_make_${ac_make}_set=yes;;
+  *)
+    eval ac_cv_prog_make_${ac_make}_set=no;;
+esac
+rm -f conftest.make
+fi
+if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+  SET_MAKE=
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+  SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+  am__leading_dot=.
+else
+  am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+
+# Check whether --enable-silent-rules was given.
+if test "${enable_silent_rules+set}" = set; then :
+  enableval=$enable_silent_rules;
+fi
+
+case $enable_silent_rules in # (((
+  yes) AM_DEFAULT_VERBOSITY=0;;
+   no) AM_DEFAULT_VERBOSITY=1;;
+    *) AM_DEFAULT_VERBOSITY=1;;
+esac
+am_make=${MAKE-make}
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5
+$as_echo_n "checking whether $am_make supports nested variables... " >&6; }
+if ${am_cv_make_support_nested_variables+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if $as_echo 'TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+	@$(TRUE)
+.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then
+  am_cv_make_support_nested_variables=yes
+else
+  am_cv_make_support_nested_variables=no
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5
+$as_echo "$am_cv_make_support_nested_variables" >&6; }
+if test $am_cv_make_support_nested_variables = yes; then
+    AM_V='$(V)'
+  AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+  AM_V=$AM_DEFAULT_VERBOSITY
+  AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AM_BACKSLASH='\'
+
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+  # is not polluted with repeated "-I."
+  am__isrc=' -I$(srcdir)'
+  # test to see if srcdir already configured
+  if test -f $srcdir/config.status; then
+    as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5
+  fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+  if (cygpath --version) >/dev/null 2>/dev/null; then
+    CYGPATH_W='cygpath -w'
+  else
+    CYGPATH_W=echo
+  fi
+fi
+
+
+# Define the identity of the package.
+ PACKAGE='ast'
+ VERSION='8.4.0'
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE "$PACKAGE"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define VERSION "$VERSION"
+_ACEOF
+
+# Some tools Automake needs.
+
+ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"}
+
+
+AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"}
+
+
+AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"}
+
+
+AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"}
+
+
+MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
+
+# For better backward compatibility.  To be removed once Automake 1.9.x
+# dies out for good.  For more background, see:
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
+mkdir_p='$(MKDIR_P)'
+
+# We need awk for the "check" target.  The system "awk" is bad on
+# some platforms.
+# Always define AMTAR for backward compatibility.  Yes, it's still used
+# in the wild :-(  We should find a proper way to deprecate it ...
+AMTAR='$${TAR-tar}'
+
+
+# We'll loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar  pax cpio none'
+
+am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'
+
+
+
+
+
+
+# POSIX will say in a future version that running "rm -f" with no argument
+# is OK; and we want to be able to make that assumption in our Makefile
+# recipes.  So use an aggressive probe to check that the usage we want is
+# actually supported "in the wild" to an acceptable degree.
+# See automake bug#10828.
+# To make any issue more visible, cause the running configure to be aborted
+# by default if the 'rm' program in use doesn't match our expectations; the
+# user can still override this though.
+if rm -f && rm -fr && rm -rf; then : OK; else
+  cat >&2 <<'END'
+Oops!
+
+Your 'rm' program seems unable to run without file operands specified
+on the command line, even when the '-f' option is present.  This is contrary
+to the behaviour of most rm programs out there, and not conforming with
+the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542>
+
+Please tell [email-masked] about your system, including the value
+of your $PATH and any error possibly output before this message.  This
+can help us improve future automake versions.
+
+END
+  if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then
+    echo 'Configuration will proceed anyway, since you have set the' >&2
+    echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2
+    echo >&2
+  else
+    cat >&2 <<'END'
+Aborting the configuration process, to ensure you take notice of the issue.
+
+You can download and install GNU coreutils to get an 'rm' implementation
+that behaves properly: <http://www.gnu.org/software/coreutils/>.
+
+If you want to complete the configuration process using your problematic
+'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
+to "yes", and re-run configure.
+
+END
+    as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5
+  fi
+fi
+
+
+
+#   Include defaults for Starlink configurations
+##
+
+
+
+
+
+test -n "$_star_per_package_dirs" || _star_per_package_dirs=false
+test -n "$_star_docs_only"        || _star_docs_only=false
+
+
+# Ensure that STARLINK has a value, defaulting to
+# /stardev/git/starlink/star.  Note that this directory may be
+# different from /star, and reflects the value of
+# STARCONF_DEFAULT_STARLINK that the `starconf' package was configured
+# with before its installation.
+#
+# We use $STARLINK as the location of any other Starlink tools we need
+# to use during the building of our packages, and for the location of
+# any manifests we need to check.  It is permissable for it to be
+# different from $(prefix): this is partly because we have no way of
+# enforcing that the two be the same, since the user can set
+# prefix=xxx on the `make install' command line, and partly so that it
+# is possible to make a test version of a new package, using tools
+# from an old installation, but installing in a new place.
+#
+# However, we install software in /stardev/git/starlink/star by
+# default.  This is so even if $STARLINK and STARCONF_DEFAULT_STARLINK
+# are different, because in this case we are planning to use a
+# previous installation in $STARLINK or $STARCONF_DEFAULT_STARLINK,
+# but install the newly built tool elsewhere.
+#
+# In most cases, including the most important case where we are
+# building the tree from scratch, in a checked out directory,
+# STARLINK, STARCONF_DEFAULT_STARLINK and STARCONF_DEFAULT_PREFIX will
+# all be the same.  That's OK because a separate aspect of the build
+# process, respecting the various dependencies expresses in source
+# directories, ensures that we don't use (and install) any Starlink
+# tools in one component before that component has been build and
+# installed.
+
+
+test -n "$STARLINK" || STARLINK=/stardev/git/starlink/star
+
+# Handle the --with-starlink option.  If --with-starlink is present
+# with no argument (the default), we do nothing as this simply
+# indicates that this is part of a Starlink tree.  If it has an
+# argument, then this overrides the location of the Starlink tree.
+# Option --without-starlink or --with-starlink=no indicates that this
+# is being built _not_ as part of a Starlink build (that is, it's
+# being distributed as something other than a Starlink package).  In
+# this case, the variable STARLINK is unset.
+
+# Check whether --with-starlink was given.
+if test "${with_starlink+set}" = set; then :
+  withval=$with_starlink; if test -z "$withval" -o "$withval" = yes; then
+                 : nothing needs to be done
+             elif test "X$withval" = Xno; then
+                 unset STARLINK
+             elif test -d "$withval"; then
+                 STARLINK="$withval"
+             else
+                 { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: --with-starlink given nonexistent directory; ignored: using default $STARLINK instead" >&5
+$as_echo "$as_me: WARNING: --with-starlink given nonexistent directory; ignored: using default $STARLINK instead" >&2;}
+             fi
+fi
+
+if test -n "$STARLINK"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: Starlink tree located at $STARLINK" >&5
+$as_echo "$as_me: Starlink tree located at $STARLINK" >&6;}
+else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: Not being built as part of the Starlink tree" >&5
+$as_echo "$as_me: Not being built as part of the Starlink tree" >&6;}
+fi
+
+# Handle --without-stardocs.  Don't build and install documentation.
+# Default is --with-stardocs.
+_star_build_docs=:
+
+# Check whether --with-stardocs was given.
+if test "${with_stardocs+set}" = set; then :
+  withval=$with_stardocs; if test -z "$withval"; then
+                 _star_build_docs=: # default
+             elif test "X$withval" = Xno; then
+                 _star_build_docs=false
+             elif test "X$withval" = Xyes; then
+                 _star_build_docs=:
+             else
+                 { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: bad arg to --with-stardocs: using yes" >&5
+$as_echo "$as_me: WARNING: bad arg to --with-stardocs: using yes" >&2;}
+                 _star_build_docs=:
+             fi
+fi
+
+
+if $_star_docs_only; then
+    if $_star_build_docs; then
+        : OK
+    else
+        { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Building without documentation in a docs-only directory" >&5
+$as_echo "$as_me: WARNING: Building without documentation in a docs-only directory" >&2;}
+    fi
+fi
+
+# Everything depends on where /star is.  Declare STARLINK as a
+# `precious variable'.  Amongst other things, this will make
+# ./configure squeal if the package is re-configured with an
+# inconsistent value of this variable.
+
+# AC_SUBST the STARLINK variable.  Macro AC_ARG_VAR does this anyway,
+# but automake doesn't know that (in 1.6 at least): however any
+# variable that automake finds has been AC_SUBSTed, it includes in
+# Makefile.in, and we need that.
+
+
+# Use the above information: $STARLINK indicates a preexisting
+# Starlink tree.
+#
+# Avoid doing anything if $STARLINK was unset above.
+#
+# Add library search paths using STAR_LDFLAGS.  Do it this way, rather than
+# by defining LIBS (which is also a non-user variable): (a) these are
+# really options to the linker, rather than adjustments to the set of
+# libraries, so this makes sense; also (b) adding them to LIBS is too
+# late, since that adds -L _after_ any -l options found in *_LDADD.
+if test -n "$STARLINK"; then
+    STAR_CPPFLAGS="-I$STARLINK/include"
+    STAR_FCFLAGS="-I$STARLINK/include"
+    STAR_FFLAGS="-I$STARLINK/include"
+    STAR_LDFLAGS="-L$STARLINK/lib"
+else
+    STAR_CPPFLAGS=
+    STAR_FCFLAGS=
+    STAR_FFLAGS=
+    STAR_LDFLAGS=
+fi
+
+
+
+
+
+# Installation directory options (these are no longer handled
+# by _STAR_EXTRADIR_COMMON).  There should be an entry here for each of
+# Starlink's special installation locations.
+stardocsdir='${prefix}/docs'
+staretcdir='${prefix}/etc'
+starexamplesdir='${prefix}/examples'
+starfacsdir='${prefix}/help'
+starhelpdir='${prefix}/help'
+starnewsdir='${prefix}/news'
+
+# Certain directories are affected by the $_star_per_package_dir variable;
+# if it's true, then add the $PACKAGE_NAME to the directory.
+# The directories currently adjusted by this are bin and help;
+# there are others: see PWD's message of 2004-02-16
+# <http://www.jiscmail.ac.uk/cgi-bin/wa.exe?A2=ind0402&L=stardev&T=0&F=&S=&P=5153>
+if $_star_per_package_dirs; then
+    bindir="$bindir/$PACKAGE_NAME"
+    starhelpdir="$starhelpdir/$PACKAGE_NAME"
+    staretcdir="$staretcdir/$PACKAGE_NAME"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: STAR_DEFAULTS has option per-package-dirs:" >&5
+$as_echo "$as_me: STAR_DEFAULTS has option per-package-dirs:" >&6;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}:     bindir=$bindir starhelpdir=$starhelpdir staretcdir=$staretcdir" >&5
+$as_echo "$as_me:     bindir=$bindir starhelpdir=$starhelpdir staretcdir=$staretcdir" >&6;}
+    # Note that starfacsdir is unaffected by per-package-dirs -- facility
+    # files must always be installed in .../help (this also facilitates
+    # changing this installation location in future, to somewhere with a
+    # more logical name than .../help).
+fi
+
+
+# Dependency declarations and checks.
+# Everything is dependent on starconf, so we don't have to declare that
+# for each package separately.
+# STAR_DEPENDENCIES_ATTRIBUTES is currently not used.
+STAR_DEPENDENCIES_ATTRIBUTES=''
+STAR_DEPENDENCIES_CHILDREN=''
+
+
+
+# List of documentation.  See [STAR_LATEX_DOCUMENTATION].
+# STAR_DOCUMENTATION is a list of document codes,
+STAR_DOCUMENTATION=
+
+
+# Create a PACKAGE_VERSION_INTEGER variable, which contains the
+# package's version number as an integer major*1e6+minor*1e3+release.
+eval `echo $VERSION | sed -e 's/\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*/PACKAGE_VERSION_MAJOR=\1; PACKAGE_VERSION_MINOR=\2; PACKAGE_VERSION_RELEASE=\3;/'`
+test -n "$PACKAGE_VERSION_MAJOR"   || PACKAGE_VERSION_MAJOR=0
+test -n "$PACKAGE_VERSION_MINOR"   || PACKAGE_VERSION_MINOR=0
+test -n "$PACKAGE_VERSION_RELEASE" || PACKAGE_VERSION_RELEASE=0
+PACKAGE_VERSION_INTEGER=`expr $PACKAGE_VERSION_MAJOR \* 1000000 + $PACKAGE_VERSION_MINOR \* 1000 + $PACKAGE_VERSION_RELEASE`
+
+
+
+
+
+# When we do dependency checking, using the dependencies declared in
+# the package's configure.ac, we do so by looking at what tools have
+# already been installed in the Starlink tree.  The tree in question
+# is to be found under $STARLINK (see above), so we check that a
+# package is installed by checking that its manifest can be found in
+# $STARLINK/manifests.  We don't AC_SUBST this.
+current_MANIFESTS=$STARLINK/manifests
+
+# When we install manifests, however, they should go in the
+# installation directory.  Allow this to be defaulted from the environment.
+# In particular, if it is set to null in the environment, this will
+# suppress the installation of manifests.
+: ${STAR_MANIFEST_DIR='$(prefix)/manifests'}
+
+
+# Each package updates the "starlink.version" file installed into the
+# manifests directory. This tracks the last git sha1 checkin for
+# the current code state by running the git show on the HEAD.
+# Define GIT as the program to run, but allow it to be overridden
+# (most likely by ":" to avoid the overhead).
+# Also requires that STAR_SOURCE_ROOT_DIR is defined to locate the
+# head of the source tree.
+: ${GIT='git'}
+if test "${GIT}" = "git"; then
+   # Extract the first word of "git", so it can be a program name with args.
+set dummy git; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_GIT+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $GIT in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_GIT="$GIT" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_GIT="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+GIT=$ac_cv_path_GIT
+if test -n "$GIT"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GIT" >&5
+$as_echo "$GIT" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+
+
+: ${STAR_SOURCE_ROOT_DIR=''}
+
+
+# Although PACKAGE_VERSION is a default output variable, it isn't
+# added as a Makefile variable by default.  We need it below, however,
+# so add it now.
+
+
+# Initialise state of predist/postdist flags (see STAR_PREDIST_SOURCES).
+# The value of _star_predist_status must be inherited by any
+# ./configure run in a subdirectory, so that we there avoid the predist
+# test of starconf.status: in a pre-distribution state, this file must
+# be present in the component directory (where we are running
+# ./configure), but must not be present in any subdirectory.
+_star_predist_status=unknown
+PREDIST='#'  # safe default
+
+
+# pax and/or tar are used in some install targets.
+# Note: value-if-not-found should be blank, so this can be tested for.
+# Extract the first word of "pax", so it can be a program name with args.
+set dummy pax; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PAX+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PAX in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PAX="$PAX" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_PAX="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+PAX=$ac_cv_path_PAX
+if test -n "$PAX"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PAX" >&5
+$as_echo "$PAX" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+for ac_prog in gnutar tar
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_TAR+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $TAR in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_TAR="$TAR" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_TAR="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+TAR=$ac_cv_path_TAR
+if test -n "$TAR"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TAR" >&5
+$as_echo "$TAR" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$TAR" && break
+done
+
+
+ALL_TARGET=all-am-normal
+
+# Default $prefix.  This is done by the standard autoconf configure, but at
+# a slightly later stage than this.  Doing it here, as part of STAR_[]DEFAULTS
+# means that the defaulted value of $prefix can be used within the body of
+# the configure.ac, for example to pass it to a ./configure in a subdirectory.
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+
+#   See if the --with-starmem option has been provided. This sets the
+#   preprocesor macro HAVE_STAR_MEM_H.
+
+# Check whether --with-starmem was given.
+if test "${with_starmem+set}" = set; then :
+  withval=$with_starmem;
+$as_echo "#define HAVE_STAR_MEM_H 1" >>confdefs.h
+
+fi
+
+
+#   See if the --with-memdebug option has been provided. This sets the
+#   preprocesor macro MEM_DEBUG which enables facilities used to track
+#   down memory leaks, etc.
+
+# Check whether --with-memdebug was given.
+if test "${with_memdebug+set}" = set; then :
+  withval=$with_memdebug;
+$as_echo "#define MEM_DEBUG 1" >>confdefs.h
+
+fi
+
+
+#   See if the --with-external_pal option has been provided. This sets the
+#   preprocesor macro EXTERNAL_PAL which prevents use of the PAL & ERFA
+#   library functions that are included in the AST distribution. Suitable
+#   link options are used within ast_link(_adam) scripts to pull in libpal.
+
+# Check whether --with-external_pal was given.
+if test "${with_external_pal+set}" = set; then :
+  withval=$with_external_pal; if test "$withval" = "yes"; then
+           external_pal="1"
+        else
+           external_pal="0"
+        fi
+else
+  external_pal="0"
+fi
+
+EXTERNAL_PAL=$external_pal
+
+if test "$external_pal" = "1"; then
+   LIBPAL="-lpal"
+
+
+$as_echo "#define EXTERNAL_PAL 1" >>confdefs.h
+,
+else
+   LIBPAL=""
+
+fi
+ if test x$external_pal = x1; then
+  EXTERNAL_PAL_TRUE=
+  EXTERNAL_PAL_FALSE='#'
+else
+  EXTERNAL_PAL_TRUE='#'
+  EXTERNAL_PAL_FALSE=
+fi
+
+
+
+#   Checks for programs
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+          if test -n "$ac_tool_prefix"; then
+    # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  fi
+fi
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+       ac_prog_rejected=yes
+       continue
+     fi
+    ac_cv_prog_CC="cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# != 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+  fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  for ac_prog in cl.exe
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$CC" && break
+  done
+fi
+if test -z "$CC"; then
+  ac_ct_CC=$CC
+  for ac_prog in cl.exe
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CC" && break
+done
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+$as_echo_n "checking whether the C compiler works... " >&6; }
+ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+  esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link_default") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile.  We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+	;;
+    [ab].out )
+	# We found the default executable, but exeext='' is most
+	# certainly right.
+	break;;
+    *.* )
+	if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+	then :; else
+	   ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+	fi
+	# We set ac_cv_exeext here because the later test for it is not
+	# safe: cross compilers may not add the suffix if given an `-o'
+	# argument, so we may need to know it at that point already.
+	# Even if this section looks crufty: it has the advantage of
+	# actually working.
+	break;;
+    * )
+	break;;
+  esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+  ac_file=''
+fi
+if test -z "$ac_file"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+$as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+$as_echo_n "checking for C compiler default output file name... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+$as_echo "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+$as_echo_n "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+	  break;;
+    * ) break;;
+  esac
+done
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+$as_echo "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdio.h>
+int
+main ()
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+$as_echo_n "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+  { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+  if { ac_try='./conftest$ac_cv_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then
+    cross_compiling=no
+  else
+    if test "$cross_compiling" = maybe; then
+	cross_compiling=yes
+    else
+	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+    fi
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+$as_echo "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+$as_echo_n "checking for suffix of object files... " >&6; }
+if ${ac_cv_objext+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  for ac_file in conftest.o conftest.obj conftest.*; do
+  test -f "$ac_file" || continue;
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+       break;;
+  esac
+done
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+$as_echo "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if ${ac_cv_c_compiler_gnu+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if ${ac_cv_prog_cc_g+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_c_werror_flag=$ac_c_werror_flag
+   ac_c_werror_flag=yes
+   ac_cv_prog_cc_g=no
+   CFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+else
+  CFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  ac_c_werror_flag=$ac_save_c_werror_flag
+	 CFLAGS="-g"
+	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if ${ac_cv_prog_cc_c89+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdio.h>
+struct stat;
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not '\xHH' hex character constants.
+   These don't provoke an error unfortunately, instead are silently treated
+   as 'x'.  The following induces an error, until -std is added to get
+   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
+   array size at least.  It's necessary to write '\x00'==0 to get something
+   that's true only with -std.  */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+   inside strings and character constants.  */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
+  ;
+  return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+	-Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+  test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+  x)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+  xno)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_c89"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5
+$as_echo_n "checking whether $CC understands -c and -o together... " >&6; }
+if ${am_cv_prog_cc_c_o+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+  # Make sure it works both with $CC and with simple cc.
+  # Following AC_PROG_CC_C_O, we do the test twice because some
+  # compilers refuse to overwrite an existing .o file with -o,
+  # though they will create one.
+  am_cv_prog_cc_c_o=yes
+  for am_i in 1 2; do
+    if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5
+   ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5
+   ac_status=$?
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   (exit $ac_status); } \
+         && test -f conftest2.$ac_objext; then
+      : OK
+    else
+      am_cv_prog_cc_c_o=no
+      break
+    fi
+  done
+  rm -f core conftest*
+  unset am_i
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5
+$as_echo "$am_cv_prog_cc_c_o" >&6; }
+if test "$am_cv_prog_cc_c_o" != yes; then
+   # Losing compiler, so override with the script.
+   # FIXME: It is wrong to rewrite CC.
+   # But if we don't then we get into trouble of one sort or another.
+   # A longer-term fix would be to have automake use am__CC in this case,
+   # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)"
+   CC="$am_aux_dir/compile $CC"
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+DEPDIR="${am__leading_dot}deps"
+
+ac_config_commands="$ac_config_commands depfiles"
+
+
+am_make=${MAKE-make}
+cat > confinc << 'END'
+am__doit:
+	@echo this is the am__doit target
+.PHONY: am__doit
+END
+# If we don't find an include directive, just comment out the code.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5
+$as_echo_n "checking for style of include used by $am_make... " >&6; }
+am__include="#"
+am__quote=
+_am_result=none
+# First try GNU make style include.
+echo "include confinc" > confmf
+# Ignore all kinds of additional output from 'make'.
+case `$am_make -s -f confmf 2> /dev/null` in #(
+*the\ am__doit\ target*)
+  am__include=include
+  am__quote=
+  _am_result=GNU
+  ;;
+esac
+# Now try BSD make style include.
+if test "$am__include" = "#"; then
+   echo '.include "confinc"' > confmf
+   case `$am_make -s -f confmf 2> /dev/null` in #(
+   *the\ am__doit\ target*)
+     am__include=.include
+     am__quote="\""
+     _am_result=BSD
+     ;;
+   esac
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5
+$as_echo "$_am_result" >&6; }
+rm -f confinc confmf
+
+# Check whether --enable-dependency-tracking was given.
+if test "${enable_dependency_tracking+set}" = set; then :
+  enableval=$enable_dependency_tracking;
+fi
+
+if test "x$enable_dependency_tracking" != xno; then
+  am_depcomp="$ac_aux_dir/depcomp"
+  AMDEPBACKSLASH='\'
+  am__nodep='_no'
+fi
+ if test "x$enable_dependency_tracking" != xno; then
+  AMDEP_TRUE=
+  AMDEP_FALSE='#'
+else
+  AMDEP_TRUE='#'
+  AMDEP_FALSE=
+fi
+
+
+
+depcc="$CC"   am_compiler_list=
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5
+$as_echo_n "checking dependency style of $depcc... " >&6; }
+if ${am_cv_CC_dependencies_compiler_type+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+  # We make a subdir and do the tests there.  Otherwise we can end up
+  # making bogus files that we don't know about and never remove.  For
+  # instance it was reported that on HP-UX the gcc test will end up
+  # making a dummy file named 'D' -- because '-MD' means "put the output
+  # in D".
+  rm -rf conftest.dir
+  mkdir conftest.dir
+  # Copy depcomp to subdir because otherwise we won't find it if we're
+  # using a relative directory.
+  cp "$am_depcomp" conftest.dir
+  cd conftest.dir
+  # We will build objects and dependencies in a subdirectory because
+  # it helps to detect inapplicable dependency modes.  For instance
+  # both Tru64's cc and ICC support -MD to output dependencies as a
+  # side effect of compilation, but ICC will put the dependencies in
+  # the current directory while Tru64 will put them in the object
+  # directory.
+  mkdir sub
+
+  am_cv_CC_dependencies_compiler_type=none
+  if test "$am_compiler_list" = ""; then
+     am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp`
+  fi
+  am__universal=false
+  case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac
+
+  for depmode in $am_compiler_list; do
+    # Setup a source with many dependencies, because some compilers
+    # like to wrap large dependency lists on column 80 (with \), and
+    # we should not choose a depcomp mode which is confused by this.
+    #
+    # We need to recreate these files for each test, as the compiler may
+    # overwrite some of them when testing with obscure command lines.
+    # This happens at least with the AIX C compiler.
+    : > sub/conftest.c
+    for i in 1 2 3 4 5 6; do
+      echo '#include "conftst'$i'.h"' >> sub/conftest.c
+      # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
+      # Solaris 10 /bin/sh.
+      echo '/* dummy */' > sub/conftst$i.h
+    done
+    echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+    # We check with '-c' and '-o' for the sake of the "dashmstdout"
+    # mode.  It turns out that the SunPro C++ compiler does not properly
+    # handle '-M -o', and we need to detect this.  Also, some Intel
+    # versions had trouble with output in subdirs.
+    am__obj=sub/conftest.${OBJEXT-o}
+    am__minus_obj="-o $am__obj"
+    case $depmode in
+    gcc)
+      # This depmode causes a compiler race in universal mode.
+      test "$am__universal" = false || continue
+      ;;
+    nosideeffect)
+      # After this tag, mechanisms are not by side-effect, so they'll
+      # only be used when explicitly requested.
+      if test "x$enable_dependency_tracking" = xyes; then
+	continue
+      else
+	break
+      fi
+      ;;
+    msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+      # This compiler won't grok '-c -o', but also, the minuso test has
+      # not run yet.  These depmodes are late enough in the game, and
+      # so weak that their functioning should not be impacted.
+      am__obj=conftest.${OBJEXT-o}
+      am__minus_obj=
+      ;;
+    none) break ;;
+    esac
+    if depmode=$depmode \
+       source=sub/conftest.c object=$am__obj \
+       depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+       $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+         >/dev/null 2>conftest.err &&
+       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+       ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+      # icc doesn't choke on unknown options, it will just issue warnings
+      # or remarks (even with -Werror).  So we grep stderr for any message
+      # that says an option was ignored or not supported.
+      # When given -MP, icc 7.0 and 7.1 complain thusly:
+      #   icc: Command line warning: ignoring option '-M'; no argument required
+      # The diagnosis changed in icc 8.0:
+      #   icc: Command line remark: option '-MP' not supported
+      if (grep 'ignoring option' conftest.err ||
+          grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+        am_cv_CC_dependencies_compiler_type=$depmode
+        break
+      fi
+    fi
+  done
+
+  cd ..
+  rm -rf conftest.dir
+else
+  am_cv_CC_dependencies_compiler_type=none
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5
+$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; }
+CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type
+
+ if
+  test "x$enable_dependency_tracking" != xno \
+  && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then
+  am__fastdepCC_TRUE=
+  am__fastdepCC_FALSE='#'
+else
+  am__fastdepCC_TRUE='#'
+  am__fastdepCC_FALSE=
+fi
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+  if ${ac_cv_prog_CPP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+      # Double quotes because CPP needs to be expanded
+    for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+    do
+      ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+		     Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+  break
+fi
+
+    done
+    ac_cv_prog_CPP=$CPP
+
+fi
+  CPP=$ac_cv_prog_CPP
+else
+  ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+		     Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+case `pwd` in
+  *\ * | *\	*)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5
+$as_echo "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;;
+esac
+
+
+
+macro_version='2.4.2'
+macro_revision='1.3337'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ltmain="$ac_aux_dir/ltmain.sh"
+
+# Make sure we can run config.sub.
+$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
+  as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
+$as_echo_n "checking build system type... " >&6; }
+if ${ac_cv_build+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_build_alias=$build_alias
+test "x$ac_build_alias" = x &&
+  ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
+test "x$ac_build_alias" = x &&
+  as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5
+ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` ||
+  as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
+$as_echo "$ac_cv_build" >&6; }
+case $ac_cv_build in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;;
+esac
+build=$ac_cv_build
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_build
+shift
+build_cpu=$1
+build_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+build_os=$*
+IFS=$ac_save_IFS
+case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5
+$as_echo_n "checking host system type... " >&6; }
+if ${ac_cv_host+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test "x$host_alias" = x; then
+  ac_cv_host=$ac_cv_build
+else
+  ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
+    as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5
+$as_echo "$ac_cv_host" >&6; }
+case $ac_cv_host in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;;
+esac
+host=$ac_cv_host
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_host
+shift
+host_cpu=$1
+host_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+host_os=$*
+IFS=$ac_save_IFS
+case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
+
+
+# Backslashify metacharacters that are still active within
+# double-quoted strings.
+sed_quote_subst='s/\(["`$\\]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\(["`\\]\)/\\\1/g'
+
+# Sed substitution to delay expansion of an escaped shell variable in a
+# double_quote_subst'ed string.
+delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g'
+
+# Sed substitution to delay expansion of an escaped single quote.
+delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g'
+
+# Sed substitution to avoid accidental globbing in evaled expressions
+no_glob_subst='s/\*/\\\*/g'
+
+ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5
+$as_echo_n "checking how to print strings... " >&6; }
+# Test print first, because it will be a builtin if present.
+if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \
+   test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then
+  ECHO='print -r --'
+elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then
+  ECHO='printf %s\n'
+else
+  # Use this function as a fallback that always works.
+  func_fallback_echo ()
+  {
+    eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+  }
+  ECHO='func_fallback_echo'
+fi
+
+# func_echo_all arg...
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+    $ECHO ""
+}
+
+case "$ECHO" in
+  printf*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: printf" >&5
+$as_echo "printf" >&6; } ;;
+  print*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: print -r" >&5
+$as_echo "print -r" >&6; } ;;
+  *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: cat" >&5
+$as_echo "cat" >&6; } ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5
+$as_echo_n "checking for a sed that does not truncate output... " >&6; }
+if ${ac_cv_path_SED+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+            ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/
+     for ac_i in 1 2 3 4 5 6 7; do
+       ac_script="$ac_script$as_nl$ac_script"
+     done
+     echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed
+     { ac_script=; unset ac_script;}
+     if test -z "$SED"; then
+  ac_path_SED_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in sed gsed; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_SED="$as_dir/$ac_prog$ac_exec_ext"
+      as_fn_executable_p "$ac_path_SED" || continue
+# Check for GNU ac_path_SED and select it if it is found.
+  # Check for GNU $ac_path_SED
+case `"$ac_path_SED" --version 2>&1` in
+*GNU*)
+  ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo '' >> "conftest.nl"
+    "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_SED_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_SED="$ac_path_SED"
+      ac_path_SED_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_SED_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_SED"; then
+    as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5
+  fi
+else
+  ac_cv_path_SED=$SED
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5
+$as_echo "$ac_cv_path_SED" >&6; }
+ SED="$ac_cv_path_SED"
+  rm -f conftest.sed
+
+test -z "$SED" && SED=sed
+Xsed="$SED -e 1s/^X//"
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
+if ${ac_cv_path_GREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$GREP"; then
+  ac_path_GREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in grep ggrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+      as_fn_executable_p "$ac_path_GREP" || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+  # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'GREP' >> "conftest.nl"
+    "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_GREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_GREP="$ac_path_GREP"
+      ac_path_GREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_GREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_GREP"; then
+    as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+$as_echo "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+$as_echo_n "checking for egrep... " >&6; }
+if ${ac_cv_path_EGREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+   then ac_cv_path_EGREP="$GREP -E"
+   else
+     if test -z "$EGREP"; then
+  ac_path_EGREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in egrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+      as_fn_executable_p "$ac_path_EGREP" || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+  # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'EGREP' >> "conftest.nl"
+    "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_EGREP="$ac_path_EGREP"
+      ac_path_EGREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_EGREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_EGREP"; then
+    as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_EGREP=$EGREP
+fi
+
+   fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+$as_echo "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5
+$as_echo_n "checking for fgrep... " >&6; }
+if ${ac_cv_path_FGREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1
+   then ac_cv_path_FGREP="$GREP -F"
+   else
+     if test -z "$FGREP"; then
+  ac_path_FGREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in fgrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_FGREP="$as_dir/$ac_prog$ac_exec_ext"
+      as_fn_executable_p "$ac_path_FGREP" || continue
+# Check for GNU ac_path_FGREP and select it if it is found.
+  # Check for GNU $ac_path_FGREP
+case `"$ac_path_FGREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'FGREP' >> "conftest.nl"
+    "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_FGREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_FGREP="$ac_path_FGREP"
+      ac_path_FGREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_FGREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_FGREP"; then
+    as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_FGREP=$FGREP
+fi
+
+   fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5
+$as_echo "$ac_cv_path_FGREP" >&6; }
+ FGREP="$ac_cv_path_FGREP"
+
+
+test -z "$GREP" && GREP=grep
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Check whether --with-gnu-ld was given.
+if test "${with_gnu_ld+set}" = set; then :
+  withval=$with_gnu_ld; test "$withval" = no || with_gnu_ld=yes
+else
+  with_gnu_ld=no
+fi
+
+ac_prog=ld
+if test "$GCC" = yes; then
+  # Check if gcc -print-prog-name=ld gives a path.
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5
+$as_echo_n "checking for ld used by $CC... " >&6; }
+  case $host in
+  *-*-mingw*)
+    # gcc leaves a trailing carriage return which upsets mingw
+    ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+  *)
+    ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+  esac
+  case $ac_prog in
+    # Accept absolute paths.
+    [\\/]* | ?:[\\/]*)
+      re_direlt='/[^/][^/]*/\.\./'
+      # Canonicalize the pathname of ld
+      ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'`
+      while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do
+	ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"`
+      done
+      test -z "$LD" && LD="$ac_prog"
+      ;;
+  "")
+    # If it fails, then pretend we aren't using GCC.
+    ac_prog=ld
+    ;;
+  *)
+    # If it is relative, then search for the first ld in PATH.
+    with_gnu_ld=unknown
+    ;;
+  esac
+elif test "$with_gnu_ld" = yes; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5
+$as_echo_n "checking for GNU ld... " >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5
+$as_echo_n "checking for non-GNU ld... " >&6; }
+fi
+if ${lt_cv_path_LD+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$LD"; then
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+  for ac_dir in $PATH; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+      lt_cv_path_LD="$ac_dir/$ac_prog"
+      # Check to see if the program is GNU ld.  I'd rather use --version,
+      # but apparently some variants of GNU ld only accept -v.
+      # Break only if it was the GNU/non-GNU ld that we prefer.
+      case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in
+      *GNU* | *'with BFD'*)
+	test "$with_gnu_ld" != no && break
+	;;
+      *)
+	test "$with_gnu_ld" != yes && break
+	;;
+      esac
+    fi
+  done
+  IFS="$lt_save_ifs"
+else
+  lt_cv_path_LD="$LD" # Let the user override the test with a path.
+fi
+fi
+
+LD="$lt_cv_path_LD"
+if test -n "$LD"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LD" >&5
+$as_echo "$LD" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5
+$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; }
+if ${lt_cv_prog_gnu_ld+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  # I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+  lt_cv_prog_gnu_ld=yes
+  ;;
+*)
+  lt_cv_prog_gnu_ld=no
+  ;;
+esac
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_gnu_ld" >&5
+$as_echo "$lt_cv_prog_gnu_ld" >&6; }
+with_gnu_ld=$lt_cv_prog_gnu_ld
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5
+$as_echo_n "checking for BSD- or MS-compatible name lister (nm)... " >&6; }
+if ${lt_cv_path_NM+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$NM"; then
+  # Let the user override the test.
+  lt_cv_path_NM="$NM"
+else
+  lt_nm_to_check="${ac_tool_prefix}nm"
+  if test -n "$ac_tool_prefix" && test "$build" = "$host"; then
+    lt_nm_to_check="$lt_nm_to_check nm"
+  fi
+  for lt_tmp_nm in $lt_nm_to_check; do
+    lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+    for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do
+      IFS="$lt_save_ifs"
+      test -z "$ac_dir" && ac_dir=.
+      tmp_nm="$ac_dir/$lt_tmp_nm"
+      if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then
+	# Check to see if the nm accepts a BSD-compat flag.
+	# Adding the `sed 1q' prevents false positives on HP-UX, which says:
+	#   nm: unknown option "B" ignored
+	# Tru64's nm complains that /dev/null is an invalid object file
+	case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in
+	*/dev/null* | *'Invalid file or object type'*)
+	  lt_cv_path_NM="$tmp_nm -B"
+	  break
+	  ;;
+	*)
+	  case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in
+	  */dev/null*)
+	    lt_cv_path_NM="$tmp_nm -p"
+	    break
+	    ;;
+	  *)
+	    lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but
+	    continue # so that we can try to find one that supports BSD flags
+	    ;;
+	  esac
+	  ;;
+	esac
+      fi
+    done
+    IFS="$lt_save_ifs"
+  done
+  : ${lt_cv_path_NM=no}
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5
+$as_echo "$lt_cv_path_NM" >&6; }
+if test "$lt_cv_path_NM" != "no"; then
+  NM="$lt_cv_path_NM"
+else
+  # Didn't find any BSD compatible name lister, look for dumpbin.
+  if test -n "$DUMPBIN"; then :
+    # Let the user override the test.
+  else
+    if test -n "$ac_tool_prefix"; then
+  for ac_prog in dumpbin "link -dump"
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_DUMPBIN+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$DUMPBIN"; then
+  ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+DUMPBIN=$ac_cv_prog_DUMPBIN
+if test -n "$DUMPBIN"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5
+$as_echo "$DUMPBIN" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$DUMPBIN" && break
+  done
+fi
+if test -z "$DUMPBIN"; then
+  ac_ct_DUMPBIN=$DUMPBIN
+  for ac_prog in dumpbin "link -dump"
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_DUMPBIN+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_DUMPBIN"; then
+  ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_DUMPBIN="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN
+if test -n "$ac_ct_DUMPBIN"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5
+$as_echo "$ac_ct_DUMPBIN" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_DUMPBIN" && break
+done
+
+  if test "x$ac_ct_DUMPBIN" = x; then
+    DUMPBIN=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    DUMPBIN=$ac_ct_DUMPBIN
+  fi
+fi
+
+    case `$DUMPBIN -symbols /dev/null 2>&1 | sed '1q'` in
+    *COFF*)
+      DUMPBIN="$DUMPBIN -symbols"
+      ;;
+    *)
+      DUMPBIN=:
+      ;;
+    esac
+  fi
+
+  if test "$DUMPBIN" != ":"; then
+    NM="$DUMPBIN"
+  fi
+fi
+test -z "$NM" && NM=nm
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5
+$as_echo_n "checking the name lister ($NM) interface... " >&6; }
+if ${lt_cv_nm_interface+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_nm_interface="BSD nm"
+  echo "int some_variable = 0;" > conftest.$ac_ext
+  (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5)
+  (eval "$ac_compile" 2>conftest.err)
+  cat conftest.err >&5
+  (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
+  (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
+  cat conftest.err >&5
+  (eval echo "\"\$as_me:$LINENO: output\"" >&5)
+  cat conftest.out >&5
+  if $GREP 'External.*some_variable' conftest.out > /dev/null; then
+    lt_cv_nm_interface="MS dumpbin"
+  fi
+  rm -f conftest*
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5
+$as_echo "$lt_cv_nm_interface" >&6; }
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5
+$as_echo_n "checking whether ln -s works... " >&6; }
+LN_S=$as_ln_s
+if test "$LN_S" = "ln -s"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5
+$as_echo "no, using $LN_S" >&6; }
+fi
+
+# find the maximum length of command line arguments
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5
+$as_echo_n "checking the maximum length of command line arguments... " >&6; }
+if ${lt_cv_sys_max_cmd_len+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+    i=0
+  teststring="ABCD"
+
+  case $build_os in
+  msdosdjgpp*)
+    # On DJGPP, this test can blow up pretty badly due to problems in libc
+    # (any single argument exceeding 2000 bytes causes a buffer overrun
+    # during glob expansion).  Even if it were fixed, the result of this
+    # check would be larger than it should be.
+    lt_cv_sys_max_cmd_len=12288;    # 12K is about right
+    ;;
+
+  gnu*)
+    # Under GNU Hurd, this test is not required because there is
+    # no limit to the length of command line arguments.
+    # Libtool will interpret -1 as no limit whatsoever
+    lt_cv_sys_max_cmd_len=-1;
+    ;;
+
+  cygwin* | mingw* | cegcc*)
+    # On Win9x/ME, this test blows up -- it succeeds, but takes
+    # about 5 minutes as the teststring grows exponentially.
+    # Worse, since 9x/ME are not pre-emptively multitasking,
+    # you end up with a "frozen" computer, even though with patience
+    # the test eventually succeeds (with a max line length of 256k).
+    # Instead, let's just punt: use the minimum linelength reported by
+    # all of the supported platforms: 8192 (on NT/2K/XP).
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  mint*)
+    # On MiNT this can take a long time and run out of memory.
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  amigaos*)
+    # On AmigaOS with pdksh, this test takes hours, literally.
+    # So we just punt and use a minimum line length of 8192.
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  netbsd* | freebsd* | openbsd* | darwin* | dragonfly*)
+    # This has been around since 386BSD, at least.  Likely further.
+    if test -x /sbin/sysctl; then
+      lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax`
+    elif test -x /usr/sbin/sysctl; then
+      lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax`
+    else
+      lt_cv_sys_max_cmd_len=65536	# usable default for all BSDs
+    fi
+    # And add a safety zone
+    lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+    lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+    ;;
+
+  interix*)
+    # We know the value 262144 and hardcode it with a safety zone (like BSD)
+    lt_cv_sys_max_cmd_len=196608
+    ;;
+
+  os2*)
+    # The test takes a long time on OS/2.
+    lt_cv_sys_max_cmd_len=8192
+    ;;
+
+  osf*)
+    # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure
+    # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not
+    # nice to cause kernel panics so lets avoid the loop below.
+    # First set a reasonable default.
+    lt_cv_sys_max_cmd_len=16384
+    #
+    if test -x /sbin/sysconfig; then
+      case `/sbin/sysconfig -q proc exec_disable_arg_limit` in
+        *1*) lt_cv_sys_max_cmd_len=-1 ;;
+      esac
+    fi
+    ;;
+  sco3.2v5*)
+    lt_cv_sys_max_cmd_len=102400
+    ;;
+  sysv5* | sco5v6* | sysv4.2uw2*)
+    kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null`
+    if test -n "$kargmax"; then
+      lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[	 ]//'`
+    else
+      lt_cv_sys_max_cmd_len=32768
+    fi
+    ;;
+  *)
+    lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null`
+    if test -n "$lt_cv_sys_max_cmd_len"; then
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+    else
+      # Make teststring a little bigger before we do anything with it.
+      # a 1K string should be a reasonable start.
+      for i in 1 2 3 4 5 6 7 8 ; do
+        teststring=$teststring$teststring
+      done
+      SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}}
+      # If test is not a shell built-in, we'll probably end up computing a
+      # maximum length that is only half of the actual maximum length, but
+      # we can't tell.
+      while { test "X"`env echo "$teststring$teststring" 2>/dev/null` \
+	         = "X$teststring$teststring"; } >/dev/null 2>&1 &&
+	      test $i != 17 # 1/2 MB should be enough
+      do
+        i=`expr $i + 1`
+        teststring=$teststring$teststring
+      done
+      # Only check the string length outside the loop.
+      lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1`
+      teststring=
+      # Add a significant safety factor because C++ compilers can tack on
+      # massive amounts of additional arguments before passing them to the
+      # linker.  It appears as though 1/2 is a usable value.
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2`
+    fi
+    ;;
+  esac
+
+fi
+
+if test -n $lt_cv_sys_max_cmd_len ; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5
+$as_echo "$lt_cv_sys_max_cmd_len" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5
+$as_echo "none" >&6; }
+fi
+max_cmd_len=$lt_cv_sys_max_cmd_len
+
+
+
+
+
+
+: ${CP="cp -f"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the shell understands some XSI constructs" >&5
+$as_echo_n "checking whether the shell understands some XSI constructs... " >&6; }
+# Try some XSI features
+xsi_shell=no
+( _lt_dummy="a/b/c"
+  test "${_lt_dummy##*/},${_lt_dummy%/*},${_lt_dummy#??}"${_lt_dummy%"$_lt_dummy"}, \
+      = c,a/b,b/c, \
+    && eval 'test $(( 1 + 1 )) -eq 2 \
+    && test "${#_lt_dummy}" -eq 5' ) >/dev/null 2>&1 \
+  && xsi_shell=yes
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $xsi_shell" >&5
+$as_echo "$xsi_shell" >&6; }
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the shell understands \"+=\"" >&5
+$as_echo_n "checking whether the shell understands \"+=\"... " >&6; }
+lt_shell_append=no
+( foo=bar; set foo baz; eval "$1+=\$2" && test "$foo" = barbaz ) \
+    >/dev/null 2>&1 \
+  && lt_shell_append=yes
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_shell_append" >&5
+$as_echo "$lt_shell_append" >&6; }
+
+
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+  lt_unset=unset
+else
+  lt_unset=false
+fi
+
+
+
+
+
+# test EBCDIC or ASCII
+case `echo X|tr X '\101'` in
+ A) # ASCII based system
+    # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr
+  lt_SP2NL='tr \040 \012'
+  lt_NL2SP='tr \015\012 \040\040'
+  ;;
+ *) # EBCDIC based system
+  lt_SP2NL='tr \100 \n'
+  lt_NL2SP='tr \r\n \100\100'
+  ;;
+esac
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5
+$as_echo_n "checking how to convert $build file names to $host format... " >&6; }
+if ${lt_cv_to_host_file_cmd+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $host in
+  *-*-mingw* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32
+        ;;
+      *-*-cygwin* )
+        lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32
+        ;;
+      * ) # otherwise, assume *nix
+        lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32
+        ;;
+    esac
+    ;;
+  *-*-cygwin* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin
+        ;;
+      *-*-cygwin* )
+        lt_cv_to_host_file_cmd=func_convert_file_noop
+        ;;
+      * ) # otherwise, assume *nix
+        lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin
+        ;;
+    esac
+    ;;
+  * ) # unhandled hosts (and "normal" native builds)
+    lt_cv_to_host_file_cmd=func_convert_file_noop
+    ;;
+esac
+
+fi
+
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5
+$as_echo "$lt_cv_to_host_file_cmd" >&6; }
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5
+$as_echo_n "checking how to convert $build file names to toolchain format... " >&6; }
+if ${lt_cv_to_tool_file_cmd+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  #assume ordinary cross tools, or native build.
+lt_cv_to_tool_file_cmd=func_convert_file_noop
+case $host in
+  *-*-mingw* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32
+        ;;
+    esac
+    ;;
+esac
+
+fi
+
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5
+$as_echo "$lt_cv_to_tool_file_cmd" >&6; }
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5
+$as_echo_n "checking for $LD option to reload object files... " >&6; }
+if ${lt_cv_ld_reload_flag+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_ld_reload_flag='-r'
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5
+$as_echo "$lt_cv_ld_reload_flag" >&6; }
+reload_flag=$lt_cv_ld_reload_flag
+case $reload_flag in
+"" | " "*) ;;
+*) reload_flag=" $reload_flag" ;;
+esac
+reload_cmds='$LD$reload_flag -o $output$reload_objs'
+case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    if test "$GCC" != yes; then
+      reload_cmds=false
+    fi
+    ;;
+  darwin*)
+    if test "$GCC" = yes; then
+      reload_cmds='$LTCC $LTCFLAGS -nostdlib ${wl}-r -o $output$reload_objs'
+    else
+      reload_cmds='$LD$reload_flag -o $output$reload_objs'
+    fi
+    ;;
+esac
+
+
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args.
+set dummy ${ac_tool_prefix}objdump; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_OBJDUMP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$OBJDUMP"; then
+  ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+OBJDUMP=$ac_cv_prog_OBJDUMP
+if test -n "$OBJDUMP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5
+$as_echo "$OBJDUMP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OBJDUMP"; then
+  ac_ct_OBJDUMP=$OBJDUMP
+  # Extract the first word of "objdump", so it can be a program name with args.
+set dummy objdump; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_OBJDUMP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_OBJDUMP"; then
+  ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_OBJDUMP="objdump"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP
+if test -n "$ac_ct_OBJDUMP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5
+$as_echo "$ac_ct_OBJDUMP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_OBJDUMP" = x; then
+    OBJDUMP="false"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    OBJDUMP=$ac_ct_OBJDUMP
+  fi
+else
+  OBJDUMP="$ac_cv_prog_OBJDUMP"
+fi
+
+test -z "$OBJDUMP" && OBJDUMP=objdump
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5
+$as_echo_n "checking how to recognize dependent libraries... " >&6; }
+if ${lt_cv_deplibs_check_method+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_file_magic_cmd='$MAGIC_CMD'
+lt_cv_file_magic_test_file=
+lt_cv_deplibs_check_method='unknown'
+# Need to set the preceding variable on all platforms that support
+# interlibrary dependencies.
+# 'none' -- dependencies not supported.
+# `unknown' -- same as none, but documents that we really don't know.
+# 'pass_all' -- all dependencies passed with no checks.
+# 'test_compile' -- check by making test program.
+# 'file_magic [[regex]]' -- check by looking for files in library path
+# which responds to the $file_magic_cmd with a given extended regex.
+# If you have `file' or equivalent on your system and you're not sure
+# whether `pass_all' will *always* work, you probably want this one.
+
+case $host_os in
+aix[4-9]*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+beos*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+bsdi[45]*)
+  lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)'
+  lt_cv_file_magic_cmd='/usr/bin/file -L'
+  lt_cv_file_magic_test_file=/shlib/libc.so
+  ;;
+
+cygwin*)
+  # func_win32_libid is a shell function defined in ltmain.sh
+  lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+  lt_cv_file_magic_cmd='func_win32_libid'
+  ;;
+
+mingw* | pw32*)
+  # Base MSYS/MinGW do not provide the 'file' command needed by
+  # func_win32_libid shell function, so use a weaker test based on 'objdump',
+  # unless we find 'file', for example because we are cross-compiling.
+  # func_win32_libid assumes BSD nm, so disallow it if using MS dumpbin.
+  if ( test "$lt_cv_nm_interface" = "BSD nm" && file / ) >/dev/null 2>&1; then
+    lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+    lt_cv_file_magic_cmd='func_win32_libid'
+  else
+    # Keep this pattern in sync with the one in func_win32_libid.
+    lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)'
+    lt_cv_file_magic_cmd='$OBJDUMP -f'
+  fi
+  ;;
+
+cegcc*)
+  # use the weaker test based on 'objdump'. See mingw*.
+  lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?'
+  lt_cv_file_magic_cmd='$OBJDUMP -f'
+  ;;
+
+darwin* | rhapsody*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+freebsd* | dragonfly*)
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+    case $host_cpu in
+    i*86 )
+      # Not sure whether the presence of OpenBSD here was a mistake.
+      # Let's accept both of them until this is cleared up.
+      lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library'
+      lt_cv_file_magic_cmd=/usr/bin/file
+      lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+      ;;
+    esac
+  else
+    lt_cv_deplibs_check_method=pass_all
+  fi
+  ;;
+
+gnu*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+haiku*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+hpux10.20* | hpux11*)
+  lt_cv_file_magic_cmd=/usr/bin/file
+  case $host_cpu in
+  ia64*)
+    lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64'
+    lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so
+    ;;
+  hppa*64*)
+    lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'
+    lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl
+    ;;
+  *)
+    lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library'
+    lt_cv_file_magic_test_file=/usr/lib/libc.sl
+    ;;
+  esac
+  ;;
+
+interix[3-9]*)
+  # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here
+  lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$'
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $LD in
+  *-32|*"-32 ") libmagic=32-bit;;
+  *-n32|*"-n32 ") libmagic=N32;;
+  *-64|*"-64 ") libmagic=64-bit;;
+  *) libmagic=never-match;;
+  esac
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+netbsd*)
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+    lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$'
+  else
+    lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$'
+  fi
+  ;;
+
+newos6*)
+  lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)'
+  lt_cv_file_magic_cmd=/usr/bin/file
+  lt_cv_file_magic_test_file=/usr/lib/libnls.so
+  ;;
+
+*nto* | *qnx*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+openbsd*)
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$'
+  else
+    lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$'
+  fi
+  ;;
+
+osf3* | osf4* | osf5*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+rdos*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+solaris*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+sysv4 | sysv4.3*)
+  case $host_vendor in
+  motorola)
+    lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]'
+    lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*`
+    ;;
+  ncr)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  sequent)
+    lt_cv_file_magic_cmd='/bin/file'
+    lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )'
+    ;;
+  sni)
+    lt_cv_file_magic_cmd='/bin/file'
+    lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib"
+    lt_cv_file_magic_test_file=/lib/libc.so
+    ;;
+  siemens)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  pc)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  esac
+  ;;
+
+tpf*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+esac
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5
+$as_echo "$lt_cv_deplibs_check_method" >&6; }
+
+file_magic_glob=
+want_nocaseglob=no
+if test "$build" = "$host"; then
+  case $host_os in
+  mingw* | pw32*)
+    if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then
+      want_nocaseglob=yes
+    else
+      file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"`
+    fi
+    ;;
+  esac
+fi
+
+file_magic_cmd=$lt_cv_file_magic_cmd
+deplibs_check_method=$lt_cv_deplibs_check_method
+test -z "$deplibs_check_method" && deplibs_check_method=unknown
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args.
+set dummy ${ac_tool_prefix}dlltool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_DLLTOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$DLLTOOL"; then
+  ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+DLLTOOL=$ac_cv_prog_DLLTOOL
+if test -n "$DLLTOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5
+$as_echo "$DLLTOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_DLLTOOL"; then
+  ac_ct_DLLTOOL=$DLLTOOL
+  # Extract the first word of "dlltool", so it can be a program name with args.
+set dummy dlltool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_DLLTOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_DLLTOOL"; then
+  ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_DLLTOOL="dlltool"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL
+if test -n "$ac_ct_DLLTOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5
+$as_echo "$ac_ct_DLLTOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_DLLTOOL" = x; then
+    DLLTOOL="false"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    DLLTOOL=$ac_ct_DLLTOOL
+  fi
+else
+  DLLTOOL="$ac_cv_prog_DLLTOOL"
+fi
+
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5
+$as_echo_n "checking how to associate runtime and link libraries... " >&6; }
+if ${lt_cv_sharedlib_from_linklib_cmd+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_sharedlib_from_linklib_cmd='unknown'
+
+case $host_os in
+cygwin* | mingw* | pw32* | cegcc*)
+  # two different shell functions defined in ltmain.sh
+  # decide which to use based on capabilities of $DLLTOOL
+  case `$DLLTOOL --help 2>&1` in
+  *--identify-strict*)
+    lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib
+    ;;
+  *)
+    lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback
+    ;;
+  esac
+  ;;
+*)
+  # fallback: assume linklib IS sharedlib
+  lt_cv_sharedlib_from_linklib_cmd="$ECHO"
+  ;;
+esac
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5
+$as_echo "$lt_cv_sharedlib_from_linklib_cmd" >&6; }
+sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd
+test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO
+
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+  for ac_prog in ar
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_AR+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$AR"; then
+  ac_cv_prog_AR="$AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_AR="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+AR=$ac_cv_prog_AR
+if test -n "$AR"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5
+$as_echo "$AR" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$AR" && break
+  done
+fi
+if test -z "$AR"; then
+  ac_ct_AR=$AR
+  for ac_prog in ar
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_AR+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_AR"; then
+  ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_AR="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_AR=$ac_cv_prog_ac_ct_AR
+if test -n "$ac_ct_AR"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5
+$as_echo "$ac_ct_AR" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_AR" && break
+done
+
+  if test "x$ac_ct_AR" = x; then
+    AR="false"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    AR=$ac_ct_AR
+  fi
+fi
+
+: ${AR=ar}
+: ${AR_FLAGS=cru}
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5
+$as_echo_n "checking for archiver @FILE support... " >&6; }
+if ${lt_cv_ar_at_file+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_ar_at_file=no
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  echo conftest.$ac_objext > conftest.lst
+      lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5'
+      { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5
+  (eval $lt_ar_try) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+      if test "$ac_status" -eq 0; then
+	# Ensure the archiver fails upon bogus file names.
+	rm -f conftest.$ac_objext libconftest.a
+	{ { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5
+  (eval $lt_ar_try) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+	if test "$ac_status" -ne 0; then
+          lt_cv_ar_at_file=@
+        fi
+      fi
+      rm -f conftest.* libconftest.a
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5
+$as_echo "$lt_cv_ar_at_file" >&6; }
+
+if test "x$lt_cv_ar_at_file" = xno; then
+  archiver_list_spec=
+else
+  archiver_list_spec=$lt_cv_ar_at_file
+fi
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$STRIP"; then
+  ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+$as_echo "$STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+  ac_ct_STRIP=$STRIP
+  # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_STRIP"; then
+  ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_STRIP="strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+$as_echo "$ac_ct_STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_STRIP" = x; then
+    STRIP=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    STRIP=$ac_ct_STRIP
+  fi
+else
+  STRIP="$ac_cv_prog_STRIP"
+fi
+
+test -z "$STRIP" && STRIP=:
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_RANLIB+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$RANLIB"; then
+  ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+RANLIB=$ac_cv_prog_RANLIB
+if test -n "$RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5
+$as_echo "$RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_RANLIB"; then
+  ac_ct_RANLIB=$RANLIB
+  # Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_RANLIB+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_RANLIB"; then
+  ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_RANLIB="ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB
+if test -n "$ac_ct_RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5
+$as_echo "$ac_ct_RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_RANLIB" = x; then
+    RANLIB=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    RANLIB=$ac_ct_RANLIB
+  fi
+else
+  RANLIB="$ac_cv_prog_RANLIB"
+fi
+
+test -z "$RANLIB" && RANLIB=:
+
+
+
+
+
+
+# Determine commands to create old-style static archives.
+old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs'
+old_postinstall_cmds='chmod 644 $oldlib'
+old_postuninstall_cmds=
+
+if test -n "$RANLIB"; then
+  case $host_os in
+  openbsd*)
+    old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib"
+    ;;
+  *)
+    old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib"
+    ;;
+  esac
+  old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib"
+fi
+
+case $host_os in
+  darwin*)
+    lock_old_archive_extraction=yes ;;
+  *)
+    lock_old_archive_extraction=no ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+
+
+# Check for command to grab the raw symbol name followed by C symbol from nm.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5
+$as_echo_n "checking command to parse $NM output from $compiler object... " >&6; }
+if ${lt_cv_sys_global_symbol_pipe+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+
+# These are sane defaults that work on at least a few old systems.
+# [They come from Ultrix.  What could be older than Ultrix?!! ;)]
+
+# Character class describing NM global symbol codes.
+symcode='[BCDEGRST]'
+
+# Regexp to match symbols that can be accessed directly from C.
+sympat='\([_A-Za-z][_A-Za-z0-9]*\)'
+
+# Define system-specific variables.
+case $host_os in
+aix*)
+  symcode='[BCDT]'
+  ;;
+cygwin* | mingw* | pw32* | cegcc*)
+  symcode='[ABCDGISTW]'
+  ;;
+hpux*)
+  if test "$host_cpu" = ia64; then
+    symcode='[ABCDEGRST]'
+  fi
+  ;;
+irix* | nonstopux*)
+  symcode='[BCDEGRST]'
+  ;;
+osf*)
+  symcode='[BCDEGQRST]'
+  ;;
+solaris*)
+  symcode='[BDRT]'
+  ;;
+sco3.2v5*)
+  symcode='[DT]'
+  ;;
+sysv4.2uw2*)
+  symcode='[DT]'
+  ;;
+sysv5* | sco5v6* | unixware* | OpenUNIX*)
+  symcode='[ABDT]'
+  ;;
+sysv4)
+  symcode='[DFNSTU]'
+  ;;
+esac
+
+# If we're using GNU nm, then use its standard symbol codes.
+case `$NM -V 2>&1` in
+*GNU* | *'with BFD'*)
+  symcode='[ABCDGIRSTW]' ;;
+esac
+
+# Transform an extracted symbol line into a proper C declaration.
+# Some systems (esp. on ia64) link data and code symbols differently,
+# so use this general approach.
+lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'"
+
+# Transform an extracted symbol line into symbol name and symbol address
+lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([^ ]*\)[ ]*$/  {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/  {\"\2\", (void *) \&\2},/p'"
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n -e 's/^: \([^ ]*\)[ ]*$/  {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([^ ]*\) \(lib[^ ]*\)$/  {\"\2\", (void *) \&\2},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/  {\"lib\2\", (void *) \&\2},/p'"
+
+# Handle CRLF in mingw tool chain
+opt_cr=
+case $build_os in
+mingw*)
+  opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp
+  ;;
+esac
+
+# Try without a prefix underscore, then with it.
+for ac_symprfx in "" "_"; do
+
+  # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol.
+  symxfrm="\\1 $ac_symprfx\\2 \\2"
+
+  # Write the raw and C identifiers.
+  if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+    # Fake it for dumpbin and say T for any non-static function
+    # and D for any global variable.
+    # Also find C++ and __fastcall symbols from MSVC++,
+    # which start with @ or ?.
+    lt_cv_sys_global_symbol_pipe="$AWK '"\
+"     {last_section=section; section=\$ 3};"\
+"     /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\
+"     /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\
+"     \$ 0!~/External *\|/{next};"\
+"     / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\
+"     {if(hide[section]) next};"\
+"     {f=0}; \$ 0~/\(\).*\|/{f=1}; {printf f ? \"T \" : \"D \"};"\
+"     {split(\$ 0, a, /\||\r/); split(a[2], s)};"\
+"     s[1]~/^[@?]/{print s[1], s[1]; next};"\
+"     s[1]~prfx {split(s[1],t,\"@\"); print t[1], substr(t[1],length(prfx))}"\
+"     ' prfx=^$ac_symprfx"
+  else
+    lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[	 ]\($symcode$symcode*\)[	 ][	 ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'"
+  fi
+  lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'"
+
+  # Check to see that the pipe works correctly.
+  pipe_works=no
+
+  rm -f conftest*
+  cat > conftest.$ac_ext <<_LT_EOF
+#ifdef __cplusplus
+extern "C" {
+#endif
+char nm_test_var;
+void nm_test_func(void);
+void nm_test_func(void){}
+#ifdef __cplusplus
+}
+#endif
+int main(){nm_test_var='a';nm_test_func();return(0);}
+_LT_EOF
+
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+    # Now try to grab the symbols.
+    nlist=conftest.nm
+    if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist\""; } >&5
+  (eval $NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && test -s "$nlist"; then
+      # Try sorting and uniquifying the output.
+      if sort "$nlist" | uniq > "$nlist"T; then
+	mv -f "$nlist"T "$nlist"
+      else
+	rm -f "$nlist"T
+      fi
+
+      # Make sure that we snagged all the symbols we need.
+      if $GREP ' nm_test_var$' "$nlist" >/dev/null; then
+	if $GREP ' nm_test_func$' "$nlist" >/dev/null; then
+	  cat <<_LT_EOF > conftest.$ac_ext
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests.  */
+#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE)
+/* DATA imports from DLLs on WIN32 con't be const, because runtime
+   relocations are performed -- see ld's documentation on pseudo-relocs.  */
+# define LT_DLSYM_CONST
+#elif defined(__osf__)
+/* This system does not cope well with relocations in const data.  */
+# define LT_DLSYM_CONST
+#else
+# define LT_DLSYM_CONST const
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_LT_EOF
+	  # Now generate the symbol file.
+	  eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext'
+
+	  cat <<_LT_EOF >> conftest.$ac_ext
+
+/* The mapping between symbol names and symbols.  */
+LT_DLSYM_CONST struct {
+  const char *name;
+  void       *address;
+}
+lt__PROGRAM__LTX_preloaded_symbols[] =
+{
+  { "@PROGRAM@", (void *) 0 },
+_LT_EOF
+	  $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/  {\"\2\", (void *) \&\2},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext
+	  cat <<\_LT_EOF >> conftest.$ac_ext
+  {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+  return lt__PROGRAM__LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+_LT_EOF
+	  # Now try linking the two files.
+	  mv conftest.$ac_objext conftstm.$ac_objext
+	  lt_globsym_save_LIBS=$LIBS
+	  lt_globsym_save_CFLAGS=$CFLAGS
+	  LIBS="conftstm.$ac_objext"
+	  CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag"
+	  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+  (eval $ac_link) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && test -s conftest${ac_exeext}; then
+	    pipe_works=yes
+	  fi
+	  LIBS=$lt_globsym_save_LIBS
+	  CFLAGS=$lt_globsym_save_CFLAGS
+	else
+	  echo "cannot find nm_test_func in $nlist" >&5
+	fi
+      else
+	echo "cannot find nm_test_var in $nlist" >&5
+      fi
+    else
+      echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5
+    fi
+  else
+    echo "$progname: failed program was:" >&5
+    cat conftest.$ac_ext >&5
+  fi
+  rm -rf conftest* conftst*
+
+  # Do not use the global_symbol_pipe unless it works.
+  if test "$pipe_works" = yes; then
+    break
+  else
+    lt_cv_sys_global_symbol_pipe=
+  fi
+done
+
+fi
+
+if test -z "$lt_cv_sys_global_symbol_pipe"; then
+  lt_cv_sys_global_symbol_to_cdecl=
+fi
+if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5
+$as_echo "failed" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok" >&5
+$as_echo "ok" >&6; }
+fi
+
+# Response file support.
+if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+  nm_file_list_spec='@'
+elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then
+  nm_file_list_spec='@'
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5
+$as_echo_n "checking for sysroot... " >&6; }
+
+# Check whether --with-sysroot was given.
+if test "${with_sysroot+set}" = set; then :
+  withval=$with_sysroot;
+else
+  with_sysroot=no
+fi
+
+
+lt_sysroot=
+case ${with_sysroot} in #(
+ yes)
+   if test "$GCC" = yes; then
+     lt_sysroot=`$CC --print-sysroot 2>/dev/null`
+   fi
+   ;; #(
+ /*)
+   lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"`
+   ;; #(
+ no|'')
+   ;; #(
+ *)
+   { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${with_sysroot}" >&5
+$as_echo "${with_sysroot}" >&6; }
+   as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5
+   ;;
+esac
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5
+$as_echo "${lt_sysroot:-no}" >&6; }
+
+
+
+
+
+# Check whether --enable-libtool-lock was given.
+if test "${enable_libtool_lock+set}" = set; then :
+  enableval=$enable_libtool_lock;
+fi
+
+test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes
+
+# Some flags need to be propagated to the compiler or linker for good
+# libtool support.
+case $host in
+ia64-*-hpux*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+    case `/usr/bin/file conftest.$ac_objext` in
+      *ELF-32*)
+	HPUX_IA64_MODE="32"
+	;;
+      *ELF-64*)
+	HPUX_IA64_MODE="64"
+	;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+*-*-irix6*)
+  # Find out which ABI we are using.
+  echo '#line '$LINENO' "configure"' > conftest.$ac_ext
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+    if test "$lt_cv_prog_gnu_ld" = yes; then
+      case `/usr/bin/file conftest.$ac_objext` in
+	*32-bit*)
+	  LD="${LD-ld} -melf32bsmip"
+	  ;;
+	*N32*)
+	  LD="${LD-ld} -melf32bmipn32"
+	  ;;
+	*64-bit*)
+	  LD="${LD-ld} -melf64bmip"
+	;;
+      esac
+    else
+      case `/usr/bin/file conftest.$ac_objext` in
+	*32-bit*)
+	  LD="${LD-ld} -32"
+	  ;;
+	*N32*)
+	  LD="${LD-ld} -n32"
+	  ;;
+	*64-bit*)
+	  LD="${LD-ld} -64"
+	  ;;
+      esac
+    fi
+  fi
+  rm -rf conftest*
+  ;;
+
+x86_64-*kfreebsd*-gnu|x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*| \
+s390*-*linux*|s390*-*tpf*|sparc*-*linux*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+    case `/usr/bin/file conftest.o` in
+      *32-bit*)
+	case $host in
+	  x86_64-*kfreebsd*-gnu)
+	    LD="${LD-ld} -m elf_i386_fbsd"
+	    ;;
+	  x86_64-*linux*)
+	    LD="${LD-ld} -m elf_i386"
+	    ;;
+	  ppc64-*linux*|powerpc64-*linux*)
+	    LD="${LD-ld} -m elf32ppclinux"
+	    ;;
+	  s390x-*linux*)
+	    LD="${LD-ld} -m elf_s390"
+	    ;;
+	  sparc64-*linux*)
+	    LD="${LD-ld} -m elf32_sparc"
+	    ;;
+	esac
+	;;
+      *64-bit*)
+	case $host in
+	  x86_64-*kfreebsd*-gnu)
+	    LD="${LD-ld} -m elf_x86_64_fbsd"
+	    ;;
+	  x86_64-*linux*)
+	    LD="${LD-ld} -m elf_x86_64"
+	    ;;
+	  ppc*-*linux*|powerpc*-*linux*)
+	    LD="${LD-ld} -m elf64ppc"
+	    ;;
+	  s390*-*linux*|s390*-*tpf*)
+	    LD="${LD-ld} -m elf64_s390"
+	    ;;
+	  sparc*-*linux*)
+	    LD="${LD-ld} -m elf64_sparc"
+	    ;;
+	esac
+	;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+
+*-*-sco3.2v5*)
+  # On SCO OpenServer 5, we need -belf to get full-featured binaries.
+  SAVE_CFLAGS="$CFLAGS"
+  CFLAGS="$CFLAGS -belf"
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5
+$as_echo_n "checking whether the C compiler needs -belf... " >&6; }
+if ${lt_cv_cc_needs_belf+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+     cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  lt_cv_cc_needs_belf=yes
+else
+  lt_cv_cc_needs_belf=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+     ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5
+$as_echo "$lt_cv_cc_needs_belf" >&6; }
+  if test x"$lt_cv_cc_needs_belf" != x"yes"; then
+    # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf
+    CFLAGS="$SAVE_CFLAGS"
+  fi
+  ;;
+*-*solaris*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+    case `/usr/bin/file conftest.o` in
+    *64-bit*)
+      case $lt_cv_prog_gnu_ld in
+      yes*)
+        case $host in
+        i?86-*-solaris*)
+          LD="${LD-ld} -m elf_x86_64"
+          ;;
+        sparc*-*-solaris*)
+          LD="${LD-ld} -m elf64_sparc"
+          ;;
+        esac
+        # GNU ld 2.21 introduced _sol2 emulations.  Use them if available.
+        if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then
+          LD="${LD-ld}_sol2"
+        fi
+        ;;
+      *)
+	if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then
+	  LD="${LD-ld} -64"
+	fi
+	;;
+      esac
+      ;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+esac
+
+need_locks="$enable_libtool_lock"
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args.
+set dummy ${ac_tool_prefix}mt; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_MANIFEST_TOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$MANIFEST_TOOL"; then
+  ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL
+if test -n "$MANIFEST_TOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5
+$as_echo "$MANIFEST_TOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_MANIFEST_TOOL"; then
+  ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL
+  # Extract the first word of "mt", so it can be a program name with args.
+set dummy mt; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_MANIFEST_TOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_MANIFEST_TOOL"; then
+  ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_MANIFEST_TOOL="mt"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL
+if test -n "$ac_ct_MANIFEST_TOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5
+$as_echo "$ac_ct_MANIFEST_TOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_MANIFEST_TOOL" = x; then
+    MANIFEST_TOOL=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL
+  fi
+else
+  MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL"
+fi
+
+test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5
+$as_echo_n "checking if $MANIFEST_TOOL is a manifest tool... " >&6; }
+if ${lt_cv_path_mainfest_tool+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_path_mainfest_tool=no
+  echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5
+  $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out
+  cat conftest.err >&5
+  if $GREP 'Manifest Tool' conftest.out > /dev/null; then
+    lt_cv_path_mainfest_tool=yes
+  fi
+  rm -f conftest*
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_mainfest_tool" >&5
+$as_echo "$lt_cv_path_mainfest_tool" >&6; }
+if test "x$lt_cv_path_mainfest_tool" != xyes; then
+  MANIFEST_TOOL=:
+fi
+
+
+
+
+
+
+  case $host_os in
+    rhapsody* | darwin*)
+    if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args.
+set dummy ${ac_tool_prefix}dsymutil; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_DSYMUTIL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$DSYMUTIL"; then
+  ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+DSYMUTIL=$ac_cv_prog_DSYMUTIL
+if test -n "$DSYMUTIL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5
+$as_echo "$DSYMUTIL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_DSYMUTIL"; then
+  ac_ct_DSYMUTIL=$DSYMUTIL
+  # Extract the first word of "dsymutil", so it can be a program name with args.
+set dummy dsymutil; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_DSYMUTIL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_DSYMUTIL"; then
+  ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_DSYMUTIL="dsymutil"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL
+if test -n "$ac_ct_DSYMUTIL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5
+$as_echo "$ac_ct_DSYMUTIL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_DSYMUTIL" = x; then
+    DSYMUTIL=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    DSYMUTIL=$ac_ct_DSYMUTIL
+  fi
+else
+  DSYMUTIL="$ac_cv_prog_DSYMUTIL"
+fi
+
+    if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args.
+set dummy ${ac_tool_prefix}nmedit; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_NMEDIT+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$NMEDIT"; then
+  ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+NMEDIT=$ac_cv_prog_NMEDIT
+if test -n "$NMEDIT"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5
+$as_echo "$NMEDIT" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_NMEDIT"; then
+  ac_ct_NMEDIT=$NMEDIT
+  # Extract the first word of "nmedit", so it can be a program name with args.
+set dummy nmedit; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_NMEDIT+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_NMEDIT"; then
+  ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_NMEDIT="nmedit"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT
+if test -n "$ac_ct_NMEDIT"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5
+$as_echo "$ac_ct_NMEDIT" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_NMEDIT" = x; then
+    NMEDIT=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    NMEDIT=$ac_ct_NMEDIT
+  fi
+else
+  NMEDIT="$ac_cv_prog_NMEDIT"
+fi
+
+    if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args.
+set dummy ${ac_tool_prefix}lipo; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_LIPO+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$LIPO"; then
+  ac_cv_prog_LIPO="$LIPO" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_LIPO="${ac_tool_prefix}lipo"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+LIPO=$ac_cv_prog_LIPO
+if test -n "$LIPO"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5
+$as_echo "$LIPO" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_LIPO"; then
+  ac_ct_LIPO=$LIPO
+  # Extract the first word of "lipo", so it can be a program name with args.
+set dummy lipo; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_LIPO+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_LIPO"; then
+  ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_LIPO="lipo"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO
+if test -n "$ac_ct_LIPO"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5
+$as_echo "$ac_ct_LIPO" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_LIPO" = x; then
+    LIPO=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    LIPO=$ac_ct_LIPO
+  fi
+else
+  LIPO="$ac_cv_prog_LIPO"
+fi
+
+    if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args.
+set dummy ${ac_tool_prefix}otool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_OTOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$OTOOL"; then
+  ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_OTOOL="${ac_tool_prefix}otool"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+OTOOL=$ac_cv_prog_OTOOL
+if test -n "$OTOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5
+$as_echo "$OTOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OTOOL"; then
+  ac_ct_OTOOL=$OTOOL
+  # Extract the first word of "otool", so it can be a program name with args.
+set dummy otool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_OTOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_OTOOL"; then
+  ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_OTOOL="otool"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL
+if test -n "$ac_ct_OTOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5
+$as_echo "$ac_ct_OTOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_OTOOL" = x; then
+    OTOOL=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    OTOOL=$ac_ct_OTOOL
+  fi
+else
+  OTOOL="$ac_cv_prog_OTOOL"
+fi
+
+    if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args.
+set dummy ${ac_tool_prefix}otool64; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_OTOOL64+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$OTOOL64"; then
+  ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+OTOOL64=$ac_cv_prog_OTOOL64
+if test -n "$OTOOL64"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5
+$as_echo "$OTOOL64" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OTOOL64"; then
+  ac_ct_OTOOL64=$OTOOL64
+  # Extract the first word of "otool64", so it can be a program name with args.
+set dummy otool64; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_OTOOL64+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_OTOOL64"; then
+  ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_OTOOL64="otool64"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64
+if test -n "$ac_ct_OTOOL64"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5
+$as_echo "$ac_ct_OTOOL64" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_OTOOL64" = x; then
+    OTOOL64=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    OTOOL64=$ac_ct_OTOOL64
+  fi
+else
+  OTOOL64="$ac_cv_prog_OTOOL64"
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5
+$as_echo_n "checking for -single_module linker flag... " >&6; }
+if ${lt_cv_apple_cc_single_mod+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_apple_cc_single_mod=no
+      if test -z "${LT_MULTI_MODULE}"; then
+	# By default we will add the -single_module flag. You can override
+	# by either setting the environment variable LT_MULTI_MODULE
+	# non-empty at configure time, or by adding -multi_module to the
+	# link flags.
+	rm -rf libconftest.dylib*
+	echo "int foo(void){return 1;}" > conftest.c
+	echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+-dynamiclib -Wl,-single_module conftest.c" >&5
+	$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+	  -dynamiclib -Wl,-single_module conftest.c 2>conftest.err
+        _lt_result=$?
+	# If there is a non-empty error log, and "single_module"
+	# appears in it, assume the flag caused a linker warning
+        if test -s conftest.err && $GREP single_module conftest.err; then
+	  cat conftest.err >&5
+	# Otherwise, if the output was created with a 0 exit code from
+	# the compiler, it worked.
+	elif test -f libconftest.dylib && test $_lt_result -eq 0; then
+	  lt_cv_apple_cc_single_mod=yes
+	else
+	  cat conftest.err >&5
+	fi
+	rm -rf libconftest.dylib*
+	rm -f conftest.*
+      fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5
+$as_echo "$lt_cv_apple_cc_single_mod" >&6; }
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5
+$as_echo_n "checking for -exported_symbols_list linker flag... " >&6; }
+if ${lt_cv_ld_exported_symbols_list+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_ld_exported_symbols_list=no
+      save_LDFLAGS=$LDFLAGS
+      echo "_main" > conftest.sym
+      LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym"
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  lt_cv_ld_exported_symbols_list=yes
+else
+  lt_cv_ld_exported_symbols_list=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+	LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5
+$as_echo "$lt_cv_ld_exported_symbols_list" >&6; }
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5
+$as_echo_n "checking for -force_load linker flag... " >&6; }
+if ${lt_cv_ld_force_load+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_ld_force_load=no
+      cat > conftest.c << _LT_EOF
+int forced_loaded() { return 2;}
+_LT_EOF
+      echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5
+      $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5
+      echo "$AR cru libconftest.a conftest.o" >&5
+      $AR cru libconftest.a conftest.o 2>&5
+      echo "$RANLIB libconftest.a" >&5
+      $RANLIB libconftest.a 2>&5
+      cat > conftest.c << _LT_EOF
+int main() { return 0;}
+_LT_EOF
+      echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5
+      $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err
+      _lt_result=$?
+      if test -s conftest.err && $GREP force_load conftest.err; then
+	cat conftest.err >&5
+      elif test -f conftest && test $_lt_result -eq 0 && $GREP forced_load conftest >/dev/null 2>&1 ; then
+	lt_cv_ld_force_load=yes
+      else
+	cat conftest.err >&5
+      fi
+        rm -f conftest.err libconftest.a conftest conftest.c
+        rm -rf conftest.dSYM
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5
+$as_echo "$lt_cv_ld_force_load" >&6; }
+    case $host_os in
+    rhapsody* | darwin1.[012])
+      _lt_dar_allow_undefined='${wl}-undefined ${wl}suppress' ;;
+    darwin1.*)
+      _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+    darwin*) # darwin 5.x on
+      # if running on 10.5 or later, the deployment target defaults
+      # to the OS version, if on x86, and 10.4, the deployment
+      # target defaults to 10.4. Don't you love it?
+      case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in
+	10.0,*86*-darwin8*|10.0,*-darwin[91]*)
+	  _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+	10.[012]*)
+	  _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+	10.*)
+	  _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+      esac
+    ;;
+  esac
+    if test "$lt_cv_apple_cc_single_mod" = "yes"; then
+      _lt_dar_single_mod='$single_module'
+    fi
+    if test "$lt_cv_ld_exported_symbols_list" = "yes"; then
+      _lt_dar_export_syms=' ${wl}-exported_symbols_list,$output_objdir/${libname}-symbols.expsym'
+    else
+      _lt_dar_export_syms='~$NMEDIT -s $output_objdir/${libname}-symbols.expsym ${lib}'
+    fi
+    if test "$DSYMUTIL" != ":" && test "$lt_cv_ld_force_load" = "no"; then
+      _lt_dsymutil='~$DSYMUTIL $lib || :'
+    else
+      _lt_dsymutil=
+    fi
+    ;;
+  esac
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if ${ac_cv_header_stdc+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_header_stdc=yes
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "free" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+  if test "$cross_compiling" = yes; then :
+  :
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+		   (('a' <= (c) && (c) <= 'i') \
+		     || ('j' <= (c) && (c) <= 'r') \
+		     || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+  int i;
+  for (i = 0; i < 256; i++)
+    if (XOR (islower (i), ISLOWER (i))
+	|| toupper (i) != TOUPPER (i))
+      return 2;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+		  inttypes.h stdint.h unistd.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
+"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+for ac_header in dlfcn.h
+do :
+  ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default
+"
+if test "x$ac_cv_header_dlfcn_h" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_DLFCN_H 1
+_ACEOF
+
+fi
+
+done
+
+
+
+
+
+# Set options
+
+
+
+        enable_dlopen=no
+
+
+  enable_win32_dll=no
+
+
+            # Check whether --enable-shared was given.
+if test "${enable_shared+set}" = set; then :
+  enableval=$enable_shared; p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_shared=yes ;;
+    no) enable_shared=no ;;
+    *)
+      enable_shared=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+	IFS="$lt_save_ifs"
+	if test "X$pkg" = "X$p"; then
+	  enable_shared=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac
+else
+  enable_shared=yes
+fi
+
+
+
+
+
+
+
+
+
+  # Check whether --enable-static was given.
+if test "${enable_static+set}" = set; then :
+  enableval=$enable_static; p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_static=yes ;;
+    no) enable_static=no ;;
+    *)
+     enable_static=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+	IFS="$lt_save_ifs"
+	if test "X$pkg" = "X$p"; then
+	  enable_static=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac
+else
+  enable_static=yes
+fi
+
+
+
+
+
+
+
+
+
+
+# Check whether --with-pic was given.
+if test "${with_pic+set}" = set; then :
+  withval=$with_pic; lt_p=${PACKAGE-default}
+    case $withval in
+    yes|no) pic_mode=$withval ;;
+    *)
+      pic_mode=default
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for lt_pkg in $withval; do
+	IFS="$lt_save_ifs"
+	if test "X$lt_pkg" = "X$lt_p"; then
+	  pic_mode=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac
+else
+  pic_mode=default
+fi
+
+
+test -z "$pic_mode" && pic_mode=default
+
+
+
+
+
+
+
+  # Check whether --enable-fast-install was given.
+if test "${enable_fast_install+set}" = set; then :
+  enableval=$enable_fast_install; p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_fast_install=yes ;;
+    no) enable_fast_install=no ;;
+    *)
+      enable_fast_install=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+	IFS="$lt_save_ifs"
+	if test "X$pkg" = "X$p"; then
+	  enable_fast_install=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac
+else
+  enable_fast_install=yes
+fi
+
+
+
+
+
+
+
+
+
+
+
+# This can be used to rebuild libtool when needed
+LIBTOOL_DEPS="$ltmain"
+
+# Always use our own libtool.
+LIBTOOL='$(SHELL) $(top_builddir)/libtool'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+test -z "$LN_S" && LN_S="ln -s"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+if test -n "${ZSH_VERSION+set}" ; then
+   setopt NO_GLOB_SUBST
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5
+$as_echo_n "checking for objdir... " >&6; }
+if ${lt_cv_objdir+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  rm -f .libs 2>/dev/null
+mkdir .libs 2>/dev/null
+if test -d .libs; then
+  lt_cv_objdir=.libs
+else
+  # MS-DOS does not allow filenames that begin with a dot.
+  lt_cv_objdir=_libs
+fi
+rmdir .libs 2>/dev/null
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5
+$as_echo "$lt_cv_objdir" >&6; }
+objdir=$lt_cv_objdir
+
+
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define LT_OBJDIR "$lt_cv_objdir/"
+_ACEOF
+
+
+
+
+case $host_os in
+aix3*)
+  # AIX sometimes has problems with the GCC collect2 program.  For some
+  # reason, if we set the COLLECT_NAMES environment variable, the problems
+  # vanish in a puff of smoke.
+  if test "X${COLLECT_NAMES+set}" != Xset; then
+    COLLECT_NAMES=
+    export COLLECT_NAMES
+  fi
+  ;;
+esac
+
+# Global variables:
+ofile=libtool
+can_build_shared=yes
+
+# All known linkers require a `.a' archive for static linking (except MSVC,
+# which needs '.lib').
+libext=a
+
+with_gnu_ld="$lt_cv_prog_gnu_ld"
+
+old_CC="$CC"
+old_CFLAGS="$CFLAGS"
+
+# Set sane defaults for various variables
+test -z "$CC" && CC=cc
+test -z "$LTCC" && LTCC=$CC
+test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS
+test -z "$LD" && LD=ld
+test -z "$ac_objext" && ac_objext=o
+
+for cc_temp in $compiler""; do
+  case $cc_temp in
+    compile | *[\\/]compile | ccache | *[\\/]ccache ) ;;
+    distcc | *[\\/]distcc | purify | *[\\/]purify ) ;;
+    \-*) ;;
+    *) break;;
+  esac
+done
+cc_basename=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+
+
+# Only perform the check for file, if the check method requires it
+test -z "$MAGIC_CMD" && MAGIC_CMD=file
+case $deplibs_check_method in
+file_magic*)
+  if test "$file_magic_cmd" = '$MAGIC_CMD'; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5
+$as_echo_n "checking for ${ac_tool_prefix}file... " >&6; }
+if ${lt_cv_path_MAGIC_CMD+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $MAGIC_CMD in
+[\\/*] |  ?:[\\/]*)
+  lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+  ;;
+*)
+  lt_save_MAGIC_CMD="$MAGIC_CMD"
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+  ac_dummy="/usr/bin$PATH_SEPARATOR$PATH"
+  for ac_dir in $ac_dummy; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/${ac_tool_prefix}file; then
+      lt_cv_path_MAGIC_CMD="$ac_dir/${ac_tool_prefix}file"
+      if test -n "$file_magic_test_file"; then
+	case $deplibs_check_method in
+	"file_magic "*)
+	  file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+	  MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+	  if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+	    $EGREP "$file_magic_regex" > /dev/null; then
+	    :
+	  else
+	    cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such.  This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem.  Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** [email-masked]
+
+_LT_EOF
+	  fi ;;
+	esac
+      fi
+      break
+    fi
+  done
+  IFS="$lt_save_ifs"
+  MAGIC_CMD="$lt_save_MAGIC_CMD"
+  ;;
+esac
+fi
+
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5
+$as_echo "$MAGIC_CMD" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+
+
+if test -z "$lt_cv_path_MAGIC_CMD"; then
+  if test -n "$ac_tool_prefix"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for file" >&5
+$as_echo_n "checking for file... " >&6; }
+if ${lt_cv_path_MAGIC_CMD+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $MAGIC_CMD in
+[\\/*] |  ?:[\\/]*)
+  lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+  ;;
+*)
+  lt_save_MAGIC_CMD="$MAGIC_CMD"
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+  ac_dummy="/usr/bin$PATH_SEPARATOR$PATH"
+  for ac_dir in $ac_dummy; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/file; then
+      lt_cv_path_MAGIC_CMD="$ac_dir/file"
+      if test -n "$file_magic_test_file"; then
+	case $deplibs_check_method in
+	"file_magic "*)
+	  file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+	  MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+	  if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+	    $EGREP "$file_magic_regex" > /dev/null; then
+	    :
+	  else
+	    cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such.  This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem.  Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** [email-masked]
+
+_LT_EOF
+	  fi ;;
+	esac
+      fi
+      break
+    fi
+  done
+  IFS="$lt_save_ifs"
+  MAGIC_CMD="$lt_save_MAGIC_CMD"
+  ;;
+esac
+fi
+
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5
+$as_echo "$MAGIC_CMD" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  else
+    MAGIC_CMD=:
+  fi
+fi
+
+  fi
+  ;;
+esac
+
+# Use C for the default configuration in the libtool script
+
+lt_save_CC="$CC"
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+# Source file extension for C test sources.
+ac_ext=c
+
+# Object file extension for compiled C test sources.
+objext=o
+objext=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="int some_variable = 0;"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='int main(){return(0);}'
+
+
+
+
+
+
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+
+# Save the default compiler, since it gets overwritten when the other
+# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP.
+compiler_DEFAULT=$CC
+
+# save warnings/boilerplate of simple test code
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_compile_test_code" >conftest.$ac_ext
+eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_compiler_boilerplate=`cat conftest.err`
+$RM conftest*
+
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_link_test_code" >conftest.$ac_ext
+eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_linker_boilerplate=`cat conftest.err`
+$RM -r conftest*
+
+
+if test -n "$compiler"; then
+
+lt_prog_compiler_no_builtin_flag=
+
+if test "$GCC" = yes; then
+  case $cc_basename in
+  nvcc*)
+    lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;;
+  *)
+    lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;;
+  esac
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5
+$as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; }
+if ${lt_cv_prog_compiler_rtti_exceptions+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_rtti_exceptions=no
+   ac_outfile=conftest.$ac_objext
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+   lt_compiler_flag="-fno-rtti -fno-exceptions"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   # The option is referenced via a variable to avoid confusing sed.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>conftest.err)
+   ac_status=$?
+   cat conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s "$ac_outfile"; then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings other than the usual output.
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+     $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+     if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_rtti_exceptions=yes
+     fi
+   fi
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5
+$as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; }
+
+if test x"$lt_cv_prog_compiler_rtti_exceptions" = xyes; then
+    lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions"
+else
+    :
+fi
+
+fi
+
+
+
+
+
+
+  lt_prog_compiler_wl=
+lt_prog_compiler_pic=
+lt_prog_compiler_static=
+
+
+  if test "$GCC" = yes; then
+    lt_prog_compiler_wl='-Wl,'
+    lt_prog_compiler_static='-static'
+
+    case $host_os in
+      aix*)
+      # All AIX code is PIC.
+      if test "$host_cpu" = ia64; then
+	# AIX 5 now supports IA64 processor
+	lt_prog_compiler_static='-Bstatic'
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            lt_prog_compiler_pic='-fPIC'
+        ;;
+      m68k)
+            # FIXME: we need at least 68020 code to build shared libraries, but
+            # adding the `-m68020' flag to GCC prevents building anything better,
+            # like `-m68040'.
+            lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4'
+        ;;
+      esac
+      ;;
+
+    beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+      # PIC is the default for these OSes.
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      # Although the cygwin gcc ignores -fPIC, still need this for old-style
+      # (--disable-auto-import) libraries
+      lt_prog_compiler_pic='-DDLL_EXPORT'
+      ;;
+
+    darwin* | rhapsody*)
+      # PIC is the default on this platform
+      # Common symbols not allowed in MH_DYLIB files
+      lt_prog_compiler_pic='-fno-common'
+      ;;
+
+    haiku*)
+      # PIC is the default for Haiku.
+      # The "-static" flag exists, but is broken.
+      lt_prog_compiler_static=
+      ;;
+
+    hpux*)
+      # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+      # PA HP-UX.  On IA64 HP-UX, PIC is the default but the pic flag
+      # sets the default TLS model and affects inlining.
+      case $host_cpu in
+      hppa*64*)
+	# +Z the default
+	;;
+      *)
+	lt_prog_compiler_pic='-fPIC'
+	;;
+      esac
+      ;;
+
+    interix[3-9]*)
+      # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+      # Instead, we relocate shared libraries at runtime.
+      ;;
+
+    msdosdjgpp*)
+      # Just because we use GCC doesn't mean we suddenly get shared libraries
+      # on systems that don't support them.
+      lt_prog_compiler_can_build_shared=no
+      enable_shared=no
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      lt_prog_compiler_pic='-fPIC -shared'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+	lt_prog_compiler_pic=-Kconform_pic
+      fi
+      ;;
+
+    *)
+      lt_prog_compiler_pic='-fPIC'
+      ;;
+    esac
+
+    case $cc_basename in
+    nvcc*) # Cuda Compiler Driver 2.2
+      lt_prog_compiler_wl='-Xlinker '
+      if test -n "$lt_prog_compiler_pic"; then
+        lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic"
+      fi
+      ;;
+    esac
+  else
+    # PORTME Check for flag to pass linker flags through the system compiler.
+    case $host_os in
+    aix*)
+      lt_prog_compiler_wl='-Wl,'
+      if test "$host_cpu" = ia64; then
+	# AIX 5 now supports IA64 processor
+	lt_prog_compiler_static='-Bstatic'
+      else
+	lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp'
+      fi
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      lt_prog_compiler_pic='-DDLL_EXPORT'
+      ;;
+
+    hpux9* | hpux10* | hpux11*)
+      lt_prog_compiler_wl='-Wl,'
+      # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but
+      # not for PA HP-UX.
+      case $host_cpu in
+      hppa*64*|ia64*)
+	# +Z the default
+	;;
+      *)
+	lt_prog_compiler_pic='+Z'
+	;;
+      esac
+      # Is there a better lt_prog_compiler_static that works with the bundled CC?
+      lt_prog_compiler_static='${wl}-a ${wl}archive'
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      lt_prog_compiler_wl='-Wl,'
+      # PIC (with -KPIC) is the default.
+      lt_prog_compiler_static='-non_shared'
+      ;;
+
+    linux* | k*bsd*-gnu | kopensolaris*-gnu)
+      case $cc_basename in
+      # old Intel for x86_64 which still supported -KPIC.
+      ecc*)
+	lt_prog_compiler_wl='-Wl,'
+	lt_prog_compiler_pic='-KPIC'
+	lt_prog_compiler_static='-static'
+        ;;
+      # icc used to be incompatible with GCC.
+      # ICC 10 doesn't accept -KPIC any more.
+      icc* | ifort*)
+	lt_prog_compiler_wl='-Wl,'
+	lt_prog_compiler_pic='-fPIC'
+	lt_prog_compiler_static='-static'
+        ;;
+      # Lahey Fortran 8.1.
+      lf95*)
+	lt_prog_compiler_wl='-Wl,'
+	lt_prog_compiler_pic='--shared'
+	lt_prog_compiler_static='--static'
+	;;
+      nagfor*)
+	# NAG Fortran compiler
+	lt_prog_compiler_wl='-Wl,-Wl,,'
+	lt_prog_compiler_pic='-PIC'
+	lt_prog_compiler_static='-Bstatic'
+	;;
+      pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*)
+        # Portland Group compilers (*not* the Pentium gcc compiler,
+	# which looks to be a dead project)
+	lt_prog_compiler_wl='-Wl,'
+	lt_prog_compiler_pic='-fpic'
+	lt_prog_compiler_static='-Bstatic'
+        ;;
+      ccc*)
+        lt_prog_compiler_wl='-Wl,'
+        # All Alpha code is PIC.
+        lt_prog_compiler_static='-non_shared'
+        ;;
+      xl* | bgxl* | bgf* | mpixl*)
+	# IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene
+	lt_prog_compiler_wl='-Wl,'
+	lt_prog_compiler_pic='-qpic'
+	lt_prog_compiler_static='-qstaticlink'
+	;;
+      *)
+	case `$CC -V 2>&1 | sed 5q` in
+	*Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*)
+	  # Sun Fortran 8.3 passes all unrecognized flags to the linker
+	  lt_prog_compiler_pic='-KPIC'
+	  lt_prog_compiler_static='-Bstatic'
+	  lt_prog_compiler_wl=''
+	  ;;
+	*Sun\ F* | *Sun*Fortran*)
+	  lt_prog_compiler_pic='-KPIC'
+	  lt_prog_compiler_static='-Bstatic'
+	  lt_prog_compiler_wl='-Qoption ld '
+	  ;;
+	*Sun\ C*)
+	  # Sun C 5.9
+	  lt_prog_compiler_pic='-KPIC'
+	  lt_prog_compiler_static='-Bstatic'
+	  lt_prog_compiler_wl='-Wl,'
+	  ;;
+        *Intel*\ [CF]*Compiler*)
+	  lt_prog_compiler_wl='-Wl,'
+	  lt_prog_compiler_pic='-fPIC'
+	  lt_prog_compiler_static='-static'
+	  ;;
+	*Portland\ Group*)
+	  lt_prog_compiler_wl='-Wl,'
+	  lt_prog_compiler_pic='-fpic'
+	  lt_prog_compiler_static='-Bstatic'
+	  ;;
+	esac
+	;;
+      esac
+      ;;
+
+    newsos6)
+      lt_prog_compiler_pic='-KPIC'
+      lt_prog_compiler_static='-Bstatic'
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      lt_prog_compiler_pic='-fPIC -shared'
+      ;;
+
+    osf3* | osf4* | osf5*)
+      lt_prog_compiler_wl='-Wl,'
+      # All OSF/1 code is PIC.
+      lt_prog_compiler_static='-non_shared'
+      ;;
+
+    rdos*)
+      lt_prog_compiler_static='-non_shared'
+      ;;
+
+    solaris*)
+      lt_prog_compiler_pic='-KPIC'
+      lt_prog_compiler_static='-Bstatic'
+      case $cc_basename in
+      f77* | f90* | f95* | sunf77* | sunf90* | sunf95*)
+	lt_prog_compiler_wl='-Qoption ld ';;
+      *)
+	lt_prog_compiler_wl='-Wl,';;
+      esac
+      ;;
+
+    sunos4*)
+      lt_prog_compiler_wl='-Qoption ld '
+      lt_prog_compiler_pic='-PIC'
+      lt_prog_compiler_static='-Bstatic'
+      ;;
+
+    sysv4 | sysv4.2uw2* | sysv4.3*)
+      lt_prog_compiler_wl='-Wl,'
+      lt_prog_compiler_pic='-KPIC'
+      lt_prog_compiler_static='-Bstatic'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec ;then
+	lt_prog_compiler_pic='-Kconform_pic'
+	lt_prog_compiler_static='-Bstatic'
+      fi
+      ;;
+
+    sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+      lt_prog_compiler_wl='-Wl,'
+      lt_prog_compiler_pic='-KPIC'
+      lt_prog_compiler_static='-Bstatic'
+      ;;
+
+    unicos*)
+      lt_prog_compiler_wl='-Wl,'
+      lt_prog_compiler_can_build_shared=no
+      ;;
+
+    uts4*)
+      lt_prog_compiler_pic='-pic'
+      lt_prog_compiler_static='-Bstatic'
+      ;;
+
+    *)
+      lt_prog_compiler_can_build_shared=no
+      ;;
+    esac
+  fi
+
+case $host_os in
+  # For platforms which do not support PIC, -DPIC is meaningless:
+  *djgpp*)
+    lt_prog_compiler_pic=
+    ;;
+  *)
+    lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC"
+    ;;
+esac
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5
+$as_echo_n "checking for $compiler option to produce PIC... " >&6; }
+if ${lt_cv_prog_compiler_pic+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_pic=$lt_prog_compiler_pic
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5
+$as_echo "$lt_cv_prog_compiler_pic" >&6; }
+lt_prog_compiler_pic=$lt_cv_prog_compiler_pic
+
+#
+# Check to make sure the PIC flag actually works.
+#
+if test -n "$lt_prog_compiler_pic"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5
+$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; }
+if ${lt_cv_prog_compiler_pic_works+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_pic_works=no
+   ac_outfile=conftest.$ac_objext
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+   lt_compiler_flag="$lt_prog_compiler_pic -DPIC"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   # The option is referenced via a variable to avoid confusing sed.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>conftest.err)
+   ac_status=$?
+   cat conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s "$ac_outfile"; then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings other than the usual output.
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+     $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+     if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_pic_works=yes
+     fi
+   fi
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5
+$as_echo "$lt_cv_prog_compiler_pic_works" >&6; }
+
+if test x"$lt_cv_prog_compiler_pic_works" = xyes; then
+    case $lt_prog_compiler_pic in
+     "" | " "*) ;;
+     *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;;
+     esac
+else
+    lt_prog_compiler_pic=
+     lt_prog_compiler_can_build_shared=no
+fi
+
+fi
+
+
+
+
+
+
+
+
+
+
+
+#
+# Check to make sure the static flag actually works.
+#
+wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5
+$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; }
+if ${lt_cv_prog_compiler_static_works+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_static_works=no
+   save_LDFLAGS="$LDFLAGS"
+   LDFLAGS="$LDFLAGS $lt_tmp_static_flag"
+   echo "$lt_simple_link_test_code" > conftest.$ac_ext
+   if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+     # The linker can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     if test -s conftest.err; then
+       # Append any errors to the config.log.
+       cat conftest.err 1>&5
+       $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+       $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+       if diff conftest.exp conftest.er2 >/dev/null; then
+         lt_cv_prog_compiler_static_works=yes
+       fi
+     else
+       lt_cv_prog_compiler_static_works=yes
+     fi
+   fi
+   $RM -r conftest*
+   LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5
+$as_echo "$lt_cv_prog_compiler_static_works" >&6; }
+
+if test x"$lt_cv_prog_compiler_static_works" = xyes; then
+    :
+else
+    lt_prog_compiler_static=
+fi
+
+
+
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if ${lt_cv_prog_compiler_c_o+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_c_o=no
+   $RM -r conftest 2>/dev/null
+   mkdir conftest
+   cd conftest
+   mkdir out
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+   lt_compiler_flag="-o out/conftest2.$ac_objext"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>out/conftest.err)
+   ac_status=$?
+   cat out/conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s out/conftest2.$ac_objext
+   then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+     $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+     if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_c_o=yes
+     fi
+   fi
+   chmod u+w . 2>&5
+   $RM conftest*
+   # SGI C++ compiler will create directory out/ii_files/ for
+   # template instantiation
+   test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+   $RM out/* && rmdir out
+   cd ..
+   $RM -r conftest
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5
+$as_echo "$lt_cv_prog_compiler_c_o" >&6; }
+
+
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if ${lt_cv_prog_compiler_c_o+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_c_o=no
+   $RM -r conftest 2>/dev/null
+   mkdir conftest
+   cd conftest
+   mkdir out
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+   lt_compiler_flag="-o out/conftest2.$ac_objext"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>out/conftest.err)
+   ac_status=$?
+   cat out/conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s out/conftest2.$ac_objext
+   then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+     $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+     if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_c_o=yes
+     fi
+   fi
+   chmod u+w . 2>&5
+   $RM conftest*
+   # SGI C++ compiler will create directory out/ii_files/ for
+   # template instantiation
+   test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+   $RM out/* && rmdir out
+   cd ..
+   $RM -r conftest
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5
+$as_echo "$lt_cv_prog_compiler_c_o" >&6; }
+
+
+
+
+hard_links="nottested"
+if test "$lt_cv_prog_compiler_c_o" = no && test "$need_locks" != no; then
+  # do not overwrite the value of need_locks provided by the user
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5
+$as_echo_n "checking if we can lock with hard links... " >&6; }
+  hard_links=yes
+  $RM conftest*
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  touch conftest.a
+  ln conftest.a conftest.b 2>&5 || hard_links=no
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5
+$as_echo "$hard_links" >&6; }
+  if test "$hard_links" = no; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5
+$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;}
+    need_locks=warn
+  fi
+else
+  need_locks=no
+fi
+
+
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5
+$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; }
+
+  runpath_var=
+  allow_undefined_flag=
+  always_export_symbols=no
+  archive_cmds=
+  archive_expsym_cmds=
+  compiler_needs_object=no
+  enable_shared_with_static_runtimes=no
+  export_dynamic_flag_spec=
+  export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+  hardcode_automatic=no
+  hardcode_direct=no
+  hardcode_direct_absolute=no
+  hardcode_libdir_flag_spec=
+  hardcode_libdir_separator=
+  hardcode_minus_L=no
+  hardcode_shlibpath_var=unsupported
+  inherit_rpath=no
+  link_all_deplibs=unknown
+  module_cmds=
+  module_expsym_cmds=
+  old_archive_from_new_cmds=
+  old_archive_from_expsyms_cmds=
+  thread_safe_flag_spec=
+  whole_archive_flag_spec=
+  # include_expsyms should be a list of space-separated symbols to be *always*
+  # included in the symbol list
+  include_expsyms=
+  # exclude_expsyms can be an extended regexp of symbols to exclude
+  # it will be wrapped by ` (' and `)$', so one must not match beginning or
+  # end of line.  Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc',
+  # as well as any symbol that contains `d'.
+  exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'
+  # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+  # platforms (ab)use it in PIC code, but their linkers get confused if
+  # the symbol is explicitly referenced.  Since portable code cannot
+  # rely on this symbol name, it's probably fine to never include it in
+  # preloaded symbol tables.
+  # Exclude shared library initialization/finalization symbols.
+  extract_expsyms_cmds=
+
+  case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    # FIXME: the MSVC++ port hasn't been tested in a loooong time
+    # When not using gcc, we currently assume that we are using
+    # Microsoft Visual C++.
+    if test "$GCC" != yes; then
+      with_gnu_ld=no
+    fi
+    ;;
+  interix*)
+    # we just hope/assume this is gcc and not c89 (= MSVC++)
+    with_gnu_ld=yes
+    ;;
+  openbsd*)
+    with_gnu_ld=no
+    ;;
+  esac
+
+  ld_shlibs=yes
+
+  # On some targets, GNU ld is compatible enough with the native linker
+  # that we're better off using the native interface for both.
+  lt_use_gnu_ld_interface=no
+  if test "$with_gnu_ld" = yes; then
+    case $host_os in
+      aix*)
+	# The AIX port of GNU ld has always aspired to compatibility
+	# with the native linker.  However, as the warning in the GNU ld
+	# block says, versions before 2.19.5* couldn't really create working
+	# shared libraries, regardless of the interface used.
+	case `$LD -v 2>&1` in
+	  *\ \(GNU\ Binutils\)\ 2.19.5*) ;;
+	  *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;;
+	  *\ \(GNU\ Binutils\)\ [3-9]*) ;;
+	  *)
+	    lt_use_gnu_ld_interface=yes
+	    ;;
+	esac
+	;;
+      *)
+	lt_use_gnu_ld_interface=yes
+	;;
+    esac
+  fi
+
+  if test "$lt_use_gnu_ld_interface" = yes; then
+    # If archive_cmds runs LD, not CC, wlarc should be empty
+    wlarc='${wl}'
+
+    # Set some defaults for GNU ld with shared library support. These
+    # are reset later if shared libraries are not supported. Putting them
+    # here allows them to be overridden if necessary.
+    runpath_var=LD_RUN_PATH
+    hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+    export_dynamic_flag_spec='${wl}--export-dynamic'
+    # ancient GNU ld didn't support --whole-archive et. al.
+    if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then
+      whole_archive_flag_spec="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+    else
+      whole_archive_flag_spec=
+    fi
+    supports_anon_versioning=no
+    case `$LD -v 2>&1` in
+      *GNU\ gold*) supports_anon_versioning=yes ;;
+      *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11
+      *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ...
+      *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ...
+      *\ 2.11.*) ;; # other 2.11 versions
+      *) supports_anon_versioning=yes ;;
+    esac
+
+    # See if GNU ld supports shared libraries.
+    case $host_os in
+    aix[3-9]*)
+      # On AIX/PPC, the GNU linker is very broken
+      if test "$host_cpu" != ia64; then
+	ld_shlibs=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.19, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support.  If you
+*** really care for shared libraries, you may want to install binutils
+*** 2.20 or above, or modify your PATH so that a non-GNU linker is found.
+*** You will then need to restart the configuration process.
+
+_LT_EOF
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            archive_expsym_cmds=''
+        ;;
+      m68k)
+            archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            hardcode_libdir_flag_spec='-L$libdir'
+            hardcode_minus_L=yes
+        ;;
+      esac
+      ;;
+
+    beos*)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	allow_undefined_flag=unsupported
+	# Joseph Beckenbach <[email-masked]> says some releases of gcc
+	# support --undefined.  This deserves some investigation.  FIXME
+	archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      else
+	ld_shlibs=no
+      fi
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless,
+      # as there is no search path for DLLs.
+      hardcode_libdir_flag_spec='-L$libdir'
+      export_dynamic_flag_spec='${wl}--export-all-symbols'
+      allow_undefined_flag=unsupported
+      always_export_symbols=no
+      enable_shared_with_static_runtimes=yes
+      export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols'
+      exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'
+
+      if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+        archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+	# If the export-symbols file already is a .def file (1st line
+	# is EXPORTS), use it as is; otherwise, prepend...
+	archive_expsym_cmds='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	  cp $export_symbols $output_objdir/$soname.def;
+	else
+	  echo EXPORTS > $output_objdir/$soname.def;
+	  cat $export_symbols >> $output_objdir/$soname.def;
+	fi~
+	$CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+      else
+	ld_shlibs=no
+      fi
+      ;;
+
+    haiku*)
+      archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      link_all_deplibs=yes
+      ;;
+
+    interix[3-9]*)
+      hardcode_direct=no
+      hardcode_shlibpath_var=no
+      hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+      export_dynamic_flag_spec='${wl}-E'
+      # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+      # Instead, shared libraries are loaded at an image base (0x10000000 by
+      # default) and relocated if they conflict, which is a slow very memory
+      # consuming and fragmenting process.  To avoid this, we pick a random,
+      # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+      # time.  Moving up from 0x10000000 also allows more sbrk(2) space.
+      archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      archive_expsym_cmds='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      ;;
+
+    gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu)
+      tmp_diet=no
+      if test "$host_os" = linux-dietlibc; then
+	case $cc_basename in
+	  diet\ *) tmp_diet=yes;;	# linux-dietlibc with static linking (!diet-dyn)
+	esac
+      fi
+      if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \
+	 && test "$tmp_diet" = no
+      then
+	tmp_addflag=' $pic_flag'
+	tmp_sharedflag='-shared'
+	case $cc_basename,$host_cpu in
+        pgcc*)				# Portland Group C compiler
+	  whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  tmp_addflag=' $pic_flag'
+	  ;;
+	pgf77* | pgf90* | pgf95* | pgfortran*)
+					# Portland Group f77 and f90 compilers
+	  whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  tmp_addflag=' $pic_flag -Mnomain' ;;
+	ecc*,ia64* | icc*,ia64*)	# Intel C compiler on ia64
+	  tmp_addflag=' -i_dynamic' ;;
+	efc*,ia64* | ifort*,ia64*)	# Intel Fortran compiler on ia64
+	  tmp_addflag=' -i_dynamic -nofor_main' ;;
+	ifc* | ifort*)			# Intel Fortran compiler
+	  tmp_addflag=' -nofor_main' ;;
+	lf95*)				# Lahey Fortran 8.1
+	  whole_archive_flag_spec=
+	  tmp_sharedflag='--shared' ;;
+	xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below)
+	  tmp_sharedflag='-qmkshrobj'
+	  tmp_addflag= ;;
+	nvcc*)	# Cuda Compiler Driver 2.2
+	  whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  compiler_needs_object=yes
+	  ;;
+	esac
+	case `$CC -V 2>&1 | sed 5q` in
+	*Sun\ C*)			# Sun C 5.9
+	  whole_archive_flag_spec='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  compiler_needs_object=yes
+	  tmp_sharedflag='-G' ;;
+	*Sun\ F*)			# Sun Fortran 8.3
+	  tmp_sharedflag='-G' ;;
+	esac
+	archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+
+        if test "x$supports_anon_versioning" = xyes; then
+          archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~
+	    cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+	    echo "local: *; };" >> $output_objdir/$libname.ver~
+	    $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+        fi
+
+	case $cc_basename in
+	xlf* | bgf* | bgxlf* | mpixlf*)
+	  # IBM XL Fortran 10.1 on PPC cannot create shared libs itself
+	  whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive'
+	  hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+	  archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib'
+	  if test "x$supports_anon_versioning" = xyes; then
+	    archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~
+	      cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+	      echo "local: *; };" >> $output_objdir/$libname.ver~
+	      $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib'
+	  fi
+	  ;;
+	esac
+      else
+        ld_shlibs=no
+      fi
+      ;;
+
+    netbsd*)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+	archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+	wlarc=
+      else
+	archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      fi
+      ;;
+
+    solaris*)
+      if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then
+	ld_shlibs=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+      elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+	ld_shlibs=no
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
+      case `$LD -v 2>&1` in
+        *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*)
+	ld_shlibs=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not
+*** reliably create shared libraries on SCO systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.16.91.0.3 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+	;;
+	*)
+	  # For security reasons, it is highly recommended that you always
+	  # use absolute paths for naming shared libraries, and exclude the
+	  # DT_RUNPATH tag from executables and libraries.  But doing so
+	  # requires that you compile everything twice, which is a pain.
+	  if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	    hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+	    archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	    archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+	  else
+	    ld_shlibs=no
+	  fi
+	;;
+      esac
+      ;;
+
+    sunos4*)
+      archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      wlarc=
+      hardcode_direct=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    *)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+	ld_shlibs=no
+      fi
+      ;;
+    esac
+
+    if test "$ld_shlibs" = no; then
+      runpath_var=
+      hardcode_libdir_flag_spec=
+      export_dynamic_flag_spec=
+      whole_archive_flag_spec=
+    fi
+  else
+    # PORTME fill in a description of your system's linker (not GNU ld)
+    case $host_os in
+    aix3*)
+      allow_undefined_flag=unsupported
+      always_export_symbols=yes
+      archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+      # Note: this linker hardcodes the directories in LIBPATH if there
+      # are no directories specified by -L.
+      hardcode_minus_L=yes
+      if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then
+	# Neither direct hardcoding nor static linking is supported with a
+	# broken collect2.
+	hardcode_direct=unsupported
+      fi
+      ;;
+
+    aix[4-9]*)
+      if test "$host_cpu" = ia64; then
+	# On IA64, the linker does run time linking by default, so we don't
+	# have to do anything special.
+	aix_use_runtimelinking=no
+	exp_sym_flag='-Bexport'
+	no_entry_flag=""
+      else
+	# If we're using GNU nm, then we don't want the "-C" option.
+	# -C means demangle to AIX nm, but means don't demangle with GNU nm
+	# Also, AIX nm treats weak defined symbols like other global
+	# defined symbols, whereas GNU nm marks them as "W".
+	if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+	  export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+	else
+	  export_symbols_cmds='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+	fi
+	aix_use_runtimelinking=no
+
+	# Test if we are trying to use run time linking or normal
+	# AIX style linking. If -brtl is somewhere in LDFLAGS, we
+	# need to do runtime linking.
+	case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*)
+	  for ld_flag in $LDFLAGS; do
+	  if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
+	    aix_use_runtimelinking=yes
+	    break
+	  fi
+	  done
+	  ;;
+	esac
+
+	exp_sym_flag='-bexport'
+	no_entry_flag='-bnoentry'
+      fi
+
+      # When large executables or shared objects are built, AIX ld can
+      # have problems creating the table of contents.  If linking a library
+      # or program results in "error TOC overflow" add -mminimal-toc to
+      # CXXFLAGS/CFLAGS for g++/gcc.  In the cases where that is not
+      # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+      archive_cmds=''
+      hardcode_direct=yes
+      hardcode_direct_absolute=yes
+      hardcode_libdir_separator=':'
+      link_all_deplibs=yes
+      file_list_spec='${wl}-f,'
+
+      if test "$GCC" = yes; then
+	case $host_os in aix4.[012]|aix4.[012].*)
+	# We only want to do this on AIX 4.2 and lower, the check
+	# below for broken collect2 doesn't work under 4.3+
+	  collect2name=`${CC} -print-prog-name=collect2`
+	  if test -f "$collect2name" &&
+	   strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+	  then
+	  # We have reworked collect2
+	  :
+	  else
+	  # We have old collect2
+	  hardcode_direct=unsupported
+	  # It fails to find uninstalled libraries when the uninstalled
+	  # path is not listed in the libpath.  Setting hardcode_minus_L
+	  # to unsupported forces relinking
+	  hardcode_minus_L=yes
+	  hardcode_libdir_flag_spec='-L$libdir'
+	  hardcode_libdir_separator=
+	  fi
+	  ;;
+	esac
+	shared_flag='-shared'
+	if test "$aix_use_runtimelinking" = yes; then
+	  shared_flag="$shared_flag "'${wl}-G'
+	fi
+      else
+	# not using gcc
+	if test "$host_cpu" = ia64; then
+	# VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+	# chokes on -Wl,-G. The following line is correct:
+	  shared_flag='-G'
+	else
+	  if test "$aix_use_runtimelinking" = yes; then
+	    shared_flag='${wl}-G'
+	  else
+	    shared_flag='${wl}-bM:SRE'
+	  fi
+	fi
+      fi
+
+      export_dynamic_flag_spec='${wl}-bexpall'
+      # It seems that -bexpall does not export symbols beginning with
+      # underscore (_), so it is better to generate a list of symbols to export.
+      always_export_symbols=yes
+      if test "$aix_use_runtimelinking" = yes; then
+	# Warning - without using the other runtime loading flags (-brtl),
+	# -berok will link without error, but may produce a broken library.
+	allow_undefined_flag='-berok'
+        # Determine the default libpath from the value encoded in an
+        # empty executable.
+        if test "${lt_cv_aix_libpath+set}" = set; then
+  aix_libpath=$lt_cv_aix_libpath
+else
+  if ${lt_cv_aix_libpath_+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+
+  lt_aix_libpath_sed='
+      /Import File Strings/,/^$/ {
+	  /^0/ {
+	      s/^0  *\([^ ]*\) *$/\1/
+	      p
+	  }
+      }'
+  lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  # Check for a 64-bit object if we didn't find anything.
+  if test -z "$lt_cv_aix_libpath_"; then
+    lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+  if test -z "$lt_cv_aix_libpath_"; then
+    lt_cv_aix_libpath_="/usr/lib:/lib"
+  fi
+
+fi
+
+  aix_libpath=$lt_cv_aix_libpath_
+fi
+
+        hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath"
+        archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+      else
+	if test "$host_cpu" = ia64; then
+	  hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib'
+	  allow_undefined_flag="-z nodefs"
+	  archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+	else
+	 # Determine the default libpath from the value encoded in an
+	 # empty executable.
+	 if test "${lt_cv_aix_libpath+set}" = set; then
+  aix_libpath=$lt_cv_aix_libpath
+else
+  if ${lt_cv_aix_libpath_+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+
+  lt_aix_libpath_sed='
+      /Import File Strings/,/^$/ {
+	  /^0/ {
+	      s/^0  *\([^ ]*\) *$/\1/
+	      p
+	  }
+      }'
+  lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  # Check for a 64-bit object if we didn't find anything.
+  if test -z "$lt_cv_aix_libpath_"; then
+    lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+  if test -z "$lt_cv_aix_libpath_"; then
+    lt_cv_aix_libpath_="/usr/lib:/lib"
+  fi
+
+fi
+
+  aix_libpath=$lt_cv_aix_libpath_
+fi
+
+	 hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath"
+	  # Warning - without using the other run time loading flags,
+	  # -berok will link without error, but may produce a broken library.
+	  no_undefined_flag=' ${wl}-bernotok'
+	  allow_undefined_flag=' ${wl}-berok'
+	  if test "$with_gnu_ld" = yes; then
+	    # We only use this code for GNU lds that support --whole-archive.
+	    whole_archive_flag_spec='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+	  else
+	    # Exported symbols can be pulled into shared objects from archives
+	    whole_archive_flag_spec='$convenience'
+	  fi
+	  archive_cmds_need_lc=yes
+	  # This is similar to how AIX traditionally builds its shared libraries.
+	  archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+	fi
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            archive_expsym_cmds=''
+        ;;
+      m68k)
+            archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            hardcode_libdir_flag_spec='-L$libdir'
+            hardcode_minus_L=yes
+        ;;
+      esac
+      ;;
+
+    bsdi[45]*)
+      export_dynamic_flag_spec=-rdynamic
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # When not using gcc, we currently assume that we are using
+      # Microsoft Visual C++.
+      # hardcode_libdir_flag_spec is actually meaningless, as there is
+      # no search path for DLLs.
+      case $cc_basename in
+      cl*)
+	# Native MSVC
+	hardcode_libdir_flag_spec=' '
+	allow_undefined_flag=unsupported
+	always_export_symbols=yes
+	file_list_spec='@'
+	# Tell ltmain to make .lib files, not .a files.
+	libext=lib
+	# Tell ltmain to make .dll files, not .so files.
+	shrext_cmds=".dll"
+	# FIXME: Setting linknames here is a bad hack.
+	archive_cmds='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+	archive_expsym_cmds='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	    sed -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+	  else
+	    sed -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+	  fi~
+	  $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+	  linknames='
+	# The linker will not automatically build a static lib if we build a DLL.
+	# _LT_TAGVAR(old_archive_from_new_cmds, )='true'
+	enable_shared_with_static_runtimes=yes
+	exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+	export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols'
+	# Don't use ranlib
+	old_postinstall_cmds='chmod 644 $oldlib'
+	postlink_cmds='lt_outputfile="@OUTPUT@"~
+	  lt_tool_outputfile="@TOOL_OUTPUT@"~
+	  case $lt_outputfile in
+	    *.exe|*.EXE) ;;
+	    *)
+	      lt_outputfile="$lt_outputfile.exe"
+	      lt_tool_outputfile="$lt_tool_outputfile.exe"
+	      ;;
+	  esac~
+	  if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+	    $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+	    $RM "$lt_outputfile.manifest";
+	  fi'
+	;;
+      *)
+	# Assume MSVC wrapper
+	hardcode_libdir_flag_spec=' '
+	allow_undefined_flag=unsupported
+	# Tell ltmain to make .lib files, not .a files.
+	libext=lib
+	# Tell ltmain to make .dll files, not .so files.
+	shrext_cmds=".dll"
+	# FIXME: Setting linknames here is a bad hack.
+	archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames='
+	# The linker will automatically build a .lib file if we build a DLL.
+	old_archive_from_new_cmds='true'
+	# FIXME: Should let the user specify the lib program.
+	old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs'
+	enable_shared_with_static_runtimes=yes
+	;;
+      esac
+      ;;
+
+    darwin* | rhapsody*)
+
+
+  archive_cmds_need_lc=no
+  hardcode_direct=no
+  hardcode_automatic=yes
+  hardcode_shlibpath_var=unsupported
+  if test "$lt_cv_ld_force_load" = "yes"; then
+    whole_archive_flag_spec='`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience ${wl}-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`'
+
+  else
+    whole_archive_flag_spec=''
+  fi
+  link_all_deplibs=yes
+  allow_undefined_flag="$_lt_dar_allow_undefined"
+  case $cc_basename in
+     ifort*) _lt_dar_can_shared=yes ;;
+     *) _lt_dar_can_shared=$GCC ;;
+  esac
+  if test "$_lt_dar_can_shared" = "yes"; then
+    output_verbose_link_cmd=func_echo_all
+    archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}"
+    module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}"
+    archive_expsym_cmds="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}"
+    module_expsym_cmds="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}"
+
+  else
+  ld_shlibs=no
+  fi
+
+      ;;
+
+    dgux*)
+      archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_libdir_flag_spec='-L$libdir'
+      hardcode_shlibpath_var=no
+      ;;
+
+    # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+    # support.  Future versions do this automatically, but an explicit c++rt0.o
+    # does not break anything, and helps significantly (at the cost of a little
+    # extra space).
+    freebsd2.2*)
+      archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+      hardcode_libdir_flag_spec='-R$libdir'
+      hardcode_direct=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+    freebsd2.*)
+      archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_direct=yes
+      hardcode_minus_L=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+    freebsd* | dragonfly*)
+      archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+      hardcode_libdir_flag_spec='-R$libdir'
+      hardcode_direct=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    hpux9*)
+      if test "$GCC" = yes; then
+	archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      else
+	archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      fi
+      hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+      hardcode_libdir_separator=:
+      hardcode_direct=yes
+
+      # hardcode_minus_L: Not really in the search PATH,
+      # but as the default location of the library.
+      hardcode_minus_L=yes
+      export_dynamic_flag_spec='${wl}-E'
+      ;;
+
+    hpux10*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+	archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      if test "$with_gnu_ld" = no; then
+	hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+	hardcode_libdir_separator=:
+	hardcode_direct=yes
+	hardcode_direct_absolute=yes
+	export_dynamic_flag_spec='${wl}-E'
+	# hardcode_minus_L: Not really in the search PATH,
+	# but as the default location of the library.
+	hardcode_minus_L=yes
+      fi
+      ;;
+
+    hpux11*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+	case $host_cpu in
+	hppa*64*)
+	  archive_cmds='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	ia64*)
+	  archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	*)
+	  archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	esac
+      else
+	case $host_cpu in
+	hppa*64*)
+	  archive_cmds='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	ia64*)
+	  archive_cmds='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	*)
+
+	  # Older versions of the 11.00 compiler do not understand -b yet
+	  # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does)
+	  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5
+$as_echo_n "checking if $CC understands -b... " >&6; }
+if ${lt_cv_prog_compiler__b+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler__b=no
+   save_LDFLAGS="$LDFLAGS"
+   LDFLAGS="$LDFLAGS -b"
+   echo "$lt_simple_link_test_code" > conftest.$ac_ext
+   if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+     # The linker can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     if test -s conftest.err; then
+       # Append any errors to the config.log.
+       cat conftest.err 1>&5
+       $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+       $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+       if diff conftest.exp conftest.er2 >/dev/null; then
+         lt_cv_prog_compiler__b=yes
+       fi
+     else
+       lt_cv_prog_compiler__b=yes
+     fi
+   fi
+   $RM -r conftest*
+   LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5
+$as_echo "$lt_cv_prog_compiler__b" >&6; }
+
+if test x"$lt_cv_prog_compiler__b" = xyes; then
+    archive_cmds='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+else
+    archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+fi
+
+	  ;;
+	esac
+      fi
+      if test "$with_gnu_ld" = no; then
+	hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+	hardcode_libdir_separator=:
+
+	case $host_cpu in
+	hppa*64*|ia64*)
+	  hardcode_direct=no
+	  hardcode_shlibpath_var=no
+	  ;;
+	*)
+	  hardcode_direct=yes
+	  hardcode_direct_absolute=yes
+	  export_dynamic_flag_spec='${wl}-E'
+
+	  # hardcode_minus_L: Not really in the search PATH,
+	  # but as the default location of the library.
+	  hardcode_minus_L=yes
+	  ;;
+	esac
+      fi
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      if test "$GCC" = yes; then
+	archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+	# Try to use the -exported_symbol ld option, if it does not
+	# work, assume that -exports_file does not work either and
+	# implicitly export all symbols.
+	# This should be the same for all languages, so no per-tag cache variable.
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5
+$as_echo_n "checking whether the $host_os linker accepts -exported_symbol... " >&6; }
+if ${lt_cv_irix_exported_symbol+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  save_LDFLAGS="$LDFLAGS"
+	   LDFLAGS="$LDFLAGS -shared ${wl}-exported_symbol ${wl}foo ${wl}-update_registry ${wl}/dev/null"
+	   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+int foo (void) { return 0; }
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  lt_cv_irix_exported_symbol=yes
+else
+  lt_cv_irix_exported_symbol=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+           LDFLAGS="$save_LDFLAGS"
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5
+$as_echo "$lt_cv_irix_exported_symbol" >&6; }
+	if test "$lt_cv_irix_exported_symbol" = yes; then
+          archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations ${wl}-exports_file ${wl}$export_symbols -o $lib'
+	fi
+      else
+	archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -exports_file $export_symbols -o $lib'
+      fi
+      archive_cmds_need_lc='no'
+      hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+      hardcode_libdir_separator=:
+      inherit_rpath=yes
+      link_all_deplibs=yes
+      ;;
+
+    netbsd*)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+	archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'  # a.out
+      else
+	archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags'      # ELF
+      fi
+      hardcode_libdir_flag_spec='-R$libdir'
+      hardcode_direct=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    newsos6)
+      archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_direct=yes
+      hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+      hardcode_libdir_separator=:
+      hardcode_shlibpath_var=no
+      ;;
+
+    *nto* | *qnx*)
+      ;;
+
+    openbsd*)
+      if test -f /usr/libexec/ld.so; then
+	hardcode_direct=yes
+	hardcode_shlibpath_var=no
+	hardcode_direct_absolute=yes
+	if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+	  archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+	  archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols'
+	  hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+	  export_dynamic_flag_spec='${wl}-E'
+	else
+	  case $host_os in
+	   openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*)
+	     archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+	     hardcode_libdir_flag_spec='-R$libdir'
+	     ;;
+	   *)
+	     archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+	     hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+	     ;;
+	  esac
+	fi
+      else
+	ld_shlibs=no
+      fi
+      ;;
+
+    os2*)
+      hardcode_libdir_flag_spec='-L$libdir'
+      hardcode_minus_L=yes
+      allow_undefined_flag=unsupported
+      archive_cmds='$ECHO "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~echo DATA >> $output_objdir/$libname.def~echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def'
+      old_archive_from_new_cmds='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def'
+      ;;
+
+    osf3*)
+      if test "$GCC" = yes; then
+	allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*'
+	archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+      else
+	allow_undefined_flag=' -expect_unresolved \*'
+	archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+      fi
+      archive_cmds_need_lc='no'
+      hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+      hardcode_libdir_separator=:
+      ;;
+
+    osf4* | osf5*)	# as osf3* with the addition of -msym flag
+      if test "$GCC" = yes; then
+	allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*'
+	archive_cmds='$CC -shared${allow_undefined_flag} $pic_flag $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+	hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+      else
+	allow_undefined_flag=' -expect_unresolved \*'
+	archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~
+	$CC -shared${allow_undefined_flag} ${wl}-input ${wl}$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~$RM $lib.exp'
+
+	# Both c and cxx compiler support -rpath directly
+	hardcode_libdir_flag_spec='-rpath $libdir'
+      fi
+      archive_cmds_need_lc='no'
+      hardcode_libdir_separator=:
+      ;;
+
+    solaris*)
+      no_undefined_flag=' -z defs'
+      if test "$GCC" = yes; then
+	wlarc='${wl}'
+	archive_cmds='$CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+      else
+	case `$CC -V 2>&1` in
+	*"Compilers 5.0"*)
+	  wlarc=''
+	  archive_cmds='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp'
+	  ;;
+	*)
+	  wlarc='${wl}'
+	  archive_cmds='$CC -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $compiler_flags'
+	  archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $CC -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+	  ;;
+	esac
+      fi
+      hardcode_libdir_flag_spec='-R$libdir'
+      hardcode_shlibpath_var=no
+      case $host_os in
+      solaris2.[0-5] | solaris2.[0-5].*) ;;
+      *)
+	# The compiler driver will combine and reorder linker options,
+	# but understands `-z linker_flag'.  GCC discards it without `$wl',
+	# but is careful enough not to reorder.
+	# Supported since Solaris 2.6 (maybe 2.5.1?)
+	if test "$GCC" = yes; then
+	  whole_archive_flag_spec='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+	else
+	  whole_archive_flag_spec='-z allextract$convenience -z defaultextract'
+	fi
+	;;
+      esac
+      link_all_deplibs=yes
+      ;;
+
+    sunos4*)
+      if test "x$host_vendor" = xsequent; then
+	# Use $CC to link under sequent, because it throws in some extra .o
+	# files that make .init and .fini sections work.
+	archive_cmds='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      hardcode_libdir_flag_spec='-L$libdir'
+      hardcode_direct=yes
+      hardcode_minus_L=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    sysv4)
+      case $host_vendor in
+	sni)
+	  archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  hardcode_direct=yes # is this really true???
+	;;
+	siemens)
+	  ## LD is ld it makes a PLAMLIB
+	  ## CC just makes a GrossModule.
+	  archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+	  reload_cmds='$CC -r -o $output$reload_objs'
+	  hardcode_direct=no
+        ;;
+	motorola)
+	  archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  hardcode_direct=no #Motorola manual says yes, but my tests say they lie
+	;;
+      esac
+      runpath_var='LD_RUN_PATH'
+      hardcode_shlibpath_var=no
+      ;;
+
+    sysv4.3*)
+      archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_shlibpath_var=no
+      export_dynamic_flag_spec='-Bexport'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+	archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	hardcode_shlibpath_var=no
+	runpath_var=LD_RUN_PATH
+	hardcode_runpath_var=yes
+	ld_shlibs=yes
+      fi
+      ;;
+
+    sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*)
+      no_undefined_flag='${wl}-z,text'
+      archive_cmds_need_lc=no
+      hardcode_shlibpath_var=no
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+	archive_cmds='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	archive_expsym_cmds='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	archive_cmds='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	archive_expsym_cmds='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6*)
+      # Note: We can NOT use -z defs as we might desire, because we do not
+      # link with -lc, and that would cause any symbols used from libc to
+      # always be unresolved, which means just about no library would
+      # ever link correctly.  If we're not using GNU ld we use -z text
+      # though, which does catch some bad symbols but isn't as heavy-handed
+      # as -z defs.
+      no_undefined_flag='${wl}-z,text'
+      allow_undefined_flag='${wl}-z,nodefs'
+      archive_cmds_need_lc=no
+      hardcode_shlibpath_var=no
+      hardcode_libdir_flag_spec='${wl}-R,$libdir'
+      hardcode_libdir_separator=':'
+      link_all_deplibs=yes
+      export_dynamic_flag_spec='${wl}-Bexport'
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+	archive_cmds='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	archive_expsym_cmds='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	archive_cmds='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	archive_expsym_cmds='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    uts4*)
+      archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_libdir_flag_spec='-L$libdir'
+      hardcode_shlibpath_var=no
+      ;;
+
+    *)
+      ld_shlibs=no
+      ;;
+    esac
+
+    if test x$host_vendor = xsni; then
+      case $host in
+      sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+	export_dynamic_flag_spec='${wl}-Blargedynsym'
+	;;
+      esac
+    fi
+  fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5
+$as_echo "$ld_shlibs" >&6; }
+test "$ld_shlibs" = no && can_build_shared=no
+
+with_gnu_ld=$with_gnu_ld
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#
+# Do we need to explicitly link libc?
+#
+case "x$archive_cmds_need_lc" in
+x|xyes)
+  # Assume -lc should be added
+  archive_cmds_need_lc=yes
+
+  if test "$enable_shared" = yes && test "$GCC" = yes; then
+    case $archive_cmds in
+    *'~'*)
+      # FIXME: we may have to deal with multi-command sequences.
+      ;;
+    '$CC '*)
+      # Test whether the compiler implicitly links with -lc since on some
+      # systems, -lgcc has to come before -lc. If gcc already passes -lc
+      # to ld, don't add -lc before -lgcc.
+      { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5
+$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; }
+if ${lt_cv_archive_cmds_need_lc+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  $RM conftest*
+	echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+	if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } 2>conftest.err; then
+	  soname=conftest
+	  lib=conftest
+	  libobjs=conftest.$ac_objext
+	  deplibs=
+	  wl=$lt_prog_compiler_wl
+	  pic_flag=$lt_prog_compiler_pic
+	  compiler_flags=-v
+	  linker_flags=-v
+	  verstring=
+	  output_objdir=.
+	  libname=conftest
+	  lt_save_allow_undefined_flag=$allow_undefined_flag
+	  allow_undefined_flag=
+	  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5
+  (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+	  then
+	    lt_cv_archive_cmds_need_lc=no
+	  else
+	    lt_cv_archive_cmds_need_lc=yes
+	  fi
+	  allow_undefined_flag=$lt_save_allow_undefined_flag
+	else
+	  cat conftest.err 1>&5
+	fi
+	$RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5
+$as_echo "$lt_cv_archive_cmds_need_lc" >&6; }
+      archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc
+      ;;
+    esac
+  fi
+  ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5
+$as_echo_n "checking dynamic linker characteristics... " >&6; }
+
+if test "$GCC" = yes; then
+  case $host_os in
+    darwin*) lt_awk_arg="/^libraries:/,/LR/" ;;
+    *) lt_awk_arg="/^libraries:/" ;;
+  esac
+  case $host_os in
+    mingw* | cegcc*) lt_sed_strip_eq="s,=\([A-Za-z]:\),\1,g" ;;
+    *) lt_sed_strip_eq="s,=/,/,g" ;;
+  esac
+  lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq`
+  case $lt_search_path_spec in
+  *\;*)
+    # if the path contains ";" then we assume it to be the separator
+    # otherwise default to the standard path separator (i.e. ":") - it is
+    # assumed that no part of a normal pathname contains ";" but that should
+    # okay in the real world where ";" in dirpaths is itself problematic.
+    lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'`
+    ;;
+  *)
+    lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"`
+    ;;
+  esac
+  # Ok, now we have the path, separated by spaces, we can step through it
+  # and add multilib dir if necessary.
+  lt_tmp_lt_search_path_spec=
+  lt_multi_os_dir=`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null`
+  for lt_sys_path in $lt_search_path_spec; do
+    if test -d "$lt_sys_path/$lt_multi_os_dir"; then
+      lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path/$lt_multi_os_dir"
+    else
+      test -d "$lt_sys_path" && \
+	lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path"
+    fi
+  done
+  lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk '
+BEGIN {RS=" "; FS="/|\n";} {
+  lt_foo="";
+  lt_count=0;
+  for (lt_i = NF; lt_i > 0; lt_i--) {
+    if ($lt_i != "" && $lt_i != ".") {
+      if ($lt_i == "..") {
+        lt_count++;
+      } else {
+        if (lt_count == 0) {
+          lt_foo="/" $lt_i lt_foo;
+        } else {
+          lt_count--;
+        }
+      }
+    }
+  }
+  if (lt_foo != "") { lt_freq[lt_foo]++; }
+  if (lt_freq[lt_foo] == 1) { print lt_foo; }
+}'`
+  # AWK program above erroneously prepends '/' to C:/dos/paths
+  # for these hosts.
+  case $host_os in
+    mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\
+      $SED 's,/\([A-Za-z]:\),\1,g'` ;;
+  esac
+  sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP`
+else
+  sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+fi
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+shrext_cmds=".so"
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+need_lib_prefix=unknown
+hardcode_into_libs=no
+
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+need_version=unknown
+
+case $host_os in
+aix3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a'
+  shlibpath_var=LIBPATH
+
+  # AIX 3 has no versioning support, so we append a major version to the name.
+  soname_spec='${libname}${release}${shared_ext}$major'
+  ;;
+
+aix[4-9]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  hardcode_into_libs=yes
+  if test "$host_cpu" = ia64; then
+    # AIX 5 supports IA64
+    library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}'
+    shlibpath_var=LD_LIBRARY_PATH
+  else
+    # With GCC up to 2.95.x, collect2 would create an import file
+    # for dependence libraries.  The import file would start with
+    # the line `#! .'.  This would cause the generated library to
+    # depend on `.', always an invalid library.  This was fixed in
+    # development snapshots of GCC prior to 3.0.
+    case $host_os in
+      aix4 | aix4.[01] | aix4.[01].*)
+      if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+	   echo ' yes '
+	   echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then
+	:
+      else
+	can_build_shared=no
+      fi
+      ;;
+    esac
+    # AIX (on Power*) has no versioning support, so currently we can not hardcode correct
+    # soname into executable. Probably we can add versioning support to
+    # collect2, so additional links can be useful in future.
+    if test "$aix_use_runtimelinking" = yes; then
+      # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+      # instead of lib<name>.a to let people know that these are not
+      # typical AIX shared libraries.
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    else
+      # We preserve .a as extension for shared libraries through AIX4.2
+      # and later when we are not doing run time linking.
+      library_names_spec='${libname}${release}.a $libname.a'
+      soname_spec='${libname}${release}${shared_ext}$major'
+    fi
+    shlibpath_var=LIBPATH
+  fi
+  ;;
+
+amigaos*)
+  case $host_cpu in
+  powerpc)
+    # Since July 2007 AmigaOS4 officially supports .so libraries.
+    # When compiling the executable, add -use-dynld -Lsobjs: to the compileline.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    ;;
+  m68k)
+    library_names_spec='$libname.ixlibrary $libname.a'
+    # Create ${libname}_ixlibrary.a entries in /sys/libs.
+    finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done'
+    ;;
+  esac
+  ;;
+
+beos*)
+  library_names_spec='${libname}${shared_ext}'
+  dynamic_linker="$host_os ld.so"
+  shlibpath_var=LIBRARY_PATH
+  ;;
+
+bsdi[45]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+  sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+  # the default ld.so.conf also contains /usr/contrib/lib and
+  # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+  # libtool to hard-code these into programs
+  ;;
+
+cygwin* | mingw* | pw32* | cegcc*)
+  version_type=windows
+  shrext_cmds=".dll"
+  need_version=no
+  need_lib_prefix=no
+
+  case $GCC,$cc_basename in
+  yes,*)
+    # gcc
+    library_names_spec='$libname.dll.a'
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname~
+      chmod a+x \$dldir/$dlname~
+      if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+        eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+      fi'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+
+    case $host_os in
+    cygwin*)
+      # Cygwin DLLs use 'cyg' prefix rather than 'lib'
+      soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+
+      sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"
+      ;;
+    mingw* | cegcc*)
+      # MinGW DLLs use traditional 'lib' prefix
+      soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    pw32*)
+      # pw32 DLLs use 'pw' prefix rather than 'lib'
+      library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    esac
+    dynamic_linker='Win32 ld.exe'
+    ;;
+
+  *,cl*)
+    # Native MSVC
+    libname_spec='$name'
+    soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+    library_names_spec='${libname}.dll.lib'
+
+    case $build_os in
+    mingw*)
+      sys_lib_search_path_spec=
+      lt_save_ifs=$IFS
+      IFS=';'
+      for lt_path in $LIB
+      do
+        IFS=$lt_save_ifs
+        # Let DOS variable expansion print the short 8.3 style file name.
+        lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"`
+        sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path"
+      done
+      IFS=$lt_save_ifs
+      # Convert to MSYS style.
+      sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'`
+      ;;
+    cygwin*)
+      # Convert to unix form, then to dos form, then back to unix form
+      # but this time dos style (no spaces!) so that the unix form looks
+      # like /cygdrive/c/PROGRA~1:/cygdr...
+      sys_lib_search_path_spec=`cygpath --path --unix "$LIB"`
+      sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null`
+      sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      ;;
+    *)
+      sys_lib_search_path_spec="$LIB"
+      if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then
+        # It is most probably a Windows format PATH.
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'`
+      else
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      fi
+      # FIXME: find the short name or the path components, as spaces are
+      # common. (e.g. "Program Files" -> "PROGRA~1")
+      ;;
+    esac
+
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+    dynamic_linker='Win32 link.exe'
+    ;;
+
+  *)
+    # Assume MSVC wrapper
+    library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib'
+    dynamic_linker='Win32 ld.exe'
+    ;;
+  esac
+  # FIXME: first we should search . and the directory the executable is in
+  shlibpath_var=PATH
+  ;;
+
+darwin* | rhapsody*)
+  dynamic_linker="$host_os dyld"
+  version_type=darwin
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext'
+  soname_spec='${libname}${release}${major}$shared_ext'
+  shlibpath_overrides_runpath=yes
+  shlibpath_var=DYLD_LIBRARY_PATH
+  shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`'
+
+  sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"
+  sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib'
+  ;;
+
+dgux*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+freebsd* | dragonfly*)
+  # DragonFly does not have aout.  When/if they implement a new
+  # versioning mechanism, adjust this.
+  if test -x /usr/bin/objformat; then
+    objformat=`/usr/bin/objformat`
+  else
+    case $host_os in
+    freebsd[23].*) objformat=aout ;;
+    *) objformat=elf ;;
+    esac
+  fi
+  version_type=freebsd-$objformat
+  case $version_type in
+    freebsd-elf*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+      need_version=no
+      need_lib_prefix=no
+      ;;
+    freebsd-*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix'
+      need_version=yes
+      ;;
+  esac
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_os in
+  freebsd2.*)
+    shlibpath_overrides_runpath=yes
+    ;;
+  freebsd3.[01]* | freebsdelf3.[01]*)
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  freebsd3.[2-9]* | freebsdelf3.[2-9]* | \
+  freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1)
+    shlibpath_overrides_runpath=no
+    hardcode_into_libs=yes
+    ;;
+  *) # from 4.6 on, and DragonFly
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  esac
+  ;;
+
+gnu*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+haiku*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  dynamic_linker="$host_os runtime_loader"
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib'
+  hardcode_into_libs=yes
+  ;;
+
+hpux9* | hpux10* | hpux11*)
+  # Give a soname corresponding to the major version so that dld.sl refuses to
+  # link against other versions.
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  case $host_cpu in
+  ia64*)
+    shrext_cmds='.so'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.so"
+    shlibpath_var=LD_LIBRARY_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    if test "X$HPUX_IA64_MODE" = X32; then
+      sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib"
+    else
+      sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64"
+    fi
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  hppa*64*)
+    shrext_cmds='.sl'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64"
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  *)
+    shrext_cmds='.sl'
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=SHLIB_PATH
+    shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    ;;
+  esac
+  # HP-UX runs *really* slowly unless shared libraries are mode 555, ...
+  postinstall_cmds='chmod 555 $lib'
+  # or fails outright, so override atomically:
+  install_override_mode=555
+  ;;
+
+interix[3-9]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $host_os in
+    nonstopux*) version_type=nonstopux ;;
+    *)
+	if test "$lt_cv_prog_gnu_ld" = yes; then
+		version_type=linux # correct to gnu/linux during the next big refactor
+	else
+		version_type=irix
+	fi ;;
+  esac
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}'
+  case $host_os in
+  irix5* | nonstopux*)
+    libsuff= shlibsuff=
+    ;;
+  *)
+    case $LD in # libtool.m4 will add one of these switches to LD
+    *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ")
+      libsuff= shlibsuff= libmagic=32-bit;;
+    *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ")
+      libsuff=32 shlibsuff=N32 libmagic=N32;;
+    *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ")
+      libsuff=64 shlibsuff=64 libmagic=64-bit;;
+    *) libsuff= shlibsuff= libmagic=never-match;;
+    esac
+    ;;
+  esac
+  shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+  shlibpath_overrides_runpath=no
+  sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}"
+  sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}"
+  hardcode_into_libs=yes
+  ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux*oldld* | linux*aout* | linux*coff*)
+  dynamic_linker=no
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+
+  # Some binutils ld are patched to set DT_RUNPATH
+  if ${lt_cv_shlibpath_overrides_runpath+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_shlibpath_overrides_runpath=no
+    save_LDFLAGS=$LDFLAGS
+    save_libdir=$libdir
+    eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \
+	 LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\""
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  if  ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then :
+  lt_cv_shlibpath_overrides_runpath=yes
+fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+    LDFLAGS=$save_LDFLAGS
+    libdir=$save_libdir
+
+fi
+
+  shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath
+
+  # This implies no fast_install, which is unacceptable.
+  # Some rework will be needed to allow for fast_install
+  # before this can be enabled.
+  hardcode_into_libs=yes
+
+  # Append ld.so.conf contents to the search path
+  if test -f /etc/ld.so.conf; then
+    lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[	 ]*hwcap[	 ]/d;s/[:,	]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '`
+    sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra"
+  fi
+
+  # We used to test for /lib/ld.so.1 and disable shared libraries on
+  # powerpc, because MkLinux only supported shared libraries with the
+  # GNU dynamic linker.  Since this was broken with cross compilers,
+  # most powerpc-linux boxes support dynamic linking these days and
+  # people can always --disable-shared, the test was removed, and we
+  # assume the GNU/Linux dynamic linker is in use.
+  dynamic_linker='GNU/Linux ld.so'
+  ;;
+
+netbsd*)
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+    finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+    dynamic_linker='NetBSD (a.out) ld.so'
+  else
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    dynamic_linker='NetBSD ld.elf_so'
+  fi
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  ;;
+
+newsos6)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  ;;
+
+*nto* | *qnx*)
+  version_type=qnx
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  dynamic_linker='ldqnx.so'
+  ;;
+
+openbsd*)
+  version_type=sunos
+  sys_lib_dlsearch_path_spec="/usr/lib"
+  need_lib_prefix=no
+  # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs.
+  case $host_os in
+    openbsd3.3 | openbsd3.3.*)	need_version=yes ;;
+    *)				need_version=no  ;;
+  esac
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    case $host_os in
+      openbsd2.[89] | openbsd2.[89].*)
+	shlibpath_overrides_runpath=no
+	;;
+      *)
+	shlibpath_overrides_runpath=yes
+	;;
+      esac
+  else
+    shlibpath_overrides_runpath=yes
+  fi
+  ;;
+
+os2*)
+  libname_spec='$name'
+  shrext_cmds=".dll"
+  need_lib_prefix=no
+  library_names_spec='$libname${shared_ext} $libname.a'
+  dynamic_linker='OS/2 ld.exe'
+  shlibpath_var=LIBPATH
+  ;;
+
+osf3* | osf4* | osf5*)
+  version_type=osf
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+  sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec"
+  ;;
+
+rdos*)
+  dynamic_linker=no
+  ;;
+
+solaris*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  # ldd complains unless libraries are executable
+  postinstall_cmds='chmod +x $lib'
+  ;;
+
+sunos4*)
+  version_type=sunos
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  if test "$with_gnu_ld" = yes; then
+    need_lib_prefix=no
+  fi
+  need_version=yes
+  ;;
+
+sysv4 | sysv4.3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_vendor in
+    sni)
+      shlibpath_overrides_runpath=no
+      need_lib_prefix=no
+      runpath_var=LD_RUN_PATH
+      ;;
+    siemens)
+      need_lib_prefix=no
+      ;;
+    motorola)
+      need_lib_prefix=no
+      need_version=no
+      shlibpath_overrides_runpath=no
+      sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+      ;;
+  esac
+  ;;
+
+sysv4*MP*)
+  if test -d /usr/nec ;then
+    version_type=linux # correct to gnu/linux during the next big refactor
+    library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}'
+    soname_spec='$libname${shared_ext}.$major'
+    shlibpath_var=LD_LIBRARY_PATH
+  fi
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  version_type=freebsd-elf
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  if test "$with_gnu_ld" = yes; then
+    sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib'
+  else
+    sys_lib_search_path_spec='/usr/ccs/lib /usr/lib'
+    case $host_os in
+      sco3.2v5*)
+        sys_lib_search_path_spec="$sys_lib_search_path_spec /lib"
+	;;
+    esac
+  fi
+  sys_lib_dlsearch_path_spec='/usr/lib'
+  ;;
+
+tpf*)
+  # TPF is a cross-target only.  Preferred cross-host = GNU/Linux.
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+uts4*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+*)
+  dynamic_linker=no
+  ;;
+esac
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5
+$as_echo "$dynamic_linker" >&6; }
+test "$dynamic_linker" = no && can_build_shared=no
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test "$GCC" = yes; then
+  variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then
+  sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec"
+fi
+if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then
+  sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec"
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5
+$as_echo_n "checking how to hardcode library paths into programs... " >&6; }
+hardcode_action=
+if test -n "$hardcode_libdir_flag_spec" ||
+   test -n "$runpath_var" ||
+   test "X$hardcode_automatic" = "Xyes" ; then
+
+  # We can hardcode non-existent directories.
+  if test "$hardcode_direct" != no &&
+     # If the only mechanism to avoid hardcoding is shlibpath_var, we
+     # have to relink, otherwise we might link with an installed library
+     # when we should be linking with a yet-to-be-installed one
+     ## test "$_LT_TAGVAR(hardcode_shlibpath_var, )" != no &&
+     test "$hardcode_minus_L" != no; then
+    # Linking always hardcodes the temporary library directory.
+    hardcode_action=relink
+  else
+    # We can link without hardcoding, and we can hardcode nonexisting dirs.
+    hardcode_action=immediate
+  fi
+else
+  # We cannot hardcode anything, or else we can only hardcode existing
+  # directories.
+  hardcode_action=unsupported
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5
+$as_echo "$hardcode_action" >&6; }
+
+if test "$hardcode_action" = relink ||
+   test "$inherit_rpath" = yes; then
+  # Fast installation is not supported
+  enable_fast_install=no
+elif test "$shlibpath_overrides_runpath" = yes ||
+     test "$enable_shared" = no; then
+  # Fast installation is not necessary
+  enable_fast_install=needless
+fi
+
+
+
+
+
+
+  if test "x$enable_dlopen" != xyes; then
+  enable_dlopen=unknown
+  enable_dlopen_self=unknown
+  enable_dlopen_self_static=unknown
+else
+  lt_cv_dlopen=no
+  lt_cv_dlopen_libs=
+
+  case $host_os in
+  beos*)
+    lt_cv_dlopen="load_add_on"
+    lt_cv_dlopen_libs=
+    lt_cv_dlopen_self=yes
+    ;;
+
+  mingw* | pw32* | cegcc*)
+    lt_cv_dlopen="LoadLibrary"
+    lt_cv_dlopen_libs=
+    ;;
+
+  cygwin*)
+    lt_cv_dlopen="dlopen"
+    lt_cv_dlopen_libs=
+    ;;
+
+  darwin*)
+  # if libdl is installed we need to link against it
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+$as_echo_n "checking for dlopen in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlopen+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_dl_dlopen=yes
+else
+  ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+  lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"
+else
+
+    lt_cv_dlopen="dyld"
+    lt_cv_dlopen_libs=
+    lt_cv_dlopen_self=yes
+
+fi
+
+    ;;
+
+  *)
+    ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load"
+if test "x$ac_cv_func_shl_load" = xyes; then :
+  lt_cv_dlopen="shl_load"
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5
+$as_echo_n "checking for shl_load in -ldld... " >&6; }
+if ${ac_cv_lib_dld_shl_load+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char shl_load ();
+int
+main ()
+{
+return shl_load ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_dld_shl_load=yes
+else
+  ac_cv_lib_dld_shl_load=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5
+$as_echo "$ac_cv_lib_dld_shl_load" >&6; }
+if test "x$ac_cv_lib_dld_shl_load" = xyes; then :
+  lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld"
+else
+  ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen"
+if test "x$ac_cv_func_dlopen" = xyes; then :
+  lt_cv_dlopen="dlopen"
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+$as_echo_n "checking for dlopen in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlopen+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_dl_dlopen=yes
+else
+  ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+  lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5
+$as_echo_n "checking for dlopen in -lsvld... " >&6; }
+if ${ac_cv_lib_svld_dlopen+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsvld  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_svld_dlopen=yes
+else
+  ac_cv_lib_svld_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5
+$as_echo "$ac_cv_lib_svld_dlopen" >&6; }
+if test "x$ac_cv_lib_svld_dlopen" = xyes; then :
+  lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5
+$as_echo_n "checking for dld_link in -ldld... " >&6; }
+if ${ac_cv_lib_dld_dld_link+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dld_link ();
+int
+main ()
+{
+return dld_link ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_dld_dld_link=yes
+else
+  ac_cv_lib_dld_dld_link=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5
+$as_echo "$ac_cv_lib_dld_dld_link" >&6; }
+if test "x$ac_cv_lib_dld_dld_link" = xyes; then :
+  lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld"
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+    ;;
+  esac
+
+  if test "x$lt_cv_dlopen" != xno; then
+    enable_dlopen=yes
+  else
+    enable_dlopen=no
+  fi
+
+  case $lt_cv_dlopen in
+  dlopen)
+    save_CPPFLAGS="$CPPFLAGS"
+    test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H"
+
+    save_LDFLAGS="$LDFLAGS"
+    wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\"
+
+    save_LIBS="$LIBS"
+    LIBS="$lt_cv_dlopen_libs $LIBS"
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5
+$as_echo_n "checking whether a program can dlopen itself... " >&6; }
+if ${lt_cv_dlopen_self+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  	  if test "$cross_compiling" = yes; then :
+  lt_cv_dlopen_self=cross
+else
+  lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+  lt_status=$lt_dlunknown
+  cat > conftest.$ac_ext <<_LT_EOF
+#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+#  define LT_DLGLOBAL		RTLD_GLOBAL
+#else
+#  ifdef DL_GLOBAL
+#    define LT_DLGLOBAL		DL_GLOBAL
+#  else
+#    define LT_DLGLOBAL		0
+#  endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+   find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+#  ifdef RTLD_LAZY
+#    define LT_DLLAZY_OR_NOW		RTLD_LAZY
+#  else
+#    ifdef DL_LAZY
+#      define LT_DLLAZY_OR_NOW		DL_LAZY
+#    else
+#      ifdef RTLD_NOW
+#        define LT_DLLAZY_OR_NOW	RTLD_NOW
+#      else
+#        ifdef DL_NOW
+#          define LT_DLLAZY_OR_NOW	DL_NOW
+#        else
+#          define LT_DLLAZY_OR_NOW	0
+#        endif
+#      endif
+#    endif
+#  endif
+#endif
+
+/* When -fvisbility=hidden is used, assume the code has been annotated
+   correspondingly for the symbols needed.  */
+#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+  void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+  int status = $lt_dlunknown;
+
+  if (self)
+    {
+      if (dlsym (self,"fnord"))       status = $lt_dlno_uscore;
+      else
+        {
+	  if (dlsym( self,"_fnord"))  status = $lt_dlneed_uscore;
+          else puts (dlerror ());
+	}
+      /* dlclose (self); */
+    }
+  else
+    puts (dlerror ());
+
+  return status;
+}
+_LT_EOF
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+  (eval $ac_link) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && test -s conftest${ac_exeext} 2>/dev/null; then
+    (./conftest; exit; ) >&5 2>/dev/null
+    lt_status=$?
+    case x$lt_status in
+      x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;;
+      x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;;
+      x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;;
+    esac
+  else :
+    # compilation failed
+    lt_cv_dlopen_self=no
+  fi
+fi
+rm -fr conftest*
+
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5
+$as_echo "$lt_cv_dlopen_self" >&6; }
+
+    if test "x$lt_cv_dlopen_self" = xyes; then
+      wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5
+$as_echo_n "checking whether a statically linked program can dlopen itself... " >&6; }
+if ${lt_cv_dlopen_self_static+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  	  if test "$cross_compiling" = yes; then :
+  lt_cv_dlopen_self_static=cross
+else
+  lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+  lt_status=$lt_dlunknown
+  cat > conftest.$ac_ext <<_LT_EOF
+#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+#  define LT_DLGLOBAL		RTLD_GLOBAL
+#else
+#  ifdef DL_GLOBAL
+#    define LT_DLGLOBAL		DL_GLOBAL
+#  else
+#    define LT_DLGLOBAL		0
+#  endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+   find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+#  ifdef RTLD_LAZY
+#    define LT_DLLAZY_OR_NOW		RTLD_LAZY
+#  else
+#    ifdef DL_LAZY
+#      define LT_DLLAZY_OR_NOW		DL_LAZY
+#    else
+#      ifdef RTLD_NOW
+#        define LT_DLLAZY_OR_NOW	RTLD_NOW
+#      else
+#        ifdef DL_NOW
+#          define LT_DLLAZY_OR_NOW	DL_NOW
+#        else
+#          define LT_DLLAZY_OR_NOW	0
+#        endif
+#      endif
+#    endif
+#  endif
+#endif
+
+/* When -fvisbility=hidden is used, assume the code has been annotated
+   correspondingly for the symbols needed.  */
+#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+  void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+  int status = $lt_dlunknown;
+
+  if (self)
+    {
+      if (dlsym (self,"fnord"))       status = $lt_dlno_uscore;
+      else
+        {
+	  if (dlsym( self,"_fnord"))  status = $lt_dlneed_uscore;
+          else puts (dlerror ());
+	}
+      /* dlclose (self); */
+    }
+  else
+    puts (dlerror ());
+
+  return status;
+}
+_LT_EOF
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+  (eval $ac_link) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && test -s conftest${ac_exeext} 2>/dev/null; then
+    (./conftest; exit; ) >&5 2>/dev/null
+    lt_status=$?
+    case x$lt_status in
+      x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;;
+      x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;;
+      x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;;
+    esac
+  else :
+    # compilation failed
+    lt_cv_dlopen_self_static=no
+  fi
+fi
+rm -fr conftest*
+
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5
+$as_echo "$lt_cv_dlopen_self_static" >&6; }
+    fi
+
+    CPPFLAGS="$save_CPPFLAGS"
+    LDFLAGS="$save_LDFLAGS"
+    LIBS="$save_LIBS"
+    ;;
+  esac
+
+  case $lt_cv_dlopen_self in
+  yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;;
+  *) enable_dlopen_self=unknown ;;
+  esac
+
+  case $lt_cv_dlopen_self_static in
+  yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;;
+  *) enable_dlopen_self_static=unknown ;;
+  esac
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+striplib=
+old_striplib=
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5
+$as_echo_n "checking whether stripping libraries is possible... " >&6; }
+if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then
+  test -z "$old_striplib" && old_striplib="$STRIP --strip-debug"
+  test -z "$striplib" && striplib="$STRIP --strip-unneeded"
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+# FIXME - insert some real tests, host_os isn't really good enough
+  case $host_os in
+  darwin*)
+    if test -n "$STRIP" ; then
+      striplib="$STRIP -x"
+      old_striplib="$STRIP -S"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+    else
+      { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+    fi
+    ;;
+  *)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+    ;;
+  esac
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+  # Report which library types will actually be built
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5
+$as_echo_n "checking if libtool supports shared libraries... " >&6; }
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5
+$as_echo "$can_build_shared" >&6; }
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5
+$as_echo_n "checking whether to build shared libraries... " >&6; }
+  test "$can_build_shared" = "no" && enable_shared=no
+
+  # On AIX, shared libraries and static libraries use the same namespace, and
+  # are all built from PIC.
+  case $host_os in
+  aix3*)
+    test "$enable_shared" = yes && enable_static=no
+    if test -n "$RANLIB"; then
+      archive_cmds="$archive_cmds~\$RANLIB \$lib"
+      postinstall_cmds='$RANLIB $lib'
+    fi
+    ;;
+
+  aix[4-9]*)
+    if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+      test "$enable_shared" = yes && enable_static=no
+    fi
+    ;;
+  esac
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5
+$as_echo "$enable_shared" >&6; }
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5
+$as_echo_n "checking whether to build static libraries... " >&6; }
+  # Make sure either enable_shared or enable_static is yes.
+  test "$enable_shared" = yes || enable_static=yes
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5
+$as_echo "$enable_static" >&6; }
+
+
+
+
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+CC="$lt_save_CC"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+        ac_config_commands="$ac_config_commands libtool"
+
+
+
+
+# Only expand once:
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5
+$as_echo_n "checking whether ln -s works... " >&6; }
+LN_S=$as_ln_s
+if test "$LN_S" = "ln -s"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5
+$as_echo "no, using $LN_S" >&6; }
+fi
+
+
+#   If --with-pic=no is set we should honour that.
+ if test x$pic_mode = xno; then
+  NOPIC_TRUE=
+  NOPIC_FALSE='#'
+else
+  NOPIC_TRUE='#'
+  NOPIC_FALSE=
+fi
+
+
+#  Conditional defining whether we build with POSIX thread support.
+
+# Check whether --with-pthreads was given.
+if test "${with_pthreads+set}" = set; then :
+  withval=$with_pthreads; if test "$withval" = "yes"; then
+           use_pthreads="1"
+        else
+           use_pthreads="0"
+        fi
+else
+  use_pthreads="1"
+fi
+
+if test "$use_pthreads" = "1"; then
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthread" >&5
+$as_echo_n "checking for pthread_create in -lpthread... " >&6; }
+if ${ac_cv_lib_pthread_pthread_create+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpthread  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char pthread_create ();
+int
+main ()
+{
+return pthread_create ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_pthread_pthread_create=yes
+else
+  ac_cv_lib_pthread_pthread_create=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5
+$as_echo "$ac_cv_lib_pthread_pthread_create" >&6; }
+if test "x$ac_cv_lib_pthread_pthread_create" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBPTHREAD 1
+_ACEOF
+
+  LIBS="-lpthread $LIBS"
+
+else
+  use_pthreads="0"
+fi
+
+fi
+ if test x$use_pthreads = x0; then
+  NOTHREADS_TRUE=
+  NOTHREADS_FALSE='#'
+else
+  NOTHREADS_TRUE='#'
+  NOTHREADS_FALSE=
+fi
+
+THREADS=$use_pthreads
+
+
+# See which variadic function API to use
+for ac_header in stdarg.h varargs.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+ break
+fi
+
+done
+
+
+# Can we use backtrace?
+for ac_header in execinfo.h
+do :
+  ac_fn_c_check_header_mongrel "$LINENO" "execinfo.h" "ac_cv_header_execinfo_h" "$ac_includes_default"
+if test "x$ac_cv_header_execinfo_h" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_EXECINFO_H 1
+_ACEOF
+
+fi
+
+done
+
+for ac_func in backtrace
+do :
+  ac_fn_c_check_func "$LINENO" "backtrace" "ac_cv_func_backtrace"
+if test "x$ac_cv_func_backtrace" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_BACKTRACE 1
+_ACEOF
+
+fi
+done
+
+
+# Do we have reentrant strerror and strtok?
+for ac_func in strerror_r strtok_r
+do :
+  as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
+if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+done
+
+
+# Do we have vsnprintf?
+for ac_func in vsnprintf
+do :
+  ac_fn_c_check_func "$LINENO" "vsnprintf" "ac_cv_func_vsnprintf"
+if test "x$ac_cv_func_vsnprintf" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_VSNPRINTF 1
+_ACEOF
+
+fi
+done
+
+
+#  See if we have long doubles (used by the Mapping and Region classes)
+ac_fn_c_check_type "$LINENO" "long double" "ac_cv_type_long_double" "$ac_includes_default"
+if test "x$ac_cv_type_long_double" = xyes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_LONG_DOUBLE 1
+_ACEOF
+
+
+fi
+
+
+#  See if we have 64 bit integers.
+ac_fn_c_check_type "$LINENO" "int64_t" "ac_cv_type_int64_t" "$ac_includes_default"
+if test "x$ac_cv_type_int64_t" = xyes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_INT64_T 1
+_ACEOF
+
+
+fi
+ac_fn_c_check_type "$LINENO" "uint64_t" "ac_cv_type_uint64_t" "$ac_includes_default"
+if test "x$ac_cv_type_uint64_t" = xyes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_UINT64_T 1
+_ACEOF
+
+
+fi
+
+
+# Calculate alternative 64 bit integer sizes
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of long" >&5
+$as_echo_n "checking size of long... " >&6; }
+if ${ac_cv_sizeof_long+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (long))" "ac_cv_sizeof_long"        "$ac_includes_default"; then :
+
+else
+  if test "$ac_cv_type_long" = yes; then
+     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (long)
+See \`config.log' for more details" "$LINENO" 5; }
+   else
+     ac_cv_sizeof_long=0
+   fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_long" >&5
+$as_echo "$ac_cv_sizeof_long" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_LONG $ac_cv_sizeof_long
+_ACEOF
+
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of long long" >&5
+$as_echo_n "checking size of long long... " >&6; }
+if ${ac_cv_sizeof_long_long+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (long long))" "ac_cv_sizeof_long_long"        "$ac_includes_default"; then :
+
+else
+  if test "$ac_cv_type_long_long" = yes; then
+     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (long long)
+See \`config.log' for more details" "$LINENO" 5; }
+   else
+     ac_cv_sizeof_long_long=0
+   fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_long_long" >&5
+$as_echo "$ac_cv_sizeof_long_long" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_LONG_LONG $ac_cv_sizeof_long_long
+_ACEOF
+
+
+
+#  Conditional defining whether we want to support Fortran libraries
+#  (e.g. pgplot) and applications.
+
+# Check whether --with-fortran was given.
+if test "${with_fortran+set}" = set; then :
+  withval=$with_fortran; if test "$withval" = "yes"; then
+           use_fortran="1"
+        else
+           use_fortran="0"
+        fi
+else
+  use_fortran="1"
+fi
+
+ if test x$use_fortran = x0; then
+  NOFORTRAN_TRUE=
+  NOFORTRAN_FALSE='#'
+else
+  NOFORTRAN_TRUE='#'
+  NOFORTRAN_FALSE=
+fi
+
+FORTRAN=$use_fortran
+
+
+#   ast_link needs to be able to link against the Fortran runtime if
+#   necessary
+if test "$use_fortran" = "1"; then
+ac_ext=${ac_fc_srcext-f}
+ac_compile='$FC -c $FCFLAGS $ac_fcflags_srcext conftest.$ac_ext >&5'
+ac_link='$FC -o conftest$ac_exeext $FCFLAGS $LDFLAGS $ac_fcflags_srcext conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_fc_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  for ac_prog in gfortran g95 lf95 xlf95 f95 fort ifc ifort efc pgfortran pgf95 ftn nagfor xlf90 f90 pgf90 pghpf epcf90 g77 f77 xlf frt pgf77 cf77 fort77 fl32 af77 'f77 -old_f77'
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_FC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$FC"; then
+  ac_cv_prog_FC="$FC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_FC="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+FC=$ac_cv_prog_FC
+if test -n "$FC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $FC" >&5
+$as_echo "$FC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$FC" && break
+  done
+fi
+if test -z "$FC"; then
+  ac_ct_FC=$FC
+  for ac_prog in gfortran g95 lf95 xlf95 f95 fort ifc ifort efc pgfortran pgf95 ftn nagfor xlf90 f90 pgf90 pghpf epcf90 g77 f77 xlf frt pgf77 cf77 fort77 fl32 af77 'f77 -old_f77'
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_FC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_FC"; then
+  ac_cv_prog_ac_ct_FC="$ac_ct_FC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_FC="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_FC=$ac_cv_prog_ac_ct_FC
+if test -n "$ac_ct_FC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_FC" >&5
+$as_echo "$ac_ct_FC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_FC" && break
+done
+
+  if test "x$ac_ct_FC" = x; then
+    FC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    FC=$ac_ct_FC
+  fi
+fi
+
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for Fortran compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+rm -f a.out
+
+# If we don't use `.F' as extension, the preprocessor is not run on the
+# input file.  (Note that this only needs to work for GNU compilers.)
+ac_save_ext=$ac_ext
+ac_ext=F
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU Fortran compiler" >&5
+$as_echo_n "checking whether we are using the GNU Fortran compiler... " >&6; }
+if ${ac_cv_fc_compiler_gnu+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat > conftest.$ac_ext <<_ACEOF
+      program main
+#ifndef __GNUC__
+       choke me
+#endif
+
+      end
+_ACEOF
+if ac_fn_fc_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_fc_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_fc_compiler_gnu" >&5
+$as_echo "$ac_cv_fc_compiler_gnu" >&6; }
+ac_ext=$ac_save_ext
+ac_test_FCFLAGS=${FCFLAGS+set}
+ac_save_FCFLAGS=$FCFLAGS
+FCFLAGS=
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $FC accepts -g" >&5
+$as_echo_n "checking whether $FC accepts -g... " >&6; }
+if ${ac_cv_prog_fc_g+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  FCFLAGS=-g
+cat > conftest.$ac_ext <<_ACEOF
+      program main
+
+      end
+_ACEOF
+if ac_fn_fc_try_compile "$LINENO"; then :
+  ac_cv_prog_fc_g=yes
+else
+  ac_cv_prog_fc_g=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_fc_g" >&5
+$as_echo "$ac_cv_prog_fc_g" >&6; }
+if test "$ac_test_FCFLAGS" = set; then
+  FCFLAGS=$ac_save_FCFLAGS
+elif test $ac_cv_prog_fc_g = yes; then
+  if test "x$ac_cv_fc_compiler_gnu" = xyes; then
+    FCFLAGS="-g -O2"
+  else
+    FCFLAGS="-g"
+  fi
+else
+  if test "x$ac_cv_fc_compiler_gnu" = xyes; then
+    FCFLAGS="-O2"
+  else
+    FCFLAGS=
+  fi
+fi
+
+if test $ac_compiler_gnu = yes; then
+  GFC=yes
+else
+  GFC=
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+func_stripname_cnf ()
+{
+  case ${2} in
+  .*) func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%\\\\${2}\$%%"`;;
+  *)  func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%${2}\$%%"`;;
+  esac
+} # func_stripname_cnf
+
+
+      ac_ext=${ac_fc_srcext-f}
+ac_compile='$FC -c $FCFLAGS $ac_fcflags_srcext conftest.$ac_ext >&5'
+ac_link='$FC -o conftest$ac_exeext $FCFLAGS $LDFLAGS $ac_fcflags_srcext conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_fc_compiler_gnu
+
+
+if test -z "$FC" || test "X$FC" = "Xno"; then
+  _lt_disable_FC=yes
+fi
+
+archive_cmds_need_lc_FC=no
+allow_undefined_flag_FC=
+always_export_symbols_FC=no
+archive_expsym_cmds_FC=
+export_dynamic_flag_spec_FC=
+hardcode_direct_FC=no
+hardcode_direct_absolute_FC=no
+hardcode_libdir_flag_spec_FC=
+hardcode_libdir_separator_FC=
+hardcode_minus_L_FC=no
+hardcode_automatic_FC=no
+inherit_rpath_FC=no
+module_cmds_FC=
+module_expsym_cmds_FC=
+link_all_deplibs_FC=unknown
+old_archive_cmds_FC=$old_archive_cmds
+reload_flag_FC=$reload_flag
+reload_cmds_FC=$reload_cmds
+no_undefined_flag_FC=
+whole_archive_flag_spec_FC=
+enable_shared_with_static_runtimes_FC=no
+
+# Source file extension for fc test sources.
+ac_ext=${ac_fc_srcext-f}
+
+# Object file extension for compiled fc test sources.
+objext=o
+objext_FC=$objext
+
+# No sense in running all these tests if we already determined that
+# the FC compiler isn't working.  Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_disable_FC" != yes; then
+  # Code to be used in simple compile tests
+  lt_simple_compile_test_code="\
+      subroutine t
+      return
+      end
+"
+
+  # Code to be used in simple link tests
+  lt_simple_link_test_code="\
+      program t
+      end
+"
+
+  # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+
+
+
+
+
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+
+
+  # save warnings/boilerplate of simple test code
+  ac_outfile=conftest.$ac_objext
+echo "$lt_simple_compile_test_code" >conftest.$ac_ext
+eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_compiler_boilerplate=`cat conftest.err`
+$RM conftest*
+
+  ac_outfile=conftest.$ac_objext
+echo "$lt_simple_link_test_code" >conftest.$ac_ext
+eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_linker_boilerplate=`cat conftest.err`
+$RM -r conftest*
+
+
+  # Allow CC to be a program name with arguments.
+  lt_save_CC="$CC"
+  lt_save_GCC=$GCC
+  lt_save_CFLAGS=$CFLAGS
+  CC=${FC-"f95"}
+  CFLAGS=$FCFLAGS
+  compiler=$CC
+  GCC=$ac_cv_fc_compiler_gnu
+
+  compiler_FC=$CC
+  for cc_temp in $compiler""; do
+  case $cc_temp in
+    compile | *[\\/]compile | ccache | *[\\/]ccache ) ;;
+    distcc | *[\\/]distcc | purify | *[\\/]purify ) ;;
+    \-*) ;;
+    *) break;;
+  esac
+done
+cc_basename=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+
+
+  if test -n "$compiler"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5
+$as_echo_n "checking if libtool supports shared libraries... " >&6; }
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5
+$as_echo "$can_build_shared" >&6; }
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5
+$as_echo_n "checking whether to build shared libraries... " >&6; }
+    test "$can_build_shared" = "no" && enable_shared=no
+
+    # On AIX, shared libraries and static libraries use the same namespace, and
+    # are all built from PIC.
+    case $host_os in
+      aix3*)
+        test "$enable_shared" = yes && enable_static=no
+        if test -n "$RANLIB"; then
+          archive_cmds="$archive_cmds~\$RANLIB \$lib"
+          postinstall_cmds='$RANLIB $lib'
+        fi
+        ;;
+      aix[4-9]*)
+	if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+	  test "$enable_shared" = yes && enable_static=no
+	fi
+        ;;
+    esac
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5
+$as_echo "$enable_shared" >&6; }
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5
+$as_echo_n "checking whether to build static libraries... " >&6; }
+    # Make sure either enable_shared or enable_static is yes.
+    test "$enable_shared" = yes || enable_static=yes
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5
+$as_echo "$enable_static" >&6; }
+
+    GCC_FC="$ac_cv_fc_compiler_gnu"
+    LD_FC="$LD"
+
+    ## CAVEAT EMPTOR:
+    ## There is no encapsulation within the following macros, do not change
+    ## the running order or otherwise move them around unless you know exactly
+    ## what you are doing...
+    # Dependencies to place before and after the object being linked:
+predep_objects_FC=
+postdep_objects_FC=
+predeps_FC=
+postdeps_FC=
+compiler_lib_search_path_FC=
+
+cat > conftest.$ac_ext <<_LT_EOF
+      subroutine foo
+      implicit none
+      integer a
+      a=0
+      return
+      end
+_LT_EOF
+
+
+_lt_libdeps_save_CFLAGS=$CFLAGS
+case "$CC $CFLAGS " in #(
+*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;;
+*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;;
+*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;;
+esac
+
+if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  # Parse the compiler output and extract the necessary
+  # objects, libraries and library flags.
+
+  # Sentinel used to keep track of whether or not we are before
+  # the conftest object file.
+  pre_test_object_deps_done=no
+
+  for p in `eval "$output_verbose_link_cmd"`; do
+    case ${prev}${p} in
+
+    -L* | -R* | -l*)
+       # Some compilers place space between "-{L,R}" and the path.
+       # Remove the space.
+       if test $p = "-L" ||
+          test $p = "-R"; then
+	 prev=$p
+	 continue
+       fi
+
+       # Expand the sysroot to ease extracting the directories later.
+       if test -z "$prev"; then
+         case $p in
+         -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;;
+         -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;;
+         -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;;
+         esac
+       fi
+       case $p in
+       =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;;
+       esac
+       if test "$pre_test_object_deps_done" = no; then
+	 case ${prev} in
+	 -L | -R)
+	   # Internal compiler library paths should come after those
+	   # provided the user.  The postdeps already come after the
+	   # user supplied libs so there is no need to process them.
+	   if test -z "$compiler_lib_search_path_FC"; then
+	     compiler_lib_search_path_FC="${prev}${p}"
+	   else
+	     compiler_lib_search_path_FC="${compiler_lib_search_path_FC} ${prev}${p}"
+	   fi
+	   ;;
+	 # The "-l" case would never come before the object being
+	 # linked, so don't bother handling this case.
+	 esac
+       else
+	 if test -z "$postdeps_FC"; then
+	   postdeps_FC="${prev}${p}"
+	 else
+	   postdeps_FC="${postdeps_FC} ${prev}${p}"
+	 fi
+       fi
+       prev=
+       ;;
+
+    *.lto.$objext) ;; # Ignore GCC LTO objects
+    *.$objext)
+       # This assumes that the test object file only shows up
+       # once in the compiler output.
+       if test "$p" = "conftest.$objext"; then
+	 pre_test_object_deps_done=yes
+	 continue
+       fi
+
+       if test "$pre_test_object_deps_done" = no; then
+	 if test -z "$predep_objects_FC"; then
+	   predep_objects_FC="$p"
+	 else
+	   predep_objects_FC="$predep_objects_FC $p"
+	 fi
+       else
+	 if test -z "$postdep_objects_FC"; then
+	   postdep_objects_FC="$p"
+	 else
+	   postdep_objects_FC="$postdep_objects_FC $p"
+	 fi
+       fi
+       ;;
+
+    *) ;; # Ignore the rest.
+
+    esac
+  done
+
+  # Clean up.
+  rm -f a.out a.exe
+else
+  echo "libtool.m4: error: problem compiling FC test program"
+fi
+
+$RM -f confest.$objext
+CFLAGS=$_lt_libdeps_save_CFLAGS
+
+# PORTME: override above test on systems where it is broken
+
+
+case " $postdeps_FC " in
+*" -lc "*) archive_cmds_need_lc_FC=no ;;
+esac
+ compiler_lib_search_dirs_FC=
+if test -n "${compiler_lib_search_path_FC}"; then
+ compiler_lib_search_dirs_FC=`echo " ${compiler_lib_search_path_FC}" | ${SED} -e 's! -L! !g' -e 's!^ !!'`
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    lt_prog_compiler_wl_FC=
+lt_prog_compiler_pic_FC=
+lt_prog_compiler_static_FC=
+
+
+  if test "$GCC" = yes; then
+    lt_prog_compiler_wl_FC='-Wl,'
+    lt_prog_compiler_static_FC='-static'
+
+    case $host_os in
+      aix*)
+      # All AIX code is PIC.
+      if test "$host_cpu" = ia64; then
+	# AIX 5 now supports IA64 processor
+	lt_prog_compiler_static_FC='-Bstatic'
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            lt_prog_compiler_pic_FC='-fPIC'
+        ;;
+      m68k)
+            # FIXME: we need at least 68020 code to build shared libraries, but
+            # adding the `-m68020' flag to GCC prevents building anything better,
+            # like `-m68040'.
+            lt_prog_compiler_pic_FC='-m68020 -resident32 -malways-restore-a4'
+        ;;
+      esac
+      ;;
+
+    beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+      # PIC is the default for these OSes.
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      # Although the cygwin gcc ignores -fPIC, still need this for old-style
+      # (--disable-auto-import) libraries
+      lt_prog_compiler_pic_FC='-DDLL_EXPORT'
+      ;;
+
+    darwin* | rhapsody*)
+      # PIC is the default on this platform
+      # Common symbols not allowed in MH_DYLIB files
+      lt_prog_compiler_pic_FC='-fno-common'
+      ;;
+
+    haiku*)
+      # PIC is the default for Haiku.
+      # The "-static" flag exists, but is broken.
+      lt_prog_compiler_static_FC=
+      ;;
+
+    hpux*)
+      # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+      # PA HP-UX.  On IA64 HP-UX, PIC is the default but the pic flag
+      # sets the default TLS model and affects inlining.
+      case $host_cpu in
+      hppa*64*)
+	# +Z the default
+	;;
+      *)
+	lt_prog_compiler_pic_FC='-fPIC'
+	;;
+      esac
+      ;;
+
+    interix[3-9]*)
+      # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+      # Instead, we relocate shared libraries at runtime.
+      ;;
+
+    msdosdjgpp*)
+      # Just because we use GCC doesn't mean we suddenly get shared libraries
+      # on systems that don't support them.
+      lt_prog_compiler_can_build_shared_FC=no
+      enable_shared=no
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      lt_prog_compiler_pic_FC='-fPIC -shared'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+	lt_prog_compiler_pic_FC=-Kconform_pic
+      fi
+      ;;
+
+    *)
+      lt_prog_compiler_pic_FC='-fPIC'
+      ;;
+    esac
+
+    case $cc_basename in
+    nvcc*) # Cuda Compiler Driver 2.2
+      lt_prog_compiler_wl_FC='-Xlinker '
+      if test -n "$lt_prog_compiler_pic_FC"; then
+        lt_prog_compiler_pic_FC="-Xcompiler $lt_prog_compiler_pic_FC"
+      fi
+      ;;
+    esac
+  else
+    # PORTME Check for flag to pass linker flags through the system compiler.
+    case $host_os in
+    aix*)
+      lt_prog_compiler_wl_FC='-Wl,'
+      if test "$host_cpu" = ia64; then
+	# AIX 5 now supports IA64 processor
+	lt_prog_compiler_static_FC='-Bstatic'
+      else
+	lt_prog_compiler_static_FC='-bnso -bI:/lib/syscalls.exp'
+      fi
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      lt_prog_compiler_pic_FC='-DDLL_EXPORT'
+      ;;
+
+    hpux9* | hpux10* | hpux11*)
+      lt_prog_compiler_wl_FC='-Wl,'
+      # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but
+      # not for PA HP-UX.
+      case $host_cpu in
+      hppa*64*|ia64*)
+	# +Z the default
+	;;
+      *)
+	lt_prog_compiler_pic_FC='+Z'
+	;;
+      esac
+      # Is there a better lt_prog_compiler_static that works with the bundled CC?
+      lt_prog_compiler_static_FC='${wl}-a ${wl}archive'
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      lt_prog_compiler_wl_FC='-Wl,'
+      # PIC (with -KPIC) is the default.
+      lt_prog_compiler_static_FC='-non_shared'
+      ;;
+
+    linux* | k*bsd*-gnu | kopensolaris*-gnu)
+      case $cc_basename in
+      # old Intel for x86_64 which still supported -KPIC.
+      ecc*)
+	lt_prog_compiler_wl_FC='-Wl,'
+	lt_prog_compiler_pic_FC='-KPIC'
+	lt_prog_compiler_static_FC='-static'
+        ;;
+      # icc used to be incompatible with GCC.
+      # ICC 10 doesn't accept -KPIC any more.
+      icc* | ifort*)
+	lt_prog_compiler_wl_FC='-Wl,'
+	lt_prog_compiler_pic_FC='-fPIC'
+	lt_prog_compiler_static_FC='-static'
+        ;;
+      # Lahey Fortran 8.1.
+      lf95*)
+	lt_prog_compiler_wl_FC='-Wl,'
+	lt_prog_compiler_pic_FC='--shared'
+	lt_prog_compiler_static_FC='--static'
+	;;
+      nagfor*)
+	# NAG Fortran compiler
+	lt_prog_compiler_wl_FC='-Wl,-Wl,,'
+	lt_prog_compiler_pic_FC='-PIC'
+	lt_prog_compiler_static_FC='-Bstatic'
+	;;
+      pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*)
+        # Portland Group compilers (*not* the Pentium gcc compiler,
+	# which looks to be a dead project)
+	lt_prog_compiler_wl_FC='-Wl,'
+	lt_prog_compiler_pic_FC='-fpic'
+	lt_prog_compiler_static_FC='-Bstatic'
+        ;;
+      ccc*)
+        lt_prog_compiler_wl_FC='-Wl,'
+        # All Alpha code is PIC.
+        lt_prog_compiler_static_FC='-non_shared'
+        ;;
+      xl* | bgxl* | bgf* | mpixl*)
+	# IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene
+	lt_prog_compiler_wl_FC='-Wl,'
+	lt_prog_compiler_pic_FC='-qpic'
+	lt_prog_compiler_static_FC='-qstaticlink'
+	;;
+      *)
+	case `$CC -V 2>&1 | sed 5q` in
+	*Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*)
+	  # Sun Fortran 8.3 passes all unrecognized flags to the linker
+	  lt_prog_compiler_pic_FC='-KPIC'
+	  lt_prog_compiler_static_FC='-Bstatic'
+	  lt_prog_compiler_wl_FC=''
+	  ;;
+	*Sun\ F* | *Sun*Fortran*)
+	  lt_prog_compiler_pic_FC='-KPIC'
+	  lt_prog_compiler_static_FC='-Bstatic'
+	  lt_prog_compiler_wl_FC='-Qoption ld '
+	  ;;
+	*Sun\ C*)
+	  # Sun C 5.9
+	  lt_prog_compiler_pic_FC='-KPIC'
+	  lt_prog_compiler_static_FC='-Bstatic'
+	  lt_prog_compiler_wl_FC='-Wl,'
+	  ;;
+        *Intel*\ [CF]*Compiler*)
+	  lt_prog_compiler_wl_FC='-Wl,'
+	  lt_prog_compiler_pic_FC='-fPIC'
+	  lt_prog_compiler_static_FC='-static'
+	  ;;
+	*Portland\ Group*)
+	  lt_prog_compiler_wl_FC='-Wl,'
+	  lt_prog_compiler_pic_FC='-fpic'
+	  lt_prog_compiler_static_FC='-Bstatic'
+	  ;;
+	esac
+	;;
+      esac
+      ;;
+
+    newsos6)
+      lt_prog_compiler_pic_FC='-KPIC'
+      lt_prog_compiler_static_FC='-Bstatic'
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      lt_prog_compiler_pic_FC='-fPIC -shared'
+      ;;
+
+    osf3* | osf4* | osf5*)
+      lt_prog_compiler_wl_FC='-Wl,'
+      # All OSF/1 code is PIC.
+      lt_prog_compiler_static_FC='-non_shared'
+      ;;
+
+    rdos*)
+      lt_prog_compiler_static_FC='-non_shared'
+      ;;
+
+    solaris*)
+      lt_prog_compiler_pic_FC='-KPIC'
+      lt_prog_compiler_static_FC='-Bstatic'
+      case $cc_basename in
+      f77* | f90* | f95* | sunf77* | sunf90* | sunf95*)
+	lt_prog_compiler_wl_FC='-Qoption ld ';;
+      *)
+	lt_prog_compiler_wl_FC='-Wl,';;
+      esac
+      ;;
+
+    sunos4*)
+      lt_prog_compiler_wl_FC='-Qoption ld '
+      lt_prog_compiler_pic_FC='-PIC'
+      lt_prog_compiler_static_FC='-Bstatic'
+      ;;
+
+    sysv4 | sysv4.2uw2* | sysv4.3*)
+      lt_prog_compiler_wl_FC='-Wl,'
+      lt_prog_compiler_pic_FC='-KPIC'
+      lt_prog_compiler_static_FC='-Bstatic'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec ;then
+	lt_prog_compiler_pic_FC='-Kconform_pic'
+	lt_prog_compiler_static_FC='-Bstatic'
+      fi
+      ;;
+
+    sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+      lt_prog_compiler_wl_FC='-Wl,'
+      lt_prog_compiler_pic_FC='-KPIC'
+      lt_prog_compiler_static_FC='-Bstatic'
+      ;;
+
+    unicos*)
+      lt_prog_compiler_wl_FC='-Wl,'
+      lt_prog_compiler_can_build_shared_FC=no
+      ;;
+
+    uts4*)
+      lt_prog_compiler_pic_FC='-pic'
+      lt_prog_compiler_static_FC='-Bstatic'
+      ;;
+
+    *)
+      lt_prog_compiler_can_build_shared_FC=no
+      ;;
+    esac
+  fi
+
+case $host_os in
+  # For platforms which do not support PIC, -DPIC is meaningless:
+  *djgpp*)
+    lt_prog_compiler_pic_FC=
+    ;;
+  *)
+    lt_prog_compiler_pic_FC="$lt_prog_compiler_pic_FC"
+    ;;
+esac
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5
+$as_echo_n "checking for $compiler option to produce PIC... " >&6; }
+if ${lt_cv_prog_compiler_pic_FC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_pic_FC=$lt_prog_compiler_pic_FC
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_FC" >&5
+$as_echo "$lt_cv_prog_compiler_pic_FC" >&6; }
+lt_prog_compiler_pic_FC=$lt_cv_prog_compiler_pic_FC
+
+#
+# Check to make sure the PIC flag actually works.
+#
+if test -n "$lt_prog_compiler_pic_FC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic_FC works" >&5
+$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic_FC works... " >&6; }
+if ${lt_cv_prog_compiler_pic_works_FC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_pic_works_FC=no
+   ac_outfile=conftest.$ac_objext
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+   lt_compiler_flag="$lt_prog_compiler_pic_FC"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   # The option is referenced via a variable to avoid confusing sed.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>conftest.err)
+   ac_status=$?
+   cat conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s "$ac_outfile"; then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings other than the usual output.
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+     $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+     if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_pic_works_FC=yes
+     fi
+   fi
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works_FC" >&5
+$as_echo "$lt_cv_prog_compiler_pic_works_FC" >&6; }
+
+if test x"$lt_cv_prog_compiler_pic_works_FC" = xyes; then
+    case $lt_prog_compiler_pic_FC in
+     "" | " "*) ;;
+     *) lt_prog_compiler_pic_FC=" $lt_prog_compiler_pic_FC" ;;
+     esac
+else
+    lt_prog_compiler_pic_FC=
+     lt_prog_compiler_can_build_shared_FC=no
+fi
+
+fi
+
+
+
+
+
+#
+# Check to make sure the static flag actually works.
+#
+wl=$lt_prog_compiler_wl_FC eval lt_tmp_static_flag=\"$lt_prog_compiler_static_FC\"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5
+$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; }
+if ${lt_cv_prog_compiler_static_works_FC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_static_works_FC=no
+   save_LDFLAGS="$LDFLAGS"
+   LDFLAGS="$LDFLAGS $lt_tmp_static_flag"
+   echo "$lt_simple_link_test_code" > conftest.$ac_ext
+   if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+     # The linker can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     if test -s conftest.err; then
+       # Append any errors to the config.log.
+       cat conftest.err 1>&5
+       $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+       $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+       if diff conftest.exp conftest.er2 >/dev/null; then
+         lt_cv_prog_compiler_static_works_FC=yes
+       fi
+     else
+       lt_cv_prog_compiler_static_works_FC=yes
+     fi
+   fi
+   $RM -r conftest*
+   LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works_FC" >&5
+$as_echo "$lt_cv_prog_compiler_static_works_FC" >&6; }
+
+if test x"$lt_cv_prog_compiler_static_works_FC" = xyes; then
+    :
+else
+    lt_prog_compiler_static_FC=
+fi
+
+
+
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if ${lt_cv_prog_compiler_c_o_FC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_c_o_FC=no
+   $RM -r conftest 2>/dev/null
+   mkdir conftest
+   cd conftest
+   mkdir out
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+   lt_compiler_flag="-o out/conftest2.$ac_objext"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>out/conftest.err)
+   ac_status=$?
+   cat out/conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s out/conftest2.$ac_objext
+   then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+     $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+     if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_c_o_FC=yes
+     fi
+   fi
+   chmod u+w . 2>&5
+   $RM conftest*
+   # SGI C++ compiler will create directory out/ii_files/ for
+   # template instantiation
+   test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+   $RM out/* && rmdir out
+   cd ..
+   $RM -r conftest
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_FC" >&5
+$as_echo "$lt_cv_prog_compiler_c_o_FC" >&6; }
+
+
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if ${lt_cv_prog_compiler_c_o_FC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_c_o_FC=no
+   $RM -r conftest 2>/dev/null
+   mkdir conftest
+   cd conftest
+   mkdir out
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+   lt_compiler_flag="-o out/conftest2.$ac_objext"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>out/conftest.err)
+   ac_status=$?
+   cat out/conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s out/conftest2.$ac_objext
+   then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+     $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+     if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_c_o_FC=yes
+     fi
+   fi
+   chmod u+w . 2>&5
+   $RM conftest*
+   # SGI C++ compiler will create directory out/ii_files/ for
+   # template instantiation
+   test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+   $RM out/* && rmdir out
+   cd ..
+   $RM -r conftest
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_FC" >&5
+$as_echo "$lt_cv_prog_compiler_c_o_FC" >&6; }
+
+
+
+
+hard_links="nottested"
+if test "$lt_cv_prog_compiler_c_o_FC" = no && test "$need_locks" != no; then
+  # do not overwrite the value of need_locks provided by the user
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5
+$as_echo_n "checking if we can lock with hard links... " >&6; }
+  hard_links=yes
+  $RM conftest*
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  touch conftest.a
+  ln conftest.a conftest.b 2>&5 || hard_links=no
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5
+$as_echo "$hard_links" >&6; }
+  if test "$hard_links" = no; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5
+$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;}
+    need_locks=warn
+  fi
+else
+  need_locks=no
+fi
+
+
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5
+$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; }
+
+  runpath_var=
+  allow_undefined_flag_FC=
+  always_export_symbols_FC=no
+  archive_cmds_FC=
+  archive_expsym_cmds_FC=
+  compiler_needs_object_FC=no
+  enable_shared_with_static_runtimes_FC=no
+  export_dynamic_flag_spec_FC=
+  export_symbols_cmds_FC='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+  hardcode_automatic_FC=no
+  hardcode_direct_FC=no
+  hardcode_direct_absolute_FC=no
+  hardcode_libdir_flag_spec_FC=
+  hardcode_libdir_separator_FC=
+  hardcode_minus_L_FC=no
+  hardcode_shlibpath_var_FC=unsupported
+  inherit_rpath_FC=no
+  link_all_deplibs_FC=unknown
+  module_cmds_FC=
+  module_expsym_cmds_FC=
+  old_archive_from_new_cmds_FC=
+  old_archive_from_expsyms_cmds_FC=
+  thread_safe_flag_spec_FC=
+  whole_archive_flag_spec_FC=
+  # include_expsyms should be a list of space-separated symbols to be *always*
+  # included in the symbol list
+  include_expsyms_FC=
+  # exclude_expsyms can be an extended regexp of symbols to exclude
+  # it will be wrapped by ` (' and `)$', so one must not match beginning or
+  # end of line.  Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc',
+  # as well as any symbol that contains `d'.
+  exclude_expsyms_FC='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'
+  # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+  # platforms (ab)use it in PIC code, but their linkers get confused if
+  # the symbol is explicitly referenced.  Since portable code cannot
+  # rely on this symbol name, it's probably fine to never include it in
+  # preloaded symbol tables.
+  # Exclude shared library initialization/finalization symbols.
+  extract_expsyms_cmds=
+
+  case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    # FIXME: the MSVC++ port hasn't been tested in a loooong time
+    # When not using gcc, we currently assume that we are using
+    # Microsoft Visual C++.
+    if test "$GCC" != yes; then
+      with_gnu_ld=no
+    fi
+    ;;
+  interix*)
+    # we just hope/assume this is gcc and not c89 (= MSVC++)
+    with_gnu_ld=yes
+    ;;
+  openbsd*)
+    with_gnu_ld=no
+    ;;
+  esac
+
+  ld_shlibs_FC=yes
+
+  # On some targets, GNU ld is compatible enough with the native linker
+  # that we're better off using the native interface for both.
+  lt_use_gnu_ld_interface=no
+  if test "$with_gnu_ld" = yes; then
+    case $host_os in
+      aix*)
+	# The AIX port of GNU ld has always aspired to compatibility
+	# with the native linker.  However, as the warning in the GNU ld
+	# block says, versions before 2.19.5* couldn't really create working
+	# shared libraries, regardless of the interface used.
+	case `$LD -v 2>&1` in
+	  *\ \(GNU\ Binutils\)\ 2.19.5*) ;;
+	  *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;;
+	  *\ \(GNU\ Binutils\)\ [3-9]*) ;;
+	  *)
+	    lt_use_gnu_ld_interface=yes
+	    ;;
+	esac
+	;;
+      *)
+	lt_use_gnu_ld_interface=yes
+	;;
+    esac
+  fi
+
+  if test "$lt_use_gnu_ld_interface" = yes; then
+    # If archive_cmds runs LD, not CC, wlarc should be empty
+    wlarc='${wl}'
+
+    # Set some defaults for GNU ld with shared library support. These
+    # are reset later if shared libraries are not supported. Putting them
+    # here allows them to be overridden if necessary.
+    runpath_var=LD_RUN_PATH
+    hardcode_libdir_flag_spec_FC='${wl}-rpath ${wl}$libdir'
+    export_dynamic_flag_spec_FC='${wl}--export-dynamic'
+    # ancient GNU ld didn't support --whole-archive et. al.
+    if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then
+      whole_archive_flag_spec_FC="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+    else
+      whole_archive_flag_spec_FC=
+    fi
+    supports_anon_versioning=no
+    case `$LD -v 2>&1` in
+      *GNU\ gold*) supports_anon_versioning=yes ;;
+      *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11
+      *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ...
+      *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ...
+      *\ 2.11.*) ;; # other 2.11 versions
+      *) supports_anon_versioning=yes ;;
+    esac
+
+    # See if GNU ld supports shared libraries.
+    case $host_os in
+    aix[3-9]*)
+      # On AIX/PPC, the GNU linker is very broken
+      if test "$host_cpu" != ia64; then
+	ld_shlibs_FC=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.19, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support.  If you
+*** really care for shared libraries, you may want to install binutils
+*** 2.20 or above, or modify your PATH so that a non-GNU linker is found.
+*** You will then need to restart the configuration process.
+
+_LT_EOF
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            archive_cmds_FC='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            archive_expsym_cmds_FC=''
+        ;;
+      m68k)
+            archive_cmds_FC='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            hardcode_libdir_flag_spec_FC='-L$libdir'
+            hardcode_minus_L_FC=yes
+        ;;
+      esac
+      ;;
+
+    beos*)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	allow_undefined_flag_FC=unsupported
+	# Joseph Beckenbach <[email-masked]> says some releases of gcc
+	# support --undefined.  This deserves some investigation.  FIXME
+	archive_cmds_FC='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      else
+	ld_shlibs_FC=no
+      fi
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # _LT_TAGVAR(hardcode_libdir_flag_spec, FC) is actually meaningless,
+      # as there is no search path for DLLs.
+      hardcode_libdir_flag_spec_FC='-L$libdir'
+      export_dynamic_flag_spec_FC='${wl}--export-all-symbols'
+      allow_undefined_flag_FC=unsupported
+      always_export_symbols_FC=no
+      enable_shared_with_static_runtimes_FC=yes
+      export_symbols_cmds_FC='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols'
+      exclude_expsyms_FC='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'
+
+      if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+        archive_cmds_FC='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+	# If the export-symbols file already is a .def file (1st line
+	# is EXPORTS), use it as is; otherwise, prepend...
+	archive_expsym_cmds_FC='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	  cp $export_symbols $output_objdir/$soname.def;
+	else
+	  echo EXPORTS > $output_objdir/$soname.def;
+	  cat $export_symbols >> $output_objdir/$soname.def;
+	fi~
+	$CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+      else
+	ld_shlibs_FC=no
+      fi
+      ;;
+
+    haiku*)
+      archive_cmds_FC='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      link_all_deplibs_FC=yes
+      ;;
+
+    interix[3-9]*)
+      hardcode_direct_FC=no
+      hardcode_shlibpath_var_FC=no
+      hardcode_libdir_flag_spec_FC='${wl}-rpath,$libdir'
+      export_dynamic_flag_spec_FC='${wl}-E'
+      # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+      # Instead, shared libraries are loaded at an image base (0x10000000 by
+      # default) and relocated if they conflict, which is a slow very memory
+      # consuming and fragmenting process.  To avoid this, we pick a random,
+      # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+      # time.  Moving up from 0x10000000 also allows more sbrk(2) space.
+      archive_cmds_FC='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      archive_expsym_cmds_FC='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      ;;
+
+    gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu)
+      tmp_diet=no
+      if test "$host_os" = linux-dietlibc; then
+	case $cc_basename in
+	  diet\ *) tmp_diet=yes;;	# linux-dietlibc with static linking (!diet-dyn)
+	esac
+      fi
+      if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \
+	 && test "$tmp_diet" = no
+      then
+	tmp_addflag=' $pic_flag'
+	tmp_sharedflag='-shared'
+	case $cc_basename,$host_cpu in
+        pgcc*)				# Portland Group C compiler
+	  whole_archive_flag_spec_FC='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  tmp_addflag=' $pic_flag'
+	  ;;
+	pgf77* | pgf90* | pgf95* | pgfortran*)
+					# Portland Group f77 and f90 compilers
+	  whole_archive_flag_spec_FC='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  tmp_addflag=' $pic_flag -Mnomain' ;;
+	ecc*,ia64* | icc*,ia64*)	# Intel C compiler on ia64
+	  tmp_addflag=' -i_dynamic' ;;
+	efc*,ia64* | ifort*,ia64*)	# Intel Fortran compiler on ia64
+	  tmp_addflag=' -i_dynamic -nofor_main' ;;
+	ifc* | ifort*)			# Intel Fortran compiler
+	  tmp_addflag=' -nofor_main' ;;
+	lf95*)				# Lahey Fortran 8.1
+	  whole_archive_flag_spec_FC=
+	  tmp_sharedflag='--shared' ;;
+	xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below)
+	  tmp_sharedflag='-qmkshrobj'
+	  tmp_addflag= ;;
+	nvcc*)	# Cuda Compiler Driver 2.2
+	  whole_archive_flag_spec_FC='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  compiler_needs_object_FC=yes
+	  ;;
+	esac
+	case `$CC -V 2>&1 | sed 5q` in
+	*Sun\ C*)			# Sun C 5.9
+	  whole_archive_flag_spec_FC='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  compiler_needs_object_FC=yes
+	  tmp_sharedflag='-G' ;;
+	*Sun\ F*)			# Sun Fortran 8.3
+	  tmp_sharedflag='-G' ;;
+	esac
+	archive_cmds_FC='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+
+        if test "x$supports_anon_versioning" = xyes; then
+          archive_expsym_cmds_FC='echo "{ global:" > $output_objdir/$libname.ver~
+	    cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+	    echo "local: *; };" >> $output_objdir/$libname.ver~
+	    $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+        fi
+
+	case $cc_basename in
+	xlf* | bgf* | bgxlf* | mpixlf*)
+	  # IBM XL Fortran 10.1 on PPC cannot create shared libs itself
+	  whole_archive_flag_spec_FC='--whole-archive$convenience --no-whole-archive'
+	  hardcode_libdir_flag_spec_FC='${wl}-rpath ${wl}$libdir'
+	  archive_cmds_FC='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib'
+	  if test "x$supports_anon_versioning" = xyes; then
+	    archive_expsym_cmds_FC='echo "{ global:" > $output_objdir/$libname.ver~
+	      cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+	      echo "local: *; };" >> $output_objdir/$libname.ver~
+	      $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib'
+	  fi
+	  ;;
+	esac
+      else
+        ld_shlibs_FC=no
+      fi
+      ;;
+
+    netbsd*)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+	archive_cmds_FC='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+	wlarc=
+      else
+	archive_cmds_FC='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	archive_expsym_cmds_FC='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      fi
+      ;;
+
+    solaris*)
+      if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then
+	ld_shlibs_FC=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+      elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	archive_cmds_FC='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	archive_expsym_cmds_FC='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+	ld_shlibs_FC=no
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
+      case `$LD -v 2>&1` in
+        *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*)
+	ld_shlibs_FC=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not
+*** reliably create shared libraries on SCO systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.16.91.0.3 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+	;;
+	*)
+	  # For security reasons, it is highly recommended that you always
+	  # use absolute paths for naming shared libraries, and exclude the
+	  # DT_RUNPATH tag from executables and libraries.  But doing so
+	  # requires that you compile everything twice, which is a pain.
+	  if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	    hardcode_libdir_flag_spec_FC='${wl}-rpath ${wl}$libdir'
+	    archive_cmds_FC='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	    archive_expsym_cmds_FC='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+	  else
+	    ld_shlibs_FC=no
+	  fi
+	;;
+      esac
+      ;;
+
+    sunos4*)
+      archive_cmds_FC='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      wlarc=
+      hardcode_direct_FC=yes
+      hardcode_shlibpath_var_FC=no
+      ;;
+
+    *)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	archive_cmds_FC='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	archive_expsym_cmds_FC='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+	ld_shlibs_FC=no
+      fi
+      ;;
+    esac
+
+    if test "$ld_shlibs_FC" = no; then
+      runpath_var=
+      hardcode_libdir_flag_spec_FC=
+      export_dynamic_flag_spec_FC=
+      whole_archive_flag_spec_FC=
+    fi
+  else
+    # PORTME fill in a description of your system's linker (not GNU ld)
+    case $host_os in
+    aix3*)
+      allow_undefined_flag_FC=unsupported
+      always_export_symbols_FC=yes
+      archive_expsym_cmds_FC='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+      # Note: this linker hardcodes the directories in LIBPATH if there
+      # are no directories specified by -L.
+      hardcode_minus_L_FC=yes
+      if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then
+	# Neither direct hardcoding nor static linking is supported with a
+	# broken collect2.
+	hardcode_direct_FC=unsupported
+      fi
+      ;;
+
+    aix[4-9]*)
+      if test "$host_cpu" = ia64; then
+	# On IA64, the linker does run time linking by default, so we don't
+	# have to do anything special.
+	aix_use_runtimelinking=no
+	exp_sym_flag='-Bexport'
+	no_entry_flag=""
+      else
+	# If we're using GNU nm, then we don't want the "-C" option.
+	# -C means demangle to AIX nm, but means don't demangle with GNU nm
+	# Also, AIX nm treats weak defined symbols like other global
+	# defined symbols, whereas GNU nm marks them as "W".
+	if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+	  export_symbols_cmds_FC='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+	else
+	  export_symbols_cmds_FC='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+	fi
+	aix_use_runtimelinking=no
+
+	# Test if we are trying to use run time linking or normal
+	# AIX style linking. If -brtl is somewhere in LDFLAGS, we
+	# need to do runtime linking.
+	case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*)
+	  for ld_flag in $LDFLAGS; do
+	  if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
+	    aix_use_runtimelinking=yes
+	    break
+	  fi
+	  done
+	  ;;
+	esac
+
+	exp_sym_flag='-bexport'
+	no_entry_flag='-bnoentry'
+      fi
+
+      # When large executables or shared objects are built, AIX ld can
+      # have problems creating the table of contents.  If linking a library
+      # or program results in "error TOC overflow" add -mminimal-toc to
+      # CXXFLAGS/CFLAGS for g++/gcc.  In the cases where that is not
+      # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+      archive_cmds_FC=''
+      hardcode_direct_FC=yes
+      hardcode_direct_absolute_FC=yes
+      hardcode_libdir_separator_FC=':'
+      link_all_deplibs_FC=yes
+      file_list_spec_FC='${wl}-f,'
+
+      if test "$GCC" = yes; then
+	case $host_os in aix4.[012]|aix4.[012].*)
+	# We only want to do this on AIX 4.2 and lower, the check
+	# below for broken collect2 doesn't work under 4.3+
+	  collect2name=`${CC} -print-prog-name=collect2`
+	  if test -f "$collect2name" &&
+	   strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+	  then
+	  # We have reworked collect2
+	  :
+	  else
+	  # We have old collect2
+	  hardcode_direct_FC=unsupported
+	  # It fails to find uninstalled libraries when the uninstalled
+	  # path is not listed in the libpath.  Setting hardcode_minus_L
+	  # to unsupported forces relinking
+	  hardcode_minus_L_FC=yes
+	  hardcode_libdir_flag_spec_FC='-L$libdir'
+	  hardcode_libdir_separator_FC=
+	  fi
+	  ;;
+	esac
+	shared_flag='-shared'
+	if test "$aix_use_runtimelinking" = yes; then
+	  shared_flag="$shared_flag "'${wl}-G'
+	fi
+      else
+	# not using gcc
+	if test "$host_cpu" = ia64; then
+	# VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+	# chokes on -Wl,-G. The following line is correct:
+	  shared_flag='-G'
+	else
+	  if test "$aix_use_runtimelinking" = yes; then
+	    shared_flag='${wl}-G'
+	  else
+	    shared_flag='${wl}-bM:SRE'
+	  fi
+	fi
+      fi
+
+      export_dynamic_flag_spec_FC='${wl}-bexpall'
+      # It seems that -bexpall does not export symbols beginning with
+      # underscore (_), so it is better to generate a list of symbols to export.
+      always_export_symbols_FC=yes
+      if test "$aix_use_runtimelinking" = yes; then
+	# Warning - without using the other runtime loading flags (-brtl),
+	# -berok will link without error, but may produce a broken library.
+	allow_undefined_flag_FC='-berok'
+        # Determine the default libpath from the value encoded in an
+        # empty executable.
+        if test "${lt_cv_aix_libpath+set}" = set; then
+  aix_libpath=$lt_cv_aix_libpath
+else
+  if ${lt_cv_aix_libpath__FC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat > conftest.$ac_ext <<_ACEOF
+      program main
+
+      end
+_ACEOF
+if ac_fn_fc_try_link "$LINENO"; then :
+
+  lt_aix_libpath_sed='
+      /Import File Strings/,/^$/ {
+	  /^0/ {
+	      s/^0  *\([^ ]*\) *$/\1/
+	      p
+	  }
+      }'
+  lt_cv_aix_libpath__FC=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  # Check for a 64-bit object if we didn't find anything.
+  if test -z "$lt_cv_aix_libpath__FC"; then
+    lt_cv_aix_libpath__FC=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+  if test -z "$lt_cv_aix_libpath__FC"; then
+    lt_cv_aix_libpath__FC="/usr/lib:/lib"
+  fi
+
+fi
+
+  aix_libpath=$lt_cv_aix_libpath__FC
+fi
+
+        hardcode_libdir_flag_spec_FC='${wl}-blibpath:$libdir:'"$aix_libpath"
+        archive_expsym_cmds_FC='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+      else
+	if test "$host_cpu" = ia64; then
+	  hardcode_libdir_flag_spec_FC='${wl}-R $libdir:/usr/lib:/lib'
+	  allow_undefined_flag_FC="-z nodefs"
+	  archive_expsym_cmds_FC="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+	else
+	 # Determine the default libpath from the value encoded in an
+	 # empty executable.
+	 if test "${lt_cv_aix_libpath+set}" = set; then
+  aix_libpath=$lt_cv_aix_libpath
+else
+  if ${lt_cv_aix_libpath__FC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat > conftest.$ac_ext <<_ACEOF
+      program main
+
+      end
+_ACEOF
+if ac_fn_fc_try_link "$LINENO"; then :
+
+  lt_aix_libpath_sed='
+      /Import File Strings/,/^$/ {
+	  /^0/ {
+	      s/^0  *\([^ ]*\) *$/\1/
+	      p
+	  }
+      }'
+  lt_cv_aix_libpath__FC=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  # Check for a 64-bit object if we didn't find anything.
+  if test -z "$lt_cv_aix_libpath__FC"; then
+    lt_cv_aix_libpath__FC=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+  if test -z "$lt_cv_aix_libpath__FC"; then
+    lt_cv_aix_libpath__FC="/usr/lib:/lib"
+  fi
+
+fi
+
+  aix_libpath=$lt_cv_aix_libpath__FC
+fi
+
+	 hardcode_libdir_flag_spec_FC='${wl}-blibpath:$libdir:'"$aix_libpath"
+	  # Warning - without using the other run time loading flags,
+	  # -berok will link without error, but may produce a broken library.
+	  no_undefined_flag_FC=' ${wl}-bernotok'
+	  allow_undefined_flag_FC=' ${wl}-berok'
+	  if test "$with_gnu_ld" = yes; then
+	    # We only use this code for GNU lds that support --whole-archive.
+	    whole_archive_flag_spec_FC='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+	  else
+	    # Exported symbols can be pulled into shared objects from archives
+	    whole_archive_flag_spec_FC='$convenience'
+	  fi
+	  archive_cmds_need_lc_FC=yes
+	  # This is similar to how AIX traditionally builds its shared libraries.
+	  archive_expsym_cmds_FC="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+	fi
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            archive_cmds_FC='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            archive_expsym_cmds_FC=''
+        ;;
+      m68k)
+            archive_cmds_FC='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            hardcode_libdir_flag_spec_FC='-L$libdir'
+            hardcode_minus_L_FC=yes
+        ;;
+      esac
+      ;;
+
+    bsdi[45]*)
+      export_dynamic_flag_spec_FC=-rdynamic
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # When not using gcc, we currently assume that we are using
+      # Microsoft Visual C++.
+      # hardcode_libdir_flag_spec is actually meaningless, as there is
+      # no search path for DLLs.
+      case $cc_basename in
+      cl*)
+	# Native MSVC
+	hardcode_libdir_flag_spec_FC=' '
+	allow_undefined_flag_FC=unsupported
+	always_export_symbols_FC=yes
+	file_list_spec_FC='@'
+	# Tell ltmain to make .lib files, not .a files.
+	libext=lib
+	# Tell ltmain to make .dll files, not .so files.
+	shrext_cmds=".dll"
+	# FIXME: Setting linknames here is a bad hack.
+	archive_cmds_FC='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+	archive_expsym_cmds_FC='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	    sed -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+	  else
+	    sed -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+	  fi~
+	  $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+	  linknames='
+	# The linker will not automatically build a static lib if we build a DLL.
+	# _LT_TAGVAR(old_archive_from_new_cmds, FC)='true'
+	enable_shared_with_static_runtimes_FC=yes
+	exclude_expsyms_FC='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+	export_symbols_cmds_FC='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols'
+	# Don't use ranlib
+	old_postinstall_cmds_FC='chmod 644 $oldlib'
+	postlink_cmds_FC='lt_outputfile="@OUTPUT@"~
+	  lt_tool_outputfile="@TOOL_OUTPUT@"~
+	  case $lt_outputfile in
+	    *.exe|*.EXE) ;;
+	    *)
+	      lt_outputfile="$lt_outputfile.exe"
+	      lt_tool_outputfile="$lt_tool_outputfile.exe"
+	      ;;
+	  esac~
+	  if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+	    $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+	    $RM "$lt_outputfile.manifest";
+	  fi'
+	;;
+      *)
+	# Assume MSVC wrapper
+	hardcode_libdir_flag_spec_FC=' '
+	allow_undefined_flag_FC=unsupported
+	# Tell ltmain to make .lib files, not .a files.
+	libext=lib
+	# Tell ltmain to make .dll files, not .so files.
+	shrext_cmds=".dll"
+	# FIXME: Setting linknames here is a bad hack.
+	archive_cmds_FC='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames='
+	# The linker will automatically build a .lib file if we build a DLL.
+	old_archive_from_new_cmds_FC='true'
+	# FIXME: Should let the user specify the lib program.
+	old_archive_cmds_FC='lib -OUT:$oldlib$oldobjs$old_deplibs'
+	enable_shared_with_static_runtimes_FC=yes
+	;;
+      esac
+      ;;
+
+    darwin* | rhapsody*)
+
+
+  archive_cmds_need_lc_FC=no
+  hardcode_direct_FC=no
+  hardcode_automatic_FC=yes
+  hardcode_shlibpath_var_FC=unsupported
+  if test "$lt_cv_ld_force_load" = "yes"; then
+    whole_archive_flag_spec_FC='`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience ${wl}-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`'
+    compiler_needs_object_FC=yes
+  else
+    whole_archive_flag_spec_FC=''
+  fi
+  link_all_deplibs_FC=yes
+  allow_undefined_flag_FC="$_lt_dar_allow_undefined"
+  case $cc_basename in
+     ifort*) _lt_dar_can_shared=yes ;;
+     *) _lt_dar_can_shared=$GCC ;;
+  esac
+  if test "$_lt_dar_can_shared" = "yes"; then
+    output_verbose_link_cmd=func_echo_all
+    archive_cmds_FC="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}"
+    module_cmds_FC="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}"
+    archive_expsym_cmds_FC="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}"
+    module_expsym_cmds_FC="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}"
+
+  else
+  ld_shlibs_FC=no
+  fi
+
+      ;;
+
+    dgux*)
+      archive_cmds_FC='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_libdir_flag_spec_FC='-L$libdir'
+      hardcode_shlibpath_var_FC=no
+      ;;
+
+    # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+    # support.  Future versions do this automatically, but an explicit c++rt0.o
+    # does not break anything, and helps significantly (at the cost of a little
+    # extra space).
+    freebsd2.2*)
+      archive_cmds_FC='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+      hardcode_libdir_flag_spec_FC='-R$libdir'
+      hardcode_direct_FC=yes
+      hardcode_shlibpath_var_FC=no
+      ;;
+
+    # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+    freebsd2.*)
+      archive_cmds_FC='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_direct_FC=yes
+      hardcode_minus_L_FC=yes
+      hardcode_shlibpath_var_FC=no
+      ;;
+
+    # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+    freebsd* | dragonfly*)
+      archive_cmds_FC='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+      hardcode_libdir_flag_spec_FC='-R$libdir'
+      hardcode_direct_FC=yes
+      hardcode_shlibpath_var_FC=no
+      ;;
+
+    hpux9*)
+      if test "$GCC" = yes; then
+	archive_cmds_FC='$RM $output_objdir/$soname~$CC -shared $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      else
+	archive_cmds_FC='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      fi
+      hardcode_libdir_flag_spec_FC='${wl}+b ${wl}$libdir'
+      hardcode_libdir_separator_FC=:
+      hardcode_direct_FC=yes
+
+      # hardcode_minus_L: Not really in the search PATH,
+      # but as the default location of the library.
+      hardcode_minus_L_FC=yes
+      export_dynamic_flag_spec_FC='${wl}-E'
+      ;;
+
+    hpux10*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+	archive_cmds_FC='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	archive_cmds_FC='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      if test "$with_gnu_ld" = no; then
+	hardcode_libdir_flag_spec_FC='${wl}+b ${wl}$libdir'
+	hardcode_libdir_separator_FC=:
+	hardcode_direct_FC=yes
+	hardcode_direct_absolute_FC=yes
+	export_dynamic_flag_spec_FC='${wl}-E'
+	# hardcode_minus_L: Not really in the search PATH,
+	# but as the default location of the library.
+	hardcode_minus_L_FC=yes
+      fi
+      ;;
+
+    hpux11*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+	case $host_cpu in
+	hppa*64*)
+	  archive_cmds_FC='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	ia64*)
+	  archive_cmds_FC='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	*)
+	  archive_cmds_FC='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	esac
+      else
+	case $host_cpu in
+	hppa*64*)
+	  archive_cmds_FC='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	ia64*)
+	  archive_cmds_FC='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	*)
+	archive_cmds_FC='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	esac
+      fi
+      if test "$with_gnu_ld" = no; then
+	hardcode_libdir_flag_spec_FC='${wl}+b ${wl}$libdir'
+	hardcode_libdir_separator_FC=:
+
+	case $host_cpu in
+	hppa*64*|ia64*)
+	  hardcode_direct_FC=no
+	  hardcode_shlibpath_var_FC=no
+	  ;;
+	*)
+	  hardcode_direct_FC=yes
+	  hardcode_direct_absolute_FC=yes
+	  export_dynamic_flag_spec_FC='${wl}-E'
+
+	  # hardcode_minus_L: Not really in the search PATH,
+	  # but as the default location of the library.
+	  hardcode_minus_L_FC=yes
+	  ;;
+	esac
+      fi
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      if test "$GCC" = yes; then
+	archive_cmds_FC='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+	# Try to use the -exported_symbol ld option, if it does not
+	# work, assume that -exports_file does not work either and
+	# implicitly export all symbols.
+	# This should be the same for all languages, so no per-tag cache variable.
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5
+$as_echo_n "checking whether the $host_os linker accepts -exported_symbol... " >&6; }
+if ${lt_cv_irix_exported_symbol+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  save_LDFLAGS="$LDFLAGS"
+	   LDFLAGS="$LDFLAGS -shared ${wl}-exported_symbol ${wl}foo ${wl}-update_registry ${wl}/dev/null"
+	   cat > conftest.$ac_ext <<_ACEOF
+
+      subroutine foo
+      end
+_ACEOF
+if ac_fn_fc_try_link "$LINENO"; then :
+  lt_cv_irix_exported_symbol=yes
+else
+  lt_cv_irix_exported_symbol=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+           LDFLAGS="$save_LDFLAGS"
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5
+$as_echo "$lt_cv_irix_exported_symbol" >&6; }
+	if test "$lt_cv_irix_exported_symbol" = yes; then
+          archive_expsym_cmds_FC='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations ${wl}-exports_file ${wl}$export_symbols -o $lib'
+	fi
+      else
+	archive_cmds_FC='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	archive_expsym_cmds_FC='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -exports_file $export_symbols -o $lib'
+      fi
+      archive_cmds_need_lc_FC='no'
+      hardcode_libdir_flag_spec_FC='${wl}-rpath ${wl}$libdir'
+      hardcode_libdir_separator_FC=:
+      inherit_rpath_FC=yes
+      link_all_deplibs_FC=yes
+      ;;
+
+    netbsd*)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+	archive_cmds_FC='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'  # a.out
+      else
+	archive_cmds_FC='$LD -shared -o $lib $libobjs $deplibs $linker_flags'      # ELF
+      fi
+      hardcode_libdir_flag_spec_FC='-R$libdir'
+      hardcode_direct_FC=yes
+      hardcode_shlibpath_var_FC=no
+      ;;
+
+    newsos6)
+      archive_cmds_FC='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_direct_FC=yes
+      hardcode_libdir_flag_spec_FC='${wl}-rpath ${wl}$libdir'
+      hardcode_libdir_separator_FC=:
+      hardcode_shlibpath_var_FC=no
+      ;;
+
+    *nto* | *qnx*)
+      ;;
+
+    openbsd*)
+      if test -f /usr/libexec/ld.so; then
+	hardcode_direct_FC=yes
+	hardcode_shlibpath_var_FC=no
+	hardcode_direct_absolute_FC=yes
+	if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+	  archive_cmds_FC='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+	  archive_expsym_cmds_FC='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols'
+	  hardcode_libdir_flag_spec_FC='${wl}-rpath,$libdir'
+	  export_dynamic_flag_spec_FC='${wl}-E'
+	else
+	  case $host_os in
+	   openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*)
+	     archive_cmds_FC='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+	     hardcode_libdir_flag_spec_FC='-R$libdir'
+	     ;;
+	   *)
+	     archive_cmds_FC='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+	     hardcode_libdir_flag_spec_FC='${wl}-rpath,$libdir'
+	     ;;
+	  esac
+	fi
+      else
+	ld_shlibs_FC=no
+      fi
+      ;;
+
+    os2*)
+      hardcode_libdir_flag_spec_FC='-L$libdir'
+      hardcode_minus_L_FC=yes
+      allow_undefined_flag_FC=unsupported
+      archive_cmds_FC='$ECHO "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~echo DATA >> $output_objdir/$libname.def~echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def'
+      old_archive_from_new_cmds_FC='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def'
+      ;;
+
+    osf3*)
+      if test "$GCC" = yes; then
+	allow_undefined_flag_FC=' ${wl}-expect_unresolved ${wl}\*'
+	archive_cmds_FC='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+      else
+	allow_undefined_flag_FC=' -expect_unresolved \*'
+	archive_cmds_FC='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+      fi
+      archive_cmds_need_lc_FC='no'
+      hardcode_libdir_flag_spec_FC='${wl}-rpath ${wl}$libdir'
+      hardcode_libdir_separator_FC=:
+      ;;
+
+    osf4* | osf5*)	# as osf3* with the addition of -msym flag
+      if test "$GCC" = yes; then
+	allow_undefined_flag_FC=' ${wl}-expect_unresolved ${wl}\*'
+	archive_cmds_FC='$CC -shared${allow_undefined_flag} $pic_flag $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+	hardcode_libdir_flag_spec_FC='${wl}-rpath ${wl}$libdir'
+      else
+	allow_undefined_flag_FC=' -expect_unresolved \*'
+	archive_cmds_FC='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	archive_expsym_cmds_FC='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~
+	$CC -shared${allow_undefined_flag} ${wl}-input ${wl}$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~$RM $lib.exp'
+
+	# Both c and cxx compiler support -rpath directly
+	hardcode_libdir_flag_spec_FC='-rpath $libdir'
+      fi
+      archive_cmds_need_lc_FC='no'
+      hardcode_libdir_separator_FC=:
+      ;;
+
+    solaris*)
+      no_undefined_flag_FC=' -z defs'
+      if test "$GCC" = yes; then
+	wlarc='${wl}'
+	archive_cmds_FC='$CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	archive_expsym_cmds_FC='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+      else
+	case `$CC -V 2>&1` in
+	*"Compilers 5.0"*)
+	  wlarc=''
+	  archive_cmds_FC='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  archive_expsym_cmds_FC='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp'
+	  ;;
+	*)
+	  wlarc='${wl}'
+	  archive_cmds_FC='$CC -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $compiler_flags'
+	  archive_expsym_cmds_FC='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $CC -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+	  ;;
+	esac
+      fi
+      hardcode_libdir_flag_spec_FC='-R$libdir'
+      hardcode_shlibpath_var_FC=no
+      case $host_os in
+      solaris2.[0-5] | solaris2.[0-5].*) ;;
+      *)
+	# The compiler driver will combine and reorder linker options,
+	# but understands `-z linker_flag'.  GCC discards it without `$wl',
+	# but is careful enough not to reorder.
+	# Supported since Solaris 2.6 (maybe 2.5.1?)
+	if test "$GCC" = yes; then
+	  whole_archive_flag_spec_FC='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+	else
+	  whole_archive_flag_spec_FC='-z allextract$convenience -z defaultextract'
+	fi
+	;;
+      esac
+      link_all_deplibs_FC=yes
+      ;;
+
+    sunos4*)
+      if test "x$host_vendor" = xsequent; then
+	# Use $CC to link under sequent, because it throws in some extra .o
+	# files that make .init and .fini sections work.
+	archive_cmds_FC='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	archive_cmds_FC='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      hardcode_libdir_flag_spec_FC='-L$libdir'
+      hardcode_direct_FC=yes
+      hardcode_minus_L_FC=yes
+      hardcode_shlibpath_var_FC=no
+      ;;
+
+    sysv4)
+      case $host_vendor in
+	sni)
+	  archive_cmds_FC='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  hardcode_direct_FC=yes # is this really true???
+	;;
+	siemens)
+	  ## LD is ld it makes a PLAMLIB
+	  ## CC just makes a GrossModule.
+	  archive_cmds_FC='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+	  reload_cmds_FC='$CC -r -o $output$reload_objs'
+	  hardcode_direct_FC=no
+        ;;
+	motorola)
+	  archive_cmds_FC='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  hardcode_direct_FC=no #Motorola manual says yes, but my tests say they lie
+	;;
+      esac
+      runpath_var='LD_RUN_PATH'
+      hardcode_shlibpath_var_FC=no
+      ;;
+
+    sysv4.3*)
+      archive_cmds_FC='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_shlibpath_var_FC=no
+      export_dynamic_flag_spec_FC='-Bexport'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+	archive_cmds_FC='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	hardcode_shlibpath_var_FC=no
+	runpath_var=LD_RUN_PATH
+	hardcode_runpath_var=yes
+	ld_shlibs_FC=yes
+      fi
+      ;;
+
+    sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*)
+      no_undefined_flag_FC='${wl}-z,text'
+      archive_cmds_need_lc_FC=no
+      hardcode_shlibpath_var_FC=no
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+	archive_cmds_FC='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	archive_expsym_cmds_FC='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	archive_cmds_FC='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	archive_expsym_cmds_FC='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6*)
+      # Note: We can NOT use -z defs as we might desire, because we do not
+      # link with -lc, and that would cause any symbols used from libc to
+      # always be unresolved, which means just about no library would
+      # ever link correctly.  If we're not using GNU ld we use -z text
+      # though, which does catch some bad symbols but isn't as heavy-handed
+      # as -z defs.
+      no_undefined_flag_FC='${wl}-z,text'
+      allow_undefined_flag_FC='${wl}-z,nodefs'
+      archive_cmds_need_lc_FC=no
+      hardcode_shlibpath_var_FC=no
+      hardcode_libdir_flag_spec_FC='${wl}-R,$libdir'
+      hardcode_libdir_separator_FC=':'
+      link_all_deplibs_FC=yes
+      export_dynamic_flag_spec_FC='${wl}-Bexport'
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+	archive_cmds_FC='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	archive_expsym_cmds_FC='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	archive_cmds_FC='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	archive_expsym_cmds_FC='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    uts4*)
+      archive_cmds_FC='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_libdir_flag_spec_FC='-L$libdir'
+      hardcode_shlibpath_var_FC=no
+      ;;
+
+    *)
+      ld_shlibs_FC=no
+      ;;
+    esac
+
+    if test x$host_vendor = xsni; then
+      case $host in
+      sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+	export_dynamic_flag_spec_FC='${wl}-Blargedynsym'
+	;;
+      esac
+    fi
+  fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_FC" >&5
+$as_echo "$ld_shlibs_FC" >&6; }
+test "$ld_shlibs_FC" = no && can_build_shared=no
+
+with_gnu_ld_FC=$with_gnu_ld
+
+
+
+
+
+
+#
+# Do we need to explicitly link libc?
+#
+case "x$archive_cmds_need_lc_FC" in
+x|xyes)
+  # Assume -lc should be added
+  archive_cmds_need_lc_FC=yes
+
+  if test "$enable_shared" = yes && test "$GCC" = yes; then
+    case $archive_cmds_FC in
+    *'~'*)
+      # FIXME: we may have to deal with multi-command sequences.
+      ;;
+    '$CC '*)
+      # Test whether the compiler implicitly links with -lc since on some
+      # systems, -lgcc has to come before -lc. If gcc already passes -lc
+      # to ld, don't add -lc before -lgcc.
+      { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5
+$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; }
+if ${lt_cv_archive_cmds_need_lc_FC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  $RM conftest*
+	echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+	if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } 2>conftest.err; then
+	  soname=conftest
+	  lib=conftest
+	  libobjs=conftest.$ac_objext
+	  deplibs=
+	  wl=$lt_prog_compiler_wl_FC
+	  pic_flag=$lt_prog_compiler_pic_FC
+	  compiler_flags=-v
+	  linker_flags=-v
+	  verstring=
+	  output_objdir=.
+	  libname=conftest
+	  lt_save_allow_undefined_flag=$allow_undefined_flag_FC
+	  allow_undefined_flag_FC=
+	  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds_FC 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5
+  (eval $archive_cmds_FC 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+	  then
+	    lt_cv_archive_cmds_need_lc_FC=no
+	  else
+	    lt_cv_archive_cmds_need_lc_FC=yes
+	  fi
+	  allow_undefined_flag_FC=$lt_save_allow_undefined_flag
+	else
+	  cat conftest.err 1>&5
+	fi
+	$RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc_FC" >&5
+$as_echo "$lt_cv_archive_cmds_need_lc_FC" >&6; }
+      archive_cmds_need_lc_FC=$lt_cv_archive_cmds_need_lc_FC
+      ;;
+    esac
+  fi
+  ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5
+$as_echo_n "checking dynamic linker characteristics... " >&6; }
+
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+shrext_cmds=".so"
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+need_lib_prefix=unknown
+hardcode_into_libs=no
+
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+need_version=unknown
+
+case $host_os in
+aix3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a'
+  shlibpath_var=LIBPATH
+
+  # AIX 3 has no versioning support, so we append a major version to the name.
+  soname_spec='${libname}${release}${shared_ext}$major'
+  ;;
+
+aix[4-9]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  hardcode_into_libs=yes
+  if test "$host_cpu" = ia64; then
+    # AIX 5 supports IA64
+    library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}'
+    shlibpath_var=LD_LIBRARY_PATH
+  else
+    # With GCC up to 2.95.x, collect2 would create an import file
+    # for dependence libraries.  The import file would start with
+    # the line `#! .'.  This would cause the generated library to
+    # depend on `.', always an invalid library.  This was fixed in
+    # development snapshots of GCC prior to 3.0.
+    case $host_os in
+      aix4 | aix4.[01] | aix4.[01].*)
+      if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+	   echo ' yes '
+	   echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then
+	:
+      else
+	can_build_shared=no
+      fi
+      ;;
+    esac
+    # AIX (on Power*) has no versioning support, so currently we can not hardcode correct
+    # soname into executable. Probably we can add versioning support to
+    # collect2, so additional links can be useful in future.
+    if test "$aix_use_runtimelinking" = yes; then
+      # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+      # instead of lib<name>.a to let people know that these are not
+      # typical AIX shared libraries.
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    else
+      # We preserve .a as extension for shared libraries through AIX4.2
+      # and later when we are not doing run time linking.
+      library_names_spec='${libname}${release}.a $libname.a'
+      soname_spec='${libname}${release}${shared_ext}$major'
+    fi
+    shlibpath_var=LIBPATH
+  fi
+  ;;
+
+amigaos*)
+  case $host_cpu in
+  powerpc)
+    # Since July 2007 AmigaOS4 officially supports .so libraries.
+    # When compiling the executable, add -use-dynld -Lsobjs: to the compileline.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    ;;
+  m68k)
+    library_names_spec='$libname.ixlibrary $libname.a'
+    # Create ${libname}_ixlibrary.a entries in /sys/libs.
+    finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done'
+    ;;
+  esac
+  ;;
+
+beos*)
+  library_names_spec='${libname}${shared_ext}'
+  dynamic_linker="$host_os ld.so"
+  shlibpath_var=LIBRARY_PATH
+  ;;
+
+bsdi[45]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+  sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+  # the default ld.so.conf also contains /usr/contrib/lib and
+  # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+  # libtool to hard-code these into programs
+  ;;
+
+cygwin* | mingw* | pw32* | cegcc*)
+  version_type=windows
+  shrext_cmds=".dll"
+  need_version=no
+  need_lib_prefix=no
+
+  case $GCC,$cc_basename in
+  yes,*)
+    # gcc
+    library_names_spec='$libname.dll.a'
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname~
+      chmod a+x \$dldir/$dlname~
+      if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+        eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+      fi'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+
+    case $host_os in
+    cygwin*)
+      # Cygwin DLLs use 'cyg' prefix rather than 'lib'
+      soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+
+      ;;
+    mingw* | cegcc*)
+      # MinGW DLLs use traditional 'lib' prefix
+      soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    pw32*)
+      # pw32 DLLs use 'pw' prefix rather than 'lib'
+      library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    esac
+    dynamic_linker='Win32 ld.exe'
+    ;;
+
+  *,cl*)
+    # Native MSVC
+    libname_spec='$name'
+    soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+    library_names_spec='${libname}.dll.lib'
+
+    case $build_os in
+    mingw*)
+      sys_lib_search_path_spec=
+      lt_save_ifs=$IFS
+      IFS=';'
+      for lt_path in $LIB
+      do
+        IFS=$lt_save_ifs
+        # Let DOS variable expansion print the short 8.3 style file name.
+        lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"`
+        sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path"
+      done
+      IFS=$lt_save_ifs
+      # Convert to MSYS style.
+      sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'`
+      ;;
+    cygwin*)
+      # Convert to unix form, then to dos form, then back to unix form
+      # but this time dos style (no spaces!) so that the unix form looks
+      # like /cygdrive/c/PROGRA~1:/cygdr...
+      sys_lib_search_path_spec=`cygpath --path --unix "$LIB"`
+      sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null`
+      sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      ;;
+    *)
+      sys_lib_search_path_spec="$LIB"
+      if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then
+        # It is most probably a Windows format PATH.
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'`
+      else
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      fi
+      # FIXME: find the short name or the path components, as spaces are
+      # common. (e.g. "Program Files" -> "PROGRA~1")
+      ;;
+    esac
+
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+    dynamic_linker='Win32 link.exe'
+    ;;
+
+  *)
+    # Assume MSVC wrapper
+    library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib'
+    dynamic_linker='Win32 ld.exe'
+    ;;
+  esac
+  # FIXME: first we should search . and the directory the executable is in
+  shlibpath_var=PATH
+  ;;
+
+darwin* | rhapsody*)
+  dynamic_linker="$host_os dyld"
+  version_type=darwin
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext'
+  soname_spec='${libname}${release}${major}$shared_ext'
+  shlibpath_overrides_runpath=yes
+  shlibpath_var=DYLD_LIBRARY_PATH
+  shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`'
+
+  sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib'
+  ;;
+
+dgux*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+freebsd* | dragonfly*)
+  # DragonFly does not have aout.  When/if they implement a new
+  # versioning mechanism, adjust this.
+  if test -x /usr/bin/objformat; then
+    objformat=`/usr/bin/objformat`
+  else
+    case $host_os in
+    freebsd[23].*) objformat=aout ;;
+    *) objformat=elf ;;
+    esac
+  fi
+  version_type=freebsd-$objformat
+  case $version_type in
+    freebsd-elf*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+      need_version=no
+      need_lib_prefix=no
+      ;;
+    freebsd-*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix'
+      need_version=yes
+      ;;
+  esac
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_os in
+  freebsd2.*)
+    shlibpath_overrides_runpath=yes
+    ;;
+  freebsd3.[01]* | freebsdelf3.[01]*)
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  freebsd3.[2-9]* | freebsdelf3.[2-9]* | \
+  freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1)
+    shlibpath_overrides_runpath=no
+    hardcode_into_libs=yes
+    ;;
+  *) # from 4.6 on, and DragonFly
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  esac
+  ;;
+
+gnu*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+haiku*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  dynamic_linker="$host_os runtime_loader"
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib'
+  hardcode_into_libs=yes
+  ;;
+
+hpux9* | hpux10* | hpux11*)
+  # Give a soname corresponding to the major version so that dld.sl refuses to
+  # link against other versions.
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  case $host_cpu in
+  ia64*)
+    shrext_cmds='.so'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.so"
+    shlibpath_var=LD_LIBRARY_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    if test "X$HPUX_IA64_MODE" = X32; then
+      sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib"
+    else
+      sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64"
+    fi
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  hppa*64*)
+    shrext_cmds='.sl'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64"
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  *)
+    shrext_cmds='.sl'
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=SHLIB_PATH
+    shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    ;;
+  esac
+  # HP-UX runs *really* slowly unless shared libraries are mode 555, ...
+  postinstall_cmds='chmod 555 $lib'
+  # or fails outright, so override atomically:
+  install_override_mode=555
+  ;;
+
+interix[3-9]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $host_os in
+    nonstopux*) version_type=nonstopux ;;
+    *)
+	if test "$lt_cv_prog_gnu_ld" = yes; then
+		version_type=linux # correct to gnu/linux during the next big refactor
+	else
+		version_type=irix
+	fi ;;
+  esac
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}'
+  case $host_os in
+  irix5* | nonstopux*)
+    libsuff= shlibsuff=
+    ;;
+  *)
+    case $LD in # libtool.m4 will add one of these switches to LD
+    *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ")
+      libsuff= shlibsuff= libmagic=32-bit;;
+    *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ")
+      libsuff=32 shlibsuff=N32 libmagic=N32;;
+    *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ")
+      libsuff=64 shlibsuff=64 libmagic=64-bit;;
+    *) libsuff= shlibsuff= libmagic=never-match;;
+    esac
+    ;;
+  esac
+  shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+  shlibpath_overrides_runpath=no
+  sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}"
+  sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}"
+  hardcode_into_libs=yes
+  ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux*oldld* | linux*aout* | linux*coff*)
+  dynamic_linker=no
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+
+  # Some binutils ld are patched to set DT_RUNPATH
+  if ${lt_cv_shlibpath_overrides_runpath+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_shlibpath_overrides_runpath=no
+    save_LDFLAGS=$LDFLAGS
+    save_libdir=$libdir
+    eval "libdir=/foo; wl=\"$lt_prog_compiler_wl_FC\"; \
+	 LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec_FC\""
+    cat > conftest.$ac_ext <<_ACEOF
+      program main
+
+      end
+_ACEOF
+if ac_fn_fc_try_link "$LINENO"; then :
+  if  ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then :
+  lt_cv_shlibpath_overrides_runpath=yes
+fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+    LDFLAGS=$save_LDFLAGS
+    libdir=$save_libdir
+
+fi
+
+  shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath
+
+  # This implies no fast_install, which is unacceptable.
+  # Some rework will be needed to allow for fast_install
+  # before this can be enabled.
+  hardcode_into_libs=yes
+
+  # Append ld.so.conf contents to the search path
+  if test -f /etc/ld.so.conf; then
+    lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[	 ]*hwcap[	 ]/d;s/[:,	]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '`
+    sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra"
+  fi
+
+  # We used to test for /lib/ld.so.1 and disable shared libraries on
+  # powerpc, because MkLinux only supported shared libraries with the
+  # GNU dynamic linker.  Since this was broken with cross compilers,
+  # most powerpc-linux boxes support dynamic linking these days and
+  # people can always --disable-shared, the test was removed, and we
+  # assume the GNU/Linux dynamic linker is in use.
+  dynamic_linker='GNU/Linux ld.so'
+  ;;
+
+netbsd*)
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+    finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+    dynamic_linker='NetBSD (a.out) ld.so'
+  else
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    dynamic_linker='NetBSD ld.elf_so'
+  fi
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  ;;
+
+newsos6)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  ;;
+
+*nto* | *qnx*)
+  version_type=qnx
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  dynamic_linker='ldqnx.so'
+  ;;
+
+openbsd*)
+  version_type=sunos
+  sys_lib_dlsearch_path_spec="/usr/lib"
+  need_lib_prefix=no
+  # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs.
+  case $host_os in
+    openbsd3.3 | openbsd3.3.*)	need_version=yes ;;
+    *)				need_version=no  ;;
+  esac
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    case $host_os in
+      openbsd2.[89] | openbsd2.[89].*)
+	shlibpath_overrides_runpath=no
+	;;
+      *)
+	shlibpath_overrides_runpath=yes
+	;;
+      esac
+  else
+    shlibpath_overrides_runpath=yes
+  fi
+  ;;
+
+os2*)
+  libname_spec='$name'
+  shrext_cmds=".dll"
+  need_lib_prefix=no
+  library_names_spec='$libname${shared_ext} $libname.a'
+  dynamic_linker='OS/2 ld.exe'
+  shlibpath_var=LIBPATH
+  ;;
+
+osf3* | osf4* | osf5*)
+  version_type=osf
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+  sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec"
+  ;;
+
+rdos*)
+  dynamic_linker=no
+  ;;
+
+solaris*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  # ldd complains unless libraries are executable
+  postinstall_cmds='chmod +x $lib'
+  ;;
+
+sunos4*)
+  version_type=sunos
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  if test "$with_gnu_ld" = yes; then
+    need_lib_prefix=no
+  fi
+  need_version=yes
+  ;;
+
+sysv4 | sysv4.3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_vendor in
+    sni)
+      shlibpath_overrides_runpath=no
+      need_lib_prefix=no
+      runpath_var=LD_RUN_PATH
+      ;;
+    siemens)
+      need_lib_prefix=no
+      ;;
+    motorola)
+      need_lib_prefix=no
+      need_version=no
+      shlibpath_overrides_runpath=no
+      sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+      ;;
+  esac
+  ;;
+
+sysv4*MP*)
+  if test -d /usr/nec ;then
+    version_type=linux # correct to gnu/linux during the next big refactor
+    library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}'
+    soname_spec='$libname${shared_ext}.$major'
+    shlibpath_var=LD_LIBRARY_PATH
+  fi
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  version_type=freebsd-elf
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  if test "$with_gnu_ld" = yes; then
+    sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib'
+  else
+    sys_lib_search_path_spec='/usr/ccs/lib /usr/lib'
+    case $host_os in
+      sco3.2v5*)
+        sys_lib_search_path_spec="$sys_lib_search_path_spec /lib"
+	;;
+    esac
+  fi
+  sys_lib_dlsearch_path_spec='/usr/lib'
+  ;;
+
+tpf*)
+  # TPF is a cross-target only.  Preferred cross-host = GNU/Linux.
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+uts4*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+*)
+  dynamic_linker=no
+  ;;
+esac
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5
+$as_echo "$dynamic_linker" >&6; }
+test "$dynamic_linker" = no && can_build_shared=no
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test "$GCC" = yes; then
+  variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then
+  sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec"
+fi
+if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then
+  sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec"
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5
+$as_echo_n "checking how to hardcode library paths into programs... " >&6; }
+hardcode_action_FC=
+if test -n "$hardcode_libdir_flag_spec_FC" ||
+   test -n "$runpath_var_FC" ||
+   test "X$hardcode_automatic_FC" = "Xyes" ; then
+
+  # We can hardcode non-existent directories.
+  if test "$hardcode_direct_FC" != no &&
+     # If the only mechanism to avoid hardcoding is shlibpath_var, we
+     # have to relink, otherwise we might link with an installed library
+     # when we should be linking with a yet-to-be-installed one
+     ## test "$_LT_TAGVAR(hardcode_shlibpath_var, FC)" != no &&
+     test "$hardcode_minus_L_FC" != no; then
+    # Linking always hardcodes the temporary library directory.
+    hardcode_action_FC=relink
+  else
+    # We can link without hardcoding, and we can hardcode nonexisting dirs.
+    hardcode_action_FC=immediate
+  fi
+else
+  # We cannot hardcode anything, or else we can only hardcode existing
+  # directories.
+  hardcode_action_FC=unsupported
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action_FC" >&5
+$as_echo "$hardcode_action_FC" >&6; }
+
+if test "$hardcode_action_FC" = relink ||
+   test "$inherit_rpath_FC" = yes; then
+  # Fast installation is not supported
+  enable_fast_install=no
+elif test "$shlibpath_overrides_runpath" = yes ||
+     test "$enable_shared" = no; then
+  # Fast installation is not necessary
+  enable_fast_install=needless
+fi
+
+
+
+
+
+
+
+  fi # test -n "$compiler"
+
+  GCC=$lt_save_GCC
+  CC=$lt_save_CC
+  CFLAGS=$lt_save_CFLAGS
+fi # test "$_lt_disable_FC" != yes
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ac_ext=${ac_fc_srcext-f}
+ac_compile='$FC -c $FCFLAGS $ac_fcflags_srcext conftest.$ac_ext >&5'
+ac_link='$FC -o conftest$ac_exeext $FCFLAGS $LDFLAGS $ac_fcflags_srcext conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_fc_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to get verbose linking output from $FC" >&5
+$as_echo_n "checking how to get verbose linking output from $FC... " >&6; }
+if ${ac_cv_prog_fc_v+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat > conftest.$ac_ext <<_ACEOF
+      program main
+
+      end
+_ACEOF
+if ac_fn_fc_try_compile "$LINENO"; then :
+  ac_cv_prog_fc_v=
+# Try some options frequently used verbose output
+for ac_verb in -v -verbose --verbose -V -\#\#\#; do
+  cat > conftest.$ac_ext <<_ACEOF
+      program main
+
+      end
+_ACEOF
+
+# Compile and link our simple test program by passing a flag (argument
+# 1 to this macro) to the Fortran compiler in order to get
+# "verbose" output that we can then parse for the Fortran linker
+# flags.
+ac_save_FCFLAGS=$FCFLAGS
+FCFLAGS="$FCFLAGS $ac_verb"
+eval "set x $ac_link"
+shift
+$as_echo "$as_me:${as_lineno-$LINENO}: $*" >&5
+# gfortran 4.3 outputs lines setting COLLECT_GCC_OPTIONS, COMPILER_PATH,
+# LIBRARY_PATH; skip all such settings.
+ac_fc_v_output=`eval $ac_link 5>&1 2>&1 |
+  sed '/^Driving:/d; /^Configured with:/d;
+      '"/^[_$as_cr_Letters][_$as_cr_alnum]*=/d"`
+$as_echo "$ac_fc_v_output" >&5
+FCFLAGS=$ac_save_FCFLAGS
+
+rm -rf conftest*
+
+# On HP/UX there is a line like: "LPATH is: /foo:/bar:/baz" where
+# /foo, /bar, and /baz are search directories for the Fortran linker.
+# Here, we change these into -L/foo -L/bar -L/baz (and put it first):
+ac_fc_v_output="`echo $ac_fc_v_output |
+	grep 'LPATH is:' |
+	sed 's|.*LPATH is\(: *[^ ]*\).*|\1|;s|: */| -L/|g'` $ac_fc_v_output"
+
+# FIXME: we keep getting bitten by quoted arguments; a more general fix
+#        that detects unbalanced quotes in FLIBS should be implemented
+#        and (ugh) tested at some point.
+case $ac_fc_v_output in
+  # With xlf replace commas with spaces,
+  # and remove "-link" and closing parenthesis.
+  *xlfentry*)
+    ac_fc_v_output=`echo $ac_fc_v_output |
+      sed '
+        s/,/ /g
+        s/ -link / /g
+        s/) *$//
+      '
+    ` ;;
+
+  # With Intel ifc, ignore the quoted -mGLOB_options_string stuff (quoted
+  # $LIBS confuse us, and the libraries appear later in the output anyway).
+  *mGLOB_options_string*)
+    ac_fc_v_output=`echo $ac_fc_v_output | sed 's/"-mGLOB[^"]*"/ /g'` ;;
+
+  # Portland Group compiler has singly- or doubly-quoted -cmdline argument
+  # Singly-quoted arguments were reported for versions 5.2-4 and 6.0-4.
+  # Doubly-quoted arguments were reported for "PGF90/x86 Linux/x86 5.0-2".
+  *-cmdline\ * | *-ignore\ * | *-def\ *)
+    ac_fc_v_output=`echo $ac_fc_v_output | sed "\
+	s/-cmdline  *'[^']*'/ /g; s/-cmdline  *\"[^\"]*\"/ /g
+	s/-ignore  *'[^']*'/ /g; s/-ignore  *\"[^\"]*\"/ /g
+	s/-def  *'[^']*'/ /g; s/-def  *\"[^\"]*\"/ /g"` ;;
+
+  # If we are using fort77 (the f2c wrapper) then filter output and delete quotes.
+  *fort77*f2c*gcc*)
+    ac_fc_v_output=`echo "$ac_fc_v_output" | sed -n '
+        /:[	 ]\+Running[	 ]\{1,\}"gcc"/{
+          /"-c"/d
+          /[.]c"*/d
+          s/^.*"gcc"/"gcc"/
+          s/"//gp
+        }'` ;;
+
+  # If we are using Cray Fortran then delete quotes.
+  *cft90*)
+    ac_fc_v_output=`echo $ac_fc_v_output | sed 's/"//g'` ;;
+esac
+
+
+  # look for -l* and *.a constructs in the output
+  for ac_arg in $ac_fc_v_output; do
+     case $ac_arg in
+	[\\/]*.a | ?:[\\/]*.a | -[lLRu]*)
+	  ac_cv_prog_fc_v=$ac_verb
+	  break 2 ;;
+     esac
+  done
+done
+if test -z "$ac_cv_prog_fc_v"; then
+   { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cannot determine how to obtain linking information from $FC" >&5
+$as_echo "$as_me: WARNING: cannot determine how to obtain linking information from $FC" >&2;}
+fi
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: compilation failed" >&5
+$as_echo "$as_me: WARNING: compilation failed" >&2;}
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_fc_v" >&5
+$as_echo "$ac_cv_prog_fc_v" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Fortran libraries of $FC" >&5
+$as_echo_n "checking for Fortran libraries of $FC... " >&6; }
+if ${ac_cv_fc_libs+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test "x$FCLIBS" != "x"; then
+  ac_cv_fc_libs="$FCLIBS" # Let the user override the test.
+else
+
+cat > conftest.$ac_ext <<_ACEOF
+      program main
+
+      end
+_ACEOF
+
+# Compile and link our simple test program by passing a flag (argument
+# 1 to this macro) to the Fortran compiler in order to get
+# "verbose" output that we can then parse for the Fortran linker
+# flags.
+ac_save_FCFLAGS=$FCFLAGS
+FCFLAGS="$FCFLAGS $ac_cv_prog_fc_v"
+eval "set x $ac_link"
+shift
+$as_echo "$as_me:${as_lineno-$LINENO}: $*" >&5
+# gfortran 4.3 outputs lines setting COLLECT_GCC_OPTIONS, COMPILER_PATH,
+# LIBRARY_PATH; skip all such settings.
+ac_fc_v_output=`eval $ac_link 5>&1 2>&1 |
+  sed '/^Driving:/d; /^Configured with:/d;
+      '"/^[_$as_cr_Letters][_$as_cr_alnum]*=/d"`
+$as_echo "$ac_fc_v_output" >&5
+FCFLAGS=$ac_save_FCFLAGS
+
+rm -rf conftest*
+
+# On HP/UX there is a line like: "LPATH is: /foo:/bar:/baz" where
+# /foo, /bar, and /baz are search directories for the Fortran linker.
+# Here, we change these into -L/foo -L/bar -L/baz (and put it first):
+ac_fc_v_output="`echo $ac_fc_v_output |
+	grep 'LPATH is:' |
+	sed 's|.*LPATH is\(: *[^ ]*\).*|\1|;s|: */| -L/|g'` $ac_fc_v_output"
+
+# FIXME: we keep getting bitten by quoted arguments; a more general fix
+#        that detects unbalanced quotes in FLIBS should be implemented
+#        and (ugh) tested at some point.
+case $ac_fc_v_output in
+  # With xlf replace commas with spaces,
+  # and remove "-link" and closing parenthesis.
+  *xlfentry*)
+    ac_fc_v_output=`echo $ac_fc_v_output |
+      sed '
+        s/,/ /g
+        s/ -link / /g
+        s/) *$//
+      '
+    ` ;;
+
+  # With Intel ifc, ignore the quoted -mGLOB_options_string stuff (quoted
+  # $LIBS confuse us, and the libraries appear later in the output anyway).
+  *mGLOB_options_string*)
+    ac_fc_v_output=`echo $ac_fc_v_output | sed 's/"-mGLOB[^"]*"/ /g'` ;;
+
+  # Portland Group compiler has singly- or doubly-quoted -cmdline argument
+  # Singly-quoted arguments were reported for versions 5.2-4 and 6.0-4.
+  # Doubly-quoted arguments were reported for "PGF90/x86 Linux/x86 5.0-2".
+  *-cmdline\ * | *-ignore\ * | *-def\ *)
+    ac_fc_v_output=`echo $ac_fc_v_output | sed "\
+	s/-cmdline  *'[^']*'/ /g; s/-cmdline  *\"[^\"]*\"/ /g
+	s/-ignore  *'[^']*'/ /g; s/-ignore  *\"[^\"]*\"/ /g
+	s/-def  *'[^']*'/ /g; s/-def  *\"[^\"]*\"/ /g"` ;;
+
+  # If we are using fort77 (the f2c wrapper) then filter output and delete quotes.
+  *fort77*f2c*gcc*)
+    ac_fc_v_output=`echo "$ac_fc_v_output" | sed -n '
+        /:[	 ]\+Running[	 ]\{1,\}"gcc"/{
+          /"-c"/d
+          /[.]c"*/d
+          s/^.*"gcc"/"gcc"/
+          s/"//gp
+        }'` ;;
+
+  # If we are using Cray Fortran then delete quotes.
+  *cft90*)
+    ac_fc_v_output=`echo $ac_fc_v_output | sed 's/"//g'` ;;
+esac
+
+
+
+ac_cv_fc_libs=
+
+# Save positional arguments (if any)
+ac_save_positional="$@"
+
+set X $ac_fc_v_output
+while test $# != 1; do
+  shift
+  ac_arg=$1
+  case $ac_arg in
+	[\\/]*.a | ?:[\\/]*.a)
+	    ac_exists=false
+  for ac_i in $ac_cv_fc_libs; do
+    if test x"$ac_arg" = x"$ac_i"; then
+      ac_exists=true
+      break
+    fi
+  done
+
+  if test x"$ac_exists" = xtrue; then :
+
+else
+  ac_cv_fc_libs="$ac_cv_fc_libs $ac_arg"
+fi
+	  ;;
+	-bI:*)
+	    ac_exists=false
+  for ac_i in $ac_cv_fc_libs; do
+    if test x"$ac_arg" = x"$ac_i"; then
+      ac_exists=true
+      break
+    fi
+  done
+
+  if test x"$ac_exists" = xtrue; then :
+
+else
+  if test "$ac_compiler_gnu" = yes; then
+  for ac_link_opt in $ac_arg; do
+    ac_cv_fc_libs="$ac_cv_fc_libs -Xlinker $ac_link_opt"
+  done
+else
+  ac_cv_fc_libs="$ac_cv_fc_libs $ac_arg"
+fi
+fi
+	  ;;
+	  # Ignore these flags.
+	-lang* | -lcrt*.o | -lc | -lgcc* | -lSystem | -libmil | -little \
+	  |-LANG:=* | -LIST:* | -LNO:* | -link)
+	  ;;
+	-lkernel32)
+	  case $host_os in
+	  *cygwin*) ;;
+	  *) ac_cv_fc_libs="$ac_cv_fc_libs $ac_arg"
+	    ;;
+	  esac
+	  ;;
+	-[LRuYz])
+	  # These flags, when seen by themselves, take an argument.
+	  # We remove the space between option and argument and re-iterate
+	  # unless we find an empty arg or a new option (starting with -)
+	  case $2 in
+	     "" | -*);;
+	     *)
+		ac_arg="$ac_arg$2"
+		shift; shift
+		set X $ac_arg "$@"
+		;;
+	  esac
+	  ;;
+	-YP,*)
+	  for ac_j in `$as_echo "$ac_arg" | sed -e 's/-YP,/-L/;s/:/ -L/g'`; do
+	      ac_exists=false
+  for ac_i in $ac_cv_fc_libs; do
+    if test x"$ac_j" = x"$ac_i"; then
+      ac_exists=true
+      break
+    fi
+  done
+
+  if test x"$ac_exists" = xtrue; then :
+
+else
+  ac_arg="$ac_arg $ac_j"
+			       ac_cv_fc_libs="$ac_cv_fc_libs $ac_j"
+fi
+	  done
+	  ;;
+	-[lLR]*)
+	    ac_exists=false
+  for ac_i in $ac_cv_fc_libs; do
+    if test x"$ac_arg" = x"$ac_i"; then
+      ac_exists=true
+      break
+    fi
+  done
+
+  if test x"$ac_exists" = xtrue; then :
+
+else
+  ac_cv_fc_libs="$ac_cv_fc_libs $ac_arg"
+fi
+	  ;;
+	-zallextract*| -zdefaultextract)
+	  ac_cv_fc_libs="$ac_cv_fc_libs $ac_arg"
+	  ;;
+	  # Ignore everything else.
+  esac
+done
+# restore positional arguments
+set X $ac_save_positional; shift
+
+# We only consider "LD_RUN_PATH" on Solaris systems.  If this is seen,
+# then we insist that the "run path" must be an absolute path (i.e. it
+# must begin with a "/").
+case `(uname -sr) 2>/dev/null` in
+   "SunOS 5"*)
+      ac_ld_run_path=`$as_echo "$ac_fc_v_output" |
+			sed -n 's,^.*LD_RUN_PATH *= *\(/[^ ]*\).*$,-R\1,p'`
+      test "x$ac_ld_run_path" != x &&
+	if test "$ac_compiler_gnu" = yes; then
+  for ac_link_opt in $ac_ld_run_path; do
+    ac_cv_fc_libs="$ac_cv_fc_libs -Xlinker $ac_link_opt"
+  done
+else
+  ac_cv_fc_libs="$ac_cv_fc_libs $ac_ld_run_path"
+fi
+      ;;
+esac
+fi # test "x$[]_AC_LANG_PREFIX[]LIBS" = "x"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_fc_libs" >&5
+$as_echo "$ac_cv_fc_libs" >&6; }
+FCLIBS="$ac_cv_fc_libs"
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+fi
+
+#   Find an absolute path to the Perl binary, augmenting the path with the
+#   location of the Starlink Perl build.  If this fails, then set @PERL@
+#   to the backup `/usr/bin/env perl', which will find Perl if it exists
+#   in the path at runtime.
+# Extract the first word of "perl", so it can be a program name with args.
+set dummy perl; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PERL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PERL in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PERL="$PERL" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_dummy="$STARLINK/Perl/bin:$PATH"
+for as_dir in $as_dummy
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_PERL="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_path_PERL" && ac_cv_path_PERL="/usr/bin/env perl"
+  ;;
+esac
+fi
+PERL=$ac_cv_path_PERL
+if test -n "$PERL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PERL" >&5
+$as_echo "$PERL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+#   Function and declaration checks
+for ac_func in isnan
+do :
+  ac_fn_c_check_func "$LINENO" "isnan" "ac_cv_func_isnan"
+if test "x$ac_cv_func_isnan" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_ISNAN 1
+_ACEOF
+
+fi
+done
+
+ac_fn_c_check_decl "$LINENO" "isnan" "ac_cv_have_decl_isnan" "#include <math.h>
+
+"
+if test "x$ac_cv_have_decl_isnan" = xyes; then :
+  ac_have_decl=1
+else
+  ac_have_decl=0
+fi
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_DECL_ISNAN $ac_have_decl
+_ACEOF
+
+for ac_func in isfinite
+do :
+  ac_fn_c_check_func "$LINENO" "isfinite" "ac_cv_func_isfinite"
+if test "x$ac_cv_func_isfinite" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_ISFINITE 1
+_ACEOF
+
+fi
+done
+
+ac_fn_c_check_decl "$LINENO" "isfinite" "ac_cv_have_decl_isfinite" "#include <math.h>
+
+"
+if test "x$ac_cv_have_decl_isfinite" = xyes; then :
+  ac_have_decl=1
+else
+  ac_have_decl=0
+fi
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_DECL_ISFINITE $ac_have_decl
+_ACEOF
+
+      for _star_tmp in sst htx
+  do
+    STAR_DEPENDENCIES_CHILDREN="$STAR_DEPENDENCIES_CHILDREN<sourceset>$_star_tmp</sourceset>"
+  done
+
+
+#  Perform the check that configures f77.h.in for the return type of REAL
+#  Fortran functions. On 64-bit g77 with f2c compatibility this is double
+#  not float.
+if test "$use_fortran" = "1"; then
+$_star_docs_only &&
+        as_fn_error $? "STAR_CNF_F2C_SYMBOLS in docs-only dir" "$LINENO" 5
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $FC is in strict f2c compatible mode" >&5
+$as_echo_n "checking if $FC is in strict f2c compatible mode... " >&6; }
+if ${star_cv_cnf_f2c_compatible+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+          if test "$ac_cv_fc_compiler_gnu" = yes; then
+                      ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+           cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+float fred_() {
+   return 1.0f;
+}
+
+_ACEOF
+           if (eval $ac_compile) 2>&5
+           then
+               mv conftest.$ac_objext c-conftest.$ac_objext
+           else
+               as_fn_error $? "cannot compile a C function!" "$LINENO" 5
+           fi
+           ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+           ac_ext=${ac_fc_srcext-f}
+ac_compile='$FC -c $FCFLAGS $ac_fcflags_srcext conftest.$ac_ext >&5'
+ac_link='$FC -o conftest$ac_exeext $FCFLAGS $LDFLAGS $ac_fcflags_srcext conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_fc_compiler_gnu
+
+           cat > conftest.$ac_ext <<_ACEOF
+
+      PROGRAM F2CTEST
+      REAL FRED
+      REAL R
+      R = FRED()
+      IF ( R .NE. 0.0 ) THEN
+         WRITE(*,*) 'no'
+      ELSE
+         WRITE(*,*) 'yes'
+      ENDIF
+      END
+
+_ACEOF
+           star_cv_cnf_f2c_compatible=yes
+           $FC $FCFLAGS $opt -o conftest conftest.f c-conftest.$ac_objext 2>&5
+           if test -r conftest
+           then
+              star_cv_cnf_f2c_compatible=`eval ./conftest | sed 's/\ //g'` > /dev/null
+           else
+              as_fn_error $? "failed to link program" "$LINENO" 5
+           fi
+           ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+           rm -f conftest* c-conftest*
+      else
+         # Not a GNU compiler.
+         star_cv_cnf_f2c_compatible=no
+      fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $star_cv_cnf_f2c_compatible" >&5
+$as_echo "$star_cv_cnf_f2c_compatible" >&6; }
+    if test "$star_cv_cnf_f2c_compatible" = "yes"
+    then
+        REAL_FUNCTION_TYPE=double
+
+    else
+        REAL_FUNCTION_TYPE=float
+
+    fi
+
+
+# Determine the type of Fortran character string lengths.
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of void*" >&5
+$as_echo_n "checking size of void*... " >&6; }
+if ${ac_cv_sizeof_voidp+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (void*))" "ac_cv_sizeof_voidp"        "$ac_includes_default"; then :
+
+else
+  if test "$ac_cv_type_voidp" = yes; then
+     { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (void*)
+See \`config.log' for more details" "$LINENO" 5; }
+   else
+     ac_cv_sizeof_voidp=0
+   fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_voidp" >&5
+$as_echo "$ac_cv_sizeof_voidp" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_VOIDP $ac_cv_sizeof_voidp
+_ACEOF
+
+              { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${FC} has the %LOC extension" >&5
+$as_echo_n "checking whether ${FC} has the %LOC extension... " >&6; }
+if ${ac_cv_fc_have_percentloc+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_ext=${ac_fc_srcext-f}
+ac_compile='$FC -c $FCFLAGS $ac_fcflags_srcext conftest.$ac_ext >&5'
+ac_link='$FC -o conftest$ac_exeext $FCFLAGS $LDFLAGS $ac_fcflags_srcext conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_fc_compiler_gnu
+
+                          cat > conftest.$ac_ext <<_ACEOF
+      program main
+
+      INTEGER I, ADDR
+      I = 1
+      ADDR = %LOC( I )
+
+      end
+_ACEOF
+if ac_fn_fc_try_compile "$LINENO"; then :
+  ac_cv_fc_have_percentloc=yes
+else
+  ac_cv_fc_have_percentloc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+                          ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_fc_have_percentloc" >&5
+$as_echo "$ac_cv_fc_have_percentloc" >&6; }
+          if test $ac_cv_fc_have_percentloc = yes; then
+
+$as_echo "#define HAVE_PERCENTLOC 1" >>confdefs.h
+
+          fi
+     { $as_echo "$as_me:${as_lineno-$LINENO}: checking type used for Fortran string lengths" >&5
+$as_echo_n "checking type used for Fortran string lengths... " >&6; }
+if ${star_cv_cnf_trail_type+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test "$ac_cv_sizeof_voidp" = 8 -a "$ac_cv_fc_compiler_gnu" = no; then
+           if "$FC" -V 2>&1 < /dev/null | grep 'Intel.*64' > /dev/null; then
+              star_cv_cnf_trail_type=long
+           elif "$FC" -V 2>&1 < /dev/null | grep 'Sun.*Fortran' > /dev/null; then
+              star_cv_cnf_trail_type=long
+           else
+                            ac_ext=${ac_fc_srcext-f}
+ac_compile='$FC -c $FCFLAGS $ac_fcflags_srcext conftest.$ac_ext >&5'
+ac_link='$FC -o conftest$ac_exeext $FCFLAGS $LDFLAGS $ac_fcflags_srcext conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_fc_compiler_gnu
+
+              if test "$ac_cv_fc_have_percentloc" = yes; then
+                 FORTRAN_GETLOC='%loc'
+              else
+                 FORTRAN_GETLOC='loc'
+              fi
+              cat > conftest.$ac_ext <<_ACEOF
+
+      program conftest
+
+C  checks passing 4 byte character string lengths on 64bit compiler.
+
+      integer*8 ip1, ip2
+      integer*4 l1, l2
+      integer dummy1, dummy2
+      real dummy3, dummy4
+      double precision dummy5, dummy6
+
+      character str1*(1024)
+      character str2*(2048)
+
+      ip1 = $FORTRAN_GETLOC (str1)
+      ip2 = $FORTRAN_GETLOC (str2)
+
+      l1 = 1024
+      l2 = 2048
+
+      call report( dummy1, dummy2, %val(ip1), dummy3, dummy4,
+     :             %val(ip2), dummy5, dummy6,
+     :             %val(l1), %val(l2) )
+
+      end
+
+      subroutine report( dummy1, dummy2, str1, dummy3, dummy4,
+     :                   str2, dummy5, dummy6 )
+      integer dummy1, dummy2
+      real dummy3, dummy4
+      double precision dummy5, dummy6
+
+      character*(*) str1
+      character*(*) str2
+
+      if ( len(str1) .eq. 1024 .and. len(str2) .eq. 2048 ) then
+         print *, 'int'
+      else
+         print *, 'long'
+      endif
+      end
+
+_ACEOF
+              star_cv_cnf_trail_type=int
+              $FC $FCFLAGS $opt -o conftest conftest.f 2>&5
+              if test -r conftest
+              then
+                 star_cv_cnf_trail_type=`eval ./conftest | sed 's/\ //g'` > /dev/null
+              else
+                 as_fn_error $? "failed to link program" "$LINENO" 5
+              fi
+              rm -f conftest*
+              ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+           fi
+        else
+           star_cv_cnf_trail_type=int
+        fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $star_cv_cnf_trail_type" >&5
+$as_echo "$star_cv_cnf_trail_type" >&6; }
+    TRAIL_TYPE=$star_cv_cnf_trail_type
+
+
+cat >>confdefs.h <<_ACEOF
+#define TRAIL_TYPE $star_cv_cnf_trail_type
+_ACEOF
+
+
+fi
+
+#   Declare the message file.
+#
+    $_star_docs_only &&
+        as_fn_error $? "STAR_MESSGEN in docs-only directory" "$LINENO" 5
+          for _star_tmp in messgen
+  do
+    STAR_DEPENDENCIES_CHILDREN="$STAR_DEPENDENCIES_CHILDREN<sourceset>$_star_tmp</sourceset>"
+  done
+
+    _star_tmp='ast_err.msg'
+for marker in $_star_tmp
+do
+    if test -f $marker; then
+        _star_predist_marker_present=:
+        { $as_echo "$as_me:${as_lineno-$LINENO}: found predist marker file $marker" >&5
+$as_echo "$as_me: found predist marker file $marker" >&6;}
+    else
+        _star_predist_marker_present=false
+    fi
+    case $_star_predist_status in
+        unknown)
+            if $_star_predist_marker_present; then
+                # we do want to build sourceset files
+                _star_predist_status=predist
+                PREDIST=
+                { $as_echo "$as_me:${as_lineno-$LINENO}: in pre-distribution state" >&5
+$as_echo "$as_me: in pre-distribution state" >&6;}
+            else
+                _star_predist_status=postdist
+                PREDIST='#'
+                { $as_echo "$as_me:${as_lineno-$LINENO}: in post-distribution state" >&5
+$as_echo "$as_me: in post-distribution state" >&6;}
+            fi
+            ;;
+        predist)
+            if $_star_predist_marker_present; then
+                : OK
+            else
+                { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Building predist, but marker file $marker is not present" >&5
+$as_echo "$as_me: WARNING: Building predist, but marker file $marker is not present" >&2;}
+            fi
+            ;;
+        postdist)
+            if $_star_predist_marker_present; then
+                { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: In postdistribution state, but predist marker file $marker is present" >&5
+$as_echo "$as_me: WARNING: In postdistribution state, but predist marker file $marker is present" >&2;}
+            fi
+            ;;
+        *)
+            as_fn_error $? "impossible predist status $_star_predist_status" "$LINENO" 5
+            ;;
+    esac
+done
+
+    eval default_bindir=`echo $bindir | sed 's,\${exec_prefix},$ac_default_prefix,'`
+
+                      # Extract the first word of "messgen", so it can be a program name with args.
+set dummy messgen; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_MESSGEN+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $MESSGEN in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_MESSGEN="$MESSGEN" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_dummy="$STARLINK/Perl/bin:$STARLINK/starjava/bin:$STARLINK/bin:$default_bindir:$PATH"
+for as_dir in $as_dummy
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_MESSGEN="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_path_MESSGEN" && ac_cv_path_MESSGEN="messgen"
+  ;;
+esac
+fi
+MESSGEN=$ac_cv_path_MESSGEN
+if test -n "$MESSGEN"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MESSGEN" >&5
+$as_echo "$MESSGEN" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+
+
+
+#   Test for non-ANSI behaviour in sscanf on some platforms, reported by
+#   Bill Joye. Also check for bad MINGW sscanf. That returns nc=0 in the
+#   System test.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the sscanf function is ANSI-compatible" >&5
+$as_echo_n "checking whether the sscanf function is ANSI-compatible... " >&6; }
+if test "$cross_compiling" = yes; then :
+
+
+$as_echo "#define HAVE_NONANSI_SSCANF 1" >>confdefs.h
+
+
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+  char foo[] = " name 1111.1 ";
+  char mingw[] = "system= FK4_NO_E";
+
+  char bar[8];
+  float ff;
+  int system;
+  int nc;
+  int r;
+
+  nc = 0;
+  r = sscanf(foo, " %s %f %n", bar, &ff, &nc);
+
+  if ( nc == 13 ) {
+     nc = 0;
+     r = sscanf( mingw, "system= %n%*s %n", &system, &nc );
+     if ( nc != 0 ) nc = 13;
+  }
+  exit( ( nc != 13 ) ? 0 : 1 );
+}
+
+
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+   { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; };
+$as_echo "#define HAVE_NONANSI_SSCANF 1" >>confdefs.h
+
+
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+
+#   Declare the documentation.  We need to do complicated things to
+#   satisfy these targets, so give a non-null value
+#   for the second argument, to suppress automatic generation of
+#   rules.
+
+    STAR_DOCUMENTATION="$STAR_DOCUMENTATION sun210 sun211"
+
+        if $_star_build_docs; then
+            STAR_LATEX_DOCUMENTATION="sun210.pdf sun210.tex sun211.pdf sun211.tex sun210.htx_tar sun211.htx_tar"
+        fi
+
+    if $_star_build_docs; then
+        : ${LATEX2DVI='$$LATEX "\\batchmode\\input $$1" && $$LATEX "\\batchmode\\input $$1"'}
+
+    else
+        { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: not installing docs sun210 sun211" >&5
+$as_echo "$as_me: WARNING: not installing docs sun210 sun211" >&2;}
+    fi
+
+_star_tmp='sun_master.tex'
+for marker in $_star_tmp
+do
+    if test -f $marker; then
+        _star_predist_marker_present=:
+        { $as_echo "$as_me:${as_lineno-$LINENO}: found predist marker file $marker" >&5
+$as_echo "$as_me: found predist marker file $marker" >&6;}
+    else
+        _star_predist_marker_present=false
+    fi
+    case $_star_predist_status in
+        unknown)
+            if $_star_predist_marker_present; then
+                # we do want to build sourceset files
+                _star_predist_status=predist
+                PREDIST=
+                { $as_echo "$as_me:${as_lineno-$LINENO}: in pre-distribution state" >&5
+$as_echo "$as_me: in pre-distribution state" >&6;}
+            else
+                _star_predist_status=postdist
+                PREDIST='#'
+                { $as_echo "$as_me:${as_lineno-$LINENO}: in post-distribution state" >&5
+$as_echo "$as_me: in post-distribution state" >&6;}
+            fi
+            ;;
+        predist)
+            if $_star_predist_marker_present; then
+                : OK
+            else
+                { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Building predist, but marker file $marker is not present" >&5
+$as_echo "$as_me: WARNING: Building predist, but marker file $marker is not present" >&2;}
+            fi
+            ;;
+        postdist)
+            if $_star_predist_marker_present; then
+                { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: In postdistribution state, but predist marker file $marker is present" >&5
+$as_echo "$as_me: WARNING: In postdistribution state, but predist marker file $marker is present" >&2;}
+            fi
+            ;;
+        *)
+            as_fn_error $? "impossible predist status $_star_predist_status" "$LINENO" 5
+            ;;
+    esac
+done
+
+eval default_bindir=`echo $bindir | sed 's,\${exec_prefix},$ac_default_prefix,'`
+
+                      # Extract the first word of "star2html", so it can be a program name with args.
+set dummy star2html; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_STAR2HTML+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $STAR2HTML in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_STAR2HTML="$STAR2HTML" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_dummy="$STARLINK/Perl/bin:$STARLINK/starjava/bin:$STARLINK/bin:$default_bindir:$PATH"
+for as_dir in $as_dummy
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_STAR2HTML="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_path_STAR2HTML" && ac_cv_path_STAR2HTML="star2html"
+  ;;
+esac
+fi
+STAR2HTML=$ac_cv_path_STAR2HTML
+if test -n "$STAR2HTML"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STAR2HTML" >&5
+$as_echo "$STAR2HTML" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+
+eval default_bindir=`echo $bindir | sed 's,\${exec_prefix},$ac_default_prefix,'`
+
+                      # Extract the first word of "prolat", so it can be a program name with args.
+set dummy prolat; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PROLAT+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PROLAT in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PROLAT="$PROLAT" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_dummy="$STARLINK/Perl/bin:$STARLINK/starjava/bin:$STARLINK/bin:$STARLINK/bin/sst:$default_bindir:$default_bindir/sst:$PATH"
+for as_dir in $as_dummy
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_PROLAT="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_path_PROLAT" && ac_cv_path_PROLAT="prolat"
+  ;;
+esac
+fi
+PROLAT=$ac_cv_path_PROLAT
+if test -n "$PROLAT"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROLAT" >&5
+$as_echo "$PROLAT" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+   # prolat is part of SST
+
+ac_config_headers="$ac_config_headers config.h"
+
+
+ac_config_files="$ac_config_files Makefile component.xml ast_link ast_link_adam object.h"
+
+if test "$use_fortran" = "1"; then
+ac_config_files="$ac_config_files f77.h"
+
+fi
+
+ac_config_files="$ac_config_files ast_cpp"
+
+#   Following are files which are substituted by the Makefile at
+#   distribution time, rather than by configure.  They are not distributed.
+_star_tmp='version.h.in builddocs.in addversion.in'
+for marker in $_star_tmp
+do
+    if test -f $marker; then
+        _star_predist_marker_present=:
+        { $as_echo "$as_me:${as_lineno-$LINENO}: found predist marker file $marker" >&5
+$as_echo "$as_me: found predist marker file $marker" >&6;}
+    else
+        _star_predist_marker_present=false
+    fi
+    case $_star_predist_status in
+        unknown)
+            if $_star_predist_marker_present; then
+                # we do want to build sourceset files
+                _star_predist_status=predist
+                PREDIST=
+                { $as_echo "$as_me:${as_lineno-$LINENO}: in pre-distribution state" >&5
+$as_echo "$as_me: in pre-distribution state" >&6;}
+            else
+                _star_predist_status=postdist
+                PREDIST='#'
+                { $as_echo "$as_me:${as_lineno-$LINENO}: in post-distribution state" >&5
+$as_echo "$as_me: in post-distribution state" >&6;}
+            fi
+            ;;
+        predist)
+            if $_star_predist_marker_present; then
+                : OK
+            else
+                { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Building predist, but marker file $marker is not present" >&5
+$as_echo "$as_me: WARNING: Building predist, but marker file $marker is not present" >&2;}
+            fi
+            ;;
+        postdist)
+            if $_star_predist_marker_present; then
+                { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: In postdistribution state, but predist marker file $marker is present" >&5
+$as_echo "$as_me: WARNING: In postdistribution state, but predist marker file $marker is present" >&2;}
+            fi
+            ;;
+        *)
+            as_fn_error $? "impossible predist status $_star_predist_status" "$LINENO" 5
+            ;;
+    esac
+done
+
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+  for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+
+  (set) 2>&1 |
+    case $as_nl`(ac_space=' '; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      # `set' does not quote correctly, so add quotes: double-quote
+      # substitution turns \\\\ into \\, and sed turns \\ into \.
+      sed -n \
+	"s/'/'\\\\''/g;
+	  s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+      ;; #(
+    *)
+      # `set' quotes correctly as required by POSIX, so do not add quotes.
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+) |
+  sed '
+     /^ac_cv_env_/b end
+     t clear
+     :clear
+     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     t end
+     s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+     :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+  if test -w "$cache_file"; then
+    if test "x$cache_file" != "x/dev/null"; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+      if test ! -f "$cache_file" || test -h "$cache_file"; then
+	cat confcache >"$cache_file"
+      else
+        case $cache_file in #(
+        */* | ?:*)
+	  mv -f confcache "$cache_file"$$ &&
+	  mv -f "$cache_file"$$ "$cache_file" ;; #(
+        *)
+	  mv -f confcache "$cache_file" ;;
+	esac
+      fi
+    fi
+  else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+  fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+DEFS=-DHAVE_CONFIG_H
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+  # 1. Remove the extension, and $U if already installed.
+  ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+  ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+  # 2. Prepend LIBOBJDIR.  When used with automake>=1.10 LIBOBJDIR
+  #    will be set to the directory where LIBOBJS objects are built.
+  as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+  as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5
+$as_echo_n "checking that generated files are newer than configure... " >&6; }
+   if test -n "$am_sleep_pid"; then
+     # Hide warnings about reused PIDs.
+     wait $am_sleep_pid 2>/dev/null
+   fi
+   { $as_echo "$as_me:${as_lineno-$LINENO}: result: done" >&5
+$as_echo "done" >&6; }
+ if test -n "$EXEEXT"; then
+  am__EXEEXT_TRUE=
+  am__EXEEXT_FALSE='#'
+else
+  am__EXEEXT_TRUE='#'
+  am__EXEEXT_FALSE=
+fi
+
+if test -z "${EXTERNAL_PAL_TRUE}" && test -z "${EXTERNAL_PAL_FALSE}"; then
+  as_fn_error $? "conditional \"EXTERNAL_PAL\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then
+  as_fn_error $? "conditional \"AMDEP\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then
+  as_fn_error $? "conditional \"am__fastdepCC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${NOPIC_TRUE}" && test -z "${NOPIC_FALSE}"; then
+  as_fn_error $? "conditional \"NOPIC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${NOTHREADS_TRUE}" && test -z "${NOTHREADS_FALSE}"; then
+  as_fn_error $? "conditional \"NOTHREADS\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${NOFORTRAN_TRUE}" && test -z "${NOFORTRAN_FALSE}"; then
+  as_fn_error $? "conditional \"NOFORTRAN\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+	expr "X$arg" : "X\\(.*\\)$as_nl";
+	arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""	$as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+	 X"$0" : 'X\(//\)$' \| \
+	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='	';;	# ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='	';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -pR'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -pR'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -pR'
+  fi
+else
+  as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_dir" : 'X\(//\)[^/]' \| \
+	 X"$as_dir" : 'X\(//\)$' \| \
+	 X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by ast $as_me 8.4.0, which was
+generated by Starlink Autoconf 2.69.  Invocation command line was
+
+  CONFIG_FILES    = $CONFIG_FILES
+  CONFIG_HEADERS  = $CONFIG_HEADERS
+  CONFIG_LINKS    = $CONFIG_LINKS
+  CONFIG_COMMANDS = $CONFIG_COMMANDS
+  $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+case $ac_config_headers in *"
+"*) set x $ac_config_headers; shift; ac_config_headers=$*;;
+esac
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_headers="$ac_config_headers"
+config_commands="$ac_config_commands"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration.  Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+  -h, --help       print this help, then exit
+  -V, --version    print version number and configuration settings, then exit
+      --config     print configuration, then exit
+  -q, --quiet, --silent
+                   do not print progress messages
+  -d, --debug      don't remove temporary files
+      --recheck    update $as_me by reconfiguring in the same conditions
+      --file=FILE[:TEMPLATE]
+                   instantiate the configuration file FILE
+      --header=FILE[:TEMPLATE]
+                   instantiate the configuration header FILE
+
+Configuration files:
+$config_files
+
+Configuration headers:
+$config_headers
+
+Configuration commands:
+$config_commands
+
+Report bugs to <[email-masked]>."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_version="\\
+ast config.status 8.4.0
+configured by $0, generated by Starlink Autoconf 2.69,
+  with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+INSTALL='$INSTALL'
+MKDIR_P='$MKDIR_P'
+AWK='$AWK'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+  case $1 in
+  --*=?*)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+    ac_shift=:
+    ;;
+  --*=)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=
+    ac_shift=:
+    ;;
+  *)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
+    ;;
+  esac
+
+  case $ac_option in
+  # Handling of the options.
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    ac_cs_recheck=: ;;
+  --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+    $as_echo "$ac_cs_version"; exit ;;
+  --config | --confi | --conf | --con | --co | --c )
+    $as_echo "$ac_cs_config"; exit ;;
+  --debug | --debu | --deb | --de | --d | -d )
+    debug=: ;;
+  --file | --fil | --fi | --f )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    '') as_fn_error $? "missing file argument" ;;
+    esac
+    as_fn_append CONFIG_FILES " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --header | --heade | --head | --hea )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    as_fn_append CONFIG_HEADERS " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --he | --h)
+    # Conflict between --help and --header
+    as_fn_error $? "ambiguous option: \`$1'
+Try \`$0 --help' for more information.";;
+  --help | --hel | -h )
+    $as_echo "$ac_cs_usage"; exit ;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
+
+  # This is an error.
+  -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+  *) as_fn_append ac_config_targets " $1"
+     ac_need_defaults=false ;;
+
+  esac
+  shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+  set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+  shift
+  \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+  CONFIG_SHELL='$SHELL'
+  export CONFIG_SHELL
+  exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+  echo
+  sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+  $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+#
+# INIT-COMMANDS
+#
+AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"
+
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+sed_quote_subst='$sed_quote_subst'
+double_quote_subst='$double_quote_subst'
+delay_variable_subst='$delay_variable_subst'
+macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`'
+macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`'
+enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`'
+enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`'
+pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`'
+enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`'
+SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`'
+ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`'
+PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`'
+host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`'
+host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`'
+host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`'
+build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`'
+build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`'
+build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`'
+SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`'
+Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`'
+GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`'
+EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`'
+FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`'
+LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`'
+NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`'
+LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`'
+max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`'
+ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`'
+exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`'
+lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`'
+lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`'
+lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`'
+lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`'
+lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`'
+reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`'
+reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`'
+OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`'
+deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`'
+file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`'
+file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`'
+want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`'
+DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`'
+sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`'
+AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`'
+AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`'
+archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`'
+STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`'
+RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`'
+old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`'
+old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`'
+old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`'
+lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`'
+CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`'
+CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`'
+compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`'
+GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`'
+nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`'
+lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`'
+objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`'
+MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`'
+lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`'
+need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`'
+MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`'
+DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`'
+NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`'
+LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`'
+OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`'
+OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`'
+libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`'
+shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`'
+extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`'
+archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`'
+enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`'
+export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`'
+whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`'
+compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`'
+old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`'
+old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`'
+archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`'
+archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`'
+module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`'
+module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`'
+with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`'
+allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`'
+no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`'
+hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`'
+hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`'
+hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`'
+hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`'
+hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`'
+inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`'
+link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`'
+always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`'
+export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`'
+exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`'
+include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`'
+prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`'
+postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`'
+file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`'
+variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`'
+need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`'
+need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`'
+version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`'
+runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`'
+shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`'
+shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`'
+libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`'
+library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`'
+soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`'
+install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`'
+postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`'
+postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`'
+finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`'
+finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`'
+hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`'
+sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`'
+sys_lib_dlsearch_path_spec='`$ECHO "$sys_lib_dlsearch_path_spec" | $SED "$delay_single_quote_subst"`'
+hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`'
+enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`'
+enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`'
+enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`'
+old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`'
+striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`'
+compiler_lib_search_dirs='`$ECHO "$compiler_lib_search_dirs" | $SED "$delay_single_quote_subst"`'
+predep_objects='`$ECHO "$predep_objects" | $SED "$delay_single_quote_subst"`'
+postdep_objects='`$ECHO "$postdep_objects" | $SED "$delay_single_quote_subst"`'
+predeps='`$ECHO "$predeps" | $SED "$delay_single_quote_subst"`'
+postdeps='`$ECHO "$postdeps" | $SED "$delay_single_quote_subst"`'
+compiler_lib_search_path='`$ECHO "$compiler_lib_search_path" | $SED "$delay_single_quote_subst"`'
+LD_FC='`$ECHO "$LD_FC" | $SED "$delay_single_quote_subst"`'
+reload_flag_FC='`$ECHO "$reload_flag_FC" | $SED "$delay_single_quote_subst"`'
+reload_cmds_FC='`$ECHO "$reload_cmds_FC" | $SED "$delay_single_quote_subst"`'
+old_archive_cmds_FC='`$ECHO "$old_archive_cmds_FC" | $SED "$delay_single_quote_subst"`'
+compiler_FC='`$ECHO "$compiler_FC" | $SED "$delay_single_quote_subst"`'
+GCC_FC='`$ECHO "$GCC_FC" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_no_builtin_flag_FC='`$ECHO "$lt_prog_compiler_no_builtin_flag_FC" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_pic_FC='`$ECHO "$lt_prog_compiler_pic_FC" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_wl_FC='`$ECHO "$lt_prog_compiler_wl_FC" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_static_FC='`$ECHO "$lt_prog_compiler_static_FC" | $SED "$delay_single_quote_subst"`'
+lt_cv_prog_compiler_c_o_FC='`$ECHO "$lt_cv_prog_compiler_c_o_FC" | $SED "$delay_single_quote_subst"`'
+archive_cmds_need_lc_FC='`$ECHO "$archive_cmds_need_lc_FC" | $SED "$delay_single_quote_subst"`'
+enable_shared_with_static_runtimes_FC='`$ECHO "$enable_shared_with_static_runtimes_FC" | $SED "$delay_single_quote_subst"`'
+export_dynamic_flag_spec_FC='`$ECHO "$export_dynamic_flag_spec_FC" | $SED "$delay_single_quote_subst"`'
+whole_archive_flag_spec_FC='`$ECHO "$whole_archive_flag_spec_FC" | $SED "$delay_single_quote_subst"`'
+compiler_needs_object_FC='`$ECHO "$compiler_needs_object_FC" | $SED "$delay_single_quote_subst"`'
+old_archive_from_new_cmds_FC='`$ECHO "$old_archive_from_new_cmds_FC" | $SED "$delay_single_quote_subst"`'
+old_archive_from_expsyms_cmds_FC='`$ECHO "$old_archive_from_expsyms_cmds_FC" | $SED "$delay_single_quote_subst"`'
+archive_cmds_FC='`$ECHO "$archive_cmds_FC" | $SED "$delay_single_quote_subst"`'
+archive_expsym_cmds_FC='`$ECHO "$archive_expsym_cmds_FC" | $SED "$delay_single_quote_subst"`'
+module_cmds_FC='`$ECHO "$module_cmds_FC" | $SED "$delay_single_quote_subst"`'
+module_expsym_cmds_FC='`$ECHO "$module_expsym_cmds_FC" | $SED "$delay_single_quote_subst"`'
+with_gnu_ld_FC='`$ECHO "$with_gnu_ld_FC" | $SED "$delay_single_quote_subst"`'
+allow_undefined_flag_FC='`$ECHO "$allow_undefined_flag_FC" | $SED "$delay_single_quote_subst"`'
+no_undefined_flag_FC='`$ECHO "$no_undefined_flag_FC" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_flag_spec_FC='`$ECHO "$hardcode_libdir_flag_spec_FC" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_separator_FC='`$ECHO "$hardcode_libdir_separator_FC" | $SED "$delay_single_quote_subst"`'
+hardcode_direct_FC='`$ECHO "$hardcode_direct_FC" | $SED "$delay_single_quote_subst"`'
+hardcode_direct_absolute_FC='`$ECHO "$hardcode_direct_absolute_FC" | $SED "$delay_single_quote_subst"`'
+hardcode_minus_L_FC='`$ECHO "$hardcode_minus_L_FC" | $SED "$delay_single_quote_subst"`'
+hardcode_shlibpath_var_FC='`$ECHO "$hardcode_shlibpath_var_FC" | $SED "$delay_single_quote_subst"`'
+hardcode_automatic_FC='`$ECHO "$hardcode_automatic_FC" | $SED "$delay_single_quote_subst"`'
+inherit_rpath_FC='`$ECHO "$inherit_rpath_FC" | $SED "$delay_single_quote_subst"`'
+link_all_deplibs_FC='`$ECHO "$link_all_deplibs_FC" | $SED "$delay_single_quote_subst"`'
+always_export_symbols_FC='`$ECHO "$always_export_symbols_FC" | $SED "$delay_single_quote_subst"`'
+export_symbols_cmds_FC='`$ECHO "$export_symbols_cmds_FC" | $SED "$delay_single_quote_subst"`'
+exclude_expsyms_FC='`$ECHO "$exclude_expsyms_FC" | $SED "$delay_single_quote_subst"`'
+include_expsyms_FC='`$ECHO "$include_expsyms_FC" | $SED "$delay_single_quote_subst"`'
+prelink_cmds_FC='`$ECHO "$prelink_cmds_FC" | $SED "$delay_single_quote_subst"`'
+postlink_cmds_FC='`$ECHO "$postlink_cmds_FC" | $SED "$delay_single_quote_subst"`'
+file_list_spec_FC='`$ECHO "$file_list_spec_FC" | $SED "$delay_single_quote_subst"`'
+hardcode_action_FC='`$ECHO "$hardcode_action_FC" | $SED "$delay_single_quote_subst"`'
+compiler_lib_search_dirs_FC='`$ECHO "$compiler_lib_search_dirs_FC" | $SED "$delay_single_quote_subst"`'
+predep_objects_FC='`$ECHO "$predep_objects_FC" | $SED "$delay_single_quote_subst"`'
+postdep_objects_FC='`$ECHO "$postdep_objects_FC" | $SED "$delay_single_quote_subst"`'
+predeps_FC='`$ECHO "$predeps_FC" | $SED "$delay_single_quote_subst"`'
+postdeps_FC='`$ECHO "$postdeps_FC" | $SED "$delay_single_quote_subst"`'
+compiler_lib_search_path_FC='`$ECHO "$compiler_lib_search_path_FC" | $SED "$delay_single_quote_subst"`'
+
+LTCC='$LTCC'
+LTCFLAGS='$LTCFLAGS'
+compiler='$compiler_DEFAULT'
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+  eval 'cat <<_LTECHO_EOF
+\$1
+_LTECHO_EOF'
+}
+
+# Quote evaled strings.
+for var in SHELL \
+ECHO \
+PATH_SEPARATOR \
+SED \
+GREP \
+EGREP \
+FGREP \
+LD \
+NM \
+LN_S \
+lt_SP2NL \
+lt_NL2SP \
+reload_flag \
+OBJDUMP \
+deplibs_check_method \
+file_magic_cmd \
+file_magic_glob \
+want_nocaseglob \
+DLLTOOL \
+sharedlib_from_linklib_cmd \
+AR \
+AR_FLAGS \
+archiver_list_spec \
+STRIP \
+RANLIB \
+CC \
+CFLAGS \
+compiler \
+lt_cv_sys_global_symbol_pipe \
+lt_cv_sys_global_symbol_to_cdecl \
+lt_cv_sys_global_symbol_to_c_name_address \
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \
+nm_file_list_spec \
+lt_prog_compiler_no_builtin_flag \
+lt_prog_compiler_pic \
+lt_prog_compiler_wl \
+lt_prog_compiler_static \
+lt_cv_prog_compiler_c_o \
+need_locks \
+MANIFEST_TOOL \
+DSYMUTIL \
+NMEDIT \
+LIPO \
+OTOOL \
+OTOOL64 \
+shrext_cmds \
+export_dynamic_flag_spec \
+whole_archive_flag_spec \
+compiler_needs_object \
+with_gnu_ld \
+allow_undefined_flag \
+no_undefined_flag \
+hardcode_libdir_flag_spec \
+hardcode_libdir_separator \
+exclude_expsyms \
+include_expsyms \
+file_list_spec \
+variables_saved_for_relink \
+libname_spec \
+library_names_spec \
+soname_spec \
+install_override_mode \
+finish_eval \
+old_striplib \
+striplib \
+compiler_lib_search_dirs \
+predep_objects \
+postdep_objects \
+predeps \
+postdeps \
+compiler_lib_search_path \
+LD_FC \
+reload_flag_FC \
+compiler_FC \
+lt_prog_compiler_no_builtin_flag_FC \
+lt_prog_compiler_pic_FC \
+lt_prog_compiler_wl_FC \
+lt_prog_compiler_static_FC \
+lt_cv_prog_compiler_c_o_FC \
+export_dynamic_flag_spec_FC \
+whole_archive_flag_spec_FC \
+compiler_needs_object_FC \
+with_gnu_ld_FC \
+allow_undefined_flag_FC \
+no_undefined_flag_FC \
+hardcode_libdir_flag_spec_FC \
+hardcode_libdir_separator_FC \
+exclude_expsyms_FC \
+include_expsyms_FC \
+file_list_spec_FC \
+compiler_lib_search_dirs_FC \
+predep_objects_FC \
+postdep_objects_FC \
+predeps_FC \
+postdeps_FC \
+compiler_lib_search_path_FC; do
+    case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+    *[\\\\\\\`\\"\\\$]*)
+      eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\""
+      ;;
+    *)
+      eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+      ;;
+    esac
+done
+
+# Double-quote double-evaled strings.
+for var in reload_cmds \
+old_postinstall_cmds \
+old_postuninstall_cmds \
+old_archive_cmds \
+extract_expsyms_cmds \
+old_archive_from_new_cmds \
+old_archive_from_expsyms_cmds \
+archive_cmds \
+archive_expsym_cmds \
+module_cmds \
+module_expsym_cmds \
+export_symbols_cmds \
+prelink_cmds \
+postlink_cmds \
+postinstall_cmds \
+postuninstall_cmds \
+finish_cmds \
+sys_lib_search_path_spec \
+sys_lib_dlsearch_path_spec \
+reload_cmds_FC \
+old_archive_cmds_FC \
+old_archive_from_new_cmds_FC \
+old_archive_from_expsyms_cmds_FC \
+archive_cmds_FC \
+archive_expsym_cmds_FC \
+module_cmds_FC \
+module_expsym_cmds_FC \
+export_symbols_cmds_FC \
+prelink_cmds_FC \
+postlink_cmds_FC; do
+    case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+    *[\\\\\\\`\\"\\\$]*)
+      eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\""
+      ;;
+    *)
+      eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+      ;;
+    esac
+done
+
+ac_aux_dir='$ac_aux_dir'
+xsi_shell='$xsi_shell'
+lt_shell_append='$lt_shell_append'
+
+# See if we are running on zsh, and set the options which allow our
+# commands through without removal of \ escapes INIT.
+if test -n "\${ZSH_VERSION+set}" ; then
+   setopt NO_GLOB_SUBST
+fi
+
+
+    PACKAGE='$PACKAGE'
+    VERSION='$VERSION'
+    TIMESTAMP='$TIMESTAMP'
+    RM='$RM'
+    ofile='$ofile'
+
+
+
+
+
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+  case $ac_config_target in
+    "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;;
+    "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;;
+    "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
+    "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+    "component.xml") CONFIG_FILES="$CONFIG_FILES component.xml" ;;
+    "ast_link") CONFIG_FILES="$CONFIG_FILES ast_link" ;;
+    "ast_link_adam") CONFIG_FILES="$CONFIG_FILES ast_link_adam" ;;
+    "object.h") CONFIG_FILES="$CONFIG_FILES object.h" ;;
+    "f77.h") CONFIG_FILES="$CONFIG_FILES f77.h" ;;
+    "ast_cpp") CONFIG_FILES="$CONFIG_FILES ast_cpp" ;;
+
+  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+  esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used.  Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+  test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
+  test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands
+fi
+
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+  tmp= ac_tmp=
+  trap 'exit_status=$?
+  : "${ac_tmp:=$tmp}"
+  { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+  trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+  tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+  test -d "$tmp"
+}  ||
+{
+  tmp=./conf$$-$RANDOM
+  (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+  eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+  ac_cs_awk_cr='\\r'
+else
+  ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+  echo "cat >conf$$subs.awk <<_ACEOF" &&
+  echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+  echo "_ACEOF"
+} >conf$$subs.sh ||
+  as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+  . ./conf$$subs.sh ||
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+  ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+  if test $ac_delim_n = $ac_delim_num; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+  N
+  s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+  for (key in S) S_is_set[key] = 1
+  FS = ""
+
+}
+{
+  line = $ 0
+  nfields = split(line, field, "@")
+  substed = 0
+  len = length(field[1])
+  for (i = 2; i < nfields; i++) {
+    key = field[i]
+    keylen = length(key)
+    if (S_is_set[key]) {
+      value = S[key]
+      line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+      len += length(value) + length(field[++i])
+      substed = 1
+    } else
+      len += 1 + keylen
+  }
+
+  print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+  sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+  cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+  || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[	 ]*VPATH[	 ]*=[	 ]*/{
+h
+s///
+s/^/:/
+s/[	 ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[	 ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[	 ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+# Set up the scripts for CONFIG_HEADERS section.
+# No need to generate them if there are no CONFIG_HEADERS.
+# This happens for instance with `./config.status Makefile'.
+if test -n "$CONFIG_HEADERS"; then
+cat >"$ac_tmp/defines.awk" <<\_ACAWK ||
+BEGIN {
+_ACEOF
+
+# Transform confdefs.h into an awk script `defines.awk', embedded as
+# here-document in config.status, that substitutes the proper values into
+# config.h.in to produce config.h.
+
+# Create a delimiter string that does not exist in confdefs.h, to ease
+# handling of long lines.
+ac_delim='%!_!# '
+for ac_last_try in false false :; do
+  ac_tt=`sed -n "/$ac_delim/p" confdefs.h`
+  if test -z "$ac_tt"; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+
+# For the awk script, D is an array of macro values keyed by name,
+# likewise P contains macro parameters if any.  Preserve backslash
+# newline sequences.
+
+ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]*
+sed -n '
+s/.\{148\}/&'"$ac_delim"'/g
+t rset
+:rset
+s/^[	 ]*#[	 ]*define[	 ][	 ]*/ /
+t def
+d
+:def
+s/\\$//
+t bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[	 ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3"/p
+s/^ \('"$ac_word_re"'\)[	 ]*\(.*\)/D["\1"]=" \2"/p
+d
+:bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[	 ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3\\\\\\n"\\/p
+t cont
+s/^ \('"$ac_word_re"'\)[	 ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p
+t cont
+d
+:cont
+n
+s/.\{148\}/&'"$ac_delim"'/g
+t clear
+:clear
+s/\\$//
+t bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/"/p
+d
+:bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p
+b cont
+' <confdefs.h | sed '
+s/'"$ac_delim"'/"\\\
+"/g' >>$CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  for (key in D) D_is_set[key] = 1
+  FS = ""
+}
+/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ {
+  line = \$ 0
+  split(line, arg, " ")
+  if (arg[1] == "#") {
+    defundef = arg[2]
+    mac1 = arg[3]
+  } else {
+    defundef = substr(arg[1], 2)
+    mac1 = arg[2]
+  }
+  split(mac1, mac2, "(") #)
+  macro = mac2[1]
+  prefix = substr(line, 1, index(line, defundef) - 1)
+  if (D_is_set[macro]) {
+    # Preserve the white space surrounding the "#".
+    print prefix "define", macro P[macro] D[macro]
+    next
+  } else {
+    # Replace #undef with comments.  This is necessary, for example,
+    # in the case of _POSIX_SOURCE, which is predefined and required
+    # on some systems where configure will not decide to define it.
+    if (defundef == "undef") {
+      print "/*", prefix defundef, macro, "*/"
+      next
+    }
+  }
+}
+{ print }
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+  as_fn_error $? "could not setup config headers machinery" "$LINENO" 5
+fi # test -n "$CONFIG_HEADERS"
+
+
+eval set X "  :F $CONFIG_FILES  :H $CONFIG_HEADERS    :C $CONFIG_COMMANDS"
+shift
+for ac_tag
+do
+  case $ac_tag in
+  :[FHLC]) ac_mode=$ac_tag; continue;;
+  esac
+  case $ac_mode$ac_tag in
+  :[FHL]*:*);;
+  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+  :[FH]-) ac_tag=-:-;;
+  :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+  esac
+  ac_save_IFS=$IFS
+  IFS=:
+  set x $ac_tag
+  IFS=$ac_save_IFS
+  shift
+  ac_file=$1
+  shift
+
+  case $ac_mode in
+  :L) ac_source=$1;;
+  :[FH])
+    ac_file_inputs=
+    for ac_f
+    do
+      case $ac_f in
+      -) ac_f="$ac_tmp/stdin";;
+      *) # Look for the file first in the build tree, then in the source tree
+	 # (if the path is not absolute).  The absolute path cannot be DOS-style,
+	 # because $ac_f cannot contain `:'.
+	 test -f "$ac_f" ||
+	   case $ac_f in
+	   [\\/$]*) false;;
+	   *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+	   esac ||
+	   as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+      esac
+      case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+      as_fn_append ac_file_inputs " '$ac_f'"
+    done
+
+    # Let's still pretend it is `configure' which instantiates (i.e., don't
+    # use $as_me), people would be surprised to read:
+    #    /* config.h.  Generated by config.status.  */
+    configure_input='Generated from '`
+	  $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+	`' by configure.'
+    if test x"$ac_file" != x-; then
+      configure_input="$ac_file.  $configure_input"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+    fi
+    # Neutralize special characters interpreted by sed in replacement strings.
+    case $configure_input in #(
+    *\&* | *\|* | *\\* )
+       ac_sed_conf_input=`$as_echo "$configure_input" |
+       sed 's/[\\\\&|]/\\\\&/g'`;; #(
+    *) ac_sed_conf_input=$configure_input;;
+    esac
+
+    case $ac_tag in
+    *:-:* | *:-) cat >"$ac_tmp/stdin" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+    esac
+    ;;
+  esac
+
+  ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$ac_file" : 'X\(//\)[^/]' \| \
+	 X"$ac_file" : 'X\(//\)$' \| \
+	 X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+  as_dir="$ac_dir"; as_fn_mkdir_p
+  ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+  case $ac_mode in
+  :F)
+  #
+  # CONFIG_FILE
+  #
+
+  case $INSTALL in
+  [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+  *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;;
+  esac
+  ac_MKDIR_P=$MKDIR_P
+  case $MKDIR_P in
+  [\\/$]* | ?:[\\/]* ) ;;
+  */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;;
+  esac
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+  p
+  q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  ac_datarootdir_hack='
+  s&@datadir@&$datadir&g
+  s&@docdir@&$docdir&g
+  s&@infodir@&$infodir&g
+  s&@localedir@&$localedir&g
+  s&@mandir@&$mandir&g
+  s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+s&@INSTALL@&$ac_INSTALL&;t t
+s&@MKDIR_P@&$ac_MKDIR_P&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+  >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[	 ]*datarootdir[	 ]*:*=/p' \
+      "$ac_tmp/out"`; test -z "$ac_out"; } &&
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&2;}
+
+  rm -f "$ac_tmp/stdin"
+  case $ac_file in
+  -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+  *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+  esac \
+  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+  :H)
+  #
+  # CONFIG_HEADER
+  #
+  if test x"$ac_file" != x-; then
+    {
+      $as_echo "/* $configure_input  */" \
+      && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs"
+    } >"$ac_tmp/config.h" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+    if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
+$as_echo "$as_me: $ac_file is unchanged" >&6;}
+    else
+      rm -f "$ac_file"
+      mv "$ac_tmp/config.h" "$ac_file" \
+	|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
+    fi
+  else
+    $as_echo "/* $configure_input  */" \
+      && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \
+      || as_fn_error $? "could not create -" "$LINENO" 5
+  fi
+# Compute "$ac_file"'s index in $config_headers.
+_am_arg="$ac_file"
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+  case $_am_header in
+    $_am_arg | $_am_arg:* )
+      break ;;
+    * )
+      _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+  esac
+done
+echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" ||
+$as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$_am_arg" : 'X\(//\)[^/]' \| \
+	 X"$_am_arg" : 'X\(//\)$' \| \
+	 X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$_am_arg" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`/stamp-h$_am_stamp_count
+ ;;
+
+  :C)  { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5
+$as_echo "$as_me: executing $ac_file commands" >&6;}
+ ;;
+  esac
+
+
+  case $ac_file$ac_mode in
+    "depfiles":C) test x"$AMDEP_TRUE" != x"" || {
+  # Older Autoconf quotes --file arguments for eval, but not when files
+  # are listed without --file.  Let's play safe and only enable the eval
+  # if we detect the quoting.
+  case $CONFIG_FILES in
+  *\'*) eval set x "$CONFIG_FILES" ;;
+  *)   set x $CONFIG_FILES ;;
+  esac
+  shift
+  for mf
+  do
+    # Strip MF so we end up with the name of the file.
+    mf=`echo "$mf" | sed -e 's/:.*$//'`
+    # Check whether this is an Automake generated Makefile or not.
+    # We used to match only the files named 'Makefile.in', but
+    # some people rename them; so instead we look at the file content.
+    # Grep'ing the first line is not enough: some people post-process
+    # each Makefile.in and add a new line on top of each file to say so.
+    # Grep'ing the whole file is not good either: AIX grep has a line
+    # limit of 2048, but all sed's we know have understand at least 4000.
+    if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+      dirpart=`$as_dirname -- "$mf" ||
+$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$mf" : 'X\(//\)[^/]' \| \
+	 X"$mf" : 'X\(//\)$' \| \
+	 X"$mf" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$mf" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+    else
+      continue
+    fi
+    # Extract the definition of DEPDIR, am__include, and am__quote
+    # from the Makefile without running 'make'.
+    DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
+    test -z "$DEPDIR" && continue
+    am__include=`sed -n 's/^am__include = //p' < "$mf"`
+    test -z "$am__include" && continue
+    am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
+    # Find all dependency output files, they are included files with
+    # $(DEPDIR) in their names.  We invoke sed twice because it is the
+    # simplest approach to changing $(DEPDIR) to its actual value in the
+    # expansion.
+    for file in `sed -n "
+      s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
+	 sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do
+      # Make sure the directory exists.
+      test -f "$dirpart/$file" && continue
+      fdir=`$as_dirname -- "$file" ||
+$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$file" : 'X\(//\)[^/]' \| \
+	 X"$file" : 'X\(//\)$' \| \
+	 X"$file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+      as_dir=$dirpart/$fdir; as_fn_mkdir_p
+      # echo "creating $dirpart/$file"
+      echo '# dummy' > "$dirpart/$file"
+    done
+  done
+}
+ ;;
+    "libtool":C)
+
+    # See if we are running on zsh, and set the options which allow our
+    # commands through without removal of \ escapes.
+    if test -n "${ZSH_VERSION+set}" ; then
+      setopt NO_GLOB_SUBST
+    fi
+
+    cfgfile="${ofile}T"
+    trap "$RM \"$cfgfile\"; exit 1" 1 2 15
+    $RM "$cfgfile"
+
+    cat <<_LT_EOF >> "$cfgfile"
+#! $SHELL
+
+# `$ECHO "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services.
+# Generated automatically by $as_me ($PACKAGE$TIMESTAMP) $VERSION
+# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+# NOTE: Changes made to this file will be lost: look at ltmain.sh.
+#
+#   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
+#                 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+#                 Foundation, Inc.
+#   Written by Gordon Matzigkeit, 1996
+#
+#   This file is part of GNU Libtool.
+#
+# GNU Libtool is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# As a special exception to the GNU General Public License,
+# if you distribute this file as part of a program or library that
+# is built using GNU Libtool, you may include this file under the
+# same distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Libtool; see the file COPYING.  If not, a copy
+# can be downloaded from http://www.gnu.org/licenses/gpl.html, or
+# obtained by writing to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+
+# The names of the tagged configurations supported by this script.
+available_tags="FC "
+
+# ### BEGIN LIBTOOL CONFIG
+
+# Which release of libtool.m4 was used?
+macro_version=$macro_version
+macro_revision=$macro_revision
+
+# Whether or not to build shared libraries.
+build_libtool_libs=$enable_shared
+
+# Whether or not to build static libraries.
+build_old_libs=$enable_static
+
+# What type of objects to build.
+pic_mode=$pic_mode
+
+# Whether or not to optimize for fast installation.
+fast_install=$enable_fast_install
+
+# Shell to use when invoking shell scripts.
+SHELL=$lt_SHELL
+
+# An echo program that protects backslashes.
+ECHO=$lt_ECHO
+
+# The PATH separator for the build system.
+PATH_SEPARATOR=$lt_PATH_SEPARATOR
+
+# The host system.
+host_alias=$host_alias
+host=$host
+host_os=$host_os
+
+# The build system.
+build_alias=$build_alias
+build=$build
+build_os=$build_os
+
+# A sed program that does not truncate output.
+SED=$lt_SED
+
+# Sed that helps us avoid accidentally triggering echo(1) options like -n.
+Xsed="\$SED -e 1s/^X//"
+
+# A grep program that handles long lines.
+GREP=$lt_GREP
+
+# An ERE matcher.
+EGREP=$lt_EGREP
+
+# A literal string matcher.
+FGREP=$lt_FGREP
+
+# A BSD- or MS-compatible name lister.
+NM=$lt_NM
+
+# Whether we need soft or hard links.
+LN_S=$lt_LN_S
+
+# What is the maximum length of a command?
+max_cmd_len=$max_cmd_len
+
+# Object file suffix (normally "o").
+objext=$ac_objext
+
+# Executable file suffix (normally "").
+exeext=$exeext
+
+# whether the shell understands "unset".
+lt_unset=$lt_unset
+
+# turn spaces into newlines.
+SP2NL=$lt_lt_SP2NL
+
+# turn newlines into spaces.
+NL2SP=$lt_lt_NL2SP
+
+# convert \$build file names to \$host format.
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+
+# convert \$build files to toolchain format.
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+
+# An object symbol dumper.
+OBJDUMP=$lt_OBJDUMP
+
+# Method to check whether dependent libraries are shared objects.
+deplibs_check_method=$lt_deplibs_check_method
+
+# Command to use when deplibs_check_method = "file_magic".
+file_magic_cmd=$lt_file_magic_cmd
+
+# How to find potential files when deplibs_check_method = "file_magic".
+file_magic_glob=$lt_file_magic_glob
+
+# Find potential files using nocaseglob when deplibs_check_method = "file_magic".
+want_nocaseglob=$lt_want_nocaseglob
+
+# DLL creation program.
+DLLTOOL=$lt_DLLTOOL
+
+# Command to associate shared and link libraries.
+sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd
+
+# The archiver.
+AR=$lt_AR
+
+# Flags to create an archive.
+AR_FLAGS=$lt_AR_FLAGS
+
+# How to feed a file listing to the archiver.
+archiver_list_spec=$lt_archiver_list_spec
+
+# A symbol stripping program.
+STRIP=$lt_STRIP
+
+# Commands used to install an old-style archive.
+RANLIB=$lt_RANLIB
+old_postinstall_cmds=$lt_old_postinstall_cmds
+old_postuninstall_cmds=$lt_old_postuninstall_cmds
+
+# Whether to use a lock for old archive extraction.
+lock_old_archive_extraction=$lock_old_archive_extraction
+
+# A C compiler.
+LTCC=$lt_CC
+
+# LTCC compiler flags.
+LTCFLAGS=$lt_CFLAGS
+
+# Take the output of nm and produce a listing of raw symbols and C names.
+global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe
+
+# Transform the output of nm in a proper C declaration.
+global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl
+
+# Transform the output of nm in a C name address pair.
+global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address
+
+# Transform the output of nm in a C name address pair when lib prefix is needed.
+global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix
+
+# Specify filename containing input files for \$NM.
+nm_file_list_spec=$lt_nm_file_list_spec
+
+# The root where to search for dependent libraries,and in which our libraries should be installed.
+lt_sysroot=$lt_sysroot
+
+# The name of the directory that contains temporary libtool files.
+objdir=$objdir
+
+# Used to examine libraries when file_magic_cmd begins with "file".
+MAGIC_CMD=$MAGIC_CMD
+
+# Must we lock files when doing compilation?
+need_locks=$lt_need_locks
+
+# Manifest tool.
+MANIFEST_TOOL=$lt_MANIFEST_TOOL
+
+# Tool to manipulate archived DWARF debug symbol files on Mac OS X.
+DSYMUTIL=$lt_DSYMUTIL
+
+# Tool to change global to local symbols on Mac OS X.
+NMEDIT=$lt_NMEDIT
+
+# Tool to manipulate fat objects and archives on Mac OS X.
+LIPO=$lt_LIPO
+
+# ldd/readelf like tool for Mach-O binaries on Mac OS X.
+OTOOL=$lt_OTOOL
+
+# ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4.
+OTOOL64=$lt_OTOOL64
+
+# Old archive suffix (normally "a").
+libext=$libext
+
+# Shared library suffix (normally ".so").
+shrext_cmds=$lt_shrext_cmds
+
+# The commands to extract the exported symbol list from a shared archive.
+extract_expsyms_cmds=$lt_extract_expsyms_cmds
+
+# Variables whose values should be saved in libtool wrapper scripts and
+# restored at link time.
+variables_saved_for_relink=$lt_variables_saved_for_relink
+
+# Do we need the "lib" prefix for modules?
+need_lib_prefix=$need_lib_prefix
+
+# Do we need a version for libraries?
+need_version=$need_version
+
+# Library versioning type.
+version_type=$version_type
+
+# Shared library runtime path variable.
+runpath_var=$runpath_var
+
+# Shared library path variable.
+shlibpath_var=$shlibpath_var
+
+# Is shlibpath searched before the hard-coded library search path?
+shlibpath_overrides_runpath=$shlibpath_overrides_runpath
+
+# Format of library name prefix.
+libname_spec=$lt_libname_spec
+
+# List of archive names.  First name is the real one, the rest are links.
+# The last name is the one that the linker finds with -lNAME
+library_names_spec=$lt_library_names_spec
+
+# The coded name of the library, if different from the real name.
+soname_spec=$lt_soname_spec
+
+# Permission mode override for installation of shared libraries.
+install_override_mode=$lt_install_override_mode
+
+# Command to use after installation of a shared archive.
+postinstall_cmds=$lt_postinstall_cmds
+
+# Command to use after uninstallation of a shared archive.
+postuninstall_cmds=$lt_postuninstall_cmds
+
+# Commands used to finish a libtool library installation in a directory.
+finish_cmds=$lt_finish_cmds
+
+# As "finish_cmds", except a single script fragment to be evaled but
+# not shown.
+finish_eval=$lt_finish_eval
+
+# Whether we should hardcode library paths into libraries.
+hardcode_into_libs=$hardcode_into_libs
+
+# Compile-time system search path for libraries.
+sys_lib_search_path_spec=$lt_sys_lib_search_path_spec
+
+# Run-time system search path for libraries.
+sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec
+
+# Whether dlopen is supported.
+dlopen_support=$enable_dlopen
+
+# Whether dlopen of programs is supported.
+dlopen_self=$enable_dlopen_self
+
+# Whether dlopen of statically linked programs is supported.
+dlopen_self_static=$enable_dlopen_self_static
+
+# Commands to strip libraries.
+old_striplib=$lt_old_striplib
+striplib=$lt_striplib
+
+
+# The linker used to build libraries.
+LD=$lt_LD
+
+# How to create reloadable object files.
+reload_flag=$lt_reload_flag
+reload_cmds=$lt_reload_cmds
+
+# Commands used to build an old-style archive.
+old_archive_cmds=$lt_old_archive_cmds
+
+# A language specific compiler.
+CC=$lt_compiler
+
+# Is the compiler the GNU compiler?
+with_gcc=$GCC
+
+# Compiler flag to turn off builtin functions.
+no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag
+
+# Additional compiler flags for building library objects.
+pic_flag=$lt_lt_prog_compiler_pic
+
+# How to pass a linker flag through the compiler.
+wl=$lt_lt_prog_compiler_wl
+
+# Compiler flag to prevent dynamic linking.
+link_static_flag=$lt_lt_prog_compiler_static
+
+# Does compiler simultaneously support -c and -o options?
+compiler_c_o=$lt_lt_cv_prog_compiler_c_o
+
+# Whether or not to add -lc for building shared libraries.
+build_libtool_need_lc=$archive_cmds_need_lc
+
+# Whether or not to disallow shared libs when runtime libs are static.
+allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes
+
+# Compiler flag to allow reflexive dlopens.
+export_dynamic_flag_spec=$lt_export_dynamic_flag_spec
+
+# Compiler flag to generate shared objects directly from archives.
+whole_archive_flag_spec=$lt_whole_archive_flag_spec
+
+# Whether the compiler copes with passing no objects directly.
+compiler_needs_object=$lt_compiler_needs_object
+
+# Create an old-style archive from a shared archive.
+old_archive_from_new_cmds=$lt_old_archive_from_new_cmds
+
+# Create a temporary old-style archive to link instead of a shared archive.
+old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds
+
+# Commands used to build a shared archive.
+archive_cmds=$lt_archive_cmds
+archive_expsym_cmds=$lt_archive_expsym_cmds
+
+# Commands used to build a loadable module if different from building
+# a shared archive.
+module_cmds=$lt_module_cmds
+module_expsym_cmds=$lt_module_expsym_cmds
+
+# Whether we are building with GNU ld or not.
+with_gnu_ld=$lt_with_gnu_ld
+
+# Flag that allows shared libraries with undefined symbols to be built.
+allow_undefined_flag=$lt_allow_undefined_flag
+
+# Flag that enforces no undefined symbols.
+no_undefined_flag=$lt_no_undefined_flag
+
+# Flag to hardcode \$libdir into a binary during linking.
+# This must work even if \$libdir does not exist
+hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec
+
+# Whether we need a single "-rpath" flag with a separated argument.
+hardcode_libdir_separator=$lt_hardcode_libdir_separator
+
+# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes
+# DIR into the resulting binary.
+hardcode_direct=$hardcode_direct
+
+# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes
+# DIR into the resulting binary and the resulting library dependency is
+# "absolute",i.e impossible to change by setting \${shlibpath_var} if the
+# library is relocated.
+hardcode_direct_absolute=$hardcode_direct_absolute
+
+# Set to "yes" if using the -LDIR flag during linking hardcodes DIR
+# into the resulting binary.
+hardcode_minus_L=$hardcode_minus_L
+
+# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR
+# into the resulting binary.
+hardcode_shlibpath_var=$hardcode_shlibpath_var
+
+# Set to "yes" if building a shared library automatically hardcodes DIR
+# into the library and all subsequent libraries and executables linked
+# against it.
+hardcode_automatic=$hardcode_automatic
+
+# Set to yes if linker adds runtime paths of dependent libraries
+# to runtime path list.
+inherit_rpath=$inherit_rpath
+
+# Whether libtool must link a program against all its dependency libraries.
+link_all_deplibs=$link_all_deplibs
+
+# Set to "yes" if exported symbols are required.
+always_export_symbols=$always_export_symbols
+
+# The commands to list exported symbols.
+export_symbols_cmds=$lt_export_symbols_cmds
+
+# Symbols that should not be listed in the preloaded symbols.
+exclude_expsyms=$lt_exclude_expsyms
+
+# Symbols that must always be exported.
+include_expsyms=$lt_include_expsyms
+
+# Commands necessary for linking programs (against libraries) with templates.
+prelink_cmds=$lt_prelink_cmds
+
+# Commands necessary for finishing linking programs.
+postlink_cmds=$lt_postlink_cmds
+
+# Specify filename containing input files.
+file_list_spec=$lt_file_list_spec
+
+# How to hardcode a shared library path into an executable.
+hardcode_action=$hardcode_action
+
+# The directories searched by this compiler when creating a shared library.
+compiler_lib_search_dirs=$lt_compiler_lib_search_dirs
+
+# Dependencies to place before and after the objects being linked to
+# create a shared library.
+predep_objects=$lt_predep_objects
+postdep_objects=$lt_postdep_objects
+predeps=$lt_predeps
+postdeps=$lt_postdeps
+
+# The library search path used internally by the compiler when linking
+# a shared library.
+compiler_lib_search_path=$lt_compiler_lib_search_path
+
+# ### END LIBTOOL CONFIG
+
+_LT_EOF
+
+  case $host_os in
+  aix3*)
+    cat <<\_LT_EOF >> "$cfgfile"
+# AIX sometimes has problems with the GCC collect2 program.  For some
+# reason, if we set the COLLECT_NAMES environment variable, the problems
+# vanish in a puff of smoke.
+if test "X${COLLECT_NAMES+set}" != Xset; then
+  COLLECT_NAMES=
+  export COLLECT_NAMES
+fi
+_LT_EOF
+    ;;
+  esac
+
+
+ltmain="$ac_aux_dir/ltmain.sh"
+
+
+  # We use sed instead of cat because bash on DJGPP gets confused if
+  # if finds mixed CR/LF and LF-only lines.  Since sed operates in
+  # text mode, it properly converts lines to CR/LF.  This bash problem
+  # is reportedly fixed, but why not run on old versions too?
+  sed '$q' "$ltmain" >> "$cfgfile" \
+     || (rm -f "$cfgfile"; exit 1)
+
+  if test x"$xsi_shell" = xyes; then
+  sed -e '/^func_dirname ()$/,/^} # func_dirname /c\
+func_dirname ()\
+{\
+\    case ${1} in\
+\      */*) func_dirname_result="${1%/*}${2}" ;;\
+\      *  ) func_dirname_result="${3}" ;;\
+\    esac\
+} # Extended-shell func_dirname implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_basename ()$/,/^} # func_basename /c\
+func_basename ()\
+{\
+\    func_basename_result="${1##*/}"\
+} # Extended-shell func_basename implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_dirname_and_basename ()$/,/^} # func_dirname_and_basename /c\
+func_dirname_and_basename ()\
+{\
+\    case ${1} in\
+\      */*) func_dirname_result="${1%/*}${2}" ;;\
+\      *  ) func_dirname_result="${3}" ;;\
+\    esac\
+\    func_basename_result="${1##*/}"\
+} # Extended-shell func_dirname_and_basename implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_stripname ()$/,/^} # func_stripname /c\
+func_stripname ()\
+{\
+\    # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are\
+\    # positional parameters, so assign one to ordinary parameter first.\
+\    func_stripname_result=${3}\
+\    func_stripname_result=${func_stripname_result#"${1}"}\
+\    func_stripname_result=${func_stripname_result%"${2}"}\
+} # Extended-shell func_stripname implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_split_long_opt ()$/,/^} # func_split_long_opt /c\
+func_split_long_opt ()\
+{\
+\    func_split_long_opt_name=${1%%=*}\
+\    func_split_long_opt_arg=${1#*=}\
+} # Extended-shell func_split_long_opt implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_split_short_opt ()$/,/^} # func_split_short_opt /c\
+func_split_short_opt ()\
+{\
+\    func_split_short_opt_arg=${1#??}\
+\    func_split_short_opt_name=${1%"$func_split_short_opt_arg"}\
+} # Extended-shell func_split_short_opt implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_lo2o ()$/,/^} # func_lo2o /c\
+func_lo2o ()\
+{\
+\    case ${1} in\
+\      *.lo) func_lo2o_result=${1%.lo}.${objext} ;;\
+\      *)    func_lo2o_result=${1} ;;\
+\    esac\
+} # Extended-shell func_lo2o implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_xform ()$/,/^} # func_xform /c\
+func_xform ()\
+{\
+    func_xform_result=${1%.*}.lo\
+} # Extended-shell func_xform implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_arith ()$/,/^} # func_arith /c\
+func_arith ()\
+{\
+    func_arith_result=$(( $* ))\
+} # Extended-shell func_arith implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_len ()$/,/^} # func_len /c\
+func_len ()\
+{\
+    func_len_result=${#1}\
+} # Extended-shell func_len implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+fi
+
+if test x"$lt_shell_append" = xyes; then
+  sed -e '/^func_append ()$/,/^} # func_append /c\
+func_append ()\
+{\
+    eval "${1}+=\\${2}"\
+} # Extended-shell func_append implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_append_quoted ()$/,/^} # func_append_quoted /c\
+func_append_quoted ()\
+{\
+\    func_quote_for_eval "${2}"\
+\    eval "${1}+=\\\\ \\$func_quote_for_eval_result"\
+} # Extended-shell func_append_quoted implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  # Save a `func_append' function call where possible by direct use of '+='
+  sed -e 's%func_append \([a-zA-Z_]\{1,\}\) "%\1+="%g' $cfgfile > $cfgfile.tmp \
+    && mv -f "$cfgfile.tmp" "$cfgfile" \
+      || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+  test 0 -eq $? || _lt_function_replace_fail=:
+else
+  # Save a `func_append' function call even when '+=' is not available
+  sed -e 's%func_append \([a-zA-Z_]\{1,\}\) "%\1="$\1%g' $cfgfile > $cfgfile.tmp \
+    && mv -f "$cfgfile.tmp" "$cfgfile" \
+      || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+  test 0 -eq $? || _lt_function_replace_fail=:
+fi
+
+if test x"$_lt_function_replace_fail" = x":"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Unable to substitute extended shell functions in $ofile" >&5
+$as_echo "$as_me: WARNING: Unable to substitute extended shell functions in $ofile" >&2;}
+fi
+
+
+   mv -f "$cfgfile" "$ofile" ||
+    (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile")
+  chmod +x "$ofile"
+
+
+    cat <<_LT_EOF >> "$ofile"
+
+# ### BEGIN LIBTOOL TAG CONFIG: FC
+
+# The linker used to build libraries.
+LD=$lt_LD_FC
+
+# How to create reloadable object files.
+reload_flag=$lt_reload_flag_FC
+reload_cmds=$lt_reload_cmds_FC
+
+# Commands used to build an old-style archive.
+old_archive_cmds=$lt_old_archive_cmds_FC
+
+# A language specific compiler.
+CC=$lt_compiler_FC
+
+# Is the compiler the GNU compiler?
+with_gcc=$GCC_FC
+
+# Compiler flag to turn off builtin functions.
+no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_FC
+
+# Additional compiler flags for building library objects.
+pic_flag=$lt_lt_prog_compiler_pic_FC
+
+# How to pass a linker flag through the compiler.
+wl=$lt_lt_prog_compiler_wl_FC
+
+# Compiler flag to prevent dynamic linking.
+link_static_flag=$lt_lt_prog_compiler_static_FC
+
+# Does compiler simultaneously support -c and -o options?
+compiler_c_o=$lt_lt_cv_prog_compiler_c_o_FC
+
+# Whether or not to add -lc for building shared libraries.
+build_libtool_need_lc=$archive_cmds_need_lc_FC
+
+# Whether or not to disallow shared libs when runtime libs are static.
+allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_FC
+
+# Compiler flag to allow reflexive dlopens.
+export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_FC
+
+# Compiler flag to generate shared objects directly from archives.
+whole_archive_flag_spec=$lt_whole_archive_flag_spec_FC
+
+# Whether the compiler copes with passing no objects directly.
+compiler_needs_object=$lt_compiler_needs_object_FC
+
+# Create an old-style archive from a shared archive.
+old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_FC
+
+# Create a temporary old-style archive to link instead of a shared archive.
+old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_FC
+
+# Commands used to build a shared archive.
+archive_cmds=$lt_archive_cmds_FC
+archive_expsym_cmds=$lt_archive_expsym_cmds_FC
+
+# Commands used to build a loadable module if different from building
+# a shared archive.
+module_cmds=$lt_module_cmds_FC
+module_expsym_cmds=$lt_module_expsym_cmds_FC
+
+# Whether we are building with GNU ld or not.
+with_gnu_ld=$lt_with_gnu_ld_FC
+
+# Flag that allows shared libraries with undefined symbols to be built.
+allow_undefined_flag=$lt_allow_undefined_flag_FC
+
+# Flag that enforces no undefined symbols.
+no_undefined_flag=$lt_no_undefined_flag_FC
+
+# Flag to hardcode \$libdir into a binary during linking.
+# This must work even if \$libdir does not exist
+hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_FC
+
+# Whether we need a single "-rpath" flag with a separated argument.
+hardcode_libdir_separator=$lt_hardcode_libdir_separator_FC
+
+# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes
+# DIR into the resulting binary.
+hardcode_direct=$hardcode_direct_FC
+
+# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes
+# DIR into the resulting binary and the resulting library dependency is
+# "absolute",i.e impossible to change by setting \${shlibpath_var} if the
+# library is relocated.
+hardcode_direct_absolute=$hardcode_direct_absolute_FC
+
+# Set to "yes" if using the -LDIR flag during linking hardcodes DIR
+# into the resulting binary.
+hardcode_minus_L=$hardcode_minus_L_FC
+
+# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR
+# into the resulting binary.
+hardcode_shlibpath_var=$hardcode_shlibpath_var_FC
+
+# Set to "yes" if building a shared library automatically hardcodes DIR
+# into the library and all subsequent libraries and executables linked
+# against it.
+hardcode_automatic=$hardcode_automatic_FC
+
+# Set to yes if linker adds runtime paths of dependent libraries
+# to runtime path list.
+inherit_rpath=$inherit_rpath_FC
+
+# Whether libtool must link a program against all its dependency libraries.
+link_all_deplibs=$link_all_deplibs_FC
+
+# Set to "yes" if exported symbols are required.
+always_export_symbols=$always_export_symbols_FC
+
+# The commands to list exported symbols.
+export_symbols_cmds=$lt_export_symbols_cmds_FC
+
+# Symbols that should not be listed in the preloaded symbols.
+exclude_expsyms=$lt_exclude_expsyms_FC
+
+# Symbols that must always be exported.
+include_expsyms=$lt_include_expsyms_FC
+
+# Commands necessary for linking programs (against libraries) with templates.
+prelink_cmds=$lt_prelink_cmds_FC
+
+# Commands necessary for finishing linking programs.
+postlink_cmds=$lt_postlink_cmds_FC
+
+# Specify filename containing input files.
+file_list_spec=$lt_file_list_spec_FC
+
+# How to hardcode a shared library path into an executable.
+hardcode_action=$hardcode_action_FC
+
+# The directories searched by this compiler when creating a shared library.
+compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_FC
+
+# Dependencies to place before and after the objects being linked to
+# create a shared library.
+predep_objects=$lt_predep_objects_FC
+postdep_objects=$lt_postdep_objects_FC
+predeps=$lt_predeps_FC
+postdeps=$lt_postdeps_FC
+
+# The library search path used internally by the compiler when linking
+# a shared library.
+compiler_lib_search_path=$lt_compiler_lib_search_path_FC
+
+# ### END LIBTOOL TAG CONFIG: FC
+_LT_EOF
+
+ ;;
+    "ast_cpp":F) chmod +x ast_cpp ;;
+
+  esac
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+  as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded.  So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status.  When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+  ac_cs_success=:
+  ac_config_status_args=
+  test "$silent" = yes &&
+    ac_config_status_args="$ac_config_status_args --quiet"
+  exec 5>/dev/null
+  $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+  exec 5>>config.log
+  # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+  # would make configure fail if this is the last instruction.
+  $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
diff --git a/ast/configure.ac b/ast/configure.ac
new file mode 100644
index 0000000..2170fd7
--- /dev/null
+++ b/ast/configure.ac
@@ -0,0 +1,215 @@
+dnl  Process this file with autoconf to produce a configure script
+AC_REVISION($Revision$)
+
+dnl   This configure file is known to work with autoconf-2.57,
+dnl   automake versions 1.6.3 and 1.7.5, and libtool versions 1.4.2 and
+dnl   1.5.  It should work with autoconf versions 2.50 or better, and
+dnl   automake 1.6 or better.
+
+dnl   Initialisation: package name and version number
+AC_INIT([ast],[8.4.0],[[email-masked]])
+AC_CONFIG_AUX_DIR([build-aux])
+
+dnl   Require autoconf-2.50 at least
+AC_PREREQ([2.69])
+dnl   Require Starlink automake
+AM_INIT_AUTOMAKE([1.8.2-starlink subdir-objects])
+
+dnl   Sanity-check: name a file in the source directory
+AC_CONFIG_SRCDIR([ast_link.in])
+
+#   Include defaults for Starlink configurations
+STAR_DEFAULTS
+
+#   See if the --with-starmem option has been provided. This sets the
+#   preprocesor macro HAVE_STAR_MEM_H.
+AC_ARG_WITH(
+      [starmem],
+      AS_HELP_STRING([--with-starmem],[use starmem library for memory management]),
+      AC_DEFINE([HAVE_STAR_MEM_H],[1],[Use the starmem library for memory management]),
+)
+
+#   See if the --with-memdebug option has been provided. This sets the
+#   preprocesor macro MEM_DEBUG which enables facilities used to track
+#   down memory leaks, etc.
+AC_ARG_WITH(
+      [memdebug],
+      AS_HELP_STRING([--with-memdebug],[enable AST memory leak debugging functions]),
+      AC_DEFINE([MEM_DEBUG],[1],[enable AST memory leak debugging functions in memory.c]),
+)
+
+#   See if the --with-external_pal option has been provided. This sets the
+#   preprocesor macro EXTERNAL_PAL which prevents use of the PAL & ERFA
+#   library functions that are included in the AST distribution. Suitable
+#   link options are used within ast_link(_adam) scripts to pull in libpal.
+AC_ARG_WITH([external_pal],
+            [ --with-external_pal   Use external PAL and ERFA libraries],
+        if test "$withval" = "yes"; then
+           external_pal="1"
+        else
+           external_pal="0"
+        fi,
+        external_pal="0")
+AC_SUBST( EXTERNAL_PAL, $external_pal )
+if test "$external_pal" = "1"; then
+   AC_SUBST( LIBPAL, "-lpal" )
+   AC_DEFINE([EXTERNAL_PAL],[1],[use external PAL and ERFA libraries]),
+else
+   AC_SUBST( LIBPAL, "" )
+fi
+AM_CONDITIONAL(EXTERNAL_PAL, test x$external_pal = x1)
+
+
+#   Checks for programs
+AC_PROG_CC
+AC_PROG_CPP
+LT_INIT
+AC_PROG_LN_S
+
+#   If --with-pic=no is set we should honour that.
+AM_CONDITIONAL(NOPIC, test x$pic_mode = xno)
+
+#  Conditional defining whether we build with POSIX thread support.
+AC_ARG_WITH([pthreads],
+            [ --without-pthreads   Build package without POSIX threads support],
+        if test "$withval" = "yes"; then
+           use_pthreads="1"
+        else
+           use_pthreads="0"
+        fi,
+        use_pthreads="1")
+if test "$use_pthreads" = "1"; then
+AC_CHECK_LIB([pthread], [pthread_create], ,[use_pthreads="0"])
+fi
+AM_CONDITIONAL(NOTHREADS, test x$use_pthreads = x0)
+AC_SUBST(THREADS, $use_pthreads)
+
+# See which variadic function API to use
+AC_CHECK_HEADERS(stdarg.h varargs.h, break)
+
+# Can we use backtrace?
+AC_CHECK_HEADERS([execinfo.h])
+AC_CHECK_FUNCS([backtrace])
+
+# Do we have reentrant strerror and strtok?
+AC_CHECK_FUNCS([strerror_r strtok_r])
+
+# Do we have vsnprintf?
+AC_CHECK_FUNCS([vsnprintf])
+
+#  See if we have long doubles (used by the Mapping and Region classes)
+AC_CHECK_TYPES([long double])
+
+#  See if we have 64 bit integers.
+AC_CHECK_TYPES([int64_t, uint64_t])
+
+# Calculate alternative 64 bit integer sizes
+AC_CHECK_SIZEOF(long)
+AC_CHECK_SIZEOF(long long)
+
+#  Conditional defining whether we want to support Fortran libraries
+#  (e.g. pgplot) and applications.
+AC_ARG_WITH([fortran],
+            [ --without-fortran   Build package without Fortran support],
+        if test "$withval" = "yes"; then
+           use_fortran="1"
+        else
+           use_fortran="0"
+        fi,
+        use_fortran="1")
+AM_CONDITIONAL(NOFORTRAN, test x$use_fortran = x0)
+AC_SUBST(FORTRAN, $use_fortran)
+
+#   ast_link needs to be able to link against the Fortran runtime if
+#   necessary
+if test "$use_fortran" = "1"; then
+AC_PROG_FC
+AC_FC_LIBRARY_LDFLAGS
+fi
+
+#   Find an absolute path to the Perl binary, augmenting the path with the
+#   location of the Starlink Perl build.  If this fails, then set @PERL@
+#   to the backup `/usr/bin/env perl', which will find Perl if it exists
+#   in the path at runtime.
+AC_PATH_PROG(PERL, perl, [/usr/bin/env perl], [$STARLINK/Perl/bin:$PATH])
+
+#   Function and declaration checks
+AC_CHECK_FUNCS([isnan])
+AC_CHECK_DECLS([isnan],,,[#include <math.h>
+                         ])
+AC_CHECK_FUNCS([isfinite])
+AC_CHECK_DECLS([isfinite],,,[#include <math.h>
+                         ])
+STAR_DECLARE_DEPENDENCIES(sourceset, [sst htx])
+
+#  Perform the check that configures f77.h.in for the return type of REAL
+#  Fortran functions. On 64-bit g77 with f2c compatibility this is double
+#  not float.
+if test "$use_fortran" = "1"; then
+STAR_CNF_F2C_COMPATIBLE
+
+# Determine the type of Fortran character string lengths.
+STAR_CNF_TRAIL_TYPE
+fi
+
+#   Declare the message file.
+STAR_MESSGEN(ast_err.msg)
+
+#   Test for non-ANSI behaviour in sscanf on some platforms, reported by
+#   Bill Joye. Also check for bad MINGW sscanf. That returns nc=0 in the
+#   System test.
+AC_MSG_CHECKING([whether the sscanf function is ANSI-compatible])
+AC_RUN_IFELSE([AC_LANG_SOURCE([[
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+  char foo[] = " name 1111.1 ";
+  char mingw[] = "system= FK4_NO_E";
+
+  char bar[8];
+  float ff;
+  int system;
+  int nc;
+  int r;
+
+  nc = 0;
+  r = sscanf(foo, " %s %f %n", bar, &ff, &nc);
+
+  if ( nc == 13 ) {
+     nc = 0;
+     r = sscanf( mingw, "system= %n%*s %n", &system, &nc );
+     if ( nc != 0 ) nc = 13;
+  }
+  exit( ( nc != 13 ) ? 0 : 1 );
+}
+
+]])],[
+   AC_MSG_RESULT([no]);AC_DEFINE([HAVE_NONANSI_SSCANF],[1],[The sscanf shows the non-ANSI behaviour reported by Bill Joye])
+],[AC_MSG_RESULT([yes])],[
+   AC_DEFINE([HAVE_NONANSI_SSCANF],[1],[The sscanf may show the non-ANSI behaviour reported by Bill Joye])
+])
+
+#   Declare the documentation.  We need to do complicated things to
+#   satisfy these targets, so give a non-null value
+#   for the second argument, to suppress automatic generation of
+#   rules.
+STAR_LATEX_DOCUMENTATION([sun210 sun211], [sun210.pdf sun210.tex sun211.pdf sun211.tex sun210.htx_tar sun211.htx_tar])
+STAR_PREDIST_SOURCES(sun_master.tex)
+STAR_CHECK_PROGS(star2html)
+STAR_CHECK_PROGS(prolat, sst)   # prolat is part of SST
+
+AC_CONFIG_HEADERS(config.h)
+
+AC_CONFIG_FILES(Makefile component.xml ast_link ast_link_adam object.h)
+if test "$use_fortran" = "1"; then
+AC_CONFIG_FILES(f77.h)
+fi
+
+AC_CONFIG_FILES([ast_cpp], [chmod +x ast_cpp])
+#   Following are files which are substituted by the Makefile at
+#   distribution time, rather than by configure.  They are not distributed.
+STAR_PREDIST_SOURCES(version.h.in builddocs.in addversion.in)
+
+AC_OUTPUT
diff --git a/ast/dsbspecframe.c b/ast/dsbspecframe.c
new file mode 100644
index 0000000..20a8ed2
--- /dev/null
+++ b/ast/dsbspecframe.c
@@ -0,0 +1,3266 @@
+/*
+*class++
+*  Name:
+*     DSBSpecFrame
+
+*  Purpose:
+*     Dual sideband spectral coordinate system description.
+
+*  Constructor Function:
+c     astDSBSpecFrame
+f     AST_DSBSPECFRAME
+
+*  Description:
+*     A DSBSpecFrame is a specialised form of SpecFrame which represents
+*     positions in a spectrum obtained using a dual sideband instrument.
+*     Such an instrument produces a spectrum in which each point contains
+*     contributions from two distinctly different frequencies, one from
+*     the "lower side band" (LSB) and one from the "upper side band" (USB).
+*     Corresponding LSB and USB frequencies are connected by the fact
+*     that they are an equal distance on either side of a fixed central
+*     frequency known as the "Local Oscillator" (LO) frequency.
+*
+*     When quoting a position within such a spectrum, it is necessary to
+*     indicate whether the quoted position is the USB position or the
+*     corresponding LSB position. The SideBand attribute provides this
+*     indication. Another option that the SideBand attribute provides is
+*     to represent a spectral position by its topocentric offset from the
+*     LO frequency.
+*
+*     In practice, the LO frequency is specified by giving the distance
+*     from the LO frequency to some "central" spectral position. Typically
+*     this central position is that of some interesting spectral feature.
+*     The distance from this central position to the LO frequency is known
+*     as the "intermediate frequency" (IF). The value supplied for IF can
+*     be a signed value in order to indicate whether the LO frequency is
+*     above or below the central position.
+
+*  Inheritance:
+*     The DSBSpecFrame class inherits from the SpecFrame class.
+
+*  Attributes:
+*     In addition to those attributes common to all SpecFrames, every
+*     DSBSpecFrame also has the following attributes:
+*
+*     - AlignSideBand: Should alignment occur between sidebands?
+*     - DSBCentre: The central position of interest.
+*     - IF: The intermediate frequency used to define the LO frequency.
+*     - ImagFreq: The image sideband equivalent of the rest frequency.
+*     - SideBand: Indicates which sideband the DSBSpecFrame represents.
+
+*  Functions:
+c     The DSBSpecFrame class does not define any new functions beyond those
+f     The DSBSpecFrame class does not define any new routines beyond those
+*     which are applicable to all SpecFrames.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David Berry (Starlink)
+
+*  History:
+*     5-AUG-2004 (DSB):
+*        Original version.
+*     7-OCT-2004 (DSB):
+*        Fixed SetAttrib code which assigns values to SideBand. Previously
+*        all supplied values were ignored, leaving SideBand unchanged.
+*     2-SEP-2005 (DSB):
+*        Allow conversion in any Domain within TopoMap (sometimes
+*        SpecFrames have a new Domain set which is not equal to SPECTRUM").
+*     12-SEP-2005 (DSB):
+*        Set all attributes required to described the RestFreq value
+*        before determining Mapping from RestFreq to ImagFreq in
+*        GetImageFreq.
+*     2-DEC-2005 (DSB):
+*        Change default Domain from SPECTRUM to DSBSPECTRUM
+*     3-APR-2006 (DSB):
+*        Fix memory leak in astLoadDSBSpecFrame.
+*     6-OCT-2006 (DSB):
+*        Guard against annulling null pointers in subFrame.
+*     27-OCT-2006 (DSB):
+*        Added AlignSideBand attribute.
+*     31-OCT-2006 (DSB):
+*        Use AlignSideBand attribute in SubFrame only if we are not
+*        currently restoring a FrameSet's integrity.
+*     31-JAN-2007 (DSB):
+*        Modified so that a DSBSpecFrame can be used as a template to find a
+*        DSBSpecFrame (or SpecFrame) contained within a CmpFrame. This
+*        involves changes in Match.
+*     1-MAY-2007 (DSB):
+*        The default for AlignSideband has been changed from 1 to 0.
+*     8-MAY-2007 (DSB):
+*        Correct initialisation of alignsideband in astInitDSBSpecFrame_.
+*     19-OCT-2007 (DSB):
+*        Ignore SideBand alignment if the AlignSideBand attribute is zero
+*        in either the target or the template.
+*     16-JAN-2007 (DSB):
+*        Modify SubFrame so that DSBSpecFrames are aligned in the
+*        observed sideband (LSB or USB) rather than always being aligned
+*        in the USB.
+*     12-FEB-2010 (DSB):
+*        Report an error if the local oscillator frequency looks silly
+*        (specifically, if it less than the absolute intermediate frequency).
+*     29-APR-2011 (DSB):
+*        Prevent astFindFrame from matching a subclass template against a
+*        superclass target.
+*class--
+
+*  Implementation Deficiencies:
+*     - The default values for System and StdOfRest inherited from the
+*     SpecFrame class are "Wave" and "Heliocentric". These are not
+*     usually what is wanted for a DSB instrument. Defaults such as
+*     "Freq" and "Topo" may be more appropriate. However, changing the
+*     defaults inherited from SpecFrame may cause problems in the
+*     astConvert algorithm. The astConvertX function in frame.c includes
+*     the following implementation deficiency warning: "One likely
+*     problem is with attributes which default in both the source and
+*     destination Frames. This means they also default in the common
+*     coordinate system. If these default values were to differ when
+*     matching different target Frames, however, we would be in trouble,
+*     because the common coordinate system would not then be remaining
+*     constant. The longer-term solution to this is probably to provide
+*     some mechanism to "fix" all attribute values for a Frame, by taking
+*     any attributes that are un-set and explicitly setting a firm value
+*     (equal to the default) so they cannot then change". So the defaults
+*     should probably be left unchanged until this fix is made.
+
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS DSBSpecFrame
+
+#define BADSB -9999
+#define FIRST_SB -1
+#define LSB -1
+#define LO  0
+#define USB 1
+#define LAST_SB 1
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory management facilities */
+#include "object.h"              /* Base Object class */
+#include "channel.h"             /* I/O channels */
+#include "specframe.h"           /* Spectral frames (parent class) */
+#include "unit.h"                /* Unit handling */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "winmap.h"              /* Window Mappings */
+#include "dsbspecframe.h"        /* Interface definition for this class */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static const char *(* parent_getlabel)( AstFrame *, int, int * );
+static int (* parent_match)( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int (* parent_subframe)( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static void (* parent_overlay)( AstFrame *, const int *, AstFrame *, int * );
+static const char *(* parent_getdomain)( AstFrame *, int * );
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->GetLabel_Buff[ 0 ] = 0; \
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(DSBSpecFrame)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(DSBSpecFrame,Class_Init)
+#define class_vtab astGLOBAL(DSBSpecFrame,Class_Vtab)
+#define getattrib_buff astGLOBAL(DSBSpecFrame,GetAttrib_Buff)
+#define getlabel_buff astGLOBAL(DSBSpecFrame,GetLabel_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Define the thread-specific globals for this class. */
+
+/* Buffer returned by GetAttrib. */
+static char getattrib_buff[ 101 ];
+
+/* Default Label string buffer */
+static char getlabel_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstDSBSpecFrameVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstDSBSpecFrame *astDSBSpecFrameId_( const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+
+static AstMapping *TopoMap( AstDSBSpecFrame *, int, const char *, int * );
+static AstMapping *ToLOMapping( AstDSBSpecFrame *, const char *, int * )__attribute__((unused));
+static AstMapping *ToLSBMapping( AstDSBSpecFrame *, const char *, int * );
+static AstMapping *ToUSBMapping( AstDSBSpecFrame *, const char *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static const char *GetLabel( AstFrame *, int, int * );
+static double GetImagFreq( AstDSBSpecFrame *, int * );
+static double GetLO( AstDSBSpecFrame *, const char *, const char *, int * );
+static int Match( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int SubFrame( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void Overlay( AstFrame *, const int *, AstFrame *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void VerifyAttrs( AstDSBSpecFrame *, const char *, const char *, const char *, int * );
+static const char *GetDomain( AstFrame *, int * );
+
+static double GetIF( AstDSBSpecFrame *, int * );
+static int TestIF( AstDSBSpecFrame *, int * );
+static void ClearIF( AstDSBSpecFrame *, int * );
+static void SetIF( AstDSBSpecFrame *, double, int * );
+
+static double GetDSBCentre( AstDSBSpecFrame *, int * );
+static int TestDSBCentre( AstDSBSpecFrame *, int * );
+static void ClearDSBCentre( AstDSBSpecFrame *, int * );
+static void SetDSBCentre( AstDSBSpecFrame *, double, int * );
+
+static int GetSideBand( AstDSBSpecFrame *, int * );
+static int TestSideBand( AstDSBSpecFrame *, int * );
+static void ClearSideBand( AstDSBSpecFrame *, int * );
+static void SetSideBand( AstDSBSpecFrame *, int, int * );
+
+static int GetAlignSideBand( AstDSBSpecFrame *, int * );
+static int TestAlignSideBand( AstDSBSpecFrame *, int * );
+static void ClearAlignSideBand( AstDSBSpecFrame *, int * );
+static void SetAlignSideBand( AstDSBSpecFrame *, int, int * );
+
+
+/* Member functions. */
+/* ================= */
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a DSBSpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function (over-rides the astClearAttrib protected
+*     method inherited from the SpecFrame class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     DSBSpecFrame, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstDSBSpecFrame *this;        /* Pointer to the DSBSpecFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the DSBSpecFrame structure. */
+   this = (AstDSBSpecFrame *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* DSBCentre. */
+/* ---------- */
+   if ( !strcmp( attrib, "dsbcentre" ) ) {
+      astClearDSBCentre( this );
+
+/* IF */
+/* -- */
+   } else if ( !strcmp( attrib, "if" ) ) {
+      astClearIF( this );
+
+/* SideBand */
+/* -------- */
+   } else if ( !strcmp( attrib, "sideband" ) ) {
+      astClearSideBand( this );
+
+/* AlignSideBand */
+/* ------------- */
+   } else if ( !strcmp( attrib, "alignsideband" ) ) {
+      astClearAlignSideBand( this );
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Test if the attribute name matches any of the read-only attributes
+   of this class. If it does, then report an error. */
+   } else if ( !strcmp( attrib, "imagfreq" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a DSBSpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function (over-rides the protected astGetAttrib
+*     method inherited from the SpecFrame class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a DSBSpecFrame, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the DSBSpecFrame, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the DSBSpecFrame. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstDSBSpecFrame *this;        /* Pointer to the DSBSpecFrame structure */
+   AstMapping *tmap;             /* Ptr to Mapping from topofreq to this */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Attribute value */
+   double dtemp;                 /* Attribute value */
+   int ival;                     /* Attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstDSBSpecFrame *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* DSBCentre */
+/* --------- */
+   if ( !strcmp( attrib, "dsbcentre" ) ) {
+
+/* Get the value as topocentric frequency in Hz. */
+      dval = astGetDSBCentre( this );
+
+/* Find the Mapping from topocentric frequency in Hz to the spectral system
+   described by this SpecFrame. */
+      tmap = TopoMap( this, 0, "astGetAttrib", status );
+      if ( astOK ) {
+
+/* Transform the internal value from topocentric frequency into the required
+   system. */
+         astTran1( tmap, 1, &dval, 1, &dtemp );
+         if( dtemp == AST__BAD ) {
+            astError( AST__INTER, "astGetAttrib(%s): Cannot convert DSBCentre "
+                      "value from topocentric frequency to the required "
+                      "system.", status, astGetClass( this ) );
+         } else {
+
+/* Format it. */
+            (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dtemp );
+            result = getattrib_buff;
+         }
+         tmap = astAnnul( tmap );
+      }
+
+/* IF */
+/* -- */
+   } else if ( !strcmp( attrib, "if" ) ) {
+      dval = astGetIF( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval*1.0E-9 );
+         result = getattrib_buff;
+      }
+
+/* ImagFreq */
+/* -------- */
+   } else if ( !strcmp( attrib, "imagfreq" ) ) {
+      dval = astGetImagFreq( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval*1.0E-9 );
+         result = getattrib_buff;
+      }
+
+/* SideBand */
+/* -------- */
+   } else if ( !strcmp( attrib, "sideband" ) ) {
+      ival = astGetSideBand( this );
+      if ( astOK ) {
+         result = ( ival == USB ) ? "USB" : (( ival == LO ) ? "LO" : "LSB" );
+      }
+
+/* AlignSideBand */
+/* ------------- */
+   } else if ( !strcmp( attrib, "alignsideband" ) ) {
+      ival = astGetAlignSideBand( this ) ? 1 : 0;
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static const char *GetDomain( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetDomain
+
+*  Purpose:
+*     Obtain a pointer to the Domain attribute string for a DSBSpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     const char *GetDomain( AstFrame *this, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function (over-rides the astGetDomain protected
+*     method inherited from the SpecFrame class).
+
+*  Description:
+*    This function returns a pointer to the Domain attribute string
+*    for a DSBSpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a constant null-terminated string containing the
+*     Domain value.
+
+*  Notes:
+*     - The returned pointer or the string it refers to may become
+*     invalid following further invocation of this function or
+*     modification of the DSBSpecFrame.
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstDSBSpecFrame *this;        /* Pointer to DSBSpecFrame structure */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the DSBSpecFrame structure. */
+   this = (AstDSBSpecFrame *) this_frame;
+
+/* If a Domain attribute string has been set, invoke the parent method
+   to obtain a pointer to it. */
+   if ( astTestDomain( this ) ) {
+      result = (*parent_getdomain)( this_frame, status );
+
+/* Otherwise, provide a pointer to a suitable default string. */
+   } else {
+      result = "DSBSPECTRUM";
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetImagFreq( AstDSBSpecFrame *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetImagFreq
+
+*  Purpose:
+*     Get the value of the ImagFreq attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     double GetImagFreq( AstDSBSpecFrame *this )
+
+*  Class Membership:
+*     DSBSpecFrame method.
+
+*  Description:
+*     This function returns the image sideband frequency corresponding to
+*     the rest frequency.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+
+*  Returned Value:
+*     The required frequency, in Hz.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstDSBSpecFrame *rf_frame;/* DSBSpecFrame describing the rest frequency */
+   AstMapping *map;          /* Pointer to "Observed to Image" mapping */
+   double result;            /* The returned frequency */
+   double rf;                /* Rest frequency in observed sideband */
+   int sb;                   /* SideBand value */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* The RestFreq attribute is an observed sideband frequency in the
+   source's standard of rest, measured in Hz. Temporaily set attributes
+   to these values. Create a copy of the supplied DSBSpecFrame and set
+   its attributes to these values.  */
+   rf_frame = astCopy( this );
+   astSetStdOfRest( rf_frame, AST__SCSOR );
+   astSetSystem( rf_frame, AST__FREQ );
+   astSetUnit( rf_frame, 0, "Hz" );
+   astSetC( rf_frame, "SideBand", "observed" );
+
+/* Create a Mapping which transforms positions from the observed to the
+   image sideband. */
+   sb = astGetSideBand( rf_frame );
+   if( sb == USB ) {
+      map = ToLSBMapping( rf_frame, "astGetImagFreq", status );
+
+   } else if( sb == LSB ) {
+      map = ToUSBMapping( rf_frame, "astGetImagFreq", status );
+
+   } else {
+      map = NULL;
+      astError( AST__INTER, "astGetImagFreq(%s): Illegal sideband value "
+                "(%d) encountered (internal AST programming error).", status,
+                astGetClass( this ), sb );
+   }
+
+/* Get the rest frequency in Hz, and transform it using the above Mapping. */
+   rf = astGetRestFreq( rf_frame );
+   astTran1( map, 1, &rf, 1, &result );
+
+/* Free resources */
+   map = astAnnul( map );
+   rf_frame = astAnnul( rf_frame );
+
+/* If an error has occurrred, return AST__BAD. */
+   if( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+
+}
+
+static const char *GetLabel( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetLabel
+
+*  Purpose:
+*     Access the Label string for a DSBSpecFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     const char *GetLabel( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function (over-rides the astGetLabel method inherited
+*     from the SpecFrame class).
+
+*  Description:
+*     This function returns a pointer to the Label string for a specified axis
+*     of a DSBSpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated character string containing the
+*     requested information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   const char *result;           /* Pointer to label string */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise. */
+   result = NULL;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astGetLabel" );
+
+/* Invoke the parent astGetLabel method to obtain a pointer to it. */
+   result = (*parent_getlabel)( this, axis, status );
+
+/* Check if this is a default value. If so, append a string indicating
+   the sideband. */
+   if ( !astTestLabel( this, axis ) ) {
+
+/* If OK, supply a pointer to a suitable default label string. */
+      sprintf( getlabel_buff, "%s (%s)", result, astGetAttrib( this, "sideband" ) );
+      result = getlabel_buff;
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static double GetLO( AstDSBSpecFrame *this, const char *check_msg,
+                     const char *method, int *status ) {
+/*
+*  Name:
+*     GetLO
+
+*  Purpose:
+*     Get the Local Oscillator frequency.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     double GetLO( AstDSBSpecFrame *this, const char *check_msg,
+*                   const char *method, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame method.
+
+*  Description:
+*     This function returns the local oscillator frequency in topocentric
+*     frequency.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     check_msg
+*        If not NULL, an error will be reported if either the DSBCentre
+*        or IF attribute has not been set to an explicit value. In this
+*        case, the error message will include the supplied text.
+*     method
+*        The name of the calling method - used in error messages.
+*     status
+*        Pointer to the inherited status value.
+
+*  Returned Value:
+*     The local oscillator frequency, in Hz.
+
+*  Notes:
+*     - An error is reported if the local oscillator frequency looks
+*     un-physical (specifically, if it is less than the absolute value of
+*     the intermediate frequency).
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   double f_if;              /* Intermediate frequency (topo,HZ) */
+   double result;            /* The returned frequency */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* If required, check that explicit values have been assigned to the required
+   attributes (i.e. report an error if a default value would be used for
+   either attribute). */
+   if( check_msg ) VerifyAttrs( this, check_msg, "IF DSBCentre", method,
+                                status );
+
+/* The local oscillator is the sum of the intermediate frequency and the
+   observation centre frequency. */
+   f_if = astGetIF( this );
+   result = astGetDSBCentre( this ) + f_if;
+
+/* Check the local  oscillator frequency is no smaller than the absolute
+   intermediate frequency. */
+   if( result < fabs( f_if ) && astOK ) {
+      astError( AST__ATTIN, "%s(%s): The local oscillator frequency (%g Hz) "
+                "is too low (less than the intermediate frequency: %g Hz).",
+                status, method, astGetClass( this ), result, fabs( f_if ) );
+      astError( AST__ATTIN, "   This could be caused by a bad value for"
+                " either the IF attribute (currently %g Hz) or the DSBCentre "
+                "attribute (currently %g Hz).", status, f_if,
+                astGetDSBCentre( this ) );
+   }
+
+/* If an error has occurrred, return AST__BAD. */
+   if( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+void astInitDSBSpecFrameVtab_(  AstDSBSpecFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitDSBSpecFrameVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a DSBSpecFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     void astInitDSBSpecFrameVtab( AstDSBSpecFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     DSBSpecFrame vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the DSBSpecFrame class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstFrameVtab *frame;          /* Pointer to Frame component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitSpecFrameVtab( (AstSpecFrameVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsADSBSpecFrame) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstSpecFrameVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->ClearDSBCentre = ClearDSBCentre;
+   vtab->TestDSBCentre = TestDSBCentre;
+   vtab->GetDSBCentre = GetDSBCentre;
+   vtab->SetDSBCentre = SetDSBCentre;
+
+   vtab->ClearIF = ClearIF;
+   vtab->TestIF = TestIF;
+   vtab->GetIF = GetIF;
+   vtab->SetIF = SetIF;
+
+   vtab->ClearSideBand = ClearSideBand;
+   vtab->TestSideBand = TestSideBand;
+   vtab->GetSideBand = GetSideBand;
+   vtab->SetSideBand = SetSideBand;
+
+   vtab->ClearAlignSideBand = ClearAlignSideBand;
+   vtab->TestAlignSideBand = TestAlignSideBand;
+   vtab->GetAlignSideBand = GetAlignSideBand;
+   vtab->SetAlignSideBand = SetAlignSideBand;
+
+   vtab->GetImagFreq = GetImagFreq;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   frame = (AstFrameVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_getdomain = frame->GetDomain;
+   frame->GetDomain = GetDomain;
+
+   parent_overlay = frame->Overlay;
+   frame->Overlay = Overlay;
+
+   parent_match = frame->Match;
+   frame->Match = Match;
+
+   parent_subframe = frame->SubFrame;
+   frame->SubFrame = SubFrame;
+
+   parent_getlabel = frame->GetLabel;
+   frame->GetLabel = GetLabel;
+
+/* Declare the class delete function.*/
+   astSetDump( vtab, Dump, "DSBSpecFrame", "Dual sideband spectral axis" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int Match( AstFrame *template_frame, AstFrame *target, int matchsub,
+                  int **template_axes, int **target_axes, AstMapping **map,
+                  AstFrame **result, int *status ) {
+/*
+*  Name:
+*     Match
+
+*  Purpose:
+*     Determine if conversion is possible between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     int Match( AstFrame *template, AstFrame *target, int matchsub,
+*                int **template_axes, int **target_axes,
+*                AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function (over-rides the protected astMatch method
+*     inherited from the SpecFrame class).
+
+*  Description:
+*     This function matches a "template" DSBSpecFrame to a "target" Frame and
+*     determines whether it is possible to convert coordinates between them.
+*     If it is, a mapping that performs the transformation is returned along
+*     with a new Frame that describes the coordinate system that results when
+*     this mapping is applied to the "target" coordinate system. In addition,
+*     information is returned to allow the axes in this "result" Frame to be
+*     associated with the corresponding axes in the "target" and "template"
+*     Frames from which they are derived.
+
+*  Parameters:
+*     template
+*        Pointer to the template DSBSpecFrame. This describes the coordinate
+*        system (or set of possible coordinate systems) into which we wish to
+*        convert our coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate system in
+*        which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case.
+*     template_axes
+*        Address of a location where a pointer to int will be returned if the
+*        requested coordinate conversion is possible. This pointer will point
+*        at a dynamically allocated array of integers with one element for each
+*        axis of the "result" Frame (see below). It must be freed by the caller
+*        (using astFree) when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element of this
+*        array will return the index of the template DSBSpecFrame axis from
+*        which it is derived. If it is not derived from any template
+*        DSBSpecFrame axis, a value of -1 will be returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned if the
+*        requested coordinate conversion is possible. This pointer will point
+*        at a dynamically allocated array of integers with one element for each
+*        axis of the "result" Frame (see below). It must be freed by the caller
+*        (using astFree) when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element of this
+*        array will return the index of the target Frame axis from which it
+*        is derived. If it is not derived from any target Frame axis, a value
+*        of -1 will be returned instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will be
+*        returned if the requested coordinate conversion is possible. If
+*        returned, the forward transformation of this Mapping may be used to
+*        convert coordinates between the "target" Frame and the "result"
+*        Frame (see below) and the inverse transformation will convert in the
+*        opposite direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be returned
+*        if the requested coordinate conversion is possible. If returned, this
+*        Frame describes the coordinate system that results from applying the
+*        returned Mapping (above) to the "target" coordinate system. In
+*        general, this Frame will combine attributes from (and will therefore
+*        be more specific than) both the target and the template Frames. In
+*        particular, when the template allows the possibility of transformaing
+*        to any one of a set of alternative coordinate systems, the "result"
+*        Frame will indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate conversion is
+*     possible. Otherwise zero is returned (this will not in itself result in
+*     an error condition).
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+
+*  Implementation Notes:
+*     This implementation addresses the matching of a DSBSpecFrame class
+*     object to any other class of Frame. A DSBSpecFrame will match any class
+*     of DSBSpecFrame (i.e. possibly from a derived class) but will not match
+*     a less specialised class of Frame (except for a SpecFrame).
+*/
+
+/* Local Variables: */
+   AstDSBSpecFrame *template;    /* Pointer to template DSBSpecFrame structure */
+   AstFrame *frame0;             /* Pointer to Frame underlying axis 0 */
+   int iaxis0;                   /* Axis index underlying axis 0 */
+   int match;                    /* Coordinate conversion possible? */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the template DSBSpecFrame structure. */
+   template = (AstDSBSpecFrame *) template_frame;
+
+/* The first criterion for a match is that the template matches as a
+   SpecFrame class object. This ensures that the number of axes (1) and
+   domain, class, etc. of the target Frame are suitable. Invoke the parent
+   "astMatch" method to verify this. */
+   match = (*parent_match)( template_frame, target, matchsub,
+                            template_axes, target_axes, map, result, status );
+
+/* If a match was found, the target Frame must be (or contain) a SpecFrame,
+   but this target SpecFrame may be a simple SpecFrame rather than a
+   DSBSpecFrame. We use the returned objects directly if the target
+   SpecFrame is not a DSBSpecFrame. So if a DSBSpecFrame and a base
+   SpecFrame are aligned, this will result in the DSBSpecFrame behaving as
+   a normal SpecFrame. */
+   if ( astOK && match ) {
+
+/* Get the primary Frame associated with the matching target axis. */
+      astPrimaryFrame( target, (*target_axes)[ 0 ], &frame0, &iaxis0 );
+
+/* Skip this next section, thus retaining the values returned by the
+   parent Match method above, if the target axis is not a DSBSpecFrame. */
+      if( astIsADSBSpecFrame( frame0 ) ) {
+
+/* Annul the returned objects, which are not needed, but keep the axis
+   association arrays which already hold the correct values. */
+         *map = astAnnul( *map );
+         *result = astAnnul( *result );
+
+/* Use the target's "astSubFrame" method to create a new Frame (the
+   result Frame) with a copy of of the target axis. This process also
+   overlays the template attributes on to the target Frame and returns a
+   Mapping between the target and result Frames which effects the required
+   coordinate conversion. */
+         match = astSubFrame( target, template, 1, *target_axes, *template_axes,
+                              map, result );
+      }
+
+/* Free resources. */
+      frame0 = astAnnul( frame0 );
+
+   }
+
+/* If an error occurred, or conversion to the result Frame's coordinate
+   system was not possible, then free all memory, annul the returned
+   objects, and reset the returned value. */
+   if ( !astOK || !match ) {
+      if( *template_axes ) *template_axes = astFree( *template_axes );
+      if( *target_axes ) *target_axes = astFree( *target_axes );
+      if( *map ) *map = astAnnul( *map );
+      if( *result ) *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static void Overlay( AstFrame *template, const int *template_axes,
+                     AstFrame *result, int *status ) {
+/*
+*  Name:
+*     Overlay
+
+*  Purpose:
+*     Overlay the attributes of a template DSBSpecFrame on to another Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void Overlay( AstFrame *template, const int *template_axes,
+*                   AstFrame *result, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function (over-rides the protected astOverlay method
+*     inherited from the SpecFrame class).
+
+*  Description:
+*     This function overlays attributes of a DSBSpecFrame (the "template") on to
+*     another Frame, so as to over-ride selected attributes of that second
+*     Frame. Normally only those attributes which have been specifically set
+*     in the template will be transferred. This implements a form of
+*     defaulting, in which a Frame acquires attributes from the template, but
+*     retains its original attributes (as the default) if new values have not
+*     previously been explicitly set in the template.
+*
+*     Note that if the result Frame is a DSBSpecFrame and a change of spectral
+*     coordinate system occurs as a result of overlaying its System
+*     attribute, then some of its original attribute values may no
+*     longer be appropriate (e.g. the Title, or attributes describing
+*     its axes). In this case, these will be cleared before overlaying
+*     any new values.
+
+*  Parameters:
+*     template
+*        Pointer to the template DSBSpecFrame, for which values should have been
+*        explicitly set for any attribute which is to be transferred.
+*     template_axes
+*        Pointer to an array of int, with one element for each axis of the
+*        "result" Frame (see below). For each axis in the result frame, the
+*        corresponding element of this array should contain the (zero-based)
+*        index of the template axis to which it corresponds. This array is used
+*        to establish from which template axis any axis-dependent attributes
+*        should be obtained.
+*
+*        If any axis in the result Frame is not associated with a template
+*        axis, the corresponding element of this array should be set to -1.
+*
+*        If a NULL pointer is supplied, the template and result axis
+*        indices are assumed to be identical.
+*     result
+*        Pointer to the Frame which is to receive the new attribute values.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  In general, if the result Frame is not from the same class as the
+*     template DSBSpecFrame, or from a class derived from it, then attributes may
+*     exist in the template DSBSpecFrame which do not exist in the result Frame.
+*     In this case, these attributes will not be transferred.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent class astOverlay method to transfer attributes inherited
+   from the parent class. */
+   (*parent_overlay)( template, template_axes, result, status );
+
+/* Check if the result Frame is a DSBSpecFrame or from a class derived from
+   DSBSpecFrame. If not, we cannot transfer DSBSpecFrame attributes to it as it is
+   insufficiently specialised. In this case simply omit these attributes. */
+   if( astIsADSBSpecFrame( result ) && astOK ) {
+
+/* Define macros that test whether an attribute is set in the template and,
+   if so, transfers its value to the result. */
+#define OVERLAY(attribute) \
+   if ( astTest##attribute( template ) ) { \
+      astSet##attribute( result, astGet##attribute( template ) ); \
+   }
+
+/* Use the macro to transfer each DSBSpecFrame attribute in turn. */
+      OVERLAY(DSBCentre)
+      OVERLAY(IF)
+      OVERLAY(SideBand)
+      OVERLAY(AlignSideBand)
+   }
+
+/* Undefine macros local to this function. */
+#undef OVERLAY
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a DSBSpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     DSBSpecFrame member function (over-rides the astSetAttrib protected
+*     method inherited from the SpecFrame class).
+
+*  Description:
+*     This function assigns an attribute value for a DSBSpecFrame, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstDSBSpecFrame *this;        /* Pointer to the DSBSpecFrame structure */
+   AstMapping *tmap;             /* Ptr to Mapping from this to topofreq */
+   AstMapping *umap;             /* Ptr to Mapping between units */
+   double dtemp;                 /* Attribute value */
+   double dval;                  /* Attribute value */
+   int ival;                     /* Attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Used length */
+   int off;                      /* Offset to start of string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the DSBSpecFrame structure. */
+   this = (AstDSBSpecFrame *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* DSBCentre */
+/* --------- */
+   if ( strstr( setting, "dsbcentre=" ) ) {
+
+/* Without any units indication - assume it is supplied in the system of
+   the DSBSpecFrame. */
+      int ok = 0;
+      if( nc = 0,
+           ( 1 == astSscanf( setting, "dsbcentre= %lg %n", &dval, &nc ) )
+           && ( nc >= len ) ) {
+         ok = 1;
+
+/* With units indication. Is there a Mapping from the supplied units to the
+   units used by the DSBSpecFrame? If so, use the Mapping to convert the
+   supplied value to the required units. */
+      } else if ( nc = 0,
+           ( 1 == astSscanf( setting, "dsbcentre= %lg %n%*s %n", &dval, &off, &nc ) )
+           && ( nc >= len ) ) {
+
+         if( ( umap = astUnitMapper( setting + off, astGetUnit( this, 0 ), NULL, NULL ) ) ) {
+            astTran1( umap, 1, &dval, 1, &dtemp );
+            dval = dtemp;
+            umap = astAnnul( umap );
+            if( astOK && dval != AST__BAD ) ok = 1;
+
+/* Otherwise report an error. */
+         } else if( astOK ) {
+            astError( AST__ATTIN, "astSetAttrib(%s): Value supplied for "
+                      "attribute \"DSBCentre\" (%s) uses units which are "
+                      "inappropriate for the current spectral system (%s).", status,
+                       astGetClass( this ), setting + 10,
+                       astGetTitle( this ) );
+         }
+      }
+
+/* Convert the value from the supplied system to topocentric frequency in
+   Hx, and store. */
+      if( ok ) {
+
+/* Find the Mapping from the spectral system described by this SpecFrame to
+   topocentric frequency in Hz. */
+         tmap = TopoMap( this, 1, "astSetAttrib", status );
+         if ( astOK ) {
+
+/* Transform the supplied value to topocentric frequency. */
+            astTran1( tmap, 1, &dval, 1, &dtemp );
+            if( dtemp == AST__BAD ) {
+               astError( AST__ATTIN, "astSetAttrib(%s): The setting \"%s\" is "
+                         "invalid for a %s.", status, astGetClass( this ), setting,
+                         astGetClass( this ) );
+            } else {
+
+/* Store it. */
+               astSetDSBCentre( this, dtemp );
+
+            }
+            tmap = astAnnul( tmap );
+         }
+
+      } else if( astOK ) {
+         astError( AST__ATTIN, "astSetAttrib(%s): The setting \"%s\" is "
+                   "invalid for a %s.", status, astGetClass( this ), setting,
+                   astGetClass( this ) );
+      }
+
+/* IF */
+/* -- */
+/* Without any units indication - assume GHz. Convert to Hz for storage. */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "if= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetIF( this, dval*1.0E9 );
+
+/* With units indication. */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "if= %lg %n%*s %n", &dval, &off, &nc ) )
+        && ( nc >= len ) ) {
+
+/* Is there a Mapping from the supplied units to Hz? If so, use the
+   Mapping to convert the supplied value to Hz. */
+      if( ( umap = astUnitMapper( setting + off, "Hz", NULL, NULL ) ) ) {
+         astTran1( umap, 1, &dval, 1, &dtemp );
+         umap = astAnnul( umap );
+
+/* Set the intermediate frequency. */
+         astSetIF( this, dtemp );
+
+/* Otherwise report an error. */
+      } else if( astOK ) {
+         astError( AST__ATTIN, "astSetAttrib(%s): Intermediate frequency given "
+                   "in an inappropriate system of units \"%g %s\".", status,
+                   astGetClass( this ), dval, setting + off );
+      }
+
+/* SideBand */
+/* -------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "sideband= %n%*s %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+
+      if( astChrMatch( "usb", setting+ival ) ) {
+         astSetSideBand( this, USB );
+
+      } else if( astChrMatch( "lsb", setting+ival ) ) {
+         astSetSideBand( this, LSB );
+
+      } else if( astChrMatch( "lo", setting+ival ) ) {
+         astSetSideBand( this, LO );
+
+      } else if( astChrMatch( "observed", setting+ival ) ) {
+         astSetSideBand( this, ( astGetIF( this ) > 0 ) ? LSB : USB );
+
+      } else if( astChrMatch( "image", setting+ival ) ) {
+         astSetSideBand( this, ( astGetIF( this ) <= 0 ) ? LSB : USB );
+
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): The setting \"%s\" is "
+                   "invalid for a %s.", status, astGetClass( this ), setting,
+                   astGetClass( this ) );
+      }
+
+/* AlignSideBand */
+/* ------------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "alignsideband= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAlignSideBand( this, ival );
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* Use this macro to report an error if a read-only attribute has been
+   specified. */
+   } else if ( MATCH( "imagfreq" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Pass any unrecognised setting to the parent method for further
+   interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static int SubFrame( AstFrame *target_frame, AstFrame *template,
+                     int result_naxes, const int *target_axes,
+                     const int *template_axes, AstMapping **map,
+                     AstFrame **result, int *status ) {
+/*
+*  Name:
+*     SubFrame
+
+*  Purpose:
+*     Select axes from a DSBSpecFrame and convert to the new coordinate
+*     system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     int SubFrame( AstFrame *target, AstFrame *template,
+*                   int result_naxes, const int *target_axes,
+*                   const int *template_axes, AstMapping **map,
+*                   AstFrame **result, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function (over-rides the protected astSubFrame
+*     method inherited from the SpecFrame class).
+
+*  Description:
+*     This function selects a requested sub-set (or super-set) of the axes
+*     from a "target" DSBSpecFrame and creates a new Frame with copies of
+*     the selected axes assembled in the requested order. It then
+*     optionally overlays the attributes of a "template" Frame on to the
+*     result. It returns both the resulting Frame and a Mapping that
+*     describes how to convert between the coordinate systems described by
+*     the target and result Frames. If necessary, this Mapping takes
+*     account of any differences in the Frames' attributes due to the
+*     influence of the template.
+
+*  Parameters:
+*     target
+*        Pointer to the target DSBSpecFrame, from which axes are to be
+*        selected.
+*     template
+*        Pointer to the template Frame, from which new attributes for the
+*        result Frame are to be obtained. Optionally, this may be NULL, in
+*        which case no overlaying of template attributes will be performed.
+*     result_naxes
+*        Number of axes to be selected from the target Frame. This number may
+*        be greater than or less than the number of axes in this Frame (or
+*        equal).
+*     target_axes
+*        Pointer to an array of int with result_naxes elements, giving a list
+*        of the (zero-based) axis indices of the axes to be selected from the
+*        target DSBSpecFrame. The order in which these are given determines
+*        the order in which the axes appear in the result Frame. If any of the
+*        values in this array is set to -1, the corresponding result axis will
+*        not be derived from the target Frame, but will be assigned default
+*        attributes instead.
+*     template_axes
+*        Pointer to an array of int with result_naxes elements. This should
+*        contain a list of the template axes (given as zero-based axis indices)
+*        with which the axes of the result Frame are to be associated. This
+*        array determines which axes are used when overlaying axis-dependent
+*        attributes of the template on to the result. If any element of this
+*        array is set to -1, the corresponding result axis will not receive any
+*        template attributes.
+*
+*        If the template argument is given as NULL, this array is not used and
+*        a NULL pointer may also be supplied here.
+*     map
+*        Address of a location to receive a pointer to the returned Mapping.
+*        The forward transformation of this Mapping will describe how to
+*        convert coordinates from the coordinate system described by the target
+*        DSBSpecFrame to that described by the result Frame. The inverse
+*        transformation will convert in the opposite direction.
+*     result
+*        Address of a location to receive a pointer to the result Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if coordinate conversion is possible
+*     between the target and the result Frame. Otherwise zero is returned and
+*     *map and *result are returned as NULL (but this will not in itself
+*     result in an error condition). In general, coordinate conversion should
+*     always be possible if no template Frame is supplied but may not always
+*     be possible otherwise.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+
+*  Implementation Notes:
+*     -  This implementation addresses the selection of axes from a
+*     DSBSpecFrame object. This results in another object of the same class
+*     only if the single DSBSpecFrame axis is selected exactly once.
+*     Otherwise, the result is a Frame class object which inherits the
+*     DSBSpecFrame's axis information (if appropriate) but none of the other
+*     properties of a DSBSpecFrame.
+*     -  In the event that a DSBSpecFrame results, the returned Mapping will
+*     take proper account of the relationship between the target and result
+*     coordinate systems.
+*     -  In the event that a Frame class object results, the returned Mapping
+*     will only represent a selection/permutation of axes.
+
+*  Implementation Deficiencies:
+*     -  Any axis selection is currently permitted. Probably this should be
+*     restricted so that each axis can only be selected once. The
+*     astValidateAxisSelection method will do this but currently there are bugs
+*     in the CmpFrame class that cause axis selections which will not pass this
+*     test. Install the validation when these are fixed.
+*/
+
+/* Local Variables: */
+   AstDSBSpecFrame *dsbresult;/* Pointer to the DSBSpecFrame result Frame */
+   AstDSBSpecFrame *dsbtarget;/* Pointer to the DSBSpecFrame target Frame */
+   AstMapping *map1;          /* Intermediate Mapping */
+   AstMapping *map2;          /* Intermediate Mapping */
+   AstMapping *map3;          /* Intermediate Mapping */
+   int alignsb;               /* Use sidebands to align the Frames? */
+   int match;                 /* Coordinate conversion is possible? */
+   int obs_sb;                /* The observed sideband value */
+   int old_sb;                /* The original Sideband value */
+
+/* Initialise the returned values. */
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Invoke the astSubFrame method inherited from the parent SpecFrame
+   class. This will (if possible) create a result Frame which is a
+   DSBSpecFrame (since the supplied target Frame is a DSBSpecFrame).
+   However, the Mapping from target to result Frame will take no account
+   of any differences in the values of the attributes specific to the
+   DSBSpecFrame class. */
+   match = (*parent_subframe)( target_frame, template, result_naxes,
+                               target_axes, template_axes, map, result, status );
+
+/* If a match occurred, and the result and template Frames are both
+   DSBSpecFrames, we now modify the Mapping to take account of
+   DSBSpecFrame-specific attributes. */
+   if( match && template && astIsADSBSpecFrame( template ) &&
+                            astIsADSBSpecFrame( *result ) ) {
+
+/* Get pointers to the two DSBSpecFrames */
+      dsbtarget = (AstDSBSpecFrame *) target_frame;
+
+/* See whether alignment occurs between sidebands. If the current call to
+   this function is part of the process of restoring a FrameSet's integrity
+   following changes to the FrameSet's current Frame, then we ignore the
+   setting of the AlignSideBand attributes and use 1. This ensures that
+   when the SideBand attribute (for instance) is changed via a FrameSet
+   pointer, the Mappings within the FrameSet are modified to produce
+   frequencies in the new SideBand. In most other cases, astronomers
+   usually want to align the DSBSpecFrames as if they were basic SpecFrames
+   (that is, ignoring the setting of the SideBand attribute). */
+      if( astGetFrameFlags( target_frame ) & AST__INTFLAG ) {
+         alignsb = 1;
+      } else {
+         alignsb = astGetAlignSideBand( dsbtarget ) &&
+                   astGetAlignSideBand( (AstDSBSpecFrame *) template );
+      }
+
+/* If we are aligning the sidebands we need to modify the Mapping
+   returned above by the parent SubFrame method. The existing Mapping
+   will convert between the spectral systems represented by the two
+   DSBSpecFrames but will not take account of any difference in
+   sidebands. */
+      if( alignsb ) {
+
+/* We assume that alignment occurs in the observed sideband. Determine
+   which side band is the observed sideband in the target. */
+         old_sb = astGetSideBand( dsbtarget );
+         astSetC( dsbtarget, "SideBand", "observed" );
+         obs_sb = astGetSideBand( dsbtarget );
+         astSetSideBand( dsbtarget, old_sb );
+
+/* Create a Mapping which transforms positions from the target to an exact
+   copy of the target in which the SideBand attribute is set to the
+   observed (USB or LSB) sideband. This will be a UnitMap if the target
+   already represents the observed sideband. */
+         if( obs_sb == USB ) {
+            map1 = ToUSBMapping( dsbtarget, "astSubFrame", status );
+
+         } else if( obs_sb == LSB ) {
+            map1 = ToLSBMapping( dsbtarget, "astSubFrame", status );
+
+         } else {
+            map1 = NULL;
+            astError( AST__INTER, "astGetImagFreq(%s): Illegal sideband value "
+                      "(%d) encountered (internal AST programming error).", status,
+                      astGetClass( target_frame ), obs_sb );
+         }
+
+/* Determine which side band is the observed sideband in the result. */
+         dsbresult = (AstDSBSpecFrame *) *result;
+         old_sb = astGetSideBand( dsbresult );
+         astSetC( dsbresult, "SideBand", "observed" );
+         obs_sb = astGetSideBand( dsbresult );
+         astSetSideBand( dsbresult, old_sb );
+
+/* Create a Mapping which transforms positions from the result to an exact
+   copy of the result in which the SideBand attribute is set to the
+   obserfed sideband. This will be a UnitMap if the target already represents
+   the observed sideband. */
+         if( obs_sb == USB ) {
+            map2 = ToUSBMapping( dsbresult, "astSubFrame", status );
+
+         } else if( obs_sb == LSB ) {
+            map2 = ToLSBMapping( dsbresult, "astSubFrame", status );
+
+         } else {
+            map2 = NULL;
+            astError( AST__INTER, "astGetImagFreq(%s): Illegal sideband value "
+                      "(%d) encountered (internal AST programming error).", status,
+                      astGetClass( target_frame ), obs_sb );
+         }
+
+/* Invert it to get the mapping from the observed sideband to the result. */
+         astInvert( map2 );
+
+/* Form a Mapping which first maps target values to the observed sideband,
+   then applies the Mapping returned by the parent SubFrame method in
+   order to convert between spectral systems, and then converts from the
+   observed sideband to the SideBand of the result. */
+         map3 = (AstMapping *) astCmpMap( map1, *map, 1, "", status );
+         map1 = astAnnul( map1 );
+         *map = astAnnul( *map );
+         map1 = (AstMapping *) astCmpMap( map3, map2, 1, "", status );
+         map3 = astAnnul( map3 );
+         map2 = astAnnul( map2 );
+
+/* Returned the simplified Mapping. */
+         *map = astSimplify( map1 );
+         map1 = astAnnul( map1 );
+      }
+   }
+
+/* If an error occurred or no match was found, annul the returned
+   objects and reset the returned result. */
+   if ( !astOK || !match ) {
+      if( *map ) *map = astAnnul( *map );
+      if( *result ) *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a DSBSpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function (over-rides the astTestAttrib protected
+*     method inherited from the SpecFrame class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a DSBSpecFrame's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstDSBSpecFrame *this;        /* Pointer to the DSBSpecFrame structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the DSBSpecFrame structure. */
+   this = (AstDSBSpecFrame *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* DSBCentre */
+/* --------- */
+   if ( !strcmp( attrib, "dsbcentre" ) ) {
+      result = astTestDSBCentre( this );
+
+/* IF */
+/* -- */
+   } else if ( !strcmp( attrib, "if" ) ) {
+      result = astTestIF( this );
+
+/* SideBand */
+/* -------- */
+   } else if ( !strcmp( attrib, "sideband" ) ) {
+      result = astTestSideBand( this );
+
+/* AlignSideBand */
+/* ------------- */
+   } else if ( !strcmp( attrib, "alignsideband" ) ) {
+      result = astTestAlignSideBand( this );
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Test if the attribute name matches any of the read-only attributes
+   of this class. If it does, then return zero. */
+   } else if ( !strcmp( attrib, "imagfreq" ) ) {
+      result = 0;
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstMapping *ToLOMapping( AstDSBSpecFrame *this, const char *method, int *status ){
+/*
+*  Name:
+*     ToLOMapping
+
+*  Purpose:
+*     Create a Mapping which transforms a DSBSpecFrame to offset from the
+*     local oscillator frequency.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     AstMapping *ToLOMapping( AstDSBSpecFrame *this, const char *method, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function
+
+*  Description:
+*     This function returns a pointer to a new Mapping which transforms
+*     positions in the supplied DSBSpecFrame into an offset from the local
+*     oscillator frequency. This will be a UnitMap if the DSBSpecFrame
+*     already represents offset from the local oscillator frequency.
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame.
+*     method
+*        Pointer to a null-terminated string containing the name of the
+*        public invoking method. This is only used in the construction of
+*        error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a new Mapping.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *fmap;         /* LSB to USB (topo freq) */
+   AstMapping *map1;         /* This to USB (topo freq) */
+   AstMapping *map2;         /* This (LSB) to This (USB) */
+   AstMapping *result;       /* Pointer to the returned Mapping */
+   AstMapping *tmap;         /* This to topocentric freq */
+   double f_lo;              /* Local oscillator freq (topo Hz) */
+   double f_in_a;            /* First LSB or USB freq */
+   double f_in_b;            /* Second LSB or USB freq */
+   double f_out_a;           /* First LO freq */
+   double f_out_b;           /* Second LO freq */
+   int sb;                   /* SideBand value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the DSBSpecFrame already represents LO offset, return a UnitMap.*/
+   sb = astGetSideBand( this );
+   if( sb == LO ) {
+      result = (AstMapping *) astUnitMap( 1, "", status );
+
+/* If the DSBSpecFrame represents the USB or LSB, create a suitable WinMap. */
+   } else {
+
+/* Find the Mapping from the spectral system described by this SpecFrame to
+   topocentric frequency in Hz. */
+      tmap = TopoMap( this, 1, method, status );
+
+/* Calculate the local oscillator frequency (topocentric in Hertz). */
+      f_lo = GetLO( this, "create a Mapping to upper sideband",
+                    "astGetImagFreq", status );
+
+/* Create a 1D WinMap which converts f_in to f_out. */
+      if( sb == LSB ) {
+         f_in_a = 0.0;
+         f_in_b = f_lo;
+         f_out_a = f_lo;
+         f_out_b = 0.0;
+      } else {
+         f_in_a = 0.0;
+         f_in_b = -f_lo;
+         f_out_a = f_lo;
+         f_out_b = 0.0;
+      }
+
+      fmap = (AstMapping *) astWinMap( 1, &f_in_a, &f_in_b, &f_out_a, &f_out_b, "", status );
+
+/* Construct the Mapping: input to f_in, f_in to f_out, f_out to input */
+      map1 = (AstMapping *) astCmpMap( tmap, fmap, 1, "", status );
+      astInvert( tmap );
+      map2 = (AstMapping *) astCmpMap( map1, tmap, 1, "", status );
+
+/* Simplify */
+      result = astSimplify( map2 );
+
+/* Free resources */
+      tmap = astAnnul( tmap );
+      fmap = astAnnul( fmap );
+      map1 = astAnnul( map1 );
+      map2 = astAnnul( map2 );
+   }
+
+/* Return NULL if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+
+}
+
+static AstMapping *ToLSBMapping( AstDSBSpecFrame *this, const char *method, int *status ){
+/*
+*  Name:
+*     ToLSBMapping
+
+*  Purpose:
+*     Create a Mapping which transforms a DSBSpecFrame to the lower
+*     sideband.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     AstMapping *ToLSBMapping( AstDSBSpecFrame *this, const char *method, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function
+
+*  Description:
+*     This function returns a pointer to a new Mapping which transforms
+*     positions in the supplied DSBSpecFrame to the lower sideband. This
+*     will be a UnitMap if the DSBSpecFrame already represents the lower
+*     sideband.
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame.
+*     method
+*        Pointer to a null-terminated string containing the name of the
+*        public invoking method. This is only used in the construction of
+*        error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a new Mapping.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *fmap;         /* LSB to USB (topo freq) */
+   AstMapping *map1;         /* This to USB (topo freq) */
+   AstMapping *map2;         /* This (LSB) to This (USB) */
+   AstMapping *result;       /* Pointer to the returned Mapping */
+   AstMapping *tmap;         /* This to topocentric freq */
+   double f_lo;              /* Local oscillator freq (topo Hz) */
+   double f_out_a;           /* First LSB freq */
+   double f_out_b;           /* Second LSB freq */
+   double f_in_a;            /* First USB or LO freq */
+   double f_in_b;            /* Second USB or LO freq */
+   int sb;                   /* SideBand value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the DSBSpecFrame already represents the LSB, return a UnitMap.*/
+   sb = astGetSideBand( this );
+   if( sb == LSB ) {
+      result = (AstMapping *) astUnitMap( 1, "", status );
+
+/* If the DSBSpecFrame represents the USB or LO offset, create a suitable
+   WinMap. */
+   } else {
+
+/* Find the Mapping from the spectral system described by this SpecFrame to
+   topocentric frequency in Hz. */
+      tmap = TopoMap( this, 1, method, status );
+
+/* Calculate the local oscillator frequency (topocentric in Hertz). */
+      f_lo = GetLO( this, "create a Mapping to lower sideband",
+                    "astGetImagFreq", status );
+
+/* Create a 1D WinMap which converts USB or LO to LSB. */
+      if( sb == USB ) {
+         f_in_a = 0.0;
+         f_in_b = 2*f_lo;
+         f_out_a = 2*f_lo;
+         f_out_b = 0.0;
+      } else {
+         f_in_a = 0.0;
+         f_in_b = f_lo;
+         f_out_a = f_lo;
+         f_out_b = 0.0;
+      }
+
+      fmap = (AstMapping *) astWinMap( 1, &f_in_a, &f_in_b, &f_out_a, &f_out_b, "", status );
+
+/* Construct the Mapping: input to f_in, f_in to f_out, f_out to input */
+      map1 = (AstMapping *) astCmpMap( tmap, fmap, 1, "", status );
+      astInvert( tmap );
+      map2 = (AstMapping *) astCmpMap( map1, tmap, 1, "", status );
+
+/* Simplify */
+      result = astSimplify( map2 );
+
+/* Free resources */
+      tmap = astAnnul( tmap );
+      fmap = astAnnul( fmap );
+      map1 = astAnnul( map1 );
+      map2 = astAnnul( map2 );
+   }
+
+/* Return NULL if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+
+}
+
+static AstMapping *TopoMap( AstDSBSpecFrame *this, int forward,
+                            const char *method, int *status ){
+/*
+*  Name:
+*     TopoMap
+
+*  Purpose:
+*     Create a Mapping which transforms a DSBSpecFrame to topocentric
+*     frequency.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     AstMapping *TopoMap( AstDSBSpecFrame *this, int forward,
+*                          const char *method, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function
+
+*  Description:
+*     This function returns a pointer to a new Mapping which transforms
+*     positions in the supplied DSBSpecFrame to the corresponding
+*     topocentric frequency values in Hz (or the inverse of this).
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame.
+*     forward
+*        If zero, the calcuated Mapping is inverted before being returned.
+*     method
+*        Pointer to a null-terminated string containing the name of the
+*        public invoking method. This is only used in the construction of
+*        error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a new Mapping.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *result;       /* The returned Mapping */
+   AstFrameSet *fs;          /* FrameSet connecting tf1 and tf2 */
+   AstSpecFrame *tf1;        /* SpecFrame corresponding to this DSBSpecFrame */
+   AstSpecFrame *tf2;        /* Topocentric frequency SpecFrame */
+   int template_axis;        /* The axis to overlay */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Make a SpecFrame and then overlay the SpecFrame attributes of this
+   DSBSpecFrame onto the new SpecFrame. This means it inherits the current
+   values of things like ObsLon and ObsLat. */
+   tf1 = astSpecFrame( "", status );
+   template_axis = 0;
+   (*parent_overlay)( (AstFrame *) this, &template_axis, (AstFrame *) tf1, status );
+
+/* Copy this new SpecFrame and set its attributes to describe topocentric
+   frequency in Hz. Ensure that alignment occurs in the topocentric Frame. */
+   astSetAlignStdOfRest( tf1, AST__TPSOR);
+   tf2 = astCopy( tf1 );
+   astSetSystem( tf2, AST__FREQ );
+   astSetStdOfRest( tf2, AST__TPSOR );
+   astSetUnit( tf2, 0, "Hz" );
+
+/* Find the Mapping from the spectral system described by this SpecFrame to
+   topocentric frequency in Hz. */
+   fs = astConvert( tf1, tf2, "" );
+   if ( astOK ) {
+      if( !fs ) {
+         astError( AST__INTER, "%s(%s): Cannot convert DSBCentre "
+                   "value from the supplied system to topocentric frequency "
+                   "(internal AST programming error).", status, method,
+                   astGetClass( this ) );
+      } else {
+         result = astGetMapping( fs, AST__BASE, AST__CURRENT );
+         if( !forward ) astInvert( result );
+      }
+      fs = astAnnul( fs );
+   }
+
+/* Free resources */
+   tf1 = astAnnul( tf1 );
+   tf2 = astAnnul( tf2 );
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+
+}
+
+static AstMapping *ToUSBMapping( AstDSBSpecFrame *this, const char *method, int *status ){
+/*
+*  Name:
+*     ToUSBMapping
+
+*  Purpose:
+*     Create a Mapping which transforms a DSBSpecFrame to the upper
+*     sideband.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     AstMapping *ToUSBMapping( AstDSBSpecFrame *this, const char *method, int *status )
+
+*  Class Membership:
+*     DSBSpecFrame member function
+
+*  Description:
+*     This function returns a pointer to a new Mapping which transforms
+*     positions in the supplied DSBSpecFrame to the upper sideband. This
+*     will be a UnitMap if the DSBSpecFrame already represents the upper
+*     sideband.
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame.
+*     method
+*        Pointer to a null-terminated string containing the name of the
+*        public invoking method. This is only used in the construction of
+*        error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a new Mapping.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *fmap;         /* LSB to USB (topo freq) */
+   AstMapping *map1;         /* This to USB (topo freq) */
+   AstMapping *map2;         /* This (LSB) to This (USB) */
+   AstMapping *result;       /* Pointer to the returned Mapping */
+   AstMapping *tmap;         /* This to topocentric freq */
+   double f_lo;              /* Local oscillator freq (topo Hz) */
+   double f_in_a;            /* First LSB or LO freq */
+   double f_in_b;            /* Second LSB or LO freq */
+   double f_out_a;           /* First USB freq */
+   double f_out_b;           /* Second USB freq */
+   int sb;                   /* SideBand value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the DSBSpecFrame already represents the USB, return a UnitMap.*/
+   sb = astGetSideBand( this );
+   if( sb == USB ) {
+      result = (AstMapping *) astUnitMap( 1, "", status );
+
+/* If the DSBSpecFrame represents the LSB, or LO offset, create a suitable
+   WinMap. */
+   } else {
+
+/* Find the Mapping from the spectral system described by this SpecFrame to
+   topocentric frequency in Hz. */
+      tmap = TopoMap( this, 1, method, status );
+
+/* Calculate the local oscillator frequency (topocentric in Hertz). */
+      f_lo = GetLO( this, "create a Mapping to upper sideband",
+                    "astGetImagFreq", status );
+
+/* Create a 1D WinMap which converts f_in to f_out. */
+      if( sb == LSB ) {
+         f_in_a = 0.0;
+         f_in_b = 2*f_lo;
+         f_out_a = 2*f_lo;
+         f_out_b = 0.0;
+      } else {
+         f_in_a = 0.0;
+         f_in_b = -f_lo;
+         f_out_a = f_lo;
+         f_out_b = 0.0;
+      }
+
+      fmap = (AstMapping *) astWinMap( 1, &f_in_a, &f_in_b, &f_out_a, &f_out_b, "", status );
+
+/* Construct the Mapping: input to f_in, f_in to f_out, f_out to input */
+      map1 = (AstMapping *) astCmpMap( tmap, fmap, 1, "", status );
+      astInvert( tmap );
+      map2 = (AstMapping *) astCmpMap( map1, tmap, 1, "", status );
+
+/* Simplify */
+      result = astSimplify( map2 );
+
+/* Free resources */
+      tmap = astAnnul( tmap );
+      fmap = astAnnul( fmap );
+      map1 = astAnnul( map1 );
+      map2 = astAnnul( map2 );
+   }
+
+/* Return NULL if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+
+}
+
+static void VerifyAttrs( AstDSBSpecFrame *this, const char *purp,
+                         const char *attrs, const char *method, int *status ) {
+/*
+*  Name:
+*     VerifyAttrs
+
+*  Purpose:
+*     Verify that usable attribute values are available.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     void VerifyAttrs( AstDSBSpecFrame *this, const char *purp,
+*                       const char *attrs, const char *method, int *status  )
+
+*  Class Membership:
+*     DSBSpecFrame member function
+
+*  Description:
+*     This function tests each attribute listed in "attrs". It returns
+*     without action if 1) an explicit value has been set for each attribute
+*     or 2) the UseDefs attribute of the supplied DSBSpecFrame is non-zero.
+*
+*     If UseDefs is zero (indicating that default values should not be
+*     used for attributes), and any of the named attributes does not have
+*     an explicitly set value, then an error is reported.
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame.
+*     purp
+*        Pointer to a text string containing a message which will be
+*        included in any error report. This shouldindicate the purpose
+*        for which the attribute value is required.
+*     attrs
+*        A string holding a space separated list of attribute names.
+*     method
+*        A string holding the name of the calling method for use in error
+*        messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   const char *a;
+   const char *desc;
+   const char *p;
+   int len;
+   int set;
+   int state;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* If the DSBSpecFrame has a non-zero value for its UseDefs attribute, then
+   all attributes are assumed to have usable values, since the defaults
+   will be used if no explicit value has been set. So we only need to do
+   any checks if UseDefs is zero. */
+   if( !astGetUseDefs( this ) ) {
+
+/* Initialise variables to avoid compiler warnings. */
+      a = NULL;
+      desc = NULL;
+      len = 0;
+      set = 0;
+
+/* Loop round the "attrs" string identifying the start and length of each
+   non-blank word in the string. */
+      state = 0;
+      p = attrs;
+      while( 1 ) {
+         if( state == 0 ) {
+            if( !isspace( *p ) ) {
+               a = p;
+               len = 1;
+               state = 1;
+            }
+         } else {
+            if( isspace( *p ) || !*p ) {
+
+/* The end of a word has just been reached. Compare it to each known
+   attribute value. Get a flag indicating if the attribute has a set
+   value, and a string describing the attribute.*/
+               if( len > 0 ) {
+
+                  if( !strncmp( "DSBCentre", a, len ) ) {
+                     set = astTestDSBCentre( this );
+                     desc = "central position of interest";
+
+                  } else if( !strncmp( "IF", a, len ) ) {
+                     set = astTestIF( this );
+                     desc = "intermediate frequency";
+
+                  } else {
+                     astError( AST__INTER, "VerifyAttrs(DSBSpecFrame): "
+                               "Unknown attribute name \"%.*s\" supplied (AST "
+                               "internal programming error).", status, len, a );
+                  }
+
+/* If the attribute does not have a set value, report an error. */
+                  if( !set && astOK ) {
+                     astError( AST__NOVAL, "%s(%s): Cannot %s.", status, method,
+                               astGetClass( this ), purp );
+                     astError( AST__NOVAL, "No value has been set for "
+                               "the AST \"%.*s\" attribute (%s).", status, len, a,
+                               desc );
+                  }
+
+/* Continue the word search algorithm. */
+               }
+               len = 0;
+               state = 0;
+            } else {
+               len++;
+            }
+         }
+         if( !*(p++) ) break;
+      }
+   }
+}
+
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     ImagFreq
+
+*  Purpose:
+*     The image sideband equivalent of the rest frequency.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point, read-only.
+
+*  Description:
+*     This is a read-only attribute giving the frequency which
+*     corresponds to the rest frequency but is in the opposite sideband.
+
+*     The value is calculated by first transforming the rest frequency
+*     (given by the RestFreq attribute) from the standard of rest of the
+*     source (given by the SourceVel and SourceVRF attributes) to the
+*     standard of rest of the observer (i.e. the topocentric standard of
+*     rest). The resulting topocentric frequency is assumed to be in the
+*     same sideband as the value given for the DSBCentre attribute (the
+*     "observed" sideband), and is transformed to the other sideband (the
+*     "image" sideband). The new frequency is converted back to the standard
+*     of rest of the source, and the resulting value is returned as the
+*     attribute value, in units of GHz.
+
+*  Applicability:
+*     DSBSpecFrame
+*        All DSBSpecFrames have this attribute.
+
+*att--
+*/
+
+
+/*
+*att++
+*  Name:
+*     DSBCentre
+
+*  Purpose:
+*     The central position of interest in a dual sideband spectrum.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute specifies the central position of interest in a dual
+*     sideband spectrum. Its sole use is to determine the local oscillator
+*     frequency (the frequency which marks the boundary between the lower
+*     and upper sidebands). See the description of the IF (intermediate
+*     frequency) attribute for details of how the local oscillator frequency
+*     is calculated. The sideband containing this central position is
+*     referred to as the "observed" sideband, and the other sideband as
+*     the "image" sideband.
+*
+*     The value is accessed as a position in the spectral system
+*     represented by the SpecFrame attributes inherited by this class, but
+*     is stored internally as topocentric frequency. Thus, if the System
+*     attribute of the DSBSpecFrame is set to "VRAD", the Unit attribute
+*     set to "m/s" and the StdOfRest attribute set to "LSRK", then values
+*     for the DSBCentre attribute should be supplied as radio velocity in
+*     units of "m/s" relative to the kinematic LSR (alternative units may
+*     be used by appending a suitable units string to the end of the value).
+*     This value is then converted to topocentric frequency and stored. If
+*     (say) the Unit attribute is subsequently changed to "km/s" before
+*     retrieving the current value of the DSBCentre attribute, the stored
+*     topocentric frequency will be converted back to LSRK radio velocity,
+*     this time in units of "km/s", before being returned.
+*
+*     The default value for this attribute is 30 GHz.
+
+*  Applicability:
+*     DSBSpecFrame
+*        All DSBSpecFrames have this attribute.
+
+*  Note:
+*     - The attributes which define the transformation to or from topocentric
+*     frequency should be assigned their correct values before accessing
+*     this attribute. These potentially include System, Unit, StdOfRest,
+*     ObsLon, ObsLat, ObsAlt, Epoch, RefRA, RefDec and RestFreq.
+
+*att--
+*/
+/* The central frequency (topocentric frequency in Hz). */
+astMAKE_CLEAR(DSBSpecFrame,DSBCentre,dsbcentre,AST__BAD)
+astMAKE_GET(DSBSpecFrame,DSBCentre,double,3.0E10,((this->dsbcentre!=AST__BAD)?this->dsbcentre:3.0E10))
+astMAKE_SET(DSBSpecFrame,DSBCentre,double,dsbcentre,value)
+astMAKE_TEST(DSBSpecFrame,DSBCentre,( this->dsbcentre != AST__BAD ))
+
+
+/*
+*att++
+*  Name:
+*     IF
+
+*  Purpose:
+*     The intermediate frequency in a dual sideband spectrum.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute specifies the (topocentric) intermediate frequency in
+*     a dual sideband spectrum. Its sole use is to determine the local
+*     oscillator (LO) frequency (the frequency which marks the boundary
+*     between the lower and upper sidebands). The LO frequency is
+*     equal to the sum of the centre frequency and the intermediate
+*     frequency. Here, the "centre frequency" is the topocentric
+*     frequency in Hz corresponding to the current value of the DSBCentre
+*     attribute. The value of the IF attribute may be positive or
+*     negative: a positive value results in the LO frequency being above
+*     the central frequency, whilst a negative IF value results in the LO
+*     frequency being below the central frequency. The sign of the IF
+*     attribute value determines the default value for the SideBand
+*     attribute.
+*
+*     When setting a new value for this attribute, the units in which the
+*     frequency value is supplied may be indicated by appending a suitable
+*     string to the end of the formatted value. If the units are not
+*     specified, then the supplied value is assumed to be in units of GHz.
+*     For instance, the following strings all result in an IF of 4 GHz being
+*     used: "4.0", "4.0 GHz", "4.0E9 Hz", etc.
+*
+*     When getting the value of this attribute, the returned value is
+*     always in units of GHz. The default value for this attribute is 4 GHz.
+
+*  Applicability:
+*     DSBSpecFrame
+*        All DSBSpecFrames have this attribute.
+
+*att--
+*/
+/* The intermediate frequency (topocentric in Hz). */
+astMAKE_CLEAR(DSBSpecFrame,IF,ifr,AST__BAD)
+astMAKE_GET(DSBSpecFrame,IF,double,4.0E9,((this->ifr!=AST__BAD)?this->ifr:4.0E9))
+astMAKE_SET(DSBSpecFrame,IF,double,ifr,value)
+astMAKE_TEST(DSBSpecFrame,IF,( this->ifr != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     SideBand
+
+*  Purpose:
+*     Indicates which sideband a dual sideband spectrum represents.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute indicates whether the DSBSpecFrame currently
+*     represents its lower or upper sideband, or an offset from the local
+*     oscillator frequency. When querying the current value, the returned
+*     string is always one of "usb" (for upper sideband), "lsb" (for lower
+*     sideband), or "lo" (for offset from the local oscillator frequency).
+*     When setting a new value, any of the strings "lsb", "usb", "observed",
+*     "image" or "lo" may be supplied (case insensitive). The "observed"
+*     sideband is which ever sideband (upper or lower) contains the central
+*     spectral position given by attribute DSBCentre, and the "image"
+*     sideband is the other sideband. It is the sign of the IF attribute
+*     which determines if the observed sideband is the upper or lower
+*     sideband. The default value for SideBand is the observed sideband.
+
+*  Applicability:
+*     DSBSpecFrame
+*        All DSBSpecFrames have this attribute.
+
+*att--
+*/
+/* Protected access to the SideBand attribute uses BADSB to indicate
+   "unset". Other negative values mean "LSB", zero means "LO" and
+   positive values mean "USB". */
+astMAKE_CLEAR(DSBSpecFrame,SideBand,sideband,BADSB)
+astMAKE_SET(DSBSpecFrame,SideBand,int,sideband,((value<0)?LSB:((value==0)?LO:USB)))
+astMAKE_TEST(DSBSpecFrame,SideBand,( this->sideband != BADSB ))
+astMAKE_GET(DSBSpecFrame,SideBand,int,USB,(this->sideband == BADSB ? ((astGetIF( this )>0)?LSB:USB):this->sideband))
+
+/*
+*att++
+*  Name:
+*     AlignSideBand
+
+*  Purpose:
+*     Should the SideBand attribute be taken into account when aligning
+*     this DSBSpecFrame with another DSBSpecFrame?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls how a DSBSpecFrame behaves when an attempt
+*     is made to align it with another DSBSpecFrame using
+c     astFindFrame or astConvert.
+f     AST_FINDFRAME or AST_CONVERT.
+*     If both DSBSpecFrames have a non-zero value for AlignSideBand, the
+*     value of the SideBand attribute in each DSBSpecFrame is used so that
+*     alignment occurs between sidebands. That is, if one DSBSpecFrame
+*     represents USB and the other represents LSB then
+c     astFindFrame and astConvert
+f     AST_FINDFRAME and AST_CONVERT
+*     will recognise that the DSBSpecFrames represent different sidebands
+*     and will take this into account when constructing the Mapping that
+*     maps positions in one DSBSpecFrame into the other. If AlignSideBand
+*     in either DSBSpecFrame is set to zero, then the values of the SideBand
+*     attributes are ignored. In the above example, this would result in a
+*     frequency in the first DSBSpecFrame being mapped onto the same
+*     frequency in the second DSBSpecFrame, even though those frequencies
+*     refer to different sidebands. In other words, if either AlignSideBand
+*     attribute is zero, then the two DSBSpecFrames aligns like basic
+*     SpecFrames. The default value for AlignSideBand is zero.
+*
+c     When astFindFrame or astConvert
+f     When AST_FINDFRAME or AST_CONVERT
+*     is used on two DSBSpecFrames (potentially describing different spectral
+*     coordinate systems and/or sidebands), it returns a Mapping which can be
+*     used to transform a position in one DSBSpecFrame into the corresponding
+*     position in the other. The Mapping is made up of the following steps in
+*     the indicated order:
+*
+*     - If both DSBSpecFrames have a value of 1 for the AlignSideBand
+*     attribute, map values from the target's current sideband (given by its
+*     SideBand attribute) to the observed sideband (whether USB or LSB). If
+*     the target already represents the observed sideband, this step will
+*     leave the values unchanged. If either of the two DSBSpecFrames have a
+*     value of zero for its AlignSideBand attribute, then this step is omitted.
+*
+*     - Map the values from the spectral system of the target to the spectral
+*     system of the template. This Mapping takes into account all the
+*     inherited SpecFrame attributes such as System, StdOfRest, Unit, etc.
+*
+*     - If both DSBSpecFrames have a value of 1 for the AlignSideBand
+*     attribute, map values from the result's observed sideband to the
+*     result's current sideband (given by its SideBand attribute). If the
+*     result already represents the observed sideband, this step will leave
+*     the values unchanged. If either of the two DSBSpecFrames have a value
+*     of zero for its AlignSideBand attribute, then this step is omitted.
+
+*  Applicability:
+*     DSBSpecFrame
+*        All DSBSpecFrames have this attribute.
+
+*att--
+*/
+/* The AlignSideBand value has a value of -1 when not set yielding a
+   default of 0. */
+astMAKE_TEST(DSBSpecFrame,AlignSideBand,( this->alignsideband != -1 ))
+astMAKE_CLEAR(DSBSpecFrame,AlignSideBand,alignsideband,-1)
+astMAKE_GET(DSBSpecFrame,AlignSideBand,int,-1,((this->alignsideband==-1)?0:this->alignsideband) )
+astMAKE_SET(DSBSpecFrame,AlignSideBand,int,alignsideband,(value?1:0))
+
+/* Copy constructor. */
+/* ----------------- */
+/* None needed */
+
+/* Destructor. */
+/* ----------- */
+/* None needed */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for DSBSpecFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the DSBSpecFrame class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the DSBSpecFrame whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstDSBSpecFrame *this;        /* Pointer to the DSBSpecFrame structure */
+   const char *cval;             /* Attribute value */
+   const char *comm;             /* Attribute comment */
+   double dval;                  /* Attribute value */
+   int ival;                     /* Attribute value */
+   int set;                      /* Is attribute set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the DSBSpecFrame structure. */
+   this = (AstDSBSpecFrame *) this_object;
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* DSBCentre */
+/* --------- */
+   set = TestDSBCentre( this, status );
+   dval = set ? GetDSBCentre( this, status ) : astGetDSBCentre( this );
+   astWriteDouble( channel, "DSBCen", set, 1, dval, "Central frequency (Hz topo)" );
+
+/* IF */
+/* -- */
+   set = TestIF( this, status );
+   dval = set ? GetIF( this, status ) : astGetIF( this );
+   astWriteDouble( channel, "IF", set, 1, dval, "Intermediate frequency (Hz)" );
+
+/* SideBand */
+/* -------- */
+   set = TestSideBand( this, status );
+   ival = set ? GetSideBand( this, status ) : astGetSideBand( this );
+   if( ival == LSB ) {
+      cval = "LSB";
+      comm = "Represents lower sideband";
+
+   } else if( ival == LO ) {
+      cval = "LO";
+      comm = "Represents offset from LO frequency";
+
+   } else {
+      cval = "USB";
+      comm = "Represents upper sideband";
+   }
+   astWriteString( channel, "SideBn", set, 1, cval, comm );
+
+/* AlignSideBand */
+/* ------------- */
+   set = TestAlignSideBand( this, status );
+   ival = set ? GetAlignSideBand( this, status ) : astGetAlignSideBand( this );
+   astWriteInt( channel, "AlSdBn", set, 1, ival, "Align sidebands?" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsADSBSpecFrame and astCheckDSBSpecFrame functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(DSBSpecFrame,SpecFrame)
+astMAKE_CHECK(DSBSpecFrame)
+
+AstDSBSpecFrame *astDSBSpecFrame_( const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astDSBSpecFrame
+f     AST_DSBSPECFRAME
+
+*  Purpose:
+*     Create a DSBSpecFrame.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "dsbspecframe.h"
+c     AstDSBSpecFrame *astDSBSpecFrame( const char *options, ... )
+f     RESULT = AST_DSBSPECFRAME( OPTIONS, STATUS )
+
+*  Class Membership:
+*     DSBSpecFrame constructor.
+
+*  Description:
+*     This function creates a new DSBSpecFrame and optionally initialises its
+*     attributes.
+*
+*     A DSBSpecFrame is a specialised form of SpecFrame which represents
+*     positions in a spectrum obtained using a dual sideband instrument.
+*     Such an instrument produces a spectrum in which each point contains
+*     contributions from two distinctly different frequencies, one from
+*     the "lower side band" (LSB) and one from the "upper side band" (USB).
+*     Corresponding LSB and USB frequencies are connected by the fact
+*     that they are an equal distance on either side of a fixed central
+*     frequency known as the "Local Oscillator" (LO) frequency.
+*
+*     When quoting a position within such a spectrum, it is necessary to
+*     indicate whether the quoted position is the USB position or the
+*     corresponding LSB position. The SideBand attribute provides this
+*     indication. Another option that the SideBand attribute provides is
+*     to represent a spectral position by its topocentric offset from the
+*     LO frequency.
+*
+*     In practice, the LO frequency is specified by giving the distance
+*     from the LO frequency to some "central" spectral position. Typically
+*     this central position is that of some interesting spectral feature.
+*     The distance from this central position to the LO frequency is known
+*     as the "intermediate frequency" (IF). The value supplied for IF can
+*     be a signed value in order to indicate whether the LO frequency is
+*     above or below the central position.
+
+*  Parameters:
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new DSBSpecFrame. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new DSBSpecFrame. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astDSBSpecFrame()
+f     AST_DSBSPECFRAME = INTEGER
+*        A pointer to the new DSBSpecFrame.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstDSBSpecFrame *new;        /* Pointer to new DSBSpecFrame */
+   va_list args;                /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the DSBSpecFrame, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitDSBSpecFrame( NULL, sizeof( AstDSBSpecFrame ), !class_init, &class_vtab,
+                              "DSBSpecFrame" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new DSBSpecFrame's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new DSBSpecFrame. */
+   return new;
+}
+
+AstDSBSpecFrame *astDSBSpecFrameId_( const char *options, ... ) {
+/*
+*  Name:
+*     astDSBSpecFrameId_
+
+*  Purpose:
+*     Create a DSBSpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     AstDSBSpecFrame *astDSBSpecFrameId_( const char *options, ... )
+
+*  Class Membership:
+*     DSBSpecFrame constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astDSBSpecFrame constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astDSBSpecFrame_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astDSBSpecFrame_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astDSBSpecFrame_.
+
+*  Returned Value:
+*     The ID value associated with the new DSBSpecFrame.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstDSBSpecFrame *new;        /* Pointer to new DSBSpecFrame */
+   va_list args;                /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the DSBSpecFrame, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitDSBSpecFrame( NULL, sizeof( AstDSBSpecFrame ), !class_init, &class_vtab,
+                              "DSBSpecFrame" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new DSBSpecFrame's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new DSBSpecFrame. */
+   return astMakeId( new );
+}
+
+AstDSBSpecFrame *astInitDSBSpecFrame_( void *mem, size_t size, int init,
+                                       AstDSBSpecFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitDSBSpecFrame
+
+*  Purpose:
+*     Initialise a DSBSpecFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     AstDSBSpecFrame *astInitDSBSpecFrame( void *mem, size_t size, int init,
+*                               AstDSBSpecFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     DSBSpecFrame initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new DSBSpecFrame object. It allocates memory (if necessary) to accommodate
+*     the DSBSpecFrame plus any additional data associated with the derived class.
+*     It then initialises a DSBSpecFrame structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a DSBSpecFrame at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the DSBSpecFrame is to be initialised.
+*        This must be of sufficient size to accommodate the DSBSpecFrame data
+*        (sizeof(DSBSpecFrame)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the DSBSpecFrame (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the DSBSpecFrame
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the DSBSpecFrame's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new DSBSpecFrame.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+
+*  Returned Value:
+*     A pointer to the new DSBSpecFrame.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstDSBSpecFrame *new;        /* Pointer to new DSBSpecFrame */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitDSBSpecFrameVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Initialise a SpecFrame structure (the parent class) as the first component
+   within the DSBSpecFrame structure, allocating memory if necessary. Specify that
+   the SpecFrame should be defined in both the forward and inverse directions. */
+   new = (AstDSBSpecFrame *) astInitSpecFrame( mem, size, 0,
+                                       (AstSpecFrameVtab *) vtab, name );
+   if ( astOK ) {
+
+/* Initialise the DSBSpecFrame data. */
+/* --------------------------------- */
+      new->dsbcentre = AST__BAD;
+      new->ifr = AST__BAD;
+      new->sideband = BADSB;
+      new->alignsideband = -1;
+
+/* If an error occurred, clean up by deleting the new DSBSpecFrame. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new DSBSpecFrame. */
+   return new;
+}
+
+AstDSBSpecFrame *astLoadDSBSpecFrame_( void *mem, size_t size,
+                           AstDSBSpecFrameVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadDSBSpecFrame
+
+*  Purpose:
+*     Load a DSBSpecFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "dsbspecframe.h"
+*     AstDSBSpecFrame *astLoadDSBSpecFrame( void *mem, size_t size,
+*                               AstDSBSpecFrameVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     DSBSpecFrame loader.
+
+*  Description:
+*     This function is provided to load a new DSBSpecFrame using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     DSBSpecFrame structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a DSBSpecFrame at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the DSBSpecFrame is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        DSBSpecFrame data (sizeof(DSBSpecFrame)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the DSBSpecFrame (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the DSBSpecFrame structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstDSBSpecFrame) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new DSBSpecFrame. If this is NULL, a pointer
+*        to the (static) virtual function table for the DSBSpecFrame class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "DSBSpecFrame" is used instead.
+
+*  Returned Value:
+*     A pointer to the new DSBSpecFrame.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants. */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstDSBSpecFrame *new;         /* Pointer to the new DSBSpecFrame */
+   char *text;                   /* Pointer to string value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this DSBSpecFrame. In this case the
+   DSBSpecFrame belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstDSBSpecFrame );
+      vtab = &class_vtab;
+      name = "DSBSpecFrame";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitDSBSpecFrameVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built DSBSpecFrame. */
+   new = astLoadSpecFrame( mem, size, (AstSpecFrameVtab *) vtab, name,
+                           channel );
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+       astReadClassData( channel, "DSBSpecFrame" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* DSBCentre */
+/* --------- */
+      new->dsbcentre = astReadDouble( channel, "dsbcen", AST__BAD );
+      if ( TestDSBCentre( new, status ) ) SetDSBCentre( new, new->dsbcentre, status );
+
+/* IF */
+/* -- */
+      new->ifr = astReadDouble( channel, "if", AST__BAD );
+      if ( TestIF( new, status ) ) SetIF( new, new->ifr, status );
+
+/* SideBand */
+/* -------- */
+      text = astReadString( channel, "sidebn", " " );
+      if( astOK ) {
+         if( !strcmp( text, " " ) ) {
+            new->sideband = BADSB;
+         } else if( !strcmp( text, "USB" ) ) {
+            new->sideband = USB;
+         } else if( !strcmp( text, "LSB" ) ) {
+            new->sideband = LSB;
+         } else if( !strcmp( text, "LO" ) ) {
+            new->sideband = LO;
+         } else {
+            astError( AST__ATTIN, "astRead(%s): Invalid SideBand description "
+                      "\"%s\".", status, astGetClass( channel ), text );
+         }
+         if ( TestSideBand( new, status ) ) SetSideBand( new, new->sideband, status );
+         text = astFree( text );
+      }
+
+/* AlignSideBand */
+/* ------------- */
+      new->alignsideband = astReadInt( channel, "alsdbn", -1 );
+      if( TestAlignSideBand( new, status ) ) SetAlignSideBand( new, new->alignsideband, status );
+
+/* If an error occurred, clean up by deleting the new DSBSpecFrame. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new DSBSpecFrame pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+double astGetImagFreq_( AstDSBSpecFrame *this, int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,DSBSpecFrame,GetImagFreq))( this, status );
+}
+
+
+
+
+
+
diff --git a/ast/dsbspecframe.h b/ast/dsbspecframe.h
new file mode 100644
index 0000000..87617f5
--- /dev/null
+++ b/ast/dsbspecframe.h
@@ -0,0 +1,298 @@
+#if !defined( DSBSPECFRAME_INCLUDED ) /* Include this file only once */
+#define DSBSPECFRAME_INCLUDED
+/*
+*+
+*  Name:
+*     dsbspecframe.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the DSBSpecFrame class.
+
+*  Invocation:
+*     #include "dsbspecframe.h"
+
+*  Description:
+*     This include file defines the interface to the DSBSpecFrame class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+
+*  Inheritance:
+*     The DSBSpecFrame class inherits from the SpecFrame class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     5-AUG-2004 (DSB):
+*        Original version.
+*     27-OCT-2006 (DSB):
+*        Added AlignSideBand.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "specframe.h"           /* Spectral coord systems (parent class) */
+
+/* C header files. */
+/* --------------- */
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* DSBSpecFrame structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstDSBSpecFrame {
+
+/* Attributes inherited from the parent class. */
+   AstSpecFrame specframe;      /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double dsbcentre;            /* Centre frequency */
+   double ifr;                  /* Intermediate frequency */
+   int sideband;                /* Current sideband */
+   int alignsideband;           /* Aligns sidebands? */
+
+} AstDSBSpecFrame;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstDSBSpecFrameVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstSpecFrameVtab specframe_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   double (* GetDSBCentre)( AstDSBSpecFrame *, int * );
+   int (* TestDSBCentre)( AstDSBSpecFrame *, int * );
+   void (* ClearDSBCentre)( AstDSBSpecFrame *, int * );
+   void (* SetDSBCentre)( AstDSBSpecFrame *, double, int * );
+
+   double (* GetIF)( AstDSBSpecFrame *, int * );
+   int (* TestIF)( AstDSBSpecFrame *, int * );
+   void (* ClearIF)( AstDSBSpecFrame *, int * );
+   void (* SetIF)( AstDSBSpecFrame *, double, int * );
+
+   int (* GetSideBand)( AstDSBSpecFrame *, int * );
+   int (* TestSideBand)( AstDSBSpecFrame *, int * );
+   void (* ClearSideBand)( AstDSBSpecFrame *, int * );
+   void (* SetSideBand)( AstDSBSpecFrame *, int, int * );
+
+   int (* GetAlignSideBand)( AstDSBSpecFrame *, int * );
+   int (* TestAlignSideBand)( AstDSBSpecFrame *, int * );
+   void (* ClearAlignSideBand)( AstDSBSpecFrame *, int * );
+   void (* SetAlignSideBand)( AstDSBSpecFrame *, int, int * );
+
+   double (* GetImagFreq)( AstDSBSpecFrame *, int * );
+
+} AstDSBSpecFrameVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstDSBSpecFrameGlobals {
+   AstDSBSpecFrameVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 101 ];
+   char GetLabel_Buff[ 101 ];
+} AstDSBSpecFrameGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(DSBSpecFrame)          /* Check class membership */
+astPROTO_ISA(DSBSpecFrame)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstDSBSpecFrame *astDSBSpecFrame_( const char *, int *, ...);
+#else
+AstDSBSpecFrame *astDSBSpecFrameId_( const char *, ... )__attribute__((format(printf,1,2)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstDSBSpecFrame *astInitDSBSpecFrame_( void *, size_t, int, AstDSBSpecFrameVtab *,
+                                       const char *, int * );
+
+/* Vtab initialiser. */
+void astInitDSBSpecFrameVtab_( AstDSBSpecFrameVtab *, const char *, int * );
+
+/* Loader. */
+AstDSBSpecFrame *astLoadDSBSpecFrame_( void *, size_t, AstDSBSpecFrameVtab *,
+                                       const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitDSBSpecFrameGlobals_( AstDSBSpecFrameGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+   double astGetDSBCentre_( AstDSBSpecFrame *, int * );
+   int astTestDSBCentre_( AstDSBSpecFrame *, int * );
+   void astClearDSBCentre_( AstDSBSpecFrame *, int * );
+   void astSetDSBCentre_( AstDSBSpecFrame *, double, int * );
+
+   double astGetIF_( AstDSBSpecFrame *, int * );
+   int astTestIF_( AstDSBSpecFrame *, int * );
+   void astClearIF_( AstDSBSpecFrame *, int * );
+   void astSetIF_( AstDSBSpecFrame *, double, int * );
+
+   int astGetSideBand_( AstDSBSpecFrame *, int * );
+   int astTestSideBand_( AstDSBSpecFrame *, int * );
+   void astClearSideBand_( AstDSBSpecFrame *, int * );
+   void astSetSideBand_( AstDSBSpecFrame *, int, int * );
+
+   int astGetAlignSideBand_( AstDSBSpecFrame *, int * );
+   int astTestAlignSideBand_( AstDSBSpecFrame *, int * );
+   void astClearAlignSideBand_( AstDSBSpecFrame *, int * );
+   void astSetAlignSideBand_( AstDSBSpecFrame *, int, int * );
+
+   double astGetImagFreq_( AstDSBSpecFrame *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckDSBSpecFrame(this) astINVOKE_CHECK(DSBSpecFrame,this,0)
+#define astVerifyDSBSpecFrame(this) astINVOKE_CHECK(DSBSpecFrame,this,1)
+
+/* Test class membership. */
+#define astIsADSBSpecFrame(this) astINVOKE_ISA(DSBSpecFrame,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astDSBSpecFrame astINVOKE(F,astDSBSpecFrame_)
+#else
+#define astDSBSpecFrame astINVOKE(F,astDSBSpecFrameId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define \
+astInitDSBSpecFrame(mem,size,init,vtab,name) \
+astINVOKE(O,astInitDSBSpecFrame_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitDSBSpecFrameVtab(vtab,name) astINVOKE(V,astInitDSBSpecFrameVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadDSBSpecFrame(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadDSBSpecFrame_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckDSBSpecFrame to validate DSBSpecFrame pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+
+#define astGetDSBCentre(this) \
+astINVOKE(V,astGetDSBCentre_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astTestDSBCentre(this) \
+astINVOKE(V,astTestDSBCentre_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astClearDSBCentre(this) \
+astINVOKE(V,astClearDSBCentre_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astSetDSBCentre(this,val) \
+astINVOKE(V,astSetDSBCentre_(astCheckDSBSpecFrame(this),val,STATUS_PTR))
+
+#define astGetIF(this) \
+astINVOKE(V,astGetIF_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astTestIF(this) \
+astINVOKE(V,astTestIF_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astClearIF(this) \
+astINVOKE(V,astClearIF_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astSetIF(this,val) \
+astINVOKE(V,astSetIF_(astCheckDSBSpecFrame(this),val,STATUS_PTR))
+
+#define astGetSideBand(this) \
+astINVOKE(V,astGetSideBand_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astTestSideBand(this) \
+astINVOKE(V,astTestSideBand_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astClearSideBand(this) \
+astINVOKE(V,astClearSideBand_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astSetSideBand(this,val) \
+astINVOKE(V,astSetSideBand_(astCheckDSBSpecFrame(this),val,STATUS_PTR))
+
+#define astGetAlignSideBand(this) \
+astINVOKE(V,astGetAlignSideBand_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astTestAlignSideBand(this) \
+astINVOKE(V,astTestAlignSideBand_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astClearAlignSideBand(this) \
+astINVOKE(V,astClearAlignSideBand_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#define astSetAlignSideBand(this,val) \
+astINVOKE(V,astSetAlignSideBand_(astCheckDSBSpecFrame(this),val,STATUS_PTR))
+
+#define astGetImagFreq(this) \
+astINVOKE(V,astGetImagFreq_(astCheckDSBSpecFrame(this),STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/dssmap.c b/ast/dssmap.c
new file mode 100644
index 0000000..b869d57
--- /dev/null
+++ b/ast/dssmap.c
@@ -0,0 +1,2283 @@
+/*
+*class++
+*  Name:
+*     DssMap
+
+*  Purpose:
+*     Map points using a Digitised Sky Survey plate solution.
+
+*  Constructor Function:
+*     The DssMap class does not have a constructor function.  A DssMap
+*     is created only as a by-product of reading a FrameSet (using
+c     astRead) from a FitsChan which contains FITS header cards
+f     AST_READ) from a FitsChan which contains FITS header cards
+*     describing a DSS plate solution, and whose Encoding attribute is
+*     set to "DSS". The result of such a read, if successful, is a
+*     FrameSet whose base and current Frames are related by a DssMap.
+
+*  Description:
+*     The DssMap class implements a Mapping which transforms between
+*     2-dimensional pixel coordinates and an equatorial sky coordinate
+*     system (right ascension and declination) using a Digitised Sky
+*     Survey (DSS) astrometric plate solution.
+*
+*     The input coordinates are pixel numbers along the first and
+*     second dimensions of an image, where the centre of the first
+*     pixel is located at (1,1) and the spacing between pixel centres
+*     is unity.
+*
+*     The output coordinates are right ascension and declination in
+*     radians. The celestial coordinate system used (FK4, FK5, etc.)
+*     is unspecified, and will usually be indicated by appropriate
+*     keywords in a FITS header.
+
+*  Inheritance:
+*     The DssMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The DssMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     The DssMap class does not define any new functions beyond those
+f     The DssMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+*     (except for code supplied by Doug Mink, as noted in this file)
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+*     RFWS: R.F. Warren-Smith (Starlink, RAL)
+
+*  History:
+*     18-FEB-1997 (DSB):
+*        Original version.
+*     30-JUN-1997 (DSB):
+*        astDssFits and astDssMap made protected instead of public.
+*     15-JUL-1997 (RFWS):
+*        Tidied public prologues.
+*     5-SEP-197 (RFWS):
+*        Added prototypes for platepos and platepix.
+*     4-NOV-1997 (DSB):
+*        o  A copy of the supplied FitsChan is no longer stored inside
+*        the DssMap. The FitsChan returned by DssFits is now derived from
+*        the wcs information stored in the SAOimage "WorldCoor" structure
+*        (stored within the DssMap), and only contains the keywords
+*        necessary to reconstruct the DssMap.
+*        o  The external representation of a DssMap is now stored in a set
+*        of scalar values, rather than a FitsChan.
+*     22-DEC-1997 (DSB):
+*        Bug fixed in MapMerge which caused a core dump when a
+*        DssMap/WinMap combination is succesfully simplified.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitDssMapVtab
+*        method.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS DssMap
+
+/* Macro which returns the nearest integer to a given floating point
+   value. */
+#define NINT(x) (int)((x)+(((x)>0.0)?0.5:-0.5))
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "memory.h"              /* Memory allocation facilities */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "fitschan.h"            /* Manipulation of FITS header cards */
+#include "wcsmap.h"              /* Degrees/radians conversion factors */
+#include "winmap.h"              /* Shift and scale mappings */
+#include "dssmap.h"              /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(DssMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(DssMap,Class_Init)
+#define class_vtab astGLOBAL(DssMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstDssMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstFitsChan *DssFits( AstDssMap *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int platepix( double, double, struct WorldCoor *, double *, double * );
+static int platepos( double, double, struct WorldCoor *, double *, double * );
+static struct WorldCoor *BuildWcs( AstFitsChan *, const char *, const char *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *obj, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+
+static int GetObjSize( AstObject *, int * );
+/* Member functions. */
+/* ================= */
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two DssMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dssmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     DssMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two DssMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a DssMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the DssMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstDssMap *that;
+   AstDssMap *this;
+   int i;
+   int nin;
+   int nout;
+   int result;
+   struct WorldCoor *this_wcs;
+   struct WorldCoor *that_wcs;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two DssMap structures. */
+   this = (AstDssMap *) this_object;
+   that = (AstDssMap *) that_object;
+
+/* Check the second object is a DssMap. We know the first is a
+   DssMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsADssMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two DssMaps differ, it may still be possible
+   for them to be equivalent. First compare the DssMaps if their Invert
+   flags are the same. In this case all the attributes of the two DssMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+
+            this_wcs = ( struct WorldCoor *) this->wcs;
+            that_wcs = ( struct WorldCoor *) that->wcs;
+
+            if( this_wcs->x_pixel_offset == that_wcs->x_pixel_offset &&
+                this_wcs->y_pixel_offset == that_wcs->y_pixel_offset &&
+                this_wcs->ppo_coeff[2] == that_wcs->ppo_coeff[2] &&
+                this_wcs->ppo_coeff[5] == that_wcs->ppo_coeff[5] &&
+                this_wcs->x_pixel_size == that_wcs->x_pixel_size &&
+                this_wcs->y_pixel_size == that_wcs->y_pixel_size &&
+                this_wcs->plate_dec == that_wcs->plate_dec &&
+                this_wcs->plate_ra == that_wcs->plate_ra ) {
+
+                result = 1;
+                for( i = 0; i < 13; i++ ) {
+                   if( this_wcs->amd_x_coeff[i] != that_wcs->amd_x_coeff[i] ||
+                       this_wcs->amd_y_coeff[i] != that_wcs->amd_y_coeff[i] ) {
+                      result = 0;
+                      break;
+                   }
+                }
+
+             }
+
+/* If the Invert flags for the two DssMaps differ, the attributes of the two
+   DssMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a DssMap, Invert flags must be equal. */
+            result = 0;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dssmap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     DssMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied DssMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the DssMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstDssMap *this;         /* Pointer to DssMap structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the DssMap structure. */
+   this = (AstDssMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astTSizeOf( this->wcs );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+
+static struct WorldCoor *BuildWcs( AstFitsChan *fits, const char *method,
+                                   const char *class, int *status ) {
+/*
+*  Name:
+*     BuildWcs
+
+*  Purpose:
+*     Copy DSS plate fit information from a FitsChan to a SAOimage
+*     WorldCoor structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dssmap.h"
+*     struct WorldCoor *BuildWcs( AstFitsChan *fits, const char *method,
+*                                 const char *class )
+
+*  Class Membership:
+*     DssMap member function.
+
+*  Description:
+*     This creates a WorldCoor structure and copies the required data
+*     from the supplied FitsChan into the new WorldCoor structure. Note,
+*     only those components of the WorldCoor structure which are needed to
+*     transform between pixel and sky coordinates are initialised in the
+*     returned structure.
+
+*  Parameters:
+*     fits
+*        Pointer to the FitsChan containing the FITS header describing
+*        the DSS plate fit.
+*     method
+*        The calling method (for error messages).
+*     class
+*        The object class (for error messages).
+
+*  Returned Value:
+*     A pointer to the new WorldCoor structure. This should be freed
+*     using astFree when no longer needed.
+
+*  Notes:
+*     -  A NULL pointer is returned if an error has already occurred, or
+*     if this function should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   char name_buff[ 10 ];          /* Buffer for keyword name */
+   char *name;                    /* Pointer to jeyword name string */
+   char *ckeyval;                 /* Pointer to string keyword value */
+   struct WorldCoor *ret;         /* Pointer to the returned structure */
+   double rah,ram,ras;            /* Centre RA hours, minutes and seconds */
+   double dsign;                  /* Sign of centre dec */
+   double decd,decm,decs;         /* Centre Dec degrees, minutes, seconds */
+   double dec_deg;                /* Centre Dec in degrees */
+   double ra_hours;               /* Centre RA in hours */
+   int i;                         /* Coefficient index */
+
+/* Check the local error status. */
+   if ( !astOK ) return NULL;
+
+/* Get memory to hold the returned structure. */
+   ret = (struct WorldCoor *) astMalloc( sizeof( struct WorldCoor ) );
+
+/* Check the memory can be used. */
+   if( astOK ){
+
+/* The following code is based on the "wcsinit" function in SAOimage file
+   wcs.c. Note, only the values needed in the platepos and platepix
+   functions are set up. The FITS keywords are accessed in the order in
+   which they are usually stored in a FITS file. This will cut down the
+   time spent searching for keywords. Report an error if any required
+   keyword is not found. */
+
+/* Plate center RA */
+      rah = 0.0;
+      ram = 0.0;
+      ras = 0.0;
+
+      name = "PLTRAH";
+      if( !astGetFitsF( fits, name, &rah ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                   "FITS keyword '%s'.", status, method, class, name );
+      }
+
+      name = "PLTRAM";
+      if( !astGetFitsF( fits, name, &ram ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                  "FITS keyword '%s'.", status, method, class, name );
+      }
+
+      name = "PLTRAS";
+      if( !astGetFitsF( fits, name, &ras ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                  "FITS keyword '%s'.", status, method, class, name );
+      }
+
+      ra_hours = rah + (ram / (double)60.0) + (ras / (double)3600.0);
+      ret->plate_ra = AST__DD2R*15.0*ra_hours;
+
+
+/* Plate center Dec */
+      name = "PLTDECSN";
+      if( !astGetFitsS( fits, name, &ckeyval ) && astOK ){
+         dsign = 1.0;
+
+      } else {
+         if( *ckeyval == '-' ){
+            dsign = -1.0;
+         } else {
+            dsign = 1.0;
+         }
+
+      }
+
+      decd = 0.0;
+      decm = 0.0;
+      decs = 0.0;
+
+      name = "PLTDECD";
+      if( !astGetFitsF( fits, name, &decd ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                  "FITS keyword '%s'.", status, method, class, name );
+      }
+
+      name = "PLTDECM";
+      if( !astGetFitsF( fits, name, &decm ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                  "FITS keyword '%s'.", status, method, class, name );
+      }
+
+      name = "PLTDECS";
+      if( !astGetFitsF( fits, name, &decs ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                  "FITS keyword '%s'.", status, method, class, name );
+      }
+
+      dec_deg = dsign * (decd+(decm/(double)60.0)+(decs/(double)3600.0));
+      ret->plate_dec = AST__DD2R*dec_deg;
+
+/* Plate Scale arcsec per mm  */
+      name = "PLTSCALE";
+      if( !astGetFitsF( fits, name, &ret->plate_scale ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                  "FITS keyword '%s'.", status, method, class, name );
+      }
+
+/* X and Y corners (in pixels) */
+      name = "CNPIX1";
+      if( !astGetFitsF( fits, name, &ret->x_pixel_offset ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                  "FITS keyword '%s'.", status, method, class, name );
+      }
+
+      name = "CNPIX2";
+      if( !astGetFitsF( fits, name, &ret->y_pixel_offset ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                  "FITS keyword '%s'.", status, method, class, name );
+      }
+
+/* X and Y pixel sizes (microns). */
+      name = "XPIXELSZ";
+      if( !astGetFitsF( fits, name, &ret->x_pixel_size ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                  "FITS keyword '%s'.", status, method, class, name );
+      }
+
+      name = "YPIXELSZ";
+      if( !astGetFitsF( fits, name, &ret->y_pixel_size ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): No value has been supplied for the "
+                  "FITS keyword '%s'.", status, method, class, name );
+      }
+
+/* Orientation Coefficients. Only report an error if PPO3 or PPO6 are
+   missing (these are the only two which are actually used). Assume a
+   value of zero for any of the others which are missing. */
+      name = name_buff;
+      for ( i = 0; i < 6; i++ ) {
+         sprintf( name_buff, "PPO%d", i + 1 );
+         if( !astGetFitsF( fits, name, &ret->ppo_coeff[i] ) ) {
+            ret->ppo_coeff[i] = 0.0;
+            if( ( i == 2 || i == 5 ) && astOK ) {
+               astError( AST__BDFTS, "%s(%s): No value has been supplied "
+                         "for the FITS keyword '%s'.", status, method, class,
+                         name );
+               break;
+            }
+         }
+      }
+
+/* Plate solution x and y coefficients. Report an error if any of
+   coefficients 1 to 14 are missing. Assume a value of zero for any
+   others which are missing.  */
+      name = name_buff;
+      for( i = 0; i < 19; i++ ){
+         sprintf( name_buff, "AMDX%d", i + 1 );
+         if( !astGetFitsF( fits, name, &ret->amd_x_coeff[i] ) ) {
+            ret->amd_x_coeff[i] = 0.0;
+            if( i < 13 && astOK ){
+               astError( AST__BDFTS, "%s(%s): No value has been supplied "
+                         "for the FITS keyword '%s'.", status, method, class, name );
+               break;
+            }
+         }
+      }
+
+      for( i = 0; i < 19; i++ ){
+         sprintf( name_buff, "AMDY%d", i + 1 );
+         if( !astGetFitsF( fits, name, &ret->amd_y_coeff[i] ) ){
+            ret->amd_y_coeff[i] = 0.0;
+            if( i < 13 && astOK ){
+               astError( AST__BDFTS, "%s(%s): No value has been supplied "
+                         "for the FITS keyword '%s'.", status, method, class, name );
+               break;
+            }
+         }
+      }
+
+/* If anything went wrong, free the returned structure. */
+      if( !astOK ) ret = (struct WorldCoor *) astFree( (void *) ret );
+   }
+
+/* Return the pointer. */
+   return ret;
+}
+
+static AstFitsChan *DssFits( AstDssMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     astDssFits
+
+*  Purpose:
+*     Return a pointer to a FitsChan describing a DssMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "dssmap.h"
+*     AstFitsChan *DssFits( AstDssMap *this )
+
+*  Class Membership:
+*     DssMap method.
+
+*  Description:
+*     This function returns a pointer to a DSS-encoded FitsChan containing
+*     cards generated from the information stored with the DssMap. The
+*     keywords contained in the FitsChan are those which would ne needed to
+*     re-create the DssMap (see astDSSMap).
+
+*  Parameters:
+*     this
+*        Pointer to the DssMap.
+
+*  Returned Value:
+*     astDssFits()
+*        A pointer to the FitsChan.
+
+*  Notes:
+*     - The returned pointer should be annuled using astAnnul when no longer
+*     needed.
+*     - A value of NULL will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFitsChan *ret;              /* Pointer to the returned FitsChan  */
+   char *comm;                    /* Pointer to keyword comment string */
+   char *name;                    /* Pointer to keyword name string */
+   char name_buff[ 10 ];          /* Buffer for keyword name */
+   double dec;                    /* Centre Dec in degrees */
+   double decd,decm,decs;         /* Centre Dec degrees, minutes, seconds */
+   double ra;                     /* Centre RA in hours */
+   double rah,ram,ras;            /* Centre RA hours, minutes and seconds */
+   int i;                         /* Coefficient index */
+   struct WorldCoor *wcs;         /* WCS information from the DssMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Store a pointer to the WCS information stored in the DSSMap. */
+   wcs = (struct WorldCoor *) this->wcs,
+
+/* Create a new empty FitsChan, using DSS encoding. */
+   ret = astFitsChan( NULL, NULL, "Encoding=DSS", status );
+
+/* Create the keyword values and stored them in the returned FitsChan... */
+
+/* Plate centre RA. */
+   ra = wcs->plate_ra/( AST__DD2R*15.0 );
+   ra = modf( ra, &rah );
+   ra = modf( 60.0*ra, &ram );
+   ras = 60.0*ra;
+
+   astSetFitsI( ret, "PLTRAH", NINT( rah ), "Plate centre RA", 0 );
+   astSetFitsI( ret, "PLTRAM", NINT( ram ), " ", 0 );
+   astSetFitsF( ret, "PLTRAS", ras, " ", 0 );
+
+/* Plate centre DEC. */
+   dec = wcs->plate_dec/AST__DD2R;
+   if( dec < 0.0 ) {
+      dec = -dec;
+      astSetFitsS( ret, "PLTDECSN", "-", "Plate centre DEC", 0 );
+   } else {
+      astSetFitsS( ret, "PLTDECSN", "+", "Plate centre DEC", 0 );
+   }
+
+   dec = modf( dec, &decd );
+   dec = modf( 60.0*dec, &decm );
+   decs = 60.0*dec;
+
+   astSetFitsI( ret, "PLTDECD", NINT( decd ), " ", 0 );
+   astSetFitsI( ret, "PLTDECM", NINT( decm ), " ", 0 );
+   astSetFitsF( ret, "PLTDECS", decs, " ", 0 );
+
+/* Plate Scale arcsec per mm  */
+   astSetFitsF( ret, "PLTSCALE", wcs->plate_scale, "Plate Scale arcsec per mm",
+                0 );
+
+/* X and Y corners (in pixels) */
+   astSetFitsI( ret, "CNPIX1", NINT( wcs->x_pixel_offset ),
+                "X corner  (pixels)", 0 );
+   astSetFitsI( ret, "CNPIX2", NINT( wcs->y_pixel_offset ),
+                "Y corner", 0 );
+
+/* X and Y pixel sizes (microns). */
+   astSetFitsF( ret, "XPIXELSZ", wcs->x_pixel_size,
+                "X pixel size (microns)", 0 );
+   astSetFitsF( ret, "YPIXELSZ", wcs->y_pixel_size,
+                "Y pixel size (microns)", 0 );
+
+/* Orientation Coefficients. */
+   name = name_buff;
+   comm = "Orientation Coefficients";
+   for ( i = 0; i < 6; i++ ) {
+      sprintf( name_buff, "PPO%d", i + 1 );
+      astSetFitsF( ret, name, wcs->ppo_coeff[i], comm, 0 );
+      comm = " ";
+   }
+
+/* Plate solution x and y coefficients. */
+   comm = "Plate solution x coefficients";
+   for( i = 0; i < 19; i++ ){
+      sprintf( name_buff, "AMDX%d", i + 1 );
+      astSetFitsF( ret, name, wcs->amd_x_coeff[i], comm, 0 );
+      comm = " ";
+   }
+
+   comm = "Plate solution y coefficients";
+   for( i = 0; i < 19; i++ ){
+      sprintf( name_buff, "AMDY%d", i + 1 );
+      astSetFitsF( ret, name, wcs->amd_y_coeff[i], comm, 0 );
+      comm = " ";
+   }
+
+/* Return a pointer to the FitsChan. */
+   return ret;
+}
+
+void astInitDssMapVtab_(  AstDssMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitDssMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a DssMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "dssmap.h"
+*     void astInitDssMapVtab( AstDssMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     DssMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the DssMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsADssMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->DssFits = DssFits;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   mapping = (AstMappingVtab *) vtab;
+   object = (AstObjectVtab *) vtab;
+
+   parent_transform = mapping->Transform;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the class dump, copy and delete function. */
+   astSetDump( object, Dump, "DssMap", "DSS plate fit mapping" );
+   astSetCopy( object, Copy );
+   astSetDelete( object, Delete );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a DssMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     DssMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated DssMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated DssMap with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated DssMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the DssMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        DssMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated DssMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstDssMap *dm;        /* Pointer to supplied DssMap */
+   AstDssMap *dmnew;     /* Pointer to replacement DssMap */
+   AstFitsChan *fits;    /* FITS headers for replacement DssMap */
+   AstFitsChan *fits_dss;/* FITS headers for supplied DssMap */
+   AstWinMap *wm;        /* Pointer to the adjacent WinMap */
+   double *a;            /* Pointer to shift terms */
+   double *b;            /* Pointer to scale terms */
+   double cnpix1;        /* X pixel origin */
+   double cnpix2;        /* Y pixel origin */
+   double xpixelsz;      /* X pixel size */
+   double ypixelsz;      /* Y pixel size */
+   int i;                /* Loop counter */
+   int ok;               /* All FITS keywords found? */
+   int old_winv;         /* original Invert value for supplied WinMap */
+   int result;           /* Result value to return */
+   int wmi;              /* Index of adjacent WinMap in map list */
+   struct WorldCoor *wcs;/* Pointer to SAOimage wcs structure */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* The only simplification easily possible is if a WinMap maps the pixel
+   coordinates prior to a DssMap. If the DssMap has not been inverted, the
+   WinMap must be applied before the DssMap, otherwise the WinMap must be
+   applied after the DssMap. */
+   if( series ){
+
+      if( !( *invert_list )[ where ] ){
+         wmi = where - 1;
+      } else {
+         wmi = where + 1;
+      }
+
+      if( wmi >= 0 && wmi < *nmap ){
+         if( !strcmp( astGetClass( ( *map_list )[ wmi ] ), "WinMap" ) ){
+
+/* Temporarily set the Invert attribute of the WinMap to the supplied value. */
+            wm = (AstWinMap *) ( *map_list )[ wmi ];
+            old_winv = astGetInvert( wm );
+            astSetInvert( wm, ( *invert_list )[ wmi ] );
+
+/* Get a copy of the scale and shift terms from the WinMap. */
+            astWinTerms( wm, &a, &b );
+
+/* Check that the scale and shift terms are usable. */
+            if( astOK &&
+                a[ 0 ] != AST__BAD && b[ 0 ] != AST__BAD && b[ 0 ] != 0.0 &&
+                a[ 1 ] != AST__BAD && b[ 1 ] != AST__BAD && b[ 1 ] != 0.0 ){
+
+/* Get the values of the keywords which define the origin and scales of
+   the DssMap pixel coordinate system. */
+               dm = (AstDssMap *) ( *map_list )[ where ];
+               wcs = (struct WorldCoor *) ( dm->wcs );
+
+               cnpix1 = wcs->x_pixel_offset;
+               cnpix2 = wcs->y_pixel_offset;
+               xpixelsz = wcs->x_pixel_size;
+               ypixelsz = wcs->y_pixel_size;
+
+/* Calculate new values which take into account the WinMap. */
+               if( wmi == where - 1 ){
+                  xpixelsz *= b[ 0 ];
+                  ypixelsz *= b[ 1 ];
+                  cnpix1 = 0.5 + ( cnpix1 + a[ 0 ] - 0.5 )/b[ 0 ];
+                  cnpix2 = 0.5 + ( cnpix2 + a[ 1 ] - 0.5 )/b[ 1 ];
+
+               } else {
+                  xpixelsz /= b[ 0 ];
+                  ypixelsz /= b[ 1 ];
+                  cnpix1 = b[ 0 ]*( cnpix1 - 0.5 ) - a[ 0 ] + 0.5;
+                  cnpix2 = b[ 1 ]*( cnpix2 - 0.5 ) - a[ 1 ] + 0.5;
+               }
+
+/* The CNPIX1 and CNPIX2 keywords are integer keywords. Therefore, we can
+   only do the simplification if the new values are integer to a good
+   approximation. We use one hundredth of a pixel. */
+               if( fabs( cnpix1 - NINT( cnpix1 ) ) < 0.01 &&
+                   fabs( cnpix2 - NINT( cnpix2 ) ) < 0.01 ){
+
+/* Get a copy of the FitsChan holding the header cards which define the
+   DssMap. */
+                  fits_dss = astDssFits( dm );
+                  fits = astCopy( fits_dss );
+                  fits_dss = astAnnul( fits_dss );
+
+/* Update the value of each of the changed keywords. */
+                  ok = 1;
+
+                  astClearCard( fits );
+                  if( astFindFits( fits, "CNPIX1", NULL, 0 ) ){
+                     astSetFitsI( fits, "CNPIX1", NINT( cnpix1 ), NULL, 1 );
+                  } else {
+                     ok = 0;
+                  }
+
+                  astClearCard( fits );
+                  if( astFindFits( fits, "CNPIX2", NULL, 0 ) ){
+                     astSetFitsI( fits, "CNPIX2", NINT( cnpix2 ), NULL, 1 );
+                  } else {
+                     ok = 0;
+                  }
+
+                  astClearCard( fits );
+                  if( astFindFits( fits, "XPIXELSZ", NULL, 0 ) ){
+                     astSetFitsF( fits, "XPIXELSZ", xpixelsz, NULL, 1 );
+                  } else {
+                     ok = 0;
+                  }
+
+                  astClearCard( fits );
+                  if( astFindFits( fits, "YPIXELSZ", NULL, 0 ) ){
+                     astSetFitsF( fits, "YPIXELSZ", ypixelsz, NULL, 1 );
+                  } else {
+                     ok = 0;
+                  }
+
+/* If all the keywords were updated succesfully, create the new DssMap
+   based on the modified FITS header cards. */
+                  if( ok ){
+		     dmnew = astDssMap( fits, "", status );
+
+/* Anull the DssMap pointer in the list and replace it with the new one.
+   The invert flag is left unchanged. */
+                     dm = astAnnul( dm );
+                     ( *map_list )[ where ] = (AstMapping *) dmnew;
+
+/* Annul the WinMap pointer in the list, and shuffle any remaining
+   Mappings down to fill the gap. */
+                     wm = astAnnul( wm );
+                     for ( i = wmi + 1; i < *nmap; i++ ) {
+                        ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+                        ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+                     }
+
+/* Clear the vacated element at the end. */
+                     ( *map_list )[ *nmap - 1 ] = NULL;
+                     ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+                     ( *nmap )--;
+                     result = astMIN( wmi, where );
+
+                  }
+
+/* Annul the FitsChan holding the modified header cards. */
+                  fits = astAnnul( fits );
+               }
+            }
+
+/* Free the arrays holding scale and shift terms from the WinMap. */
+            a = (double *) astFree( (void *) a );
+            b = (double *) astFree( (void *) b );
+
+/* Reinstate the original setting of the Invert attribute of the WinMap (if
+   it still exists). */
+            if( wm ) astSetInvert( wm, old_winv );
+
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a DssMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "dssmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     DssMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a DssMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required DSS
+*     plate fit.
+
+*  Parameters:
+*     this
+*        Pointer to the DssMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     be 2.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstDssMap *map;               /* Pointer to DssMap to be applied */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *aa;                   /* Pointer to next longitude value */
+   double *bb;                   /* Pointer to next latitude value */
+   double *xx;                   /* Pointer to next pixel X value */
+   double *yy;                   /* Pointer to next pixel Y value */
+   int npoint;                   /* Number of points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the DssMap. */
+   map = (AstDssMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points from the input PointSet and obtain
+   pointers for accessing the input and output coordinate values. */
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse mapping, according to the
+   direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+
+/* First deal with forward transformations. */
+      if( forward ){
+
+/* Store pointers to the next value on each axis. */
+         xx = ptr_in[ 0 ];
+         yy = ptr_in[ 1 ];
+         aa = ptr_out[ 0 ];
+         bb = ptr_out[ 1 ];
+
+/* Loop to apply the plate fit to all the points, checking for (and
+   propagating) bad values in the process. */
+         for ( point = 0; point < npoint; point++ ) {
+            if( *xx != AST__BAD && *yy != AST__BAD ){
+
+/* If the pixel position is transformed succesfully, convert the returned
+   RA/DEC from degrees to radians. Otherwise, store bad values. NB,
+   platepos returns zero for success. */
+               if( !platepos( *xx, *yy, (struct WorldCoor *) map->wcs,
+                              aa, bb ) ){
+                  (*aa) *= AST__DD2R;
+                  (*bb) *= AST__DD2R;
+
+               } else {
+                  *aa = AST__BAD;
+                  *bb = AST__BAD;
+               }
+
+            } else {
+               *aa = AST__BAD;
+               *bb = AST__BAD;
+            }
+
+/* Move on to the next point. */
+            xx++;
+            yy++;
+            aa++;
+            bb++;
+         }
+
+/* Now deal with inverse transformations in the same way. */
+      } else {
+         aa = ptr_in[ 0 ];
+         bb = ptr_in[ 1 ];
+         xx = ptr_out[ 0 ];
+         yy = ptr_out[ 1 ];
+
+         for ( point = 0; point < npoint; point++ ) {
+            if( *aa != AST__BAD && *bb != AST__BAD ){
+
+               if( platepix( AST__DR2D*(*aa), AST__DR2D*(*bb),
+                             (struct WorldCoor *) map->wcs, xx, yy ) ){
+                  *xx = AST__BAD;
+                  *yy = AST__BAD;
+               }
+
+            } else {
+               *xx = AST__BAD;
+               *yy = AST__BAD;
+            }
+
+            xx++;
+            yy++;
+            aa++;
+            bb++;
+         }
+      }
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for DssMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for DssMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+
+/* Local Variables: */
+   AstDssMap *in;                /* Pointer to input DssMap */
+   AstDssMap *out;               /* Pointer to output DssMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output DssMaps. */
+   in = (AstDssMap *) objin;
+   out = (AstDssMap *) objout;
+
+/* Store a copy of the input SAOIMAGE WorldCoor structure in the output. */
+   out->wcs = astStore( NULL, in->wcs, sizeof( struct WorldCoor ) );
+
+   return;
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for DssMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for DssMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstDssMap *this;            /* Pointer to DssMap */
+
+/* Obtain a pointer to the DssMap structure. */
+   this = (AstDssMap *) obj;
+
+/* Free the SAOIMAGE WorldCoor structure. */
+   this->wcs = astFree( this->wcs );
+
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for DssMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the DssMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the DssMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+   AstDssMap *this;          /* Pointer to the DssMap structure */
+   struct WorldCoor *wcs;    /* Pointer to SAOimage wcs structure */
+   char name_buff[ 11 ];     /* Buffer for keyword string */
+   int i;                    /* Coefficient index */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the DssMap structure. */
+   this = (AstDssMap *) this_object;
+
+/* Get a pointer to the WorldCoor structure holding the description of the
+   DssMap. */
+   wcs = (struct WorldCoor *) ( this->wcs );
+
+/* Write out values representing the contents of the WorldCoor structure.
+   Only the components which are required to re-create the DssMap are
+   written out. */
+   astWriteDouble( channel, "PltRA", 1, 1, wcs->plate_ra, "Plate centre RA (radians)" );
+   astWriteDouble( channel, "PltDec", 1, 1, wcs->plate_dec, "Plate centre Dec (radians)" );
+   astWriteDouble( channel, "PltScl", 1, 1, wcs->plate_scale, "Plate scale (arcsec/mm)" );
+   astWriteDouble( channel, "CNPix1", 1, 1, wcs->x_pixel_offset, "X Pixel offset (pixels)" );
+   astWriteDouble( channel, "CNPix2", 1, 1, wcs->y_pixel_offset, "Y Pixel offset (pixels)" );
+   astWriteDouble( channel, "XPixSz", 1, 1, wcs->x_pixel_size, "X Pixel size (microns)" );
+   astWriteDouble( channel, "YPixSz", 1, 1, wcs->y_pixel_size, "Y Pixel size (microns)" );
+
+   for( i = 0; i < 6; i++ ) {
+      sprintf( name_buff, "PPO%d", i + 1 );
+      astWriteDouble( channel, name_buff, 1, 1, wcs->ppo_coeff[i],
+                      "Orientation coefficients" );
+   }
+
+   for( i = 0; i < 19; i++ ) {
+      sprintf( name_buff, "AMDX%d", i + 1 );
+      astWriteDouble( channel, name_buff, 1, 1, wcs->amd_x_coeff[i],
+                      "Plate solution X coefficients" );
+   }
+
+   for( i = 0; i < 19; i++ ) {
+      sprintf( name_buff, "AMDY%d", i + 1 );
+      astWriteDouble( channel, name_buff, 1, 1, wcs->amd_y_coeff[i],
+                      "Plate solution Y coefficients" );
+   }
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsADssMap and astCheckDssMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(DssMap,Mapping)
+astMAKE_CHECK(DssMap)
+
+AstDssMap *astDssMap_( void *fits_void, const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astDssMap
+
+*  Purpose:
+*     Create a DssMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "dssmap.h"
+*     AstDssMap *astDssMap( AstFitsChan *fits, const char *options, int *status, ... )
+
+*  Class Membership:
+*     DssMap constructor.
+
+*  Description:
+*     This function creates a new DssMap and optionally initialises its
+*     attributes.
+*
+*     A DssMap is a Mapping which uses a Digitised Sky Survey plate fit to
+*     transform a set of points from pixel coordinates to equatorial
+*     coordinates (i.e. Right Ascension and Declination).
+
+*  Parameters:
+*     fits
+*        A pointer to a FitsChan holding a set of FITS header cards
+*        describing the plate fit to be used. The FitsChan may contain
+*        other header cards which will be ignored, and it is unchanged on
+*        exit. The required information is copied from the FitsChan, and
+*        so the supplied FitsChan may subsequently be changed or deleted
+*        without changing the DssMap.
+*     options
+*        Pointer to a null-terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new DssMap. The syntax used is identical to
+*        that for the astSet function and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of additional arguments may follow it in
+*        order to supply values to be substituted for these
+*        specifiers. The rules for supplying these are identical to
+*        those for the astSet function (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     astDssMap()
+*        A pointer to the new DssMap.
+
+*  Attributes:
+*     The DssMap class has no additional attributes over and above those
+*     common to all Mappings.
+
+*  Notes:
+*     - The supplied FitsChan must contain values for the following FITS
+*     keywords: CNPIX1, CNPIX2, PPO3, PPO6, XPIXELSZ, YPIXELSZ, PLTRAH,
+*     PLTRAM, PLTRAS, PLTDECD, PLTDECM, PLTDECS, PLTDECSN, PLTSCALE,
+*     AMDX1, AMDX2, ..., AMDX13, AMDY1, AMDY2, ..., AMDY13.
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFitsChan *fits;            /* Pointer to supplied FitsChan */
+   AstDssMap *new;               /* Pointer to new DssMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   new = NULL;
+   if ( !astOK ) return new;
+
+/* Obtain and validate a pointer to the FitsChan structure provided. */
+   fits = astCheckFitsChan( fits_void );
+   if ( astOK ) {
+
+/* Initialise the DssMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitDssMap( NULL, sizeof( AstDssMap ), !class_init, &class_vtab,
+                           "DssMap", fits );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new DssMap's attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new DssMap. */
+   return new;
+}
+
+AstDssMap *astInitDssMap_( void *mem, size_t size, int init,
+                           AstDssMapVtab *vtab, const char *name,
+                           AstFitsChan *fits, int *status ) {
+/*
+*+
+*  Name:
+*     astInitDssMap
+
+*  Purpose:
+*     Initialise a DssMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "dssmap.h"
+*     AstDssMap *astInitDssMap( void *mem, size_t size, int init,
+*                               AstDssMapVtab *vtab, const char *name,
+*                               AstFitsChan *fits )
+
+*  Class Membership:
+*     DssMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new DssMap object. It allocates memory (if necessary) to accommodate
+*     the DssMap plus any additional data associated with the derived class.
+*     It then initialises a DssMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a DssMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the DssMap is to be initialised.
+*        This must be of sufficient size to accommodate the DssMap data
+*        (sizeof(DssMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the DssMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the DssMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the DssMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new DssMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     fits
+*        Pointer to a FitsChan containing the DSS FITS Header.
+
+*  Returned Value:
+*     A pointer to the new DssMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstDssMap *new;              /* Pointer to new DssMap */
+   struct WorldCoor *wcs;       /* Pointer to SAOIMAGE wcs structure */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitDssMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Create a structure holding the information required by the SAOIMAGE
+   "platepos" function. The required values are extracted from the
+   supplied FitsChan. An error is reported and NULL returned if any required
+   keywords are missing or unusable. */
+   if ( ( wcs = BuildWcs( fits, "astInitDssMap", name, status ) ) ) {
+
+/* Initialise a 2-D Mapping structure (the parent class) as the first component
+   within the DssMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+      new = (AstDssMap *) astInitMapping( mem, size, 0,
+                                          (AstMappingVtab *) vtab, name,
+                                          2, 2, 1, 1 );
+
+      if ( astOK ) {
+
+/* Initialise the DssMap data. */
+/* --------------------------- */
+/* Store a copy of the SAOIMAGE wcs structure. */
+         new->wcs = astStore( NULL, (void *) wcs, sizeof( struct WorldCoor ) );
+
+/* If an error occurred, clean up by deleting the new DssMap. */
+         if ( !astOK ) new = astDelete( new );
+      }
+
+/* Free the SAOIMAGE wcs structure. */
+      wcs = (struct WorldCoor *) astFree( (void *) wcs );
+
+   }
+
+/* Return a pointer to the new DssMap. */
+   return new;
+}
+
+AstDssMap *astLoadDssMap_( void *mem, size_t size,
+                             AstDssMapVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadDssMap
+
+*  Purpose:
+*     Load a DssMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "dssmap.h"
+*     AstDssMap *astLoadDssMap( void *mem, size_t size,
+*                                 AstDssMapVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     DssMap loader.
+
+*  Description:
+*     This function is provided to load a new DssMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     DssMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a DssMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the DssMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        DssMap data (sizeof(DssMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the DssMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the DssMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstDssMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new DssMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the DssMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "DssMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new DssMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstDssMap *new;              /* Pointer to the new DssMap */
+   char name_buff[ 11 ];        /* Buffer for item name */
+   int i;                       /* Coefficient index */
+   struct WorldCoor *wcs;       /* Pointer to Wcs information */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this DssMap. In this case the
+   DssMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstDssMap );
+      vtab = &class_vtab;
+      name = "DssMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitDssMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built DssMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "DssMap" );
+
+/* Allocate memory to hold the WorldCoor structure which holds the wcs
+   information in a form usable by the SAOimage projection functions. */
+      new->wcs = astMalloc( sizeof(struct WorldCoor) );
+      if( astOK ) {
+
+/* Get a pointer to the WorldCoor structure holding the description of the
+   DssMap. */
+         wcs = (struct WorldCoor *) ( new->wcs );
+
+/* Read the values representing the contents of the WorldCoor structure.
+   Only the components which are required to re-create the DssMap are
+   read. */
+         wcs->plate_ra = astReadDouble( channel, "pltra", AST__BAD );
+         if( wcs->plate_ra == AST__BAD && astOK ){
+            astError( AST__RDERR, "astRead(DssMap): 'PltRA' object (Plate "
+                      "centre RA) missing from input." , status);
+         }
+
+         wcs->plate_dec = astReadDouble( channel, "pltdec", AST__BAD );
+         if( wcs->plate_dec == AST__BAD && astOK ){
+            astError( AST__RDERR, "astRead(DssMap): 'PltDec' object (Plate "
+                      "centre Dec) missing from input." , status);
+         }
+
+         wcs->plate_scale = astReadDouble( channel, "pltscl", AST__BAD );
+         if( wcs->plate_scale == AST__BAD && astOK ){
+            astError( AST__RDERR, "astRead(DssMap): 'PltScl' object (Plate "
+                      "scale) missing from input." , status);
+         }
+
+         wcs->x_pixel_offset = astReadDouble( channel, "cnpix1", AST__BAD );
+         if( wcs->x_pixel_offset == AST__BAD && astOK ){
+            astError( AST__RDERR, "astRead(DssMap): 'CNPix1' object (X pixel "
+                      "offset) missing from input." , status);
+         }
+
+         wcs->y_pixel_offset = astReadDouble( channel, "cnpix2", AST__BAD );
+         if( wcs->y_pixel_offset == AST__BAD && astOK ){
+            astError( AST__RDERR, "astRead(DssMap): 'CNPix2' object (Y pixel "
+                      "offset) missing from input." , status);
+         }
+
+         wcs->x_pixel_size = astReadDouble( channel, "xpixsz", AST__BAD );
+         if( wcs->x_pixel_size == AST__BAD && astOK ){
+            astError( AST__RDERR, "astRead(DssMap): 'XPixSz' object (X pixel "
+                      "size) missing from input." , status);
+         }
+
+         wcs->y_pixel_size = astReadDouble( channel, "ypixsz", AST__BAD );
+         if( wcs->y_pixel_size == AST__BAD && astOK ){
+            astError( AST__RDERR, "astRead(DssMap): 'YPixSz' object (Y pixel "
+                      "size) missing from input." , status);
+         }
+
+         for( i = 0; i < 6 && astOK; i++ ) {
+            sprintf( name_buff, "ppo%d", i + 1 );
+            wcs->ppo_coeff[i] = astReadDouble( channel, name_buff, AST__BAD );
+            if( wcs->ppo_coeff[i] == AST__BAD ){
+               if( i == 2 || i == 5 ) {
+                  if( astOK ) astError( AST__RDERR, "astRead(DssMap): 'PPO%d' "
+                                        "object (orientation coefficient %d) "
+                                        "missing from input.", status, i + 1, i + 1 );
+               } else {
+                  wcs->ppo_coeff[i] = 0.0;
+               }
+            }
+         }
+
+         for( i = 0; i < 19 && astOK; i++ ) {
+            sprintf( name_buff, "amdx%d", i + 1 );
+            wcs->amd_x_coeff[i] = astReadDouble( channel, name_buff, AST__BAD );
+            if( wcs->amd_x_coeff[i] == AST__BAD ){
+               if( i < 13 ){
+                  if( astOK ) astError( AST__RDERR, "astRead(DssMap): 'AMDX%d' "
+                                        "object (plate solution X coefficient "
+                                        "%d) missing from input.", status, i + 1, i + 1 );
+               } else {
+                  wcs->amd_x_coeff[i] = 0.0;
+               }
+            }
+         }
+
+         for( i = 0; i < 19 && astOK; i++ ) {
+            sprintf( name_buff, "amdy%d", i + 1 );
+            wcs->amd_y_coeff[i] = astReadDouble( channel, name_buff, AST__BAD );
+            if( wcs->amd_y_coeff[i] == AST__BAD ){
+               if( i < 13 ){
+                  if( astOK ) astError( AST__RDERR, "astRead(DssMap): 'AMDY%d' "
+                                        "object (plate solution Y coefficient "
+                                        "%d) missing from input.", status, i + 1, i + 1 );
+               } else {
+                  wcs->amd_y_coeff[i] = 0.0;
+               }
+            }
+         }
+      }
+
+/* If an error occurred, clean up by deleting the new DssMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new DssMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+AstFitsChan *astDssFits_( AstDssMap *this, int *status ){
+   if( !astOK ) return NULL;
+   return (**astMEMBER(this,DssMap,DssFits))( this, status );
+}
+
+/* The code which follows in this file is covered by the following
+   statement of terms and conditions, which differ from the terms and
+   conditions which apply above.
+
+***************************************************************************
+*
+* Copyright:   1988 Smithsonian Astrophysical Observatory
+*              You may do anything you like with these files except remove
+*              this copyright.  The Smithsonian Astrophysical Observatory
+*              makes no representations about the suitability of this
+*              software for any purpose.  It is provided "as is" without
+*              express or implied warranty.
+*
+*****************************************************************************
+*/
+
+/* >>>>>>>>>>>>>>>>>>>>>>  platepos.c <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
+
+/* File saoimage/wcslib/platepos.c
+ * February 25, 1996
+ * By Doug Mink, Harvard-Smithsonian Center for Astrophysics
+
+ * Module:	platepos.c (Plate solution WCS conversion
+ * Purpose:	Compute WCS from Digital Sky Survey plate fit
+ * Subroutine:	platepos() converts from pixel location to RA,Dec
+ * Subroutine:	platepix() converts from RA,Dec to pixel location
+
+    These functions are based on the astrmcal.c portion of GETIMAGE by
+    J. Doggett and the documentation distributed with the Digital Sky Survey.
+
+    >>>>>>>   STARLINK VERSION <<<<<<<<
+
+*/
+
+/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+  Changed by R.F. Warren-Smith (Starlink) to make the function static. */
+
+static int
+platepos (xpix, ypix, wcs, xpos, ypos)
+
+/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
+
+/* Routine to determine accurate position for pixel coordinates */
+/* returns 0 if successful otherwise 1 = angle too large for projection; */
+/* based on amdpos() from getimage */
+
+/* Input: */
+double	xpix;		/* x pixel number  (RA or long without rotation) */
+double	ypix;		/* y pixel number  (dec or lat without rotation) */
+struct WorldCoor *wcs;	/* WCS parameter structure */
+
+/* Output: */
+double	*xpos;		/* Right ascension or longitude in degrees */
+double	*ypos;		/* Declination or latitude in degrees */
+
+{
+  double x, y, xmm, ymm, xmm2, ymm2, xmm3, ymm3, x2y2;
+  double xi, xir, eta, etar, raoff, ra, dec;
+  double cond2r = 1.745329252e-2;
+  double cons2r = 206264.8062470964;
+  double twopi = 6.28318530717959;
+  double ctan, ccos;
+
+/*  Ignore magnitude and color terms
+  double mag = 0.0;
+  double color = 0.0; */
+
+/* Convert from image pixels to plate pixels */
+  x = xpix + wcs->x_pixel_offset - 1.0 + 0.5;
+  y = ypix + wcs->y_pixel_offset - 1.0 + 0.5;
+
+/* Convert from pixels to millimeters */
+  xmm = (wcs->ppo_coeff[2] - x * wcs->x_pixel_size) / 1000.0;
+  ymm = (y * wcs->y_pixel_size - wcs->ppo_coeff[5]) / 1000.0;
+  xmm2 = xmm * xmm;
+  ymm2 = ymm * ymm;
+  xmm3 = xmm * xmm2;
+  ymm3 = ymm * ymm2;
+  x2y2 = xmm2 + ymm2;
+
+/*  Compute coordinates from x,y and plate model */
+
+  xi =  wcs->amd_x_coeff[ 0]*xmm	+ wcs->amd_x_coeff[ 1]*ymm +
+	wcs->amd_x_coeff[ 2]		+ wcs->amd_x_coeff[ 3]*xmm2 +
+	wcs->amd_x_coeff[ 4]*xmm*ymm	+ wcs->amd_x_coeff[ 5]*ymm2 +
+	wcs->amd_x_coeff[ 6]*(x2y2)	+ wcs->amd_x_coeff[ 7]*xmm3 +
+	wcs->amd_x_coeff[ 8]*xmm2*ymm	+ wcs->amd_x_coeff[ 9]*xmm*ymm2 +
+	wcs->amd_x_coeff[10]*ymm3	+ wcs->amd_x_coeff[11]*xmm*(x2y2) +
+	wcs->amd_x_coeff[12]*xmm*x2y2*x2y2;
+
+/*  Ignore magnitude and color terms
+	+ wcs->amd_x_coeff[13]*mag	+ wcs->amd_x_coeff[14]*mag*mag +
+	wcs->amd_x_coeff[15]*mag*mag*mag + wcs->amd_x_coeff[16]*mag*xmm +
+	wcs->amd_x_coeff[17]*mag*x2y2	+ wcs->amd_x_coeff[18]*mag*xmm*x2y2 +
+	wcs->amd_x_coeff[19]*color; */
+
+  eta =	wcs->amd_y_coeff[ 0]*ymm	+ wcs->amd_y_coeff[ 1]*xmm +
+	wcs->amd_y_coeff[ 2]		+ wcs->amd_y_coeff[ 3]*ymm2 +
+	wcs->amd_y_coeff[ 4]*xmm*ymm	+ wcs->amd_y_coeff[ 5]*xmm2 +
+	wcs->amd_y_coeff[ 6]*(x2y2)	+ wcs->amd_y_coeff[ 7]*ymm3 +
+	wcs->amd_y_coeff[ 8]*ymm2*xmm	+ wcs->amd_y_coeff[ 9]*ymm*xmm2 +
+	wcs->amd_y_coeff[10]*xmm3	+ wcs->amd_y_coeff[11]*ymm*(x2y2) +
+	wcs->amd_y_coeff[12]*ymm*x2y2*x2y2;
+
+/*  Ignore magnitude and color terms
+	+ wcs->amd_y_coeff[13]*mag	+ wcs->amd_y_coeff[14]*mag*mag +
+	wcs->amd_y_coeff[15]*mag*mag*mag + wcs->amd_y_coeff[16]*mag*ymm +
+	wcs->amd_y_coeff[17]*mag*x2y2)	+ wcs->amd_y_coeff[18]*mag*ymm*x2y2 +
+	wcs->amd_y_coeff[19]*color; */
+
+/* Convert to radians */
+
+  xir = xi / cons2r;
+  etar = eta / cons2r;
+
+/* Convert to RA and Dec */
+
+  ctan = tan (wcs->plate_dec);
+  ccos = cos (wcs->plate_dec);
+  raoff = atan2 (xir / ccos, 1.0 - etar * ctan);
+  ra = raoff + wcs->plate_ra;
+  if (ra < 0.0) ra = ra + twopi;
+  *xpos = ra / cond2r;
+
+  dec = atan (cos (raoff) / ((1.0 - (etar * ctan)) / (etar + ctan)));
+  *ypos = dec / cond2r;
+  return 0;
+}
+
+
+/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+  Changed by R.F. Warren-Smith (Starlink) to make the function static. */
+
+static int
+platepix (xpos, ypos, wcs, xpix, ypix)
+
+/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
+
+/* Routine to determine pixel coordinates for sky position */
+/* returns 0 if successful otherwise 1 = angle too large for projection; */
+/* based on amdinv() from getimage */
+
+/* Input: */
+double	xpos;		/* Right ascension or longitude in degrees */
+double	ypos;		/* Declination or latitude in degrees */
+struct WorldCoor *wcs;	/* WCS parameter structure */
+
+/* Output: */
+double	*xpix;		/* x pixel number  (RA or long without rotation) */
+double	*ypix;		/* y pixel number  (dec or lat without rotation) */
+
+{
+  double div,xi,eta,x,y,xy,x2,y2,x2y,y2x,x3,y3,x4,y4,x2y2,cjunk,dx,dy;
+  double sypos,cypos,syplate,cyplate,sxdiff,cxdiff;
+  double f,fx,fy,g,gx,gy, xmm, ymm;
+  double conr2s = 206264.8062470964;
+  double tolerance = 0.0000005;
+  int    max_iterations = 50;
+  int    i;
+  double xr, yr; 	/* position in radians */
+  double cond2r = 1.745329252e-2;
+
+/* Convert RA and Dec in radians to standard coordinates on a plate */
+  xr = xpos * cond2r;
+  yr = ypos * cond2r;
+  sypos = sin (yr);
+  cypos = cos (yr);
+  syplate = sin (wcs->plate_dec);
+  cyplate = cos (wcs->plate_dec);
+  sxdiff = sin (xr - wcs->plate_ra);
+  cxdiff = cos (xr - wcs->plate_ra);
+  div = (sypos * syplate) + (cypos * cyplate * cxdiff);
+  xi = cypos * sxdiff * conr2s / div;
+  eta = ((sypos * cyplate) - (cypos * syplate * cxdiff)) * conr2s / div;
+
+/* Set initial value for x,y */
+  xmm = xi / wcs->plate_scale;
+  ymm = eta / wcs->plate_scale;
+
+/* Iterate by Newton's method */
+  for (i = 0; i < max_iterations; i++) {
+
+    /* X plate model */
+    xy = xmm * ymm;
+    x2 = xmm * xmm;
+    y2 = ymm * ymm;
+    x2y = x2 * ymm;
+    y2x = y2 * xmm;
+    x2y2 = x2 + y2;
+    cjunk = x2y2 * x2y2;
+    x3 = x2 * xmm;
+    y3 = y2 * ymm;
+    x4 = x2 * x2;
+    y4 = y2 * y2;
+    f = wcs->amd_x_coeff[0]*xmm      + wcs->amd_x_coeff[1]*ymm +
+        wcs->amd_x_coeff[2]          + wcs->amd_x_coeff[3]*x2 +
+        wcs->amd_x_coeff[4]*xy       + wcs->amd_x_coeff[5]*y2 +
+        wcs->amd_x_coeff[6]*x2y2     + wcs->amd_x_coeff[7]*x3 +
+        wcs->amd_x_coeff[8]*x2y      + wcs->amd_x_coeff[9]*y2x +
+        wcs->amd_x_coeff[10]*y3      + wcs->amd_x_coeff[11]*xmm*x2y2 +
+        wcs->amd_x_coeff[12]*xmm*cjunk;
+    /* magnitude and color terms ignored
+      + wcs->amd_x_coeff[13]*mag +
+        wcs->amd_x_coeff[14]*mag*mag   + wcs->amd_x_coeff[15]*mag*mag*mag +
+        wcs->amd_x_coeff[16]*mag*xmm   + wcs->amd_x_coeff[17]*mag*(x2+y2) +
+        wcs->amd_x_coeff[18]*mag*xmm*(x2+y2)  + wcs->amd_x_coeff[19]*color;
+    */
+
+    /*  Derivative of X model wrt x */
+    fx = wcs->amd_x_coeff[0]           + wcs->amd_x_coeff[3]*2.0*xmm +
+         wcs->amd_x_coeff[4]*ymm       + wcs->amd_x_coeff[6]*2.0*xmm +
+         wcs->amd_x_coeff[7]*3.0*x2    + wcs->amd_x_coeff[8]*2.0*xy +
+         wcs->amd_x_coeff[9]*y2        + wcs->amd_x_coeff[11]*(3.0*x2+y2) +
+         wcs->amd_x_coeff[12]*(5.0*x4 +6.0*x2*y2+y4);
+    /* magnitude and color terms ignored
+         wcs->amd_x_coeff[16]*mag      + wcs->amd_x_coeff[17]*mag*2.0*xmm +
+         wcs->amd_x_coeff[18]*mag*(3.0*x2+y2);
+    */
+
+    /* Derivative of X model wrt y */
+    fy = wcs->amd_x_coeff[1]           + wcs->amd_x_coeff[4]*xmm +
+         wcs->amd_x_coeff[5]*2.0*ymm   + wcs->amd_x_coeff[6]*2.0*ymm +
+         wcs->amd_x_coeff[8]*x2        + wcs->amd_x_coeff[9]*2.0*xy +
+         wcs->amd_x_coeff[10]*3.0*y2   + wcs->amd_x_coeff[11]*2.0*xy +
+         wcs->amd_x_coeff[12]*4.0*xy*x2y2;
+    /* magnitude and color terms ignored
+         wcs->amd_x_coeff[17]*mag*2.0*ymm +
+         wcs->amd_x_coeff[18]*mag*2.0*xy;
+    */
+
+    /* Y plate model */
+    g = wcs->amd_y_coeff[0]*ymm       + wcs->amd_y_coeff[1]*xmm +
+       wcs->amd_y_coeff[2]            + wcs->amd_y_coeff[3]*y2 +
+       wcs->amd_y_coeff[4]*xy         + wcs->amd_y_coeff[5]*x2 +
+       wcs->amd_y_coeff[6]*x2y2       + wcs->amd_y_coeff[7]*y3 +
+       wcs->amd_y_coeff[8]*y2x        + wcs->amd_y_coeff[9]*x2y +
+       wcs->amd_y_coeff[10]*x3        + wcs->amd_y_coeff[11]*ymm*x2y2 +
+       wcs->amd_y_coeff[12]*ymm*cjunk;
+    /* magnitude and color terms ignored
+       wcs->amd_y_coeff[13]*mag        + wcs->amd_y_coeff[14]*mag*mag +
+       wcs->amd_y_coeff[15]*mag*mag*mag + wcs->amd_y_coeff[16]*mag*ymm +
+       wcs->amd_y_coeff[17]*mag*x2y2 +
+       wcs->amd_y_coeff[18]*mag*ymm*x2y2 + wcs->amd_y_coeff[19]*color;
+    */
+
+    /* Derivative of Y model wrt x */
+    gx = wcs->amd_y_coeff[1]           + wcs->amd_y_coeff[4]*ymm +
+         wcs->amd_y_coeff[5]*2.0*xmm   + wcs->amd_y_coeff[6]*2.0*xmm +
+         wcs->amd_y_coeff[8]*y2       + wcs->amd_y_coeff[9]*2.0*xy +
+         wcs->amd_y_coeff[10]*3.0*x2  + wcs->amd_y_coeff[11]*2.0*xy +
+         wcs->amd_y_coeff[12]*4.0*xy*x2y2;
+    /* magnitude and color terms ignored
+         wcs->amd_y_coeff[17]*mag*2.0*xmm +
+         wcs->amd_y_coeff[18]*mag*ymm*2.0*xmm;
+    */
+
+    /* Derivative of Y model wrt y */
+    gy = wcs->amd_y_coeff[0]            + wcs->amd_y_coeff[3]*2.0*ymm +
+         wcs->amd_y_coeff[4]*xmm        + wcs->amd_y_coeff[6]*2.0*ymm +
+         wcs->amd_y_coeff[7]*3.0*y2     + wcs->amd_y_coeff[8]*2.0*xy +
+         wcs->amd_y_coeff[9]*x2         + wcs->amd_y_coeff[11]*(x2+3.0*y2) +
+         wcs->amd_y_coeff[12]*(5.0*y4 + 6.0*x2*y2 + x4);
+    /* magnitude and color terms ignored
+         wcs->amd_y_coeff[16]*mag       + wcs->amd_y_coeff[17]*mag*2.0*ymm +
+         wcs->amd_y_coeff[18]*mag*(x2+3.0*y2);
+    */
+
+    f = f - xi;
+    g = g - eta;
+    dx = ((-f * gy) + (g * fy)) / ((fx * gy) - (fy * gx));
+    dy = ((-g * fx) + (f * gx)) / ((fx * gy) - (fy * gx));
+    xmm = xmm + dx;
+    ymm = ymm + dy;
+    if ((fabs(dx) < tolerance) && (fabs(dy) < tolerance)) break;
+    }
+
+/* Convert mm from plate center to plate pixels */
+  x = (wcs->ppo_coeff[2] - xmm*1000.0) / wcs->x_pixel_size;
+  y = (wcs->ppo_coeff[5] + ymm*1000.0) / wcs->y_pixel_size;
+
+/* Convert from plate pixels to image pixels */
+  *xpix = x - wcs->x_pixel_offset + 1.0 - 0.5;
+  *ypix = y - wcs->y_pixel_offset + 1.0 - 0.5;
+
+/* If position is off of the image, return offscale code */
+
+/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+  Commented out by D.Berry (Starlink) in order to remove dependancy
+  on NAXIS1/NAXIS2 keywords >>>>>>>>
+
+  if (*xpix < 0.5 || *xpix > wcs->nxpix+0.5)
+    return -1;
+  if (*ypix < 0.5 || *ypix > wcs->nypix+0.5)
+    return -1;
+
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
+
+  return 0;
+}
+/* Mar  6 1995	Original version of this code
+   May  4 1995	Fix eta cross terms which were all in y
+   Jun 21 1995	Add inverse routine
+   Oct 17 1995	Fix inverse routine (degrees -> radians)
+   Nov  7 1995	Add half pixel to image coordinates to get astrometric
+                  plate coordinates
+   Feb 26 1996	Fix plate to image pixel conversion error
+   Feb 18 1997  Modified by D.S. Berry (Starlink) to avoid use of the image
+                  dimensions stored in wcs->nxpix and wcs->nypix.
+   Sep  5 1997  Modified by R.F. Warren-Smith (Starlink) to make the
+                platepos and platepix functions static.
+ */
+
+
+
+
diff --git a/ast/dssmap.h b/ast/dssmap.h
new file mode 100644
index 0000000..b7550de
--- /dev/null
+++ b/ast/dssmap.h
@@ -0,0 +1,401 @@
+#if !defined( DSSMAP_INCLUDED ) /* Include this file only once */
+#define DSSMAP_INCLUDED
+/*
+*+
+*  Name:
+*     dssmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the DssMap class.
+
+*  Invocation:
+*     #include "dssmap.h"
+
+*  Description:
+*     This include file defines the interface to the DssMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The DssMap class implements Mappings which use a Digitised Sky
+*     Survey plate fit to transform between pixel coordinates and
+*     Equatorial coordinates.
+
+*  Inheritance:
+*     The DssMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astTransform
+*           Apply a DssMap to transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        astDssFits
+*           Create a FitsChan holding a FITS description of the DSS plate fit.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsADssMap
+*           Test class membership.
+*        astDssMap
+*           Create a DssMap.
+*
+*     Protected:
+*        astCheckDssMap
+*           Validate class membership.
+*        astInitDssMap
+*           Initialise a DssMap.
+*        astInitDssMapVtab
+*           Initialise the virtual function table for the DssMap class.
+*        astLoadDssMap
+*           Load a DssMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstDssMap
+*           DssMap object type.
+*
+*     Protected:
+*        AstDssMapVtab
+*           DssMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+*     (except for code supplied by Doug Mink, as noted in this file)
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     18-FEB-1997 (DSB):
+*        Original version.
+*     30-JUN-1997 (DSB):
+*        All public functions made protected.
+*     4-NOV-1997 (DSB):
+*        Removed copy of supplied FitsChan from DssMap structure.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitDssMapVtab method.
+*     21-OCT-2004 (DSB):
+*        Removed wcstools prototypes which clash with the MS Windows
+*        runtime library.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#if defined(astCLASS)            /* Protected */
+
+/* The code within this #if...#endif block is covered by the following
+   statement of terms and conditions, which differ from the terms and
+   conditions which apply elsewhere in this file.
+
+***************************************************************************
+*
+* Copyright:   1988 Smithsonian Astrophysical Observatory
+*              You may do anything you like with these files except remove
+*              this copyright.  The Smithsonian Astrophysical Observatory
+*              makes no representations about the suitability of this
+*              software for any purpose.  It is provided "as is" without
+*              express or implied warranty.
+*
+*****************************************************************************
+*/
+
+/* >>>>>>>>>>>>>>>>>> SAOimage wcs.h header file <<<<<<<<<<<<<<<<<< */
+
+/* libwcs/wcs.h
+   November 1, 1996
+   By Doug Mink, Harvard-Smithsonian Center for Astrophysics */
+
+struct WorldCoor {
+  double	xref;		/* x reference coordinate value (deg) */
+  double	yref;		/* y reference coordinate value (deg) */
+  double	xrefpix;	/* x reference pixel */
+  double	yrefpix;	/* y reference pixel */
+  double	xinc;		/* x coordinate increment (deg) */
+  double	yinc;		/* y coordinate increment (deg) */
+  double	rot;		/* rotation (deg)  (from N through E) */
+  double	crot,srot;	/* Cosine and sine of rotation angle */
+  double	cd11,cd12,cd21,cd22;
+				/* rotation matrix */
+  double	dc11,dc12,dc21,dc22;
+				/* inverse rotation matrix */
+  double	equinox;	/* Equinox of coordinates default to 1950.0 */
+  double	epoch;		/* Epoch of coordinates default to equinox */
+  double	nxpix;		/* Number of pixels in X-dimension of image */
+  double	nypix;		/* Number of pixels in Y-dimension of image */
+  double	plate_ra;	/* Right ascension of plate center */
+  double	plate_dec;	/* Declination of plate center */
+  double	plate_scale;	/* Plate scale in arcsec/mm */
+  double	x_pixel_offset;	/* X pixel offset of image lower right */
+  double	y_pixel_offset;	/* Y pixel offset of image lower right */
+  double	x_pixel_size;	/* X pixel_size */
+  double	y_pixel_size;	/* Y pixel_size */
+  double	ppo_coeff[6];
+  double	amd_x_coeff[20]; /* X coefficients for plate model */
+  double	amd_y_coeff[20]; /* Y coefficients for plate model */
+  double	xpix;		/* x (RA) coordinate (pixels) */
+  double	ypix;		/* y (dec) coordinate (pixels) */
+  double	xpos;		/* x (RA) coordinate (deg) */
+  double	ypos;		/* y (dec) coordinate (deg) */
+  int		pcode;		/* projection code (1-8) */
+  int		changesys;	/* 1 for FK4->FK5, 2 for FK5->FK4 */
+  				/* 3 for FK4->galactic, 4 for FK5->galactic */
+  int		printsys;	/* 1 to print coordinate system, else 0 */
+  int		ndec;		/* Number of decimal places in PIX2WCST */
+  int		degout;		/* 1 to always print degrees in PIX2WCST */
+  int		tabsys;		/* 1 to put tab between RA & Dec, else 0 */
+  int		rotmat;		/* 0 if CDELT, CROTA; 1 if CD */
+  int		coorflip;	/* 0 if x=RA, y=Dec; 1 if x=Dec, y=RA */
+  int		offscl;		/* 0 if OK, 1 if offscale */
+  int		plate_fit;	/* 1 if plate fit, else 0 */
+  int		wcson;		 /* 1 if WCS is set, else 0 */
+  char		c1type[8];	/*  1st coordinate type code:
+					RA--, GLON, ELON */
+  char		c2type[8];	/*  2nd coordinate type code:
+					DEC-, GLAT, ELAT */
+  char		ptype[8];	/*  projection type code:
+				    -SIN, -TAN, -ARC, -NCP, -GLS, -MER, -AIT */
+  char		radecsys[16];	/* Reference frame: FK4, FK4-NO-E, FK5, GAPPT*/
+  char		sysout[16];	/* Reference frame for output: FK4, FK5 */
+  char		center[32];	/* Center coordinates (with frame) */
+  char		search_format[120];	/* search command format */
+				/* where %s is replaced by WCS coordinates */
+};
+
+#ifndef PI
+#define PI		3.141592653589793
+#endif
+
+/* Conversions among hours of RA, degrees and radians. */
+#define degrad(x)	((x)*PI/180.)
+#define raddeg(x)	((x)*180./PI)
+#define hrdeg(x)	((x)*15.)
+#define deghr(x)	((x)/15.)
+#define hrrad(x)	degrad(hrdeg(x))
+#define radhr(x)	deghr(raddeg(x))
+
+
+/* WCS subroutines in wcs.c */
+
+/* >>>>> DSB: Prototypes for "subroutines in wcs.c" have been removed since
+   they clash with prototypes defined by the MS windows runtime library and
+   are not needed by AST. */
+
+/* Oct 26 1994	New file
+ * Dec 21 1994	Add rotation matrix
+ * Dec 22 1994	Add flag for coordinate reversal
+
+ * Mar  6 1995	Add parameters for Digital Sky Survey plate fit
+ * Jun  8 1995	Add parameters for coordinate system change
+ * Jun 21 1995	Add parameter for plate scale
+ * Jul  6 1995	Add parameter to note whether WCS is set
+ * Aug  8 1995	Add parameter to note whether to print coordinate system
+ * Oct 16 1995	Add parameters to save image dimensions and center coordinates
+
+ * Feb 15 1996	Add coordinate conversion functions
+ * Feb 20 1996	Add flag for tab tables
+ * Apr 26 1996	Add epoch of positions (actual date of image)
+ * Jul  5 1996	Add subroutine declarations
+ * Jul 19 1996	Add WCSFULL declaration
+ * Aug  5 1996	Add WCSNINIT to initialize WCS for non-terminated header
+ * Oct 31 1996	Add DCnn inverse rotation matrix
+ * Nov  1 1996	Add NDEC number of decimal places in output
+ */
+/* >>>>>>>>>>>>>>>>>>>> End of SAOimage wcs.h header file <<<<<<<<<<<<<<<< */
+#endif
+
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "fitschan.h"            /* Storage for FITS header cards */
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* DssMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstDssMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   void *wcs;                    /* Pointer to structure holding plate fit info */
+
+} AstDssMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstDssMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstFitsChan *(* DssFits)( AstDssMap *, int * );
+
+} AstDssMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstDssMapGlobals {
+   AstDssMapVtab Class_Vtab;
+   int Class_Init;
+} AstDssMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitDssMapGlobals_( AstDssMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(DssMap)          /* Check class membership */
+astPROTO_ISA(DssMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstDssMap *astDssMap_( void *, const char *, int *, ...);
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstDssMap *astInitDssMap_( void *, size_t, int, AstDssMapVtab *,
+                             const char *, AstFitsChan *, int * );
+
+/* Vtab initialiser. */
+void astInitDssMapVtab_( AstDssMapVtab *, const char *, int * );
+
+/* Loader. */
+AstDssMap *astLoadDssMap_( void *, size_t, AstDssMapVtab *,
+                             const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+AstFitsChan *astDssFits_( AstDssMap *, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckDssMap(this) astINVOKE_CHECK(DssMap,this,0)
+#define astVerifyDssMap(this) astINVOKE_CHECK(DssMap,this,1)
+
+/* Test class membership. */
+#define astIsADssMap(this) astINVOKE_ISA(DssMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astDssMap astINVOKE(F,astDssMap_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitDssMap(mem,size,init,vtab,name,fits) \
+astINVOKE(O,astInitDssMap_(mem,size,init,vtab,name,astCheckFitsChan(fits),STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitDssMapVtab(vtab,name) astINVOKE(V,astInitDssMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadDssMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadDssMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckDssMap to validate DssMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astDssFits(this) astINVOKE(O,astDssFits_(astCheckDssMap(this),STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/ellipse.c b/ast/ellipse.c
new file mode 100644
index 0000000..1e23724
--- /dev/null
+++ b/ast/ellipse.c
@@ -0,0 +1,3055 @@
+/*
+*class++
+*  Name:
+*     Ellipse
+
+*  Purpose:
+*     An elliptical region within a 2-dimensional Frame.
+
+*  Constructor Function:
+c     astEllipse
+f     AST_ELLIPSE
+
+*  Description:
+*     The Ellipse class implements a Region which represents a ellipse
+*     within a 2-dimensional Frame.
+
+*  Inheritance:
+*     The Ellipse class inherits from the Region class.
+
+*  Attributes:
+*     The Ellipse class does not define any new attributes beyond
+*     those which are applicable to all Regions.
+
+*  Functions:
+c     In addition to those functions applicable to all Regions, the
+c     following functions may also be applied to all Ellipses:
+f     In addition to those routines applicable to all Regions, the
+f     following routines may also be applied to all Ellipses:
+*
+c     - astEllipsePars: Get the geometric parameters of the Ellipse
+f     - AST_ELLIPSEPARS: Get the geometric parameters of the Ellipse
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     7-SEP-2004 (DSB):
+*        Original version.
+*     4-NOV-2013 (DSB):
+*        Modify RegPins so that it can handle uncertainty regions that straddle
+*        a discontinuity. Previously, such uncertainty Regions could have a huge
+*        bounding box resulting in matching region being far too big.
+*     6-JAN-2014 (DSB):
+*        Ensure cached information is available in RegCentre even if no new
+*        centre is supplied.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Ellipse
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "region.h"              /* Coordinate regions (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "box.h"                 /* Box Regions */
+#include "wcsmap.h"              /* Definitons of AST__DPI etc */
+#include "circle.h"              /* Interface definition for circle class */
+#include "ellipse.h"             /* Interface definition for this class */
+#include "mapping.h"             /* Position mappings */
+#include "unitmap.h"             /* Unit Mapping */
+#include "pal.h"                 /* Positional astronomy library */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstMapping *(* parent_simplify)( AstMapping *, int * );
+static void (* parent_setregfs)( AstRegion *, AstFrame *, int * );
+static void (* parent_resetcache)( AstRegion *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Ellipse)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Ellipse,Class_Init)
+#define class_vtab astGLOBAL(Ellipse,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstEllipseVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstEllipse *astEllipseId_( void *, int, const double[2], const double[2], const double[2], void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double *RegCentre( AstRegion *this, double *, double **, int, int, int * );
+static int RegPins( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+static int RegTrace( AstRegion *, int, double *, double **, int * );
+static void Cache( AstEllipse *, int * );
+static void CalcPars( AstFrame *, double[2], double[2], double[2], double *, double *, double *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void EllipsePars( AstEllipse *, double[2], double *, double *, double *, double[2], double[2], int * );
+static void RegBaseBox( AstRegion *this, double *, double *, int * );
+static void ResetCache( AstRegion *this, int * );
+static void SetRegFS( AstRegion *, AstFrame *, int * );
+
+/* Member functions. */
+/* ================= */
+
+AstRegion *astBestEllipse_( AstPointSet *mesh, double *cen, AstRegion *unc, int *status ){
+/*
+*+
+*  Name:
+*     astBestEllipse
+
+*  Purpose:
+*     Find the best fitting Ellipse through a given mesh of points.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     AstRegion *astBestEllipse( AstPointSet *mesh, double *cen, AstRegion *unc )
+
+*  Class Membership:
+*     Ellipse member function
+
+*  Description:
+*     This function finds the best fitting Ellipse through a given mesh of
+*     points. Ellispes are always 2-dimensional.
+
+*  Parameters:
+*     mesh
+*        Pointer to a PointSet holding the mesh of points. They are
+*        assumed to be in the Frame represented by "unc".
+*     cen
+*        Pointer to an array holding the coordinates of the new Ellipse
+*        centre.
+*     unc
+*        A Region representing the uncertainty associated with each point
+*        on the mesh.
+
+*  Returned Value:
+*     Pointer to the best fitting Ellipse. It will inherit the positional
+*     uncertainty and Frame represented by "unc".
+
+*  Implementation Deficiencies:
+*    - The method used by this function is not very accurate, and assumes
+*    that the supplied mesh provides uniform coverage of the entire ellipse.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstPointSet *ps2;
+   AstRegion *result;
+   double **ptr2;
+   double **ptr;
+   double *ang;
+   double *dist;
+   double *px;
+   double *py;
+   double a0;
+   double a;
+   double aa[2];
+   double at;
+   double b;
+   double c0;
+   double c1;
+   double c2;
+   double c;
+   double d;
+   double den;
+   double e;
+   double f;
+   double mn;
+   double mx;
+   double p[2];
+   double pa[2];
+   double pb[2];
+   double r1;
+   double r2;
+   double r3;
+   double smn;
+   double t1;
+   double t2;
+   double t3;
+   int ip;
+   int maxat;
+   int np;
+   double sw;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get no. of points in the mesh. */
+   np = astGetNpoint( mesh );
+
+/* Get pointers to the axis values. */
+   ptr = astGetPoints( mesh );
+
+/* Allocate work space */
+   dist = astMalloc( sizeof( double )*(size_t) np );
+   ang = astMalloc( sizeof( double )*(size_t) np );
+
+/* Get a pointer to the Frame represented by "unc". This is the Frame to
+   which the supplied mesh points refer. */
+   frm = astGetFrame( unc->frameset, AST__CURRENT );
+
+/* Check pointers can be used safely */
+   if( astOK ) {
+
+/* Find the first mesh point which is at a non-zero distance from the
+   centre. */
+      px = ptr[ 0 ];
+      py = ptr[ 1 ];
+      for( ip = 0; ip < np; ip++, px++, py++ ) {
+         p[ 0 ] = *px;
+         p[ 1 ] = *py;
+         dist[ ip ] = astDistance( frm, cen, p );
+         if( dist[ ip ] != AST__BAD && dist[ ip ] != 0.0 ) {
+            break;
+         } else {
+            ang[ ip ] = AST__BAD;
+            dist[ ip ] = AST__BAD;
+         }
+      }
+
+/* Find a point which is this distance away from the centre along the second
+   axis. This point is used to define zero angle when calling astAngle
+   below. */
+      astOffset2( frm, cen, 0.0, dist[ ip ], aa );
+      ang[ ip ] = astAngle( frm, aa, cen, p );
+
+/* Get the distance from the centre to each of the remaining mesh points. Also
+   find the orientation of the radial lines through the centre to each mesh
+   point. At the same time, find the index of the point with the largest
+   radial distance. */
+      maxat = ip;
+      r2 = dist[ maxat ];
+      ip++;
+      px++;
+      py++;
+      for( ; ip < np; ip++, px++, py++ ) {
+         p[ 0 ] = *px;
+         p[ 1 ] = *py;
+         dist[ ip ] = astDistance( frm, cen, p );
+         ang[ ip ] = astAngle( frm, aa, cen, p );
+         if( dist[ ip ] != AST__BAD && dist[ ip ] > r2 ) {
+            r2 = dist[ ip ];
+            maxat = ip;
+         }
+      }
+
+/* Find the higher index neighbouring point, wrapping back to the start
+   of the list when the end is reached. Note the radius and position angle
+   at this neighbouring point. */
+      t2 = 0.0;
+      r3 = AST__BAD;
+      t3 = AST__BAD;
+      a0 = ang[ maxat ];
+      for( ip = maxat + 1; ip < np; ip++ ) {
+         if( dist[ ip ] != AST__BAD ) {
+            r3 = dist[ ip ];
+            t3 = palDrange( ang[ ip ] - a0 );
+            break;
+         }
+      }
+      if( r3 == AST__BAD ) {
+         for( ip = 0; ip < maxat; ip++ ) {
+            if( dist[ ip ] != AST__BAD ) {
+               r3 = dist[ ip ];
+               t3 = palDrange( ang[ ip ] - a0 );
+               break;
+            }
+         }
+      }
+
+/* Find the lower index neighbouring point, wrapping back to the end
+   of the list when the start is reached. Note the radius and position angle
+   at this neighbouring point. */
+      r1 = AST__BAD;
+      t1 = AST__BAD;
+      for( ip = maxat - 1; ip > -1; ip-- ) {
+         if( dist[ ip ] != AST__BAD ) {
+            r1 = dist[ ip ];
+            t1 = palDrange( ang[ ip ] - a0 );
+            break;
+         }
+      }
+      if( r1 == AST__BAD ) {
+         for( ip = np - 1; ip > maxat; ip-- ) {
+            if( dist[ ip ] != AST__BAD ) {
+               r1 = dist[ ip ];
+               t1 = palDrange( ang[ ip ] - a0 );
+               break;
+            }
+         }
+      }
+
+/* Fit a quadratic through the three pairs of (radius,angle) values. The
+   centre point (r2,t2) is the point which is furthest from the centre,
+   and the other two are the neighbouring points found above. */
+      a = r2 - r1;
+      b = t2 - t1;
+      c = t2*t2 - t1*t1;
+      d = r3 - r2;
+      e = t3 - t2;
+      f = t3*t3 - t2*t2;
+
+      den = c*e - b*f;
+      if( den != 0.0 ) {
+
+/* The co-efficients of the interpolating polynomial... */
+         c1 = ( d*c - a*f )/den;
+         c2 = ( a*e - d*b )/den;
+         c0 = r1 - c1*t1 - c2*t1*t1;
+
+/* Find the largest radius (the turning point of the quadratic), and the
+   angle at which it occurs. */
+         if( c2 < 0.0 ) {
+            mx = ( 4*c0*c2 - c1*c1 )/( 4*c2 );
+            at = a0 - c1/( 2*c2 );
+         } else {
+            mx = r2;
+            at = a0 - t2;
+         }
+
+/* This point is the end of the ellipse primary axis. Find its (x,y)
+   coords, and store in "pa". */
+         astOffset2( frm, cen, at, mx, pa );
+
+/* Resolve all the supplied points into components parallel and
+   perpendicular to the line joining the centre and "pa". */
+         ps2 = astResolvePoints( frm, cen, pa, mesh, NULL );
+         ptr2 = astGetPoints( ps2 );
+         if( astOK ) {
+
+/* For each other mesh point, work out the length of the secondary
+   axis which would result if we used that point to define the ellipse.
+   Find the mean of these secondary axis lengths, weighted by the length
+   of the y component to reduce influence of poor conditioning at very
+   low y. */
+            smn = 0.0;
+            sw = 0.0;
+            px = ptr2[ 0 ];
+            py = ptr2[ 1 ];
+            for( ip = 0; ip < np; ip++, px++, py++ ) {
+               if( *px != AST__BAD && *py != AST__BAD ) {
+                  den = mx*mx - (*px)*(*px);
+                  if( den > 0.0 ) {
+                     smn += fabs( mx*(*py)*(*py) )/sqrt( den );
+                     sw += fabs( *py );
+                  }
+               }
+            }
+
+            if( sw > 0 ) {
+               mn = smn/sw;
+
+/* Find the coords at the end of the mean secondary axis. */
+               astOffset2( frm, cen, at + AST__DPIBY2, mn, pb );
+
+/* Create the Ellipse to return. */
+               result = (AstRegion *) astEllipse( frm, 0, cen, pa, pb, unc, "", status );
+            }
+         }
+
+/* Free resources. */
+         ps2 = astAnnul( ps2 );
+
+      }
+   }
+
+   dist = astFree( dist );
+   ang = astFree( ang );
+   frm = astAnnul( frm );
+
+/* Return NULL if anything went wrong. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result.*/
+   return result;
+}
+
+void astInitEllipseVtab_(  AstEllipseVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitEllipseVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Ellipse.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     void astInitEllipseVtab( AstEllipseVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Ellipse vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Ellipse class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstRegionVtab *region;        /* Pointer to Region component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitRegionVtab( (AstRegionVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAEllipse) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstRegionVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->EllipsePars = EllipsePars;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   mapping = (AstMappingVtab *) vtab;
+   region = (AstRegionVtab *) vtab;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_simplify = mapping->Simplify;
+   mapping->Simplify = Simplify;
+
+   parent_setregfs = region->SetRegFS;
+   region->SetRegFS = SetRegFS;
+
+   parent_resetcache = region->ResetCache;
+   region->ResetCache = ResetCache;
+
+   region->RegPins = RegPins;
+   region->RegBaseMesh = RegBaseMesh;
+   region->RegBaseBox = RegBaseBox;
+   region->RegCentre = RegCentre;
+   region->RegTrace = RegTrace;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "Ellipse", "Elliptical region" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void Cache( AstEllipse *this, int *status ){
+/*
+*  Name:
+*     Cache
+
+*  Purpose:
+*     Calculate intermediate values and cache them in the Ellipse structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     void Cache( AstEllipse *this, int *status )
+
+*  Class Membership:
+*     Ellipse member function
+
+*  Description:
+*     This function uses the PointSet stored in the parent Region to calculate
+*     some intermediate values which are useful in other methods. These
+*     values are stored within the Ellipse structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Ellipse.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrame *frm;       /* Pointer to base Frame in Ellipse */
+   double **ptr;        /* Pointer to data in the encapsulated PointSet */
+   double *centre;      /* Array holding centre coords */
+   double *point1;      /* Array holding coords at end of primary axis */
+   double *point2;      /* Array holding coords at another point on ellipse */
+   double a;            /* The half-length of the primary axis */
+   double angle;        /* Orientation of primary axis */
+   double b;            /* The half-length of the secondary axis */
+   int i;               /* Axis index */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Do Nothing if the cached information is up to date. */
+   if( this->stale ) {
+
+/* Get a pointer to the base Frame. */
+      frm = astGetFrame( ((AstRegion *) this)->frameset, AST__BASE );
+
+/* Allocate memory. */
+      centre = (double *) astMalloc( sizeof( double )*2 );
+      point1 = (double *) astMalloc( sizeof( double )*2 );
+      point2 = (double *) astMalloc( sizeof( double )*2 );
+
+/* Get pointers to the coordinate data in the parent Region structure. */
+      ptr = astGetPoints( ((AstRegion *) this)->points );
+
+/* Check pointers can be used safely. */
+      if( astOK ) {
+
+/* Copy the points in to the allocated memory. */
+         for( i = 0; i < 2; i++ ) {
+            centre[ i ] = ptr[ i ][ 0 ];
+            point1[ i ] = ptr[ i ][ 1 ];
+            point2[ i ] = ptr[ i ][ 2 ];
+         }
+
+/* Calculate the geometric parameters of the ellipse. */
+         CalcPars( frm, centre, point1, point2, &a, &b, &angle, status );
+
+/* Check the returned values. */
+         if( a <= 0.0 || a == AST__BAD || b <= 0.0 || b == AST__BAD ) {
+            if( astOK ) astError( AST__BADIN, "astInitEllipse(%s): The "
+                                  "supplied points do not determine an "
+                                  "ellipse.", status, astGetClass( this ) );
+         }
+
+/* Store useful things in the Ellipse structure. */
+         if( astOK ) {
+           astFree( this->centre );
+           this->centre = centre;
+           centre = NULL;
+
+           astFree( this->point1 );
+           this->point1 = point1;
+           point1 = NULL;
+
+           this->a = a;
+           this->b = b;
+           this->angle = angle;
+         }
+      }
+
+/* Initialise the bounding box. This is set properly when the astRegBaseMesh
+   function is called. These variables should not be used unless the
+   "basemesh" component of the parent Region structure is set to a non-null
+   value. */
+      this->lbx = -DBL_MAX;
+      this->ubx = DBL_MAX;
+      this->lby = -DBL_MAX;
+      this->uby = DBL_MAX;
+
+/* Free resources */
+      frm = astAnnul( frm );
+      if( centre ) centre = astFree( centre );
+      if( point1 ) point1 = astFree( point1 );
+      point2 = astFree( point2 );
+
+/* Indicate cached information is up to date. */
+      this->stale = 0;
+
+   }
+}
+
+static void CalcPars( AstFrame *frm, double centre[2], double point1[2],
+                      double point2[2], double *a, double *b,
+                      double *angle, int *status ){
+/*
+*  Name:
+*     CalcPars
+
+*  Purpose:
+*     Calculate ellipse parameters.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     void CalcPars( AstFrame *frm, double centre[2], double point1[2],
+*                    double point2[2], double *a, double *b, double *angle,
+*                    int *status )
+
+*  Class Membership:
+*     Ellipse member function
+
+*  Description:
+*     This function uses the supplied positions to calculate the
+*     geometric parameters of an ellipse.
+
+*  Parameters:
+*     frm
+*        Pointer to the Frame in which the positions are defined.
+*     centre;
+*        Array holding centre coords.
+*     point1
+*        Array holding coords at end of primary axis
+*     point2
+*        Array holding coords at another point on ellipse. On exit it
+*        holds the coords at the end of the secondary axis.
+*     a
+*        Pointer to location at which to store the half-length of the
+*        primary axis.
+*     b
+*        Pointer to location at which to store the half-length of the
+*        secondary axis.
+*     angle
+*        Pointer to location at which to store the angle from the
+*        positive direction of the second Frame axis to the primary
+*        ellipse axis, in radians. Rotation from the second to the first
+*        Frame axis is positive.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double point3[ 2 ];  /* Array holding a point on the primary axis */
+   double x;            /* The offset parallel to the primary axis */
+   double y;            /* The offset perpendicular to the primary axis */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the geodesic distance between the centre and point 1 (the end of
+   the primary axis of the ellipse). This is the half length of the
+   primary axis of the ellipse (the axis which joins the centre position to
+   point 1). */
+   *a = astDistance( frm, centre, point1 );
+
+/* Find the point (point3) on the primary axis which is closest to point 2,
+   and thus get the geodesic offsets (resolved parallel and perpendicular to
+   the primary axis) between the centre and point 2. */
+   if( *a > 0.0 ) {
+      astResolve( frm, centre, point1, point2, point3, &x, &y );
+
+/* Find the half-length of the secondary ellipse axis. */
+      if( astOK ) {
+         *b = (*a)*(*a) - x*x;
+         if( *b > 0.0 ) *b = (*a)*y/sqrt( *b );
+      } else {
+         *b = *a;
+      }
+
+/* Find the angle from the positive direction of the second axis to the
+   primary ellipse axis. */
+      point3[ 0 ] = centre[ 0 ];
+      point3[ 1 ] = centre[ 1 ] + fabs( 0.1*(*a) );
+      *angle = astAngle( frm, point3, centre, point1 );
+
+/* Find the end point of the secondary axis. */
+      (void) astOffset2( frm, centre, *angle + AST__DPIBY2, *b, point2 );
+   }
+}
+
+static void EllipsePars( AstEllipse *this, double centre[2], double *a,
+                         double *b, double *angle, double p1[2],
+                         double p2[2], int *status ){
+/*
+*++
+*  Name:
+c     astEllipsePars
+f     AST_ELLIPSEPARS
+
+*  Purpose:
+*     Returns the geometric parameters of an Ellipse.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "ellipse.h"
+c     void astEllipsePars( AstEllipse *this, double centre[2], double *a,
+c                          double *b, double *angle, double p1[2], double p2[2] )
+f     CALL AST_ELLIPSEPARS( THIS, CENTRE, A, B, ANGLE, P1, P2, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns the geometric parameters describing the supplied ellipse.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+c     centre
+f     CENTRE( 2 ) = DOUBLE PRECISION (Returned)
+*        The coordinates of the Ellipse centre are returned in this arrays.
+c     a
+f     A = DOUBLE PRECISION (Returned)
+*        Returned holding the half-length of the first axis of the
+*        ellipse.
+c     b
+f     B = DOUBLE PRECISION (Returned)
+*        Returned holding the half-length of the second axis of the
+*        ellipse.
+c     angle
+f     ANGLE = DOUBLE PRECISION (Returned)
+*        If the coordinate system in which the Ellipse is defined has
+*        axes (X,Y), then
+c        "*angle"
+f        ANGLE
+*        is returned holding the angle from the positive direction of
+*        the Y axis to the first axis of the ellipse, in radians.
+*        Positive rotation is in the same sense as rotation from the
+*        positive direction of Y to the positive direction of X.
+c     p1
+f     P1( 2 ) = DOUBLE PRECISION (Returned)
+*        An array in which to return the coordinates at one of the two ends
+*        of the first axis  of the ellipse.
+c        A NULL pointer can be supplied if these coordinates are not needed.
+c     p2
+f     P2( 2 ) = DOUBLE PRECISION (Returned)
+*        An array in which to return the coordinates at one of the two ends
+*        of the second axis  of the ellipse.
+c        A NULL pointer can be supplied if these coordinates are not needed.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - If the coordinate system represented by the Ellipse has been
+*     changed since it was first created, the returned parameters refer
+*     to the new (changed) coordinate system, rather than the original
+*     coordinate system. Note however that if the transformation from
+*     original to new coordinate system is non-linear, the shape
+*     represented by the supplied Ellipse object may not be an accurate
+*     ellipse.
+*     - Values of AST__BAD are returned for the parameters without error
+*     if the ellipse is degenerate or undefined.
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *frm;           /* Current Frame represented by the Ellipse */
+   AstPointSet *pset;       /* PointSet holding PointList axis values */
+   AstRegion *this_region;  /* Parent Region pointer */
+   double **ptr;            /* Pointer to axes values in the PointList */
+   double *point1;          /* Pointer to "p1" or "buf1" */
+   double *point2;          /* Pointer to "p2" or "buf2" */
+   double buf1[2];          /* Local substitute array for "p1" */
+   double buf2[2];          /* Local substitute array for "p2" */
+   int i;                   /* Axis index */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Store a pointer to the parent region structure. */
+   this_region = (AstRegion *) this;
+
+/* Transform the base Frame axis values into the current Frame. */
+   pset = astTransform( this_region->frameset, this_region->points, 1, NULL );
+
+/* Get pointers to the coordinate data. */
+   ptr = astGetPoints( pset );
+
+/* Choose the arrays to use - supplied arrays if possible, local arrays
+   otherwise. */
+   if( p1 ) {
+      point1 = p1;
+   } else {
+      point1 = buf1;
+   }
+   if( p2 ) {
+      point2 = p2;
+   } else {
+      point2 = buf2;
+   }
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Copy the points in to separate arrays. */
+      for( i = 0; i < 2; i++ ) {
+         centre[ i ] = ptr[ i ][ 0 ];
+         point1[ i ] = ptr[ i ][ 1 ];
+         point2[ i ] = ptr[ i ][ 2 ];
+      }
+
+/* Get the Ellipse frame. */
+      frm = astGetFrame( this_region->frameset, AST__CURRENT );
+
+/* Calculate the geometric parameters of the ellipse. */
+      CalcPars( frm, centre, point1, point2, a, b, angle, status );
+
+/* Ensure no zero values are returned. */
+      if( *a <= 0.0  || *b <= 0.0 ) {
+         *a = AST__BAD;
+         *b = AST__BAD;
+         *angle = AST__BAD;
+      }
+
+/* Free resources */
+      frm = astAnnul( frm );
+   }
+   pset = astAnnul( pset );
+}
+
+static void RegBaseBox( AstRegion *this_region, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     RegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     Ellipse member function (over-rides the astRegBaseBox protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstEllipse *this;             /* Pointer to Ellipse structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Ellipse structure */
+   this = (AstEllipse *) this_region;
+
+/* The bounding box of the mesh returned by astRegBaseMesh is used as the
+   bounding box of the Ellipse. These bounds are cached in the Ellipse
+   structure by astRegBaseMesh. Ensure astRegBaseMesh has been invoked,
+   so that it is safe to use the cached bounding box. */
+   if( !this_region->basemesh ) (void) astAnnul( astRegBaseMesh( this ) );
+
+/* Store the bounding box. */
+   lbnd[ 0 ] = this->lbx;
+   ubnd[ 0 ] = this->ubx;
+   lbnd[ 1 ] = this->lby;
+   ubnd[ 1 ] = this->uby;
+
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this_region, int *status ){
+/*
+*  Name:
+*     RegBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing a mesh of points on the boundary of a
+*     Region in its base Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     AstPointSet *astRegBaseMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Ellipse member function (over-rides the astRegBaseMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. Annul the pointer using astAnnul when it
+*     is no longer needed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*/
+
+/* Local Constants: */
+#define NP_EDGE 50                /* No. of points for determining geodesic */
+
+/* Local Variables: */
+   AstEllipse *this;              /* The Ellipse structure */
+   AstFrame *frm;                 /* Base Frame in encapsulated FrameSet */
+   AstPointSet *result;           /* Returned pointer */
+   AstRegion *reg;                /* Copy of supplied Ellipse */
+   double **ptr;                  /* Pointers to data */
+   double ang;                    /* Position angular of primary axis at "dx" */
+   double angle;                  /* Ellipse parametric angle at point */
+   double delta;                  /* Angular separation of points */
+   double dist;                   /* Offset along an axis */
+   double dx;                     /* Primary axis offset */
+   double dy;                     /* Secondary axis offset */
+   double lbnd[2];                /* Lower bounding box bounds */
+   double lbx;                    /* Lower x bound of mesh bounding box */
+   double lby;                    /* Lower y bound of mesh bounding box */
+   double p2[ 2 ];                /* Position in 2D Frame */
+   double p[ 2 ];                 /* Position in 2D Frame */
+   double ubnd[2];                /* Upper bounding box bounds */
+   double ubx;                    /* Upper x bound of mesh bounding box */
+   double uby;                    /* Upper y bound of mesh bounding box */
+   int i;                         /* Point index */
+   int np;                        /* No. of points in returned PointSet */
+
+/* Initialise */
+   result= NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the Region structure contains a pointer to a PointSet holding
+   a previously created mesh, return it. */
+   if( this_region->basemesh ) {
+      result = astClone( this_region->basemesh );
+
+/* Otherwise, create a new mesh. */
+   } else {
+
+/* Initialise the bounding box of the mesh points. */
+      lbx = DBL_MAX;
+      ubx = -DBL_MAX;
+      lby = DBL_MAX;
+      uby = -DBL_MAX;
+
+/* Get a pointer to the Ellipse structure. */
+      this = (AstEllipse *) this_region;
+
+/* Ensure cached information is available. */
+      Cache( this, status );
+
+/* Get a pointer to the base Frame in the encapsulated FrameSet. */
+      frm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Get the requested number of points to put on the mesh. */
+      np = astGetMeshSize( this );
+
+/* Store the angular increment between points. */
+      delta = 2*AST__DPI/np;
+
+/* Create a suitable PointSet to hold the returned positions. */
+      result = astPointSet( np, 2, "", status );
+      ptr = astGetPoints( result );
+      if( astOK ) {
+
+/* Loop round each point. The angle is the parametric angle, phi, where
+   the ellipse is defined by:
+
+   dx = a.cos( phi )
+   dy = a.sin( phi )
+
+   measured from the primary ellipse. Positive in the sense of rotation from
+   axis 2 to axis 1.  */
+         angle = 0.0;
+         for( i = 0; i < np; i++ ) {
+
+/* Find the offsets from the centre. "dx" is geodesic distance along the
+   primary axis, and dy is geodesic distance along the secondary axis. */
+            dx = this->a*cos( angle );
+            dy = this->b*sin( angle );
+
+/* Now find the point which corresponds to this dx and dy, taking account
+   of the potential spherical geometry of hte coordinate system. First
+   move a distance "dx" from the centre along the primary axis. The
+   function value returned is the direction of the geodesic curve at the
+   end point. That is, the angle (in radians) between the positive direction
+   of the second axis and the continuation of the geodesic curve at the
+   requested end point. */
+            ang = astOffset2( frm, this->centre, this->angle, dx, p );
+
+/* Now move a distance "dy" from the point found above at right angles to
+   the primary axis. */
+            astOffset2( frm, p, ang + AST__DPIBY2, dy, p2 );
+
+/* Store the resulting axis values. */
+            ptr[ 0 ][ i ] = p2[ 0 ];
+            ptr[ 1 ][ i ] = p2[ 1 ];
+
+/* Update the bounds of the mesh bounding box. The box is expressed in
+   terms of axis offsets from the centre, in order to avoid problems with
+   boxes that cross RA=0 or RA=12h */
+            if( p2[ 0 ] != AST__BAD && p2[ 1 ] != AST__BAD ){
+
+               dist =  astAxDistance( frm, 1, this->centre[ 0 ], p2[ 0 ] );
+               if( dist < lbx ) {
+                  lbx = dist;
+               } else if( dist > ubx ) {
+                  ubx = dist;
+               }
+
+               dist =  astAxDistance( frm, 1, this->centre[ 1 ], p2[ 1 ] );
+               if( dist < lby ) {
+                  lby = dist;
+               } else if( dist > uby ) {
+                  uby = dist;
+               }
+            }
+
+/* Increment the angular position of the next mesh point. */
+            angle += delta;
+         }
+      }
+
+/* Save the returned pointer in the Region structure so that it does not
+   need to be created again next time this function is called. Also cache
+   the bounding box in the Ellipse structure. */
+      if( astOK && result ) {
+         this_region->basemesh = astClone( result );
+
+/* Extend the bounding box if it contains any singularies. The astNormBox
+   requires a Mapping which can be used to test points in the base Frame.
+   Create a copy of the Circle and then set its FrameSet so that the current
+   Frame in the copy is the same as the base Frame in the original. */
+         reg = astCopy( this );
+         astSetRegFS( reg, frm );
+         astSetNegated( reg, 0 );
+
+/* Normalise this box. */
+         lbnd[ 0 ] = this->centre[ 0 ] + lbx;
+         lbnd[ 1 ] = this->centre[ 1 ] + lby;
+         ubnd[ 0 ] = this->centre[ 0 ] + ubx;
+         ubnd[ 1 ] = this->centre[ 1 ] + uby;
+         astNormBox( frm, lbnd, ubnd, reg );
+
+/* Save this box */
+         this->lbx = lbnd[ 0 ];
+         this->ubx = ubnd[ 0 ];
+         this->lby = lbnd[ 1 ];
+         this->uby = ubnd[ 1 ];
+
+/* Free resources. */
+         reg = astAnnul( reg );
+      }
+      frm = astAnnul( frm );
+
+   }
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static double *RegCentre( AstRegion *this_region, double *cen, double **ptr,
+                          int index, int ifrm, int *status ){
+/*
+*  Name:
+*     RegCentre
+
+*  Purpose:
+*     Re-centre a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     double *RegCentre( AstRegion *this, double *cen, double **ptr,
+*                        int index, int ifrm, int *status )
+
+*  Class Membership:
+*     Ellipse member function (over-rides the astRegCentre protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function shifts the centre of the supplied Region to a
+*     specified position, or returns the current centre of the Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     cen
+*        Pointer to an array of axis values, giving the new centre.
+*        Supply a NULL value for this in order to use "ptr" and "index" to
+*        specify the new centre.
+*     ptr
+*        Pointer to an array of points, one for each axis in the Region.
+*        Each pointer locates an array of axis values. This is the format
+*        returned by the PointSet method astGetPoints. Only used if "cen"
+*        is NULL.
+*     index
+*        The index of the point within the arrays identified by "ptr" at
+*        which is stored the coords for the new centre position. Only used
+*        if "cen" is NULL.
+*     ifrm
+*        Should be AST__BASE or AST__CURRENT. Indicates whether the centre
+*        position is supplied and returned in the base or current Frame of
+*        the FrameSet encapsulated within "this".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If both "cen" and "ptr" are NULL then a pointer to a newly
+*     allocated dynamic array is returned which contains the centre
+*     coords of the Region. This array should be freed using astFree when
+*     no longer needed. If either of "ptr" or "cen" is not NULL, then a
+*     NULL pointer is returned.
+
+*  Notes:
+*    - Some Region sub-classes do not have a centre. Such classes will report
+*    an AST__INTER error code if this method is called.
+*/
+
+/* Local Variables: */
+   AstEllipse *this;   /* Pointer to Ellipse structure */
+   AstFrame *frm;      /* Base Frame */
+   double **rptr;      /* Data pointers for Region PointSet */
+   double *bc;         /* Base Frame centre position */
+   double *result;     /* Returned pointer */
+   double *tmp;        /* Temporary array pointer */
+   double a[ 2 ];      /* Original position */
+   double angle;       /* Orietentation of offset from old to new centre */
+   double axval;       /* Axis value */
+   double b[ 2 ];      /* New position */
+   double dist;        /* Distance from old to new centre */
+   double newcen[ 2 ]; /* New centre */
+   int ic;             /* Coordinate index */
+   int ip;             /* Position index */
+   int ncb;            /* Number of base frame coordinate values per point */
+   int ncc;            /* Number of current frame coordinate values per point */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Ellipse structure. */
+   this = (AstEllipse *) this_region;
+
+/* Ensure cached information is available. */
+   Cache( this, status );
+
+/* Get the number of axis values per point in the current Frame. */
+   ncc = astGetNout( this_region->frameset );
+
+/* An ellipse always has 2 base frame axes. */
+   ncb = 2;
+
+/* If the centre coords are to be returned, return either a copy of the
+   base Frame centre coords, or transform the base Frame centre coords
+   into the current Frame. */
+   if( !ptr && !cen ) {
+      if( ifrm == AST__CURRENT ) {
+         result = astRegTranPoint( this_region, this->centre, 1, 1 );
+      } else {
+         result = astStore( NULL, this->centre, sizeof( double )*ncb );
+      }
+
+/* Otherwise, we store the supplied new centre coords and return a NULL
+   pointer. */
+   } else {
+
+/* Get a pointer to the axis values stored in the Region structure. */
+      rptr = astGetPoints( this_region->points );
+
+/* Check pointers can be used safely */
+      if( astOK ) {
+
+/* If the centre position was supplied in the current Frame, find the
+   corresponding base Frame position... */
+         if( ifrm == AST__CURRENT ) {
+            if( cen ) {
+               bc = astRegTranPoint( this_region, cen, 1, 0 );
+            } else {
+               tmp = astMalloc( sizeof( double)*(size_t)ncc );
+               if( astOK ) {
+                  for( ic = 0; ic < ncc; ic++ ) tmp[ ic ] = ptr[ ic ][ index ];
+               }
+               bc = astRegTranPoint( this_region, tmp, 1, 0 );
+               tmp = astFree( tmp );
+            }
+
+/* Replace any bad centre values with their current values. */
+            for( ic = 0; ic < ncb; ic++ ) {
+               if( bc[ ic ] ==  AST__BAD ) bc[ ic ] = this->centre[ ic ];
+            }
+
+/* If the centre position was supplied in the base Frame, use the
+   supplied "cen" or "ptr" pointer directly to change the coords in the
+   parent Region structure and the cached coords in the Ellipse structure. */
+         } else {
+            bc = newcen;
+            for( ic = 0; ic < ncb; ic++ ) {
+               axval = cen ? cen[ ic ] : ptr[ ic ][ index ];
+               newcen[ ic ] = ( axval != AST__BAD ) ? axval : this->centre[ ic ];
+            }
+         }
+
+/* Find the direction and length of the offset between the old and new
+   centre. */
+         frm = astGetFrame( this_region->frameset, AST__BASE );
+         angle = astAxAngle( frm, this->centre, bc, 2 );
+         dist = astDistance( frm, this->centre, bc );
+
+/* Shift each point in the parent Region structure by the same length and
+   direction. */
+         for( ip = 0; ip < 3; ip++ ) {
+            a[ 0 ] = rptr[ ip ][ 0 ];
+            a[ 1 ] = rptr[ ip ][ 1 ];
+            astOffset2( frm, a, angle, dist, b );
+            rptr[ ip ][ 0 ] = b[ 0 ];
+            rptr[ ip ][ 1 ] = b[ 1 ];
+         }
+
+/* Indicate that the cache is stale. */
+         astResetCache( this );
+
+/* Free resources */
+         frm = astAnnul( frm );
+         if( bc != newcen ) bc = astFree( bc );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int RegPins( AstRegion *this_region, AstPointSet *pset, AstRegion *unc,
+                    int **mask, int *status ){
+/*
+*  Name:
+*     RegPins
+
+*  Purpose:
+*     Check if a set of points fall on the boundary of a given Ellipse.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+*                  int **mask, int *status )
+
+*  Class Membership:
+*     Ellipse member function (over-rides the astRegPins protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied set of
+*     points all fall on the boundary of the given Ellipse.
+*
+*     Some tolerance is allowed, as specified by the uncertainty Region
+*     stored in the supplied Ellipse "this", and the supplied uncertainty
+*     Region "unc" which describes the uncertainty of the supplied points.
+
+*  Parameters:
+*     this
+*        Pointer to the Ellipse.
+*     pset
+*        Pointer to the PointSet. The points are assumed to refer to the
+*        base Frame of the FrameSet encapsulated by "this".
+*     unc
+*        Pointer to a Region representing the uncertainties in the points
+*        given by "pset". The Region is assumed to represent the base Frame
+*        of the FrameSet encapsulated by "this". Zero uncertainity is assumed
+*        if NULL is supplied.
+*     mask
+*        Pointer to location at which to return a pointer to a newly
+*        allocated dynamic array of ints. The number of elements in this
+*        array is equal to the value of the Npoint attribute of "pset".
+*        Each element in the returned array is set to 1 if the
+*        corresponding position in "pset" is on the boundary of the Region
+*        and is set to zero otherwise. A NULL value may be supplied
+*        in which case no array is created. If created, the array should
+*        be freed using astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the points all fall on the boundary of the given
+*     Region, to within the tolerance specified. Zero otherwise.
+
+*/
+
+/* Local variables: */
+   AstEllipse *large_ellipse;   /* Ellipse slightly larger than "this" */
+   AstEllipse *small_ellipse;   /* Ellipse slightly smaller than "this" */
+   AstEllipse *this;            /* Pointer to the Ellipse structure. */
+   AstFrame *frm;               /* Base Frame in supplied Ellipse */
+   AstPointSet *ps1;            /* Points masked by larger Ellipse */
+   AstPointSet *ps2;            /* Points masked by larger and smaller Ellipsees */
+   AstRegion *tunc;             /* Uncertainity Region from "this" */
+   double **ptr;                /* Pointer to axis values in "ps2" */
+   double *p;                   /* Pointer to next axis value */
+   double *safe;                /* An interior point in "this" */
+   double drad;                 /* Radius increment corresponding to border width */
+   double l1;                   /* Length of bounding box diagonal */
+   double l2;                   /* Length of bounding box diagonal */
+   double lbnd_tunc[2];         /* Lower bounds of "this" uncertainty Region */
+   double lbnd_unc[2];          /* Lower bounds of supplied uncertainty Region */
+   double lim;                  /* Smallest semi-minor/major axis length */
+   double p1[2];                /* New ellipse axis lengths */
+   double ubnd_tunc[2];         /* Upper bounds of "this" uncertainty Region */
+   double ubnd_unc[2];          /* Upper bounds of supplied uncertainty Region */
+   int i;                       /* Axis index */
+   int j;                       /* Point index */
+   int np;                      /* No. of supplied points */
+   int result;                  /* Returned flag */
+
+/* Initialise */
+   result = 0;
+   if( mask ) *mask = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a pointer to the Ellipse structure. */
+   this = (AstEllipse *) this_region;
+
+/* Check the supplied PointSet has 2 axis values per point. */
+   if( astGetNcoord( pset ) != 2 && astOK ) {
+      astError( AST__INTER, "astRegPins(%s): Illegal number of axis "
+                "values per point (%d) in the supplied PointSet - should be "
+                "2 (internal AST programming error).", status, astGetClass( this ),
+                astGetNcoord( pset ) );
+   }
+
+/* Get the number of axes in the uncertainty Region and check it is also 2. */
+   if( unc && astGetNaxes( unc ) != 2 && astOK ) {
+      astError( AST__INTER, "astRegPins(%s): Illegal number of axes (%d) "
+                "in the supplied uncertainty Region - should be 2 "
+                "(internal AST programming error).", status, astGetClass( this ),
+                astGetNaxes( unc ) );
+   }
+
+/* Get the centre of the region in the base Frame. We use this as a "safe"
+   interior point within the region. */
+   safe = astRegCentre( this, NULL, NULL, 0, AST__BASE );
+
+/* We now find the maximum distance on each axis that a point can be from the
+   boundary of the Ellipse for it still to be considered to be on the boundary.
+   First get the Region which defines the uncertainty within the Ellipse being
+   checked (in its base Frame), re-centre it on the interior point found
+   above (to avoid problems if the uncertainty region straddles a
+   discontinuity), and get its bounding box. */
+   tunc = astGetUncFrm( this, AST__BASE );
+   if( safe ) astRegCentre( tunc, safe, NULL, 0, AST__CURRENT );
+   astGetRegionBounds( tunc, lbnd_tunc, ubnd_tunc );
+
+/* Find the geodesic length within the base Frame of "this" of the diagonal of
+   the bounding box. */
+   frm = astGetFrame( this_region->frameset, AST__BASE );
+   l1 = astDistance( frm, lbnd_tunc, ubnd_tunc );
+
+/* Also get the Region which defines the uncertainty of the supplied
+   points and get its bounding box. First re-centre the uncertainty at the
+   interior position to avoid problems from uncertainties that straddle a
+   discontinuity. */
+   if( unc ) {
+      if( safe ) astRegCentre( unc, safe, NULL, 0, AST__CURRENT );
+      astGetRegionBounds( unc, lbnd_unc, ubnd_unc );
+
+/* Find the geodesic length of the diagonal of this bounding box. */
+      l2 = astDistance( frm, lbnd_unc, ubnd_unc );
+
+/* Assume zero uncertainty if no "unc" Region was supplied. */
+   } else {
+      l2 = 0.0;
+   }
+
+/* Ensure cached information is available. */
+   Cache( this, status );
+
+/* The required border width is half of the total diagonal of the two bounding
+   boxes. */
+   if( astOK ) {
+      drad = 0.5*( l1 + l2 );
+
+/* Create two new Ellipse, one of which is larger than "this" by the amount
+   found above, and the other of which is smaller than "this" by the amount
+   found above. */
+      p1[ 0 ] = this->a + 0.5*drad;
+      p1[ 1 ] = this->b + 0.5*drad;
+      large_ellipse = astEllipse( frm, 1, this->centre, p1, &(this->angle),
+                                  NULL, " ", status );
+
+      p1[ 0 ] = this->a - 0.5*drad;
+      p1[ 1 ] = this->b - 0.5*drad;
+      lim = 1.0E-6*drad;
+      if( p1[ 0 ] < lim ) p1[ 0 ] = lim;
+      if( p1[ 1 ] < lim ) p1[ 1 ] = lim;
+      small_ellipse = astEllipse( frm, 1, this->centre, p1, &(this->angle),
+                                  NULL, " ", status );
+
+/* Negate the smaller region.*/
+      astNegate( small_ellipse );
+
+/* Points are on the boundary of "this" if they are inside both the large
+   Ellipse and the negated small Ellipse. First transform the supplied PointSet
+   using the large Ellipse, then transform them using the negated smaller
+   Ellipse. */
+      ps1 = astTransform( large_ellipse, pset, 1, NULL );
+      ps2 = astTransform( small_ellipse, ps1, 1, NULL );
+
+/* Get a point to the resulting axis values, and the number of axis
+   values per axis. */
+      ptr = astGetPoints( ps2 );
+      np = astGetNpoint( ps2 );
+
+/* If a mask array is to be returned, create one. */
+      if( mask ) {
+         *mask = astMalloc( sizeof(int)*(size_t) np );
+
+/* Check all the resulting points, setting mask values for all of them. */
+         if( astOK ) {
+
+/* Initialise the mask elements on the basis of the first axis values */
+            result = 1;
+            p = ptr[ 0 ];
+            for( j = 0; j < np; j++ ) {
+               if( *(p++) == AST__BAD ) {
+                  result = 0;
+                  (*mask)[ j ] = 0;
+               } else {
+                  (*mask)[ j ] = 1;
+               }
+            }
+
+/* Now check for bad values on other axes. */
+            for( i = 1; i < 2; i++ ) {
+               p = ptr[ i ];
+               for( j = 0; j < np; j++ ) {
+                  if( *(p++) == AST__BAD ) {
+                     result = 0;
+                     (*mask)[ j ] = 0;
+                  }
+               }
+            }
+         }
+
+/* If no output mask is to be made, we can break out of the check as soon
+   as the first bad value is found. */
+      } else if( astOK ) {
+         result = 1;
+         for( i = 0; i < 2 && result; i++ ) {
+            p = ptr[ i ];
+            for( j = 0; j < np; j++ ) {
+               if( *(p++) == AST__BAD ) {
+                  result = 0;
+                  break;
+               }
+            }
+         }
+      }
+
+/* Free resources. */
+      large_ellipse = astAnnul( large_ellipse );
+      small_ellipse = astAnnul( small_ellipse );
+      ps1 = astAnnul( ps1 );
+      ps2 = astAnnul( ps2 );
+   }
+
+   tunc = astAnnul( tunc );
+   frm = astAnnul( frm );
+   safe = astFree( safe );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) {
+      result = 0;
+      if( mask ) *mask = astAnnul( *mask );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int RegTrace( AstRegion *this_region, int n, double *dist, double **ptr,
+                     int *status ){
+/*
+*+
+*  Name:
+*     RegTrace
+
+*  Purpose:
+*     Return requested positions on the boundary of a 2D Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     int astTraceRegion( AstRegion *this, int n, double *dist, double **ptr );
+
+*  Class Membership:
+*     Ellipse member function (overrides the astTraceRegion method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function returns positions on the boundary of the supplied
+*     Region, if possible. The required positions are indicated by a
+*     supplied list of scalar parameter values in the range zero to one.
+*     Zero corresponds to some arbitrary starting point on the boundary,
+*     and one corresponds to the end (which for a closed region will be
+*     the same place as the start).
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     n
+*        The number of positions to return. If this is zero, the function
+*        returns without action (but the returned function value still
+*        indicates if the method is supported or not).
+*     dist
+*        Pointer to an array of "n" scalar parameter values in the range
+*        0 to 1.0.
+*     ptr
+*        A pointer to an array of pointers. The number of elements in
+*        this array should equal tthe number of axes in the Frame spanned
+*        by the Region. Each element of the array should be a pointer to
+*        an array of "n" doubles, in which to return the "n" values for
+*        the corresponding axis. The contents of the arrays are unchanged
+*        if the supplied Region belongs to a class that does not
+*        implement this method.
+
+*  Returned Value:
+*     Non-zero if the astTraceRegion method is implemented by the class
+*     of Region supplied, and zero if not.
+
+*-
+*/
+
+/* Local Variables; */
+   AstEllipse *this;
+   AstFrame *frm;
+   AstMapping *map;
+   AstPointSet *bpset;
+   AstPointSet *cpset;
+   double **bptr;
+   double ang;
+   double angle;
+   double dx;
+   double dy;
+   double p2[ 2 ];
+   double p[ 2 ];
+   int i;
+   int ncur;
+
+/* Check inherited status, and the number of points to return, returning
+   a non-zero value to indicate that this class supports the astRegTrace
+   method. */
+   if( ! astOK || n == 0 ) return 1;
+
+/* Get a pointer to the Ellipse structure. */
+   this = (AstEllipse *) this_region;
+
+/* Ensure cached information is available. */
+   Cache( this, status );
+
+/* Get a pointer to the base Frame in the encapsulated FrameSet. */
+   frm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* We first determine the required positions in the base Frame of the
+   Region, and then transform them into the current Frame. Get the
+   base->current Mapping, and the number of current Frame axes. */
+   map = astGetMapping( this_region->frameset, AST__BASE, AST__CURRENT );
+
+/* If it's a UnitMap we do not need to do the transformation, so put the
+   base Frame positions directly into the supplied arrays. */
+   if( astIsAUnitMap( map ) ) {
+      bpset = NULL;
+      bptr = ptr;
+      ncur = 2;
+
+/* Otherwise, create a PointSet to hold the base Frame positions (known
+   to be 2D since this is an ellipse). */
+   } else {
+      bpset = astPointSet( n, 2, " ", status );
+      bptr = astGetPoints( bpset );
+      ncur = astGetNout( map );
+   }
+
+/* Check the pointers can be used safely. */
+   if( astOK ) {
+
+/* Loop round each point. */
+      for( i = 0; i < n; i++ ) {
+
+/* The supplied scalar parameter values are the parametric angles, phi,
+   where the ellipse is defined by:
+
+   dx = a.cos( phi )
+   dy = a.sin( phi )
+
+   measured from the primary ellipse. Positive in the sense of rotation from
+   axis 2 to axis 1.  */
+         angle = dist[ i ]*2*AST__DPI;
+
+/* Find the offsets from the centre. "dx" is geodesic distance along the
+   primary axis, and dy is geodesic distance along the secondary axis. */
+         dx = this->a*cos( angle );
+         dy = this->b*sin( angle );
+
+/* Now find the point which corresponds to this dx and dy, taking account
+   of the potential spherical geometry of hte coordinate system. First
+   move a distance "dx" from the centre along the primary axis. The
+   function value returned is the direction of the geodesic curve at the
+   end point. That is, the angle (in radians) between the positive direction
+   of the second axis and the continuation of the geodesic curve at the
+   requested end point. */
+         ang = astOffset2( frm, this->centre, this->angle, dx, p );
+
+/* Now move a distance "dy" from the point found above at right angles to
+   the primary axis. */
+         astOffset2( frm, p, ang + AST__DPIBY2, dy, p2 );
+
+/* Store the resulting axis values. */
+         bptr[ 0 ][ i ] = p2[ 0 ];
+         bptr[ 1 ][ i ] = p2[ 1 ];
+      }
+   }
+
+/* If required, transform the base frame positions into the current
+   Frame, storing them in the supplied array. Then free resources. */
+   if( bpset ) {
+      cpset = astPointSet( n, ncur, " ", status );
+      astSetPoints( cpset, ptr );
+
+      (void) astTransform( map, bpset, 1, cpset );
+
+      cpset = astAnnul( cpset );
+      bpset = astAnnul( bpset );
+   }
+
+/* Free remaining resources. */
+   map = astAnnul( map );
+   frm = astAnnul( frm );
+
+/* Return a non-zero value to indicate that this class supports the
+   astRegTrace method. */
+   return 1;
+}
+
+static void ResetCache( AstRegion *this, int *status ){
+/*
+*  Name:
+*     ResetCache
+
+*  Purpose:
+*     Clear cached information within the supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     void ResetCache( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Region member function (overrides the astResetCache method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function clears cached information from the supplied Region
+*     structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   if( this ) {
+      ( (AstEllipse *) this )->stale = 1;
+      (*parent_resetcache)( this, status );
+   }
+}
+
+static void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status ) {
+/*
+*  Name:
+*     SetRegFS
+
+*  Purpose:
+*     Stores a new FrameSet in a Region
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     Ellipse method (over-rides the astSetRegFS method inherited from
+*     the Region class).
+
+*  Description:
+*     This function creates a new FrameSet and stores it in the supplied
+*     Region. The new FrameSet contains two copies of the supplied
+*     Frame, connected by a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     frm
+*        The Frame to use.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent method to store the FrameSet in the parent Region
+   structure. */
+   (* parent_setregfs)( this_region, frm, status );
+
+/* Indicate that cached information will need to be re-calculated before
+   it is next used. */
+   astResetCache( this_region );
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify the Mapping represented by a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Ellipse method (over-rides the astSimplify method inherited
+*     from the Region class).
+
+*  Description:
+*     This function invokes the parent Region Simplify method, and then
+*     performs any further region-specific simplification.
+*
+*     If the Mapping from base to current Frame is not a UnitMap, this
+*     will include attempting to fit a new Region to the boundary defined
+*     in the current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the simplified Region. A cloned pointer to the
+*     supplied Region will be returned if no simplication could be
+*     performed.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map;           /* Base -> current Mapping */
+   AstMapping *result;        /* Result pointer to return */
+   AstPointSet *mesh;         /* Mesh of current Frame positions */
+   AstPointSet *ps2;          /* Ellipse PointSet in current Frame */
+   AstRegion *new;            /* Pointer to simplified Region */
+   AstRegion *newreg;         /* Equivalent circle or ellipse */
+   AstRegion *this;           /* Pointer to supplied Region structure */
+   AstRegion *unc;            /* Pointer to uncertainty Region */
+   double **ptr2;             /* Pointer axis values in "ps2" */
+   double *cen;               /* Pointer to array holding new centre coords */
+   int ic;                    /* Axis index */
+   int nc;                    /* No. of axis values per point */
+   int ok;                    /* Was the new centre found OK? */
+   int simpler;               /* Has some simplication taken place? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the supplied Region structure. */
+   this = (AstRegion *) this_mapping;
+
+/* Invoke the parent Simplify method inherited from the Region class. This
+   will simplify the encapsulated FrameSet and uncertainty Region. */
+   new = (AstRegion *) (*parent_simplify)( this_mapping, status );
+
+/* Note if any simplification took place. This is assumed to be the case
+   if the pointer returned by the above call is different to the supplied
+   pointer. */
+   simpler = ( new != this );
+
+/* We attempt to simplify the Ellipse by re-defining it within its current
+   Frame. Transforming the Ellipse from its base to its current Frame may
+   result in the region no longer being an ellipse. We test this by
+   transforming a set of bounds on the Ellipse boundary. */
+   map = astGetMapping( new->frameset, AST__BASE, AST__CURRENT );
+
+/* Get a mesh of points covering the Ellipse in its current Frame. */
+   mesh = astRegMesh( new );
+
+/* Get the Region describing the positional uncertainty within the Ellipse in
+   its current Frame. */
+   unc = astGetUncFrm( new, AST__CURRENT );
+
+/* Transform the PointSet holding the ellipse centre into the current
+   Frame, and copy the axis values into a new array. */
+   ps2 = astRegTransform( this, this->points, 1, NULL, NULL );
+   nc = astGetNcoord( ps2 );
+   cen = astMalloc( sizeof( double )*(size_t) nc );
+   ptr2 = astGetPoints( ps2 );
+   if( astOK ) {
+      ok = 1;
+      for( ic = 0; ic < nc; ic++ ) {
+         cen[ ic ] = ptr2[ ic ][ 0 ];
+         if( cen[ ic ] == AST__BAD ) ok = 0;
+      }
+
+/* Find the best fitting Circle (defined in the current Frame) through these
+   points */
+      newreg = ok ? astBestCircle( mesh, cen, unc ) : NULL;
+
+/* See if all points within this mesh fall on the boundary of the best
+   fitting Circle, to within the uncertainty of the Region. */
+      if( newreg && astRegPins( newreg, mesh, NULL, NULL ) ) {
+
+/* If so, use the new Circle in place of the original Region. */
+         (void) astAnnul( new );
+         new = astClone( newreg );
+         simpler =1;
+
+/* Otherwise, if the region is 2-d we see if an Ellipse can represent the
+   mesh. */
+      } else if( ok && nc == 2 ){
+
+/* Find the best fitting Ellipse (defined in the current Frame) through these
+   points */
+         if( newreg ) (void) astAnnul( newreg );
+         newreg = astBestEllipse( mesh, cen, unc );
+
+/* See if all points within this mesh fall on the boundary of the best
+   fitting Ellipse, to within the uncertainty of the Region. */
+         if( newreg && astRegPins( newreg, mesh, NULL, NULL ) ) {
+
+/* If so, use the new Ellipse in place of the original Region. */
+            (void) astAnnul( new );
+            new = astClone( newreg );
+            simpler = 1;
+         }
+      }
+
+/* Free resources. */
+      if( newreg ) newreg = astAnnul( newreg );
+   }
+
+   ps2 = astAnnul( ps2 );
+   cen = astFree( cen );
+   mesh = astAnnul( mesh );
+   unc = astAnnul( unc );
+   map = astAnnul( map );
+
+/* If any simplification could be performed, copy Region attributes from
+   the supplied Region to the returned Region, and return a pointer to it.
+   If the supplied Region had no uncertainty, ensure the returned Region
+   has no uncertainty. Otherwise, return a clone of the supplied pointer. */
+   if( simpler ){
+      astRegOverlay( new, this, 1 );
+      result = (AstMapping *) new;
+
+   } else {
+      new = astAnnul( new );
+      result = astClone( this );
+   }
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a Ellipse to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     Ellipse member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a Ellipse and a set of points encapsulated in a
+*     PointSet and transforms the points by setting axis values to
+*     AST__BAD for all points which are outside the region. Points inside
+*     the region are copied unchanged from input to output.
+
+*  Parameters:
+*     this
+*        Pointer to the Ellipse.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  The forward and inverse transformations are identical for a
+*     Region.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of axes in the Frame represented by the Ellipse.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstEllipse *this;             /* Pointer to Ellipse */
+   AstFrame *frm;                /* Pointer to base Frame in FrameSet */
+   AstPointSet *pset_res;        /* Pointer to PointSet holding resolved components */
+   AstPointSet *pset_tmp;        /* Pointer to PointSet holding base Frame positions*/
+   AstPointSet *result;          /* Pointer to output PointSet */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double **ptr_res;             /* Pointer to resolved components coordinate data */
+   double *px;                   /* Pointer to array of primary axis components */
+   double *py;                   /* Pointer to array of secondary axis components */
+   double c1;                    /* Constant */
+   double c2;                    /* Constant */
+   double d;                     /* Elliptical distance to current point */
+   int closed;                   /* Is the boundary part of the Region? */
+   int coord;                    /* Zero-based index for coordinates */
+   int inside;                   /* Is the point inside the Region? */
+   int ncoord_out;               /* No. of coordinates per output point */
+   int neg;                      /* Has the Region been negated? */
+   int npoint;                   /* No. of points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the Ellipse structure. */
+   this = (AstEllipse *) this_mapping;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Region class. This function validates
+   all arguments and generates an output PointSet if necessary,
+   containing a copy of the input PointSet. */
+   result = (*parent_transform)( this_mapping, in, forward, out, status );
+
+/* Ensure cached information is available. */
+   Cache( this, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* First use the encapsulated FrameSet to transform the supplied positions
+   from the current Frame in the encapsulated FrameSet (the Frame
+   represented by the Region), to the base Frame (the Frame in which the
+   Region is defined). This call also returns a pointer to the base Frame
+   of the encapsulated FrameSet. Note, the returned pointer may be a
+   clone of the "in" pointer, and so we must be carefull not to modify the
+   contents of the returned PointSet. */
+   pset_tmp = astRegTransform( this, in, 0, NULL, &frm );
+
+/* Resolve all the base Frame positions into components parallel to and
+   perpendicular to the primary axis, relative to the ellipse centre. The
+   components are returned in a new PointSet. */
+   pset_res = astResolvePoints( frm, this->centre, this->point1, pset_tmp, NULL );
+
+/* Determine the numbers of points from the component PointSet and obtain
+   pointers for accessing the component and output coordinate values. */
+   npoint = astGetNpoint( pset_res );
+   ptr_res = astGetPoints( pset_res );
+   ncoord_out = astGetNcoord( result );
+   ptr_out = astGetPoints( result );
+
+/* See if the boundary is part of the Region. */
+   closed = astGetClosed( this );
+
+/* See if the Region has been negated. */
+   neg = astGetNegated( this );
+
+/* Form some frequently needed constants. */
+   c1 = 1.0/(this->a*this->a);
+   c2 = 1.0/(this->b*this->b);
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+      px = ptr_res[ 0 ];
+      py = ptr_res[ 1 ];
+
+/* Loop round each point */
+      for ( point = 0; point < npoint; point++, px++, py++ ) {
+
+/* Bad input points result in bad output points */
+         if( *px == AST__BAD || *py == AST__BAD ) {
+            inside = 0;
+
+/* If the input points are good... */
+         } else {
+
+/* Find the elliptical distance from the centre to the supplied point (the
+   ellipse circumference has an "elliptical distance" of 1.0 at all points).*/
+            d = c1*(*px)*(*px) + c2*(*py)*(*py);
+
+/* Now consider whether this radius value puts the point in or out of the
+   Ellipse. */
+            if( d != AST__BAD ){
+               if( neg ) {
+                  if( closed ) {
+                     inside = ( d >= 1.0 );
+                  } else {
+                     inside = ( d > 1.0 );
+                  }
+               } else {
+                  if( closed ) {
+                     inside = ( d <= 1.0 );
+                  } else {
+                     inside = ( d < 1.0 );
+                  }
+               }
+            } else {
+               inside = 0;
+            }
+         }
+
+/* If the point is outside, store bad output values. */
+         if( !inside ) {
+            for ( coord = 0; coord < ncoord_out; coord++ ) {
+               ptr_out[ coord ][ point ] = AST__BAD;
+            }
+         }
+      }
+   }
+
+/* Free resources */
+   pset_tmp = astAnnul( pset_tmp );
+   pset_res = astAnnul( pset_res );
+   frm = astAnnul( frm );
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Ellipse objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Ellipse objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstEllipse *in;             /* Pointer to input Ellipse */
+   AstEllipse *out;            /* Pointer to output Ellipse */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Ellipses. */
+   in = (AstEllipse *) objin;
+   out = (AstEllipse *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output Ellipse. */
+   out->centre = NULL;
+   out->point1 = NULL;
+
+/* Copy dynamic memory contents */
+   out->centre = astStore( NULL, in->centre, sizeof( double )*2 );
+   out->point1 = astStore( NULL, in->point1, sizeof( double )*2 );
+}
+
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Ellipse objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Ellipse objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstEllipse *this;                 /* Pointer to Ellipse */
+
+/* Obtain a pointer to the Ellipse structure. */
+   this = (AstEllipse *) obj;
+
+/* Annul all resources. */
+   this->centre = astFree( this->centre );
+   this->point1 = astFree( this->point1 );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Ellipse objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Ellipse class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Ellipse whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstEllipse *this;                 /* Pointer to the Ellipse structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Ellipse structure. */
+   this = (AstEllipse *) this_object;
+
+/* Write out values representing the instance variables for the
+   Ellipse class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* There are no values to write, so return without further action. */
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAEllipse and astCheckEllipse functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Ellipse,Region)
+astMAKE_CHECK(Ellipse)
+
+AstEllipse *astEllipse_( void *frame_void, int form, const double centre[2],
+                         const double point1[2], const double point2[2],
+                         AstRegion *unc, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astEllipse
+f     AST_ELLIPSE
+
+*  Purpose:
+*     Create a Ellipse.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "ellipse.h"
+c     AstEllipse *astEllipse( AstFrame *frame, int form, const double centre[2],
+c                             const double point1[2], const double point2[2],
+c                             AstRegion *unc, const char *options, ... )
+f     RESULT = AST_ELLIPSE( FRAME, FORM, CENTRE, POINT1, POINT2, UNC, OPTIONS,
+f                           STATUS )
+
+*  Class Membership:
+*     Ellipse constructor.
+
+*  Description:
+*     This function creates a new Ellipse and optionally initialises its
+*     attributes.
+*
+*     A Ellipse is a Region which represents a elliptical area within the
+*     supplied 2-dimensional Frame.
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        A pointer to the Frame in which the region is defined. It must
+*        have exactly 2 axes. A deep copy is taken of the supplied Frame.
+*        This means that any subsequent changes made to the Frame using the
+*        supplied pointer will have no effect the Region.
+c     form
+f     FORM = INTEGER (Given)
+*        Indicates how the ellipse is described by the remaining parameters.
+*        A value of zero indicates that the ellipse is specified by a
+*        centre position and two positions on the circumference. A value of
+*        one indicates that the ellipse is specified by its centre position,
+*        the half-lengths of its two axes, and the orientation of its first
+*        axis.
+c     centre
+f     CENTRE( 2 ) = DOUBLE PRECISION (Given)
+c        An array of 2 doubles,
+f        An array
+*        containing the coordinates at the centre of
+*        the ellipse.
+c     point1
+f     POINT1( 2 ) = DOUBLE PRECISION (Given)
+c        An array of 2 doubles. If "form"
+f        If FORM
+*        is zero, this array should contain the coordinates of one of the four
+*        points where an axis of the ellipse crosses the circumference of the
+*        ellipse.
+c        If "form"
+f        If FORM
+*        is one, it should contain the lengths of semi-major and
+*        semi-minor axes of the ellipse, given as geodesic distances
+*        within the Frame.
+c     point2
+f     POINT2( 2 ) = DOUBLE PRECISION (Given)
+c        An array of 2 doubles. If "form"
+f        If FORM
+*        is zero, this array should containing the coordinates at some other
+*        point on the circumference of the ellipse, distinct from
+c        "point1". If "form"
+f        POINT1. If FORM
+*        is one, the first element of this array should hold the angle
+*        between the second axis of the Frame and the first ellipse axis
+*        (i.e. the ellipse axis which is specified first in the
+c        "point1"
+f        POINT1
+*        array), and the second element will be ignored. The angle should be
+*        given in radians, measured positive in the same sense as rotation
+*        from the positive direction of the second Frame axis to the positive
+*        direction of the first Frame axis.
+c     unc
+f     UNC = INTEGER (Given)
+*        An optional pointer to an existing Region which specifies the
+*        uncertainties associated with the boundary of the Ellipse being created.
+*        The uncertainty in any point on the boundary of the Ellipse is found by
+*        shifting the supplied "uncertainty" Region so that it is centred at
+*        the boundary point being considered. The area covered by the
+*        shifted uncertainty Region then represents the uncertainty in the
+*        boundary position. The uncertainty is assumed to be the same for
+*        all points.
+*
+*        If supplied, the uncertainty Region must be of a class for which
+*        all instances are centro-symetric (e.g. Box, Circle, Ellipse, etc.)
+*        or be a Prism containing centro-symetric component Regions. A deep
+*        copy of the supplied Region will be taken, so subsequent changes to
+*        the uncertainty Region using the supplied pointer will have no
+*        effect on the created Ellipse. Alternatively,
+f        a null Object pointer (AST__NULL)
+c        a NULL Object pointer
+*        may be supplied, in which case a default uncertainty is used
+*        equivalent to a box 1.0E-6 of the size of the Ellipse being created.
+*
+*        The uncertainty Region has two uses: 1) when the
+c        astOverlap
+f        AST_OVERLAP
+*        function compares two Regions for equality the uncertainty
+*        Region is used to determine the tolerance on the comparison, and 2)
+*        when a Region is mapped into a different coordinate system and
+*        subsequently simplified (using
+c        astSimplify),
+f        AST_SIMPLIFY),
+*        the uncertainties are used to determine if the transformed boundary
+*        can be accurately represented by a specific shape of Region.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Ellipse. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Ellipse. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astEllipse()
+f     AST_ELLIPSE = INTEGER
+*        A pointer to the new Ellipse.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstEllipse *new;              /* Pointer to new Ellipse */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the supplied Frame structure. */
+   frame = astCheckFrame( frame_void );
+
+/* Initialise the Ellipse, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitEllipse( NULL, sizeof( AstEllipse ), !class_init, &class_vtab,
+                         "Ellipse", frame, form, centre, point1, point2, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Ellipse's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Ellipse. */
+   return new;
+}
+
+AstEllipse *astEllipseId_( void *frame_void, int form, const double centre[2],
+                         const double point1[2], const double point2[2],
+                         void *unc_void, const char *options, ... ) {
+/*
+*  Name:
+*     astEllipseId_
+
+*  Purpose:
+*     Create a Ellipse.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     AstEllipse *astEllipseId( void *frame_void, int form, const double centre[2],
+*                         const double point1[2], const double point2[2],
+*                         void *unc_void, const char *options, ..., int *status )
+
+*  Class Membership:
+*     Ellipse constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astEllipse constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astEllipse_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astEllipse_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astEllipse_.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The ID value associated with the new Ellipse.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstEllipse *new;              /* Pointer to new Ellipse */
+   AstRegion *unc;               /* Pointer to Region structure */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Frame pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Frame. */
+   frame = astVerifyFrame( astMakePointer( frame_void ) );
+
+/* Obtain a Region pointer from the supplied "unc" ID and validate the
+   pointer to ensure it identifies a valid Region . */
+   unc = unc_void ? astCheckRegion( astMakePointer( unc_void ) ) : NULL;
+
+/* Initialise the Ellipse, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitEllipse( NULL, sizeof( AstEllipse ), !class_init, &class_vtab,
+                        "Ellipse", frame, form, centre, point1, point2, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Ellipse's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new Ellipse. */
+   return astMakeId( new );
+}
+
+AstEllipse *astInitEllipse_( void *mem, size_t size, int init, AstEllipseVtab *vtab,
+                             const char *name, AstFrame *frame, int form,
+                             const double centre[2], const double point1[2],
+                             const double point2[2], AstRegion *unc, int *status ){
+/*
+*+
+*  Name:
+*     astInitEllipse
+
+*  Purpose:
+*     Initialise a Ellipse.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     AstEllipse *astInitEllipse( void *mem, size_t size, int init,
+*                                 AstEllipseVtab *vtab, const char *name,
+*                                 AstFrame *frame, const double centre[2],
+*                                 const double point1[2], const double point2[2],
+*                                 AstRegion *unc )
+
+*  Class Membership:
+*     Ellipse initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Ellipse object. It allocates memory (if necessary) to accommodate
+*     the Ellipse plus any additional data associated with the derived class.
+*     It then initialises a Ellipse structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a Ellipse at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Ellipse is to be initialised.
+*        This must be of sufficient size to accommodate the Ellipse data
+*        (sizeof(Ellipse)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Ellipse (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Ellipse
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Ellipse's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Ellipse.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     frame
+*        A pointer to the Frame in which the region is defined.
+*     form
+*        Indicates how the "point" parameter should be interpreted.
+*        Should be either 0 or 1.
+*     centre
+*        An array of double, with one element for each Frame axis (Naxes
+*        attribute) containing the coordinates of the ellipse centre.
+*     point1
+*        An array of double, with one element for each Frame axis (Naxes
+*        attribute). If "form" is zero, it should contain the coordinates at
+*        the end of one of the axes of the ellipse. If "form" is one, it
+*        should contain the semi-major and semi-minor axes of the ellipse.
+*     point2
+*        An array of double, with one element for each Frame axis (Naxes
+*        attribute). If "form" is zero, it should contain the coordinates at
+*        some other point on the circumference of the ellipse. If "form" is
+*        one, element [1] is ignored and element [0] should contain the
+*        angle from the second frame axis to the first ellipse axis, given in
+*        radians, measured positive in the same sense as rotation from the
+*        positive direction of the second Frame axis to the positive
+*        direction of the first Frame axis. The "first" ellipse axis is
+*        whichever of the semi-major or semi-minor axis is specified first in
+*        the "point1" array.
+*     unc
+*        A pointer to a Region which specifies the uncertainty in the
+*        supplied positions (all points on the boundary of the new Ellipse
+*        being initialised are assumed to have the same uncertainty). A NULL
+*        pointer can be supplied, in which case default uncertainties equal to
+*        1.0E-6 of the dimensions of the new Ellipse's bounding box are used.
+*        If an uncertainty Region is supplied, it must be either a Box, a
+*        Circle or an Ellipse, and its encapsulated Frame must be related
+*        to the Frame supplied for parameter "frame" (i.e. astConvert
+*        should be able to find a Mapping between them). Two positions
+*        the "frame" Frame are considered to be co-incident if their
+*        uncertainty Regions overlap. The centre of the supplied
+*        uncertainty Region is immaterial since it will be re-centred on the
+*        point being tested before use. A deep copy is taken of the supplied
+*        Region.
+
+*  Returned Value:
+*     A pointer to the new Ellipse.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstEllipse *new;          /* Pointer to new Ellipse */
+   AstPointSet *pset;        /* PointSet to pass to Region initialiser */
+   double **ptr;             /* Pointer to coords data in pset */
+   const double *p1;         /* Pointer to circumference point 1 */
+   const double *p2;         /* Pointer to circumference point 2 */
+   int i;                    /* axis index */
+   int nc;                   /* No. of axes */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitEllipseVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the supplied value for "form" is legal. */
+   if( form != 0 && form != 1 && astOK ) {
+      astError( AST__BADIN, "astInitEllipse(%s): The value supplied for "
+                "parameter \"form\" (%d) is illegal - it should be 0 or 1 "
+                "(programming error).", status, name, form );
+   }
+
+/* Get the number of axis values required for each position. */
+   nc = astGetNaxes( frame );
+
+/* Report an error if the Frame is not 2-dimensional. */
+   if( nc != 2 ) {
+      astError( AST__BADIN, "astInitEllipse(%s): The supplied %s has %d "
+                "axes - ellipses must have exactly 2 axes.", status, name,
+                astGetClass( frame ), nc );
+   }
+
+/* If the ellipse is specified by axis lengths and orientation, find two
+   points on the circumference (ends of the two ellipse axes). */
+   if( form == 1 ) {
+      p1 = astMalloc( sizeof( double )*2 );
+      p2 = astMalloc( sizeof( double )*2 );
+      if( astOK ) {
+         astOffset2( frame, centre, *point2, point1[ 0 ], (double *) p1 );
+         astOffset2( frame, centre, *point2 + AST__DPIBY2, point1[ 1 ],
+                     (double *) p2 );
+      }
+
+/* If the ellipse is specified by two points on the circumference, use
+   them. */
+   } else {
+      p1 = point1;
+      p2 = point2;
+   }
+
+/* Create a PointSet to hold the supplied values, and get points to the
+   data arrays. */
+   pset = astPointSet( 3, nc, " ", status );
+   ptr = astGetPoints( pset );
+
+/* Copy the supplied coordinates into the PointSet, checking that no bad
+   values have been supplied. */
+   for( i = 0; astOK && i < nc; i++ ) {
+      if( centre[ i ] == AST__BAD ) {
+         astError( AST__BADIN, "astInitEllipse(%s): The value of axis %d is "
+                   "undefined at the ellipse centre.", status, name, i + 1 );
+      }
+      if( astOK && p1[ i ] == AST__BAD ) {
+         astError( AST__BADIN, "astInitEllipse(%s): The value of axis %d is "
+                   "undefined at point 1 on the circumference of "
+                   "the ellipse.", status, name, i + 1 );
+      }
+      if( astOK && p2[ i ] == AST__BAD ) {
+         astError( AST__BADIN, "astInitEllipse(%s): The value of axis %d is "
+                   "undefined at point 2 on the circumference of "
+                   "the ellipse.", status, name, i + 1 );
+      }
+      ptr[ i ][ 0 ] = centre[ i ];
+      ptr[ i ][ 1 ] = p1[ i ];
+      ptr[ i ][ 2 ] = p2[ i ];
+   }
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Initialise a Region structure (the parent class) as the first component
+   within the Ellipse structure, allocating memory if necessary. */
+      new = (AstEllipse *) astInitRegion( mem, size, 0, (AstRegionVtab *) vtab,
+                                         name, frame, pset, unc );
+
+      if ( astOK ) {
+
+/* Initialise the Ellipse data. */
+/* ------------------------ */
+         new->stale = 1;
+
+/* If an error occurred, clean up by deleting the new Ellipse. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Free resources. */
+   pset = astAnnul( pset );
+   if( form == 1 ) {
+      p1 = astFree( (void *) p1 );
+      p2 = astFree( (void *) p2 );
+   }
+
+/* Return a pointer to the new Ellipse. */
+   return new;
+}
+
+AstEllipse *astLoadEllipse_( void *mem, size_t size, AstEllipseVtab *vtab,
+                           const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadEllipse
+
+*  Purpose:
+*     Load a Ellipse.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "ellipse.h"
+*     AstEllipse *astLoadEllipse( void *mem, size_t size, AstEllipseVtab *vtab,
+*                               const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     Ellipse loader.
+
+*  Description:
+*     This function is provided to load a new Ellipse using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Ellipse structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Ellipse at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Ellipse is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Ellipse data (sizeof(Ellipse)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Ellipse (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Ellipse structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstEllipse) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Ellipse. If this is NULL, a pointer
+*        to the (static) virtual function table for the Ellipse class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Ellipse" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Ellipse.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstEllipse *new;              /* Pointer to the new Ellipse */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Ellipse. In this case the
+   Ellipse belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstEllipse );
+      vtab = &class_vtab;
+      name = "Ellipse";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitEllipseVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Ellipse. */
+   new = astLoadRegion( mem, size, (AstRegionVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Ellipse" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* There are no values to read. */
+/* ---------------------------- */
+
+/* Indicate that no cache intermediate results are yet available in the
+   Ellipse structure */
+      new->stale = 1;
+
+/* If an error occurred, clean up by deleting the new Ellipse. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Ellipse pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+void astEllipsePars_( AstEllipse *this, double centre[2], double *a,
+                      double *b, double *angle, double p1[2], double p2[2],
+                      int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Ellipse,EllipsePars))( this, centre, a, b,
+                                            angle, p1, p2, status );
+}
+
+
+
+
+
+
diff --git a/ast/ellipse.h b/ast/ellipse.h
new file mode 100644
index 0000000..cdd46f7
--- /dev/null
+++ b/ast/ellipse.h
@@ -0,0 +1,244 @@
+#if !defined( ELLIPSE_INCLUDED ) /* Include this file only once */
+#define ELLIPSE_INCLUDED
+/*
+*+
+*  Name:
+*     ellipse.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Ellipse class.
+
+*  Invocation:
+*     #include "ellipse.h"
+
+*  Description:
+*     This include file defines the interface to the Ellipse class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The Ellipse class implement a Region which represents a simple interval
+*     on each axis of the encapsulated Frame
+
+*  Inheritance:
+*     The Ellipse class inherits from the Region class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     7-SEP-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "region.h"              /* Coordinate regions (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* Ellipse structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstEllipse {
+
+/* Attributes inherited from the parent class. */
+   AstRegion region;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *centre;               /* Ellipse centre coords */
+   double *point1;               /* Point at end of primary axis */
+   double angle;                 /* Orientation of primary axis */
+   double a;                     /* Half-length of primary axis */
+   double b;                     /* Half-length of secondary axis */
+   double lbx;                   /* Lower x limit of mesh bounding box */
+   double ubx;                   /* Upper y limit of mesh bounding box */
+   double lby;                   /* Lower x limit of mesh bounding box */
+   double uby;                   /* Upper y limit of mesh bounding box */
+   int stale;                    /* Is cached information stale? */
+
+} AstEllipse;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstEllipseVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstRegionVtab region_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   void (* EllipsePars)( AstEllipse *, double[2], double *, double *, double *, double[2], double[2], int * );
+
+} AstEllipseVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstEllipseGlobals {
+   AstEllipseVtab Class_Vtab;
+   int Class_Init;
+} AstEllipseGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitEllipseGlobals_( AstEllipseGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Ellipse)          /* Check class membership */
+astPROTO_ISA(Ellipse)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstEllipse *astEllipse_( void *, int, const double[2], const double[2], const double[2], AstRegion *, const char *, int *, ...);
+#else
+AstEllipse *astEllipseId_( void *, int, const double[2], const double[2], const double[2], AstRegion *, const char *, ... )__attribute__((format(printf,7,8)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstEllipse *astInitEllipse_( void *, size_t, int, AstEllipseVtab *,
+                           const char *, AstFrame *, int, const double[2],
+                           const double[2], const double[2], AstRegion *, int * );
+
+/* Vtab initialiser. */
+void astInitEllipseVtab_( AstEllipseVtab *, const char *, int * );
+
+/* Loader. */
+AstEllipse *astLoadEllipse_( void *, size_t, AstEllipseVtab *,
+                             const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+void astEllipsePars_( AstEllipse *, double[2], double *, double *, double *, double[2], double[2], int * );
+# if defined(astCLASS)           /* Protected */
+AstRegion *astBestEllipse_( AstPointSet *, double *, AstRegion *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckEllipse(this) astINVOKE_CHECK(Ellipse,this,0)
+#define astVerifyEllipse(this) astINVOKE_CHECK(Ellipse,this,1)
+
+/* Test class membership. */
+#define astIsAEllipse(this) astINVOKE_ISA(Ellipse,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astEllipse astINVOKE(F,astEllipse_)
+#else
+#define astEllipse astINVOKE(F,astEllipseId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitEllipse(mem,size,init,vtab,name,frame,form,p1,p2,p3,unc) \
+astINVOKE(O,astInitEllipse_(mem,size,init,vtab,name,frame,form,p1,p2,p3,unc,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitEllipseVtab(vtab,name) astINVOKE(V,astInitEllipseVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadEllipse(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadEllipse_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckEllipse to validate Ellipse pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+#define astEllipsePars(this,centre,a,b,angle,p1,p2) \
+astINVOKE(V,astEllipsePars_(astCheckEllipse(this),centre,a,b,angle,p1,p2,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#define astBestEllipse(pset,cen,unc) astBestEllipse_(pset,cen,unc,STATUS_PTR)
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/ems.h b/ast/ems.h
new file mode 100644
index 0000000..5b6fef3
--- /dev/null
+++ b/ast/ems.h
@@ -0,0 +1,185 @@
+/*+
+ *  Name:
+ *     ems.h
+
+ *  Purpose:
+ *     EMS_ C interface header file.
+
+ *  Language:
+ *     Starlink ANSI C
+
+ *  Description:
+ *     This include file contains the function prototypes for all
+ *     EMS C interface routines and defines EMS__VERSN to be the major
+ *     version number
+
+ *  Authors:
+ *     PCTR: P.C.T. Rees (STARLINK)
+ *     AJC: A.J.Chipperfield (STARLINK)
+ *     TIMJ: Tim Jenness (JAC, Hawaii)
+ *     {enter_new_authors_here}
+
+ *  History:
+ *     19-SEP-1990 (PCTR):
+ *        Original version.
+ *     21-JUN-1991 (PCTR):
+ *        Made all given character strings type "const".
+ *     5-OCT-1993 (PCTR):
+ *        Updated for Vn. 1.2-3
+ *     28-SEP-1994 (AJC):
+ *        V1.4 Added ems_facer_c and ems_errno_c
+ *     21-JUN-1995 (AJC):
+ *        V1.5 Added ems1_starf_c
+ *     13-MAY-1999 (AJC):
+ *        Added the emsXxx form of name
+ *        and #define old_names = new_names
+ *        Removed ems_tune/gtune/show/_c
+ *        Added ems1_get_facility_error
+ *     27-JUL-2001 (AJC):
+ *        Removed emsFmtx
+ *        Add emsExpnd, emsTune
+ *     13-AUG-2001 (AJC):
+ *        Removed emsFioer
+ *        #define EMS__VERSN
+ *     20-SEP-2001 (AJC):
+ *        Added emsSetnc and point ems_setc_c at it
+ *      3-MAR-2006 (TIMJ):
+ *        Add emsSetu / emsSetp / emsSeti64
+ *     30-JUL-2008 (PWD):
+ *        Added emsGtune.
+ *     31-JUL-2008 (PWD):
+ *        Added emsStune and changed emsGtune to return the value as a result.
+ *        Marked emsTune as deprecated.
+ *     10-SEP-2008 (TIMJ):
+ *        Remove fortran prototypes. Should not be in a public include file.
+ *     16-SEP-2008 (TIMJ):
+ *        Remove 3 arg version of emsSetc
+ *     {enter_changes_here}
+
+ *  Bugs:
+ *     {note_any_bugs_here}
+
+ *- */
+
+#ifndef EMS_DEFINED
+#define EMS_DEFINED
+
+/* ANSI types */
+#include <stdarg.h>
+#include <stddef.h>
+#include <inttypes.h>
+
+
+/* EMS Major Version */
+#define EMS__VERSN 2
+
+/* Function Prototypes: */
+void emsAnnul( int *status );
+
+void emsBegin( int *status );
+
+void emsEload( char *param,
+               int *parlen,
+               char *opstr,
+               int *oplen,
+               int *status );
+
+void emsEnd( int * status );
+
+void emsErrno( const char *token,
+               int errval );
+
+void emsExpnd( const char *text,
+               char *opstr,
+               const int maxlen,
+               int *oplen,
+               int *status );
+
+void emsFacer( const char *token,
+               int status );
+
+int emsGtune( const char *key,
+              int *status );
+
+void emsLevel( int *level );
+
+void emsMark( void );
+
+void emsMload( const char *msg,
+               const char *text,
+               char *opstr,
+               int *oplen,
+               int *status );
+
+void emsRenew( void );
+
+void emsRep( const char *err,
+             const char *text,
+             int *status );
+
+void emsRlse( void );
+
+void emsSetc( const char *token,
+              const char *cvalue );
+
+void emsSetnc( const char *token,
+               const char *cvalue,
+               int mxchar );
+
+void emsSetd( const char *token,
+              double dvalue );
+
+void emsSeti( const char *token,
+              int ivalue );
+
+void emsSeti64( const char *token,
+                int64_t ivalue );
+
+void emsSetl( const char *token,
+              int lvalue );
+
+void emsSetr( const char *token,
+              float rvalue );
+
+void emsSetp( const char *token,
+              void * pvalue );
+
+void emsSetu( const char *token,
+              unsigned int ivalue );
+
+void emsStat( int *status );
+
+void emsSyser( const char *token,
+               int systat );
+
+int emsStune( const char *key,
+              const int value,
+              int *status );
+
+/*  Deprecated function. */
+void emsTune( const char *key,
+              const int value,
+              int *status );
+
+/* Internal Functions */
+/* Not for general use */
+int ems1Starf( const char *envar,
+               const char *relpath,
+               const char *acmode,
+               char **filename,
+               int *pathlen );
+
+void ems1_get_facility_error( unsigned int errcode,
+                              char **facility_name,
+                              char **error_ident,
+                              char **error_text );
+
+/* Required by MERS. Not to be used by anyone else */
+
+void ems1Rform( const char *text, const int maxlen, int *iposn, char *string, int *strlength  );
+
+void ems1Gesc( const char *escchr, const char *string, int *iposn );
+
+void ems1Gnam( const char *string, int *iposn, char *name, int *namlen, int *status);
+
+#endif	/* EMS_DEFINED */
diff --git a/ast/erfa.h b/ast/erfa.h
new file mode 100644
index 0000000..1f98296
--- /dev/null
+++ b/ast/erfa.h
@@ -0,0 +1,72 @@
+#if !defined( ERFA_INCLUDED )  /* Include this file only once */
+#define ERFA_INCLUDED
+/*
+*+
+*  Name:
+*     erfa.h
+
+*  Purpose:
+*     Function prototypes for ERFA routines.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Include file
+
+*  Description:
+*     Function prototypes for ERFA routines. Note, the
+*     --with-external_pal configuration option implies that an external
+*     ERFA library will also be used.
+
+*  Authors:
+*     DSBJ: David S Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     23-FEB-2012 (DSB):
+*        Initial version.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software; you can redistribute it and/or
+*     modify it under the terms of the GNU General Public License as
+*     published by the Free Software Foundation; either version 3 of
+*     the License, or (at your option) any later version.
+*
+*     This program is distributed in the hope that it will be
+*     useful, but WITHOUT ANY WARRANTY; without even the implied
+*     warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+*     PURPOSE. See the GNU General Public License for more details.
+*
+*     You should have received a copy of the GNU General Public License
+*     along with this program; if not, write to the Free Software
+*     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+*     USA.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+/* Include configuration results in order to get any definition for the
+   EXTERNAL_PAL macro. This macro is set if the --with-external_pal
+   option was set when AST was configured. */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* If we not using an external ERFA library, rename all ERFA functions so
+   that references to "iauXxx" below get translated to "astIauXxx". */
+#ifndef EXTERNAL_PAL
+#include "erfa2ast.h"
+#endif
+
+/* Include the prototypes defined in the erfa header file. */
+#include "erfa/erfa.h"
+
+#endif
diff --git a/ast/erfa/INFO b/ast/erfa/INFO
new file mode 100644
index 0000000..0719bcc
--- /dev/null
+++ b/ast/erfa/INFO
@@ -0,0 +1,19 @@
+ERFA has received explicit permission from the IAU SOFA Board to re-license
+and re-copyright ERFA from SOFA. The email providing permission is shown here:
+
+> The IAU Standards Of Fundamental Astronomy Board approves the
+> relicensing of a changed SOFA library by the NumFOCUS Foundation to use
+> a "Three Clause BSD" license.  The changed, relicensed version shall
+> differ from the SOFA version in that all function names shall change to
+> use "era" as a prefix in place of "iau", and that the SOFA Board shall
+> be removed as a copyright holder in the relicensed version.
+
+> Catherine
+> Chair, IAU SOFA Board
+> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> HM Nautical Almanac Office
+> United Kingdom Hydrographic Office
+> Admiralty Way
+> Taunton TA1 2DN
+> [email-masked]
+> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/ast/erfa/LICENSE b/ast/erfa/LICENSE
new file mode 100644
index 0000000..8b3e92c
--- /dev/null
+++ b/ast/erfa/LICENSE
@@ -0,0 +1,53 @@
+Copyright (C) 2013-2014, NumFOCUS Foundation.
+All rights reserved.
+
+This library is derived, with permission, from the International
+Astronomical Union's "Standards of Fundamental Astronomy" library,
+available from http://www.iausofa.org.
+
+The ERFA version is intended to retain identical
+functionality to the SOFA library, but made distinct through
+different function and file names, as set out in the SOFA license
+conditions. The SOFA original has a role as a reference standard
+for the IAU and IERS, and consequently redistribution is permitted only
+in its unaltered state. The ERFA version is not subject to this
+restriction and therefore can be included in distributions which do not
+support the concept of "read only" software.
+
+Although the intent is to replicate the SOFA API (other than replacement of
+prefix names) and results (with the exception of bugs; any that are
+discovered will be fixed), SOFA is not responsible for any errors found
+in this version of the library.
+
+If you wish to acknowledge the SOFA heritage, please acknowledge that
+you are using a library derived from SOFA, rather than SOFA itself.
+
+
+TERMS AND CONDITIONS
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1 Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2 Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+3 Neither the name of the Standards Of Fundamental Astronomy Board, the
+   International Astronomical Union nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/ast/erfa/Makefile.am b/ast/erfa/Makefile.am
new file mode 100644
index 0000000..e0dce1b
--- /dev/null
+++ b/ast/erfa/Makefile.am
@@ -0,0 +1,47 @@
+lib_LTLIBRARIES = liberfa.la
+liberfa_la_SOURCES = a2af.c a2tf.c ab.c af2a.c anp.c anpm.c apcg13.c \
+apcg.c apci13.c apci.c apco13.c apco.c apcs13.c apcs.c aper13.c \
+aper.c apio13.c apio.c atci13.c atciq.c atciqn.c atciqz.c atco13.c \
+atic13.c aticq.c aticqn.c atio13.c atioq.c atoc13.c atoi13.c atoiq.c \
+bi00.c bp00.c bp06.c bpn2xy.c c2i00a.c c2i00b.c c2i06a.c c2ibpn.c \
+c2ixy.c c2ixys.c c2s.c c2t00a.c c2t00b.c c2t06a.c c2tcio.c c2teqx.c \
+c2tpe.c c2txy.c cal2jd.c cp.c cpv.c cr.c d2dtf.c d2tf.c dat.c dtdb.c \
+dtf2d.c eceq06.c ee00a.c ee00.c eect00.c eo06a.c epb2jd.c epj2jd.c epv00.c \
+eqeq94.c ecm06.c ee00b.c ee06a.c eform.c eors.c epb.c epj.c eqec06.c \
+era00.c fad03.c fae03.c faf03.c faju03.c fal03.c falp03.c fama03.c \
+fame03.c fane03.c faom03.c fapa03.c fasa03.c faur03.c fave03.c \
+fk52h.c fk5hip.c fk5hz.c fw2m.c fw2xy.c g2icrs.c gc2gd.c gc2gde.c gd2gc.c \
+gd2gce.c gmst00.c gmst06.c gmst82.c gst00a.c gst00b.c gst06a.c \
+gst06.c gst94.c h2fk5.c hfk5z.c icrs2g.c ir.c jd2cal.c jdcalf.c ld.c \
+ldn.c ldsun.c lteceq.c ltecm.c lteqec.c ltpb.c ltp.c ltpecl.c ltpequ.c \
+num00a.c num00b.c num06a.c numat.c nut00a.c nut00b.c \
+nut06a.c nut80.c nutm80.c obl06.c obl80.c p06e.c p2pv.c p2s.c pap.c \
+pas.c pb06.c pdp.c pfw06.c plan94.c pmat00.c pmat06.c pmat76.c \
+pm.c pmp.c pmpx.c pmsafe.c pn00a.c pn00b.c pn00.c pn06a.c \
+pn06.c pn.c pnm00a.c pnm00b.c pnm06a.c pnm80.c pom00.c ppp.c \
+ppsp.c pr00.c prec76.c pv2p.c pv2s.c pvdpv.c pvm.c pvmpv.c pvppv.c \
+pvstar.c pvtob.c pvu.c pvup.c pvxpv.c pxp.c refco.c rm2v.c rv2m.c \
+rx.c rxp.c rxpv.c rxr.c ry.c rz.c s00a.c s00b.c s00.c \
+s06a.c s06.c s2c.c s2p.c s2pv.c s2xpv.c sepp.c seps.c sp00.c \
+starpm.c starpv.c sxp.c sxpv.c taitt.c taiut1.c taiutc.c tcbtdb.c \
+tcgtt.c tdbtcb.c tdbtt.c tf2a.c tf2d.c tr.c trxp.c trxpv.c tttai.c \
+tttcg.c tttdb.c ttut1.c ut1tai.c ut1tt.c ut1utc.c utctai.c utcut1.c \
+xy06.c xys00a.c xys00b.c xys06a.c zp.c zpv.c zr.c
+
+include_HEADERS = erfa.h erfam.h
+
+## Version info is in current : revision : age form
+## A library supports interfaces from current downto current - age
+## Revision is the version of the current interface
+
+## VI_ALL is set in the macro ERFA_LIB_VERSION_INFO in configure.ac
+
+liberfa_la_LDFLAGS = -version-info $(VI_ALL)
+
+
+## Check program
+TESTS = t_erfa_c
+check_PROGRAMS = t_erfa_c
+t_erfa_c_SOURCES = t_erfa_c.c
+AM_CPPFLAGS = -I$(top_srcdir)
+LDADD = $(top_builddir)/src/liberfa.la
diff --git a/ast/erfa/README.rst b/ast/erfa/README.rst
new file mode 100644
index 0000000..418d138
--- /dev/null
+++ b/ast/erfa/README.rst
@@ -0,0 +1,93 @@
+This is the source code repository for ERFA (Essential Routines for
+Fundamental Astronomy).  ERFA is a C library containing key algorithms for
+astronomy, and is based on the `SOFA library <http://www.iausofa.org/>`_ published by the International
+Astronomical Union (IAU).
+
+ERFA is intended to replicate the functionality of SOFA (aside from possible
+bugfixes in ERFA that have not yet been included in SOFA), but is licensed
+under a three-clause BSD license to enable its compatibility with a wide
+range of open source licenses. Permission for this release has been
+obtained from the SOFA board, and is avilable in the ``LICENSE`` file included
+in this source distribution.
+
+Differences from SOFA
+---------------------
+
+This version of ERFA (v1.3.0) is based on SOFA version "20160503_a", with the
+differences outlined below.
+
+ERFA branding
+^^^^^^^^^^^^^
+
+All references to "SOFA" in the source code have been changed to ERFA, and
+functions have the prefix ``era`` instead of ``iau``.
+
+C macro prefixes
+^^^^^^^^^^^^^^^^
+
+All C macros used in ERFA are the same as their SOFA equivalents, but with an
+``ERFA_`` prefix to prevent namespace collisions.
+
+Bugfixes
+^^^^^^^^
+
+ERFA includes smaller changes that may or may not eventually make it into SOFA,
+addressing localized bugs or similar smaller issues:
+
+* ERFA 1.3.0 and SOFA "20160503_a"
+
+  + There are no differences between ERFA 1.3.0 and SOFA "20160503_a".
+
+* ERFA 1.2.0 and SOFA "20150209_a"
+
+  + Typos have been corrected in the documentation of atco13 and atio13 (see https://github.com/liberfa/erfa/issues/29).
+
+Note that issues identified in ERFA should generally also be reported upstream to SOFA at [email-masked].
+
+Building and installing ERFA
+----------------------------
+
+To build and install a released version of ERFA in your OS's standard
+location, simply do::
+
+    ./configure
+    make
+    make install
+
+If you want to run the tests to make sure ERFA built correctly, before
+installing do::
+
+    make check
+
+
+For developers
+^^^^^^^^^^^^^^
+
+If you are using a developer version from github, you will need to first do
+``./bootstrap.sh`` before the above commands. This requires ``autoconf`` and
+``libtool``.
+
+If you wish to build against the ERFA static library without installing, you
+will find it in ``$ERFAROOT/src/.libs/liberfa.a`` after running ``make``.
+
+Creating a single-file version of the source code
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Alternatively, if you wish to bundle the ERFA source code with a separate
+package, you can use the ``source_flattener.py`` script from the
+`erfa-fetch repository`_ to combine
+the ERFA source code into just two files: a ``erfa.c`` source file, and an
+``erfa.h`` include file.  You should run this script like this::
+
+    cd /path/to/erfa-source-code
+    python /path/to/erfa-fetch/source_flattener.py src -n erfa
+
+If possible, however, it is recommended that you provide an option to use any
+copy of the ERFA library that is already installed on the system.
+
+Travis build status
+-------------------
+.. image:: https://travis-ci.org/liberfa/erfa.png
+    :target: https://travis-ci.org/liberfa/erfa
+
+.. _erfa-fetch repository: https://github.com/liberfa/erfa-fetch
diff --git a/ast/erfa/RELEASE.rst b/ast/erfa/RELEASE.rst
new file mode 100644
index 0000000..3726fdc
--- /dev/null
+++ b/ast/erfa/RELEASE.rst
@@ -0,0 +1,180 @@
+Instructions for releasing ERFA
+===============================
+
+* Clone the ERFA repository from github (if you haven't already done so),
+  and change to the ERFA directory.
+
+* Make sure you are on the "master" branch from the "liberfa" github 
+  repository and have the latest version (if you have a fresh clone, this
+  should already be the case).
+
+* If a new version of SOFA exists, run `sofa_deriver.py` from the `erfa-fetch
+  repository`_ in its own directory.  That will create a directory called `erfa`
+  inside the `erfa-fetch` directory, and   you should copy its contents to the 
+  `src` directory of `erfa`.  Add any new C files or header files added by SOFA 
+  to ``src/Makefile.am``, as appropriate. Use ``git diff`` in `erfa` to inspect 
+  the changes, and then commit and push them to github.
+
+* Update the version number in the `AC_INIT` macro of `configure.ac` to
+  the version number you are about to release, and also update the version 
+  mentioned in `README.rst`. Follow the instructions in 
+  `Version numbering` below.
+
+* Update the version info of the shared library in the `ERFA_LIB_VERSION_INFO` 
+  macro of `configure.ac`. Follow the instructions in `Version numbering` below.
+
+* Commit these changes using ``git commit``, with a commit message like 
+  ``Preparing release v0.0.1``.
+
+* Run `./bootstrap.sh`: you need `automake`, `autoconf` and `libtool` 
+  installed.  If no errors appear, this will create a new `./configure`
+  file.
+
+* Run ``./configure``, which should create a `Makefile` in the top level 
+  directory and in ./src
+
+* Run ``make check``, which will build the library and run the tests -
+  make sure they pass before proceeding.
+
+* Run ``make distcheck``: this creates the distribution tarball, 
+  unpackages it and runs the check inside the untarred directory.
+  The resulting tarball will be named e.g., `erfa-0.0.1.tar.gz` and
+  will be placed in the working directory.
+
+* Tag the current commit with the version number.  A signed tag is preferred if 
+  you have an a signing key (e.g., do ``git tag -s v0.0.1``).  
+
+* Push up your changes and the new tag to github: 
+  ``git push --tags origin master``. (The command here assumes the git remote
+  "origin" points to the "liberfa/erfa" repository.  If not, substitute the
+  appropriate name.)
+
+* Go to the "liberfa/erfa" repository for the github page, and click on the
+  "releases" button, and then the release corresponding to the tag you just 
+  made. 
+
+* Click on the "Draft release notes or downloads" button (or it might be 
+  "Edit release").  Put the version number as the title (e.g., ``v0.0.1``)and 
+  for the description put ``See `README.rst` for release notes.``
+
+* Upload the tarball you created (e.g., `erfa-0.0.1.tar.gz`) by dropping it
+  in the area that says "Attach binaries for this release  by dropping them 
+  here." 
+
+* Click the "Publish release" button.
+
+* Update the release listing on Github Pages to include this release:
+  Do ``git checkout gh-pages``, add a new ``<li>...</li>`` entry for the
+  release in `index.html`, do ``git commit``, and then
+  ``git push origin gh-pages``.
+
+Version numbering
+=================
+
+ERFA needs to provide two different version numbers.  You need to update both.
+The first is the 
+**package version number** or **version number** proper. ERFA uses 
+`semantic versioning <http://semver.org/>`_ to create this number.
+For more on this choice, see 
+`liberfa/erfa#6 <https://github.com/liberfa/erfa/issues/6>`_.
+
+The second number is `shared library version info`. When a program has been 
+linked with the ERFA shared library, the dynamic linker checks the version
+info of the library requested by the program with those of the libraries 
+present if the system. This version info is important to binary distributions
+(such as Linux distributions). ERFA uses `libtool versioning <http://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html>`_.
+
+
+Package version number
+----------------------
+
+Semantic versioning dictates how to change the version number according to
+changes to the API of the library. In the case of ERFA the API is:
+
+  * The public C macros defined in erfam.h
+  * The names, return types, number of arguments and types of the functions in erfa.h
+
+To update the package version, the release manager has to check the relevant
+information about the release, such as:
+
+  * upstream SOFA documentation in http://www.iausofa.org/current_changes.html
+  * relevant bug reports in the github project page
+
+If the version is given in the form MAJOR.MINOR.PATCH, then
+
+  * if there is a backwards incompatible change (function removed, types of
+    arguments altered, macros renamed...) then increase MAJOR by one and set 
+    the others to zero.
+  * else if there is backwards compatible change (new function added or 
+    new macro added) then do not change MAJOR, increase MINOR by one and 
+    set PATCH to zero.
+  * else
+        you are either fixing a bug or making other improvements. Increase
+        patch by one and do not change the others.
+
+Change the version number in the `AC_INIT` macro and in `README.rst`
+
+Shared library version info
+---------------------------
+
+For the shared  library version info, we are only interested in a subset of
+the API, the **interfaces of the shared library**. As the C macros are 
+interpolated away at compile time, the interfaces in the ERFA 
+shared library are:
+
+  * The names, return types, number of arguments and types of the functions 
+  
+Again, the release manager has to review the relevant information:
+
+  * upstream SOFA documentation in http://www.iausofa.org/current_changes.html
+  * relevant bug reports in the github project page
+
+The shared library version info is stored in three numbers called *current*, 
+*revision* and *age*. These numbers appear in the macro `ERFA_LIB_VERSION_INFO` 
+in the mentioned order.
+
+If the version is given in the form CURRENT,REVISION,AGE then
+
+  * if there is a backwards incompatible change (function removed, types of
+    arguments altered...) then increase CURRENT by one and set 
+    the others to zero (c,r,a -> c+1,0,0).
+  * else if there is backwards compatible change (new function added)
+    then increase both CURRENT and AGE by one, set REVISON to zero 
+    (c,r,a -> c+1,0,a+1).
+  * else if the library code has been modified at all
+    then increase REVISION by one (c,r,a -> c,r+1,a)
+  * else
+       do not change the version info (c,r,a -> c,r,a)
+
+Change the verion info in `ERFA_LIB_VERSION_INFO`
+
+Examples
+---------
+We start with ERFA version 1.0.0 and library version info 0,0,0
+
+* SOFA makes a new release. A function is added and two functions change their
+  arguments. This is a backawars incompatible change, so the new package will
+  have version 2.0.0 and the shared library version info will be 1,0,0
+
+* We forgot to add README.rst to the release. We make a new one. The change
+  is a bugfix (no API changes), the new release will be 2.0.1. The shared
+  library version is not modified (no changes in the library source code).
+
+* SOFA makes a new release. They just add a new function. The new package
+  version will be 2.1.0. The shared library info will be 2,0,1 (both current
+  and age are incremented).
+
+* SOFA makes a new relase fixing some bugs in the code without changing the 
+  API. New package version is 2.1.1. The shared library version is 2,1,1
+
+* A contributor finds a bug in ERFA. The fix doesn't change the API. New
+  package version is 2.1.2. The shared library version is 2,2,1
+
+* SOFA makes a new release incorporating the bug fix and adding new functions.
+  The new package version is 2.2.0. The shared library version is 3,0,2
+
+* SOFA makes a new release removing functions. This is a backawars 
+  incompatible change, so the new package will
+  have version 3.0.0 and the shared library version info will be 4,0,0
+
+.. _erfa-fetch repository: https://github.com/liberfa/erfa-fetch
diff --git a/ast/erfa/a2af.c b/ast/erfa/a2af.c
new file mode 100644
index 0000000..0101630
--- /dev/null
+++ b/ast/erfa/a2af.c
@@ -0,0 +1,129 @@
+#include "erfa.h"
+
+void eraA2af(int ndp, double angle, char *sign, int idmsf[4])
+/*
+**  - - - - - - - -
+**   e r a A 2 a f
+**  - - - - - - - -
+**
+**  Decompose radians into degrees, arcminutes, arcseconds, fraction.
+**
+**  Given:
+**     ndp     int     resolution (Note 1)
+**     angle   double  angle in radians
+**
+**  Returned:
+**     sign    char    '+' or '-'
+**     idmsf   int[4]  degrees, arcminutes, arcseconds, fraction
+**
+**  Called:
+**     eraD2tf      decompose days to hms
+**
+**  Notes:
+**
+**  1) The argument ndp is interpreted as follows:
+**
+**     ndp         resolution
+**      :      ...0000 00 00
+**     -7         1000 00 00
+**     -6          100 00 00
+**     -5           10 00 00
+**     -4            1 00 00
+**     -3            0 10 00
+**     -2            0 01 00
+**     -1            0 00 10
+**      0            0 00 01
+**      1            0 00 00.1
+**      2            0 00 00.01
+**      3            0 00 00.001
+**      :            0 00 00.000...
+**
+**  2) The largest positive useful value for ndp is determined by the
+**     size of angle, the format of doubles on the target platform, and
+**     the risk of overflowing idmsf[3].  On a typical platform, for
+**     angle up to 2pi, the available floating-point precision might
+**     correspond to ndp=12.  However, the practical limit is typically
+**     ndp=9, set by the capacity of a 32-bit int, or ndp=4 if int is
+**     only 16 bits.
+**
+**  3) The absolute value of angle may exceed 2pi.  In cases where it
+**     does not, it is up to the caller to test for and handle the
+**     case where angle is very nearly 2pi and rounds up to 360 degrees,
+**     by testing for idmsf[0]=360 and setting idmsf[0-3] to zero.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Hours to degrees * radians to turns */
+   const double F = 15.0 / ERFA_D2PI;
+
+
+/* Scale then use days to h,m,s function. */
+   eraD2tf(ndp, angle*F, sign, idmsf);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/a2tf.c b/ast/erfa/a2tf.c
new file mode 100644
index 0000000..ac3aa98
--- /dev/null
+++ b/ast/erfa/a2tf.c
@@ -0,0 +1,125 @@
+#include "erfa.h"
+
+void eraA2tf(int ndp, double angle, char *sign, int ihmsf[4])
+/*
+**  - - - - - - - -
+**   e r a A 2 t f
+**  - - - - - - - -
+**
+**  Decompose radians into hours, minutes, seconds, fraction.
+**
+**  Given:
+**     ndp     int     resolution (Note 1)
+**     angle   double  angle in radians
+**
+**  Returned:
+**     sign    char    '+' or '-'
+**     ihmsf   int[4]  hours, minutes, seconds, fraction
+**
+**  Called:
+**     eraD2tf      decompose days to hms
+**
+**  Notes:
+**
+**  1) The argument ndp is interpreted as follows:
+**
+**     ndp         resolution
+**      :      ...0000 00 00
+**     -7         1000 00 00
+**     -6          100 00 00
+**     -5           10 00 00
+**     -4            1 00 00
+**     -3            0 10 00
+**     -2            0 01 00
+**     -1            0 00 10
+**      0            0 00 01
+**      1            0 00 00.1
+**      2            0 00 00.01
+**      3            0 00 00.001
+**      :            0 00 00.000...
+**
+**  2) The largest positive useful value for ndp is determined by the
+**     size of angle, the format of doubles on the target platform, and
+**     the risk of overflowing ihmsf[3].  On a typical platform, for
+**     angle up to 2pi, the available floating-point precision might
+**     correspond to ndp=12.  However, the practical limit is typically
+**     ndp=9, set by the capacity of a 32-bit int, or ndp=4 if int is
+**     only 16 bits.
+**
+**  3) The absolute value of angle may exceed 2pi.  In cases where it
+**     does not, it is up to the caller to test for and handle the
+**     case where angle is very nearly 2pi and rounds up to 24 hours,
+**     by testing for ihmsf[0]=24 and setting ihmsf[0-3] to zero.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Scale then use days to h,m,s function. */
+   eraD2tf(ndp, angle/ERFA_D2PI, sign, ihmsf);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ab.c b/ast/erfa/ab.c
new file mode 100644
index 0000000..5d56656
--- /dev/null
+++ b/ast/erfa/ab.c
@@ -0,0 +1,137 @@
+#include "erfa.h"
+
+void eraAb(double pnat[3], double v[3], double s, double bm1,
+           double ppr[3])
+/*
+**  - - - - - -
+**   e r a A b
+**  - - - - - -
+**
+**  Apply aberration to transform natural direction into proper
+**  direction.
+**
+**  Given:
+**    pnat    double[3]   natural direction to the source (unit vector)
+**    v       double[3]   observer barycentric velocity in units of c
+**    s       double      distance between the Sun and the observer (au)
+**    bm1     double      sqrt(1-|v|^2): reciprocal of Lorenz factor
+**
+**  Returned:
+**    ppr     double[3]   proper direction to source (unit vector)
+**
+**  Notes:
+**
+**  1) The algorithm is based on Expr. (7.40) in the Explanatory
+**     Supplement (Urban & Seidelmann 2013), but with the following
+**     changes:
+**
+**     o  Rigorous rather than approximate normalization is applied.
+**
+**     o  The gravitational potential term from Expr. (7) in
+**        Klioner (2003) is added, taking into account only the Sun's
+**        contribution.  This has a maximum effect of about
+**        0.4 microarcsecond.
+**
+**  2) In almost all cases, the maximum accuracy will be limited by the
+**     supplied velocity.  For example, if the ERFA eraEpv00 function is
+**     used, errors of up to 5 microarcseconds could occur.
+**
+**  References:
+**
+**     Urban, S. & Seidelmann, P. K. (eds), Explanatory Supplement to
+**     the Astronomical Almanac, 3rd ed., University Science Books
+**     (2013).
+**
+**     Klioner, Sergei A., "A practical relativistic model for micro-
+**     arcsecond astrometry in space", Astr. J. 125, 1580-1597 (2003).
+**
+**  Called:
+**     eraPdp       scalar product of two p-vectors
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int i;
+   double pdv, w1, w2, r2, w, p[3], r;
+
+
+   pdv = eraPdp(pnat, v);
+   w1 = 1.0 + pdv/(1.0 + bm1);
+   w2 = ERFA_SRS/s;
+   r2 = 0.0;
+   for (i = 0; i < 3; i++) {
+      w = pnat[i]*bm1 + w1*v[i] + w2*(v[i] - pdv*pnat[i]);
+      p[i] = w;
+      r2 = r2 + w*w;
+   }
+   r = sqrt(r2);
+   for (i = 0; i < 3; i++) {
+      ppr[i] = p[i]/r;
+   }
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/af2a.c b/ast/erfa/af2a.c
new file mode 100644
index 0000000..b74dd75
--- /dev/null
+++ b/ast/erfa/af2a.c
@@ -0,0 +1,116 @@
+#include "erfa.h"
+#include <stdlib.h>
+
+int eraAf2a(char s, int ideg, int iamin, double asec, double *rad)
+/*
+**  - - - - - - - -
+**   e r a A f 2 a
+**  - - - - - - - -
+**
+**  Convert degrees, arcminutes, arcseconds to radians.
+**
+**  Given:
+**     s         char    sign:  '-' = negative, otherwise positive
+**     ideg      int     degrees
+**     iamin     int     arcminutes
+**     asec      double  arcseconds
+**
+**  Returned:
+**     rad       double  angle in radians
+**
+**  Returned (function value):
+**               int     status:  0 = OK
+**                                1 = ideg outside range 0-359
+**                                2 = iamin outside range 0-59
+**                                3 = asec outside range 0-59.999...
+**
+**  Notes:
+**
+**  1)  The result is computed even if any of the range checks fail.
+**
+**  2)  Negative ideg, iamin and/or asec produce a warning status, but
+**      the absolute value is used in the conversion.
+**
+**  3)  If there are multiple errors, the status value reflects only the
+**      first, the smallest taking precedence.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* Compute the interval. */
+   *rad  = ( s == '-' ? -1.0 : 1.0 ) *
+           ( 60.0 * ( 60.0 * ( (double) abs(ideg) ) +
+                             ( (double) abs(iamin) ) ) +
+                                        fabs(asec) ) * ERFA_DAS2R;
+
+/* Validate arguments and return status. */
+   if ( ideg < 0 || ideg > 359 ) return 1;
+   if ( iamin < 0 || iamin > 59 ) return 2;
+   if ( asec < 0.0 || asec >= 60.0 ) return 3;
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/anp.c b/ast/erfa/anp.c
new file mode 100644
index 0000000..dcd678e
--- /dev/null
+++ b/ast/erfa/anp.c
@@ -0,0 +1,91 @@
+#include "erfa.h"
+
+double eraAnp(double a)
+/*
+**  - - - - - - -
+**   e r a A n p
+**  - - - - - - -
+**
+**  Normalize angle into the range 0 <= a < 2pi.
+**
+**  Given:
+**     a        double     angle (radians)
+**
+**  Returned (function value):
+**              double     angle in range 0-2pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double w;
+
+
+   w = fmod(a, ERFA_D2PI);
+   if (w < 0) w += ERFA_D2PI;
+
+   return w;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/anpm.c b/ast/erfa/anpm.c
new file mode 100644
index 0000000..bd1e059
--- /dev/null
+++ b/ast/erfa/anpm.c
@@ -0,0 +1,91 @@
+#include "erfa.h"
+
+double eraAnpm(double a)
+/*
+**  - - - - - - - -
+**   e r a A n p m
+**  - - - - - - - -
+**
+**  Normalize angle into the range -pi <= a < +pi.
+**
+**  Given:
+**     a        double     angle (radians)
+**
+**  Returned (function value):
+**              double     angle in range +/-pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double w;
+
+
+   w = fmod(a, ERFA_D2PI);
+   if (fabs(w) >= ERFA_DPI) w -= ERFA_DSIGN(ERFA_D2PI, a);
+
+   return w;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/apcg.c b/ast/erfa/apcg.c
new file mode 100644
index 0000000..e49d4ae
--- /dev/null
+++ b/ast/erfa/apcg.c
@@ -0,0 +1,181 @@
+#include "erfa.h"
+
+void eraApcg(double date1, double date2,
+             double ebpv[2][3], double ehp[3],
+             eraASTROM *astrom)
+/*
+**  - - - - - - - -
+**   e r a A p c g
+**  - - - - - - - -
+**
+**  For a geocentric observer, prepare star-independent astrometry
+**  parameters for transformations between ICRS and GCRS coordinates.
+**  The Earth ephemeris is supplied by the caller.
+**
+**  The parameters produced by this function are required in the
+**  parallax, light deflection and aberration parts of the astrometric
+**  transformation chain.
+**
+**  Given:
+**     date1  double       TDB as a 2-part...
+**     date2  double       ...Julian Date (Note 1)
+**     ebpv   double[2][3] Earth barycentric pos/vel (au, au/day)
+**     ehp    double[3]    Earth heliocentric position (au)
+**
+**  Returned:
+**     astrom eraASTROM*   star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       unchanged
+**      xpl    double       unchanged
+**      ypl    double       unchanged
+**      sphi   double       unchanged
+**      cphi   double       unchanged
+**      diurab double       unchanged
+**      eral   double       unchanged
+**      refa   double       unchanged
+**      refb   double       unchanged
+**
+**  Notes:
+**
+**  1) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  For most
+**     applications of this function the choice will not be at all
+**     critical.
+**
+**     TT can be used instead of TDB without any significant impact on
+**     accuracy.
+**
+**  2) All the vectors are with respect to BCRS axes.
+**
+**  3) This is one of several functions that inserts into the astrom
+**     structure star-independent parameters needed for the chain of
+**     astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**     The various functions support different classes of observer and
+**     portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**     Those with names ending in "13" use contemporary ERFA models to
+**     compute the various ephemerides.  The others accept ephemerides
+**     supplied by the caller.
+**
+**     The transformation from ICRS to GCRS covers space motion,
+**     parallax, light deflection, and aberration.  From GCRS to CIRS
+**     comprises frame bias and precession-nutation.  From CIRS to
+**     observed takes account of Earth rotation, polar motion, diurnal
+**     aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**     transformation), and atmospheric refraction.
+**
+**  4) The context structure astrom produced by this function is used by
+**     eraAtciq* and eraAticq*.
+**
+**  Called:
+**     eraApcs      astrometry parameters, ICRS-GCRS, space observer
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Geocentric observer */
+   double pv[2][3] = { { 0.0, 0.0, 0.0 },
+                       { 0.0, 0.0, 0.0 } };
+
+
+/* Compute the star-independent astrometry parameters. */
+   eraApcs(date1, date2, pv, ebpv, ehp, astrom);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/apcg13.c b/ast/erfa/apcg13.c
new file mode 100644
index 0000000..4157785
--- /dev/null
+++ b/ast/erfa/apcg13.c
@@ -0,0 +1,184 @@
+#include "erfa.h"
+
+void eraApcg13(double date1, double date2, eraASTROM *astrom)
+/*
+**  - - - - - - - - - -
+**   e r a A p c g 1 3
+**  - - - - - - - - - -
+**
+**  For a geocentric observer, prepare star-independent astrometry
+**  parameters for transformations between ICRS and GCRS coordinates.
+**  The caller supplies the date, and ERFA models are used to predict
+**  the Earth ephemeris.
+**
+**  The parameters produced by this function are required in the
+**  parallax, light deflection and aberration parts of the astrometric
+**  transformation chain.
+**
+**  Given:
+**     date1  double     TDB as a 2-part...
+**     date2  double     ...Julian Date (Note 1)
+**
+**  Returned:
+**     astrom eraASTROM* star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       unchanged
+**      xpl    double       unchanged
+**      ypl    double       unchanged
+**      sphi   double       unchanged
+**      cphi   double       unchanged
+**      diurab double       unchanged
+**      eral   double       unchanged
+**      refa   double       unchanged
+**      refb   double       unchanged
+**
+**  Notes:
+**
+**  1) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  For most
+**     applications of this function the choice will not be at all
+**     critical.
+**
+**     TT can be used instead of TDB without any significant impact on
+**     accuracy.
+**
+**  2) All the vectors are with respect to BCRS axes.
+**
+**  3) In cases where the caller wishes to supply his own Earth
+**     ephemeris, the function eraApcg can be used instead of the present
+**     function.
+**
+**  4) This is one of several functions that inserts into the astrom
+**     structure star-independent parameters needed for the chain of
+**     astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**     The various functions support different classes of observer and
+**     portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**     Those with names ending in "13" use contemporary ERFA models to
+**     compute the various ephemerides.  The others accept ephemerides
+**     supplied by the caller.
+**
+**     The transformation from ICRS to GCRS covers space motion,
+**     parallax, light deflection, and aberration.  From GCRS to CIRS
+**     comprises frame bias and precession-nutation.  From CIRS to
+**     observed takes account of Earth rotation, polar motion, diurnal
+**     aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**     transformation), and atmospheric refraction.
+**
+**  5) The context structure astrom produced by this function is used by
+**     eraAtciq* and eraAticq*.
+**
+**  Called:
+**     eraEpv00     Earth position and velocity
+**     eraApcg      astrometry parameters, ICRS-GCRS, geocenter
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double ehpv[2][3], ebpv[2][3];
+
+
+/* Earth barycentric & heliocentric position/velocity (au, au/d). */
+   (void) eraEpv00(date1, date2, ehpv, ebpv);
+
+/* Compute the star-independent astrometry parameters. */
+   eraApcg(date1, date2, ebpv, ehpv[0], astrom);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/apci.c b/ast/erfa/apci.c
new file mode 100644
index 0000000..74dbc3b
--- /dev/null
+++ b/ast/erfa/apci.c
@@ -0,0 +1,190 @@
+#include "erfa.h"
+
+void eraApci(double date1, double date2,
+             double ebpv[2][3], double ehp[3],
+             double x, double y, double s,
+             eraASTROM *astrom)
+/*
+**  - - - - - - - -
+**   e r a A p c i
+**  - - - - - - - -
+**
+**  For a terrestrial observer, prepare star-independent astrometry
+**  parameters for transformations between ICRS and geocentric CIRS
+**  coordinates.  The Earth ephemeris and CIP/CIO are supplied by the
+**  caller.
+**
+**  The parameters produced by this function are required in the
+**  parallax, light deflection, aberration, and bias-precession-nutation
+**  parts of the astrometric transformation chain.
+**
+**  Given:
+**     date1  double       TDB as a 2-part...
+**     date2  double       ...Julian Date (Note 1)
+**     ebpv   double[2][3] Earth barycentric position/velocity (au, au/day)
+**     ehp    double[3]    Earth heliocentric position (au)
+**     x,y    double       CIP X,Y (components of unit vector)
+**     s      double       the CIO locator s (radians)
+**
+**  Returned:
+**     astrom eraASTROM*   star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       unchanged
+**      xpl    double       unchanged
+**      ypl    double       unchanged
+**      sphi   double       unchanged
+**      cphi   double       unchanged
+**      diurab double       unchanged
+**      eral   double       unchanged
+**      refa   double       unchanged
+**      refb   double       unchanged
+**
+**  Notes:
+**
+**  1) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  For most
+**     applications of this function the choice will not be at all
+**     critical.
+**
+**     TT can be used instead of TDB without any significant impact on
+**     accuracy.
+**
+**  2) All the vectors are with respect to BCRS axes.
+**
+**  3) In cases where the caller does not wish to provide the Earth
+**     ephemeris and CIP/CIO, the function eraApci13 can be used instead
+**     of the present function.  This computes the required quantities
+**     using other ERFA functions.
+**
+**  4) This is one of several functions that inserts into the astrom
+**     structure star-independent parameters needed for the chain of
+**     astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**     The various functions support different classes of observer and
+**     portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**     Those with names ending in "13" use contemporary ERFA models to
+**     compute the various ephemerides.  The others accept ephemerides
+**     supplied by the caller.
+**
+**     The transformation from ICRS to GCRS covers space motion,
+**     parallax, light deflection, and aberration.  From GCRS to CIRS
+**     comprises frame bias and precession-nutation.  From CIRS to
+**     observed takes account of Earth rotation, polar motion, diurnal
+**     aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**     transformation), and atmospheric refraction.
+**
+**  5) The context structure astrom produced by this function is used by
+**     eraAtciq* and eraAticq*.
+**
+**  Called:
+**     eraApcg      astrometry parameters, ICRS-GCRS, geocenter
+**     eraC2ixys    celestial-to-intermediate matrix, given X,Y and s
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* Star-independent astrometry parameters for geocenter. */
+   eraApcg(date1, date2, ebpv, ehp, astrom);
+
+/* CIO based BPN matrix. */
+   eraC2ixys(x, y, s, astrom->bpn);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/apci13.c b/ast/erfa/apci13.c
new file mode 100644
index 0000000..c041652
--- /dev/null
+++ b/ast/erfa/apci13.c
@@ -0,0 +1,202 @@
+#include "erfa.h"
+
+void eraApci13(double date1, double date2,
+               eraASTROM *astrom, double *eo)
+/*
+**  - - - - - - - - - -
+**   e r a A p c i 1 3
+**  - - - - - - - - - -
+**
+**  For a terrestrial observer, prepare star-independent astrometry
+**  parameters for transformations between ICRS and geocentric CIRS
+**  coordinates.  The caller supplies the date, and ERFA models are used
+**  to predict the Earth ephemeris and CIP/CIO.
+**
+**  The parameters produced by this function are required in the
+**  parallax, light deflection, aberration, and bias-precession-nutation
+**  parts of the astrometric transformation chain.
+**
+**  Given:
+**     date1  double      TDB as a 2-part...
+**     date2  double      ...Julian Date (Note 1)
+**
+**  Returned:
+**     astrom eraASTROM*  star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       unchanged
+**      xpl    double       unchanged
+**      ypl    double       unchanged
+**      sphi   double       unchanged
+**      cphi   double       unchanged
+**      diurab double       unchanged
+**      eral   double       unchanged
+**      refa   double       unchanged
+**      refb   double       unchanged
+**     eo     double*     equation of the origins (ERA-GST)
+**
+**  Notes:
+**
+**  1) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  For most
+**     applications of this function the choice will not be at all
+**     critical.
+**
+**     TT can be used instead of TDB without any significant impact on
+**     accuracy.
+**
+**  2) All the vectors are with respect to BCRS axes.
+**
+**  3) In cases where the caller wishes to supply his own Earth
+**     ephemeris and CIP/CIO, the function eraApci can be used instead
+**     of the present function.
+**
+**  4) This is one of several functions that inserts into the astrom
+**     structure star-independent parameters needed for the chain of
+**     astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**     The various functions support different classes of observer and
+**     portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**     Those with names ending in "13" use contemporary ERFA models to
+**     compute the various ephemerides.  The others accept ephemerides
+**     supplied by the caller.
+**
+**     The transformation from ICRS to GCRS covers space motion,
+**     parallax, light deflection, and aberration.  From GCRS to CIRS
+**     comprises frame bias and precession-nutation.  From CIRS to
+**     observed takes account of Earth rotation, polar motion, diurnal
+**     aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**     transformation), and atmospheric refraction.
+**
+**  5) The context structure astrom produced by this function is used by
+**     eraAtciq* and eraAticq*.
+**
+**  Called:
+**     eraEpv00     Earth position and velocity
+**     eraPnm06a    classical NPB matrix, IAU 2006/2000A
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**     eraS06       the CIO locator s, given X,Y, IAU 2006
+**     eraApci      astrometry parameters, ICRS-CIRS
+**     eraEors      equation of the origins, given NPB matrix and s
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double ehpv[2][3], ebpv[2][3], r[3][3], x, y, s;
+
+
+/* Earth barycentric & heliocentric position/velocity (au, au/d). */
+   (void) eraEpv00(date1, date2, ehpv, ebpv);
+
+/* Form the equinox based BPN matrix, IAU 2006/2000A. */
+   eraPnm06a(date1, date2, r);
+
+/* Extract CIP X,Y. */
+   eraBpn2xy(r, &x, &y);
+
+/* Obtain CIO locator s. */
+   s = eraS06(date1, date2, x, y);
+
+/* Compute the star-independent astrometry parameters. */
+   eraApci(date1, date2, ebpv, ehpv[0], x, y, s, astrom);
+
+/* Equation of the origins. */
+   *eo = eraEors(r, s);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/apco.c b/ast/erfa/apco.c
new file mode 100644
index 0000000..1b61ab8
--- /dev/null
+++ b/ast/erfa/apco.c
@@ -0,0 +1,264 @@
+#include "erfa.h"
+
+void eraApco(double date1, double date2,
+             double ebpv[2][3], double ehp[3],
+             double x, double y, double s, double theta,
+             double elong, double phi, double hm,
+             double xp, double yp, double sp,
+             double refa, double refb,
+             eraASTROM *astrom)
+/*
+**  - - - - - - - -
+**   e r a A p c o
+**  - - - - - - - -
+**
+**  For a terrestrial observer, prepare star-independent astrometry
+**  parameters for transformations between ICRS and observed
+**  coordinates.  The caller supplies the Earth ephemeris, the Earth
+**  rotation information and the refraction constants as well as the
+**  site coordinates.
+**
+**  Given:
+**     date1  double       TDB as a 2-part...
+**     date2  double       ...Julian Date (Note 1)
+**     ebpv   double[2][3] Earth barycentric PV (au, au/day, Note 2)
+**     ehp    double[3]    Earth heliocentric P (au, Note 2)
+**     x,y    double       CIP X,Y (components of unit vector)
+**     s      double       the CIO locator s (radians)
+**     theta  double       Earth rotation angle (radians)
+**     elong  double       longitude (radians, east +ve, Note 3)
+**     phi    double       latitude (geodetic, radians, Note 3)
+**     hm     double       height above ellipsoid (m, geodetic, Note 3)
+**     xp,yp  double       polar motion coordinates (radians, Note 4)
+**     sp     double       the TIO locator s' (radians, Note 4)
+**     refa   double       refraction constant A (radians, Note 5)
+**     refb   double       refraction constant B (radians, Note 5)
+**
+**  Returned:
+**     astrom eraASTROM*   star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**
+**  Notes:
+**
+**  1) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  For most
+**     applications of this function the choice will not be at all
+**     critical.
+**
+**     TT can be used instead of TDB without any significant impact on
+**     accuracy.
+**
+**  2) The vectors eb, eh, and all the astrom vectors, are with respect
+**     to BCRS axes.
+**
+**  3) The geographical coordinates are with respect to the ERFA_WGS84
+**     reference ellipsoid.  TAKE CARE WITH THE LONGITUDE SIGN
+**     CONVENTION:  the longitude required by the present function is
+**     right-handed, i.e. east-positive, in accordance with geographical
+**     convention.
+**
+**  4) xp and yp are the coordinates (in radians) of the Celestial
+**     Intermediate Pole with respect to the International Terrestrial
+**     Reference System (see IERS Conventions), measured along the
+**     meridians 0 and 90 deg west respectively.  sp is the TIO locator
+**     s', in radians, which positions the Terrestrial Intermediate
+**     Origin on the equator.  For many applications, xp, yp and
+**     (especially) sp can be set to zero.
+**
+**     Internally, the polar motion is stored in a form rotated onto the
+**     local meridian.
+**
+**  5) The refraction constants refa and refb are for use in a
+**     dZ = A*tan(Z)+B*tan^3(Z) model, where Z is the observed
+**     (i.e. refracted) zenith distance and dZ is the amount of
+**     refraction.
+**
+**  6) It is advisable to take great care with units, as even unlikely
+**     values of the input parameters are accepted and processed in
+**     accordance with the models used.
+**
+**  7) In cases where the caller does not wish to provide the Earth
+**     Ephemeris, the Earth rotation information and refraction
+**     constants, the function eraApco13 can be used instead of the
+**     present function.  This starts from UTC and weather readings etc.
+**     and computes suitable values using other ERFA functions.
+**
+**  8) This is one of several functions that inserts into the astrom
+**     structure star-independent parameters needed for the chain of
+**     astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**     The various functions support different classes of observer and
+**     portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**     Those with names ending in "13" use contemporary ERFA models to
+**     compute the various ephemerides.  The others accept ephemerides
+**     supplied by the caller.
+**
+**     The transformation from ICRS to GCRS covers space motion,
+**     parallax, light deflection, and aberration.  From GCRS to CIRS
+**     comprises frame bias and precession-nutation.  From CIRS to
+**     observed takes account of Earth rotation, polar motion, diurnal
+**     aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**     transformation), and atmospheric refraction.
+**
+**  9) The context structure astrom produced by this function is used by
+**     eraAtioq, eraAtoiq, eraAtciq* and eraAticq*.
+**
+**  Called:
+**     eraAper      astrometry parameters: update ERA
+**     eraC2ixys    celestial-to-intermediate matrix, given X,Y and s
+**     eraPvtob     position/velocity of terrestrial station
+**     eraTrxpv     product of transpose of r-matrix and pv-vector
+**     eraApcs      astrometry parameters, ICRS-GCRS, space observer
+**     eraCr        copy r-matrix
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double sl, cl, r[3][3], pvc[2][3], pv[2][3];
+
+
+/* Longitude with adjustment for TIO locator s'. */
+   astrom->along = elong + sp;
+
+/* Polar motion, rotated onto the local meridian. */
+   sl = sin(astrom->along);
+   cl = cos(astrom->along);
+   astrom->xpl = xp*cl - yp*sl;
+   astrom->ypl = xp*sl + yp*cl;
+
+/* Functions of latitude. */
+   astrom->sphi = sin(phi);
+   astrom->cphi = cos(phi);
+
+/* Refraction constants. */
+   astrom->refa = refa;
+   astrom->refb = refb;
+
+/* Local Earth rotation angle. */
+   eraAper(theta, astrom);
+
+/* Disable the (redundant) diurnal aberration step. */
+   astrom->diurab = 0.0;
+
+/* CIO based BPN matrix. */
+   eraC2ixys(x, y, s, r);
+
+/* Observer's geocentric position and velocity (m, m/s, CIRS). */
+   eraPvtob(elong, phi, hm, xp, yp, sp, theta, pvc);
+
+/* Rotate into GCRS. */
+   eraTrxpv(r, pvc, pv);
+
+/* ICRS <-> GCRS parameters. */
+   eraApcs(date1, date2, pv, ebpv, ehp, astrom);
+
+/* Store the CIO based BPN matrix. */
+   eraCr(r, astrom->bpn );
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/apco13.c b/ast/erfa/apco13.c
new file mode 100644
index 0000000..cdd4d4f
--- /dev/null
+++ b/ast/erfa/apco13.c
@@ -0,0 +1,287 @@
+#include "erfa.h"
+
+int eraApco13(double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              eraASTROM *astrom, double *eo)
+/*
+**  - - - - - - - - - -
+**   e r a A p c o 1 3
+**  - - - - - - - - - -
+**
+**  For a terrestrial observer, prepare star-independent astrometry
+**  parameters for transformations between ICRS and observed
+**  coordinates.  The caller supplies UTC, site coordinates, ambient air
+**  conditions and observing wavelength, and ERFA models are used to
+**  obtain the Earth ephemeris, CIP/CIO and refraction constants.
+**
+**  The parameters produced by this function are required in the
+**  parallax, light deflection, aberration, and bias-precession-nutation
+**  parts of the ICRS/CIRS transformations.
+**
+**  Given:
+**     utc1   double     UTC as a 2-part...
+**     utc2   double     ...quasi Julian Date (Notes 1,2)
+**     dut1   double     UT1-UTC (seconds, Note 3)
+**     elong  double     longitude (radians, east +ve, Note 4)
+**     phi    double     latitude (geodetic, radians, Note 4)
+**     hm     double     height above ellipsoid (m, geodetic, Notes 4,6)
+**     xp,yp  double     polar motion coordinates (radians, Note 5)
+**     phpa   double     pressure at the observer (hPa = mB, Note 6)
+**     tc     double     ambient temperature at the observer (deg C)
+**     rh     double     relative humidity at the observer (range 0-1)
+**     wl     double     wavelength (micrometers, Note 7)
+**
+**  Returned:
+**     astrom eraASTROM* star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**     eo     double*    equation of the origins (ERA-GST)
+**
+**  Returned (function value):
+**            int        status: +1 = dubious year (Note 2)
+**                                0 = OK
+**                               -1 = unacceptable date
+**
+**  Notes:
+**
+**  1)  utc1+utc2 is quasi Julian Date (see Note 2), apportioned in any
+**      convenient way between the two arguments, for example where utc1
+**      is the Julian Day Number and utc2 is the fraction of a day.
+**
+**      However, JD cannot unambiguously represent UTC during a leap
+**      second unless special measures are taken.  The convention in the
+**      present function is that the JD day represents UTC days whether
+**      the length is 86399, 86400 or 86401 SI seconds.
+**
+**      Applications should use the function eraDtf2d to convert from
+**      calendar date and time of day into 2-part quasi Julian Date, as
+**      it implements the leap-second-ambiguity convention just
+**      described.
+**
+**  2)  The warning status "dubious year" flags UTCs that predate the
+**      introduction of the time scale or that are too far in the
+**      future to be trusted.  See eraDat for further details.
+**
+**  3)  UT1-UTC is tabulated in IERS bulletins.  It increases by exactly
+**      one second at the end of each positive UTC leap second,
+**      introduced in order to keep UT1-UTC within +/- 0.9s.  n.b. This
+**      practice is under review, and in the future UT1-UTC may grow
+**      essentially without limit.
+**
+**  4)  The geographical coordinates are with respect to the ERFA_WGS84
+**      reference ellipsoid.  TAKE CARE WITH THE LONGITUDE SIGN:  the
+**      longitude required by the present function is east-positive
+**      (i.e. right-handed), in accordance with geographical convention.
+**
+**  5)  The polar motion xp,yp can be obtained from IERS bulletins.  The
+**      values are the coordinates (in radians) of the Celestial
+**      Intermediate Pole with respect to the International Terrestrial
+**      Reference System (see IERS Conventions 2003), measured along the
+**      meridians 0 and 90 deg west respectively.  For many
+**      applications, xp and yp can be set to zero.
+**
+**      Internally, the polar motion is stored in a form rotated onto
+**      the local meridian.
+**
+**  6)  If hm, the height above the ellipsoid of the observing station
+**      in meters, is not known but phpa, the pressure in hPa (=mB), is
+**      available, an adequate estimate of hm can be obtained from the
+**      expression
+**
+**            hm = -29.3 * tsl * log ( phpa / 1013.25 );
+**
+**      where tsl is the approximate sea-level air temperature in K
+**      (See Astrophysical Quantities, C.W.Allen, 3rd edition, section
+**      52).  Similarly, if the pressure phpa is not known, it can be
+**      estimated from the height of the observing station, hm, as
+**      follows:
+**
+**            phpa = 1013.25 * exp ( -hm / ( 29.3 * tsl ) );
+**
+**      Note, however, that the refraction is nearly proportional to
+**      the pressure and that an accurate phpa value is important for
+**      precise work.
+**
+**  7)  The argument wl specifies the observing wavelength in
+**      micrometers.  The transition from optical to radio is assumed to
+**      occur at 100 micrometers (about 3000 GHz).
+**
+**  8)  It is advisable to take great care with units, as even unlikely
+**      values of the input parameters are accepted and processed in
+**      accordance with the models used.
+**
+**  9)  In cases where the caller wishes to supply his own Earth
+**      ephemeris, Earth rotation information and refraction constants,
+**      the function eraApco can be used instead of the present function.
+**
+**  10) This is one of several functions that inserts into the astrom
+**      structure star-independent parameters needed for the chain of
+**      astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**      The various functions support different classes of observer and
+**      portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**      Those with names ending in "13" use contemporary ERFA models to
+**      compute the various ephemerides.  The others accept ephemerides
+**      supplied by the caller.
+**
+**      The transformation from ICRS to GCRS covers space motion,
+**      parallax, light deflection, and aberration.  From GCRS to CIRS
+**      comprises frame bias and precession-nutation.  From CIRS to
+**      observed takes account of Earth rotation, polar motion, diurnal
+**      aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**      transformation), and atmospheric refraction.
+**
+**  11) The context structure astrom produced by this function is used
+**      by eraAtioq, eraAtoiq, eraAtciq* and eraAticq*.
+**
+**  Called:
+**     eraUtctai    UTC to TAI
+**     eraTaitt     TAI to TT
+**     eraUtcut1    UTC to UT1
+**     eraEpv00     Earth position and velocity
+**     eraPnm06a    classical NPB matrix, IAU 2006/2000A
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**     eraS06       the CIO locator s, given X,Y, IAU 2006
+**     eraEra00     Earth rotation angle, IAU 2000
+**     eraSp00      the TIO locator s', IERS 2000
+**     eraRefco     refraction constants for given ambient conditions
+**     eraApco      astrometry parameters, ICRS-observed
+**     eraEors      equation of the origins, given NPB matrix and s
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j;
+   double tai1, tai2, tt1, tt2, ut11, ut12, ehpv[2][3], ebpv[2][3],
+          r[3][3], x, y, s, theta, sp, refa, refb;
+
+
+/* UTC to other time scales. */
+   j = eraUtctai(utc1, utc2, &tai1, &tai2);
+   if ( j < 0 ) return -1;
+   j = eraTaitt(tai1, tai2, &tt1, &tt2);
+   j = eraUtcut1(utc1, utc2, dut1, &ut11, &ut12);
+   if ( j < 0 ) return -1;
+
+/* Earth barycentric & heliocentric position/velocity (au, au/d). */
+   (void) eraEpv00(tt1, tt2, ehpv, ebpv);
+
+/* Form the equinox based BPN matrix, IAU 2006/2000A. */
+   eraPnm06a(tt1, tt2, r);
+
+/* Extract CIP X,Y. */
+   eraBpn2xy(r, &x, &y);
+
+/* Obtain CIO locator s. */
+   s = eraS06(tt1, tt2, x, y);
+
+/* Earth rotation angle. */
+   theta = eraEra00(ut11, ut12);
+
+/* TIO locator s'. */
+   sp = eraSp00(tt1, tt2);
+
+/* Refraction constants A and B. */
+   eraRefco(phpa, tc, rh, wl, &refa, &refb);
+
+/* Compute the star-independent astrometry parameters. */
+   eraApco(tt1, tt2, ebpv, ehpv[0], x, y, s, theta,
+           elong, phi, hm, xp, yp, sp, refa, refb, astrom);
+
+/* Equation of the origins. */
+   *eo = eraEors(r, s);
+
+/* Return any warning status. */
+   return j;
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/apcs.c b/ast/erfa/apcs.c
new file mode 100644
index 0000000..0a34f14
--- /dev/null
+++ b/ast/erfa/apcs.c
@@ -0,0 +1,233 @@
+#include "erfa.h"
+
+void eraApcs(double date1, double date2, double pv[2][3],
+             double ebpv[2][3], double ehp[3],
+             eraASTROM *astrom)
+/*
+**  - - - - - - - -
+**   e r a A p c s
+**  - - - - - - - -
+**
+**  For an observer whose geocentric position and velocity are known,
+**  prepare star-independent astrometry parameters for transformations
+**  between ICRS and GCRS.  The Earth ephemeris is supplied by the
+**  caller.
+**
+**  The parameters produced by this function are required in the space
+**  motion, parallax, light deflection and aberration parts of the
+**  astrometric transformation chain.
+**
+**  Given:
+**     date1  double       TDB as a 2-part...
+**     date2  double       ...Julian Date (Note 1)
+**     pv     double[2][3] observer's geocentric pos/vel (m, m/s)
+**     ebpv   double[2][3] Earth barycentric PV (au, au/day)
+**     ehp    double[3]    Earth heliocentric P (au)
+**
+**  Returned:
+**     astrom eraASTROM*   star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       unchanged
+**      xpl    double       unchanged
+**      ypl    double       unchanged
+**      sphi   double       unchanged
+**      cphi   double       unchanged
+**      diurab double       unchanged
+**      eral   double       unchanged
+**      refa   double       unchanged
+**      refb   double       unchanged
+**
+**  Notes:
+**
+**  1) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  For most
+**     applications of this function the choice will not be at all
+**     critical.
+**
+**     TT can be used instead of TDB without any significant impact on
+**     accuracy.
+**
+**  2) All the vectors are with respect to BCRS axes.
+**
+**  3) Providing separate arguments for (i) the observer's geocentric
+**     position and velocity and (ii) the Earth ephemeris is done for
+**     convenience in the geocentric, terrestrial and Earth orbit cases.
+**     For deep space applications it maybe more convenient to specify
+**     zero geocentric position and velocity and to supply the
+**     observer's position and velocity information directly instead of
+**     with respect to the Earth.  However, note the different units:
+**     m and m/s for the geocentric vectors, au and au/day for the
+**     heliocentric and barycentric vectors.
+**
+**  4) In cases where the caller does not wish to provide the Earth
+**     ephemeris, the function eraApcs13 can be used instead of the
+**     present function.  This computes the Earth ephemeris using the
+**     ERFA function eraEpv00.
+**
+**  5) This is one of several functions that inserts into the astrom
+**     structure star-independent parameters needed for the chain of
+**     astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**     The various functions support different classes of observer and
+**     portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**     Those with names ending in "13" use contemporary ERFA models to
+**     compute the various ephemerides.  The others accept ephemerides
+**     supplied by the caller.
+**
+**     The transformation from ICRS to GCRS covers space motion,
+**     parallax, light deflection, and aberration.  From GCRS to CIRS
+**     comprises frame bias and precession-nutation.  From CIRS to
+**     observed takes account of Earth rotation, polar motion, diurnal
+**     aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**     transformation), and atmospheric refraction.
+**
+**  6) The context structure astrom produced by this function is used by
+**     eraAtciq* and eraAticq*.
+**
+**  Called:
+**     eraCp        copy p-vector
+**     eraPm        modulus of p-vector
+**     eraPn        decompose p-vector into modulus and direction
+**     eraIr        initialize r-matrix to identity
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* au/d to m/s */
+   const double AUDMS = ERFA_DAU/ERFA_DAYSEC;
+
+/* Light time for 1 AU (day) */
+   const double CR = ERFA_AULT/ERFA_DAYSEC;
+
+   int i;
+   double dp, dv, pb[3], vb[3], ph[3], v2, w;
+
+
+/* Time since reference epoch, years (for proper motion calculation). */
+   astrom->pmt = ( (date1 - ERFA_DJ00) + date2 ) / ERFA_DJY;
+
+/* Adjust Earth ephemeris to observer. */
+   for (i = 0; i < 3; i++) {
+      dp = pv[0][i] / ERFA_DAU;
+      dv = pv[1][i] / AUDMS;
+      pb[i] = ebpv[0][i] + dp;
+      vb[i] = ebpv[1][i] + dv;
+      ph[i] = ehp[i] + dp;
+   }
+
+/* Barycentric position of observer (au). */
+   eraCp(pb, astrom->eb);
+
+/* Heliocentric direction and distance (unit vector and au). */
+   eraPn(ph, &astrom->em, astrom->eh);
+
+/* Barycentric vel. in units of c, and reciprocal of Lorenz factor. */
+   v2 = 0.0;
+   for (i = 0; i < 3; i++) {
+      w = vb[i] * CR;
+      astrom->v[i] = w;
+      v2 += w*w;
+   }
+   astrom->bm1 = sqrt(1.0 - v2);
+
+/* Reset the NPB matrix. */
+   eraIr(astrom->bpn);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/apcs13.c b/ast/erfa/apcs13.c
new file mode 100644
index 0000000..0eb2d72
--- /dev/null
+++ b/ast/erfa/apcs13.c
@@ -0,0 +1,191 @@
+#include "erfa.h"
+
+void eraApcs13(double date1, double date2, double pv[2][3],
+               eraASTROM *astrom)
+/*
+**  - - - - - - - - - -
+**   e r a A p c s 1 3
+**  - - - - - - - - - -
+**
+**  For an observer whose geocentric position and velocity are known,
+**  prepare star-independent astrometry parameters for transformations
+**  between ICRS and GCRS.  The Earth ephemeris is from ERFA models.
+**
+**  The parameters produced by this function are required in the space
+**  motion, parallax, light deflection and aberration parts of the
+**  astrometric transformation chain.
+**
+**  Given:
+**     date1  double       TDB as a 2-part...
+**     date2  double       ...Julian Date (Note 1)
+**     pv     double[2][3] observer's geocentric pos/vel (Note 3)
+**
+**  Returned:
+**     astrom eraASTROM*   star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       unchanged
+**      xpl    double       unchanged
+**      ypl    double       unchanged
+**      sphi   double       unchanged
+**      cphi   double       unchanged
+**      diurab double       unchanged
+**      eral   double       unchanged
+**      refa   double       unchanged
+**      refb   double       unchanged
+**
+**  Notes:
+**
+**  1) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  For most
+**     applications of this function the choice will not be at all
+**     critical.
+**
+**     TT can be used instead of TDB without any significant impact on
+**     accuracy.
+**
+**  2) All the vectors are with respect to BCRS axes.
+**
+**  3) The observer's position and velocity pv are geocentric but with
+**     respect to BCRS axes, and in units of m and m/s.  No assumptions
+**     are made about proximity to the Earth, and the function can be
+**     used for deep space applications as well as Earth orbit and
+**     terrestrial.
+**
+**  4) In cases where the caller wishes to supply his own Earth
+**     ephemeris, the function eraApcs can be used instead of the present
+**     function.
+**
+**  5) This is one of several functions that inserts into the astrom
+**     structure star-independent parameters needed for the chain of
+**     astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**     The various functions support different classes of observer and
+**     portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**     Those with names ending in "13" use contemporary ERFA models to
+**     compute the various ephemerides.  The others accept ephemerides
+**     supplied by the caller.
+**
+**     The transformation from ICRS to GCRS covers space motion,
+**     parallax, light deflection, and aberration.  From GCRS to CIRS
+**     comprises frame bias and precession-nutation.  From CIRS to
+**     observed takes account of Earth rotation, polar motion, diurnal
+**     aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**     transformation), and atmospheric refraction.
+**
+**  6) The context structure astrom produced by this function is used by
+**     eraAtciq* and eraAticq*.
+**
+**  Called:
+**     eraEpv00     Earth position and velocity
+**     eraApcs      astrometry parameters, ICRS-GCRS, space observer
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double ehpv[2][3], ebpv[2][3];
+
+
+/* Earth barycentric & heliocentric position/velocity (au, au/d). */
+   (void) eraEpv00(date1, date2, ehpv, ebpv);
+
+/* Compute the star-independent astrometry parameters. */
+   eraApcs(date1, date2, pv, ebpv, ehpv[0], astrom);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/aper.c b/ast/erfa/aper.c
new file mode 100644
index 0000000..372c565
--- /dev/null
+++ b/ast/erfa/aper.c
@@ -0,0 +1,162 @@
+#include "erfa.h"
+
+void eraAper(double theta, eraASTROM *astrom)
+/*
+**  - - - - - - - -
+**   e r a A p e r
+**  - - - - - - - -
+**
+**  In the star-independent astrometry parameters, update only the
+**  Earth rotation angle, supplied by the caller explicitly.
+**
+**  Given:
+**     theta   double      Earth rotation angle (radians, Note 2)
+**     astrom  eraASTROM*  star-independent astrometry parameters:
+**      pmt    double       not used
+**      eb     double[3]    not used
+**      eh     double[3]    not used
+**      em     double       not used
+**      v      double[3]    not used
+**      bm1    double       not used
+**      bpn    double[3][3] not used
+**      along  double       longitude + s' (radians)
+**      xpl    double       not used
+**      ypl    double       not used
+**      sphi   double       not used
+**      cphi   double       not used
+**      diurab double       not used
+**      eral   double       not used
+**      refa   double       not used
+**      refb   double       not used
+**
+**  Returned:
+**     astrom  eraASTROM*  star-independent astrometry parameters:
+**      pmt    double       unchanged
+**      eb     double[3]    unchanged
+**      eh     double[3]    unchanged
+**      em     double       unchanged
+**      v      double[3]    unchanged
+**      bm1    double       unchanged
+**      bpn    double[3][3] unchanged
+**      along  double       unchanged
+**      xpl    double       unchanged
+**      ypl    double       unchanged
+**      sphi   double       unchanged
+**      cphi   double       unchanged
+**      diurab double       unchanged
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       unchanged
+**      refb   double       unchanged
+**
+**  Notes:
+**
+**  1) This function exists to enable sidereal-tracking applications to
+**     avoid wasteful recomputation of the bulk of the astrometry
+**     parameters:  only the Earth rotation is updated.
+**
+**  2) For targets expressed as equinox based positions, such as
+**     classical geocentric apparent (RA,Dec), the supplied theta can be
+**     Greenwich apparent sidereal time rather than Earth rotation
+**     angle.
+**
+**  3) The function eraAper13 can be used instead of the present
+**     function, and starts from UT1 rather than ERA itself.
+**
+**  4) This is one of several functions that inserts into the astrom
+**     structure star-independent parameters needed for the chain of
+**     astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**     The various functions support different classes of observer and
+**     portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**     Those with names ending in "13" use contemporary ERFA models to
+**     compute the various ephemerides.  The others accept ephemerides
+**     supplied by the caller.
+**
+**     The transformation from ICRS to GCRS covers space motion,
+**     parallax, light deflection, and aberration.  From GCRS to CIRS
+**     comprises frame bias and precession-nutation.  From CIRS to
+**     observed takes account of Earth rotation, polar motion, diurnal
+**     aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**     transformation), and atmospheric refraction.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   astrom->eral = theta + astrom->along;
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/aper13.c b/ast/erfa/aper13.c
new file mode 100644
index 0000000..e2b019c
--- /dev/null
+++ b/ast/erfa/aper13.c
@@ -0,0 +1,181 @@
+#include "erfa.h"
+
+void eraAper13(double ut11, double ut12, eraASTROM *astrom)
+/*
+**  - - - - - - - - - -
+**   e r a A p e r 1 3
+**  - - - - - - - - - -
+**
+**  In the star-independent astrometry parameters, update only the
+**  Earth rotation angle.  The caller provides UT1, (n.b. not UTC).
+**
+**  Given:
+**     ut11    double      UT1 as a 2-part...
+**     ut12    double      ...Julian Date (Note 1)
+**     astrom  eraASTROM*  star-independent astrometry parameters:
+**      pmt    double       not used
+**      eb     double[3]    not used
+**      eh     double[3]    not used
+**      em     double       not used
+**      v      double[3]    not used
+**      bm1    double       not used
+**      bpn    double[3][3] not used
+**      along  double       longitude + s' (radians)
+**      xpl    double       not used
+**      ypl    double       not used
+**      sphi   double       not used
+**      cphi   double       not used
+**      diurab double       not used
+**      eral   double       not used
+**      refa   double       not used
+**      refb   double       not used
+**
+**  Returned:
+**     astrom  eraASTROM*  star-independent astrometry parameters:
+**      pmt    double       unchanged
+**      eb     double[3]    unchanged
+**      eh     double[3]    unchanged
+**      em     double       unchanged
+**      v      double[3]    unchanged
+**      bm1    double       unchanged
+**      bpn    double[3][3] unchanged
+**      along  double       unchanged
+**      xpl    double       unchanged
+**      ypl    double       unchanged
+**      sphi   double       unchanged
+**      cphi   double       unchanged
+**      diurab double       unchanged
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       unchanged
+**      refb   double       unchanged
+**
+**  Notes:
+**
+**  1) The UT1 date (n.b. not UTC) ut11+ut12 is a Julian Date,
+**     apportioned in any convenient way between the arguments ut11 and
+**     ut12.  For example, JD(UT1)=2450123.7 could be expressed in any
+**     of these ways, among others:
+**
+**            ut11           ut12
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  The date & time method is
+**     best matched to the algorithm used:  maximum precision is
+**     delivered when the ut11 argument is for 0hrs UT1 on the day in
+**     question and the ut12 argument lies in the range 0 to 1, or vice
+**     versa.
+**
+**  2) If the caller wishes to provide the Earth rotation angle itself,
+**     the function eraAper can be used instead.  One use of this
+**     technique is to substitute Greenwich apparent sidereal time and
+**     thereby to support equinox based transformations directly.
+**
+**  3) This is one of several functions that inserts into the astrom
+**     structure star-independent parameters needed for the chain of
+**     astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**     The various functions support different classes of observer and
+**     portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**     Those with names ending in "13" use contemporary ERFA models to
+**     compute the various ephemerides.  The others accept ephemerides
+**     supplied by the caller.
+**
+**     The transformation from ICRS to GCRS covers space motion,
+**     parallax, light deflection, and aberration.  From GCRS to CIRS
+**     comprises frame bias and precession-nutation.  From CIRS to
+**     observed takes account of Earth rotation, polar motion, diurnal
+**     aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**     transformation), and atmospheric refraction.
+**
+**  Called:
+**     eraAper      astrometry parameters: update ERA
+**     eraEra00     Earth rotation angle, IAU 2000
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraAper(eraEra00(ut11,ut12), astrom);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/apio.c b/ast/erfa/apio.c
new file mode 100644
index 0000000..ebc5569
--- /dev/null
+++ b/ast/erfa/apio.c
@@ -0,0 +1,213 @@
+#include "erfa.h"
+
+void eraApio(double sp, double theta,
+             double elong, double phi, double hm, double xp, double yp,
+             double refa, double refb,
+             eraASTROM *astrom)
+/*
+**  - - - - - - - -
+**   e r a A p i o
+**  - - - - - - - -
+**
+**  For a terrestrial observer, prepare star-independent astrometry
+**  parameters for transformations between CIRS and observed
+**  coordinates.  The caller supplies the Earth orientation information
+**  and the refraction constants as well as the site coordinates.
+**
+**  Given:
+**     sp     double      the TIO locator s' (radians, Note 1)
+**     theta  double      Earth rotation angle (radians)
+**     elong  double      longitude (radians, east +ve, Note 2)
+**     phi    double      geodetic latitude (radians, Note 2)
+**     hm     double      height above ellipsoid (m, geodetic Note 2)
+**     xp,yp  double      polar motion coordinates (radians, Note 3)
+**     refa   double      refraction constant A (radians, Note 4)
+**     refb   double      refraction constant B (radians, Note 4)
+**
+**  Returned:
+**     astrom eraASTROM*  star-independent astrometry parameters:
+**      pmt    double       unchanged
+**      eb     double[3]    unchanged
+**      eh     double[3]    unchanged
+**      em     double       unchanged
+**      v      double[3]    unchanged
+**      bm1    double       unchanged
+**      bpn    double[3][3] unchanged
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**
+**  Notes:
+**
+**  1) sp, the TIO locator s', is a tiny quantity needed only by the
+**     most precise applications.  It can either be set to zero or
+**     predicted using the ERFA function eraSp00.
+**
+**  2) The geographical coordinates are with respect to the ERFA_WGS84
+**     reference ellipsoid.  TAKE CARE WITH THE LONGITUDE SIGN:  the
+**     longitude required by the present function is east-positive
+**     (i.e. right-handed), in accordance with geographical convention.
+**
+**  3) The polar motion xp,yp can be obtained from IERS bulletins.  The
+**     values are the coordinates (in radians) of the Celestial
+**     Intermediate Pole with respect to the International Terrestrial
+**     Reference System (see IERS Conventions 2003), measured along the
+**     meridians 0 and 90 deg west respectively.  For many applications,
+**     xp and yp can be set to zero.
+**
+**     Internally, the polar motion is stored in a form rotated onto the
+**     local meridian.
+**
+**  4) The refraction constants refa and refb are for use in a
+**     dZ = A*tan(Z)+B*tan^3(Z) model, where Z is the observed
+**     (i.e. refracted) zenith distance and dZ is the amount of
+**     refraction.
+**
+**  5) It is advisable to take great care with units, as even unlikely
+**     values of the input parameters are accepted and processed in
+**     accordance with the models used.
+**
+**  6) In cases where the caller does not wish to provide the Earth
+**     rotation information and refraction constants, the function
+**     eraApio13 can be used instead of the present function.  This
+**     starts from UTC and weather readings etc. and computes suitable
+**     values using other ERFA functions.
+**
+**  7) This is one of several functions that inserts into the astrom
+**     structure star-independent parameters needed for the chain of
+**     astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**     The various functions support different classes of observer and
+**     portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**     Those with names ending in "13" use contemporary ERFA models to
+**     compute the various ephemerides.  The others accept ephemerides
+**     supplied by the caller.
+**
+**     The transformation from ICRS to GCRS covers space motion,
+**     parallax, light deflection, and aberration.  From GCRS to CIRS
+**     comprises frame bias and precession-nutation.  From CIRS to
+**     observed takes account of Earth rotation, polar motion, diurnal
+**     aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**     transformation), and atmospheric refraction.
+**
+**  8) The context structure astrom produced by this function is used by
+**     eraAtioq and eraAtoiq.
+**
+**  Called:
+**     eraPvtob     position/velocity of terrestrial station
+**     eraAper      astrometry parameters: update ERA
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double sl, cl, pv[2][3];
+
+
+/* Longitude with adjustment for TIO locator s'. */
+   astrom->along = elong + sp;
+
+/* Polar motion, rotated onto the local meridian. */
+   sl = sin(astrom->along);
+   cl = cos(astrom->along);
+   astrom->xpl = xp*cl - yp*sl;
+   astrom->ypl = xp*sl + yp*cl;
+
+/* Functions of latitude. */
+   astrom->sphi = sin(phi);
+   astrom->cphi = cos(phi);
+
+/* Observer's geocentric position and velocity (m, m/s, CIRS). */
+   eraPvtob(elong, phi, hm, xp, yp, sp, theta, pv);
+
+/* Magnitude of diurnal aberration vector. */
+   astrom->diurab = sqrt(pv[1][0]*pv[1][0]+pv[1][1]*pv[1][1]) / ERFA_CMPS;
+
+/* Refraction constants. */
+   astrom->refa = refa;
+   astrom->refb = refb;
+
+/* Local Earth rotation angle. */
+   eraAper(theta, astrom);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/apio13.c b/ast/erfa/apio13.c
new file mode 100644
index 0000000..4544c9e
--- /dev/null
+++ b/ast/erfa/apio13.c
@@ -0,0 +1,259 @@
+#include "erfa.h"
+
+int eraApio13(double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              eraASTROM *astrom)
+/*
+**  - - - - - - - - - -
+**   e r a A p i o 1 3
+**  - - - - - - - - - -
+**
+**  For a terrestrial observer, prepare star-independent astrometry
+**  parameters for transformations between CIRS and observed
+**  coordinates.  The caller supplies UTC, site coordinates, ambient air
+**  conditions and observing wavelength.
+**
+**  Given:
+**     utc1   double      UTC as a 2-part...
+**     utc2   double      ...quasi Julian Date (Notes 1,2)
+**     dut1   double      UT1-UTC (seconds)
+**     elong  double      longitude (radians, east +ve, Note 3)
+**     phi    double      geodetic latitude (radians, Note 3)
+**     hm     double      height above ellipsoid (m, geodetic Notes 4,6)
+**     xp,yp  double      polar motion coordinates (radians, Note 5)
+**     phpa   double      pressure at the observer (hPa = mB, Note 6)
+**     tc     double      ambient temperature at the observer (deg C)
+**     rh     double      relative humidity at the observer (range 0-1)
+**     wl     double      wavelength (micrometers, Note 7)
+**
+**  Returned:
+**     astrom eraASTROM*  star-independent astrometry parameters:
+**      pmt    double       unchanged
+**      eb     double[3]    unchanged
+**      eh     double[3]    unchanged
+**      em     double       unchanged
+**      v      double[3]    unchanged
+**      bm1    double       unchanged
+**      bpn    double[3][3] unchanged
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**
+**  Returned (function value):
+**            int         status: +1 = dubious year (Note 2)
+**                                 0 = OK
+**                                -1 = unacceptable date
+**
+**  Notes:
+**
+**  1)  utc1+utc2 is quasi Julian Date (see Note 2), apportioned in any
+**      convenient way between the two arguments, for example where utc1
+**      is the Julian Day Number and utc2 is the fraction of a day.
+**
+**      However, JD cannot unambiguously represent UTC during a leap
+**      second unless special measures are taken.  The convention in the
+**      present function is that the JD day represents UTC days whether
+**      the length is 86399, 86400 or 86401 SI seconds.
+**
+**      Applications should use the function eraDtf2d to convert from
+**      calendar date and time of day into 2-part quasi Julian Date, as
+**      it implements the leap-second-ambiguity convention just
+**      described.
+**
+**  2)  The warning status "dubious year" flags UTCs that predate the
+**      introduction of the time scale or that are too far in the future
+**      to be trusted.  See eraDat for further details.
+**
+**  3)  UT1-UTC is tabulated in IERS bulletins.  It increases by exactly
+**      one second at the end of each positive UTC leap second,
+**      introduced in order to keep UT1-UTC within +/- 0.9s.  n.b. This
+**      practice is under review, and in the future UT1-UTC may grow
+**      essentially without limit.
+**
+**  4)  The geographical coordinates are with respect to the ERFA_WGS84
+**      reference ellipsoid.  TAKE CARE WITH THE LONGITUDE SIGN:  the
+**      longitude required by the present function is east-positive
+**      (i.e. right-handed), in accordance with geographical convention.
+**
+**  5)  The polar motion xp,yp can be obtained from IERS bulletins.  The
+**      values are the coordinates (in radians) of the Celestial
+**      Intermediate Pole with respect to the International Terrestrial
+**      Reference System (see IERS Conventions 2003), measured along the
+**      meridians 0 and 90 deg west respectively.  For many applications,
+**      xp and yp can be set to zero.
+**
+**      Internally, the polar motion is stored in a form rotated onto
+**      the local meridian.
+**
+**  6)  If hm, the height above the ellipsoid of the observing station
+**      in meters, is not known but phpa, the pressure in hPa (=mB), is
+**      available, an adequate estimate of hm can be obtained from the
+**      expression
+**
+**            hm = -29.3 * tsl * log ( phpa / 1013.25 );
+**
+**      where tsl is the approximate sea-level air temperature in K
+**      (See Astrophysical Quantities, C.W.Allen, 3rd edition, section
+**      52).  Similarly, if the pressure phpa is not known, it can be
+**      estimated from the height of the observing station, hm, as
+**      follows:
+**
+**            phpa = 1013.25 * exp ( -hm / ( 29.3 * tsl ) );
+**
+**      Note, however, that the refraction is nearly proportional to the
+**      pressure and that an accurate phpa value is important for
+**      precise work.
+**
+**  7)  The argument wl specifies the observing wavelength in
+**      micrometers.  The transition from optical to radio is assumed to
+**      occur at 100 micrometers (about 3000 GHz).
+**
+**  8)  It is advisable to take great care with units, as even unlikely
+**      values of the input parameters are accepted and processed in
+**      accordance with the models used.
+**
+**  9)  In cases where the caller wishes to supply his own Earth
+**      rotation information and refraction constants, the function
+**      eraApc can be used instead of the present function.
+**
+**  10) This is one of several functions that inserts into the astrom
+**      structure star-independent parameters needed for the chain of
+**      astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.
+**
+**      The various functions support different classes of observer and
+**      portions of the transformation chain:
+**
+**          functions         observer        transformation
+**
+**       eraApcg eraApcg13    geocentric      ICRS <-> GCRS
+**       eraApci eraApci13    terrestrial     ICRS <-> CIRS
+**       eraApco eraApco13    terrestrial     ICRS <-> observed
+**       eraApcs eraApcs13    space           ICRS <-> GCRS
+**       eraAper eraAper13    terrestrial     update Earth rotation
+**       eraApio eraApio13    terrestrial     CIRS <-> observed
+**
+**      Those with names ending in "13" use contemporary ERFA models to
+**      compute the various ephemerides.  The others accept ephemerides
+**      supplied by the caller.
+**
+**      The transformation from ICRS to GCRS covers space motion,
+**      parallax, light deflection, and aberration.  From GCRS to CIRS
+**      comprises frame bias and precession-nutation.  From CIRS to
+**      observed takes account of Earth rotation, polar motion, diurnal
+**      aberration and parallax (unless subsumed into the ICRS <-> GCRS
+**      transformation), and atmospheric refraction.
+**
+**  11) The context structure astrom produced by this function is used
+**      by eraAtioq and eraAtoiq.
+**
+**  Called:
+**     eraUtctai    UTC to TAI
+**     eraTaitt     TAI to TT
+**     eraUtcut1    UTC to UT1
+**     eraSp00      the TIO locator s', IERS 2000
+**     eraEra00     Earth rotation angle, IAU 2000
+**     eraRefco     refraction constants for given ambient conditions
+**     eraApio      astrometry parameters, CIRS-observed
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j;
+   double tai1, tai2, tt1, tt2, ut11, ut12, sp, theta, refa, refb;
+
+
+/* UTC to other time scales. */
+   j = eraUtctai(utc1, utc2, &tai1, &tai2);
+   if ( j < 0 ) return -1;
+   j = eraTaitt(tai1, tai2, &tt1, &tt2);
+   j = eraUtcut1(utc1, utc2, dut1, &ut11, &ut12);
+   if ( j < 0 ) return -1;
+
+/* TIO locator s'. */
+   sp = eraSp00(tt1, tt2);
+
+/* Earth rotation angle. */
+   theta = eraEra00(ut11, ut12);
+
+/* Refraction constants A and B. */
+   eraRefco(phpa, tc, rh, wl, &refa, &refb);
+
+/* CIRS <-> observed astrometry parameters. */
+   eraApio(sp, theta, elong, phi, hm, xp, yp, refa, refb, astrom);
+
+/* Return any warning status. */
+   return j;
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atci13.c b/ast/erfa/atci13.c
new file mode 100644
index 0000000..40a3a04
--- /dev/null
+++ b/ast/erfa/atci13.c
@@ -0,0 +1,159 @@
+#include "erfa.h"
+
+void eraAtci13(double rc, double dc,
+               double pr, double pd, double px, double rv,
+               double date1, double date2,
+               double *ri, double *di, double *eo)
+/*
+**  - - - - - - - - - -
+**   e r a A t c i 1 3
+**  - - - - - - - - - -
+**
+**  Transform ICRS star data, epoch J2000.0, to CIRS.
+**
+**  Given:
+**     rc     double   ICRS right ascension at J2000.0 (radians, Note 1)
+**     dc     double   ICRS declination at J2000.0 (radians, Note 1)
+**     pr     double   RA proper motion (radians/year; Note 2)
+**     pd     double   Dec proper motion (radians/year)
+**     px     double   parallax (arcsec)
+**     rv     double   radial velocity (km/s, +ve if receding)
+**     date1  double   TDB as a 2-part...
+**     date2  double   ...Julian Date (Note 3)
+**
+**  Returned:
+**     ri,di  double*  CIRS geocentric RA,Dec (radians)
+**     eo     double*  equation of the origins (ERA-GST, Note 5)
+**
+**  Notes:
+**
+**  1) Star data for an epoch other than J2000.0 (for example from the
+**     Hipparcos catalog, which has an epoch of J1991.25) will require a
+**     preliminary call to eraPmsafe before use.
+**
+**  2) The proper motion in RA is dRA/dt rather than cos(Dec)*dRA/dt.
+**
+**  3) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.8g could be expressed in any of these ways, among
+**     others:
+**
+**            date1          date2
+**
+**         2450123.8g           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  For most
+**     applications of this function the choice will not be at all
+**     critical.
+**
+**     TT can be used instead of TDB without any significant impact on
+**     accuracy.
+**
+**  4) The available accuracy is better than 1 milliarcsecond, limited
+**     mainly by the precession-nutation model that is used, namely
+**     IAU 2000A/2006.  Very close to solar system bodies, additional
+**     errors of up to several milliarcseconds can occur because of
+**     unmodeled light deflection;  however, the Sun's contribution is
+**     taken into account, to first order.  The accuracy limitations of
+**     the ERFA function eraEpv00 (used to compute Earth position and
+**     velocity) can contribute aberration errors of up to
+**     5 microarcseconds.  Light deflection at the Sun's limb is
+**     uncertain at the 0.4 mas level.
+**
+**  5) Should the transformation to (equinox based) apparent place be
+**     required rather than (CIO based) intermediate place, subtract the
+**     equation of the origins from the returned right ascension:
+**     RA = RI - EO. (The eraAnp function can then be applied, as
+**     required, to keep the result in the conventional 0-2pi range.)
+**
+**  Called:
+**     eraApci13    astrometry parameters, ICRS-CIRS, 2013
+**     eraAtciq     quick ICRS to CIRS
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Star-independent astrometry parameters */
+   eraASTROM astrom;
+
+
+/* The transformation parameters. */
+   eraApci13(date1, date2, &astrom, eo);
+
+/* ICRS (epoch J2000.0) to CIRS. */
+   eraAtciq(rc, dc, pr, pd, px, rv, &astrom, ri, di);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atciq.c b/ast/erfa/atciq.c
new file mode 100644
index 0000000..68947ba
--- /dev/null
+++ b/ast/erfa/atciq.c
@@ -0,0 +1,154 @@
+#include "erfa.h"
+
+void eraAtciq(double rc, double dc,
+              double pr, double pd, double px, double rv,
+              eraASTROM *astrom, double *ri, double *di)
+/*
+**  - - - - - - - - -
+**   e r a A t c i q
+**  - - - - - - - - -
+**
+**  Quick ICRS, epoch J2000.0, to CIRS transformation, given precomputed
+**  star-independent astrometry parameters.
+**
+**  Use of this function is appropriate when efficiency is important and
+**  where many star positions are to be transformed for one date.  The
+**  star-independent parameters can be obtained by calling one of the
+**  functions eraApci[13], eraApcg[13], eraApco[13] or eraApcs[13].
+**
+**  If the parallax and proper motions are zero the eraAtciqz function
+**  can be used instead.
+**
+**  Given:
+**     rc,dc  double     ICRS RA,Dec at J2000.0 (radians)
+**     pr     double     RA proper motion (radians/year; Note 3)
+**     pd     double     Dec proper motion (radians/year)
+**     px     double     parallax (arcsec)
+**     rv     double     radial velocity (km/s, +ve if receding)
+**     astrom eraASTROM* star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**
+**  Returned:
+**     ri,di   double    CIRS RA,Dec (radians)
+**
+**  Notes:
+**
+**  1) All the vectors are with respect to BCRS axes.
+**
+**  2) Star data for an epoch other than J2000.0 (for example from the
+**     Hipparcos catalog, which has an epoch of J1991.25) will require a
+**     preliminary call to eraPmsafe before use.
+**
+**  3) The proper motion in RA is dRA/dt rather than cos(Dec)*dRA/dt.
+**
+**  Called:
+**     eraPmpx      proper motion and parallax
+**     eraLdsun     light deflection by the Sun
+**     eraAb        stellar aberration
+**     eraRxp       product of r-matrix and pv-vector
+**     eraC2s       p-vector to spherical
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double pco[3], pnat[3], ppr[3], pi[3], w;
+
+
+/* Proper motion and parallax, giving BCRS coordinate direction. */
+   eraPmpx(rc, dc, pr, pd, px, rv, astrom->pmt, astrom->eb, pco);
+
+/* Light deflection by the Sun, giving BCRS natural direction. */
+   eraLdsun(pco, astrom->eh, astrom->em, pnat);
+
+/* Aberration, giving GCRS proper direction. */
+   eraAb(pnat, astrom->v, astrom->em, astrom->bm1, ppr);
+
+/* Bias-precession-nutation, giving CIRS proper direction. */
+   eraRxp(astrom->bpn, ppr, pi);
+
+/* CIRS RA,Dec. */
+   eraC2s(pi, &w, di);
+   *ri = eraAnp(w);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atciqn.c b/ast/erfa/atciqn.c
new file mode 100644
index 0000000..0ad89a0
--- /dev/null
+++ b/ast/erfa/atciqn.c
@@ -0,0 +1,191 @@
+#include "erfa.h"
+
+void eraAtciqn(double rc, double dc, double pr, double pd,
+               double px, double rv, eraASTROM *astrom,
+               int n, eraLDBODY b[], double *ri, double *di)
+/*
+**  - - - - - - - - - -
+**   e r a A t c i q n
+**  - - - - - - - - - -
+**
+**  Quick ICRS, epoch J2000.0, to CIRS transformation, given precomputed
+**  star-independent astrometry parameters plus a list of light-
+**  deflecting bodies.
+**
+**  Use of this function is appropriate when efficiency is important and
+**  where many star positions are to be transformed for one date.  The
+**  star-independent parameters can be obtained by calling one of the
+**  functions eraApci[13], eraApcg[13], eraApco[13] or eraApcs[13].
+**
+**
+**  If the only light-deflecting body to be taken into account is the
+**  Sun, the eraAtciq function can be used instead.  If in addition the
+**  parallax and proper motions are zero, the eraAtciqz function can be
+**  used.
+**
+**  Given:
+**     rc,dc  double       ICRS RA,Dec at J2000.0 (radians)
+**     pr     double       RA proper motion (radians/year; Note 3)
+**     pd     double       Dec proper motion (radians/year)
+**     px     double       parallax (arcsec)
+**     rv     double       radial velocity (km/s, +ve if receding)
+**     astrom eraASTROM*   star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**      n     int           number of bodies (Note 3)
+**      b     eraLDBODY[n] data for each of the n bodies (Notes 3,4):
+**       bm    double        mass of the body (solar masses, Note 5)
+**       dl    double        deflection limiter (Note 6)
+**       pv    [2][3]        barycentric PV of the body (au, au/day)
+**
+**  Returned:
+**     ri,di   double    CIRS RA,Dec (radians)
+**
+**  Notes:
+**
+**  1) Star data for an epoch other than J2000.0 (for example from the
+**     Hipparcos catalog, which has an epoch of J1991.25) will require a
+**     preliminary call to eraPmsafe before use.
+**
+**  2) The proper motion in RA is dRA/dt rather than cos(Dec)*dRA/dt.
+**
+**  3) The struct b contains n entries, one for each body to be
+**     considered.  If n = 0, no gravitational light deflection will be
+**     applied, not even for the Sun.
+**
+**  4) The struct b should include an entry for the Sun as well as for
+**     any planet or other body to be taken into account.  The entries
+**     should be in the order in which the light passes the body.
+**
+**  5) In the entry in the b struct for body i, the mass parameter
+**     b[i].bm can, as required, be adjusted in order to allow for such
+**     effects as quadrupole field.
+**
+**  6) The deflection limiter parameter b[i].dl is phi^2/2, where phi is
+**     the angular separation (in radians) between star and body at
+**     which limiting is applied.  As phi shrinks below the chosen
+**     threshold, the deflection is artificially reduced, reaching zero
+**     for phi = 0.   Example values suitable for a terrestrial
+**     observer, together with masses, are as follows:
+**
+**        body i     b[i].bm        b[i].dl
+**
+**        Sun        1.0            6e-6
+**        Jupiter    0.00095435     3e-9
+**        Saturn     0.00028574     3e-10
+**
+**  7) For efficiency, validation of the contents of the b array is
+**     omitted.  The supplied masses must be greater than zero, the
+**     position and velocity vectors must be right, and the deflection
+**     limiter greater than zero.
+**
+**  Called:
+**     eraPmpx      proper motion and parallax
+**     eraLdn       light deflection by n bodies
+**     eraAb        stellar aberration
+**     eraRxp       product of r-matrix and pv-vector
+**     eraC2s       p-vector to spherical
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double pco[3], pnat[3], ppr[3], pi[3], w;
+
+
+/* Proper motion and parallax, giving BCRS coordinate direction. */
+   eraPmpx(rc, dc, pr, pd, px, rv, astrom->pmt, astrom->eb, pco);
+
+/* Light deflection, giving BCRS natural direction. */
+   eraLdn(n, b, astrom->eb, pco, pnat);
+
+/* Aberration, giving GCRS proper direction. */
+   eraAb(pnat, astrom->v, astrom->em, astrom->bm1, ppr);
+
+/* Bias-precession-nutation, giving CIRS proper direction. */
+   eraRxp(astrom->bpn, ppr, pi);
+
+/* CIRS RA,Dec. */
+   eraC2s(pi, &w, di);
+   *ri = eraAnp(w);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atciqz.c b/ast/erfa/atciqz.c
new file mode 100644
index 0000000..d141b91
--- /dev/null
+++ b/ast/erfa/atciqz.c
@@ -0,0 +1,153 @@
+#include "erfa.h"
+
+void eraAtciqz(double rc, double dc, eraASTROM *astrom,
+               double *ri, double *di)
+/*
+**  - - - - - - - - - -
+**   e r a A t c i q z
+**  - - - - - - - - - -
+**
+**  Quick ICRS to CIRS transformation, given precomputed star-
+**  independent astrometry parameters, and assuming zero parallax and
+**  proper motion.
+**
+**  Use of this function is appropriate when efficiency is important and
+**  where many star positions are to be transformed for one date.  The
+**  star-independent parameters can be obtained by calling one of the
+**  functions eraApci[13], eraApcg[13], eraApco[13] or eraApcs[13].
+**
+**  The corresponding function for the case of non-zero parallax and
+**  proper motion is eraAtciq.
+**
+**  Given:
+**     rc,dc  double     ICRS astrometric RA,Dec (radians)
+**     astrom eraASTROM* star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**
+**  Returned:
+**     ri,di  double     CIRS RA,Dec (radians)
+**
+**  Note:
+**
+**     All the vectors are with respect to BCRS axes.
+**
+**  References:
+**
+**     Urban, S. & Seidelmann, P. K. (eds), Explanatory Supplement to
+**     the Astronomical Almanac, 3rd ed., University Science Books
+**     (2013).
+**
+**     Klioner, Sergei A., "A practical relativistic model for micro-
+**     arcsecond astrometry in space", Astr. J. 125, 1580-1597 (2003).
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraLdsun     light deflection due to Sun
+**     eraAb        stellar aberration
+**     eraRxp       product of r-matrix and p-vector
+**     eraC2s       p-vector to spherical
+**     eraAnp       normalize angle into range +/- pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double pco[3], pnat[3], ppr[3], pi[3], w;
+
+
+/* BCRS coordinate direction (unit vector). */
+   eraS2c(rc, dc, pco);
+
+/* Light deflection by the Sun, giving BCRS natural direction. */
+   eraLdsun(pco, astrom->eh, astrom->em, pnat);
+
+/* Aberration, giving GCRS proper direction. */
+   eraAb(pnat, astrom->v, astrom->em, astrom->bm1, ppr);
+
+/* Bias-precession-nutation, giving CIRS proper direction. */
+   eraRxp(astrom->bpn, ppr, pi);
+
+/* CIRS RA,Dec. */
+   eraC2s(pi, &w, di);
+   *ri = eraAnp(w);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atco13.c b/ast/erfa/atco13.c
new file mode 100644
index 0000000..eaaabe8
--- /dev/null
+++ b/ast/erfa/atco13.c
@@ -0,0 +1,243 @@
+#include "erfa.h"
+
+int eraAtco13(double rc, double dc,
+              double pr, double pd, double px, double rv,
+              double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              double *aob, double *zob, double *hob,
+              double *dob, double *rob, double *eo)
+/*
+**  - - - - - - - - - -
+**   e r a A t c o 1 3
+**  - - - - - - - - - -
+**
+**  ICRS RA,Dec to observed place.  The caller supplies UTC, site
+**  coordinates, ambient air conditions and observing wavelength.
+**
+**  ERFA models are used for the Earth ephemeris, bias-precession-
+**  nutation, Earth orientation and refraction.
+**
+**  Given:
+**     rc,dc  double   ICRS right ascension at J2000.0 (radians, Note 1)
+**     pr     double   RA proper motion (radians/year; Note 2)
+**     pd     double   Dec proper motion (radians/year)
+**     px     double   parallax (arcsec)
+**     rv     double   radial velocity (km/s, +ve if receding)
+**     utc1   double   UTC as a 2-part...
+**     utc2   double   ...quasi Julian Date (Notes 3-4)
+**     dut1   double   UT1-UTC (seconds, Note 5)
+**     elong  double   longitude (radians, east +ve, Note 6)
+**     phi    double   latitude (geodetic, radians, Note 6)
+**     hm     double   height above ellipsoid (m, geodetic, Notes 6,8)
+**     xp,yp  double   polar motion coordinates (radians, Note 7)
+**     phpa   double   pressure at the observer (hPa = mB, Note 8)
+**     tc     double   ambient temperature at the observer (deg C)
+**     rh     double   relative humidity at the observer (range 0-1)
+**     wl     double   wavelength (micrometers, Note 9)
+**
+**  Returned:
+**     aob    double*  observed azimuth (radians: N=0,E=90)
+**     zob    double*  observed zenith distance (radians)
+**     hob    double*  observed hour angle (radians)
+**     dob    double*  observed declination (radians)
+**     rob    double*  observed right ascension (CIO-based, radians)
+**     eo     double*  equation of the origins (ERA-GST)
+**
+**  Returned (function value):
+**            int      status: +1 = dubious year (Note 4)
+**                              0 = OK
+**                             -1 = unacceptable date
+**
+**  Notes:
+**
+**  1)  Star data for an epoch other than J2000.0 (for example from the
+**      Hipparcos catalog, which has an epoch of J1991.25) will require
+**      a preliminary call to eraPmsafe before use.
+**
+**  2)  The proper motion in RA is dRA/dt rather than cos(Dec)*dRA/dt.
+**
+**  3)  utc1+utc2 is quasi Julian Date (see Note 2), apportioned in any
+**      convenient way between the two arguments, for example where utc1
+**      is the Julian Day Number and utc2 is the fraction of a day.
+**
+**      However, JD cannot unambiguously represent UTC during a leap
+**      second unless special measures are taken.  The convention in the
+**      present function is that the JD day represents UTC days whether
+**      the length is 86399, 86400 or 86401 SI seconds.
+**
+**      Applications should use the function eraDtf2d to convert from
+**      calendar date and time of day into 2-part quasi Julian Date, as
+**      it implements the leap-second-ambiguity convention just
+**      described.
+**
+**  4)  The warning status "dubious year" flags UTCs that predate the
+**      introduction of the time scale or that are too far in the
+**      future to be trusted.  See eraDat for further details.
+**
+**  5)  UT1-UTC is tabulated in IERS bulletins.  It increases by exactly
+**      one second at the end of each positive UTC leap second,
+**      introduced in order to keep UT1-UTC within +/- 0.9s.  n.b. This
+**      practice is under review, and in the future UT1-UTC may grow
+**      essentially without limit.
+**
+**  6)  The geographical coordinates are with respect to the ERFA_WGS84
+**      reference ellipsoid.  TAKE CARE WITH THE LONGITUDE SIGN:  the
+**      longitude required by the present function is east-positive
+**      (i.e. right-handed), in accordance with geographical convention.
+**
+**  7)  The polar motion xp,yp can be obtained from IERS bulletins.  The
+**      values are the coordinates (in radians) of the Celestial
+**      Intermediate Pole with respect to the International Terrestrial
+**      Reference System (see IERS Conventions 2003), measured along the
+**      meridians 0 and 90 deg west respectively.  For many
+**      applications, xp and yp can be set to zero.
+**
+**  8)  If hm, the height above the ellipsoid of the observing station
+**      in meters, is not known but phpa, the pressure in hPa (=mB),
+**      is available, an adequate estimate of hm can be obtained from
+**      the expression
+**
+**            hm = -29.3 * tsl * log ( phpa / 1013.25 );
+**
+**      where tsl is the approximate sea-level air temperature in K
+**      (See Astrophysical Quantities, C.W.Allen, 3rd edition, section
+**      52).  Similarly, if the pressure phpa is not known, it can be
+**      estimated from the height of the observing station, hm, as
+**      follows:
+**
+**            phpa = 1013.25 * exp ( -hm / ( 29.3 * tsl ) );
+**
+**      Note, however, that the refraction is nearly proportional to
+**      the pressure and that an accurate phpa value is important for
+**      precise work.
+**
+**  9)  The argument wl specifies the observing wavelength in
+**      micrometers.  The transition from optical to radio is assumed to
+**      occur at 100 micrometers (about 3000 GHz).
+**
+**  10) The accuracy of the result is limited by the corrections for
+**      refraction, which use a simple A*tan(z) + B*tan^3(z) model.
+**      Providing the meteorological parameters are known accurately and
+**      there are no gross local effects, the predicted observed
+**      coordinates should be within 0.05 arcsec (optical) or 1 arcsec
+**      (radio) for a zenith distance of less than 70 degrees, better
+**      than 30 arcsec (optical or radio) at 85 degrees and better
+**      than 20 arcmin (optical) or 30 arcmin (radio) at the horizon.
+**
+**      Without refraction, the complementary functions eraAtco13 and
+**      eraAtoc13 are self-consistent to better than 1 microarcsecond
+**      all over the celestial sphere.  With refraction included,
+**      consistency falls off at high zenith distances, but is still
+**      better than 0.05 arcsec at 85 degrees.
+**
+**  11) "Observed" Az,ZD means the position that would be seen by a
+**      perfect geodetically aligned theodolite.  (Zenith distance is
+**      used rather than altitude in order to reflect the fact that no
+**      allowance is made for depression of the horizon.)  This is
+**      related to the observed HA,Dec via the standard rotation, using
+**      the geodetic latitude (corrected for polar motion), while the
+**      observed HA and RA are related simply through the Earth rotation
+**      angle and the site longitude.  "Observed" RA,Dec or HA,Dec thus
+**      means the position that would be seen by a perfect equatorial
+**      with its polar axis aligned to the Earth's axis of rotation.
+**
+**  12) It is advisable to take great care with units, as even unlikely
+**      values of the input parameters are accepted and processed in
+**      accordance with the models used.
+**
+**  Called:
+**     eraApco13    astrometry parameters, ICRS-observed, 2013
+**     eraAtciq     quick ICRS to CIRS
+**     eraAtioq     quick CIRS to observed
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j;
+   eraASTROM astrom;
+   double ri, di;
+
+
+/* Star-independent astrometry parameters. */
+   j = eraApco13(utc1, utc2, dut1, elong, phi, hm, xp, yp,
+                 phpa, tc, rh, wl, &astrom, eo);
+
+/* Abort if bad UTC. */
+   if ( j < 0 ) return j;
+
+/* Transform ICRS to CIRS. */
+   eraAtciq(rc, dc, pr, pd, px, rv, &astrom, &ri, &di);
+
+/* Transform CIRS to observed. */
+   eraAtioq(ri, di, &astrom, aob, zob, hob, dob, rob);
+
+/* Return OK/warning status. */
+   return j;
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atic13.c b/ast/erfa/atic13.c
new file mode 100644
index 0000000..bd70ce5
--- /dev/null
+++ b/ast/erfa/atic13.c
@@ -0,0 +1,152 @@
+#include "erfa.h"
+
+void eraAtic13(double ri, double di, double date1, double date2,
+               double *rc, double *dc, double *eo)
+/*
+**  - - - - - - - - - -
+**   e r a A t i c 1 3
+**  - - - - - - - - - -
+**
+**  Transform star RA,Dec from geocentric CIRS to ICRS astrometric.
+**
+**  Given:
+**     ri,di  double  CIRS geocentric RA,Dec (radians)
+**     date1  double  TDB as a 2-part...
+**     date2  double  ...Julian Date (Note 1)
+**
+**  Returned:
+**     rc,dc  double  ICRS astrometric RA,Dec (radians)
+**     eo     double  equation of the origins (ERA-GST, Note 4)
+**
+**  Notes:
+**
+**  1) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  For most
+**     applications of this function the choice will not be at all
+**     critical.
+**
+**     TT can be used instead of TDB without any significant impact on
+**     accuracy.
+**
+**  2) Iterative techniques are used for the aberration and light
+**     deflection corrections so that the functions eraAtic13 (or
+**     eraAticq) and eraAtci13 (or eraAtciq) are accurate inverses;
+**     even at the edge of the Sun's disk the discrepancy is only about
+**     1 nanoarcsecond.
+**
+**  3) The available accuracy is better than 1 milliarcsecond, limited
+**     mainly by the precession-nutation model that is used, namely
+**     IAU 2000A/2006.  Very close to solar system bodies, additional
+**     errors of up to several milliarcseconds can occur because of
+**     unmodeled light deflection;  however, the Sun's contribution is
+**     taken into account, to first order.  The accuracy limitations of
+**     the ERFA function eraEpv00 (used to compute Earth position and
+**     velocity) can contribute aberration errors of up to
+**     5 microarcseconds.  Light deflection at the Sun's limb is
+**     uncertain at the 0.4 mas level.
+**
+**  4) Should the transformation to (equinox based) J2000.0 mean place
+**     be required rather than (CIO based) ICRS coordinates, subtract the
+**     equation of the origins from the returned right ascension:
+**     RA = RI - EO.  (The eraAnp function can then be applied, as
+**     required, to keep the result in the conventional 0-2pi range.)
+**
+**  Called:
+**     eraApci13    astrometry parameters, ICRS-CIRS, 2013
+**     eraAticq     quick CIRS to ICRS astrometric
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Star-independent astrometry parameters */
+   eraASTROM astrom;
+
+
+/* Star-independent astrometry parameters. */
+   eraApci13(date1, date2, &astrom, eo);
+
+/* CIRS to ICRS astrometric. */
+   eraAticq(ri, di, &astrom, rc, dc);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/aticq.c b/ast/erfa/aticq.c
new file mode 100644
index 0000000..1e205b8
--- /dev/null
+++ b/ast/erfa/aticq.c
@@ -0,0 +1,199 @@
+#include "erfa.h"
+
+void eraAticq(double ri, double di, eraASTROM *astrom,
+              double *rc, double *dc)
+/*
+**  - - - - - - - - -
+**   e r a A t i c q
+**  - - - - - - - - -
+**
+**  Quick CIRS RA,Dec to ICRS astrometric place, given the star-
+**  independent astrometry parameters.
+**
+**  Use of this function is appropriate when efficiency is important and
+**  where many star positions are all to be transformed for one date.
+**  The star-independent astrometry parameters can be obtained by
+**  calling one of the functions eraApci[13], eraApcg[13], eraApco[13]
+**  or eraApcs[13].
+**
+**  Given:
+**     ri,di  double     CIRS RA,Dec (radians)
+**     astrom eraASTROM* star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**
+**  Returned:
+**     rc,dc  double     ICRS astrometric RA,Dec (radians)
+**
+**  Notes:
+**
+**  1) Only the Sun is taken into account in the light deflection
+**     correction.
+**
+**  2) Iterative techniques are used for the aberration and light
+**     deflection corrections so that the functions eraAtic13 (or
+**     eraAticq) and eraAtci13 (or eraAtciq) are accurate inverses;
+**     even at the edge of the Sun's disk the discrepancy is only about
+**     1 nanoarcsecond.
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraTrxp      product of transpose of r-matrix and p-vector
+**     eraZp        zero p-vector
+**     eraAb        stellar aberration
+**     eraLdsun     light deflection by the Sun
+**     eraC2s       p-vector to spherical
+**     eraAnp       normalize angle into range +/- pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j, i;
+   double pi[3], ppr[3], pnat[3], pco[3], w, d[3], before[3], r2, r,
+          after[3];
+
+
+/* CIRS RA,Dec to Cartesian. */
+   eraS2c(ri, di, pi);
+
+/* Bias-precession-nutation, giving GCRS proper direction. */
+   eraTrxp(astrom->bpn, pi, ppr);
+
+/* Aberration, giving GCRS natural direction. */
+   eraZp(d);
+   for (j = 0; j < 2; j++) {
+      r2 = 0.0;
+      for (i = 0; i < 3; i++) {
+         w = ppr[i] - d[i];
+         before[i] = w;
+         r2 += w*w;
+      }
+      r = sqrt(r2);
+      for (i = 0; i < 3; i++) {
+         before[i] /= r;
+      }
+      eraAb(before, astrom->v, astrom->em, astrom->bm1, after);
+      r2 = 0.0;
+      for (i = 0; i < 3; i++) {
+         d[i] = after[i] - before[i];
+         w = ppr[i] - d[i];
+         pnat[i] = w;
+         r2 += w*w;
+      }
+      r = sqrt(r2);
+      for (i = 0; i < 3; i++) {
+         pnat[i] /= r;
+      }
+   }
+
+/* Light deflection by the Sun, giving BCRS coordinate direction. */
+   eraZp(d);
+   for (j = 0; j < 5; j++) {
+      r2 = 0.0;
+      for (i = 0; i < 3; i++) {
+         w = pnat[i] - d[i];
+         before[i] = w;
+         r2 += w*w;
+      }
+      r = sqrt(r2);
+      for (i = 0; i < 3; i++) {
+         before[i] /= r;
+      }
+      eraLdsun(before, astrom->eh, astrom->em, after);
+      r2 = 0.0;
+      for (i = 0; i < 3; i++) {
+         d[i] = after[i] - before[i];
+         w = pnat[i] - d[i];
+         pco[i] = w;
+         r2 += w*w;
+      }
+      r = sqrt(r2);
+      for (i = 0; i < 3; i++) {
+         pco[i] /= r;
+      }
+   }
+
+/* ICRS astrometric RA,Dec. */
+   eraC2s(pco, &w, dc);
+   *rc = eraAnp(w);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/aticqn.c b/ast/erfa/aticqn.c
new file mode 100644
index 0000000..d69119e
--- /dev/null
+++ b/ast/erfa/aticqn.c
@@ -0,0 +1,237 @@
+#include "erfa.h"
+
+void eraAticqn(double ri, double di, eraASTROM *astrom,
+               int n, eraLDBODY b[], double *rc, double *dc)
+/*
+**  - - - - - - - - -
+**   e r a A t i c q n
+**  - - - - - - - - -
+**
+**  Quick CIRS to ICRS astrometric place transformation, given the star-
+**  independent astrometry parameters plus a list of light-deflecting
+**  bodies.
+**
+**  Use of this function is appropriate when efficiency is important and
+**  where many star positions are all to be transformed for one date.
+**  The star-independent astrometry parameters can be obtained by
+**  calling one of the functions eraApci[13], eraApcg[13], eraApco[13]
+**  or eraApcs[13].
+*
+*  If the only light-deflecting body to be taken into account is the
+*  Sun, the eraAticq function can be used instead.
+**
+**  Given:
+**     ri,di  double      CIRS RA,Dec (radians)
+**     astrom eraASTROM*  star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**      n     int           number of bodies (Note 3)
+**      b     eraLDBODY[n] data for each of the n bodies (Notes 3,4):
+**       bm    double       mass of the body (solar masses, Note 5)
+**       dl    double       deflection limiter (Note 6)
+**       pv    [2][3]       barycentric PV of the body (au, au/day)
+**
+**  Returned:
+**     rc,dc  double     ICRS astrometric RA,Dec (radians)
+**
+**  Notes:
+**
+**  1) Iterative techniques are used for the aberration and light
+**     deflection corrections so that the functions eraAticqn and
+**     eraAtciqn are accurate inverses; even at the edge of the Sun's
+**     disk the discrepancy is only about 1 nanoarcsecond.
+**
+**  2) If the only light-deflecting body to be taken into account is the
+**     Sun, the eraAticq function can be used instead.
+**
+**  3) The struct b contains n entries, one for each body to be
+**     considered.  If n = 0, no gravitational light deflection will be
+**     applied, not even for the Sun.
+**
+**  4) The struct b should include an entry for the Sun as well as for
+**     any planet or other body to be taken into account.  The entries
+**     should be in the order in which the light passes the body.
+**
+**  5) In the entry in the b struct for body i, the mass parameter
+**     b[i].bm can, as required, be adjusted in order to allow for such
+**     effects as quadrupole field.
+**
+**  6) The deflection limiter parameter b[i].dl is phi^2/2, where phi is
+**     the angular separation (in radians) between star and body at
+**     which limiting is applied.  As phi shrinks below the chosen
+**     threshold, the deflection is artificially reduced, reaching zero
+**     for phi = 0.   Example values suitable for a terrestrial
+**     observer, together with masses, are as follows:
+**
+**        body i     b[i].bm        b[i].dl
+**
+**        Sun        1.0            6e-6
+**        Jupiter    0.00095435     3e-9
+**        Saturn     0.00028574     3e-10
+**
+**  7) For efficiency, validation of the contents of the b array is
+**     omitted.  The supplied masses must be greater than zero, the
+**     position and velocity vectors must be right, and the deflection
+**     limiter greater than zero.
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraTrxp      product of transpose of r-matrix and p-vector
+**     eraZp        zero p-vector
+**     eraAb        stellar aberration
+**     eraLdn       light deflection by n bodies
+**     eraC2s       p-vector to spherical
+**     eraAnp       normalize angle into range +/- pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j, i;
+   double pi[3], ppr[3], pnat[3], pco[3], w, d[3], before[3], r2, r,
+          after[3];
+
+
+/* CIRS RA,Dec to Cartesian. */
+   eraS2c(ri, di, pi);
+
+/* Bias-precession-nutation, giving GCRS proper direction. */
+   eraTrxp(astrom->bpn, pi, ppr);
+
+/* Aberration, giving GCRS natural direction. */
+   eraZp(d);
+   for (j = 0; j < 2; j++) {
+      r2 = 0.0;
+      for (i = 0; i < 3; i++) {
+         w = ppr[i] - d[i];
+         before[i] = w;
+         r2 += w*w;
+      }
+      r = sqrt(r2);
+      for (i = 0; i < 3; i++) {
+         before[i] /= r;
+      }
+      eraAb(before, astrom->v, astrom->em, astrom->bm1, after);
+      r2 = 0.0;
+      for (i = 0; i < 3; i++) {
+         d[i] = after[i] - before[i];
+         w = ppr[i] - d[i];
+         pnat[i] = w;
+         r2 += w*w;
+      }
+      r = sqrt(r2);
+      for (i = 0; i < 3; i++) {
+         pnat[i] /= r;
+      }
+   }
+
+/* Light deflection, giving BCRS coordinate direction. */
+   eraZp(d);
+   for (j = 0; j < 5; j++) {
+      r2 = 0.0;
+      for (i = 0; i < 3; i++) {
+         w = pnat[i] - d[i];
+         before[i] = w;
+         r2 += w*w;
+      }
+      r = sqrt(r2);
+      for (i = 0; i < 3; i++) {
+         before[i] /= r;
+      }
+      eraLdn(n, b, astrom->eb, before, after);
+      r2 = 0.0;
+      for (i = 0; i < 3; i++) {
+         d[i] = after[i] - before[i];
+         w = pnat[i] - d[i];
+         pco[i] = w;
+         r2 += w*w;
+      }
+      r = sqrt(r2);
+      for (i = 0; i < 3; i++) {
+         pco[i] /= r;
+      }
+   }
+
+/* ICRS astrometric RA,Dec. */
+   eraC2s(pco, &w, dc);
+   *rc = eraAnp(w);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atio13.c b/ast/erfa/atio13.c
new file mode 100644
index 0000000..6201776
--- /dev/null
+++ b/ast/erfa/atio13.c
@@ -0,0 +1,222 @@
+#include "erfa.h"
+
+int eraAtio13(double ri, double di,
+              double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              double *aob, double *zob, double *hob,
+              double *dob, double *rob)
+/*
+**  - - - - - - - - - -
+**   e r a A t i o 1 3
+**  - - - - - - - - - -
+**
+**  CIRS RA,Dec to observed place.  The caller supplies UTC, site
+**  coordinates, ambient air conditions and observing wavelength.
+**
+**  Given:
+**     ri     double   CIRS right ascension (CIO-based, radians)
+**     di     double   CIRS declination (radians)
+**     utc1   double   UTC as a 2-part...
+**     utc2   double   ...quasi Julian Date (Notes 1,2)
+**     dut1   double   UT1-UTC (seconds, Note 3)
+**     elong  double   longitude (radians, east +ve, Note 4)
+**     phi    double   geodetic latitude (radians, Note 4)
+**     hm     double   height above ellipsoid (m, geodetic Notes 4,6)
+**     xp,yp  double   polar motion coordinates (radians, Note 5)
+**     phpa   double   pressure at the observer (hPa = mB, Note 6)
+**     tc     double   ambient temperature at the observer (deg C)
+**     rh     double   relative humidity at the observer (range 0-1)
+**     wl     double   wavelength (micrometers, Note 7)
+**
+**  Returned:
+**     aob    double*  observed azimuth (radians: N=0,E=90)
+**     zob    double*  observed zenith distance (radians)
+**     hob    double*  observed hour angle (radians)
+**     dob    double*  observed declination (radians)
+**     rob    double*  observed right ascension (CIO-based, radians)
+**
+**  Returned (function value):
+**            int      status: +1 = dubious year (Note 2)
+**                              0 = OK
+**                             -1 = unacceptable date
+**
+**  Notes:
+**
+**  1)  utc1+utc2 is quasi Julian Date (see Note 2), apportioned in any
+**      convenient way between the two arguments, for example where utc1
+**      is the Julian Day Number and utc2 is the fraction of a day.
+**
+**      However, JD cannot unambiguously represent UTC during a leap
+**      second unless special measures are taken.  The convention in the
+**      present function is that the JD day represents UTC days whether
+**      the length is 86399, 86400 or 86401 SI seconds.
+**
+**      Applications should use the function eraDtf2d to convert from
+**      calendar date and time of day into 2-part quasi Julian Date, as
+**      it implements the leap-second-ambiguity convention just
+**      described.
+**
+**  2)  The warning status "dubious year" flags UTCs that predate the
+**      introduction of the time scale or that are too far in the
+**      future to be trusted.  See eraDat for further details.
+**
+**  3)  UT1-UTC is tabulated in IERS bulletins.  It increases by exactly
+**      one second at the end of each positive UTC leap second,
+**      introduced in order to keep UT1-UTC within +/- 0.9s.  n.b. This
+**      practice is under review, and in the future UT1-UTC may grow
+**      essentially without limit.
+**
+**  4)  The geographical coordinates are with respect to the ERFA_WGS84
+**      reference ellipsoid.  TAKE CARE WITH THE LONGITUDE SIGN:  the
+**      longitude required by the present function is east-positive
+**      (i.e. right-handed), in accordance with geographical convention.
+**
+**  5)  The polar motion xp,yp can be obtained from IERS bulletins.  The
+**      values are the coordinates (in radians) of the Celestial
+**      Intermediate Pole with respect to the International Terrestrial
+**      Reference System (see IERS Conventions 2003), measured along the
+**      meridians 0 and 90 deg west respectively.  For many
+**      applications, xp and yp can be set to zero.
+**
+**  6)  If hm, the height above the ellipsoid of the observing station
+**      in meters, is not known but phpa, the pressure in hPa (=mB), is
+**      available, an adequate estimate of hm can be obtained from the
+**      expression
+**
+**            hm = -29.3 * tsl * log ( phpa / 1013.25 );
+**
+**      where tsl is the approximate sea-level air temperature in K
+**      (See Astrophysical Quantities, C.W.Allen, 3rd edition, section
+**      52).  Similarly, if the pressure phpa is not known, it can be
+**      estimated from the height of the observing station, hm, as
+**      follows:
+**
+**            phpa = 1013.25 * exp ( -hm / ( 29.3 * tsl ) );
+**
+**      Note, however, that the refraction is nearly proportional to
+**      the pressure and that an accurate phpa value is important for
+**      precise work.
+**
+**  7)  The argument wl specifies the observing wavelength in
+**      micrometers.  The transition from optical to radio is assumed to
+**      occur at 100 micrometers (about 3000 GHz).
+**
+**  8)  "Observed" Az,ZD means the position that would be seen by a
+**      perfect geodetically aligned theodolite.  (Zenith distance is
+**      used rather than altitude in order to reflect the fact that no
+**      allowance is made for depression of the horizon.)  This is
+**      related to the observed HA,Dec via the standard rotation, using
+**      the geodetic latitude (corrected for polar motion), while the
+**      observed HA and RA are related simply through the Earth rotation
+**      angle and the site longitude.  "Observed" RA,Dec or HA,Dec thus
+**      means the position that would be seen by a perfect equatorial
+**      with its polar axis aligned to the Earth's axis of rotation.
+**
+**  9)  The accuracy of the result is limited by the corrections for
+**      refraction, which use a simple A*tan(z) + B*tan^3(z) model.
+**      Providing the meteorological parameters are known accurately and
+**      there are no gross local effects, the predicted astrometric
+**      coordinates should be within 0.05 arcsec (optical) or 1 arcsec
+**      (radio) for a zenith distance of less than 70 degrees, better
+**      than 30 arcsec (optical or radio) at 85 degrees and better
+**      than 20 arcmin (optical) or 30 arcmin (radio) at the horizon.
+**
+**  10) The complementary functions eraAtio13 and eraAtoi13 are self-
+**      consistent to better than 1 microarcsecond all over the
+**      celestial sphere.
+**
+**  11) It is advisable to take great care with units, as even unlikely
+**      values of the input parameters are accepted and processed in
+**      accordance with the models used.
+**
+**  Called:
+**     eraApio13    astrometry parameters, CIRS-observed, 2013
+**     eraAtioq     quick CIRS to observed
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j;
+   eraASTROM astrom;
+
+
+/* Star-independent astrometry parameters for CIRS->observed. */
+   j = eraApio13(utc1, utc2, dut1, elong, phi, hm, xp, yp,
+                 phpa, tc, rh, wl, &astrom);
+
+/* Abort if bad UTC. */
+   if ( j < 0 ) return j;
+
+/* Transform CIRS to observed. */
+   eraAtioq(ri, di, &astrom, aob, zob, hob, dob, rob);
+
+/* Return OK/warning status. */
+   return j;
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atioq.c b/ast/erfa/atioq.c
new file mode 100644
index 0000000..d8a1f7b
--- /dev/null
+++ b/ast/erfa/atioq.c
@@ -0,0 +1,243 @@
+#include "erfa.h"
+
+void eraAtioq(double ri, double di, eraASTROM *astrom,
+              double *aob, double *zob,
+              double *hob, double *dob, double *rob)
+/*
+**  - - - - - - - - -
+**   e r a A t i o q
+**  - - - - - - - - -
+**
+**  Quick CIRS to observed place transformation.
+**
+**  Use of this function is appropriate when efficiency is important and
+**  where many star positions are all to be transformed for one date.
+**  The star-independent astrometry parameters can be obtained by
+**  calling eraApio[13] or eraApco[13].
+**
+**  Given:
+**     ri     double     CIRS right ascension
+**     di     double     CIRS declination
+**     astrom eraASTROM* star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**
+**  Returned:
+**     aob    double*    observed azimuth (radians: N=0,E=90)
+**     zob    double*    observed zenith distance (radians)
+**     hob    double*    observed hour angle (radians)
+**     dob    double*    observed declination (radians)
+**     rob    double*    observed right ascension (CIO-based, radians)
+**
+**  Notes:
+**
+**  1) This function returns zenith distance rather than altitude in
+**     order to reflect the fact that no allowance is made for
+**     depression of the horizon.
+**
+**  2) The accuracy of the result is limited by the corrections for
+**     refraction, which use a simple A*tan(z) + B*tan^3(z) model.
+**     Providing the meteorological parameters are known accurately and
+**     there are no gross local effects, the predicted observed
+**     coordinates should be within 0.05 arcsec (optical) or 1 arcsec
+**     (radio) for a zenith distance of less than 70 degrees, better
+**     than 30 arcsec (optical or radio) at 85 degrees and better
+**     than 20 arcmin (optical) or 30 arcmin (radio) at the horizon.
+**
+**     Without refraction, the complementary functions eraAtioq and
+**     eraAtoiq are self-consistent to better than 1 microarcsecond all
+**     over the celestial sphere.  With refraction included, consistency
+**     falls off at high zenith distances, but is still better than
+**     0.05 arcsec at 85 degrees.
+**
+**  3) It is advisable to take great care with units, as even unlikely
+**     values of the input parameters are accepted and processed in
+**     accordance with the models used.
+**
+**  4) The CIRS RA,Dec is obtained from a star catalog mean place by
+**     allowing for space motion, parallax, the Sun's gravitational lens
+**     effect, annual aberration and precession-nutation.  For star
+**     positions in the ICRS, these effects can be applied by means of
+**     the eraAtci13 (etc.) functions.  Starting from classical "mean
+**     place" systems, additional transformations will be needed first.
+**
+**  5) "Observed" Az,El means the position that would be seen by a
+**     perfect geodetically aligned theodolite.  This is obtained from
+**     the CIRS RA,Dec by allowing for Earth orientation and diurnal
+**     aberration, rotating from equator to horizon coordinates, and
+**     then adjusting for refraction.  The HA,Dec is obtained by
+**     rotating back into equatorial coordinates, and is the position
+**     that would be seen by a perfect equatorial with its polar axis
+**     aligned to the Earth's axis of rotation.  Finally, the RA is
+**     obtained by subtracting the HA from the local ERA.
+**
+**  6) The star-independent CIRS-to-observed-place parameters in ASTROM
+**     may be computed with eraApio[13] or eraApco[13].  If nothing has
+**     changed significantly except the time, eraAper[13] may be used to
+**     perform the requisite adjustment to the astrom structure.
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraC2s       p-vector to spherical
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Minimum cos(alt) and sin(alt) for refraction purposes */
+   const double CELMIN = 1e-6;
+   const double SELMIN = 0.05;
+
+   double v[3], x, y, z, xhd, yhd, zhd, f, xhdt, yhdt, zhdt,
+          xaet, yaet, zaet, azobs, r, tz, w, del, cosdel,
+          xaeo, yaeo, zaeo, zdobs, hmobs, dcobs, raobs;
+
+
+/* CIRS RA,Dec to Cartesian -HA,Dec. */
+   eraS2c(ri-astrom->eral, di, v);
+   x = v[0];
+   y = v[1];
+   z = v[2];
+
+/* Polar motion. */
+   xhd = x + astrom->xpl*z;
+   yhd = y - astrom->ypl*z;
+   zhd = z - astrom->xpl*x + astrom->ypl*y;
+
+/* Diurnal aberration. */
+   f = ( 1.0 - astrom->diurab*yhd );
+   xhdt = f * xhd;
+   yhdt = f * ( yhd + astrom->diurab );
+   zhdt = f * zhd;
+
+/* Cartesian -HA,Dec to Cartesian Az,El (S=0,E=90). */
+   xaet = astrom->sphi*xhdt - astrom->cphi*zhdt;
+   yaet = yhdt;
+   zaet = astrom->cphi*xhdt + astrom->sphi*zhdt;
+
+/* Azimuth (N=0,E=90). */
+   azobs = ( xaet != 0.0 || yaet != 0.0 ) ? atan2(yaet,-xaet) : 0.0;
+
+/* ---------- */
+/* Refraction */
+/* ---------- */
+
+/* Cosine and sine of altitude, with precautions. */
+   r = sqrt(xaet*xaet + yaet*yaet);
+   r = r > CELMIN ? r : CELMIN;
+   z = zaet > SELMIN ? zaet : SELMIN;
+
+/* A*tan(z)+B*tan^3(z) model, with Newton-Raphson correction. */
+   tz = r/z;
+   w = astrom->refb*tz*tz;
+   del = ( astrom->refa + w ) * tz /
+         ( 1.0 + ( astrom->refa + 3.0*w ) / ( z*z ) );
+
+/* Apply the change, giving observed vector. */
+   cosdel = 1.0 - del*del/2.0;
+   f = cosdel - del*z/r;
+   xaeo = xaet*f;
+   yaeo = yaet*f;
+   zaeo = cosdel*zaet + del*r;
+
+/* Observed ZD. */
+   zdobs = atan2(sqrt(xaeo*xaeo+yaeo*yaeo), zaeo);
+
+/* Az/El vector to HA,Dec vector (both right-handed). */
+   v[0] = astrom->sphi*xaeo + astrom->cphi*zaeo;
+   v[1] = yaeo;
+   v[2] = - astrom->cphi*xaeo + astrom->sphi*zaeo;
+
+/* To spherical -HA,Dec. */
+   eraC2s ( v, &hmobs, &dcobs );
+
+/* Right ascension (with respect to CIO). */
+   raobs = astrom->eral + hmobs;
+
+/* Return the results. */
+   *aob = eraAnp(azobs);
+   *zob = zdobs;
+   *hob = -hmobs;
+   *dob = dcobs;
+   *rob = eraAnp(raobs);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atoc13.c b/ast/erfa/atoc13.c
new file mode 100644
index 0000000..8f0932c
--- /dev/null
+++ b/ast/erfa/atoc13.c
@@ -0,0 +1,233 @@
+#include "erfa.h"
+
+int eraAtoc13(const char *type, double ob1, double ob2,
+              double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              double *rc, double *dc)
+/*
+**  - - - - - - - - - -
+**   e r a A t o c 1 3
+**  - - - - - - - - - -
+**
+**  Observed place at a groundbased site to to ICRS astrometric RA,Dec.
+**  The caller supplies UTC, site coordinates, ambient air conditions
+**  and observing wavelength.
+**
+**  Given:
+**     type   char[]   type of coordinates - "R", "H" or "A" (Notes 1,2)
+**     ob1    double   observed Az, HA or RA (radians; Az is N=0,E=90)
+**     ob2    double   observed ZD or Dec (radians)
+**     utc1   double   UTC as a 2-part...
+**     utc2   double   ...quasi Julian Date (Notes 3,4)
+**     dut1   double   UT1-UTC (seconds, Note 5)
+**     elong  double   longitude (radians, east +ve, Note 6)
+**     phi    double   geodetic latitude (radians, Note 6)
+**     hm     double   height above ellipsoid (m, geodetic Notes 6,8)
+**     xp,yp  double   polar motion coordinates (radians, Note 7)
+**     phpa   double   pressure at the observer (hPa = mB, Note 8)
+**     tc     double   ambient temperature at the observer (deg C)
+**     rh     double   relative humidity at the observer (range 0-1)
+**     wl     double   wavelength (micrometers, Note 9)
+**
+**  Returned:
+**     rc,dc  double   ICRS astrometric RA,Dec (radians)
+**
+**  Returned (function value):
+**            int      status: +1 = dubious year (Note 4)
+**                              0 = OK
+**                             -1 = unacceptable date
+**
+**  Notes:
+**
+**  1)  "Observed" Az,ZD means the position that would be seen by a
+**      perfect geodetically aligned theodolite.  (Zenith distance is
+**      used rather than altitude in order to reflect the fact that no
+**      allowance is made for depression of the horizon.)  This is
+**      related to the observed HA,Dec via the standard rotation, using
+**      the geodetic latitude (corrected for polar motion), while the
+**      observed HA and RA are related simply through the Earth rotation
+**      angle and the site longitude.  "Observed" RA,Dec or HA,Dec thus
+**      means the position that would be seen by a perfect equatorial
+**      with its polar axis aligned to the Earth's axis of rotation.
+**
+**  2)  Only the first character of the type argument is significant.
+**      "R" or "r" indicates that ob1 and ob2 are the observed right
+**      ascension and declination;  "H" or "h" indicates that they are
+**      hour angle (west +ve) and declination;  anything else ("A" or
+**      "a" is recommended) indicates that ob1 and ob2 are azimuth
+**      (north zero, east 90 deg) and zenith distance.
+**
+**  3)  utc1+utc2 is quasi Julian Date (see Note 2), apportioned in any
+**      convenient way between the two arguments, for example where utc1
+**      is the Julian Day Number and utc2 is the fraction of a day.
+**
+**      However, JD cannot unambiguously represent UTC during a leap
+**      second unless special measures are taken.  The convention in the
+**      present function is that the JD day represents UTC days whether
+**      the length is 86399, 86400 or 86401 SI seconds.
+**
+**      Applications should use the function eraDtf2d to convert from
+**      calendar date and time of day into 2-part quasi Julian Date, as
+**      it implements the leap-second-ambiguity convention just
+**      described.
+**
+**  4)  The warning status "dubious year" flags UTCs that predate the
+**      introduction of the time scale or that are too far in the
+**      future to be trusted.  See eraDat for further details.
+**
+**  5)  UT1-UTC is tabulated in IERS bulletins.  It increases by exactly
+**      one second at the end of each positive UTC leap second,
+**      introduced in order to keep UT1-UTC within +/- 0.9s.  n.b. This
+**      practice is under review, and in the future UT1-UTC may grow
+**      essentially without limit.
+**
+**  6)  The geographical coordinates are with respect to the ERFA_WGS84
+**      reference ellipsoid.  TAKE CARE WITH THE LONGITUDE SIGN:  the
+**      longitude required by the present function is east-positive
+**      (i.e. right-handed), in accordance with geographical convention.
+**
+**  7)  The polar motion xp,yp can be obtained from IERS bulletins.  The
+**      values are the coordinates (in radians) of the Celestial
+**      Intermediate Pole with respect to the International Terrestrial
+**      Reference System (see IERS Conventions 2003), measured along the
+**      meridians 0 and 90 deg west respectively.  For many
+**      applications, xp and yp can be set to zero.
+**
+**  8)  If hm, the height above the ellipsoid of the observing station
+**      in meters, is not known but phpa, the pressure in hPa (=mB), is
+**      available, an adequate estimate of hm can be obtained from the
+**      expression
+**
+**            hm = -29.3 * tsl * log ( phpa / 1013.25 );
+**
+**      where tsl is the approximate sea-level air temperature in K
+**      (See Astrophysical Quantities, C.W.Allen, 3rd edition, section
+**      52).  Similarly, if the pressure phpa is not known, it can be
+**      estimated from the height of the observing station, hm, as
+**      follows:
+**
+**            phpa = 1013.25 * exp ( -hm / ( 29.3 * tsl ) );
+**
+**      Note, however, that the refraction is nearly proportional to
+**      the pressure and that an accurate phpa value is important for
+**      precise work.
+**
+**  9)  The argument wl specifies the observing wavelength in
+**      micrometers.  The transition from optical to radio is assumed to
+**      occur at 100 micrometers (about 3000 GHz).
+**
+**  10) The accuracy of the result is limited by the corrections for
+**      refraction, which use a simple A*tan(z) + B*tan^3(z) model.
+**      Providing the meteorological parameters are known accurately and
+**      there are no gross local effects, the predicted astrometric
+**      coordinates should be within 0.05 arcsec (optical) or 1 arcsec
+**      (radio) for a zenith distance of less than 70 degrees, better
+**      than 30 arcsec (optical or radio) at 85 degrees and better
+**      than 20 arcmin (optical) or 30 arcmin (radio) at the horizon.
+**
+**      Without refraction, the complementary functions eraAtco13 and
+**      eraAtoc13 are self-consistent to better than 1 microarcsecond
+**      all over the celestial sphere.  With refraction included,
+**      consistency falls off at high zenith distances, but is still
+**      better than 0.05 arcsec at 85 degrees.
+**
+**  11) It is advisable to take great care with units, as even unlikely
+**      values of the input parameters are accepted and processed in
+**      accordance with the models used.
+**
+**  Called:
+**     eraApco13    astrometry parameters, ICRS-observed
+**     eraAtoiq     quick observed to CIRS
+**     eraAticq     quick CIRS to ICRS
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j;
+   eraASTROM astrom;
+   double eo, ri, di;
+
+
+/* Star-independent astrometry parameters. */
+   j = eraApco13(utc1, utc2, dut1, elong, phi, hm, xp, yp,
+                 phpa, tc, rh, wl, &astrom, &eo);
+
+/* Abort if bad UTC. */
+   if ( j < 0 ) return j;
+
+/* Transform observed to CIRS. */
+   eraAtoiq(type, ob1, ob2, &astrom, &ri, &di);
+
+/* Transform CIRS to ICRS. */
+   eraAticq(ri, di, &astrom, rc, dc);
+
+/* Return OK/warning status. */
+   return j;
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atoi13.c b/ast/erfa/atoi13.c
new file mode 100644
index 0000000..41f2632
--- /dev/null
+++ b/ast/erfa/atoi13.c
@@ -0,0 +1,228 @@
+#include "erfa.h"
+
+int eraAtoi13(const char *type, double ob1, double ob2,
+              double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              double *ri, double *di)
+/*
+**  - - - - - - - - - -
+**   e r a A t o i 1 3
+**  - - - - - - - - - -
+**
+**  Observed place to CIRS.  The caller supplies UTC, site coordinates,
+**  ambient air conditions and observing wavelength.
+**
+**  Given:
+**     type   char[]   type of coordinates - "R", "H" or "A" (Notes 1,2)
+**     ob1    double   observed Az, HA or RA (radians; Az is N=0,E=90)
+**     ob2    double   observed ZD or Dec (radians)
+**     utc1   double   UTC as a 2-part...
+**     utc2   double   ...quasi Julian Date (Notes 3,4)
+**     dut1   double   UT1-UTC (seconds, Note 5)
+**     elong  double   longitude (radians, east +ve, Note 6)
+**     phi    double   geodetic latitude (radians, Note 6)
+**     hm     double   height above the ellipsoid (meters, Notes 6,8)
+**     xp,yp  double   polar motion coordinates (radians, Note 7)
+**     phpa   double   pressure at the observer (hPa = mB, Note 8)
+**     tc     double   ambient temperature at the observer (deg C)
+**     rh     double   relative humidity at the observer (range 0-1)
+**     wl     double   wavelength (micrometers, Note 9)
+**
+**  Returned:
+**     ri     double*  CIRS right ascension (CIO-based, radians)
+**     di     double*  CIRS declination (radians)
+**
+**  Returned (function value):
+**            int      status: +1 = dubious year (Note 2)
+**                              0 = OK
+**                             -1 = unacceptable date
+**
+**  Notes:
+**
+**  1)  "Observed" Az,ZD means the position that would be seen by a
+**      perfect geodetically aligned theodolite.  (Zenith distance is
+**      used rather than altitude in order to reflect the fact that no
+**      allowance is made for depression of the horizon.)  This is
+**      related to the observed HA,Dec via the standard rotation, using
+**      the geodetic latitude (corrected for polar motion), while the
+**      observed HA and RA are related simply through the Earth rotation
+**      angle and the site longitude.  "Observed" RA,Dec or HA,Dec thus
+**      means the position that would be seen by a perfect equatorial
+**      with its polar axis aligned to the Earth's axis of rotation.
+**
+**  2)  Only the first character of the type argument is significant.
+**      "R" or "r" indicates that ob1 and ob2 are the observed right
+**      ascension and declination;  "H" or "h" indicates that they are
+**      hour angle (west +ve) and declination;  anything else ("A" or
+**      "a" is recommended) indicates that ob1 and ob2 are azimuth
+**      (north zero, east 90 deg) and zenith distance.
+**
+**  3)  utc1+utc2 is quasi Julian Date (see Note 2), apportioned in any
+**      convenient way between the two arguments, for example where utc1
+**      is the Julian Day Number and utc2 is the fraction of a day.
+**
+**      However, JD cannot unambiguously represent UTC during a leap
+**      second unless special measures are taken.  The convention in the
+**      present function is that the JD day represents UTC days whether
+**      the length is 86399, 86400 or 86401 SI seconds.
+**
+**      Applications should use the function eraDtf2d to convert from
+**      calendar date and time of day into 2-part quasi Julian Date, as
+**      it implements the leap-second-ambiguity convention just
+**      described.
+**
+**  4)  The warning status "dubious year" flags UTCs that predate the
+**      introduction of the time scale or that are too far in the
+**      future to be trusted.  See eraDat for further details.
+**
+**  5)  UT1-UTC is tabulated in IERS bulletins.  It increases by exactly
+**      one second at the end of each positive UTC leap second,
+**      introduced in order to keep UT1-UTC within +/- 0.9s.  n.b. This
+**      practice is under review, and in the future UT1-UTC may grow
+**      essentially without limit.
+**
+**  6)  The geographical coordinates are with respect to the ERFA_WGS84
+**      reference ellipsoid.  TAKE CARE WITH THE LONGITUDE SIGN:  the
+**      longitude required by the present function is east-positive
+**      (i.e. right-handed), in accordance with geographical convention.
+**
+**  7)  The polar motion xp,yp can be obtained from IERS bulletins.  The
+**      values are the coordinates (in radians) of the Celestial
+**      Intermediate Pole with respect to the International Terrestrial
+**      Reference System (see IERS Conventions 2003), measured along the
+**      meridians 0 and 90 deg west respectively.  For many
+**      applications, xp and yp can be set to zero.
+**
+**  8)  If hm, the height above the ellipsoid of the observing station
+**      in meters, is not known but phpa, the pressure in hPa (=mB), is
+**      available, an adequate estimate of hm can be obtained from the
+**      expression
+**
+**            hm = -29.3 * tsl * log ( phpa / 1013.25 );
+**
+**      where tsl is the approximate sea-level air temperature in K
+**      (See Astrophysical Quantities, C.W.Allen, 3rd edition, section
+**      52).  Similarly, if the pressure phpa is not known, it can be
+**      estimated from the height of the observing station, hm, as
+**      follows:
+**
+**            phpa = 1013.25 * exp ( -hm / ( 29.3 * tsl ) );
+**
+**      Note, however, that the refraction is nearly proportional to
+**      the pressure and that an accurate phpa value is important for
+**      precise work.
+**
+**  9)  The argument wl specifies the observing wavelength in
+**      micrometers.  The transition from optical to radio is assumed to
+**      occur at 100 micrometers (about 3000 GHz).
+**
+**  10) The accuracy of the result is limited by the corrections for
+**      refraction, which use a simple A*tan(z) + B*tan^3(z) model.
+**      Providing the meteorological parameters are known accurately and
+**      there are no gross local effects, the predicted astrometric
+**      coordinates should be within 0.05 arcsec (optical) or 1 arcsec
+**      (radio) for a zenith distance of less than 70 degrees, better
+**      than 30 arcsec (optical or radio) at 85 degrees and better
+**      than 20 arcmin (optical) or 30 arcmin (radio) at the horizon.
+**
+**      Without refraction, the complementary functions eraAtio13 and
+**      eraAtoi13 are self-consistent to better than 1 microarcsecond
+**      all over the celestial sphere.  With refraction included,
+**      consistency falls off at high zenith distances, but is still
+**      better than 0.05 arcsec at 85 degrees.
+**
+**  12) It is advisable to take great care with units, as even unlikely
+**      values of the input parameters are accepted and processed in
+**      accordance with the models used.
+**
+**  Called:
+**     eraApio13    astrometry parameters, CIRS-observed, 2013
+**     eraAtoiq     quick observed to CIRS
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j;
+   eraASTROM astrom;
+
+
+/* Star-independent astrometry parameters for CIRS->observed. */
+   j = eraApio13(utc1, utc2, dut1, elong, phi, hm, xp, yp,
+                 phpa, tc, rh, wl, &astrom);
+
+/* Abort if bad UTC. */
+   if ( j < 0 ) return j;
+
+/* Transform observed to CIRS. */
+   eraAtoiq(type, ob1, ob2, &astrom, ri, di);
+
+/* Return OK/warning status. */
+   return j;
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/atoiq.c b/ast/erfa/atoiq.c
new file mode 100644
index 0000000..d5501a7
--- /dev/null
+++ b/ast/erfa/atoiq.c
@@ -0,0 +1,260 @@
+#include "erfa.h"
+
+void eraAtoiq(const char *type,
+              double ob1, double ob2, eraASTROM *astrom,
+              double *ri, double *di)
+/*
+**  - - - - - - - - -
+**   e r a A t o i q
+**  - - - - - - - - -
+**
+**  Quick observed place to CIRS, given the star-independent astrometry
+**  parameters.
+**
+**  Use of this function is appropriate when efficiency is important and
+**  where many star positions are all to be transformed for one date.
+**  The star-independent astrometry parameters can be obtained by
+**  calling eraApio[13] or eraApco[13].
+**
+**  Given:
+**     type   char[]     type of coordinates: "R", "H" or "A" (Note 1)
+**     ob1    double     observed Az, HA or RA (radians; Az is N=0,E=90)
+**     ob2    double     observed ZD or Dec (radians)
+**     astrom eraASTROM* star-independent astrometry parameters:
+**      pmt    double       PM time interval (SSB, Julian years)
+**      eb     double[3]    SSB to observer (vector, au)
+**      eh     double[3]    Sun to observer (unit vector)
+**      em     double       distance from Sun to observer (au)
+**      v      double[3]    barycentric observer velocity (vector, c)
+**      bm1    double       sqrt(1-|v|^2): reciprocal of Lorenz factor
+**      bpn    double[3][3] bias-precession-nutation matrix
+**      along  double       longitude + s' (radians)
+**      xpl    double       polar motion xp wrt local meridian (radians)
+**      ypl    double       polar motion yp wrt local meridian (radians)
+**      sphi   double       sine of geodetic latitude
+**      cphi   double       cosine of geodetic latitude
+**      diurab double       magnitude of diurnal aberration vector
+**      eral   double       "local" Earth rotation angle (radians)
+**      refa   double       refraction constant A (radians)
+**      refb   double       refraction constant B (radians)
+**
+**  Returned:
+**     ri     double*    CIRS right ascension (CIO-based, radians)
+**     di     double*    CIRS declination (radians)
+**
+**  Notes:
+**
+**  1) "Observed" Az,El means the position that would be seen by a
+**     perfect geodetically aligned theodolite.  This is related to
+**     the observed HA,Dec via the standard rotation, using the geodetic
+**     latitude (corrected for polar motion), while the observed HA and
+**     RA are related simply through the Earth rotation angle and the
+**     site longitude.  "Observed" RA,Dec or HA,Dec thus means the
+**     position that would be seen by a perfect equatorial with its
+**     polar axis aligned to the Earth's axis of rotation.  By removing
+**     from the observed place the effects of atmospheric refraction and
+**     diurnal aberration, the CIRS RA,Dec is obtained.
+**
+**  2) Only the first character of the type argument is significant.
+**     "R" or "r" indicates that ob1 and ob2 are the observed right
+**     ascension and declination;  "H" or "h" indicates that they are
+**     hour angle (west +ve) and declination;  anything else ("A" or
+**     "a" is recommended) indicates that ob1 and ob2 are azimuth (north
+**     zero, east 90 deg) and zenith distance.  (Zenith distance is used
+**     rather than altitude in order to reflect the fact that no
+**     allowance is made for depression of the horizon.)
+**
+**  3) The accuracy of the result is limited by the corrections for
+**     refraction, which use a simple A*tan(z) + B*tan^3(z) model.
+**     Providing the meteorological parameters are known accurately and
+**     there are no gross local effects, the predicted observed
+**     coordinates should be within 0.05 arcsec (optical) or 1 arcsec
+**     (radio) for a zenith distance of less than 70 degrees, better
+**     than 30 arcsec (optical or radio) at 85 degrees and better than
+**     20 arcmin (optical) or 30 arcmin (radio) at the horizon.
+**
+**     Without refraction, the complementary functions eraAtioq and
+**     eraAtoiq are self-consistent to better than 1 microarcsecond all
+**     over the celestial sphere.  With refraction included, consistency
+**     falls off at high zenith distances, but is still better than
+**     0.05 arcsec at 85 degrees.
+**
+**  4) It is advisable to take great care with units, as even unlikely
+**     values of the input parameters are accepted and processed in
+**     accordance with the models used.
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraC2s       p-vector to spherical
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int c;
+   double c1, c2, sphi, cphi, ce, xaeo, yaeo, zaeo, v[3],
+          xmhdo, ymhdo, zmhdo, az, sz, zdo, refa, refb, tz, dref,
+          zdt, xaet, yaet, zaet, xmhda, ymhda, zmhda,
+          f, xhd, yhd, zhd, xpl, ypl, w, hma;
+
+
+/* Coordinate type. */
+   c = (int) type[0];
+
+/* Coordinates. */
+   c1 = ob1;
+   c2 = ob2;
+
+/* Sin, cos of latitude. */
+   sphi = astrom->sphi;
+   cphi = astrom->cphi;
+
+/* Standardize coordinate type. */
+   if ( c == 'r' || c == 'R' ) {
+      c = 'R';
+   } else if ( c == 'h' || c == 'H' ) {
+      c = 'H';
+   } else {
+      c = 'A';
+   }
+
+/* If Az,ZD, convert to Cartesian (S=0,E=90). */
+   if ( c == 'A' ) {
+      ce = sin(c2);
+      xaeo = - cos(c1) * ce;
+      yaeo = sin(c1) * ce;
+      zaeo = cos(c2);
+
+   } else {
+
+   /* If RA,Dec, convert to HA,Dec. */
+      if ( c == 'R' ) c1 = astrom->eral - c1;
+
+   /* To Cartesian -HA,Dec. */
+      eraS2c ( -c1, c2, v );
+      xmhdo = v[0];
+      ymhdo = v[1];
+      zmhdo = v[2];
+
+   /* To Cartesian Az,El (S=0,E=90). */
+      xaeo = sphi*xmhdo - cphi*zmhdo;
+      yaeo = ymhdo;
+      zaeo = cphi*xmhdo + sphi*zmhdo;
+   }
+
+/* Azimuth (S=0,E=90). */
+   az = ( xaeo != 0.0 || yaeo != 0.0 ) ? atan2(yaeo,xaeo) : 0.0;
+
+/* Sine of observed ZD, and observed ZD. */
+   sz = sqrt ( xaeo*xaeo + yaeo*yaeo );
+   zdo = atan2 ( sz, zaeo );
+
+/*
+** Refraction
+** ----------
+*/
+
+/* Fast algorithm using two constant model. */
+   refa = astrom->refa;
+   refb = astrom->refb;
+   tz = sz / zaeo;
+   dref = ( refa + refb*tz*tz ) * tz;
+   zdt = zdo + dref;
+
+/* To Cartesian Az,ZD. */
+   ce = sin(zdt);
+   xaet = cos(az) * ce;
+   yaet = sin(az) * ce;
+   zaet = cos(zdt);
+
+/* Cartesian Az,ZD to Cartesian -HA,Dec. */
+   xmhda = sphi*xaet + cphi*zaet;
+   ymhda = yaet;
+   zmhda = - cphi*xaet + sphi*zaet;
+
+/* Diurnal aberration. */
+   f = ( 1.0 + astrom->diurab*ymhda );
+   xhd = f * xmhda;
+   yhd = f * ( ymhda - astrom->diurab );
+   zhd = f * zmhda;
+
+/* Polar motion. */
+   xpl = astrom->xpl;
+   ypl = astrom->ypl;
+   w = xpl*xhd - ypl*yhd + zhd;
+   v[0] = xhd - xpl*w;
+   v[1] = yhd + ypl*w;
+   v[2] = w - ( xpl*xpl + ypl*ypl ) * zhd;
+
+/* To spherical -HA,Dec. */
+   eraC2s(v, &hma, di);
+
+/* Right ascension. */
+   *ri = eraAnp(astrom->eral + hma);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/bi00.c b/ast/erfa/bi00.c
new file mode 100644
index 0000000..9000221
--- /dev/null
+++ b/ast/erfa/bi00.c
@@ -0,0 +1,125 @@
+#include "erfa.h"
+
+void eraBi00(double *dpsibi, double *depsbi, double *dra)
+/*
+**  - - - - - - - -
+**   e r a B i 0 0
+**  - - - - - - - -
+**
+**  Frame bias components of IAU 2000 precession-nutation models (part
+**  of MHB2000 with additions).
+**
+**  Returned:
+**     dpsibi,depsbi  double  longitude and obliquity corrections
+**     dra            double  the ICRS RA of the J2000.0 mean equinox
+**
+**  Notes:
+**
+**  1) The frame bias corrections in longitude and obliquity (radians)
+**     are required in order to correct for the offset between the GCRS
+**     pole and the mean J2000.0 pole.  They define, with respect to the
+**     GCRS frame, a J2000.0 mean pole that is consistent with the rest
+**     of the IAU 2000A precession-nutation model.
+**
+**  2) In addition to the displacement of the pole, the complete
+**     description of the frame bias requires also an offset in right
+**     ascension.  This is not part of the IAU 2000A model, and is from
+**     Chapront et al. (2002).  It is returned in radians.
+**
+**  3) This is a supplemented implementation of one aspect of the IAU
+**     2000A nutation model, formally adopted by the IAU General
+**     Assembly in 2000, namely MHB2000 (Mathews et al. 2002).
+**
+**  References:
+**
+**     Chapront, J., Chapront-Touze, M. & Francou, G., Astron.
+**     Astrophys., 387, 700, 2002.
+**
+**     Mathews, P.M., Herring, T.A., Buffet, B.A., "Modeling of nutation
+**     and precession   New nutation series for nonrigid Earth and
+**     insights into the Earth's interior", J.Geophys.Res., 107, B4,
+**     2002.  The MHB2000 code itself was obtained on 9th September 2002
+**     from ftp://maia.usno.navy.mil/conv2000/chapter5/IAU2000A.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* The frame bias corrections in longitude and obliquity */
+   const double DPBIAS = -0.041775  * ERFA_DAS2R,
+                DEBIAS = -0.0068192 * ERFA_DAS2R;
+
+/* The ICRS RA of the J2000.0 equinox (Chapront et al., 2002) */
+   const double DRA0 = -0.0146 * ERFA_DAS2R;
+
+
+/* Return the results (which are fixed). */
+   *dpsibi = DPBIAS;
+   *depsbi = DEBIAS;
+   *dra = DRA0;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/bp00.c b/ast/erfa/bp00.c
new file mode 100644
index 0000000..dd387ea
--- /dev/null
+++ b/ast/erfa/bp00.c
@@ -0,0 +1,181 @@
+#include "erfa.h"
+
+void eraBp00(double date1, double date2,
+             double rb[3][3], double rp[3][3], double rbp[3][3])
+/*
+**  - - - - - - - -
+**   e r a B p 0 0
+**  - - - - - - - -
+**
+**  Frame bias and precession, IAU 2000.
+**
+**  Given:
+**     date1,date2  double         TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rb           double[3][3]   frame bias matrix (Note 2)
+**     rp           double[3][3]   precession matrix (Note 3)
+**     rbp          double[3][3]   bias-precession matrix (Note 4)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**             date1         date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix rb transforms vectors from GCRS to mean J2000.0 by
+**     applying frame bias.
+**
+**  3) The matrix rp transforms vectors from J2000.0 mean equator and
+**     equinox to mean equator and equinox of date by applying
+**     precession.
+**
+**  4) The matrix rbp transforms vectors from GCRS to mean equator and
+**     equinox of date by applying frame bias then precession.  It is
+**     the product rp x rb.
+**
+**  5) It is permissible to re-use the same array in the returned
+**     arguments.  The arrays are filled in the order given.
+**
+**  Called:
+**     eraBi00      frame bias components, IAU 2000
+**     eraPr00      IAU 2000 precession adjustments
+**     eraIr        initialize r-matrix to identity
+**     eraRx        rotate around X-axis
+**     eraRy        rotate around Y-axis
+**     eraRz        rotate around Z-axis
+**     eraCr        copy r-matrix
+**     eraRxr       product of two r-matrices
+**
+**  Reference:
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154 (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* J2000.0 obliquity (Lieske et al. 1977) */
+   const double EPS0 = 84381.448 * ERFA_DAS2R;
+
+   double t, dpsibi, depsbi, dra0, psia77, oma77, chia,
+          dpsipr, depspr, psia, oma, rbw[3][3];
+
+
+/* Interval between fundamental epoch J2000.0 and current date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Frame bias. */
+   eraBi00(&dpsibi, &depsbi, &dra0);
+
+/* Precession angles (Lieske et al. 1977) */
+   psia77 = (5038.7784 + (-1.07259 + (-0.001147) * t) * t) * t * ERFA_DAS2R;
+   oma77  =       EPS0 + ((0.05127 + (-0.007726) * t) * t) * t * ERFA_DAS2R;
+   chia   = (  10.5526 + (-2.38064 + (-0.001125) * t) * t) * t * ERFA_DAS2R;
+
+/* Apply IAU 2000 precession corrections. */
+   eraPr00(date1, date2, &dpsipr, &depspr);
+   psia = psia77 + dpsipr;
+   oma  = oma77  + depspr;
+
+/* Frame bias matrix: GCRS to J2000.0. */
+   eraIr(rbw);
+   eraRz(dra0, rbw);
+   eraRy(dpsibi*sin(EPS0), rbw);
+   eraRx(-depsbi, rbw);
+   eraCr(rbw, rb);
+
+/* Precession matrix: J2000.0 to mean of date. */
+   eraIr(rp);
+   eraRx(EPS0, rp);
+   eraRz(-psia, rp);
+   eraRx(-oma, rp);
+   eraRz(chia, rp);
+
+/* Bias-precession matrix: GCRS to mean of date. */
+   eraRxr(rp, rbw, rbp);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/bp06.c b/ast/erfa/bp06.c
new file mode 100644
index 0000000..b5a60c4
--- /dev/null
+++ b/ast/erfa/bp06.c
@@ -0,0 +1,152 @@
+#include "erfa.h"
+
+void eraBp06(double date1, double date2,
+             double rb[3][3], double rp[3][3], double rbp[3][3])
+/*
+**  - - - - - - - -
+**   e r a B p 0 6
+**  - - - - - - - -
+**
+**  Frame bias and precession, IAU 2006.
+**
+**  Given:
+**     date1,date2  double         TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rb           double[3][3]   frame bias matrix (Note 2)
+**     rp           double[3][3]   precession matrix (Note 3)
+**     rbp          double[3][3]   bias-precession matrix (Note 4)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**             date1         date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix rb transforms vectors from GCRS to mean J2000.0 by
+**     applying frame bias.
+**
+**  3) The matrix rp transforms vectors from mean J2000.0 to mean of
+**     date by applying precession.
+**
+**  4) The matrix rbp transforms vectors from GCRS to mean of date by
+**     applying frame bias then precession.  It is the product rp x rb.
+**
+**  5) It is permissible to re-use the same array in the returned
+**     arguments.  The arrays are filled in the order given.
+**
+**  Called:
+**     eraPfw06     bias-precession F-W angles, IAU 2006
+**     eraFw2m      F-W angles to r-matrix
+**     eraPmat06    PB matrix, IAU 2006
+**     eraTr        transpose r-matrix
+**     eraRxr       product of two r-matrices
+**     eraCr        copy r-matrix
+**
+**  References:
+**
+**     Capitaine, N. & Wallace, P.T., 2006, Astron.Astrophys. 450, 855
+**
+**     Wallace, P.T. & Capitaine, N., 2006, Astron.Astrophys. 459, 981
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double gamb, phib, psib, epsa, rbpw[3][3], rbt[3][3];
+
+
+/* B matrix. */
+   eraPfw06(ERFA_DJM0, ERFA_DJM00, &gamb, &phib, &psib, &epsa);
+   eraFw2m(gamb, phib, psib, epsa, rb);
+
+/* PxB matrix (temporary). */
+   eraPmat06(date1, date2, rbpw);
+
+/* P matrix. */
+   eraTr(rb, rbt);
+   eraRxr(rbpw, rbt, rp);
+
+/* PxB matrix. */
+   eraCr(rbpw, rbp);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/bpn2xy.c b/ast/erfa/bpn2xy.c
new file mode 100644
index 0000000..769bd33
--- /dev/null
+++ b/ast/erfa/bpn2xy.c
@@ -0,0 +1,109 @@
+#include "erfa.h"
+
+void eraBpn2xy(double rbpn[3][3], double *x, double *y)
+/*
+**  - - - - - - - - - -
+**   e r a B p n 2 x y
+**  - - - - - - - - - -
+**
+**  Extract from the bias-precession-nutation matrix the X,Y coordinates
+**  of the Celestial Intermediate Pole.
+**
+**  Given:
+**     rbpn      double[3][3]  celestial-to-true matrix (Note 1)
+**
+**  Returned:
+**     x,y       double        Celestial Intermediate Pole (Note 2)
+**
+**  Notes:
+**
+**  1) The matrix rbpn transforms vectors from GCRS to true equator (and
+**     CIO or equinox) of date, and therefore the Celestial Intermediate
+**     Pole unit vector is the bottom row of the matrix.
+**
+**  2) The arguments x,y are components of the Celestial Intermediate
+**     Pole unit vector in the Geocentric Celestial Reference System.
+**
+**  Reference:
+**
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154
+**     (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Extract the X,Y coordinates. */
+   *x = rbpn[2][0];
+   *y = rbpn[2][1];
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2i00a.c b/ast/erfa/c2i00a.c
new file mode 100644
index 0000000..a83b12b
--- /dev/null
+++ b/ast/erfa/c2i00a.c
@@ -0,0 +1,148 @@
+#include "erfa.h"
+
+void eraC2i00a(double date1, double date2, double rc2i[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a C 2 i 0 0 a
+**  - - - - - - - - - -
+**
+**  Form the celestial-to-intermediate matrix for a given date using the
+**  IAU 2000A precession-nutation model.
+**
+**  Given:
+**     date1,date2 double       TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rc2i        double[3][3] celestial-to-intermediate matrix (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix rc2i is the first stage in the transformation from
+**     celestial to terrestrial coordinates:
+**
+**        [TRS]  =  RPOM * R_3(ERA) * rc2i * [CRS]
+**
+**               =  rc2t * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), ERA is the Earth
+**     Rotation Angle and RPOM is the polar motion matrix.
+**
+**  3) A faster, but slightly less accurate result (about 1 mas), can be
+**     obtained by using instead the eraC2i00b function.
+**
+**  Called:
+**     eraPnm00a    classical NPB matrix, IAU 2000A
+**     eraC2ibpn    celestial-to-intermediate matrix, given NPB matrix
+**
+**  References:
+**
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154
+**     (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rbpn[3][3];
+
+
+/* Obtain the celestial-to-true matrix (IAU 2000A). */
+   eraPnm00a(date1, date2, rbpn);
+
+/* Form the celestial-to-intermediate matrix. */
+   eraC2ibpn(date1, date2, rbpn, rc2i);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2i00b.c b/ast/erfa/c2i00b.c
new file mode 100644
index 0000000..7969a34
--- /dev/null
+++ b/ast/erfa/c2i00b.c
@@ -0,0 +1,148 @@
+#include "erfa.h"
+
+void eraC2i00b(double date1, double date2, double rc2i[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a C 2 i 0 0 b
+**  - - - - - - - - - -
+**
+**  Form the celestial-to-intermediate matrix for a given date using the
+**  IAU 2000B precession-nutation model.
+**
+**  Given:
+**     date1,date2 double       TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rc2i        double[3][3] celestial-to-intermediate matrix (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix rc2i is the first stage in the transformation from
+**     celestial to terrestrial coordinates:
+**
+**        [TRS]  =  RPOM * R_3(ERA) * rc2i * [CRS]
+**
+**               =  rc2t * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), ERA is the Earth
+**     Rotation Angle and RPOM is the polar motion matrix.
+**
+**  3) The present function is faster, but slightly less accurate (about
+**     1 mas), than the eraC2i00a function.
+**
+**  Called:
+**     eraPnm00b    classical NPB matrix, IAU 2000B
+**     eraC2ibpn    celestial-to-intermediate matrix, given NPB matrix
+**
+**  References:
+**
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154
+**     (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rbpn[3][3];
+
+
+/* Obtain the celestial-to-true matrix (IAU 2000B). */
+   eraPnm00b(date1, date2, rbpn);
+
+/* Form the celestial-to-intermediate matrix. */
+   eraC2ibpn(date1, date2, rbpn, rc2i);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2i06a.c b/ast/erfa/c2i06a.c
new file mode 100644
index 0000000..9fd6bfe
--- /dev/null
+++ b/ast/erfa/c2i06a.c
@@ -0,0 +1,145 @@
+#include "erfa.h"
+
+void eraC2i06a(double date1, double date2, double rc2i[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a C 2 i 0 6 a
+**  - - - - - - - - - -
+**
+**  Form the celestial-to-intermediate matrix for a given date using the
+**  IAU 2006 precession and IAU 2000A nutation models.
+**
+**  Given:
+**     date1,date2 double       TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rc2i        double[3][3] celestial-to-intermediate matrix (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix rc2i is the first stage in the transformation from
+**     celestial to terrestrial coordinates:
+**
+**        [TRS]  =  RPOM * R_3(ERA) * rc2i * [CRS]
+**
+**               =  RC2T * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), ERA is the Earth
+**     Rotation Angle and RPOM is the polar motion matrix.
+**
+**  Called:
+**     eraPnm06a    classical NPB matrix, IAU 2006/2000A
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**     eraS06       the CIO locator s, given X,Y, IAU 2006
+**     eraC2ixys    celestial-to-intermediate matrix, given X,Y and s
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), 2004, IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rbpn[3][3], x, y, s;
+
+
+/* Obtain the celestial-to-true matrix (IAU 2006/2000A). */
+   eraPnm06a(date1, date2, rbpn);
+
+/* Extract the X,Y coordinates. */
+   eraBpn2xy(rbpn, &x, &y);
+
+/* Obtain the CIO locator. */
+   s = eraS06(date1, date2, x, y);
+
+/* Form the celestial-to-intermediate matrix. */
+   eraC2ixys(x, y, s, rc2i);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2ibpn.c b/ast/erfa/c2ibpn.c
new file mode 100644
index 0000000..62f34a2
--- /dev/null
+++ b/ast/erfa/c2ibpn.c
@@ -0,0 +1,151 @@
+#include "erfa.h"
+
+void eraC2ibpn(double date1, double date2, double rbpn[3][3],
+               double rc2i[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a C 2 i b p n
+**  - - - - - - - - - -
+**
+**  Form the celestial-to-intermediate matrix for a given date given
+**  the bias-precession-nutation matrix.  IAU 2000.
+**
+**  Given:
+**     date1,date2 double       TT as a 2-part Julian Date (Note 1)
+**     rbpn        double[3][3] celestial-to-true matrix (Note 2)
+**
+**  Returned:
+**     rc2i        double[3][3] celestial-to-intermediate matrix (Note 3)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix rbpn transforms vectors from GCRS to true equator (and
+**     CIO or equinox) of date.  Only the CIP (bottom row) is used.
+**
+**  3) The matrix rc2i is the first stage in the transformation from
+**     celestial to terrestrial coordinates:
+**
+**        [TRS] = RPOM * R_3(ERA) * rc2i * [CRS]
+**
+**              = RC2T * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), ERA is the Earth
+**     Rotation Angle and RPOM is the polar motion matrix.
+**
+**  4) Although its name does not include "00", This function is in fact
+**     specific to the IAU 2000 models.
+**
+**  Called:
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**     eraC2ixy     celestial-to-intermediate matrix, given X,Y
+**
+**  References:
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154 (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double x, y;
+
+
+/* Extract the X,Y coordinates. */
+   eraBpn2xy(rbpn, &x, &y);
+
+/* Form the celestial-to-intermediate matrix (n.b. IAU 2000 specific). */
+   eraC2ixy(date1, date2, x, y, rc2i);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2ixy.c b/ast/erfa/c2ixy.c
new file mode 100644
index 0000000..a488f20
--- /dev/null
+++ b/ast/erfa/c2ixy.c
@@ -0,0 +1,140 @@
+#include "erfa.h"
+
+void eraC2ixy(double date1, double date2, double x, double y,
+              double rc2i[3][3])
+/*
+**  - - - - - - - - -
+**   e r a C 2 i x y
+**  - - - - - - - - -
+**
+**  Form the celestial to intermediate-frame-of-date matrix for a given
+**  date when the CIP X,Y coordinates are known.  IAU 2000.
+**
+**  Given:
+**     date1,date2 double       TT as a 2-part Julian Date (Note 1)
+**     x,y         double       Celestial Intermediate Pole (Note 2)
+**
+**  Returned:
+**     rc2i        double[3][3] celestial-to-intermediate matrix (Note 3)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The Celestial Intermediate Pole coordinates are the x,y components
+**     of the unit vector in the Geocentric Celestial Reference System.
+**
+**  3) The matrix rc2i is the first stage in the transformation from
+**     celestial to terrestrial coordinates:
+**
+**        [TRS] = RPOM * R_3(ERA) * rc2i * [CRS]
+**
+**              = RC2T * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), ERA is the Earth
+**     Rotation Angle and RPOM is the polar motion matrix.
+**
+**  4) Although its name does not include "00", This function is in fact
+**     specific to the IAU 2000 models.
+**
+**  Called:
+**     eraC2ixys    celestial-to-intermediate matrix, given X,Y and s
+**     eraS00       the CIO locator s, given X,Y, IAU 2000A
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+
+{
+/* Compute s and then the matrix. */
+   eraC2ixys(x, y, eraS00(date1, date2, x, y), rc2i);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2ixys.c b/ast/erfa/c2ixys.c
new file mode 100644
index 0000000..1c92687
--- /dev/null
+++ b/ast/erfa/c2ixys.c
@@ -0,0 +1,132 @@
+#include "erfa.h"
+
+void eraC2ixys(double x, double y, double s, double rc2i[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a C 2 i x y s
+**  - - - - - - - - - -
+**
+**  Form the celestial to intermediate-frame-of-date matrix given the CIP
+**  X,Y and the CIO locator s.
+**
+**  Given:
+**     x,y      double         Celestial Intermediate Pole (Note 1)
+**     s        double         the CIO locator s (Note 2)
+**
+**  Returned:
+**     rc2i     double[3][3]   celestial-to-intermediate matrix (Note 3)
+**
+**  Notes:
+**
+**  1) The Celestial Intermediate Pole coordinates are the x,y
+**     components of the unit vector in the Geocentric Celestial
+**     Reference System.
+**
+**  2) The CIO locator s (in radians) positions the Celestial
+**     Intermediate Origin on the equator of the CIP.
+**
+**  3) The matrix rc2i is the first stage in the transformation from
+**     celestial to terrestrial coordinates:
+**
+**        [TRS] = RPOM * R_3(ERA) * rc2i * [CRS]
+**
+**              = RC2T * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), ERA is the Earth
+**     Rotation Angle and RPOM is the polar motion matrix.
+**
+**  Called:
+**     eraIr        initialize r-matrix to identity
+**     eraRz        rotate around Z-axis
+**     eraRy        rotate around Y-axis
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double r2, e, d;
+
+
+/* Obtain the spherical angles E and d. */
+   r2 = x*x + y*y;
+   e = (r2 > 0.0) ? atan2(y, x) : 0.0;
+   d = atan(sqrt(r2 / (1.0 - r2)));
+
+/* Form the matrix. */
+   eraIr(rc2i);
+   eraRz(e, rc2i);
+   eraRy(d, rc2i);
+   eraRz(-(e+s), rc2i);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2s.c b/ast/erfa/c2s.c
new file mode 100644
index 0000000..6f31a2b
--- /dev/null
+++ b/ast/erfa/c2s.c
@@ -0,0 +1,105 @@
+#include "erfa.h"
+
+void eraC2s(double p[3], double *theta, double *phi)
+/*
+**  - - - - - - -
+**   e r a C 2 s
+**  - - - - - - -
+**
+**  P-vector to spherical coordinates.
+**
+**  Given:
+**     p      double[3]    p-vector
+**
+**  Returned:
+**     theta  double       longitude angle (radians)
+**     phi    double       latitude angle (radians)
+**
+**  Notes:
+**
+**  1) The vector p can have any magnitude; only its direction is used.
+**
+**  2) If p is null, zero theta and phi are returned.
+**
+**  3) At either pole, zero theta is returned.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double x, y, z, d2;
+
+
+   x  = p[0];
+   y  = p[1];
+   z  = p[2];
+   d2 = x*x + y*y;
+
+   *theta = (d2 == 0.0) ? 0.0 : atan2(y, x);
+   *phi = (z == 0.0) ? 0.0 : atan2(z, sqrt(d2));
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2t00a.c b/ast/erfa/c2t00a.c
new file mode 100644
index 0000000..94439fa
--- /dev/null
+++ b/ast/erfa/c2t00a.c
@@ -0,0 +1,163 @@
+#include "erfa.h"
+
+void eraC2t00a(double tta, double ttb, double uta, double utb,
+               double xp, double yp, double rc2t[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a C 2 t 0 0 a
+**  - - - - - - - - - -
+**
+**  Form the celestial to terrestrial matrix given the date, the UT1 and
+**  the polar motion, using the IAU 2000A nutation model.
+**
+**  Given:
+**     tta,ttb  double         TT as a 2-part Julian Date (Note 1)
+**     uta,utb  double         UT1 as a 2-part Julian Date (Note 1)
+**     xp,yp    double         coordinates of the pole (radians, Note 2)
+**
+**  Returned:
+**     rc2t     double[3][3]   celestial-to-terrestrial matrix (Note 3)
+**
+**  Notes:
+**
+**  1) The TT and UT1 dates tta+ttb and uta+utb are Julian Dates,
+**     apportioned in any convenient way between the arguments uta and
+**     utb.  For example, JD(UT1)=2450123.7 could be expressed in any of
+**     these ways, among others:
+**
+**             uta            utb
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  In the case of uta,utb, the
+**     date & time method is best matched to the Earth rotation angle
+**     algorithm used:  maximum precision is delivered when the uta
+**     argument is for 0hrs UT1 on the day in question and the utb
+**     argument lies in the range 0 to 1, or vice versa.
+**
+**  2) The arguments xp and yp are the coordinates (in radians) of the
+**     Celestial Intermediate Pole with respect to the International
+**     Terrestrial Reference System (see IERS Conventions 2003),
+**     measured along the meridians to 0 and 90 deg west respectively.
+**
+**  3) The matrix rc2t transforms from celestial to terrestrial
+**     coordinates:
+**
+**        [TRS] = RPOM * R_3(ERA) * RC2I * [CRS]
+**
+**              = rc2t * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), RC2I is the
+**     celestial-to-intermediate matrix, ERA is the Earth rotation
+**     angle and RPOM is the polar motion matrix.
+**
+**  4) A faster, but slightly less accurate result (about 1 mas), can
+**     be obtained by using instead the eraC2t00b function.
+**
+**  Called:
+**     eraC2i00a    celestial-to-intermediate matrix, IAU 2000A
+**     eraEra00     Earth rotation angle, IAU 2000
+**     eraSp00      the TIO locator s', IERS 2000
+**     eraPom00     polar motion matrix
+**     eraC2tcio    form CIO-based celestial-to-terrestrial matrix
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rc2i[3][3], era, sp, rpom[3][3];
+
+
+/* Form the celestial-to-intermediate matrix for this TT (IAU 2000A). */
+   eraC2i00a(tta, ttb, rc2i );
+
+/* Predict the Earth rotation angle for this UT1. */
+   era = eraEra00(uta, utb);
+
+/* Estimate s'. */
+   sp = eraSp00(tta, ttb);
+
+/* Form the polar motion matrix. */
+   eraPom00(xp, yp, sp, rpom);
+
+/* Combine to form the celestial-to-terrestrial matrix. */
+   eraC2tcio(rc2i, era, rpom, rc2t);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2t00b.c b/ast/erfa/c2t00b.c
new file mode 100644
index 0000000..fe19504
--- /dev/null
+++ b/ast/erfa/c2t00b.c
@@ -0,0 +1,159 @@
+#include "erfa.h"
+
+void eraC2t00b(double tta, double ttb, double uta, double utb,
+               double xp, double yp, double rc2t[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a C 2 t 0 0 b
+**  - - - - - - - - - -
+**
+**  Form the celestial to terrestrial matrix given the date, the UT1 and
+**  the polar motion, using the IAU 2000B nutation model.
+**
+**  Given:
+**     tta,ttb  double         TT as a 2-part Julian Date (Note 1)
+**     uta,utb  double         UT1 as a 2-part Julian Date (Note 1)
+**     xp,yp    double         coordinates of the pole (radians, Note 2)
+**
+**  Returned:
+**     rc2t     double[3][3]   celestial-to-terrestrial matrix (Note 3)
+**
+**  Notes:
+**
+**  1) The TT and UT1 dates tta+ttb and uta+utb are Julian Dates,
+**     apportioned in any convenient way between the arguments uta and
+**     utb.  For example, JD(UT1)=2450123.7 could be expressed in any of
+**     these ways, among others:
+**
+**             uta            utb
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  In the case of uta,utb, the
+**     date & time method is best matched to the Earth rotation angle
+**     algorithm used:  maximum precision is delivered when the uta
+**     argument is for 0hrs UT1 on the day in question and the utb
+**     argument lies in the range 0 to 1, or vice versa.
+**
+**  2) The arguments xp and yp are the coordinates (in radians) of the
+**     Celestial Intermediate Pole with respect to the International
+**     Terrestrial Reference System (see IERS Conventions 2003),
+**     measured along the meridians to 0 and 90 deg west respectively.
+**
+**  3) The matrix rc2t transforms from celestial to terrestrial
+**     coordinates:
+**
+**        [TRS] = RPOM * R_3(ERA) * RC2I * [CRS]
+**
+**              = rc2t * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), RC2I is the
+**     celestial-to-intermediate matrix, ERA is the Earth rotation
+**     angle and RPOM is the polar motion matrix.
+**
+**  4) The present function is faster, but slightly less accurate (about
+**     1 mas), than the eraC2t00a function.
+**
+**  Called:
+**     eraC2i00b    celestial-to-intermediate matrix, IAU 2000B
+**     eraEra00     Earth rotation angle, IAU 2000
+**     eraPom00     polar motion matrix
+**     eraC2tcio    form CIO-based celestial-to-terrestrial matrix
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rc2i[3][3], era, rpom[3][3];
+
+
+/* Form the celestial-to-intermediate matrix for this TT (IAU 2000B). */
+   eraC2i00b(tta, ttb, rc2i);
+
+/* Predict the Earth rotation angle for this UT1. */
+   era = eraEra00(uta, utb);
+
+/* Form the polar motion matrix (neglecting s'). */
+   eraPom00(xp, yp, 0.0, rpom);
+
+/* Combine to form the celestial-to-terrestrial matrix. */
+   eraC2tcio(rc2i, era, rpom, rc2t);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2t06a.c b/ast/erfa/c2t06a.c
new file mode 100644
index 0000000..3b8b2ff
--- /dev/null
+++ b/ast/erfa/c2t06a.c
@@ -0,0 +1,161 @@
+#include "erfa.h"
+
+void eraC2t06a(double tta, double ttb, double uta, double utb,
+               double xp, double yp, double rc2t[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a C 2 t 0 6 a
+**  - - - - - - - - - -
+**
+**  Form the celestial to terrestrial matrix given the date, the UT1 and
+**  the polar motion, using the IAU 2006 precession and IAU 2000A
+**  nutation models.
+**
+**  Given:
+**     tta,ttb  double         TT as a 2-part Julian Date (Note 1)
+**     uta,utb  double         UT1 as a 2-part Julian Date (Note 1)
+**     xp,yp    double         coordinates of the pole (radians, Note 2)
+**
+**  Returned:
+**     rc2t     double[3][3]   celestial-to-terrestrial matrix (Note 3)
+**
+**  Notes:
+**
+**  1) The TT and UT1 dates tta+ttb and uta+utb are Julian Dates,
+**     apportioned in any convenient way between the arguments uta and
+**     utb.  For example, JD(UT1)=2450123.7 could be expressed in any of
+**     these ways, among others:
+**
+**             uta            utb
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  In the case of uta,utb, the
+**     date & time method is best matched to the Earth rotation angle
+**     algorithm used:  maximum precision is delivered when the uta
+**     argument is for 0hrs UT1 on the day in question and the utb
+**     argument lies in the range 0 to 1, or vice versa.
+**
+**  2) The arguments xp and yp are the coordinates (in radians) of the
+**     Celestial Intermediate Pole with respect to the International
+**     Terrestrial Reference System (see IERS Conventions 2003),
+**     measured along the meridians to 0 and 90 deg west respectively.
+**
+**  3) The matrix rc2t transforms from celestial to terrestrial
+**     coordinates:
+**
+**        [TRS] = RPOM * R_3(ERA) * RC2I * [CRS]
+**
+**              = rc2t * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), RC2I is the
+**     celestial-to-intermediate matrix, ERA is the Earth rotation
+**     angle and RPOM is the polar motion matrix.
+**
+**  Called:
+**     eraC2i06a    celestial-to-intermediate matrix, IAU 2006/2000A
+**     eraEra00     Earth rotation angle, IAU 2000
+**     eraSp00      the TIO locator s', IERS 2000
+**     eraPom00     polar motion matrix
+**     eraC2tcio    form CIO-based celestial-to-terrestrial matrix
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), 2004, IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rc2i[3][3], era, sp, rpom[3][3];
+
+
+/* Form the celestial-to-intermediate matrix for this TT. */
+   eraC2i06a(tta, ttb, rc2i);
+
+/* Predict the Earth rotation angle for this UT1. */
+   era = eraEra00(uta, utb);
+
+/* Estimate s'. */
+   sp = eraSp00(tta, ttb);
+
+/* Form the polar motion matrix. */
+   eraPom00(xp, yp, sp, rpom);
+
+/* Combine to form the celestial-to-terrestrial matrix. */
+   eraC2tcio(rc2i, era, rpom, rc2t);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2tcio.c b/ast/erfa/c2tcio.c
new file mode 100644
index 0000000..2bdd004
--- /dev/null
+++ b/ast/erfa/c2tcio.c
@@ -0,0 +1,131 @@
+#include "erfa.h"
+
+void eraC2tcio(double rc2i[3][3], double era, double rpom[3][3],
+               double rc2t[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a C 2 t c i o
+**  - - - - - - - - - -
+**
+**  Assemble the celestial to terrestrial matrix from CIO-based
+**  components (the celestial-to-intermediate matrix, the Earth Rotation
+**  Angle and the polar motion matrix).
+**
+**  Given:
+**     rc2i     double[3][3]    celestial-to-intermediate matrix
+**     era      double          Earth rotation angle (radians)
+**     rpom     double[3][3]    polar-motion matrix
+**
+**  Returned:
+**     rc2t     double[3][3]    celestial-to-terrestrial matrix
+**
+**  Notes:
+**
+**  1) This function constructs the rotation matrix that transforms
+**     vectors in the celestial system into vectors in the terrestrial
+**     system.  It does so starting from precomputed components, namely
+**     the matrix which rotates from celestial coordinates to the
+**     intermediate frame, the Earth rotation angle and the polar motion
+**     matrix.  One use of the present function is when generating a
+**     series of celestial-to-terrestrial matrices where only the Earth
+**     Rotation Angle changes, avoiding the considerable overhead of
+**     recomputing the precession-nutation more often than necessary to
+**     achieve given accuracy objectives.
+**
+**  2) The relationship between the arguments is as follows:
+**
+**        [TRS] = RPOM * R_3(ERA) * rc2i * [CRS]
+**
+**              = rc2t * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003).
+**
+**  Called:
+**     eraCr        copy r-matrix
+**     eraRz        rotate around Z-axis
+**     eraRxr       product of two r-matrices
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), 2004, IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double r[3][3];
+
+
+/* Construct the matrix. */
+   eraCr(rc2i, r);
+   eraRz(era, r);
+   eraRxr(rpom, r, rc2t);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2teqx.c b/ast/erfa/c2teqx.c
new file mode 100644
index 0000000..c86e76c
--- /dev/null
+++ b/ast/erfa/c2teqx.c
@@ -0,0 +1,131 @@
+#include "erfa.h"
+
+void eraC2teqx(double rbpn[3][3], double gst, double rpom[3][3],
+               double rc2t[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a C 2 t e q x
+**  - - - - - - - - - -
+**
+**  Assemble the celestial to terrestrial matrix from equinox-based
+**  components (the celestial-to-true matrix, the Greenwich Apparent
+**  Sidereal Time and the polar motion matrix).
+**
+**  Given:
+**     rbpn   double[3][3]  celestial-to-true matrix
+**     gst    double        Greenwich (apparent) Sidereal Time (radians)
+**     rpom   double[3][3]  polar-motion matrix
+**
+**  Returned:
+**     rc2t   double[3][3]  celestial-to-terrestrial matrix (Note 2)
+**
+**  Notes:
+**
+**  1) This function constructs the rotation matrix that transforms
+**     vectors in the celestial system into vectors in the terrestrial
+**     system.  It does so starting from precomputed components, namely
+**     the matrix which rotates from celestial coordinates to the
+**     true equator and equinox of date, the Greenwich Apparent Sidereal
+**     Time and the polar motion matrix.  One use of the present function
+**     is when generating a series of celestial-to-terrestrial matrices
+**     where only the Sidereal Time changes, avoiding the considerable
+**     overhead of recomputing the precession-nutation more often than
+**     necessary to achieve given accuracy objectives.
+**
+**  2) The relationship between the arguments is as follows:
+**
+**        [TRS] = rpom * R_3(gst) * rbpn * [CRS]
+**
+**              = rc2t * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003).
+**
+**  Called:
+**     eraCr        copy r-matrix
+**     eraRz        rotate around Z-axis
+**     eraRxr       product of two r-matrices
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double r[3][3];
+
+
+/* Construct the matrix. */
+   eraCr(rbpn, r);
+   eraRz(gst, r);
+   eraRxr(rpom, r, rc2t);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2tpe.c b/ast/erfa/c2tpe.c
new file mode 100644
index 0000000..c6e4bda
--- /dev/null
+++ b/ast/erfa/c2tpe.c
@@ -0,0 +1,176 @@
+#include "erfa.h"
+
+void eraC2tpe(double tta, double ttb, double uta, double utb,
+              double dpsi, double deps, double xp, double yp,
+              double rc2t[3][3])
+/*
+**  - - - - - - - - -
+**   e r a C 2 t p e
+**  - - - - - - - - -
+**
+**  Form the celestial to terrestrial matrix given the date, the UT1,
+**  the nutation and the polar motion.  IAU 2000.
+**
+**  Given:
+**     tta,ttb    double        TT as a 2-part Julian Date (Note 1)
+**     uta,utb    double        UT1 as a 2-part Julian Date (Note 1)
+**     dpsi,deps  double        nutation (Note 2)
+**     xp,yp      double        coordinates of the pole (radians, Note 3)
+**
+**  Returned:
+**     rc2t       double[3][3]  celestial-to-terrestrial matrix (Note 4)
+**
+**  Notes:
+**
+**  1) The TT and UT1 dates tta+ttb and uta+utb are Julian Dates,
+**     apportioned in any convenient way between the arguments uta and
+**     utb.  For example, JD(UT1)=2450123.7 could be expressed in any of
+**     these ways, among others:
+**
+**             uta            utb
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  In the case of uta,utb, the
+**     date & time method is best matched to the Earth rotation angle
+**     algorithm used:  maximum precision is delivered when the uta
+**     argument is for 0hrs UT1 on the day in question and the utb
+**     argument lies in the range 0 to 1, or vice versa.
+**
+**  2) The caller is responsible for providing the nutation components;
+**     they are in longitude and obliquity, in radians and are with
+**     respect to the equinox and ecliptic of date.  For high-accuracy
+**     applications, free core nutation should be included as well as
+**     any other relevant corrections to the position of the CIP.
+**
+**  3) The arguments xp and yp are the coordinates (in radians) of the
+**     Celestial Intermediate Pole with respect to the International
+**     Terrestrial Reference System (see IERS Conventions 2003),
+**     measured along the meridians to 0 and 90 deg west respectively.
+**
+**  4) The matrix rc2t transforms from celestial to terrestrial
+**     coordinates:
+**
+**        [TRS] = RPOM * R_3(GST) * RBPN * [CRS]
+**
+**              = rc2t * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), RBPN is the
+**     bias-precession-nutation matrix, GST is the Greenwich (apparent)
+**     Sidereal Time and RPOM is the polar motion matrix.
+**
+**  5) Although its name does not include "00", This function is in fact
+**     specific to the IAU 2000 models.
+**
+**  Called:
+**     eraPn00      bias/precession/nutation results, IAU 2000
+**     eraGmst00    Greenwich mean sidereal time, IAU 2000
+**     eraSp00      the TIO locator s', IERS 2000
+**     eraEe00      equation of the equinoxes, IAU 2000
+**     eraPom00     polar motion matrix
+**     eraC2teqx    form equinox-based celestial-to-terrestrial matrix
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double epsa, rb[3][3], rp[3][3], rbp[3][3], rn[3][3],
+          rbpn[3][3], gmst, ee, sp, rpom[3][3];
+
+
+/* Form the celestial-to-true matrix for this TT. */
+   eraPn00(tta, ttb, dpsi, deps, &epsa, rb, rp, rbp, rn, rbpn);
+
+/* Predict the Greenwich Mean Sidereal Time for this UT1 and TT. */
+   gmst = eraGmst00(uta, utb, tta, ttb);
+
+/* Predict the equation of the equinoxes given TT and nutation. */
+   ee = eraEe00(tta, ttb, epsa, dpsi);
+
+/* Estimate s'. */
+   sp = eraSp00(tta, ttb);
+
+/* Form the polar motion matrix. */
+   eraPom00(xp, yp, sp, rpom);
+
+/* Combine to form the celestial-to-terrestrial matrix. */
+   eraC2teqx(rbpn, gmst + ee, rpom, rc2t);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/c2txy.c b/ast/erfa/c2txy.c
new file mode 100644
index 0000000..198c7a8
--- /dev/null
+++ b/ast/erfa/c2txy.c
@@ -0,0 +1,168 @@
+#include "erfa.h"
+
+void eraC2txy(double tta, double ttb, double uta, double utb,
+              double x, double y, double xp, double yp,
+              double rc2t[3][3])
+/*
+**  - - - - - - - - -
+**   e r a C 2 t x y
+**  - - - - - - - - -
+**
+**  Form the celestial to terrestrial matrix given the date, the UT1,
+**  the CIP coordinates and the polar motion.  IAU 2000.
+**
+**  Given:
+**     tta,ttb  double         TT as a 2-part Julian Date (Note 1)
+**     uta,utb  double         UT1 as a 2-part Julian Date (Note 1)
+**     x,y      double         Celestial Intermediate Pole (Note 2)
+**     xp,yp    double         coordinates of the pole (radians, Note 3)
+**
+**  Returned:
+**     rc2t     double[3][3]   celestial-to-terrestrial matrix (Note 4)
+**
+**  Notes:
+**
+**  1) The TT and UT1 dates tta+ttb and uta+utb are Julian Dates,
+**     apportioned in any convenient way between the arguments uta and
+**     utb.  For example, JD(UT1)=2450123.7 could be expressed in any o
+**     these ways, among others:
+**
+**             uta            utb
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  In the case of uta,utb, the
+**     date & time method is best matched to the Earth rotation angle
+**     algorithm used:  maximum precision is delivered when the uta
+**     argument is for 0hrs UT1 on the day in question and the utb
+**     argument lies in the range 0 to 1, or vice versa.
+**
+**  2) The Celestial Intermediate Pole coordinates are the x,y
+**     components of the unit vector in the Geocentric Celestial
+**     Reference System.
+**
+**  3) The arguments xp and yp are the coordinates (in radians) of the
+**     Celestial Intermediate Pole with respect to the International
+**     Terrestrial Reference System (see IERS Conventions 2003),
+**     measured along the meridians to 0 and 90 deg west respectively.
+**
+**  4) The matrix rc2t transforms from celestial to terrestrial
+**     coordinates:
+**
+**        [TRS] = RPOM * R_3(ERA) * RC2I * [CRS]
+**
+**              = rc2t * [CRS]
+**
+**     where [CRS] is a vector in the Geocentric Celestial Reference
+**     System and [TRS] is a vector in the International Terrestrial
+**     Reference System (see IERS Conventions 2003), ERA is the Earth
+**     Rotation Angle and RPOM is the polar motion matrix.
+**
+**  5) Although its name does not include "00", This function is in fact
+**     specific to the IAU 2000 models.
+**
+**  Called:
+**     eraC2ixy     celestial-to-intermediate matrix, given X,Y
+**     eraEra00     Earth rotation angle, IAU 2000
+**     eraSp00      the TIO locator s', IERS 2000
+**     eraPom00     polar motion matrix
+**     eraC2tcio    form CIO-based celestial-to-terrestrial matrix
+**
+** Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rc2i[3][3], era, sp, rpom[3][3];
+
+
+/* Form the celestial-to-intermediate matrix for this TT. */
+   eraC2ixy(tta, ttb, x, y, rc2i);
+
+/* Predict the Earth rotation angle for this UT1. */
+   era = eraEra00(uta, utb);
+
+/* Estimate s'. */
+   sp = eraSp00(tta, ttb);
+
+/* Form the polar motion matrix. */
+   eraPom00(xp, yp, sp, rpom);
+
+/* Combine to form the celestial-to-terrestrial matrix. */
+   eraC2tcio(rc2i, era, rpom, rc2t);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/cal2jd.c b/ast/erfa/cal2jd.c
new file mode 100644
index 0000000..5d4d689
--- /dev/null
+++ b/ast/erfa/cal2jd.c
@@ -0,0 +1,148 @@
+#include "erfa.h"
+
+int eraCal2jd(int iy, int im, int id, double *djm0, double *djm)
+/*
+**  - - - - - - - - - -
+**   e r a C a l 2 j d
+**  - - - - - - - - - -
+**
+**  Gregorian Calendar to Julian Date.
+**
+**  Given:
+**     iy,im,id  int     year, month, day in Gregorian calendar (Note 1)
+**
+**  Returned:
+**     djm0      double  MJD zero-point: always 2400000.5
+**     djm       double  Modified Julian Date for 0 hrs
+**
+**  Returned (function value):
+**               int     status:
+**                           0 = OK
+**                          -1 = bad year   (Note 3: JD not computed)
+**                          -2 = bad month  (JD not computed)
+**                          -3 = bad day    (JD computed)
+**
+**  Notes:
+**
+**  1) The algorithm used is valid from -4800 March 1, but this
+**     implementation rejects dates before -4799 January 1.
+**
+**  2) The Julian Date is returned in two pieces, in the usual ERFA
+**     manner, which is designed to preserve time resolution.  The
+**     Julian Date is available as a single number by adding djm0 and
+**     djm.
+**
+**  3) In early eras the conversion is from the "Proleptic Gregorian
+**     Calendar";  no account is taken of the date(s) of adoption of
+**     the Gregorian Calendar, nor is the AD/BC numbering convention
+**     observed.
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Section 12.92 (p604).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j, ly, my;
+   long iypmy;
+
+/* Earliest year allowed (4800BC) */
+   const int IYMIN = -4799;
+
+/* Month lengths in days */
+   static const int mtab[]
+                     = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+
+/* Preset status. */
+   j = 0;
+
+/* Validate year and month. */
+   if (iy < IYMIN) return -1;
+   if (im < 1 || im > 12) return -2;
+
+/* If February in a leap year, 1, otherwise 0. */
+   ly = ((im == 2) && !(iy%4) && (iy%100 || !(iy%400)));
+
+/* Validate day, taking into account leap years. */
+   if ( (id < 1) || (id > (mtab[im-1] + ly))) j = -3;
+
+/* Return result. */
+   my = (im - 14) / 12;
+   iypmy = (long) (iy + my);
+   *djm0 = ERFA_DJM0;
+   *djm = (double)((1461L * (iypmy + 4800L)) / 4L
+                 + (367L * (long) (im - 2 - 12 * my)) / 12L
+                 - (3L * ((iypmy + 4900L) / 100L)) / 4L
+                 + (long) id - 2432076L);
+
+/* Return status. */
+   return j;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/cp.c b/ast/erfa/cp.c
new file mode 100644
index 0000000..cf3ab90
--- /dev/null
+++ b/ast/erfa/cp.c
@@ -0,0 +1,89 @@
+#include "erfa.h"
+
+void eraCp(double p[3], double c[3])
+/*
+**  - - - - - -
+**   e r a C p
+**  - - - - - -
+**
+**  Copy a p-vector.
+**
+**  Given:
+**     p        double[3]     p-vector to be copied
+**
+**  Returned:
+**     c        double[3]     copy
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   c[0] = p[0];
+   c[1] = p[1];
+   c[2] = p[2];
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/cpv.c b/ast/erfa/cpv.c
new file mode 100644
index 0000000..05204f5
--- /dev/null
+++ b/ast/erfa/cpv.c
@@ -0,0 +1,91 @@
+#include "erfa.h"
+
+void eraCpv(double pv[2][3], double c[2][3])
+/*
+**  - - - - - - -
+**   e r a C p v
+**  - - - - - - -
+**
+**  Copy a position/velocity vector.
+**
+**  Given:
+**     pv     double[2][3]    position/velocity vector to be copied
+**
+**  Returned:
+**     c      double[2][3]    copy
+**
+**  Called:
+**     eraCp        copy p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraCp(pv[0], c[0]);
+   eraCp(pv[1], c[1]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/cr.c b/ast/erfa/cr.c
new file mode 100644
index 0000000..721781d
--- /dev/null
+++ b/ast/erfa/cr.c
@@ -0,0 +1,92 @@
+#include "erfa.h"
+
+void eraCr(double r[3][3], double c[3][3])
+/*
+**  - - - - - -
+**   e r a C r
+**  - - - - - -
+**
+**  Copy an r-matrix.
+**
+**  Given:
+**     r        double[3][3]    r-matrix to be copied
+**
+**  Returned:
+**     c        double[3][3]    copy
+**
+**  Called:
+**     eraCp        copy p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraCp(r[0], c[0]);
+   eraCp(r[1], c[1]);
+   eraCp(r[2], c[2]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/d2dtf.c b/ast/erfa/d2dtf.c
new file mode 100644
index 0000000..4476970
--- /dev/null
+++ b/ast/erfa/d2dtf.c
@@ -0,0 +1,245 @@
+#include "erfa.h"
+#include <string.h>
+
+int eraD2dtf(const char *scale, int ndp, double d1, double d2,
+             int *iy, int *im, int *id, int ihmsf[4])
+/*
+**  - - - - - - - - -
+**   e r a D 2 d t f
+**  - - - - - - - - -
+**
+**  Format for output a 2-part Julian Date (or in the case of UTC a
+**  quasi-JD form that includes special provision for leap seconds).
+**
+**  Given:
+**     scale     char[]  time scale ID (Note 1)
+**     ndp       int     resolution (Note 2)
+**     d1,d2     double  time as a 2-part Julian Date (Notes 3,4)
+**
+**  Returned:
+**     iy,im,id  int     year, month, day in Gregorian calendar (Note 5)
+**     ihmsf     int[4]  hours, minutes, seconds, fraction (Note 1)
+**
+**  Returned (function value):
+**               int     status: +1 = dubious year (Note 5)
+**                                0 = OK
+**                               -1 = unacceptable date (Note 6)
+**
+**  Notes:
+**
+**  1) scale identifies the time scale.  Only the value "UTC" (in upper
+**     case) is significant, and enables handling of leap seconds (see
+**     Note 4).
+**
+**  2) ndp is the number of decimal places in the seconds field, and can
+**     have negative as well as positive values, such as:
+**
+**     ndp         resolution
+**     -4            1 00 00
+**     -3            0 10 00
+**     -2            0 01 00
+**     -1            0 00 10
+**      0            0 00 01
+**      1            0 00 00.1
+**      2            0 00 00.01
+**      3            0 00 00.001
+**
+**     The limits are platform dependent, but a safe range is -5 to +9.
+**
+**  3) d1+d2 is Julian Date, apportioned in any convenient way between
+**     the two arguments, for example where d1 is the Julian Day Number
+**     and d2 is the fraction of a day.  In the case of UTC, where the
+**     use of JD is problematical, special conventions apply:  see the
+**     next note.
+**
+**  4) JD cannot unambiguously represent UTC during a leap second unless
+**     special measures are taken.  The ERFA internal convention is that
+**     the quasi-JD day represents UTC days whether the length is 86399,
+**     86400 or 86401 SI seconds.  In the 1960-1972 era there were
+**     smaller jumps (in either direction) each time the linear UTC(TAI)
+**     expression was changed, and these "mini-leaps" are also included
+**     in the ERFA convention.
+**
+**  5) The warning status "dubious year" flags UTCs that predate the
+**     introduction of the time scale or that are too far in the future
+**     to be trusted.  See eraDat for further details.
+**
+**  6) For calendar conventions and limitations, see eraCal2jd.
+**
+**  Called:
+**     eraJd2cal    JD to Gregorian calendar
+**     eraD2tf      decompose days to hms
+**     eraDat       delta(AT) = TAI-UTC
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int leap;
+   char s;
+   int iy1, im1, id1, js, iy2, im2, id2, ihmsf1[4], i;
+   double a1, b1, fd, dat0, dat12, w, dat24, dleap;
+
+
+/* The two-part JD. */
+   a1 = d1;
+   b1 = d2;
+
+/* Provisional calendar date. */
+   js = eraJd2cal(a1, b1, &iy1, &im1, &id1, &fd);
+   if ( js ) return -1;
+
+/* Is this a leap second day? */
+   leap = 0;
+   if ( ! strcmp(scale,"UTC") ) {
+
+   /* TAI-UTC at 0h today. */
+      js = eraDat(iy1, im1, id1, 0.0, &dat0);
+      if ( js < 0 ) return -1;
+
+   /* TAI-UTC at 12h today (to detect drift). */
+      js = eraDat(iy1, im1, id1, 0.5, &dat12);
+      if ( js < 0 ) return -1;
+
+   /* TAI-UTC at 0h tomorrow (to detect jumps). */
+      js = eraJd2cal(a1+1.5, b1-fd, &iy2, &im2, &id2, &w);
+      if ( js ) return -1;
+      js = eraDat(iy2, im2, id2, 0.0, &dat24);
+      if ( js < 0 ) return -1;
+
+   /* Any sudden change in TAI-UTC (seconds). */
+      dleap = dat24 - (2.0*dat12 - dat0);
+
+   /* If leap second day, scale the fraction of a day into SI. */
+      leap = (dleap != 0.0);
+      if (leap) fd += fd * dleap/ERFA_DAYSEC;
+   }
+
+/* Provisional time of day. */
+   eraD2tf ( ndp, fd, &s, ihmsf1 );
+
+/* Has the (rounded) time gone past 24h? */
+   if ( ihmsf1[0] > 23 ) {
+
+   /* Yes.  We probably need tomorrow's calendar date. */
+      js = eraJd2cal(a1+1.5, b1-fd, &iy2, &im2, &id2, &w);
+      if ( js ) return -1;
+
+   /* Is today a leap second day? */
+      if ( ! leap ) {
+
+      /* No.  Use 0h tomorrow. */
+         iy1 = iy2;
+         im1 = im2;
+         id1 = id2;
+         ihmsf1[0] = 0;
+         ihmsf1[1] = 0;
+         ihmsf1[2] = 0;
+
+      } else {
+
+      /* Yes.  Are we past the leap second itself? */
+         if ( ihmsf1[2] > 0 ) {
+
+         /* Yes.  Use tomorrow but allow for the leap second. */
+            iy1 = iy2;
+            im1 = im2;
+            id1 = id2;
+            ihmsf1[0] = 0;
+            ihmsf1[1] = 0;
+            ihmsf1[2] = 0;
+
+         } else {
+
+         /* No.  Use 23 59 60... today. */
+            ihmsf1[0] = 23;
+            ihmsf1[1] = 59;
+            ihmsf1[2] = 60;
+         }
+
+      /* If rounding to 10s or coarser always go up to new day. */
+         if ( ndp < 0 && ihmsf1[2] == 60 ) {
+            iy1 = iy2;
+            im1 = im2;
+            id1 = id2;
+            ihmsf1[0] = 0;
+            ihmsf1[1] = 0;
+            ihmsf1[2] = 0;
+         }
+      }
+   }
+
+/* Results. */
+   *iy = iy1;
+   *im = im1;
+   *id = id1;
+   for ( i = 0; i < 4; i++ ) {
+      ihmsf[i] = ihmsf1[i];
+   }
+
+/* Status. */
+   return js;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/d2tf.c b/ast/erfa/d2tf.c
new file mode 100644
index 0000000..3608f7f
--- /dev/null
+++ b/ast/erfa/d2tf.c
@@ -0,0 +1,169 @@
+#include "erfa.h"
+
+void eraD2tf(int ndp, double days, char *sign, int ihmsf[4])
+/*
+**  - - - - - - - -
+**   e r a D 2 t f
+**  - - - - - - - -
+**
+**  Decompose days to hours, minutes, seconds, fraction.
+**
+**  Given:
+**     ndp     int     resolution (Note 1)
+**     days    double  interval in days
+**
+**  Returned:
+**     sign    char    '+' or '-'
+**     ihmsf   int[4]  hours, minutes, seconds, fraction
+**
+**  Notes:
+**
+**  1) The argument ndp is interpreted as follows:
+**
+**     ndp         resolution
+**      :      ...0000 00 00
+**     -7         1000 00 00
+**     -6          100 00 00
+**     -5           10 00 00
+**     -4            1 00 00
+**     -3            0 10 00
+**     -2            0 01 00
+**     -1            0 00 10
+**      0            0 00 01
+**      1            0 00 00.1
+**      2            0 00 00.01
+**      3            0 00 00.001
+**      :            0 00 00.000...
+**
+**  2) The largest positive useful value for ndp is determined by the
+**     size of days, the format of double on the target platform, and
+**     the risk of overflowing ihmsf[3].  On a typical platform, for
+**     days up to 1.0, the available floating-point precision might
+**     correspond to ndp=12.  However, the practical limit is typically
+**     ndp=9, set by the capacity of a 32-bit int, or ndp=4 if int is
+**     only 16 bits.
+**
+**  3) The absolute value of days may exceed 1.0.  In cases where it
+**     does not, it is up to the caller to test for and handle the
+**     case where days is very nearly 1.0 and rounds up to 24 hours,
+**     by testing for ihmsf[0]=24 and setting ihmsf[0-3] to zero.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int nrs, n;
+   double rs, rm, rh, a, w, ah, am, as, af;
+
+
+/* Handle sign. */
+   *sign = (char) ( ( days >= 0.0 ) ? '+' : '-' );
+
+/* Interval in seconds. */
+   a = ERFA_DAYSEC * fabs(days);
+
+/* Pre-round if resolution coarser than 1s (then pretend ndp=1). */
+   if (ndp < 0) {
+      nrs = 1;
+      for (n = 1; n <= -ndp; n++) {
+          nrs *= (n == 2 || n == 4) ? 6 : 10;
+      }
+      rs = (double) nrs;
+      w = a / rs;
+      a = rs * ERFA_DNINT(w);
+   }
+
+/* Express the unit of each field in resolution units. */
+   nrs = 1;
+   for (n = 1; n <= ndp; n++) {
+      nrs *= 10;
+   }
+   rs = (double) nrs;
+   rm = rs * 60.0;
+   rh = rm * 60.0;
+
+/* Round the interval and express in resolution units. */
+   a = ERFA_DNINT(rs * a);
+
+/* Break into fields. */
+   ah = a / rh;
+   ah = ERFA_DINT(ah);
+   a -= ah * rh;
+   am = a / rm;
+   am = ERFA_DINT(am);
+   a -= am * rm;
+   as = a / rs;
+   as = ERFA_DINT(as);
+   af = a - as * rs;
+
+/* Return results. */
+   ihmsf[0] = (int) ah;
+   ihmsf[1] = (int) am;
+   ihmsf[2] = (int) as;
+   ihmsf[3] = (int) af;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/dat.c b/ast/erfa/dat.c
new file mode 100644
index 0000000..c3b1836
--- /dev/null
+++ b/ast/erfa/dat.c
@@ -0,0 +1,306 @@
+#include "erfa.h"
+
+int eraDat(int iy, int im, int id, double fd, double *deltat )
+/*
+**  - - - - - - -
+**   e r a D a t
+**  - - - - - - -
+**
+**  For a given UTC date, calculate delta(AT) = TAI-UTC.
+**
+**     :------------------------------------------:
+**     :                                          :
+**     :                 IMPORTANT                :
+**     :                                          :
+**     :  A new version of this function must be  :
+**     :  produced whenever a new leap second is  :
+**     :  announced.  There are four items to     :
+**     :  change on each such occasion:           :
+**     :                                          :
+**     :  1) A new line must be added to the set  :
+**     :     of statements that initialize the    :
+**     :     array "changes".                     :
+**     :                                          :
+**     :  2) The constant IYV must be set to the  :
+**     :     current year.                        :
+**     :                                          :
+**     :  3) The "Latest leap second" comment     :
+**     :     below must be set to the new leap    :
+**     :     second date.                         :
+**     :                                          :
+**     :  4) The "This revision" comment, later,  :
+**     :     must be set to the current date.     :
+**     :                                          :
+**     :  Change (2) must also be carried out     :
+**     :  whenever the function is re-issued,     :
+**     :  even if no leap seconds have been       :
+**     :  added.                                  :
+**     :                                          :
+**     :  Latest leap second:  2016 December 31   :
+**     :                                          :
+**     :__________________________________________:
+**
+**  Given:
+**     iy     int      UTC:  year (Notes 1 and 2)
+**     im     int            month (Note 2)
+**     id     int            day (Notes 2 and 3)
+**     fd     double         fraction of day (Note 4)
+**
+**  Returned:
+**     deltat double   TAI minus UTC, seconds
+**
+**  Returned (function value):
+**            int      status (Note 5):
+**                       1 = dubious year (Note 1)
+**                       0 = OK
+**                      -1 = bad year
+**                      -2 = bad month
+**                      -3 = bad day (Note 3)
+**                      -4 = bad fraction (Note 4)
+**                      -5 = internal error (Note 5)
+**
+**  Notes:
+**
+**  1) UTC began at 1960 January 1.0 (JD 2436934.5) and it is improper
+**     to call the function with an earlier date.  If this is attempted,
+**     zero is returned together with a warning status.
+**
+**     Because leap seconds cannot, in principle, be predicted in
+**     advance, a reliable check for dates beyond the valid range is
+**     impossible.  To guard against gross errors, a year five or more
+**     after the release year of the present function (see the constant
+**     IYV) is considered dubious.  In this case a warning status is
+**     returned but the result is computed in the normal way.
+**
+**     For both too-early and too-late years, the warning status is +1.
+**     This is distinct from the error status -1, which signifies a year
+**     so early that JD could not be computed.
+**
+**  2) If the specified date is for a day which ends with a leap second,
+**     the UTC-TAI value returned is for the period leading up to the
+**     leap second.  If the date is for a day which begins as a leap
+**     second ends, the UTC-TAI returned is for the period following the
+**     leap second.
+**
+**  3) The day number must be in the normal calendar range, for example
+**     1 through 30 for April.  The "almanac" convention of allowing
+**     such dates as January 0 and December 32 is not supported in this
+**     function, in order to avoid confusion near leap seconds.
+**
+**  4) The fraction of day is used only for dates before the
+**     introduction of leap seconds, the first of which occurred at the
+**     end of 1971.  It is tested for validity (0 to 1 is the valid
+**     range) even if not used;  if invalid, zero is used and status -4
+**     is returned.  For many applications, setting fd to zero is
+**     acceptable;  the resulting error is always less than 3 ms (and
+**     occurs only pre-1972).
+**
+**  5) The status value returned in the case where there are multiple
+**     errors refers to the first error detected.  For example, if the
+**     month and day are 13 and 32 respectively, status -2 (bad month)
+**     will be returned.  The "internal error" status refers to a
+**     case that is impossible but causes some compilers to issue a
+**     warning.
+**
+**  6) In cases where a valid result is not available, zero is returned.
+**
+**  References:
+**
+**  1) For dates from 1961 January 1 onwards, the expressions from the
+**     file ftp://maia.usno.navy.mil/ser7/tai-utc.dat are used.
+**
+**  2) The 5ms timestep at 1961 January 1 is taken from 2.58.1 (p87) of
+**     the 1992 Explanatory Supplement.
+**
+**  Called:
+**     eraCal2jd    Gregorian calendar to JD
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Release year for this version of eraDat */
+   enum { IYV = 2016};
+
+/* Reference dates (MJD) and drift rates (s/day), pre leap seconds */
+   static const double drift[][2] = {
+      { 37300.0, 0.0012960 },
+      { 37300.0, 0.0012960 },
+      { 37300.0, 0.0012960 },
+      { 37665.0, 0.0011232 },
+      { 37665.0, 0.0011232 },
+      { 38761.0, 0.0012960 },
+      { 38761.0, 0.0012960 },
+      { 38761.0, 0.0012960 },
+      { 38761.0, 0.0012960 },
+      { 38761.0, 0.0012960 },
+      { 38761.0, 0.0012960 },
+      { 38761.0, 0.0012960 },
+      { 39126.0, 0.0025920 },
+      { 39126.0, 0.0025920 }
+   };
+
+/* Number of Delta(AT) expressions before leap seconds were introduced */
+   enum { NERA1 = (int) (sizeof drift / sizeof (double) / 2) };
+
+/* Dates and Delta(AT)s */
+   static const struct {
+      int iyear, month;
+      double delat;
+   } changes[] = {
+      { 1960,  1,  1.4178180 },
+      { 1961,  1,  1.4228180 },
+      { 1961,  8,  1.3728180 },
+      { 1962,  1,  1.8458580 },
+      { 1963, 11,  1.9458580 },
+      { 1964,  1,  3.2401300 },
+      { 1964,  4,  3.3401300 },
+      { 1964,  9,  3.4401300 },
+      { 1965,  1,  3.5401300 },
+      { 1965,  3,  3.6401300 },
+      { 1965,  7,  3.7401300 },
+      { 1965,  9,  3.8401300 },
+      { 1966,  1,  4.3131700 },
+      { 1968,  2,  4.2131700 },
+      { 1972,  1, 10.0       },
+      { 1972,  7, 11.0       },
+      { 1973,  1, 12.0       },
+      { 1974,  1, 13.0       },
+      { 1975,  1, 14.0       },
+      { 1976,  1, 15.0       },
+      { 1977,  1, 16.0       },
+      { 1978,  1, 17.0       },
+      { 1979,  1, 18.0       },
+      { 1980,  1, 19.0       },
+      { 1981,  7, 20.0       },
+      { 1982,  7, 21.0       },
+      { 1983,  7, 22.0       },
+      { 1985,  7, 23.0       },
+      { 1988,  1, 24.0       },
+      { 1990,  1, 25.0       },
+      { 1991,  1, 26.0       },
+      { 1992,  7, 27.0       },
+      { 1993,  7, 28.0       },
+      { 1994,  7, 29.0       },
+      { 1996,  1, 30.0       },
+      { 1997,  7, 31.0       },
+      { 1999,  1, 32.0       },
+      { 2006,  1, 33.0       },
+      { 2009,  1, 34.0       },
+      { 2012,  7, 35.0       },
+      { 2015,  7, 36.0       },
+      { 2017,  1, 37.0       }
+   };
+
+/* Number of Delta(AT) changes */
+   enum { NDAT = (int) (sizeof changes / sizeof changes[0]) };
+
+/* Miscellaneous local variables */
+   int j, i, m;
+   double da, djm0, djm;
+
+
+/* Initialize the result to zero. */
+   *deltat = da = 0.0;
+
+/* If invalid fraction of a day, set error status and give up. */
+   if (fd < 0.0 || fd > 1.0) return -4;
+
+/* Convert the date into an MJD. */
+   j = eraCal2jd(iy, im, id, &djm0, &djm);
+
+/* If invalid year, month, or day, give up. */
+   if (j < 0) return j;
+
+/* If pre-UTC year, set warning status and give up. */
+   if (iy < changes[0].iyear) return 1;
+
+/* If suspiciously late year, set warning status but proceed. */
+   if (iy > IYV + 5) j = 1;
+
+/* Combine year and month to form a date-ordered integer... */
+   m = 12*iy + im;
+
+/* ...and use it to find the preceding table entry. */
+   for (i = NDAT-1; i >=0; i--) {
+      if (m >= (12 * changes[i].iyear + changes[i].month)) break;
+   }
+
+/* Prevent underflow warnings. */
+   if (i < 0) return -5;
+
+/* Get the Delta(AT). */
+   da = changes[i].delat;
+
+/* If pre-1972, adjust for drift. */
+   if (i < NERA1) da += (djm + fd - drift[i][0]) * drift[i][1];
+
+/* Return the Delta(AT) value. */
+   *deltat = da;
+
+/* Return the status. */
+   return j;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/dtdb.c b/ast/erfa/dtdb.c
new file mode 100644
index 0000000..1ac4f43
--- /dev/null
+++ b/ast/erfa/dtdb.c
@@ -0,0 +1,1222 @@
+#include "erfa.h"
+
+double eraDtdb(double date1, double date2,
+               double ut, double elong, double u, double v)
+/*
+**  - - - - - - - -
+**   e r a D t d b
+**  - - - - - - - -
+**
+**  An approximation to TDB-TT, the difference between barycentric
+**  dynamical time and terrestrial time, for an observer on the Earth.
+**
+**  The different time scales - proper, coordinate and realized - are
+**  related to each other:
+**
+**            TAI             <-  physically realized
+**             :
+**          offset            <-  observed (nominally +32.184s)
+**             :
+**            TT              <-  terrestrial time
+**             :
+**    rate adjustment (L_G)   <-  definition of TT
+**             :
+**            TCG             <-  time scale for GCRS
+**             :
+**      "periodic" terms      <-  eraDtdb  is an implementation
+**             :
+**    rate adjustment (L_C)   <-  function of solar-system ephemeris
+**             :
+**            TCB             <-  time scale for BCRS
+**             :
+**    rate adjustment (-L_B)  <-  definition of TDB
+**             :
+**            TDB             <-  TCB scaled to track TT
+**             :
+**      "periodic" terms      <-  -eraDtdb is an approximation
+**             :
+**            TT              <-  terrestrial time
+**
+**  Adopted values for the various constants can be found in the IERS
+**  Conventions (McCarthy & Petit 2003).
+**
+**  Given:
+**     date1,date2   double  date, TDB (Notes 1-3)
+**     ut            double  universal time (UT1, fraction of one day)
+**     elong         double  longitude (east positive, radians)
+**     u             double  distance from Earth spin axis (km)
+**     v             double  distance north of equatorial plane (km)
+**
+**  Returned (function value):
+**                   double  TDB-TT (seconds)
+**
+**  Notes:
+**
+**  1) The date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**     Although the date is, formally, barycentric dynamical time (TDB),
+**     the terrestrial dynamical time (TT) can be used with no practical
+**     effect on the accuracy of the prediction.
+**
+**  2) TT can be regarded as a coordinate time that is realized as an
+**     offset of 32.184s from International Atomic Time, TAI.  TT is a
+**     specific linear transformation of geocentric coordinate time TCG,
+**     which is the time scale for the Geocentric Celestial Reference
+**     System, GCRS.
+**
+**  3) TDB is a coordinate time, and is a specific linear transformation
+**     of barycentric coordinate time TCB, which is the time scale for
+**     the Barycentric Celestial Reference System, BCRS.
+**
+**  4) The difference TCG-TCB depends on the masses and positions of the
+**     bodies of the solar system and the velocity of the Earth.  It is
+**     dominated by a rate difference, the residual being of a periodic
+**     character.  The latter, which is modeled by the present function,
+**     comprises a main (annual) sinusoidal term of amplitude
+**     approximately 0.00166 seconds, plus planetary terms up to about
+**     20 microseconds, and lunar and diurnal terms up to 2 microseconds.
+**     These effects come from the changing transverse Doppler effect
+**     and gravitational red-shift as the observer (on the Earth's
+**     surface) experiences variations in speed (with respect to the
+**     BCRS) and gravitational potential.
+**
+**  5) TDB can be regarded as the same as TCB but with a rate adjustment
+**     to keep it close to TT, which is convenient for many applications.
+**     The history of successive attempts to define TDB is set out in
+**     Resolution 3 adopted by the IAU General Assembly in 2006, which
+**     defines a fixed TDB(TCB) transformation that is consistent with
+**     contemporary solar-system ephemerides.  Future ephemerides will
+**     imply slightly changed transformations between TCG and TCB, which
+**     could introduce a linear drift between TDB and TT;  however, any
+**     such drift is unlikely to exceed 1 nanosecond per century.
+**
+**  6) The geocentric TDB-TT model used in the present function is that of
+**     Fairhead & Bretagnon (1990), in its full form.  It was originally
+**     supplied by Fairhead (private communications with P.T.Wallace,
+**     1990) as a Fortran subroutine.  The present C function contains an
+**     adaptation of the Fairhead code.  The numerical results are
+**     essentially unaffected by the changes, the differences with
+**     respect to the Fairhead & Bretagnon original being at the 1e-20 s
+**     level.
+**
+**     The topocentric part of the model is from Moyer (1981) and
+**     Murray (1983), with fundamental arguments adapted from
+**     Simon et al. 1994.  It is an approximation to the expression
+**     ( v / c ) . ( r / c ), where v is the barycentric velocity of
+**     the Earth, r is the geocentric position of the observer and
+**     c is the speed of light.
+**
+**     By supplying zeroes for u and v, the topocentric part of the
+**     model can be nullified, and the function will return the Fairhead
+**     & Bretagnon result alone.
+**
+**  7) During the interval 1950-2050, the absolute accuracy is better
+**     than +/- 3 nanoseconds relative to time ephemerides obtained by
+**     direct numerical integrations based on the JPL DE405 solar system
+**     ephemeris.
+**
+**  8) It must be stressed that the present function is merely a model,
+**     and that numerical integration of solar-system ephemerides is the
+**     definitive method for predicting the relationship between TCG and
+**     TCB and hence between TT and TDB.
+**
+**  References:
+**
+**     Fairhead, L., & Bretagnon, P., Astron.Astrophys., 229, 240-247
+**     (1990).
+**
+**     IAU 2006 Resolution 3.
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Moyer, T.D., Cel.Mech., 23, 33 (1981).
+**
+**     Murray, C.A., Vectorial Astrometry, Adam Hilger (1983).
+**
+**     Seidelmann, P.K. et al., Explanatory Supplement to the
+**     Astronomical Almanac, Chapter 2, University Science Books (1992).
+**
+**     Simon, J.L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G. & Laskar, J., Astron.Astrophys., 282, 663-683 (1994).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, tsol, w, elsun, emsun, d, elj, els, wt, w0, w1, w2, w3, w4,
+          wf, wj;
+   int j;
+
+/*
+** =====================
+** Fairhead et al. model
+** =====================
+**
+** 787 sets of three coefficients.
+**
+** Each set is
+**    amplitude (microseconds)
+**      frequency (radians per Julian millennium since J2000.0)
+**      phase (radians)
+**
+** Sets   1-474 are the T**0 terms
+**  "   475-679  "   "  T**1
+**  "   680-764  "   "  T**2
+**  "   765-784  "   "  T**3
+**  "   785-787  "   "  T**4
+*/
+
+   static const double fairhd[787][3] = {
+   /* 1, 10 */
+      { 1656.674564e-6,     6283.075849991,  6.240054195 },
+      {   22.417471e-6,     5753.384884897,  4.296977442 },
+      {   13.839792e-6,    12566.151699983,  6.196904410 },
+      {    4.770086e-6,      529.690965095,  0.444401603 },
+      {    4.676740e-6,     6069.776754553,  4.021195093 },
+      {    2.256707e-6,      213.299095438,  5.543113262 },
+      {    1.694205e-6,      -3.523118349,   5.025132748 },
+      {    1.554905e-6,    77713.771467920,  5.198467090 },
+      {    1.276839e-6,     7860.419392439,  5.988822341 },
+      {    1.193379e-6,     5223.693919802,  3.649823730 },
+   /* 11, 20 */
+      {    1.115322e-6,     3930.209696220,  1.422745069 },
+      {    0.794185e-6,    11506.769769794,  2.322313077 },
+      {    0.447061e-6,       26.298319800,  3.615796498 },
+      {    0.435206e-6,     -398.149003408,  4.349338347 },
+      {    0.600309e-6,     1577.343542448,  2.678271909 },
+      {    0.496817e-6,     6208.294251424,  5.696701824 },
+      {    0.486306e-6,     5884.926846583,  0.520007179 },
+      {    0.432392e-6,       74.781598567,  2.435898309 },
+      {    0.468597e-6,     6244.942814354,  5.866398759 },
+      {    0.375510e-6,     5507.553238667,  4.103476804 },
+   /* 21, 30 */
+      {    0.243085e-6,     -775.522611324,  3.651837925 },
+      {    0.173435e-6,    18849.227549974,  6.153743485 },
+      {    0.230685e-6,     5856.477659115,  4.773852582 },
+      {    0.203747e-6,    12036.460734888,  4.333987818 },
+      {    0.143935e-6,     -796.298006816,  5.957517795 },
+      {    0.159080e-6,    10977.078804699,  1.890075226 },
+      {    0.119979e-6,       38.133035638,  4.551585768 },
+      {    0.118971e-6,     5486.777843175,  1.914547226 },
+      {    0.116120e-6,     1059.381930189,  0.873504123 },
+      {    0.137927e-6,    11790.629088659,  1.135934669 },
+   /* 31, 40 */
+      {    0.098358e-6,     2544.314419883,  0.092793886 },
+      {    0.101868e-6,    -5573.142801634,  5.984503847 },
+      {    0.080164e-6,      206.185548437,  2.095377709 },
+      {    0.079645e-6,     4694.002954708,  2.949233637 },
+      {    0.062617e-6,       20.775395492,  2.654394814 },
+      {    0.075019e-6,     2942.463423292,  4.980931759 },
+      {    0.064397e-6,     5746.271337896,  1.280308748 },
+      {    0.063814e-6,     5760.498431898,  4.167901731 },
+      {    0.048042e-6,     2146.165416475,  1.495846011 },
+      {    0.048373e-6,      155.420399434,  2.251573730 },
+   /* 41, 50 */
+      {    0.058844e-6,      426.598190876,  4.839650148 },
+      {    0.046551e-6,       -0.980321068,  0.921573539 },
+      {    0.054139e-6,    17260.154654690,  3.411091093 },
+      {    0.042411e-6,     6275.962302991,  2.869567043 },
+      {    0.040184e-6,       -7.113547001,  3.565975565 },
+      {    0.036564e-6,     5088.628839767,  3.324679049 },
+      {    0.040759e-6,    12352.852604545,  3.981496998 },
+      {    0.036507e-6,      801.820931124,  6.248866009 },
+      {    0.036955e-6,     3154.687084896,  5.071801441 },
+      {    0.042732e-6,      632.783739313,  5.720622217 },
+   /* 51, 60 */
+      {    0.042560e-6,   161000.685737473,  1.270837679 },
+      {    0.040480e-6,    15720.838784878,  2.546610123 },
+      {    0.028244e-6,    -6286.598968340,  5.069663519 },
+      {    0.033477e-6,     6062.663207553,  4.144987272 },
+      {    0.034867e-6,      522.577418094,  5.210064075 },
+      {    0.032438e-6,     6076.890301554,  0.749317412 },
+      {    0.030215e-6,     7084.896781115,  3.389610345 },
+      {    0.029247e-6,   -71430.695617928,  4.183178762 },
+      {    0.033529e-6,     9437.762934887,  2.404714239 },
+      {    0.032423e-6,     8827.390269875,  5.541473556 },
+   /* 61, 70 */
+      {    0.027567e-6,     6279.552731642,  5.040846034 },
+      {    0.029862e-6,    12139.553509107,  1.770181024 },
+      {    0.022509e-6,    10447.387839604,  1.460726241 },
+      {    0.020937e-6,     8429.241266467,  0.652303414 },
+      {    0.020322e-6,      419.484643875,  3.735430632 },
+      {    0.024816e-6,    -1194.447010225,  1.087136918 },
+      {    0.025196e-6,     1748.016413067,  2.901883301 },
+      {    0.021691e-6,    14143.495242431,  5.952658009 },
+      {    0.017673e-6,     6812.766815086,  3.186129845 },
+      {    0.022567e-6,     6133.512652857,  3.307984806 },
+   /* 71, 80 */
+      {    0.016155e-6,    10213.285546211,  1.331103168 },
+      {    0.014751e-6,     1349.867409659,  4.308933301 },
+      {    0.015949e-6,     -220.412642439,  4.005298270 },
+      {    0.015974e-6,    -2352.866153772,  6.145309371 },
+      {    0.014223e-6,    17789.845619785,  2.104551349 },
+      {    0.017806e-6,       73.297125859,  3.475975097 },
+      {    0.013671e-6,     -536.804512095,  5.971672571 },
+      {    0.011942e-6,     8031.092263058,  2.053414715 },
+      {    0.014318e-6,    16730.463689596,  3.016058075 },
+      {    0.012462e-6,      103.092774219,  1.737438797 },
+   /* 81, 90 */
+      {    0.010962e-6,        3.590428652,  2.196567739 },
+      {    0.015078e-6,    19651.048481098,  3.969480770 },
+      {    0.010396e-6,      951.718406251,  5.717799605 },
+      {    0.011707e-6,    -4705.732307544,  2.654125618 },
+      {    0.010453e-6,     5863.591206116,  1.913704550 },
+      {    0.012420e-6,     4690.479836359,  4.734090399 },
+      {    0.011847e-6,     5643.178563677,  5.489005403 },
+      {    0.008610e-6,     3340.612426700,  3.661698944 },
+      {    0.011622e-6,     5120.601145584,  4.863931876 },
+      {    0.010825e-6,      553.569402842,  0.842715011 },
+   /* 91, 100 */
+      {    0.008666e-6,     -135.065080035,  3.293406547 },
+      {    0.009963e-6,      149.563197135,  4.870690598 },
+      {    0.009858e-6,     6309.374169791,  1.061816410 },
+      {    0.007959e-6,      316.391869657,  2.465042647 },
+      {    0.010099e-6,      283.859318865,  1.942176992 },
+      {    0.007147e-6,     -242.728603974,  3.661486981 },
+      {    0.007505e-6,     5230.807466803,  4.920937029 },
+      {    0.008323e-6,    11769.853693166,  1.229392026 },
+      {    0.007490e-6,    -6256.777530192,  3.658444681 },
+      {    0.009370e-6,   149854.400134205,  0.673880395 },
+   /* 101, 110 */
+      {    0.007117e-6,       38.027672636,  5.294249518 },
+      {    0.007857e-6,    12168.002696575,  0.525733528 },
+      {    0.007019e-6,     6206.809778716,  0.837688810 },
+      {    0.006056e-6,      955.599741609,  4.194535082 },
+      {    0.008107e-6,    13367.972631107,  3.793235253 },
+      {    0.006731e-6,     5650.292110678,  5.639906583 },
+      {    0.007332e-6,       36.648562930,  0.114858677 },
+      {    0.006366e-6,     4164.311989613,  2.262081818 },
+      {    0.006858e-6,     5216.580372801,  0.642063318 },
+      {    0.006919e-6,     6681.224853400,  6.018501522 },
+   /* 111, 120 */
+      {    0.006826e-6,     7632.943259650,  3.458654112 },
+      {    0.005308e-6,    -1592.596013633,  2.500382359 },
+      {    0.005096e-6,    11371.704689758,  2.547107806 },
+      {    0.004841e-6,     5333.900241022,  0.437078094 },
+      {    0.005582e-6,     5966.683980335,  2.246174308 },
+      {    0.006304e-6,    11926.254413669,  2.512929171 },
+      {    0.006603e-6,    23581.258177318,  5.393136889 },
+      {    0.005123e-6,       -1.484472708,  2.999641028 },
+      {    0.004648e-6,     1589.072895284,  1.275847090 },
+      {    0.005119e-6,     6438.496249426,  1.486539246 },
+   /* 121, 130 */
+      {    0.004521e-6,     4292.330832950,  6.140635794 },
+      {    0.005680e-6,    23013.539539587,  4.557814849 },
+      {    0.005488e-6,       -3.455808046,  0.090675389 },
+      {    0.004193e-6,     7234.794256242,  4.869091389 },
+      {    0.003742e-6,     7238.675591600,  4.691976180 },
+      {    0.004148e-6,     -110.206321219,  3.016173439 },
+      {    0.004553e-6,    11499.656222793,  5.554998314 },
+      {    0.004892e-6,     5436.993015240,  1.475415597 },
+      {    0.004044e-6,     4732.030627343,  1.398784824 },
+      {    0.004164e-6,    12491.370101415,  5.650931916 },
+   /* 131, 140 */
+      {    0.004349e-6,    11513.883316794,  2.181745369 },
+      {    0.003919e-6,    12528.018664345,  5.823319737 },
+      {    0.003129e-6,     6836.645252834,  0.003844094 },
+      {    0.004080e-6,    -7058.598461315,  3.690360123 },
+      {    0.003270e-6,       76.266071276,  1.517189902 },
+      {    0.002954e-6,     6283.143160294,  4.447203799 },
+      {    0.002872e-6,       28.449187468,  1.158692983 },
+      {    0.002881e-6,      735.876513532,  0.349250250 },
+      {    0.003279e-6,     5849.364112115,  4.893384368 },
+      {    0.003625e-6,     6209.778724132,  1.473760578 },
+   /* 141, 150 */
+      {    0.003074e-6,      949.175608970,  5.185878737 },
+      {    0.002775e-6,     9917.696874510,  1.030026325 },
+      {    0.002646e-6,    10973.555686350,  3.918259169 },
+      {    0.002575e-6,    25132.303399966,  6.109659023 },
+      {    0.003500e-6,      263.083923373,  1.892100742 },
+      {    0.002740e-6,    18319.536584880,  4.320519510 },
+      {    0.002464e-6,      202.253395174,  4.698203059 },
+      {    0.002409e-6,        2.542797281,  5.325009315 },
+      {    0.003354e-6,   -90955.551694697,  1.942656623 },
+      {    0.002296e-6,     6496.374945429,  5.061810696 },
+   /* 151, 160 */
+      {    0.003002e-6,     6172.869528772,  2.797822767 },
+      {    0.003202e-6,    27511.467873537,  0.531673101 },
+      {    0.002954e-6,    -6283.008539689,  4.533471191 },
+      {    0.002353e-6,      639.897286314,  3.734548088 },
+      {    0.002401e-6,    16200.772724501,  2.605547070 },
+      {    0.003053e-6,   233141.314403759,  3.029030662 },
+      {    0.003024e-6,    83286.914269554,  2.355556099 },
+      {    0.002863e-6,    17298.182327326,  5.240963796 },
+      {    0.002103e-6,    -7079.373856808,  5.756641637 },
+      {    0.002303e-6,    83996.847317911,  2.013686814 },
+   /* 161, 170 */
+      {    0.002303e-6,    18073.704938650,  1.089100410 },
+      {    0.002381e-6,       63.735898303,  0.759188178 },
+      {    0.002493e-6,     6386.168624210,  0.645026535 },
+      {    0.002366e-6,        3.932153263,  6.215885448 },
+      {    0.002169e-6,    11015.106477335,  4.845297676 },
+      {    0.002397e-6,     6243.458341645,  3.809290043 },
+      {    0.002183e-6,     1162.474704408,  6.179611691 },
+      {    0.002353e-6,     6246.427287062,  4.781719760 },
+      {    0.002199e-6,     -245.831646229,  5.956152284 },
+      {    0.001729e-6,     3894.181829542,  1.264976635 },
+   /* 171, 180 */
+      {    0.001896e-6,    -3128.388765096,  4.914231596 },
+      {    0.002085e-6,       35.164090221,  1.405158503 },
+      {    0.002024e-6,    14712.317116458,  2.752035928 },
+      {    0.001737e-6,     6290.189396992,  5.280820144 },
+      {    0.002229e-6,      491.557929457,  1.571007057 },
+      {    0.001602e-6,    14314.168113050,  4.203664806 },
+      {    0.002186e-6,      454.909366527,  1.402101526 },
+      {    0.001897e-6,    22483.848574493,  4.167932508 },
+      {    0.001825e-6,    -3738.761430108,  0.545828785 },
+      {    0.001894e-6,     1052.268383188,  5.817167450 },
+   /* 181, 190 */
+      {    0.001421e-6,       20.355319399,  2.419886601 },
+      {    0.001408e-6,    10984.192351700,  2.732084787 },
+      {    0.001847e-6,    10873.986030480,  2.903477885 },
+      {    0.001391e-6,    -8635.942003763,  0.593891500 },
+      {    0.001388e-6,       -7.046236698,  1.166145902 },
+      {    0.001810e-6,   -88860.057071188,  0.487355242 },
+      {    0.001288e-6,    -1990.745017041,  3.913022880 },
+      {    0.001297e-6,    23543.230504682,  3.063805171 },
+      {    0.001335e-6,     -266.607041722,  3.995764039 },
+      {    0.001376e-6,    10969.965257698,  5.152914309 },
+   /* 191, 200 */
+      {    0.001745e-6,   244287.600007027,  3.626395673 },
+      {    0.001649e-6,    31441.677569757,  1.952049260 },
+      {    0.001416e-6,     9225.539273283,  4.996408389 },
+      {    0.001238e-6,     4804.209275927,  5.503379738 },
+      {    0.001472e-6,     4590.910180489,  4.164913291 },
+      {    0.001169e-6,     6040.347246017,  5.841719038 },
+      {    0.001039e-6,     5540.085789459,  2.769753519 },
+      {    0.001004e-6,     -170.672870619,  0.755008103 },
+      {    0.001284e-6,    10575.406682942,  5.306538209 },
+      {    0.001278e-6,       71.812653151,  4.713486491 },
+   /* 201, 210 */
+      {    0.001321e-6,    18209.330263660,  2.624866359 },
+      {    0.001297e-6,    21228.392023546,  0.382603541 },
+      {    0.000954e-6,     6282.095528923,  0.882213514 },
+      {    0.001145e-6,     6058.731054289,  1.169483931 },
+      {    0.000979e-6,     5547.199336460,  5.448375984 },
+      {    0.000987e-6,    -6262.300454499,  2.656486959 },
+      {    0.001070e-6,  -154717.609887482,  1.827624012 },
+      {    0.000991e-6,     4701.116501708,  4.387001801 },
+      {    0.001155e-6,      -14.227094002,  3.042700750 },
+      {    0.001176e-6,      277.034993741,  3.335519004 },
+   /* 211, 220 */
+      {    0.000890e-6,    13916.019109642,  5.601498297 },
+      {    0.000884e-6,    -1551.045222648,  1.088831705 },
+      {    0.000876e-6,     5017.508371365,  3.969902609 },
+      {    0.000806e-6,    15110.466119866,  5.142876744 },
+      {    0.000773e-6,    -4136.910433516,  0.022067765 },
+      {    0.001077e-6,      175.166059800,  1.844913056 },
+      {    0.000954e-6,    -6284.056171060,  0.968480906 },
+      {    0.000737e-6,     5326.786694021,  4.923831588 },
+      {    0.000845e-6,     -433.711737877,  4.749245231 },
+      {    0.000819e-6,     8662.240323563,  5.991247817 },
+   /* 221, 230 */
+      {    0.000852e-6,      199.072001436,  2.189604979 },
+      {    0.000723e-6,    17256.631536341,  6.068719637 },
+      {    0.000940e-6,     6037.244203762,  6.197428148 },
+      {    0.000885e-6,    11712.955318231,  3.280414875 },
+      {    0.000706e-6,    12559.038152982,  2.824848947 },
+      {    0.000732e-6,     2379.164473572,  2.501813417 },
+      {    0.000764e-6,    -6127.655450557,  2.236346329 },
+      {    0.000908e-6,      131.541961686,  2.521257490 },
+      {    0.000907e-6,    35371.887265976,  3.370195967 },
+      {    0.000673e-6,     1066.495477190,  3.876512374 },
+   /* 231, 240 */
+      {    0.000814e-6,    17654.780539750,  4.627122566 },
+      {    0.000630e-6,       36.027866677,  0.156368499 },
+      {    0.000798e-6,      515.463871093,  5.151962502 },
+      {    0.000798e-6,      148.078724426,  5.909225055 },
+      {    0.000806e-6,      309.278322656,  6.054064447 },
+      {    0.000607e-6,      -39.617508346,  2.839021623 },
+      {    0.000601e-6,      412.371096874,  3.984225404 },
+      {    0.000646e-6,    11403.676995575,  3.852959484 },
+      {    0.000704e-6,    13521.751441591,  2.300991267 },
+      {    0.000603e-6,   -65147.619767937,  4.140083146 },
+   /* 241, 250 */
+      {    0.000609e-6,    10177.257679534,  0.437122327 },
+      {    0.000631e-6,     5767.611978898,  4.026532329 },
+      {    0.000576e-6,    11087.285125918,  4.760293101 },
+      {    0.000674e-6,    14945.316173554,  6.270510511 },
+      {    0.000726e-6,     5429.879468239,  6.039606892 },
+      {    0.000710e-6,    28766.924424484,  5.672617711 },
+      {    0.000647e-6,    11856.218651625,  3.397132627 },
+      {    0.000678e-6,    -5481.254918868,  6.249666675 },
+      {    0.000618e-6,    22003.914634870,  2.466427018 },
+      {    0.000738e-6,     6134.997125565,  2.242668890 },
+   /* 251, 260 */
+      {    0.000660e-6,      625.670192312,  5.864091907 },
+      {    0.000694e-6,     3496.032826134,  2.668309141 },
+      {    0.000531e-6,     6489.261398429,  1.681888780 },
+      {    0.000611e-6,  -143571.324284214,  2.424978312 },
+      {    0.000575e-6,    12043.574281889,  4.216492400 },
+      {    0.000553e-6,    12416.588502848,  4.772158039 },
+      {    0.000689e-6,     4686.889407707,  6.224271088 },
+      {    0.000495e-6,     7342.457780181,  3.817285811 },
+      {    0.000567e-6,     3634.621024518,  1.649264690 },
+      {    0.000515e-6,    18635.928454536,  3.945345892 },
+   /* 261, 270 */
+      {    0.000486e-6,     -323.505416657,  4.061673868 },
+      {    0.000662e-6,    25158.601719765,  1.794058369 },
+      {    0.000509e-6,      846.082834751,  3.053874588 },
+      {    0.000472e-6,   -12569.674818332,  5.112133338 },
+      {    0.000461e-6,     6179.983075773,  0.513669325 },
+      {    0.000641e-6,    83467.156352816,  3.210727723 },
+      {    0.000520e-6,    10344.295065386,  2.445597761 },
+      {    0.000493e-6,    18422.629359098,  1.676939306 },
+      {    0.000478e-6,     1265.567478626,  5.487314569 },
+      {    0.000472e-6,      -18.159247265,  1.999707589 },
+   /* 271, 280 */
+      {    0.000559e-6,    11190.377900137,  5.783236356 },
+      {    0.000494e-6,     9623.688276691,  3.022645053 },
+      {    0.000463e-6,     5739.157790895,  1.411223013 },
+      {    0.000432e-6,    16858.482532933,  1.179256434 },
+      {    0.000574e-6,    72140.628666286,  1.758191830 },
+      {    0.000484e-6,    17267.268201691,  3.290589143 },
+      {    0.000550e-6,     4907.302050146,  0.864024298 },
+      {    0.000399e-6,       14.977853527,  2.094441910 },
+      {    0.000491e-6,      224.344795702,  0.878372791 },
+      {    0.000432e-6,    20426.571092422,  6.003829241 },
+   /* 281, 290 */
+      {    0.000481e-6,     5749.452731634,  4.309591964 },
+      {    0.000480e-6,     5757.317038160,  1.142348571 },
+      {    0.000485e-6,     6702.560493867,  0.210580917 },
+      {    0.000426e-6,     6055.549660552,  4.274476529 },
+      {    0.000480e-6,     5959.570433334,  5.031351030 },
+      {    0.000466e-6,    12562.628581634,  4.959581597 },
+      {    0.000520e-6,    39302.096962196,  4.788002889 },
+      {    0.000458e-6,    12132.439962106,  1.880103788 },
+      {    0.000470e-6,    12029.347187887,  1.405611197 },
+      {    0.000416e-6,    -7477.522860216,  1.082356330 },
+   /* 291, 300 */
+      {    0.000449e-6,    11609.862544012,  4.179989585 },
+      {    0.000465e-6,    17253.041107690,  0.353496295 },
+      {    0.000362e-6,    -4535.059436924,  1.583849576 },
+      {    0.000383e-6,    21954.157609398,  3.747376371 },
+      {    0.000389e-6,       17.252277143,  1.395753179 },
+      {    0.000331e-6,    18052.929543158,  0.566790582 },
+      {    0.000430e-6,    13517.870106233,  0.685827538 },
+      {    0.000368e-6,    -5756.908003246,  0.731374317 },
+      {    0.000330e-6,    10557.594160824,  3.710043680 },
+      {    0.000332e-6,    20199.094959633,  1.652901407 },
+   /* 301, 310 */
+      {    0.000384e-6,    11933.367960670,  5.827781531 },
+      {    0.000387e-6,    10454.501386605,  2.541182564 },
+      {    0.000325e-6,    15671.081759407,  2.178850542 },
+      {    0.000318e-6,      138.517496871,  2.253253037 },
+      {    0.000305e-6,     9388.005909415,  0.578340206 },
+      {    0.000352e-6,     5749.861766548,  3.000297967 },
+      {    0.000311e-6,     6915.859589305,  1.693574249 },
+      {    0.000297e-6,    24072.921469776,  1.997249392 },
+      {    0.000363e-6,     -640.877607382,  5.071820966 },
+      {    0.000323e-6,    12592.450019783,  1.072262823 },
+   /* 311, 320 */
+      {    0.000341e-6,    12146.667056108,  4.700657997 },
+      {    0.000290e-6,     9779.108676125,  1.812320441 },
+      {    0.000342e-6,     6132.028180148,  4.322238614 },
+      {    0.000329e-6,     6268.848755990,  3.033827743 },
+      {    0.000374e-6,    17996.031168222,  3.388716544 },
+      {    0.000285e-6,     -533.214083444,  4.687313233 },
+      {    0.000338e-6,     6065.844601290,  0.877776108 },
+      {    0.000276e-6,       24.298513841,  0.770299429 },
+      {    0.000336e-6,    -2388.894020449,  5.353796034 },
+      {    0.000290e-6,     3097.883822726,  4.075291557 },
+   /* 321, 330 */
+      {    0.000318e-6,      709.933048357,  5.941207518 },
+      {    0.000271e-6,    13095.842665077,  3.208912203 },
+      {    0.000331e-6,     6073.708907816,  4.007881169 },
+      {    0.000292e-6,      742.990060533,  2.714333592 },
+      {    0.000362e-6,    29088.811415985,  3.215977013 },
+      {    0.000280e-6,    12359.966151546,  0.710872502 },
+      {    0.000267e-6,    10440.274292604,  4.730108488 },
+      {    0.000262e-6,      838.969287750,  1.327720272 },
+      {    0.000250e-6,    16496.361396202,  0.898769761 },
+      {    0.000325e-6,    20597.243963041,  0.180044365 },
+   /* 331, 340 */
+      {    0.000268e-6,     6148.010769956,  5.152666276 },
+      {    0.000284e-6,     5636.065016677,  5.655385808 },
+      {    0.000301e-6,     6080.822454817,  2.135396205 },
+      {    0.000294e-6,     -377.373607916,  3.708784168 },
+      {    0.000236e-6,     2118.763860378,  1.733578756 },
+      {    0.000234e-6,     5867.523359379,  5.575209112 },
+      {    0.000268e-6,  -226858.238553767,  0.069432392 },
+      {    0.000265e-6,   167283.761587465,  4.369302826 },
+      {    0.000280e-6,    28237.233459389,  5.304829118 },
+      {    0.000292e-6,    12345.739057544,  4.096094132 },
+   /* 341, 350 */
+      {    0.000223e-6,    19800.945956225,  3.069327406 },
+      {    0.000301e-6,    43232.306658416,  6.205311188 },
+      {    0.000264e-6,    18875.525869774,  1.417263408 },
+      {    0.000304e-6,    -1823.175188677,  3.409035232 },
+      {    0.000301e-6,      109.945688789,  0.510922054 },
+      {    0.000260e-6,      813.550283960,  2.389438934 },
+      {    0.000299e-6,   316428.228673312,  5.384595078 },
+      {    0.000211e-6,     5756.566278634,  3.789392838 },
+      {    0.000209e-6,     5750.203491159,  1.661943545 },
+      {    0.000240e-6,    12489.885628707,  5.684549045 },
+   /* 351, 360 */
+      {    0.000216e-6,     6303.851245484,  3.862942261 },
+      {    0.000203e-6,     1581.959348283,  5.549853589 },
+      {    0.000200e-6,     5642.198242609,  1.016115785 },
+      {    0.000197e-6,      -70.849445304,  4.690702525 },
+      {    0.000227e-6,     6287.008003254,  2.911891613 },
+      {    0.000197e-6,      533.623118358,  1.048982898 },
+      {    0.000205e-6,    -6279.485421340,  1.829362730 },
+      {    0.000209e-6,   -10988.808157535,  2.636140084 },
+      {    0.000208e-6,     -227.526189440,  4.127883842 },
+      {    0.000191e-6,      415.552490612,  4.401165650 },
+   /* 361, 370 */
+      {    0.000190e-6,    29296.615389579,  4.175658539 },
+      {    0.000264e-6,    66567.485864652,  4.601102551 },
+      {    0.000256e-6,    -3646.350377354,  0.506364778 },
+      {    0.000188e-6,    13119.721102825,  2.032195842 },
+      {    0.000185e-6,     -209.366942175,  4.694756586 },
+      {    0.000198e-6,    25934.124331089,  3.832703118 },
+      {    0.000195e-6,     4061.219215394,  3.308463427 },
+      {    0.000234e-6,     5113.487598583,  1.716090661 },
+      {    0.000188e-6,     1478.866574064,  5.686865780 },
+      {    0.000222e-6,    11823.161639450,  1.942386641 },
+   /* 371, 380 */
+      {    0.000181e-6,    10770.893256262,  1.999482059 },
+      {    0.000171e-6,     6546.159773364,  1.182807992 },
+      {    0.000206e-6,       70.328180442,  5.934076062 },
+      {    0.000169e-6,    20995.392966449,  2.169080622 },
+      {    0.000191e-6,    10660.686935042,  5.405515999 },
+      {    0.000228e-6,    33019.021112205,  4.656985514 },
+      {    0.000184e-6,    -4933.208440333,  3.327476868 },
+      {    0.000220e-6,     -135.625325010,  1.765430262 },
+      {    0.000166e-6,    23141.558382925,  3.454132746 },
+      {    0.000191e-6,     6144.558353121,  5.020393445 },
+   /* 381, 390 */
+      {    0.000180e-6,     6084.003848555,  0.602182191 },
+      {    0.000163e-6,    17782.732072784,  4.960593133 },
+      {    0.000225e-6,    16460.333529525,  2.596451817 },
+      {    0.000222e-6,     5905.702242076,  3.731990323 },
+      {    0.000204e-6,      227.476132789,  5.636192701 },
+      {    0.000159e-6,    16737.577236597,  3.600691544 },
+      {    0.000200e-6,     6805.653268085,  0.868220961 },
+      {    0.000187e-6,    11919.140866668,  2.629456641 },
+      {    0.000161e-6,      127.471796607,  2.862574720 },
+      {    0.000205e-6,     6286.666278643,  1.742882331 },
+   /* 391, 400 */
+      {    0.000189e-6,      153.778810485,  4.812372643 },
+      {    0.000168e-6,    16723.350142595,  0.027860588 },
+      {    0.000149e-6,    11720.068865232,  0.659721876 },
+      {    0.000189e-6,     5237.921013804,  5.245313000 },
+      {    0.000143e-6,     6709.674040867,  4.317625647 },
+      {    0.000146e-6,     4487.817406270,  4.815297007 },
+      {    0.000144e-6,     -664.756045130,  5.381366880 },
+      {    0.000175e-6,     5127.714692584,  4.728443327 },
+      {    0.000162e-6,     6254.626662524,  1.435132069 },
+      {    0.000187e-6,    47162.516354635,  1.354371923 },
+   /* 401, 410 */
+      {    0.000146e-6,    11080.171578918,  3.369695406 },
+      {    0.000180e-6,     -348.924420448,  2.490902145 },
+      {    0.000148e-6,      151.047669843,  3.799109588 },
+      {    0.000157e-6,     6197.248551160,  1.284375887 },
+      {    0.000167e-6,      146.594251718,  0.759969109 },
+      {    0.000133e-6,    -5331.357443741,  5.409701889 },
+      {    0.000154e-6,       95.979227218,  3.366890614 },
+      {    0.000148e-6,    -6418.140930027,  3.384104996 },
+      {    0.000128e-6,    -6525.804453965,  3.803419985 },
+      {    0.000130e-6,    11293.470674356,  0.939039445 },
+   /* 411, 420 */
+      {    0.000152e-6,    -5729.506447149,  0.734117523 },
+      {    0.000138e-6,      210.117701700,  2.564216078 },
+      {    0.000123e-6,     6066.595360816,  4.517099537 },
+      {    0.000140e-6,    18451.078546566,  0.642049130 },
+      {    0.000126e-6,    11300.584221356,  3.485280663 },
+      {    0.000119e-6,    10027.903195729,  3.217431161 },
+      {    0.000151e-6,     4274.518310832,  4.404359108 },
+      {    0.000117e-6,     6072.958148291,  0.366324650 },
+      {    0.000165e-6,    -7668.637425143,  4.298212528 },
+      {    0.000117e-6,    -6245.048177356,  5.379518958 },
+   /* 421, 430 */
+      {    0.000130e-6,    -5888.449964932,  4.527681115 },
+      {    0.000121e-6,     -543.918059096,  6.109429504 },
+      {    0.000162e-6,     9683.594581116,  5.720092446 },
+      {    0.000141e-6,     6219.339951688,  0.679068671 },
+      {    0.000118e-6,    22743.409379516,  4.881123092 },
+      {    0.000129e-6,     1692.165669502,  0.351407289 },
+      {    0.000126e-6,     5657.405657679,  5.146592349 },
+      {    0.000114e-6,      728.762966531,  0.520791814 },
+      {    0.000120e-6,       52.596639600,  0.948516300 },
+      {    0.000115e-6,       65.220371012,  3.504914846 },
+   /* 431, 440 */
+      {    0.000126e-6,     5881.403728234,  5.577502482 },
+      {    0.000158e-6,   163096.180360983,  2.957128968 },
+      {    0.000134e-6,    12341.806904281,  2.598576764 },
+      {    0.000151e-6,    16627.370915377,  3.985702050 },
+      {    0.000109e-6,     1368.660252845,  0.014730471 },
+      {    0.000131e-6,     6211.263196841,  0.085077024 },
+      {    0.000146e-6,     5792.741760812,  0.708426604 },
+      {    0.000146e-6,      -77.750543984,  3.121576600 },
+      {    0.000107e-6,     5341.013788022,  0.288231904 },
+      {    0.000138e-6,     6281.591377283,  2.797450317 },
+   /* 441, 450 */
+      {    0.000113e-6,    -6277.552925684,  2.788904128 },
+      {    0.000115e-6,     -525.758811831,  5.895222200 },
+      {    0.000138e-6,     6016.468808270,  6.096188999 },
+      {    0.000139e-6,    23539.707386333,  2.028195445 },
+      {    0.000146e-6,    -4176.041342449,  4.660008502 },
+      {    0.000107e-6,    16062.184526117,  4.066520001 },
+      {    0.000142e-6,    83783.548222473,  2.936315115 },
+      {    0.000128e-6,     9380.959672717,  3.223844306 },
+      {    0.000135e-6,     6205.325306007,  1.638054048 },
+      {    0.000101e-6,     2699.734819318,  5.481603249 },
+   /* 451, 460 */
+      {    0.000104e-6,     -568.821874027,  2.205734493 },
+      {    0.000103e-6,     6321.103522627,  2.440421099 },
+      {    0.000119e-6,     6321.208885629,  2.547496264 },
+      {    0.000138e-6,     1975.492545856,  2.314608466 },
+      {    0.000121e-6,      137.033024162,  4.539108237 },
+      {    0.000123e-6,    19402.796952817,  4.538074405 },
+      {    0.000119e-6,    22805.735565994,  2.869040566 },
+      {    0.000133e-6,    64471.991241142,  6.056405489 },
+      {    0.000129e-6,      -85.827298831,  2.540635083 },
+      {    0.000131e-6,    13613.804277336,  4.005732868 },
+   /* 461, 470 */
+      {    0.000104e-6,     9814.604100291,  1.959967212 },
+      {    0.000112e-6,    16097.679950283,  3.589026260 },
+      {    0.000123e-6,     2107.034507542,  1.728627253 },
+      {    0.000121e-6,    36949.230808424,  6.072332087 },
+      {    0.000108e-6,   -12539.853380183,  3.716133846 },
+      {    0.000113e-6,    -7875.671863624,  2.725771122 },
+      {    0.000109e-6,     4171.425536614,  4.033338079 },
+      {    0.000101e-6,     6247.911759770,  3.441347021 },
+      {    0.000113e-6,     7330.728427345,  0.656372122 },
+      {    0.000113e-6,    51092.726050855,  2.791483066 },
+   /* 471, 480 */
+      {    0.000106e-6,     5621.842923210,  1.815323326 },
+      {    0.000101e-6,      111.430161497,  5.711033677 },
+      {    0.000103e-6,      909.818733055,  2.812745443 },
+      {    0.000101e-6,     1790.642637886,  1.965746028 },
+
+   /* T */
+      {  102.156724e-6,     6283.075849991,  4.249032005 },
+      {    1.706807e-6,    12566.151699983,  4.205904248 },
+      {    0.269668e-6,      213.299095438,  3.400290479 },
+      {    0.265919e-6,      529.690965095,  5.836047367 },
+      {    0.210568e-6,       -3.523118349,  6.262738348 },
+      {    0.077996e-6,     5223.693919802,  4.670344204 },
+   /* 481, 490 */
+      {    0.054764e-6,     1577.343542448,  4.534800170 },
+      {    0.059146e-6,       26.298319800,  1.083044735 },
+      {    0.034420e-6,     -398.149003408,  5.980077351 },
+      {    0.032088e-6,    18849.227549974,  4.162913471 },
+      {    0.033595e-6,     5507.553238667,  5.980162321 },
+      {    0.029198e-6,     5856.477659115,  0.623811863 },
+      {    0.027764e-6,      155.420399434,  3.745318113 },
+      {    0.025190e-6,     5746.271337896,  2.980330535 },
+      {    0.022997e-6,     -796.298006816,  1.174411803 },
+      {    0.024976e-6,     5760.498431898,  2.467913690 },
+   /* 491, 500 */
+      {    0.021774e-6,      206.185548437,  3.854787540 },
+      {    0.017925e-6,     -775.522611324,  1.092065955 },
+      {    0.013794e-6,      426.598190876,  2.699831988 },
+      {    0.013276e-6,     6062.663207553,  5.845801920 },
+      {    0.011774e-6,    12036.460734888,  2.292832062 },
+      {    0.012869e-6,     6076.890301554,  5.333425680 },
+      {    0.012152e-6,     1059.381930189,  6.222874454 },
+      {    0.011081e-6,       -7.113547001,  5.154724984 },
+      {    0.010143e-6,     4694.002954708,  4.044013795 },
+      {    0.009357e-6,     5486.777843175,  3.416081409 },
+   /* 501, 510 */
+      {    0.010084e-6,      522.577418094,  0.749320262 },
+      {    0.008587e-6,    10977.078804699,  2.777152598 },
+      {    0.008628e-6,     6275.962302991,  4.562060226 },
+      {    0.008158e-6,     -220.412642439,  5.806891533 },
+      {    0.007746e-6,     2544.314419883,  1.603197066 },
+      {    0.007670e-6,     2146.165416475,  3.000200440 },
+      {    0.007098e-6,       74.781598567,  0.443725817 },
+      {    0.006180e-6,     -536.804512095,  1.302642751 },
+      {    0.005818e-6,     5088.628839767,  4.827723531 },
+      {    0.004945e-6,    -6286.598968340,  0.268305170 },
+   /* 511, 520 */
+      {    0.004774e-6,     1349.867409659,  5.808636673 },
+      {    0.004687e-6,     -242.728603974,  5.154890570 },
+      {    0.006089e-6,     1748.016413067,  4.403765209 },
+      {    0.005975e-6,    -1194.447010225,  2.583472591 },
+      {    0.004229e-6,      951.718406251,  0.931172179 },
+      {    0.005264e-6,      553.569402842,  2.336107252 },
+      {    0.003049e-6,     5643.178563677,  1.362634430 },
+      {    0.002974e-6,     6812.766815086,  1.583012668 },
+      {    0.003403e-6,    -2352.866153772,  2.552189886 },
+      {    0.003030e-6,      419.484643875,  5.286473844 },
+   /* 521, 530 */
+      {    0.003210e-6,       -7.046236698,  1.863796539 },
+      {    0.003058e-6,     9437.762934887,  4.226420633 },
+      {    0.002589e-6,    12352.852604545,  1.991935820 },
+      {    0.002927e-6,     5216.580372801,  2.319951253 },
+      {    0.002425e-6,     5230.807466803,  3.084752833 },
+      {    0.002656e-6,     3154.687084896,  2.487447866 },
+      {    0.002445e-6,    10447.387839604,  2.347139160 },
+      {    0.002990e-6,     4690.479836359,  6.235872050 },
+      {    0.002890e-6,     5863.591206116,  0.095197563 },
+      {    0.002498e-6,     6438.496249426,  2.994779800 },
+   /* 531, 540 */
+      {    0.001889e-6,     8031.092263058,  3.569003717 },
+      {    0.002567e-6,      801.820931124,  3.425611498 },
+      {    0.001803e-6,   -71430.695617928,  2.192295512 },
+      {    0.001782e-6,        3.932153263,  5.180433689 },
+      {    0.001694e-6,    -4705.732307544,  4.641779174 },
+      {    0.001704e-6,    -1592.596013633,  3.997097652 },
+      {    0.001735e-6,     5849.364112115,  0.417558428 },
+      {    0.001643e-6,     8429.241266467,  2.180619584 },
+      {    0.001680e-6,       38.133035638,  4.164529426 },
+      {    0.002045e-6,     7084.896781115,  0.526323854 },
+   /* 541, 550 */
+      {    0.001458e-6,     4292.330832950,  1.356098141 },
+      {    0.001437e-6,       20.355319399,  3.895439360 },
+      {    0.001738e-6,     6279.552731642,  0.087484036 },
+      {    0.001367e-6,    14143.495242431,  3.987576591 },
+      {    0.001344e-6,     7234.794256242,  0.090454338 },
+      {    0.001438e-6,    11499.656222793,  0.974387904 },
+      {    0.001257e-6,     6836.645252834,  1.509069366 },
+      {    0.001358e-6,    11513.883316794,  0.495572260 },
+      {    0.001628e-6,     7632.943259650,  4.968445721 },
+      {    0.001169e-6,      103.092774219,  2.838496795 },
+   /* 551, 560 */
+      {    0.001162e-6,     4164.311989613,  3.408387778 },
+      {    0.001092e-6,     6069.776754553,  3.617942651 },
+      {    0.001008e-6,    17789.845619785,  0.286350174 },
+      {    0.001008e-6,      639.897286314,  1.610762073 },
+      {    0.000918e-6,    10213.285546211,  5.532798067 },
+      {    0.001011e-6,    -6256.777530192,  0.661826484 },
+      {    0.000753e-6,    16730.463689596,  3.905030235 },
+      {    0.000737e-6,    11926.254413669,  4.641956361 },
+      {    0.000694e-6,     3340.612426700,  2.111120332 },
+      {    0.000701e-6,     3894.181829542,  2.760823491 },
+   /* 561, 570 */
+      {    0.000689e-6,     -135.065080035,  4.768800780 },
+      {    0.000700e-6,    13367.972631107,  5.760439898 },
+      {    0.000664e-6,     6040.347246017,  1.051215840 },
+      {    0.000654e-6,     5650.292110678,  4.911332503 },
+      {    0.000788e-6,     6681.224853400,  4.699648011 },
+      {    0.000628e-6,     5333.900241022,  5.024608847 },
+      {    0.000755e-6,     -110.206321219,  4.370971253 },
+      {    0.000628e-6,     6290.189396992,  3.660478857 },
+      {    0.000635e-6,    25132.303399966,  4.121051532 },
+      {    0.000534e-6,     5966.683980335,  1.173284524 },
+   /* 571, 580 */
+      {    0.000543e-6,     -433.711737877,  0.345585464 },
+      {    0.000517e-6,    -1990.745017041,  5.414571768 },
+      {    0.000504e-6,     5767.611978898,  2.328281115 },
+      {    0.000485e-6,     5753.384884897,  1.685874771 },
+      {    0.000463e-6,     7860.419392439,  5.297703006 },
+      {    0.000604e-6,      515.463871093,  0.591998446 },
+      {    0.000443e-6,    12168.002696575,  4.830881244 },
+      {    0.000570e-6,      199.072001436,  3.899190272 },
+      {    0.000465e-6,    10969.965257698,  0.476681802 },
+      {    0.000424e-6,    -7079.373856808,  1.112242763 },
+   /* 581, 590 */
+      {    0.000427e-6,      735.876513532,  1.994214480 },
+      {    0.000478e-6,    -6127.655450557,  3.778025483 },
+      {    0.000414e-6,    10973.555686350,  5.441088327 },
+      {    0.000512e-6,     1589.072895284,  0.107123853 },
+      {    0.000378e-6,    10984.192351700,  0.915087231 },
+      {    0.000402e-6,    11371.704689758,  4.107281715 },
+      {    0.000453e-6,     9917.696874510,  1.917490952 },
+      {    0.000395e-6,      149.563197135,  2.763124165 },
+      {    0.000371e-6,     5739.157790895,  3.112111866 },
+      {    0.000350e-6,    11790.629088659,  0.440639857 },
+   /* 591, 600 */
+      {    0.000356e-6,     6133.512652857,  5.444568842 },
+      {    0.000344e-6,      412.371096874,  5.676832684 },
+      {    0.000383e-6,      955.599741609,  5.559734846 },
+      {    0.000333e-6,     6496.374945429,  0.261537984 },
+      {    0.000340e-6,     6055.549660552,  5.975534987 },
+      {    0.000334e-6,     1066.495477190,  2.335063907 },
+      {    0.000399e-6,    11506.769769794,  5.321230910 },
+      {    0.000314e-6,    18319.536584880,  2.313312404 },
+      {    0.000424e-6,     1052.268383188,  1.211961766 },
+      {    0.000307e-6,       63.735898303,  3.169551388 },
+   /* 601, 610 */
+      {    0.000329e-6,       29.821438149,  6.106912080 },
+      {    0.000357e-6,     6309.374169791,  4.223760346 },
+      {    0.000312e-6,    -3738.761430108,  2.180556645 },
+      {    0.000301e-6,      309.278322656,  1.499984572 },
+      {    0.000268e-6,    12043.574281889,  2.447520648 },
+      {    0.000257e-6,    12491.370101415,  3.662331761 },
+      {    0.000290e-6,      625.670192312,  1.272834584 },
+      {    0.000256e-6,     5429.879468239,  1.913426912 },
+      {    0.000339e-6,     3496.032826134,  4.165930011 },
+      {    0.000283e-6,     3930.209696220,  4.325565754 },
+   /* 611, 620 */
+      {    0.000241e-6,    12528.018664345,  3.832324536 },
+      {    0.000304e-6,     4686.889407707,  1.612348468 },
+      {    0.000259e-6,    16200.772724501,  3.470173146 },
+      {    0.000238e-6,    12139.553509107,  1.147977842 },
+      {    0.000236e-6,     6172.869528772,  3.776271728 },
+      {    0.000296e-6,    -7058.598461315,  0.460368852 },
+      {    0.000306e-6,    10575.406682942,  0.554749016 },
+      {    0.000251e-6,    17298.182327326,  0.834332510 },
+      {    0.000290e-6,     4732.030627343,  4.759564091 },
+      {    0.000261e-6,     5884.926846583,  0.298259862 },
+   /* 621, 630 */
+      {    0.000249e-6,     5547.199336460,  3.749366406 },
+      {    0.000213e-6,    11712.955318231,  5.415666119 },
+      {    0.000223e-6,     4701.116501708,  2.703203558 },
+      {    0.000268e-6,     -640.877607382,  0.283670793 },
+      {    0.000209e-6,     5636.065016677,  1.238477199 },
+      {    0.000193e-6,    10177.257679534,  1.943251340 },
+      {    0.000182e-6,     6283.143160294,  2.456157599 },
+      {    0.000184e-6,     -227.526189440,  5.888038582 },
+      {    0.000182e-6,    -6283.008539689,  0.241332086 },
+      {    0.000228e-6,    -6284.056171060,  2.657323816 },
+   /* 631, 640 */
+      {    0.000166e-6,     7238.675591600,  5.930629110 },
+      {    0.000167e-6,     3097.883822726,  5.570955333 },
+      {    0.000159e-6,     -323.505416657,  5.786670700 },
+      {    0.000154e-6,    -4136.910433516,  1.517805532 },
+      {    0.000176e-6,    12029.347187887,  3.139266834 },
+      {    0.000167e-6,    12132.439962106,  3.556352289 },
+      {    0.000153e-6,      202.253395174,  1.463313961 },
+      {    0.000157e-6,    17267.268201691,  1.586837396 },
+      {    0.000142e-6,    83996.847317911,  0.022670115 },
+      {    0.000152e-6,    17260.154654690,  0.708528947 },
+   /* 641, 650 */
+      {    0.000144e-6,     6084.003848555,  5.187075177 },
+      {    0.000135e-6,     5756.566278634,  1.993229262 },
+      {    0.000134e-6,     5750.203491159,  3.457197134 },
+      {    0.000144e-6,     5326.786694021,  6.066193291 },
+      {    0.000160e-6,    11015.106477335,  1.710431974 },
+      {    0.000133e-6,     3634.621024518,  2.836451652 },
+      {    0.000134e-6,    18073.704938650,  5.453106665 },
+      {    0.000134e-6,     1162.474704408,  5.326898811 },
+      {    0.000128e-6,     5642.198242609,  2.511652591 },
+      {    0.000160e-6,      632.783739313,  5.628785365 },
+   /* 651, 660 */
+      {    0.000132e-6,    13916.019109642,  0.819294053 },
+      {    0.000122e-6,    14314.168113050,  5.677408071 },
+      {    0.000125e-6,    12359.966151546,  5.251984735 },
+      {    0.000121e-6,     5749.452731634,  2.210924603 },
+      {    0.000136e-6,     -245.831646229,  1.646502367 },
+      {    0.000120e-6,     5757.317038160,  3.240883049 },
+      {    0.000134e-6,    12146.667056108,  3.059480037 },
+      {    0.000137e-6,     6206.809778716,  1.867105418 },
+      {    0.000141e-6,    17253.041107690,  2.069217456 },
+      {    0.000129e-6,    -7477.522860216,  2.781469314 },
+   /* 661, 670 */
+      {    0.000116e-6,     5540.085789459,  4.281176991 },
+      {    0.000116e-6,     9779.108676125,  3.320925381 },
+      {    0.000129e-6,     5237.921013804,  3.497704076 },
+      {    0.000113e-6,     5959.570433334,  0.983210840 },
+      {    0.000122e-6,     6282.095528923,  2.674938860 },
+      {    0.000140e-6,      -11.045700264,  4.957936982 },
+      {    0.000108e-6,    23543.230504682,  1.390113589 },
+      {    0.000106e-6,   -12569.674818332,  0.429631317 },
+      {    0.000110e-6,     -266.607041722,  5.501340197 },
+      {    0.000115e-6,    12559.038152982,  4.691456618 },
+   /* 671, 680 */
+      {    0.000134e-6,    -2388.894020449,  0.577313584 },
+      {    0.000109e-6,    10440.274292604,  6.218148717 },
+      {    0.000102e-6,     -543.918059096,  1.477842615 },
+      {    0.000108e-6,    21228.392023546,  2.237753948 },
+      {    0.000101e-6,    -4535.059436924,  3.100492232 },
+      {    0.000103e-6,       76.266071276,  5.594294322 },
+      {    0.000104e-6,      949.175608970,  5.674287810 },
+      {    0.000101e-6,    13517.870106233,  2.196632348 },
+      {    0.000100e-6,    11933.367960670,  4.056084160 },
+
+   /* T^2 */
+      {    4.322990e-6,     6283.075849991,  2.642893748 },
+   /* 681, 690 */
+      {    0.406495e-6,        0.000000000,  4.712388980 },
+      {    0.122605e-6,    12566.151699983,  2.438140634 },
+      {    0.019476e-6,      213.299095438,  1.642186981 },
+      {    0.016916e-6,      529.690965095,  4.510959344 },
+      {    0.013374e-6,       -3.523118349,  1.502210314 },
+      {    0.008042e-6,       26.298319800,  0.478549024 },
+      {    0.007824e-6,      155.420399434,  5.254710405 },
+      {    0.004894e-6,     5746.271337896,  4.683210850 },
+      {    0.004875e-6,     5760.498431898,  0.759507698 },
+      {    0.004416e-6,     5223.693919802,  6.028853166 },
+   /* 691, 700 */
+      {    0.004088e-6,       -7.113547001,  0.060926389 },
+      {    0.004433e-6,    77713.771467920,  3.627734103 },
+      {    0.003277e-6,    18849.227549974,  2.327912542 },
+      {    0.002703e-6,     6062.663207553,  1.271941729 },
+      {    0.003435e-6,     -775.522611324,  0.747446224 },
+      {    0.002618e-6,     6076.890301554,  3.633715689 },
+      {    0.003146e-6,      206.185548437,  5.647874613 },
+      {    0.002544e-6,     1577.343542448,  6.232904270 },
+      {    0.002218e-6,     -220.412642439,  1.309509946 },
+      {    0.002197e-6,     5856.477659115,  2.407212349 },
+   /* 701, 710 */
+      {    0.002897e-6,     5753.384884897,  5.863842246 },
+      {    0.001766e-6,      426.598190876,  0.754113147 },
+      {    0.001738e-6,     -796.298006816,  2.714942671 },
+      {    0.001695e-6,      522.577418094,  2.629369842 },
+      {    0.001584e-6,     5507.553238667,  1.341138229 },
+      {    0.001503e-6,     -242.728603974,  0.377699736 },
+      {    0.001552e-6,     -536.804512095,  2.904684667 },
+      {    0.001370e-6,     -398.149003408,  1.265599125 },
+      {    0.001889e-6,    -5573.142801634,  4.413514859 },
+      {    0.001722e-6,     6069.776754553,  2.445966339 },
+   /* 711, 720 */
+      {    0.001124e-6,     1059.381930189,  5.041799657 },
+      {    0.001258e-6,      553.569402842,  3.849557278 },
+      {    0.000831e-6,      951.718406251,  2.471094709 },
+      {    0.000767e-6,     4694.002954708,  5.363125422 },
+      {    0.000756e-6,     1349.867409659,  1.046195744 },
+      {    0.000775e-6,      -11.045700264,  0.245548001 },
+      {    0.000597e-6,     2146.165416475,  4.543268798 },
+      {    0.000568e-6,     5216.580372801,  4.178853144 },
+      {    0.000711e-6,     1748.016413067,  5.934271972 },
+      {    0.000499e-6,    12036.460734888,  0.624434410 },
+   /* 721, 730 */
+      {    0.000671e-6,    -1194.447010225,  4.136047594 },
+      {    0.000488e-6,     5849.364112115,  2.209679987 },
+      {    0.000621e-6,     6438.496249426,  4.518860804 },
+      {    0.000495e-6,    -6286.598968340,  1.868201275 },
+      {    0.000456e-6,     5230.807466803,  1.271231591 },
+      {    0.000451e-6,     5088.628839767,  0.084060889 },
+      {    0.000435e-6,     5643.178563677,  3.324456609 },
+      {    0.000387e-6,    10977.078804699,  4.052488477 },
+      {    0.000547e-6,   161000.685737473,  2.841633844 },
+      {    0.000522e-6,     3154.687084896,  2.171979966 },
+   /* 731, 740 */
+      {    0.000375e-6,     5486.777843175,  4.983027306 },
+      {    0.000421e-6,     5863.591206116,  4.546432249 },
+      {    0.000439e-6,     7084.896781115,  0.522967921 },
+      {    0.000309e-6,     2544.314419883,  3.172606705 },
+      {    0.000347e-6,     4690.479836359,  1.479586566 },
+      {    0.000317e-6,      801.820931124,  3.553088096 },
+      {    0.000262e-6,      419.484643875,  0.606635550 },
+      {    0.000248e-6,     6836.645252834,  3.014082064 },
+      {    0.000245e-6,    -1592.596013633,  5.519526220 },
+      {    0.000225e-6,     4292.330832950,  2.877956536 },
+   /* 741, 750 */
+      {    0.000214e-6,     7234.794256242,  1.605227587 },
+      {    0.000205e-6,     5767.611978898,  0.625804796 },
+      {    0.000180e-6,    10447.387839604,  3.499954526 },
+      {    0.000229e-6,      199.072001436,  5.632304604 },
+      {    0.000214e-6,      639.897286314,  5.960227667 },
+      {    0.000175e-6,     -433.711737877,  2.162417992 },
+      {    0.000209e-6,      515.463871093,  2.322150893 },
+      {    0.000173e-6,     6040.347246017,  2.556183691 },
+      {    0.000184e-6,     6309.374169791,  4.732296790 },
+      {    0.000227e-6,   149854.400134205,  5.385812217 },
+   /* 751, 760 */
+      {    0.000154e-6,     8031.092263058,  5.120720920 },
+      {    0.000151e-6,     5739.157790895,  4.815000443 },
+      {    0.000197e-6,     7632.943259650,  0.222827271 },
+      {    0.000197e-6,       74.781598567,  3.910456770 },
+      {    0.000138e-6,     6055.549660552,  1.397484253 },
+      {    0.000149e-6,    -6127.655450557,  5.333727496 },
+      {    0.000137e-6,     3894.181829542,  4.281749907 },
+      {    0.000135e-6,     9437.762934887,  5.979971885 },
+      {    0.000139e-6,    -2352.866153772,  4.715630782 },
+      {    0.000142e-6,     6812.766815086,  0.513330157 },
+   /* 761, 770 */
+      {    0.000120e-6,    -4705.732307544,  0.194160689 },
+      {    0.000131e-6,   -71430.695617928,  0.000379226 },
+      {    0.000124e-6,     6279.552731642,  2.122264908 },
+      {    0.000108e-6,    -6256.777530192,  0.883445696 },
+
+   /* T^3 */
+      {    0.143388e-6,     6283.075849991,  1.131453581 },
+      {    0.006671e-6,    12566.151699983,  0.775148887 },
+      {    0.001480e-6,      155.420399434,  0.480016880 },
+      {    0.000934e-6,      213.299095438,  6.144453084 },
+      {    0.000795e-6,      529.690965095,  2.941595619 },
+      {    0.000673e-6,     5746.271337896,  0.120415406 },
+   /* 771, 780 */
+      {    0.000672e-6,     5760.498431898,  5.317009738 },
+      {    0.000389e-6,     -220.412642439,  3.090323467 },
+      {    0.000373e-6,     6062.663207553,  3.003551964 },
+      {    0.000360e-6,     6076.890301554,  1.918913041 },
+      {    0.000316e-6,      -21.340641002,  5.545798121 },
+      {    0.000315e-6,     -242.728603974,  1.884932563 },
+      {    0.000278e-6,      206.185548437,  1.266254859 },
+      {    0.000238e-6,     -536.804512095,  4.532664830 },
+      {    0.000185e-6,      522.577418094,  4.578313856 },
+      {    0.000245e-6,    18849.227549974,  0.587467082 },
+   /* 781, 787 */
+      {    0.000180e-6,      426.598190876,  5.151178553 },
+      {    0.000200e-6,      553.569402842,  5.355983739 },
+      {    0.000141e-6,     5223.693919802,  1.336556009 },
+      {    0.000104e-6,     5856.477659115,  4.239842759 },
+
+   /* T^4 */
+      {    0.003826e-6,     6283.075849991,  5.705257275 },
+      {    0.000303e-6,    12566.151699983,  5.407132842 },
+      {    0.000209e-6,      155.420399434,  1.989815753 }
+   };
+
+
+/* Time since J2000.0 in Julian millennia. */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJM;
+
+/* ================= */
+/* Topocentric terms */
+/* ================= */
+
+/* Convert UT to local solar time in radians. */
+   tsol = fmod(ut, 1.0) * ERFA_D2PI + elong;
+
+/* FUNDAMENTAL ARGUMENTS:  Simon et al. 1994. */
+
+/* Combine time argument (millennia) with deg/arcsec factor. */
+   w = t / 3600.0;
+
+/* Sun Mean Longitude. */
+   elsun = fmod(280.46645683 + 1296027711.03429 * w, 360.0) * ERFA_DD2R;
+
+/* Sun Mean Anomaly. */
+   emsun = fmod(357.52910918 + 1295965810.481 * w, 360.0) * ERFA_DD2R;
+
+/* Mean Elongation of Moon from Sun. */
+   d = fmod(297.85019547 + 16029616012.090 * w, 360.0) * ERFA_DD2R;
+
+/* Mean Longitude of Jupiter. */
+   elj = fmod(34.35151874 + 109306899.89453 * w, 360.0) * ERFA_DD2R;
+
+/* Mean Longitude of Saturn. */
+   els = fmod(50.07744430 + 44046398.47038 * w, 360.0) * ERFA_DD2R;
+
+/* TOPOCENTRIC TERMS:  Moyer 1981 and Murray 1983. */
+   wt =   +  0.00029e-10 * u * sin(tsol + elsun - els)
+          +  0.00100e-10 * u * sin(tsol - 2.0 * emsun)
+          +  0.00133e-10 * u * sin(tsol - d)
+          +  0.00133e-10 * u * sin(tsol + elsun - elj)
+          -  0.00229e-10 * u * sin(tsol + 2.0 * elsun + emsun)
+          -  0.02200e-10 * v * cos(elsun + emsun)
+          +  0.05312e-10 * u * sin(tsol - emsun)
+          -  0.13677e-10 * u * sin(tsol + 2.0 * elsun)
+          -  1.31840e-10 * v * cos(elsun)
+          +  3.17679e-10 * u * sin(tsol);
+
+/* ===================== */
+/* Fairhead et al. model */
+/* ===================== */
+
+/* T**0 */
+   w0 = 0;
+   for (j = 473; j >= 0; j--) {
+      w0 += fairhd[j][0] * sin(fairhd[j][1] * t + fairhd[j][2]);
+   }
+
+/* T**1 */
+   w1 = 0;
+   for (j = 678; j >= 474; j--) {
+      w1 += fairhd[j][0] * sin(fairhd[j][1] * t + fairhd[j][2]);
+   }
+
+/* T**2 */
+   w2 = 0;
+   for (j = 763; j >= 679; j--) {
+      w2 += fairhd[j][0] * sin(fairhd[j][1] * t + fairhd[j][2]);
+   }
+
+/* T**3 */
+   w3 = 0;
+   for (j = 783; j >= 764; j--) {
+      w3 += fairhd[j][0] * sin(fairhd[j][1] * t + fairhd[j][2]);
+   }
+
+/* T**4 */
+   w4 = 0;
+   for (j = 786; j >= 784; j--) {
+      w4 += fairhd[j][0] * sin(fairhd[j][1] * t + fairhd[j][2]);
+   }
+
+/* Multiply by powers of T and combine. */
+   wf = t * (t * (t * (t * w4 + w3) + w2) + w1) + w0;
+
+/* Adjustments to use JPL planetary masses instead of IAU. */
+   wj =   0.00065e-6 * sin(6069.776754 * t + 4.021194) +
+          0.00033e-6 * sin( 213.299095 * t + 5.543132) +
+        (-0.00196e-6 * sin(6208.294251 * t + 5.696701)) +
+        (-0.00173e-6 * sin(  74.781599 * t + 2.435900)) +
+          0.03638e-6 * t * t;
+
+/* ============ */
+/* Final result */
+/* ============ */
+
+/* TDB-TT in seconds. */
+   w = wt + wf + wj;
+
+   return w;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/dtf2d.c b/ast/erfa/dtf2d.c
new file mode 100644
index 0000000..70d6b25
--- /dev/null
+++ b/ast/erfa/dtf2d.c
@@ -0,0 +1,212 @@
+#include "erfa.h"
+#include <string.h>
+
+int eraDtf2d(const char *scale, int iy, int im, int id,
+             int ihr, int imn, double sec, double *d1, double *d2)
+/*
+**  - - - - - - - - -
+**   e r a D t f 2 d
+**  - - - - - - - - -
+**
+**  Encode date and time fields into 2-part Julian Date (or in the case
+**  of UTC a quasi-JD form that includes special provision for leap
+**  seconds).
+**
+**  Given:
+**     scale     char[]  time scale ID (Note 1)
+**     iy,im,id  int     year, month, day in Gregorian calendar (Note 2)
+**     ihr,imn   int     hour, minute
+**     sec       double  seconds
+**
+**  Returned:
+**     d1,d2     double  2-part Julian Date (Notes 3,4)
+**
+**  Returned (function value):
+**               int     status: +3 = both of next two
+**                               +2 = time is after end of day (Note 5)
+**                               +1 = dubious year (Note 6)
+**                                0 = OK
+**                               -1 = bad year
+**                               -2 = bad month
+**                               -3 = bad day
+**                               -4 = bad hour
+**                               -5 = bad minute
+**                               -6 = bad second (<0)
+**
+**  Notes:
+**
+**  1) scale identifies the time scale.  Only the value "UTC" (in upper
+**     case) is significant, and enables handling of leap seconds (see
+**     Note 4).
+**
+**  2) For calendar conventions and limitations, see eraCal2jd.
+**
+**  3) The sum of the results, d1+d2, is Julian Date, where normally d1
+**     is the Julian Day Number and d2 is the fraction of a day.  In the
+**     case of UTC, where the use of JD is problematical, special
+**     conventions apply:  see the next note.
+**
+**  4) JD cannot unambiguously represent UTC during a leap second unless
+**     special measures are taken.  The ERFA internal convention is that
+**     the quasi-JD day represents UTC days whether the length is 86399,
+**     86400 or 86401 SI seconds.  In the 1960-1972 era there were
+**     smaller jumps (in either direction) each time the linear UTC(TAI)
+**     expression was changed, and these "mini-leaps" are also included
+**     in the ERFA convention.
+**
+**  5) The warning status "time is after end of day" usually means that
+**     the sec argument is greater than 60.0.  However, in a day ending
+**     in a leap second the limit changes to 61.0 (or 59.0 in the case
+**     of a negative leap second).
+**
+**  6) The warning status "dubious year" flags UTCs that predate the
+**     introduction of the time scale or that are too far in the future
+**     to be trusted.  See eraDat for further details.
+**
+**  7) Only in the case of continuous and regular time scales (TAI, TT,
+**     TCG, TCB and TDB) is the result d1+d2 a Julian Date, strictly
+**     speaking.  In the other cases (UT1 and UTC) the result must be
+**     used with circumspection;  in particular the difference between
+**     two such results cannot be interpreted as a precise time
+**     interval.
+**
+**  Called:
+**     eraCal2jd    Gregorian calendar to JD
+**     eraDat       delta(AT) = TAI-UTC
+**     eraJd2cal    JD to Gregorian calendar
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int js, iy2, im2, id2;
+   double dj, w, day, seclim, dat0, dat12, dat24, dleap, time;
+
+
+/* Today's Julian Day Number. */
+   js = eraCal2jd(iy, im, id, &dj, &w);
+   if ( js ) return js;
+   dj += w;
+
+/* Day length and final minute length in seconds (provisional). */
+   day = ERFA_DAYSEC;
+   seclim = 60.0;
+
+/* Deal with the UTC leap second case. */
+   if ( ! strcmp(scale,"UTC") ) {
+
+   /* TAI-UTC at 0h today. */
+      js = eraDat(iy, im, id, 0.0, &dat0);
+      if ( js < 0 ) return js;
+
+   /* TAI-UTC at 12h today (to detect drift). */
+      js = eraDat(iy, im, id, 0.5, &dat12);
+      if ( js < 0 ) return js;
+
+   /* TAI-UTC at 0h tomorrow (to detect jumps). */
+      js = eraJd2cal ( dj, 1.5, &iy2, &im2, &id2, &w);
+      if ( js ) return js;
+      js = eraDat(iy2, im2, id2, 0.0, &dat24);
+      if ( js < 0 ) return js;
+
+   /* Any sudden change in TAI-UTC between today and tomorrow. */
+      dleap = dat24 - (2.0*dat12 - dat0);
+
+   /* If leap second day, correct the day and final minute lengths. */
+      day += dleap;
+      if ( ihr == 23 && imn == 59 ) seclim += dleap;
+
+   /* End of UTC-specific actions. */
+   }
+
+/* Validate the time. */
+   if ( ihr >= 0 && ihr <= 23 ) {
+      if ( imn >= 0 && imn <= 59 ) {
+         if ( sec >= 0 ) {
+            if ( sec >= seclim ) {
+               js += 2;
+            }
+         } else {
+            js = -6;
+         }
+      } else {
+         js = -5;
+      }
+   } else {
+      js = -4;
+   }
+   if ( js < 0 ) return js;
+
+/* The time in days. */
+   time  = ( 60.0 * ( (double) ( 60 * ihr + imn ) ) + sec ) / day;
+
+/* Return the date and time. */
+   *d1 = dj;
+   *d2 = time;
+
+/* Status. */
+   return js;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/eceq06.c b/ast/erfa/eceq06.c
new file mode 100644
index 0000000..e1ba72d
--- /dev/null
+++ b/ast/erfa/eceq06.c
@@ -0,0 +1,141 @@
+#include "erfa.h"
+
+void eraEceq06(double date1, double date2, double dl, double db,
+               double *dr, double *dd)
+/*
+**  - - - - - - - - - -
+**   e r a E c e q 0 6
+**  - - - - - - - - - -
+**
+**  Transformation from ecliptic coordinates (mean equinox and ecliptic
+**  of date) to ICRS RA,Dec, using the IAU 2006 precession model.
+**
+**  Given:
+**     date1,date2 double TT as a 2-part Julian date (Note 1)
+**     dl,db       double ecliptic longitude and latitude (radians)
+**
+**  Returned:
+**     dr,dd       double ICRS right ascension and declination (radians)
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) No assumptions are made about whether the coordinates represent
+**     starlight and embody astrometric effects such as parallax or
+**     aberration.
+**
+**  3) The transformation is approximately that from ecliptic longitude
+**     and latitude (mean equinox and ecliptic of date) to mean J2000.0
+**     right ascension and declination, with only frame bias (always
+**     less than 25 mas) to disturb this classical picture.
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraEcm06     J2000.0 to ecliptic rotation matrix, IAU 2006
+**     eraTrxp      product of transpose of r-matrix and p-vector
+**     eraC2s       unit vector to spherical coordinates
+**     eraAnp       normalize angle into range 0 to 2pi
+**     eraAnpm      normalize angle into range +/- pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rm[3][3], v1[3], v2[3], a, b;
+
+
+/* Spherical to Cartesian. */
+   eraS2c(dl, db, v1);
+
+/* Rotation matrix, ICRS equatorial to ecliptic. */
+   eraEcm06(date1, date2, rm);
+
+/* The transformation from ecliptic to ICRS. */
+   eraTrxp(rm, v1, v2);
+
+/* Cartesian to spherical. */
+   eraC2s(v2, &a, &b);
+
+/* Express in conventional ranges. */
+   *dr = eraAnp(a);
+   *dd = eraAnpm(b);
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ecm06.c b/ast/erfa/ecm06.c
new file mode 100644
index 0000000..ce19ba2
--- /dev/null
+++ b/ast/erfa/ecm06.c
@@ -0,0 +1,144 @@
+#include "erfa.h"
+
+void eraEcm06(double date1, double date2, double rm[3][3])
+/*
+**  - - - - - - - - -
+**   e r a E c m 0 6
+**  - - - - - - - - -
+**
+**  ICRS equatorial to ecliptic rotation matrix, IAU 2006.
+**
+**  Given:
+**     date1,date2  double         TT as a 2-part Julian date (Note 1)
+**
+**  Returned:
+**     rm           double[3][3]   ICRS to ecliptic rotation matrix
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  1) The matrix is in the sense
+**
+**        E_ep = rm x P_ICRS,
+**
+**     where P_ICRS is a vector with respect to ICRS right ascension
+**     and declination axes and E_ep is the same vector with respect to
+**     the (inertial) ecliptic and equinox of date.
+**
+**  2) P_ICRS is a free vector, merely a direction, typically of unit
+**     magnitude, and not bound to any particular spatial origin, such
+**     as the Earth, Sun or SSB.  No assumptions are made about whether
+**     it represents starlight and embodies astrometric effects such as
+**     parallax or aberration.  The transformation is approximately that
+**     between mean J2000.0 right ascension and declination and ecliptic
+**     longitude and latitude, with only frame bias (always less than
+**     25 mas) to disturb this classical picture.
+**
+**  Called:
+**     eraObl06     mean obliquity, IAU 2006
+**     eraPmat06    PB matrix, IAU 2006
+**     eraIr        initialize r-matrix to identity
+**     eraRx        rotate around X-axis
+**     eraRxr       product of two r-matrices
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double ob, bp[3][3], e[3][3];
+
+
+/* Obliquity, IAU 2006. */
+   ob = eraObl06(date1, date2);
+
+/* Precession-bias matrix, IAU 2006. */
+   eraPmat06(date1, date2, bp);
+
+/* Equatorial of date to ecliptic matrix. */
+   eraIr(e);
+   eraRx(ob, e);
+
+/* ICRS to ecliptic coordinates rotation matrix, IAU 2006. */
+   eraRxr(e, bp, rm);
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ee00.c b/ast/erfa/ee00.c
new file mode 100644
index 0000000..de1b6b9
--- /dev/null
+++ b/ast/erfa/ee00.c
@@ -0,0 +1,137 @@
+#include "erfa.h"
+
+double eraEe00(double date1, double date2, double epsa, double dpsi)
+/*
+**  - - - - - - - -
+**   e r a E e 0 0
+**  - - - - - - - -
+**
+**  The equation of the equinoxes, compatible with IAU 2000 resolutions,
+**  given the nutation in longitude and the mean obliquity.
+**
+**  Given:
+**     date1,date2  double    TT as a 2-part Julian Date (Note 1)
+**     epsa         double    mean obliquity (Note 2)
+**     dpsi         double    nutation in longitude (Note 3)
+**
+**  Returned (function value):
+**                  double    equation of the equinoxes (Note 4)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The obliquity, in radians, is mean of date.
+**
+**  3) The result, which is in radians, operates in the following sense:
+**
+**        Greenwich apparent ST = GMST + equation of the equinoxes
+**
+**  4) The result is compatible with the IAU 2000 resolutions.  For
+**     further details, see IERS Conventions 2003 and Capitaine et al.
+**     (2002).
+**
+**  Called:
+**     eraEect00    equation of the equinoxes complementary terms
+**
+**  References:
+**
+**     Capitaine, N., Wallace, P.T. and McCarthy, D.D., "Expressions to
+**     implement the IAU 2000 definition of UT1", Astronomy &
+**     Astrophysics, 406, 1135-1149 (2003)
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double ee;
+
+
+/* Equation of the equinoxes. */
+   ee = dpsi * cos(epsa) + eraEect00(date1, date2);
+
+   return ee;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ee00a.c b/ast/erfa/ee00a.c
new file mode 100644
index 0000000..90eade7
--- /dev/null
+++ b/ast/erfa/ee00a.c
@@ -0,0 +1,144 @@
+#include "erfa.h"
+
+double eraEe00a(double date1, double date2)
+/*
+**  - - - - - - - - -
+**   e r a E e 0 0 a
+**  - - - - - - - - -
+**
+**  Equation of the equinoxes, compatible with IAU 2000 resolutions.
+**
+**  Given:
+**     date1,date2  double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                  double    equation of the equinoxes (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The result, which is in radians, operates in the following sense:
+**
+**        Greenwich apparent ST = GMST + equation of the equinoxes
+**
+**  3) The result is compatible with the IAU 2000 resolutions.  For
+**     further details, see IERS Conventions 2003 and Capitaine et al.
+**     (2002).
+**
+**  Called:
+**     eraPr00      IAU 2000 precession adjustments
+**     eraObl80     mean obliquity, IAU 1980
+**     eraNut00a    nutation, IAU 2000A
+**     eraEe00      equation of the equinoxes, IAU 2000
+**
+**  References:
+**
+**     Capitaine, N., Wallace, P.T. and McCarthy, D.D., "Expressions to
+**     implement the IAU 2000 definition of UT1", Astronomy &
+**     Astrophysics, 406, 1135-1149 (2003).
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dpsipr, depspr, epsa, dpsi, deps, ee;
+
+
+/* IAU 2000 precession-rate adjustments. */
+   eraPr00(date1, date2, &dpsipr, &depspr);
+
+/* Mean obliquity, consistent with IAU 2000 precession-nutation. */
+   epsa = eraObl80(date1, date2) + depspr;
+
+/* Nutation in longitude. */
+   eraNut00a(date1, date2, &dpsi, &deps);
+
+/* Equation of the equinoxes. */
+   ee = eraEe00(date1, date2, epsa, dpsi);
+
+   return ee;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ee00b.c b/ast/erfa/ee00b.c
new file mode 100644
index 0000000..2bed962
--- /dev/null
+++ b/ast/erfa/ee00b.c
@@ -0,0 +1,150 @@
+#include "erfa.h"
+
+double eraEe00b(double date1, double date2)
+/*
+**  - - - - - - - - -
+**   e r a E e 0 0 b
+**  - - - - - - - - -
+**
+**  Equation of the equinoxes, compatible with IAU 2000 resolutions but
+**  using the truncated nutation model IAU 2000B.
+**
+**  Given:
+**     date1,date2  double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                  double    equation of the equinoxes (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The result, which is in radians, operates in the following sense:
+**
+**        Greenwich apparent ST = GMST + equation of the equinoxes
+**
+**  3) The result is compatible with the IAU 2000 resolutions except
+**     that accuracy has been compromised for the sake of speed.  For
+**     further details, see McCarthy & Luzum (2001), IERS Conventions
+**     2003 and Capitaine et al. (2003).
+**
+**  Called:
+**     eraPr00      IAU 2000 precession adjustments
+**     eraObl80     mean obliquity, IAU 1980
+**     eraNut00b    nutation, IAU 2000B
+**     eraEe00      equation of the equinoxes, IAU 2000
+**
+**  References:
+**
+**     Capitaine, N., Wallace, P.T. and McCarthy, D.D., "Expressions to
+**     implement the IAU 2000 definition of UT1", Astronomy &
+**     Astrophysics, 406, 1135-1149 (2003)
+**
+**     McCarthy, D.D. & Luzum, B.J., "An abridged model of the
+**     precession-nutation of the celestial pole", Celestial Mechanics &
+**     Dynamical Astronomy, 85, 37-49 (2003)
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dpsipr, depspr, epsa, dpsi, deps, ee;
+
+
+/* IAU 2000 precession-rate adjustments. */
+   eraPr00(date1, date2, &dpsipr, &depspr);
+
+/* Mean obliquity, consistent with IAU 2000 precession-nutation. */
+   epsa = eraObl80(date1, date2) + depspr;
+
+/* Nutation in longitude. */
+   eraNut00b(date1, date2, &dpsi, &deps);
+
+/* Equation of the equinoxes. */
+   ee = eraEe00(date1, date2, epsa, dpsi);
+
+   return ee;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ee06a.c b/ast/erfa/ee06a.c
new file mode 100644
index 0000000..c64a65f
--- /dev/null
+++ b/ast/erfa/ee06a.c
@@ -0,0 +1,131 @@
+#include "erfa.h"
+
+double eraEe06a(double date1, double date2)
+/*
+**  - - - - - - - - -
+**   e r a E e 0 6 a
+**  - - - - - - - - -
+**
+**  Equation of the equinoxes, compatible with IAU 2000 resolutions and
+**  IAU 2006/2000A precession-nutation.
+**
+**  Given:
+**     date1,date2  double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                  double    equation of the equinoxes (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The result, which is in radians, operates in the following sense:
+**
+**        Greenwich apparent ST = GMST + equation of the equinoxes
+**
+**  Called:
+**     eraAnpm      normalize angle into range +/- pi
+**     eraGst06a    Greenwich apparent sidereal time, IAU 2006/2000A
+**     eraGmst06    Greenwich mean sidereal time, IAU 2006
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), 2004, IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double gst06a, gmst06, ee;
+
+
+/* Apparent and mean sidereal times. */
+   gst06a = eraGst06a(0.0, 0.0, date1, date2);
+   gmst06 = eraGmst06(0.0, 0.0, date1, date2);
+
+/* Equation of the equinoxes. */
+   ee  = eraAnpm(gst06a - gmst06);
+
+   return ee;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/eect00.c b/ast/erfa/eect00.c
new file mode 100644
index 0000000..b8e4bf3
--- /dev/null
+++ b/ast/erfa/eect00.c
@@ -0,0 +1,291 @@
+#include "erfa.h"
+
+double eraEect00(double date1, double date2)
+/*
+**  - - - - - - - - - -
+**   e r a E e c t 0 0
+**  - - - - - - - - - -
+**
+**  Equation of the equinoxes complementary terms, consistent with
+**  IAU 2000 resolutions.
+**
+**  Given:
+**     date1,date2  double   TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                  double   complementary terms (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The "complementary terms" are part of the equation of the
+**     equinoxes (EE), classically the difference between apparent and
+**     mean Sidereal Time:
+**
+**        GAST = GMST + EE
+**
+**     with:
+**
+**        EE = dpsi * cos(eps)
+**
+**     where dpsi is the nutation in longitude and eps is the obliquity
+**     of date.  However, if the rotation of the Earth were constant in
+**     an inertial frame the classical formulation would lead to
+**     apparent irregularities in the UT1 timescale traceable to side-
+**     effects of precession-nutation.  In order to eliminate these
+**     effects from UT1, "complementary terms" were introduced in 1994
+**     (IAU, 1994) and took effect from 1997 (Capitaine and Gontier,
+**     1993):
+**
+**        GAST = GMST + CT + EE
+**
+**     By convention, the complementary terms are included as part of
+**     the equation of the equinoxes rather than as part of the mean
+**     Sidereal Time.  This slightly compromises the "geometrical"
+**     interpretation of mean sidereal time but is otherwise
+**     inconsequential.
+**
+**     The present function computes CT in the above expression,
+**     compatible with IAU 2000 resolutions (Capitaine et al., 2002, and
+**     IERS Conventions 2003).
+**
+**  Called:
+**     eraFal03     mean anomaly of the Moon
+**     eraFalp03    mean anomaly of the Sun
+**     eraFaf03     mean argument of the latitude of the Moon
+**     eraFad03     mean elongation of the Moon from the Sun
+**     eraFaom03    mean longitude of the Moon's ascending node
+**     eraFave03    mean longitude of Venus
+**     eraFae03     mean longitude of Earth
+**     eraFapa03    general accumulated precession in longitude
+**
+**  References:
+**
+**     Capitaine, N. & Gontier, A.-M., Astron. Astrophys., 275,
+**     645-650 (1993)
+**
+**     Capitaine, N., Wallace, P.T. and McCarthy, D.D., "Expressions to
+**     implement the IAU 2000 definition of UT1", Astronomy &
+**     Astrophysics, 406, 1135-1149 (2003)
+**
+**     IAU Resolution C7, Recommendation 3 (1994)
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Time since J2000.0, in Julian centuries */
+   double t;
+
+/* Miscellaneous */
+   int i, j;
+   double a, s0, s1;
+
+/* Fundamental arguments */
+   double fa[14];
+
+/* Returned value. */
+   double eect;
+
+/* ----------------------------------------- */
+/* The series for the EE complementary terms */
+/* ----------------------------------------- */
+
+   typedef struct {
+      int nfa[8];      /* coefficients of l,l',F,D,Om,LVe,LE,pA */
+      double s, c;     /* sine and cosine coefficients */
+   } TERM;
+
+/* Terms of order t^0 */
+   static const TERM e0[] = {
+
+   /* 1-10 */
+      {{ 0,  0,  0,  0,  1,  0,  0,  0}, 2640.96e-6, -0.39e-6 },
+      {{ 0,  0,  0,  0,  2,  0,  0,  0},   63.52e-6, -0.02e-6 },
+      {{ 0,  0,  2, -2,  3,  0,  0,  0},   11.75e-6,  0.01e-6 },
+      {{ 0,  0,  2, -2,  1,  0,  0,  0},   11.21e-6,  0.01e-6 },
+      {{ 0,  0,  2, -2,  2,  0,  0,  0},   -4.55e-6,  0.00e-6 },
+      {{ 0,  0,  2,  0,  3,  0,  0,  0},    2.02e-6,  0.00e-6 },
+      {{ 0,  0,  2,  0,  1,  0,  0,  0},    1.98e-6,  0.00e-6 },
+      {{ 0,  0,  0,  0,  3,  0,  0,  0},   -1.72e-6,  0.00e-6 },
+      {{ 0,  1,  0,  0,  1,  0,  0,  0},   -1.41e-6, -0.01e-6 },
+      {{ 0,  1,  0,  0, -1,  0,  0,  0},   -1.26e-6, -0.01e-6 },
+
+   /* 11-20 */
+      {{ 1,  0,  0,  0, -1,  0,  0,  0},   -0.63e-6,  0.00e-6 },
+      {{ 1,  0,  0,  0,  1,  0,  0,  0},   -0.63e-6,  0.00e-6 },
+      {{ 0,  1,  2, -2,  3,  0,  0,  0},    0.46e-6,  0.00e-6 },
+      {{ 0,  1,  2, -2,  1,  0,  0,  0},    0.45e-6,  0.00e-6 },
+      {{ 0,  0,  4, -4,  4,  0,  0,  0},    0.36e-6,  0.00e-6 },
+      {{ 0,  0,  1, -1,  1, -8, 12,  0},   -0.24e-6, -0.12e-6 },
+      {{ 0,  0,  2,  0,  0,  0,  0,  0},    0.32e-6,  0.00e-6 },
+      {{ 0,  0,  2,  0,  2,  0,  0,  0},    0.28e-6,  0.00e-6 },
+      {{ 1,  0,  2,  0,  3,  0,  0,  0},    0.27e-6,  0.00e-6 },
+      {{ 1,  0,  2,  0,  1,  0,  0,  0},    0.26e-6,  0.00e-6 },
+
+   /* 21-30 */
+      {{ 0,  0,  2, -2,  0,  0,  0,  0},   -0.21e-6,  0.00e-6 },
+      {{ 0,  1, -2,  2, -3,  0,  0,  0},    0.19e-6,  0.00e-6 },
+      {{ 0,  1, -2,  2, -1,  0,  0,  0},    0.18e-6,  0.00e-6 },
+      {{ 0,  0,  0,  0,  0,  8,-13, -1},   -0.10e-6,  0.05e-6 },
+      {{ 0,  0,  0,  2,  0,  0,  0,  0},    0.15e-6,  0.00e-6 },
+      {{ 2,  0, -2,  0, -1,  0,  0,  0},   -0.14e-6,  0.00e-6 },
+      {{ 1,  0,  0, -2,  1,  0,  0,  0},    0.14e-6,  0.00e-6 },
+      {{ 0,  1,  2, -2,  2,  0,  0,  0},   -0.14e-6,  0.00e-6 },
+      {{ 1,  0,  0, -2, -1,  0,  0,  0},    0.14e-6,  0.00e-6 },
+      {{ 0,  0,  4, -2,  4,  0,  0,  0},    0.13e-6,  0.00e-6 },
+
+   /* 31-33 */
+      {{ 0,  0,  2, -2,  4,  0,  0,  0},   -0.11e-6,  0.00e-6 },
+      {{ 1,  0, -2,  0, -3,  0,  0,  0},    0.11e-6,  0.00e-6 },
+      {{ 1,  0, -2,  0, -1,  0,  0,  0},    0.11e-6,  0.00e-6 }
+   };
+
+/* Terms of order t^1 */
+   static const TERM e1[] = {
+      {{ 0,  0,  0,  0,  1,  0,  0,  0},    -0.87e-6,  0.00e-6 }
+   };
+
+/* Number of terms in the series */
+   const int NE0 = (int) (sizeof e0 / sizeof (TERM));
+   const int NE1 = (int) (sizeof e1 / sizeof (TERM));
+
+/*--------------------------------------------------------------------*/
+
+/* Interval between fundamental epoch J2000.0 and current date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Fundamental Arguments (from IERS Conventions 2003) */
+
+/* Mean anomaly of the Moon. */
+   fa[0] = eraFal03(t);
+
+/* Mean anomaly of the Sun. */
+   fa[1] = eraFalp03(t);
+
+/* Mean longitude of the Moon minus that of the ascending node. */
+   fa[2] = eraFaf03(t);
+
+/* Mean elongation of the Moon from the Sun. */
+   fa[3] = eraFad03(t);
+
+/* Mean longitude of the ascending node of the Moon. */
+   fa[4] = eraFaom03(t);
+
+/* Mean longitude of Venus. */
+   fa[5] = eraFave03(t);
+
+/* Mean longitude of Earth. */
+   fa[6] = eraFae03(t);
+
+/* General precession in longitude. */
+   fa[7] = eraFapa03(t);
+
+/* Evaluate the EE complementary terms. */
+   s0 = 0.0;
+   s1 = 0.0;
+
+   for (i = NE0-1; i >= 0; i--) {
+      a = 0.0;
+      for (j = 0; j < 8; j++) {
+         a += (double)(e0[i].nfa[j]) * fa[j];
+      }
+      s0 += e0[i].s * sin(a) + e0[i].c * cos(a);
+   }
+
+   for (i = NE1-1; i >= 0; i--) {
+      a = 0.0;
+      for (j = 0; j < 8; j++) {
+         a += (double)(e1[i].nfa[j]) * fa[j];
+      }
+      s1 += e1[i].s * sin(a) + e1[i].c * cos(a);
+   }
+
+   eect = (s0 + s1 * t ) * ERFA_DAS2R;
+
+   return eect;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/eform.c b/ast/erfa/eform.c
new file mode 100644
index 0000000..0982ab4
--- /dev/null
+++ b/ast/erfa/eform.c
@@ -0,0 +1,155 @@
+#include "erfa.h"
+
+int eraEform ( int n, double *a, double *f )
+/*
+**  - - - - - - - - -
+**   e r a E f o r m
+**  - - - - - - - - -
+**
+**  Earth reference ellipsoids.
+**
+**  Given:
+**     n    int         ellipsoid identifier (Note 1)
+**
+**  Returned:
+**     a    double      equatorial radius (meters, Note 2)
+**     f    double      flattening (Note 2)
+**
+**  Returned (function value):
+**          int         status:  0 = OK
+**                              -1 = illegal identifier (Note 3)
+**
+**  Notes:
+**
+**  1) The identifier n is a number that specifies the choice of
+**     reference ellipsoid.  The following are supported:
+**
+**        n    ellipsoid
+**
+**        1     ERFA_WGS84
+**        2     ERFA_GRS80
+**        3     ERFA_WGS72
+**
+**     The n value has no significance outside the ERFA software.  For
+**     convenience, symbols ERFA_WGS84 etc. are defined in erfam.h.
+**
+**  2) The ellipsoid parameters are returned in the form of equatorial
+**     radius in meters (a) and flattening (f).  The latter is a number
+**     around 0.00335, i.e. around 1/298.
+**
+**  3) For the case where an unsupported n value is supplied, zero a and
+**     f are returned, as well as error status.
+**
+**  References:
+**
+**     Department of Defense World Geodetic System 1984, National
+**     Imagery and Mapping Agency Technical Report 8350.2, Third
+**     Edition, p3-2.
+**
+**     Moritz, H., Bull. Geodesique 66-2, 187 (1992).
+**
+**     The Department of Defense World Geodetic System 1972, World
+**     Geodetic System Committee, May 1974.
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     p220.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* Look up a and f for the specified reference ellipsoid. */
+   switch ( n ) {
+
+   case ERFA_WGS84:
+      *a = 6378137.0;
+      *f = 1.0 / 298.257223563;
+      break;
+
+   case ERFA_GRS80:
+      *a = 6378137.0;
+      *f = 1.0 / 298.257222101;
+      break;
+
+   case ERFA_WGS72:
+      *a = 6378135.0;
+      *f = 1.0 / 298.26;
+      break;
+
+   default:
+
+   /* Invalid identifier. */
+      *a = 0.0;
+      *f = 0.0;
+      return -1;
+
+   }
+
+/* OK status. */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/eo06a.c b/ast/erfa/eo06a.c
new file mode 100644
index 0000000..6fd27c9
--- /dev/null
+++ b/ast/erfa/eo06a.c
@@ -0,0 +1,140 @@
+#include "erfa.h"
+
+double eraEo06a(double date1, double date2)
+/*
+**  - - - - - - - - -
+**   e r a E o 0 6 a
+**  - - - - - - - - -
+**
+**  Equation of the origins, IAU 2006 precession and IAU 2000A nutation.
+**
+**  Given:
+**     date1,date2  double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                  double    equation of the origins in radians
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The equation of the origins is the distance between the true
+**     equinox and the celestial intermediate origin and, equivalently,
+**     the difference between Earth rotation angle and Greenwich
+**     apparent sidereal time (ERA-GST).  It comprises the precession
+**     (since J2000.0) in right ascension plus the equation of the
+**     equinoxes (including the small correction terms).
+**
+**  Called:
+**     eraPnm06a    classical NPB matrix, IAU 2006/2000A
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**     eraS06       the CIO locator s, given X,Y, IAU 2006
+**     eraEors      equation of the origins, given NPB matrix and s
+**
+**  References:
+**
+**     Capitaine, N. & Wallace, P.T., 2006, Astron.Astrophys. 450, 855
+**
+**     Wallace, P.T. & Capitaine, N., 2006, Astron.Astrophys. 459, 981
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double r[3][3], x, y, s, eo;
+
+
+/* Classical nutation x precession x bias matrix. */
+   eraPnm06a(date1, date2, r);
+
+/* Extract CIP coordinates. */
+   eraBpn2xy(r, &x, &y);
+
+/* The CIO locator, s. */
+   s = eraS06(date1, date2, x, y);
+
+/* Solve for the EO. */
+   eo = eraEors(r, s);
+
+   return eo;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/eors.c b/ast/erfa/eors.c
new file mode 100644
index 0000000..c1b5557
--- /dev/null
+++ b/ast/erfa/eors.c
@@ -0,0 +1,117 @@
+#include "erfa.h"
+
+double eraEors(double rnpb[3][3], double s)
+/*
+**  - - - - - - - -
+**   e r a E o r s
+**  - - - - - - - -
+**
+**  Equation of the origins, given the classical NPB matrix and the
+**  quantity s.
+**
+**  Given:
+**     rnpb  double[3][3]  classical nutation x precession x bias matrix
+**     s     double        the quantity s (the CIO locator)
+**
+**  Returned (function value):
+**           double        the equation of the origins in radians.
+**
+**  Notes:
+**
+**  1)  The equation of the origins is the distance between the true
+**      equinox and the celestial intermediate origin and, equivalently,
+**      the difference between Earth rotation angle and Greenwich
+**      apparent sidereal time (ERA-GST).  It comprises the precession
+**      (since J2000.0) in right ascension plus the equation of the
+**      equinoxes (including the small correction terms).
+**
+**  2)  The algorithm is from Wallace & Capitaine (2006).
+**
+** References:
+**
+**     Capitaine, N. & Wallace, P.T., 2006, Astron.Astrophys. 450, 855
+**
+**     Wallace, P. & Capitaine, N., 2006, Astron.Astrophys. 459, 981
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double x, ax, xs, ys, zs, p, q, eo;
+
+
+/* Evaluate Wallace & Capitaine (2006) expression (16). */
+   x = rnpb[2][0];
+   ax = x / (1.0 + rnpb[2][2]);
+   xs = 1.0 - ax * x;
+   ys = -ax * rnpb[2][1];
+   zs = -x;
+   p = rnpb[0][0] * xs + rnpb[0][1] * ys + rnpb[0][2] * zs;
+   q = rnpb[1][0] * xs + rnpb[1][1] * ys + rnpb[1][2] * zs;
+   eo = ((p != 0) || (q != 0)) ? s - atan2(q, p) : s;
+
+   return eo;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/epb.c b/ast/erfa/epb.c
new file mode 100644
index 0000000..3d06508
--- /dev/null
+++ b/ast/erfa/epb.c
@@ -0,0 +1,100 @@
+#include "erfa.h"
+
+double eraEpb(double dj1, double dj2)
+/*
+**  - - - - - - -
+**   e r a E p b
+**  - - - - - - -
+**
+**  Julian Date to Besselian Epoch.
+**
+**  Given:
+**     dj1,dj2    double     Julian Date (see note)
+**
+**  Returned (function value):
+**                double     Besselian Epoch.
+**
+**  Note:
+**
+**     The Julian Date is supplied in two pieces, in the usual ERFA
+**     manner, which is designed to preserve time resolution.  The
+**     Julian Date is available as a single number by adding dj1 and
+**     dj2.  The maximum resolution is achieved if dj1 is 2451545.0
+**     (J2000.0).
+**
+**  Reference:
+**
+**     Lieske, J.H., 1979. Astron.Astrophys., 73, 282.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* J2000.0-B1900.0 (2415019.81352) in days */
+   const double D1900 = 36524.68648;
+
+   return 1900.0 + ((dj1 - ERFA_DJ00) + (dj2 + D1900)) / ERFA_DTY;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/epb2jd.c b/ast/erfa/epb2jd.c
new file mode 100644
index 0000000..4dca8c9
--- /dev/null
+++ b/ast/erfa/epb2jd.c
@@ -0,0 +1,100 @@
+#include "erfa.h"
+
+void eraEpb2jd(double epb, double *djm0, double *djm)
+/*
+**  - - - - - - - - - -
+**   e r a E p b 2 j d
+**  - - - - - - - - - -
+**
+**  Besselian Epoch to Julian Date.
+**
+**  Given:
+**     epb      double    Besselian Epoch (e.g. 1957.3)
+**
+**  Returned:
+**     djm0     double    MJD zero-point: always 2400000.5
+**     djm      double    Modified Julian Date
+**
+**  Note:
+**
+**     The Julian Date is returned in two pieces, in the usual ERFA
+**     manner, which is designed to preserve time resolution.  The
+**     Julian Date is available as a single number by adding djm0 and
+**     djm.
+**
+**  Reference:
+**
+**     Lieske, J.H., 1979, Astron.Astrophys. 73, 282.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   *djm0 = ERFA_DJM0;
+   *djm  =   15019.81352 + (epb - 1900.0) * ERFA_DTY;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/epj.c b/ast/erfa/epj.c
new file mode 100644
index 0000000..059853d
--- /dev/null
+++ b/ast/erfa/epj.c
@@ -0,0 +1,102 @@
+#include "erfa.h"
+
+double eraEpj(double dj1, double dj2)
+/*
+**  - - - - - - -
+**   e r a E p j
+**  - - - - - - -
+**
+**  Julian Date to Julian Epoch.
+**
+**  Given:
+**     dj1,dj2    double     Julian Date (see note)
+**
+**  Returned (function value):
+**                double     Julian Epoch
+**
+**  Note:
+**
+**     The Julian Date is supplied in two pieces, in the usual ERFA
+**     manner, which is designed to preserve time resolution.  The
+**     Julian Date is available as a single number by adding dj1 and
+**     dj2.  The maximum resolution is achieved if dj1 is 2451545.0
+**     (J2000.0).
+**
+**  Reference:
+**
+**     Lieske, J.H., 1979, Astron.Astrophys. 73, 282.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double epj;
+
+
+   epj = 2000.0 + ((dj1 - ERFA_DJ00) + dj2) / ERFA_DJY;
+
+   return epj;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/epj2jd.c b/ast/erfa/epj2jd.c
new file mode 100644
index 0000000..751eb15
--- /dev/null
+++ b/ast/erfa/epj2jd.c
@@ -0,0 +1,100 @@
+#include "erfa.h"
+
+void eraEpj2jd(double epj, double *djm0, double *djm)
+/*
+**  - - - - - - - - - -
+**   e r a E p j 2 j d
+**  - - - - - - - - - -
+**
+**  Julian Epoch to Julian Date.
+**
+**  Given:
+**     epj      double    Julian Epoch (e.g. 1996.8)
+**
+**  Returned:
+**     djm0     double    MJD zero-point: always 2400000.5
+**     djm      double    Modified Julian Date
+**
+**  Note:
+**
+**     The Julian Date is returned in two pieces, in the usual ERFA
+**     manner, which is designed to preserve time resolution.  The
+**     Julian Date is available as a single number by adding djm0 and
+**     djm.
+**
+**  Reference:
+**
+**     Lieske, J.H., 1979, Astron.Astrophys. 73, 282.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   *djm0 = ERFA_DJM0;
+   *djm  = ERFA_DJM00 + (epj - 2000.0) * 365.25;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/epv00.c b/ast/erfa/epv00.c
new file mode 100644
index 0000000..4f37b9c
--- /dev/null
+++ b/ast/erfa/epv00.c
@@ -0,0 +1,2598 @@
+#include "erfa.h"
+
+int eraEpv00(double date1, double date2,
+             double pvh[2][3], double pvb[2][3])
+/*
+**  - - - - - - - - -
+**   e r a E p v 0 0
+**  - - - - - - - - -
+**
+**  Earth position and velocity, heliocentric and barycentric, with
+**  respect to the Barycentric Celestial Reference System.
+**
+**  Given:
+**     date1,date2  double        TDB date (Note 1)
+**
+**  Returned:
+**     pvh          double[2][3]  heliocentric Earth position/velocity
+**     pvb          double[2][3]  barycentric Earth position/velocity
+**
+**  Returned (function value):
+**                  int           status: 0 = OK
+**                                       +1 = warning: date outside
+**                                            the range 1900-2100 AD
+**
+**  Notes:
+**
+**  1) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  However,
+**     the accuracy of the result is more likely to be limited by the
+**     algorithm itself than the way the date has been expressed.
+**
+**     n.b. TT can be used instead of TDB in most applications.
+**
+**  2) On return, the arrays pvh and pvb contain the following:
+**
+**        pvh[0][0]  x       }
+**        pvh[0][1]  y       } heliocentric position, AU
+**        pvh[0][2]  z       }
+**
+**        pvh[1][0]  xdot    }
+**        pvh[1][1]  ydot    } heliocentric velocity, AU/d
+**        pvh[1][2]  zdot    }
+**
+**        pvb[0][0]  x       }
+**        pvb[0][1]  y       } barycentric position, AU
+**        pvb[0][2]  z       }
+**
+**        pvb[1][0]  xdot    }
+**        pvb[1][1]  ydot    } barycentric velocity, AU/d
+**        pvb[1][2]  zdot    }
+**
+**     The vectors are with respect to the Barycentric Celestial
+**     Reference System.  The time unit is one day in TDB.
+**
+**  3) The function is a SIMPLIFIED SOLUTION from the planetary theory
+**     VSOP2000 (X. Moisson, P. Bretagnon, 2001, Celes. Mechanics &
+**     Dyn. Astron., 80, 3/4, 205-213) and is an adaptation of original
+**     Fortran code supplied by P. Bretagnon (private comm., 2000).
+**
+**  4) Comparisons over the time span 1900-2100 with this simplified
+**     solution and the JPL DE405 ephemeris give the following results:
+**
+**                                RMS    max
+**           Heliocentric:
+**              position error    3.7   11.2   km
+**              velocity error    1.4    5.0   mm/s
+**
+**           Barycentric:
+**              position error    4.6   13.4   km
+**              velocity error    1.4    4.9   mm/s
+**
+**     Comparisons with the JPL DE406 ephemeris show that by 1800 and
+**     2200 the position errors are approximately double their 1900-2100
+**     size.  By 1500 and 2500 the deterioration is a factor of 10 and
+**     by 1000 and 3000 a factor of 60.  The velocity accuracy falls off
+**     at about half that rate.
+**
+**  5) It is permissible to use the same array for pvh and pvb, which
+**     will receive the barycentric values.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/*
+** Matrix elements for orienting the analytical model to DE405.
+**
+** The corresponding Euler angles are:
+**
+**                       d  '  "
+**   1st rotation    -  23 26 21.4091 about the x-axis  (obliquity)
+**   2nd rotation    +         0.0475 about the z-axis  (RA offset)
+**
+** These were obtained empirically, by comparisons with DE405 over
+** 1900-2100.
+*/
+   static const double am12 =  0.000000211284,
+                       am13 = -0.000000091603,
+                       am21 = -0.000000230286,
+                       am22 =  0.917482137087,
+                       am23 = -0.397776982902,
+                       am32 =  0.397776982902,
+                       am33 =  0.917482137087;
+
+/*
+** ----------------------
+** Ephemeris Coefficients
+** ----------------------
+**
+** The ephemeris consists of harmonic terms for predicting (i) the Sun
+** to Earth vector and (ii) the Solar-System-barycenter to Sun vector
+** respectively.  The coefficients are stored in arrays which, although
+** 1-demensional, contain groups of three.  Each triplet of
+** coefficients is the amplitude, phase and frequency for one term in
+** the model, and each array contains the number of terms called for by
+** the model.
+**
+** There are eighteen such arrays, named as follows:
+**
+**     array         model      power of T      component
+**
+**      e0x      Sun-to-Earth        0              x
+**      e0y      Sun-to-Earth        0              y
+**      e0z      Sun-to-Earth        0              z
+**
+**      e1x      Sun-to-Earth        1              x
+**      e1y      Sun-to-Earth        1              y
+**      e1z      Sun-to-Earth        1              z
+**
+**      e2x      Sun-to-Earth        2              x
+**      e2y      Sun-to-Earth        2              y
+**      e2z      Sun-to-Earth        2              z
+**
+**      s0x      SSB-to-Sun          0              x
+**      s0y      SSB-to-Sun          0              y
+**      s0z      SSB-to-Sun          0              z
+**
+**      s1x      SSB-to-Sun          1              x
+**      s1y      SSB-to-Sun          1              y
+**      s1z      SSB-to-Sun          1              z
+**
+**      s2x      SSB-to-Sun          2              x
+**      s2y      SSB-to-Sun          2              y
+**      s2z      SSB-to-Sun          2              z
+*/
+
+/* Sun-to-Earth, T^0, X */
+   static const double e0x[] = {
+      0.9998292878132e+00, 0.1753485171504e+01, 0.6283075850446e+01,
+      0.8352579567414e-02, 0.1710344404582e+01, 0.1256615170089e+02,
+      0.5611445335148e-02, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.1046664295572e-03, 0.1667225416770e+01, 0.1884922755134e+02,
+      0.3110842534677e-04, 0.6687513390251e+00, 0.8399684731857e+02,
+      0.2552413503550e-04, 0.5830637358413e+00, 0.5296909721118e+00,
+      0.2137207845781e-04, 0.1092330954011e+01, 0.1577343543434e+01,
+      0.1680240182951e-04, 0.4955366134987e+00, 0.6279552690824e+01,
+      0.1679012370795e-04, 0.6153014091901e+01, 0.6286599010068e+01,
+      0.1445526946777e-04, 0.3472744100492e+01, 0.2352866153506e+01,
+
+      0.1091038246184e-04, 0.3689845786119e+01, 0.5223693906222e+01,
+      0.9344399733932e-05, 0.6073934645672e+01, 0.1203646072878e+02,
+      0.8993182910652e-05, 0.3175705249069e+01, 0.1021328554739e+02,
+      0.5665546034116e-05, 0.2152484672246e+01, 0.1059381944224e+01,
+      0.6844146703035e-05, 0.1306964099750e+01, 0.5753384878334e+01,
+      0.7346610905565e-05, 0.4354980070466e+01, 0.3981490189893e+00,
+      0.6815396474414e-05, 0.2218229211267e+01, 0.4705732307012e+01,
+      0.6112787253053e-05, 0.5384788425458e+01, 0.6812766822558e+01,
+      0.4518120711239e-05, 0.6087604012291e+01, 0.5884926831456e+01,
+      0.4521963430706e-05, 0.1279424524906e+01, 0.6256777527156e+01,
+
+      0.4497426764085e-05, 0.5369129144266e+01, 0.6309374173736e+01,
+      0.4062190566959e-05, 0.5436473303367e+00, 0.6681224869435e+01,
+      0.5412193480192e-05, 0.7867838528395e+00, 0.7755226100720e+00,
+      0.5469839049386e-05, 0.1461440311134e+01, 0.1414349524433e+02,
+      0.5205264083477e-05, 0.4432944696116e+01, 0.7860419393880e+01,
+      0.2149759935455e-05, 0.4502237496846e+01, 0.1150676975667e+02,
+      0.2279109618501e-05, 0.1239441308815e+01, 0.7058598460518e+01,
+      0.2259282939683e-05, 0.3272430985331e+01, 0.4694002934110e+01,
+      0.2558950271319e-05, 0.2265471086404e+01, 0.1216800268190e+02,
+      0.2561581447555e-05, 0.1454740653245e+01, 0.7099330490126e+00,
+
+      0.1781441115440e-05, 0.2962068630206e+01, 0.7962980379786e+00,
+      0.1612005874644e-05, 0.1473255041006e+01, 0.5486777812467e+01,
+      0.1818630667105e-05, 0.3743903293447e+00, 0.6283008715021e+01,
+      0.1818601377529e-05, 0.6274174354554e+01, 0.6283142985870e+01,
+      0.1554475925257e-05, 0.1624110906816e+01, 0.2513230340178e+02,
+      0.2090948029241e-05, 0.5852052276256e+01, 0.1179062909082e+02,
+      0.2000176345460e-05, 0.4072093298513e+01, 0.1778984560711e+02,
+      0.1289535917759e-05, 0.5217019331069e+01, 0.7079373888424e+01,
+      0.1281135307881e-05, 0.4802054538934e+01, 0.3738761453707e+01,
+      0.1518229005692e-05, 0.8691914742502e+00, 0.2132990797783e+00,
+
+      0.9450128579027e-06, 0.4601859529950e+01, 0.1097707878456e+02,
+      0.7781119494996e-06, 0.1844352816694e+01, 0.8827390247185e+01,
+      0.7733407759912e-06, 0.3582790154750e+01, 0.5507553240374e+01,
+      0.7350644318120e-06, 0.2695277788230e+01, 0.1589072916335e+01,
+      0.6535928827023e-06, 0.3651327986142e+01, 0.1176985366291e+02,
+      0.6324624183656e-06, 0.2241302375862e+01, 0.6262300422539e+01,
+      0.6298565300557e-06, 0.4407122406081e+01, 0.6303851278352e+01,
+      0.8587037089179e-06, 0.3024307223119e+01, 0.1672837615881e+03,
+      0.8299954491035e-06, 0.6192539428237e+01, 0.3340612434717e+01,
+      0.6311263503401e-06, 0.2014758795416e+01, 0.7113454667900e-02,
+
+      0.6005646745452e-06, 0.3399500503397e+01, 0.4136910472696e+01,
+      0.7917715109929e-06, 0.2493386877837e+01, 0.6069776770667e+01,
+      0.7556958099685e-06, 0.4159491740143e+01, 0.6496374930224e+01,
+      0.6773228244949e-06, 0.4034162934230e+01, 0.9437762937313e+01,
+      0.5370708577847e-06, 0.1562219163734e+01, 0.1194447056968e+01,
+      0.5710804266203e-06, 0.2662730803386e+01, 0.6282095334605e+01,
+      0.5709824583726e-06, 0.3985828430833e+01, 0.6284056366286e+01,
+      0.5143950896447e-06, 0.1308144688689e+01, 0.6290189305114e+01,
+      0.5088010604546e-06, 0.5352817214804e+01, 0.6275962395778e+01,
+      0.4960369085172e-06, 0.2644267922349e+01, 0.6127655567643e+01,
+
+      0.4803137891183e-06, 0.4008844192080e+01, 0.6438496133249e+01,
+      0.5731747768225e-06, 0.3794550174597e+01, 0.3154687086868e+01,
+      0.4735947960579e-06, 0.6107118308982e+01, 0.3128388763578e+01,
+      0.4808348796625e-06, 0.4771458618163e+01, 0.8018209333619e+00,
+      0.4115073743137e-06, 0.3327111335159e+01, 0.8429241228195e+01,
+      0.5230575889287e-06, 0.5305708551694e+01, 0.1336797263425e+02,
+      0.5133977889215e-06, 0.5784230738814e+01, 0.1235285262111e+02,
+      0.5065815825327e-06, 0.2052064793679e+01, 0.1185621865188e+02,
+      0.4339831593868e-06, 0.3644994195830e+01, 0.1726015463500e+02,
+      0.3952928638953e-06, 0.4930376436758e+01, 0.5481254917084e+01,
+
+      0.4898498111942e-06, 0.4542084219731e+00, 0.9225539266174e+01,
+      0.4757490209328e-06, 0.3161126388878e+01, 0.5856477690889e+01,
+      0.4727701669749e-06, 0.6214993845446e+00, 0.2544314396739e+01,
+      0.3800966681863e-06, 0.3040132339297e+01, 0.4265981595566e+00,
+      0.3257301077939e-06, 0.8064977360087e+00, 0.3930209696940e+01,
+      0.3255810528674e-06, 0.1974147981034e+01, 0.2146165377750e+01,
+      0.3252029748187e-06, 0.2845924913135e+01, 0.4164311961999e+01,
+      0.3255505635308e-06, 0.3017900824120e+01, 0.5088628793478e+01,
+      0.2801345211990e-06, 0.6109717793179e+01, 0.1256967486051e+02,
+      0.3688987740970e-06, 0.2911550235289e+01, 0.1807370494127e+02,
+
+      0.2475153429458e-06, 0.2179146025856e+01, 0.2629832328990e-01,
+      0.3033457749150e-06, 0.1994161050744e+01, 0.4535059491685e+01,
+      0.2186743763110e-06, 0.5125687237936e+01, 0.1137170464392e+02,
+      0.2764777032774e-06, 0.4822646860252e+00, 0.1256262854127e+02,
+      0.2199028768592e-06, 0.4637633293831e+01, 0.1255903824622e+02,
+      0.2046482824760e-06, 0.1467038733093e+01, 0.7084896783808e+01,
+      0.2611209147507e-06, 0.3044718783485e+00, 0.7143069561767e+02,
+      0.2286079656818e-06, 0.4764220356805e+01, 0.8031092209206e+01,
+      0.1855071202587e-06, 0.3383637774428e+01, 0.1748016358760e+01,
+      0.2324669506784e-06, 0.6189088449251e+01, 0.1831953657923e+02,
+
+      0.1709528015688e-06, 0.5874966729774e+00, 0.4933208510675e+01,
+      0.2168156875828e-06, 0.4302994009132e+01, 0.1044738781244e+02,
+      0.2106675556535e-06, 0.3800475419891e+01, 0.7477522907414e+01,
+      0.1430213830465e-06, 0.1294660846502e+01, 0.2942463415728e+01,
+      0.1388396901944e-06, 0.4594797202114e+01, 0.8635942003952e+01,
+      0.1922258844190e-06, 0.4943044543591e+00, 0.1729818233119e+02,
+      0.1888460058292e-06, 0.2426943912028e+01, 0.1561374759853e+03,
+      0.1789449386107e-06, 0.1582973303499e+00, 0.1592596075957e+01,
+      0.1360803685374e-06, 0.5197240440504e+01, 0.1309584267300e+02,
+      0.1504038014709e-06, 0.3120360916217e+01, 0.1649636139783e+02,
+
+      0.1382769533389e-06, 0.6164702888205e+01, 0.7632943190217e+01,
+      0.1438059769079e-06, 0.1437423770979e+01, 0.2042657109477e+02,
+      0.1326303260037e-06, 0.3609688799679e+01, 0.1213955354133e+02,
+      0.1159244950540e-06, 0.5463018167225e+01, 0.5331357529664e+01,
+      0.1433118149136e-06, 0.6028909912097e+01, 0.7342457794669e+01,
+      0.1234623148594e-06, 0.3109645574997e+01, 0.6279485555400e+01,
+      0.1233949875344e-06, 0.3539359332866e+01, 0.6286666145492e+01,
+      0.9927196061299e-07, 0.1259321569772e+01, 0.7234794171227e+01,
+      0.1242302191316e-06, 0.1065949392609e+01, 0.1511046609763e+02,
+      0.1098402195201e-06, 0.2192508743837e+01, 0.1098880815746e+02,
+
+      0.1158191395315e-06, 0.4054411278650e+01, 0.5729506548653e+01,
+      0.9048475596241e-07, 0.5429764748518e+01, 0.9623688285163e+01,
+      0.8889853269023e-07, 0.5046586206575e+01, 0.6148010737701e+01,
+      0.1048694242164e-06, 0.2628858030806e+01, 0.6836645152238e+01,
+      0.1112308378646e-06, 0.4177292719907e+01, 0.1572083878776e+02,
+      0.8631729709901e-07, 0.1601345232557e+01, 0.6418140963190e+01,
+      0.8527816951664e-07, 0.2463888997513e+01, 0.1471231707864e+02,
+      0.7892139456991e-07, 0.3154022088718e+01, 0.2118763888447e+01,
+      0.1051782905236e-06, 0.4795035816088e+01, 0.1349867339771e+01,
+      0.1048219943164e-06, 0.2952983395230e+01, 0.5999216516294e+01,
+
+      0.7435760775143e-07, 0.5420547991464e+01, 0.6040347114260e+01,
+      0.9869574106949e-07, 0.3695646753667e+01, 0.6566935184597e+01,
+      0.9156886364226e-07, 0.3922675306609e+01, 0.5643178611111e+01,
+      0.7006834356188e-07, 0.1233968624861e+01, 0.6525804586632e+01,
+      0.9806170182601e-07, 0.1919542280684e+01, 0.2122839202813e+02,
+      0.9052289673607e-07, 0.4615902724369e+01, 0.4690479774488e+01,
+      0.7554200867893e-07, 0.1236863719072e+01, 0.1253985337760e+02,
+      0.8215741286498e-07, 0.3286800101559e+00, 0.1097355562493e+02,
+      0.7185178575397e-07, 0.5880942158367e+01, 0.6245048154254e+01,
+      0.7130726476180e-07, 0.7674871987661e+00, 0.6321103546637e+01,
+
+      0.6650894461162e-07, 0.6987129150116e+00, 0.5327476111629e+01,
+      0.7396888823688e-07, 0.3576824794443e+01, 0.5368044267797e+00,
+      0.7420588884775e-07, 0.5033615245369e+01, 0.2354323048545e+02,
+      0.6141181642908e-07, 0.9449927045673e+00, 0.1296430071988e+02,
+      0.6373557924058e-07, 0.6206342280341e+01, 0.9517183207817e+00,
+      0.6359474329261e-07, 0.5036079095757e+01, 0.1990745094947e+01,
+      0.5740173582646e-07, 0.6105106371350e+01, 0.9555997388169e+00,
+      0.7019864084602e-07, 0.7237747359018e+00, 0.5225775174439e+00,
+      0.6398054487042e-07, 0.3976367969666e+01, 0.2407292145756e+02,
+      0.7797092650498e-07, 0.4305423910623e+01, 0.2200391463820e+02,
+
+      0.6466760000900e-07, 0.3500136825200e+01, 0.5230807360890e+01,
+      0.7529417043890e-07, 0.3514779246100e+01, 0.1842262939178e+02,
+      0.6924571140892e-07, 0.2743457928679e+01, 0.1554202828031e+00,
+      0.6220798650222e-07, 0.2242598118209e+01, 0.1845107853235e+02,
+      0.5870209391853e-07, 0.2332832707527e+01, 0.6398972393349e+00,
+      0.6263953473888e-07, 0.2191105358956e+01, 0.6277552955062e+01,
+      0.6257781390012e-07, 0.4457559396698e+01, 0.6288598745829e+01,
+      0.5697304945123e-07, 0.3499234761404e+01, 0.1551045220144e+01,
+      0.6335438746791e-07, 0.6441691079251e+00, 0.5216580451554e+01,
+      0.6377258441152e-07, 0.2252599151092e+01, 0.5650292065779e+01,
+
+      0.6484841818165e-07, 0.1992812417646e+01, 0.1030928125552e+00,
+      0.4735551485250e-07, 0.3744672082942e+01, 0.1431416805965e+02,
+      0.4628595996170e-07, 0.1334226211745e+01, 0.5535693017924e+00,
+      0.6258152336933e-07, 0.4395836159154e+01, 0.2608790314060e+02,
+      0.6196171366594e-07, 0.2587043007997e+01, 0.8467247584405e+02,
+      0.6159556952126e-07, 0.4782499769128e+01, 0.2394243902548e+03,
+      0.4987741172394e-07, 0.7312257619924e+00, 0.7771377146812e+02,
+      0.5459280703142e-07, 0.3001376372532e+01, 0.6179983037890e+01,
+      0.4863461189999e-07, 0.3767222128541e+01, 0.9027992316901e+02,
+      0.5349912093158e-07, 0.3663594450273e+01, 0.6386168663001e+01,
+
+      0.5673725607806e-07, 0.4331187919049e+01, 0.6915859635113e+01,
+      0.4745485060512e-07, 0.5816195745518e+01, 0.6282970628506e+01,
+      0.4745379005326e-07, 0.8323672435672e+00, 0.6283181072386e+01,
+      0.4049002796321e-07, 0.3785023976293e+01, 0.6254626709878e+01,
+      0.4247084014515e-07, 0.2378220728783e+01, 0.7875671926403e+01,
+      0.4026912363055e-07, 0.2864103423269e+01, 0.6311524991013e+01,
+      0.4062935011774e-07, 0.2415408595975e+01, 0.3634620989887e+01,
+      0.5347771048509e-07, 0.3343479309801e+01, 0.2515860172507e+02,
+      0.4829494136505e-07, 0.2821742398262e+01, 0.5760498333002e+01,
+      0.4342554404599e-07, 0.5624662458712e+01, 0.7238675589263e+01,
+
+      0.4021599184361e-07, 0.5557250275009e+00, 0.1101510648075e+02,
+      0.4104900474558e-07, 0.3296691780005e+01, 0.6709674010002e+01,
+      0.4376532905131e-07, 0.3814443999443e+01, 0.6805653367890e+01,
+      0.3314590480650e-07, 0.3560229189250e+01, 0.1259245002418e+02,
+      0.3232421839643e-07, 0.5185389180568e+01, 0.1066495398892e+01,
+      0.3541176318876e-07, 0.3921381909679e+01, 0.9917696840332e+01,
+      0.3689831242681e-07, 0.4190658955386e+01, 0.1192625446156e+02,
+      0.3890605376774e-07, 0.5546023371097e+01, 0.7478166569050e-01,
+      0.3038559339780e-07, 0.6231032794494e+01, 0.1256621883632e+02,
+      0.3137083969782e-07, 0.6207063419190e+01, 0.4292330755499e+01,
+
+      0.4024004081854e-07, 0.1195257375713e+01, 0.1334167431096e+02,
+      0.3300234879283e-07, 0.1804694240998e+01, 0.1057540660594e+02,
+      0.3635399155575e-07, 0.5597811343500e+01, 0.6208294184755e+01,
+      0.3032668691356e-07, 0.3191059366530e+01, 0.1805292951336e+02,
+      0.2809652069058e-07, 0.4094348032570e+01, 0.3523159621801e-02,
+      0.3696955383823e-07, 0.5219282738794e+01, 0.5966683958112e+01,
+      0.3562894142503e-07, 0.1037247544554e+01, 0.6357857516136e+01,
+      0.3510598524148e-07, 0.1430020816116e+01, 0.6599467742779e+01,
+      0.3617736142953e-07, 0.3002911403677e+01, 0.6019991944201e+01,
+      0.2624524910730e-07, 0.2437046757292e+01, 0.6702560555334e+01,
+
+      0.2535824204490e-07, 0.1581594689647e+01, 0.3141537925223e+02,
+      0.3519787226257e-07, 0.5379863121521e+01, 0.2505706758577e+03,
+      0.2578406709982e-07, 0.4904222639329e+01, 0.1673046366289e+02,
+      0.3423887981473e-07, 0.3646448997315e+01, 0.6546159756691e+01,
+      0.2776083886467e-07, 0.3307829300144e+01, 0.1272157198369e+02,
+      0.3379592818379e-07, 0.1747541251125e+01, 0.1494531617769e+02,
+      0.3050255426284e-07, 0.1784689432607e-01, 0.4732030630302e+01,
+      0.2652378350236e-07, 0.4420055276260e+01, 0.5863591145557e+01,
+      0.2374498173768e-07, 0.3629773929208e+01, 0.2388894113936e+01,
+      0.2716451255140e-07, 0.3079623706780e+01, 0.1202934727411e+02,
+
+      0.3038583699229e-07, 0.3312487903507e+00, 0.1256608456547e+02,
+      0.2220681228760e-07, 0.5265520401774e+01, 0.1336244973887e+02,
+      0.3044156540912e-07, 0.4766664081250e+01, 0.2908881142201e+02,
+      0.2731859923561e-07, 0.5069146530691e+01, 0.1391601904066e+02,
+      0.2285603018171e-07, 0.5954935112271e+01, 0.6076890225335e+01,
+      0.2025006454555e-07, 0.4061789589267e+01, 0.4701116388778e+01,
+      0.2012597519804e-07, 0.2485047705241e+01, 0.6262720680387e+01,
+      0.2003406962258e-07, 0.4163779209320e+01, 0.6303431020504e+01,
+      0.2207863441371e-07, 0.6923839133828e+00, 0.6489261475556e+01,
+      0.2481374305624e-07, 0.5944173595676e+01, 0.1204357418345e+02,
+
+      0.2130923288870e-07, 0.4641013671967e+01, 0.5746271423666e+01,
+      0.2446370543391e-07, 0.6125796518757e+01, 0.1495633313810e+00,
+      0.1932492759052e-07, 0.2234572324504e+00, 0.1352175143971e+02,
+      0.2600122568049e-07, 0.4281012405440e+01, 0.4590910121555e+01,
+      0.2431754047488e-07, 0.1429943874870e+00, 0.1162474756779e+01,
+      0.1875902869209e-07, 0.9781803816948e+00, 0.6279194432410e+01,
+      0.1874381139426e-07, 0.5670368130173e+01, 0.6286957268481e+01,
+      0.2156696047173e-07, 0.2008985006833e+01, 0.1813929450232e+02,
+      0.1965076182484e-07, 0.2566186202453e+00, 0.4686889479442e+01,
+      0.2334816372359e-07, 0.4408121891493e+01, 0.1002183730415e+02,
+
+      0.1869937408802e-07, 0.5272745038656e+01, 0.2427287361862e+00,
+      0.2436236460883e-07, 0.4407720479029e+01, 0.9514313292143e+02,
+      0.1761365216611e-07, 0.1943892315074e+00, 0.1351787002167e+02,
+      0.2156289480503e-07, 0.1418570924545e+01, 0.6037244212485e+01,
+      0.2164748979255e-07, 0.4724603439430e+01, 0.2301353951334e+02,
+      0.2222286670853e-07, 0.2400266874598e+01, 0.1266924451345e+02,
+      0.2070901414929e-07, 0.5230348028732e+01, 0.6528907488406e+01,
+      0.1792745177020e-07, 0.2099190328945e+01, 0.6819880277225e+01,
+      0.1841802068445e-07, 0.3467527844848e+00, 0.6514761976723e+02,
+      0.1578401631718e-07, 0.7098642356340e+00, 0.2077542790660e-01,
+
+      0.1561690152531e-07, 0.5943349620372e+01, 0.6272439236156e+01,
+      0.1558591045463e-07, 0.7040653478980e+00, 0.6293712464735e+01,
+      0.1737356469576e-07, 0.4487064760345e+01, 0.1765478049437e+02,
+      0.1434755619991e-07, 0.2993391570995e+01, 0.1102062672231e+00,
+      0.1482187806654e-07, 0.2278049198251e+01, 0.1052268489556e+01,
+      0.1424812827089e-07, 0.1682114725827e+01, 0.1311972100268e+02,
+      0.1380282448623e-07, 0.3262668602579e+01, 0.1017725758696e+02,
+      0.1811481244566e-07, 0.3187771221777e+01, 0.1887552587463e+02,
+      0.1504446185696e-07, 0.5650162308647e+01, 0.7626583626240e-01,
+      0.1740776154137e-07, 0.5487068607507e+01, 0.1965104848470e+02,
+
+      0.1374339536251e-07, 0.5745688172201e+01, 0.6016468784579e+01,
+      0.1761377477704e-07, 0.5748060203659e+01, 0.2593412433514e+02,
+      0.1535138225795e-07, 0.6226848505790e+01, 0.9411464614024e+01,
+      0.1788140543676e-07, 0.6189318878563e+01, 0.3301902111895e+02,
+      0.1375002807996e-07, 0.5371812884394e+01, 0.6327837846670e+00,
+      0.1242115758632e-07, 0.1471687569712e+01, 0.3894181736510e+01,
+      0.1450977333938e-07, 0.4143836662127e+01, 0.1277945078067e+02,
+      0.1297579575023e-07, 0.9003477661957e+00, 0.6549682916313e+01,
+      0.1462667934821e-07, 0.5760505536428e+01, 0.1863592847156e+02,
+      0.1381774374799e-07, 0.1085471729463e+01, 0.2379164476796e+01,
+
+      0.1682333169307e-07, 0.5409870870133e+01, 0.1620077269078e+02,
+      0.1190812918837e-07, 0.1397205174601e+01, 0.1149965630200e+02,
+      0.1221434762106e-07, 0.9001804809095e+00, 0.1257326515556e+02,
+      0.1549934644860e-07, 0.4262528275544e+01, 0.1820933031200e+02,
+      0.1252138953050e-07, 0.1411642012027e+01, 0.6993008899458e+01,
+      0.1237078905387e-07, 0.2844472403615e+01, 0.2435678079171e+02,
+      0.1446953389615e-07, 0.5295835522223e+01, 0.3813291813120e-01,
+      0.1388446457170e-07, 0.4969428135497e+01, 0.2458316379602e+00,
+      0.1019339179228e-07, 0.2491369561806e+01, 0.6112403035119e+01,
+      0.1258880815343e-07, 0.4679426248976e+01, 0.5429879531333e+01,
+
+      0.1297768238261e-07, 0.1074509953328e+01, 0.1249137003520e+02,
+      0.9913505718094e-08, 0.4735097918224e+01, 0.6247047890016e+01,
+      0.9830453155969e-08, 0.4158649187338e+01, 0.6453748665772e+01,
+      0.1192615865309e-07, 0.3438208613699e+01, 0.6290122169689e+01,
+      0.9835874798277e-08, 0.1913300781229e+01, 0.6319103810876e+01,
+      0.9639087569277e-08, 0.9487683644125e+00, 0.8273820945392e+01,
+      0.1175716107001e-07, 0.3228141664287e+01, 0.6276029531202e+01,
+      0.1018926508678e-07, 0.2216607854300e+01, 0.1254537627298e+02,
+      0.9500087869225e-08, 0.2625116459733e+01, 0.1256517118505e+02,
+      0.9664192916575e-08, 0.5860562449214e+01, 0.6259197520765e+01,
+
+      0.9612858712203e-08, 0.7885682917381e+00, 0.6306954180126e+01,
+      0.1117645675413e-07, 0.3932148831189e+01, 0.1779695906178e+02,
+      0.1158864052160e-07, 0.9995605521691e+00, 0.1778273215245e+02,
+      0.9021043467028e-08, 0.5263769742673e+01, 0.6172869583223e+01,
+      0.8836134773563e-08, 0.1496843220365e+01, 0.1692165728891e+01,
+      0.1045872200691e-07, 0.7009039517214e+00, 0.2204125344462e+00,
+      0.1211463487798e-07, 0.4041544938511e+01, 0.8257698122054e+02,
+      0.8541990804094e-08, 0.1447586692316e+01, 0.6393282117669e+01,
+      0.1038720703636e-07, 0.4594249718112e+00, 0.1550861511662e+02,
+      0.1126722351445e-07, 0.3925550579036e+01, 0.2061856251104e+00,
+
+      0.8697373859631e-08, 0.4411341856037e+01, 0.9491756770005e+00,
+      0.8869380028441e-08, 0.2402659724813e+01, 0.3903911373650e+01,
+      0.9247014693258e-08, 0.1401579743423e+01, 0.6267823317922e+01,
+      0.9205062930950e-08, 0.5245978000814e+01, 0.6298328382969e+01,
+      0.8000745038049e-08, 0.3590803356945e+01, 0.2648454860559e+01,
+      0.9168973650819e-08, 0.2470150501679e+01, 0.1498544001348e+03,
+      0.1075444949238e-07, 0.1328606161230e+01, 0.3694923081589e+02,
+      0.7817298525817e-08, 0.6162256225998e+01, 0.4804209201333e+01,
+      0.9541469226356e-08, 0.3942568967039e+01, 0.1256713221673e+02,
+      0.9821910122027e-08, 0.2360246287233e+00, 0.1140367694411e+02,
+
+      0.9897822023777e-08, 0.4619805634280e+01, 0.2280573557157e+02,
+      0.7737289283765e-08, 0.3784727847451e+01, 0.7834121070590e+01,
+      0.9260204034710e-08, 0.2223352487601e+01, 0.2787043132925e+01,
+      0.7320252888486e-08, 0.1288694636874e+01, 0.6282655592598e+01,
+      0.7319785780946e-08, 0.5359869567774e+01, 0.6283496108294e+01,
+      0.7147219933778e-08, 0.5516616675856e+01, 0.1725663147538e+02,
+      0.7946502829878e-08, 0.2630459984567e+01, 0.1241073141809e+02,
+      0.9001711808932e-08, 0.2849815827227e+01, 0.6281591679874e+01,
+      0.8994041507257e-08, 0.3795244450750e+01, 0.6284560021018e+01,
+      0.8298582787358e-08, 0.5236413127363e+00, 0.1241658836951e+02,
+
+      0.8526596520710e-08, 0.4794605424426e+01, 0.1098419223922e+02,
+      0.8209822103197e-08, 0.1578752370328e+01, 0.1096996532989e+02,
+      0.6357049861094e-08, 0.5708926113761e+01, 0.1596186371003e+01,
+      0.7370473179049e-08, 0.3842402530241e+01, 0.4061219149443e+01,
+      0.7232154664726e-08, 0.3067548981535e+01, 0.1610006857377e+03,
+      0.6328765494903e-08, 0.1313930030069e+01, 0.1193336791622e+02,
+      0.8030064908595e-08, 0.3488500408886e+01, 0.8460828644453e+00,
+      0.6275464259232e-08, 0.1532061626198e+01, 0.8531963191132e+00,
+      0.7051897446325e-08, 0.3285859929993e+01, 0.5849364236221e+01,
+      0.6161593705428e-08, 0.1477341999464e+01, 0.5573142801433e+01,
+
+      0.7754683957278e-08, 0.1586118663096e+01, 0.8662240327241e+01,
+      0.5889928990701e-08, 0.1304887868803e+01, 0.1232342296471e+02,
+      0.5705756047075e-08, 0.4555333589350e+01, 0.1258692712880e+02,
+      0.5964178808332e-08, 0.3001762842062e+01, 0.5333900173445e+01,
+      0.6712446027467e-08, 0.4886780007595e+01, 0.1171295538178e+02,
+      0.5941809275464e-08, 0.4701509603824e+01, 0.9779108567966e+01,
+      0.5466993627395e-08, 0.4588357817278e+01, 0.1884211409667e+02,
+      0.6340512090980e-08, 0.1164543038893e+01, 0.5217580628120e+02,
+      0.6325505710045e-08, 0.3919171259645e+01, 0.1041998632314e+02,
+      0.6164789509685e-08, 0.2143828253542e+01, 0.6151533897323e+01,
+
+      0.5263330812430e-08, 0.6066564434241e+01, 0.1885275071096e+02,
+      0.5597087780221e-08, 0.2926316429472e+01, 0.4337116142245e+00,
+      0.5396556236817e-08, 0.3244303591505e+01, 0.6286362197481e+01,
+      0.5396615148223e-08, 0.3404304703662e+01, 0.6279789503410e+01,
+      0.7091832443341e-08, 0.8532377803192e+00, 0.4907302013889e+01,
+      0.6572352589782e-08, 0.4901966774419e+01, 0.1176433076753e+02,
+      0.5960236060795e-08, 0.1874672315797e+01, 0.1422690933580e-01,
+      0.5125480043511e-08, 0.3735726064334e+01, 0.1245594543367e+02,
+      0.5928241866410e-08, 0.4502033899935e+01, 0.6414617803568e+01,
+      0.5249600357424e-08, 0.4372334799878e+01, 0.1151388321134e+02,
+
+      0.6059171276087e-08, 0.2581617302908e+01, 0.6062663316000e+01,
+      0.5295235081662e-08, 0.2974811513158e+01, 0.3496032717521e+01,
+      0.5820561875933e-08, 0.1796073748244e+00, 0.2838593341516e+00,
+      0.4754696606440e-08, 0.1981998136973e+01, 0.3104930017775e+01,
+      0.6385053548955e-08, 0.2559174171605e+00, 0.6133512519065e+01,
+      0.6589828273941e-08, 0.2750967106776e+01, 0.4087944051283e+02,
+      0.5383376567189e-08, 0.6325947523578e+00, 0.2248384854122e+02,
+      0.5928941683538e-08, 0.1672304519067e+01, 0.1581959461667e+01,
+      0.4816060709794e-08, 0.3512566172575e+01, 0.9388005868221e+01,
+      0.6003381586512e-08, 0.5610932219189e+01, 0.5326786718777e+01,
+
+      0.5504225393105e-08, 0.4037501131256e+01, 0.6503488384892e+01,
+      0.5353772620129e-08, 0.6122774968240e+01, 0.1735668374386e+03,
+      0.5786253768544e-08, 0.5527984999515e+01, 0.1350651127443e+00,
+      0.5065706702002e-08, 0.9980765573624e+00, 0.1248988586463e+02,
+      0.5972838885276e-08, 0.6044489493203e+01, 0.2673594526851e+02,
+      0.5323585877961e-08, 0.3924265998147e+01, 0.4171425416666e+01,
+      0.5210772682858e-08, 0.6220111376901e+01, 0.2460261242967e+02,
+      0.4726549040535e-08, 0.3716043206862e+01, 0.7232251527446e+01,
+      0.6029425105059e-08, 0.8548704071116e+00, 0.3227113045244e+03,
+      0.4481542826513e-08, 0.1426925072829e+01, 0.5547199253223e+01,
+
+      0.5836024505068e-08, 0.7135651752625e-01, 0.7285056171570e+02,
+      0.4137046613272e-08, 0.5330767643283e+01, 0.1087398597200e+02,
+      0.5171977473924e-08, 0.4494262335353e+00, 0.1884570439172e+02,
+      0.5694429833732e-08, 0.2952369582215e+01, 0.9723862754494e+02,
+      0.4009158925298e-08, 0.3500003416535e+01, 0.6244942932314e+01,
+      0.4784939596873e-08, 0.6196709413181e+01, 0.2929661536378e+02,
+      0.3983725022610e-08, 0.5103690031897e+01, 0.4274518229222e+01,
+      0.3870535232462e-08, 0.3187569587401e+01, 0.6321208768577e+01,
+      0.5140501213951e-08, 0.1668924357457e+01, 0.1232032006293e+02,
+      0.3849034819355e-08, 0.4445722510309e+01, 0.1726726808967e+02,
+
+      0.4002383075060e-08, 0.5226224152423e+01, 0.7018952447668e+01,
+      0.3890719543549e-08, 0.4371166550274e+01, 0.1491901785440e+02,
+      0.4887084607881e-08, 0.5973556689693e+01, 0.1478866649112e+01,
+      0.3739939287592e-08, 0.2089084714600e+01, 0.6922973089781e+01,
+      0.5031925918209e-08, 0.4658371936827e+01, 0.1715706182245e+02,
+      0.4387748764954e-08, 0.4825580552819e+01, 0.2331413144044e+03,
+      0.4147398098865e-08, 0.3739003524998e+01, 0.1376059875786e+02,
+      0.3719089993586e-08, 0.1148941386536e+01, 0.6297302759782e+01,
+      0.3934238461056e-08, 0.1559893008343e+01, 0.7872148766781e+01,
+      0.3672471375622e-08, 0.5516145383612e+01, 0.6268848941110e+01,
+
+      0.3768911277583e-08, 0.6116053700563e+01, 0.4157198507331e+01,
+      0.4033388417295e-08, 0.5076821746017e+01, 0.1567108171867e+02,
+      0.3764194617832e-08, 0.8164676232075e+00, 0.3185192151914e+01,
+      0.4840628226284e-08, 0.1360479453671e+01, 0.1252801878276e+02,
+      0.4949443923785e-08, 0.2725622229926e+01, 0.1617106187867e+03,
+      0.4117393089971e-08, 0.6054459628492e+00, 0.5642198095270e+01,
+      0.3925754020428e-08, 0.8570462135210e+00, 0.2139354194808e+02,
+      0.3630551757923e-08, 0.3552067338279e+01, 0.6294805223347e+01,
+      0.3627274802357e-08, 0.3096565085313e+01, 0.6271346477544e+01,
+      0.3806143885093e-08, 0.6367751709777e+00, 0.1725304118033e+02,
+
+      0.4433254641565e-08, 0.4848461503937e+01, 0.7445550607224e+01,
+      0.3712319846576e-08, 0.1331950643655e+01, 0.4194847048887e+00,
+      0.3849847534783e-08, 0.4958368297746e+00, 0.9562891316684e+00,
+      0.3483955430165e-08, 0.2237215515707e+01, 0.1161697602389e+02,
+      0.3961912730982e-08, 0.3332402188575e+01, 0.2277943724828e+02,
+      0.3419978244481e-08, 0.5785600576016e+01, 0.1362553364512e+02,
+      0.3329417758177e-08, 0.9812676559709e-01, 0.1685848245639e+02,
+      0.4207206893193e-08, 0.9494780468236e+00, 0.2986433403208e+02,
+      0.3268548976410e-08, 0.1739332095686e+00, 0.5749861718712e+01,
+      0.3321880082685e-08, 0.1423354800666e+01, 0.6279143387820e+01,
+
+      0.4503173010852e-08, 0.2314972675293e+00, 0.1385561574497e+01,
+      0.4316599090954e-08, 0.1012646782616e+00, 0.4176041334900e+01,
+      0.3283493323850e-08, 0.5233306881265e+01, 0.6287008313071e+01,
+      0.3164033542343e-08, 0.4005597257511e+01, 0.2099539292909e+02,
+      0.4159720956725e-08, 0.5365676242020e+01, 0.5905702259363e+01,
+      0.3565176892217e-08, 0.4284440620612e+01, 0.3932462625300e-02,
+      0.3514440950221e-08, 0.4270562636575e+01, 0.7335344340001e+01,
+      0.3540596871909e-08, 0.5953553201060e+01, 0.1234573916645e+02,
+      0.2960769905118e-08, 0.1115180417718e+01, 0.2670964694522e+02,
+      0.2962213739684e-08, 0.3863811918186e+01, 0.6408777551755e+00,
+
+      0.3883556700251e-08, 0.1268617928302e+01, 0.6660449441528e+01,
+      0.2919225516346e-08, 0.4908605223265e+01, 0.1375773836557e+01,
+      0.3115158863370e-08, 0.3744519976885e+01, 0.3802769619140e-01,
+      0.4099438144212e-08, 0.4173244670532e+01, 0.4480965020977e+02,
+      0.2899531858964e-08, 0.5910601428850e+01, 0.2059724391010e+02,
+      0.3289733429855e-08, 0.2488050078239e+01, 0.1081813534213e+02,
+      0.3933075612875e-08, 0.1122363652883e+01, 0.3773735910827e+00,
+      0.3021403764467e-08, 0.4951973724904e+01, 0.2982630633589e+02,
+      0.2798598949757e-08, 0.5117057845513e+01, 0.1937891852345e+02,
+      0.3397421302707e-08, 0.6104159180476e+01, 0.6923953605621e+01,
+
+      0.3720398002179e-08, 0.1184933429829e+01, 0.3066615496545e+02,
+      0.3598484186267e-08, 0.3505282086105e+01, 0.6147450479709e+01,
+      0.3694594027310e-08, 0.2286651088141e+01, 0.2636725487657e+01,
+      0.2680444152969e-08, 0.1871816775482e+00, 0.6816289982179e+01,
+      0.3497574865641e-08, 0.3143251755431e+01, 0.6418701221183e+01,
+      0.3130274129494e-08, 0.2462167316018e+01, 0.1235996607578e+02,
+      0.3241119069551e-08, 0.4256374004686e+01, 0.1652265972112e+02,
+      0.2601960842061e-08, 0.4970362941425e+01, 0.1045450126711e+02,
+      0.2690601527504e-08, 0.2372657824898e+01, 0.3163918923335e+00,
+      0.2908688152664e-08, 0.4232652627721e+01, 0.2828699048865e+02,
+
+      0.3120456131875e-08, 0.3925747001137e+00, 0.2195415756911e+02,
+      0.3148855423384e-08, 0.3093478330445e+01, 0.1172006883645e+02,
+      0.3051044261017e-08, 0.5560948248212e+01, 0.6055599646783e+01,
+      0.2826006876660e-08, 0.5072790310072e+01, 0.5120601093667e+01,
+      0.3100034191711e-08, 0.4998530231096e+01, 0.1799603123222e+02,
+      0.2398771640101e-08, 0.2561739802176e+01, 0.6255674361143e+01,
+      0.2384002842728e-08, 0.4087420284111e+01, 0.6310477339748e+01,
+      0.2842146517568e-08, 0.2515048217955e+01, 0.5469525544182e+01,
+      0.2847674371340e-08, 0.5235326497443e+01, 0.1034429499989e+02,
+      0.2903722140764e-08, 0.1088200795797e+01, 0.6510552054109e+01,
+
+      0.3187610710605e-08, 0.4710624424816e+01, 0.1693792562116e+03,
+      0.3048869992813e-08, 0.2857975896445e+00, 0.8390110365991e+01,
+      0.2860216950984e-08, 0.2241619020815e+01, 0.2243449970715e+00,
+      0.2701117683113e-08, 0.6651573305272e-01, 0.6129297044991e+01,
+      0.2509891590152e-08, 0.1285135324585e+01, 0.1044027435778e+02,
+      0.2623200252223e-08, 0.2981229834530e+00, 0.6436854655901e+01,
+      0.2622541669202e-08, 0.6122470726189e+01, 0.9380959548977e+01,
+      0.2818435667099e-08, 0.4251087148947e+01, 0.5934151399930e+01,
+      0.2365196797465e-08, 0.3465070460790e+01, 0.2470570524223e+02,
+      0.2358704646143e-08, 0.5791603815350e+01, 0.8671969964381e+01,
+
+      0.2388299481390e-08, 0.4142483772941e+01, 0.7096626156709e+01,
+      0.1996041217224e-08, 0.2101901889496e+01, 0.1727188400790e+02,
+      0.2687593060336e-08, 0.1526689456959e+01, 0.7075506709219e+02,
+      0.2618913670810e-08, 0.2397684236095e+01, 0.6632000300961e+01,
+      0.2571523050364e-08, 0.5751929456787e+00, 0.6206810014183e+01,
+      0.2582135006946e-08, 0.5595464352926e+01, 0.4873985990671e+02,
+      0.2372530190361e-08, 0.5092689490655e+01, 0.1590676413561e+02,
+      0.2357178484712e-08, 0.4444363527851e+01, 0.3097883698531e+01,
+      0.2451590394723e-08, 0.3108251687661e+01, 0.6612329252343e+00,
+      0.2370045949608e-08, 0.2608133861079e+01, 0.3459636466239e+02,
+
+      0.2268997267358e-08, 0.3639717753384e+01, 0.2844914056730e-01,
+      0.1731432137906e-08, 0.1741898445707e+00, 0.2019909489111e+02,
+      0.1629869741622e-08, 0.3902225646724e+01, 0.3035599730800e+02,
+      0.2206215801974e-08, 0.4971131250731e+01, 0.6281667977667e+01,
+      0.2205469554680e-08, 0.1677462357110e+01, 0.6284483723224e+01,
+      0.2148792362509e-08, 0.4236259604006e+01, 0.1980482729015e+02,
+      0.1873733657847e-08, 0.5926814998687e+01, 0.2876692439167e+02,
+      0.2026573758959e-08, 0.4349643351962e+01, 0.2449240616245e+02,
+      0.1807770325110e-08, 0.5700940482701e+01, 0.2045286941806e+02,
+      0.1881174408581e-08, 0.6601286363430e+00, 0.2358125818164e+02,
+
+      0.1368023671690e-08, 0.2211098592752e+01, 0.2473415438279e+02,
+      0.1720017916280e-08, 0.4942488551129e+01, 0.1679593901136e+03,
+      0.1702427665131e-08, 0.1452233856386e+01, 0.3338575901272e+03,
+      0.1414032510054e-08, 0.5525357721439e+01, 0.1624205518357e+03,
+      0.1652626045364e-08, 0.4108794283624e+01, 0.8956999012000e+02,
+      0.1642957769686e-08, 0.7344335209984e+00, 0.5267006960365e+02,
+      0.1614952403624e-08, 0.3541213951363e+01, 0.3332657872986e+02,
+      0.1535988291188e-08, 0.4031094072151e+01, 0.3852657435933e+02,
+      0.1593193738177e-08, 0.4185136203609e+01, 0.2282781046519e+03,
+      0.1074569126382e-08, 0.1720485636868e+01, 0.8397383534231e+02,
+
+      0.1074408214509e-08, 0.2758613420318e+01, 0.8401985929482e+02,
+      0.9700199670465e-09, 0.4216686842097e+01, 0.7826370942180e+02,
+      0.1258433517061e-08, 0.2575068876639e+00, 0.3115650189215e+03,
+      0.1240303229539e-08, 0.4800844956756e+00, 0.1784300471910e+03,
+      0.9018345948127e-09, 0.3896756361552e+00, 0.5886454391678e+02,
+      0.1135301432805e-08, 0.3700805023550e+00, 0.7842370451713e+02,
+      0.9215887951370e-09, 0.4364579276638e+01, 0.1014262087719e+03,
+      0.1055401054147e-08, 0.2156564222111e+01, 0.5660027930059e+02,
+      0.1008725979831e-08, 0.5454015785234e+01, 0.4245678405627e+02,
+      0.7217398104321e-09, 0.1597772562175e+01, 0.2457074661053e+03,
+
+      0.6912033134447e-09, 0.5824090621461e+01, 0.1679936946371e+03,
+      0.6833881523549e-09, 0.3578778482835e+01, 0.6053048899753e+02,
+      0.4887304205142e-09, 0.3724362812423e+01, 0.9656299901946e+02,
+      0.5173709754788e-09, 0.5422427507933e+01, 0.2442876000072e+03,
+      0.4671353097145e-09, 0.2396106924439e+01, 0.1435713242844e+03,
+      0.5652608439480e-09, 0.2804028838685e+01, 0.8365903305582e+02,
+      0.5604061331253e-09, 0.1638816006247e+01, 0.8433466158131e+02,
+      0.4712723365400e-09, 0.8979003224474e+00, 0.3164282286739e+03,
+      0.4909967465112e-09, 0.3210426725516e+01, 0.4059982187939e+03,
+      0.4771358267658e-09, 0.5308027211629e+01, 0.1805255418145e+03,
+
+      0.3943451445989e-09, 0.2195145341074e+01, 0.2568537517081e+03,
+      0.3952109120244e-09, 0.5081189491586e+01, 0.2449975330562e+03,
+      0.3788134594789e-09, 0.4345171264441e+01, 0.1568131045107e+03,
+      0.3738330190479e-09, 0.2613062847997e+01, 0.3948519331910e+03,
+      0.3099866678136e-09, 0.2846760817689e+01, 0.1547176098872e+03,
+      0.2002962716768e-09, 0.4921360989412e+01, 0.2268582385539e+03,
+      0.2198291338754e-09, 0.1130360117454e+00, 0.1658638954901e+03,
+      0.1491958330784e-09, 0.4228195232278e+01, 0.2219950288015e+03,
+      0.1475384076173e-09, 0.3005721811604e+00, 0.3052819430710e+03,
+      0.1661626624624e-09, 0.7830125621203e+00, 0.2526661704812e+03,
+
+      0.9015823460025e-10, 0.3807792942715e+01, 0.4171445043968e+03 };
+
+/* Sun-to-Earth, T^0, Y */
+   static const double e0y[] = {
+      0.9998921098898e+00, 0.1826583913846e+00, 0.6283075850446e+01,
+     -0.2442700893735e-01, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.8352929742915e-02, 0.1395277998680e+00, 0.1256615170089e+02,
+      0.1046697300177e-03, 0.9641423109763e-01, 0.1884922755134e+02,
+      0.3110841876663e-04, 0.5381140401712e+01, 0.8399684731857e+02,
+      0.2570269094593e-04, 0.5301016407128e+01, 0.5296909721118e+00,
+      0.2147389623610e-04, 0.2662510869850e+01, 0.1577343543434e+01,
+      0.1680344384050e-04, 0.5207904119704e+01, 0.6279552690824e+01,
+      0.1679117312193e-04, 0.4582187486968e+01, 0.6286599010068e+01,
+      0.1440512068440e-04, 0.1900688517726e+01, 0.2352866153506e+01,
+
+      0.1135139664999e-04, 0.5273108538556e+01, 0.5223693906222e+01,
+      0.9345482571018e-05, 0.4503047687738e+01, 0.1203646072878e+02,
+      0.9007418719568e-05, 0.1605621059637e+01, 0.1021328554739e+02,
+      0.5671536712314e-05, 0.5812849070861e+00, 0.1059381944224e+01,
+      0.7451401861666e-05, 0.2807346794836e+01, 0.3981490189893e+00,
+      0.6393470057114e-05, 0.6029224133855e+01, 0.5753384878334e+01,
+      0.6814275881697e-05, 0.6472990145974e+00, 0.4705732307012e+01,
+      0.6113705628887e-05, 0.3813843419700e+01, 0.6812766822558e+01,
+      0.4503851367273e-05, 0.4527804370996e+01, 0.5884926831456e+01,
+      0.4522249141926e-05, 0.5991783029224e+01, 0.6256777527156e+01,
+
+      0.4501794307018e-05, 0.3798703844397e+01, 0.6309374173736e+01,
+      0.5514927480180e-05, 0.3961257833388e+01, 0.5507553240374e+01,
+      0.4062862799995e-05, 0.5256247296369e+01, 0.6681224869435e+01,
+      0.5414900429712e-05, 0.5499032014097e+01, 0.7755226100720e+00,
+      0.5463153987424e-05, 0.6173092454097e+01, 0.1414349524433e+02,
+      0.5071611859329e-05, 0.2870244247651e+01, 0.7860419393880e+01,
+      0.2195112094455e-05, 0.2952338617201e+01, 0.1150676975667e+02,
+      0.2279139233919e-05, 0.5951775132933e+01, 0.7058598460518e+01,
+      0.2278386100876e-05, 0.4845456398785e+01, 0.4694002934110e+01,
+      0.2559088003308e-05, 0.6945321117311e+00, 0.1216800268190e+02,
+
+      0.2561079286856e-05, 0.6167224608301e+01, 0.7099330490126e+00,
+      0.1792755796387e-05, 0.1400122509632e+01, 0.7962980379786e+00,
+      0.1818715656502e-05, 0.4703347611830e+01, 0.6283142985870e+01,
+      0.1818744924791e-05, 0.5086748900237e+01, 0.6283008715021e+01,
+      0.1554518791390e-05, 0.5331008042713e-01, 0.2513230340178e+02,
+      0.2063265737239e-05, 0.4283680484178e+01, 0.1179062909082e+02,
+      0.1497613520041e-05, 0.6074207826073e+01, 0.5486777812467e+01,
+      0.2000617940427e-05, 0.2501426281450e+01, 0.1778984560711e+02,
+      0.1289731195580e-05, 0.3646340599536e+01, 0.7079373888424e+01,
+      0.1282657998934e-05, 0.3232864804902e+01, 0.3738761453707e+01,
+
+      0.1528915968658e-05, 0.5581433416669e+01, 0.2132990797783e+00,
+      0.1187304098432e-05, 0.5453576453694e+01, 0.9437762937313e+01,
+      0.7842782928118e-06, 0.2823953922273e+00, 0.8827390247185e+01,
+      0.7352892280868e-06, 0.1124369580175e+01, 0.1589072916335e+01,
+      0.6570189360797e-06, 0.2089154042840e+01, 0.1176985366291e+02,
+      0.6324967590410e-06, 0.6704855581230e+00, 0.6262300422539e+01,
+      0.6298289872283e-06, 0.2836414855840e+01, 0.6303851278352e+01,
+      0.6476686465855e-06, 0.4852433866467e+00, 0.7113454667900e-02,
+      0.8587034651234e-06, 0.1453511005668e+01, 0.1672837615881e+03,
+      0.8068948788113e-06, 0.9224087798609e+00, 0.6069776770667e+01,
+
+      0.8353786011661e-06, 0.4631707184895e+01, 0.3340612434717e+01,
+      0.6009324532132e-06, 0.1829498827726e+01, 0.4136910472696e+01,
+      0.7558158559566e-06, 0.2588596800317e+01, 0.6496374930224e+01,
+      0.5809279504503e-06, 0.5516818853476e+00, 0.1097707878456e+02,
+      0.5374131950254e-06, 0.6275674734960e+01, 0.1194447056968e+01,
+      0.5711160507326e-06, 0.1091905956872e+01, 0.6282095334605e+01,
+      0.5710183170746e-06, 0.2415001635090e+01, 0.6284056366286e+01,
+      0.5144373590610e-06, 0.6020336443438e+01, 0.6290189305114e+01,
+      0.5103108927267e-06, 0.3775634564605e+01, 0.6275962395778e+01,
+      0.4960654697891e-06, 0.1073450946756e+01, 0.6127655567643e+01,
+
+      0.4786385689280e-06, 0.2431178012310e+01, 0.6438496133249e+01,
+      0.6109911263665e-06, 0.5343356157914e+01, 0.3154687086868e+01,
+      0.4839898944024e-06, 0.5830833594047e-01, 0.8018209333619e+00,
+      0.4734822623919e-06, 0.4536080134821e+01, 0.3128388763578e+01,
+      0.4834741473290e-06, 0.2585090489754e+00, 0.7084896783808e+01,
+      0.5134858581156e-06, 0.4213317172603e+01, 0.1235285262111e+02,
+      0.5064004264978e-06, 0.4814418806478e+00, 0.1185621865188e+02,
+      0.3753476772761e-06, 0.1599953399788e+01, 0.8429241228195e+01,
+      0.4935264014283e-06, 0.2157417556873e+01, 0.2544314396739e+01,
+      0.3950929600897e-06, 0.3359394184254e+01, 0.5481254917084e+01,
+
+      0.4895849789777e-06, 0.5165704376558e+01, 0.9225539266174e+01,
+      0.4215241688886e-06, 0.2065368800993e+01, 0.1726015463500e+02,
+      0.3796773731132e-06, 0.1468606346612e+01, 0.4265981595566e+00,
+      0.3114178142515e-06, 0.3615638079474e+01, 0.2146165377750e+01,
+      0.3260664220838e-06, 0.4417134922435e+01, 0.4164311961999e+01,
+      0.3976996123008e-06, 0.4700866883004e+01, 0.5856477690889e+01,
+      0.2801459672924e-06, 0.4538902060922e+01, 0.1256967486051e+02,
+      0.3638931868861e-06, 0.1334197991475e+01, 0.1807370494127e+02,
+      0.2487013269476e-06, 0.3749275558275e+01, 0.2629832328990e-01,
+      0.3034165481994e-06, 0.4236622030873e+00, 0.4535059491685e+01,
+
+      0.2676278825586e-06, 0.5970848007811e+01, 0.3930209696940e+01,
+      0.2764903818918e-06, 0.5194636754501e+01, 0.1256262854127e+02,
+      0.2485149930507e-06, 0.1002434207846e+01, 0.5088628793478e+01,
+      0.2199305540941e-06, 0.3066773098403e+01, 0.1255903824622e+02,
+      0.2571106500435e-06, 0.7588312459063e+00, 0.1336797263425e+02,
+      0.2049751817158e-06, 0.3444977434856e+01, 0.1137170464392e+02,
+      0.2599707296297e-06, 0.1873128542205e+01, 0.7143069561767e+02,
+      0.1785018072217e-06, 0.5015891306615e+01, 0.1748016358760e+01,
+      0.2324833891115e-06, 0.4618271239730e+01, 0.1831953657923e+02,
+      0.1709711119545e-06, 0.5300003455669e+01, 0.4933208510675e+01,
+
+      0.2107159351716e-06, 0.2229819815115e+01, 0.7477522907414e+01,
+      0.1750333080295e-06, 0.6161485880008e+01, 0.1044738781244e+02,
+      0.2000598210339e-06, 0.2967357299999e+01, 0.8031092209206e+01,
+      0.1380920248681e-06, 0.3027007923917e+01, 0.8635942003952e+01,
+      0.1412460470299e-06, 0.6037597163798e+01, 0.2942463415728e+01,
+      0.1888459803001e-06, 0.8561476243374e+00, 0.1561374759853e+03,
+      0.1788370542585e-06, 0.4869736290209e+01, 0.1592596075957e+01,
+      0.1360893296167e-06, 0.3626411886436e+01, 0.1309584267300e+02,
+      0.1506846530160e-06, 0.1550975377427e+01, 0.1649636139783e+02,
+      0.1800913376176e-06, 0.2075826033190e+01, 0.1729818233119e+02,
+
+      0.1436261390649e-06, 0.6148876420255e+01, 0.2042657109477e+02,
+      0.1220227114151e-06, 0.4382583879906e+01, 0.7632943190217e+01,
+      0.1337883603592e-06, 0.2036644327361e+01, 0.1213955354133e+02,
+      0.1159326650738e-06, 0.3892276994687e+01, 0.5331357529664e+01,
+      0.1352853128569e-06, 0.1447950649744e+01, 0.1673046366289e+02,
+      0.1433408296083e-06, 0.4457854692961e+01, 0.7342457794669e+01,
+      0.1234701666518e-06, 0.1538818147151e+01, 0.6279485555400e+01,
+      0.1234027192007e-06, 0.1968523220760e+01, 0.6286666145492e+01,
+      0.1244024091797e-06, 0.5779803499985e+01, 0.1511046609763e+02,
+      0.1097934945516e-06, 0.6210975221388e+00, 0.1098880815746e+02,
+
+      0.1254611329856e-06, 0.2591963807998e+01, 0.1572083878776e+02,
+      0.1158247286784e-06, 0.2483612812670e+01, 0.5729506548653e+01,
+      0.9039078252960e-07, 0.3857554579796e+01, 0.9623688285163e+01,
+      0.9108024978836e-07, 0.5826368512984e+01, 0.7234794171227e+01,
+      0.8887068108436e-07, 0.3475694573987e+01, 0.6148010737701e+01,
+      0.8632374035438e-07, 0.3059070488983e-01, 0.6418140963190e+01,
+      0.7893186992967e-07, 0.1583194837728e+01, 0.2118763888447e+01,
+      0.8297650201172e-07, 0.8519770534637e+00, 0.1471231707864e+02,
+      0.1019759578988e-06, 0.1319598738732e+00, 0.1349867339771e+01,
+      0.1010037696236e-06, 0.9937860115618e+00, 0.6836645152238e+01,
+
+      0.1047727548266e-06, 0.1382138405399e+01, 0.5999216516294e+01,
+      0.7351993881086e-07, 0.3833397851735e+01, 0.6040347114260e+01,
+      0.9868771092341e-07, 0.2124913814390e+01, 0.6566935184597e+01,
+      0.7007321959390e-07, 0.5946305343763e+01, 0.6525804586632e+01,
+      0.6861411679709e-07, 0.4574654977089e+01, 0.7238675589263e+01,
+      0.7554519809614e-07, 0.5949232686844e+01, 0.1253985337760e+02,
+      0.9541880448335e-07, 0.3495242990564e+01, 0.2122839202813e+02,
+      0.7185606722155e-07, 0.4310113471661e+01, 0.6245048154254e+01,
+      0.7131360871710e-07, 0.5480309323650e+01, 0.6321103546637e+01,
+      0.6651142021039e-07, 0.5411097713654e+01, 0.5327476111629e+01,
+
+      0.8538618213667e-07, 0.1827849973951e+01, 0.1101510648075e+02,
+      0.8634954288044e-07, 0.5443584943349e+01, 0.5643178611111e+01,
+      0.7449415051484e-07, 0.2011535459060e+01, 0.5368044267797e+00,
+      0.7421047599169e-07, 0.3464562529249e+01, 0.2354323048545e+02,
+      0.6140694354424e-07, 0.5657556228815e+01, 0.1296430071988e+02,
+      0.6353525143033e-07, 0.3463816593821e+01, 0.1990745094947e+01,
+      0.6221964013447e-07, 0.1532259498697e+01, 0.9517183207817e+00,
+      0.5852480257244e-07, 0.1375396598875e+01, 0.9555997388169e+00,
+      0.6398637498911e-07, 0.2405645801972e+01, 0.2407292145756e+02,
+      0.7039744069878e-07, 0.5397541799027e+01, 0.5225775174439e+00,
+
+      0.6977997694382e-07, 0.4762347105419e+01, 0.1097355562493e+02,
+      0.7460629558396e-07, 0.2711944692164e+01, 0.2200391463820e+02,
+      0.5376577536101e-07, 0.2352980430239e+01, 0.1431416805965e+02,
+      0.7530607893556e-07, 0.1943940180699e+01, 0.1842262939178e+02,
+      0.6822928971605e-07, 0.4337651846959e+01, 0.1554202828031e+00,
+      0.6220772380094e-07, 0.6716871369278e+00, 0.1845107853235e+02,
+      0.6586950799043e-07, 0.2229714460505e+01, 0.5216580451554e+01,
+      0.5873800565771e-07, 0.7627013920580e+00, 0.6398972393349e+00,
+      0.6264346929745e-07, 0.6202785478961e+00, 0.6277552955062e+01,
+      0.6257929115669e-07, 0.2886775596668e+01, 0.6288598745829e+01,
+
+      0.5343536033409e-07, 0.1977241012051e+01, 0.4690479774488e+01,
+      0.5587849781714e-07, 0.1922923484825e+01, 0.1551045220144e+01,
+      0.6905100845603e-07, 0.3570757164631e+01, 0.1030928125552e+00,
+      0.6178957066649e-07, 0.5197558947765e+01, 0.5230807360890e+01,
+      0.6187270224331e-07, 0.8193497368922e+00, 0.5650292065779e+01,
+      0.5385664291426e-07, 0.5406336665586e+01, 0.7771377146812e+02,
+      0.6329363917926e-07, 0.2837760654536e+01, 0.2608790314060e+02,
+      0.4546018761604e-07, 0.2933580297050e+01, 0.5535693017924e+00,
+      0.6196091049375e-07, 0.4157871494377e+01, 0.8467247584405e+02,
+      0.6159555108218e-07, 0.3211703561703e+01, 0.2394243902548e+03,
+
+      0.4995340539317e-07, 0.1459098102922e+01, 0.4732030630302e+01,
+      0.5457031243572e-07, 0.1430457676136e+01, 0.6179983037890e+01,
+      0.4863461418397e-07, 0.2196425916730e+01, 0.9027992316901e+02,
+      0.5342947626870e-07, 0.2086612890268e+01, 0.6386168663001e+01,
+      0.5674296648439e-07, 0.2760204966535e+01, 0.6915859635113e+01,
+      0.4745783120161e-07, 0.4245368971862e+01, 0.6282970628506e+01,
+      0.4745676961198e-07, 0.5544725787016e+01, 0.6283181072386e+01,
+      0.4049796869973e-07, 0.2213984363586e+01, 0.6254626709878e+01,
+      0.4248333596940e-07, 0.8075781952896e+00, 0.7875671926403e+01,
+      0.4027178070205e-07, 0.1293268540378e+01, 0.6311524991013e+01,
+
+      0.4066543943476e-07, 0.3986141175804e+01, 0.3634620989887e+01,
+      0.4858863787880e-07, 0.1276112738231e+01, 0.5760498333002e+01,
+      0.5277398263530e-07, 0.4916111741527e+01, 0.2515860172507e+02,
+      0.4105635656559e-07, 0.1725805864426e+01, 0.6709674010002e+01,
+      0.4376781925772e-07, 0.2243642442106e+01, 0.6805653367890e+01,
+      0.3235827894693e-07, 0.3614135118271e+01, 0.1066495398892e+01,
+      0.3073244740308e-07, 0.2460873393460e+01, 0.5863591145557e+01,
+      0.3088609271373e-07, 0.5678431771790e+01, 0.9917696840332e+01,
+      0.3393022279836e-07, 0.3814017477291e+01, 0.1391601904066e+02,
+      0.3038686508802e-07, 0.4660216229171e+01, 0.1256621883632e+02,
+
+      0.4019677752497e-07, 0.5906906243735e+01, 0.1334167431096e+02,
+      0.3288834998232e-07, 0.9536146445882e+00, 0.1620077269078e+02,
+      0.3889973794631e-07, 0.3942205097644e+01, 0.7478166569050e-01,
+      0.3050438987141e-07, 0.1624810271286e+01, 0.1805292951336e+02,
+      0.3601142564638e-07, 0.4030467142575e+01, 0.6208294184755e+01,
+      0.3689015557141e-07, 0.3648878818694e+01, 0.5966683958112e+01,
+      0.3563471893565e-07, 0.5749584017096e+01, 0.6357857516136e+01,
+      0.2776183170667e-07, 0.2630124187070e+01, 0.3523159621801e-02,
+      0.2922350530341e-07, 0.1790346403629e+01, 0.1272157198369e+02,
+      0.3511076917302e-07, 0.6142198301611e+01, 0.6599467742779e+01,
+
+      0.3619351007632e-07, 0.1432421386492e+01, 0.6019991944201e+01,
+      0.2561254711098e-07, 0.2302822475792e+01, 0.1259245002418e+02,
+      0.2626903942920e-07, 0.8660470994571e+00, 0.6702560555334e+01,
+      0.2550187397083e-07, 0.6069721995383e+01, 0.1057540660594e+02,
+      0.2535873526138e-07, 0.1079020331795e-01, 0.3141537925223e+02,
+      0.3519786153847e-07, 0.3809066902283e+01, 0.2505706758577e+03,
+      0.3424651492873e-07, 0.2075435114417e+01, 0.6546159756691e+01,
+      0.2372676630861e-07, 0.2057803120154e+01, 0.2388894113936e+01,
+      0.2710980779541e-07, 0.1510068488010e+01, 0.1202934727411e+02,
+      0.3038710889704e-07, 0.5043617528901e+01, 0.1256608456547e+02,
+
+      0.2220364130585e-07, 0.3694793218205e+01, 0.1336244973887e+02,
+      0.3025880825460e-07, 0.5450618999049e-01, 0.2908881142201e+02,
+      0.2784493486864e-07, 0.3381164084502e+01, 0.1494531617769e+02,
+      0.2294414142438e-07, 0.4382309025210e+01, 0.6076890225335e+01,
+      0.2012723294724e-07, 0.9142212256518e+00, 0.6262720680387e+01,
+      0.2036357831958e-07, 0.5676172293154e+01, 0.4701116388778e+01,
+      0.2003474823288e-07, 0.2592767977625e+01, 0.6303431020504e+01,
+      0.2207144900109e-07, 0.5404976271180e+01, 0.6489261475556e+01,
+      0.2481664905135e-07, 0.4373284587027e+01, 0.1204357418345e+02,
+      0.2674949182295e-07, 0.5859182188482e+01, 0.4590910121555e+01,
+
+      0.2450554720322e-07, 0.4555381557451e+01, 0.1495633313810e+00,
+      0.2601975986457e-07, 0.3933165584959e+01, 0.1965104848470e+02,
+      0.2199860022848e-07, 0.5227977189087e+01, 0.1351787002167e+02,
+      0.2448121172316e-07, 0.4858060353949e+01, 0.1162474756779e+01,
+      0.1876014864049e-07, 0.5690546553605e+01, 0.6279194432410e+01,
+      0.1874513219396e-07, 0.4099539297446e+01, 0.6286957268481e+01,
+      0.2156380842559e-07, 0.4382594769913e+00, 0.1813929450232e+02,
+      0.1981691240061e-07, 0.1829784152444e+01, 0.4686889479442e+01,
+      0.2329992648539e-07, 0.2836254278973e+01, 0.1002183730415e+02,
+      0.1765184135302e-07, 0.2803494925833e+01, 0.4292330755499e+01,
+
+      0.2436368366085e-07, 0.2836897959677e+01, 0.9514313292143e+02,
+      0.2164089203889e-07, 0.6127522446024e+01, 0.6037244212485e+01,
+      0.1847755034221e-07, 0.3683163635008e+01, 0.2427287361862e+00,
+      0.1674798769966e-07, 0.3316993867246e+00, 0.1311972100268e+02,
+      0.2222542124356e-07, 0.8294097805480e+00, 0.1266924451345e+02,
+      0.2071074505925e-07, 0.3659492220261e+01, 0.6528907488406e+01,
+      0.1608224471835e-07, 0.4774492067182e+01, 0.1352175143971e+02,
+      0.1857583439071e-07, 0.2873120597682e+01, 0.8662240327241e+01,
+      0.1793018836159e-07, 0.5282441177929e+00, 0.6819880277225e+01,
+      0.1575391221692e-07, 0.1320789654258e+01, 0.1102062672231e+00,
+
+      0.1840132009557e-07, 0.1917110916256e+01, 0.6514761976723e+02,
+      0.1760917288281e-07, 0.2972635937132e+01, 0.5746271423666e+01,
+      0.1561779518516e-07, 0.4372569261981e+01, 0.6272439236156e+01,
+      0.1558687885205e-07, 0.5416424926425e+01, 0.6293712464735e+01,
+      0.1951359382579e-07, 0.3094448898752e+01, 0.2301353951334e+02,
+      0.1569144275614e-07, 0.2802103689808e+01, 0.1765478049437e+02,
+      0.1479130389462e-07, 0.2136435020467e+01, 0.2077542790660e-01,
+      0.1467828510764e-07, 0.7072627435674e+00, 0.1052268489556e+01,
+      0.1627627337440e-07, 0.3947607143237e+01, 0.6327837846670e+00,
+      0.1503498479758e-07, 0.4079248909190e+01, 0.7626583626240e-01,
+
+      0.1297967708237e-07, 0.6269637122840e+01, 0.1149965630200e+02,
+      0.1374416896634e-07, 0.4175657970702e+01, 0.6016468784579e+01,
+      0.1783812325219e-07, 0.1476540547560e+01, 0.3301902111895e+02,
+      0.1525884228756e-07, 0.4653477715241e+01, 0.9411464614024e+01,
+      0.1451067396763e-07, 0.2573001128225e+01, 0.1277945078067e+02,
+      0.1297713111950e-07, 0.5612799618771e+01, 0.6549682916313e+01,
+      0.1462784012820e-07, 0.4189661623870e+01, 0.1863592847156e+02,
+      0.1384185980007e-07, 0.2656915472196e+01, 0.2379164476796e+01,
+      0.1221497599801e-07, 0.5612515760138e+01, 0.1257326515556e+02,
+      0.1560574525896e-07, 0.4783414317919e+01, 0.1887552587463e+02,
+
+      0.1544598372036e-07, 0.2694431138063e+01, 0.1820933031200e+02,
+      0.1531678928696e-07, 0.4105103489666e+01, 0.2593412433514e+02,
+      0.1349321503795e-07, 0.3082437194015e+00, 0.5120601093667e+01,
+      0.1252030290917e-07, 0.6124072334087e+01, 0.6993008899458e+01,
+      0.1459243816687e-07, 0.3733103981697e+01, 0.3813291813120e-01,
+      0.1226103625262e-07, 0.1267127706817e+01, 0.2435678079171e+02,
+      0.1019449641504e-07, 0.4367790112269e+01, 0.1725663147538e+02,
+      0.1380789433607e-07, 0.3387201768700e+01, 0.2458316379602e+00,
+      0.1019453421658e-07, 0.9204143073737e+00, 0.6112403035119e+01,
+      0.1297929434405e-07, 0.5786874896426e+01, 0.1249137003520e+02,
+
+      0.9912677786097e-08, 0.3164232870746e+01, 0.6247047890016e+01,
+      0.9829386098599e-08, 0.2586762413351e+01, 0.6453748665772e+01,
+      0.1226807746104e-07, 0.6239068436607e+01, 0.5429879531333e+01,
+      0.1192691755997e-07, 0.1867380051424e+01, 0.6290122169689e+01,
+      0.9836499227081e-08, 0.3424716293727e+00, 0.6319103810876e+01,
+      0.9642862564285e-08, 0.5661372990657e+01, 0.8273820945392e+01,
+      0.1165184404862e-07, 0.5768367239093e+01, 0.1778273215245e+02,
+      0.1175794418818e-07, 0.1657351222943e+01, 0.6276029531202e+01,
+      0.1018948635601e-07, 0.6458292350865e+00, 0.1254537627298e+02,
+      0.9500383606676e-08, 0.1054306140741e+01, 0.1256517118505e+02,
+
+      0.1227512202906e-07, 0.2505278379114e+01, 0.2248384854122e+02,
+      0.9664792009993e-08, 0.4289737277000e+01, 0.6259197520765e+01,
+      0.9613285666331e-08, 0.5500597673141e+01, 0.6306954180126e+01,
+      0.1117906736211e-07, 0.2361405953468e+01, 0.1779695906178e+02,
+      0.9611378640782e-08, 0.2851310576269e+01, 0.2061856251104e+00,
+      0.8845354852370e-08, 0.6208777705343e+01, 0.1692165728891e+01,
+      0.1054046966600e-07, 0.5413091423934e+01, 0.2204125344462e+00,
+      0.1215539124483e-07, 0.5613969479755e+01, 0.8257698122054e+02,
+      0.9932460955209e-08, 0.1106124877015e+01, 0.1017725758696e+02,
+      0.8785804715043e-08, 0.2869224476477e+01, 0.9491756770005e+00,
+
+      0.8538084097562e-08, 0.6159640899344e+01, 0.6393282117669e+01,
+      0.8648994369529e-08, 0.1374901198784e+01, 0.4804209201333e+01,
+      0.1039063219067e-07, 0.5171080641327e+01, 0.1550861511662e+02,
+      0.8867983926439e-08, 0.8317320304902e+00, 0.3903911373650e+01,
+      0.8327495955244e-08, 0.3605591969180e+01, 0.6172869583223e+01,
+      0.9243088356133e-08, 0.6114299196843e+01, 0.6267823317922e+01,
+      0.9205657357835e-08, 0.3675153683737e+01, 0.6298328382969e+01,
+      0.1033269714606e-07, 0.3313328813024e+01, 0.5573142801433e+01,
+      0.8001706275552e-08, 0.2019980960053e+01, 0.2648454860559e+01,
+      0.9171858254191e-08, 0.8992015524177e+00, 0.1498544001348e+03,
+
+      0.1075327150242e-07, 0.2898669963648e+01, 0.3694923081589e+02,
+      0.9884866689828e-08, 0.4946715904478e+01, 0.1140367694411e+02,
+      0.9541835576677e-08, 0.2371787888469e+01, 0.1256713221673e+02,
+      0.7739903376237e-08, 0.2213775190612e+01, 0.7834121070590e+01,
+      0.7311962684106e-08, 0.3429378787739e+01, 0.1192625446156e+02,
+      0.9724904869624e-08, 0.6195878564404e+01, 0.2280573557157e+02,
+      0.9251628983612e-08, 0.6511509527390e+00, 0.2787043132925e+01,
+      0.7320763787842e-08, 0.6001083639421e+01, 0.6282655592598e+01,
+      0.7320296650962e-08, 0.3789073265087e+01, 0.6283496108294e+01,
+      0.7947032271039e-08, 0.1059659582204e+01, 0.1241073141809e+02,
+
+      0.9005277053115e-08, 0.1280315624361e+01, 0.6281591679874e+01,
+      0.8995601652048e-08, 0.2224439106766e+01, 0.6284560021018e+01,
+      0.8288040568796e-08, 0.5234914433867e+01, 0.1241658836951e+02,
+      0.6359381347255e-08, 0.4137989441490e+01, 0.1596186371003e+01,
+      0.8699572228626e-08, 0.1758411009497e+01, 0.6133512519065e+01,
+      0.6456797542736e-08, 0.5919285089994e+01, 0.1685848245639e+02,
+      0.7424573475452e-08, 0.5414616938827e+01, 0.4061219149443e+01,
+      0.7235671196168e-08, 0.1496516557134e+01, 0.1610006857377e+03,
+      0.8104015182733e-08, 0.1919918242764e+01, 0.8460828644453e+00,
+      0.8098576535937e-08, 0.3819615855458e+01, 0.3894181736510e+01,
+
+      0.6275292346625e-08, 0.6244264115141e+01, 0.8531963191132e+00,
+      0.6052432989112e-08, 0.5037731872610e+00, 0.1567108171867e+02,
+      0.5705651535817e-08, 0.2984557271995e+01, 0.1258692712880e+02,
+      0.5789650115138e-08, 0.6087038140697e+01, 0.1193336791622e+02,
+      0.5512132153377e-08, 0.5855668994076e+01, 0.1232342296471e+02,
+      0.7388890819102e-08, 0.2443128574740e+01, 0.4907302013889e+01,
+      0.5467593991798e-08, 0.3017561234194e+01, 0.1884211409667e+02,
+      0.6388519802999e-08, 0.5887386712935e+01, 0.5217580628120e+02,
+      0.6106777149944e-08, 0.3483461059895e+00, 0.1422690933580e-01,
+      0.7383420275489e-08, 0.5417387056707e+01, 0.2358125818164e+02,
+
+      0.5505208141738e-08, 0.2848193644783e+01, 0.1151388321134e+02,
+      0.6310757462877e-08, 0.2349882520828e+01, 0.1041998632314e+02,
+      0.6166904929691e-08, 0.5728575944077e+00, 0.6151533897323e+01,
+      0.5263442042754e-08, 0.4495796125937e+01, 0.1885275071096e+02,
+      0.5591828082629e-08, 0.1355441967677e+01, 0.4337116142245e+00,
+      0.5397051680497e-08, 0.1673422864307e+01, 0.6286362197481e+01,
+      0.5396992745159e-08, 0.1833502206373e+01, 0.6279789503410e+01,
+      0.6572913000726e-08, 0.3331122065824e+01, 0.1176433076753e+02,
+      0.5123421866413e-08, 0.2165327142679e+01, 0.1245594543367e+02,
+      0.5930495725999e-08, 0.2931146089284e+01, 0.6414617803568e+01,
+
+      0.6431797403933e-08, 0.4134407994088e+01, 0.1350651127443e+00,
+      0.5003182207604e-08, 0.3805420303749e+01, 0.1096996532989e+02,
+      0.5587731032504e-08, 0.1082469260599e+01, 0.6062663316000e+01,
+      0.5935263407816e-08, 0.8384333678401e+00, 0.5326786718777e+01,
+      0.4756019827760e-08, 0.3552588749309e+01, 0.3104930017775e+01,
+      0.6599951172637e-08, 0.4320826409528e+01, 0.4087944051283e+02,
+      0.5902606868464e-08, 0.4811879454445e+01, 0.5849364236221e+01,
+      0.5921147809031e-08, 0.9942628922396e-01, 0.1581959461667e+01,
+      0.5505382581266e-08, 0.2466557607764e+01, 0.6503488384892e+01,
+      0.5353771071862e-08, 0.4551978748683e+01, 0.1735668374386e+03,
+
+      0.5063282210946e-08, 0.5710812312425e+01, 0.1248988586463e+02,
+      0.5926120403383e-08, 0.1333998428358e+01, 0.2673594526851e+02,
+      0.5211016176149e-08, 0.4649315360760e+01, 0.2460261242967e+02,
+      0.5347075084894e-08, 0.5512754081205e+01, 0.4171425416666e+01,
+      0.4872609773574e-08, 0.1308025299938e+01, 0.5333900173445e+01,
+      0.4727711321420e-08, 0.2144908368062e+01, 0.7232251527446e+01,
+      0.6029426018652e-08, 0.5567259412084e+01, 0.3227113045244e+03,
+      0.4321485284369e-08, 0.5230667156451e+01, 0.9388005868221e+01,
+      0.4476406760553e-08, 0.6134081115303e+01, 0.5547199253223e+01,
+      0.5835268277420e-08, 0.4783808492071e+01, 0.7285056171570e+02,
+
+      0.5172183602748e-08, 0.5161817911099e+01, 0.1884570439172e+02,
+      0.5693571465184e-08, 0.1381646203111e+01, 0.9723862754494e+02,
+      0.4060634965349e-08, 0.3876705259495e+00, 0.4274518229222e+01,
+      0.3967398770473e-08, 0.5029491776223e+01, 0.3496032717521e+01,
+      0.3943754005255e-08, 0.1923162955490e+01, 0.6244942932314e+01,
+      0.4781323427824e-08, 0.4633332586423e+01, 0.2929661536378e+02,
+      0.3871483781204e-08, 0.1616650009743e+01, 0.6321208768577e+01,
+      0.5141741733997e-08, 0.9817316704659e-01, 0.1232032006293e+02,
+      0.4002385978497e-08, 0.3656161212139e+01, 0.7018952447668e+01,
+      0.4901092604097e-08, 0.4404098713092e+01, 0.1478866649112e+01,
+
+      0.3740932630345e-08, 0.5181188732639e+00, 0.6922973089781e+01,
+      0.4387283718538e-08, 0.3254859566869e+01, 0.2331413144044e+03,
+      0.5019197802033e-08, 0.3086773224677e+01, 0.1715706182245e+02,
+      0.3834931695175e-08, 0.2797882673542e+01, 0.1491901785440e+02,
+      0.3760413942497e-08, 0.2892676280217e+01, 0.1726726808967e+02,
+      0.3719717204628e-08, 0.5861046025739e+01, 0.6297302759782e+01,
+      0.4145623530149e-08, 0.2168239627033e+01, 0.1376059875786e+02,
+      0.3932788425380e-08, 0.6271811124181e+01, 0.7872148766781e+01,
+      0.3686377476857e-08, 0.3936853151404e+01, 0.6268848941110e+01,
+      0.3779077950339e-08, 0.1404148734043e+01, 0.4157198507331e+01,
+
+      0.4091334550598e-08, 0.2452436180854e+01, 0.9779108567966e+01,
+      0.3926694536146e-08, 0.6102292739040e+01, 0.1098419223922e+02,
+      0.4841000253289e-08, 0.6072760457276e+01, 0.1252801878276e+02,
+      0.4949340130240e-08, 0.1154832815171e+01, 0.1617106187867e+03,
+      0.3761557737360e-08, 0.5527545321897e+01, 0.3185192151914e+01,
+      0.3647396268188e-08, 0.1525035688629e+01, 0.6271346477544e+01,
+      0.3932405074189e-08, 0.5570681040569e+01, 0.2139354194808e+02,
+      0.3631322501141e-08, 0.1981240601160e+01, 0.6294805223347e+01,
+      0.4130007425139e-08, 0.2050060880201e+01, 0.2195415756911e+02,
+      0.4433905965176e-08, 0.3277477970321e+01, 0.7445550607224e+01,
+
+      0.3851814176947e-08, 0.5210690074886e+01, 0.9562891316684e+00,
+      0.3485807052785e-08, 0.6653274904611e+00, 0.1161697602389e+02,
+      0.3979772816991e-08, 0.1767941436148e+01, 0.2277943724828e+02,
+      0.3402607460500e-08, 0.3421746306465e+01, 0.1087398597200e+02,
+      0.4049993000926e-08, 0.1127144787547e+01, 0.3163918923335e+00,
+      0.3420511182382e-08, 0.4214794779161e+01, 0.1362553364512e+02,
+      0.3640772365012e-08, 0.5324905497687e+01, 0.1725304118033e+02,
+      0.3323037987501e-08, 0.6135761838271e+01, 0.6279143387820e+01,
+      0.4503141663637e-08, 0.1802305450666e+01, 0.1385561574497e+01,
+      0.4314560055588e-08, 0.4812299731574e+01, 0.4176041334900e+01,
+
+      0.3294226949110e-08, 0.3657547059723e+01, 0.6287008313071e+01,
+      0.3215657197281e-08, 0.4866676894425e+01, 0.5749861718712e+01,
+      0.4129362656266e-08, 0.3809342558906e+01, 0.5905702259363e+01,
+      0.3137762976388e-08, 0.2494635174443e+01, 0.2099539292909e+02,
+      0.3514010952384e-08, 0.2699961831678e+01, 0.7335344340001e+01,
+      0.3327607571530e-08, 0.3318457714816e+01, 0.5436992986000e+01,
+      0.3541066946675e-08, 0.4382703582466e+01, 0.1234573916645e+02,
+      0.3216179847052e-08, 0.5271066317054e+01, 0.3802769619140e-01,
+      0.2959045059570e-08, 0.5819591585302e+01, 0.2670964694522e+02,
+      0.3884040326665e-08, 0.5980934960428e+01, 0.6660449441528e+01,
+
+      0.2922027539886e-08, 0.3337290282483e+01, 0.1375773836557e+01,
+      0.4110846382042e-08, 0.5742978187327e+01, 0.4480965020977e+02,
+      0.2934508411032e-08, 0.2278075804200e+01, 0.6408777551755e+00,
+      0.3966896193000e-08, 0.5835747858477e+01, 0.3773735910827e+00,
+      0.3286695827610e-08, 0.5838898193902e+01, 0.3932462625300e-02,
+      0.3720643094196e-08, 0.1122212337858e+01, 0.1646033343740e+02,
+      0.3285508906174e-08, 0.9182250996416e+00, 0.1081813534213e+02,
+      0.3753880575973e-08, 0.5174761973266e+01, 0.5642198095270e+01,
+      0.3022129385587e-08, 0.3381611020639e+01, 0.2982630633589e+02,
+      0.2798569205621e-08, 0.3546193723922e+01, 0.1937891852345e+02,
+
+      0.3397872070505e-08, 0.4533203197934e+01, 0.6923953605621e+01,
+      0.3708099772977e-08, 0.2756168198616e+01, 0.3066615496545e+02,
+      0.3599283541510e-08, 0.1934395469918e+01, 0.6147450479709e+01,
+      0.3688702753059e-08, 0.7149920971109e+00, 0.2636725487657e+01,
+      0.2681084724003e-08, 0.4899819493154e+01, 0.6816289982179e+01,
+      0.3495993460759e-08, 0.1572418915115e+01, 0.6418701221183e+01,
+      0.3130770324995e-08, 0.8912190180489e+00, 0.1235996607578e+02,
+      0.2744353821941e-08, 0.3800821940055e+01, 0.2059724391010e+02,
+      0.2842732906341e-08, 0.2644717440029e+01, 0.2828699048865e+02,
+      0.3046882682154e-08, 0.3987793020179e+01, 0.6055599646783e+01,
+
+      0.2399072455143e-08, 0.9908826440764e+00, 0.6255674361143e+01,
+      0.2384306274204e-08, 0.2516149752220e+01, 0.6310477339748e+01,
+      0.2977324500559e-08, 0.5849195642118e+01, 0.1652265972112e+02,
+      0.3062835258972e-08, 0.1681660100162e+01, 0.1172006883645e+02,
+      0.3109682589231e-08, 0.5804143987737e+00, 0.2751146787858e+02,
+      0.2903920355299e-08, 0.5800768280123e+01, 0.6510552054109e+01,
+      0.2823221989212e-08, 0.9241118370216e+00, 0.5469525544182e+01,
+      0.3187949696649e-08, 0.3139776445735e+01, 0.1693792562116e+03,
+      0.2922559771655e-08, 0.3549440782984e+01, 0.2630839062450e+00,
+      0.2436302066603e-08, 0.4735540696319e+01, 0.3946258593675e+00,
+
+      0.3049473043606e-08, 0.4998289124561e+01, 0.8390110365991e+01,
+      0.2863682575784e-08, 0.6709515671102e+00, 0.2243449970715e+00,
+      0.2641750517966e-08, 0.5410978257284e+01, 0.2986433403208e+02,
+      0.2704093466243e-08, 0.4778317207821e+01, 0.6129297044991e+01,
+      0.2445522177011e-08, 0.6009020662222e+01, 0.1171295538178e+02,
+      0.2623608810230e-08, 0.5010449777147e+01, 0.6436854655901e+01,
+      0.2079259704053e-08, 0.5980943768809e+01, 0.2019909489111e+02,
+      0.2820225596771e-08, 0.2679965110468e+01, 0.5934151399930e+01,
+      0.2365221950927e-08, 0.1894231148810e+01, 0.2470570524223e+02,
+      0.2359682077149e-08, 0.4220752950780e+01, 0.8671969964381e+01,
+
+      0.2387577137206e-08, 0.2571783940617e+01, 0.7096626156709e+01,
+      0.1982102089816e-08, 0.5169765997119e+00, 0.1727188400790e+02,
+      0.2687502389925e-08, 0.6239078264579e+01, 0.7075506709219e+02,
+      0.2207751669135e-08, 0.2031184412677e+01, 0.4377611041777e+01,
+      0.2618370214274e-08, 0.8266079985979e+00, 0.6632000300961e+01,
+      0.2591951887361e-08, 0.8819350522008e+00, 0.4873985990671e+02,
+      0.2375055656248e-08, 0.3520944177789e+01, 0.1590676413561e+02,
+      0.2472019978911e-08, 0.1551431908671e+01, 0.6612329252343e+00,
+      0.2368157127199e-08, 0.4178610147412e+01, 0.3459636466239e+02,
+      0.1764846605693e-08, 0.1506764000157e+01, 0.1980094587212e+02,
+
+      0.2291769608798e-08, 0.2118250611782e+01, 0.2844914056730e-01,
+      0.2209997316943e-08, 0.3363255261678e+01, 0.2666070658668e+00,
+      0.2292699097923e-08, 0.4200423956460e+00, 0.1484170571900e-02,
+      0.1629683015329e-08, 0.2331362582487e+01, 0.3035599730800e+02,
+      0.2206492862426e-08, 0.3400274026992e+01, 0.6281667977667e+01,
+      0.2205746568257e-08, 0.1066051230724e+00, 0.6284483723224e+01,
+      0.2026310767991e-08, 0.2779066487979e+01, 0.2449240616245e+02,
+      0.1762977622163e-08, 0.9951450691840e+00, 0.2045286941806e+02,
+      0.1368535049606e-08, 0.6402447365817e+00, 0.2473415438279e+02,
+      0.1720598775450e-08, 0.2303524214705e+00, 0.1679593901136e+03,
+
+      0.1702429015449e-08, 0.6164622655048e+01, 0.3338575901272e+03,
+      0.1414033197685e-08, 0.3954561185580e+01, 0.1624205518357e+03,
+      0.1573768958043e-08, 0.2028286308984e+01, 0.3144167757552e+02,
+      0.1650705184447e-08, 0.2304040666128e+01, 0.5267006960365e+02,
+      0.1651087618855e-08, 0.2538461057280e+01, 0.8956999012000e+02,
+      0.1616409518983e-08, 0.5111054348152e+01, 0.3332657872986e+02,
+      0.1537175173581e-08, 0.5601130666603e+01, 0.3852657435933e+02,
+      0.1593191980553e-08, 0.2614340453411e+01, 0.2282781046519e+03,
+      0.1499480170643e-08, 0.3624721577264e+01, 0.2823723341956e+02,
+      0.1493807843235e-08, 0.4214569879008e+01, 0.2876692439167e+02,
+
+      0.1074571199328e-08, 0.1496911744704e+00, 0.8397383534231e+02,
+      0.1074406983417e-08, 0.1187817671922e+01, 0.8401985929482e+02,
+      0.9757576855851e-09, 0.2655703035858e+01, 0.7826370942180e+02,
+      0.1258432887565e-08, 0.4969896184844e+01, 0.3115650189215e+03,
+      0.1240336343282e-08, 0.5192460776926e+01, 0.1784300471910e+03,
+      0.9016107005164e-09, 0.1960356923057e+01, 0.5886454391678e+02,
+      0.1135392360918e-08, 0.5082427809068e+01, 0.7842370451713e+02,
+      0.9216046089565e-09, 0.2793775037273e+01, 0.1014262087719e+03,
+      0.1061276615030e-08, 0.3726144311409e+01, 0.5660027930059e+02,
+      0.1010110596263e-08, 0.7404080708937e+00, 0.4245678405627e+02,
+
+      0.7217424756199e-09, 0.2697449980577e-01, 0.2457074661053e+03,
+      0.6912003846756e-09, 0.4253296276335e+01, 0.1679936946371e+03,
+      0.6871814664847e-09, 0.5148072412354e+01, 0.6053048899753e+02,
+      0.4887158016343e-09, 0.2153581148294e+01, 0.9656299901946e+02,
+      0.5161802866314e-09, 0.3852750634351e+01, 0.2442876000072e+03,
+      0.5652599559057e-09, 0.1233233356270e+01, 0.8365903305582e+02,
+      0.4710812608586e-09, 0.5610486976767e+01, 0.3164282286739e+03,
+      0.4909977500324e-09, 0.1639629524123e+01, 0.4059982187939e+03,
+      0.4772641839378e-09, 0.3737100368583e+01, 0.1805255418145e+03,
+      0.4487562567153e-09, 0.1158417054478e+00, 0.8433466158131e+02,
+
+      0.3943441230497e-09, 0.6243502862796e+00, 0.2568537517081e+03,
+      0.3952236913598e-09, 0.3510377382385e+01, 0.2449975330562e+03,
+      0.3788898363417e-09, 0.5916128302299e+01, 0.1568131045107e+03,
+      0.3738329328831e-09, 0.1042266763456e+01, 0.3948519331910e+03,
+      0.2451199165151e-09, 0.1166788435700e+01, 0.1435713242844e+03,
+      0.2436734402904e-09, 0.3254726114901e+01, 0.2268582385539e+03,
+      0.2213605274325e-09, 0.1687210598530e+01, 0.1658638954901e+03,
+      0.1491521204829e-09, 0.2657541786794e+01, 0.2219950288015e+03,
+      0.1474995329744e-09, 0.5013089805819e+01, 0.3052819430710e+03,
+      0.1661939475656e-09, 0.5495315428418e+01, 0.2526661704812e+03,
+
+      0.9015946748003e-10, 0.2236989966505e+01, 0.4171445043968e+03 };
+
+/* Sun-to-Earth, T^0, Z */
+   static const double e0z[] = {
+      0.2796207639075e-05, 0.3198701560209e+01, 0.8433466158131e+02,
+      0.1016042198142e-05, 0.5422360395913e+01, 0.5507553240374e+01,
+      0.8044305033647e-06, 0.3880222866652e+01, 0.5223693906222e+01,
+      0.4385347909274e-06, 0.3704369937468e+01, 0.2352866153506e+01,
+      0.3186156414906e-06, 0.3999639363235e+01, 0.1577343543434e+01,
+      0.2272412285792e-06, 0.3984738315952e+01, 0.1047747311755e+01,
+      0.1645620103007e-06, 0.3565412516841e+01, 0.5856477690889e+01,
+      0.1815836921166e-06, 0.4984507059020e+01, 0.6283075850446e+01,
+      0.1447461676364e-06, 0.3702753570108e+01, 0.9437762937313e+01,
+      0.1430760876382e-06, 0.3409658712357e+01, 0.1021328554739e+02,
+
+      0.1120445753226e-06, 0.4829561570246e+01, 0.1414349524433e+02,
+      0.1090232840797e-06, 0.2080729178066e+01, 0.6812766822558e+01,
+      0.9715727346551e-07, 0.3476295881948e+01, 0.4694002934110e+01,
+      0.1036267136217e-06, 0.4056639536648e+01, 0.7109288135493e+02,
+      0.8752665271340e-07, 0.4448159519911e+01, 0.5753384878334e+01,
+      0.8331864956004e-07, 0.4991704044208e+01, 0.7084896783808e+01,
+      0.6901658670245e-07, 0.4325358994219e+01, 0.6275962395778e+01,
+      0.9144536848998e-07, 0.1141826375363e+01, 0.6620890113188e+01,
+      0.7205085037435e-07, 0.3624344170143e+01, 0.5296909721118e+00,
+      0.7697874654176e-07, 0.5554257458998e+01, 0.1676215758509e+03,
+
+      0.5197545738384e-07, 0.6251760961735e+01, 0.1807370494127e+02,
+      0.5031345378608e-07, 0.2497341091913e+01, 0.4705732307012e+01,
+      0.4527110205840e-07, 0.2335079920992e+01, 0.6309374173736e+01,
+      0.4753355798089e-07, 0.7094148987474e+00, 0.5884926831456e+01,
+      0.4296951977516e-07, 0.1101916352091e+01, 0.6681224869435e+01,
+      0.3855341568387e-07, 0.1825495405486e+01, 0.5486777812467e+01,
+      0.5253930970990e-07, 0.4424740687208e+01, 0.7860419393880e+01,
+      0.4024630496471e-07, 0.5120498157053e+01, 0.1336797263425e+02,
+      0.4061069791453e-07, 0.6029771435451e+01, 0.3930209696940e+01,
+      0.3797883804205e-07, 0.4435193600836e+00, 0.3154687086868e+01,
+
+      0.2933033225587e-07, 0.5124157356507e+01, 0.1059381944224e+01,
+      0.3503000930426e-07, 0.5421830162065e+01, 0.6069776770667e+01,
+      0.3670096214050e-07, 0.4582101667297e+01, 0.1219403291462e+02,
+      0.2905609437008e-07, 0.1926566420072e+01, 0.1097707878456e+02,
+      0.2466827821713e-07, 0.6090174539834e+00, 0.6496374930224e+01,
+      0.2691647295332e-07, 0.1393432595077e+01, 0.2200391463820e+02,
+      0.2150554667946e-07, 0.4308671715951e+01, 0.5643178611111e+01,
+      0.2237481922680e-07, 0.8133968269414e+00, 0.8635942003952e+01,
+      0.1817741038157e-07, 0.3755205127454e+01, 0.3340612434717e+01,
+      0.2227820762132e-07, 0.2759558596664e+01, 0.1203646072878e+02,
+
+      0.1944713772307e-07, 0.5699645869121e+01, 0.1179062909082e+02,
+      0.1527340520662e-07, 0.1986749091746e+01, 0.3981490189893e+00,
+      0.1577282574914e-07, 0.3205017217983e+01, 0.5088628793478e+01,
+      0.1424738825424e-07, 0.6256747903666e+01, 0.2544314396739e+01,
+      0.1616563121701e-07, 0.2601671259394e+00, 0.1729818233119e+02,
+      0.1401210391692e-07, 0.4686939173506e+01, 0.7058598460518e+01,
+      0.1488726974214e-07, 0.2815862451372e+01, 0.2593412433514e+02,
+      0.1692626442388e-07, 0.4956894109797e+01, 0.1564752902480e+03,
+      0.1123571582910e-07, 0.2381192697696e+01, 0.3738761453707e+01,
+      0.9903308606317e-08, 0.4294851657684e+01, 0.9225539266174e+01,
+
+      0.9174533187191e-08, 0.3075171510642e+01, 0.4164311961999e+01,
+      0.8645985631457e-08, 0.5477534821633e+00, 0.8429241228195e+01,
+     -0.1085876492688e-07, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.9264309077815e-08, 0.5968571670097e+01, 0.7079373888424e+01,
+      0.8243116984954e-08, 0.1489098777643e+01, 0.1044738781244e+02,
+      0.8268102113708e-08, 0.3512977691983e+01, 0.1150676975667e+02,
+      0.9043613988227e-08, 0.1290704408221e+00, 0.1101510648075e+02,
+      0.7432912038789e-08, 0.1991086893337e+01, 0.2608790314060e+02,
+      0.8586233727285e-08, 0.4238357924414e+01, 0.2986433403208e+02,
+      0.7612230060131e-08, 0.2911090150166e+01, 0.4732030630302e+01,
+
+      0.7097787751408e-08, 0.1908938392390e+01, 0.8031092209206e+01,
+      0.7640237040175e-08, 0.6129219000168e+00, 0.7962980379786e+00,
+      0.7070445688081e-08, 0.1380417036651e+01, 0.2146165377750e+01,
+      0.7690770957702e-08, 0.1680504249084e+01, 0.2122839202813e+02,
+      0.8051292542594e-08, 0.5127423484511e+01, 0.2942463415728e+01,
+      0.5902709104515e-08, 0.2020274190917e+01, 0.7755226100720e+00,
+      0.5134567496462e-08, 0.2606778676418e+01, 0.1256615170089e+02,
+      0.5525802046102e-08, 0.1613011769663e+01, 0.8018209333619e+00,
+      0.5880724784221e-08, 0.4604483417236e+01, 0.4690479774488e+01,
+      0.5211699081370e-08, 0.5718964114193e+01, 0.8827390247185e+01,
+
+      0.4891849573562e-08, 0.3689658932196e+01, 0.2132990797783e+00,
+      0.5150246069997e-08, 0.4099769855122e+01, 0.6480980550449e+02,
+      0.5102434319633e-08, 0.5660834602509e+01, 0.3379454372902e+02,
+      0.5083405254252e-08, 0.9842221218974e+00, 0.4136910472696e+01,
+      0.4206562585682e-08, 0.1341363634163e+00, 0.3128388763578e+01,
+      0.4663249683579e-08, 0.8130132735866e+00, 0.5216580451554e+01,
+      0.4099474416530e-08, 0.5791497770644e+01, 0.4265981595566e+00,
+      0.4628251220767e-08, 0.1249802769331e+01, 0.1572083878776e+02,
+      0.5024068728142e-08, 0.4795684802743e+01, 0.6290189305114e+01,
+      0.5120234327758e-08, 0.3810420387208e+01, 0.5230807360890e+01,
+
+      0.5524029815280e-08, 0.1029264714351e+01, 0.2397622045175e+03,
+      0.4757415718860e-08, 0.3528044781779e+01, 0.1649636139783e+02,
+      0.3915786131127e-08, 0.5593889282646e+01, 0.1589072916335e+01,
+      0.4869053149991e-08, 0.3299636454433e+01, 0.7632943190217e+01,
+      0.3649365703729e-08, 0.1286049002584e+01, 0.6206810014183e+01,
+      0.3992493949002e-08, 0.3100307589464e+01, 0.2515860172507e+02,
+      0.3320247477418e-08, 0.6212683940807e+01, 0.1216800268190e+02,
+      0.3287123739696e-08, 0.4699118445928e+01, 0.7234794171227e+01,
+      0.3472776811103e-08, 0.2630507142004e+01, 0.7342457794669e+01,
+      0.3423253294767e-08, 0.2946432844305e+01, 0.9623688285163e+01,
+
+      0.3896173898244e-08, 0.1224834179264e+01, 0.6438496133249e+01,
+      0.3388455337924e-08, 0.1543807616351e+01, 0.1494531617769e+02,
+      0.3062704716523e-08, 0.1191777572310e+01, 0.8662240327241e+01,
+      0.3270075600400e-08, 0.5483498767737e+01, 0.1194447056968e+01,
+      0.3101209215259e-08, 0.8000833804348e+00, 0.3772475342596e+02,
+      0.2780883347311e-08, 0.4077980721888e+00, 0.5863591145557e+01,
+      0.2903605931824e-08, 0.2617490302147e+01, 0.1965104848470e+02,
+      0.2682014743119e-08, 0.2634703158290e+01, 0.7238675589263e+01,
+      0.2534360108492e-08, 0.6102446114873e+01, 0.6836645152238e+01,
+      0.2392564882509e-08, 0.3681820208691e+01, 0.5849364236221e+01,
+
+      0.2656667254856e-08, 0.6216045388886e+01, 0.6133512519065e+01,
+      0.2331242096773e-08, 0.5864949777744e+01, 0.4535059491685e+01,
+      0.2287898363668e-08, 0.4566628532802e+01, 0.7477522907414e+01,
+      0.2336944521306e-08, 0.2442722126930e+01, 0.1137170464392e+02,
+      0.3156632236269e-08, 0.1626628050682e+01, 0.2509084901204e+03,
+      0.2982612402766e-08, 0.2803604512609e+01, 0.1748016358760e+01,
+      0.2774031674807e-08, 0.4654002897158e+01, 0.8223916695780e+02,
+      0.2295236548638e-08, 0.4326518333253e+01, 0.3378142627421e+00,
+      0.2190714699873e-08, 0.4519614578328e+01, 0.2908881142201e+02,
+      0.2191495845045e-08, 0.3012626912549e+01, 0.1673046366289e+02,
+
+      0.2492901628386e-08, 0.1290101424052e+00, 0.1543797956245e+03,
+      0.1993778064319e-08, 0.3864046799414e+01, 0.1778984560711e+02,
+      0.1898146479022e-08, 0.5053777235891e+01, 0.2042657109477e+02,
+      0.1918280127634e-08, 0.2222470192548e+01, 0.4165496312290e+02,
+      0.1916351061607e-08, 0.8719067257774e+00, 0.7737595720538e+02,
+      0.1834720181466e-08, 0.4031491098040e+01, 0.2358125818164e+02,
+      0.1249201523806e-08, 0.5938379466835e+01, 0.3301902111895e+02,
+      0.1477304050539e-08, 0.6544722606797e+00, 0.9548094718417e+02,
+      0.1264316431249e-08, 0.2059072853236e+01, 0.8399684731857e+02,
+      0.1203526495039e-08, 0.3644813532605e+01, 0.4558517281984e+02,
+
+      0.9221681059831e-09, 0.3241815055602e+01, 0.7805158573086e+02,
+      0.7849278367646e-09, 0.5043812342457e+01, 0.5217580628120e+02,
+      0.7983392077387e-09, 0.5000024502753e+01, 0.1501922143975e+03,
+      0.7925395431654e-09, 0.1398734871821e-01, 0.9061773743175e+02,
+      0.7640473285886e-09, 0.5067111723130e+01, 0.4951538251678e+02,
+      0.5398937754482e-09, 0.5597382200075e+01, 0.1613385000004e+03,
+      0.5626247550193e-09, 0.2601338209422e+01, 0.7318837597844e+02,
+      0.5525197197855e-09, 0.5814832109256e+01, 0.1432335100216e+03,
+      0.5407629837898e-09, 0.3384820609076e+01, 0.3230491187871e+03,
+      0.3856739119801e-09, 0.1072391840473e+01, 0.2334791286671e+03,
+
+      0.3856425239987e-09, 0.2369540393327e+01, 0.1739046517013e+03,
+      0.4350867755983e-09, 0.5255575751082e+01, 0.1620484330494e+03,
+      0.3844113924996e-09, 0.5482356246182e+01, 0.9757644180768e+02,
+      0.2854869155431e-09, 0.9573634763143e+00, 0.1697170704744e+03,
+      0.1719227671416e-09, 0.1887203025202e+01, 0.2265204242912e+03,
+      0.1527846879755e-09, 0.3982183931157e+01, 0.3341954043900e+03,
+      0.1128229264847e-09, 0.2787457156298e+01, 0.3119028331842e+03 };
+
+/* Sun-to-Earth, T^1, X */
+   static const double e1x[] = {
+      0.1234046326004e-05, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.5150068824701e-06, 0.6002664557501e+01, 0.1256615170089e+02,
+      0.1290743923245e-07, 0.5959437664199e+01, 0.1884922755134e+02,
+      0.1068615564952e-07, 0.2015529654209e+01, 0.6283075850446e+01,
+      0.2079619142538e-08, 0.1732960531432e+01, 0.6279552690824e+01,
+      0.2078009243969e-08, 0.4915604476996e+01, 0.6286599010068e+01,
+      0.6206330058856e-09, 0.3616457953824e+00, 0.4705732307012e+01,
+      0.5989335313746e-09, 0.3802607304474e+01, 0.6256777527156e+01,
+      0.5958495663840e-09, 0.2845866560031e+01, 0.6309374173736e+01,
+      0.4866923261539e-09, 0.5213203771824e+01, 0.7755226100720e+00,
+
+      0.4267785823142e-09, 0.4368189727818e+00, 0.1059381944224e+01,
+      0.4610675141648e-09, 0.1837249181372e-01, 0.7860419393880e+01,
+      0.3626989993973e-09, 0.2161590545326e+01, 0.5753384878334e+01,
+      0.3563071194389e-09, 0.1452631954746e+01, 0.5884926831456e+01,
+      0.3557015642807e-09, 0.4470593393054e+01, 0.6812766822558e+01,
+      0.3210412089122e-09, 0.5195926078314e+01, 0.6681224869435e+01,
+      0.2875473577986e-09, 0.5916256610193e+01, 0.2513230340178e+02,
+      0.2842913681629e-09, 0.1149902426047e+01, 0.6127655567643e+01,
+      0.2751248215916e-09, 0.5502088574662e+01, 0.6438496133249e+01,
+      0.2481432881127e-09, 0.2921989846637e+01, 0.5486777812467e+01,
+
+      0.2059885976560e-09, 0.3718070376585e+01, 0.7079373888424e+01,
+      0.2015522342591e-09, 0.5979395259740e+01, 0.6290189305114e+01,
+      0.1995364084253e-09, 0.6772087985494e+00, 0.6275962395778e+01,
+      0.1957436436943e-09, 0.2899210654665e+01, 0.5507553240374e+01,
+      0.1651609818948e-09, 0.6228206482192e+01, 0.1150676975667e+02,
+      0.1822980550699e-09, 0.1469348746179e+01, 0.1179062909082e+02,
+      0.1675223159760e-09, 0.3813910555688e+01, 0.7058598460518e+01,
+      0.1706491764745e-09, 0.3004380506684e+00, 0.7113454667900e-02,
+      0.1392952362615e-09, 0.1440393973406e+01, 0.7962980379786e+00,
+      0.1209868266342e-09, 0.4150425791727e+01, 0.4694002934110e+01,
+
+      0.1009827202611e-09, 0.3290040429843e+01, 0.3738761453707e+01,
+      0.1047261388602e-09, 0.4229590090227e+01, 0.6282095334605e+01,
+      0.1047006652004e-09, 0.2418967680575e+01, 0.6284056366286e+01,
+      0.9609993143095e-10, 0.4627943659201e+01, 0.6069776770667e+01,
+      0.9590900593873e-10, 0.1894393939924e+01, 0.4136910472696e+01,
+      0.9146249188071e-10, 0.2010647519562e+01, 0.6496374930224e+01,
+      0.8545274480290e-10, 0.5529846956226e-01, 0.1194447056968e+01,
+      0.8224377881194e-10, 0.1254304102174e+01, 0.1589072916335e+01,
+      0.6183529510410e-10, 0.3360862168815e+01, 0.8827390247185e+01,
+      0.6259255147141e-10, 0.4755628243179e+01, 0.8429241228195e+01,
+
+      0.5539291694151e-10, 0.5371746955142e+01, 0.4933208510675e+01,
+      0.7328259466314e-10, 0.4927699613906e+00, 0.4535059491685e+01,
+      0.6017835843560e-10, 0.5776682001734e-01, 0.1255903824622e+02,
+      0.7079827775243e-10, 0.4395059432251e+01, 0.5088628793478e+01,
+      0.5170358878213e-10, 0.5154062619954e+01, 0.1176985366291e+02,
+      0.4872301838682e-10, 0.6289611648973e+00, 0.6040347114260e+01,
+      0.5249869411058e-10, 0.5617272046949e+01, 0.3154687086868e+01,
+      0.4716172354411e-10, 0.3965901800877e+01, 0.5331357529664e+01,
+      0.4871214940964e-10, 0.4627507050093e+01, 0.1256967486051e+02,
+      0.4598076850751e-10, 0.6023631226459e+01, 0.6525804586632e+01,
+
+      0.4562196089485e-10, 0.4138562084068e+01, 0.3930209696940e+01,
+      0.4325493872224e-10, 0.1330845906564e+01, 0.7632943190217e+01,
+      0.5673781176748e-10, 0.2558752615657e+01, 0.5729506548653e+01,
+      0.3961436642503e-10, 0.2728071734630e+01, 0.7234794171227e+01,
+      0.5101868209058e-10, 0.4113444965144e+01, 0.6836645152238e+01,
+      0.5257043167676e-10, 0.6195089830590e+01, 0.8031092209206e+01,
+      0.5076613989393e-10, 0.2305124132918e+01, 0.7477522907414e+01,
+      0.3342169352778e-10, 0.5415998155071e+01, 0.1097707878456e+02,
+      0.3545881983591e-10, 0.3727160564574e+01, 0.4164311961999e+01,
+      0.3364063738599e-10, 0.2901121049204e+00, 0.1137170464392e+02,
+
+      0.3357039670776e-10, 0.1652229354331e+01, 0.5223693906222e+01,
+      0.4307412268687e-10, 0.4938909587445e+01, 0.1592596075957e+01,
+      0.3405769115435e-10, 0.2408890766511e+01, 0.3128388763578e+01,
+      0.3001926198480e-10, 0.4862239006386e+01, 0.1748016358760e+01,
+      0.2778264787325e-10, 0.5241168661353e+01, 0.7342457794669e+01,
+      0.2676159480666e-10, 0.3423593942199e+01, 0.2146165377750e+01,
+      0.2954273399939e-10, 0.1881721265406e+01, 0.5368044267797e+00,
+      0.3309362888795e-10, 0.1931525677349e+01, 0.8018209333619e+00,
+      0.2810283608438e-10, 0.2414659495050e+01, 0.5225775174439e+00,
+      0.3378045637764e-10, 0.4238019163430e+01, 0.1554202828031e+00,
+
+      0.2558134979840e-10, 0.1828225235805e+01, 0.5230807360890e+01,
+      0.2273755578447e-10, 0.5858184283998e+01, 0.7084896783808e+01,
+      0.2294176037690e-10, 0.4514589779057e+01, 0.1726015463500e+02,
+      0.2533506099435e-10, 0.2355717851551e+01, 0.5216580451554e+01,
+      0.2716685375812e-10, 0.2221003625100e+01, 0.8635942003952e+01,
+      0.2419043435198e-10, 0.5955704951635e+01, 0.4690479774488e+01,
+      0.2521232544812e-10, 0.1395676848521e+01, 0.5481254917084e+01,
+      0.2630195021491e-10, 0.5727468918743e+01, 0.2629832328990e-01,
+      0.2548395840944e-10, 0.2628351859400e-03, 0.1349867339771e+01 };
+
+/* Sun-to-Earth, T^1, Y */
+   static const double e1y[] = {
+      0.9304690546528e-06, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.5150715570663e-06, 0.4431807116294e+01, 0.1256615170089e+02,
+      0.1290825411056e-07, 0.4388610039678e+01, 0.1884922755134e+02,
+      0.4645466665386e-08, 0.5827263376034e+01, 0.6283075850446e+01,
+      0.2079625310718e-08, 0.1621698662282e+00, 0.6279552690824e+01,
+      0.2078189850907e-08, 0.3344713435140e+01, 0.6286599010068e+01,
+      0.6207190138027e-09, 0.5074049319576e+01, 0.4705732307012e+01,
+      0.5989826532569e-09, 0.2231842216620e+01, 0.6256777527156e+01,
+      0.5961360812618e-09, 0.1274975769045e+01, 0.6309374173736e+01,
+      0.4874165471016e-09, 0.3642277426779e+01, 0.7755226100720e+00,
+
+      0.4283834034360e-09, 0.5148765510106e+01, 0.1059381944224e+01,
+      0.4652389287529e-09, 0.4715794792175e+01, 0.7860419393880e+01,
+      0.3751707476401e-09, 0.6617207370325e+00, 0.5753384878334e+01,
+      0.3559998806198e-09, 0.6155548875404e+01, 0.5884926831456e+01,
+      0.3558447558857e-09, 0.2898827297664e+01, 0.6812766822558e+01,
+      0.3211116927106e-09, 0.3625813502509e+01, 0.6681224869435e+01,
+      0.2875609914672e-09, 0.4345435813134e+01, 0.2513230340178e+02,
+      0.2843109704069e-09, 0.5862263940038e+01, 0.6127655567643e+01,
+      0.2744676468427e-09, 0.3926419475089e+01, 0.6438496133249e+01,
+      0.2481285237789e-09, 0.1351976572828e+01, 0.5486777812467e+01,
+
+      0.2060338481033e-09, 0.2147556998591e+01, 0.7079373888424e+01,
+      0.2015822358331e-09, 0.4408358972216e+01, 0.6290189305114e+01,
+      0.2001195944195e-09, 0.5385829822531e+01, 0.6275962395778e+01,
+      0.1953667642377e-09, 0.1304933746120e+01, 0.5507553240374e+01,
+      0.1839744078713e-09, 0.6173567228835e+01, 0.1179062909082e+02,
+      0.1643334294845e-09, 0.4635942997523e+01, 0.1150676975667e+02,
+      0.1768051018652e-09, 0.5086283558874e+01, 0.7113454667900e-02,
+      0.1674874205489e-09, 0.2243332137241e+01, 0.7058598460518e+01,
+      0.1421445397609e-09, 0.6186899771515e+01, 0.7962980379786e+00,
+      0.1255163958267e-09, 0.5730238465658e+01, 0.4694002934110e+01,
+
+      0.1013945281961e-09, 0.1726055228402e+01, 0.3738761453707e+01,
+      0.1047294335852e-09, 0.2658801228129e+01, 0.6282095334605e+01,
+      0.1047103879392e-09, 0.8481047835035e+00, 0.6284056366286e+01,
+      0.9530343962826e-10, 0.3079267149859e+01, 0.6069776770667e+01,
+      0.9604637611690e-10, 0.3258679792918e+00, 0.4136910472696e+01,
+      0.9153518537177e-10, 0.4398599886584e+00, 0.6496374930224e+01,
+      0.8562458214922e-10, 0.4772686794145e+01, 0.1194447056968e+01,
+      0.8232525360654e-10, 0.5966220721679e+01, 0.1589072916335e+01,
+      0.6150223411438e-10, 0.1780985591923e+01, 0.8827390247185e+01,
+      0.6272087858000e-10, 0.3184305429012e+01, 0.8429241228195e+01,
+
+      0.5540476311040e-10, 0.3801260595433e+01, 0.4933208510675e+01,
+      0.7331901699361e-10, 0.5205948591865e+01, 0.4535059491685e+01,
+      0.6018528702791e-10, 0.4770139083623e+01, 0.1255903824622e+02,
+      0.5150530724804e-10, 0.3574796899585e+01, 0.1176985366291e+02,
+      0.6471933741811e-10, 0.2679787266521e+01, 0.5088628793478e+01,
+      0.5317460644174e-10, 0.9528763345494e+00, 0.3154687086868e+01,
+      0.4832187748783e-10, 0.5329322498232e+01, 0.6040347114260e+01,
+      0.4716763555110e-10, 0.2395235316466e+01, 0.5331357529664e+01,
+      0.4871509139861e-10, 0.3056663648823e+01, 0.1256967486051e+02,
+      0.4598417696768e-10, 0.4452762609019e+01, 0.6525804586632e+01,
+
+      0.5674189533175e-10, 0.9879680872193e+00, 0.5729506548653e+01,
+      0.4073560328195e-10, 0.5939127696986e+01, 0.7632943190217e+01,
+      0.5040994945359e-10, 0.4549875824510e+01, 0.8031092209206e+01,
+      0.5078185134679e-10, 0.7346659893982e+00, 0.7477522907414e+01,
+      0.3769343537061e-10, 0.1071317188367e+01, 0.7234794171227e+01,
+      0.4980331365299e-10, 0.2500345341784e+01, 0.6836645152238e+01,
+      0.3458236594757e-10, 0.3825159450711e+01, 0.1097707878456e+02,
+      0.3578859493602e-10, 0.5299664791549e+01, 0.4164311961999e+01,
+      0.3370504646419e-10, 0.5002316301593e+01, 0.1137170464392e+02,
+      0.3299873338428e-10, 0.2526123275282e+01, 0.3930209696940e+01,
+
+      0.4304917318409e-10, 0.3368078557132e+01, 0.1592596075957e+01,
+      0.3402418753455e-10, 0.8385495425800e+00, 0.3128388763578e+01,
+      0.2778460572146e-10, 0.3669905203240e+01, 0.7342457794669e+01,
+      0.2782710128902e-10, 0.2691664812170e+00, 0.1748016358760e+01,
+      0.2711725179646e-10, 0.4707487217718e+01, 0.5296909721118e+00,
+      0.2981760946340e-10, 0.3190260867816e+00, 0.5368044267797e+00,
+      0.2811672977772e-10, 0.3196532315372e+01, 0.7084896783808e+01,
+      0.2863454474467e-10, 0.2263240324780e+00, 0.5223693906222e+01,
+      0.3333464634051e-10, 0.3498451685065e+01, 0.8018209333619e+00,
+      0.3312991747609e-10, 0.5839154477412e+01, 0.1554202828031e+00,
+
+      0.2813255564006e-10, 0.8268044346621e+00, 0.5225775174439e+00,
+      0.2665098083966e-10, 0.3934021725360e+01, 0.5216580451554e+01,
+      0.2349795705216e-10, 0.5197620913779e+01, 0.2146165377750e+01,
+      0.2330352293961e-10, 0.2984999231807e+01, 0.1726015463500e+02,
+      0.2728001683419e-10, 0.6521679638544e+00, 0.8635942003952e+01,
+      0.2484061007669e-10, 0.3468955561097e+01, 0.5230807360890e+01,
+      0.2646328768427e-10, 0.1013724533516e+01, 0.2629832328990e-01,
+      0.2518630264831e-10, 0.6108081057122e+01, 0.5481254917084e+01,
+      0.2421901455384e-10, 0.1651097776260e+01, 0.1349867339771e+01,
+      0.6348533267831e-11, 0.3220226560321e+01, 0.8433466158131e+02 };
+
+/* Sun-to-Earth, T^1, Z */
+   static const double e1z[] = {
+      0.2278290449966e-05, 0.3413716033863e+01, 0.6283075850446e+01,
+      0.5429458209830e-07, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.1903240492525e-07, 0.3370592358297e+01, 0.1256615170089e+02,
+      0.2385409276743e-09, 0.3327914718416e+01, 0.1884922755134e+02,
+      0.8676928342573e-10, 0.1824006811264e+01, 0.5223693906222e+01,
+      0.7765442593544e-10, 0.3888564279247e+01, 0.5507553240374e+01,
+      0.7066158332715e-10, 0.5194267231944e+01, 0.2352866153506e+01,
+      0.7092175288657e-10, 0.2333246960021e+01, 0.8399684731857e+02,
+      0.5357582213535e-10, 0.2224031176619e+01, 0.5296909721118e+00,
+      0.3828035865021e-10, 0.2156710933584e+01, 0.6279552690824e+01,
+
+      0.3824857220427e-10, 0.1529755219915e+01, 0.6286599010068e+01,
+      0.3286995181628e-10, 0.4879512900483e+01, 0.1021328554739e+02 };
+
+/* Sun-to-Earth, T^2, X */
+   static const double e2x[] = {
+     -0.4143818297913e-10, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.2171497694435e-10, 0.4398225628264e+01, 0.1256615170089e+02,
+      0.9845398442516e-11, 0.2079720838384e+00, 0.6283075850446e+01,
+      0.9256833552682e-12, 0.4191264694361e+01, 0.1884922755134e+02,
+      0.1022049384115e-12, 0.5381133195658e+01, 0.8399684731857e+02 };
+
+/* Sun-to-Earth, T^2, Y */
+   static const double e2y[] = {
+      0.5063375872532e-10, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.2173815785980e-10, 0.2827805833053e+01, 0.1256615170089e+02,
+      0.1010231999920e-10, 0.4634612377133e+01, 0.6283075850446e+01,
+      0.9259745317636e-12, 0.2620612076189e+01, 0.1884922755134e+02,
+      0.1022202095812e-12, 0.3809562326066e+01, 0.8399684731857e+02 };
+
+/* Sun-to-Earth, T^2, Z */
+   static const double e2z[] = {
+      0.9722666114891e-10, 0.5152219582658e+01, 0.6283075850446e+01,
+     -0.3494819171909e-11, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.6713034376076e-12, 0.6440188750495e+00, 0.1256615170089e+02 };
+
+/* SSB-to-Sun, T^0, X */
+   static const double s0x[] = {
+      0.4956757536410e-02, 0.3741073751789e+01, 0.5296909721118e+00,
+      0.2718490072522e-02, 0.4016011511425e+01, 0.2132990797783e+00,
+      0.1546493974344e-02, 0.2170528330642e+01, 0.3813291813120e-01,
+      0.8366855276341e-03, 0.2339614075294e+01, 0.7478166569050e-01,
+      0.2936777942117e-03, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.1201317439469e-03, 0.4090736353305e+01, 0.1059381944224e+01,
+      0.7578550887230e-04, 0.3241518088140e+01, 0.4265981595566e+00,
+      0.1941787367773e-04, 0.1012202064330e+01, 0.2061856251104e+00,
+      0.1889227765991e-04, 0.3892520416440e+01, 0.2204125344462e+00,
+      0.1937896968613e-04, 0.4797779441161e+01, 0.1495633313810e+00,
+
+      0.1434506110873e-04, 0.3868960697933e+01, 0.5225775174439e+00,
+      0.1406659911580e-04, 0.4759766557397e+00, 0.5368044267797e+00,
+      0.1179022300202e-04, 0.7774961520598e+00, 0.7626583626240e-01,
+      0.8085864460959e-05, 0.3254654471465e+01, 0.3664874755930e-01,
+      0.7622752967615e-05, 0.4227633103489e+01, 0.3961708870310e-01,
+      0.6209171139066e-05, 0.2791828325711e+00, 0.7329749511860e-01,
+      0.4366435633970e-05, 0.4440454875925e+01, 0.1589072916335e+01,
+      0.3792124889348e-05, 0.5156393842356e+01, 0.7113454667900e-02,
+      0.3154548963402e-05, 0.6157005730093e+01, 0.4194847048887e+00,
+      0.3088359882942e-05, 0.2494567553163e+01, 0.6398972393349e+00,
+
+      0.2788440902136e-05, 0.4934318747989e+01, 0.1102062672231e+00,
+      0.3039928456376e-05, 0.4895077702640e+01, 0.6283075850446e+01,
+      0.2272258457679e-05, 0.5278394064764e+01, 0.1030928125552e+00,
+      0.2162007057957e-05, 0.5802978019099e+01, 0.3163918923335e+00,
+      0.1767632855737e-05, 0.3415346595193e-01, 0.1021328554739e+02,
+      0.1349413459362e-05, 0.2001643230755e+01, 0.1484170571900e-02,
+      0.1170141900476e-05, 0.2424750491620e+01, 0.6327837846670e+00,
+      0.1054355266820e-05, 0.3123311487576e+01, 0.4337116142245e+00,
+      0.9800822461610e-06, 0.3026258088130e+01, 0.1052268489556e+01,
+      0.1091203749931e-05, 0.3157811670347e+01, 0.1162474756779e+01,
+
+      0.6960236715913e-06, 0.8219570542313e+00, 0.1066495398892e+01,
+      0.5689257296909e-06, 0.1323052375236e+01, 0.9491756770005e+00,
+      0.6613172135802e-06, 0.2765348881598e+00, 0.8460828644453e+00,
+      0.6277702517571e-06, 0.5794064466382e+01, 0.1480791608091e+00,
+      0.6304884066699e-06, 0.7323555380787e+00, 0.2243449970715e+00,
+      0.4897850467382e-06, 0.3062464235399e+01, 0.3340612434717e+01,
+      0.3759148598786e-06, 0.4588290469664e+01, 0.3516457698740e-01,
+      0.3110520548195e-06, 0.1374299536572e+01, 0.6373574839730e-01,
+      0.3064708359780e-06, 0.4222267485047e+01, 0.1104591729320e-01,
+      0.2856347168241e-06, 0.3714202944973e+01, 0.1510475019529e+00,
+
+      0.2840945514288e-06, 0.2847972875882e+01, 0.4110125927500e-01,
+      0.2378951599405e-06, 0.3762072563388e+01, 0.2275259891141e+00,
+      0.2714229481417e-06, 0.1036049980031e+01, 0.2535050500000e-01,
+      0.2323551717307e-06, 0.4682388599076e+00, 0.8582758298370e-01,
+      0.1881790512219e-06, 0.4790565425418e+01, 0.2118763888447e+01,
+      0.2261353968371e-06, 0.1669144912212e+01, 0.7181332454670e-01,
+      0.2214546389848e-06, 0.3937717281614e+01, 0.2968341143800e-02,
+      0.2184915594933e-06, 0.1129169845099e+00, 0.7775000683430e-01,
+      0.2000164937936e-06, 0.4030009638488e+01, 0.2093666171530e+00,
+      0.1966105136719e-06, 0.8745955786834e+00, 0.2172315424036e+00,
+
+      0.1904742332624e-06, 0.5919743598964e+01, 0.2022531624851e+00,
+      0.1657399705031e-06, 0.2549141484884e+01, 0.7358765972222e+00,
+      0.1574070533987e-06, 0.5277533020230e+01, 0.7429900518901e+00,
+      0.1832261651039e-06, 0.3064688127777e+01, 0.3235053470014e+00,
+      0.1733615346569e-06, 0.3011432799094e+01, 0.1385174140878e+00,
+      0.1549124014496e-06, 0.4005569132359e+01, 0.5154640627760e+00,
+      0.1637044713838e-06, 0.1831375966632e+01, 0.8531963191132e+00,
+      0.1123420082383e-06, 0.1180270407578e+01, 0.1990721704425e+00,
+      0.1083754165740e-06, 0.3414101320863e+00, 0.5439178814476e+00,
+      0.1156638012655e-06, 0.6130479452594e+00, 0.5257585094865e+00,
+
+      0.1142548785134e-06, 0.3724761948846e+01, 0.5336234347371e+00,
+      0.7921463895965e-07, 0.2435425589361e+01, 0.1478866649112e+01,
+      0.7428600285231e-07, 0.3542144398753e+01, 0.2164800718209e+00,
+      0.8323211246747e-07, 0.3525058072354e+01, 0.1692165728891e+01,
+      0.7257595116312e-07, 0.1364299431982e+01, 0.2101180877357e+00,
+      0.7111185833236e-07, 0.2460478875808e+01, 0.4155522422634e+00,
+      0.6868090383716e-07, 0.4397327670704e+01, 0.1173197218910e+00,
+      0.7226419974175e-07, 0.4042647308905e+01, 0.1265567569334e+01,
+      0.6955642383177e-07, 0.2865047906085e+01, 0.9562891316684e+00,
+      0.7492139296331e-07, 0.5014278994215e+01, 0.1422690933580e-01,
+
+      0.6598363128857e-07, 0.2376730020492e+01, 0.6470106940028e+00,
+      0.7381147293385e-07, 0.3272990384244e+01, 0.1581959461667e+01,
+      0.6402909624032e-07, 0.5302290955138e+01, 0.9597935788730e-01,
+      0.6237454263857e-07, 0.5444144425332e+01, 0.7084920306520e-01,
+      0.5241198544016e-07, 0.4215359579205e+01, 0.5265099800692e+00,
+      0.5144463853918e-07, 0.1218916689916e+00, 0.5328719641544e+00,
+      0.5868164772299e-07, 0.2369402002213e+01, 0.7871412831580e-01,
+      0.6233195669151e-07, 0.1254922242403e+01, 0.2608790314060e+02,
+      0.6068463791422e-07, 0.5679713760431e+01, 0.1114304132498e+00,
+      0.4359361135065e-07, 0.6097219641646e+00, 0.1375773836557e+01,
+
+      0.4686510366826e-07, 0.4786231041431e+01, 0.1143987543936e+00,
+      0.3758977287225e-07, 0.1167368068139e+01, 0.1596186371003e+01,
+      0.4282051974778e-07, 0.1519471064319e+01, 0.2770348281756e+00,
+      0.5153765386113e-07, 0.1860532322984e+01, 0.2228608264996e+00,
+      0.4575129387188e-07, 0.7632857887158e+00, 0.1465949902372e+00,
+      0.3326844933286e-07, 0.1298219485285e+01, 0.5070101000000e-01,
+      0.3748617450984e-07, 0.1046510321062e+01, 0.4903339079539e+00,
+      0.2816756661499e-07, 0.3434522346190e+01, 0.2991266627620e+00,
+      0.3412750405039e-07, 0.2523766270318e+01, 0.3518164938661e+00,
+      0.2655796761776e-07, 0.2904422260194e+01, 0.6256703299991e+00,
+
+      0.2963597929458e-07, 0.5923900431149e+00, 0.1099462426779e+00,
+      0.2539523734781e-07, 0.4851947722567e+01, 0.1256615170089e+02,
+      0.2283087914139e-07, 0.3400498595496e+01, 0.6681224869435e+01,
+      0.2321309799331e-07, 0.5789099148673e+01, 0.3368040641550e-01,
+      0.2549657649750e-07, 0.3991856479792e-01, 0.1169588211447e+01,
+      0.2290462303977e-07, 0.2788567577052e+01, 0.1045155034888e+01,
+      0.1945398522914e-07, 0.3290896998176e+01, 0.1155361302111e+01,
+      0.1849171512638e-07, 0.2698060129367e+01, 0.4452511715700e-02,
+      0.1647199834254e-07, 0.3016735644085e+01, 0.4408250688924e+00,
+      0.1529530765273e-07, 0.5573043116178e+01, 0.6521991896920e-01,
+
+      0.1433199339978e-07, 0.1481192356147e+01, 0.9420622223326e+00,
+      0.1729134193602e-07, 0.1422817538933e+01, 0.2108507877249e+00,
+      0.1716463931346e-07, 0.3469468901855e+01, 0.2157473718317e+00,
+      0.1391206061378e-07, 0.6122436220547e+01, 0.4123712502208e+00,
+      0.1404746661924e-07, 0.1647765641936e+01, 0.4258542984690e-01,
+      0.1410452399455e-07, 0.5989729161964e+01, 0.2258291676434e+00,
+      0.1089828772168e-07, 0.2833705509371e+01, 0.4226656969313e+00,
+      0.1047374564948e-07, 0.5090690007331e+00, 0.3092784376656e+00,
+      0.1358279126532e-07, 0.5128990262836e+01, 0.7923417740620e-01,
+      0.1020456476148e-07, 0.9632772880808e+00, 0.1456308687557e+00,
+
+      0.1033428735328e-07, 0.3223779318418e+01, 0.1795258541446e+01,
+      0.1412435841540e-07, 0.2410271572721e+01, 0.1525316725248e+00,
+      0.9722759371574e-08, 0.2333531395690e+01, 0.8434341241180e-01,
+      0.9657334084704e-08, 0.6199270974168e+01, 0.1272681024002e+01,
+      0.1083641148690e-07, 0.2864222292929e+01, 0.7032915397480e-01,
+      0.1067318403838e-07, 0.5833458866568e+00, 0.2123349582968e+00,
+      0.1062366201976e-07, 0.4307753989494e+01, 0.2142632012598e+00,
+      0.1236364149266e-07, 0.2873917870593e+01, 0.1847279083684e+00,
+      0.1092759489593e-07, 0.2959887266733e+01, 0.1370332435159e+00,
+      0.8912069362899e-08, 0.5141213702562e+01, 0.2648454860559e+01,
+
+      0.9656467707970e-08, 0.4532182462323e+01, 0.4376440768498e+00,
+      0.8098386150135e-08, 0.2268906338379e+01, 0.2880807454688e+00,
+      0.7857714675000e-08, 0.4055544260745e+01, 0.2037373330570e+00,
+      0.7288455940646e-08, 0.5357901655142e+01, 0.1129145838217e+00,
+      0.9450595950552e-08, 0.4264926963939e+01, 0.5272426800584e+00,
+      0.9381718247537e-08, 0.7489366976576e-01, 0.5321392641652e+00,
+      0.7079052646038e-08, 0.1923311052874e+01, 0.6288513220417e+00,
+      0.9259004415344e-08, 0.2970256853438e+01, 0.1606092486742e+00,
+      0.8259801499742e-08, 0.3327056314697e+01, 0.8389694097774e+00,
+      0.6476334355779e-08, 0.2954925505727e+01, 0.2008557621224e+01,
+
+      0.5984021492007e-08, 0.9138753105829e+00, 0.2042657109477e+02,
+      0.5989546863181e-08, 0.3244464082031e+01, 0.2111650433779e+01,
+      0.6233108606023e-08, 0.4995232638403e+00, 0.4305306221819e+00,
+      0.6877299149965e-08, 0.2834987233449e+01, 0.9561746721300e-02,
+      0.8311234227190e-08, 0.2202951835758e+01, 0.3801276407308e+00,
+      0.6599472832414e-08, 0.4478581462618e+01, 0.1063314406849e+01,
+      0.6160491096549e-08, 0.5145858696411e+01, 0.1368660381889e+01,
+      0.6164772043891e-08, 0.3762976697911e+00, 0.4234171675140e+00,
+      0.6363248684450e-08, 0.3162246718685e+01, 0.1253008786510e-01,
+      0.6448587520999e-08, 0.3442693302119e+01, 0.5287268506303e+00,
+
+      0.6431662283977e-08, 0.8977549136606e+00, 0.5306550935933e+00,
+      0.6351223158474e-08, 0.4306447410369e+01, 0.5217580628120e+02,
+      0.5476721393451e-08, 0.3888529177855e+01, 0.2221856701002e+01,
+      0.5341772572619e-08, 0.2655560662512e+01, 0.7466759693650e-01,
+      0.5337055758302e-08, 0.5164990735946e+01, 0.7489573444450e-01,
+      0.5373120816787e-08, 0.6041214553456e+01, 0.1274714967946e+00,
+      0.5392351705426e-08, 0.9177763485932e+00, 0.1055449481598e+01,
+      0.6688495850205e-08, 0.3089608126937e+01, 0.2213766559277e+00,
+      0.5072003660362e-08, 0.4311316541553e+01, 0.2132517061319e+00,
+      0.5070726650455e-08, 0.5790675464444e+00, 0.2133464534247e+00,
+
+      0.5658012950032e-08, 0.2703945510675e+01, 0.7287631425543e+00,
+      0.4835509924854e-08, 0.2975422976065e+01, 0.7160067364790e-01,
+      0.6479821978012e-08, 0.1324168733114e+01, 0.2209183458640e-01,
+      0.6230636494980e-08, 0.2860103632836e+01, 0.3306188016693e+00,
+      0.4649239516213e-08, 0.4832259763403e+01, 0.7796265773310e-01,
+      0.6487325792700e-08, 0.2726165825042e+01, 0.3884652414254e+00,
+      0.4682823682770e-08, 0.6966602455408e+00, 0.1073608853559e+01,
+      0.5704230804976e-08, 0.5669634104606e+01, 0.8731175355560e-01,
+      0.6125413585489e-08, 0.1513386538915e+01, 0.7605151500000e-01,
+      0.6035825038187e-08, 0.1983509168227e+01, 0.9846002785331e+00,
+
+      0.4331123462303e-08, 0.2782892992807e+01, 0.4297791515992e+00,
+      0.4681107685143e-08, 0.5337232886836e+01, 0.2127790306879e+00,
+      0.4669105829655e-08, 0.5837133792160e+01, 0.2138191288687e+00,
+      0.5138823602365e-08, 0.3080560200507e+01, 0.7233337363710e-01,
+      0.4615856664534e-08, 0.1661747897471e+01, 0.8603097737811e+00,
+      0.4496916702197e-08, 0.2112508027068e+01, 0.7381754420900e-01,
+      0.4278479042945e-08, 0.5716528462627e+01, 0.7574578717200e-01,
+      0.3840525503932e-08, 0.6424172726492e+00, 0.3407705765729e+00,
+      0.4866636509685e-08, 0.4919244697715e+01, 0.7722995774390e-01,
+      0.3526100639296e-08, 0.2550821052734e+01, 0.6225157782540e-01,
+
+      0.3939558488075e-08, 0.3939331491710e+01, 0.5268983110410e-01,
+      0.4041268772576e-08, 0.2275337571218e+01, 0.3503323232942e+00,
+      0.3948761842853e-08, 0.1999324200790e+01, 0.1451108196653e+00,
+      0.3258394550029e-08, 0.9121001378200e+00, 0.5296435984654e+00,
+      0.3257897048761e-08, 0.3428428660869e+01, 0.5297383457582e+00,
+      0.3842559031298e-08, 0.6132927720035e+01, 0.9098186128426e+00,
+      0.3109920095448e-08, 0.7693650193003e+00, 0.3932462625300e-02,
+      0.3132237775119e-08, 0.3621293854908e+01, 0.2346394437820e+00,
+      0.3942189421510e-08, 0.4841863659733e+01, 0.3180992042600e-02,
+      0.3796972285340e-08, 0.1814174994268e+01, 0.1862120789403e+00,
+
+      0.3995640233688e-08, 0.1386990406091e+01, 0.4549093064213e+00,
+      0.2875013727414e-08, 0.9178318587177e+00, 0.1905464808669e+01,
+      0.3073719932844e-08, 0.2688923811835e+01, 0.3628624111593e+00,
+      0.2731016580075e-08, 0.1188259127584e+01, 0.2131850110243e+00,
+      0.2729549896546e-08, 0.3702160634273e+01, 0.2134131485323e+00,
+      0.3339372892449e-08, 0.7199163960331e+00, 0.2007689919132e+00,
+      0.2898833764204e-08, 0.1916709364999e+01, 0.5291709230214e+00,
+      0.2894536549362e-08, 0.2424043195547e+01, 0.5302110212022e+00,
+      0.3096872473843e-08, 0.4445894977497e+01, 0.2976424921901e+00,
+      0.2635672326810e-08, 0.3814366984117e+01, 0.1485980103780e+01,
+
+      0.3649302697001e-08, 0.2924200596084e+01, 0.6044726378023e+00,
+      0.3127954585895e-08, 0.1842251648327e+01, 0.1084620721060e+00,
+      0.2616040173947e-08, 0.4155841921984e+01, 0.1258454114666e+01,
+      0.2597395859860e-08, 0.1158045978874e+00, 0.2103781122809e+00,
+      0.2593286172210e-08, 0.4771850408691e+01, 0.2162200472757e+00,
+      0.2481823585747e-08, 0.4608842558889e+00, 0.1062562936266e+01,
+      0.2742219550725e-08, 0.1538781127028e+01, 0.5651155736444e+00,
+      0.3199558469610e-08, 0.3226647822878e+00, 0.7036329877322e+00,
+      0.2666088542957e-08, 0.1967991731219e+00, 0.1400015846597e+00,
+      0.2397067430580e-08, 0.3707036669873e+01, 0.2125476091956e+00,
+
+      0.2376570772738e-08, 0.1182086628042e+01, 0.2140505503610e+00,
+      0.2547228007887e-08, 0.4906256820629e+01, 0.1534957940063e+00,
+      0.2265575594114e-08, 0.3414949866857e+01, 0.2235935264888e+00,
+      0.2464381430585e-08, 0.4599122275378e+01, 0.2091065926078e+00,
+      0.2433408527044e-08, 0.2830751145445e+00, 0.2174915669488e+00,
+      0.2443605509076e-08, 0.4212046432538e+01, 0.1739420156204e+00,
+      0.2319779262465e-08, 0.9881978408630e+00, 0.7530171478090e-01,
+      0.2284622835465e-08, 0.5565347331588e+00, 0.7426161660010e-01,
+      0.2467268750783e-08, 0.5655708150766e+00, 0.2526561439362e+00,
+      0.2808513492782e-08, 0.1418405053408e+01, 0.5636314030725e+00,
+
+      0.2329528932532e-08, 0.4069557545675e+01, 0.1056200952181e+01,
+      0.9698639532817e-09, 0.1074134313634e+01, 0.7826370942180e+02 };
+
+/* SSB-to-Sun, T^0, Y */
+   static const double s0y[] = {
+      0.4955392320126e-02, 0.2170467313679e+01, 0.5296909721118e+00,
+      0.2722325167392e-02, 0.2444433682196e+01, 0.2132990797783e+00,
+      0.1546579925346e-02, 0.5992779281546e+00, 0.3813291813120e-01,
+      0.8363140252966e-03, 0.7687356310801e+00, 0.7478166569050e-01,
+      0.3385792683603e-03, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.1201192221613e-03, 0.2520035601514e+01, 0.1059381944224e+01,
+      0.7587125720554e-04, 0.1669954006449e+01, 0.4265981595566e+00,
+      0.1964155361250e-04, 0.5707743963343e+01, 0.2061856251104e+00,
+      0.1891900364909e-04, 0.2320960679937e+01, 0.2204125344462e+00,
+      0.1937373433356e-04, 0.3226940689555e+01, 0.1495633313810e+00,
+
+      0.1437139941351e-04, 0.2301626908096e+01, 0.5225775174439e+00,
+      0.1406267683099e-04, 0.5188579265542e+01, 0.5368044267797e+00,
+      0.1178703080346e-04, 0.5489483248476e+01, 0.7626583626240e-01,
+      0.8079835186041e-05, 0.1683751835264e+01, 0.3664874755930e-01,
+      0.7623253594652e-05, 0.2656400462961e+01, 0.3961708870310e-01,
+      0.6248667483971e-05, 0.4992775362055e+01, 0.7329749511860e-01,
+      0.4366353695038e-05, 0.2869706279678e+01, 0.1589072916335e+01,
+      0.3829101568895e-05, 0.3572131359950e+01, 0.7113454667900e-02,
+      0.3175733773908e-05, 0.4535372530045e+01, 0.4194847048887e+00,
+      0.3092437902159e-05, 0.9230153317909e+00, 0.6398972393349e+00,
+
+      0.2874168812154e-05, 0.3363143761101e+01, 0.1102062672231e+00,
+      0.3040119321826e-05, 0.3324250895675e+01, 0.6283075850446e+01,
+      0.2699723308006e-05, 0.2917882441928e+00, 0.1030928125552e+00,
+      0.2134832683534e-05, 0.4220997202487e+01, 0.3163918923335e+00,
+      0.1770412139433e-05, 0.4747318496462e+01, 0.1021328554739e+02,
+      0.1377264209373e-05, 0.4305058462401e+00, 0.1484170571900e-02,
+      0.1127814538960e-05, 0.8538177240740e+00, 0.6327837846670e+00,
+      0.1055608090130e-05, 0.1551800742580e+01, 0.4337116142245e+00,
+      0.9802673861420e-06, 0.1459646735377e+01, 0.1052268489556e+01,
+      0.1090329461951e-05, 0.1587351228711e+01, 0.1162474756779e+01,
+
+      0.6959590025090e-06, 0.5534442628766e+01, 0.1066495398892e+01,
+      0.5664914529542e-06, 0.6030673003297e+01, 0.9491756770005e+00,
+      0.6607787763599e-06, 0.4989507233927e+01, 0.8460828644453e+00,
+      0.6269725742838e-06, 0.4222951804572e+01, 0.1480791608091e+00,
+      0.6301889697863e-06, 0.5444316669126e+01, 0.2243449970715e+00,
+      0.4891042662861e-06, 0.1490552839784e+01, 0.3340612434717e+01,
+      0.3457083123290e-06, 0.3030475486049e+01, 0.3516457698740e-01,
+      0.3032559967314e-06, 0.2652038793632e+01, 0.1104591729320e-01,
+      0.2841133988903e-06, 0.1276744786829e+01, 0.4110125927500e-01,
+      0.2855564444432e-06, 0.2143368674733e+01, 0.1510475019529e+00,
+
+      0.2765157135038e-06, 0.5444186109077e+01, 0.6373574839730e-01,
+      0.2382312465034e-06, 0.2190521137593e+01, 0.2275259891141e+00,
+      0.2808060365077e-06, 0.5735195064841e+01, 0.2535050500000e-01,
+      0.2332175234405e-06, 0.9481985524859e-01, 0.7181332454670e-01,
+      0.2322488199659e-06, 0.5180499361533e+01, 0.8582758298370e-01,
+      0.1881850258423e-06, 0.3219788273885e+01, 0.2118763888447e+01,
+      0.2196111392808e-06, 0.2366941159761e+01, 0.2968341143800e-02,
+      0.2183810335519e-06, 0.4825445110915e+01, 0.7775000683430e-01,
+      0.2002733093326e-06, 0.2457148995307e+01, 0.2093666171530e+00,
+      0.1967111767229e-06, 0.5586291545459e+01, 0.2172315424036e+00,
+
+      0.1568473250543e-06, 0.3708003123320e+01, 0.7429900518901e+00,
+      0.1852528314300e-06, 0.4310638151560e+01, 0.2022531624851e+00,
+      0.1832111226447e-06, 0.1494665322656e+01, 0.3235053470014e+00,
+      0.1746805502310e-06, 0.1451378500784e+01, 0.1385174140878e+00,
+      0.1555730966650e-06, 0.1068040418198e+01, 0.7358765972222e+00,
+      0.1554883462559e-06, 0.2442579035461e+01, 0.5154640627760e+00,
+      0.1638380568746e-06, 0.2597913420625e+00, 0.8531963191132e+00,
+      0.1159938593640e-06, 0.5834512021280e+01, 0.1990721704425e+00,
+      0.1083427965695e-06, 0.5054033177950e+01, 0.5439178814476e+00,
+      0.1156480369431e-06, 0.5325677432457e+01, 0.5257585094865e+00,
+
+      0.1141308860095e-06, 0.2153403923857e+01, 0.5336234347371e+00,
+      0.7913146470946e-07, 0.8642846847027e+00, 0.1478866649112e+01,
+      0.7439752463733e-07, 0.1970628496213e+01, 0.2164800718209e+00,
+      0.7280277104079e-07, 0.6073307250609e+01, 0.2101180877357e+00,
+      0.8319567719136e-07, 0.1954371928334e+01, 0.1692165728891e+01,
+      0.7137705549290e-07, 0.8904989440909e+00, 0.4155522422634e+00,
+      0.6900825396225e-07, 0.2825717714977e+01, 0.1173197218910e+00,
+      0.7245757216635e-07, 0.2481677513331e+01, 0.1265567569334e+01,
+      0.6961165696255e-07, 0.1292955312978e+01, 0.9562891316684e+00,
+      0.7571804456890e-07, 0.3427517575069e+01, 0.1422690933580e-01,
+
+      0.6605425721904e-07, 0.8052192701492e+00, 0.6470106940028e+00,
+      0.7375477357248e-07, 0.1705076390088e+01, 0.1581959461667e+01,
+      0.7041664951470e-07, 0.4848356967891e+00, 0.9597935788730e-01,
+      0.6322199535763e-07, 0.3878069473909e+01, 0.7084920306520e-01,
+      0.5244380279191e-07, 0.2645560544125e+01, 0.5265099800692e+00,
+      0.5143125704988e-07, 0.4834486101370e+01, 0.5328719641544e+00,
+      0.5871866319373e-07, 0.7981472548900e+00, 0.7871412831580e-01,
+      0.6300822573871e-07, 0.5979398788281e+01, 0.2608790314060e+02,
+      0.6062154271548e-07, 0.4108655402756e+01, 0.1114304132498e+00,
+      0.4361912339976e-07, 0.5322624319280e+01, 0.1375773836557e+01,
+
+      0.4417005920067e-07, 0.6240817359284e+01, 0.2770348281756e+00,
+      0.4686806749936e-07, 0.3214977301156e+01, 0.1143987543936e+00,
+      0.3758892132305e-07, 0.5879809634765e+01, 0.1596186371003e+01,
+      0.5151351332319e-07, 0.2893377688007e+00, 0.2228608264996e+00,
+      0.4554683578572e-07, 0.5475427144122e+01, 0.1465949902372e+00,
+      0.3442381385338e-07, 0.5992034796640e+01, 0.5070101000000e-01,
+      0.2831093954933e-07, 0.5367350273914e+01, 0.3092784376656e+00,
+      0.3756267090084e-07, 0.5758171285420e+01, 0.4903339079539e+00,
+      0.2816374679892e-07, 0.1863718700923e+01, 0.2991266627620e+00,
+      0.3419307025569e-07, 0.9524347534130e+00, 0.3518164938661e+00,
+
+      0.2904250494239e-07, 0.5304471615602e+01, 0.1099462426779e+00,
+      0.2471734511206e-07, 0.1297069793530e+01, 0.6256703299991e+00,
+      0.2539620831872e-07, 0.3281126083375e+01, 0.1256615170089e+02,
+      0.2281017868007e-07, 0.1829122133165e+01, 0.6681224869435e+01,
+      0.2275319473335e-07, 0.5797198160181e+01, 0.3932462625300e-02,
+      0.2547755368442e-07, 0.4752697708330e+01, 0.1169588211447e+01,
+      0.2285979669317e-07, 0.1223205292886e+01, 0.1045155034888e+01,
+      0.1913386560994e-07, 0.1757532993389e+01, 0.1155361302111e+01,
+      0.1809020525147e-07, 0.4246116108791e+01, 0.3368040641550e-01,
+      0.1649213300201e-07, 0.1445162890627e+01, 0.4408250688924e+00,
+
+      0.1834972793932e-07, 0.1126917567225e+01, 0.4452511715700e-02,
+      0.1439550648138e-07, 0.6160756834764e+01, 0.9420622223326e+00,
+      0.1487645457041e-07, 0.4358761931792e+01, 0.4123712502208e+00,
+      0.1731729516660e-07, 0.6134456753344e+01, 0.2108507877249e+00,
+      0.1717747163567e-07, 0.1898186084455e+01, 0.2157473718317e+00,
+      0.1418190430374e-07, 0.4180286741266e+01, 0.6521991896920e-01,
+      0.1404844134873e-07, 0.7654053565412e-01, 0.4258542984690e-01,
+      0.1409842846538e-07, 0.4418612420312e+01, 0.2258291676434e+00,
+      0.1090948346291e-07, 0.1260615686131e+01, 0.4226656969313e+00,
+      0.1357577323612e-07, 0.3558248818690e+01, 0.7923417740620e-01,
+
+      0.1018154061960e-07, 0.5676087241256e+01, 0.1456308687557e+00,
+      0.1412073972109e-07, 0.8394392632422e+00, 0.1525316725248e+00,
+      0.1030938326496e-07, 0.1653593274064e+01, 0.1795258541446e+01,
+      0.1180081567104e-07, 0.1285802592036e+01, 0.7032915397480e-01,
+      0.9708510575650e-08, 0.7631889488106e+00, 0.8434341241180e-01,
+      0.9637689663447e-08, 0.4630642649176e+01, 0.1272681024002e+01,
+      0.1068910429389e-07, 0.5294934032165e+01, 0.2123349582968e+00,
+      0.1063716179336e-07, 0.2736266800832e+01, 0.2142632012598e+00,
+      0.1234858713814e-07, 0.1302891146570e+01, 0.1847279083684e+00,
+      0.8912631189738e-08, 0.3570415993621e+01, 0.2648454860559e+01,
+
+      0.1036378285534e-07, 0.4236693440949e+01, 0.1370332435159e+00,
+      0.9667798501561e-08, 0.2960768892398e+01, 0.4376440768498e+00,
+      0.8108314201902e-08, 0.6987781646841e+00, 0.2880807454688e+00,
+      0.7648364324628e-08, 0.2499017863863e+01, 0.2037373330570e+00,
+      0.7286136828406e-08, 0.3787426951665e+01, 0.1129145838217e+00,
+      0.9448237743913e-08, 0.2694354332983e+01, 0.5272426800584e+00,
+      0.9374276106428e-08, 0.4787121277064e+01, 0.5321392641652e+00,
+      0.7100226287462e-08, 0.3530238792101e+00, 0.6288513220417e+00,
+      0.9253056659571e-08, 0.1399478925664e+01, 0.1606092486742e+00,
+      0.6636432145504e-08, 0.3479575438447e+01, 0.1368660381889e+01,
+
+      0.6469975312932e-08, 0.1383669964800e+01, 0.2008557621224e+01,
+      0.7335849729765e-08, 0.1243698166898e+01, 0.9561746721300e-02,
+      0.8743421205855e-08, 0.3776164289301e+01, 0.3801276407308e+00,
+      0.5993635744494e-08, 0.5627122113596e+01, 0.2042657109477e+02,
+      0.5981008479693e-08, 0.1674336636752e+01, 0.2111650433779e+01,
+      0.6188535145838e-08, 0.5214925208672e+01, 0.4305306221819e+00,
+      0.6596074017566e-08, 0.2907653268124e+01, 0.1063314406849e+01,
+      0.6630815126226e-08, 0.2127643669658e+01, 0.8389694097774e+00,
+      0.6156772830040e-08, 0.5082160803295e+01, 0.4234171675140e+00,
+      0.6446960563014e-08, 0.1872100916905e+01, 0.5287268506303e+00,
+
+      0.6429324424668e-08, 0.5610276103577e+01, 0.5306550935933e+00,
+      0.6302232396465e-08, 0.1592152049607e+01, 0.1253008786510e-01,
+      0.6399244436159e-08, 0.2746214421532e+01, 0.5217580628120e+02,
+      0.5474965172558e-08, 0.2317666374383e+01, 0.2221856701002e+01,
+      0.5339293190692e-08, 0.1084724961156e+01, 0.7466759693650e-01,
+      0.5334733683389e-08, 0.3594106067745e+01, 0.7489573444450e-01,
+      0.5392665782110e-08, 0.5630254365606e+01, 0.1055449481598e+01,
+      0.6682075673789e-08, 0.1518480041732e+01, 0.2213766559277e+00,
+      0.5079130495960e-08, 0.2739765115711e+01, 0.2132517061319e+00,
+      0.5077759793261e-08, 0.5290711290094e+01, 0.2133464534247e+00,
+
+      0.4832037368310e-08, 0.1404473217200e+01, 0.7160067364790e-01,
+      0.6463279674802e-08, 0.6038381695210e+01, 0.2209183458640e-01,
+      0.6240592771560e-08, 0.1290170653666e+01, 0.3306188016693e+00,
+      0.4672013521493e-08, 0.3261895939677e+01, 0.7796265773310e-01,
+      0.6500650750348e-08, 0.1154522312095e+01, 0.3884652414254e+00,
+      0.6344161389053e-08, 0.6206111545062e+01, 0.7605151500000e-01,
+      0.4682518370646e-08, 0.5409118796685e+01, 0.1073608853559e+01,
+      0.5329460015591e-08, 0.1202985784864e+01, 0.7287631425543e+00,
+      0.5701588675898e-08, 0.4098715257064e+01, 0.8731175355560e-01,
+      0.6030690867211e-08, 0.4132033218460e+00, 0.9846002785331e+00,
+
+      0.4336256312655e-08, 0.1211415991827e+01, 0.4297791515992e+00,
+      0.4688498808975e-08, 0.3765479072409e+01, 0.2127790306879e+00,
+      0.4675578609335e-08, 0.4265540037226e+01, 0.2138191288687e+00,
+      0.4225578112158e-08, 0.5237566010676e+01, 0.3407705765729e+00,
+      0.5139422230028e-08, 0.1507173079513e+01, 0.7233337363710e-01,
+      0.4619995093571e-08, 0.9023957449848e-01, 0.8603097737811e+00,
+      0.4494776255461e-08, 0.5414930552139e+00, 0.7381754420900e-01,
+      0.4274026276788e-08, 0.4145735303659e+01, 0.7574578717200e-01,
+      0.5018141789353e-08, 0.3344408829055e+01, 0.3180992042600e-02,
+      0.4866163952181e-08, 0.3348534657607e+01, 0.7722995774390e-01,
+
+      0.4111986020501e-08, 0.4198823597220e+00, 0.1451108196653e+00,
+      0.3356142784950e-08, 0.5609144747180e+01, 0.1274714967946e+00,
+      0.4070575554551e-08, 0.7028411059224e+00, 0.3503323232942e+00,
+      0.3257451857278e-08, 0.5624697983086e+01, 0.5296435984654e+00,
+      0.3256973703026e-08, 0.1857842076707e+01, 0.5297383457582e+00,
+      0.3830771508640e-08, 0.4562887279931e+01, 0.9098186128426e+00,
+      0.3725024005962e-08, 0.2358058692652e+00, 0.1084620721060e+00,
+      0.3136763921756e-08, 0.2049731526845e+01, 0.2346394437820e+00,
+      0.3795147256194e-08, 0.2432356296933e+00, 0.1862120789403e+00,
+      0.2877342229911e-08, 0.5631101279387e+01, 0.1905464808669e+01,
+
+      0.3076931798805e-08, 0.1117615737392e+01, 0.3628624111593e+00,
+      0.2734765945273e-08, 0.5899826516955e+01, 0.2131850110243e+00,
+      0.2733405296885e-08, 0.2130562964070e+01, 0.2134131485323e+00,
+      0.2898552353410e-08, 0.3462387048225e+00, 0.5291709230214e+00,
+      0.2893736103681e-08, 0.8534352781543e+00, 0.5302110212022e+00,
+      0.3095717734137e-08, 0.2875061429041e+01, 0.2976424921901e+00,
+      0.2636190425832e-08, 0.2242512846659e+01, 0.1485980103780e+01,
+      0.3645512095537e-08, 0.1354016903958e+01, 0.6044726378023e+00,
+      0.2808173547723e-08, 0.6705114365631e-01, 0.6225157782540e-01,
+      0.2625012866888e-08, 0.4775705748482e+01, 0.5268983110410e-01,
+
+      0.2572233995651e-08, 0.2638924216139e+01, 0.1258454114666e+01,
+      0.2604238824792e-08, 0.4826358927373e+01, 0.2103781122809e+00,
+      0.2596886385239e-08, 0.3200388483118e+01, 0.2162200472757e+00,
+      0.3228057304264e-08, 0.5384848409563e+01, 0.2007689919132e+00,
+      0.2481601798252e-08, 0.5173373487744e+01, 0.1062562936266e+01,
+      0.2745977498864e-08, 0.6250966149853e+01, 0.5651155736444e+00,
+      0.2669878833811e-08, 0.4906001352499e+01, 0.1400015846597e+00,
+      0.3203986611711e-08, 0.5034333010005e+01, 0.7036329877322e+00,
+      0.3354961227212e-08, 0.6108262423137e+01, 0.4549093064213e+00,
+      0.2400407324558e-08, 0.2135399294955e+01, 0.2125476091956e+00,
+
+      0.2379905859802e-08, 0.5893721933961e+01, 0.2140505503610e+00,
+      0.2550844302187e-08, 0.3331940762063e+01, 0.1534957940063e+00,
+      0.2268824211001e-08, 0.1843418461035e+01, 0.2235935264888e+00,
+      0.2464700891204e-08, 0.3029548547230e+01, 0.2091065926078e+00,
+      0.2436814726024e-08, 0.4994717970364e+01, 0.2174915669488e+00,
+      0.2443623894745e-08, 0.2645102591375e+01, 0.1739420156204e+00,
+      0.2318701783838e-08, 0.5700547397897e+01, 0.7530171478090e-01,
+      0.2284448700256e-08, 0.5268898905872e+01, 0.7426161660010e-01,
+      0.2468848123510e-08, 0.5276280575078e+01, 0.2526561439362e+00,
+      0.2814052350303e-08, 0.6130168623475e+01, 0.5636314030725e+00,
+
+      0.2243662755220e-08, 0.6631692457995e+00, 0.8886590321940e-01,
+      0.2330795855941e-08, 0.2499435487702e+01, 0.1056200952181e+01,
+      0.9757679038404e-09, 0.5796846023126e+01, 0.7826370942180e+02 };
+
+/* SSB-to-Sun, T^0, Z */
+   static const double s0z[] = {
+      0.1181255122986e-03, 0.4607918989164e+00, 0.2132990797783e+00,
+      0.1127777651095e-03, 0.4169146331296e+00, 0.5296909721118e+00,
+      0.4777754401806e-04, 0.4582657007130e+01, 0.3813291813120e-01,
+      0.1129354285772e-04, 0.5758735142480e+01, 0.7478166569050e-01,
+     -0.1149543637123e-04, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.3298730512306e-05, 0.5978801994625e+01, 0.4265981595566e+00,
+      0.2733376706079e-05, 0.7665413691040e+00, 0.1059381944224e+01,
+      0.9426389657270e-06, 0.3710201265838e+01, 0.2061856251104e+00,
+      0.8187517749552e-06, 0.3390675605802e+00, 0.2204125344462e+00,
+      0.4080447871819e-06, 0.4552296640088e+00, 0.5225775174439e+00,
+
+      0.3169973017028e-06, 0.3445455899321e+01, 0.5368044267797e+00,
+      0.2438098615549e-06, 0.5664675150648e+01, 0.3664874755930e-01,
+      0.2601897517235e-06, 0.1931894095697e+01, 0.1495633313810e+00,
+      0.2314558080079e-06, 0.3666319115574e+00, 0.3961708870310e-01,
+      0.1962549548002e-06, 0.3167411699020e+01, 0.7626583626240e-01,
+      0.2180518287925e-06, 0.1544420746580e+01, 0.7113454667900e-02,
+      0.1451382442868e-06, 0.1583756740070e+01, 0.1102062672231e+00,
+      0.1358439007389e-06, 0.5239941758280e+01, 0.6398972393349e+00,
+      0.1050585898028e-06, 0.2266958352859e+01, 0.3163918923335e+00,
+      0.1050029870186e-06, 0.2711495250354e+01, 0.4194847048887e+00,
+
+      0.9934920679800e-07, 0.1116208151396e+01, 0.1589072916335e+01,
+      0.1048395331560e-06, 0.3408619600206e+01, 0.1021328554739e+02,
+      0.8370147196668e-07, 0.3810459401087e+01, 0.2535050500000e-01,
+      0.7989856510998e-07, 0.3769910473647e+01, 0.7329749511860e-01,
+      0.5441221655233e-07, 0.2416994903374e+01, 0.1030928125552e+00,
+      0.4610812906784e-07, 0.5858503336994e+01, 0.4337116142245e+00,
+      0.3923022803444e-07, 0.3354170010125e+00, 0.1484170571900e-02,
+      0.2610725582128e-07, 0.5410600646324e+01, 0.6327837846670e+00,
+      0.2455279767721e-07, 0.6120216681403e+01, 0.1162474756779e+01,
+      0.2375530706525e-07, 0.6055443426143e+01, 0.1052268489556e+01,
+
+      0.1782967577553e-07, 0.3146108708004e+01, 0.8460828644453e+00,
+      0.1581687095238e-07, 0.6255496089819e+00, 0.3340612434717e+01,
+      0.1594657672461e-07, 0.3782604300261e+01, 0.1066495398892e+01,
+      0.1563448615040e-07, 0.1997775733196e+01, 0.2022531624851e+00,
+      0.1463624258525e-07, 0.1736316792088e+00, 0.3516457698740e-01,
+      0.1331585056673e-07, 0.4331941830747e+01, 0.9491756770005e+00,
+      0.1130634557637e-07, 0.6152017751825e+01, 0.2968341143800e-02,
+      0.1028949607145e-07, 0.2101792614637e+00, 0.2275259891141e+00,
+      0.1024074971618e-07, 0.4071833211074e+01, 0.5070101000000e-01,
+      0.8826956060303e-08, 0.4861633688145e+00, 0.2093666171530e+00,
+
+      0.8572230171541e-08, 0.5268190724302e+01, 0.4110125927500e-01,
+      0.7649332643544e-08, 0.5134543417106e+01, 0.2608790314060e+02,
+      0.8581673291033e-08, 0.2920218146681e+01, 0.1480791608091e+00,
+      0.8430589300938e-08, 0.3604576619108e+01, 0.2172315424036e+00,
+      0.7776165501012e-08, 0.3772942249792e+01, 0.6373574839730e-01,
+      0.8311070234408e-08, 0.6200412329888e+01, 0.3235053470014e+00,
+      0.6927365212582e-08, 0.4543353113437e+01, 0.8531963191132e+00,
+      0.6791574208598e-08, 0.2882188406238e+01, 0.7181332454670e-01,
+      0.5593100811839e-08, 0.1776646892780e+01, 0.7429900518901e+00,
+      0.4553381853021e-08, 0.3949617611240e+01, 0.7775000683430e-01,
+
+      0.5758000450068e-08, 0.3859251775075e+01, 0.1990721704425e+00,
+      0.4281283457133e-08, 0.1466294631206e+01, 0.2118763888447e+01,
+      0.4206935661097e-08, 0.5421776011706e+01, 0.1104591729320e-01,
+      0.4213751641837e-08, 0.3412048993322e+01, 0.2243449970715e+00,
+      0.5310506239878e-08, 0.5421641370995e+00, 0.5154640627760e+00,
+      0.3827450341320e-08, 0.8887314524995e+00, 0.1510475019529e+00,
+      0.4292435241187e-08, 0.1405043757194e+01, 0.1422690933580e-01,
+      0.3189780702289e-08, 0.1060049293445e+01, 0.1173197218910e+00,
+      0.3226611928069e-08, 0.6270858897442e+01, 0.2164800718209e+00,
+      0.2893897608830e-08, 0.5117563223301e+01, 0.6470106940028e+00,
+
+      0.3239852024578e-08, 0.4079092237983e+01, 0.2101180877357e+00,
+      0.2956892222200e-08, 0.1594917021704e+01, 0.3092784376656e+00,
+      0.2980177912437e-08, 0.5258787667564e+01, 0.4155522422634e+00,
+      0.3163725690776e-08, 0.3854589225479e+01, 0.8582758298370e-01,
+      0.2662262399118e-08, 0.3561326430187e+01, 0.5257585094865e+00,
+      0.2766689135729e-08, 0.3180732086830e+00, 0.1385174140878e+00,
+      0.2411600278464e-08, 0.3324798335058e+01, 0.5439178814476e+00,
+      0.2483527695131e-08, 0.4169069291947e+00, 0.5336234347371e+00,
+      0.7788777276590e-09, 0.1900569908215e+01, 0.5217580628120e+02 };
+
+/* SSB-to-Sun, T^1, X */
+   static const double s1x[] = {
+     -0.1296310361520e-07, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.8975769009438e-08, 0.1128891609250e+01, 0.4265981595566e+00,
+      0.7771113441307e-08, 0.2706039877077e+01, 0.2061856251104e+00,
+      0.7538303866642e-08, 0.2191281289498e+01, 0.2204125344462e+00,
+      0.6061384579336e-08, 0.3248167319958e+01, 0.1059381944224e+01,
+      0.5726994235594e-08, 0.5569981398610e+01, 0.5225775174439e+00,
+      0.5616492836424e-08, 0.5057386614909e+01, 0.5368044267797e+00,
+      0.1010881584769e-08, 0.3473577116095e+01, 0.7113454667900e-02,
+      0.7259606157626e-09, 0.3651858593665e+00, 0.6398972393349e+00,
+      0.8755095026935e-09, 0.1662835408338e+01, 0.4194847048887e+00,
+
+      0.5370491182812e-09, 0.1327673878077e+01, 0.4337116142245e+00,
+      0.5743773887665e-09, 0.4250200846687e+01, 0.2132990797783e+00,
+      0.4408103140300e-09, 0.3598752574277e+01, 0.1589072916335e+01,
+      0.3101892374445e-09, 0.4887822983319e+01, 0.1052268489556e+01,
+      0.3209453713578e-09, 0.9702272295114e+00, 0.5296909721118e+00,
+      0.3017228286064e-09, 0.5484462275949e+01, 0.1066495398892e+01,
+      0.3200700038601e-09, 0.2846613338643e+01, 0.1495633313810e+00,
+      0.2137637279911e-09, 0.5692163292729e+00, 0.3163918923335e+00,
+      0.1899686386727e-09, 0.2061077157189e+01, 0.2275259891141e+00,
+      0.1401994545308e-09, 0.4177771136967e+01, 0.1102062672231e+00,
+
+      0.1578057810499e-09, 0.5782460597335e+01, 0.7626583626240e-01,
+      0.1237713253351e-09, 0.5705900866881e+01, 0.5154640627760e+00,
+      0.1313076837395e-09, 0.5163438179576e+01, 0.3664874755930e-01,
+      0.1184963304860e-09, 0.3054804427242e+01, 0.6327837846670e+00,
+      0.1238130878565e-09, 0.2317292575962e+01, 0.3961708870310e-01,
+      0.1015959527736e-09, 0.2194643645526e+01, 0.7329749511860e-01,
+      0.9017954423714e-10, 0.2868603545435e+01, 0.1990721704425e+00,
+      0.8668024955603e-10, 0.4923849675082e+01, 0.5439178814476e+00,
+      0.7756083930103e-10, 0.3014334135200e+01, 0.9491756770005e+00,
+      0.7536503401741e-10, 0.2704886279769e+01, 0.1030928125552e+00,
+
+      0.5483308679332e-10, 0.6010983673799e+01, 0.8531963191132e+00,
+      0.5184339620428e-10, 0.1952704573291e+01, 0.2093666171530e+00,
+      0.5108658712030e-10, 0.2958575786649e+01, 0.2172315424036e+00,
+      0.5019424524650e-10, 0.1736317621318e+01, 0.2164800718209e+00,
+      0.4909312625978e-10, 0.3167216416257e+01, 0.2101180877357e+00,
+      0.4456638901107e-10, 0.7697579923471e+00, 0.3235053470014e+00,
+      0.4227030350925e-10, 0.3490910137928e+01, 0.6373574839730e-01,
+      0.4095456040093e-10, 0.5178888984491e+00, 0.6470106940028e+00,
+      0.4990537041422e-10, 0.3323887668974e+01, 0.1422690933580e-01,
+      0.4321170010845e-10, 0.4288484987118e+01, 0.7358765972222e+00,
+
+      0.3544072091802e-10, 0.6021051579251e+01, 0.5265099800692e+00,
+      0.3480198638687e-10, 0.4600027054714e+01, 0.5328719641544e+00,
+      0.3440287244435e-10, 0.4349525970742e+01, 0.8582758298370e-01,
+      0.3330628322713e-10, 0.2347391505082e+01, 0.1104591729320e-01,
+      0.2973060707184e-10, 0.4789409286400e+01, 0.5257585094865e+00,
+      0.2932606766089e-10, 0.5831693799927e+01, 0.5336234347371e+00,
+      0.2876972310953e-10, 0.2692638514771e+01, 0.1173197218910e+00,
+      0.2827488278556e-10, 0.2056052487960e+01, 0.2022531624851e+00,
+      0.2515028239756e-10, 0.7411863262449e+00, 0.9597935788730e-01,
+      0.2853033744415e-10, 0.3948481024894e+01, 0.2118763888447e+01 };
+
+/* SSB-to-Sun, T^1, Y */
+   static const double s1y[] = {
+      0.8989047573576e-08, 0.5840593672122e+01, 0.4265981595566e+00,
+      0.7815938401048e-08, 0.1129664707133e+01, 0.2061856251104e+00,
+      0.7550926713280e-08, 0.6196589104845e+00, 0.2204125344462e+00,
+      0.6056556925895e-08, 0.1677494667846e+01, 0.1059381944224e+01,
+      0.5734142698204e-08, 0.4000920852962e+01, 0.5225775174439e+00,
+      0.5614341822459e-08, 0.3486722577328e+01, 0.5368044267797e+00,
+      0.1028678147656e-08, 0.1877141024787e+01, 0.7113454667900e-02,
+      0.7270792075266e-09, 0.5077167301739e+01, 0.6398972393349e+00,
+      0.8734141726040e-09, 0.9069550282609e-01, 0.4194847048887e+00,
+      0.5377371402113e-09, 0.6039381844671e+01, 0.4337116142245e+00,
+
+      0.4729719431571e-09, 0.2153086311760e+01, 0.2132990797783e+00,
+      0.4458052820973e-09, 0.5059830025565e+01, 0.5296909721118e+00,
+      0.4406855467908e-09, 0.2027971692630e+01, 0.1589072916335e+01,
+      0.3101659310977e-09, 0.3317677981860e+01, 0.1052268489556e+01,
+      0.3016749232545e-09, 0.3913703482532e+01, 0.1066495398892e+01,
+      0.3198541352656e-09, 0.1275513098525e+01, 0.1495633313810e+00,
+      0.2142065389871e-09, 0.5301351614597e+01, 0.3163918923335e+00,
+      0.1902615247592e-09, 0.4894943352736e+00, 0.2275259891141e+00,
+      0.1613410990871e-09, 0.2449891130437e+01, 0.1102062672231e+00,
+      0.1576992165097e-09, 0.4211421447633e+01, 0.7626583626240e-01,
+
+      0.1241637259894e-09, 0.4140803368133e+01, 0.5154640627760e+00,
+      0.1313974830355e-09, 0.3591920305503e+01, 0.3664874755930e-01,
+      0.1181697118258e-09, 0.1506314382788e+01, 0.6327837846670e+00,
+      0.1238239742779e-09, 0.7461405378404e+00, 0.3961708870310e-01,
+      0.1010107068241e-09, 0.6271010795475e+00, 0.7329749511860e-01,
+      0.9226316616509e-10, 0.1259158839583e+01, 0.1990721704425e+00,
+      0.8664946419555e-10, 0.3353244696934e+01, 0.5439178814476e+00,
+      0.7757230468978e-10, 0.1447677295196e+01, 0.9491756770005e+00,
+      0.7693168628139e-10, 0.1120509896721e+01, 0.1030928125552e+00,
+      0.5487897454612e-10, 0.4439380426795e+01, 0.8531963191132e+00,
+
+      0.5196118677218e-10, 0.3788856619137e+00, 0.2093666171530e+00,
+      0.5110853339935e-10, 0.1386879372016e+01, 0.2172315424036e+00,
+      0.5027804534813e-10, 0.1647881805466e+00, 0.2164800718209e+00,
+      0.4922485922674e-10, 0.1594315079862e+01, 0.2101180877357e+00,
+      0.6155599524400e-10, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.4447147832161e-10, 0.5480720918976e+01, 0.3235053470014e+00,
+      0.4144691276422e-10, 0.1931371033660e+01, 0.6373574839730e-01,
+      0.4099950625452e-10, 0.5229611294335e+01, 0.6470106940028e+00,
+      0.5060541682953e-10, 0.1731112486298e+01, 0.1422690933580e-01,
+      0.4293615946300e-10, 0.2714571038925e+01, 0.7358765972222e+00,
+
+      0.3545659845763e-10, 0.4451041444634e+01, 0.5265099800692e+00,
+      0.3479112041196e-10, 0.3029385448081e+01, 0.5328719641544e+00,
+      0.3438516493570e-10, 0.2778507143731e+01, 0.8582758298370e-01,
+      0.3297341285033e-10, 0.7898709807584e+00, 0.1104591729320e-01,
+      0.2972585818015e-10, 0.3218785316973e+01, 0.5257585094865e+00,
+      0.2931707295017e-10, 0.4260731012098e+01, 0.5336234347371e+00,
+      0.2897198149403e-10, 0.1120753978101e+01, 0.1173197218910e+00,
+      0.2832293240878e-10, 0.4597682717827e+00, 0.2022531624851e+00,
+      0.2864348326612e-10, 0.2169939928448e+01, 0.9597935788730e-01,
+      0.2852714675471e-10, 0.2377659870578e+01, 0.2118763888447e+01 };
+
+/* SSB-to-Sun, T^1, Z */
+   static const double s1z[] = {
+      0.5444220475678e-08, 0.1803825509310e+01, 0.2132990797783e+00,
+      0.3883412695596e-08, 0.4668616389392e+01, 0.5296909721118e+00,
+      0.1334341434551e-08, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.3730001266883e-09, 0.5401405918943e+01, 0.2061856251104e+00,
+      0.2894929197956e-09, 0.4932415609852e+01, 0.2204125344462e+00,
+      0.2857950357701e-09, 0.3154625362131e+01, 0.7478166569050e-01,
+      0.2499226432292e-09, 0.3657486128988e+01, 0.4265981595566e+00,
+      0.1937705443593e-09, 0.5740434679002e+01, 0.1059381944224e+01,
+      0.1374894396320e-09, 0.1712857366891e+01, 0.5368044267797e+00,
+      0.1217248678408e-09, 0.2312090870932e+01, 0.5225775174439e+00,
+
+      0.7961052740870e-10, 0.5283368554163e+01, 0.3813291813120e-01,
+      0.4979225949689e-10, 0.4298290471860e+01, 0.4194847048887e+00,
+      0.4388552286597e-10, 0.6145515047406e+01, 0.7113454667900e-02,
+      0.2586835212560e-10, 0.3019448001809e+01, 0.6398972393349e+00 };
+
+/* SSB-to-Sun, T^2, X */
+   static const double s2x[] = {
+      0.1603551636587e-11, 0.4404109410481e+01, 0.2061856251104e+00,
+      0.1556935889384e-11, 0.4818040873603e+00, 0.2204125344462e+00,
+      0.1182594414915e-11, 0.9935762734472e+00, 0.5225775174439e+00,
+      0.1158794583180e-11, 0.3353180966450e+01, 0.5368044267797e+00,
+      0.9597358943932e-12, 0.5567045358298e+01, 0.2132990797783e+00,
+      0.6511516579605e-12, 0.5630872420788e+01, 0.4265981595566e+00,
+      0.7419792747688e-12, 0.2156188581957e+01, 0.5296909721118e+00,
+      0.3951972655848e-12, 0.1981022541805e+01, 0.1059381944224e+01,
+      0.4478223877045e-12, 0.0000000000000e+00, 0.0000000000000e+00 };
+
+/* SSB-to-Sun, T^2, Y */
+   static const double s2y[] = {
+      0.1609114495091e-11, 0.2831096993481e+01, 0.2061856251104e+00,
+      0.1560330784946e-11, 0.5193058213906e+01, 0.2204125344462e+00,
+      0.1183535479202e-11, 0.5707003443890e+01, 0.5225775174439e+00,
+      0.1158183066182e-11, 0.1782400404928e+01, 0.5368044267797e+00,
+      0.1032868027407e-11, 0.4036925452011e+01, 0.2132990797783e+00,
+      0.6540142847741e-12, 0.4058241056717e+01, 0.4265981595566e+00,
+      0.7305236491596e-12, 0.6175401942957e+00, 0.5296909721118e+00,
+     -0.5580725052968e-12, 0.0000000000000e+00, 0.0000000000000e+00,
+      0.3946122651015e-12, 0.4108265279171e+00, 0.1059381944224e+01 };
+
+/* SSB-to-Sun, T^2, Z */
+   static const double s2z[] = {
+      0.3749920358054e-12, 0.3230285558668e+01, 0.2132990797783e+00,
+      0.2735037220939e-12, 0.6154322683046e+01, 0.5296909721118e+00 };
+
+/* Pointers to coefficient arrays, in x,y,z sets */
+   static const double *ce0[] = { e0x, e0y, e0z },
+                       *ce1[] = { e1x, e1y, e1z },
+                       *ce2[] = { e2x, e2y, e2z },
+                       *cs0[] = { s0x, s0y, s0z },
+                       *cs1[] = { s1x, s1y, s1z },
+                       *cs2[] = { s2x, s2y, s2z };
+   const double *coeffs;
+
+/* Numbers of terms for each component of the model, in x,y,z sets */
+   static const int ne0[3] = {(int)(sizeof e0x / sizeof (double) / 3),
+                              (int)(sizeof e0y / sizeof (double) / 3),
+                              (int)(sizeof e0z / sizeof (double) / 3) },
+                    ne1[3] = {(int)(sizeof e1x / sizeof (double) / 3),
+                              (int)(sizeof e1y / sizeof (double) / 3),
+                              (int)(sizeof e1z / sizeof (double) / 3) },
+                    ne2[3] = {(int)(sizeof e2x / sizeof (double) / 3),
+                              (int)(sizeof e2y / sizeof (double) / 3),
+                              (int)(sizeof e2z / sizeof (double) / 3) },
+                    ns0[3] = {(int)(sizeof s0x / sizeof (double) / 3),
+                              (int)(sizeof s0y / sizeof (double) / 3),
+                              (int)(sizeof s0z / sizeof (double) / 3) },
+                    ns1[3] = {(int)(sizeof s1x / sizeof (double) / 3),
+                              (int)(sizeof s1y / sizeof (double) / 3),
+                              (int)(sizeof s1z / sizeof (double) / 3) },
+                    ns2[3] = {(int)(sizeof s2x / sizeof (double) / 3),
+                              (int)(sizeof s2y / sizeof (double) / 3),
+                              (int)(sizeof s2z / sizeof (double) / 3) };
+   int nterms;
+
+/* Miscellaneous */
+   int jstat, i, j;
+   double t, t2, xyz, xyzd, a, b, c, ct, p, cp,
+          ph[3], vh[3], pb[3], vb[3], x, y, z;
+
+/*--------------------------------------------------------------------*/
+
+/* Time since reference epoch, Julian years. */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJY;
+   t2 = t*t;
+
+/* Set status. */
+   jstat = fabs(t) <= 100.0 ? 0 : 1;
+
+/* X then Y then Z. */
+   for (i = 0; i < 3; i++) {
+
+   /* Initialize position and velocity component. */
+      xyz = 0.0;
+      xyzd = 0.0;
+
+   /* ------------------------------------------------ */
+   /* Obtain component of Sun to Earth ecliptic vector */
+   /* ------------------------------------------------ */
+
+   /* Sun to Earth, T^0 terms. */
+      coeffs = ce0[i];
+      nterms = ne0[i];
+      for (j = 0; j < nterms; j++) {
+         a = *coeffs++;
+         b = *coeffs++;
+         c = *coeffs++;
+         p = b + c*t;
+         xyz  += a*cos(p);
+         xyzd -= a*c*sin(p);
+      }
+
+   /* Sun to Earth, T^1 terms. */
+      coeffs = ce1[i];
+      nterms = ne1[i];
+      for (j = 0; j < nterms; j++) {
+         a = *coeffs++;
+         b = *coeffs++;
+         c = *coeffs++;
+         ct = c*t;
+         p = b + ct;
+         cp = cos(p);
+         xyz  += a*t*cp;
+         xyzd += a*( cp - ct*sin(p) );
+      }
+
+   /* Sun to Earth, T^2 terms. */
+      coeffs = ce2[i];
+      nterms = ne2[i];
+      for (j = 0; j < nterms; j++) {
+         a = *coeffs++;
+         b = *coeffs++;
+         c = *coeffs++;
+         ct = c*t;
+         p = b + ct;
+         cp = cos(p);
+         xyz  += a*t2*cp;
+         xyzd += a*t*( 2.0*cp - ct*sin(p) );
+      }
+
+   /* Heliocentric Earth position and velocity component. */
+      ph[i] = xyz;
+      vh[i] = xyzd / ERFA_DJY;
+
+   /* ------------------------------------------------ */
+   /* Obtain component of SSB to Earth ecliptic vector */
+   /* ------------------------------------------------ */
+
+   /* SSB to Sun, T^0 terms. */
+      coeffs = cs0[i];
+      nterms = ns0[i];
+      for (j = 0; j < nterms; j++) {
+         a = *coeffs++;
+         b = *coeffs++;
+         c = *coeffs++;
+         p = b + c*t;
+         xyz  += a*cos(p);
+         xyzd -= a*c*sin(p);
+      }
+
+   /* SSB to Sun, T^1 terms. */
+      coeffs = cs1[i];
+      nterms = ns1[i];
+      for (j = 0; j < nterms; j++) {
+         a = *coeffs++;
+         b = *coeffs++;
+         c = *coeffs++;
+         ct = c*t;
+         p = b + ct;
+         cp = cos(p);
+         xyz  += a*t*cp;
+         xyzd += a*(cp - ct*sin(p));
+      }
+
+   /* SSB to Sun, T^2 terms. */
+      coeffs = cs2[i];
+      nterms = ns2[i];
+      for (j = 0; j < nterms; j++) {
+         a = *coeffs++;
+         b = *coeffs++;
+         c = *coeffs++;
+         ct = c*t;
+         p = b + ct;
+         cp = cos(p);
+         xyz  += a*t2*cp;
+         xyzd += a*t*(2.0*cp - ct*sin(p));
+     }
+
+   /* Barycentric Earth position and velocity component. */
+     pb[i] = xyz;
+     vb[i] = xyzd / ERFA_DJY;
+
+   /* Next Cartesian component. */
+   }
+
+/* Rotate from ecliptic to BCRS coordinates. */
+
+   x = ph[0];
+   y = ph[1];
+   z = ph[2];
+   pvh[0][0] =      x + am12*y + am13*z;
+   pvh[0][1] = am21*x + am22*y + am23*z;
+   pvh[0][2] =          am32*y + am33*z;
+
+   x = vh[0];
+   y = vh[1];
+   z = vh[2];
+   pvh[1][0] =      x + am12*y + am13*z;
+   pvh[1][1] = am21*x + am22*y + am23*z;
+   pvh[1][2] =          am32*y + am33*z;
+
+   x = pb[0];
+   y = pb[1];
+   z = pb[2];
+   pvb[0][0] =      x + am12*y + am13*z;
+   pvb[0][1] = am21*x + am22*y + am23*z;
+   pvb[0][2] =          am32*y + am33*z;
+
+   x = vb[0];
+   y = vb[1];
+   z = vb[2];
+   pvb[1][0] =      x + am12*y + am13*z;
+   pvb[1][1] = am21*x + am22*y + am23*z;
+   pvb[1][2] =          am32*y + am33*z;
+
+/* Return the status. */
+   return jstat;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/eqec06.c b/ast/erfa/eqec06.c
new file mode 100644
index 0000000..6a667c9
--- /dev/null
+++ b/ast/erfa/eqec06.c
@@ -0,0 +1,142 @@
+#include "erfa.h"
+
+void eraEqec06(double date1, double date2, double dr, double dd,
+               double *dl, double *db)
+/*
+**  - - - - - - - - - -
+**   e r a E q e c 0 6
+**  - - - - - - - - - -
+**
+**  Transformation from ICRS equatorial coordinates to ecliptic
+**  coordinates (mean equinox and ecliptic of date) using IAU 2006
+**  precession model.
+**
+**  Given:
+**     date1,date2 double TT as a 2-part Julian date (Note 1)
+**     dr,dd       double ICRS right ascension and declination (radians)
+**
+**  Returned:
+**     dl,db       double ecliptic longitude and latitude (radians)
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) No assumptions are made about whether the coordinates represent
+**     starlight and embody astrometric effects such as parallax or
+**     aberration.
+**
+**  3) The transformation is approximately that from mean J2000.0 right
+**     ascension and declination to ecliptic longitude and latitude
+**     (mean equinox and ecliptic of date), with only frame bias (always
+**     less than 25 mas) to disturb this classical picture.
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraEcm06     J2000.0 to ecliptic rotation matrix, IAU 2006
+**     eraRxp       product of r-matrix and p-vector
+**     eraC2s       unit vector to spherical coordinates
+**     eraAnp       normalize angle into range 0 to 2pi
+**     eraAnpm      normalize angle into range +/- pi
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rm[3][3], v1[3], v2[3], a, b;
+
+
+/* Spherical to Cartesian. */
+   eraS2c(dr, dd, v1);
+
+/* Rotation matrix, ICRS equatorial to ecliptic. */
+   eraEcm06(date1, date2, rm);
+
+/* The transformation from ICRS to ecliptic. */
+   eraRxp(rm, v1, v2);
+
+/* Cartesian to spherical. */
+   eraC2s(v2, &a, &b);
+
+/* Express in conventional ranges. */
+   *dl = eraAnp(a);
+   *db = eraAnpm(b);
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/eqeq94.c b/ast/erfa/eqeq94.c
new file mode 100644
index 0000000..00076b1
--- /dev/null
+++ b/ast/erfa/eqeq94.c
@@ -0,0 +1,141 @@
+#include "erfa.h"
+
+double eraEqeq94(double date1, double date2)
+/*
+**  - - - - - - - - - -
+**   e r a E q e q 9 4
+**  - - - - - - - - - -
+**
+**  Equation of the equinoxes, IAU 1994 model.
+**
+**  Given:
+**     date1,date2   double     TDB date (Note 1)
+**
+**  Returned (function value):
+**                   double     equation of the equinoxes (Note 2)
+**
+**  Notes:
+**
+**  1) The date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The result, which is in radians, operates in the following sense:
+**
+**        Greenwich apparent ST = GMST + equation of the equinoxes
+**
+**  Called:
+**     eraAnpm      normalize angle into range +/- pi
+**     eraNut80     nutation, IAU 1980
+**     eraObl80     mean obliquity, IAU 1980
+**
+**  References:
+**
+**     IAU Resolution C7, Recommendation 3 (1994).
+**
+**     Capitaine, N. & Gontier, A.-M., 1993, Astron. Astrophys., 275,
+**     645-650.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t,  om,  dpsi,  deps,  eps0, ee;
+
+
+/* Interval between fundamental epoch J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Longitude of the mean ascending node of the lunar orbit on the */
+/* ecliptic, measured from the mean equinox of date. */
+   om = eraAnpm((450160.280 + (-482890.539
+           + (7.455 + 0.008 * t) * t) * t) * ERFA_DAS2R
+           + fmod(-5.0 * t, 1.0) * ERFA_D2PI);
+
+/* Nutation components and mean obliquity. */
+   eraNut80(date1, date2, &dpsi, &deps);
+   eps0 = eraObl80(date1, date2);
+
+/* Equation of the equinoxes. */
+   ee = dpsi*cos(eps0) + ERFA_DAS2R*(0.00264*sin(om) + 0.000063*sin(om+om));
+
+   return ee;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/era00.c b/ast/erfa/era00.c
new file mode 100644
index 0000000..01e06f5
--- /dev/null
+++ b/ast/erfa/era00.c
@@ -0,0 +1,145 @@
+#include "erfa.h"
+
+double eraEra00(double dj1, double dj2)
+/*
+**  - - - - - - - - -
+**   e r a E r a 0 0
+**  - - - - - - - - -
+**
+**  Earth rotation angle (IAU 2000 model).
+**
+**  Given:
+**     dj1,dj2   double    UT1 as a 2-part Julian Date (see note)
+**
+**  Returned (function value):
+**               double    Earth rotation angle (radians), range 0-2pi
+**
+**  Notes:
+**
+**  1) The UT1 date dj1+dj2 is a Julian Date, apportioned in any
+**     convenient way between the arguments dj1 and dj2.  For example,
+**     JD(UT1)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**             dj1            dj2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  The date & time method is
+**     best matched to the algorithm used:  maximum precision is
+**     delivered when the dj1 argument is for 0hrs UT1 on the day in
+**     question and the dj2 argument lies in the range 0 to 1, or vice
+**     versa.
+**
+**  2) The algorithm is adapted from Expression 22 of Capitaine et al.
+**     2000.  The time argument has been expressed in days directly,
+**     and, to retain precision, integer contributions have been
+**     eliminated.  The same formulation is given in IERS Conventions
+**     (2003), Chap. 5, Eq. 14.
+**
+**  Called:
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  References:
+**
+**     Capitaine N., Guinot B. and McCarthy D.D, 2000, Astron.
+**     Astrophys., 355, 398-405.
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double d1, d2, t, f, theta;
+
+
+/* Days since fundamental epoch. */
+   if (dj1 < dj2) {
+      d1 = dj1;
+      d2 = dj2;
+   } else {
+      d1 = dj2;
+      d2 = dj1;
+   }
+   t = d1 + (d2- ERFA_DJ00);
+
+/* Fractional part of T (days). */
+   f = fmod(d1, 1.0) + fmod(d2, 1.0);
+
+/* Earth rotation angle at this UT1. */
+   theta = eraAnp(ERFA_D2PI * (f + 0.7790572732640
+                            + 0.00273781191135448 * t));
+
+   return theta;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/erfa.h b/ast/erfa/erfa.h
new file mode 100644
index 0000000..c22f470
--- /dev/null
+++ b/ast/erfa/erfa.h
@@ -0,0 +1,517 @@
+#ifndef ERFAHDEF
+#define ERFAHDEF
+
+/*
+**  - - - - - - -
+**   e r f a . h
+**  - - - - - - -
+**
+**  Prototype function declarations for ERFA library.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+
+#include "erfam.h"
+#include "math.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Astronomy/Calendars */
+int eraCal2jd(int iy, int im, int id, double *djm0, double *djm);
+double eraEpb(double dj1, double dj2);
+void eraEpb2jd(double epb, double *djm0, double *djm);
+double eraEpj(double dj1, double dj2);
+void eraEpj2jd(double epj, double *djm0, double *djm);
+int eraJd2cal(double dj1, double dj2,
+                     int *iy, int *im, int *id, double *fd);
+int eraJdcalf(int ndp, double dj1, double dj2, int iymdf[4]);
+
+/* Astronomy/Astrometry */
+void eraAb(double pnat[3], double v[3], double s, double bm1,
+           double ppr[3]);
+void eraApcg(double date1, double date2,
+             double ebpv[2][3], double ehp[3],
+             eraASTROM *astrom);
+void eraApcg13(double date1, double date2, eraASTROM *astrom);
+void eraApci(double date1, double date2,
+             double ebpv[2][3], double ehp[3],
+             double x, double y, double s,
+             eraASTROM *astrom);
+void eraApci13(double date1, double date2,
+               eraASTROM *astrom, double *eo);
+void eraApco(double date1, double date2,
+             double ebpv[2][3], double ehp[3],
+             double x, double y, double s, double theta,
+             double elong, double phi, double hm,
+             double xp, double yp, double sp,
+             double refa, double refb,
+             eraASTROM *astrom);
+int eraApco13(double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              eraASTROM *astrom, double *eo);
+void eraApcs(double date1, double date2, double pv[2][3],
+             double ebpv[2][3], double ehp[3],
+             eraASTROM *astrom);
+void eraApcs13(double date1, double date2, double pv[2][3],
+               eraASTROM *astrom);
+void eraAper(double theta, eraASTROM *astrom);
+void eraAper13(double ut11, double ut12, eraASTROM *astrom);
+void eraApio(double sp, double theta,
+             double elong, double phi, double hm, double xp, double yp,
+             double refa, double refb,
+             eraASTROM *astrom);
+int eraApio13(double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              eraASTROM *astrom);
+void eraAtci13(double rc, double dc,
+               double pr, double pd, double px, double rv,
+               double date1, double date2,
+               double *ri, double *di, double *eo);
+void eraAtciq(double rc, double dc, double pr, double pd,
+              double px, double rv, eraASTROM *astrom,
+              double *ri, double *di);
+void eraAtciqn(double rc, double dc, double pr, double pd,
+               double px, double rv, eraASTROM *astrom,
+               int n, eraLDBODY b[], double *ri, double *di);
+void eraAtciqz(double rc, double dc, eraASTROM *astrom,
+               double *ri, double *di);
+int eraAtco13(double rc, double dc,
+              double pr, double pd, double px, double rv,
+              double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              double *aob, double *zob, double *hob,
+              double *dob, double *rob, double *eo);
+void eraAtic13(double ri, double di,
+               double date1, double date2,
+               double *rc, double *dc, double *eo);
+void eraAticq(double ri, double di, eraASTROM *astrom,
+              double *rc, double *dc);
+void eraAticqn(double ri, double di, eraASTROM *astrom,
+               int n, eraLDBODY b[], double *rc, double *dc);
+int eraAtio13(double ri, double di,
+              double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              double *aob, double *zob, double *hob,
+              double *dob, double *rob);
+void eraAtioq(double ri, double di, eraASTROM *astrom,
+              double *aob, double *zob,
+              double *hob, double *dob, double *rob);
+int eraAtoc13(const char *type, double ob1, double ob2,
+              double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              double *rc, double *dc);
+int eraAtoi13(const char *type, double ob1, double ob2,
+              double utc1, double utc2, double dut1,
+              double elong, double phi, double hm, double xp, double yp,
+              double phpa, double tc, double rh, double wl,
+              double *ri, double *di);
+void eraAtoiq(const char *type,
+              double ob1, double ob2, eraASTROM *astrom,
+              double *ri, double *di);
+void eraLd(double bm, double p[3], double q[3], double e[3],
+           double em, double dlim, double p1[3]);
+void eraLdn(int n, eraLDBODY b[], double ob[3], double sc[3],
+            double sn[3]);
+void eraLdsun(double p[3], double e[3], double em, double p1[3]);
+void eraPmpx(double rc, double dc, double pr, double pd,
+             double px, double rv, double pmt, double pob[3],
+             double pco[3]);
+int eraPmsafe(double ra1, double dec1, double pmr1, double pmd1,
+              double px1, double rv1,
+              double ep1a, double ep1b, double ep2a, double ep2b,
+              double *ra2, double *dec2, double *pmr2, double *pmd2,
+              double *px2, double *rv2);
+void eraPvtob(double elong, double phi, double height, double xp,
+              double yp, double sp, double theta, double pv[2][3]);
+void eraRefco(double phpa, double tc, double rh, double wl,
+              double *refa, double *refb);
+
+/* Astronomy/Ephemerides */
+int eraEpv00(double date1, double date2,
+             double pvh[2][3], double pvb[2][3]);
+int eraPlan94(double date1, double date2, int np, double pv[2][3]);
+
+/* Astronomy/FundamentalArgs */
+double eraFad03(double t);
+double eraFae03(double t);
+double eraFaf03(double t);
+double eraFaju03(double t);
+double eraFal03(double t);
+double eraFalp03(double t);
+double eraFama03(double t);
+double eraFame03(double t);
+double eraFane03(double t);
+double eraFaom03(double t);
+double eraFapa03(double t);
+double eraFasa03(double t);
+double eraFaur03(double t);
+double eraFave03(double t);
+
+/* Astronomy/PrecNutPolar */
+void eraBi00(double *dpsibi, double *depsbi, double *dra);
+void eraBp00(double date1, double date2,
+             double rb[3][3], double rp[3][3], double rbp[3][3]);
+void eraBp06(double date1, double date2,
+             double rb[3][3], double rp[3][3], double rbp[3][3]);
+void eraBpn2xy(double rbpn[3][3], double *x, double *y);
+void eraC2i00a(double date1, double date2, double rc2i[3][3]);
+void eraC2i00b(double date1, double date2, double rc2i[3][3]);
+void eraC2i06a(double date1, double date2, double rc2i[3][3]);
+void eraC2ibpn(double date1, double date2, double rbpn[3][3],
+               double rc2i[3][3]);
+void eraC2ixy(double date1, double date2, double x, double y,
+              double rc2i[3][3]);
+void eraC2ixys(double x, double y, double s, double rc2i[3][3]);
+void eraC2t00a(double tta, double ttb, double uta, double utb,
+               double xp, double yp, double rc2t[3][3]);
+void eraC2t00b(double tta, double ttb, double uta, double utb,
+               double xp, double yp, double rc2t[3][3]);
+void eraC2t06a(double tta, double ttb, double uta, double utb,
+               double xp, double yp, double rc2t[3][3]);
+void eraC2tcio(double rc2i[3][3], double era, double rpom[3][3],
+               double rc2t[3][3]);
+void eraC2teqx(double rbpn[3][3], double gst, double rpom[3][3],
+               double rc2t[3][3]);
+void eraC2tpe(double tta, double ttb, double uta, double utb,
+              double dpsi, double deps, double xp, double yp,
+              double rc2t[3][3]);
+void eraC2txy(double tta, double ttb, double uta, double utb,
+              double x, double y, double xp, double yp,
+              double rc2t[3][3]);
+double eraEo06a(double date1, double date2);
+double eraEors(double rnpb[3][3], double s);
+void eraFw2m(double gamb, double phib, double psi, double eps,
+             double r[3][3]);
+void eraFw2xy(double gamb, double phib, double psi, double eps,
+              double *x, double *y);
+void eraLtp(double epj, double rp[3][3]);
+void eraLtpb(double epj, double rpb[3][3]);
+void eraLtpecl(double epj, double vec[3]);
+void eraLtpequ(double epj, double veq[3]);
+void eraNum00a(double date1, double date2, double rmatn[3][3]);
+void eraNum00b(double date1, double date2, double rmatn[3][3]);
+void eraNum06a(double date1, double date2, double rmatn[3][3]);
+void eraNumat(double epsa, double dpsi, double deps, double rmatn[3][3]);
+void eraNut00a(double date1, double date2, double *dpsi, double *deps);
+void eraNut00b(double date1, double date2, double *dpsi, double *deps);
+void eraNut06a(double date1, double date2, double *dpsi, double *deps);
+void eraNut80(double date1, double date2, double *dpsi, double *deps);
+void eraNutm80(double date1, double date2, double rmatn[3][3]);
+double eraObl06(double date1, double date2);
+double eraObl80(double date1, double date2);
+void eraP06e(double date1, double date2,
+             double *eps0, double *psia, double *oma, double *bpa,
+             double *bqa, double *pia, double *bpia,
+             double *epsa, double *chia, double *za, double *zetaa,
+             double *thetaa, double *pa,
+             double *gam, double *phi, double *psi);
+void eraPb06(double date1, double date2,
+             double *bzeta, double *bz, double *btheta);
+void eraPfw06(double date1, double date2,
+              double *gamb, double *phib, double *psib, double *epsa);
+void eraPmat00(double date1, double date2, double rbp[3][3]);
+void eraPmat06(double date1, double date2, double rbp[3][3]);
+void eraPmat76(double date1, double date2, double rmatp[3][3]);
+void eraPn00(double date1, double date2, double dpsi, double deps,
+             double *epsa,
+             double rb[3][3], double rp[3][3], double rbp[3][3],
+             double rn[3][3], double rbpn[3][3]);
+void eraPn00a(double date1, double date2,
+              double *dpsi, double *deps, double *epsa,
+              double rb[3][3], double rp[3][3], double rbp[3][3],
+              double rn[3][3], double rbpn[3][3]);
+void eraPn00b(double date1, double date2,
+              double *dpsi, double *deps, double *epsa,
+              double rb[3][3], double rp[3][3], double rbp[3][3],
+              double rn[3][3], double rbpn[3][3]);
+void eraPn06(double date1, double date2, double dpsi, double deps,
+             double *epsa,
+             double rb[3][3], double rp[3][3], double rbp[3][3],
+             double rn[3][3], double rbpn[3][3]);
+void eraPn06a(double date1, double date2,
+              double *dpsi, double *deps, double *epsa,
+              double rb[3][3], double rp[3][3], double rbp[3][3],
+              double rn[3][3], double rbpn[3][3]);
+void eraPnm00a(double date1, double date2, double rbpn[3][3]);
+void eraPnm00b(double date1, double date2, double rbpn[3][3]);
+void eraPnm06a(double date1, double date2, double rnpb[3][3]);
+void eraPnm80(double date1, double date2, double rmatpn[3][3]);
+void eraPom00(double xp, double yp, double sp, double rpom[3][3]);
+void eraPr00(double date1, double date2,
+             double *dpsipr, double *depspr);
+void eraPrec76(double date01, double date02,
+               double date11, double date12,
+               double *zeta, double *z, double *theta);
+double eraS00(double date1, double date2, double x, double y);
+double eraS00a(double date1, double date2);
+double eraS00b(double date1, double date2);
+double eraS06(double date1, double date2, double x, double y);
+double eraS06a(double date1, double date2);
+double eraSp00(double date1, double date2);
+void eraXy06(double date1, double date2, double *x, double *y);
+void eraXys00a(double date1, double date2,
+               double *x, double *y, double *s);
+void eraXys00b(double date1, double date2,
+               double *x, double *y, double *s);
+void eraXys06a(double date1, double date2,
+               double *x, double *y, double *s);
+
+/* Astronomy/RotationAndTime */
+double eraEe00(double date1, double date2, double epsa, double dpsi);
+double eraEe00a(double date1, double date2);
+double eraEe00b(double date1, double date2);
+double eraEe06a(double date1, double date2);
+double eraEect00(double date1, double date2);
+double eraEqeq94(double date1, double date2);
+double eraEra00(double dj1, double dj2);
+double eraGmst00(double uta, double utb, double tta, double ttb);
+double eraGmst06(double uta, double utb, double tta, double ttb);
+double eraGmst82(double dj1, double dj2);
+double eraGst00a(double uta, double utb, double tta, double ttb);
+double eraGst00b(double uta, double utb);
+double eraGst06(double uta, double utb, double tta, double ttb,
+                double rnpb[3][3]);
+double eraGst06a(double uta, double utb, double tta, double ttb);
+double eraGst94(double uta, double utb);
+
+/* Astronomy/SpaceMotion */
+int eraPvstar(double pv[2][3], double *ra, double *dec,
+              double *pmr, double *pmd, double *px, double *rv);
+int eraStarpv(double ra, double dec,
+              double pmr, double pmd, double px, double rv,
+              double pv[2][3]);
+
+/* Astronomy/StarCatalogs */
+void eraFk52h(double r5, double d5,
+              double dr5, double dd5, double px5, double rv5,
+              double *rh, double *dh,
+              double *drh, double *ddh, double *pxh, double *rvh);
+void eraFk5hip(double r5h[3][3], double s5h[3]);
+void eraFk5hz(double r5, double d5, double date1, double date2,
+              double *rh, double *dh);
+void eraH2fk5(double rh, double dh,
+              double drh, double ddh, double pxh, double rvh,
+              double *r5, double *d5,
+              double *dr5, double *dd5, double *px5, double *rv5);
+void eraHfk5z(double rh, double dh, double date1, double date2,
+              double *r5, double *d5, double *dr5, double *dd5);
+int eraStarpm(double ra1, double dec1,
+              double pmr1, double pmd1, double px1, double rv1,
+              double ep1a, double ep1b, double ep2a, double ep2b,
+              double *ra2, double *dec2,
+              double *pmr2, double *pmd2, double *px2, double *rv2);
+
+/* Astronomy/EclipticCoordinates */
+void eraEceq06(double date1, double date2, double dl, double db,
+               double *dr, double *dd);
+void eraEcm06(double date1, double date2, double rm[3][3]);
+void eraEqec06(double date1, double date2, double dr, double dd,
+               double *dl, double *db);
+void eraLteceq(double epj, double dl, double db, double *dr, double *dd);
+void eraLtecm(double epj, double rm[3][3]);
+void eraLteqec(double epj, double dr, double dd, double *dl, double *db);
+
+/* Astronomy/GalacticCoordinates */
+void eraG2icrs(double dl, double db, double *dr, double *dd);
+void eraIcrs2g(double dr, double dd, double *dl, double *db);
+
+/* Astronomy/GeodeticGeocentric */
+int eraEform(int n, double *a, double *f);
+int eraGc2gd(int n, double xyz[3],
+             double *elong, double *phi, double *height);
+int eraGc2gde(double a, double f, double xyz[3],
+              double *elong, double *phi, double *height);
+int eraGd2gc(int n, double elong, double phi, double height,
+             double xyz[3]);
+int eraGd2gce(double a, double f,
+              double elong, double phi, double height, double xyz[3]);
+
+/* Astronomy/Timescales */
+int eraD2dtf(const char *scale, int ndp, double d1, double d2,
+             int *iy, int *im, int *id, int ihmsf[4]);
+int eraDat(int iy, int im, int id, double fd, double *deltat);
+double eraDtdb(double date1, double date2,
+               double ut, double elong, double u, double v);
+int eraDtf2d(const char *scale, int iy, int im, int id,
+             int ihr, int imn, double sec, double *d1, double *d2);
+int eraTaitt(double tai1, double tai2, double *tt1, double *tt2);
+int eraTaiut1(double tai1, double tai2, double dta,
+              double *ut11, double *ut12);
+int eraTaiutc(double tai1, double tai2, double *utc1, double *utc2);
+int eraTcbtdb(double tcb1, double tcb2, double *tdb1, double *tdb2);
+int eraTcgtt(double tcg1, double tcg2, double *tt1, double *tt2);
+int eraTdbtcb(double tdb1, double tdb2, double *tcb1, double *tcb2);
+int eraTdbtt(double tdb1, double tdb2, double dtr,
+             double *tt1, double *tt2);
+int eraTttai(double tt1, double tt2, double *tai1, double *tai2);
+int eraTttcg(double tt1, double tt2, double *tcg1, double *tcg2);
+int eraTttdb(double tt1, double tt2, double dtr,
+             double *tdb1, double *tdb2);
+int eraTtut1(double tt1, double tt2, double dt,
+             double *ut11, double *ut12);
+int eraUt1tai(double ut11, double ut12, double dta,
+              double *tai1, double *tai2);
+int eraUt1tt(double ut11, double ut12, double dt,
+             double *tt1, double *tt2);
+int eraUt1utc(double ut11, double ut12, double dut1,
+              double *utc1, double *utc2);
+int eraUtctai(double utc1, double utc2, double *tai1, double *tai2);
+int eraUtcut1(double utc1, double utc2, double dut1,
+              double *ut11, double *ut12);
+
+/* VectorMatrix/AngleOps */
+void eraA2af(int ndp, double angle, char *sign, int idmsf[4]);
+void eraA2tf(int ndp, double angle, char *sign, int ihmsf[4]);
+int eraAf2a(char s, int ideg, int iamin, double asec, double *rad);
+double eraAnp(double a);
+double eraAnpm(double a);
+void eraD2tf(int ndp, double days, char *sign, int ihmsf[4]);
+int eraTf2a(char s, int ihour, int imin, double sec, double *rad);
+int eraTf2d(char s, int ihour, int imin, double sec, double *days);
+
+/* VectorMatrix/BuildRotations */
+void eraRx(double phi, double r[3][3]);
+void eraRy(double theta, double r[3][3]);
+void eraRz(double psi, double r[3][3]);
+
+/* VectorMatrix/CopyExtendExtract */
+void eraCp(double p[3], double c[3]);
+void eraCpv(double pv[2][3], double c[2][3]);
+void eraCr(double r[3][3], double c[3][3]);
+void eraP2pv(double p[3], double pv[2][3]);
+void eraPv2p(double pv[2][3], double p[3]);
+
+/* VectorMatrix/Initialization */
+void eraIr(double r[3][3]);
+void eraZp(double p[3]);
+void eraZpv(double pv[2][3]);
+void eraZr(double r[3][3]);
+
+/* VectorMatrix/MatrixOps */
+void eraRxr(double a[3][3], double b[3][3], double atb[3][3]);
+void eraTr(double r[3][3], double rt[3][3]);
+
+/* VectorMatrix/MatrixVectorProducts */
+void eraRxp(double r[3][3], double p[3], double rp[3]);
+void eraRxpv(double r[3][3], double pv[2][3], double rpv[2][3]);
+void eraTrxp(double r[3][3], double p[3], double trp[3]);
+void eraTrxpv(double r[3][3], double pv[2][3], double trpv[2][3]);
+
+/* VectorMatrix/RotationVectors */
+void eraRm2v(double r[3][3], double w[3]);
+void eraRv2m(double w[3], double r[3][3]);
+
+/* VectorMatrix/SeparationAndAngle */
+double eraPap(double a[3], double b[3]);
+double eraPas(double al, double ap, double bl, double bp);
+double eraSepp(double a[3], double b[3]);
+double eraSeps(double al, double ap, double bl, double bp);
+
+/* VectorMatrix/SphericalCartesian */
+void eraC2s(double p[3], double *theta, double *phi);
+void eraP2s(double p[3], double *theta, double *phi, double *r);
+void eraPv2s(double pv[2][3],
+             double *theta, double *phi, double *r,
+             double *td, double *pd, double *rd);
+void eraS2c(double theta, double phi, double c[3]);
+void eraS2p(double theta, double phi, double r, double p[3]);
+void eraS2pv(double theta, double phi, double r,
+             double td, double pd, double rd,
+             double pv[2][3]);
+
+/* VectorMatrix/VectorOps */
+double eraPdp(double a[3], double b[3]);
+double eraPm(double p[3]);
+void eraPmp(double a[3], double b[3], double amb[3]);
+void eraPn(double p[3], double *r, double u[3]);
+void eraPpp(double a[3], double b[3], double apb[3]);
+void eraPpsp(double a[3], double s, double b[3], double apsb[3]);
+void eraPvdpv(double a[2][3], double b[2][3], double adb[2]);
+void eraPvm(double pv[2][3], double *r, double *s);
+void eraPvmpv(double a[2][3], double b[2][3], double amb[2][3]);
+void eraPvppv(double a[2][3], double b[2][3], double apb[2][3]);
+void eraPvu(double dt, double pv[2][3], double upv[2][3]);
+void eraPvup(double dt, double pv[2][3], double p[3]);
+void eraPvxpv(double a[2][3], double b[2][3], double axb[2][3]);
+void eraPxp(double a[3], double b[3], double axb[3]);
+void eraS2xpv(double s1, double s2, double pv[2][3], double spv[2][3]);
+void eraSxp(double s, double p[3], double sp[3]);
+void eraSxpv(double s, double pv[2][3], double spv[2][3]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/erfam.h b/ast/erfa/erfam.h
new file mode 100644
index 0000000..750c4c2
--- /dev/null
+++ b/ast/erfa/erfam.h
@@ -0,0 +1,208 @@
+#ifndef ERFAMHDEF
+#define ERFAMHDEF
+
+/*
+**  - - - - - - - -
+**   e r f a m . h
+**  - - - - - - - -
+**
+**  Macros used by ERFA library.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+
+/* Star-independent astrometry parameters */
+typedef struct {
+   double pmt;        /* PM time interval (SSB, Julian years) */
+   double eb[3];      /* SSB to observer (vector, au) */
+   double eh[3];      /* Sun to observer (unit vector) */
+   double em;         /* distance from Sun to observer (au) */
+   double v[3];       /* barycentric observer velocity (vector, c) */
+   double bm1;        /* sqrt(1-|v|^2): reciprocal of Lorenz factor */
+   double bpn[3][3];  /* bias-precession-nutation matrix */
+   double along;      /* longitude + s' + dERA(DUT) (radians) */
+   double phi;        /* geodetic latitude (radians) */
+   double xpl;        /* polar motion xp wrt local meridian (radians) */
+   double ypl;        /* polar motion yp wrt local meridian (radians) */
+   double sphi;       /* sine of geodetic latitude */
+   double cphi;       /* cosine of geodetic latitude */
+   double diurab;     /* magnitude of diurnal aberration vector */
+   double eral;       /* "local" Earth rotation angle (radians) */
+   double refa;       /* refraction constant A (radians) */
+   double refb;       /* refraction constant B (radians) */
+} eraASTROM;
+/* (Vectors eb, eh, em and v are all with respect to BCRS axes.) */
+
+/* Body parameters for light deflection */
+typedef struct {
+   double bm;         /* mass of the body (solar masses) */
+   double dl;         /* deflection limiter (radians^2/2) */
+   double pv[2][3];   /* barycentric PV of the body (au, au/day) */
+} eraLDBODY;
+
+/* Pi */
+#define ERFA_DPI (3.141592653589793238462643)
+
+/* 2Pi */
+#define ERFA_D2PI (6.283185307179586476925287)
+
+/* Radians to degrees */
+#define ERFA_DR2D (57.29577951308232087679815)
+
+/* Degrees to radians */
+#define ERFA_DD2R (1.745329251994329576923691e-2)
+
+/* Radians to arcseconds */
+#define ERFA_DR2AS (206264.8062470963551564734)
+
+/* Arcseconds to radians */
+#define ERFA_DAS2R (4.848136811095359935899141e-6)
+
+/* Seconds of time to radians */
+#define ERFA_DS2R (7.272205216643039903848712e-5)
+
+/* Arcseconds in a full circle */
+#define ERFA_TURNAS (1296000.0)
+
+/* Milliarcseconds to radians */
+#define ERFA_DMAS2R (ERFA_DAS2R / 1e3)
+
+/* Length of tropical year B1900 (days) */
+#define ERFA_DTY (365.242198781)
+
+/* Seconds per day. */
+#define ERFA_DAYSEC (86400.0)
+
+/* Days per Julian year */
+#define ERFA_DJY (365.25)
+
+/* Days per Julian century */
+#define ERFA_DJC (36525.0)
+
+/* Days per Julian millennium */
+#define ERFA_DJM (365250.0)
+
+/* Reference epoch (J2000.0), Julian Date */
+#define ERFA_DJ00 (2451545.0)
+
+/* Julian Date of Modified Julian Date zero */
+#define ERFA_DJM0 (2400000.5)
+
+/* Reference epoch (J2000.0), Modified Julian Date */
+#define ERFA_DJM00 (51544.5)
+
+/* 1977 Jan 1.0 as MJD */
+#define ERFA_DJM77 (43144.0)
+
+/* TT minus TAI (s) */
+#define ERFA_TTMTAI (32.184)
+
+/* Astronomical unit (m) */
+#define ERFA_DAU (149597870e3)
+
+/* Speed of light (m/s) */
+#define ERFA_CMPS 299792458.0
+
+/* Light time for 1 au (s) */
+#define ERFA_AULT 499.004782
+
+/* Speed of light (AU per day) */
+#define ERFA_DC (ERFA_DAYSEC / ERFA_AULT)
+
+/* L_G = 1 - d(TT)/d(TCG) */
+#define ERFA_ELG (6.969290134e-10)
+
+/* L_B = 1 - d(TDB)/d(TCB), and TDB (s) at TAI 1977/1/1.0 */
+#define ERFA_ELB (1.550519768e-8)
+#define ERFA_TDB0 (-6.55e-5)
+
+/* Schwarzschild radius of the Sun (au) */
+/* = 2 * 1.32712440041e20 / (2.99792458e8)^2 / 1.49597870700e11 */
+#define ERFA_SRS 1.97412574336e-8
+
+/* ERFA_DINT(A) - truncate to nearest whole number towards zero (double) */
+#define ERFA_DINT(A) ((A)<0.0?ceil(A):floor(A))
+
+/* ERFA_DNINT(A) - round to nearest whole number (double) */
+#define ERFA_DNINT(A) ((A)<0.0?ceil((A)-0.5):floor((A)+0.5))
+
+/* ERFA_DSIGN(A,B) - magnitude of A with sign of B (double) */
+#define ERFA_DSIGN(A,B) ((B)<0.0?-fabs(A):fabs(A))
+
+/* max(A,B) - larger (most +ve) of two numbers (generic) */
+#define ERFA_GMAX(A,B) (((A)>(B))?(A):(B))
+
+/* min(A,B) - smaller (least +ve) of two numbers (generic) */
+#define ERFA_GMIN(A,B) (((A)<(B))?(A):(B))
+
+/* Reference ellipsoids */
+#define ERFA_WGS84 1
+#define ERFA_GRS80 2
+#define ERFA_WGS72 3
+
+#endif
+
+
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fad03.c b/ast/erfa/fad03.c
new file mode 100644
index 0000000..421f937
--- /dev/null
+++ b/ast/erfa/fad03.c
@@ -0,0 +1,112 @@
+#include "erfa.h"
+
+double eraFad03(double t)
+/*
+**  - - - - - - - - -
+**   e r a F a d 0 3
+**  - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean elongation of the Moon from the Sun.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    D, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     is from Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean elongation of the Moon from the Sun (IERS Conventions 2003). */
+   a = fmod(          1072260.703692 +
+             t * ( 1602961601.2090 +
+             t * (        - 6.3706 +
+             t * (          0.006593 +
+             t * (        - 0.00003169 ) ) ) ), ERFA_TURNAS ) * ERFA_DAS2R;
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fae03.c b/ast/erfa/fae03.c
new file mode 100644
index 0000000..e40b1c9
--- /dev/null
+++ b/ast/erfa/fae03.c
@@ -0,0 +1,111 @@
+#include "erfa.h"
+
+double eraFae03(double t)
+/*
+**  - - - - - - - - -
+**   e r a F a e 0 3
+**  - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean longitude of Earth.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    mean longitude of Earth, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     comes from Souchay et al. (1999) after Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**     Souchay, J., Loysel, B., Kinoshita, H., Folgueira, M. 1999,
+**     Astron.Astrophys.Supp.Ser. 135, 111
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean longitude of Earth (IERS Conventions 2003). */
+   a = fmod(1.753470314 + 628.3075849991 * t, ERFA_D2PI);
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/faf03.c b/ast/erfa/faf03.c
new file mode 100644
index 0000000..aa7850a
--- /dev/null
+++ b/ast/erfa/faf03.c
@@ -0,0 +1,115 @@
+#include "erfa.h"
+
+double eraFaf03(double t)
+/*
+**  - - - - - - - - -
+**   e r a F a f 0 3
+**  - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean longitude of the Moon minus mean longitude of the ascending
+**  node.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    F, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     is from Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean longitude of the Moon minus that of the ascending node */
+/* (IERS Conventions 2003).                                    */
+   a = fmod(           335779.526232 +
+             t * ( 1739527262.8478 +
+             t * (       - 12.7512 +
+             t * (        - 0.001037 +
+             t * (          0.00000417 ) ) ) ), ERFA_TURNAS ) * ERFA_DAS2R;
+
+   return a;
+
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/faju03.c b/ast/erfa/faju03.c
new file mode 100644
index 0000000..9b9298f
--- /dev/null
+++ b/ast/erfa/faju03.c
@@ -0,0 +1,111 @@
+#include "erfa.h"
+
+double eraFaju03(double t)
+/*
+**  - - - - - - - - - -
+**   e r a F a j u 0 3
+**  - - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean longitude of Jupiter.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    mean longitude of Jupiter, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     comes from Souchay et al. (1999) after Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**     Souchay, J., Loysel, B., Kinoshita, H., Folgueira, M. 1999,
+**     Astron.Astrophys.Supp.Ser. 135, 111
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean longitude of Jupiter (IERS Conventions 2003). */
+   a = fmod(0.599546497 + 52.9690962641 * t, ERFA_D2PI);
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fal03.c b/ast/erfa/fal03.c
new file mode 100644
index 0000000..1d80d20
--- /dev/null
+++ b/ast/erfa/fal03.c
@@ -0,0 +1,112 @@
+#include "erfa.h"
+
+double eraFal03(double t)
+/*
+**  - - - - - - - - -
+**   e r a F a l 0 3
+**  - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean anomaly of the Moon.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    l, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     is from Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean anomaly of the Moon (IERS Conventions 2003). */
+   a = fmod(           485868.249036  +
+             t * ( 1717915923.2178 +
+             t * (         31.8792 +
+             t * (          0.051635 +
+             t * (        - 0.00024470 ) ) ) ), ERFA_TURNAS ) * ERFA_DAS2R;
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/falp03.c b/ast/erfa/falp03.c
new file mode 100644
index 0000000..4080809
--- /dev/null
+++ b/ast/erfa/falp03.c
@@ -0,0 +1,112 @@
+#include "erfa.h"
+
+double eraFalp03(double t)
+/*
+**  - - - - - - - - - -
+**   e r a F a l p 0 3
+**  - - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean anomaly of the Sun.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    l', radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     is from Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean anomaly of the Sun (IERS Conventions 2003). */
+   a = fmod(         1287104.793048 +
+             t * ( 129596581.0481 +
+             t * (       - 0.5532 +
+             t * (         0.000136 +
+             t * (       - 0.00001149 ) ) ) ), ERFA_TURNAS ) * ERFA_DAS2R;
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fama03.c b/ast/erfa/fama03.c
new file mode 100644
index 0000000..21bff75
--- /dev/null
+++ b/ast/erfa/fama03.c
@@ -0,0 +1,111 @@
+#include "erfa.h"
+
+double eraFama03(double t)
+/*
+**  - - - - - - - - - -
+**   e r a F a m a 0 3
+**  - - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean longitude of Mars.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    mean longitude of Mars, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     comes from Souchay et al. (1999) after Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**     Souchay, J., Loysel, B., Kinoshita, H., Folgueira, M. 1999,
+**     Astron.Astrophys.Supp.Ser. 135, 111
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean longitude of Mars (IERS Conventions 2003). */
+   a = fmod(6.203480913 + 334.0612426700 * t, ERFA_D2PI);
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fame03.c b/ast/erfa/fame03.c
new file mode 100644
index 0000000..c28ab0b
--- /dev/null
+++ b/ast/erfa/fame03.c
@@ -0,0 +1,111 @@
+#include "erfa.h"
+
+double eraFame03(double t)
+/*
+**  - - - - - - - - - -
+**   e r a F a m e 0 3
+**  - - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean longitude of Mercury.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    mean longitude of Mercury, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     comes from Souchay et al. (1999) after Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**     Souchay, J., Loysel, B., Kinoshita, H., Folgueira, M. 1999,
+**     Astron.Astrophys.Supp.Ser. 135, 111
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean longitude of Mercury (IERS Conventions 2003). */
+   a = fmod(4.402608842 + 2608.7903141574 * t, ERFA_D2PI);
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fane03.c b/ast/erfa/fane03.c
new file mode 100644
index 0000000..682e7bf
--- /dev/null
+++ b/ast/erfa/fane03.c
@@ -0,0 +1,108 @@
+#include "erfa.h"
+
+double eraFane03(double t)
+/*
+**  - - - - - - - - - -
+**   e r a F a n e 0 3
+**  - - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean longitude of Neptune.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    mean longitude of Neptune, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     is adapted from Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean longitude of Neptune (IERS Conventions 2003). */
+   a = fmod(5.311886287 + 3.8133035638 * t, ERFA_D2PI);
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/faom03.c b/ast/erfa/faom03.c
new file mode 100644
index 0000000..ff6b3e9
--- /dev/null
+++ b/ast/erfa/faom03.c
@@ -0,0 +1,113 @@
+#include "erfa.h"
+
+double eraFaom03(double t)
+/*
+**  - - - - - - - - - -
+**   e r a F a o m 0 3
+**  - - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean longitude of the Moon's ascending node.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    Omega, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     is from Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean longitude of the Moon's ascending node */
+/* (IERS Conventions 2003).                    */
+   a = fmod(          450160.398036 +
+             t * ( - 6962890.5431 +
+             t * (         7.4722 +
+             t * (         0.007702 +
+             t * (       - 0.00005939 ) ) ) ), ERFA_TURNAS ) * ERFA_DAS2R;
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fapa03.c b/ast/erfa/fapa03.c
new file mode 100644
index 0000000..aae2af4
--- /dev/null
+++ b/ast/erfa/fapa03.c
@@ -0,0 +1,112 @@
+#include "erfa.h"
+
+double eraFapa03(double t)
+/*
+**  - - - - - - - - - -
+**   e r a F a p a 0 3
+**  - - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  general accumulated precession in longitude.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    general precession in longitude, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003).  It
+**     is taken from Kinoshita & Souchay (1990) and comes originally
+**     from Lieske et al. (1977).
+**
+**  References:
+**
+**     Kinoshita, H. and Souchay J. 1990, Celest.Mech. and Dyn.Astron.
+**     48, 187
+**
+**     Lieske, J.H., Lederle, T., Fricke, W. & Morando, B. 1977,
+**     Astron.Astrophys. 58, 1-16
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* General accumulated precession in longitude. */
+   a = (0.024381750 + 0.00000538691 * t) * t;
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fasa03.c b/ast/erfa/fasa03.c
new file mode 100644
index 0000000..d5e8cbe
--- /dev/null
+++ b/ast/erfa/fasa03.c
@@ -0,0 +1,111 @@
+#include "erfa.h"
+
+double eraFasa03(double t)
+/*
+**  - - - - - - - - - -
+**   e r a F a s a 0 3
+**  - - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean longitude of Saturn.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    mean longitude of Saturn, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     comes from Souchay et al. (1999) after Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**     Souchay, J., Loysel, B., Kinoshita, H., Folgueira, M. 1999,
+**     Astron.Astrophys.Supp.Ser. 135, 111
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean longitude of Saturn (IERS Conventions 2003). */
+   a = fmod(0.874016757 + 21.3299104960 * t, ERFA_D2PI);
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/faur03.c b/ast/erfa/faur03.c
new file mode 100644
index 0000000..cce0f83
--- /dev/null
+++ b/ast/erfa/faur03.c
@@ -0,0 +1,108 @@
+#include "erfa.h"
+
+double eraFaur03(double t)
+/*
+**  - - - - - - - - - -
+**   e r a F a u r 0 3
+**  - - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean longitude of Uranus.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned  (function value):
+**           double    mean longitude of Uranus, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     is adapted from Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean longitude of Uranus (IERS Conventions 2003). */
+   a = fmod(5.481293872 + 7.4781598567 * t, ERFA_D2PI);
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fave03.c b/ast/erfa/fave03.c
new file mode 100644
index 0000000..b91678c
--- /dev/null
+++ b/ast/erfa/fave03.c
@@ -0,0 +1,111 @@
+#include "erfa.h"
+
+double eraFave03(double t)
+/*
+**  - - - - - - - - - -
+**   e r a F a v e 0 3
+**  - - - - - - - - - -
+**
+**  Fundamental argument, IERS Conventions (2003):
+**  mean longitude of Venus.
+**
+**  Given:
+**     t     double    TDB, Julian centuries since J2000.0 (Note 1)
+**
+**  Returned (function value):
+**           double    mean longitude of Venus, radians (Note 2)
+**
+**  Notes:
+**
+**  1) Though t is strictly TDB, it is usually more convenient to use
+**     TT, which makes no significant difference.
+**
+**  2) The expression used is as adopted in IERS Conventions (2003) and
+**     comes from Souchay et al. (1999) after Simon et al. (1994).
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**     Souchay, J., Loysel, B., Kinoshita, H., Folgueira, M. 1999,
+**     Astron.Astrophys.Supp.Ser. 135, 111
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double a;
+
+
+/* Mean longitude of Venus (IERS Conventions 2003). */
+   a = fmod(3.176146697 + 1021.3285546211 * t, ERFA_D2PI);
+
+   return a;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fk52h.c b/ast/erfa/fk52h.c
new file mode 100644
index 0000000..e169cbe
--- /dev/null
+++ b/ast/erfa/fk52h.c
@@ -0,0 +1,152 @@
+#include "erfa.h"
+
+void eraFk52h(double r5, double d5,
+              double dr5, double dd5, double px5, double rv5,
+              double *rh, double *dh,
+              double *drh, double *ddh, double *pxh, double *rvh)
+/*
+**  - - - - - - - - -
+**   e r a F k 5 2 h
+**  - - - - - - - - -
+**
+**  Transform FK5 (J2000.0) star data into the Hipparcos system.
+**
+**  Given (all FK5, equinox J2000.0, epoch J2000.0):
+**     r5      double    RA (radians)
+**     d5      double    Dec (radians)
+**     dr5     double    proper motion in RA (dRA/dt, rad/Jyear)
+**     dd5     double    proper motion in Dec (dDec/dt, rad/Jyear)
+**     px5     double    parallax (arcsec)
+**     rv5     double    radial velocity (km/s, positive = receding)
+**
+**  Returned (all Hipparcos, epoch J2000.0):
+**     rh      double    RA (radians)
+**     dh      double    Dec (radians)
+**     drh     double    proper motion in RA (dRA/dt, rad/Jyear)
+**     ddh     double    proper motion in Dec (dDec/dt, rad/Jyear)
+**     pxh     double    parallax (arcsec)
+**     rvh     double    radial velocity (km/s, positive = receding)
+**
+**  Notes:
+**
+**  1) This function transforms FK5 star positions and proper motions
+**     into the system of the Hipparcos catalog.
+**
+**  2) The proper motions in RA are dRA/dt rather than
+**     cos(Dec)*dRA/dt, and are per year rather than per century.
+**
+**  3) The FK5 to Hipparcos transformation is modeled as a pure
+**     rotation and spin;  zonal errors in the FK5 catalog are not
+**     taken into account.
+**
+**  4) See also eraH2fk5, eraFk5hz, eraHfk5z.
+**
+**  Called:
+**     eraStarpv    star catalog data to space motion pv-vector
+**     eraFk5hip    FK5 to Hipparcos rotation and spin
+**     eraRxp       product of r-matrix and p-vector
+**     eraPxp       vector product of two p-vectors
+**     eraPpp       p-vector plus p-vector
+**     eraPvstar    space motion pv-vector to star catalog data
+**
+**  Reference:
+**
+**     F.Mignard & M.Froeschle, Astron. Astrophys. 354, 732-739 (2000).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int i;
+   double pv5[2][3], r5h[3][3], s5h[3], wxp[3], vv[3], pvh[2][3];
+
+
+/* FK5 barycentric position/velocity pv-vector (normalized). */
+   eraStarpv(r5, d5, dr5, dd5, px5, rv5, pv5);
+
+/* FK5 to Hipparcos orientation matrix and spin vector. */
+   eraFk5hip(r5h, s5h);
+
+/* Make spin units per day instead of per year. */
+   for ( i = 0; i < 3; s5h[i++] /= 365.25 );
+
+/* Orient the FK5 position into the Hipparcos system. */
+   eraRxp(r5h, pv5[0], pvh[0]);
+
+/* Apply spin to the position giving an extra space motion component. */
+   eraPxp(pv5[0], s5h, wxp);
+
+/* Add this component to the FK5 space motion. */
+   eraPpp(wxp, pv5[1], vv);
+
+/* Orient the FK5 space motion into the Hipparcos system. */
+   eraRxp(r5h, vv, pvh[1]);
+
+/* Hipparcos pv-vector to spherical. */
+   eraPvstar(pvh, rh, dh, drh, ddh, pxh, rvh);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fk5hip.c b/ast/erfa/fk5hip.c
new file mode 100644
index 0000000..5a05d4d
--- /dev/null
+++ b/ast/erfa/fk5hip.c
@@ -0,0 +1,135 @@
+#include "erfa.h"
+
+void eraFk5hip(double r5h[3][3], double s5h[3])
+/*
+**  - - - - - - - - - -
+**   e r a F k 5 h i p
+**  - - - - - - - - - -
+**
+**  FK5 to Hipparcos rotation and spin.
+**
+**  Returned:
+**     r5h   double[3][3]  r-matrix: FK5 rotation wrt Hipparcos (Note 2)
+**     s5h   double[3]     r-vector: FK5 spin wrt Hipparcos (Note 3)
+**
+**  Notes:
+**
+**  1) This function models the FK5 to Hipparcos transformation as a
+**     pure rotation and spin;  zonal errors in the FK5 catalogue are
+**     not taken into account.
+**
+**  2) The r-matrix r5h operates in the sense:
+**
+**           P_Hipparcos = r5h x P_FK5
+**
+**     where P_FK5 is a p-vector in the FK5 frame, and P_Hipparcos is
+**     the equivalent Hipparcos p-vector.
+**
+**  3) The r-vector s5h represents the time derivative of the FK5 to
+**     Hipparcos rotation.  The units are radians per year (Julian,
+**     TDB).
+**
+**  Called:
+**     eraRv2m      r-vector to r-matrix
+**
+**  Reference:
+**
+**     F.Mignard & M.Froeschle, Astron. Astrophys. 354, 732-739 (2000).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double v[3];
+
+/* FK5 wrt Hipparcos orientation and spin (radians, radians/year) */
+   double epx, epy, epz;
+   double omx, omy, omz;
+
+
+   epx = -19.9e-3 * ERFA_DAS2R;
+   epy =  -9.1e-3 * ERFA_DAS2R;
+   epz =  22.9e-3 * ERFA_DAS2R;
+
+   omx = -0.30e-3 * ERFA_DAS2R;
+   omy =  0.60e-3 * ERFA_DAS2R;
+   omz =  0.70e-3 * ERFA_DAS2R;
+
+/* FK5 to Hipparcos orientation expressed as an r-vector. */
+   v[0] = epx;
+   v[1] = epy;
+   v[2] = epz;
+
+/* Re-express as an r-matrix. */
+   eraRv2m(v, r5h);
+
+/* Hipparcos wrt FK5 spin expressed as an r-vector. */
+   s5h[0] = omx;
+   s5h[1] = omy;
+   s5h[2] = omz;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fk5hz.c b/ast/erfa/fk5hz.c
new file mode 100644
index 0000000..4134f7c
--- /dev/null
+++ b/ast/erfa/fk5hz.c
@@ -0,0 +1,169 @@
+#include "erfa.h"
+
+void eraFk5hz(double r5, double d5, double date1, double date2,
+              double *rh, double *dh)
+/*
+**  - - - - - - - - -
+**   e r a F k 5 h z
+**  - - - - - - - - -
+**
+**  Transform an FK5 (J2000.0) star position into the system of the
+**  Hipparcos catalogue, assuming zero Hipparcos proper motion.
+**
+**  Given:
+**     r5           double   FK5 RA (radians), equinox J2000.0, at date
+**     d5           double   FK5 Dec (radians), equinox J2000.0, at date
+**     date1,date2  double   TDB date (Notes 1,2)
+**
+**  Returned:
+**     rh           double   Hipparcos RA (radians)
+**     dh           double   Hipparcos Dec (radians)
+**
+**  Notes:
+**
+**  1) This function converts a star position from the FK5 system to
+**     the Hipparcos system, in such a way that the Hipparcos proper
+**     motion is zero.  Because such a star has, in general, a non-zero
+**     proper motion in the FK5 system, the function requires the date
+**     at which the position in the FK5 system was determined.
+**
+**  2) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  3) The FK5 to Hipparcos transformation is modeled as a pure
+**     rotation and spin;  zonal errors in the FK5 catalogue are not
+**     taken into account.
+**
+**  4) The position returned by this function is in the Hipparcos
+**     reference system but at date date1+date2.
+**
+**  5) See also eraFk52h, eraH2fk5, eraHfk5z.
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraFk5hip    FK5 to Hipparcos rotation and spin
+**     eraSxp       multiply p-vector by scalar
+**     eraRv2m      r-vector to r-matrix
+**     eraTrxp      product of transpose of r-matrix and p-vector
+**     eraPxp       vector product of two p-vectors
+**     eraC2s       p-vector to spherical
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  Reference:
+**
+**     F.Mignard & M.Froeschle, 2000, Astron.Astrophys. 354, 732-739.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, p5e[3], r5h[3][3], s5h[3], vst[3], rst[3][3], p5[3],
+          ph[3], w;
+
+
+/* Interval from given date to fundamental epoch J2000.0 (JY). */
+   t = - ((date1 - ERFA_DJ00) + date2) / ERFA_DJY;
+
+/* FK5 barycentric position vector. */
+   eraS2c(r5, d5, p5e);
+
+/* FK5 to Hipparcos orientation matrix and spin vector. */
+   eraFk5hip(r5h, s5h);
+
+/* Accumulated Hipparcos wrt FK5 spin over that interval. */
+   eraSxp(t, s5h, vst);
+
+/* Express the accumulated spin as a rotation matrix. */
+   eraRv2m(vst, rst);
+
+/* Derotate the vector's FK5 axes back to date. */
+   eraTrxp(rst, p5e, p5);
+
+/* Rotate the vector into the Hipparcos system. */
+   eraRxp(r5h, p5, ph);
+
+/* Hipparcos vector to spherical. */
+   eraC2s(ph, &w, dh);
+   *rh = eraAnp(w);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fw2m.c b/ast/erfa/fw2m.c
new file mode 100644
index 0000000..d1b73d2
--- /dev/null
+++ b/ast/erfa/fw2m.c
@@ -0,0 +1,143 @@
+#include "erfa.h"
+
+void eraFw2m(double gamb, double phib, double psi, double eps,
+             double r[3][3])
+/*
+**  - - - - - - - -
+**   e r a F w 2 m
+**  - - - - - - - -
+**
+**  Form rotation matrix given the Fukushima-Williams angles.
+**
+**  Given:
+**     gamb     double         F-W angle gamma_bar (radians)
+**     phib     double         F-W angle phi_bar (radians)
+**     psi      double         F-W angle psi (radians)
+**     eps      double         F-W angle epsilon (radians)
+**
+**  Returned:
+**     r        double[3][3]   rotation matrix
+**
+**  Notes:
+**
+**  1) Naming the following points:
+**
+**           e = J2000.0 ecliptic pole,
+**           p = GCRS pole,
+**           E = ecliptic pole of date,
+**     and   P = CIP,
+**
+**     the four Fukushima-Williams angles are as follows:
+**
+**        gamb = gamma = epE
+**        phib = phi = pE
+**        psi = psi = pEP
+**        eps = epsilon = EP
+**
+**  2) The matrix representing the combined effects of frame bias,
+**     precession and nutation is:
+**
+**        NxPxB = R_1(-eps).R_3(-psi).R_1(phib).R_3(gamb)
+**
+**  3) Three different matrices can be constructed, depending on the
+**     supplied angles:
+**
+**     o  To obtain the nutation x precession x frame bias matrix,
+**        generate the four precession angles, generate the nutation
+**        components and add them to the psi_bar and epsilon_A angles,
+**        and call the present function.
+**
+**     o  To obtain the precession x frame bias matrix, generate the
+**        four precession angles and call the present function.
+**
+**     o  To obtain the frame bias matrix, generate the four precession
+**        angles for date J2000.0 and call the present function.
+**
+**     The nutation-only and precession-only matrices can if necessary
+**     be obtained by combining these three appropriately.
+**
+**  Called:
+**     eraIr        initialize r-matrix to identity
+**     eraRz        rotate around Z-axis
+**     eraRx        rotate around X-axis
+**
+**  Reference:
+**
+**     Hilton, J. et al., 2006, Celest.Mech.Dyn.Astron. 94, 351
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Construct the matrix. */
+   eraIr(r);
+   eraRz(gamb, r);
+   eraRx(phib, r);
+   eraRz(-psi, r);
+   eraRx(-eps, r);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/fw2xy.c b/ast/erfa/fw2xy.c
new file mode 100644
index 0000000..b4e0693
--- /dev/null
+++ b/ast/erfa/fw2xy.c
@@ -0,0 +1,130 @@
+#include "erfa.h"
+
+void eraFw2xy(double gamb, double phib, double psi, double eps,
+              double *x, double *y)
+/*
+**  - - - - - - - - -
+**   e r a F w 2 x y
+**  - - - - - - - - -
+**
+**  CIP X,Y given Fukushima-Williams bias-precession-nutation angles.
+**
+**  Given:
+**     gamb     double    F-W angle gamma_bar (radians)
+**     phib     double    F-W angle phi_bar (radians)
+**     psi      double    F-W angle psi (radians)
+**     eps      double    F-W angle epsilon (radians)
+**
+**  Returned:
+**     x,y      double    CIP unit vector X,Y
+**
+**  Notes:
+**
+**  1) Naming the following points:
+**
+**           e = J2000.0 ecliptic pole,
+**           p = GCRS pole
+**           E = ecliptic pole of date,
+**     and   P = CIP,
+**
+**     the four Fukushima-Williams angles are as follows:
+**
+**        gamb = gamma = epE
+**        phib = phi = pE
+**        psi = psi = pEP
+**        eps = epsilon = EP
+**
+**  2) The matrix representing the combined effects of frame bias,
+**     precession and nutation is:
+**
+**        NxPxB = R_1(-epsA).R_3(-psi).R_1(phib).R_3(gamb)
+**
+**     The returned values x,y are elements [2][0] and [2][1] of the
+**     matrix.  Near J2000.0, they are essentially angles in radians.
+**
+**  Called:
+**     eraFw2m      F-W angles to r-matrix
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**
+**  Reference:
+**
+**     Hilton, J. et al., 2006, Celest.Mech.Dyn.Astron. 94, 351
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double r[3][3];
+
+
+/* Form NxPxB matrix. */
+   eraFw2m(gamb, phib, psi, eps, r);
+
+/* Extract CIP X,Y. */
+   eraBpn2xy(r, x, y);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/g2icrs.c b/ast/erfa/g2icrs.c
new file mode 100644
index 0000000..4612726
--- /dev/null
+++ b/ast/erfa/g2icrs.c
@@ -0,0 +1,170 @@
+#include "erfa.h"
+
+void eraG2icrs ( double dl, double db, double *dr, double *dd )
+/*
+**  - - - - - - - - - -
+**   e r a G 2 i c r s
+**  - - - - - - - - - -
+**
+**  Transformation from Galactic Coordinates to ICRS.
+**
+**  Given:
+**     dl     double      galactic longitude (radians)
+**     db     double      galactic latitude (radians)
+**
+**  Returned:
+**     dr     double      ICRS right ascension (radians)
+**     dd     double      ICRS declination (radians)
+**
+**  Notes:
+**
+**  1) The IAU 1958 system of Galactic coordinates was defined with
+**     respect to the now obsolete reference system FK4 B1950.0.  When
+**     interpreting the system in a modern context, several factors have
+**     to be taken into account:
+**
+**     . The inclusion in FK4 positions of the E-terms of aberration.
+**
+**     . The distortion of the FK4 proper motion system by differential
+**       Galactic rotation.
+**
+**     . The use of the B1950.0 equinox rather than the now-standard
+**       J2000.0.
+**
+**     . The frame bias between ICRS and the J2000.0 mean place system.
+**
+**     The Hipparcos Catalogue (Perryman & ESA 1997) provides a rotation
+**     matrix that transforms directly between ICRS and Galactic
+**     coordinates with the above factors taken into account.  The
+**     matrix is derived from three angles, namely the ICRS coordinates
+**     of the Galactic pole and the longitude of the ascending node of
+**     the galactic equator on the ICRS equator.  They are given in
+**     degrees to five decimal places and for canonical purposes are
+**     regarded as exact.  In the Hipparcos Catalogue the matrix
+**     elements are given to 10 decimal places (about 20 microarcsec).
+**     In the present ERFA function the matrix elements have been
+**     recomputed from the canonical three angles and are given to 30
+**     decimal places.
+**
+**  2) The inverse transformation is performed by the function eraIcrs2g.
+**
+**  Called:
+**     eraAnp       normalize angle into range 0 to 2pi
+**     eraAnpm      normalize angle into range +/- pi
+**     eraS2c       spherical coordinates to unit vector
+**     eraTrxp      product of transpose of r-matrix and p-vector
+**     eraC2s       p-vector to spherical
+**
+**  Reference:
+**     Perryman M.A.C. & ESA, 1997, ESA SP-1200, The Hipparcos and Tycho
+**     catalogues.  Astrometric and photometric star catalogues
+**     derived from the ESA Hipparcos Space Astrometry Mission.  ESA
+**     Publications Division, Noordwijk, Netherlands.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double v1[3], v2[3];
+
+/*
+**  L2,B2 system of galactic coordinates in the form presented in the
+**  Hipparcos Catalogue.  In degrees:
+**
+**  P = 192.85948    right ascension of the Galactic north pole in ICRS
+**  Q =  27.12825    declination of the Galactic north pole in ICRS
+**  R =  32.93192    longitude of the ascending node of the Galactic
+**                   plane on the ICRS equator
+**
+**  ICRS to galactic rotation matrix, obtained by computing
+**  R_3(-R) R_1(pi/2-Q) R_3(pi/2+P) to the full precision shown:
+*/
+   double r[3][3] = { { -0.054875560416215368492398900454,
+                        -0.873437090234885048760383168409,
+                        -0.483835015548713226831774175116 },
+                      { +0.494109427875583673525222371358,
+                        -0.444829629960011178146614061616,
+                        +0.746982244497218890527388004556 },
+                      { -0.867666149019004701181616534570,
+                        -0.198076373431201528180486091412,
+                        +0.455983776175066922272100478348 } };
+
+
+/* Spherical to Cartesian. */
+   eraS2c(dl, db, v1);
+
+/* Galactic to ICRS. */
+   eraTrxp(r, v1, v2);
+
+/* Cartesian to spherical. */
+   eraC2s(v2, dr, dd);
+
+/* Express in conventional ranges. */
+   *dr = eraAnp(*dr);
+   *dd = eraAnpm(*dd);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gc2gd.c b/ast/erfa/gc2gd.c
new file mode 100644
index 0000000..385e7d5
--- /dev/null
+++ b/ast/erfa/gc2gd.c
@@ -0,0 +1,143 @@
+#include "erfa.h"
+
+int eraGc2gd ( int n, double xyz[3],
+               double *elong, double *phi, double *height )
+/*
+**  - - - - - - - - -
+**   e r a G c 2 g d
+**  - - - - - - - - -
+**
+**  Transform geocentric coordinates to geodetic using the specified
+**  reference ellipsoid.
+**
+**  Given:
+**     n       int        ellipsoid identifier (Note 1)
+**     xyz     double[3]  geocentric vector (Note 2)
+**
+**  Returned:
+**     elong   double     longitude (radians, east +ve, Note 3)
+**     phi     double     latitude (geodetic, radians, Note 3)
+**     height  double     height above ellipsoid (geodetic, Notes 2,3)
+**
+**  Returned (function value):
+**            int         status:  0 = OK
+**                                -1 = illegal identifier (Note 3)
+**                                -2 = internal error (Note 3)
+**
+**  Notes:
+**
+**  1) The identifier n is a number that specifies the choice of
+**     reference ellipsoid.  The following are supported:
+**
+**        n    ellipsoid
+**
+**        1     ERFA_WGS84
+**        2     ERFA_GRS80
+**        3     ERFA_WGS72
+**
+**     The n value has no significance outside the ERFA software.  For
+**     convenience, symbols ERFA_WGS84 etc. are defined in erfam.h.
+**
+**  2) The geocentric vector (xyz, given) and height (height, returned)
+**     are in meters.
+**
+**  3) An error status -1 means that the identifier n is illegal.  An
+**     error status -2 is theoretically impossible.  In all error cases,
+**     all three results are set to -1e9.
+**
+**  4) The inverse transformation is performed in the function eraGd2gc.
+**
+**  Called:
+**     eraEform     Earth reference ellipsoids
+**     eraGc2gde    geocentric to geodetic transformation, general
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j;
+   double a, f;
+
+
+/* Obtain reference ellipsoid parameters. */
+   j = eraEform ( n, &a, &f );
+
+/* If OK, transform x,y,z to longitude, geodetic latitude, height. */
+   if ( j == 0 ) {
+      j = eraGc2gde ( a, f, xyz, elong, phi, height );
+      if ( j < 0 ) j = -2;
+   }
+
+/* Deal with any errors. */
+   if ( j < 0 ) {
+      *elong = -1e9;
+      *phi = -1e9;
+      *height = -1e9;
+   }
+
+/* Return the status. */
+   return j;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gc2gde.c b/ast/erfa/gc2gde.c
new file mode 100644
index 0000000..43ed61d
--- /dev/null
+++ b/ast/erfa/gc2gde.c
@@ -0,0 +1,208 @@
+#include "erfa.h"
+
+int eraGc2gde ( double a, double f, double xyz[3],
+                double *elong, double *phi, double *height )
+/*
+**  - - - - - - - - - -
+**   e r a G c 2 g d e
+**  - - - - - - - - - -
+**
+**  Transform geocentric coordinates to geodetic for a reference
+**  ellipsoid of specified form.
+**
+**  Given:
+**     a       double     equatorial radius (Notes 2,4)
+**     f       double     flattening (Note 3)
+**     xyz     double[3]  geocentric vector (Note 4)
+**
+**  Returned:
+**     elong   double     longitude (radians, east +ve)
+**     phi     double     latitude (geodetic, radians)
+**     height  double     height above ellipsoid (geodetic, Note 4)
+**
+**  Returned (function value):
+**             int        status:  0 = OK
+**                                -1 = illegal f
+**                                -2 = illegal a
+**
+**  Notes:
+**
+**  1) This function is based on the GCONV2H Fortran subroutine by
+**     Toshio Fukushima (see reference).
+**
+**  2) The equatorial radius, a, can be in any units, but meters is
+**     the conventional choice.
+**
+**  3) The flattening, f, is (for the Earth) a value around 0.00335,
+**     i.e. around 1/298.
+**
+**  4) The equatorial radius, a, and the geocentric vector, xyz,
+**     must be given in the same units, and determine the units of
+**     the returned height, height.
+**
+**  5) If an error occurs (status < 0), elong, phi and height are
+**     unchanged.
+**
+**  6) The inverse transformation is performed in the function
+**     eraGd2gce.
+**
+**  7) The transformation for a standard ellipsoid (such as ERFA_WGS84) can
+**     more conveniently be performed by calling eraGc2gd, which uses a
+**     numerical code to identify the required A and F values.
+**
+**  Reference:
+**
+**     Fukushima, T., "Transformation from Cartesian to geodetic
+**     coordinates accelerated by Halley's method", J.Geodesy (2006)
+**     79: 689-693
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double aeps2, e2, e4t, ec2, ec, b, x, y, z, p2, absz, p, s0, pn, zc,
+                 c0, c02, c03, s02, s03, a02, a0, a03, d0, f0, b0, s1,
+                 cc, s12, cc2;
+
+
+/* ------------- */
+/* Preliminaries */
+/* ------------- */
+
+/* Validate ellipsoid parameters. */
+   if ( f < 0.0 || f >= 1.0 ) return -1;
+   if ( a <= 0.0 ) return -2;
+
+/* Functions of ellipsoid parameters (with further validation of f). */
+   aeps2 = a*a * 1e-32;
+   e2 = (2.0 - f) * f;
+   e4t = e2*e2 * 1.5;
+   ec2 = 1.0 - e2;
+   if ( ec2 <= 0.0 ) return -1;
+   ec = sqrt(ec2);
+   b = a * ec;
+
+/* Cartesian components. */
+   x = xyz[0];
+   y = xyz[1];
+   z = xyz[2];
+
+/* Distance from polar axis squared. */
+   p2 = x*x + y*y;
+
+/* Longitude. */
+   *elong = p2 > 0.0 ? atan2(y, x) : 0.0;
+
+/* Unsigned z-coordinate. */
+   absz = fabs(z);
+
+/* Proceed unless polar case. */
+   if ( p2 > aeps2 ) {
+
+   /* Distance from polar axis. */
+      p = sqrt(p2);
+
+   /* Normalization. */
+      s0 = absz / a;
+      pn = p / a;
+      zc = ec * s0;
+
+   /* Prepare Newton correction factors. */
+      c0 = ec * pn;
+      c02 = c0 * c0;
+      c03 = c02 * c0;
+      s02 = s0 * s0;
+      s03 = s02 * s0;
+      a02 = c02 + s02;
+      a0 = sqrt(a02);
+      a03 = a02 * a0;
+      d0 = zc*a03 + e2*s03;
+      f0 = pn*a03 - e2*c03;
+
+   /* Prepare Halley correction factor. */
+      b0 = e4t * s02 * c02 * pn * (a0 - ec);
+      s1 = d0*f0 - b0*s0;
+      cc = ec * (f0*f0 - b0*c0);
+
+   /* Evaluate latitude and height. */
+      *phi = atan(s1/cc);
+      s12 = s1 * s1;
+      cc2 = cc * cc;
+      *height = (p*cc + absz*s1 - a * sqrt(ec2*s12 + cc2)) /
+                                                        sqrt(s12 + cc2);
+   } else {
+
+   /* Exception: pole. */
+      *phi = ERFA_DPI / 2.0;
+      *height = absz - b;
+   }
+
+/* Restore sign of latitude. */
+   if ( z < 0 ) *phi = -*phi;
+
+/* OK status. */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gd2gc.c b/ast/erfa/gd2gc.c
new file mode 100644
index 0000000..383c928
--- /dev/null
+++ b/ast/erfa/gd2gc.c
@@ -0,0 +1,142 @@
+#include "erfa.h"
+
+int eraGd2gc ( int n, double elong, double phi, double height,
+               double xyz[3] )
+/*
+**  - - - - - - - - -
+**   e r a G d 2 g c
+**  - - - - - - - - -
+**
+**  Transform geodetic coordinates to geocentric using the specified
+**  reference ellipsoid.
+**
+**  Given:
+**     n       int        ellipsoid identifier (Note 1)
+**     elong   double     longitude (radians, east +ve)
+**     phi     double     latitude (geodetic, radians, Note 3)
+**     height  double     height above ellipsoid (geodetic, Notes 2,3)
+**
+**  Returned:
+**     xyz     double[3]  geocentric vector (Note 2)
+**
+**  Returned (function value):
+**             int        status:  0 = OK
+**                                -1 = illegal identifier (Note 3)
+**                                -2 = illegal case (Note 3)
+**
+**  Notes:
+**
+**  1) The identifier n is a number that specifies the choice of
+**     reference ellipsoid.  The following are supported:
+**
+**        n    ellipsoid
+**
+**        1     ERFA_WGS84
+**        2     ERFA_GRS80
+**        3     ERFA_WGS72
+**
+**     The n value has no significance outside the ERFA software.  For
+**     convenience, symbols ERFA_WGS84 etc. are defined in erfam.h.
+**
+**  2) The height (height, given) and the geocentric vector (xyz,
+**     returned) are in meters.
+**
+**  3) No validation is performed on the arguments elong, phi and
+**     height.  An error status -1 means that the identifier n is
+**     illegal.  An error status -2 protects against cases that would
+**     lead to arithmetic exceptions.  In all error cases, xyz is set
+**     to zeros.
+**
+**  4) The inverse transformation is performed in the function eraGc2gd.
+**
+**  Called:
+**     eraEform     Earth reference ellipsoids
+**     eraGd2gce    geodetic to geocentric transformation, general
+**     eraZp        zero p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j;
+   double a, f;
+
+
+/* Obtain reference ellipsoid parameters. */
+   j = eraEform ( n, &a, &f );
+
+/* If OK, transform longitude, geodetic latitude, height to x,y,z. */
+   if ( j == 0 ) {
+      j = eraGd2gce ( a, f, elong, phi, height, xyz );
+      if ( j != 0 ) j = -2;
+   }
+
+/* Deal with any errors. */
+   if ( j != 0 ) eraZp ( xyz );
+
+/* Return the status. */
+   return j;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gd2gce.c b/ast/erfa/gd2gce.c
new file mode 100644
index 0000000..52b9f0e
--- /dev/null
+++ b/ast/erfa/gd2gce.c
@@ -0,0 +1,146 @@
+#include "erfa.h"
+
+int eraGd2gce ( double a, double f, double elong, double phi,
+                double height, double xyz[3] )
+/*
+**  - - - - - - - - - -
+**   e r a G d 2 g c e
+**  - - - - - - - - - -
+**
+**  Transform geodetic coordinates to geocentric for a reference
+**  ellipsoid of specified form.
+**
+**  Given:
+**     a       double     equatorial radius (Notes 1,4)
+**     f       double     flattening (Notes 2,4)
+**     elong   double     longitude (radians, east +ve)
+**     phi     double     latitude (geodetic, radians, Note 4)
+**     height  double     height above ellipsoid (geodetic, Notes 3,4)
+**
+**  Returned:
+**     xyz     double[3]  geocentric vector (Note 3)
+**
+**  Returned (function value):
+**             int        status:  0 = OK
+**                                -1 = illegal case (Note 4)
+**  Notes:
+**
+**  1) The equatorial radius, a, can be in any units, but meters is
+**     the conventional choice.
+**
+**  2) The flattening, f, is (for the Earth) a value around 0.00335,
+**     i.e. around 1/298.
+**
+**  3) The equatorial radius, a, and the height, height, must be
+**     given in the same units, and determine the units of the
+**     returned geocentric vector, xyz.
+**
+**  4) No validation is performed on individual arguments.  The error
+**     status -1 protects against (unrealistic) cases that would lead
+**     to arithmetic exceptions.  If an error occurs, xyz is unchanged.
+**
+**  5) The inverse transformation is performed in the function
+**     eraGc2gde.
+**
+**  6) The transformation for a standard ellipsoid (such as ERFA_WGS84) can
+**     more conveniently be performed by calling eraGd2gc,  which uses a
+**     numerical code to identify the required a and f values.
+**
+**  References:
+**
+**     Green, R.M., Spherical Astronomy, Cambridge University Press,
+**     (1985) Section 4.5, p96.
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Section 4.22, p202.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double sp, cp, w, d, ac, as, r;
+
+
+/* Functions of geodetic latitude. */
+   sp = sin(phi);
+   cp = cos(phi);
+   w = 1.0 - f;
+   w = w * w;
+   d = cp*cp + w*sp*sp;
+   if ( d <= 0.0 ) return -1;
+   ac = a / sqrt(d);
+   as = w * ac;
+
+/* Geocentric vector. */
+   r = (ac + height) * cp;
+   xyz[0] = r * cos(elong);
+   xyz[1] = r * sin(elong);
+   xyz[2] = (as + height) * sp;
+
+/* Success. */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gmst00.c b/ast/erfa/gmst00.c
new file mode 100644
index 0000000..6b598bb
--- /dev/null
+++ b/ast/erfa/gmst00.c
@@ -0,0 +1,154 @@
+#include "erfa.h"
+
+double eraGmst00(double uta, double utb, double tta, double ttb)
+/*
+**  - - - - - - - - - -
+**   e r a G m s t 0 0
+**  - - - - - - - - - -
+**
+**  Greenwich mean sidereal time (model consistent with IAU 2000
+**  resolutions).
+**
+**  Given:
+**     uta,utb    double    UT1 as a 2-part Julian Date (Notes 1,2)
+**     tta,ttb    double    TT as a 2-part Julian Date (Notes 1,2)
+**
+**  Returned (function value):
+**                double    Greenwich mean sidereal time (radians)
+**
+**  Notes:
+**
+**  1) The UT1 and TT dates uta+utb and tta+ttb respectively, are both
+**     Julian Dates, apportioned in any convenient way between the
+**     argument pairs.  For example, JD=2450123.7 could be expressed in
+**     any of these ways, among others:
+**
+**            Part A         Part B
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable (in the case of UT;  the TT is not at all critical
+**     in this respect).  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  For UT, the date & time
+**     method is best matched to the algorithm that is used by the Earth
+**     Rotation Angle function, called internally:  maximum precision is
+**     delivered when the uta argument is for 0hrs UT1 on the day in
+**     question and the utb argument lies in the range 0 to 1, or vice
+**     versa.
+**
+**  2) Both UT1 and TT are required, UT1 to predict the Earth rotation
+**     and TT to predict the effects of precession.  If UT1 is used for
+**     both purposes, errors of order 100 microarcseconds result.
+**
+**  3) This GMST is compatible with the IAU 2000 resolutions and must be
+**     used only in conjunction with other IAU 2000 compatible
+**     components such as precession-nutation and equation of the
+**     equinoxes.
+**
+**  4) The result is returned in the range 0 to 2pi.
+**
+**  5) The algorithm is from Capitaine et al. (2003) and IERS
+**     Conventions 2003.
+**
+**  Called:
+**     eraEra00     Earth rotation angle, IAU 2000
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  References:
+**
+**     Capitaine, N., Wallace, P.T. and McCarthy, D.D., "Expressions to
+**     implement the IAU 2000 definition of UT1", Astronomy &
+**     Astrophysics, 406, 1135-1149 (2003)
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, gmst;
+
+
+/* TT Julian centuries since J2000.0. */
+   t = ((tta - ERFA_DJ00) + ttb) / ERFA_DJC;
+
+/* Greenwich Mean Sidereal Time, IAU 2000. */
+   gmst = eraAnp(eraEra00(uta, utb) +
+                   (     0.014506   +
+                   (  4612.15739966 +
+                   (     1.39667721 +
+                   (    -0.00009344 +
+                   (     0.00001882 )
+          * t) * t) * t) * t) * ERFA_DAS2R);
+
+   return gmst;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gmst06.c b/ast/erfa/gmst06.c
new file mode 100644
index 0000000..2fcb1a5
--- /dev/null
+++ b/ast/erfa/gmst06.c
@@ -0,0 +1,145 @@
+#include "erfa.h"
+
+double eraGmst06(double uta, double utb, double tta, double ttb)
+/*
+**  - - - - - - - - - -
+**   e r a G m s t 0 6
+**  - - - - - - - - - -
+**
+**  Greenwich mean sidereal time (consistent with IAU 2006 precession).
+**
+**  Given:
+**     uta,utb    double    UT1 as a 2-part Julian Date (Notes 1,2)
+**     tta,ttb    double    TT as a 2-part Julian Date (Notes 1,2)
+**
+**  Returned (function value):
+**                double    Greenwich mean sidereal time (radians)
+**
+**  Notes:
+**
+**  1) The UT1 and TT dates uta+utb and tta+ttb respectively, are both
+**     Julian Dates, apportioned in any convenient way between the
+**     argument pairs.  For example, JD=2450123.7 could be expressed in
+**     any of these ways, among others:
+**
+**            Part A        Part B
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable (in the case of UT;  the TT is not at all critical
+**     in this respect).  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  For UT, the date & time
+**     method is best matched to the algorithm that is used by the Earth
+**     rotation angle function, called internally:  maximum precision is
+**     delivered when the uta argument is for 0hrs UT1 on the day in
+**     question and the utb argument lies in the range 0 to 1, or vice
+**     versa.
+**
+**  2) Both UT1 and TT are required, UT1 to predict the Earth rotation
+**     and TT to predict the effects of precession.  If UT1 is used for
+**     both purposes, errors of order 100 microarcseconds result.
+**
+**  3) This GMST is compatible with the IAU 2006 precession and must not
+**     be used with other precession models.
+**
+**  4) The result is returned in the range 0 to 2pi.
+**
+**  Called:
+**     eraEra00     Earth rotation angle, IAU 2000
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  Reference:
+**
+**     Capitaine, N., Wallace, P.T. & Chapront, J., 2005,
+**     Astron.Astrophys. 432, 355
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, gmst;
+
+
+/* TT Julian centuries since J2000.0. */
+   t = ((tta - ERFA_DJ00) + ttb) / ERFA_DJC;
+
+/* Greenwich mean sidereal time, IAU 2006. */
+   gmst = eraAnp(eraEra00(uta, utb) +
+                  (    0.014506     +
+                  (  4612.156534    +
+                  (     1.3915817   +
+                  (    -0.00000044  +
+                  (    -0.000029956 +
+                  (    -0.0000000368 )
+          * t) * t) * t) * t) * t) * ERFA_DAS2R);
+
+   return gmst;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gmst82.c b/ast/erfa/gmst82.c
new file mode 100644
index 0000000..9484b66
--- /dev/null
+++ b/ast/erfa/gmst82.c
@@ -0,0 +1,160 @@
+#include "erfa.h"
+
+double eraGmst82(double dj1, double dj2)
+/*
+**  - - - - - - - - - -
+**   e r a G m s t 8 2
+**  - - - - - - - - - -
+**
+**  Universal Time to Greenwich mean sidereal time (IAU 1982 model).
+**
+**  Given:
+**     dj1,dj2    double    UT1 Julian Date (see note)
+**
+**  Returned (function value):
+**                double    Greenwich mean sidereal time (radians)
+**
+**  Notes:
+**
+**  1) The UT1 date dj1+dj2 is a Julian Date, apportioned in any
+**     convenient way between the arguments dj1 and dj2.  For example,
+**     JD(UT1)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**             dj1            dj2
+**
+**         2450123.7          0          (JD method)
+**          2451545        -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5         0.2         (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  The date & time method is
+**     best matched to the algorithm used:  maximum accuracy (or, at
+**     least, minimum noise) is delivered when the dj1 argument is for
+**     0hrs UT1 on the day in question and the dj2 argument lies in the
+**     range 0 to 1, or vice versa.
+**
+**  2) The algorithm is based on the IAU 1982 expression.  This is
+**     always described as giving the GMST at 0 hours UT1.  In fact, it
+**     gives the difference between the GMST and the UT, the steady
+**     4-minutes-per-day drawing-ahead of ST with respect to UT.  When
+**     whole days are ignored, the expression happens to equal the GMST
+**     at 0 hours UT1 each day.
+**
+**  3) In this function, the entire UT1 (the sum of the two arguments
+**     dj1 and dj2) is used directly as the argument for the standard
+**     formula, the constant term of which is adjusted by 12 hours to
+**     take account of the noon phasing of Julian Date.  The UT1 is then
+**     added, but omitting whole days to conserve accuracy.
+**
+**  Called:
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  References:
+**
+**     Transactions of the International Astronomical Union,
+**     XVIII B, 67 (1983).
+**
+**     Aoki et al., Astron. Astrophys. 105, 359-361 (1982).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Coefficients of IAU 1982 GMST-UT1 model */
+   double A = 24110.54841  -  ERFA_DAYSEC / 2.0;
+   double B = 8640184.812866;
+   double C = 0.093104;
+   double D =  -6.2e-6;
+
+/* Note: the first constant, A, has to be adjusted by 12 hours */
+/* because the UT1 is supplied as a Julian date, which begins  */
+/* at noon.                                                    */
+
+   double d1, d2, t, f, gmst;
+
+
+/* Julian centuries since fundamental epoch. */
+   if (dj1 < dj2) {
+      d1 = dj1;
+      d2 = dj2;
+   } else {
+      d1 = dj2;
+      d2 = dj1;
+   }
+   t = (d1 + (d2 - ERFA_DJ00)) / ERFA_DJC;
+
+/* Fractional part of JD(UT1), in seconds. */
+   f = ERFA_DAYSEC * (fmod(d1, 1.0) + fmod(d2, 1.0));
+
+/* GMST at this UT1. */
+   gmst = eraAnp(ERFA_DS2R * ((A + (B + (C + D * t) * t) * t) + f));
+
+   return gmst;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gst00a.c b/ast/erfa/gst00a.c
new file mode 100644
index 0000000..e185576
--- /dev/null
+++ b/ast/erfa/gst00a.c
@@ -0,0 +1,147 @@
+#include "erfa.h"
+
+double eraGst00a(double uta, double utb, double tta, double ttb)
+/*
+**  - - - - - - - - - -
+**   e r a G s t 0 0 a
+**  - - - - - - - - - -
+**
+**  Greenwich apparent sidereal time (consistent with IAU 2000
+**  resolutions).
+**
+**  Given:
+**     uta,utb    double    UT1 as a 2-part Julian Date (Notes 1,2)
+**     tta,ttb    double    TT as a 2-part Julian Date (Notes 1,2)
+**
+**  Returned (function value):
+**                double    Greenwich apparent sidereal time (radians)
+**
+**  Notes:
+**
+**  1) The UT1 and TT dates uta+utb and tta+ttb respectively, are both
+**     Julian Dates, apportioned in any convenient way between the
+**     argument pairs.  For example, JD=2450123.7 could be expressed in
+**     any of these ways, among others:
+**
+**            Part A        Part B
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable (in the case of UT;  the TT is not at all critical
+**     in this respect).  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  For UT, the date & time
+**     method is best matched to the algorithm that is used by the Earth
+**     Rotation Angle function, called internally:  maximum precision is
+**     delivered when the uta argument is for 0hrs UT1 on the day in
+**     question and the utb argument lies in the range 0 to 1, or vice
+**     versa.
+**
+**  2) Both UT1 and TT are required, UT1 to predict the Earth rotation
+**     and TT to predict the effects of precession-nutation.  If UT1 is
+**     used for both purposes, errors of order 100 microarcseconds
+**     result.
+**
+**  3) This GAST is compatible with the IAU 2000 resolutions and must be
+**     used only in conjunction with other IAU 2000 compatible
+**     components such as precession-nutation.
+**
+**  4) The result is returned in the range 0 to 2pi.
+**
+**  5) The algorithm is from Capitaine et al. (2003) and IERS
+**     Conventions 2003.
+**
+**  Called:
+**     eraGmst00    Greenwich mean sidereal time, IAU 2000
+**     eraEe00a     equation of the equinoxes, IAU 2000A
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  References:
+**
+**     Capitaine, N., Wallace, P.T. and McCarthy, D.D., "Expressions to
+**     implement the IAU 2000 definition of UT1", Astronomy &
+**     Astrophysics, 406, 1135-1149 (2003)
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double gmst00, ee00a, gst;
+
+
+   gmst00 = eraGmst00(uta, utb, tta, ttb);
+   ee00a = eraEe00a(tta, ttb);
+   gst = eraAnp(gmst00 + ee00a);
+
+   return gst;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gst00b.c b/ast/erfa/gst00b.c
new file mode 100644
index 0000000..cfb5293
--- /dev/null
+++ b/ast/erfa/gst00b.c
@@ -0,0 +1,155 @@
+#include "erfa.h"
+
+double eraGst00b(double uta, double utb)
+/*
+**  - - - - - - - - - -
+**   e r a G s t 0 0 b
+**  - - - - - - - - - -
+**
+**  Greenwich apparent sidereal time (consistent with IAU 2000
+**  resolutions but using the truncated nutation model IAU 2000B).
+**
+**  Given:
+**     uta,utb    double    UT1 as a 2-part Julian Date (Notes 1,2)
+**
+**  Returned (function value):
+**                double    Greenwich apparent sidereal time (radians)
+**
+**  Notes:
+**
+**  1) The UT1 date uta+utb is a Julian Date, apportioned in any
+**     convenient way between the argument pair.  For example,
+**     JD=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**             uta            utb
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  For UT, the date & time
+**     method is best matched to the algorithm that is used by the Earth
+**     Rotation Angle function, called internally:  maximum precision is
+**     delivered when the uta argument is for 0hrs UT1 on the day in
+**     question and the utb argument lies in the range 0 to 1, or vice
+**     versa.
+**
+**  2) The result is compatible with the IAU 2000 resolutions, except
+**     that accuracy has been compromised for the sake of speed and
+**     convenience in two respects:
+**
+**     . UT is used instead of TDB (or TT) to compute the precession
+**       component of GMST and the equation of the equinoxes.  This
+**       results in errors of order 0.1 mas at present.
+**
+**     . The IAU 2000B abridged nutation model (McCarthy & Luzum, 2001)
+**       is used, introducing errors of up to 1 mas.
+**
+**  3) This GAST is compatible with the IAU 2000 resolutions and must be
+**     used only in conjunction with other IAU 2000 compatible
+**     components such as precession-nutation.
+**
+**  4) The result is returned in the range 0 to 2pi.
+**
+**  5) The algorithm is from Capitaine et al. (2003) and IERS
+**     Conventions 2003.
+**
+**  Called:
+**     eraGmst00    Greenwich mean sidereal time, IAU 2000
+**     eraEe00b     equation of the equinoxes, IAU 2000B
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  References:
+**
+**     Capitaine, N., Wallace, P.T. and McCarthy, D.D., "Expressions to
+**     implement the IAU 2000 definition of UT1", Astronomy &
+**     Astrophysics, 406, 1135-1149 (2003)
+**
+**     McCarthy, D.D. & Luzum, B.J., "An abridged model of the
+**     precession-nutation of the celestial pole", Celestial Mechanics &
+**     Dynamical Astronomy, 85, 37-49 (2003)
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double gmst00, ee00b, gst;
+
+
+   gmst00 = eraGmst00(uta, utb, uta, utb);
+   ee00b = eraEe00b(uta, utb);
+   gst = eraAnp(gmst00 + ee00b);
+
+   return gst;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gst06.c b/ast/erfa/gst06.c
new file mode 100644
index 0000000..c4c85df
--- /dev/null
+++ b/ast/erfa/gst06.c
@@ -0,0 +1,149 @@
+#include "erfa.h"
+
+double eraGst06(double uta, double utb, double tta, double ttb,
+                double rnpb[3][3])
+/*
+**  - - - - - - - - -
+**   e r a G s t 0 6
+**  - - - - - - - - -
+**
+**  Greenwich apparent sidereal time, IAU 2006, given the NPB matrix.
+**
+**  Given:
+**     uta,utb  double        UT1 as a 2-part Julian Date (Notes 1,2)
+**     tta,ttb  double        TT as a 2-part Julian Date (Notes 1,2)
+**     rnpb     double[3][3]  nutation x precession x bias matrix
+**
+**  Returned (function value):
+**              double        Greenwich apparent sidereal time (radians)
+**
+**  Notes:
+**
+**  1) The UT1 and TT dates uta+utb and tta+ttb respectively, are both
+**     Julian Dates, apportioned in any convenient way between the
+**     argument pairs.  For example, JD=2450123.7 could be expressed in
+**     any of these ways, among others:
+**
+**            Part A        Part B
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable (in the case of UT;  the TT is not at all critical
+**     in this respect).  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  For UT, the date & time
+**     method is best matched to the algorithm that is used by the Earth
+**     rotation angle function, called internally:  maximum precision is
+**     delivered when the uta argument is for 0hrs UT1 on the day in
+**     question and the utb argument lies in the range 0 to 1, or vice
+**     versa.
+**
+**  2) Both UT1 and TT are required, UT1 to predict the Earth rotation
+**     and TT to predict the effects of precession-nutation.  If UT1 is
+**     used for both purposes, errors of order 100 microarcseconds
+**     result.
+**
+**  3) Although the function uses the IAU 2006 series for s+XY/2, it is
+**     otherwise independent of the precession-nutation model and can in
+**     practice be used with any equinox-based NPB matrix.
+**
+**  4) The result is returned in the range 0 to 2pi.
+**
+**  Called:
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**     eraS06       the CIO locator s, given X,Y, IAU 2006
+**     eraAnp       normalize angle into range 0 to 2pi
+**     eraEra00     Earth rotation angle, IAU 2000
+**     eraEors      equation of the origins, given NPB matrix and s
+**
+**  Reference:
+**
+**     Wallace, P.T. & Capitaine, N., 2006, Astron.Astrophys. 459, 981
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double x, y, s, era, eors, gst;
+
+
+/* Extract CIP coordinates. */
+   eraBpn2xy(rnpb, &x, &y);
+
+/* The CIO locator, s. */
+   s = eraS06(tta, ttb, x, y);
+
+/* Greenwich apparent sidereal time. */
+   era = eraEra00(uta, utb);
+   eors = eraEors(rnpb, s);
+   gst = eraAnp(era - eors);
+
+   return gst;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gst06a.c b/ast/erfa/gst06a.c
new file mode 100644
index 0000000..e80fd83
--- /dev/null
+++ b/ast/erfa/gst06a.c
@@ -0,0 +1,140 @@
+#include "erfa.h"
+
+double eraGst06a(double uta, double utb, double tta, double ttb)
+/*
+**  - - - - - - - - - -
+**   e r a G s t 0 6 a
+**  - - - - - - - - - -
+**
+**  Greenwich apparent sidereal time (consistent with IAU 2000 and 2006
+**  resolutions).
+**
+**  Given:
+**     uta,utb    double    UT1 as a 2-part Julian Date (Notes 1,2)
+**     tta,ttb    double    TT as a 2-part Julian Date (Notes 1,2)
+**
+**  Returned (function value):
+**                double    Greenwich apparent sidereal time (radians)
+**
+**  Notes:
+**
+**  1) The UT1 and TT dates uta+utb and tta+ttb respectively, are both
+**     Julian Dates, apportioned in any convenient way between the
+**     argument pairs.  For example, JD=2450123.7 could be expressed in
+**     any of these ways, among others:
+**
+**            Part A        Part B
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable (in the case of UT;  the TT is not at all critical
+**     in this respect).  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  For UT, the date & time
+**     method is best matched to the algorithm that is used by the Earth
+**     rotation angle function, called internally:  maximum precision is
+**     delivered when the uta argument is for 0hrs UT1 on the day in
+**     question and the utb argument lies in the range 0 to 1, or vice
+**     versa.
+**
+**  2) Both UT1 and TT are required, UT1 to predict the Earth rotation
+**     and TT to predict the effects of precession-nutation.  If UT1 is
+**     used for both purposes, errors of order 100 microarcseconds
+**     result.
+**
+**  3) This GAST is compatible with the IAU 2000/2006 resolutions and
+**     must be used only in conjunction with IAU 2006 precession and
+**     IAU 2000A nutation.
+**
+**  4) The result is returned in the range 0 to 2pi.
+**
+**  Called:
+**     eraPnm06a    classical NPB matrix, IAU 2006/2000A
+**     eraGst06     Greenwich apparent ST, IAU 2006, given NPB matrix
+**
+**  Reference:
+**
+**     Wallace, P.T. & Capitaine, N., 2006, Astron.Astrophys. 459, 981
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rnpb[3][3], gst;
+
+
+/* Classical nutation x precession x bias matrix, IAU 2000A. */
+   eraPnm06a(tta, ttb, rnpb);
+
+/* Greenwich apparent sidereal time. */
+   gst = eraGst06(uta, utb, tta, ttb, rnpb);
+
+   return gst;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/gst94.c b/ast/erfa/gst94.c
new file mode 100644
index 0000000..112cf61
--- /dev/null
+++ b/ast/erfa/gst94.c
@@ -0,0 +1,140 @@
+#include "erfa.h"
+
+double eraGst94(double uta, double utb)
+/*
+**  - - - - - - - - -
+**   e r a G s t 9 4
+**  - - - - - - - - -
+**
+**  Greenwich apparent sidereal time (consistent with IAU 1982/94
+**  resolutions).
+**
+**  Given:
+**     uta,utb    double    UT1 as a 2-part Julian Date (Notes 1,2)
+**
+**  Returned (function value):
+**                double    Greenwich apparent sidereal time (radians)
+**
+**  Notes:
+**
+**  1) The UT1 date uta+utb is a Julian Date, apportioned in any
+**     convenient way between the argument pair.  For example,
+**     JD=2450123.7 could be expressed in any of these ways, among
+**     others:
+**
+**             uta            utb
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 and MJD methods are good compromises
+**     between resolution and convenience.  For UT, the date & time
+**     method is best matched to the algorithm that is used by the Earth
+**     Rotation Angle function, called internally:  maximum precision is
+**     delivered when the uta argument is for 0hrs UT1 on the day in
+**     question and the utb argument lies in the range 0 to 1, or vice
+**     versa.
+**
+**  2) The result is compatible with the IAU 1982 and 1994 resolutions,
+**     except that accuracy has been compromised for the sake of
+**     convenience in that UT is used instead of TDB (or TT) to compute
+**     the equation of the equinoxes.
+**
+**  3) This GAST must be used only in conjunction with contemporaneous
+**     IAU standards such as 1976 precession, 1980 obliquity and 1982
+**     nutation.  It is not compatible with the IAU 2000 resolutions.
+**
+**  4) The result is returned in the range 0 to 2pi.
+**
+**  Called:
+**     eraGmst82    Greenwich mean sidereal time, IAU 1982
+**     eraEqeq94    equation of the equinoxes, IAU 1994
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  References:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**     IAU Resolution C7, Recommendation 3 (1994)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double gmst82, eqeq94, gst;
+
+
+   gmst82 = eraGmst82(uta, utb);
+   eqeq94 = eraEqeq94(uta, utb);
+   gst = eraAnp(gmst82  + eqeq94);
+
+   return gst;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/h2fk5.c b/ast/erfa/h2fk5.c
new file mode 100644
index 0000000..ad7e9a6
--- /dev/null
+++ b/ast/erfa/h2fk5.c
@@ -0,0 +1,157 @@
+#include "erfa.h"
+
+void eraH2fk5(double rh, double dh,
+              double drh, double ddh, double pxh, double rvh,
+              double *r5, double *d5,
+              double *dr5, double *dd5, double *px5, double *rv5)
+/*
+**  - - - - - - - - -
+**   e r a H 2 f k 5
+**  - - - - - - - - -
+**
+**  Transform Hipparcos star data into the FK5 (J2000.0) system.
+**
+**  Given (all Hipparcos, epoch J2000.0):
+**     rh      double    RA (radians)
+**     dh      double    Dec (radians)
+**     drh     double    proper motion in RA (dRA/dt, rad/Jyear)
+**     ddh     double    proper motion in Dec (dDec/dt, rad/Jyear)
+**     pxh     double    parallax (arcsec)
+**     rvh     double    radial velocity (km/s, positive = receding)
+**
+**  Returned (all FK5, equinox J2000.0, epoch J2000.0):
+**     r5      double    RA (radians)
+**     d5      double    Dec (radians)
+**     dr5     double    proper motion in RA (dRA/dt, rad/Jyear)
+**     dd5     double    proper motion in Dec (dDec/dt, rad/Jyear)
+**     px5     double    parallax (arcsec)
+**     rv5     double    radial velocity (km/s, positive = receding)
+**
+**  Notes:
+**
+**  1) This function transforms Hipparcos star positions and proper
+**     motions into FK5 J2000.0.
+**
+**  2) The proper motions in RA are dRA/dt rather than
+**     cos(Dec)*dRA/dt, and are per year rather than per century.
+**
+**  3) The FK5 to Hipparcos transformation is modeled as a pure
+**     rotation and spin;  zonal errors in the FK5 catalog are not
+**     taken into account.
+**
+**  4) See also eraFk52h, eraFk5hz, eraHfk5z.
+**
+**  Called:
+**     eraStarpv    star catalog data to space motion pv-vector
+**     eraFk5hip    FK5 to Hipparcos rotation and spin
+**     eraRv2m      r-vector to r-matrix
+**     eraRxp       product of r-matrix and p-vector
+**     eraTrxp      product of transpose of r-matrix and p-vector
+**     eraPxp       vector product of two p-vectors
+**     eraPmp       p-vector minus p-vector
+**     eraPvstar    space motion pv-vector to star catalog data
+**
+**  Reference:
+**
+**     F.Mignard & M.Froeschle, Astron. Astrophys. 354, 732-739 (2000).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int i;
+   double pvh[2][3], r5h[3][3], s5h[3], sh[3], wxp[3], vv[3], pv5[2][3];
+
+
+/* Hipparcos barycentric position/velocity pv-vector (normalized). */
+   eraStarpv(rh, dh, drh, ddh, pxh, rvh, pvh);
+
+/* FK5 to Hipparcos orientation matrix and spin vector. */
+   eraFk5hip(r5h, s5h);
+
+/* Make spin units per day instead of per year. */
+   for ( i = 0; i < 3; s5h[i++] /= 365.25 );
+
+/* Orient the spin into the Hipparcos system. */
+   eraRxp(r5h, s5h, sh);
+
+/* De-orient the Hipparcos position into the FK5 system. */
+   eraTrxp(r5h, pvh[0], pv5[0]);
+
+/* Apply spin to the position giving an extra space motion component. */
+   eraPxp(pvh[0], sh, wxp);
+
+/* Subtract this component from the Hipparcos space motion. */
+   eraPmp(pvh[1], wxp, vv);
+
+/* De-orient the Hipparcos space motion into the FK5 system. */
+   eraTrxp(r5h, vv, pv5[1]);
+
+/* FK5 pv-vector to spherical. */
+   eraPvstar(pv5, r5, d5, dr5, dd5, px5, rv5);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/hfk5z.c b/ast/erfa/hfk5z.c
new file mode 100644
index 0000000..8824e96
--- /dev/null
+++ b/ast/erfa/hfk5z.c
@@ -0,0 +1,184 @@
+#include "erfa.h"
+
+void eraHfk5z(double rh, double dh, double date1, double date2,
+              double *r5, double *d5, double *dr5, double *dd5)
+/*
+**  - - - - - - - - -
+**   e r a H f k 5 z
+**  - - - - - - - - -
+**
+**  Transform a Hipparcos star position into FK5 J2000.0, assuming
+**  zero Hipparcos proper motion.
+**
+**  Given:
+**     rh            double    Hipparcos RA (radians)
+**     dh            double    Hipparcos Dec (radians)
+**     date1,date2   double    TDB date (Note 1)
+**
+**  Returned (all FK5, equinox J2000.0, date date1+date2):
+**     r5            double    RA (radians)
+**     d5            double    Dec (radians)
+**     dr5           double    FK5 RA proper motion (rad/year, Note 4)
+**     dd5           double    Dec proper motion (rad/year, Note 4)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The proper motion in RA is dRA/dt rather than cos(Dec)*dRA/dt.
+**
+**  3) The FK5 to Hipparcos transformation is modeled as a pure rotation
+**     and spin;  zonal errors in the FK5 catalogue are not taken into
+**     account.
+**
+**  4) It was the intention that Hipparcos should be a close
+**     approximation to an inertial frame, so that distant objects have
+**     zero proper motion;  such objects have (in general) non-zero
+**     proper motion in FK5, and this function returns those fictitious
+**     proper motions.
+**
+**  5) The position returned by this function is in the FK5 J2000.0
+**     reference system but at date date1+date2.
+**
+**  6) See also eraFk52h, eraH2fk5, eraFk5zhz.
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraFk5hip    FK5 to Hipparcos rotation and spin
+**     eraRxp       product of r-matrix and p-vector
+**     eraSxp       multiply p-vector by scalar
+**     eraRxr       product of two r-matrices
+**     eraTrxp      product of transpose of r-matrix and p-vector
+**     eraPxp       vector product of two p-vectors
+**     eraPv2s      pv-vector to spherical
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  Reference:
+**
+**     F.Mignard & M.Froeschle, 2000, Astron.Astrophys. 354, 732-739.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, ph[3], r5h[3][3], s5h[3], sh[3], vst[3],
+   rst[3][3], r5ht[3][3], pv5e[2][3], vv[3],
+   w, r, v;
+
+
+/* Time interval from fundamental epoch J2000.0 to given date (JY). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJY;
+
+/* Hipparcos barycentric position vector (normalized). */
+   eraS2c(rh, dh, ph);
+
+/* FK5 to Hipparcos orientation matrix and spin vector. */
+   eraFk5hip(r5h, s5h);
+
+/* Rotate the spin into the Hipparcos system. */
+   eraRxp(r5h, s5h, sh);
+
+/* Accumulated Hipparcos wrt FK5 spin over that interval. */
+   eraSxp(t, s5h, vst);
+
+/* Express the accumulated spin as a rotation matrix. */
+   eraRv2m(vst, rst);
+
+/* Rotation matrix:  accumulated spin, then FK5 to Hipparcos. */
+   eraRxr(r5h, rst, r5ht);
+
+/* De-orient & de-spin the Hipparcos position into FK5 J2000.0. */
+   eraTrxp(r5ht, ph, pv5e[0]);
+
+/* Apply spin to the position giving a space motion. */
+   eraPxp(sh, ph, vv);
+
+/* De-orient & de-spin the Hipparcos space motion into FK5 J2000.0. */
+   eraTrxp(r5ht, vv, pv5e[1]);
+
+/* FK5 position/velocity pv-vector to spherical. */
+   eraPv2s(pv5e, &w, d5, &r, dr5, dd5, &v);
+   *r5 = eraAnp(w);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/icrs2g.c b/ast/erfa/icrs2g.c
new file mode 100644
index 0000000..cf59118
--- /dev/null
+++ b/ast/erfa/icrs2g.c
@@ -0,0 +1,170 @@
+#include "erfa.h"
+
+void eraIcrs2g ( double dr, double dd, double *dl, double *db )
+/*
+**  - - - - - - - - - -
+**   e r a I c r s 2 g
+**  - - - - - - - - - -
+**
+**  Transformation from ICRS to Galactic Coordinates.
+**
+**  Given:
+**     dr     double      ICRS right ascension (radians)
+**     dd     double      ICRS declination (radians)
+**
+**  Returned:
+**     dl     double      galactic longitude (radians)
+**     db     double      galactic latitude (radians)
+**
+**  Notes:
+**
+**  1) The IAU 1958 system of Galactic coordinates was defined with
+**     respect to the now obsolete reference system FK4 B1950.0.  When
+**     interpreting the system in a modern context, several factors have
+**     to be taken into account:
+**
+**     . The inclusion in FK4 positions of the E-terms of aberration.
+**
+**     . The distortion of the FK4 proper motion system by differential
+**       Galactic rotation.
+**
+**     . The use of the B1950.0 equinox rather than the now-standard
+**       J2000.0.
+**
+**     . The frame bias between ICRS and the J2000.0 mean place system.
+**
+**     The Hipparcos Catalogue (Perryman & ESA 1997) provides a rotation
+**     matrix that transforms directly between ICRS and Galactic
+**     coordinates with the above factors taken into account.  The
+**     matrix is derived from three angles, namely the ICRS coordinates
+**     of the Galactic pole and the longitude of the ascending node of
+**     the galactic equator on the ICRS equator.  They are given in
+**     degrees to five decimal places and for canonical purposes are
+**     regarded as exact.  In the Hipparcos Catalogue the matrix
+**     elements are given to 10 decimal places (about 20 microarcsec).
+**     In the present ERFA function the matrix elements have been
+**     recomputed from the canonical three angles and are given to 30
+**     decimal places.
+**
+**  2) The inverse transformation is performed by the function eraG2icrs.
+**
+**  Called:
+**     eraAnp       normalize angle into range 0 to 2pi
+**     eraAnpm      normalize angle into range +/- pi
+**     eraS2c       spherical coordinates to unit vector
+**     eraRxp       product of r-matrix and p-vector
+**     eraC2s       p-vector to spherical
+**
+**  Reference:
+**     Perryman M.A.C. & ESA, 1997, ESA SP-1200, The Hipparcos and Tycho
+**     catalogues.  Astrometric and photometric star catalogues
+**     derived from the ESA Hipparcos Space Astrometry Mission.  ESA
+**     Publications Division, Noordwijk, Netherlands.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double v1[3], v2[3];
+
+/*
+**  L2,B2 system of galactic coordinates in the form presented in the
+**  Hipparcos Catalogue.  In degrees:
+**
+**  P = 192.85948    right ascension of the Galactic north pole in ICRS
+**  Q =  27.12825    declination of the Galactic north pole in ICRS
+**  R =  32.93192    longitude of the ascending node of the Galactic
+**                   plane on the ICRS equator
+**
+**  ICRS to galactic rotation matrix, obtained by computing
+**  R_3(-R) R_1(pi/2-Q) R_3(pi/2+P) to the full precision shown:
+*/
+   double r[3][3] = { { -0.054875560416215368492398900454,
+                        -0.873437090234885048760383168409,
+                        -0.483835015548713226831774175116 },
+                      { +0.494109427875583673525222371358,
+                        -0.444829629960011178146614061616,
+                        +0.746982244497218890527388004556 },
+                      { -0.867666149019004701181616534570,
+                        -0.198076373431201528180486091412,
+                        +0.455983776175066922272100478348 } };
+
+
+/* Spherical to Cartesian. */
+   eraS2c(dr, dd, v1);
+
+/* ICRS to Galactic. */
+   eraRxp(r, v1, v2);
+
+/* Cartesian to spherical. */
+   eraC2s(v2, dl, db);
+
+/* Express in conventional ranges. */
+   *dl = eraAnp(*dl);
+   *db = eraAnpm(*db);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ir.c b/ast/erfa/ir.c
new file mode 100644
index 0000000..8c7cd1e
--- /dev/null
+++ b/ast/erfa/ir.c
@@ -0,0 +1,92 @@
+#include "erfa.h"
+
+void eraIr(double r[3][3])
+/*
+**  - - - - - -
+**   e r a I r
+**  - - - - - -
+**
+**  Initialize an r-matrix to the identity matrix.
+**
+**  Returned:
+**     r       double[3][3]    r-matrix
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   r[0][0] = 1.0;
+   r[0][1] = 0.0;
+   r[0][2] = 0.0;
+   r[1][0] = 0.0;
+   r[1][1] = 1.0;
+   r[1][2] = 0.0;
+   r[2][0] = 0.0;
+   r[2][1] = 0.0;
+   r[2][2] = 1.0;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/jd2cal.c b/ast/erfa/jd2cal.c
new file mode 100644
index 0000000..abe2b8f
--- /dev/null
+++ b/ast/erfa/jd2cal.c
@@ -0,0 +1,164 @@
+#include "erfa.h"
+
+int eraJd2cal(double dj1, double dj2,
+              int *iy, int *im, int *id, double *fd)
+/*
+**  - - - - - - - - - -
+**   e r a J d 2 c a l
+**  - - - - - - - - - -
+**
+**  Julian Date to Gregorian year, month, day, and fraction of a day.
+**
+**  Given:
+**     dj1,dj2   double   Julian Date (Notes 1, 2)
+**
+**  Returned (arguments):
+**     iy        int      year
+**     im        int      month
+**     id        int      day
+**     fd        double   fraction of day
+**
+**  Returned (function value):
+**               int      status:
+**                           0 = OK
+**                          -1 = unacceptable date (Note 3)
+**
+**  Notes:
+**
+**  1) The earliest valid date is -68569.5 (-4900 March 1).  The
+**     largest value accepted is 1e9.
+**
+**  2) The Julian Date is apportioned in any convenient way between
+**     the arguments dj1 and dj2.  For example, JD=2450123.7 could
+**     be expressed in any of these ways, among others:
+**
+**            dj1             dj2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**  3) In early eras the conversion is from the "proleptic Gregorian
+**     calendar";  no account is taken of the date(s) of adoption of
+**     the Gregorian calendar, nor is the AD/BC numbering convention
+**     observed.
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Section 12.92 (p604).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Minimum and maximum allowed JD */
+   const double DJMIN = -68569.5;
+   const double DJMAX = 1e9;
+
+   long jd, l, n, i, k;
+   double dj, d1, d2, f1, f2, f, d;
+
+
+/* Verify date is acceptable. */
+   dj = dj1 + dj2;
+   if (dj < DJMIN || dj > DJMAX) return -1;
+
+/* Copy the date, big then small, and re-align to midnight. */
+   if (dj1 >= dj2) {
+      d1 = dj1;
+      d2 = dj2;
+   } else {
+      d1 = dj2;
+      d2 = dj1;
+   }
+   d2 -= 0.5;
+
+/* Separate day and fraction. */
+   f1 = fmod(d1, 1.0);
+   f2 = fmod(d2, 1.0);
+   f = fmod(f1 + f2, 1.0);
+   if (f < 0.0) f += 1.0;
+   d = floor(d1 - f1) + floor(d2 - f2) + floor(f1 + f2 - f);
+   jd = (long) floor(d) + 1L;
+
+/* Express day in Gregorian calendar. */
+   l = jd + 68569L;
+   n = (4L * l) / 146097L;
+   l -= (146097L * n + 3L) / 4L;
+   i = (4000L * (l + 1L)) / 1461001L;
+   l -= (1461L * i) / 4L - 31L;
+   k = (80L * l) / 2447L;
+   *id = (int) (l - (2447L * k) / 80L);
+   l = k / 11L;
+   *im = (int) (k + 2L - 12L * l);
+   *iy = (int) (100L * (n - 49L) + i + l);
+   *fd = f;
+
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/jdcalf.c b/ast/erfa/jdcalf.c
new file mode 100644
index 0000000..85efb68
--- /dev/null
+++ b/ast/erfa/jdcalf.c
@@ -0,0 +1,170 @@
+#include "erfa.h"
+
+int eraJdcalf(int ndp, double dj1, double dj2, int iymdf[4])
+/*
+**  - - - - - - - - - -
+**   e r a J d c a l f
+**  - - - - - - - - - -
+**
+**  Julian Date to Gregorian Calendar, expressed in a form convenient
+**  for formatting messages:  rounded to a specified precision.
+**
+**  Given:
+**     ndp       int      number of decimal places of days in fraction
+**     dj1,dj2   double   dj1+dj2 = Julian Date (Note 1)
+**
+**  Returned:
+**     iymdf     int[4]   year, month, day, fraction in Gregorian
+**                        calendar
+**
+**  Returned (function value):
+**               int      status:
+**                          -1 = date out of range
+**                           0 = OK
+**                          +1 = NDP not 0-9 (interpreted as 0)
+**
+**  Notes:
+**
+**  1) The Julian Date is apportioned in any convenient way between
+**     the arguments dj1 and dj2.  For example, JD=2450123.7 could
+**     be expressed in any of these ways, among others:
+**
+**             dj1            dj2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**  2) In early eras the conversion is from the "Proleptic Gregorian
+**     Calendar";  no account is taken of the date(s) of adoption of
+**     the Gregorian Calendar, nor is the AD/BC numbering convention
+**     observed.
+**
+**  3) Refer to the function eraJd2cal.
+**
+**  4) NDP should be 4 or less if internal overflows are to be
+**     avoided on machines which use 16-bit integers.
+**
+**  Called:
+**     eraJd2cal    JD to Gregorian calendar
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Section 12.92 (p604).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int j, js;
+   double denom, d1, d2, f1, f2, f;
+
+
+/* Denominator of fraction (e.g. 100 for 2 decimal places). */
+   if ((ndp >= 0) && (ndp <= 9)) {
+      j = 0;
+      denom = pow(10.0, ndp);
+   } else {
+      j = 1;
+      denom = 1.0;
+   }
+
+/* Copy the date, big then small, and realign to midnight. */
+   if (dj1 >= dj2) {
+      d1 = dj1;
+      d2 = dj2;
+   } else {
+      d1 = dj2;
+      d2 = dj1;
+   }
+   d2 -= 0.5;
+
+/* Separate days and fractions. */
+   f1 = fmod(d1, 1.0);
+   f2 = fmod(d2, 1.0);
+   d1 = floor(d1 - f1);
+   d2 = floor(d2 - f2);
+
+/* Round the total fraction to the specified number of places. */
+   f = floor((f1+f2)*denom + 0.5) / denom;
+
+/* Re-assemble the rounded date and re-align to noon. */
+   d2 += f + 0.5;
+
+/* Convert to Gregorian calendar. */
+   js = eraJd2cal(d1, d2, &iymdf[0], &iymdf[1], &iymdf[2], &f);
+   if (js == 0) {
+      iymdf[3] = (int) (f * denom);
+   } else {
+      j = js;
+   }
+
+/* Return the status. */
+   return j;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ld.c b/ast/erfa/ld.c
new file mode 100644
index 0000000..c1569fd
--- /dev/null
+++ b/ast/erfa/ld.c
@@ -0,0 +1,161 @@
+#include "erfa.h"
+
+void eraLd(double bm, double p[3], double q[3], double e[3],
+           double em, double dlim, double p1[3])
+/*
+**  - - - - - -
+**   e r a L d
+**  - - - - - -
+**
+**  Apply light deflection by a solar-system body, as part of
+**  transforming coordinate direction into natural direction.
+**
+**  Given:
+**     bm     double     mass of the gravitating body (solar masses)
+**     p      double[3]  direction from observer to source (unit vector)
+**     q      double[3]  direction from body to source (unit vector)
+**     e      double[3]  direction from body to observer (unit vector)
+**     em     double     distance from body to observer (au)
+**     dlim   double     deflection limiter (Note 4)
+**
+**  Returned:
+**     p1     double[3]  observer to deflected source (unit vector)
+**
+**  Notes:
+**
+**  1) The algorithm is based on Expr. (70) in Klioner (2003) and
+**     Expr. (7.63) in the Explanatory Supplement (Urban & Seidelmann
+**     2013), with some rearrangement to minimize the effects of machine
+**     precision.
+**
+**  2) The mass parameter bm can, as required, be adjusted in order to
+**     allow for such effects as quadrupole field.
+**
+**  3) The barycentric position of the deflecting body should ideally
+**     correspond to the time of closest approach of the light ray to
+**     the body.
+**
+**  4) The deflection limiter parameter dlim is phi^2/2, where phi is
+**     the angular separation (in radians) between source and body at
+**     which limiting is applied.  As phi shrinks below the chosen
+**     threshold, the deflection is artificially reduced, reaching zero
+**     for phi = 0.
+**
+**  5) The returned vector p1 is not normalized, but the consequential
+**     departure from unit magnitude is always negligible.
+**
+**  6) The arguments p and p1 can be the same array.
+**
+**  7) To accumulate total light deflection taking into account the
+**     contributions from several bodies, call the present function for
+**     each body in succession, in decreasing order of distance from the
+**     observer.
+**
+**  8) For efficiency, validation is omitted.  The supplied vectors must
+**     be of unit magnitude, and the deflection limiter non-zero and
+**     positive.
+**
+**  References:
+**
+**     Urban, S. & Seidelmann, P. K. (eds), Explanatory Supplement to
+**     the Astronomical Almanac, 3rd ed., University Science Books
+**     (2013).
+**
+**     Klioner, Sergei A., "A practical relativistic model for micro-
+**     arcsecond astrometry in space", Astr. J. 125, 1580-1597 (2003).
+**
+**  Called:
+**     eraPdp       scalar product of two p-vectors
+**     eraPxp       vector product of two p-vectors
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int i;
+   double qpe[3], qdqpe, w, eq[3], peq[3];
+
+
+/* q . (q + e). */
+   for (i = 0; i < 3; i++) {
+      qpe[i] = q[i] + e[i];
+   }
+   qdqpe = eraPdp(q, qpe);
+
+/* 2 x G x bm / ( em x c^2 x ( q . (q + e) ) ). */
+   w = bm * ERFA_SRS / em / ERFA_GMAX(qdqpe,dlim);
+
+/* p x (e x q). */
+   eraPxp(e, q, eq);
+   eraPxp(p, eq, peq);
+
+/* Apply the deflection. */
+   for (i = 0; i < 3; i++) {
+      p1[i] = p[i] + w*peq[i];
+   }
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ldn.c b/ast/erfa/ldn.c
new file mode 100644
index 0000000..5ad0a50
--- /dev/null
+++ b/ast/erfa/ldn.c
@@ -0,0 +1,183 @@
+#include "erfa.h"
+
+void eraLdn(int n, eraLDBODY b[], double ob[3], double sc[3],
+            double sn[3])
+/*+
+**  - - - - - - -
+**   e r a L d n
+**  - - - - - - -
+**
+**  For a star, apply light deflection by multiple solar-system bodies,
+**  as part of transforming coordinate direction into natural direction.
+**
+**  Given:
+**     n    int           number of bodies (note 1)
+**     b    eraLDBODY[n]  data for each of the n bodies (Notes 1,2):
+**      bm   double         mass of the body (solar masses, Note 3)
+**      dl   double         deflection limiter (Note 4)
+**      pv   [2][3]         barycentric PV of the body (au, au/day)
+**     ob   double[3]     barycentric position of the observer (au)
+**     sc   double[3]     observer to star coord direction (unit vector)
+**
+**  Returned:
+**     sn    double[3]      observer to deflected star (unit vector)
+**
+**  1) The array b contains n entries, one for each body to be
+**     considered.  If n = 0, no gravitational light deflection will be
+**     applied, not even for the Sun.
+**
+**  2) The array b should include an entry for the Sun as well as for
+**     any planet or other body to be taken into account.  The entries
+**     should be in the order in which the light passes the body.
+**
+**  3) In the entry in the b array for body i, the mass parameter
+**     b[i].bm can, as required, be adjusted in order to allow for such
+**     effects as quadrupole field.
+**
+**  4) The deflection limiter parameter b[i].dl is phi^2/2, where phi is
+**     the angular separation (in radians) between star and body at
+**     which limiting is applied.  As phi shrinks below the chosen
+**     threshold, the deflection is artificially reduced, reaching zero
+**     for phi = 0.   Example values suitable for a terrestrial
+**     observer, together with masses, are as follows:
+**
+**        body i     b[i].bm        b[i].dl
+**
+**        Sun        1.0            6e-6
+**        Jupiter    0.00095435     3e-9
+**        Saturn     0.00028574     3e-10
+**
+**  5) For cases where the starlight passes the body before reaching the
+**     observer, the body is placed back along its barycentric track by
+**     the light time from that point to the observer.  For cases where
+**     the body is "behind" the observer no such shift is applied.  If
+**     a different treatment is preferred, the user has the option of
+**     instead using the eraLd function.  Similarly, eraLd can be used
+**     for cases where the source is nearby, not a star.
+**
+**  6) The returned vector sn is not normalized, but the consequential
+**     departure from unit magnitude is always negligible.
+**
+**  7) The arguments sc and sn can be the same array.
+**
+**  8) For efficiency, validation is omitted.  The supplied masses must
+**     be greater than zero, the position and velocity vectors must be
+**     right, and the deflection limiter greater than zero.
+**
+**  Reference:
+**
+**     Urban, S. & Seidelmann, P. K. (eds), Explanatory Supplement to
+**     the Astronomical Almanac, 3rd ed., University Science Books
+**     (2013), Section 7.2.4.
+**
+**  Called:
+**     eraCp        copy p-vector
+**     eraPdp       scalar product of two p-vectors
+**     eraPmp       p-vector minus p-vector
+**     eraPpsp      p-vector plus scaled p-vector
+**     eraPn        decompose p-vector into modulus and direction
+**     eraLd        light deflection by a solar-system body
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Light time for 1 AU (days) */
+   const double CR = ERFA_AULT/ERFA_DAYSEC;
+
+   int i;
+   double  v[3], dt, ev[3], em, e[3];
+
+
+/* Star direction prior to deflection. */
+   eraCp(sc, sn);
+
+/* Body by body. */
+   for ( i = 0; i < n; i++ ) {
+
+   /* Body to observer vector at epoch of observation (au). */
+      eraPmp ( ob, b[i].pv[0], v );
+
+   /* Minus the time since the light passed the body (days). */
+      dt = eraPdp(sn,v) * CR;
+
+   /* Neutralize if the star is "behind" the observer. */
+      dt = ERFA_GMIN(dt, 0.0);
+
+   /* Backtrack the body to the time the light was passing the body. */
+      eraPpsp(v, -dt, b[i].pv[1], ev);
+
+   /* Body to observer vector as magnitude and direction. */
+      eraPn(ev, &em, e);
+
+   /* Apply light deflection for this body. */
+      eraLd ( b[i].bm, sn, sn, e, em, b[i].dl, sn );
+
+   /* Next body. */
+   }
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ldsun.c b/ast/erfa/ldsun.c
new file mode 100644
index 0000000..62ed117
--- /dev/null
+++ b/ast/erfa/ldsun.c
@@ -0,0 +1,115 @@
+#include "erfa.h"
+
+void eraLdsun(double p[3], double e[3], double em, double p1[3])
+/*
+**  - - - - - - - - -
+**   e r a L d s u n
+**  - - - - - - - - -
+**
+**  Deflection of starlight by the Sun.
+**
+**  Given:
+**     p      double[3]  direction from observer to star (unit vector)
+**     e      double[3]  direction from Sun to observer (unit vector)
+**     em     double     distance from Sun to observer (au)
+**
+**  Returned:
+**     p1     double[3]  observer to deflected star (unit vector)
+**
+**  Notes:
+**
+**  1) The source is presumed to be sufficiently distant that its
+**     directions seen from the Sun and the observer are essentially
+**     the same.
+**
+**  2) The deflection is restrained when the angle between the star and
+**     the center of the Sun is less than a threshold value, falling to
+**     zero deflection for zero separation.  The chosen threshold value
+**     is within the solar limb for all solar-system applications, and
+**     is about 5 arcminutes for the case of a terrestrial observer.
+**
+**  3) The arguments p and p1 can be the same array.
+**
+**  Called:
+**     eraLd        light deflection by a solar-system body
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double em2, dlim;
+
+
+/* Deflection limiter (smaller for distant observers). */
+   em2 = em*em;
+   if ( em2 < 1.0 ) em2 = 1.0;
+   dlim = 1e-6 / (em2 > 1.0 ? em2 : 1.0);
+
+/* Apply the deflection. */
+   eraLd(1.0, p, p, e, em, dlim, p1);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/lteceq.c b/ast/erfa/lteceq.c
new file mode 100644
index 0000000..9ba725d
--- /dev/null
+++ b/ast/erfa/lteceq.c
@@ -0,0 +1,138 @@
+#include "erfa.h"
+
+void eraLteceq(double epj, double dl, double db, double *dr, double *dd)
+/*
+**  - - - - - - - - - -
+**   e r a L t e c e q
+**  - - - - - - - - - -
+**
+**  Transformation from ecliptic coordinates (mean equinox and ecliptic
+**  of date) to ICRS RA,Dec, using a long-term precession model.
+**
+**  Given:
+**     epj     double     Julian epoch (TT)
+**     dl,db   double     ecliptic longitude and latitude (radians)
+**
+**  Returned:
+**     dr,dd   double     ICRS right ascension and declination (radians)
+**
+**  1) No assumptions are made about whether the coordinates represent
+**     starlight and embody astrometric effects such as parallax or
+**     aberration.
+**
+**  2) The transformation is approximately that from ecliptic longitude
+**     and latitude (mean equinox and ecliptic of date) to mean J2000.0
+**     right ascension and declination, with only frame bias (always
+**     less than 25 mas) to disturb this classical picture.
+**
+**  3) The Vondrak et al. (2011, 2012) 400 millennia precession model
+**     agrees with the IAU 2006 precession at J2000.0 and stays within
+**     100 microarcseconds during the 20th and 21st centuries.  It is
+**     accurate to a few arcseconds throughout the historical period,
+**     worsening to a few tenths of a degree at the end of the
+**     +/- 200,000 year time span.
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraLtecm     J2000.0 to ecliptic rotation matrix, long term
+**     eraTrxp      product of transpose of r-matrix and p-vector
+**     eraC2s       unit vector to spherical coordinates
+**     eraAnp       normalize angle into range 0 to 2pi
+**     eraAnpm      normalize angle into range +/- pi
+**
+**  References:
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2011, New precession
+**    expressions, valid for long time intervals, Astron.Astrophys. 534,
+**    A22
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2012, New precession
+**    expressions, valid for long time intervals (Corrigendum),
+**    Astron.Astrophys. 541, C1
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rm[3][3], v1[3], v2[3], a, b;
+
+
+/* Spherical to Cartesian. */
+   eraS2c(dl, db, v1);
+
+/* Rotation matrix, ICRS equatorial to ecliptic. */
+   eraLtecm(epj, rm);
+
+/* The transformation from ecliptic to ICRS. */
+   eraTrxp(rm, v1, v2);
+
+/* Cartesian to spherical. */
+   eraC2s(v2, &a, &b);
+
+/* Express in conventional ranges. */
+   *dr = eraAnp(a);
+   *dd = eraAnpm(b);
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ltecm.c b/ast/erfa/ltecm.c
new file mode 100644
index 0000000..c0bf28b
--- /dev/null
+++ b/ast/erfa/ltecm.c
@@ -0,0 +1,157 @@
+#include "erfa.h"
+
+void eraLtecm(double epj, double rm[3][3])
+/*
+**  - - - - - - - - -
+**   e r a L t e c m
+**  - - - - - - - - -
+**
+**  ICRS equatorial to ecliptic rotation matrix, long-term.
+**
+**  Given:
+**     epj     double         Julian epoch (TT)
+**
+**  Returned:
+**     rm      double[3][3]   ICRS to ecliptic rotation matrix
+**
+**  Notes:
+**
+**  1) The matrix is in the sense
+**
+**        E_ep = rm x P_ICRS,
+**
+**     where P_ICRS is a vector with respect to ICRS right ascension
+**     and declination axes and E_ep is the same vector with respect to
+**     the (inertial) ecliptic and equinox of epoch epj.
+**
+**  2) P_ICRS is a free vector, merely a direction, typically of unit
+**     magnitude, and not bound to any particular spatial origin, such
+**     as the Earth, Sun or SSB.  No assumptions are made about whether
+**     it represents starlight and embodies astrometric effects such as
+**     parallax or aberration.  The transformation is approximately that
+**     between mean J2000.0 right ascension and declination and ecliptic
+**     longitude and latitude, with only frame bias (always less than
+**     25 mas) to disturb this classical picture.
+**
+**  3) The Vondrak et al. (2011, 2012) 400 millennia precession model
+**     agrees with the IAU 2006 precession at J2000.0 and stays within
+**     100 microarcseconds during the 20th and 21st centuries.  It is
+**     accurate to a few arcseconds throughout the historical period,
+**     worsening to a few tenths of a degree at the end of the
+**     +/- 200,000 year time span.
+**
+**  Called:
+**     eraLtpequ    equator pole, long term
+**     eraLtpecl    ecliptic pole, long term
+**     eraPxp       vector product
+**     eraPn        normalize vector
+**
+**  References:
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2011, New precession
+**    expressions, valid for long time intervals, Astron.Astrophys. 534,
+**    A22
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2012, New precession
+**    expressions, valid for long time intervals (Corrigendum),
+**    Astron.Astrophys. 541, C1
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Frame bias (IERS Conventions 2010, Eqs. 5.21 and 5.33) */
+   const double dx = -0.016617 * ERFA_DAS2R,
+                de = -0.0068192 * ERFA_DAS2R,
+                dr = -0.0146 * ERFA_DAS2R;
+
+   double p[3], z[3], w[3], s, x[3], y[3];
+
+
+/* Equator pole. */
+   eraLtpequ(epj, p);
+
+/* Ecliptic pole (bottom row of equatorial to ecliptic matrix). */
+   eraLtpecl(epj, z);
+
+/* Equinox (top row of matrix). */
+   eraPxp(p, z, w);
+   eraPn(w, &s, x);
+
+/* Middle row of matrix. */
+   eraPxp(z, x, y);
+
+/* Combine with frame bias. */
+   rm[0][0] =   x[0]    - x[1]*dr + x[2]*dx;
+   rm[0][1] =   x[0]*dr + x[1]    + x[2]*de;
+   rm[0][2] = - x[0]*dx - x[1]*de + x[2];
+   rm[1][0] =   y[0]    - y[1]*dr + y[2]*dx;
+   rm[1][1] =   y[0]*dr + y[1]    + y[2]*de;
+   rm[1][2] = - y[0]*dx - y[1]*de + y[2];
+   rm[2][0] =   z[0]    - z[1]*dr + z[2]*dx;
+   rm[2][1] =   z[0]*dr + z[1]    + z[2]*de;
+   rm[2][2] = - z[0]*dx - z[1]*de + z[2];
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/lteqec.c b/ast/erfa/lteqec.c
new file mode 100644
index 0000000..a93ec0f
--- /dev/null
+++ b/ast/erfa/lteqec.c
@@ -0,0 +1,139 @@
+#include "erfa.h"
+
+void eraLteqec(double epj, double dr, double dd, double *dl, double *db)
+/*
+**  - - - - - - - - - -
+**   e r a L t e q e c
+**  - - - - - - - - - -
+**
+**  Transformation from ICRS equatorial coordinates to ecliptic
+**  coordinates (mean equinox and ecliptic of date) using a long-term
+**  precession model.
+**
+**  Given:
+**     epj     double     Julian epoch (TT)
+**     dr,dd   double     ICRS right ascension and declination (radians)
+**
+**  Returned:
+**     dl,db   double     ecliptic longitude and latitude (radians)
+**
+**  1) No assumptions are made about whether the coordinates represent
+**     starlight and embody astrometric effects such as parallax or
+**     aberration.
+**
+**  2) The transformation is approximately that from mean J2000.0 right
+**     ascension and declination to ecliptic longitude and latitude
+**     (mean equinox and ecliptic of date), with only frame bias (always
+**     less than 25 mas) to disturb this classical picture.
+**
+**  3) The Vondrak et al. (2011, 2012) 400 millennia precession model
+**     agrees with the IAU 2006 precession at J2000.0 and stays within
+**     100 microarcseconds during the 20th and 21st centuries.  It is
+**     accurate to a few arcseconds throughout the historical period,
+**     worsening to a few tenths of a degree at the end of the
+**     +/- 200,000 year time span.
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraLtecm     J2000.0 to ecliptic rotation matrix, long term
+**     eraRxp       product of r-matrix and p-vector
+**     eraC2s       unit vector to spherical coordinates
+**     eraAnp       normalize angle into range 0 to 2pi
+**     eraAnpm      normalize angle into range +/- pi
+**
+**  References:
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2011, New precession
+**    expressions, valid for long time intervals, Astron.Astrophys. 534,
+**    A22
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2012, New precession
+**    expressions, valid for long time intervals (Corrigendum),
+**    Astron.Astrophys. 541, C1
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rm[3][3], v1[3], v2[3], a, b;
+
+
+/* Spherical to Cartesian. */
+   eraS2c(dr, dd, v1);
+
+/* Rotation matrix, ICRS equatorial to ecliptic. */
+   eraLtecm(epj, rm);
+
+/* The transformation from ICRS to ecliptic. */
+   eraRxp(rm, v1, v2);
+
+/* Cartesian to spherical. */
+   eraC2s(v2, &a, &b);
+
+/* Express in conventional ranges. */
+   *dl = eraAnp(a);
+   *db = eraAnpm(b);
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ltp.c b/ast/erfa/ltp.c
new file mode 100644
index 0000000..2b8249c
--- /dev/null
+++ b/ast/erfa/ltp.c
@@ -0,0 +1,140 @@
+#include "erfa.h"
+
+void eraLtp(double epj, double rp[3][3])
+/*
+**  - - - - - - -
+**   e r a L t p
+**  - - - - - - -
+**
+**  Long-term precession matrix.
+**
+**  Given:
+**     epj     double         Julian epoch (TT)
+**
+**  Returned:
+**     rp      double[3][3]   precession matrix, J2000.0 to date
+**
+**  Notes:
+**
+**  1) The matrix is in the sense
+**
+**        P_date = rp x P_J2000,
+**
+**     where P_J2000 is a vector with respect to the J2000.0 mean
+**     equator and equinox and P_date is the same vector with respect to
+**     the equator and equinox of epoch epj.
+**
+**  2) The Vondrak et al. (2011, 2012) 400 millennia precession model
+**     agrees with the IAU 2006 precession at J2000.0 and stays within
+**     100 microarcseconds during the 20th and 21st centuries.  It is
+**     accurate to a few arcseconds throughout the historical period,
+**     worsening to a few tenths of a degree at the end of the
+**     +/- 200,000 year time span.
+**
+**  Called:
+**     eraLtpequ    equator pole, long term
+**     eraLtpecl    ecliptic pole, long term
+**     eraPxp       vector product
+**     eraPn        normalize vector
+**
+**  References:
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2011, New precession
+**    expressions, valid for long time intervals, Astron.Astrophys. 534,
+**    A22
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2012, New precession
+**    expressions, valid for long time intervals (Corrigendum),
+**    Astron.Astrophys. 541, C1
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int i;
+   double peqr[3], pecl[3], v[3], w, eqx[3];
+
+
+/* Equator pole (bottom row of matrix). */
+   eraLtpequ(epj, peqr);
+
+/* Ecliptic pole. */
+   eraLtpecl(epj, pecl);
+
+/* Equinox (top row of matrix). */
+   eraPxp(peqr, pecl, v);
+   eraPn(v, &w, eqx);
+
+/* Middle row of matrix. */
+   eraPxp(peqr, eqx, v);
+
+/* Assemble the matrix. */
+   for ( i = 0; i < 3; i++ ) {
+      rp[0][i] = eqx[i];
+      rp[1][i] = v[i];
+      rp[2][i] = peqr[i];
+   }
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ltpb.c b/ast/erfa/ltpb.c
new file mode 100644
index 0000000..6818f19
--- /dev/null
+++ b/ast/erfa/ltpb.c
@@ -0,0 +1,133 @@
+#include "erfa.h"
+
+void eraLtpb(double epj, double rpb[3][3])
+/*
+**  - - - - - - - -
+**   e r a L t p b
+**  - - - - - - - -
+**
+**  Long-term precession matrix, including ICRS frame bias.
+**
+**  Given:
+**     epj     double         Julian epoch (TT)
+**
+**  Returned:
+**     rpb     double[3][3]   precession-bias matrix, J2000.0 to date
+**
+**  Notes:
+**
+**  1) The matrix is in the sense
+**
+**        P_date = rpb x P_ICRS,
+**
+**     where P_ICRS is a vector in the Geocentric Celestial Reference
+**     System, and P_date is the vector with respect to the Celestial
+**     Intermediate Reference System at that date but with nutation
+**     neglected.
+**
+**  2) A first order frame bias formulation is used, of sub-
+**     microarcsecond accuracy compared with a full 3D rotation.
+**
+**  3) The Vondrak et al. (2011, 2012) 400 millennia precession model
+**     agrees with the IAU 2006 precession at J2000.0 and stays within
+**     100 microarcseconds during the 20th and 21st centuries.  It is
+**     accurate to a few arcseconds throughout the historical period,
+**     worsening to a few tenths of a degree at the end of the
+**     +/- 200,000 year time span.
+**
+**  References:
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2011, New precession
+**    expressions, valid for long time intervals, Astron.Astrophys. 534,
+**    A22
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2012, New precession
+**    expressions, valid for long time intervals (Corrigendum),
+**    Astron.Astrophys. 541, C1
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Frame bias (IERS Conventions 2010, Eqs. 5.21 and 5.33) */
+   const double dx = -0.016617 * ERFA_DAS2R,
+                de = -0.0068192 * ERFA_DAS2R,
+                dr = -0.0146 * ERFA_DAS2R;
+
+   int i;
+   double rp[3][3];
+
+
+/* Precession matrix. */
+   eraLtp(epj, rp);
+
+/* Apply the bias. */
+   for ( i = 0; i < 3; i++ ) {
+      rpb[i][0] =  rp[i][0]    - rp[i][1]*dr + rp[i][2]*dx;
+      rpb[i][1] =  rp[i][0]*dr + rp[i][1]    + rp[i][2]*de;
+      rpb[i][2] = -rp[i][0]*dx - rp[i][1]*de + rp[i][2];
+   }
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ltpecl.c b/ast/erfa/ltpecl.c
new file mode 100644
index 0000000..feb3d55
--- /dev/null
+++ b/ast/erfa/ltpecl.c
@@ -0,0 +1,177 @@
+#include "erfa.h"
+
+void eraLtpecl(double epj, double vec[3])
+/*
+**  - - - - - - - - - -
+**   e r a L t p e c l
+**  - - - - - - - - - -
+**
+**  Long-term precession of the ecliptic.
+**
+**  Given:
+**     epj     double         Julian epoch (TT)
+**
+**  Returned:
+**     vec     double[3]      ecliptic pole unit vector
+**
+**  Notes:
+**
+**  1) The returned vector is with respect to the J2000.0 mean equator
+**     and equinox.
+**
+**  2) The Vondrak et al. (2011, 2012) 400 millennia precession model
+**     agrees with the IAU 2006 precession at J2000.0 and stays within
+**     100 microarcseconds during the 20th and 21st centuries.  It is
+**     accurate to a few arcseconds throughout the historical period,
+**     worsening to a few tenths of a degree at the end of the
+**     +/- 200,000 year time span.
+**
+**  References:
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2011, New precession
+**    expressions, valid for long time intervals, Astron.Astrophys. 534,
+**    A22
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2012, New precession
+**    expressions, valid for long time intervals (Corrigendum),
+**    Astron.Astrophys. 541, C1
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Obliquity at J2000.0 (radians). */
+   static const double eps0 = 84381.406 * ERFA_DAS2R;
+
+/* Polynomial coefficients */
+   enum { NPOL = 4 };
+   static const double pqpol[2][NPOL] = {
+      { 5851.607687,
+          -0.1189000,
+          -0.00028913,
+           0.000000101},
+      {-1600.886300,
+           1.1689818,
+          -0.00000020,
+          -0.000000437}
+   };
+
+/* Periodic coefficients */
+   static const double pqper[][5] = {
+      { 708.15,-5486.751211,-684.661560,  667.666730,-5523.863691},
+      {2309.00,  -17.127623,2446.283880,-2354.886252, -549.747450},
+      {1620.00, -617.517403, 399.671049, -428.152441, -310.998056},
+      { 492.20,  413.442940,-356.652376,  376.202861,  421.535876},
+      {1183.00,   78.614193,-186.387003,  184.778874,  -36.776172},
+      { 622.00, -180.732815,-316.800070,  335.321713, -145.278396},
+      { 882.00,  -87.676083, 198.296701, -185.138669,  -34.744450},
+      { 547.00,   46.140315, 101.135679, -120.972830,   22.885731}
+   };
+   static const int NPER = (int) ( sizeof pqper / 5 / sizeof (double) );
+
+/* Miscellaneous */
+   int i;
+   double t, p, q, w, a, s, c;
+
+
+/* Centuries since J2000. */
+   t  = ( epj - 2000.0 ) / 100.0;
+
+/* Initialize P_A and Q_A accumulators. */
+   p = 0.0;
+   q = 0.0;
+
+/* Periodic terms. */
+   w = ERFA_D2PI*t;
+   for ( i = 0; i < NPER; i++ ) {
+      a = w/pqper[i][0];
+      s = sin(a);
+      c = cos(a);
+      p += c*pqper[i][1] + s*pqper[i][3];
+      q += c*pqper[i][2] + s*pqper[i][4];
+   }
+
+/* Polynomial terms. */
+   w = 1.0;
+   for ( i = 0; i < NPOL; i++ ) {
+      p += pqpol[0][i]*w;
+      q += pqpol[1][i]*w;
+      w *= t;
+   }
+
+/* P_A and Q_A (radians). */
+   p *= ERFA_DAS2R;
+   q *= ERFA_DAS2R;
+
+/* Form the ecliptic pole vector. */
+   w = 1.0 - p*p - q*q;
+   w = w < 0.0 ? 0.0 : sqrt(w);
+   s = sin(eps0);
+   c = cos(eps0);
+   vec[0] = p;
+   vec[1] = - q*c - w*s;
+   vec[2] = - q*s + w*c;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ltpequ.c b/ast/erfa/ltpequ.c
new file mode 100644
index 0000000..354ce32
--- /dev/null
+++ b/ast/erfa/ltpequ.c
@@ -0,0 +1,177 @@
+#include "erfa.h"
+
+void eraLtpequ(double epj, double veq[3])
+/*
+**  - - - - - - - - - -
+**   e r a L t p e q u
+**  - - - - - - - - - -
+**
+**  Long-term precession of the equator.
+**
+**  Given:
+**     epj     double         Julian epoch (TT)
+**
+**  Returned:
+**     veq     double[3]      equator pole unit vector
+**
+**  Notes:
+**
+**  1) The returned vector is with respect to the J2000.0 mean equator
+**     and equinox.
+**
+**  2) The Vondrak et al. (2011, 2012) 400 millennia precession model
+**     agrees with the IAU 2006 precession at J2000.0 and stays within
+**     100 microarcseconds during the 20th and 21st centuries.  It is
+**     accurate to a few arcseconds throughout the historical period,
+**     worsening to a few tenths of a degree at the end of the
+**     +/- 200,000 year time span.
+**
+**  References:
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2011, New precession
+**    expressions, valid for long time intervals, Astron.Astrophys. 534,
+**    A22
+**
+**    Vondrak, J., Capitaine, N. and Wallace, P., 2012, New precession
+**    expressions, valid for long time intervals (Corrigendum),
+**    Astron.Astrophys. 541, C1
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Polynomial coefficients */
+   enum { NPOL = 4 };
+   static const double xypol[2][NPOL] = {
+      {  5453.282155,
+            0.4252841,
+           -0.00037173,
+           -0.000000152},
+      {-73750.930350,
+           -0.7675452,
+           -0.00018725,
+            0.000000231}
+   };
+
+/* Periodic coefficients */
+   static const double xyper[][5] = {
+      { 256.75, -819.940624,75004.344875,81491.287984, 1558.515853},
+      { 708.15,-8444.676815,  624.033993,  787.163481, 7774.939698},
+      { 274.20, 2600.009459, 1251.136893, 1251.296102,-2219.534038},
+      { 241.45, 2755.175630,-1102.212834,-1257.950837,-2523.969396},
+      {2309.00, -167.659835,-2660.664980,-2966.799730,  247.850422},
+      { 492.20,  871.855056,  699.291817,  639.744522, -846.485643},
+      { 396.10,   44.769698,  153.167220,  131.600209,-1393.124055},
+      { 288.90, -512.313065, -950.865637, -445.040117,  368.526116},
+      { 231.10, -819.415595,  499.754645,  584.522874,  749.045012},
+      {1610.00, -538.071099, -145.188210,  -89.756563,  444.704518},
+      { 620.00, -189.793622,  558.116553,  524.429630,  235.934465},
+      { 157.87, -402.922932,  -23.923029,  -13.549067,  374.049623},
+      { 220.30,  179.516345, -165.405086, -210.157124, -171.330180},
+      {1200.00,   -9.814756,    9.344131,  -44.919798,  -22.899655}
+   };
+   static const int NPER = (int) ( sizeof xyper / 5 / sizeof (double) );
+
+/* Miscellaneous */
+   int i;
+   double t, x, y, w, a, s, c;
+
+
+/* Centuries since J2000. */
+   t  = ( epj - 2000.0 ) / 100.0;
+
+/* Initialize X and Y accumulators. */
+   x = 0.0;
+   y = 0.0;
+
+/* Periodic terms. */
+   w = ERFA_D2PI * t;
+   for ( i = 0; i < NPER; i++ ) {
+      a = w / xyper[i][0];
+      s = sin(a);
+      c = cos(a);
+      x += c*xyper[i][1] + s*xyper[i][3];
+      y += c*xyper[i][2] + s*xyper[i][4];
+   }
+
+/* Polynomial terms. */
+   w = 1.0;
+   for ( i = 0; i < NPOL; i++ ) {
+      x += xypol[0][i]*w;
+      y += xypol[1][i]*w;
+      w *= t;
+   }
+
+/* X and Y (direction cosines). */
+   x *= ERFA_DAS2R;
+   y *= ERFA_DAS2R;
+
+/* Form the equator pole vector. */
+   veq[0] = x;
+   veq[1] = y;
+   w = 1.0 - x*x - y*y;
+   veq[2] = w < 0.0 ? 0.0 : sqrt(w);
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/num00a.c b/ast/erfa/num00a.c
new file mode 100644
index 0000000..24964e3
--- /dev/null
+++ b/ast/erfa/num00a.c
@@ -0,0 +1,130 @@
+#include "erfa.h"
+
+void eraNum00a(double date1, double date2, double rmatn[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a N u m 0 0 a
+**  - - - - - - - - - -
+**
+**  Form the matrix of nutation for a given date, IAU 2000A model.
+**
+**  Given:
+**     date1,date2  double          TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rmatn        double[3][3]    nutation matrix
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(true) = rmatn * V(mean), where
+**     the p-vector V(true) is with respect to the true equatorial triad
+**     of date and the p-vector V(mean) is with respect to the mean
+**     equatorial triad of date.
+**
+**  3) A faster, but slightly less accurate result (about 1 mas), can be
+**     obtained by using instead the eraNum00b function.
+**
+**  Called:
+**     eraPn00a     bias/precession/nutation, IAU 2000A
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Section 3.222-3 (p114).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dpsi, deps, epsa, rb[3][3], rp[3][3], rbp[3][3], rbpn[3][3];
+
+
+/* Obtain the required matrix (discarding other results). */
+   eraPn00a(date1, date2,
+            &dpsi, &deps, &epsa, rb, rp, rbp, rmatn, rbpn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/num00b.c b/ast/erfa/num00b.c
new file mode 100644
index 0000000..f006257
--- /dev/null
+++ b/ast/erfa/num00b.c
@@ -0,0 +1,130 @@
+#include "erfa.h"
+
+void eraNum00b(double date1, double date2, double rmatn[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a N u m 0 0 b
+**  - - - - - - - - - -
+**
+**  Form the matrix of nutation for a given date, IAU 2000B model.
+**
+**  Given:
+**     date1,date2  double         TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rmatn        double[3][3]   nutation matrix
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(true) = rmatn * V(mean), where
+**     the p-vector V(true) is with respect to the true equatorial triad
+**     of date and the p-vector V(mean) is with respect to the mean
+**     equatorial triad of date.
+**
+**  3) The present function is faster, but slightly less accurate (about
+**     1 mas), than the eraNum00a function.
+**
+**  Called:
+**     eraPn00b     bias/precession/nutation, IAU 2000B
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Section 3.222-3 (p114).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dpsi, deps, epsa, rb[3][3], rp[3][3], rbp[3][3], rbpn[3][3];
+
+
+/* Obtain the required matrix (discarding other results). */
+   eraPn00b(date1, date2,
+            &dpsi, &deps, &epsa, rb, rp, rbp, rmatn, rbpn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/num06a.c b/ast/erfa/num06a.c
new file mode 100644
index 0000000..a27d23a
--- /dev/null
+++ b/ast/erfa/num06a.c
@@ -0,0 +1,134 @@
+#include "erfa.h"
+
+void eraNum06a(double date1, double date2, double rmatn[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a N u m 0 6 a
+**  - - - - - - - - - -
+**
+**  Form the matrix of nutation for a given date, IAU 2006/2000A model.
+**
+**  Given:
+**     date1,date2   double          TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rmatn         double[3][3]    nutation matrix
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(true) = rmatn * V(mean), where
+**     the p-vector V(true) is with respect to the true equatorial triad
+**     of date and the p-vector V(mean) is with respect to the mean
+**     equatorial triad of date.
+**
+**  Called:
+**     eraObl06     mean obliquity, IAU 2006
+**     eraNut06a    nutation, IAU 2006/2000A
+**     eraNumat     form nutation matrix
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Section 3.222-3 (p114).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double eps, dp, de;
+
+
+/* Mean obliquity. */
+   eps = eraObl06(date1, date2);
+
+/* Nutation components. */
+   eraNut06a(date1, date2, &dp, &de);
+
+/* Nutation matrix. */
+   eraNumat(eps, dp, de, rmatn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/numat.c b/ast/erfa/numat.c
new file mode 100644
index 0000000..43a3b00
--- /dev/null
+++ b/ast/erfa/numat.c
@@ -0,0 +1,118 @@
+#include "erfa.h"
+
+void eraNumat(double epsa, double dpsi, double deps, double rmatn[3][3])
+/*
+**  - - - - - - - - -
+**   e r a N u m a t
+**  - - - - - - - - -
+**
+**  Form the matrix of nutation.
+**
+**  Given:
+**     epsa        double         mean obliquity of date (Note 1)
+**     dpsi,deps   double         nutation (Note 2)
+**
+**  Returned:
+**     rmatn       double[3][3]   nutation matrix (Note 3)
+**
+**  Notes:
+**
+**
+**  1) The supplied mean obliquity epsa, must be consistent with the
+**     precession-nutation models from which dpsi and deps were obtained.
+**
+**  2) The caller is responsible for providing the nutation components;
+**     they are in longitude and obliquity, in radians and are with
+**     respect to the equinox and ecliptic of date.
+**
+**  3) The matrix operates in the sense V(true) = rmatn * V(mean),
+**     where the p-vector V(true) is with respect to the true
+**     equatorial triad of date and the p-vector V(mean) is with
+**     respect to the mean equatorial triad of date.
+**
+**  Called:
+**     eraIr        initialize r-matrix to identity
+**     eraRx        rotate around X-axis
+**     eraRz        rotate around Z-axis
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Section 3.222-3 (p114).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Build the rotation matrix. */
+   eraIr(rmatn);
+   eraRx(epsa, rmatn);
+   eraRz(-dpsi, rmatn);
+   eraRx(-(epsa + deps), rmatn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/nut00a.c b/ast/erfa/nut00a.c
new file mode 100644
index 0000000..4c4e89a
--- /dev/null
+++ b/ast/erfa/nut00a.c
@@ -0,0 +1,2056 @@
+#include "erfa.h"
+
+void eraNut00a(double date1, double date2, double *dpsi, double *deps)
+/*
+**  - - - - - - - - - -
+**   e r a N u t 0 0 a
+**  - - - - - - - - - -
+**
+**  Nutation, IAU 2000A model (MHB2000 luni-solar and planetary nutation
+**  with free core nutation omitted).
+**
+**  Given:
+**     date1,date2   double   TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     dpsi,deps     double   nutation, luni-solar + planetary (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The nutation components in longitude and obliquity are in radians
+**     and with respect to the equinox and ecliptic of date.  The
+**     obliquity at J2000.0 is assumed to be the Lieske et al. (1977)
+**     value of 84381.448 arcsec.
+**
+**     Both the luni-solar and planetary nutations are included.  The
+**     latter are due to direct planetary nutations and the
+**     perturbations of the lunar and terrestrial orbits.
+**
+**  3) The function computes the MHB2000 nutation series with the
+**     associated corrections for planetary nutations.  It is an
+**     implementation of the nutation part of the IAU 2000A precession-
+**     nutation model, formally adopted by the IAU General Assembly in
+**     2000, namely MHB2000 (Mathews et al. 2002), but with the free
+**     core nutation (FCN - see Note 4) omitted.
+**
+**  4) The full MHB2000 model also contains contributions to the
+**     nutations in longitude and obliquity due to the free-excitation
+**     of the free-core-nutation during the period 1979-2000.  These FCN
+**     terms, which are time-dependent and unpredictable, are NOT
+**     included in the present function and, if required, must be
+**     independently computed.  With the FCN corrections included, the
+**     present function delivers a pole which is at current epochs
+**     accurate to a few hundred microarcseconds.  The omission of FCN
+**     introduces further errors of about that size.
+**
+**  5) The present function provides classical nutation.  The MHB2000
+**     algorithm, from which it is adapted, deals also with (i) the
+**     offsets between the GCRS and mean poles and (ii) the adjustments
+**     in longitude and obliquity due to the changed precession rates.
+**     These additional functions, namely frame bias and precession
+**     adjustments, are supported by the ERFA functions eraBi00  and
+**     eraPr00.
+**
+**  6) The MHB2000 algorithm also provides "total" nutations, comprising
+**     the arithmetic sum of the frame bias, precession adjustments,
+**     luni-solar nutation and planetary nutation.  These total
+**     nutations can be used in combination with an existing IAU 1976
+**     precession implementation, such as eraPmat76,  to deliver GCRS-
+**     to-true predictions of sub-mas accuracy at current dates.
+**     However, there are three shortcomings in the MHB2000 model that
+**     must be taken into account if more accurate or definitive results
+**     are required (see Wallace 2002):
+**
+**       (i) The MHB2000 total nutations are simply arithmetic sums,
+**           yet in reality the various components are successive Euler
+**           rotations.  This slight lack of rigor leads to cross terms
+**           that exceed 1 mas after a century.  The rigorous procedure
+**           is to form the GCRS-to-true rotation matrix by applying the
+**           bias, precession and nutation in that order.
+**
+**      (ii) Although the precession adjustments are stated to be with
+**           respect to Lieske et al. (1977), the MHB2000 model does
+**           not specify which set of Euler angles are to be used and
+**           how the adjustments are to be applied.  The most literal
+**           and straightforward procedure is to adopt the 4-rotation
+**           epsilon_0, psi_A, omega_A, xi_A option, and to add DPSIPR
+**           to psi_A and DEPSPR to both omega_A and eps_A.
+**
+**     (iii) The MHB2000 model predates the determination by Chapront
+**           et al. (2002) of a 14.6 mas displacement between the
+**           J2000.0 mean equinox and the origin of the ICRS frame.  It
+**           should, however, be noted that neglecting this displacement
+**           when calculating star coordinates does not lead to a
+**           14.6 mas change in right ascension, only a small second-
+**           order distortion in the pattern of the precession-nutation
+**           effect.
+**
+**     For these reasons, the ERFA functions do not generate the "total
+**     nutations" directly, though they can of course easily be
+**     generated by calling eraBi00, eraPr00 and the present function
+**     and adding the results.
+**
+**  7) The MHB2000 model contains 41 instances where the same frequency
+**     appears multiple times, of which 38 are duplicates and three are
+**     triplicates.  To keep the present code close to the original MHB
+**     algorithm, this small inefficiency has not been corrected.
+**
+**  Called:
+**     eraFal03     mean anomaly of the Moon
+**     eraFaf03     mean argument of the latitude of the Moon
+**     eraFaom03    mean longitude of the Moon's ascending node
+**     eraFame03    mean longitude of Mercury
+**     eraFave03    mean longitude of Venus
+**     eraFae03     mean longitude of Earth
+**     eraFama03    mean longitude of Mars
+**     eraFaju03    mean longitude of Jupiter
+**     eraFasa03    mean longitude of Saturn
+**     eraFaur03    mean longitude of Uranus
+**     eraFapa03    general accumulated precession in longitude
+**
+**  References:
+**
+**     Chapront, J., Chapront-Touze, M. & Francou, G. 2002,
+**     Astron.Astrophys. 387, 700
+**
+**     Lieske, J.H., Lederle, T., Fricke, W. & Morando, B. 1977,
+**     Astron.Astrophys. 58, 1-16
+**
+**     Mathews, P.M., Herring, T.A., Buffet, B.A. 2002, J.Geophys.Res.
+**     107, B4.  The MHB_2000 code itself was obtained on 9th September
+**     2002 from ftp//maia.usno.navy.mil/conv2000/chapter5/IAU2000A.
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**     Souchay, J., Loysel, B., Kinoshita, H., Folgueira, M. 1999,
+**     Astron.Astrophys.Supp.Ser. 135, 111
+**
+**     Wallace, P.T., "Software for Implementing the IAU 2000
+**     Resolutions", in IERS Workshop 5.1 (2002)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int i;
+   double t, el, elp, f, d, om, arg, dp, de, sarg, carg,
+          al, af, ad, aom, alme, alve, alea, alma,
+          alju, alsa, alur, alne, apa, dpsils, depsls,
+          dpsipl, depspl;
+
+/* Units of 0.1 microarcsecond to radians */
+   const double U2R = ERFA_DAS2R / 1e7;
+
+/* ------------------------- */
+/* Luni-Solar nutation model */
+/* ------------------------- */
+
+/* The units for the sine and cosine coefficients are */
+/* 0.1 microarcsecond and the same per Julian century */
+
+   static const struct {
+      int nl,nlp,nf,nd,nom; /* coefficients of l,l',F,D,Om */
+      double sp,spt,cp;     /* longitude sin, t*sin, cos coefficients */
+      double ce,cet,se;     /* obliquity cos, t*cos, sin coefficients */
+   } xls[] = {
+
+   /* 1- 10 */
+      { 0, 0, 0, 0, 1,
+         -172064161.0, -174666.0, 33386.0, 92052331.0, 9086.0, 15377.0},
+      { 0, 0, 2,-2, 2,
+           -13170906.0, -1675.0, -13696.0, 5730336.0, -3015.0, -4587.0},
+      { 0, 0, 2, 0, 2,-2276413.0,-234.0,2796.0,978459.0,-485.0, 1374.0},
+      { 0, 0, 0, 0, 2,2074554.0, 207.0, -698.0,-897492.0,470.0, -291.0},
+      { 0, 1, 0, 0, 0,1475877.0,-3633.0,11817.0,73871.0,-184.0,-1924.0},
+      { 0, 1, 2,-2, 2,-516821.0,1226.0, -524.0,224386.0,-677.0, -174.0},
+      { 1, 0, 0, 0, 0, 711159.0,  73.0, -872.0,  -6750.0,  0.0,  358.0},
+      { 0, 0, 2, 0, 1,-387298.0,-367.0,  380.0, 200728.0, 18.0,  318.0},
+      { 1, 0, 2, 0, 2,-301461.0, -36.0,  816.0, 129025.0,-63.0,  367.0},
+      { 0,-1, 2,-2, 2, 215829.0,-494.0,  111.0, -95929.0,299.0,  132.0},
+
+   /* 11-20 */
+      { 0, 0, 2,-2, 1, 128227.0, 137.0,  181.0, -68982.0, -9.0,   39.0},
+      {-1, 0, 2, 0, 2, 123457.0,  11.0,   19.0, -53311.0, 32.0,   -4.0},
+      {-1, 0, 0, 2, 0, 156994.0,  10.0, -168.0,  -1235.0,  0.0,   82.0},
+      { 1, 0, 0, 0, 1,  63110.0,  63.0,   27.0, -33228.0,  0.0,   -9.0},
+      {-1, 0, 0, 0, 1, -57976.0, -63.0, -189.0,  31429.0,  0.0,  -75.0},
+      {-1, 0, 2, 2, 2, -59641.0, -11.0,  149.0,  25543.0,-11.0,   66.0},
+      { 1, 0, 2, 0, 1, -51613.0, -42.0,  129.0,  26366.0,  0.0,   78.0},
+      {-2, 0, 2, 0, 1,  45893.0,  50.0,   31.0, -24236.0,-10.0,   20.0},
+      { 0, 0, 0, 2, 0,  63384.0,  11.0, -150.0,  -1220.0,  0.0,   29.0},
+      { 0, 0, 2, 2, 2, -38571.0,  -1.0,  158.0,  16452.0,-11.0,   68.0},
+
+   /* 21-30 */
+      { 0,-2, 2,-2, 2,  32481.0,   0.0,    0.0, -13870.0,  0.0,    0.0},
+      {-2, 0, 0, 2, 0, -47722.0,   0.0,  -18.0,    477.0,  0.0,  -25.0},
+      { 2, 0, 2, 0, 2, -31046.0,  -1.0,  131.0,  13238.0,-11.0,   59.0},
+      { 1, 0, 2,-2, 2,  28593.0,   0.0,   -1.0, -12338.0, 10.0,   -3.0},
+      {-1, 0, 2, 0, 1,  20441.0,  21.0,   10.0, -10758.0,  0.0,   -3.0},
+      { 2, 0, 0, 0, 0,  29243.0,   0.0,  -74.0,   -609.0,  0.0,   13.0},
+      { 0, 0, 2, 0, 0,  25887.0,   0.0,  -66.0,   -550.0,  0.0,   11.0},
+      { 0, 1, 0, 0, 1, -14053.0, -25.0,   79.0,   8551.0, -2.0,  -45.0},
+      {-1, 0, 0, 2, 1,  15164.0,  10.0,   11.0,  -8001.0,  0.0,   -1.0},
+      { 0, 2, 2,-2, 2, -15794.0,  72.0,  -16.0,   6850.0,-42.0,   -5.0},
+
+   /* 31-40 */
+      { 0, 0,-2, 2, 0,  21783.0,   0.0,   13.0,   -167.0,  0.0,   13.0},
+      { 1, 0, 0,-2, 1, -12873.0, -10.0,  -37.0,   6953.0,  0.0,  -14.0},
+      { 0,-1, 0, 0, 1, -12654.0,  11.0,   63.0,   6415.0,  0.0,   26.0},
+      {-1, 0, 2, 2, 1, -10204.0,   0.0,   25.0,   5222.0,  0.0,   15.0},
+      { 0, 2, 0, 0, 0,  16707.0, -85.0,  -10.0,    168.0, -1.0,   10.0},
+      { 1, 0, 2, 2, 2,  -7691.0,   0.0,   44.0,   3268.0,  0.0,   19.0},
+      {-2, 0, 2, 0, 0, -11024.0,   0.0,  -14.0,    104.0,  0.0,    2.0},
+      { 0, 1, 2, 0, 2,   7566.0, -21.0,  -11.0,  -3250.0,  0.0,   -5.0},
+      { 0, 0, 2, 2, 1,  -6637.0, -11.0,   25.0,   3353.0,  0.0,   14.0},
+      { 0,-1, 2, 0, 2,  -7141.0,  21.0,    8.0,   3070.0,  0.0,    4.0},
+
+   /* 41-50 */
+      { 0, 0, 0, 2, 1,  -6302.0, -11.0,    2.0,   3272.0,  0.0,    4.0},
+      { 1, 0, 2,-2, 1,   5800.0,  10.0,    2.0,  -3045.0,  0.0,   -1.0},
+      { 2, 0, 2,-2, 2,   6443.0,   0.0,   -7.0,  -2768.0,  0.0,   -4.0},
+      {-2, 0, 0, 2, 1,  -5774.0, -11.0,  -15.0,   3041.0,  0.0,   -5.0},
+      { 2, 0, 2, 0, 1,  -5350.0,   0.0,   21.0,   2695.0,  0.0,   12.0},
+      { 0,-1, 2,-2, 1,  -4752.0, -11.0,   -3.0,   2719.0,  0.0,   -3.0},
+      { 0, 0, 0,-2, 1,  -4940.0, -11.0,  -21.0,   2720.0,  0.0,   -9.0},
+      {-1,-1, 0, 2, 0,   7350.0,   0.0,   -8.0,    -51.0,  0.0,    4.0},
+      { 2, 0, 0,-2, 1,   4065.0,   0.0,    6.0,  -2206.0,  0.0,    1.0},
+      { 1, 0, 0, 2, 0,   6579.0,   0.0,  -24.0,   -199.0,  0.0,    2.0},
+
+   /* 51-60 */
+      { 0, 1, 2,-2, 1,   3579.0,   0.0,    5.0,  -1900.0,  0.0,    1.0},
+      { 1,-1, 0, 0, 0,   4725.0,   0.0,   -6.0,    -41.0,  0.0,    3.0},
+      {-2, 0, 2, 0, 2,  -3075.0,   0.0,   -2.0,   1313.0,  0.0,   -1.0},
+      { 3, 0, 2, 0, 2,  -2904.0,   0.0,   15.0,   1233.0,  0.0,    7.0},
+      { 0,-1, 0, 2, 0,   4348.0,   0.0,  -10.0,    -81.0,  0.0,    2.0},
+      { 1,-1, 2, 0, 2,  -2878.0,   0.0,    8.0,   1232.0,  0.0,    4.0},
+      { 0, 0, 0, 1, 0,  -4230.0,   0.0,    5.0,    -20.0,  0.0,   -2.0},
+      {-1,-1, 2, 2, 2,  -2819.0,   0.0,    7.0,   1207.0,  0.0,    3.0},
+      {-1, 0, 2, 0, 0,  -4056.0,   0.0,    5.0,     40.0,  0.0,   -2.0},
+      { 0,-1, 2, 2, 2,  -2647.0,   0.0,   11.0,   1129.0,  0.0,    5.0},
+
+   /* 61-70 */
+      {-2, 0, 0, 0, 1,  -2294.0,   0.0,  -10.0,   1266.0,  0.0,   -4.0},
+      { 1, 1, 2, 0, 2,   2481.0,   0.0,   -7.0,  -1062.0,  0.0,   -3.0},
+      { 2, 0, 0, 0, 1,   2179.0,   0.0,   -2.0,  -1129.0,  0.0,   -2.0},
+      {-1, 1, 0, 1, 0,   3276.0,   0.0,    1.0,     -9.0,  0.0,    0.0},
+      { 1, 1, 0, 0, 0,  -3389.0,   0.0,    5.0,     35.0,  0.0,   -2.0},
+      { 1, 0, 2, 0, 0,   3339.0,   0.0,  -13.0,   -107.0,  0.0,    1.0},
+      {-1, 0, 2,-2, 1,  -1987.0,   0.0,   -6.0,   1073.0,  0.0,   -2.0},
+      { 1, 0, 0, 0, 2,  -1981.0,   0.0,    0.0,    854.0,  0.0,    0.0},
+      {-1, 0, 0, 1, 0,   4026.0,   0.0, -353.0,   -553.0,  0.0, -139.0},
+      { 0, 0, 2, 1, 2,   1660.0,   0.0,   -5.0,   -710.0,  0.0,   -2.0},
+
+   /* 71-80 */
+      {-1, 0, 2, 4, 2,  -1521.0,   0.0,    9.0,    647.0,  0.0,    4.0},
+      {-1, 1, 0, 1, 1,   1314.0,   0.0,    0.0,   -700.0,  0.0,    0.0},
+      { 0,-2, 2,-2, 1,  -1283.0,   0.0,    0.0,    672.0,  0.0,    0.0},
+      { 1, 0, 2, 2, 1,  -1331.0,   0.0,    8.0,    663.0,  0.0,    4.0},
+      {-2, 0, 2, 2, 2,   1383.0,   0.0,   -2.0,   -594.0,  0.0,   -2.0},
+      {-1, 0, 0, 0, 2,   1405.0,   0.0,    4.0,   -610.0,  0.0,    2.0},
+      { 1, 1, 2,-2, 2,   1290.0,   0.0,    0.0,   -556.0,  0.0,    0.0},
+      {-2, 0, 2, 4, 2,  -1214.0,   0.0,    5.0,    518.0,  0.0,    2.0},
+      {-1, 0, 4, 0, 2,   1146.0,   0.0,   -3.0,   -490.0,  0.0,   -1.0},
+      { 2, 0, 2,-2, 1,   1019.0,   0.0,   -1.0,   -527.0,  0.0,   -1.0},
+
+   /* 81-90 */
+      { 2, 0, 2, 2, 2,  -1100.0,   0.0,    9.0,    465.0,  0.0,    4.0},
+      { 1, 0, 0, 2, 1,   -970.0,   0.0,    2.0,    496.0,  0.0,    1.0},
+      { 3, 0, 0, 0, 0,   1575.0,   0.0,   -6.0,    -50.0,  0.0,    0.0},
+      { 3, 0, 2,-2, 2,    934.0,   0.0,   -3.0,   -399.0,  0.0,   -1.0},
+      { 0, 0, 4,-2, 2,    922.0,   0.0,   -1.0,   -395.0,  0.0,   -1.0},
+      { 0, 1, 2, 0, 1,    815.0,   0.0,   -1.0,   -422.0,  0.0,   -1.0},
+      { 0, 0,-2, 2, 1,    834.0,   0.0,    2.0,   -440.0,  0.0,    1.0},
+      { 0, 0, 2,-2, 3,   1248.0,   0.0,    0.0,   -170.0,  0.0,    1.0},
+      {-1, 0, 0, 4, 0,   1338.0,   0.0,   -5.0,    -39.0,  0.0,    0.0},
+      { 2, 0,-2, 0, 1,    716.0,   0.0,   -2.0,   -389.0,  0.0,   -1.0},
+
+   /* 91-100 */
+      {-2, 0, 0, 4, 0,   1282.0,   0.0,   -3.0,    -23.0,  0.0,    1.0},
+      {-1,-1, 0, 2, 1,    742.0,   0.0,    1.0,   -391.0,  0.0,    0.0},
+      {-1, 0, 0, 1, 1,   1020.0,   0.0,  -25.0,   -495.0,  0.0,  -10.0},
+      { 0, 1, 0, 0, 2,    715.0,   0.0,   -4.0,   -326.0,  0.0,    2.0},
+      { 0, 0,-2, 0, 1,   -666.0,   0.0,   -3.0,    369.0,  0.0,   -1.0},
+      { 0,-1, 2, 0, 1,   -667.0,   0.0,    1.0,    346.0,  0.0,    1.0},
+      { 0, 0, 2,-1, 2,   -704.0,   0.0,    0.0,    304.0,  0.0,    0.0},
+      { 0, 0, 2, 4, 2,   -694.0,   0.0,    5.0,    294.0,  0.0,    2.0},
+      {-2,-1, 0, 2, 0,  -1014.0,   0.0,   -1.0,      4.0,  0.0,   -1.0},
+      { 1, 1, 0,-2, 1,   -585.0,   0.0,   -2.0,    316.0,  0.0,   -1.0},
+
+   /* 101-110 */
+      {-1, 1, 0, 2, 0,   -949.0,   0.0,    1.0,      8.0,  0.0,   -1.0},
+      {-1, 1, 0, 1, 2,   -595.0,   0.0,    0.0,    258.0,  0.0,    0.0},
+      { 1,-1, 0, 0, 1,    528.0,   0.0,    0.0,   -279.0,  0.0,    0.0},
+      { 1,-1, 2, 2, 2,   -590.0,   0.0,    4.0,    252.0,  0.0,    2.0},
+      {-1, 1, 2, 2, 2,    570.0,   0.0,   -2.0,   -244.0,  0.0,   -1.0},
+      { 3, 0, 2, 0, 1,   -502.0,   0.0,    3.0,    250.0,  0.0,    2.0},
+      { 0, 1,-2, 2, 0,   -875.0,   0.0,    1.0,     29.0,  0.0,    0.0},
+      {-1, 0, 0,-2, 1,   -492.0,   0.0,   -3.0,    275.0,  0.0,   -1.0},
+      { 0, 1, 2, 2, 2,    535.0,   0.0,   -2.0,   -228.0,  0.0,   -1.0},
+      {-1,-1, 2, 2, 1,   -467.0,   0.0,    1.0,    240.0,  0.0,    1.0},
+
+   /* 111-120 */
+      { 0,-1, 0, 0, 2,    591.0,   0.0,    0.0,   -253.0,  0.0,    0.0},
+      { 1, 0, 2,-4, 1,   -453.0,   0.0,   -1.0,    244.0,  0.0,   -1.0},
+      {-1, 0,-2, 2, 0,    766.0,   0.0,    1.0,      9.0,  0.0,    0.0},
+      { 0,-1, 2, 2, 1,   -446.0,   0.0,    2.0,    225.0,  0.0,    1.0},
+      { 2,-1, 2, 0, 2,   -488.0,   0.0,    2.0,    207.0,  0.0,    1.0},
+      { 0, 0, 0, 2, 2,   -468.0,   0.0,    0.0,    201.0,  0.0,    0.0},
+      { 1,-1, 2, 0, 1,   -421.0,   0.0,    1.0,    216.0,  0.0,    1.0},
+      {-1, 1, 2, 0, 2,    463.0,   0.0,    0.0,   -200.0,  0.0,    0.0},
+      { 0, 1, 0, 2, 0,   -673.0,   0.0,    2.0,     14.0,  0.0,    0.0},
+      { 0,-1,-2, 2, 0,    658.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+
+   /* 121-130 */
+      { 0, 3, 2,-2, 2,   -438.0,   0.0,    0.0,    188.0,  0.0,    0.0},
+      { 0, 0, 0, 1, 1,   -390.0,   0.0,    0.0,    205.0,  0.0,    0.0},
+      {-1, 0, 2, 2, 0,    639.0, -11.0,   -2.0,    -19.0,  0.0,    0.0},
+      { 2, 1, 2, 0, 2,    412.0,   0.0,   -2.0,   -176.0,  0.0,   -1.0},
+      { 1, 1, 0, 0, 1,   -361.0,   0.0,    0.0,    189.0,  0.0,    0.0},
+      { 1, 1, 2, 0, 1,    360.0,   0.0,   -1.0,   -185.0,  0.0,   -1.0},
+      { 2, 0, 0, 2, 0,    588.0,   0.0,   -3.0,    -24.0,  0.0,    0.0},
+      { 1, 0,-2, 2, 0,   -578.0,   0.0,    1.0,      5.0,  0.0,    0.0},
+      {-1, 0, 0, 2, 2,   -396.0,   0.0,    0.0,    171.0,  0.0,    0.0},
+      { 0, 1, 0, 1, 0,    565.0,   0.0,   -1.0,     -6.0,  0.0,    0.0},
+
+   /* 131-140 */
+      { 0, 1, 0,-2, 1,   -335.0,   0.0,   -1.0,    184.0,  0.0,   -1.0},
+      {-1, 0, 2,-2, 2,    357.0,   0.0,    1.0,   -154.0,  0.0,    0.0},
+      { 0, 0, 0,-1, 1,    321.0,   0.0,    1.0,   -174.0,  0.0,    0.0},
+      {-1, 1, 0, 0, 1,   -301.0,   0.0,   -1.0,    162.0,  0.0,    0.0},
+      { 1, 0, 2,-1, 2,   -334.0,   0.0,    0.0,    144.0,  0.0,    0.0},
+      { 1,-1, 0, 2, 0,    493.0,   0.0,   -2.0,    -15.0,  0.0,    0.0},
+      { 0, 0, 0, 4, 0,    494.0,   0.0,   -2.0,    -19.0,  0.0,    0.0},
+      { 1, 0, 2, 1, 2,    337.0,   0.0,   -1.0,   -143.0,  0.0,   -1.0},
+      { 0, 0, 2, 1, 1,    280.0,   0.0,   -1.0,   -144.0,  0.0,    0.0},
+      { 1, 0, 0,-2, 2,    309.0,   0.0,    1.0,   -134.0,  0.0,    0.0},
+
+   /* 141-150 */
+      {-1, 0, 2, 4, 1,   -263.0,   0.0,    2.0,    131.0,  0.0,    1.0},
+      { 1, 0,-2, 0, 1,    253.0,   0.0,    1.0,   -138.0,  0.0,    0.0},
+      { 1, 1, 2,-2, 1,    245.0,   0.0,    0.0,   -128.0,  0.0,    0.0},
+      { 0, 0, 2, 2, 0,    416.0,   0.0,   -2.0,    -17.0,  0.0,    0.0},
+      {-1, 0, 2,-1, 1,   -229.0,   0.0,    0.0,    128.0,  0.0,    0.0},
+      {-2, 0, 2, 2, 1,    231.0,   0.0,    0.0,   -120.0,  0.0,    0.0},
+      { 4, 0, 2, 0, 2,   -259.0,   0.0,    2.0,    109.0,  0.0,    1.0},
+      { 2,-1, 0, 0, 0,    375.0,   0.0,   -1.0,     -8.0,  0.0,    0.0},
+      { 2, 1, 2,-2, 2,    252.0,   0.0,    0.0,   -108.0,  0.0,    0.0},
+      { 0, 1, 2, 1, 2,   -245.0,   0.0,    1.0,    104.0,  0.0,    0.0},
+
+   /* 151-160 */
+      { 1, 0, 4,-2, 2,    243.0,   0.0,   -1.0,   -104.0,  0.0,    0.0},
+      {-1,-1, 0, 0, 1,    208.0,   0.0,    1.0,   -112.0,  0.0,    0.0},
+      { 0, 1, 0, 2, 1,    199.0,   0.0,    0.0,   -102.0,  0.0,    0.0},
+      {-2, 0, 2, 4, 1,   -208.0,   0.0,    1.0,    105.0,  0.0,    0.0},
+      { 2, 0, 2, 0, 0,    335.0,   0.0,   -2.0,    -14.0,  0.0,    0.0},
+      { 1, 0, 0, 1, 0,   -325.0,   0.0,    1.0,      7.0,  0.0,    0.0},
+      {-1, 0, 0, 4, 1,   -187.0,   0.0,    0.0,     96.0,  0.0,    0.0},
+      {-1, 0, 4, 0, 1,    197.0,   0.0,   -1.0,   -100.0,  0.0,    0.0},
+      { 2, 0, 2, 2, 1,   -192.0,   0.0,    2.0,     94.0,  0.0,    1.0},
+      { 0, 0, 2,-3, 2,   -188.0,   0.0,    0.0,     83.0,  0.0,    0.0},
+
+   /* 161-170 */
+      {-1,-2, 0, 2, 0,    276.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 2, 1, 0, 0, 0,   -286.0,   0.0,    1.0,      6.0,  0.0,    0.0},
+      { 0, 0, 4, 0, 2,    186.0,   0.0,   -1.0,    -79.0,  0.0,    0.0},
+      { 0, 0, 0, 0, 3,   -219.0,   0.0,    0.0,     43.0,  0.0,    0.0},
+      { 0, 3, 0, 0, 0,    276.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 0, 0, 2,-4, 1,   -153.0,   0.0,   -1.0,     84.0,  0.0,    0.0},
+      { 0,-1, 0, 2, 1,   -156.0,   0.0,    0.0,     81.0,  0.0,    0.0},
+      { 0, 0, 0, 4, 1,   -154.0,   0.0,    1.0,     78.0,  0.0,    0.0},
+      {-1,-1, 2, 4, 2,   -174.0,   0.0,    1.0,     75.0,  0.0,    0.0},
+      { 1, 0, 2, 4, 2,   -163.0,   0.0,    2.0,     69.0,  0.0,    1.0},
+
+   /* 171-180 */
+      {-2, 2, 0, 2, 0,   -228.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      {-2,-1, 2, 0, 1,     91.0,   0.0,   -4.0,    -54.0,  0.0,   -2.0},
+      {-2, 0, 0, 2, 2,    175.0,   0.0,    0.0,    -75.0,  0.0,    0.0},
+      {-1,-1, 2, 0, 2,   -159.0,   0.0,    0.0,     69.0,  0.0,    0.0},
+      { 0, 0, 4,-2, 1,    141.0,   0.0,    0.0,    -72.0,  0.0,    0.0},
+      { 3, 0, 2,-2, 1,    147.0,   0.0,    0.0,    -75.0,  0.0,    0.0},
+      {-2,-1, 0, 2, 1,   -132.0,   0.0,    0.0,     69.0,  0.0,    0.0},
+      { 1, 0, 0,-1, 1,    159.0,   0.0,  -28.0,    -54.0,  0.0,   11.0},
+      { 0,-2, 0, 2, 0,    213.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      {-2, 0, 0, 4, 1,    123.0,   0.0,    0.0,    -64.0,  0.0,    0.0},
+
+   /* 181-190 */
+      {-3, 0, 0, 0, 1,   -118.0,   0.0,   -1.0,     66.0,  0.0,    0.0},
+      { 1, 1, 2, 2, 2,    144.0,   0.0,   -1.0,    -61.0,  0.0,    0.0},
+      { 0, 0, 2, 4, 1,   -121.0,   0.0,    1.0,     60.0,  0.0,    0.0},
+      { 3, 0, 2, 2, 2,   -134.0,   0.0,    1.0,     56.0,  0.0,    1.0},
+      {-1, 1, 2,-2, 1,   -105.0,   0.0,    0.0,     57.0,  0.0,    0.0},
+      { 2, 0, 0,-4, 1,   -102.0,   0.0,    0.0,     56.0,  0.0,    0.0},
+      { 0, 0, 0,-2, 2,    120.0,   0.0,    0.0,    -52.0,  0.0,    0.0},
+      { 2, 0, 2,-4, 1,    101.0,   0.0,    0.0,    -54.0,  0.0,    0.0},
+      {-1, 1, 0, 2, 1,   -113.0,   0.0,    0.0,     59.0,  0.0,    0.0},
+      { 0, 0, 2,-1, 1,   -106.0,   0.0,    0.0,     61.0,  0.0,    0.0},
+
+   /* 191-200 */
+      { 0,-2, 2, 2, 2,   -129.0,   0.0,    1.0,     55.0,  0.0,    0.0},
+      { 2, 0, 0, 2, 1,   -114.0,   0.0,    0.0,     57.0,  0.0,    0.0},
+      { 4, 0, 2,-2, 2,    113.0,   0.0,   -1.0,    -49.0,  0.0,    0.0},
+      { 2, 0, 0,-2, 2,   -102.0,   0.0,    0.0,     44.0,  0.0,    0.0},
+      { 0, 2, 0, 0, 1,    -94.0,   0.0,    0.0,     51.0,  0.0,    0.0},
+      { 1, 0, 0,-4, 1,   -100.0,   0.0,   -1.0,     56.0,  0.0,    0.0},
+      { 0, 2, 2,-2, 1,     87.0,   0.0,    0.0,    -47.0,  0.0,    0.0},
+      {-3, 0, 0, 4, 0,    161.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      {-1, 1, 2, 0, 1,     96.0,   0.0,    0.0,    -50.0,  0.0,    0.0},
+      {-1,-1, 0, 4, 0,    151.0,   0.0,   -1.0,     -5.0,  0.0,    0.0},
+
+   /* 201-210 */
+      {-1,-2, 2, 2, 2,   -104.0,   0.0,    0.0,     44.0,  0.0,    0.0},
+      {-2,-1, 2, 4, 2,   -110.0,   0.0,    0.0,     48.0,  0.0,    0.0},
+      { 1,-1, 2, 2, 1,   -100.0,   0.0,    1.0,     50.0,  0.0,    0.0},
+      {-2, 1, 0, 2, 0,     92.0,   0.0,   -5.0,     12.0,  0.0,   -2.0},
+      {-2, 1, 2, 0, 1,     82.0,   0.0,    0.0,    -45.0,  0.0,    0.0},
+      { 2, 1, 0,-2, 1,     82.0,   0.0,    0.0,    -45.0,  0.0,    0.0},
+      {-3, 0, 2, 0, 1,    -78.0,   0.0,    0.0,     41.0,  0.0,    0.0},
+      {-2, 0, 2,-2, 1,    -77.0,   0.0,    0.0,     43.0,  0.0,    0.0},
+      {-1, 1, 0, 2, 2,      2.0,   0.0,    0.0,     54.0,  0.0,    0.0},
+      { 0,-1, 2,-1, 2,     94.0,   0.0,    0.0,    -40.0,  0.0,    0.0},
+
+   /* 211-220 */
+      {-1, 0, 4,-2, 2,    -93.0,   0.0,    0.0,     40.0,  0.0,    0.0},
+      { 0,-2, 2, 0, 2,    -83.0,   0.0,   10.0,     40.0,  0.0,   -2.0},
+      {-1, 0, 2, 1, 2,     83.0,   0.0,    0.0,    -36.0,  0.0,    0.0},
+      { 2, 0, 0, 0, 2,    -91.0,   0.0,    0.0,     39.0,  0.0,    0.0},
+      { 0, 0, 2, 0, 3,    128.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      {-2, 0, 4, 0, 2,    -79.0,   0.0,    0.0,     34.0,  0.0,    0.0},
+      {-1, 0,-2, 0, 1,    -83.0,   0.0,    0.0,     47.0,  0.0,    0.0},
+      {-1, 1, 2, 2, 1,     84.0,   0.0,    0.0,    -44.0,  0.0,    0.0},
+      { 3, 0, 0, 0, 1,     83.0,   0.0,    0.0,    -43.0,  0.0,    0.0},
+      {-1, 0, 2, 3, 2,     91.0,   0.0,    0.0,    -39.0,  0.0,    0.0},
+
+   /* 221-230 */
+      { 2,-1, 2, 0, 1,    -77.0,   0.0,    0.0,     39.0,  0.0,    0.0},
+      { 0, 1, 2, 2, 1,     84.0,   0.0,    0.0,    -43.0,  0.0,    0.0},
+      { 0,-1, 2, 4, 2,    -92.0,   0.0,    1.0,     39.0,  0.0,    0.0},
+      { 2,-1, 2, 2, 2,    -92.0,   0.0,    1.0,     39.0,  0.0,    0.0},
+      { 0, 2,-2, 2, 0,    -94.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1,-1, 2,-1, 1,     68.0,   0.0,    0.0,    -36.0,  0.0,    0.0},
+      { 0,-2, 0, 0, 1,    -61.0,   0.0,    0.0,     32.0,  0.0,    0.0},
+      { 1, 0, 2,-4, 2,     71.0,   0.0,    0.0,    -31.0,  0.0,    0.0},
+      { 1,-1, 0,-2, 1,     62.0,   0.0,    0.0,    -34.0,  0.0,    0.0},
+      {-1,-1, 2, 0, 1,    -63.0,   0.0,    0.0,     33.0,  0.0,    0.0},
+
+   /* 231-240 */
+      { 1,-1, 2,-2, 2,    -73.0,   0.0,    0.0,     32.0,  0.0,    0.0},
+      {-2,-1, 0, 4, 0,    115.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-1, 0, 0, 3, 0,   -103.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-2,-1, 2, 2, 2,     63.0,   0.0,    0.0,    -28.0,  0.0,    0.0},
+      { 0, 2, 2, 0, 2,     74.0,   0.0,    0.0,    -32.0,  0.0,    0.0},
+      { 1, 1, 0, 2, 0,   -103.0,   0.0,   -3.0,      3.0,  0.0,   -1.0},
+      { 2, 0, 2,-1, 2,    -69.0,   0.0,    0.0,     30.0,  0.0,    0.0},
+      { 1, 0, 2, 1, 1,     57.0,   0.0,    0.0,    -29.0,  0.0,    0.0},
+      { 4, 0, 0, 0, 0,     94.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      { 2, 1, 2, 0, 1,     64.0,   0.0,    0.0,    -33.0,  0.0,    0.0},
+
+   /* 241-250 */
+      { 3,-1, 2, 0, 2,    -63.0,   0.0,    0.0,     26.0,  0.0,    0.0},
+      {-2, 2, 0, 2, 1,    -38.0,   0.0,    0.0,     20.0,  0.0,    0.0},
+      { 1, 0, 2,-3, 1,    -43.0,   0.0,    0.0,     24.0,  0.0,    0.0},
+      { 1, 1, 2,-4, 1,    -45.0,   0.0,    0.0,     23.0,  0.0,    0.0},
+      {-1,-1, 2,-2, 1,     47.0,   0.0,    0.0,    -24.0,  0.0,    0.0},
+      { 0,-1, 0,-1, 1,    -48.0,   0.0,    0.0,     25.0,  0.0,    0.0},
+      { 0,-1, 0,-2, 1,     45.0,   0.0,    0.0,    -26.0,  0.0,    0.0},
+      {-2, 0, 0, 0, 2,     56.0,   0.0,    0.0,    -25.0,  0.0,    0.0},
+      {-2, 0,-2, 2, 0,     88.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-1, 0,-2, 4, 0,    -75.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 251-260 */
+      { 1,-2, 0, 0, 0,     85.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 1, 0, 1, 1,     49.0,   0.0,    0.0,    -26.0,  0.0,    0.0},
+      {-1, 2, 0, 2, 0,    -74.0,   0.0,   -3.0,     -1.0,  0.0,   -1.0},
+      { 1,-1, 2,-2, 1,    -39.0,   0.0,    0.0,     21.0,  0.0,    0.0},
+      { 1, 2, 2,-2, 2,     45.0,   0.0,    0.0,    -20.0,  0.0,    0.0},
+      { 2,-1, 2,-2, 2,     51.0,   0.0,    0.0,    -22.0,  0.0,    0.0},
+      { 1, 0, 2,-1, 1,    -40.0,   0.0,    0.0,     21.0,  0.0,    0.0},
+      { 2, 1, 2,-2, 1,     41.0,   0.0,    0.0,    -21.0,  0.0,    0.0},
+      {-2, 0, 0,-2, 1,    -42.0,   0.0,    0.0,     24.0,  0.0,    0.0},
+      { 1,-2, 2, 0, 2,    -51.0,   0.0,    0.0,     22.0,  0.0,    0.0},
+
+   /* 261-270 */
+      { 0, 1, 2, 1, 1,    -42.0,   0.0,    0.0,     22.0,  0.0,    0.0},
+      { 1, 0, 4,-2, 1,     39.0,   0.0,    0.0,    -21.0,  0.0,    0.0},
+      {-2, 0, 4, 2, 2,     46.0,   0.0,    0.0,    -18.0,  0.0,    0.0},
+      { 1, 1, 2, 1, 2,    -53.0,   0.0,    0.0,     22.0,  0.0,    0.0},
+      { 1, 0, 0, 4, 0,     82.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      { 1, 0, 2, 2, 0,     81.0,   0.0,   -1.0,     -4.0,  0.0,    0.0},
+      { 2, 0, 2, 1, 2,     47.0,   0.0,    0.0,    -19.0,  0.0,    0.0},
+      { 3, 1, 2, 0, 2,     53.0,   0.0,    0.0,    -23.0,  0.0,    0.0},
+      { 4, 0, 2, 0, 1,    -45.0,   0.0,    0.0,     22.0,  0.0,    0.0},
+      {-2,-1, 2, 0, 0,    -44.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+
+   /* 271-280 */
+      { 0, 1,-2, 2, 1,    -33.0,   0.0,    0.0,     16.0,  0.0,    0.0},
+      { 1, 0,-2, 1, 0,    -61.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      { 0,-1,-2, 2, 1,     28.0,   0.0,    0.0,    -15.0,  0.0,    0.0},
+      { 2,-1, 0,-2, 1,    -38.0,   0.0,    0.0,     19.0,  0.0,    0.0},
+      {-1, 0, 2,-1, 2,    -33.0,   0.0,    0.0,     21.0,  0.0,    0.0},
+      { 1, 0, 2,-3, 2,    -60.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 1, 2,-2, 3,     48.0,   0.0,    0.0,    -10.0,  0.0,    0.0},
+      { 0, 0, 2,-3, 1,     27.0,   0.0,    0.0,    -14.0,  0.0,    0.0},
+      {-1, 0,-2, 2, 1,     38.0,   0.0,    0.0,    -20.0,  0.0,    0.0},
+      { 0, 0, 2,-4, 2,     31.0,   0.0,    0.0,    -13.0,  0.0,    0.0},
+
+   /* 281-290 */
+      {-2, 1, 0, 0, 1,    -29.0,   0.0,    0.0,     15.0,  0.0,    0.0},
+      {-1, 0, 0,-1, 1,     28.0,   0.0,    0.0,    -15.0,  0.0,    0.0},
+      { 2, 0, 2,-4, 2,    -32.0,   0.0,    0.0,     15.0,  0.0,    0.0},
+      { 0, 0, 4,-4, 4,     45.0,   0.0,    0.0,     -8.0,  0.0,    0.0},
+      { 0, 0, 4,-4, 2,    -44.0,   0.0,    0.0,     19.0,  0.0,    0.0},
+      {-1,-2, 0, 2, 1,     28.0,   0.0,    0.0,    -15.0,  0.0,    0.0},
+      {-2, 0, 0, 3, 0,    -51.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 0,-2, 2, 1,    -36.0,   0.0,    0.0,     20.0,  0.0,    0.0},
+      {-3, 0, 2, 2, 2,     44.0,   0.0,    0.0,    -19.0,  0.0,    0.0},
+      {-3, 0, 2, 2, 1,     26.0,   0.0,    0.0,    -14.0,  0.0,    0.0},
+
+   /* 291-300 */
+      {-2, 0, 2, 2, 0,    -60.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 2,-1, 0, 0, 1,     35.0,   0.0,    0.0,    -18.0,  0.0,    0.0},
+      {-2, 1, 2, 2, 2,    -27.0,   0.0,    0.0,     11.0,  0.0,    0.0},
+      { 1, 1, 0, 1, 0,     47.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 0, 1, 4,-2, 2,     36.0,   0.0,    0.0,    -15.0,  0.0,    0.0},
+      {-1, 1, 0,-2, 1,    -36.0,   0.0,    0.0,     20.0,  0.0,    0.0},
+      { 0, 0, 0,-4, 1,    -35.0,   0.0,    0.0,     19.0,  0.0,    0.0},
+      { 1,-1, 0, 2, 1,    -37.0,   0.0,    0.0,     19.0,  0.0,    0.0},
+      { 1, 1, 0, 2, 1,     32.0,   0.0,    0.0,    -16.0,  0.0,    0.0},
+      {-1, 2, 2, 2, 2,     35.0,   0.0,    0.0,    -14.0,  0.0,    0.0},
+
+   /* 301-310 */
+      { 3, 1, 2,-2, 2,     32.0,   0.0,    0.0,    -13.0,  0.0,    0.0},
+      { 0,-1, 0, 4, 0,     65.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 2,-1, 0, 2, 0,     47.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 0, 0, 4, 0, 1,     32.0,   0.0,    0.0,    -16.0,  0.0,    0.0},
+      { 2, 0, 4,-2, 2,     37.0,   0.0,    0.0,    -16.0,  0.0,    0.0},
+      {-1,-1, 2, 4, 1,    -30.0,   0.0,    0.0,     15.0,  0.0,    0.0},
+      { 1, 0, 0, 4, 1,    -32.0,   0.0,    0.0,     16.0,  0.0,    0.0},
+      { 1,-2, 2, 2, 2,    -31.0,   0.0,    0.0,     13.0,  0.0,    0.0},
+      { 0, 0, 2, 3, 2,     37.0,   0.0,    0.0,    -16.0,  0.0,    0.0},
+      {-1, 1, 2, 4, 2,     31.0,   0.0,    0.0,    -13.0,  0.0,    0.0},
+
+   /* 311-320 */
+      { 3, 0, 0, 2, 0,     49.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-1, 0, 4, 2, 2,     32.0,   0.0,    0.0,    -13.0,  0.0,    0.0},
+      { 1, 1, 2, 2, 1,     23.0,   0.0,    0.0,    -12.0,  0.0,    0.0},
+      {-2, 0, 2, 6, 2,    -43.0,   0.0,    0.0,     18.0,  0.0,    0.0},
+      { 2, 1, 2, 2, 2,     26.0,   0.0,    0.0,    -11.0,  0.0,    0.0},
+      {-1, 0, 2, 6, 2,    -32.0,   0.0,    0.0,     14.0,  0.0,    0.0},
+      { 1, 0, 2, 4, 1,    -29.0,   0.0,    0.0,     14.0,  0.0,    0.0},
+      { 2, 0, 2, 4, 2,    -27.0,   0.0,    0.0,     12.0,  0.0,    0.0},
+      { 1, 1,-2, 1, 0,     30.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-3, 1, 2, 1, 2,    -11.0,   0.0,    0.0,      5.0,  0.0,    0.0},
+
+   /* 321-330 */
+      { 2, 0,-2, 0, 2,    -21.0,   0.0,    0.0,     10.0,  0.0,    0.0},
+      {-1, 0, 0, 1, 2,    -34.0,   0.0,    0.0,     15.0,  0.0,    0.0},
+      {-4, 0, 2, 2, 1,    -10.0,   0.0,    0.0,      6.0,  0.0,    0.0},
+      {-1,-1, 0, 1, 0,    -36.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 0,-2, 2, 2,     -9.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      { 1, 0, 0,-1, 2,    -12.0,   0.0,    0.0,      5.0,  0.0,    0.0},
+      { 0,-1, 2,-2, 3,    -21.0,   0.0,    0.0,      5.0,  0.0,    0.0},
+      {-2, 1, 2, 0, 0,    -29.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 0, 0, 2,-2, 4,    -15.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      {-2,-2, 0, 2, 0,    -20.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 331-340 */
+      {-2, 0,-2, 4, 0,     28.0,   0.0,    0.0,      0.0,  0.0,   -2.0},
+      { 0,-2,-2, 2, 0,     17.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 2, 0,-2, 1,    -22.0,   0.0,    0.0,     12.0,  0.0,    0.0},
+      { 3, 0, 0,-4, 1,    -14.0,   0.0,    0.0,      7.0,  0.0,    0.0},
+      {-1, 1, 2,-2, 2,     24.0,   0.0,    0.0,    -11.0,  0.0,    0.0},
+      { 1,-1, 2,-4, 1,     11.0,   0.0,    0.0,     -6.0,  0.0,    0.0},
+      { 1, 1, 0,-2, 2,     14.0,   0.0,    0.0,     -6.0,  0.0,    0.0},
+      {-3, 0, 2, 0, 0,     24.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-3, 0, 2, 0, 2,     18.0,   0.0,    0.0,     -8.0,  0.0,    0.0},
+      {-2, 0, 0, 1, 0,    -38.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 341-350 */
+      { 0, 0,-2, 1, 0,    -31.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-3, 0, 0, 2, 1,    -16.0,   0.0,    0.0,      8.0,  0.0,    0.0},
+      {-1,-1,-2, 2, 0,     29.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 1, 2,-4, 1,    -18.0,   0.0,    0.0,     10.0,  0.0,    0.0},
+      { 2, 1, 0,-4, 1,    -10.0,   0.0,    0.0,      5.0,  0.0,    0.0},
+      { 0, 2, 0,-2, 1,    -17.0,   0.0,    0.0,     10.0,  0.0,    0.0},
+      { 1, 0, 0,-3, 1,      9.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      {-2, 0, 2,-2, 2,     16.0,   0.0,    0.0,     -6.0,  0.0,    0.0},
+      {-2,-1, 0, 0, 1,     22.0,   0.0,    0.0,    -12.0,  0.0,    0.0},
+      {-4, 0, 0, 2, 0,     20.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 351-360 */
+      { 1, 1, 0,-4, 1,    -13.0,   0.0,    0.0,      6.0,  0.0,    0.0},
+      {-1, 0, 2,-4, 1,    -17.0,   0.0,    0.0,      9.0,  0.0,    0.0},
+      { 0, 0, 4,-4, 1,    -14.0,   0.0,    0.0,      8.0,  0.0,    0.0},
+      { 0, 3, 2,-2, 2,      0.0,   0.0,    0.0,     -7.0,  0.0,    0.0},
+      {-3,-1, 0, 4, 0,     14.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-3, 0, 0, 4, 1,     19.0,   0.0,    0.0,    -10.0,  0.0,    0.0},
+      { 1,-1,-2, 2, 0,    -34.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1,-1, 0, 2, 2,    -20.0,   0.0,    0.0,      8.0,  0.0,    0.0},
+      { 1,-2, 0, 0, 1,      9.0,   0.0,    0.0,     -5.0,  0.0,    0.0},
+      { 1,-1, 0, 0, 2,    -18.0,   0.0,    0.0,      7.0,  0.0,    0.0},
+
+   /* 361-370 */
+      { 0, 0, 0, 1, 2,     13.0,   0.0,    0.0,     -6.0,  0.0,    0.0},
+      {-1,-1, 2, 0, 0,     17.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1,-2, 2,-2, 2,    -12.0,   0.0,    0.0,      5.0,  0.0,    0.0},
+      { 0,-1, 2,-1, 1,     15.0,   0.0,    0.0,     -8.0,  0.0,    0.0},
+      {-1, 0, 2, 0, 3,    -11.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      { 1, 1, 0, 0, 2,     13.0,   0.0,    0.0,     -5.0,  0.0,    0.0},
+      {-1, 1, 2, 0, 0,    -18.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 2, 0, 0, 0,    -35.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1, 2, 2, 0, 2,      9.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      {-1, 0, 4,-2, 1,    -19.0,   0.0,    0.0,     10.0,  0.0,    0.0},
+
+   /* 371-380 */
+      { 3, 0, 2,-4, 2,    -26.0,   0.0,    0.0,     11.0,  0.0,    0.0},
+      { 1, 2, 2,-2, 1,      8.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      { 1, 0, 4,-4, 2,    -10.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      {-2,-1, 0, 4, 1,     10.0,   0.0,    0.0,     -6.0,  0.0,    0.0},
+      { 0,-1, 0, 2, 2,    -21.0,   0.0,    0.0,      9.0,  0.0,    0.0},
+      {-2, 1, 0, 4, 0,    -15.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-2,-1, 2, 2, 1,      9.0,   0.0,    0.0,     -5.0,  0.0,    0.0},
+      { 2, 0,-2, 2, 0,    -29.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 0, 0, 1, 1,    -19.0,   0.0,    0.0,     10.0,  0.0,    0.0},
+      { 0, 1, 0, 2, 2,     12.0,   0.0,    0.0,     -5.0,  0.0,    0.0},
+
+   /* 381-390 */
+      { 1,-1, 2,-1, 2,     22.0,   0.0,    0.0,     -9.0,  0.0,    0.0},
+      {-2, 0, 4, 0, 1,    -10.0,   0.0,    0.0,      5.0,  0.0,    0.0},
+      { 2, 1, 0, 0, 1,    -20.0,   0.0,    0.0,     11.0,  0.0,    0.0},
+      { 0, 1, 2, 0, 0,    -20.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0,-1, 4,-2, 2,    -17.0,   0.0,    0.0,      7.0,  0.0,    0.0},
+      { 0, 0, 4,-2, 4,     15.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 0, 2, 2, 0, 1,      8.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      {-3, 0, 0, 6, 0,     14.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1,-1, 0, 4, 1,    -12.0,   0.0,    0.0,      6.0,  0.0,    0.0},
+      { 1,-2, 0, 2, 0,     25.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 391-400 */
+      {-1, 0, 0, 4, 2,    -13.0,   0.0,    0.0,      6.0,  0.0,    0.0},
+      {-1,-2, 2, 2, 1,    -14.0,   0.0,    0.0,      8.0,  0.0,    0.0},
+      {-1, 0, 0,-2, 2,     13.0,   0.0,    0.0,     -5.0,  0.0,    0.0},
+      { 1, 0,-2,-2, 1,    -17.0,   0.0,    0.0,      9.0,  0.0,    0.0},
+      { 0, 0,-2,-2, 1,    -12.0,   0.0,    0.0,      6.0,  0.0,    0.0},
+      {-2, 0,-2, 0, 1,    -10.0,   0.0,    0.0,      5.0,  0.0,    0.0},
+      { 0, 0, 0, 3, 1,     10.0,   0.0,    0.0,     -6.0,  0.0,    0.0},
+      { 0, 0, 0, 3, 0,    -15.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1, 1, 0, 4, 0,    -22.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1,-1, 2, 2, 0,     28.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+
+   /* 401-410 */
+      {-2, 0, 2, 3, 2,     15.0,   0.0,    0.0,     -7.0,  0.0,    0.0},
+      { 1, 0, 0, 2, 2,     23.0,   0.0,    0.0,    -10.0,  0.0,    0.0},
+      { 0,-1, 2, 1, 2,     12.0,   0.0,    0.0,     -5.0,  0.0,    0.0},
+      { 3,-1, 0, 0, 0,     29.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 2, 0, 0, 1, 0,    -25.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      { 1,-1, 2, 0, 0,     22.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 0, 2, 1, 0,    -18.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 0, 2, 0, 3,     15.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      { 3, 1, 0, 0, 0,    -23.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 3,-1, 2,-2, 2,     12.0,   0.0,    0.0,     -5.0,  0.0,    0.0},
+
+   /* 411-420 */
+      { 2, 0, 2,-1, 1,     -8.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      { 1, 1, 2, 0, 0,    -19.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 0, 4,-1, 2,    -10.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      { 1, 2, 2, 0, 2,     21.0,   0.0,    0.0,     -9.0,  0.0,    0.0},
+      {-2, 0, 0, 6, 0,     23.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 0,-1, 0, 4, 1,    -16.0,   0.0,    0.0,      8.0,  0.0,    0.0},
+      {-2,-1, 2, 4, 1,    -19.0,   0.0,    0.0,      9.0,  0.0,    0.0},
+      { 0,-2, 2, 2, 1,    -22.0,   0.0,    0.0,     10.0,  0.0,    0.0},
+      { 0,-1, 2, 2, 0,     27.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      {-1, 0, 2, 3, 1,     16.0,   0.0,    0.0,     -8.0,  0.0,    0.0},
+
+   /* 421-430 */
+      {-2, 1, 2, 4, 2,     19.0,   0.0,    0.0,     -8.0,  0.0,    0.0},
+      { 2, 0, 0, 2, 2,      9.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      { 2,-2, 2, 0, 2,     -9.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      {-1, 1, 2, 3, 2,     -9.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      { 3, 0, 2,-1, 2,     -8.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      { 4, 0, 2,-2, 1,     18.0,   0.0,    0.0,     -9.0,  0.0,    0.0},
+      {-1, 0, 0, 6, 0,     16.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      {-1,-2, 2, 4, 2,    -10.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      {-3, 0, 2, 6, 2,    -23.0,   0.0,    0.0,      9.0,  0.0,    0.0},
+      {-1, 0, 2, 4, 0,     16.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+
+   /* 431-440 */
+      { 3, 0, 0, 2, 1,    -12.0,   0.0,    0.0,      6.0,  0.0,    0.0},
+      { 3,-1, 2, 0, 1,     -8.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      { 3, 0, 2, 0, 0,     30.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 1, 0, 4, 0, 2,     24.0,   0.0,    0.0,    -10.0,  0.0,    0.0},
+      { 5, 0, 2,-2, 2,     10.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      { 0,-1, 2, 4, 1,    -16.0,   0.0,    0.0,      7.0,  0.0,    0.0},
+      { 2,-1, 2, 2, 1,    -16.0,   0.0,    0.0,      7.0,  0.0,    0.0},
+      { 0, 1, 2, 4, 2,     17.0,   0.0,    0.0,     -7.0,  0.0,    0.0},
+      { 1,-1, 2, 4, 2,    -24.0,   0.0,    0.0,     10.0,  0.0,    0.0},
+      { 3,-1, 2, 2, 2,    -12.0,   0.0,    0.0,      5.0,  0.0,    0.0},
+
+   /* 441-450 */
+      { 3, 0, 2, 2, 1,    -24.0,   0.0,    0.0,     11.0,  0.0,    0.0},
+      { 5, 0, 2, 0, 2,    -23.0,   0.0,    0.0,      9.0,  0.0,    0.0},
+      { 0, 0, 2, 6, 2,    -13.0,   0.0,    0.0,      5.0,  0.0,    0.0},
+      { 4, 0, 2, 2, 2,    -15.0,   0.0,    0.0,      7.0,  0.0,    0.0},
+      { 0,-1, 1,-1, 1,      0.0,   0.0,-1988.0,      0.0,  0.0,-1679.0},
+      {-1, 0, 1, 0, 3,      0.0,   0.0,  -63.0,      0.0,  0.0,  -27.0},
+      { 0,-2, 2,-2, 3,     -4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 0,-1, 0, 1,      0.0,   0.0,    5.0,      0.0,  0.0,    4.0},
+      { 2,-2, 0,-2, 1,      5.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      {-1, 0, 1, 0, 2,      0.0,   0.0,  364.0,      0.0,  0.0,  176.0},
+
+   /* 451-460 */
+      {-1, 0, 1, 0, 1,      0.0,   0.0,-1044.0,      0.0,  0.0, -891.0},
+      {-1,-1, 2,-1, 2,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      {-2, 2, 0, 2, 2,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-1, 0, 1, 0, 0,      0.0,   0.0,  330.0,      0.0,  0.0,    0.0},
+      {-4, 1, 2, 2, 2,      5.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-3, 0, 2, 1, 1,      3.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-2,-1, 2, 0, 2,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      { 1, 0,-2, 1, 1,     -5.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 2,-1,-2, 0, 1,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      {-4, 0, 2, 2, 0,      3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 461-470 */
+      {-3, 1, 0, 3, 0,      3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1, 0,-1, 2, 0,      0.0,   0.0,    5.0,      0.0,  0.0,    0.0},
+      { 0,-2, 0, 0, 2,      0.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      { 0,-2, 0, 0, 2,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-3, 0, 0, 3, 0,      6.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-2,-1, 0, 2, 2,      5.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-1, 0,-2, 3, 0,     -7.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-4, 0, 0, 4, 0,    -12.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 2, 1,-2, 0, 1,      5.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 2,-1, 0,-2, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+
+   /* 471-480 */
+      { 0, 0, 1,-1, 0,     -5.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1, 2, 0, 1, 0,      3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-2, 1, 2, 0, 2,     -7.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      { 1, 1, 0,-1, 1,      7.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      { 1, 0, 1,-2, 1,      0.0,   0.0,  -12.0,      0.0,  0.0,  -10.0},
+      { 0, 2, 0, 0, 2,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 1,-1, 2,-3, 1,      3.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-1, 1, 2,-1, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-2, 0, 4,-2, 2,     -7.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      {-2, 0, 4,-2, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+
+   /* 481-490 */
+      {-2,-2, 0, 2, 1,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      {-2, 0,-2, 4, 0,      0.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 2, 2,-4, 1,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      { 1, 1, 2,-4, 2,      7.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      {-1, 2, 2,-2, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 2, 0, 0,-3, 1,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-1, 2, 0, 0, 1,     -5.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      { 0, 0, 0,-2, 0,      5.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1,-1, 2,-2, 2,     -5.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-1, 1, 0, 0, 2,      5.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+
+   /* 491-500 */
+      { 0, 0, 0,-1, 2,     -8.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      {-2, 1, 0, 1, 0,      9.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1,-2, 0,-2, 1,      6.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 1, 0,-2, 0, 2,     -5.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-3, 1, 0, 2, 0,      3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1, 1,-2, 2, 0,     -7.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1,-1, 0, 0, 2,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      {-3, 0, 0, 2, 0,      5.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-3,-1, 0, 2, 0,      3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 2, 0, 2,-6, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+
+   /* 501-510 */
+      { 0, 1, 2,-4, 2,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 2, 0, 0,-4, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      {-2, 1, 2,-2, 1,     -5.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 0,-1, 2,-4, 1,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 0, 1, 0,-2, 2,      9.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      {-1, 0, 0,-2, 0,      4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 2, 0,-2,-2, 1,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-4, 0, 2, 0, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-1,-1, 0,-1, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 0, 0,-2, 0, 2,      9.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+
+   /* 511-520 */
+      {-3, 0, 0, 1, 0,     -4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1, 0,-2, 1, 0,     -4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-2, 0,-2, 2, 1,      3.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 0, 0,-4, 2, 0,      8.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-2,-1,-2, 2, 0,      3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 0, 2,-6, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-1, 0, 2,-4, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 1, 0, 0,-4, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 2, 1, 2,-4, 2,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      { 2, 1, 2,-4, 1,      6.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+
+   /* 521-530 */
+      { 0, 1, 4,-4, 4,      3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 1, 4,-4, 2,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      {-1,-1,-2, 4, 0,     -7.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1,-3, 0, 2, 0,      9.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1, 0,-2, 4, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-2,-1, 0, 3, 0,     -3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 0,-2, 3, 0,     -4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-2, 0, 0, 3, 1,     -5.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      { 0,-1, 0, 1, 0,    -13.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-3, 0, 2, 2, 0,     -7.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 531-540 */
+      { 1, 1,-2, 2, 0,     10.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1, 1, 0, 2, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 1,-2, 2,-2, 1,     10.0,   0.0,   13.0,      6.0,  0.0,   -5.0},
+      { 0, 0, 1, 0, 2,      0.0,   0.0,   30.0,      0.0,  0.0,   14.0},
+      { 0, 0, 1, 0, 1,      0.0,   0.0, -162.0,      0.0,  0.0, -138.0},
+      { 0, 0, 1, 0, 0,      0.0,   0.0,   75.0,      0.0,  0.0,    0.0},
+      {-1, 2, 0, 2, 1,     -7.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      { 0, 0, 2, 0, 2,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-2, 0, 2, 0, 2,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 2, 0, 0,-1, 1,      5.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+
+   /* 541-550 */
+      { 3, 0, 0,-2, 1,      5.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 1, 0, 2,-2, 3,     -3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 2, 0, 0, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 2, 0, 2,-3, 2,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-1, 1, 4,-2, 2,     -5.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-2,-2, 0, 4, 0,      6.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0,-3, 0, 2, 0,      9.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 0,-2, 4, 0,      5.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1,-1, 0, 3, 0,     -7.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-2, 0, 0, 4, 2,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+
+   /* 551-560 */
+      {-1, 0, 0, 3, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 2,-2, 0, 0, 0,      7.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1,-1, 0, 1, 0,     -4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1, 0, 0, 2, 0,      4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0,-2, 2, 0, 1,     -6.0,   0.0,   -3.0,      3.0,  0.0,    1.0},
+      {-1, 0, 1, 2, 1,      0.0,   0.0,   -3.0,      0.0,  0.0,   -2.0},
+      {-1, 1, 0, 3, 0,     11.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1,-1, 2, 1, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 0,-1, 2, 0, 0,     11.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-2, 1, 2, 2, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+
+   /* 561-570 */
+      { 2,-2, 2,-2, 2,     -1.0,   0.0,    3.0,      3.0,  0.0,   -1.0},
+      { 1, 1, 0, 1, 1,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 1, 0, 1, 0, 1,      0.0,   0.0,  -13.0,      0.0,  0.0,  -11.0},
+      { 1, 0, 1, 0, 0,      3.0,   0.0,    6.0,      0.0,  0.0,    0.0},
+      { 0, 2, 0, 2, 0,     -7.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 2,-1, 2,-2, 1,      5.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 0,-1, 4,-2, 1,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      { 0, 0, 4,-2, 3,      3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 1, 4,-2, 1,      5.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 4, 0, 2,-4, 2,     -7.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+
+   /* 571-580 */
+      { 2, 2, 2,-2, 2,      8.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 2, 0, 4,-4, 2,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-1,-2, 0, 4, 0,     11.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1,-3, 2, 2, 2,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      {-3, 0, 2, 4, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      {-3, 0, 2,-2, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-1,-1, 0,-2, 1,      8.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      {-3, 0, 0, 0, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      {-3, 0,-2, 2, 0,     11.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 1, 0,-4, 1,     -6.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+
+   /* 581-590 */
+      {-2, 1, 0,-2, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-4, 0, 0, 0, 1,     -8.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+      {-1, 0, 0,-4, 1,     -7.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      {-3, 0, 0,-2, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 0, 0, 0, 3, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      {-1, 1, 0, 4, 1,      6.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 1,-2, 2, 0, 1,     -6.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      { 0, 1, 0, 3, 0,      6.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-1, 0, 2, 2, 3,      6.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 0, 0, 2, 2, 2,      5.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+
+   /* 591-600 */
+      {-2, 0, 2, 2, 2,     -5.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-1, 1, 2, 2, 0,     -4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 3, 0, 0, 0, 2,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 2, 1, 0, 1, 0,      4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 2,-1, 2,-1, 2,      6.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 0, 0, 2, 0, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 0, 0, 3, 0, 3,      0.0,   0.0,  -26.0,      0.0,  0.0,  -11.0},
+      { 0, 0, 3, 0, 2,      0.0,   0.0,  -10.0,      0.0,  0.0,   -5.0},
+      {-1, 2, 2, 2, 1,      5.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      {-1, 0, 4, 0, 0,    -13.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 601-610 */
+      { 1, 2, 2, 0, 1,      3.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 3, 1, 2,-2, 1,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 1, 1, 4,-2, 2,      7.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      {-2,-1, 0, 6, 0,      4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0,-2, 0, 4, 0,      5.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-2, 0, 0, 6, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-2,-2, 2, 4, 2,     -6.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 0,-3, 2, 2, 2,     -5.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 0, 0, 0, 4, 2,     -7.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      {-1,-1, 2, 3, 2,      5.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+
+   /* 611-620 */
+      {-2, 0, 2, 4, 0,     13.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 2,-1, 0, 2, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 1, 0, 0, 3, 0,     -3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 1, 0, 4, 1,      5.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 0, 1, 0, 4, 0,    -11.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1,-1, 2, 1, 2,      5.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 0, 0, 2, 2, 3,      4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 0, 2, 2, 2,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-1, 0, 2, 2, 2,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-2, 0, 4, 2, 1,      6.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+
+   /* 621-630 */
+      { 2, 1, 0, 2, 1,      3.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 2, 1, 0, 2, 0,    -12.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 2,-1, 2, 0, 0,      4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 0, 2, 1, 0,     -3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 1, 2, 2, 0,     -4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 2, 0, 2, 0, 3,      3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 3, 0, 2, 0, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 1, 0, 2, 0, 2,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      { 1, 0, 3, 0, 3,      0.0,   0.0,   -5.0,      0.0,  0.0,   -2.0},
+      { 1, 1, 2, 1, 1,     -7.0,   0.0,    0.0,      4.0,  0.0,    0.0},
+
+   /* 631-640 */
+      { 0, 2, 2, 2, 2,      6.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 2, 1, 2, 0, 0,     -3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 2, 0, 4,-2, 1,      5.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 4, 1, 2,-2, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      {-1,-1, 0, 6, 0,      3.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      {-3,-1, 2, 6, 2,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      {-1, 0, 0, 6, 1,     -5.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      {-3, 0, 2, 6, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 1,-1, 0, 4, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 1,-1, 0, 4, 0,     12.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 641-650 */
+      {-2, 0, 2, 5, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 1,-2, 2, 2, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 3,-1, 0, 2, 0,      4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1,-1, 2, 2, 0,      6.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 0, 2, 3, 1,      5.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      {-1, 1, 2, 4, 1,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 0, 1, 2, 3, 2,     -6.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      {-1, 0, 4, 2, 1,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 2, 0, 2, 1, 1,      6.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 5, 0, 0, 0, 0,      6.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 651-660 */
+      { 2, 1, 2, 1, 2,     -6.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      { 1, 0, 4, 0, 1,      3.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 3, 1, 2, 0, 1,      7.0,   0.0,    0.0,     -4.0,  0.0,    0.0},
+      { 3, 0, 4,-2, 2,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      {-2,-1, 2, 6, 2,     -5.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 0, 0, 0, 6, 0,      5.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0,-2, 2, 4, 2,     -6.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      {-2, 0, 2, 6, 1,     -6.0,   0.0,    0.0,      3.0,  0.0,    0.0},
+      { 2, 0, 0, 4, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 2, 0, 0, 4, 0,     10.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+
+   /* 661-670 */
+      { 2,-2, 2, 2, 2,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 0, 0, 2, 4, 0,      7.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 1, 0, 2, 3, 2,      7.0,   0.0,    0.0,     -3.0,  0.0,    0.0},
+      { 4, 0, 0, 2, 0,      4.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 2, 0, 2, 2, 0,     11.0,   0.0,    0.0,      0.0,  0.0,    0.0},
+      { 0, 0, 4, 2, 2,      5.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 4,-1, 2, 0, 2,     -6.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 3, 0, 2, 1, 2,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 2, 1, 2, 2, 1,      3.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 4, 1, 2, 0, 2,      5.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+
+   /* 671-678 */
+      {-1,-1, 2, 6, 2,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      {-1, 0, 2, 6, 1,     -4.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 1,-1, 2, 4, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0},
+      { 1, 1, 2, 4, 2,      4.0,   0.0,    0.0,     -2.0,  0.0,    0.0},
+      { 3, 1, 2, 2, 2,      3.0,   0.0,    0.0,     -1.0,  0.0,    0.0},
+      { 5, 0, 2, 0, 1,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      { 2,-1, 2, 4, 2,     -3.0,   0.0,    0.0,      1.0,  0.0,    0.0},
+      { 2, 0, 2, 4, 1,     -3.0,   0.0,    0.0,      2.0,  0.0,    0.0}
+   };
+
+/* Number of terms in the luni-solar nutation model */
+   const int NLS = (int) (sizeof xls / sizeof xls[0]);
+
+/* ------------------------ */
+/* Planetary nutation model */
+/* ------------------------ */
+
+/* The units for the sine and cosine coefficients are */
+/* 0.1 microarcsecond                                 */
+
+   static const struct {
+      int nl,               /* coefficients of l, F, D and Omega */
+          nf,
+          nd,
+          nom,
+          nme,              /* coefficients of planetary longitudes */
+          nve,
+          nea,
+          nma,
+          nju,
+          nsa,
+          nur,
+          nne,
+          npa;              /* coefficient of general precession */
+      int sp,cp;            /* longitude sin, cos coefficients */
+      int se,ce;            /* obliquity sin, cos coefficients */
+   } xpl[] = {
+
+   /* 1-10 */
+      { 0, 0, 0, 0, 0,  0,  8,-16, 4, 5, 0, 0, 0, 1440,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -8, 16,-4,-5, 0, 0, 2,   56,-117,  -42, -40},
+      { 0, 0, 0, 0, 0,  0,  8,-16, 4, 5, 0, 0, 2,  125, -43,    0, -54},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 0,-1, 2, 2,    0,   5,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -4,  8,-1,-5, 0, 0, 2,    3,  -7,   -3,   0},
+      { 0, 0, 0, 0, 0,  0,  4, -8, 3, 0, 0, 0, 1,    3,   0,    0,  -2},
+      { 0, 1,-1, 1, 0,  0,  3, -8, 3, 0, 0, 0, 0, -114,   0,    0,  61},
+      {-1, 0, 0, 0, 0, 10, -3,  0, 0, 0, 0, 0, 0, -219,  89,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  0,-2, 6,-3, 0, 2,   -3,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  4, -8, 3, 0, 0, 0, 0, -462,1604,    0,   0},
+
+   /* 11-20 */
+      { 0, 1,-1, 1, 0,  0, -5,  8,-3, 0, 0, 0, 0,   99,   0,    0, -53},
+      { 0, 0, 0, 0, 0,  0, -4,  8,-3, 0, 0, 0, 1,   -3,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  0,  4, -8, 1, 5, 0, 0, 2,    0,   6,    2,   0},
+      { 0, 0, 0, 0, 0, -5,  6,  4, 0, 0, 0, 0, 2,    3,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 2,-5, 0, 0, 2,  -12,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 2,-5, 0, 0, 1,   14,-218,  117,   8},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 2,-5, 0, 0, 0,   31,-481, -257, -17},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 2,-5, 0, 0, 0, -491, 128,    0,   0},
+      { 0, 1,-1, 1, 0,  0, -1,  0,-2, 5, 0, 0, 0,-3084,5123, 2735,1647},
+      { 0, 0, 0, 0, 0,  0,  0,  0,-2, 5, 0, 0, 1,-1444,2409,-1286,-771},
+
+   /* 21-30 */
+      { 0, 0, 0, 0, 0,  0,  0,  0,-2, 5, 0, 0, 2,   11, -24,  -11,  -9},
+      { 2,-1,-1, 0, 0,  0,  3, -7, 0, 0, 0, 0, 0,   26,  -9,    0,   0},
+      { 1, 0,-2, 0, 0, 19,-21,  3, 0, 0, 0, 0, 0,  103, -60,    0,   0},
+      { 0, 1,-1, 1, 0,  2, -4,  0,-3, 0, 0, 0, 0,    0, -13,   -7,   0},
+      { 1, 0,-1, 1, 0,  0, -1,  0, 2, 0, 0, 0, 0,  -26, -29,  -16,  14},
+      { 0, 1,-1, 1, 0,  0, -1,  0,-4,10, 0, 0, 0,    9, -27,  -14,  -5},
+      {-2, 0, 2, 1, 0,  0,  2,  0, 0,-5, 0, 0, 0,   12,   0,    0,  -6},
+      { 0, 0, 0, 0, 0,  3, -7,  4, 0, 0, 0, 0, 0,   -7,   0,    0,   0},
+      { 0,-1, 1, 0, 0,  0,  1,  0, 1,-1, 0, 0, 0,    0,  24,    0,   0},
+      {-2, 0, 2, 1, 0,  0,  2,  0,-2, 0, 0, 0, 0,  284,   0,    0,-151},
+
+   /* 31-40 */
+      {-1, 0, 0, 0, 0, 18,-16,  0, 0, 0, 0, 0, 0,  226, 101,    0,   0},
+      {-2, 1, 1, 2, 0,  0,  1,  0,-2, 0, 0, 0, 0,    0,  -8,   -2,   0},
+      {-1, 1,-1, 1, 0, 18,-17,  0, 0, 0, 0, 0, 0,    0,  -6,   -3,   0},
+      {-1, 0, 1, 1, 0,  0,  2, -2, 0, 0, 0, 0, 0,    5,   0,    0,  -3},
+      { 0, 0, 0, 0, 0, -8, 13,  0, 0, 0, 0, 0, 2,  -41, 175,   76,  17},
+      { 0, 2,-2, 2, 0, -8, 11,  0, 0, 0, 0, 0, 0,    0,  15,    6,   0},
+      { 0, 0, 0, 0, 0, -8, 13,  0, 0, 0, 0, 0, 1,  425, 212, -133, 269},
+      { 0, 1,-1, 1, 0, -8, 12,  0, 0, 0, 0, 0, 0, 1200, 598,  319,-641},
+      { 0, 0, 0, 0, 0,  8,-13,  0, 0, 0, 0, 0, 0,  235, 334,    0,   0},
+      { 0, 1,-1, 1, 0,  8,-14,  0, 0, 0, 0, 0, 0,   11, -12,   -7,  -6},
+
+   /* 41-50 */
+      { 0, 0, 0, 0, 0,  8,-13,  0, 0, 0, 0, 0, 1,    5,  -6,    3,   3},
+      {-2, 0, 2, 1, 0,  0,  2,  0,-4, 5, 0, 0, 0,   -5,   0,    0,   3},
+      {-2, 0, 2, 2, 0,  3, -3,  0, 0, 0, 0, 0, 0,    6,   0,    0,  -3},
+      {-2, 0, 2, 0, 0,  0,  2,  0,-3, 1, 0, 0, 0,   15,   0,    0,   0},
+      { 0, 0, 0, 1, 0,  3, -5,  0, 2, 0, 0, 0, 0,   13,   0,    0,  -7},
+      {-2, 0, 2, 0, 0,  0,  2,  0,-4, 3, 0, 0, 0,   -6,  -9,    0,   0},
+      { 0,-1, 1, 0, 0,  0,  0,  2, 0, 0, 0, 0, 0,  266, -78,    0,   0},
+      { 0, 0, 0, 1, 0,  0, -1,  2, 0, 0, 0, 0, 0, -460,-435, -232, 246},
+      { 0, 1,-1, 2, 0,  0, -2,  2, 0, 0, 0, 0, 0,    0,  15,    7,   0},
+      {-1, 1, 0, 1, 0,  3, -5,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   2},
+
+   /* 51-60 */
+      {-1, 0, 1, 0, 0,  3, -4,  0, 0, 0, 0, 0, 0,    0, 131,    0,   0},
+      {-2, 0, 2, 0, 0,  0,  2,  0,-2,-2, 0, 0, 0,    4,   0,    0,   0},
+      {-2, 2, 0, 2, 0,  0, -5,  9, 0, 0, 0, 0, 0,    0,   3,    0,   0},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 0, 0,-1, 0, 0,    0,   4,    2,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 0, 1, 0, 0,    0,   3,    0,   0},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 0, 0, 0, 2, 0,  -17, -19,  -10,   9},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 0, 0, 2, 1,   -9, -11,    6,  -5},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 0, 0, 2, 2,   -6,   0,    0,   3},
+      {-1, 0, 1, 0, 0,  0,  3, -4, 0, 0, 0, 0, 0,  -16,   8,    0,   0},
+      { 0,-1, 1, 0, 0,  0,  1,  0, 0, 2, 0, 0, 0,    0,   3,    0,   0},
+
+   /* 61-70 */
+      { 0, 1,-1, 2, 0,  0, -1,  0, 0, 2, 0, 0, 0,   11,  24,   11,  -5},
+      { 0, 0, 0, 1, 0,  0, -9, 17, 0, 0, 0, 0, 0,   -3,  -4,   -2,   1},
+      { 0, 0, 0, 2, 0, -3,  5,  0, 0, 0, 0, 0, 0,    3,   0,    0,  -1},
+      { 0, 1,-1, 1, 0,  0, -1,  0,-1, 2, 0, 0, 0,    0,  -8,   -4,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 1,-2, 0, 0, 0,    0,   3,    0,   0},
+      { 1, 0,-2, 0, 0, 17,-16,  0,-2, 0, 0, 0, 0,    0,   5,    0,   0},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 1,-3, 0, 0, 0,    0,   3,    2,   0},
+      {-2, 0, 2, 1, 0,  0,  5, -6, 0, 0, 0, 0, 0,   -6,   4,    2,   3},
+      { 0,-2, 2, 0, 0,  0,  9,-13, 0, 0, 0, 0, 0,   -3,  -5,    0,   0},
+      { 0, 1,-1, 2, 0,  0, -1,  0, 0, 1, 0, 0, 0,   -5,   0,    0,   2},
+
+   /* 71-80 */
+      { 0, 0, 0, 1, 0,  0,  0,  0, 0, 1, 0, 0, 0,    4,  24,   13,  -2},
+      { 0,-1, 1, 0, 0,  0,  1,  0, 0, 1, 0, 0, 0,  -42,  20,    0,   0},
+      { 0,-2, 2, 0, 0,  5, -6,  0, 0, 0, 0, 0, 0,  -10, 233,    0,   0},
+      { 0,-1, 1, 1, 0,  5, -7,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   1},
+      {-2, 0, 2, 0, 0,  6, -8,  0, 0, 0, 0, 0, 0,   78, -18,    0,   0},
+      { 2, 1,-3, 1, 0, -6,  7,  0, 0, 0, 0, 0, 0,    0,   3,    1,   0},
+      { 0, 0, 0, 2, 0,  0,  0,  0, 1, 0, 0, 0, 0,    0,  -3,   -1,   0},
+      { 0,-1, 1, 1, 0,  0,  1,  0, 1, 0, 0, 0, 0,    0,  -4,   -2,   1},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 0, 0, 2, 0, 0,    0,  -8,   -4,  -1},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 0, 2, 0, 1,    0,  -5,    3,   0},
+
+   /* 81-90 */
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 0, 2, 0, 2,   -7,   0,    0,   3},
+      { 0, 0, 0, 0, 0,  0, -8, 15, 0, 0, 0, 0, 2,  -14,   8,    3,   6},
+      { 0, 0, 0, 0, 0,  0, -8, 15, 0, 0, 0, 0, 1,    0,   8,   -4,   0},
+      { 0, 1,-1, 1, 0,  0, -9, 15, 0, 0, 0, 0, 0,    0,  19,   10,   0},
+      { 0, 0, 0, 0, 0,  0,  8,-15, 0, 0, 0, 0, 0,   45, -22,    0,   0},
+      { 1,-1,-1, 0, 0,  0,  8,-15, 0, 0, 0, 0, 0,   -3,   0,    0,   0},
+      { 2, 0,-2, 0, 0,  2, -5,  0, 0, 0, 0, 0, 0,    0,  -3,    0,   0},
+      {-2, 0, 2, 0, 0,  0,  2,  0,-5, 5, 0, 0, 0,    0,   3,    0,   0},
+      { 2, 0,-2, 1, 0,  0, -6,  8, 0, 0, 0, 0, 0,    3,   5,    3,  -2},
+      { 2, 0,-2, 1, 0,  0, -2,  0, 3, 0, 0, 0, 0,   89, -16,   -9, -48},
+
+   /* 91-100 */
+      {-2, 1, 1, 0, 0,  0,  1,  0,-3, 0, 0, 0, 0,    0,   3,    0,   0},
+      {-2, 1, 1, 1, 0,  0,  1,  0,-3, 0, 0, 0, 0,   -3,   7,    4,   2},
+      {-2, 0, 2, 0, 0,  0,  2,  0,-3, 0, 0, 0, 0, -349, -62,    0,   0},
+      {-2, 0, 2, 0, 0,  0,  6, -8, 0, 0, 0, 0, 0,  -15,  22,    0,   0},
+      {-2, 0, 2, 0, 0,  0,  2,  0,-1,-5, 0, 0, 0,   -3,   0,    0,   0},
+      {-1, 0, 1, 0, 0,  0,  1,  0,-1, 0, 0, 0, 0,  -53,   0,    0,   0},
+      {-1, 1, 1, 1, 0,-20, 20,  0, 0, 0, 0, 0, 0,    5,   0,    0,  -3},
+      { 1, 0,-2, 0, 0, 20,-21,  0, 0, 0, 0, 0, 0,    0,  -8,    0,   0},
+      { 0, 0, 0, 1, 0,  0,  8,-15, 0, 0, 0, 0, 0,   15,  -7,   -4,  -8},
+      { 0, 2,-2, 1, 0,  0,-10, 15, 0, 0, 0, 0, 0,   -3,   0,    0,   1},
+
+   /* 101-110 */
+      { 0,-1, 1, 0, 0,  0,  1,  0, 1, 0, 0, 0, 0,  -21, -78,    0,   0},
+      { 0, 0, 0, 1, 0,  0,  0,  0, 1, 0, 0, 0, 0,   20, -70,  -37, -11},
+      { 0, 1,-1, 2, 0,  0, -1,  0, 1, 0, 0, 0, 0,    0,   6,    3,   0},
+      { 0, 1,-1, 1, 0,  0, -1,  0,-2, 4, 0, 0, 0,    5,   3,    2,  -2},
+      { 2, 0,-2, 1, 0, -6,  8,  0, 0, 0, 0, 0, 0,  -17,  -4,   -2,   9},
+      { 0,-2, 2, 1, 0,  5, -6,  0, 0, 0, 0, 0, 0,    0,   6,    3,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0,-1, 0, 0, 1,   32,  15,   -8,  17},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 0,-1, 0, 0, 0,  174,  84,   45, -93},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 1, 0, 0, 0,   11,  56,    0,   0},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 0, 1, 0, 0, 0,  -66, -12,   -6,  35},
+
+   /* 111-120 */
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 1, 0, 0, 1,   47,   8,    4, -25},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 1, 0, 0, 2,    0,   8,    4,   0},
+      { 0, 2,-2, 1, 0,  0, -9, 13, 0, 0, 0, 0, 0,   10, -22,  -12,  -5},
+      { 0, 0, 0, 1, 0,  0,  7,-13, 0, 0, 0, 0, 0,   -3,   0,    0,   2},
+      {-2, 0, 2, 0, 0,  0,  5, -6, 0, 0, 0, 0, 0,  -24,  12,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  9,-17, 0, 0, 0, 0, 0,    5,  -6,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -9, 17, 0, 0, 0, 0, 2,    3,   0,    0,  -2},
+      { 1, 0,-1, 1, 0,  0, -3,  4, 0, 0, 0, 0, 0,    4,   3,    1,  -2},
+      { 1, 0,-1, 1, 0, -3,  4,  0, 0, 0, 0, 0, 0,    0,  29,   15,   0},
+      { 0, 0, 0, 2, 0,  0, -1,  2, 0, 0, 0, 0, 0,   -5,  -4,   -2,   2},
+
+   /* 121-130 */
+      { 0,-1, 1, 1, 0,  0,  0,  2, 0, 0, 0, 0, 0,    8,  -3,   -1,  -5},
+      { 0,-2, 2, 0, 1,  0, -2,  0, 0, 0, 0, 0, 0,    0,  -3,    0,   0},
+      { 0, 0, 0, 0, 0,  3, -5,  0, 2, 0, 0, 0, 0,   10,   0,    0,   0},
+      {-2, 0, 2, 1, 0,  0,  2,  0,-3, 1, 0, 0, 0,    3,   0,    0,  -2},
+      {-2, 0, 2, 1, 0,  3, -3,  0, 0, 0, 0, 0, 0,   -5,   0,    0,   3},
+      { 0, 0, 0, 1, 0,  8,-13,  0, 0, 0, 0, 0, 0,   46,  66,   35, -25},
+      { 0,-1, 1, 0, 0,  8,-12,  0, 0, 0, 0, 0, 0,  -14,   7,    0,   0},
+      { 0, 2,-2, 1, 0, -8, 11,  0, 0, 0, 0, 0, 0,    0,   3,    2,   0},
+      {-1, 0, 1, 0, 0,  0,  2, -2, 0, 0, 0, 0, 0,   -5,   0,    0,   0},
+      {-1, 0, 0, 1, 0, 18,-16,  0, 0, 0, 0, 0, 0,  -68, -34,  -18,  36},
+
+   /* 131-140 */
+      { 0, 1,-1, 1, 0,  0, -1,  0,-1, 1, 0, 0, 0,    0,  14,    7,   0},
+      { 0, 0, 0, 1, 0,  3, -7,  4, 0, 0, 0, 0, 0,   10,  -6,   -3,  -5},
+      {-2, 1, 1, 1, 0,  0, -3,  7, 0, 0, 0, 0, 0,   -5,  -4,   -2,   3},
+      { 0, 1,-1, 2, 0,  0, -1,  0,-2, 5, 0, 0, 0,   -3,   5,    2,   1},
+      { 0, 0, 0, 1, 0,  0,  0,  0,-2, 5, 0, 0, 0,   76,  17,    9, -41},
+      { 0, 0, 0, 1, 0,  0, -4,  8,-3, 0, 0, 0, 0,   84, 298,  159, -45},
+      { 1, 0, 0, 1, 0,-10,  3,  0, 0, 0, 0, 0, 0,    3,   0,    0,  -1},
+      { 0, 2,-2, 1, 0,  0, -2,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   2},
+      {-1, 0, 0, 1, 0, 10, -3,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   1},
+      { 0, 0, 0, 1, 0,  0,  4, -8, 3, 0, 0, 0, 0,  -82, 292,  156,  44},
+
+   /* 141-150 */
+      { 0, 0, 0, 1, 0,  0,  0,  0, 2,-5, 0, 0, 0,  -73,  17,    9,  39},
+      { 0,-1, 1, 0, 0,  0,  1,  0, 2,-5, 0, 0, 0,   -9, -16,    0,   0},
+      { 2,-1,-1, 1, 0,  0,  3, -7, 0, 0, 0, 0, 0,    3,   0,   -1,  -2},
+      {-2, 0, 2, 0, 0,  0,  2,  0, 0,-5, 0, 0, 0,   -3,   0,    0,   0},
+      { 0, 0, 0, 1, 0, -3,  7, -4, 0, 0, 0, 0, 0,   -9,  -5,   -3,   5},
+      {-2, 0, 2, 0, 0,  0,  2,  0,-2, 0, 0, 0, 0, -439,   0,    0,   0},
+      { 1, 0, 0, 1, 0,-18, 16,  0, 0, 0, 0, 0, 0,   57, -28,  -15, -30},
+      {-2, 1, 1, 1, 0,  0,  1,  0,-2, 0, 0, 0, 0,    0,  -6,   -3,   0},
+      { 0, 1,-1, 2, 0, -8, 12,  0, 0, 0, 0, 0, 0,   -4,   0,    0,   2},
+      { 0, 0, 0, 1, 0, -8, 13,  0, 0, 0, 0, 0, 0,  -40,  57,   30,  21},
+
+   /* 151-160 */
+      { 0, 0, 0, 0, 0,  0,  1, -2, 0, 0, 0, 0, 1,   23,   7,    3, -13},
+      { 0, 1,-1, 1, 0,  0,  0, -2, 0, 0, 0, 0, 0,  273,  80,   43,-146},
+      { 0, 0, 0, 0, 0,  0,  1, -2, 0, 0, 0, 0, 0, -449, 430,    0,   0},
+      { 0, 1,-1, 1, 0,  0, -2,  2, 0, 0, 0, 0, 0,   -8, -47,  -25,   4},
+      { 0, 0, 0, 0, 0,  0, -1,  2, 0, 0, 0, 0, 1,    6,  47,   25,  -3},
+      {-1, 0, 1, 1, 0,  3, -4,  0, 0, 0, 0, 0, 0,    0,  23,   13,   0},
+      {-1, 0, 1, 1, 0,  0,  3, -4, 0, 0, 0, 0, 0,   -3,   0,    0,   2},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 0,-2, 0, 0, 0,    3,  -4,   -2,  -2},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 0, 2, 0, 0, 0,  -48,-110,  -59,  26},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 2, 0, 0, 1,   51, 114,   61, -27},
+
+   /* 161-170 */
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 2, 0, 0, 2, -133,   0,    0,  57},
+      { 0, 1,-1, 0, 0,  3, -6,  0, 0, 0, 0, 0, 0,    0,   4,    0,   0},
+      { 0, 0, 0, 1, 0, -3,  5,  0, 0, 0, 0, 0, 0,  -21,  -6,   -3,  11},
+      { 0, 1,-1, 2, 0, -3,  4,  0, 0, 0, 0, 0, 0,    0,  -3,   -1,   0},
+      { 0, 0, 0, 1, 0,  0, -2,  4, 0, 0, 0, 0, 0,  -11, -21,  -11,   6},
+      { 0, 2,-2, 1, 0, -5,  6,  0, 0, 0, 0, 0, 0,  -18,-436, -233,   9},
+      { 0,-1, 1, 0, 0,  5, -7,  0, 0, 0, 0, 0, 0,   35,  -7,    0,   0},
+      { 0, 0, 0, 1, 0,  5, -8,  0, 0, 0, 0, 0, 0,    0,   5,    3,   0},
+      {-2, 0, 2, 1, 0,  6, -8,  0, 0, 0, 0, 0, 0,   11,  -3,   -1,  -6},
+      { 0, 0, 0, 1, 0,  0, -8, 15, 0, 0, 0, 0, 0,   -5,  -3,   -1,   3},
+
+   /* 171-180 */
+      {-2, 0, 2, 1, 0,  0,  2,  0,-3, 0, 0, 0, 0,  -53,  -9,   -5,  28},
+      {-2, 0, 2, 1, 0,  0,  6, -8, 0, 0, 0, 0, 0,    0,   3,    2,   1},
+      { 1, 0,-1, 1, 0,  0, -1,  0, 1, 0, 0, 0, 0,    4,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 3,-5, 0, 0, 0,    0,  -4,    0,   0},
+      { 0, 1,-1, 1, 0,  0, -1,  0,-1, 0, 0, 0, 0,  -50, 194,  103,  27},
+      { 0, 0, 0, 0, 0,  0,  0,  0,-1, 0, 0, 0, 1,  -13,  52,   28,   7},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 1, 0, 0, 0, 0,  -91, 248,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 1, 0, 0, 0, 1,    6,  49,   26,  -3},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 1, 0, 0, 0, 0,   -6, -47,  -25,   3},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 1, 0, 0, 0, 1,    0,   5,    3,   0},
+
+   /* 181-190 */
+      { 0, 0, 0, 0, 0,  0,  0,  0, 1, 0, 0, 0, 2,   52,  23,   10, -23},
+      { 0, 1,-1, 2, 0,  0, -1,  0, 0,-1, 0, 0, 0,   -3,   0,    0,   1},
+      { 0, 0, 0, 1, 0,  0,  0,  0, 0,-1, 0, 0, 0,    0,   5,    3,   0},
+      { 0,-1, 1, 0, 0,  0,  1,  0, 0,-1, 0, 0, 0,   -4,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -7, 13, 0, 0, 0, 0, 2,   -4,   8,    3,   2},
+      { 0, 0, 0, 0, 0,  0,  7,-13, 0, 0, 0, 0, 0,   10,   0,    0,   0},
+      { 2, 0,-2, 1, 0,  0, -5,  6, 0, 0, 0, 0, 0,    3,   0,    0,  -2},
+      { 0, 2,-2, 1, 0,  0, -8, 11, 0, 0, 0, 0, 0,    0,   8,    4,   0},
+      { 0, 2,-2, 1,-1,  0,  2,  0, 0, 0, 0, 0, 0,    0,   8,    4,   1},
+      {-2, 0, 2, 0, 0,  0,  4, -4, 0, 0, 0, 0, 0,   -4,   0,    0,   0},
+
+   /* 191-200 */
+      { 0, 0, 0, 0, 0,  0,  0,  0, 2,-2, 0, 0, 0,   -4,   0,    0,   0},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 0, 3, 0, 0, 0,   -8,   4,    2,   4},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 3, 0, 0, 1,    8,  -4,   -2,  -4},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 3, 0, 0, 2,    0,  15,    7,   0},
+      {-2, 0, 2, 0, 0,  3, -3,  0, 0, 0, 0, 0, 0, -138,   0,    0,   0},
+      { 0, 0, 0, 2, 0,  0, -4,  8,-3, 0, 0, 0, 0,    0,  -7,   -3,   0},
+      { 0, 0, 0, 2, 0,  0,  4, -8, 3, 0, 0, 0, 0,    0,  -7,   -3,   0},
+      { 2, 0,-2, 1, 0,  0, -2,  0, 2, 0, 0, 0, 0,   54,   0,    0, -29},
+      { 0, 1,-1, 2, 0,  0, -1,  0, 2, 0, 0, 0, 0,    0,  10,    4,   0},
+      { 0, 1,-1, 2, 0,  0,  0, -2, 0, 0, 0, 0, 0,   -7,   0,    0,   3},
+
+   /* 201-210 */
+      { 0, 0, 0, 1, 0,  0,  1, -2, 0, 0, 0, 0, 0,  -37,  35,   19,  20},
+      { 0,-1, 1, 0, 0,  0,  2, -2, 0, 0, 0, 0, 0,    0,   4,    0,   0},
+      { 0,-1, 1, 0, 0,  0,  1,  0, 0,-2, 0, 0, 0,   -4,   9,    0,   0},
+      { 0, 2,-2, 1, 0,  0, -2,  0, 0, 2, 0, 0, 0,    8,   0,    0,  -4},
+      { 0, 1,-1, 1, 0,  3, -6,  0, 0, 0, 0, 0, 0,   -9, -14,   -8,   5},
+      { 0, 0, 0, 0, 0,  3, -5,  0, 0, 0, 0, 0, 1,   -3,  -9,   -5,   3},
+      { 0, 0, 0, 0, 0,  3, -5,  0, 0, 0, 0, 0, 0, -145,  47,    0,   0},
+      { 0, 1,-1, 1, 0, -3,  4,  0, 0, 0, 0, 0, 0,  -10,  40,   21,   5},
+      { 0, 0, 0, 0, 0, -3,  5,  0, 0, 0, 0, 0, 1,   11, -49,  -26,  -7},
+      { 0, 0, 0, 0, 0, -3,  5,  0, 0, 0, 0, 0, 2,-2150,   0,    0, 932},
+
+   /* 211-220 */
+      { 0, 2,-2, 2, 0, -3,  3,  0, 0, 0, 0, 0, 0,  -12,   0,    0,   5},
+      { 0, 0, 0, 0, 0, -3,  5,  0, 0, 0, 0, 0, 2,   85,   0,    0, -37},
+      { 0, 0, 0, 0, 0,  0,  2, -4, 0, 0, 0, 0, 1,    4,   0,    0,  -2},
+      { 0, 1,-1, 1, 0,  0,  1, -4, 0, 0, 0, 0, 0,    3,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  0,  2, -4, 0, 0, 0, 0, 0,  -86, 153,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -2,  4, 0, 0, 0, 0, 1,   -6,   9,    5,   3},
+      { 0, 1,-1, 1, 0,  0, -3,  4, 0, 0, 0, 0, 0,    9, -13,   -7,  -5},
+      { 0, 0, 0, 0, 0,  0, -2,  4, 0, 0, 0, 0, 1,   -8,  12,    6,   4},
+      { 0, 0, 0, 0, 0,  0, -2,  4, 0, 0, 0, 0, 2,  -51,   0,    0,  22},
+      { 0, 0, 0, 0, 0, -5,  8,  0, 0, 0, 0, 0, 2,  -11,-268, -116,   5},
+
+   /* 221-230 */
+      { 0, 2,-2, 2, 0, -5,  6,  0, 0, 0, 0, 0, 0,    0,  12,    5,   0},
+      { 0, 0, 0, 0, 0, -5,  8,  0, 0, 0, 0, 0, 2,    0,   7,    3,   0},
+      { 0, 0, 0, 0, 0, -5,  8,  0, 0, 0, 0, 0, 1,   31,   6,    3, -17},
+      { 0, 1,-1, 1, 0, -5,  7,  0, 0, 0, 0, 0, 0,  140,  27,   14, -75},
+      { 0, 0, 0, 0, 0, -5,  8,  0, 0, 0, 0, 0, 1,   57,  11,    6, -30},
+      { 0, 0, 0, 0, 0,  5, -8,  0, 0, 0, 0, 0, 0,  -14, -39,    0,   0},
+      { 0, 1,-1, 2, 0,  0, -1,  0,-1, 0, 0, 0, 0,    0,  -6,   -2,   0},
+      { 0, 0, 0, 1, 0,  0,  0,  0,-1, 0, 0, 0, 0,    4,  15,    8,  -2},
+      { 0,-1, 1, 0, 0,  0,  1,  0,-1, 0, 0, 0, 0,    0,   4,    0,   0},
+      { 0, 2,-2, 1, 0,  0, -2,  0, 1, 0, 0, 0, 0,   -3,   0,    0,   1},
+
+   /* 231-240 */
+      { 0, 0, 0, 0, 0,  0, -6, 11, 0, 0, 0, 0, 2,    0,  11,    5,   0},
+      { 0, 0, 0, 0, 0,  0,  6,-11, 0, 0, 0, 0, 0,    9,   6,    0,   0},
+      { 0, 0, 0, 0,-1,  0,  4,  0, 0, 0, 0, 0, 2,   -4,  10,    4,   2},
+      { 0, 0, 0, 0, 1,  0, -4,  0, 0, 0, 0, 0, 0,    5,   3,    0,   0},
+      { 2, 0,-2, 1, 0, -3,  3,  0, 0, 0, 0, 0, 0,   16,   0,    0,  -9},
+      {-2, 0, 2, 0, 0,  0,  2,  0, 0,-2, 0, 0, 0,   -3,   0,    0,   0},
+      { 0, 2,-2, 1, 0,  0, -7,  9, 0, 0, 0, 0, 0,    0,   3,    2,  -1},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 4,-5, 0, 0, 2,    7,   0,    0,  -3},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 2, 0, 0, 0, 0,  -25,  22,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 2, 0, 0, 0, 1,   42, 223,  119, -22},
+
+   /* 241-250 */
+      { 0, 1,-1, 1, 0,  0, -1,  0, 2, 0, 0, 0, 0,  -27,-143,  -77,  14},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 2, 0, 0, 0, 1,    9,  49,   26,  -5},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 2, 0, 0, 0, 2,-1166,   0,    0, 505},
+      { 0, 2,-2, 2, 0,  0, -2,  0, 2, 0, 0, 0, 0,   -5,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 5, 0, 0, 2,   -6,   0,    0,   3},
+      { 0, 0, 0, 1, 0,  3, -5,  0, 0, 0, 0, 0, 0,   -8,   0,    1,   4},
+      { 0,-1, 1, 0, 0,  3, -4,  0, 0, 0, 0, 0, 0,    0,  -4,    0,   0},
+      { 0, 2,-2, 1, 0, -3,  3,  0, 0, 0, 0, 0, 0,  117,   0,    0, -63},
+      { 0, 0, 0, 1, 0,  0,  2, -4, 0, 0, 0, 0, 0,   -4,   8,    4,   2},
+      { 0, 2,-2, 1, 0,  0, -4,  4, 0, 0, 0, 0, 0,    3,   0,    0,  -2},
+
+   /* 251-260 */
+      { 0, 1,-1, 2, 0, -5,  7,  0, 0, 0, 0, 0, 0,   -5,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  0,  3, -6, 0, 0, 0, 0, 0,    0,  31,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -3,  6, 0, 0, 0, 0, 1,   -5,   0,    1,   3},
+      { 0, 1,-1, 1, 0,  0, -4,  6, 0, 0, 0, 0, 0,    4,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  0, -3,  6, 0, 0, 0, 0, 1,   -4,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  0, -3,  6, 0, 0, 0, 0, 2,  -24, -13,   -6,  10},
+      { 0,-1, 1, 0, 0,  2, -2,  0, 0, 0, 0, 0, 0,    3,   0,    0,   0},
+      { 0, 0, 0, 1, 0,  2, -3,  0, 0, 0, 0, 0, 0,    0, -32,  -17,   0},
+      { 0, 0, 0, 0, 0,  0, -5,  9, 0, 0, 0, 0, 2,    8,  12,    5,  -3},
+      { 0, 0, 0, 0, 0,  0, -5,  9, 0, 0, 0, 0, 1,    3,   0,    0,  -1},
+
+   /* 261-270 */
+      { 0, 0, 0, 0, 0,  0,  5, -9, 0, 0, 0, 0, 0,    7,  13,    0,   0},
+      { 0,-1, 1, 0, 0,  0,  1,  0,-2, 0, 0, 0, 0,   -3,  16,    0,   0},
+      { 0, 2,-2, 1, 0,  0, -2,  0, 2, 0, 0, 0, 0,   50,   0,    0, -27},
+      {-2, 1, 1, 1, 0,  0,  1,  0, 0, 0, 0, 0, 0,    0,  -5,   -3,   0},
+      { 0,-2, 2, 0, 0,  3, -3,  0, 0, 0, 0, 0, 0,   13,   0,    0,   0},
+      { 0, 0, 0, 0, 0, -6, 10,  0, 0, 0, 0, 0, 1,    0,   5,    3,   1},
+      { 0, 0, 0, 0, 0, -6, 10,  0, 0, 0, 0, 0, 2,   24,   5,    2, -11},
+      { 0, 0, 0, 0, 0, -2,  3,  0, 0, 0, 0, 0, 2,    5, -11,   -5,  -2},
+      { 0, 0, 0, 0, 0, -2,  3,  0, 0, 0, 0, 0, 1,   30,  -3,   -2, -16},
+      { 0, 1,-1, 1, 0, -2,  2,  0, 0, 0, 0, 0, 0,   18,   0,    0,  -9},
+
+   /* 271-280 */
+      { 0, 0, 0, 0, 0,  2, -3,  0, 0, 0, 0, 0, 0,    8, 614,    0,   0},
+      { 0, 0, 0, 0, 0,  2, -3,  0, 0, 0, 0, 0, 1,    3,  -3,   -1,  -2},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 3, 0, 0, 0, 1,    6,  17,    9,  -3},
+      { 0, 1,-1, 1, 0,  0, -1,  0, 3, 0, 0, 0, 0,   -3,  -9,   -5,   2},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 3, 0, 0, 0, 1,    0,   6,    3,  -1},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 3, 0, 0, 0, 2, -127,  21,    9,  55},
+      { 0, 0, 0, 0, 0,  0,  4, -8, 0, 0, 0, 0, 0,    3,   5,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -4,  8, 0, 0, 0, 0, 2,   -6, -10,   -4,   3},
+      { 0,-2, 2, 0, 0,  0,  2,  0,-2, 0, 0, 0, 0,    5,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -4,  7, 0, 0, 0, 0, 2,   16,   9,    4,  -7},
+
+   /* 281-290 */
+      { 0, 0, 0, 0, 0,  0, -4,  7, 0, 0, 0, 0, 1,    3,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  0,  4, -7, 0, 0, 0, 0, 0,    0,  22,    0,   0},
+      { 0, 0, 0, 1, 0, -2,  3,  0, 0, 0, 0, 0, 0,    0,  19,   10,   0},
+      { 0, 2,-2, 1, 0,  0, -2,  0, 3, 0, 0, 0, 0,    7,   0,    0,  -4},
+      { 0, 0, 0, 0, 0,  0, -5, 10, 0, 0, 0, 0, 2,    0,  -5,   -2,   0},
+      { 0, 0, 0, 1, 0, -1,  2,  0, 0, 0, 0, 0, 0,    0,   3,    1,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  0, 4, 0, 0, 0, 2,   -9,   3,    1,   4},
+      { 0, 0, 0, 0, 0,  0, -3,  5, 0, 0, 0, 0, 2,   17,   0,    0,  -7},
+      { 0, 0, 0, 0, 0,  0, -3,  5, 0, 0, 0, 0, 1,    0,  -3,   -2,  -1},
+      { 0, 0, 0, 0, 0,  0,  3, -5, 0, 0, 0, 0, 0,  -20,  34,    0,   0},
+
+   /* 291-300 */
+      { 0, 0, 0, 0, 0,  1, -2,  0, 0, 0, 0, 0, 1,  -10,   0,    1,   5},
+      { 0, 1,-1, 1, 0,  1, -3,  0, 0, 0, 0, 0, 0,   -4,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  1, -2,  0, 0, 0, 0, 0, 0,   22, -87,    0,   0},
+      { 0, 0, 0, 0, 0, -1,  2,  0, 0, 0, 0, 0, 1,   -4,   0,    0,   2},
+      { 0, 0, 0, 0, 0, -1,  2,  0, 0, 0, 0, 0, 2,   -3,  -6,   -2,   1},
+      { 0, 0, 0, 0, 0, -7, 11,  0, 0, 0, 0, 0, 2,  -16,  -3,   -1,   7},
+      { 0, 0, 0, 0, 0, -7, 11,  0, 0, 0, 0, 0, 1,    0,  -3,   -2,   0},
+      { 0,-2, 2, 0, 0,  4, -4,  0, 0, 0, 0, 0, 0,    4,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  2, -3, 0, 0, 0, 0, 0,  -68,  39,    0,   0},
+      { 0, 2,-2, 1, 0, -4,  4,  0, 0, 0, 0, 0, 0,   27,   0,    0, -14},
+
+   /* 301-310 */
+      { 0,-1, 1, 0, 0,  4, -5,  0, 0, 0, 0, 0, 0,    0,  -4,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  1, -1, 0, 0, 0, 0, 0,  -25,   0,    0,   0},
+      { 0, 0, 0, 0, 0, -4,  7,  0, 0, 0, 0, 0, 1,  -12,  -3,   -2,   6},
+      { 0, 1,-1, 1, 0, -4,  6,  0, 0, 0, 0, 0, 0,    3,   0,    0,  -1},
+      { 0, 0, 0, 0, 0, -4,  7,  0, 0, 0, 0, 0, 2,    3,  66,   29,  -1},
+      { 0, 0, 0, 0, 0, -4,  6,  0, 0, 0, 0, 0, 2,  490,   0,    0,-213},
+      { 0, 0, 0, 0, 0, -4,  6,  0, 0, 0, 0, 0, 1,  -22,  93,   49,  12},
+      { 0, 1,-1, 1, 0, -4,  5,  0, 0, 0, 0, 0, 0,   -7,  28,   15,   4},
+      { 0, 0, 0, 0, 0, -4,  6,  0, 0, 0, 0, 0, 1,   -3,  13,    7,   2},
+      { 0, 0, 0, 0, 0,  4, -6,  0, 0, 0, 0, 0, 0,  -46,  14,    0,   0},
+
+   /* 311-320 */
+      {-2, 0, 2, 0, 0,  2, -2,  0, 0, 0, 0, 0, 0,   -5,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  1, 0, 0, 0, 0, 0,    2,   1,    0,   0},
+      { 0,-1, 1, 0, 0,  1,  0,  0, 0, 0, 0, 0, 0,    0,  -3,    0,   0},
+      { 0, 0, 0, 1, 0,  1, -1,  0, 0, 0, 0, 0, 0,  -28,   0,    0,  15},
+      { 0, 0, 0, 0, 0,  0, -1,  0, 5, 0, 0, 0, 2,    5,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  0,  1, -3, 0, 0, 0, 0, 0,    0,   3,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -1,  3, 0, 0, 0, 0, 2,  -11,   0,    0,   5},
+      { 0, 0, 0, 0, 0,  0, -7, 12, 0, 0, 0, 0, 2,    0,   3,    1,   0},
+      { 0, 0, 0, 0, 0, -1,  1,  0, 0, 0, 0, 0, 2,   -3,   0,    0,   1},
+      { 0, 0, 0, 0, 0, -1,  1,  0, 0, 0, 0, 0, 1,   25, 106,   57, -13},
+
+   /* 321-330 */
+      { 0, 1,-1, 1, 0, -1,  0,  0, 0, 0, 0, 0, 0,    5,  21,   11,  -3},
+      { 0, 0, 0, 0, 0,  1, -1,  0, 0, 0, 0, 0, 0, 1485,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  1, -1,  0, 0, 0, 0, 0, 1,   -7, -32,  -17,   4},
+      { 0, 1,-1, 1, 0,  1, -2,  0, 0, 0, 0, 0, 0,    0,   5,    3,   0},
+      { 0, 0, 0, 0, 0,  0, -2,  5, 0, 0, 0, 0, 2,   -6,  -3,   -2,   3},
+      { 0, 0, 0, 0, 0,  0, -1,  0, 4, 0, 0, 0, 2,   30,  -6,   -2, -13},
+      { 0, 0, 0, 0, 0,  0,  1,  0,-4, 0, 0, 0, 0,   -4,   4,    0,   0},
+      { 0, 0, 0, 1, 0, -1,  1,  0, 0, 0, 0, 0, 0,  -19,   0,    0,  10},
+      { 0, 0, 0, 0, 0,  0, -6, 10, 0, 0, 0, 0, 2,    0,   4,    2,  -1},
+      { 0, 0, 0, 0, 0,  0, -6, 10, 0, 0, 0, 0, 0,    0,   3,    0,   0},
+
+   /* 331-340 */
+      { 0, 2,-2, 1, 0,  0, -3,  0, 3, 0, 0, 0, 0,    4,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  0, -3,  7, 0, 0, 0, 0, 2,    0,  -3,   -1,   0},
+      {-2, 0, 2, 0, 0,  4, -4,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -5,  8, 0, 0, 0, 0, 2,    5,   3,    1,  -2},
+      { 0, 0, 0, 0, 0,  0,  5, -8, 0, 0, 0, 0, 0,    0,  11,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -1,  0, 3, 0, 0, 0, 2,  118,   0,    0, -52},
+      { 0, 0, 0, 0, 0,  0, -1,  0, 3, 0, 0, 0, 1,    0,  -5,   -3,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0,-3, 0, 0, 0, 0,  -28,  36,    0,   0},
+      { 0, 0, 0, 0, 0,  2, -4,  0, 0, 0, 0, 0, 0,    5,  -5,    0,   0},
+      { 0, 0, 0, 0, 0, -2,  4,  0, 0, 0, 0, 0, 1,   14, -59,  -31,  -8},
+
+   /* 341-350 */
+      { 0, 1,-1, 1, 0, -2,  3,  0, 0, 0, 0, 0, 0,    0,   9,    5,   1},
+      { 0, 0, 0, 0, 0, -2,  4,  0, 0, 0, 0, 0, 2, -458,   0,    0, 198},
+      { 0, 0, 0, 0, 0, -6,  9,  0, 0, 0, 0, 0, 2,    0, -45,  -20,   0},
+      { 0, 0, 0, 0, 0, -6,  9,  0, 0, 0, 0, 0, 1,    9,   0,    0,  -5},
+      { 0, 0, 0, 0, 0,  6, -9,  0, 0, 0, 0, 0, 0,    0,  -3,    0,   0},
+      { 0, 0, 0, 1, 0,  0,  1,  0,-2, 0, 0, 0, 0,    0,  -4,   -2,  -1},
+      { 0, 2,-2, 1, 0, -2,  2,  0, 0, 0, 0, 0, 0,   11,   0,    0,  -6},
+      { 0, 0, 0, 0, 0,  0, -4,  6, 0, 0, 0, 0, 2,    6,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  0,  4, -6, 0, 0, 0, 0, 0,  -16,  23,    0,   0},
+      { 0, 0, 0, 1, 0,  3, -4,  0, 0, 0, 0, 0, 0,    0,  -4,   -2,   0},
+
+   /* 351-360 */
+      { 0, 0, 0, 0, 0,  0, -1,  0, 2, 0, 0, 0, 2,   -5,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  0,  1,  0,-2, 0, 0, 0, 0, -166, 269,    0,   0},
+      { 0, 0, 0, 1, 0,  0,  1,  0,-1, 0, 0, 0, 0,   15,   0,    0,  -8},
+      { 0, 0, 0, 0, 0, -5,  9,  0, 0, 0, 0, 0, 2,   10,   0,    0,  -4},
+      { 0, 0, 0, 0, 0,  0,  3, -4, 0, 0, 0, 0, 0,  -78,  45,    0,   0},
+      { 0, 0, 0, 0, 0, -3,  4,  0, 0, 0, 0, 0, 2,    0,  -5,   -2,   0},
+      { 0, 0, 0, 0, 0, -3,  4,  0, 0, 0, 0, 0, 1,    7,   0,    0,  -4},
+      { 0, 0, 0, 0, 0,  3, -4,  0, 0, 0, 0, 0, 0,   -5, 328,    0,   0},
+      { 0, 0, 0, 0, 0,  3, -4,  0, 0, 0, 0, 0, 1,    3,   0,    0,  -2},
+      { 0, 0, 0, 1, 0,  0,  2, -2, 0, 0, 0, 0, 0,    5,   0,    0,  -2},
+
+   /* 361-370 */
+      { 0, 0, 0, 1, 0,  0, -1,  0, 2, 0, 0, 0, 0,    0,   3,    1,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 0,-3, 0, 0, 0,   -3,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 1,-5, 0, 0, 0,   -3,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -1,  0, 1, 0, 0, 0, 1,    0,  -4,   -2,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0,-1, 0, 0, 0, 0,-1223, -26,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0,-1, 0, 0, 0, 1,    0,   7,    3,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0,-3, 5, 0, 0, 0,    3,   0,    0,   0},
+      { 0, 0, 0, 1, 0, -3,  4,  0, 0, 0, 0, 0, 0,    0,   3,    2,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 0,-2, 0, 0, 0,   -6,  20,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  2, -2, 0, 0, 0, 0, 0, -368,   0,    0,   0},
+
+   /* 371-380 */
+      { 0, 0, 0, 0, 0,  0,  1,  0, 0,-1, 0, 0, 0,  -75,   0,    0,   0},
+      { 0, 0, 0, 1, 0,  0, -1,  0, 1, 0, 0, 0, 0,   11,   0,    0,  -6},
+      { 0, 0, 0, 1, 0,  0, -2,  2, 0, 0, 0, 0, 0,    3,   0,    0,  -2},
+      { 0, 0, 0, 0, 0, -8, 14,  0, 0, 0, 0, 0, 2,   -3,   0,    0,   1},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 2,-5, 0, 0, 0,  -13, -30,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  5, -8, 3, 0, 0, 0, 0,   21,   3,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  5, -8, 3, 0, 0, 0, 2,   -3,   0,    0,   1},
+      { 0, 0, 0, 0, 0,  0, -1,  0, 0, 0, 0, 0, 1,   -4,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 0, 0, 0, 0, 0,    8, -27,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  3, -8, 3, 0, 0, 0, 0,  -19, -11,    0,   0},
+
+   /* 381-390 */
+      { 0, 0, 0, 0, 0,  0, -3,  8,-3, 0, 0, 0, 2,   -4,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  0,  1,  0,-2, 5, 0, 0, 2,    0,   5,    2,   0},
+      { 0, 0, 0, 0, 0, -8, 12,  0, 0, 0, 0, 0, 2,   -6,   0,    0,   2},
+      { 0, 0, 0, 0, 0, -8, 12,  0, 0, 0, 0, 0, 0,   -8,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 1,-2, 0, 0, 0,   -1,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 0, 1, 0, 0, 2,  -14,   0,    0,   6},
+      { 0, 0, 0, 0, 0,  0,  0,  2, 0, 0, 0, 0, 0,    6,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  0,  2, 0, 0, 0, 0, 2,  -74,   0,    0,  32},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 0, 2, 0, 0, 2,    0,  -3,   -1,   0},
+      { 0, 2,-2, 1, 0, -5,  5,  0, 0, 0, 0, 0, 0,    4,   0,    0,  -2},
+
+   /* 391-400 */
+      { 0, 0, 0, 0, 0,  0,  1,  0, 1, 0, 0, 0, 0,    8,  11,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 1, 0, 0, 0, 1,    0,   3,    2,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 1, 0, 0, 0, 2, -262,   0,    0, 114},
+      { 0, 0, 0, 0, 0,  3, -6,  0, 0, 0, 0, 0, 0,    0,  -4,    0,   0},
+      { 0, 0, 0, 0, 0, -3,  6,  0, 0, 0, 0, 0, 1,   -7,   0,    0,   4},
+      { 0, 0, 0, 0, 0, -3,  6,  0, 0, 0, 0, 0, 2,    0, -27,  -12,   0},
+      { 0, 0, 0, 0, 0,  0, -1,  4, 0, 0, 0, 0, 2,  -19,  -8,   -4,   8},
+      { 0, 0, 0, 0, 0, -5,  7,  0, 0, 0, 0, 0, 2,  202,   0,    0, -87},
+      { 0, 0, 0, 0, 0, -5,  7,  0, 0, 0, 0, 0, 1,   -8,  35,   19,   5},
+      { 0, 1,-1, 1, 0, -5,  6,  0, 0, 0, 0, 0, 0,    0,   4,    2,   0},
+
+   /* 401-410 */
+      { 0, 0, 0, 0, 0,  5, -7,  0, 0, 0, 0, 0, 0,   16,  -5,    0,   0},
+      { 0, 2,-2, 1, 0,  0, -1,  0, 1, 0, 0, 0, 0,    5,   0,    0,  -3},
+      { 0, 0, 0, 0, 0,  0, -1,  0, 1, 0, 0, 0, 0,    0,  -3,    0,   0},
+      { 0, 0, 0, 0,-1,  0,  3,  0, 0, 0, 0, 0, 2,    1,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 2, 0, 0, 0, 2,  -35, -48,  -21,  15},
+      { 0, 0, 0, 0, 0,  0, -2,  6, 0, 0, 0, 0, 2,   -3,  -5,   -2,   1},
+      { 0, 0, 0, 1, 0,  2, -2,  0, 0, 0, 0, 0, 0,    6,   0,    0,  -3},
+      { 0, 0, 0, 0, 0,  0, -6,  9, 0, 0, 0, 0, 2,    3,   0,    0,  -1},
+      { 0, 0, 0, 0, 0,  0,  6, -9, 0, 0, 0, 0, 0,    0,  -5,    0,   0},
+      { 0, 0, 0, 0, 0, -2,  2,  0, 0, 0, 0, 0, 1,   12,  55,   29,  -6},
+
+   /* 411-420 */
+      { 0, 1,-1, 1, 0, -2,  1,  0, 0, 0, 0, 0, 0,    0,   5,    3,   0},
+      { 0, 0, 0, 0, 0,  2, -2,  0, 0, 0, 0, 0, 0, -598,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  2, -2,  0, 0, 0, 0, 0, 1,   -3, -13,   -7,   1},
+      { 0, 0, 0, 0, 0,  0,  1,  0, 3, 0, 0, 0, 2,   -5,  -7,   -3,   2},
+      { 0, 0, 0, 0, 0,  0, -5,  7, 0, 0, 0, 0, 2,    3,   0,    0,  -1},
+      { 0, 0, 0, 0, 0,  0,  5, -7, 0, 0, 0, 0, 0,    5,  -7,    0,   0},
+      { 0, 0, 0, 1, 0, -2,  2,  0, 0, 0, 0, 0, 0,    4,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  0,  4, -5, 0, 0, 0, 0, 0,   16,  -6,    0,   0},
+      { 0, 0, 0, 0, 0,  1, -3,  0, 0, 0, 0, 0, 0,    8,  -3,    0,   0},
+      { 0, 0, 0, 0, 0, -1,  3,  0, 0, 0, 0, 0, 1,    8, -31,  -16,  -4},
+
+   /* 421-430 */
+      { 0, 1,-1, 1, 0, -1,  2,  0, 0, 0, 0, 0, 0,    0,   3,    1,   0},
+      { 0, 0, 0, 0, 0, -1,  3,  0, 0, 0, 0, 0, 2,  113,   0,    0, -49},
+      { 0, 0, 0, 0, 0, -7, 10,  0, 0, 0, 0, 0, 2,    0, -24,  -10,   0},
+      { 0, 0, 0, 0, 0, -7, 10,  0, 0, 0, 0, 0, 1,    4,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  0,  3, -3, 0, 0, 0, 0, 0,   27,   0,    0,   0},
+      { 0, 0, 0, 0, 0, -4,  8,  0, 0, 0, 0, 0, 2,   -3,   0,    0,   1},
+      { 0, 0, 0, 0, 0, -4,  5,  0, 0, 0, 0, 0, 2,    0,  -4,   -2,   0},
+      { 0, 0, 0, 0, 0, -4,  5,  0, 0, 0, 0, 0, 1,    5,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  4, -5,  0, 0, 0, 0, 0, 0,    0,  -3,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  1, 0, 0, 0, 0, 2,  -13,   0,    0,   6},
+
+   /* 431-440 */
+      { 0, 0, 0, 0, 0,  0, -2,  0, 5, 0, 0, 0, 2,    5,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  0,  0,  3, 0, 0, 0, 0, 2,  -18, -10,   -4,   8},
+      { 0, 0, 0, 0, 0,  1,  0,  0, 0, 0, 0, 0, 0,   -4, -28,    0,   0},
+      { 0, 0, 0, 0, 0,  1,  0,  0, 0, 0, 0, 0, 2,   -5,   6,    3,   2},
+      { 0, 0, 0, 0, 0, -9, 13,  0, 0, 0, 0, 0, 2,   -3,   0,    0,   1},
+      { 0, 0, 0, 0, 0,  0, -1,  5, 0, 0, 0, 0, 2,   -5,  -9,   -4,   2},
+      { 0, 0, 0, 0, 0,  0, -2,  0, 4, 0, 0, 0, 2,   17,   0,    0,  -7},
+      { 0, 0, 0, 0, 0,  0,  2,  0,-4, 0, 0, 0, 0,   11,   4,    0,   0},
+      { 0, 0, 0, 0, 0,  0, -2,  7, 0, 0, 0, 0, 2,    0,  -6,   -2,   0},
+      { 0, 0, 0, 0, 0,  0,  2,  0,-3, 0, 0, 0, 0,   83,  15,    0,   0},
+
+   /* 441-450 */
+      { 0, 0, 0, 0, 0, -2,  5,  0, 0, 0, 0, 0, 1,   -4,   0,    0,   2},
+      { 0, 0, 0, 0, 0, -2,  5,  0, 0, 0, 0, 0, 2,    0,-114,  -49,   0},
+      { 0, 0, 0, 0, 0, -6,  8,  0, 0, 0, 0, 0, 2,  117,   0,    0, -51},
+      { 0, 0, 0, 0, 0, -6,  8,  0, 0, 0, 0, 0, 1,   -5,  19,   10,   2},
+      { 0, 0, 0, 0, 0,  6, -8,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   0},
+      { 0, 0, 0, 1, 0,  0,  2,  0,-2, 0, 0, 0, 0,   -3,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  0, -3,  9, 0, 0, 0, 0, 2,    0,  -3,   -1,   0},
+      { 0, 0, 0, 0, 0,  0,  5, -6, 0, 0, 0, 0, 0,    3,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  5, -6, 0, 0, 0, 0, 2,    0,  -6,   -2,   0},
+      { 0, 0, 0, 0, 0,  0,  2,  0,-2, 0, 0, 0, 0,  393,   3,    0,   0},
+
+   /* 451-460 */
+      { 0, 0, 0, 0, 0,  0,  2,  0,-2, 0, 0, 0, 1,   -4,  21,   11,   2},
+      { 0, 0, 0, 0, 0,  0,  2,  0,-2, 0, 0, 0, 2,   -6,   0,   -1,   3},
+      { 0, 0, 0, 0, 0, -5, 10,  0, 0, 0, 0, 0, 2,   -3,   8,    4,   1},
+      { 0, 0, 0, 0, 0,  0,  4, -4, 0, 0, 0, 0, 0,    8,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  4, -4, 0, 0, 0, 0, 2,   18, -29,  -13,  -8},
+      { 0, 0, 0, 0, 0, -3,  3,  0, 0, 0, 0, 0, 1,    8,  34,   18,  -4},
+      { 0, 0, 0, 0, 0,  3, -3,  0, 0, 0, 0, 0, 0,   89,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  3, -3,  0, 0, 0, 0, 0, 1,    3,  12,    6,  -1},
+      { 0, 0, 0, 0, 0,  3, -3,  0, 0, 0, 0, 0, 2,   54, -15,   -7, -24},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 0,-3, 0, 0, 0,    0,   3,    0,   0},
+
+   /* 461-470 */
+      { 0, 0, 0, 0, 0,  0, -5, 13, 0, 0, 0, 0, 2,    3,   0,    0,  -1},
+      { 0, 0, 0, 0, 0,  0,  2,  0,-1, 0, 0, 0, 0,    0,  35,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  2,  0,-1, 0, 0, 0, 2, -154, -30,  -13,  67},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 0,-2, 0, 0, 0,   15,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 0,-2, 0, 0, 1,    0,   4,    2,   0},
+      { 0, 0, 0, 0, 0,  0,  3, -2, 0, 0, 0, 0, 0,    0,   9,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  3, -2, 0, 0, 0, 0, 2,   80, -71,  -31, -35},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 0,-1, 0, 0, 2,    0, -20,   -9,   0},
+      { 0, 0, 0, 0, 0,  0, -6, 15, 0, 0, 0, 0, 2,   11,   5,    2,  -5},
+      { 0, 0, 0, 0, 0, -8, 15,  0, 0, 0, 0, 0, 2,   61, -96,  -42, -27},
+
+   /* 471-480 */
+      { 0, 0, 0, 0, 0, -3,  9, -4, 0, 0, 0, 0, 2,   14,   9,    4,  -6},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 2,-5, 0, 0, 2,  -11,  -6,   -3,   5},
+      { 0, 0, 0, 0, 0,  0, -2,  8,-1,-5, 0, 0, 2,    0,  -3,   -1,   0},
+      { 0, 0, 0, 0, 0,  0,  6, -8, 3, 0, 0, 0, 2,  123,-415, -180, -53},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 0, 0, 0, 0, 0,    0,   0,    0, -35},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 0, 0, 0, 0, 0,   -5,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 0, 0, 0, 0, 1,    7, -32,  -17,  -4},
+      { 0, 1,-1, 1, 0,  0,  1,  0, 0, 0, 0, 0, 0,    0,  -9,   -5,   0},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 0, 0, 0, 0, 1,    0,  -4,    2,   0},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 0, 0, 0, 0, 2,  -89,   0,    0,  38},
+
+   /* 481-490 */
+      { 0, 0, 0, 0, 0,  0, -6, 16,-4,-5, 0, 0, 2,    0, -86,  -19,  -6},
+      { 0, 0, 0, 0, 0,  0, -2,  8,-3, 0, 0, 0, 2,    0,   0,  -19,   6},
+      { 0, 0, 0, 0, 0,  0, -2,  8,-3, 0, 0, 0, 2, -123,-416, -180,  53},
+      { 0, 0, 0, 0, 0,  0,  6, -8, 1, 5, 0, 0, 2,    0,  -3,   -1,   0},
+      { 0, 0, 0, 0, 0,  0,  2,  0,-2, 5, 0, 0, 2,   12,  -6,   -3,  -5},
+      { 0, 0, 0, 0, 0,  3, -5,  4, 0, 0, 0, 0, 2,  -13,   9,    4,   6},
+      { 0, 0, 0, 0, 0, -8, 11,  0, 0, 0, 0, 0, 2,    0, -15,   -7,   0},
+      { 0, 0, 0, 0, 0, -8, 11,  0, 0, 0, 0, 0, 1,    3,   0,    0,  -1},
+      { 0, 0, 0, 0, 0, -8, 11,  0, 0, 0, 0, 0, 2,  -62, -97,  -42,  27},
+      { 0, 0, 0, 0, 0,  0, 11,  0, 0, 0, 0, 0, 2,  -11,   5,    2,   5},
+
+   /* 491-500 */
+      { 0, 0, 0, 0, 0,  0,  2,  0, 0, 1, 0, 0, 2,    0, -19,   -8,   0},
+      { 0, 0, 0, 0, 0,  3, -3,  0, 2, 0, 0, 0, 2,   -3,   0,    0,   1},
+      { 0, 2,-2, 1, 0,  0,  4, -8, 3, 0, 0, 0, 0,    0,   4,    2,   0},
+      { 0, 1,-1, 0, 0,  0,  1,  0, 0, 0, 0, 0, 0,    0,   3,    0,   0},
+      { 0, 2,-2, 1, 0,  0, -4,  8,-3, 0, 0, 0, 0,    0,   4,    2,   0},
+      { 0, 0, 0, 0, 0,  0,  1,  2, 0, 0, 0, 0, 2,  -85, -70,  -31,  37},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 1, 0, 0, 0, 2,  163, -12,   -5, -72},
+      { 0, 0, 0, 0, 0, -3,  7,  0, 0, 0, 0, 0, 2,  -63, -16,   -7,  28},
+      { 0, 0, 0, 0, 0,  0,  0,  4, 0, 0, 0, 0, 2,  -21, -32,  -14,   9},
+      { 0, 0, 0, 0, 0, -5,  6,  0, 0, 0, 0, 0, 2,    0,  -3,   -1,   0},
+
+   /* 501-510 */
+      { 0, 0, 0, 0, 0, -5,  6,  0, 0, 0, 0, 0, 1,    3,   0,    0,  -2},
+      { 0, 0, 0, 0, 0,  5, -6,  0, 0, 0, 0, 0, 0,    0,   8,    0,   0},
+      { 0, 0, 0, 0, 0,  5, -6,  0, 0, 0, 0, 0, 2,    3,  10,    4,  -1},
+      { 0, 0, 0, 0, 0,  0,  2,  0, 2, 0, 0, 0, 2,    3,   0,    0,  -1},
+      { 0, 0, 0, 0, 0,  0, -1,  6, 0, 0, 0, 0, 2,    0,  -7,   -3,   0},
+      { 0, 0, 0, 0, 0,  0,  7, -9, 0, 0, 0, 0, 2,    0,  -4,   -2,   0},
+      { 0, 0, 0, 0, 0,  2, -1,  0, 0, 0, 0, 0, 0,    6,  19,    0,   0},
+      { 0, 0, 0, 0, 0,  2, -1,  0, 0, 0, 0, 0, 2,    5,-173,  -75,  -2},
+      { 0, 0, 0, 0, 0,  0,  6, -7, 0, 0, 0, 0, 2,    0,  -7,   -3,   0},
+      { 0, 0, 0, 0, 0,  0,  5, -5, 0, 0, 0, 0, 2,    7, -12,   -5,  -3},
+
+   /* 511-520 */
+      { 0, 0, 0, 0, 0, -1,  4,  0, 0, 0, 0, 0, 1,   -3,   0,    0,   2},
+      { 0, 0, 0, 0, 0, -1,  4,  0, 0, 0, 0, 0, 2,    3,  -4,   -2,  -1},
+      { 0, 0, 0, 0, 0, -7,  9,  0, 0, 0, 0, 0, 2,   74,   0,    0, -32},
+      { 0, 0, 0, 0, 0, -7,  9,  0, 0, 0, 0, 0, 1,   -3,  12,    6,   2},
+      { 0, 0, 0, 0, 0,  0,  4, -3, 0, 0, 0, 0, 2,   26, -14,   -6, -11},
+      { 0, 0, 0, 0, 0,  0,  3, -1, 0, 0, 0, 0, 2,   19,   0,    0,  -8},
+      { 0, 0, 0, 0, 0, -4,  4,  0, 0, 0, 0, 0, 1,    6,  24,   13,  -3},
+      { 0, 0, 0, 0, 0,  4, -4,  0, 0, 0, 0, 0, 0,   83,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  4, -4,  0, 0, 0, 0, 0, 1,    0, -10,   -5,   0},
+      { 0, 0, 0, 0, 0,  4, -4,  0, 0, 0, 0, 0, 2,   11,  -3,   -1,  -5},
+
+   /* 521-530 */
+      { 0, 0, 0, 0, 0,  0,  2,  1, 0, 0, 0, 0, 2,    3,   0,    1,  -1},
+      { 0, 0, 0, 0, 0,  0, -3,  0, 5, 0, 0, 0, 2,    3,   0,    0,  -1},
+      { 0, 0, 0, 0, 0,  1,  1,  0, 0, 0, 0, 0, 0,   -4,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  1,  1,  0, 0, 0, 0, 0, 1,    5, -23,  -12,  -3},
+      { 0, 0, 0, 0, 0,  1,  1,  0, 0, 0, 0, 0, 2, -339,   0,    0, 147},
+      { 0, 0, 0, 0, 0, -9, 12,  0, 0, 0, 0, 0, 2,    0, -10,   -5,   0},
+      { 0, 0, 0, 0, 0,  0,  3,  0,-4, 0, 0, 0, 0,    5,   0,    0,   0},
+      { 0, 2,-2, 1, 0,  1, -1,  0, 0, 0, 0, 0, 0,    3,   0,    0,  -1},
+      { 0, 0, 0, 0, 0,  0,  7, -8, 0, 0, 0, 0, 2,    0,  -4,   -2,   0},
+      { 0, 0, 0, 0, 0,  0,  3,  0,-3, 0, 0, 0, 0,   18,  -3,    0,   0},
+
+   /* 531-540 */
+      { 0, 0, 0, 0, 0,  0,  3,  0,-3, 0, 0, 0, 2,    9, -11,   -5,  -4},
+      { 0, 0, 0, 0, 0, -2,  6,  0, 0, 0, 0, 0, 2,   -8,   0,    0,   4},
+      { 0, 0, 0, 0, 0, -6,  7,  0, 0, 0, 0, 0, 1,    3,   0,    0,  -1},
+      { 0, 0, 0, 0, 0,  6, -7,  0, 0, 0, 0, 0, 0,    0,   9,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  6, -6, 0, 0, 0, 0, 2,    6,  -9,   -4,  -2},
+      { 0, 0, 0, 0, 0,  0,  3,  0,-2, 0, 0, 0, 0,   -4, -12,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  3,  0,-2, 0, 0, 0, 2,   67, -91,  -39, -29},
+      { 0, 0, 0, 0, 0,  0,  5, -4, 0, 0, 0, 0, 2,   30, -18,   -8, -13},
+      { 0, 0, 0, 0, 0,  3, -2,  0, 0, 0, 0, 0, 0,    0,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  3, -2,  0, 0, 0, 0, 0, 2,    0,-114,  -50,   0},
+
+   /* 541-550 */
+      { 0, 0, 0, 0, 0,  0,  3,  0,-1, 0, 0, 0, 2,    0,   0,    0,  23},
+      { 0, 0, 0, 0, 0,  0,  3,  0,-1, 0, 0, 0, 2,  517,  16,    7,-224},
+      { 0, 0, 0, 0, 0,  0,  3,  0, 0,-2, 0, 0, 2,    0,  -7,   -3,   0},
+      { 0, 0, 0, 0, 0,  0,  4, -2, 0, 0, 0, 0, 2,  143,  -3,   -1, -62},
+      { 0, 0, 0, 0, 0,  0,  3,  0, 0,-1, 0, 0, 2,   29,   0,    0, -13},
+      { 0, 2,-2, 1, 0,  0,  1,  0,-1, 0, 0, 0, 0,   -4,   0,    0,   2},
+      { 0, 0, 0, 0, 0, -8, 16,  0, 0, 0, 0, 0, 2,   -6,   0,    0,   3},
+      { 0, 0, 0, 0, 0,  0,  3,  0, 2,-5, 0, 0, 2,    5,  12,    5,  -2},
+      { 0, 0, 0, 0, 0,  0,  7, -8, 3, 0, 0, 0, 2,  -25,   0,    0,  11},
+      { 0, 0, 0, 0, 0,  0, -5, 16,-4,-5, 0, 0, 2,   -3,   0,    0,   1},
+
+   /* 551-560 */
+      { 0, 0, 0, 0, 0,  0,  3,  0, 0, 0, 0, 0, 2,    0,   4,    2,   0},
+      { 0, 0, 0, 0, 0,  0, -1,  8,-3, 0, 0, 0, 2,  -22,  12,    5,  10},
+      { 0, 0, 0, 0, 0, -8, 10,  0, 0, 0, 0, 0, 2,   50,   0,    0, -22},
+      { 0, 0, 0, 0, 0, -8, 10,  0, 0, 0, 0, 0, 1,    0,   7,    4,   0},
+      { 0, 0, 0, 0, 0, -8, 10,  0, 0, 0, 0, 0, 2,    0,   3,    1,   0},
+      { 0, 0, 0, 0, 0,  0,  2,  2, 0, 0, 0, 0, 2,   -4,   4,    2,   2},
+      { 0, 0, 0, 0, 0,  0,  3,  0, 1, 0, 0, 0, 2,   -5, -11,   -5,   2},
+      { 0, 0, 0, 0, 0, -3,  8,  0, 0, 0, 0, 0, 2,    0,   4,    2,   0},
+      { 0, 0, 0, 0, 0, -5,  5,  0, 0, 0, 0, 0, 1,    4,  17,    9,  -2},
+      { 0, 0, 0, 0, 0,  5, -5,  0, 0, 0, 0, 0, 0,   59,   0,    0,   0},
+
+   /* 561-570 */
+      { 0, 0, 0, 0, 0,  5, -5,  0, 0, 0, 0, 0, 1,    0,  -4,   -2,   0},
+      { 0, 0, 0, 0, 0,  5, -5,  0, 0, 0, 0, 0, 2,   -8,   0,    0,   4},
+      { 0, 0, 0, 0, 0,  2,  0,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  2,  0,  0, 0, 0, 0, 0, 1,    4, -15,   -8,  -2},
+      { 0, 0, 0, 0, 0,  2,  0,  0, 0, 0, 0, 0, 2,  370,  -8,    0,-160},
+      { 0, 0, 0, 0, 0,  0,  7, -7, 0, 0, 0, 0, 2,    0,   0,   -3,   0},
+      { 0, 0, 0, 0, 0,  0,  7, -7, 0, 0, 0, 0, 2,    0,   3,    1,   0},
+      { 0, 0, 0, 0, 0,  0,  6, -5, 0, 0, 0, 0, 2,   -6,   3,    1,   3},
+      { 0, 0, 0, 0, 0,  7, -8,  0, 0, 0, 0, 0, 0,    0,   6,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  5, -3, 0, 0, 0, 0, 2,  -10,   0,    0,   4},
+
+   /* 571-580 */
+      { 0, 0, 0, 0, 0,  4, -3,  0, 0, 0, 0, 0, 2,    0,   9,    4,   0},
+      { 0, 0, 0, 0, 0,  1,  2,  0, 0, 0, 0, 0, 2,    4,  17,    7,  -2},
+      { 0, 0, 0, 0, 0, -9, 11,  0, 0, 0, 0, 0, 2,   34,   0,    0, -15},
+      { 0, 0, 0, 0, 0, -9, 11,  0, 0, 0, 0, 0, 1,    0,   5,    3,   0},
+      { 0, 0, 0, 0, 0,  0,  4,  0,-4, 0, 0, 0, 2,   -5,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  0,  4,  0,-3, 0, 0, 0, 2,  -37,  -7,   -3,  16},
+      { 0, 0, 0, 0, 0, -6,  6,  0, 0, 0, 0, 0, 1,    3,  13,    7,  -2},
+      { 0, 0, 0, 0, 0,  6, -6,  0, 0, 0, 0, 0, 0,   40,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  6, -6,  0, 0, 0, 0, 0, 1,    0,  -3,   -2,   0},
+      { 0, 0, 0, 0, 0,  0,  4,  0,-2, 0, 0, 0, 2, -184,  -3,   -1,  80},
+
+   /* 581-590 */
+      { 0, 0, 0, 0, 0,  0,  6, -4, 0, 0, 0, 0, 2,   -3,   0,    0,   1},
+      { 0, 0, 0, 0, 0,  3, -1,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  3, -1,  0, 0, 0, 0, 0, 1,    0, -10,   -6,  -1},
+      { 0, 0, 0, 0, 0,  3, -1,  0, 0, 0, 0, 0, 2,   31,  -6,    0, -13},
+      { 0, 0, 0, 0, 0,  0,  4,  0,-1, 0, 0, 0, 2,   -3, -32,  -14,   1},
+      { 0, 0, 0, 0, 0,  0,  4,  0, 0,-2, 0, 0, 2,   -7,   0,    0,   3},
+      { 0, 0, 0, 0, 0,  0,  5, -2, 0, 0, 0, 0, 2,    0,  -8,   -4,   0},
+      { 0, 0, 0, 0, 0,  0,  4,  0, 0, 0, 0, 0, 0,    3,  -4,    0,   0},
+      { 0, 0, 0, 0, 0,  8, -9,  0, 0, 0, 0, 0, 0,    0,   4,    0,   0},
+      { 0, 0, 0, 0, 0,  5, -4,  0, 0, 0, 0, 0, 2,    0,   3,    1,   0},
+
+   /* 591-600 */
+      { 0, 0, 0, 0, 0,  2,  1,  0, 0, 0, 0, 0, 2,   19, -23,  -10,   2},
+      { 0, 0, 0, 0, 0,  2,  1,  0, 0, 0, 0, 0, 1,    0,   0,    0, -10},
+      { 0, 0, 0, 0, 0,  2,  1,  0, 0, 0, 0, 0, 1,    0,   3,    2,   0},
+      { 0, 0, 0, 0, 0, -7,  7,  0, 0, 0, 0, 0, 1,    0,   9,    5,  -1},
+      { 0, 0, 0, 0, 0,  7, -7,  0, 0, 0, 0, 0, 0,   28,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  4, -2,  0, 0, 0, 0, 0, 1,    0,  -7,   -4,   0},
+      { 0, 0, 0, 0, 0,  4, -2,  0, 0, 0, 0, 0, 2,    8,  -4,    0,  -4},
+      { 0, 0, 0, 0, 0,  4, -2,  0, 0, 0, 0, 0, 0,    0,   0,   -2,   0},
+      { 0, 0, 0, 0, 0,  4, -2,  0, 0, 0, 0, 0, 0,    0,   3,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  5,  0,-4, 0, 0, 0, 2,   -3,   0,    0,   1},
+
+   /* 601-610 */
+      { 0, 0, 0, 0, 0,  0,  5,  0,-3, 0, 0, 0, 2,   -9,   0,    1,   4},
+      { 0, 0, 0, 0, 0,  0,  5,  0,-2, 0, 0, 0, 2,    3,  12,    5,  -1},
+      { 0, 0, 0, 0, 0,  3,  0,  0, 0, 0, 0, 0, 2,   17,  -3,   -1,   0},
+      { 0, 0, 0, 0, 0, -8,  8,  0, 0, 0, 0, 0, 1,    0,   7,    4,   0},
+      { 0, 0, 0, 0, 0,  8, -8,  0, 0, 0, 0, 0, 0,   19,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  5, -3,  0, 0, 0, 0, 0, 1,    0,  -5,   -3,   0},
+      { 0, 0, 0, 0, 0,  5, -3,  0, 0, 0, 0, 0, 2,   14,  -3,    0,  -1},
+      { 0, 0, 0, 0, 0, -9,  9,  0, 0, 0, 0, 0, 1,    0,   0,   -1,   0},
+      { 0, 0, 0, 0, 0, -9,  9,  0, 0, 0, 0, 0, 1,    0,   0,    0,  -5},
+      { 0, 0, 0, 0, 0, -9,  9,  0, 0, 0, 0, 0, 1,    0,   5,    3,   0},
+
+   /* 611-620 */
+      { 0, 0, 0, 0, 0,  9, -9,  0, 0, 0, 0, 0, 0,   13,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  6, -4,  0, 0, 0, 0, 0, 1,    0,  -3,   -2,   0},
+      { 0, 0, 0, 0, 0,  0,  6,  0, 0, 0, 0, 0, 2,    2,   9,    4,   3},
+      { 0, 0, 0, 0, 0,  0,  6,  0, 0, 0, 0, 0, 0,    0,   0,    0,  -4},
+      { 0, 0, 0, 0, 0,  0,  6,  0, 0, 0, 0, 0, 0,    8,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  6,  0, 0, 0, 0, 0, 1,    0,   4,    2,   0},
+      { 0, 0, 0, 0, 0,  0,  6,  0, 0, 0, 0, 0, 2,    6,   0,    0,  -3},
+      { 0, 0, 0, 0, 0,  0,  6,  0, 0, 0, 0, 0, 0,    6,   0,    0,   0},
+      { 0, 0, 0, 0, 0,  0,  6,  0, 0, 0, 0, 0, 1,    0,   3,    1,   0},
+      { 0, 0, 0, 0, 0,  0,  6,  0, 0, 0, 0, 0, 2,    5,   0,    0,  -2},
+
+   /* 621-630 */
+      { 0, 0, 0, 0, 0,  0,  0,  0, 0, 0, 0, 0, 2,    3,   0,    0,  -1},
+      { 1, 0,-2, 0, 0,  0,  2,  0,-2, 0, 0, 0, 0,   -3,   0,    0,   0},
+      { 1, 0,-2, 0, 0,  2, -2,  0, 0, 0, 0, 0, 0,    6,   0,    0,   0},
+      { 1, 0,-2, 0, 0,  0,  1,  0,-1, 0, 0, 0, 0,    7,   0,    0,   0},
+      { 1, 0,-2, 0, 0,  1, -1,  0, 0, 0, 0, 0, 0,   -4,   0,    0,   0},
+      {-1, 0, 0, 0, 0,  3, -3,  0, 0, 0, 0, 0, 0,    4,   0,    0,   0},
+      {-1, 0, 0, 0, 0,  0,  2,  0,-2, 0, 0, 0, 0,    6,   0,    0,   0},
+      {-1, 0, 2, 0, 0,  0,  4, -8, 3, 0, 0, 0, 0,    0,  -4,    0,   0},
+      { 1, 0,-2, 0, 0,  0,  4, -8, 3, 0, 0, 0, 0,    0,  -4,    0,   0},
+      {-2, 0, 2, 0, 0,  0,  4, -8, 3, 0, 0, 0, 0,    5,   0,    0,   0},
+
+   /* 631-640 */
+      {-1, 0, 0, 0, 0,  0,  2,  0,-3, 0, 0, 0, 0,   -3,   0,    0,   0},
+      {-1, 0, 0, 0, 0,  0,  1,  0,-1, 0, 0, 0, 0,    4,   0,    0,   0},
+      {-1, 0, 0, 0, 0,  1, -1,  0, 0, 0, 0, 0, 0,   -5,   0,    0,   0},
+      {-1, 0, 2, 0, 0,  2, -2,  0, 0, 0, 0, 0, 0,    4,   0,    0,   0},
+      { 1,-1, 1, 0, 0,  0,  1,  0, 0, 0, 0, 0, 0,    0,   3,    0,   0},
+      {-1, 0, 2, 0, 0,  0,  2,  0,-3, 0, 0, 0, 0,   13,   0,    0,   0},
+      {-2, 0, 0, 0, 0,  0,  2,  0,-3, 0, 0, 0, 0,   21,  11,    0,   0},
+      { 1, 0, 0, 0, 0,  0,  4, -8, 3, 0, 0, 0, 0,    0,  -5,    0,   0},
+      {-1, 1,-1, 1, 0,  0, -1,  0, 0, 0, 0, 0, 0,    0,  -5,   -2,   0},
+      { 1, 1,-1, 1, 0,  0, -1,  0, 0, 0, 0, 0, 0,    0,   5,    3,   0},
+
+   /* 641-650 */
+      {-1, 0, 0, 0, 0,  0,  4, -8, 3, 0, 0, 0, 0,    0,  -5,    0,   0},
+      {-1, 0, 2, 1, 0,  0,  2,  0,-2, 0, 0, 0, 0,   -3,   0,    0,   2},
+      { 0, 0, 0, 0, 0,  0,  2,  0,-2, 0, 0, 0, 0,   20,  10,    0,   0},
+      {-1, 0, 2, 0, 0,  0,  2,  0,-2, 0, 0, 0, 0,  -34,   0,    0,   0},
+      {-1, 0, 2, 0, 0,  3, -3,  0, 0, 0, 0, 0, 0,  -19,   0,    0,   0},
+      { 1, 0,-2, 1, 0,  0, -2,  0, 2, 0, 0, 0, 0,    3,   0,    0,  -2},
+      { 1, 2,-2, 2, 0, -3,  3,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   1},
+      { 1, 2,-2, 2, 0,  0, -2,  0, 2, 0, 0, 0, 0,   -6,   0,    0,   3},
+      { 1, 0, 0, 0, 0,  1, -1,  0, 0, 0, 0, 0, 0,   -4,   0,    0,   0},
+      { 1, 0, 0, 0, 0,  0,  1,  0,-1, 0, 0, 0, 0,    3,   0,    0,   0},
+
+   /* 651-660 */
+      { 0, 0,-2, 0, 0,  2, -2,  0, 0, 0, 0, 0, 0,    3,   0,    0,   0},
+      { 0, 0,-2, 0, 0,  0,  1,  0,-1, 0, 0, 0, 0,    4,   0,    0,   0},
+      { 0, 2, 0, 2, 0, -2,  2,  0, 0, 0, 0, 0, 0,    3,   0,    0,  -1},
+      { 0, 2, 0, 2, 0,  0, -1,  0, 1, 0, 0, 0, 0,    6,   0,    0,  -3},
+      { 0, 2, 0, 2, 0, -1,  1,  0, 0, 0, 0, 0, 0,   -8,   0,    0,   3},
+      { 0, 2, 0, 2, 0, -2,  3,  0, 0, 0, 0, 0, 0,    0,   3,    1,   0},
+      { 0, 0, 2, 0, 0,  0,  2,  0,-2, 0, 0, 0, 0,   -3,   0,    0,   0},
+      { 0, 1, 1, 2, 0,  0,  1,  0, 0, 0, 0, 0, 0,    0,  -3,   -2,   0},
+      { 1, 2, 0, 2, 0,  0,  1,  0, 0, 0, 0, 0, 0,  126, -63,  -27, -55},
+      {-1, 2, 0, 2, 0, 10, -3,  0, 0, 0, 0, 0, 0,   -5,   0,    1,   2},
+
+   /* 661-670 */
+      { 0, 1, 1, 1, 0,  0,  1,  0, 0, 0, 0, 0, 0,   -3,  28,   15,   2},
+      { 1, 2, 0, 2, 0,  0,  1,  0, 0, 0, 0, 0, 0,    5,   0,    1,  -2},
+      { 0, 2, 0, 2, 0,  0,  4, -8, 3, 0, 0, 0, 0,    0,   9,    4,   1},
+      { 0, 2, 0, 2, 0,  0, -4,  8,-3, 0, 0, 0, 0,    0,   9,    4,  -1},
+      {-1, 2, 0, 2, 0,  0, -4,  8,-3, 0, 0, 0, 0, -126, -63,  -27,  55},
+      { 2, 2,-2, 2, 0,  0, -2,  0, 3, 0, 0, 0, 0,    3,   0,    0,  -1},
+      { 1, 2, 0, 1, 0,  0, -2,  0, 3, 0, 0, 0, 0,   21, -11,   -6, -11},
+      { 0, 1, 1, 0, 0,  0,  1,  0, 0, 0, 0, 0, 0,    0,  -4,    0,   0},
+      {-1, 2, 0, 1, 0,  0,  1,  0, 0, 0, 0, 0, 0,  -21, -11,   -6,  11},
+      {-2, 2, 2, 2, 0,  0,  2,  0,-2, 0, 0, 0, 0,   -3,   0,    0,   1},
+
+   /* 671-680 */
+      { 0, 2, 0, 2, 0,  2, -3,  0, 0, 0, 0, 0, 0,    0,   3,    1,   0},
+      { 0, 2, 0, 2, 0,  1, -1,  0, 0, 0, 0, 0, 0,    8,   0,    0,  -4},
+      { 0, 2, 0, 2, 0,  0,  1,  0,-1, 0, 0, 0, 0,   -6,   0,    0,   3},
+      { 0, 2, 0, 2, 0,  2, -2,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   1},
+      {-1, 2, 2, 2, 0,  0, -1,  0, 1, 0, 0, 0, 0,    3,   0,    0,  -1},
+      { 1, 2, 0, 2, 0, -1,  1,  0, 0, 0, 0, 0, 0,   -3,   0,    0,   1},
+      {-1, 2, 2, 2, 0,  0,  2,  0,-3, 0, 0, 0, 0,   -5,   0,    0,   2},
+      { 2, 2, 0, 2, 0,  0,  2,  0,-3, 0, 0, 0, 0,   24, -12,   -5, -11},
+      { 1, 2, 0, 2, 0,  0, -4,  8,-3, 0, 0, 0, 0,    0,   3,    1,   0},
+      { 1, 2, 0, 2, 0,  0,  4, -8, 3, 0, 0, 0, 0,    0,   3,    1,   0},
+
+   /* 681-687 */
+      { 1, 1, 1, 1, 0,  0,  1,  0, 0, 0, 0, 0, 0,    0,   3,    2,   0},
+      { 0, 2, 0, 2, 0,  0,  1,  0, 0, 0, 0, 0, 0,  -24, -12,   -5,  10},
+      { 2, 2, 0, 1, 0,  0,  1,  0, 0, 0, 0, 0, 0,    4,   0,   -1,  -2},
+      {-1, 2, 2, 2, 0,  0,  2,  0,-2, 0, 0, 0, 0,   13,   0,    0,  -6},
+      {-1, 2, 2, 2, 0,  3, -3,  0, 0, 0, 0, 0, 0,    7,   0,    0,  -3},
+      { 1, 2, 0, 2, 0,  1, -1,  0, 0, 0, 0, 0, 0,    3,   0,    0,  -1},
+      { 0, 2, 2, 2, 0,  0,  2,  0,-2, 0, 0, 0, 0,    3,   0,    0,  -1}
+   };
+
+/* Number of terms in the planetary nutation model */
+   const int NPL = (int) (sizeof xpl / sizeof xpl[0]);
+
+/*--------------------------------------------------------------------*/
+
+/* Interval between fundamental date J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* ------------------- */
+/* LUNI-SOLAR NUTATION */
+/* ------------------- */
+
+/* Fundamental (Delaunay) arguments */
+
+/* Mean anomaly of the Moon (IERS 2003). */
+   el = eraFal03(t);
+
+/* Mean anomaly of the Sun (MHB2000). */
+   elp = fmod(1287104.79305  +
+            t * (129596581.0481  +
+            t * (-0.5532  +
+            t * (0.000136  +
+            t * (-0.00001149)))), ERFA_TURNAS) * ERFA_DAS2R;
+
+/* Mean longitude of the Moon minus that of the ascending node */
+/* (IERS 2003. */
+   f = eraFaf03(t);
+
+/* Mean elongation of the Moon from the Sun (MHB2000). */
+   d = fmod(1072260.70369  +
+          t * (1602961601.2090  +
+          t * (-6.3706  +
+          t * (0.006593  +
+          t * (-0.00003169)))), ERFA_TURNAS) * ERFA_DAS2R;
+
+/* Mean longitude of the ascending node of the Moon (IERS 2003). */
+   om = eraFaom03(t);
+
+/* Initialize the nutation values. */
+   dp = 0.0;
+   de = 0.0;
+
+/* Summation of luni-solar nutation series (in reverse order). */
+   for (i = NLS-1; i >= 0; i--) {
+
+   /* Argument and functions. */
+      arg = fmod((double)xls[i].nl  * el +
+                 (double)xls[i].nlp * elp +
+                 (double)xls[i].nf  * f +
+                 (double)xls[i].nd  * d +
+                 (double)xls[i].nom * om, ERFA_D2PI);
+      sarg = sin(arg);
+      carg = cos(arg);
+
+   /* Term. */
+      dp += (xls[i].sp + xls[i].spt * t) * sarg + xls[i].cp * carg;
+      de += (xls[i].ce + xls[i].cet * t) * carg + xls[i].se * sarg;
+   }
+
+/* Convert from 0.1 microarcsec units to radians. */
+   dpsils = dp * U2R;
+   depsls = de * U2R;
+
+/* ------------------ */
+/* PLANETARY NUTATION */
+/* ------------------ */
+
+/* n.b.  The MHB2000 code computes the luni-solar and planetary nutation */
+/* in different functions, using slightly different Delaunay */
+/* arguments in the two cases.  This behaviour is faithfully */
+/* reproduced here.  Use of the IERS 2003 expressions for both */
+/* cases leads to negligible changes, well below */
+/* 0.1 microarcsecond. */
+
+/* Mean anomaly of the Moon (MHB2000). */
+   al = fmod(2.35555598 + 8328.6914269554 * t, ERFA_D2PI);
+
+/* Mean longitude of the Moon minus that of the ascending node */
+/*(MHB2000). */
+   af = fmod(1.627905234 + 8433.466158131 * t, ERFA_D2PI);
+
+/* Mean elongation of the Moon from the Sun (MHB2000). */
+   ad = fmod(5.198466741 + 7771.3771468121 * t, ERFA_D2PI);
+
+/* Mean longitude of the ascending node of the Moon (MHB2000). */
+   aom = fmod(2.18243920 - 33.757045 * t, ERFA_D2PI);
+
+/* General accumulated precession in longitude (IERS 2003). */
+   apa = eraFapa03(t);
+
+/* Planetary longitudes, Mercury through Uranus (IERS 2003). */
+   alme = eraFame03(t);
+   alve = eraFave03(t);
+   alea = eraFae03(t);
+   alma = eraFama03(t);
+   alju = eraFaju03(t);
+   alsa = eraFasa03(t);
+   alur = eraFaur03(t);
+
+/* Neptune longitude (MHB2000). */
+   alne = fmod(5.321159000 + 3.8127774000 * t, ERFA_D2PI);
+
+/* Initialize the nutation values. */
+   dp = 0.0;
+   de = 0.0;
+
+/* Summation of planetary nutation series (in reverse order). */
+   for (i = NPL-1; i >= 0; i--) {
+
+   /* Argument and functions. */
+      arg = fmod((double)xpl[i].nl  * al   +
+                 (double)xpl[i].nf  * af   +
+                 (double)xpl[i].nd  * ad   +
+                 (double)xpl[i].nom * aom  +
+                 (double)xpl[i].nme * alme +
+                 (double)xpl[i].nve * alve +
+                 (double)xpl[i].nea * alea +
+                 (double)xpl[i].nma * alma +
+                 (double)xpl[i].nju * alju +
+                 (double)xpl[i].nsa * alsa +
+                 (double)xpl[i].nur * alur +
+                 (double)xpl[i].nne * alne +
+                 (double)xpl[i].npa * apa, ERFA_D2PI);
+      sarg = sin(arg);
+      carg = cos(arg);
+
+   /* Term. */
+      dp += (double)xpl[i].sp * sarg + (double)xpl[i].cp * carg;
+      de += (double)xpl[i].se * sarg + (double)xpl[i].ce * carg;
+
+   }
+
+/* Convert from 0.1 microarcsec units to radians. */
+   dpsipl = dp * U2R;
+   depspl = de * U2R;
+
+/* ------- */
+/* RESULTS */
+/* ------- */
+
+/* Add luni-solar and planetary components. */
+   *dpsi = dpsils + dpsipl;
+   *deps = depsls + depspl;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/nut00b.c b/ast/erfa/nut00b.c
new file mode 100644
index 0000000..775613f
--- /dev/null
+++ b/ast/erfa/nut00b.c
@@ -0,0 +1,381 @@
+#include "erfa.h"
+
+void eraNut00b(double date1, double date2, double *dpsi, double *deps)
+/*
+**  - - - - - - - - - -
+**   e r a N u t 0 0 b
+**  - - - - - - - - - -
+**
+**  Nutation, IAU 2000B model.
+**
+**  Given:
+**     date1,date2   double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     dpsi,deps     double    nutation, luni-solar + planetary (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The nutation components in longitude and obliquity are in radians
+**     and with respect to the equinox and ecliptic of date.  The
+**     obliquity at J2000.0 is assumed to be the Lieske et al. (1977)
+**     value of 84381.448 arcsec.  (The errors that result from using
+**     this function with the IAU 2006 value of 84381.406 arcsec can be
+**     neglected.)
+**
+**     The nutation model consists only of luni-solar terms, but
+**     includes also a fixed offset which compensates for certain long-
+**     period planetary terms (Note 7).
+**
+**  3) This function is an implementation of the IAU 2000B abridged
+**     nutation model formally adopted by the IAU General Assembly in
+**     2000.  The function computes the MHB_2000_SHORT luni-solar
+**     nutation series (Luzum 2001), but without the associated
+**     corrections for the precession rate adjustments and the offset
+**     between the GCRS and J2000.0 mean poles.
+**
+**  4) The full IAU 2000A (MHB2000) nutation model contains nearly 1400
+**     terms.  The IAU 2000B model (McCarthy & Luzum 2003) contains only
+**     77 terms, plus additional simplifications, yet still delivers
+**     results of 1 mas accuracy at present epochs.  This combination of
+**     accuracy and size makes the IAU 2000B abridged nutation model
+**     suitable for most practical applications.
+**
+**     The function delivers a pole accurate to 1 mas from 1900 to 2100
+**     (usually better than 1 mas, very occasionally just outside
+**     1 mas).  The full IAU 2000A model, which is implemented in the
+**     function eraNut00a (q.v.), delivers considerably greater accuracy
+**     at current dates;  however, to realize this improved accuracy,
+**     corrections for the essentially unpredictable free-core-nutation
+**     (FCN) must also be included.
+**
+**  5) The present function provides classical nutation.  The
+**     MHB_2000_SHORT algorithm, from which it is adapted, deals also
+**     with (i) the offsets between the GCRS and mean poles and (ii) the
+**     adjustments in longitude and obliquity due to the changed
+**     precession rates.  These additional functions, namely frame bias
+**     and precession adjustments, are supported by the ERFA functions
+**     eraBi00  and eraPr00.
+**
+**  6) The MHB_2000_SHORT algorithm also provides "total" nutations,
+**     comprising the arithmetic sum of the frame bias, precession
+**     adjustments, and nutation (luni-solar + planetary).  These total
+**     nutations can be used in combination with an existing IAU 1976
+**     precession implementation, such as eraPmat76,  to deliver GCRS-
+**     to-true predictions of mas accuracy at current epochs.  However,
+**     for symmetry with the eraNut00a  function (q.v. for the reasons),
+**     the ERFA functions do not generate the "total nutations"
+**     directly.  Should they be required, they could of course easily
+**     be generated by calling eraBi00, eraPr00 and the present function
+**     and adding the results.
+**
+**  7) The IAU 2000B model includes "planetary bias" terms that are
+**     fixed in size but compensate for long-period nutations.  The
+**     amplitudes quoted in McCarthy & Luzum (2003), namely
+**     Dpsi = -1.5835 mas and Depsilon = +1.6339 mas, are optimized for
+**     the "total nutations" method described in Note 6.  The Luzum
+**     (2001) values used in this ERFA implementation, namely -0.135 mas
+**     and +0.388 mas, are optimized for the "rigorous" method, where
+**     frame bias, precession and nutation are applied separately and in
+**     that order.  During the interval 1995-2050, the ERFA
+**     implementation delivers a maximum error of 1.001 mas (not
+**     including FCN).
+**
+**  References:
+**
+**     Lieske, J.H., Lederle, T., Fricke, W., Morando, B., "Expressions
+**     for the precession quantities based upon the IAU /1976/ system of
+**     astronomical constants", Astron.Astrophys. 58, 1-2, 1-16. (1977)
+**
+**     Luzum, B., private communication, 2001 (Fortran code
+**     MHB_2000_SHORT)
+**
+**     McCarthy, D.D. & Luzum, B.J., "An abridged model of the
+**     precession-nutation of the celestial pole", Cel.Mech.Dyn.Astron.
+**     85, 37-49 (2003)
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J., Astron.Astrophys. 282, 663-683 (1994)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, el, elp, f, d, om, arg, dp, de, sarg, carg,
+          dpsils, depsls, dpsipl, depspl;
+   int i;
+
+/* Units of 0.1 microarcsecond to radians */
+   static const double U2R = ERFA_DAS2R / 1e7;
+
+/* ---------------------------------------- */
+/* Fixed offsets in lieu of planetary terms */
+/* ---------------------------------------- */
+
+   static const double DPPLAN = -0.135 * ERFA_DMAS2R;
+   static const double DEPLAN =  0.388 * ERFA_DMAS2R;
+
+/* --------------------------------------------------- */
+/* Luni-solar nutation: argument and term coefficients */
+/* --------------------------------------------------- */
+
+/* The units for the sine and cosine coefficients are */
+/* 0.1 microarcsec and the same per Julian century    */
+
+   static const struct {
+      int nl,nlp,nf,nd,nom; /* coefficients of l,l',F,D,Om */
+      double ps,pst,pc;     /* longitude sin, t*sin, cos coefficients */
+      double ec,ect,es;     /* obliquity cos, t*cos, sin coefficients */
+
+   } x[] = {
+
+   /* 1-10 */
+      { 0, 0, 0, 0,1,
+         -172064161.0, -174666.0, 33386.0, 92052331.0, 9086.0, 15377.0},
+      { 0, 0, 2,-2,2,
+           -13170906.0, -1675.0, -13696.0, 5730336.0, -3015.0, -4587.0},
+      { 0, 0, 2, 0,2,-2276413.0,-234.0, 2796.0, 978459.0,-485.0,1374.0},
+      { 0, 0, 0, 0,2,2074554.0,  207.0, -698.0,-897492.0, 470.0,-291.0},
+      { 0, 1, 0, 0,0,1475877.0,-3633.0,11817.0, 73871.0,-184.0,-1924.0},
+      { 0, 1, 2,-2,2,-516821.0, 1226.0, -524.0, 224386.0,-677.0,-174.0},
+      { 1, 0, 0, 0,0, 711159.0,   73.0, -872.0,  -6750.0,   0.0, 358.0},
+      { 0, 0, 2, 0,1,-387298.0, -367.0,  380.0, 200728.0,  18.0, 318.0},
+      { 1, 0, 2, 0,2,-301461.0,  -36.0,  816.0, 129025.0, -63.0, 367.0},
+      { 0,-1, 2,-2,2, 215829.0, -494.0,  111.0, -95929.0, 299.0, 132.0},
+
+   /* 11-20 */
+      { 0, 0, 2,-2,1, 128227.0,  137.0,  181.0, -68982.0,  -9.0,  39.0},
+      {-1, 0, 2, 0,2, 123457.0,   11.0,   19.0, -53311.0,  32.0,  -4.0},
+      {-1, 0, 0, 2,0, 156994.0,   10.0, -168.0,  -1235.0,   0.0,  82.0},
+      { 1, 0, 0, 0,1,  63110.0,   63.0,   27.0, -33228.0,   0.0,  -9.0},
+      {-1, 0, 0, 0,1, -57976.0,  -63.0, -189.0,  31429.0,   0.0, -75.0},
+      {-1, 0, 2, 2,2, -59641.0,  -11.0,  149.0,  25543.0, -11.0,  66.0},
+      { 1, 0, 2, 0,1, -51613.0,  -42.0,  129.0,  26366.0,   0.0,  78.0},
+      {-2, 0, 2, 0,1,  45893.0,   50.0,   31.0, -24236.0, -10.0,  20.0},
+      { 0, 0, 0, 2,0,  63384.0,   11.0, -150.0,  -1220.0,   0.0,  29.0},
+      { 0, 0, 2, 2,2, -38571.0,   -1.0,  158.0,  16452.0, -11.0,  68.0},
+
+   /* 21-30 */
+      { 0,-2, 2,-2,2,  32481.0,    0.0,    0.0, -13870.0,   0.0,   0.0},
+      {-2, 0, 0, 2,0, -47722.0,    0.0,  -18.0,    477.0,   0.0, -25.0},
+      { 2, 0, 2, 0,2, -31046.0,   -1.0,  131.0,  13238.0, -11.0,  59.0},
+      { 1, 0, 2,-2,2,  28593.0,    0.0,   -1.0, -12338.0,  10.0,  -3.0},
+      {-1, 0, 2, 0,1,  20441.0,   21.0,   10.0, -10758.0,   0.0,  -3.0},
+      { 2, 0, 0, 0,0,  29243.0,    0.0,  -74.0,   -609.0,   0.0,  13.0},
+      { 0, 0, 2, 0,0,  25887.0,    0.0,  -66.0,   -550.0,   0.0,  11.0},
+      { 0, 1, 0, 0,1, -14053.0,  -25.0,   79.0,   8551.0,  -2.0, -45.0},
+      {-1, 0, 0, 2,1,  15164.0,   10.0,   11.0,  -8001.0,   0.0,  -1.0},
+      { 0, 2, 2,-2,2, -15794.0,   72.0,  -16.0,   6850.0, -42.0,  -5.0},
+
+   /* 31-40 */
+      { 0, 0,-2, 2,0,  21783.0,    0.0,   13.0,   -167.0,   0.0,  13.0},
+      { 1, 0, 0,-2,1, -12873.0,  -10.0,  -37.0,   6953.0,   0.0, -14.0},
+      { 0,-1, 0, 0,1, -12654.0,   11.0,   63.0,   6415.0,   0.0,  26.0},
+      {-1, 0, 2, 2,1, -10204.0,    0.0,   25.0,   5222.0,   0.0,  15.0},
+      { 0, 2, 0, 0,0,  16707.0,  -85.0,  -10.0,    168.0,  -1.0,  10.0},
+      { 1, 0, 2, 2,2,  -7691.0,    0.0,   44.0,   3268.0,   0.0,  19.0},
+      {-2, 0, 2, 0,0, -11024.0,    0.0,  -14.0,    104.0,   0.0,   2.0},
+      { 0, 1, 2, 0,2,   7566.0,  -21.0,  -11.0,  -3250.0,   0.0,  -5.0},
+      { 0, 0, 2, 2,1,  -6637.0,  -11.0,   25.0,   3353.0,   0.0,  14.0},
+      { 0,-1, 2, 0,2,  -7141.0,   21.0,    8.0,   3070.0,   0.0,   4.0},
+
+   /* 41-50 */
+      { 0, 0, 0, 2,1,  -6302.0,  -11.0,    2.0,   3272.0,   0.0,   4.0},
+      { 1, 0, 2,-2,1,   5800.0,   10.0,    2.0,  -3045.0,   0.0,  -1.0},
+      { 2, 0, 2,-2,2,   6443.0,    0.0,   -7.0,  -2768.0,   0.0,  -4.0},
+      {-2, 0, 0, 2,1,  -5774.0,  -11.0,  -15.0,   3041.0,   0.0,  -5.0},
+      { 2, 0, 2, 0,1,  -5350.0,    0.0,   21.0,   2695.0,   0.0,  12.0},
+      { 0,-1, 2,-2,1,  -4752.0,  -11.0,   -3.0,   2719.0,   0.0,  -3.0},
+      { 0, 0, 0,-2,1,  -4940.0,  -11.0,  -21.0,   2720.0,   0.0,  -9.0},
+      {-1,-1, 0, 2,0,   7350.0,    0.0,   -8.0,    -51.0,   0.0,   4.0},
+      { 2, 0, 0,-2,1,   4065.0,    0.0,    6.0,  -2206.0,   0.0,   1.0},
+      { 1, 0, 0, 2,0,   6579.0,    0.0,  -24.0,   -199.0,   0.0,   2.0},
+
+   /* 51-60 */
+      { 0, 1, 2,-2,1,   3579.0,    0.0,    5.0,  -1900.0,   0.0,   1.0},
+      { 1,-1, 0, 0,0,   4725.0,    0.0,   -6.0,    -41.0,   0.0,   3.0},
+      {-2, 0, 2, 0,2,  -3075.0,    0.0,   -2.0,   1313.0,   0.0,  -1.0},
+      { 3, 0, 2, 0,2,  -2904.0,    0.0,   15.0,   1233.0,   0.0,   7.0},
+      { 0,-1, 0, 2,0,   4348.0,    0.0,  -10.0,    -81.0,   0.0,   2.0},
+      { 1,-1, 2, 0,2,  -2878.0,    0.0,    8.0,   1232.0,   0.0,   4.0},
+      { 0, 0, 0, 1,0,  -4230.0,    0.0,    5.0,    -20.0,   0.0,  -2.0},
+      {-1,-1, 2, 2,2,  -2819.0,    0.0,    7.0,   1207.0,   0.0,   3.0},
+      {-1, 0, 2, 0,0,  -4056.0,    0.0,    5.0,     40.0,   0.0,  -2.0},
+      { 0,-1, 2, 2,2,  -2647.0,    0.0,   11.0,   1129.0,   0.0,   5.0},
+
+   /* 61-70 */
+      {-2, 0, 0, 0,1,  -2294.0,    0.0,  -10.0,   1266.0,   0.0,  -4.0},
+      { 1, 1, 2, 0,2,   2481.0,    0.0,   -7.0,  -1062.0,   0.0,  -3.0},
+      { 2, 0, 0, 0,1,   2179.0,    0.0,   -2.0,  -1129.0,   0.0,  -2.0},
+      {-1, 1, 0, 1,0,   3276.0,    0.0,    1.0,     -9.0,   0.0,   0.0},
+      { 1, 1, 0, 0,0,  -3389.0,    0.0,    5.0,     35.0,   0.0,  -2.0},
+      { 1, 0, 2, 0,0,   3339.0,    0.0,  -13.0,   -107.0,   0.0,   1.0},
+      {-1, 0, 2,-2,1,  -1987.0,    0.0,   -6.0,   1073.0,   0.0,  -2.0},
+      { 1, 0, 0, 0,2,  -1981.0,    0.0,    0.0,    854.0,   0.0,   0.0},
+      {-1, 0, 0, 1,0,   4026.0,    0.0, -353.0,   -553.0,   0.0,-139.0},
+      { 0, 0, 2, 1,2,   1660.0,    0.0,   -5.0,   -710.0,   0.0,  -2.0},
+
+   /* 71-77 */
+      {-1, 0, 2, 4,2,  -1521.0,    0.0,    9.0,    647.0,   0.0,   4.0},
+      {-1, 1, 0, 1,1,   1314.0,    0.0,    0.0,   -700.0,   0.0,   0.0},
+      { 0,-2, 2,-2,1,  -1283.0,    0.0,    0.0,    672.0,   0.0,   0.0},
+      { 1, 0, 2, 2,1,  -1331.0,    0.0,    8.0,    663.0,   0.0,   4.0},
+      {-2, 0, 2, 2,2,   1383.0,    0.0,   -2.0,   -594.0,   0.0,  -2.0},
+      {-1, 0, 0, 0,2,   1405.0,    0.0,    4.0,   -610.0,   0.0,   2.0},
+      { 1, 1, 2,-2,2,   1290.0,    0.0,    0.0,   -556.0,   0.0,   0.0}
+   };
+
+/* Number of terms in the series */
+   const int NLS = (int) (sizeof x / sizeof x[0]);
+
+/*--------------------------------------------------------------------*/
+
+/* Interval between fundamental epoch J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* --------------------*/
+/* LUNI-SOLAR NUTATION */
+/* --------------------*/
+
+/* Fundamental (Delaunay) arguments from Simon et al. (1994) */
+
+/* Mean anomaly of the Moon. */
+   el = fmod(485868.249036 + (1717915923.2178) * t, ERFA_TURNAS) * ERFA_DAS2R;
+
+/* Mean anomaly of the Sun. */
+   elp = fmod(1287104.79305 + (129596581.0481) * t, ERFA_TURNAS) * ERFA_DAS2R;
+
+/* Mean argument of the latitude of the Moon. */
+   f = fmod(335779.526232 + (1739527262.8478) * t, ERFA_TURNAS) * ERFA_DAS2R;
+
+/* Mean elongation of the Moon from the Sun. */
+   d = fmod(1072260.70369 + (1602961601.2090) * t, ERFA_TURNAS) * ERFA_DAS2R;
+
+/* Mean longitude of the ascending node of the Moon. */
+   om = fmod(450160.398036 + (-6962890.5431) * t, ERFA_TURNAS) * ERFA_DAS2R;
+
+/* Initialize the nutation values. */
+   dp = 0.0;
+   de = 0.0;
+
+/* Summation of luni-solar nutation series (smallest terms first). */
+   for (i = NLS-1; i >= 0; i--) {
+
+   /* Argument and functions. */
+      arg = fmod( (double)x[i].nl  * el  +
+                  (double)x[i].nlp * elp +
+                  (double)x[i].nf  * f   +
+                  (double)x[i].nd  * d   +
+                  (double)x[i].nom * om, ERFA_D2PI  );
+      sarg = sin(arg);
+      carg = cos(arg);
+
+   /* Term. */
+      dp += (x[i].ps + x[i].pst * t) * sarg + x[i].pc * carg;
+      de += (x[i].ec + x[i].ect * t) * carg + x[i].es * sarg;
+   }
+
+/* Convert from 0.1 microarcsec units to radians. */
+   dpsils = dp * U2R;
+   depsls = de * U2R;
+
+/* ------------------------------*/
+/* IN LIEU OF PLANETARY NUTATION */
+/* ------------------------------*/
+
+/* Fixed offset to correct for missing terms in truncated series. */
+   dpsipl = DPPLAN;
+   depspl = DEPLAN;
+
+/* --------*/
+/* RESULTS */
+/* --------*/
+
+/* Add luni-solar and planetary components. */
+   *dpsi = dpsils + dpsipl;
+   *deps = depsls + depspl;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/nut06a.c b/ast/erfa/nut06a.c
new file mode 100644
index 0000000..c4d945e
--- /dev/null
+++ b/ast/erfa/nut06a.c
@@ -0,0 +1,162 @@
+#include "erfa.h"
+
+void eraNut06a(double date1, double date2, double *dpsi, double *deps)
+/*
+**  - - - - - - - - - -
+**   e r a N u t 0 6 a
+**  - - - - - - - - - -
+**
+**  IAU 2000A nutation with adjustments to match the IAU 2006
+**  precession.
+**
+**  Given:
+**     date1,date2   double   TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     dpsi,deps     double   nutation, luni-solar + planetary (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The nutation components in longitude and obliquity are in radians
+**     and with respect to the mean equinox and ecliptic of date,
+**     IAU 2006 precession model (Hilton et al. 2006, Capitaine et al.
+**     2005).
+**
+**  3) The function first computes the IAU 2000A nutation, then applies
+**     adjustments for (i) the consequences of the change in obliquity
+**     from the IAU 1980 ecliptic to the IAU 2006 ecliptic and (ii) the
+**     secular variation in the Earth's dynamical form factor J2.
+**
+**  4) The present function provides classical nutation, complementing
+**     the IAU 2000 frame bias and IAU 2006 precession.  It delivers a
+**     pole which is at current epochs accurate to a few tens of
+**     microarcseconds, apart from the free core nutation.
+**
+**  Called:
+**     eraNut00a    nutation, IAU 2000A
+**
+**  References:
+**
+**     Chapront, J., Chapront-Touze, M. & Francou, G. 2002,
+**     Astron.Astrophys. 387, 700
+**
+**     Lieske, J.H., Lederle, T., Fricke, W. & Morando, B. 1977,
+**     Astron.Astrophys. 58, 1-16
+**
+**     Mathews, P.M., Herring, T.A., Buffet, B.A. 2002, J.Geophys.Res.
+**     107, B4.  The MHB_2000 code itself was obtained on 9th September
+**     2002 from ftp//maia.usno.navy.mil/conv2000/chapter5/IAU2000A.
+**
+**     Simon, J.-L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G., Laskar, J. 1994, Astron.Astrophys. 282, 663-683
+**
+**     Souchay, J., Loysel, B., Kinoshita, H., Folgueira, M. 1999,
+**     Astron.Astrophys.Supp.Ser. 135, 111
+**
+**     Wallace, P.T., "Software for Implementing the IAU 2000
+**     Resolutions", in IERS Workshop 5.1 (2002)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, fj2, dp, de;
+
+
+/* Interval between fundamental date J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Factor correcting for secular variation of J2. */
+   fj2 = -2.7774e-6 * t;
+
+/* Obtain IAU 2000A nutation. */
+   eraNut00a(date1, date2, &dp, &de);
+
+/* Apply P03 adjustments (Wallace & Capitaine, 2006, Eqs.5). */
+   *dpsi = dp + dp * (0.4697e-6 + fj2);
+   *deps = de + de * fj2;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/nut80.c b/ast/erfa/nut80.c
new file mode 100644
index 0000000..5ffb23c
--- /dev/null
+++ b/ast/erfa/nut80.c
@@ -0,0 +1,334 @@
+#include "erfa.h"
+
+void eraNut80(double date1, double date2, double *dpsi, double *deps)
+/*
+**  - - - - - - - - -
+**   e r a N u t 8 0
+**  - - - - - - - - -
+**
+**  Nutation, IAU 1980 model.
+**
+**  Given:
+**     date1,date2   double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     dpsi          double    nutation in longitude (radians)
+**     deps          double    nutation in obliquity (radians)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The nutation components are with respect to the ecliptic of
+**     date.
+**
+**  Called:
+**     eraAnpm      normalize angle into range +/- pi
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Section 3.222 (p111).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, el, elp, f, d, om, dp, de, arg, s, c;
+   int j;
+
+/* Units of 0.1 milliarcsecond to radians */
+   const double U2R = ERFA_DAS2R / 1e4;
+
+/* ------------------------------------------------ */
+/* Table of multiples of arguments and coefficients */
+/* ------------------------------------------------ */
+
+/* The units for the sine and cosine coefficients are 0.1 mas and */
+/* the same per Julian century */
+
+   static const struct {
+      int nl,nlp,nf,nd,nom; /* coefficients of l,l',F,D,Om */
+      double sp,spt;        /* longitude sine, 1 and t coefficients */
+      double ce,cet;        /* obliquity cosine, 1 and t coefficients */
+   } x[] = {
+
+   /* 1-10 */
+      {  0,  0,  0,  0,  1, -171996.0, -174.2,  92025.0,    8.9 },
+      {  0,  0,  0,  0,  2,    2062.0,    0.2,   -895.0,    0.5 },
+      { -2,  0,  2,  0,  1,      46.0,    0.0,    -24.0,    0.0 },
+      {  2,  0, -2,  0,  0,      11.0,    0.0,      0.0,    0.0 },
+      { -2,  0,  2,  0,  2,      -3.0,    0.0,      1.0,    0.0 },
+      {  1, -1,  0, -1,  0,      -3.0,    0.0,      0.0,    0.0 },
+      {  0, -2,  2, -2,  1,      -2.0,    0.0,      1.0,    0.0 },
+      {  2,  0, -2,  0,  1,       1.0,    0.0,      0.0,    0.0 },
+      {  0,  0,  2, -2,  2,  -13187.0,   -1.6,   5736.0,   -3.1 },
+      {  0,  1,  0,  0,  0,    1426.0,   -3.4,     54.0,   -0.1 },
+
+   /* 11-20 */
+      {  0,  1,  2, -2,  2,    -517.0,    1.2,    224.0,   -0.6 },
+      {  0, -1,  2, -2,  2,     217.0,   -0.5,    -95.0,    0.3 },
+      {  0,  0,  2, -2,  1,     129.0,    0.1,    -70.0,    0.0 },
+      {  2,  0,  0, -2,  0,      48.0,    0.0,      1.0,    0.0 },
+      {  0,  0,  2, -2,  0,     -22.0,    0.0,      0.0,    0.0 },
+      {  0,  2,  0,  0,  0,      17.0,   -0.1,      0.0,    0.0 },
+      {  0,  1,  0,  0,  1,     -15.0,    0.0,      9.0,    0.0 },
+      {  0,  2,  2, -2,  2,     -16.0,    0.1,      7.0,    0.0 },
+      {  0, -1,  0,  0,  1,     -12.0,    0.0,      6.0,    0.0 },
+      { -2,  0,  0,  2,  1,      -6.0,    0.0,      3.0,    0.0 },
+
+   /* 21-30 */
+      {  0, -1,  2, -2,  1,      -5.0,    0.0,      3.0,    0.0 },
+      {  2,  0,  0, -2,  1,       4.0,    0.0,     -2.0,    0.0 },
+      {  0,  1,  2, -2,  1,       4.0,    0.0,     -2.0,    0.0 },
+      {  1,  0,  0, -1,  0,      -4.0,    0.0,      0.0,    0.0 },
+      {  2,  1,  0, -2,  0,       1.0,    0.0,      0.0,    0.0 },
+      {  0,  0, -2,  2,  1,       1.0,    0.0,      0.0,    0.0 },
+      {  0,  1, -2,  2,  0,      -1.0,    0.0,      0.0,    0.0 },
+      {  0,  1,  0,  0,  2,       1.0,    0.0,      0.0,    0.0 },
+      { -1,  0,  0,  1,  1,       1.0,    0.0,      0.0,    0.0 },
+      {  0,  1,  2, -2,  0,      -1.0,    0.0,      0.0,    0.0 },
+
+   /* 31-40 */
+      {  0,  0,  2,  0,  2,   -2274.0,   -0.2,    977.0,   -0.5 },
+      {  1,  0,  0,  0,  0,     712.0,    0.1,     -7.0,    0.0 },
+      {  0,  0,  2,  0,  1,    -386.0,   -0.4,    200.0,    0.0 },
+      {  1,  0,  2,  0,  2,    -301.0,    0.0,    129.0,   -0.1 },
+      {  1,  0,  0, -2,  0,    -158.0,    0.0,     -1.0,    0.0 },
+      { -1,  0,  2,  0,  2,     123.0,    0.0,    -53.0,    0.0 },
+      {  0,  0,  0,  2,  0,      63.0,    0.0,     -2.0,    0.0 },
+      {  1,  0,  0,  0,  1,      63.0,    0.1,    -33.0,    0.0 },
+      { -1,  0,  0,  0,  1,     -58.0,   -0.1,     32.0,    0.0 },
+      { -1,  0,  2,  2,  2,     -59.0,    0.0,     26.0,    0.0 },
+
+   /* 41-50 */
+      {  1,  0,  2,  0,  1,     -51.0,    0.0,     27.0,    0.0 },
+      {  0,  0,  2,  2,  2,     -38.0,    0.0,     16.0,    0.0 },
+      {  2,  0,  0,  0,  0,      29.0,    0.0,     -1.0,    0.0 },
+      {  1,  0,  2, -2,  2,      29.0,    0.0,    -12.0,    0.0 },
+      {  2,  0,  2,  0,  2,     -31.0,    0.0,     13.0,    0.0 },
+      {  0,  0,  2,  0,  0,      26.0,    0.0,     -1.0,    0.0 },
+      { -1,  0,  2,  0,  1,      21.0,    0.0,    -10.0,    0.0 },
+      { -1,  0,  0,  2,  1,      16.0,    0.0,     -8.0,    0.0 },
+      {  1,  0,  0, -2,  1,     -13.0,    0.0,      7.0,    0.0 },
+      { -1,  0,  2,  2,  1,     -10.0,    0.0,      5.0,    0.0 },
+
+   /* 51-60 */
+      {  1,  1,  0, -2,  0,      -7.0,    0.0,      0.0,    0.0 },
+      {  0,  1,  2,  0,  2,       7.0,    0.0,     -3.0,    0.0 },
+      {  0, -1,  2,  0,  2,      -7.0,    0.0,      3.0,    0.0 },
+      {  1,  0,  2,  2,  2,      -8.0,    0.0,      3.0,    0.0 },
+      {  1,  0,  0,  2,  0,       6.0,    0.0,      0.0,    0.0 },
+      {  2,  0,  2, -2,  2,       6.0,    0.0,     -3.0,    0.0 },
+      {  0,  0,  0,  2,  1,      -6.0,    0.0,      3.0,    0.0 },
+      {  0,  0,  2,  2,  1,      -7.0,    0.0,      3.0,    0.0 },
+      {  1,  0,  2, -2,  1,       6.0,    0.0,     -3.0,    0.0 },
+      {  0,  0,  0, -2,  1,      -5.0,    0.0,      3.0,    0.0 },
+
+   /* 61-70 */
+      {  1, -1,  0,  0,  0,       5.0,    0.0,      0.0,    0.0 },
+      {  2,  0,  2,  0,  1,      -5.0,    0.0,      3.0,    0.0 },
+      {  0,  1,  0, -2,  0,      -4.0,    0.0,      0.0,    0.0 },
+      {  1,  0, -2,  0,  0,       4.0,    0.0,      0.0,    0.0 },
+      {  0,  0,  0,  1,  0,      -4.0,    0.0,      0.0,    0.0 },
+      {  1,  1,  0,  0,  0,      -3.0,    0.0,      0.0,    0.0 },
+      {  1,  0,  2,  0,  0,       3.0,    0.0,      0.0,    0.0 },
+      {  1, -1,  2,  0,  2,      -3.0,    0.0,      1.0,    0.0 },
+      { -1, -1,  2,  2,  2,      -3.0,    0.0,      1.0,    0.0 },
+      { -2,  0,  0,  0,  1,      -2.0,    0.0,      1.0,    0.0 },
+
+   /* 71-80 */
+      {  3,  0,  2,  0,  2,      -3.0,    0.0,      1.0,    0.0 },
+      {  0, -1,  2,  2,  2,      -3.0,    0.0,      1.0,    0.0 },
+      {  1,  1,  2,  0,  2,       2.0,    0.0,     -1.0,    0.0 },
+      { -1,  0,  2, -2,  1,      -2.0,    0.0,      1.0,    0.0 },
+      {  2,  0,  0,  0,  1,       2.0,    0.0,     -1.0,    0.0 },
+      {  1,  0,  0,  0,  2,      -2.0,    0.0,      1.0,    0.0 },
+      {  3,  0,  0,  0,  0,       2.0,    0.0,      0.0,    0.0 },
+      {  0,  0,  2,  1,  2,       2.0,    0.0,     -1.0,    0.0 },
+      { -1,  0,  0,  0,  2,       1.0,    0.0,     -1.0,    0.0 },
+      {  1,  0,  0, -4,  0,      -1.0,    0.0,      0.0,    0.0 },
+
+   /* 81-90 */
+      { -2,  0,  2,  2,  2,       1.0,    0.0,     -1.0,    0.0 },
+      { -1,  0,  2,  4,  2,      -2.0,    0.0,      1.0,    0.0 },
+      {  2,  0,  0, -4,  0,      -1.0,    0.0,      0.0,    0.0 },
+      {  1,  1,  2, -2,  2,       1.0,    0.0,     -1.0,    0.0 },
+      {  1,  0,  2,  2,  1,      -1.0,    0.0,      1.0,    0.0 },
+      { -2,  0,  2,  4,  2,      -1.0,    0.0,      1.0,    0.0 },
+      { -1,  0,  4,  0,  2,       1.0,    0.0,      0.0,    0.0 },
+      {  1, -1,  0, -2,  0,       1.0,    0.0,      0.0,    0.0 },
+      {  2,  0,  2, -2,  1,       1.0,    0.0,     -1.0,    0.0 },
+      {  2,  0,  2,  2,  2,      -1.0,    0.0,      0.0,    0.0 },
+
+   /* 91-100 */
+      {  1,  0,  0,  2,  1,      -1.0,    0.0,      0.0,    0.0 },
+      {  0,  0,  4, -2,  2,       1.0,    0.0,      0.0,    0.0 },
+      {  3,  0,  2, -2,  2,       1.0,    0.0,      0.0,    0.0 },
+      {  1,  0,  2, -2,  0,      -1.0,    0.0,      0.0,    0.0 },
+      {  0,  1,  2,  0,  1,       1.0,    0.0,      0.0,    0.0 },
+      { -1, -1,  0,  2,  1,       1.0,    0.0,      0.0,    0.0 },
+      {  0,  0, -2,  0,  1,      -1.0,    0.0,      0.0,    0.0 },
+      {  0,  0,  2, -1,  2,      -1.0,    0.0,      0.0,    0.0 },
+      {  0,  1,  0,  2,  0,      -1.0,    0.0,      0.0,    0.0 },
+      {  1,  0, -2, -2,  0,      -1.0,    0.0,      0.0,    0.0 },
+
+   /* 101-106 */
+      {  0, -1,  2,  0,  1,      -1.0,    0.0,      0.0,    0.0 },
+      {  1,  1,  0, -2,  1,      -1.0,    0.0,      0.0,    0.0 },
+      {  1,  0, -2,  2,  0,      -1.0,    0.0,      0.0,    0.0 },
+      {  2,  0,  0,  2,  0,       1.0,    0.0,      0.0,    0.0 },
+      {  0,  0,  2,  4,  2,      -1.0,    0.0,      0.0,    0.0 },
+      {  0,  1,  0,  1,  0,       1.0,    0.0,      0.0,    0.0 }
+   };
+
+/* Number of terms in the series */
+   const int NT = (int) (sizeof x / sizeof x[0]);
+
+/*--------------------------------------------------------------------*/
+
+/* Interval between fundamental epoch J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* --------------------- */
+/* Fundamental arguments */
+/* --------------------- */
+
+/* Mean longitude of Moon minus mean longitude of Moon's perigee. */
+   el = eraAnpm(
+        (485866.733 + (715922.633 + (31.310 + 0.064 * t) * t) * t)
+        * ERFA_DAS2R + fmod(1325.0 * t, 1.0) * ERFA_D2PI);
+
+/* Mean longitude of Sun minus mean longitude of Sun's perigee. */
+   elp = eraAnpm(
+         (1287099.804 + (1292581.224 + (-0.577 - 0.012 * t) * t) * t)
+         * ERFA_DAS2R + fmod(99.0 * t, 1.0) * ERFA_D2PI);
+
+/* Mean longitude of Moon minus mean longitude of Moon's node. */
+   f = eraAnpm(
+       (335778.877 + (295263.137 + (-13.257 + 0.011 * t) * t) * t)
+       * ERFA_DAS2R + fmod(1342.0 * t, 1.0) * ERFA_D2PI);
+
+/* Mean elongation of Moon from Sun. */
+   d = eraAnpm(
+       (1072261.307 + (1105601.328 + (-6.891 + 0.019 * t) * t) * t)
+       * ERFA_DAS2R + fmod(1236.0 * t, 1.0) * ERFA_D2PI);
+
+/* Longitude of the mean ascending node of the lunar orbit on the */
+/* ecliptic, measured from the mean equinox of date. */
+   om = eraAnpm(
+        (450160.280 + (-482890.539 + (7.455 + 0.008 * t) * t) * t)
+        * ERFA_DAS2R + fmod(-5.0 * t, 1.0) * ERFA_D2PI);
+
+/* --------------- */
+/* Nutation series */
+/* --------------- */
+
+/* Initialize nutation components. */
+   dp = 0.0;
+   de = 0.0;
+
+/* Sum the nutation terms, ending with the biggest. */
+   for (j = NT-1; j >= 0; j--) {
+
+   /* Form argument for current term. */
+      arg = (double)x[j].nl  * el
+          + (double)x[j].nlp * elp
+          + (double)x[j].nf  * f
+          + (double)x[j].nd  * d
+          + (double)x[j].nom * om;
+
+   /* Accumulate current nutation term. */
+      s = x[j].sp + x[j].spt * t;
+      c = x[j].ce + x[j].cet * t;
+      if (s != 0.0) dp += s * sin(arg);
+      if (c != 0.0) de += c * cos(arg);
+   }
+
+/* Convert results from 0.1 mas units to radians. */
+   *dpsi = dp * U2R;
+   *deps = de * U2R;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/nutm80.c b/ast/erfa/nutm80.c
new file mode 100644
index 0000000..c526da2
--- /dev/null
+++ b/ast/erfa/nutm80.c
@@ -0,0 +1,126 @@
+#include "erfa.h"
+
+void eraNutm80(double date1, double date2, double rmatn[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a N u t m 8 0
+**  - - - - - - - - - -
+**
+**  Form the matrix of nutation for a given date, IAU 1980 model.
+**
+**  Given:
+**     date1,date2    double          TDB date (Note 1)
+**
+**  Returned:
+**     rmatn          double[3][3]    nutation matrix
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(true) = rmatn * V(mean),
+**     where the p-vector V(true) is with respect to the true
+**     equatorial triad of date and the p-vector V(mean) is with
+**     respect to the mean equatorial triad of date.
+**
+**  Called:
+**     eraNut80     nutation, IAU 1980
+**     eraObl80     mean obliquity, IAU 1980
+**     eraNumat     form nutation matrix
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dpsi, deps, epsa;
+
+
+/* Nutation components and mean obliquity. */
+   eraNut80(date1, date2, &dpsi, &deps);
+   epsa = eraObl80(date1, date2);
+
+/* Build the rotation matrix. */
+   eraNumat(epsa, dpsi, deps, rmatn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/obl06.c b/ast/erfa/obl06.c
new file mode 100644
index 0000000..9b93626
--- /dev/null
+++ b/ast/erfa/obl06.c
@@ -0,0 +1,127 @@
+#include "erfa.h"
+
+double eraObl06(double date1, double date2)
+/*
+**  - - - - - - - - -
+**   e r a O b l 0 6
+**  - - - - - - - - -
+**
+**  Mean obliquity of the ecliptic, IAU 2006 precession model.
+**
+**  Given:
+**     date1,date2  double   TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                  double   obliquity of the ecliptic (radians, Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The result is the angle between the ecliptic and mean equator of
+**     date date1+date2.
+**
+**  Reference:
+**
+**     Hilton, J. et al., 2006, Celest.Mech.Dyn.Astron. 94, 351
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, eps0;
+
+
+/* Interval between fundamental date J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Mean obliquity. */
+   eps0 = (84381.406     +
+          (-46.836769    +
+          ( -0.0001831   +
+          (  0.00200340  +
+          ( -0.000000576 +
+          ( -0.0000000434) * t) * t) * t) * t) * t) * ERFA_DAS2R;
+
+   return eps0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/obl80.c b/ast/erfa/obl80.c
new file mode 100644
index 0000000..1b5e288
--- /dev/null
+++ b/ast/erfa/obl80.c
@@ -0,0 +1,127 @@
+#include "erfa.h"
+
+double eraObl80(double date1, double date2)
+/*
+**  - - - - - - - - -
+**   e r a O b l 8 0
+**  - - - - - - - - -
+**
+**  Mean obliquity of the ecliptic, IAU 1980 model.
+**
+**  Given:
+**     date1,date2   double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                   double    obliquity of the ecliptic (radians, Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The result is the angle between the ecliptic and mean equator of
+**     date date1+date2.
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Expression 3.222-1 (p114).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, eps0;
+
+
+/* Interval between fundamental epoch J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Mean obliquity of date. */
+   eps0 = ERFA_DAS2R * (84381.448  +
+                  (-46.8150   +
+                  (-0.00059   +
+                  ( 0.001813) * t) * t) * t);
+
+   return eps0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/p06e.c b/ast/erfa/p06e.c
new file mode 100644
index 0000000..24f9f2b
--- /dev/null
+++ b/ast/erfa/p06e.c
@@ -0,0 +1,330 @@
+#include "erfa.h"
+
+void eraP06e(double date1, double date2,
+             double *eps0, double *psia, double *oma, double *bpa,
+             double *bqa, double *pia, double *bpia,
+             double *epsa, double *chia, double *za, double *zetaa,
+             double *thetaa, double *pa,
+             double *gam, double *phi, double *psi)
+/*
+**  - - - - - - - -
+**   e r a P 0 6 e
+**  - - - - - - - -
+**
+**  Precession angles, IAU 2006, equinox based.
+**
+**  Given:
+**     date1,date2   double   TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (see Note 2):
+**     eps0          double   epsilon_0
+**     psia          double   psi_A
+**     oma           double   omega_A
+**     bpa           double   P_A
+**     bqa           double   Q_A
+**     pia           double   pi_A
+**     bpia          double   Pi_A
+**     epsa          double   obliquity epsilon_A
+**     chia          double   chi_A
+**     za            double   z_A
+**     zetaa         double   zeta_A
+**     thetaa        double   theta_A
+**     pa            double   p_A
+**     gam           double   F-W angle gamma_J2000
+**     phi           double   F-W angle phi_J2000
+**     psi           double   F-W angle psi_J2000
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) This function returns the set of equinox based angles for the
+**     Capitaine et al. "P03" precession theory, adopted by the IAU in
+**     2006.  The angles are set out in Table 1 of Hilton et al. (2006):
+**
+**     eps0   epsilon_0   obliquity at J2000.0
+**     psia   psi_A       luni-solar precession
+**     oma    omega_A     inclination of equator wrt J2000.0 ecliptic
+**     bpa    P_A         ecliptic pole x, J2000.0 ecliptic triad
+**     bqa    Q_A         ecliptic pole -y, J2000.0 ecliptic triad
+**     pia    pi_A        angle between moving and J2000.0 ecliptics
+**     bpia   Pi_A        longitude of ascending node of the ecliptic
+**     epsa   epsilon_A   obliquity of the ecliptic
+**     chia   chi_A       planetary precession
+**     za     z_A         equatorial precession: -3rd 323 Euler angle
+**     zetaa  zeta_A      equatorial precession: -1st 323 Euler angle
+**     thetaa theta_A     equatorial precession: 2nd 323 Euler angle
+**     pa     p_A         general precession
+**     gam    gamma_J2000 J2000.0 RA difference of ecliptic poles
+**     phi    phi_J2000   J2000.0 codeclination of ecliptic pole
+**     psi    psi_J2000   longitude difference of equator poles, J2000.0
+**
+**     The returned values are all radians.
+**
+**  3) Hilton et al. (2006) Table 1 also contains angles that depend on
+**     models distinct from the P03 precession theory itself, namely the
+**     IAU 2000A frame bias and nutation.  The quoted polynomials are
+**     used in other ERFA functions:
+**
+**     . eraXy06  contains the polynomial parts of the X and Y series.
+**
+**     . eraS06  contains the polynomial part of the s+XY/2 series.
+**
+**     . eraPfw06  implements the series for the Fukushima-Williams
+**       angles that are with respect to the GCRS pole (i.e. the variants
+**       that include frame bias).
+**
+**  4) The IAU resolution stipulated that the choice of parameterization
+**     was left to the user, and so an IAU compliant precession
+**     implementation can be constructed using various combinations of
+**     the angles returned by the present function.
+**
+**  5) The parameterization used by ERFA is the version of the Fukushima-
+**     Williams angles that refers directly to the GCRS pole.  These
+**     angles may be calculated by calling the function eraPfw06.  ERFA
+**     also supports the direct computation of the CIP GCRS X,Y by
+**     series, available by calling eraXy06.
+**
+**  6) The agreement between the different parameterizations is at the
+**     1 microarcsecond level in the present era.
+**
+**  7) When constructing a precession formulation that refers to the GCRS
+**     pole rather than the dynamical pole, it may (depending on the
+**     choice of angles) be necessary to introduce the frame bias
+**     explicitly.
+**
+**  8) It is permissible to re-use the same variable in the returned
+**     arguments.  The quantities are stored in the stated order.
+**
+**  Reference:
+**
+**     Hilton, J. et al., 2006, Celest.Mech.Dyn.Astron. 94, 351
+**
+**  Called:
+**     eraObl06     mean obliquity, IAU 2006
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t;
+
+
+/* Interval between fundamental date J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Obliquity at J2000.0. */
+
+   *eps0 = 84381.406 * ERFA_DAS2R;
+
+/* Luni-solar precession. */
+
+   *psia = ( 5038.481507     +
+           (   -1.0790069    +
+           (   -0.00114045   +
+           (    0.000132851  +
+           (   -0.0000000951 )
+           * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+/* Inclination of mean equator with respect to the J2000.0 ecliptic. */
+
+   *oma = *eps0 + ( -0.025754     +
+                  (  0.0512623    +
+                  ( -0.00772503   +
+                  ( -0.000000467  +
+                  (  0.0000003337 )
+                  * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+/* Ecliptic pole x, J2000.0 ecliptic triad. */
+
+   *bpa = (  4.199094     +
+          (  0.1939873    +
+          ( -0.00022466   +
+          ( -0.000000912  +
+          (  0.0000000120 )
+          * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+/* Ecliptic pole -y, J2000.0 ecliptic triad. */
+
+   *bqa = ( -46.811015     +
+          (   0.0510283    +
+          (   0.00052413   +
+          (  -0.000000646  +
+          (  -0.0000000172 )
+          * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+/* Angle between moving and J2000.0 ecliptics. */
+
+   *pia = ( 46.998973     +
+          ( -0.0334926    +
+          ( -0.00012559   +
+          (  0.000000113  +
+          ( -0.0000000022 )
+          * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+/* Longitude of ascending node of the moving ecliptic. */
+
+   *bpia = ( 629546.7936      +
+           (   -867.95758     +
+           (      0.157992    +
+           (     -0.0005371   +
+           (     -0.00004797  +
+           (      0.000000072 )
+           * t) * t) * t) * t) * t) * ERFA_DAS2R;
+
+/* Mean obliquity of the ecliptic. */
+
+   *epsa = eraObl06(date1, date2);
+
+/* Planetary precession. */
+
+   *chia = ( 10.556403     +
+           ( -2.3814292    +
+           ( -0.00121197   +
+           (  0.000170663  +
+           ( -0.0000000560 )
+           * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+/* Equatorial precession: minus the third of the 323 Euler angles. */
+
+   *za = (   -2.650545     +
+         ( 2306.077181     +
+         (    1.0927348    +
+         (    0.01826837   +
+         (   -0.000028596  +
+         (   -0.0000002904 )
+         * t) * t) * t) * t) * t) * ERFA_DAS2R;
+
+/* Equatorial precession: minus the first of the 323 Euler angles. */
+
+   *zetaa = (    2.650545     +
+            ( 2306.083227     +
+            (    0.2988499    +
+            (    0.01801828   +
+            (   -0.000005971  +
+            (   -0.0000003173 )
+            * t) * t) * t) * t) * t) * ERFA_DAS2R;
+
+/* Equatorial precession: second of the 323 Euler angles. */
+
+   *thetaa = ( 2004.191903     +
+             (   -0.4294934    +
+             (   -0.04182264   +
+             (   -0.000007089  +
+             (   -0.0000001274 )
+             * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+/* General precession. */
+
+   *pa = ( 5028.796195     +
+         (    1.1054348    +
+         (    0.00007964   +
+         (   -0.000023857  +
+         (    0.0000000383 )
+         * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+/* Fukushima-Williams angles for precession. */
+
+   *gam = ( 10.556403     +
+          (  0.4932044    +
+          ( -0.00031238   +
+          ( -0.000002788  +
+          (  0.0000000260 )
+          * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+   *phi = *eps0 + ( -46.811015     +
+                  (   0.0511269    +
+                  (   0.00053289   +
+                  (  -0.000000440  +
+                  (  -0.0000000176 )
+                  * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+   *psi = ( 5038.481507     +
+          (    1.5584176    +
+          (   -0.00018522   +
+          (   -0.000026452  +
+          (   -0.0000000148 )
+          * t) * t) * t) * t) * t * ERFA_DAS2R;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/p2pv.c b/ast/erfa/p2pv.c
new file mode 100644
index 0000000..b5d2081
--- /dev/null
+++ b/ast/erfa/p2pv.c
@@ -0,0 +1,92 @@
+#include "erfa.h"
+
+void eraP2pv(double p[3], double pv[2][3])
+/*
+**  - - - - - - - -
+**   e r a P 2 p v
+**  - - - - - - - -
+**
+**  Extend a p-vector to a pv-vector by appending a zero velocity.
+**
+**  Given:
+**     p        double[3]       p-vector
+**
+**  Returned:
+**     pv       double[2][3]    pv-vector
+**
+**  Called:
+**     eraCp        copy p-vector
+**     eraZp        zero p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraCp(p, pv[0]);
+   eraZp(pv[1]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/p2s.c b/ast/erfa/p2s.c
new file mode 100644
index 0000000..14da5f0
--- /dev/null
+++ b/ast/erfa/p2s.c
@@ -0,0 +1,100 @@
+#include "erfa.h"
+
+void eraP2s(double p[3], double *theta, double *phi, double *r)
+/*
+**  - - - - - - -
+**   e r a P 2 s
+**  - - - - - - -
+**
+**  P-vector to spherical polar coordinates.
+**
+**  Given:
+**     p        double[3]    p-vector
+**
+**  Returned:
+**     theta    double       longitude angle (radians)
+**     phi      double       latitude angle (radians)
+**     r        double       radial distance
+**
+**  Notes:
+**
+**  1) If P is null, zero theta, phi and r are returned.
+**
+**  2) At either pole, zero theta is returned.
+**
+**  Called:
+**     eraC2s       p-vector to spherical
+**     eraPm        modulus of p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraC2s(p, theta, phi);
+   *r = eraPm(p);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pap.c b/ast/erfa/pap.c
new file mode 100644
index 0000000..afd8651
--- /dev/null
+++ b/ast/erfa/pap.c
@@ -0,0 +1,148 @@
+#include "erfa.h"
+
+double eraPap(double a[3], double b[3])
+/*
+**  - - - - - - -
+**   e r a P a p
+**  - - - - - - -
+**
+**  Position-angle from two p-vectors.
+**
+**  Given:
+**     a      double[3]  direction of reference point
+**     b      double[3]  direction of point whose PA is required
+**
+**  Returned (function value):
+**            double     position angle of b with respect to a (radians)
+**
+**  Notes:
+**
+**  1) The result is the position angle, in radians, of direction b with
+**     respect to direction a.  It is in the range -pi to +pi.  The
+**     sense is such that if b is a small distance "north" of a the
+**     position angle is approximately zero, and if b is a small
+**     distance "east" of a the position angle is approximately +pi/2.
+**
+**  2) The vectors a and b need not be of unit length.
+**
+**  3) Zero is returned if the two directions are the same or if either
+**     vector is null.
+**
+**  4) If vector a is at a pole, the result is ill-defined.
+**
+**  Called:
+**     eraPn        decompose p-vector into modulus and direction
+**     eraPm        modulus of p-vector
+**     eraPxp       vector product of two p-vectors
+**     eraPmp       p-vector minus p-vector
+**     eraPdp       scalar product of two p-vectors
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double am, au[3], bm, st, ct, xa, ya, za, eta[3], xi[3], a2b[3], pa;
+
+
+/* Modulus and direction of the a vector. */
+   eraPn(a, &am, au);
+
+/* Modulus of the b vector. */
+   bm = eraPm(b);
+
+/* Deal with the case of a null vector. */
+   if ((am == 0.0) || (bm == 0.0)) {
+      st = 0.0;
+      ct = 1.0;
+   } else {
+
+   /* The "north" axis tangential from a (arbitrary length). */
+      xa = a[0];
+      ya = a[1];
+      za = a[2];
+      eta[0] = -xa * za;
+      eta[1] = -ya * za;
+      eta[2] =  xa*xa + ya*ya;
+
+   /* The "east" axis tangential from a (same length). */
+      eraPxp(eta, au, xi);
+
+   /* The vector from a to b. */
+      eraPmp(b, a, a2b);
+
+   /* Resolve into components along the north and east axes. */
+      st = eraPdp(a2b, xi);
+      ct = eraPdp(a2b, eta);
+
+   /* Deal with degenerate cases. */
+      if ((st == 0.0) && (ct == 0.0)) ct = 1.0;
+   }
+
+/* Position angle. */
+   pa = atan2(st, ct);
+
+   return pa;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pas.c b/ast/erfa/pas.c
new file mode 100644
index 0000000..52b3264
--- /dev/null
+++ b/ast/erfa/pas.c
@@ -0,0 +1,105 @@
+#include "erfa.h"
+
+double eraPas(double al, double ap, double bl, double bp)
+/*
+**  - - - - - - -
+**   e r a P a s
+**  - - - - - - -
+**
+**  Position-angle from spherical coordinates.
+**
+**  Given:
+**     al     double     longitude of point A (e.g. RA) in radians
+**     ap     double     latitude of point A (e.g. Dec) in radians
+**     bl     double     longitude of point B
+**     bp     double     latitude of point B
+**
+**  Returned (function value):
+**            double     position angle of B with respect to A
+**
+**  Notes:
+**
+**  1) The result is the bearing (position angle), in radians, of point
+**     B with respect to point A.  It is in the range -pi to +pi.  The
+**     sense is such that if B is a small distance "east" of point A,
+**     the bearing is approximately +pi/2.
+**
+**  2) Zero is returned if the two points are coincident.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dl, x, y, pa;
+
+
+   dl = bl - al;
+   y = sin(dl) * cos(bp);
+   x = sin(bp) * cos(ap) - cos(bp) * sin(ap) * cos(dl);
+   pa = ((x != 0.0) || (y != 0.0)) ? atan2(y, x) : 0.0;
+
+   return pa;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pb06.c b/ast/erfa/pb06.c
new file mode 100644
index 0000000..7b14279
--- /dev/null
+++ b/ast/erfa/pb06.c
@@ -0,0 +1,153 @@
+#include "erfa.h"
+
+void eraPb06(double date1, double date2,
+             double *bzeta, double *bz, double *btheta)
+/*
+**  - - - - - - - -
+**   e r a P b 0 6
+**  - - - - - - - -
+**
+**  This function forms three Euler angles which implement general
+**  precession from epoch J2000.0, using the IAU 2006 model.  Frame
+**  bias (the offset between ICRS and mean J2000.0) is included.
+**
+**  Given:
+**     date1,date2  double   TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     bzeta        double   1st rotation: radians cw around z
+**     bz           double   3rd rotation: radians cw around z
+**     btheta       double   2nd rotation: radians ccw around y
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The traditional accumulated precession angles zeta_A, z_A,
+**     theta_A cannot be obtained in the usual way, namely through
+**     polynomial expressions, because of the frame bias.  The latter
+**     means that two of the angles undergo rapid changes near this
+**     date.  They are instead the results of decomposing the
+**     precession-bias matrix obtained by using the Fukushima-Williams
+**     method, which does not suffer from the problem.  The
+**     decomposition returns values which can be used in the
+**     conventional formulation and which include frame bias.
+**
+**  3) The three angles are returned in the conventional order, which
+**     is not the same as the order of the corresponding Euler
+**     rotations.  The precession-bias matrix is
+**     R_3(-z) x R_2(+theta) x R_3(-zeta).
+**
+**  4) Should zeta_A, z_A, theta_A angles be required that do not
+**     contain frame bias, they are available by calling the ERFA
+**     function eraP06e.
+**
+**  Called:
+**     eraPmat06    PB matrix, IAU 2006
+**     eraRz        rotate around Z-axis
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double r[3][3], r31, r32;
+
+
+/* Precession matrix via Fukushima-Williams angles. */
+   eraPmat06(date1, date2, r);
+
+/* Solve for z. */
+   *bz = atan2(r[1][2], r[0][2]);
+
+/* Remove it from the matrix. */
+   eraRz(*bz, r);
+
+/* Solve for the remaining two angles. */
+   *bzeta = atan2 (r[1][0], r[1][1]);
+   r31 = r[2][0];
+   r32 = r[2][1];
+   *btheta = atan2(-ERFA_DSIGN(sqrt(r31 * r31 + r32 * r32), r[0][2]),
+                   r[2][2]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pdp.c b/ast/erfa/pdp.c
new file mode 100644
index 0000000..72f1cc0
--- /dev/null
+++ b/ast/erfa/pdp.c
@@ -0,0 +1,93 @@
+#include "erfa.h"
+
+double eraPdp(double a[3], double b[3])
+/*
+**  - - - - - - -
+**   e r a P d p
+**  - - - - - - -
+**
+**  p-vector inner (=scalar=dot) product.
+**
+**  Given:
+**     a      double[3]     first p-vector
+**     b      double[3]     second p-vector
+**
+**  Returned (function value):
+**            double        a . b
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double w;
+
+
+   w  = a[0] * b[0]
+      + a[1] * b[1]
+      + a[2] * b[2];
+
+   return w;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pfw06.c b/ast/erfa/pfw06.c
new file mode 100644
index 0000000..b510f0a
--- /dev/null
+++ b/ast/erfa/pfw06.c
@@ -0,0 +1,174 @@
+#include "erfa.h"
+
+void eraPfw06(double date1, double date2,
+              double *gamb, double *phib, double *psib, double *epsa)
+/*
+**  - - - - - - - - -
+**   e r a P f w 0 6
+**  - - - - - - - - -
+**
+**  Precession angles, IAU 2006 (Fukushima-Williams 4-angle formulation).
+**
+**  Given:
+**     date1,date2  double   TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     gamb         double   F-W angle gamma_bar (radians)
+**     phib         double   F-W angle phi_bar (radians)
+**     psib         double   F-W angle psi_bar (radians)
+**     epsa         double   F-W angle epsilon_A (radians)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) Naming the following points:
+**
+**           e = J2000.0 ecliptic pole,
+**           p = GCRS pole,
+**           E = mean ecliptic pole of date,
+**     and   P = mean pole of date,
+**
+**     the four Fukushima-Williams angles are as follows:
+**
+**        gamb = gamma_bar = epE
+**        phib = phi_bar = pE
+**        psib = psi_bar = pEP
+**        epsa = epsilon_A = EP
+**
+**  3) The matrix representing the combined effects of frame bias and
+**     precession is:
+**
+**        PxB = R_1(-epsa).R_3(-psib).R_1(phib).R_3(gamb)
+**
+**  4) The matrix representing the combined effects of frame bias,
+**     precession and nutation is simply:
+**
+**        NxPxB = R_1(-epsa-dE).R_3(-psib-dP).R_1(phib).R_3(gamb)
+**
+**     where dP and dE are the nutation components with respect to the
+**     ecliptic of date.
+**
+**  Reference:
+**
+**     Hilton, J. et al., 2006, Celest.Mech.Dyn.Astron. 94, 351
+**
+**  Called:
+**     eraObl06     mean obliquity, IAU 2006
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t;
+
+
+/* Interval between fundamental date J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* P03 bias+precession angles. */
+   *gamb = (    -0.052928     +
+           (    10.556378     +
+           (     0.4932044    +
+           (    -0.00031238   +
+           (    -0.000002788  +
+           (     0.0000000260 )
+           * t) * t) * t) * t) * t) * ERFA_DAS2R;
+   *phib = ( 84381.412819     +
+           (   -46.811016     +
+           (     0.0511268    +
+           (     0.00053289   +
+           (    -0.000000440  +
+           (    -0.0000000176 )
+           * t) * t) * t) * t) * t) * ERFA_DAS2R;
+   *psib = (    -0.041775     +
+           (  5038.481484     +
+           (     1.5584175    +
+           (    -0.00018522   +
+           (    -0.000026452  +
+           (    -0.0000000148 )
+           * t) * t) * t) * t) * t) * ERFA_DAS2R;
+   *epsa =  eraObl06(date1, date2);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/plan94.c b/ast/erfa/plan94.c
new file mode 100644
index 0000000..ef878dd
--- /dev/null
+++ b/ast/erfa/plan94.c
@@ -0,0 +1,523 @@
+#include "erfa.h"
+
+int eraPlan94(double date1, double date2, int np, double pv[2][3])
+/*
+**  - - - - - - - - - -
+**   e r a P l a n 9 4
+**  - - - - - - - - - -
+**
+**  Approximate heliocentric position and velocity of a nominated major
+**  planet:  Mercury, Venus, EMB, Mars, Jupiter, Saturn, Uranus or
+**  Neptune (but not the Earth itself).
+**
+**  Given:
+**     date1  double       TDB date part A (Note 1)
+**     date2  double       TDB date part B (Note 1)
+**     np     int          planet (1=Mercury, 2=Venus, 3=EMB, 4=Mars,
+**                             5=Jupiter, 6=Saturn, 7=Uranus, 8=Neptune)
+**
+**  Returned (argument):
+**     pv     double[2][3] planet p,v (heliocentric, J2000.0, AU,AU/d)
+**
+**  Returned (function value):
+**            int          status: -1 = illegal NP (outside 1-8)
+**                                  0 = OK
+**                                 +1 = warning: year outside 1000-3000
+**                                 +2 = warning: failed to converge
+**
+**  Notes:
+**
+**  1) The date date1+date2 is in the TDB time scale (in practice TT can
+**     be used) and is a Julian Date, apportioned in any convenient way
+**     between the two arguments.  For example, JD(TDB)=2450123.7 could
+**     be expressed in any of these ways, among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.  The limited
+**     accuracy of the present algorithm is such that any of the methods
+**     is satisfactory.
+**
+**  2) If an np value outside the range 1-8 is supplied, an error status
+**     (function value -1) is returned and the pv vector set to zeroes.
+**
+**  3) For np=3 the result is for the Earth-Moon Barycenter.  To obtain
+**     the heliocentric position and velocity of the Earth, use instead
+**     the ERFA function eraEpv00.
+**
+**  4) On successful return, the array pv contains the following:
+**
+**        pv[0][0]   x      }
+**        pv[0][1]   y      } heliocentric position, AU
+**        pv[0][2]   z      }
+**
+**        pv[1][0]   xdot   }
+**        pv[1][1]   ydot   } heliocentric velocity, AU/d
+**        pv[1][2]   zdot   }
+**
+**     The reference frame is equatorial and is with respect to the
+**     mean equator and equinox of epoch J2000.0.
+**
+**  5) The algorithm is due to J.L. Simon, P. Bretagnon, J. Chapront,
+**     M. Chapront-Touze, G. Francou and J. Laskar (Bureau des
+**     Longitudes, Paris, France).  From comparisons with JPL
+**     ephemeris DE102, they quote the following maximum errors
+**     over the interval 1800-2050:
+**
+**                     L (arcsec)    B (arcsec)      R (km)
+**
+**        Mercury          4             1             300
+**        Venus            5             1             800
+**        EMB              6             1            1000
+**        Mars            17             1            7700
+**        Jupiter         71             5           76000
+**        Saturn          81            13          267000
+**        Uranus          86             7          712000
+**        Neptune         11             1          253000
+**
+**     Over the interval 1000-3000, they report that the accuracy is no
+**     worse than 1.5 times that over 1800-2050.  Outside 1000-3000 the
+**     accuracy declines.
+**
+**     Comparisons of the present function with the JPL DE200 ephemeris
+**     give the following RMS errors over the interval 1960-2025:
+**
+**                      position (km)     velocity (m/s)
+**
+**        Mercury            334               0.437
+**        Venus             1060               0.855
+**        EMB               2010               0.815
+**        Mars              7690               1.98
+**        Jupiter          71700               7.70
+**        Saturn          199000              19.4
+**        Uranus          564000              16.4
+**        Neptune         158000              14.4
+**
+**     Comparisons against DE200 over the interval 1800-2100 gave the
+**     following maximum absolute differences.  (The results using
+**     DE406 were essentially the same.)
+**
+**                   L (arcsec)   B (arcsec)     R (km)   Rdot (m/s)
+**
+**        Mercury        7            1            500       0.7
+**        Venus          7            1           1100       0.9
+**        EMB            9            1           1300       1.0
+**        Mars          26            1           9000       2.5
+**        Jupiter       78            6          82000       8.2
+**        Saturn        87           14         263000      24.6
+**        Uranus        86            7         661000      27.4
+**        Neptune       11            2         248000      21.4
+**
+**  6) The present ERFA re-implementation of the original Simon et al.
+**     Fortran code differs from the original in the following respects:
+**
+**       *  C instead of Fortran.
+**
+**       *  The date is supplied in two parts.
+**
+**       *  The result is returned only in equatorial Cartesian form;
+**          the ecliptic longitude, latitude and radius vector are not
+**          returned.
+**
+**       *  The result is in the J2000.0 equatorial frame, not ecliptic.
+**
+**       *  More is done in-line: there are fewer calls to subroutines.
+**
+**       *  Different error/warning status values are used.
+**
+**       *  A different Kepler's-equation-solver is used (avoiding
+**          use of double precision complex).
+**
+**       *  Polynomials in t are nested to minimize rounding errors.
+**
+**       *  Explicit double constants are used to avoid mixed-mode
+**          expressions.
+**
+**     None of the above changes affects the result significantly.
+**
+**  7) The returned status indicates the most serious condition
+**     encountered during execution of the function.  Illegal np is
+**     considered the most serious, overriding failure to converge,
+**     which in turn takes precedence over the remote date warning.
+**
+**  Called:
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  Reference:  Simon, J.L, Bretagnon, P., Chapront, J.,
+**              Chapront-Touze, M., Francou, G., and Laskar, J.,
+**              Astron. Astrophys. 282, 663 (1994).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Gaussian constant */
+   static const double GK = 0.017202098950;
+
+/* Sin and cos of J2000.0 mean obliquity (IAU 1976) */
+   static const double SINEPS = 0.3977771559319137;
+   static const double COSEPS = 0.9174820620691818;
+
+/* Maximum number of iterations allowed to solve Kepler's equation */
+   static const int KMAX = 10;
+
+   int jstat, i, k;
+   double t, da, dl, de, dp, di, dom, dmu, arga, argl, am,
+          ae, dae, ae2, at, r, v, si2, xq, xp, tl, xsw,
+          xcw, xm2, xf, ci2, xms, xmc, xpxq2, x, y, z;
+
+/* Planetary inverse masses */
+   static const double amas[] = { 6023600.0,       /* Mercury */
+                                   408523.5,       /* Venus   */
+                                   328900.5,       /* EMB     */
+                                  3098710.0,       /* Mars    */
+                                     1047.355,     /* Jupiter */
+                                     3498.5,       /* Saturn  */
+                                    22869.0,       /* Uranus  */
+                                    19314.0 };     /* Neptune */
+
+/*
+** Tables giving the mean Keplerian elements, limited to t^2 terms:
+**
+**   a       semi-major axis (AU)
+**   dlm     mean longitude (degree and arcsecond)
+**   e       eccentricity
+**   pi      longitude of the perihelion (degree and arcsecond)
+**   dinc    inclination (degree and arcsecond)
+**   omega   longitude of the ascending node (degree and arcsecond)
+*/
+
+   static const double a[][3] = {
+       {  0.3870983098,           0.0,     0.0 },  /* Mercury */
+       {  0.7233298200,           0.0,     0.0 },  /* Venus   */
+       {  1.0000010178,           0.0,     0.0 },  /* EMB     */
+       {  1.5236793419,         3e-10,     0.0 },  /* Mars    */
+       {  5.2026032092,     19132e-10, -39e-10 },  /* Jupiter */
+       {  9.5549091915, -0.0000213896, 444e-10 },  /* Saturn  */
+       { 19.2184460618,     -3716e-10, 979e-10 },  /* Uranus  */
+       { 30.1103868694,    -16635e-10, 686e-10 }   /* Neptune */
+   };
+
+   static const double dlm[][3] = {
+       { 252.25090552, 5381016286.88982,  -1.92789 },
+       { 181.97980085, 2106641364.33548,   0.59381 },
+       { 100.46645683, 1295977422.83429,  -2.04411 },
+       { 355.43299958,  689050774.93988,   0.94264 },
+       {  34.35151874,  109256603.77991, -30.60378 },
+       {  50.07744430,   43996098.55732,  75.61614 },
+       { 314.05500511,   15424811.93933,  -1.75083 },
+       { 304.34866548,    7865503.20744,   0.21103 }
+   };
+
+   static const double e[][3] = {
+       { 0.2056317526,  0.0002040653,    -28349e-10 },
+       { 0.0067719164, -0.0004776521,     98127e-10 },
+       { 0.0167086342, -0.0004203654, -0.0000126734 },
+       { 0.0934006477,  0.0009048438,    -80641e-10 },
+       { 0.0484979255,  0.0016322542, -0.0000471366 },
+       { 0.0555481426, -0.0034664062, -0.0000643639 },
+       { 0.0463812221, -0.0002729293,  0.0000078913 },
+       { 0.0094557470,  0.0000603263,           0.0 }
+   };
+
+   static const double pi[][3] = {
+       {  77.45611904,  5719.11590,   -4.83016 },
+       { 131.56370300,   175.48640, -498.48184 },
+       { 102.93734808, 11612.35290,   53.27577 },
+       { 336.06023395, 15980.45908,  -62.32800 },
+       {  14.33120687,  7758.75163,  259.95938 },
+       {  93.05723748, 20395.49439,  190.25952 },
+       { 173.00529106,  3215.56238,  -34.09288 },
+       {  48.12027554,  1050.71912,   27.39717 }
+   };
+
+   static const double dinc[][3] = {
+       { 7.00498625, -214.25629,   0.28977 },
+       { 3.39466189,  -30.84437, -11.67836 },
+       {        0.0,  469.97289,  -3.35053 },
+       { 1.84972648, -293.31722,  -8.11830 },
+       { 1.30326698,  -71.55890,  11.95297 },
+       { 2.48887878,   91.85195, -17.66225 },
+       { 0.77319689,  -60.72723,   1.25759 },
+       { 1.76995259,    8.12333,   0.08135 }
+   };
+
+   static const double omega[][3] = {
+       {  48.33089304,  -4515.21727,  -31.79892 },
+       {  76.67992019, -10008.48154,  -51.32614 },
+       { 174.87317577,  -8679.27034,   15.34191 },
+       {  49.55809321, -10620.90088, -230.57416 },
+       { 100.46440702,   6362.03561,  326.52178 },
+       { 113.66550252,  -9240.19942,  -66.23743 },
+       {  74.00595701,   2669.15033,  145.93964 },
+       { 131.78405702,   -221.94322,   -0.78728 }
+   };
+
+/* Tables for trigonometric terms to be added to the mean elements of */
+/* the semi-major axes */
+
+   static const double kp[][9] = {
+    {   69613, 75645, 88306, 59899, 15746, 71087, 142173,  3086,    0 },
+    {   21863, 32794, 26934, 10931, 26250, 43725,  53867, 28939,    0 },
+    {   16002, 21863, 32004, 10931, 14529, 16368,  15318, 32794,    0 },
+    {    6345,  7818, 15636,  7077,  8184, 14163,   1107,  4872,    0 },
+    {    1760,  1454,  1167,   880,   287,  2640,     19,  2047, 1454 },
+    {     574,     0,   880,   287,    19,  1760,   1167,   306,  574 },
+    {     204,     0,   177,  1265,     4,   385,    200,   208,  204 },
+    {       0,   102,   106,     4,    98,  1367,    487,   204,    0 }
+   };
+
+   static const double ca[][9] = {
+    {       4,    -13,    11,   -9,    -9,   -3,     -1,     4,     0 },
+    {    -156,     59,   -42,    6,    19,  -20,    -10,   -12,     0 },
+    {      64,   -152,    62,   -8,    32,  -41,     19,   -11,     0 },
+    {     124,    621,  -145,  208,    54,  -57,     30,    15,     0 },
+    {  -23437,  -2634,  6601, 6259, -1507,-1821,   2620, -2115, -1489 },
+    {   62911,-119919, 79336,17814,-24241,12068,   8306, -4893,  8902 },
+    {  389061,-262125,-44088, 8387,-22976,-2093,   -615, -9720,  6633 },
+    { -412235,-157046,-31430,37817, -9740,  -13,  -7449,  9644,     0 }
+   };
+
+   static const double sa[][9] = {
+    {     -29,    -1,     9,     6,    -6,     5,     4,     0,     0 },
+    {     -48,  -125,   -26,   -37,    18,   -13,   -20,    -2,     0 },
+    {    -150,   -46,    68,    54,    14,    24,   -28,    22,     0 },
+    {    -621,   532,  -694,   -20,   192,   -94,    71,   -73,     0 },
+    {  -14614,-19828, -5869,  1881, -4372, -2255,   782,   930,   913 },
+    {  139737,     0, 24667, 51123, -5102,  7429, -4095, -1976, -9566 },
+    { -138081,     0, 37205,-49039,-41901,-33872,-27037,-12474, 18797 },
+    {       0, 28492,133236, 69654, 52322,-49577,-26430, -3593,     0 }
+   };
+
+/* Tables giving the trigonometric terms to be added to the mean */
+/* elements of the mean longitudes */
+
+   static const double kq[][10] = {
+    {   3086,15746,69613,59899,75645,88306, 12661,  2658,    0,     0 },
+    {  21863,32794,10931,   73, 4387,26934,  1473,  2157,    0,     0 },
+    {     10,16002,21863,10931, 1473,32004,  4387,    73,    0,     0 },
+    {     10, 6345, 7818, 1107,15636, 7077,  8184,   532,   10,     0 },
+    {     19, 1760, 1454,  287, 1167,  880,   574,  2640,   19,  1454 },
+    {     19,  574,  287,  306, 1760,   12,    31,    38,   19,   574 },
+    {      4,  204,  177,    8,   31,  200,  1265,   102,    4,   204 },
+    {      4,  102,  106,    8,   98, 1367,   487,   204,    4,   102 }
+   };
+
+   static const double cl[][10] = {
+    {      21,   -95, -157,   41,   -5,   42,  23,  30,      0,     0 },
+    {    -160,  -313, -235,   60,  -74,  -76, -27,  34,      0,     0 },
+    {    -325,  -322,  -79,  232,  -52,   97,  55, -41,      0,     0 },
+    {    2268,  -979,  802,  602, -668,  -33, 345, 201,    -55,     0 },
+    {    7610, -4997,-7689,-5841,-2617, 1115,-748,-607,   6074,   354 },
+    {  -18549, 30125,20012, -730,  824,   23,1289,-352, -14767, -2062 },
+    { -135245,-14594, 4197,-4030,-5630,-2898,2540,-306,   2939,  1986 },
+    {   89948,  2103, 8963, 2695, 3682, 1648, 866,-154,  -1963,  -283 }
+   };
+
+   static const double sl[][10] = {
+    {   -342,   136,  -23,   62,   66,  -52, -33,    17,     0,     0 },
+    {    524,  -149,  -35,  117,  151,  122, -71,   -62,     0,     0 },
+    {   -105,  -137,  258,   35, -116,  -88,-112,   -80,     0,     0 },
+    {    854,  -205, -936, -240,  140, -341, -97,  -232,   536,     0 },
+    { -56980,  8016, 1012, 1448,-3024,-3710, 318,   503,  3767,   577 },
+    { 138606,-13478,-4964, 1441,-1319,-1482, 427,  1236, -9167, -1918 },
+    {  71234,-41116, 5334,-4935,-1848,   66, 434, -1748,  3780,  -701 },
+    { -47645, 11647, 2166, 3194,  679,    0,-244,  -419, -2531,    48 }
+   };
+
+/*--------------------------------------------------------------------*/
+
+/* Validate the planet number. */
+   if ((np < 1) || (np > 8)) {
+      jstat = -1;
+
+   /* Reset the result in case of failure. */
+      for (k = 0; k < 2; k++) {
+         for (i = 0; i < 3; i++) {
+            pv[k][i] = 0.0;
+         }
+      }
+
+   } else {
+
+   /* Decrement the planet number to start at zero. */
+      np--;
+
+   /* Time: Julian millennia since J2000.0. */
+      t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJM;
+
+   /* OK status unless remote date. */
+      jstat = fabs(t) <= 1.0 ? 0 : 1;
+
+   /* Compute the mean elements. */
+      da = a[np][0] +
+          (a[np][1] +
+           a[np][2] * t) * t;
+      dl = (3600.0 * dlm[np][0] +
+                    (dlm[np][1] +
+                     dlm[np][2] * t) * t) * ERFA_DAS2R;
+      de = e[np][0] +
+         ( e[np][1] +
+           e[np][2] * t) * t;
+      dp = eraAnpm((3600.0 * pi[np][0] +
+                            (pi[np][1] +
+                             pi[np][2] * t) * t) * ERFA_DAS2R);
+      di = (3600.0 * dinc[np][0] +
+                    (dinc[np][1] +
+                     dinc[np][2] * t) * t) * ERFA_DAS2R;
+      dom = eraAnpm((3600.0 * omega[np][0] +
+                             (omega[np][1] +
+                              omega[np][2] * t) * t) * ERFA_DAS2R);
+
+   /* Apply the trigonometric terms. */
+      dmu = 0.35953620 * t;
+      for (k = 0; k < 8; k++) {
+         arga = kp[np][k] * dmu;
+         argl = kq[np][k] * dmu;
+         da += (ca[np][k] * cos(arga) +
+                sa[np][k] * sin(arga)) * 1e-7;
+         dl += (cl[np][k] * cos(argl) +
+                sl[np][k] * sin(argl)) * 1e-7;
+      }
+      arga = kp[np][8] * dmu;
+      da += t * (ca[np][8] * cos(arga) +
+                 sa[np][8] * sin(arga)) * 1e-7;
+      for (k = 8; k < 10; k++) {
+         argl = kq[np][k] * dmu;
+         dl += t * (cl[np][k] * cos(argl) +
+                    sl[np][k] * sin(argl)) * 1e-7;
+      }
+      dl = fmod(dl, ERFA_D2PI);
+
+   /* Iterative soln. of Kepler's equation to get eccentric anomaly. */
+      am = dl - dp;
+      ae = am + de * sin(am);
+      k = 0;
+      dae = 1.0;
+      while (k < KMAX && fabs(dae) > 1e-12) {
+         dae = (am - ae + de * sin(ae)) / (1.0 - de * cos(ae));
+         ae += dae;
+         k++;
+         if (k == KMAX-1) jstat = 2;
+      }
+
+   /* True anomaly. */
+      ae2 = ae / 2.0;
+      at = 2.0 * atan2(sqrt((1.0 + de) / (1.0 - de)) * sin(ae2),
+                                                       cos(ae2));
+
+   /* Distance (AU) and speed (radians per day). */
+      r = da * (1.0 - de * cos(ae));
+      v = GK * sqrt((1.0 + 1.0 / amas[np]) / (da * da * da));
+
+      si2 = sin(di / 2.0);
+      xq = si2 * cos(dom);
+      xp = si2 * sin(dom);
+      tl = at + dp;
+      xsw = sin(tl);
+      xcw = cos(tl);
+      xm2 = 2.0 * (xp * xcw - xq * xsw);
+      xf = da / sqrt(1  -  de * de);
+      ci2 = cos(di / 2.0);
+      xms = (de * sin(dp) + xsw) * xf;
+      xmc = (de * cos(dp) + xcw) * xf;
+      xpxq2 = 2 * xp * xq;
+
+   /* Position (J2000.0 ecliptic x,y,z in AU). */
+      x = r * (xcw - xm2 * xp);
+      y = r * (xsw + xm2 * xq);
+      z = r * (-xm2 * ci2);
+
+   /* Rotate to equatorial. */
+      pv[0][0] = x;
+      pv[0][1] = y * COSEPS - z * SINEPS;
+      pv[0][2] = y * SINEPS + z * COSEPS;
+
+   /* Velocity (J2000.0 ecliptic xdot,ydot,zdot in AU/d). */
+      x = v * (( -1.0 + 2.0 * xp * xp) * xms + xpxq2 * xmc);
+      y = v * ((  1.0 - 2.0 * xq * xq) * xmc - xpxq2 * xms);
+      z = v * (2.0 * ci2 * (xp * xms + xq * xmc));
+
+   /* Rotate to equatorial. */
+      pv[1][0] = x;
+      pv[1][1] = y * COSEPS - z * SINEPS;
+      pv[1][2] = y * SINEPS + z * COSEPS;
+
+   }
+
+/* Return the status. */
+   return jstat;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pm.c b/ast/erfa/pm.c
new file mode 100644
index 0000000..6b08fa7
--- /dev/null
+++ b/ast/erfa/pm.c
@@ -0,0 +1,85 @@
+#include "erfa.h"
+
+double eraPm(double p[3])
+/*
+**  - - - - - -
+**   e r a P m
+**  - - - - - -
+**
+**  Modulus of p-vector.
+**
+**  Given:
+**     p      double[3]     p-vector
+**
+**  Returned (function value):
+**            double        modulus
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   return sqrt( p[0]*p[0] + p[1]*p[1] + p[2]*p[2] );
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pmat00.c b/ast/erfa/pmat00.c
new file mode 100644
index 0000000..e25315a
--- /dev/null
+++ b/ast/erfa/pmat00.c
@@ -0,0 +1,127 @@
+#include "erfa.h"
+
+void eraPmat00(double date1, double date2, double rbp[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a P m a t 0 0
+**  - - - - - - - - - -
+**
+**  Precession matrix (including frame bias) from GCRS to a specified
+**  date, IAU 2000 model.
+**
+**  Given:
+**     date1,date2  double          TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rbp          double[3][3]    bias-precession matrix (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(date) = rbp * V(GCRS), where
+**     the p-vector V(GCRS) is with respect to the Geocentric Celestial
+**     Reference System (IAU, 2000) and the p-vector V(date) is with
+**     respect to the mean equatorial triad of the given date.
+**
+**  Called:
+**     eraBp00      frame bias and precession matrices, IAU 2000
+**
+**  Reference:
+**
+**     IAU: Trans. International Astronomical Union, Vol. XXIVB;  Proc.
+**     24th General Assembly, Manchester, UK.  Resolutions B1.3, B1.6.
+**     (2000)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rb[3][3], rp[3][3];
+
+
+/* Obtain the required matrix (discarding others). */
+   eraBp00(date1, date2, rb, rp, rbp);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pmat06.c b/ast/erfa/pmat06.c
new file mode 100644
index 0000000..364211a
--- /dev/null
+++ b/ast/erfa/pmat06.c
@@ -0,0 +1,131 @@
+#include "erfa.h"
+
+void eraPmat06(double date1, double date2, double rbp[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a P m a t 0 6
+**  - - - - - - - - - -
+**
+**  Precession matrix (including frame bias) from GCRS to a specified
+**  date, IAU 2006 model.
+**
+**  Given:
+**     date1,date2  double          TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rbp          double[3][3]    bias-precession matrix (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(date) = rbp * V(GCRS), where
+**     the p-vector V(GCRS) is with respect to the Geocentric Celestial
+**     Reference System (IAU, 2000) and the p-vector V(date) is with
+**     respect to the mean equatorial triad of the given date.
+**
+**  Called:
+**     eraPfw06     bias-precession F-W angles, IAU 2006
+**     eraFw2m      F-W angles to r-matrix
+**
+**  References:
+**
+**     Capitaine, N. & Wallace, P.T., 2006, Astron.Astrophys. 450, 855
+**
+**     Wallace, P.T. & Capitaine, N., 2006, Astron.Astrophys. 459, 981
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double gamb, phib, psib, epsa;
+
+
+/* Bias-precession Fukushima-Williams angles. */
+   eraPfw06(date1, date2, &gamb, &phib, &psib, &epsa);
+
+/* Form the matrix. */
+   eraFw2m(gamb, phib, psib, epsa, rbp);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pmat76.c b/ast/erfa/pmat76.c
new file mode 100644
index 0000000..c38e6c4
--- /dev/null
+++ b/ast/erfa/pmat76.c
@@ -0,0 +1,150 @@
+#include "erfa.h"
+
+void eraPmat76(double date1, double date2, double rmatp[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a P m a t 7 6
+**  - - - - - - - - - -
+**
+**  Precession matrix from J2000.0 to a specified date, IAU 1976 model.
+**
+**  Given:
+**     date1,date2 double       ending date, TT (Note 1)
+**
+**  Returned:
+**     rmatp       double[3][3] precession matrix, J2000.0 -> date1+date2
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(date) = RMATP * V(J2000),
+**     where the p-vector V(J2000) is with respect to the mean
+**     equatorial triad of epoch J2000.0 and the p-vector V(date)
+**     is with respect to the mean equatorial triad of the given
+**     date.
+**
+**  3) Though the matrix method itself is rigorous, the precession
+**     angles are expressed through canonical polynomials which are
+**     valid only for a limited time span.  In addition, the IAU 1976
+**     precession rate is known to be imperfect.  The absolute accuracy
+**     of the present formulation is better than 0.1 arcsec from
+**     1960AD to 2040AD, better than 1 arcsec from 1640AD to 2360AD,
+**     and remains below 3 arcsec for the whole of the period
+**     500BC to 3000AD.  The errors exceed 10 arcsec outside the
+**     range 1200BC to 3900AD, exceed 100 arcsec outside 4200BC to
+**     5600AD and exceed 1000 arcsec outside 6800BC to 8200AD.
+**
+**  Called:
+**     eraPrec76    accumulated precession angles, IAU 1976
+**     eraIr        initialize r-matrix to identity
+**     eraRz        rotate around Z-axis
+**     eraRy        rotate around Y-axis
+**     eraCr        copy r-matrix
+**
+**  References:
+**
+**     Lieske, J.H., 1979, Astron.Astrophys. 73, 282.
+**      equations (6) & (7), p283.
+**
+**     Kaplan,G.H., 1981. USNO circular no. 163, pA2.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double zeta, z, theta, wmat[3][3];
+
+
+/* Precession Euler angles, J2000.0 to specified date. */
+   eraPrec76(ERFA_DJ00, 0.0, date1, date2, &zeta, &z, &theta);
+
+/* Form the rotation matrix. */
+   eraIr(  wmat);
+   eraRz( -zeta, wmat);
+   eraRy(  theta, wmat);
+   eraRz( -z, wmat);
+   eraCr( wmat, rmatp);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pmp.c b/ast/erfa/pmp.c
new file mode 100644
index 0000000..e11c12e
--- /dev/null
+++ b/ast/erfa/pmp.c
@@ -0,0 +1,94 @@
+#include "erfa.h"
+
+void eraPmp(double a[3], double b[3], double amb[3])
+/*
+**  - - - - - - -
+**   e r a P m p
+**  - - - - - - -
+**
+**  P-vector subtraction.
+**
+**  Given:
+**     a        double[3]      first p-vector
+**     b        double[3]      second p-vector
+**
+**  Returned:
+**     amb      double[3]      a - b
+**
+**  Note:
+**     It is permissible to re-use the same array for any of the
+**     arguments.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   amb[0] = a[0] - b[0];
+   amb[1] = a[1] - b[1];
+   amb[2] = a[2] - b[2];
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pmpx.c b/ast/erfa/pmpx.c
new file mode 100644
index 0000000..034e8f0
--- /dev/null
+++ b/ast/erfa/pmpx.c
@@ -0,0 +1,153 @@
+#include "erfa.h"
+
+void eraPmpx(double rc, double dc, double pr, double pd,
+             double px, double rv, double pmt, double pob[3],
+             double pco[3])
+/*
+**  - - - - - - - -
+**   e r a P m p x
+**  - - - - - - - -
+**
+**  Proper motion and parallax.
+**
+**  Given:
+**     rc,dc  double     ICRS RA,Dec at catalog epoch (radians)
+**     pr     double     RA proper motion (radians/year; Note 1)
+**     pd     double     Dec proper motion (radians/year)
+**     px     double     parallax (arcsec)
+**     rv     double     radial velocity (km/s, +ve if receding)
+**     pmt    double     proper motion time interval (SSB, Julian years)
+**     pob    double[3]  SSB to observer vector (au)
+**
+**  Returned:
+**     pco    double[3]  coordinate direction (BCRS unit vector)
+**
+**  Notes:
+**
+**  1) The proper motion in RA is dRA/dt rather than cos(Dec)*dRA/dt.
+**
+**  2) The proper motion time interval is for when the starlight
+**     reaches the solar system barycenter.
+**
+**  3) To avoid the need for iteration, the Roemer effect (i.e. the
+**     small annual modulation of the proper motion coming from the
+**     changing light time) is applied approximately, using the
+**     direction of the star at the catalog epoch.
+**
+**  References:
+**
+**     1984 Astronomical Almanac, pp B39-B41.
+**
+**     Urban, S. & Seidelmann, P. K. (eds), Explanatory Supplement to
+**     the Astronomical Almanac, 3rd ed., University Science Books
+**     (2013), Section 7.2.
+**
+**  Called:
+**     eraPdp       scalar product of two p-vectors
+**     eraPn        decompose p-vector into modulus and direction
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Km/s to au/year */
+   const double VF = ERFA_DAYSEC*ERFA_DJM/ERFA_DAU;
+
+/* Light time for 1 au, Julian years */
+   const double AULTY = ERFA_AULT/ERFA_DAYSEC/ERFA_DJY;
+
+   int i;
+   double sr, cr, sd, cd, x, y, z, p[3], dt, pxr, w, pdz, pm[3];
+
+
+/* Spherical coordinates to unit vector (and useful functions). */
+   sr = sin(rc);
+   cr = cos(rc);
+   sd = sin(dc);
+   cd = cos(dc);
+   p[0] = x = cr*cd;
+   p[1] = y = sr*cd;
+   p[2] = z = sd;
+
+/* Proper motion time interval (y) including Roemer effect. */
+   dt = pmt + eraPdp(p,pob)*AULTY;
+
+/* Space motion (radians per year). */
+   pxr = px * ERFA_DAS2R;
+   w = VF * rv * pxr;
+   pdz = pd * z;
+   pm[0] = - pr*y - pdz*cr + w*x;
+   pm[1] =   pr*x - pdz*sr + w*y;
+   pm[2] =   pd*cd + w*z;
+
+/* Coordinate direction of star (unit vector, BCRS). */
+   for (i = 0; i < 3; i++) {
+      p[i] += dt*pm[i] - pxr*pob[i];
+   }
+   eraPn(p, &w, pco);
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pmsafe.c b/ast/erfa/pmsafe.c
new file mode 100644
index 0000000..02e5824
--- /dev/null
+++ b/ast/erfa/pmsafe.c
@@ -0,0 +1,206 @@
+#include "erfa.h"
+
+int eraPmsafe(double ra1, double dec1, double pmr1, double pmd1,
+              double px1, double rv1,
+              double ep1a, double ep1b, double ep2a, double ep2b,
+              double *ra2, double *dec2, double *pmr2, double *pmd2,
+              double *px2, double *rv2)
+/*
+**  - - - - - - - - - -
+**   e r a P m s a f e
+**  - - - - - - - - - -
+**
+**  Star proper motion:  update star catalog data for space motion, with
+**  special handling to handle the zero parallax case.
+**
+**  Given:
+**     ra1    double      right ascension (radians), before
+**     dec1   double      declination (radians), before
+**     pmr1   double      RA proper motion (radians/year), before
+**     pmd1   double      Dec proper motion (radians/year), before
+**     px1    double      parallax (arcseconds), before
+**     rv1    double      radial velocity (km/s, +ve = receding), before
+**     ep1a   double      "before" epoch, part A (Note 1)
+**     ep1b   double      "before" epoch, part B (Note 1)
+**     ep2a   double      "after" epoch, part A (Note 1)
+**     ep2b   double      "after" epoch, part B (Note 1)
+**
+**  Returned:
+**     ra2    double      right ascension (radians), after
+**     dec2   double      declination (radians), after
+**     pmr2   double      RA proper motion (radians/year), after
+**     pmd2   double      Dec proper motion (radians/year), after
+**     px2    double      parallax (arcseconds), after
+**     rv2    double      radial velocity (km/s, +ve = receding), after
+**
+**  Returned (function value):
+**            int         status:
+**                         -1 = system error (should not occur)
+**                          0 = no warnings or errors
+**                          1 = distance overridden (Note 6)
+**                          2 = excessive velocity (Note 7)
+**                          4 = solution didn't converge (Note 8)
+**                       else = binary logical OR of the above warnings
+**
+**  Notes:
+**
+**  1) The starting and ending TDB epochs ep1a+ep1b and ep2a+ep2b are
+**     Julian Dates, apportioned in any convenient way between the two
+**     parts (A and B).  For example, JD(TDB)=2450123.7 could be
+**     expressed in any of these ways, among others:
+**
+**            epNa            epNb
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     resolution.  The MJD method and the date & time methods are both
+**     good compromises between resolution and convenience.
+**
+**  2) In accordance with normal star-catalog conventions, the object's
+**     right ascension and declination are freed from the effects of
+**     secular aberration.  The frame, which is aligned to the catalog
+**     equator and equinox, is Lorentzian and centered on the SSB.
+**
+**     The proper motions are the rate of change of the right ascension
+**     and declination at the catalog epoch and are in radians per TDB
+**     Julian year.
+**
+**     The parallax and radial velocity are in the same frame.
+**
+**  3) Care is needed with units.  The star coordinates are in radians
+**     and the proper motions in radians per Julian year, but the
+**     parallax is in arcseconds.
+**
+**  4) The RA proper motion is in terms of coordinate angle, not true
+**     angle.  If the catalog uses arcseconds for both RA and Dec proper
+**     motions, the RA proper motion will need to be divided by cos(Dec)
+**     before use.
+**
+**  5) Straight-line motion at constant speed, in the inertial frame, is
+**     assumed.
+**
+**  6) An extremely small (or zero or negative) parallax is overridden
+**     to ensure that the object is at a finite but very large distance,
+**     but not so large that the proper motion is equivalent to a large
+**     but safe speed (about 0.1c using the chosen constant).  A warning
+**     status of 1 is added to the status if this action has been taken.
+**
+**  7) If the space velocity is a significant fraction of c (see the
+**     constant VMAX in the function eraStarpv), it is arbitrarily set
+**     to zero.  When this action occurs, 2 is added to the status.
+**
+**  8) The relativistic adjustment carried out in the eraStarpv function
+**     involves an iterative calculation.  If the process fails to
+**     converge within a set number of iterations, 4 is added to the
+**     status.
+**
+**  Called:
+**     eraSeps      angle between two points
+**     eraStarpm    update star catalog data for space motion
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* Minimum allowed parallax (arcsec) */
+   const double PXMIN = 5e-7;
+
+/* Factor giving maximum allowed transverse speed of about 1% c */
+   const double F = 326.0;
+
+   int jpx, j;
+   double pm, px1a;
+
+
+/* Proper motion in one year (radians). */
+   pm = eraSeps(ra1, dec1, ra1+pmr1, dec1+pmd1);
+
+/* Override the parallax to reduce the chances of a warning status. */
+   jpx = 0;
+   px1a = px1;
+   pm *= F;
+   if (px1a < pm) {jpx = 1; px1a = pm;}
+   if (px1a < PXMIN) {jpx = 1; px1a = PXMIN;}
+
+/* Carry out the transformation using the modified parallax. */
+   j = eraStarpm(ra1, dec1, pmr1, pmd1, px1a, rv1,
+                 ep1a, ep1b, ep2a, ep2b,
+                 ra2, dec2, pmr2, pmd2, px2, rv2);
+
+/* Revise and return the status. */
+   if ( !(j%2) ) j += jpx;
+   return j;
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pn.c b/ast/erfa/pn.c
new file mode 100644
index 0000000..7bdebbb
--- /dev/null
+++ b/ast/erfa/pn.c
@@ -0,0 +1,118 @@
+#include "erfa.h"
+
+void eraPn(double p[3], double *r, double u[3])
+/*
+**  - - - - - -
+**   e r a P n
+**  - - - - - -
+**
+**  Convert a p-vector into modulus and unit vector.
+**
+**  Given:
+**     p        double[3]      p-vector
+**
+**  Returned:
+**     r        double         modulus
+**     u        double[3]      unit vector
+**
+**  Notes:
+**
+**  1) If p is null, the result is null.  Otherwise the result is a unit
+**     vector.
+**
+**  2) It is permissible to re-use the same array for any of the
+**     arguments.
+**
+**  Called:
+**     eraPm        modulus of p-vector
+**     eraZp        zero p-vector
+**     eraSxp       multiply p-vector by scalar
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double w;
+
+
+/* Obtain the modulus and test for zero. */
+   w = eraPm(p);
+   if (w == 0.0) {
+
+   /* Null vector. */
+      eraZp(u);
+
+   } else {
+
+   /* Unit vector. */
+      eraSxp(1.0/w, p, u);
+   }
+
+/* Return the modulus. */
+   *r = w;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pn00.c b/ast/erfa/pn00.c
new file mode 100644
index 0000000..f088649
--- /dev/null
+++ b/ast/erfa/pn00.c
@@ -0,0 +1,186 @@
+#include "erfa.h"
+
+void eraPn00(double date1, double date2, double dpsi, double deps,
+             double *epsa,
+             double rb[3][3], double rp[3][3], double rbp[3][3],
+             double rn[3][3], double rbpn[3][3])
+/*
+**  - - - - - - - -
+**   e r a P n 0 0
+**  - - - - - - - -
+**
+**  Precession-nutation, IAU 2000 model:  a multi-purpose function,
+**  supporting classical (equinox-based) use directly and CIO-based
+**  use indirectly.
+**
+**  Given:
+**     date1,date2  double          TT as a 2-part Julian Date (Note 1)
+**     dpsi,deps    double          nutation (Note 2)
+**
+**  Returned:
+**     epsa         double          mean obliquity (Note 3)
+**     rb           double[3][3]    frame bias matrix (Note 4)
+**     rp           double[3][3]    precession matrix (Note 5)
+**     rbp          double[3][3]    bias-precession matrix (Note 6)
+**     rn           double[3][3]    nutation matrix (Note 7)
+**     rbpn         double[3][3]    GCRS-to-true matrix (Note 8)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The caller is responsible for providing the nutation components;
+**     they are in longitude and obliquity, in radians and are with
+**     respect to the equinox and ecliptic of date.  For high-accuracy
+**     applications, free core nutation should be included as well as
+**     any other relevant corrections to the position of the CIP.
+**
+**  3) The returned mean obliquity is consistent with the IAU 2000
+**     precession-nutation models.
+**
+**  4) The matrix rb transforms vectors from GCRS to J2000.0 mean
+**     equator and equinox by applying frame bias.
+**
+**  5) The matrix rp transforms vectors from J2000.0 mean equator and
+**     equinox to mean equator and equinox of date by applying
+**     precession.
+**
+**  6) The matrix rbp transforms vectors from GCRS to mean equator and
+**     equinox of date by applying frame bias then precession.  It is
+**     the product rp x rb.
+**
+**  7) The matrix rn transforms vectors from mean equator and equinox of
+**     date to true equator and equinox of date by applying the nutation
+**     (luni-solar + planetary).
+**
+**  8) The matrix rbpn transforms vectors from GCRS to true equator and
+**     equinox of date.  It is the product rn x rbp, applying frame
+**     bias, precession and nutation in that order.
+**
+**  9) It is permissible to re-use the same array in the returned
+**     arguments.  The arrays are filled in the order given.
+**
+**  Called:
+**     eraPr00      IAU 2000 precession adjustments
+**     eraObl80     mean obliquity, IAU 1980
+**     eraBp00      frame bias and precession matrices, IAU 2000
+**     eraCr        copy r-matrix
+**     eraNumat     form nutation matrix
+**     eraRxr       product of two r-matrices
+**
+**  Reference:
+**
+**     Capitaine, N., Chapront, J., Lambert, S. and Wallace, P.,
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154 (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dpsipr, depspr, rbpw[3][3], rnw[3][3];
+
+
+/* IAU 2000 precession-rate adjustments. */
+   eraPr00(date1, date2, &dpsipr, &depspr);
+
+/* Mean obliquity, consistent with IAU 2000 precession-nutation. */
+   *epsa = eraObl80(date1, date2) + depspr;
+
+/* Frame bias and precession matrices and their product. */
+   eraBp00(date1, date2, rb, rp, rbpw);
+   eraCr(rbpw, rbp);
+
+/* Nutation matrix. */
+   eraNumat(*epsa, dpsi, deps, rnw);
+   eraCr(rnw, rn);
+
+/* Bias-precession-nutation matrix (classical). */
+   eraRxr(rnw, rbpw, rbpn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pn00a.c b/ast/erfa/pn00a.c
new file mode 100644
index 0000000..3e47f0e
--- /dev/null
+++ b/ast/erfa/pn00a.c
@@ -0,0 +1,172 @@
+#include "erfa.h"
+
+void eraPn00a(double date1, double date2,
+              double *dpsi, double *deps, double *epsa,
+              double rb[3][3], double rp[3][3], double rbp[3][3],
+              double rn[3][3], double rbpn[3][3])
+/*
+**  - - - - - - - - -
+**   e r a P n 0 0 a
+**  - - - - - - - - -
+**
+**  Precession-nutation, IAU 2000A model:  a multi-purpose function,
+**  supporting classical (equinox-based) use directly and CIO-based
+**  use indirectly.
+**
+**  Given:
+**     date1,date2  double          TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     dpsi,deps    double          nutation (Note 2)
+**     epsa         double          mean obliquity (Note 3)
+**     rb           double[3][3]    frame bias matrix (Note 4)
+**     rp           double[3][3]    precession matrix (Note 5)
+**     rbp          double[3][3]    bias-precession matrix (Note 6)
+**     rn           double[3][3]    nutation matrix (Note 7)
+**     rbpn         double[3][3]    GCRS-to-true matrix (Notes 8,9)
+**
+**  Notes:
+**
+**  1)  The TT date date1+date2 is a Julian Date, apportioned in any
+**      convenient way between the two arguments.  For example,
+**      JD(TT)=2450123.7 could be expressed in any of these ways,
+**      among others:
+**
+**             date1          date2
+**
+**          2450123.7           0.0       (JD method)
+**          2451545.0       -1421.3       (J2000 method)
+**          2400000.5       50123.2       (MJD method)
+**          2450123.5           0.2       (date & time method)
+**
+**      The JD method is the most natural and convenient to use in
+**      cases where the loss of several decimal digits of resolution
+**      is acceptable.  The J2000 method is best matched to the way
+**      the argument is handled internally and will deliver the
+**      optimum resolution.  The MJD method and the date & time methods
+**      are both good compromises between resolution and convenience.
+**
+**  2)  The nutation components (luni-solar + planetary, IAU 2000A) in
+**      longitude and obliquity are in radians and with respect to the
+**      equinox and ecliptic of date.  Free core nutation is omitted;
+**      for the utmost accuracy, use the eraPn00  function, where the
+**      nutation components are caller-specified.  For faster but
+**      slightly less accurate results, use the eraPn00b function.
+**
+**  3)  The mean obliquity is consistent with the IAU 2000 precession.
+**
+**  4)  The matrix rb transforms vectors from GCRS to J2000.0 mean
+**      equator and equinox by applying frame bias.
+**
+**  5)  The matrix rp transforms vectors from J2000.0 mean equator and
+**      equinox to mean equator and equinox of date by applying
+**      precession.
+**
+**  6)  The matrix rbp transforms vectors from GCRS to mean equator and
+**      equinox of date by applying frame bias then precession.  It is
+**      the product rp x rb.
+**
+**  7)  The matrix rn transforms vectors from mean equator and equinox
+**      of date to true equator and equinox of date by applying the
+**      nutation (luni-solar + planetary).
+**
+**  8)  The matrix rbpn transforms vectors from GCRS to true equator and
+**      equinox of date.  It is the product rn x rbp, applying frame
+**      bias, precession and nutation in that order.
+**
+**  9)  The X,Y,Z coordinates of the IAU 2000A Celestial Intermediate
+**      Pole are elements (3,1-3) of the GCRS-to-true matrix,
+**      i.e. rbpn[2][0-2].
+**
+**  10) It is permissible to re-use the same array in the returned
+**      arguments.  The arrays are filled in the order given.
+**
+**  Called:
+**     eraNut00a    nutation, IAU 2000A
+**     eraPn00      bias/precession/nutation results, IAU 2000
+**
+**  Reference:
+**
+**     Capitaine, N., Chapront, J., Lambert, S. and Wallace, P.,
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154 (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Nutation. */
+   eraNut00a(date1, date2, dpsi, deps);
+
+/* Remaining results. */
+   eraPn00(date1, date2, *dpsi, *deps, epsa, rb, rp, rbp, rn, rbpn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pn00b.c b/ast/erfa/pn00b.c
new file mode 100644
index 0000000..d93376f
--- /dev/null
+++ b/ast/erfa/pn00b.c
@@ -0,0 +1,172 @@
+#include "erfa.h"
+
+void eraPn00b(double date1, double date2,
+              double *dpsi, double *deps, double *epsa,
+              double rb[3][3], double rp[3][3], double rbp[3][3],
+              double rn[3][3], double rbpn[3][3])
+/*
+**  - - - - - - - - -
+**   e r a P n 0 0 b
+**  - - - - - - - - -
+**
+**  Precession-nutation, IAU 2000B model:  a multi-purpose function,
+**  supporting classical (equinox-based) use directly and CIO-based
+**  use indirectly.
+**
+**  Given:
+**     date1,date2  double          TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     dpsi,deps    double          nutation (Note 2)
+**     epsa         double          mean obliquity (Note 3)
+**     rb           double[3][3]    frame bias matrix (Note 4)
+**     rp           double[3][3]    precession matrix (Note 5)
+**     rbp          double[3][3]    bias-precession matrix (Note 6)
+**     rn           double[3][3]    nutation matrix (Note 7)
+**     rbpn         double[3][3]    GCRS-to-true matrix (Notes 8,9)
+**
+**  Notes:
+**
+**  1)  The TT date date1+date2 is a Julian Date, apportioned in any
+**      convenient way between the two arguments.  For example,
+**      JD(TT)=2450123.7 could be expressed in any of these ways,
+**      among others:
+**
+**             date1          date2
+**
+**          2450123.7           0.0       (JD method)
+**          2451545.0       -1421.3       (J2000 method)
+**          2400000.5       50123.2       (MJD method)
+**          2450123.5           0.2       (date & time method)
+**
+**      The JD method is the most natural and convenient to use in
+**      cases where the loss of several decimal digits of resolution
+**      is acceptable.  The J2000 method is best matched to the way
+**      the argument is handled internally and will deliver the
+**      optimum resolution.  The MJD method and the date & time methods
+**      are both good compromises between resolution and convenience.
+**
+**  2)  The nutation components (luni-solar + planetary, IAU 2000B) in
+**      longitude and obliquity are in radians and with respect to the
+**      equinox and ecliptic of date.  For more accurate results, but
+**      at the cost of increased computation, use the eraPn00a function.
+**      For the utmost accuracy, use the eraPn00  function, where the
+**      nutation components are caller-specified.
+**
+**  3)  The mean obliquity is consistent with the IAU 2000 precession.
+**
+**  4)  The matrix rb transforms vectors from GCRS to J2000.0 mean
+**      equator and equinox by applying frame bias.
+**
+**  5)  The matrix rp transforms vectors from J2000.0 mean equator and
+**      equinox to mean equator and equinox of date by applying
+**      precession.
+**
+**  6)  The matrix rbp transforms vectors from GCRS to mean equator and
+**      equinox of date by applying frame bias then precession.  It is
+**      the product rp x rb.
+**
+**  7)  The matrix rn transforms vectors from mean equator and equinox
+**      of date to true equator and equinox of date by applying the
+**      nutation (luni-solar + planetary).
+**
+**  8)  The matrix rbpn transforms vectors from GCRS to true equator and
+**      equinox of date.  It is the product rn x rbp, applying frame
+**      bias, precession and nutation in that order.
+**
+**  9)  The X,Y,Z coordinates of the IAU 2000B Celestial Intermediate
+**      Pole are elements (3,1-3) of the GCRS-to-true matrix,
+**      i.e. rbpn[2][0-2].
+**
+**  10) It is permissible to re-use the same array in the returned
+**      arguments.  The arrays are filled in the stated order.
+**
+**  Called:
+**     eraNut00b    nutation, IAU 2000B
+**     eraPn00      bias/precession/nutation results, IAU 2000
+**
+**  Reference:
+**
+**     Capitaine, N., Chapront, J., Lambert, S. and Wallace, P.,
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154 (2003).
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Nutation. */
+   eraNut00b(date1, date2, dpsi, deps);
+
+/* Remaining results. */
+   eraPn00(date1, date2, *dpsi, *deps, epsa, rb, rp, rbp, rn, rbpn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pn06.c b/ast/erfa/pn06.c
new file mode 100644
index 0000000..63cf117
--- /dev/null
+++ b/ast/erfa/pn06.c
@@ -0,0 +1,196 @@
+#include "erfa.h"
+
+void eraPn06(double date1, double date2, double dpsi, double deps,
+             double *epsa,
+             double rb[3][3], double rp[3][3], double rbp[3][3],
+             double rn[3][3], double rbpn[3][3])
+/*
+**  - - - - - - - -
+**   e r a P n 0 6
+**  - - - - - - - -
+**
+**  Precession-nutation, IAU 2006 model:  a multi-purpose function,
+**  supporting classical (equinox-based) use directly and CIO-based use
+**  indirectly.
+**
+**  Given:
+**     date1,date2  double          TT as a 2-part Julian Date (Note 1)
+**     dpsi,deps    double          nutation (Note 2)
+**
+**  Returned:
+**     epsa         double          mean obliquity (Note 3)
+**     rb           double[3][3]    frame bias matrix (Note 4)
+**     rp           double[3][3]    precession matrix (Note 5)
+**     rbp          double[3][3]    bias-precession matrix (Note 6)
+**     rn           double[3][3]    nutation matrix (Note 7)
+**     rbpn         double[3][3]    GCRS-to-true matrix (Note 8)
+**
+**  Notes:
+**
+**  1)  The TT date date1+date2 is a Julian Date, apportioned in any
+**      convenient way between the two arguments.  For example,
+**      JD(TT)=2450123.7 could be expressed in any of these ways,
+**      among others:
+**
+**             date1          date2
+**
+**          2450123.7           0.0       (JD method)
+**          2451545.0       -1421.3       (J2000 method)
+**          2400000.5       50123.2       (MJD method)
+**          2450123.5           0.2       (date & time method)
+**
+**      The JD method is the most natural and convenient to use in
+**      cases where the loss of several decimal digits of resolution
+**      is acceptable.  The J2000 method is best matched to the way
+**      the argument is handled internally and will deliver the
+**      optimum resolution.  The MJD method and the date & time methods
+**      are both good compromises between resolution and convenience.
+**
+**  2)  The caller is responsible for providing the nutation components;
+**      they are in longitude and obliquity, in radians and are with
+**      respect to the equinox and ecliptic of date.  For high-accuracy
+**      applications, free core nutation should be included as well as
+**      any other relevant corrections to the position of the CIP.
+**
+**  3)  The returned mean obliquity is consistent with the IAU 2006
+**      precession.
+**
+**  4)  The matrix rb transforms vectors from GCRS to J2000.0 mean
+**      equator and equinox by applying frame bias.
+**
+**  5)  The matrix rp transforms vectors from J2000.0 mean equator and
+**      equinox to mean equator and equinox of date by applying
+**      precession.
+**
+**  6)  The matrix rbp transforms vectors from GCRS to mean equator and
+**      equinox of date by applying frame bias then precession.  It is
+**      the product rp x rb.
+**
+**  7)  The matrix rn transforms vectors from mean equator and equinox
+**      of date to true equator and equinox of date by applying the
+**      nutation (luni-solar + planetary).
+**
+**  8)  The matrix rbpn transforms vectors from GCRS to true equator and
+**      equinox of date.  It is the product rn x rbp, applying frame
+**      bias, precession and nutation in that order.
+**
+**  9)  The X,Y,Z coordinates of the Celestial Intermediate Pole are
+**      elements (3,1-3) of the GCRS-to-true matrix, i.e. rbpn[2][0-2].
+**
+**  10) It is permissible to re-use the same array in the returned
+**      arguments.  The arrays are filled in the stated order.
+**
+**  Called:
+**     eraPfw06     bias-precession F-W angles, IAU 2006
+**     eraFw2m      F-W angles to r-matrix
+**     eraCr        copy r-matrix
+**     eraTr        transpose r-matrix
+**     eraRxr       product of two r-matrices
+**
+**  References:
+**
+**     Capitaine, N. & Wallace, P.T., 2006, Astron.Astrophys. 450, 855
+**
+**     Wallace, P.T. & Capitaine, N., 2006, Astron.Astrophys. 459, 981
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double gamb, phib, psib, eps, r1[3][3], r2[3][3], rt[3][3];
+
+
+/* Bias-precession Fukushima-Williams angles of J2000.0 = frame bias. */
+   eraPfw06(ERFA_DJM0, ERFA_DJM00, &gamb, &phib, &psib, &eps);
+
+/* B matrix. */
+   eraFw2m(gamb, phib, psib, eps, r1);
+   eraCr(r1, rb);
+
+/* Bias-precession Fukushima-Williams angles of date. */
+   eraPfw06(date1, date2, &gamb, &phib, &psib, &eps);
+
+/* Bias-precession matrix. */
+   eraFw2m(gamb, phib, psib, eps, r2);
+   eraCr(r2, rbp);
+
+/* Solve for precession matrix. */
+   eraTr(r1, rt);
+   eraRxr(r2, rt, rp);
+
+/* Equinox-based bias-precession-nutation matrix. */
+   eraFw2m(gamb, phib, psib + dpsi, eps + deps, r1);
+   eraCr(r1, rbpn);
+
+/* Solve for nutation matrix. */
+   eraTr(r2, rt);
+   eraRxr(r1, rt, rn);
+
+/* Obliquity, mean of date. */
+   *epsa = eps;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pn06a.c b/ast/erfa/pn06a.c
new file mode 100644
index 0000000..69948a3
--- /dev/null
+++ b/ast/erfa/pn06a.c
@@ -0,0 +1,162 @@
+#include "erfa.h"
+
+void eraPn06a(double date1, double date2,
+              double *dpsi, double *deps, double *epsa,
+              double rb[3][3], double rp[3][3], double rbp[3][3],
+              double rn[3][3], double rbpn[3][3])
+/*
+**  - - - - - - - - -
+**   e r a P n 0 6 a
+**  - - - - - - - - -
+**
+**  Precession-nutation, IAU 2006/2000A models:  a multi-purpose function,
+**  supporting classical (equinox-based) use directly and CIO-based use
+**  indirectly.
+**
+**  Given:
+**     date1,date2  double          TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     dpsi,deps    double          nutation (Note 2)
+**     epsa         double          mean obliquity (Note 3)
+**     rb           double[3][3]    frame bias matrix (Note 4)
+**     rp           double[3][3]    precession matrix (Note 5)
+**     rbp          double[3][3]    bias-precession matrix (Note 6)
+**     rn           double[3][3]    nutation matrix (Note 7)
+**     rbpn         double[3][3]    GCRS-to-true matrix (Notes 8,9)
+**
+**  Notes:
+**
+**  1)  The TT date date1+date2 is a Julian Date, apportioned in any
+**      convenient way between the two arguments.  For example,
+**      JD(TT)=2450123.7 could be expressed in any of these ways,
+**      among others:
+**
+**             date1          date2
+**
+**          2450123.7           0.0       (JD method)
+**          2451545.0       -1421.3       (J2000 method)
+**          2400000.5       50123.2       (MJD method)
+**          2450123.5           0.2       (date & time method)
+**
+**      The JD method is the most natural and convenient to use in
+**      cases where the loss of several decimal digits of resolution
+**      is acceptable.  The J2000 method is best matched to the way
+**      the argument is handled internally and will deliver the
+**      optimum resolution.  The MJD method and the date & time methods
+**      are both good compromises between resolution and convenience.
+**
+**  2)  The nutation components (luni-solar + planetary, IAU 2000A) in
+**      longitude and obliquity are in radians and with respect to the
+**      equinox and ecliptic of date.  Free core nutation is omitted;
+**      for the utmost accuracy, use the eraPn06 function, where the
+**      nutation components are caller-specified.
+**
+**  3)  The mean obliquity is consistent with the IAU 2006 precession.
+**
+**  4)  The matrix rb transforms vectors from GCRS to mean J2000.0 by
+**      applying frame bias.
+**
+**  5)  The matrix rp transforms vectors from mean J2000.0 to mean of
+**      date by applying precession.
+**
+**  6)  The matrix rbp transforms vectors from GCRS to mean of date by
+**      applying frame bias then precession.  It is the product rp x rb.
+**
+**  7)  The matrix rn transforms vectors from mean of date to true of
+**      date by applying the nutation (luni-solar + planetary).
+**
+**  8)  The matrix rbpn transforms vectors from GCRS to true of date
+**      (CIP/equinox).  It is the product rn x rbp, applying frame bias,
+**      precession and nutation in that order.
+**
+**  9)  The X,Y,Z coordinates of the IAU 2006/2000A Celestial
+**      Intermediate Pole are elements (3,1-3) of the GCRS-to-true
+**      matrix, i.e. rbpn[2][0-2].
+**
+**  10) It is permissible to re-use the same array in the returned
+**      arguments.  The arrays are filled in the stated order.
+**
+**  Called:
+**     eraNut06a    nutation, IAU 2006/2000A
+**     eraPn06      bias/precession/nutation results, IAU 2006
+**
+**  Reference:
+**
+**     Capitaine, N. & Wallace, P.T., 2006, Astron.Astrophys. 450, 855
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Nutation. */
+   eraNut06a(date1, date2, dpsi, deps);
+
+/* Remaining results. */
+   eraPn06(date1, date2, *dpsi, *deps, epsa, rb, rp, rbp, rn, rbpn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pnm00a.c b/ast/erfa/pnm00a.c
new file mode 100644
index 0000000..9e70aa9
--- /dev/null
+++ b/ast/erfa/pnm00a.c
@@ -0,0 +1,130 @@
+#include "erfa.h"
+
+void eraPnm00a(double date1, double date2, double rbpn[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a P n m 0 0 a
+**  - - - - - - - - - -
+**
+**  Form the matrix of precession-nutation for a given date (including
+**  frame bias), equinox-based, IAU 2000A model.
+**
+**  Given:
+**     date1,date2  double     TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rbpn         double[3][3]    classical NPB matrix (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(date) = rbpn * V(GCRS), where
+**     the p-vector V(date) is with respect to the true equatorial triad
+**     of date date1+date2 and the p-vector V(GCRS) is with respect to
+**     the Geocentric Celestial Reference System (IAU, 2000).
+**
+**  3) A faster, but slightly less accurate result (about 1 mas), can be
+**     obtained by using instead the eraPnm00b function.
+**
+**  Called:
+**     eraPn00a     bias/precession/nutation, IAU 2000A
+**
+**  Reference:
+**
+**     IAU: Trans. International Astronomical Union, Vol. XXIVB;  Proc.
+**     24th General Assembly, Manchester, UK.  Resolutions B1.3, B1.6.
+**     (2000)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dpsi, deps, epsa, rb[3][3], rp[3][3], rbp[3][3], rn[3][3];
+
+
+/* Obtain the required matrix (discarding other results). */
+   eraPn00a(date1, date2, &dpsi, &deps, &epsa, rb, rp, rbp, rn, rbpn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pnm00b.c b/ast/erfa/pnm00b.c
new file mode 100644
index 0000000..5989b85
--- /dev/null
+++ b/ast/erfa/pnm00b.c
@@ -0,0 +1,130 @@
+#include "erfa.h"
+
+void eraPnm00b(double date1, double date2, double rbpn[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a P n m 0 0 b
+**  - - - - - - - - - -
+**
+**  Form the matrix of precession-nutation for a given date (including
+**  frame bias), equinox-based, IAU 2000B model.
+**
+**  Given:
+**     date1,date2 double       TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rbpn        double[3][3] bias-precession-nutation matrix (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(date) = rbpn * V(GCRS), where
+**     the p-vector V(date) is with respect to the true equatorial triad
+**     of date date1+date2 and the p-vector V(GCRS) is with respect to
+**     the Geocentric Celestial Reference System (IAU, 2000).
+**
+**  3) The present function is faster, but slightly less accurate (about
+**     1 mas), than the eraPnm00a function.
+**
+**  Called:
+**     eraPn00b     bias/precession/nutation, IAU 2000B
+**
+**  Reference:
+**
+**     IAU: Trans. International Astronomical Union, Vol. XXIVB;  Proc.
+**     24th General Assembly, Manchester, UK.  Resolutions B1.3, B1.6.
+**     (2000)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dpsi, deps, epsa, rb[3][3], rp[3][3], rbp[3][3], rn[3][3];
+
+
+/* Obtain the required matrix (discarding other results). */
+   eraPn00b(date1, date2, &dpsi, &deps, &epsa, rb, rp, rbp, rn, rbpn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pnm06a.c b/ast/erfa/pnm06a.c
new file mode 100644
index 0000000..f587721
--- /dev/null
+++ b/ast/erfa/pnm06a.c
@@ -0,0 +1,133 @@
+#include "erfa.h"
+
+void eraPnm06a(double date1, double date2, double rnpb[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a P n m 0 6 a
+**  - - - - - - - - - -
+**
+**  Form the matrix of precession-nutation for a given date (including
+**  frame bias), IAU 2006 precession and IAU 2000A nutation models.
+**
+**  Given:
+**     date1,date2 double       TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     rnpb        double[3][3] bias-precession-nutation matrix (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(date) = rnpb * V(GCRS), where
+**     the p-vector V(date) is with respect to the true equatorial triad
+**     of date date1+date2 and the p-vector V(GCRS) is with respect to
+**     the Geocentric Celestial Reference System (IAU, 2000).
+**
+**  Called:
+**     eraPfw06     bias-precession F-W angles, IAU 2006
+**     eraNut06a    nutation, IAU 2006/2000A
+**     eraFw2m      F-W angles to r-matrix
+**
+**  Reference:
+**
+**     Capitaine, N. & Wallace, P.T., 2006, Astron.Astrophys. 450, 855.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double gamb, phib, psib, epsa, dp, de;
+
+
+/* Fukushima-Williams angles for frame bias and precession. */
+   eraPfw06(date1, date2, &gamb, &phib, &psib, &epsa);
+
+/* Nutation components. */
+   eraNut06a(date1, date2, &dp, &de);
+
+/* Equinox based nutation x precession x bias matrix. */
+   eraFw2m(gamb, phib, psib + dp, epsa + de, rnpb);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pnm80.c b/ast/erfa/pnm80.c
new file mode 100644
index 0000000..a62f937
--- /dev/null
+++ b/ast/erfa/pnm80.c
@@ -0,0 +1,135 @@
+#include "erfa.h"
+
+void eraPnm80(double date1, double date2, double rmatpn[3][3])
+/*
+**  - - - - - - - - -
+**   e r a P n m 8 0
+**  - - - - - - - - -
+**
+**  Form the matrix of precession/nutation for a given date, IAU 1976
+**  precession model, IAU 1980 nutation model.
+**
+**  Given:
+**     date1,date2    double         TDB date (Note 1)
+**
+**  Returned:
+**     rmatpn         double[3][3]   combined precession/nutation matrix
+**
+**  Notes:
+**
+**  1) The TDB date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TDB)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The matrix operates in the sense V(date) = rmatpn * V(J2000),
+**     where the p-vector V(date) is with respect to the true equatorial
+**     triad of date date1+date2 and the p-vector V(J2000) is with
+**     respect to the mean equatorial triad of epoch J2000.0.
+**
+**  Called:
+**     eraPmat76    precession matrix, IAU 1976
+**     eraNutm80    nutation matrix, IAU 1980
+**     eraRxr       product of two r-matrices
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992),
+**     Section 3.3 (p145).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rmatp[3][3], rmatn[3][3];
+
+
+/* Precession matrix, J2000.0 to date. */
+   eraPmat76(date1, date2, rmatp);
+
+/* Nutation matrix. */
+   eraNutm80(date1, date2, rmatn);
+
+/* Combine the matrices:  PN = N x P. */
+   eraRxr(rmatn, rmatp, rmatpn);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pom00.c b/ast/erfa/pom00.c
new file mode 100644
index 0000000..0f61496
--- /dev/null
+++ b/ast/erfa/pom00.c
@@ -0,0 +1,124 @@
+#include "erfa.h"
+
+void eraPom00(double xp, double yp, double sp, double rpom[3][3])
+/*
+**  - - - - - - - - - -
+**   e r a P o m 0 0
+**  - - - - - - - - - -
+**
+**  Form the matrix of polar motion for a given date, IAU 2000.
+**
+**  Given:
+**     xp,yp    double    coordinates of the pole (radians, Note 1)
+**     sp       double    the TIO locator s' (radians, Note 2)
+**
+**  Returned:
+**     rpom     double[3][3]   polar-motion matrix (Note 3)
+**
+**  Notes:
+**
+**  1) The arguments xp and yp are the coordinates (in radians) of the
+**     Celestial Intermediate Pole with respect to the International
+**     Terrestrial Reference System (see IERS Conventions 2003),
+**     measured along the meridians to 0 and 90 deg west respectively.
+**
+**  2) The argument sp is the TIO locator s', in radians, which
+**     positions the Terrestrial Intermediate Origin on the equator.  It
+**     is obtained from polar motion observations by numerical
+**     integration, and so is in essence unpredictable.  However, it is
+**     dominated by a secular drift of about 47 microarcseconds per
+**     century, and so can be taken into account by using s' = -47*t,
+**     where t is centuries since J2000.0.  The function eraSp00
+**     implements this approximation.
+**
+**  3) The matrix operates in the sense V(TRS) = rpom * V(CIP), meaning
+**     that it is the final rotation when computing the pointing
+**     direction to a celestial source.
+**
+**  Called:
+**     eraIr        initialize r-matrix to identity
+**     eraRz        rotate around Z-axis
+**     eraRy        rotate around Y-axis
+**     eraRx        rotate around X-axis
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* Construct the matrix. */
+   eraIr(rpom);
+   eraRz(sp, rpom);
+   eraRy(-xp, rpom);
+   eraRx(-yp, rpom);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ppp.c b/ast/erfa/ppp.c
new file mode 100644
index 0000000..26cb957
--- /dev/null
+++ b/ast/erfa/ppp.c
@@ -0,0 +1,94 @@
+#include "erfa.h"
+
+void eraPpp(double a[3], double b[3], double apb[3])
+/*
+**  - - - - - - -
+**   e r a P p p
+**  - - - - - - -
+**
+**  P-vector addition.
+**
+**  Given:
+**     a        double[3]      first p-vector
+**     b        double[3]      second p-vector
+**
+**  Returned:
+**     apb      double[3]      a + b
+**
+**  Note:
+**     It is permissible to re-use the same array for any of the
+**     arguments.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   apb[0] = a[0] + b[0];
+   apb[1] = a[1] + b[1];
+   apb[2] = a[2] + b[2];
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ppsp.c b/ast/erfa/ppsp.c
new file mode 100644
index 0000000..c58cbcb
--- /dev/null
+++ b/ast/erfa/ppsp.c
@@ -0,0 +1,103 @@
+#include "erfa.h"
+
+void eraPpsp(double a[3], double s, double b[3], double apsb[3])
+/*
+**  - - - - - - - -
+**   e r a P p s p
+**  - - - - - - - -
+**
+**  P-vector plus scaled p-vector.
+**
+**  Given:
+**     a      double[3]     first p-vector
+**     s      double        scalar (multiplier for b)
+**     b      double[3]     second p-vector
+**
+**  Returned:
+**     apsb   double[3]     a + s*b
+**
+**  Note:
+**     It is permissible for any of a, b and apsb to be the same array.
+**
+**  Called:
+**     eraSxp       multiply p-vector by scalar
+**     eraPpp       p-vector plus p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double sb[3];
+
+
+/* s*b. */
+   eraSxp(s, b, sb);
+
+/* a + s*b. */
+   eraPpp(a, sb, apsb);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pr00.c b/ast/erfa/pr00.c
new file mode 100644
index 0000000..0cdf7d3
--- /dev/null
+++ b/ast/erfa/pr00.c
@@ -0,0 +1,151 @@
+#include "erfa.h"
+
+void eraPr00(double date1, double date2, double *dpsipr, double *depspr)
+/*
+**  - - - - - - - -
+**   e r a P r 0 0
+**  - - - - - - - -
+**
+**  Precession-rate part of the IAU 2000 precession-nutation models
+**  (part of MHB2000).
+**
+**  Given:
+**     date1,date2    double  TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     dpsipr,depspr  double  precession corrections (Notes 2,3)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The precession adjustments are expressed as "nutation
+**     components", corrections in longitude and obliquity with respect
+**     to the J2000.0 equinox and ecliptic.
+**
+**  3) Although the precession adjustments are stated to be with respect
+**     to Lieske et al. (1977), the MHB2000 model does not specify which
+**     set of Euler angles are to be used and how the adjustments are to
+**     be applied.  The most literal and straightforward procedure is to
+**     adopt the 4-rotation epsilon_0, psi_A, omega_A, xi_A option, and
+**     to add dpsipr to psi_A and depspr to both omega_A and eps_A.
+**
+**  4) This is an implementation of one aspect of the IAU 2000A nutation
+**     model, formally adopted by the IAU General Assembly in 2000,
+**     namely MHB2000 (Mathews et al. 2002).
+**
+**  References:
+**
+**     Lieske, J.H., Lederle, T., Fricke, W. & Morando, B., "Expressions
+**     for the precession quantities based upon the IAU (1976) System of
+**     Astronomical Constants", Astron.Astrophys., 58, 1-16 (1977)
+**
+**     Mathews, P.M., Herring, T.A., Buffet, B.A., "Modeling of nutation
+**     and precession   New nutation series for nonrigid Earth and
+**     insights into the Earth's interior", J.Geophys.Res., 107, B4,
+**     2002.  The MHB2000 code itself was obtained on 9th September 2002
+**     from ftp://maia.usno.navy.mil/conv2000/chapter5/IAU2000A.
+**
+**     Wallace, P.T., "Software for Implementing the IAU 2000
+**     Resolutions", in IERS Workshop 5.1 (2002).
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t;
+
+/* Precession and obliquity corrections (radians per century) */
+   static const double PRECOR = -0.29965 * ERFA_DAS2R,
+                       OBLCOR = -0.02524 * ERFA_DAS2R;
+
+
+/* Interval between fundamental epoch J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Precession rate contributions with respect to IAU 1976/80. */
+   *dpsipr = PRECOR * t;
+   *depspr = OBLCOR * t;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/prec76.c b/ast/erfa/prec76.c
new file mode 100644
index 0000000..4c47985
--- /dev/null
+++ b/ast/erfa/prec76.c
@@ -0,0 +1,157 @@
+#include "erfa.h"
+
+void eraPrec76(double date01, double date02, double date11, double date12,
+               double *zeta, double *z, double *theta)
+/*
+**  - - - - - - - - - -
+**   e r a P r e c 7 6
+**  - - - - - - - - - -
+**
+**  IAU 1976 precession model.
+**
+**  This function forms the three Euler angles which implement general
+**  precession between two dates, using the IAU 1976 model (as for the
+**  FK5 catalog).
+**
+**  Given:
+**     date01,date02   double    TDB starting date (Note 1)
+**     date11,date12   double    TDB ending date (Note 1)
+**
+**  Returned:
+**     zeta            double    1st rotation: radians cw around z
+**     z               double    3rd rotation: radians cw around z
+**     theta           double    2nd rotation: radians ccw around y
+**
+**  Notes:
+**
+**  1) The dates date01+date02 and date11+date12 are Julian Dates,
+**     apportioned in any convenient way between the arguments daten1
+**     and daten2.  For example, JD(TDB)=2450123.7 could be expressed in
+**     any of these ways, among others:
+**
+**           daten1        daten2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in cases
+**     where the loss of several decimal digits of resolution is
+**     acceptable.  The J2000 method is best matched to the way the
+**     argument is handled internally and will deliver the optimum
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**     The two dates may be expressed using different methods, but at
+**     the risk of losing some resolution.
+**
+**  2) The accumulated precession angles zeta, z, theta are expressed
+**     through canonical polynomials which are valid only for a limited
+**     time span.  In addition, the IAU 1976 precession rate is known to
+**     be imperfect.  The absolute accuracy of the present formulation
+**     is better than 0.1 arcsec from 1960AD to 2040AD, better than
+**     1 arcsec from 1640AD to 2360AD, and remains below 3 arcsec for
+**     the whole of the period 500BC to 3000AD.  The errors exceed
+**     10 arcsec outside the range 1200BC to 3900AD, exceed 100 arcsec
+**     outside 4200BC to 5600AD and exceed 1000 arcsec outside 6800BC to
+**     8200AD.
+**
+**  3) The three angles are returned in the conventional order, which
+**     is not the same as the order of the corresponding Euler
+**     rotations.  The precession matrix is
+**     R_3(-z) x R_2(+theta) x R_3(-zeta).
+**
+**  Reference:
+**
+**     Lieske, J.H., 1979, Astron.Astrophys. 73, 282, equations
+**     (6) & (7), p283.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t0, t, tas2r, w;
+
+
+/* Interval between fundamental epoch J2000.0 and start date (JC). */
+   t0 = ((date01 - ERFA_DJ00) + date02) / ERFA_DJC;
+
+/* Interval over which precession required (JC). */
+   t = ((date11 - date01) + (date12 - date02)) / ERFA_DJC;
+
+/* Euler angles. */
+   tas2r = t * ERFA_DAS2R;
+   w = 2306.2181 + (1.39656 - 0.000139 * t0) * t0;
+
+   *zeta = (w + ((0.30188 - 0.000344 * t0) + 0.017998 * t) * t) * tas2r;
+
+   *z = (w + ((1.09468 + 0.000066 * t0) + 0.018203 * t) * t) * tas2r;
+
+   *theta = ((2004.3109 + (-0.85330 - 0.000217 * t0) * t0)
+          + ((-0.42665 - 0.000217 * t0) - 0.041833 * t) * t) * tas2r;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pv2p.c b/ast/erfa/pv2p.c
new file mode 100644
index 0000000..b6ea164
--- /dev/null
+++ b/ast/erfa/pv2p.c
@@ -0,0 +1,90 @@
+#include "erfa.h"
+
+void eraPv2p(double pv[2][3], double p[3])
+/*
+**  - - - - - - - -
+**   e r a P v 2 p
+**  - - - - - - - -
+**
+**  Discard velocity component of a pv-vector.
+**
+**  Given:
+**     pv      double[2][3]     pv-vector
+**
+**  Returned:
+**     p       double[3]        p-vector
+**
+**  Called:
+**     eraCp        copy p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraCp(pv[0], p);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pv2s.c b/ast/erfa/pv2s.c
new file mode 100644
index 0000000..58d86a9
--- /dev/null
+++ b/ast/erfa/pv2s.c
@@ -0,0 +1,153 @@
+#include "erfa.h"
+
+void eraPv2s(double pv[2][3],
+             double *theta, double *phi, double *r,
+             double *td, double *pd, double *rd)
+/*
+**  - - - - - - - -
+**   e r a P v 2 s
+**  - - - - - - - -
+**
+**  Convert position/velocity from Cartesian to spherical coordinates.
+**
+**  Given:
+**     pv       double[2][3]  pv-vector
+**
+**  Returned:
+**     theta    double        longitude angle (radians)
+**     phi      double        latitude angle (radians)
+**     r        double        radial distance
+**     td       double        rate of change of theta
+**     pd       double        rate of change of phi
+**     rd       double        rate of change of r
+**
+**  Notes:
+**
+**  1) If the position part of pv is null, theta, phi, td and pd
+**     are indeterminate.  This is handled by extrapolating the
+**     position through unit time by using the velocity part of
+**     pv.  This moves the origin without changing the direction
+**     of the velocity component.  If the position and velocity
+**     components of pv are both null, zeroes are returned for all
+**     six results.
+**
+**  2) If the position is a pole, theta, td and pd are indeterminate.
+**     In such cases zeroes are returned for all three.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double x, y, z, xd, yd, zd, rxy2, rxy, r2, rtrue, rw, xyp;
+
+
+/* Components of position/velocity vector. */
+   x  = pv[0][0];
+   y  = pv[0][1];
+   z  = pv[0][2];
+   xd = pv[1][0];
+   yd = pv[1][1];
+   zd = pv[1][2];
+
+/* Component of r in XY plane squared. */
+   rxy2 = x*x + y*y;
+
+/* Modulus squared. */
+   r2 = rxy2 + z*z;
+
+/* Modulus. */
+   rtrue = sqrt(r2);
+
+/* If null vector, move the origin along the direction of movement. */
+   rw = rtrue;
+   if (rtrue == 0.0) {
+       x = xd;
+       y = yd;
+       z = zd;
+       rxy2 = x*x + y*y;
+       r2 = rxy2 + z*z;
+       rw = sqrt(r2);
+   }
+
+/* Position and velocity in spherical coordinates. */
+   rxy = sqrt(rxy2);
+   xyp = x*xd + y*yd;
+   if (rxy2 != 0.0) {
+       *theta = atan2(y, x);
+       *phi = atan2(z, rxy);
+       *td = (x*yd - y*xd) / rxy2;
+       *pd = (zd*rxy2 - z*xyp) / (r2*rxy);
+   } else {
+       *theta = 0.0;
+       *phi = (z != 0.0) ? atan2(z, rxy) : 0.0;
+       *td = 0.0;
+       *pd = 0.0;
+   }
+   *r = rtrue;
+   *rd = (rw != 0.0) ? (xyp + z*zd) / rw : 0.0;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pvdpv.c b/ast/erfa/pvdpv.c
new file mode 100644
index 0000000..103c308
--- /dev/null
+++ b/ast/erfa/pvdpv.c
@@ -0,0 +1,111 @@
+#include "erfa.h"
+
+void eraPvdpv(double a[2][3], double b[2][3], double adb[2])
+/*
+**  - - - - - - - - -
+**   e r a P v d p v
+**  - - - - - - - - -
+**
+**  Inner (=scalar=dot) product of two pv-vectors.
+**
+**  Given:
+**     a        double[2][3]      first pv-vector
+**     b        double[2][3]      second pv-vector
+**
+**  Returned:
+**     adb      double[2]         a . b (see note)
+**
+**  Note:
+**
+**     If the position and velocity components of the two pv-vectors are
+**     ( ap, av ) and ( bp, bv ), the result, a . b, is the pair of
+**     numbers ( ap . bp , ap . bv + av . bp ).  The two numbers are the
+**     dot-product of the two p-vectors and its derivative.
+**
+**  Called:
+**     eraPdp       scalar product of two p-vectors
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double adbd, addb;
+
+
+/* a . b = constant part of result. */
+   adb[0] = eraPdp(a[0], b[0]);
+
+/* a . bdot */
+   adbd = eraPdp(a[0], b[1]);
+
+/* adot . b */
+   addb = eraPdp(a[1], b[0]);
+
+/* Velocity part of result. */
+   adb[1] = adbd + addb;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pvm.c b/ast/erfa/pvm.c
new file mode 100644
index 0000000..fcb94c5
--- /dev/null
+++ b/ast/erfa/pvm.c
@@ -0,0 +1,95 @@
+#include "erfa.h"
+
+void eraPvm(double pv[2][3], double *r, double *s)
+/*
+**  - - - - - - -
+**   e r a P v m
+**  - - - - - - -
+**
+**  Modulus of pv-vector.
+**
+**  Given:
+**     pv     double[2][3]   pv-vector
+**
+**  Returned:
+**     r      double         modulus of position component
+**     s      double         modulus of velocity component
+**
+**  Called:
+**     eraPm        modulus of p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Distance. */
+   *r = eraPm(pv[0]);
+
+/* Speed. */
+   *s = eraPm(pv[1]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pvmpv.c b/ast/erfa/pvmpv.c
new file mode 100644
index 0000000..dda99ee
--- /dev/null
+++ b/ast/erfa/pvmpv.c
@@ -0,0 +1,96 @@
+#include "erfa.h"
+
+void eraPvmpv(double a[2][3], double b[2][3], double amb[2][3])
+/*
+**  - - - - - - - - -
+**   e r a P v m p v
+**  - - - - - - - - -
+**
+**  Subtract one pv-vector from another.
+**
+**  Given:
+**     a       double[2][3]      first pv-vector
+**     b       double[2][3]      second pv-vector
+**
+**  Returned:
+**     amb     double[2][3]      a - b
+**
+**  Note:
+**     It is permissible to re-use the same array for any of the
+**     arguments.
+**
+**  Called:
+**     eraPmp       p-vector minus p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraPmp(a[0], b[0], amb[0]);
+   eraPmp(a[1], b[1], amb[1]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pvppv.c b/ast/erfa/pvppv.c
new file mode 100644
index 0000000..19c2875
--- /dev/null
+++ b/ast/erfa/pvppv.c
@@ -0,0 +1,96 @@
+#include "erfa.h"
+
+void eraPvppv(double a[2][3], double b[2][3], double apb[2][3])
+/*
+**  - - - - - - - - -
+**   e r a P v p p v
+**  - - - - - - - - -
+**
+**  Add one pv-vector to another.
+**
+**  Given:
+**     a        double[2][3]      first pv-vector
+**     b        double[2][3]      second pv-vector
+**
+**  Returned:
+**     apb      double[2][3]      a + b
+**
+**  Note:
+**     It is permissible to re-use the same array for any of the
+**     arguments.
+**
+**  Called:
+**     eraPpp       p-vector plus p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraPpp(a[0], b[0], apb[0]);
+   eraPpp(a[1], b[1], apb[1]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pvstar.c b/ast/erfa/pvstar.c
new file mode 100644
index 0000000..3a4e00b
--- /dev/null
+++ b/ast/erfa/pvstar.c
@@ -0,0 +1,216 @@
+#include "erfa.h"
+
+int eraPvstar(double pv[2][3], double *ra, double *dec,
+              double *pmr, double *pmd, double *px, double *rv)
+/*
+**  - - - - - - - - - -
+**   e r a P v s t a r
+**  - - - - - - - - - -
+**
+**  Convert star position+velocity vector to catalog coordinates.
+**
+**  Given (Note 1):
+**     pv     double[2][3]   pv-vector (AU, AU/day)
+**
+**  Returned (Note 2):
+**     ra     double         right ascension (radians)
+**     dec    double         declination (radians)
+**     pmr    double         RA proper motion (radians/year)
+**     pmd    double         Dec proper motion (radians/year)
+**     px     double         parallax (arcsec)
+**     rv     double         radial velocity (km/s, positive = receding)
+**
+**  Returned (function value):
+**            int            status:
+**                              0 = OK
+**                             -1 = superluminal speed (Note 5)
+**                             -2 = null position vector
+**
+**  Notes:
+**
+**  1) The specified pv-vector is the coordinate direction (and its rate
+**     of change) for the date at which the light leaving the star
+**     reached the solar-system barycenter.
+**
+**  2) The star data returned by this function are "observables" for an
+**     imaginary observer at the solar-system barycenter.  Proper motion
+**     and radial velocity are, strictly, in terms of barycentric
+**     coordinate time, TCB.  For most practical applications, it is
+**     permissible to neglect the distinction between TCB and ordinary
+**     "proper" time on Earth (TT/TAI).  The result will, as a rule, be
+**     limited by the intrinsic accuracy of the proper-motion and
+**     radial-velocity data;  moreover, the supplied pv-vector is likely
+**     to be merely an intermediate result (for example generated by the
+**     function eraStarpv), so that a change of time unit will cancel
+**     out overall.
+**
+**     In accordance with normal star-catalog conventions, the object's
+**     right ascension and declination are freed from the effects of
+**     secular aberration.  The frame, which is aligned to the catalog
+**     equator and equinox, is Lorentzian and centered on the SSB.
+**
+**     Summarizing, the specified pv-vector is for most stars almost
+**     identical to the result of applying the standard geometrical
+**     "space motion" transformation to the catalog data.  The
+**     differences, which are the subject of the Stumpff paper cited
+**     below, are:
+**
+**     (i) In stars with significant radial velocity and proper motion,
+**     the constantly changing light-time distorts the apparent proper
+**     motion.  Note that this is a classical, not a relativistic,
+**     effect.
+**
+**     (ii) The transformation complies with special relativity.
+**
+**  3) Care is needed with units.  The star coordinates are in radians
+**     and the proper motions in radians per Julian year, but the
+**     parallax is in arcseconds; the radial velocity is in km/s, but
+**     the pv-vector result is in AU and AU/day.
+**
+**  4) The proper motions are the rate of change of the right ascension
+**     and declination at the catalog epoch and are in radians per Julian
+**     year.  The RA proper motion is in terms of coordinate angle, not
+**     true angle, and will thus be numerically larger at high
+**     declinations.
+**
+**  5) Straight-line motion at constant speed in the inertial frame is
+**     assumed.  If the speed is greater than or equal to the speed of
+**     light, the function aborts with an error status.
+**
+**  6) The inverse transformation is performed by the function eraStarpv.
+**
+**  Called:
+**     eraPn        decompose p-vector into modulus and direction
+**     eraPdp       scalar product of two p-vectors
+**     eraSxp       multiply p-vector by scalar
+**     eraPmp       p-vector minus p-vector
+**     eraPm        modulus of p-vector
+**     eraPpp       p-vector plus p-vector
+**     eraPv2s      pv-vector to spherical
+**     eraAnp       normalize angle into range 0 to 2pi
+**
+**  Reference:
+**
+**     Stumpff, P., 1985, Astron.Astrophys. 144, 232-240.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double r, x[3], vr, ur[3], vt, ut[3], bett, betr, d, w, del,
+          usr[3], ust[3], a, rad, decd, rd;
+
+
+/* Isolate the radial component of the velocity (AU/day, inertial). */
+   eraPn(pv[0], &r, x);
+   vr = eraPdp(x, pv[1]);
+   eraSxp(vr, x, ur);
+
+/* Isolate the transverse component of the velocity (AU/day, inertial). */
+   eraPmp(pv[1], ur, ut);
+   vt = eraPm(ut);
+
+/* Special-relativity dimensionless parameters. */
+   bett = vt / ERFA_DC;
+   betr = vr / ERFA_DC;
+
+/* The inertial-to-observed correction terms. */
+   d = 1.0 + betr;
+   w = 1.0 - betr*betr - bett*bett;
+   if (d == 0.0 || w < 0) return -1;
+   del = sqrt(w) - 1.0;
+
+/* Apply relativistic correction factor to radial velocity component. */
+   w = (betr != 0) ? (betr - del) / (betr * d) : 1.0;
+   eraSxp(w, ur, usr);
+
+/* Apply relativistic correction factor to tangential velocity */
+/* component.                                                  */
+   eraSxp(1.0/d, ut, ust);
+
+/* Combine the two to obtain the observed velocity vector (AU/day). */
+   eraPpp(usr, ust, pv[1]);
+
+/* Cartesian to spherical. */
+   eraPv2s(pv, &a, dec, &r, &rad, &decd, &rd);
+   if (r == 0.0) return -2;
+
+/* Return RA in range 0 to 2pi. */
+   *ra = eraAnp(a);
+
+/* Return proper motions in radians per year. */
+   *pmr = rad * ERFA_DJY;
+   *pmd = decd * ERFA_DJY;
+
+/* Return parallax in arcsec. */
+   *px = ERFA_DR2AS / r;
+
+/* Return radial velocity in km/s. */
+   *rv = 1e-3 * rd * ERFA_DAU / ERFA_DAYSEC;
+
+/* OK status. */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pvtob.c b/ast/erfa/pvtob.c
new file mode 100644
index 0000000..943a63c
--- /dev/null
+++ b/ast/erfa/pvtob.c
@@ -0,0 +1,162 @@
+#include "erfa.h"
+
+void eraPvtob(double elong, double phi, double hm,
+              double xp, double yp, double sp, double theta,
+              double pv[2][3])
+/*
+**  - - - - - - - - -
+**   e r a P v t o b
+**  - - - - - - - - -
+**
+**  Position and velocity of a terrestrial observing station.
+**
+**  Given:
+**     elong   double       longitude (radians, east +ve, Note 1)
+**     phi     double       latitude (geodetic, radians, Note 1)
+**     hm      double       height above ref. ellipsoid (geodetic, m)
+**     xp,yp   double       coordinates of the pole (radians, Note 2)
+**     sp      double       the TIO locator s' (radians, Note 2)
+**     theta   double       Earth rotation angle (radians, Note 3)
+**
+**  Returned:
+**     pv      double[2][3] position/velocity vector (m, m/s, CIRS)
+**
+**  Notes:
+**
+**  1) The terrestrial coordinates are with respect to the ERFA_WGS84
+**     reference ellipsoid.
+**
+**  2) xp and yp are the coordinates (in radians) of the Celestial
+**     Intermediate Pole with respect to the International Terrestrial
+**     Reference System (see IERS Conventions), measured along the
+**     meridians 0 and 90 deg west respectively.  sp is the TIO locator
+**     s', in radians, which positions the Terrestrial Intermediate
+**     Origin on the equator.  For many applications, xp, yp and
+**     (especially) sp can be set to zero.
+**
+**  3) If theta is Greenwich apparent sidereal time instead of Earth
+**     rotation angle, the result is with respect to the true equator
+**     and equinox of date, i.e. with the x-axis at the equinox rather
+**     than the celestial intermediate origin.
+**
+**  4) The velocity units are meters per UT1 second, not per SI second.
+**     This is unlikely to have any practical consequences in the modern
+**     era.
+**
+**  5) No validation is performed on the arguments.  Error cases that
+**     could lead to arithmetic exceptions are trapped by the eraGd2gc
+**     function, and the result set to zeros.
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Urban, S. & Seidelmann, P. K. (eds), Explanatory Supplement to
+**     the Astronomical Almanac, 3rd ed., University Science Books
+**     (2013), Section 7.4.3.3.
+**
+**  Called:
+**     eraGd2gc     geodetic to geocentric transformation
+**     eraPom00     polar motion matrix
+**     eraTrxp      product of transpose of r-matrix and p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Earth rotation rate in radians per UT1 second */
+   const double OM = 1.00273781191135448 * ERFA_D2PI / ERFA_DAYSEC;
+
+   double xyzm[3], rpm[3][3], xyz[3], x, y, z, s, c;
+
+
+/* Geodetic to geocentric transformation (ERFA_WGS84). */
+   (void) eraGd2gc(1, elong, phi, hm, xyzm);
+
+/* Polar motion and TIO position. */
+   eraPom00(xp, yp, sp, rpm);
+   eraTrxp(rpm, xyzm, xyz);
+   x = xyz[0];
+   y = xyz[1];
+   z = xyz[2];
+
+/* Functions of ERA. */
+   s = sin(theta);
+   c = cos(theta);
+
+/* Position. */
+   pv[0][0] = c*x - s*y;
+   pv[0][1] = s*x + c*y;
+   pv[0][2] = z;
+
+/* Velocity. */
+   pv[1][0] = OM * ( -s*x - c*y );
+   pv[1][1] = OM * (  c*x - s*y );
+   pv[1][2] = 0.0;
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pvu.c b/ast/erfa/pvu.c
new file mode 100644
index 0000000..34d9db2
--- /dev/null
+++ b/ast/erfa/pvu.c
@@ -0,0 +1,102 @@
+#include "erfa.h"
+
+void eraPvu(double dt, double pv[2][3], double upv[2][3])
+/*
+**  - - - - - - -
+**   e r a P v u
+**  - - - - - - -
+**
+**  Update a pv-vector.
+**
+**  Given:
+**     dt       double           time interval
+**     pv       double[2][3]     pv-vector
+**
+**  Returned:
+**     upv      double[2][3]     p updated, v unchanged
+**
+**  Notes:
+**
+**  1) "Update" means "refer the position component of the vector
+**     to a new date dt time units from the existing date".
+**
+**  2) The time units of dt must match those of the velocity.
+**
+**  3) It is permissible for pv and upv to be the same array.
+**
+**  Called:
+**     eraPpsp      p-vector plus scaled p-vector
+**     eraCp        copy p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraPpsp(pv[0], dt, pv[1], upv[0]);
+   eraCp(pv[1], upv[1]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pvup.c b/ast/erfa/pvup.c
new file mode 100644
index 0000000..7f50e6c
--- /dev/null
+++ b/ast/erfa/pvup.c
@@ -0,0 +1,97 @@
+#include "erfa.h"
+
+void eraPvup(double dt, double pv[2][3], double p[3])
+/*
+**  - - - - - - - -
+**   e r a P v u p
+**  - - - - - - - -
+**
+**  Update a pv-vector, discarding the velocity component.
+**
+**  Given:
+**     dt       double            time interval
+**     pv       double[2][3]      pv-vector
+**
+**  Returned:
+**     p        double[3]         p-vector
+**
+**  Notes:
+**
+**  1) "Update" means "refer the position component of the vector to a
+**     new date dt time units from the existing date".
+**
+**  2) The time units of dt must match those of the velocity.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   p[0] = pv[0][0] + dt * pv[1][0];
+   p[1] = pv[0][1] + dt * pv[1][1];
+   p[2] = pv[0][2] + dt * pv[1][2];
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pvxpv.c b/ast/erfa/pvxpv.c
new file mode 100644
index 0000000..0d87479
--- /dev/null
+++ b/ast/erfa/pvxpv.c
@@ -0,0 +1,116 @@
+#include "erfa.h"
+
+void eraPvxpv(double a[2][3], double b[2][3], double axb[2][3])
+/*
+**  - - - - - - - - -
+**   e r a P v x p v
+**  - - - - - - - - -
+**
+**  Outer (=vector=cross) product of two pv-vectors.
+**
+**  Given:
+**     a        double[2][3]      first pv-vector
+**     b        double[2][3]      second pv-vector
+**
+**  Returned:
+**     axb      double[2][3]      a x b
+**
+**  Notes:
+**
+**  1) If the position and velocity components of the two pv-vectors are
+**     ( ap, av ) and ( bp, bv ), the result, a x b, is the pair of
+**     vectors ( ap x bp, ap x bv + av x bp ).  The two vectors are the
+**     cross-product of the two p-vectors and its derivative.
+**
+**  2) It is permissible to re-use the same array for any of the
+**     arguments.
+**
+**  Called:
+**     eraCpv       copy pv-vector
+**     eraPxp       vector product of two p-vectors
+**     eraPpp       p-vector plus p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double wa[2][3], wb[2][3], axbd[3], adxb[3];
+
+
+/* Make copies of the inputs. */
+   eraCpv(a, wa);
+   eraCpv(b, wb);
+
+/* a x b = position part of result. */
+   eraPxp(wa[0], wb[0], axb[0]);
+
+/* a x bdot + adot x b = velocity part of result. */
+   eraPxp(wa[0], wb[1], axbd);
+   eraPxp(wa[1], wb[0], adxb);
+   eraPpp(axbd, adxb, axb[1]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/pxp.c b/ast/erfa/pxp.c
new file mode 100644
index 0000000..5b5a98b
--- /dev/null
+++ b/ast/erfa/pxp.c
@@ -0,0 +1,103 @@
+#include "erfa.h"
+
+void eraPxp(double a[3], double b[3], double axb[3])
+/*
+**  - - - - - - -
+**   e r a P x p
+**  - - - - - - -
+**
+**  p-vector outer (=vector=cross) product.
+**
+**  Given:
+**     a        double[3]      first p-vector
+**     b        double[3]      second p-vector
+**
+**  Returned:
+**     axb      double[3]      a x b
+**
+**  Note:
+**     It is permissible to re-use the same array for any of the
+**     arguments.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double xa, ya, za, xb, yb, zb;
+
+
+   xa = a[0];
+   ya = a[1];
+   za = a[2];
+   xb = b[0];
+   yb = b[1];
+   zb = b[2];
+   axb[0] = ya*zb - za*yb;
+   axb[1] = za*xb - xa*zb;
+   axb[2] = xa*yb - ya*xb;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/refco.c b/ast/erfa/refco.c
new file mode 100644
index 0000000..918d990
--- /dev/null
+++ b/ast/erfa/refco.c
@@ -0,0 +1,262 @@
+#include "erfa.h"
+
+void eraRefco(double phpa, double tc, double rh, double wl,
+              double *refa, double *refb)
+/*
+**  - - - - - - - - -
+**   e r a R e f c o
+**  - - - - - - - - -
+**
+**  Determine the constants A and B in the atmospheric refraction model
+**  dZ = A tan Z + B tan^3 Z.
+**
+**  Z is the "observed" zenith distance (i.e. affected by refraction)
+**  and dZ is what to add to Z to give the "topocentric" (i.e. in vacuo)
+**  zenith distance.
+**
+**  Given:
+**    phpa   double    pressure at the observer (hPa = millibar)
+**    tc     double    ambient temperature at the observer (deg C)
+**    rh     double    relative humidity at the observer (range 0-1)
+**    wl     double    wavelength (micrometers)
+**
+**  Returned:
+**    refa   double*   tan Z coefficient (radians)
+**    refb   double*   tan^3 Z coefficient (radians)
+**
+**  Notes:
+**
+**  1) The model balances speed and accuracy to give good results in
+**     applications where performance at low altitudes is not paramount.
+**     Performance is maintained across a range of conditions, and
+**     applies to both optical/IR and radio.
+**
+**  2) The model omits the effects of (i) height above sea level (apart
+**     from the reduced pressure itself), (ii) latitude (i.e. the
+**     flattening of the Earth), (iii) variations in tropospheric lapse
+**     rate and (iv) dispersive effects in the radio.
+**
+**     The model was tested using the following range of conditions:
+**
+**       lapse rates 0.0055, 0.0065, 0.0075 deg/meter
+**       latitudes 0, 25, 50, 75 degrees
+**       heights 0, 2500, 5000 meters ASL
+**       pressures mean for height -10% to +5% in steps of 5%
+**       temperatures -10 deg to +20 deg with respect to 280 deg at SL
+**       relative humidity 0, 0.5, 1
+**       wavelengths 0.4, 0.6, ... 2 micron, + radio
+**       zenith distances 15, 45, 75 degrees
+**
+**     The accuracy with respect to raytracing through a model
+**     atmosphere was as follows:
+**
+**                            worst         RMS
+**
+**       optical/IR           62 mas       8 mas
+**       radio               319 mas      49 mas
+**
+**     For this particular set of conditions:
+**
+**       lapse rate 0.0065 K/meter
+**       latitude 50 degrees
+**       sea level
+**       pressure 1005 mb
+**       temperature 280.15 K
+**       humidity 80%
+**       wavelength 5740 Angstroms
+**
+**     the results were as follows:
+**
+**       ZD       raytrace     eraRefco   Saastamoinen
+**
+**       10         10.27        10.27        10.27
+**       20         21.19        21.20        21.19
+**       30         33.61        33.61        33.60
+**       40         48.82        48.83        48.81
+**       45         58.16        58.18        58.16
+**       50         69.28        69.30        69.27
+**       55         82.97        82.99        82.95
+**       60        100.51       100.54       100.50
+**       65        124.23       124.26       124.20
+**       70        158.63       158.68       158.61
+**       72        177.32       177.37       177.31
+**       74        200.35       200.38       200.32
+**       76        229.45       229.43       229.42
+**       78        267.44       267.29       267.41
+**       80        319.13       318.55       319.10
+**
+**      deg        arcsec       arcsec       arcsec
+**
+**     The values for Saastamoinen's formula (which includes terms
+**     up to tan^5) are taken from Hohenkerk and Sinclair (1985).
+**
+**  3) A wl value in the range 0-100 selects the optical/IR case and is
+**     wavelength in micrometers.  Any value outside this range selects
+**     the radio case.
+**
+**  4) Outlandish input parameters are silently limited to
+**     mathematically safe values.  Zero pressure is permissible, and
+**     causes zeroes to be returned.
+**
+**  5) The algorithm draws on several sources, as follows:
+**
+**     a) The formula for the saturation vapour pressure of water as
+**        a function of temperature and temperature is taken from
+**        Equations (A4.5-A4.7) of Gill (1982).
+**
+**     b) The formula for the water vapour pressure, given the
+**        saturation pressure and the relative humidity, is from
+**        Crane (1976), Equation (2.5.5).
+**
+**     c) The refractivity of air is a function of temperature,
+**        total pressure, water-vapour pressure and, in the case
+**        of optical/IR, wavelength.  The formulae for the two cases are
+**        developed from Hohenkerk & Sinclair (1985) and Rueger (2002).
+**
+**     d) The formula for beta, the ratio of the scale height of the
+**        atmosphere to the geocentric distance of the observer, is
+**        an adaption of Equation (9) from Stone (1996).  The
+**        adaptations, arrived at empirically, consist of (i) a small
+**        adjustment to the coefficient and (ii) a humidity term for the
+**        radio case only.
+**
+**     e) The formulae for the refraction constants as a function of
+**        n-1 and beta are from Green (1987), Equation (4.31).
+**
+**  References:
+**
+**     Crane, R.K., Meeks, M.L. (ed), "Refraction Effects in the Neutral
+**     Atmosphere", Methods of Experimental Physics: Astrophysics 12B,
+**     Academic Press, 1976.
+**
+**     Gill, Adrian E., "Atmosphere-Ocean Dynamics", Academic Press,
+**     1982.
+**
+**     Green, R.M., "Spherical Astronomy", Cambridge University Press,
+**     1987.
+**
+**     Hohenkerk, C.Y., & Sinclair, A.T., NAO Technical Note No. 63,
+**     1985.
+**
+**     Rueger, J.M., "Refractive Index Formulae for Electronic Distance
+**     Measurement with Radio and Millimetre Waves", in Unisurv Report
+**     S-68, School of Surveying and Spatial Information Systems,
+**     University of New South Wales, Sydney, Australia, 2002.
+**
+**     Stone, Ronald C., P.A.S.P. 108, 1051-1058, 1996.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int optic;
+   double p, t, r, w, ps, pw, tk, wlsq, gamma, beta;
+
+
+/* Decide whether optical/IR or radio case:  switch at 100 microns. */
+   optic = ( wl <= 100.0 );
+
+/* Restrict parameters to safe values. */
+   t = ERFA_GMAX ( tc, -150.0 );
+   t = ERFA_GMIN ( t, 200.0 );
+   p = ERFA_GMAX ( phpa, 0.0 );
+   p = ERFA_GMIN ( p, 10000.0 );
+   r = ERFA_GMAX ( rh, 0.0 );
+   r = ERFA_GMIN ( r, 1.0 );
+   w = ERFA_GMAX ( wl, 0.1 );
+   w = ERFA_GMIN ( w, 1e6 );
+
+/* Water vapour pressure at the observer. */
+   if ( p > 0.0 ) {
+      ps = pow ( 10.0, ( 0.7859 + 0.03477*t ) /
+                          ( 1.0 + 0.00412*t ) ) *
+                 ( 1.0 + p * ( 4.5e-6 + 6e-10*t*t )  );
+      pw = r * ps / ( 1.0 - (1.0-r)*ps/p );
+   } else {
+      pw = 0.0;
+   }
+
+/* Refractive index minus 1 at the observer. */
+   tk = t + 273.15;
+   if ( optic ) {
+      wlsq = w * w;
+      gamma = ( ( 77.53484e-6 +
+                 ( 4.39108e-7 + 3.666e-9/wlsq ) / wlsq ) * p
+                    - 11.2684e-6*pw ) / tk;
+   } else {
+      gamma = ( 77.6890e-6*p - ( 6.3938e-6 - 0.375463/tk ) * pw ) / tk;
+   }
+
+/* Formula for beta from Stone, with empirical adjustments. */
+   beta = 4.4474e-6 * tk;
+   if ( ! optic ) beta -= 0.0074 * pw * beta;
+
+/* Refraction constants from Green. */
+   *refa = gamma * ( 1.0 - beta );
+   *refb = - gamma * ( beta - gamma / 2.0 );
+
+/* Finished. */
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/rm2v.c b/ast/erfa/rm2v.c
new file mode 100644
index 0000000..2de16ce
--- /dev/null
+++ b/ast/erfa/rm2v.c
@@ -0,0 +1,120 @@
+#include "erfa.h"
+
+void eraRm2v(double r[3][3], double w[3])
+/*
+**  - - - - - - - -
+**   e r a R m 2 v
+**  - - - - - - - -
+**
+**  Express an r-matrix as an r-vector.
+**
+**  Given:
+**     r        double[3][3]    rotation matrix
+**
+**  Returned:
+**     w        double[3]       rotation vector (Note 1)
+**
+**  Notes:
+**
+**  1) A rotation matrix describes a rotation through some angle about
+**     some arbitrary axis called the Euler axis.  The "rotation vector"
+**     returned by this function has the same direction as the Euler axis,
+**     and its magnitude is the angle in radians.  (The magnitude and
+**     direction can be separated by means of the function eraPn.)
+**
+**  2) If r is null, so is the result.  If r is not a rotation matrix
+**     the result is undefined;  r must be proper (i.e. have a positive
+**     determinant) and real orthogonal (inverse = transpose).
+**
+**  3) The reference frame rotates clockwise as seen looking along
+**     the rotation vector from the origin.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double x, y, z, s2, c2, phi, f;
+
+
+   x = r[1][2] - r[2][1];
+   y = r[2][0] - r[0][2];
+   z = r[0][1] - r[1][0];
+   s2 = sqrt(x*x + y*y + z*z);
+   if (s2 > 0) {
+      c2 = r[0][0] + r[1][1] + r[2][2] - 1.0;
+      phi = atan2(s2, c2);
+      f =  phi / s2;
+      w[0] = x * f;
+      w[1] = y * f;
+      w[2] = z * f;
+   } else {
+      w[0] = 0.0;
+      w[1] = 0.0;
+      w[2] = 0.0;
+   }
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/rv2m.c b/ast/erfa/rv2m.c
new file mode 100644
index 0000000..9fe3c4b
--- /dev/null
+++ b/ast/erfa/rv2m.c
@@ -0,0 +1,127 @@
+#include "erfa.h"
+
+void eraRv2m(double w[3], double r[3][3])
+/*
+**  - - - - - - - -
+**   e r a R v 2 m
+**  - - - - - - - -
+**
+**  Form the r-matrix corresponding to a given r-vector.
+**
+**  Given:
+**     w        double[3]      rotation vector (Note 1)
+**
+**  Returned:
+**     r        double[3][3]    rotation matrix
+**
+**  Notes:
+**
+**  1) A rotation matrix describes a rotation through some angle about
+**     some arbitrary axis called the Euler axis.  The "rotation vector"
+**     supplied to This function has the same direction as the Euler
+**     axis, and its magnitude is the angle in radians.
+**
+**  2) If w is null, the unit matrix is returned.
+**
+**  3) The reference frame rotates clockwise as seen looking along the
+**     rotation vector from the origin.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double x, y, z, phi, s, c, f;
+
+
+/* Euler angle (magnitude of rotation vector) and functions. */
+   x = w[0];
+   y = w[1];
+   z = w[2];
+   phi = sqrt(x*x + y*y + z*z);
+   s = sin(phi);
+   c = cos(phi);
+   f = 1.0 - c;
+
+/* Euler axis (direction of rotation vector), perhaps null. */
+   if (phi > 0.0) {
+       x /= phi;
+       y /= phi;
+       z /= phi;
+   }
+
+/* Form the rotation matrix. */
+   r[0][0] = x*x*f + c;
+   r[0][1] = x*y*f + z*s;
+   r[0][2] = x*z*f - y*s;
+   r[1][0] = y*x*f - z*s;
+   r[1][1] = y*y*f + c;
+   r[1][2] = y*z*f + x*s;
+   r[2][0] = z*x*f + y*s;
+   r[2][1] = z*y*f - x*s;
+   r[2][2] = z*z*f + c;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/rx.c b/ast/erfa/rx.c
new file mode 100644
index 0000000..df080d5
--- /dev/null
+++ b/ast/erfa/rx.c
@@ -0,0 +1,119 @@
+#include "erfa.h"
+
+void eraRx(double phi, double r[3][3])
+/*
+**  - - - - - -
+**   e r a R x
+**  - - - - - -
+**
+**  Rotate an r-matrix about the x-axis.
+**
+**  Given:
+**     phi    double          angle (radians)
+**
+**  Given and returned:
+**     r      double[3][3]    r-matrix, rotated
+**
+**  Notes:
+**
+**  1) Calling this function with positive phi incorporates in the
+**     supplied r-matrix r an additional rotation, about the x-axis,
+**     anticlockwise as seen looking towards the origin from positive x.
+**
+**  2) The additional rotation can be represented by this matrix:
+**
+**         (  1        0            0      )
+**         (                               )
+**         (  0   + cos(phi)   + sin(phi)  )
+**         (                               )
+**         (  0   - sin(phi)   + cos(phi)  )
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double s, c, a10, a11, a12, a20, a21, a22;
+
+
+   s = sin(phi);
+   c = cos(phi);
+
+   a10 =   c*r[1][0] + s*r[2][0];
+   a11 =   c*r[1][1] + s*r[2][1];
+   a12 =   c*r[1][2] + s*r[2][2];
+   a20 = - s*r[1][0] + c*r[2][0];
+   a21 = - s*r[1][1] + c*r[2][1];
+   a22 = - s*r[1][2] + c*r[2][2];
+
+   r[1][0] = a10;
+   r[1][1] = a11;
+   r[1][2] = a12;
+   r[2][0] = a20;
+   r[2][1] = a21;
+   r[2][2] = a22;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/rxp.c b/ast/erfa/rxp.c
new file mode 100644
index 0000000..f1dbe82
--- /dev/null
+++ b/ast/erfa/rxp.c
@@ -0,0 +1,108 @@
+#include "erfa.h"
+
+void eraRxp(double r[3][3], double p[3], double rp[3])
+/*
+**  - - - - - - -
+**   e r a R x p
+**  - - - - - - -
+**
+**  Multiply a p-vector by an r-matrix.
+**
+**  Given:
+**     r        double[3][3]    r-matrix
+**     p        double[3]       p-vector
+**
+**  Returned:
+**     rp       double[3]       r * p
+**
+**  Note:
+**     It is permissible for p and rp to be the same array.
+**
+**  Called:
+**     eraCp        copy p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double w, wrp[3];
+   int i, j;
+
+
+/* Matrix r * vector p. */
+   for (j = 0; j < 3; j++) {
+       w = 0.0;
+       for (i = 0; i < 3; i++) {
+           w += r[j][i] * p[i];
+       }
+       wrp[j] = w;
+   }
+
+/* Return the result. */
+   eraCp(wrp, rp);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/rxpv.c b/ast/erfa/rxpv.c
new file mode 100644
index 0000000..f7bb53d
--- /dev/null
+++ b/ast/erfa/rxpv.c
@@ -0,0 +1,95 @@
+#include "erfa.h"
+
+void eraRxpv(double r[3][3], double pv[2][3], double rpv[2][3])
+/*
+**  - - - - - - - -
+**   e r a R x p v
+**  - - - - - - - -
+**
+**  Multiply a pv-vector by an r-matrix.
+**
+**  Given:
+**     r        double[3][3]    r-matrix
+**     pv       double[2][3]    pv-vector
+**
+**  Returned:
+**     rpv      double[2][3]    r * pv
+**
+**  Note:
+**     It is permissible for pv and rpv to be the same array.
+**
+**  Called:
+**     eraRxp       product of r-matrix and p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraRxp(r, pv[0], rpv[0]);
+   eraRxp(r, pv[1], rpv[1]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/rxr.c b/ast/erfa/rxr.c
new file mode 100644
index 0000000..ff0485b
--- /dev/null
+++ b/ast/erfa/rxr.c
@@ -0,0 +1,108 @@
+#include "erfa.h"
+
+void eraRxr(double a[3][3], double b[3][3], double atb[3][3])
+/*
+**  - - - - - - -
+**   e r a R x r
+**  - - - - - - -
+**
+**  Multiply two r-matrices.
+**
+**  Given:
+**     a        double[3][3]    first r-matrix
+**     b        double[3][3]    second r-matrix
+**
+**  Returned:
+**     atb      double[3][3]    a * b
+**
+**  Note:
+**     It is permissible to re-use the same array for any of the
+**     arguments.
+**
+**  Called:
+**     eraCr        copy r-matrix
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int i, j, k;
+   double w, wm[3][3];
+
+
+   for (i = 0; i < 3; i++) {
+      for (j = 0; j < 3; j++) {
+         w = 0.0;
+         for (k = 0; k < 3; k++) {
+            w +=  a[i][k] * b[k][j];
+         }
+         wm[i][j] = w;
+      }
+   }
+   eraCr(wm, atb);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ry.c b/ast/erfa/ry.c
new file mode 100644
index 0000000..193d7a9
--- /dev/null
+++ b/ast/erfa/ry.c
@@ -0,0 +1,119 @@
+#include "erfa.h"
+
+void eraRy(double theta, double r[3][3])
+/*
+**  - - - - - -
+**   e r a R y
+**  - - - - - -
+**
+**  Rotate an r-matrix about the y-axis.
+**
+**  Given:
+**     theta  double          angle (radians)
+**
+**  Given and returned:
+**     r      double[3][3]    r-matrix, rotated
+**
+**  Notes:
+**
+**  1) Calling this function with positive theta incorporates in the
+**     supplied r-matrix r an additional rotation, about the y-axis,
+**     anticlockwise as seen looking towards the origin from positive y.
+**
+**  2) The additional rotation can be represented by this matrix:
+**
+**         (  + cos(theta)     0      - sin(theta)  )
+**         (                                        )
+**         (       0           1           0        )
+**         (                                        )
+**         (  + sin(theta)     0      + cos(theta)  )
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double s, c, a00, a01, a02, a20, a21, a22;
+
+
+   s = sin(theta);
+   c = cos(theta);
+
+   a00 = c*r[0][0] - s*r[2][0];
+   a01 = c*r[0][1] - s*r[2][1];
+   a02 = c*r[0][2] - s*r[2][2];
+   a20 = s*r[0][0] + c*r[2][0];
+   a21 = s*r[0][1] + c*r[2][1];
+   a22 = s*r[0][2] + c*r[2][2];
+
+   r[0][0] = a00;
+   r[0][1] = a01;
+   r[0][2] = a02;
+   r[2][0] = a20;
+   r[2][1] = a21;
+   r[2][2] = a22;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/rz.c b/ast/erfa/rz.c
new file mode 100644
index 0000000..58a26e7
--- /dev/null
+++ b/ast/erfa/rz.c
@@ -0,0 +1,119 @@
+#include "erfa.h"
+
+void eraRz(double psi, double r[3][3])
+/*
+**  - - - - - -
+**   e r a R z
+**  - - - - - -
+**
+**  Rotate an r-matrix about the z-axis.
+**
+**  Given:
+**     psi    double          angle (radians)
+**
+**  Given and returned:
+**     r      double[3][3]    r-matrix, rotated
+**
+**  Notes:
+**
+**  1) Calling this function with positive psi incorporates in the
+**     supplied r-matrix r an additional rotation, about the z-axis,
+**     anticlockwise as seen looking towards the origin from positive z.
+**
+**  2) The additional rotation can be represented by this matrix:
+**
+**         (  + cos(psi)   + sin(psi)     0  )
+**         (                                 )
+**         (  - sin(psi)   + cos(psi)     0  )
+**         (                                 )
+**         (       0            0         1  )
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double s, c, a00, a01, a02, a10, a11, a12;
+
+
+   s = sin(psi);
+   c = cos(psi);
+
+   a00 =   c*r[0][0] + s*r[1][0];
+   a01 =   c*r[0][1] + s*r[1][1];
+   a02 =   c*r[0][2] + s*r[1][2];
+   a10 = - s*r[0][0] + c*r[1][0];
+   a11 = - s*r[0][1] + c*r[1][1];
+   a12 = - s*r[0][2] + c*r[1][2];
+
+   r[0][0] = a00;
+   r[0][1] = a01;
+   r[0][2] = a02;
+   r[1][0] = a10;
+   r[1][1] = a11;
+   r[1][2] = a12;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/s00.c b/ast/erfa/s00.c
new file mode 100644
index 0000000..82d0a5f
--- /dev/null
+++ b/ast/erfa/s00.c
@@ -0,0 +1,380 @@
+#include "erfa.h"
+
+double eraS00(double date1, double date2, double x, double y)
+/*
+**  - - - - - - -
+**   e r a S 0 0
+**  - - - - - - -
+**
+**  The CIO locator s, positioning the Celestial Intermediate Origin on
+**  the equator of the Celestial Intermediate Pole, given the CIP's X,Y
+**  coordinates.  Compatible with IAU 2000A precession-nutation.
+**
+**  Given:
+**     date1,date2   double    TT as a 2-part Julian Date (Note 1)
+**     x,y           double    CIP coordinates (Note 3)
+**
+**  Returned (function value):
+**                   double    the CIO locator s in radians (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The CIO locator s is the difference between the right ascensions
+**     of the same point in two systems:  the two systems are the GCRS
+**     and the CIP,CIO, and the point is the ascending node of the
+**     CIP equator.  The quantity s remains below 0.1 arcsecond
+**     throughout 1900-2100.
+**
+**  3) The series used to compute s is in fact for s+XY/2, where X and Y
+**     are the x and y components of the CIP unit vector;  this series
+**     is more compact than a direct series for s would be.  This
+**     function requires X,Y to be supplied by the caller, who is
+**     responsible for providing values that are consistent with the
+**     supplied date.
+**
+**  4) The model is consistent with the IAU 2000A precession-nutation.
+**
+**  Called:
+**     eraFal03     mean anomaly of the Moon
+**     eraFalp03    mean anomaly of the Sun
+**     eraFaf03     mean argument of the latitude of the Moon
+**     eraFad03     mean elongation of the Moon from the Sun
+**     eraFaom03    mean longitude of the Moon's ascending node
+**     eraFave03    mean longitude of Venus
+**     eraFae03     mean longitude of Earth
+**     eraFapa03    general accumulated precession in longitude
+**
+**  References:
+**
+**     Capitaine, N., Chapront, J., Lambert, S. and Wallace, P.,
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154 (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Time since J2000.0, in Julian centuries */
+   double t;
+
+/* Miscellaneous */
+   int i, j;
+   double a, w0, w1, w2, w3, w4, w5;
+
+/* Fundamental arguments */
+   double fa[8];
+
+/* Returned value */
+   double s;
+
+/* --------------------- */
+/* The series for s+XY/2 */
+/* --------------------- */
+
+   typedef struct {
+      int nfa[8];      /* coefficients of l,l',F,D,Om,LVe,LE,pA */
+      double s, c;     /* sine and cosine coefficients */
+   } TERM;
+
+/* Polynomial coefficients */
+   static const double sp[] = {
+
+   /* 1-6 */
+          94.00e-6,
+        3808.35e-6,
+        -119.94e-6,
+      -72574.09e-6,
+          27.70e-6,
+          15.61e-6
+   };
+
+/* Terms of order t^0 */
+   static const TERM s0[] = {
+
+   /* 1-10 */
+      {{ 0,  0,  0,  0,  1,  0,  0,  0}, -2640.73e-6,   0.39e-6 },
+      {{ 0,  0,  0,  0,  2,  0,  0,  0},   -63.53e-6,   0.02e-6 },
+      {{ 0,  0,  2, -2,  3,  0,  0,  0},   -11.75e-6,  -0.01e-6 },
+      {{ 0,  0,  2, -2,  1,  0,  0,  0},   -11.21e-6,  -0.01e-6 },
+      {{ 0,  0,  2, -2,  2,  0,  0,  0},     4.57e-6,   0.00e-6 },
+      {{ 0,  0,  2,  0,  3,  0,  0,  0},    -2.02e-6,   0.00e-6 },
+      {{ 0,  0,  2,  0,  1,  0,  0,  0},    -1.98e-6,   0.00e-6 },
+      {{ 0,  0,  0,  0,  3,  0,  0,  0},     1.72e-6,   0.00e-6 },
+      {{ 0,  1,  0,  0,  1,  0,  0,  0},     1.41e-6,   0.01e-6 },
+      {{ 0,  1,  0,  0, -1,  0,  0,  0},     1.26e-6,   0.01e-6 },
+
+   /* 11-20 */
+      {{ 1,  0,  0,  0, -1,  0,  0,  0},     0.63e-6,   0.00e-6 },
+      {{ 1,  0,  0,  0,  1,  0,  0,  0},     0.63e-6,   0.00e-6 },
+      {{ 0,  1,  2, -2,  3,  0,  0,  0},    -0.46e-6,   0.00e-6 },
+      {{ 0,  1,  2, -2,  1,  0,  0,  0},    -0.45e-6,   0.00e-6 },
+      {{ 0,  0,  4, -4,  4,  0,  0,  0},    -0.36e-6,   0.00e-6 },
+      {{ 0,  0,  1, -1,  1, -8, 12,  0},     0.24e-6,   0.12e-6 },
+      {{ 0,  0,  2,  0,  0,  0,  0,  0},    -0.32e-6,   0.00e-6 },
+      {{ 0,  0,  2,  0,  2,  0,  0,  0},    -0.28e-6,   0.00e-6 },
+      {{ 1,  0,  2,  0,  3,  0,  0,  0},    -0.27e-6,   0.00e-6 },
+      {{ 1,  0,  2,  0,  1,  0,  0,  0},    -0.26e-6,   0.00e-6 },
+
+   /* 21-30 */
+      {{ 0,  0,  2, -2,  0,  0,  0,  0},     0.21e-6,   0.00e-6 },
+      {{ 0,  1, -2,  2, -3,  0,  0,  0},    -0.19e-6,   0.00e-6 },
+      {{ 0,  1, -2,  2, -1,  0,  0,  0},    -0.18e-6,   0.00e-6 },
+      {{ 0,  0,  0,  0,  0,  8,-13, -1},     0.10e-6,  -0.05e-6 },
+      {{ 0,  0,  0,  2,  0,  0,  0,  0},    -0.15e-6,   0.00e-6 },
+      {{ 2,  0, -2,  0, -1,  0,  0,  0},     0.14e-6,   0.00e-6 },
+      {{ 0,  1,  2, -2,  2,  0,  0,  0},     0.14e-6,   0.00e-6 },
+      {{ 1,  0,  0, -2,  1,  0,  0,  0},    -0.14e-6,   0.00e-6 },
+      {{ 1,  0,  0, -2, -1,  0,  0,  0},    -0.14e-6,   0.00e-6 },
+      {{ 0,  0,  4, -2,  4,  0,  0,  0},    -0.13e-6,   0.00e-6 },
+
+   /* 31-33 */
+      {{ 0,  0,  2, -2,  4,  0,  0,  0},     0.11e-6,   0.00e-6 },
+      {{ 1,  0, -2,  0, -3,  0,  0,  0},    -0.11e-6,   0.00e-6 },
+      {{ 1,  0, -2,  0, -1,  0,  0,  0},    -0.11e-6,   0.00e-6 }
+   };
+
+/* Terms of order t^1 */
+   static const TERM s1[] ={
+
+   /* 1-3 */
+      {{ 0,  0,  0,  0,  2,  0,  0,  0},    -0.07e-6,   3.57e-6 },
+      {{ 0,  0,  0,  0,  1,  0,  0,  0},     1.71e-6,  -0.03e-6 },
+      {{ 0,  0,  2, -2,  3,  0,  0,  0},     0.00e-6,   0.48e-6 }
+   };
+
+/* Terms of order t^2 */
+   static const TERM s2[] ={
+
+   /* 1-10 */
+      {{ 0,  0,  0,  0,  1,  0,  0,  0},   743.53e-6,  -0.17e-6 },
+      {{ 0,  0,  2, -2,  2,  0,  0,  0},    56.91e-6,   0.06e-6 },
+      {{ 0,  0,  2,  0,  2,  0,  0,  0},     9.84e-6,  -0.01e-6 },
+      {{ 0,  0,  0,  0,  2,  0,  0,  0},    -8.85e-6,   0.01e-6 },
+      {{ 0,  1,  0,  0,  0,  0,  0,  0},    -6.38e-6,  -0.05e-6 },
+      {{ 1,  0,  0,  0,  0,  0,  0,  0},    -3.07e-6,   0.00e-6 },
+      {{ 0,  1,  2, -2,  2,  0,  0,  0},     2.23e-6,   0.00e-6 },
+      {{ 0,  0,  2,  0,  1,  0,  0,  0},     1.67e-6,   0.00e-6 },
+      {{ 1,  0,  2,  0,  2,  0,  0,  0},     1.30e-6,   0.00e-6 },
+      {{ 0,  1, -2,  2, -2,  0,  0,  0},     0.93e-6,   0.00e-6 },
+
+   /* 11-20 */
+      {{ 1,  0,  0, -2,  0,  0,  0,  0},     0.68e-6,   0.00e-6 },
+      {{ 0,  0,  2, -2,  1,  0,  0,  0},    -0.55e-6,   0.00e-6 },
+      {{ 1,  0, -2,  0, -2,  0,  0,  0},     0.53e-6,   0.00e-6 },
+      {{ 0,  0,  0,  2,  0,  0,  0,  0},    -0.27e-6,   0.00e-6 },
+      {{ 1,  0,  0,  0,  1,  0,  0,  0},    -0.27e-6,   0.00e-6 },
+      {{ 1,  0, -2, -2, -2,  0,  0,  0},    -0.26e-6,   0.00e-6 },
+      {{ 1,  0,  0,  0, -1,  0,  0,  0},    -0.25e-6,   0.00e-6 },
+      {{ 1,  0,  2,  0,  1,  0,  0,  0},     0.22e-6,   0.00e-6 },
+      {{ 2,  0,  0, -2,  0,  0,  0,  0},    -0.21e-6,   0.00e-6 },
+      {{ 2,  0, -2,  0, -1,  0,  0,  0},     0.20e-6,   0.00e-6 },
+
+   /* 21-25 */
+      {{ 0,  0,  2,  2,  2,  0,  0,  0},     0.17e-6,   0.00e-6 },
+      {{ 2,  0,  2,  0,  2,  0,  0,  0},     0.13e-6,   0.00e-6 },
+      {{ 2,  0,  0,  0,  0,  0,  0,  0},    -0.13e-6,   0.00e-6 },
+      {{ 1,  0,  2, -2,  2,  0,  0,  0},    -0.12e-6,   0.00e-6 },
+      {{ 0,  0,  2,  0,  0,  0,  0,  0},    -0.11e-6,   0.00e-6 }
+   };
+
+/* Terms of order t^3 */
+   static const TERM s3[] ={
+
+   /* 1-4 */
+      {{ 0,  0,  0,  0,  1,  0,  0,  0},     0.30e-6, -23.51e-6 },
+      {{ 0,  0,  2, -2,  2,  0,  0,  0},    -0.03e-6,  -1.39e-6 },
+      {{ 0,  0,  2,  0,  2,  0,  0,  0},    -0.01e-6,  -0.24e-6 },
+      {{ 0,  0,  0,  0,  2,  0,  0,  0},     0.00e-6,   0.22e-6 }
+   };
+
+/* Terms of order t^4 */
+   static const TERM s4[] ={
+
+   /* 1-1 */
+      {{ 0,  0,  0,  0,  1,  0,  0,  0},    -0.26e-6,  -0.01e-6 }
+   };
+
+/* Number of terms in the series */
+   const int NS0 = (int) (sizeof s0 / sizeof (TERM));
+   const int NS1 = (int) (sizeof s1 / sizeof (TERM));
+   const int NS2 = (int) (sizeof s2 / sizeof (TERM));
+   const int NS3 = (int) (sizeof s3 / sizeof (TERM));
+   const int NS4 = (int) (sizeof s4 / sizeof (TERM));
+
+/*--------------------------------------------------------------------*/
+
+/* Interval between fundamental epoch J2000.0 and current date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Fundamental Arguments (from IERS Conventions 2003) */
+
+/* Mean anomaly of the Moon. */
+   fa[0] = eraFal03(t);
+
+/* Mean anomaly of the Sun. */
+   fa[1] = eraFalp03(t);
+
+/* Mean longitude of the Moon minus that of the ascending node. */
+   fa[2] = eraFaf03(t);
+
+/* Mean elongation of the Moon from the Sun. */
+   fa[3] = eraFad03(t);
+
+/* Mean longitude of the ascending node of the Moon. */
+   fa[4] = eraFaom03(t);
+
+/* Mean longitude of Venus. */
+   fa[5] = eraFave03(t);
+
+/* Mean longitude of Earth. */
+   fa[6] = eraFae03(t);
+
+/* General precession in longitude. */
+   fa[7] = eraFapa03(t);
+
+/* Evaluate s. */
+   w0 = sp[0];
+   w1 = sp[1];
+   w2 = sp[2];
+   w3 = sp[3];
+   w4 = sp[4];
+   w5 = sp[5];
+
+   for (i = NS0-1; i >= 0; i--) {
+   a = 0.0;
+   for (j = 0; j < 8; j++) {
+       a += (double)s0[i].nfa[j] * fa[j];
+   }
+   w0 += s0[i].s * sin(a) + s0[i].c * cos(a);
+   }
+
+   for (i = NS1-1; i >= 0; i--) {
+   a = 0.0;
+   for (j = 0; j < 8; j++) {
+       a += (double)s1[i].nfa[j] * fa[j];
+   }
+   w1 += s1[i].s * sin(a) + s1[i].c * cos(a);
+   }
+
+   for (i = NS2-1; i >= 0; i--) {
+   a = 0.0;
+   for (j = 0; j < 8; j++) {
+       a += (double)s2[i].nfa[j] * fa[j];
+   }
+   w2 += s2[i].s * sin(a) + s2[i].c * cos(a);
+   }
+
+   for (i = NS3-1; i >= 0; i--) {
+   a = 0.0;
+   for (j = 0; j < 8; j++) {
+       a += (double)s3[i].nfa[j] * fa[j];
+   }
+   w3 += s3[i].s * sin(a) + s3[i].c * cos(a);
+   }
+
+   for (i = NS4-1; i >= 0; i--) {
+   a = 0.0;
+   for (j = 0; j < 8; j++) {
+       a += (double)s4[i].nfa[j] * fa[j];
+   }
+   w4 += s4[i].s * sin(a) + s4[i].c * cos(a);
+   }
+
+   s = (w0 +
+       (w1 +
+       (w2 +
+       (w3 +
+       (w4 +
+        w5 * t) * t) * t) * t) * t) * ERFA_DAS2R - x*y/2.0;
+
+   return s;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/s00a.c b/ast/erfa/s00a.c
new file mode 100644
index 0000000..a65bb30
--- /dev/null
+++ b/ast/erfa/s00a.c
@@ -0,0 +1,152 @@
+#include "erfa.h"
+
+double eraS00a(double date1, double date2)
+/*
+**  - - - - - - - -
+**   e r a S 0 0 a
+**  - - - - - - - -
+**
+**  The CIO locator s, positioning the Celestial Intermediate Origin on
+**  the equator of the Celestial Intermediate Pole, using the IAU 2000A
+**  precession-nutation model.
+**
+**  Given:
+**     date1,date2  double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                  double    the CIO locator s in radians (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The CIO locator s is the difference between the right ascensions
+**     of the same point in two systems.  The two systems are the GCRS
+**     and the CIP,CIO, and the point is the ascending node of the
+**     CIP equator.  The CIO locator s remains a small fraction of
+**     1 arcsecond throughout 1900-2100.
+**
+**  3) The series used to compute s is in fact for s+XY/2, where X and Y
+**     are the x and y components of the CIP unit vector;  this series
+**     is more compact than a direct series for s would be.  The present
+**     function uses the full IAU 2000A nutation model when predicting
+**     the CIP position.  Faster results, with no significant loss of
+**     accuracy, can be obtained via the function eraS00b, which uses
+**     instead the IAU 2000B truncated model.
+**
+**  Called:
+**     eraPnm00a    classical NPB matrix, IAU 2000A
+**     eraBnp2xy    extract CIP X,Y from the BPN matrix
+**     eraS00       the CIO locator s, given X,Y, IAU 2000A
+**
+**  References:
+**
+**     Capitaine, N., Chapront, J., Lambert, S. and Wallace, P.,
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154 (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rbpn[3][3], x, y, s;
+
+
+/* Bias-precession-nutation-matrix, IAU 2000A. */
+   eraPnm00a(date1, date2, rbpn);
+
+/* Extract the CIP coordinates. */
+   eraBpn2xy(rbpn, &x, &y);
+
+/* Compute the CIO locator s, given the CIP coordinates. */
+   s = eraS00(date1, date2, x, y);
+
+   return s;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/s00b.c b/ast/erfa/s00b.c
new file mode 100644
index 0000000..370f95b
--- /dev/null
+++ b/ast/erfa/s00b.c
@@ -0,0 +1,152 @@
+#include "erfa.h"
+
+double eraS00b(double date1, double date2)
+/*
+**  - - - - - - - -
+**   e r a S 0 0 b
+**  - - - - - - - -
+**
+**  The CIO locator s, positioning the Celestial Intermediate Origin on
+**  the equator of the Celestial Intermediate Pole, using the IAU 2000B
+**  precession-nutation model.
+**
+**  Given:
+**     date1,date2  double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                  double    the CIO locator s in radians (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The CIO locator s is the difference between the right ascensions
+**     of the same point in two systems.  The two systems are the GCRS
+**     and the CIP,CIO, and the point is the ascending node of the
+**     CIP equator.  The CIO locator s remains a small fraction of
+**     1 arcsecond throughout 1900-2100.
+**
+**  3) The series used to compute s is in fact for s+XY/2, where X and Y
+**     are the x and y components of the CIP unit vector;  this series
+**     is more compact than a direct series for s would be.  The present
+**     function uses the IAU 2000B truncated nutation model when
+**     predicting the CIP position.  The function eraS00a uses instead
+**     the full IAU 2000A model, but with no significant increase in
+**     accuracy and at some cost in speed.
+**
+**  Called:
+**     eraPnm00b    classical NPB matrix, IAU 2000B
+**     eraBnp2xy    extract CIP X,Y from the BPN matrix
+**     eraS00       the CIO locator s, given X,Y, IAU 2000A
+**
+**  References:
+**
+**     Capitaine, N., Chapront, J., Lambert, S. and Wallace, P.,
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154 (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rbpn[3][3], x, y, s;
+
+
+/* Bias-precession-nutation-matrix, IAU 2000B. */
+   eraPnm00b(date1, date2, rbpn);
+
+/* Extract the CIP coordinates. */
+   eraBpn2xy(rbpn, &x, &y);
+
+/* Compute the CIO locator s, given the CIP coordinates. */
+   s = eraS00(date1, date2, x, y);
+
+   return s;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/s06.c b/ast/erfa/s06.c
new file mode 100644
index 0000000..21546c5
--- /dev/null
+++ b/ast/erfa/s06.c
@@ -0,0 +1,377 @@
+#include "erfa.h"
+
+double eraS06(double date1, double date2, double x, double y)
+/*
+**  - - - - - - -
+**   e r a S 0 6
+**  - - - - - - -
+**
+**  The CIO locator s, positioning the Celestial Intermediate Origin on
+**  the equator of the Celestial Intermediate Pole, given the CIP's X,Y
+**  coordinates.  Compatible with IAU 2006/2000A precession-nutation.
+**
+**  Given:
+**     date1,date2   double    TT as a 2-part Julian Date (Note 1)
+**     x,y           double    CIP coordinates (Note 3)
+**
+**  Returned (function value):
+**                   double    the CIO locator s in radians (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The CIO locator s is the difference between the right ascensions
+**     of the same point in two systems:  the two systems are the GCRS
+**     and the CIP,CIO, and the point is the ascending node of the
+**     CIP equator.  The quantity s remains below 0.1 arcsecond
+**     throughout 1900-2100.
+**
+**  3) The series used to compute s is in fact for s+XY/2, where X and Y
+**     are the x and y components of the CIP unit vector;  this series
+**     is more compact than a direct series for s would be.  This
+**     function requires X,Y to be supplied by the caller, who is
+**     responsible for providing values that are consistent with the
+**     supplied date.
+**
+**  4) The model is consistent with the "P03" precession (Capitaine et
+**     al. 2003), adopted by IAU 2006 Resolution 1, 2006, and the
+**     IAU 2000A nutation (with P03 adjustments).
+**
+**  Called:
+**     eraFal03     mean anomaly of the Moon
+**     eraFalp03    mean anomaly of the Sun
+**     eraFaf03     mean argument of the latitude of the Moon
+**     eraFad03     mean elongation of the Moon from the Sun
+**     eraFaom03    mean longitude of the Moon's ascending node
+**     eraFave03    mean longitude of Venus
+**     eraFae03     mean longitude of Earth
+**     eraFapa03    general accumulated precession in longitude
+**
+**  References:
+**
+**     Capitaine, N., Wallace, P.T. & Chapront, J., 2003, Astron.
+**     Astrophys. 432, 355
+**
+**     McCarthy, D.D., Petit, G. (eds.) 2004, IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Time since J2000.0, in Julian centuries */
+   double t;
+
+/* Miscellaneous */
+   int i, j;
+   double a, w0, w1, w2, w3, w4, w5;
+
+/* Fundamental arguments */
+   double fa[8];
+
+/* Returned value */
+   double s;
+
+/* --------------------- */
+/* The series for s+XY/2 */
+/* --------------------- */
+
+   typedef struct {
+      int nfa[8];      /* coefficients of l,l',F,D,Om,LVe,LE,pA */
+      double s, c;     /* sine and cosine coefficients */
+   } TERM;
+
+/* Polynomial coefficients */
+   static const double sp[] = {
+
+   /* 1-6 */
+          94.00e-6,
+        3808.65e-6,
+        -122.68e-6,
+      -72574.11e-6,
+          27.98e-6,
+          15.62e-6
+   };
+
+/* Terms of order t^0 */
+   static const TERM s0[] = {
+
+   /* 1-10 */
+      {{ 0,  0,  0,  0,  1,  0,  0,  0}, -2640.73e-6,   0.39e-6 },
+      {{ 0,  0,  0,  0,  2,  0,  0,  0},   -63.53e-6,   0.02e-6 },
+      {{ 0,  0,  2, -2,  3,  0,  0,  0},   -11.75e-6,  -0.01e-6 },
+      {{ 0,  0,  2, -2,  1,  0,  0,  0},   -11.21e-6,  -0.01e-6 },
+      {{ 0,  0,  2, -2,  2,  0,  0,  0},     4.57e-6,   0.00e-6 },
+      {{ 0,  0,  2,  0,  3,  0,  0,  0},    -2.02e-6,   0.00e-6 },
+      {{ 0,  0,  2,  0,  1,  0,  0,  0},    -1.98e-6,   0.00e-6 },
+      {{ 0,  0,  0,  0,  3,  0,  0,  0},     1.72e-6,   0.00e-6 },
+      {{ 0,  1,  0,  0,  1,  0,  0,  0},     1.41e-6,   0.01e-6 },
+      {{ 0,  1,  0,  0, -1,  0,  0,  0},     1.26e-6,   0.01e-6 },
+
+   /* 11-20 */
+      {{ 1,  0,  0,  0, -1,  0,  0,  0},     0.63e-6,   0.00e-6 },
+      {{ 1,  0,  0,  0,  1,  0,  0,  0},     0.63e-6,   0.00e-6 },
+      {{ 0,  1,  2, -2,  3,  0,  0,  0},    -0.46e-6,   0.00e-6 },
+      {{ 0,  1,  2, -2,  1,  0,  0,  0},    -0.45e-6,   0.00e-6 },
+      {{ 0,  0,  4, -4,  4,  0,  0,  0},    -0.36e-6,   0.00e-6 },
+      {{ 0,  0,  1, -1,  1, -8, 12,  0},     0.24e-6,   0.12e-6 },
+      {{ 0,  0,  2,  0,  0,  0,  0,  0},    -0.32e-6,   0.00e-6 },
+      {{ 0,  0,  2,  0,  2,  0,  0,  0},    -0.28e-6,   0.00e-6 },
+      {{ 1,  0,  2,  0,  3,  0,  0,  0},    -0.27e-6,   0.00e-6 },
+      {{ 1,  0,  2,  0,  1,  0,  0,  0},    -0.26e-6,   0.00e-6 },
+
+   /* 21-30 */
+      {{ 0,  0,  2, -2,  0,  0,  0,  0},     0.21e-6,   0.00e-6 },
+      {{ 0,  1, -2,  2, -3,  0,  0,  0},    -0.19e-6,   0.00e-6 },
+      {{ 0,  1, -2,  2, -1,  0,  0,  0},    -0.18e-6,   0.00e-6 },
+      {{ 0,  0,  0,  0,  0,  8,-13, -1},     0.10e-6,  -0.05e-6 },
+      {{ 0,  0,  0,  2,  0,  0,  0,  0},    -0.15e-6,   0.00e-6 },
+      {{ 2,  0, -2,  0, -1,  0,  0,  0},     0.14e-6,   0.00e-6 },
+      {{ 0,  1,  2, -2,  2,  0,  0,  0},     0.14e-6,   0.00e-6 },
+      {{ 1,  0,  0, -2,  1,  0,  0,  0},    -0.14e-6,   0.00e-6 },
+      {{ 1,  0,  0, -2, -1,  0,  0,  0},    -0.14e-6,   0.00e-6 },
+      {{ 0,  0,  4, -2,  4,  0,  0,  0},    -0.13e-6,   0.00e-6 },
+
+   /* 31-33 */
+      {{ 0,  0,  2, -2,  4,  0,  0,  0},     0.11e-6,   0.00e-6 },
+      {{ 1,  0, -2,  0, -3,  0,  0,  0},    -0.11e-6,   0.00e-6 },
+      {{ 1,  0, -2,  0, -1,  0,  0,  0},    -0.11e-6,   0.00e-6 }
+   };
+
+/* Terms of order t^1 */
+   static const TERM s1[] = {
+
+   /* 1 - 3 */
+      {{ 0,  0,  0,  0,  2,  0,  0,  0},    -0.07e-6,   3.57e-6 },
+      {{ 0,  0,  0,  0,  1,  0,  0,  0},     1.73e-6,  -0.03e-6 },
+      {{ 0,  0,  2, -2,  3,  0,  0,  0},     0.00e-6,   0.48e-6 }
+   };
+
+/* Terms of order t^2 */
+   static const TERM s2[] = {
+
+   /* 1-10 */
+      {{ 0,  0,  0,  0,  1,  0,  0,  0},   743.52e-6,  -0.17e-6 },
+      {{ 0,  0,  2, -2,  2,  0,  0,  0},    56.91e-6,   0.06e-6 },
+      {{ 0,  0,  2,  0,  2,  0,  0,  0},     9.84e-6,  -0.01e-6 },
+      {{ 0,  0,  0,  0,  2,  0,  0,  0},    -8.85e-6,   0.01e-6 },
+      {{ 0,  1,  0,  0,  0,  0,  0,  0},    -6.38e-6,  -0.05e-6 },
+      {{ 1,  0,  0,  0,  0,  0,  0,  0},    -3.07e-6,   0.00e-6 },
+      {{ 0,  1,  2, -2,  2,  0,  0,  0},     2.23e-6,   0.00e-6 },
+      {{ 0,  0,  2,  0,  1,  0,  0,  0},     1.67e-6,   0.00e-6 },
+      {{ 1,  0,  2,  0,  2,  0,  0,  0},     1.30e-6,   0.00e-6 },
+      {{ 0,  1, -2,  2, -2,  0,  0,  0},     0.93e-6,   0.00e-6 },
+
+   /* 11-20 */
+      {{ 1,  0,  0, -2,  0,  0,  0,  0},     0.68e-6,   0.00e-6 },
+      {{ 0,  0,  2, -2,  1,  0,  0,  0},    -0.55e-6,   0.00e-6 },
+      {{ 1,  0, -2,  0, -2,  0,  0,  0},     0.53e-6,   0.00e-6 },
+      {{ 0,  0,  0,  2,  0,  0,  0,  0},    -0.27e-6,   0.00e-6 },
+      {{ 1,  0,  0,  0,  1,  0,  0,  0},    -0.27e-6,   0.00e-6 },
+      {{ 1,  0, -2, -2, -2,  0,  0,  0},    -0.26e-6,   0.00e-6 },
+      {{ 1,  0,  0,  0, -1,  0,  0,  0},    -0.25e-6,   0.00e-6 },
+      {{ 1,  0,  2,  0,  1,  0,  0,  0},     0.22e-6,   0.00e-6 },
+      {{ 2,  0,  0, -2,  0,  0,  0,  0},    -0.21e-6,   0.00e-6 },
+      {{ 2,  0, -2,  0, -1,  0,  0,  0},     0.20e-6,   0.00e-6 },
+
+   /* 21-25 */
+      {{ 0,  0,  2,  2,  2,  0,  0,  0},     0.17e-6,   0.00e-6 },
+      {{ 2,  0,  2,  0,  2,  0,  0,  0},     0.13e-6,   0.00e-6 },
+      {{ 2,  0,  0,  0,  0,  0,  0,  0},    -0.13e-6,   0.00e-6 },
+      {{ 1,  0,  2, -2,  2,  0,  0,  0},    -0.12e-6,   0.00e-6 },
+      {{ 0,  0,  2,  0,  0,  0,  0,  0},    -0.11e-6,   0.00e-6 }
+   };
+
+/* Terms of order t^3 */
+   static const TERM s3[] = {
+
+   /* 1-4 */
+      {{ 0,  0,  0,  0,  1,  0,  0,  0},     0.30e-6, -23.42e-6 },
+      {{ 0,  0,  2, -2,  2,  0,  0,  0},    -0.03e-6,  -1.46e-6 },
+      {{ 0,  0,  2,  0,  2,  0,  0,  0},    -0.01e-6,  -0.25e-6 },
+      {{ 0,  0,  0,  0,  2,  0,  0,  0},     0.00e-6,   0.23e-6 }
+   };
+
+/* Terms of order t^4 */
+   static const TERM s4[] = {
+
+   /* 1-1 */
+      {{ 0,  0,  0,  0,  1,  0,  0,  0},    -0.26e-6,  -0.01e-6 }
+   };
+
+/* Number of terms in the series */
+   static const int NS0 = (int) (sizeof s0 / sizeof (TERM));
+   static const int NS1 = (int) (sizeof s1 / sizeof (TERM));
+   static const int NS2 = (int) (sizeof s2 / sizeof (TERM));
+   static const int NS3 = (int) (sizeof s3 / sizeof (TERM));
+   static const int NS4 = (int) (sizeof s4 / sizeof (TERM));
+
+/*--------------------------------------------------------------------*/
+
+/* Interval between fundamental epoch J2000.0 and current date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Fundamental Arguments (from IERS Conventions 2003) */
+
+/* Mean anomaly of the Moon. */
+   fa[0] = eraFal03(t);
+
+/* Mean anomaly of the Sun. */
+   fa[1] = eraFalp03(t);
+
+/* Mean longitude of the Moon minus that of the ascending node. */
+   fa[2] = eraFaf03(t);
+
+/* Mean elongation of the Moon from the Sun. */
+   fa[3] = eraFad03(t);
+
+/* Mean longitude of the ascending node of the Moon. */
+   fa[4] = eraFaom03(t);
+
+/* Mean longitude of Venus. */
+   fa[5] = eraFave03(t);
+
+/* Mean longitude of Earth. */
+   fa[6] = eraFae03(t);
+
+/* General precession in longitude. */
+   fa[7] = eraFapa03(t);
+
+/* Evaluate s. */
+   w0 = sp[0];
+   w1 = sp[1];
+   w2 = sp[2];
+   w3 = sp[3];
+   w4 = sp[4];
+   w5 = sp[5];
+
+   for (i = NS0-1; i >= 0; i--) {
+   a = 0.0;
+   for (j = 0; j < 8; j++) {
+      a += (double)s0[i].nfa[j] * fa[j];
+   }
+   w0 += s0[i].s * sin(a) + s0[i].c * cos(a);
+   }
+
+   for (i = NS1-1; i >= 0; i--) {
+      a = 0.0;
+      for (j = 0; j < 8; j++) {
+         a += (double)s1[i].nfa[j] * fa[j];
+      }
+      w1 += s1[i].s * sin(a) + s1[i].c * cos(a);
+   }
+
+   for (i = NS2-1; i >= 0; i--) {
+      a = 0.0;
+      for (j = 0; j < 8; j++) {
+         a += (double)s2[i].nfa[j] * fa[j];
+      }
+      w2 += s2[i].s * sin(a) + s2[i].c * cos(a);
+   }
+
+   for (i = NS3-1; i >= 0; i--) {
+      a = 0.0;
+      for (j = 0; j < 8; j++) {
+         a += (double)s3[i].nfa[j] * fa[j];
+      }
+      w3 += s3[i].s * sin(a) + s3[i].c * cos(a);
+   }
+
+   for (i = NS4-1; i >= 0; i--) {
+      a = 0.0;
+      for (j = 0; j < 8; j++) {
+         a += (double)s4[i].nfa[j] * fa[j];
+      }
+      w4 += s4[i].s * sin(a) + s4[i].c * cos(a);
+   }
+
+   s = (w0 +
+       (w1 +
+       (w2 +
+       (w3 +
+       (w4 +
+        w5 * t) * t) * t) * t) * t) * ERFA_DAS2R - x*y/2.0;
+
+   return s;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/s06a.c b/ast/erfa/s06a.c
new file mode 100644
index 0000000..575c3dd
--- /dev/null
+++ b/ast/erfa/s06a.c
@@ -0,0 +1,154 @@
+#include "erfa.h"
+
+double eraS06a(double date1, double date2)
+/*
+**  - - - - - - - -
+**   e r a S 0 6 a
+**  - - - - - - - -
+**
+**  The CIO locator s, positioning the Celestial Intermediate Origin on
+**  the equator of the Celestial Intermediate Pole, using the IAU 2006
+**  precession and IAU 2000A nutation models.
+**
+**  Given:
+**     date1,date2  double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                  double    the CIO locator s in radians (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The CIO locator s is the difference between the right ascensions
+**     of the same point in two systems.  The two systems are the GCRS
+**     and the CIP,CIO, and the point is the ascending node of the
+**     CIP equator.  The CIO locator s remains a small fraction of
+**     1 arcsecond throughout 1900-2100.
+**
+**  3) The series used to compute s is in fact for s+XY/2, where X and Y
+**     are the x and y components of the CIP unit vector;  this series is
+**     more compact than a direct series for s would be.  The present
+**     function uses the full IAU 2000A nutation model when predicting
+**     the CIP position.
+**
+**  Called:
+**     eraPnm06a    classical NPB matrix, IAU 2006/2000A
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**     eraS06       the CIO locator s, given X,Y, IAU 2006
+**
+**  References:
+**
+**     Capitaine, N., Chapront, J., Lambert, S. and Wallace, P.,
+**     "Expressions for the Celestial Intermediate Pole and Celestial
+**     Ephemeris Origin consistent with the IAU 2000A precession-
+**     nutation model", Astron.Astrophys. 400, 1145-1154 (2003)
+**
+**     n.b. The celestial ephemeris origin (CEO) was renamed "celestial
+**          intermediate origin" (CIO) by IAU 2006 Resolution 2.
+**
+**     Capitaine, N. & Wallace, P.T., 2006, Astron.Astrophys. 450, 855
+**
+**     McCarthy, D. D., Petit, G. (eds.), 2004, IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG
+**
+**     Wallace, P.T. & Capitaine, N., 2006, Astron.Astrophys. 459, 981
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rnpb[3][3], x, y, s;
+
+
+/* Bias-precession-nutation-matrix, IAU 20006/2000A. */
+   eraPnm06a(date1, date2, rnpb);
+
+/* Extract the CIP coordinates. */
+   eraBpn2xy(rnpb, &x, &y);
+
+/* Compute the CIO locator s, given the CIP coordinates. */
+   s = eraS06(date1, date2, x, y);
+
+   return s;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/s2c.c b/ast/erfa/s2c.c
new file mode 100644
index 0000000..067cec1
--- /dev/null
+++ b/ast/erfa/s2c.c
@@ -0,0 +1,94 @@
+#include "erfa.h"
+
+void eraS2c(double theta, double phi, double c[3])
+/*
+**  - - - - - - -
+**   e r a S 2 c
+**  - - - - - - -
+**
+**  Convert spherical coordinates to Cartesian.
+**
+**  Given:
+**     theta    double       longitude angle (radians)
+**     phi      double       latitude angle (radians)
+**
+**  Returned:
+**     c        double[3]    direction cosines
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double cp;
+
+
+   cp = cos(phi);
+   c[0] = cos(theta) * cp;
+   c[1] = sin(theta) * cp;
+   c[2] = sin(phi);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/s2p.c b/ast/erfa/s2p.c
new file mode 100644
index 0000000..a9b02bb
--- /dev/null
+++ b/ast/erfa/s2p.c
@@ -0,0 +1,97 @@
+#include "erfa.h"
+
+void eraS2p(double theta, double phi, double r, double p[3])
+/*
+**  - - - - - - -
+**   e r a S 2 p
+**  - - - - - - -
+**
+**  Convert spherical polar coordinates to p-vector.
+**
+**  Given:
+**     theta   double       longitude angle (radians)
+**     phi     double       latitude angle (radians)
+**     r       double       radial distance
+**
+**  Returned:
+**     p       double[3]    Cartesian coordinates
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraSxp       multiply p-vector by scalar
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double u[3];
+
+
+   eraS2c(theta, phi, u);
+   eraSxp(r, u, p);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/s2pv.c b/ast/erfa/s2pv.c
new file mode 100644
index 0000000..df6e1c7
--- /dev/null
+++ b/ast/erfa/s2pv.c
@@ -0,0 +1,112 @@
+#include "erfa.h"
+
+void eraS2pv(double theta, double phi, double r,
+             double td, double pd, double rd,
+             double pv[2][3])
+/*
+**  - - - - - - - -
+**   e r a S 2 p v
+**  - - - - - - - -
+**
+**  Convert position/velocity from spherical to Cartesian coordinates.
+**
+**  Given:
+**     theta    double          longitude angle (radians)
+**     phi      double          latitude angle (radians)
+**     r        double          radial distance
+**     td       double          rate of change of theta
+**     pd       double          rate of change of phi
+**     rd       double          rate of change of r
+**
+**  Returned:
+**     pv       double[2][3]    pv-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double st, ct, sp, cp, rcp, x, y, rpd, w;
+
+
+   st = sin(theta);
+   ct = cos(theta);
+   sp = sin(phi);
+   cp = cos(phi);
+   rcp = r * cp;
+   x = rcp * ct;
+   y = rcp * st;
+   rpd = r * pd;
+   w = rpd*sp - cp*rd;
+
+   pv[0][0] = x;
+   pv[0][1] = y;
+   pv[0][2] = r * sp;
+   pv[1][0] = -y*td - w*ct;
+   pv[1][1] =  x*td - w*st;
+   pv[1][2] = rpd*cp + sp*rd;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/s2xpv.c b/ast/erfa/s2xpv.c
new file mode 100644
index 0000000..9b554da
--- /dev/null
+++ b/ast/erfa/s2xpv.c
@@ -0,0 +1,96 @@
+#include "erfa.h"
+
+void eraS2xpv(double s1, double s2, double pv[2][3], double spv[2][3])
+/*
+**  - - - - - - - - -
+**   e r a S 2 x p v
+**  - - - - - - - - -
+**
+**  Multiply a pv-vector by two scalars.
+**
+**  Given:
+**     s1     double         scalar to multiply position component by
+**     s2     double         scalar to multiply velocity component by
+**     pv     double[2][3]   pv-vector
+**
+**  Returned:
+**     spv    double[2][3]   pv-vector: p scaled by s1, v scaled by s2
+**
+**  Note:
+**     It is permissible for pv and spv to be the same array.
+**
+**  Called:
+**     eraSxp       multiply p-vector by scalar
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraSxp(s1, pv[0], spv[0]);
+   eraSxp(s2, pv[1], spv[1]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/sepp.c b/ast/erfa/sepp.c
new file mode 100644
index 0000000..98f2671
--- /dev/null
+++ b/ast/erfa/sepp.c
@@ -0,0 +1,114 @@
+#include "erfa.h"
+
+double eraSepp(double a[3], double b[3])
+/*
+**  - - - - - - - -
+**   e r a S e p p
+**  - - - - - - - -
+**
+**  Angular separation between two p-vectors.
+**
+**  Given:
+**     a      double[3]    first p-vector (not necessarily unit length)
+**     b      double[3]    second p-vector (not necessarily unit length)
+**
+**  Returned (function value):
+**            double       angular separation (radians, always positive)
+**
+**  Notes:
+**
+**  1) If either vector is null, a zero result is returned.
+**
+**  2) The angular separation is most simply formulated in terms of
+**     scalar product.  However, this gives poor accuracy for angles
+**     near zero and pi.  The present algorithm uses both cross product
+**     and dot product, to deliver full accuracy whatever the size of
+**     the angle.
+**
+**  Called:
+**     eraPxp       vector product of two p-vectors
+**     eraPm        modulus of p-vector
+**     eraPdp       scalar product of two p-vectors
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double axb[3], ss, cs, s;
+
+
+/* Sine of angle between the vectors, multiplied by the two moduli. */
+   eraPxp(a, b, axb);
+   ss = eraPm(axb);
+
+/* Cosine of the angle, multiplied by the two moduli. */
+   cs = eraPdp(a, b);
+
+/* The angle. */
+   s = ((ss != 0.0) || (cs != 0.0)) ? atan2(ss, cs) : 0.0;
+
+   return s;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/seps.c b/ast/erfa/seps.c
new file mode 100644
index 0000000..f97fc58
--- /dev/null
+++ b/ast/erfa/seps.c
@@ -0,0 +1,102 @@
+#include "erfa.h"
+
+double eraSeps(double al, double ap, double bl, double bp)
+/*
+**  - - - - - - - -
+**   e r a S e p s
+**  - - - - - - - -
+**
+**  Angular separation between two sets of spherical coordinates.
+**
+**  Given:
+**     al     double       first longitude (radians)
+**     ap     double       first latitude (radians)
+**     bl     double       second longitude (radians)
+**     bp     double       second latitude (radians)
+**
+**  Returned (function value):
+**            double       angular separation (radians)
+**
+**  Called:
+**     eraS2c       spherical coordinates to unit vector
+**     eraSepp      angular separation between two p-vectors
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double ac[3], bc[3], s;
+
+
+/* Spherical to Cartesian. */
+   eraS2c(al, ap, ac);
+   eraS2c(bl, bp, bc);
+
+/* Angle between the vectors. */
+   s = eraSepp(ac, bc);
+
+   return s;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/sp00.c b/ast/erfa/sp00.c
new file mode 100644
index 0000000..7c57abd
--- /dev/null
+++ b/ast/erfa/sp00.c
@@ -0,0 +1,127 @@
+#include "erfa.h"
+
+double eraSp00(double date1, double date2)
+/*
+**  - - - - - - - -
+**   e r a S p 0 0
+**  - - - - - - - -
+**
+**  The TIO locator s', positioning the Terrestrial Intermediate Origin
+**  on the equator of the Celestial Intermediate Pole.
+**
+**  Given:
+**     date1,date2  double    TT as a 2-part Julian Date (Note 1)
+**
+**  Returned (function value):
+**                  double    the TIO locator s' in radians (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The TIO locator s' is obtained from polar motion observations by
+**     numerical integration, and so is in essence unpredictable.
+**     However, it is dominated by a secular drift of about
+**     47 microarcseconds per century, which is the approximation
+**     evaluated by the present function.
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double t, sp;
+
+
+/* Interval between fundamental epoch J2000.0 and current date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Approximate s'. */
+   sp = -47e-6 * t * ERFA_DAS2R;
+
+   return sp;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/starpm.c b/ast/erfa/starpm.c
new file mode 100644
index 0000000..69b7708
--- /dev/null
+++ b/ast/erfa/starpm.c
@@ -0,0 +1,214 @@
+#include "erfa.h"
+
+int eraStarpm(double ra1, double dec1,
+              double pmr1, double pmd1, double px1, double rv1,
+              double ep1a, double ep1b, double ep2a, double ep2b,
+              double *ra2, double *dec2,
+              double *pmr2, double *pmd2, double *px2, double *rv2)
+/*
+**  - - - - - - - - - -
+**   e r a S t a r p m
+**  - - - - - - - - - -
+**
+**  Star proper motion:  update star catalog data for space motion.
+**
+**  Given:
+**     ra1    double     right ascension (radians), before
+**     dec1   double     declination (radians), before
+**     pmr1   double     RA proper motion (radians/year), before
+**     pmd1   double     Dec proper motion (radians/year), before
+**     px1    double     parallax (arcseconds), before
+**     rv1    double     radial velocity (km/s, +ve = receding), before
+**     ep1a   double     "before" epoch, part A (Note 1)
+**     ep1b   double     "before" epoch, part B (Note 1)
+**     ep2a   double     "after" epoch, part A (Note 1)
+**     ep2b   double     "after" epoch, part B (Note 1)
+**
+**  Returned:
+**     ra2    double     right ascension (radians), after
+**     dec2   double     declination (radians), after
+**     pmr2   double     RA proper motion (radians/year), after
+**     pmd2   double     Dec proper motion (radians/year), after
+**     px2    double     parallax (arcseconds), after
+**     rv2    double     radial velocity (km/s, +ve = receding), after
+**
+**  Returned (function value):
+**            int        status:
+**                          -1 = system error (should not occur)
+**                           0 = no warnings or errors
+**                           1 = distance overridden (Note 6)
+**                           2 = excessive velocity (Note 7)
+**                           4 = solution didn't converge (Note 8)
+**                        else = binary logical OR of the above warnings
+**
+**  Notes:
+**
+**  1) The starting and ending TDB dates ep1a+ep1b and ep2a+ep2b are
+**     Julian Dates, apportioned in any convenient way between the two
+**     parts (A and B).  For example, JD(TDB)=2450123.7 could be
+**     expressed in any of these ways, among others:
+**
+**             epna          epnb
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) In accordance with normal star-catalog conventions, the object's
+**     right ascension and declination are freed from the effects of
+**     secular aberration.  The frame, which is aligned to the catalog
+**     equator and equinox, is Lorentzian and centered on the SSB.
+**
+**     The proper motions are the rate of change of the right ascension
+**     and declination at the catalog epoch and are in radians per TDB
+**     Julian year.
+**
+**     The parallax and radial velocity are in the same frame.
+**
+**  3) Care is needed with units.  The star coordinates are in radians
+**     and the proper motions in radians per Julian year, but the
+**     parallax is in arcseconds.
+**
+**  4) The RA proper motion is in terms of coordinate angle, not true
+**     angle.  If the catalog uses arcseconds for both RA and Dec proper
+**     motions, the RA proper motion will need to be divided by cos(Dec)
+**     before use.
+**
+**  5) Straight-line motion at constant speed, in the inertial frame,
+**     is assumed.
+**
+**  6) An extremely small (or zero or negative) parallax is interpreted
+**     to mean that the object is on the "celestial sphere", the radius
+**     of which is an arbitrary (large) value (see the eraStarpv
+**     function for the value used).  When the distance is overridden in
+**     this way, the status, initially zero, has 1 added to it.
+**
+**  7) If the space velocity is a significant fraction of c (see the
+**     constant VMAX in the function eraStarpv), it is arbitrarily set
+**     to zero.  When this action occurs, 2 is added to the status.
+**
+**  8) The relativistic adjustment carried out in the eraStarpv function
+**     involves an iterative calculation.  If the process fails to
+**     converge within a set number of iterations, 4 is added to the
+**     status.
+**
+**  Called:
+**     eraStarpv    star catalog data to space motion pv-vector
+**     eraPvu       update a pv-vector
+**     eraPdp       scalar product of two p-vectors
+**     eraPvstar    space motion pv-vector to star catalog data
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double pv1[2][3], tl1, dt, pv[2][3], r2, rdv, v2, c2mv2, tl2,
+          pv2[2][3];
+   int j1, j2, j;
+
+
+/* RA,Dec etc. at the "before" epoch to space motion pv-vector. */
+   j1 = eraStarpv(ra1, dec1, pmr1, pmd1, px1, rv1, pv1);
+
+/* Light time when observed (days). */
+   tl1 = eraPm(pv1[0]) / ERFA_DC;
+
+/* Time interval, "before" to "after" (days). */
+   dt = (ep2a - ep1a) + (ep2b - ep1b);
+
+/* Move star along track from the "before" observed position to the */
+/* "after" geometric position. */
+   eraPvu(dt + tl1, pv1, pv);
+
+/* From this geometric position, deduce the observed light time (days) */
+/* at the "after" epoch (with theoretically unneccessary error check). */
+   r2 = eraPdp(pv[0], pv[0]);
+   rdv = eraPdp(pv[0], pv[1]);
+   v2 = eraPdp(pv[1], pv[1]);
+   c2mv2 = ERFA_DC*ERFA_DC - v2;
+   if (c2mv2 <=  0) return -1;
+   tl2 = (-rdv + sqrt(rdv*rdv + c2mv2*r2)) / c2mv2;
+
+/* Move the position along track from the observed place at the */
+/* "before" epoch to the observed place at the "after" epoch. */
+   eraPvu(dt + (tl1 - tl2), pv1, pv2);
+
+/* Space motion pv-vector to RA,Dec etc. at the "after" epoch. */
+   j2 = eraPvstar(pv2, ra2, dec2, pmr2, pmd2, px2, rv2);
+
+/* Final status. */
+   j = (j2 == 0) ? j1 : -1;
+
+   return j;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/starpv.c b/ast/erfa/starpv.c
new file mode 100644
index 0000000..77173ed
--- /dev/null
+++ b/ast/erfa/starpv.c
@@ -0,0 +1,273 @@
+#include "erfa.h"
+
+int eraStarpv(double ra, double dec,
+              double pmr, double pmd, double px, double rv,
+              double pv[2][3])
+/*
+**  - - - - - - - - - -
+**   e r a S t a r p v
+**  - - - - - - - - - -
+**
+**  Convert star catalog coordinates to position+velocity vector.
+**
+**  Given (Note 1):
+**     ra     double        right ascension (radians)
+**     dec    double        declination (radians)
+**     pmr    double        RA proper motion (radians/year)
+**     pmd    double        Dec proper motion (radians/year)
+**     px     double        parallax (arcseconds)
+**     rv     double        radial velocity (km/s, positive = receding)
+**
+**  Returned (Note 2):
+**     pv     double[2][3]  pv-vector (AU, AU/day)
+**
+**  Returned (function value):
+**            int           status:
+**                              0 = no warnings
+**                              1 = distance overridden (Note 6)
+**                              2 = excessive speed (Note 7)
+**                              4 = solution didn't converge (Note 8)
+**                           else = binary logical OR of the above
+**
+**  Notes:
+**
+**  1) The star data accepted by this function are "observables" for an
+**     imaginary observer at the solar-system barycenter.  Proper motion
+**     and radial velocity are, strictly, in terms of barycentric
+**     coordinate time, TCB.  For most practical applications, it is
+**     permissible to neglect the distinction between TCB and ordinary
+**     "proper" time on Earth (TT/TAI).  The result will, as a rule, be
+**     limited by the intrinsic accuracy of the proper-motion and
+**     radial-velocity data;  moreover, the pv-vector is likely to be
+**     merely an intermediate result, so that a change of time unit
+**     would cancel out overall.
+**
+**     In accordance with normal star-catalog conventions, the object's
+**     right ascension and declination are freed from the effects of
+**     secular aberration.  The frame, which is aligned to the catalog
+**     equator and equinox, is Lorentzian and centered on the SSB.
+**
+**  2) The resulting position and velocity pv-vector is with respect to
+**     the same frame and, like the catalog coordinates, is freed from
+**     the effects of secular aberration.  Should the "coordinate
+**     direction", where the object was located at the catalog epoch, be
+**     required, it may be obtained by calculating the magnitude of the
+**     position vector pv[0][0-2] dividing by the speed of light in
+**     AU/day to give the light-time, and then multiplying the space
+**     velocity pv[1][0-2] by this light-time and adding the result to
+**     pv[0][0-2].
+**
+**     Summarizing, the pv-vector returned is for most stars almost
+**     identical to the result of applying the standard geometrical
+**     "space motion" transformation.  The differences, which are the
+**     subject of the Stumpff paper referenced below, are:
+**
+**     (i) In stars with significant radial velocity and proper motion,
+**     the constantly changing light-time distorts the apparent proper
+**     motion.  Note that this is a classical, not a relativistic,
+**     effect.
+**
+**     (ii) The transformation complies with special relativity.
+**
+**  3) Care is needed with units.  The star coordinates are in radians
+**     and the proper motions in radians per Julian year, but the
+**     parallax is in arcseconds; the radial velocity is in km/s, but
+**     the pv-vector result is in AU and AU/day.
+**
+**  4) The RA proper motion is in terms of coordinate angle, not true
+**     angle.  If the catalog uses arcseconds for both RA and Dec proper
+**     motions, the RA proper motion will need to be divided by cos(Dec)
+**     before use.
+**
+**  5) Straight-line motion at constant speed, in the inertial frame,
+**     is assumed.
+**
+**  6) An extremely small (or zero or negative) parallax is interpreted
+**     to mean that the object is on the "celestial sphere", the radius
+**     of which is an arbitrary (large) value (see the constant PXMIN).
+**     When the distance is overridden in this way, the status,
+**     initially zero, has 1 added to it.
+**
+**  7) If the space velocity is a significant fraction of c (see the
+**     constant VMAX), it is arbitrarily set to zero.  When this action
+**     occurs, 2 is added to the status.
+**
+**  8) The relativistic adjustment involves an iterative calculation.
+**     If the process fails to converge within a set number (IMAX) of
+**     iterations, 4 is added to the status.
+**
+**  9) The inverse transformation is performed by the function
+**     eraPvstar.
+**
+**  Called:
+**     eraS2pv      spherical coordinates to pv-vector
+**     eraPm        modulus of p-vector
+**     eraZp        zero p-vector
+**     eraPn        decompose p-vector into modulus and direction
+**     eraPdp       scalar product of two p-vectors
+**     eraSxp       multiply p-vector by scalar
+**     eraPmp       p-vector minus p-vector
+**     eraPpp       p-vector plus p-vector
+**
+**  Reference:
+**
+**     Stumpff, P., 1985, Astron.Astrophys. 144, 232-240.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+/* Smallest allowed parallax */
+   static const double PXMIN = 1e-7;
+
+/* Largest allowed speed (fraction of c) */
+   static const double VMAX = 0.5;
+
+/* Maximum number of iterations for relativistic solution */
+   static const int IMAX = 100;
+
+   int i, iwarn;
+   double w, r, rd, rad, decd, v, x[3], usr[3], ust[3],
+          vsr, vst, betst, betsr, bett, betr,
+          dd, ddel, ur[3], ut[3],
+          d = 0.0, del = 0.0,       /* to prevent */
+          odd = 0.0, oddel = 0.0,   /* compiler   */
+          od = 0.0, odel = 0.0;     /* warnings   */
+
+
+/* Distance (AU). */
+   if (px >= PXMIN) {
+      w = px;
+      iwarn = 0;
+   } else {
+      w = PXMIN;
+      iwarn = 1;
+   }
+   r = ERFA_DR2AS / w;
+
+/* Radial velocity (AU/day). */
+   rd = ERFA_DAYSEC * rv * 1e3 / ERFA_DAU;
+
+/* Proper motion (radian/day). */
+   rad = pmr / ERFA_DJY;
+   decd = pmd / ERFA_DJY;
+
+/* To pv-vector (AU,AU/day). */
+   eraS2pv(ra, dec, r, rad, decd, rd, pv);
+
+/* If excessive velocity, arbitrarily set it to zero. */
+   v = eraPm(pv[1]);
+   if (v / ERFA_DC > VMAX) {
+      eraZp(pv[1]);
+      iwarn += 2;
+   }
+
+/* Isolate the radial component of the velocity (AU/day). */
+   eraPn(pv[0], &w, x);
+   vsr = eraPdp(x, pv[1]);
+   eraSxp(vsr, x, usr);
+
+/* Isolate the transverse component of the velocity (AU/day). */
+   eraPmp(pv[1], usr, ust);
+   vst = eraPm(ust);
+
+/* Special-relativity dimensionless parameters. */
+   betsr = vsr / ERFA_DC;
+   betst = vst / ERFA_DC;
+
+/* Determine the inertial-to-observed relativistic correction terms. */
+   bett = betst;
+   betr = betsr;
+   for (i = 0; i < IMAX; i++) {
+      d = 1.0 + betr;
+      del = sqrt(1.0 - betr*betr - bett*bett) - 1.0;
+      betr = d * betsr + del;
+      bett = d * betst;
+      if (i > 0) {
+         dd = fabs(d - od);
+         ddel = fabs(del - odel);
+         if ((i > 1) && (dd >= odd) && (ddel >= oddel)) break;
+         odd = dd;
+         oddel = ddel;
+      }
+      od = d;
+      odel = del;
+   }
+   if (i >= IMAX) iwarn += 4;
+
+/* Replace observed radial velocity with inertial value. */
+   w = (betsr != 0.0) ? d + del / betsr : 1.0;
+   eraSxp(w, usr, ur);
+
+/* Replace observed tangential velocity with inertial value. */
+   eraSxp(d, ust, ut);
+
+/* Combine the two to obtain the inertial space velocity. */
+   eraPpp(ur, ut, pv[1]);
+
+/* Return the status. */
+   return iwarn;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/sxp.c b/ast/erfa/sxp.c
new file mode 100644
index 0000000..3bd311f
--- /dev/null
+++ b/ast/erfa/sxp.c
@@ -0,0 +1,93 @@
+#include "erfa.h"
+
+void eraSxp(double s, double p[3], double sp[3])
+/*
+**  - - - - - - -
+**   e r a S x p
+**  - - - - - - -
+**
+**  Multiply a p-vector by a scalar.
+**
+**  Given:
+**     s      double        scalar
+**     p      double[3]     p-vector
+**
+**  Returned:
+**     sp     double[3]     s * p
+**
+**  Note:
+**     It is permissible for p and sp to be the same array.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   sp[0] = s * p[0];
+   sp[1] = s * p[1];
+   sp[2] = s * p[2];
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/sxpv.c b/ast/erfa/sxpv.c
new file mode 100644
index 0000000..6b497cf
--- /dev/null
+++ b/ast/erfa/sxpv.c
@@ -0,0 +1,94 @@
+#include "erfa.h"
+
+void eraSxpv(double s, double pv[2][3], double spv[2][3])
+/*
+**  - - - - - - - -
+**   e r a S x p v
+**  - - - - - - - -
+**
+**  Multiply a pv-vector by a scalar.
+**
+**  Given:
+**     s       double          scalar
+**     pv      double[2][3]    pv-vector
+**
+**  Returned:
+**     spv     double[2][3]    s * pv
+**
+**  Note:
+**     It is permissible for pv and spv to be the same array
+**
+**  Called:
+**     eraS2xpv     multiply pv-vector by two scalars
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraS2xpv(s, s, pv, spv);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/t_erfa_c.c b/ast/erfa/t_erfa_c.c
new file mode 100644
index 0000000..e32ce1e
--- /dev/null
+++ b/ast/erfa/t_erfa_c.c
@@ -0,0 +1,9742 @@
+#include <stdio.h>
+#include <erfa.h>
+
+static int verbose = 0;
+
+/*
+**  - - - - - - - - -
+**   t _ e r f a _ c
+**  - - - - - - - - -
+**
+**  Validate the ERFA C functions.
+**
+**  Each ERFA function is at least called and a usually quite basic test
+**  is performed.  Successful completion is signalled by a confirming
+**  message.  Failure of a given function or group of functions results
+**  in error messages.
+**
+**  All messages go to stdout.
+**
+**  This revision:  2016 July 11
+**
+*/
+
+static void viv(int ival, int ivalok,
+                const char *func, const char *test, int *status)
+/*
+**  - - - -
+**   v i v
+**  - - - -
+**
+**  Validate an integer result.
+**
+**  Internal function used by t_erfa_c program.
+**
+**  Given:
+**     ival     int          value computed by function under test
+**     ivalok   int          correct value
+**     func     char[]       name of function under test
+**     test     char[]       name of individual test
+**
+**  Given and returned:
+**     status   int          set to TRUE if test fails
+**
+**  This revision:  2013 August 7
+*/
+{
+   if (ival != ivalok) {
+      *status = 1;
+      printf("%s failed: %s want %d got %d\n",
+             func, test, ivalok, ival);
+   } else if (verbose) {
+      printf("%s passed: %s want %d got %d\n",
+                    func, test, ivalok, ival);
+   }
+
+}
+
+static void vvd(double val, double valok, double dval,
+                const char *func, const char *test, int *status)
+/*
+**  - - - -
+**   v v d
+**  - - - -
+**
+**  Validate a double result.
+**
+**  Internal function used by t_erfa_c program.
+**
+**  Given:
+**     val      double       value computed by function under test
+**     valok    double       expected value
+**     dval     double       maximum allowable error
+**     func     char[]       name of function under test
+**     test     char[]       name of individual test
+**
+**  Given and returned:
+**     status   int          set to TRUE if test fails
+**
+**  This revision:  2016 April 21
+*/
+{
+   double a, f;   /* absolute and fractional error */
+
+
+   a = val - valok;
+   if (a != 0.0 && fabs(a) > fabs(dval)) {
+      f = fabs(valok / a);
+      *status = 1;
+      printf("%s failed: %s want %.20g got %.20g (1/%.3g)\n",
+             func, test, valok, val, f);
+   } else if (verbose) {
+      printf("%s passed: %s want %.20g got %.20g\n",
+             func, test, valok, val);
+   }
+
+}
+
+static void t_a2af(int *status)
+/*
+**  - - - - - - -
+**   t _ a 2 a f
+**  - - - - - - -
+**
+**  Test eraA2af function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraA2af, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   int idmsf[4];
+   char s;
+
+
+   eraA2af(4, 2.345, &s, idmsf);
+
+   viv(s, '+', "eraA2af", "s", status);
+
+   viv(idmsf[0],  134, "eraA2af", "0", status);
+   viv(idmsf[1],   21, "eraA2af", "1", status);
+   viv(idmsf[2],   30, "eraA2af", "2", status);
+   viv(idmsf[3], 9706, "eraA2af", "3", status);
+
+}
+
+static void t_a2tf(int *status)
+/*
+**  - - - - - - -
+**   t _ a 2 t f
+**  - - - - - - -
+**
+**  Test eraA2tf function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraA2tf, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   int ihmsf[4];
+   char s;
+
+
+   eraA2tf(4, -3.01234, &s, ihmsf);
+
+   viv((int)s, '-', "eraA2tf", "s", status);
+
+   viv(ihmsf[0],   11, "eraA2tf", "0", status);
+   viv(ihmsf[1],   30, "eraA2tf", "1", status);
+   viv(ihmsf[2],   22, "eraA2tf", "2", status);
+   viv(ihmsf[3], 6484, "eraA2tf", "3", status);
+
+}
+
+static void t_ab(int *status)
+/*
+**  - - - - -
+**   t _ a b
+**  - - - - -
+**
+**  Test eraAb function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAb, vvd
+**
+**  This revision:  2013 October 1
+*/
+{
+   double pnat[3], v[3], s, bm1, ppr[3];
+
+
+   pnat[0] = -0.76321968546737951;
+   pnat[1] = -0.60869453983060384;
+   pnat[2] = -0.21676408580639883;
+   v[0] =  2.1044018893653786e-5;
+   v[1] = -8.9108923304429319e-5;
+   v[2] = -3.8633714797716569e-5;
+   s = 0.99980921395708788;
+   bm1 = 0.99999999506209258;
+
+   eraAb(pnat, v, s, bm1, ppr);
+
+   vvd(ppr[0], -0.7631631094219556269, 1e-12, "eraAb", "1", status);
+   vvd(ppr[1], -0.6087553082505590832, 1e-12, "eraAb", "2", status);
+   vvd(ppr[2], -0.2167926269368471279, 1e-12, "eraAb", "3", status);
+
+}
+
+static void t_af2a(int *status)
+/*
+**  - - - - - - -
+**   t _ a f 2 a
+**  - - - - - - -
+**
+**  Test eraAf2a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAf2a, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a;
+   int j;
+
+
+   j = eraAf2a('-', 45, 13, 27.2, &a);
+
+   vvd(a, -0.7893115794313644842, 1e-12, "eraAf2a", "a", status);
+   viv(j, 0, "eraAf2a", "j", status);
+
+}
+
+static void t_anp(int *status)
+/*
+**  - - - - - -
+**   t _ a n p
+**  - - - - - -
+**
+**  Test eraAnp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAnp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraAnp(-0.1), 6.183185307179586477, 1e-12, "eraAnp", "", status);
+}
+
+static void t_anpm(int *status)
+/*
+**  - - - - - - -
+**   t _ a n p m
+**  - - - - - - -
+**
+**  Test eraAnpm function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAnpm, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraAnpm(-4.0), 2.283185307179586477, 1e-12, "eraAnpm", "", status);
+}
+
+static void t_apcg(int *status)
+/*
+**  - - - - - - -
+**   t _ a p c g
+**  - - - - - - -
+**
+**  Test eraApcg function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApcg, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2, ebpv[2][3], ehp[3];
+   eraASTROM astrom;
+
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+   ebpv[0][0] =  0.901310875;
+   ebpv[0][1] = -0.417402664;
+   ebpv[0][2] = -0.180982288;
+   ebpv[1][0] =  0.00742727954;
+   ebpv[1][1] =  0.0140507459;
+   ebpv[1][2] =  0.00609045792;
+   ehp[0] =  0.903358544;
+   ehp[1] = -0.415395237;
+   ehp[2] = -0.180084014;
+
+   eraApcg(date1, date2, ebpv, ehp, &astrom);
+
+   vvd(astrom.pmt, 12.65133794027378508, 1e-11,
+                   "eraApcg", "pmt", status);
+   vvd(astrom.eb[0], 0.901310875, 1e-12,
+                     "eraApcg", "eb(1)", status);
+   vvd(astrom.eb[1], -0.417402664, 1e-12,
+                     "eraApcg", "eb(2)", status);
+   vvd(astrom.eb[2], -0.180982288, 1e-12,
+                     "eraApcg", "eb(3)", status);
+   vvd(astrom.eh[0], 0.8940025429324143045, 1e-12,
+                     "eraApcg", "eh(1)", status);
+   vvd(astrom.eh[1], -0.4110930268679817955, 1e-12,
+                     "eraApcg", "eh(2)", status);
+   vvd(astrom.eh[2], -0.1782189004872870264, 1e-12,
+                     "eraApcg", "eh(3)", status);
+   vvd(astrom.em, 1.010465295811013146, 1e-12,
+                  "eraApcg", "em", status);
+   vvd(astrom.v[0], 0.4289638897813379954e-4, 1e-16,
+                    "eraApcg", "v(1_", status);
+   vvd(astrom.v[1], 0.8115034021720941898e-4, 1e-16,
+                    "eraApcg", "v(2)", status);
+   vvd(astrom.v[2], 0.3517555123437237778e-4, 1e-16,
+                    "eraApcg", "v(3)", status);
+   vvd(astrom.bm1, 0.9999999951686013336, 1e-12,
+                   "eraApcg", "bm1", status);
+   vvd(astrom.bpn[0][0], 1.0, 0.0,
+                         "eraApcg", "bpn(1,1)", status);
+   vvd(astrom.bpn[1][0], 0.0, 0.0,
+                         "eraApcg", "bpn(2,1)", status);
+   vvd(astrom.bpn[2][0], 0.0, 0.0,
+                         "eraApcg", "bpn(3,1)", status);
+   vvd(astrom.bpn[0][1], 0.0, 0.0,
+                         "eraApcg", "bpn(1,2)", status);
+   vvd(astrom.bpn[1][1], 1.0, 0.0,
+                         "eraApcg", "bpn(2,2)", status);
+   vvd(astrom.bpn[2][1], 0.0, 0.0,
+                         "eraApcg", "bpn(3,2)", status);
+   vvd(astrom.bpn[0][2], 0.0, 0.0,
+                         "eraApcg", "bpn(1,3)", status);
+   vvd(astrom.bpn[1][2], 0.0, 0.0,
+                         "eraApcg", "bpn(2,3)", status);
+   vvd(astrom.bpn[2][2], 1.0, 0.0,
+                         "eraApcg", "bpn(3,3)", status);
+
+}
+
+static void t_apcg13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a p c g 1 3
+**  - - - - - - - - -
+**
+**  Test eraApcg13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApcg13, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2;
+   eraASTROM astrom;
+
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+
+   eraApcg13(date1, date2, &astrom);
+
+   vvd(astrom.pmt, 12.65133794027378508, 1e-11,
+                   "eraApcg13", "pmt", status);
+   vvd(astrom.eb[0], 0.9013108747340644755, 1e-12,
+                   "eraApcg13", "eb(1)", status);
+   vvd(astrom.eb[1], -0.4174026640406119957, 1e-12,
+                   "eraApcg13", "eb(2)", status);
+   vvd(astrom.eb[2], -0.1809822877867817771, 1e-12,
+                   "eraApcg13", "eb(3)", status);
+   vvd(astrom.eh[0], 0.8940025429255499549, 1e-12,
+                   "eraApcg13", "eh(1)", status);
+   vvd(astrom.eh[1], -0.4110930268331896318, 1e-12,
+                   "eraApcg13", "eh(2)", status);
+   vvd(astrom.eh[2], -0.1782189006019749850, 1e-12,
+                   "eraApcg13", "eh(3)", status);
+   vvd(astrom.em, 1.010465295964664178, 1e-12,
+                   "eraApcg13", "em", status);
+   vvd(astrom.v[0], 0.4289638897157027528e-4, 1e-16,
+                   "eraApcg13", "v(1)", status);
+   vvd(astrom.v[1], 0.8115034002544663526e-4, 1e-16,
+                   "eraApcg13", "v(2)", status);
+   vvd(astrom.v[2], 0.3517555122593144633e-4, 1e-16,
+                   "eraApcg13", "v(3)", status);
+   vvd(astrom.bm1, 0.9999999951686013498, 1e-12,
+                   "eraApcg13", "bm1", status);
+   vvd(astrom.bpn[0][0], 1.0, 0.0,
+                         "eraApcg13", "bpn(1,1)", status);
+   vvd(astrom.bpn[1][0], 0.0, 0.0,
+                         "eraApcg13", "bpn(2,1)", status);
+   vvd(astrom.bpn[2][0], 0.0, 0.0,
+                         "eraApcg13", "bpn(3,1)", status);
+   vvd(astrom.bpn[0][1], 0.0, 0.0,
+                         "eraApcg13", "bpn(1,2)", status);
+   vvd(astrom.bpn[1][1], 1.0, 0.0,
+                         "eraApcg13", "bpn(2,2)", status);
+   vvd(astrom.bpn[2][1], 0.0, 0.0,
+                         "eraApcg13", "bpn(3,2)", status);
+   vvd(astrom.bpn[0][2], 0.0, 0.0,
+                         "eraApcg13", "bpn(1,3)", status);
+   vvd(astrom.bpn[1][2], 0.0, 0.0,
+                         "eraApcg13", "bpn(2,3)", status);
+   vvd(astrom.bpn[2][2], 1.0, 0.0,
+                         "eraApcg13", "bpn(3,3)", status);
+
+}
+
+static void t_apci(int *status)
+/*
+**  - - - - - - -
+**   t _ a p c i
+**  - - - - - - -
+**
+**  Test eraApci function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApci, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2, ebpv[2][3], ehp[3], x, y, s;
+   eraASTROM astrom;
+
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+   ebpv[0][0] =  0.901310875;
+   ebpv[0][1] = -0.417402664;
+   ebpv[0][2] = -0.180982288;
+   ebpv[1][0] =  0.00742727954;
+   ebpv[1][1] =  0.0140507459;
+   ebpv[1][2] =  0.00609045792;
+   ehp[0] =  0.903358544;
+   ehp[1] = -0.415395237;
+   ehp[2] = -0.180084014;
+   x =  0.0013122272;
+   y = -2.92808623e-5;
+   s =  3.05749468e-8;
+
+   eraApci(date1, date2, ebpv, ehp, x, y, s, &astrom);
+
+   vvd(astrom.pmt, 12.65133794027378508, 1e-11,
+                   "eraApci", "pmt", status);
+   vvd(astrom.eb[0], 0.901310875, 1e-12,
+                     "eraApci", "eb(1)", status);
+   vvd(astrom.eb[1], -0.417402664, 1e-12,
+                     "eraApci", "eb(2)", status);
+   vvd(astrom.eb[2], -0.180982288, 1e-12,
+                     "eraApci", "eb(3)", status);
+   vvd(astrom.eh[0], 0.8940025429324143045, 1e-12,
+                     "eraApci", "eh(1)", status);
+   vvd(astrom.eh[1], -0.4110930268679817955, 1e-12,
+                     "eraApci", "eh(2)", status);
+   vvd(astrom.eh[2], -0.1782189004872870264, 1e-12,
+                     "eraApci", "eh(3)", status);
+   vvd(astrom.em, 1.010465295811013146, 1e-12,
+                  "eraApci", "em", status);
+   vvd(astrom.v[0], 0.4289638897813379954e-4, 1e-16,
+                    "eraApci", "v(1)", status);
+   vvd(astrom.v[1], 0.8115034021720941898e-4, 1e-16,
+                    "eraApci", "v(2)", status);
+   vvd(astrom.v[2], 0.3517555123437237778e-4, 1e-16,
+                    "eraApci", "v(3)", status);
+   vvd(astrom.bm1, 0.9999999951686013336, 1e-12,
+                   "eraApci", "bm1", status);
+   vvd(astrom.bpn[0][0], 0.9999991390295159156, 1e-12,
+                         "eraApci", "bpn(1,1)", status);
+   vvd(astrom.bpn[1][0], 0.4978650072505016932e-7, 1e-12,
+                         "eraApci", "bpn(2,1)", status);
+   vvd(astrom.bpn[2][0], 0.1312227200000000000e-2, 1e-12,
+                         "eraApci", "bpn(3,1)", status);
+   vvd(astrom.bpn[0][1], -0.1136336653771609630e-7, 1e-12,
+                         "eraApci", "bpn(1,2)", status);
+   vvd(astrom.bpn[1][1], 0.9999999995713154868, 1e-12,
+                         "eraApci", "bpn(2,2)", status);
+   vvd(astrom.bpn[2][1], -0.2928086230000000000e-4, 1e-12,
+                         "eraApci", "bpn(3,2)", status);
+   vvd(astrom.bpn[0][2], -0.1312227200895260194e-2, 1e-12,
+                         "eraApci", "bpn(1,3)", status);
+   vvd(astrom.bpn[1][2], 0.2928082217872315680e-4, 1e-12,
+                         "eraApci", "bpn(2,3)", status);
+   vvd(astrom.bpn[2][2], 0.9999991386008323373, 1e-12,
+                         "eraApci", "bpn(3,3)", status);
+
+}
+
+static void t_apci13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a p c i 1 3
+**  - - - - - - - - -
+**
+**  Test eraApci13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApci13, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2, eo;
+   eraASTROM astrom;
+
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+
+   eraApci13(date1, date2, &astrom, &eo);
+
+   vvd(astrom.pmt, 12.65133794027378508, 1e-11,
+                   "eraApci13", "pmt", status);
+   vvd(astrom.eb[0], 0.9013108747340644755, 1e-12,
+                     "eraApci13", "eb(1)", status);
+   vvd(astrom.eb[1], -0.4174026640406119957, 1e-12,
+                     "eraApci13", "eb(2)", status);
+   vvd(astrom.eb[2], -0.1809822877867817771, 1e-12,
+                     "eraApci13", "eb(3)", status);
+   vvd(astrom.eh[0], 0.8940025429255499549, 1e-12,
+                     "eraApci13", "eh(1)", status);
+   vvd(astrom.eh[1], -0.4110930268331896318, 1e-12,
+                     "eraApci13", "eh(2)", status);
+   vvd(astrom.eh[2], -0.1782189006019749850, 1e-12,
+                     "eraApci13", "eh(3)", status);
+   vvd(astrom.em, 1.010465295964664178, 1e-12,
+                  "eraApci13", "em", status);
+   vvd(astrom.v[0], 0.4289638897157027528e-4, 1e-16,
+                    "eraApci13", "v(1)", status);
+   vvd(astrom.v[1], 0.8115034002544663526e-4, 1e-16,
+                    "eraApci13", "v(2)", status);
+   vvd(astrom.v[2], 0.3517555122593144633e-4, 1e-16,
+                    "eraApci13", "v(3)", status);
+   vvd(astrom.bm1, 0.9999999951686013498, 1e-12,
+                   "eraApci13", "bm1", status);
+   vvd(astrom.bpn[0][0], 0.9999992060376761710, 1e-12,
+                         "eraApci13", "bpn(1,1)", status);
+   vvd(astrom.bpn[1][0], 0.4124244860106037157e-7, 1e-12,
+                         "eraApci13", "bpn(2,1)", status);
+   vvd(astrom.bpn[2][0], 0.1260128571051709670e-2, 1e-12,
+                         "eraApci13", "bpn(3,1)", status);
+   vvd(astrom.bpn[0][1], -0.1282291987222130690e-7, 1e-12,
+                         "eraApci13", "bpn(1,2)", status);
+   vvd(astrom.bpn[1][1], 0.9999999997456835325, 1e-12,
+                         "eraApci13", "bpn(2,2)", status);
+   vvd(astrom.bpn[2][1], -0.2255288829420524935e-4, 1e-12,
+                         "eraApci13", "bpn(3,2)", status);
+   vvd(astrom.bpn[0][2], -0.1260128571661374559e-2, 1e-12,
+                         "eraApci13", "bpn(1,3)", status);
+   vvd(astrom.bpn[1][2], 0.2255285422953395494e-4, 1e-12,
+                         "eraApci13", "bpn(2,3)", status);
+   vvd(astrom.bpn[2][2], 0.9999992057833604343, 1e-12,
+                         "eraApci13", "bpn(3,3)", status);
+   vvd(eo, -0.2900618712657375647e-2, 1e-12,
+           "eraApci13", "eo", status);
+
+}
+
+static void t_apco(int *status)
+/*
+**  - - - - - - -
+**   t _ a p c o
+**  - - - - - - -
+**
+**  Test eraApco function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApco, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2, ebpv[2][3], ehp[3], x, y, s,
+          theta, elong, phi, hm, xp, yp, sp, refa, refb;
+   eraASTROM astrom;
+
+
+   date1 = 2456384.5;
+   date2 = 0.970031644;
+   ebpv[0][0] = -0.974170438;
+   ebpv[0][1] = -0.211520082;
+   ebpv[0][2] = -0.0917583024;
+   ebpv[1][0] = 0.00364365824;
+   ebpv[1][1] = -0.0154287319;
+   ebpv[1][2] = -0.00668922024;
+   ehp[0] = -0.973458265;
+   ehp[1] = -0.209215307;
+   ehp[2] = -0.0906996477;
+   x = 0.0013122272;
+   y = -2.92808623e-5;
+   s = 3.05749468e-8;
+   theta = 3.14540971;
+   elong = -0.527800806;
+   phi = -1.2345856;
+   hm = 2738.0;
+   xp = 2.47230737e-7;
+   yp = 1.82640464e-6;
+   sp = -3.01974337e-11;
+   refa = 0.000201418779;
+   refb = -2.36140831e-7;
+
+   eraApco(date1, date2, ebpv, ehp, x, y, s,
+           theta, elong, phi, hm, xp, yp, sp,
+           refa, refb, &astrom);
+
+   vvd(astrom.pmt, 13.25248468622587269, 1e-11,
+                   "eraApco", "pmt", status);
+   vvd(astrom.eb[0], -0.9741827110630897003, 1e-12,
+                     "eraApco", "eb(1)", status);
+   vvd(astrom.eb[1], -0.2115130190135014340, 1e-12,
+                     "eraApco", "eb(2)", status);
+   vvd(astrom.eb[2], -0.09179840186968295686, 1e-12,
+                     "eraApco", "eb(3)", status);
+   vvd(astrom.eh[0], -0.9736425571689670428, 1e-12,
+                     "eraApco", "eh(1)", status);
+   vvd(astrom.eh[1], -0.2092452125848862201, 1e-12,
+                     "eraApco", "eh(2)", status);
+   vvd(astrom.eh[2], -0.09075578152261439954, 1e-12,
+                     "eraApco", "eh(3)", status);
+   vvd(astrom.em, 0.9998233241710617934, 1e-12,
+                  "eraApco", "em", status);
+   vvd(astrom.v[0], 0.2078704985147609823e-4, 1e-16,
+                    "eraApco", "v(1)", status);
+   vvd(astrom.v[1], -0.8955360074407552709e-4, 1e-16,
+                    "eraApco", "v(2)", status);
+   vvd(astrom.v[2], -0.3863338980073114703e-4, 1e-16,
+                    "eraApco", "v(3)", status);
+   vvd(astrom.bm1, 0.9999999950277561600, 1e-12,
+                   "eraApco", "bm1", status);
+   vvd(astrom.bpn[0][0], 0.9999991390295159156, 1e-12,
+                         "eraApco", "bpn(1,1)", status);
+   vvd(astrom.bpn[1][0], 0.4978650072505016932e-7, 1e-12,
+                         "eraApco", "bpn(2,1)", status);
+   vvd(astrom.bpn[2][0], 0.1312227200000000000e-2, 1e-12,
+                         "eraApco", "bpn(3,1)", status);
+   vvd(astrom.bpn[0][1], -0.1136336653771609630e-7, 1e-12,
+                         "eraApco", "bpn(1,2)", status);
+   vvd(astrom.bpn[1][1], 0.9999999995713154868, 1e-12,
+                         "eraApco", "bpn(2,2)", status);
+   vvd(astrom.bpn[2][1], -0.2928086230000000000e-4, 1e-12,
+                         "eraApco", "bpn(3,2)", status);
+   vvd(astrom.bpn[0][2], -0.1312227200895260194e-2, 1e-12,
+                         "eraApco", "bpn(1,3)", status);
+   vvd(astrom.bpn[1][2], 0.2928082217872315680e-4, 1e-12,
+                         "eraApco", "bpn(2,3)", status);
+   vvd(astrom.bpn[2][2], 0.9999991386008323373, 1e-12,
+                         "eraApco", "bpn(3,3)", status);
+   vvd(astrom.along, -0.5278008060301974337, 1e-12,
+                     "eraApco", "along", status);
+   vvd(astrom.xpl, 0.1133427418174939329e-5, 1e-17,
+                   "eraApco", "xpl", status);
+   vvd(astrom.ypl, 0.1453347595745898629e-5, 1e-17,
+                   "eraApco", "ypl", status);
+   vvd(astrom.sphi, -0.9440115679003211329, 1e-12,
+                    "eraApco", "sphi", status);
+   vvd(astrom.cphi, 0.3299123514971474711, 1e-12,
+                    "eraApco", "cphi", status);
+   vvd(astrom.diurab, 0, 0,
+                      "eraApco", "diurab", status);
+   vvd(astrom.eral, 2.617608903969802566, 1e-12,
+                    "eraApco", "eral", status);
+   vvd(astrom.refa, 0.2014187790000000000e-3, 1e-15,
+                    "eraApco", "refa", status);
+   vvd(astrom.refb, -0.2361408310000000000e-6, 1e-18,
+                    "eraApco", "refb", status);
+
+}
+
+static void t_apco13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a p c o 1 3
+**  - - - - - - - - -
+**
+**  Test eraApco13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApco13, vvd, viv
+**
+**  This revision:  2013 October 4
+*/
+{
+   double utc1, utc2, dut1, elong, phi, hm, xp, yp,
+          phpa, tc, rh, wl, eo;
+   eraASTROM astrom;
+   int j;
+
+
+   utc1 = 2456384.5;
+   utc2 = 0.969254051;
+   dut1 = 0.1550675;
+   elong = -0.527800806;
+   phi = -1.2345856;
+   hm = 2738.0;
+   xp = 2.47230737e-7;
+   yp = 1.82640464e-6;
+   phpa = 731.0;
+   tc = 12.8;
+   rh = 0.59;
+   wl = 0.55;
+
+   j = eraApco13(utc1, utc2, dut1, elong, phi, hm, xp, yp,
+                 phpa, tc, rh, wl, &astrom, &eo);
+
+   vvd(astrom.pmt, 13.25248468622475727, 1e-11,
+                   "eraApco13", "pmt", status);
+   vvd(astrom.eb[0], -0.9741827107321449445, 1e-12,
+                   "eraApco13", "eb(1)", status);
+   vvd(astrom.eb[1], -0.2115130190489386190, 1e-12,
+                     "eraApco13", "eb(2)", status);
+   vvd(astrom.eb[2], -0.09179840189515518726, 1e-12,
+                     "eraApco13", "eb(3)", status);
+   vvd(astrom.eh[0], -0.9736425572586866640, 1e-12,
+                     "eraApco13", "eh(1)", status);
+   vvd(astrom.eh[1], -0.2092452121602867431, 1e-12,
+                     "eraApco13", "eh(2)", status);
+   vvd(astrom.eh[2], -0.09075578153903832650, 1e-12,
+                     "eraApco13", "eh(3)", status);
+   vvd(astrom.em, 0.9998233240914558422, 1e-12,
+                  "eraApco13", "em", status);
+   vvd(astrom.v[0], 0.2078704986751370303e-4, 1e-16,
+                    "eraApco13", "v(1)", status);
+   vvd(astrom.v[1], -0.8955360100494469232e-4, 1e-16,
+                    "eraApco13", "v(2)", status);
+   vvd(astrom.v[2], -0.3863338978840051024e-4, 1e-16,
+                    "eraApco13", "v(3)", status);
+   vvd(astrom.bm1, 0.9999999950277561368, 1e-12,
+                   "eraApco13", "bm1", status);
+   vvd(astrom.bpn[0][0], 0.9999991390295147999, 1e-12,
+                         "eraApco13", "bpn(1,1)", status);
+   vvd(astrom.bpn[1][0], 0.4978650075315529277e-7, 1e-12,
+                         "eraApco13", "bpn(2,1)", status);
+   vvd(astrom.bpn[2][0], 0.001312227200850293372, 1e-12,
+                         "eraApco13", "bpn(3,1)", status);
+   vvd(astrom.bpn[0][1], -0.1136336652812486604e-7, 1e-12,
+                         "eraApco13", "bpn(1,2)", status);
+   vvd(astrom.bpn[1][1], 0.9999999995713154865, 1e-12,
+                         "eraApco13", "bpn(2,2)", status);
+   vvd(astrom.bpn[2][1], -0.2928086230975367296e-4, 1e-12,
+                         "eraApco13", "bpn(3,2)", status);
+   vvd(astrom.bpn[0][2], -0.001312227201745553566, 1e-12,
+                         "eraApco13", "bpn(1,3)", status);
+   vvd(astrom.bpn[1][2], 0.2928082218847679162e-4, 1e-12,
+                         "eraApco13", "bpn(2,3)", status);
+   vvd(astrom.bpn[2][2], 0.9999991386008312212, 1e-12,
+                         "eraApco13", "bpn(3,3)", status);
+   vvd(astrom.along, -0.5278008060301974337, 1e-12,
+                     "eraApco13", "along", status);
+   vvd(astrom.xpl, 0.1133427418174939329e-5, 1e-17,
+                   "eraApco13", "xpl", status);
+   vvd(astrom.ypl, 0.1453347595745898629e-5, 1e-17,
+                   "eraApco13", "ypl", status);
+   vvd(astrom.sphi, -0.9440115679003211329, 1e-12,
+                    "eraApco13", "sphi", status);
+   vvd(astrom.cphi, 0.3299123514971474711, 1e-12,
+                    "eraApco13", "cphi", status);
+   vvd(astrom.diurab, 0, 0,
+                      "eraApco13", "diurab", status);
+   vvd(astrom.eral, 2.617608909189066140, 1e-12,
+                    "eraApco13", "eral", status);
+   vvd(astrom.refa, 0.2014187785940396921e-3, 1e-15,
+                    "eraApco13", "refa", status);
+   vvd(astrom.refb, -0.2361408314943696227e-6, 1e-18,
+                    "eraApco13", "refb", status);
+   vvd(eo, -0.003020548354802412839, 1e-14,
+           "eraApco13", "eo", status);
+   viv(j, 0, "eraApco13", "j", status);
+
+}
+
+static void t_apcs(int *status)
+/*
+**  - - - - - - -
+**   t _ a p c s
+**  - - - - - - -
+**
+**  Test eraApcs function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApcs, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2, pv[2][3], ebpv[2][3], ehp[3];
+   eraASTROM astrom;
+
+
+   date1 = 2456384.5;
+   date2 = 0.970031644;
+   pv[0][0] = -1836024.09;
+   pv[0][1] = 1056607.72;
+   pv[0][2] = -5998795.26;
+   pv[1][0] = -77.0361767;
+   pv[1][1] = -133.310856;
+   pv[1][2] = 0.0971855934;
+   ebpv[0][0] = -0.974170438;
+   ebpv[0][1] = -0.211520082;
+   ebpv[0][2] = -0.0917583024;
+   ebpv[1][0] = 0.00364365824;
+   ebpv[1][1] = -0.0154287319;
+   ebpv[1][2] = -0.00668922024;
+   ehp[0] = -0.973458265;
+   ehp[1] = -0.209215307;
+   ehp[2] = -0.0906996477;
+
+   eraApcs(date1, date2, pv, ebpv, ehp, &astrom);
+
+   vvd(astrom.pmt, 13.25248468622587269, 1e-11,
+                   "eraApcs", "pmt", status);
+   vvd(astrom.eb[0], -0.9741827110630456169, 1e-12,
+                     "eraApcs", "eb(1)", status);
+   vvd(astrom.eb[1], -0.2115130190136085494, 1e-12,
+                     "eraApcs", "eb(2)", status);
+   vvd(astrom.eb[2], -0.09179840186973175487, 1e-12,
+                     "eraApcs", "eb(3)", status);
+   vvd(astrom.eh[0], -0.9736425571689386099, 1e-12,
+                     "eraApcs", "eh(1)", status);
+   vvd(astrom.eh[1], -0.2092452125849967195, 1e-12,
+                     "eraApcs", "eh(2)", status);
+   vvd(astrom.eh[2], -0.09075578152266466572, 1e-12,
+                     "eraApcs", "eh(3)", status);
+   vvd(astrom.em, 0.9998233241710457140, 1e-12,
+                  "eraApcs", "em", status);
+   vvd(astrom.v[0], 0.2078704985513566571e-4, 1e-16,
+                    "eraApcs", "v(1)", status);
+   vvd(astrom.v[1], -0.8955360074245006073e-4, 1e-16,
+                    "eraApcs", "v(2)", status);
+   vvd(astrom.v[2], -0.3863338980073572719e-4, 1e-16,
+                    "eraApcs", "v(3)", status);
+   vvd(astrom.bm1, 0.9999999950277561601, 1e-12,
+                   "eraApcs", "bm1", status);
+   vvd(astrom.bpn[0][0], 1, 0,
+                         "eraApcs", "bpn(1,1)", status);
+   vvd(astrom.bpn[1][0], 0, 0,
+                         "eraApcs", "bpn(2,1)", status);
+   vvd(astrom.bpn[2][0], 0, 0,
+                         "eraApcs", "bpn(3,1)", status);
+   vvd(astrom.bpn[0][1], 0, 0,
+                         "eraApcs", "bpn(1,2)", status);
+   vvd(astrom.bpn[1][1], 1, 0,
+                         "eraApcs", "bpn(2,2)", status);
+   vvd(astrom.bpn[2][1], 0, 0,
+                         "eraApcs", "bpn(3,2)", status);
+   vvd(astrom.bpn[0][2], 0, 0,
+                         "eraApcs", "bpn(1,3)", status);
+   vvd(astrom.bpn[1][2], 0, 0,
+                         "eraApcs", "bpn(2,3)", status);
+   vvd(astrom.bpn[2][2], 1, 0,
+                         "eraApcs", "bpn(3,3)", status);
+
+}
+
+static void t_apcs13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a p c s 1 3
+**  - - - - - - - - -
+**
+**  Test eraApcs13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApcs13, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2, pv[2][3];
+   eraASTROM astrom;
+
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+   pv[0][0] = -6241497.16;
+   pv[0][1] = 401346.896;
+   pv[0][2] = -1251136.04;
+   pv[1][0] = -29.264597;
+   pv[1][1] = -455.021831;
+   pv[1][2] = 0.0266151194;
+
+   eraApcs13(date1, date2, pv, &astrom);
+
+   vvd(astrom.pmt, 12.65133794027378508, 1e-11,
+                   "eraApcs13", "pmt", status);
+   vvd(astrom.eb[0], 0.9012691529023298391, 1e-12,
+                     "eraApcs13", "eb(1)", status);
+   vvd(astrom.eb[1], -0.4173999812023068781, 1e-12,
+                     "eraApcs13", "eb(2)", status);
+   vvd(astrom.eb[2], -0.1809906511146821008, 1e-12,
+                     "eraApcs13", "eb(3)", status);
+   vvd(astrom.eh[0], 0.8939939101759726824, 1e-12,
+                     "eraApcs13", "eh(1)", status);
+   vvd(astrom.eh[1], -0.4111053891734599955, 1e-12,
+                     "eraApcs13", "eh(2)", status);
+   vvd(astrom.eh[2], -0.1782336880637689334, 1e-12,
+                     "eraApcs13", "eh(3)", status);
+   vvd(astrom.em, 1.010428384373318379, 1e-12,
+                  "eraApcs13", "em", status);
+   vvd(astrom.v[0], 0.4279877278327626511e-4, 1e-16,
+                    "eraApcs13", "v(1)", status);
+   vvd(astrom.v[1], 0.7963255057040027770e-4, 1e-16,
+                    "eraApcs13", "v(2)", status);
+   vvd(astrom.v[2], 0.3517564000441374759e-4, 1e-16,
+                    "eraApcs13", "v(3)", status);
+   vvd(astrom.bm1, 0.9999999952947981330, 1e-12,
+                   "eraApcs13", "bm1", status);
+   vvd(astrom.bpn[0][0], 1, 0,
+                         "eraApcs13", "bpn(1,1)", status);
+   vvd(astrom.bpn[1][0], 0, 0,
+                         "eraApcs13", "bpn(2,1)", status);
+   vvd(astrom.bpn[2][0], 0, 0,
+                         "eraApcs13", "bpn(3,1)", status);
+   vvd(astrom.bpn[0][1], 0, 0,
+                         "eraApcs13", "bpn(1,2)", status);
+   vvd(astrom.bpn[1][1], 1, 0,
+                         "eraApcs13", "bpn(2,2)", status);
+   vvd(astrom.bpn[2][1], 0, 0,
+                         "eraApcs13", "bpn(3,2)", status);
+   vvd(astrom.bpn[0][2], 0, 0,
+                         "eraApcs13", "bpn(1,3)", status);
+   vvd(astrom.bpn[1][2], 0, 0,
+                         "eraApcs13", "bpn(2,3)", status);
+   vvd(astrom.bpn[2][2], 1, 0,
+                         "eraApcs13", "bpn(3,3)", status);
+
+}
+
+static void t_aper(int *status)
+/*
+**  - - - - - - -
+**   t _ a p e r
+**  - - - - - - -
+*
+**  Test eraAper function.
+*
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+*
+**  Called:  eraAper, vvd
+*
+**  This revision:  2013 October 3
+*/
+{
+   double theta;
+   eraASTROM astrom;
+
+
+   astrom.along = 1.234;
+   theta = 5.678;
+
+   eraAper(theta, &astrom);
+
+   vvd(astrom.eral, 6.912000000000000000, 1e-12,
+                    "eraAper", "pmt", status);
+
+}
+
+static void t_aper13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a p e r 1 3
+**  - - - - - - - - -
+**
+**  Test eraAper13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAper13, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double ut11, ut12;
+   eraASTROM astrom;
+
+
+   astrom.along = 1.234;
+   ut11 = 2456165.5;
+   ut12 = 0.401182685;
+
+   eraAper13(ut11, ut12, &astrom);
+
+   vvd(astrom.eral, 3.316236661789694933, 1e-12,
+                    "eraAper13", "pmt", status);
+
+}
+
+static void t_apio(int *status)
+/*
+**  - - - - - - -
+**   t _ a p i o
+**  - - - - - - -
+**
+**  Test eraApio function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApio, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double sp, theta, elong, phi, hm, xp, yp, refa, refb;
+   eraASTROM astrom;
+
+
+   sp = -3.01974337e-11;
+   theta = 3.14540971;
+   elong = -0.527800806;
+   phi = -1.2345856;
+   hm = 2738.0;
+   xp = 2.47230737e-7;
+   yp = 1.82640464e-6;
+   refa = 0.000201418779;
+   refb = -2.36140831e-7;
+
+   eraApio(sp, theta, elong, phi, hm, xp, yp, refa, refb, &astrom);
+
+   vvd(astrom.along, -0.5278008060301974337, 1e-12,
+                     "eraApio", "along", status);
+   vvd(astrom.xpl, 0.1133427418174939329e-5, 1e-17,
+                   "eraApio", "xpl", status);
+   vvd(astrom.ypl, 0.1453347595745898629e-5, 1e-17,
+                   "eraApio", "ypl", status);
+   vvd(astrom.sphi, -0.9440115679003211329, 1e-12,
+                    "eraApio", "sphi", status);
+   vvd(astrom.cphi, 0.3299123514971474711, 1e-12,
+                    "eraApio", "cphi", status);
+   vvd(astrom.diurab, 0.5135843661699913529e-6, 1e-12,
+                      "eraApio", "diurab", status);
+   vvd(astrom.eral, 2.617608903969802566, 1e-12,
+                    "eraApio", "eral", status);
+   vvd(astrom.refa, 0.2014187790000000000e-3, 1e-15,
+                    "eraApio", "refa", status);
+   vvd(astrom.refb, -0.2361408310000000000e-6, 1e-18,
+                    "eraApio", "refb", status);
+
+}
+
+static void t_apio13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a p i o 1 3
+**  - - - - - - - - -
+**
+**  Test eraApio13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApio13, vvd, viv
+**
+**  This revision:  2013 October 4
+*/
+{
+   double utc1, utc2, dut1, elong, phi, hm, xp, yp, phpa, tc, rh, wl;
+   int j;
+   eraASTROM astrom;
+
+
+   utc1 = 2456384.5;
+   utc2 = 0.969254051;
+   dut1 = 0.1550675;
+   elong = -0.527800806;
+   phi = -1.2345856;
+   hm = 2738.0;
+   xp = 2.47230737e-7;
+   yp = 1.82640464e-6;
+   phpa = 731.0;
+   tc = 12.8;
+   rh = 0.59;
+   wl = 0.55;
+
+   j = eraApio13(utc1, utc2, dut1, elong, phi, hm, xp, yp,
+                 phpa, tc, rh, wl, &astrom);
+
+   vvd(astrom.along, -0.5278008060301974337, 1e-12,
+                     "eraApio13", "along", status);
+   vvd(astrom.xpl, 0.1133427418174939329e-5, 1e-17,
+                   "eraApio13", "xpl", status);
+   vvd(astrom.ypl, 0.1453347595745898629e-5, 1e-17,
+                   "eraApio13", "ypl", status);
+   vvd(astrom.sphi, -0.9440115679003211329, 1e-12,
+                    "eraApio13", "sphi", status);
+   vvd(astrom.cphi, 0.3299123514971474711, 1e-12,
+                    "eraApio13", "cphi", status);
+   vvd(astrom.diurab, 0.5135843661699913529e-6, 1e-12,
+                      "eraApio13", "diurab", status);
+   vvd(astrom.eral, 2.617608909189066140, 1e-12,
+                    "eraApio13", "eral", status);
+   vvd(astrom.refa, 0.2014187785940396921e-3, 1e-15,
+                    "eraApio13", "refa", status);
+   vvd(astrom.refb, -0.2361408314943696227e-6, 1e-18,
+                    "eraApio13", "refb", status);
+   viv(j, 0, "eraApio13", "j", status);
+
+}
+
+static void t_atci13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a t c i 1 3
+**  - - - - - - - - -
+**
+**  Test eraAtci13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAtci13, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double rc, dc, pr, pd, px, rv, date1, date2, ri, di, eo;
+
+
+   rc = 2.71;
+   dc = 0.174;
+   pr = 1e-5;
+   pd = 5e-6;
+   px = 0.1;
+   rv = 55.0;
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+
+   eraAtci13(rc, dc, pr, pd, px, rv, date1, date2, &ri, &di, &eo);
+
+   vvd(ri, 2.710121572969038991, 1e-12,
+           "eraAtci13", "ri", status);
+   vvd(di, 0.1729371367218230438, 1e-12,
+           "eraAtci13", "di", status);
+   vvd(eo, -0.002900618712657375647, 1e-14,
+           "eraAtci13", "eo", status);
+
+}
+
+static void t_atciq(int *status)
+/*
+**  - - - - - - - -
+**   t _ a t c i q
+**  - - - - - - - -
+**
+**  Test eraAtciq function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApci13, eraAtciq, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2, eo, rc, dc, pr, pd, px, rv, ri, di;
+   eraASTROM astrom;
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+   eraApci13(date1, date2, &astrom, &eo);
+   rc = 2.71;
+   dc = 0.174;
+   pr = 1e-5;
+   pd = 5e-6;
+   px = 0.1;
+   rv = 55.0;
+
+   eraAtciq(rc, dc, pr, pd, px, rv, &astrom, &ri, &di);
+
+   vvd(ri, 2.710121572969038991, 1e-12, "eraAtciq", "ri", status);
+   vvd(di, 0.1729371367218230438, 1e-12, "eraAtciq", "di", status);
+
+}
+
+static void t_atciqn(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a t c i q n
+**  - - - - - - - - -
+**
+**  Test eraAtciqn function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApci13, eraAtciqn, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   eraLDBODY b[3];
+   double date1, date2, eo, rc, dc, pr, pd, px, rv, ri, di;
+   eraASTROM astrom;
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+   eraApci13(date1, date2, &astrom, &eo);
+   rc = 2.71;
+   dc = 0.174;
+   pr = 1e-5;
+   pd = 5e-6;
+   px = 0.1;
+   rv = 55.0;
+   b[0].bm = 0.00028574;
+   b[0].dl = 3e-10;
+   b[0].pv[0][0] = -7.81014427;
+   b[0].pv[0][1] = -5.60956681;
+   b[0].pv[0][2] = -1.98079819;
+   b[0].pv[1][0] =  0.0030723249;
+   b[0].pv[1][1] = -0.00406995477;
+   b[0].pv[1][2] = -0.00181335842;
+   b[1].bm = 0.00095435;
+   b[1].dl = 3e-9;
+   b[1].pv[0][0] =  0.738098796;
+   b[1].pv[0][1] =  4.63658692;
+   b[1].pv[0][2] =  1.9693136;
+   b[1].pv[1][0] = -0.00755816922;
+   b[1].pv[1][1] =  0.00126913722;
+   b[1].pv[1][2] =  0.000727999001;
+   b[2].bm = 1.0;
+   b[2].dl = 6e-6;
+   b[2].pv[0][0] = -0.000712174377;
+   b[2].pv[0][1] = -0.00230478303;
+   b[2].pv[0][2] = -0.00105865966;
+   b[2].pv[1][0] =  6.29235213e-6;
+   b[2].pv[1][1] = -3.30888387e-7;
+   b[2].pv[1][2] = -2.96486623e-7;
+
+   eraAtciqn ( rc, dc, pr, pd, px, rv, &astrom, 3, b, &ri, &di);
+
+   vvd(ri, 2.710122008105325582, 1e-12, "eraAtciqn", "ri", status);
+   vvd(di, 0.1729371916491459122, 1e-12, "eraAtciqn", "di", status);
+
+}
+
+static void t_atciqz(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a t c i q z
+**  - - - - - - - - -
+**
+**  Test eraAtciqz function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApci13, eraAtciqz, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2, eo, rc, dc, ri, di;
+   eraASTROM astrom;
+
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+   eraApci13(date1, date2, &astrom, &eo);
+   rc = 2.71;
+   dc = 0.174;
+
+   eraAtciqz(rc, dc, &astrom, &ri, &di);
+
+   vvd(ri, 2.709994899247599271, 1e-12, "eraAtciqz", "ri", status);
+   vvd(di, 0.1728740720983623469, 1e-12, "eraAtciqz", "di", status);
+
+}
+
+static void t_atco13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a t c o 1 3
+**  - - - - - - - - -
+**
+**  Test eraAtco13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAtco13, vvd, viv
+**
+**  This revision:  2013 October 4
+*/
+{
+   double rc, dc, pr, pd, px, rv, utc1, utc2, dut1,
+          elong, phi, hm, xp, yp, phpa, tc, rh, wl,
+          aob, zob, hob, dob, rob, eo;
+   int j;
+
+
+   rc = 2.71;
+   dc = 0.174;
+   pr = 1e-5;
+   pd = 5e-6;
+   px = 0.1;
+   rv = 55.0;
+   utc1 = 2456384.5;
+   utc2 = 0.969254051;
+   dut1 = 0.1550675;
+   elong = -0.527800806;
+   phi = -1.2345856;
+   hm = 2738.0;
+   xp = 2.47230737e-7;
+   yp = 1.82640464e-6;
+   phpa = 731.0;
+   tc = 12.8;
+   rh = 0.59;
+   wl = 0.55;
+
+   j = eraAtco13(rc, dc, pr, pd, px, rv,
+                 utc1, utc2, dut1, elong, phi, hm, xp, yp,
+                 phpa, tc, rh, wl,
+                 &aob, &zob, &hob, &dob, &rob, &eo);
+
+   vvd(aob, 0.09251774485358230653, 1e-12, "eraAtco13", "aob", status);
+   vvd(zob, 1.407661405256767021, 1e-12, "eraAtco13", "zob", status);
+   vvd(hob, -0.09265154431403157925, 1e-12, "eraAtco13", "hob", status);
+   vvd(dob, 0.1716626560075591655, 1e-12, "eraAtco13", "dob", status);
+   vvd(rob, 2.710260453503097719, 1e-12, "eraAtco13", "rob", status);
+   vvd(eo, -0.003020548354802412839, 1e-14, "eraAtco13", "eo", status);
+   viv(j, 0, "eraAtco13", "j", status);
+
+}
+
+static void t_atic13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a t i c 1 3
+**  - - - - - - - - -
+**
+**  Test eraAtic13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAtic13, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double ri, di, date1, date2, rc, dc, eo;
+
+
+   ri = 2.710121572969038991;
+   di = 0.1729371367218230438;
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+
+   eraAtic13(ri, di, date1, date2, &rc, &dc, &eo);
+
+   vvd(rc, 2.710126504531374930, 1e-12, "eraAtic13", "rc", status);
+   vvd(dc, 0.1740632537628342320, 1e-12, "eraAtic13", "dc", status);
+   vvd(eo, -0.002900618712657375647, 1e-14, "eraAtic13", "eo", status);
+
+}
+
+static void t_aticq(int *status)
+/*
+**  - - - - - - - -
+**   t _ a t i c q
+**  - - - - - - - -
+**
+**  Test eraAticq function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApci13, eraAticq, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2, eo, ri, di, rc, dc;
+   eraASTROM astrom;
+
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+   eraApci13(date1, date2, &astrom, &eo);
+   ri = 2.710121572969038991;
+   di = 0.1729371367218230438;
+
+   eraAticq(ri, di, &astrom, &rc, &dc);
+
+   vvd(rc, 2.710126504531374930, 1e-12, "eraAticq", "rc", status);
+   vvd(dc, 0.1740632537628342320, 1e-12, "eraAticq", "dc", status);
+
+}
+
+static void t_aticqn(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a t i c q n
+**  - - - - - - - - -
+**
+**  Test eraAticqn function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApci13, eraAticqn, vvd
+**
+**  This revision:  2013 October 3
+*/
+{
+   double date1, date2, eo, ri, di, rc, dc;
+   eraLDBODY b[3];
+   eraASTROM astrom;
+
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+   eraApci13(date1, date2, &astrom, &eo);
+   ri = 2.709994899247599271;
+   di = 0.1728740720983623469;
+   b[0].bm = 0.00028574;
+   b[0].dl = 3e-10;
+   b[0].pv[0][0] = -7.81014427;
+   b[0].pv[0][1] = -5.60956681;
+   b[0].pv[0][2] = -1.98079819;
+   b[0].pv[1][0] =  0.0030723249;
+   b[0].pv[1][1] = -0.00406995477;
+   b[0].pv[1][2] = -0.00181335842;
+   b[1].bm = 0.00095435;
+   b[1].dl = 3e-9;
+   b[1].pv[0][0] =  0.738098796;
+   b[1].pv[0][1] =  4.63658692;
+   b[1].pv[0][2] =  1.9693136;
+   b[1].pv[1][0] = -0.00755816922;
+   b[1].pv[1][1] =  0.00126913722;
+   b[1].pv[1][2] =  0.000727999001;
+   b[2].bm = 1.0;
+   b[2].dl = 6e-6;
+   b[2].pv[0][0] = -0.000712174377;
+   b[2].pv[0][1] = -0.00230478303;
+   b[2].pv[0][2] = -0.00105865966;
+   b[2].pv[1][0] =  6.29235213e-6;
+   b[2].pv[1][1] = -3.30888387e-7;
+   b[2].pv[1][2] = -2.96486623e-7;
+
+   eraAticqn(ri, di, &astrom, 3, b, &rc, &dc);
+
+   vvd(rc, 2.709999575032685412, 1e-12, "eraAtciqn", "rc", status);
+   vvd(dc, 0.1739999656317778034, 1e-12, "eraAtciqn", "dc", status);
+
+}
+
+static void t_atio13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a t i o 1 3
+**  - - - - - - - - -
+**
+**  Test eraAtio13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAtio13, vvd, viv
+**
+**  This revision:  2013 October 3
+*/
+{
+   double ri, di, utc1, utc2, dut1, elong, phi, hm, xp, yp,
+          phpa, tc, rh, wl, aob, zob, hob, dob, rob;
+   int j;
+
+
+   ri = 2.710121572969038991;
+   di = 0.1729371367218230438;
+   utc1 = 2456384.5;
+   utc2 = 0.969254051;
+   dut1 = 0.1550675;
+   elong = -0.527800806;
+   phi = -1.2345856;
+   hm = 2738.0;
+   xp = 2.47230737e-7;
+   yp = 1.82640464e-6;
+   phpa = 731.0;
+   tc = 12.8;
+   rh = 0.59;
+   wl = 0.55;
+
+   j = eraAtio13(ri, di, utc1, utc2, dut1, elong, phi, hm,
+                 xp, yp, phpa, tc, rh, wl,
+                 &aob, &zob, &hob, &dob, &rob);
+
+   vvd(aob, 0.09233952224794989993, 1e-12, "eraAtio13", "aob", status);
+   vvd(zob, 1.407758704513722461, 1e-12, "eraAtio13", "zob", status);
+   vvd(hob, -0.09247619879782006106, 1e-12, "eraAtio13", "hob", status);
+   vvd(dob, 0.1717653435758265198, 1e-12, "eraAtio13", "dob", status);
+   vvd(rob, 2.710085107986886201, 1e-12, "eraAtio13", "rob", status);
+   viv(j, 0, "eraAtio13", "j", status);
+
+}
+
+static void t_atioq(int *status)
+/*
+**  - - - - - - - -
+**   t _ a t i o q
+**  - - - - - - - -
+**
+**  Test eraAtioq function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraApio13, eraAtioq, vvd, viv
+**
+**  This revision:  2013 October 4
+*/
+{
+   double utc1, utc2, dut1, elong, phi, hm, xp, yp,
+          phpa, tc, rh, wl, ri, di, aob, zob, hob, dob, rob;
+   eraASTROM astrom;
+
+
+   utc1 = 2456384.5;
+   utc2 = 0.969254051;
+   dut1 = 0.1550675;
+   elong = -0.527800806;
+   phi = -1.2345856;
+   hm = 2738.0;
+   xp = 2.47230737e-7;
+   yp = 1.82640464e-6;
+   phpa = 731.0;
+   tc = 12.8;
+   rh = 0.59;
+   wl = 0.55;
+   (void) eraApio13(utc1, utc2, dut1, elong, phi, hm, xp, yp,
+                    phpa, tc, rh, wl, &astrom);
+   ri = 2.710121572969038991;
+   di = 0.1729371367218230438;
+
+   eraAtioq(ri, di, &astrom, &aob, &zob, &hob, &dob, &rob);
+
+   vvd(aob, 0.09233952224794989993, 1e-12, "eraAtioq", "aob", status);
+   vvd(zob, 1.407758704513722461, 1e-12, "eraAtioq", "zob", status);
+   vvd(hob, -0.09247619879782006106, 1e-12, "eraAtioq", "hob", status);
+   vvd(dob, 0.1717653435758265198, 1e-12, "eraAtioq", "dob", status);
+   vvd(rob, 2.710085107986886201, 1e-12, "eraAtioq", "rob", status);
+
+}
+
+static void t_atoc13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a t o c 1 3
+**  - - - - - - - - -
+**
+**  Test eraAtoc13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAtoc13, vvd, viv
+**
+**  This revision:  2013 October 3
+*/
+{
+   double utc1, utc2, dut1,
+          elong, phi, hm, xp, yp, phpa, tc, rh, wl,
+          ob1, ob2, rc, dc;
+   int j;
+
+
+   utc1 = 2456384.5;
+   utc2 = 0.969254051;
+   dut1 = 0.1550675;
+   elong = -0.527800806;
+   phi = -1.2345856;
+   hm = 2738.0;
+   xp = 2.47230737e-7;
+   yp = 1.82640464e-6;
+   phpa = 731.0;
+   tc = 12.8;
+   rh = 0.59;
+   wl = 0.55;
+
+   ob1 = 2.710085107986886201;
+   ob2 = 0.1717653435758265198;
+   j = eraAtoc13 ( "R", ob1, ob2, utc1, utc2, dut1,
+                   elong, phi, hm, xp, yp, phpa, tc, rh, wl,
+                   &rc, &dc);
+   vvd(rc, 2.709956744661000609, 1e-12, "eraAtoc13", "R/rc", status);
+   vvd(dc, 0.1741696500895398562, 1e-12, "eraAtoc13", "R/dc", status);
+   viv(j, 0, "eraAtoc13", "R/j", status);
+
+   ob1 = -0.09247619879782006106;
+   ob2 = 0.1717653435758265198;
+   j = eraAtoc13 ( "H", ob1, ob2, utc1, utc2, dut1,
+                   elong, phi, hm, xp, yp, phpa, tc, rh, wl,
+                   &rc, &dc);
+   vvd(rc, 2.709956744661000609, 1e-12, "eraAtoc13", "H/rc", status);
+   vvd(dc, 0.1741696500895398562, 1e-12, "eraAtoc13", "H/dc", status);
+   viv(j, 0, "eraAtoc13", "H/j", status);
+
+   ob1 = 0.09233952224794989993;
+   ob2 = 1.407758704513722461;
+   j = eraAtoc13 ( "A", ob1, ob2, utc1, utc2, dut1,
+                   elong, phi, hm, xp, yp, phpa, tc, rh, wl,
+                   &rc, &dc);
+   vvd(rc, 2.709956744661000609, 1e-12, "eraAtoc13", "A/rc", status);
+   vvd(dc, 0.1741696500895398565, 1e-12, "eraAtoc13", "A/dc", status);
+   viv(j, 0, "eraAtoc13", "A/j", status);
+
+}
+
+static void t_atoi13(int *status)
+/*
+**  - - - - - - - - -
+**   t _ a t o i 1 3
+**  - - - - - - - - -
+**
+**  Test eraAtoi13 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraAtoi13, vvd, viv
+**
+**  This revision:  2013 October 3
+*/
+{
+   double utc1, utc2, dut1, elong, phi, hm, xp, yp, phpa, tc, rh, wl,
+          ob1, ob2, ri, di;
+   int j;
+
+
+   utc1 = 2456384.5;
+   utc2 = 0.969254051;
+   dut1 = 0.1550675;
+   elong = -0.527800806;
+   phi = -1.2345856;
+   hm = 2738.0;
+   xp = 2.47230737e-7;
+   yp = 1.82640464e-6;
+   phpa = 731.0;
+   tc = 12.8;
+   rh = 0.59;
+   wl = 0.55;
+
+   ob1 = 2.710085107986886201;
+   ob2 = 0.1717653435758265198;
+   j = eraAtoi13 ( "R", ob1, ob2, utc1, utc2, dut1,
+                   elong, phi, hm, xp, yp, phpa, tc, rh, wl,
+                   &ri, &di);
+   vvd(ri, 2.710121574449135955, 1e-12, "eraAtoi13", "R/ri", status);
+   vvd(di, 0.1729371839114567725, 1e-12, "eraAtoi13", "R/di", status);
+   viv(j, 0, "eraAtoi13", "R/J", status);
+
+   ob1 = -0.09247619879782006106;
+   ob2 = 0.1717653435758265198;
+   j = eraAtoi13 ( "H", ob1, ob2, utc1, utc2, dut1,
+                   elong, phi, hm, xp, yp, phpa, tc, rh, wl,
+                   &ri, &di);
+   vvd(ri, 2.710121574449135955, 1e-12, "eraAtoi13", "H/ri", status);
+   vvd(di, 0.1729371839114567725, 1e-12, "eraAtoi13", "H/di", status);
+   viv(j, 0, "eraAtoi13", "H/J", status);
+
+   ob1 = 0.09233952224794989993;
+   ob2 = 1.407758704513722461;
+   j = eraAtoi13 ( "A", ob1, ob2, utc1, utc2, dut1,
+                   elong, phi, hm, xp, yp, phpa, tc, rh, wl,
+                   &ri, &di);
+   vvd(ri, 2.710121574449135955, 1e-12, "eraAtoi13", "A/ri", status);
+   vvd(di, 0.1729371839114567728, 1e-12, "eraAtoi13", "A/di", status);
+   viv(j, 0, "eraAtoi13", "A/J", status);
+
+}
+
+static void t_atoiq(int *status)
+/*
+**  - - - - - - - -
+**   t _ a t o i q
+**  - - - - - - - -
+*
+**  Test eraAtoiq function.
+*
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+*
+**  Called:  eraApio13, eraAtoiq, vvd
+*
+**  This revision:  2013 October 4
+*/
+{
+   double utc1, utc2, dut1, elong, phi, hm, xp, yp, phpa, tc, rh, wl,
+          ob1, ob2, ri, di;
+   eraASTROM astrom;
+
+
+   utc1 = 2456384.5;
+   utc2 = 0.969254051;
+   dut1 = 0.1550675;
+   elong = -0.527800806;
+   phi = -1.2345856;
+   hm = 2738.0;
+   xp = 2.47230737e-7;
+   yp = 1.82640464e-6;
+   phpa = 731.0;
+   tc = 12.8;
+   rh = 0.59;
+   wl = 0.55;
+   (void) eraApio13(utc1, utc2, dut1, elong, phi, hm, xp, yp,
+                    phpa, tc, rh, wl, &astrom);
+
+   ob1 = 2.710085107986886201;
+   ob2 = 0.1717653435758265198;
+   eraAtoiq("R", ob1, ob2, &astrom, &ri, &di);
+   vvd(ri, 2.710121574449135955, 1e-12,
+           "eraAtoiq", "R/ri", status);
+   vvd(di, 0.1729371839114567725, 1e-12,
+           "eraAtoiq", "R/di", status);
+
+   ob1 = -0.09247619879782006106;
+   ob2 = 0.1717653435758265198;
+   eraAtoiq("H", ob1, ob2, &astrom, &ri, &di);
+   vvd(ri, 2.710121574449135955, 1e-12,
+           "eraAtoiq", "H/ri", status);
+   vvd(di, 0.1729371839114567725, 1e-12,
+           "eraAtoiq", "H/di", status);
+
+   ob1 = 0.09233952224794989993;
+   ob2 = 1.407758704513722461;
+   eraAtoiq("A", ob1, ob2, &astrom, &ri, &di);
+   vvd(ri, 2.710121574449135955, 1e-12,
+           "eraAtoiq", "A/ri", status);
+   vvd(di, 0.1729371839114567728, 1e-12,
+           "eraAtoiq", "A/di", status);
+
+}
+
+static void t_bi00(int *status)
+/*
+**  - - - - - - -
+**   t _ b i 0 0
+**  - - - - - - -
+**
+**  Test eraBi00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraBi00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsibi, depsbi, dra;
+
+   eraBi00(&dpsibi, &depsbi, &dra);
+
+   vvd(dpsibi, -0.2025309152835086613e-6, 1e-12,
+      "eraBi00", "dpsibi", status);
+   vvd(depsbi, -0.3306041454222147847e-7, 1e-12,
+      "eraBi00", "depsbi", status);
+   vvd(dra, -0.7078279744199225506e-7, 1e-12,
+      "eraBi00", "dra", status);
+}
+
+static void t_bp00(int *status)
+/*
+**  - - - - - - -
+**   t _ b p 0 0
+**  - - - - - - -
+**
+**  Test eraBp00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraBp00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rb[3][3], rp[3][3], rbp[3][3];
+
+
+   eraBp00(2400000.5, 50123.9999, rb, rp, rbp);
+
+   vvd(rb[0][0], 0.9999999999999942498, 1e-12,
+       "eraBp00", "rb11", status);
+   vvd(rb[0][1], -0.7078279744199196626e-7, 1e-16,
+       "eraBp00", "rb12", status);
+   vvd(rb[0][2], 0.8056217146976134152e-7, 1e-16,
+       "eraBp00", "rb13", status);
+   vvd(rb[1][0], 0.7078279477857337206e-7, 1e-16,
+       "eraBp00", "rb21", status);
+   vvd(rb[1][1], 0.9999999999999969484, 1e-12,
+       "eraBp00", "rb22", status);
+   vvd(rb[1][2], 0.3306041454222136517e-7, 1e-16,
+       "eraBp00", "rb23", status);
+   vvd(rb[2][0], -0.8056217380986972157e-7, 1e-16,
+       "eraBp00", "rb31", status);
+   vvd(rb[2][1], -0.3306040883980552500e-7, 1e-16,
+       "eraBp00", "rb32", status);
+   vvd(rb[2][2], 0.9999999999999962084, 1e-12,
+       "eraBp00", "rb33", status);
+
+   vvd(rp[0][0], 0.9999995504864048241, 1e-12,
+       "eraBp00", "rp11", status);
+   vvd(rp[0][1], 0.8696113836207084411e-3, 1e-14,
+       "eraBp00", "rp12", status);
+   vvd(rp[0][2], 0.3778928813389333402e-3, 1e-14,
+       "eraBp00", "rp13", status);
+   vvd(rp[1][0], -0.8696113818227265968e-3, 1e-14,
+       "eraBp00", "rp21", status);
+   vvd(rp[1][1], 0.9999996218879365258, 1e-12,
+       "eraBp00", "rp22", status);
+   vvd(rp[1][2], -0.1690679263009242066e-6, 1e-14,
+       "eraBp00", "rp23", status);
+   vvd(rp[2][0], -0.3778928854764695214e-3, 1e-14,
+       "eraBp00", "rp31", status);
+   vvd(rp[2][1], -0.1595521004195286491e-6, 1e-14,
+       "eraBp00", "rp32", status);
+   vvd(rp[2][2], 0.9999999285984682756, 1e-12,
+       "eraBp00", "rp33", status);
+
+   vvd(rbp[0][0], 0.9999995505175087260, 1e-12,
+       "eraBp00", "rbp11", status);
+   vvd(rbp[0][1], 0.8695405883617884705e-3, 1e-14,
+       "eraBp00", "rbp12", status);
+   vvd(rbp[0][2], 0.3779734722239007105e-3, 1e-14,
+       "eraBp00", "rbp13", status);
+   vvd(rbp[1][0], -0.8695405990410863719e-3, 1e-14,
+       "eraBp00", "rbp21", status);
+   vvd(rbp[1][1], 0.9999996219494925900, 1e-12,
+       "eraBp00", "rbp22", status);
+   vvd(rbp[1][2], -0.1360775820404982209e-6, 1e-14,
+       "eraBp00", "rbp23", status);
+   vvd(rbp[2][0], -0.3779734476558184991e-3, 1e-14,
+       "eraBp00", "rbp31", status);
+   vvd(rbp[2][1], -0.1925857585832024058e-6, 1e-14,
+       "eraBp00", "rbp32", status);
+   vvd(rbp[2][2], 0.9999999285680153377, 1e-12,
+       "eraBp00", "rbp33", status);
+}
+
+static void t_bp06(int *status)
+/*
+**  - - - - - - -
+**   t _ b p 0 6
+**  - - - - - - -
+**
+**  Test eraBp06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraBp06, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rb[3][3], rp[3][3], rbp[3][3];
+
+
+   eraBp06(2400000.5, 50123.9999, rb, rp, rbp);
+
+   vvd(rb[0][0], 0.9999999999999942497, 1e-12,
+       "eraBp06", "rb11", status);
+   vvd(rb[0][1], -0.7078368960971557145e-7, 1e-14,
+       "eraBp06", "rb12", status);
+   vvd(rb[0][2], 0.8056213977613185606e-7, 1e-14,
+       "eraBp06", "rb13", status);
+   vvd(rb[1][0], 0.7078368694637674333e-7, 1e-14,
+       "eraBp06", "rb21", status);
+   vvd(rb[1][1], 0.9999999999999969484, 1e-12,
+       "eraBp06", "rb22", status);
+   vvd(rb[1][2], 0.3305943742989134124e-7, 1e-14,
+       "eraBp06", "rb23", status);
+   vvd(rb[2][0], -0.8056214211620056792e-7, 1e-14,
+       "eraBp06", "rb31", status);
+   vvd(rb[2][1], -0.3305943172740586950e-7, 1e-14,
+       "eraBp06", "rb32", status);
+   vvd(rb[2][2], 0.9999999999999962084, 1e-12,
+       "eraBp06", "rb33", status);
+
+   vvd(rp[0][0], 0.9999995504864960278, 1e-12,
+       "eraBp06", "rp11", status);
+   vvd(rp[0][1], 0.8696112578855404832e-3, 1e-14,
+       "eraBp06", "rp12", status);
+   vvd(rp[0][2], 0.3778929293341390127e-3, 1e-14,
+       "eraBp06", "rp13", status);
+   vvd(rp[1][0], -0.8696112560510186244e-3, 1e-14,
+       "eraBp06", "rp21", status);
+   vvd(rp[1][1], 0.9999996218880458820, 1e-12,
+       "eraBp06", "rp22", status);
+   vvd(rp[1][2], -0.1691646168941896285e-6, 1e-14,
+       "eraBp06", "rp23", status);
+   vvd(rp[2][0], -0.3778929335557603418e-3, 1e-14,
+       "eraBp06", "rp31", status);
+   vvd(rp[2][1], -0.1594554040786495076e-6, 1e-14,
+       "eraBp06", "rp32", status);
+   vvd(rp[2][2], 0.9999999285984501222, 1e-12,
+       "eraBp06", "rp33", status);
+
+   vvd(rbp[0][0], 0.9999995505176007047, 1e-12,
+       "eraBp06", "rbp11", status);
+   vvd(rbp[0][1], 0.8695404617348208406e-3, 1e-14,
+       "eraBp06", "rbp12", status);
+   vvd(rbp[0][2], 0.3779735201865589104e-3, 1e-14,
+       "eraBp06", "rbp13", status);
+   vvd(rbp[1][0], -0.8695404723772031414e-3, 1e-14,
+       "eraBp06", "rbp21", status);
+   vvd(rbp[1][1], 0.9999996219496027161, 1e-12,
+       "eraBp06", "rbp22", status);
+   vvd(rbp[1][2], -0.1361752497080270143e-6, 1e-14,
+       "eraBp06", "rbp23", status);
+   vvd(rbp[2][0], -0.3779734957034089490e-3, 1e-14,
+       "eraBp06", "rbp31", status);
+   vvd(rbp[2][1], -0.1924880847894457113e-6, 1e-14,
+       "eraBp06", "rbp32", status);
+   vvd(rbp[2][2], 0.9999999285679971958, 1e-12,
+       "eraBp06", "rbp33", status);
+}
+
+static void t_bpn2xy(int *status)
+/*
+**  - - - - - - - - -
+**   t _ b p n 2 x y
+**  - - - - - - - - -
+**
+**  Test eraBpn2xy function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraBpn2xy, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rbpn[3][3], x, y;
+
+
+   rbpn[0][0] =  9.999962358680738e-1;
+   rbpn[0][1] = -2.516417057665452e-3;
+   rbpn[0][2] = -1.093569785342370e-3;
+
+   rbpn[1][0] =  2.516462370370876e-3;
+   rbpn[1][1] =  9.999968329010883e-1;
+   rbpn[1][2] =  4.006159587358310e-5;
+
+   rbpn[2][0] =  1.093465510215479e-3;
+   rbpn[2][1] = -4.281337229063151e-5;
+   rbpn[2][2] =  9.999994012499173e-1;
+
+   eraBpn2xy(rbpn, &x, &y);
+
+   vvd(x,  1.093465510215479e-3, 1e-12, "eraBpn2xy", "x", status);
+   vvd(y, -4.281337229063151e-5, 1e-12, "eraBpn2xy", "y", status);
+
+}
+
+static void t_c2i00a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c 2 i 0 0 a
+**  - - - - - - - - -
+**
+**  Test eraC2i00a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2i00a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rc2i[3][3];
+
+
+   eraC2i00a(2400000.5, 53736.0, rc2i);
+
+   vvd(rc2i[0][0], 0.9999998323037165557, 1e-12,
+       "eraC2i00a", "11", status);
+   vvd(rc2i[0][1], 0.5581526348992140183e-9, 1e-12,
+       "eraC2i00a", "12", status);
+   vvd(rc2i[0][2], -0.5791308477073443415e-3, 1e-12,
+       "eraC2i00a", "13", status);
+
+   vvd(rc2i[1][0], -0.2384266227870752452e-7, 1e-12,
+       "eraC2i00a", "21", status);
+   vvd(rc2i[1][1], 0.9999999991917405258, 1e-12,
+       "eraC2i00a", "22", status);
+   vvd(rc2i[1][2], -0.4020594955028209745e-4, 1e-12,
+       "eraC2i00a", "23", status);
+
+   vvd(rc2i[2][0], 0.5791308472168152904e-3, 1e-12,
+       "eraC2i00a", "31", status);
+   vvd(rc2i[2][1], 0.4020595661591500259e-4, 1e-12,
+       "eraC2i00a", "32", status);
+   vvd(rc2i[2][2], 0.9999998314954572304, 1e-12,
+       "eraC2i00a", "33", status);
+
+}
+
+static void t_c2i00b(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c 2 i 0 0 b
+**  - - - - - - - - -
+**
+**  Test eraC2i00b function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2i00b, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rc2i[3][3];
+
+
+   eraC2i00b(2400000.5, 53736.0, rc2i);
+
+   vvd(rc2i[0][0], 0.9999998323040954356, 1e-12,
+       "eraC2i00b", "11", status);
+   vvd(rc2i[0][1], 0.5581526349131823372e-9, 1e-12,
+       "eraC2i00b", "12", status);
+   vvd(rc2i[0][2], -0.5791301934855394005e-3, 1e-12,
+       "eraC2i00b", "13", status);
+
+   vvd(rc2i[1][0], -0.2384239285499175543e-7, 1e-12,
+       "eraC2i00b", "21", status);
+   vvd(rc2i[1][1], 0.9999999991917574043, 1e-12,
+       "eraC2i00b", "22", status);
+   vvd(rc2i[1][2], -0.4020552974819030066e-4, 1e-12,
+       "eraC2i00b", "23", status);
+
+   vvd(rc2i[2][0], 0.5791301929950208873e-3, 1e-12,
+       "eraC2i00b", "31", status);
+   vvd(rc2i[2][1], 0.4020553681373720832e-4, 1e-12,
+       "eraC2i00b", "32", status);
+   vvd(rc2i[2][2], 0.9999998314958529887, 1e-12,
+       "eraC2i00b", "33", status);
+
+}
+
+static void t_c2i06a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c 2 i 0 6 a
+**  - - - - - - - - -
+**
+**  Test eraC2i06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2i06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rc2i[3][3];
+
+
+   eraC2i06a(2400000.5, 53736.0, rc2i);
+
+   vvd(rc2i[0][0], 0.9999998323037159379, 1e-12,
+       "eraC2i06a", "11", status);
+   vvd(rc2i[0][1], 0.5581121329587613787e-9, 1e-12,
+       "eraC2i06a", "12", status);
+   vvd(rc2i[0][2], -0.5791308487740529749e-3, 1e-12,
+       "eraC2i06a", "13", status);
+
+   vvd(rc2i[1][0], -0.2384253169452306581e-7, 1e-12,
+       "eraC2i06a", "21", status);
+   vvd(rc2i[1][1], 0.9999999991917467827, 1e-12,
+       "eraC2i06a", "22", status);
+   vvd(rc2i[1][2], -0.4020579392895682558e-4, 1e-12,
+       "eraC2i06a", "23", status);
+
+   vvd(rc2i[2][0], 0.5791308482835292617e-3, 1e-12,
+       "eraC2i06a", "31", status);
+   vvd(rc2i[2][1], 0.4020580099454020310e-4, 1e-12,
+       "eraC2i06a", "32", status);
+   vvd(rc2i[2][2], 0.9999998314954628695, 1e-12,
+       "eraC2i06a", "33", status);
+
+}
+
+static void t_c2ibpn(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c 2 i b p n
+**  - - - - - - - - -
+**
+**  Test eraC2ibpn function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2ibpn, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rbpn[3][3], rc2i[3][3];
+
+
+   rbpn[0][0] =  9.999962358680738e-1;
+   rbpn[0][1] = -2.516417057665452e-3;
+   rbpn[0][2] = -1.093569785342370e-3;
+
+   rbpn[1][0] =  2.516462370370876e-3;
+   rbpn[1][1] =  9.999968329010883e-1;
+   rbpn[1][2] =  4.006159587358310e-5;
+
+   rbpn[2][0] =  1.093465510215479e-3;
+   rbpn[2][1] = -4.281337229063151e-5;
+   rbpn[2][2] =  9.999994012499173e-1;
+
+   eraC2ibpn(2400000.5, 50123.9999, rbpn, rc2i);
+
+   vvd(rc2i[0][0], 0.9999994021664089977, 1e-12,
+       "eraC2ibpn", "11", status);
+   vvd(rc2i[0][1], -0.3869195948017503664e-8, 1e-12,
+       "eraC2ibpn", "12", status);
+   vvd(rc2i[0][2], -0.1093465511383285076e-2, 1e-12,
+       "eraC2ibpn", "13", status);
+
+   vvd(rc2i[1][0], 0.5068413965715446111e-7, 1e-12,
+       "eraC2ibpn", "21", status);
+   vvd(rc2i[1][1], 0.9999999990835075686, 1e-12,
+       "eraC2ibpn", "22", status);
+   vvd(rc2i[1][2], 0.4281334246452708915e-4, 1e-12,
+       "eraC2ibpn", "23", status);
+
+   vvd(rc2i[2][0], 0.1093465510215479000e-2, 1e-12,
+       "eraC2ibpn", "31", status);
+   vvd(rc2i[2][1], -0.4281337229063151000e-4, 1e-12,
+       "eraC2ibpn", "32", status);
+   vvd(rc2i[2][2], 0.9999994012499173103, 1e-12,
+       "eraC2ibpn", "33", status);
+
+}
+
+static void t_c2ixy(int *status)
+/*
+**  - - - - - - - -
+**   t _ c 2 i x y
+**  - - - - - - - -
+**
+**  Test eraC2ixy function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2ixy, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double x, y, rc2i[3][3];
+
+
+   x = 0.5791308486706011000e-3;
+   y = 0.4020579816732961219e-4;
+
+   eraC2ixy(2400000.5, 53736, x, y, rc2i);
+
+   vvd(rc2i[0][0], 0.9999998323037157138, 1e-12,
+       "eraC2ixy", "11", status);
+   vvd(rc2i[0][1], 0.5581526349032241205e-9, 1e-12,
+       "eraC2ixy", "12", status);
+   vvd(rc2i[0][2], -0.5791308491611263745e-3, 1e-12,
+       "eraC2ixy", "13", status);
+
+   vvd(rc2i[1][0], -0.2384257057469842953e-7, 1e-12,
+       "eraC2ixy", "21", status);
+   vvd(rc2i[1][1], 0.9999999991917468964, 1e-12,
+       "eraC2ixy", "22", status);
+   vvd(rc2i[1][2], -0.4020579110172324363e-4, 1e-12,
+       "eraC2ixy", "23", status);
+
+   vvd(rc2i[2][0], 0.5791308486706011000e-3, 1e-12,
+       "eraC2ixy", "31", status);
+   vvd(rc2i[2][1], 0.4020579816732961219e-4, 1e-12,
+       "eraC2ixy", "32", status);
+   vvd(rc2i[2][2], 0.9999998314954627590, 1e-12,
+       "eraC2ixy", "33", status);
+
+}
+
+static void t_c2ixys(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c 2 i x y s
+**  - - - - - - - - -
+**
+**  Test eraC2ixys function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2ixys, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double x, y, s, rc2i[3][3];
+
+
+   x =  0.5791308486706011000e-3;
+   y =  0.4020579816732961219e-4;
+   s = -0.1220040848472271978e-7;
+
+   eraC2ixys(x, y, s, rc2i);
+
+   vvd(rc2i[0][0], 0.9999998323037157138, 1e-12,
+       "eraC2ixys", "11", status);
+   vvd(rc2i[0][1], 0.5581984869168499149e-9, 1e-12,
+       "eraC2ixys", "12", status);
+   vvd(rc2i[0][2], -0.5791308491611282180e-3, 1e-12,
+       "eraC2ixys", "13", status);
+
+   vvd(rc2i[1][0], -0.2384261642670440317e-7, 1e-12,
+       "eraC2ixys", "21", status);
+   vvd(rc2i[1][1], 0.9999999991917468964, 1e-12,
+       "eraC2ixys", "22", status);
+   vvd(rc2i[1][2], -0.4020579110169668931e-4, 1e-12,
+       "eraC2ixys", "23", status);
+
+   vvd(rc2i[2][0], 0.5791308486706011000e-3, 1e-12,
+       "eraC2ixys", "31", status);
+   vvd(rc2i[2][1], 0.4020579816732961219e-4, 1e-12,
+       "eraC2ixys", "32", status);
+   vvd(rc2i[2][2], 0.9999998314954627590, 1e-12,
+       "eraC2ixys", "33", status);
+
+}
+
+static void t_c2s(int *status)
+/*
+**  - - - - - -
+**   t _ c 2 s
+**  - - - - - -
+**
+**  Test eraC2s function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2s, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double p[3], theta, phi;
+
+
+   p[0] = 100.0;
+   p[1] = -50.0;
+   p[2] =  25.0;
+
+   eraC2s(p, &theta, &phi);
+
+   vvd(theta, -0.4636476090008061162, 1e-14, "eraC2s", "theta", status);
+   vvd(phi, 0.2199879773954594463, 1e-14, "eraC2s", "phi", status);
+
+}
+
+static void t_c2t00a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c 2 t 0 0 a
+**  - - - - - - - - -
+**
+**  Test eraC2t00a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2t00a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double tta, ttb, uta, utb, xp, yp, rc2t[3][3];
+
+
+   tta = 2400000.5;
+   uta = 2400000.5;
+   ttb = 53736.0;
+   utb = 53736.0;
+   xp = 2.55060238e-7;
+   yp = 1.860359247e-6;
+
+   eraC2t00a(tta, ttb, uta, utb, xp, yp, rc2t);
+
+   vvd(rc2t[0][0], -0.1810332128307182668, 1e-12,
+       "eraC2t00a", "11", status);
+   vvd(rc2t[0][1], 0.9834769806938457836, 1e-12,
+       "eraC2t00a", "12", status);
+   vvd(rc2t[0][2], 0.6555535638688341725e-4, 1e-12,
+       "eraC2t00a", "13", status);
+
+   vvd(rc2t[1][0], -0.9834768134135984552, 1e-12,
+       "eraC2t00a", "21", status);
+   vvd(rc2t[1][1], -0.1810332203649520727, 1e-12,
+       "eraC2t00a", "22", status);
+   vvd(rc2t[1][2], 0.5749801116141056317e-3, 1e-12,
+       "eraC2t00a", "23", status);
+
+   vvd(rc2t[2][0], 0.5773474014081406921e-3, 1e-12,
+       "eraC2t00a", "31", status);
+   vvd(rc2t[2][1], 0.3961832391770163647e-4, 1e-12,
+       "eraC2t00a", "32", status);
+   vvd(rc2t[2][2], 0.9999998325501692289, 1e-12,
+       "eraC2t00a", "33", status);
+
+}
+
+static void t_c2t00b(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c 2 t 0 0 b
+**  - - - - - - - - -
+**
+**  Test eraC2t00b function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2t00b, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double tta, ttb, uta, utb, xp, yp, rc2t[3][3];
+
+
+   tta = 2400000.5;
+   uta = 2400000.5;
+   ttb = 53736.0;
+   utb = 53736.0;
+   xp = 2.55060238e-7;
+   yp = 1.860359247e-6;
+
+   eraC2t00b(tta, ttb, uta, utb, xp, yp, rc2t);
+
+   vvd(rc2t[0][0], -0.1810332128439678965, 1e-12,
+       "eraC2t00b", "11", status);
+   vvd(rc2t[0][1], 0.9834769806913872359, 1e-12,
+       "eraC2t00b", "12", status);
+   vvd(rc2t[0][2], 0.6555565082458415611e-4, 1e-12,
+       "eraC2t00b", "13", status);
+
+   vvd(rc2t[1][0], -0.9834768134115435923, 1e-12,
+       "eraC2t00b", "21", status);
+   vvd(rc2t[1][1], -0.1810332203784001946, 1e-12,
+       "eraC2t00b", "22", status);
+   vvd(rc2t[1][2], 0.5749793922030017230e-3, 1e-12,
+       "eraC2t00b", "23", status);
+
+   vvd(rc2t[2][0], 0.5773467471863534901e-3, 1e-12,
+       "eraC2t00b", "31", status);
+   vvd(rc2t[2][1], 0.3961790411549945020e-4, 1e-12,
+       "eraC2t00b", "32", status);
+   vvd(rc2t[2][2], 0.9999998325505635738, 1e-12,
+       "eraC2t00b", "33", status);
+
+}
+
+static void t_c2t06a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c 2 t 0 6 a
+**  - - - - - - - - -
+**
+**  Test eraC2t06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2t06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double tta, ttb, uta, utb, xp, yp, rc2t[3][3];
+
+
+   tta = 2400000.5;
+   uta = 2400000.5;
+   ttb = 53736.0;
+   utb = 53736.0;
+   xp = 2.55060238e-7;
+   yp = 1.860359247e-6;
+
+   eraC2t06a(tta, ttb, uta, utb, xp, yp, rc2t);
+
+   vvd(rc2t[0][0], -0.1810332128305897282, 1e-12,
+       "eraC2t06a", "11", status);
+   vvd(rc2t[0][1], 0.9834769806938592296, 1e-12,
+       "eraC2t06a", "12", status);
+   vvd(rc2t[0][2], 0.6555550962998436505e-4, 1e-12,
+       "eraC2t06a", "13", status);
+
+   vvd(rc2t[1][0], -0.9834768134136214897, 1e-12,
+       "eraC2t06a", "21", status);
+   vvd(rc2t[1][1], -0.1810332203649130832, 1e-12,
+       "eraC2t06a", "22", status);
+   vvd(rc2t[1][2], 0.5749800844905594110e-3, 1e-12,
+       "eraC2t06a", "23", status);
+
+   vvd(rc2t[2][0], 0.5773474024748545878e-3, 1e-12,
+       "eraC2t06a", "31", status);
+   vvd(rc2t[2][1], 0.3961816829632690581e-4, 1e-12,
+       "eraC2t06a", "32", status);
+   vvd(rc2t[2][2], 0.9999998325501747785, 1e-12,
+       "eraC2t06a", "33", status);
+
+}
+
+static void t_c2tcio(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c 2 t c i o
+**  - - - - - - - - -
+**
+**  Test eraC2tcio function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2tcio, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rc2i[3][3], era, rpom[3][3], rc2t[3][3];
+
+
+   rc2i[0][0] =  0.9999998323037164738;
+   rc2i[0][1] =  0.5581526271714303683e-9;
+   rc2i[0][2] = -0.5791308477073443903e-3;
+
+   rc2i[1][0] = -0.2384266227524722273e-7;
+   rc2i[1][1] =  0.9999999991917404296;
+   rc2i[1][2] = -0.4020594955030704125e-4;
+
+   rc2i[2][0] =  0.5791308472168153320e-3;
+   rc2i[2][1] =  0.4020595661593994396e-4;
+   rc2i[2][2] =  0.9999998314954572365;
+
+   era = 1.75283325530307;
+
+   rpom[0][0] =  0.9999999999999674705;
+   rpom[0][1] = -0.1367174580728847031e-10;
+   rpom[0][2] =  0.2550602379999972723e-6;
+
+   rpom[1][0] =  0.1414624947957029721e-10;
+   rpom[1][1] =  0.9999999999982694954;
+   rpom[1][2] = -0.1860359246998866338e-5;
+
+   rpom[2][0] = -0.2550602379741215275e-6;
+   rpom[2][1] =  0.1860359247002413923e-5;
+   rpom[2][2] =  0.9999999999982369658;
+
+
+   eraC2tcio(rc2i, era, rpom, rc2t);
+
+   vvd(rc2t[0][0], -0.1810332128307110439, 1e-12,
+       "eraC2tcio", "11", status);
+   vvd(rc2t[0][1], 0.9834769806938470149, 1e-12,
+       "eraC2tcio", "12", status);
+   vvd(rc2t[0][2], 0.6555535638685466874e-4, 1e-12,
+       "eraC2tcio", "13", status);
+
+   vvd(rc2t[1][0], -0.9834768134135996657, 1e-12,
+       "eraC2tcio", "21", status);
+   vvd(rc2t[1][1], -0.1810332203649448367, 1e-12,
+       "eraC2tcio", "22", status);
+   vvd(rc2t[1][2], 0.5749801116141106528e-3, 1e-12,
+       "eraC2tcio", "23", status);
+
+   vvd(rc2t[2][0], 0.5773474014081407076e-3, 1e-12,
+       "eraC2tcio", "31", status);
+   vvd(rc2t[2][1], 0.3961832391772658944e-4, 1e-12,
+       "eraC2tcio", "32", status);
+   vvd(rc2t[2][2], 0.9999998325501691969, 1e-12,
+       "eraC2tcio", "33", status);
+
+}
+
+static void t_c2teqx(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c 2 t e q x
+**  - - - - - - - - -
+**
+**  Test eraC2teqx function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2teqx, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rbpn[3][3], gst, rpom[3][3], rc2t[3][3];
+
+
+   rbpn[0][0] =  0.9999989440476103608;
+   rbpn[0][1] = -0.1332881761240011518e-2;
+   rbpn[0][2] = -0.5790767434730085097e-3;
+
+   rbpn[1][0] =  0.1332858254308954453e-2;
+   rbpn[1][1] =  0.9999991109044505944;
+   rbpn[1][2] = -0.4097782710401555759e-4;
+
+   rbpn[2][0] =  0.5791308472168153320e-3;
+   rbpn[2][1] =  0.4020595661593994396e-4;
+   rbpn[2][2] =  0.9999998314954572365;
+
+   gst = 1.754166138040730516;
+
+   rpom[0][0] =  0.9999999999999674705;
+   rpom[0][1] = -0.1367174580728847031e-10;
+   rpom[0][2] =  0.2550602379999972723e-6;
+
+   rpom[1][0] =  0.1414624947957029721e-10;
+   rpom[1][1] =  0.9999999999982694954;
+   rpom[1][2] = -0.1860359246998866338e-5;
+
+   rpom[2][0] = -0.2550602379741215275e-6;
+   rpom[2][1] =  0.1860359247002413923e-5;
+   rpom[2][2] =  0.9999999999982369658;
+
+   eraC2teqx(rbpn, gst, rpom, rc2t);
+
+   vvd(rc2t[0][0], -0.1810332128528685730, 1e-12,
+       "eraC2teqx", "11", status);
+   vvd(rc2t[0][1], 0.9834769806897685071, 1e-12,
+       "eraC2teqx", "12", status);
+   vvd(rc2t[0][2], 0.6555535639982634449e-4, 1e-12,
+       "eraC2teqx", "13", status);
+
+   vvd(rc2t[1][0], -0.9834768134095211257, 1e-12,
+       "eraC2teqx", "21", status);
+   vvd(rc2t[1][1], -0.1810332203871023800, 1e-12,
+       "eraC2teqx", "22", status);
+   vvd(rc2t[1][2], 0.5749801116126438962e-3, 1e-12,
+       "eraC2teqx", "23", status);
+
+   vvd(rc2t[2][0], 0.5773474014081539467e-3, 1e-12,
+       "eraC2teqx", "31", status);
+   vvd(rc2t[2][1], 0.3961832391768640871e-4, 1e-12,
+       "eraC2teqx", "32", status);
+   vvd(rc2t[2][2], 0.9999998325501691969, 1e-12,
+       "eraC2teqx", "33", status);
+
+}
+
+static void t_c2tpe(int *status)
+/*
+**  - - - - - - - -
+**   t _ c 2 t p e
+**  - - - - - - - -
+**
+**  Test eraC2tpe function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2tpe, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double tta, ttb, uta, utb, dpsi, deps, xp, yp, rc2t[3][3];
+
+
+   tta = 2400000.5;
+   uta = 2400000.5;
+   ttb = 53736.0;
+   utb = 53736.0;
+   deps =  0.4090789763356509900;
+   dpsi = -0.9630909107115582393e-5;
+   xp = 2.55060238e-7;
+   yp = 1.860359247e-6;
+
+   eraC2tpe(tta, ttb, uta, utb, dpsi, deps, xp, yp, rc2t);
+
+   vvd(rc2t[0][0], -0.1813677995763029394, 1e-12,
+       "eraC2tpe", "11", status);
+   vvd(rc2t[0][1], 0.9023482206891683275, 1e-12,
+       "eraC2tpe", "12", status);
+   vvd(rc2t[0][2], -0.3909902938641085751, 1e-12,
+       "eraC2tpe", "13", status);
+
+   vvd(rc2t[1][0], -0.9834147641476804807, 1e-12,
+       "eraC2tpe", "21", status);
+   vvd(rc2t[1][1], -0.1659883635434995121, 1e-12,
+       "eraC2tpe", "22", status);
+   vvd(rc2t[1][2], 0.7309763898042819705e-1, 1e-12,
+       "eraC2tpe", "23", status);
+
+   vvd(rc2t[2][0], 0.1059685430673215247e-2, 1e-12,
+       "eraC2tpe", "31", status);
+   vvd(rc2t[2][1], 0.3977631855605078674, 1e-12,
+       "eraC2tpe", "32", status);
+   vvd(rc2t[2][2], 0.9174875068792735362, 1e-12,
+       "eraC2tpe", "33", status);
+
+}
+
+static void t_c2txy(int *status)
+/*
+**  - - - - - - - -
+**   t _ c 2 t x y
+**  - - - - - - - -
+**
+**  Test eraC2txy function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraC2txy, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double tta, ttb, uta, utb, x, y, xp, yp, rc2t[3][3];
+
+
+   tta = 2400000.5;
+   uta = 2400000.5;
+   ttb = 53736.0;
+   utb = 53736.0;
+   x = 0.5791308486706011000e-3;
+   y = 0.4020579816732961219e-4;
+   xp = 2.55060238e-7;
+   yp = 1.860359247e-6;
+
+   eraC2txy(tta, ttb, uta, utb, x, y, xp, yp, rc2t);
+
+   vvd(rc2t[0][0], -0.1810332128306279253, 1e-12,
+       "eraC2txy", "11", status);
+   vvd(rc2t[0][1], 0.9834769806938520084, 1e-12,
+       "eraC2txy", "12", status);
+   vvd(rc2t[0][2], 0.6555551248057665829e-4, 1e-12,
+       "eraC2txy", "13", status);
+
+   vvd(rc2t[1][0], -0.9834768134136142314, 1e-12,
+       "eraC2txy", "21", status);
+   vvd(rc2t[1][1], -0.1810332203649529312, 1e-12,
+       "eraC2txy", "22", status);
+   vvd(rc2t[1][2], 0.5749800843594139912e-3, 1e-12,
+       "eraC2txy", "23", status);
+
+   vvd(rc2t[2][0], 0.5773474028619264494e-3, 1e-12,
+       "eraC2txy", "31", status);
+   vvd(rc2t[2][1], 0.3961816546911624260e-4, 1e-12,
+       "eraC2txy", "32", status);
+   vvd(rc2t[2][2], 0.9999998325501746670, 1e-12,
+       "eraC2txy", "33", status);
+
+}
+
+static void t_cal2jd(int *status)
+/*
+**  - - - - - - - - -
+**   t _ c a l 2 j d
+**  - - - - - - - - -
+**
+**  Test eraCal2jd function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraCal2jd, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   int j;
+   double djm0, djm;
+
+
+   j = eraCal2jd(2003, 06, 01, &djm0, &djm);
+
+   vvd(djm0, 2400000.5, 0.0, "eraCal2jd", "djm0", status);
+   vvd(djm,    52791.0, 0.0, "eraCal2jd", "djm", status);
+
+   viv(j, 0, "eraCal2jd", "j", status);
+
+}
+
+static void t_cp(int *status)
+/*
+**  - - - - -
+**   t _ c p
+**  - - - - -
+**
+**  Test eraCp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraCp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double p[3], c[3];
+
+
+   p[0] =  0.3;
+   p[1] =  1.2;
+   p[2] = -2.5;
+
+   eraCp(p, c);
+
+   vvd(c[0],  0.3, 0.0, "eraCp", "1", status);
+   vvd(c[1],  1.2, 0.0, "eraCp", "2", status);
+   vvd(c[2], -2.5, 0.0, "eraCp", "3", status);
+}
+
+static void t_cpv(int *status)
+/*
+**  - - - - - -
+**   t _ c p v
+**  - - - - - -
+**
+**  Test eraCpv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraCpv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double pv[2][3], c[2][3];
+
+
+   pv[0][0] =  0.3;
+   pv[0][1] =  1.2;
+   pv[0][2] = -2.5;
+
+   pv[1][0] = -0.5;
+   pv[1][1] =  3.1;
+   pv[1][2] =  0.9;
+
+   eraCpv(pv, c);
+
+   vvd(c[0][0],  0.3, 0.0, "eraCpv", "p1", status);
+   vvd(c[0][1],  1.2, 0.0, "eraCpv", "p2", status);
+   vvd(c[0][2], -2.5, 0.0, "eraCpv", "p3", status);
+
+   vvd(c[1][0], -0.5, 0.0, "eraCpv", "v1", status);
+   vvd(c[1][1],  3.1, 0.0, "eraCpv", "v2", status);
+   vvd(c[1][2],  0.9, 0.0, "eraCpv", "v3", status);
+
+}
+
+static void t_cr(int *status)
+/*
+**  - - - - -
+**   t _ c r
+**  - - - - -
+**
+**  Test eraCr function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraCr, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r[3][3], c[3][3];
+
+
+   r[0][0] = 2.0;
+   r[0][1] = 3.0;
+   r[0][2] = 2.0;
+
+   r[1][0] = 3.0;
+   r[1][1] = 2.0;
+   r[1][2] = 3.0;
+
+   r[2][0] = 3.0;
+   r[2][1] = 4.0;
+   r[2][2] = 5.0;
+
+   eraCr(r, c);
+
+   vvd(c[0][0], 2.0, 0.0, "eraCr", "11", status);
+   vvd(c[0][1], 3.0, 0.0, "eraCr", "12", status);
+   vvd(c[0][2], 2.0, 0.0, "eraCr", "13", status);
+
+   vvd(c[1][0], 3.0, 0.0, "eraCr", "21", status);
+   vvd(c[1][1], 2.0, 0.0, "eraCr", "22", status);
+   vvd(c[1][2], 3.0, 0.0, "eraCr", "23", status);
+
+   vvd(c[2][0], 3.0, 0.0, "eraCr", "31", status);
+   vvd(c[2][1], 4.0, 0.0, "eraCr", "32", status);
+   vvd(c[2][2], 5.0, 0.0, "eraCr", "33", status);
+}
+
+static void t_d2dtf(int *status )
+/*
+**  - - - - - - - -
+**   t _ d 2 d t f
+**  - - - - - - - -
+**
+**  Test eraD2dtf function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraD2dtf, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   int j, iy, im, id, ihmsf[4];
+
+
+   j = eraD2dtf("UTC", 5, 2400000.5, 49533.99999, &iy, &im, &id, ihmsf);
+
+   viv(iy, 1994, "eraD2dtf", "y", status);
+   viv(im, 6, "eraD2dtf", "mo", status);
+   viv(id, 30, "eraD2dtf", "d", status);
+   viv(ihmsf[0], 23, "eraD2dtf", "h", status);
+   viv(ihmsf[1], 59, "eraD2dtf", "m", status);
+   viv(ihmsf[2], 60, "eraD2dtf", "s", status);
+   viv(ihmsf[3], 13599, "eraD2dtf", "f", status);
+   viv(j, 0, "eraD2dtf", "j", status);
+
+}
+
+static void t_d2tf(int *status)
+/*
+**  - - - - - - -
+**   t _ d 2 t f
+**  - - - - - - -
+**
+**  Test eraD2tf function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraD2tf, viv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   int ihmsf[4];
+   char s;
+
+
+   eraD2tf(4, -0.987654321, &s, ihmsf);
+
+   viv((int)s, '-', "eraD2tf", "s", status);
+
+   viv(ihmsf[0], 23, "eraD2tf", "0", status);
+   viv(ihmsf[1], 42, "eraD2tf", "1", status);
+   viv(ihmsf[2], 13, "eraD2tf", "2", status);
+   viv(ihmsf[3], 3333, "eraD2tf", "3", status);
+
+}
+
+static void t_dat(int *status)
+/*
+**  - - - - - -
+**   t _ d a t
+**  - - - - - -
+**
+**  Test eraDat function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraDat, vvd, viv
+**
+**  This revision:  2016 July 11
+*/
+{
+   int j;
+   double deltat;
+
+
+   j = eraDat(2003, 6, 1, 0.0, &deltat);
+
+   vvd(deltat, 32.0, 0.0, "eraDat", "d1", status);
+   viv(j, 0, "eraDat", "j1", status);
+
+   j = eraDat(2008, 1, 17, 0.0, &deltat);
+
+   vvd(deltat, 33.0, 0.0, "eraDat", "d2", status);
+   viv(j, 0, "eraDat", "j2", status);
+
+   j = eraDat(2017, 9, 1, 0.0, &deltat);
+
+   vvd(deltat, 37.0, 0.0, "eraDat", "d3", status);
+   viv(j, 0, "eraDat", "j3", status);
+
+}
+
+static void t_dtdb(int *status)
+/*
+**  - - - - - - -
+**   t _ d t d b
+**  - - - - - - -
+**
+**  Test eraDtdb function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraDtdb, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dtdb;
+
+
+   dtdb = eraDtdb(2448939.5, 0.123, 0.76543, 5.0123, 5525.242, 3190.0);
+
+   vvd(dtdb, -0.1280368005936998991e-2, 1e-15, "eraDtdb", "", status);
+
+}
+
+static void t_dtf2d(int *status)
+/*
+**  - - - - - - - -
+**   t _ d t f 2 d
+**  - - - - - - - -
+**
+**  Test eraDtf2d function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraDtf2d, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double u1, u2;
+   int j;
+
+
+   j = eraDtf2d("UTC", 1994, 6, 30, 23, 59, 60.13599, &u1, &u2);
+
+   vvd(u1+u2, 2449534.49999, 1e-6, "eraDtf2d", "u", status);
+   viv(j, 0, "eraDtf2d", "j", status);
+
+}
+
+static void t_eceq06(int *status)
+/*
+**  - - - - -
+**   t _ e c e q 0 6
+**  - - - - -
+**
+**  Test eraEceq06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEceq06, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   double date1, date2, dl, db, dr, dd;
+
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+   dl = 5.1;
+   db = -0.9;
+
+   eraEceq06(date1, date2, dl, db, &dr, &dd);
+
+   vvd(dr, 5.533459733613627767, 1e-14, "eraEceq06", "dr", status);
+   vvd(dd, -1.246542932554480576, 1e-14, "eraEceq06", "dd", status);
+
+}
+
+static void t_ecm06(int *status)
+/*
+**  - - - - - - - -
+**   t _ e c m 0 6
+**  - - - - - - - -
+**
+**  Test eraEcm06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEcm06, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   double date1, date2, rm[3][3];
+
+
+   date1 = 2456165.5;
+   date2 = 0.401182685;
+
+   eraEcm06(date1, date2, rm);
+
+   vvd(rm[0][0], 0.9999952427708701137, 1e-14,
+       "eraEcm06", "rm11", status);
+   vvd(rm[0][1], -0.2829062057663042347e-2, 1e-14,
+       "eraEcm06", "rm12", status);
+   vvd(rm[0][2], -0.1229163741100017629e-2, 1e-14,
+       "eraEcm06", "rm13", status);
+   vvd(rm[1][0], 0.3084546876908653562e-2, 1e-14,
+       "eraEcm06", "rm21", status);
+   vvd(rm[1][1], 0.9174891871550392514, 1e-14,
+       "eraEcm06", "rm22", status);
+   vvd(rm[1][2], 0.3977487611849338124, 1e-14,
+       "eraEcm06", "rm23", status);
+   vvd(rm[2][0], 0.2488512951527405928e-5, 1e-14,
+       "eraEcm06", "rm31", status);
+   vvd(rm[2][1], -0.3977506604161195467, 1e-14,
+       "eraEcm06", "rm32", status);
+   vvd(rm[2][2], 0.9174935488232863071, 1e-14,
+       "eraEcm06", "rm33", status);
+
+}
+
+static void t_ee00(int *status)
+/*
+**  - - - - - - -
+**   t _ e e 0 0
+**  - - - - - - -
+**
+**  Test eraEe00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEe00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double epsa, dpsi, ee;
+
+
+   epsa =  0.4090789763356509900;
+   dpsi = -0.9630909107115582393e-5;
+
+   ee = eraEe00(2400000.5, 53736.0, epsa, dpsi);
+
+   vvd(ee, -0.8834193235367965479e-5, 1e-18, "eraEe00", "", status);
+
+}
+
+static void t_ee00a(int *status)
+/*
+**  - - - - - - - -
+**   t _ e e 0 0 a
+**  - - - - - - - -
+**
+**  Test eraEe00a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEe00a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double ee;
+
+
+   ee = eraEe00a(2400000.5, 53736.0);
+
+   vvd(ee, -0.8834192459222588227e-5, 1e-18, "eraEe00a", "", status);
+
+}
+
+static void t_ee00b(int *status)
+/*
+**  - - - - - - - -
+**   t _ e e 0 0 b
+**  - - - - - - - -
+**
+**  Test eraEe00b function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEe00b, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double ee;
+
+
+   ee = eraEe00b(2400000.5, 53736.0);
+
+   vvd(ee, -0.8835700060003032831e-5, 1e-18, "eraEe00b", "", status);
+
+}
+
+static void t_ee06a(int *status)
+/*
+**  - - - - - - - -
+**   t _ e e 0 6 a
+**  - - - - - - - -
+**
+**  Test eraEe06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEe06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double ee;
+
+
+   ee = eraEe06a(2400000.5, 53736.0);
+
+   vvd(ee, -0.8834195072043790156e-5, 1e-15, "eraEe06a", "", status);
+}
+
+static void t_eect00(int *status)
+/*
+**  - - - - - - - - -
+**   t _ e e c t 0 0
+**  - - - - - - - - -
+**
+**  Test eraEect00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEect00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double eect;
+
+
+   eect = eraEect00(2400000.5, 53736.0);
+
+   vvd(eect, 0.2046085004885125264e-8, 1e-20, "eraEect00", "", status);
+
+}
+
+static void t_eform(int *status)
+/*
+**  - - - - - - - -
+**   t _ e f o r m
+**  - - - - - - - -
+**
+**  Test eraEform function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEform, viv, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   int j;
+   double a, f;
+
+   j = eraEform(0, &a, &f);
+
+   viv(j, -1, "eraEform", "j0", status);
+
+   j = eraEform(ERFA_WGS84, &a, &f);
+
+   viv(j, 0, "eraEform", "j1", status);
+   vvd(a, 6378137.0, 1e-10, "eraEform", "a1", status);
+   vvd(f, 0.3352810664747480720e-2, 1e-18, "eraEform", "f1", status);
+
+   j = eraEform(ERFA_GRS80, &a, &f);
+
+   viv(j, 0, "eraEform", "j2", status);
+   vvd(a, 6378137.0, 1e-10, "eraEform", "a2", status);
+   vvd(f, 0.3352810681182318935e-2, 1e-18, "eraEform", "f2", status);
+
+   j = eraEform(ERFA_WGS72, &a, &f);
+
+   viv(j, 0, "eraEform", "j2", status);
+   vvd(a, 6378135.0, 1e-10, "eraEform", "a3", status);
+   vvd(f, 0.3352779454167504862e-2, 1e-18, "eraEform", "f3", status);
+
+   j = eraEform(4, &a, &f);
+   viv(j, -1, "eraEform", "j3", status);
+}
+
+static void t_eo06a(int *status)
+/*
+**  - - - - - - - -
+**   t _ e o 0 6 a
+**  - - - - - - - -
+**
+**  Test eraEo06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEo06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double eo;
+
+
+   eo = eraEo06a(2400000.5, 53736.0);
+
+   vvd(eo, -0.1332882371941833644e-2, 1e-15, "eraEo06a", "", status);
+
+}
+
+static void t_eors(int *status)
+/*
+**  - - - - - - -
+**   t _ e o r s
+**  - - - - - - -
+**
+**  Test eraEors function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEors, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rnpb[3][3], s, eo;
+
+
+   rnpb[0][0] =  0.9999989440476103608;
+   rnpb[0][1] = -0.1332881761240011518e-2;
+   rnpb[0][2] = -0.5790767434730085097e-3;
+
+   rnpb[1][0] =  0.1332858254308954453e-2;
+   rnpb[1][1] =  0.9999991109044505944;
+   rnpb[1][2] = -0.4097782710401555759e-4;
+
+   rnpb[2][0] =  0.5791308472168153320e-3;
+   rnpb[2][1] =  0.4020595661593994396e-4;
+   rnpb[2][2] =  0.9999998314954572365;
+
+   s = -0.1220040848472271978e-7;
+
+   eo = eraEors(rnpb, s);
+
+   vvd(eo, -0.1332882715130744606e-2, 1e-14, "eraEors", "", status);
+
+}
+
+static void t_epb(int *status)
+/*
+**  - - - - - -
+**   t _ e p b
+**  - - - - - -
+**
+**  Test eraEpb function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEpb, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double epb;
+
+
+   epb = eraEpb(2415019.8135, 30103.18648);
+
+   vvd(epb, 1982.418424159278580, 1e-12, "eraEpb", "", status);
+
+}
+
+static void t_epb2jd(int *status)
+/*
+**  - - - - - - - - -
+**   t _ e p b 2 j d
+**  - - - - - - - - -
+**
+**  Test eraEpb2jd function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEpb2jd, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double epb, djm0, djm;
+
+
+   epb = 1957.3;
+
+   eraEpb2jd(epb, &djm0, &djm);
+
+   vvd(djm0, 2400000.5, 1e-9, "eraEpb2jd", "djm0", status);
+   vvd(djm, 35948.1915101513, 1e-9, "eraEpb2jd", "mjd", status);
+
+}
+
+static void t_epj(int *status)
+/*
+**  - - - - - -
+**   t _ e p j
+**  - - - - - -
+**
+**  Test eraEpj function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEpj, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double epj;
+
+
+   epj = eraEpj(2451545, -7392.5);
+
+   vvd(epj, 1979.760438056125941, 1e-12, "eraEpj", "", status);
+
+}
+
+static void t_epj2jd(int *status)
+/*
+**  - - - - - - - - -
+**   t _ e p j 2 j d
+**  - - - - - - - - -
+**
+**  Test eraEpj2jd function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEpj2jd, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double epj, djm0, djm;
+
+
+   epj = 1996.8;
+
+   eraEpj2jd(epj, &djm0, &djm);
+
+   vvd(djm0, 2400000.5, 1e-9, "eraEpj2jd", "djm0", status);
+   vvd(djm,    50375.7, 1e-9, "eraEpj2jd", "mjd",  status);
+
+}
+
+static void t_epv00(int *status)
+/*
+**  - - - - - - - -
+**   t _ e p v 0 0
+**  - - - - - - - -
+**
+**  Test eraEpv00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called: eraEpv00, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double pvh[2][3], pvb[2][3];
+   int j;
+
+
+   j = eraEpv00(2400000.5, 53411.52501161, pvh, pvb);
+
+   vvd(pvh[0][0], -0.7757238809297706813, 1e-14,
+       "eraEpv00", "ph(x)", status);
+   vvd(pvh[0][1], 0.5598052241363340596, 1e-14,
+       "eraEpv00", "ph(y)", status);
+   vvd(pvh[0][2], 0.2426998466481686993, 1e-14,
+       "eraEpv00", "ph(z)", status);
+
+   vvd(pvh[1][0], -0.1091891824147313846e-1, 1e-15,
+       "eraEpv00", "vh(x)", status);
+   vvd(pvh[1][1], -0.1247187268440845008e-1, 1e-15,
+       "eraEpv00", "vh(y)", status);
+   vvd(pvh[1][2], -0.5407569418065039061e-2, 1e-15,
+       "eraEpv00", "vh(z)", status);
+
+   vvd(pvb[0][0], -0.7714104440491111971, 1e-14,
+       "eraEpv00", "pb(x)", status);
+   vvd(pvb[0][1], 0.5598412061824171323, 1e-14,
+       "eraEpv00", "pb(y)", status);
+   vvd(pvb[0][2], 0.2425996277722452400, 1e-14,
+       "eraEpv00", "pb(z)", status);
+
+   vvd(pvb[1][0], -0.1091874268116823295e-1, 1e-15,
+       "eraEpv00", "vb(x)", status);
+   vvd(pvb[1][1], -0.1246525461732861538e-1, 1e-15,
+       "eraEpv00", "vb(y)", status);
+   vvd(pvb[1][2], -0.5404773180966231279e-2, 1e-15,
+       "eraEpv00", "vb(z)", status);
+
+   viv(j, 0, "eraEpv00", "j", status);
+
+}
+
+static void t_eqec06(int *status)
+/*
+**  - - - - - - - - -
+**   t _ e q e c 0 6
+**  - - - - - - - - -
+**
+**  Test eraEqec06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEqec06, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   double date1, date2, dr, dd, dl, db;
+
+
+   date1 = 1234.5;
+   date2 = 2440000.5;
+   dr = 1.234;
+   dd = 0.987;
+
+   eraEqec06(date1, date2, dr, dd, &dl, &db);
+
+   vvd(dl, 1.342509918994654619, 1e-14, "eraEqec06", "dl", status);
+   vvd(db, 0.5926215259704608132, 1e-14, "eraEqec06", "db", status);
+
+}
+
+static void t_eqeq94(int *status)
+/*
+**  - - - - - - - - -
+**   t _ e q e q 9 4
+**  - - - - - - - - -
+**
+**  Test eraEqeq94 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEqeq94, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double eqeq;
+
+
+   eqeq = eraEqeq94(2400000.5, 41234.0);
+
+   vvd(eqeq, 0.5357758254609256894e-4, 1e-17, "eraEqeq94", "", status);
+
+}
+
+static void t_era00(int *status)
+/*
+**  - - - - - - - -
+**   t _ e r a 0 0
+**  - - - - - - - -
+**
+**  Test eraEra00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraEra00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double era00;
+
+
+   era00 = eraEra00(2400000.5, 54388.0);
+
+   vvd(era00, 0.4022837240028158102, 1e-12, "eraEra00", "", status);
+
+}
+
+static void t_fad03(int *status)
+/*
+**  - - - - - - - -
+**   t _ f a d 0 3
+**  - - - - - - - -
+**
+**  Test eraFad03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFad03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFad03(0.80), 1.946709205396925672, 1e-12,
+       "eraFad03", "", status);
+}
+
+static void t_fae03(int *status)
+/*
+**  - - - - - - - -
+**   t _ f a e 0 3
+**  - - - - - - - -
+**
+**  Test eraFae03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFae03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFae03(0.80), 1.744713738913081846, 1e-12,
+       "eraFae03", "", status);
+}
+
+static void t_faf03(int *status)
+/*
+**  - - - - - - - -
+**   t _ f a f 0 3
+**  - - - - - - - -
+**
+**  Test eraFaf03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFaf03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFaf03(0.80), 0.2597711366745499518, 1e-12,
+       "eraFaf03", "", status);
+}
+
+static void t_faju03(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f a j u 0 3
+**  - - - - - - - - -
+**
+**  Test eraFaju03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFaju03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFaju03(0.80), 5.275711665202481138, 1e-12,
+       "eraFaju03", "", status);
+}
+
+static void t_fal03(int *status)
+/*
+**  - - - - - - - -
+**   t _ f a l 0 3
+**  - - - - - - - -
+**
+**  Test eraFal03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFal03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFal03(0.80), 5.132369751108684150, 1e-12,
+       "eraFal03", "", status);
+}
+
+static void t_falp03(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f a l p 0 3
+**  - - - - - - - - -
+**
+**  Test eraFalp03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFalp03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFalp03(0.80), 6.226797973505507345, 1e-12,
+      "eraFalp03", "", status);
+}
+
+static void t_fama03(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f a m a 0 3
+**  - - - - - - - - -
+**
+**  Test eraFama03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFama03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFama03(0.80), 3.275506840277781492, 1e-12,
+       "eraFama03", "", status);
+}
+
+static void t_fame03(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f a m e 0 3
+**  - - - - - - - - -
+**
+**  Test eraFame03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFame03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFame03(0.80), 5.417338184297289661, 1e-12,
+       "eraFame03", "", status);
+}
+
+static void t_fane03(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f a n e 0 3
+**  - - - - - - - - -
+**
+**  Test eraFane03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFane03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFane03(0.80), 2.079343830860413523, 1e-12,
+       "eraFane03", "", status);
+}
+
+static void t_faom03(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f a o m 0 3
+**  - - - - - - - - -
+**
+**  Test eraFaom03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFaom03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFaom03(0.80), -5.973618440951302183, 1e-12,
+       "eraFaom03", "", status);
+}
+
+static void t_fapa03(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f a p a 0 3
+**  - - - - - - - - -
+**
+**  Test eraFapa03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFapa03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFapa03(0.80), 0.1950884762240000000e-1, 1e-12,
+       "eraFapa03", "", status);
+}
+
+static void t_fasa03(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f a s a 0 3
+**  - - - - - - - - -
+**
+**  Test eraFasa03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFasa03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFasa03(0.80), 5.371574539440827046, 1e-12,
+       "eraFasa03", "", status);
+}
+
+static void t_faur03(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f a u r 0 3
+**  - - - - - - - - -
+**
+**  Test eraFaur03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFaur03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFaur03(0.80), 5.180636450180413523, 1e-12,
+       "eraFaur03", "", status);
+}
+
+static void t_fave03(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f a v e 0 3
+**  - - - - - - - - -
+**
+**  Test eraFave03 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFave03, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraFave03(0.80), 3.424900460533758000, 1e-12,
+       "eraFave03", "", status);
+}
+
+static void t_fk52h(int *status)
+/*
+**  - - - - - - - -
+**   t _ f k 5 2 h
+**  - - - - - - - -
+**
+**  Test eraFk52h function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFk52h, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r5, d5, dr5, dd5, px5, rv5, rh, dh, drh, ddh, pxh, rvh;
+
+
+   r5  =  1.76779433;
+   d5  = -0.2917517103;
+   dr5 = -1.91851572e-7;
+   dd5 = -5.8468475e-6;
+   px5 =  0.379210;
+   rv5 = -7.6;
+
+   eraFk52h(r5, d5, dr5, dd5, px5, rv5,
+            &rh, &dh, &drh, &ddh, &pxh, &rvh);
+
+   vvd(rh, 1.767794226299947632, 1e-14,
+       "eraFk52h", "ra", status);
+   vvd(dh,  -0.2917516070530391757, 1e-14,
+       "eraFk52h", "dec", status);
+   vvd(drh, -0.19618741256057224e-6,1e-19,
+       "eraFk52h", "dr5", status);
+   vvd(ddh, -0.58459905176693911e-5, 1e-19,
+       "eraFk52h", "dd5", status);
+   vvd(pxh,  0.37921, 1e-14,
+       "eraFk52h", "px", status);
+   vvd(rvh, -7.6000000940000254, 1e-11,
+       "eraFk52h", "rv", status);
+
+}
+
+static void t_fk5hip(int *status)
+/*
+**  - - - - - - - - -
+**   t _ f k 5 h i p
+**  - - - - - - - - -
+**
+**  Test eraFk5hip function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFk5hip, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r5h[3][3], s5h[3];
+
+
+   eraFk5hip(r5h, s5h);
+
+   vvd(r5h[0][0], 0.9999999999999928638, 1e-14,
+       "eraFk5hip", "11", status);
+   vvd(r5h[0][1], 0.1110223351022919694e-6, 1e-17,
+       "eraFk5hip", "12", status);
+   vvd(r5h[0][2], 0.4411803962536558154e-7, 1e-17,
+       "eraFk5hip", "13", status);
+   vvd(r5h[1][0], -0.1110223308458746430e-6, 1e-17,
+       "eraFk5hip", "21", status);
+   vvd(r5h[1][1], 0.9999999999999891830, 1e-14,
+       "eraFk5hip", "22", status);
+   vvd(r5h[1][2], -0.9647792498984142358e-7, 1e-17,
+       "eraFk5hip", "23", status);
+   vvd(r5h[2][0], -0.4411805033656962252e-7, 1e-17,
+       "eraFk5hip", "31", status);
+   vvd(r5h[2][1], 0.9647792009175314354e-7, 1e-17,
+       "eraFk5hip", "32", status);
+   vvd(r5h[2][2], 0.9999999999999943728, 1e-14,
+       "eraFk5hip", "33", status);
+   vvd(s5h[0], -0.1454441043328607981e-8, 1e-17,
+       "eraFk5hip", "s1", status);
+   vvd(s5h[1], 0.2908882086657215962e-8, 1e-17,
+       "eraFk5hip", "s2", status);
+   vvd(s5h[2], 0.3393695767766751955e-8, 1e-17,
+       "eraFk5hip", "s3", status);
+
+}
+
+static void t_fk5hz(int *status)
+/*
+**  - - - - - - - -
+**   t _ f k 5 h z
+**  - - - - - - - -
+**
+**  Test eraFk5hz function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFk5hz, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r5, d5, rh, dh;
+
+
+   r5 =  1.76779433;
+   d5 = -0.2917517103;
+
+   eraFk5hz(r5, d5, 2400000.5, 54479.0, &rh, &dh);
+
+   vvd(rh,  1.767794191464423978, 1e-12, "eraFk5hz", "ra", status);
+   vvd(dh, -0.2917516001679884419, 1e-12, "eraFk5hz", "dec", status);
+
+}
+
+static void t_fw2m(int *status)
+/*
+**  - - - - - - -
+**   t _ f w 2 m
+**  - - - - - - -
+**
+**  Test eraFw2m function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFw2m, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double gamb, phib, psi, eps, r[3][3];
+
+
+   gamb = -0.2243387670997992368e-5;
+   phib =  0.4091014602391312982;
+   psi  = -0.9501954178013015092e-3;
+   eps  =  0.4091014316587367472;
+
+   eraFw2m(gamb, phib, psi, eps, r);
+
+   vvd(r[0][0], 0.9999995505176007047, 1e-12,
+       "eraFw2m", "11", status);
+   vvd(r[0][1], 0.8695404617348192957e-3, 1e-12,
+       "eraFw2m", "12", status);
+   vvd(r[0][2], 0.3779735201865582571e-3, 1e-12,
+       "eraFw2m", "13", status);
+
+   vvd(r[1][0], -0.8695404723772016038e-3, 1e-12,
+       "eraFw2m", "21", status);
+   vvd(r[1][1], 0.9999996219496027161, 1e-12,
+       "eraFw2m", "22", status);
+   vvd(r[1][2], -0.1361752496887100026e-6, 1e-12,
+       "eraFw2m", "23", status);
+
+   vvd(r[2][0], -0.3779734957034082790e-3, 1e-12,
+       "eraFw2m", "31", status);
+   vvd(r[2][1], -0.1924880848087615651e-6, 1e-12,
+       "eraFw2m", "32", status);
+   vvd(r[2][2], 0.9999999285679971958, 1e-12,
+       "eraFw2m", "33", status);
+
+}
+
+static void t_fw2xy(int *status)
+/*
+**  - - - - - - - -
+**   t _ f w 2 x y
+**  - - - - - - - -
+**
+**  Test eraFw2xy function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraFw2xy, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double gamb, phib, psi, eps, x, y;
+
+
+   gamb = -0.2243387670997992368e-5;
+   phib =  0.4091014602391312982;
+   psi  = -0.9501954178013015092e-3;
+   eps  =  0.4091014316587367472;
+
+   eraFw2xy(gamb, phib, psi, eps, &x, &y);
+
+   vvd(x, -0.3779734957034082790e-3, 1e-14, "eraFw2xy", "x", status);
+   vvd(y, -0.1924880848087615651e-6, 1e-14, "eraFw2xy", "y", status);
+
+}
+
+static void t_g2icrs(int *status)
+/*
+**  - - - - - - - - -
+**   t _ g 2 i c r s
+**  - - - - - - - - -
+**
+**  Test eraG2icrs function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraG2icrs, vvd
+**
+**  This revision:  2015 January 30
+*/
+{
+   double dl, db, dr, dd;
+
+
+   dl =  5.5850536063818546461558105;
+   db = -0.7853981633974483096156608;
+   eraG2icrs (dl, db, &dr, &dd);
+   vvd(dr,  5.9338074302227188048671, 1e-14, "eraG2icrs", "R", status);
+   vvd(dd, -1.1784870613579944551541, 1e-14, "eraG2icrs", "D", status);
+ }
+
+static void t_gc2gd(int *status)
+/*
+**  - - - - - - - -
+**   t _ g c 2 g d
+**  - - - - - - - -
+**
+**  Test eraGc2gd function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGc2gd, viv, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   int j;
+   double xyz[] = {2e6, 3e6, 5.244e6};
+   double e, p, h;
+
+   j = eraGc2gd(0, xyz, &e, &p, &h);
+
+   viv(j, -1, "eraGc2gd", "j0", status);
+
+   j = eraGc2gd(ERFA_WGS84, xyz, &e, &p, &h);
+
+   viv(j, 0, "eraGc2gd", "j1", status);
+   vvd(e, 0.9827937232473290680, 1e-14, "eraGc2gd", "e1", status);
+   vvd(p, 0.97160184819075459, 1e-14, "eraGc2gd", "p1", status);
+   vvd(h, 331.4172461426059892, 1e-8, "eraGc2gd", "h1", status);
+
+   j = eraGc2gd(ERFA_GRS80, xyz, &e, &p, &h);
+
+   viv(j, 0, "eraGc2gd", "j2", status);
+   vvd(e, 0.9827937232473290680, 1e-14, "eraGc2gd", "e2", status);
+   vvd(p, 0.97160184820607853, 1e-14, "eraGc2gd", "p2", status);
+   vvd(h, 331.41731754844348, 1e-8, "eraGc2gd", "h2", status);
+
+   j = eraGc2gd(ERFA_WGS72, xyz, &e, &p, &h);
+
+   viv(j, 0, "eraGc2gd", "j3", status);
+   vvd(e, 0.9827937232473290680, 1e-14, "eraGc2gd", "e3", status);
+   vvd(p, 0.9716018181101511937, 1e-14, "eraGc2gd", "p3", status);
+   vvd(h, 333.2770726130318123, 1e-8, "eraGc2gd", "h3", status);
+
+   j = eraGc2gd(4, xyz, &e, &p, &h);
+
+   viv(j, -1, "eraGc2gd", "j4", status);
+}
+
+static void t_gc2gde(int *status)
+/*
+**  - - - - - - - - -
+**   t _ g c 2 g d e
+**  - - - - - - - - -
+**
+**  Test eraGc2gde function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGc2gde, viv, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   int j;
+   double a = 6378136.0, f = 0.0033528;
+   double xyz[] = {2e6, 3e6, 5.244e6};
+   double e, p, h;
+
+   j = eraGc2gde(a, f, xyz, &e, &p, &h);
+
+   viv(j, 0, "eraGc2gde", "j", status);
+   vvd(e, 0.9827937232473290680, 1e-14, "eraGc2gde", "e", status);
+   vvd(p, 0.9716018377570411532, 1e-14, "eraGc2gde", "p", status);
+   vvd(h, 332.36862495764397, 1e-8, "eraGc2gde", "h", status);
+}
+
+static void t_gd2gc(int *status)
+/*
+**  - - - - - - - -
+**   t _ g d 2 g c
+**  - - - - - - - -
+**
+**  Test eraGd2gc function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGd2gc, viv, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   int j;
+   double e = 3.1, p = -0.5, h = 2500.0;
+   double xyz[3];
+
+   j = eraGd2gc(0, e, p, h, xyz);
+
+   viv(j, -1, "eraGd2gc", "j0", status);
+
+   j = eraGd2gc(ERFA_WGS84, e, p, h, xyz);
+
+   viv(j, 0, "eraGd2gc", "j1", status);
+   vvd(xyz[0], -5599000.5577049947, 1e-7, "eraGd2gc", "1/1", status);
+   vvd(xyz[1], 233011.67223479203, 1e-7, "eraGd2gc", "2/1", status);
+   vvd(xyz[2], -3040909.4706983363, 1e-7, "eraGd2gc", "3/1", status);
+
+   j = eraGd2gc(ERFA_GRS80, e, p, h, xyz);
+
+   viv(j, 0, "eraGd2gc", "j2", status);
+   vvd(xyz[0], -5599000.5577260984, 1e-7, "eraGd2gc", "1/2", status);
+   vvd(xyz[1], 233011.6722356702949, 1e-7, "eraGd2gc", "2/2", status);
+   vvd(xyz[2], -3040909.4706095476, 1e-7, "eraGd2gc", "3/2", status);
+
+   j = eraGd2gc(ERFA_WGS72, e, p, h, xyz);
+
+   viv(j, 0, "eraGd2gc", "j3", status);
+   vvd(xyz[0], -5598998.7626301490, 1e-7, "eraGd2gc", "1/3", status);
+   vvd(xyz[1], 233011.5975297822211, 1e-7, "eraGd2gc", "2/3", status);
+   vvd(xyz[2], -3040908.6861467111, 1e-7, "eraGd2gc", "3/3", status);
+
+   j = eraGd2gc(4, e, p, h, xyz);
+
+   viv(j, -1, "eraGd2gc", "j4", status);
+}
+
+static void t_gd2gce(int *status)
+/*
+**  - - - - - - - - -
+**   t _ g d 2 g c e
+**  - - - - - - - - -
+**
+**  Test eraGd2gce function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGd2gce, viv, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   int j;
+   double a = 6378136.0, f = 0.0033528;
+   double e = 3.1, p = -0.5, h = 2500.0;
+   double xyz[3];
+
+   j = eraGd2gce(a, f, e, p, h, xyz);
+
+   viv(j, 0, "eraGd2gce", "j", status);
+   vvd(xyz[0], -5598999.6665116328, 1e-7, "eraGd2gce", "1", status);
+   vvd(xyz[1], 233011.6351463057189, 1e-7, "eraGd2gce", "2", status);
+   vvd(xyz[2], -3040909.0517314132, 1e-7, "eraGd2gce", "3", status);
+}
+
+static void t_gmst00(int *status)
+/*
+**  - - - - - - - - -
+**   t _ g m s t 0 0
+**  - - - - - - - - -
+**
+**  Test eraGmst00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGmst00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double theta;
+
+
+   theta = eraGmst00(2400000.5, 53736.0, 2400000.5, 53736.0);
+
+   vvd(theta, 1.754174972210740592, 1e-12, "eraGmst00", "", status);
+
+}
+
+static void t_gmst06(int *status)
+/*
+**  - - - - - - - - -
+**   t _ g m s t 0 6
+**  - - - - - - - - -
+**
+**  Test eraGmst06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGmst06, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double theta;
+
+
+   theta = eraGmst06(2400000.5, 53736.0, 2400000.5, 53736.0);
+
+   vvd(theta, 1.754174971870091203, 1e-12, "eraGmst06", "", status);
+
+}
+
+static void t_gmst82(int *status)
+/*
+**  - - - - - - - - -
+**   t _ g m s t 8 2
+**  - - - - - - - - -
+**
+**  Test eraGmst82 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGmst82, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double theta;
+
+
+   theta = eraGmst82(2400000.5, 53736.0);
+
+   vvd(theta, 1.754174981860675096, 1e-12, "eraGmst82", "", status);
+
+}
+
+static void t_gst00a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ g s t 0 0 a
+**  - - - - - - - - -
+**
+**  Test eraGst00a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGst00a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double theta;
+
+
+   theta = eraGst00a(2400000.5, 53736.0, 2400000.5, 53736.0);
+
+   vvd(theta, 1.754166138018281369, 1e-12, "eraGst00a", "", status);
+
+}
+
+static void t_gst00b(int *status)
+/*
+**  - - - - - - - - -
+**   t _ g s t 0 0 b
+**  - - - - - - - - -
+**
+**  Test eraGst00b function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGst00b, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double theta;
+
+
+   theta = eraGst00b(2400000.5, 53736.0);
+
+   vvd(theta, 1.754166136510680589, 1e-12, "eraGst00b", "", status);
+
+}
+
+static void t_gst06(int *status)
+/*
+**  - - - - - - - -
+**   t _ g s t 0 6
+**  - - - - - - - -
+**
+**  Test eraGst06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGst06, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rnpb[3][3], theta;
+
+
+   rnpb[0][0] =  0.9999989440476103608;
+   rnpb[0][1] = -0.1332881761240011518e-2;
+   rnpb[0][2] = -0.5790767434730085097e-3;
+
+   rnpb[1][0] =  0.1332858254308954453e-2;
+   rnpb[1][1] =  0.9999991109044505944;
+   rnpb[1][2] = -0.4097782710401555759e-4;
+
+   rnpb[2][0] =  0.5791308472168153320e-3;
+   rnpb[2][1] =  0.4020595661593994396e-4;
+   rnpb[2][2] =  0.9999998314954572365;
+
+   theta = eraGst06(2400000.5, 53736.0, 2400000.5, 53736.0, rnpb);
+
+   vvd(theta, 1.754166138018167568, 1e-12, "eraGst06", "", status);
+
+}
+
+static void t_gst06a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ g s t 0 6 a
+**  - - - - - - - - -
+**
+**  Test eraGst06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGst06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double theta;
+
+
+   theta = eraGst06a(2400000.5, 53736.0, 2400000.5, 53736.0);
+
+   vvd(theta, 1.754166137675019159, 1e-12, "eraGst06a", "", status);
+
+}
+
+static void t_gst94(int *status)
+/*
+**  - - - - - - - -
+**   t _ g s t 9 4
+**  - - - - - - - -
+**
+**  Test eraGst94 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraGst94, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double theta;
+
+
+   theta = eraGst94(2400000.5, 53736.0);
+
+   vvd(theta, 1.754166136020645203, 1e-12, "eraGst94", "", status);
+
+}
+
+static void t_icrs2g(int *status)
+/*
+**  - - - - - - - - -
+**   t _ i c r s 2 g
+**  - - - - - - - - -
+**
+**  Test eraIcrs2g function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraIcrs2g, vvd
+**
+**  This revision:  2015 January 30
+*/
+{
+   double dr, dd, dl, db;
+
+   dr =  5.9338074302227188048671087;
+   dd = -1.1784870613579944551540570;
+   eraIcrs2g (dr, dd, &dl, &db);
+   vvd(dl,  5.5850536063818546461558, 1e-14, "eraIcrs2g", "L", status);
+   vvd(db, -0.7853981633974483096157, 1e-14, "eraIcrs2g", "B", status);
+ }
+
+static void t_h2fk5(int *status)
+/*
+**  - - - - - - - -
+**   t _ h 2 f k 5
+**  - - - - - - - -
+**
+**  Test eraH2fk5 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraH2fk5, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rh, dh, drh, ddh, pxh, rvh, r5, d5, dr5, dd5, px5, rv5;
+
+
+   rh  =  1.767794352;
+   dh  = -0.2917512594;
+   drh = -2.76413026e-6;
+   ddh = -5.92994449e-6;
+   pxh =  0.379210;
+   rvh = -7.6;
+
+   eraH2fk5(rh, dh, drh, ddh, pxh, rvh,
+            &r5, &d5, &dr5, &dd5, &px5, &rv5);
+
+   vvd(r5, 1.767794455700065506, 1e-13,
+       "eraH2fk5", "ra", status);
+   vvd(d5, -0.2917513626469638890, 1e-13,
+       "eraH2fk5", "dec", status);
+   vvd(dr5, -0.27597945024511204e-5, 1e-18,
+       "eraH2fk5", "dr5", status);
+   vvd(dd5, -0.59308014093262838e-5, 1e-18,
+       "eraH2fk5", "dd5", status);
+   vvd(px5, 0.37921, 1e-13,
+       "eraH2fk5", "px", status);
+   vvd(rv5, -7.6000001309071126, 1e-10,
+       "eraH2fk5", "rv", status);
+
+}
+
+static void t_hfk5z(int *status)
+/*
+**  - - - - - - - -
+**   t _ h f k 5 z
+**  - - - - - - - -
+**
+**  Test eraHfk5z function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraHfk5z, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rh, dh, r5, d5, dr5, dd5;
+
+
+
+   rh =  1.767794352;
+   dh = -0.2917512594;
+
+   eraHfk5z(rh, dh, 2400000.5, 54479.0, &r5, &d5, &dr5, &dd5);
+
+   vvd(r5, 1.767794490535581026, 1e-13,
+       "eraHfk5z", "ra", status);
+   vvd(d5, -0.2917513695320114258, 1e-14,
+       "eraHfk5z", "dec", status);
+   vvd(dr5, 0.4335890983539243029e-8, 1e-22,
+       "eraHfk5z", "dr5", status);
+   vvd(dd5, -0.8569648841237745902e-9, 1e-23,
+       "eraHfk5z", "dd5", status);
+
+}
+
+static void t_ir(int *status)
+/*
+**  - - - - -
+**   t _ i r
+**  - - - - -
+**
+**  Test eraIr function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraIr, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r[3][3];
+
+
+   r[0][0] = 2.0;
+   r[0][1] = 3.0;
+   r[0][2] = 2.0;
+
+   r[1][0] = 3.0;
+   r[1][1] = 2.0;
+   r[1][2] = 3.0;
+
+   r[2][0] = 3.0;
+   r[2][1] = 4.0;
+   r[2][2] = 5.0;
+
+   eraIr(r);
+
+   vvd(r[0][0], 1.0, 0.0, "eraIr", "11", status);
+   vvd(r[0][1], 0.0, 0.0, "eraIr", "12", status);
+   vvd(r[0][2], 0.0, 0.0, "eraIr", "13", status);
+
+   vvd(r[1][0], 0.0, 0.0, "eraIr", "21", status);
+   vvd(r[1][1], 1.0, 0.0, "eraIr", "22", status);
+   vvd(r[1][2], 0.0, 0.0, "eraIr", "23", status);
+
+   vvd(r[2][0], 0.0, 0.0, "eraIr", "31", status);
+   vvd(r[2][1], 0.0, 0.0, "eraIr", "32", status);
+   vvd(r[2][2], 1.0, 0.0, "eraIr", "33", status);
+
+}
+
+static void t_jd2cal(int *status)
+/*
+**  - - - - - - - - -
+**   t _ j d 2 c a l
+**  - - - - - - - - -
+**
+**  Test eraJd2cal function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraJd2cal, viv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dj1, dj2, fd;
+   int iy, im, id, j;
+
+
+   dj1 = 2400000.5;
+   dj2 = 50123.9999;
+
+   j = eraJd2cal(dj1, dj2, &iy, &im, &id, &fd);
+
+   viv(iy, 1996, "eraJd2cal", "y", status);
+   viv(im, 2, "eraJd2cal", "m", status);
+   viv(id, 10, "eraJd2cal", "d", status);
+   vvd(fd, 0.9999, 1e-7, "eraJd2cal", "fd", status);
+   viv(j, 0, "eraJd2cal", "j", status);
+
+}
+
+static void t_jdcalf(int *status)
+/*
+**  - - - - - - - - -
+**   t _ j d c a l f
+**  - - - - - - - - -
+**
+**  Test eraJdcalf function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraJdcalf, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dj1, dj2;
+   int iydmf[4], j;
+
+
+   dj1 = 2400000.5;
+   dj2 = 50123.9999;
+
+   j = eraJdcalf(4, dj1, dj2, iydmf);
+
+   viv(iydmf[0], 1996, "eraJdcalf", "y", status);
+   viv(iydmf[1], 2, "eraJdcalf", "m", status);
+   viv(iydmf[2], 10, "eraJdcalf", "d", status);
+   viv(iydmf[3], 9999, "eraJdcalf", "f", status);
+
+   viv(j, 0, "eraJdcalf", "j", status);
+
+}
+
+static void t_ld(int *status)
+/*
+**  - - - - -
+**   t _ l d
+**  - - - - -
+**
+**  Test eraLd function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraLd, vvd
+*
+**  This revision:  2013 October 2
+*/
+{
+   double bm, p[3], q[3], e[3], em, dlim, p1[3];
+
+
+   bm = 0.00028574;
+   p[0] = -0.763276255;
+   p[1] = -0.608633767;
+   p[2] = -0.216735543;
+   q[0] = -0.763276255;
+   q[1] = -0.608633767;
+   q[2] = -0.216735543;
+   e[0] = 0.76700421;
+   e[1] = 0.605629598;
+   e[2] = 0.211937094;
+   em = 8.91276983;
+   dlim = 3e-10;
+
+   eraLd(bm, p, q, e, em, dlim, p1);
+
+   vvd(p1[0], -0.7632762548968159627, 1e-12,
+               "eraLd", "1", status);
+   vvd(p1[1], -0.6086337670823762701, 1e-12,
+               "eraLd", "2", status);
+   vvd(p1[2], -0.2167355431320546947, 1e-12,
+               "eraLd", "3", status);
+
+}
+
+static void t_ldn(int *status)
+/*
+**  - - - - - -
+**   t _ l d n
+**  - - - - - -
+**
+**  Test eraLdn function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraLdn, vvd
+**
+**  This revision:  2013 October 2
+*/
+{
+   int n;
+   eraLDBODY b[3];
+   double ob[3], sc[3], sn[3];
+
+
+   n = 3;
+   b[0].bm = 0.00028574;
+   b[0].dl = 3e-10;
+   b[0].pv[0][0] = -7.81014427;
+   b[0].pv[0][1] = -5.60956681;
+   b[0].pv[0][2] = -1.98079819;
+   b[0].pv[1][0] =  0.0030723249;
+   b[0].pv[1][1] = -0.00406995477;
+   b[0].pv[1][2] = -0.00181335842;
+   b[1].bm = 0.00095435;
+   b[1].dl = 3e-9;
+   b[1].pv[0][0] =  0.738098796;
+   b[1].pv[0][1] =  4.63658692;
+   b[1].pv[0][2] =  1.9693136;
+   b[1].pv[1][0] = -0.00755816922;
+   b[1].pv[1][1] =  0.00126913722;
+   b[1].pv[1][2] =  0.000727999001;
+   b[2].bm = 1.0;
+   b[2].dl = 6e-6;
+   b[2].pv[0][0] = -0.000712174377;
+   b[2].pv[0][1] = -0.00230478303;
+   b[2].pv[0][2] = -0.00105865966;
+   b[2].pv[1][0] =  6.29235213e-6;
+   b[2].pv[1][1] = -3.30888387e-7;
+   b[2].pv[1][2] = -2.96486623e-7;
+   ob[0] =  -0.974170437;
+   ob[1] =  -0.2115201;
+   ob[2] =  -0.0917583114;
+   sc[0] =  -0.763276255;
+   sc[1] =  -0.608633767;
+   sc[2] =  -0.216735543;
+
+   eraLdn(n, b, ob, sc, sn);
+
+   vvd(sn[0], -0.7632762579693333866, 1e-12,
+               "eraLdn", "1", status);
+   vvd(sn[1], -0.6086337636093002660, 1e-12,
+               "eraLdn", "2", status);
+   vvd(sn[2], -0.2167355420646328159, 1e-12,
+               "eraLdn", "3", status);
+
+}
+
+static void t_ldsun(int *status)
+/*
+**  - - - - - - - -
+**   t _ l d s u n
+**  - - - - - - - -
+**
+**  Test eraLdsun function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraLdsun, vvd
+**
+**  This revision:  2013 October 2
+*/
+{
+   double p[3], e[3], em, p1[3];
+
+
+   p[0] = -0.763276255;
+   p[1] = -0.608633767;
+   p[2] = -0.216735543;
+   e[0] = -0.973644023;
+   e[1] = -0.20925523;
+   e[2] = -0.0907169552;
+   em = 0.999809214;
+
+   eraLdsun(p, e, em, p1);
+
+   vvd(p1[0], -0.7632762580731413169, 1e-12,
+               "eraLdsun", "1", status);
+   vvd(p1[1], -0.6086337635262647900, 1e-12,
+               "eraLdsun", "2", status);
+   vvd(p1[2], -0.2167355419322321302, 1e-12,
+               "eraLdsun", "3", status);
+
+}
+
+static void t_lteceq(int *status)
+/*
+**  - - - - - - - - -
+**   t _ l t e c e q
+**  - - - - - - - - -
+**
+**  Test eraLteceq function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraLteceq, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   double epj, dl, db, dr, dd;
+
+
+   epj = 2500.0;
+   dl = 1.5;
+   db = 0.6;
+
+   eraLteceq(epj, dl, db, &dr, &dd);
+
+   vvd(dr, 1.275156021861921167, 1e-14, "eraLteceq", "dr", status);
+   vvd(dd, 0.9966573543519204791, 1e-14, "eraLteceq", "dd", status);
+
+}
+
+static void t_ltecm(int *status)
+/*
+**  - - - - - - - -
+**   t _ l t e c m
+**  - - - - - - - -
+**
+**  Test eraLtecm function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraLtecm, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   double epj, rm[3][3];
+
+
+   epj = -3000.0;
+
+   eraLtecm(epj, rm);
+
+   vvd(rm[0][0], 0.3564105644859788825, 1e-14,
+       "eraLtecm", "rm11", status);
+   vvd(rm[0][1], 0.8530575738617682284, 1e-14,
+       "eraLtecm", "rm12", status);
+   vvd(rm[0][2], 0.3811355207795060435, 1e-14,
+       "eraLtecm", "rm13", status);
+   vvd(rm[1][0], -0.9343283469640709942, 1e-14,
+       "eraLtecm", "rm21", status);
+   vvd(rm[1][1], 0.3247830597681745976, 1e-14,
+       "eraLtecm", "rm22", status);
+   vvd(rm[1][2], 0.1467872751535940865, 1e-14,
+       "eraLtecm", "rm23", status);
+   vvd(rm[2][0], 0.1431636191201167793e-2, 1e-14,
+       "eraLtecm", "rm31", status);
+   vvd(rm[2][1], -0.4084222566960599342, 1e-14,
+       "eraLtecm", "rm32", status);
+   vvd(rm[2][2], 0.9127919865189030899, 1e-14,
+       "eraLtecm", "rm33", status);
+
+}
+
+static void t_lteqec(int *status)
+/*
+**  - - - - - - - - -
+**   t _ l t e q e c
+**  - - - - - - - - -
+**
+**  Test eraLteqec function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraLteqec, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   double epj, dr, dd, dl, db;
+
+
+   epj = -1500.0;
+   dr = 1.234;
+   dd = 0.987;
+
+   eraLteqec(epj, dr, dd, &dl, &db);
+
+   vvd(dl, 0.5039483649047114859, 1e-14, "eraLteqec", "dl", status);
+   vvd(db, 0.5848534459726224882, 1e-14, "eraLteqec", "db", status);
+
+}
+
+static void t_ltp(int *status)
+/*
+**  - - - - - -
+**   t _ l t p
+**  - - - - - -
+**
+**  Test eraLtp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraLtp, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   double epj, rp[3][3];
+
+
+   epj = 1666.666;
+
+   eraLtp(epj, rp);
+
+   vvd(rp[0][0], 0.9967044141159213819, 1e-14,
+       "eraLtp", "rp11", status);
+   vvd(rp[0][1], 0.7437801893193210840e-1, 1e-14,
+       "eraLtp", "rp12", status);
+   vvd(rp[0][2], 0.3237624409345603401e-1, 1e-14,
+       "eraLtp", "rp13", status);
+   vvd(rp[1][0], -0.7437802731819618167e-1, 1e-14,
+       "eraLtp", "rp21", status);
+   vvd(rp[1][1], 0.9972293894454533070, 1e-14,
+       "eraLtp", "rp22", status);
+   vvd(rp[1][2], -0.1205768842723593346e-2, 1e-14,
+       "eraLtp", "rp23", status);
+   vvd(rp[2][0], -0.3237622482766575399e-1, 1e-14,
+       "eraLtp", "rp31", status);
+   vvd(rp[2][1], -0.1206286039697609008e-2, 1e-14,
+       "eraLtp", "rp32", status);
+   vvd(rp[2][2], 0.9994750246704010914, 1e-14,
+       "eraLtp", "rp33", status);
+
+}
+
+static void t_ltpb(int *status)
+/*
+**  - - - - - - -
+**   t _ l t p b
+**  - - - - - - -
+**
+**  Test eraLtpb function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraLtpb, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   double epj, rpb[3][3];
+
+
+   epj = 1666.666;
+
+   eraLtpb(epj, rpb);
+
+   vvd(rpb[0][0], 0.9967044167723271851, 1e-14,
+       "eraLtpb", "rpb11", status);
+   vvd(rpb[0][1], 0.7437794731203340345e-1, 1e-14,
+       "eraLtpb", "rpb12", status);
+   vvd(rpb[0][2], 0.3237632684841625547e-1, 1e-14,
+       "eraLtpb", "rpb13", status);
+   vvd(rpb[1][0], -0.7437795663437177152e-1, 1e-14,
+       "eraLtpb", "rpb21", status);
+   vvd(rpb[1][1], 0.9972293947500013666, 1e-14,
+       "eraLtpb", "rpb22", status);
+   vvd(rpb[1][2], -0.1205741865911243235e-2, 1e-14,
+       "eraLtpb", "rpb23", status);
+   vvd(rpb[2][0], -0.3237630543224664992e-1, 1e-14,
+       "eraLtpb", "rpb31", status);
+   vvd(rpb[2][1], -0.1206316791076485295e-2, 1e-14,
+       "eraLtpb", "rpb32", status);
+   vvd(rpb[2][2], 0.9994750220222438819, 1e-14,
+       "eraLtpb", "rpb33", status);
+
+}
+
+static void t_ltpecl(int *status)
+/*
+**  - - - - - - - - -
+**   t _ l t p e c l
+**  - - - - - - - - -
+**
+**  Test eraLtpecl function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraLtpecl, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   double epj, vec[3];
+
+
+   epj = -1500.0;
+
+   eraLtpecl(epj, vec);
+
+   vvd(vec[0], 0.4768625676477096525e-3, 1e-14,
+       "eraLtpecl", "vec1", status);
+   vvd(vec[1], -0.4052259533091875112, 1e-14,
+       "eraLtpecl", "vec2", status);
+   vvd(vec[2], 0.9142164401096448012, 1e-14,
+       "eraLtpecl", "vec3", status);
+
+}
+
+static void t_ltpequ(int *status)
+/*
+**  - - - - - - - - -
+**   t _ l t p e q u
+**  - - - - - - - - -
+**
+**  Test eraLtpequ function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraLtpequ, vvd
+**
+**  This revision:  2016 March 12
+*/
+{
+   double epj, veq[3];
+
+
+   epj = -2500.0;
+
+   eraLtpequ(epj, veq);
+
+   vvd(veq[0], -0.3586652560237326659, 1e-14,
+       "eraLtpequ", "veq1", status);
+   vvd(veq[1], -0.1996978910771128475, 1e-14,
+       "eraLtpequ", "veq2", status);
+   vvd(veq[2], 0.9118552442250819624, 1e-14,
+       "eraLtpequ", "veq3", status);
+
+}
+
+static void t_num00a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ n u m 0 0 a
+**  - - - - - - - - -
+**
+**  Test eraNum00a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraNum00a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rmatn[3][3];
+
+
+   eraNum00a(2400000.5, 53736.0, rmatn);
+
+   vvd(rmatn[0][0], 0.9999999999536227949, 1e-12,
+       "eraNum00a", "11", status);
+   vvd(rmatn[0][1], 0.8836238544090873336e-5, 1e-12,
+       "eraNum00a", "12", status);
+   vvd(rmatn[0][2], 0.3830835237722400669e-5, 1e-12,
+       "eraNum00a", "13", status);
+
+   vvd(rmatn[1][0], -0.8836082880798569274e-5, 1e-12,
+       "eraNum00a", "21", status);
+   vvd(rmatn[1][1], 0.9999999991354655028, 1e-12,
+       "eraNum00a", "22", status);
+   vvd(rmatn[1][2], -0.4063240865362499850e-4, 1e-12,
+       "eraNum00a", "23", status);
+
+   vvd(rmatn[2][0], -0.3831194272065995866e-5, 1e-12,
+       "eraNum00a", "31", status);
+   vvd(rmatn[2][1], 0.4063237480216291775e-4, 1e-12,
+       "eraNum00a", "32", status);
+   vvd(rmatn[2][2], 0.9999999991671660338, 1e-12,
+       "eraNum00a", "33", status);
+
+}
+
+static void t_num00b(int *status)
+/*
+**  - - - - - - - - -
+**   t _ n u m 0 0 b
+**  - - - - - - - - -
+**
+**  Test eraNum00b function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraNum00b, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+    double rmatn[3][3];
+
+    eraNum00b(2400000.5, 53736, rmatn);
+
+   vvd(rmatn[0][0], 0.9999999999536069682, 1e-12,
+       "eraNum00b", "11", status);
+   vvd(rmatn[0][1], 0.8837746144871248011e-5, 1e-12,
+       "eraNum00b", "12", status);
+   vvd(rmatn[0][2], 0.3831488838252202945e-5, 1e-12,
+       "eraNum00b", "13", status);
+
+   vvd(rmatn[1][0], -0.8837590456632304720e-5, 1e-12,
+       "eraNum00b", "21", status);
+   vvd(rmatn[1][1], 0.9999999991354692733, 1e-12,
+       "eraNum00b", "22", status);
+   vvd(rmatn[1][2], -0.4063198798559591654e-4, 1e-12,
+       "eraNum00b", "23", status);
+
+   vvd(rmatn[2][0], -0.3831847930134941271e-5, 1e-12,
+       "eraNum00b", "31", status);
+   vvd(rmatn[2][1], 0.4063195412258168380e-4, 1e-12,
+       "eraNum00b", "32", status);
+   vvd(rmatn[2][2], 0.9999999991671806225, 1e-12,
+       "eraNum00b", "33", status);
+
+}
+
+static void t_num06a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ n u m 0 6 a
+**  - - - - - - - - -
+**
+**  Test eraNum06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraNum06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+    double rmatn[3][3];
+
+    eraNum06a(2400000.5, 53736, rmatn);
+
+   vvd(rmatn[0][0], 0.9999999999536227668, 1e-12,
+       "eraNum06a", "11", status);
+   vvd(rmatn[0][1], 0.8836241998111535233e-5, 1e-12,
+       "eraNum06a", "12", status);
+   vvd(rmatn[0][2], 0.3830834608415287707e-5, 1e-12,
+       "eraNum06a", "13", status);
+
+   vvd(rmatn[1][0], -0.8836086334870740138e-5, 1e-12,
+       "eraNum06a", "21", status);
+   vvd(rmatn[1][1], 0.9999999991354657474, 1e-12,
+       "eraNum06a", "22", status);
+   vvd(rmatn[1][2], -0.4063240188248455065e-4, 1e-12,
+       "eraNum06a", "23", status);
+
+   vvd(rmatn[2][0], -0.3831193642839398128e-5, 1e-12,
+       "eraNum06a", "31", status);
+   vvd(rmatn[2][1], 0.4063236803101479770e-4, 1e-12,
+       "eraNum06a", "32", status);
+   vvd(rmatn[2][2], 0.9999999991671663114, 1e-12,
+       "eraNum06a", "33", status);
+
+}
+
+static void t_numat(int *status)
+/*
+**  - - - - - - - -
+**   t _ n u m a t
+**  - - - - - - - -
+**
+**  Test eraNumat function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraNumat, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double epsa, dpsi, deps, rmatn[3][3];
+
+
+   epsa =  0.4090789763356509900;
+   dpsi = -0.9630909107115582393e-5;
+   deps =  0.4063239174001678826e-4;
+
+   eraNumat(epsa, dpsi, deps, rmatn);
+
+   vvd(rmatn[0][0], 0.9999999999536227949, 1e-12,
+       "eraNumat", "11", status);
+   vvd(rmatn[0][1], 0.8836239320236250577e-5, 1e-12,
+       "eraNumat", "12", status);
+   vvd(rmatn[0][2], 0.3830833447458251908e-5, 1e-12,
+       "eraNumat", "13", status);
+
+   vvd(rmatn[1][0], -0.8836083657016688588e-5, 1e-12,
+       "eraNumat", "21", status);
+   vvd(rmatn[1][1], 0.9999999991354654959, 1e-12,
+       "eraNumat", "22", status);
+   vvd(rmatn[1][2], -0.4063240865361857698e-4, 1e-12,
+       "eraNumat", "23", status);
+
+   vvd(rmatn[2][0], -0.3831192481833385226e-5, 1e-12,
+       "eraNumat", "31", status);
+   vvd(rmatn[2][1], 0.4063237480216934159e-4, 1e-12,
+       "eraNumat", "32", status);
+   vvd(rmatn[2][2], 0.9999999991671660407, 1e-12,
+       "eraNumat", "33", status);
+
+}
+
+static void t_nut00a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ n u t 0 0 a
+**  - - - - - - - - -
+**
+**  Test eraNut00a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraNut00a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsi, deps;
+
+
+   eraNut00a(2400000.5, 53736.0, &dpsi, &deps);
+
+   vvd(dpsi, -0.9630909107115518431e-5, 1e-13,
+       "eraNut00a", "dpsi", status);
+   vvd(deps,  0.4063239174001678710e-4, 1e-13,
+       "eraNut00a", "deps", status);
+
+}
+
+static void t_nut00b(int *status)
+/*
+**  - - - - - - - - -
+**   t _ n u t 0 0 b
+**  - - - - - - - - -
+**
+**  Test eraNut00b function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraNut00b, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsi, deps;
+
+
+   eraNut00b(2400000.5, 53736.0, &dpsi, &deps);
+
+   vvd(dpsi, -0.9632552291148362783e-5, 1e-13,
+       "eraNut00b", "dpsi", status);
+   vvd(deps,  0.4063197106621159367e-4, 1e-13,
+       "eraNut00b", "deps", status);
+
+}
+
+static void t_nut06a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ n u t 0 6 a
+**  - - - - - - - - -
+**
+**  Test eraNut06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraNut06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsi, deps;
+
+
+   eraNut06a(2400000.5, 53736.0, &dpsi, &deps);
+
+   vvd(dpsi, -0.9630912025820308797e-5, 1e-13,
+       "eraNut06a", "dpsi", status);
+   vvd(deps,  0.4063238496887249798e-4, 1e-13,
+       "eraNut06a", "deps", status);
+
+}
+
+static void t_nut80(int *status)
+/*
+**  - - - - - - - -
+**   t _ n u t 8 0
+**  - - - - - - - -
+**
+**  Test eraNut80 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraNut80, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsi, deps;
+
+
+   eraNut80(2400000.5, 53736.0, &dpsi, &deps);
+
+   vvd(dpsi, -0.9643658353226563966e-5, 1e-13,
+       "eraNut80", "dpsi", status);
+   vvd(deps,  0.4060051006879713322e-4, 1e-13,
+       "eraNut80", "deps", status);
+
+}
+
+static void t_nutm80(int *status)
+/*
+**  - - - - - - - - -
+**   t _ n u t m 8 0
+**  - - - - - - - - -
+**
+**  Test eraNutm80 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraNutm80, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rmatn[3][3];
+
+
+   eraNutm80(2400000.5, 53736.0, rmatn);
+
+   vvd(rmatn[0][0], 0.9999999999534999268, 1e-12,
+      "eraNutm80", "11", status);
+   vvd(rmatn[0][1], 0.8847935789636432161e-5, 1e-12,
+      "eraNutm80", "12", status);
+   vvd(rmatn[0][2], 0.3835906502164019142e-5, 1e-12,
+      "eraNutm80", "13", status);
+
+   vvd(rmatn[1][0], -0.8847780042583435924e-5, 1e-12,
+      "eraNutm80", "21", status);
+   vvd(rmatn[1][1], 0.9999999991366569963, 1e-12,
+      "eraNutm80", "22", status);
+   vvd(rmatn[1][2], -0.4060052702727130809e-4, 1e-12,
+      "eraNutm80", "23", status);
+
+   vvd(rmatn[2][0], -0.3836265729708478796e-5, 1e-12,
+      "eraNutm80", "31", status);
+   vvd(rmatn[2][1], 0.4060049308612638555e-4, 1e-12,
+      "eraNutm80", "32", status);
+   vvd(rmatn[2][2], 0.9999999991684415129, 1e-12,
+      "eraNutm80", "33", status);
+
+}
+
+static void t_obl06(int *status)
+/*
+**  - - - - - - - -
+**   t _ o b l 0 6
+**  - - - - - - - -
+**
+**  Test eraObl06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraObl06, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraObl06(2400000.5, 54388.0), 0.4090749229387258204, 1e-14,
+       "eraObl06", "", status);
+}
+
+static void t_obl80(int *status)
+/*
+**  - - - - - - - -
+**   t _ o b l 8 0
+**  - - - - - - - -
+**
+**  Test eraObl80 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraObl80, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double eps0;
+
+
+   eps0 = eraObl80(2400000.5, 54388.0);
+
+   vvd(eps0, 0.4090751347643816218, 1e-14, "eraObl80", "", status);
+
+}
+
+static void t_p06e(int *status)
+/*
+**  - - - - - - -
+**   t _ p 0 6 e
+**  - - - - - - -
+**
+**  Test eraP06e function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraP06e, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+    double eps0, psia, oma, bpa, bqa, pia, bpia,
+           epsa, chia, za, zetaa, thetaa, pa, gam, phi, psi;
+
+
+   eraP06e(2400000.5, 52541.0, &eps0, &psia, &oma, &bpa,
+           &bqa, &pia, &bpia, &epsa, &chia, &za,
+           &zetaa, &thetaa, &pa, &gam, &phi, &psi);
+
+   vvd(eps0, 0.4090926006005828715, 1e-14,
+       "eraP06e", "eps0", status);
+   vvd(psia, 0.6664369630191613431e-3, 1e-14,
+       "eraP06e", "psia", status);
+   vvd(oma , 0.4090925973783255982, 1e-14,
+       "eraP06e", "oma", status);
+   vvd(bpa, 0.5561149371265209445e-6, 1e-14,
+       "eraP06e", "bpa", status);
+   vvd(bqa, -0.6191517193290621270e-5, 1e-14,
+       "eraP06e", "bqa", status);
+   vvd(pia, 0.6216441751884382923e-5, 1e-14,
+       "eraP06e", "pia", status);
+   vvd(bpia, 3.052014180023779882, 1e-14,
+       "eraP06e", "bpia", status);
+   vvd(epsa, 0.4090864054922431688, 1e-14,
+       "eraP06e", "epsa", status);
+   vvd(chia, 0.1387703379530915364e-5, 1e-14,
+       "eraP06e", "chia", status);
+   vvd(za, 0.2921789846651790546e-3, 1e-14,
+       "eraP06e", "za", status);
+   vvd(zetaa, 0.3178773290332009310e-3, 1e-14,
+       "eraP06e", "zetaa", status);
+   vvd(thetaa, 0.2650932701657497181e-3, 1e-14,
+       "eraP06e", "thetaa", status);
+   vvd(pa, 0.6651637681381016344e-3, 1e-14,
+       "eraP06e", "pa", status);
+   vvd(gam, 0.1398077115963754987e-5, 1e-14,
+       "eraP06e", "gam", status);
+   vvd(phi, 0.4090864090837462602, 1e-14,
+       "eraP06e", "phi", status);
+   vvd(psi, 0.6664464807480920325e-3, 1e-14,
+       "eraP06e", "psi", status);
+
+}
+
+static void t_p2pv(int *status)
+/*
+**  - - - - - - -
+**   t _ p 2 p v
+**  - - - - - - -
+**
+**  Test eraP2pv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraP2pv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double p[3], pv[2][3];
+
+
+   p[0] = 0.25;
+   p[1] = 1.2;
+   p[2] = 3.0;
+
+   pv[0][0] =  0.3;
+   pv[0][1] =  1.2;
+   pv[0][2] = -2.5;
+
+   pv[1][0] = -0.5;
+   pv[1][1] =  3.1;
+   pv[1][2] =  0.9;
+
+   eraP2pv(p, pv);
+
+   vvd(pv[0][0], 0.25, 0.0, "eraP2pv", "p1", status);
+   vvd(pv[0][1], 1.2,  0.0, "eraP2pv", "p2", status);
+   vvd(pv[0][2], 3.0,  0.0, "eraP2pv", "p3", status);
+
+   vvd(pv[1][0], 0.0,  0.0, "eraP2pv", "v1", status);
+   vvd(pv[1][1], 0.0,  0.0, "eraP2pv", "v2", status);
+   vvd(pv[1][2], 0.0,  0.0, "eraP2pv", "v3", status);
+
+}
+
+static void t_p2s(int *status)
+/*
+**  - - - - - -
+**   t _ p 2 s
+**  - - - - - -
+**
+**  Test eraP2s function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraP2s, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double p[3], theta, phi, r;
+
+
+   p[0] = 100.0;
+   p[1] = -50.0;
+   p[2] =  25.0;
+
+   eraP2s(p, &theta, &phi, &r);
+
+   vvd(theta, -0.4636476090008061162, 1e-12, "eraP2s", "theta", status);
+   vvd(phi, 0.2199879773954594463, 1e-12, "eraP2s", "phi", status);
+   vvd(r, 114.5643923738960002, 1e-9, "eraP2s", "r", status);
+
+}
+
+static void t_pap(int *status)
+/*
+**  - - - - - -
+**   t _ p a p
+**  - - - - - -
+**
+**  Test eraPap function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPap, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[3], b[3], theta;
+
+
+   a[0] =  1.0;
+   a[1] =  0.1;
+   a[2] =  0.2;
+
+   b[0] = -3.0;
+   b[1] = 1e-3;
+   b[2] =  0.2;
+
+   theta = eraPap(a, b);
+
+   vvd(theta, 0.3671514267841113674, 1e-12, "eraPap", "", status);
+
+}
+
+static void t_pas(int *status)
+/*
+**  - - - - - -
+**   t _ p a s
+**  - - - - - -
+**
+**  Test eraPas function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPas, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double al, ap, bl, bp, theta;
+
+
+   al =  1.0;
+   ap =  0.1;
+   bl =  0.2;
+   bp = -1.0;
+
+   theta = eraPas(al, ap, bl, bp);
+
+   vvd(theta, -2.724544922932270424, 1e-12, "eraPas", "", status);
+
+}
+
+static void t_pb06(int *status)
+/*
+**  - - - - - - -
+**   t _ p b 0 6
+**  - - - - - - -
+**
+**  Test eraPb06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPb06, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double bzeta, bz, btheta;
+
+
+   eraPb06(2400000.5, 50123.9999, &bzeta, &bz, &btheta);
+
+   vvd(bzeta, -0.5092634016326478238e-3, 1e-12,
+       "eraPb06", "bzeta", status);
+   vvd(bz, -0.3602772060566044413e-3, 1e-12,
+       "eraPb06", "bz", status);
+   vvd(btheta, -0.3779735537167811177e-3, 1e-12,
+       "eraPb06", "btheta", status);
+
+}
+
+static void t_pdp(int *status)
+/*
+**  - - - - - -
+**   t _ p d p
+**  - - - - - -
+**
+**  Test eraPdp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPdp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[3], b[3], adb;
+
+
+   a[0] = 2.0;
+   a[1] = 2.0;
+   a[2] = 3.0;
+
+   b[0] = 1.0;
+   b[1] = 3.0;
+   b[2] = 4.0;
+
+   adb = eraPdp(a, b);
+
+   vvd(adb, 20, 1e-12, "eraPdp", "", status);
+
+}
+
+static void t_pfw06(int *status)
+/*
+**  - - - - - - - -
+**   t _ p f w 0 6
+**  - - - - - - - -
+**
+**  Test eraPfw06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPfw06, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double gamb, phib, psib, epsa;
+
+
+   eraPfw06(2400000.5, 50123.9999, &gamb, &phib, &psib, &epsa);
+
+   vvd(gamb, -0.2243387670997995690e-5, 1e-16,
+       "eraPfw06", "gamb", status);
+   vvd(phib,  0.4091014602391312808, 1e-12,
+       "eraPfw06", "phib", status);
+   vvd(psib, -0.9501954178013031895e-3, 1e-14,
+       "eraPfw06", "psib", status);
+   vvd(epsa,  0.4091014316587367491, 1e-12,
+       "eraPfw06", "epsa", status);
+
+}
+
+static void t_plan94(int *status)
+/*
+**  - - - - - - - - -
+**   t _ p l a n 9 4
+**  - - - - - - - - -
+**
+**  Test eraPlan94 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPlan94, vvd, viv
+**
+**  This revision:  2013 October 2
+*/
+{
+   double pv[2][3];
+   int j;
+
+
+   j = eraPlan94(2400000.5, 1e6, 0, pv);
+
+   vvd(pv[0][0], 0.0, 0.0, "eraPlan94", "x 1", status);
+   vvd(pv[0][1], 0.0, 0.0, "eraPlan94", "y 1", status);
+   vvd(pv[0][2], 0.0, 0.0, "eraPlan94", "z 1", status);
+
+   vvd(pv[1][0], 0.0, 0.0, "eraPlan94", "xd 1", status);
+   vvd(pv[1][1], 0.0, 0.0, "eraPlan94", "yd 1", status);
+   vvd(pv[1][2], 0.0, 0.0, "eraPlan94", "zd 1", status);
+
+   viv(j, -1, "eraPlan94", "j 1", status);
+
+   j = eraPlan94(2400000.5, 1e6, 10, pv);
+
+   viv(j, -1, "eraPlan94", "j 2", status);
+
+   j = eraPlan94(2400000.5, -320000, 3, pv);
+
+   vvd(pv[0][0], 0.9308038666832975759, 1e-11,
+       "eraPlan94", "x 3", status);
+   vvd(pv[0][1], 0.3258319040261346000, 1e-11,
+       "eraPlan94", "y 3", status);
+   vvd(pv[0][2], 0.1422794544481140560, 1e-11,
+       "eraPlan94", "z 3", status);
+
+   vvd(pv[1][0], -0.6429458958255170006e-2, 1e-11,
+       "eraPlan94", "xd 3", status);
+   vvd(pv[1][1], 0.1468570657704237764e-1, 1e-11,
+       "eraPlan94", "yd 3", status);
+   vvd(pv[1][2], 0.6406996426270981189e-2, 1e-11,
+       "eraPlan94", "zd 3", status);
+
+   viv(j, 1, "eraPlan94", "j 3", status);
+
+   j = eraPlan94(2400000.5, 43999.9, 1, pv);
+
+   vvd(pv[0][0], 0.2945293959257430832, 1e-11,
+       "eraPlan94", "x 4", status);
+   vvd(pv[0][1], -0.2452204176601049596, 1e-11,
+       "eraPlan94", "y 4", status);
+   vvd(pv[0][2], -0.1615427700571978153, 1e-11,
+       "eraPlan94", "z 4", status);
+
+   vvd(pv[1][0], 0.1413867871404614441e-1, 1e-11,
+       "eraPlan94", "xd 4", status);
+   vvd(pv[1][1], 0.1946548301104706582e-1, 1e-11,
+       "eraPlan94", "yd 4", status);
+   vvd(pv[1][2], 0.8929809783898904786e-2, 1e-11,
+       "eraPlan94", "zd 4", status);
+
+   viv(j, 0, "eraPlan94", "j 4", status);
+
+}
+
+static void t_pmat00(int *status)
+/*
+**  - - - - - - - - -
+**   t _ p m a t 0 0
+**  - - - - - - - - -
+**
+**  Test eraPmat00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPmat00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rbp[3][3];
+
+
+   eraPmat00(2400000.5, 50123.9999, rbp);
+
+   vvd(rbp[0][0], 0.9999995505175087260, 1e-12,
+       "eraPmat00", "11", status);
+   vvd(rbp[0][1], 0.8695405883617884705e-3, 1e-14,
+       "eraPmat00", "12", status);
+   vvd(rbp[0][2], 0.3779734722239007105e-3, 1e-14,
+       "eraPmat00", "13", status);
+
+   vvd(rbp[1][0], -0.8695405990410863719e-3, 1e-14,
+       "eraPmat00", "21", status);
+   vvd(rbp[1][1], 0.9999996219494925900, 1e-12,
+       "eraPmat00", "22", status);
+   vvd(rbp[1][2], -0.1360775820404982209e-6, 1e-14,
+       "eraPmat00", "23", status);
+
+   vvd(rbp[2][0], -0.3779734476558184991e-3, 1e-14,
+       "eraPmat00", "31", status);
+   vvd(rbp[2][1], -0.1925857585832024058e-6, 1e-14,
+       "eraPmat00", "32", status);
+   vvd(rbp[2][2], 0.9999999285680153377, 1e-12,
+       "eraPmat00", "33", status);
+
+}
+
+static void t_pmat06(int *status)
+/*
+**  - - - - - - - - -
+**   t _ p m a t 0 6
+**  - - - - - - - - -
+**
+**  Test eraPmat06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPmat06, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rbp[3][3];
+
+
+   eraPmat06(2400000.5, 50123.9999, rbp);
+
+   vvd(rbp[0][0], 0.9999995505176007047, 1e-12,
+       "eraPmat06", "11", status);
+   vvd(rbp[0][1], 0.8695404617348208406e-3, 1e-14,
+       "eraPmat06", "12", status);
+   vvd(rbp[0][2], 0.3779735201865589104e-3, 1e-14,
+       "eraPmat06", "13", status);
+
+   vvd(rbp[1][0], -0.8695404723772031414e-3, 1e-14,
+       "eraPmat06", "21", status);
+   vvd(rbp[1][1], 0.9999996219496027161, 1e-12,
+       "eraPmat06", "22", status);
+   vvd(rbp[1][2], -0.1361752497080270143e-6, 1e-14,
+       "eraPmat06", "23", status);
+
+   vvd(rbp[2][0], -0.3779734957034089490e-3, 1e-14,
+       "eraPmat06", "31", status);
+   vvd(rbp[2][1], -0.1924880847894457113e-6, 1e-14,
+       "eraPmat06", "32", status);
+   vvd(rbp[2][2], 0.9999999285679971958, 1e-12,
+       "eraPmat06", "33", status);
+
+}
+
+static void t_pmat76(int *status)
+/*
+**  - - - - - - - - -
+**   t _ p m a t 7 6
+**  - - - - - - - - -
+**
+**  Test eraPmat76 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPmat76, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rmatp[3][3];
+
+
+   eraPmat76(2400000.5, 50123.9999, rmatp);
+
+   vvd(rmatp[0][0], 0.9999995504328350733, 1e-12,
+       "eraPmat76", "11", status);
+   vvd(rmatp[0][1], 0.8696632209480960785e-3, 1e-14,
+       "eraPmat76", "12", status);
+   vvd(rmatp[0][2], 0.3779153474959888345e-3, 1e-14,
+       "eraPmat76", "13", status);
+
+   vvd(rmatp[1][0], -0.8696632209485112192e-3, 1e-14,
+       "eraPmat76", "21", status);
+   vvd(rmatp[1][1], 0.9999996218428560614, 1e-12,
+       "eraPmat76", "22", status);
+   vvd(rmatp[1][2], -0.1643284776111886407e-6, 1e-14,
+       "eraPmat76", "23", status);
+
+   vvd(rmatp[2][0], -0.3779153474950335077e-3, 1e-14,
+       "eraPmat76", "31", status);
+   vvd(rmatp[2][1], -0.1643306746147366896e-6, 1e-14,
+       "eraPmat76", "32", status);
+   vvd(rmatp[2][2], 0.9999999285899790119, 1e-12,
+       "eraPmat76", "33", status);
+
+}
+
+static void t_pm(int *status)
+/*
+**  - - - - -
+**   t _ p m
+**  - - - - -
+**
+**  Test eraPm function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPm, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double p[3], r;
+
+
+   p[0] =  0.3;
+   p[1] =  1.2;
+   p[2] = -2.5;
+
+   r = eraPm(p);
+
+   vvd(r, 2.789265136196270604, 1e-12, "eraPm", "", status);
+
+}
+
+static void t_pmp(int *status)
+/*
+**  - - - - - -
+**   t _ p m p
+**  - - - - - -
+**
+**  Test eraPmp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPmp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[3], b[3], amb[3];
+
+
+   a[0] = 2.0;
+   a[1] = 2.0;
+   a[2] = 3.0;
+
+   b[0] = 1.0;
+   b[1] = 3.0;
+   b[2] = 4.0;
+
+   eraPmp(a, b, amb);
+
+   vvd(amb[0],  1.0, 1e-12, "eraPmp", "0", status);
+   vvd(amb[1], -1.0, 1e-12, "eraPmp", "1", status);
+   vvd(amb[2], -1.0, 1e-12, "eraPmp", "2", status);
+
+}
+
+static void t_pmpx(int *status)
+/*
+**  - - - - - - -
+**   t _ p m p x
+**  - - - - - - -
+**
+**  Test eraPmpx function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPmpx, vvd
+**
+**  This revision:  2013 October 2
+*/
+{
+   double rc, dc, pr, pd, px, rv, pmt, pob[3], pco[3];
+
+
+   rc = 1.234;
+   dc = 0.789;
+   pr = 1e-5;
+   pd = -2e-5;
+   px = 1e-2;
+   rv = 10.0;
+   pmt = 8.75;
+   pob[0] = 0.9;
+   pob[1] = 0.4;
+   pob[2] = 0.1;
+
+   eraPmpx(rc, dc, pr, pd, px, rv, pmt, pob, pco);
+
+   vvd(pco[0], 0.2328137623960308440, 1e-12,
+               "eraPmpx", "1", status);
+   vvd(pco[1], 0.6651097085397855317, 1e-12,
+               "eraPmpx", "2", status);
+   vvd(pco[2], 0.7095257765896359847, 1e-12,
+               "eraPmpx", "3", status);
+
+}
+
+static void t_pmsafe(int *status)
+/*
+**  - - - - - - - - -
+**   t _ p m s a f e
+**  - - - - - - - - -
+**
+**  Test eraPmsafe function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPmsafe, vvd, viv
+**
+**  This revision:  2013 October 2
+*/
+{
+   int j;
+   double ra1, dec1, pmr1, pmd1, px1, rv1, ep1a, ep1b, ep2a, ep2b,
+          ra2, dec2, pmr2, pmd2, px2, rv2;
+
+
+   ra1 = 1.234;
+   dec1 = 0.789;
+   pmr1 = 1e-5;
+   pmd1 = -2e-5;
+   px1 = 1e-2;
+   rv1 = 10.0;
+   ep1a = 2400000.5;
+   ep1b = 48348.5625;
+   ep2a = 2400000.5;
+   ep2b = 51544.5;
+
+   j = eraPmsafe(ra1, dec1, pmr1, pmd1, px1, rv1,
+                 ep1a, ep1b, ep2a, ep2b,
+                 &ra2, &dec2, &pmr2, &pmd2, &px2, &rv2);
+
+   vvd(ra2, 1.234087484501017061, 1e-12,
+            "eraPmsafe", "ra2", status);
+   vvd(dec2, 0.7888249982450468574, 1e-12,
+            "eraPmsafe", "dec2", status);
+   vvd(pmr2, 0.9996457663586073988e-5, 1e-12,
+             "eraPmsafe", "pmr2", status);
+   vvd(pmd2, -0.2000040085106737816e-4, 1e-16,
+             "eraPmsafe", "pmd2", status);
+   vvd(px2, 0.9999997295356765185e-2, 1e-12,
+            "eraPmsafe", "px2", status);
+   vvd(rv2, 10.38468380113917014, 1e-10,
+            "eraPmsafe", "rv2", status);
+   viv ( j, 0, "eraPmsafe", "j", status);
+
+}
+
+static void t_pn(int *status)
+/*
+**  - - - - -
+**   t _ p n
+**  - - - - -
+**
+**  Test eraPn function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPn, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double p[3], r, u[3];
+
+
+   p[0] =  0.3;
+   p[1] =  1.2;
+   p[2] = -2.5;
+
+   eraPn(p, &r, u);
+
+   vvd(r, 2.789265136196270604, 1e-12, "eraPn", "r", status);
+
+   vvd(u[0], 0.1075552109073112058, 1e-12, "eraPn", "u1", status);
+   vvd(u[1], 0.4302208436292448232, 1e-12, "eraPn", "u2", status);
+   vvd(u[2], -0.8962934242275933816, 1e-12, "eraPn", "u3", status);
+
+}
+
+static void t_pn00(int *status)
+/*
+**  - - - - - - -
+**   t _ p n 0 0
+**  - - - - - - -
+**
+**  Test eraPn00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPn00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsi, deps, epsa,
+          rb[3][3], rp[3][3], rbp[3][3], rn[3][3], rbpn[3][3];
+
+
+   dpsi = -0.9632552291149335877e-5;
+   deps =  0.4063197106621141414e-4;
+
+   eraPn00(2400000.5, 53736.0, dpsi, deps,
+           &epsa, rb, rp, rbp, rn, rbpn);
+
+   vvd(epsa, 0.4090791789404229916, 1e-12, "eraPn00", "epsa", status);
+
+   vvd(rb[0][0], 0.9999999999999942498, 1e-12,
+       "eraPn00", "rb11", status);
+   vvd(rb[0][1], -0.7078279744199196626e-7, 1e-18,
+       "eraPn00", "rb12", status);
+   vvd(rb[0][2], 0.8056217146976134152e-7, 1e-18,
+       "eraPn00", "rb13", status);
+
+   vvd(rb[1][0], 0.7078279477857337206e-7, 1e-18,
+       "eraPn00", "rb21", status);
+   vvd(rb[1][1], 0.9999999999999969484, 1e-12,
+       "eraPn00", "rb22", status);
+   vvd(rb[1][2], 0.3306041454222136517e-7, 1e-18,
+       "eraPn00", "rb23", status);
+
+   vvd(rb[2][0], -0.8056217380986972157e-7, 1e-18,
+       "eraPn00", "rb31", status);
+   vvd(rb[2][1], -0.3306040883980552500e-7, 1e-18,
+       "eraPn00", "rb32", status);
+   vvd(rb[2][2], 0.9999999999999962084, 1e-12,
+       "eraPn00", "rb33", status);
+
+   vvd(rp[0][0], 0.9999989300532289018, 1e-12,
+       "eraPn00", "rp11", status);
+   vvd(rp[0][1], -0.1341647226791824349e-2, 1e-14,
+       "eraPn00", "rp12", status);
+   vvd(rp[0][2], -0.5829880927190296547e-3, 1e-14,
+       "eraPn00", "rp13", status);
+
+   vvd(rp[1][0], 0.1341647231069759008e-2, 1e-14,
+       "eraPn00", "rp21", status);
+   vvd(rp[1][1], 0.9999990999908750433, 1e-12,
+       "eraPn00", "rp22", status);
+   vvd(rp[1][2], -0.3837444441583715468e-6, 1e-14,
+       "eraPn00", "rp23", status);
+
+   vvd(rp[2][0], 0.5829880828740957684e-3, 1e-14,
+       "eraPn00", "rp31", status);
+   vvd(rp[2][1], -0.3984203267708834759e-6, 1e-14,
+       "eraPn00", "rp32", status);
+   vvd(rp[2][2], 0.9999998300623538046, 1e-12,
+       "eraPn00", "rp33", status);
+
+   vvd(rbp[0][0], 0.9999989300052243993, 1e-12,
+       "eraPn00", "rbp11", status);
+   vvd(rbp[0][1], -0.1341717990239703727e-2, 1e-14,
+       "eraPn00", "rbp12", status);
+   vvd(rbp[0][2], -0.5829075749891684053e-3, 1e-14,
+       "eraPn00", "rbp13", status);
+
+   vvd(rbp[1][0], 0.1341718013831739992e-2, 1e-14,
+       "eraPn00", "rbp21", status);
+   vvd(rbp[1][1], 0.9999990998959191343, 1e-12,
+       "eraPn00", "rbp22", status);
+   vvd(rbp[1][2], -0.3505759733565421170e-6, 1e-14,
+       "eraPn00", "rbp23", status);
+
+   vvd(rbp[2][0], 0.5829075206857717883e-3, 1e-14,
+       "eraPn00", "rbp31", status);
+   vvd(rbp[2][1], -0.4315219955198608970e-6, 1e-14,
+       "eraPn00", "rbp32", status);
+   vvd(rbp[2][2], 0.9999998301093036269, 1e-12,
+       "eraPn00", "rbp33", status);
+
+   vvd(rn[0][0], 0.9999999999536069682, 1e-12,
+       "eraPn00", "rn11", status);
+   vvd(rn[0][1], 0.8837746144872140812e-5, 1e-16,
+       "eraPn00", "rn12", status);
+   vvd(rn[0][2], 0.3831488838252590008e-5, 1e-16,
+       "eraPn00", "rn13", status);
+
+   vvd(rn[1][0], -0.8837590456633197506e-5, 1e-16,
+       "eraPn00", "rn21", status);
+   vvd(rn[1][1], 0.9999999991354692733, 1e-12,
+       "eraPn00", "rn22", status);
+   vvd(rn[1][2], -0.4063198798559573702e-4, 1e-16,
+       "eraPn00", "rn23", status);
+
+   vvd(rn[2][0], -0.3831847930135328368e-5, 1e-16,
+       "eraPn00", "rn31", status);
+   vvd(rn[2][1], 0.4063195412258150427e-4, 1e-16,
+       "eraPn00", "rn32", status);
+   vvd(rn[2][2], 0.9999999991671806225, 1e-12,
+       "eraPn00", "rn33", status);
+
+   vvd(rbpn[0][0], 0.9999989440499982806, 1e-12,
+       "eraPn00", "rbpn11", status);
+   vvd(rbpn[0][1], -0.1332880253640848301e-2, 1e-14,
+       "eraPn00", "rbpn12", status);
+   vvd(rbpn[0][2], -0.5790760898731087295e-3, 1e-14,
+       "eraPn00", "rbpn13", status);
+
+   vvd(rbpn[1][0], 0.1332856746979948745e-2, 1e-14,
+       "eraPn00", "rbpn21", status);
+   vvd(rbpn[1][1], 0.9999991109064768883, 1e-12,
+       "eraPn00", "rbpn22", status);
+   vvd(rbpn[1][2], -0.4097740555723063806e-4, 1e-14,
+       "eraPn00", "rbpn23", status);
+
+   vvd(rbpn[2][0], 0.5791301929950205000e-3, 1e-14,
+       "eraPn00", "rbpn31", status);
+   vvd(rbpn[2][1], 0.4020553681373702931e-4, 1e-14,
+       "eraPn00", "rbpn32", status);
+   vvd(rbpn[2][2], 0.9999998314958529887, 1e-12,
+       "eraPn00", "rbpn33", status);
+
+}
+
+static void t_pn00a(int *status)
+/*
+**  - - - - - - - -
+**   t _ p n 0 0 a
+**  - - - - - - - -
+**
+**  Test eraPn00a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPn00a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsi, deps, epsa,
+          rb[3][3], rp[3][3], rbp[3][3], rn[3][3], rbpn[3][3];
+
+
+   eraPn00a(2400000.5, 53736.0,
+            &dpsi, &deps, &epsa, rb, rp, rbp, rn, rbpn);
+
+   vvd(dpsi, -0.9630909107115518431e-5, 1e-12,
+       "eraPn00a", "dpsi", status);
+   vvd(deps,  0.4063239174001678710e-4, 1e-12,
+       "eraPn00a", "deps", status);
+   vvd(epsa,  0.4090791789404229916, 1e-12, "eraPn00a", "epsa", status);
+
+   vvd(rb[0][0], 0.9999999999999942498, 1e-12,
+       "eraPn00a", "rb11", status);
+   vvd(rb[0][1], -0.7078279744199196626e-7, 1e-16,
+       "eraPn00a", "rb12", status);
+   vvd(rb[0][2], 0.8056217146976134152e-7, 1e-16,
+       "eraPn00a", "rb13", status);
+
+   vvd(rb[1][0], 0.7078279477857337206e-7, 1e-16,
+       "eraPn00a", "rb21", status);
+   vvd(rb[1][1], 0.9999999999999969484, 1e-12,
+       "eraPn00a", "rb22", status);
+   vvd(rb[1][2], 0.3306041454222136517e-7, 1e-16,
+       "eraPn00a", "rb23", status);
+
+   vvd(rb[2][0], -0.8056217380986972157e-7, 1e-16,
+       "eraPn00a", "rb31", status);
+   vvd(rb[2][1], -0.3306040883980552500e-7, 1e-16,
+       "eraPn00a", "rb32", status);
+   vvd(rb[2][2], 0.9999999999999962084, 1e-12,
+       "eraPn00a", "rb33", status);
+
+   vvd(rp[0][0], 0.9999989300532289018, 1e-12,
+       "eraPn00a", "rp11", status);
+   vvd(rp[0][1], -0.1341647226791824349e-2, 1e-14,
+       "eraPn00a", "rp12", status);
+   vvd(rp[0][2], -0.5829880927190296547e-3, 1e-14,
+       "eraPn00a", "rp13", status);
+
+   vvd(rp[1][0], 0.1341647231069759008e-2, 1e-14,
+       "eraPn00a", "rp21", status);
+   vvd(rp[1][1], 0.9999990999908750433, 1e-12,
+       "eraPn00a", "rp22", status);
+   vvd(rp[1][2], -0.3837444441583715468e-6, 1e-14,
+       "eraPn00a", "rp23", status);
+
+   vvd(rp[2][0], 0.5829880828740957684e-3, 1e-14,
+       "eraPn00a", "rp31", status);
+   vvd(rp[2][1], -0.3984203267708834759e-6, 1e-14,
+       "eraPn00a", "rp32", status);
+   vvd(rp[2][2], 0.9999998300623538046, 1e-12,
+       "eraPn00a", "rp33", status);
+
+   vvd(rbp[0][0], 0.9999989300052243993, 1e-12,
+       "eraPn00a", "rbp11", status);
+   vvd(rbp[0][1], -0.1341717990239703727e-2, 1e-14,
+       "eraPn00a", "rbp12", status);
+   vvd(rbp[0][2], -0.5829075749891684053e-3, 1e-14,
+       "eraPn00a", "rbp13", status);
+
+   vvd(rbp[1][0], 0.1341718013831739992e-2, 1e-14,
+       "eraPn00a", "rbp21", status);
+   vvd(rbp[1][1], 0.9999990998959191343, 1e-12,
+       "eraPn00a", "rbp22", status);
+   vvd(rbp[1][2], -0.3505759733565421170e-6, 1e-14,
+       "eraPn00a", "rbp23", status);
+
+   vvd(rbp[2][0], 0.5829075206857717883e-3, 1e-14,
+       "eraPn00a", "rbp31", status);
+   vvd(rbp[2][1], -0.4315219955198608970e-6, 1e-14,
+       "eraPn00a", "rbp32", status);
+   vvd(rbp[2][2], 0.9999998301093036269, 1e-12,
+       "eraPn00a", "rbp33", status);
+
+   vvd(rn[0][0], 0.9999999999536227949, 1e-12,
+       "eraPn00a", "rn11", status);
+   vvd(rn[0][1], 0.8836238544090873336e-5, 1e-14,
+       "eraPn00a", "rn12", status);
+   vvd(rn[0][2], 0.3830835237722400669e-5, 1e-14,
+       "eraPn00a", "rn13", status);
+
+   vvd(rn[1][0], -0.8836082880798569274e-5, 1e-14,
+       "eraPn00a", "rn21", status);
+   vvd(rn[1][1], 0.9999999991354655028, 1e-12,
+       "eraPn00a", "rn22", status);
+   vvd(rn[1][2], -0.4063240865362499850e-4, 1e-14,
+       "eraPn00a", "rn23", status);
+
+   vvd(rn[2][0], -0.3831194272065995866e-5, 1e-14,
+       "eraPn00a", "rn31", status);
+   vvd(rn[2][1], 0.4063237480216291775e-4, 1e-14,
+       "eraPn00a", "rn32", status);
+   vvd(rn[2][2], 0.9999999991671660338, 1e-12,
+       "eraPn00a", "rn33", status);
+
+   vvd(rbpn[0][0], 0.9999989440476103435, 1e-12,
+       "eraPn00a", "rbpn11", status);
+   vvd(rbpn[0][1], -0.1332881761240011763e-2, 1e-14,
+       "eraPn00a", "rbpn12", status);
+   vvd(rbpn[0][2], -0.5790767434730085751e-3, 1e-14,
+       "eraPn00a", "rbpn13", status);
+
+   vvd(rbpn[1][0], 0.1332858254308954658e-2, 1e-14,
+       "eraPn00a", "rbpn21", status);
+   vvd(rbpn[1][1], 0.9999991109044505577, 1e-12,
+       "eraPn00a", "rbpn22", status);
+   vvd(rbpn[1][2], -0.4097782710396580452e-4, 1e-14,
+       "eraPn00a", "rbpn23", status);
+
+   vvd(rbpn[2][0], 0.5791308472168152904e-3, 1e-14,
+       "eraPn00a", "rbpn31", status);
+   vvd(rbpn[2][1], 0.4020595661591500259e-4, 1e-14,
+       "eraPn00a", "rbpn32", status);
+   vvd(rbpn[2][2], 0.9999998314954572304, 1e-12,
+       "eraPn00a", "rbpn33", status);
+
+}
+
+static void t_pn00b(int *status)
+/*
+**  - - - - - - - -
+**   t _ p n 0 0 b
+**  - - - - - - - -
+**
+**  Test eraPn00b function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPn00b, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsi, deps, epsa,
+          rb[3][3], rp[3][3], rbp[3][3], rn[3][3], rbpn[3][3];
+
+
+   eraPn00b(2400000.5, 53736.0, &dpsi, &deps, &epsa,
+            rb, rp, rbp, rn, rbpn);
+
+   vvd(dpsi, -0.9632552291148362783e-5, 1e-12,
+       "eraPn00b", "dpsi", status);
+   vvd(deps,  0.4063197106621159367e-4, 1e-12,
+       "eraPn00b", "deps", status);
+   vvd(epsa,  0.4090791789404229916, 1e-12, "eraPn00b", "epsa", status);
+
+   vvd(rb[0][0], 0.9999999999999942498, 1e-12,
+      "eraPn00b", "rb11", status);
+   vvd(rb[0][1], -0.7078279744199196626e-7, 1e-16,
+      "eraPn00b", "rb12", status);
+   vvd(rb[0][2], 0.8056217146976134152e-7, 1e-16,
+      "eraPn00b", "rb13", status);
+
+   vvd(rb[1][0], 0.7078279477857337206e-7, 1e-16,
+      "eraPn00b", "rb21", status);
+   vvd(rb[1][1], 0.9999999999999969484, 1e-12,
+      "eraPn00b", "rb22", status);
+   vvd(rb[1][2], 0.3306041454222136517e-7, 1e-16,
+      "eraPn00b", "rb23", status);
+
+   vvd(rb[2][0], -0.8056217380986972157e-7, 1e-16,
+      "eraPn00b", "rb31", status);
+   vvd(rb[2][1], -0.3306040883980552500e-7, 1e-16,
+      "eraPn00b", "rb32", status);
+   vvd(rb[2][2], 0.9999999999999962084, 1e-12,
+      "eraPn00b", "rb33", status);
+
+   vvd(rp[0][0], 0.9999989300532289018, 1e-12,
+      "eraPn00b", "rp11", status);
+   vvd(rp[0][1], -0.1341647226791824349e-2, 1e-14,
+      "eraPn00b", "rp12", status);
+   vvd(rp[0][2], -0.5829880927190296547e-3, 1e-14,
+      "eraPn00b", "rp13", status);
+
+   vvd(rp[1][0], 0.1341647231069759008e-2, 1e-14,
+      "eraPn00b", "rp21", status);
+   vvd(rp[1][1], 0.9999990999908750433, 1e-12,
+      "eraPn00b", "rp22", status);
+   vvd(rp[1][2], -0.3837444441583715468e-6, 1e-14,
+      "eraPn00b", "rp23", status);
+
+   vvd(rp[2][0], 0.5829880828740957684e-3, 1e-14,
+      "eraPn00b", "rp31", status);
+   vvd(rp[2][1], -0.3984203267708834759e-6, 1e-14,
+      "eraPn00b", "rp32", status);
+   vvd(rp[2][2], 0.9999998300623538046, 1e-12,
+      "eraPn00b", "rp33", status);
+
+   vvd(rbp[0][0], 0.9999989300052243993, 1e-12,
+      "eraPn00b", "rbp11", status);
+   vvd(rbp[0][1], -0.1341717990239703727e-2, 1e-14,
+      "eraPn00b", "rbp12", status);
+   vvd(rbp[0][2], -0.5829075749891684053e-3, 1e-14,
+      "eraPn00b", "rbp13", status);
+
+   vvd(rbp[1][0], 0.1341718013831739992e-2, 1e-14,
+      "eraPn00b", "rbp21", status);
+   vvd(rbp[1][1], 0.9999990998959191343, 1e-12,
+      "eraPn00b", "rbp22", status);
+   vvd(rbp[1][2], -0.3505759733565421170e-6, 1e-14,
+      "eraPn00b", "rbp23", status);
+
+   vvd(rbp[2][0], 0.5829075206857717883e-3, 1e-14,
+      "eraPn00b", "rbp31", status);
+   vvd(rbp[2][1], -0.4315219955198608970e-6, 1e-14,
+      "eraPn00b", "rbp32", status);
+   vvd(rbp[2][2], 0.9999998301093036269, 1e-12,
+      "eraPn00b", "rbp33", status);
+
+   vvd(rn[0][0], 0.9999999999536069682, 1e-12,
+      "eraPn00b", "rn11", status);
+   vvd(rn[0][1], 0.8837746144871248011e-5, 1e-14,
+      "eraPn00b", "rn12", status);
+   vvd(rn[0][2], 0.3831488838252202945e-5, 1e-14,
+      "eraPn00b", "rn13", status);
+
+   vvd(rn[1][0], -0.8837590456632304720e-5, 1e-14,
+      "eraPn00b", "rn21", status);
+   vvd(rn[1][1], 0.9999999991354692733, 1e-12,
+      "eraPn00b", "rn22", status);
+   vvd(rn[1][2], -0.4063198798559591654e-4, 1e-14,
+      "eraPn00b", "rn23", status);
+
+   vvd(rn[2][0], -0.3831847930134941271e-5, 1e-14,
+      "eraPn00b", "rn31", status);
+   vvd(rn[2][1], 0.4063195412258168380e-4, 1e-14,
+      "eraPn00b", "rn32", status);
+   vvd(rn[2][2], 0.9999999991671806225, 1e-12,
+      "eraPn00b", "rn33", status);
+
+   vvd(rbpn[0][0], 0.9999989440499982806, 1e-12,
+      "eraPn00b", "rbpn11", status);
+   vvd(rbpn[0][1], -0.1332880253640849194e-2, 1e-14,
+      "eraPn00b", "rbpn12", status);
+   vvd(rbpn[0][2], -0.5790760898731091166e-3, 1e-14,
+      "eraPn00b", "rbpn13", status);
+
+   vvd(rbpn[1][0], 0.1332856746979949638e-2, 1e-14,
+      "eraPn00b", "rbpn21", status);
+   vvd(rbpn[1][1], 0.9999991109064768883, 1e-12,
+      "eraPn00b", "rbpn22", status);
+   vvd(rbpn[1][2], -0.4097740555723081811e-4, 1e-14,
+      "eraPn00b", "rbpn23", status);
+
+   vvd(rbpn[2][0], 0.5791301929950208873e-3, 1e-14,
+      "eraPn00b", "rbpn31", status);
+   vvd(rbpn[2][1], 0.4020553681373720832e-4, 1e-14,
+      "eraPn00b", "rbpn32", status);
+   vvd(rbpn[2][2], 0.9999998314958529887, 1e-12,
+      "eraPn00b", "rbpn33", status);
+
+}
+
+static void t_pn06a(int *status)
+/*
+**  - - - - - - - -
+**   t _ p n 0 6 a
+**  - - - - - - - -
+**
+**  Test eraPn06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPn06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsi, deps, epsa;
+   double rb[3][3], rp[3][3], rbp[3][3], rn[3][3], rbpn[3][3];
+
+
+   eraPn06a(2400000.5, 53736.0, &dpsi, &deps, &epsa,
+            rb, rp, rbp, rn, rbpn);
+
+   vvd(dpsi, -0.9630912025820308797e-5, 1e-12,
+       "eraPn06a", "dpsi", status);
+   vvd(deps,  0.4063238496887249798e-4, 1e-12,
+       "eraPn06a", "deps", status);
+   vvd(epsa,  0.4090789763356509926, 1e-12, "eraPn06a", "epsa", status);
+
+   vvd(rb[0][0], 0.9999999999999942497, 1e-12,
+       "eraPn06a", "rb11", status);
+   vvd(rb[0][1], -0.7078368960971557145e-7, 1e-14,
+       "eraPn06a", "rb12", status);
+   vvd(rb[0][2], 0.8056213977613185606e-7, 1e-14,
+       "eraPn06a", "rb13", status);
+
+   vvd(rb[1][0], 0.7078368694637674333e-7, 1e-14,
+       "eraPn06a", "rb21", status);
+   vvd(rb[1][1], 0.9999999999999969484, 1e-12,
+       "eraPn06a", "rb22", status);
+   vvd(rb[1][2], 0.3305943742989134124e-7, 1e-14,
+       "eraPn06a", "rb23", status);
+
+   vvd(rb[2][0], -0.8056214211620056792e-7, 1e-14,
+       "eraPn06a", "rb31", status);
+   vvd(rb[2][1], -0.3305943172740586950e-7, 1e-14,
+       "eraPn06a", "rb32", status);
+   vvd(rb[2][2], 0.9999999999999962084, 1e-12,
+       "eraPn06a", "rb33", status);
+
+   vvd(rp[0][0], 0.9999989300536854831, 1e-12,
+       "eraPn06a", "rp11", status);
+   vvd(rp[0][1], -0.1341646886204443795e-2, 1e-14,
+       "eraPn06a", "rp12", status);
+   vvd(rp[0][2], -0.5829880933488627759e-3, 1e-14,
+       "eraPn06a", "rp13", status);
+
+   vvd(rp[1][0], 0.1341646890569782183e-2, 1e-14,
+       "eraPn06a", "rp21", status);
+   vvd(rp[1][1], 0.9999990999913319321, 1e-12,
+       "eraPn06a", "rp22", status);
+   vvd(rp[1][2], -0.3835944216374477457e-6, 1e-14,
+       "eraPn06a", "rp23", status);
+
+   vvd(rp[2][0], 0.5829880833027867368e-3, 1e-14,
+       "eraPn06a", "rp31", status);
+   vvd(rp[2][1], -0.3985701514686976112e-6, 1e-14,
+       "eraPn06a", "rp32", status);
+   vvd(rp[2][2], 0.9999998300623534950, 1e-12,
+       "eraPn06a", "rp33", status);
+
+   vvd(rbp[0][0], 0.9999989300056797893, 1e-12,
+       "eraPn06a", "rbp11", status);
+   vvd(rbp[0][1], -0.1341717650545059598e-2, 1e-14,
+       "eraPn06a", "rbp12", status);
+   vvd(rbp[0][2], -0.5829075756493728856e-3, 1e-14,
+       "eraPn06a", "rbp13", status);
+
+   vvd(rbp[1][0], 0.1341717674223918101e-2, 1e-14,
+       "eraPn06a", "rbp21", status);
+   vvd(rbp[1][1], 0.9999990998963748448, 1e-12,
+       "eraPn06a", "rbp22", status);
+   vvd(rbp[1][2], -0.3504269280170069029e-6, 1e-14,
+       "eraPn06a", "rbp23", status);
+
+   vvd(rbp[2][0], 0.5829075211461454599e-3, 1e-14,
+       "eraPn06a", "rbp31", status);
+   vvd(rbp[2][1], -0.4316708436255949093e-6, 1e-14,
+       "eraPn06a", "rbp32", status);
+   vvd(rbp[2][2], 0.9999998301093032943, 1e-12,
+       "eraPn06a", "rbp33", status);
+
+   vvd(rn[0][0], 0.9999999999536227668, 1e-12,
+       "eraPn06a", "rn11", status);
+   vvd(rn[0][1], 0.8836241998111535233e-5, 1e-14,
+       "eraPn06a", "rn12", status);
+   vvd(rn[0][2], 0.3830834608415287707e-5, 1e-14,
+       "eraPn06a", "rn13", status);
+
+   vvd(rn[1][0], -0.8836086334870740138e-5, 1e-14,
+       "eraPn06a", "rn21", status);
+   vvd(rn[1][1], 0.9999999991354657474, 1e-12,
+       "eraPn06a", "rn22", status);
+   vvd(rn[1][2], -0.4063240188248455065e-4, 1e-14,
+       "eraPn06a", "rn23", status);
+
+   vvd(rn[2][0], -0.3831193642839398128e-5, 1e-14,
+       "eraPn06a", "rn31", status);
+   vvd(rn[2][1], 0.4063236803101479770e-4, 1e-14,
+       "eraPn06a", "rn32", status);
+   vvd(rn[2][2], 0.9999999991671663114, 1e-12,
+       "eraPn06a", "rn33", status);
+
+   vvd(rbpn[0][0], 0.9999989440480669738, 1e-12,
+       "eraPn06a", "rbpn11", status);
+   vvd(rbpn[0][1], -0.1332881418091915973e-2, 1e-14,
+       "eraPn06a", "rbpn12", status);
+   vvd(rbpn[0][2], -0.5790767447612042565e-3, 1e-14,
+       "eraPn06a", "rbpn13", status);
+
+   vvd(rbpn[1][0], 0.1332857911250989133e-2, 1e-14,
+       "eraPn06a", "rbpn21", status);
+   vvd(rbpn[1][1], 0.9999991109049141908, 1e-12,
+       "eraPn06a", "rbpn22", status);
+   vvd(rbpn[1][2], -0.4097767128546784878e-4, 1e-14,
+       "eraPn06a", "rbpn23", status);
+
+   vvd(rbpn[2][0], 0.5791308482835292617e-3, 1e-14,
+       "eraPn06a", "rbpn31", status);
+   vvd(rbpn[2][1], 0.4020580099454020310e-4, 1e-14,
+       "eraPn06a", "rbpn32", status);
+   vvd(rbpn[2][2], 0.9999998314954628695, 1e-12,
+       "eraPn06a", "rbpn33", status);
+
+}
+
+static void t_pn06(int *status)
+/*
+**  - - - - - - -
+**   t _ p n 0 6
+**  - - - - - - -
+**
+**  Test eraPn06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPn06, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsi, deps, epsa,
+          rb[3][3], rp[3][3], rbp[3][3], rn[3][3], rbpn[3][3];
+
+
+   dpsi = -0.9632552291149335877e-5;
+   deps =  0.4063197106621141414e-4;
+
+   eraPn06(2400000.5, 53736.0, dpsi, deps,
+           &epsa, rb, rp, rbp, rn, rbpn);
+
+   vvd(epsa, 0.4090789763356509926, 1e-12, "eraPn06", "epsa", status);
+
+   vvd(rb[0][0], 0.9999999999999942497, 1e-12,
+       "eraPn06", "rb11", status);
+   vvd(rb[0][1], -0.7078368960971557145e-7, 1e-14,
+       "eraPn06", "rb12", status);
+   vvd(rb[0][2], 0.8056213977613185606e-7, 1e-14,
+       "eraPn06", "rb13", status);
+
+   vvd(rb[1][0], 0.7078368694637674333e-7, 1e-14,
+       "eraPn06", "rb21", status);
+   vvd(rb[1][1], 0.9999999999999969484, 1e-12,
+       "eraPn06", "rb22", status);
+   vvd(rb[1][2], 0.3305943742989134124e-7, 1e-14,
+       "eraPn06", "rb23", status);
+
+   vvd(rb[2][0], -0.8056214211620056792e-7, 1e-14,
+       "eraPn06", "rb31", status);
+   vvd(rb[2][1], -0.3305943172740586950e-7, 1e-14,
+       "eraPn06", "rb32", status);
+   vvd(rb[2][2], 0.9999999999999962084, 1e-12,
+       "eraPn06", "rb33", status);
+
+   vvd(rp[0][0], 0.9999989300536854831, 1e-12,
+       "eraPn06", "rp11", status);
+   vvd(rp[0][1], -0.1341646886204443795e-2, 1e-14,
+       "eraPn06", "rp12", status);
+   vvd(rp[0][2], -0.5829880933488627759e-3, 1e-14,
+       "eraPn06", "rp13", status);
+
+   vvd(rp[1][0], 0.1341646890569782183e-2, 1e-14,
+       "eraPn06", "rp21", status);
+   vvd(rp[1][1], 0.9999990999913319321, 1e-12,
+       "eraPn06", "rp22", status);
+   vvd(rp[1][2], -0.3835944216374477457e-6, 1e-14,
+       "eraPn06", "rp23", status);
+
+   vvd(rp[2][0], 0.5829880833027867368e-3, 1e-14,
+       "eraPn06", "rp31", status);
+   vvd(rp[2][1], -0.3985701514686976112e-6, 1e-14,
+       "eraPn06", "rp32", status);
+   vvd(rp[2][2], 0.9999998300623534950, 1e-12,
+       "eraPn06", "rp33", status);
+
+   vvd(rbp[0][0], 0.9999989300056797893, 1e-12,
+       "eraPn06", "rbp11", status);
+   vvd(rbp[0][1], -0.1341717650545059598e-2, 1e-14,
+       "eraPn06", "rbp12", status);
+   vvd(rbp[0][2], -0.5829075756493728856e-3, 1e-14,
+       "eraPn06", "rbp13", status);
+
+   vvd(rbp[1][0], 0.1341717674223918101e-2, 1e-14,
+       "eraPn06", "rbp21", status);
+   vvd(rbp[1][1], 0.9999990998963748448, 1e-12,
+       "eraPn06", "rbp22", status);
+   vvd(rbp[1][2], -0.3504269280170069029e-6, 1e-14,
+       "eraPn06", "rbp23", status);
+
+   vvd(rbp[2][0], 0.5829075211461454599e-3, 1e-14,
+       "eraPn06", "rbp31", status);
+   vvd(rbp[2][1], -0.4316708436255949093e-6, 1e-14,
+       "eraPn06", "rbp32", status);
+   vvd(rbp[2][2], 0.9999998301093032943, 1e-12,
+       "eraPn06", "rbp33", status);
+
+   vvd(rn[0][0], 0.9999999999536069682, 1e-12,
+       "eraPn06", "rn11", status);
+   vvd(rn[0][1], 0.8837746921149881914e-5, 1e-14,
+       "eraPn06", "rn12", status);
+   vvd(rn[0][2], 0.3831487047682968703e-5, 1e-14,
+       "eraPn06", "rn13", status);
+
+   vvd(rn[1][0], -0.8837591232983692340e-5, 1e-14,
+       "eraPn06", "rn21", status);
+   vvd(rn[1][1], 0.9999999991354692664, 1e-12,
+       "eraPn06", "rn22", status);
+   vvd(rn[1][2], -0.4063198798558931215e-4, 1e-14,
+       "eraPn06", "rn23", status);
+
+   vvd(rn[2][0], -0.3831846139597250235e-5, 1e-14,
+       "eraPn06", "rn31", status);
+   vvd(rn[2][1], 0.4063195412258792914e-4, 1e-14,
+       "eraPn06", "rn32", status);
+   vvd(rn[2][2], 0.9999999991671806293, 1e-12,
+       "eraPn06", "rn33", status);
+
+   vvd(rbpn[0][0], 0.9999989440504506688, 1e-12,
+       "eraPn06", "rbpn11", status);
+   vvd(rbpn[0][1], -0.1332879913170492655e-2, 1e-14,
+       "eraPn06", "rbpn12", status);
+   vvd(rbpn[0][2], -0.5790760923225655753e-3, 1e-14,
+       "eraPn06", "rbpn13", status);
+
+   vvd(rbpn[1][0], 0.1332856406595754748e-2, 1e-14,
+       "eraPn06", "rbpn21", status);
+   vvd(rbpn[1][1], 0.9999991109069366795, 1e-12,
+       "eraPn06", "rbpn22", status);
+   vvd(rbpn[1][2], -0.4097725651142641812e-4, 1e-14,
+       "eraPn06", "rbpn23", status);
+
+   vvd(rbpn[2][0], 0.5791301952321296716e-3, 1e-14,
+       "eraPn06", "rbpn31", status);
+   vvd(rbpn[2][1], 0.4020538796195230577e-4, 1e-14,
+       "eraPn06", "rbpn32", status);
+   vvd(rbpn[2][2], 0.9999998314958576778, 1e-12,
+       "eraPn06", "rbpn33", status);
+
+}
+
+static void t_pnm00a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ p n m 0 0 a
+**  - - - - - - - - -
+**
+**  Test eraPnm00a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPnm00a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rbpn[3][3];
+
+
+   eraPnm00a(2400000.5, 50123.9999, rbpn);
+
+   vvd(rbpn[0][0], 0.9999995832793134257, 1e-12,
+       "eraPnm00a", "11", status);
+   vvd(rbpn[0][1], 0.8372384254137809439e-3, 1e-14,
+       "eraPnm00a", "12", status);
+   vvd(rbpn[0][2], 0.3639684306407150645e-3, 1e-14,
+       "eraPnm00a", "13", status);
+
+   vvd(rbpn[1][0], -0.8372535226570394543e-3, 1e-14,
+       "eraPnm00a", "21", status);
+   vvd(rbpn[1][1], 0.9999996486491582471, 1e-12,
+       "eraPnm00a", "22", status);
+   vvd(rbpn[1][2], 0.4132915262664072381e-4, 1e-14,
+       "eraPnm00a", "23", status);
+
+   vvd(rbpn[2][0], -0.3639337004054317729e-3, 1e-14,
+       "eraPnm00a", "31", status);
+   vvd(rbpn[2][1], -0.4163386925461775873e-4, 1e-14,
+       "eraPnm00a", "32", status);
+   vvd(rbpn[2][2], 0.9999999329094390695, 1e-12,
+       "eraPnm00a", "33", status);
+
+}
+
+static void t_pnm00b(int *status)
+/*
+**  - - - - - - - - -
+**   t _ p n m 0 0 b
+**  - - - - - - - - -
+**
+**  Test eraPnm00b function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPnm00b, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rbpn[3][3];
+
+
+   eraPnm00b(2400000.5, 50123.9999, rbpn);
+
+   vvd(rbpn[0][0], 0.9999995832776208280, 1e-12,
+       "eraPnm00b", "11", status);
+   vvd(rbpn[0][1], 0.8372401264429654837e-3, 1e-14,
+       "eraPnm00b", "12", status);
+   vvd(rbpn[0][2], 0.3639691681450271771e-3, 1e-14,
+       "eraPnm00b", "13", status);
+
+   vvd(rbpn[1][0], -0.8372552234147137424e-3, 1e-14,
+       "eraPnm00b", "21", status);
+   vvd(rbpn[1][1], 0.9999996486477686123, 1e-12,
+       "eraPnm00b", "22", status);
+   vvd(rbpn[1][2], 0.4132832190946052890e-4, 1e-14,
+       "eraPnm00b", "23", status);
+
+   vvd(rbpn[2][0], -0.3639344385341866407e-3, 1e-14,
+       "eraPnm00b", "31", status);
+   vvd(rbpn[2][1], -0.4163303977421522785e-4, 1e-14,
+       "eraPnm00b", "32", status);
+   vvd(rbpn[2][2], 0.9999999329092049734, 1e-12,
+       "eraPnm00b", "33", status);
+
+}
+
+static void t_pnm06a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ p n m 0 6 a
+**  - - - - - - - - -
+**
+**  Test eraPnm06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPnm06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rbpn[3][3];
+
+
+   eraPnm06a(2400000.5, 50123.9999, rbpn);
+
+   vvd(rbpn[0][0], 0.9999995832794205484, 1e-12,
+       "eraPnm06a", "11", status);
+   vvd(rbpn[0][1], 0.8372382772630962111e-3, 1e-14,
+       "eraPnm06a", "12", status);
+   vvd(rbpn[0][2], 0.3639684771140623099e-3, 1e-14,
+       "eraPnm06a", "13", status);
+
+   vvd(rbpn[1][0], -0.8372533744743683605e-3, 1e-14,
+       "eraPnm06a", "21", status);
+   vvd(rbpn[1][1], 0.9999996486492861646, 1e-12,
+       "eraPnm06a", "22", status);
+   vvd(rbpn[1][2], 0.4132905944611019498e-4, 1e-14,
+       "eraPnm06a", "23", status);
+
+   vvd(rbpn[2][0], -0.3639337469629464969e-3, 1e-14,
+       "eraPnm06a", "31", status);
+   vvd(rbpn[2][1], -0.4163377605910663999e-4, 1e-14,
+       "eraPnm06a", "32", status);
+   vvd(rbpn[2][2], 0.9999999329094260057, 1e-12,
+       "eraPnm06a", "33", status);
+
+}
+
+static void t_pnm80(int *status)
+/*
+**  - - - - - - - -
+**   t _ p n m 8 0
+**  - - - - - - - -
+**
+**  Test eraPnm80 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPnm80, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double rmatpn[3][3];
+
+
+   eraPnm80(2400000.5, 50123.9999, rmatpn);
+
+   vvd(rmatpn[0][0], 0.9999995831934611169, 1e-12,
+       "eraPnm80", "11", status);
+   vvd(rmatpn[0][1], 0.8373654045728124011e-3, 1e-14,
+       "eraPnm80", "12", status);
+   vvd(rmatpn[0][2], 0.3639121916933106191e-3, 1e-14,
+       "eraPnm80", "13", status);
+
+   vvd(rmatpn[1][0], -0.8373804896118301316e-3, 1e-14,
+       "eraPnm80", "21", status);
+   vvd(rmatpn[1][1], 0.9999996485439674092, 1e-12,
+       "eraPnm80", "22", status);
+   vvd(rmatpn[1][2], 0.4130202510421549752e-4, 1e-14,
+       "eraPnm80", "23", status);
+
+   vvd(rmatpn[2][0], -0.3638774789072144473e-3, 1e-14,
+       "eraPnm80", "31", status);
+   vvd(rmatpn[2][1], -0.4160674085851722359e-4, 1e-14,
+       "eraPnm80", "32", status);
+   vvd(rmatpn[2][2], 0.9999999329310274805, 1e-12,
+       "eraPnm80", "33", status);
+
+}
+
+static void t_pom00(int *status)
+/*
+**  - - - - - - - -
+**   t _ p o m 0 0
+**  - - - - - - - -
+**
+**  Test eraPom00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPom00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double xp, yp, sp, rpom[3][3];
+
+
+   xp =  2.55060238e-7;
+   yp =  1.860359247e-6;
+   sp = -0.1367174580728891460e-10;
+
+   eraPom00(xp, yp, sp, rpom);
+
+   vvd(rpom[0][0], 0.9999999999999674721, 1e-12,
+       "eraPom00", "11", status);
+   vvd(rpom[0][1], -0.1367174580728846989e-10, 1e-16,
+       "eraPom00", "12", status);
+   vvd(rpom[0][2], 0.2550602379999972345e-6, 1e-16,
+       "eraPom00", "13", status);
+
+   vvd(rpom[1][0], 0.1414624947957029801e-10, 1e-16,
+       "eraPom00", "21", status);
+   vvd(rpom[1][1], 0.9999999999982695317, 1e-12,
+       "eraPom00", "22", status);
+   vvd(rpom[1][2], -0.1860359246998866389e-5, 1e-16,
+       "eraPom00", "23", status);
+
+   vvd(rpom[2][0], -0.2550602379741215021e-6, 1e-16,
+       "eraPom00", "31", status);
+   vvd(rpom[2][1], 0.1860359247002414021e-5, 1e-16,
+       "eraPom00", "32", status);
+   vvd(rpom[2][2], 0.9999999999982370039, 1e-12,
+       "eraPom00", "33", status);
+
+}
+
+static void t_ppp(int *status)
+/*
+**  - - - - - -
+**   t _ p p p
+**  - - - - - -
+**
+**  Test eraPpp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPpp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[3], b[3], apb[3];
+
+
+   a[0] = 2.0;
+   a[1] = 2.0;
+   a[2] = 3.0;
+
+   b[0] = 1.0;
+   b[1] = 3.0;
+   b[2] = 4.0;
+
+   eraPpp(a, b, apb);
+
+   vvd(apb[0], 3.0, 1e-12, "eraPpp", "0", status);
+   vvd(apb[1], 5.0, 1e-12, "eraPpp", "1", status);
+   vvd(apb[2], 7.0, 1e-12, "eraPpp", "2", status);
+
+}
+
+static void t_ppsp(int *status)
+/*
+**  - - - - - - -
+**   t _ p p s p
+**  - - - - - - -
+**
+**  Test eraPpsp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPpsp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[3], s, b[3], apsb[3];
+
+
+   a[0] = 2.0;
+   a[1] = 2.0;
+   a[2] = 3.0;
+
+   s = 5.0;
+
+   b[0] = 1.0;
+   b[1] = 3.0;
+   b[2] = 4.0;
+
+   eraPpsp(a, s, b, apsb);
+
+   vvd(apsb[0], 7.0, 1e-12, "eraPpsp", "0", status);
+   vvd(apsb[1], 17.0, 1e-12, "eraPpsp", "1", status);
+   vvd(apsb[2], 23.0, 1e-12, "eraPpsp", "2", status);
+
+}
+
+static void t_pr00(int *status)
+/*
+**  - - - - - - -
+**   t _ p r 0 0
+**  - - - - - - -
+**
+**  Test eraPr00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPr00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double dpsipr, depspr;
+
+   eraPr00(2400000.5, 53736, &dpsipr, &depspr);
+
+   vvd(dpsipr, -0.8716465172668347629e-7, 1e-22,
+      "eraPr00", "dpsipr", status);
+   vvd(depspr, -0.7342018386722813087e-8, 1e-22,
+      "eraPr00", "depspr", status);
+
+}
+
+static void t_prec76(int *status)
+/*
+**  - - - - - - - - -
+**   t _ p r e c 7 6
+**  - - - - - - - - -
+**
+**  Test eraPrec76 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPrec76, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double ep01, ep02, ep11, ep12, zeta, z, theta;
+
+
+   ep01 = 2400000.5;
+   ep02 = 33282.0;
+   ep11 = 2400000.5;
+   ep12 = 51544.0;
+
+   eraPrec76(ep01, ep02, ep11, ep12, &zeta, &z, &theta);
+
+   vvd(zeta,  0.5588961642000161243e-2, 1e-12,
+       "eraPrec76", "zeta",  status);
+   vvd(z,     0.5589922365870680624e-2, 1e-12,
+       "eraPrec76", "z",     status);
+   vvd(theta, 0.4858945471687296760e-2, 1e-12,
+       "eraPrec76", "theta", status);
+
+}
+
+static void t_pv2p(int *status)
+/*
+**  - - - - - - -
+**   t _ p v 2 p
+**  - - - - - - -
+**
+**  Test eraPv2p function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPv2p, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double pv[2][3], p[3];
+
+
+   pv[0][0] =  0.3;
+   pv[0][1] =  1.2;
+   pv[0][2] = -2.5;
+
+   pv[1][0] = -0.5;
+   pv[1][1] =  3.1;
+   pv[1][2] =  0.9;
+
+   eraPv2p(pv, p);
+
+   vvd(p[0],  0.3, 0.0, "eraPv2p", "1", status);
+   vvd(p[1],  1.2, 0.0, "eraPv2p", "2", status);
+   vvd(p[2], -2.5, 0.0, "eraPv2p", "3", status);
+
+}
+
+static void t_pv2s(int *status)
+/*
+**  - - - - - - -
+**   t _ p v 2 s
+**  - - - - - - -
+**
+**  Test eraPv2s function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPv2s, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double pv[2][3], theta, phi, r, td, pd, rd;
+
+
+   pv[0][0] = -0.4514964673880165;
+   pv[0][1] =  0.03093394277342585;
+   pv[0][2] =  0.05594668105108779;
+
+   pv[1][0] =  1.292270850663260e-5;
+   pv[1][1] =  2.652814182060692e-6;
+   pv[1][2] =  2.568431853930293e-6;
+
+   eraPv2s(pv, &theta, &phi, &r, &td, &pd, &rd);
+
+   vvd(theta, 3.073185307179586515, 1e-12, "eraPv2s", "theta", status);
+   vvd(phi, 0.1229999999999999992, 1e-12, "eraPv2s", "phi", status);
+   vvd(r, 0.4559999999999999757, 1e-12, "eraPv2s", "r", status);
+   vvd(td, -0.7800000000000000364e-5, 1e-16, "eraPv2s", "td", status);
+   vvd(pd, 0.9010000000000001639e-5, 1e-16, "eraPv2s", "pd", status);
+   vvd(rd, -0.1229999999999999832e-4, 1e-16, "eraPv2s", "rd", status);
+
+}
+
+static void t_pvdpv(int *status)
+/*
+**  - - - - - - - -
+**   t _ p v d p v
+**  - - - - - - - -
+**
+**  Test eraPvdpv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPvdpv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[2][3], b[2][3], adb[2];
+
+
+   a[0][0] = 2.0;
+   a[0][1] = 2.0;
+   a[0][2] = 3.0;
+
+   a[1][0] = 6.0;
+   a[1][1] = 0.0;
+   a[1][2] = 4.0;
+
+   b[0][0] = 1.0;
+   b[0][1] = 3.0;
+   b[0][2] = 4.0;
+
+   b[1][0] = 0.0;
+   b[1][1] = 2.0;
+   b[1][2] = 8.0;
+
+   eraPvdpv(a, b, adb);
+
+   vvd(adb[0], 20.0, 1e-12, "eraPvdpv", "1", status);
+   vvd(adb[1], 50.0, 1e-12, "eraPvdpv", "2", status);
+
+}
+
+static void t_pvm(int *status)
+/*
+**  - - - - - -
+**   t _ p v m
+**  - - - - - -
+**
+**  Test eraPvm function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPvm, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double pv[2][3], r, s;
+
+
+   pv[0][0] =  0.3;
+   pv[0][1] =  1.2;
+   pv[0][2] = -2.5;
+
+   pv[1][0] =  0.45;
+   pv[1][1] = -0.25;
+   pv[1][2] =  1.1;
+
+   eraPvm(pv, &r, &s);
+
+   vvd(r, 2.789265136196270604, 1e-12, "eraPvm", "r", status);
+   vvd(s, 1.214495780149111922, 1e-12, "eraPvm", "s", status);
+
+}
+
+static void t_pvmpv(int *status)
+/*
+**  - - - - - - - -
+**   t _ p v m p v
+**  - - - - - - - -
+**
+**  Test eraPvmpv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPvmpv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[2][3], b[2][3], amb[2][3];
+
+
+   a[0][0] = 2.0;
+   a[0][1] = 2.0;
+   a[0][2] = 3.0;
+
+   a[1][0] = 5.0;
+   a[1][1] = 6.0;
+   a[1][2] = 3.0;
+
+   b[0][0] = 1.0;
+   b[0][1] = 3.0;
+   b[0][2] = 4.0;
+
+   b[1][0] = 3.0;
+   b[1][1] = 2.0;
+   b[1][2] = 1.0;
+
+   eraPvmpv(a, b, amb);
+
+   vvd(amb[0][0],  1.0, 1e-12, "eraPvmpv", "11", status);
+   vvd(amb[0][1], -1.0, 1e-12, "eraPvmpv", "21", status);
+   vvd(amb[0][2], -1.0, 1e-12, "eraPvmpv", "31", status);
+
+   vvd(amb[1][0],  2.0, 1e-12, "eraPvmpv", "12", status);
+   vvd(amb[1][1],  4.0, 1e-12, "eraPvmpv", "22", status);
+   vvd(amb[1][2],  2.0, 1e-12, "eraPvmpv", "32", status);
+
+}
+
+static void t_pvppv(int *status)
+/*
+**  - - - - - - - -
+**   t _ p v p p v
+**  - - - - - - - -
+**
+**  Test eraPvppv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPvppv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[2][3], b[2][3], apb[2][3];
+
+
+   a[0][0] = 2.0;
+   a[0][1] = 2.0;
+   a[0][2] = 3.0;
+
+   a[1][0] = 5.0;
+   a[1][1] = 6.0;
+   a[1][2] = 3.0;
+
+   b[0][0] = 1.0;
+   b[0][1] = 3.0;
+   b[0][2] = 4.0;
+
+   b[1][0] = 3.0;
+   b[1][1] = 2.0;
+   b[1][2] = 1.0;
+
+   eraPvppv(a, b, apb);
+
+   vvd(apb[0][0], 3.0, 1e-12, "eraPvppv", "p1", status);
+   vvd(apb[0][1], 5.0, 1e-12, "eraPvppv", "p2", status);
+   vvd(apb[0][2], 7.0, 1e-12, "eraPvppv", "p3", status);
+
+   vvd(apb[1][0], 8.0, 1e-12, "eraPvppv", "v1", status);
+   vvd(apb[1][1], 8.0, 1e-12, "eraPvppv", "v2", status);
+   vvd(apb[1][2], 4.0, 1e-12, "eraPvppv", "v3", status);
+
+}
+
+static void t_pvstar(int *status)
+/*
+**  - - - - - - - - -
+**   t _ p v s t a r
+**  - - - - - - - - -
+**
+**  Test eraPvstar function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPvstar, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double pv[2][3], ra, dec, pmr, pmd, px, rv;
+   int j;
+
+
+   pv[0][0] =  126668.5912743160601;
+   pv[0][1] =  2136.792716839935195;
+   pv[0][2] = -245251.2339876830091;
+
+   pv[1][0] = -0.4051854035740712739e-2;
+   pv[1][1] = -0.6253919754866173866e-2;
+   pv[1][2] =  0.1189353719774107189e-1;
+
+   j = eraPvstar(pv, &ra, &dec, &pmr, &pmd, &px, &rv);
+
+   vvd(ra, 0.1686756e-1, 1e-12, "eraPvstar", "ra", status);
+   vvd(dec, -1.093989828, 1e-12, "eraPvstar", "dec", status);
+   vvd(pmr, -0.178323516e-4, 1e-16, "eraPvstar", "pmr", status);
+   vvd(pmd, 0.2336024047e-5, 1e-16, "eraPvstar", "pmd", status);
+   vvd(px, 0.74723, 1e-12, "eraPvstar", "px", status);
+   vvd(rv, -21.6, 1e-11, "eraPvstar", "rv", status);
+
+   viv(j, 0, "eraPvstar", "j", status);
+
+}
+
+static void t_pvtob(int *status)
+/*
+**  - - - - - - - -
+**   t _ p v t o b
+**  - - - - - - - -
+**
+**  Test eraPvtob function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPvtob, vvd
+**
+**  This revision:  2013 October 2
+*/
+{
+   double elong, phi, hm, xp, yp, sp, theta, pv[2][3];
+
+
+   elong = 2.0;
+   phi = 0.5;
+   hm = 3000.0;
+   xp = 1e-6;
+   yp = -0.5e-6;
+   sp = 1e-8;
+   theta = 5.0;
+
+   eraPvtob(elong, phi, hm, xp, yp, sp, theta, pv);
+
+   vvd(pv[0][0], 4225081.367071159207, 1e-5,
+                 "eraPvtob", "p(1)", status);
+   vvd(pv[0][1], 3681943.215856198144, 1e-5,
+                 "eraPvtob", "p(2)", status);
+   vvd(pv[0][2], 3041149.399241260785, 1e-5,
+                 "eraPvtob", "p(3)", status);
+   vvd(pv[1][0], -268.4915389365998787, 1e-9,
+                 "eraPvtob", "v(1)", status);
+   vvd(pv[1][1], 308.0977983288903123, 1e-9,
+                 "eraPvtob", "v(2)", status);
+   vvd(pv[1][2], 0, 0,
+                 "eraPvtob", "v(3)", status);
+
+}
+
+static void t_pvu(int *status)
+/*
+**  - - - - - -
+**   t _ p v u
+**  - - - - - -
+**
+**  Test eraPvu function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPvu, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double pv[2][3], upv[2][3];
+
+
+   pv[0][0] =  126668.5912743160734;
+   pv[0][1] =  2136.792716839935565;
+   pv[0][2] = -245251.2339876830229;
+
+   pv[1][0] = -0.4051854035740713039e-2;
+   pv[1][1] = -0.6253919754866175788e-2;
+   pv[1][2] =  0.1189353719774107615e-1;
+
+   eraPvu(2920.0, pv, upv);
+
+   vvd(upv[0][0], 126656.7598605317105, 1e-12,
+       "eraPvu", "p1", status);
+   vvd(upv[0][1], 2118.531271155726332, 1e-12,
+       "eraPvu", "p2", status);
+   vvd(upv[0][2], -245216.5048590656190, 1e-12,
+       "eraPvu", "p3", status);
+
+   vvd(upv[1][0], -0.4051854035740713039e-2, 1e-12,
+       "eraPvu", "v1", status);
+   vvd(upv[1][1], -0.6253919754866175788e-2, 1e-12,
+       "eraPvu", "v2", status);
+   vvd(upv[1][2], 0.1189353719774107615e-1, 1e-12,
+       "eraPvu", "v3", status);
+
+}
+
+static void t_pvup(int *status)
+/*
+**  - - - - - - -
+**   t _ p v u p
+**  - - - - - - -
+**
+**  Test eraPvup function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPvup, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double pv[2][3], p[3];
+
+
+   pv[0][0] =  126668.5912743160734;
+   pv[0][1] =  2136.792716839935565;
+   pv[0][2] = -245251.2339876830229;
+
+   pv[1][0] = -0.4051854035740713039e-2;
+   pv[1][1] = -0.6253919754866175788e-2;
+   pv[1][2] =  0.1189353719774107615e-1;
+
+   eraPvup(2920.0, pv, p);
+
+   vvd(p[0],  126656.7598605317105,   1e-12, "eraPvup", "1", status);
+   vvd(p[1],    2118.531271155726332, 1e-12, "eraPvup", "2", status);
+   vvd(p[2], -245216.5048590656190,   1e-12, "eraPvup", "3", status);
+
+}
+
+static void t_pvxpv(int *status)
+/*
+**  - - - - - - - -
+**   t _ p v x p v
+**  - - - - - - - -
+**
+**  Test eraPvxpv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPvxpv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[2][3], b[2][3], axb[2][3];
+
+
+   a[0][0] = 2.0;
+   a[0][1] = 2.0;
+   a[0][2] = 3.0;
+
+   a[1][0] = 6.0;
+   a[1][1] = 0.0;
+   a[1][2] = 4.0;
+
+   b[0][0] = 1.0;
+   b[0][1] = 3.0;
+   b[0][2] = 4.0;
+
+   b[1][0] = 0.0;
+   b[1][1] = 2.0;
+   b[1][2] = 8.0;
+
+   eraPvxpv(a, b, axb);
+
+   vvd(axb[0][0],  -1.0, 1e-12, "eraPvxpv", "p1", status);
+   vvd(axb[0][1],  -5.0, 1e-12, "eraPvxpv", "p2", status);
+   vvd(axb[0][2],   4.0, 1e-12, "eraPvxpv", "p3", status);
+
+   vvd(axb[1][0],  -2.0, 1e-12, "eraPvxpv", "v1", status);
+   vvd(axb[1][1], -36.0, 1e-12, "eraPvxpv", "v2", status);
+   vvd(axb[1][2],  22.0, 1e-12, "eraPvxpv", "v3", status);
+
+}
+
+static void t_pxp(int *status)
+/*
+**  - - - - - -
+**   t _ p x p
+**  - - - - - -
+**
+**  Test eraPxp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraPxp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[3], b[3], axb[3];
+
+
+   a[0] = 2.0;
+   a[1] = 2.0;
+   a[2] = 3.0;
+
+   b[0] = 1.0;
+   b[1] = 3.0;
+   b[2] = 4.0;
+
+   eraPxp(a, b, axb);
+
+   vvd(axb[0], -1.0, 1e-12, "eraPxp", "1", status);
+   vvd(axb[1], -5.0, 1e-12, "eraPxp", "2", status);
+   vvd(axb[2],  4.0, 1e-12, "eraPxp", "3", status);
+
+}
+
+static void t_refco(int *status)
+/*
+**  - - - - - - - -
+**   t _ r e f c o
+**  - - - - - - - -
+**
+**  Test eraRefco function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraRefco, vvd
+**
+**  This revision:  2013 October 2
+*/
+{
+   double phpa, tc, rh, wl, refa, refb;
+
+
+   phpa = 800.0;
+   tc = 10.0;
+   rh = 0.9;
+   wl = 0.4;
+
+   eraRefco(phpa, tc, rh, wl, &refa, &refb);
+
+   vvd(refa, 0.2264949956241415009e-3, 1e-15,
+             "eraRefco", "refa", status);
+   vvd(refb, -0.2598658261729343970e-6, 1e-18,
+             "eraRefco", "refb", status);
+
+}
+
+static void t_rm2v(int *status)
+/*
+**  - - - - - - -
+**   t _ r m 2 v
+**  - - - - - - -
+**
+**  Test eraRm2v function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraRm2v, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r[3][3], w[3];
+
+
+   r[0][0] =  0.00;
+   r[0][1] = -0.80;
+   r[0][2] = -0.60;
+
+   r[1][0] =  0.80;
+   r[1][1] = -0.36;
+   r[1][2] =  0.48;
+
+   r[2][0] =  0.60;
+   r[2][1] =  0.48;
+   r[2][2] = -0.64;
+
+   eraRm2v(r, w);
+
+   vvd(w[0],  0.0,                  1e-12, "eraRm2v", "1", status);
+   vvd(w[1],  1.413716694115406957, 1e-12, "eraRm2v", "2", status);
+   vvd(w[2], -1.884955592153875943, 1e-12, "eraRm2v", "3", status);
+
+}
+
+static void t_rv2m(int *status)
+/*
+**  - - - - - - -
+**   t _ r v 2 m
+**  - - - - - - -
+**
+**  Test eraRv2m function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraRv2m, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double w[3], r[3][3];
+
+
+   w[0] =  0.0;
+   w[1] =  1.41371669;
+   w[2] = -1.88495559;
+
+   eraRv2m(w, r);
+
+   vvd(r[0][0], -0.7071067782221119905, 1e-14, "eraRv2m", "11", status);
+   vvd(r[0][1], -0.5656854276809129651, 1e-14, "eraRv2m", "12", status);
+   vvd(r[0][2], -0.4242640700104211225, 1e-14, "eraRv2m", "13", status);
+
+   vvd(r[1][0],  0.5656854276809129651, 1e-14, "eraRv2m", "21", status);
+   vvd(r[1][1], -0.0925483394532274246, 1e-14, "eraRv2m", "22", status);
+   vvd(r[1][2], -0.8194112531408833269, 1e-14, "eraRv2m", "23", status);
+
+   vvd(r[2][0],  0.4242640700104211225, 1e-14, "eraRv2m", "31", status);
+   vvd(r[2][1], -0.8194112531408833269, 1e-14, "eraRv2m", "32", status);
+   vvd(r[2][2],  0.3854415612311154341, 1e-14, "eraRv2m", "33", status);
+
+}
+
+static void t_rx(int *status)
+/*
+**  - - - - -
+**   t _ r x
+**  - - - - -
+**
+**  Test eraRx function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraRx, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double phi, r[3][3];
+
+
+   phi = 0.3456789;
+
+   r[0][0] = 2.0;
+   r[0][1] = 3.0;
+   r[0][2] = 2.0;
+
+   r[1][0] = 3.0;
+   r[1][1] = 2.0;
+   r[1][2] = 3.0;
+
+   r[2][0] = 3.0;
+   r[2][1] = 4.0;
+   r[2][2] = 5.0;
+
+   eraRx(phi, r);
+
+   vvd(r[0][0], 2.0, 0.0, "eraRx", "11", status);
+   vvd(r[0][1], 3.0, 0.0, "eraRx", "12", status);
+   vvd(r[0][2], 2.0, 0.0, "eraRx", "13", status);
+
+   vvd(r[1][0], 3.839043388235612460, 1e-12, "eraRx", "21", status);
+   vvd(r[1][1], 3.237033249594111899, 1e-12, "eraRx", "22", status);
+   vvd(r[1][2], 4.516714379005982719, 1e-12, "eraRx", "23", status);
+
+   vvd(r[2][0], 1.806030415924501684, 1e-12, "eraRx", "31", status);
+   vvd(r[2][1], 3.085711545336372503, 1e-12, "eraRx", "32", status);
+   vvd(r[2][2], 3.687721683977873065, 1e-12, "eraRx", "33", status);
+
+}
+
+static void t_rxp(int *status)
+/*
+**  - - - - - -
+**   t _ r x p
+**  - - - - - -
+**
+**  Test eraRxp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraRxp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r[3][3], p[3], rp[3];
+
+
+   r[0][0] = 2.0;
+   r[0][1] = 3.0;
+   r[0][2] = 2.0;
+
+   r[1][0] = 3.0;
+   r[1][1] = 2.0;
+   r[1][2] = 3.0;
+
+   r[2][0] = 3.0;
+   r[2][1] = 4.0;
+   r[2][2] = 5.0;
+
+   p[0] = 0.2;
+   p[1] = 1.5;
+   p[2] = 0.1;
+
+   eraRxp(r, p, rp);
+
+   vvd(rp[0], 5.1, 1e-12, "eraRxp", "1", status);
+   vvd(rp[1], 3.9, 1e-12, "eraRxp", "2", status);
+   vvd(rp[2], 7.1, 1e-12, "eraRxp", "3", status);
+
+}
+
+static void t_rxpv(int *status)
+/*
+**  - - - - - - -
+**   t _ r x p v
+**  - - - - - - -
+**
+**  Test eraRxpv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraRxpv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r[3][3], pv[2][3], rpv[2][3];
+
+
+   r[0][0] = 2.0;
+   r[0][1] = 3.0;
+   r[0][2] = 2.0;
+
+   r[1][0] = 3.0;
+   r[1][1] = 2.0;
+   r[1][2] = 3.0;
+
+   r[2][0] = 3.0;
+   r[2][1] = 4.0;
+   r[2][2] = 5.0;
+
+   pv[0][0] = 0.2;
+   pv[0][1] = 1.5;
+   pv[0][2] = 0.1;
+
+   pv[1][0] = 1.5;
+   pv[1][1] = 0.2;
+   pv[1][2] = 0.1;
+
+   eraRxpv(r, pv, rpv);
+
+   vvd(rpv[0][0], 5.1, 1e-12, "eraRxpv", "11", status);
+   vvd(rpv[1][0], 3.8, 1e-12, "eraRxpv", "12", status);
+
+   vvd(rpv[0][1], 3.9, 1e-12, "eraRxpv", "21", status);
+   vvd(rpv[1][1], 5.2, 1e-12, "eraRxpv", "22", status);
+
+   vvd(rpv[0][2], 7.1, 1e-12, "eraRxpv", "31", status);
+   vvd(rpv[1][2], 5.8, 1e-12, "eraRxpv", "32", status);
+
+}
+
+static void t_rxr(int *status)
+/*
+**  - - - - - -
+**   t _ r x r
+**  - - - - - -
+**
+**  Test eraRxr function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraRxr, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[3][3], b[3][3], atb[3][3];
+
+
+   a[0][0] = 2.0;
+   a[0][1] = 3.0;
+   a[0][2] = 2.0;
+
+   a[1][0] = 3.0;
+   a[1][1] = 2.0;
+   a[1][2] = 3.0;
+
+   a[2][0] = 3.0;
+   a[2][1] = 4.0;
+   a[2][2] = 5.0;
+
+   b[0][0] = 1.0;
+   b[0][1] = 2.0;
+   b[0][2] = 2.0;
+
+   b[1][0] = 4.0;
+   b[1][1] = 1.0;
+   b[1][2] = 1.0;
+
+   b[2][0] = 3.0;
+   b[2][1] = 0.0;
+   b[2][2] = 1.0;
+
+   eraRxr(a, b, atb);
+
+   vvd(atb[0][0], 20.0, 1e-12, "eraRxr", "11", status);
+   vvd(atb[0][1],  7.0, 1e-12, "eraRxr", "12", status);
+   vvd(atb[0][2],  9.0, 1e-12, "eraRxr", "13", status);
+
+   vvd(atb[1][0], 20.0, 1e-12, "eraRxr", "21", status);
+   vvd(atb[1][1],  8.0, 1e-12, "eraRxr", "22", status);
+   vvd(atb[1][2], 11.0, 1e-12, "eraRxr", "23", status);
+
+   vvd(atb[2][0], 34.0, 1e-12, "eraRxr", "31", status);
+   vvd(atb[2][1], 10.0, 1e-12, "eraRxr", "32", status);
+   vvd(atb[2][2], 15.0, 1e-12, "eraRxr", "33", status);
+
+}
+
+static void t_ry(int *status)
+/*
+**  - - - - -
+**   t _ r y
+**  - - - - -
+**
+**  Test eraRy function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraRy, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double theta, r[3][3];
+
+
+   theta = 0.3456789;
+
+   r[0][0] = 2.0;
+   r[0][1] = 3.0;
+   r[0][2] = 2.0;
+
+   r[1][0] = 3.0;
+   r[1][1] = 2.0;
+   r[1][2] = 3.0;
+
+   r[2][0] = 3.0;
+   r[2][1] = 4.0;
+   r[2][2] = 5.0;
+
+   eraRy(theta, r);
+
+   vvd(r[0][0], 0.8651847818978159930, 1e-12, "eraRy", "11", status);
+   vvd(r[0][1], 1.467194920539316554, 1e-12, "eraRy", "12", status);
+   vvd(r[0][2], 0.1875137911274457342, 1e-12, "eraRy", "13", status);
+
+   vvd(r[1][0], 3, 1e-12, "eraRy", "21", status);
+   vvd(r[1][1], 2, 1e-12, "eraRy", "22", status);
+   vvd(r[1][2], 3, 1e-12, "eraRy", "23", status);
+
+   vvd(r[2][0], 3.500207892850427330, 1e-12, "eraRy", "31", status);
+   vvd(r[2][1], 4.779889022262298150, 1e-12, "eraRy", "32", status);
+   vvd(r[2][2], 5.381899160903798712, 1e-12, "eraRy", "33", status);
+
+}
+
+static void t_rz(int *status)
+/*
+**  - - - - -
+**   t _ r z
+**  - - - - -
+**
+**  Test eraRz function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraRz, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double psi, r[3][3];
+
+
+   psi = 0.3456789;
+
+   r[0][0] = 2.0;
+   r[0][1] = 3.0;
+   r[0][2] = 2.0;
+
+   r[1][0] = 3.0;
+   r[1][1] = 2.0;
+   r[1][2] = 3.0;
+
+   r[2][0] = 3.0;
+   r[2][1] = 4.0;
+   r[2][2] = 5.0;
+
+   eraRz(psi, r);
+
+   vvd(r[0][0], 2.898197754208926769, 1e-12, "eraRz", "11", status);
+   vvd(r[0][1], 3.500207892850427330, 1e-12, "eraRz", "12", status);
+   vvd(r[0][2], 2.898197754208926769, 1e-12, "eraRz", "13", status);
+
+   vvd(r[1][0], 2.144865911309686813, 1e-12, "eraRz", "21", status);
+   vvd(r[1][1], 0.865184781897815993, 1e-12, "eraRz", "22", status);
+   vvd(r[1][2], 2.144865911309686813, 1e-12, "eraRz", "23", status);
+
+   vvd(r[2][0], 3.0, 1e-12, "eraRz", "31", status);
+   vvd(r[2][1], 4.0, 1e-12, "eraRz", "32", status);
+   vvd(r[2][2], 5.0, 1e-12, "eraRz", "33", status);
+
+}
+
+static void t_s00a(int *status)
+/*
+**  - - - - - - -
+**   t _ s 0 0 a
+**  - - - - - - -
+**
+**  Test eraS00a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraS00a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double s;
+
+
+   s = eraS00a(2400000.5, 52541.0);
+
+   vvd(s, -0.1340684448919163584e-7, 1e-18, "eraS00a", "", status);
+
+}
+
+static void t_s00b(int *status)
+/*
+**  - - - - - - -
+**   t _ s 0 0 b
+**  - - - - - - -
+**
+**  Test eraS00b function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraS00b, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double s;
+
+
+   s = eraS00b(2400000.5, 52541.0);
+
+   vvd(s, -0.1340695782951026584e-7, 1e-18, "eraS00b", "", status);
+
+}
+
+static void t_s00(int *status)
+/*
+**  - - - - - -
+**   t _ s 0 0
+**  - - - - - -
+**
+**  Test eraS00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraS00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double x, y, s;
+
+
+   x = 0.5791308486706011000e-3;
+   y = 0.4020579816732961219e-4;
+
+   s = eraS00(2400000.5, 53736.0, x, y);
+
+   vvd(s, -0.1220036263270905693e-7, 1e-18, "eraS00", "", status);
+
+}
+
+static void t_s06a(int *status)
+/*
+**  - - - - - - -
+**   t _ s 0 6 a
+**  - - - - - - -
+**
+**  Test eraS06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraS06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double s;
+
+
+   s = eraS06a(2400000.5, 52541.0);
+
+   vvd(s, -0.1340680437291812383e-7, 1e-18, "eraS06a", "", status);
+
+}
+
+static void t_s06(int *status)
+/*
+**  - - - - - -
+**   t _ s 0 6
+**  - - - - - -
+**
+**  Test eraS06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraS06, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double x, y, s;
+
+
+   x = 0.5791308486706011000e-3;
+   y = 0.4020579816732961219e-4;
+
+   s = eraS06(2400000.5, 53736.0, x, y);
+
+   vvd(s, -0.1220032213076463117e-7, 1e-18, "eraS06", "", status);
+
+}
+
+static void t_s2c(int *status)
+/*
+**  - - - - - -
+**   t _ s 2 c
+**  - - - - - -
+**
+**  Test eraS2c function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraS2c, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double c[3];
+
+
+   eraS2c(3.0123, -0.999, c);
+
+   vvd(c[0], -0.5366267667260523906, 1e-12, "eraS2c", "1", status);
+   vvd(c[1],  0.0697711109765145365, 1e-12, "eraS2c", "2", status);
+   vvd(c[2], -0.8409302618566214041, 1e-12, "eraS2c", "3", status);
+
+}
+
+static void t_s2p(int *status)
+/*
+**  - - - - - -
+**   t _ s 2 p
+**  - - - - - -
+**
+**  Test eraS2p function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraS2p, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double p[3];
+
+
+   eraS2p(-3.21, 0.123, 0.456, p);
+
+   vvd(p[0], -0.4514964673880165228, 1e-12, "eraS2p", "x", status);
+   vvd(p[1],  0.0309339427734258688, 1e-12, "eraS2p", "y", status);
+   vvd(p[2],  0.0559466810510877933, 1e-12, "eraS2p", "z", status);
+
+}
+
+static void t_s2pv(int *status)
+/*
+**  - - - - - - -
+**   t _ s 2 p v
+**  - - - - - - -
+**
+**  Test eraS2pv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraS2pv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double pv[2][3];
+
+
+   eraS2pv(-3.21, 0.123, 0.456, -7.8e-6, 9.01e-6, -1.23e-5, pv);
+
+   vvd(pv[0][0], -0.4514964673880165228, 1e-12, "eraS2pv", "x", status);
+   vvd(pv[0][1],  0.0309339427734258688, 1e-12, "eraS2pv", "y", status);
+   vvd(pv[0][2],  0.0559466810510877933, 1e-12, "eraS2pv", "z", status);
+
+   vvd(pv[1][0],  0.1292270850663260170e-4, 1e-16,
+       "eraS2pv", "vx", status);
+   vvd(pv[1][1],  0.2652814182060691422e-5, 1e-16,
+       "eraS2pv", "vy", status);
+   vvd(pv[1][2],  0.2568431853930292259e-5, 1e-16,
+       "eraS2pv", "vz", status);
+
+}
+
+static void t_s2xpv(int *status)
+/*
+**  - - - - - - - -
+**   t _ s 2 x p v
+**  - - - - - - - -
+**
+**  Test eraS2xpv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraS2xpv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double s1, s2, pv[2][3], spv[2][3];
+
+
+   s1 = 2.0;
+   s2 = 3.0;
+
+   pv[0][0] =  0.3;
+   pv[0][1] =  1.2;
+   pv[0][2] = -2.5;
+
+   pv[1][0] =  0.5;
+   pv[1][1] =  2.3;
+   pv[1][2] = -0.4;
+
+   eraS2xpv(s1, s2, pv, spv);
+
+   vvd(spv[0][0],  0.6, 1e-12, "eraS2xpv", "p1", status);
+   vvd(spv[0][1],  2.4, 1e-12, "eraS2xpv", "p2", status);
+   vvd(spv[0][2], -5.0, 1e-12, "eraS2xpv", "p3", status);
+
+   vvd(spv[1][0],  1.5, 1e-12, "eraS2xpv", "v1", status);
+   vvd(spv[1][1],  6.9, 1e-12, "eraS2xpv", "v2", status);
+   vvd(spv[1][2], -1.2, 1e-12, "eraS2xpv", "v3", status);
+
+}
+
+static void t_sepp(int *status)
+/*
+**  - - - - - - -
+**   t _ s e p p
+**  - - - - - - -
+**
+**  Test eraSepp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraSepp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a[3], b[3], s;
+
+
+   a[0] =  1.0;
+   a[1] =  0.1;
+   a[2] =  0.2;
+
+   b[0] = -3.0;
+   b[1] =  1e-3;
+   b[2] =  0.2;
+
+   s = eraSepp(a, b);
+
+   vvd(s, 2.860391919024660768, 1e-12, "eraSepp", "", status);
+
+}
+
+static void t_seps(int *status)
+/*
+**  - - - - - - -
+**   t _ s e p s
+**  - - - - - - -
+**
+**  Test eraSeps function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraSeps, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double al, ap, bl, bp, s;
+
+
+   al =  1.0;
+   ap =  0.1;
+
+   bl =  0.2;
+   bp = -3.0;
+
+   s = eraSeps(al, ap, bl, bp);
+
+   vvd(s, 2.346722016996998842, 1e-14, "eraSeps", "", status);
+
+}
+
+static void t_sp00(int *status)
+/*
+**  - - - - - - -
+**   t _ s p 0 0
+**  - - - - - - -
+**
+**  Test eraSp00 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraSp00, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   vvd(eraSp00(2400000.5, 52541.0),
+       -0.6216698469981019309e-11, 1e-12, "eraSp00", "", status);
+
+}
+
+static void t_starpm(int *status)
+/*
+**  - - - - - - - - -
+**   t _ s t a r p m
+**  - - - - - - - - -
+**
+**  Test eraStarpm function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraStarpm, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double ra1, dec1, pmr1, pmd1, px1, rv1;
+   double ra2, dec2, pmr2, pmd2, px2, rv2;
+   int j;
+
+
+   ra1 =   0.01686756;
+   dec1 = -1.093989828;
+   pmr1 = -1.78323516e-5;
+   pmd1 =  2.336024047e-6;
+   px1 =   0.74723;
+   rv1 = -21.6;
+
+   j = eraStarpm(ra1, dec1, pmr1, pmd1, px1, rv1,
+                 2400000.5, 50083.0, 2400000.5, 53736.0,
+                 &ra2, &dec2, &pmr2, &pmd2, &px2, &rv2);
+
+   vvd(ra2, 0.01668919069414242368, 1e-13,
+       "eraStarpm", "ra", status);
+   vvd(dec2, -1.093966454217127879, 1e-13,
+       "eraStarpm", "dec", status);
+   vvd(pmr2, -0.1783662682155932702e-4, 1e-17,
+       "eraStarpm", "pmr", status);
+   vvd(pmd2, 0.2338092915987603664e-5, 1e-17,
+       "eraStarpm", "pmd", status);
+   vvd(px2, 0.7473533835323493644, 1e-13,
+       "eraStarpm", "px", status);
+   vvd(rv2, -21.59905170476860786, 1e-11,
+       "eraStarpm", "rv", status);
+
+   viv(j, 0, "eraStarpm", "j", status);
+
+}
+
+static void t_starpv(int *status)
+/*
+**  - - - - - - - - -
+**   t _ s t a r p v
+**  - - - - - - - - -
+**
+**  Test eraStarpv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraStarpv, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double ra, dec, pmr, pmd, px, rv, pv[2][3];
+   int j;
+
+
+   ra =   0.01686756;
+   dec = -1.093989828;
+   pmr = -1.78323516e-5;
+   pmd =  2.336024047e-6;
+   px =   0.74723;
+   rv = -21.6;
+
+   j = eraStarpv(ra, dec, pmr, pmd, px, rv, pv);
+
+   vvd(pv[0][0], 126668.5912743160601, 1e-10,
+       "eraStarpv", "11", status);
+   vvd(pv[0][1], 2136.792716839935195, 1e-12,
+       "eraStarpv", "12", status);
+   vvd(pv[0][2], -245251.2339876830091, 1e-10,
+       "eraStarpv", "13", status);
+
+   vvd(pv[1][0], -0.4051854035740712739e-2, 1e-13,
+       "eraStarpv", "21", status);
+   vvd(pv[1][1], -0.6253919754866173866e-2, 1e-15,
+       "eraStarpv", "22", status);
+   vvd(pv[1][2], 0.1189353719774107189e-1, 1e-13,
+       "eraStarpv", "23", status);
+
+   viv(j, 0, "eraStarpv", "j", status);
+
+}
+
+static void t_sxp(int *status)
+/*
+**  - - - - - -
+**   t _ s x p
+**  - - - - - -
+**
+**  Test eraSxp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraSxp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double s, p[3], sp[3];
+
+
+   s = 2.0;
+
+   p[0] =  0.3;
+   p[1] =  1.2;
+   p[2] = -2.5;
+
+   eraSxp(s, p, sp);
+
+   vvd(sp[0],  0.6, 0.0, "eraSxp", "1", status);
+   vvd(sp[1],  2.4, 0.0, "eraSxp", "2", status);
+   vvd(sp[2], -5.0, 0.0, "eraSxp", "3", status);
+
+}
+
+
+static void t_sxpv(int *status)
+/*
+**  - - - - - - -
+**   t _ s x p v
+**  - - - - - - -
+**
+**  Test eraSxpv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraSxpv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double s, pv[2][3], spv[2][3];
+
+
+   s = 2.0;
+
+   pv[0][0] =  0.3;
+   pv[0][1] =  1.2;
+   pv[0][2] = -2.5;
+
+   pv[1][0] =  0.5;
+   pv[1][1] =  3.2;
+   pv[1][2] = -0.7;
+
+   eraSxpv(s, pv, spv);
+
+   vvd(spv[0][0],  0.6, 0.0, "eraSxpv", "p1", status);
+   vvd(spv[0][1],  2.4, 0.0, "eraSxpv", "p2", status);
+   vvd(spv[0][2], -5.0, 0.0, "eraSxpv", "p3", status);
+
+   vvd(spv[1][0],  1.0, 0.0, "eraSxpv", "v1", status);
+   vvd(spv[1][1],  6.4, 0.0, "eraSxpv", "v2", status);
+   vvd(spv[1][2], -1.4, 0.0, "eraSxpv", "v3", status);
+
+}
+
+static void t_taitt(int *status)
+/*
+**  - - - - - - - -
+**   t _ t a i t t
+**  - - - - - - - -
+**
+**  Test eraTaitt function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTaitt, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double t1, t2;
+   int j;
+
+
+   j = eraTaitt(2453750.5, 0.892482639, &t1, &t2);
+
+   vvd(t1, 2453750.5, 1e-6, "eraTaitt", "t1", status);
+   vvd(t2, 0.892855139, 1e-12, "eraTaitt", "t2", status);
+   viv(j, 0, "eraTaitt", "j", status);
+
+}
+
+static void t_taiut1(int *status)
+/*
+**  - - - - - - - - -
+**   t _ t a i u t 1
+**  - - - - - - - - -
+**
+**  Test eraTaiut1 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTaiut1, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double u1, u2;
+   int j;
+
+
+   j = eraTaiut1(2453750.5, 0.892482639, -32.6659, &u1, &u2);
+
+   vvd(u1, 2453750.5, 1e-6, "eraTaiut1", "u1", status);
+   vvd(u2, 0.8921045614537037037, 1e-12, "eraTaiut1", "u2", status);
+   viv(j, 0, "eraTaiut1", "j", status);
+
+}
+
+static void t_taiutc(int *status)
+/*
+**  - - - - - - - - -
+**   t _ t a i u t c
+**  - - - - - - - - -
+**
+**  Test eraTaiutc function.
+**
+**  Returned:
+**     status    LOGICAL     TRUE = success, FALSE = fail
+**
+**  Called:  eraTaiutc, vvd, viv
+**
+**  This revision:  2013 October 3
+*/
+{
+   double u1, u2;
+   int j;
+
+
+   j = eraTaiutc(2453750.5, 0.892482639, &u1, &u2);
+
+   vvd(u1, 2453750.5, 1e-6, "eraTaiutc", "u1", status);
+   vvd(u2, 0.8921006945555555556, 1e-12, "eraTaiutc", "u2", status);
+   viv(j, 0, "eraTaiutc", "j", status);
+
+}
+
+static void t_tcbtdb(int *status)
+/*
+**  - - - - - - - - -
+**   t _ t c b t d b
+**  - - - - - - - - -
+**
+**  Test eraTcbtdb function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTcbtdb, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double b1, b2;
+   int j;
+
+
+   j = eraTcbtdb(2453750.5, 0.893019599, &b1, &b2);
+
+   vvd(b1, 2453750.5, 1e-6, "eraTcbtdb", "b1", status);
+   vvd(b2, 0.8928551362746343397, 1e-12, "eraTcbtdb", "b2", status);
+   viv(j, 0, "eraTcbtdb", "j", status);
+
+}
+
+static void t_tcgtt(int *status)
+/*
+**  - - - - - - - -
+**   t _ t c g t t
+**  - - - - - - - -
+**
+**  Test eraTcgtt function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTcgtt, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double t1, t2;
+   int j;
+
+
+   j = eraTcgtt(2453750.5, 0.892862531, &t1, &t2);
+
+   vvd(t1, 2453750.5, 1e-6, "eraTcgtt", "t1", status);
+   vvd(t2, 0.8928551387488816828, 1e-12, "eraTcgtt", "t2", status);
+   viv(j, 0, "eraTcgtt", "j", status);
+
+}
+
+static void t_tdbtcb(int *status)
+/*
+**  - - - - - - - - -
+**   t _ t d b t c b
+**  - - - - - - - - -
+**
+**  Test eraTdbtcb function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTdbtcb, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double b1, b2;
+   int j;
+
+
+   j = eraTdbtcb(2453750.5, 0.892855137, &b1, &b2);
+
+   vvd( b1, 2453750.5, 1e-6, "eraTdbtcb", "b1", status);
+   vvd( b2, 0.8930195997253656716, 1e-12, "eraTdbtcb", "b2", status);
+   viv(j, 0, "eraTdbtcb", "j", status);
+
+}
+
+static void t_tdbtt(int *status)
+/*
+**  - - - - - - - -
+**   t _ t d b t t
+**  - - - - - - - -
+**
+**  Test eraTdbtt function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTdbtt, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double t1, t2;
+   int j;
+
+
+   j = eraTdbtt(2453750.5, 0.892855137, -0.000201, &t1, &t2);
+
+   vvd(t1, 2453750.5, 1e-6, "eraTdbtt", "t1", status);
+   vvd(t2, 0.8928551393263888889, 1e-12, "eraTdbtt", "t2", status);
+   viv(j, 0, "eraTdbtt", "j", status);
+
+}
+
+static void t_tf2a(int *status)
+/*
+**  - - - - - - -
+**   t _ t f 2 a
+**  - - - - - - -
+**
+**  Test eraTf2a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTf2a, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a;
+   int j;
+
+
+   j = eraTf2a('+', 4, 58, 20.2, &a);
+
+   vvd(a, 1.301739278189537429, 1e-12, "eraTf2a", "a", status);
+   viv(j, 0, "eraTf2a", "j", status);
+
+}
+
+static void t_tf2d(int *status)
+/*
+**  - - - - - - -
+**   t _ t f 2 d
+**  - - - - - - -
+**
+**  Test eraTf2d function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTf2d, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double d;
+   int j;
+
+
+   j = eraTf2d(' ', 23, 55, 10.9, &d);
+
+   vvd(d, 0.9966539351851851852, 1e-12, "eraTf2d", "d", status);
+   viv(j, 0, "eraTf2d", "j", status);
+
+}
+
+static void t_tr(int *status)
+/*
+**  - - - - -
+**   t _ t r
+**  - - - - -
+**
+**  Test eraTr function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTr, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r[3][3], rt[3][3];
+
+
+   r[0][0] = 2.0;
+   r[0][1] = 3.0;
+   r[0][2] = 2.0;
+
+   r[1][0] = 3.0;
+   r[1][1] = 2.0;
+   r[1][2] = 3.0;
+
+   r[2][0] = 3.0;
+   r[2][1] = 4.0;
+   r[2][2] = 5.0;
+
+   eraTr(r, rt);
+
+   vvd(rt[0][0], 2.0, 0.0, "eraTr", "11", status);
+   vvd(rt[0][1], 3.0, 0.0, "eraTr", "12", status);
+   vvd(rt[0][2], 3.0, 0.0, "eraTr", "13", status);
+
+   vvd(rt[1][0], 3.0, 0.0, "eraTr", "21", status);
+   vvd(rt[1][1], 2.0, 0.0, "eraTr", "22", status);
+   vvd(rt[1][2], 4.0, 0.0, "eraTr", "23", status);
+
+   vvd(rt[2][0], 2.0, 0.0, "eraTr", "31", status);
+   vvd(rt[2][1], 3.0, 0.0, "eraTr", "32", status);
+   vvd(rt[2][2], 5.0, 0.0, "eraTr", "33", status);
+
+}
+
+static void t_trxp(int *status)
+/*
+**  - - - - - - -
+**   t _ t r x p
+**  - - - - - - -
+**
+**  Test eraTrxp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTrxp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r[3][3], p[3], trp[3];
+
+
+   r[0][0] = 2.0;
+   r[0][1] = 3.0;
+   r[0][2] = 2.0;
+
+   r[1][0] = 3.0;
+   r[1][1] = 2.0;
+   r[1][2] = 3.0;
+
+   r[2][0] = 3.0;
+   r[2][1] = 4.0;
+   r[2][2] = 5.0;
+
+   p[0] = 0.2;
+   p[1] = 1.5;
+   p[2] = 0.1;
+
+   eraTrxp(r, p, trp);
+
+   vvd(trp[0], 5.2, 1e-12, "eraTrxp", "1", status);
+   vvd(trp[1], 4.0, 1e-12, "eraTrxp", "2", status);
+   vvd(trp[2], 5.4, 1e-12, "eraTrxp", "3", status);
+
+}
+
+static void t_trxpv(int *status)
+/*
+**  - - - - - - - -
+**   t _ t r x p v
+**  - - - - - - - -
+**
+**  Test eraTrxpv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTrxpv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r[3][3], pv[2][3], trpv[2][3];
+
+
+   r[0][0] = 2.0;
+   r[0][1] = 3.0;
+   r[0][2] = 2.0;
+
+   r[1][0] = 3.0;
+   r[1][1] = 2.0;
+   r[1][2] = 3.0;
+
+   r[2][0] = 3.0;
+   r[2][1] = 4.0;
+   r[2][2] = 5.0;
+
+   pv[0][0] = 0.2;
+   pv[0][1] = 1.5;
+   pv[0][2] = 0.1;
+
+   pv[1][0] = 1.5;
+   pv[1][1] = 0.2;
+   pv[1][2] = 0.1;
+
+   eraTrxpv(r, pv, trpv);
+
+   vvd(trpv[0][0], 5.2, 1e-12, "eraTrxpv", "p1", status);
+   vvd(trpv[0][1], 4.0, 1e-12, "eraTrxpv", "p1", status);
+   vvd(trpv[0][2], 5.4, 1e-12, "eraTrxpv", "p1", status);
+
+   vvd(trpv[1][0], 3.9, 1e-12, "eraTrxpv", "v1", status);
+   vvd(trpv[1][1], 5.3, 1e-12, "eraTrxpv", "v2", status);
+   vvd(trpv[1][2], 4.1, 1e-12, "eraTrxpv", "v3", status);
+
+}
+
+static void t_tttai(int *status)
+/*
+**  - - - - - - - -
+**   t _ t t t a i
+**  - - - - - - - -
+**
+**  Test eraTttai function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTttai, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a1, a2;
+   int j;
+
+
+   j = eraTttai(2453750.5, 0.892482639, &a1, &a2);
+
+   vvd(a1, 2453750.5, 1e-6, "eraTttai", "a1", status);
+   vvd(a2, 0.892110139, 1e-12, "eraTttai", "a2", status);
+   viv(j, 0, "eraTttai", "j", status);
+
+}
+
+static void t_tttcg(int *status)
+/*
+**  - - - - - - - -
+**   t _ t t t c g
+**  - - - - - - - -
+**
+**  Test eraTttcg function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTttcg, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double g1, g2;
+   int j;
+
+
+   j = eraTttcg(2453750.5, 0.892482639, &g1, &g2);
+
+   vvd( g1, 2453750.5, 1e-6, "eraTttcg", "g1", status);
+   vvd( g2, 0.8924900312508587113, 1e-12, "eraTttcg", "g2", status);
+   viv(j, 0, "eraTttcg", "j", status);
+
+}
+
+static void t_tttdb(int *status)
+/*
+**  - - - - - - - -
+**   t _ t t t d b
+**  - - - - - - - -
+**
+**  Test eraTttdb function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTttdb, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double b1, b2;
+   int j;
+
+
+   j = eraTttdb(2453750.5, 0.892855139, -0.000201, &b1, &b2);
+
+   vvd(b1, 2453750.5, 1e-6, "eraTttdb", "b1", status);
+   vvd(b2, 0.8928551366736111111, 1e-12, "eraTttdb", "b2", status);
+   viv(j, 0, "eraTttdb", "j", status);
+
+}
+
+static void t_ttut1(int *status)
+/*
+**  - - - - - - - -
+**   t _ t t u t 1
+**  - - - - - - - -
+**
+**  Test eraTtut1 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraTtut1, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double u1, u2;
+   int j;
+
+
+   j = eraTtut1(2453750.5, 0.892855139, 64.8499, &u1, &u2);
+
+   vvd(u1, 2453750.5, 1e-6, "eraTtut1", "u1", status);
+   vvd(u2, 0.8921045614537037037, 1e-12, "eraTtut1", "u2", status);
+   viv(j, 0, "eraTtut1", "j", status);
+
+}
+
+static void t_ut1tai(int *status)
+/*
+**  - - - - - - - - -
+**   t _ u t 1 t a i
+**  - - - - - - - - -
+**
+**  Test eraUt1tai function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraUt1tai, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double a1, a2;
+   int j;
+
+
+   j = eraUt1tai(2453750.5, 0.892104561, -32.6659, &a1, &a2);
+
+   vvd(a1, 2453750.5, 1e-6, "eraUt1tai", "a1", status);
+   vvd(a2, 0.8924826385462962963, 1e-12, "eraUt1tai", "a2", status);
+   viv(j, 0, "eraUt1tai", "j", status);
+
+}
+
+static void t_ut1tt(int *status)
+/*
+**  - - - - - - - -
+**   t _ u t 1 t t
+**  - - - - - - - -
+**
+**  Test eraUt1tt function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraUt1tt, vvd, viv
+**
+**  This revision:  2013 October 3
+*/
+{
+   double t1, t2;
+   int j;
+
+
+   j = eraUt1tt(2453750.5, 0.892104561, 64.8499, &t1, &t2);
+
+   vvd(t1, 2453750.5, 1e-6, "eraUt1tt", "t1", status);
+   vvd(t2, 0.8928551385462962963, 1e-12, "eraUt1tt", "t2", status);
+   viv(j, 0, "eraUt1tt", "j", status);
+
+}
+
+static void t_ut1utc(int *status)
+/*
+**  - - - - - - - - -
+**   t _ u t 1 u t c
+**  - - - - - - - - -
+**
+**  Test eraUt1utc function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraUt1utc, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double u1, u2;
+   int j;
+
+
+   j = eraUt1utc(2453750.5, 0.892104561, 0.3341, &u1, &u2);
+
+   vvd(u1, 2453750.5, 1e-6, "eraUt1utc", "u1", status);
+   vvd(u2, 0.8921006941018518519, 1e-12, "eraUt1utc", "u2", status);
+   viv(j, 0, "eraUt1utc", "j", status);
+
+}
+
+static void t_utctai(int *status)
+/*
+**  - - - - - - - - -
+**   t _ u t c t a i
+**  - - - - - - - - -
+**
+**  Test eraUtctai function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraUtctai, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double u1, u2;
+   int j;
+
+
+   j = eraUtctai(2453750.5, 0.892100694, &u1, &u2);
+
+   vvd(u1, 2453750.5, 1e-6, "eraUtctai", "u1", status);
+   vvd(u2, 0.8924826384444444444, 1e-12, "eraUtctai", "u2", status);
+   viv(j, 0, "eraUtctai", "j", status);
+
+}
+
+static void t_utcut1(int *status)
+/*
+**  - - - - - - - - -
+**   t _ u t c u t 1
+**  - - - - - - - - -
+**
+**  Test eraUtcut1 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraUtcut1, vvd, viv
+**
+**  This revision:  2013 August 7
+*/
+{
+   double u1, u2;
+   int j;
+
+
+   j = eraUtcut1(2453750.5, 0.892100694, 0.3341, &u1, &u2);
+
+   vvd(u1, 2453750.5, 1e-6, "eraUtcut1", "u1", status);
+   vvd(u2, 0.8921045608981481481, 1e-12, "eraUtcut1", "u2", status);
+   viv(j, 0, "eraUtcut1", "j", status);
+
+}
+
+static void t_xy06(int *status)
+/*
+**  - - - - - - -
+**   t _ x y 0 6
+**  - - - - - - -
+**
+**  Test eraXy06 function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraXy06, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double x, y;
+
+
+   eraXy06(2400000.5, 53736.0, &x, &y);
+
+   vvd(x, 0.5791308486706010975e-3, 1e-15, "eraXy06", "x", status);
+   vvd(y, 0.4020579816732958141e-4, 1e-16, "eraXy06", "y", status);
+
+}
+
+static void t_xys00a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ x y s 0 0 a
+**  - - - - - - - - -
+**
+**  Test eraXys00a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraXys00a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double x, y, s;
+
+
+   eraXys00a(2400000.5, 53736.0, &x, &y, &s);
+
+   vvd(x,  0.5791308472168152904e-3, 1e-14, "eraXys00a", "x", status);
+   vvd(y,  0.4020595661591500259e-4, 1e-15, "eraXys00a", "y", status);
+   vvd(s, -0.1220040848471549623e-7, 1e-18, "eraXys00a", "s", status);
+
+}
+
+static void t_xys00b(int *status)
+/*
+**  - - - - - - - - -
+**   t _ x y s 0 0 b
+**  - - - - - - - - -
+**
+**  Test eraXys00b function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraXys00b, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double x, y, s;
+
+
+   eraXys00b(2400000.5, 53736.0, &x, &y, &s);
+
+   vvd(x,  0.5791301929950208873e-3, 1e-14, "eraXys00b", "x", status);
+   vvd(y,  0.4020553681373720832e-4, 1e-15, "eraXys00b", "y", status);
+   vvd(s, -0.1220027377285083189e-7, 1e-18, "eraXys00b", "s", status);
+
+}
+
+static void t_xys06a(int *status)
+/*
+**  - - - - - - - - -
+**   t _ x y s 0 6 a
+**  - - - - - - - - -
+**
+**  Test eraXys06a function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraXys06a, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double x, y, s;
+
+
+   eraXys06a(2400000.5, 53736.0, &x, &y, &s);
+
+   vvd(x,  0.5791308482835292617e-3, 1e-14, "eraXys06a", "x", status);
+   vvd(y,  0.4020580099454020310e-4, 1e-15, "eraXys06a", "y", status);
+   vvd(s, -0.1220032294164579896e-7, 1e-18, "eraXys06a", "s", status);
+
+}
+
+static void t_zp(int *status)
+/*
+**  - - - - -
+**   t _ z p
+**  - - - - -
+**
+**  Test eraZp function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraZp, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double p[3];
+
+
+   p[0] =  0.3;
+   p[1] =  1.2;
+   p[2] = -2.5;
+
+   eraZp(p);
+
+   vvd(p[0], 0.0, 0.0, "eraZp", "1", status);
+   vvd(p[1], 0.0, 0.0, "eraZp", "2", status);
+   vvd(p[2], 0.0, 0.0, "eraZp", "3", status);
+
+}
+
+static void t_zpv(int *status)
+/*
+**  - - - - - -
+**   t _ z p v
+**  - - - - - -
+**
+**  Test eraZpv function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraZpv, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double pv[2][3];
+
+
+   pv[0][0] =  0.3;
+   pv[0][1] =  1.2;
+   pv[0][2] = -2.5;
+
+   pv[1][0] = -0.5;
+   pv[1][1] =  3.1;
+   pv[1][2] =  0.9;
+
+   eraZpv(pv);
+
+   vvd(pv[0][0], 0.0, 0.0, "eraZpv", "p1", status);
+   vvd(pv[0][1], 0.0, 0.0, "eraZpv", "p2", status);
+   vvd(pv[0][2], 0.0, 0.0, "eraZpv", "p3", status);
+
+   vvd(pv[1][0], 0.0, 0.0, "eraZpv", "v1", status);
+   vvd(pv[1][1], 0.0, 0.0, "eraZpv", "v2", status);
+   vvd(pv[1][2], 0.0, 0.0, "eraZpv", "v3", status);
+
+}
+
+static void t_zr(int *status)
+/*
+**  - - - - -
+**   t _ z r
+**  - - - - -
+**
+**  Test eraZr function.
+**
+**  Returned:
+**     status    int         FALSE = success, TRUE = fail
+**
+**  Called:  eraZr, vvd
+**
+**  This revision:  2013 August 7
+*/
+{
+   double r[3][3];
+
+
+   r[0][0] = 2.0;
+   r[1][0] = 3.0;
+   r[2][0] = 2.0;
+
+   r[0][1] = 3.0;
+   r[1][1] = 2.0;
+   r[2][1] = 3.0;
+
+   r[0][2] = 3.0;
+   r[1][2] = 4.0;
+   r[2][2] = 5.0;
+
+   eraZr(r);
+
+   vvd(r[0][0], 0.0, 0.0, "eraZr", "00", status);
+   vvd(r[1][0], 0.0, 0.0, "eraZr", "01", status);
+   vvd(r[2][0], 0.0, 0.0, "eraZr", "02", status);
+
+   vvd(r[0][1], 0.0, 0.0, "eraZr", "10", status);
+   vvd(r[1][1], 0.0, 0.0, "eraZr", "11", status);
+   vvd(r[2][1], 0.0, 0.0, "eraZr", "12", status);
+
+   vvd(r[0][2], 0.0, 0.0, "eraZr", "20", status);
+   vvd(r[1][2], 0.0, 0.0, "eraZr", "21", status);
+   vvd(r[2][2], 0.0, 0.0, "eraZr", "22", status);
+
+}
+
+int main(int argc, char *argv[])
+/*
+**  - - - - -
+**   m a i n
+**  - - - - -
+**
+**  This revision:  2016 March 12
+*/
+{
+   int status;
+
+
+/* If any command-line argument, switch to verbose reporting. */
+   if (argc > 1) {
+      verbose = 1;
+      argv[0][0] += 0;    /* to avoid compiler warnings */
+   }
+
+/* Preset the &status to FALSE = success. */
+   status = 0;
+
+/* Test all of the ERFA functions. */
+   t_a2af(&status);
+   t_a2tf(&status);
+   t_ab(&status);
+   t_af2a(&status);
+   t_anp(&status);
+   t_anpm(&status);
+   t_apcg(&status);
+   t_apcg13(&status);
+   t_apci(&status);
+   t_apci13(&status);
+   t_apco(&status);
+   t_apco13(&status);
+   t_apcs(&status);
+   t_apcs13(&status);
+   t_aper(&status);
+   t_aper13(&status);
+   t_apio(&status);
+   t_apio13(&status);
+   t_atci13(&status);
+   t_atciq(&status);
+   t_atciqn(&status);
+   t_atciqz(&status);
+   t_atco13(&status);
+   t_atic13(&status);
+   t_aticq(&status);
+   t_aticqn(&status);
+   t_atio13(&status);
+   t_atioq(&status);
+   t_atoc13(&status);
+   t_atoi13(&status);
+   t_atoiq(&status);
+   t_bi00(&status);
+   t_bp00(&status);
+   t_bp06(&status);
+   t_bpn2xy(&status);
+   t_c2i00a(&status);
+   t_c2i00b(&status);
+   t_c2i06a(&status);
+   t_c2ibpn(&status);
+   t_c2ixy(&status);
+   t_c2ixys(&status);
+   t_c2s(&status);
+   t_c2t00a(&status);
+   t_c2t00b(&status);
+   t_c2t06a(&status);
+   t_c2tcio(&status);
+   t_c2teqx(&status);
+   t_c2tpe(&status);
+   t_c2txy(&status);
+   t_cal2jd(&status);
+   t_cp(&status);
+   t_cpv(&status);
+   t_cr(&status);
+   t_d2dtf(&status);
+   t_d2tf(&status);
+   t_dat(&status);
+   t_dtdb(&status);
+   t_dtf2d(&status);
+   t_eceq06(&status);
+   t_ecm06(&status);
+   t_ee00(&status);
+   t_ee00a(&status);
+   t_ee00b(&status);
+   t_ee06a(&status);
+   t_eect00(&status);
+   t_eform(&status);
+   t_eo06a(&status);
+   t_eors(&status);
+   t_epb(&status);
+   t_epb2jd(&status);
+   t_epj(&status);
+   t_epj2jd(&status);
+   t_epv00(&status);
+   t_eqec06(&status);
+   t_eqeq94(&status);
+   t_era00(&status);
+   t_fad03(&status);
+   t_fae03(&status);
+   t_faf03(&status);
+   t_faju03(&status);
+   t_fal03(&status);
+   t_falp03(&status);
+   t_fama03(&status);
+   t_fame03(&status);
+   t_fane03(&status);
+   t_faom03(&status);
+   t_fapa03(&status);
+   t_fasa03(&status);
+   t_faur03(&status);
+   t_fave03(&status);
+   t_fk52h(&status);
+   t_fk5hip(&status);
+   t_fk5hz(&status);
+   t_fw2m(&status);
+   t_fw2xy(&status);
+   t_g2icrs(&status);
+   t_gc2gd(&status);
+   t_gc2gde(&status);
+   t_gd2gc(&status);
+   t_gd2gce(&status);
+   t_gmst00(&status);
+   t_gmst06(&status);
+   t_gmst82(&status);
+   t_gst00a(&status);
+   t_gst00b(&status);
+   t_gst06(&status);
+   t_gst06a(&status);
+   t_gst94(&status);
+   t_h2fk5(&status);
+   t_hfk5z(&status);
+   t_icrs2g(&status);
+   t_ir(&status);
+   t_jd2cal(&status);
+   t_jdcalf(&status);
+   t_ld(&status);
+   t_ldn(&status);
+   t_ldsun(&status);
+   t_lteceq(&status);
+   t_ltecm(&status);
+   t_lteqec(&status);
+   t_ltp(&status);
+   t_ltpb(&status);
+   t_ltpecl(&status);
+   t_ltpequ(&status);
+   t_num00a(&status);
+   t_num00b(&status);
+   t_num06a(&status);
+   t_numat(&status);
+   t_nut00a(&status);
+   t_nut00b(&status);
+   t_nut06a(&status);
+   t_nut80(&status);
+   t_nutm80(&status);
+   t_obl06(&status);
+   t_obl80(&status);
+   t_p06e(&status);
+   t_p2pv(&status);
+   t_p2s(&status);
+   t_pap(&status);
+   t_pas(&status);
+   t_pb06(&status);
+   t_pdp(&status);
+   t_pfw06(&status);
+   t_plan94(&status);
+   t_pmat00(&status);
+   t_pmat06(&status);
+   t_pmat76(&status);
+   t_pm(&status);
+   t_pmp(&status);
+   t_pmpx(&status);
+   t_pmsafe(&status);
+   t_pn(&status);
+   t_pn00(&status);
+   t_pn00a(&status);
+   t_pn00b(&status);
+   t_pn06a(&status);
+   t_pn06(&status);
+   t_pnm00a(&status);
+   t_pnm00b(&status);
+   t_pnm06a(&status);
+   t_pnm80(&status);
+   t_pom00(&status);
+   t_ppp(&status);
+   t_ppsp(&status);
+   t_pr00(&status);
+   t_prec76(&status);
+   t_pv2p(&status);
+   t_pv2s(&status);
+   t_pvdpv(&status);
+   t_pvm(&status);
+   t_pvmpv(&status);
+   t_pvppv(&status);
+   t_pvstar(&status);
+   t_pvtob(&status);
+   t_pvu(&status);
+   t_pvup(&status);
+   t_pvxpv(&status);
+   t_pxp(&status);
+   t_refco(&status);
+   t_rm2v(&status);
+   t_rv2m(&status);
+   t_rx(&status);
+   t_rxp(&status);
+   t_rxpv(&status);
+   t_rxr(&status);
+   t_ry(&status);
+   t_rz(&status);
+   t_s00a(&status);
+   t_s00b(&status);
+   t_s00(&status);
+   t_s06a(&status);
+   t_s06(&status);
+   t_s2c(&status);
+   t_s2p(&status);
+   t_s2pv(&status);
+   t_s2xpv(&status);
+   t_sepp(&status);
+   t_seps(&status);
+   t_sp00(&status);
+   t_starpm(&status);
+   t_starpv(&status);
+   t_sxp(&status);
+   t_sxpv(&status);
+   t_taitt(&status);
+   t_taiut1(&status);
+   t_taiutc(&status);
+   t_tcbtdb(&status);
+   t_tcgtt(&status);
+   t_tdbtcb(&status);
+   t_tdbtt(&status);
+   t_tf2a(&status);
+   t_tf2d(&status);
+   t_tr(&status);
+   t_trxp(&status);
+   t_trxpv(&status);
+   t_tttai(&status);
+   t_tttcg(&status);
+   t_tttdb(&status);
+   t_ttut1(&status);
+   t_ut1tai(&status);
+   t_ut1tt(&status) ;
+   t_ut1utc(&status);
+   t_utctai(&status);
+   t_utcut1(&status);
+   t_xy06(&status);
+   t_xys00a(&status);
+   t_xys00b(&status);
+   t_xys06a(&status);
+   t_zp(&status);
+   t_zpv(&status);
+   t_zr(&status);
+
+/* Report, set up an appropriate exit status, and finish. */
+   if (status) {
+      printf("t_erfa_c validation failed!\n");
+   } else {
+      printf("t_erfa_c validation successful\n");
+   }
+   return status;
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/taitt.c b/ast/erfa/taitt.c
new file mode 100644
index 0000000..087399e
--- /dev/null
+++ b/ast/erfa/taitt.c
@@ -0,0 +1,119 @@
+#include "erfa.h"
+
+int eraTaitt(double tai1, double tai2, double *tt1, double *tt2)
+/*
+**  - - - - - - - - -
+**   e r a T a i t t
+**  - - - - - - - - -
+**
+**  Time scale transformation:  International Atomic Time, TAI, to
+**  Terrestrial Time, TT.
+**
+**  Given:
+**     tai1,tai2  double    TAI as a 2-part Julian Date
+**
+**  Returned:
+**     tt1,tt2    double    TT as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Note:
+**
+**     tai1+tai2 is Julian Date, apportioned in any convenient way
+**     between the two arguments, for example where tai1 is the Julian
+**     Day Number and tai2 is the fraction of a day.  The returned
+**     tt1,tt2 follow suit.
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* TT minus TAI (days). */
+   static const double dtat = ERFA_TTMTAI/ERFA_DAYSEC;
+
+
+/* Result, safeguarding precision. */
+   if ( tai1 > tai2 ) {
+      *tt1 = tai1;
+      *tt2 = tai2 + dtat;
+   } else {
+      *tt1 = tai1 + dtat;
+      *tt2 = tai2;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/taiut1.c b/ast/erfa/taiut1.c
new file mode 100644
index 0000000..4a9a485
--- /dev/null
+++ b/ast/erfa/taiut1.c
@@ -0,0 +1,120 @@
+#include "erfa.h"
+
+int eraTaiut1(double tai1, double tai2, double dta,
+              double *ut11, double *ut12)
+/*
+**  - - - - - - - - - -
+**   e r a T a i u t 1
+**  - - - - - - - - - -
+**
+**  Time scale transformation:  International Atomic Time, TAI, to
+**  Universal Time, UT1.
+**
+**  Given:
+**     tai1,tai2  double    TAI as a 2-part Julian Date
+**     dta        double    UT1-TAI in seconds
+**
+**  Returned:
+**     ut11,ut12  double    UT1 as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Notes:
+**
+**  1) tai1+tai2 is Julian Date, apportioned in any convenient way
+**     between the two arguments, for example where tai1 is the Julian
+**     Day Number and tai2 is the fraction of a day.  The returned
+**     UT11,UT12 follow suit.
+**
+**  2) The argument dta, i.e. UT1-TAI, is an observed quantity, and is
+**     available from IERS tabulations.
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dtad;
+
+
+/* Result, safeguarding precision. */
+   dtad = dta / ERFA_DAYSEC;
+   if ( tai1 > tai2 ) {
+      *ut11 = tai1;
+      *ut12 = tai2 + dtad;
+   } else {
+      *ut11 = tai1 + dtad;
+      *ut12 = tai2;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/taiutc.c b/ast/erfa/taiutc.c
new file mode 100644
index 0000000..ce3a177
--- /dev/null
+++ b/ast/erfa/taiutc.c
@@ -0,0 +1,168 @@
+#include "erfa.h"
+
+int eraTaiutc(double tai1, double tai2, double *utc1, double *utc2)
+/*
+**  - - - - - - - - - -
+**   e r a T a i u t c
+**  - - - - - - - - - -
+**
+**  Time scale transformation:  International Atomic Time, TAI, to
+**  Coordinated Universal Time, UTC.
+**
+**  Given:
+**     tai1,tai2  double   TAI as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     utc1,utc2  double   UTC as a 2-part quasi Julian Date (Notes 1-3)
+**
+**  Returned (function value):
+**                int      status: +1 = dubious year (Note 4)
+**                                  0 = OK
+**                                 -1 = unacceptable date
+**
+**  Notes:
+**
+**  1) tai1+tai2 is Julian Date, apportioned in any convenient way
+**     between the two arguments, for example where tai1 is the Julian
+**     Day Number and tai2 is the fraction of a day.  The returned utc1
+**     and utc2 form an analogous pair, except that a special convention
+**     is used, to deal with the problem of leap seconds - see the next
+**     note.
+**
+**  2) JD cannot unambiguously represent UTC during a leap second unless
+**     special measures are taken.  The convention in the present
+**     function is that the JD day represents UTC days whether the
+**     length is 86399, 86400 or 86401 SI seconds.  In the 1960-1972 era
+**     there were smaller jumps (in either direction) each time the
+**     linear UTC(TAI) expression was changed, and these "mini-leaps"
+**     are also included in the ERFA convention.
+**
+**  3) The function eraD2dtf can be used to transform the UTC quasi-JD
+**     into calendar date and clock time, including UTC leap second
+**     handling.
+**
+**  4) The warning status "dubious year" flags UTCs that predate the
+**     introduction of the time scale or that are too far in the future
+**     to be trusted.  See eraDat for further details.
+**
+**  Called:
+**     eraUtctai    UTC to TAI
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int big1;
+   int i, j;
+   double a1, a2, u1, u2, g1, g2;
+
+
+/* Put the two parts of the TAI into big-first order. */
+   big1 = ( tai1 >= tai2 );
+   if ( big1 ) {
+      a1 = tai1;
+      a2 = tai2;
+   } else {
+      a1 = tai2;
+      a2 = tai1;
+   }
+
+/* Initial guess for UTC. */
+   u1 = a1;
+   u2 = a2;
+
+/* Iterate (though in most cases just once is enough). */
+   for ( i = 0; i < 3; i++ ) {
+
+   /* Guessed UTC to TAI. */
+      j = eraUtctai(u1, u2, &g1, &g2);
+      if ( j < 0 ) return j;
+
+   /* Adjust guessed UTC. */
+      u2 += a1 - g1;
+      u2 += a2 - g2;
+   }
+
+/* Return the UTC result, preserving the TAI order. */
+   if ( big1 ) {
+      *utc1 = u1;
+      *utc2 = u2;
+   } else {
+      *utc1 = u2;
+      *utc2 = u1;
+   }
+
+/* Status. */
+   return j;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/tcbtdb.c b/ast/erfa/tcbtdb.c
new file mode 100644
index 0000000..007af2e
--- /dev/null
+++ b/ast/erfa/tcbtdb.c
@@ -0,0 +1,141 @@
+#include "erfa.h"
+
+int eraTcbtdb(double tcb1, double tcb2, double *tdb1, double *tdb2)
+/*
+**  - - - - - - - - - -
+**   e r a T c b t d b
+**  - - - - - - - - - -
+**
+**  Time scale transformation:  Barycentric Coordinate Time, TCB, to
+**  Barycentric Dynamical Time, TDB.
+**
+**  Given:
+**     tcb1,tcb2  double    TCB as a 2-part Julian Date
+**
+**  Returned:
+**     tdb1,tdb2  double    TDB as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Notes:
+**
+**  1) tcb1+tcb2 is Julian Date, apportioned in any convenient way
+**     between the two arguments, for example where tcb1 is the Julian
+**     Day Number and tcb2 is the fraction of a day.  The returned
+**     tdb1,tdb2 follow suit.
+**
+**  2) The 2006 IAU General Assembly introduced a conventional linear
+**     transformation between TDB and TCB.  This transformation
+**     compensates for the drift between TCB and terrestrial time TT,
+**     and keeps TDB approximately centered on TT.  Because the
+**     relationship between TT and TCB depends on the adopted solar
+**     system ephemeris, the degree of alignment between TDB and TT over
+**     long intervals will vary according to which ephemeris is used.
+**     Former definitions of TDB attempted to avoid this problem by
+**     stipulating that TDB and TT should differ only by periodic
+**     effects.  This is a good description of the nature of the
+**     relationship but eluded precise mathematical formulation.  The
+**     conventional linear relationship adopted in 2006 sidestepped
+**     these difficulties whilst delivering a TDB that in practice was
+**     consistent with values before that date.
+**
+**  3) TDB is essentially the same as Teph, the time argument for the
+**     JPL solar system ephemerides.
+**
+**  Reference:
+**
+**     IAU 2006 Resolution B3
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* 1977 Jan 1 00:00:32.184 TT, as two-part JD */
+   static const double t77td = ERFA_DJM0 + ERFA_DJM77;
+   static const double t77tf = ERFA_TTMTAI/ERFA_DAYSEC;
+
+/* TDB (days) at TAI 1977 Jan 1.0 */
+   static const double tdb0 = ERFA_TDB0/ERFA_DAYSEC;
+
+   double d;
+
+
+/* Result, safeguarding precision. */
+   if ( tcb1 > tcb2 ) {
+      d = tcb1 - t77td;
+      *tdb1 = tcb1;
+      *tdb2 = tcb2 + tdb0 - ( d + ( tcb2 - t77tf ) ) * ERFA_ELB;
+   } else {
+      d = tcb2 - t77td;
+      *tdb1 = tcb1 + tdb0 - ( d + ( tcb1 - t77tf ) ) * ERFA_ELB;
+      *tdb2 = tcb2;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/tcgtt.c b/ast/erfa/tcgtt.c
new file mode 100644
index 0000000..0e463b4
--- /dev/null
+++ b/ast/erfa/tcgtt.c
@@ -0,0 +1,118 @@
+#include "erfa.h"
+
+int eraTcgtt(double tcg1, double tcg2, double *tt1, double *tt2)
+/*
+**  - - - - - - - - -
+**   e r a T c g t t
+**  - - - - - - - - -
+**
+**  Time scale transformation:  Geocentric Coordinate Time, TCG, to
+**  Terrestrial Time, TT.
+**
+**  Given:
+**     tcg1,tcg2  double    TCG as a 2-part Julian Date
+**
+**  Returned:
+**     tt1,tt2    double    TT as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Note:
+**
+**     tcg1+tcg2 is Julian Date, apportioned in any convenient way
+**     between the two arguments, for example where tcg1 is the Julian
+**     Day Number and tcg22 is the fraction of a day.  The returned
+**     tt1,tt2 follow suit.
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),.
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     IAU 2000 Resolution B1.9
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* 1977 Jan 1 00:00:32.184 TT, as MJD */
+   static const double t77t = ERFA_DJM77 + ERFA_TTMTAI/ERFA_DAYSEC;
+
+
+/* Result, safeguarding precision. */
+   if ( tcg1 > tcg2 ) {
+      *tt1 = tcg1;
+      *tt2 = tcg2 - ( ( tcg1 - ERFA_DJM0 ) + ( tcg2 - t77t ) ) * ERFA_ELG;
+   } else {
+      *tt1 = tcg1 - ( ( tcg2 - ERFA_DJM0 ) + ( tcg1 - t77t ) ) * ERFA_ELG;
+      *tt2 = tcg2;
+   }
+
+/* OK status. */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/tdbtcb.c b/ast/erfa/tdbtcb.c
new file mode 100644
index 0000000..c12f807
--- /dev/null
+++ b/ast/erfa/tdbtcb.c
@@ -0,0 +1,146 @@
+#include "erfa.h"
+
+int eraTdbtcb(double tdb1, double tdb2, double *tcb1, double *tcb2)
+/*
+**  - - - - - - - - - -
+**   e r a T d b t c b
+**  - - - - - - - - - -
+**
+**  Time scale transformation:  Barycentric Dynamical Time, TDB, to
+**  Barycentric Coordinate Time, TCB.
+**
+**  Given:
+**     tdb1,tdb2  double    TDB as a 2-part Julian Date
+**
+**  Returned:
+**     tcb1,tcb2  double    TCB as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Notes:
+**
+**  1) tdb1+tdb2 is Julian Date, apportioned in any convenient way
+**     between the two arguments, for example where tdb1 is the Julian
+**     Day Number and tdb2 is the fraction of a day.  The returned
+**     tcb1,tcb2 follow suit.
+**
+**  2) The 2006 IAU General Assembly introduced a conventional linear
+**     transformation between TDB and TCB.  This transformation
+**     compensates for the drift between TCB and terrestrial time TT,
+**     and keeps TDB approximately centered on TT.  Because the
+**     relationship between TT and TCB depends on the adopted solar
+**     system ephemeris, the degree of alignment between TDB and TT over
+**     long intervals will vary according to which ephemeris is used.
+**     Former definitions of TDB attempted to avoid this problem by
+**     stipulating that TDB and TT should differ only by periodic
+**     effects.  This is a good description of the nature of the
+**     relationship but eluded precise mathematical formulation.  The
+**     conventional linear relationship adopted in 2006 sidestepped
+**     these difficulties whilst delivering a TDB that in practice was
+**     consistent with values before that date.
+**
+**  3) TDB is essentially the same as Teph, the time argument for the
+**     JPL solar system ephemerides.
+**
+**  Reference:
+**
+**     IAU 2006 Resolution B3
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* 1977 Jan 1 00:00:32.184 TT, as two-part JD */
+   static const double t77td = ERFA_DJM0 + ERFA_DJM77;
+   static const double t77tf = ERFA_TTMTAI/ERFA_DAYSEC;
+
+/* TDB (days) at TAI 1977 Jan 1.0 */
+   static const double tdb0 = ERFA_TDB0/ERFA_DAYSEC;
+
+/* TDB to TCB rate */
+   static const double elbb = ERFA_ELB/(1.0-ERFA_ELB);
+
+   double d, f;
+
+
+/* Result, preserving date format but safeguarding precision. */
+   if ( tdb1 > tdb2 ) {
+      d = t77td - tdb1;
+      f  = tdb2 - tdb0;
+      *tcb1 = tdb1;
+      *tcb2 = f - ( d - ( f - t77tf ) ) * elbb;
+   } else {
+      d = t77td - tdb2;
+      f  = tdb1 - tdb0;
+      *tcb1 = f + ( d - ( f - t77tf ) ) * elbb;
+      *tcb2 = tdb2;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/tdbtt.c b/ast/erfa/tdbtt.c
new file mode 100644
index 0000000..879ffe5
--- /dev/null
+++ b/ast/erfa/tdbtt.c
@@ -0,0 +1,130 @@
+#include "erfa.h"
+
+int eraTdbtt(double tdb1, double tdb2, double dtr,
+             double *tt1, double *tt2 )
+/*
+**  - - - - - - - - -
+**   e r a T d b t t
+**  - - - - - - - - -
+**
+**  Time scale transformation:  Barycentric Dynamical Time, TDB, to
+**  Terrestrial Time, TT.
+**
+**  Given:
+**     tdb1,tdb2  double    TDB as a 2-part Julian Date
+**     dtr        double    TDB-TT in seconds
+**
+**  Returned:
+**     tt1,tt2    double    TT as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Notes:
+**
+**  1) tdb1+tdb2 is Julian Date, apportioned in any convenient way
+**     between the two arguments, for example where tdb1 is the Julian
+**     Day Number and tdb2 is the fraction of a day.  The returned
+**     tt1,tt2 follow suit.
+**
+**  2) The argument dtr represents the quasi-periodic component of the
+**     GR transformation between TT and TCB.  It is dependent upon the
+**     adopted solar-system ephemeris, and can be obtained by numerical
+**     integration, by interrogating a precomputed time ephemeris or by
+**     evaluating a model such as that implemented in the ERFA function
+**     eraDtdb.   The quantity is dominated by an annual term of 1.7 ms
+**     amplitude.
+**
+**  3) TDB is essentially the same as Teph, the time argument for the
+**     JPL solar system ephemerides.
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     IAU 2006 Resolution 3
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dtrd;
+
+
+/* Result, safeguarding precision. */
+   dtrd = dtr / ERFA_DAYSEC;
+   if ( tdb1 > tdb2 ) {
+      *tt1 = tdb1;
+      *tt2 = tdb2 - dtrd;
+   } else {
+      *tt1 = tdb1 - dtrd;
+      *tt2 = tdb2;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/tf2a.c b/ast/erfa/tf2a.c
new file mode 100644
index 0000000..678a33d
--- /dev/null
+++ b/ast/erfa/tf2a.c
@@ -0,0 +1,116 @@
+#include "erfa.h"
+#include <stdlib.h>
+
+int eraTf2a(char s, int ihour, int imin, double sec, double *rad)
+/*
+**  - - - - - - - -
+**   e r a T f 2 a
+**  - - - - - - - -
+**
+**  Convert hours, minutes, seconds to radians.
+**
+**  Given:
+**     s         char    sign:  '-' = negative, otherwise positive
+**     ihour     int     hours
+**     imin      int     minutes
+**     sec       double  seconds
+**
+**  Returned:
+**     rad       double  angle in radians
+**
+**  Returned (function value):
+**               int     status:  0 = OK
+**                                1 = ihour outside range 0-23
+**                                2 = imin outside range 0-59
+**                                3 = sec outside range 0-59.999...
+**
+**  Notes:
+**
+**  1)  The result is computed even if any of the range checks fail.
+**
+**  2)  Negative ihour, imin and/or sec produce a warning status, but
+**      the absolute value is used in the conversion.
+**
+**  3)  If there are multiple errors, the status value reflects only the
+**      first, the smallest taking precedence.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* Compute the interval. */
+   *rad  = ( s == '-' ? -1.0 : 1.0 ) *
+           ( 60.0 * ( 60.0 * ( (double) abs(ihour) ) +
+                             ( (double) abs(imin) ) ) +
+                                        fabs(sec) ) * ERFA_DS2R;
+
+/* Validate arguments and return status. */
+   if ( ihour < 0 || ihour > 23 ) return 1;
+   if ( imin < 0 || imin > 59 ) return 2;
+   if ( sec < 0.0 || sec >= 60.0 ) return 3;
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/tf2d.c b/ast/erfa/tf2d.c
new file mode 100644
index 0000000..3936bfc
--- /dev/null
+++ b/ast/erfa/tf2d.c
@@ -0,0 +1,116 @@
+#include "erfa.h"
+#include <stdlib.h>
+
+int eraTf2d(char s, int ihour, int imin, double sec, double *days)
+/*
+**  - - - - - - - -
+**   e r a T f 2 d
+**  - - - - - - - -
+**
+**  Convert hours, minutes, seconds to days.
+**
+**  Given:
+**     s         char    sign:  '-' = negative, otherwise positive
+**     ihour     int     hours
+**     imin      int     minutes
+**     sec       double  seconds
+**
+**  Returned:
+**     days      double  interval in days
+**
+**  Returned (function value):
+**               int     status:  0 = OK
+**                                1 = ihour outside range 0-23
+**                                2 = imin outside range 0-59
+**                                3 = sec outside range 0-59.999...
+**
+**  Notes:
+**
+**  1)  The result is computed even if any of the range checks fail.
+**
+**  2)  Negative ihour, imin and/or sec produce a warning status, but
+**      the absolute value is used in the conversion.
+**
+**  3)  If there are multiple errors, the status value reflects only the
+**      first, the smallest taking precedence.
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* Compute the interval. */
+   *days  = ( s == '-' ? -1.0 : 1.0 ) *
+            ( 60.0 * ( 60.0 * ( (double) abs(ihour) ) +
+                              ( (double) abs(imin) ) ) +
+                                         fabs(sec) ) / ERFA_DAYSEC;
+
+/* Validate arguments and return status. */
+   if ( ihour < 0 || ihour > 23 ) return 1;
+   if ( imin < 0 || imin > 59 ) return 2;
+   if ( sec < 0.0 || sec >= 60.0 ) return 3;
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/tr.c b/ast/erfa/tr.c
new file mode 100644
index 0000000..d49dc27
--- /dev/null
+++ b/ast/erfa/tr.c
@@ -0,0 +1,102 @@
+#include "erfa.h"
+
+void eraTr(double r[3][3], double rt[3][3])
+/*
+**  - - - - - -
+**   e r a T r
+**  - - - - - -
+**
+**  Transpose an r-matrix.
+**
+**  Given:
+**     r        double[3][3]    r-matrix
+**
+**  Returned:
+**     rt       double[3][3]    transpose
+**
+**  Note:
+**     It is permissible for r and rt to be the same array.
+**
+**  Called:
+**     eraCr        copy r-matrix
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double wm[3][3];
+   int i, j;
+
+
+   for (i = 0; i < 3; i++) {
+      for (j = 0; j < 3; j++) {
+         wm[i][j] = r[j][i];
+      }
+   }
+   eraCr(wm, rt);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/trxp.c b/ast/erfa/trxp.c
new file mode 100644
index 0000000..eaba6f1
--- /dev/null
+++ b/ast/erfa/trxp.c
@@ -0,0 +1,102 @@
+#include "erfa.h"
+
+void eraTrxp(double r[3][3], double p[3], double trp[3])
+/*
+**  - - - - - - - -
+**   e r a T r x p
+**  - - - - - - - -
+**
+**  Multiply a p-vector by the transpose of an r-matrix.
+**
+**  Given:
+**     r        double[3][3]   r-matrix
+**     p        double[3]      p-vector
+**
+**  Returned:
+**     trp      double[3]      r * p
+**
+**  Note:
+**     It is permissible for p and trp to be the same array.
+**
+**  Called:
+**     eraTr        transpose r-matrix
+**     eraRxp       product of r-matrix and p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double tr[3][3];
+
+
+/* Transpose of matrix r. */
+   eraTr(r, tr);
+
+/* Matrix tr * vector p -> vector trp. */
+   eraRxp(tr, p, trp);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/trxpv.c b/ast/erfa/trxpv.c
new file mode 100644
index 0000000..ce6e5ae
--- /dev/null
+++ b/ast/erfa/trxpv.c
@@ -0,0 +1,102 @@
+#include "erfa.h"
+
+void eraTrxpv(double r[3][3], double pv[2][3], double trpv[2][3])
+/*
+**  - - - - - - - - -
+**   e r a T r x p v
+**  - - - - - - - - -
+**
+**  Multiply a pv-vector by the transpose of an r-matrix.
+**
+**  Given:
+**     r        double[3][3]    r-matrix
+**     pv       double[2][3]    pv-vector
+**
+**  Returned:
+**     trpv     double[2][3]    r * pv
+**
+**  Note:
+**     It is permissible for pv and trpv to be the same array.
+**
+**  Called:
+**     eraTr        transpose r-matrix
+**     eraRxpv      product of r-matrix and pv-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double tr[3][3];
+
+
+/* Transpose of matrix r. */
+   eraTr(r, tr);
+
+/* Matrix tr * vector pv -> vector trpv. */
+   eraRxpv(tr, pv, trpv);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/tttai.c b/ast/erfa/tttai.c
new file mode 100644
index 0000000..0392aee
--- /dev/null
+++ b/ast/erfa/tttai.c
@@ -0,0 +1,119 @@
+#include "erfa.h"
+
+int eraTttai(double tt1, double tt2, double *tai1, double *tai2)
+/*
+**  - - - - - - - - -
+**   e r a T t t a i
+**  - - - - - - - - -
+**
+**  Time scale transformation:  Terrestrial Time, TT, to International
+**  Atomic Time, TAI.
+**
+**  Given:
+**     tt1,tt2    double    TT as a 2-part Julian Date
+**
+**  Returned:
+**     tai1,tai2  double    TAI as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Note:
+**
+**     tt1+tt2 is Julian Date, apportioned in any convenient way between
+**     the two arguments, for example where tt1 is the Julian Day Number
+**     and tt2 is the fraction of a day.  The returned tai1,tai2 follow
+**     suit.
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* TT minus TAI (days). */
+   static const double dtat = ERFA_TTMTAI/ERFA_DAYSEC;
+
+
+/* Result, safeguarding precision. */
+   if ( tt1 > tt2 ) {
+      *tai1 = tt1;
+      *tai2 = tt2 - dtat;
+   } else {
+      *tai1 = tt1 - dtat;
+      *tai2 = tt2;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/tttcg.c b/ast/erfa/tttcg.c
new file mode 100644
index 0000000..a0367dc
--- /dev/null
+++ b/ast/erfa/tttcg.c
@@ -0,0 +1,121 @@
+#include "erfa.h"
+
+int eraTttcg(double tt1, double tt2, double *tcg1, double *tcg2)
+/*
+**  - - - - - - - - -
+**   e r a T t t c g
+**  - - - - - - - - -
+**
+**  Time scale transformation:  Terrestrial Time, TT, to Geocentric
+**  Coordinate Time, TCG.
+**
+**  Given:
+**     tt1,tt2    double    TT as a 2-part Julian Date
+**
+**  Returned:
+**     tcg1,tcg2  double    TCG as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Note:
+**
+**     tt1+tt2 is Julian Date, apportioned in any convenient way between
+**     the two arguments, for example where tt1 is the Julian Day Number
+**     and tt2 is the fraction of a day.  The returned tcg1,tcg2 follow
+**     suit.
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     IAU 2000 Resolution B1.9
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* 1977 Jan 1 00:00:32.184 TT, as MJD */
+   static const double t77t = ERFA_DJM77 + ERFA_TTMTAI/ERFA_DAYSEC;
+
+/* TT to TCG rate */
+   static const double elgg = ERFA_ELG/(1.0-ERFA_ELG);
+
+
+/* Result, safeguarding precision. */
+   if ( tt1 > tt2 ) {
+      *tcg1 = tt1;
+      *tcg2 = tt2 + ( ( tt1 - ERFA_DJM0 ) + ( tt2 - t77t ) ) * elgg;
+   } else {
+      *tcg1 = tt1 + ( ( tt2 - ERFA_DJM0 ) + ( tt1 - t77t ) ) * elgg;
+      *tcg2 = tt2;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/tttdb.c b/ast/erfa/tttdb.c
new file mode 100644
index 0000000..2d59625
--- /dev/null
+++ b/ast/erfa/tttdb.c
@@ -0,0 +1,130 @@
+#include "erfa.h"
+
+int eraTttdb(double tt1, double tt2, double dtr,
+             double *tdb1, double *tdb2)
+/*
+**  - - - - - - - - -
+**   e r a T t t d b
+**  - - - - - - - - -
+**
+**  Time scale transformation:  Terrestrial Time, TT, to Barycentric
+**  Dynamical Time, TDB.
+**
+**  Given:
+**     tt1,tt2    double    TT as a 2-part Julian Date
+**     dtr        double    TDB-TT in seconds
+**
+**  Returned:
+**     tdb1,tdb2  double    TDB as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Notes:
+**
+**  1) tt1+tt2 is Julian Date, apportioned in any convenient way between
+**     the two arguments, for example where tt1 is the Julian Day Number
+**     and tt2 is the fraction of a day.  The returned tdb1,tdb2 follow
+**     suit.
+**
+**  2) The argument dtr represents the quasi-periodic component of the
+**     GR transformation between TT and TCB.  It is dependent upon the
+**     adopted solar-system ephemeris, and can be obtained by numerical
+**     integration, by interrogating a precomputed time ephemeris or by
+**     evaluating a model such as that implemented in the ERFA function
+**     eraDtdb.   The quantity is dominated by an annual term of 1.7 ms
+**     amplitude.
+**
+**  3) TDB is essentially the same as Teph, the time argument for the JPL
+**     solar system ephemerides.
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     IAU 2006 Resolution 3
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dtrd;
+
+
+/* Result, safeguarding precision. */
+   dtrd = dtr / ERFA_DAYSEC;
+   if ( tt1 > tt2 ) {
+      *tdb1 = tt1;
+      *tdb2 = tt2 + dtrd;
+   } else {
+      *tdb1 = tt1 + dtrd;
+      *tdb2 = tt2;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ttut1.c b/ast/erfa/ttut1.c
new file mode 100644
index 0000000..b225f3a
--- /dev/null
+++ b/ast/erfa/ttut1.c
@@ -0,0 +1,119 @@
+#include "erfa.h"
+
+int eraTtut1(double tt1, double tt2, double dt,
+             double *ut11, double *ut12)
+/*
+**  - - - - - - - - -
+**   e r a T t u t 1
+**  - - - - - - - - -
+**
+**  Time scale transformation:  Terrestrial Time, TT, to Universal Time,
+**  UT1.
+**
+**  Given:
+**     tt1,tt2    double    TT as a 2-part Julian Date
+**     dt         double    TT-UT1 in seconds
+**
+**  Returned:
+**     ut11,ut12  double    UT1 as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Notes:
+**
+**  1) tt1+tt2 is Julian Date, apportioned in any convenient way between
+**     the two arguments, for example where tt1 is the Julian Day Number
+**     and tt2 is the fraction of a day.  The returned ut11,ut12 follow
+**     suit.
+**
+**  2) The argument dt is classical Delta T.
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dtd;
+
+
+/* Result, safeguarding precision. */
+   dtd = dt / ERFA_DAYSEC;
+   if ( tt1 > tt2 ) {
+      *ut11 = tt1;
+      *ut12 = tt2 - dtd;
+   } else {
+      *ut11 = tt1 - dtd;
+      *ut12 = tt2;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ut1tai.c b/ast/erfa/ut1tai.c
new file mode 100644
index 0000000..34a87c6
--- /dev/null
+++ b/ast/erfa/ut1tai.c
@@ -0,0 +1,120 @@
+#include "erfa.h"
+
+int eraUt1tai(double ut11, double ut12, double dta,
+              double *tai1, double *tai2)
+/*
+**  - - - - - - - - - -
+**   e r a U t 1 t a i
+**  - - - - - - - - - -
+**
+**  Time scale transformation:  Universal Time, UT1, to International
+**  Atomic Time, TAI.
+**
+**  Given:
+**     ut11,ut12  double    UT1 as a 2-part Julian Date
+**     dta        double    UT1-TAI in seconds
+**
+**  Returned:
+**     tai1,tai2  double    TAI as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Notes:
+**
+**  1) ut11+ut12 is Julian Date, apportioned in any convenient way
+**     between the two arguments, for example where ut11 is the Julian
+**     Day Number and ut12 is the fraction of a day.  The returned
+**     tai1,tai2 follow suit.
+**
+**  2) The argument dta, i.e. UT1-TAI, is an observed quantity, and is
+**     available from IERS tabulations.
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dtad;
+
+
+/* Result, safeguarding precision. */
+   dtad = dta / ERFA_DAYSEC;
+   if ( ut11 > ut12 ) {
+      *tai1 = ut11;
+      *tai2 = ut12 - dtad;
+   } else {
+      *tai1 = ut11 - dtad;
+      *tai2 = ut12;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ut1tt.c b/ast/erfa/ut1tt.c
new file mode 100644
index 0000000..56964e0
--- /dev/null
+++ b/ast/erfa/ut1tt.c
@@ -0,0 +1,119 @@
+#include "erfa.h"
+
+int eraUt1tt(double ut11, double ut12, double dt,
+             double *tt1, double *tt2)
+/*
+**  - - - - - - - - -
+**   e r a U t 1 t t
+**  - - - - - - - - -
+**
+**  Time scale transformation:  Universal Time, UT1, to Terrestrial
+**  Time, TT.
+**
+**  Given:
+**     ut11,ut12  double    UT1 as a 2-part Julian Date
+**     dt         double    TT-UT1 in seconds
+**
+**  Returned:
+**     tt1,tt2    double    TT as a 2-part Julian Date
+**
+**  Returned (function value):
+**                int       status:  0 = OK
+**
+**  Notes:
+**
+**  1) ut11+ut12 is Julian Date, apportioned in any convenient way
+**     between the two arguments, for example where ut11 is the Julian
+**     Day Number and ut12 is the fraction of a day.  The returned
+**     tt1,tt2 follow suit.
+**
+**  2) The argument dt is classical Delta T.
+**
+**  Reference:
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double dtd;
+
+
+/* Result, safeguarding precision. */
+   dtd = dt / ERFA_DAYSEC;
+   if ( ut11 > ut12 ) {
+      *tt1 = ut11;
+      *tt2 = ut12 + dtd;
+   } else {
+      *tt1 = ut11 + dtd;
+      *tt2 = ut12;
+   }
+
+/* Status (always OK). */
+   return 0;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/ut1utc.c b/ast/erfa/ut1utc.c
new file mode 100644
index 0000000..6ec1b44
--- /dev/null
+++ b/ast/erfa/ut1utc.c
@@ -0,0 +1,202 @@
+#include "erfa.h"
+
+int eraUt1utc(double ut11, double ut12, double dut1,
+              double *utc1, double *utc2)
+/*
+**  - - - - - - - - - -
+**   e r a U t 1 u t c
+**  - - - - - - - - - -
+**
+**  Time scale transformation:  Universal Time, UT1, to Coordinated
+**  Universal Time, UTC.
+**
+**  Given:
+**     ut11,ut12  double   UT1 as a 2-part Julian Date (Note 1)
+**     dut1       double   Delta UT1: UT1-UTC in seconds (Note 2)
+**
+**  Returned:
+**     utc1,utc2  double   UTC as a 2-part quasi Julian Date (Notes 3,4)
+**
+**  Returned (function value):
+**                int      status: +1 = dubious year (Note 5)
+**                                  0 = OK
+**                                 -1 = unacceptable date
+**
+**  Notes:
+**
+**  1) ut11+ut12 is Julian Date, apportioned in any convenient way
+**     between the two arguments, for example where ut11 is the Julian
+**     Day Number and ut12 is the fraction of a day.  The returned utc1
+**     and utc2 form an analogous pair, except that a special convention
+**     is used, to deal with the problem of leap seconds - see Note 3.
+**
+**  2) Delta UT1 can be obtained from tabulations provided by the
+**     International Earth Rotation and Reference Systems Service.  The
+**     value changes abruptly by 1s at a leap second;  however, close to
+**     a leap second the algorithm used here is tolerant of the "wrong"
+**     choice of value being made.
+**
+**  3) JD cannot unambiguously represent UTC during a leap second unless
+**     special measures are taken.  The convention in the present
+**     function is that the returned quasi JD day UTC1+UTC2 represents
+**     UTC days whether the length is 86399, 86400 or 86401 SI seconds.
+**
+**  4) The function eraD2dtf can be used to transform the UTC quasi-JD
+**     into calendar date and clock time, including UTC leap second
+**     handling.
+**
+**  5) The warning status "dubious year" flags UTCs that predate the
+**     introduction of the time scale or that are too far in the future
+**     to be trusted.  See eraDat for further details.
+**
+**  Called:
+**     eraJd2cal    JD to Gregorian calendar
+**     eraDat       delta(AT) = TAI-UTC
+**     eraCal2jd    Gregorian calendar to JD
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int big1;
+   int i, iy, im, id, js;
+   double duts, u1, u2, d1, dats1, d2, fd, dats2, ddats, us1, us2, du;
+
+
+/* UT1-UTC in seconds. */
+   duts = dut1;
+
+/* Put the two parts of the UT1 into big-first order. */
+   big1 = ( ut11 >= ut12 );
+   if ( big1 ) {
+      u1 = ut11;
+      u2 = ut12;
+   } else {
+      u1 = ut12;
+      u2 = ut11;
+   }
+
+/* See if the UT1 can possibly be in a leap-second day. */
+   d1 = u1;
+   dats1 = 0;
+   for ( i = -1; i <= 3; i++ ) {
+      d2 = u2 + (double) i;
+      if ( eraJd2cal(d1, d2, &iy, &im, &id, &fd) ) return -1;
+      js = eraDat(iy, im, id, 0.0, &dats2);
+      if ( js < 0 ) return -1;
+      if ( i == - 1 ) dats1 = dats2;
+      ddats = dats2 - dats1;
+      if ( fabs(ddats) >= 0.5 ) {
+
+      /* Yes, leap second nearby: ensure UT1-UTC is "before" value. */
+         if ( ddats * duts >= 0 ) duts -= ddats;
+
+      /* UT1 for the start of the UTC day that ends in a leap. */
+         if ( eraCal2jd(iy, im, id, &d1, &d2) ) return -1;
+         us1 = d1;
+         us2 = d2 - 1.0 + duts/ERFA_DAYSEC;
+
+      /* Is the UT1 after this point? */
+         du = u1 - us1;
+         du += u2 - us2;
+         if ( du > 0 ) {
+
+         /* Yes:  fraction of the current UTC day that has elapsed. */
+            fd = du * ERFA_DAYSEC / ( ERFA_DAYSEC + ddats );
+
+         /* Ramp UT1-UTC to bring about ERFA's JD(UTC) convention. */
+            duts += ddats * ( fd <= 1.0 ? fd : 1.0 );
+         }
+
+      /* Done. */
+         break;
+      }
+      dats1 = dats2;
+   }
+
+/* Subtract the (possibly adjusted) UT1-UTC from UT1 to give UTC. */
+   u2 -= duts / ERFA_DAYSEC;
+
+/* Result, safeguarding precision. */
+   if ( big1 ) {
+      *utc1 = u1;
+      *utc2 = u2;
+   } else {
+      *utc1 = u2;
+      *utc2 = u1;
+   }
+
+/* Status. */
+   return js;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/utctai.c b/ast/erfa/utctai.c
new file mode 100644
index 0000000..2863867
--- /dev/null
+++ b/ast/erfa/utctai.c
@@ -0,0 +1,186 @@
+#include "erfa.h"
+
+int eraUtctai(double utc1, double utc2, double *tai1, double *tai2)
+/*
+**  - - - - - - - - - -
+**   e r a U t c t a i
+**  - - - - - - - - - -
+**
+**  Time scale transformation:  Coordinated Universal Time, UTC, to
+**  International Atomic Time, TAI.
+**
+**  Given:
+**     utc1,utc2  double   UTC as a 2-part quasi Julian Date (Notes 1-4)
+**
+**  Returned:
+**     tai1,tai2  double   TAI as a 2-part Julian Date (Note 5)
+**
+**  Returned (function value):
+**                int      status: +1 = dubious year (Note 3)
+**                                  0 = OK
+**                                 -1 = unacceptable date
+**
+**  Notes:
+**
+**  1) utc1+utc2 is quasi Julian Date (see Note 2), apportioned in any
+**     convenient way between the two arguments, for example where utc1
+**     is the Julian Day Number and utc2 is the fraction of a day.
+**
+**  2) JD cannot unambiguously represent UTC during a leap second unless
+**     special measures are taken.  The convention in the present
+**     function is that the JD day represents UTC days whether the
+**     length is 86399, 86400 or 86401 SI seconds.  In the 1960-1972 era
+**     there were smaller jumps (in either direction) each time the
+**     linear UTC(TAI) expression was changed, and these "mini-leaps"
+**     are also included in the ERFA convention.
+**
+**  3) The warning status "dubious year" flags UTCs that predate the
+**     introduction of the time scale or that are too far in the future
+**     to be trusted.  See eraDat for further details.
+**
+**  4) The function eraDtf2d converts from calendar date and time of day
+**     into 2-part Julian Date, and in the case of UTC implements the
+**     leap-second-ambiguity convention described above.
+**
+**  5) The returned TAI1,TAI2 are such that their sum is the TAI Julian
+**     Date.
+**
+**  Called:
+**     eraJd2cal    JD to Gregorian calendar
+**     eraDat       delta(AT) = TAI-UTC
+**     eraCal2jd    Gregorian calendar to JD
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int big1;
+   int iy, im, id, j, iyt, imt, idt;
+   double u1, u2, fd, dat0, dat12, w, dat24, dlod, dleap, z1, z2, a2;
+
+
+/* Put the two parts of the UTC into big-first order. */
+   big1 = ( utc1 >= utc2 );
+   if ( big1 ) {
+      u1 = utc1;
+      u2 = utc2;
+   } else {
+      u1 = utc2;
+      u2 = utc1;
+   }
+
+/* Get TAI-UTC at 0h today. */
+   j = eraJd2cal(u1, u2, &iy, &im, &id, &fd);
+   if ( j ) return j;
+   j = eraDat(iy, im, id, 0.0, &dat0);
+   if ( j < 0 ) return j;
+
+/* Get TAI-UTC at 12h today (to detect drift). */
+   j = eraDat(iy, im, id, 0.5, &dat12);
+   if ( j < 0 ) return j;
+
+/* Get TAI-UTC at 0h tomorrow (to detect jumps). */
+   j = eraJd2cal(u1+1.5, u2-fd, &iyt, &imt, &idt, &w);
+   if ( j ) return j;
+   j = eraDat(iyt, imt, idt, 0.0, &dat24);
+   if ( j < 0 ) return j;
+
+/* Separate TAI-UTC change into per-day (DLOD) and any jump (DLEAP). */
+   dlod = 2.0 * (dat12 - dat0);
+   dleap = dat24 - (dat0 + dlod);
+
+/* Remove any scaling applied to spread leap into preceding day. */
+   fd *= (ERFA_DAYSEC+dleap)/ERFA_DAYSEC;
+
+/* Scale from (pre-1972) UTC seconds to SI seconds. */
+   fd *= (ERFA_DAYSEC+dlod)/ERFA_DAYSEC;
+
+/* Today's calendar date to 2-part JD. */
+   if ( eraCal2jd(iy, im, id, &z1, &z2) ) return -1;
+
+/* Assemble the TAI result, preserving the UTC split and order. */
+   a2 = z1 - u1;
+   a2 += z2;
+   a2 += fd + dat0/ERFA_DAYSEC;
+   if ( big1 ) {
+      *tai1 = u1;
+      *tai2 = a2;
+   } else {
+      *tai1 = a2;
+      *tai2 = u1;
+   }
+
+/* Status. */
+   return j;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/utcut1.c b/ast/erfa/utcut1.c
new file mode 100644
index 0000000..1ab4e94
--- /dev/null
+++ b/ast/erfa/utcut1.c
@@ -0,0 +1,156 @@
+#include "erfa.h"
+
+int eraUtcut1(double utc1, double utc2, double dut1,
+              double *ut11, double *ut12)
+/*
+**  - - - - - - - - - -
+**   e r a U t c u t 1
+**  - - - - - - - - - -
+**
+**  Time scale transformation:  Coordinated Universal Time, UTC, to
+**  Universal Time, UT1.
+**
+**  Given:
+**     utc1,utc2  double   UTC as a 2-part quasi Julian Date (Notes 1-4)
+**     dut1       double   Delta UT1 = UT1-UTC in seconds (Note 5)
+**
+**  Returned:
+**     ut11,ut12  double   UT1 as a 2-part Julian Date (Note 6)
+**
+**  Returned (function value):
+**                int      status: +1 = dubious year (Note 3)
+**                                  0 = OK
+**                                 -1 = unacceptable date
+**
+**  Notes:
+**
+**  1) utc1+utc2 is quasi Julian Date (see Note 2), apportioned in any
+**     convenient way between the two arguments, for example where utc1
+**     is the Julian Day Number and utc2 is the fraction of a day.
+**
+**  2) JD cannot unambiguously represent UTC during a leap second unless
+**     special measures are taken.  The convention in the present
+**     function is that the JD day represents UTC days whether the
+**     length is 86399, 86400 or 86401 SI seconds.
+**
+**  3) The warning status "dubious year" flags UTCs that predate the
+**     introduction of the time scale or that are too far in the future
+**     to be trusted.  See eraDat for further details.
+**
+**  4) The function eraDtf2d converts from calendar date and time of
+**     day into 2-part Julian Date, and in the case of UTC implements
+**     the leap-second-ambiguity convention described above.
+**
+**  5) Delta UT1 can be obtained from tabulations provided by the
+**     International Earth Rotation and Reference Systems Service.
+**     It is the caller's responsibility to supply a dut1 argument
+**     containing the UT1-UTC value that matches the given UTC.
+**
+**  6) The returned ut11,ut12 are such that their sum is the UT1 Julian
+**     Date.
+**
+**  References:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**     Explanatory Supplement to the Astronomical Almanac,
+**     P. Kenneth Seidelmann (ed), University Science Books (1992)
+**
+**  Called:
+**     eraJd2cal    JD to Gregorian calendar
+**     eraDat       delta(AT) = TAI-UTC
+**     eraUtctai    UTC to TAI
+**     eraTaiut1    TAI to UT1
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   int iy, im, id, js, jw;
+   double w, dat, dta, tai1, tai2;
+
+
+/* Look up TAI-UTC. */
+   if ( eraJd2cal(utc1, utc2, &iy, &im, &id, &w) ) return -1;
+   js = eraDat ( iy, im, id, 0.0, &dat);
+   if ( js < 0 ) return -1;
+
+/* Form UT1-TAI. */
+   dta = dut1 - dat;
+
+/* UTC to TAI to UT1. */
+   jw = eraUtctai(utc1, utc2, &tai1, &tai2);
+   if ( jw < 0 ) {
+      return -1;
+   } else if ( jw > 0 ) {
+      js = jw;
+   }
+   if ( eraTaiut1(tai1, tai2, dta, ut11, ut12) ) return -1;
+
+/* Status. */
+   return js;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/xy06.c b/ast/erfa/xy06.c
new file mode 100644
index 0000000..67079b4
--- /dev/null
+++ b/ast/erfa/xy06.c
@@ -0,0 +1,2767 @@
+#include "erfa.h"
+
+void eraXy06(double date1, double date2, double *x, double *y)
+/*
+**  - - - - - - - -
+**   e r a X y 0 6
+**  - - - - - - - -
+**
+**  X,Y coordinates of celestial intermediate pole from series based
+**  on IAU 2006 precession and IAU 2000A nutation.
+**
+**  Given:
+**     date1,date2  double     TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     x,y          double     CIP X,Y coordinates (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The X,Y coordinates are those of the unit vector towards the
+**     celestial intermediate pole.  They represent the combined effects
+**     of frame bias, precession and nutation.
+**
+**  3) The fundamental arguments used are as adopted in IERS Conventions
+**     (2003) and are from Simon et al. (1994) and Souchay et al.
+**     (1999).
+**
+**  4) This is an alternative to the angles-based method, via the ERFA
+**     function eraFw2xy and as used in eraXys06a for example.  The two
+**     methods agree at the 1 microarcsecond level (at present), a
+**     negligible amount compared with the intrinsic accuracy of the
+**     models.  However, it would be unwise to mix the two methods
+**     (angles-based and series-based) in a single application.
+**
+**  Called:
+**     eraFal03     mean anomaly of the Moon
+**     eraFalp03    mean anomaly of the Sun
+**     eraFaf03     mean argument of the latitude of the Moon
+**     eraFad03     mean elongation of the Moon from the Sun
+**     eraFaom03    mean longitude of the Moon's ascending node
+**     eraFame03    mean longitude of Mercury
+**     eraFave03    mean longitude of Venus
+**     eraFae03     mean longitude of Earth
+**     eraFama03    mean longitude of Mars
+**     eraFaju03    mean longitude of Jupiter
+**     eraFasa03    mean longitude of Saturn
+**     eraFaur03    mean longitude of Uranus
+**     eraFane03    mean longitude of Neptune
+**     eraFapa03    general accumulated precession in longitude
+**
+**  References:
+**
+**     Capitaine, N., Wallace, P.T. & Chapront, J., 2003,
+**     Astron.Astrophys., 412, 567
+**
+**     Capitaine, N. & Wallace, P.T., 2006, Astron.Astrophys. 450, 855
+**
+**     McCarthy, D. D., Petit, G. (eds.), 2004, IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG
+**
+**     Simon, J.L., Bretagnon, P., Chapront, J., Chapront-Touze, M.,
+**     Francou, G. & Laskar, J., Astron.Astrophys., 1994, 282, 663
+**
+**     Souchay, J., Loysel, B., Kinoshita, H., Folgueira, M., 1999,
+**     Astron.Astrophys.Supp.Ser. 135, 111
+**
+**     Wallace, P.T. & Capitaine, N., 2006, Astron.Astrophys. 459, 981
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+
+/* Maximum power of T in the polynomials for X and Y */
+   enum { MAXPT = 5 };
+
+/* Polynomial coefficients (arcsec, X then Y). */
+   static const double xyp[2][MAXPT+1] = {
+
+      {    -0.016617,
+         2004.191898,
+           -0.4297829,
+           -0.19861834,
+            0.000007578,
+            0.0000059285
+      },
+      {    -0.006951,
+           -0.025896,
+          -22.4072747,
+            0.00190059,
+            0.001112526,
+            0.0000001358
+      }
+   };
+
+/* Fundamental-argument multipliers:  luni-solar terms */
+   static const int mfals[][5] = {
+
+   /* 1-10 */
+      {  0,   0,   0,   0,   1 },
+      {  0,   0,   2,  -2,   2 },
+      {  0,   0,   2,   0,   2 },
+      {  0,   0,   0,   0,   2 },
+      {  0,   1,   0,   0,   0 },
+      {  0,   1,   2,  -2,   2 },
+      {  1,   0,   0,   0,   0 },
+      {  0,   0,   2,   0,   1 },
+      {  1,   0,   2,   0,   2 },
+      {  0,   1,  -2,   2,  -2 },
+
+   /* 11-20 */
+      {  0,   0,   2,  -2,   1 },
+      {  1,   0,  -2,   0,  -2 },
+      {  1,   0,   0,  -2,   0 },
+      {  1,   0,   0,   0,   1 },
+      {  1,   0,   0,   0,  -1 },
+      {  1,   0,  -2,  -2,  -2 },
+      {  1,   0,   2,   0,   1 },
+      {  2,   0,  -2,   0,  -1 },
+      {  0,   0,   0,   2,   0 },
+      {  0,   0,   2,   2,   2 },
+
+   /* 21-30 */
+      {  2,   0,   0,  -2,   0 },
+      {  0,   2,  -2,   2,  -2 },
+      {  2,   0,   2,   0,   2 },
+      {  1,   0,   2,  -2,   2 },
+      {  1,   0,  -2,   0,  -1 },
+      {  2,   0,   0,   0,   0 },
+      {  0,   0,   2,   0,   0 },
+      {  0,   1,   0,   0,   1 },
+      {  1,   0,   0,  -2,  -1 },
+      {  0,   2,   2,  -2,   2 },
+
+   /* 31-40 */
+      {  0,   0,   2,  -2,   0 },
+      {  1,   0,   0,  -2,   1 },
+      {  0,   1,   0,   0,  -1 },
+      {  0,   2,   0,   0,   0 },
+      {  1,   0,  -2,  -2,  -1 },
+      {  1,   0,   2,   2,   2 },
+      {  0,   1,   2,   0,   2 },
+      {  2,   0,  -2,   0,   0 },
+      {  0,   0,   2,   2,   1 },
+      {  0,   1,  -2,   0,  -2 },
+
+   /* 41-50 */
+      {  0,   0,   0,   2,   1 },
+      {  1,   0,   2,  -2,   1 },
+      {  2,   0,   0,  -2,  -1 },
+      {  2,   0,   2,  -2,   2 },
+      {  2,   0,   2,   0,   1 },
+      {  0,   0,   0,   2,  -1 },
+      {  0,   1,  -2,   2,  -1 },
+      {  1,   1,   0,  -2,   0 },
+      {  2,   0,   0,  -2,   1 },
+      {  1,   0,   0,   2,   0 },
+
+   /* 51-60 */
+      {  0,   1,   2,  -2,   1 },
+      {  1,  -1,   0,   0,   0 },
+      {  0,   1,  -1,   1,  -1 },
+      {  2,   0,  -2,   0,  -2 },
+      {  0,   1,   0,  -2,   0 },
+      {  1,   0,   0,  -1,   0 },
+      {  3,   0,   2,   0,   2 },
+      {  0,   0,   0,   1,   0 },
+      {  1,  -1,   2,   0,   2 },
+      {  1,   1,  -2,  -2,  -2 },
+
+   /* 61-70 */
+      {  1,   0,  -2,   0,   0 },
+      {  2,   0,   0,   0,  -1 },
+      {  0,   1,  -2,  -2,  -2 },
+      {  1,   1,   2,   0,   2 },
+      {  2,   0,   0,   0,   1 },
+      {  1,   1,   0,   0,   0 },
+      {  1,   0,  -2,   2,  -1 },
+      {  1,   0,   2,   0,   0 },
+      {  1,  -1,   0,  -1,   0 },
+      {  1,   0,   0,   0,   2 },
+
+   /* 71-80 */
+      {  1,   0,  -1,   0,  -1 },
+      {  0,   0,   2,   1,   2 },
+      {  1,   0,  -2,  -4,  -2 },
+      {  1,  -1,   0,  -1,  -1 },
+      {  1,   0,   2,   2,   1 },
+      {  0,   2,  -2,   2,  -1 },
+      {  1,   0,   0,   0,  -2 },
+      {  2,   0,  -2,  -2,  -2 },
+      {  1,   1,   2,  -2,   2 },
+      {  2,   0,  -2,  -4,  -2 },
+
+   /* 81-90 */
+      {  1,   0,  -4,   0,  -2 },
+      {  2,   0,   2,  -2,   1 },
+      {  1,   0,   0,  -1,  -1 },
+      {  2,   0,   2,   2,   2 },
+      {  3,   0,   0,   0,   0 },
+      {  1,   0,   0,   2,   1 },
+      {  0,   0,   2,  -2,  -1 },
+      {  3,   0,   2,  -2,   2 },
+      {  0,   0,   4,  -2,   2 },
+      {  1,   0,   0,  -4,   0 },
+
+   /* 91-100 */
+      {  0,   1,   2,   0,   1 },
+      {  2,   0,   0,  -4,   0 },
+      {  1,   1,   0,  -2,  -1 },
+      {  2,   0,  -2,   0,   1 },
+      {  0,   0,   2,   0,  -1 },
+      {  0,   1,  -2,   0,  -1 },
+      {  0,   1,   0,   0,   2 },
+      {  0,   0,   2,  -1,   2 },
+      {  0,   0,   2,   4,   2 },
+      {  2,   1,   0,  -2,   0 },
+
+   /* 101-110 */
+      {  1,   1,   0,  -2,   1 },
+      {  1,  -1,   0,  -2,   0 },
+      {  1,  -1,   0,  -1,  -2 },
+      {  1,  -1,   0,   0,   1 },
+      {  0,   1,  -2,   2,   0 },
+      {  0,   1,   0,   0,  -2 },
+      {  1,  -1,   2,   2,   2 },
+      {  1,   0,   0,   2,  -1 },
+      {  1,  -1,  -2,  -2,  -2 },
+      {  3,   0,   2,   0,   1 },
+
+   /* 111-120 */
+      {  0,   1,   2,   2,   2 },
+      {  1,   0,   2,  -2,   0 },
+      {  1,   1,  -2,  -2,  -1 },
+      {  1,   0,   2,  -4,   1 },
+      {  0,   1,  -2,  -2,  -1 },
+      {  2,  -1,   2,   0,   2 },
+      {  0,   0,   0,   2,   2 },
+      {  1,  -1,   2,   0,   1 },
+      {  1,  -1,  -2,   0,  -2 },
+      {  0,   1,   0,   2,   0 },
+
+   /* 121-130 */
+      {  0,   1,   2,  -2,   0 },
+      {  0,   0,   0,   1,   1 },
+      {  1,   0,  -2,  -2,   0 },
+      {  0,   3,   2,  -2,   2 },
+      {  2,   1,   2,   0,   2 },
+      {  1,   1,   0,   0,   1 },
+      {  2,   0,   0,   2,   0 },
+      {  1,   1,   2,   0,   1 },
+      {  1,   0,   0,  -2,  -2 },
+      {  1,   0,  -2,   2,   0 },
+
+   /* 131-140 */
+      {  1,   0,  -1,   0,  -2 },
+      {  0,   1,   0,  -2,   1 },
+      {  0,   1,   0,   1,   0 },
+      {  0,   0,   0,   1,  -1 },
+      {  1,   0,  -2,   2,  -2 },
+      {  1,  -1,   0,   0,  -1 },
+      {  0,   0,   0,   4,   0 },
+      {  1,  -1,   0,   2,   0 },
+      {  1,   0,   2,   1,   2 },
+      {  1,   0,   2,  -1,   2 },
+
+   /* 141-150 */
+      {  0,   0,   2,   1,   1 },
+      {  1,   0,   0,  -2,   2 },
+      {  1,   0,  -2,   0,   1 },
+      {  1,   0,  -2,  -4,  -1 },
+      {  0,   0,   2,   2,   0 },
+      {  1,   1,   2,  -2,   1 },
+      {  1,   0,  -2,   1,  -1 },
+      {  0,   0,   1,   0,   1 },
+      {  2,   0,  -2,  -2,  -1 },
+      {  4,   0,   2,   0,   2 },
+
+   /* 151-160 */
+      {  2,  -1,   0,   0,   0 },
+      {  2,   1,   2,  -2,   2 },
+      {  0,   1,   2,   1,   2 },
+      {  1,   0,   4,  -2,   2 },
+      {  1,   1,   0,   0,  -1 },
+      {  2,   0,   2,   0,   0 },
+      {  2,   0,  -2,  -4,  -1 },
+      {  1,   0,  -1,   0,   0 },
+      {  1,   0,   0,   1,   0 },
+      {  0,   1,   0,   2,   1 },
+
+   /* 161-170 */
+      {  1,   0,  -4,   0,  -1 },
+      {  1,   0,   0,  -4,  -1 },
+      {  2,   0,   2,   2,   1 },
+      {  2,   1,   0,   0,   0 },
+      {  0,   0,   2,  -3,   2 },
+      {  1,   2,   0,  -2,   0 },
+      {  0,   3,   0,   0,   0 },
+      {  0,   0,   4,   0,   2 },
+      {  0,   0,   2,  -4,   1 },
+      {  2,   0,   0,  -2,  -2 },
+
+   /* 171-180 */
+      {  1,   1,  -2,  -4,  -2 },
+      {  0,   1,   0,  -2,  -1 },
+      {  0,   0,   0,   4,   1 },
+      {  3,   0,   2,  -2,   1 },
+      {  1,   0,   2,   4,   2 },
+      {  1,   1,  -2,   0,  -2 },
+      {  0,   0,   4,  -2,   1 },
+      {  2,  -2,   0,  -2,   0 },
+      {  2,   1,   0,  -2,  -1 },
+      {  0,   2,   0,  -2,   0 },
+
+   /* 181-190 */
+      {  1,   0,   0,  -1,   1 },
+      {  1,   1,   2,   2,   2 },
+      {  3,   0,   0,   0,  -1 },
+      {  2,   0,   0,  -4,  -1 },
+      {  3,   0,   2,   2,   2 },
+      {  0,   0,   2,   4,   1 },
+      {  0,   2,  -2,  -2,  -2 },
+      {  1,  -1,   0,  -2,  -1 },
+      {  0,   0,   2,  -1,   1 },
+      {  2,   0,   0,   2,   1 },
+
+   /* 191-200 */
+      {  1,  -1,  -2,   2,  -1 },
+      {  0,   0,   0,   2,  -2 },
+      {  2,   0,   0,  -4,   1 },
+      {  1,   0,   0,  -4,   1 },
+      {  2,   0,   2,  -4,   1 },
+      {  4,   0,   2,  -2,   2 },
+      {  2,   1,  -2,   0,  -1 },
+      {  2,   1,  -2,  -4,  -2 },
+      {  3,   0,   0,  -4,   0 },
+      {  1,  -1,   2,   2,   1 },
+
+   /* 201-210 */
+      {  1,  -1,  -2,   0,  -1 },
+      {  0,   2,   0,   0,   1 },
+      {  1,   2,  -2,  -2,  -2 },
+      {  1,   1,   0,  -4,   0 },
+      {  2,   0,   0,  -2,   2 },
+      {  0,   2,   2,  -2,   1 },
+      {  1,   0,   2,   0,  -1 },
+      {  2,   1,   0,  -2,   1 },
+      {  2,  -1,  -2,   0,  -1 },
+      {  1,  -1,  -2,  -2,  -1 },
+
+   /* 211-220 */
+      {  0,   1,  -2,   1,  -2 },
+      {  1,   0,  -4,   2,  -2 },
+      {  0,   1,   2,   2,   1 },
+      {  3,   0,   0,   0,   1 },
+      {  2,  -1,   2,   2,   2 },
+      {  0,   1,  -2,  -4,  -2 },
+      {  1,   0,  -2,  -3,  -2 },
+      {  2,   0,   0,   0,   2 },
+      {  1,  -1,   0,  -2,  -2 },
+      {  2,   0,  -2,   2,  -1 },
+
+   /* 221-230 */
+      {  0,   2,  -2,   0,  -2 },
+      {  3,   0,  -2,   0,  -1 },
+      {  2,  -1,   2,   0,   1 },
+      {  1,   0,  -2,  -1,  -2 },
+      {  0,   0,   2,   0,   3 },
+      {  2,   0,  -4,   0,  -2 },
+      {  2,   1,   0,  -4,   0 },
+      {  1,   1,  -2,   1,  -1 },
+      {  0,   2,   2,   0,   2 },
+      {  1,  -1,   2,  -2,   2 },
+
+   /* 231-240 */
+      {  1,  -1,   0,  -2,   1 },
+      {  2,   1,   2,   0,   1 },
+      {  1,   0,   2,  -4,   2 },
+      {  1,   1,  -2,   0,  -1 },
+      {  1,   1,   0,   2,   0 },
+      {  1,   0,   0,  -3,   0 },
+      {  2,   0,   2,  -1,   2 },
+      {  0,   2,   0,   0,  -1 },
+      {  2,  -1,   0,  -2,   0 },
+      {  4,   0,   0,   0,   0 },
+
+   /* 241-250 */
+      {  2,   1,  -2,  -2,  -2 },
+      {  0,   2,  -2,   2,   0 },
+      {  1,   0,   2,   1,   1 },
+      {  1,   0,  -1,   0,  -3 },
+      {  3,  -1,   2,   0,   2 },
+      {  2,   0,   2,  -2,   0 },
+      {  1,  -2,   0,   0,   0 },
+      {  2,   0,   0,   0,  -2 },
+      {  1,   0,   0,   4,   0 },
+      {  0,   1,   0,   1,   1 },
+
+   /* 251-260 */
+      {  1,   0,   2,   2,   0 },
+      {  0,   1,   0,   2,  -1 },
+      {  0,   1,   0,   1,  -1 },
+      {  0,   0,   2,  -2,   3 },
+      {  3,   1,   2,   0,   2 },
+      {  1,   1,   2,   1,   2 },
+      {  1,   1,  -2,   2,  -1 },
+      {  2,  -1,   2,  -2,   2 },
+      {  1,  -2,   2,   0,   2 },
+      {  1,   0,   2,  -4,   0 },
+
+   /* 261-270 */
+      {  0,   0,   1,   0,   0 },
+      {  1,   0,   2,  -3,   1 },
+      {  1,  -2,   0,  -2,   0 },
+      {  2,   0,   0,   2,  -1 },
+      {  1,   1,   2,  -4,   1 },
+      {  4,   0,   2,   0,   1 },
+      {  0,   1,   2,   1,   1 },
+      {  1,   2,   2,  -2,   2 },
+      {  2,   0,   2,   1,   2 },
+      {  2,   1,   2,  -2,   1 },
+
+   /* 271-280 */
+      {  1,   0,   2,  -1,   1 },
+      {  1,   0,   4,  -2,   1 },
+      {  1,  -1,   2,  -2,   1 },
+      {  0,   1,   0,  -4,   0 },
+      {  3,   0,  -2,  -2,  -2 },
+      {  0,   0,   4,  -4,   2 },
+      {  2,   0,  -4,  -2,  -2 },
+      {  2,  -2,   0,  -2,  -1 },
+      {  1,   0,   2,  -2,  -1 },
+      {  2,   0,  -2,  -6,  -2 },
+
+   /* 281-290 */
+      {  1,   0,  -2,   1,  -2 },
+      {  1,   0,  -2,   2,   1 },
+      {  1,  -1,   0,   2,  -1 },
+      {  1,   0,  -2,   1,   0 },
+      {  2,  -1,   0,  -2,   1 },
+      {  1,  -1,   0,   2,   1 },
+      {  2,   0,  -2,  -2,   0 },
+      {  1,   0,   2,  -3,   2 },
+      {  0,   0,   0,   4,  -1 },
+      {  2,  -1,   0,   0,   1 },
+
+   /* 291-300 */
+      {  2,   0,   4,  -2,   2 },
+      {  0,   0,   2,   3,   2 },
+      {  0,   1,   4,  -2,   2 },
+      {  0,   1,  -2,   2,   1 },
+      {  1,   1,   0,   2,   1 },
+      {  1,   0,   0,   4,   1 },
+      {  0,   0,   4,   0,   1 },
+      {  2,   0,   0,  -3,   0 },
+      {  1,   0,   0,  -1,  -2 },
+      {  1,  -2,  -2,  -2,  -2 },
+
+   /* 301-310 */
+      {  3,   0,   0,   2,   0 },
+      {  2,   0,   2,  -4,   2 },
+      {  1,   1,  -2,  -4,  -1 },
+      {  1,   0,  -2,  -6,  -2 },
+      {  2,  -1,   0,   0,  -1 },
+      {  2,  -1,   0,   2,   0 },
+      {  0,   1,   2,  -2,  -1 },
+      {  1,   1,   0,   1,   0 },
+      {  1,   2,   0,  -2,  -1 },
+      {  1,   0,   0,   1,  -1 },
+
+   /* 311-320 */
+      {  0,   0,   1,   0,   2 },
+      {  3,   1,   2,  -2,   2 },
+      {  1,   0,  -4,  -2,  -2 },
+      {  1,   0,   2,   4,   1 },
+      {  1,  -2,   2,   2,   2 },
+      {  1,  -1,  -2,  -4,  -2 },
+      {  0,   0,   2,  -4,   2 },
+      {  0,   0,   2,  -3,   1 },
+      {  2,   1,  -2,   0,   0 },
+      {  3,   0,  -2,  -2,  -1 },
+
+   /* 321-330 */
+      {  2,   0,   2,   4,   2 },
+      {  0,   0,   0,   0,   3 },
+      {  2,  -1,  -2,  -2,  -2 },
+      {  2,   0,   0,  -1,   0 },
+      {  3,   0,   2,  -4,   2 },
+      {  2,   1,   2,   2,   2 },
+      {  0,   0,   3,   0,   3 },
+      {  1,   1,   2,   2,   1 },
+      {  2,   1,   0,   0,  -1 },
+      {  1,   2,   0,  -2,   1 },
+
+   /* 331-340 */
+      {  3,   0,   2,   2,   1 },
+      {  1,  -1,  -2,   2,  -2 },
+      {  1,   1,   0,  -1,   0 },
+      {  1,   2,   0,   0,   0 },
+      {  1,   0,   4,   0,   2 },
+      {  1,  -1,   2,   4,   2 },
+      {  2,   1,   0,   0,   1 },
+      {  1,   0,   0,   2,   2 },
+      {  1,  -1,  -2,   2,   0 },
+      {  0,   2,  -2,  -2,  -1 },
+
+   /* 341-350 */
+      {  2,   0,  -2,   0,   2 },
+      {  5,   0,   2,   0,   2 },
+      {  3,   0,  -2,  -6,  -2 },
+      {  1,  -1,   2,  -1,   2 },
+      {  3,   0,   0,  -4,  -1 },
+      {  1,   0,   0,   1,   1 },
+      {  1,   0,  -4,   2,  -1 },
+      {  0,   1,   2,  -4,   1 },
+      {  1,   2,   2,   0,   2 },
+      {  0,   1,   0,  -2,  -2 },
+
+   /* 351-360 */
+      {  0,   0,   2,  -1,   0 },
+      {  1,   0,   1,   0,   1 },
+      {  0,   2,   0,  -2,   1 },
+      {  3,   0,   2,   0,   0 },
+      {  1,   1,  -2,   1,   0 },
+      {  2,   1,  -2,  -4,  -1 },
+      {  3,  -1,   0,   0,   0 },
+      {  2,  -1,  -2,   0,   0 },
+      {  4,   0,   2,  -2,   1 },
+      {  2,   0,  -2,   2,   0 },
+
+   /* 361-370 */
+      {  1,   1,   2,  -2,   0 },
+      {  1,   0,  -2,   4,  -1 },
+      {  1,   0,  -2,  -2,   1 },
+      {  2,   0,   2,  -4,   0 },
+      {  1,   1,   0,  -2,  -2 },
+      {  1,   1,  -2,  -2,   0 },
+      {  1,   0,   1,  -2,   1 },
+      {  2,  -1,  -2,  -4,  -2 },
+      {  3,   0,  -2,   0,  -2 },
+      {  0,   1,  -2,  -2,   0 },
+
+   /* 371-380 */
+      {  3,   0,   0,  -2,  -1 },
+      {  1,   0,  -2,  -3,  -1 },
+      {  0,   1,   0,  -4,  -1 },
+      {  1,  -2,   2,  -2,   1 },
+      {  0,   1,  -2,   1,  -1 },
+      {  1,  -1,   0,   0,   2 },
+      {  2,   0,   0,   1,   0 },
+      {  1,  -2,   0,   2,   0 },
+      {  1,   2,  -2,  -2,  -1 },
+      {  0,   0,   4,  -4,   1 },
+
+   /* 381-390 */
+      {  0,   1,   2,   4,   2 },
+      {  0,   1,  -4,   2,  -2 },
+      {  3,   0,  -2,   0,   0 },
+      {  2,  -1,   2,   2,   1 },
+      {  0,   1,  -2,  -4,  -1 },
+      {  4,   0,   2,   2,   2 },
+      {  2,   0,  -2,  -3,  -2 },
+      {  2,   0,   0,  -6,   0 },
+      {  1,   0,   2,   0,   3 },
+      {  3,   1,   0,   0,   0 },
+
+   /* 391-400 */
+      {  3,   0,   0,  -4,   1 },
+      {  1,  -1,   2,   0,   0 },
+      {  1,  -1,   0,  -4,   0 },
+      {  2,   0,  -2,   2,  -2 },
+      {  1,   1,   0,  -2,   2 },
+      {  4,   0,   0,  -2,   0 },
+      {  2,   2,   0,  -2,   0 },
+      {  0,   1,   2,   0,   0 },
+      {  1,   1,   0,  -4,   1 },
+      {  1,   0,   0,  -4,  -2 },
+
+   /* 401-410 */
+      {  0,   0,   0,   1,   2 },
+      {  3,   0,   0,   2,   1 },
+      {  1,   1,   0,  -4,  -1 },
+      {  0,   0,   2,   2,  -1 },
+      {  1,   1,   2,   0,   0 },
+      {  1,  -1,   2,  -4,   1 },
+      {  1,   1,   0,   0,   2 },
+      {  0,   0,   2,   6,   2 },
+      {  4,   0,  -2,  -2,  -1 },
+      {  2,   1,   0,  -4,  -1 },
+
+   /* 411-420 */
+      {  0,   0,   0,   3,   1 },
+      {  1,  -1,  -2,   0,   0 },
+      {  0,   0,   2,   1,   0 },
+      {  1,   0,   0,   2,  -2 },
+      {  3,  -1,   2,   2,   2 },
+      {  3,  -1,   2,  -2,   2 },
+      {  1,   0,   0,  -1,   2 },
+      {  1,  -2,   2,  -2,   2 },
+      {  0,   1,   0,   2,   2 },
+      {  0,   1,  -2,  -1,  -2 },
+
+   /* 421-430 */
+      {  1,   1,  -2,   0,   0 },
+      {  0,   2,   2,  -2,   0 },
+      {  3,  -1,  -2,  -1,  -2 },
+      {  1,   0,   0,  -6,   0 },
+      {  1,   0,  -2,  -4,   0 },
+      {  2,   1,   0,  -4,   1 },
+      {  2,   0,   2,   0,  -1 },
+      {  2,   0,  -4,   0,  -1 },
+      {  0,   0,   3,   0,   2 },
+      {  2,   1,  -2,  -2,  -1 },
+
+   /* 431-440 */
+      {  1,  -2,   0,   0,   1 },
+      {  2,  -1,   0,  -4,   0 },
+      {  0,   0,   0,   3,   0 },
+      {  5,   0,   2,  -2,   2 },
+      {  1,   2,  -2,  -4,  -2 },
+      {  1,   0,   4,  -4,   2 },
+      {  0,   0,   4,  -1,   2 },
+      {  3,   1,   0,  -4,   0 },
+      {  3,   0,   0,  -6,   0 },
+      {  2,   0,   0,   2,   2 },
+
+   /* 441-450 */
+      {  2,  -2,   2,   0,   2 },
+      {  1,   0,   0,  -3,   1 },
+      {  1,  -2,  -2,   0,  -2 },
+      {  1,  -1,  -2,  -3,  -2 },
+      {  0,   0,   2,  -2,  -2 },
+      {  2,   0,  -2,  -4,   0 },
+      {  1,   0,  -4,   0,   0 },
+      {  0,   1,   0,  -1,   0 },
+      {  4,   0,   0,   0,  -1 },
+      {  3,   0,   2,  -1,   2 },
+
+   /* 451-460 */
+      {  3,  -1,   2,   0,   1 },
+      {  2,   0,   2,  -1,   1 },
+      {  1,   2,   2,  -2,   1 },
+      {  1,   1,   0,   2,  -1 },
+      {  0,   2,   2,   0,   1 },
+      {  3,   1,   2,   0,   1 },
+      {  1,   1,   2,   1,   1 },
+      {  1,   1,   0,  -1,   1 },
+      {  1,  -2,   0,  -2,  -1 },
+      {  4,   0,   0,  -4,   0 },
+
+   /* 461-470 */
+      {  2,   1,   0,   2,   0 },
+      {  1,  -1,   0,   4,   0 },
+      {  0,   1,   0,  -2,   2 },
+      {  0,   0,   2,   0,  -2 },
+      {  1,   0,  -1,   0,   1 },
+      {  3,   0,   2,  -2,   0 },
+      {  2,   0,   2,   2,   0 },
+      {  1,   2,   0,  -4,   0 },
+      {  1,  -1,   0,  -3,   0 },
+      {  0,   1,   0,   4,   0 },
+
+   /* 471 - 480 */
+      {  0,   1,  -2,   0,   0 },
+      {  2,   2,   2,  -2,   2 },
+      {  0,   0,   0,   1,  -2 },
+      {  0,   2,  -2,   0,  -1 },
+      {  4,   0,   2,  -4,   2 },
+      {  2,   0,  -4,   2,  -2 },
+      {  2,  -1,  -2,   0,  -2 },
+      {  1,   1,   4,  -2,   2 },
+      {  1,   1,   2,  -4,   2 },
+      {  1,   0,   2,   3,   2 },
+
+   /* 481-490 */
+      {  1,   0,   0,   4,  -1 },
+      {  0,   0,   0,   4,   2 },
+      {  2,   0,   0,   4,   0 },
+      {  1,   1,  -2,   2,   0 },
+      {  2,   1,   2,   1,   2 },
+      {  2,   1,   2,  -4,   1 },
+      {  2,   0,   2,   1,   1 },
+      {  2,   0,  -4,  -2,  -1 },
+      {  2,   0,  -2,  -6,  -1 },
+      {  2,  -1,   2,  -1,   2 },
+
+   /* 491-500 */
+      {  1,  -2,   2,   0,   1 },
+      {  1,  -2,   0,  -2,   1 },
+      {  1,  -1,   0,  -4,  -1 },
+      {  0,   2,   2,   2,   2 },
+      {  0,   2,  -2,  -4,  -2 },
+      {  0,   1,   2,   3,   2 },
+      {  0,   1,   0,  -4,   1 },
+      {  3,   0,   0,  -2,   1 },
+      {  2,   1,  -2,   0,   1 },
+      {  2,   0,   4,  -2,   1 },
+
+   /* 501-510 */
+      {  2,   0,   0,  -3,  -1 },
+      {  2,  -2,   0,  -2,   1 },
+      {  2,  -1,   2,  -2,   1 },
+      {  1,   0,   0,  -6,  -1 },
+      {  1,  -2,   0,   0,  -1 },
+      {  1,  -2,  -2,  -2,  -1 },
+      {  0,   1,   4,  -2,   1 },
+      {  0,   0,   2,   3,   1 },
+      {  2,  -1,   0,  -1,   0 },
+      {  1,   3,   0,  -2,   0 },
+
+   /* 511-520 */
+      {  0,   3,   0,  -2,   0 },
+      {  2,  -2,   2,  -2,   2 },
+      {  0,   0,   4,  -2,   0 },
+      {  4,  -1,   2,   0,   2 },
+      {  2,   2,  -2,  -4,  -2 },
+      {  4,   1,   2,   0,   2 },
+      {  4,  -1,  -2,  -2,  -2 },
+      {  2,   1,   0,  -2,  -2 },
+      {  2,   1,  -2,  -6,  -2 },
+      {  2,   0,   0,  -1,   1 },
+
+   /* 521-530 */
+      {  2,  -1,  -2,   2,  -1 },
+      {  1,   1,  -2,   2,  -2 },
+      {  1,   1,  -2,  -3,  -2 },
+      {  1,   0,   3,   0,   3 },
+      {  1,   0,  -2,   1,   1 },
+      {  1,   0,  -2,   0,   2 },
+      {  1,  -1,   2,   1,   2 },
+      {  1,  -1,   0,   0,  -2 },
+      {  1,  -1,  -4,   2,  -2 },
+      {  0,   3,  -2,  -2,  -2 },
+
+   /* 531-540 */
+      {  0,   1,   0,   4,   1 },
+      {  0,   0,   4,   2,   2 },
+      {  3,   0,  -2,  -2,   0 },
+      {  2,  -2,   0,   0,   0 },
+      {  1,   1,   2,  -4,   0 },
+      {  1,   1,   0,  -3,   0 },
+      {  1,   0,   2,  -3,   0 },
+      {  1,  -1,   2,  -2,   0 },
+      {  0,   2,   0,   2,   0 },
+      {  0,   0,   2,   4,   0 },
+
+   /* 541-550 */
+      {  1,   0,   1,   0,   0 },
+      {  3,   1,   2,  -2,   1 },
+      {  3,   0,   4,  -2,   2 },
+      {  3,   0,   2,   1,   2 },
+      {  3,   0,   0,   2,  -1 },
+      {  3,   0,   0,   0,   2 },
+      {  3,   0,  -2,   2,  -1 },
+      {  2,   0,   4,  -4,   2 },
+      {  2,   0,   2,  -3,   2 },
+      {  2,   0,   0,   4,   1 },
+
+   /* 551-560 */
+      {  2,   0,   0,  -3,   1 },
+      {  2,   0,  -4,   2,  -1 },
+      {  2,   0,  -2,  -2,   1 },
+      {  2,  -2,   2,   2,   2 },
+      {  2,  -2,   0,  -2,  -2 },
+      {  2,  -1,   0,   2,   1 },
+      {  2,  -1,   0,   2,  -1 },
+      {  1,   1,   2,   4,   2 },
+      {  1,   1,   0,   1,   1 },
+      {  1,   1,   0,   1,  -1 },
+
+   /* 561-570 */
+      {  1,   1,  -2,  -6,  -2 },
+      {  1,   0,   0,  -3,  -1 },
+      {  1,   0,  -4,  -2,  -1 },
+      {  1,   0,  -2,  -6,  -1 },
+      {  1,  -2,   2,   2,   1 },
+      {  1,  -2,  -2,   2,  -1 },
+      {  1,  -1,  -2,  -4,  -1 },
+      {  0,   2,   0,   0,   2 },
+      {  0,   1,   2,  -4,   2 },
+      {  0,   1,  -2,   4,  -1 },
+
+   /* 571-580 */
+      {  5,   0,   0,   0,   0 },
+      {  3,   0,   0,  -3,   0 },
+      {  2,   2,   0,  -4,   0 },
+      {  1,  -1,   2,   2,   0 },
+      {  0,   1,   0,   3,   0 },
+      {  4,   0,  -2,   0,  -1 },
+      {  3,   0,  -2,  -6,  -1 },
+      {  3,   0,  -2,  -1,  -1 },
+      {  2,   1,   2,   2,   1 },
+      {  2,   1,   0,   2,   1 },
+
+   /* 581-590 */
+      {  2,   0,   2,   4,   1 },
+      {  2,   0,   2,  -6,   1 },
+      {  2,   0,   2,  -2,  -1 },
+      {  2,   0,   0,  -6,  -1 },
+      {  2,  -1,  -2,  -2,  -1 },
+      {  1,   2,   2,   0,   1 },
+      {  1,   2,   0,   0,   1 },
+      {  1,   0,   4,   0,   1 },
+      {  1,   0,   2,  -6,   1 },
+      {  1,   0,   2,  -4,  -1 },
+
+   /* 591-600 */
+      {  1,   0,  -1,  -2,  -1 },
+      {  1,  -1,   2,   4,   1 },
+      {  1,  -1,   2,  -3,   1 },
+      {  1,  -1,   0,   4,   1 },
+      {  1,  -1,  -2,   1,  -1 },
+      {  0,   1,   2,  -2,   3 },
+      {  3,   0,   0,  -2,   0 },
+      {  1,   0,   1,  -2,   0 },
+      {  0,   2,   0,  -4,   0 },
+      {  0,   0,   2,  -4,   0 },
+
+   /* 601-610 */
+      {  0,   0,   1,  -1,   0 },
+      {  0,   0,   0,   6,   0 },
+      {  0,   2,   0,   0,  -2 },
+      {  0,   1,  -2,   2,  -3 },
+      {  4,   0,   0,   2,   0 },
+      {  3,   0,   0,  -1,   0 },
+      {  3,  -1,   0,   2,   0 },
+      {  2,   1,   0,   1,   0 },
+      {  2,   1,   0,  -6,   0 },
+      {  2,  -1,   2,   0,   0 },
+
+   /* 611-620 */
+      {  1,   0,   2,  -1,   0 },
+      {  1,  -1,   0,   1,   0 },
+      {  1,  -1,  -2,  -2,   0 },
+      {  0,   1,   2,   2,   0 },
+      {  0,   0,   2,  -3,   0 },
+      {  2,   2,   0,  -2,  -1 },
+      {  2,  -1,  -2,   0,   1 },
+      {  1,   2,   2,  -4,   1 },
+      {  0,   1,   4,  -4,   2 },
+      {  0,   0,   0,   3,   2 },
+
+   /* 621-630 */
+      {  5,   0,   2,   0,   1 },
+      {  4,   1,   2,  -2,   2 },
+      {  4,   0,  -2,  -2,   0 },
+      {  3,   1,   2,   2,   2 },
+      {  3,   1,   0,  -2,   0 },
+      {  3,   1,  -2,  -6,  -2 },
+      {  3,   0,   0,   0,  -2 },
+      {  3,   0,  -2,  -4,  -2 },
+      {  3,  -1,   0,  -3,   0 },
+      {  3,  -1,   0,  -2,   0 },
+
+   /* 631-640 */
+      {  2,   1,   2,   0,   0 },
+      {  2,   1,   2,  -4,   2 },
+      {  2,   1,   2,  -2,   0 },
+      {  2,   1,   0,  -3,   0 },
+      {  2,   1,  -2,   0,  -2 },
+      {  2,   0,   0,  -4,   2 },
+      {  2,   0,   0,  -4,  -2 },
+      {  2,   0,  -2,  -5,  -2 },
+      {  2,  -1,   2,   4,   2 },
+      {  2,  -1,   0,  -2,   2 },
+
+   /* 641-650 */
+      {  1,   3,  -2,  -2,  -2 },
+      {  1,   1,   0,   0,  -2 },
+      {  1,   1,   0,  -6,   0 },
+      {  1,   1,  -2,   1,  -2 },
+      {  1,   1,  -2,  -1,  -2 },
+      {  1,   0,   2,   1,   0 },
+      {  1,   0,   0,   3,   0 },
+      {  1,   0,   0,  -4,   2 },
+      {  1,   0,  -2,   4,  -2 },
+      {  1,  -2,   0,  -1,   0 },
+
+   /* 651-NFLS */
+      {  0,   1,  -4,   2,  -1 },
+      {  1,   0,  -2,   0,  -3 },
+      {  0,   0,   4,  -4,   4 }
+   };
+
+/* Number of frequencies:  luni-solar */
+   static const int NFLS = (int) (sizeof mfals / sizeof (int) / 5);
+
+/* Fundamental-argument multipliers:  planetary terms */
+   static const int mfapl[][14] = {
+
+   /* 1-10 */
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0, -2,  5,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  2, -5,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  3, -5,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  1, -1,  1,  0, -8, 12,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -8,  3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  1, -1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  8,-16,  4,  5,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0, -1,  2,  0,  0,  0,  0,  0 },
+
+   /* 11-20 */
+      {  0,  0,  0,  0,  0,  0,  8,-13,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  2, -5,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0, -5,  6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  4, -6,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0, -1,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -8,  3,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  2, -4,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  6, -8,  3,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  1, -2,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  2, -3,  0,  0,  0,  0,  0,  0 },
+
+   /* 21-30 */
+      {  0,  0,  0,  0,  0,  0,  2, -2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  1,  0,  0, -4,  8, -3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0,  4, -8,  3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  2, -5,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  1,  1,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  1, -1,  1,  0,  0,  0, -2,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2, -1,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  1 },
+      {  2,  0,  0, -2,  0,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+
+   /* 31-40 */
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0, -2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  8,-13,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  5, -8,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -2,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  2, -5,  0,  0,  1 },
+      {  2,  0,  0, -2,  0,  0,  0, -2,  0,  3,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0, -1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3, -4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  0, -1,  0,  0,  0 },
+
+   /* 41-50 */
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  5, -7,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  1, -1,  0,  0,  0,  0, -2,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  4,  0, -2,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  8,-13,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  2, -1,  0,  0,  0,  0,  0,  2 },
+      {  1,  0,  0,  0,  0,  0,-18, 16,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  1,  0,  0,  0,  2 },
+
+   /* 51-60 */
+      {  0,  0,  1, -1,  1,  0, -5,  7,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0,  0,  0,  0,-10,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  0,  0, -5,  6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0, -1,  0,  0,  0,  2 },
+      {  1,  0,  2,  0,  2,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -2,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  1 },
+      {  1,  0, -2,  0, -2,  0,  0,  4, -8,  3,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  0,  2,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+
+   /* 61-70 */
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  8,-16,  4,  5,  0,  0, -2 },
+      {  0,  0,  1, -1,  1,  0,  0,  3, -8,  3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  8,-11,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  3,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  8,-16,  4,  5,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  1, -1,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  4, -6,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -3,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -4,  0,  0,  0,  0,  0 },
+
+   /* 71-80 */
+      {  0,  0,  0,  0,  0,  0,  6, -8,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  3, -2,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  8,-15,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  2, -5,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  1, -3,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0, -2,  0,  0,  0,  2 },
+      {  0,  0,  1, -1,  1,  0,  0, -5,  8, -3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  2,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -2,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  3, -5,  0,  0,  0,  0,  0,  0 },
+
+   /* 81-90 */
+      {  2,  0,  0, -2,  1,  0,  0, -2,  0,  3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  5, -8,  0,  0,  0,  0,  0, -1 },
+      {  2,  0,  0, -2,  0,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  8,-13,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0,  0,  0, -2,  5,  0,  0,  0 },
+      {  1,  0,  0, -1,  0,  0, -3,  4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  0,  0,  2 },
+      {  1,  0,  0,  0, -1,  0,-18, 16,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0,  0,  0,  2, -5,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0,  0,  0,  1,  0,  0,  0,  0 },
+
+   /* 91-100 */
+      {  1,  0,  0, -2,  0,  0, 19,-21,  3,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0, -8, 13,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  0,  1,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  7, -9,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  0,  2 },
+      {  1,  0,  0,  0,  1,  0,-18, 16,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  2, -4,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  6,-16,  4,  5,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  4, -7,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  3, -7,  0,  0,  0,  0,  0, -2 },
+
+   /* 101-110 */
+      {  0,  0,  0,  0,  0,  0,  2, -2,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  1 },
+      {  2,  0,  0, -2,  1,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -4,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  1, -2,  0,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2, -1,  0,  0, -2,  0,  3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3, -3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  2,  0,  0,  0,  2 },
+
+   /* 111-120 */
+      {  0,  0,  0,  0,  1,  0,  0,  1, -2,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  2 },
+      {  0,  0,  2, -2,  1,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0, -3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3, -5,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  3, -3,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  4, -4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0,  0, -1,  0, -1,  0,  0,  0,  0 },
+      {  2,  0,  0, -2,  0,  0, -6,  8,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -2,  2,  0,  0,  0,  0,  0 },
+
+   /* 121-130 */
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  1 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1, -2,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -3,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -4,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  0, -1,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  8,-10,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  1, -1,  1,  0, -3,  4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  6, -9,  0,  0,  0,  0,  0, -2 },
+      {  1,  0,  0, -1,  1,  0,  0, -1,  0,  2,  0,  0,  0,  0 },
+
+   /* 131-140 */
+      {  0,  0,  0,  0,  0,  0,  5, -7,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  5, -5,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  3, -3,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  4,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4,  0, -3,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  1, -1,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  1,  0,  2, -3,  0,  0,  0,  0,  0,  0 },
+
+   /* 141-150 */
+      {  1,  0,  0, -1,  0,  0,  0, -1,  0,  1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  1, -3,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -4,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -4,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  9,-11,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  2, -3,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  8,-15,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0, -4,  5,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  4, -6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  4,  0, -1,  0,  0,  0,  2 },
+
+   /* 151-160 */
+      {  1,  0,  0, -1,  1,  0, -3,  4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1,  1,  1,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0, -4, 10,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  1, -1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0,  0, -1,  0,  0, -1,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3, -1,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -4,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  2, -5,  0,  0, -2 },
+      {  0,  0,  2, -2,  1,  0, -4,  4,  0,  0,  0,  0,  0,  0 },
+
+   /* 161-170 */
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0,  0, -1,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -3,  0,  0,  0,  0,  2 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  0,  0,  0,  2,  0 },
+      {  0,  0,  0,  0,  0,  0,  4, -4,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -4,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  5, -8,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1, -2,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  1,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0, -9, 13,  0,  0,  0,  0,  0 },
+      {  2,  0,  2,  0,  2,  0,  0,  2,  0, -3,  0,  0,  0,  0 },
+
+   /* 171-180 */
+      {  0,  0,  0,  0,  0,  0,  3, -6,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  1, -1,  2,  0,  0, -1,  0,  0,  2,  0,  0,  0 },
+      {  1,  0,  0, -1, -1,  0, -3,  4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -6,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  6, -6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  3,  0,  0,  0,  1 },
+      {  1,  0,  2,  0,  1,  0,  0, -2,  0,  3,  0,  0,  0,  0 },
+      {  1,  0, -2,  0, -1,  0,  0, -1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0, -2,  4,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -5,  0,  0,  0,  0,  0 },
+
+   /* 181-190 */
+      {  0,  0,  0,  0,  0,  0,  2,  1,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  1,  1,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  2,  0,  2,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1, -8,  3,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  6,-10,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  7, -8,  3,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  1,  0, -3,  5,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0, -1,  0,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0, -5,  7,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0, -2,  0,  0,  0,  1 },
+
+   /* 191-200 */
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0, -1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  7,-10,  0,  0,  0,  0,  0, -2 },
+      {  1,  0,  0, -2,  0,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  2, -5,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  6, -8,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  1, -1,  1,  0,  0, -9, 15,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0, -2,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0, -1,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -6,  0,  0,  0,  0,  0 },
+
+   /* 201-210 */
+      {  0,  0,  0,  0,  0,  0,  0,  1, -4,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  3,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  0, -1,  0,  0,  2 },
+      {  2,  0,  0, -2,  1,  0, -6,  8,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  5, -5,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  1, -1,  1,  0,  3, -6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0, -2,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  8,-14,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+
+   /* 211-220 */
+      {  0,  0,  0,  0,  1,  0,  0,  8,-15,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -6,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  7, -7,  0,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2,  1,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -1,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  1,  0,  0,  2 },
+      {  2,  0, -1, -1,  0,  0,  0,  3, -7,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -7,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -3,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -3,  4,  0,  0,  0,  0,  0 },
+
+   /* 221-230 */
+      {  2,  0,  0, -2,  0,  0,  0, -6,  8,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2,  0,  0,  0, -5,  6,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0,  0,  0, -1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  2,  1,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  1,  2,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  1,  0,  0,  1,  0, -1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1, -1,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3, -9,  4,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -5,  0,  0,  0,  0, -2 },
+
+   /* 231-240 */
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0, -4,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  2,  1 },
+      {  0,  0,  0,  0,  0,  0,  7,-11,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  3, -5,  4,  0,  0,  0,  0,  2 },
+      {  0,  0,  1, -1,  0,  0,  0, -1,  0, -1,  1,  0,  0,  0 },
+      {  2,  0,  0,  0,  0,  0,  0, -2,  0,  3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  8,-15,  0,  0,  0,  0, -2 },
+      {  0,  0,  1, -1,  2,  0,  0, -2,  2,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  3,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  6, -6,  0,  0,  0,  0,  0, -1 },
+
+   /* 241-250 */
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0, -1,  1,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  2, -2,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -7,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -8,  3,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  2, -4,  0, -3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  3, -5,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0, -3,  0,  0,  0,  2 },
+      {  0,  0,  2, -2,  2,  0, -8, 11,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -8,  3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  0, -2,  0,  0,  0 },
+
+   /* 251-260 */
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  1,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -9,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -5,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  7, -9,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  4, -7,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  2, -1,  0,  0,  0,  0,  0,  0 },
+      {  1,  0, -2, -2, -2,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  1,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0, -2,  5,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  3, -3,  0,  0,  0,  0,  0,  1 },
+
+   /* 261-270 */
+      {  0,  0,  0,  0,  0,  0,  0,  6,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  2, -5,  0,  0,  2 },
+      {  2,  0,  0, -2, -1,  0,  0, -2,  0,  0,  5,  0,  0,  0 },
+      {  2,  0,  0, -2, -1,  0, -6,  8,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0, -2,  0,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  8, -8,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0,  2, -5,  0,  0,  2 },
+      {  0,  0,  0,  0,  1,  0,  3, -7,  4,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0, -2,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0, -1,  0,  1,  0,  0,  0,  0 },
+
+   /* 271-280 */
+      {  0,  0,  1, -1,  0,  0,  0, -1,  0, -2,  5,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0, -3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3, -1,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  2, -3,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0, 11,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  6,-15,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0,  1,  0,  0,  0,  2 },
+      {  1,  0,  0, -1,  0,  0,  0, -3,  4,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0, -3,  7, -4,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  5,  0, -2,  0,  0,  0,  2 },
+
+   /* 281-290 */
+      {  0,  0,  0,  0,  0,  0,  3, -5,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  2, -2,  2,  0, -5,  6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  2,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3,  0,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  4, -4,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -8,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -5,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  5, -7,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  6,-11,  0,  0,  0,  0, -2 },
+
+   /* 291-300 */
+      {  0,  0,  0,  0,  0,  0,  0,  1, -3,  0,  0,  0,  0, -2 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  3,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0,  0, -1,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  1, -2,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  9,-12,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  4, -4,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  1, -1,  0,  0, -8, 12,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0, -2,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  7, -7,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -6,  0,  0,  0,  0, -1 },
+
+   /* 301-310 */
+      {  0,  0,  0,  0,  0,  0,  0,  6, -6,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  1,  0, -4,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  1, -1,  1,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  6, -9,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  1, -1, -1,  0,  0,  0, -2,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1, -5,  0,  0,  0,  0, -2 },
+      {  2,  0,  0, -2,  0,  0,  0, -2,  0,  3, -1,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  0, -2,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -9,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  5, -6,  0,  0,  0,  0,  0,  2 },
+
+   /* 311-320 */
+      {  0,  0,  0,  0,  0,  0,  9, -9,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  0,  3,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0,  2, -4,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  5, -3,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  3,  0,  0,  1 },
+      {  0,  0,  1, -1,  2,  0,  0, -1,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  5, -9,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -3,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  4,  0,  0,  0,  2 },
+      {  0,  0,  2,  0,  2,  0,  0,  4, -8,  3,  0,  0,  0,  0 },
+
+   /* 321-330 */
+      {  0,  0,  2,  0,  2,  0,  0, -4,  8, -3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  5,  0, -3,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  0,  0,  0,  0 },
+      {  2,  0, -1, -1, -1,  0,  0, -1,  0,  3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  4, -3,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  4, -2,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  5,-10,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  8,-13,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  2, -2,  1, -1,  0,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  0,  0,  2,  0,  0 },
+
+   /* 331-340 */
+      {  0,  0,  0,  0,  1,  0,  3, -5,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0, -2,  0,  0,  0, -2,  0,  3,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  0,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  9, -9,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2,  0,  2,  0,  1, -1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0, -8, 11,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0, -2,  0,  0,  2,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0, -1,  2,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  5, -5,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  2, -6,  0,  0,  0,  0,  0, -2 },
+
+   /* 341-350 */
+      {  0,  0,  0,  0,  0,  0,  0,  8,-15,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -2,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  7,-13,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0, -2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  3,  0,  0,  0,  2 },
+      {  0,  0,  2, -2,  1,  0,  0, -2,  0,  3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  8, -8,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  8,-10,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  4, -2,  0,  0,  0,  0,  0,  1 },
+
+   /* 351-360 */
+      {  0,  0,  0,  0,  0,  0,  3, -6,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  3, -4,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  2, -5,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0, -4,  0,  0,  0,  0 },
+      {  2,  0,  0, -2, -1,  0,  0, -5,  6,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -5,  0,  0,  0,  0, -2 },
+      {  2,  0, -1, -1, -1,  0,  0,  3, -7,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -8,  0,  0,  0,  0,  0 },
+      {  0,  0,  2,  0,  2,  0, -1,  1,  0,  0,  0,  0,  0,  0 },
+
+   /* 361-370 */
+      {  2,  0,  0, -2,  0,  0,  0, -2,  0,  4, -3,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  6,-11,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2,  1,  0,  0, -6,  8,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -8,  1,  5,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  6, -5,  0,  0,  0,  0,  2 },
+      {  1,  0, -2, -2, -2,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  2,  0,  0,  0, -2,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  2,  0,  0,  4, -8,  3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  2,  0,  0, -4,  8, -3,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  6,  0,  0,  0,  0,  0,  1 },
+
+   /* 371-380 */
+      {  0,  0,  0,  0,  0,  0,  0,  6, -7,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4,  0,  0, -2,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0,  0, -2,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -1,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  0,  1, -6,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  4, -5,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  2,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  3, -5,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  7,-13,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0, -2,  0,  0,  0,  2 },
+
+   /* 381-390 */
+      {  0,  0,  1, -1,  0,  0,  0, -1,  0,  0,  2,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0, -8, 15,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2, -2,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+      {  2,  0, -1, -1, -1,  0,  0, -1,  0,  2,  0,  0,  0,  0 },
+      {  1,  0,  2, -2,  2,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  1,  0, -1,  1, -1,  0,-18, 17,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2,  0,  2,  0,  0,  1,  0, -1,  0,  0,  0,  0 },
+      {  0,  0,  2,  0,  2,  0,  0, -1,  0,  1,  0,  0,  0,  0 },
+      {  0,  0,  2, -2, -1,  0, -5,  6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  2,  0,  0, -1,  0,  1,  0,  0,  0,  0 },
+
+   /* 391-400 */
+      {  0,  0,  0,  0,  1,  0,  2, -2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  8,-16,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  5,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  2,  2 },
+      {  0,  0,  0,  0,  2,  0,  0, -1,  2,  0,  0,  0,  0,  0 },
+      {  2,  0, -1, -1, -2,  0,  0, -1,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  6,-10,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0, -2,  4,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  2,  0,  0,  0,  0,  2 },
+      {  2,  0,  0, -2, -1,  0,  0, -2,  0,  4, -5,  0,  0,  0 },
+
+   /* 401-410 */
+      {  2,  0,  0, -2, -1,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+      {  2,  0, -1, -1, -1,  0,  0, -1,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  1, -1,  1,  0,  0, -1,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0, -1, -1,  0,  0, -2,  2,  0,  0,  0,  0,  0 },
+      {  1,  0, -1, -1, -1,  0, 20,-20,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0, -1,  0,  1,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  1, -2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0, -2,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  5, -8,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0,  0,  0,  0, -1,  0,  0,  0 },
+
+   /* 411-420 */
+      {  0,  0,  0,  0,  0,  0,  9,-11,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  5, -3,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -3,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  2,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  6, -7,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -2,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  1, -2,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  0, -2,  0,  0,  0 },
+      {  0,  0,  1, -1,  2,  0,  0, -1,  0, -2,  5,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -7,  0,  0,  0,  0,  0 },
+
+   /* 421-430 */
+      {  0,  0,  0,  0,  0,  0,  1, -3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -8,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -6,  0,  0,  0,  0, -2 },
+      {  1,  0,  0, -2,  0,  0, 20,-21,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  8,-12,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  5, -6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -4,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  2,  0,  0, -1,  0, -1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  8,-12,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  9,-17,  0,  0,  0,  0,  0 },
+
+   /* 431-440 */
+      {  0,  0,  0,  0,  0,  0,  0,  5, -6,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -8,  1,  5,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -6,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -7,  0,  0,  0,  0, -2 },
+      {  1,  0,  0, -1,  1,  0,  0, -3,  4,  0,  0,  0,  0,  0 },
+      {  1,  0, -2,  0, -2,  0,-10,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0, -9, 17,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  1, -4,  0,  0,  0,  0,  0, -2 },
+      {  1,  0, -2, -2, -2,  0,  0, -2,  0,  3,  0,  0,  0,  0 },
+      {  1,  0, -1,  1, -1,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+
+   /* 441-450 */
+      {  0,  0,  2, -2,  2,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  2,  0,  0, -1,  0,  0,  1,  0,  0,  0 },
+      {  0,  0,  1, -1,  2,  0, -5,  7,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0,  2, -2,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  4, -5,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  3, -4,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  2, -4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  5,-10,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4,  0, -4,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0, -5,  0,  0,  0, -2 },
+
+   /* 451-460 */
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -5,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -2,  5,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -2,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  2, -3,  0,  0,  0,  0,  0,  1 },
+      {  1,  0,  0, -2,  0,  0,  0,  1,  0, -1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3, -7,  4,  0,  0,  0,  0,  0 },
+      {  2,  0,  2,  0,  1,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1, -1,  0,  0, -1,  0, -1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0,  1,  0, -2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  6,-10,  0,  0,  0,  0, -2 },
+
+   /* 461-470 */
+      {  1,  0,  0, -1,  1,  0,  0, -1,  0,  1,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0,  4, -8,  3,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0,  1,  0, -1,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0, -4,  8, -3,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0, -3,  0,  3,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0, -5,  5,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  1, -3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -4,  6,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  0,  0, -1,  0,  0 },
+      {  0,  0,  1, -1,  1,  0, -5,  6,  0,  0,  0,  0,  0,  0 },
+
+   /* 471-480 */
+      {  0,  0,  0,  0,  1,  0,  3, -4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0, -2,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  7,-10,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  5, -5,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  4, -5,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  3, -8,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  2, -5,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  1, -2,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  7, -9,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  7, -8,  0,  0,  0,  0,  2 },
+
+   /* 481-490 */
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -8,  3,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  0, -2,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -4,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -1,  0,  0,  0, -1 },
+      {  2,  0,  0, -2, -1,  0,  0, -6,  8,  0,  0,  0,  0,  0 },
+      {  2,  0, -1, -1,  1,  0,  0,  3, -7,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0, -7,  9,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -5,  0,  0,  0,  0, -1 },
+
+   /* 491-500 */
+      {  0,  0,  1, -1,  2,  0, -8, 12,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0,  0,  0,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  1,  0,  0, -2,  0,  0,  2, -2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  7, -8,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2,  1,  0,  0, -5,  6,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2, -1,  0,  0, -2,  0,  3, -1,  0,  0,  0 },
+      {  1,  0,  1,  1,  1,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0, -2,  1,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  1,  0,  0, -2, -1,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+
+   /* 501-510 */
+      {  1,  0,  0, -1, -1,  0,  0, -3,  4,  0,  0,  0,  0,  0 },
+      {  1,  0, -1,  0, -1,  0, -3,  5,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0, -4,  4,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0, -2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0, -8, 11,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  0,  0,  0, -9, 13,  0,  0,  0,  0,  0 },
+      {  0,  0,  1,  1,  2,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0,  1, -4,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0,  0, -1,  0,  1, -3,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0,  7,-13,  0,  0,  0,  0,  0 },
+
+   /* 511-520 */
+      {  0,  0,  0,  0,  1,  0,  0,  2,  0, -2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0, -2,  2,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0, -3,  4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  1,  0, -4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  7,-11,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  6, -6,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  6, -4,  0,  0,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  5, -6,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  4, -2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3, -4,  0,  0,  0,  0,  0,  1 },
+
+   /* 521-530 */
+      {  0,  0,  0,  0,  0,  0,  1, -4,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  9,-17,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  7, -7,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -8,  3,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -8,  3,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -8,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  4, -7,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  0,  0,  0,  1 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -4,  0,  0,  0,  0 },
+      {  2,  0,  0, -2,  0,  0,  0, -4,  8, -3,  0,  0,  0,  0 },
+
+   /* 531-540 */
+      {  2,  0,  0, -2,  0,  0, -2,  2,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0,  0,  0,  0,  0,  4, -8,  3,  0,  0,  0,  0 },
+      {  1,  0,  0,  0,  0,  0,  0, -4,  8, -3,  0,  0,  0,  0 },
+      {  1,  0,  0,  0,  0,  0, -1,  1,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0, -2,  0,  0, 17,-16,  0, -2,  0,  0,  0,  0 },
+      {  1,  0,  0, -1,  0,  0,  0, -2,  2,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  0,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  6, -9,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0, -4,  0,  0,  0,  0 },
+
+   /* 541-550 */
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1, -2, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  1,  0,  0,  0,  0,  2 },
+      {  2,  0,  0, -2,  0,  0,  0, -4,  4,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2,  0,  0,  0, -2,  0,  2,  2,  0,  0,  0 },
+      {  1,  0,  0,  0,  0,  0,  1, -1,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0,  0,  0,  0,  0, -1,  0,  1,  0,  0,  0,  0 },
+      {  1,  0,  0,  0,  0,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0, -2,  0,  0,  1, -1,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0, -2,  0,  0,  0,  4, -8,  3,  0,  0,  0,  0 },
+      {  1,  0,  0, -2,  0,  0,  0, -4,  8, -3,  0,  0,  0,  0 },
+
+   /* 551-560 */
+      {  1,  0,  0, -2,  0,  0, -2,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  0,  0, -4,  4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1,  1,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0,  3, -6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0,  0, -2,  2,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0,  0, -1,  0,  1,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0,  0, -1,  0,  0,  1,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0, -4,  5,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0, -3,  4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  2,  0,  0,  0, -1,  0,  1,  0,  0,  0,  0 },
+
+   /* 561-570 */
+      {  0,  0,  0,  0,  0,  0,  8, -9,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3, -6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  1,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  3, -5,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  2, -2,  0,  0,  0 },
+      {  2,  0, -2, -2, -2,  0,  0, -2,  0,  2,  0,  0,  0,  0 },
+      {  1,  0,  0,  0,  1,  0,-10,  3,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0,  0, -1,  0,-10,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2,  0,  2,  0,  2, -3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2,  0,  2,  0,  2, -2,  0,  0,  0,  0,  0,  0 },
+
+   /* 571-580 */
+      {  0,  0,  2,  0,  2,  0, -2,  3,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2,  0,  2,  0, -2,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  2,  0,  0,  0,  0,  1,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0,  0, -1,  0,  2,  0,  0,  0,  0 },
+      {  2,  0,  2, -2,  2,  0,  0, -2,  0,  3,  0,  0,  0,  0 },
+      {  2,  0,  1, -3,  1,  0, -6,  7,  0,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2,  0,  0,  2, -5,  0,  0,  0,  0,  0,  0 },
+      {  2,  0,  0, -2,  0,  0,  0, -2,  0,  5, -5,  0,  0,  0 },
+      {  2,  0,  0, -2,  0,  0,  0, -2,  0,  1,  5,  0,  0,  0 },
+      {  2,  0,  0, -2,  0,  0,  0, -2,  0,  0,  5,  0,  0,  0 },
+
+   /* 581-590 */
+      {  2,  0,  0, -2,  0,  0,  0, -2,  0,  0,  2,  0,  0,  0 },
+      {  2,  0,  0, -2,  0,  0, -4,  4,  0,  0,  0,  0,  0,  0 },
+      {  2,  0, -2,  0, -2,  0,  0,  5, -9,  0,  0,  0,  0,  0 },
+      {  2,  0, -1, -1,  0,  0,  0, -1,  0,  3,  0,  0,  0,  0 },
+      {  1,  0,  2,  0,  2,  0,  1, -1,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  2,  0,  2,  0,  0,  4, -8,  3,  0,  0,  0,  0 },
+      {  1,  0,  2,  0,  2,  0,  0, -4,  8, -3,  0,  0,  0,  0 },
+      {  1,  0,  2,  0,  2,  0, -1,  1,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  2, -2,  2,  0, -3,  3,  0,  0,  0,  0,  0,  0 },
+      {  1,  0,  0,  0,  0,  0,  0,  1,  0, -1,  0,  0,  0,  0 },
+
+   /* 591-600 */
+      {  1,  0,  0,  0,  0,  0,  0, -2,  0,  3,  0,  0,  0,  0 },
+      {  1,  0,  0, -2,  0,  0,  0,  2,  0, -2,  0,  0,  0,  0 },
+      {  1,  0, -2, -2, -2,  0,  0,  1,  0, -1,  0,  0,  0,  0 },
+      {  1,  0, -1,  1,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+      {  1,  0, -1, -1,  0,  0,  0,  8,-15,  0,  0,  0,  0,  0 },
+      {  0,  0,  2,  2,  2,  0,  0,  2,  0, -2,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  1, -1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0, -2,  0,  1,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  1,  0,  0,-10, 15,  0,  0,  0,  0,  0 },
+      {  0,  0,  2, -2,  0, -1,  0,  2,  0,  0,  0,  0,  0,  0 },
+
+   /* 601-610 */
+      {  0,  0,  1, -1,  2,  0,  0, -1,  0,  0, -1,  0,  0,  0 },
+      {  0,  0,  1, -1,  2,  0, -3,  4,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0, -4,  6,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  1,  0, -1,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0,  0, -1,  0,  0, -2,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0, -2,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1,  0,  0, -1,  0,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  1, -1, -1,  0, -5,  7,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  2,  0,  0,  0,  2,  0, -2,  0,  0,  0,  0 },
+
+   /* 611-620 */
+      {  0,  0,  0,  2,  0,  0, -2,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  2,  0, -3,  5,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  1,  0, -1,  2,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  9,-13,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  8,-14,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  8,-11,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  6, -9,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  6, -8,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  6, -7,  0,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  5, -6,  0,  0,  0,  0,  0, -2 },
+
+   /* 621-630 */
+      {  0,  0,  0,  0,  0,  0,  5, -6, -4,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  5, -4,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  4, -8,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  4, -5,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  3, -3,  0,  2,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  3, -1,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  2,  0,  0,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  1, -1,  0,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  7,-12,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  6, -9,  0,  0,  0,  0, -2 },
+
+   /* 631-640 */
+      {  0,  0,  0,  0,  0,  0,  0,  6, -8,  1,  5,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  6, -4,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  6,-10,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  5,  0, -4,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -9,  0,  0,  0,  0, -1 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -8,  3,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -7,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  5, -6,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  5,-16,  4,  5,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  5,-13,  0,  0,  0,  0, -2 },
+
+   /* 641-650 */
+      {  0,  0,  0,  0,  0,  0,  0,  3,  0, -5,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -9,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  3, -7,  0,  0,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  2,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  2,  0,  0, -3,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  2, -8,  1,  5,  0,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  1, -5,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  2,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0,  0, -3,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  1,  0, -3,  5,  0,  0,  0 },
+
+   /* 651-NFPL */
+      {  0,  0,  0,  0,  0,  0,  0,  1, -3,  0,  0,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  2, -6,  3,  0, -2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  1, -2,  0,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  2 },
+      {  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0 }
+   };
+
+/* Number of frequencies:  planetary */
+   static const int NFPL = (int) (sizeof mfapl / sizeof (int) / 14);
+
+/* Pointers into amplitudes array, one pointer per frequency */
+   static const int nc[] = {
+
+   /* 1-100 */
+       1,    21,    37,    51,    65,    79,    91,   103,   115,   127,
+     139,   151,   163,   172,   184,   196,   207,   219,   231,   240,
+     252,   261,   273,   285,   297,   309,   318,   327,   339,   351,
+     363,   372,   384,   396,   405,   415,   423,   435,   444,   452,
+     460,   467,   474,   482,   490,   498,   506,   513,   521,   528,
+     536,   543,   551,   559,   566,   574,   582,   590,   597,   605,
+     613,   620,   628,   636,   644,   651,   658,   666,   674,   680,
+     687,   695,   702,   710,   717,   725,   732,   739,   746,   753,
+     760,   767,   774,   782,   790,   798,   805,   812,   819,   826,
+     833,   840,   846,   853,   860,   867,   874,   881,   888,   895,
+
+   /* 101-200 */
+     901,   908,   914,   921,   928,   934,   941,   948,   955,   962,
+     969,   976,   982,   989,   996,  1003,  1010,  1017,  1024,  1031,
+    1037,  1043,  1050,  1057,  1064,  1071,  1078,  1084,  1091,  1098,
+    1104,  1112,  1118,  1124,  1131,  1138,  1145,  1151,  1157,  1164,
+    1171,  1178,  1185,  1192,  1199,  1205,  1212,  1218,  1226,  1232,
+    1239,  1245,  1252,  1259,  1266,  1272,  1278,  1284,  1292,  1298,
+    1304,  1310,  1316,  1323,  1329,  1335,  1341,  1347,  1353,  1359,
+    1365,  1371,  1377,  1383,  1389,  1396,  1402,  1408,  1414,  1420,
+    1426,  1434,  1440,  1446,  1452,  1459,  1465,  1471,  1477,  1482,
+    1488,  1493,  1499,  1504,  1509,  1514,  1520,  1527,  1532,  1538,
+
+   /* 201-300 */
+    1543,  1548,  1553,  1558,  1564,  1569,  1574,  1579,  1584,  1589,
+    1594,  1596,  1598,  1600,  1602,  1605,  1608,  1610,  1612,  1617,
+    1619,  1623,  1625,  1627,  1629,  1632,  1634,  1640,  1642,  1644,
+    1646,  1648,  1650,  1652,  1654,  1658,  1660,  1662,  1664,  1668,
+    1670,  1672,  1673,  1675,  1679,  1681,  1683,  1684,  1686,  1688,
+    1690,  1693,  1695,  1697,  1701,  1703,  1705,  1707,  1709,  1711,
+    1712,  1715,  1717,  1721,  1723,  1725,  1727,  1729,  1731,  1733,
+    1735,  1737,  1739,  1741,  1743,  1745,  1747,  1749,  1751,  1753,
+    1755,  1757,  1759,  1761,  1762,  1764,  1766,  1768,  1769,  1771,
+    1773,  1775,  1777,  1779,  1781,  1783,  1785,  1787,  1788,  1790,
+
+   /* 301-400 */
+    1792,  1794,  1796,  1798,  1800,  1802,  1804,  1806,  1807,  1809,
+    1811,  1815,  1817,  1819,  1821,  1823,  1825,  1827,  1829,  1831,
+    1833,  1835,  1837,  1839,  1840,  1842,  1844,  1848,  1850,  1852,
+    1854,  1856,  1858,  1859,  1860,  1862,  1864,  1866,  1868,  1869,
+    1871,  1873,  1875,  1877,  1879,  1881,  1883,  1885,  1887,  1889,
+    1891,  1892,  1896,  1898,  1900,  1901,  1903,  1905,  1907,  1909,
+    1910,  1911,  1913,  1915,  1919,  1921,  1923,  1927,  1929,  1931,
+    1933,  1935,  1937,  1939,  1943,  1945,  1947,  1948,  1949,  1951,
+    1953,  1955,  1957,  1958,  1960,  1962,  1964,  1966,  1968,  1970,
+    1971,  1973,  1974,  1975,  1977,  1979,  1980,  1981,  1982,  1984,
+
+   /* 401-500 */
+    1986,  1988,  1990,  1992,  1994,  1995,  1997,  1999,  2001,  2003,
+    2005,  2007,  2008,  2009,  2011,  2013,  2015,  2017,  2019,  2021,
+    2023,  2024,  2025,  2027,  2029,  2031,  2033,  2035,  2037,  2041,
+    2043,  2045,  2046,  2047,  2049,  2051,  2053,  2055,  2056,  2057,
+    2059,  2061,  2063,  2065,  2067,  2069,  2070,  2071,  2072,  2074,
+    2076,  2078,  2080,  2082,  2084,  2086,  2088,  2090,  2092,  2094,
+    2095,  2096,  2097,  2099,  2101,  2105,  2106,  2107,  2108,  2109,
+    2110,  2111,  2113,  2115,  2119,  2121,  2123,  2125,  2127,  2129,
+    2131,  2133,  2135,  2136,  2137,  2139,  2141,  2143,  2145,  2147,
+    2149,  2151,  2153,  2155,  2157,  2159,  2161,  2163,  2165,  2167,
+
+   /* 501-600 */
+    2169,  2171,  2173,  2175,  2177,  2179,  2181,  2183,  2185,  2186,
+    2187,  2188,  2192,  2193,  2195,  2197,  2199,  2201,  2203,  2205,
+    2207,  2209,  2211,  2213,  2217,  2219,  2221,  2223,  2225,  2227,
+    2229,  2231,  2233,  2234,  2235,  2236,  2237,  2238,  2239,  2240,
+    2241,  2244,  2246,  2248,  2250,  2252,  2254,  2256,  2258,  2260,
+    2262,  2264,  2266,  2268,  2270,  2272,  2274,  2276,  2278,  2280,
+    2282,  2284,  2286,  2288,  2290,  2292,  2294,  2296,  2298,  2300,
+    2302,  2303,  2304,  2305,  2306,  2307,  2309,  2311,  2313,  2315,
+    2317,  2319,  2321,  2323,  2325,  2327,  2329,  2331,  2333,  2335,
+    2337,  2341,  2343,  2345,  2347,  2349,  2351,  2352,  2355,  2356,
+
+   /* 601-700 */
+    2357,  2358,  2359,  2361,  2363,  2364,  2365,  2366,  2367,  2368,
+    2369,  2370,  2371,  2372,  2373,  2374,  2376,  2378,  2380,  2382,
+    2384,  2385,  2386,  2387,  2388,  2389,  2390,  2391,  2392,  2393,
+    2394,  2395,  2396,  2397,  2398,  2399,  2400,  2401,  2402,  2403,
+    2404,  2405,  2406,  2407,  2408,  2409,  2410,  2411,  2412,  2413,
+    2414,  2415,  2417,  2418,  2430,  2438,  2445,  2453,  2460,  2468,
+    2474,  2480,  2488,  2496,  2504,  2512,  2520,  2527,  2535,  2543,
+    2550,  2558,  2566,  2574,  2580,  2588,  2596,  2604,  2612,  2619,
+    2627,  2634,  2642,  2648,  2656,  2664,  2671,  2679,  2685,  2693,
+    2701,  2709,  2717,  2725,  2733,  2739,  2747,  2753,  2761,  2769,
+
+   /* 701-800 */
+    2777,  2785,  2793,  2801,  2809,  2817,  2825,  2833,  2841,  2848,
+    2856,  2864,  2872,  2878,  2884,  2892,  2898,  2906,  2914,  2922,
+    2930,  2938,  2944,  2952,  2958,  2966,  2974,  2982,  2988,  2996,
+    3001,  3009,  3017,  3025,  3032,  3039,  3045,  3052,  3059,  3067,
+    3069,  3076,  3083,  3090,  3098,  3105,  3109,  3111,  3113,  3120,
+    3124,  3128,  3132,  3136,  3140,  3144,  3146,  3150,  3158,  3161,
+    3165,  3166,  3168,  3172,  3176,  3180,  3182,  3185,  3189,  3193,
+    3194,  3197,  3200,  3204,  3208,  3212,  3216,  3219,  3221,  3222,
+    3226,  3230,  3234,  3238,  3242,  3243,  3247,  3251,  3254,  3258,
+    3262,  3266,  3270,  3274,  3275,  3279,  3283,  3287,  3289,  3293,
+
+   /* 801-900 */
+    3296,  3300,  3303,  3307,  3311,  3315,  3319,  3321,  3324,  3327,
+    3330,  3334,  3338,  3340,  3342,  3346,  3350,  3354,  3358,  3361,
+    3365,  3369,  3373,  3377,  3381,  3385,  3389,  3393,  3394,  3398,
+    3402,  3406,  3410,  3413,  3417,  3421,  3425,  3429,  3433,  3435,
+    3439,  3443,  3446,  3450,  3453,  3457,  3458,  3461,  3464,  3468,
+    3472,  3476,  3478,  3481,  3485,  3489,  3493,  3497,  3501,  3505,
+    3507,  3511,  3514,  3517,  3521,  3524,  3525,  3527,  3529,  3533,
+    3536,  3540,  3541,  3545,  3548,  3551,  3555,  3559,  3563,  3567,
+    3569,  3570,  3574,  3576,  3578,  3582,  3586,  3590,  3593,  3596,
+    3600,  3604,  3608,  3612,  3616,  3620,  3623,  3626,  3630,  3632,
+
+   /* 901-1000 */
+    3636,  3640,  3643,  3646,  3648,  3652,  3656,  3660,  3664,  3667,
+    3669,  3671,  3675,  3679,  3683,  3687,  3689,  3693,  3694,  3695,
+    3699,  3703,  3705,  3707,  3710,  3713,  3717,  3721,  3725,  3729,
+    3733,  3736,  3740,  3744,  3748,  3752,  3754,  3757,  3759,  3763,
+    3767,  3770,  3773,  3777,  3779,  3783,  3786,  3790,  3794,  3798,
+    3801,  3805,  3809,  3813,  3817,  3821,  3825,  3827,  3831,  3835,
+    3836,  3837,  3840,  3844,  3848,  3852,  3856,  3859,  3863,  3867,
+    3869,  3871,  3875,  3879,  3883,  3887,  3890,  3894,  3898,  3901,
+    3905,  3909,  3913,  3917,  3921,  3922,  3923,  3924,  3926,  3930,
+    3932,  3936,  3938,  3940,  3944,  3948,  3952,  3956,  3959,  3963,
+
+   /* 1001-1100 */
+    3965,  3969,  3973,  3977,  3979,  3981,  3982,  3986,  3989,  3993,
+    3997,  4001,  4004,  4006,  4009,  4012,  4016,  4020,  4024,  4026,
+    4028,  4032,  4036,  4040,  4044,  4046,  4050,  4054,  4058,  4060,
+    4062,  4063,  4064,  4068,  4071,  4075,  4077,  4081,  4083,  4087,
+    4089,  4091,  4095,  4099,  4101,  4103,  4105,  4107,  4111,  4115,
+    4119,  4123,  4127,  4129,  4131,  4135,  4139,  4141,  4143,  4145,
+    4149,  4153,  4157,  4161,  4165,  4169,  4173,  4177,  4180,  4183,
+    4187,  4191,  4195,  4198,  4201,  4205,  4209,  4212,  4213,  4216,
+    4217,  4221,  4223,  4226,  4230,  4234,  4236,  4240,  4244,  4248,
+    4252,  4256,  4258,  4262,  4264,  4266,  4268,  4270,  4272,  4276,
+
+   /* 1101-1200 */
+    4279,  4283,  4285,  4287,  4289,  4293,  4295,  4299,  4300,  4301,
+    4305,  4309,  4313,  4317,  4319,  4323,  4325,  4329,  4331,  4333,
+    4335,  4337,  4341,  4345,  4349,  4351,  4353,  4357,  4361,  4365,
+    4367,  4369,  4373,  4377,  4381,  4383,  4387,  4389,  4391,  4395,
+    4399,  4403,  4407,  4411,  4413,  4414,  4415,  4418,  4419,  4421,
+    4423,  4427,  4429,  4431,  4433,  4435,  4437,  4439,  4443,  4446,
+    4450,  4452,  4456,  4458,  4460,  4462,  4466,  4469,  4473,  4477,
+    4481,  4483,  4487,  4489,  4491,  4493,  4497,  4499,  4501,  4504,
+    4506,  4510,  4513,  4514,  4515,  4518,  4521,  4522,  4525,  4526,
+    4527,  4530,  4533,  4534,  4537,  4541,  4542,  4543,  4544,  4545,
+
+   /* 1201-1300 */
+    4546,  4547,  4550,  4553,  4554,  4555,  4558,  4561,  4564,  4567,
+    4568,  4571,  4574,  4575,  4578,  4581,  4582,  4585,  4586,  4588,
+    4590,  4592,  4596,  4598,  4602,  4604,  4608,  4612,  4613,  4616,
+    4619,  4622,  4623,  4624,  4625,  4626,  4629,  4632,  4633,  4636,
+    4639,  4640,  4641,  4642,  4643,  4644,  4645,  4648,  4649,  4650,
+    4651,  4652,  4653,  4656,  4657,  4660,  4661,  4664,  4667,  4670,
+    4671,  4674,  4675,  4676,  4677,  4678,  4681,  4682,  4683,  4684,
+    4687,  4688,  4689,  4692,  4693,  4696,  4697,  4700,  4701,  4702,
+    4703,  4704,  4707,  4708,  4711,  4712,  4715,  4716,  4717,  4718,
+    4719,  4720,  4721,  4722,  4723,  4726,  4729,  4730,  4733,  4736,
+
+   /* 1301-(NFLS+NFPL) */
+    4737,  4740,  4741,  4742,  4745,  4746,  4749,  4752,  4753
+   };
+
+/* Amplitude coefficients (microarcsec);  indexed using the nc array. */
+   static const double a[] = {
+
+   /* 1-105 */
+         -6844318.44,     9205236.26,1328.67,1538.18,      205833.11,
+           153041.79,       -3309.73, 853.32,2037.98,       -2301.27,
+       81.46, 120.56, -20.39, -15.22,   1.73,  -1.61,  -0.10,   0.11,
+       -0.02,  -0.02,     -523908.04,      573033.42,-544.75,-458.66,
+            12814.01,       11714.49, 198.97,-290.91, 155.74,-143.27,
+       -2.75,  -1.03,  -1.27,  -1.16,   0.00,  -0.01,      -90552.22,
+            97846.69, 111.23, 137.41,2187.91,2024.68,  41.44, -51.26,
+       26.92, -24.46,  -0.46,  -0.28,  -0.22,  -0.20,       82168.76,
+           -89618.24, -27.64, -29.05,       -2004.36,       -1837.32,
+      -36.07,  48.00, -24.43,  22.41,   0.47,   0.24,   0.20,   0.18,
+            58707.02,7387.02, 470.05,-192.40, 164.33,       -1312.21,
+     -179.73, -28.93, -17.36,  -1.83,  -0.50,   3.57,   0.00,   0.13,
+           -20557.78,       22438.42, -20.84, -17.40, 501.82, 459.68,
+       59.20, -67.30,   6.08,  -5.61,  -1.36,  -1.19,       28288.28,
+     -674.99, -34.69,  35.80, -15.07,-632.54, -11.19,   0.78,  -8.41,
+        0.17,   0.01,   0.07,      -15406.85,       20069.50,  15.12,
+
+   /* 106-219 */
+       31.80, 448.76, 344.50,  -5.77,   1.41,   4.59,  -5.02,   0.17,
+        0.24,      -11991.74,       12902.66,  32.46,  36.70, 288.49,
+      268.14,   5.70,  -7.06,   3.57,  -3.23,  -0.06,  -0.04,
+            -8584.95,       -9592.72,   4.42, -13.20,-214.50, 192.06,
+       23.87,  29.83,   2.54,   2.40,   0.60,  -0.48,5095.50,
+            -6918.22,   7.19,   3.92,-154.91,-113.94,   2.86,  -1.04,
+       -1.52,   1.73,  -0.07,  -0.10,       -4910.93,       -5331.13,
+        0.76,   0.40,-119.21, 109.81,   2.16,   3.20,   1.46,   1.33,
+        0.04,  -0.02,       -6245.02,-123.48,  -6.68,  -8.20,  -2.76,
+      139.64,   2.71,   0.15,   1.86,2511.85,       -3323.89,   1.07,
+       -0.90, -74.33, -56.17,   1.16,  -0.01,  -0.75,   0.83,  -0.02,
+       -0.04,2307.58,3143.98,  -7.52,   7.50,  70.31, -51.60,   1.46,
+        0.16,  -0.69,  -0.79,   0.02,  -0.05,2372.58,2554.51,   5.93,
+       -6.60,  57.12, -53.05,  -0.96,  -1.24,  -0.71,  -0.64,  -0.01,
+            -2053.16,2636.13,   5.13,   7.80,  58.94,  45.91,  -0.42,
+       -0.12,   0.61,  -0.66,   0.02,   0.03,       -1825.49,
+
+   /* 220-339 */
+            -2423.59,   1.23,  -2.00, -54.19,  40.82,  -1.07,  -1.02,
+        0.54,   0.61,  -0.04,   0.04,2521.07,-122.28,  -5.97,   2.90,
+       -2.73, -56.37,  -0.82,   0.13,  -0.75,       -1534.09,1645.01,
+        6.29,   6.80,  36.78,  34.30,   0.92,  -1.25,   0.46,  -0.41,
+       -0.02,  -0.01,1898.27,  47.70,  -0.72,   2.50,   1.07, -42.45,
+       -0.94,   0.02,  -0.56,       -1292.02,       -1387.00,   0.00,
+        0.00, -31.01,  28.89,   0.68,   0.00,   0.38,   0.35,  -0.01,
+       -0.01,       -1234.96,1323.81,   5.21,   5.90,  29.60,  27.61,
+        0.74,  -1.22,   0.37,  -0.33,  -0.02,  -0.01,1137.48,
+            -1233.89,  -0.04,  -0.30, -27.59, -25.43,  -0.61,   1.00,
+       -0.34,   0.31,   0.01,   0.01,-813.13,       -1075.60,   0.40,
+        0.30, -24.05,  18.18,  -0.40,  -0.01,   0.24,   0.27,  -0.01,
+        0.01,1163.22, -60.90,  -2.94,   1.30,  -1.36, -26.01,  -0.58,
+        0.07,  -0.35,1029.70, -55.55,  -2.63,   1.10,  -1.25, -23.02,
+       -0.52,   0.06,  -0.31,-556.26, 852.85,   3.16,  -4.48,  19.06,
+       12.44,  -0.81,  -0.27,   0.17,  -0.21,   0.00,   0.02,-603.52,
+
+   /* 340-467 */
+     -800.34,   0.44,   0.10, -17.90,  13.49,  -0.08,  -0.01,   0.18,
+        0.20,  -0.01,   0.01,-628.24, 684.99,  -0.64,  -0.50,  15.32,
+       14.05,   3.18,  -4.19,   0.19,  -0.17,  -0.09,  -0.07,-866.48,
+      -16.26,   0.52,  -1.30,  -0.36,  19.37,   0.43,  -0.01,   0.26,
+     -512.37, 695.54,  -1.47,  -1.40,  15.55,  11.46,  -0.16,   0.03,
+        0.15,  -0.17,   0.01,   0.01, 506.65, 643.75,   2.54,  -2.62,
+       14.40, -11.33,  -0.77,  -0.06,  -0.15,  -0.16,   0.00,   0.01,
+      664.57,  16.81,  -0.40,   1.00,   0.38, -14.86,  -3.71,  -0.09,
+       -0.20, 405.91, 522.11,   0.99,  -1.50,  11.67,  -9.08,  -0.25,
+       -0.02,  -0.12,  -0.13,-305.78, 326.60,   1.75,   1.90,   7.30,
+        6.84,   0.20,  -0.04, 300.99,-325.03,  -0.44,  -0.50,  -7.27,
+       -6.73,  -1.01,   0.01,   0.00,   0.08,   0.00,   0.02, 438.51,
+       10.47,  -0.56,  -0.20,   0.24,  -9.81,  -0.24,   0.01,  -0.13,
+     -264.02, 335.24,   0.99,   1.40,   7.49,   5.90,  -0.27,  -0.02,
+      284.09, 307.03,   0.32,  -0.40,   6.87,  -6.35,  -0.99,  -0.01,
+     -250.54, 327.11,   0.08,   0.40,   7.31,   5.60,  -0.30, 230.72,
+
+   /* 468-595 */
+     -304.46,   0.08,  -0.10,  -6.81,  -5.16,   0.27, 229.78, 304.17,
+       -0.60,   0.50,   6.80,  -5.14,   0.33,   0.01, 256.30,-276.81,
+       -0.28,  -0.40,  -6.19,  -5.73,  -0.14,   0.01,-212.82, 269.45,
+        0.84,   1.20,   6.02,   4.76,   0.14,  -0.02, 196.64, 272.05,
+       -0.84,   0.90,   6.08,  -4.40,   0.35,   0.02, 188.95, 272.22,
+       -0.12,   0.30,   6.09,  -4.22,   0.34,-292.37,  -5.10,  -0.32,
+       -0.40,  -0.11,   6.54,   0.14,   0.01, 161.79,-220.67,   0.24,
+        0.10,  -4.93,  -3.62,  -0.08, 261.54, -19.94,  -0.95,   0.20,
+       -0.45,  -5.85,  -0.13,   0.02, 142.16,-190.79,   0.20,   0.10,
+       -4.27,  -3.18,  -0.07, 187.95,  -4.11,  -0.24,   0.30,  -0.09,
+       -4.20,  -0.09,   0.01,   0.00,   0.00, -79.08, 167.90,   0.04,
+        0.00,   3.75,   1.77, 121.98, 131.04,  -0.08,   0.10,   2.93,
+       -2.73,  -0.06,-172.95,  -8.11,  -0.40,  -0.20,  -0.18,   3.87,
+        0.09,   0.01,-160.15, -55.30, -14.04,  13.90,  -1.23,   3.58,
+        0.40,   0.31,-115.40, 123.20,   0.60,   0.70,   2.75,   2.58,
+        0.08,  -0.01,-168.26,  -2.00,   0.20,  -0.20,  -0.04,   3.76,
+
+   /* 596-723 */
+        0.08,-114.49, 123.20,   0.32,   0.40,   2.75,   2.56,   0.07,
+       -0.01, 112.14, 120.70,   0.28,  -0.30,   2.70,  -2.51,  -0.07,
+       -0.01, 161.34,   4.03,   0.20,   0.20,   0.09,  -3.61,  -0.08,
+       91.31, 126.64,  -0.40,   0.40,   2.83,  -2.04,  -0.04,   0.01,
+      105.29, 112.90,   0.44,  -0.50,   2.52,  -2.35,  -0.07,  -0.01,
+       98.69,-106.20,  -0.28,  -0.30,  -2.37,  -2.21,  -0.06,   0.01,
+       86.74,-112.94,  -0.08,  -0.20,  -2.53,  -1.94,  -0.05,-134.81,
+        3.51,   0.20,  -0.20,   0.08,   3.01,   0.07,  79.03, 107.31,
+       -0.24,   0.20,   2.40,  -1.77,  -0.04,   0.01, 132.81, -10.77,
+       -0.52,   0.10,  -0.24,  -2.97,  -0.07,   0.01,-130.31,  -0.90,
+        0.04,   0.00,   0.00,   2.91, -78.56,  85.32,   0.00,   0.00,
+        1.91,   1.76,   0.04,   0.00,   0.00, -41.53,  89.10,   0.02,
+        0.00,   1.99,   0.93,  66.03, -71.00,  -0.20,  -0.20,  -1.59,
+       -1.48,  -0.04,  60.50,  64.70,   0.36,  -0.40,   1.45,  -1.35,
+       -0.04,  -0.01, -52.27, -70.01,   0.00,   0.00,  -1.57,   1.17,
+        0.03, -52.95,  66.29,   0.32,   0.40,   1.48,   1.18,   0.04,
+
+   /* 724-851 */
+       -0.01,  51.02,  67.25,   0.00,   0.00,   1.50,  -1.14,  -0.03,
+      -55.66, -60.92,   0.16,  -0.20,  -1.36,   1.24,   0.03, -54.81,
+      -59.20,  -0.08,   0.20,  -1.32,   1.23,   0.03,  51.32, -55.60,
+        0.00,   0.00,  -1.24,  -1.15,  -0.03,  48.29,  51.80,   0.20,
+       -0.20,   1.16,  -1.08,  -0.03, -45.59, -49.00,  -0.12,   0.10,
+       -1.10,   1.02,   0.03,  40.54, -52.69,  -0.04,  -0.10,  -1.18,
+       -0.91,  -0.02, -40.58, -49.51,  -1.00,   1.00,  -1.11,   0.91,
+        0.04,   0.02, -43.76,  46.50,   0.36,   0.40,   1.04,   0.98,
+        0.03,  -0.01,  62.65,  -5.00,  -0.24,   0.00,  -0.11,  -1.40,
+       -0.03,   0.01, -38.57,  49.59,   0.08,   0.10,   1.11,   0.86,
+        0.02, -33.22, -44.04,   0.08,  -0.10,  -0.98,   0.74,   0.02,
+       37.15, -39.90,  -0.12,  -0.10,  -0.89,  -0.83,  -0.02,  36.68,
+      -39.50,  -0.04,  -0.10,  -0.88,  -0.82,  -0.02, -53.22,  -3.91,
+       -0.20,   0.00,  -0.09,   1.19,   0.03,  32.43, -42.19,  -0.04,
+       -0.10,  -0.94,  -0.73,  -0.02, -51.00,  -2.30,  -0.12,  -0.10,
+        0.00,   1.14, -29.53, -39.11,   0.04,   0.00,  -0.87,   0.66,
+
+   /* 852-979 */
+        0.02,  28.50, -38.92,  -0.08,  -0.10,  -0.87,  -0.64,  -0.02,
+       26.54,  36.95,  -0.12,   0.10,   0.83,  -0.59,  -0.01,  26.54,
+       34.59,   0.04,  -0.10,   0.77,  -0.59,  -0.02,  28.35, -32.55,
+       -0.16,   0.20,  -0.73,  -0.63,  -0.01, -28.00,  30.40,   0.00,
+        0.00,   0.68,   0.63,   0.01, -27.61,  29.40,   0.20,   0.20,
+        0.66,   0.62,   0.02,  40.33,   0.40,  -0.04,   0.10,   0.00,
+       -0.90, -23.28,  31.61,  -0.08,  -0.10,   0.71,   0.52,   0.01,
+       37.75,   0.80,   0.04,   0.10,   0.00,  -0.84,  23.66,  25.80,
+        0.00,   0.00,   0.58,  -0.53,  -0.01,  21.01, -27.91,   0.00,
+        0.00,  -0.62,  -0.47,  -0.01, -34.81,   2.89,   0.04,   0.00,
+        0.00,   0.78, -23.49, -25.31,   0.00,   0.00,  -0.57,   0.53,
+        0.01, -23.47,  25.20,   0.16,   0.20,   0.56,   0.52,   0.02,
+       19.58,  27.50,  -0.12,   0.10,   0.62,  -0.44,  -0.01, -22.67,
+      -24.40,  -0.08,   0.10,  -0.55,   0.51,   0.01, -19.97,  25.00,
+        0.12,   0.20,   0.56,   0.45,   0.01,  21.28, -22.80,  -0.08,
+       -0.10,  -0.51,  -0.48,  -0.01, -30.47,   0.91,   0.04,   0.00,
+
+   /* 980-1107 */
+        0.00,   0.68,  18.58,  24.00,   0.04,  -0.10,   0.54,  -0.42,
+       -0.01, -18.02,  24.40,  -0.04,  -0.10,   0.55,   0.40,   0.01,
+       17.74,  22.50,   0.08,  -0.10,   0.50,  -0.40,  -0.01, -19.41,
+       20.70,   0.08,   0.10,   0.46,   0.43,   0.01, -18.64,  20.11,
+        0.00,   0.00,   0.45,   0.42,   0.01, -16.75,  21.60,   0.04,
+        0.10,   0.48,   0.37,   0.01, -18.42, -20.00,   0.00,   0.00,
+       -0.45,   0.41,   0.01, -26.77,   1.41,   0.08,   0.00,   0.00,
+        0.60, -26.17,  -0.19,   0.00,   0.00,   0.00,   0.59, -15.52,
+       20.51,   0.00,   0.00,   0.46,   0.35,   0.01, -25.42,  -1.91,
+       -0.08,   0.00,  -0.04,   0.57,   0.45, -17.42,  18.10,   0.00,
+        0.00,   0.40,   0.39,   0.01,  16.39, -17.60,  -0.08,  -0.10,
+       -0.39,  -0.37,  -0.01, -14.37,  18.91,   0.00,   0.00,   0.42,
+        0.32,   0.01,  23.39,  -2.40,  -0.12,   0.00,   0.00,  -0.52,
+       14.32, -18.50,  -0.04,  -0.10,  -0.41,  -0.32,  -0.01,  15.69,
+       17.08,   0.00,   0.00,   0.38,  -0.35,  -0.01, -22.99,   0.50,
+        0.04,   0.00,   0.00,   0.51,   0.00,   0.00,  14.47, -17.60,
+
+   /* 1108-1235 */
+       -0.01,   0.00,  -0.39,  -0.32, -13.33,  18.40,  -0.04,  -0.10,
+        0.41,   0.30,  22.47,  -0.60,  -0.04,   0.00,   0.00,  -0.50,
+      -12.78, -17.41,   0.04,   0.00,  -0.39,   0.29,   0.01, -14.10,
+      -15.31,   0.04,   0.00,  -0.34,   0.32,   0.01,  11.98,  16.21,
+       -0.04,   0.00,   0.36,  -0.27,  -0.01,  19.65,  -1.90,  -0.08,
+        0.00,   0.00,  -0.44,  19.61,  -1.50,  -0.08,   0.00,   0.00,
+       -0.44,  13.41, -14.30,  -0.04,  -0.10,  -0.32,  -0.30,  -0.01,
+      -13.29,  14.40,   0.00,   0.00,   0.32,   0.30,   0.01,  11.14,
+      -14.40,  -0.04,   0.00,  -0.32,  -0.25,  -0.01,  12.24, -13.38,
+        0.04,   0.00,  -0.30,  -0.27,  -0.01,  10.07, -13.81,   0.04,
+        0.00,  -0.31,  -0.23,  -0.01,  10.46,  13.10,   0.08,  -0.10,
+        0.29,  -0.23,  -0.01,  16.55,  -1.71,  -0.08,   0.00,   0.00,
+       -0.37,   9.75, -12.80,   0.00,   0.00,  -0.29,  -0.22,  -0.01,
+        9.11,  12.80,   0.00,   0.00,   0.29,  -0.20,   0.00,   0.00,
+       -6.44, -13.80,   0.00,   0.00,  -0.31,   0.14,  -9.19, -12.00,
+        0.00,   0.00,  -0.27,   0.21, -10.30,  10.90,   0.08,   0.10,
+
+   /* 1236-1363 */
+        0.24,   0.23,   0.01,  14.92,  -0.80,  -0.04,   0.00,   0.00,
+       -0.33,  10.02, -10.80,   0.00,   0.00,  -0.24,  -0.22,  -0.01,
+       -9.75,  10.40,   0.04,   0.00,   0.23,   0.22,   0.01,   9.67,
+      -10.40,  -0.04,   0.00,  -0.23,  -0.22,  -0.01,  -8.28, -11.20,
+        0.04,   0.00,  -0.25,   0.19,  13.32,  -1.41,  -0.08,   0.00,
+        0.00,  -0.30,   8.27,  10.50,   0.04,   0.00,   0.23,  -0.19,
+        0.00,   0.00,  13.13,   0.00,   0.00,   0.00,   0.00,  -0.29,
+      -12.93,   0.70,   0.04,   0.00,   0.00,   0.29,   7.91, -10.20,
+        0.00,   0.00,  -0.23,  -0.18,  -7.84, -10.00,  -0.04,   0.00,
+       -0.22,   0.18,   7.44,   9.60,   0.00,   0.00,   0.21,  -0.17,
+       -7.64,   9.40,   0.08,   0.10,   0.21,   0.17,   0.01, -11.38,
+        0.60,   0.04,   0.00,   0.00,   0.25,  -7.48,   8.30,   0.00,
+        0.00,   0.19,   0.17, -10.98,  -0.20,   0.00,   0.00,   0.00,
+        0.25,  10.98,   0.20,   0.00,   0.00,   0.00,  -0.25,   7.40,
+       -7.90,  -0.04,   0.00,  -0.18,  -0.17,  -6.09,   8.40,  -0.04,
+        0.00,   0.19,   0.14,  -6.94,  -7.49,   0.00,   0.00,  -0.17,
+
+   /* 1364-1491 */
+        0.16,   6.92,   7.50,   0.04,   0.00,   0.17,  -0.15,   6.20,
+        8.09,   0.00,   0.00,   0.18,  -0.14,  -6.12,   7.80,   0.04,
+        0.00,   0.17,   0.14,   5.85,  -7.50,   0.00,   0.00,  -0.17,
+       -0.13,  -6.48,   6.90,   0.08,   0.10,   0.15,   0.14,   0.01,
+        6.32,   6.90,   0.00,   0.00,   0.15,  -0.14,   5.61,  -7.20,
+        0.00,   0.00,  -0.16,  -0.13,   9.07,   0.00,   0.00,   0.00,
+        0.00,  -0.20,   5.25,   6.90,   0.00,   0.00,   0.15,  -0.12,
+       -8.47,  -0.40,   0.00,   0.00,   0.00,   0.19,   6.32,  -5.39,
+       -1.11,   1.10,  -0.12,  -0.14,   0.02,   0.02,   5.73,  -6.10,
+       -0.04,   0.00,  -0.14,  -0.13,   4.70,   6.60,  -0.04,   0.00,
+        0.15,  -0.11,  -4.90,  -6.40,   0.00,   0.00,  -0.14,   0.11,
+       -5.33,   5.60,   0.04,   0.10,   0.13,   0.12,   0.01,  -4.81,
+        6.00,   0.04,   0.00,   0.13,   0.11,   5.13,   5.50,   0.04,
+        0.00,   0.12,  -0.11,   4.50,   5.90,   0.00,   0.00,   0.13,
+       -0.10,  -4.22,   6.10,   0.00,   0.00,   0.14,  -4.53,   5.70,
+        0.00,   0.00,   0.13,   0.10,   4.18,   5.70,   0.00,   0.00,
+
+   /* 1492-1619 */
+        0.13,  -4.75,  -5.19,   0.00,   0.00,  -0.12,   0.11,  -4.06,
+        5.60,   0.00,   0.00,   0.13,  -3.98,   5.60,  -0.04,   0.00,
+        0.13,   4.02,  -5.40,   0.00,   0.00,  -0.12,   4.49,  -4.90,
+       -0.04,   0.00,  -0.11,  -0.10,  -3.62,  -5.40,  -0.16,   0.20,
+       -0.12,   0.00,   0.01,   4.38,   4.80,   0.00,   0.00,   0.11,
+       -6.40,  -0.10,   0.00,   0.00,   0.00,   0.14,  -3.98,   5.00,
+        0.04,   0.00,   0.11,  -3.82,  -5.00,   0.00,   0.00,  -0.11,
+       -3.71,   5.07,   0.00,   0.00,   0.11,   4.14,   4.40,   0.00,
+        0.00,   0.10,  -6.01,  -0.50,  -0.04,   0.00,   0.00,   0.13,
+       -4.04,   4.39,   0.00,   0.00,   0.10,   3.45,  -4.72,   0.00,
+        0.00,  -0.11,   3.31,   4.71,   0.00,   0.00,   0.11,   3.26,
+       -4.50,   0.00,   0.00,  -0.10,  -3.26,  -4.50,   0.00,   0.00,
+       -0.10,  -3.34,  -4.40,   0.00,   0.00,  -0.10,  -3.74,  -4.00,
+        3.70,   4.00,   3.34,  -4.30,   3.30,  -4.30,  -3.66,   3.90,
+        0.04,   3.66,   3.90,   0.04,  -3.62,  -3.90,  -3.61,   3.90,
+       -0.20,   5.30,   0.00,   0.00,   0.12,   3.06,   4.30,   3.30,
+
+   /* 1620-1747 */
+        4.00,   0.40,   0.20,   3.10,   4.10,  -3.06,   3.90,  -3.30,
+       -3.60,  -3.30,   3.36,   0.01,   3.14,   3.40,  -4.57,  -0.20,
+        0.00,   0.00,   0.00,   0.10,  -2.70,  -3.60,   2.94,  -3.20,
+       -2.90,   3.20,   2.47,  -3.40,   2.55,  -3.30,   2.80,  -3.08,
+        2.51,   3.30,  -4.10,   0.30,  -0.12,  -0.10,   4.10,   0.20,
+       -2.74,   3.00,   2.46,   3.23,  -3.66,   1.20,  -0.20,   0.20,
+        3.74,  -0.40,  -2.51,  -2.80,  -3.74,   2.27,  -2.90,   0.00,
+        0.00,  -2.50,   2.70,  -2.51,   2.60,  -3.50,   0.20,   3.38,
+       -2.22,  -2.50,   3.26,  -0.40,   1.95,  -2.60,   3.22,  -0.40,
+       -0.04,  -1.79,  -2.60,   1.91,   2.50,   0.74,   3.05,  -0.04,
+        0.08,   2.11,  -2.30,  -2.11,   2.20,  -1.87,  -2.40,   2.03,
+       -2.20,  -2.03,   2.20,   2.98,   0.00,   0.00,   2.98,  -1.71,
+        2.40,   2.94,  -0.10,  -0.12,   0.10,   1.67,   2.40,  -1.79,
+        2.30,  -1.79,   2.20,  -1.67,   2.20,   1.79,  -2.00,   1.87,
+       -1.90,   1.63,  -2.10,  -1.59,   2.10,   1.55,  -2.10,  -1.55,
+        2.10,  -2.59,  -0.20,  -1.75,  -1.90,  -1.75,   1.90,  -1.83,
+
+   /* 1748-1875 */
+       -1.80,   1.51,   2.00,  -1.51,  -2.00,   1.71,   1.80,   1.31,
+        2.10,  -1.43,   2.00,   1.43,   2.00,  -2.43,  -1.51,   1.90,
+       -1.47,   1.90,   2.39,   0.20,  -2.39,   1.39,   1.90,   1.39,
+       -1.80,   1.47,  -1.60,   1.47,  -1.60,   1.43,  -1.50,  -1.31,
+        1.60,   1.27,  -1.60,  -1.27,   1.60,   1.27,  -1.60,   2.03,
+        1.35,   1.50,  -1.39,  -1.40,   1.95,  -0.20,  -1.27,   1.49,
+        1.19,   1.50,   1.27,   1.40,   1.15,   1.50,   1.87,  -0.10,
+       -1.12,  -1.50,   1.87,  -1.11,  -1.50,  -1.11,  -1.50,   0.00,
+        0.00,   1.19,   1.40,   1.27,  -1.30,  -1.27,  -1.30,  -1.15,
+        1.40,  -1.23,   1.30,  -1.23,  -1.30,   1.22,  -1.29,   1.07,
+       -1.40,   1.75,  -0.20,  -1.03,  -1.40,  -1.07,   1.20,  -1.03,
+        1.15,   1.07,   1.10,   1.51,  -1.03,   1.10,   1.03,  -1.10,
+        0.00,   0.00,  -1.03,  -1.10,   0.91,  -1.20,  -0.88,  -1.20,
+       -0.88,   1.20,  -0.95,   1.10,  -0.95,  -1.10,   1.43,  -1.39,
+        0.95,  -1.00,  -0.95,   1.00,  -0.80,   1.10,   0.91,  -1.00,
+       -1.35,   0.88,   1.00,  -0.83,   1.00,  -0.91,   0.90,   0.91,
+
+   /* 1876-2003 */
+        0.90,   0.88,  -0.90,  -0.76,  -1.00,  -0.76,   1.00,   0.76,
+        1.00,  -0.72,   1.00,   0.84,  -0.90,   0.84,   0.90,   1.23,
+        0.00,   0.00,  -0.52,  -1.10,  -0.68,   1.00,   1.19,  -0.20,
+        1.19,   0.76,   0.90,   1.15,  -0.10,   1.15,  -0.10,   0.72,
+       -0.90,  -1.15,  -1.15,   0.68,   0.90,  -0.68,   0.90,  -1.11,
+        0.00,   0.00,   0.20,   0.79,   0.80,  -1.11,  -0.10,   0.00,
+        0.00,  -0.48,  -1.00,  -0.76,  -0.80,  -0.72,  -0.80,  -1.07,
+       -0.10,   0.64,   0.80,  -0.64,  -0.80,   0.64,   0.80,   0.40,
+        0.60,   0.52,  -0.50,  -0.60,  -0.80,  -0.71,   0.70,  -0.99,
+        0.99,   0.56,   0.80,  -0.56,   0.80,   0.68,  -0.70,   0.68,
+        0.70,  -0.95,  -0.64,   0.70,   0.64,   0.70,  -0.60,   0.70,
+       -0.60,  -0.70,  -0.91,  -0.10,  -0.51,   0.76,  -0.91,  -0.56,
+        0.70,   0.88,   0.88,  -0.63,  -0.60,   0.55,  -0.60,  -0.80,
+        0.80,  -0.80,  -0.52,   0.60,   0.52,   0.60,   0.52,  -0.60,
+       -0.48,   0.60,   0.48,   0.60,   0.48,   0.60,  -0.76,   0.44,
+       -0.60,   0.52,  -0.50,  -0.52,   0.50,   0.40,   0.60,  -0.40,
+
+   /* 2004-2131 */
+       -0.60,   0.40,  -0.60,   0.72,  -0.72,  -0.51,  -0.50,  -0.48,
+        0.50,   0.48,  -0.50,  -0.48,   0.50,  -0.48,   0.50,   0.48,
+       -0.50,  -0.48,  -0.50,  -0.68,  -0.68,   0.44,   0.50,  -0.64,
+       -0.10,  -0.64,  -0.10,  -0.40,   0.50,   0.40,   0.50,   0.40,
+        0.50,   0.00,   0.00,  -0.40,  -0.50,  -0.36,  -0.50,   0.36,
+       -0.50,   0.60,  -0.60,   0.40,  -0.40,   0.40,   0.40,  -0.40,
+        0.40,  -0.40,   0.40,  -0.56,  -0.56,   0.36,  -0.40,  -0.36,
+        0.40,   0.36,  -0.40,  -0.36,  -0.40,   0.36,   0.40,   0.36,
+        0.40,  -0.52,   0.52,   0.52,   0.32,   0.40,  -0.32,   0.40,
+       -0.32,   0.40,  -0.32,   0.40,   0.32,  -0.40,  -0.32,  -0.40,
+        0.32,  -0.40,   0.28,  -0.40,  -0.28,   0.40,   0.28,  -0.40,
+        0.28,   0.40,   0.48,  -0.48,   0.48,   0.36,  -0.30,  -0.36,
+       -0.30,   0.00,   0.00,   0.20,   0.40,  -0.44,   0.44,  -0.44,
+       -0.44,  -0.44,  -0.44,   0.32,  -0.30,   0.32,   0.30,   0.24,
+        0.30,  -0.12,  -0.10,  -0.28,   0.30,   0.28,   0.30,   0.28,
+        0.30,   0.28,  -0.30,   0.28,  -0.30,   0.28,  -0.30,   0.28,
+
+   /* 2132-2259 */
+        0.30,  -0.28,   0.30,   0.40,   0.40,  -0.24,   0.30,   0.24,
+       -0.30,   0.24,  -0.30,  -0.24,  -0.30,   0.24,   0.30,   0.24,
+       -0.30,  -0.24,   0.30,   0.24,  -0.30,  -0.24,  -0.30,   0.24,
+       -0.30,   0.24,   0.30,  -0.24,   0.30,  -0.24,   0.30,   0.20,
+       -0.30,   0.20,  -0.30,   0.20,  -0.30,   0.20,   0.30,   0.20,
+       -0.30,   0.20,  -0.30,   0.20,   0.30,   0.20,   0.30,  -0.20,
+       -0.30,   0.20,  -0.30,   0.20,  -0.30,  -0.36,  -0.36,  -0.36,
+       -0.04,   0.30,   0.12,  -0.10,  -0.32,  -0.24,   0.20,   0.24,
+        0.20,   0.20,  -0.20,  -0.20,  -0.20,  -0.20,  -0.20,   0.20,
+        0.20,   0.20,  -0.20,   0.20,   0.20,   0.20,   0.20,  -0.20,
+       -0.20,   0.00,   0.00,  -0.20,  -0.20,  -0.20,   0.20,  -0.20,
+        0.20,   0.20,  -0.20,  -0.20,  -0.20,   0.20,   0.20,   0.20,
+        0.20,   0.20,  -0.20,   0.20,  -0.20,   0.28,   0.28,   0.28,
+        0.28,   0.28,   0.28,  -0.28,   0.28,   0.12,   0.00,   0.24,
+        0.16,  -0.20,   0.16,  -0.20,   0.16,  -0.20,   0.16,   0.20,
+       -0.16,   0.20,   0.16,   0.20,  -0.16,   0.20,  -0.16,   0.20,
+
+   /* 2260-2387 */
+       -0.16,   0.20,   0.16,  -0.20,   0.16,   0.20,   0.16,  -0.20,
+       -0.16,   0.20,  -0.16,  -0.20,  -0.16,   0.20,   0.16,   0.20,
+        0.16,  -0.20,   0.16,  -0.20,   0.16,   0.20,   0.16,   0.20,
+        0.16,   0.20,  -0.16,  -0.20,   0.16,   0.20,  -0.16,   0.20,
+        0.16,   0.20,  -0.16,  -0.20,   0.16,  -0.20,   0.16,  -0.20,
+       -0.16,  -0.20,   0.24,  -0.24,  -0.24,   0.24,   0.24,   0.12,
+        0.20,   0.12,   0.20,  -0.12,  -0.20,   0.12,  -0.20,   0.12,
+       -0.20,  -0.12,   0.20,  -0.12,   0.20,  -0.12,  -0.20,   0.12,
+        0.20,   0.12,   0.20,   0.12,  -0.20,  -0.12,   0.20,   0.12,
+       -0.20,  -0.12,   0.20,   0.12,   0.20,   0.00,   0.00,  -0.12,
+        0.20,  -0.12,   0.20,   0.12,  -0.20,  -0.12,   0.20,   0.12,
+        0.20,   0.00,  -0.21,  -0.20,   0.00,   0.00,   0.20,  -0.20,
+       -0.20,  -0.20,   0.20,  -0.16,  -0.10,   0.00,   0.17,   0.16,
+        0.16,   0.16,   0.16,  -0.16,   0.16,   0.16,  -0.16,   0.16,
+       -0.16,   0.16,   0.12,   0.10,   0.12,  -0.10,  -0.12,   0.10,
+       -0.12,   0.10,   0.12,  -0.10,  -0.12,   0.12,  -0.12,   0.12,
+
+   /* 2388-2515 */
+       -0.12,   0.12,  -0.12,  -0.12,  -0.12,  -0.12,  -0.12,  -0.12,
+       -0.12,   0.12,   0.12,   0.12,   0.12,  -0.12,  -0.12,   0.12,
+        0.12,   0.12,  -0.12,   0.12,  -0.12,  -0.12,  -0.12,   0.12,
+       -0.12,  -0.12,   0.12,   0.00,   0.11,   0.11,-122.67, 164.70,
+      203.78, 273.50,   3.58,   2.74,   6.18,  -4.56,   0.00,  -0.04,
+        0.00,  -0.07,  57.44, -77.10,  95.82, 128.60,  -1.77,  -1.28,
+        2.85,  -2.14,  82.14,  89.50,   0.00,   0.00,   2.00,  -1.84,
+       -0.04,  47.73, -64.10,  23.79,  31.90,  -1.45,  -1.07,   0.69,
+       -0.53, -46.38,  50.50,   0.00,   0.00,   1.13,   1.04,   0.02,
+      -18.38,   0.00,  63.80,   0.00,   0.00,   0.41,   0.00,  -1.43,
+       59.07,   0.00,   0.00,   0.00,   0.00,  -1.32,  57.28,   0.00,
+        0.00,   0.00,   0.00,  -1.28, -48.65,   0.00,  -1.15,   0.00,
+        0.00,   1.09,   0.00,   0.03, -18.30,  24.60, -17.30, -23.20,
+        0.56,   0.41,  -0.51,   0.39, -16.91,  26.90,   8.43,  13.30,
+        0.60,   0.38,   0.31,  -0.19,   1.23,  -1.70, -19.13, -25.70,
+       -0.03,  -0.03,  -0.58,   0.43,  -0.72,   0.90, -17.34, -23.30,
+
+   /* 2516-2643 */
+        0.03,   0.02,  -0.52,   0.39, -19.49, -21.30,   0.00,   0.00,
+       -0.48,   0.44,   0.01,  20.57, -20.10,   0.64,   0.70,  -0.45,
+       -0.46,   0.00,  -0.01,   4.89,   5.90, -16.55,  19.90,   0.14,
+       -0.11,   0.44,   0.37,  18.22,  19.80,   0.00,   0.00,   0.44,
+       -0.41,  -0.01,   4.89,  -5.30, -16.51, -18.00,  -0.11,  -0.11,
+       -0.41,   0.37, -17.86,   0.00,  17.10,   0.00,   0.00,   0.40,
+        0.00,  -0.38,   0.32,   0.00,  24.42,   0.00,   0.00,  -0.01,
+        0.00,  -0.55, -23.79,   0.00,   0.00,   0.00,   0.00,   0.53,
+       14.72, -16.00,  -0.32,   0.00,  -0.36,  -0.33,  -0.01,   0.01,
+        3.34,  -4.50,  11.86,  15.90,  -0.11,  -0.07,   0.35,  -0.27,
+       -3.26,   4.40,  11.62,  15.60,   0.09,   0.07,   0.35,  -0.26,
+      -19.53,   0.00,   5.09,   0.00,   0.00,   0.44,   0.00,  -0.11,
+      -13.48,  14.70,   0.00,   0.00,   0.33,   0.30,   0.01,  10.86,
+      -14.60,   3.18,   4.30,  -0.33,  -0.24,   0.09,  -0.07, -11.30,
+      -15.10,   0.00,   0.00,  -0.34,   0.25,   0.01,   2.03,  -2.70,
+       10.82,  14.50,  -0.07,  -0.05,   0.32,  -0.24,  17.46,   0.00,
+
+   /* 2644-2771 */
+        0.00,   0.00,   0.00,  -0.39,  16.43,   0.00,   0.52,   0.00,
+        0.00,  -0.37,   0.00,  -0.01,   9.35,   0.00,  13.29,   0.00,
+        0.00,  -0.21,   0.00,  -0.30, -10.42,  11.40,   0.00,   0.00,
+        0.25,   0.23,   0.01,   0.44,   0.50, -10.38,  11.30,   0.02,
+       -0.01,   0.25,   0.23, -14.64,   0.00,   0.00,   0.00,   0.00,
+        0.33,   0.56,   0.80,  -8.67,  11.70,   0.02,  -0.01,   0.26,
+        0.19,  13.88,   0.00,  -2.47,   0.00,   0.00,  -0.31,   0.00,
+        0.06,  -1.99,   2.70,   7.72,  10.30,   0.06,   0.04,   0.23,
+       -0.17,  -0.20,   0.00,  13.05,   0.00,   0.00,   0.00,   0.00,
+       -0.29,   6.92,  -9.30,   3.34,   4.50,  -0.21,  -0.15,   0.10,
+       -0.07,  -6.60,   0.00,  10.70,   0.00,   0.00,   0.15,   0.00,
+       -0.24,  -8.04,  -8.70,   0.00,   0.00,  -0.19,   0.18, -10.58,
+        0.00,  -3.10,   0.00,   0.00,   0.24,   0.00,   0.07,  -7.32,
+        8.00,  -0.12,  -0.10,   0.18,   0.16,   1.63,   1.70,   6.96,
+       -7.60,   0.03,  -0.04,  -0.17,  -0.16,  -3.62,   0.00,   9.86,
+        0.00,   0.00,   0.08,   0.00,  -0.22,   0.20,  -0.20,  -6.88,
+
+   /* 2772-2899 */
+       -7.50,   0.00,   0.00,  -0.17,   0.15,  -8.99,   0.00,   4.02,
+        0.00,   0.00,   0.20,   0.00,  -0.09,  -1.07,   1.40,  -5.69,
+       -7.70,   0.03,   0.02,  -0.17,   0.13,   6.48,  -7.20,  -0.48,
+       -0.50,  -0.16,  -0.14,  -0.01,   0.01,   5.57,  -7.50,   1.07,
+        1.40,  -0.17,  -0.12,   0.03,  -0.02,   8.71,   0.00,   3.54,
+        0.00,   0.00,  -0.19,   0.00,  -0.08,   0.40,   0.00,   9.27,
+        0.00,   0.00,  -0.01,   0.00,  -0.21,  -6.13,   6.70,  -1.19,
+       -1.30,   0.15,   0.14,  -0.03,   0.03,   5.21,  -5.70,  -2.51,
+       -2.60,  -0.13,  -0.12,  -0.06,   0.06,   5.69,  -6.20,  -0.12,
+       -0.10,  -0.14,  -0.13,  -0.01,   2.03,  -2.70,   4.53,   6.10,
+       -0.06,  -0.05,   0.14,  -0.10,   5.01,   5.50,  -2.51,   2.70,
+        0.12,  -0.11,   0.06,   0.06,  -1.91,   2.60,  -4.38,  -5.90,
+        0.06,   0.04,  -0.13,   0.10,   4.65,  -6.30,   0.00,   0.00,
+       -0.14,  -0.10,  -5.29,   5.70,   0.00,   0.00,   0.13,   0.12,
+       -2.23,  -4.00,  -4.65,   4.20,  -0.09,   0.05,   0.10,   0.10,
+       -4.53,   6.10,   0.00,   0.00,   0.14,   0.10,   2.47,   2.70,
+
+   /* 2900-3027 */
+       -4.46,   4.90,   0.06,  -0.06,   0.11,   0.10,  -5.05,   5.50,
+        0.84,   0.90,   0.12,   0.11,   0.02,  -0.02,   4.97,  -5.40,
+       -1.71,   0.00,  -0.12,  -0.11,   0.00,   0.04,  -0.99,  -1.30,
+        4.22,  -5.70,  -0.03,   0.02,  -0.13,  -0.09,   0.99,   1.40,
+        4.22,  -5.60,   0.03,  -0.02,  -0.13,  -0.09,  -4.69,  -5.20,
+        0.00,   0.00,  -0.12,   0.10,  -3.42,   0.00,   6.09,   0.00,
+        0.00,   0.08,   0.00,  -0.14,  -4.65,  -5.10,   0.00,   0.00,
+       -0.11,   0.10,   0.00,   0.00,  -4.53,  -5.00,   0.00,   0.00,
+       -0.11,   0.10,  -2.43,  -2.70,  -3.82,   4.20,  -0.06,   0.05,
+        0.10,   0.09,   0.00,   0.00,  -4.53,   4.90,   0.00,   0.00,
+        0.11,   0.10,  -4.49,  -4.90,   0.00,   0.00,  -0.11,   0.10,
+        2.67,  -2.90,  -3.62,  -3.90,  -0.06,  -0.06,  -0.09,   0.08,
+        3.94,  -5.30,   0.00,   0.00,  -0.12,  -3.38,   3.70,  -2.78,
+       -3.10,   0.08,   0.08,  -0.07,   0.06,   3.18,  -3.50,  -2.82,
+       -3.10,  -0.08,  -0.07,  -0.07,   0.06,  -5.77,   0.00,   1.87,
+        0.00,   0.00,   0.13,   0.00,  -0.04,   3.54,  -4.80,  -0.64,
+
+   /* 3028-3155 */
+       -0.90,  -0.11,   0.00,  -0.02,  -3.50,  -4.70,   0.68,  -0.90,
+       -0.11,   0.00,  -0.02,   5.49,   0.00,   0.00,   0.00,   0.00,
+       -0.12,   1.83,  -2.50,   2.63,   3.50,  -0.06,   0.00,   0.08,
+        3.02,  -4.10,   0.68,   0.90,  -0.09,   0.00,   0.02,   0.00,
+        0.00,   5.21,   0.00,   0.00,   0.00,   0.00,  -0.12,  -3.54,
+        3.80,   2.70,   3.60,  -1.35,   1.80,   0.08,   0.00,   0.04,
+       -2.90,   3.90,   0.68,   0.90,   0.09,   0.00,   0.02,   0.80,
+       -1.10,  -2.78,  -3.70,  -0.02,   0.00,  -0.08,   4.10,   0.00,
+       -2.39,   0.00,   0.00,  -0.09,   0.00,   0.05,  -1.59,   2.10,
+        2.27,   3.00,   0.05,   0.00,   0.07,  -2.63,   3.50,  -0.48,
+       -0.60,  -2.94,  -3.20,  -2.94,   3.20,   2.27,  -3.00,  -1.11,
+       -1.50,  -0.07,   0.00,  -0.03,  -0.56,  -0.80,  -2.35,   3.10,
+        0.00,  -0.60,  -3.42,   1.90,  -0.12,  -0.10,   2.63,  -2.90,
+        2.51,   2.80,  -0.64,   0.70,  -0.48,  -0.60,   2.19,  -2.90,
+        0.24,  -0.30,   2.15,   2.90,   2.15,  -2.90,   0.52,   0.70,
+        2.07,  -2.80,  -3.10,   0.00,   1.79,   0.00,   0.00,   0.07,
+
+   /* 3156-3283 */
+        0.00,  -0.04,   0.88,   0.00,  -3.46,   2.11,   2.80,  -0.36,
+        0.50,   3.54,  -0.20,  -3.50,  -1.39,   1.50,  -1.91,  -2.10,
+       -1.47,   2.00,   1.39,   1.90,   2.07,  -2.30,   0.91,   1.00,
+        1.99,  -2.70,   3.30,   0.00,   0.60,  -0.44,  -0.70,  -1.95,
+        2.60,   2.15,  -2.40,  -0.60,  -0.70,   3.30,   0.84,   0.00,
+       -3.10,  -3.10,   0.00,  -0.72,  -0.32,   0.40,  -1.87,  -2.50,
+        1.87,  -2.50,   0.32,   0.40,  -0.24,   0.30,  -1.87,  -2.50,
+       -0.24,  -0.30,   1.87,  -2.50,  -2.70,   0.00,   1.55,   2.03,
+        2.20,  -2.98,  -1.99,  -2.20,   0.12,  -0.10,  -0.40,   0.50,
+        1.59,   2.10,   0.00,   0.00,  -1.79,   2.00,  -1.03,   1.40,
+       -1.15,  -1.60,   0.32,   0.50,   1.39,  -1.90,   2.35,  -1.27,
+        1.70,   0.60,   0.80,  -0.32,  -0.40,   1.35,  -1.80,   0.44,
+        0.00,   2.23,  -0.84,   0.90,  -1.27,  -1.40,  -1.47,   1.60,
+       -0.28,  -0.30,  -0.28,   0.40,  -1.27,  -1.70,   0.28,  -0.40,
+       -1.43,  -1.50,   0.00,   0.00,  -1.27,  -1.70,   2.11,  -0.32,
+       -0.40,  -1.23,   1.60,   1.19,  -1.30,  -0.72,  -0.80,   0.72,
+
+   /* 3284-3411 */
+       -0.80,  -1.15,  -1.30,  -1.35,  -1.50,  -1.19,  -1.60,  -0.12,
+        0.20,   1.79,   0.00,  -0.88,  -0.28,   0.40,   1.11,   1.50,
+       -1.83,   0.00,   0.56,  -0.12,   0.10,  -1.27,  -1.40,   0.00,
+        0.00,   1.15,   1.50,  -0.12,   0.20,   1.11,   1.50,   0.36,
+       -0.50,  -1.07,  -1.40,  -1.11,   1.50,   1.67,   0.00,   0.80,
+       -1.11,   0.00,   1.43,   1.23,  -1.30,  -0.24,  -1.19,  -1.30,
+       -0.24,   0.20,  -0.44,  -0.90,  -0.95,   1.10,   1.07,  -1.40,
+        1.15,  -1.30,   1.03,  -1.10,  -0.56,  -0.60,  -0.68,   0.90,
+       -0.76,  -1.00,  -0.24,  -0.30,   0.95,  -1.30,   0.56,   0.70,
+        0.84,  -1.10,  -0.56,   0.00,  -1.55,   0.91,  -1.30,   0.28,
+        0.30,   0.16,  -0.20,   0.95,   1.30,   0.40,  -0.50,  -0.88,
+       -1.20,   0.95,  -1.10,  -0.48,  -0.50,   0.00,   0.00,  -1.07,
+        1.20,   0.44,  -0.50,   0.95,   1.10,   0.00,   0.00,   0.92,
+       -1.30,   0.95,   1.00,  -0.52,   0.60,   1.59,   0.24,  -0.40,
+        0.91,   1.20,   0.84,  -1.10,  -0.44,  -0.60,   0.84,   1.10,
+       -0.44,   0.60,  -0.44,   0.60,  -0.84,  -1.10,  -0.80,   0.00,
+
+   /* 3412-3539 */
+        1.35,   0.76,   0.20,  -0.91,  -1.00,   0.20,  -0.30,  -0.91,
+       -1.20,  -0.95,   1.00,  -0.48,  -0.50,   0.88,   1.00,   0.48,
+       -0.50,  -0.95,  -1.10,   0.20,  -0.20,  -0.99,   1.10,  -0.84,
+        1.10,  -0.24,  -0.30,   0.20,  -0.30,   0.84,   1.10,  -1.39,
+        0.00,  -0.28,  -0.16,   0.20,   0.84,   1.10,   0.00,   0.00,
+        1.39,   0.00,   0.00,  -0.95,   1.00,   1.35,  -0.99,   0.00,
+        0.88,  -0.52,   0.00,  -1.19,   0.20,   0.20,   0.76,  -1.00,
+        0.00,   0.00,   0.76,   1.00,   0.00,   0.00,   0.76,   1.00,
+       -0.76,   1.00,   0.00,   0.00,   1.23,   0.76,   0.80,  -0.32,
+        0.40,  -0.72,   0.80,  -0.40,  -0.40,   0.00,   0.00,  -0.80,
+       -0.90,  -0.68,   0.90,  -0.16,  -0.20,  -0.16,  -0.20,   0.68,
+       -0.90,  -0.36,   0.50,  -0.56,  -0.80,   0.72,  -0.90,   0.44,
+       -0.60,  -0.48,  -0.70,  -0.16,   0.00,  -1.11,   0.32,   0.00,
+       -1.07,   0.60,  -0.80,  -0.28,  -0.40,  -0.64,   0.00,   0.91,
+        1.11,   0.64,  -0.90,   0.76,  -0.80,   0.00,   0.00,  -0.76,
+       -0.80,   1.03,   0.00,  -0.36,  -0.64,  -0.70,   0.36,  -0.40,
+
+   /* 3540-3667 */
+        1.07,   0.36,  -0.50,  -0.52,  -0.70,   0.60,   0.00,   0.88,
+        0.95,   0.00,   0.48,   0.16,  -0.20,   0.60,   0.80,   0.16,
+       -0.20,  -0.60,  -0.80,   0.00,  -1.00,   0.12,   0.20,   0.16,
+       -0.20,   0.68,   0.70,   0.59,  -0.80,  -0.99,  -0.56,  -0.60,
+        0.36,  -0.40,  -0.68,  -0.70,  -0.68,  -0.70,  -0.36,  -0.50,
+       -0.44,   0.60,   0.64,   0.70,  -0.12,   0.10,  -0.52,   0.60,
+        0.36,   0.40,   0.00,   0.00,   0.95,  -0.84,   0.00,   0.44,
+        0.56,   0.60,   0.32,  -0.30,   0.00,   0.00,   0.60,   0.70,
+        0.00,   0.00,   0.60,   0.70,  -0.12,  -0.20,   0.52,  -0.70,
+        0.00,   0.00,   0.56,   0.70,  -0.12,   0.10,  -0.52,  -0.70,
+        0.00,   0.00,   0.88,  -0.76,   0.00,  -0.44,   0.00,   0.00,
+       -0.52,  -0.70,   0.52,  -0.70,   0.36,  -0.40,  -0.44,  -0.50,
+        0.00,   0.00,   0.60,   0.60,   0.84,   0.00,   0.12,  -0.24,
+        0.00,   0.80,  -0.56,   0.60,  -0.32,  -0.30,   0.48,  -0.50,
+        0.28,  -0.30,  -0.48,  -0.50,   0.12,   0.20,   0.48,  -0.60,
+        0.48,   0.60,  -0.12,   0.20,   0.24,   0.00,   0.76,  -0.52,
+
+   /* 3668-3795 */
+       -0.60,  -0.52,   0.60,   0.48,  -0.50,  -0.24,  -0.30,   0.12,
+       -0.10,   0.48,   0.60,   0.52,  -0.20,   0.36,   0.40,  -0.44,
+        0.50,  -0.24,  -0.30,  -0.48,  -0.60,  -0.44,  -0.60,  -0.12,
+        0.10,   0.76,   0.76,   0.20,  -0.20,   0.48,   0.50,   0.40,
+       -0.50,  -0.24,  -0.30,   0.44,  -0.60,   0.44,  -0.60,   0.36,
+        0.00,  -0.64,   0.72,   0.00,  -0.12,   0.00,  -0.10,  -0.40,
+       -0.60,  -0.20,  -0.20,  -0.44,   0.50,  -0.44,   0.50,   0.20,
+        0.20,  -0.44,  -0.50,   0.20,  -0.20,  -0.20,   0.20,  -0.44,
+       -0.50,   0.64,   0.00,   0.32,  -0.36,   0.50,  -0.20,  -0.30,
+        0.12,  -0.10,   0.48,   0.50,  -0.12,   0.30,  -0.36,  -0.50,
+        0.00,   0.00,   0.48,   0.50,  -0.48,   0.50,   0.68,   0.00,
+       -0.12,   0.56,  -0.40,   0.44,  -0.50,  -0.12,  -0.10,   0.24,
+        0.30,  -0.40,   0.40,   0.64,   0.00,  -0.24,   0.64,   0.00,
+       -0.20,   0.00,   0.00,   0.44,  -0.50,   0.44,   0.50,  -0.12,
+        0.20,  -0.36,  -0.50,   0.12,   0.00,   0.64,  -0.40,   0.50,
+        0.00,   0.10,   0.00,   0.00,  -0.40,   0.50,   0.00,   0.00,
+
+   /* 3796-3923 */
+       -0.40,  -0.50,   0.56,   0.00,   0.28,   0.00,   0.10,   0.36,
+        0.50,   0.00,  -0.10,   0.36,  -0.50,   0.36,   0.50,   0.00,
+       -0.10,   0.24,  -0.20,  -0.36,  -0.40,   0.16,   0.20,   0.40,
+       -0.40,   0.00,   0.00,  -0.36,  -0.50,  -0.36,  -0.50,  -0.32,
+       -0.50,  -0.12,   0.10,   0.20,   0.20,  -0.36,   0.40,  -0.60,
+        0.60,   0.28,   0.00,   0.52,   0.12,  -0.10,   0.40,   0.40,
+        0.00,  -0.50,   0.20,  -0.20,  -0.32,   0.40,   0.16,   0.20,
+       -0.16,   0.20,   0.32,   0.40,   0.56,   0.00,  -0.12,   0.32,
+       -0.40,  -0.16,  -0.20,   0.00,   0.00,   0.40,   0.40,  -0.40,
+       -0.40,  -0.40,   0.40,  -0.36,   0.40,   0.12,   0.10,   0.00,
+        0.10,   0.36,   0.40,   0.00,  -0.10,   0.36,   0.40,  -0.36,
+        0.40,   0.00,   0.10,   0.32,   0.00,   0.44,   0.12,   0.20,
+        0.28,  -0.40,   0.00,   0.00,   0.36,   0.40,   0.32,  -0.40,
+       -0.16,   0.12,   0.10,   0.32,  -0.40,   0.20,   0.30,  -0.24,
+        0.30,   0.00,   0.10,   0.32,   0.40,   0.00,  -0.10,  -0.32,
+       -0.40,  -0.32,   0.40,   0.00,   0.10,  -0.52,  -0.52,   0.52,
+
+   /* 3924-4051 */
+        0.32,  -0.40,   0.00,   0.00,   0.32,   0.40,   0.32,  -0.40,
+        0.00,   0.00,  -0.32,  -0.40,  -0.32,   0.40,   0.32,   0.40,
+        0.00,   0.00,   0.32,   0.40,   0.00,   0.00,  -0.32,  -0.40,
+        0.00,   0.00,   0.32,   0.40,   0.16,   0.20,   0.32,  -0.30,
+       -0.16,   0.00,  -0.48,  -0.20,   0.20,  -0.28,  -0.30,   0.28,
+       -0.40,   0.00,   0.00,   0.28,  -0.40,   0.00,   0.00,   0.28,
+       -0.40,   0.00,   0.00,  -0.28,  -0.40,   0.28,   0.40,  -0.28,
+       -0.40,  -0.48,  -0.20,   0.20,   0.24,   0.30,   0.44,   0.00,
+        0.16,   0.24,   0.30,   0.16,  -0.20,   0.24,   0.30,  -0.12,
+        0.20,   0.20,   0.30,  -0.16,   0.20,   0.00,   0.00,   0.44,
+       -0.32,   0.30,   0.24,   0.00,  -0.36,   0.36,   0.00,   0.24,
+        0.12,  -0.20,   0.20,   0.30,  -0.12,   0.00,  -0.28,   0.30,
+       -0.24,   0.30,   0.12,   0.10,  -0.28,  -0.30,  -0.28,   0.30,
+        0.00,   0.00,  -0.28,  -0.30,   0.00,   0.00,  -0.28,  -0.30,
+        0.00,   0.00,   0.28,   0.30,   0.00,   0.00,  -0.28,  -0.30,
+       -0.28,   0.30,   0.00,   0.00,  -0.28,  -0.30,   0.00,   0.00,
+
+   /* 4052-4179 */
+        0.28,   0.30,   0.00,   0.00,  -0.28,   0.30,   0.28,  -0.30,
+       -0.28,   0.30,   0.40,   0.40,  -0.24,   0.30,   0.00,  -0.10,
+        0.16,   0.00,   0.36,  -0.20,   0.30,  -0.12,  -0.10,  -0.24,
+       -0.30,   0.00,   0.00,  -0.24,   0.30,  -0.24,   0.30,   0.00,
+        0.00,  -0.24,   0.30,  -0.24,   0.30,   0.24,  -0.30,   0.00,
+        0.00,   0.24,  -0.30,   0.00,   0.00,   0.24,   0.30,   0.24,
+       -0.30,   0.24,   0.30,  -0.24,   0.30,  -0.24,   0.30,  -0.20,
+        0.20,  -0.16,  -0.20,   0.00,   0.00,  -0.32,   0.20,   0.00,
+        0.10,   0.20,  -0.30,   0.20,  -0.20,   0.12,   0.20,  -0.16,
+        0.20,   0.16,   0.20,   0.20,   0.30,   0.20,   0.30,   0.00,
+        0.00,  -0.20,   0.30,   0.00,   0.00,   0.20,   0.30,  -0.20,
+       -0.30,  -0.20,  -0.30,   0.20,  -0.30,   0.00,   0.00,   0.20,
+        0.30,   0.00,   0.00,   0.20,   0.30,   0.00,   0.00,   0.20,
+        0.30,   0.00,   0.00,   0.20,   0.30,   0.00,   0.00,   0.20,
+       -0.30,   0.00,   0.00,  -0.20,  -0.30,   0.00,   0.00,  -0.20,
+        0.30,   0.00,   0.00,  -0.20,   0.30,   0.00,   0.00,   0.36,
+
+   /* 4180-4307 */
+        0.00,   0.00,   0.36,   0.12,   0.10,  -0.24,   0.20,   0.12,
+       -0.20,  -0.16,  -0.20,  -0.13,   0.10,   0.22,   0.21,   0.20,
+        0.00,  -0.28,   0.32,   0.00,  -0.12,  -0.20,  -0.20,   0.12,
+       -0.10,   0.12,   0.10,  -0.20,   0.20,   0.00,   0.00,  -0.32,
+        0.32,   0.00,   0.00,   0.32,   0.32,   0.00,   0.00,  -0.24,
+       -0.20,   0.24,   0.20,   0.20,   0.00,  -0.24,   0.00,   0.00,
+       -0.24,  -0.20,   0.00,   0.00,   0.24,   0.20,  -0.24,  -0.20,
+        0.00,   0.00,  -0.24,   0.20,   0.16,  -0.20,   0.12,   0.10,
+        0.20,   0.20,   0.00,  -0.10,  -0.12,   0.10,  -0.16,  -0.20,
+       -0.12,  -0.10,  -0.16,   0.20,   0.20,   0.20,   0.00,   0.00,
+       -0.20,   0.20,  -0.20,   0.20,  -0.20,   0.20,  -0.20,   0.20,
+        0.20,  -0.20,  -0.20,  -0.20,   0.00,   0.00,  -0.20,   0.20,
+        0.20,   0.00,  -0.20,   0.00,   0.00,  -0.20,   0.20,  -0.20,
+        0.20,  -0.20,  -0.20,  -0.20,  -0.20,   0.00,   0.00,   0.20,
+        0.20,   0.20,   0.20,   0.12,  -0.20,  -0.12,  -0.10,   0.28,
+       -0.28,   0.16,  -0.20,   0.00,  -0.10,   0.00,   0.10,  -0.16,
+
+   /* 4308-4435 */
+        0.20,   0.00,  -0.10,  -0.16,  -0.20,   0.00,  -0.10,   0.16,
+       -0.20,   0.16,  -0.20,   0.00,   0.00,   0.16,   0.20,  -0.16,
+        0.20,   0.00,   0.00,   0.16,   0.20,   0.16,  -0.20,   0.16,
+       -0.20,  -0.16,   0.20,   0.16,  -0.20,   0.00,   0.00,   0.16,
+        0.20,   0.00,   0.00,   0.16,   0.20,   0.00,   0.00,  -0.16,
+       -0.20,   0.16,  -0.20,  -0.16,  -0.20,   0.00,   0.00,  -0.16,
+       -0.20,   0.00,   0.00,  -0.16,   0.20,   0.00,   0.00,   0.16,
+       -0.20,   0.16,   0.20,   0.16,   0.20,   0.00,   0.00,  -0.16,
+       -0.20,   0.00,   0.00,  -0.16,  -0.20,   0.00,   0.00,   0.16,
+        0.20,   0.16,   0.20,   0.00,   0.00,   0.16,   0.20,   0.16,
+       -0.20,   0.16,   0.20,   0.00,   0.00,  -0.16,   0.20,   0.00,
+        0.10,   0.12,  -0.20,   0.12,  -0.20,   0.00,  -0.10,   0.00,
+       -0.10,   0.12,   0.20,   0.00,  -0.10,  -0.12,   0.20,  -0.15,
+        0.20,  -0.24,   0.24,   0.00,   0.00,   0.24,   0.24,   0.12,
+       -0.20,  -0.12,  -0.20,   0.00,   0.00,   0.12,   0.20,   0.12,
+       -0.20,   0.12,   0.20,   0.12,   0.20,   0.12,   0.20,   0.12,
+
+   /* 4436-4563 */
+       -0.20,  -0.12,   0.20,   0.00,   0.00,   0.12,   0.20,   0.12,
+        0.00,  -0.20,   0.00,   0.00,  -0.12,  -0.20,   0.12,  -0.20,
+        0.00,   0.00,   0.12,   0.20,  -0.12,   0.20,  -0.12,   0.20,
+        0.12,  -0.20,   0.00,   0.00,   0.12,   0.20,   0.20,   0.00,
+        0.12,   0.00,   0.00,  -0.12,   0.20,   0.00,   0.00,  -0.12,
+       -0.20,   0.00,   0.00,  -0.12,  -0.20,  -0.12,  -0.20,   0.00,
+        0.00,   0.12,  -0.20,   0.12,  -0.20,   0.12,   0.20,  -0.12,
+       -0.20,   0.00,   0.00,   0.12,  -0.20,   0.12,  -0.20,   0.12,
+        0.20,   0.12,   0.00,   0.20,  -0.12,  -0.20,   0.00,   0.00,
+        0.12,   0.20,  -0.16,   0.00,   0.16,  -0.20,   0.20,   0.00,
+        0.00,  -0.20,   0.00,   0.00,  -0.20,   0.20,   0.00,   0.00,
+        0.20,   0.20,  -0.20,   0.00,   0.00,  -0.20,   0.12,   0.00,
+       -0.16,   0.20,   0.00,   0.00,   0.20,   0.12,  -0.10,   0.00,
+        0.10,   0.16,  -0.16,  -0.16,  -0.16,  -0.16,  -0.16,   0.00,
+        0.00,  -0.16,   0.00,   0.00,  -0.16,  -0.16,  -0.16,   0.00,
+        0.00,  -0.16,   0.00,   0.00,   0.16,   0.00,   0.00,   0.16,
+
+   /* 4564-4691 */
+        0.00,   0.00,   0.16,   0.16,   0.00,   0.00,  -0.16,   0.00,
+        0.00,  -0.16,  -0.16,   0.00,   0.00,   0.16,   0.00,   0.00,
+       -0.16,  -0.16,   0.00,   0.00,  -0.16,  -0.16,   0.12,   0.10,
+        0.12,  -0.10,   0.12,   0.10,   0.00,   0.00,   0.12,   0.10,
+       -0.12,   0.10,   0.00,   0.00,   0.12,   0.10,   0.12,  -0.10,
+        0.00,   0.00,  -0.12,  -0.10,   0.00,   0.00,   0.12,   0.10,
+        0.12,   0.00,   0.00,   0.12,   0.00,   0.00,  -0.12,   0.00,
+        0.00,   0.12,   0.12,   0.12,   0.12,   0.12,   0.00,   0.00,
+        0.12,   0.00,   0.00,   0.12,   0.12,   0.00,   0.00,   0.12,
+        0.00,   0.00,   0.12,  -0.12,  -0.12,   0.12,   0.12,  -0.12,
+       -0.12,   0.00,   0.00,   0.12,  -0.12,   0.12,   0.12,  -0.12,
+       -0.12,   0.00,   0.00,  -0.12,  -0.12,   0.00,   0.00,  -0.12,
+        0.12,   0.00,   0.00,   0.12,   0.00,   0.00,   0.12,   0.00,
+        0.00,   0.12,  -0.12,   0.00,   0.00,  -0.12,   0.12,  -0.12,
+       -0.12,   0.12,   0.00,   0.00,   0.12,   0.12,   0.12,  -0.12,
+        0.00,   0.00,  -0.12,  -0.12,  -0.12,   0.00,   0.00,  -0.12,
+
+   /* 4692-NA */
+       -0.12,   0.00,   0.00,   0.12,   0.12,   0.00,   0.00,  -0.12,
+       -0.12,  -0.12,  -0.12,   0.12,   0.00,   0.00,   0.12,  -0.12,
+        0.00,   0.00,  -0.12,  -0.12,   0.00,   0.00,   0.12,  -0.12,
+       -0.12,  -0.12,  -0.12,   0.12,   0.12,  -0.12,  -0.12,   0.00,
+        0.00,  -0.12,   0.00,   0.00,  -0.12,   0.12,   0.00,   0.00,
+        0.12,   0.00,   0.00,  -0.12,  -0.12,   0.00,   0.00,  -0.12,
+       -0.12,   0.12,   0.00,   0.00,   0.12,   0.12,   0.00,   0.00,
+        0.12,   0.00,   0.00,   0.12,   0.12,   0.08,   0.00,   0.04
+   };
+
+/* Number of amplitude coefficients */
+   static const int NA = (int) (sizeof a / sizeof (double));
+
+/* Amplitude usage: X or Y, sin or cos, power of T. */
+   static const int jaxy[] = {0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1};
+   static const int jasc[] = {0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0};
+   static const int japt[] = {0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4};
+
+/* Miscellaneous */
+   double t, w, pt[MAXPT+1], fa[14], xypr[2], xypl[2], xyls[2], arg,
+          sc[2];
+   int jpt, i, j, jxy, ialast, ifreq, m, ia, jsc;
+
+/*--------------------------------------------------------------------*/
+
+/* Interval between fundamental date J2000.0 and given date (JC). */
+   t = ((date1 - ERFA_DJ00) + date2) / ERFA_DJC;
+
+/* Powers of T. */
+   w = 1.0;
+   for (jpt = 0; jpt <= MAXPT; jpt++) {
+      pt[jpt] = w;
+      w *= t;
+   }
+
+/* Initialize totals in X and Y:  polynomial, luni-solar, planetary. */
+   for (jxy = 0; jxy < 2; jxy++) {
+      xypr[jxy] = 0.0;
+      xyls[jxy] = 0.0;
+      xypl[jxy] = 0.0;
+   }
+
+/* --------------------------------- */
+/* Fundamental arguments (IERS 2003) */
+/* --------------------------------- */
+
+/* Mean anomaly of the Moon. */
+   fa[0] = eraFal03(t);
+
+/* Mean anomaly of the Sun. */
+   fa[1] = eraFalp03(t);
+
+/* Mean argument of the latitude of the Moon. */
+   fa[2] = eraFaf03(t);
+
+/* Mean elongation of the Moon from the Sun. */
+   fa[3] = eraFad03(t);
+
+/* Mean longitude of the ascending node of the Moon. */
+   fa[4] = eraFaom03(t);
+
+/* Planetary longitudes, Mercury through Neptune. */
+   fa[5] = eraFame03(t);
+   fa[6] = eraFave03(t);
+   fa[7] = eraFae03(t);
+   fa[8] = eraFama03(t);
+   fa[9] = eraFaju03(t);
+   fa[10] = eraFasa03(t);
+   fa[11] = eraFaur03(t);
+   fa[12] = eraFane03(t);
+
+/* General accumulated precession in longitude. */
+   fa[13] = eraFapa03(t);
+
+/* -------------------------------------- */
+/* Polynomial part of precession-nutation */
+/* -------------------------------------- */
+
+   for (jxy = 0; jxy < 2; jxy++) {
+      for (j = MAXPT; j >= 0; j--) {
+         xypr[jxy] += xyp[jxy][j] * pt[j];
+      }
+   }
+
+/* ---------------------------------- */
+/* Nutation periodic terms, planetary */
+/* ---------------------------------- */
+
+/* Work backwards through the coefficients per frequency list. */
+   ialast = NA;
+   for (ifreq = NFPL-1; ifreq >= 0; ifreq--) {
+
+   /* Obtain the argument functions. */
+      arg = 0.0;
+      for (i = 0; i < 14; i++) {
+         m = mfapl[ifreq][i];
+         if (m != 0) arg += (double)m * fa[i];
+      }
+      sc[0] = sin(arg);
+      sc[1] = cos(arg);
+
+   /* Work backwards through the amplitudes at this frequency. */
+      ia = nc[ifreq+NFLS];
+      for (i = ialast; i >= ia; i--) {
+
+      /* Coefficient number (0 = 1st). */
+         j = i-ia;
+
+      /* X or Y. */
+         jxy = jaxy[j];
+
+      /* Sin or cos. */
+         jsc = jasc[j];
+
+      /* Power of T. */
+         jpt = japt[j];
+
+      /* Accumulate the component. */
+         xypl[jxy] += a[i-1] * sc[jsc] * pt[jpt];
+      }
+      ialast = ia-1;
+   }
+
+/* ----------------------------------- */
+/* Nutation periodic terms, luni-solar */
+/* ----------------------------------- */
+
+/* Continue working backwards through the number of coefficients list. */
+   for (ifreq = NFLS-1; ifreq >= 0; ifreq--) {
+
+   /* Obtain the argument functions. */
+      arg = 0.0;
+      for (i = 0; i < 5; i++) {
+         m = mfals[ifreq][i];
+         if (m != 0) arg += (double)m * fa[i];
+      }
+      sc[0] = sin(arg);
+      sc[1] = cos(arg);
+
+   /* Work backwards through the amplitudes at this frequency. */
+      ia = nc[ifreq];
+      for (i = ialast; i >= ia; i--) {
+
+      /* Coefficient number (0 = 1st). */
+         j = i-ia;
+
+      /* X or Y. */
+         jxy = jaxy[j];
+
+      /* Sin or cos. */
+         jsc = jasc[j];
+
+      /* Power of T. */
+         jpt = japt[j];
+
+      /* Accumulate the component. */
+         xyls[jxy] += a[i-1] * sc[jsc] * pt[jpt];
+      }
+      ialast = ia-1;
+   }
+
+/* ------------------------------------ */
+/* Results:  CIP unit vector components */
+/* ------------------------------------ */
+
+   *x = ERFA_DAS2R * (xypr[0] + (xyls[0] + xypl[0]) / 1e6);
+   *y = ERFA_DAS2R * (xypr[1] + (xyls[1] + xypl[1]) / 1e6);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/xys00a.c b/ast/erfa/xys00a.c
new file mode 100644
index 0000000..41a0723
--- /dev/null
+++ b/ast/erfa/xys00a.c
@@ -0,0 +1,142 @@
+#include "erfa.h"
+
+void eraXys00a(double date1, double date2,
+               double *x, double *y, double *s)
+/*
+**  - - - - - - - - - -
+**   e r a X y s 0 0 a
+**  - - - - - - - - - -
+**
+**  For a given TT date, compute the X,Y coordinates of the Celestial
+**  Intermediate Pole and the CIO locator s, using the IAU 2000A
+**  precession-nutation model.
+**
+**  Given:
+**     date1,date2  double   TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     x,y          double   Celestial Intermediate Pole (Note 2)
+**     s            double   the CIO locator s (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The Celestial Intermediate Pole coordinates are the x,y
+**     components of the unit vector in the Geocentric Celestial
+**     Reference System.
+**
+**  3) The CIO locator s (in radians) positions the Celestial
+**     Intermediate Origin on the equator of the CIP.
+**
+**  4) A faster, but slightly less accurate result (about 1 mas for
+**     X,Y), can be obtained by using instead the eraXys00b function.
+**
+**  Called:
+**     eraPnm00a    classical NPB matrix, IAU 2000A
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**     eraS00       the CIO locator s, given X,Y, IAU 2000A
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rbpn[3][3];
+
+
+/* Form the bias-precession-nutation matrix, IAU 2000A. */
+   eraPnm00a(date1, date2, rbpn);
+
+/* Extract X,Y. */
+   eraBpn2xy(rbpn, x, y);
+
+/* Obtain s. */
+   *s = eraS00(date1, date2, *x, *y);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/xys00b.c b/ast/erfa/xys00b.c
new file mode 100644
index 0000000..d2ebda7
--- /dev/null
+++ b/ast/erfa/xys00b.c
@@ -0,0 +1,142 @@
+#include "erfa.h"
+
+void eraXys00b(double date1, double date2,
+               double *x, double *y, double *s)
+/*
+**  - - - - - - - - - -
+**   e r a X y s 0 0 b
+**  - - - - - - - - - -
+**
+**  For a given TT date, compute the X,Y coordinates of the Celestial
+**  Intermediate Pole and the CIO locator s, using the IAU 2000B
+**  precession-nutation model.
+**
+**  Given:
+**     date1,date2  double   TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     x,y          double   Celestial Intermediate Pole (Note 2)
+**     s            double   the CIO locator s (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The Celestial Intermediate Pole coordinates are the x,y
+**     components of the unit vector in the Geocentric Celestial
+**     Reference System.
+**
+**  3) The CIO locator s (in radians) positions the Celestial
+**     Intermediate Origin on the equator of the CIP.
+**
+**  4) The present function is faster, but slightly less accurate (about
+**     1 mas in X,Y), than the eraXys00a function.
+**
+**  Called:
+**     eraPnm00b    classical NPB matrix, IAU 2000B
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**     eraS00       the CIO locator s, given X,Y, IAU 2000A
+**
+**  Reference:
+**
+**     McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
+**     IERS Technical Note No. 32, BKG (2004)
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rbpn[3][3];
+
+
+/* Form the bias-precession-nutation matrix, IAU 2000A. */
+   eraPnm00b(date1, date2, rbpn);
+
+/* Extract X,Y. */
+   eraBpn2xy(rbpn, x, y);
+
+/* Obtain s. */
+   *s = eraS00(date1, date2, *x, *y);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/xys06a.c b/ast/erfa/xys06a.c
new file mode 100644
index 0000000..e116e15
--- /dev/null
+++ b/ast/erfa/xys06a.c
@@ -0,0 +1,142 @@
+#include "erfa.h"
+
+void eraXys06a(double date1, double date2,
+               double *x, double *y, double *s)
+/*
+**  - - - - - - - - - -
+**   e r a X y s 0 6 a
+**  - - - - - - - - - -
+**
+**  For a given TT date, compute the X,Y coordinates of the Celestial
+**  Intermediate Pole and the CIO locator s, using the IAU 2006
+**  precession and IAU 2000A nutation models.
+**
+**  Given:
+**     date1,date2  double  TT as a 2-part Julian Date (Note 1)
+**
+**  Returned:
+**     x,y          double  Celestial Intermediate Pole (Note 2)
+**     s            double  the CIO locator s (Note 2)
+**
+**  Notes:
+**
+**  1) The TT date date1+date2 is a Julian Date, apportioned in any
+**     convenient way between the two arguments.  For example,
+**     JD(TT)=2450123.7 could be expressed in any of these ways,
+**     among others:
+**
+**            date1          date2
+**
+**         2450123.7           0.0       (JD method)
+**         2451545.0       -1421.3       (J2000 method)
+**         2400000.5       50123.2       (MJD method)
+**         2450123.5           0.2       (date & time method)
+**
+**     The JD method is the most natural and convenient to use in
+**     cases where the loss of several decimal digits of resolution
+**     is acceptable.  The J2000 method is best matched to the way
+**     the argument is handled internally and will deliver the
+**     optimum resolution.  The MJD method and the date & time methods
+**     are both good compromises between resolution and convenience.
+**
+**  2) The Celestial Intermediate Pole coordinates are the x,y components
+**     of the unit vector in the Geocentric Celestial Reference System.
+**
+**  3) The CIO locator s (in radians) positions the Celestial
+**     Intermediate Origin on the equator of the CIP.
+**
+**  4) Series-based solutions for generating X and Y are also available:
+**     see Capitaine & Wallace (2006) and eraXy06.
+**
+**  Called:
+**     eraPnm06a    classical NPB matrix, IAU 2006/2000A
+**     eraBpn2xy    extract CIP X,Y coordinates from NPB matrix
+**     eraS06       the CIO locator s, given X,Y, IAU 2006
+**
+**  References:
+**
+**     Capitaine, N. & Wallace, P.T., 2006, Astron.Astrophys. 450, 855
+**
+**     Wallace, P.T. & Capitaine, N., 2006, Astron.Astrophys. 459, 981
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   double rbpn[3][3];
+
+
+/* Form the bias-precession-nutation matrix, IAU 2006/2000A. */
+   eraPnm06a(date1, date2, rbpn);
+
+/* Extract X,Y. */
+   eraBpn2xy(rbpn, x, y);
+
+/* Obtain s. */
+   *s = eraS06(date1, date2, *x, *y);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/zp.c b/ast/erfa/zp.c
new file mode 100644
index 0000000..77a8e59
--- /dev/null
+++ b/ast/erfa/zp.c
@@ -0,0 +1,86 @@
+#include "erfa.h"
+
+void eraZp(double p[3])
+/*
+**  - - - - - -
+**   e r a Z p
+**  - - - - - -
+**
+**  Zero a p-vector.
+**
+**  Returned:
+**     p        double[3]      p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   p[0] = 0.0;
+   p[1] = 0.0;
+   p[2] = 0.0;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/zpv.c b/ast/erfa/zpv.c
new file mode 100644
index 0000000..ca176be
--- /dev/null
+++ b/ast/erfa/zpv.c
@@ -0,0 +1,88 @@
+#include "erfa.h"
+
+void eraZpv(double pv[2][3])
+/*
+**  - - - - - - -
+**   e r a Z p v
+**  - - - - - - -
+**
+**  Zero a pv-vector.
+**
+**  Returned:
+**     pv       double[2][3]      pv-vector
+**
+**  Called:
+**     eraZp        zero p-vector
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   eraZp(pv[0]);
+   eraZp(pv[1]);
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa/zr.c b/ast/erfa/zr.c
new file mode 100644
index 0000000..3a07ff8
--- /dev/null
+++ b/ast/erfa/zr.c
@@ -0,0 +1,92 @@
+#include "erfa.h"
+
+void eraZr(double r[3][3])
+/*
+**  - - - - - -
+**   e r a Z r
+**  - - - - - -
+**
+**  Initialize an r-matrix to the null matrix.
+**
+**  Returned:
+**     r        double[3][3]    r-matrix
+**
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  Derived, with permission, from the SOFA library.  See notes at end of file.
+*/
+{
+   r[0][0] = 0.0;
+   r[0][1] = 0.0;
+   r[0][2] = 0.0;
+   r[1][0] = 0.0;
+   r[1][1] = 0.0;
+   r[1][2] = 0.0;
+   r[2][0] = 0.0;
+   r[2][1] = 0.0;
+   r[2][2] = 0.0;
+
+   return;
+
+}
+/*----------------------------------------------------------------------
+**  
+**  
+**  Copyright (C) 2013-2016, NumFOCUS Foundation.
+**  All rights reserved.
+**  
+**  This library is derived, with permission, from the International
+**  Astronomical Union's "Standards of Fundamental Astronomy" library,
+**  available from http://www.iausofa.org.
+**  
+**  The ERFA version is intended to retain identical functionality to
+**  the SOFA library, but made distinct through different function and
+**  file names, as set out in the SOFA license conditions.  The SOFA
+**  original has a role as a reference standard for the IAU and IERS,
+**  and consequently redistribution is permitted only in its unaltered
+**  state.  The ERFA version is not subject to this restriction and
+**  therefore can be included in distributions which do not support the
+**  concept of "read only" software.
+**  
+**  Although the intent is to replicate the SOFA API (other than
+**  replacement of prefix names) and results (with the exception of
+**  bugs;  any that are discovered will be fixed), SOFA is not
+**  responsible for any errors found in this version of the library.
+**  
+**  If you wish to acknowledge the SOFA heritage, please acknowledge
+**  that you are using a library derived from SOFA, rather than SOFA
+**  itself.
+**  
+**  
+**  TERMS AND CONDITIONS
+**  
+**  Redistribution and use in source and binary forms, with or without
+**  modification, are permitted provided that the following conditions
+**  are met:
+**  
+**  1 Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+**  
+**  2 Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in
+**    the documentation and/or other materials provided with the
+**    distribution.
+**  
+**  3 Neither the name of the Standards Of Fundamental Astronomy Board,
+**    the International Astronomical Union nor the names of its
+**    contributors may be used to endorse or promote products derived
+**    from this software without specific prior written permission.
+**  
+**  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+**  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+**  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+**  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+**  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+**  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+**  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+**  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+**  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+**  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+**  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+**  POSSIBILITY OF SUCH DAMAGE.
+**  
+*/
diff --git a/ast/erfa2ast.h b/ast/erfa2ast.h
new file mode 100644
index 0000000..b19e0fc
--- /dev/null
+++ b/ast/erfa2ast.h
@@ -0,0 +1,248 @@
+#if !defined( ERFA2AST_INCLUDED )  /* Include this file only once */
+#define ERFA2AST_INCLUDED
+/*
+*  Name:
+*     erfa2ast.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Defines new names for symbols exported by the ERFA library.
+
+*  Invocation:
+*     #include "erfa2ast.h"
+
+*  Description:
+*     This include file defines a new name for each public function
+*     defined by the ERFA library. The names defined by ERFA itself are
+*     of the form "eraXxx" (e.g. eraPmp) - this include file defines
+*     a macro that translates each such name to the form "astEraXxx"
+*     (e.g. astEraPmp). This is done so that the names do not clash
+*     with any external ERFA library with which the application is linked.
+*
+*     It should be included at the start of any AST source file that refers
+*     to ERFA functions using the standard names (e.g. eraPmp).
+
+*  Copyright:
+*     Copyright (C) 2012 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     16-FEB-2012 (DSB):
+*        Original version.
+*/
+
+/* Rename all ERFA functions called directlty from PAL. */
+#define eraAf2a    astEraAf2a
+#define eraAnp	   astEraAnp
+#define eraAnpm	   astEraAnpm
+#define eraC2s	   astEraC2s
+#define eraCal2jd  astEraCal2jd
+#define eraD2tf	   astEraD2tf
+#define eraDat	   astEraDat
+#define eraEe06a   astEraEe06a
+#define eraEpb	   astEraEpb
+#define eraEpb2jd  astEraEpb2jd
+#define eraEpj	   astEraEpj
+#define eraEpj2jd  astEraEpj2jd
+#define eraEpv00   astEraEpv00
+#define eraFk5hz   astEraFk5hz
+#define eraGd2gc   astEraGd2gc
+#define eraGmst06  astEraGmst06
+#define eraHfk5z   astEraHfk5z
+#define eraIr	   astEraIr
+#define eraJd2cal  astEraJd2cal
+#define eraObl06   astEraObl06
+#define eraP06e	   astEraP06e
+#define eraPap	   astEraPap
+#define eraPas	   astEraPas
+#define eraPdp	   astEraPdp
+#define eraPmat06  astEraPmat06
+#define eraPn	   astEraPn
+#define eraPnm06a  astEraPnm06a
+#define eraPxp	   astEraPxp
+#define eraRm2v	   astEraRm2v
+#define eraRv2m	   astEraRv2m
+#define eraRx	   astEraRx
+#define eraRxp	   astEraRxp
+#define eraRxpv	   astEraRxpv
+#define eraRxr	   astEraRxr
+#define eraRy	   astEraRy
+#define eraRz	   astEraRz
+#define eraS2c	   astEraS2c
+#define eraSepp	   astEraSepp
+#define eraSeps	   astEraSeps
+#define eraTf2a	   astEraTf2a
+#define eraTf2d	   astEraTf2d
+#define eraTr	   astEraTr
+#define eraTrxp    astEraTrxp
+
+
+/* Rename all ERFA functions called internally within the above ERFA
+   functions. */
+#define eraA2af    astEraA2af
+#define eraA2tf	   astEraA2tf
+#define eraBi00	   astEraBi00
+#define eraBp00	   astEraBp00
+#define eraBp06	   astEraBp06
+#define eraBpn2xy  astEraBpn2xy
+#define eraC2i00a  astEraC2i00a
+#define eraC2i00b  astEraC2i00b
+#define eraC2i06a  astEraC2i06a
+#define eraC2ibpn  astEraC2ibpn
+#define eraC2ixy   astEraC2ixy
+#define eraC2ixys  astEraC2ixys
+#define eraC2t00a  astEraC2t00a
+#define eraC2t00b  astEraC2t00b
+#define eraC2t06a  astEraC2t06a
+#define eraC2tcio  astEraC2tcio
+#define eraC2teqx  astEraC2teqx
+#define eraC2tpe   astEraC2tpe
+#define eraC2txy   astEraC2txy
+#define eraCp	   astEraCp
+#define eraCpv	   astEraCpv
+#define eraCr	   astEraCr
+#define eraD2dtf   astEraD2dtf
+#define eraDtdb	   astEraDtdb
+#define eraDtf2d   astEraDtf2d
+#define eraEe00	   astEraEe00
+#define eraEe00a   astEraEe00a
+#define eraEe00b   astEraEe00b
+#define eraEect00  astEraEect00
+#define eraEform   astEraEform
+#define eraEo06a   astEraEo06a
+#define eraEors	   astEraEors
+#define eraEqeq94  astEraEqeq94
+#define eraEra00   astEraEra00
+#define eraFad03   astEraFad03
+#define eraFae03   astEraFae03
+#define eraFaf03   astEraFaf03
+#define eraFaju03  astEraFaju03
+#define eraFal03   astEraFal03
+#define eraFalp03  astEraFalp03
+#define eraFama03  astEraFama03
+#define eraFame03  astEraFame03
+#define eraFane03  astEraFane03
+#define eraFaom03  astEraFaom03
+#define eraFapa03  astEraFapa03
+#define eraFasa03  astEraFasa03
+#define eraFaur03  astEraFaur03
+#define eraFave03  astEraFave03
+#define eraFk52h   astEraFk52h
+#define eraFk5hip  astEraFk5hip
+#define eraFw2m	   astEraFw2m
+#define eraFw2xy   astEraFw2xy
+#define eraGc2gd   astEraGc2gd
+#define eraGc2gde  astEraGc2gde
+#define eraGd2gce  astEraGd2gce
+#define eraGmst00  astEraGmst00
+#define eraGmst82  astEraGmst82
+#define eraGst00a  astEraGst00a
+#define eraGst00b  astEraGst00b
+#define eraGst06   astEraGst06
+#define eraGst06a  astEraGst06a
+#define eraGst94   astEraGst94
+#define eraH2fk5   astEraH2fk5
+#define eraJdcalf  astEraJdcalf
+#define eraNum00a  astEraNum00a
+#define eraNum00b  astEraNum00b
+#define eraNum06a  astEraNum06a
+#define eraNumat   astEraNumat
+#define eraNut00a  astEraNut00a
+#define eraNut00b  astEraNut00b
+#define eraNut06a  astEraNut06a
+#define eraNut80   astEraNut80
+#define eraNutm80  astEraNutm80
+#define eraObl80   astEraObl80
+#define eraP2pv	   astEraP2pv
+#define eraP2s	   astEraP2s
+#define eraPb06	   astEraPb06
+#define eraPfw06   astEraPfw06
+#define eraPlan94  astEraPlan94
+#define eraPm	   astEraPm
+#define eraPmat00  astEraPmat00
+#define eraPmat76  astEraPmat76
+#define eraPmp	   astEraPmp
+#define eraPn00	   astEraPn00
+#define eraPn00a   astEraPn00a
+#define eraPn00b   astEraPn00b
+#define eraPn06	   astEraPn06
+#define eraPn06a   astEraPn06a
+#define eraPnm00a  astEraPnm00a
+#define eraPnm00b  astEraPnm00b
+#define eraPnm80   astEraPnm80
+#define eraPom00   astEraPom00
+#define eraPpp	   astEraPpp
+#define eraPpsp	   astEraPpsp
+#define eraPr00	   astEraPr00
+#define eraPrec76  astEraPrec76
+#define eraPv2p	   astEraPv2p
+#define eraPv2s	   astEraPv2s
+#define eraPvdpv   astEraPvdpv
+#define eraPvm	   astEraPvm
+#define eraPvmpv   astEraPvmpv
+#define eraPvppv   astEraPvppv
+#define eraPvstar  astEraPvstar
+#define eraPvu	   astEraPvu
+#define eraPvup	   astEraPvup
+#define eraPvxpv   astEraPvxpv
+#define eraRefco   astEraRefco
+#define eraS00	   astEraS00
+#define eraS00a	   astEraS00a
+#define eraS00b	   astEraS00b
+#define eraS06	   astEraS06
+#define eraS06a	   astEraS06a
+#define eraS2p	   astEraS2p
+#define eraS2pv	   astEraS2pv
+#define eraS2xpv   astEraS2xpv
+#define eraSp00	   astEraSp00
+#define eraStarpm  astEraStarpm
+#define eraStarpv  astEraStarpv
+#define eraSxp	   astEraSxp
+#define eraSxpv	   astEraSxpv
+#define eraTaitt   astEraTaitt
+#define eraTaiut1  astEraTaiut1
+#define eraTaiutc  astEraTaiutc
+#define eraTcbtdb  astEraTcbtdb
+#define eraTcgtt   astEraTcgtt
+#define eraTdbtcb  astEraTdbtcb
+#define eraTdbtt   astEraTdbtt
+#define eraTrxpv   astEraTrxpv
+#define eraTttai   astEraTttai
+#define eraTttcg   astEraTttcg
+#define eraTttdb   astEraTttdb
+#define eraTtut1   astEraTtut1
+#define eraUt1tai  astEraUt1tai
+#define eraUt1tt   astEraUt1tt
+#define eraUt1utc  astEraUt1utc
+#define eraUtctai  astEraUtctai
+#define eraUtcut1  astEraUtcut1
+#define eraXy06	   astEraXy06
+#define eraXys00a  astEraXys00a
+#define eraXys00b  astEraXys00b
+#define eraXys06a  astEraXys06a
+#define eraZp	   astEraZp
+#define eraZpv	   astEraZpv
+#define eraZr      astEranZr
+
+#endif
diff --git a/ast/erfam.h b/ast/erfam.h
new file mode 100644
index 0000000..5190b3e
--- /dev/null
+++ b/ast/erfam.h
@@ -0,0 +1,61 @@
+#if !defined( ERFAM_INCLUDED )  /* Include this file only once */
+#define ERFAM_INCLUDED
+/*
+*+
+*  Name:
+*     erfam.h
+
+*  Purpose:
+*     Macros defined by the ERFA library.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Include file
+
+*  Description:
+*     Macros defined by the ERFA library. This is needed by the pal.c
+*     file, which includes source files that include "erfam.h" from the
+*     current directory (i.e. the main AST source directory), not the
+*     erfa subdirectory.
+
+*  Authors:
+*     DSBJ: David S Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     23-FEB-2012 (DSB):
+*        Initial version.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software; you can redistribute it and/or
+*     modify it under the terms of the GNU General Public License as
+*     published by the Free Software Foundation; either version 3 of
+*     the License, or (at your option) any later version.
+*
+*     This program is distributed in the hope that it will be
+*     useful, but WITHOUT ANY WARRANTY; without even the implied
+*     warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+*     PURPOSE. See the GNU General Public License for more details.
+*
+*     You should have received a copy of the GNU General Public License
+*     along with this program; if not, write to the Free Software
+*     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+*     USA.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+/* Include the macros defined in the corresponding header file in the
+   erfa subdirectory. */
+#include "erfa/erfam.h"
+
+#endif
diff --git a/ast/err.h b/ast/err.h
new file mode 100644
index 0000000..9448d86
--- /dev/null
+++ b/ast/err.h
@@ -0,0 +1,66 @@
+#if !defined( ERR_INCLUDED ) /* Include this file only once */
+#define ERR_INCLUDED
+/*
+*+
+*  Name:
+*     err.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the err module.
+
+*  Invocation:
+*     #include "err.h"
+
+*  Description:
+*     This include file defines the interface to the err module and
+*     provides the type definitions, function prototypes and macros, etc.
+*     needed to use this module.
+
+*  Inheritance:
+*     The err module is not a class and does not inherit.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (STARLINK)
+*     {enter_new_authors_here}
+
+*  History:
+*     6-NOV-1996 (DSB):
+*        Original version.
+*     {enter_changes_here}
+*-
+*/
+
+/* Function prototypes. */
+/* ==================== */
+#if defined(astCLASS)            /* Protected */
+void astPutErr_( int, const char * );
+
+/* Function interfaces. */
+/* ==================== */
+#define astPutErr(status,message) astPutErr_(status,message)
+#endif
+
+#endif
diff --git a/ast/err_drama.c b/ast/err_drama.c
new file mode 100644
index 0000000..643afda
--- /dev/null
+++ b/ast/err_drama.c
@@ -0,0 +1,122 @@
+/*
+*  Name:
+*     err_ems.c
+
+*  Purpose:
+*     Implement the "err" module for the DRAMA ERS error system.
+
+*  Description:
+*     This file implements an alternative "err" module for the AST
+*     library. It is used to deliver error messages through the
+*     DRAMA ERS error message system rather than by the default mechanism.
+
+*  Copyright:
+*     Copyright (C) 2008 Science and Technology Facilities Council.
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (UCLan)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     RFWS: R.F. Warren-Smith (STARLINK)
+*     {enter_new_authors_here}
+
+*  History:
+*     6-NOV-1996 (DSB):
+*        Original version.
+*     16-SEP-2008 (TIMJ):
+*        Use modern EMS interface
+*     13-NOV-2008 (TIMJ):
+*        Modify for DRAMA
+*     {enter_changes_here}
+*/
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Module Macros. */
+/* ============== */
+/* Define the astCLASS macro (even although this is not a class
+   implementation). This indicates to header files that they should
+   make "protected" symbols available. */
+#define astCLASS
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "err.h"                 /* Interface to this module */
+
+/* Need to define these for DRAMA. Otherwise we have to include drama.h
+   in the distribution as well */
+#if HAVE_STDARG_H
+# define DSTDARG_OK
+#endif
+#define ERS_STANDALONE
+#include "Ers.h"                 /* Interface to the Ers system */
+
+/* Function implementations. */
+/* ========================= */
+void astPutErr_( int status, const char *message ) {
+/*
+*+
+*  Name:
+*     astPutErr
+
+*  Purpose:
+*     Deliver an error message.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "err.h"
+*     void astPutErr( int status, const char *message )
+
+*  Description:
+*     This function delivers an error message and (optionally) an
+*     accompanying status value to the user. It may be re-implemented
+*     in order to deliver error messages in different ways, according
+*     to the environment in which the AST library is being used.
+
+*  Parameters:
+*     status
+*        The error status value.
+*     message
+*        A pointer to a null-terminated character string containing
+*        the error message to be delivered. This should not contain
+*        newline characters.
+
+*  Notes:
+*     - This function is documented as "protected" but, in fact, is
+*     publicly accessible so that it may be re-implemented as
+*     required.
+*-
+*/
+
+/* Local Variables: */
+   StatusType local_status;             /* Local status value */
+
+/* Make a copy of the status value supplied. Then invoke ems_rep_c to
+   report the error message through EMS and to associate the error
+   status with it. Ignore any returned status value. */
+   local_status = status;
+   ErsRep( 0, &local_status, "%s", message );
+}
diff --git a/ast/err_ems.c b/ast/err_ems.c
new file mode 100644
index 0000000..63bcab6
--- /dev/null
+++ b/ast/err_ems.c
@@ -0,0 +1,108 @@
+/*
+*  Name:
+*     err_ems.c
+
+*  Purpose:
+*     Implement the "err" module for the EMS error system.
+
+*  Description:
+*     This file implements an alternative "err" module for the AST
+*     library. It is used to deliver error messages through the
+*     Starlink EMS error message system (Starlink System Note 4)
+*     rather than by the default mechanism.
+
+*  Copyright:
+*     Copyright (C) 2008 Science and Technology Facilities Council.
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (STARLINK)
+*     {enter_new_authors_here}
+
+*  History:
+*     6-NOV-1996 (DSB):
+*        Original version.
+*     16-SEP-2008 (TIMJ):
+*        Use modern EMS interface
+*     {enter_changes_here}
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Define the astCLASS macro (even although this is not a class
+   implementation). This indicates to header files that they should
+   make "protected" symbols available. */
+#define astCLASS
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "err.h"                 /* Interface to this module */
+#include "ems.h"                 /* Interface to the EMS system */
+
+/* Function implementations. */
+/* ========================= */
+void astPutErr_( int status, const char *message ) {
+/*
+*+
+*  Name:
+*     astPutErr
+
+*  Purpose:
+*     Deliver an error message.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "err.h"
+*     void astPutErr( int status, const char *message )
+
+*  Description:
+*     This function delivers an error message and (optionally) an
+*     accompanying status value to the user. It may be re-implemented
+*     in order to deliver error messages in different ways, according
+*     to the environment in which the AST library is being used.
+
+*  Parameters:
+*     status
+*        The error status value.
+*     message
+*        A pointer to a null-terminated character string containing
+*        the error message to be delivered. This should not contain
+*        newline characters.
+
+*  Notes:
+*     - This function is documented as "protected" but, in fact, is
+*     publicly accessible so that it may be re-implemented as
+*     required.
+*-
+*/
+
+/* Local Variables: */
+   int local_status;             /* Local status value */
+
+/* Make a copy of the status value supplied. Then invoke ems_rep_c to
+   report the error message through EMS and to associate the error
+   status with it. Ignore any returned status value. */
+   local_status = status;
+   emsRep( "AST_ERROR", message, &local_status );
+}
diff --git a/ast/err_null.c b/ast/err_null.c
new file mode 100644
index 0000000..8e84ee5
--- /dev/null
+++ b/ast/err_null.c
@@ -0,0 +1,108 @@
+/*
+*  Name:
+*     err_null.c
+
+*  Purpose:
+*     Implement the default "err" module.
+
+*  Description:
+*     This file implements the default "err" module for the AST
+*     library. It is used to deliver error messages if no alternative
+*     error delivery mechanism is provided.
+*
+*     To provide an alternative mechanism, re-implement this module
+*     and link your program against the resulting library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (STARLINK)
+*     {enter_new_authors_here}
+
+*  History:
+*     6-NOV-1996 (DSB):
+*        Original version.
+*     {enter_changes_here}
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Define the astCLASS macro (even although this is not a class
+   implementation). This indicates to header files that they should
+   make "protected" symbols available. */
+#define astCLASS
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "err.h"                 /* Interface to this module */
+#include "error.h"               /* Interface to the error module */
+
+/* C header files. */
+/* --------------- */
+#include <stdio.h>
+
+/* Function implementations. */
+/* ========================= */
+void astPutErr_( int status_value, const char *message ) {
+/*
+*+
+*  Name:
+*     astPutErr
+
+*  Purpose:
+*     Deliver an error message.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "err.h"
+*     void astPutErr( int status_value, const char *message )
+
+*  Description:
+*     This function delivers an error message and (optionally) an
+*     accompanying status value to the user. It may be re-implemented
+*     in order to deliver error messages in different ways, according
+*     to the environment in which the AST library is being used.
+
+*  Parameters:
+*     status_value
+*        The error status value.
+*     message
+*        A pointer to a null-terminated character string containing
+*        the error message to be delivered. This should not contain
+*        newline characters.
+
+*  Notes:
+*     - This function is documented as "protected" but, in fact, is
+*     publicly accessible so that it may be re-implemented as
+*     required.
+*-
+*/
+
+
+/* This is the default implementation. Simply write the message to
+   standard error with a newline appended. Ignore the status value. */
+   int *status = astGetStatusPtr;
+   (void) fprintf( stderr, "%s%s\n", astOK ? "!! " : "!  ", message );
+}
diff --git a/ast/error.c b/ast/error.c
new file mode 100644
index 0000000..586a8ca
--- /dev/null
+++ b/ast/error.c
@@ -0,0 +1,1024 @@
+/*
+*  Name:
+*     error.c
+
+*  Purpose:
+*     Implement error handling functions.
+
+*  Description:
+*     This file implements the Error module which provides functions
+*     for handling error conditions in the AST library.  For a
+*     description of the module and its interface, see the .h file of
+*     the same name.
+*
+*     Since its initial release, AST has used a global status variable
+*     rather than adding an explicit status parameter to the argument
+*     list of each AST function. This caused problems for the thread-safe
+*     version of AST since each thread needs its own status value. Whilst
+*     it would have been possible for each function to access a global
+*     status value via the pthreads "thread speific data key" mechanism,
+*     the huge number of status checks performed within AST caused this
+*     option to be slow. Instead AST has been modified so that every
+*     function has an explicit status pointer parameter. This though
+*     causes problems in that we cannot change the public interface to
+*     AST because doing so would break large amounts of external software.
+*     To get round this, the macros that define the public interface to
+*     AST have been modified so that they provide a status pointer
+*     automatically to the function that is being invoked. This is how
+*     the system works...
+*
+*     All AST functions have an integer inherited status pointer parameter
+*     called "status". This parameter is "hidden" in AST functions that
+*     are invoked via macros (typically public and protected functions).
+*     This means that whilst "int *status" appears explicitly at the end
+*     of the function argument list (in both prototype and definition), it
+*     is not included in the prologue documentation, and is not included
+*     explicitly in the argument list when invoking the function. Instead,
+*     the macro that is used to invoke the function adds in the required
+*     status parameter to the function invocation.
+*
+*     Macros which are invoked within AST (the protected interface) expand
+*     to include ", status" at the end of the function parameter list. For
+*     backward compatability with previous versions of AST, macros which
+*     are invoked from outside AST (the public interface) expand to include
+*     ", astGetStatusPtr" at the end of the function parameter list. The
+*     astGetStatusPtr function returns a pointer to the interbal AST
+*     status variable or to the external variable specified via astWatch.
+*
+*     Parameter lists for functions that have variable argument lists
+*     (such as astError) cannot be handled in this way, since macros cannot
+*     have variable numbers of arguments. Instead, separate public and
+*     protected implementations of such functions are provided within AST.
+*     Protected implementations include an explicit, documented status
+*     pointer parameter that must be given explicitly when invoking the
+*     function. Public implementations do not have a status pointer
+*     parameter. Instead they obtain the status pointer internally using
+*     astGetStatusPtr.
+*
+*     Private functions are called directly rather than via macros, and so
+*     they have a documented status pointer parameter that should be
+*     included explicitly in the parameter list when invoking the
+*     function.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2008-2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     2-JAN-1996 (RFWS):
+*        Original version.
+*     8-JAN-1996 (RFWS):
+*        Tidied up.
+*     26-JAN-1996 (RFWS):
+*        Incorporated changes to prologue style.
+*     14-JUN-1996 (RFWS):
+*        Added astAt.
+*     20-JUN-1996 (RFWS):
+*        Added astSetStatus.
+*     15-JUL-1996 (RFWS):
+*        Sorted out the public interface.
+*     16-JUL-1996 (RFWS):
+*        Added astWatch.
+*     18-MAR-1998 (RFWS):
+*        Added notes about functions being available for writing
+*        foreign language and graphics interfaces, etc.
+*     27-NOV-2002 (DSB):
+*        Added suppression of error reporting using astReporting.
+*     11-MAR-2004 (DSB):
+*        Add facility to astAt to allow astAt to be called from public
+*        interface without private interface settings over-riding the
+*        public interface settings.
+*     30-MAR-2005 (DSB):
+*        Added facility to report deferred messages when reporting is
+*        switched back on.
+*     16-FEB-2006 (DSB):
+*        Improve efficiency by replacing the astOK_ function with a macro
+*        which tests the value of status variable. The pointer which points
+*        to the AST status variable are now global rather than static.
+*     19-SEP-2008 (DSB):
+*        Big changes for the thread-safe version of AST.
+*     3-FEB-2009 (DSB):
+*        Added astBacktrace.
+*/
+
+/* Define the astCLASS macro (even although this is not a class
+   implementation) to obtain access to protected interfaces. */
+#define astCLASS
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "err.h"                 /* Interface to the err module */
+#include "error.h"               /* Interface to this module */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Configuration results. */
+/* ---------------------- */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Select the appropriate memory management functions. These will be the
+   system's malloc, free and realloc unless AST was configured with the
+   "--with-starmem" option, in which case they will be the starmem
+   malloc, free and realloc. */
+#ifdef HAVE_STAR_MEM_H
+#  include <star/mem.h>
+#  define MALLOC starMalloc
+#  define FREE starFree
+#  define REALLOC starRealloc
+#else
+#  define MALLOC malloc
+#  define FREE free
+#  define REALLOC realloc
+#endif
+
+/* Include execinfo.h if the backtrace function is available */
+#if HAVE_EXECINFO_H
+#include <execinfo.h>
+#endif
+
+
+
+/* Module Variables. */
+/* ================= */
+
+/* Define macros for accessing all items of thread-safe global data
+   used by this module. */
+#ifdef THREAD_SAFE
+
+#define reporting astGLOBAL(Error,Reporting)
+#define current_file astGLOBAL(Error,Current_File)
+#define current_routine astGLOBAL(Error,Current_Routine)
+#define current_line astGLOBAL(Error,Current_Line)
+#define foreign_set astGLOBAL(Error,Foreign_Set)
+#define message_stack astGLOBAL(Error,Message_Stack)
+#define mstack_size astGLOBAL(Error,Mstack_Size)
+
+/* Since the external astPutErr function may not be thread safe, we need
+   to ensure that it cannot be invoked simultaneously from two different
+   threads. So we lock a mutex before each call to astPutErr. */
+static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+
+#define INVOKE_ASTPUTERR( status, buff ) \
+   ( pthread_mutex_lock( &mutex1 ), \
+     astPutErr( (status), (buff) ), \
+     (void) pthread_mutex_unlock( &mutex1 ) )
+
+/* Define the initial values for the global data for this module. */
+#define GLOBAL_inits \
+   globals->Reporting = 1; \
+   globals->Current_File = NULL;  \
+   globals->Current_Routine = NULL;  \
+   globals->Current_Line = 0; \
+   globals->Foreign_Set = 0; \
+   globals->Mstack_Size = 0; \
+
+/* Create the global initialisation function. */
+astMAKE_INITGLOBALS(Error)
+
+
+/* If thread safety is not needed, declare globals at static variables. */
+/* -------------------------------------------------------------------- */
+#else
+
+/* Status variable. */
+static int internal_status = 0;  /* Internal error status */
+int *starlink_ast_status_ptr = &internal_status; /* Pointer to status variable */
+
+/* Reporting flag: delivery of message is supressed if zero. */
+static int reporting = 1;
+
+/* Error context. */
+static const char *current_file = NULL; /* Current file name pointer */
+static const char *current_routine = NULL; /* Current routine name pointer */
+static int current_line = 0;     /* Current line number */
+static int foreign_set = 0;      /* Have foreign values been set? */
+
+/* Un-reported message stack */
+static char *message_stack[ AST__ERROR_MSTACK_SIZE ];
+static int mstack_size = 0;
+
+/* If thread-safety is not needed, we can invoke the external astPutErr
+   function directly. */
+#define INVOKE_ASTPUTERR( status, buff ) \
+   astPutErr( (status), (buff) );
+
+#endif
+
+
+/* Function prototypes. */
+/* ==================== */
+static void EmptyStack( int, int * );
+
+/* Function implementations. */
+/* ========================= */
+void astAt_( const char *routine, const char *file, int line, int forn,
+             int *status) {
+/*
+*+
+*  Name:
+*     astAt
+
+*  Purpose:
+*     Store a routine, file and line number context in case of error.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "error.h"
+*     void astAt( const char *routine, const char *file, int line, int forn)
+
+*  Description:
+*     This function stores a pointer to two strings containing the
+*     names of a routine and a file, together with an integer line
+*     number. These values are retained for subsequent use in
+*     reporting the context of any error that may arise.
+
+*  Parameters:
+*     routine
+*        Pointer to a null terminated C string containing a routine
+*        name (which should reside in static memory).
+*     file
+*        Pointer to a null terminated C string containing a file name
+*        (which should reside in static memory).
+*     line
+*        The line number in the file.
+*     for
+*        Is this call being made from a foreign language interface?
+*        If so any values supplied will take precedence of the values
+*        set by the C interface.
+
+*  Notes:
+*     - This function returns without action (i.e. without changing
+*     the stored values) if the global error status is set. It
+*     performs no other error checking.
+*     - Any (or all) of the arguments may be omitted by supplying a
+*     NULL or zero value (as appropriate) and will then not be included
+*     in any error report.
+*     - This function is documented as protected because it should not
+*     be invoked by external code. However, it is available via the
+*     external C interface so that it may be used when writing (e.g.)
+*     foreign language or graphics interfaces.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS             /* Pointer to thread-specific global data */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* If the values refer to a foreign interface, or if no foreign values
+   have yet been set, store the supplied values. */
+   if( forn|| !foreign_set ) {
+      current_routine = routine;
+      current_file = file;
+      current_line = line;
+   }
+
+/* If the values relate to a foreign interface, set a flag which prevents
+   local values set later replacing them. */
+   foreign_set = forn;
+}
+
+void astBacktrace_( int *status ) {
+/*
+c+
+*  Name:
+*     astBacktrace
+
+*  Purpose:
+*     Display a backtrace on standard output.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "error.h"
+*     astBacktrace;
+
+*  Description:
+*     This macro displays a set of messages on standard output that
+*     give a backtrace of the caller. It can be useful for debugging AST
+*     code in situations when it is not easy or possible to use a
+*     debugger (for instance, when debugging JNIAST).
+
+*  Notes:
+*     - Only non-static function names are included in the backtrace.
+*     - This function requires the GNU C library. When called, it will
+*     just issue a warning if the GNU 'backtrace' function was not
+*     available when AST was configured.
+c-
+*/
+#if HAVE_BACKTRACE
+
+#define MAX_ADDR 100
+
+/* Local Variables: */
+   char **strings;           /* Pointer to array of formated strings */
+   char buf[ 120 ];          /* Output line buffer */
+   int j;                    /* String index */
+   int np;                   /* Number of used return addresses */
+   void *buffer[ MAX_ADDR ]; /* Array of return addresses */
+
+/* Get the array of return addresses. */
+   np = backtrace( buffer, MAX_ADDR );
+
+/* Convert them into strings. */
+   strings = backtrace_symbols( buffer, np );
+
+/* If succesful, display them and then free the array. Note we skip the
+   first one since that will refer to this function. */
+   if( strings ) {
+      INVOKE_ASTPUTERR( astStatus, " " );
+      for( j = 1; j < np; j++ ) {
+         sprintf( buf, "%d: %s", j, strings[j] );
+         INVOKE_ASTPUTERR( astStatus, buf );
+      }
+      free( strings );
+      INVOKE_ASTPUTERR( astStatus, " " );
+
+/* If not succesful, issue a warning. */
+   } else {
+      INVOKE_ASTPUTERR( astStatus, "Cannot convert backtrace addresses into formatted strings" );
+   }
+
+#else
+   INVOKE_ASTPUTERR( astStatus, "Backtrace functionality is not available "
+                     "on the current operating system." );
+#endif
+}
+
+void astClearStatus_( int *status ) {
+/*
+c++
+*  Name:
+*     astClearStatus
+
+*  Purpose:
+*     Clear the AST error status.
+
+*  Type:
+*     Public macro.
+
+*  Synopsis:
+*     #include "error.h"
+*     void astClearStatus
+
+*  Description:
+*     This macro resets the AST error status to an OK value,
+*     indicating that an error condition (if any) has been cleared.
+
+*  Notes:
+*     - If the AST error status is set to an error value (after an
+*     error), most AST functions will not execute and will simply
+*     return without action. Using astClearStatus will restore normal
+*     behaviour.
+c--
+*/
+
+/* Empty the deferred error stack without displaying the messages on the
+   stack. */
+   EmptyStack( 0, status );
+
+/* Reset the error status value. */
+   *status = 0;
+}
+
+static void EmptyStack( int display, int *status ) {
+/*
+*  Name:
+*     EmptyStack
+
+*  Purpose:
+*     Empty the stack of deferred error messages, optionally displaying
+*     them.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "error.h"
+*     void EmptyStack( int display, int *status )
+
+*  Description:
+*     This function removes all messages from the stack of deferred error
+*     messages. If "display" is non-zero it reports them using astPutErr
+*     before deleting them.
+
+*  Parameters:
+*     display
+*        Report messages before deleting them?
+*     status
+*        Pointer to the integer holding the inherited status value.
+
+*/
+
+/* Local variables; */
+   astDECLARE_GLOBALS         /* Pointer to thread-specific global data */
+   int i;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Loop round all messages on the stack. */
+   for( i = 0; i < mstack_size; i++ ) {
+
+/* Display the message if required. */
+      if( display ) INVOKE_ASTPUTERR( astStatus, message_stack[ i ] );
+
+/* Free the memory used to hold the message. */
+      FREE( message_stack[ i ] );
+      message_stack[ i ] = NULL;
+   }
+
+/* Reset the stack size to zero. */
+   mstack_size = 0;
+
+}
+
+void astErrorPublic_( int status_value, const char *fmt, ... ) {
+/*
+*+
+*  Name:
+*     astError
+
+*  Purpose:
+*     Set the AST error status and report an error message.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "error.h"
+*     void astError( int status_value, const char *fmt, ... )
+
+*  Description:
+*     This function sets the AST error status to a specified value and
+*     reports an associated error message.
+
+*  Parameters:
+*     status_value
+*        The new error status value to be set.
+*     fmt
+*        Pointer to a null-terminated character string containing the
+*        format specification for the error message, in the same way
+*        as for a call to the C "printf" family of functions.
+*     ...
+*        Additional optional arguments (as used by e.g. "printf")
+*        which specify values which are to appear in the error
+*        message.
+
+*  Notes:
+*     This function operates just like "printf", except that:
+*     - The first argument is an error status.
+*     - The return value is void.
+*     - A newline is automatically appended to the error message
+*     (there is no need to add one).
+*     - This function is documented as protected because it should not
+*     be invoked by external code. However, it is available via the
+*     external C interface so that it may be used when writing (e.g.)
+*     foreign language or graphics interfaces.
+*-
+
+*  This is the public implementation of astError. It does not have an
+   status pointer parameter, but instead obtains the status pointer
+   explicitly using the astGetStatusPtr function.  This is different to
+   other public functions, which typically have a status pointer parameter
+   that is supplied via a call to astGetStatusPtr within the associated
+   interface macro. The benefit of doing it the usual way is that the
+   public and protected implementations are the same, with the
+   differences between public and protecte dinterfaces wrapped up in the
+   associated interface macro. We cannot do this with this function
+   because of the variale argument list. The prologue for the astError_
+   function defines the interface for use internally within AST.
+
+*/
+
+/* Local Constants: */
+#define BUFF_LEN 1023            /* Max. length of an error message */
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   char buff[ BUFF_LEN + 1 ];    /* Message buffer */
+   int *status;                  /* Pointer to inherited status value */
+   int imess;                    /* Index into deferred message stack */
+   int nc;                       /* Number of characters written */
+   va_list args;                 /* Variable argument list pointer */
+
+/* Initialise the variable argument list pointer. */
+   va_start( args, fmt );
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the integer holding the inherited status value. */
+   status = astGetStatusPtr;
+
+/* If this is the first report of an error (the global status has not
+   previously been set) and error context information is available,
+   then construct an error context message. */
+   if ( astOK &&
+        ( current_routine || current_file || current_line ) ) {
+      nc = sprintf( buff, "AST: Error" );
+      if ( current_routine ) {
+         nc += sprintf( buff + nc, " in routine %s", current_routine );
+      }
+      if ( current_line ) {
+         nc += sprintf( buff + nc, " at line %d", current_line );
+      }
+      if ( current_file ) {
+         nc += sprintf( buff + nc, " in file %s", current_file );
+      }
+      nc += sprintf( buff + nc, "." );
+
+/* Deliver the error message unless reporting has been switched off using
+   astReporting. In which case store them in a static array. */
+      if( reporting ) {
+         INVOKE_ASTPUTERR( status_value, buff );
+      } else if( mstack_size < AST__ERROR_MSTACK_SIZE ){
+         imess = mstack_size++;
+         message_stack[ imess ] = MALLOC( strlen( buff ) + 1 );
+         if( message_stack[ imess ] ) {
+            strcpy( message_stack[ imess ], buff );
+         }
+      }
+
+/* Set the global status. */
+      astSetStatus( status_value );
+   }
+
+/* Write the error message supplied to the formatting buffer. */
+   nc = vsprintf( buff, fmt, args );
+
+/* Tidy up the argument pointer. */
+   va_end( args );
+
+/* Deliver the error message unless reporting has been switched off using
+   astReporting. */
+   if( reporting ) {
+      INVOKE_ASTPUTERR( status_value, buff );
+   } else if( mstack_size < AST__ERROR_MSTACK_SIZE ){
+      imess = mstack_size++;
+      message_stack[ imess ] = MALLOC( strlen( buff ) + 1 );
+      if( message_stack[ imess ] ) {
+         strcpy( message_stack[ imess ], buff );
+      }
+   }
+
+/* Set the error status value. */
+   astSetStatus( status_value );
+
+/* Undefine macros local to this function. */
+#undef BUFF_LEN
+}
+
+void astError_( int status_value, const char *fmt, int *status, ... ) {
+/*
+*+
+*  Name:
+*     astError
+
+*  Purpose:
+*     Set the AST error status and report an error message.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "error.h"
+*     void astError( int status_value, const char *fmt, int *status, ... )
+
+*  Description:
+*     This function sets the AST error status to a specified value and
+*     reports an associated error message.
+
+*  Parameters:
+*     status_value
+*        The error status value to be set.
+*     fmt
+*        Pointer to a null-terminated character string containing the
+*        format specification for the error message, in the same way
+*        as for a call to the C "printf" family of functions.
+*     status
+*        Pointer to the integer holding the inherited status value.
+*     ...
+*        Additional optional arguments (as used by e.g. "printf")
+*        which specify values which are to appear in the error
+*        message.
+
+*  Notes:
+*     This function operates just like "printf", except that:
+*     - The first argument is an error status.
+*     - The return value is void.
+*     - A newline is automatically appended to the error message
+*     (there is no need to add one).
+*     - This function is documented as protected because it should not
+*     be invoked by external code. However, it is available via the
+*     external C interface so that it may be used when writing (e.g.)
+*     foreign language or graphics interfaces.
+*-
+
+*  This is the protected implementation of astError. It has a status
+   pointer parameter that is not present in the public form. Different
+   implementations for protected and public interfaces are required
+   because of the variable argument list.
+
+*/
+
+/* Local Constants: */
+#define BUFF_LEN 1023            /* Max. length of an error message */
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   char buff[ BUFF_LEN + 1 ];    /* Message buffer */
+   int imess;                    /* Index into deferred message stack */
+   int nc;                       /* Number of characters written */
+   va_list args;                 /* Variable argument list pointer */
+
+/* Initialise the variable argument list pointer. */
+   va_start( args, status );
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* If this is the first report of an error (the global status has not
+   previously been set) and error context information is available,
+   then construct an error context message. */
+   if ( astOK &&
+        ( current_routine || current_file || current_line ) ) {
+      nc = sprintf( buff, "AST: Error" );
+      if ( current_routine ) {
+         nc += sprintf( buff + nc, " in routine %s", current_routine );
+      }
+      if ( current_line ) {
+         nc += sprintf( buff + nc, " at line %d", current_line );
+      }
+      if ( current_file ) {
+         nc += sprintf( buff + nc, " in file %s", current_file );
+      }
+      nc += sprintf( buff + nc, "." );
+
+/* Deliver the error message unless reporting has been switched off using
+   astReporting. In which case store them in a static array. */
+      if( reporting ) {
+         INVOKE_ASTPUTERR( status_value, buff );
+      } else if( mstack_size < AST__ERROR_MSTACK_SIZE ){
+         imess = mstack_size++;
+         message_stack[ imess ] = MALLOC( strlen( buff ) + 1 );
+         if( message_stack[ imess ] ) {
+            strcpy( message_stack[ imess ], buff );
+         }
+      }
+
+/* Set the global status. */
+      astSetStatus( status_value );
+   }
+
+/* Write the error message supplied to the formatting buffer. */
+   nc = vsprintf( buff, fmt, args );
+
+/* Tidy up the argument pointer. */
+   va_end( args );
+
+/* Deliver the error message unless reporting has been switched off using
+   astReporting. */
+   if( reporting ) {
+      INVOKE_ASTPUTERR( status_value, buff );
+   } else if( mstack_size < AST__ERROR_MSTACK_SIZE ){
+      imess = mstack_size++;
+      message_stack[ imess ] = MALLOC( strlen( buff ) + 1 );
+      if( message_stack[ imess ] ) {
+         strcpy( message_stack[ imess ], buff );
+      }
+   }
+
+/* Set the error status value. */
+   astSetStatus( status_value );
+
+/* Undefine macros local to this function. */
+#undef BUFF_LEN
+}
+
+int *astGetStatusPtr_(){
+/*
+*+
+*  Name:
+*     astGetStatusPtr
+
+*  Purpose:
+*     Return a pointer to the integer holding the inherited status value.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "error.h"
+*     int *astGetStatusPtr;
+
+*  Description:
+*     This macro returns a pointer to the integer holding the inherited
+*     status pointer. This will either be an internal global integer
+*     (possibly stored as thread specific data), or an ineger specified
+*     via the astWatch function.
+
+*  Returned Value:
+*      A pointer to the integer holding the inherited status value.
+
+*-
+*/
+
+/* The thread-safe version of AST stores the status pointer in thread
+   specific data, using the key stored in the global variable
+   "starlink_ast_status_key". */
+#if defined(THREAD_SAFE)
+   astDECLARE_GLOBALS
+   AstStatusBlock *sb;
+
+   astGET_GLOBALS(NULL);
+   sb = (AstStatusBlock *) pthread_getspecific(starlink_ast_status_key);
+   return sb->status_ptr;
+
+/* The non thread-safe version of AST stores the status pointer in the
+   global variable "starlink_ast_status_ptr". */
+#else
+   return starlink_ast_status_ptr;
+#endif
+}
+
+/*
+c++
+*  Name:
+*     astOK
+
+*  Purpose:
+*     Test whether AST functions have been successful.
+
+*  Type:
+*     Public macro.
+
+*  Synopsis:
+*     #include "error.h"
+*     int astOK
+
+*  Description:
+*     This macro returns a boolean value (0 or 1) to indicate if
+*     preceding AST functions have completed successfully
+*     (i.e. without setting the AST error status). If the error status
+*     is set to an error value, a value of zero is returned, otherwise
+*     the result is one.
+
+*  Returned Value:
+*     astOK
+*        One if the AST error status is OK, otherwise zero.
+
+*  Notes:
+*     - If the AST error status is set to an error value (after an
+*     error), most AST functions will not execute and will simply
+*     return without action. To clear the error status and restore
+*     normal behaviour, use astClearStatus.
+c--
+*/
+
+
+int astReporting_( int report, int *status ) {
+/*
+c+
+*  Name:
+*     astReporting
+
+*  Purpose:
+*     Controls the reporting of error messages.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "error.h"
+*     int astReporting( int report )
+
+*  Description:
+*     Error messages supplied to astError will only be delivered to the
+*     underlying error system if the "Reporting" flag is set to a
+*     non-zero value. Setting this flag to zero suppresses the reporting
+*     of error messages (the value of the AST error status however is
+*     unaffected). Instead, the reports are saved in an internal message
+*     stack. When reporting is switched back on again, any messages on this
+*     stack of deferred messages will be reported (and the stack emptied)
+*     if the AST error status is not astOK. Also the stack is emptied each
+*     time astClearStatus is called (the stacked messages are not displayed
+*     in this case).
+
+*  Parameters:
+*     report
+*        The new value for the Reporting flag.
+
+*  Returned Value:
+*     The original value of the Reporting flag.
+
+*  Notes:
+*     - The Reporting flag is initially set to 1.
+c-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS         /* Pointer to thread-specific global data */
+   int oldval;                /* Original "reporting" value */
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Save the original reporting value, and then store the new value. */
+   oldval = reporting;
+   reporting = report;
+
+/* If we are now reporting errors, flush any messages on the error stack.
+   This causes the messages to be displayed and the stack emptied. */
+   if( reporting ) EmptyStack( 1, status );
+
+/* Return the original reporting value. */
+   return oldval;
+}
+
+/*
+c++
+*  Name:
+*     astSetStatus
+
+*  Purpose:
+*     Set the AST error status to an explicit value.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "error.h"
+*     void astSetStatus( int status_value )
+
+*  Description:
+*     This function sets the AST error status to the value supplied.
+*     It does not cause any error message to be produced and should
+*     not be used as part of normal error reporting. Its purpose is
+*     simply to communicate to AST that an error has occurred in some
+*     other item of software.
+*
+*     For example, a source or sink function supplied as an argument
+*     to astChannel or astFitsChan might use this to signal that an
+*     input/output error has occurred. AST could then respond by
+*     terminating the current read or write operation.
+
+*  Parameters:
+*     status_value
+*        The new error status value to be set.
+
+*  Notes:
+*     - If the AST error status is set to an error value, most AST
+*     functions will not execute and will simply return without
+*     action. To clear the error status and restore normal behaviour,
+*     use astClearStatus.
+c--
+*/
+
+/*
+c++
+*  Name:
+*     astStatus
+
+*  Purpose:
+*     Obtain the current AST error status value.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "error.h"
+*     int astStatus
+
+*  Description:
+*     This function returns the current value of the AST error status.
+
+*  Returned Value:
+*     astStatus
+*        The AST error status value.
+
+*  Notes:
+*     - If the AST error status is set to an error value (after an
+*     error), most AST functions will not execute and will simply
+*     return without action. To clear the error status and restore
+*     normal behaviour, use astClearStatus.
+c--
+*/
+
+int *astWatch_( int *status_ptr ) {
+/*
+c++
+*  Name:
+*     astWatch
+
+*  Purpose:
+*     Identify a new error status variable for the AST library.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "error.h"
+*     int *astWatch( int *status_ptr )
+
+*  Description:
+*     This function allows a new error status variable to be accessed
+*     by the AST library when checking for and reporting error
+*     conditions.
+*
+*     By default, the library uses an internal integer error status
+*     which is set to an error value if an error occurs. Use of
+*     astWatch allows the internal error status to be replaced by an
+*     integer variable of your choosing, so that the AST library can
+*     share its error status directly with other code which uses the
+*     same error detection convention.
+*
+*     If an alternative error status variable is supplied, it is used
+*     by all related AST functions and macros (e.g. astOK, astStatus
+*     and astClearStatus).
+
+*  Parameters:
+*     status_ptr
+*        Pointer to an int whose value is to be used subsequently as
+*        the AST inherited status value. If a NULL pointer is supplied,
+*        the AST library will revert to using its own internal error status.
+
+*  Returned Value:
+*     astWatch()
+*        Address of the previous error status variable. This may later
+*        be passed back to astWatch to restore the previous behaviour
+*        of the library. (Note that on the first invocation of
+*        astWatch the returned value will be the address of the
+*        internal error status variable.)
+
+*  Notes:
+*     - This function is not available in the FORTRAN 77 interface to
+*     the AST library.
+c--
+*/
+
+/* Local Variables: */
+   int *result;               /* Value to be returned */
+   astDECLARE_GLOBALS         /* Pointer to thread-specific global data */
+
+#if defined(THREAD_SAFE)
+       AstStatusBlock *sb = NULL;
+#endif
+
+/* Ensure that the thread-specific status block has been created and
+   ininitialised. */
+   astGET_GLOBALS(NULL);
+
+#if defined(THREAD_SAFE)
+   sb = (AstStatusBlock *) pthread_getspecific( starlink_ast_status_key );
+   result = sb->status_ptr;
+   sb->status_ptr = status_ptr ? status_ptr : &(sb->internal_status);
+#else
+   result = starlink_ast_status_ptr;
+   starlink_ast_status_ptr = status_ptr ? status_ptr : &internal_status;
+#endif
+
+/* Return the old address. */
+   return result;
+}
+
+
diff --git a/ast/error.h b/ast/error.h
new file mode 100644
index 0000000..28907fc
--- /dev/null
+++ b/ast/error.h
@@ -0,0 +1,327 @@
+#if !defined( ERROR_INCLUDED )   /* Include this file only once */
+#define ERROR_INCLUDED 1
+/*
+*+
+*  Name:
+*     error.h
+
+*  Purpose:
+*     Define the interface to the Error module.
+
+*  Description:
+*     This module defines functions which implement error handling and
+*     reporting of error messages from within the AST library. A
+*     simple public interface is included to allow the AST error
+*     status to be tested and cleared after an error.
+*
+*     Note that this module is not a class implementation, although it
+*     resembles one.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2008 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S Berry (Starlink)
+*     NG: Norman Gray (Starlink)
+
+*  History:
+*     2-JAN-1996 (RFWS):
+*        Original version.
+*     26-JAN-1996 (RFWS):
+*        Added function interfaces.
+*     14-JUN-1996 (RFWS):
+*        Added AST__FAC and astAt.
+*     20-JUN-1996 (RFWS):
+*        Added astSetStatus.
+*     16-JUL-1996 (RFWS):
+*        Added astWatch.
+*     18-MAR-1998 (RFWS):
+*        Make interface available for writing foreign language and
+*        graphics interfaces, etc.
+*     27-NOV-2002 (DSB):
+*        Added astReporting.
+*     14-MAR-2005 (NG):
+*        Added astAssert
+*     20-MAY-2005 (DSB):
+*        Modified astAssert so that it does nothing if the AST error
+*        status is already set, and also so that does nothing unless
+*        the DEBUG macro is defined.
+*     16-FEB-2006 (DSB):
+*        Improve efficiency by replacing the astOK_ function with a macro
+*        which tests the value of status variable. The pointer which points
+*        to the status variable are now global rather than static.
+*     1-MAR-2006 (DSB):
+*        Remove astAssert.
+*     19-SEP-2008 (DSB)
+*        Big changes for thread-safe version of AST.
+*-
+*/
+
+/* Suppress "operands are evaluated in unspecified order" warnings from
+   the Intel icc compiler. These are caused by the astGetStatusPtr_
+   function being called several times within each of the macros that
+   form the public interface for AST. */
+#ifdef __INTEL_COMPILER
+#pragma warning(disable:981)
+#endif
+
+
+/* Include files. */
+/* ============== */
+#if defined(THREAD_SAFE)
+#include <pthread.h>
+#endif
+
+
+/* Macros. */
+/* ======= */
+#if defined(astCLASS) || defined(astFORTRAN77) /* Protected */
+
+/* Define a facility number that is unique to this library.  The number here
+ * is the facility code assigned to the AST library, but it doesn't have to
+ * be this number -- it only has to be probably unique.  If that code were
+ * ever to change, then you can update this number if you feel it's tidier
+ * that way. */
+#define AST__FAC (1521)
+
+/* Max number of messages which can be deferred when reporting is
+   switched off. */
+#define AST__ERROR_MSTACK_SIZE 100
+
+#endif
+
+/* This macro expands to an invocation of a specified function, together
+   with a call to astAt to record the file and line number at which the
+   invocation occurs. These are included in public error reports. This is
+   only done for invocations from outside of AST (i.e. public invocations).*/
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define astERROR_INVOKE(function) (function)
+#else
+#define astERROR_INVOKE(function) (astAt_(NULL,__FILE__,__LINE__,0,astGetStatusPtr),(function))
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type definitions */
+/* ================ */
+
+/* Define a structure to hold information about an error context. */
+typedef struct AstErrorContext {
+   int reporting;   /* Value of error reporting flag at start of context */
+   int ok;          /* Was the status value OK at start of context? */
+   int status;      /* The status value at the start of the context */
+} AstErrorContext;
+
+#if defined(THREAD_SAFE) && ( defined(astCLASS) || defined(astFORTRAN77) )
+
+/* Define a structure holding all data items that are global within the
+   error.c file. */
+typedef struct AstErrorGlobals {
+
+/* Reporting flag: delivery of message is supressed if zero. */
+   int Reporting;
+
+/* Error context. */
+   const char *Current_File;     /* Current file name pointer */
+   const char *Current_Routine;  /* Current routine name pointer */
+   int Current_Line;             /* Current line number */
+   int Foreign_Set;              /* Have foreign values been set? */
+
+/* Un-reported message stack */
+   char *Message_Stack[ AST__ERROR_MSTACK_SIZE ];
+   int Mstack_Size;
+
+} AstErrorGlobals;
+
+/* Structure to hold the internal status variable, and the status
+   pointer for a single thread. */
+typedef struct AstStatusBlock {
+   int internal_status;
+   int *status_ptr;
+} AstStatusBlock;
+
+#endif
+
+
+/* Function Macros. */
+/* ================ */
+
+#if defined(astCLASS)
+
+/*
+*+
+*  Name:
+*     astErrorBegin
+
+*  Purpose:
+*     Begin a new error reporting context.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "error.h"
+*     astErrorBegin( AstErrorContext *context );
+
+*  Description:
+*     This macro starts a new error reporting context. It saves any
+*     existing error status in the supplied ontext structure, and then
+*     clears the status value. It also defers further error reporting.
+*
+*     Each invocation of this macro should be followed (eventually) by a
+*     matching invocation of astErrorEnd.
+
+*  Parameters:
+*     context
+*        Pointer to a structure in which to to storeinformation about the
+*        current error reporting context. This structure should be passed
+*        unchanged to the corresponding invocation of astErrorEnd.
+
+*-
+*/
+#define astErrorBegin(context) {\
+\
+/* Save the original error status value. */ \
+   (context)->status = astStatus; \
+\
+/* Save a flag indicating if the original error status was good. */ \
+   (context)->ok = astOK; \
+\
+/* Switch off the immediate delivery of error messages, recording the  \
+   original value of the reporting flag. */ \
+   (context)->reporting = astReporting( 0 ); \
+\
+/* Clear any existing error condition. */ \
+   astClearStatus; \
+}
+
+
+/*
+*+
+*  Name:
+*     astErrorEnd
+
+*  Purpose:
+*     End an error reporting context.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "error.h"
+*     astErrorEnd( AstErrorContext *context );
+
+*  Description:
+*     This macro ends an error reporting context started using
+*     astErrorBegin.
+*
+*     Each invocation of this macro should correspond to an earlier
+*     invocation of astErrorBegin.
+
+*  Parameters:
+*     context
+*        Pointer to a structure holding information returned by the
+*        matching invocation of astErrorBegin.
+
+*-
+*/
+#define astErrorEnd(context) { \
+\
+/* If an error condition existed when astErrorBegin was called, and \
+   another error has since occurred, clear the deferred messages \
+   reported during the error context without displaying them. */ \
+   if( !(context)->ok && !astOK ) astClearStatus; \
+\
+/* Put the error reporting flag back to its original value. This will \
+   have the effect of displaying any remaining errors reported within \
+   the error context (they will already have been cleared if an error \
+   condition existed at the start of the context). */ \
+   astReporting( (context)->reporting ); \
+\
+/* If an error condition existed at the start of the context, re-instate \
+   the original status value. */ \
+   if( !(context)->ok ) astSetStatus( (context)->status ); \
+}
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+
+int *astWatch_( int * );
+void astClearStatus_( int * );
+int *astGetStatusPtr_( void )__attribute__((pure));
+void astAt_( const char *, const char *, int, int, int * );
+
+#if defined(astCLASS) || defined(astFORTRAN77)      /* Protected only */
+int astReporting_( int, int * );
+void astError_( int, const char *, int *, ... )__attribute__((format(printf,2,4)));
+void astBacktrace_( int * );
+#if defined(THREAD_SAFE)
+void astInitErrorGlobals_( AstErrorGlobals * );
+#endif
+#endif
+
+void astErrorPublic_( int, const char *, ... )__attribute__((format(printf,2,3)));
+
+
+
+/* Function interfaces. */
+/* ==================== */
+/* These wrap up the functions defined by this module to make them
+   easier to use. */
+
+#define astWatch(status_ptr) astWatch_(status_ptr)
+#define astGetStatusPtr astGetStatusPtr_()
+#define astOK (astStatus==0)
+#define astSetStatus(status_value) (astStatus=(status_value))
+
+#if defined(astCLASS)     /* Protected */
+
+#define astAt(routine,file,line) astAt_(routine,file,line,0,status)
+#define astClearStatus astClearStatus_(status)
+#define astStatus (*status)
+#define astError astError_
+#define astReporting(report) astReporting_(report,status)
+#define astBacktrace astBacktrace_(status)
+
+#elif defined(astFORTRAN77)
+
+#define astAt(routine,file,line) astAt_(routine,file,line,1,STATUS)
+#define astClearStatus astClearStatus_(status)
+#define astStatus (*status)
+#define astError astError_
+#define astReporting(report) astReporting_(report,status)
+
+#else
+
+#define astAt(routine,file,line) astAt_(routine,file,line,1,astGetStatusPtr)
+#define astClearStatus astClearStatus_(astGetStatusPtr)
+#define astStatus (*astGetStatusPtr)
+#define astError astErrorPublic_
+
+#endif
+
+#endif
diff --git a/ast/f77.h b/ast/f77.h
new file mode 100644
index 0000000..7f1a728
--- /dev/null
+++ b/ast/f77.h
@@ -0,0 +1,1096 @@
+/*
+*+
+*  Name:
+*     f77.h and cnf.h
+
+*  Purpose:
+*     C - FORTRAN interace macros and prototypes
+
+*  Language:
+*     C (part ANSI, part not)
+
+*  Type of Module:
+*     C include file
+
+*  Description:
+*     For historical reasons two files, F77.h and cnf.h are required
+*     but the have now been combined and for new code, only one is
+*     necessary.
+*
+*     This file defines the macros needed to write C functions that are
+*     designed to be called from FORTRAN programs, and to do so in a
+*     portable way. Arguments are normally passed by reference from a
+*     FORTRAN program, and so the F77 macros arrange for a pointer to
+*     all arguments to be available. This requires no work on most
+*     machines, but will actually generate the pointers on a machine
+*     that passes FORTRAN arguments by value.
+
+*  Notes:
+*     -  Macros are provided to handle the conversion of logical data
+*        values between the way that FORTRAN represents a value and the
+*        way that C represents it.
+*     -  Macros are provided to convert variables between the FORTRAN and
+*        C method of representing them. In most cases there is no
+*        conversion required, the macros just arrange for a pointer to
+*        the FORTRAN variable to be set appropriately. The possibility that
+*        FORTRAN and C might use different ways of representing integer
+*        and floating point values is considered remote, the macros are
+*        really only there for completeness and to assist in the automatic
+*        generation of C interfaces.
+*     -  For character variables the macros convert between
+*        the FORTRAN method of representing them (fixed length, blank
+*        filled strings) and the C method (variable length, null
+*        terminated strings) using calls to the CNF functions.
+
+*  Implementation Deficiencies:
+*     -  The macros support the K&R style of function definition, but
+*        this file may not work with all K&R compilers as it contains
+*        "#if defined" statements. These could be replaced with #ifdef's
+*        if necessary. This has not been done as is would make the code
+*        less clear and the need for support for K&R sytle definitions
+*        should disappear as ANSI compilers become the default.
+
+*  Copyright:
+*     Copyright (C) 1991, 1993 Science & Engineering Research Council.
+*     Copyright (C) 2006 Particle Physics and Astronomy Research Council.
+*     Copyright (C) 2007,2008 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software; you can redistribute it and/or
+*     modify it under the terms of the GNU General Public License as
+*     published by the Free Software Foundation; either version 2 of
+*     the License, or (at your option) any later version.
+*
+*     This program is distributed in the hope that it will be
+*     useful,but WITHOUT ANY WARRANTY; without even the implied
+*     warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+*     PURPOSE. See the GNU General Public License for more details.
+*
+*     You should have received a copy of the GNU General Public License
+*     along with this program; if not, write to the Free Software
+*     Foundation, Inc., 51 Franklin Street,Fifth Floor, Boston, MA
+*     02110-1301, USA
+
+*  Authors:
+*     PMA: Peter Allan (Starlink, RAL)
+*     AJC: Alan Chipperfield (Starlink, RAL)
+*     TIMJ: Tim Jenness (JAC)
+*     PWD: Peter W. Draper (JAC, Durham University)
+*     {enter_new_authors_here}
+
+*  History:
+*     23-MAY-1991 (PMA):
+*        Original version.
+*     19-JUN-1991 (PMA):
+*        Removed VMS versions of IM(EX)PORT_LOGICAL macros that tried
+*        to convert data representations.
+*     24-JUN-1991 (PMA):
+*        Changed the names of IMPORT macros to GENPTR.
+*        Removed the EXPORT macros.
+*     27-JUN-1991 (PMA):
+*        Modified DECstation specific stuff to allow use of the c89
+*        compiler.
+*      8-JUL-1991 (PMA):
+*        Added macros to call FORTRAN from C.
+*     16-OCT-1991 (PMA):
+*        Remove type_ARRAY2 definitions.
+*        Remove the length argument from CHARACTER_ARRAY and the
+*        dimension specifier from GENPTR_type_ARRAY.
+*        Add extra brackets to F77_ISFALSE and F77_ISTRUE.
+*     25-OCT-1991 (PMA):
+*        Changed "if defined(sun4)" to "if defined(sun)"
+*     2-JUN-1992 (PMA):
+*        Changed "if defined(mips)" to "if defined(ultrix)" to prevent
+*        those definitions being used on a Silicon Graphics machine.
+*     11-JUN-1992 (PMA):
+*        Changed "if defined(ultrix)" back to "if defined(mips)" so that
+*        it still works on OSF/1 on a DECstation.
+*        Add support for general non-ANSI compilers, but not basic K&R
+*        ones.
+*     12-JUN-1992 (PMA):
+*        Change declaration of dummy scalar arguments to be const
+*        pointers.  Change declaration of dummy array arguments to be
+*        const pointers.
+*     5-JAN-1993 (PMA):
+*        Changed "if defined(mips)" so that it will recognise a
+*        DECstation running Ultrix or OSF/1, but not a Silicon Graphics
+*        workstation.
+*        Change the definition of F77_BYTE_TYPE to add "signed".
+*        Redefine this on VMS where signed is invalid syntax.
+*        Add new types of UBYTE and UWORD.
+*     8-JAN-1993 (PMA):
+*        Fix bug in the definition of CHARACTER_RETURN_VALUE. There was
+*        an extraneous space.
+*        Add a macro F77_POINTER_TYPE and use it to define POINTER.
+*     13-JAN-1993 (PMA):
+*        Start to add support for K&R function definitions. These are
+*        done on a per machine basis.
+*     16-APR-1993 (PMA):
+*        Change the definition of F77_POINTER_TYPE from int to unsigned
+*        int.
+*     7-MAY-1993 (PMA):
+*        Change from using a null comment as a token concatenation
+*        operator to using the internal macro _f77_x on non-ANSI
+*        systems.
+*     10-MAY-1993 (PMA):
+*        Finish adding K&R support. This will form version 2.0 of F77.
+*     10-MAY-1993 (PMA):
+*        Add support for Alpha OSF/1.
+*     9-JUL-1993 (PMA):
+*        Add further POINTER macros: POINTER_ARRAY,
+*        GENPTR_POINTER_ARRAY, DECLARE_POINTER, DECLARE_POINTER_ARRAY,
+*        POINTER_ARG, POINTER_ARRAY_ARG, F77_POINTER_FUNCTION,
+*        KR_POINTER_ARRAY.
+*     24-AUG-1993 (PMA):
+*        Add const to the VMS definitions of CHARACTER and CHARACTER_ARRAY.
+*     3-NOV-1993 (PMA):
+*        Remove K&R stuff to a separate file.
+*        Released on Unix as version 2.0 of CNF.
+*     11-NOV-1993 (PMA):
+*        Return to using the null comment to concatenate text on non-ANSI
+*        systems as _f77_x caused problems with the c89 -common flag on
+*        DECstations.
+*     23-JAN-1996 (AJC):
+*        Add SUBROUTINE, type_FUNCTION, SUBROUTINE_ARG,
+*        type_FUNCTION_ARG, GENPTR_SUBROUTINE and GENPTR_type_FUNCTION
+*        required for passed subroutine and function name.
+*     29-JAN-1996 (AJC):
+*        Add the dynamic CHARACTER_ macros
+*        and CHARACTER_ARG_TYPE
+*     22-FEB-1996 (AJC):
+*        Add CHARACTER_RETURN_ARG
+*     23-MAY-1996 (AJC):
+*        Add DECLARE_CHARACTER_ARRAY_DYN
+*            F77_CREATE_CHARACTER_ARRAY
+*            F77_CHARACTER_ARG_TYPE
+*     14-JUN-1996 (AJC):
+*        Add DECLARE_LOGICAL_ARRAY_DYN
+*            F77_CREATE_LOGICAL_ARRAY
+*     21-JUN-1996 (AJC):
+*        Add cast to _ARRAY_ARGs to allow multidimensional arrays
+*     17-MAR-1998 (AJC):
+*        Add DECLARE, CREATE and FREE dynamic array macros for all types
+*        Changed CREATE_CHARACTER_ARRAY and CREATE_LOGICAL_ARRAY to use
+*         number of elements rather than dimensions.
+*        Add IMPORT, EXPORT and ASSOC macros
+*     22-JUL-1998 (AJC):
+*        Combined F77.h and cnf.h
+*     23-SEP-1998 (AJC):
+*        Input strings for cnf -> const char *
+*        Input int arrays for cnf -> const int *
+*      4-NOV-1998 (AJC):
+*        Bring cnf prototypes in line with .c routines
+*      8-FEB-1999 (AJC):
+*        Added cnf_mem stuff
+*      9-FEB-1999 (AJC):
+*        Use cnf_cptr/fptr for IMPORT/EXPORT_POINTER
+*     16-FEB-1999 (AJC):
+*        Added missing cnf_fptr prototype
+*     23-JUN-1999 (AJC):
+*        Change cnf_name to cnfName
+*        and add macros for cnf_name
+*      1-DEC-1999 (AJC):
+*        Add define cnf_free
+*      7-JAN-2000 (AJC):
+*        Correct omission of F77_ASSOC_UBYTE_ARRAY
+*        Correct F77_EXPORT_UWORD_ARRAY
+*      25-AUG-2005 (TIMJ):
+*        Add cnfInitRTL
+*      23-FEB-2006 (TIMJ):
+*        Add cnfRealloc
+*        Use starMalloc rather than malloc in F77_CREATE_POINTER_ARRAY
+*        (since it needs to match what goes on in cnfFree)
+*      21-JUN-2006 (PWD):
+*        Changed to use a different return type for REAL functions. This
+*        effects g77 under 64-bit, when the f2c bindings expect the return
+*        value of a REAL function to be a double, not a float.
+*      25-SEP-2006 (PWD):
+*        Introduced F77_CREATE_IMPORT_CHARACTER. Match length of
+*        F77_CREATE_CHARACTER to result from cnfCref.
+*      13-JUL-2007 (PWD):
+*        Parameterise the type of Fortran character string lengths. Can
+*        be long.
+*      7-OCT-2008 (TIMJ):
+*        Initialise pointers.
+*      11-MAY-2011 (DSB):
+*        Added F77_LOCK
+*     {enter_further_changes_here}
+*
+
+*  Bugs:
+*     {note_any_bugs_here}
+
+*-
+------------------------------------------------------------------------------
+*/
+#if !defined(CNF_MACROS)
+#define CNF_MACROS
+
+#include <stdlib.h>
+#include <string.h>
+/*  This initial sections defines values for all macros. These are the	    */
+/*  values that are generally appropriate to an ANSI C compiler on Unix.    */
+/*  For macros that have different values on other systems, the macros	    */
+/*  should be undefined and then redefined in the system specific sections. */
+/*  At the end of this section, some macros are redefined if the compiler   */
+/*  is non-ANSI.							    */
+
+#if defined(__STDC__) || defined(VMS)
+#define CNF_CONST const
+#else
+#define CNF_CONST
+#endif
+
+/*  -----  Macros common to calling C from FORTRAN and FORTRAN from C  ---- */
+
+
+/*  ---  External Names  ---						    */
+
+/*  Macro to define the name of a Fortran routine or common block. This	    */
+/*  ends in an underscore on many Unix systems.				    */
+
+#define F77_EXTERNAL_NAME(X) X ## _
+
+
+/*  ---  Logical Values  ---						    */
+
+/*  Define the values that are used to represent the logical values TRUE    */
+/*  and FALSE in Fortran.						    */
+
+#define F77_TRUE 1
+#define F77_FALSE 0
+
+/*  Define macros that evaluate to C logical values, given a FORTRAN	    */
+/*  logical value.							    */
+
+#define F77_ISTRUE(X) ( X )
+#define F77_ISFALSE(X) ( !( X ) )
+
+
+/*  ---  Common Blocks  ---						    */
+
+/*  Macros used in referring to FORTRAN common blocks.			    */
+
+#define F77_BLANK_COMMON                @BLANK_COMMON_SYMBOL@
+#define F77_NAMED_COMMON(B)             F77_EXTERNAL_NAME(B)
+
+
+
+/*  ------------------  Calling C from FORTRAN  --------------------------- */
+
+
+/*  ---  Data Types  ---						    */
+
+/*  Define macros for all the Fortran data types (except COMPLEX, which is  */
+/*  not handled by this package).					    */
+
+#define F77_INTEGER_TYPE   int
+#define F77_REAL_TYPE      float
+#define F77_REAL_FUNCTION_TYPE      float
+#define F77_DOUBLE_TYPE    double
+#define F77_LOGICAL_TYPE   int
+#define F77_CHARACTER_TYPE char
+#define F77_BYTE_TYPE      signed char
+#define F77_WORD_TYPE      short int
+#define F77_UBYTE_TYPE     unsigned char
+#define F77_UWORD_TYPE     unsigned short int
+
+/*  Define macros for the type of a CHARACTER and CHARACTER_ARRAY argument  */
+#define F77_CHARACTER_ARG_TYPE char
+#define F77_CHARACTER_ARRAY_ARG_TYPE char
+
+/*  Define a macro to use when passing arguments that STARLINK FORTRAN	    */
+/*  treats as a pointer. From the point of view of C, this type should be   */
+/*  (void *), but it is declared as type unsigned int as we actually pass   */
+/*  an INTEGER from the FORTRAN routine. The distinction is important for   */
+/*  architectures where the size of an INTEGER is not the same as the size  */
+/*  of a pointer.							    */
+
+#define F77_POINTER_TYPE   unsigned int
+
+
+/*  ---  Subroutine Names  ---						    */
+
+/* This declares that the C function returns a value of void.		    */
+
+#define F77_SUBROUTINE(X)  void F77_EXTERNAL_NAME(X)
+
+
+/*  ---  Function Names  ---						    */
+
+/*  Macros to define the types and names of functions that return values.   */
+/*  Due the the different ways that function return values could be	    */
+/*  implemented, it is better not to use functions, but to stick to using   */
+/*  subroutines.							    */
+
+/*  Character functions are implemented, but in a way that cannot be	    */
+/*  guaranteed to be portable although it will work on VMS, SunOS, Ultrix   */
+/*  and DEC OSF/1. It would be better to return the character value as a    */
+/*  subroutine argument where possible, rather than use a character	    */
+/*  function.								    */
+
+#define F77_INTEGER_FUNCTION(X)   F77_INTEGER_TYPE F77_EXTERNAL_NAME(X)
+#define F77_REAL_FUNCTION(X)      F77_REAL_FUNCTION_TYPE F77_EXTERNAL_NAME(X)
+#define F77_DOUBLE_FUNCTION(X)    F77_DOUBLE_TYPE F77_EXTERNAL_NAME(X)
+#define F77_LOGICAL_FUNCTION(X)   F77_LOGICAL_TYPE F77_EXTERNAL_NAME(X)
+#define F77_CHARACTER_FUNCTION(X) void F77_EXTERNAL_NAME(X)
+#define F77_BYTE_FUNCTION(X)      F77_BYTE_TYPE F77_EXTERNAL_NAME(X)
+#define F77_WORD_FUNCTION(X)      F77_WORD_TYPE F77_EXTERNAL_NAME(X)
+#define F77_UBYTE_FUNCTION(X)     F77_UBYTE_TYPE F77_EXTERNAL_NAME(X)
+#define F77_UWORD_FUNCTION(X)     F77_UWORD_TYPE F77_EXTERNAL_NAME(X)
+#define F77_POINTER_FUNCTION(X)   F77_POINTER_TYPE F77_EXTERNAL_NAME(X)
+
+
+/*  ---  Character return value for a function  ---			    */
+
+#define CHARACTER_RETURN_VALUE(X) CHARACTER(X) TRAIL(X)
+#define CHARACTER_RETURN_ARG(X) CHARACTER_ARG(X) TRAIL_ARG(X)
+
+/*  ---  Dummy Arguments  ---						    */
+
+/*  Macros for defining subroutine arguments. All these macros take a	    */
+/*  single argument; the name of the parameter. On most systems, a numeric  */
+/*  argument is passed as a pointer.					    */
+
+#define INTEGER(X)     F77_INTEGER_TYPE *CNF_CONST X
+#define REAL(X)        F77_REAL_TYPE    *CNF_CONST X
+#define DOUBLE(X)      F77_DOUBLE_TYPE  *CNF_CONST X
+#define LOGICAL(X)     F77_LOGICAL_TYPE *CNF_CONST X
+#define BYTE(X)        F77_BYTE_TYPE    *CNF_CONST X
+#define WORD(X)        F77_WORD_TYPE    *CNF_CONST X
+#define UBYTE(X)       F77_UBYTE_TYPE   *CNF_CONST X
+#define UWORD(X)       F77_UWORD_TYPE   *CNF_CONST X
+
+/*  Pointer arguments. Define a pointer type for passing pointer values	    */
+/*  between subroutines.						    */
+
+#define POINTER(X)     F77_POINTER_TYPE *CNF_CONST X
+
+/*  EXTERNAL arguments. Define a passed subroutine or function name */
+#define SUBROUTINE(X)  void (*X)()
+#define INTEGER_FUNCTION(X)  F77_INTEGER_TYPE (*X)()
+#define REAL_FUNCTION(X)  F77_REAL_TYPE (*X)()
+#define DOUBLE_FUNCTION(X)  F77_DOUBLE_TYPE (*X)()
+#define LOGICAL_FUNCTION(X)  F77_LOGICAL_TYPE (*X)()
+#define CHARACTER_FUNCTION(X)  F77_CHARACTER_TYPE (*X)()
+#define BYTE_FUNCTION(X)  F77_BYTE_TYPE (*X)()
+#define WORD_FUNCTION(X)  F77_WORD_TYPE (*X)()
+#define UBYTE_FUNCTION(X)  F77_UBYTE_TYPE (*X)()
+#define UWORD_FUNCTION(X)  F77_UWORD_TYPE (*X)()
+#define POINTER_FUNCTION(X)  F77_POINTER_TYPE (*X)()
+
+/*  Array arguments.							    */
+
+#define INTEGER_ARRAY(X)     F77_INTEGER_TYPE *CNF_CONST X
+#define REAL_ARRAY(X)        F77_REAL_TYPE    *CNF_CONST X
+#define DOUBLE_ARRAY(X)      F77_DOUBLE_TYPE  *CNF_CONST X
+#define LOGICAL_ARRAY(X)     F77_LOGICAL_TYPE *CNF_CONST X
+#define BYTE_ARRAY(X)        F77_BYTE_TYPE    *CNF_CONST X
+#define WORD_ARRAY(X)        F77_WORD_TYPE    *CNF_CONST X
+#define UBYTE_ARRAY(X)       F77_UBYTE_TYPE   *CNF_CONST X
+#define UWORD_ARRAY(X)       F77_UWORD_TYPE   *CNF_CONST X
+
+#define POINTER_ARRAY(X)     F77_POINTER_TYPE *CNF_CONST X
+
+/*  Macros to handle character arguments.				    */
+
+/*  Character arguments can be passed in many ways. The purpose of these    */
+/*  macros and the GENPTR_CHARACTER macro (defined in the next section) is  */
+/*  to generate a pointer to a character variable called ARG and an integer */
+/*  ARG_length containing the length of ARG. If these two variables are	    */
+/*  available directly from the argument list of the routine, then the	    */
+/*  GENPTR_CHARACTER macro is null, otherwise it works on intermediate	    */
+/*  variables.								    */
+
+#define CHARACTER(X)             F77_CHARACTER_TYPE *CNF_CONST X
+#define TRAIL(X)                 ,int X ## _length
+#define CHARACTER_ARRAY(X)       F77_CHARACTER_TYPE *CNF_CONST X
+
+
+/*  ---  Getting Pointers to Arguments  ---				    */
+
+/*  Macros that ensure that a pointer to each argument is available for the */
+/*  programmer to use. Usually this means that these macros are null. On    */
+/*  VMS, a pointer to a character variable has to be generated. If a	    */
+/*  particular machine were to pass arguments by reference, rather than by  */
+/*  value, then these macros would construct the appropriate pointers.	    */
+
+#define GENPTR_INTEGER(X)
+#define GENPTR_REAL(X)
+#define GENPTR_DOUBLE(X)
+#define GENPTR_CHARACTER(X)
+#define GENPTR_LOGICAL(X)
+#define GENPTR_BYTE(X)
+#define GENPTR_WORD(X)
+#define GENPTR_UBYTE(X)
+#define GENPTR_UWORD(X)
+#define GENPTR_POINTER(X)
+
+#define GENPTR_INTEGER_ARRAY(X)
+#define GENPTR_REAL_ARRAY(X)
+#define GENPTR_DOUBLE_ARRAY(X)
+#define GENPTR_CHARACTER_ARRAY(X)
+#define GENPTR_LOGICAL_ARRAY(X)
+#define GENPTR_BYTE_ARRAY(X)
+#define GENPTR_WORD_ARRAY(X)
+#define GENPTR_UBYTE_ARRAY(X)
+#define GENPTR_UWORD_ARRAY(X)
+#define GENPTR_POINTER_ARRAY(X)
+
+#define GENPTR_SUBROUTINE(X)
+#define GENPTR_INTEGER_FUNCTION(X)
+#define GENPTR_REAL_FUNCTION(X)
+#define GENPTR_DOUBLE_FUNCTION(X)
+#define GENPTR_CHARACTER_FUNCTION(X)
+#define GENPTR_LOGICAL_FUNCTION(X)
+#define GENPTR_BYTE_FUNCTION(X)
+#define GENPTR_WORD_FUNCTION(X)
+#define GENPTR_UBYTE_FUNCTION(X)
+#define GENPTR_UWORD_FUNCTION(X)
+#define GENPTR_POINTER_FUNCTION(X)
+
+
+
+/*  ------------------  Calling FORTRAN from C  --------------------------- */
+
+
+/*  ---  Declare variables  ---						    */
+
+#define DECLARE_INTEGER(X) F77_INTEGER_TYPE X
+#define DECLARE_REAL(X)    F77_REAL_TYPE X
+#define DECLARE_DOUBLE(X)  F77_DOUBLE_TYPE X
+#define DECLARE_LOGICAL(X) F77_LOGICAL_TYPE X
+#define DECLARE_BYTE(X)    F77_BYTE_TYPE X
+#define DECLARE_WORD(X)    F77_WORD_TYPE X
+#define DECLARE_UBYTE(X)   F77_UBYTE_TYPE X
+#define DECLARE_UWORD(X)   F77_UWORD_TYPE X
+
+#define DECLARE_POINTER(X) F77_POINTER_TYPE X
+
+#define DECLARE_CHARACTER(X,L) F77_CHARACTER_TYPE X[L]; \
+   const int X##_length = L
+
+
+/*  ---  Declare arrays ---						    */
+
+#define DECLARE_INTEGER_ARRAY(X,D) F77_INTEGER_TYPE X[D]
+#define DECLARE_REAL_ARRAY(X,D)    F77_REAL_TYPE X[D]
+#define DECLARE_DOUBLE_ARRAY(X,D)  F77_DOUBLE_TYPE X[D]
+#define DECLARE_LOGICAL_ARRAY(X,D) F77_LOGICAL_TYPE X[D]
+#define DECLARE_BYTE_ARRAY(X,D)    F77_BYTE_TYPE X[D]
+#define DECLARE_WORD_ARRAY(X,D)    F77_WORD_TYPE X[D]
+#define DECLARE_UBYTE_ARRAY(X,D)   F77_UBYTE_TYPE X[D]
+#define DECLARE_UWORD_ARRAY(X,D)   F77_UWORD_TYPE X[D]
+#define DECLARE_POINTER_ARRAY(X,D) F77_POINTER_TYPE X[D]
+#define DECLARE_CHARACTER_ARRAY(X,L,D) F77_CHARACTER_TYPE X[D][L]; \
+   const int X##_length = L
+
+/*  ---  Declare and construct dynamic CHARACTER arguments ---                      */
+#define DECLARE_CHARACTER_DYN(X)   F77_CHARACTER_TYPE *X = NULL;\
+   int X##_length = 0
+#define F77_CREATE_CHARACTER(X,L)  X=cnfCref(L);\
+   X##_length = (L>0?L:1)
+
+/* Declare Dynamic Fortran arrays */
+#define DECLARE_INTEGER_ARRAY_DYN(X) F77_INTEGER_TYPE *X = NULL
+#define DECLARE_REAL_ARRAY_DYN(X)    F77_REAL_TYPE *X = NULL
+#define DECLARE_DOUBLE_ARRAY_DYN(X)  F77_DOUBLE_TYPE *X = NULL
+#define DECLARE_LOGICAL_ARRAY_DYN(X) F77_LOGICAL_TYPE *X = NULL
+#define DECLARE_BYTE_ARRAY_DYN(X)    F77_BYTE_TYPE *X = NULL
+#define DECLARE_WORD_ARRAY_DYN(X)    F77_WORD_TYPE *X = NULL
+#define DECLARE_UBYTE_ARRAY_DYN(X)   F77_UBYTE_TYPE *X = NULL
+#define DECLARE_UWORD_ARRAY_DYN(X)   F77_UWORD_TYPE *X = NULL
+#define DECLARE_POINTER_ARRAY_DYN(X) F77_POINTER_TYPE *X = NULL
+#define DECLARE_CHARACTER_ARRAY_DYN(X)   F77_CHARACTER_TYPE *X = NULL;\
+   int X##_length = 0
+
+/* Create arrays dynamic Fortran arrays for those types which require */
+/* Separate space for Fortran and C arrays                            */
+/* Character and logical are already defined                          */
+/* For most types there is nothing to do                              */
+#define F77_CREATE_CHARACTER_ARRAY(X,L,N) \
+        {int f77dims[1];f77dims[0]=N;X=cnfCrefa(L,1,f77dims);X##_length=L;}
+#define F77_CREATE_CHARACTER_ARRAY_M(X,L,N,D)  X=cnfCrefa(L,N,D);\
+   X##_length = L
+#define F77_CREATE_LOGICAL_ARRAY(X,N) \
+        {int f77dims[1];f77dims[0]=N;X=cnfCrela(1,f77dims);}
+#define F77_CREATE_LOGICAL_ARRAY_M(X,N,D) X=cnfCrela(N,D)
+#define F77_CREATE_INTEGER_ARRAY(X,N)
+#define F77_CREATE_REAL_ARRAY(X,N)
+#define F77_CREATE_DOUBLE_ARRAY(X,N)
+#define F77_CREATE_BYTE_ARRAY(X,N)
+#define F77_CREATE_UBYTE_ARRAY(X,N)
+#define F77_CREATE_WORD_ARRAY(X,N)
+#define F77_CREATE_UWORD_ARRAY(X,N)
+#define F77_CREATE_POINTER_ARRAY(X,N)\
+        X=(F77_POINTER_TYPE *) malloc(N*sizeof(F77_POINTER_TYPE))
+
+/* Associate Fortran arrays with C arrays                             */
+/* These macros ensure that there is space somewhere for the Fortran  */
+/* array. They are complemetary to the CREATE_type_ARRAY macros       */
+#define F77_ASSOC_CHARACTER_ARRAY(F,C)
+#define F77_ASSOC_LOGICAL_ARRAY(F,C)
+#define F77_ASSOC_INTEGER_ARRAY(F,C) F=C
+#define F77_ASSOC_REAL_ARRAY(F,C) F=C
+#define F77_ASSOC_DOUBLE_ARRAY(F,C) F=C
+#define F77_ASSOC_BYTE_ARRAY(F,C) F=C
+#define F77_ASSOC_UBYTE_ARRAY(F,C) F=C
+#define F77_ASSOC_WORD_ARRAY(F,C) F=C
+#define F77_ASSOC_UWORD_ARRAY(F,C) F=C
+#define F77_ASSOC_POINTER_ARRAY(F,C)
+
+/* Free created dynamic arrays */
+/* Character and logical are already defined */
+/* For most types there is nothing to do */
+#define F77_FREE_INTEGER(X)
+#define F77_FREE_REAL(X)
+#define F77_FREE_DOUBLE(X)
+#define F77_FREE_BYTE(X)
+#define F77_FREE_UBYTE(X)
+#define F77_FREE_WORD(X)
+#define F77_FREE_UWORD(X)
+#define F77_FREE_POINTER(X) cnfFree((void *)X);
+#define F77_FREE_CHARACTER(X) cnfFreef( X )
+#define F77_FREE_LOGICAL(X) cnfFree( (char *)X )
+
+/*  ---  IMPORT and EXPORT of values  --- */
+/* Export C variables to Fortran variables */
+#define F77_EXPORT_CHARACTER(C,F,L) cnfExprt(C,F,L)
+#define F77_EXPORT_DOUBLE(C,F) F=C
+#define F77_EXPORT_INTEGER(C,F) F=C
+#define F77_EXPORT_LOGICAL(C,F) F=C?F77_TRUE:F77_FALSE
+#define F77_EXPORT_REAL(C,F) F=C
+#define F77_EXPORT_BYTE(C,F) F=C
+#define F77_EXPORT_WORD(C,F) F=C
+#define F77_EXPORT_UBYTE(C,F) F=C
+#define F77_EXPORT_UWORD(C,F) F=C
+#define F77_EXPORT_POINTER(C,F) F=cnfFptr(C)
+#define F77_EXPORT_LOCATOR(C,F) cnfExpch(C,F,DAT__SZLOC)
+
+/* Allow for character strings to be NULL, protects strlen. Note this
+ * does not allow lengths to differ. */
+#define F77_CREATE_EXPORT_CHARACTER(C,F) \
+     if (C) { \
+        F77_CREATE_CHARACTER(F,strlen(C)); \
+        F77_EXPORT_CHARACTER(C,F,F##_length); \
+     } else { \
+        F77_CREATE_CHARACTER(F,1); \
+        F77_EXPORT_CHARACTER(" ",F,F##_length); \
+     }
+
+/* Export C arrays to Fortran */
+/* Arrays are assumed to be 1-d so just the number of elements is given  */
+/* This may be OK for n-d arrays also */
+/* CHARACTER arrays may be represented in C as arrays of arrays of char or */
+/* as arrays of pointers to char (the _P variant) */
+#define F77_EXPORT_CHARACTER_ARRAY(C,LC,F,LF,N) \
+        {int f77dims[1];f77dims[0]=N;cnfExprta(C,LC,F,LF,1,f77dims);}
+#define F77_EXPORT_CHARACTER_ARRAY_P(C,F,LF,N) \
+        {int f77dims[1];f77dims[0]=N;cnfExprtap(C,F,LF,1,f77dims);}
+#define F77_EXPORT_DOUBLE_ARRAY(C,F,N) F=(F77_DOUBLE_TYPE *)C
+#define F77_EXPORT_INTEGER_ARRAY(C,F,N) F=(F77_INTEGER_TYPE *)C
+#define F77_EXPORT_LOGICAL_ARRAY(C,F,N) \
+        {int f77dims[1];f77dims[0]=N;cnfExpla(C,F,1,f77dims);}
+#define F77_EXPORT_REAL_ARRAY(C,F,N) F=(F77_REAL_TYPE *)C
+#define F77_EXPORT_BYTE_ARRAY(C,F,N) F=(F77_BYTE_TYPE *)C
+#define F77_EXPORT_WORD_ARRAY(C,F,N) F=(F77_WORD_TYPE *)C
+#define F77_EXPORT_UBYTE_ARRAY(C,F,N) F=(F77_UBYTE_TYPE *)C
+#define F77_EXPORT_UWORD_ARRAY(C,F,N) F=(F77_UWORD_TYPE * )C
+#define F77_EXPORT_POINTER_ARRAY(C,F,N) \
+     {int f77i;for (f77i=0;f77i<N;f77i++)F[f77i]=cnfFptr(C[f77i]);}
+#define F77_EXPORT_LOCATOR_ARRAY(C,F,N) \
+        {int f77i;for (f77i=0;f77i<N;f77i++)cnfExpch(C,F,DAT__SZLOC);}
+
+/* Import Fortran variables to C */
+#define F77_IMPORT_CHARACTER(F,L,C) cnfImprt(F,L,C)
+#define F77_IMPORT_DOUBLE(F,C) C=F
+#define F77_IMPORT_INTEGER(F,C) C=F
+#define F77_IMPORT_LOGICAL(F,C) C=F77_ISTRUE(F)
+#define F77_IMPORT_REAL(F,C) C=F
+#define F77_IMPORT_BYTE(F,C) C=F
+#define F77_IMPORT_WORD(F,C) C=F
+#define F77_IMPORT_UBYTE(F,C) C=F
+#define F77_IMPORT_UWORD(F,C) C=F
+#define F77_IMPORT_POINTER(F,C) C=cnfCptr(F)
+#define F77_IMPORT_LOCATOR(F,C) cnfImpch(F,DAT__SZLOC,C)
+
+/* Import Fortran arrays to C */
+/* Arrays are assumed to be 1-d so just the number of elements is given  */
+/* This may be OK for n-d arrays also */
+/* CHARACTER arrays may be represented in C as arrays of arrays of char or */
+/* as arrays of pointers to char (the _P variant) */
+#define F77_IMPORT_CHARACTER_ARRAY(F,LF,C,LC,N) \
+        {int f77dims[1];f77dims[0]=N;cnfImprta(F,LF,C,LC,1,f77dims);}
+#define F77_IMPORT_CHARACTER_ARRAY_P(F,LF,C,LC,N) \
+        {int f77dims[1];f77dims[0]=N;cnfImprtap(F,LF,C,LC,1,f77dims);}
+#define F77_IMPORT_DOUBLE_ARRAY(F,C,N)
+#define F77_IMPORT_INTEGER_ARRAY(F,C,N)
+#define F77_IMPORT_LOGICAL_ARRAY(F,C,N) \
+        {int f77dims[1];f77dims[0]=N;cnfImpla(F,C,1,f77dims);}
+#define F77_IMPORT_REAL_ARRAY(F,C,N)
+#define F77_IMPORT_BYTE_ARRAY(F,C,N)
+#define F77_IMPORT_WORD_ARRAY(F,C,N)
+#define F77_IMPORT_UBYTE_ARRAY(F,C,N)
+#define F77_IMPORT_UWORD_ARRAY(F,C,N)
+#define F77_IMPORT_POINTER_ARRAY(F,C,N) \
+        {int f77i;for (f77i=0;f77i<N;f77i++)C[f77i]=cnfCptr(F[f77i]);}
+#define F77_IMPORT_LOCATOR_ARRAY(F,C,N) \
+        {int f77i;for (f77i=0;f77i<N;f77i++)cnfImpch(F,DAT__SZLOC,C);}
+
+/*  ---  Call a FORTRAN routine  ---					    */
+
+#define F77_CALL(X)  F77_EXTERNAL_NAME(X)
+
+
+/*  ---  Execute code synchronised by the CNF global mutex                  */
+#define F77_LOCK(code) \
+   cnfLock(); \
+   code \
+   cnfUnlock();
+
+/*  ---  Pass arguments to a FORTRAN routine  ---			    */
+
+#define INTEGER_ARG(X)   X
+#define REAL_ARG(X)      X
+#define DOUBLE_ARG(X)    X
+#define LOGICAL_ARG(X)   X
+#define BYTE_ARG(X)      X
+#define WORD_ARG(X)      X
+#define UBYTE_ARG(X)     X
+#define UWORD_ARG(X)     X
+#define POINTER_ARG(X)   X
+#define CHARACTER_ARG(X) X
+#define TRAIL_ARG(X)     ,X##_length
+
+#define SUBROUTINE_ARG(X)  X
+#define INTEGER_FUNCTION_ARG(X)  X
+#define REAL_FUNCTION_ARG(X)  X
+#define DOUBLE_FUNCTION_ARG(X)  X
+#define LOGICAL_FUNCTION_ARG(X)  X
+#define CHARACTER_FUNCTION_ARG(X)  X
+#define BYTE_FUNCTION_ARG(X)  X
+#define WORD_FUNCTION_ARG(X)  X
+#define UBYTE_FUNCTION_ARG(X)  X
+#define UWORD_FUNCTION_ARG(X)  X
+#define POINTER_FUNCTION_ARG(X)  X
+
+#define INTEGER_ARRAY_ARG(X)   (F77_INTEGER_TYPE *)X
+#define REAL_ARRAY_ARG(X)      (F77_REAL_TYPE *)X
+#define DOUBLE_ARRAY_ARG(X)    (F77_DOUBLE_TYPE *)X
+#define LOGICAL_ARRAY_ARG(X)   (F77_LOGICAL_TYPE *)X
+#define BYTE_ARRAY_ARG(X)      (F77_BYTE_TYPE *)X
+#define WORD_ARRAY_ARG(X)      (F77_WORD_TYPE *)X
+#define UBYTE_ARRAY_ARG(X)     (F77_UBYTE_TYPE *)X
+#define UWORD_ARRAY_ARG(X)     (F77_UWORD_TYPE *)X
+#define POINTER_ARRAY_ARG(X)   (F77_POINTER_TYPE *)X
+#define CHARACTER_ARRAY_ARG(X) (F77_CHARACTER_ARRAY_ARG_TYPE *)X
+
+/* Put the 64-bit INT support in one place */
+#define F77_INTEGER8_TYPE     int64_t
+#define F77_INTEGER8_FUNCTION(X)   F77_INTEGER8_TYPE F77_EXTERNAL_NAME(X)
+#define INTEGER8(X)     F77_INTEGER8_TYPE *CNF_CONST X
+#define INTEGER8_FUNCTION(X)  F77_INTEGER8_TYPE (*X)()
+#define INTEGER8_ARRAY(X)     F77_INTEGER8_TYPE *CNF_CONST X
+#define GENPTR_INTEGER8(X)
+#define GENPTR_INTEGER8_ARRAY(X)
+#define GENPTR_INTEGER8_FUNCTION(X)
+#define DECLARE_INTEGER8(X) F77_INTEGER8_TYPE X
+#define DECLARE_INTEGER8_ARRAY(X,D) F77_INTEGER8_TYPE X[D]
+#define DECLARE_INTEGER8_ARRAY_DYN(X) F77_INTEGER8_TYPE *X = NULL
+#define F77_CREATE_INTEGER8_ARRAY(X,N)
+#define F77_ASSOC_INTEGER8_ARRAY(F,C) F=C
+#define F77_FREE_INTEGER8(X)
+#define F77_EXPORT_INTEGER8(C,F) F=C
+#define F77_EXPORT_INTEGER8_ARRAY(C,F,N) F=(F77_INTEGER8_TYPE *)C
+#define F77_IMPORT_INTEGER8(F,C) C=F
+#define F77_IMPORT_INTEGER8_ARRAY(F,C,N)
+#define INTEGER8_ARG(X)   X
+#define INTEGER8_FUNCTION_ARG(X)  X
+#define INTEGER8_ARRAY_ARG(X)   (F77_INTEGER8_TYPE *)X
+
+/* ------------------------ Non-ansi section ------------------------------ */
+
+/*  The difference between ANSI and non-ANSI compilers, as far as macro	    */
+/*  definition is concerned, is that non-ANSI compilers do not support the  */
+/*  token concatenation operator (##). To work around this, we use the fact */
+/*  that the null comment is preprocessed to produce no characters at all   */
+/*  by our non-ANSI compilers.						    */
+/*  This section does not deal with the fact that some non-ANSI compilers   */
+/*  cannot handle function prototypes. That is handled in the machine 	    */
+/*  specific sections.							    */
+
+#if !defined(__STDC__)
+
+/*  ---  External Name  ---						    */
+
+/*  Macro to define the name of a Fortran routine or common block. This	    */
+/*  ends in an underscore on many Unix systems.				    */
+
+#undef  F77_EXTERNAL_NAME
+#define F77_EXTERNAL_NAME(X) X/**/_
+
+
+/*  ---  Dummy Arguments  ---						    */
+
+/*  Macros to handle character dummy arguments.				    */
+
+#undef  TRAIL
+#define TRAIL(X) ,int X/**/_length
+
+
+/*  ---  Declare variables  ---						    */
+
+#undef  DECLARE_CHARACTER
+#define DECLARE_CHARACTER(X,L)         F77_CHARACTER_TYPE X[L]; \
+   const int X/**/_length = L
+#undef  DECLARE_CHARACTER_ARRAY
+#define DECLARE_CHARACTER_ARRAY(X,L,D) F77_CHARACTER_TYPE X[D][L]; \
+   const int X/**/_length = L
+#undef DECLARE_CHARACTER_DYN
+#define DECLARE_CHARACTER_DYN(X)   F77_CHARACTER_TYPE *X;\
+   int X/**/_length
+#undef DECLARE_CHARACTER_ARRAY_DYN
+#define DECLARE_CHARACTER_ARRAY_DYN(X)   F77_CHARACTER_TYPE *X;\
+   int X/**/_length
+#undef F77_CREATE_CHARACTER
+#define F77_CREATE_CHARACTER(X,L)  X=cnfCref(L);\
+   X/**/_length = L
+#undef F77_CREATE_CHARACTER_ARRAY
+#define F77_CREATE_CHARACTER_ARRAY(X,L,N) \
+        {int f77dims[1];f77dims[0]=N;X=cnfCrefa(L,1,f77dims);X/**/_length=L;}
+
+/*  ---  Pass arguments to a FORTRAN routine  ---			    */
+
+#undef  TRAIL_ARG
+#define TRAIL_ARG(X)     ,X/**/_length
+
+
+#endif  /* of non ANSI redefinitions					    */
+
+
+/* -----------------------------------------------------------------------  */
+
+/* The standard macros defined above are known to work with the following   */
+/* systems:								    */
+
+/*--------
+|   Sun   |
+---------*/
+
+/*  On SunOS, the ANSI definitions work with the acc and gcc compilers.     */
+/*  The cc compiler uses the non ANSI definitions. It also needs the K&R    */
+/*  definitions in the file kr.h.					    */
+/*  On Solaris, the standard definitions work with the cc compiler.	    */
+
+#if defined(sun)
+
+#if !defined(__STDC__)
+#if !defined(_F77_KR)
+#define _F77_KR
+#endif
+#endif
+
+#endif	/* Sun								    */
+
+/* -------------------- System dependent sections ------------------------- */
+
+/*------------
+|   VAX/VMS   |
+-------------*/
+
+/* Many macros need to be changed due to the way that VMS handles external  */
+/* names, passes character arguments and handles logical values.	    */
+
+
+#if defined(VMS)
+
+/*  ---  Data Types  ---						    */
+
+/*  Redefine the macro for the byte data type as signed is not valid syntax */
+/*  as the VMS compiler is not ANSI compliant.				    */
+
+#undef  F77_BYTE_TYPE
+#define F77_BYTE_TYPE      char
+
+
+/*  ---  External Names  ---						    */
+
+/*  Macro to define the name of a Fortran routine or common block.	    */
+/*  Fortran and C routines names are the same on VMS.			    */
+
+#undef  F77_EXTERNAL_NAME
+#define F77_EXTERNAL_NAME(X) X
+
+
+/*  ---  Dummy Arguments  ---						    */
+
+/*  Macros to handle character arguments.				    */
+/*  Character string arguments are pointers to character string descriptors */
+/*  and there are no trailing arguments.				    */
+
+#if( VMS != 0 )
+#include <descrip.h>
+#endif
+
+
+#undef  F77_CHARACTER_ARG_TYPE
+#define F77_CHARACTER_ARG_TYPE struct dsc$descriptor_s
+#undef  F77_CHARACTER_ARRAY_ARG_TYPE
+#define F77_CHARACTER_ARRAY_ARG_TYPE struct dsc$descriptor_a
+#undef  CHARACTER
+#define CHARACTER(X) F77_CHARACTER_ARG_TYPE *CNF_CONST X/**/_arg
+#undef  TRAIL
+#define TRAIL(X)
+#undef  CHARACTER_ARRAY
+#define CHARACTER_ARRAY(X)  F77_CHARACTER_ARRAY_ARG_TYPE *CNF_CONST X/**/_arg
+#undef  GENPTR_CHARACTER
+#define GENPTR_CHARACTER(X) \
+   F77_CHARACTER_TYPE *X = X/**/_arg->dsc$a_pointer; \
+   int X/**/_length = X/**/_arg->dsc$w_length;
+#undef  GENPTR_CHARACTER_ARRAY
+#define GENPTR_CHARACTER_ARRAY(X)   GENPTR_CHARACTER(X)
+
+
+/*  ---  Logical Values  ---						    */
+
+#undef  F77_TRUE
+#define F77_TRUE -1
+#undef  F77_ISTRUE
+#define F77_ISTRUE(X) ( (X)&1 )
+#undef  F77_ISFALSE
+#define F77_ISFALSE(X) ( ! ( (X)&1 ) )
+
+
+/*  ---  Common Blocks  ---						    */
+
+#undef  F77_BLANK_COMMON
+#define F77_BLANK_COMMON  $BLANK
+
+
+/*  ---  Declare Variables  ---						    */
+
+#undef  DECLARE_CHARACTER
+#define DECLARE_CHARACTER(X,L) \
+   F77_CHARACTER_TYPE X[L];    const int X/**/_length = L; \
+   F77_CHARACTER_ARG_TYPE X/**/_descr = \
+      { L, DSC$K_DTYPE_T, DSC$K_CLASS_S, X }; \
+   F77_CHARACTER_ARG_TYPE *X/**/_arg = &X/**/_descr
+#undef  DECLARE_CHARACTER_ARRAY
+#define DECLARE_CHARACTER_ARRAY(X,L,D) \
+   F77_CHARACTER_TYPE X[D][L]; const int X/**/_length = L; \
+   F77_CHARACTER_ARRAY_ARG_TYPE X/**/_descr = \
+      { L, DSC$K_DTYPE_T, DSC$K_CLASS_S, X }; \
+   F77_CHARACTER_ARRAY_ARG_TYPE *X/**/_arg = &X/**/_descr
+
+
+/*  ---  The dynamic allocation of character arguments  ---                 */
+#undef DECLARE_CHARACTER_DYN
+#define DECLARE_CHARACTER_DYN(X) int X/**/_length;\
+                                  F77_CHARACTER_ARG_TYPE *X/**/_arg;\
+                                  F77_CHARACTER_TYPE *X
+#undef DECLARE_CHARACTER_ARRAY_DYN
+#define DECLARE_CHARACTER_ARRAY_DYN(X) int X/**/_length;\
+                                  F77_CHARACTER_ARRAY_ARG_TYPE *X/**/_arg;\
+                                  F77_CHARACTER_TYPE *X
+#undef F77_CREATE_CHARACTER
+#define F77_CREATE_CHARACTER(X,L) X/**/_arg = cnfCref(L);\
+                                  X = X/**/_arg->dsc$a_pointer; \
+                                  X/**/_length = X/**/_arg->dsc$w_length
+#undef F77_CREATE_CHARACTER_ARRAY
+#define F77_CREATE_CHARACTER_ARRAY(X,L,N) \
+  {int f77dims[1];f77dims[0]=N;X/**/_arg=cnfCrefa(L,1,f77dims);X/**/_length=L;}
+#define F77_CREATE_CHARACTER_ARRAY_M(X,L,N,D) X/**/_arg = cnfCrefa(L,N,D);\
+                                  X = X/**/_arg->dsc$a_pointer; \
+                                  X/**/_length = X/**/_arg->dsc$w_length
+#undef F77_FREE_CHARACTER
+#define F77_FREE_CHARACTER(X)     cnfFreef( X/**/_arg )
+
+/*  ---  Pass arguments to a FORTRAN routine  ---			    */
+
+#undef  CHARACTER_ARG
+#define CHARACTER_ARG(X) X/**/_arg
+#undef  CHARACTER_ARRAY_ARG
+#define CHARACTER_ARRAY_ARG(X) X/**/_arg
+#undef  TRAIL_ARG
+#define TRAIL_ARG(X)
+
+#endif  /* VMS								    */
+
+/* -----------------------------------------------------------------------  */
+
+/*--------------------------
+|   DECstation Ultrix (cc)  |
+|   DECstation Ultrix (c89) |
+|   DECstation OSF/1        |
+|   Alpha OSF/1             |
+ --------------------------*/
+
+/* Do this complicated set of definitions as a single #if cannot be	    */
+/* continued across multiple lines.					    */
+
+#if defined(mips) && defined(ultrix)
+#define _dec_unix 1
+#endif
+#if defined(__mips) && defined(__ultrix)
+#define _dec_unix 1
+#endif
+#if defined(__mips__) && defined(__osf__)
+#define _dec_unix 1
+#endif
+#if defined(__alpha) && defined(__osf__)
+#define _dec_unix 1
+#endif
+
+#if _dec_unix
+
+/*  The macros for Ultrix are the same as the standard ones except for ones */
+/*  dealing with logical values. The ANSI definitions work with the c89	    */
+/*  compiler, and the non ANSI definitions work with the cc compiler.	    */
+/*  The same applies to DEC OSF/1, except that its cc compiler is ANSI	    */
+/*  compliant.								    */
+
+
+/*  ---  Logical Values  ---						    */
+
+/*  Redefine macros that evaluate to a C logical value, given a FORTRAN	    */
+/*  logical value. These definitions are only valid when used with the DEC  */
+/*  FORTRAN for RISC compiler. If you are using the earlier FORTRAN for	    */
+/*  RISC compiler from MIPS, then these macros should be deleted.	    */
+
+#undef  F77_TRUE
+#define F77_TRUE -1
+#undef  F77_ISTRUE
+#define F77_ISTRUE(X) ( (X)&1 )
+#undef  F77_ISFALSE
+#define F77_ISFALSE(X) ( ! ( (X)&1 ) )
+
+
+#endif  /* DEC Unix							    */
+
+/*
+*+
+*  Name:
+*     cnf.h
+
+*  Purpose:
+*     Function prototypes for cnf routines
+
+*  Language:
+*     ANSI C
+
+*  Type of Module:
+*     C include file
+
+*  Description:
+*     These are the prototype definitions for the functions in the CNF
+*     library. They are used used in mixing C and FORTRAN programs.
+
+*  Copyright:
+*     Copyright (C) 1991 Science & Engineering Research Council
+
+*  Authors:
+*     PMA: Peter Allan (Starlink, RAL)
+*     AJC: Alan Chipperfield (Starlink, RAL)
+*     {enter_new_authors_here}
+
+*  History:
+*     23-MAY-1991 (PMA):
+*        Original version.
+*     12-JAN-1996 (AJC):
+*        Add cnf_cref and cnf_freef
+*     14-JUN-1996 (AJC):
+*        Add cnf_crefa, imprta, exprta
+*                crela, impla, expla
+*     18-JUL-1996 (AJC):
+*        Add impch and expch
+*     17-MAR-1998 (AJC):
+*        Add imprtap and exprtap
+*     {enter_changes_here}
+
+*  Bugs:
+*     {note_any_bugs_here}
+
+*-
+------------------------------------------------------------------------------
+*/
+void cnfInitRTL( int, char** );
+void *cnfCalloc( size_t, size_t );
+void cnfCopyf( const char *source_f, int source_len, char *dest_f,
+                int dest_len );
+void *cnfCptr( F77_POINTER_TYPE );
+char *cnfCreat( int length );
+F77_CHARACTER_ARG_TYPE *cnfCref( int length );
+F77_CHARACTER_ARG_TYPE *cnfCrefa( int length, int ndims, const int *dims );
+char *cnfCreib( const char *source_f, int source_len );
+char *cnfCreim( const char *source_f, int source_len );
+F77_LOGICAL_TYPE *cnfCrela( int ndims, const int *dims );
+void cnfExpch( const char *source_c, char *dest_f, int nchars );
+void cnfExpla( const int *source_c, F77_LOGICAL_TYPE *dest_f, int ndims,
+                const int *dims );
+void cnfExpn( const char *source_c, int max, char *dest_f, int dest_len );
+void cnfExprt( const char *source_c, char *dest_f, int dest_len );
+void cnfExprta( const char *source_c, int source_len, char *dest_f,
+                 int dest_len, int ndims, const int *dims );
+void cnfExprtap( char *const *source_c, char *dest_f, int dest_len,
+                  int ndims, const int *dims );
+F77_POINTER_TYPE cnfFptr( void *cpointer );
+void cnfFree( void * );
+void cnfFreef( F77_CHARACTER_ARG_TYPE *temp );
+void cnfImpb( const char *source_f, int source_len, char *dest_c );
+void cnfImpbn( const char *source_f, int source_len, int max, char *dest_c );
+void cnfImpch( const char *source_f, int nchars, char *dest_c );
+void cnfImpla( const F77_LOGICAL_TYPE *source_f, int *dest_c,
+                int ndims, const int *dims );
+void cnfImpn( const char *source_f, int source_len, int max, char *dest_c );
+void cnfImprt( const char *source_f, int source_len, char *dest_c );
+void cnfImprta( const char *source_f, int source_len, char *dest_c,
+                 int dest_len, int ndims, const int *dims );
+void cnfImprtap( const char *source_f, int source_len, char *const *dest_c,
+                  int dest_len, int ndims, const int *dims );
+int cnfLenc( const char *source_c );
+int cnfLenf( const char *source_f, int source_len );
+void *cnfMalloc( size_t );
+void *cnfRealloc( void *, size_t );
+int cnfRegp( void * );
+void cnfUregp( void * );
+void cnfLock( void );
+void cnfUnlock( void );
+#endif
+
+#ifndef CNF_OLD_DEFINED
+#define CNF_OLD_DEFINED
+/* Define old names to be new names */
+#define cnf_calloc cnfCalloc
+#define cnf_copyf cnfCopyf
+#define cnf_cptr cnfCptr
+#define cnf_creat cnfCreat
+#define cnf_cref cnfCref
+#define cnf_crefa cnfCrefa
+#define cnf_creib cnfCreib
+#define cnf_creim cnfCreim
+#define cnf_crela cnfCrela
+#define cnf_expch cnfExpch
+#define cnf_expla cnfExpla
+#define cnf_expn cnfExpn
+#define cnf_exprt cnfExprt
+#define cnf_exprta cnfExprta
+#define cnf_exprtap cnfExprtap
+#define cnf_fptr cnfFptr
+#define cnf_free cnfFree
+#define cnf_freef cnfFreef
+#define cnf_impb cnfImpb
+#define cnf_impbn cnfImpbn
+#define cnf_impch cnfImpch
+#define cnf_impla cnfImpla
+#define cnf_impn cnfImpn
+#define cnf_imprt cnfImprt
+#define cnf_imprta cnfImprta
+#define cnf_imprtap cnfImprtap
+#define cnf_lenc cnfLenc
+#define cnf_lenf cnfLenf
+#define cnf_malloc cnfMalloc
+#define cnf_regp cnfRegp
+#define cnf_uregp cnfUregp
+
+#endif /* CNF_MACROS */
diff --git a/ast/f77.h.in b/ast/f77.h.in
new file mode 100644
index 0000000..3a886fc
--- /dev/null
+++ b/ast/f77.h.in
@@ -0,0 +1,1096 @@
+/*
+*+
+*  Name:
+*     f77.h and cnf.h
+
+*  Purpose:
+*     C - FORTRAN interace macros and prototypes
+
+*  Language:
+*     C (part ANSI, part not)
+
+*  Type of Module:
+*     C include file
+
+*  Description:
+*     For historical reasons two files, F77.h and cnf.h are required
+*     but the have now been combined and for new code, only one is
+*     necessary.
+*
+*     This file defines the macros needed to write C functions that are
+*     designed to be called from FORTRAN programs, and to do so in a
+*     portable way. Arguments are normally passed by reference from a
+*     FORTRAN program, and so the F77 macros arrange for a pointer to
+*     all arguments to be available. This requires no work on most
+*     machines, but will actually generate the pointers on a machine
+*     that passes FORTRAN arguments by value.
+
+*  Notes:
+*     -  Macros are provided to handle the conversion of logical data
+*        values between the way that FORTRAN represents a value and the
+*        way that C represents it.
+*     -  Macros are provided to convert variables between the FORTRAN and
+*        C method of representing them. In most cases there is no
+*        conversion required, the macros just arrange for a pointer to
+*        the FORTRAN variable to be set appropriately. The possibility that
+*        FORTRAN and C might use different ways of representing integer
+*        and floating point values is considered remote, the macros are
+*        really only there for completeness and to assist in the automatic
+*        generation of C interfaces.
+*     -  For character variables the macros convert between
+*        the FORTRAN method of representing them (fixed length, blank
+*        filled strings) and the C method (variable length, null
+*        terminated strings) using calls to the CNF functions.
+
+*  Implementation Deficiencies:
+*     -  The macros support the K&R style of function definition, but
+*        this file may not work with all K&R compilers as it contains
+*        "#if defined" statements. These could be replaced with #ifdef's
+*        if necessary. This has not been done as is would make the code
+*        less clear and the need for support for K&R sytle definitions
+*        should disappear as ANSI compilers become the default.
+
+*  Copyright:
+*     Copyright (C) 1991, 1993 Science & Engineering Research Council.
+*     Copyright (C) 2006 Particle Physics and Astronomy Research Council.
+*     Copyright (C) 2007,2008 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software; you can redistribute it and/or
+*     modify it under the terms of the GNU General Public License as
+*     published by the Free Software Foundation; either version 2 of
+*     the License, or (at your option) any later version.
+*
+*     This program is distributed in the hope that it will be
+*     useful,but WITHOUT ANY WARRANTY; without even the implied
+*     warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+*     PURPOSE. See the GNU General Public License for more details.
+*
+*     You should have received a copy of the GNU General Public License
+*     along with this program; if not, write to the Free Software
+*     Foundation, Inc., 51 Franklin Street,Fifth Floor, Boston, MA
+*     02110-1301, USA
+
+*  Authors:
+*     PMA: Peter Allan (Starlink, RAL)
+*     AJC: Alan Chipperfield (Starlink, RAL)
+*     TIMJ: Tim Jenness (JAC)
+*     PWD: Peter W. Draper (JAC, Durham University)
+*     {enter_new_authors_here}
+
+*  History:
+*     23-MAY-1991 (PMA):
+*        Original version.
+*     19-JUN-1991 (PMA):
+*        Removed VMS versions of IM(EX)PORT_LOGICAL macros that tried
+*        to convert data representations.
+*     24-JUN-1991 (PMA):
+*        Changed the names of IMPORT macros to GENPTR.
+*        Removed the EXPORT macros.
+*     27-JUN-1991 (PMA):
+*        Modified DECstation specific stuff to allow use of the c89
+*        compiler.
+*      8-JUL-1991 (PMA):
+*        Added macros to call FORTRAN from C.
+*     16-OCT-1991 (PMA):
+*        Remove type_ARRAY2 definitions.
+*        Remove the length argument from CHARACTER_ARRAY and the
+*        dimension specifier from GENPTR_type_ARRAY.
+*        Add extra brackets to F77_ISFALSE and F77_ISTRUE.
+*     25-OCT-1991 (PMA):
+*        Changed "if defined(sun4)" to "if defined(sun)"
+*     2-JUN-1992 (PMA):
+*        Changed "if defined(mips)" to "if defined(ultrix)" to prevent
+*        those definitions being used on a Silicon Graphics machine.
+*     11-JUN-1992 (PMA):
+*        Changed "if defined(ultrix)" back to "if defined(mips)" so that
+*        it still works on OSF/1 on a DECstation.
+*        Add support for general non-ANSI compilers, but not basic K&R
+*        ones.
+*     12-JUN-1992 (PMA):
+*        Change declaration of dummy scalar arguments to be const
+*        pointers.  Change declaration of dummy array arguments to be
+*        const pointers.
+*     5-JAN-1993 (PMA):
+*        Changed "if defined(mips)" so that it will recognise a
+*        DECstation running Ultrix or OSF/1, but not a Silicon Graphics
+*        workstation.
+*        Change the definition of F77_BYTE_TYPE to add "signed".
+*        Redefine this on VMS where signed is invalid syntax.
+*        Add new types of UBYTE and UWORD.
+*     8-JAN-1993 (PMA):
+*        Fix bug in the definition of CHARACTER_RETURN_VALUE. There was
+*        an extraneous space.
+*        Add a macro F77_POINTER_TYPE and use it to define POINTER.
+*     13-JAN-1993 (PMA):
+*        Start to add support for K&R function definitions. These are
+*        done on a per machine basis.
+*     16-APR-1993 (PMA):
+*        Change the definition of F77_POINTER_TYPE from int to unsigned
+*        int.
+*     7-MAY-1993 (PMA):
+*        Change from using a null comment as a token concatenation
+*        operator to using the internal macro _f77_x on non-ANSI
+*        systems.
+*     10-MAY-1993 (PMA):
+*        Finish adding K&R support. This will form version 2.0 of F77.
+*     10-MAY-1993 (PMA):
+*        Add support for Alpha OSF/1.
+*     9-JUL-1993 (PMA):
+*        Add further POINTER macros: POINTER_ARRAY,
+*        GENPTR_POINTER_ARRAY, DECLARE_POINTER, DECLARE_POINTER_ARRAY,
+*        POINTER_ARG, POINTER_ARRAY_ARG, F77_POINTER_FUNCTION,
+*        KR_POINTER_ARRAY.
+*     24-AUG-1993 (PMA):
+*        Add const to the VMS definitions of CHARACTER and CHARACTER_ARRAY.
+*     3-NOV-1993 (PMA):
+*        Remove K&R stuff to a separate file.
+*        Released on Unix as version 2.0 of CNF.
+*     11-NOV-1993 (PMA):
+*        Return to using the null comment to concatenate text on non-ANSI
+*        systems as _f77_x caused problems with the c89 -common flag on
+*        DECstations.
+*     23-JAN-1996 (AJC):
+*        Add SUBROUTINE, type_FUNCTION, SUBROUTINE_ARG,
+*        type_FUNCTION_ARG, GENPTR_SUBROUTINE and GENPTR_type_FUNCTION
+*        required for passed subroutine and function name.
+*     29-JAN-1996 (AJC):
+*        Add the dynamic CHARACTER_ macros
+*        and CHARACTER_ARG_TYPE
+*     22-FEB-1996 (AJC):
+*        Add CHARACTER_RETURN_ARG
+*     23-MAY-1996 (AJC):
+*        Add DECLARE_CHARACTER_ARRAY_DYN
+*            F77_CREATE_CHARACTER_ARRAY
+*            F77_CHARACTER_ARG_TYPE
+*     14-JUN-1996 (AJC):
+*        Add DECLARE_LOGICAL_ARRAY_DYN
+*            F77_CREATE_LOGICAL_ARRAY
+*     21-JUN-1996 (AJC):
+*        Add cast to _ARRAY_ARGs to allow multidimensional arrays
+*     17-MAR-1998 (AJC):
+*        Add DECLARE, CREATE and FREE dynamic array macros for all types
+*        Changed CREATE_CHARACTER_ARRAY and CREATE_LOGICAL_ARRAY to use
+*         number of elements rather than dimensions.
+*        Add IMPORT, EXPORT and ASSOC macros
+*     22-JUL-1998 (AJC):
+*        Combined F77.h and cnf.h
+*     23-SEP-1998 (AJC):
+*        Input strings for cnf -> const char *
+*        Input int arrays for cnf -> const int *
+*      4-NOV-1998 (AJC):
+*        Bring cnf prototypes in line with .c routines
+*      8-FEB-1999 (AJC):
+*        Added cnf_mem stuff
+*      9-FEB-1999 (AJC):
+*        Use cnf_cptr/fptr for IMPORT/EXPORT_POINTER
+*     16-FEB-1999 (AJC):
+*        Added missing cnf_fptr prototype
+*     23-JUN-1999 (AJC):
+*        Change cnf_name to cnfName
+*        and add macros for cnf_name
+*      1-DEC-1999 (AJC):
+*        Add define cnf_free
+*      7-JAN-2000 (AJC):
+*        Correct omission of F77_ASSOC_UBYTE_ARRAY
+*        Correct F77_EXPORT_UWORD_ARRAY
+*      25-AUG-2005 (TIMJ):
+*        Add cnfInitRTL
+*      23-FEB-2006 (TIMJ):
+*        Add cnfRealloc
+*        Use starMalloc rather than malloc in F77_CREATE_POINTER_ARRAY
+*        (since it needs to match what goes on in cnfFree)
+*      21-JUN-2006 (PWD):
+*        Changed to use a different return type for REAL functions. This
+*        effects g77 under 64-bit, when the f2c bindings expect the return
+*        value of a REAL function to be a double, not a float.
+*      25-SEP-2006 (PWD):
+*        Introduced F77_CREATE_IMPORT_CHARACTER. Match length of
+*        F77_CREATE_CHARACTER to result from cnfCref.
+*      13-JUL-2007 (PWD):
+*        Parameterise the type of Fortran character string lengths. Can
+*        be long.
+*      7-OCT-2008 (TIMJ):
+*        Initialise pointers.
+*      11-MAY-2011 (DSB):
+*        Added F77_LOCK
+*     {enter_further_changes_here}
+*
+
+*  Bugs:
+*     {note_any_bugs_here}
+
+*-
+------------------------------------------------------------------------------
+*/
+#if !defined(CNF_MACROS)
+#define CNF_MACROS
+
+#include <stdlib.h>
+#include <string.h>
+/*  This initial sections defines values for all macros. These are the	    */
+/*  values that are generally appropriate to an ANSI C compiler on Unix.    */
+/*  For macros that have different values on other systems, the macros	    */
+/*  should be undefined and then redefined in the system specific sections. */
+/*  At the end of this section, some macros are redefined if the compiler   */
+/*  is non-ANSI.							    */
+
+#if defined(__STDC__) || defined(VMS)
+#define CNF_CONST const
+#else
+#define CNF_CONST
+#endif
+
+/*  -----  Macros common to calling C from FORTRAN and FORTRAN from C  ---- */
+
+
+/*  ---  External Names  ---						    */
+
+/*  Macro to define the name of a Fortran routine or common block. This	    */
+/*  ends in an underscore on many Unix systems.				    */
+
+#define F77_EXTERNAL_NAME(X) X ## _
+
+
+/*  ---  Logical Values  ---						    */
+
+/*  Define the values that are used to represent the logical values TRUE    */
+/*  and FALSE in Fortran.						    */
+
+#define F77_TRUE 1
+#define F77_FALSE 0
+
+/*  Define macros that evaluate to C logical values, given a FORTRAN	    */
+/*  logical value.							    */
+
+#define F77_ISTRUE(X) ( X )
+#define F77_ISFALSE(X) ( !( X ) )
+
+
+/*  ---  Common Blocks  ---						    */
+
+/*  Macros used in referring to FORTRAN common blocks.			    */
+
+#define F77_BLANK_COMMON                @BLANK_COMMON_SYMBOL@
+#define F77_NAMED_COMMON(B)             F77_EXTERNAL_NAME(B)
+
+
+
+/*  ------------------  Calling C from FORTRAN  --------------------------- */
+
+
+/*  ---  Data Types  ---						    */
+
+/*  Define macros for all the Fortran data types (except COMPLEX, which is  */
+/*  not handled by this package).					    */
+
+#define F77_INTEGER_TYPE   int
+#define F77_REAL_TYPE      float
+#define F77_REAL_FUNCTION_TYPE      @REAL_FUNCTION_TYPE@
+#define F77_DOUBLE_TYPE    double
+#define F77_LOGICAL_TYPE   int
+#define F77_CHARACTER_TYPE char
+#define F77_BYTE_TYPE      signed char
+#define F77_WORD_TYPE      short int
+#define F77_UBYTE_TYPE     unsigned char
+#define F77_UWORD_TYPE     unsigned short int
+
+/*  Define macros for the type of a CHARACTER and CHARACTER_ARRAY argument  */
+#define F77_CHARACTER_ARG_TYPE char
+#define F77_CHARACTER_ARRAY_ARG_TYPE char
+
+/*  Define a macro to use when passing arguments that STARLINK FORTRAN	    */
+/*  treats as a pointer. From the point of view of C, this type should be   */
+/*  (void *), but it is declared as type unsigned int as we actually pass   */
+/*  an INTEGER from the FORTRAN routine. The distinction is important for   */
+/*  architectures where the size of an INTEGER is not the same as the size  */
+/*  of a pointer.							    */
+
+#define F77_POINTER_TYPE   unsigned int
+
+
+/*  ---  Subroutine Names  ---						    */
+
+/* This declares that the C function returns a value of void.		    */
+
+#define F77_SUBROUTINE(X)  void F77_EXTERNAL_NAME(X)
+
+
+/*  ---  Function Names  ---						    */
+
+/*  Macros to define the types and names of functions that return values.   */
+/*  Due the the different ways that function return values could be	    */
+/*  implemented, it is better not to use functions, but to stick to using   */
+/*  subroutines.							    */
+
+/*  Character functions are implemented, but in a way that cannot be	    */
+/*  guaranteed to be portable although it will work on VMS, SunOS, Ultrix   */
+/*  and DEC OSF/1. It would be better to return the character value as a    */
+/*  subroutine argument where possible, rather than use a character	    */
+/*  function.								    */
+
+#define F77_INTEGER_FUNCTION(X)   F77_INTEGER_TYPE F77_EXTERNAL_NAME(X)
+#define F77_REAL_FUNCTION(X)      F77_REAL_FUNCTION_TYPE F77_EXTERNAL_NAME(X)
+#define F77_DOUBLE_FUNCTION(X)    F77_DOUBLE_TYPE F77_EXTERNAL_NAME(X)
+#define F77_LOGICAL_FUNCTION(X)   F77_LOGICAL_TYPE F77_EXTERNAL_NAME(X)
+#define F77_CHARACTER_FUNCTION(X) void F77_EXTERNAL_NAME(X)
+#define F77_BYTE_FUNCTION(X)      F77_BYTE_TYPE F77_EXTERNAL_NAME(X)
+#define F77_WORD_FUNCTION(X)      F77_WORD_TYPE F77_EXTERNAL_NAME(X)
+#define F77_UBYTE_FUNCTION(X)     F77_UBYTE_TYPE F77_EXTERNAL_NAME(X)
+#define F77_UWORD_FUNCTION(X)     F77_UWORD_TYPE F77_EXTERNAL_NAME(X)
+#define F77_POINTER_FUNCTION(X)   F77_POINTER_TYPE F77_EXTERNAL_NAME(X)
+
+
+/*  ---  Character return value for a function  ---			    */
+
+#define CHARACTER_RETURN_VALUE(X) CHARACTER(X) TRAIL(X)
+#define CHARACTER_RETURN_ARG(X) CHARACTER_ARG(X) TRAIL_ARG(X)
+
+/*  ---  Dummy Arguments  ---						    */
+
+/*  Macros for defining subroutine arguments. All these macros take a	    */
+/*  single argument; the name of the parameter. On most systems, a numeric  */
+/*  argument is passed as a pointer.					    */
+
+#define INTEGER(X)     F77_INTEGER_TYPE *CNF_CONST X
+#define REAL(X)        F77_REAL_TYPE    *CNF_CONST X
+#define DOUBLE(X)      F77_DOUBLE_TYPE  *CNF_CONST X
+#define LOGICAL(X)     F77_LOGICAL_TYPE *CNF_CONST X
+#define BYTE(X)        F77_BYTE_TYPE    *CNF_CONST X
+#define WORD(X)        F77_WORD_TYPE    *CNF_CONST X
+#define UBYTE(X)       F77_UBYTE_TYPE   *CNF_CONST X
+#define UWORD(X)       F77_UWORD_TYPE   *CNF_CONST X
+
+/*  Pointer arguments. Define a pointer type for passing pointer values	    */
+/*  between subroutines.						    */
+
+#define POINTER(X)     F77_POINTER_TYPE *CNF_CONST X
+
+/*  EXTERNAL arguments. Define a passed subroutine or function name */
+#define SUBROUTINE(X)  void (*X)()
+#define INTEGER_FUNCTION(X)  F77_INTEGER_TYPE (*X)()
+#define REAL_FUNCTION(X)  F77_REAL_TYPE (*X)()
+#define DOUBLE_FUNCTION(X)  F77_DOUBLE_TYPE (*X)()
+#define LOGICAL_FUNCTION(X)  F77_LOGICAL_TYPE (*X)()
+#define CHARACTER_FUNCTION(X)  F77_CHARACTER_TYPE (*X)()
+#define BYTE_FUNCTION(X)  F77_BYTE_TYPE (*X)()
+#define WORD_FUNCTION(X)  F77_WORD_TYPE (*X)()
+#define UBYTE_FUNCTION(X)  F77_UBYTE_TYPE (*X)()
+#define UWORD_FUNCTION(X)  F77_UWORD_TYPE (*X)()
+#define POINTER_FUNCTION(X)  F77_POINTER_TYPE (*X)()
+
+/*  Array arguments.							    */
+
+#define INTEGER_ARRAY(X)     F77_INTEGER_TYPE *CNF_CONST X
+#define REAL_ARRAY(X)        F77_REAL_TYPE    *CNF_CONST X
+#define DOUBLE_ARRAY(X)      F77_DOUBLE_TYPE  *CNF_CONST X
+#define LOGICAL_ARRAY(X)     F77_LOGICAL_TYPE *CNF_CONST X
+#define BYTE_ARRAY(X)        F77_BYTE_TYPE    *CNF_CONST X
+#define WORD_ARRAY(X)        F77_WORD_TYPE    *CNF_CONST X
+#define UBYTE_ARRAY(X)       F77_UBYTE_TYPE   *CNF_CONST X
+#define UWORD_ARRAY(X)       F77_UWORD_TYPE   *CNF_CONST X
+
+#define POINTER_ARRAY(X)     F77_POINTER_TYPE *CNF_CONST X
+
+/*  Macros to handle character arguments.				    */
+
+/*  Character arguments can be passed in many ways. The purpose of these    */
+/*  macros and the GENPTR_CHARACTER macro (defined in the next section) is  */
+/*  to generate a pointer to a character variable called ARG and an integer */
+/*  ARG_length containing the length of ARG. If these two variables are	    */
+/*  available directly from the argument list of the routine, then the	    */
+/*  GENPTR_CHARACTER macro is null, otherwise it works on intermediate	    */
+/*  variables.								    */
+
+#define CHARACTER(X)             F77_CHARACTER_TYPE *CNF_CONST X
+#define TRAIL(X)                 ,@TRAIL_TYPE@ X ## _length
+#define CHARACTER_ARRAY(X)       F77_CHARACTER_TYPE *CNF_CONST X
+
+
+/*  ---  Getting Pointers to Arguments  ---				    */
+
+/*  Macros that ensure that a pointer to each argument is available for the */
+/*  programmer to use. Usually this means that these macros are null. On    */
+/*  VMS, a pointer to a character variable has to be generated. If a	    */
+/*  particular machine were to pass arguments by reference, rather than by  */
+/*  value, then these macros would construct the appropriate pointers.	    */
+
+#define GENPTR_INTEGER(X)
+#define GENPTR_REAL(X)
+#define GENPTR_DOUBLE(X)
+#define GENPTR_CHARACTER(X)
+#define GENPTR_LOGICAL(X)
+#define GENPTR_BYTE(X)
+#define GENPTR_WORD(X)
+#define GENPTR_UBYTE(X)
+#define GENPTR_UWORD(X)
+#define GENPTR_POINTER(X)
+
+#define GENPTR_INTEGER_ARRAY(X)
+#define GENPTR_REAL_ARRAY(X)
+#define GENPTR_DOUBLE_ARRAY(X)
+#define GENPTR_CHARACTER_ARRAY(X)
+#define GENPTR_LOGICAL_ARRAY(X)
+#define GENPTR_BYTE_ARRAY(X)
+#define GENPTR_WORD_ARRAY(X)
+#define GENPTR_UBYTE_ARRAY(X)
+#define GENPTR_UWORD_ARRAY(X)
+#define GENPTR_POINTER_ARRAY(X)
+
+#define GENPTR_SUBROUTINE(X)
+#define GENPTR_INTEGER_FUNCTION(X)
+#define GENPTR_REAL_FUNCTION(X)
+#define GENPTR_DOUBLE_FUNCTION(X)
+#define GENPTR_CHARACTER_FUNCTION(X)
+#define GENPTR_LOGICAL_FUNCTION(X)
+#define GENPTR_BYTE_FUNCTION(X)
+#define GENPTR_WORD_FUNCTION(X)
+#define GENPTR_UBYTE_FUNCTION(X)
+#define GENPTR_UWORD_FUNCTION(X)
+#define GENPTR_POINTER_FUNCTION(X)
+
+
+
+/*  ------------------  Calling FORTRAN from C  --------------------------- */
+
+
+/*  ---  Declare variables  ---						    */
+
+#define DECLARE_INTEGER(X) F77_INTEGER_TYPE X
+#define DECLARE_REAL(X)    F77_REAL_TYPE X
+#define DECLARE_DOUBLE(X)  F77_DOUBLE_TYPE X
+#define DECLARE_LOGICAL(X) F77_LOGICAL_TYPE X
+#define DECLARE_BYTE(X)    F77_BYTE_TYPE X
+#define DECLARE_WORD(X)    F77_WORD_TYPE X
+#define DECLARE_UBYTE(X)   F77_UBYTE_TYPE X
+#define DECLARE_UWORD(X)   F77_UWORD_TYPE X
+
+#define DECLARE_POINTER(X) F77_POINTER_TYPE X
+
+#define DECLARE_CHARACTER(X,L) F77_CHARACTER_TYPE X[L]; \
+   const int X##_length = L
+
+
+/*  ---  Declare arrays ---						    */
+
+#define DECLARE_INTEGER_ARRAY(X,D) F77_INTEGER_TYPE X[D]
+#define DECLARE_REAL_ARRAY(X,D)    F77_REAL_TYPE X[D]
+#define DECLARE_DOUBLE_ARRAY(X,D)  F77_DOUBLE_TYPE X[D]
+#define DECLARE_LOGICAL_ARRAY(X,D) F77_LOGICAL_TYPE X[D]
+#define DECLARE_BYTE_ARRAY(X,D)    F77_BYTE_TYPE X[D]
+#define DECLARE_WORD_ARRAY(X,D)    F77_WORD_TYPE X[D]
+#define DECLARE_UBYTE_ARRAY(X,D)   F77_UBYTE_TYPE X[D]
+#define DECLARE_UWORD_ARRAY(X,D)   F77_UWORD_TYPE X[D]
+#define DECLARE_POINTER_ARRAY(X,D) F77_POINTER_TYPE X[D]
+#define DECLARE_CHARACTER_ARRAY(X,L,D) F77_CHARACTER_TYPE X[D][L]; \
+   const int X##_length = L
+
+/*  ---  Declare and construct dynamic CHARACTER arguments ---                      */
+#define DECLARE_CHARACTER_DYN(X)   F77_CHARACTER_TYPE *X = NULL;\
+   int X##_length = 0
+#define F77_CREATE_CHARACTER(X,L)  X=cnfCref(L);\
+   X##_length = (L>0?L:1)
+
+/* Declare Dynamic Fortran arrays */
+#define DECLARE_INTEGER_ARRAY_DYN(X) F77_INTEGER_TYPE *X = NULL
+#define DECLARE_REAL_ARRAY_DYN(X)    F77_REAL_TYPE *X = NULL
+#define DECLARE_DOUBLE_ARRAY_DYN(X)  F77_DOUBLE_TYPE *X = NULL
+#define DECLARE_LOGICAL_ARRAY_DYN(X) F77_LOGICAL_TYPE *X = NULL
+#define DECLARE_BYTE_ARRAY_DYN(X)    F77_BYTE_TYPE *X = NULL
+#define DECLARE_WORD_ARRAY_DYN(X)    F77_WORD_TYPE *X = NULL
+#define DECLARE_UBYTE_ARRAY_DYN(X)   F77_UBYTE_TYPE *X = NULL
+#define DECLARE_UWORD_ARRAY_DYN(X)   F77_UWORD_TYPE *X = NULL
+#define DECLARE_POINTER_ARRAY_DYN(X) F77_POINTER_TYPE *X = NULL
+#define DECLARE_CHARACTER_ARRAY_DYN(X)   F77_CHARACTER_TYPE *X = NULL;\
+   int X##_length = 0
+
+/* Create arrays dynamic Fortran arrays for those types which require */
+/* Separate space for Fortran and C arrays                            */
+/* Character and logical are already defined                          */
+/* For most types there is nothing to do                              */
+#define F77_CREATE_CHARACTER_ARRAY(X,L,N) \
+        {int f77dims[1];f77dims[0]=N;X=cnfCrefa(L,1,f77dims);X##_length=L;}
+#define F77_CREATE_CHARACTER_ARRAY_M(X,L,N,D)  X=cnfCrefa(L,N,D);\
+   X##_length = L
+#define F77_CREATE_LOGICAL_ARRAY(X,N) \
+        {int f77dims[1];f77dims[0]=N;X=cnfCrela(1,f77dims);}
+#define F77_CREATE_LOGICAL_ARRAY_M(X,N,D) X=cnfCrela(N,D)
+#define F77_CREATE_INTEGER_ARRAY(X,N)
+#define F77_CREATE_REAL_ARRAY(X,N)
+#define F77_CREATE_DOUBLE_ARRAY(X,N)
+#define F77_CREATE_BYTE_ARRAY(X,N)
+#define F77_CREATE_UBYTE_ARRAY(X,N)
+#define F77_CREATE_WORD_ARRAY(X,N)
+#define F77_CREATE_UWORD_ARRAY(X,N)
+#define F77_CREATE_POINTER_ARRAY(X,N)\
+        X=(F77_POINTER_TYPE *) malloc(N*sizeof(F77_POINTER_TYPE))
+
+/* Associate Fortran arrays with C arrays                             */
+/* These macros ensure that there is space somewhere for the Fortran  */
+/* array. They are complemetary to the CREATE_type_ARRAY macros       */
+#define F77_ASSOC_CHARACTER_ARRAY(F,C)
+#define F77_ASSOC_LOGICAL_ARRAY(F,C)
+#define F77_ASSOC_INTEGER_ARRAY(F,C) F=C
+#define F77_ASSOC_REAL_ARRAY(F,C) F=C
+#define F77_ASSOC_DOUBLE_ARRAY(F,C) F=C
+#define F77_ASSOC_BYTE_ARRAY(F,C) F=C
+#define F77_ASSOC_UBYTE_ARRAY(F,C) F=C
+#define F77_ASSOC_WORD_ARRAY(F,C) F=C
+#define F77_ASSOC_UWORD_ARRAY(F,C) F=C
+#define F77_ASSOC_POINTER_ARRAY(F,C)
+
+/* Free created dynamic arrays */
+/* Character and logical are already defined */
+/* For most types there is nothing to do */
+#define F77_FREE_INTEGER(X)
+#define F77_FREE_REAL(X)
+#define F77_FREE_DOUBLE(X)
+#define F77_FREE_BYTE(X)
+#define F77_FREE_UBYTE(X)
+#define F77_FREE_WORD(X)
+#define F77_FREE_UWORD(X)
+#define F77_FREE_POINTER(X) cnfFree((void *)X);
+#define F77_FREE_CHARACTER(X) cnfFreef( X )
+#define F77_FREE_LOGICAL(X) cnfFree( (char *)X )
+
+/*  ---  IMPORT and EXPORT of values  --- */
+/* Export C variables to Fortran variables */
+#define F77_EXPORT_CHARACTER(C,F,L) cnfExprt(C,F,L)
+#define F77_EXPORT_DOUBLE(C,F) F=C
+#define F77_EXPORT_INTEGER(C,F) F=C
+#define F77_EXPORT_LOGICAL(C,F) F=C?F77_TRUE:F77_FALSE
+#define F77_EXPORT_REAL(C,F) F=C
+#define F77_EXPORT_BYTE(C,F) F=C
+#define F77_EXPORT_WORD(C,F) F=C
+#define F77_EXPORT_UBYTE(C,F) F=C
+#define F77_EXPORT_UWORD(C,F) F=C
+#define F77_EXPORT_POINTER(C,F) F=cnfFptr(C)
+#define F77_EXPORT_LOCATOR(C,F) cnfExpch(C,F,DAT__SZLOC)
+
+/* Allow for character strings to be NULL, protects strlen. Note this
+ * does not allow lengths to differ. */
+#define F77_CREATE_EXPORT_CHARACTER(C,F) \
+     if (C) { \
+        F77_CREATE_CHARACTER(F,strlen(C)); \
+        F77_EXPORT_CHARACTER(C,F,F##_length); \
+     } else { \
+        F77_CREATE_CHARACTER(F,1); \
+        F77_EXPORT_CHARACTER(" ",F,F##_length); \
+     }
+
+/* Export C arrays to Fortran */
+/* Arrays are assumed to be 1-d so just the number of elements is given  */
+/* This may be OK for n-d arrays also */
+/* CHARACTER arrays may be represented in C as arrays of arrays of char or */
+/* as arrays of pointers to char (the _P variant) */
+#define F77_EXPORT_CHARACTER_ARRAY(C,LC,F,LF,N) \
+        {int f77dims[1];f77dims[0]=N;cnfExprta(C,LC,F,LF,1,f77dims);}
+#define F77_EXPORT_CHARACTER_ARRAY_P(C,F,LF,N) \
+        {int f77dims[1];f77dims[0]=N;cnfExprtap(C,F,LF,1,f77dims);}
+#define F77_EXPORT_DOUBLE_ARRAY(C,F,N) F=(F77_DOUBLE_TYPE *)C
+#define F77_EXPORT_INTEGER_ARRAY(C,F,N) F=(F77_INTEGER_TYPE *)C
+#define F77_EXPORT_LOGICAL_ARRAY(C,F,N) \
+        {int f77dims[1];f77dims[0]=N;cnfExpla(C,F,1,f77dims);}
+#define F77_EXPORT_REAL_ARRAY(C,F,N) F=(F77_REAL_TYPE *)C
+#define F77_EXPORT_BYTE_ARRAY(C,F,N) F=(F77_BYTE_TYPE *)C
+#define F77_EXPORT_WORD_ARRAY(C,F,N) F=(F77_WORD_TYPE *)C
+#define F77_EXPORT_UBYTE_ARRAY(C,F,N) F=(F77_UBYTE_TYPE *)C
+#define F77_EXPORT_UWORD_ARRAY(C,F,N) F=(F77_UWORD_TYPE * )C
+#define F77_EXPORT_POINTER_ARRAY(C,F,N) \
+     {int f77i;for (f77i=0;f77i<N;f77i++)F[f77i]=cnfFptr(C[f77i]);}
+#define F77_EXPORT_LOCATOR_ARRAY(C,F,N) \
+        {int f77i;for (f77i=0;f77i<N;f77i++)cnfExpch(C,F,DAT__SZLOC);}
+
+/* Import Fortran variables to C */
+#define F77_IMPORT_CHARACTER(F,L,C) cnfImprt(F,L,C)
+#define F77_IMPORT_DOUBLE(F,C) C=F
+#define F77_IMPORT_INTEGER(F,C) C=F
+#define F77_IMPORT_LOGICAL(F,C) C=F77_ISTRUE(F)
+#define F77_IMPORT_REAL(F,C) C=F
+#define F77_IMPORT_BYTE(F,C) C=F
+#define F77_IMPORT_WORD(F,C) C=F
+#define F77_IMPORT_UBYTE(F,C) C=F
+#define F77_IMPORT_UWORD(F,C) C=F
+#define F77_IMPORT_POINTER(F,C) C=cnfCptr(F)
+#define F77_IMPORT_LOCATOR(F,C) cnfImpch(F,DAT__SZLOC,C)
+
+/* Import Fortran arrays to C */
+/* Arrays are assumed to be 1-d so just the number of elements is given  */
+/* This may be OK for n-d arrays also */
+/* CHARACTER arrays may be represented in C as arrays of arrays of char or */
+/* as arrays of pointers to char (the _P variant) */
+#define F77_IMPORT_CHARACTER_ARRAY(F,LF,C,LC,N) \
+        {int f77dims[1];f77dims[0]=N;cnfImprta(F,LF,C,LC,1,f77dims);}
+#define F77_IMPORT_CHARACTER_ARRAY_P(F,LF,C,LC,N) \
+        {int f77dims[1];f77dims[0]=N;cnfImprtap(F,LF,C,LC,1,f77dims);}
+#define F77_IMPORT_DOUBLE_ARRAY(F,C,N)
+#define F77_IMPORT_INTEGER_ARRAY(F,C,N)
+#define F77_IMPORT_LOGICAL_ARRAY(F,C,N) \
+        {int f77dims[1];f77dims[0]=N;cnfImpla(F,C,1,f77dims);}
+#define F77_IMPORT_REAL_ARRAY(F,C,N)
+#define F77_IMPORT_BYTE_ARRAY(F,C,N)
+#define F77_IMPORT_WORD_ARRAY(F,C,N)
+#define F77_IMPORT_UBYTE_ARRAY(F,C,N)
+#define F77_IMPORT_UWORD_ARRAY(F,C,N)
+#define F77_IMPORT_POINTER_ARRAY(F,C,N) \
+        {int f77i;for (f77i=0;f77i<N;f77i++)C[f77i]=cnfCptr(F[f77i]);}
+#define F77_IMPORT_LOCATOR_ARRAY(F,C,N) \
+        {int f77i;for (f77i=0;f77i<N;f77i++)cnfImpch(F,DAT__SZLOC,C);}
+
+/*  ---  Call a FORTRAN routine  ---					    */
+
+#define F77_CALL(X)  F77_EXTERNAL_NAME(X)
+
+
+/*  ---  Execute code synchronised by the CNF global mutex                  */
+#define F77_LOCK(code) \
+   cnfLock(); \
+   code \
+   cnfUnlock();
+
+/*  ---  Pass arguments to a FORTRAN routine  ---			    */
+
+#define INTEGER_ARG(X)   X
+#define REAL_ARG(X)      X
+#define DOUBLE_ARG(X)    X
+#define LOGICAL_ARG(X)   X
+#define BYTE_ARG(X)      X
+#define WORD_ARG(X)      X
+#define UBYTE_ARG(X)     X
+#define UWORD_ARG(X)     X
+#define POINTER_ARG(X)   X
+#define CHARACTER_ARG(X) X
+#define TRAIL_ARG(X)     ,X##_length
+
+#define SUBROUTINE_ARG(X)  X
+#define INTEGER_FUNCTION_ARG(X)  X
+#define REAL_FUNCTION_ARG(X)  X
+#define DOUBLE_FUNCTION_ARG(X)  X
+#define LOGICAL_FUNCTION_ARG(X)  X
+#define CHARACTER_FUNCTION_ARG(X)  X
+#define BYTE_FUNCTION_ARG(X)  X
+#define WORD_FUNCTION_ARG(X)  X
+#define UBYTE_FUNCTION_ARG(X)  X
+#define UWORD_FUNCTION_ARG(X)  X
+#define POINTER_FUNCTION_ARG(X)  X
+
+#define INTEGER_ARRAY_ARG(X)   (F77_INTEGER_TYPE *)X
+#define REAL_ARRAY_ARG(X)      (F77_REAL_TYPE *)X
+#define DOUBLE_ARRAY_ARG(X)    (F77_DOUBLE_TYPE *)X
+#define LOGICAL_ARRAY_ARG(X)   (F77_LOGICAL_TYPE *)X
+#define BYTE_ARRAY_ARG(X)      (F77_BYTE_TYPE *)X
+#define WORD_ARRAY_ARG(X)      (F77_WORD_TYPE *)X
+#define UBYTE_ARRAY_ARG(X)     (F77_UBYTE_TYPE *)X
+#define UWORD_ARRAY_ARG(X)     (F77_UWORD_TYPE *)X
+#define POINTER_ARRAY_ARG(X)   (F77_POINTER_TYPE *)X
+#define CHARACTER_ARRAY_ARG(X) (F77_CHARACTER_ARRAY_ARG_TYPE *)X
+
+/* Put the 64-bit INT support in one place */
+#define F77_INTEGER8_TYPE     int64_t
+#define F77_INTEGER8_FUNCTION(X)   F77_INTEGER8_TYPE F77_EXTERNAL_NAME(X)
+#define INTEGER8(X)     F77_INTEGER8_TYPE *CNF_CONST X
+#define INTEGER8_FUNCTION(X)  F77_INTEGER8_TYPE (*X)()
+#define INTEGER8_ARRAY(X)     F77_INTEGER8_TYPE *CNF_CONST X
+#define GENPTR_INTEGER8(X)
+#define GENPTR_INTEGER8_ARRAY(X)
+#define GENPTR_INTEGER8_FUNCTION(X)
+#define DECLARE_INTEGER8(X) F77_INTEGER8_TYPE X
+#define DECLARE_INTEGER8_ARRAY(X,D) F77_INTEGER8_TYPE X[D]
+#define DECLARE_INTEGER8_ARRAY_DYN(X) F77_INTEGER8_TYPE *X = NULL
+#define F77_CREATE_INTEGER8_ARRAY(X,N)
+#define F77_ASSOC_INTEGER8_ARRAY(F,C) F=C
+#define F77_FREE_INTEGER8(X)
+#define F77_EXPORT_INTEGER8(C,F) F=C
+#define F77_EXPORT_INTEGER8_ARRAY(C,F,N) F=(F77_INTEGER8_TYPE *)C
+#define F77_IMPORT_INTEGER8(F,C) C=F
+#define F77_IMPORT_INTEGER8_ARRAY(F,C,N)
+#define INTEGER8_ARG(X)   X
+#define INTEGER8_FUNCTION_ARG(X)  X
+#define INTEGER8_ARRAY_ARG(X)   (F77_INTEGER8_TYPE *)X
+
+/* ------------------------ Non-ansi section ------------------------------ */
+
+/*  The difference between ANSI and non-ANSI compilers, as far as macro	    */
+/*  definition is concerned, is that non-ANSI compilers do not support the  */
+/*  token concatenation operator (##). To work around this, we use the fact */
+/*  that the null comment is preprocessed to produce no characters at all   */
+/*  by our non-ANSI compilers.						    */
+/*  This section does not deal with the fact that some non-ANSI compilers   */
+/*  cannot handle function prototypes. That is handled in the machine 	    */
+/*  specific sections.							    */
+
+#if !defined(__STDC__)
+
+/*  ---  External Name  ---						    */
+
+/*  Macro to define the name of a Fortran routine or common block. This	    */
+/*  ends in an underscore on many Unix systems.				    */
+
+#undef  F77_EXTERNAL_NAME
+#define F77_EXTERNAL_NAME(X) X/**/_
+
+
+/*  ---  Dummy Arguments  ---						    */
+
+/*  Macros to handle character dummy arguments.				    */
+
+#undef  TRAIL
+#define TRAIL(X) ,@TRAIL_TYPE@ X/**/_length
+
+
+/*  ---  Declare variables  ---						    */
+
+#undef  DECLARE_CHARACTER
+#define DECLARE_CHARACTER(X,L)         F77_CHARACTER_TYPE X[L]; \
+   const int X/**/_length = L
+#undef  DECLARE_CHARACTER_ARRAY
+#define DECLARE_CHARACTER_ARRAY(X,L,D) F77_CHARACTER_TYPE X[D][L]; \
+   const int X/**/_length = L
+#undef DECLARE_CHARACTER_DYN
+#define DECLARE_CHARACTER_DYN(X)   F77_CHARACTER_TYPE *X;\
+   int X/**/_length
+#undef DECLARE_CHARACTER_ARRAY_DYN
+#define DECLARE_CHARACTER_ARRAY_DYN(X)   F77_CHARACTER_TYPE *X;\
+   int X/**/_length
+#undef F77_CREATE_CHARACTER
+#define F77_CREATE_CHARACTER(X,L)  X=cnfCref(L);\
+   X/**/_length = L
+#undef F77_CREATE_CHARACTER_ARRAY
+#define F77_CREATE_CHARACTER_ARRAY(X,L,N) \
+        {int f77dims[1];f77dims[0]=N;X=cnfCrefa(L,1,f77dims);X/**/_length=L;}
+
+/*  ---  Pass arguments to a FORTRAN routine  ---			    */
+
+#undef  TRAIL_ARG
+#define TRAIL_ARG(X)     ,X/**/_length
+
+
+#endif  /* of non ANSI redefinitions					    */
+
+
+/* -----------------------------------------------------------------------  */
+
+/* The standard macros defined above are known to work with the following   */
+/* systems:								    */
+
+/*--------
+|   Sun   |
+---------*/
+
+/*  On SunOS, the ANSI definitions work with the acc and gcc compilers.     */
+/*  The cc compiler uses the non ANSI definitions. It also needs the K&R    */
+/*  definitions in the file kr.h.					    */
+/*  On Solaris, the standard definitions work with the cc compiler.	    */
+
+#if defined(sun)
+
+#if !defined(__STDC__)
+#if !defined(_F77_KR)
+#define _F77_KR
+#endif
+#endif
+
+#endif	/* Sun								    */
+
+/* -------------------- System dependent sections ------------------------- */
+
+/*------------
+|   VAX/VMS   |
+-------------*/
+
+/* Many macros need to be changed due to the way that VMS handles external  */
+/* names, passes character arguments and handles logical values.	    */
+
+
+#if defined(VMS)
+
+/*  ---  Data Types  ---						    */
+
+/*  Redefine the macro for the byte data type as signed is not valid syntax */
+/*  as the VMS compiler is not ANSI compliant.				    */
+
+#undef  F77_BYTE_TYPE
+#define F77_BYTE_TYPE      char
+
+
+/*  ---  External Names  ---						    */
+
+/*  Macro to define the name of a Fortran routine or common block.	    */
+/*  Fortran and C routines names are the same on VMS.			    */
+
+#undef  F77_EXTERNAL_NAME
+#define F77_EXTERNAL_NAME(X) X
+
+
+/*  ---  Dummy Arguments  ---						    */
+
+/*  Macros to handle character arguments.				    */
+/*  Character string arguments are pointers to character string descriptors */
+/*  and there are no trailing arguments.				    */
+
+#if( VMS != 0 )
+#include <descrip.h>
+#endif
+
+
+#undef  F77_CHARACTER_ARG_TYPE
+#define F77_CHARACTER_ARG_TYPE struct dsc$descriptor_s
+#undef  F77_CHARACTER_ARRAY_ARG_TYPE
+#define F77_CHARACTER_ARRAY_ARG_TYPE struct dsc$descriptor_a
+#undef  CHARACTER
+#define CHARACTER(X) F77_CHARACTER_ARG_TYPE *CNF_CONST X/**/_arg
+#undef  TRAIL
+#define TRAIL(X)
+#undef  CHARACTER_ARRAY
+#define CHARACTER_ARRAY(X)  F77_CHARACTER_ARRAY_ARG_TYPE *CNF_CONST X/**/_arg
+#undef  GENPTR_CHARACTER
+#define GENPTR_CHARACTER(X) \
+   F77_CHARACTER_TYPE *X = X/**/_arg->dsc$a_pointer; \
+   int X/**/_length = X/**/_arg->dsc$w_length;
+#undef  GENPTR_CHARACTER_ARRAY
+#define GENPTR_CHARACTER_ARRAY(X)   GENPTR_CHARACTER(X)
+
+
+/*  ---  Logical Values  ---						    */
+
+#undef  F77_TRUE
+#define F77_TRUE -1
+#undef  F77_ISTRUE
+#define F77_ISTRUE(X) ( (X)&1 )
+#undef  F77_ISFALSE
+#define F77_ISFALSE(X) ( ! ( (X)&1 ) )
+
+
+/*  ---  Common Blocks  ---						    */
+
+#undef  F77_BLANK_COMMON
+#define F77_BLANK_COMMON  $BLANK
+
+
+/*  ---  Declare Variables  ---						    */
+
+#undef  DECLARE_CHARACTER
+#define DECLARE_CHARACTER(X,L) \
+   F77_CHARACTER_TYPE X[L];    const int X/**/_length = L; \
+   F77_CHARACTER_ARG_TYPE X/**/_descr = \
+      { L, DSC$K_DTYPE_T, DSC$K_CLASS_S, X }; \
+   F77_CHARACTER_ARG_TYPE *X/**/_arg = &X/**/_descr
+#undef  DECLARE_CHARACTER_ARRAY
+#define DECLARE_CHARACTER_ARRAY(X,L,D) \
+   F77_CHARACTER_TYPE X[D][L]; const int X/**/_length = L; \
+   F77_CHARACTER_ARRAY_ARG_TYPE X/**/_descr = \
+      { L, DSC$K_DTYPE_T, DSC$K_CLASS_S, X }; \
+   F77_CHARACTER_ARRAY_ARG_TYPE *X/**/_arg = &X/**/_descr
+
+
+/*  ---  The dynamic allocation of character arguments  ---                 */
+#undef DECLARE_CHARACTER_DYN
+#define DECLARE_CHARACTER_DYN(X) int X/**/_length;\
+                                  F77_CHARACTER_ARG_TYPE *X/**/_arg;\
+                                  F77_CHARACTER_TYPE *X
+#undef DECLARE_CHARACTER_ARRAY_DYN
+#define DECLARE_CHARACTER_ARRAY_DYN(X) int X/**/_length;\
+                                  F77_CHARACTER_ARRAY_ARG_TYPE *X/**/_arg;\
+                                  F77_CHARACTER_TYPE *X
+#undef F77_CREATE_CHARACTER
+#define F77_CREATE_CHARACTER(X,L) X/**/_arg = cnfCref(L);\
+                                  X = X/**/_arg->dsc$a_pointer; \
+                                  X/**/_length = X/**/_arg->dsc$w_length
+#undef F77_CREATE_CHARACTER_ARRAY
+#define F77_CREATE_CHARACTER_ARRAY(X,L,N) \
+  {int f77dims[1];f77dims[0]=N;X/**/_arg=cnfCrefa(L,1,f77dims);X/**/_length=L;}
+#define F77_CREATE_CHARACTER_ARRAY_M(X,L,N,D) X/**/_arg = cnfCrefa(L,N,D);\
+                                  X = X/**/_arg->dsc$a_pointer; \
+                                  X/**/_length = X/**/_arg->dsc$w_length
+#undef F77_FREE_CHARACTER
+#define F77_FREE_CHARACTER(X)     cnfFreef( X/**/_arg )
+
+/*  ---  Pass arguments to a FORTRAN routine  ---			    */
+
+#undef  CHARACTER_ARG
+#define CHARACTER_ARG(X) X/**/_arg
+#undef  CHARACTER_ARRAY_ARG
+#define CHARACTER_ARRAY_ARG(X) X/**/_arg
+#undef  TRAIL_ARG
+#define TRAIL_ARG(X)
+
+#endif  /* VMS								    */
+
+/* -----------------------------------------------------------------------  */
+
+/*--------------------------
+|   DECstation Ultrix (cc)  |
+|   DECstation Ultrix (c89) |
+|   DECstation OSF/1        |
+|   Alpha OSF/1             |
+ --------------------------*/
+
+/* Do this complicated set of definitions as a single #if cannot be	    */
+/* continued across multiple lines.					    */
+
+#if defined(mips) && defined(ultrix)
+#define _dec_unix 1
+#endif
+#if defined(__mips) && defined(__ultrix)
+#define _dec_unix 1
+#endif
+#if defined(__mips__) && defined(__osf__)
+#define _dec_unix 1
+#endif
+#if defined(__alpha) && defined(__osf__)
+#define _dec_unix 1
+#endif
+
+#if _dec_unix
+
+/*  The macros for Ultrix are the same as the standard ones except for ones */
+/*  dealing with logical values. The ANSI definitions work with the c89	    */
+/*  compiler, and the non ANSI definitions work with the cc compiler.	    */
+/*  The same applies to DEC OSF/1, except that its cc compiler is ANSI	    */
+/*  compliant.								    */
+
+
+/*  ---  Logical Values  ---						    */
+
+/*  Redefine macros that evaluate to a C logical value, given a FORTRAN	    */
+/*  logical value. These definitions are only valid when used with the DEC  */
+/*  FORTRAN for RISC compiler. If you are using the earlier FORTRAN for	    */
+/*  RISC compiler from MIPS, then these macros should be deleted.	    */
+
+#undef  F77_TRUE
+#define F77_TRUE -1
+#undef  F77_ISTRUE
+#define F77_ISTRUE(X) ( (X)&1 )
+#undef  F77_ISFALSE
+#define F77_ISFALSE(X) ( ! ( (X)&1 ) )
+
+
+#endif  /* DEC Unix							    */
+
+/*
+*+
+*  Name:
+*     cnf.h
+
+*  Purpose:
+*     Function prototypes for cnf routines
+
+*  Language:
+*     ANSI C
+
+*  Type of Module:
+*     C include file
+
+*  Description:
+*     These are the prototype definitions for the functions in the CNF
+*     library. They are used used in mixing C and FORTRAN programs.
+
+*  Copyright:
+*     Copyright (C) 1991 Science & Engineering Research Council
+
+*  Authors:
+*     PMA: Peter Allan (Starlink, RAL)
+*     AJC: Alan Chipperfield (Starlink, RAL)
+*     {enter_new_authors_here}
+
+*  History:
+*     23-MAY-1991 (PMA):
+*        Original version.
+*     12-JAN-1996 (AJC):
+*        Add cnf_cref and cnf_freef
+*     14-JUN-1996 (AJC):
+*        Add cnf_crefa, imprta, exprta
+*                crela, impla, expla
+*     18-JUL-1996 (AJC):
+*        Add impch and expch
+*     17-MAR-1998 (AJC):
+*        Add imprtap and exprtap
+*     {enter_changes_here}
+
+*  Bugs:
+*     {note_any_bugs_here}
+
+*-
+------------------------------------------------------------------------------
+*/
+void cnfInitRTL( int, char** );
+void *cnfCalloc( size_t, size_t );
+void cnfCopyf( const char *source_f, int source_len, char *dest_f,
+                int dest_len );
+void *cnfCptr( F77_POINTER_TYPE );
+char *cnfCreat( int length );
+F77_CHARACTER_ARG_TYPE *cnfCref( int length );
+F77_CHARACTER_ARG_TYPE *cnfCrefa( int length, int ndims, const int *dims );
+char *cnfCreib( const char *source_f, int source_len );
+char *cnfCreim( const char *source_f, int source_len );
+F77_LOGICAL_TYPE *cnfCrela( int ndims, const int *dims );
+void cnfExpch( const char *source_c, char *dest_f, int nchars );
+void cnfExpla( const int *source_c, F77_LOGICAL_TYPE *dest_f, int ndims,
+                const int *dims );
+void cnfExpn( const char *source_c, int max, char *dest_f, int dest_len );
+void cnfExprt( const char *source_c, char *dest_f, int dest_len );
+void cnfExprta( const char *source_c, int source_len, char *dest_f,
+                 int dest_len, int ndims, const int *dims );
+void cnfExprtap( char *const *source_c, char *dest_f, int dest_len,
+                  int ndims, const int *dims );
+F77_POINTER_TYPE cnfFptr( void *cpointer );
+void cnfFree( void * );
+void cnfFreef( F77_CHARACTER_ARG_TYPE *temp );
+void cnfImpb( const char *source_f, int source_len, char *dest_c );
+void cnfImpbn( const char *source_f, int source_len, int max, char *dest_c );
+void cnfImpch( const char *source_f, int nchars, char *dest_c );
+void cnfImpla( const F77_LOGICAL_TYPE *source_f, int *dest_c,
+                int ndims, const int *dims );
+void cnfImpn( const char *source_f, int source_len, int max, char *dest_c );
+void cnfImprt( const char *source_f, int source_len, char *dest_c );
+void cnfImprta( const char *source_f, int source_len, char *dest_c,
+                 int dest_len, int ndims, const int *dims );
+void cnfImprtap( const char *source_f, int source_len, char *const *dest_c,
+                  int dest_len, int ndims, const int *dims );
+int cnfLenc( const char *source_c );
+int cnfLenf( const char *source_f, int source_len );
+void *cnfMalloc( size_t );
+void *cnfRealloc( void *, size_t );
+int cnfRegp( void * );
+void cnfUregp( void * );
+void cnfLock( void );
+void cnfUnlock( void );
+#endif
+
+#ifndef CNF_OLD_DEFINED
+#define CNF_OLD_DEFINED
+/* Define old names to be new names */
+#define cnf_calloc cnfCalloc
+#define cnf_copyf cnfCopyf
+#define cnf_cptr cnfCptr
+#define cnf_creat cnfCreat
+#define cnf_cref cnfCref
+#define cnf_crefa cnfCrefa
+#define cnf_creib cnfCreib
+#define cnf_creim cnfCreim
+#define cnf_crela cnfCrela
+#define cnf_expch cnfExpch
+#define cnf_expla cnfExpla
+#define cnf_expn cnfExpn
+#define cnf_exprt cnfExprt
+#define cnf_exprta cnfExprta
+#define cnf_exprtap cnfExprtap
+#define cnf_fptr cnfFptr
+#define cnf_free cnfFree
+#define cnf_freef cnfFreef
+#define cnf_impb cnfImpb
+#define cnf_impbn cnfImpbn
+#define cnf_impch cnfImpch
+#define cnf_impla cnfImpla
+#define cnf_impn cnfImpn
+#define cnf_imprt cnfImprt
+#define cnf_imprta cnfImprta
+#define cnf_imprtap cnfImprtap
+#define cnf_lenc cnfLenc
+#define cnf_lenf cnfLenf
+#define cnf_malloc cnfMalloc
+#define cnf_regp cnfRegp
+#define cnf_uregp cnfUregp
+
+#endif /* CNF_MACROS */
diff --git a/ast/fac_1521_err b/ast/fac_1521_err
new file mode 100644
index 0000000..3799f34
--- /dev/null
+++ b/ast/fac_1521_err
@@ -0,0 +1,160 @@
+FACILITY AST
+300,ATGER,attribute getting error
+301,ATSER,attribute setting error
+302,ATTIN,attribute value invalid
+303,AXIIN,axis index invalid
+304,BADAT,bad attribute name
+305,BADBX,zero-sized box given
+306,BADIN,bad input data
+307,BADNI,bad number of input coordinates
+308,BADNO,bad number of output coordinates
+309,BADPW,PolyMap contains illegal power value
+310,BADSM,ShiftMap contains no shift information
+311,BADWM,WinMap contains no bounds information
+312,BDBRK,bad break index
+313,BDFMT,bad field specifier
+314,BDFTS,invalid FITS keyword value found
+315,BDOBJ,inappropriate Object supplied
+316,CLPAX,wrong number of clipping axes
+317,CORNG,range of coordinates invalid
+318,CVBRK,too many breaks in a curve
+319,DIMIN,array dimensions invalid
+320,DTERR,date/time error
+321,ENDIN,invalid use of astEnd
+322,EOCHN,end of input Channel encountered
+323,EXPIN,attempt to export Object pointer from level zero
+324,FCRPT,corrupted FitsChan supplied
+325,FMTER,error while formatting coordinate value
+326,FRMIN,Frame index invalid
+327,FRSIN,FrameSet invalid
+328,FTCNV,cannot convert FITS data value type
+329,GRFER,low level graphics error
+330,INHAN,invalid Handle
+331,INNCO,incompatible numbers of coordinates
+332,INTER,internal programming error
+333,INTRD,incompatible transformation directions
+334,KYCIR,circular dependency between KeyMaps
+335,LDERR,class loader error
+336,LUTII,invalid lookup table increment
+337,LUTIN,invalid number of lookup table elements
+338,MEMIN,requested memory size invalid
+339,MTR23,not a 2d or 3d MatrixMap
+340,MTRAX,null rotation axis supplied
+341,MTRML,bad matrix shapes for multiplication
+342,MTRMT,null matrix supplied
+343,NAXIN,number of axes invalid
+344,NCHIN,number of characters invalid
+345,NCOIN,number of coordinates invalid
+346,NCPIN,number of coordinates per point invalid
+347,NELIN,number of array elements invalid
+348,NOCTS,number of output coordinates too small
+349,NODEF,transformation not defined
+350,NOFTS,required FITS keywords missing
+351,NOMEM,unable to allocate memory
+352,NOPTS,number of output points too small
+353,NOWRT,attribute is read-only
+354,NPTIN,number of points invalid
+355,OBJIN,Object invalid
+356,OPT,invalid Plot option
+357,PDSIN,points data structure invalid
+358,PLFMT,no numerical labels can be produced
+359,PRMIN,permutation invalid
+360,PTRIN,pointer invalid
+361,PTRNG,range of points invalid
+362,RDERR,read error
+363,REGIN,invalid or corrupted Region structure supplied
+364,REMIN,invalid attempt to remove last Frame
+365,SCSIN,sky coordinate system invalid
+366,SELIN,axis selection invalid
+367,SLAIN,bad SLALIB transformation type
+368,TRNND,coordinate transformation not defined
+369,UNMQT,unmatched quotes
+370,VSMAL,valid area too small
+371,WCSAX,non-existent longitude or latitude axis
+372,WCSNC,too few mapping coordinates
+373,WCSPA,invalid projection parameters
+374,WCSTY,unknown projection type
+375,XSOBJ,too many Objects in use at once
+376,ZOOMI,zoom factor invalid
+377,BADCI,bad coordinate index
+378,ILOST,FrameSet integrity lost
+379,ITFER,error in IntraMap transformation function
+380,ITFNI,IntraMap transformation function name invalid
+381,MBBNF,Mapping bounding box not found
+382,MRITF,multiple registration of IntraMap transformation function
+383,OCLUK,Object class unknown
+384,UNFER,error while unformatting a coordinate value
+385,URITF,unregistered IntraMap transformation function
+386,GBDIN,grid bounds invalid
+387,NGDIN,number of grid dimensions invalid
+388,PATIN,positional accuracy tolerance invalid
+389,SISIN,sub-pixel interpolation scheme invalid
+390,SSPIN,scale size in pixels invalid
+391,UINER,error in user-supplied sub-pixel interpolation function
+392,UK1ER,error in user-supplied 1-d sub-pixel interpolation kernel
+393,COMIN,invalid comma in expression
+394,CONIN,invalid constant in expression
+395,DUVAR,duplicate variable name
+396,INNTF,invalid number of transformation functions
+397,MIOPA,missing or invalid operand in expression
+398,MIOPR,missing or invalid operator in expression
+399,MISVN,missing variable name
+400,MLPAR,missing left parenthesis in expression
+401,MRPAR,missing right parenthesis in expression
+402,NORHS,missing right hand side in function
+403,UDVOF,undefined variable or function in expression
+404,VARIN,variable name invalid
+405,WRNFA,wrong number of function arguments in expression
+406,BADUN,invalid units specification
+407,NORSF,no rest frequency is defined
+408,NOSOR,no standard of rest is defined
+409,SPCIN,invalid SpecMap
+410,XMLNM,invalid XML name or prefix
+411,XMLCM,invalid XML comment text
+412,XMLPT,invalid XML processing instruction target text
+413,XMLIT,invalid XML content item index
+414,XMLWF,supplied XML document is not well formed
+415,ZERAX,Range of log axis scale includes zero
+416,BADOC,Invalid parameters for offset sky coordinate system
+417,MPGER,error getting a named value from a KeyMap
+418,MPIND,invalid integer index supplied for a KeyMap entry
+419,REGCN,region cannot be re-centred
+420,NOVAL,attribute has no usable value
+421,INCTS,incompatible time scales
+422,TIMIN,invalid TimeMap
+423,STCKEY,cannot use supplied AstroCoords info
+424,STCIND,invalid AstroCoords index
+425,CNFLX,cannot conserve flux whilst resampling an array of data
+426,TUNAM,Unknown AST tuning parameter name supplied
+427,BDPAR,Bad value supplied for a public function parameter
+428,3DFSET,Supplied FrameSet does not contain any independent axes
+429,PXFRRM,Attempt to delete original Plot3D base Frame
+430,BADSUB,Illegal syntax for string substitution template
+431,BADFLG,Incompatible flags for re-sampling or re-binning
+432,LCKERR,Error locking or unlocking an AST Object
+433,FUNDEF,FITS keyword had undefined value
+434,MPVIN,invalid integer index supplied for a KeyMap vector element
+435,OPRIN,operation specifier invalid
+436,NONIN,no inside point found
+437,MPKER,requested key not found in KeyMap
+438,MPPER,error putting a named value into a KeyMap
+439,BADKEY,Attempt made to add an entry to a locked KeyMap
+440,BADTYP,Bad data type
+441,OLDCOL,Column already exists with different properties
+442,BADNULL,Bad null value for a FITS table column
+443,BIGKEY,Key string is too long
+444,BADCOL,No such column exists in the table
+445,BIGTAB,Table is too large
+446,BADSIZ,Invalid array size
+447,BADTAB,Error reading WCS from FITS binary table
+448,NOTAB,Cannot access FITS binary table
+449,LEVMAR,Error in levmar Levenberg-Marquardt code
+450,NOFIT,Fit failed
+451,ISNAN,A transformation generated one or more NaN values
+452,WRERR,write error
+453,BDVNM,Bad variant Mapping name
+454,MIRRO,Attempt to add a variant Mapping to a mirror Frame
+455,MNPCK,Error in cminpack Levenberg-Marquardt code
+456,EXSPIX,Supplied array has too many pixels
+457,NOCNV,No mapping found between coordinate systems
+458,IMMUT,Attempt to change an immutable attribute
diff --git a/ast/fbox.c b/ast/fbox.c
new file mode 100644
index 0000000..8dff34f
--- /dev/null
+++ b/ast/fbox.c
@@ -0,0 +1,110 @@
+/*
+*+
+*  Name:
+*     fbox.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Box class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Box class.
+
+*  Routines Defined:
+*     AST_ISABOX
+*     AST_BOX
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-MAR-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "box.h"                 /* C interface to the Box class */
+
+F77_LOGICAL_FUNCTION(ast_isabox)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISABOX", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsABox( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_box)( INTEGER(FRAME),
+                               INTEGER(FORM),
+                               DOUBLE_ARRAY(POINT1),
+                               DOUBLE_ARRAY(POINT2),
+                               INTEGER(UNC),
+                               CHARACTER(OPTIONS),
+                               INTEGER(STATUS)
+                               TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_INTEGER(FORM)
+   GENPTR_DOUBLE_ARRAY(POINT1)
+   GENPTR_DOUBLE_ARRAY(POINT2)
+   GENPTR_INTEGER(UNC)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_BOX", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+      RESULT = astP2I( astBox( astI2P( *FRAME ), *FORM, POINT1, POINT2,
+                               astI2P( *UNC ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fchannel.c b/ast/fchannel.c
new file mode 100644
index 0000000..9f70315
--- /dev/null
+++ b/ast/fchannel.c
@@ -0,0 +1,473 @@
+/*
+*+
+*  Name:
+*     fchannel.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Channel class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Channel class.
+
+*  Routines Defined:
+*     AST_CHANNEL
+*     AST_ISACHANNEL
+*     AST_READ
+*     AST_WRITE
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     6-SEP-1996 (RFWS):
+*        Original version.
+*     12-DEC-1996 (RFWS):
+*        Added SOURCE and SINK arguments to AST_CHANNEL.
+*     13-NOV-2003 (DSB):
+*        Made SourceWrap and SinkWrap into protected functions rather
+*        than private functions, so that they can be used in fxmlchan.c
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "channel.h"             /* C interface to the Channel class */
+
+#include <stddef.h>
+
+/* Module Variables. */
+/* ================= */
+static char *line_in = NULL;     /* Pointer to incoming line of text */
+static const char *line_out = NULL; /* Pointer to outgoing line of text */
+
+/* Prototypes for external functions. */
+/* ================================== */
+/* This is the null function defined by the FORTRAN interface in fobject.c. */
+F77_SUBROUTINE(ast_null)( void );
+
+/* Source and sink function interfaces. */
+/* ==================================== */
+/* These functions are concerned with allowing FORTRAN implementations
+   of Channel source and sink functions to be passed to the Channel
+   class and invoked when necessary by C code in the main class
+   implementation. All FORTRAN-specific aspects of this interface are
+   encapsulated here. */
+F77_SUBROUTINE(ast_getline)( CHARACTER(LINE),
+                             INTEGER(L),
+                             INTEGER(STATUS)
+                             TRAIL(LINE) ) {
+/*
+f++
+*  Name:
+*     AST_GETLINE
+
+*  Purpose:
+*     Obtain text to be written by a Channel sink routine.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     CALL AST_GETLINE( LINE, L, STATUS )
+
+*  Description:
+*     This routine should only be used when implementing a routine
+*     which will be passed as the SINK argument to AST_CHANNEL. It
+*     should be used to obtain (from the AST library) each line of
+*     text which is to be written to the external data sink. One such
+*     line should be obtained in this way for each invocation of the
+*     sink routine.
+
+*  Parameters:
+*     LINE = CHARACTER * ( * ) (Returned)
+*        The line of text to be written. Depending on the length of
+*        character variable supplied, the returned text may be
+*        truncated if necessary. Note, however, that it will not be
+*        padded with blanks in order to fill this variable.
+*     L = INTEGER (Returned)
+*        The number of characters returned, which may be zero. Note
+*        that characters beyond the L'th character in the LINE
+*        variable are not modified and may therefore contain junk.
+*     STATUS = INTEGER (Given and Returned)
+*        The global status.
+
+*  Notes:
+*     - This routine is only available in the Fortran interface to the
+*     AST library.
+f--
+*/
+
+/* Argument Pointers: */
+   GENPTR_CHARACTER(LINE)
+   GENPTR_INTEGER(L)
+
+/* Local Variables: */
+   int i;                        /* Loop counter for characters */
+
+/* Set the error context and watch the STATUS value. */
+   astAt( "AST_GETLINE", NULL, 0 );
+   astWatchSTATUS(
+
+/* Initialise the returned string length. */
+      *L = 0;
+
+/* Check the global error status. */
+      if ( !astOK ) return;
+
+/* If there is no outgoing line ready (e.g. if this routine has been
+   called at an inappropriate point), we simply return
+   nothing. Otherwise, loop to copy the text into the character
+   argument supplied, ensuring that its length is not exceeded. */
+      if ( line_out ) {
+         for ( i = 0; line_out[ i ] && ( i < LINE_length ); i++ ) {
+            LINE[ i ] = line_out[ i ];
+         }
+
+/* Return the number of characters copied. */
+         *L = i;
+      }
+   )
+}
+
+F77_SUBROUTINE(ast_putline)( CHARACTER(LINE),
+                             INTEGER(L),
+                             INTEGER(STATUS)
+                             TRAIL(LINE) ) {
+/*
+f++
+*  Name:
+*     AST_PUTLINE
+
+*  Purpose:
+*     Store a text line read by a Channel source routine.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     CALL AST_PUTLINE( LINE, L, STATUS )
+
+*  Description:
+*     This routine should only be used when implementing a routine
+*     which will be passed as the SOURCE argument to AST_CHANNEL. It
+*     should be used to pass back (to the AST library) each line of
+*     text read from the external data source. One such line should be
+*     passed back in this way for each invocation of the source
+*     routine.
+
+*  Parameters:
+*     LINE = CHARACTER * ( * ) (Given)
+*        A character string containing the line of input text which
+*        has been read.
+*     L = INTEGER (Given)
+*        The number of characters in the input line, which may be
+*        zero. If there is no more input available (e.g. an end of
+*        file has been reached), this value should be set negative and
+*        this will terminate the read operation on the Channel.
+*     STATUS = INTEGER (Given and Returned)
+*        The global status.
+
+*  Notes:
+*     - This routine is only available in the Fortran interface to the
+*     AST library.
+f--
+*/
+
+/* Argument Pointers: */
+   GENPTR_CHARACTER(LINE)
+   GENPTR_INTEGER(L)
+
+/* Local Variables: */
+   int l;                        /* Number of characters in line */
+
+/* Set the error context and watch the STATUS value. */
+   astAt( "AST_PUTLINE", NULL, 0 );
+   astWatchSTATUS(
+
+/* Initialise the incoming line pointer. */
+      line_in = NULL;
+
+/* Check the global error status. */
+      if ( !astOK ) return;
+
+/* Obtain the number of characters in the line. */
+      l = *L;
+
+/* Negative values (or STATUS set) indicate end of input. If the value
+   is not negative, limit the number of characters to the length of
+   the character variable supplied. */
+      if ( l >= 0 ) {
+         if ( l > LINE_length ) l = LINE_length;
+
+/* Create a dynamic string and fill it with the incoming data. Store
+   the resulting pointer, which will be picked up by the SourceWrap
+   function. */
+         line_in = astString( LINE, l );
+      }
+   )
+}
+
+void astSinkWrap_( void (* sink)( const char * ), const char *line, int *status ) {
+/*
+*+
+*  Name:
+*     astSinkWrap
+
+*  Purpose:
+*     Wrapper function to invoke a FORTRAN Channel sink function.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     void astSinkWrap( void (* sink)( const char * ), const char *line )
+
+*  Description:
+*     This function invokes the sink function whose pointer is
+*     supplied in order to write an output line to an external data
+*     store.
+
+*  Parameters:
+*     sink
+*        Pointer to a sink function. This should result from a cast
+*        applied to a pointer to a function, with a single FORTRAN
+*        INTEGER error status argument, that returns void.  This is
+*        the form of Channel sink function employed by the FORTRAN
+*        language interface to the AST library.
+*     line
+*        Pointer to a constant null-terminated string containing the
+*        line of output text.
+*-
+*/
+
+/* Local Variables; */
+   DECLARE_INTEGER(STATUS);      /* FORTRAN error status variable */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store the pointer to the output text line in the (static)
+   "line_out" variable, where it will be accessed by the sink function
+   invoking AST_GETLINE. */
+   line_out = line;
+
+/* Cast the sink function pointer to a pointer to the FORTRAN
+   subroutine and then invoke it. Transfer the AST error status to and
+   from the subroutine's error status argument. */
+   STATUS = astStatus;
+   ( *(void (*)()) sink )( INTEGER_ARG(&STATUS) );
+   astSetStatus( STATUS );
+
+/* Clear the outgoing line pointer. */
+   line_out = NULL;
+}
+
+char *astSourceWrap_( const char *(* source)( void ), int *status ) {
+/*
+*+
+*  Name:
+*     astSourceWrap
+
+*  Purpose:
+*     Wrapper function to invoke a FORTRAN Channel source function.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     char *astSourceWrap( const char *(* source)( void ) )
+
+*  Description:
+*     This function invokes the source function whose pointer is
+*     supplied in order to read the next input line from an external
+*     data store. It then returns a pointer to a dynamic string
+*     containing a copy of the text that was read.
+
+*  Parameters:
+*     source
+*        Pointer to a source function. This should result from a cast
+*        applied to a pointer to a function, with a single FORTRAN
+*        INTEGER error status argument, that returns void.  This is
+*        the form of Channel source function employed by the FORTRAN
+*        language interface to the AST library.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated, null terminated string
+*     containing a copy of the text that was read. This string must be
+*     freed by the caller (using astFree) when no longer required.
+*
+*     A NULL pointer will be returned if there is no more input text
+*     to read.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*-
+*/
+
+/* Local Variables: */
+   DECLARE_INTEGER(STATUS);      /* FORTRAN error status variable */
+   char *result;                 /* Result pointer to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise the incoming line pointer. */
+   line_in = NULL;
+
+/* Cast the source function pointer to a pointer to the FORTRAN
+   subroutine and then invoke it. Transfer the AST error status to and
+   from the subroutine's error status argument. */
+   STATUS = astStatus;
+   ( *(void (*)()) source )( INTEGER_ARG(&STATUS) );
+   astSetStatus( STATUS );
+
+/* This should result in a pointer to a dynamic string containing the
+   input text being stored in the (static) "line_in" variable as a
+   result of the source function invoking AST_PUTLINE. Save this
+   string pointer and clear the original. */
+   result = line_in;
+   line_in = NULL;
+
+/* If an error occurred, free the returned string. */
+   if ( ! astOK ) result = astFree( result );
+
+/* Return the result. */
+   return result;
+}
+
+/* FORTRAN interface functions. */
+/* ============================ */
+/* These functions implement the remainder of the FORTRAN interface. */
+F77_INTEGER_FUNCTION(ast_channel)( void (* SOURCE)(),
+                                   void (* SINK)(),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   const char *(* source)( void );
+   int i;
+   void (* sink)( const char * );
+
+   astAt( "AST_CHANNEL", NULL, 0 );
+   astWatchSTATUS(
+
+/* Set the source and sink function pointers to NULL if a pointer to
+   the null routine AST_NULL has been supplied. */
+      source = (const char *(*)( void )) SOURCE;
+      if ( source == (const char *(*)( void )) F77_EXTERNAL_NAME(ast_null) ) {
+         source = NULL;
+      }
+      sink = (void (*)( const char * )) SINK;
+      if ( sink == (void (*)( const char * )) F77_EXTERNAL_NAME(ast_null) ) {
+         sink = NULL;
+      }
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astChannelFor( source, astSourceWrap, sink, astSinkWrap,
+                                      "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_isachannel)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISACHANNEL", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAChannel( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_read)( INTEGER(THIS),
+                                INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_READ", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astRead( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_write)( INTEGER(THIS),
+                                 INTEGER(OBJECT),
+                                 INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(OBJECT)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_WRITE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astWrite( astI2P( *THIS ), astI2P( *OBJECT ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_warnings)( INTEGER(THIS),
+                                    INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_WARNINGS", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astWarnings( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+
+
diff --git a/ast/fcircle.c b/ast/fcircle.c
new file mode 100644
index 0000000..b217a4f
--- /dev/null
+++ b/ast/fcircle.c
@@ -0,0 +1,128 @@
+/*
+*+
+*  Name:
+*     fcircle.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Circle class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Circle class.
+
+*  Routines Defined:
+*     AST_ISACIRCLE
+*     AST_CIRCLE
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     31-AUG-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "circle.h"              /* C interface to the Circle class */
+
+
+F77_LOGICAL_FUNCTION(ast_isacircle)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISACIRCLE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsACircle( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_circle)( INTEGER(FRAME),
+                               INTEGER(FORM),
+                               DOUBLE_ARRAY(POINT1),
+                               DOUBLE_ARRAY(POINT2),
+                               INTEGER(UNC),
+                               CHARACTER(OPTIONS),
+                               INTEGER(STATUS)
+                               TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_INTEGER(FORM)
+   GENPTR_DOUBLE_ARRAY(POINT1)
+   GENPTR_DOUBLE_ARRAY(POINT2)
+   GENPTR_INTEGER(UNC)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_CIRCLE", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+      RESULT = astP2I( astCircle( astI2P( *FRAME ), *FORM, POINT1, POINT2,
+                                  astI2P( *UNC ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_circlepars)( INTEGER(THIS),
+                                DOUBLE_ARRAY(CENTRE),
+                                DOUBLE(RADIUS),
+                                DOUBLE_ARRAY(P1),
+                                INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(CENTRE)
+   GENPTR_DOUBLE(RADIUS)
+   GENPTR_DOUBLE_ARRAY(P1)
+
+   astAt( "AST_CIRCLEPARS", NULL, 0 );
+   astWatchSTATUS(
+      astCirclePars( astI2P( *THIS ), CENTRE, RADIUS, P1 );
+   )
+}
+
diff --git a/ast/fcmpframe.c b/ast/fcmpframe.c
new file mode 100644
index 0000000..8d99d3b
--- /dev/null
+++ b/ast/fcmpframe.c
@@ -0,0 +1,104 @@
+/*
+*+
+*  Name:
+*     fcmpframe.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST CmpFrame class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the CmpFrame class.
+
+*  Routines Defined:
+*     AST_CMPFRAME
+*     AST_ISACMPFRAME
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     30-SEP-1996 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "cmpframe.h"            /* C interface to the CmpFrame class */
+
+F77_LOGICAL_FUNCTION(ast_isacmpframe)( INTEGER(THIS),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISACMPFRAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsACmpFrame( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_cmpframe)( INTEGER(FRAME1),
+                                    INTEGER(FRAME2),
+                                    CHARACTER(OPTIONS),
+                                    INTEGER(STATUS)
+                                    TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME1)
+   GENPTR_INTEGER(FRAME2)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_CMPFRAME", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astCmpFrame( astI2P( *FRAME1 ), astI2P( *FRAME2 ),
+                                    "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fcmpmap.c b/ast/fcmpmap.c
new file mode 100644
index 0000000..2888519
--- /dev/null
+++ b/ast/fcmpmap.c
@@ -0,0 +1,106 @@
+/*
+*+
+*  Name:
+*     fcmpmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST CmpMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the CmpMap class.
+
+*  Routines Defined:
+*     AST_ISACMPMAP
+*     AST_CMPMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     25-SEP-1996 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "cmpmap.h"              /* C interface to the CmpMap class */
+
+F77_LOGICAL_FUNCTION(ast_isacmpmap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astWatchSTATUS(
+   astAt( "AST_ISACMPMAP", NULL, 0 );
+      RESULT = astIsACmpMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_cmpmap)( INTEGER(MAP1),
+                                  INTEGER(MAP2),
+                                  LOGICAL(SERIES),
+                                  CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(MAP1)
+   GENPTR_INTEGER(MAP2)
+   GENPTR_LOGICAL(SERIES)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_CMPMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astCmpMap( astI2P( *MAP1 ), astI2P( *MAP2 ),
+                                  F77_ISTRUE( *SERIES ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fcmpregion.c b/ast/fcmpregion.c
new file mode 100644
index 0000000..f494d28
--- /dev/null
+++ b/ast/fcmpregion.c
@@ -0,0 +1,107 @@
+/*
+*+
+*  Name:
+*     fcmpregion.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST CmpRegion class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the CmpRegion class.
+
+*  Routines Defined:
+*     AST_ISACMPREGION
+*     AST_CMPREGION
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     12-OCT-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "cmpregion.h"           /* C interface to the CmpRegion class */
+
+
+F77_LOGICAL_FUNCTION(ast_isacmpregion)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISACMPREGION", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsACmpRegion( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_cmpregion)( INTEGER(REG1),
+                                     INTEGER(REG2),
+                                     INTEGER(OPER),
+                                     CHARACTER(OPTIONS),
+                                     INTEGER(STATUS)
+                                     TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(REG1)
+   GENPTR_INTEGER(REG2)
+   GENPTR_INTEGER(OPER)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_CMPREGION", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+      RESULT = astP2I( astCmpRegion( astI2P( *REG1 ), astI2P( *REG2 ),
+                                     *OPER, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fdsbspecframe.c b/ast/fdsbspecframe.c
new file mode 100644
index 0000000..f0ec5db
--- /dev/null
+++ b/ast/fdsbspecframe.c
@@ -0,0 +1,100 @@
+/*
+*+
+*  Name:
+*     fdsbspecframe.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST DSBSpecFrame class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the DSBSpecFrame class.
+
+*  Routines Defined:
+*     AST_ISADSBSPECFRAME
+*     AST_DSBSPECFRAME
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     5-AUG-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "dsbspecframe.h"        /* C interface to the DSBSpecFrame class */
+
+F77_LOGICAL_FUNCTION(ast_isadsbspecframe)( INTEGER(THIS),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISADSBSPECFRAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsADSBSpecFrame( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_dsbspecframe)( CHARACTER(OPTIONS),
+                                    INTEGER(STATUS)
+                                    TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_DSBSPECFRAME", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astDSBSpecFrame( "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
diff --git a/ast/fdssmap.c b/ast/fdssmap.c
new file mode 100644
index 0000000..8e570de
--- /dev/null
+++ b/ast/fdssmap.c
@@ -0,0 +1,75 @@
+/*
+*+
+*  Name:
+*     fdssmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST DssMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the DssMap class.
+
+*  Routines Defined:
+*     AST_ISADSSMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     19-FEB-1997 (DSB):
+*        Original version.
+*     5-SEP-1997 (RFWS)
+*        Removed the AST_DSSMAP function (now protected, so not
+*        required in the Fortran interface).
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "dssmap.h"              /* C interface to the DssMap class */
+
+F77_LOGICAL_FUNCTION(ast_isadssmap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISADSSMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsADssMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
diff --git a/ast/fellipse.c b/ast/fellipse.c
new file mode 100644
index 0000000..2570a2c
--- /dev/null
+++ b/ast/fellipse.c
@@ -0,0 +1,136 @@
+/*
+*+
+*  Name:
+*     fellipse.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Ellipse class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Ellipse class.
+
+*  Routines Defined:
+*     AST_ISAELLIPSE
+*     AST_ELLIPSE
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     31-AUG-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "ellipse.h"             /* C interface to the Ellipse class */
+
+
+F77_LOGICAL_FUNCTION(ast_isaellipse)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAELLIPSE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAEllipse( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_ellipse)( INTEGER(FRAME),
+                               INTEGER(FORM),
+                               DOUBLE_ARRAY(POINT1),
+                               DOUBLE_ARRAY(POINT2),
+                               DOUBLE_ARRAY(POINT3),
+                               INTEGER(UNC),
+                               CHARACTER(OPTIONS),
+                               INTEGER(STATUS)
+                               TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_INTEGER(FORM)
+   GENPTR_DOUBLE_ARRAY(POINT1)
+   GENPTR_DOUBLE_ARRAY(POINT2)
+   GENPTR_DOUBLE_ARRAY(POINT3)
+   GENPTR_INTEGER(UNC)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_ELLIPSE", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+      RESULT = astP2I( astEllipse( astI2P( *FRAME ), *FORM, POINT1, POINT2,
+                                  POINT3, astI2P( *UNC ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_ellipsepars)( INTEGER(THIS),
+                                 DOUBLE_ARRAY(CENTRE),
+                                 DOUBLE(A),
+                                 DOUBLE(B),
+                                 DOUBLE(ANGLE),
+                                 DOUBLE_ARRAY(P1),
+                                 DOUBLE_ARRAY(P2),
+                                 INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(CENTRE)
+   GENPTR_DOUBLE(A)
+   GENPTR_DOUBLE(B)
+   GENPTR_DOUBLE(ANGLE)
+   GENPTR_DOUBLE_ARRAY(P1)
+   GENPTR_DOUBLE_ARRAY(P2)
+
+   astAt( "AST_ELLIPSEPARS", NULL, 0 );
+   astWatchSTATUS(
+      astEllipsePars( astI2P( *THIS ), CENTRE, A, B, ANGLE, P1, P2 );
+   )
+}
+
diff --git a/ast/ferror.c b/ast/ferror.c
new file mode 100644
index 0000000..73ab7eb
--- /dev/null
+++ b/ast/ferror.c
@@ -0,0 +1,58 @@
+/*
+*+
+*  Name:
+*     ferror.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Error module.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Error module.
+
+*  Routines Defined:
+*     None.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     15-JUL-1996 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "error.h"               /* C interface to the Error module */
+
+/* At present there are no Fortran callable routines in this module. */
diff --git a/ast/ffitschan.c b/ast/ffitschan.c
new file mode 100644
index 0000000..fd0d419
--- /dev/null
+++ b/ast/ffitschan.c
@@ -0,0 +1,1022 @@
+/*
+*+
+*  Name:
+*     ffitschan.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST FitsChan class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the FitsChan class.
+
+*  Routines Defined:
+*     AST_DELFITS
+*     AST_PURGEWCS
+*     AST_FINDFITS
+*     AST_FITSCHAN
+*     AST_ISAFITSCHAN
+*     AST_PUTCARDS
+*     AST_PUTFITS
+*     AST_RETAINFITS
+*     AST_SETFITS<X>
+*     AST_GETFITS<X>
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     11-DEC-1996 (DSB):
+*        Original version.
+*     21-FEB-1997 (DSB):
+*        Added source and sink functions to AST_FITSCHAN.
+*     20-MAR-1997 (DSB):
+*        Functions for accessing named keywords removed. Others renamed.
+*     28-APR-1997 (DSB):
+*        FindFits and GetFits merged.
+*     10-SEP-2004 (TIMJ):
+*        Only copy the fits header to fortran string if it was found
+*        by astFindFits.
+*     17-NOV-2004 (DSB):
+*        Added AST_SETFITS<X>
+*     7-OCT-2005 (DSB):
+*        Added AST_GETFITS<X>
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "ast_err.h"             /* AST error codes */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "object.h"              /* C interface to the base Object class */
+#include "fitschan.h"            /* C interface to the FitsChan class */
+
+#include <stddef.h>
+#include <string.h>
+
+/* Prototypes for private functions. */
+/* ================================= */
+static char *SourceWrap( const char *(*)( void ), int * );
+static void SinkWrap( void (*)( const char * ), const char *, int * );
+static void TabSourceWrap( void (*)( void  ),
+                           AstFitsChan *, const char *, int, int, int * );
+
+/* Prototypes for external functions. */
+/* ================================== */
+/* This is the null function defined by the FORTRAN interface in fobject.c. */
+F77_SUBROUTINE(ast_null)( void );
+
+/* Source and sink function interfaces. */
+/* ==================================== */
+/* These functions are concerned with allowing FORTRAN implementations
+   of FitsChan source and sink functions to be passed to the FitsChan
+   class and invoked when necessary by C code in the main class
+   implementation. All FORTRAN-specific aspects of this interface are
+   encapsulated here. */
+static void SinkWrap( void (* sink)( const char * ), const char *line,
+                      int *status ) {
+/*
+*  Name:
+*     SinkWrap
+
+*  Purpose:
+*     Wrapper function to invoke a FORTRAN FitsChan sink function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     static void SinkWrap( void (* sink)( const char * ), const char *line,
+*                           int *status )
+
+*  Description:
+*     This function invokes the sink function whose pointer is
+*     supplied in order to write an output line to an external data
+*     store.
+
+*  Parameters:
+*     sink
+*        Pointer to a sink function. This should result from a cast
+*        applied to a pointer to a function (with two FORTRAN
+*        arguments: a character string of length 80 to receive a FITS
+*        card and an integer error status), that returns void.  This
+*        is the form of FitsChan sink function employed by the FORTRAN
+*        language interface to the AST library.
+*     status
+*        Pointer to inherited status value.
+*/
+
+/* Local Variables: */
+   DECLARE_CHARACTER(CARD,80);
+   DECLARE_INTEGER(STATUS);
+   char *d;
+   const char *c;
+   int i,lim;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Copy the supplied null terminated string to a fixed length, blank
+   padded string which can be passed to the Fortran routine. */
+   c = line;
+   d = CARD;
+
+   lim = (int) strlen( line );
+   if( lim > 80 ) lim = 80;
+
+   for( i = 0; i < lim; i++ ){
+      *(d++) = (*c++);
+   }
+
+   for( ; i < 80; i++ ){
+      *(d++) = ' ';
+   }
+
+/* Cast the sink function pointer to a pointer to the FORTRAN
+   subroutine and then invoke it. Transfer the AST error status to and
+   from the subroutine's error status argument. */
+   STATUS = astStatus;
+   ( ( void (*)() ) sink )( CHARACTER_ARG(CARD), INTEGER_ARG(&STATUS)
+                            TRAIL_ARG(CARD) );
+   astSetStatus( STATUS );
+}
+
+static char *SourceWrap( const char *(* source)( void ), int *status ) {
+/*
+*  Name:
+*     SourceWrap
+
+*  Purpose:
+*     Wrapper function to invoke a FORTRAN FitsChan source function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     static char *SourceWrap( const char *(* source)( void ), int *status )
+
+*  Description:
+*     This function invokes the source function whose pointer is
+*     supplied in order to read the next input line from an external
+*     data store. It then returns a pointer to a dynamic string
+*     containing a copy of the text that was read.
+
+*  Parameters:
+*     source
+*        Pointer to a source function. This should result from a cast
+*        applied to a pointer to a function (with two FORTRAN
+*        arguments: a character string of length 80 to return a FITS
+*        card and an integer error status), that returns a Fortran
+*        integer.  This is the form of FitsChan source function
+*        employed by the FORTRAN language interface to the AST
+*        library.
+*     status
+*        Pointer to inherited status.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated, null terminated string
+*     containing a copy of the text that was read. This string must be
+*     freed by the caller (using astFree) when no longer required.
+*
+*     A NULL pointer will be returned if there is no more input text
+*     to read.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   DECLARE_CHARACTER(CARD,81);   /* Fixed length Fortran string */
+   DECLARE_INTEGER(STATUS);      /* Fortran error status value */
+   char *result;                 /* Result pointer to return */
+   int retval;                   /* Value returned by source subroutine */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Cast the source function pointer to a pointer to the FORTRAN
+   function and then invoke it. Transfer the AST error status to and
+   from the subroutine's error status argument. */
+   STATUS = astStatus;
+   retval = ( *(F77_INTEGER_TYPE (*)()) source )( CHARACTER_ARG(CARD),
+                                                  INTEGER_ARG(&STATUS)
+                                                  TRAIL_ARG(CARD) );
+   astSetStatus( STATUS );
+
+/* If a card was returned, make a dynamic copy of it. */
+   if ( astOK && retval ) result = astString( CARD, 80 );
+
+/* Return the result. */
+   return result;
+}
+
+static void TabSourceWrap( void (*tabsource)( void  ),
+                           AstFitsChan *this, const char *extname,
+                           int extver, int extlevel, int *status ){
+/*
+*  Name:
+*     TabSourceWrap
+
+*  Purpose:
+*     Wrapper function to invoke the F77 table source function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void TabSourceWrap( void (*tabsource)( void ),
+*                         AstFitsChan *this, const char *extname,
+*                         int extver, int extlevel, int *status ){
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function invokes the table source function whose pointer is
+*     supplied in order to read a named FITS binary table from an external
+*     FITS file.
+
+*  Parameters:
+*     tabsource
+*        Pointer to the C tab source function.
+*     this
+*        Pointer to the FitsChan. It's reference count will be decremented
+*        by this function.
+*     extname
+*        Pointer to the string holding the name of the FITS extension
+*        from which a table is to be read.
+*     extver
+*        FITS "EXTVER" value for required extension.
+*     extlevel
+*        FITS "EXTLEVEL" value for required extension.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   DECLARE_CHARACTER(EXTNAME,80);
+   DECLARE_INTEGER(THIS_ID);
+   DECLARE_INTEGER(LSTAT);
+   DECLARE_INTEGER(EXTVER);
+   DECLARE_INTEGER(EXTLEVEL);
+   AstObject *this_id;
+   char *d;
+   const char *c;
+   int i;
+   int lim;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get an external identifier for the FitsChan. Note, this does not
+   increment the Object's reference count. Cannot use astClone as we
+   are in a "public" environment and so astClone would require an object
+   identifier, not a true C pointer. So the calling function should clone
+   the pointer before calling this function to avoid the reference count
+   dropping to zero when the associated identifier is annulled at the end of
+   this function. */
+   this_id = astMakeId( this );
+   THIS_ID = astP2I( this_id );
+
+/* Export the extver and extlevel values */
+   EXTVER = extver;
+   EXTLEVEL = extlevel;
+
+/* Copy the supplied null terminated string to a fixed length, blank
+   padded string which can be passed to the Fortran routine. */
+   c = extname;
+   d = EXTNAME;
+
+   lim = (int) strlen( extname );
+   if( lim > 80 ) lim = 80;
+
+   for( i = 0; i < lim; i++ ){
+      *(d++) = (*c++);
+   }
+
+   for( ; i < 80; i++ ){
+      *(d++) = ' ';
+   }
+
+/* Invoke the table source function (casting it to the F77 API first) to
+   read the table, and store it in the FitsChan. */
+   if( astOK ) {
+      LSTAT = 0;
+      ( ( void (*)() ) tabsource )(
+           INTEGER_ARG(&THIS_ID), CHARACTER_ARG(EXTNAME), INTEGER_ARG(&EXTVER),
+           INTEGER_ARG(&EXTLEVEL), INTEGER_ARG(&LSTAT) TRAIL_ARG(EXTNAME) );
+   }
+
+/* Report an error if the source function failed. */
+   if( LSTAT ) {
+      if( astOK ) {
+         astError( AST__NOTAB, "astRead(%s): The table source function failed to read "
+                   "a binary table from extension %s in an external FITS file.",
+                   status, astGetC( this_id, "Class" ), extname );
+      } else {
+         astError( astStatus, "astRead(%s): The table source function failed to read "
+                   "a binary table from extension %s in an external FITS file.",
+                   status, astGetC( this_id, "Class" ), extname );
+      }
+   }
+
+
+/* Free the external identifier for the FitsChan. Note, this decrements
+   the Object reference count. See comments above. */
+   (void) astAnnulId( this_id );
+
+}
+
+/* FORTRAN interface functions. */
+/* ============================ */
+/* These functions implement the remainder of the FORTRAN interface. */
+F77_INTEGER_FUNCTION(ast_fitschan)( F77_INTEGER_TYPE (* SOURCE)(),
+                                    void (* SINK)(),
+                                    CHARACTER(OPTIONS),
+                                    INTEGER(STATUS)
+                                    TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   const char *(* source)( void );
+   int i;
+   void (* sink)( const char * );
+
+   astAt( "AST_FITSCHAN", NULL, 0 );
+   astWatchSTATUS(
+
+/* Set the source and sink function pointers to NULL if a pointer to
+   the null routine AST_NULL has been supplied. */
+      source = (const char *(*)( void )) SOURCE;
+      if ( source == (const char *(*)( void )) F77_EXTERNAL_NAME(ast_null) ) {
+         source = NULL;
+      }
+      sink = (void (*)( const char * )) SINK;
+      if ( sink == (void (*)( const char * )) F77_EXTERNAL_NAME(ast_null) ) {
+         sink = NULL;
+      }
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astFitsChanFor( source, SourceWrap, sink, SinkWrap,
+                                       "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_isafitschan)( INTEGER(THIS),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAFITSCHAN", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAFitsChan( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_putcards)( INTEGER(THIS),
+                              CHARACTER(CARDS),
+                              INTEGER(STATUS)
+                              TRAIL(CARDS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(CARDS)
+   char *cards;
+
+   astAt( "AST_PUTCARDS", NULL, 0 );
+   astWatchSTATUS(
+      cards = astString( CARDS, CARDS_length );
+      astPutCards( astI2P( *THIS ), cards );
+      (void) astFree( (void *) cards );
+   )
+}
+
+F77_SUBROUTINE(ast_putfits)( INTEGER(THIS),
+                             CHARACTER(CARD),
+                             LOGICAL(OVERWRITE),
+                             INTEGER(STATUS)
+                             TRAIL(CARD) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(CARD)
+   GENPTR_LOGICAL(OVERWRITE)
+   int overwrite;
+   char *card;
+
+   astAt( "AST_PUTFITS", NULL, 0 );
+   astWatchSTATUS(
+      card = astString( CARD, CARD_length );
+      overwrite = F77_ISTRUE( *OVERWRITE );
+      astPutFits( astI2P( *THIS ), card, overwrite );
+      (void) astFree( (void *) card );
+   )
+}
+
+F77_SUBROUTINE(ast_delfits)( INTEGER(THIS),
+                             INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_DELFITS", NULL, 0 );
+   astWatchSTATUS(
+      astDelFits( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_purgewcs)( INTEGER(THIS),
+                             INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_PURGEWCS", NULL, 0 );
+   astWatchSTATUS(
+      astPurgeWCS( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_retainfits)( INTEGER(THIS),
+                             INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_RETAINFITS", NULL, 0 );
+   astWatchSTATUS(
+      astRetainFits( astI2P( *THIS ) );
+   )
+}
+
+F77_LOGICAL_FUNCTION(ast_findfits)( INTEGER(THIS),
+                                    CHARACTER(NAME),
+                                    CHARACTER(CARD),
+                                    LOGICAL(INC),
+                                    INTEGER(STATUS)
+                                    TRAIL(NAME)
+                                    TRAIL(CARD) ){
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_CHARACTER(CARD)
+   GENPTR_LOGICAL(INC)
+   F77_LOGICAL_TYPE(RESULT);
+   int i, len;
+   char *name;
+   char card[ 81 ];
+   int inc;
+
+   astAt( "AST_FINDFITS", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      inc = F77_ISTRUE( *INC );
+      RESULT = astFindFits( astI2P( *THIS ), name, card, inc ) ?
+               F77_TRUE : F77_FALSE;
+      i = 0;
+      if ( astOK && F77_ISTRUE(RESULT) ) {
+         len = (int) strlen( card );
+         for( i = 0; i < CARD_length && i < len; i++ ) CARD[i] = card[i];
+      }
+      for( ; i < CARD_length; i++ ) CARD[i] = ' ';
+      (void) astFree( (void *) name );
+   )
+   return RESULT;
+}
+
+
+F77_SUBROUTINE(ast_setfitsf)( INTEGER(THIS),
+                              CHARACTER(NAME),
+                              DOUBLE(VALUE),
+                              CHARACTER(COMMENT),
+                              LOGICAL(OVERWRITE),
+                              INTEGER(STATUS)
+                              TRAIL(NAME)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_DOUBLE(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   GENPTR_LOGICAL(OVERWRITE)
+   int overwrite;
+   char *name, *comment;
+
+   astAt( "AST_SETFITSF", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      comment = astString( COMMENT, COMMENT_length );
+      overwrite = F77_ISTRUE( *OVERWRITE );
+      astSetFitsF( astI2P( *THIS ), name, *VALUE, comment, overwrite );
+      (void) astFree( (void *) name );
+      (void) astFree( (void *) comment );
+   )
+}
+
+F77_SUBROUTINE(ast_setfitsu)( INTEGER(THIS),
+                              CHARACTER(NAME),
+                              CHARACTER(COMMENT),
+                              LOGICAL(OVERWRITE),
+                              INTEGER(STATUS)
+                              TRAIL(NAME)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_CHARACTER(COMMENT)
+   GENPTR_LOGICAL(OVERWRITE)
+   int overwrite;
+   char *name, *comment;
+
+   astAt( "AST_SETFITSU", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      comment = astString( COMMENT, COMMENT_length );
+      overwrite = F77_ISTRUE( *OVERWRITE );
+      astSetFitsU( astI2P( *THIS ), name, comment, overwrite );
+      (void) astFree( (void *) name );
+      (void) astFree( (void *) comment );
+   )
+}
+
+F77_SUBROUTINE(ast_setfitscm)( INTEGER(THIS),
+                               CHARACTER(COMMENT),
+                               LOGICAL(OVERWRITE),
+                               INTEGER(STATUS)
+                               TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(COMMENT)
+   GENPTR_LOGICAL(OVERWRITE)
+   int overwrite;
+   char *comment;
+
+   astAt( "AST_SETFITSCM", NULL, 0 );
+   astWatchSTATUS(
+      comment = astString( COMMENT, COMMENT_length );
+      overwrite = F77_ISTRUE( *OVERWRITE );
+      astSetFitsCM( astI2P( *THIS ), comment, overwrite );
+      (void) astFree( (void *) comment );
+   )
+}
+
+
+F77_SUBROUTINE(ast_setfitsi)( INTEGER(THIS),
+                              CHARACTER(NAME),
+                              INTEGER(VALUE),
+                              CHARACTER(COMMENT),
+                              LOGICAL(OVERWRITE),
+                              INTEGER(STATUS)
+                              TRAIL(NAME)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_INTEGER(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   GENPTR_LOGICAL(OVERWRITE)
+   int overwrite;
+   char *name, *comment;
+
+   astAt( "AST_SETFITSI", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      comment = astString( COMMENT, COMMENT_length );
+      overwrite = F77_ISTRUE( *OVERWRITE );
+      astSetFitsI( astI2P( *THIS ), name, *VALUE, comment, overwrite );
+      (void) astFree( (void *) name );
+      (void) astFree( (void *) comment );
+   )
+}
+
+
+F77_SUBROUTINE(ast_setfitscf)( INTEGER(THIS),
+                               CHARACTER(NAME),
+                               DOUBLE_ARRAY(VALUE),
+                               CHARACTER(COMMENT),
+                               LOGICAL(OVERWRITE),
+                               INTEGER(STATUS)
+                               TRAIL(NAME)
+                               TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_DOUBLE_ARRAY(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   GENPTR_LOGICAL(OVERWRITE)
+   int overwrite;
+   char *name, *comment;
+
+   astAt( "AST_SETFITSCF", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      comment = astString( COMMENT, COMMENT_length );
+      overwrite = F77_ISTRUE( *OVERWRITE );
+      astSetFitsCF( astI2P( *THIS ), name, VALUE, comment, overwrite );
+      (void) astFree( (void *) name );
+      (void) astFree( (void *) comment );
+   )
+}
+
+
+F77_SUBROUTINE(ast_setfitsci)( INTEGER(THIS),
+                               CHARACTER(NAME),
+                               INTEGER_ARRAY(VALUE),
+                               CHARACTER(COMMENT),
+                               LOGICAL(OVERWRITE),
+                               INTEGER(STATUS)
+                               TRAIL(NAME)
+                               TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_INTEGER_ARRAY(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   GENPTR_LOGICAL(OVERWRITE)
+   int overwrite;
+   char *name, *comment;
+
+   astAt( "AST_SETFITSCI", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      comment = astString( COMMENT, COMMENT_length );
+      overwrite = F77_ISTRUE( *OVERWRITE );
+      astSetFitsCI( astI2P( *THIS ), name, VALUE, comment, overwrite );
+      (void) astFree( (void *) name );
+      (void) astFree( (void *) comment );
+   )
+}
+
+
+F77_SUBROUTINE(ast_setfitsl)( INTEGER(THIS),
+                              CHARACTER(NAME),
+                              LOGICAL(VALUE),
+                              CHARACTER(COMMENT),
+                              LOGICAL(OVERWRITE),
+                              INTEGER(STATUS)
+                              TRAIL(NAME)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_LOGICAL(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   GENPTR_LOGICAL(OVERWRITE)
+   int overwrite, value;
+   char *name, *comment;
+
+   astAt( "AST_SETFITSL", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      comment = astString( COMMENT, COMMENT_length );
+      overwrite = F77_ISTRUE( *OVERWRITE );
+      value = F77_ISTRUE( *VALUE );
+      astSetFitsL( astI2P( *THIS ), name, value, comment, overwrite );
+      (void) astFree( (void *) name );
+      (void) astFree( (void *) comment );
+   )
+}
+
+
+F77_SUBROUTINE(ast_setfitss)( INTEGER(THIS),
+                              CHARACTER(NAME),
+                              CHARACTER(VALUE),
+                              CHARACTER(COMMENT),
+                              LOGICAL(OVERWRITE),
+                              INTEGER(STATUS)
+                              TRAIL(NAME)
+                              TRAIL(VALUE)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_CHARACTER(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   GENPTR_LOGICAL(OVERWRITE)
+   int overwrite;
+   char *name, *comment, *value;
+
+   astAt( "AST_SETFITSS", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      value = astString( VALUE, VALUE_length );
+      comment = astString( COMMENT, COMMENT_length );
+      overwrite = F77_ISTRUE( *OVERWRITE );
+      astSetFitsS( astI2P( *THIS ), name, value, comment, overwrite );
+      (void) astFree( (void *) name );
+      (void) astFree( (void *) value );
+      (void) astFree( (void *) comment );
+   )
+}
+
+F77_SUBROUTINE(ast_setfitscn)( INTEGER(THIS),
+                               CHARACTER(NAME),
+                               CHARACTER(VALUE),
+                               CHARACTER(COMMENT),
+                               LOGICAL(OVERWRITE),
+                               INTEGER(STATUS)
+                               TRAIL(NAME)
+                               TRAIL(VALUE)
+                               TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_CHARACTER(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   GENPTR_LOGICAL(OVERWRITE)
+   int overwrite;
+   char *name, *comment, *value;
+
+   astAt( "AST_SETFITSS", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      value = astString( VALUE, VALUE_length );
+      comment = astString( COMMENT, COMMENT_length );
+      overwrite = F77_ISTRUE( *OVERWRITE );
+      astSetFitsCN( astI2P( *THIS ), name, value, comment, overwrite );
+      (void) astFree( (void *) name );
+      (void) astFree( (void *) value );
+      (void) astFree( (void *) comment );
+   )
+}
+
+#define MAKE_AST_GETFITS(f,F,Ftype,X,Xtype) \
+F77_LOGICAL_FUNCTION(ast_getfits##f)( INTEGER(THIS), \
+                                      CHARACTER(NAME), \
+                                      Ftype(VALUE), \
+                                      INTEGER(STATUS) \
+                                      TRAIL(NAME) ){ \
+   GENPTR_INTEGER(THIS) \
+   GENPTR_CHARACTER(NAME) \
+   GENPTR_##Ftype(VALUE) \
+   GENPTR_INTEGER(STATUS) \
+   F77_LOGICAL_TYPE(RESULT); \
+\
+   char *name; \
+   Xtype *value; \
+\
+   value = (Xtype *) VALUE; \
+\
+   astAt( "AST_GETFITS"#F, NULL, 0 ); \
+   astWatchSTATUS( \
+      name = astString( NAME, NAME_length ); \
+      if( name && !strcmp( name, "." ) ) name = astFree( name ); \
+      RESULT = astGetFits##X( astI2P( *THIS ), name, value ) ? \
+               F77_TRUE : F77_FALSE; \
+      (void) astFree( (void *) name ); \
+   ) \
+   return RESULT; \
+}
+
+MAKE_AST_GETFITS(f,F,DOUBLE,F,double)
+MAKE_AST_GETFITS(i,I,INTEGER,I,int)
+MAKE_AST_GETFITS(l,L,LOGICAL,L,int)
+#undef MAKE_AST_GETFITS
+
+
+F77_LOGICAL_FUNCTION(ast_testfits)( INTEGER(THIS),
+                                    CHARACTER(NAME),
+                                    LOGICAL(THERE),
+                                    INTEGER(STATUS)
+                                    TRAIL(NAME) ){
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_LOGICAL(THERE)
+   GENPTR_INTEGER(STATUS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   char *name;
+   int there;
+
+   astAt( "AST_TESTFITS", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      RESULT = astTestFits( astI2P( *THIS ), name, &there ) ?
+               F77_TRUE : F77_FALSE;
+      (void) astFree( (void *) name );
+   )
+   *THERE = there ? F77_TRUE : F77_FALSE;
+   return RESULT;
+}
+
+
+#define MAKE_AST_GETFITS(f,F,Ftype,X,Xtype) \
+F77_LOGICAL_FUNCTION(ast_getfits##f)( INTEGER(THIS), \
+                                      CHARACTER(NAME), \
+                                      Ftype##_ARRAY(VALUE), \
+                                      INTEGER(STATUS) \
+                                      TRAIL(NAME) ){ \
+   GENPTR_INTEGER(THIS) \
+   GENPTR_CHARACTER(NAME) \
+   GENPTR_##Ftype##_ARRAY(VALUE) \
+   GENPTR_INTEGER(STATUS) \
+   F77_LOGICAL_TYPE(RESULT); \
+\
+   char *name; \
+   Xtype value[2]; \
+\
+   astAt( "AST_GETFITS"#F, NULL, 0 ); \
+   astWatchSTATUS( \
+      name = astString( NAME, NAME_length ); \
+      if( name && !strcmp( name, "." ) ) name = astFree( name ); \
+      RESULT = astGetFits##X( astI2P( *THIS ), name, value ) ? \
+               F77_TRUE : F77_FALSE; \
+      VALUE[ 0 ] = (F77_DOUBLE_TYPE) value[ 0 ]; \
+      VALUE[ 1 ] = (F77_DOUBLE_TYPE) value[ 1 ]; \
+      (void) astFree( (void *) name ); \
+   ) \
+   return RESULT; \
+}
+
+
+MAKE_AST_GETFITS(cf,CF,DOUBLE,CF,double)
+MAKE_AST_GETFITS(ci,CI,INTEGER,CI,int)
+
+#undef MAKE_AST_GETFITS
+
+#define MAKE_AST_GETFITS(f,F,X) \
+F77_LOGICAL_FUNCTION(ast_getfits##f)( INTEGER(THIS), \
+                                      CHARACTER(NAME), \
+                                      CHARACTER(VALUE), \
+                                      INTEGER(STATUS) \
+                                      TRAIL(NAME) \
+                                      TRAIL(VALUE) ){ \
+   GENPTR_INTEGER(THIS) \
+   GENPTR_CHARACTER(NAME) \
+   GENPTR_CHARACTER(VALUE) \
+   GENPTR_INTEGER(STATUS) \
+   F77_LOGICAL_TYPE(RESULT); \
+\
+   char *name; \
+   int i, len; \
+   char *value; \
+\
+   astAt( "AST_GETFITS"#F, NULL, 0 ); \
+   astWatchSTATUS( \
+      name = astString( NAME, NAME_length ); \
+      if( name && !strcmp( name, "." ) ) name = astFree( name ); \
+      RESULT = astGetFits##X( astI2P( *THIS ), name, &value ) ? \
+               F77_TRUE : F77_FALSE; \
+      if ( astOK && F77_ISTRUE(RESULT) ) { \
+         len = (int) strlen( value ); \
+         for( i = 0; i < VALUE_length && i < len; i++ ) VALUE[i] = value[i]; \
+      } else { \
+         i = 0; \
+      } \
+      for( ; i < VALUE_length; i++ ) VALUE[i] = ' '; \
+      (void) astFree( (void *) name ); \
+   ) \
+   return RESULT; \
+}
+
+MAKE_AST_GETFITS(s,S,S)
+MAKE_AST_GETFITS(cn,CN,CN)
+
+#undef MAKE_AST_GETFITS
+
+F77_SUBROUTINE(ast_readfits)( INTEGER(THIS),
+                              INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_READFITS", NULL, 0 );
+   astWatchSTATUS(
+      astReadFits( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_writefits)( INTEGER(THIS),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_WRITEFITS", NULL, 0 );
+   astWatchSTATUS(
+      astWriteFits( astI2P( *THIS ) );
+   )
+}
+
+
+F77_SUBROUTINE(ast_emptyfits)( INTEGER(THIS),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_EMPTYFITS", NULL, 0 );
+   astWatchSTATUS(
+      astEmptyFits( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_showfits)( INTEGER(THIS),
+                              INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_SHOWFITS", NULL, 0 );
+   astWatchSTATUS(
+      astShowFits( astI2P( *THIS ) );
+   )
+}
+
+
+F77_INTEGER_FUNCTION(ast_gettables)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETTABLES", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astGetTables( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_removetables)( INTEGER(THIS),
+                                  CHARACTER(KEY),
+                                  INTEGER(STATUS)
+                                  TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   char *key;
+
+   astAt( "AST_REMOVETABLES", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      astRemoveTables( astI2P( *THIS ), key );
+      (void) astFree( (void *) key );
+   )
+}
+
+F77_SUBROUTINE(ast_puttable)( INTEGER(THIS),
+                              INTEGER(TABLE),
+                              CHARACTER(EXTNAM),
+                              INTEGER(STATUS)
+                              TRAIL(EXTNAM) ){
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(TABLES)
+   GENPTR_CHARACTER(EXTNAM)
+   char *extnam;
+
+   astAt( "AST_PUTTABLE", NULL, 0 );
+   astWatchSTATUS(
+      extnam = astString( EXTNAM, EXTNAM_length );
+      astPutTable( astI2P( *THIS ), astI2P( *TABLE ), extnam );
+      extnam = astFree( extnam );
+   )
+}
+
+F77_SUBROUTINE(ast_puttables)( INTEGER(THIS),
+                               INTEGER(TABLES),
+                               INTEGER(STATUS) ){
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(TABLES)
+
+   astAt( "AST_PUTTABLES", NULL, 0 );
+   astWatchSTATUS(
+      astPutTables( astI2P( *THIS ), astI2P( *TABLES ) );
+   )
+}
+
+F77_SUBROUTINE(ast_tablesource)( INTEGER(THIS),
+                                 void (* SOURCE)(),
+                                 INTEGER(STATUS) ){
+   GENPTR_INTEGER(THIS)
+   void (* source)( void );
+
+   astAt( "AST_TABLESOURCE", NULL, 0 );
+   astWatchSTATUS(
+      source = (void (*)( void )) SOURCE;
+      if ( source == (void (*)( void )) F77_EXTERNAL_NAME(ast_null) ) {
+         source = NULL;
+      }
+      astSetTableSource( astI2P( *THIS ), source, TabSourceWrap );
+   )
+}
+
+
diff --git a/ast/ffitstable.c b/ast/ffitstable.c
new file mode 100644
index 0000000..9f2db37
--- /dev/null
+++ b/ast/ffitstable.c
@@ -0,0 +1,234 @@
+/*
+*+
+*  Name:
+*     ffitstable.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST FitsTable class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the FitsTable class.
+
+*  Routines Defined:
+*     AST_ISAFITSTABLE
+*     AST_FITSTABLE
+
+*  Copyright:
+*     Copyright (C) 2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S.Berry (Starlink)
+
+*  History:
+*     25-NOV-2010 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "ast_err.h"             /* AST error codes */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "fitstable.h"           /* C interface to the FitsTable class */
+
+F77_LOGICAL_FUNCTION(ast_isafitstable)( INTEGER(THIS),
+                                        INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astWatchSTATUS(
+   astAt( "AST_ISAFITSTABLE", NULL, 0 );
+      RESULT = astIsAFitsTable( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_fitstable)( INTEGER(HEADER),
+                                     CHARACTER(OPTIONS),
+                                     INTEGER(STATUS)
+                                     TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(HEADER)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_FITSTABLE", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astFitsTable( astI2P( *HEADER ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_gettableheader)( INTEGER(THIS),
+                                          INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETTABLEHEADER", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astGetTableHeader( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+
+F77_SUBROUTINE(ast_puttableheader)( INTEGER(THIS),
+                                    INTEGER(HEADER),
+                                    INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(HEADER)
+
+   astAt( "AST_PUTTABLEHEADER", NULL, 0 );
+   astWatchSTATUS(
+      astPutTableHeader( astI2P( *THIS ), astI2P( *HEADER ) );
+   )
+}
+
+F77_INTEGER_FUNCTION(ast_columnnull)( INTEGER(THIS),
+                                      CHARACTER(COLUMN),
+                                      LOGICAL(SET),
+                                      INTEGER(NEWVAL),
+                                      LOGICAL(WASSET),
+                                      LOGICAL(HASNULL),
+                                      INTEGER(STATUS)
+                                      TRAIL(COLUMN) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(COLUMN)
+   GENPTR_LOGICAL(SET)
+   GENPTR_INTEGER(NEWVAL)
+   GENPTR_LOGICAL(WASSET)
+   GENPTR_LOGICAL(HASNULL)
+   F77_INTEGER_TYPE(RESULT);
+   int wasset, hasnull;
+   char *column;
+
+   astAt( "AST_COLUMNNULL", NULL, 0 );
+   astWatchSTATUS(
+      column = astString( COLUMN, COLUMN_length );
+      RESULT = astColumnNull( astI2P( *THIS ), column,
+                              F77_ISTRUE( *SET ) ? 1 : 0, *NEWVAL,
+                              &wasset, &hasnull );
+      *WASSET = wasset ? F77_TRUE : F77_FALSE;
+      *HASNULL = hasnull ? F77_TRUE : F77_FALSE;
+      astFree( column );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_columnsize)( INTEGER(THIS),
+                                      CHARACTER(COLUMN),
+                                      INTEGER(STATUS)
+                                      TRAIL(COLUMN) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(COLUMN)
+   F77_INTEGER_TYPE(RESULT);
+   char *column;
+   size_t result;
+
+   astAt( "AST_COLUMNSIZE", NULL, 0 );
+   astWatchSTATUS(
+      column = astString( COLUMN, COLUMN_length );
+      result = astColumnSize( astI2P( *THIS ), column );
+      astFree( column );
+
+      RESULT = result;
+      if( (size_t) RESULT != result && astOK ) {
+         astError( AST__BIGTAB, "AST_COLUMNSIZE(FitsTable): The number of bytes in the "
+                   "column is too large to fit in a Fortran INTEGER.", status );
+      }
+
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_getcolumndata)( INTEGER(THIS),
+                                   CHARACTER(COLUMN),
+                                   REAL(RNULL),
+                                   DOUBLE(DNULL),
+                                   INTEGER(MXSIZE),
+                                   BYTE_ARRAY(COLDATA),
+                                   INTEGER(NELEM),
+                                   INTEGER(STATUS)
+                                   TRAIL(COLUMN) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(COLUMN)
+   GENPTR_REAL(RNULL)
+   GENPTR_DOUBLE(DNULL)
+   GENPTR_INTEGER(MXSIZE)
+   GENPTR_BYTE_ARRAY(COLDATA)
+   GENPTR_INTEGER(NELEM)
+   char *column;
+
+   astAt( "AST_GETCOLUMNDATA", NULL, 0 );
+   astWatchSTATUS(
+      column = astString( COLUMN, COLUMN_length );
+      astGetColumnData( astI2P( *THIS ), column, *RNULL, *DNULL, *MXSIZE,
+                        (void *) COLDATA, NELEM );
+      astFree( column );
+   )
+}
+
+F77_SUBROUTINE(ast_putcolumndata)( INTEGER(THIS),
+                                   CHARACTER(COLUMN),
+                                   INTEGER(CLEN),
+                                   INTEGER(SIZE),
+                                   BYTE_ARRAY(COLDATA),
+                                   INTEGER(STATUS)
+                                   TRAIL(COLUMN) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(COLUMN)
+   GENPTR_INTEGER(CLEN)
+   GENPTR_INTEGER(SIZE)
+   GENPTR_BYTE_ARRAY(COLDATA)
+   char *column;
+
+   astAt( "AST_PUTCOLUMNDATA", NULL, 0 );
+   astWatchSTATUS(
+      column = astString( COLUMN, COLUMN_length );
+      astPutColumnData( astI2P( *THIS ), column, *CLEN, *SIZE, (void *) COLDATA );
+      astFree( column );
+   )
+}
+
diff --git a/ast/ffluxframe.c b/ast/ffluxframe.c
new file mode 100644
index 0000000..ab55e80
--- /dev/null
+++ b/ast/ffluxframe.c
@@ -0,0 +1,104 @@
+/*
+*+
+*  Name:
+*     ffluxframe.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST FluxFrame class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the FluxFrame class.
+
+*  Routines Defined:
+*     AST_ISAFLUXFRAME
+*     AST_FLUXFRAME
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     1-DEC-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "fluxframe.h"           /* C interface to the FluxFrame class */
+
+F77_LOGICAL_FUNCTION(ast_isafluxframe)( INTEGER(THIS),
+                                        INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAFLUXFRAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAFluxFrame( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_fluxframe)( DOUBLE(SPECVAL),
+                                     INTEGER(SPECFRM),
+                                     CHARACTER(OPTIONS),
+                                     INTEGER(STATUS)
+                                     TRAIL(OPTIONS) ) {
+   GENPTR_DOUBLE(SPECVAL)
+   GENPTR_INTEGER(SPECFRM)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_FLUXFRAME", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astFluxFrame( *SPECVAL, astI2P( *SPECFRM ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
diff --git a/ast/fframe.c b/ast/fframe.c
new file mode 100644
index 0000000..6a71cc1
--- /dev/null
+++ b/ast/fframe.c
@@ -0,0 +1,514 @@
+/*
+*+
+*  Name:
+*     fframe.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Frame class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Frame class.
+
+*  Routines Defined:
+*     AST_ANGLE
+*     AST_AXANGLE
+*     AST_AXDISTANCE
+*     AST_AXOFFSET
+*     AST_CONVERT
+*     AST_DISTANCE
+*     AST_FORMAT
+*     AST_FRAME
+*     AST_GETACTIVEUNIT
+*     AST_INTERSECT
+*     AST_ISAFRAME
+*     AST_NORM
+*     AST_OFFSET
+*     AST_OFFSET2
+*     AST_PERMAXES
+*     AST_PICKAXES
+*     AST_RESOLVE
+*     AST_SETACTIVEUNIT
+*     AST_UNFORMAT
+
+*  Copyright:
+*     Copyright (C) 1997-2009 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     23-JUL-1996 (RFWS):
+*        Original version.
+*     16-SEP-1996 (RFWS):
+*        Added AST_DISTANCE and AST_OFFSET.
+*     25-FEB-1998 (RFWS):
+*        Added AST_UNFORMAT.
+*     21-JUN-2001 (DSB):
+*        Added AST_ANGLE and AST_OFFSET2.
+*     29-AUG-2001 (DSB):
+*        Added AST_AXDISTANCE and AST_AXOFFSET.
+*     9-SEP-2001 (DSB):
+*        Added AST_RESOLVE and AST_BEAR.
+*     21-SEP-2001 (DSB):
+*        Replaced AST_BEAR by AST_AXANGLE.
+*     17-DEC-2002 (DSB):
+*        Added AST_GETACTIVEUNIT and AST_SETACTIVEUNIT.
+*     14-JAN-2009 (DSB):
+*        Added AST_INTERSECT.
+*     26-OCT-2016 (DSB):
+*        Added method AST_AXNORM.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "mapping.h"             /* C interface to the Mapping class */
+#include "frame.h"               /* C interface to the Frame class */
+
+
+F77_INTEGER_FUNCTION(ast_convert)( INTEGER(FROM),
+                                   INTEGER(TO),
+                                   CHARACTER(NAMELIST),
+                                   INTEGER(STATUS)
+                                   TRAIL(NAMELIST) ) {
+   GENPTR_INTEGER(FROM)
+   GENPTR_INTEGER(TO)
+   GENPTR_INTEGER(NAMELIST)
+   F77_INTEGER_TYPE(RESULT);
+   char *namelist;
+
+   astAt( "AST_CONVERT", NULL, 0 );
+   astWatchSTATUS(
+      namelist = astString( NAMELIST, NAMELIST_length );
+      RESULT = astP2I( astConvert( astI2P( *FROM ), astI2P( *TO ),
+                                   namelist ) );
+      namelist = astFree( namelist );
+   )
+   return RESULT;
+}
+
+F77_DOUBLE_FUNCTION(ast_angle)( INTEGER(THIS),
+                                   DOUBLE_ARRAY(A),
+                                   DOUBLE_ARRAY(B),
+                                   DOUBLE_ARRAY(C),
+                                   INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(A)
+   GENPTR_DOUBLE_ARRAY(B)
+   GENPTR_DOUBLE_ARRAY(C)
+   F77_DOUBLE_TYPE(RESULT);
+
+   astAt( "AST_ANGLE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astAngle( astI2P( *THIS ), A, B, C );
+   )
+   return RESULT;
+}
+
+F77_DOUBLE_FUNCTION(ast_axangle)( INTEGER(THIS),
+                                  DOUBLE_ARRAY(A),
+                                  DOUBLE_ARRAY(B),
+                                  INTEGER(AXIS),
+                                  INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(A)
+   GENPTR_DOUBLE_ARRAY(B)
+   GENPTR_INTEGER(AXIS)
+   F77_DOUBLE_TYPE(RESULT);
+
+   astAt( "AST_AXANGLE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astAxAngle( astI2P( *THIS ), A, B, *AXIS );
+   )
+   return RESULT;
+}
+
+F77_DOUBLE_FUNCTION(ast_distance)( INTEGER(THIS),
+                                   DOUBLE_ARRAY(POINT1),
+                                   DOUBLE_ARRAY(POINT2),
+                                   INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(POINT1)
+   GENPTR_DOUBLE_ARRAY(POINT2)
+   F77_DOUBLE_TYPE(RESULT);
+
+   astAt( "AST_DISTANCE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astDistance( astI2P( *THIS ), POINT1, POINT2 );
+   )
+   return RESULT;
+}
+
+F77_DOUBLE_FUNCTION(ast_axdistance)( INTEGER(THIS),
+                                     INTEGER(AXIS),
+                                     DOUBLE(V1),
+                                     DOUBLE(V2),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(AXIS)
+   GENPTR_DOUBLE(V1)
+   GENPTR_DOUBLE(V2)
+   F77_DOUBLE_TYPE(RESULT);
+
+   astAt( "AST_AXDISTANCE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astAxDistance( astI2P( *THIS ), *AXIS, *V1, *V2 );
+   )
+   return RESULT;
+}
+
+F77_DOUBLE_FUNCTION(ast_axoffset)( INTEGER(THIS),
+                                   INTEGER(AXIS),
+                                   DOUBLE(V1),
+                                   DOUBLE(DIST),
+                                   INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(AXIS)
+   GENPTR_DOUBLE(V1)
+   GENPTR_DOUBLE(DIST)
+   F77_DOUBLE_TYPE(RESULT);
+
+   astAt( "AST_AXOFFSET", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astAxOffset( astI2P( *THIS ), *AXIS, *V1, *DIST );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_findframe)( INTEGER(TARGET),
+                                     INTEGER(TEMPLATE),
+                                     CHARACTER(NAMELIST),
+                                     INTEGER(STATUS)
+                                     TRAIL(NAMELIST) ) {
+   GENPTR_INTEGER(TARGET)
+   GENPTR_INTEGER(TEMPLATE)
+   GENPTR_INTEGER(NAMELIST)
+   F77_INTEGER_TYPE(RESULT);
+   char *namelist;
+
+   astAt( "AST_FINDFRAME", NULL, 0 );
+   astWatchSTATUS(
+      namelist = astString( NAMELIST, NAMELIST_length );
+      RESULT = astP2I( astFindFrame( astI2P( *TARGET ), astI2P( *TEMPLATE ),
+                                     namelist ) );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_matchaxes)( INTEGER(FRM1),
+                               INTEGER(FRM2),
+                               INTEGER_ARRAY(AXES),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(FRM1)
+   GENPTR_INTEGER(FRM2)
+   GENPTR_INTEGER_ARRAY(AXES)
+
+   astAt( "AST_MATCHAXES", NULL, 0 );
+   astWatchSTATUS(
+      astMatchAxes( astI2P( *FRM1 ), astI2P( *FRM2 ), AXES );
+   )
+}
+
+/* NO_CHAR_FUNCTION indicates that the f77.h method of returning a
+   character result doesn't work, so add an extra argument instead and
+   wrap this function up in a normal FORTRAN 77 function (in the file
+   frame.f). */
+#if NO_CHAR_FUNCTION
+F77_SUBROUTINE(ast_format_a)( CHARACTER(RESULT),
+#else
+F77_SUBROUTINE(ast_format)( CHARACTER_RETURN_VALUE(RESULT),
+#endif
+                            INTEGER(THIS),
+                            INTEGER(AXIS),
+                            DOUBLE(VALUE),
+                            INTEGER(STATUS)
+#if NO_CHAR_FUNCTION
+                            TRAIL(RESULT)
+#endif
+                              ) {
+   GENPTR_CHARACTER(RESULT)
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(AXIS)
+   GENPTR_DOUBLE(VALUE)
+   const char *result;
+   int i;
+
+   astAt( "AST_FORMAT", NULL, 0 );
+   astWatchSTATUS(
+      result = astFormat( astI2P( *THIS ), *AXIS, *VALUE );
+      i = 0;
+      if ( astOK ) {             /* Copy result */
+         for ( ; result[ i ] && i < RESULT_length; i++ ) {
+            RESULT[ i ] = result[ i ];
+         }
+      }
+      while ( i < RESULT_length ) RESULT[ i++ ] = ' '; /* Pad with blanks */
+   )
+}
+
+F77_INTEGER_FUNCTION(ast_frame)( INTEGER(NAXES),
+                                 CHARACTER(OPTIONS),
+                                 INTEGER(STATUS)
+                                 TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NAXES)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_FRAME", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exclude any trailing spaces. */
+      astChrTrunc( options );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astFrame( *NAXES, "%s", options ) );
+      (void) astFree( options );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_isaframe)( INTEGER(THIS),
+                                    INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAFRAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAFrame( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_getactiveunit)( INTEGER(THIS),
+                                         INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_GETACTIVEUNIT", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astGetActiveUnit( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_setactiveunit)( INTEGER(THIS),
+                                   LOGICAL(VALUE),
+                                   INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_LOGICAL(VALUE)
+
+   astAt( "AST_SETACTIVEUNIT", NULL, 0 );
+   astWatchSTATUS(
+      astSetActiveUnit( astI2P( *THIS ), F77_ISTRUE( *VALUE ) ? 1 : 0 );
+   )
+}
+
+F77_SUBROUTINE(ast_norm)( INTEGER(THIS),
+                          DOUBLE_ARRAY(VALUE),
+                          INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(VALUE)
+
+   astAt( "AST_NORM", NULL, 0 );
+   astWatchSTATUS(
+      astNorm( astI2P( *THIS ), VALUE );
+   )
+}
+
+F77_SUBROUTINE(ast_offset)( INTEGER(THIS),
+                            DOUBLE_ARRAY(POINT1),
+                            DOUBLE_ARRAY(POINT2),
+                            DOUBLE(OFFSET),
+                            DOUBLE_ARRAY(POINT3),
+                            INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(POINT1)
+   GENPTR_DOUBLE_ARRAY(POINT2)
+   GENPTR_DOUBLE(OFFSET)
+   GENPTR_DOUBLE_ARRAY(POINT3)
+
+   astAt( "AST_OFFSET", NULL, 0 );
+   astWatchSTATUS(
+      astOffset( astI2P( *THIS ), POINT1, POINT2, *OFFSET, POINT3 );
+   )
+}
+
+F77_DOUBLE_FUNCTION(ast_offset2)( INTEGER(THIS),
+                             DOUBLE_ARRAY(POINT1),
+                             DOUBLE(ANGLE),
+                             DOUBLE(OFFSET),
+                             DOUBLE_ARRAY(POINT2),
+                             INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(POINT1)
+   GENPTR_DOUBLE(ANGLE)
+   GENPTR_DOUBLE(OFFSET)
+   GENPTR_DOUBLE_ARRAY(POINT2)
+   F77_DOUBLE_TYPE(RESULT);
+
+   astAt( "AST_OFFSET2", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astOffset2( astI2P( *THIS ), POINT1, *ANGLE, *OFFSET, POINT2 );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_resolve)( INTEGER(THIS),
+                             DOUBLE_ARRAY(POINT1),
+                             DOUBLE_ARRAY(POINT2),
+                             DOUBLE_ARRAY(POINT3),
+                             DOUBLE_ARRAY(POINT4),
+                             DOUBLE(D1),
+                             DOUBLE(D2),
+                             INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(POINT1)
+   GENPTR_DOUBLE_ARRAY(POINT2)
+   GENPTR_DOUBLE_ARRAY(POINT3)
+   GENPTR_DOUBLE_ARRAY(POINT4)
+   GENPTR_DOUBLE(D1)
+   GENPTR_DOUBLE(D2)
+
+   astAt( "AST_RESOLVE", NULL, 0 );
+   astWatchSTATUS(
+      astResolve( astI2P( *THIS ), POINT1, POINT2, POINT3, POINT4, D1, D2 );
+   )
+}
+
+F77_SUBROUTINE(ast_intersect)( INTEGER(THIS),
+                               DOUBLE_ARRAY(A1),
+                               DOUBLE_ARRAY(A2),
+                               DOUBLE_ARRAY(B1),
+                               DOUBLE_ARRAY(B2),
+                               DOUBLE_ARRAY(CROSS),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(A1)
+   GENPTR_DOUBLE_ARRAY(A2)
+   GENPTR_DOUBLE_ARRAY(B1)
+   GENPTR_DOUBLE_ARRAY(B2)
+   GENPTR_DOUBLE_ARRAY(CROSS)
+
+   astAt( "AST_INTERSECT", NULL, 0 );
+   astWatchSTATUS(
+      astIntersect( astI2P( *THIS ), A1, A2, B1, B2, CROSS );
+   )
+}
+
+F77_SUBROUTINE(ast_permaxes)( INTEGER(THIS),
+                              INTEGER_ARRAY(PERM),
+                              INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER_ARRAY(PERM)
+
+   astAt( "AST_PERMAXES", NULL, 0 );
+   astWatchSTATUS(
+      astPermAxes( astI2P( *THIS ), PERM );
+   )
+}
+
+F77_INTEGER_FUNCTION(ast_pickaxes)( INTEGER(THIS),
+                                    INTEGER(NAXES),
+                                    INTEGER_ARRAY(AXES),
+                                    INTEGER(MAP),
+                                    INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(NAXES)
+   GENPTR_INTEGER_ARRAY(AXES)
+   GENPTR_INTEGER(MAP)
+   F77_INTEGER_TYPE(RESULT);
+   AstMapping *map;
+
+   astAt( "AST_PICKAXES", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astPickAxes( astI2P( *THIS ), *NAXES, AXES, &map ) );
+      *MAP = astP2I( map );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_unformat)( INTEGER(THIS),
+                                    INTEGER(AXIS),
+                                    CHARACTER(STRING),
+                                    DOUBLE(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(STRING) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(AXIS)
+   GENPTR_CHARACTER(STRING)
+   GENPTR_DOUBLE(VALUE)
+   GENPTR_INTEGER(STATUS)
+   F77_INTEGER_TYPE(RESULT);
+   char *string;
+   double value;
+
+   astAt( "AST_UNFORMAT", NULL, 0 );
+   astWatchSTATUS(
+      string = astString( STRING, STRING_length );
+
+      RESULT = astUnformat( astI2P( *THIS ), *AXIS, string, &value );
+      *VALUE = value;
+      (void) astFree( string );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_axnorm)( INTEGER(THIS),
+                            INTEGER(AXIS),
+                            INTEGER(OPER),
+                            INTEGER(NVAL),
+                            DOUBLE(VALUES),
+                            INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(AXIS)
+   GENPTR_INTEGER(OPER)
+   GENPTR_INTEGER(NVAL)
+   GENPTR_DOUBLE(VALUES)
+
+   astAt( "AST_AXNORM", NULL, 0 );
+   astWatchSTATUS(
+      astAxNorm( astI2P( *THIS ), *AXIS, *OPER, *NVAL, VALUES );
+   )
+}
diff --git a/ast/fframeset.c b/ast/fframeset.c
new file mode 100644
index 0000000..1698847
--- /dev/null
+++ b/ast/fframeset.c
@@ -0,0 +1,214 @@
+/*
+*+
+*  Name:
+*     fframeset.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST FrameSet class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the FrameSet class.
+
+*  Routines Defined:
+*     AST_ADDFRAME
+*     AST_ADDVARIANT
+*     AST_MIRRORVARIANTS
+*     AST_FRAMESET
+*     AST_GETFRAME
+*     AST_GETMAPPING
+*     AST_ISAFRAMESET
+*     AST_REMAPFRAME
+*     AST_REMOVEFRAME
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     1-AUG-1996 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "mapping.h"             /* C interface to the Mapping class */
+#include "frame.h"               /* C interface to the Frame class */
+#include "frameset.h"            /* C interface to the FrameSet class */
+
+F77_SUBROUTINE(ast_addframe)( INTEGER(THIS),
+                              INTEGER(IFRAME),
+                              INTEGER(MAP),
+                              INTEGER(FRAME),
+                              INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(IFRAME)
+   GENPTR_INTEGER(MAP)
+   GENPTR_INTEGER(FRAME)
+
+   astAt( "AST_ADDFRAME", NULL, 0 );
+   astWatchSTATUS(
+      astAddFrame( astI2P( *THIS ), *IFRAME, astI2P( *MAP ),
+                   astI2P( *FRAME ) );
+   )
+}
+
+F77_INTEGER_FUNCTION(ast_frameset)( INTEGER(FRAME),
+                                    CHARACTER(OPTIONS),
+                                    INTEGER(STATUS)
+                                    TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_FRAMESET", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astFrameSet( astI2P( *FRAME ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_getframe)( INTEGER(THIS),
+                                    INTEGER(IFRAME),
+                                    INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(IFRAME)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETFRAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astGetFrame( astI2P( *THIS ), *IFRAME ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_getmapping)( INTEGER(THIS),
+                                      INTEGER(IFRAME1),
+                                      INTEGER(IFRAME2),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(IFRAME1)
+   GENPTR_INTEGER(IFRAME2)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETMAPPING", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astGetMapping( astI2P( *THIS ), *IFRAME1, *IFRAME2 ) );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_isaframeset)( INTEGER(THIS),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAFRAMESET", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAFrameSet( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_remapframe)( INTEGER(THIS),
+                                INTEGER(IFRAME),
+                                INTEGER(MAP),
+                                INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(IFRAME)
+   GENPTR_INTEGER(MAP)
+
+   astAt( "AST_REMAPFRAME", NULL, 0 );
+   astWatchSTATUS(
+      astRemapFrame( astI2P( *THIS ), *IFRAME, astI2P( *MAP ) );
+   )
+}
+
+F77_SUBROUTINE(ast_removeframe)( INTEGER(THIS),
+                                 INTEGER(IFRAME),
+                                 INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(IFRAME)
+
+   astAt( "AST_REMOVEFRAME", NULL, 0 );
+   astWatchSTATUS(
+      astRemoveFrame( astI2P( *THIS ), *IFRAME );
+   )
+}
+
+F77_SUBROUTINE(ast_addvariant)( INTEGER(THIS),
+                                INTEGER(MAP),
+                                CHARACTER(NAME),
+                                INTEGER(STATUS)
+                                TRAIL(NAME) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(MAP)
+   GENPTR_CHARACTER(NAME)
+   char *name;
+
+   astAt( "AST_ADDVARIANT", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      astAddVariant( astI2P( *THIS ), astI2P( *MAP ), name );
+      name = astFree( name );
+   )
+}
+
+F77_SUBROUTINE(ast_mirrorvariants)( INTEGER(THIS),
+                                    INTEGER(IFRAME),
+                                    INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(IFRAME)
+
+   astAt( "AST_MIRRORVARIANTS", NULL, 0 );
+   astWatchSTATUS(
+      astMirrorVariants( astI2P( *THIS ), *IFRAME );
+   )
+}
+
diff --git a/ast/fgrismmap.c b/ast/fgrismmap.c
new file mode 100644
index 0000000..f26159c
--- /dev/null
+++ b/ast/fgrismmap.c
@@ -0,0 +1,99 @@
+/*
+*+
+*  Name:
+*     fgrismmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST GrismMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the GrismMap class.
+
+*  Routines Defined:
+*     AST_ISAGRISMMAP
+*     AST_GRISMMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     10-JUL-2003 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "grismmap.h"            /* C interface to the GrismMap class */
+
+F77_LOGICAL_FUNCTION(ast_isagrismmap)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAGRISMMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAGrismMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_grismmap)( CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_GRISMMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astGrismMap( "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/finterval.c b/ast/finterval.c
new file mode 100644
index 0000000..7023868
--- /dev/null
+++ b/ast/finterval.c
@@ -0,0 +1,109 @@
+/*
+*+
+*  Name:
+*     finterval.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Interval class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Interval class.
+
+*  Routines Defined:
+*     AST_ISAINTERVAL
+*     AST_INTERVAL
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     31-AUG-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "interval.h"              /* C interface to the Interval class */
+
+
+F77_LOGICAL_FUNCTION(ast_isainterval)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAINTERVAL", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAInterval( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_interval)( INTEGER(FRAME),
+                                    DOUBLE_ARRAY(LBND),
+                                    DOUBLE_ARRAY(UBND),
+                                    INTEGER(UNC),
+                                    CHARACTER(OPTIONS),
+                                    INTEGER(STATUS)
+                                    TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_DOUBLE_ARRAY(LBND)
+   GENPTR_DOUBLE_ARRAY(UBND)
+   GENPTR_INTEGER(UNC)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_INTERVAL", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+      RESULT = astP2I( astInterval( astI2P( *FRAME ), LBND, UBND,
+                                    astI2P( *UNC ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fintramap.c b/ast/fintramap.c
new file mode 100644
index 0000000..cdf3e4e
--- /dev/null
+++ b/ast/fintramap.c
@@ -0,0 +1,332 @@
+/*
+*+
+*  Name:
+*     fintramap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST IntraMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the IntraMap class.
+
+*  Routines Defined:
+*     AST_INTRAMAP
+*     AST_INTRAREG
+*     AST_ISAINTRAMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     18-MAR-1998 (RFWS):
+*        Original version.
+*     15-SEP-1999 (RFWS):
+*        Added a THIS pointer to the external transformation function
+*        used by an IntraMap.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "intramap.h"            /* C interface to the IntraMap class */
+
+#include <stddef.h>
+#include <string.h>
+
+/* Prototypes for private functions. */
+/* ================================= */
+static void TranWrap( void (*)( AstMapping *, int, int, const double *[], int, int, double *[] ), AstMapping *, int, int, const double *[], int, int, double *[], int * );
+
+/* Transformation function interface. */
+/* ================================== */
+/* This is concerned with allowing FORTRAN implementations of
+   transformation functions to be passed to the IntraMap class and
+   invoked when necessary by C code in the main class
+   implementation. All FORTRAN-specific aspects of this interface are
+   encapsulated here. */
+static void TranWrap( void (* tran)( AstMapping *, int, int, const double *[],
+                                     int, int, double *[] ),
+                      AstMapping *this, int npoint, int ncoord_in,
+                      const double *ptr_in[], int forward, int ncoord_out,
+                      double *ptr_out[], int *status ) {
+/*
+*  Name:
+*     TranWrap
+
+*  Purpose:
+*     Wrapper function to invoke a FORTRAN transformation function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void TranWrap( void (* tran)( AstMapping *, int, int, const double *[],
+*                                   int, int, double *[] ),
+*                    AstMapping *this, int npoint, int ncoord_in,
+*                    const double *ptr_in[], int forward, int ncoord_out,
+*                    double *ptr_out[], int *status )
+
+*  Description:
+*     This function invokes a FORTRAN implementation of a
+*     transformation function (which resembles AST_TRANN from the
+*     Mapping class FORTRAN interface) in order to make it callable by
+*     C code which would prefer to call a C function that resembles
+*     astTranP (from the Mapping class C interface).
+
+*  Parameters:
+*     tran
+*        Pointer to the FORTRAN transformation function to be invoked.
+*        This should result from a cast applied to a pointer to a
+*        function that resembles AST_TRANN (but with the first
+*        argument omitted).
+*     this
+*        An external Mapping ID associated with the internal (true C) pointer
+*        for the IntraMap whose transformation is being evaluated.
+*     npoint
+*        The number of points to be transformed.
+*     ncoord_in
+*        The number of coordinates being supplied for each input point
+*        (i.e. the number of dimensions of the space in which the
+*        input points reside).
+*     ptr_in
+*        An array of pointers to double, with "ncoord_in"
+*        elements. Element "ptr_in[coord]" should point at the first
+*        element of an array of double (with "npoint" elements) which
+*        contain the values of coordinate number "coord" for each
+*        input (untransformed) point. The value of coordinate number
+*        "coord" for input point number "point" is therefore given by
+*        "ptr_in[coord][point]".
+*     forward
+*        A non-zero value indicates that the forward coordinate
+*        transformation is to be applied, while a zero value indicates
+*        that the inverse transformation should be used.
+*     ncoord_out
+*        The number of coordinates being generated for each output
+*        point (i.e. the number of dimensions of the space in which
+*        the output points reside). This need not be the same as
+*        "ncoord_in".
+*     ptr_out
+*        An array of pointers to double, with "ncoord_out"
+*        elements. Element "ptr_out[coord]" should point at the first
+*        element of an array of double (with "npoint" elements) into
+*        which the values of coordinate number "coord" for each output
+*        (transformed) point will be written.  The value of coordinate
+*        number "coord" for output point number "point" will therefore
+*        be found in "ptr_out[coord][point]".
+*     status
+*        Pointer to the inherited status value.
+*/
+
+/* Local Variables; */
+   DECLARE_INTEGER(INDIM);       /* First dimension size of input array */
+   DECLARE_INTEGER(NCOORD_IN);   /* Number of input coordinates */
+   DECLARE_INTEGER(NCOORD_OUT);  /* Number of output coordinates */
+   DECLARE_INTEGER(NPOINT);      /* Number of points */
+   DECLARE_INTEGER(OUTDIM);      /* First dimension size of output array */
+   DECLARE_INTEGER(STATUS);      /* FORTRAN error status variable */
+   DECLARE_INTEGER(THIS);        /* External ID for the IntraMap */
+   DECLARE_LOGICAL(FORWARD);     /* Use forward transformation? */
+   F77_DOUBLE_TYPE *IN;          /* Input coordinate array for FORTRAN */
+   F77_DOUBLE_TYPE *OUT;         /* Output coordinate array for FORTRAN */
+   int coord;                    /* Loop counter for coordinates */
+   int i;                        /* Index into FORTRAN arrays */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Assign input values to the arguments for the FORTRAN transformation
+   function. */
+   THIS = astP2I( this );
+   NPOINT = npoint;
+   NCOORD_IN = ncoord_in;
+   INDIM = npoint;
+   FORWARD = forward ? F77_TRUE : F77_FALSE;
+   NCOORD_OUT = ncoord_out;
+   OUTDIM = npoint;
+
+/* Since the input/output coordinate values may be stored in separate
+   arrays, we must move them temporarily into new 2-dimensional
+   arrays, as required by the FORTRAN transformation function
+   interface. Allocate memory for these arrays. */
+   IN = astMalloc( (size_t) ( npoint * ncoord_in ) *
+                   sizeof( F77_DOUBLE_TYPE ) );
+   OUT = astMalloc( (size_t) ( npoint * ncoord_out ) *
+                    sizeof( F77_DOUBLE_TYPE ) );
+
+/* If OK, fill the input array with coordinate values. Use "memcpy" to
+   avoid numerical errors if the data contain junk - this allows the
+   transformation function to produce the appropriate error instead of
+   it happening here. */
+   if ( astOK ) {
+      i = 0;
+      for ( coord = 0; coord < ncoord_in; coord++ ) {
+         (void) memcpy( IN + i, ptr_in[ coord ],
+                        (size_t) npoint * sizeof( F77_DOUBLE_TYPE ) );
+         i += npoint;
+      }
+   }
+
+/* Cast the transformation function pointer to a pointer to the
+   FORTRAN subroutine and then invoke it. Transfer the AST error
+   status to and from the subroutine's error status argument. */
+   if ( astOK ) {
+      STATUS = astStatus;
+      ( *(void (*)()) tran )( INTEGER_ARG(&THIS),
+                              INTEGER_ARG(&NPOINT),
+                              INTEGER_ARG(&NCOORD_IN),
+                              INTEGER_ARG(&INDIM),
+                              DOUBLE_ARRAY_ARG(IN),
+                              LOGICAL_ARG(&FORWARD),
+                              INTEGER_ARG(&NCOORD_OUT),
+                              INTEGER_ARG(&OUTDIM),
+                              DOUBLE_ARRAY_ARG(OUT),
+                              INTEGER_ARG(&STATUS) );
+      astSetStatus( STATUS );
+   }
+
+
+/* If OK, transfer the transformed coordinate values to the output
+   arrays. */
+   if ( astOK ) {
+      i = 0;
+      for ( coord = 0; coord < ncoord_out; coord++ ) {
+         (void) memcpy( ptr_out[ coord ], OUT + i,
+                        (size_t) npoint * sizeof( F77_DOUBLE_TYPE ) );
+         i += npoint;
+      }
+   }
+
+/* Free the temporary arrays. */
+   astFree( IN );
+   astFree( OUT );
+}
+
+/* FORTRAN interface functions. */
+/* ============================ */
+/* These functions implement the remainder of the FORTRAN interface. */
+F77_SUBROUTINE(ast_intrareg)( CHARACTER(NAME),
+                              INTEGER(NIN),
+                              INTEGER(NOUT),
+                              void (* TRAN)(),
+                              INTEGER(FLAGS),
+                              CHARACTER(PURPOSE),
+                              CHARACTER(AUTHOR),
+                              CHARACTER(CONTACT),
+                              INTEGER(STATUS)
+                              TRAIL(NAME)
+                              TRAIL(PURPOSE)
+                              TRAIL(AUTHOR)
+                              TRAIL(CONTACT) ) {
+   GENPTR_CHARACTER(NAME)
+   GENPTR_INTEGER(NIN)
+   GENPTR_INTEGER(NOUT)
+   GENPTR_INTEGER(FLAGS)
+   GENPTR_CHARACTER(PURPOSE)
+   GENPTR_CHARACTER(AUTHOR)
+   GENPTR_CHARACTER(CONTACT)
+   char *name;
+   void (* tran)( AstMapping *, int, int, const double *[], int, int,
+                  double *[] );
+   char *purpose;
+   char *author;
+   char *contact;
+
+   astAt( "AST_INTRAREG", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      tran =
+         (void (*)( AstMapping *, int, int, const double *[], int, int,
+                    double *[] )) TRAN;
+      purpose = astString( PURPOSE, PURPOSE_length );
+      author = astString( AUTHOR, AUTHOR_length );
+      contact = astString( CONTACT, CONTACT_length );
+      astIntraRegFor( name, *NIN, *NOUT, tran, TranWrap, *FLAGS, purpose,
+                      author, contact );
+      astFree( name );
+      astFree( purpose );
+      astFree( author );
+      astFree( contact );
+   )
+}
+
+F77_INTEGER_FUNCTION(ast_intramap)( CHARACTER(NAME),
+                                    INTEGER(NIN),
+                                    INTEGER(NOUT),
+                                    CHARACTER(OPTIONS),
+                                    INTEGER(STATUS)
+                                    TRAIL(NAME)
+                                    TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(NAME)
+   GENPTR_INTEGER(NIN)
+   GENPTR_INTEGER(NOUT)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *name;
+   char *options;
+   int i;
+
+   astAt( "AST_INTRAMAP", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astIntraMap( name, *NIN, *NOUT, "%s", options ) );
+      astFree( name );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_isaintramap)( INTEGER(THIS),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAINTRAMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAIntraMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
diff --git a/ast/fitschan.c b/ast/fitschan.c
new file mode 100644
index 0000000..72a567b
--- /dev/null
+++ b/ast/fitschan.c
@@ -0,0 +1,42623 @@
+/*
+*class++
+*  Name:
+*     FitsChan
+
+*  Purpose:
+*     I/O Channel using FITS header cards to represent Objects.
+
+*  Constructor Function:
+c     astFitsChan
+f     AST_FITSCHAN
+
+*  Description:
+*     A FitsChan is a specialised form of Channel which supports I/O
+*     operations involving the use of FITS (Flexible Image Transport
+*     System) header cards. Writing an Object to a FitsChan (using
+c     astWrite) will, if the Object is suitable, generate a
+f     AST_WRITE) will, if the Object is suitable, generate a
+*     description of that Object composed of FITS header cards, and
+*     reading from a FitsChan will create a new Object from its FITS
+*     header card description.
+*
+*     While a FitsChan is active, it represents a buffer which may
+*     contain zero or more 80-character "header cards" conforming to
+*     FITS conventions. Any sequence of FITS-conforming header cards
+*     may be stored, apart from the "END" card whose existence is
+*     merely implied.  The cards may be accessed in any order by using
+*     the FitsChan's integer Card attribute, which identifies a "current"
+*     card, to which subsequent operations apply. Searches
+c     based on keyword may be performed (using astFindFits), new
+c     cards may be inserted (astPutFits, astPutCards, astSetFits<X>) and
+c     existing ones may be deleted (astDelFits), extracted (astGetFits<X>),
+c     or changed (astSetFits<X>).
+f     based on keyword may be performed (using AST_FINDFITS), new
+f     cards may be inserted (AST_PUTFITS, AST_PUTCARDS, AST_SETFITS<X>) and
+f     existing ones may be deleted (AST_DELFITS), extracted
+f     (AST_GETFITS<X>) or changed (AST_SETFITS<X>).
+*
+*     When you create a FitsChan, you have the option of specifying
+*     "source" and "sink" functions which connect it to external data
+*     stores by reading and writing FITS header cards. If you provide
+*     a source function, it is used to fill the FitsChan with header cards
+*     when it is accessed for the first time. If you do not provide a
+*     source function, the FitsChan remains empty until you explicitly enter
+c     data into it (e.g. using astPutFits, astPutCards, astWrite
+f     data into it (e.g. using AST_PUTFITS, AST_PUTCARDS, AST_WRITE
+*     or by using the SourceFile attribute to specifying a text file from
+*     which headers should be read). When the FitsChan is deleted, any
+*     remaining header cards in the FitsChan can be saved in either of
+*     two ways: 1) by specifying a value for the SinkFile attribute (the
+*     name of a text file to which header cards should be written), or 2)
+*     by providing a sink function (used to to deliver header cards to an
+*     external data store). If you do not provide a sink function or a
+*     value for SinkFile, any header cards remaining when the FitsChan
+*     is deleted will be lost, so you should arrange to extract them
+*     first if necessary
+c     (e.g. using astFindFits or astRead).
+f     (e.g. using AST_FINDFITS or AST_READ).
+*
+*     Coordinate system information may be described using FITS header
+*     cards using several different conventions, termed
+*     "encodings". When an AST Object is written to (or read from) a
+*     FitsChan, the value of the FitsChan's Encoding attribute
+*     determines how the Object is converted to (or from) a
+*     description involving FITS header cards. In general, different
+*     encodings will result in different sets of header cards to
+*     describe the same Object. Examples of encodings include the DSS
+*     encoding (based on conventions used by the STScI Digitised Sky
+*     Survey data), the FITS-WCS encoding (based on a proposed FITS
+*     standard) and the NATIVE encoding (a near loss-less way of
+*     storing AST Objects in FITS headers).
+*
+*     The available encodings differ in the range of Objects they can
+*     represent, in the number of Object descriptions that can coexist
+*     in the same FitsChan, and in their accessibility to other
+*     (external) astronomy applications (see the Encoding attribute
+*     for details). Encodings are not necessarily mutually exclusive
+*     and it may sometimes be possible to describe the same Object in
+*     several ways within a particular set of FITS header cards by
+*     using several different encodings.
+*
+c     The detailed behaviour of astRead and astWrite, when used with
+f     The detailed behaviour of AST_READ and AST_WRITE, when used with
+*     a FitsChan, depends on the encoding in use. In general, however,
+c     all successful use of astRead is destructive, so that FITS header cards
+f     all successful use of AST_READ is destructive, so that FITS header cards
+*     are consumed in the process of reading an Object, and are
+*     removed from the FitsChan (this deletion can be prevented for
+*     specific cards by calling the
+c     astRetainFits function).
+f     AST_RETAINFITS routine).
+*     An unsuccessful call of
+c     astRead
+f     AST_READ
+*     (for instance, caused by the FitsChan not containing the necessary
+*     FITS headers cards needed to create an Object) results in the
+*     contents of the FitsChan being left unchanged.
+*
+*     If the encoding in use allows only a single Object description
+*     to be stored in a FitsChan (e.g. the DSS, FITS-WCS and FITS-IRAF
+c     encodings), then write operations using astWrite will
+f     encodings), then write operations using AST_WRITE will
+*     over-write any existing Object description using that
+*     encoding. Otherwise (e.g. the NATIVE encoding), multiple Object
+*     descriptions are written sequentially and may later be read
+*     back in the same sequence.
+
+*  Inheritance:
+*     The FitsChan class inherits from the Channel class.
+
+*  Attributes:
+*     In addition to those attributes common to all Channels, every
+
+*     FitsChan also has the following attributes:
+*
+*     - AllWarnings: A list of the available conditions
+*     - Card: Index of current FITS card in a FitsChan
+*     - CardComm: The comment of the current FITS card in a FitsChan
+*     - CardName: The keyword name of the current FITS card in a FitsChan
+*     - CardType: The data type of the current FITS card in a FitsChan
+*     - CarLin: Ignore spherical rotations on CAR projections?
+*     - CDMatrix: Use a CD matrix instead of a PC matrix?
+*     - Clean: Remove cards used whilst reading even if an error occurs?
+*     - DefB1950: Use FK4 B1950 as default equatorial coordinates?
+*     - Encoding: System for encoding Objects as FITS headers
+*     - FitsAxisOrder: Sets the order of WCS axes within new FITS-WCS headers
+*     - FitsDigits: Digits of precision for floating-point FITS values
+*     - Iwc: Add a Frame describing Intermediate World Coords?
+*     - Ncard: Number of FITS header cards in a FitsChan
+*     - Nkey: Number of unique keywords in a FitsChan
+*     - TabOK: Should the FITS "-TAB" algorithm be recognised?
+*     - PolyTan: Use PVi_m keywords to define distorted TAN projection?
+*     - Warnings: Produces warnings about selected conditions
+
+*  Functions:
+c     In addition to those functions applicable to all Channels, the
+c     following functions may also be applied to all FitsChans:
+f     In addition to those routines applicable to all Channels, the
+f     following routines may also be applied to all FitsChans:
+*
+c     - astDelFits: Delete the current FITS card in a FitsChan
+c     - astEmptyFits: Delete all cards in a FitsChan
+c     - astFindFits: Find a FITS card in a FitsChan by keyword
+c     - astGetFits<X>: Get a keyword value from a FitsChan
+c     - astGetTables: Retrieve any FitsTables from a FitsChan
+c     - astPurgeWCS: Delete all WCS-related cards in a FitsChan
+c     - astPutCards: Stores a set of FITS header card in a FitsChan
+c     - astPutFits: Store a FITS header card in a FitsChan
+c     - astPutTable: Store a single FitsTable in a FitsChan
+c     - astPutTables: Store multiple FitsTables in a FitsChan
+c     - astReadFits: Read cards in through the source function
+c     - astRemoveTables: Remove one or more FitsTables from a FitsChan
+c     - astRetainFits: Ensure current card is retained in a FitsChan
+c     - astSetFits<X>: Store a new keyword value in a FitsChan
+c     - astShowFits: Display the contents of a FitsChan on standard output
+c     - astTableSource: Register a source function for FITS table access
+c     - astTestFits: Test if a keyword has a defined value in a FitsChan
+c     - astWriteFits: Write all cards out to the sink function
+f     - AST_DELFITS: Delete the current FITS card in a FitsChan
+f     - AST_EMPTYFITS: Delete all cards in a FitsChan
+f     - AST_FINDFITS: Find a FITS card in a FitsChan by keyword
+f     - AST_GETFITS<X>: Get a keyword value from a FitsChan
+f     - AST_GETTABLES: Retrieve any FitsTables from a FitsChan
+f     - AST_PURGEWCS: Delete all WCS-related cards in a FitsChan
+f     - AST_PUTCARDS: Stores a set of FITS header card in a FitsChan
+f     - AST_PUTFITS: Store a FITS header card in a FitsChan
+f     - AST_PUTTABLE: Store a single FitsTables in a FitsChan
+f     - AST_PUTTABLES: Store multiple FitsTables in a FitsChan
+f     - AST_READFITS: Read cards in through the source function
+f     - AST_REMOVETABLES: Remove one or more FitsTables from a FitsChan
+f     - AST_RETAINFITS: Ensure current card is retained in a FitsChan
+f     - AST_SETFITS<X>: Store a new keyword value in a FitsChan
+c     - AST_SHOWFITS: Display the contents of a FitsChan on standard output
+f     - AST_TABLESOURCE: Register a source function for FITS table access
+f     - AST_TESTFITS: Test if a keyword has a defined value in a FitsChan
+f     - AST_WRITEFITS: Write all cards out to the sink function
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2008-2011 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David Berry (Starlink)
+*     RFWS: R.F. Warren-Smith (Starlink, RAL)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+
+*  History:
+*     11-DEC-1996 (DSB):
+*        Original version.
+*     20-MAR-1997 (DSB):
+*        Made keyword setting and getting functions protected instead of
+*        public. Renamed public methods. Added Ncard attribute.
+*     20-MAY-1997 (RFWS):
+*        Tidied public prologues.
+*     30-JUN-1997 (DSB):
+*        Added support for reading post-2000 DATE-OBS strings. Reading DSS
+*        or FITS-WCS objects now returns NULL unless the FitsChan is
+*        positioned at the start-of-file prior to the read. Bug fixed
+*        which caused Ncard to be returned too large by one. Removed
+*        dependancy on hard-wired header and footer text in Native
+*        FitsChans.
+*     18-AUG-1997 (DSB):
+*        Bug fixed in WcsNative which caused incorrect CRVAL values
+*        to be used if the axes needed permuting. Values assigned to the
+*        Projection attribute fo the SkyFrames created by astRead.
+*     2-SEP-1997 (DSB):
+*        Added the IRAF convention that EPOCH=0.0 really means EPOCH=1950.0
+*        (the EPOCH keyword is deprecated in the new FITS-WCS conventions
+*        and is taken always as a Besselian epoch).
+*     19-SEP-1997 (DSB):
+*        Corrected interpretation of the FITS CD matrix.
+*     25-SEP-1997 (DSB):
+*        o  Fix bug in LinearMap which caused it always to detect a linear
+*        mapping. For instance, this allowed DssMaps to be erroneously
+*        written out using FITS-WCS encoding with a CAR projection.
+*        o  Assign a full textual description to SkyFrame's Projection
+*        attribute instead of a 3 letter acronym.
+*        o  If DATE-OBS >= 1999.0 then DATE-OBS is now written in new
+*        Y2000 format. For DATE-OBS < 1999.0, the old format is written.
+*        o  Add new attribute CDMatrix to determine whether PC or CD
+*        matrices should be used when writing objects using FITS-WCS
+*        encoding.
+*        o  Modified the way floating point values are formatted to omit
+*        unnecessary leading zeros from the exponent (i.e. E-5 instead of
+*        E-05).
+*        o  New-line characters at the end of supplied header cards are now
+*        ignored.
+*        o  Cater for EQUINOX specified as a string prefixed by B or J
+*        rather than as a floating point value (some HST data does this).
+*        o  Corrected SetValue so that it always inserts comment cards
+*        rather than over-write existing comment cards. Previously,
+*        writing a FrameSet to a DSS encoded FitsChan resulted in all
+*        comments cards being stripped except for the last one.
+*        o  Reading a FrameSet from a DSS-encoded FrameSet now only
+*        removes the keywords actually required to construct the FrameSet.
+*        Previously, all keywords were removed.
+*        o  The EPOCH and EQUINOX keywords created when a FrameSet is
+*        written to a DSS-encoded FitsChan are now determined from the
+*        epoch and equinox of the current Frame, instead of from a copy
+*        of the original FitsChan stored within the DssMap.
+*        o  The Encoding and CDMatrix attributes, and keyword types are
+*        now stored as strings externally instead of integers.
+*     11-NOV-1997 (DSB):
+*        o  Assume default of j2000 for DSS EQUINOX value.
+*        o  Check for null object pointers in the interfaces for
+*        virtual functions which execute even if an error has previously
+*        occurred. Otherwise, a segmentation violation can occur when
+*        trying to find the member function pointer.
+*        o  Trailing spaces ignored in Encoding attribute.
+*        o  Bugs fixed in FindWcs and SetValue which resulted in WCS cards
+*        being written at the wrong place if the supplied FitsChan does not
+*        contain any WCS keywords.
+*        o  Default for CDMatrix (if no axis rotation keywords can be found)
+*        changed to 2 (i.e. use "CDi_j" form keywords).
+*        o  Write now leaves the current card unchanged if nothing is
+*        written to the FitsChan.
+*     17-NOV-1997 (RFWS):
+*        Disabled use of CDmatrix. Fixed initialisation problems in
+*        astLoadFitsChan.
+*     24-NOV-1997 (DSB):
+*        Replace references to error code AST__OPT with AST__RDERR.
+*     28-NOV-1997 (DSB):
+*        o  Function WcsValues modified to prevent it from changing the
+*        current card. Previously, this could cause new cards to be
+*        written to the wrong place in a FITS-WCS encoded FitsChan.
+*        o  Description of argument "value" corrected in prologue of
+*        function SetFits.
+*        o  Argument "lastkey" removed from function SetValue since it
+*        was never used (it was a relic from a previous method of
+*        determining where to store new cards). Corresponding changes
+*        have been made to all the functions which create "lastkey" values
+*        or pass them on to SetValue (i.e DescWcs, WcsPrimary, WcsSecondary,
+*        WriteWcs and WriteDss).
+*     10-DEC-1997 (DSB):
+*        Bug fixed which caused the initial character designating the system
+*        within CTYPE value (eg E in ELON, G in GLON, etc) to be omitted.
+*     1-JUN-1998 (DSB):
+*        CDELT values of zero are now replaced by a small non-zero value
+*        when creating the "pixel-to-relative physical" transformation
+*        matrix. Previously, zero CDELT values could cause the matrix to
+*        be non-invertable.
+*     4-SEP-1998 (DSB):
+*        - Indicate that SphMaps created by this class when using FITS-WCS
+*        encoding all operate on the unit sphere. This aids simplification.
+*        - Fix a bug in StoreFits which caused CD matrices to be indexed
+*        incorrectly (sometimes causing floating exceptions) if they do not
+*        describe a celestial longitude/latitude system.
+*        - Changed astFindFits to ignore trailing spaces in the keyword
+*        template.
+*        - astSplit changed so that an error is not reported if a textual
+*        keyword value ends before column 20.
+*     7-OCT-1998 (DSB):
+*        - Corrected test for linearity in LinearMap to include a factor
+*        of the test vector length. Also LinearMap now uses a simplified
+*        Mapping.
+*     5-NOV-1998 (DSB):
+*        Added FITS-IRAF encoding.
+*     9-NOV-1998 (DSB):
+*        - Corrected values of macros DSS_ENCODING and MAX_ENCODING.
+*        - Corrected erroneous success indication in IrafStore.
+*        - Included checks for bad values in function LinearMap.
+*     17-NOV-1998 (DSB):
+*        The Domain name GRID is now given to the Base Frame in any FrameSets
+*        created by astRead when using FitsChans with DSS, FITS-WCS or
+*        FITS-IRAF encodings.
+*     18-DEC-1998 (DSB):
+*        Check for "D" exponents in floating point keyword strings.
+*     12-FEB-1998 (DSB):
+*        Modified EncodeFloat to avoid exceeding the 20 character FITS
+*        limit wherever possible if FitsDigits is positive.
+*     10-MAY-1998 (DSB):
+*        Bug fixed in astSplit which caused comments associated with string
+*        keywords to be lost when storing the card in a FitsChan.
+*     15-JUN-1999 (DSB):
+*        Report an error if an unrecognised projection name is supplied.
+*     9-DEC-1999 (DSB):
+*        - Fixed bug in WcsNatPole which could result in longitude values
+*        being out by 180 degrees for cylindrical projections such as CAR.
+*        - Only report an "unrecognised projection" error for CTYPE values
+*        which look like celestial longitude or latitude axes (i.e. if the
+*        first 4 characters are "RA--", "DEC-", "xLON" or "xLAT", and the
+*        fifth character is "-").
+*        - Added function SpecTrans to translated keywords related to the
+*        IRAF ZPX projection into keyword for the standard ZPN projection.
+*        - Add ICRS as a valid value for the RADECSYS keyword. Since the
+*        SkyFrame class does not yet support ICRS, an FK5 SkyFrame is
+*        created if RADECSYS=ICRS.
+*     16-DEC-1999 (DSB):
+*        - Modified SpecTrans so that all keywords used to created a
+*        standard WCS representation from a non-standard one are consumed
+*        by the astRead operation.
+*        - Changed the text of ASTWARN cards added to the FitsChan if an
+*        IRAF ZPX projection is found to require unsupported corrections.
+*        - Simplified the documentation describing the handling of the IRAF
+*        ZPX projection.
+*        - Fixed code which assumed that the 10 FITS-WCS projection
+*        parameters were PROJP1 -> PROJP10. In fact they are PROJP0 -
+*        PROJP9. This could cause projection parameter values to be
+*        incorrectly numbered when they are written out upon deletion of
+*        the FitsChan.
+*     1-FEB-2000 (DSB):
+*        Check that FITS_IRAF encoding is not being used before using a
+*        PC matrix when reading WCS information from a header. This is
+*        important if the header contains both PC and CD matrices.
+*     8-FEB-2000 (DSB):
+*        - Header cards are now only consumed by an astRead operation if the
+*        operation succeeds (i.e. returns a non-null Object).
+*        - The original FITS-WCS encoding has been renamed as FITS-PC (to
+*        indicate the use of a PCiiijjj matrix), and a new FITS-WCS
+*        encoding has been added.
+*        - The disabled CDMatrix attribute has been removed.
+*        - Bug in LinearMap corrected which prevented genuinely linear
+*        Mappings from being judged to be linear. This bug was previously
+*        fudged (so it now appears) by the introduction of the test vector
+*        length factor (see History entry for 7-OCT-1998). This test
+*        vector length scale factor has consequently now been removed.
+*        - Added FITS-AIPS encoding.
+*        - The critical keywords used to select default encoding have been
+*        changed.
+*        - Support for common flavours of IRAF TNX projections added.
+*        - The algorithm used to find a WcsMap in the supplied FrameSet
+*        has been improved so that compound Mappings which contain complex
+*        mixtures of parallel and serial Mappings can be translated into
+*        FITS-WCS encoding.
+*        - Trailing white space in string keyword values is now retained
+*        when using foreign encodings to enable correct concatenation where
+*        a string has been split over several keywords. E.g. if 2 string
+*        keywords contain a list of formatted numerical values (e.g. IRAF
+*        WAT... keywords), and the 1st one ends "0.123 " and the next one
+*        begins "1234.5 ", the trailing space at the end of the first keyword
+*        is needed to prevent the two numbers being merged into "0.1231234.5".
+*        Trailing spaces in native encodings is still protected by enclosing
+*        the whole string in double quotes.
+*        - The Channel methods WriteString and GetNextData can now save
+*        and restore strings of arbitary length. This is done by storing
+*        as much of the string as possible in the usual way, and then
+*        storing any remaining characters in subsequent CONTINUE cards,
+*        using the FITSIO conventions. This storage and retrieval of long
+*        strings is only available for native encodings.
+*     19-MAY-2000 (DSB):
+*        Added attribute Warnings. Lowered DSS in the priority list
+*        of encodings implemented by GetEncoding.
+*     6-OCT-2000 (DSB):
+*        Increased size of buffers used to store CTYPE values to take
+*        account of the possiblity of lots of trailing spaces.
+*     5-DEC-2000 (DSB):
+*        Add support for the WCSNAME FITS keyword.
+*     12-DEC-2000 (DSB):
+*        Add a title to each physical, non-celestial coord Frame based on
+*        its Domain name (if any).
+*     3-APR-2001 (DSB):
+*        -  Use an "unknown" celestial coordinate system, instead of a
+*        Cartesian coordinate system, if the CTYPE keywords specify an
+*        unknown celestial coordinate system.
+*        -  Do not report an error if there are no CTYPE keywords in the
+*        header (assume a unit mapping, like in La Palma FITS files).
+*        -  Add a NoCTYPE warning condition.
+*        -  Added AllWarnings attribute.
+*        -  Ensure multiple copies of identical warnings are not produced.
+*        -  Use the Object Ident attribute to store the identifier letter
+*        associated with each Frame read from a secondary axis description,
+*        so that they can be given the same letter when they are written
+*        out to a new FITS file.
+*     10-AUG-2001 (DSB):
+*        - Corrected function value returned by SkySys to be 1 unless an
+*        error occurs. This error resulted in CAR headers being produced
+*        by astWrite with CRVAL and CD values till in radians rather than
+*        degrees.
+*        - Introduced SplitMap2 in order to guard against producing
+*        celestial FITS headers for a Mapping which includes more than
+*        one WcsMap.
+*     13-AUG-2001 (DSB):
+*        - Modified FixNew so that it retains the current card index if possible.
+*        This fixed a bug which could cause headers written out using Native
+*        encodings to be non-contiguous.
+*        - Corrected ComBlock to correctly remove AST comment blocks in
+*        native encoded fitschans.
+*     14-AUG-2001 (DSB):
+*        - Modified FixUsed so that it it does not set the current card
+*        back to the start of file if the last card in the FitsChan is
+*        deleted.
+*     16-AUG-2001 (DSB):
+*        Modified WcsNative to limit reference point latitude to range
+*        +/-90 degs (previously values outside this range were wrapped
+*        round onto the opposite meridian). Also added new warning
+*        condition "badlat".
+*     23-AUG-2001 (DSB):
+*        - Re-write LinearMap to use a least squares fit.
+*        - Check that CDj_i is not AST__BAD within WcsWithWcs when
+*        forming the increments along each physical axis.
+*     28-SEP-2001 (DSB):
+*        GoodWarns changed so that no error is reported if a blank list
+*        of conditions is supplied.
+*     12-OCT-2001 (DSB):
+*        - Added DefB1950 attribute.
+*        - Corrected equations which calculate CROTA when writing
+*        FITS-AIPS encodings.
+*        - Corrected equations which turn a CROTA value into a CD matrix.
+*     29-NOV-2001 (DSB):
+*        Corrected use of "_" and "-" characters when referring to FK4-NO-E
+*        system in function SkySys.
+*     20-FEB-2002 (DSB)
+*        Added CarLin attribute.
+*     8-MAY-2002 (DSB):
+*        Correct DSSToStore to ignore trailing blanks in the PLTDECSN
+*        keyword value.
+*     9-MAY-2002 (DSB):
+*        Correct GetCard to avoid infinite loop if the current card has
+*        been marked as deleted.
+*     25-SEP-2002 (DSB):
+*        AIPSFromStore: use larger of coscro and sincro when determining
+*        CDELT values. Previously a non-zero coscro was always used, even
+*        if it was a s small as 1.0E-17.
+*     3-OCT-2002 (DSB):
+*        - SkySys: Corrected calculation of longitude axis index for unknown
+*        celestial systems.
+*        - SpecTrans: Corrected check for latcor terms for ZPX projections.
+*        - WcsFrame: Only store an explicit equinox value in a skyframe if
+*        it needs one (i.e. if the system is ecliptic or equatorial).
+*        - WcsWithWcs: For Zenithal projections, always use the default
+*        LONPOLE value, and absorb any excess rotation caused by this
+*        into the CD matrix.
+*        - WcsWithWcs: Improve the check that the native->celestial mapping
+*        is a pure rotation, allowing for rotations which change the
+*        handed-ness of the system (if possible).
+*        - WcsWithWcs: Avoid using LONPOLE keywords when creating headers
+*        for a zenithal projection. Instead, add the corresponding rotation
+*        into the CD matrix.
+*     22-OCT-2002 (DSB):
+*        - Retain leading and trailing white space within COMMENT cards.
+*        - Only use CTYPE comments as axis labels if all non-celestial
+*          axes have a unique non-blank comment (otherwise use CTYPE
+*          values as labels).
+*        - Updated to use latest FITS-WCS projections. This means that the
+*          "TAN with projection terms" is no longer a standard FITS
+*          projection. It is now represented using the AST-specific TPN
+*          projection (until such time as FITS-WCS paper IV is finished).
+*        - Remove trailing "Z" from DATE-OBS values created by astWrite.
+*     14-NOV-2002 (DSB):
+*        - WcsWithWcs: Corrected to ignore longitude axis returned by
+*        astPrimaryFrame since it does not take into account any axis
+*        permutation.
+*     26-NOV-2002 (DSB):
+*        - SpecTrans: Corrected no. of characters copied from CTYPE to PRJ,
+*        (from 5 to 4), and terminate PRJ correctly.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitFitsChanVtab
+*        method.
+*     22-JAN-2003 (DSB):
+*        Restructured the functions used for reading FITS_WCS headers to
+*        make the distinction between the generic parts (pixel->intermediate
+*        world coordinates) and the specialised parts (e.g. celestial,
+*        spectral, etc) clearer.
+*     31-JAN-2003 (DSB)
+*        - Added Clean attribute.
+*        - Corrected initialisation and defaulting of CarLin and DefB1950
+*        attributes.
+*        - Extensive changes to allow foreign encodings to be produced in
+*        cases where the Base Frame has fewer axes than the Current Frame.
+*     12-FEB-2003 (DSB)
+*        - Modified SetFits so that the existing card comment is retained
+*        if the new data value equals the existing data value.
+*     30-APR-2003 (DSB):
+*        - Revert to standard "TAN" code for distorted tan projections,
+*        rather than using the "TPN" code. Also recognise QVi_m (produced
+*        by AUTOASTROM) as an alternative to PVi_m when reading distorted
+*        TAN headers.
+*     22-MAY-2003 (DSB):
+*        Modified GetEncoding so that the presence of RADECSYS and/or
+*        PROJPm is only considered significant if the modern equivalent
+*        keyword (REDESYS or PVi_m) is *NOT* present.
+*     2-JUN-2003 (DSB):
+*        - Added support for PCi_j kewwords within FITS-WCS encoding
+*        - Added CDMatrix attribute
+*        - Changed internal FitsStore usage to use PC/CDELT instead of CD
+*        (as preparation for FITS-WCS paper IV).
+*        - Added warning "BadMat".
+*     11-JUN-2003 (DSB):
+*        - Modified WcsNative to use the new SphMap PolarLong attribute
+*        in order to ensure correct propagation of the longitude CRVAL
+*        value in cases where the fiducial point is coincident with a pole.
+*        - Use PVi_3 and PVi_4 for longitude axis "i" (if present) in
+*        preference to LONPOLE and LATPOLE when reading a FITS-WCS header.
+*        Note, these projection values are never written out (LONPOLE and
+*        LATPOLE are written instead).
+*        - Associate "RADESYS=ICRS" with SkyFrame( "System=ICRS" ), rather
+*        than SkyFrame( "System=FK5" ).
+*        - If DefB1950 is zero, use ICRS instead of FK5 as the default RADESYS
+*        if no EQUINOX is present.
+*     1-SEP-2003 (DSB):
+*        - Modify Dump so that it dumps all cards including those flagged as
+*        having been read.
+*        - Added "reset" parameter to FixUsed.
+*        - WcsMapFrm: store an Ident of ' ' for the primary coordinate
+*        description (previously Ident was left unset)
+*        - Default value for DefB1950 attribute now depends on the value
+*        of the Encoding attribute.
+*     15-SEP-2003 (DSB):
+*        - Added Warnings "BadVal", "Distortion".
+*        - Ignore FITS-WCS paper IV CTYPE distortion codes (except for
+*          "-SIP" which is interpreted correctly on reading).
+*     22-OCT-2003 (DSB):
+*        - GetEncoding: If the header contains CDi_j but does not contain
+*        any of the old IRAF keywords (RADECSYS, etc) then assume FITS-WCS
+*        encoding. This allows a FITS-WCS header to have both CDi_j *and*
+*        CROTA keywords.
+*     5-JAN-2004 (DSB):
+*        - SpecTrans: Use 1.0 (instead of the CDELT value) as the
+*        diagonal PCi_j term for non-celestial axes with associated CROTA
+*        values.
+*     12-JAN-2004 (DSB):
+*        - CelestialAxes: Initialise "tmap1" pointer to NULL in case of error
+*        (avoids a segvio happening in the case of an error).
+*        - AddVersion: Do not attempt to add a Frame into the FITS header
+*        if the mapping from grid to frame is not invertable.
+*        - WorldAxes: Initialise the returned "perm" values to safe values,
+*        and return these values if no basis vectors cen be created.
+*     19-JAN-2004 (DSB):
+*        - When reading a FITS-WCS header, allow all keywords to be defaulted
+*        as decribed in paper I.
+*     27-JAN-2004 (DSB):
+*        - Modify FitLine to use correlation between actual and estimated
+*        axis value as the test for linearity.
+*        - Modify RoundFString to avoid writing beyond the end of the
+*        supplied buffer if the supplied string contains a long list of 9's.
+*     11-MAR-2004 (DSB):
+*        - Modified SpecTrans to check all axis descriptions for keywords
+*        to be translated.
+*     19-MAR-2004 (DSB):
+*        - Added astPutCards to support new fits_hdr2str function in
+*        CFITSIO.
+*     25-MAR-2004 (DSB):
+*        - Corrected bug in astSplit which causes legal cards to be
+*        rejected because characters beyond the 80 char limit are being
+*        considered significant.
+*        - Corrected bug in SpecTrans which caused QV keywords to be
+*        ignored.
+*     15-APR-2004 (DSB):
+*        - SpecTrans modified to include translation of old "-WAV", "-FRQ"
+*        and "-VEL" spectral algorithm codes to modern "-X2P" form.
+*        - WcsFromStore modified to supress creation of WCSAXES keywords
+*        for un-used axis versions.
+*        - IsMapLinear modified to improve fit by doing a second least
+*        squares fit to the residualleft by the first least squares fit.
+*     16-APR-2004 (DSB):
+*        - NonLinSpecWcs: Issue a warning if an illegal non-linear
+*        spectral code is encountered.
+*        - Add a BadCTYPE warning condition.
+*        - Corrected default value for Clean so that it is zero (as
+*        documented).
+*     21-APR-2004 (DSB):
+*        - FindWcs: Corrected to use correct OBSGEO template. This bug
+*        caused OBSGEO keywords to be misplaced in written headers.
+*     23-APR-2004 (DSB):
+*        - SplitMap: Modified so that a Mapping which has celestial axes
+*        with constant values (such as produced by a PermMap) are treated
+*        as a valid sky coordinate Mapping.
+*        - AddFrame modified so that WCS Frames with a different number
+*        of axes to the pixel Frame can be added into the FrameSet.
+*        - IRAFFromStore and AIPSFromStore modified so that they do not
+*        create any output keywords if the number of WCS axes is different
+*        to the number of pixel axes.
+*        - Handling of OBSGEO-X/Y/Z corrected again.
+*        - WCSFromStore modified to avouid writing partial axis descriptions.
+*     26-APR-2004 (DSB):
+*        - Corrected text of output SPECSYS keyword values.
+*     17-MAY-2004 (DSB):
+*        - Added IWC attribute.
+*     15-JUN-2004 (DSB):
+*        - Ensure out-of-bounds longitude CRPIX values for CAR
+*        projections are wrapped back into bounds.
+*     21-JUN-2004 (DSB):
+*        - Ensure primary MJD-OBS value is used when reading foreign FITS
+*        headers.
+*     7-JUL-2004 (DSB):
+*        - Issue errors if an un-invertable PC/CD matrix is supplied in a
+*        FITS-WCS Header.
+*     11-JUL-2004 (DSB):
+*        - Re-factor code for checking spectral axis CTYPE values into
+*        new function IsSpectral.
+*        - Modify AIPSFromSTore to create spectral axis keywords if
+*        possible.
+*        - Modify SpecTrans to recognize AIPS spectral axis keywords, and
+*        to convert "HZ" to "Hz".
+*        - Added FITS-AIPS++ encoding.
+*     12-AUG-2004 (DSB):
+*        - Convert GLS projection codes to equivalent SFL in SpecTrans.
+*        - Added FITS-CLASS encoding.
+*     16-AUG-2004 (DSB):
+*        - Removed support for paper III keyword VSOURCE, and added
+*        support for SSYSSRC keyword.
+*        - Added initial support for CLASS encoding.
+*        - In FitOK: Changed tolerance for detecting constant values
+*        from 1.0E-10 to 1.0E-8.
+*     17-AUG-2004 (DSB):
+*        Correct GetFiducialNSC so that the stored values for longitude
+*        parameters 1 and 2 are ignored unless the value of parameter 0 is
+*        not zero.
+*     19-AUG-2004 (DSB):
+*        Modify SpecTrans to ignore any CDELT values if the header
+*        includes some CDi_j values.
+*     26-AUG-2004 (DSB):
+*        Modify astSplit_ to allow floating point keyword values which
+*        include an exponent to be specified with no decimal point
+*        (e.g. "2E-4").
+*     27-AUG-2004 (DSB):
+*        Completed initial attempt at a FITS-CLASS encoding.
+*     9-SEP-2004 (DSB):
+*        Fixed usage of uninitialised values within ReadCrval.
+*     13-SEP-2004 (DSB):
+*        Check the "text" pointer can be used safely before using it in
+*        DSSToStore.
+*     27-SEP-2004 (DSB):
+*        In SpecTrans, before creating new PCi_j values,  check that no
+*        PCi_j values have been created via an earlier translation.
+*     28-SEP-2004 (DSB):
+*        In AIPSPPFromStore only get projection parameters values if there
+*        are some celestialaxes. Also allow CROTA to describe rotation of
+*        non-celestial axes (same for AIPSFromSTore).
+*     4-OCT-2004 (DSB):
+*        Correct rounding of CRPIX in AddVersion to avoid integer overflow.
+*     11-NOV-2004 (DSB):
+*        - WcsFcRead: Avoid issuing warnings about bad keywords which
+*        have already been translated into equivalent good forms.
+*        - SpecTrans: If both PROJP and PV keywords are present, use PV
+*        in favour of PROJP only if the PV values look correct.
+*     17-NOV-2004 (DSB):
+*        - Make astSetFits<X> public.
+*     16-MAR-2005 (DSB):
+*        - Primary OBSGEO-X/Y/Z, MJD-AVG and MJDOBS keywords are associated
+*        with all axis descriptions and should not have a trailing single
+*        character indicating an alternate axis set.
+*     9-AUG-2005 (DSB):
+*        In WcsMapFrm, check reffrm is used before annulling it.
+*     8-SEP-2005 (DSB):
+*        - Change "if( a < b < c )" constructs to "if( a < b && b < c )"
+*        - DSBSetup: correct test on FrameSet pointer state
+*        - Ensure CLASS keywords written to a FitsChan do not come before
+*        the final fixed position keyword.
+*     9-SEP-2005 (DSB):
+*        - Added "AZ--" and "EL--" as allowed axis types in FITS-WCS
+*        ctype values.
+*     12-SEP-2005 (DSB):
+*        - Cast difference between two pointers to (int)
+*        - CLASSFromStore:Check source velocity is defined before
+*          storing it in the output header.
+*     13-SEP-2005 (DSB):
+*        - Corrected B1940 to B1950 in AddEncodingFrame. This bug
+*        prevented some FrameSets being written out using FITS-CLASS.
+*        - Rationalise the use of the "mapping" pointer in AddVersion.
+*        - WcsCelestial: Modified so that the FITS reference point is
+*        stored as the SkyFrame SkyRef attribute value.
+*     7-OCT-2005 (DSB):
+*        Make astGetFits<X> public.
+*     30-NOV-2005 (DSB):
+*        Add support for undefined FITS keyword values.
+*     5-DEC-2005 (DSB):
+*        - Include an IMAGFREQ keyword in the output when writing a
+*        DSBSpecFrame out using FITS-WCS encoding.
+*        - Correct test for constant values in FitOK.
+*     7-DEC-2005 (DSB):
+*        Free memory allocated by calls to astReadString.
+*     30-JAN-2006 (DSB):
+*        Modify astSplit so that it does no read the supplied card beyond
+*        column 80.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     28-FEB-2006 (DSB):
+*        Correct documentation typo ("NCards" -> "Ncard").
+*     5-APR-2006 (DSB):
+*        Modify SpecTrans to convert CTYPE="LAMBDA" to CTYPE="WAVE".
+*     26-MAY-2006 (DSB):
+*        Guard against NULL comment pointer when converting RESTFREQ to
+*        RESTFRQ in SpecTrans.
+*     29-JUN-2006 (DSB):
+*        - Added astRetainFits.
+*        - Consume VELOSYS FITS-WCS keywords when reading an object.
+*        - Write out VELOSYS FITS-WCS keywords when writing an object.
+*     7-AUG-2006 (DSB):
+*        Remove trailing spaces from the string returned by astGetFitsS
+*        if the original string contains 8 or fewer characters.
+*     16-AUG-2006 (DSB):
+*        Document non-destructive nature of unsuccessful astRead calls.
+*     17-AUG-2006 (DSB):
+*        Fix bugs so that the value of the Clean attribute is honoured
+*        even if an error has occurred.
+*     4-SEP-2006 (DSB):
+*        Modify GetClean so that it ignores the inherited status.
+*     20-SEP-2006 (DSB):
+*        Fix memory leak in WcsSpectral.
+*     6-OCT-2006 (DSB):
+*        Modify IsSpectral and IsAIPSSpectral to allow for CTYPE values that
+*        are shorter than eight characters.
+*     13-OCT-2006 (DSB):
+*        - Ensure SpecFrames and SkyFrames created from a foreign FITS header
+*        are consistent in their choice of Epoch.
+*        - Convert MJD-OBS and MJD-AVG values from TIMESYS timescale to
+*        TDB before using as the Epoch value in an AstFrame. Use UTC if
+*        TIMESYS is absent.
+*        - Convert Epoch values from TDB to UTC before storing as the
+*        value of an MJD-OBS or MJD-AVG keyword (no TIMESYS keyword is
+*        written).
+*     23-OCT-2006 (DSB):
+*        Prefer MJD-AVG over MJD-OBS.
+*     30-OCT-2006 (DSB):
+*        In FitOK: Changed lower limit on acceptbale correlation from
+*        0.999999 to 0.99999.
+*     1-NOV-2006 (DSB):
+*        - When reading a foreign header that contains a DUT1 keyword,
+*        use it to set the Dut1 attribute in the SkyFrame. Note, JACH
+*        store DUT1 in units of days. This may clash with the FITS-WCS
+*        standard (when its produced). Also note that DUT1 is not written
+*        out as yet when writing a FrameSet to a foreign FITS header.
+*        - Correct bug that prevented ZSOURCE keyword being added to the
+*        output header if the source velocity was negative.
+*     9-NOV-2006 (DSB):
+*        Add STATUS argument to docs for F77 AST_SETx.
+*     20-DEC-2006 (DSB):
+*        Correct FK5 to ICRS in error message issued if no RADESYS or
+*        EQUINOX is found.
+*     16-JAN-2007 (DSB):
+*        Cast ignored function return values to (void) to avoid compiler
+*        warnings.
+*     31-JAN-2007 (DSB):
+*        Change SpecTrans to ignore blank unit strings (previously
+*        converted them to "Hz").
+*     16-APR-2007 (DSB):
+*        In SplitMat, increase the allowed level of rounding erros from
+*        1.0E-10 to 1.0E-7 (to avoid spurious low CDi_j values being
+*        created that should be zero).
+*     30-APR-2007 (DSB):
+*        - Change DSBSetup so that the central DSBSpecFrame frequency is
+*        CRVAL and the IF is the difference between CRVAL and LO.
+*        - Change tolerance in FitOK from 0.99999 to 0.995 to handle data from Nicolas
+*        Peretto.
+*     1-MAY-2007 (DSB):
+*        - In astSplit, if a keyword value looks like an int but is too long to
+*         fit in an int, then treat it as a float instead.
+*     18-MAY-2007 (DSB):
+*        In CnvType, use input type rather than output type when checking
+*        for a COMMENT card. Also, return a null data value buffer for a
+*        COMMENT card.
+*     4-JUN-2007 (DSB):
+*        In CLASSFromStore, create a DELTAV header even if it is equal to
+*        the spectral CDELT value. Also, convert spatial reference point
+*        to (az,el) and write out as headers AZIMUTH and ELEVATIO.
+*     9-JUL-2007 (DSB):
+*        Fixed bug in DSBSetUp - previously, this function assumed that
+*        the supplied DSBSpecFrame represented frequency, and so gave
+*        incorrect values for IF and DSBCentre if the header described
+*        velocity.
+*     9-AUG-2007 (DSB):
+*        Changed GetEncoding so that critcal keywords are ignored if
+*        there are no CTYPE, CRPIX or CRVAL keywords in the header.
+*     10-AUG-2007 (DSB):
+*        - Changed GetEncoding so that FITS_PC is not returned if there are
+*        any CDi_j or PCi_j keywords in the header.
+*        - Added astPurgeWCS method.
+*     13-AUG-2007 (DSB):
+*        - Include the DSS keywords AMDX%d and AMDY%d in FindWCS.
+*     16-AUG-2007 (DSB):
+*        - Force all FITS-CLASS headers to contain frequency axes
+*        (velocity axes seem not to be recognised properly by CLASS).
+*        - Change the CLASS "VELO-LSR" header to be the velocity at the
+*        reference channel, not the source velocity.
+*     22-AUG-2007 (DSB):
+*        - Remove debugging printf statements.
+*     20-SEP-2007 (DSB):
+*        Changed FitOK to check that the RMS residual is not more than
+*        a fixed small fraction of the pixel size.
+*     4-DEC-2007 (DSB):
+*        Changed CreateKeyword so that it uses a KeyMap to search for
+*        existing keywords. This is much faster than checking every
+*        FitsCard in the FitsChan explicitly.
+*     18-DEC-2007 (DSB):
+*        Add keyword VLSR to the CLASS encoding. It holds the same value
+*        as VELO-LSR, but different versions of class use different names.
+*        Also write out the DELTAV keyword in the LSR rest frame rather
+*        than the source rest frame.
+*     31-JAN-2008 (DSB):
+*        Correct calculation of redshift from radio velocity in ClassTrans.
+*     25-FEB-2008 (DSB):
+*        Ensure a SkyFrame represents absolute (rather than offset)
+*        coords before writing it out in any non-native encoding.
+*     28-FEB-2008 (DSB):
+*        Test for existing of SkyRefIs attribute before accessing it.
+*     2-APR-2008 (DSB):
+*        In CLASSFromStore, adjust the spatial CRVAL and CRPIX values to be
+*        the centre of the first pixel if the spatial axes are degenerate.
+*     17-APR-2008 (DSB):
+*        Ignore latitude axis PV terms supplied in a TAN header
+*        (previously, such PV terms were used as polynomial correction
+*        terms in a TPN projection).
+*     30-APR-2008 (DSB):
+*        SetValue changed so that new keywords are inserted before the
+*        current card.
+*     1-MAY-2008 (DSB):
+*        Added UndefRead warning.
+*     7-MAY-2008 (DSB):
+*        Correct conversion of CDi_j to PCi_j/CDELT in SpecTrans.
+*     8-MAY-2008 (DSB):
+*        When writing out a FITS-WCS header, allow linear grid->WCS
+*        mapping to be represented by a CAR projection.
+*     9-MAY-2008 (DSB):
+*        Make class variables IgnoreUsed and MarkNew static.
+*     30-JUN-2008 (DSB):
+*        Improve efficiency of FindWcs.
+*     2-JUL-2008 (DSB):
+*        FitsSof now returns non-zero if the FitsChan is empty.
+*     16-JUL-2008 (DSB):
+*        Plug memory leak caused by failure to free the Warnings
+*        attribute string when a FitsChan is deleted.
+*     24-JUL-2008 (TIMJ):
+*        Fix buffer overrun in astGetFits when writing the keyword
+*        to the buffer (occurred if the input string was 80 characters).
+*     1-OCT-2008 (DSB):
+*        When reading a FITS-WCS header, spurious PVi_j keywords no
+*        longer generate an error. Instead they generate warnings via the
+*        new "BadPV" warning type.
+*     21-NOV-2008 (DSB):
+*        Do not remove keywords from read headers if they may be of
+*        relevance to things other than WCS (e.g. MJD-OBS, OBSGEO, etc).
+*     2-DEC-2008 (DSB):
+*        - astGetFits<X> now reports an error if the keyword value is undefined.
+*        - Add new functions astTestFits and astSetFitsU.
+*        - Remove use of AST__UNDEF<X> constants.
+*        - Remove "undefread" warning.
+*     16-JAN-2009 (DSB):
+*        Use astAddWarning to store each warning in the parent Channel
+*        structure.
+*     4-MAR-2009 (DSB):
+*        DATE-OBS and MJD-OBS cannot have an axis description character.
+*     13-MAR-2009 (DSB):
+*        The VELOSYS value read from the header is never used, so do not
+*        report an error if VELOSYS has an undefined value.
+*     11-JUN-2009 (DSB):
+*        Delay reading cards from the source until they are actually
+*        needed. Previously, the source function was called in the
+*        FitsChan initialiser, but this means it is not possible for
+*        application code to call astPutChannelData before the source
+*        function is called. The ReadFromSource function is now called
+*        at the start of each (nearly) public or protected function to
+*        ensure the source function has been called (the source function
+*        pointer in the FitsChan is then nullified to ensure it is not
+*        called again).
+*     18-JUN-2009 (DSB):
+*        Include the effect of observer height (in the ObsAlt attribute)
+*        when creating OBSGEO-X/Y/Z headers, and store a value for
+*        ObsAlt when reading a set of OBSGEO-X/Y/Z headers.
+*     2-JUL-2009 (DSB):
+*        Check FitsChan is not empty at start of FindWcs.
+*     7-JUL-2009 (DSB):
+*        Add new function astSetFitsCM.
+*     30-JUL-2009 (DSB):
+*        Fix axis numbering in SkyPole.
+*     12-FEB-2010 (DSB):
+*        Use "<bad>" to represent AST__BAD externally.
+*     25-JUN-2010 (DSB):
+*        Fix problem rounding lots of 9's in RoundFString. The problem
+*        only affected negative values, and could lead to an extra zero
+*        being included in the integer part.
+*     28-JUN-2010 (DSB):
+*        Another problem in RoundFString! If the value has a series of
+*        9's followed by a series of zeros, with no decimal point (e.g.
+*        "260579999000"), then the trailing zeros were being lost.
+*     16-JUL-2010 (DSB):
+*        In SpecTrans, avoid over-writing the spatial projection code
+*        with the spectral projection code.
+*     20-JUL-2010 (DSB):
+*        Correct interpretation of NCP projection code.
+*     14-OCT-2010 (DSB):
+*        - Correct loading of FitsChans that contain UNDEF keywords.
+*        - Correct translation of spectral units with non-standard
+*        capitalisation in SpecTrans.
+*     10-JAN-2011 (DSB):
+*        Fix memory leak in MakeIntWorld.
+*     13-JAN-2011 (DSB):
+*        Rename astEmpty ast astEmptyFits and make public.
+*     20-JAN-2011 (DSB):
+*        - Extensive changes to support -TAB algorithm
+*        - Recovery from a major unrequested reformatting of whitespace by
+*        my editor!
+*     7-FEB-2011 (DSB):
+*        Put a space between keyword value and slash that starts a comment
+*        when formatting a FITS header card.
+*     11-FEB-2011 (DSB):
+*        Change meaning of TabOK attribute. It is no longer a simple
+*        boolean indicating if the -TAB algorithm is supported. Instead
+*        it gives the value to be used for the EXTVER header - i.e. the
+*        version number to store with any binary table created as a
+*        result of calling astWrite. If TabOK is zero or begative, then
+*        the -TAB algorithm is not supported. This is so that there is
+*        some way of having multiple binary table extensions with the same
+*        name (but different EXTVER values).
+*     14-FEB-2011 (DSB):
+*        - Spectral reference point CRVAL records the obs. centre. So for -TAB
+*        (when CRVAL is set to zero) we need to record the obs centre some
+*        other way (use the AST-specific AXREF keywords, as for spatial axes).
+*        - Whether to scale spatial axes from degs to rads depends on
+*        whether the spatial axes are descirbed by -TAB or not.
+*        - Relax the linearity requirement in IsMapLinear by a factor of
+*        10 to prevent a change in rest frame resulting in a non-linear
+*        mapping.
+*     17-FEB-2011 (DSB):
+*        Fix bug in axis linearity check (IsMapLinear).
+*     22-FEB-2011 (DSB):
+*        The translations of AIPS non-standard CTYPE values were always
+*        stored as primary axis description keywords, even if the original
+*        non-standard CTYPE values were read from an alternative axis
+*        descriptions.
+*     5-APR-2011 (DSB):
+*        In SpecTrans, correct the MSX CAR projection translation. The
+*        first pixel starts at GRID=0.5, not GRID=0.0. So the CRPIX value
+*        needs to be reduced by 0.5 prior to normalisation, and then
+*        increased by 0.5 after normalisation.
+*     23-MAY-2011 (DSB):
+*        Add support for TNX projections that use Chebyshev polynomials.
+*     24-MAY-2011 (DSB):
+*        - Add support for ZPX projections that include IRAF polynomial
+*        corrections.
+*        - Add PolyTan attribute.
+*        - Fix interpretation of -SIP headers that have no inverse.
+*     1-JUN-2011 (DSB):
+*        In astInitFitsChanVtab, only create the two TimeFrames if they
+*        have not already been created (fixes scuba2 trac ticket #666).
+*     9-JUN-2011 (DSB):
+*        In WCSFcRead, ignore trailing spaces when reading string values
+*        for WCS keywords.
+*     23-JUN-2011 (DSB):
+*        - Override the parent astSetSourceFile method so that it reads
+*        headers from the SourceFile and appends them to the end of the
+*        FitsChan.
+*        - On deletion, write out the FitsChan contents to the file
+*        specified by the SinkFile attribute. If no file is specified,
+*        use the sink function specified when the FitsChan was created.
+*     30-AUG-2011 (DSB):
+*        - Added astWriteFits and astReadFits.
+*        - Move the deletion of tables and warnings from Delete to
+*        EmptyFits.
+*     21-SEP-2011 (DSB):
+*        - In RoundFString, remember to update the pointer to the exponent.
+*        This bug caused parts of the exponent to dissappear when
+*        formatting a value that included some trailing zeros and a
+*        series of adjacent 9's.
+*        - Added Nkey attribute.
+*     22-SEP-2011 (DSB):
+*        - Added CardType attribute
+*        - Allow GetFits to be used to get/set the value of the current
+*        card.
+*     4-OCT-2011 (DSB):
+*        When reading a FITS-WCFS header, if the projection is TPV (as produced
+*        by SCAMP), change to TPN (the internal AST code for a distorted
+*        TAN projection).
+*     22-NOV-2011 (DSB):
+*        Allow the "-SIP" code to be used with non-celestial axes.
+*     1-FEB-2012 (DSB):
+*        Write out MJD-OBS in the timescale specified by any TIMESYS
+*        keyword in the FitsChan, and ensure the TIMESYS value is included
+*        in the output header.
+*     23-FEB-2012 (DSB):
+*        Use iauGd2gc in place of palGeoc where is saves some calculations.
+*     24-FEB-2012 (DSB):
+*        Move invocation of AddEncodingFrame from Write to end of
+*        MakeFitsFrameSet. This is so that AddEncodingFrame can take
+*        advantage of any standardisations (such as adding celestial axes)
+*        performed by MakeFItsFrameSet. Without this, a FRameSet contain
+*        a 1D SpecFrame (no celestial axes) would fail to be exported using
+*        FITS-CLASS encoding.
+*     29-FEB-2012 (DSB):
+*        Fix bug in CLASSFromStore that caused spatial axes added by
+*        MakeFitsFrameSet to be ignored.
+*     2-MAR-2012 (DSB):
+*        - In CLASSFromSTore, ensure NAXIS2/3 values are stored in teh FitsChan,
+*        and cater for FrameSets that have only a apectral axis and no celestial
+*        axes (this prevented the VELO_LSR keyword being created)..
+*     7-MAR-2012 (DSB):
+*        Use iauGc2gd in place of Geod.
+*     22-JUN-2012 (DSB):
+*        - Check for distorted TAN projections that have zero for all PVi_m
+*        coefficients. Issue a warning and ignore the distortion in such
+*        cases.
+*        - Remove all set but unused variables.
+*        - Convert SAO distorted TAN projections (which use COi_j keywords
+*        for polynomial coeffs) to TPN.
+*     26-JUN-2012 (DSB):
+*        Correct call to astKeyFields in SAOTrans (thanks to Bill Joye
+*        for pointing out this error).
+*     8-AUG-2012 (DSB):
+*        Correct assignment to lonpole within CLASSFromStore.
+*     10-AUG-2012 (DSB):
+*        Default DSS keywords CNPIX1 and CNPIX2 to zero if they are
+*        absent, rather than reporting an error.
+*     7-DEC-2012 (DSB):
+*        - When writing out a FrameSet that uses an SkyFrame to describe a
+*        generalised spherical coordinate system ("system=unknown"), ensure
+*        that the generated FITS CTYPE values use FITS-compliant codes
+*        for the axis type ( "xxLN/xxLT" or "xLON/xLAT" ).
+*        - Add support for reading and writing offset SkyFrames to
+*        FITS-WCS.
+*     30-JAN-2013 (DSB):
+*        When reading a FITS-CLASS header, use "VLSR" keyword if
+*        "VELO-..." is not available.
+*     15-APR-2013 (DSB):
+*        Correct initialisation of missing coefficients When reading a
+*        SAO plate solution header.
+*     16-APR-2013 (DSB):
+*        When determining default Encoding value, use "VLSR" keyword if
+*        "VELO-..." is not available.
+*     30-MAY-2013 (DSB):
+*        Prevent seg fault caused by overrunning the coeffs array in
+*        WATCoeffs in cases where the TNX/ZPX projection is found to be
+*        of a form that cannot be implemented as a TPN projection.
+*     11-JUN-2013 (DSB):
+*        Fix support for reading GLS projections, and add support for
+*        rotated GLS projections.
+*     28-AUG-2013 (DSB):
+*        In WcsCelestial, if celestial axes are found with no projection
+*        code in CTYPE, assume an old-fashioned CAR projection (i.e. no
+*        rotation from native to WCS coords). Before this change,
+*        CTYPE = "RA" | "DEC" axes got treated as radians, not degrees.
+*     16-SEP-2013 (DSB):
+*        When exporting alternate offset SkyFrames to FITS-WCS headers,
+*        correctly test the alternate Frame in the supplied FrameSet, rather
+*        than the current Frame.
+*     24-SEP-2013 (DSB):
+*        Fix bug in choosing default value for PolyTan attribute.
+*     19-OCT-2013 (DSB):
+*        - In SIPMapping, always ignore any inverse polynomial supplied in
+*        a SIP header as they seem often to be inaccurate. A new inverse is
+*        created to replace it.
+*        - In SIPMapping, only use a fit to the inverted SIP transformation
+*        if an accuracy of 0.01 pixel can be achieved over an area three
+*        times the dimensions of the image. Otherwise use an iterative
+*        inverse for each point. People were seeing bad round-trip errors
+*        when transforming points outside the image because the fit was
+*        being used when it was not very accurate.
+*     12-NOV-2013 (DSB):
+*        Added CardName and CardComm attributes.
+*     13-NOV-2013 (DSB):
+*        Use a zero-length string for the CardComm attribute if the card
+*        has no comment.
+*     15-NOV-2013 (DSB):
+*        - Added method astShowFits.
+*        - Ensure PurgeWcs removes WCS cards even if an error occurs when
+*        reading FrameSets from the FitsChan.
+*        - Change IsMapTab1D to improve chances of a -TAB mapping being found.
+*     6-JAN-2014 (DSB):
+*        - Allow default options for newly created FitsChans to be
+*        specified by the FITSCHAN_OPTIONS environment variable.
+*        - Ensure the used CarLin value is not changed by a trailing frequency axis.
+*     9-JUL-2014 (DSB):
+*        Added attribute FitsAxisOrder, which allows an order to be
+*        specified for WCS axis within FITS headers generated using astWrite.
+*     9-SEP-2014 (DSB):
+*        Modify Split so that any non-printing characters such as
+*        newlines at the end of the string are ignored.
+*     30-SEP-2014 (DSB):
+*        Modify CnvType to indicate that comment cards cannot be
+*        converted to any other data type. For instance, this causes
+*        a warning to be issued if an equals sign is misplaced in a
+*        WCS-related card (causing it to be treated as a comment card).
+*     24-MAR-2015 (DSB):
+*        Modify SpecTrans to avoid modifying the CRPIC value for CAR
+*        projections when they do not need to be modified. The princiuple
+*        is that the bulk of the array should be witin the native longitude
+*        range [-180,+180]. Prompted by bug report from Bill Joye "yet
+*        another CAR issue" on 24-MAR-2015 (file CHIPASS_Equ.head in
+*        ast_tester).
+*     27-APR-2015 (DSB):
+*        Modify MakeFitsFrameSet so that isolated SkyAxes (e.g.
+*        individual axes that have been oicked from a SkyFrame) are
+*        re-mapped into degrees before being used.
+*     20-APR-2015 (DSB):
+*        In MakeIntWorld, relax tolerance for checking that each FITS-WCS IWC
+*        axis is linear, from 0.01 of a pixel to 0.1 of a pixel.
+*     6-JUL-2015 (DSB):
+*        When checking a sub-string, ensure the whole string is at least as
+*        long as the offset to the start of the sub-string. Without this, you
+*        can get erroneous sub-string matches by chance, depending on what
+*        characters happen to be present in memory after the end of the string.
+*     11-AUG-2015 (DSB):
+*        - Fix bug in CheckFitsName that prevented an error from being reported
+*        if the FITS keyword name contained any illegal printable characters.
+*        - Add new Warning "badkeyname", and issue such a warning instead
+*        of an error if illegal characters are found in a keyword name.
+*     31-AUG-2015 (DSB):
+*        In FitLine, use the whole axis rather than 0.1 of the axis (if "dim"
+*        is supplied). This is because non-linearity can become greater at
+*        further distances along the axis. In practice, it meant that SIP
+*        distortion were being treated as linear because the test did not
+*        explore a large enough region of pixel space.
+*     12-OCT-2015 (DSB):
+*        Only add sky axes to a SpecFrame if the WCS Frame contains no
+*        other axes other than the SpecFrame (MakeFitsFrameSet).
+*     17-OCT-2015 (DSB):
+*        - Add new Warning "badkeyvalue", and issue such a warning instead
+*        of an error if the Split function cannot determine the keyword value.
+*        - Move the check for PLTRAH (i.e. DSS encoding) higher up in GetEncoding.
+*        This is because some DSS file slaos have CD and/or PC keywords.
+*     5-NOV-2015 (DSB):
+*        Fix bug in MakeFitsFrameSet that could cause an inappropriate
+*        RefRA and RefDec values to be used when writing out a SpecFrame
+*        using FITS-WCS. This bug was caused by the assumption that
+*        changing the current Frame of a FRameSet also changes the Frame
+*        that was added into the FRameSet. This used to be the  case as
+*        astAddFrame took a clone of the supplied Frame pointer, but it
+*        now takes a deep copy, so the original Frame and the FrameSet's
+*        current Frame are now independent of each other.
+*     28-JUN-2016 ((DSB):
+*        IsAipsSpectral: Trailing spaces in CTYPE values are insignificant.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS FitsChan
+
+/* A macro which tests a character to see if it can be used within a FITS
+   keyword. We include lower case letters here, but they are considered
+   as equivalent to upper case letter. */
+#define isFits(a) ( islower(a) || isupper(a) || isdigit(a) || (a)=='-' || (a)=='_' )
+
+/* A amacro to test if a Frame is a SkyFrame, and is used to describe the
+   sky (skyframes could be used for other purposes - eg a POLANAL Frame). */
+#define IsASkyFrame(frame) (astIsASkyFrame(frame)&&!strcmp("SKY",astGetDomain(frame)))
+
+/* Macro which takes a pointer to a FitsCard and returns non-zero if the
+   card has been used and so should be ignored. */
+#define CARDUSED(card)  ( \
+             ( ignore_used == 2 && \
+                ( (FitsCard *) (card) )->flags & PROVISIONALLY_USED ) || \
+             ( ignore_used >= 1 && \
+                ( (FitsCard *) (card) )->flags & USED ) )
+
+/* Set of characters used to encode a "sequence number" at the end of
+   FITS keywords in an attempt to make them unique.. */
+#define SEQ_CHARS "_ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+/* A general tolerance for equality between floating point values. */
+#define TOL1 10.0*DBL_EPSILON
+
+/* A tolerance for equality between angular values in radians. */
+#define TOL2 1.0E-10
+
+/* Macro to check for equality of floating point angular values. We cannot
+   compare bad values directory because of the danger of floating point
+   exceptions, so bad values are dealt with explicitly. The smallest
+   significant angle is assumed to be 1E-9 radians (0.0002 arc-seconds).*/
+#define EQUALANG(aa,bb) (((aa)==AST__BAD)?(((bb)==AST__BAD)?1:0):(((bb)==AST__BAD)?0:(fabs((aa)-(bb))<=astMAX(1.0E5*(fabs(aa)+fabs(bb))*DBL_EPSILON,1.0E-9))))
+
+/* Macro to compare an angle in radians with zero, allowing some tolerance. */
+#define ZEROANG(aa) (fabs(aa)<1.0E-9)
+
+/* Constants: */
+#define UNKNOWN_ENCODING  -1
+#define NATIVE_ENCODING    0
+#define FITSPC_ENCODING    1
+#define DSS_ENCODING       2
+#define FITSWCS_ENCODING   3
+#define FITSIRAF_ENCODING  4
+#define FITSAIPS_ENCODING  5
+#define FITSAIPSPP_ENCODING 6
+#define FITSCLASS_ENCODING 7
+#define MAX_ENCODING       7
+#define UNKNOWN_STRING     "UNKNOWN"
+#define NATIVE_STRING      "NATIVE"
+#define FITSPC_STRING      "FITS-PC"
+#define FITSPC_STRING2     "FITS_PC"
+#define DSS_STRING         "DSS"
+#define FITSWCS_STRING     "FITS-WCS"
+#define FITSWCS_STRING2    "FITS_WCS"
+#define FITSIRAF_STRING    "FITS-IRAF"
+#define FITSIRAF_STRING2   "FITS_IRAF"
+#define FITSAIPS_STRING    "FITS-AIPS"
+#define FITSAIPS_STRING2   "FITS_AIPS"
+#define FITSAIPSPP_STRING  "FITS-AIPS++"
+#define FITSAIPSPP_STRING2 "FITS_AIPS++"
+#define FITSCLASS_STRING  "FITS-CLASS"
+#define FITSCLASS_STRING2 "FITS_CLASS"
+#define INDENT_INC         3
+#define PREVIOUS           0
+#define NEXT               1
+#define HEADER_TEXT        "Beginning of AST data for "
+#define FOOTER_TEXT        "End of AST data for "
+#define FITSNAMLEN         8
+#define FITSSTCOL          20
+#define FITSRLCOL          30
+#define FITSIMCOL          50
+#define FITSCOMCOL         32
+#define NORADEC            0
+#define FK4                1
+#define FK4NOE             2
+#define FK5                3
+#define GAPPT              4
+#define ICRS               5
+#define NOCEL              0
+#define RADEC              1
+#define ECLIP              2
+#define GALAC              3
+#define SUPER              4
+#define HECLIP             5
+#define AZEL               6
+#define LONAX             -1
+#define NONAX              0
+#define LATAX              1
+#define NDESC              9
+#define MXCTYPELEN        81
+#define ALLWARNINGS       " distortion noequinox noradesys nomjd-obs nolonpole nolatpole tnx zpx badcel noctype badlat badmat badval badctype badpv badkeyname badkeyvalue "
+#define NPFIT             10
+#define SPD               86400.0
+#define FL  1.0/298.257  /*  Reference spheroid flattening factor */
+#define A0  6378140.0    /*  Earth equatorial radius (metres) */
+
+/* String used to represent AST__BAD externally. */
+#define BAD_STRING "<bad>"
+
+/* Each card in the fitschan has a set of flags associated with it,
+   stored in different bits of the "flags" item within each FitsCard
+   structure (note, in AST V1.4 these flags were stored in the "del"
+   item... Dump and LoadFitsChan will need to be changed to use a
+   correspondingly changed name for the external representation of this
+   item). The following flags are currently defined: */
+
+/* "USED" - This flag indicates that the the card has been used in the
+   construction of an AST Object returned by astRead. Such cards should
+   usually be treated as if they do not exist, i.e. they should not be
+   used again by subsequent calls to astRead, they should not be recognised
+   by public FitsChan methods which search the FitsChan for specified
+   cards, and they should not be written out when the FitsChan is deleted.
+   This flag was the only flag available in AST V1.4, and was called
+   "Del" (for "deleted"). Used cards are retained in order to give an
+   indication of where abouts within the header new cards should be placed
+   when astWrite is called (i.e. new cards should usually be placed at
+   the same point within the header as the cards which they replace). */
+#define USED 	1
+
+/* "PROVISIONALLY_USED" - This flag indicates that the the card is being
+   considered as a candidate for inclusion in the construction of an AST
+   Object. If the Object is constructed succesfully, cards flagged as
+   "provisionally used" will be changed to be flagged as "definitely used"
+   (using the USED flag). If the Object fails to be constructed
+   succesfully (if some required cards are missing from the FitsChan
+   for instance), then "provisionally used" cards will be returned to the
+   former state which they had prior to the attempt to construct the
+   object. */
+#define PROVISIONALLY_USED 2
+
+/* "NEW" - This flag indicates that the the card has just been added to
+   the FitsChan and may yet proove to be unrequired. For instance if the
+   supplied Object is not of an appropriate flavour to be stored using
+   the requested encoding, all "new" cards which were added before the
+   inappropriateness was discovered will be removed from the FitsChan.
+   Two different levels of "newness" are available. */
+#define NEW1 4
+#define NEW2 8
+
+/* "PROTECTED" - This flag indicates that the the card should not be
+   removed form the FitsChan when an Object is read using astRead. If
+   this flag is not set, then the card will dehave as if it has been
+   deleted if it was used in the construction of the returned AST Object. */
+#define PROTECTED 16
+
+/* Include files. */
+/* ============== */
+
+/* Interface definitions. */
+/* ---------------------- */
+#include "channel.h"
+#include "cmpframe.h"
+#include "cmpmap.h"
+#include "dssmap.h"
+#include "error.h"
+#include "fitschan.h"
+#include "frame.h"
+#include "frameset.h"
+#include "grismmap.h"
+#include "lutmap.h"
+#include "mathmap.h"
+#include "matrixmap.h"
+#include "memory.h"
+#include "object.h"
+#include "permmap.h"
+#include "pointset.h"
+#include "shiftmap.h"
+#include "skyframe.h"
+#include "timeframe.h"
+#include "keymap.h"
+#include "pal.h"
+#include "erfa.h"
+#include "slamap.h"
+#include "specframe.h"
+#include "dsbspecframe.h"
+#include "specmap.h"
+#include "sphmap.h"
+#include "unit.h"
+#include "unitmap.h"
+#include "polymap.h"
+#include "wcsmap.h"
+#include "winmap.h"
+#include "zoommap.h"
+#include "globals.h"
+#include "fitstable.h"
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <float.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <errno.h>
+
+/* Type Definitions */
+/* ================ */
+
+/* This structure contains information describing a single FITS header card
+   in a circular list of such structures. */
+typedef struct FitsCard {
+   char name[ FITSNAMLEN + 1 ];/* Keyword name (plus terminating null). */
+   int type;                  /* Data type. */
+   void *data;                /* Pointer to the keyword's data value. */
+   char *comment;             /* Pointer to a comment for the keyword. */
+   int flags;                 /* Flags for each card */
+   size_t size;               /* Size of data value */
+   struct FitsCard *next;     /* Pointer to next structure in list. */
+   struct FitsCard *prev;     /* Pointer to previous structure in list. */
+} FitsCard;
+
+/* Structure used to store information derived from the FITS WCS keyword
+   values in a form more convenient to further processing. Conventions
+   for units, etc, for values in a FitsStore follow FITS-WCS (e.g. angular
+   values are stored in degrees, equinox is B or J depending on RADECSYS,
+   etc). */
+typedef struct FitsStore {
+   char ****cname;
+   char ****ctype;
+   char ****ctype_com;
+   char ****cunit;
+   char ****radesys;
+   char ****wcsname;
+   char ****specsys;
+   char ****ssyssrc;
+   char ****ps;
+   char ****timesys;
+   double ***pc;
+   double ***cdelt;
+   double ***crpix;
+   double ***crval;
+   double ***equinox;
+   double ***latpole;
+   double ***lonpole;
+   double ***mjdobs;
+   double ***dut1;
+   double ***mjdavg;
+   double ***pv;
+   double ***wcsaxes;
+   double ***obsgeox;
+   double ***obsgeoy;
+   double ***obsgeoz;
+   double ***restfrq;
+   double ***restwav;
+   double ***zsource;
+   double ***velosys;
+   double ***asip;
+   double ***bsip;
+   double ***apsip;
+   double ***bpsip;
+   double ***imagfreq;
+   double ***axref;
+   int naxis;
+   AstKeyMap *tables;
+   double ***skyref;
+   double ***skyrefp;
+   char ****skyrefis;
+} FitsStore;
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static void (* parent_setsourcefile)( AstChannel *, const char *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_getfull)( AstChannel *, int * );
+static int (* parent_getskip)( AstChannel *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static int (* parent_write)( AstChannel *, AstObject *, int * );
+static AstObject *(* parent_read)( AstChannel *, int * );
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Strings to describe each data type. These should be in the order implied
+   by the corresponding macros (eg AST__FLOAT, etc). */
+static const char *type_names[9] = {"comment", "integer", "floating point",
+                                    "string", "complex floating point",
+                                    "complex integer", "logical",
+                                    "continuation string", "undef" };
+
+/* Text values used to represent Encoding values externally. */
+
+static const char *xencod[8] = { NATIVE_STRING, FITSPC_STRING,
+                                 DSS_STRING, FITSWCS_STRING,
+                                 FITSIRAF_STRING, FITSAIPS_STRING,
+                                 FITSAIPSPP_STRING, FITSCLASS_STRING };
+/* Define two variables to hold TimeFrames which will be used for converting
+   MJD values between time scales. */
+static AstTimeFrame *tdbframe = NULL;
+static AstTimeFrame *timeframe = NULL;
+
+/* Max number of characters in a formatted int */
+static int int_dig;
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->Items_Written = 0; \
+   globals->Write_Nest = -1; \
+   globals->Current_Indent = 0; \
+   globals->Ignore_Used = 1; \
+   globals->Mark_New = 0; \
+   globals->CnvType_Text[ 0 ] = 0; \
+   globals->CnvType_Text0[ 0 ] = 0; \
+   globals->CnvType_Text1[ 0 ] = 0; \
+   globals->CreateKeyword_Seq_Nchars = -1; \
+   globals->FormatKey_Buff[ 0 ] = 0; \
+   globals->FitsGetCom_Sval[ 0 ] = 0; \
+   globals->IsSpectral_Ret = NULL; \
+   globals->Match_Fmt[ 0 ] = 0; \
+   globals->Match_Template = NULL; \
+   globals->Match_PA = 0; \
+   globals->Match_PB = 0; \
+   globals->Match_NA = 0; \
+   globals->Match_NB = 0; \
+   globals->Match_Nentry = 0; \
+   globals->WcsCelestial_Type[ 0 ] = 0; \
+   globals->Ignore_Used = 1; \
+   globals->Mark_New = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(FitsChan)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(FitsChan,Class_Init)
+#define class_vtab astGLOBAL(FitsChan,Class_Vtab)
+#define getattrib_buff astGLOBAL(FitsChan,GetAttrib_Buff)
+#define items_written astGLOBAL(FitsChan,Items_Written)
+#define write_nest astGLOBAL(FitsChan,Write_Nest)
+#define current_indent astGLOBAL(FitsChan,Current_Indent)
+#define ignore_used astGLOBAL(FitsChan,Ignore_Used)
+#define mark_new astGLOBAL(FitsChan,Mark_New)
+#define cnvtype_text astGLOBAL(FitsChan,CnvType_Text)
+#define cnvtype_text0 astGLOBAL(FitsChan,CnvType_Text0)
+#define cnvtype_text1 astGLOBAL(FitsChan,CnvType_Text1)
+#define createkeyword_seq_nchars astGLOBAL(FitsChan,CreateKeyword_Seq_Nchars)
+#define formatkey_buff astGLOBAL(FitsChan,FormatKey_Buff)
+#define fitsgetcom_sval astGLOBAL(FitsChan,FitsGetCom_Sval)
+#define isspectral_ret astGLOBAL(FitsChan,IsSpectral_Ret)
+#define match_fmt astGLOBAL(FitsChan,Match_Fmt)
+#define match_template astGLOBAL(FitsChan,Match_Template)
+#define match_pa astGLOBAL(FitsChan,Match_PA)
+#define match_pb astGLOBAL(FitsChan,Match_PB)
+#define match_na astGLOBAL(FitsChan,Match_NA)
+#define match_nb astGLOBAL(FitsChan,Match_NB)
+#define match_nentry  astGLOBAL(FitsChan,Match_Nentry)
+#define wcscelestial_type astGLOBAL(FitsChan,WcsCelestial_Type)
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+static pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX3 pthread_mutex_lock( &mutex3 );
+#define UNLOCK_MUTEX3 pthread_mutex_unlock( &mutex3 );
+static pthread_mutex_t mutex4 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX4 pthread_mutex_lock( &mutex4 );
+#define UNLOCK_MUTEX4 pthread_mutex_unlock( &mutex4 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Buffer returned by GetAttrib. */
+static char getattrib_buff[ AST__FITSCHAN_GETATTRIB_BUFF_LEN + 1 ];
+
+/* Buffer for returned text string in CnvType */
+static char cnvtype_text[ AST__FITSCHAN_FITSCARDLEN + 1 ];
+
+/* Buffer for real value in CnvType */
+static char cnvtype_text0[ AST__FITSCHAN_FITSCARDLEN + 1 ];
+
+/* Buffer for imaginary value in CnvType */
+static char cnvtype_text1[ AST__FITSCHAN_FITSCARDLEN + 1 ];
+
+/* Number of output items written since the last "Begin" or "IsA"
+   output item, and level of Object nesting during recursive
+   invocation of the astWrite method. */
+static int items_written = 0;
+static int write_nest = -1;
+
+/* Indentation level for indented comments when writing Objects to a
+   FitsChan. */
+static int current_indent = 0;
+
+/* Ignore_Used: If 2, then cards which have been marked as either "definitely
+   used" or "provisionally used" (see the USED flag above) will be ignored
+   when searching the FitsChan, etc (i.e. they will be treated as if they
+   have been removed from the FitsChan). If 1, then cards which have been
+   "definitely used" will be skipped over. If zero then no cards will be
+   skipped over. */
+static int ignore_used = 1;
+
+/* Mark_New: If non-zero, then all cards added to the FitsChan will be
+   marked with both the NEW1 and NEW2 flags (see above). If zero then
+   new cards will not be marked with either NEW1 or NEW2. */
+static int mark_new = 0;
+
+/* Number of characters used for encoding */
+static int createkeyword_seq_nchars = -1;
+
+/* Buffer for value returned by FormatKey */
+static char formatkey_buff[ 10 ];
+
+/* Buffer for value returned by FitsGetCom */
+static char fitsgetcom_sval[ AST__FITSCHAN_FITSCARDLEN + 1 ];
+
+/* Pointer returned by IsSpectral */
+static const char *isspectral_ret = NULL;
+
+/* Format specifier for reading an integer field in Match */
+static char match_fmt[ 10 ];
+
+/* Pointer to start of template in Match */
+static const char *match_template = NULL;
+
+/* Pointer to first returned field value in Match */
+static int *match_pa = 0;
+
+/* Pointer to last returned field value in Match */
+static int *match_pb = 0;
+
+/* No. of characters read from the test string in Match */
+static int match_na = 0;
+
+/* No. of characters read from the template string in Match */
+static int match_nb = 0;
+
+/* Number of recursive entries into Match */
+static int match_nentry = 0;
+
+/* Buffer for celestial system in WcsCelestial */
+static char wcscelestial_type[ 4 ];
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstFitsChanVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+#define LOCK_MUTEX3
+#define UNLOCK_MUTEX3
+#define LOCK_MUTEX4
+#define UNLOCK_MUTEX4
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstFitsChan *astFitsChanForId_( const char *(*)( void ),
+                           char *(*)( const char *(*)( void ), int * ),
+                           void (*)( const char * ),
+                           void (*)( void (*)( const char * ), const char *, int * ),
+                           const char *, ... );
+AstFitsChan *astFitsChanId_( const char *(* source)( void ),
+                             void (* sink)( const char * ),
+                             const char *options, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static int GetObjSize( AstObject *, int * );
+static void ClearCard( AstFitsChan *, int * );
+static int GetCard( AstFitsChan *, int * );
+static int TestCard( AstFitsChan *, int * );
+static void SetCard( AstFitsChan *, int, int * );
+static void ClearEncoding( AstFitsChan *, int * );
+static int GetEncoding( AstFitsChan *, int * );
+static int TestEncoding( AstFitsChan *, int * );
+static void SetEncoding( AstFitsChan *, int, int * );
+static void ClearCDMatrix( AstFitsChan *, int * );
+static int GetCDMatrix( AstFitsChan *, int * );
+static int TestCDMatrix( AstFitsChan *, int * );
+static void SetCDMatrix( AstFitsChan *, int, int * );
+static void ClearFitsDigits( AstFitsChan *, int * );
+static int GetFitsDigits( AstFitsChan *, int * );
+static int TestFitsDigits( AstFitsChan *, int * );
+static void SetFitsDigits( AstFitsChan *, int, int * );
+static void ClearFitsAxisOrder( AstFitsChan *, int * );
+static const char *GetFitsAxisOrder( AstFitsChan *, int * );
+static int TestFitsAxisOrder( AstFitsChan *, int * );
+static void SetFitsAxisOrder( AstFitsChan *, const char *, int * );
+static void ClearDefB1950( AstFitsChan *, int * );
+static int GetDefB1950( AstFitsChan *, int * );
+static int TestDefB1950( AstFitsChan *, int * );
+static void SetDefB1950( AstFitsChan *, int, int * );
+static void ClearTabOK( AstFitsChan *, int * );
+static int GetTabOK( AstFitsChan *, int * );
+static int TestTabOK( AstFitsChan *, int * );
+static void SetTabOK( AstFitsChan *, int, int * );
+static void ClearCarLin( AstFitsChan *, int * );
+static int GetCarLin( AstFitsChan *, int * );
+static int TestCarLin( AstFitsChan *, int * );
+static void SetCarLin( AstFitsChan *, int, int * );
+static void ClearPolyTan( AstFitsChan *, int * );
+static int GetPolyTan( AstFitsChan *, int * );
+static int TestPolyTan( AstFitsChan *, int * );
+static void SetPolyTan( AstFitsChan *, int, int * );
+static void ClearIwc( AstFitsChan *, int * );
+static int GetIwc( AstFitsChan *, int * );
+static int TestIwc( AstFitsChan *, int * );
+static void SetIwc( AstFitsChan *, int, int * );
+static void ClearClean( AstFitsChan *, int * );
+static int GetClean( AstFitsChan *, int * );
+static int TestClean( AstFitsChan *, int * );
+static void SetClean( AstFitsChan *, int, int * );
+static void ClearWarnings( AstFitsChan *, int * );
+static const char *GetWarnings( AstFitsChan *, int * );
+static int TestWarnings( AstFitsChan *, int * );
+static void SetWarnings( AstFitsChan *, const char *, int * );
+
+static AstFitsChan *SpecTrans( AstFitsChan *, int, const char *, const char *, int * );
+static AstFitsTable *GetNamedTable( AstFitsChan *, const char *, int, int, int, const char *, int * );
+static AstFrameSet *MakeFitsFrameSet( AstFitsChan *, AstFrameSet *, int, int, int, const char *, const char *, int * );
+static AstGrismMap *ExtractGrismMap( AstMapping *, int, AstMapping **, int * );
+static AstKeyMap *GetTables( AstFitsChan *, int * );
+static AstMapping *AddUnitMaps( AstMapping *, int, int, int * );
+static AstMapping *CelestialAxes( AstFitsChan *this, AstFrameSet *, double *, int *, char, FitsStore *, int *, int, const char *, const char *, int * );
+static AstMapping *GrismSpecWcs( char *, FitsStore *, int, char, AstSpecFrame *, const char *, const char *, int * );
+static AstMapping *IsMapTab1D( AstMapping *, double, const char *, AstFrame *, double *, int, int, AstFitsTable **, int *, int *, int *, int * );
+static AstMapping *IsMapTab2D( AstMapping *, double, const char *, AstFrame *, double *, int, int, int, int, AstFitsTable **, int *, int *, int *, int *, int *, int *, int *, int *, int * );
+static AstMapping *LinearWcs( FitsStore *, int, char, const char *, const char *, int * );
+static AstMapping *LogAxis( AstMapping *, int, int, double *, double *, double, int * );
+static AstMapping *LogWcs( FitsStore *, int, char, const char *, const char *, int * );
+static AstMapping *MakeColumnMap( AstFitsTable *, const char *, int, int, const char *, const char *, int * );
+static AstMapping *NonLinSpecWcs( AstFitsChan *, char *, FitsStore *, int, char, AstSpecFrame *, const char *, const char *, int * );
+static AstMapping *OtherAxes( AstFitsChan *, AstFrameSet *, double *, int *, char, FitsStore *, double *, int *, const char *, const char *, int * );
+static AstMapping *SIPMapping( double *, FitsStore *, char, int, const char *, const char *, int * );
+static AstMapping *SpectralAxes( AstFitsChan *, AstFrameSet *, double *, int *, char, FitsStore *, double *, int *, const char *, const char *, int * );
+static AstMapping *TabMapping( AstFitsChan *, FitsStore *, char, int **, const char *, const char *, int *);
+static AstMapping *WcsCelestial( AstFitsChan *, FitsStore *, char, AstFrame **, AstFrame *, double *, double *, AstSkyFrame **, AstMapping **, int *, const char *, const char *, int * );
+static AstMapping *WcsIntWorld( AstFitsChan *, FitsStore *, char, int, const char *, const char *, int * );
+static AstMapping *WcsMapFrm( AstFitsChan *, FitsStore *, char, AstFrame **, const char *, const char *, int * );
+static AstMapping *WcsNative( AstFitsChan *, FitsStore *, char, AstWcsMap *, int, int, const char *, const char *, int * );
+static AstMapping *WcsOthers( AstFitsChan *, FitsStore *, char, AstFrame **, AstFrame *, const char *, const char *, int * );
+static AstMapping *WcsSpectral( AstFitsChan *, FitsStore *, char, AstFrame **, AstFrame *, double, double, AstSkyFrame *, const char *, const char *, int * );
+static AstMapping *ZPXMapping( AstFitsChan *, FitsStore *, char, int, int[2], const char *, const char *, int * );
+static AstMatrixMap *WcsCDeltMatrix( FitsStore *, char, int, const char *, const char *, int * );
+static AstMatrixMap *WcsPCMatrix( FitsStore *, char, int, const char *, const char *, int * );
+static AstObject *FsetFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
+static AstObject *Read( AstChannel *, int * );
+static AstSkyFrame *WcsSkyFrame( AstFitsChan *, FitsStore *, char, int, char *, int, int, const char *, const char *, int * );
+static AstTimeScaleType TimeSysToAst( AstFitsChan *, const char *, const char *, const char *, int * );
+static AstWinMap *WcsShift( FitsStore *, char, int, const char *, const char *, int * );
+static FitsCard *GetLink( FitsCard *, int, const char *, const char *, int * );
+static FitsStore *FitsToStore( AstFitsChan *, int, const char *, const char *, int * );
+static FitsStore *FreeStore( FitsStore *, int * );
+static FitsStore *FsetToStore( AstFitsChan *, AstFrameSet *, int, double *, int, const char *, const char *, int * );
+static char *CardComm( AstFitsChan *, int * );
+static char *CardName( AstFitsChan *, int * );
+static char *ConcatWAT( AstFitsChan *, int, const char *, const char *, int * );
+static char *FormatKey( const char *, int, int, char, int * );
+static char *GetItemC( char *****, int, int, char, char *, const char *method, const char *class, int * );
+static char *SourceWrap( const char *(*)( void ), int * );
+static char *UnPreQuote( const char *, int * );
+static char GetMaxS( double ****item, int * );
+static const char *GetAllWarnings( AstFitsChan *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static const char *GetCardComm( AstFitsChan *, int * );
+static const char *GetCardName( AstFitsChan *, int * );
+static const char *GetFitsSor( const char *, int * );
+static const char *IsSpectral( const char *, char[5], char[5], int * );
+static double **OrthVectorSet( int, int, double **, int * );
+static double *Cheb2Poly( double *, int, int, double, double, double, double, int * );
+static double *FitLine( AstMapping *, double *, double *, double *, double, double *, int * );
+static double *OrthVector( int, int, double **, int * );
+static double *ReadCrval( AstFitsChan *, AstFrame *, char, const char *, const char *, int * );
+static double ChooseEpoch( AstFitsChan *, FitsStore *, char, const char *, const char *, int * );
+static double DateObs( const char *, int * );
+static double GetItem( double ****, int, int, char, char *, const char *method, const char *class, int * );
+static double NearestPix( AstMapping *, double, int, int * );
+static double TDBConv( double, int, int, const char *, const char *, int * );
+static int *CardFlags( AstFitsChan *, int * );
+static int AIPSFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
+static int AIPSPPFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
+static int AddEncodingFrame( AstFitsChan *, AstFrameSet *, int, const char *, const char *, int * );
+static int AddVersion( AstFitsChan *, AstFrameSet *, int, int, FitsStore *, double *, char, int, int, const char *, const char *, int * );
+static int CLASSFromStore( AstFitsChan *, FitsStore *, AstFrameSet *, double *, const char *, const char *, int * );
+static int CardType( AstFitsChan *, int * );
+static int CheckFitsName( AstFitsChan *, const char *, const char *, const char *, int * );
+static int ChrLen( const char *, int * );
+static int CnvType( int, void *, size_t, int, int, void *, const char *, const char *, const char *, int * );
+static int CnvValue( AstFitsChan *, int , int, void *, const char *, int * );
+static int ComBlock( AstFitsChan *, int, const char *, const char *, int * );
+static int CountFields( const char *, char, const char *, const char *, int * );
+static int DSSFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
+static int EncodeFloat( char *, int, int, int, double, int * );
+static int EncodeValue( AstFitsChan *, char *, int, int, const char *, int * );
+static int FindBasisVectors( AstMapping *, int, int, double *, AstPointSet *, AstPointSet *, int * );
+static int FindFits( AstFitsChan *, const char *, char[ AST__FITSCHAN_FITSCARDLEN + 1 ], int, int * );
+static int FindKeyCard( AstFitsChan *, const char *, const char *, const char *, int * );
+static int FindLonLatSpecAxes( FitsStore *, char, int *, int *, int *, const char *, const char *, int * );
+static int FindString( int, const char *[], const char *, const char *, const char *, const char *, int * );
+static int FitOK( int, double *, double *, double, int * );
+static int FitsAxisOrder( AstFitsChan *this, int nwcs, AstFrame *wcsfrm, int *perm, int *status );
+static int FitsEof( AstFitsChan *, int * );
+static int FitsFromStore( AstFitsChan *, FitsStore *, int, double *, AstFrameSet *, const char *, const char *, int * );
+static int FitsGetCom( AstFitsChan *, const char *, char **, int * );
+static int FitsSof( AstFitsChan *, int * );
+static int FullForm( const char *, const char *, int, int * );
+static int GetCardType( AstFitsChan *, int * );
+static int GetFiducialWCS( AstWcsMap *, AstMapping *, int, int, double *, double *, int * );
+static int GetFitsCF( AstFitsChan *, const char *, double *, int * );
+static int GetFitsCI( AstFitsChan *, const char *, int *, int * );
+static int GetFitsCN( AstFitsChan *, const char *, char **, int * );
+static int GetFitsF( AstFitsChan *, const char *, double *, int * );
+static int GetFitsI( AstFitsChan *, const char *, int *, int * );
+static int GetFitsL( AstFitsChan *, const char *, int *, int * );
+static int GetFitsS( AstFitsChan *, const char *, char **, int * );
+static int GetFull( AstChannel *, int * );
+static int GetMaxI( double ****item, char, int * );
+static int GetMaxJM( double ****item, char, int * );
+static int GetMaxJMC( char *****item, char, int * );
+static int GetNcard( AstFitsChan *, int * );
+static int GetNkey( AstFitsChan *, int * );
+static int GetSkip( AstChannel *, int * );
+static int GetUsedPolyTan( AstFitsChan *, AstFitsChan *, int, int, char, const char *, const char *, int * );
+static int GetValue( AstFitsChan *, const char *, int, void *, int, int, const char *, const char *, int * );
+static int GetValue2( AstFitsChan *, AstFitsChan *, const char *, int, void *, int, const char *, const char *, int * );
+static int GoodWarns( const char *, int * );
+static int HasAIPSSpecAxis( AstFitsChan *, const char *, const char *, int * );
+static int HasCard( AstFitsChan *, const char *, const char *, const char *, int * );
+static int IRAFFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
+static int IsAIPSSpectral( const char *, char **, char **, int * );
+static int IsMapLinear( AstMapping *, const double [], const double [], int, int * );
+static int IsSkyOff( AstFrameSet *, int, int * );
+static int KeyFields( AstFitsChan *, const char *, int, int *, int *, int * );
+static int LooksLikeClass( AstFitsChan *, const char *, const char *, int * );
+static int MakeBasisVectors( AstMapping *, int, int, double *, AstPointSet *, AstPointSet *, int * );
+static int MakeIntWorld( AstMapping *, AstFrame *, int *, char, FitsStore *, double *, const char *, const char *, int * );
+static int Match( const char *, const char *, int, int *, int *, const char *, const char *, int * );
+static int MatchChar( char, char, const char *, const char *, const char *, int * );
+static int MatchFront( const char *, const char *, char *, int *, int *, int *, const char *, const char *, const char *, int * );
+static int MoveCard( AstFitsChan *, int, const char *, const char *, int * );
+static int PCFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
+static int SAOTrans( AstFitsChan *, AstFitsChan *, const char *, const char *, int * );
+static int SearchCard( AstFitsChan *, const char *, const char *, const char *, int * );
+static int SetFits( AstFitsChan *, const char *, void *, int, const char *, int, int * );
+static int Similar( const char *, const char *, int * );
+static int SkySys( AstFitsChan *, AstSkyFrame *, int, int, FitsStore *, int, int, char c, int, const char *, const char *, int * );
+static int Split( AstFitsChan *, const char *, char **, char **, char **, const char *, const char *, int * );
+static int SplitMap( AstMapping *, int, int, int, AstMapping **, AstWcsMap **, AstMapping **, int * );
+static int SplitMap2( AstMapping *, int, AstMapping **, AstWcsMap **, AstMapping **, int * );
+static int SplitMat( int , double *, double *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestFits( AstFitsChan *, const char *, int *, int * );
+static int Use( AstFitsChan *, int, int, int * );
+static int Ustrcmp( const char *, const char *, int * );
+static int Ustrncmp( const char *, const char *, size_t, int * );
+static int WATCoeffs( const char *, int, double **, int **, int *, int * );
+static int WcsFromStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
+static int WcsNatPole( AstFitsChan *, AstWcsMap *, double, double, double, double *, double *, double *, int * );
+static int WorldAxes( AstFitsChan *this, AstMapping *, double *, int *, int * );
+static int Write( AstChannel *, AstObject *, int * );
+static void *CardData( AstFitsChan *, size_t *, int * );
+static void AdaptLut( AstMapping *, int, double, double, double, double, double, double **, double **, int *, int * );
+static void AddFrame( AstFitsChan *, AstFrameSet *, int, int, FitsStore *, char, const char *, const char *, int * );
+static void ChangePermSplit( AstMapping *, int * );
+static void CheckZero( char *, double, int, int * );
+static void Chpc1( double *, double *, int, int *, int *, int * );
+static void ClassTrans( AstFitsChan *, AstFitsChan *, int, int, const char *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void CreateKeyword( AstFitsChan *, const char *, char [ FITSNAMLEN + 1 ], int * );
+static void DSBSetUp( AstFitsChan *, FitsStore *, AstDSBSpecFrame *, char, double, const char *, const char *, int * );
+static void DSSToStore( AstFitsChan *, FitsStore *, const char *, const char *, int * );
+static void DelFits( AstFitsChan *, int * );
+static void Delete( AstObject *, int * );
+static void DeleteCard( AstFitsChan *, const char *, const char *, int * );
+static void DistortMaps( AstFitsChan *, FitsStore *, char, int , AstMapping **, AstMapping **, AstMapping **, AstMapping **, const char *, const char *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void EmptyFits( AstFitsChan *, int * );
+static void FindWcs( AstFitsChan *, int, int, int, const char *, const char *, int * );
+static void FixNew( AstFitsChan *, int, int, const char *, const char *, int * );
+static void FixUsed( AstFitsChan *, int, int, int, const char *, const char *, int * );
+static void FormatCard( AstFitsChan *, char *, const char *, int * );
+static void FreeItem( double ****, int * );
+static void FreeItemC( char *****, int * );
+static void GetFiducialNSC( AstWcsMap *, double *, double *, int * );
+static void GetFiducialPPC( AstWcsMap *, double *, double *, int * );
+static void GetNextData( AstChannel *, int, char **, char **, int * );
+static void InsCard( AstFitsChan *, int, const char *, int, void *, const char *, const char *, const char *, int * );
+static void MakeBanner( const char *, const char *, const char *, char [ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ], int * );
+static void MakeIndentedComment( int, char, const char *, const char *, char [ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1], int * );
+static void MakeIntoComment( AstFitsChan *, const char *, const char *, int * );
+static void MakeInvertable( double **, int, double *, int * );
+static void MarkCard( AstFitsChan *, int * );
+static void NewCard( AstFitsChan *, const char *, int, const void *, const char *, int, int * );
+static void PreQuote( const char *, char [ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 3 ], int * );
+static void PurgeWCS( AstFitsChan *, int * );
+static void PutCards( AstFitsChan *, const char *, int * );
+static void PutFits( AstFitsChan *, const char [ AST__FITSCHAN_FITSCARDLEN + 1 ], int, int * );
+static void PutTable( AstFitsChan *, AstFitsTable *, const char *, int * );
+static void PutTables( AstFitsChan *, AstKeyMap *, int * );
+static void ReadFits( AstFitsChan *, int * );
+static void ReadFromSource( AstFitsChan *, int * );
+static void RemoveTables( AstFitsChan *, const char *, int * );
+static void RetainFits( AstFitsChan *, int * );
+static void RoundFString( char *, int, int * );
+static void SetAlgCode( char *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetFitsCF( AstFitsChan *, const char *, double *, const char *, int, int * );
+static void SetFitsCI( AstFitsChan *, const char *, int *, const char *, int, int * );
+static void SetFitsCM( AstFitsChan *, const char *, int, int * );
+static void SetFitsCN( AstFitsChan *, const char *, const char *, const char *, int, int * );
+static void SetFitsCom( AstFitsChan *, const char *, const char *, int, int * );
+static void SetFitsF( AstFitsChan *, const char *, double, const char *, int, int * );
+static void SetFitsI( AstFitsChan *, const char *, int, const char *, int, int * );
+static void SetFitsL( AstFitsChan *, const char *, int, const char *, int, int * );
+static void SetFitsS( AstFitsChan *, const char *, const char *, const char *, int, int * );
+static void SetFitsU( AstFitsChan *, const char *, const char *, int, int * );
+static void SetItem( double ****, int, int, char, double, int * );
+static void SetItemC( char *****, int, int, char, const char *, int * );
+static void SetSourceFile( AstChannel *, const char *, int * );
+static void SetValue( AstFitsChan *, const char *, void *, int, const char *, int * );
+static void ShowFits( AstFitsChan *, int * );
+static void Shpc1( double, double, int, double *, double *, int * );
+static void SinkWrap( void (*)( const char * ), const char *, int * );
+static void SkyPole( AstWcsMap *, AstMapping *, int, int, int *, char, FitsStore *, const char *, const char *, int * );
+static void TableSource( AstFitsChan *, void (*)( AstFitsChan *, const char *, int, int, int * ), int * );
+static void TidyOffsets( AstFrameSet *, int * );
+static void Warn( AstFitsChan *, const char *, const char *, const char *, const char *, int * );
+static void WcsFcRead( AstFitsChan *, AstFitsChan *, FitsStore *, const char *, const char *, int * );
+static void WcsToStore( AstFitsChan *, AstFitsChan *, FitsStore *, const char *, const char *, int * );
+static void WriteBegin( AstChannel *, const char *, const char *, int * );
+static void WriteDouble( AstChannel *, const char *, int, int, double, const char *, int * );
+static void WriteEnd( AstChannel *, const char *, int * );
+static void WriteFits( AstFitsChan *, int * );
+static void WriteInt( AstChannel *, const char *, int, int, int, const char *, int * );
+static void WriteIsA( AstChannel *, const char *, const char *, int * );
+static void WriteObject( AstChannel *, const char *, int, int, AstObject *, const char *, int * );
+static void WriteString( AstChannel *, const char *, int, int, const char *, const char *, int * );
+static void WriteToSink( AstFitsChan *, int * );
+static void SetTableSource( AstFitsChan *,
+                            void (*)( void ),
+                            void (*)( void (*)( void ),
+                                      AstFitsChan *, const char *, int, int, int * ), int * );
+static void TabSourceWrap( void (*)( void ),
+                           AstFitsChan *, const char *, int, int, int * );
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+
+static void AdaptLut( AstMapping *map, int npos, double eps, double x0,
+                      double x1, double v0, double v1, double **xtab,
+                      double **vtab, int *nsamp, int *status ){
+/*
+*  Name:
+*     AdaptLut
+
+*  Purpose:
+*     Create a table of optimally sampled values for a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void AdaptLut( AstMapping *map, int npos, double eps, double x0,
+*                    double x1, double v0, double v1, double **xtab,
+*                    double **vtab, int *nsamp, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function returns a look-up table holding samples of the supplied
+*     1D mapping. The input values at which the samples are taken are
+*     returned in the "xtab" array, and the Mapping output values at
+*     these input values are returned in the "vtab" array. The sample
+*     spacing is smaller at positions where the output gradient is
+*     changing more rapidly (i.e. where the output is more non-linear).
+
+*  Parameters:
+*     map
+*        Pointer to the Mapping. Should have 1 input and 1 output.
+*     npos
+*        The minimum number of samples to place within the interval to be
+*         sampled, excluding the two end points (which are always sampeld
+*         anyway). These samples are placed evenly through the [x0,x1]
+          interval. The interval between adjacent samples will be further
+*         subdivided if necessary by calling this function recursively.
+*     eps
+*        The maximum error in X (i.e. the Mapping input) allowed before
+*        the supplied interval is subdivided further by a recursive call
+*        to this function.
+*     x0
+*        The Mapping input value at the start of the interval to be sampled.
+*        It is assumed that this value is already stored in (*xtab)[0] on
+*        entry.
+*     x1
+*        The Mapping input value at the end of the interval to be sampled.
+*     v0
+*        The Mapping output value at the start of the interval to be sampled.
+*        It is assumed that this value is already stored in (*vtab)[0] on
+*        entry.
+*     v1
+*        The Mapping output value at the end of the interval to be sampled.
+*     xtab
+*        Address of a pointer to the array in which to store the Mapping
+*        input values at which samples were taken. The supplied pointer
+*        may be changed on exit to point to a larger array. New values
+*        are added to the end of this array. The initial size of the array
+*        is given by the supplied value for "*nsamp"
+*     vtab
+*        Address of a pointer to the array in which to store the Mapping
+*        output value at each sample. The supplied pointer may be changed
+*        on exit to point to a larger array. New values are added to the
+*        end of this array. The initial size of the array is given by the
+*        supplied value for "*nsamp".
+*     nsamp
+*        Address of an int holding the number of values in the "*xtab"
+*        and "*ytab" arrays. Updated on exit to include the new values
+*        added to the arrays by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The size of the returned xtab and vtab arrays.
+*/
+
+/* Local Variables: */
+   double *vv;               /* Pointer to Mapping output values */
+   double *xx;               /* Pointer to Mapping input values */
+   double dx;                /* Step between sample positions */
+   double rg;                /* Reciprocal of gradient of (x0,v0)->(x1,v1) line */
+   double xx0;               /* X at first new sample position */
+   int ipos;                 /* Interior sample index */
+   int isamp;                /* Index into extended xtab and vtab arrays. */
+   int subdivide;            /* Subdivide each subinterval? */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Allocate work space. */
+   xx = astMalloc( sizeof( double )*npos );
+   vv = astMalloc( sizeof( double )*npos );
+   if( astOK ) {
+
+/* Set up the evenly spaced interior sample positions. */
+      dx = ( x1 - x0 )/( npos + 1 );
+      xx0 = x0 + dx;
+      for( ipos = 0; ipos < npos; ipos++ ) {
+         xx[ ipos ] = xx0 + ipos*dx;
+      }
+
+/* Find the Mapping output values at these input values. */
+      astTran1( map, npos, xx, 1, vv );
+
+/* See if any of these samples deviate significantly from the straight line
+   defined by (x0,v0) and (x1,v1). If any such sample is found, we call
+   this function recursively to sample the subdivided intervals. First
+   handle cases where the straight line has zero gradient. */
+      subdivide = 0;
+      if( v0 == v1 ) {
+
+/* Subdivide if any of the interior sample values are different to the
+   end values. */
+         for( ipos = 0; ipos < npos; ipos++ ) {
+            if( vv[ ipos ] != v0 ) {
+               subdivide = 1;
+               break;
+            }
+         }
+
+/* Now handle cases where the line has non-zero gradient. Subdivide if any
+   of the interior sample input positions are further than "eps" from the
+   input position that would give the same output value if the mapping was
+   linear. */
+      } else {
+         rg = ( x1 - x0 )/( v1 - v0 );
+         for( ipos = 0; ipos < npos; ipos++ ) {
+            if( vv[ ipos ] == AST__BAD ||
+                fabs( rg*( vv[ ipos ] - v0 ) - ( xx[ ipos ] - x0 ) ) > eps ) {
+               subdivide = 1;
+               break;
+            }
+         }
+      }
+
+/* If required, call this function recursively to subdivide each section
+   of the supplied input interval, and append samples to the returned
+   arrays. */
+      if( subdivide ) {
+
+/* Do each sub-interval, except the last one. The number of subintervals
+   is one more than the number of interior samples. */
+         for( ipos = 0; ipos < npos; ipos++ ) {
+
+/* Append samples covering the current subinterval to the ends of the
+   arrays. */
+            AdaptLut( map, npos, eps, x0, xx[ ipos ], v0, vv[ ipos ],
+                      xtab, vtab, nsamp, status );
+
+/* Store the starting position for the next sub-interval. */
+            x0 = xx[ ipos ];
+            v0 = vv[ ipos ];
+         }
+
+/* Now do the final sub-interval. */
+         AdaptLut( map, npos, eps, x0, x1, v0, v1, xtab, vtab, nsamp, status );
+
+/* If we do not need to subdivide, store the samples in the returned
+   array, together with the supplied final point. */
+      } else {
+
+/* Extend the arrays. */
+         isamp = *nsamp;
+         *nsamp += npos + 1;
+         *xtab = astGrow( *xtab, *nsamp, sizeof( double ) );
+         *vtab = astGrow( *vtab, *nsamp, sizeof( double ) );
+         if( astOK ) {
+
+/* Store the sample positions and values at the end of the extended
+   arrays. */
+            for( ipos = 0; ipos < npos; ipos++, isamp++ ) {
+               (*xtab)[ isamp ] = xx[ ipos ];
+               (*vtab)[ isamp ] = vv[ ipos ];
+            }
+            (*xtab)[ isamp ] = x1;
+            (*vtab)[ isamp ] = v1;
+         }
+      }
+   }
+
+/* Free resources. */
+   xx = astFree( xx );
+   vv= astFree( vv );
+}
+
+static int AddEncodingFrame( AstFitsChan *this, AstFrameSet *fs, int encoding,
+                             const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     AddEncodingFrame
+
+*  Purpose:
+*     Add a Frame which conforms to the requirements of the specified encoding.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int AddEncodingFrame( AstFitsChan *this, AstFrameSet *fs, int encoding,
+*                           const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function attempts to create a Frame based on the current Frame
+*     of the supplied FrameSet, which conforms to the requirements of the
+*     specified Encoding. If created, this Frame is added into the
+*     FrameSet as the new current Frame, and the index of the original current
+*     Frame is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     fs
+*        Pointer to the FrameSet.
+*     encoding
+*        The encoding in use.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The index of the original current Frame in the FrameSet. A value of
+*     AST__NOFRAME is returned if no new Frame is added to the FrameSet,
+*     or if an error occurs.
+*/
+
+/* Local Variables: */
+   AstCmpFrame *cmpfrm;   /* Pointer to spectral cube frame */
+   AstFrame *cfrm;        /* Pointer to original current Frame */
+   AstFrame *newfrm;      /* Frame describing coord system to be used */
+   AstFrame *pfrm;        /* Pointer to primary Frame containing axis */
+   AstFrameSet *fsconv;   /* FrameSet converting what we have to what we want */
+   AstMapping *map;       /* Mapping from what we have to what we want */
+   AstSkyFrame *skyfrm;   /* Pointer to SkyFrame */
+   AstSpecFrame *specfrm; /* Pointer to SpecFrame */
+   AstSystemType sys;     /* Frame coordinate system */
+   int i;                 /* Axis index */
+   int naxc;              /* No. of axes in original current Frame */
+   int paxis;             /* Axis index in primary frame */
+   int result;            /* Returned value */
+
+/* Initialise */
+   result = AST__NOFRAME;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a pointer to the current Frame and note how many axes it has. */
+   cfrm = astGetFrame( fs, AST__CURRENT );
+   naxc = astGetNaxes( cfrm );
+
+/* FITS-CLASS */
+/* ========== */
+   if( encoding == FITSCLASS_ENCODING ) {
+
+/* Try to locate a SpecFrame and a SkyFrame in the current Frame. */
+      specfrm = NULL;
+      skyfrm = NULL;
+      for( i = 0; i < naxc; i++ ) {
+         astPrimaryFrame( cfrm, i, &pfrm, &paxis );
+         if( astIsASpecFrame( pfrm ) ) {
+            if( !specfrm ) specfrm = astCopy( pfrm );
+         } else if( IsASkyFrame( pfrm ) ) {
+            if( !skyfrm ) skyfrm = astCopy( pfrm );
+         }
+         pfrm = astAnnul( pfrm );
+      }
+
+/* Cannot do anything if either is missing. */
+      if( specfrm && skyfrm ) {
+
+/* If the spectral axis is not frequency, set it to frequency. Also set
+   spectral units of "Hz". */
+         sys = astGetSystem( specfrm );
+         if( sys != AST__FREQ ) {
+            astSetSystem( specfrm, AST__FREQ );
+            sys = AST__FREQ;
+         }
+
+/* Ensure the standard of rest is Source and units are "Hz". */
+         astSetUnit( specfrm, 0, "Hz" );
+         astSetStdOfRest( specfrm, AST__SCSOR );
+
+/* The celestial axes must be either FK4, FK5 or galactic. */
+         sys = astGetSystem( skyfrm );
+         if( sys != AST__FK4 && sys != AST__FK5 && sys != AST__GALACTIC ) {
+            astSetSystem( skyfrm, AST__FK5 );
+            sys = AST__FK5;
+         }
+
+/* FK5 systems must be J2000, and FK4 must be B1950. */
+         if( sys == AST__FK5 ) {
+            astSetC( skyfrm, "Equinox", "J2000.0" );
+         } else if( sys == AST__FK4 ) {
+            astSetC( skyfrm, "Equinox", "B1950.0" );
+         }
+
+/* Combine the spectral and celestial Frames into a single CmpFrame with
+   the spectral axis being the first axis. */
+         cmpfrm = astCmpFrame( specfrm, skyfrm, "", status );
+
+/* Attempt to obtain the current Frame of the supplied FrameSet to this
+   new Frame. */
+         fsconv = astConvert( cfrm, cmpfrm, "" );
+         if( fsconv ) {
+
+/* Get the Mapping and current Frame from the rconversion FrameSet. */
+            newfrm = astGetFrame( fsconv, AST__CURRENT );
+            map = astGetMapping( fsconv, AST__BASE, AST__CURRENT );
+
+/* Save the original current Frame index. */
+            result = astGetCurrent( fs );
+
+/* Add the new Frame into the supplied FrameSet using the above Mapping
+   to connect it to the original current Frame. The new Frame becomes the
+   current Frame. */
+            astAddFrame( fs, AST__CURRENT, map, newfrm );
+
+/* Free resources */
+            map = astAnnul( map );
+            newfrm = astAnnul( newfrm );
+            fsconv = astAnnul( fsconv );
+         }
+
+/* Free resources */
+         cmpfrm = astAnnul( cmpfrm );
+      }
+
+/* Release resources. */
+      if( specfrm ) specfrm = astAnnul( specfrm );
+      if( skyfrm ) skyfrm = astAnnul( skyfrm );
+   }
+
+/* Free reources. */
+   cfrm = astAnnul( cfrm );
+
+/* Return the result */
+   return result;
+}
+
+static void AddFrame( AstFitsChan *this, AstFrameSet *fset, int pixel,
+                      int npix, FitsStore *store, char s, const char *method,
+                      const char *class, int *status ){
+/*
+*  Name:
+*     AddFrame
+
+*  Purpose:
+*     Create a Frame describing a set of axes with a given co-ordinate
+*     version, and add it to the supplied FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void AddFrame( AstFitsChan *this, AstFrameSet *fset, int pixel,
+*                    int npix, FitsStore *store, char s, const char *method,
+*                    const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A Frame is created describing axis with a specific co-ordinate
+*     version character, reading information from the supplied FitsStore.
+*     A suitable Mapping is created to connect the new Frame to the pixel
+*     (GRID) Frame in the supplied FrameSet, and the Frame is added into
+*     the FrameSet using this Mapping.
+
+*  Parameters:
+*     this
+*        The FitsChan from which the keywords were read. Warning messages
+*        are added to this FitsChan if the celestial co-ordinate system is
+*        not recognized.
+*     fset
+*        Pointer to the FrameSet to be extended.
+*     pixel
+*        The index of the pixel (GRID) Frame within fset.
+*     npix
+*        The number of pixel axes.
+*     store
+*        The FitsStore containing the required information extracted from
+*        the FitsChan.
+*     s
+*        The co-ordinate version character. A space means the primary
+*        axis descriptions. Otherwise the supplied character should be
+*        an upper case alphabetical character ('A' to 'Z').
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *frame;            /* Requested Frame */
+   AstMapping *mapping;        /* Mapping from pixel to requested Frame */
+   AstMapping *tmap;           /* Temporary Mapping pointer */
+   AstPermMap *pmap;           /* PermMap pointer to add or remove axes */
+   double con;                 /* Value to be assigned to missing axes */
+   int *inperm;                /* Pointer to input axis permutation array */
+   int *outperm;               /* Pointer to output axis permutation array */
+   int i;                      /* Axis index */
+   int nwcs;                   /* Number of wcs axes */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Get a Mapping between pixel coordinates and physical coordinates, using
+   the requested axis descriptions. Also returns a Frame describing the
+   physical coordinate system. */
+   mapping = WcsMapFrm( this, store, s, &frame, method, class, status );
+
+/* Add the Frame into the FrameSet, and annul the mapping and frame. If
+   the new Frame has more axes than the pixel Frame, use a PermMap which
+   assigns constant value 1.0 to the extra axes. If the new Frame has less
+   axes than the pixel Frame, use a PermMap which throws away the extra
+   axes. */
+   if( mapping != NULL ) {
+      nwcs = astGetNin( mapping );
+      if( nwcs != npix ) {
+         inperm = astMalloc( sizeof(int)*(size_t)npix );
+         outperm = astMalloc( sizeof(int)*(size_t)nwcs );
+         if( astOK ) {
+            for( i = 0; i < npix; i++ ) {
+               inperm[ i ] = ( i < nwcs ) ? i : -1;
+            }
+            for( i = 0; i < nwcs; i++ ) {
+               outperm[ i ] = ( i < npix ) ? i : -1;
+            }
+            con = 1.0;
+            pmap = astPermMap( npix, inperm, nwcs, outperm, &con, "", status );
+            tmap = (AstMapping *) astCmpMap( pmap, mapping, 1, "", status );
+            pmap = astAnnul( pmap );
+            (void) astAnnul( mapping );
+            mapping = tmap;
+         }
+         inperm = astFree( inperm );
+         outperm = astFree( outperm );
+      }
+      astAddFrame( fset, pixel, mapping, frame );
+
+/* Annul temporary resources. */
+      mapping = astAnnul( mapping );
+   }
+   frame = astAnnul( frame );
+}
+
+static int AddVersion( AstFitsChan *this, AstFrameSet *fs, int ipix, int iwcs,
+                       FitsStore *store, double *dim, char s, int encoding,
+                       int isoff, const char *method, const char *class,
+                       int *status ){
+
+/*
+*  Name:
+*     AddVersion
+
+*  Purpose:
+*     Add values to a FitsStore describing a specified Frame in a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int AddVersion( AstFitsChan *this, AstFrameSet *fs, int ipix, int iwcs,
+*                     FitsStore *store, double *dim, char s, int encoding,
+*                     int isoff, const char *method, const char *class,
+*                     int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Values are added to the supplied FitsStore describing the specified
+*     WCS Frame, and its relationship to the specified pixel Frame. These
+*     values are based on the standard FITS-WCS conventions.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     fs
+*        Pointer to the FrameSet.
+*     ipix
+*        The index of the pixel (GRID) Frame within fset.
+*     iwcs
+*        The index of the Frame within fset to use as the WCS co-ordinate
+*        Frame.
+*     store
+*        The FitsStore in which to store the information extracted from
+*        the FrameSet.
+*     dim
+*        Pointer to an array of pixel axis dimensions. Individual elements
+*        will be AST__BAD if dimensions are not known. The number of
+*        elements should equal the number of axes in the base Frame of the
+*        supplied FrameSet.
+*     s
+*        The co-ordinate version character. A space means the primary
+*        axis descriptions. Otherwise the supplied character should be
+*        an upper case alphabetical character ('A' to 'Z').
+*     encoding
+*        The encoding being used.
+*     isoff
+*        If greater than zero, the Frame is an offset SkyFrame and the
+*        description added to the FitsStore should describe offset coordinates.
+*        If less than than zero, the Frame is an offset SkyFrame and the
+*        description added to the FitsStore should describe absolute coordinates.
+*        If zero, the Frame is an absolute SkyFrame and the description added
+*        to the FitsSTore should (by  necessity) describe absolute coordinates.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Retuned Value:
+*     A value of 1 is returned if the WCS Frame was succesfully added to
+*     the FitsStore. A value of zero is returned otherwise.
+*/
+
+/* Local Variables: */
+   AstFrame *wcsfrm;        /* WCS Frame */
+   AstFrameSet *fset;       /* Temporary FrameSet */
+   AstMapping *iwcmap;      /* Mapping from WCS to IWC Frame */
+   AstMapping *mapping;     /* Mapping from pixel to WCS Frame */
+   AstMapping *pixiwcmap;   /* Mapping from pixel to IWC Frame */
+   AstMapping *tmap2;       /* Temporary Mapping */
+   AstMapping *tmap;        /* Temporary Mapping */
+   const char *old_skyrefis;/* Old value of SkyRefIs attribute */
+   double *crvals;          /* Pointer to array holding default CRVAL values */
+   double cdelt2;           /* Sum of squared PC values */
+   double cdelt;            /* CDELT value for axis */
+   double crpix;            /* CRPIX value for axis */
+   double crval;            /* CRVAL value for axis */
+   double pc;               /* Element of the PC array */
+   int *axis_done;          /* Flags indicating which axes have been done */
+   int *wperm;              /* FITS axis for each Mapping output (Frame axis) */
+   int fits_i;              /* FITS WCS axis index */
+   int fits_j;              /* FITS pixel axis index */
+   int iax;                 /* Frame axis index */
+   int icurr;               /* Index of current Frame */
+   int nwcs;                /* No. of axes in WCS frame */
+   int ret;                 /* Returned value */
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* If the frame is a SkyFrame describing offset coordinates, but the
+   description added to the FitsStore should be for absolute coordinates,
+   temporarily clear the SkyFrame SkyRefIs attribute. We need to make it
+   the current Frame first so that we can use the FrameSet to clear the
+   attribte, so that the SkyFrame will be re-mapped within the FrameSet
+   to take account of the clearing. For negative isoff values, set the
+   specific negative value to indicate the original SkyRefIs value. */
+   if( isoff < 0 ) {
+      icurr = astGetCurrent( fs );
+      astSetCurrent( fs, iwcs );
+      old_skyrefis = astGetC( fs, "SkyRefIs" );
+      if( astOK ) {
+         if( !Ustrcmp( old_skyrefis, "POLE", status ) ) {
+            isoff = -1;
+         } else if( !Ustrcmp( old_skyrefis, "ORIGIN", status ) ) {
+            isoff = -2;
+         } else {
+            isoff = -3;
+         }
+      }
+      astClear( fs, "SkyRefIs" );
+      astSetCurrent( fs, icurr );
+   } else {
+      old_skyrefis = AST__BAD_REF;
+   }
+
+/* Construct a new FrameSet holding the pixel and WCS Frames from the
+   supplied FrameSet, but in which the current Frame is a copy of the
+   supplied WCS Frame, but optionally extended to include any extra axes
+   needed to conform to the FITS model. For instance, if the WCS Frame
+   consists of a single 1D SpecFrame with a defined celestial reference
+   position (SpecFrame attributes RefRA and RefDec), then FITS-WCS paper
+   III requires there to be a pair of celestial axes in the WCS Frame in
+   which the celestial reference point for the spectral axis is defined. */
+   fset = MakeFitsFrameSet( this, fs, ipix, iwcs, encoding, method, class, status );
+
+/* If required, re-instate the original value of the SkyRefIs attribute
+   in the supplied FrameSet. */
+   if( old_skyrefis != AST__BAD_REF ) {
+      astSetCurrent( fs, iwcs );
+      astSetC( fs, "SkyRefIs", old_skyrefis );
+      astSetCurrent( fs, icurr );
+   }
+
+/* Abort if the FrameSet could not be produced. */
+   if( !fset ) return ret;
+
+/* Get the Mapping from base to current Frame and check its inverse is
+   defined. Return if not. Note, we can handle non-invertable Mappings if
+   we are allowed to use the -TAB algorithm. */
+   mapping = astGetMapping( fset, AST__BASE, AST__CURRENT );
+   wcsfrm = astGetFrame( fset, AST__CURRENT );
+   if( !astGetTranInverse( mapping ) && astGetTabOK( this ) <= 0 ) {
+      mapping = astAnnul( mapping );
+      wcsfrm = astAnnul( wcsfrm );
+      fset = astAnnul( fset );
+      return ret;
+   }
+
+/* We now need to choose the "FITS WCS axis" (i.e. the number that is included
+   in FITS keywords such as CRVAL2) for each axis of the output Frame.
+   Allocate memory to store these indices. */
+   nwcs= astGetNout( mapping );
+   wperm = astMalloc( sizeof(int)*(size_t) nwcs );
+
+/* Attempt to use the FitsAxisOrder attribute to determine the order. If
+   this is set to "<auto>", then for each WCS axis, we use the index of
+   the pixel axis which is most closely aligned with it. */
+   if( !FitsAxisOrder( this, nwcs, wcsfrm, wperm, status ) &&
+       !WorldAxes( this, mapping, dim, wperm, status ) ) {
+      wperm = astFree( wperm );
+      mapping = astAnnul( mapping );
+      wcsfrm = astAnnul( wcsfrm );
+      fset = astAnnul( fset );
+      return ret;
+   }
+
+/* Allocate an array of flags, one for each axis, which indicate if a
+   description of the corresponding axis has yet been stored in the
+   FitsStore. Initialise them to indicate that no axes have yet been
+   described. */
+   axis_done = astMalloc( sizeof(int)*(size_t) nwcs );
+   if( astOK ) for( iax = 0; iax < nwcs; iax++ ) axis_done[ iax ] = 0;
+
+/* Get the original reference point from the FitsChan and convert it into
+   the require WCS Frame. This is used as the default reference point (some
+   algorithms may choose to ignore this default reference point ). */
+   crvals = ReadCrval( this, wcsfrm, s, method, class, status );
+
+/* For each class of FITS conventions (celestial, spectral, others),
+   identify any corresponding axes within the WCS Frame and add
+   descriptions of them to the FitsStore. These descriptions are in terms
+   of the FITS keywords defined in the corresponding FITS-WCS paper. Note,
+   the keywords which descirbed the pixel->IWC mapping (CRPIX, CD, PC,
+   CDELT) are not stored by these functions, instead each function
+   returns a Mapping from WCS to IWC coords (these Mappings
+   pass on axes of the wrong class without change). These Mappings are
+   combined in series to get the final WCS->IWC Mapping. First do
+   celestial axes. */
+   iwcmap = CelestialAxes( this, fset, dim, wperm, s, store, axis_done,
+                           isoff, method, class, status );
+
+/* Now look for spectral axes, and update the iwcmap. */
+   tmap = SpectralAxes( this, fset, dim, wperm, s, store, crvals, axis_done,
+                        method, class, status );
+   tmap2 = (AstMapping *) astCmpMap( iwcmap, tmap, 1, "", status );
+   tmap = astAnnul( tmap );
+   (void) astAnnul( iwcmap );
+   iwcmap = tmap2;
+
+/* Finally add descriptions of any axes not yet described (they are
+   assumed to be linear), and update the iwcmap. */
+   tmap = OtherAxes( this, fset, dim, wperm, s, store, crvals, axis_done,
+                     method, class, status );
+   tmap2 = (AstMapping *) astCmpMap( iwcmap, tmap, 1, "", status );
+   tmap = astAnnul( tmap );
+   (void) astAnnul( iwcmap );
+   iwcmap = tmap2;
+
+/* The "iwcmap" Mapping found above converts from the WCS Frame to the IWC
+   Frame. Combine the pixel->WCS Mapping with this WCS->IWC Mapping to
+   get the pixel->IWC Mapping. */
+   pixiwcmap = (AstMapping *) astCmpMap( mapping, iwcmap, 1, "", status );
+   mapping = astAnnul( mapping );
+   iwcmap = astAnnul( iwcmap );
+
+/* Now attempt to store values for the keywords describing the pixel->IWC
+   Mapping (CRPIX, CD, PC, CDELT). This tests that the iwcmap is linear.
+   Zero is returned if the test fails. */
+   ret = MakeIntWorld( pixiwcmap, wcsfrm, wperm, s, store, dim, method, class,
+                       status );
+
+/* If succesfull... */
+   if( ret ) {
+
+/* Store the Domain name as the WCSNAME keyword (if set). */
+      if( astTestDomain( wcsfrm ) ) {
+         SetItemC( &(store->wcsname), 0, 0, s, (char *) astGetDomain( wcsfrm ),
+                   status );
+      }
+
+/* Store the UT1-UTC correction, if set, converting from seconds to days
+   (as used by JACH). */
+      if( astTestDut1( wcsfrm ) && s == ' ' ) {
+         SetItem( &(store->dut1), 0, 0, ' ', astGetDut1( wcsfrm )/SPD, status );
+      }
+
+/* Set CRVAL values which are very small compared to the pixel size to
+   zero. */
+      for( iax = 0; iax < nwcs; iax++ ) {
+         fits_i = wperm[ iax ];
+         crval = GetItem( &(store->crval), fits_i, 0, s, NULL, method, class,
+                          status );
+         if( crval != AST__BAD ) {
+            cdelt2 = 0.0;
+            for( fits_j = 0; fits_j < nwcs; fits_j++ ){
+               pc = GetItem( &(store->pc), fits_i, fits_j, s, NULL, method, class, status );
+               if( pc == AST__BAD ) pc = ( fits_i == fits_j ) ? 1.0 : 0.0;
+               cdelt2 += pc*pc;
+            }
+            cdelt = GetItem( &(store->cdelt), fits_i, 0, s, NULL, method, class, status );
+            if( cdelt == AST__BAD ) cdelt = 1.0;
+            cdelt2 *= ( cdelt*cdelt );
+            if( fabs( crval ) < sqrt( DBL_EPSILON*cdelt2 ) ) {
+               SetItem( &(store->crval), fits_i, 0, s, 0.0, status );
+            }
+         }
+      }
+
+/* Round CRPIX values to the nearest millionth of a pixel. */
+      for( iax = 0; iax < nwcs; iax++ ) {
+         crpix = GetItem( &(store->crpix), 0, iax, s, NULL, method, class, status );
+         if( crpix != AST__BAD ) {
+            SetItem( &(store->crpix), 0, iax, s,
+                     floor( crpix*1.0E6 + 0.5 )*1.0E-6, status );
+         }
+      }
+   }
+
+/* Free remaining resources. */
+   if( crvals ) crvals = astFree( crvals );
+   wcsfrm = astAnnul( wcsfrm );
+   pixiwcmap = astAnnul( pixiwcmap );
+   axis_done = astFree( axis_done );
+   wperm = astFree( wperm );
+   fset = astAnnul( fset );
+
+/* If an error has occurred, return zero */
+   return astOK ? ret : 0;
+}
+
+static AstMapping *AddUnitMaps( AstMapping *map, int iax, int nax, int *status ) {
+/*
+*  Name:
+*     AddUnitMaps
+
+*  Purpose:
+*     Embed a Mapping within a pair of parallel UnitMaps.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstMapping *AddUnitMaps( AstMapping *map, int iax, int nax, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns a Mapping which consists of the supplied Mapping
+*     in parallel with a pair of UnitMaps so that the first axis of the
+*     supplied Mapping is at a specified axis number in the returned Mapping.
+
+*  Parameters:
+*     map
+*        Pointer to the Mapping. The Mapping must have equal numbers of
+*        input and output coordinates.
+*     iax
+*        The index for the first input of "map" within the returned
+*        Mapping.
+*     nax
+*        The number of axes for the returned Mapping.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A Mapping which has "nax" axes, and in which the "iax" axis
+*     corresponds to the first axis of "map". Axes lower than "iax" are
+*     transformed using a UnitMap, and axes higher than the last axis of
+*     "map" are transformed using a UnitMap.
+*/
+
+/* Local Variables: */
+   AstMapping *ret;      /* Returned Mapping */
+   AstMapping *tmap0;    /* Temporary Mapping */
+   AstMapping *tmap1;    /* Temporary Mapping */
+   AstMapping *tmap2;    /* Temporary Mapping */
+   int nmap;             /* Number of supplied Mapping inputs */
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Initialise the returned Mapping to be a clone of the supplied Mapping. */
+   ret = astClone( map );
+
+/* Note the number of inputs of the supplied Mapping (assumed to be equal
+   to the number of outputs). */
+   nmap = astGetNin( map );
+
+/* If necessary produce a parallel CmpMap which combines the Mapping with a
+   UnitMap representing the axes lower than "iax". */
+   if( iax > 0 ) {
+      tmap0 = (AstMapping *) astUnitMap( iax, "", status );
+      tmap1 = (AstMapping *) astCmpMap( tmap0, ret, 0, "", status );
+      ret = astAnnul( ret );
+      tmap0 = astAnnul( tmap0 );
+      ret = tmap1;
+   }
+
+/* If necessary produce a parallel CmpMap which combines the Mapping with a
+   UnitMap representing the axes higher than "iax+nmap". */
+   if( iax + nmap < nax ) {
+      tmap1 = (AstMapping *) astUnitMap( nax - iax - nmap, "", status );
+      tmap2 = (AstMapping *) astCmpMap( ret, tmap1, 0, "", status );
+      ret = astAnnul( ret );
+      tmap1 = astAnnul( tmap1 );
+      ret = tmap2;
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static int AIPSFromStore( AstFitsChan *this, FitsStore *store,
+                          const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     AIPSFromStore
+
+*  Purpose:
+*     Store WCS keywords in a FitsChan using FITS-AIPS encoding.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     int AIPSFromStore( AstFitsChan *this, FitsStore *store,
+*                        const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function copies the WCS information stored in the supplied
+*     FitsStore into the supplied FitsChan, using FITS-AIPS encoding.
+*
+*     AIPS encoding is like FITS-WCS encoding but with the following
+*     restrictions:
+*
+*     1) The celestial projection must not have any projection parameters
+*     which are not set to their default values. The one exception to this
+*     is that SIN projections are acceptable if the associated projection
+*     parameter PV<axlat>_1 is zero and PV<axlat>_2 = cot( reference point
+*     latitude). This is encoded using the string "-NCP". The SFL projection
+*     is encoded using the string "-GLS". Note, the original AIPS WCS
+*     system only recognised a small subset of the currently available
+*     projections, but some more recent AIPS-like software recognizes some
+*     of the new projections included in the FITS-WCS encoding. The AIT,
+*     GLS and MER can only be written if the CRVAL keywords are zero for
+*     both longitude and latitude axes.
+*
+*     2) The celestial axes must be RA/DEC, galactic or ecliptic.
+*
+*     3) LONPOLE and LATPOLE must take their default values.
+*
+*     4) Only primary axis descriptions are written out.
+*
+*     5) EPOCH is written instead of EQUINOX & RADECSYS, and uses the
+*        IAU 1984 rule ( EPOCH < 1984.0 is treated as a Besselian epoch
+*        and implies RADECSYS=FK4,  EPOCH >= 1984.0 is treated as a
+*        Julian epoch and implies RADECSYS=FK5). The RADECSYS & EQUINOX
+*        values in the FitsStore must be consistent with this rule.
+*
+*     6) Any rotation produced by the PC matrix must be restricted to
+*        the celestial plane, and must involve no shear. A CROTA keyword
+*        with associated CDELT values are produced instead of the PC
+*        matrix.
+*
+*     7) ICRS is not supported.
+*
+*     8) Spectral axes can be created only for FITS-WCS CTYPE values of "FREQ"
+*        "VRAD" and "VOPT-F2W" and with standards of rest of LSRK, LSRD,
+*        BARYCENT and GEOCENTR.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        Pointer to the FitsStore.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if succesfull, and zero is returned
+*     otherwise.
+*/
+
+/* Local Variables: */
+   char *comm;         /* Pointer to comment string */
+   const char *cval;   /* Pointer to string keyword value */
+   const char *specunit;/* Pointer to corrected spectral units string */
+   char combuf[80];    /* Buffer for FITS card comment */
+   char lattype[MXCTYPELEN];/* Latitude axis CTYPE */
+   char lontype[MXCTYPELEN];/* Longitude axis CTYPE */
+   char s;             /* Co-ordinate version character */
+   char sign[2];       /* Fraction's sign character */
+   char spectype[MXCTYPELEN];/* Spectral axis CTYPE */
+   double *cdelt;      /* Pointer to CDELT array */
+   double cdl;         /* CDELT term */
+   double cdlat_lon;   /* Off-diagonal CD element */
+   double cdlon_lat;   /* Off-diagonal CD element */
+   double coscro;      /* Cos( CROTA ) */
+   double crota;       /* CROTA value to use */
+   double epoch;       /* Epoch of reference equinox */
+   double fd;          /* Fraction of a day */
+   double latval;      /* CRVAL for latitude axis */
+   double lonval;      /* CRVAL for longitude axis */
+   double mjd99;       /* MJD at start of 1999 */
+   double p1, p2;      /* Projection parameters */
+   double rho_a;       /* First estimate of CROTA */
+   double rho_b;       /* Second estimate of CROTA */
+   double sincro;      /* Sin( CROTA ) */
+   double specfactor;  /* Factor for converting internal spectral units */
+   double val;         /* General purpose value */
+   int axlat;          /* Index of latitude FITS WCS axis */
+   int axlon;          /* Index of longitude FITS WCS axis */
+   int axrot1;         /* Index of first CROTA rotation axis */
+   int axrot2;         /* Index of second CROTA rotation axis */
+   int axspec;         /* Index of spectral FITS WCS axis */
+   int i;              /* Axis index */
+   int ihmsf[ 4 ];     /* Hour, minute, second, fractional second */
+   int iymdf[ 4 ];     /* Year, month, date, fractional day */
+   int j;              /* Axis index */
+   int jj;             /* SlaLib status */
+   int naxis;          /* No. of axes */
+   int ok;             /* Is FitsSTore OK for IRAF encoding? */
+   int prj;            /* Projection type */
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* Initialise */
+   specunit = "";
+   specfactor = 1.0;
+
+/* First check that the values in the FitsStore conform to the
+   requirements of the AIPS encoding. Assume they do to begin with. */
+   ok = 1;
+
+/* Just do primary axes. */
+   s = ' ';
+
+/* Look for the primary celestial axes. */
+   FindLonLatSpecAxes( store, s, &axlon, &axlat, &axspec, method, class, status );
+
+/* If both longitude and latitude axes are present ...*/
+   if( axlon >= 0 && axlat >= 0 ) {
+
+/* Get the CRVAL values for both axes. */
+      latval = GetItem( &( store->crval ), axlat, 0, s, NULL, method, class, status );
+      if( latval == AST__BAD ) ok = 0;
+      lonval = GetItem( &( store->crval ), axlon, 0, s, NULL, method, class, status );
+      if( lonval == AST__BAD ) ok = 0;
+
+/* Get the CTYPE values for both axes. Extract the projection type as
+   specified by the last 4 characters in the latitude CTYPE keyword value. */
+      cval = GetItemC( &(store->ctype), axlon, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else {
+         strcpy( lontype, cval );
+      }
+      cval = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+         prj = AST__WCSBAD;
+      } else {
+         strcpy( lattype, cval );
+         prj = astWcsPrjType( cval + 4 );
+      }
+
+/* Check the projection type is OK. */
+      if( prj == AST__WCSBAD ){
+         ok = 0;
+      } else if( prj != AST__SIN ){
+
+/* There must be no projection parameters. */
+         if( GetMaxJM( &(store->pv), ' ', status ) >= 0 ) {
+            ok = 0;
+
+/* FITS-AIPS cannot handle the AST-specific TPN projection. */
+         } else if( prj == AST__TPN ) {
+            ok = 0;
+
+/* For AIT, MER and GLS, check that the reference point is the origin of
+   the celestial co-ordinate system. */
+         } else if( prj == AST__MER ||
+                    prj == AST__AIT ||
+                    prj == AST__SFL ) {
+            if( latval != 0.0 || lonval != 0.0 ){
+               ok = 0;
+
+/* Change the new SFL projection code to to the older equivalent GLS */
+            } else if( prj == AST__SFL ){
+               (void) strcpy( lontype + 4, "-GLS" );
+               (void) strcpy( lattype + 4, "-GLS" );
+            }
+         }
+
+/* SIN projections are only acceptable if the associated projection
+   parameters are both zero, or if the first is zero and the second
+   = cot( reference point latitude )  (the latter case is equivalent to
+   the old NCP projection). */
+      } else {
+         p1 = GetItem( &( store->pv ), axlat, 1, s, NULL, method, class, status );
+         p2 = GetItem( &( store->pv ), axlat, 2, s, NULL, method, class, status );
+         if( p1 == AST__BAD ) p1 = 0.0;
+         if( p2 == AST__BAD ) p2 = 0.0;
+         ok = 0;
+         if( p1 == 0.0 ) {
+            if( p2 == 0.0 ) {
+               ok = 1;
+            } else if( fabs( p2 ) >= 1.0E14 && latval == 0.0 ){
+               ok = 1;
+               (void) strcpy( lontype + 4, "-NCP" );
+               (void) strcpy( lattype + 4, "-NCP" );
+            } else if( fabs( p2*tan( AST__DD2R*latval ) - 1.0 )
+                       < 0.01 ){
+               ok = 1;
+               (void) strcpy( lontype + 4, "-NCP" );
+               (void) strcpy( lattype + 4, "-NCP" );
+            }
+         }
+      }
+
+/* Identify the celestial coordinate system from the first 4 characters of the
+   longitude CTYPE value. Only RA, galactic longitude, and ecliptic
+   longitude can be stored using FITS-AIPS. */
+      if( ok && strncmp( lontype, "RA--", 4 ) &&
+               strncmp( lontype, "GLON", 4 ) &&
+               strncmp( lontype, "ELON", 4 ) ) ok = 0;
+
+/* If the physical Frame requires a LONPOLE or LATPOLE keyword, it cannot
+   be encoded using FITS-IRAF. */
+      if( GetItem( &(store->latpole), 0, 0, s, NULL, method, class, status )
+          != AST__BAD ||
+          GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status )
+          != AST__BAD ) ok = 0;
+   }
+
+/* If a spectral axis is present ...*/
+   if( ok && axspec >= 0 ) {
+
+/* Get the CTYPE values for the axis, and find the AIPS equivalent, if
+   possible. */
+      cval = GetItemC( &(store->ctype), axspec, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else {
+         if( !strncmp( cval, "FREQ", astChrLen( cval ) ) ) {
+            strcpy( spectype, "FREQ" );
+         } else if( !strncmp( cval, "VRAD", astChrLen( cval ) ) ) {
+            strcpy( spectype, "VELO" );
+         } else if( !strncmp( cval, "VOPT-F2W", astChrLen( cval ) ) ) {
+            strcpy( spectype, "FELO" );
+         } else {
+            ok = 0;
+         }
+      }
+
+/* If OK, check the SPECSYS value and add the AIPS equivalent onto the
+   end of the CTYPE value.*/
+      cval = GetItemC( &(store->specsys), 0, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else if( ok ) {
+         if( !strncmp( cval, "LSRK", astChrLen( cval ) ) ) {
+            strcpy( spectype+4, "-LSR" );
+         } else if( !strncmp( cval, "LSRD", astChrLen( cval ) ) ) {
+            strcpy( spectype+4, "-LSD" );
+         } else if( !strncmp( cval, "BARYCENT", astChrLen( cval ) ) ) {
+            strcpy( spectype+4, "-HEL" );
+         } else if( !strncmp( cval, "GEOCENTR", astChrLen( cval ) ) ) {
+            strcpy( spectype+4, "-GEO" );
+         } else {
+            ok = 0;
+         }
+      }
+
+/* If still OK, ensure the spectral axis units are Hz or m/s. */
+      cval = GetItemC( &(store->cunit), axspec, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else if( ok ) {
+         if( !strcmp( cval, "Hz" ) ) {
+            specunit = "HZ";
+            specfactor = 1.0;
+         } else if( !strcmp( cval, "kHz" ) ) {
+            specunit = "HZ";
+            specfactor = 1.0E3;
+         } else if( !strcmp( cval, "MHz" ) ) {
+            specunit = "HZ";
+            specfactor = 1.0E6;
+         } else if( !strcmp( cval, "GHz" ) ) {
+            specunit = "HZ";
+            specfactor = 1.0E9;
+         } else if( !strcmp( cval, "m/s" ) ) {
+            specunit = "m/s";
+            specfactor = 1.0;
+         } else if( !strcmp( cval, "km/s" ) ) {
+            specunit = "m/s";
+            specfactor = 1.0E3;
+         } else {
+            ok = 0;
+         }
+      }
+   }
+
+/* Save the number of axes */
+   naxis = GetMaxJM( &(store->crpix), ' ', status ) + 1;
+
+/* If this is different to the value of NAXIS abort since this encoding
+   does not support WCSAXES keyword. */
+   if( naxis != store->naxis ) ok = 0;
+
+/* Allocate memory to store the CDELT values */
+   if( ok ) {
+      cdelt = (double *) astMalloc( sizeof(double)*naxis );
+      if( !cdelt ) ok = 0;
+   } else {
+      cdelt = NULL;
+   }
+
+/* Check that rotation is restricted to the celestial plane, and extract
+   the CDELT (diagonal) terms, etc. If there are no celestial
+   axes, restrict rotation to the first two non-spectral axes. */
+   if( axlat < 0 && axlon < 0 ) {
+      if( axspec >= 0 && naxis > 2 ) {
+         axrot2 = ( axspec == 0 ) ? 1 : 0;
+         axrot1 = axrot2 + 1;
+         if( axrot1 == axspec ) axrot1++;
+      } else if( naxis > 1 ){
+         axrot2 = 0;
+         axrot1 = 1;
+      } else {
+         axrot2 = -1;
+         axrot1 = -1;
+      }
+   } else {
+      axrot1 = axlon;
+      axrot2 = axlat;
+   }
+   cdlat_lon = 0.0;
+   cdlon_lat = 0.0;
+   for( i = 0; i < naxis && ok; i++ ){
+      cdl = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
+      if( cdl == AST__BAD ) cdl = 1.0;
+      for( j = 0; j < naxis && ok; j++ ){
+          val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
+          if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
+          val *= cdl;
+          if( i == j ){
+             cdelt[ i ] = val;
+          } else if( i == axrot2 && j == axrot1 ){
+             cdlat_lon = val;
+          } else if( i == axrot1 && j == axrot2 ){
+             cdlon_lat = val;
+          } else if( val != 0.0 ){
+             ok = 0;
+          }
+      }
+   }
+
+/* Find the CROTA and CDELT values for the celestial axes. */
+   if( ok && axrot1 >= 0 && axrot2 >= 0 ) {
+      if( cdlat_lon > 0.0 ) {
+         rho_a = atan2( cdlat_lon, cdelt[ axrot1 ] );
+      } else if( cdlat_lon == 0.0 ) {
+         rho_a = 0.0;
+      } else {
+         rho_a = atan2( -cdlat_lon, -cdelt[ axrot1 ] );
+      }
+      if( cdlon_lat > 0.0 ) {
+         rho_b = atan2( cdlon_lat, -cdelt[ axrot2 ] );
+      } else if( cdlon_lat == 0.0 ) {
+         rho_b = 0.0;
+      } else {
+         rho_b = atan2( -cdlon_lat, cdelt[ axrot2 ] );
+      }
+      if( fabs( palDrange( rho_a - rho_b ) ) < 1.0E-2 ){
+         crota = 0.5*( palDranrm( rho_a ) + palDranrm( rho_b ) );
+         coscro = cos( crota );
+         sincro = sin( crota );
+         if( fabs( coscro ) > fabs( sincro ) ){
+            cdelt[ axrot2 ] /= coscro;
+            cdelt[ axrot1 ] /= coscro;
+         } else {
+            cdelt[ axrot2 ] = -cdlon_lat/sincro;
+            cdelt[ axrot1 ] = cdlat_lon/sincro;
+         }
+         crota *= AST__DR2D;
+      } else {
+         ok = 0;
+      }
+   } else {
+      crota = 0.0;
+   }
+
+/* Get RADECSYS and the reference equinox (called EPOCH in FITS-AIPS). */
+   cval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
+   epoch = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );
+
+/* If RADECSYS was available... */
+   if( cval ){
+
+/* ICRS is not supported in this encoding. */
+      if( !strcmp( "ICRS", cval ) ) ok = 0;
+
+/* If epoch was not available, set a default epoch. */
+      if( epoch == AST__BAD ){
+         if( !strcmp( "FK4", cval ) ){
+            epoch = 1950.0;
+         } else if( !strcmp( "FK5", cval ) ){
+            epoch = 2000.0;
+         } else {
+            ok = 0;
+         }
+
+/* If an epoch was supplied, check it is consistent with the IAU 1984
+   rule. */
+      } else {
+         if( !strcmp( "FK4", cval ) ){
+            if( epoch >= 1984.0 ) ok = 0;
+         } else if( !strcmp( "FK5", cval ) ){
+            if( epoch < 1984.0 ) ok = 0;
+         } else {
+            ok = 0;
+         }
+      }
+   }
+
+/* Only create the keywords if the FitsStore conforms to the requirements
+   of the FITS-AIPS encoding. */
+   if( ok ) {
+
+/* Get and save CRPIX for all pixel axes. These are required, so break
+   if they are not available. */
+      for( j = 0; j < naxis && ok; j++ ){
+         val = GetItem( &(store->crpix), 0, j, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+         } else {
+            sprintf( combuf, "Reference pixel on axis %d", j + 1 );
+            SetValue( this, FormatKey( "CRPIX", j + 1, -1, s, status ), &val,
+                      AST__FLOAT, combuf, status );
+         }
+      }
+
+/* Get and save CRVAL for all intermediate axes. These are required, so
+   break if they are not available. */
+      for( i = 0; i < naxis && ok; i++ ){
+         val = GetItem( &(store->crval), i, 0, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+         } else {
+            if( i == axspec ) val *= specfactor;
+            sprintf( combuf, "Value at ref. pixel on axis %d", i + 1 );
+            SetValue( this, FormatKey( "CRVAL", i + 1, -1, s, status ), &val,
+                      AST__FLOAT, combuf, status );
+         }
+      }
+
+/* Get and save CTYPE for all intermediate axes. These are required, so
+   break if they are not available. Use the potentially modified versions
+   saved above for the celestial axes. */
+      for( i = 0; i < naxis && ok; i++ ){
+         if( i == axlat ) {
+            cval = lattype;
+         } else if( i == axlon ) {
+            cval = lontype;
+         } else if( i == axspec ) {
+            cval = spectype;
+         } else {
+            cval = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+         }
+         if( cval && ( strlen(cval) < 5 || strcmp( cval + 4, "-TAB" ) ) ) {
+            comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
+            if( !comm ) {
+               sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
+               comm = combuf;
+            }
+            SetValue( this, FormatKey( "CTYPE", i + 1, -1, s, status ), &cval,
+                      AST__STRING, comm, status );
+         } else {
+            ok = 0;
+         }
+      }
+
+/* CDELT values */
+      if( axspec != -1 ) cdelt[ axspec ] *= specfactor;
+      for( i = 0; i < naxis; i++ ){
+         SetValue( this, FormatKey( "CDELT", i + 1, -1, s, status ), cdelt + i,
+                   AST__FLOAT, "Pixel size", status );
+      }
+
+/* CUNIT values. */
+      for( i = 0; i < naxis; i++ ) {
+         cval = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
+         if( cval ) {
+            if( i == axspec ) cval = specunit;
+            sprintf( combuf, "Units for axis %d", i + 1 );
+            SetValue( this, FormatKey( "CUNIT", i + 1, -1, s, status ), &cval, AST__STRING,
+                      combuf, status );
+         }
+      }
+
+/* CROTA */
+      if( axrot2 != -1 ){
+         SetValue( this, FormatKey( "CROTA", axrot2 + 1, -1, s, status ), &crota,
+                   AST__FLOAT, "Axis rotation", status );
+      } else if( ( axspec == -1 && naxis > 1 ) ||
+                  ( axspec != -1 && naxis > 2 ) )  {
+         SetValue( this, "CROTA1", &crota, AST__FLOAT, "Axis rotation", status );
+      }
+
+/* Reference equinox */
+      if( epoch != AST__BAD ) SetValue( this, "EPOCH", &epoch, AST__FLOAT,
+                                        "Epoch of reference equinox", status );
+
+/* Date of observation. */
+      val = GetItem( &(store->mjdobs), 0, 0, ' ', NULL, method, class, status );
+      if( val != AST__BAD ) {
+
+/* The format used for the DATE-OBS keyword depends on the value of the
+   keyword. For DATE-OBS < 1999.0, use the old "dd/mm/yy" format.
+   Otherwise, use the new "ccyy-mm-ddThh:mm:ss[.ssss]" format. */
+         palCaldj( 99, 1, 1, &mjd99, &jj );
+         if( val < mjd99 ) {
+            palDjcal( 0, val, iymdf, &jj );
+            sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
+                     iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
+         } else {
+            palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
+            palDd2tf( 3, fd, sign, ihmsf );
+            sprintf( combuf, "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d.%3.3d",
+                     iymdf[0], iymdf[1], iymdf[2], ihmsf[0], ihmsf[1],
+                     ihmsf[2], ihmsf[3] );
+         }
+
+/* Now store the formatted string in the FitsChan. */
+         cval = combuf;
+         SetValue( this, "DATE-OBS", (void *) &cval, AST__STRING,
+                   "Date of observation", status );
+      }
+
+/* Spectral stuff.. */
+      if( axspec >= 0 ) {
+
+/* Rest frequency */
+         val = GetItem( &(store->restfrq), 0, 0, s, NULL, method, class, status );
+         if( val != AST__BAD ) SetValue( this, FormatKey( "RESTFREQ", -1, -1, s, status ),
+                                         &val, AST__FLOAT, "[Hz] Rest frequency", status );
+      }
+   }
+
+/* Release CDELT workspace */
+   if( cdelt ) cdelt = (double *) astFree( (void *) cdelt );
+
+/* Return zero or ret depending on whether an error has occurred. */
+   return astOK ? ok : 0;
+}
+
+static int AIPSPPFromStore( AstFitsChan *this, FitsStore *store,
+                            const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     AIPSPPFromStore
+
+*  Purpose:
+*     Store WCS keywords in a FitsChan using FITS-AIPS++ encoding.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     int AIPSPPFromStore( AstFitsChan *this, FitsStore *store,
+*                        const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function copies the WCS information stored in the supplied
+*     FitsStore into the supplied FitsChan, using FITS-AIPS++ encoding.
+*
+*     AIPS++ encoding is like FITS-WCS encoding but with the following
+*     restrictions:
+*
+*     1) The celestial axes must be RA/DEC, galactic or ecliptic.
+*
+*     2) Only primary axis descriptions are written out.
+*
+*     3) RADESYS is not written and so the RADECSYS & EQUINOX values in the
+*        FitsStore must be consistent with the "1984" rule.
+*
+*     4) Any rotation produced by the PC matrix must be restricted to
+*        the celestial plane, and must involve no shear. A CROTA keyword
+*        with associated CDELT values are produced instead of the PC
+*        matrix.
+*
+*     5) ICRS is not supported.
+*
+*     6) Spectral axes can be created only for FITS-WCS CTYPE values of "FREQ"
+*        "VRAD" and "VOPT-F2W" and with standards of rest of LSRK, LSRD,
+*        BARYCENT and GEOCENTR.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        Pointer to the FitsStore.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if succesfull, and zero is returned
+*     otherwise.
+*/
+
+/* Local Variables: */
+   char *comm;         /* Pointer to comment string */
+   const char *cval;   /* Pointer to string keyword value */
+   const char *specunit;/* Pointer to corrected spectral units string */
+   char combuf[80];    /* Buffer for FITS card comment */
+   char lattype[MXCTYPELEN];/* Latitude axis CTYPE */
+   char lontype[MXCTYPELEN];/* Longitude axis CTYPE */
+   char s;             /* Co-ordinate version character */
+   char sign[2];       /* Fraction's sign character */
+   char spectype[MXCTYPELEN];/* Spectral axis CTYPE */
+   double *cdelt;      /* Pointer to CDELT array */
+   double cdl;         /* CDELT term */
+   double cdlat_lon;   /* Off-diagonal CD element */
+   double cdlon_lat;   /* Off-diagonal CD element */
+   double coscro;      /* Cos( CROTA ) */
+   double crota;       /* CROTA value to use */
+   double epoch;       /* Epoch of reference equinox */
+   double fd;          /* Fraction of a day */
+   double mjd99;       /* MJD at start of 1999 */
+   double rho_a;       /* First estimate of CROTA */
+   double rho_b;       /* Second estimate of CROTA */
+   double sincro;      /* Sin( CROTA ) */
+   double specfactor;  /* Factor for converting internal spectral units */
+   double val;         /* General purpose value */
+   int axlat;          /* Index of latitude FITS WCS axis */
+   int axlon;          /* Index of longitude FITS WCS axis */
+   int axrot1;         /* Index of first CROTA rotation axis */
+   int axrot2;         /* Index of second CROTA rotation axis */
+   int axspec;         /* Index of spectral FITS WCS axis */
+   int i;              /* Axis index */
+   int ihmsf[ 4 ];     /* Hour, minute, second, fractional second */
+   int iymdf[ 4 ];     /* Year, month, date, fractional day */
+   int j;              /* Axis index */
+   int jj;             /* SlaLib status */
+   int m;              /* Projection parameter index */
+   int maxm;           /* Max projection parameter index */
+   int naxis;          /* No. of axes */
+   int ok;             /* Is FitsSTore OK for IRAF encoding? */
+   int prj;            /* Projection type */
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* Initialise */
+   specunit = "";
+   specfactor = 1.0;
+   maxm = 0;
+
+/* First check that the values in the FitsStore conform to the
+   requirements of the AIPS++ encoding. Assume they do to begin with. */
+   ok = 1;
+
+/* Just do primary axes. */
+   s = ' ';
+
+/* Save the number of axes */
+   naxis = GetMaxJM( &(store->crpix), ' ', status ) + 1;
+
+/* Look for the primary celestial and spectral axes. */
+   FindLonLatSpecAxes( store, s, &axlon, &axlat, &axspec, method, class, status );
+
+/* If both longitude and latitude axes are present ...*/
+   if( axlon >= 0 && axlat >= 0 ) {
+
+/* Get the CTYPE values for both axes. Extract the projection type as
+   specified by the last 4 characters in the latitude CTYPE keyword value. */
+      cval = GetItemC( &(store->ctype), axlon, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else {
+         strcpy( lontype, cval );
+      }
+      cval = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+         prj = AST__WCSBAD;
+      } else {
+         strcpy( lattype, cval );
+         prj = astWcsPrjType( cval + 4 );
+      }
+
+/* FITS-AIPS++ cannot handle the AST-specific TPN projection. */
+      if( prj == AST__TPN || prj == AST__WCSBAD ) ok = 0;
+
+/* Projection parameters. FITS-AIPS++ encoding ignores projection parameters
+   associated with the longitude axis. The number of parameters is limited to
+   10. */
+      maxm = GetMaxJM( &(store->pv), ' ', status );
+      for( i = 0; i < naxis && ok; i++ ){
+         if( i != axlon ) {
+            for( m = 0; m <= maxm; m++ ){
+               val = GetItem( &(store->pv), i, m, s, NULL, method, class, status );
+               if( val != AST__BAD ) {
+                  if( i != axlat || m >= 10 ){
+                     ok = 0;
+                     break;
+                  }
+               }
+            }
+         }
+      }
+
+/* Identify the celestial coordinate system from the first 4 characters of the
+   longitude CTYPE value. Only RA, galactic longitude, and ecliptic
+   longitude can be stored using FITS-AIPS++. */
+      if( ok && strncmp( lontype, "RA--", 4 ) &&
+                strncmp( lontype, "GLON", 4 ) &&
+                strncmp( lontype, "ELON", 4 ) ) ok = 0;
+   }
+
+/* If a spectral axis is present ...*/
+   if( axspec >= 0 ) {
+
+/* Get the CTYPE values for the axis, and find the AIPS equivalent, if
+   possible. */
+      cval = GetItemC( &(store->ctype), axspec, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else {
+         if( !strncmp( cval, "FREQ", astChrLen( cval ) ) ) {
+            strcpy( spectype, "FREQ" );
+         } else if( !strncmp( cval, "VRAD", astChrLen( cval ) ) ) {
+            strcpy( spectype, "VELO" );
+         } else if( !strncmp( cval, "VOPT-F2W", astChrLen( cval ) ) ) {
+            strcpy( spectype, "FELO" );
+         } else {
+            ok = 0;
+         }
+      }
+
+/* If OK, check the SPECSYS value and add the AIPS equivalent onto the
+   end of the CTYPE value.*/
+      cval = GetItemC( &(store->specsys), 0, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else {
+         if( !strncmp( cval, "LSRK", astChrLen( cval ) ) ) {
+            strcpy( spectype+4, "-LSR" );
+         } else if( !strncmp( cval, "LSRD", astChrLen( cval ) ) ) {
+            strcpy( spectype+4, "-LSD" );
+         } else if( !strncmp( cval, "BARYCENT", astChrLen( cval ) ) ) {
+            strcpy( spectype+4, "-HEL" );
+         } else if( !strncmp( cval, "GEOCENTR", astChrLen( cval ) ) ) {
+            strcpy( spectype+4, "-GEO" );
+         } else {
+            ok = 0;
+         }
+      }
+
+/* If still OK, ensure the spectral axis units are Hz or m/s. */
+      cval = GetItemC( &(store->cunit), axspec, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else if( ok ) {
+         if( !strcmp( cval, "Hz" ) ) {
+            specunit = "HZ";
+            specfactor = 1.0;
+         } else if( !strcmp( cval, "kHz" ) ) {
+            specunit = "HZ";
+            specfactor = 1.0E3;
+         } else if( !strcmp( cval, "MHz" ) ) {
+            specunit = "HZ";
+            specfactor = 1.0E6;
+         } else if( !strcmp( cval, "GHz" ) ) {
+            specunit = "HZ";
+            specfactor = 1.0E9;
+         } else if( !strcmp( cval, "m/s" ) ) {
+            specunit = "m/s";
+            specfactor = 1.0;
+         } else if( !strcmp( cval, "km/s" ) ) {
+            specunit = "m/s";
+            specfactor = 1.0E3;
+         } else {
+            ok = 0;
+         }
+      }
+   }
+
+/* If this is different to the value of NAXIS abort since this encoding
+   does not support WCSAXES keyword. */
+   if( naxis != store->naxis ) ok = 0;
+
+/* Allocate memory to store the CDELT values */
+   if( ok ) {
+      cdelt = (double *) astMalloc( sizeof(double)*naxis );
+      if( !cdelt ) ok = 0;
+   } else {
+      cdelt = NULL;
+   }
+
+/* Check that rotation is restricted to the celestial plane, and extract
+   the CDELT (diagonal) terms, etc. If there are no celestial
+   axes, restrict rotation to the first two non-spectral axes. */
+   if( axlat < 0 && axlon < 0 ) {
+      if( axspec >= 0 && naxis > 2 ) {
+         axrot2 = ( axspec == 0 ) ? 1 : 0;
+         axrot1 = axrot2 + 1;
+         if( axrot1 == axspec ) axrot1++;
+      } else if( naxis > 1 ){
+         axrot2 = 0;
+         axrot1 = 1;
+      } else {
+         axrot2 = -1;
+         axrot1 = -1;
+      }
+   } else {
+      axrot1 = axlon;
+      axrot2 = axlat;
+   }
+   cdlat_lon = 0.0;
+   cdlon_lat = 0.0;
+   for( i = 0; i < naxis && ok; i++ ){
+      cdl = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
+      if( cdl == AST__BAD ) cdl = 1.0;
+      for( j = 0; j < naxis && ok; j++ ){
+          val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
+          if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
+          val *= cdl;
+          if( i == j ){
+             cdelt[ i ] = val;
+          } else if( i == axrot2 && j == axrot1 ){
+             cdlat_lon = val;
+          } else if( i == axrot1 && j == axrot2 ){
+             cdlon_lat = val;
+          } else if( val != 0.0 ){
+             ok = 0;
+          }
+      }
+   }
+
+/* Find the CROTA and CDELT values for the celestial axes. */
+   if( ok && axrot1 >= 0 && axrot2 >= 0 ) {
+      if( cdlat_lon > 0.0 ) {
+         rho_a = atan2( cdlat_lon, cdelt[ axrot1 ] );
+      } else if( cdlat_lon == 0.0 ) {
+         rho_a = 0.0;
+      } else {
+         rho_a = atan2( -cdlat_lon, -cdelt[ axrot1 ] );
+      }
+      if( cdlon_lat > 0.0 ) {
+         rho_b = atan2( cdlon_lat, -cdelt[ axrot2 ] );
+      } else if( cdlon_lat == 0.0 ) {
+         rho_b = 0.0;
+      } else {
+         rho_b = atan2( -cdlon_lat, cdelt[ axrot2 ] );
+      }
+      if( fabs( palDrange( rho_a - rho_b ) ) < 1.0E-2 ){
+         crota = 0.5*( palDranrm( rho_a ) + palDranrm( rho_b ) );
+         coscro = cos( crota );
+         sincro = sin( crota );
+         if( fabs( coscro ) > fabs( sincro ) ){
+            cdelt[ axrot2 ] /= coscro;
+            cdelt[ axrot1 ] /= coscro;
+         } else {
+            cdelt[ axrot2 ] = -cdlon_lat/sincro;
+            cdelt[ axrot1 ] = cdlat_lon/sincro;
+         }
+         crota *= AST__DR2D;
+
+/* Use AST__BAD to indicate that CDi_j values should be produced
+   instead of CROTA/CDELT. (I am told AIPS++ can understand CD matrices) */
+      } else {
+         crota = AST__BAD;
+      }
+   } else {
+      crota = 0.0;
+   }
+
+/* Get RADECSYS and the reference equinox. */
+   cval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
+   epoch = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );
+
+/* If RADECSYS was available... */
+   if( cval ){
+
+/* ICRS is not supported in this encoding. */
+      if( !strcmp( "ICRS", cval ) ) ok = 0;
+
+/* If epoch was not available, set a default epoch. */
+      if( epoch == AST__BAD ){
+         if( !strcmp( "FK4", cval ) ){
+            epoch = 1950.0;
+         } else if( !strcmp( "FK5", cval ) ){
+            epoch = 2000.0;
+         } else {
+            ok = 0;
+         }
+
+/* If an equinox was supplied, check it is consistent with the IAU 1984
+   rule. */
+      } else {
+         if( !strcmp( "FK4", cval ) ){
+            if( epoch >= 1984.0 ) ok = 0;
+         } else if( !strcmp( "FK5", cval ) ){
+            if( epoch < 1984.0 ) ok = 0;
+         } else {
+            ok = 0;
+         }
+      }
+   }
+
+/* Only create the keywords if the FitsStore conforms to the requirements
+   of the FITS-AIPS++ encoding. */
+   if( ok ) {
+
+/* Get and save CRPIX for all pixel axes. These are required, so break
+   if they are not available. */
+      for( j = 0; j < naxis && ok; j++ ){
+         val = GetItem( &(store->crpix), 0, j, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+         } else {
+            sprintf( combuf, "Reference pixel on axis %d", j + 1 );
+            SetValue( this, FormatKey( "CRPIX", j + 1, -1, s, status ), &val,
+                      AST__FLOAT, combuf, status );
+         }
+      }
+
+/* Get and save CRVAL for all intermediate axes. These are required, so
+   break if they are not available. */
+      for( i = 0; i < naxis && ok; i++ ){
+         val = GetItem( &(store->crval), i, 0, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+         } else {
+            if( i == axspec ) val *= specfactor;
+            sprintf( combuf, "Value at ref. pixel on axis %d", i + 1 );
+            SetValue( this, FormatKey( "CRVAL", i + 1, -1, s, status ), &val,
+                      AST__FLOAT, combuf, status );
+         }
+      }
+
+/* Get and save CTYPE for all intermediate axes. These are required, so
+   break if they are not available. Use the potentially modified versions
+   saved above for the celestial axes. */
+      for( i = 0; i < naxis && ok; i++ ){
+         if( i == axlat ) {
+            cval = lattype;
+         } else if( i == axlon ) {
+            cval = lontype;
+         } else if( i == axspec ) {
+            cval = spectype;
+         } else {
+            cval = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+         }
+         if( cval && ( strlen(cval) < 5 || strcmp( cval + 4, "-TAB" ) ) ) {
+            comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
+            if( !comm ) {
+               sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
+               comm = combuf;
+            }
+            SetValue( this, FormatKey( "CTYPE", i + 1, -1, s, status ), &cval,
+                      AST__STRING, comm, status );
+         } else {
+            ok = 0;
+         }
+      }
+
+/* CDELT values */
+      if( axspec != -1 ) cdelt[ axspec ] *= specfactor;
+      for( i = 0; i < naxis; i++ ){
+         SetValue( this, FormatKey( "CDELT", i + 1, -1, s, status ), cdelt + i,
+                   AST__FLOAT, "Pixel size", status );
+      }
+
+/* CUNIT values. [Spectral axis units should be upper-case] */
+      for( i = 0; i < naxis; i++ ) {
+         cval = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
+         if( cval ) {
+            if( i == axspec ) cval = specunit;
+            sprintf( combuf, "Units for axis %d", i + 1 );
+            SetValue( this, FormatKey( "CUNIT", i + 1, -1, s, status ), &cval, AST__STRING,
+                      combuf, status );
+         }
+      }
+
+/* CD matrix. Multiply the row of the PC matrix by the CDELT value. */
+      if( crota == AST__BAD ) {
+         for( i = 0; i < naxis; i++ ) {
+            cdl = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
+            if( cdl == AST__BAD ) cdl = 1.0;
+            for( j = 0; j < naxis; j++ ){
+               val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
+               if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
+               val *= cdl;
+               if( val != 0.0 ) {
+                   SetValue( this, FormatKey( "CD", i + 1, j + 1, s, status ), &val,
+                             AST__FLOAT, "Transformation matrix element", status );
+               }
+            }
+         }
+
+/* CROTA */
+      } else if( crota != 0.0 ) {
+         if( axrot2 != -1 ){
+            SetValue( this, FormatKey( "CROTA", axrot2 + 1, -1, s, status ), &crota,
+                      AST__FLOAT, "Axis rotation", status );
+         } else if( ( axspec == -1 && naxis > 1 ) ||
+                    ( axspec != -1 && naxis > 2 ) ) {
+            SetValue( this, "CROTA1", &crota, AST__FLOAT, "Axis rotation", status );
+         }
+      }
+
+/* Reference equinox */
+      if( epoch != AST__BAD ) SetValue( this, "EPOCH", &epoch, AST__FLOAT,
+                                        "Epoch of reference equinox", status );
+
+/* Latitude of native north pole. */
+      val = GetItem( &(store->latpole), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) SetValue( this, "LATPOLE", &val, AST__FLOAT,
+                                      "Latitude of native north pole", status );
+
+/* Longitude of native north pole. */
+      val = GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) SetValue( this, "LONPOLE", &val, AST__FLOAT,
+                                      "Longitude of native north pole", status );
+
+/* Date of observation. */
+      val = GetItem( &(store->mjdobs), 0, 0, ' ', NULL, method, class, status );
+      if( val != AST__BAD ) {
+
+/* The format used for the DATE-OBS keyword depends on the value of the
+   keyword. For DATE-OBS < 1999.0, use the old "dd/mm/yy" format.
+   Otherwise, use the new "ccyy-mm-ddThh:mm:ss[.ssss]" format. */
+         palCaldj( 99, 1, 1, &mjd99, &jj );
+         if( val < mjd99 ) {
+            palDjcal( 0, val, iymdf, &jj );
+            sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
+                     iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
+         } else {
+            palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
+            palDd2tf( 3, fd, sign, ihmsf );
+            sprintf( combuf, "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d.%3.3d",
+                     iymdf[0], iymdf[1], iymdf[2], ihmsf[0], ihmsf[1],
+                     ihmsf[2], ihmsf[3] );
+         }
+
+/* Now store the formatted string in the FitsChan. */
+         cval = combuf;
+         SetValue( this, "DATE-OBS", (void *) &cval, AST__STRING,
+                   "Date of observation", status );
+      }
+
+/* Projection parameters. */
+      if( axlat >= 0 && axlon >= 0 ) {
+         for( m = 0; m <= maxm; m++ ){
+            val = GetItem( &(store->pv), axlat, m, s, NULL, method, class, status );
+            if( val != AST__BAD ) SetValue( this, FormatKey( "PROJP", m, -1, ' ', status ),
+                                            &val, AST__FLOAT, "Projection parameter", status );
+         }
+      }
+
+/* Spectral stuff.. */
+      if( axspec >= 0 ) {
+
+/* Rest frequency */
+         val = GetItem( &(store->restfrq), 0, 0, s, NULL, method, class, status );
+         if( val != AST__BAD ) SetValue( this, FormatKey( "RESTFREQ", -1, -1, s, status ),
+                                         &val, AST__FLOAT, "[Hz] Rest frequency", status );
+      }
+   }
+
+/* Release CDELT workspace */
+   if( cdelt ) cdelt = (double *) astFree( (void *) cdelt );
+
+/* Return zero or ret depending on whether an error has occurred. */
+   return astOK ? ok : 0;
+}
+
+static char *CardComm( AstFitsChan *this, int *status ){
+
+/*
+*  Name:
+*     CardComm
+
+*  Purpose:
+*     Return the keyword comment from the current card.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     char *CardComm( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns a pointer to a string holding the keyword comment from the
+*     current card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the keyword comment, or NULL if the FitsChan is at
+*     end-of-file, or does not have a comment.
+
+*  Notes:
+*     -  The current card is not changed by this function.
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   char *ret;
+
+/* Check the supplied object. */
+   if( !this ) return NULL;
+
+/* If the current card is defined, store a pointer to its keyword comment. */
+   if( this->card ){
+      ret = ( (FitsCard *) this->card )->comment;
+
+/* Otherwise store a NULL pointer. */
+   } else {
+      ret =  NULL;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static void *CardData( AstFitsChan *this, size_t *size, int *status ){
+
+/*
+*  Name:
+*     CardData
+
+*  Purpose:
+*     Return a pointer to the keyword data value for the current card.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void *CardData( AstFitsChan *this, size_t *size, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns a pointer to keyword data value from the current card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     size
+*        A pointer to a location at which to return the number of bytes
+*        occupied by the data value. NULL can be supplied if this
+*        information is not required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the keyword data, or NULL if the FitsChan is at
+*     end-of-file, or if the keyword does not have any data.
+
+*  Notes:
+*     -  For text data, the returned value for "size" includes the
+*     terminating null character.
+*     -  The current card is not changed by this function.
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   void *ret;
+
+/* Check the supplied object. */
+   if( !this ) return NULL;
+
+/* If the current card is defined, store a pointer to its keyword data. */
+   if( this->card ){
+      ret = ( (FitsCard *) this->card )->data;
+      if( size ) *size = ( (FitsCard *) this->card )->size;
+
+/* Otherwise store a NULL pointer. */
+   } else {
+      ret =  NULL;
+      if( size ) *size = 0;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static int *CardFlags( AstFitsChan *this, int *status ){
+
+/*
+*  Name:
+*     CardFlags
+
+*  Purpose:
+*     Return a pointer to the flags mask for the current card.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int *CardFlags( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns a pointer to the flags mask for the current card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The pointer to the flags mask.
+
+*  Notes:
+*     -  The current card is not changed by this function.
+*     -  NULL is returned if the current card is not defined.
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   int *ret;
+
+/* Check the supplied object. */
+   if( !this ) return NULL;
+
+/* If the current card is defined, store its deletion flag. */
+   if( this->card ){
+      ret = &( ( (FitsCard *) this->card )->flags );
+
+/* Otherwise store zero. */
+   } else {
+      ret =  NULL;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static char *CardName( AstFitsChan *this, int *status ){
+/*
+*  Name:
+*     CardName
+
+*  Purpose:
+*     Return the keyword name from the current card.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     char *CardName( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns a pointer to a string holding the keyword name from the
+*     current card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the keyword name, or NULL if the FitsChan is at
+*     end-of-file.
+
+*  Notes:
+*     -  The current card is not changed by this function.
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   char *ret;
+
+/* Check the supplied object. */
+   if( !this ) return NULL;
+
+/* If the current card is defined, store a pointer to its keyword name. */
+   if( this->card ){
+      ret = ( (FitsCard *) this->card )->name;
+
+/* Otherwise store a NULL pointer. */
+   } else {
+      ret =  NULL;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static int CardType( AstFitsChan *this, int *status ){
+/*
+*  Name:
+*     CardType
+
+*  Purpose:
+*     Return the keyword type from the current card.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int CardType( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns the keyword type from the current card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The keyword type.
+
+*  Notes:
+*     -  The current card is not changed by this function.
+*     -  AST__NOTYPE is returned if the current card is not defined.
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   int ret;
+
+/* Check the supplied object. */
+   if( !this ) return AST__NOTYPE;
+
+/* If the current card is defined, store the keyword type. */
+   if( this->card ){
+      ret = ( (FitsCard *) this->card )->type;
+
+/* Otherwise store AST__NOTYPE. */
+   } else {
+      ret =  AST__NOTYPE;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static AstMapping *CelestialAxes( AstFitsChan *this, AstFrameSet *fs, double *dim,
+                                  int *wperm, char s, FitsStore *store, int *axis_done,
+                                  int isoff, const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     CelestialAxes
+
+*  Purpose:
+*     Add values to a FitsStore describing celestial axes in a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstMapping *CelestialAxes( AstFitsChan *this, AstFrameSet *fs, double *dim,
+*                                int *wperm, char s, FitsStore *store, int *axis_done,
+*                                int isoff, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The current Frame of the supplied FrameSet is searched for celestial
+*     axes. If any are found, FITS WCS keyword values describing the axis
+*     are added to the supplied FitsStore, if possible (the conventions
+*     of FITS-WCS paper II are used). Note, this function does not store
+*     values for keywords which define the transformation from pixel
+*     coords to Intermediate World Coords (CRPIX, PC and CDELT), but a
+*     Mapping is returned which embodies these values. This Mapping is
+*     from the current Frame in the FrameSet (WCS coords) to a Frame
+*     representing IWC. The IWC Frame has the same number of axes as the
+*     WCS Frame which may be greater than the number of base Frame (i.e.
+*     pixel) axes.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     fs
+*        Pointer to the FrameSet. The base Frame should represent FITS pixel
+*        coordinates, and the current Frame should represent FITS WCS
+*        coordinates. The number of base Frame axes should not exceed the
+*        number of current Frame axes.
+*     dim
+*        An array holding the image dimensions in pixels. AST__BAD can be
+*        supplied for any unknown dimensions.
+*     wperm
+*        Pointer to an array of integers with one element for each axis of
+*        the current Frame. Each element holds the zero-based
+*        index of the FITS-WCS axis (i.e. the value of "i" in the keyword
+*        names "CTYPEi", "CRVALi", etc) which describes the Frame axis.
+*     s
+*        The co-ordinate version character. A space means the primary
+*        axis descriptions. Otherwise the supplied character should be
+*        an upper case alphabetical character ('A' to 'Z').
+*     store
+*        The FitsStore in which to store the FITS WCS keyword values.
+*     axis_done
+*        An array of flags, one for each Frame axis, which indicate if a
+*        description of the corresponding axis has yet been stored in the
+*        FitsStore.
+*     isoff
+*        If greater than zero, the description to add to the FitsStore
+*        should describe offset coordinates. If less than zero, the
+*        description to add to the FitsStore should describe absolute
+*        coordinates but should include the SkyRefIs, SkyRef and SkyRefP
+*        attributes. If zero, ignore all offset coordinate info. The
+*        absolute value indicates the nature of the reference point:
+*        1 == "pole", 2 == "origin", otherwise "ignored".
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If celestial axes were found which can be described using the
+*     conventions of FITS-WCS paper II, then a Mapping from the current Frame
+*     of the supplied FrameSet, to the IWC Frame is returned. Otherwise,
+*     a UnitMap is returned. Note, the Mapping only defines the IWC
+*     transformation for celestial axes. Any non-celestial axes are passed
+*     unchanged by the returned Mapping.
+*/
+
+/* Local Variables: */
+   AstFitsTable *table;    /* Pointer to structure holding -TAB table info */
+   AstFrame *pframe;       /* Primary Frame containing current WCS axis*/
+   AstFrame *wcsfrm;       /* WCS Frame within FrameSet */
+   AstMapping *map1;       /* Pointer to pre-WcsMap Mapping */
+   AstMapping *map3;       /* Pointer to post-WcsMap Mapping */
+   AstMapping *map;        /* Pixel -> WCS mapping */
+   AstMapping *ret;        /* Returned Mapping */
+   AstMapping *tmap0;      /* A temporary Mapping */
+   AstMapping *tmap1;      /* A temporary Mapping */
+   AstMapping *tmap2;      /* A temporary Mapping */
+   AstMapping *tmap3;      /* A temporary Mapping */
+   AstMapping *tmap4;      /* A temporary Mapping */
+   AstSkyFrame *skyfrm;    /* The SkyFrame defining current WCS axis */
+   AstWcsMap *map2;        /* Pointer to WcsMap */
+   AstWcsMap *map2b;       /* Pointer to WcsMap with cleared lat/lonpole */
+   char *cval;             /* Pointer to keyword value */
+   char *temp;             /* Pointer to temporary string */
+   double *mat;            /* Pointer to matrix diagonal elements */
+   double *ppcfid;         /* Pointer to array holding PPC at fiducial point */
+   double con;             /* Constant value for unassigned axes */
+   double crval[ 2 ];      /* Psi coords of reference point */
+   double pv;              /* Projection parameter value */
+   double skyfid[ 2 ];     /* Sky coords of fiducial point */
+   double val;             /* Keyword value */
+   int *inperm;            /* Input axis permutation array */
+   int *outperm;           /* Output axis permutation array */
+   int *tperm;             /* Pointer to new FITS axis numbering array */
+   int axlat;              /* Index of latitude output from WcsMap */
+   int axlon;              /* Index of longitude output from WcsMap */
+   int extver;             /* Table version number for -TAB headers */
+   int fits_ilat;          /* FITS WCS axis index for latitude axis */
+   int fits_ilon;          /* FITS WCS axis index for longitude axis */
+   int i;                  /* Loop index */
+   int iax;                /* Axis index */
+   int icolindexlat;       /* Index of table column holding lat index vector */
+   int icolindexlon;       /* Index of table column holding lon index vector */
+   int icolmainlat;        /* Index of table column holding main lat coord array */
+   int icolmainlon;        /* Index of table column holding main lon coord array */
+   int interplat;          /* INterpolation method for latitude look-up tables */
+   int interplon;          /* INterpolation method for longitude look-up tables */
+   int ilat;               /* Index of latitude axis within total WCS Frame */
+   int ilon;               /* Index of longitude axis within total WCS Frame */
+   int j;                  /* Loop index */
+   int m;                  /* Projection parameter index */
+   int maxm;               /* Largest used "m" value */
+   int mlat;               /* Index of latitude axis in main lat coord array */
+   int mlon;               /* Index of longitude axis in main lon coord array */
+   int nwcs;               /* Number of WCS axes */
+   int nwcsmap;            /* Number of inputs/outputs for the WcsMap */
+   int paxis;              /* Axis index within primary Frame */
+   int skylataxis;         /* Index of latitude axis within SkyFrame */
+   int skylonaxis;         /* Index of longitude axis within SkyFrame */
+   int tpn;                /* Is the WCS projectiona TPN projection? */
+
+/* Initialise */
+   ret = NULL;
+
+/* Other initialisation to avoid compiler warnings. */
+   mlon = 0;
+   mlat = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Get a pointer to the WCS Frame. */
+   wcsfrm = astGetFrame( fs, AST__CURRENT );
+
+/* Store the number of WCS axes. */
+   nwcs = astGetNout( fs );
+
+/* Check each axis in the WCS Frame to see if it is a celestial axis. */
+   skyfrm = NULL;
+   map = NULL;
+   ilon = -1;
+   ilat = -1;
+   for( iax = 0; iax < nwcs; iax++ ) {
+
+/* Obtain a pointer to the primary Frame containing the current WCS axis. */
+      astPrimaryFrame( wcsfrm, iax, &pframe, &paxis );
+
+/* If the current axis belongs to a SkyFrame, we have found a celestial
+   axis. Keep a pointer to it, and note the indices of the celestial axes
+   within the complete WCS Frame. The MakeFitsFrameSet function will have
+   ensured that the WCS Frame only contains at most a single SkyFrame. */
+      if( IsASkyFrame( pframe ) ) {
+         if( !skyfrm ) skyfrm = astClone( pframe );
+         if( paxis == 0 ) {
+            ilon = iax;
+         } else {
+            ilat = iax;
+         }
+
+/* Indicate that this axis has been classified. */
+         axis_done[ iax ] = 1;
+      }
+
+/* Release resources. */
+      pframe = astAnnul( pframe );
+   }
+
+/* Only proceed if we found celestial axes. */
+   if( ilon != -1 && ilat != -1 ) {
+
+/* Note the FITS WCS axis indices for the longitude and latitude axes */
+      fits_ilon = wperm[ ilon ];
+      fits_ilat = wperm[ ilat ];
+
+/* Create an array to hold the Projection Plane Coords corresponding to the
+   CRVALi keywords. */
+      ppcfid = (double *) astMalloc( sizeof( double )*nwcs );
+
+/* Get the pixel->wcs Mapping. */
+      map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+
+/* Get the table version number to use if we end up using the -TAB
+   algorithm. This is the set value of the TabOK attribute (if positive). */
+      extver = astGetTabOK( this );
+
+/* Some of the required FITS Keyword values are defined by the WcsMap
+   contained within the Mapping. Split the mapping up into a list of serial
+   component mappings, and locate the first WcsMap in this list. The first
+   Mapping returned by this call is the result of compounding all the
+   Mappings up to (but not including) the WcsMap, the second returned Mapping
+   is the (inverted) WcsMap, and the third returned Mapping is anything
+   following the WcsMap. Only proceed if one and only one WcsMap is found. */
+      if( SplitMap( map, astGetInvert( map ), ilon, ilat, &map1, &map2, &map3,
+                    status ) ){
+
+/* Get the indices of the latitude and longitude axes within the SkyFrame
+   (not necessarily (1,0) because they may have been permuted). */
+         skylataxis = astGetLatAxis( skyfrm );
+         skylonaxis = astGetLonAxis( skyfrm );
+
+/* The reference point in the celestial coordinate system is found by
+   transforming the fiducial point in native spherical co-ordinates
+   into WCS coordinates using map3. */
+         if( GetFiducialWCS( map2, map3, ilon,  ilat, skyfid + skylonaxis,
+                         skyfid + skylataxis, status ) ){
+
+/* We also need to find the indices of the longitude and latitude outputs
+   from the WcsMap. These may not be the same as ilat and ilon because of
+   axis permutations in "map3". */
+            axlon = astGetWcsAxis( map2, 0 );
+            axlat = astGetWcsAxis( map2, 1 );
+
+/* Normalise the latitude and longitude values at the fiducial point. The
+   longitude and latitude values found above will be in radians, but after
+   normalization we convert them to degrees, as expected by other functions
+   which handle FitsStores. */
+            if( skyfid[ skylonaxis ] == AST__BAD ) skyfid[ skylonaxis ] = 0.0;
+            if( skyfid[ skylataxis ] == AST__BAD ) skyfid[ skylataxis ] = 0.0;
+            if( ZEROANG( skyfid[ 0 ] ) ) skyfid[ 0 ] = 0.0;
+            if( ZEROANG( skyfid[ 1 ] ) ) skyfid[ 1 ] = 0.0;
+            astNorm( skyfrm, skyfid );
+            SetItem( &(store->crval), fits_ilon, 0, s, AST__DR2D*skyfid[ skylonaxis ], status );
+            SetItem( &(store->crval), fits_ilat, 0, s, AST__DR2D*skyfid[ skylataxis ], status );
+
+/* Set a flag if we have a TPN projection. This is an AST-specific
+   projection which mimicks the old "TAN with correction terms" projection
+   which was removed from the final version of the FITS-WCS paper II. */
+            tpn = ( astGetWcsType( map2 ) == AST__TPN );
+
+/* Store the WCS projection parameters. Except for TPN projections, always
+   exclude parameters 3 and 4 on the longitude axis since these are
+   reserved to hold copies of LONPOLE and LATPOLE. */
+            for( m = 0; m < WCSLIB_MXPAR; m++ ){
+               if( astTestPV( map2, axlon, m ) ) {
+                  if( m < 3 || m > 4 || tpn ) {
+                     pv = astGetPV( map2, axlon, m );
+                     if( pv != AST__BAD ) SetItem( &(store->pv), fits_ilon, m,
+                                                   s, pv, status );
+                  }
+               }
+               if( astTestPV( map2, axlat, m ) ) {
+                  pv = astGetPV( map2, axlat, m );
+                  if( pv != AST__BAD ) SetItem( &(store->pv), fits_ilat, m,
+                                                s, pv, status );
+               }
+            }
+
+/* If PVi_0 (for the longitude axis) is non-zero, the Cartesian coordinates
+   used by the WcsMap (Projection Plane Coordinates, PPC) need to be shifted
+   to produce Intermediate World Coordinates (IWC). This shift results in
+   the pixel reference position specified by the CRPIXi values (and which
+   corresponds to the origin of IWC) mapping on to the fiducial position
+   specified by the CRVALi values. The required shifts are just the PPC
+   coordinates of the fiducial point. The AST-specific "TPN" projection uses
+   longitude projection parameters to define correction terms, and so cannot
+   use the above convention (which is part of FITS-WCS paper II). Therefore
+   TPN projections always use zero shift between PPC and IWC. */
+            for( iax = 0; iax < nwcs; iax++ ) ppcfid[ iax ] = 0.0;
+            if( !tpn && astGetPV( map2, axlon, 0 ) != 0.0 ) {
+               GetFiducialPPC( (AstWcsMap *) map2, ppcfid + ilon, ppcfid + ilat, status );
+               if( ppcfid[ ilon ] == AST__BAD ) ppcfid[ ilon ] = 0.0;
+               if( ppcfid[ ilat ] == AST__BAD ) ppcfid[ ilat ] = 0.0;
+               ppcfid[ ilon ] *= AST__DR2D;
+               ppcfid[ ilat ] *= AST__DR2D;
+            }
+
+/* Store the CTYPE, CNAME, EQUINOX, MJDOBS, and RADESYS values. */
+            SkySys( this, skyfrm, 1, astGetWcsType( map2 ), store, fits_ilon,
+                    fits_ilat, s, isoff, method, class, status );
+
+/* Store the LONPOLE and LATPOLE values in the FitsStore. */
+            SkyPole( map2, map3, ilon, ilat, wperm, s, store, method, class, status );
+
+/* The values of LONPOLE and LATPOLE stored above (in the FitsStore) will be
+   ignored by WcsNative if the WcsMap contains set values for projection
+   parameters PVi_3a and/or PVi_4a (these will be used in preference to
+   the values in the FitsStore). To avoid this happening we take a copy
+   of the WcsMap and clear the relevant parameters (but not if the WcsMap is
+   for a TPN projection because TPN uses PVi_3a and PVi_4a for other
+   purposes). */
+            if( astGetWcsType( map2 ) != AST__TPN ) {
+               map2b = astCopy( map2 );
+               astClearPV( map2b, axlon, 3 );
+               astClearPV( map2b, axlon, 4 );
+            } else {
+               map2b = astClone( map2 );
+            }
+
+/* We will now create the Mapping from WCS coords to IWC coords. In fact,
+   we produce the Mapping from IWC to WCS and then invert it. Create the
+   first component of this Mapping which implements any shift of origin
+   from IWC to PPC. */
+            tmap0 = (AstMapping *) astShiftMap( nwcs, ppcfid, "", status );
+
+/* The next component of this Mapping scales the PPC coords from degrees
+   to radians on the celestial axes. */
+            mat = astMalloc( sizeof( double )*(size_t) nwcs );
+            if( astOK ) {
+               for( iax = 0; iax < nwcs; iax++ ) mat[ iax ] = 1.0;
+               mat[ ilon ] = AST__DD2R;
+               mat[ ilat ] = AST__DD2R;
+               tmap1 = (AstMapping *) astMatrixMap( nwcs, nwcs, 1, mat, "", status );
+               mat = astFree( mat );
+            } else {
+               tmap1 = NULL;
+            }
+
+/* Now create the Mapping from Native Spherical Coords to WCS. */
+            tmap2 = WcsNative( NULL, store, s, map2b, fits_ilon, fits_ilat,
+                               method, class, status );
+
+/* Combine the WcsMap with the above Mapping, to get the Mapping from PPC
+   to WCS. */
+            tmap3 = (AstMapping *) astCmpMap( map2b, tmap2, 1, "", status );
+            tmap2 = astAnnul( tmap2 );
+
+/* If there are more WCS axes than IWC axes, create a UnitMap for the extra
+   WCS axes and add it in parallel with tmap3. */
+            nwcsmap = astGetNin( map3 );
+            if( nwcsmap < nwcs ) {
+               tmap2 = (AstMapping *) astUnitMap( nwcs - nwcsmap, "", status );
+               tmap4 = (AstMapping *) astCmpMap( tmap3, tmap2, 0, "", status );
+               tmap3 = astAnnul( tmap3 );
+               tmap2 = astAnnul( tmap2 );
+               tmap3 = tmap4;
+               nwcsmap = nwcs;
+            }
+
+/* The pixel->wcs mapping may include a PermMap which selects some sub-set
+   or super-set of the orignal WCS axes. In this case the number of inputs
+   and outputs for "tmap3" created above may not equal "nwcs". To avoid this,
+   we embed "tmap3" between 2 PermMaps which select the required axes. */
+            if( nwcsmap != nwcs || ilon != axlon || ilat != axlat ) {
+               inperm = astMalloc( sizeof( int )*(size_t) nwcs );
+               outperm = astMalloc( sizeof( int )*(size_t) nwcsmap );
+               if( astOK ) {
+
+/* Indicate that no inputs of the PermMap have yet been assigned to any
+   outputs */
+                  for( i = 0; i < nwcs; i++ ) inperm[ i ] = -1;
+
+/* Assign the WcsMap long/lat axes to the WCS Frame long/lat axes */
+                  inperm[ ilon ] = axlon;
+                  inperm[ ilat ] = axlat;
+
+/* Assign the remaining inputs arbitrarily (doesn't matter how we do this
+   since the WcsMap is effectively a UnitMap on all non-celestial axes). */
+                  iax = 0;
+                  for( i = 0; i < nwcs; i++ ) {
+                     while( iax == axlon || iax == axlat ) iax++;
+                     if( inperm[ i ] == -1 ) inperm[ i ] = iax++;
+                  }
+
+/* Do the same for the outputs. */
+                  for( i = 0; i < nwcsmap; i++ ) outperm[ i ] = -1;
+                  outperm[ axlon ] = ilon;
+                  outperm[ axlat ] = ilat;
+                  iax = 0;
+                  for( i = 0; i < nwcsmap; i++ ) {
+                     while( iax == ilon || iax == ilat ) iax++;
+                     if( outperm[ i ] == -1 ) outperm[ i ] = iax++;
+                  }
+
+/* Create the PermMap. */
+                  con = AST__BAD;
+                  tmap2 = (AstMapping *) astPermMap( nwcs, inperm, nwcsmap,
+                                                     outperm, &con, "", status );
+
+/* Sandwich the WcsMap between the PermMap and its inverse. */
+                  tmap4 = (AstMapping *) astCmpMap( tmap2, tmap3, 1, "", status );
+                  tmap3 = astAnnul( tmap3 );
+                  astInvert( tmap2 );
+                  tmap3 = (AstMapping *) astCmpMap( tmap4, tmap2, 1, "", status );
+                  tmap2 = astAnnul( tmap2 );
+                  tmap4 = astAnnul( tmap4 );
+               }
+               inperm = astFree( inperm );
+               outperm = astFree( outperm );
+            }
+
+/* Combine these Mappings together. */
+            tmap4 = (AstMapping *) astCmpMap( tmap0, tmap1, 1, "", status );
+            tmap0 = astAnnul( tmap0 );
+            tmap1 = astAnnul( tmap1 );
+            ret = (AstMapping *) astCmpMap( tmap4, tmap3, 1, "", status );
+            tmap3 = astAnnul( tmap3 );
+            tmap4 = astAnnul( tmap4 );
+
+/* Invert this Mapping to get the Mapping from WCS to IWC. */
+            astInvert( ret );
+
+/* The spherical rotation involved in converting WCS to IWC can result in
+   inappropriate numbering of the FITS axes. For instance, a LONPOLE
+   value of 90 degrees causes the IWC axes to be transposed. For this
+   reason we re-asses the FITS axis numbers assigned to the celestial
+   axes in order to make the IWC axes as close as possible to the pixel
+   axes with the same number (but only if the axis order is being
+   determined automatically). To do this, we need the Mapping from
+   pixel to IWC, which is formed by concatenating the pixel->WCS
+   Mapping with the WCS->IWC Mapping. */
+            if( astChrMatch( astGetFitsAxisOrder( this ), "<auto>" ) ) {
+               tmap0 = (AstMapping *) astCmpMap( map, ret, 1, "", status );
+
+/* Find the outputs of this Mapping which should be associated with each
+   input. */
+               tperm = astMalloc( sizeof(int)*(size_t) nwcs );
+               if( ! WorldAxes( this, tmap0, dim, tperm, status ) ) {
+                  ret = astAnnul( ret );
+               }
+
+/* If the index associated with the celestial axes appear to have been
+   swapped... */
+               if( ret && astOK && fits_ilon == tperm[ ilat ] &&
+                            fits_ilat == tperm[ ilon ] ) {
+
+/* Swap the fits axis indices associated with each WCS axis to match. */
+                  wperm[ ilon ] = fits_ilat;
+                  wperm[ ilat ] = fits_ilon;
+
+/* Swap the stored CRVAL value for the longitude and latitude axis. */
+                  val = GetItem( &(store->crval), fits_ilat, 0, s, NULL, method, class, status );
+                  SetItem( &(store->crval), fits_ilat, 0, s,
+                           GetItem( &(store->crval), fits_ilon, 0, s, NULL,
+                           method, class, status ), status );
+                  SetItem( &(store->crval), fits_ilon, 0, s, val, status );
+
+/* Swap the stored CTYPE value for the longitude and latitude axis. */
+                  cval = GetItemC( &(store->ctype), fits_ilat, 0, s, NULL, method, class, status );
+                  if( cval ) {
+                     temp = astStore( NULL, (void *) cval, strlen( cval ) + 1 );
+                     cval = GetItemC( &(store->ctype), fits_ilon, 0, s, NULL, method, class, status );
+                     if( cval ) {
+                        SetItemC( &(store->ctype), fits_ilat, 0, s, cval, status );
+                        SetItemC( &(store->ctype), fits_ilon, 0, s, temp, status );
+                     }
+                     temp = astFree( temp );
+                  }
+
+/* Swap the stored CNAME value for the longitude and latitude axis. */
+                  cval = GetItemC( &(store->cname), fits_ilat, 0, s, NULL, method, class, status );
+                  if( cval ) {
+                     temp = astStore( NULL, (void *) cval, strlen( cval ) + 1 );
+                     cval = GetItemC( &(store->cname), fits_ilon, 0, s, NULL, method, class, status );
+                     if( cval ) {
+                        SetItemC( &(store->cname), fits_ilat, 0, s, cval, status );
+                        SetItemC( &(store->cname), fits_ilon, 0, s, temp, status );
+                     }
+                     temp = astFree( temp );
+                  }
+
+/* Swap the projection parameters asociated with the longitude and latitude
+   axes. */
+                  maxm = GetMaxJM( &(store->pv), s, status );
+                  for( m = 0; m <= maxm; m++ ){
+                     val = GetItem( &(store->pv), fits_ilat, m, s, NULL, method, class, status );
+                     SetItem( &(store->pv), fits_ilat, m, s,
+                              GetItem( &(store->pv), fits_ilon, m, s, NULL,
+                              method, class, status ), status );
+                     SetItem( &(store->pv), fits_ilon, m, s, val, status );
+                  }
+               }
+
+/* Release resources. */
+               tperm = astFree( tperm );
+               tmap0 = astAnnul( tmap0 );
+            }
+            map2b = astAnnul( map2b );
+         }
+
+/* Release resources. */
+         map1 = astAnnul( map1 );
+         map2 = astAnnul( map2 );
+         map3 = astAnnul( map3 );
+
+/* If no WcsMap was found in the pixel->WCS Mapping, it may be possible
+   to describe the celestial axes using a tabular look-up table (i.e. the
+   FITS-WCS "_TAB" algorithm). Only do this if the -TAB algorithm is to
+   be supported. */
+      } else if( extver > 0 ) {
+
+/* Get any pre-existing FitsTable from the FitsStore. This is the table
+   in which the tabular data will be stored (if the Mapping can be expressed
+   in -TAB form). */
+         if( !astMapGet0A( store->tables, AST_TABEXTNAME, &table ) ) table = NULL;
+
+/* See if the transformations for the celestial axes can be expressed in -TAB
+   form. The returned Mapping (if any) is the Mapping from (lon,lat)
+   (rads) to (psi_lon,psi_lat) (pixels). See FITS-WCS paper III section 6.1.2
+   for definition of psi. Scale the values stored in the table from radians
+   to degrees. */
+         tmap0 = IsMapTab2D( map, AST__DR2D, "deg", wcsfrm, dim, ilon, ilat,
+                             fits_ilon, fits_ilat, &table, &icolmainlon,
+                             &icolmainlat, &icolindexlon, &icolindexlat,
+                             &mlon, &mlat, &interplon, &interplat, status );
+         if( tmap0 ) {
+
+/* Store the CTYPE, CNAME, EQUINOX, MJDOBS, and RADESYS values. */
+            SkySys( this, skyfrm, 0, 0, store, fits_ilon, fits_ilat, s, isoff,
+                    method, class, status );
+
+/* If possible, choose the two CRVAL values (which are values on the psi
+   axes) so that transforming them using the Mapping returned by
+   IsMapTab2D gives the sky reference position stored in the SkyFrame.
+   Check the SkyFrame has a defined reference position. */
+            if( astTestSkyRef( skyfrm, 0 ) && astTestSkyRef( skyfrm, 1 ) ){
+
+/* Get the longitude and latitude at the reference point in radians. */
+               skyfid[ 0 ] = astGetSkyRef( skyfrm, astGetLonAxis( skyfrm ));
+               skyfid[ 1 ] = astGetSkyRef( skyfrm, astGetLatAxis( skyfrm ));
+
+/* We use the WCS->psi Mapping to convert the reference point WCS coords
+   (rads) into psi coords (pixels). We can only do this if the WCS->psi
+   Mapping has a defined forward transformation. */
+               if( astGetTranForward( tmap0 ) ) {
+                  astTran2( tmap0, 1, skyfid, skyfid + 1, 1, crval,
+                            crval + 1 );
+
+/* If the WCS->psi mapping has an undefined forward transformation, then
+   just store the sky reference point coords (in degs) in keywords
+   AXREFn, and use 1.0 for the CRVAL values, so that IWC becomes equal
+   to (psi-1) i.e. (grid coords - 1). This means the reference point is
+   at grid coords (1.0,1.0). Note this choice of 1.0 for CRVAL is not
+   arbitrary since it is required by the trick used to create invertable CD
+   matrix in function MakeInvertable. */
+               } else {
+                  SetItem( &(store->axref), fits_ilon, 0, s,
+                           AST__DR2D*skyfid[ 0 ], status );
+                  SetItem( &(store->axref), fits_ilat, 0, s,
+                           AST__DR2D*skyfid[ 1 ], status );
+                  crval[ 0 ] = 1.0;
+                  crval[ 1 ] = 1.0;
+               }
+
+/* If the SkyFrame has no reference position, use 1.0 for the CRVAL values. */
+            } else {
+               crval[ 0 ] = 1.0;
+               crval[ 1 ] = 1.0;
+            }
+
+/* Create a Mapping that describes the transformation from the lon and lat
+   psi axes to the lon and lat IWC axes (i.e. a ShiftMap that just subtracts
+   the CRVAL values from each axis). */
+            crval[ 0 ] = -crval[ 0 ];
+            crval[ 1 ] = -crval[ 1 ];
+            tmap1 = (AstMapping *) astShiftMap( 2, crval, " ", status );
+            crval[ 0 ] = -crval[ 0 ];
+            crval[ 1 ] = -crval[ 1 ];
+
+/* Create a series compound Mapping that applies the Mapping returned
+   by IsMapTab2D first (the Mapping from WCS to psi), followed by the
+   Mapping from psi to IWC created above. There-after, use this compound
+   Mapping in place of the Mapping returned by IsMapTab2D. It maps WCS to
+   IWC. */
+            tmap2 = (AstMapping *) astCmpMap( tmap0, tmap1, 1, " ", status );
+            (void) astAnnul( tmap0 );
+            tmap1 = astAnnul( tmap1 );
+            tmap0 = tmap2;
+
+/* Store the CRVAL values */
+            SetItem( &(store->crval), fits_ilon, 0, s, crval[ 0 ], status );
+            SetItem( &(store->crval), fits_ilat, 0, s, crval[ 1 ], status );
+
+/* Store TAB-specific values in the FitsStore. First the name of the
+   FITS binary table extension holding the coordinate info. */
+            SetItemC( &(store->ps), fits_ilon, 0, s, AST_TABEXTNAME, status );
+            SetItemC( &(store->ps), fits_ilat, 0, s, AST_TABEXTNAME, status );
+
+/* Next the table version number. This is the set (positive) value for the
+   TabOK attribute. */
+            SetItem( &(store->pv), fits_ilon, 1, s, extver, status );
+            SetItem( &(store->pv), fits_ilat, 1, s, extver, status );
+
+/* Also store the table version in the binary table header. */
+            astSetFitsI( table->header, "EXTVER", extver, "Table version number",
+                         0 );
+
+/* Next the name of the table column containing the main coords array. */
+            SetItemC( &(store->ps), fits_ilon, 1, s,
+                      astColumnName( table, icolmainlon ), status );
+            SetItemC( &(store->ps), fits_ilat, 1, s,
+                      astColumnName( table, icolmainlat ), status );
+
+/* Next the name of the column containing the index array. */
+            if( icolindexlon >= 0 ) SetItemC( &(store->ps), fits_ilon, 2, s,
+                             astColumnName( table, icolindexlon ), status );
+            if( icolindexlat >= 0 ) SetItemC( &(store->ps), fits_ilat, 2, s,
+                             astColumnName( table, icolindexlat ), status );
+
+/* The one-based index of the axes within the coordinate array that
+   describes FITS WCS axes "fits_ilon" and "fits_ilat". */
+            SetItem( &(store->pv), fits_ilon, 3, s, mlon, status );
+            SetItem( &(store->pv), fits_ilat, 3, s, mlat, status );
+
+/* The interpolation method (an AST extension to the published -TAB
+   algorithm, communicated through the QVi_4a keyword). */
+            SetItem( &(store->pv), fits_ilon, 4, s, interplon, status );
+            SetItem( &(store->pv), fits_ilat, 4, s, interplat, status );
+
+/* Also store the FitsTable itself in the FitsStore. */
+            astMapPut0A( store->tables, AST_TABEXTNAME, table, NULL );
+
+/* Allocate space for the arrays that define the permutations required
+   for the inputs and outputs of a PermMap. */
+            inperm = astMalloc( sizeof( double )*nwcs );
+            outperm = astMalloc( sizeof( double )*nwcs );
+            if( astOK ) {
+
+/* Create the WCS -> IWC Mapping. First create a parallel CmpMap that
+   combines the Mapping returned by IsMapTab2D (which transforms the celestial
+   axes), with a UnitMap which transforms the non-celestial axes. */
+               if( nwcs > 2 ) {
+                  tmap1 = (AstMapping *) astUnitMap( nwcs - 2, " ", status );
+                  tmap2 = (AstMapping *) astCmpMap( tmap0, tmap1, 0, " ", status );
+                  tmap1 = astAnnul( tmap1 );
+               } else {
+                  tmap2 = astClone( tmap0 );
+               }
+
+/* Now create a PermMap that permutes the inputs of this CmpMap into the
+   order of the axes in the WCS Frame. */
+               outperm[ 0 ] = ilon;
+               outperm[ 1 ] = ilat;
+               j = 0;
+               for( i = 2; i < nwcs; i++ ) {
+                  while( j == ilon || j == ilat ) j++;
+                  outperm[ i ] = j++;
+               }
+               for( i = 0; i < nwcs; i++ ) inperm[ outperm[ i ] ] = i;
+               tmap1 = (AstMapping *) astPermMap( nwcs, inperm, nwcs, outperm,
+                                                  NULL, " ", status );
+
+/* Use this PermMap (and its inverse) to permute the inputs (and outputs)
+   of the parallel CmpMap created above. */
+               tmap3 = (AstMapping *) astCmpMap( tmap1, tmap2, 1, " ", status );
+               tmap2 = astAnnul( tmap2 );
+               astInvert( tmap1 );
+               tmap2 = (AstMapping *) astCmpMap( tmap3, tmap1, 1, " ", status );
+               tmap1 = astAnnul( tmap1 );
+               tmap3 = astAnnul( tmap3 );
+
+/* Now create a PermMap that permutes the WCS axes into the FITS axis order. */
+               for( i = 0; i < nwcs; i++ ) {
+                  inperm[ i ] = wperm[ i ];
+                  outperm[ wperm[ i ] ] = i;
+               }
+               tmap1 = (AstMapping *) astPermMap( nwcs, inperm, nwcs, outperm,
+                                                  NULL, "", status );
+
+/* Use this PermMap to permute the outputs of the "tmap2" Mapping. The
+   resulting Mapping is the Mapping from the current Frame to IWC and is
+   the Mapping to be returned as the function value. */
+               ret = (AstMapping *) astCmpMap( tmap2, tmap1, 1, " ", status );
+               tmap1 = astAnnul( tmap1 );
+               tmap2 = astAnnul( tmap2 );
+            }
+
+/* Free remaining resources. */
+            inperm = astFree( inperm );
+            outperm = astFree( outperm );
+            tmap0 = astAnnul( tmap0 );
+         }
+         if( table ) table = astAnnul( table );
+      }
+
+/* Release resources. */
+      ppcfid = astFree( ppcfid );
+   }
+
+/* Release resources. */
+   wcsfrm = astAnnul( wcsfrm );
+   if( skyfrm ) skyfrm = astAnnul( skyfrm );
+   if( map ) map = astAnnul( map );
+
+/* If we have a Mapping to return, simplify it. Otherwise, create
+   a UnitMap to return. */
+   if( ret ) {
+      tmap0 = ret;
+      ret = astSimplify( tmap0 );
+      tmap0 =  astAnnul( tmap0 );
+   } else {
+      ret = (AstMapping *) astUnitMap( nwcs, "", status );
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static void ChangePermSplit( AstMapping *map, int *status ){
+/*
+*  Name:
+*     ChangePermSplit
+
+*  Purpose:
+*     Change all PermMaps in a Mapping to use the alternate
+*     implementation of the astMapSplit method.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void ChangePermSplit( AstMapping *map, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The PemMap class provides two implementations of the astMapSplit
+*     method. The implementation used by each PermMap is determined by
+*     the value of the PermMap's "PermSplit" attribute. This function
+*     searches the supplied Mapping for any PermMaps, and set their
+*     PermSplit attribute to 1, indicating that the alternate
+*     implementation of astMapSplit should be used.
+
+*  Parameters:
+*     map
+*        Pointer to the Mapping. Modified on exit by setting all
+*        PermSplit attributes to 1.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstMapping *map1;
+   AstMapping *map2;
+   int series;
+   int invert1;
+   int invert2;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* If the supplied Mapping is a PermMap, set its PermSplit attribute
+   non-zero. */
+   if( astIsAPermMap( map ) ) {
+      astSetPermSplit( map, 1 );
+
+/* If the supplied Mapping is not a PermMap, attempt to decompose the
+   Mapping into two component Mappings. */
+   } else {
+      astDecompose( map, &map1, &map2, &series, &invert1, &invert2 );
+
+/* If the Mapping could be decomposed, use this function recursively to
+   set the PermSplit attributes in each component Mapping. */
+      if( map1 && map2 ) {
+         ChangePermSplit( map1, status );
+         ChangePermSplit( map2, status );
+
+/* Annul the component Mappings. */
+         map1 = astAnnul( map1 );
+         map2 = astAnnul( map2 );
+      } else if( map1 ) {
+         map1 = astAnnul( map1 );
+      } else if( map2 ) {
+         map2 = astAnnul( map2 );
+      }
+   }
+}
+
+static double *Cheb2Poly( double *c, int nx, int ny, double xmin, double xmax,
+                          double ymin, double ymax, int *status ){
+/*
+*  Name:
+*     Cheb2Poly
+
+*  Purpose:
+*     Converts a two-dimensional Chebyshev polynomial to standard form and
+*     scale the arguments.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     double *Cheb2Poly( double *c, int nx, int ny, double xmin, double xmax,
+*                        double ymin, double ymax, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     Given the coefficients of a two-dimensional Chebychev polynomial P(u,v),
+*     find the coefficients of the equivalent standard two-dimensional
+*     polynomial Q(x,y). The allowed range of u and v is assumed to be the
+*     unit square, and this maps on to the rectangle in (x,y) given by
+*     (xmin:xmax,ymin:ymax).
+
+*  Parameters:
+*     c
+*        An array of (nx,ny) elements supplied holding the coefficients of
+*        P, such that the coefficient of (Ti(u)*Tj(v)) is held in element
+*        (i + j*nx), where "Ti(u)" is the Chebychev polynomial (of the
+*        first kind) of order "i" evaluated at "u", and "Tj(v)" is the
+*        Chebychev polynomial of order "j" evaluated at "v".
+*     nx
+*        One more than the maximum power of u within P.
+*     ny
+*        One more than the maximum power of v within P.
+*     xmin
+*        X value corresponding to u = -1
+*     xmax
+*        X value corresponding to u = +1
+*     ymin
+*        Y value corresponding to v = -1
+*     ymax
+*        Y value corresponding to v = +1
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a dynamically allocated array of (nx,ny) elements holding
+*     the coefficients of Q, such that the coefficient of (x^i*y^j) is held
+*     in element (i + j*nx). Free it using astFree when no longer needed.
+*/
+
+/* Local Variables: */
+   double *d;
+   double *pa;
+   double *pw;
+   double *work1;
+   double *work2;
+   double *work3;
+   int *iw1;
+   int *iw2;
+   int i;
+   int j;
+
+/* Check the status and supplied value pointer. */
+   if( !astOK ) return NULL;
+
+/* Allocate returned array. */
+   d = astMalloc( sizeof( *d )*nx*ny );
+
+/* Allocate workspace. */
+   work1 = astMalloc( sizeof( *work1 )*ny );
+   work2 = astMalloc( sizeof( *work2 )*ny );
+   work3 = astMalloc( sizeof( *work2 )*nx );
+   iw1 = astMalloc( sizeof(int)*( nx > ny ? nx : ny ) );
+   iw2 = astMalloc( sizeof(int)*( nx > ny ? nx : ny ) );
+   if( astOK ) {
+
+/* Thinking of P as a 1D polynomial in v, each coefficient would itself then
+   be a 1D polynomial in u:
+
+   P = (   c[0] +      c[1]*T1(u) +      c[2]*T2(u) + ... ) +
+       (  c[nx] +   c[nx+1]*T1(u) +   c[nx+2]*T2(u) + ... )*T1(v) +
+       (c[2*nx] + c[2*nx+1]*T1(u) + c[2*nx+2]*T2(u) + ... )*T2(v) +
+       ...
+       (c[(ny-1)*nx] + c[(ny-1)*nx+1]*T1(u) + c[(ny-1)*nx+2]*T2(u) + ... )T{ny-1}(v)
+
+   Use Chpc1 to convert these "polynomial coefficients" to standard
+   form, storing the result in the corresponding row of "d" . Also,
+   convert them from u to x. */
+
+      for( j = 0; j < ny; j++ ) {
+         Chpc1( c + j*nx, work3, nx, iw1, iw2, status );
+         Shpc1( xmin, xmax, nx, work3, d + j*nx, status );
+      }
+
+/* The polynomial value is now:
+
+    (   d[0] +      d[1]*x +      d[2]*x*x + ... ) +
+    (  d[nx] +   d[nx+1]*x +   d[nx+2]*x*x + ... )*T1(v) +
+    (d[2*nx] + d[2*nx+1]*x + d[2*nx+2]*x*x + ... )*T2(v) +
+    ...
+    (d[(ny-1)*nx] + d[(ny-1)*nx+1]*x + d[(ny-1)*nx+2]*x*x + ... )*T{ny-1}(v)
+
+   If we rearrange this expression to view it as a 1D polynomial in x,
+   rather than v, each coefficient of the new 1D polynomial is then
+   itself a polynomial in v:
+
+    ( d[0] +   d[nx]*T1(v) +   d[2*nx]*T2(v) + ... d[(ny-1)*nx]*T{ny-1}(v) ) +
+    ( d[1] + d[nx+1]*T1(v) + d[2*nx+1]*T2(v) + ... d[(ny-1)*nx+1]T{ny-1}(v)... )*x +
+    ( d[2] + d[nx+2]*T1(v) + d[2*nx+2]*T2(v) + ... d[(ny-1)*nx+2]T{ny-1}(v)... )*x*x +
+    ...
+    ( d[nx-1] + d[2*nx-1]*T1(v) + d[3*nx-1]*T2(v) + ... d[ny*nx-1]*T{ny-1}(v) )*x*x*...
+
+
+   Now use Chpc1 to convert each of these "polynomial coefficients"
+   to standard form. We copy each column of the d array into a 1D work array,
+   use Shpc1 to modify the values in the work array, and then write
+   the modified values back into the current column of d. Also convert
+   from v to y. */
+
+      for( i = 0; i < nx; i++ ) {
+         pa = d + i;
+         pw = work1;
+         for( j = 0; j < ny; j++ ) {
+            *(pw++) = *pa;
+            pa += nx;
+         }
+
+         Chpc1( work1, work2, ny, iw1, iw2, status );
+         Shpc1( ymin, ymax, ny, work2, work1, status );
+
+         pa = d + i;
+         pw = work1;
+         for( j = 0; j < ny; j++ ) {
+            *pa = *(pw++);
+            pa += nx;
+         }
+      }
+
+/* So the polynomial is now:
+
+    ( d[0] +   d[nx]*y +   d[2*nx]*y*y + ... d[(ny-1)*nx]*y*y*... ) +
+    ( d[1] + d[nx+1]*y + d[2*nx+1]*y*y + ... d[(ny-1)*nx+1]*y*y*... )*x +
+    ( d[2] + d[nx+2]*y + d[2*nx+2]*y*y + ... d[(ny-1)*nx+2]*y*y*... )*x*x +
+    ...
+    ( d[nx-1] + d[2*nx-1]*y + d[3*nx-1]*y*y + ... d[ny*nx-1]*y*y*... )*x*x*...
+
+  Re-arranging, this is:
+
+    (   d[0] +      d[1]*x +      d[2]*x*x + ... ) +
+    (  d[nx] +   d[nx+1]*x +   d[nx+2]*x*x + ... )*y +
+    (d[2*nx] + d[2*nx+1]*x + d[2*nx+2]*x*x + ... )*y*y +
+    ...
+    (d[(ny-1)*nx] + d[(ny-1)*nx+1]*x + d[(ny-1)*nx+2]*x*x + ... )*y*y*...
+
+   as required. */
+
+   }
+
+/* Free the workspace. */
+   work1 = astFree( work1 );
+   work2 = astFree( work2 );
+   work3 = astFree( work3 );
+   iw1 = astFree( iw1 );
+   iw2 = astFree( iw2 );
+
+/* Return the result. */
+   return d;
+}
+
+static int CheckFitsName( AstFitsChan *this, const char *name,
+                          const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     CheckFitsName
+
+*  Purpose:
+*     Check a keyword name conforms to FITS standards.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int CheckFitsName( AstFitsChan *this, const char *name, const char *method,
+*                        const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     FITS keywords must contain between 1 and 8 characters, and each
+*     character must be an upper-case Latin alphabetic character, a digit,
+*     an underscore, or a hyphen. Leading, trailing or embedded white space
+*     is not allowed, with the exception of totally blank or null keyword
+*     names.
+*
+*     If the supplied keyword name is invalid, either a warning is issued
+*     (for violations that can be handled - such as illegal characters in
+*     keywords), or an error is reported (for more major violations such
+*     as the keyname containing an equals sign).
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        Pointer to a string holding the name to check.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 0 is returned if the supplied name was blank. A value of 1
+*     is returned otherwise.
+
+*  Notes:
+*     -  An error is reported if the supplied keyword name does not
+*     conform to FITS requirements, and zero is returned.
+*/
+
+/* Local Variables: */
+   char buf[100];     /* Buffer for warning text */
+   const char *c;     /* Pointer to next character in name */
+   size_t n;          /* No. of characters in supplied name */
+   int ret;           /* Returned value */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Initialise the returned value to indicate that the supplied name was
+   blank. */
+   ret = 0;
+
+/* Check that the supplied pointer is not NULL. */
+   if( name ){
+
+/* Get the number of characters in the name. */
+      n = strlen( name );
+
+/* Report an error if the name has too many characters in it. */
+      if( n > FITSNAMLEN ){
+         astError( AST__BDFTS, "%s(%s): The supplied FITS keyword name ('%s') "
+                   "has %d characters. FITS only allows up to %d.", status, method,
+                   class, name, (int) n, FITSNAMLEN );
+
+/* If the name has no characters in it, then assume it is a legal blank
+   keyword name. Otherwise, check that no illegal characters occur in the
+   name. */
+      } else if( n != 0 ) {
+
+/* Whitespace is only allowed in the special case of a name consisting
+   entirely of whitespace. Such keywords are used to indicate that the rest
+   of the card is a comment. Find the first non-whitespace character in the
+   name. */
+         c = name;
+         while( isspace( ( int ) *(c++) ) );
+
+/* If the name is filled entirely with whitespace, then the name is acceptable
+   as the special case. Otherwise, we need to do more checks. */
+         if( c - name - 1 < n ){
+
+/* Indicate that the supplied name is not blank. */
+            ret = 1;
+
+/* Loop round every character checking that it is one of the legal characters.
+   Report an error if any illegal characters are found. */
+            c = name;
+            while( *c ){
+               if( !isFits( (int) *c ) ){
+                  if( *c == '=' ){
+                     astError( AST__BDFTS, "%s(%s): An equals sign ('=') was found "
+                               "before column %d within a FITS keyword name or header "
+                               "card.", status, method, class, FITSNAMLEN + 1 );
+
+                  } else if( *c < ' ' ) {
+                     sprintf( buf, "The FITS keyword name ('%s') contains an "
+                              "illegal non-printing character (ascii value "
+                              "%d).", name, *c );
+                     Warn( this, "badkeyname", buf, method, class, status );
+
+
+                  } else if( *c > ' ' ) {
+                     sprintf( buf, "The FITS keyword name ('%s') contains an "
+                              "illegal character ('%c').", name, *c );
+                     Warn( this, "badkeyname", buf, method, class, status );
+                  }
+                  break;
+               }
+               c++;
+            }
+         }
+      }
+
+/* Report an error if no pointer was supplied. */
+   } else if( astOK ){
+      astError( AST__INTER, "CheckFitsName(%s): AST internal error; a NULL "
+                "pointer was supplied for the keyword name. ", status,
+                astGetClass( this ) );
+   }
+
+/* If an error has occurred, return 0. */
+   if( !astOK ) ret = 0;
+
+/* Return the answer. */
+   return ret;
+}
+
+static void CheckZero( char *text, double value, int width, int *status ){
+/*
+*  Name:
+*     CheckZero
+
+*  Purpose:
+*     Ensure that the formatted value zero has no minus sign.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void CheckZero( char *text, double value, int width, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     There is sometimes a problem (perhaps only on DEC UNIX) when formatting
+*     the floating-point value 0.0 using C. Sometimes it gives the string
+*     "-0". This function fixed this by checking the first character of
+*     the supplied string (if the supplied value is zero), and shunting the
+*     remaining text one character to the right if it is a minus sign. It
+*     returns without action if the supplied value is not zero.
+*
+*     In addition, this function also rounds out long sequences of
+*     adjacent zeros or nines in the number.
+
+*  Parameters:
+*     text
+*        The formatted value.
+*     value
+*        The floating value which was formatted.
+*     width
+*        The minimum field width to use. The value is right justified in
+*        this field width. Ignored if zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   char *c;
+
+/* Return if no text was supplied. */
+   if( !text ) return;
+
+/* If the numerical value is zero, check for the leading minus sign. */
+   if( value == 0.0 ) {
+
+/* Find the first non-space character. */
+      c = text;
+      while( *c && isspace( (int) *c ) ) c++;
+
+/* If the first non-space character is a minus sign, replace it with a
+      space. */
+      if( *c == '-' ) *c = ' ';
+
+/* Otherwise, round out sequences of zeros or nines. */
+   } else {
+      RoundFString( text, width, status );
+   }
+}
+
+static double ChooseEpoch( AstFitsChan *this, FitsStore *store, char s,
+                           const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     ChooseEpoch
+
+*  Purpose:
+*     Choose a FITS keyword value to use for the AST Epoch attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     double ChooseEpoch( AstFitsChan *this, FitsStore *store, char s,
+*                         const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function returns an MJD value in the TDB timescale, which can
+*     be used as the Epoch value in an AST Frame. It uses the following
+*     preference order: secondary MJD-AVG, primary MJD-AVG, secondary MJD-OBS,
+*     primary MJD-OBS. Note, DATE-OBS keywords are converted into MJD-OBS
+*     keywords by the SpecTrans function before this function is called.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        A structure containing values for FITS keywords relating to
+*        the World Coordinate System.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     method
+*        The calling method. Used only in error messages.
+*     class
+*        The object class. Used only in error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The MJD value.
+
+*  Notes:
+*     -  A value of AST__BAD is returned if an error occurs, or if none
+*     of the required keywords can be found in the FitsChan.
+*/
+
+/* Local Variables: */
+   const char *timesys;  /* The TIMESYS value in the FitsStore */
+   double mjd;           /* The returned MJD */
+
+/* Initialise the returned value. */
+   mjd = AST__BAD;
+
+/* Check the global status. */
+   if( !astOK ) return mjd;
+
+/* Otherwise, try to get the secondary MJD-AVG value. */
+   mjd = GetItem( &(store->mjdavg), 0, 0, s, NULL, method, class, status );
+
+/* Otherwise, try to get the primary MJD-AVG value. */
+   if( mjd == AST__BAD ) mjd = GetItem( &(store->mjdavg), 0, 0, ' ', NULL,
+                                        method, class, status );
+
+/* If the secondary MJD-OBS keyword is present in the FitsChan, gets its
+   value. */
+   if( mjd == AST__BAD ) mjd = GetItem( &(store->mjdobs), 0, 0, s, NULL,
+                                        method, class, status );
+
+/* Otherwise, try to get the primary MJD-OBS value. */
+   if( mjd == AST__BAD ) mjd = GetItem( &(store->mjdobs), 0, 0, ' ', NULL,
+                                        method, class, status );
+
+/* Now convert the MJD value to the TDB timescale. */
+   timesys = GetItemC( &(store->timesys), 0, 0, ' ', NULL, method, class, status );
+   mjd = TDBConv( mjd, TimeSysToAst( this, timesys, method, class, status ),
+                  0, method, class, status );
+
+/* Return the answer. */
+   return mjd;
+}
+
+static void Chpc1( double *c, double *d, int n, int *w0, int *w1, int *status ){
+/*
+*  Name:
+*     Chpc1
+
+*  Purpose:
+*     Converts a one-dimensional Chebyshev polynomial to standard form.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void Chpc1( double *c, double *d, int n, int *w0, int *w1, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     Given the coefficients of a one-dimensional Chebychev polynomial P(u),
+*     find the coefficients of the equivalent standard 1D polynomial Q(u).
+*     The allowed range of u is assumed to be the unit interval.
+
+*  Parameters:
+*     c
+*        An array of n elements supplied holding the coefficients of
+*        P, such that the coefficient of (Ti(u)) is held in element
+*        (i), where "Ti(u)" is the Chebychev polynomial (of the
+*        first kind) of order "i" evaluated at "u".
+*     d
+*        An array of n elements returned holding the coefficients of
+*        Q, such that the coefficient of (u^i) is held in element (i).
+*     n
+*        One more than the highest power of u in P.
+*     w0
+*        Pointer to a work array of n elements.
+*     w1
+*        Pointer to a work array of n elements.
+*     status
+*        Inherited status value
+
+*  Notes:
+*    - Vaguely inspired by the Numerical Recipes routine "chebpc". But the
+*    original had bugs, so I wrote this new version from first principles.
+
+*/
+
+/* Local Variables: */
+   int sv;
+   int j;
+   int k;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Initialise the returned coefficients array. */
+   for( j = 0; j < n; j++ ) d[ j ] = 0.0;
+
+/* Use the recurrence relation
+
+   T{k+1}(x) = 2.x.T{k}(x) - T{k-1}(x).
+
+   w0[i] holds the coefficient of x^i in T{k-1}. w1[i] holds the
+   coefficient of x^i in T{k}. Initialise them for T0 (="1") and
+   T1 (="x"). */
+   for( j = 0; j < n; j++ ) w0[ j ] = w1[ j ] = 0;
+   w0[ 0 ] = 1;
+   w1[ 1 ] = 1;
+
+/* Update the returned coefficients array to include the T0 and T1 terms. */
+   d[ 0 ] = c[ 0 ];
+   d[ 1 ] = c[ 1 ];
+
+/* Loop round using the above recurrence relation until we have found
+   T{n-1}. */
+   for( k = 1; k < n - 1; k++ ){
+
+/* To get the coefficients of T{k+1} shift the contents of w1 up one
+   element, introducing a zero at the low end, and then double all the
+   values in w1. Finally subtract off the values in w0. This implements
+   the above recurrence relationship. Starting at the top end and working
+   down to the bottom, store a new value for each element of w1. */
+      for( j = n - 1; j > 0; j-- ) {
+
+/* First save the original element of w1 in w0 for use next time. But we
+   also need the original w0 element later on so save it first. */
+         sv = w0[ j ];
+         w0[ j ] = w1[ j ];
+
+/* Double the lower neighbouring w1 element and subtract off the w0
+   element saved above. This forms the new value for w1. */
+         w1[ j ] = 2*w1[ j - 1 ] - sv;
+      }
+
+/* Introduce a zero into the lowest element of w1, saving the original
+   value first in w0. Then subtract off the original value of w0. */
+      sv = w0[ 0 ];
+      w0[ 0 ] = w1[ 0 ];
+      w1[ 0 ] = -sv;
+
+/* W1 now contains the coefficients of T{k+1} in w1, and the coefficients
+   of T{k} in w0. Multiply these by the supplied coefficient for T{k+1},
+   and add them into the returned array. */
+      for( j = 0; j <= k + 1; j++ ){
+         d[ j ] += c[ k + 1 ]*w1[ j ];
+      }
+   }
+}
+
+static int ChrLen( const char *string, int *status ){
+/*
+*  Name:
+*     ChrLen
+
+*  Purpose:
+*     Return the length of a string excluding any trailing white space.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int ChrLen( const char *string, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function returns the length of a string excluding any trailing
+*     white space, or non-printable characters.
+
+*  Parameters:
+*     string
+*        Pointer to the string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The length of a string excluding any trailing white space and
+*     non-printable characters.
+
+*  Notes:
+*     -  A value of zero is returned if a NULL pointer is supplied, or if an
+*     error has already occurred.
+*/
+
+/* Local Variables: */
+   const char *c;      /* Pointer to the next character to check */
+   int ret;            /* The returned string length */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Initialise the returned string length. */
+   ret = 0;
+
+/* Check a string has been supplied. */
+   if( string ){
+
+/* Check each character in turn, starting with the last one. */
+      ret = strlen( string );
+      c = string + ret - 1;
+      while( ret ){
+         if( isprint( (int) *c ) && !isspace( (int) *c ) ) break;
+         c--;
+         ret--;
+      }
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static int CLASSFromStore( AstFitsChan *this, FitsStore *store,
+                           AstFrameSet *fs, double *dim, const char *method,
+                           const char *class, int *status ){
+
+/*
+*  Name:
+*     CLASSFromStore
+
+*  Purpose:
+*     Store WCS keywords in a FitsChan using FITS-CLASS encoding.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     int CLASSFromStore( AstFitsChan *this, FitsStore *store,
+*                         AstFrameSet *fs, double *dim, const char *method,
+*                         const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function copies the WCS information stored in the supplied
+*     FitsStore into the supplied FitsChan, using FITS-CLASS encoding.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        Pointer to the FitsStore.
+*     fs
+*        Pointer to the FrameSet from which the values in the FitsStore
+*        were derived.
+*     dim
+*        Pointer to an array holding the main array dimensions (AST__BAD
+*        if a dimension is not known).
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if succesfull, and zero is returned
+*     otherwise.
+*/
+
+/* Local Variables: */
+   AstFrame *azelfrm;  /* (az,el) frame */
+   AstFrame *curfrm;   /* Current Frame in supplied FrameSet */
+   AstFrame *freqfrm;  /* Frame for reference frequency value */
+   AstFrame *radecfrm; /* Spatial frame for CRVAL values */
+   AstFrame *velofrm;  /* Frame for reference velocity value */
+   AstFrameSet *fsconv1;/* FrameSet connecting "curfrm" & "radecfrm" */
+   AstFrameSet *fsconv2;/* FrameSet connecting "curfrm" & "azelfrm" */
+   AstMapping *map1;   /* Axis permutation to get (lonaxis,lataxis) = (0,1) */
+   AstMapping *map2;   /* Mapping from FITS CTYPE to (az,el) */
+   AstMapping *map3;   /* Mapping from (lon,lat) to (az,el) */
+   char *comm;         /* Pointer to comment string */
+   char *cval;         /* Pointer to string keyword value */
+   char attbuf[20];    /* Buffer for AST attribute name */
+   char combuf[80];    /* Buffer for FITS card comment */
+   char lattype[MXCTYPELEN];/* Latitude axis CTYPE */
+   char lontype[MXCTYPELEN];/* Longitude axis CTYPE */
+   char s;             /* Co-ordinate version character */
+   char sign[2];       /* Fraction's sign character */
+   char spectype[MXCTYPELEN];/* Spectral axis CTYPE */
+   double *cdelt;      /* Pointer to CDELT array */
+   double aval[ 2 ];   /* General purpose array */
+   double azel[ 2 ];   /* Reference (az,el) values */
+   double cdl;         /* CDELT term */
+   double crval[ 3 ];  /* CRVAL values converted to rads, etc */
+   double delta;       /* Spectral axis increment */
+   double equ;         /* Epoch of reference equinox */
+   double fd;          /* Fraction of a day */
+   double latval;      /* CRVAL for latitude axis */
+   double lonpole;     /* LONPOLE value */
+   double lonval;      /* CRVAL for longitude axis */
+   double mjd99;       /* MJD at start of 1999 */
+   double p1, p2;      /* Projection parameters */
+   double radec[ 2 ];  /* Reference (lon,lat) values */
+   double rf;          /* Rest freq (Hz) */
+   double specfactor;  /* Factor for converting internal spectral units */
+   double val;         /* General purpose value */
+   double xin[ 3 ];    /* Grid coords at centre of first pixel */
+   double xout[ 3 ];   /* WCS coords at centre of first pixel */
+   int axlat;          /* Index of latitude FITS WCS axis */
+   int axlon;          /* Index of longitude FITS WCS axis */
+   int axspec;         /* Index of spectral FITS WCS axis */
+   int i;              /* Axis index */
+   int ihmsf[ 4 ];     /* Hour, minute, second, fractional second */
+   int iymdf[ 4 ];     /* Year, month, date, fractional day */
+   int j;              /* Axis index */
+   int jj;             /* SlaLib status */
+   int naxis2;         /* Length of pixel axis 2 */
+   int naxis3;         /* Length of pixel axis 3 */
+   int naxis;          /* No. of axes */
+   int ok;             /* Is FitsSTore OK for IRAF encoding? */
+   int prj;            /* Projection type */
+
+/* Other initialisation to avoid compiler warnings. */
+   lonval = 0.0;
+   latval = 0.0;
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* Initialise */
+   specfactor = 1.0;
+
+/* First check that the values in the FitsStore conform to the
+   requirements of the CLASS encoding. Assume they do not to begin with. */
+   ok = 0;
+
+/* Just do primary axes. */
+   s = ' ';
+
+/* Look for the primary celestial axes. */
+   FindLonLatSpecAxes( store, s, &axlon, &axlat, &axspec, method, class, status );
+
+/* Get the current Frame from the supplied FrameSet. */
+   curfrm = astGetFrame( fs, AST__CURRENT );
+
+/* Spectral and celestial axes must be present in axes 1,2 and 3. */
+   if( axspec >= 0 && axspec < 3 &&
+       axlon >= 0 && axlon < 3 &&
+       axlat >= 0 && axlat < 3 ) {
+      ok = 1;
+
+/* If the spatial pixel axes are degenerate (i.e. span only a single
+   pixel), modify the CRPIX and CRVAL values in the FitsStore to put
+   the reference point at the centre of the one and only spatial pixel. */
+      if( store->naxis >= 3 && dim[ axlon ] == 1.0 && dim[ axlat ] == 1.0 ){
+         xin[ 0 ] = 1.0;
+         xin[ 1 ] = 1.0;
+         xin[ 2 ] = 1.0;
+         astTranN( fs, 1, 3, 1, xin, 1, 3, 1, xout );
+         if( xout[ axlon ] != AST__BAD && xout[ axlat ] != AST__BAD ) {
+
+/* The indices of the spatial axes in the FITS header may not be the same
+   as the indices of the spatial axes in the WCS Frame of the supplied
+   FrameSet. So search the current Frame for longitude and latitude axes,
+   and store the corresponding elements of the "xout" array for later use. */
+            for( i = 0; i < 3; i++ ) {
+               sprintf( attbuf, "IsLonAxis(%d)", i + 1 );
+               if( astHasAttribute( curfrm, attbuf ) ) {
+                  if( astGetI( curfrm, attbuf ) ) {
+                     lonval = xout[ i ];
+                  } else {
+                     latval = xout[ i ];
+                  }
+               }
+            }
+
+/* Store them in the FitsStore. */
+            SetItem( &(store->crval), axlon, 0, ' ', lonval*AST__DR2D, status );
+            SetItem( &(store->crval), axlat, 0, ' ', latval*AST__DR2D, status );
+            SetItem( &(store->crpix), 0, axlon, ' ', 1.0, status );
+            SetItem( &(store->crpix), 0, axlat, ' ', 1.0, status );
+         }
+      }
+
+/* Get the CRVAL values for both spatial axes. */
+      latval = GetItem( &( store->crval ), axlat, 0, s, NULL, method, class, status );
+      if( latval == AST__BAD ) ok = 0;
+      lonval = GetItem( &( store->crval ), axlon, 0, s, NULL, method, class, status );
+      if( lonval == AST__BAD ) ok = 0;
+
+/* Get the CTYPE values for both axes. Extract the projection type as
+   specified by the last 4 characters in the latitude CTYPE keyword value. */
+      cval = GetItemC( &(store->ctype), axlon, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else {
+         strcpy( lontype, cval );
+      }
+      cval = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+         prj = AST__WCSBAD;
+      } else {
+         strcpy( lattype, cval );
+         prj = astWcsPrjType( cval + 4 );
+      }
+
+/* Check the projection type is OK. */
+      if( prj == AST__WCSBAD ){
+         ok = 0;
+      } else if( prj != AST__SIN ){
+
+/* Check the projection code is OK. */
+         ok = 0;
+         if( prj == AST__TAN ||
+             prj == AST__ARC ||
+             prj == AST__STG ||
+             prj == AST__AIT ||
+             prj == AST__SFL ) {
+            ok = 1;
+
+/* For AIT, and SFL, check that the reference point is the origin of
+   the celestial co-ordinate system. */
+            if( prj == AST__AIT ||
+                prj == AST__SFL ) {
+               if( latval != 0.0 || lonval != 0.0 ){
+                  ok = 0;
+
+/* Change the new SFL projection code to to the older equivalent GLS */
+               } else if( prj == AST__SFL ){
+                  (void) strcpy( lontype + 4, "-GLS" );
+                  (void) strcpy( lattype + 4, "-GLS" );
+
+/* Change the new AIT projection code to to the older equivalent ATF */
+               } else if( prj == AST__AIT ){
+                  (void) strcpy( lontype + 4, "-ATF" );
+                  (void) strcpy( lattype + 4, "-ATF" );
+               }
+            }
+         }
+
+/* SIN projections are only acceptable if the associated projection
+   parameters are both zero. */
+      } else {
+         p1 = GetItem( &( store->pv ), axlat, 1, s, NULL, method, class, status );
+         p2 = GetItem( &( store->pv ), axlat, 2, s, NULL, method, class, status );
+         if( p1 == AST__BAD ) p1 = 0.0;
+         if( p2 == AST__BAD ) p2 = 0.0;
+         ok = ( p1 == 0.0 && p2 == 0.0 );
+      }
+
+/* Identify the celestial coordinate system from the first 4 characters of the
+   longitude CTYPE value. Only RA and galactic longitude can be stored using
+   FITS-CLASS. */
+      if( ok && strncmp( lontype, "RA--", 4 ) &&
+               strncmp( lontype, "GLON", 4 ) ) ok = 0;
+
+/* Get the CTYPE values for the spectral axis, and find the CLASS equivalent,
+   if possible. */
+      cval = GetItemC( &(store->ctype), axspec, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else {
+         if( !strncmp( cval, "FREQ", astChrLen( cval ) ) ) {
+            strcpy( spectype, "FREQ" );
+         } else {
+            ok = 0;
+         }
+      }
+
+/* If OK, check the SPECSYS value is SOURCE. */
+      cval = GetItemC( &(store->specsys), 0, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else if( ok ) {
+         if( strncmp( cval, "SOURCE", astChrLen( cval ) ) ) ok = 0;
+      }
+
+/* If still OK, ensure the spectral axis units are Hz. */
+      cval = GetItemC( &(store->cunit), axspec, 0, s, NULL, method, class, status );
+      if( !cval ) {
+         ok = 0;
+      } else if( ok ) {
+         if( !strcmp( cval, "Hz" ) ) {
+            specfactor = 1.0;
+         } else if( !strcmp( cval, "kHz" ) ) {
+            specfactor = 1.0E3;
+         } else if( !strcmp( cval, "MHz" ) ) {
+            specfactor = 1.0E6;
+         } else if( !strcmp( cval, "GHz" ) ) {
+            specfactor = 1.0E9;
+         } else {
+            ok = 0;
+         }
+      }
+   }
+
+/* Save the number of WCS axes */
+   naxis = GetMaxJM( &(store->crpix), ' ', status ) + 1;
+
+/* If this is larger than 3, ignore the surplus WCS axes. Note, the
+   above code has checked that the spatial and spectral axes are
+   WCS axes 0, 1 and 2. */
+   if( naxis > 3 ) naxis = 3;
+
+/* Allocate memory to store the CDELT values */
+   if( ok ) {
+      cdelt = (double *) astMalloc( sizeof(double)*naxis );
+      if( !cdelt ) ok = 0;
+   } else {
+      cdelt = NULL;
+   }
+
+/* Check that there is no rotation, and extract the CDELT (diagonal) terms,
+   etc. If the spatial axes are degenerate (i.e. cover only a single pixel)
+   then ignore any rotation. */
+   if( !GetValue( this, FormatKey( "NAXIS", axlon + 1, -1, s, status ), AST__INT,
+                  &naxis2, 0, 0, method, class, status ) ) {
+      naxis2 = 0;
+   }
+   if( !GetValue( this, FormatKey( "NAXIS", axlat + 1, -1, s, status ), AST__INT,
+                  &naxis3, 0, 0, method, class, status ) ) {
+      naxis3 = 0;
+   }
+   for( i = 0; i < naxis && ok; i++ ){
+      cdl = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
+      if( cdl == AST__BAD ) cdl = 1.0;
+      for( j = 0; j < naxis && ok; j++ ){
+          val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
+          if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
+          val *= cdl;
+          if( i == j ){
+             cdelt[ i ] = val;
+          } else if( val != 0.0 ){
+             if( naxis2 != 1 || naxis3 != 1 ) ok = 0;
+          }
+      }
+   }
+
+/* Get RADECSYS and the reference equinox. */
+   cval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
+   equ = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );
+
+/* If RADECSYS was available... */
+   if( cval ){
+
+/* Only FK4 and FK5 are supported in this encoding. */
+      if( strcmp( "FK4", cval ) && strcmp( "FK5", cval ) ) ok = 0;
+
+/* If epoch was not available, set a default epoch. */
+      if( equ == AST__BAD ){
+         if( !strcmp( "FK4", cval ) ){
+            equ = 1950.0;
+         } else if( !strcmp( "FK5", cval ) ){
+            equ = 2000.0;
+         } else {
+            ok = 0;
+         }
+
+/* If an epoch was supplied, check it is consistent with the IAU 1984
+   rule. */
+      } else {
+         if( !strcmp( "FK4", cval ) ){
+            if( equ >= 1984.0 ) ok = 0;
+         } else if( !strcmp( "FK5", cval ) ){
+            if( equ < 1984.0 ) ok = 0;
+         } else {
+            ok = 0;
+         }
+      }
+
+/* Check we have a rest frequency */
+      rf = GetItem( &(store->restfrq), 0, 0, s, NULL, method, class, status );
+      if( rf == AST__BAD ) ok = 0;
+   }
+
+/* If the spatial Frame covers more than a single Frame and requires a LONPOLE
+   or LATPOLE keyword, it cannot be encoded using FITS-CLASS. However since
+   FITS-CLASS imposes a no rotation restriction, it can tolerate lonpole
+   values of +/- 180 degrees. */
+   if( ok && ( naxis2 != 1 || naxis3 != 1 ) ) {
+      lonpole =  GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status );
+      if( lonpole != AST__BAD && lonpole != -180.0 && lonpole == 180 ) ok = 0;
+      if( GetItem( &(store->latpole), 0, 0, s, NULL, method, class, status )
+          != AST__BAD ) ok = 0;
+   }
+
+/* Only create the keywords if the FitsStore conforms to the requirements
+   of the FITS-CLASS encoding. */
+   if( ok ) {
+
+/* If celestial axes were added by MakeFitsFrameSet, we need to ensure
+   the header contains 3 main array axes. This is because the CLASS
+   encoding does not support the WCSAXES keyword. */
+      if( store->naxis == 1 ) {
+
+/* Update the "NAXIS" value to 3 or put a new card in at the start. */
+         astClearCard( this );
+         i = 3;
+         SetValue( this, "NAXIS", &i, AST__INT, NULL, status );
+
+/* Put NAXIS2/3 after NAXIS1, or after NAXIS if the FitsChan does not contain
+   NAXIS1. These are set to 1 since the spatial axes are degenerate. */
+         if( FindKeyCard( this, "NAXIS1",  method, class, status ) ) {
+            MoveCard( this, 1, method, class, status );
+         }
+         i = 1;
+         SetValue( this, "NAXIS2", &i, AST__INT, NULL, status );
+         SetValue( this, "NAXIS3", &i, AST__INT, NULL, status );
+      }
+
+/* Find the last WCS related card. */
+      FindWcs( this, 1, 1, 0, method, class, status );
+
+/* Get and save CRPIX for all pixel axes. These are required, so break
+   if they are not available. */
+      for( j = 0; j < naxis && ok; j++ ){
+         val = GetItem( &(store->crpix), 0, j, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+         } else {
+            sprintf( combuf, "Reference pixel on axis %d", j + 1 );
+            SetValue( this, FormatKey( "CRPIX", j + 1, -1, s, status ), &val,
+                      AST__FLOAT, combuf, status );
+         }
+      }
+
+/* Get and save CRVAL for all intermediate axes. These are required, so
+   break if they are not available. Note, the frequency axis CRVAL is
+   redefined by FITS-CLASS by reducing it by the RESTFREQ value. */
+      for( i = 0; i < naxis && ok; i++ ){
+         val = GetItem( &(store->crval), i, 0, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+         } else {
+            crval[ i ] = val;
+            if( i == axspec ) {
+               val *= specfactor;
+               val -= rf;
+            }
+            sprintf( combuf, "Value at ref. pixel on axis %d", i + 1 );
+            SetValue( this, FormatKey( "CRVAL", i + 1, -1, s, status ), &val,
+                      AST__FLOAT, combuf, status );
+         }
+      }
+
+/* Get and save CTYPE for all intermediate axes. These are required, so
+   break if they are not available. Use the potentially modified versions
+   saved above for the celestial axes. */
+      for( i = 0; i < naxis && ok; i++ ){
+         if( i == axlat ) {
+            cval = lattype;
+         } else if( i == axlon ) {
+            cval = lontype;
+         } else if( i == axspec ) {
+            cval = spectype;
+         } else {
+            cval = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+         }
+         if( cval && ( strlen(cval) < 5 || strcmp( cval + 4, "-TAB" ) ) ) {
+            comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
+            if( !comm ) {
+               sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
+               comm = combuf;
+            }
+            SetValue( this, FormatKey( "CTYPE", i + 1, -1, s, status ), &cval,
+                      AST__STRING, comm, status );
+         } else {
+            ok = 0;
+         }
+      }
+
+/* CDELT values */
+      if( axspec != -1 ) cdelt[ axspec ] *= specfactor;
+      for( i = 0; i < naxis; i++ ){
+         SetValue( this, FormatKey( "CDELT", i + 1, -1, s, status ), cdelt + i,
+                   AST__FLOAT, "Pixel size", status );
+      }
+
+/* Reference equinox */
+      if( equ != AST__BAD ) SetValue( this, "EQUINOX", &equ, AST__FLOAT,
+                                        "Epoch of reference equinox", status );
+
+/* Date of observation. */
+      val = GetItem( &(store->mjdobs), 0, 0, ' ', NULL, method, class, status );
+      if( val != AST__BAD ) {
+
+/* The format used for the DATE-OBS keyword depends on the value of the
+   keyword. For DATE-OBS < 1999.0, use the old "dd/mm/yy" format.
+   Otherwise, use the new "ccyy-mm-ddThh:mm:ss[.ssss]" format. */
+         palCaldj( 99, 1, 1, &mjd99, &jj );
+         if( val < mjd99 ) {
+            palDjcal( 0, val, iymdf, &jj );
+            sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
+                     iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
+         } else {
+            palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
+            palDd2tf( 3, fd, sign, ihmsf );
+            sprintf( combuf, "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d.%3.3d",
+                     iymdf[0], iymdf[1], iymdf[2], ihmsf[0], ihmsf[1],
+                     ihmsf[2], ihmsf[3] );
+         }
+
+/* Now store the formatted string in the FitsChan. */
+         cval = combuf;
+         SetValue( this, "DATE-OBS", (void *) &cval, AST__STRING,
+                   "Date of observation", status );
+      }
+
+/* Rest frequency */
+      SetValue( this, "RESTFREQ", &rf, AST__FLOAT, "[Hz] Rest frequency", status );
+
+/* The image frequency corresponding to the rest frequency (only used for
+   double sideband data). */
+      val = GetItem( &(store->imagfreq), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) {
+         SetValue( this, "IMAGFREQ", &val, AST__FLOAT, "[Hz] Image frequency", status );
+      }
+
+/* Ensure the FitsChan contains OBJECT and LINE headers */
+      if( !HasCard( this, "OBJECT", method, class, status ) ) {
+         cval = " ";
+         SetValue( this, "OBJECT", &cval, AST__STRING, NULL, status );
+      }
+      if( !HasCard( this, "LINE", method, class, status ) ) {
+         cval = " ";
+         SetValue( this, "LINE", &cval, AST__STRING, NULL, status );
+      }
+
+/* CLASS expects the VELO-LSR keyword to hold the radio velocity of the
+   reference channel (NOT of the source as I was told!!) with respect to
+   the LSRK rest frame. The "crval" array holds the frequency of the
+   reference channel in the source rest frame, so we need to convert this
+   to get the value for VELO-LSR. Create a SpecFrame describing the
+   required frame (other attributes such as Epoch etc are left unset and
+   so will be picked up from the supplied FrameSet). We set MinAxes
+   and MaxAxes so that the Frame can be used as a template to match the
+   1D or 3D current Frame in the supplied FrameSet. */
+      velofrm = (AstFrame *) astSpecFrame( "System=vrad,StdOfRest=lsrk,"
+                                           "Unit=m/s,MinAxes=1,MaxAxes=3", status );
+
+/* Find the spectral axis within the current Frame of the supplied
+   FrameSet, using the above "velofrm" as a template. */
+      fsconv1 = astFindFrame( curfrm, velofrm, "" );
+
+/* If OK, extract the SpecFrame from the returned FraneSet (this will
+   have the attribute values that were assigned explicitly to "velofrm"
+   and will have inherited all unset attributes from the supplied
+   FrameSet). */
+      if( fsconv1 ) {
+         velofrm = astAnnul( velofrm );
+         velofrm = astGetFrame( fsconv1, AST__CURRENT );
+         fsconv1 = astAnnul( fsconv1 );
+
+/* Take a copy of the velofrm and modify its attributes so that it
+   describes frequency in the sources rest frame in units of Hz. This is
+   the system that CLASS expects for the CRVAL3 keyword. */
+         freqfrm = astCopy( velofrm );
+         astSet( freqfrm, "System=freq,StdOfRest=Source,Unit=Hz", status );
+
+/* Get a Mapping from frequency to velocity. */
+         fsconv1 = astConvert( freqfrm, velofrm, "" );
+         if( fsconv1 ) {
+
+/* Use this Mapping to convert the spectral crval value from frequency to
+   velocity. Also convert the value for the neighbouring channel. */
+            aval[ 0 ] = crval[ axspec ]*specfactor;
+            aval[ 1 ] = aval[ 0 ] + cdelt[ axspec ]*specfactor;
+            astTran1( fsconv1, 2, aval, 1, aval );
+
+/* Store the value. Also store it as VLSR since this keyword seems to be
+   used for the same thing. */
+            SetValue( this, "VELO-LSR", aval, AST__FLOAT, "[m/s] Reference velocity", status );
+            SetValue( this, "VLSR", aval, AST__FLOAT, "[m/s] Reference velocity", status );
+
+/* The DELTAV keyword holds the radio velocity channel spacing in the
+   LSR. */
+            delta = aval[ 1 ] - aval[ 0 ];
+            SetValue( this, "DELTAV", &delta, AST__FLOAT, "[m/s] Velocity resolution", status );
+
+/* Free remaining resources. */
+            fsconv1 = astAnnul( fsconv1 );
+         }
+      }
+      velofrm = astAnnul( velofrm );
+
+/* AZIMUTH and ELEVATIO - the (az,el) equivalent of CRVAL. We need a
+   Mapping from the CTYPE spatial system to (az,el). This depends on all
+   the extra info like telescope position, epoch, etc.  This info is in
+   the current Frame in the supplied FrameSet. First get a conversion
+   from a sky frame with default axis ordering to the supplied Frame. All
+   the extra info is picked up from the supplied Frame since it is not set
+   in the template. */
+      radecfrm = (AstFrame *) astSkyFrame( "Permute=0,MinAxes=3,MaxAxes=3", status );
+      fsconv1 = astFindFrame( curfrm, radecfrm, "" );
+
+/* Now get conversion from the an (az,el) Frame to the supplied Frame. */
+      azelfrm = (AstFrame *) astSkyFrame( "System=AZEL,Permute=0,MinAxes=3,MaxAxes=3", status );
+      fsconv2 = astFindFrame( curfrm, azelfrm, "" );
+
+/* If both conversions werew possible, concatenate their Mappings to get
+   a Mapping from (lon,lat) in the CTYPE system, to (az,el). */
+      if( fsconv1 && fsconv2 ) {
+         map1 = astGetMapping( fsconv1, AST__CURRENT, AST__BASE );
+         map2 = astGetMapping( fsconv2, AST__BASE, AST__CURRENT );
+         map3 = (AstMapping *) astCmpMap( map1, map2, 1, "", status );
+
+/* Store the CRVAL (ra,dec) values in the default order. */
+         radec[ 0 ] = crval[ axlon ]*AST__DD2R;
+         radec[ 1 ] = crval[ axlat ]*AST__DD2R;
+
+/* Transform to (az,el), normalise, convert to degrees and store. */
+         astTranN( map3, 1, 2, 1, radec, 1, 2, 1, azel );
+         if( azel[ 0 ] != AST__BAD && azel[ 1 ] != AST__BAD ) {
+            astNorm( azelfrm, azel );
+            azel[ 0 ] *= AST__DR2D;
+            azel[ 1 ] *= AST__DR2D;
+            SetValue( this, "AZIMUTH", azel, AST__FLOAT, "[Deg] Telescope azimuth", status );
+            SetValue( this, "ELEVATIO", azel + 1, AST__FLOAT, "[Deg] Telescope elevation", status );
+         }
+
+/* Free resources */
+         map1 = astAnnul( map1 );
+         map2 = astAnnul( map2 );
+         map3 = astAnnul( map3 );
+         fsconv1 = astAnnul( fsconv1 );
+         fsconv2 = astAnnul( fsconv2 );
+      }
+      radecfrm = astAnnul( radecfrm );
+      azelfrm = astAnnul( azelfrm );
+   }
+   curfrm = astAnnul( curfrm );
+
+/* Release CDELT workspace */
+   if( cdelt ) cdelt = (double *) astFree( (void *) cdelt );
+
+/* Return zero or ret depending on whether an error has occurred. */
+   return astOK ? ok : 0;
+}
+
+static void ClassTrans( AstFitsChan *this, AstFitsChan *ret, int axlat,
+                        int axlon, const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     ClassTrans
+
+*  Purpose:
+*     Translated non-standard FITS-CLASS headers into equivalent standard
+*     ones.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void ClassTrans( AstFitsChan *this, AstFitsChan *ret, int axlat,
+*                      int axlon, const char *method, const char *class )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function extends the functionality of the SpecTrans function,
+*     by converting non-standard WCS keywords into standard FITS-WCS
+*     keywords, using the conventions of the FITS-CLASS encoding.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan containing the original header cards.
+*     ret
+*        Pointer to a FitsChan in which to return the standardised header
+*        cards.
+*     axlat
+*        Zero-based index of the celestial latitude axis.
+*     axlon
+*        Zero-based index of the celestial longitude axis.
+*     method
+*        Pointer to string holding name of calling method.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*/
+
+/* Local Variables: */
+   char *cval;                    /* Pointer to character string */
+   char newtype[ 10 ];            /* New CTYPE value */
+   const char *keyname;           /* Pointer to keyword name */
+   const char *ssyssrc;           /* Pointer to SSYSSRC keyword value string */
+   double crval;                  /* CRVAL value */
+   double restfreq;               /* Rest frequency (Hz) */
+   double v0;                     /* Ref channel velocity in source frame */
+   double vref;                   /* Ref channel velocity in LSR or whatever */
+   double vsource;                /* Source velocity */
+   double zsource;                /* Source redshift */
+   int axspec;                    /* Index of spectral axis */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the rest frequency. */
+   restfreq = AST__BAD;
+   if( !GetValue2( ret, this, "RESTFRQ", AST__FLOAT, (void *) &restfreq, 0,
+                  method, class, status ) ){
+      GetValue2( ret, this, "RESTFREQ", AST__FLOAT, (void *) &restfreq, 0,
+                  method, class, status );
+   }
+   if( restfreq == AST__BAD ) {
+      astError( AST__BDFTS, "%s(%s): Keyword RESTFREQ not found in CLASS "
+                "FITS header.", status, method, class );
+   }
+
+/* Get the index of the spectral axis. */
+   if( axlat + axlon == 1 ) {
+      axspec = 2;
+   } else if( axlat + axlon == 3 ) {
+      axspec = 0;
+   } else {
+      axspec = 1;
+   }
+
+/* Get the spectral CTYPE value */
+   if( GetValue2( ret, this, FormatKey( "CTYPE", axspec + 1, -1, ' ', status ),
+                  AST__STRING, (void *) &cval, 0, method, class, status ) ){
+
+/* We can only handle frequency axes at the moment. */
+      if( !astChrMatch( "FREQ", cval ) ) {
+         astError( AST__BDFTS, "%s(%s): FITS-CLASS keyword %s has value "
+                   "\"%s\" - CLASS support in AST only includes \"FREQ\" axes.", status,
+                   method, class, FormatKey( "CTYPE", axspec + 1, -1, ' ', status ),
+                   cval );
+
+/* CRVAL for the spectral axis needs to be incremented by RESTFREQ if the
+   axis represents frequency. */
+      } else {
+         keyname = FormatKey( "CRVAL", axspec + 1, -1, ' ', status );
+         if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &crval, 1,
+                        method, class, status ) ) {
+            crval += restfreq;
+            SetValue( ret, keyname, (void *) &crval, AST__FLOAT, NULL, status );
+         }
+      }
+
+/* CLASS frequency axes describe source frame frequencies. */
+      cval = "SOURCE";
+      SetValue( ret, "SPECSYS", (void *) &cval, AST__STRING, NULL, status );
+   }
+
+/* If no projection code is supplied for the longitude and latitude axes,
+   use "-GLS". This will be translated to "-SFL" by SpecTrans. */
+   keyname = FormatKey( "CTYPE", axlon + 1, -1, ' ', status );
+   if( GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0, method,
+                  class, status ) ){
+      if( strlen(cval) > 4 && !strncmp( "    ", cval + 4, 4 ) ) {
+         strncpy( newtype, cval, 4 );
+         strcpy( newtype + 4, "-GLS" );
+         cval = newtype;
+         SetValue( ret, keyname, (void *) &cval, AST__STRING, NULL, status );
+      }
+   }
+   keyname = FormatKey( "CTYPE", axlat + 1, -1, ' ', status );
+   if( GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0, method,
+                  class, status ) ){
+      if( strlen(cval) > 4 && !strncmp( "    ", cval + 4, 4 ) ) {
+         strncpy( newtype, cval, 4 );
+         strcpy( newtype + 4, "-GLS" );
+         cval = newtype;
+         SetValue( ret, keyname, (void *) &cval, AST__STRING, NULL, status );
+      }
+   }
+
+/* Look for a keyword with name "VELO-...". This specifies the radio velocity
+   at the reference channel, in a standard of rest specified by the "..."
+   in the keyword name. If "VELO-..." is not found, look for "VLSR",
+   which is the same as "VELO-LSR". */
+   if( GetValue2( ret, this, "VELO-%3c", AST__FLOAT, (void *) &vref, 0,
+                  method, class, status ) ||
+       GetValue2( ret, this, "VLSR", AST__FLOAT, (void *) &vref, 0,
+                  method, class, status ) ){
+
+/* Calculate the radio velocity (in the rest frame of the source) corresponding
+   to the frequency at the reference channel. */
+      v0 = AST__C*( restfreq - crval )/restfreq;
+
+/* Assume that the source velocity is the difference between this velocity
+   and the reference channel velocity given by "VELO-..." */
+      vsource = vref - v0;
+
+/* Get the keyword name and find the corresponding SSYSSRC keyword value. */
+      keyname = CardName( this, status );
+      if( !strcmp( keyname, "VELO-HEL" ) ) {
+         ssyssrc = "BARYCENT";
+      } else if( !strcmp( keyname, "VELO-OBS" ) || !strcmp( keyname, "VELO-TOP" ) ) {
+         ssyssrc = "TOPOCENT";
+      } else if( !strcmp( keyname, "VELO-EAR" ) || !strcmp( keyname, "VELO-GEO" ) ) {
+         ssyssrc = "GEOCENTR";
+      } else {
+         ssyssrc = "LSRK";
+      }
+      SetValue( ret, "SSYSSRC", (void *) &ssyssrc, AST__STRING, NULL, status );
+
+/* Convert from radio velocity to redshift and store as ZSOURCE */
+      zsource = ( AST__C / (AST__C - vsource) ) - 1.0;
+      SetValue( ret, "ZSOURCE", (void *) &zsource, AST__FLOAT, NULL, status );
+   }
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the astClearAttrib protected
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     FitsChan, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Card. */
+/* ----- */
+   if ( !strcmp( attrib, "card" ) ) {
+      astClearCard( this );
+
+/* Encoding. */
+/* --------- */
+   } else if ( !strcmp( attrib, "encoding" ) ) {
+      astClearEncoding( this );
+
+/* CDMatrix */
+/* -------- */
+   } else if ( !strcmp( attrib, "cdmatrix" ) ) {
+      astClearCDMatrix( this );
+
+/* FitsAxisOrder. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "fitsaxisorder" ) ) {
+      astClearFitsAxisOrder( this );
+
+/* FitsDigits. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "fitsdigits" ) ) {
+      astClearFitsDigits( this );
+
+/* DefB1950 */
+/* -------- */
+   } else if ( !strcmp( attrib, "defb1950" ) ) {
+      astClearDefB1950( this );
+
+/* TabOK */
+/* ----- */
+   } else if ( !strcmp( attrib, "tabok" ) ) {
+      astClearTabOK( this );
+
+/* CarLin */
+/* ------ */
+   } else if ( !strcmp( attrib, "carlin" ) ) {
+      astClearCarLin( this );
+
+/* PolyTan */
+/* ------- */
+   } else if ( !strcmp( attrib, "polytan" ) ) {
+      astClearPolyTan( this );
+
+/* Iwc */
+/* --- */
+   } else if ( !strcmp( attrib, "iwc" ) ) {
+      astClearIwc( this );
+
+/* Clean */
+/* ----- */
+   } else if ( !strcmp( attrib, "clean" ) ) {
+      astClearClean( this );
+
+/* Warnings. */
+/* -------- */
+   } else if ( !strcmp( attrib, "warnings" ) ) {
+      astClearWarnings( this );
+
+/* If the name was not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then report an
+   error. */
+   } else if ( astOK && ( !strcmp( attrib, "ncard" ) ||
+                          !strcmp( attrib, "allwarnings" ) ) ){
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearCard( AstFitsChan *this, int *status ){
+
+/*
+*+
+*  Name:
+*     astClearCard
+
+*  Purpose:
+*     Clear the Card attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void astClearCard( AstFitsChan *this )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function clears the Card attribute for the supplied FitsChan by
+*     setting it to the index of the first un-used card in the FitsChan.
+*     This causes the next read operation performed on the FitsChan to
+*     read the first card. Thus, it is equivalent to "rewinding" the FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+
+*  Notes:
+*     -  This function attempts to execute even if an error has occurred.
+*-
+*/
+
+/* Local Variables; */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Check the supplied FitsChan. If its is empty, return. */
+   if ( !this || !(this->head) ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Set the pointer to the current card so that it points to the card at
+   the head of the list. */
+   this->card = this->head;
+
+/* If the current card has been read into an AST object, move on to the
+   first card which has not, unless we are not skipping such cards. */
+   if( CARDUSED(this->card) ){
+      MoveCard( this, 1, "astClearCard", astGetClass( this ), status );
+   }
+}
+
+static int CnvValue( AstFitsChan *this, int type, int undef, void *buff,
+                     const char *method, int *status ){
+
+/*
+*
+*  Name:
+*     CnvValue
+
+*  Purpose:
+*     Convert a data value into a given FITS data type.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int CnvValue( AstFitsChan *this, int type, int undef, void *buff,
+*                   const char *method, int *status )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function produces a copy of the data value for the current card
+*     converted from its stored data type to the supplied data type.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     type
+*        The FITS data type in which to return the data value of the
+*        current card.
+*     undef
+*        Determines what happens if the current card has an undefined
+*        value. If "undef" is zero, an error will be reported identifying
+*        the undefined keyword value. If "undef" is non-zero, no error is
+*        reported and the contents of the output buffer are left unchanged.
+*     buf
+*        A pointer to a buffer to recieve the converted value. It is the
+*        responsibility of the caller to ensure that a suitable buffer is
+*        supplied.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the conversion was not possible (in which case NO error is
+*     reported), one otherwise.
+
+*  Notes:
+*     -  When converting from floating point to integer, the  floating
+*     point value is truncated using a C cast.
+*     -  Non-zero numerical values are considered TRUE, and zero
+*     numerical values are considered FALSE. Any string starting with a
+*     'T' or a 'Y' (upper or lower case) is considered TRUE, and anything
+*     starting with an 'F' or an 'N' (upper or lower case) is considered
+*     FALSE. In addition, a dot ('.') may be placed in front of a 'T' or an
+*     'F'.
+*     -  A logical TRUE value is represented as a real numerical value of
+*     one and the character string "Y". A logical FALSE value is represented
+*     by a real numerical value of zero and the character string "N".
+*     -  When converting from a string to any numerical value, zero is
+*     returned if the string is not a formatted value which can be converted
+*     into the corresponding type using astSscanf.
+*     - Real and imaginary parts of a complex value should be separated by
+*     spaces within strings. If a string does contains only a single numerical
+*     value, it is assumed to be the real part, and the imaginary part is
+*     assumed to be zero.
+*     -  When converting a complex numerical type to a non-complex numerical
+*     type, the returned value is derived from the real part only, the
+*     imaginary part is ignored.
+*     -  Zero is returned if an error has occurred, or if this function
+*     should fail for any reason.
+*     - If the supplied value is undefined an error will be reported.
+*/
+
+/* Local Variables: */
+   int otype;               /* Stored data type */
+   size_t osize;            /* Size of stored data */
+   void *odata;             /* Pointer to stored data */
+
+/* Check the global error status, and the supplied buffer. */
+   if ( !astOK || !buff ) return 0;
+
+/* Get the type in which the data value is stored. */
+   otype = CardType( this, status );
+
+/* Get a pointer to the stored data value, and its size. */
+   osize = 0;
+   odata = CardData( this, &osize, status );
+
+/* Do the conversion. */
+   return CnvType( otype, odata, osize, type, undef, buff,
+                   CardName( this, status ), method, astGetClass( this ),
+                   status );
+}
+
+static int CnvType( int otype, void *odata, size_t osize, int type, int undef,
+                     void *buff, const char *name, const char *method,
+                     const char *class, int *status ){
+/*
+*
+*  Name:
+*     CnvType
+
+*  Purpose:
+*     Convert a data value into a given FITS data type.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int CnvType( int otype, void *odata, size_t osize, int type, int undef,
+*                   void *buff, const char *name, const char *method,
+*                   const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function produces a copy of the data value for the current card
+*     converted from its stored data type to the supplied data type.
+
+*  Parameters:
+*     otype
+*        The type of the supplied data value.
+*     odata
+*        Pointer to a buffer holding the supplied data value.
+*     osize
+*        The size of the data value (in bytes - strings include the
+*        terminating null).
+*     type
+*        The FITS data type in which to return the data value of the
+*        current card.
+*     undef
+*        Determines what happens if the supplied data value type is
+*        undefined If "undef" is zero, an error will be reported identifying
+*        the undefined keyword value. If "undef" is non-zero, no error is
+*        reported and the contents of the output buffer are left unchanged.
+*     buff
+*        A pointer to a buffer to recieve the converted value. It is the
+*        responsibility of the caller to ensure that a suitable buffer is
+*        supplied.
+*     name
+*        A pointer to a string holding a keyword name to include in error
+*        messages.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the conversion was not possible (in which case NO error is
+*     reported), one otherwise.
+
+*  Notes:
+*     -  When converting from floating point to integer, the  floating
+*     point value is truncated using a C cast.
+*     -  Non-zero numerical values are considered TRUE, and zero
+*     numerical values are considered FALSE. Any string starting with a
+*     'T' or a 'Y' (upper or lower case) is considered TRUE, and anything
+*     starting with an 'F' or an 'N' (upper or lower case) is considered
+*     FALSE. In addition, a dot ('.') may be placed in front of a 'T' or an
+*     'F'.
+*     -  A logical TRUE value is represented as a real numerical value of
+*     one and the character string "Y". A logical FALSE value is represented
+*     by a real numerical value of zero and the character string "N".
+*     -  When converting from a string to any numerical value, zero is
+*     returned if the string isn not a formatted value which can be converted
+*     into the corresponding type using astSscanf.
+*     - Real and imaginary parts of a complex value should be separated by
+*     spaces within strings. If a string does contains only a single numerical
+*     value, it is assumed to be the real part, and the imaginary part is
+*     assumed to be zero.
+*     -  When converting a complex numerical type to a non-complex numerical
+*     type, the returned value is derived from the real part only, the
+*     imaginary part is ignored.
+*     -  Zero is returned if an error has occurred, or if this function
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   const char *c;           /* Pointer to next character */
+   const char *ostring;     /* String data value */
+   double odouble;          /* Double data value */
+   int oint;                /* Integer data value */
+   int ival;                /* Integer value read from string */
+   int len;                 /* Length of character string */
+   int nc;                  /* No. of characetsr used */
+   int ret;                 /* Returned success flag */
+
+/* Check the global error status, and the supplied buffer. */
+   if ( !astOK || !buff ) return 0;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Assume success. */
+   ret = 1;
+
+/* If the supplied data type is undefined, report an error unless the
+   returned data type is also undefined or an undefined value is
+   acceptable for the keyword. */
+   if( otype == AST__UNDEF ) {
+      if( type != AST__UNDEF && !undef ) {
+         ret = 0;
+         astError( AST__FUNDEF, "The FITS keyword '%s' has an undefined "
+                   "value.", status, name );
+      }
+
+/* If the returned data type is undefined, the returned value is
+   immaterial, so leave the buffer contents unchanged. */
+   } else if( type == AST__UNDEF ) {
+
+/* If there is no data value and this is not a COMMENT keyword, or if
+   there is a data value and this is a COMMENT card, conversion is not
+   possible. */
+   } else if( ( odata && otype == AST__COMMENT ) ||
+              ( !odata && otype != AST__COMMENT ) ) {
+      ret = 0;
+
+/* If there is no data value (and therefore this is a comment card),
+   conversion is only possible if the output type is also a comment. */
+   } else if( !odata ) {
+      if( type != AST__COMMENT ) ret = 0;
+
+/* Otherwise we have a data value, so do each possible combination of
+   supplied and stored data types... */
+   } else {
+
+/* Convert a AST__FLOAT data value to ... */
+      if( otype == AST__FLOAT ){
+         odouble = *( (double *) odata );
+         if( type == AST__FLOAT ){
+            (void) memcpy( buff, odata, osize );
+         } else if( type == AST__STRING || type == AST__CONTINUE  ){
+            if( odouble != AST__BAD ) {
+               (void) sprintf( cnvtype_text, "%.*g", DBL_DIG, odouble );
+               CheckZero( cnvtype_text, odouble, 0, status );
+            } else {
+               strcpy( cnvtype_text, BAD_STRING );
+            }
+            *( (char **) buff ) = cnvtype_text;
+         } else if( type == AST__INT      ){
+            *( (int *) buff ) = (int) odouble;
+         } else if( type == AST__LOGICAL  ){
+            *( (int *) buff ) = ( odouble == 0.0 ) ? 0 : 1;
+         } else if( type == AST__COMPLEXF ){
+            ( (double *) buff )[ 0 ] = odouble;
+            ( (double *) buff )[ 1 ] = 0.0;
+         } else if( type == AST__COMPLEXI ){
+            ( (int *) buff )[ 0 ] = (int) odouble;
+            ( (int *) buff )[ 1 ] = 0;
+         } else if( astOK ){
+            ret = 0;
+            astError( AST__INTER, "CnvType: AST internal programming error - "
+                      "FITS data-type no. %d not yet supported.", status, type );
+         }
+
+/* Convert a AST__STRING data value to ... */
+      } else if( otype == AST__STRING || type == AST__CONTINUE ){
+         ostring = (char *) odata;
+         len = (int) strlen( ostring );
+         if( type == AST__FLOAT ){
+            if( nc = 0,
+                    ( 0 == astSscanf( ostring, BAD_STRING " %n", &nc ) )
+                  && (nc >= len ) ){
+               *( (double *) buff ) = AST__BAD;
+            } else if( nc = 0,
+                     ( 1 != astSscanf( ostring, "%lf %n", (double *) buff, &nc ) )
+                  || (nc < len ) ){
+               ret = 0;
+            }
+         } else if( type == AST__STRING || type == AST__CONTINUE  ){
+            strncpy( cnvtype_text, (char *) odata, AST__FITSCHAN_FITSCARDLEN );
+            *( (char **) buff ) = cnvtype_text;
+         } else if( type == AST__INT      ){
+            if( nc = 0,
+                     ( 1 != astSscanf( ostring, "%d %n", (int *) buff, &nc ) )
+                  || (nc < len ) ){
+               ret = 0;
+            }
+         } else if( type == AST__LOGICAL  ){
+            if( nc = 0,
+                     ( 1 == astSscanf( ostring, "%d %n", &ival, &nc ) )
+                  && (nc >= len ) ){
+               *( (int *) buff ) = ival ? 1 : 0;
+            } else {
+               c = ostring;
+               while( *c && isspace( (int) *c ) ) c++;
+               if( *c == 'y' || *c == 'Y' || *c == 't' || *c == 'T' ||
+                   ( *c == '.' && ( c[1] == 't' || c[1] == 'T' ) ) ){
+                  *( (int *) buff ) = 1;
+               } else if( *c == 'n' || *c == 'N' || *c == 'f' || *c == 'F' ||
+                   ( *c == '.' && ( c[1] == 'f' || c[1] == 'F' ) ) ){
+                  *( (int *) buff ) = 0;
+               } else {
+                  ret = 0;
+               }
+            }
+         } else if( type == AST__COMPLEXF ){
+            if( nc = 0,
+                     ( 1 != astSscanf( ostring, "%lf %lf %n", (double *) buff,
+                                    (double *) buff + 1, &nc ) )
+                  || (nc < len ) ){
+               if( nc = 0,
+                        ( 1 != astSscanf( ostring, "%lf %n", (double *) buff,
+                                       &nc ) )
+                     || (nc < len ) ){
+                  ret = 0;
+               } else {
+                  ( (double *) buff )[ 1 ] = 0.0;
+               }
+            }
+         } else if( type == AST__COMPLEXI ){
+            if( nc = 0,
+                    ( 1 != astSscanf( ostring, "%d %d %n", (int *) buff,
+                                   (int *) buff + 1, &nc ) )
+                   || (nc < len ) ){
+               if( nc = 0,
+                        ( 1 != astSscanf( ostring, "%d %n", (int *) buff, &nc ) )
+                     || (nc < len ) ){
+                  ret = 0;
+               } else {
+                  ( (int *) buff )[ 1 ] = 0;
+               }
+            }
+         } else if( astOK ){
+            ret = 0;
+            astError( AST__INTER, "CnvType: AST internal programming error - "
+                      "FITS data-type no. %d not yet supported.", status, type );
+         }
+
+/* Convert an AST__INT data value to ... */
+      } else if( otype == AST__INT      ){
+         oint = *( (int *) odata );
+         if( type == AST__FLOAT ){
+            *( (double *) buff ) = (double) oint;
+         } else if( type == AST__STRING || type == AST__CONTINUE  ){
+            (void) sprintf( cnvtype_text, "%d", oint );
+            *( (char **) buff ) = cnvtype_text;
+         } else if( type == AST__INT      ){
+            (void) memcpy( buff, odata, osize );
+         } else if( type == AST__LOGICAL  ){
+            *( (int *) buff ) = oint ? 1 : 0;
+         } else if( type == AST__COMPLEXF ){
+            ( (double *) buff )[ 0 ] = (double) oint;
+            ( (double *) buff )[ 1 ] = 0.0;
+         } else if( type == AST__COMPLEXI ){
+            ( (int *) buff )[ 0 ] = oint;
+            ( (int *) buff )[ 1 ] = 0;
+         } else if( astOK ){
+            ret = 0;
+            astError( AST__INTER, "CnvType: AST internal programming error - "
+                      "FITS data-type no. %d not yet supported.", status, type );
+         }
+
+/* Convert a LOGICAL data value to ... */
+      } else if( otype == AST__LOGICAL  ){
+         oint = *( (int *) odata );
+         if( type == AST__FLOAT ){
+            *( (double *) buff ) = oint ? 1.0 : 0.0;
+         } else if( type == AST__STRING || type == AST__CONTINUE  ){
+            if( oint ){
+               strcpy( cnvtype_text, "Y" );
+            } else {
+               strcpy( cnvtype_text, "N" );
+            }
+            *( (char **) buff ) = cnvtype_text;
+         } else if( type == AST__INT      ){
+            *( (int *) buff ) = oint;
+         } else if( type == AST__LOGICAL  ){
+            (void) memcpy( buff, odata, osize );
+         } else if( type == AST__COMPLEXF ){
+            ( (double *) buff )[ 0 ] = oint ? 1.0 : 0.0;
+            ( (double *) buff )[ 1 ] = 0.0;
+         } else if( type == AST__COMPLEXI ){
+            ( (int *) buff )[ 0 ] = oint ? 1 : 0;
+            ( (int *) buff )[ 1 ] = 0;
+         } else if( astOK ){
+            ret = 0;
+            astError( AST__INTER, "CnvType: AST internal programming error - "
+                      "FITS data-type no. %d not yet supported.", status, type );
+         }
+
+/* Convert a AST__COMPLEXF data value to ... */
+      } else if( otype == AST__COMPLEXF ){
+         odouble = ( (double *) odata )[ 0 ];
+         if( type == AST__FLOAT ){
+            *( (double *) buff ) = odouble;
+         } else if( type == AST__STRING || type == AST__CONTINUE  ){
+            (void) sprintf( cnvtype_text0, "%.*g", DBL_DIG, ( (double *) odata )[ 0 ] );
+            CheckZero( cnvtype_text0, ( (double *) odata )[ 0 ], 0, status );
+            (void) sprintf( cnvtype_text1, "%.*g", DBL_DIG, ( (double *) odata )[ 1 ] );
+            CheckZero( cnvtype_text1, ( (double *) odata )[ 1 ], 0, status );
+            (void) sprintf( cnvtype_text, "%s %s", cnvtype_text0, cnvtype_text1 );
+            *( (char **) buff ) = cnvtype_text;
+         } else if( type == AST__INT      ){
+            *( (int *) buff ) = (int) odouble;
+         } else if( type == AST__LOGICAL  ){
+            *( (int *) buff ) = ( odouble == 0.0 ) ? 0 : 1;
+         } else if( type == AST__COMPLEXF ){
+            (void) memcpy( buff, odata, osize );
+         } else if( type == AST__COMPLEXI ){
+            ( (int *) buff )[ 0 ] = (int) odouble;
+            ( (int *) buff )[ 1 ] = (int) ( (double *) odata )[ 1 ];
+         } else if( astOK ){
+            ret = 0;
+            astError( AST__INTER, "CnvType: AST internal programming error - "
+                      "FITS data-type no. %d not yet supported.", status, type );
+         }
+
+/* Convert a AST__COMPLEXI data value to ... */
+      } else if( otype == AST__COMPLEXI ){
+         oint = ( (int *) odata )[ 0 ];
+         if( type == AST__FLOAT ){
+            *( (double *) buff ) = (double) oint;
+         } else if( type == AST__STRING || type == AST__CONTINUE  ){
+            (void) sprintf( cnvtype_text, "%d %d", ( (int *) odata )[ 0 ],
+                                           ( (int *) odata )[ 1 ] );
+            *( (char **) buff ) = cnvtype_text;
+         } else if( type == AST__INT      ){
+            *( (int *) buff ) = oint;
+         } else if( type == AST__LOGICAL  ){
+            *( (int *) buff ) = oint ? 1 : 0;
+         } else if( type == AST__COMPLEXF ){
+            ( (double *) buff )[ 0 ] = (double) oint;
+            ( (double *) buff )[ 1 ] = (double) ( (int *) odata )[ 1 ];
+         } else if( type == AST__COMPLEXI ){
+            (void) memcpy( buff, odata, osize );
+         } else if( astOK ){
+            ret = 0;
+            astError( AST__INTER, "CnvType: AST internal programming error - "
+                      "FITS data-type no. %d not yet supported.", status, type );
+         }
+      } else if( astOK ){
+         ret = 0;
+         astError( AST__INTER, "CnvType: AST internal programming error - "
+                   "FITS data-type no. %d not yet supported.", status, type );
+      }
+   }
+   return ret;
+}
+
+static int ComBlock( AstFitsChan *this, int incr, const char *method,
+                     const char *class, int *status ){
+
+/*
+*  Name:
+*     ComBlock
+
+*  Purpose:
+*     Delete a AST comment block in a Native-encoded FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int ComBlock( AstFitsChan *this, int incr, const char *method,
+*                   const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function looks for a block of comment cards as defined below,
+*     and deletes all the cards in the block, if a suitable block is found.
+*
+*     Comment blocks consist of a contiguous sequence of COMMENT cards. The
+*     text of each card should start and end with the 3 characters "AST".
+*     The block is delimited above by a card containing all +'s (except
+*     for the two "AST" strings), and below by a card containing all -'s.
+*
+*     The block is assumed to start on the card which is adjacent to the
+*     current card on entry.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     incr
+*        This should be either +1 or -1, and is the increment between
+*        adjacent cards in the comment block. A value of +1 means
+*        that the card following the current card is taken as the first in
+*        the block, and subsequent cards are checked. The block must then
+*        end with a line of -'s. If -1 is supplied, then the card
+*        preceding the current card is taken as the first in the block,
+*        and preceding cards are checked. The block must then end with
+*        a row of +'s.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     1 if a block was found and deleted, 0 otherwise.
+
+*  Notes:
+*     -  The pointer to the current card is returned unchanged.
+*/
+
+/* Local Variables: */
+   FitsCard *card0;              /* Pointer to current FitsCard on entry */
+   char del;                     /* Delimiter character */
+   char *text;                   /* Pointer to the comment text */
+   int i;                        /* Card index within the block */
+   int ncard;                    /* No. of cards in the block */
+   int ret;                      /* The returned flag */
+   size_t len;                   /* Length of the comment text */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Save the pointer to the current card. */
+   card0 = this->card;
+
+/* Initialise the returned flag to indicate that we have not found a
+   comment block. */
+   ret = 0;
+
+/* Move on to the first card in the block. If this is not possible (due to
+   us already being at the start or end of the FitsChan), then return. */
+   if( MoveCard( this, incr, method, class, status ) == 1 ) {
+
+/* Store the character which is used in the delimiter line for the
+   comment block. */
+      del = ( incr == 1 ) ? '-' : '+';
+
+/* Initialise the number of cards in the comment block to zero. */
+      ncard = 0;
+
+/* Loop round until the end (or start) of the comment block is found.
+   Leave the loop if an error occurs.  */
+      while( astOK ) {
+
+/* Is this card a comment card? If not, then we have failed to find a
+   complete comment block. Break out of the loop. */
+         if( CardType( this, status ) != AST__COMMENT ) break;
+
+/* Increment the number of cards in the comment block. */
+         ncard++;
+
+/* Get the text of the comment, and its length. */
+         text = CardComm( this, status );
+         if( text ){
+            len = strlen( text );
+
+/* Check the first 3 characters. Break out of the loop if they are not
+   "AST". */
+            if( strncmp( "AST", text, 3 ) ) break;
+
+/* Check the last 3 characters. Break out of the loop if they are not
+   "AST". */
+            if( strcmp( "AST", text + len - 3 ) ) break;
+
+/* If the comment is the appropriate block delimiter (a line of +'s or
+   -'s depending on the direction), then set the flag to indicate that we
+   have a complete comment block and leave the loop. Allow spaces to be
+   included. Exclude the "AST" strings at begining and end from the check. */
+            ret = 1;
+            for( i = 3; i < len - 3; i++ ) {
+               if( text[ i ] != del && text[ i ] != ' ' ) {
+                  ret = 0;
+                  break;
+               }
+            }
+         }
+         if( ret ) break;
+
+/* Move on to the next card. If this is not possible (due to us already
+   being at the start or end of the FitsChan), then break out of the loop. */
+         if( MoveCard( this, incr, method, class, status ) == 0 ) break;
+      }
+
+/* Re-instate the original current card. */
+      this->card = card0;
+
+/* If we found a complete comment block, mark it (which is equivalent to
+   deleting it except that memory of the cards location within the
+   FitsChan is preserved for future use), and then re-instate the original
+   current card. */
+      if( ret && astOK ) {
+         for( i = 0; i < ncard; i++ ) {
+            MoveCard( this, incr, method, class, status );
+            MarkCard( this, status );
+         }
+         this->card = card0;
+      }
+   }
+
+/* If an error occurred, indicate that coment block has been deleted. */
+   if( !astOK ) ret = 0;
+   return ret;
+}
+
+static char *ConcatWAT( AstFitsChan *this, int iaxis, const char *method,
+                        const char *class, int *status ){
+/*
+*  Name:
+*     ConcatWAT
+
+*  Purpose:
+*     Concatenate all the IRAF "WAT" keywords for an axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     char *ConcatWAT( AstFitsChan *this, int iaxis, const char *method,
+*                      const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function searches the supplied FitsChan for any keywords of
+*     the form "WATi_j", where i and j are integers and i is equal to the
+*     supplied "iaxis" value plus one, and concatenates their string
+*     values into a single string. Such keywords are created by IRAF to
+*     describe their non-standard ZPX and TNX projections.
+
+*  Parameters:
+*     this
+*        The FistChan.
+*     iaxis
+*        The zero-based index of the axis to be retrieved.
+*     method
+*         The name of the calling method to include in error messages.
+*     class
+*         The object type to include in error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated, null terminated string
+*     containing a copy of the concatentated WAT values. This string must
+*     be freed by the caller (using astFree) when no longer required.
+*
+*     A NULL pointer will be returned if there are no WAT kewyords for
+*     the requested axis in the FitsChan.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   char keyname[ FITSNAMLEN + 5 ];/* Keyword name */
+   char *wat;                     /* Pointer to a single WAT string */
+   char *result;                  /* Returned string */
+   int watlen;                    /* Length of total WAT string (inc. term null)*/
+   int j;                         /* WAT index */
+   size_t size;                   /* Length of string value */
+
+/* Initialise returned value. */
+   result = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Rewind the FitsChan. */
+   astClearCard( this );
+
+/* Concatenate all the IRAF "WAT" keywords together for this axis. These
+   keywords are marked as having been used, so that they are not written
+   out when the FitsChan is deleted. */
+   watlen = 1;
+   j = 1;
+   size = 0;
+   sprintf( keyname, "WAT%d_%.3d", iaxis + 1, j );
+   while( astOK ) {
+
+/* Search forward from the current card for the next WAT card. If no
+   found, try searching again from the start of the FitsChan. If not found
+   evenm then, break. */
+      if( ! FindKeyCard( this, keyname, method, class, status ) ) {
+         astClearCard( this );
+         if( ! FindKeyCard( this, keyname, method, class, status ) ) break;
+      }
+
+      wat = (char *) CardData( this, &size, status );
+      result = (char *) astRealloc( (void *) result,
+                                    watlen - 1 + size );
+      if( result ) {
+         strcpy( result + watlen - 1, wat );
+         watlen += size - 1;
+         MarkCard( this, status );
+         MoveCard( this, 1, method, class, status );
+         j++;
+         sprintf( keyname, "WAT%d_%.3d", iaxis + 1, j );
+      } else {
+         break;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int CountFields( const char *temp, char type, const char *method,
+                        const char *class, int *status ){
+/*
+*  Name:
+*     CountFields
+
+*  Purpose:
+*     Count the number of field specifiers in a template string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int CountFields( const char *temp, char type, const char *method,
+*                      const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns the number of fields which include the
+*     specified character type in the supplied string.
+
+*  Parameters:
+*     temp
+*        Pointer to a null terminated string holding the template.
+*     type
+*        A single character giving the field type to be counted (e.g.
+*        'd', 'c' or 'f').
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of fields.
+
+*  Notes:
+*     -  No error is reported if the parameter "type" is not a valid
+*     field type specifier, but zero will be returned.
+*     -  An error is reported if the template has any invalid field
+*     specifiers in it.
+*     -  A value of zero is returned if an error has already occurred,
+*     or if this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   const char *b;         /* Pointer to next template character */
+   int nf;                /* No. of fields found so far */
+
+/* Check global status. */
+   if( !astOK ) return 0;
+
+/* Initialise a pointer to the start of the template string. */
+   b = temp;
+
+/* Initialise the number of fields found so far. */
+   nf = 0;
+
+/* Go through the string. */
+   while( *b && astOK ){
+
+/* If the current character is a '%', a field is starting. */
+      if( *b == '%' ){
+
+/* Skip over the field width (if supplied). */
+         if( isdigit( (int) *(++b) ) ) b++;
+
+/* Report an error if the end of the string occurs within the field. */
+         if( !*b ) {
+            astError( AST__BDFMT, "%s(%s): Incomplete field specifier found "
+                      "at end of filter template '%s'.", status, method, class,
+                      temp );
+            break;
+
+/* Report an error if the field type is illegal. */
+         } else if( *b != 'd' && *b != 'c' && *b != 'f' ) {
+            astError( AST__BDFMT, "%s(%s): Illegal field type or width "
+                      "specifier '%c' found in filter template '%s'.", status,
+                      method, class, *b, temp );
+            break;
+         }
+
+/* Compare the field type with the supplied type, and increment the
+   number of fields found if it is the correct type. */
+         if( *b == type ) nf++;
+      }
+
+/* Move on to the next character. */
+      b++;
+   }
+
+/* If an error has occurred, return 0. */
+   if( !astOK ) nf = 0;
+
+/* Return the answer. */
+   return nf;
+}
+
+static void CreateKeyword( AstFitsChan *this, const char *name,
+                           char keyword[ FITSNAMLEN + 1 ], int *status ){
+
+/*
+*  Name:
+*     CreateKeyword
+
+*  Purpose:
+*     Create a unique un-used keyword for a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void CreateKeyword( AstFitsChan *this, const char *name,
+*                         char keyword[ FITSNAMLEN + 1 ], int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function takes a name which forms the basis of a FITS
+*     keyword and appends a sequence number (encoded as a pair of
+*     legal FITS keyword characters) so as to generate a unique FITS
+*     keyword which has not previously been used in the FitsChan
+*     supplied.
+*
+*     It is intended for use when several keywords with the same name
+*     must be stored in a FitsChan, since to comply strictly with the
+*     FITS standard keywords should normally be unique (otherwise
+*     external software which processes the keywords might omit one or
+*     other of the values).
+*
+*     An attempt is also made to generate keywords in a form that is
+*     unlikely to clash with those from other sources (in as far as
+*     this is possible with FITS). In any event, a keyword that
+*     already appears in the FitsChan will not be re-used.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name on which the new keyword should be based. This should be
+*        a legal FITS keyword in itself, except that it should be at
+*        least two characters shorter than the maximum length, in
+*        order to accommodate the sequence number characters.
+*
+*        If this string is too long, it will be silently
+*        truncated. Mixed case is permitted, as all characters
+*        supplied are converted to upper case before use.
+*     keyword
+*        A character array in which the generated unique keyword will
+*        be returned, null terminated.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   const char *seq_chars = SEQ_CHARS;/* Pointer to characters used for encoding */
+   char seq_char;                /* The first sequence character */
+   const char *class;            /* Object clas */
+   int found;                    /* Keyword entry found in list? */
+   int limit;                    /* Sequence number has reached limit? */
+   int nc;                       /* Number of basic keyword characters */
+   int seq;                      /* The sequence number */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Store the object class. */
+   class = astGetClass( this );
+
+/* On the first invocation only, determine the number of characters
+   being used to encode sequence number information and save this
+   value. */
+   if( createkeyword_seq_nchars < 0 ) createkeyword_seq_nchars = (int) strlen( seq_chars );
+
+/* Copy the name supplied into the output array, converting to upper
+   case. Leave space for two characters to encode a sequence
+   number. Terminate the resulting string. */
+   for( nc = 0; ( nc < ( FITSNAMLEN - 2 ) ) && name[ nc ]; nc++ ) {
+      keyword[ nc ] = toupper( name[ nc ] );
+   }
+   keyword[ nc ] = '\0';
+
+/* We now search the list of sequence numbers already allocated to
+   find the next one to use for this keyword. */
+   if( this->keyseq ) {
+      found = astMapGet0I( this->keyseq, keyword, &seq );
+   } else {
+      found = 0;
+      this->keyseq = astKeyMap( " ", status );
+   }
+
+/* If the keyword was not found in the list, create a new list entry
+   to describe it. */
+   if( !found ) seq = 0;
+
+/* If OK, loop to find a new sequence number which results in a FITS
+   keyword that hasn't already been used to store data in the
+   FitsChan. */
+   if( astOK ) {
+      while( 1 ) {
+
+/* Determine if the sequence number just obtained has reached the
+   upper limit. This is unlikely to happen in practice, but if it
+   does, we simply re-use this maximum value. Otherwise, we increment
+   the sequence number last used for this keyword to obtain a new
+   one. */
+         limit = ( seq >= ( createkeyword_seq_nchars * createkeyword_seq_nchars - 1 ) );
+         if( !limit ) seq++;
+
+/* Encode the sequence number into two characters and append them to
+   the original keyword (with a terminating null). */
+         seq_char = seq_chars[ seq / createkeyword_seq_nchars ];
+         keyword[ nc ] = seq_char;
+         keyword[ nc + 1 ] = seq_chars[ seq % createkeyword_seq_nchars ];
+         keyword[ nc + 2 ] = '\0';
+
+/* If the upper sequence number limit has not been reached, try to
+   look up the resulting keyword in the FitsChan to see if it has
+   already been used. Quit searching when a suitable keyword is
+   found. */
+         if ( limit || !HasCard( this, keyword, "astWrite", class, status ) ) break;
+      }
+
+/* Store the update sequence number in the keymap. The keys into this
+   keymap are the base keyword name without the appended sequence string, so
+   temporaily terminate the returned keyword name to exclude the sequence
+   string. */
+      keyword[ nc ] = '\0';
+      astMapPut0I( this->keyseq, keyword, seq, NULL );
+      keyword[ nc ] = seq_char;
+   }
+}
+
+static double DateObs( const char *dateobs, int *status ) {
+/*
+*  Name:
+*     DateObs
+
+*  Purpose:
+*     Convert a FITS DATE-OBS keyword value to a MJD.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     double DateObs( const char *dateobs, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Extracts the date and time fields from the supplied string and converts
+*     them into a modified Julian Date. Supports both old "dd/mm/yy"
+*     format, and the new "ccyy-mm-ddThh:mm:ss[.sss...]" format.
+
+*  Parameters:
+*     dateobs
+*        Pointer to the DATE-OBS string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Modified Julian Date corresponding to the supplied DATE-OBS
+*     string.
+
+*  Notes:
+*     -  The value AST__BAD is returned (without error) if the supplied
+*     string does not conform to the requirements of a FITS DATE-OBS value,
+*     or if an error has already occurred.
+*/
+
+/* Local Variables: */
+   double days;               /* The hours, mins and secs as a fraction of a day */
+   double ret;                /* The returned MJD value */
+   double secs;               /* The total value of the two seconds fields */
+   int dd;                    /* The day field from the supplied string */
+   int fsc;                   /* The fractional seconds field from the supplied string */
+   int hr;                    /* The hour field from the supplied string */
+   int j;                     /* SLALIB status */
+   int len;                   /* The length of the supplied string */
+   int mm;                    /* The month field from the supplied string */
+   int mn;                    /* The minute field from the supplied string */
+   int nc;                    /* Number of characters used */
+   int ok;                    /* Was the string of a legal format? */
+   int rem;                   /* The least significant digit in fsc */
+   int sc;                    /* The whole seconds field from the supplied string */
+   int yy;                    /* The year field from the supplied string */
+
+/* Check the global status. */
+   if( !astOK ) return AST__BAD;
+
+/* Initialise the returned value. */
+   ret = AST__BAD;
+
+/* Save the length of the supplied string. */
+   len = (int) strlen( dateobs );
+
+/* Extract the year, month, day, hour, minute, second and fractional
+   seconds fields from the supplied string. Assume initially that the
+   string does not match any format. */
+   ok = 0;
+
+/* First check for the old "dd/mm/yy" format. */
+   if( nc = 0,
+        ( astSscanf( dateobs, " %2d/%2d/%d %n", &dd, &mm, &yy, &nc ) == 3 ) &&
+        ( nc >= len )  ){
+      ok = 1;
+      hr = 0;
+      mn = 0;
+      sc = 0;
+      fsc = 0;
+
+/* Otherwise, check for the new short format "ccyy-mm-dd". */
+   } else if( nc = 0,
+        ( astSscanf( dateobs, " %4d-%2d-%2d %n", &yy, &mm, &dd, &nc ) == 3 ) &&
+        ( nc >= len )  ){
+      ok = 1;
+      hr = 0;
+      mn = 0;
+      sc = 0;
+      fsc = 0;
+
+/* Otherwise, check for the new format "ccyy-mm-ddThh:mm:ss" without a
+   fractional seconds field or the trailing Z. */
+   } else if( nc = 0,
+        ( astSscanf( dateobs, " %4d-%2d-%2dT%2d:%2d:%2d %n", &yy, &mm, &dd,
+                  &hr, &mn, &sc, &nc ) == 6 ) && ( nc >= len )  ){
+      ok = 1;
+      fsc = 0;
+
+/* Otherwise, check for the new format "ccyy-mm-ddThh:mm:ss.sss" with a
+   fractional seconds field but without the trailing Z. */
+   } else if( nc = 0,
+        ( astSscanf( dateobs, " %4d-%2d-%2dT%2d:%2d:%2d.%d %n", &yy, &mm, &dd,
+                  &hr, &mn, &sc, &fsc, &nc ) == 7 ) && ( nc >= len )  ){
+      ok = 1;
+
+/* Otherwise, check for the new format "ccyy-mm-ddThh:mm:ssZ" without a
+   fractional seconds field but with the trailing Z. */
+   } else if( nc = 0,
+        ( astSscanf( dateobs, " %4d-%2d-%2dT%2d:%2d:%2dZ %n", &yy, &mm, &dd,
+                  &hr, &mn, &sc, &nc ) == 6 ) && ( nc >= len )  ){
+      ok = 1;
+      fsc = 0;
+
+/* Otherwise, check for the new format "ccyy-mm-ddThh:mm:ss.sssZ" with a
+   fractional seconds field and the trailing Z. */
+   } else if( nc = 0,
+        ( astSscanf( dateobs, " %4d-%2d-%2dT%2d:%2d:%2d.%dZ %n", &yy, &mm, &dd,
+                  &hr, &mn, &sc, &fsc, &nc ) == 7 ) && ( nc >= len )  ){
+      ok = 1;
+   }
+
+/* If the supplied string was legal, create a MJD from the separate fields. */
+   if( ok ) {
+
+/* Get the MJD at the start of the day. */
+      palCaldj( yy, mm, dd, &ret, &j );
+
+/* If succesful, convert the hours, minutes and seconds to a fraction of
+    a day, and add it onto the MJD found above. */
+      if( j == 0 ) {
+
+/* Obtain a floating point representation of the fractional seconds
+   field. */
+         secs = 0.0;
+         while ( fsc > 0 ) {
+             rem = ( fsc % 10  );
+             fsc /= 10;
+             secs = 0.1 * ( secs + (double) rem );
+         }
+
+/* Add on the whole seconds field. */
+         secs += (double) sc;
+
+/*Convert the hours, minutes and seconds to a fractional day. */
+         palDtf2d( hr, mn, secs, &days, &j );
+
+/* If succesful, add this onto the returned MJD. */
+         if( j == 0 ) {
+            ret = ret + days;
+
+/* If the conversion to MJD failed, return AST__BAD. */
+         } else {
+            ret = AST__BAD;
+         }
+      } else {
+         ret = AST__BAD;
+      }
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static void DeleteCard( AstFitsChan *this, const char *method,
+                        const char *class, int *status ){
+/*
+*  Name:
+*     DeleteCard
+
+*  Purpose:
+*     Delete the current card from a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void DeleteCard( AstFitsChan *this, const char *method,
+*                      const char *class )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The current card is removed from the circular linked list of structures
+*     stored in the supplied FitsChan, and the memory used to store the
+*     structure is then freed.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan containing the list.
+*     method
+*        Name of calling method.
+*     class
+*        Object class.
+
+*  Notes:
+*     -  This function returns without action if the FitsChan is
+*     currently at "end-of-file".
+*     -  The next card becomes the current card.
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   FitsCard *card;            /* Pointer to the current card */
+   FitsCard *next;            /* Pointer to next card in list */
+   FitsCard *prev;            /* Pointer to previous card in list */
+
+/* Return if the supplied object or current card is NULL. */
+   if( !this || !this->card ) return;
+
+/* Get a pointer to the card to be deleted (the current card). */
+   card = (FitsCard *) this->card;
+
+/* Remove it from the KeyMap holding all keywords. */
+   astMapRemove( this->keywords, card->name );
+
+/* Move the current card on to the next card. */
+   MoveCard( this, 1, method, class, status );
+
+/* Save pointers to the previous and next cards in the list. */
+   prev = GetLink( card, PREVIOUS, method, class, status );
+   next = GetLink( card, NEXT, method, class, status );
+
+/* If the backwards link points back to the supplied card, then it must
+   be the only one left on the list. */
+   if( prev == card ) prev = NULL;
+   if( next == card ) next = NULL;
+
+/* If the list head is to be deleted, store a value for the new list
+   head. */
+   if( this->head == (void *) card ) this->head = (void *) next;
+
+/* Free the memory used to hold the data value. */
+   (void) astFree( card->data );
+
+/* Free the memory used to hold any comment. */
+   if( card->comment ) (void) astFree( (void *) card->comment );
+
+/* Free the memory used to hold the whole structure. */
+   (void) astFree( (void *) card );
+
+/* Fix up the links between the two adjacent cards in the list, unless the
+   supplied card was the last one in the list. */
+   if( prev && next ){
+      next->prev = prev;
+      prev->next = next;
+   } else {
+      this->head = NULL;
+      this->card = NULL;
+   }
+
+/* Return. */
+   return;
+}
+
+static void DelFits( AstFitsChan *this, int *status ){
+
+/*
+*++
+*  Name:
+c     astDelFits
+f     AST_DELFITS
+
+*  Purpose:
+*     Delete the current FITS card in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+c     void astDelFits( AstFitsChan *this )
+f     CALL AST_DELFITS( THIS, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function deletes the current FITS card from a FitsChan. The
+f     This routine deletes the current FITS card from a FitsChan. The
+*     current card may be selected using the Card attribute (if its index
+c     is known) or by using astFindFits (if only the FITS keyword is
+f     is known) or by using AST_FINDFITS (if only the FITS keyword is
+*     known).
+*
+*     After deletion, the following card becomes the current card.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - This function returns without action if the FitsChan is
+*     initially positioned at the "end-of-file" (i.e. if the Card
+*     attribute exceeds the number of cards in the FitsChan).
+*     - If there are no subsequent cards in the FitsChan, then the
+*     Card attribute is left pointing at the "end-of-file" after
+*     deletion (i.e. is set to one more than the number of cards in
+*     the FitsChan).
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Delete the current card. The next card will be made the current card. */
+   DeleteCard( this, "astDelFits", astGetClass( this ), status );
+}
+
+static void DistortMaps( AstFitsChan *this, FitsStore *store, char s,
+                         int naxes, AstMapping **map1, AstMapping **map2,
+                         AstMapping **map3, AstMapping **map4,
+                         const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     DistortMap
+
+*  Purpose:
+*     Create a Mapping representing a FITS-WCS Paper IV distortion code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void DistortMaps( AstFitsChan *this, FitsStore *store, char s,
+*                       int naxes, AstMapping **map1, AstMapping **map2,
+*                       AstMapping **map3, AstMapping **map4,
+*                       const char *method, const char *class )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function checks the CTYPE keywords in the supplied FitsStore to see
+*     if they contain a known distortion code (following the syntax described
+*     in FITS-WCS paper IV). If so, Mappings are returned which represent the
+*     distortions to be applied at each stage in the pixel->IWC chain. If
+*     any distortion codes are found in the FitsStore CTYPE values, whether
+*     recognised or not, the CTYPE values in the FitsStore are modified to
+*     remove the distortion code. Warnings about any unknown or inappropriate
+*     distortion codes are added to the FitsChan.
+
+*  Parameters:
+*     this
+*        The FitsChan. ASTWARN cards may be added to this FitsChan if any
+*        anomalies are found in the keyword values in the FitsStore.
+*     store
+*        A structure containing information about the requested axis
+*        descriptions derived from a FITS header.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     naxes
+*        The number of intermediate world coordinate axes (WCSAXES).
+*     map1
+*        Address of a location at which to store a pointer to a Mapping
+*        which describes any distortion to be applied to pixel
+*        coordinates, prior to performing the translation specified by the
+*        CRPIXj keywords. NULL is returned if no distortion is necessary.
+*     map2
+*        Address of a location at which to store a pointer to a Mapping
+*        which describes any distortion to be applied to translated pixel
+*        coordinates, prior to performing the PC matrix multiplication.
+*        NULL is returned if no distortion is necessary.
+*     map3
+*        Address of a location at which to store a pointer to a Mapping
+*        which describes any distortion to be applied to unscaled IWC
+*        coordinates, prior to performing the CDELT matrix multiplication.
+*        NULL is returned if no distortion is necessary.
+*     map4
+*        Address of a location at which to store a pointer to a Mapping
+*        which describes any distortion to be applied to scaled IWC
+*        coordinates, after performing the CDELT matrix multiplication.
+*        NULL is returned if no distortion is necessary.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*/
+
+/* Local Variables: */
+   AstMapping *tmap1;        /* Mapping pointer */
+   AstMapping *tmap2;        /* Mapping pointer */
+   char *ctype;              /* Pointer to CTYPE value */
+   char code[ 4 ];           /* Projection code extracted from CTYPE */
+   char dist[ 4 ];           /* Distortion code extracted from CTYPE */
+   char msgbuf[ 250 ];       /* Buffer for warning message */
+   char type[ 5 ];           /* Axis type extracted from CTYPE */
+   double *dim;              /* Array holding array dimensions */
+   int found_axes[ 2 ];      /* Index of axes with the distortion code */
+   int i;                    /* FITS axis index */
+   int nc;                   /* No. of characters in CTYPE without "-SIP" */
+   int nfound;               /* No. of axes with the distortion code */
+   int warned;               /* Have any ASTWARN cards been issued? */
+
+/* Initialise pointers to the returned Mappings. */
+   *map1 = NULL;
+   *map2 = NULL;
+   *map3 = NULL;
+   *map4 = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return;
+
+/* Allocate memory to hold the image dimensions. */
+   dim = (double *) astMalloc( sizeof(double)*naxes );
+   if( dim ){
+
+/* Note the image dimensions, if known. If not, store AST__BAD values. */
+      for( i = 0; i < naxes; i++ ){
+         if( !astGetFitsF( this, FormatKey( "NAXIS", i + 1, -1, ' ', status ),
+                           dim + i ) ) dim[ i ] = AST__BAD;
+      }
+
+/* First check each known distortion type... */
+
+/* "-SIP": Spitzer (http://irsa.ipac.caltech.edu/data/SPITZER/docs/files/spitzer/shupeADASS.pdf)
+   ============= */
+
+/* Spitzer distortion is limited to 2D. Check the first two axes to see if
+   they have "-SIP" codes at the end of their CTYPE values. If they do,
+   terminate the ctype string in order to exclude the distortion code (this
+   is so that later functions do not need to allow for the possibility of a
+   distortion code being present in the CTYPE value). */
+      ctype = GetItemC( &(store->ctype), 0, 0, s, NULL, method, class, status );
+      if( ctype ){
+         nc = astChrLen( ctype ) - 4;
+         if( nc >= 0 && !strcmp( ctype + nc, "-SIP" ) ) {
+            ctype[ nc ] = 0;
+            ctype = GetItemC( &(store->ctype), 1, 0, s, NULL, method, class, status );
+            if( ctype ) {
+               nc = astChrLen( ctype ) - 4;
+               if( nc >= 0 && !strcmp( ctype + nc, "-SIP" ) ) {
+                  ctype[ nc ] = 0;
+
+/* Create a Mapping describing the distortion (other axes are passed
+   unchanged by this Mapping), and add it in series with the returned map2
+   (Spitzer distortion is applied to the translated pixel coordinates). */
+                  tmap1 = SIPMapping( dim, store, s, naxes, method, class, status );
+                  if( ! *map2 ) {
+                     *map2 = tmap1;
+                  } else {
+                     tmap2 = (AstMapping *) astCmpMap( *map2, tmap1, 1, "", status );
+                     *map2 = astAnnul( *map2 );
+                     tmap1 = astAnnul( tmap1 );
+                     *map2 = tmap2;
+                  }
+               }
+            }
+         }
+      }
+
+/* Check that the "-SIP" code is not included in any axes other than axes
+   0 and 1. Issue a warning if it is, and remove it. */
+      warned = 0;
+      for( i = 2; i < naxes; i++ ){
+         ctype = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+         if( ctype ){
+            nc = astChrLen( ctype ) - 4;
+            if( nc >= 0 && !strcmp( ctype + nc, "-SIP" ) ) {
+               if( !warned ){
+                  warned = 1;
+                  sprintf( msgbuf, "The \"-SIP\" distortion code can only be "
+                           "used on axes 1 and 2, but was found in keyword "
+                           "%s (='%s'). The distortion will be ignored.",
+                           FormatKey( "CTYPE", i + 1, -1, ' ', status ),  ctype );
+                  Warn( this, "distortion", msgbuf, method, class, status );
+               }
+               ctype[ nc ] = 0;
+            }
+         }
+      }
+
+/* "-ZPX": IRAF (http://iraf.noao.edu/projects/ccdmosaic/zpx.html)
+   ============= */
+
+/* An IRAF ZPX header uses a ZPX projection within each CTYPE value in place
+   of the basic ZPN projection. The SpecTrans function converts -ZPX" to
+   "-ZPN-ZPX" (i.e. a basic projection of ZPN with a distortion code of
+   "-ZPX"). This function then traps and processes the "-ZPX" distortion
+   code. */
+
+/* Look for axes that have the "-ZPX" code in their CTYPE values. If any
+   are found, check that there are exactly two such axes, and terminate the
+   ctype strings in order to exclude the distortion code (this is so that
+   later functions do not need to allow for the possibility of a distortion
+   code  being present in the CTYPE value)*/
+      nfound = 0;
+      for( i = 0; i < naxes; i++ ){
+         ctype = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+         if( ctype && 3 == astSscanf( ctype, "%4s-%3s-%3s", type, code, dist ) ){
+            if( !strcmp( "ZPX", dist ) ){
+               if( nfound < 2 ) found_axes[ nfound ] = i;
+               nfound++;
+               ctype[ 8 ] = 0;
+            }
+         }
+      }
+
+/* Issue a warning if more than two ZPX axes were found. */
+      if( nfound > 2 ) {
+         Warn( this, "distortion", "More than two axes were found "
+               "with the \"-ZPX\" projection code. A ZPN projection "
+               "will be used instead.", method, class, status );
+
+/* Otherwise, create a Mapping describing the distortion (other axes are passed
+   unchanged by this Mapping), and add it in series with the returned map4
+   (ZPX distortion is applied to the translated, rotated, scaled IWC
+   coordinates). */
+      } else if( nfound == 2 ){
+         tmap1 = ZPXMapping( this, store, s, naxes,  found_axes, method,
+                             class, status );
+         if( ! *map4 ) {
+            *map4 = tmap1;
+         } else {
+            tmap2 = (AstMapping *) astCmpMap( *map4, tmap1, 1, "", status );
+            *map4 = astAnnul( *map4 );
+            tmap1 = astAnnul( tmap1 );
+            *map4 = tmap2;
+         }
+      }
+
+/* (There are currently no other supported distortion codes.) */
+
+/* Finally, check all axes looking for any remaining (and therefore
+   unsupported) distortion codes. Issue a warning about them and remove
+   them.
+   =================================================================== */
+
+/* Indicate that we have not yet issued a warning. */
+      warned = 0;
+
+/* Do each IWC axis. */
+      for( i = 0; i < naxes; i++ ){
+
+/* Get the CTYPE value for this axis. */
+         ctype = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+         if( ctype ) {
+
+/* See if has the "4-3-3" form described in FITS-WCS paper IV. */
+            if( 3 == astSscanf( ctype, "%4s-%3s-%3s", type, code, dist ) ){
+
+/* Add an ASTWARN card to the FitsChan. Only issue one warning (this avoids
+   multiple warnings about the same distortion code in multiple CTYPE values). */
+               if( !warned ){
+                  warned = 1;
+                  sprintf( msgbuf, "The header contains CTYPE values (e.g. "
+                           "%s = '%s') which "
+                           "include a distortion code \"-%s\". AST "
+                           "currently ignores this distortion. The code "
+                           "has been removed from the CTYPE values.",
+                           FormatKey( "CTYPE", i + 1, -1, ' ', status ),  ctype, dist );
+                  Warn( this, "distortion", msgbuf, method, class, status );
+               }
+
+/* Terminate the CTYPE value in the FitsStore in order to exclude the distortion
+   code. This means that later functions will not need to take account of
+   distortion codes. */
+               ctype[ 8 ] = 0;
+            }
+         }
+      }
+   }
+
+/* Free resources. */
+   dim = astFree( dim );
+}
+
+static void DSBSetUp( AstFitsChan *this, FitsStore *store,
+                      AstDSBSpecFrame *dsb, char s, double crval,
+                      const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     DSBSetUp
+
+*  Purpose:
+*     Modify an AstDSBSpecFrame object to reflect the contents of a FitsStore.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     void DSBSetUp( AstFitsChan *this, FitsStore *store,
+*                    AstDSBSpecFrame *dsb, char s, double crval,
+*                    const char *method, const char *class, int *status  )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function sets the attributes of the supplied DSBSpecFrame to
+*     reflect the values in the supplied FitsStore.
+
+*  Parameters:
+*     this
+*        The FitsChan.
+*     store
+*        A structure containing information about the requested axis
+*        descriptions derived from a FITS header.
+*     dsb
+*        Pointer to the DSBSpecFrame.
+*     s
+*        Alternate axis code.
+*     crval
+*        The spectral CRVAL value, in the spectral system represented by
+*        the supplied DSBSPecFrame.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This implementation follows the conventions of the FITS-CLASS encoding.
+*/
+
+/* Local Variables: */
+   AstDSBSpecFrame *dsb_src; /* New DSBSpecFrame in which StdOfRest is source */
+   AstDSBSpecFrame *dsb_topo;/* New DSBSpecFrame in which StdOfRest is topo */
+   AstFrameSet *fs;        /* FrameSet connecting two standards of rest */
+   double dsbcentre;       /* Topocentric reference (CRVAL) frequency */
+   double in[2];           /* Source rest and image frequencies */
+   double lo;              /* Topocentric Local Oscillator frequency */
+   double out[2];          /* Topocentric rest and image frequencies */
+
+/* Check the global status. */
+   if ( !astOK ) return;
+
+/* In order to determine the topocentric IF, we need the topocentric
+   frequencies corresponding to the RESTFREQ and IMAGFREQ values in the
+   FITS header. The values stored in the FITS header are measured in Hz,
+   in the source's rest frame, so we need a mapping from frequency in the
+   source rest frame to topocentric frequency. Take a copy of the supplied
+   DSBSpecFrame and then set its attributes to represent frequency in the
+   sources rest frame. */
+   dsb_src = astCopy( dsb );
+   astSetStdOfRest( dsb_src, AST__SCSOR );
+   astSetSystem( dsb_src, AST__FREQ );
+   astSetUnit( dsb_src, 0, "Hz" );
+
+/* Take a copy of this DSBSpecFrame and set its standard of rest to
+   topocentric. */
+   dsb_topo = astCopy( dsb_src );
+   astSetStdOfRest( dsb_topo, AST__TPSOR );
+
+/* Now get the Mapping between these. */
+   fs = astConvert( dsb_src, dsb_topo, "" );
+   dsb_src = astAnnul( dsb_src );
+   dsb_topo = astAnnul( dsb_topo );
+
+/* Check a conversion was found. */
+   if( fs != NULL ) {
+
+/* Use this Mapping to transform the rest frequency and the image
+   frequency from the standard of rest of the source to that of the
+   observer. */
+      in[ 0 ] = astGetRestFreq( dsb );
+      in[ 1 ] = GetItem( &(store->imagfreq), 0, 0, s, NULL, method, class, status );
+      astTran1( fs, 2, in, 1, out );
+
+/* The intermediate frequency is half the distance between these two
+   frequencies. Note, the IF value is signed so as to put the rest
+   frequency in the observed sideband. */
+      if( out[ 0 ] != AST__BAD && out[ 1 ] != AST__BAD ) {
+
+/* Store the spectral CRVAL value as the centre frequency of the
+   DSBSpecFrame. The public astSetD method interprets the supplied value
+   as a value in the spectral system described by the other SpecFrame
+   attributes. */
+         astSetD( dsb, "DSBCentre", crval );
+
+/* To calculate the topocentric IF we need the topocentric frequency
+   equivalent of CRVAL. So take a copy of the DSBSpecFrame, then set it to
+   represent topocentric frequency, and read back the DSBCentre value. */
+         dsb_topo = astCopy( dsb );
+         astSetStdOfRest( dsb_topo, AST__TPSOR );
+         astSetSystem( dsb_topo, AST__FREQ );
+         astSetUnit( dsb_topo, 0, "Hz" );
+         dsbcentre = astGetD( dsb_topo, "DSBCentre" );
+         dsb_topo = astAnnul( dsb_topo );
+
+/* We also need the topocentric Local Oscillator frequency. This is
+   assumed to be half way between the topocentric IMAGFREQ and RESTFREQ
+   values. */
+         lo = 0.5*( out[ 1 ] + out[ 0 ] );
+
+/* Set the IF to be the difference between the Local Oscillator frequency
+   and the CRVAL frequency. */
+         astSetIF( dsb, lo - dsbcentre );
+
+/* Set the DSBSpecFrame to represent the observed sideband */
+         astSetC( dsb, "SideBand", "observed" );
+      }
+
+/* Free resources. */
+      fs = astAnnul( fs );
+   }
+}
+
+static int DSSFromStore( AstFitsChan *this, FitsStore *store,
+                         const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     DSSFromStore
+
+*  Purpose:
+*     Store WCS keywords in a FitsChan using DSS encoding.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     int DSSFromStore( AstFitsChan *this, FitsStore *store,
+*                       const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function copies the WCS information stored in the supplied
+*     FitsStore into the supplied FitsChan, using DSS encoding.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        Pointer to the FitsStore.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if succesfull, and zero is returned
+*     otherwise.
+*/
+
+/* Local Variables: */
+   const char *comm;   /* Pointer to comment string */
+   char *cval;         /* Pointer to string keyword value */
+   const char *pltdecsn;/* PLTDECSN keyword value */
+   double amdx[20];    /* AMDXi keyword value */
+   double amdy[20];    /* AMDYi keyword value */
+   double cdelt;       /* CDELT element */
+   double cnpix1;      /* CNPIX1 keyword value */
+   double cnpix2;      /* CNPIX2 keyword value */
+   double pc;          /* PC element */
+   double pltdecd;     /* PLTDECD keyword value */
+   double pltdecm;     /* PLTDECM keyword value */
+   double pltdecs;     /* PLTDECS keyword value */
+   double pltrah;      /* PLTRAH keyword value */
+   double pltram;      /* PLTRAM keyword value */
+   double pltras;      /* PLTRAS keyword value */
+   double pltscl;      /* PLTSCL keyword value */
+   double ppo1;        /* PPO1 keyword value */
+   double ppo2;        /* PPO2 keyword value */
+   double ppo3;        /* PPO3 keyword value */
+   double ppo4;        /* PPO4 keyword value */
+   double ppo5;        /* PPO5 keyword value */
+   double ppo6;        /* PPO6 keyword value */
+   double pvx[22];     /* X projection parameter values */
+   double pvy[22];     /* Y projection parameter values */
+   double val;         /* General purpose value */
+   double xpixelsz;    /* XPIXELSZ keyword value */
+   double ypixelsz;    /* YPIXELSZ keyword value */
+   int i;              /* Loop count */
+   int gottpn;         /* Is the projection a "TPN" projection? */
+   int m;              /* Parameter index */
+   int ret;            /* Returned value. */
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Check the image is 2 dimensional. */
+   if( GetMaxJM( &(store->crpix), ' ', status ) != 1 ) return ret;
+
+/* Check the first axis is RA with a TAN or TPN projection. */
+   cval = GetItemC( &(store->ctype), 0, 0, ' ', NULL, method, class, status );
+   if( !cval ) return ret;
+   gottpn = !strcmp( "RA---TPN", cval );
+   if( strcmp( "RA---TAN", cval ) && !gottpn ) return ret;
+
+/* Check the second axis is DEC with a TAN or TPN projection. */
+   cval = GetItemC( &(store->ctype), 1, 0, ' ', NULL, method, class, status );
+   if( !cval ) return ret;
+   if( gottpn ) {
+      if( strcmp( "DEC--TPN", cval ) ) return ret;
+   } else {
+      if( strcmp( "DEC--TAN", cval ) ) return ret;
+   }
+
+/* Check that LONPOLE is undefined or is 180 degrees. */
+   val = GetItem( &(store->lonpole), 0, 0, ' ', NULL, method, class, status );
+   if( val != AST__BAD && val != 180.0 ) return ret;
+
+/* Check that the RA/DEC system is FK5. */
+   cval = GetItemC( &(store->radesys), 0, 0, ' ', NULL, method, class, status );
+   if( !cval || strcmp( "FK5", cval ) ) return ret;
+
+/* Check that equinox is not defined or is 2000.0 */
+   val = GetItem( &(store->equinox), 0, 0, ' ', NULL, method, class, status );
+   if( val != AST__BAD && val != 2000.0 ) return ret;
+
+/* Get the pixel sizes from the PC/CDELT keywords. They must be defined and
+   not be zero.  */
+   cdelt = GetItem( &(store->cdelt), 0, 0, ' ', NULL, method, class, status );
+   if( cdelt == AST__BAD ) return ret;
+   pc = GetItem( &(store->pc), 0, 0, ' ', NULL, method, class, status );
+   if( pc == AST__BAD ) pc = 1.0;
+   xpixelsz = cdelt*pc;
+   cdelt = GetItem( &(store->cdelt), 1, 0, ' ', NULL, method, class, status );
+   if( cdelt == AST__BAD ) return ret;
+   pc = GetItem( &(store->pc), 1, 1, ' ', NULL, method, class, status );
+   if( pc == AST__BAD ) pc = 1.0;
+   ypixelsz = cdelt*pc;
+   if( xpixelsz == 0.0 || ypixelsz == 0.0 ) return ret;
+   xpixelsz *= -1000.0;
+   ypixelsz *= 1000.0;
+
+/* Check the off-diagonal PC terms are zero. DSS does not allow any rotation. */
+   val = GetItem( &(store->pc), 0, 1, ' ', NULL, method, class, status );
+   if( val != AST__BAD && val != 0.0 ) return ret;
+   val = GetItem( &(store->pc), 1, 0, ' ', NULL, method, class, status );
+   if( val != AST__BAD && val != 0.0 ) return ret;
+
+/* Get the required projection parameter values from the store, supplying
+   appropriate values if a simple TAN projection is being used. */
+   for( m = 0; m < 22; m++ ){
+      pvx[ m ] = GetItem( &(store->pv), 0, m, ' ', NULL, method, class, status );
+      if( pvx[ m ] == AST__BAD || !gottpn ) pvx[ m ] = ( m == 1 ) ? 1.0 : 0.0;
+      pvy[ m ] = GetItem( &(store->pv), 1, m, ' ', NULL, method, class, status );
+      if( pvy[ m ] == AST__BAD || !gottpn ) pvy[ m ] = ( m == 1 ) ? 1.0 : 0.0;
+   }
+
+/* Check that no other projection parameters have been set. */
+   if( GetMaxJM( &(store->pv), ' ', status ) > 21 ) return ret;
+
+/* Check that specific parameters take their required zero value. */
+   if( pvx[ 3 ] != 0.0 || pvy[ 3 ] != 0.0 ) return ret;
+   for( m = 11; m < 17; m++ ){
+      if( pvx[ m ] != 0.0 || pvy[ m ] != 0.0 ) return ret;
+   }
+   if( pvx[ 18 ] != 0.0 || pvy[ 18 ] != 0.0 ) return ret;
+   if( pvx[ 20 ] != 0.0 || pvy[ 20 ] != 0.0 ) return ret;
+
+/* Check that other projection parameters are related correctly. */
+   if( !astEQUAL( 2*pvx[ 17 ], pvx[ 19 ] ) ) return ret;
+   if( !astEQUAL( pvx[ 17 ], pvx[ 21 ] ) ) return ret;
+   if( !astEQUAL( 2*pvy[ 17 ], pvy[ 19 ] ) ) return ret;
+   if( !astEQUAL( pvy[ 17 ], pvy[ 21 ] ) ) return ret;
+
+/* Initialise all polynomial co-efficients to zero. */
+   for( m = 0; m < 20; m++ ){
+      amdx[ m ] = 0.0;
+      amdy[ m ] = 0.0;
+   }
+
+/* Polynomial co-efficients. There is redundancy here too, so we
+   arbitrarily choose to leave AMDX/Y7 and AMDX/Y12 set to zero.  */
+   amdx[ 0 ] = 3600.0*pvx[ 1 ];
+   amdx[ 1 ] = 3600.0*pvx[ 2 ];
+   amdx[ 2 ] = 3600.0*pvx[ 0 ];
+   amdx[ 3 ] = 3600.0*pvx[ 4 ];
+   amdx[ 4 ] = 3600.0*pvx[ 5 ];
+   amdx[ 5 ] = 3600.0*pvx[ 6 ];
+   amdx[ 7 ] = 3600.0*pvx[ 7 ];
+   amdx[ 8 ] = 3600.0*pvx[ 8 ];
+   amdx[ 9 ] = 3600.0*pvx[ 9 ];
+   amdx[ 10 ] = 3600.0*pvx[ 10 ];
+   amdx[ 12 ] = 3600.0*pvx[ 17 ];
+   amdy[ 0 ] = 3600.0*pvy[ 1 ];
+   amdy[ 1 ] = 3600.0*pvy[ 2 ];
+   amdy[ 2 ] = 3600.0*pvy[ 0 ];
+   amdy[ 3 ] = 3600.0*pvy[ 4 ];
+   amdy[ 4 ] = 3600.0*pvy[ 5 ];
+   amdy[ 5 ] = 3600.0*pvy[ 6 ];
+   amdy[ 7 ] = 3600.0*pvy[ 7 ];
+   amdy[ 8 ] = 3600.0*pvy[ 8 ];
+   amdy[ 9 ] = 3600.0*pvy[ 9 ];
+   amdy[ 10 ] = 3600.0*pvy[ 10 ];
+   amdy[ 12 ] = 3600.0*pvy[ 17 ];
+
+/* The plate scale is the mean of the first X and Y co-efficients. */
+   pltscl = 0.5*( amdx[ 0 ] + amdy[ 0 ] );
+
+/* There is redundancy in the DSS encoding. We can choose an arbitrary
+   pixel corner (CNPIX1, CNPIX2) so long as we use the corresponding origin
+   for the cartesian co-ordinate system in which the plate centre is
+   specified (PPO3, PPO6). Arbitrarily set CNPIX1 and CNPIX2 to one. */
+   cnpix1 = 1.0;
+   cnpix2 = 1.0;
+
+/* Find the corresponding plate centre PPO3 and PPO6 (other co-efficients
+   are set to zero). */
+   ppo1 = 0.0;
+   ppo2 = 0.0;
+   val = GetItem( &(store->crpix), 0, 0, ' ', NULL, method, class, status );
+   if( val == AST__BAD ) return ret;
+   ppo3 = xpixelsz*( val + cnpix1 - 0.5 );
+   ppo4 = 0.0;
+   ppo5 = 0.0;
+   val = GetItem( &(store->crpix), 0, 1, ' ', NULL, method, class, status );
+   if( val == AST__BAD ) return ret;
+   ppo6 = ypixelsz*( val + cnpix2 - 0.5 );
+
+/* The reference RA. Get it in degrees. */
+   val = GetItem( &(store->crval), 0, 0, ' ', NULL, method, class, status );
+   if( val == AST__BAD ) return ret;
+
+/* Convert to hours and ensure it is in the range 0 to 24 */
+   val /= 15.0;
+   while( val < 0 ) val += 24.0;
+   while( val >= 24.0 ) val -= 24.0;
+
+/* Split into hours, mins and seconds. */
+   pltrah = (int) val;
+   val = 60.0*( val - pltrah );
+   pltram = (int) val;
+   pltras = 60.0*( val - pltram );
+
+/* The reference DEC. Get it in degrees. */
+   val = GetItem( &(store->crval), 1, 0, ' ', NULL, method, class, status );
+   if( val == AST__BAD ) return ret;
+
+/* Ensure it is in the range -180 to +180 */
+   while( val < -180.0 ) val += 360.0;
+   while( val >= 180.0 ) val -= 360.0;
+
+/* Save the sign. */
+   if( val > 0.0 ){
+      pltdecsn = "+";
+   } else {
+      pltdecsn = "-";
+      val = -val;
+   }
+
+/* Split into degrees, mins and seconds. */
+   pltdecd = (int) val;
+   val = 60.0*( val - pltdecd );
+   pltdecm = (int) val;
+   pltdecs = 60.0*( val - pltdecm );
+
+/* Store the DSS keywords in the FitsChan. */
+   SetValue( this, "CNPIX1", &cnpix1, AST__FLOAT, "X corner (pixels)", status );
+   SetValue( this, "CNPIX2", &cnpix2, AST__FLOAT, "Y corner (pixels)", status );
+   SetValue( this, "PPO1", &ppo1, AST__FLOAT, "Orientation co-efficients", status );
+   SetValue( this, "PPO2", &ppo2, AST__FLOAT, "", status );
+   SetValue( this, "PPO3", &ppo3, AST__FLOAT, "", status );
+   SetValue( this, "PPO4", &ppo4, AST__FLOAT, "", status );
+   SetValue( this, "PPO5", &ppo5, AST__FLOAT, "", status );
+   SetValue( this, "PPO6", &ppo6, AST__FLOAT, "", status );
+   SetValue( this, "XPIXELSZ", &xpixelsz, AST__FLOAT, "X pixel size (microns)", status );
+   SetValue( this, "YPIXELSZ", &ypixelsz, AST__FLOAT, "Y pixel size (microns)", status );
+   SetValue( this, "PLTRAH", &pltrah, AST__FLOAT, "RA at plate centre", status );
+   SetValue( this, "PLTRAM", &pltram, AST__FLOAT, "", status );
+   SetValue( this, "PLTRAS", &pltras, AST__FLOAT, "", status );
+   SetValue( this, "PLTDECD", &pltdecd, AST__FLOAT, "DEC at plate centre", status );
+   SetValue( this, "PLTDECM", &pltdecm, AST__FLOAT, "", status );
+   SetValue( this, "PLTDECS", &pltdecs, AST__FLOAT, "", status );
+   SetValue( this, "PLTDECSN", &pltdecsn, AST__STRING, "", status );
+   SetValue( this, "PLTSCALE", &pltscl, AST__FLOAT, "Plate scale (arcsec/mm)", status );
+   comm = "Plate solution x co-efficients";
+   for( i = 0; i < 20; i++ ){
+      SetValue( this, FormatKey( "AMDX", i + 1, -1, ' ', status ), amdx + i,
+                AST__FLOAT, comm, status );
+      comm = NULL;
+   }
+   comm = "Plate solution y co-efficients";
+   for( i = 0; i < 20; i++ ){
+      SetValue( this, FormatKey( "AMDY", i + 1, -1, ' ', status ), amdy + i,
+                AST__FLOAT, comm, status );
+      comm = NULL;
+   }
+
+/* If no error has occurred, return one. */
+   if( astOK ) ret = 1;
+
+/* Return the answer. */
+   return ret;
+}
+
+static void DSSToStore( AstFitsChan *this, FitsStore *store,
+                        const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     DSSToStore
+
+*  Purpose:
+*     Extract WCS information from the supplied FitsChan using a DSS
+*     encoding, and store it in the supplied FitsStore.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void DSSToStore( AstFitsChan *this, FitsStore *store,
+                       const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function extracts DSS keywords from the supplied FitsChan, and
+*     stores the corresponding WCS information in the supplied FitsStore.
+*     The conversion from DSS encoding to standard WCS encoding is
+*     described in an ear;y draft of the Calabretta & Greisen paper
+*     "Representations of celestial coordinates in FITS" (A&A, in prep.),
+*     and uses the now deprecated "TAN with polynomial corrections",
+*     which is still supported by the WcsMap class as type AST__TPN.
+*     Here we use "lambda=1" (i.e. plate co-ordinate are measured in mm,
+*     not degrees).
+*
+*     It is assumed that DSS images are 2 dimensional.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        Pointer to the FitsStore structure.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   char *text;         /* Pointer to textual keyword value */
+   char pltdecsn[11];  /* First 10 non-blank characters from PLTDECSN keyword */
+   char keyname[10];   /* Buffer for keyword name */
+   double amdx[20];    /* AMDXi keyword value */
+   double amdy[20];    /* AMDYi keyword value */
+   double cnpix1;      /* CNPIX1 keyword value */
+   double cnpix2;      /* CNPIX2 keyword value */
+   double crval2;      /* Equivalent CRVAL2 keyword value */
+   double dummy;       /* Unused keyword value */
+   double pltdecd;     /* PLTDECD keyword value */
+   double pltdecm;     /* PLTDECM keyword value */
+   double pltdecs;     /* PLTDECS keyword value */
+   double pltrah;      /* PLTRAH keyword value */
+   double pltram;      /* PLTRAM keyword value */
+   double pltras;      /* PLTRAS keyword value */
+   double ppo3;        /* PPO3 keyword value */
+   double ppo6;        /* PPO6 keyword value */
+   double pv;          /* Projection parameter value */
+   double xpixelsz;    /* XPIXELSZ keyword value */
+   double ypixelsz;    /* YPIXELSZ keyword value */
+   int i;              /* Loop count */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Get the optional DSS keywords, supplying defaults for any missing keywords. */
+   cnpix1 = 0.0;
+   cnpix2 = 0.0;
+   GetValue( this, "CNPIX1", AST__FLOAT, &cnpix1, 0, 1, method, class, status );
+   GetValue( this, "CNPIX2", AST__FLOAT, &cnpix2, 0, 1, method, class, status );
+
+/* Get the required DSS keywords. Report an error if any are missing. */
+   GetValue( this, "PPO3", AST__FLOAT, &ppo3, 1, 1, method, class, status );
+   GetValue( this, "PPO6", AST__FLOAT, &ppo6, 1, 1, method, class, status );
+   GetValue( this, "XPIXELSZ", AST__FLOAT, &xpixelsz, 1, 1, method, class, status );
+   GetValue( this, "YPIXELSZ", AST__FLOAT, &ypixelsz, 1, 1, method, class, status );
+   GetValue( this, "PLTRAH", AST__FLOAT, &pltrah, 1, 1, method, class, status );
+   GetValue( this, "PLTRAM", AST__FLOAT, &pltram, 1, 1, method, class, status );
+   GetValue( this, "PLTRAS", AST__FLOAT, &pltras, 1, 1, method, class, status );
+   GetValue( this, "PLTDECD", AST__FLOAT, &pltdecd, 1, 1, method, class, status );
+   GetValue( this, "PLTDECM", AST__FLOAT, &pltdecm, 1, 1, method, class, status );
+   GetValue( this, "PLTDECS", AST__FLOAT, &pltdecs, 1, 1, method, class, status );
+
+/* Copy the first 10 non-blank characters from the PLTDECSN keyword. */
+   GetValue( this, "PLTDECSN", AST__STRING, &text, 1, 1, method, class, status );
+   if( astOK ) {
+      text += strspn( text, " " );
+      text[ strcspn( text, " " ) ] = 0;
+      strncpy( pltdecsn, text, 10 );
+   }
+
+/* Read other related keywords. We do not need these, but we read them
+   so that they are not propagated to any output FITS file. */
+   GetValue( this, "PLTSCALE", AST__FLOAT, &dummy, 0, 1, method, class, status );
+   GetValue( this, "PPO1", AST__FLOAT, &dummy, 0, 1, method, class, status );
+   GetValue( this, "PPO2", AST__FLOAT, &dummy, 0, 1, method, class, status );
+   GetValue( this, "PPO4", AST__FLOAT, &dummy, 0, 1, method, class, status );
+   GetValue( this, "PPO5", AST__FLOAT, &dummy, 0, 1, method, class, status );
+
+/* Get the polynomial co-efficients. These can be defaulted if they are
+   missing, so do not report an error. */
+   for( i = 0; i < 20; i++ ){
+      (void) sprintf( keyname, "AMDX%d", i + 1 );
+      amdx[i] = AST__BAD;
+      GetValue( this, keyname, AST__FLOAT, amdx + i, 0, 1, method, class, status );
+      (void) sprintf( keyname, "AMDY%d", i + 1 );
+      amdy[i] = AST__BAD;
+      GetValue( this, keyname, AST__FLOAT, amdy + i, 0, 1, method, class, status );
+   }
+
+/* Check the above went OK. */
+   if( astOK ) {
+
+/* Calculate and store the equivalent PV projection parameters. */
+      if( amdx[2] != AST__BAD ) {
+         pv = amdx[2]/3600.0;
+         SetItem( &(store->pv), 0, 0, ' ', pv, status );
+      }
+      if( amdx[0] != AST__BAD ) {
+         pv = amdx[0]/3600.0;
+         SetItem( &(store->pv), 0, 1, ' ', pv, status );
+      }
+      if( amdx[1] != AST__BAD ) {
+         pv = amdx[1]/3600.0;
+         SetItem( &(store->pv), 0, 2, ' ', pv, status );
+      }
+      if( amdx[3] != AST__BAD && amdx[6] != AST__BAD ) {
+         pv = ( amdx[3] + amdx[6] )/3600.0;
+         SetItem( &(store->pv), 0, 4, ' ', pv, status );
+      }
+      if( amdx[4] != AST__BAD ) {
+         pv = amdx[4]/3600.0;
+         SetItem( &(store->pv), 0, 5, ' ', pv, status );
+      }
+      if( amdx[5] != AST__BAD && amdx[6] != AST__BAD ) {
+         pv = ( amdx[5] + amdx[6] )/3600.0;
+         SetItem( &(store->pv), 0, 6, ' ', pv, status );
+      }
+      if( amdx[7] != AST__BAD && amdx[11] != AST__BAD ) {
+         pv = ( amdx[7] + amdx[11] )/3600.0;
+         SetItem( &(store->pv), 0, 7, ' ', pv, status );
+      }
+      if( amdx[8] != AST__BAD ) {
+         pv = amdx[8]/3600.0;
+         SetItem( &(store->pv), 0, 8, ' ', pv, status );
+      }
+      if( amdx[9] != AST__BAD && amdx[11] != AST__BAD ) {
+         pv = ( amdx[9] + amdx[11] )/3600.0;
+         SetItem( &(store->pv), 0, 9, ' ', pv, status );
+      }
+      if( amdx[10] != AST__BAD ) {
+         pv = amdx[10]/3600.0;
+         SetItem( &(store->pv), 0, 10, ' ', pv, status );
+      }
+      if( amdx[12] != AST__BAD ) {
+         pv = amdx[12]/3600.0;
+         SetItem( &(store->pv), 0, 17, ' ', pv, status );
+         SetItem( &(store->pv), 0, 19, ' ', 2*pv, status );
+         SetItem( &(store->pv), 0, 21, ' ', pv, status );
+      }
+      if( amdy[2] != AST__BAD ) {
+         pv = amdy[2]/3600.0;
+         SetItem( &(store->pv), 1, 0, ' ', pv, status );
+      }
+      if( amdy[0] != AST__BAD ) {
+         pv = amdy[0]/3600.0;
+         SetItem( &(store->pv), 1, 1, ' ', pv, status );
+      }
+      if( amdy[1] != AST__BAD ) {
+         pv = amdy[1]/3600.0;
+         SetItem( &(store->pv), 1, 2, ' ', pv, status );
+      }
+      if( amdy[3] != AST__BAD && amdy[6] != AST__BAD ) {
+         pv = ( amdy[3] + amdy[6] )/3600.0;
+         SetItem( &(store->pv), 1, 4, ' ', pv, status );
+      }
+      if( amdy[4] != AST__BAD ) {
+         pv = amdy[4]/3600.0;
+         SetItem( &(store->pv), 1, 5, ' ', pv, status );
+      }
+      if( amdy[5] != AST__BAD && amdy[6] != AST__BAD ) {
+         pv = ( amdy[5] + amdy[6] )/3600.0;
+         SetItem( &(store->pv), 1, 6, ' ', pv, status );
+      }
+      if( amdy[7] != AST__BAD && amdy[11] != AST__BAD ) {
+         pv = ( amdy[7] + amdy[11] )/3600.0;
+         SetItem( &(store->pv), 1, 7, ' ', pv, status );
+      }
+      if( amdy[8] != AST__BAD ) {
+         pv = amdy[8]/3600.0;
+         SetItem( &(store->pv), 1, 8, ' ', pv, status );
+      }
+      if( amdy[9] != AST__BAD && amdy[11] != AST__BAD ) {
+         pv = ( amdy[9] + amdy[11] )/3600.0;
+         SetItem( &(store->pv), 1, 9, ' ', pv, status );
+      }
+      if( amdy[10] != AST__BAD ) {
+         pv = amdy[10]/3600.0;
+         SetItem( &(store->pv), 1, 10, ' ', pv, status );
+      }
+      if( amdy[12] != AST__BAD ) {
+         pv = amdy[12]/3600.0;
+         SetItem( &(store->pv), 1, 17, ' ', pv, status );
+         SetItem( &(store->pv), 1, 19, ' ', 2*pv, status );
+         SetItem( &(store->pv), 1, 21, ' ', pv, status );
+      }
+
+/* Calculate and store the equivalent CRPIX values. */
+      if( xpixelsz != 0.0 ) {
+         SetItem( &(store->crpix), 0, 0, ' ',
+                  ( ppo3/xpixelsz ) - cnpix1 + 0.5, status );
+      } else if( astOK ){
+         astError( AST__BDFTS, "%s(%s): FITS keyword XPIXELSZ has illegal "
+                   "value 0.0", status, method, class );
+      }
+      if( ypixelsz != 0.0 ) {
+         SetItem( &(store->crpix), 0, 1, ' ',
+                  ( ppo6/ypixelsz ) - cnpix2 + 0.5, status );
+      } else if( astOK ){
+         astError( AST__BDFTS, "%s(%s): FITS keyword YPIXELSZ has illegal "
+                   "value 0.0", status, method, class );
+      }
+
+/* Calculate and store the equivalent CRVAL values. */
+      SetItem( &(store->crval), 0, 0, ' ',
+               15.0*( pltrah + pltram/60.0 + pltras/3600.0 ), status );
+      crval2 = pltdecd + pltdecm/60.0 + pltdecs/3600.0;
+      if( !strcmp( pltdecsn, "-") ) crval2 = -crval2;
+      SetItem( &(store->crval), 1, 0, ' ', crval2, status );
+
+/* Calculate and store the equivalent PC matrix. */
+      SetItem( &(store->pc), 0, 0, ' ', -0.001*xpixelsz, status );
+      SetItem( &(store->pc), 1, 1, ' ', 0.001*ypixelsz, status );
+
+/* Set values of 1.0 for the CDELT values. */
+      SetItem( &(store->cdelt), 0, 0, ' ', 1.0, status );
+      SetItem( &(store->cdelt), 1, 0, ' ', 1.0, status );
+
+/* Store remaining constant items */
+      SetItem( &(store->lonpole), 0, 0, ' ', 180.0, status );
+      SetItem( &(store->equinox), 0, 0, ' ', 2000.0, status );
+      SetItemC( &(store->radesys), 0, 0, ' ', "FK5", status );
+      SetItem( &(store->wcsaxes), 0, 0, ' ', 2.0, status );
+      store->naxis = 2;
+      SetItemC( &(store->ctype), 0, 0, ' ', "RA---TPN", status );
+      SetItemC( &(store->ctype), 1, 0, ' ', "DEC--TPN", status );
+   }
+}
+
+static void EmptyFits( AstFitsChan *this, int *status ){
+
+/*
+*++
+*  Name:
+c     astEmptyFits
+f     AST_EMPTYFITS
+
+*  Purpose:
+*     Delete all cards in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+c     void astEmptyFits( AstFitsChan *this )
+f     CALL AST_EMPTYFITS( THIS, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function
+f     This routine
+*     deletes all cards and associated information from a FitsChan.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - This method simply deletes the cards currently in the FitsChan.
+c     Unlike astWriteFits,
+f     Unlike AST_WRITEFITS,
+*     they are not first written out to the sink function or sink file.
+*     - Any Tables or warnings stored in the FitsChan are also deleted.
+*     - This method attempt to execute even if an error has occurred
+*     previously.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS         /* Declare the thread specific global data */
+   const char *class;         /* Pointer to string holding object class */
+   const char *method;        /* Pointer to string holding calling method */
+   int old_ignore_used;       /* Original setting of ignore_used variable */
+
+/* Check a FitsChan was supplied. */
+   if( !this ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Store the method and class strings. */
+   method = "astEmpty";
+   class = astGetClass( this );
+
+/* Delete all cards from the circular linked list stored in the FitsChan,
+   starting with the card at the head of the list. */
+   old_ignore_used = ignore_used;
+   ignore_used = 0;
+   astClearCard( this );
+   while( !astFitsEof( this ) ) DeleteCard( this, method, class, status );
+   ignore_used = old_ignore_used;
+
+/* Delete the KeyMap which holds keywords and the latest sequence number
+   used by each of them. */
+   if( this->keyseq ) this->keyseq = astAnnul( this->keyseq );
+
+/* Delete the KeyMap holding the keyword names. */
+   if( this->keywords ) this->keywords = astAnnul( this->keywords );
+
+/* Free any memory used to hold the Warnings attribute value. */
+   this->warnings = astFree( this->warnings );
+
+/* Other objects in the FitsChan structure. */
+   if( this->tables ) this->tables = astAnnul( this->tables );
+}
+
+static int EncodeFloat( char *buf, int digits, int width, int maxwidth,
+                        double value, int *status ){
+/*
+*
+*  Name:
+*     EncodeFloat
+
+*  Purpose:
+*     Formats a floating point value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int EncodeFloat( char *buf, int digits, int width, int maxwidth,
+*                      double value, int *status )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function formats the value using a G format specified in order
+*     to use the minimum field width (trailing zeros are not printed).
+*     However, the G specifier does not include a decimal point unless it
+*     is necessary. FITS requires that floating point values always include
+*     a decimal point, so this function inserts one, if necessary.
+
+*  Parameters:
+*     buf
+*        A character string into which the value is written.
+*     digits
+*        The number of digits after the decimal point. If the supplied value
+*        is negative, the number of digits actually used may be reduced if
+*        the string would otherwise extend beyond the number of columns
+*        allowed by the FITS standard. If the value is positive, the
+*        specified number of digits are always produced, even if it means
+*        breaking the FITS standard.
+*     width
+*        The minimum field width to use. The value is right justified in
+*        this field width.
+*     maxwidth
+*        The maximum field width to use. A value of zero is returned if
+*        the maximum field width is exceeded.
+*     value
+*        The value to format.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The field width actually used, or zero if the value could not be
+*     formatted. This does not include the trailing null character.
+
+*  Notes:
+*     -  If there is room, a trailing zero is also added following the
+*     inserted decimal point.
+*/
+
+/* Local Variables: */
+   char *c;
+   char *w, *r;
+   int i;
+   int ldigits;
+   int n;
+   int ret;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* The supplied value of "digits" may be negative. Obtain the positive
+   value giving the initial number of decimal digits to use. */
+   ldigits = ( digits > 0 ) ? digits : -digits;
+
+/* Loop until a suitably encoded value has been obtained. */
+   while( 1 ){
+
+/* Write the value into the buffer.  Most are formatted with a G specifier.
+   This will result in values between  -0.001 and -0.0001 being formatted
+   without an exponent, and thus occupying (ldigits+6) characters. With
+   an exponent, these values would be formatted in (ldigits+5) characters
+   thus saving one character. This is important because the default value
+   of ldigits is 15, resulting in 21 characters being used by the G
+   specifier. This is one more than the maximum allowed by the FITS
+   standard. Using an exponent instead would result in 20 characters
+   being used without any loss of precision, thus staying within the FITS
+   limit. Note, the precision used with the E specifier is one less than
+   with the G specifier because the digit to the left of the decimal place
+   is significant with the E specifier, and so we only need (ldigits-1)
+   significant digits to the right of the decimal point. */
+      if( value > -0.001 && value < -0.0001 ) {
+         (void) sprintf( buf, "%*.*E", width, ldigits - 1, value );
+      } else {
+         (void) sprintf( buf, "%*.*G", width, ldigits, value );
+      }
+
+/* Check that the value zero is not encoded with a minus sign (e.g. "-0.").
+   This also rounds out long sequences of zeros or nines.  */
+      CheckZero( buf, value, width, status );
+
+/* If the formatted value includes an exponent, it will have 2 digits.
+   If the exponent includes a leading zero, remove it. */
+      if( ( w = strstr( buf, "E-0" ) ) ) {
+         w += 2;
+      } else if( ( w = strstr( buf, "E+0" ) ) ){
+         w += 2;
+      } else if( ( w = strstr( buf, "E0" ) ) ){
+         w += 1;
+      }
+
+/* If a leading zero was found, shuffle everything down from the start of
+   the string by one character, over-writing the redundant zero, and insert
+   a space at the start of the string. */
+      if( w ) {
+         r = w - 1 ;
+         while( w != buf ) *(w--) = *(r--);
+         *w = ' ';
+      }
+
+/* If the used field width was too large, reduce it and try again, so
+   long as we are allowed to change the number of digits being used. */
+      ret = strlen( buf );
+      if( ret > width && digits < 0 ){
+         ldigits -= ( ret - width );
+
+/* Otherwise leave the loop. Return zero field width if the maximum field
+   width was exceeded. */
+      } else {
+         if( ret > maxwidth ) ret = 0;
+         break;
+      }
+   }
+
+/* If a formatted value was obtained, we need to ensure that the it includes
+   a decimal point. */
+   if( ret ){
+
+/* Get a pointer to the first digit in the buffer. */
+      c = strpbrk( buf, "0123456789" );
+
+/* Something funny is going on if there are no digits in the buffer,
+   so return a zero field width. */
+      if( !c ){
+         ret = 0;
+
+/* Otherwise... */
+      } else {
+
+/* Find the number of digits following and including the first digit. */
+         n = strspn( c, "0123456789" );
+
+/* If the first non-digit character is a decimal point, do nothing. */
+         if( c[ n ] != '.' ){
+
+/* If there are two or more leading spaces, move the start of the string
+   two character to the left, and insert ".0" in the gap created. This
+   keeps the field right justified within the desired field width. */
+            if( buf[ 0 ] == ' ' && buf[ 1 ] == ' ' ){
+               for( i = 2; i < c - buf + n; i++ ) buf[ i - 2 ] = buf[ i ];
+               c[ n - 2 ] = '.';
+               c[ n - 1 ] = '0';
+
+/* If there is just one leading space, move the start of the string
+   one character to the left, and insert "." in the gap created. This
+   keeps the field right justified within the desired field width. */
+            } else if( buf[ 0 ] == ' ' ){
+               for( i = 0; i < n; i++ ) c[ i - 1 ] = c[ i ];
+               c[ n - 1 ] = '.';
+
+/* If there are no leading spaces we need to move the end of the string
+   to the right. This will result in the string no longer being right
+   justified in the required field width. Return zero if there is
+   insufficient room for an extra character. */
+            } else {
+               ret++;
+               if( ret > maxwidth ){
+                  ret = 0;
+
+/* Otherwise, more the end of the string one place to the right and insert
+   the decimal point. */
+               } else {
+                  for( i = strlen( c ); i >= n; i-- ) c[ i + 1 ] = c[ i ];
+                  c[ n ] = '.';
+               }
+            }
+         }
+      }
+   }
+
+/* Return the field width. */
+   return ret;
+}
+
+static int EncodeValue( AstFitsChan *this, char *buf, int col, int digits,
+                        const char *method, int *status ){
+
+/*
+*  Name:
+*     EncodeValue
+
+*  Purpose:
+*     Encode the current card's keyword value into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int EncodeValue( AstFitsChan *this, char *buf, int col, int digits,
+*                      const char *method, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function encodes the keyword value defined in the current card
+*     of the supplied FitsChan and stores it at the start of the supplied
+*     buffer. The number of characters placed in the buffer is returned
+*     (not including a terminating null).
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     buf
+*        The buffer to receive the formatted value. This should be at least
+*        70 characters long.
+*     col
+*        The column number within the FITS header card corresponding to the
+*        start of "buf".
+*     digits
+*        The number of digits to use when formatting floating point
+*        values. If the supplied value is negative, the number of digits
+*        actually used may be reduced if the string would otherwise extend
+*        beyond the number of columns allowed by the FITS standard. If the
+*        value is positive, the specified number of digits are always
+*        produced, even if it means breaking the FITS standard.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of columns used by the encoded value.
+
+*  Notes:
+*     -  The function returns 0 if an error has already occurred
+*     or if an error occurs for any reason within this function.
+*/
+
+/* Local Variables: */
+   char *c;         /* Pointer to next character */
+   char *name;      /* Pointer to the keyword name */
+   double dval;     /* Keyword value */
+   void *data;      /* Pointer to keyword value */
+   int i;           /* Loop count */
+   int ilen;        /* Length of imaginary part */
+   int len;         /* Returned length */
+   int quote;       /* Quote character found? */
+   int rlen;        /* Length of real part */
+   int type;        /* Data type for keyword in current card */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Initialise returned length. */
+   len = 0;
+
+/* Get the data type of the keyword. */
+   type = CardType( this, status );
+
+/* Get a pointer to the data value in the current card. */
+   data = CardData( this, NULL, status );
+
+/* Return if there is no defined value associated with the keyword in the
+   current card. */
+   if( type != AST__UNDEF ) {
+
+/* Get the name of the keyword. */
+      name = CardName( this, status );
+
+/* Go through each supported data type (roughly in the order of
+   decreasing usage)... */
+
+/* AST__FLOAT - stored internally in a variable of type "double".  Right
+   justified to column 30 in the header card. */
+      if( type == AST__FLOAT ){
+         dval = *( (double *) data );
+         len = EncodeFloat( buf, digits, FITSRLCOL - FITSNAMLEN - 2,
+                            AST__FITSCHAN_FITSCARDLEN - col + 1, dval, status );
+         if( len <= 0 && astOK ) {
+            astError( AST__BDFTS, "%s(%s): Cannot encode floating point value "
+                      "%g into a FITS header card for keyword '%s'.", status, method,
+                      astGetClass( this ), dval, name );
+         }
+
+/* AST__STRING & AST__CONTINUE - stored internally in a null terminated array of
+   type "char".  The encoded string is enclosed in single quotes, starting
+   at FITS column 11 and ending in at least column 20. Single quotes
+   in the string are replaced by two adjacent single quotes. */
+      } else if( type == AST__STRING || type == AST__CONTINUE ){
+         c = (char *) data;
+
+/* Enter the opening quote. */
+         len = 0;
+         buf[ len++ ] = '\'';
+
+/* Inspect each character, looking for quotes. */
+         for ( i = 0; c[ i ]; ) {
+            quote = ( c[ i ] == '\'' );
+
+/* If it will not fit into the header card (allowing for doubled
+   quotes), give up here. */
+            if ( len + ( quote ? 2 : 1 ) > AST__FITSCHAN_FITSCARDLEN - col ) break;
+
+/* Otherwise, copy it into the output buffer and double any quotes. */
+            buf[ len++ ] = c[ i ];
+            if ( quote ) buf[ len++ ] = '\'';
+
+/* Look at the next character. */
+            i++;
+         }
+
+/* Pad the string out to the required minimum length with blanks and
+   add the final quote. */
+         while( len < FITSSTCOL - col ) buf[ len++ ] = ' ';
+         buf[ len++ ] = '\'';
+
+/* Inspect any characters that weren't used. If any are non-blank,
+   report an error. */
+         for ( ; c[ i ]; i++ ) {
+            if ( !isspace( c[ i ] ) ) {
+               astError( AST__BDFTS,
+                         "%s(%s): Cannot encode string '%s' into a FITS "
+                         "header card for keyword '%s'.", status, method, astGetClass( this ),
+                         (char *) data, name );
+               break;
+            }
+         }
+
+/* INTEGER - stored internally in a variable of type "int". Right justified
+   to column 30 in the header card. */
+      } else if( type == AST__INT ){
+         len = sprintf(  buf, "%*d", FITSRLCOL - col + 1,
+                         *( (int *) data ) );
+         if( len < 0 || len > AST__FITSCHAN_FITSCARDLEN - col ) {
+            astError( AST__BDFTS, "%s(%s): Cannot encode integer value %d into a "
+                      "FITS header card for keyword '%s'.", status, method, astGetClass( this ),
+                      *( (int *) data ), name );
+         }
+
+/* LOGICAL - stored internally in a variable of type "int". Represented by
+   a "T" or "F" in column 30 of the FITS header card. */
+      } else if( type == AST__LOGICAL ){
+         for( i = 0; i < FITSRLCOL - col; i++ ) buf[ i ] = ' ';
+         if( *( (int *) data ) ){
+            buf[ FITSRLCOL - col ] = 'T';
+         } else {
+            buf[ FITSRLCOL - col ] = 'F';
+         }
+         len = FITSRLCOL - col + 1;
+
+/* AST__COMPLEXF - stored internally in an array of two "doubles". The real
+   part is right justified to FITS column 30. The imaginary part is right
+   justified to FITS column 50. */
+      } else if( type == AST__COMPLEXF ){
+         dval = ( (double *) data )[ 0 ];
+         rlen = EncodeFloat( buf, digits, FITSRLCOL - FITSNAMLEN - 2,
+                             AST__FITSCHAN_FITSCARDLEN - col + 1, dval, status );
+         if( rlen <= 0 || rlen > AST__FITSCHAN_FITSCARDLEN - col ) {
+            astError( AST__BDFTS, "%s(%s): Cannot encode real part of a complex "
+                      "floating point value [%g,%g] into a FITS header card "
+                      "for keyword '%s'.", status, method, astGetClass( this ), dval,
+                      ( (double *) data )[ 1 ], name );
+         } else {
+            dval = ( (double *) data )[ 1 ];
+            ilen = EncodeFloat( buf + rlen, digits,
+                                FITSIMCOL - FITSRLCOL,
+                                AST__FITSCHAN_FITSCARDLEN - col - rlen, dval, status );
+            if( ilen <= 0 ) {
+               astError( AST__BDFTS, "%s(%s): Cannot encode imaginary part of a "
+                         "complex floating point value [%g,%g] into a FITS header "
+                         "card for keyword '%s'.", status, method, astGetClass( this ),
+                         ( (double *) data )[ 0 ], dval, name );
+            } else {
+               len = ilen + rlen;
+            }
+         }
+
+/* AST__COMPLEXI - stored internally in a an array of two "ints". */
+      } else if( type == AST__COMPLEXI ){
+         rlen = sprintf(  buf, "%*d", FITSRLCOL - col + 1,
+                          ( (int *) data )[ 0 ] );
+         if( rlen < 0 || rlen > AST__FITSCHAN_FITSCARDLEN - col ) {
+            astError( AST__BDFTS, "%s(%s): Cannot encode real part of a complex "
+                      "integer value [%d,%d] into a FITS header card "
+                      "for keyword '%s'.", status, method, astGetClass( this ),
+                      ( (int *) data )[ 0 ],
+                      ( (int *) data )[ 1 ], name );
+         } else {
+            ilen = sprintf(  buf + rlen, "%*d",  FITSIMCOL - FITSRLCOL + 1,
+                             ( (int *) data )[ 1 ] );
+            if( ilen < 0 || ilen > AST__FITSCHAN_FITSCARDLEN - col - rlen ) {
+               astError( AST__BDFTS, "%s(%s): Cannot encode imaginary part of a "
+                         "complex integer value [%d,%d] into a FITS header card "
+                         "for keyword '%s'.", status, method, astGetClass( this ),
+                         ( (int *) data )[ 0 ],
+                         ( (int *) data )[ 1 ], name );
+            } else {
+               len = ilen + rlen;
+            }
+         }
+
+/* Report an internal (ast) programming error if the keyword is of none of the
+   above types. */
+      } else if( astOK ){
+         astError( AST__INTER, "EncodeValue: AST internal programming error - "
+                   "FITS %s data-type not yet supported.", status,
+                   type_names[ type ] );
+      }
+   }
+
+/* If an error has occurred, return zero length. */
+   if( !astOK ) len = 0;
+
+/* Return the answer. */
+   return len;
+}
+
+static AstGrismMap *ExtractGrismMap( AstMapping *map, int iax,
+                                     AstMapping **new_map, int *status ){
+/*
+*  Name:
+*     ExtractGrismMap
+
+*  Purpose:
+*     Extract a GrismMap from the end of the supplied Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstGrismMap *ExtractGrismMap( AstMapping *map, int iax,
+*                                   AstMapping **new_map, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function examines the supplied Mapping; if the specified output
+*     coordinate of the Mapping is created directly by an un-inverted GrismMap,
+*     then a pointer to the GrismMap is returned as the function value. A new
+*     Mapping is also returned via parameter "new_map" which is a copy of
+*     the supplied Mapping, except that the GrismMap is replaced with a
+*     UnitMap. If no GrismMap is found, NULL is returned for both Mappings.
+*     The condition that "the specified output coordinate of the Mapping is
+*     created directly by an un-inverted GrismMap" means that the output
+*     of the GrismMap is no subsequently modified by any further Mappings
+*     before being returned as the "iax"th output of the supplied Mapping.
+*     This means the GrismMap must be "at the end of" a CmpMap, not in
+*     the middle of the CmpMap.
+
+*  Parameters:
+*     map
+*        Pointer to the Mapping to check.
+*     iax
+*        The index for the output coordinate to be checked.
+*     new_map
+*        Pointer to a location at which to return a pointer to a new
+*        Mapping which is a copy of "map" except that the GrismMap is
+*        replaced by a UnitMap. NULL is returned if the specified output
+*        was not created by a GrismMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The extracted GrismMap, or NULL if the specified output was not
+*     created by a GrismMap.
+*/
+
+/* Local Variables: */
+   AstMapping *mapa;     /* First component Mapping */
+   AstMapping *mapb;     /* Second component Mapping */
+   AstMapping *new_mapa; /* Replacement for first component Mapping */
+   AstMapping *new_mapb; /* Replacement for second component Mapping */
+   AstGrismMap *ret;     /* Returned GrismMap */
+   int inva;             /* Invert attribute for mapa within the CmpMap */
+   int invb;             /* Invert attribute for mapb within the CmpMap */
+   int na;               /* Number of outputs for mapa */
+   int old_inva;         /* Current Invert attribute for mapa */
+   int old_invb;         /* Current Invert attribute for mapb */
+   int series;           /* Are component Mappings applied in series? */
+
+/* Initialise */
+   ret = NULL;
+   *new_map = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* If the supplied Mapping is a GrismMap which has not been inverted,
+   return it as the function value and return a UnitMap as the new
+   Mapping. */
+   if( astIsAGrismMap( map ) ) {
+      if( !astGetInvert( map ) ) {
+         ret = astClone( map );
+         *new_map = (AstMapping *) astUnitMap( 1, "", status );
+      }
+
+/* If the supplied Mapping is a CmpMap, get its two component Mappings,
+   see if they are applied in parallel or series, and get the Invert
+   attribute values which the component Mappings had at the time the
+   CmpMap was created. */
+   } else if(  astIsACmpMap( map ) ) {
+      astDecompose( map, &mapa, &mapb, &series, &inva, &invb );
+
+/* Temporaily reset the Invert attributes of the component Mappings back to
+   the values they had when the CmpMap was created. */
+      old_inva = astGetInvert( mapa );
+      old_invb = astGetInvert( mapb );
+      astSetInvert( mapa, inva );
+      astSetInvert( mapb, invb );
+
+/* If the supplied Mapping is a series CmpMap, attempt to extract a
+   GrismMap from the second component Mapping ("mapb"). The first
+   component Mapping ("mapa") is unchanged. We do not need to consdier
+   the first component since we are only interested in GrismMaps which are
+   at the end of the CmpMap. */
+      if( series ) {
+         ret = ExtractGrismMap( mapb, iax, &new_mapb, status );
+         if( ret ) new_mapa = astClone( mapa );
+
+/* If the supplied Mapping is a parallel CmpMap, attempt to extract a
+   GrismMap from the component Mapping which produces output "iax". The
+   other component Mapping is unchanged. */
+      } else {
+         na = astGetNout( mapa );
+         if( iax < na ) {
+            ret = ExtractGrismMap( mapa, iax, &new_mapa, status );
+            if( ret ) new_mapb = astClone( mapb );
+         } else {
+            ret = ExtractGrismMap( mapb, iax - na, &new_mapb, status );
+            if( ret ) new_mapa = astClone( mapa );
+         }
+      }
+
+/* If succesful, create a new CmpMap to return. */
+      if( ret ) {
+         *new_map = (AstMapping *) astCmpMap( new_mapa, new_mapb, series, "", status );
+         new_mapa = astAnnul( new_mapa );
+         new_mapb = astAnnul( new_mapb );
+      }
+
+/* Re-instate the original Invert attributes of the component Mappings. */
+      astSetInvert( mapa, old_inva );
+      astSetInvert( mapb, old_invb );
+
+/* Annul the component Mapping pointers. */
+      mapa = astAnnul( mapa );
+      mapb = astAnnul( mapb );
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static int MakeBasisVectors( AstMapping *map, int nin, int nout,
+                             double *g0, AstPointSet *psetg,
+                             AstPointSet *psetw, int *status ){
+/*
+*  Name:
+*     MakeBasisVectors
+
+*  Purpose:
+*     Create a set of basis vectors in grid coordinates
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int MakeBasisVectors( AstMapping *map, int nin, int nout,
+*                           double *g0, AstPointSet *psetg,
+*                           AstPointSet *psetw, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns a set of unit vectors in grid coordinates,
+*     one for each grid axis. Each unit vector is parallel to the
+*     corresponding grid axis, and rooted at a specified grid position
+*     ("g0"). The IWC coordinates corresponding to "g0" and to the end of
+*     each of the unit vectors are also returned, together with a flag
+*     indicating if all the IWC coordinate values are good.
+
+*  Parameters:
+*     map
+*        A pointer to a Mapping which transforms grid coordinates into
+*        intermediate world coordinates (IWC). The number of outputs must
+*        be greater than or equal to the number of inputs.
+*     nin
+*        The number of inputs for "map" (i.e. the number of grid axes).
+*     nout
+*        The number of outputs for "map" (i.e. the number of IWC axes).
+*     g0
+*        Pointer to an array of holding the grid coordinates at the
+*        "root" position.
+*     psetg
+*        A pointer to a PointSet which can be used to hold the required
+*        grid positions. This should have room for nin+1 positions. On
+*        return, the first position holds "g0", and the subsequent "nin"
+*        positions hold are offset from "g0" by unit vectors along the
+*        corresponding grid axis.
+*     psetw
+*        A pointer to a PointSet which can be used to hold the required
+*        IWC position. This should also have room for nin+1 positions. On
+*        return, the values are the IWC coordinates corresponding to the
+*        grid positions returned in "psetg".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if all the axis values in "psetw" are good.
+*     Zero is returned otherwise.
+
+*  Notes:
+*     -  Zero is returned if an error occurs.
+*/
+
+/* Local Variables: */
+   double **ptrg;
+   double **ptrw;
+   double *c;
+   int i;
+   int ii;
+   int j;
+   int ret;
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Get pointers to the data in the two supplied PointSets. */
+   ptrg = astGetPoints( psetg );
+   ptrw = astGetPoints( psetw );
+
+/* Check the pointers can be used safely. */
+   if( astOK ) {
+
+/* Assume success. */
+      ret = 1;
+
+/* Store the required grid positions in PointSet "pset1". The first
+   position is the supplied root grid position, g0. The next "nin"
+   positions are offset from the root position by a unit vector along
+   each grid axis in turn. Store values for each grid axis in turn. */
+      for( i = 0; i < nin; i++ ) {
+
+/* Get a pointer to the first axis value for this grid axis. */
+         c = ptrg[ i ];
+
+/* Initially set all values for this axis to the supplied root grid value. */
+         for( ii = 0; ii < nin + 1; ii++ ) c[ ii ] = g0[ i ];
+
+/* Modify the value corresponding to the vector along this grid axis. */
+         c[ i + 1 ] += 1.0;
+      }
+
+/* Transform these grid positions in IWC positions using the supplied
+   Mapping. */
+      (void) astTransform( map, psetg, 1, psetw );
+
+/* Check that all the transformed positions are good. */
+      for( j = 0; j < nout; j++ ) {
+         c = ptrw[ j ];
+         for( ii = 0; ii < nin + 1; ii++, c++ ) {
+            if( *c == AST__BAD ) {
+               ret = 0;
+               break;
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static int FindBasisVectors( AstMapping *map, int nin, int nout,
+                             double *dim, AstPointSet *psetg,
+                             AstPointSet *psetw, int *status ){
+/*
+*  Name:
+*     FindBasisVectors
+
+*  Purpose:
+*     Find the a set of basis vectors in grid coordinates
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int FindBasisVectors( AstMapping *map, int nin, int nout,
+*                           double *dim, AstPointSet *psetg,
+*                           AstPointSet *psetw, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns a set of unit vectors in grid coordinates,
+*     one for each grid axis. Each unit vector is parallel to the
+*     corresponding grid axis, and rooted at a specified grid position
+*     ("g0"). The IWC coordinates corresponding to "g0" and to the end of
+*     each of the unit vectors are also returned, together with a flag
+*     indicating if all the IWC coordinate values are good.
+
+*  Parameters:
+*     map
+*        A pointer to a Mapping which transforms grid coordinates into
+*        intermediate world coordinates (IWC). The number of outputs must
+*        be greater than or equal to the number of inputs.
+*     nin
+*        The number of inputs for "map" (i.e. the number of grid axes).
+*     nout
+*        The number of outputs for "map" (i.e. the number of IWC axes).
+*     dim
+*        Array dimensions, in pixels, if known (otherwise supplied a NULL
+*        pointer to values of AST__BAD).
+*     psetg
+*        A pointer to a PointSet which can be used to hold the required
+*        grid position. This should have room for nin+1 positions. On
+*        return, the first position holds the "root" position and the
+*        subsequent "nin" positions hold are offset from root position
+*        by unit vectors along the corresponding grid axis.
+*     psetw
+*        A pointer to a PointSet which can be used to hold the required
+*        IWC position. This should also have room for nin+1 positions. On
+*        return, the values are the IWC coordinates corresponding to the
+*        grid positions returned in "psetg".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if a set of basis vectors was found
+*     succesfully. Zero is returned otherwise.
+
+*  Notes:
+*     -  Zero is returned if an error occurs.
+*/
+
+/* Local Variables: */
+   double *g0;
+   double dd;
+   double ddlim;
+   int i;
+   int ii;
+   int ret;
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Allocate an array to store the candidate root position. */
+   g0 = astMalloc( sizeof( double )*(size_t) nin );
+   if( astOK ) {
+
+/* First try the grid centre, if known. */
+      ddlim = 0;
+      ret = 0;
+      if( dim ) {
+         ret = 1;
+         for( i = 0; i < nin; i++ ) {
+            if( dim[ i ] != AST__BAD ) {
+               g0[ i ] = 0.5*( 1 + dim[ i ] );
+               if( dim[ i ] > ddlim ) ddlim = dim[ i ];
+            } else {
+               ret = 0;
+               break;
+            }
+         }
+      }
+      if( ret ) ret = MakeBasisVectors( map, nin, nout, g0, psetg, psetw, status );
+
+/* If this did not produce a set of good IWC positions, try grid position
+   (1,1,1...). */
+      if( !ret ) {
+         for( i = 0; i < nin; i++ ) g0[ i ] = 1.0;
+         ret = MakeBasisVectors( map, nin, nout, g0, psetg, psetw, status );
+      }
+
+/* If this did not produce a set of good IWC positions, try a sequence of
+   grid positions which move an increasing distance along each grid axis
+   from (1,1,1,...). Stop when we get further than "ddlim" from the
+   origin. */
+      dd = 10.0;
+      if( ddlim == 0.0 ) ddlim = 10240.0;
+      while( !ret && dd <= ddlim ) {
+
+/* First try positions which extend across the middle of the data set.
+   If the image dimensions are known, make the line go from the "bottom
+   left corner" towards the "top right corner", taking the aspect ratio
+   of the image into account. Otherise, just use a vector of (1,1,1,..) */
+         for( i = 0; i < nin; i++ ) {
+            if( dim && dim[ i ] != AST__BAD ) {
+               g0[ i ] = dd*dim[ i ]/ddlim;
+            } else {
+               g0[ i ] = dd;
+            }
+         }
+         ret = MakeBasisVectors( map, nin, nout, g0, psetg, psetw, status );
+
+/* If the above didn't produce good positions, try moving out along each
+   grid axis in turn. */
+         for( ii = 0; !ret && ii < nin; ii++ ) {
+            for( i = 0; i < nin; i++ ) g0[ i ] = 1.0;
+            g0[ ii ] = dd;
+            ret = MakeBasisVectors( map, nin, nout, g0, psetg, psetw, status );
+         }
+
+/* Go further out from the origin for the next set of tests (if any). */
+         dd *= 2.0;
+      }
+   }
+
+/* Free resources. */
+   g0 = astFree( g0 );
+
+/* Return the result. */
+   return ret;
+}
+
+static int FindLonLatSpecAxes( FitsStore *store, char s, int *axlon, int *axlat,
+                           int *axspec, const char *method, const char *class, int *status ) {
+/*
+*  Name:
+*     FindLonLatSpecAxes
+
+*  Purpose:
+*     Search the CTYPE values in a FitsStore for celestial and spectral axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int FindLonLatSpecAxes( FitsStore *store, char s, int *axlon, int *axlat,
+*                             int *axspec, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     The supplied FitsStore is searched for axes with a specified axis
+*     description character which describe celestial longitude or latitude
+*     or spectral position.
+
+*  Parameters:
+*     store
+*        A structure containing values for FITS keywords relating to
+*        the World Coordinate System.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     axlon
+*        Address of a location at which to return the index of the
+*        longitude axis (if found). This is the value of "i" within the
+*        keyword name "CTYPEi". A value of -1 is returned if no longitude
+*        axis is found.
+*     axlat
+*        Address of a location at which to return the index of the
+*        latitude axis (if found). This is the value of "i" within the
+*        keyword name "CTYPEi". A value of -1 is returned if no latitude
+*        axis is found.
+*     axspec
+*        Address of a location at which to return the index of the
+*        spectral axis (if found). This is the value of "i" within the
+*        keyword name "CTYPEi". A value of -1 is returned if no spectral
+*        axis is found.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One is returned if both celestial axes were found. Zero is returned if
+*     either axis was not found. The presence of a spectral axis does not
+*     affect the returned value.
+
+*  Notes:
+*     -  If an error occurs, zero is returned.
+*/
+
+/* Local Variables: */
+   char *assys;
+   char *astype;
+   char algcode[5];
+   char stype[5];
+   const char *ctype;
+   double dval;
+   int i;
+   int wcsaxes;
+
+/* Initialise */
+   *axlon = -1;
+   *axlat = -1;
+   *axspec = -1;
+
+/* Check the global status. */
+   if ( !astOK ) return 0;
+
+/* Obtain the number of FITS WCS axes in the header. If the WCSAXES header
+   was specified, use it. Otherwise assume it is the same as the number
+   of pixel axes. */
+   dval = GetItem( &(store->wcsaxes), 0, 0, s, NULL, method, class, status );
+   if( dval != AST__BAD ) {
+      wcsaxes = (int) dval + 0.5;
+   } else {
+      wcsaxes = store->naxis;
+   }
+
+/* Loop round the FITS WCS axes, getting each CTYPE value. */
+   for( i = 0; i < wcsaxes && astOK; i++ ){
+      ctype = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+
+/* Check a value was found. */
+      if( ctype ) {
+
+/* First check for spectral axes, either FITS-WCS or AIPS-like. */
+         if( IsSpectral( ctype, stype, algcode, status ) ||
+             IsAIPSSpectral( ctype, &astype, &assys, status ) ) {
+            *axspec = i;
+
+/* Otherwise look for celestial axes. Celestial axes must have a "-" as the
+   fifth character in CTYPE. */
+         } else if( ctype[4] == '-' ) {
+
+/* See if this is a longitude axis (e.g. if the first 4 characters of CTYPE
+   are "RA--" or "xLON" or "yzLN" ). */
+            if( !strncmp( ctype, "RA--", 4 ) ||
+                !strncmp( ctype, "AZ--", 4 ) ||
+                !strncmp( ctype + 1, "LON", 3 ) ||
+                !strncmp( ctype + 2, "LN", 2 ) ){
+               *axlon = i;
+
+/* Otherwise see if it is a latitude axis. */
+            } else if( !strncmp( ctype, "DEC-", 4 ) ||
+                       !strncmp( ctype, "EL--", 4 ) ||
+                       !strncmp( ctype + 1, "LAT", 3 ) ||
+                       !strncmp( ctype + 2, "LT", 2 ) ){
+               *axlat = i;
+            }
+         }
+      }
+   }
+
+/* Indicate failure if an error occurred. */
+   if( !astOK ) {
+      *axlon = -1;
+      *axlat = -1;
+      *axspec = -1;
+   }
+
+/* Return the result. */
+   return ( *axlat != -1 && *axlon != -1 );
+}
+
+static void FindWcs( AstFitsChan *this, int last, int all, int rewind,
+                     const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     FindWcs
+
+*  Purpose:
+*     Find the first or last FITS WCS related keyword in a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void FindWcs( AstFitsChan *this, int last, int all, int rewind,
+*                   const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A search is made through the FitsChan for the first or last card
+*     which relates to a FITS WCS keyword (any encoding). If "last" is
+*     non-zero, the next card becomes the current card. If "last" is
+*     zero, the WCS card is left as the current card. Cards marked as
+*     having been read are included or not, as specified by "all".
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     last
+*        If non-zero, the last WCS card is searched for. Otherwise, the
+*        first WCS card is searched for.
+*     all
+*        If non-zero, then cards marked as having been read are included
+*        in the search. Otherwise such cards are ignored.
+*     rewind
+*        Only used if "last" is zero (i.e. the first card is being
+*        searched for). If "rewind" is non-zero, then the search starts
+*        from the first card in the FitsChan. If zero, the search starts
+*        from the current card.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  The FitsChan is left at end-of-file if no FITS-WCS keyword cards
+*     are found in the FitsChan.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS       /* Declare the thread specific global data */
+   const char *keyname;     /* Keyword name from current card */
+   int nfld;                /* Number of fields in keyword template */
+   int old_ignore_used;     /* Original value of variable ignore_used */
+
+/* Check the global status. Also check the FitsChan is not empty. */
+   if( !astOK || !this->head ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Indicate that we should, or should not, skip over cards marked as having
+   been read. */
+   old_ignore_used = ignore_used;
+   ignore_used = all ? 0 : 1;
+
+/* If required, set the FitsChan to start or end of file. */
+   if( last ) {
+      astSetCard( this, INT_MAX );
+   } else if( rewind ) {
+      astClearCard( this );
+   }
+
+/* If the current card is marked as used, and we are skipping used cards,
+   move on to the next unused card */
+   if( CARDUSED( this->card ) ) MoveCard( this, last?-1:1, method, class, status );
+
+/* Check each card moving backwards from the end to the start, or
+   forwards from the start to the end, until a WCS keyword is found,
+   or the other end of the FitsChan is reached. */
+   while( astOK ){
+
+/* Get the keyword name from the current card. */
+      keyname = CardName( this, status );
+
+/* Save a pointer to the keyword if it is the first non-null, non-comment
+   card. */
+      if( keyname ) {
+
+/* If it matches any of the WCS keywords, move on one card
+   and break out of the loop. */
+         if( Match( keyname, "CRVAL%d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "CRPIX%d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "CDELT%d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "CROTA%d", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "CTYPE%d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "CUNIT%d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PC%3d%3d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "CD%3d%3d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "CD%1d_%1d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PC%1d_%1d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "LONGPOLE", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "LONPOLE%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "LATPOLE%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PROJP%d", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PV%d_%d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PS%d_%d%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "EPOCH", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "EQUINOX%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "MJD-OBS",  0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "DATE-OBS", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "TIMESYS", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "RADECSYS", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "RADESYS%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "C%1dVAL%d", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "C%1dPIX%d", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "C%1dELT%d", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "C%1dYPE%d", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "C%1dNIT%d", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "CNPIX1", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "CNPIX2", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PPO%d", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "AMDX%d", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "AMDY%d", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "XPIXELSZ", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "YPIXELSZ", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PLTRAH", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PLTRAM", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PLTRAS", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PLTDECD", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PLTDECM", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PLTDECS", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PLTDECSN", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PLTSCALE", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PPO1", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PPO2", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PPO4", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "PPO5", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "WCSNAME%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "SPECSYS%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "SSYSSRC%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "ZSOURCE%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "VELOSYS%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "RESTFRQ%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "MJD_AVG%0c", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "OBSGEO-X", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "OBSGEO-Y", 0, NULL, &nfld, method, class, status ) ||
+             Match( keyname, "OBSGEO-Z", 0, NULL, &nfld, method, class, status ) ) {
+            if( last ) MoveCard( this, 1, method, class, status );
+            break;
+         }
+      }
+
+/* Leave the FitsChan at end-of-file if no WCS cards were found. */
+      if( (last && FitsSof( this, status ) ) ||
+          (!last && astFitsEof( this ) ) ) {
+         astSetCard( this, INT_MAX );
+         break;
+      } else {
+         MoveCard( this, last?-1:1, method, class, status );
+      }
+   }
+
+/* Re-instate the original flag indicating if cards marked as having been
+   read should be skipped over. */
+   ignore_used = old_ignore_used;
+
+/* Return. */
+   return;
+}
+
+static int FindString( int n, const char *list[], const char *test,
+                       const char *text, const char *method,
+                       const char *class, int *status ){
+/*
+*  Name:
+*     FindString
+
+*  Purpose:
+*     Find a given string within an array of character strings.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int FindString( int n, const char *list[], const char *test,
+*                     const char *text, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function identifies a supplied string within a supplied
+*     array of valid strings, and returns the index of the string within
+*     the array. The test option may not be abbreviated, but case is
+*     insignificant.
+
+*  Parameters:
+*     n
+*        The number of strings in the array pointed to be "list".
+*     list
+*        A pointer to an array of legal character strings.
+*     test
+*        A candidate string.
+*     text
+*        A string giving a description of the object, parameter,
+*        attribute, etc, to which the test value refers.
+*        This is only for use in constructing error messages. It should
+*        start with a lower case letter.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The index of the identified string within the supplied array, starting
+*     at zero.
+
+*  Notes:
+*     -  A value of -1 is returned if an error has already occurred, or
+*     if this function should fail for any reason (for instance if the
+*     supplied option is not specified in the supplied list).
+*/
+
+/* Local Variables: */
+   int ret;                /* The returned index */
+
+/* Check global status. */
+   if( !astOK ) return -1;
+
+/* Compare the test string with each element of the supplied list. Leave
+   the loop when a match is found. */
+   for( ret = 0; ret < n; ret++ ) {
+      if( !Ustrcmp( test, list[ ret ], status ) ) break;
+   }
+
+/* Report an error if the supplied test string does not match any element
+   in the supplied list. */
+   if( ret >= n && astOK ) {
+      astError( AST__RDERR, "%s(%s): Illegal value '%s' supplied for %s.", status,
+                method, class, test, text );
+      ret = -1;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static int FitOK( int n, double *act, double *est, double tol, int *status ) {
+/*
+*  Name:
+*     FitOK
+
+*  Purpose:
+*     See if a fit is usable.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int FitOK( int n, double *act, double *est, double tol, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function is supplied with a set of actual data values, and the
+*     corresponding values estimated by some fitting process. It tests
+*     that the RMS residual between them is no more than "tol".
+
+*  Parameters:
+*     n
+*        Number of data points.
+*     act
+*        Pointer to the start of the actual data values.
+*     est
+*        Pointer to the start of the estimated data values.
+*     tol
+*        The largest acceptable RMS error between "act" and "est".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if the two sets of values agree. Zero is
+*     returned otherwise.
+
+*  Notes:
+*     -  Zero is returned if an error occurs.
+*/
+
+/* Local Variables: */
+   int ret, i;
+   double s1, s2;
+   double *px, *py, diff, mserr;
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Initialise the sum of the squared residuals, and the number summed. */
+   s1 = 0.0;
+   s2 = 0.0;
+
+/* Initialise pointers to the next actual and estimated values to use. */
+   px = act;
+   py = est;
+
+/* Loop round all pairs of good actual and estimate value. */
+   for( i = 0; i < n; i++, px++, py++ ){
+      if( *px != AST__BAD && *py != AST__BAD ) {
+
+/* Increment the sums need to find the RMS residual between the actual
+   and estimated values. */
+         diff = *px - *py;
+         s1 += diff*diff;
+         s2 += 1.0;
+      }
+   }
+
+/* If the sums are usable... */
+   if( s2 > 0.0 ) {
+
+/* Form the mean squared residual, and check if it is less than the
+   squared error limit. */
+      mserr = s1/s2;
+      if( mserr < tol*tol ) ret = 1;
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static int FitsAxisOrder( AstFitsChan *this, int nwcs, AstFrame *wcsfrm,
+                          int *perm, int *status ){
+/*
+*  Name:
+*     FitsAxisOrder
+
+*  Purpose:
+*     Return the order of WCS axes specified by attribute FitsAxisOrder.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int FitsAxisOrder( AstFitsChan *this, int nwcs, AstFrame *wcsfrm,
+*                        int *perm, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns an array indicating the order of the WCS axes
+*     within the output FITS header, as specified by the FitsAxisOrder
+*     attribute.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     nwcs
+*        The number of axes in "wcsfrm".
+*     wcsfrm
+*        The Frame containing the output WCS axes.
+*     perm
+*        Pointer to an array of "nwcs" integers. On exit, element "k"
+*        of this array holds the zero-based index of the FITS-WCS axis
+*        (i.e. one less than the value of "i" in the keyword names
+*        "CTYPEi", "CRVALi", etc) that describes the k'th axis in "wcsfrm".
+*        In other words, "perm[ast_index] = fits_index". The order is
+*        determined by the FitsAxisOrder attribute. If this attribute is
+*        "<copy>" or "<auto>", then "perm[k]=k" for all k on exit (i.e.
+*        a unit mapping between axes in "wcsfrm" and the FITS header).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Returns zero if the FitsAxisOrder attribute is "<auto">, and
+*     non-zero otherwise. This is a flag indicating if the returned
+*     values in "perm" can be used s they are.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *km;    /* KeyMap holding axis indices keyed by axis symbols */
+   char **words;     /* Pointer to array of words from FitsAxisOrder */
+   char attr_name[15];/* Attribute name */
+   const char *attr; /* Pointer to a string holding the FitsAxisOrder value */
+   int i;            /* Loop count */
+   int j;            /* Zero-based axis index */
+   int k;            /* Zero-based axis index */
+   int nword;        /* Number of words in FitsAxisOrder */
+   int result;       /* Retrned value */
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* Initialise the returned array to a unit mapping from Frame axis to
+   FITS axis. */
+   for( i = 0; i < nwcs; i++ ) perm[ i ] = i;
+
+/* Get the FitsAxisOrder attribute value, and set the returned value to
+   indicate if it is "<auto>". */
+   attr = astGetFitsAxisOrder( this );
+   result = !astChrMatch( attr, "<auto>" );
+
+/* Return immediately if it is "<auto>" or "<copy>". */
+   if( result && !astChrMatch( attr, "<copy>" ) ) {
+
+/* Create a KeyMap in which each key is the Symbol for an axis and the
+   associated value is the zero based index of the axis within "wcsfrm". */
+      km = astKeyMap( "KeyCase=0", status );
+      for( i = 0; i < nwcs; i++ ){
+         sprintf( attr_name, "Symbol(%d)", i + 1 );
+         astMapPut0I( km, astGetC( wcsfrm, attr_name ), i, NULL );
+      }
+
+/* Split the FitsAxisOrder value into a collection of space-separated words. */
+      words = astChrSplit( attr, &nword );
+
+/* Loop round them all. */
+      k = 0;
+      for( i = 0; i < nword; i++ ) {
+
+/* Get the zero based index within "wcsfrm" of the axis that has a Symbol
+   equal to the current word from FitsAxisOrder. */
+         if( astMapGet0I( km, words[ i ], &j ) ) {
+
+/* If this "wcsfrm" axis has already been used, report an error. */
+            if( j < 0 ) {
+               if( astOK ) astError( AST__ATTIN, "astWrite(fitschan): "
+                           "attribute FitsAxisOrder (%s) refers to axis "
+                           "%s more than once.", status, attr, words[ i ] );
+
+/* Otherwise, set the corresponding element of the returned array, and
+   ensure this axis cannot be used again by assigning it an index of -1
+   in the KeyMap. */
+            } else {
+               perm[ j ] = k++;
+               astMapPut0I( km, words[ i ], -1, NULL );
+            }
+         }
+
+/* Free the memory holding the copy of the word. */
+         words[ i ] = astFree( words[ i ] );
+      }
+
+/* Report an error if any wcsfrm axes were not included in FitsAxisOrder. */
+      if( astOK ) {
+         for( i = 0; i < nwcs; i++ ){
+            sprintf( attr_name, "Symbol(%d)", i + 1 );
+            if( astMapGet0I( km, astGetC( wcsfrm, attr_name ), &j ) ) {
+               if( j >= 0 ) {
+                  astError( AST__ATTIN, "astWrite(fitschan): attribute FitsAxisOrder "
+                      "(%s) does not specify a position for WCS axis '%s'.",
+                      status, attr, astGetC( wcsfrm, attr_name ) );
+                  break;
+               }
+            }
+         }
+      }
+
+/* Free resources. */
+      words = astFree( words );
+      km = astAnnul( km );
+   }
+
+   return result;
+}
+
+static int FitsFromStore( AstFitsChan *this, FitsStore *store, int encoding,
+                          double *dim, AstFrameSet *fs, const char *method,
+                          const char *class, int *status ){
+
+/*
+*  Name:
+*     FitsFromStore
+
+*  Purpose:
+*     Store WCS keywords in a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     int FitsFromStore( AstFitsChan *this, FitsStore *store, int encoding,
+*                        double *dim, AstFrameSet *fs, const char *method,
+*                        const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function copies the WCS information stored in the supplied
+*     FitsStore into the supplied FitsChan, using a specified encoding.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        Pointer to the FitsStore.
+*     encoding
+*        The encoding to use.
+*     dim
+*        Pointer to an array holding the array dimensions (AST__BAD
+*        indicates that the dimenson is not known).
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if succesfull, and zero is returned
+*     otherwise.
+*/
+
+/* Local Variables: */
+   int ret;
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Set the current card so that it points to the last WCS-related keyword
+   in the FitsChan (whether previously read or not). Any new WCS related
+   keywords either over-write pre-existing cards for the same keyword, or
+   (if no pre-existing card exists) are inserted after the last WCS related
+   keyword. */
+   FindWcs( this, 1, 1, 0, method, class, status );
+
+/* Do each non-standard FITS encoding... */
+   if( encoding == DSS_ENCODING ){
+      ret = DSSFromStore( this, store, method, class, status );
+   } else if( encoding == FITSPC_ENCODING ){
+      ret = PCFromStore( this, store, method, class, status );
+   } else if( encoding == FITSIRAF_ENCODING ){
+      ret = IRAFFromStore( this, store, method, class, status );
+   } else if( encoding == FITSAIPS_ENCODING ){
+      ret = AIPSFromStore( this, store, method, class, status );
+   } else if( encoding == FITSAIPSPP_ENCODING ){
+      ret = AIPSPPFromStore( this, store, method, class, status );
+   } else if( encoding == FITSCLASS_ENCODING ){
+      ret = CLASSFromStore( this, store, fs, dim, method, class, status );
+
+/* Standard FITS-WCS encoding */
+   } else {
+      ret = WcsFromStore( this, store, method, class, status );
+   }
+
+/* If there are any Tables in the FitsStore move the KeyMap that contains
+   them from the FitsStore to the FitsChan, from where they can be
+   retrieved using the public astGetTables method. */
+   if( astMapSize( store->tables ) > 0 ) {
+      if( !this->tables ) this->tables = astKeyMap( " ", status );
+      astMapCopy( this->tables, store->tables );
+      (void) astAnnul( store->tables );
+      store->tables = astKeyMap( " ", status );
+   }
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) ret = 0;
+
+/* Return the answer. */
+   return ret;
+}
+
+static FitsStore *FitsToStore( AstFitsChan *this, int encoding,
+                               const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     FitsToStore
+
+*  Purpose:
+*     Return a pointer to a FitsStore structure containing WCS information
+*     read from the supplied FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     FitsStore *FitsToStore( AstFitsChan *this, int encoding,
+*                             const char *method, const char *class )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function creates a new FitsStore containing WCS information
+*     read from the supplied FitsChan using the specified encoding. An
+*     error is reported and a null pointer returned if the FitsChan does
+*     not contain usable WCS information with the specified encoding.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     encoding
+*        The encoding to use.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+
+*  Returned Value:
+*     A pointer to a new FitsStore, or NULL if an error has occurred. The
+*     FitsStore should be released using FreeStore function when it is no
+*     longer needed.
+*/
+
+/* Local Variables: */
+   AstFitsChan *trans;
+   FitsStore *ret;
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Allocate memory for the new FitsStore, and store NULL pointers in it. */
+   ret = (FitsStore *) astMalloc( sizeof(FitsStore) );
+   if( ret ) {
+      ret->cname = NULL;
+      ret->ctype = NULL;
+      ret->ctype_com = NULL;
+      ret->cunit = NULL;
+      ret->ps = NULL;
+      ret->radesys = NULL;
+      ret->wcsname = NULL;
+      ret->wcsaxes = NULL;
+      ret->pc = NULL;
+      ret->cdelt = NULL;
+      ret->crpix = NULL;
+      ret->crval = NULL;
+      ret->equinox = NULL;
+      ret->latpole = NULL;
+      ret->lonpole = NULL;
+      ret->mjdobs = NULL;
+      ret->mjdavg = NULL;
+      ret->dut1 = NULL;
+      ret->pv = NULL;
+      ret->specsys = NULL;
+      ret->ssyssrc = NULL;
+      ret->obsgeox = NULL;
+      ret->obsgeoy = NULL;
+      ret->obsgeoz = NULL;
+      ret->restfrq = NULL;
+      ret->restwav = NULL;
+      ret->zsource = NULL;
+      ret->velosys = NULL;
+      ret->asip = NULL;
+      ret->bsip = NULL;
+      ret->apsip = NULL;
+      ret->bpsip = NULL;
+      ret->imagfreq = NULL;
+      ret->axref = NULL;
+      ret->naxis = 0;
+      ret->timesys = NULL;
+      ret->tables = astKeyMap( " ", status );
+      ret->skyref = NULL;
+      ret->skyrefp = NULL;
+      ret->skyrefis = NULL;
+   }
+
+/* Call the routine apropriate to the encoding. */
+   if( encoding == DSS_ENCODING ){
+      DSSToStore( this, ret, method, class, status );
+
+/* All other foreign encodings are treated as variants of FITS-WCS. */
+   } else {
+
+/* Create a new FitsChan containing standard translations for any
+   non-standard keywords in the supplied FitsChan. The non-standard
+   keywords are marked as provisionally read in the supplied FitsChan. */
+      trans = SpecTrans( this, encoding, method, class, status );
+
+/* Copy the required values to the FitsStore, using keywords in "trans"
+   in preference to those in "this". */
+      WcsToStore( this, trans, ret, method, class, status );
+
+/* Delete the temporary FitsChan holding translations of non-standard
+   keywords. */
+      if( trans ) trans = (AstFitsChan *) astDelete( trans );
+
+/* Store the number of pixel axes. This is taken as the highest index used
+   in any primary CRPIX keyword. */
+      ret->naxis = GetMaxJM( &(ret->crpix), ' ', status ) + 1;
+   }
+
+/* If an error has occurred, free the returned FitsStore, and return a null
+   pointer. */
+   if( !astOK ) ret = FreeStore( ret, status );
+
+/* Return the answer. */
+   return ret;
+}
+
+static void FreeItem( double ****item, int *status ){
+/*
+*  Name:
+*     FreeItem
+
+*  Purpose:
+*     Frees all dynamically allocated memory associated with a specified
+*     item in a FitsStore.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void FreeItem( double ****item, int *status );
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Frees all dynamically allocated memory associated with the specified
+*     item in a FitsStore. A NULL pointer is stored in the FitsStore.
+
+*  Parameters:
+*     item
+*        The address of the pointer within the FitsStore which locates the
+*        arrays of values for the required keyword (eg &(store->crval) ).
+*        The array located by the supplied pointer contains a vector of
+*        pointers. Each of these pointers is associated with a particular
+*        co-ordinate version (s), and locates an array of pointers for that
+*        co-ordinate version. Each such array of pointers has an element
+*        for each intermediate axis number (j), and the pointer locates an
+*        array of axis keyword values. These arrays of keyword values have
+*        one element for every pixel axis (i) or projection parameter (m).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*    - This function attempt to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   int si;               /* Integer co-ordinate version index */
+   int j;                /* Intermediate co-ordinate axis index */
+   int oldstatus;        /* Old error status value */
+   int oldreport;        /* Old error reporting value */
+
+/* Other initialisation to avoid compiler warnings. */
+   oldreport = 0;
+
+/* Check the supplied pointer */
+   if( item && *item ){
+
+/* Start a new error reporting context. */
+      oldstatus = astStatus;
+      if( !astOK ) {
+         oldreport = astReporting( 0 );
+         astClearStatus;
+      }
+
+/* Loop round each coordinate version. */
+      for( si = 0; si < astSizeOf( (void *) *item )/sizeof(double **);
+           si++ ){
+
+/* Check the pointer stored for this co-ordinate version is not null. */
+         if( (*item)[si] ) {
+
+/* Loop round the intermediate axes. */
+            for( j = 0; j < astSizeOf( (void *) (*item)[si] )/sizeof(double *);
+                 j++ ){
+
+/* Free the pixel axis/parameter index pointer. */
+               (*item)[si][j] = (double *) astFree( (void *) (*item)[si][j] );
+            }
+
+/* Free the intermediate axes pointer */
+            (*item)[si] = (double **) astFree( (void *) (*item)[si] );
+         }
+      }
+
+/* Free the co-ordinate versions pointer */
+      *item = (double ***) astFree( (void *) *item );
+
+/* If there was an error status on entry to this function, re-instate it.
+   Otherwise, allow any new error status to remain. */
+      if( oldstatus ){
+         if( !astOK ) astClearStatus;
+         astSetStatus( oldstatus );
+         astReporting( oldreport );
+      }
+   }
+}
+
+static void FreeItemC( char *****item, int *status ){
+/*
+*  Name:
+*     FreeItemC
+
+*  Purpose:
+*     Frees all dynamically allocated memory associated with a specified
+*     string item in a FitsStore.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void FreeItemC( char *****item, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Frees all dynamically allocated memory associated with the specified
+*     string item in a FitsStore. A NULL pointer is stored in the FitsStore.
+
+*  Parameters:
+*     item
+*        The address of the pointer within the FitsStore which locates the
+*        arrays of values for the required keyword (eg &(store->ctype) ).
+*        The array located by the supplied pointer contains a vector of
+*        pointers. Each of these pointers is associated with a particular
+*        co-ordinate version (s), and locates an array of pointers for that
+*        co-ordinate version. Each such array of pointers has an element
+*        for each intermediate axis number (j), and the pointer locates an
+*        array of axis keyword values. These arrays of keyword values have
+*        one element (a char pointyer) for every pixel axis (i) or
+*        projection parameter (m).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*    - This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   int si;               /* Integer co-ordinate version index */
+   int i;                /* Intermediate co-ordinate axis index */
+   int jm;               /* Pixel co-ordinate axis or parameter index */
+   int oldstatus;        /* Old error status value */
+   int oldreport;        /* Old error reporting value */
+
+/* Other initialisation to avoid compiler warnings. */
+   oldreport = 0;
+
+/* Check the supplied pointer */
+   if( item && *item ){
+
+/* Start a new error reporting context. */
+      oldstatus = astStatus;
+      if( !astOK ) {
+         oldreport = astReporting( 0 );
+         astClearStatus;
+      }
+
+/* Loop round each coordinate version. */
+      for( si = 0; si < astSizeOf( (void *) *item )/sizeof(char ***);
+           si++ ){
+
+/* Check the pointer stored for this co-ordinate version is not null. */
+         if( (*item)[si] ) {
+
+/* Loop round the intermediate axes. */
+            for( i = 0; i < astSizeOf( (void *) (*item)[si] )/sizeof(char **);
+                 i++ ){
+
+/* Check the pointer stored for this intermediate axis is not null. */
+               if( (*item)[si][i] ) {
+
+/* Loop round the pixel axes or parameter values. */
+                  for( jm = 0; jm < astSizeOf( (void *) (*item)[si][i] )/sizeof(char *);
+                       jm++ ){
+
+/* Free the string. */
+                     (*item)[si][i][jm] = (char *) astFree( (void *) (*item)[si][i][jm] );
+                  }
+
+/* Free the pixel axes/parameter pointer */
+                  (*item)[si][i] = (char **) astFree( (void *) (*item)[si][i] );
+               }
+            }
+
+/* Free the intermediate axes pointer */
+            (*item)[si] = (char ***) astFree( (void *) (*item)[si] );
+         }
+      }
+
+/* Free the co-ordinate versions pointer */
+      *item = (char ****) astFree( (void *) *item );
+
+/* If there was an error status on entry to this function, re-instate it.
+   Otherwise, allow any new error status to remain. */
+      if( oldstatus ){
+         if( !astOK ) astClearStatus;
+         astSetStatus( oldstatus );
+         astReporting( oldreport );
+      }
+   }
+}
+
+static FitsStore *FreeStore( FitsStore *store, int *status ){
+/*
+*  Name:
+*     FreeStore
+
+*  Purpose:
+*     Free dynamic arrays stored in a FitsStore structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     FitsStore *FreeStore( FitsStore *store, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function frees all dynamically allocated arrays stored in the
+*     supplied FitsStore structure, and returns a NULL pointer.
+
+*  Parameters:
+*     store
+*        Pointer to the structure to clean.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function attempts to execute even if an error exists on entry.
+*/
+
+/* Return if no FitsStore was supplied. */
+   if( !store ) return NULL;
+
+/* Free each of the dynamic arrays stored in the FitsStore. */
+   FreeItemC( &(store->cname), status );
+   FreeItemC( &(store->ctype), status );
+   FreeItemC( &(store->ctype_com), status );
+   FreeItemC( &(store->cunit), status );
+   FreeItemC( &(store->radesys), status );
+   FreeItemC( &(store->wcsname), status );
+   FreeItemC( &(store->specsys), status );
+   FreeItemC( &(store->ssyssrc), status );
+   FreeItemC( &(store->ps), status );
+   FreeItemC( &(store->timesys), status );
+   FreeItem( &(store->pc), status );
+   FreeItem( &(store->cdelt), status );
+   FreeItem( &(store->crpix), status );
+   FreeItem( &(store->crval), status );
+   FreeItem( &(store->equinox), status );
+   FreeItem( &(store->latpole), status );
+   FreeItem( &(store->lonpole), status );
+   FreeItem( &(store->mjdobs), status );
+   FreeItem( &(store->dut1), status );
+   FreeItem( &(store->mjdavg), status );
+   FreeItem( &(store->pv), status );
+   FreeItem( &(store->wcsaxes), status );
+   FreeItem( &(store->obsgeox), status );
+   FreeItem( &(store->obsgeoy), status );
+   FreeItem( &(store->obsgeoz), status );
+   FreeItem( &(store->restfrq), status );
+   FreeItem( &(store->restwav), status );
+   FreeItem( &(store->zsource), status );
+   FreeItem( &(store->velosys), status );
+   FreeItem( &(store->asip), status );
+   FreeItem( &(store->bsip), status );
+   FreeItem( &(store->apsip), status );
+   FreeItem( &(store->bpsip), status );
+   FreeItem( &(store->imagfreq), status );
+   FreeItem( &(store->axref), status );
+   store->tables = astAnnul( store->tables );
+   FreeItem( &(store->skyref), status );
+   FreeItem( &(store->skyrefp), status );
+   FreeItemC( &(store->skyrefis), status );
+   return (FitsStore *) astFree( (void *) store );
+}
+
+static char *FormatKey( const char *key, int c1, int c2, char s, int *status ){
+/*
+*  Name:
+*     FormatKey
+
+*  Purpose:
+*     Format a keyword name with indices and co-ordinate version character.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     char *FormatKey( const char *key, int c1, int c2, char s, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function formats a keyword name by including the supplied
+*     axis/parameter indices and co-ordinate version character.
+
+*  Parameters:
+*     key
+*        The base name of the keyword (e.g. "CD", "CRVAL", etc).
+*     c1
+*        An integer value to append to the end of the keyword. Ignored if
+*        less than zero.
+*     c2
+*        A second integer value to append to the end of the keyword. Ignored if
+*        less than zero. This second integer is preceded by an underscore.
+*     s
+*        The co-ordinate version character to append to the end of the
+*        final string. Ignored if blank.
+*     status
+*        Pointer to the inherited status variable.
+*  Returned Value;
+*     A pointer to a static character buffer containing the final string.
+*     NULL if an error occurs.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   char *ret;
+   int len;
+   int nc;
+
+/* Initialise */
+   ret = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return ret;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* No characters stored yet. A value of -1 is used to indicate that an
+   error has occurred. */
+   len = 0;
+
+/* Store the supplied keyword base name. */
+   if( len >= 0 && ( nc = sprintf( formatkey_buff + len, "%s", key ) ) >= 0 ){
+      len += nc;
+   } else {
+      len = -1;
+   }
+
+/* If index c1 has been supplied, append it to the end of the string. */
+   if( c1 >= 0 ) {
+      if( len >= 0 && ( nc = sprintf( formatkey_buff + len, "%d", c1 ) ) >= 0 ){
+         len += nc;
+      } else {
+         len = -1;
+      }
+
+/* If index c2 has been supplied, append it to the end of the string,
+   preceded by an underscore. */
+      if( c2 >= 0 ) {
+         if( len >= 0 && ( nc = sprintf( formatkey_buff + len, "_%d", c2 ) ) >= 0 ){
+            len += nc;
+         } else {
+            len = -1;
+         }
+      }
+   }
+
+/* If a co-ordinate version character has been supplied, append it to the end
+   of the string. */
+   if( s != ' ' ) {
+      if( len >= 0 && ( nc = sprintf( formatkey_buff + len, "%c", s ) ) >= 0 ){
+         len += nc;
+      } else {
+         len = -1;
+      }
+   }
+
+/* Report an error if necessary */
+   if( len < 0 && astOK ) {
+      astError( AST__INTER, "FormatKey(fitschan): AST internal error; failed "
+                "to format the keyword %s with indices %d and %d, and "
+                "co-ordinate version %c.", status, key, c1, c2, s );
+      ret = NULL;
+   } else {
+      ret = formatkey_buff;
+   }
+   return formatkey_buff;
+}
+
+static AstObject *FsetFromStore( AstFitsChan *this, FitsStore *store,
+                                 const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     FsetFromStore
+
+*  Purpose:
+*     Create a FrameSet using the the information previously stored in
+*     the suppllied FitsStore structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     AstObject *FsetFromStore( AstFitsChan *this, FitsStore *store,
+*                               const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function creates a new FrameSet containing WCS information
+*     stored in the supplied FitsStore. A null pointer is returned and no
+*     error is reported if this is not possible.
+
+*  Parameters:
+*     this
+*        The FitsChan from which the keywords were read. Warning messages
+*        are added to this FitsChan if the celestial co-ordinate system is
+*        not recognized.
+*     store
+*        Pointer to the FitsStore.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new FrameSet, or a null pointer if no FrameSet
+*     could be constructed.
+
+*  Notes:
+*     -  The pixel Frame is given a title of "Pixel Coordinates", and
+*     each axis in the pixel Frame is given a label of the form "Pixel
+*     axis <n>", where <n> is the axis index (starting at one).
+*     -  The FITS CTYPE keyword values are used to set the labels for any
+*     non-celestial axes in the physical coordinate Frames, and the FITS
+*     CUNIT keywords are used to set the corresponding units strings.
+*     -  On exit, the pixel Frame is the base Frame, and the physical
+*     Frame derived from the primary axis descriptions is the current Frame.
+*     - Extra Frames are added to hold any secondary axis descriptions. All
+*     axes within such a Frame refer to the same coordinate version ('A',
+*     'B', etc).
+*/
+
+/* Local Variables: */
+   AstFrame *frame;   /* Pointer to pixel Frame */
+   AstFrameSet *ret;  /* Pointer to returned FrameSet */
+   char buff[ 20 ];   /* Buffer for axis label */
+   char s;            /* Co-ordinate version character */
+   int i;             /* Pixel axis index */
+   int physical;      /* Index of primary physical co-ordinate Frame */
+   int pixel;         /* Index of pixel Frame in returned FrameSet */
+   int use;           /* Has this co-ordinate version been used? */
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return (AstObject *) ret;
+
+/* Only proceed if there are some axes. */
+   if( store->naxis ) {
+
+/* Create a Frame describing the pixel coordinate system. Give it the Domain
+   GRID. */
+      frame = astFrame( store->naxis, "Title=Pixel Coordinates,Domain=GRID", status );
+
+/* Store labels for each pixel axis. */
+      if( astOK ){
+         for( i = 0; i < store->naxis; i++ ){
+            sprintf( buff, "Pixel axis %d", i + 1 );
+            astSetLabel( frame, i, buff );
+         }
+      }
+
+/* Create the FrameSet initially holding just the pixel coordinate frame
+   (this becomes the base Frame). */
+      ret = astFrameSet( frame, "", status );
+
+/* Annul the pointer to the pixel coordinate Frame. */
+      frame = astAnnul( frame );
+
+/* Get the index of the pixel Frame in the FrameSet. */
+      pixel = astGetCurrent( ret );
+
+/* Produce the Frame describing the primary axis descriptions, and add it
+   into the FrameSet. */
+      AddFrame( this, ret, pixel, store->naxis, store, ' ', method, class, status );
+
+/* Get the index of the primary physical co-ordinate Frame in the FrameSet. */
+      physical = astGetCurrent( ret );
+
+/* Loop, producing secondary axis Frames for each of the co-ordinate
+   versions stored in the FitsStore. */
+      for( s = 'A'; s <= GetMaxS( &(store->crval), status ) && astOK; s++ ){
+
+/* Only use this co-ordinate version character if any of the required
+   keywords (for any axis) are stored in the FitsStore. */
+         use = 0;
+         for( i = 0; i < store->naxis; i++ ){
+            if( GetItem( &(store->crval), i, 0, s, NULL, method, class, status ) != AST__BAD ||
+                GetItem( &(store->crpix), 0, i, s, NULL, method, class, status ) != AST__BAD ||
+                GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status ) != NULL ){
+               use = 1;
+               break;
+            }
+         }
+
+/* If this co-ordinate version has been used, add a Frame to the returned
+   FrameSet holding this co-ordinate version. */
+         if( use ) AddFrame( this, ret, pixel, store->naxis, store, s, method, class, status );
+      }
+
+/* Ensure the pixel Frame is the Base Frame and the primary physical
+   Frame is the Current Frame. */
+      astSetBase( ret, pixel );
+      astSetCurrent( ret, physical );
+
+/* Remove any unneeded Frames that hold a FITS representation of offset
+   coordinates. */
+      TidyOffsets( ret, status );
+
+/* If an error has occurred, free the returned FrameSet and return a null
+   pointer. */
+      if( !astOK ) ret = astAnnul( ret );
+   }
+
+/* Return the answer. */
+   return (AstObject *) ret;
+}
+
+static FitsStore *FsetToStore( AstFitsChan *this, AstFrameSet *fset, int naxis,
+                               double *dim, int encoding, const char *class,
+                               const char *method, int *status ){
+
+/*
+*  Name:
+*     FsetToStore
+
+*  Purpose:
+*     Fill a FitsStore structure with a description of the supplied
+*     FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     FitsStore *FsetToStore( AstFitsChan *this, AstFrameSet *fset, int naxis,
+*                             double *dim, int encoding, const char *class,
+*                             const char *method, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function creates a new FitsStore containing WCS information
+*     read from the supplied FitsChan using the specified encoding. An
+*     error is reported and a null pointer returned if the FitsChan does
+*     not contain usable WCS information with the specified encoding.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     fset
+*        Pointer to the FrameSet.
+*     naxis
+*        The number of axes in the Base Frame of the supplied FrameSet.
+*     dim
+*        Pointer to an array of pixel axis dimensions. Individual elements
+*        will be AST__BAD if dimensions are not known.
+*     encoding
+*        The encoding being used.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a new FitsStore, or NULL if an error has occurred. The
+*     FitsStore should be released using FreeStore function when it is no
+*     longer needed.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the AST error status set, or if it should fail for any
+*     reason.
+*     - The Base Frame in the FrameSet is used as the pixel Frame, and
+*     the Current Frame is used to create the primary axis descriptions.
+*     Attempts are made to create secondary axis descriptions for any
+*     other Frames in the FrameSet (up to a total of 26).
+*/
+
+/* Local Variables: */
+   AstFrame *frame;     /* A Frame */
+   const char *id;      /* Frame Ident string */
+   int nfrm;            /* Number of Frames in FrameSet */
+   char *sid;           /* Pointer to array of version letters */
+   int frms[ 'Z' + 1 ]; /* Array of Frame indices */
+   FitsStore *ret;      /* Returned FitsStore */
+   char s;              /* Next available co-ordinate version character */
+   char s0;             /* Co-ordinate version character */
+   int ibase;           /* Base Frame index */
+   int icurr;           /* Current Frame index */
+   int ifrm;            /* Next Frame index */
+   int isoff;           /* Is the Frame an offset SkyFrame? */
+   int primok;          /* Primary Frame stored succesfully? */
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Allocate memory for the new FitsStore, and store NULL pointers in it. */
+   ret = (FitsStore *) astMalloc( sizeof(FitsStore) );
+   if( astOK ) {
+      ret->cname = NULL;
+      ret->ctype = NULL;
+      ret->ctype_com = NULL;
+      ret->cunit = NULL;
+      ret->ps = NULL;
+      ret->radesys = NULL;
+      ret->wcsname = NULL;
+      ret->wcsaxes = NULL;
+      ret->pc = NULL;
+      ret->cdelt = NULL;
+      ret->crpix = NULL;
+      ret->crval = NULL;
+      ret->equinox = NULL;
+      ret->latpole = NULL;
+      ret->lonpole = NULL;
+      ret->dut1 = NULL;
+      ret->mjdobs = NULL;
+      ret->mjdavg = NULL;
+      ret->pv = NULL;
+      ret->specsys = NULL;
+      ret->ssyssrc = NULL;
+      ret->obsgeox = NULL;
+      ret->obsgeoy = NULL;
+      ret->obsgeoz = NULL;
+      ret->restfrq = NULL;
+      ret->restwav = NULL;
+      ret->zsource = NULL;
+      ret->velosys = NULL;
+      ret->asip = NULL;
+      ret->bsip = NULL;
+      ret->apsip = NULL;
+      ret->bpsip = NULL;
+      ret->imagfreq = NULL;
+      ret->axref = NULL;
+      ret->naxis = naxis;
+      ret->timesys = NULL;
+      ret->tables = astKeyMap( " ", status );
+      ret->skyref = NULL;
+      ret->skyrefp = NULL;
+      ret->skyrefis = NULL;
+
+/* Obtain the index of the Base Frame (i.e. the pixel frame ). */
+      ibase = astGetBase( fset );
+
+/* Obtain the index of the Current Frame (i.e. the Frame to use as the
+   primary physical coordinate frame). */
+      icurr = astGetCurrent( fset );
+
+/* Does the current Frame contain a SkyFrame that describes offset
+   coordinates? */
+      isoff = IsSkyOff( fset, icurr, status );
+
+/* Add a description of the primary axes to the FitsStore, based on the
+   Current Frame in the FrameSet. */
+      primok = AddVersion( this, fset, ibase, icurr, ret, dim, ' ',
+                           encoding, isoff, method, class, status );
+
+/* Do not add any alternate axis descriptions if the primary axis
+   descriptions could not be produced. */
+      if( primok && astOK ) {
+
+/* Get the number of Frames in the FrameSet. */
+         nfrm = astGetNframe( fset );
+
+/* We now need to allocate a version letter to each Frame. Allocate
+   memory to hold the version letter assigned to each Frame. */
+         sid = (char *) astMalloc( ( nfrm + 1 )*sizeof( char ) );
+
+/* The frms array has an entry for each of the 26 possible version
+   letters (starting at A and ending at Z). Each entry holds the index of
+   the Frame which has been assigned that version character. Initialise
+   this array to indicate that no version letters have yet been assigned. */
+         for( s = 'A'; s <= 'Z'; s++ ) {
+            frms[ (int) s ] = 0;
+         }
+
+/* Loop round all frames (excluding the current and base and IWC Frames which
+   do not need version letters). If the Frame has an Ident attribute consisting
+   of a single upper case letter, use it as its version letter unless that
+   letter has already been given to an earlier frame. IWC Frames are not
+   written out - identify them by giving them a "sid" value of 1 (an
+   illegal FITS axis description character). */
+         for( ifrm = 1; ifrm <= nfrm; ifrm++ ){
+            sid[ ifrm ] = 0;
+            if( ifrm != icurr && ifrm != ibase ) {
+               frame = astGetFrame( fset, ifrm );
+               if( astChrMatchN( astGetDomain( frame ), "IWC", 3 ) ) {
+                  sid[ ifrm ] = 1;
+               } else {
+                  id = astGetIdent( frame );
+                  if( strlen( id ) == 1 && isupper( id[ 0 ] ) ) {
+                     if( frms[ (int) id[ 0 ] ] == 0 ) {
+                        frms[ (int) id[ 0 ] ] = ifrm;
+                        sid[ ifrm ] = id[ 0 ];
+                     }
+                  }
+               }
+               (void) astAnnul( frame );
+            }
+         }
+
+/* Now go round all the Frames again, looking for Frames which did not
+   get a version letter assigned to it on the previous loop. Assign them
+   letters now, selected them from the letters not already assigned
+   (lowest to highest). */
+         s = 'A' - 1;
+         for( ifrm = 1; ifrm <= nfrm; ifrm++ ){
+            if( ifrm != icurr && ifrm != ibase && sid[ ifrm ] != 1 ) {
+               if( sid[ ifrm ] == 0 ){
+                  while( frms[ (int) ++s ] != 0 );
+                  if( s <= 'Z' ) {
+                     sid[ ifrm ] = s;
+                     frms[ (int) s ] = ifrm;
+                  }
+               }
+            }
+         }
+
+/* If the primary headers describe offset coordinates, create an alternate
+   axis description for the correspondsing absolute coordinate system. */
+         if( isoff && ++s <= 'Z' ) {
+            (void) AddVersion( this, fset, ibase, icurr, ret, dim,
+                               s, encoding, -1, method, class, status );
+         }
+
+/* Now go through all the other Frames in the FrameSet, attempting to
+   create alternate axis descriptions for each one. */
+         for( ifrm = 1; ifrm <= nfrm; ifrm++ ){
+            s0 = sid[ ifrm ];
+            if( s0 != 0 && s0 != 1 ) {
+
+/* Does it contain an offset sky frame? */
+               isoff = IsSkyOff( fset, ifrm, status );
+
+/* Write out the Frame - offset if it is offset, absolute otherwise. */
+               (void) AddVersion( this, fset, ibase, ifrm, ret, dim,
+                                  s0, encoding, isoff, method, class, status );
+
+/* If the Frame is offset, create an extra alternate axis description for
+   the correspondsing absolute coordinate system. */
+               if( isoff && ++s <= 'Z' ) {
+                  (void) AddVersion( this, fset, ibase, ifrm, ret, dim,
+                                     s, encoding, -1, method, class, status );
+               }
+            }
+         }
+
+/* Free memory holding version letters */
+         sid = (char *) astFree( (void *) sid );
+      }
+
+/* If an error has occurred, or if the primary Frame could not be cerated,
+   free the returned FitsStore, and return a null pointer. */
+      if( !astOK || !primok ) ret = FreeStore( ret, status );
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static int GetClean( AstFitsChan *this, int *status ) {
+
+/*
+*  Name:
+*     GetClean
+
+*  Purpose:
+*     Return the value of the Clean attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int GetClean( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns the value of the Clean attribute. Since this
+*     attribute controls the behaviour of the FitsChan in the event of an
+*     error condition, it is is necessary to ignore any inherited error
+*     condition when getting the attribute value. This is why the
+*     astMAKE_GET macro is not used.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Clean value to use.
+*/
+
+/* Return if no FitsChan pointer was supplied. */
+   if ( !this ) return 0;
+
+/* Return the attribute value, supplying a default value of 0 (false). */
+   return ( this->clean == -1 ) ? 0 : (this->clean ? 1 : 0 );
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied FitsChan,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFitsChan *this;         /* Pointer to FitsChan structure */
+   FitsCard *card;            /* Pointer to next FitsCard */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the FitsChan structure. */
+   this = (AstFitsChan *) this_object;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astTSizeOf( this->warnings );
+   result += astGetObjSize( this->keyseq );
+   result += astGetObjSize( this->keywords );
+   result += astGetObjSize( this->tables );
+   card = (FitsCard *) ( this->head );
+   while( card ) {
+      result += astTSizeOf( card );
+      result += card->size;
+      result += astTSizeOf( card->comment );
+      card = GetLink( card, NEXT, "astGetObjSize", "FitsChan", status );
+      if( (void *) card == this->head ) break;
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetCDMatrix( AstFitsChan *this, int *status ){
+
+/*
+*  Name:
+*     GetCDMatrix
+
+*  Purpose:
+*     Get the value of the CDMatrix attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int GetCDMatrix( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     If the CDMatrix attribute has been set, then its value is returned.
+*     Otherwise, the supplied FitsChan is searched for keywords of the
+*     form CDi_j. If any are found a non-zero value is returned. Otherwise
+*     a zero value is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The attribute value to use.
+
+*  Notes:
+*     -  A value of zero is returned if an error has already occurred
+*     or if an error occurs for any reason within this function.
+*/
+
+/* Local Variables... */
+   int ret;            /* Returned value */
+   int icard;          /* Index of current card on entry */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* If a value has been supplied for the CDMatrix attribute, use it. */
+   if( astTestCDMatrix( this ) ) {
+      ret = this->cdmatrix;
+
+/* Otherwise, check for the existence of CDi_j keywords... */
+   } else {
+
+/* Save the current card index, and rewind the FitsChan. */
+      icard = astGetCard( this );
+      astClearCard( this );
+
+/* If the FitsChan contains any keywords with the format "CDi_j" then return
+   1. Otherwise return zero. */
+      ret = astKeyFields( this, "CD%1d_%1d", 0, NULL, NULL ) ? 1 : 0;
+
+/* Reinstate the original current card index. */
+      astSetCard( this, icard );
+   }
+
+/* Return  the result. */
+   return astOK ? ret : 0;
+}
+
+static int GetEncoding( AstFitsChan *this, int *status ){
+
+/*
+*  Name:
+*     GetEncoding
+
+*  Purpose:
+*     Get the value of the Encoding attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int GetEncoding( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     If the Encoding attribute has been set, then its value is returned.
+*     Otherwise, an attempt is made to determine the encoding scheme by
+*     looking for selected keywords within the FitsChan. Checks are made
+*     for the following keywords in the order specified, and the
+*     corresponding encoding is adopted when the first one is found ( where
+
+*     i, j and m are integers and s is a single upper case character):
+*
+*     1) Any keywords starting with "BEGAST" = Native encoding
+*     2) DELTAV and VELO-xxx (or VLSR) keywords = FITS-CLASS.
+*     3) Any AIPS spectral CTYPE values:
+
+*         Any of CDi_j, PROJP, LONPOLE, LATPOLE = FITS-AIPS++ encoding:
+*         None of the above = FITS-AIPS encoding.
+*     4) Any keywords matching PCiiijjj = FITS-PC encoding
+*     5) Any keywords matching CDiiijjj = FITS-IRAF encoding
+*     6) Any keywords matching CDi_j, AND at least one of RADECSYS, PROJPi
+*        or CmVALi = FITS-IRAF encoding
+*     7) Any keywords RADECSYS, PROJPi or CmVALi, and no CDi_j or PCi_j
+*        keywords, = FITS-PC encoding
+*     8) Any keywords matching CROTAi = FITS-AIPS encoding
+*     9) Keywords matching CRVALi = FITS-WCS encoding
+*     10) The PLTRAH keyword = DSS encoding
+*     11) If none of the above keywords are found, Native encoding is assumed.
+*
+*     For cases 2) to 9), a check is also made that the header contains
+*     at least one of each keyword CTYPE, CRPIX and CRVAL. If not, then
+*     the checking process continues to the next case. This goes some way
+*     towards ensuring that the critical keywords used to determine the
+*     encoding are part of a genuine WCS description and have not just been
+*     left in the header by accident.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The encoding scheme identifier.
+
+*  Notes:
+*     -  The function returns UNKNOWN_ENCODING if an error has already occurred
+*     or if an error occurs for any reason within this function.
+*/
+
+/* Local Variables... */
+   int hascd;          /* Any CDi_j keywords found? */
+   int haspc;          /* Any PCi_j keywords found? */
+   int haswcs;         /* Any CRVAL, CTYPE and CRPIX found? */
+   int icard;          /* Index of current card on entry */
+   int ret;            /* Returned value */
+
+/* Check the global status. */
+   if( !astOK ) return UNKNOWN_ENCODING;
+
+/* If a value has been supplied for the Encoding attribute, use it. */
+   if( astTestEncoding( this ) ) {
+      ret = this->encoding;
+
+/* Otherwise, check for the existence of certain critcal keywords... */
+   } else {
+
+/* See if the header contains some CTYPE, CRPIX and CRVAL keywords. */
+      haswcs = astKeyFields( this, "CTYPE%d", 0, NULL, NULL ) &&
+               astKeyFields( this, "CRPIX%d", 0, NULL, NULL ) &&
+               astKeyFields( this, "CRVAL%d", 0, NULL, NULL );
+
+/* See if there are any CDi_j keywords. */
+      hascd = astKeyFields( this, "CD%1d_%1d", 0, NULL, NULL );
+
+/* See if there are any PCi_j keywords. */
+      haspc = astKeyFields( this, "PC%1d_%1d", 0, NULL, NULL );
+
+/* Save the current card index, and rewind the FitsChan. */
+      icard = astGetCard( this );
+      astClearCard( this );
+
+/* If the FitsChan contains any keywords starting with "BEGAST", then return
+   "Native" encoding. */
+      if( astKeyFields( this, "BEGAST%2f", 0, NULL, NULL ) ){
+         ret = NATIVE_ENCODING;
+
+/* Otherwise, look for a FITS-CLASS signature... */
+      } else if( haswcs && LooksLikeClass( this, "astGetEncoding", "AstFitsChan", status ) ){
+         ret = FITSCLASS_ENCODING;
+
+/* Otherwise, if the FitsChan contains any CTYPE keywords which have the
+   peculiar form used by AIPS, then use "FITS-AIPS" or "FITS-AIPS++" encoding. */
+      } else if( haswcs && HasAIPSSpecAxis( this, "astGetEncoding", "AstFitsChan", status ) ){
+         if( hascd ||
+             astKeyFields( this, "PROJP%d", 0, NULL, NULL ) ||
+             astKeyFields( this, "LONPOLE", 0, NULL, NULL ) ||
+             astKeyFields( this, "LATPOLE", 0, NULL, NULL ) ) {
+            ret = FITSAIPSPP_ENCODING;
+         } else {
+            ret = FITSAIPS_ENCODING;
+         }
+
+/* Otherwise, if the FitsChan contains the "PLTRAH" keywords, use "DSS"
+   encoding. */
+      } else if( astKeyFields( this, "PLTRAH", 0, NULL, NULL ) ){
+         ret = DSS_ENCODING;
+
+/* Otherwise, if the FitsChan contains any keywords with the format
+   "PCiiijjj" then return "FITS-PC" encoding. */
+      } else if( haswcs && astKeyFields( this, "PC%3d%3d", 0, NULL, NULL ) ){
+         ret = FITSPC_ENCODING;
+
+/* Otherwise, if the FitsChan contains any keywords with the format
+   "CDiiijjj" then return "FITS-IRAF" encoding. */
+      } else if( haswcs && astKeyFields( this, "CD%3d%3d", 0, NULL, NULL ) ){
+         ret = FITSIRAF_ENCODING;
+
+/* Otherwise, if the FitsChan contains any keywords with the format
+   "CDi_j"  AND there is a RADECSYS. PROJPi or CmVALi keyword, then return
+   "FITS-IRAF" encoding. If "CDi_j" is present but none of the others
+   are, return "FITS-WCS" encoding. */
+      } else if( haswcs && hascd ) {
+         if( (  astKeyFields( this, "RADECSYS", 0, NULL, NULL ) &&
+               !astKeyFields( this, "RADESYS", 0, NULL, NULL ) ) ||
+             ( astKeyFields( this, "PROJP%d", 0, NULL, NULL ) &&
+              !astKeyFields( this, "PV%d_%d", 0, NULL, NULL ) ) ||
+             ( astKeyFields( this, "C%1dVAL%d", 0, NULL, NULL )) ){
+            ret = FITSIRAF_ENCODING;
+         } else {
+            ret = FITSWCS_ENCODING;
+         }
+
+/* Otherwise, if the FitsChan contains any keywords with the format
+   RADECSYS. PROJPi or CmVALi keyword, then return "FITS-PC" encoding,
+   so long as there are no FITS-WCS equivalent keywords. */
+      } else if( haswcs && !haspc && !hascd && (
+                   ( astKeyFields( this, "RADECSYS", 0, NULL, NULL ) &&
+                   !astKeyFields( this, "RADESYS", 0, NULL, NULL ) ) ||
+                 ( astKeyFields( this, "PROJP%d", 0, NULL, NULL ) &&
+                   !astKeyFields( this, "PV%d_%d", 0, NULL, NULL ) ) ||
+                 astKeyFields( this, "C%1dVAL%d", 0, NULL, NULL ) ) ) {
+         ret = FITSPC_ENCODING;
+
+/* Otherwise, if the FitsChan contains any keywords with the format
+   "CROTAi" then return "FITS-AIPS" encoding. */
+      } else if( haswcs && astKeyFields( this, "CROTA%d", 0, NULL, NULL ) ){
+         ret = FITSAIPS_ENCODING;
+
+/* Otherwise, if the FitsChan contains any keywords with the format
+   "CRVALi" then return "FITS-WCS" encoding. */
+      } else if( haswcs && astKeyFields( this, "CRVAL%d", 0, NULL, NULL ) ){
+         ret = FITSWCS_ENCODING;
+
+/* If none of these conditions is met, assume Native encoding. */
+      } else {
+         ret = NATIVE_ENCODING;
+      }
+
+/* Reinstate the original current card index. */
+      astSetCard( this, icard );
+   }
+
+/* Return  the encoding scheme. */
+   return astOK ? ret : UNKNOWN_ENCODING;
+}
+
+static void GetFiducialNSC( AstWcsMap *map, double *phi, double *theta, int *status ){
+/*
+*  Name:
+*     GetFiducialNSC
+
+*  Purpose:
+*     Return the Native Spherical Coordinates at the fiducial point of a
+*     WcsMap projection.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void GetFiducialNSC( AstWcsMap *map, double *phi, double *theta, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns the native spherical coords corresponding at
+*     the fiducial point of a WcsMap.
+*
+*     The values of parameters 1 and 2 on the longitude axis of the WcsMap
+*     are usually used as the native spherical coordinates of the
+*     fiducial point. The default values for these parameters are equal
+*     to the native spherical coordinates of the projection reference point.
+*     The exception is that a TPN projection always uses the default
+*     values, since the projection parameters are used to store polynomial
+*     coefficients.
+
+*  Parameters:
+*     map
+*        Pointer to the WcsMap.
+*     phi
+*        Address of a location at which to return the native spherical
+*        longitude at the fiducial point (radians).
+*     theta
+*        Address of a location at which to return the native spherical
+*        latitude at the fiducial point (radians).
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int axlon;                /* Index of longitude axis */
+
+/* Initialise */
+   *phi = AST__BAD;
+   *theta = AST__BAD;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* If this is not a TPN projection get he value of the required
+   projection parameters (the default values for these are equal to the
+   fixed native shperical coordinates at the projection reference point). */
+   if( astGetWcsType( map ) != AST__TPN ) {
+      axlon = astGetWcsAxis( map, 0 );
+      if( astGetPV( map, axlon, 0 ) != 0.0 ) {
+         *phi = AST__DD2R*astGetPV( map, axlon, 1 );
+         *theta = AST__DD2R*astGetPV( map, axlon, 2 );
+      } else {
+         *phi = astGetNatLon( map );
+         *theta = astGetNatLat( map );
+      }
+
+/* If this is a TPN projection, the returned values are always the fixed
+   native shperical coordinates at the projection reference point). */
+   } else {
+      *phi = astGetNatLon( map );
+      *theta = astGetNatLat( map );
+   }
+}
+
+static void GetFiducialPPC( AstWcsMap *map, double *x0, double *y0, int *status ){
+/*
+*  Name:
+*     GetFiducialPPC
+
+*  Purpose:
+*     Return the IWC at the fiducial point of a WcsMap projection.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void GetFiducialPPC( AstWcsMap *map, double *x0, double *y0, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns the projection plane coords corresponding to
+*     the native spherical coords of the fiducial point of a FITS-WCS
+*     header. Note, projection plane coordinates (PPC) are equal to
+*     Intermediate World Coordinates (IWC) except for cases where the
+*     fiducial point does not correspond to the projection reference point.
+*     In these cases, IWC and PPC will be connected by a translation
+*     which ensures that the fiducial point corresponds to the origin of
+*     IWC.
+*
+*     The values of parameters 1 and 2 on the longitude axis of
+*     the WcsMap are used as the native spherical coordinates of the
+*     fiducial point. The default values for these parameters are equal
+*     to the native spherical coordinates of the projection reference point.
+
+*  Parameters:
+*     map
+*        Pointer to the WcsMap.
+*     x0
+*        Address of a location at which to return the PPC X axis value at
+*        the fiducial point (radians).
+*     y0
+*        Address of a location at which to return the PPC Y axis value at
+*        the fiducial point (radians).
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;       /* Pointer to the native spherical PointSet */
+   AstPointSet *pset2;       /* Pointer to the intermediate world PointSet */
+   double **ptr1;            /* Pointer to pset1 data */
+   double **ptr2;            /* Pointer to pset2 data */
+   int axlat;                /* Index of latitude axis */
+   int axlon;                /* Index of longitude axis */
+   int i;                    /* Loop count */
+   int naxes;                /* Number of axes */
+
+/* Initialise */
+   *x0 = AST__BAD;
+   *y0 = AST__BAD;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Save number of axes in the WcsMap. */
+   naxes = astGetNin( map );
+
+/* Allocate resources. */
+   pset1 = astPointSet( 1, naxes, "", status );
+   ptr1 = astGetPoints( pset1 );
+   pset2 = astPointSet( 1, naxes, "", status );
+   ptr2 = astGetPoints( pset2 );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Get the indices of the longitude and latitude axes in WcsMap. */
+      axlon = astGetWcsAxis( map, 0 );
+      axlat = astGetWcsAxis( map, 1 );
+
+/* Use zero on all non-celestial axes. */
+      for( i = 0; i < naxes; i++ ) ptr1[ i ][ 0 ] = 0.0;
+
+/* Get the native spherical coords at the fiducial point. */
+      GetFiducialNSC( map, ptr1[ axlon ], ptr1[ axlat ], status );
+
+/* Use the inverse WcsMap to convert the native longitude and latitude of
+   the fiducial point into PPC (x,y). */
+      (void) astTransform( map, pset1, 0, pset2 );
+
+/* Return the calculated PPC coords. */
+      *x0 = ptr2[ axlon ][ 0 ];
+      *y0 = ptr2[ axlat ][ 0 ];
+   }
+
+/* Free resources. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+}
+
+static int GetFiducialWCS( AstWcsMap *wcsmap, AstMapping *map2, int colon,
+                           int colat, double *fidlon, double *fidlat, int *status ){
+/*
+*  Name:
+*     GetFiducialWCS
+
+*  Purpose:
+*     Decide on the celestial coordinates of the fiducial point.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int GetFiducialWCS( AstWcsMap wcsmap, AstMapping map2, int colon,
+*                         int colat, double *fidlon, double *fidlat, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns the celestial longitude and latitude values
+*     to use for the fiducial point. These are the values stored in FITS
+*     keywords CRVALi.
+
+*  Parameters:
+*     wcsmap
+*        The WcsMap which converts Projection Plane Coordinates into
+*        native spherical coordinates. The number of outputs from this
+*        Mapping should match the number of inputs to "map2".
+*     map2
+*        The Mapping which converts native spherical coordinates into WCS
+*        coordinates.
+*     colon
+*        The index of the celestial longitude output from "map2".
+*     colat
+*        The index of the celestial latitude output from "map2".
+*     fidlon
+*        Pointer to a location at which to return the celestial longitude
+*        value at the fiducial point. The value is returned in radians.
+*     fidlat
+*        Pointer to a location at which to return the celestial latitude
+*        value at the fiducial point. The value is returned in radians.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the fiducial point longitude or latitude could not be
+*     determined. One otherwise.
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;       /* Pointer to the native spherical PointSet */
+   AstPointSet *pset2;       /* Pointer to the WCS PointSet */
+   double **ptr1;            /* Pointer to pset1 data */
+   double **ptr2;            /* Pointer to pset2 data */
+   int axlat;                /* Index of latitude axis */
+   int axlon;                /* Index of longitude axis */
+   int iax;                  /* Axis index */
+   int naxin;                /* Number of IWC axes */
+   int naxout;               /* Number of WCS axes */
+   int ret;                  /* The returned FrameSet */
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Allocate resources. */
+   naxin = astGetNin( map2 );
+   naxout = astGetNout( map2 );
+   pset1 = astPointSet( 1, naxin, "", status );
+   ptr1 = astGetPoints( pset1 );
+   pset2 = astPointSet( 1, naxout, "", status );
+   ptr2 = astGetPoints( pset2 );
+   if( astOK ) {
+
+/* Get the indices of the latitude and longitude outputs in the WcsMap.
+   These are not necessarily the same as "colat" and "colon" because "map2"
+   may contain a PermMap. */
+      axlon = astGetWcsAxis( wcsmap, 0 );
+      axlat = astGetWcsAxis( wcsmap, 1 );
+
+/* Use zero on all non-celestial axes. */
+      for( iax = 0; iax < naxin; iax++ ) ptr1[ iax ][ 0 ] = 0.0;
+
+/* Get the native spherical coords at the fiducial point. */
+      GetFiducialNSC( wcsmap, ptr1[ axlon ], ptr1[ axlat ], status );
+
+/* The fiducial point in the celestial coordinate system is found by
+   transforming the fiducial point in native spherical co-ordinates
+   into absolute physical coordinates using map2. */
+      (void) astTransform( map2, pset1, 1, pset2 );
+
+/* Store the returned WCS values. */
+      *fidlon = ptr2[ colon ][ 0 ];
+      *fidlat = ptr2[ colat ][ 0 ];
+
+/* Indicate if we have been succesfull. */
+      if( astOK && *fidlon != AST__BAD && *fidlat != AST__BAD ) ret = 1;
+   }
+
+/* Free resources. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* Return the result. */
+   return ret;
+}
+
+static const char *GetFitsSor( const char *string, int *status ) {
+/*
+*  Name:
+*     GetFitsSor
+
+*  Purpose:
+*     Get the string used to represent an AST spectral standard of rest.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     const char *GetFitsSor( const char *string, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns a pointer to a static string which is the
+*     FITS equivalent to a given SpecFrame StdOfRest value.
+
+*  Parameters:
+*     string
+*        Pointer to a constant null-terminated string containing the
+*        SpecFrame StdOfRest value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a static null-terminated string containing the FITS
+*     equivalent to the supplied string. NULL is returned if the supplied
+*     string has no FITS equivalent.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked wth the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Compare the supplied string with SpecFrame value for which there is a
+   known FITS equivalent. */
+   if( !strcmp( string, "Topocentric" ) ){
+      result = "TOPOCENT";
+   } else if( !strcmp( string, "Geocentric" )){
+      result = "GEOCENTR";
+   } else if( !strcmp( string, "Barycentric" )){
+      result = "BARYCENT";
+   } else if( !strcmp( string, "Heliocentric" )){
+      result = "HELIOCEN";
+   } else if( !strcmp( string, "LSRK" )){
+      result = "LSRK";
+   } else if( !strcmp( string, "LSRD" )){
+      result = "LSRD";
+   } else if( !strcmp( string, "Galactic" )){
+      result = "GALACTOC";
+   } else if( !strcmp( string, "Local_group" )){
+      result = "LOCALGRP";
+   } else if( !strcmp( string, "Source" )){
+      result = "SOURCE";
+   } else {
+      result = NULL;
+   }
+
+/* Return the answer. */
+   return result;
+}
+
+static double GetItem( double ****item, int i, int jm, char s, char *name,
+                       const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     GetItem
+
+*  Purpose:
+*     Retrieve a value for a axis keyword value from a FitStore structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     double GetItem( double ****item, int i, int jm, char s, char *name,
+*                     const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The requested keyword value is retrieved from the specified array,
+*     at a position indicated by the axis and co-ordinate version.
+*     AST__BAD is returned if the array does not contain the requested
+*     value.
+
+*  Parameters:
+*     item
+*        The address of the pointer within the FitsStore which locates the
+*        arrays of values for the required keyword (eg &(store->crval) ).
+*        The array located by the supplied pointer contains a vector of
+*        pointers. Each of these pointers is associated with a particular
+*        co-ordinate version (s), and locates an array of pointers for that
+*        co-ordinate version. Each such array of pointers has an element
+*        for each intermediate axis number (i), and the pointer locates an
+*        array of axis keyword values. These arrays of keyword values have
+*        one element for every pixel axis (j) or projection parameter (m).
+*     i
+*        The zero based intermediate axis index in the range 0 to 98. Set
+*        this to zero for keywords (e.g. CRPIX) which are not indexed by
+*        intermediate axis number.
+*     jm
+*        The zero based pixel axis index (in the range 0 to 98) or parameter
+*        index (in the range 0 to WCSLIB_MXPAR-1). Set this to zero for
+*        keywords (e.g. CRVAL) which are not indexed by either pixel axis or
+*        parameter number.
+*     s
+*        The co-ordinate version character (A to Z, or space), case
+*        insensitive
+*     name
+*        A string holding a name for the item of information. A NULL
+*        pointer may be supplied, in which case it is ignored. If a
+*        non-NULL pointer is supplied, an error is reported if the item
+*        of information has not been stored, and the supplied name is
+*        used to identify the information within the error message.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The required keyword value, or AST__BAD if no value has previously
+*     been stored for the keyword (or if an error has occurred).
+*/
+
+/* Local Variables: */
+   double ret;           /* Returned keyword value */
+   int si;               /* Integer co-ordinate version index */
+
+/* Initialise */
+   ret = AST__BAD;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Convert the character co-ordinate version into an integer index, and
+   check it is within range. The primary axis description (s=' ') is
+   given index zero. 'A' is 1, 'B' is 2, etc. */
+   if( s == ' ' ) {
+      si = 0;
+   } else if( islower(s) ){
+      si = (int) ( s - 'a' ) + 1;
+   } else {
+      si = (int) ( s - 'A' ) + 1;
+   }
+   if( si < 0 || si > 26 ) {
+      astError( AST__INTER, "GetItem(fitschan): AST internal error; "
+                "co-ordinate version '%c' ( char(%d) ) is invalid.", status, s, s );
+
+/* Check the intermediate axis index is within range. */
+   } else if( i < 0 || i > 98 ) {
+      astError( AST__INTER, "GetItem(fitschan): AST internal error; "
+                "intermediate axis index %d is invalid.", status, i );
+
+/* Check the pixel axis or parameter index is within range. */
+   } else if( jm < 0 || jm > 99 ) {
+      astError( AST__INTER, "GetItem(fitschan): AST internal error; "
+                "pixel axis or parameter index %d is invalid.", status, jm );
+
+/* Otherwise, if the array holding the required keyword is not null,
+   proceed... */
+   } else if( *item ){
+
+/* Find the number of coordinate versions in the supplied array.
+   Only proceed if it encompasses the requested co-ordinate
+   version. */
+      if( astSizeOf( (void *) *item )/sizeof(double **) > si ){
+
+/* Find the number of intermediate axes in the supplied array.
+   Only proceed if it encompasses the requested intermediate axis. */
+         if( astSizeOf( (void *) (*item)[si] )/sizeof(double *) > i ){
+
+/* Find the number of pixel axes or parameters in the supplied array.
+   Only proceed if it encompasses the requested index. */
+            if( astSizeOf( (void *) (*item)[si][i] )/sizeof(double) > jm ){
+
+/* Return the required keyword value. */
+               ret = (*item)[si][i][jm];
+            }
+         }
+      }
+   }
+
+/* If required, report an error if the requested item of information has
+   not been stored. */
+   if( ret == AST__BAD && name && astOK ){
+      astError( AST__NOFTS, "%s(%s): No value can be found for %s.", status,
+                method, class, name );
+   }
+   return ret;
+}
+
+static int GetMaxJM( double ****item, char s, int *status ){
+/*
+*  Name:
+*     GetMaxJM
+
+*  Purpose:
+*     Return the largest pixel axis or parameter index stored for an
+*     numerical axis keyword value in a FitStore structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int GetMaxJM( double ****item, char s, int *status)
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The number of pixel axis numbers or projection parameters stored for
+*     a specified axis keyword is found and returned.
+
+*  Parameters:
+*     item
+*        The address of the pointer within the FitsStore which locates the
+*        arrays of values for the required keyword (eg &(store->crpix) ).
+*        The array located by the supplied pointer contains a vector of
+*        pointers. Each of these pointers is associated with a particular
+*        co-ordinate version (s), and locates an array of pointers for that
+*        co-ordinate version. Each such array of pointers has an element
+*        for each intermediate axis number (i), and the pointer locates an
+*        array of axis keyword values. These arrays of keyword values have
+*        one element for every pixel axis (j) or projection parameter (m).
+*     s
+*        The co-ordinate version character (A to Z, or space), case
+*        insensitive
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The maximum pixel axis number or projection parameter index (zero
+*     based).
+*/
+
+/* Local Variables: */
+   int jm;               /* Number of parameters/pixel axes */
+   int i;                /* Intermediate axis index */
+   int ret;              /* Returned axis index */
+   int si;               /* Integer co-ordinate version index */
+
+/* Initialise */
+   ret = -1;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* If the array holding the required keyword is not null, proceed... */
+   if( *item ){
+
+/* Convert the character co-ordinate version into an integer index, and
+   check it is within range. The primary axis description (s=' ') is
+   given index zero. 'A' is 1, 'B' is 2, etc. */
+      if( s == ' ' ) {
+         si = 0;
+      } else if( islower(s) ){
+         si = (int) ( s - 'a' ) + 1;
+      } else {
+         si = (int) ( s - 'A' ) + 1;
+      }
+      if( si < 0 || si > 26 ) {
+         astError( AST__INTER, "GetMaxJM(fitschan): AST internal error; "
+                   "co-ordinate version '%c' ( char(%d) ) is invalid.", status, s, s );
+         return ret;
+      }
+
+/* Find the number of coordinate versions in the supplied array.
+   Only proceed if it encompasses the requested co-ordinate
+   version. */
+      if( astSizeOf( (void *) *item )/sizeof(double **) > si ){
+
+/* Check that the pointer to the array of intermediate axis values is not null. */
+         if( (*item)[si] ){
+
+/* Loop round each used element in this array. */
+            for( i = 0; i < astSizeOf( (void *) (*item)[si] )/sizeof(double *);
+                 i++ ){
+               if( (*item)[si][i] ){
+
+/* Get the size of the pixel axis/projection parameter array for the
+   current intermediate axis, and subtract 1 to get the largest index. */
+                  jm = astSizeOf( (void *) (*item)[si][i] )/sizeof(double) - 1;
+
+/* Ignore any trailing unused (AST__BAD) values. */
+                  while( jm >= 0 && (*item)[si][i][jm] == AST__BAD ) jm--;
+
+/* Update the returned value if the current value is larger. */
+                  if( jm > ret ) ret = jm;
+               }
+            }
+         }
+      }
+   }
+   return ret;
+}
+
+static int GetMaxJMC( char *****item, char s, int *status ){
+/*
+*  Name:
+*     GetMaxJMC
+
+*  Purpose:
+*     Return the largest pixel axis or parameter index stored for an
+*     character-valued axis keyword value in a FitStore structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int GetMaxJMC( char *****item, char s, int *status)
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The number of pixel axis numbers or projection parameters stored for
+*     a specified axis keyword is found and returned.
+
+*  Parameters:
+*     item
+*        The address of the pointer within the FitsStore which locates the
+*        arrays of values for the required keyword (eg &(store->ctype) ).
+*        The array located by the supplied pointer contains a vector of
+*        pointers. Each of these pointers is associated with a particular
+*        co-ordinate version (s), and locates an array of pointers for that
+*        co-ordinate version. Each such array of pointers has an element
+*        for each intermediate axis number (i), and the pointer locates an
+*        array of axis keyword string pointers. These arrays of keyword
+*        string pointers have one element for every pixel axis (j) or
+*        projection parameter (m).
+*     s
+*        The co-ordinate version character (A to Z, or space), case
+*        insensitive
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The maximum pixel axis number or projection parameter index (zero
+*     based).
+*/
+
+/* Local Variables: */
+   int jm;               /* Number of parameters/pixel axes */
+   int i;                /* Intermediate axis index */
+   int ret;              /* Returned axis index */
+   int si;               /* Integer co-ordinate version index */
+
+/* Initialise */
+   ret = -1;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* If the array holding the required keyword is not null, proceed... */
+   if( *item ){
+
+/* Convert the character co-ordinate version into an integer index, and
+   check it is within range. The primary axis description (s=' ') is
+   given index zero. 'A' is 1, 'B' is 2, etc. */
+      if( s == ' ' ) {
+         si = 0;
+      } else if( islower(s) ){
+         si = (int) ( s - 'a' ) + 1;
+      } else {
+         si = (int) ( s - 'A' ) + 1;
+      }
+      if( si < 0 || si > 26 ) {
+         astError( AST__INTER, "GetMaxJMC(fitschan): AST internal error; "
+                   "co-ordinate version '%c' ( char(%d) ) is invalid.", status, s, s );
+         return ret;
+      }
+
+/* Find the number of coordinate versions in the supplied array.
+   Only proceed if it encompasses the requested co-ordinate
+   version. */
+      if( astSizeOf( (void *) *item )/sizeof(char ***) > si ){
+
+/* Check that the pointer to the array of intermediate axis values is not null. */
+         if( (*item)[si] ){
+
+/* Loop round each used element in this array. */
+            for( i = 0; i < astSizeOf( (void *) (*item)[si] )/sizeof(char **);
+                 i++ ){
+               if( (*item)[si][i] ){
+
+/* Get the size of the pixel axis/projection parameter array for the
+   current intermediate axis, and subtract 1 to get the largest index. */
+                  jm = astSizeOf( (void *) (*item)[si][i] )/sizeof(char *) - 1;
+
+/* Ignore any trailing unused (NULL) values. */
+                  while( jm >= 0 && (*item)[si][i][jm] == NULL ) jm--;
+
+/* Update the returned value if the current value is larger. */
+                  if( jm > ret ) ret = jm;
+               }
+            }
+         }
+      }
+   }
+   return ret;
+}
+
+static int GetMaxI( double ****item, char s, int *status ){
+/*
+*  Name:
+*     GetMaxI
+
+*  Purpose:
+*     Return the largest WCS axis index stored for an axis keyword value in
+*     a FitStore structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int GetMaxJM( double ****item, char s)
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The number of Wcs axis numbers stored for a specified axis keyword is
+*     found and returned.
+
+*  Parameters:
+*     item
+*        The address of the pointer within the FitsStore which locates the
+*        arrays of values for the required keyword (eg &(store->crval) ).
+*        The array located by the supplied pointer contains a vector of
+*        pointers. Each of these pointers is associated with a particular
+*        co-ordinate version (s), and locates an array of pointers for that
+*        co-ordinate version. Each such array of pointers has an element
+*        for each intermediate axis number (i), and the pointer locates an
+*        array of axis keyword values. These arrays of keyword values have
+*        one element for every pixel axis (j) or projection parameter (m).
+*     s
+*        The co-ordinate version character (A to Z, or space), case
+*        insensitive
+
+*  Returned Value:
+*     The maximum WCS axis index (zero based).
+*/
+
+/* Local Variables: */
+   int ret;              /* Returned axis index */
+   int si;               /* Integer co-ordinate version index */
+
+/* Initialise */
+   ret = -1;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* If the array holding the required keyword is not null, proceed... */
+   if( *item ){
+
+/* Convert the character co-ordinate version into an integer index, and
+   check it is within range. The primary axis description (s=' ') is
+   given index zero. 'A' is 1, 'B' is 2, etc. */
+      if( s == ' ' ) {
+         si = 0;
+      } else if( islower(s) ){
+         si = (int) ( s - 'a' ) + 1;
+      } else {
+         si = (int) ( s - 'A' ) + 1;
+      }
+      if( si < 0 || si > 26 ) {
+         astError( AST__INTER, "GetMaxI(fitschan): AST internal error; "
+                   "co-ordinate version '%c' ( char(%d) ) is invalid.", status, s, s );
+         return ret;
+      }
+
+/* Find the number of coordinate versions in the supplied array.
+   Only proceed if it encompasses the requested co-ordinate
+   version. */
+      if( astSizeOf( (void *) *item )/sizeof(double **) > si ){
+
+/* Check that the pointer to the array of intermediate axis values is not null. */
+         if( (*item)[si] ){
+
+/* Get the size of the intermediate axis array and subtract 1 to get the largest
+   index. */
+            ret = astSizeOf( (void *) (*item)[si] )/sizeof(double *) - 1;
+
+/* Ignore any trailing unused (NULL) values. */
+            while( ret >= 0 && (*item)[si][ret] == NULL ) ret--;
+         }
+      }
+   }
+   return ret;
+}
+
+static char GetMaxS( double ****item, int *status ){
+/*
+*  Name:
+*     GetMaxS
+
+*  Purpose:
+*     Return the largest (i.e. closest to Z) coordinate version character
+*     stored for a axis keyword value in a FitStore structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     char GetMaxS( double ****item, int *status)
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The largest (i.e. closest to Z) coordinate version character
+*     stored for a axis keyword value in a FitStore structure is found
+*     and returned.
+
+*  Parameters:
+*     item
+*        The address of the pointer within the FitsStore which locates the
+*        arrays of values for the required keyword (eg &(store->crval) ).
+*        The array located by the supplied pointer contains a vector of
+*        pointers. Each of these pointers is associated with a particular
+*        co-ordinate version (s), and locates an array of pointers for that
+*        co-ordinate version. Each such array of pointers has an element
+*        for each intermediate axis number (i), and the pointer locates an
+*        array of axis keyword values. These arrays of keyword values have
+*        one element for every pixel axis (j) or projection parameter (m).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The highest coordinate version character.
+*/
+
+/* Local Variables: */
+   char ret;              /* Returned axis index */
+   int si;                /* Integer index into alphabet */
+
+/* Initialise */
+   ret = ' ';
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* If the array holding the required keyword is not null, proceed... */
+   if( *item ){
+
+/* Find the length of this array, and subtract 1 to get the largest index
+   in the array. */
+      si = astSizeOf( (void *) *item )/sizeof(double **) - 1;
+
+/* Ignore any trailing null (i.e. unused) values. */
+      while( si >= 0 && !(*item)[si] ) si--;
+
+/* Store the corresponding character */
+      if( si == 0 ) {
+         ret = ' ';
+      } else {
+         ret = 'A' + si - 1;
+      }
+   }
+   return ret;
+}
+
+static char *GetItemC( char *****item, int i, int jm, char s, char *name,
+                       const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     GetItemC
+
+*  Purpose:
+*     Retrieve a string value for a axis keyword value from a FitStore
+*     structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     char *GetItemC( char *****item, int i, int jm, char s, char *name,
+*                     const char *method, const char *class, int *status  )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The requested keyword string value is retrieved from the specified
+*     array, at a position indicated by the axis and co-ordinate version.
+*     NULL is returned if the array does not contain the requested
+*     value.
+
+*  Parameters:
+*     item
+*        The address of the pointer within the FitsStore which locates the
+*        arrays of values for the required keyword (eg &(store->ctype) ).
+*        The array located by the supplied pointer contains a vector of
+*        pointers. Each of these pointers is associated with a particular
+*        co-ordinate version (s), and locates an array of pointers for that
+*        co-ordinate version. Each such array of pointers has an element
+*        for each intermediate axis number (i), and the pointer locates an
+*        array of axis keyword string pointers. These arrays of keyword
+*        string pointers have one element for every pixel axis (j) or
+*        projection parameter (m).
+*     i
+*        The zero based intermediate axis index in the range 0 to 98. Set
+*        this to zero for keywords (e.g. CRPIX) which are not indexed by
+*        intermediate axis number.
+*     jm
+*        The zero based pixel axis index (in the range 0 to 98) or parameter
+*        index (in the range 0 to WCSLIB__MXPAR-1). Set this to zero for
+*        keywords (e.g. CTYPE) which are not indexed by either pixel axis or
+*        parameter number.
+*     s
+*        The co-ordinate version character (A to Z, or space), case
+*        insensitive
+*     name
+*        A string holding a name for the item of information. A NULL
+*        pointer may be supplied, in which case it is ignored. If a
+*        non-NULL pointer is supplied, an error is reported if the item
+*        of information has not been stored, and the supplied name is
+*        used to identify the information within the error message.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the required keyword string value, or NULL if no value
+*     has previously been stored for the keyword (or if an error has
+*     occurred).
+*/
+
+/* Local Variables: */
+   char *ret;            /* Returned keyword value */
+   int si;               /* Integer co-ordinate version index */
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Convert the character co-ordinate version into an integer index, and
+   check it is within range. The primary axis description (s=' ') is
+   given index zero. 'A' is 1, 'B' is 2, etc. */
+   if( s == ' ' ) {
+      si = 0;
+   } else if( islower(s) ){
+      si = (int) ( s - 'a' ) + 1;
+   } else {
+      si = (int) ( s - 'A' ) + 1;
+   }
+   if( si < 0 || si > 26 ) {
+      astError( AST__INTER, "GetItemC(fitschan): AST internal error; "
+                "co-ordinate version '%c' ( char(%d) ) is invalid.", status, s, s );
+
+/* Check the intermediate axis index is within range. */
+   } else if( i < 0 || i > 98 ) {
+      astError( AST__INTER, "GetItemC(fitschan): AST internal error; "
+                "intermediate axis index %d is invalid.", status, i );
+
+/* Check the pixel axis or parameter index is within range. */
+   } else if( jm < 0 || jm > 99 ) {
+      astError( AST__INTER, "GetItem(fitschan): AST internal error; "
+                "pixel axis or parameter index %d is invalid.", status, jm );
+
+/* Otherwise, if the array holding the required keyword is not null,
+   proceed... */
+   } else if( *item ){
+
+/* Find the number of coordinate versions in the supplied array.
+   Only proceed if it encompasses the requested co-ordinate
+   version. */
+      if( astSizeOf( (void *) *item )/sizeof(char ***) > si ){
+
+/* Find the number of intermediate axes in the supplied array.
+   Only proceed if it encompasses the requested intermediate axis. */
+         if( astSizeOf( (void *) (*item)[si] )/sizeof(char **) > i ){
+
+/* Find the number of pixel axes or parameters in the supplied array.
+   Only proceed if it encompasses the requested index. */
+            if( astSizeOf( (void *) (*item)[si][i] )/sizeof(char *) > jm ){
+
+/* Return the required keyword value. */
+               ret = (*item)[si][i][jm];
+            }
+         }
+      }
+   }
+
+/* If required, report an error if the requested item of information has
+   not been stored. */
+   if( !ret && name && astOK ){
+      astError( AST__NOFTS, "%s(%s): No value can be found for %s.", status,
+                method, class, name );
+   }
+   return ret;
+}
+
+static AstFitsTable *GetNamedTable( AstFitsChan *this, const char *extname,
+                                    int extver, int extlevel, int report,
+                                    const char *method, int *status ){
+
+/*
+*  Name:
+*     GetNamedTable
+
+*  Purpose:
+*     Return a FitsTable holding the contents of a named FITS binary table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     AstFitsTable *GetNamedTable( AstFitsChan *this, const char *extname,
+*                                  int extver, int extlevel, int report,
+*                                  const char *method, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     If a table source function has been registered with FitsChan (using
+*     astTableSource), invoke it to read the required table from the external
+*     FITS file. If the extension is available in the FITS file, this will
+*     put a FitsTable into the "tables" KeyMap in the FitsChan structure,
+*     using the FITS extension name as the key - this will replace any
+*     FitsTable already present in the KeyMap with the same key. Finally,
+*     return a pointer to the FitsTable stored in the KeyMap - if any.
+*
+*     This strategy allows the astPutTables or astPutTable method to be used
+*     as an alternative to registering a table source function with the
+*     FitsChan.  Note, any table read using the source function is used
+*     in preference to any table stored in the FitsChan by an earlier call
+*     to astPutTables/astPutTable.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     extname
+*        The key associated with the required table - should be the name
+*        of the FITS extension containing the binary table.
+*     extver
+*        The FITS "EXTVER" value for the required table.
+*     extlevel
+*        The FITS "EXTLEVEL" value for the required table.
+*     report
+*        If non-zero, report an error if the named table is not available.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the FitsTable, or NULL if the table is not avalable.
+*/
+
+/* Local Variables: */
+   AstFitsTable *ret;
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Fitrst attempt to read the required table from the external FITS file.
+   Only proceed if table source function and wrapper have been supplied
+   using astTableSource. */
+   if( this->tabsource && this->tabsource_wrap ){
+
+/* Invoke the table source function asking it to place the required FITS
+   table in the FitsChan. This is an externally supplied function which may
+   not be thread-safe, so lock a mutex first. Note, a cloned FitsChan pointer
+   is sent to the table source function since the table source function will
+   annul the supplied FitsChan pointer. Also store the channel data
+   pointer in a global variable so that it can be accessed in the source
+   function using macro astChannelData. */
+      astStoreChannelData( this );
+      LOCK_MUTEX2;
+      ( *this->tabsource_wrap )( this->tabsource, astClone( this ), extname,
+                                 extver, extlevel, status );
+      UNLOCK_MUTEX2;
+   }
+
+/* Now get a pointer to the required FitsTable, stored as an entry in the
+   "tables" KeyMap. Report an error if required. */
+   if( ! (this->tables) || !astMapGet0A( this->tables, extname, &ret ) ){
+      if( report && astOK ) {
+         astError( AST__NOTAB, "%s(%s): Failed to read FITS binary table "
+                   "from extension '%s' (extver=%d, extlevel=%d).", status,
+                   method, astGetClass( this ), extname, extver, extlevel );
+      }
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static AstKeyMap *GetTables( AstFitsChan *this, int *status ) {
+
+/*
+*++
+*  Name:
+c     astGetTables
+f     AST_GETTABLES
+
+*  Purpose:
+*     Retrieve any FitsTables currently in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+c     AstKeyMap *astGetTables( AstFitsChan *this )
+f     RESULT = AST_GETTABLES( THIS, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     If the supplied FitsChan currently contains any tables, then this
+*     function returns a pointer to a KeyMap. Each entry in the KeyMap
+*     is a pointer to a FitsTable holding the data for a FITS binary
+*     table. The key used to access each entry is the FITS extension
+*     name in which the table should be stored.
+*
+*     Tables can be present in a FitsChan as a result either of using the
+c     astPutTable (or astPutTables)
+f     AST_PUTTABLE (or AST_PUTTABLES)
+*     method to store existing tables in the FitsChan, or of using the
+c     astWrite
+f     AST_WRITE
+*     method to write a FrameSet to the FitsChan. For the later case, if
+*     the FitsChan "TabOK" attribute is positive and the FrameSet requires
+*     a look-up table to describe one or more axes, then the "-TAB"
+*     algorithm code described in FITS-WCS paper III is used and the table
+*     values are stored in the FitsChan in the form of a FitsTable object
+*     (see the documentation for the "TabOK" attribute).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetTables()
+f     AST_GETTABLES = INTEGER
+*        A pointer to a deep copy of the KeyMap holding the tables currently
+*        in the FitsChan, or
+c        NULL
+f        AST__NULL
+*        if the FitsChan does not contain any tables. The returned
+*        pointer should be annulled using
+c        astAnnul
+f        AST_ANNUL
+*        when no longer needed.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *result;      /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the FitsChan contains any tables, return a pointer to a copy of
+   the KeyMap containing them. Otherwise, return a NULL pointer. */
+   if( this->tables && astMapSize( this->tables ) > 0 ) {
+      result = astCopy( this->tables );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetUsedPolyTan( AstFitsChan *this, AstFitsChan *out, int latax,
+                           int lonax, char s, const char *method,
+                           const char *class, int *status ){
+/*
+*  Name:
+*     GetUsedPolyTan
+
+*  Purpose:
+*     Get the value to use for the PolyTan attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int GetUsedPolyTan( AstFitsChan *this, AstFitsChan *out, int latax,
+*                         int lonax, char s, const char *method,
+*                         const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     If the PolyTan attribute is zero or positive, then its value is
+*     returned. If it is negative, the supplied FitsChan is searched for
+*     keywords of the form PVi_m. If any are found on the latitude axis,
+*     or if any are found on the longitude axis with "m" > 4, +1 is
+*     returned (meaning "use the distorted TAN conventio"). Otherwise 0
+*     is returned (meaning "use the standard TAN convention").
+*
+*     If all the PVi_m values for m > 0 on either axis are zero, a warning is
+*     issued and zero is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     out
+*        Pointer to a secondary FitsChan. If the PV values in "this" are
+*        found to be unusable, they will be marked as used in both "this"
+*        and "out".
+*     latax
+*        The one-based index of the latitude axis within the FITS header.
+*     lonax
+*        The one-based index of the longitude axis within the FITS header.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The attribute value to use.
+
+*  Notes:
+*     -  A value of zero is returned if an error has already occurred
+*     or if an error occurs for any reason within this function.
+*/
+
+/* Local Variables... */
+   char template[ 20 ];
+   double pval;
+   int lbnd_lat;
+   int lbnd_lon;
+   int m;
+   int nfound1;
+   int nfound2;
+   int ok;
+   int ret;
+   int ubnd_lat;
+   int ubnd_lon;
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Get the value of the PolyTan attribute. */
+   ret = astGetPolyTan( this );
+
+/* If it is negative, we examine the FitsChan to see which convention to
+   use. */
+   if( ret < 0 ) {
+      ret = 0;
+
+/* Search the FitsChan for latitude PV cards. */
+      if( s != ' ' ) {
+         sprintf( template, "PV%d_%%d%c", latax, s );
+      } else {
+         sprintf( template, "PV%d_%%d", latax );
+      }
+      nfound1 = astKeyFields( this, template, 1, &ubnd_lat, &lbnd_lat );
+
+/* Search the FitsChan for longitude PV cards. */
+      if( s != ' ' ) {
+         sprintf( template, "PV%d_%%d%c", lonax, s );
+      } else {
+         sprintf( template, "PV%d_%%d", lonax );
+      }
+      nfound2 = astKeyFields( this, template, 1, &ubnd_lon, &lbnd_lon );
+
+/* If any were found with "m" value greater than 4, assume the distorted
+   TAN convention is in use. Otherwise assume the stdanrd TAN convention is
+   in use. */
+      if( nfound1 || ( nfound2 && ubnd_lon > 4 ) ) ret = 1;
+
+/* If the distorted TAN convention is to be used, check that at least one
+   of the PVi_m values is non-zero on each axis. We ignore the PVi_0
+   (constant) terms in this check. */
+      if( ret > 0 ) {
+
+/* Do the latitude axis first, skipping the first (constant) term. Assume
+   that all latitude pV values are zero until we find one that is not. */
+         ok = 0;
+         for( m = 1; m <= ubnd_lat && !ok; m++ ) {
+
+/* Form the PVi_m keyword name. */
+            if( s != ' ' ) {
+               sprintf( template, "PV%d_%d%c", latax, m, s );
+            } else {
+               sprintf( template, "PV%d_%d", latax, m );
+            }
+
+/* Get it's value. */
+            if( ! GetValue( this, template, AST__FLOAT, &pval, 0, 0,
+                            method,  class, status ) ) {
+
+/* If the PVi_m header is not present in the FitsChan, use a default value. */
+               pval = ( m == 1 ) ? 1.0 : 0.0;
+            }
+
+/* If the PVi_m header has a non-zero value, we can leave the loop. */
+            if( pval != 0.0 ) ok = 1;
+         }
+
+/* If all the latitude PVi_m values are zero, issue a warning and return
+   zero, indicating that a simple undistorted TAN projection should be used. */
+         if( !ok ) {
+            Warn( this, "badpv", "This FITS header describes a distorted TAN "
+                  "projection, but all the distortion coefficients (the "
+                  "PVi_m headers) on the latitude axis are zero.", method,
+                  class, status );
+            ret = 0;
+
+
+/* Also, delete the PV keywords so that no attempt is made to use them. */
+            for( m = 1; m <= ubnd_lat; m++ ) {
+               if( s != ' ' ) {
+                  sprintf( template, "PV%d_%d%c", latax, m, s );
+               } else {
+                  sprintf( template, "PV%d_%d", latax, m );
+               }
+               astClearCard( this );
+               if( FindKeyCard( this, template, method, class, status ) ) {
+                  DeleteCard( this, method, class, status );
+               }
+            }
+
+/* Otherwise, do the same check for the longitude axis. */
+         } else {
+            ok = 0;
+            for( m = 1; m <= ubnd_lon && !ok; m++ ) {
+
+               if( s != ' ' ) {
+                  sprintf( template, "PV%d_%d%c", lonax, m, s );
+               } else {
+                  sprintf( template, "PV%d_%d", lonax, m );
+               }
+
+               if( ! GetValue( this, template, AST__FLOAT, &pval, 0, 0,
+                               method, class, status ) ) {
+
+                  pval = ( m == 1 ) ? 1.0 : 0.0;
+               }
+
+               if( pval != 0.0 ) ok = 1;
+            }
+
+            if( !ok ) {
+               Warn( this, "badpv", "This FITS header describes a distorted TAN "
+                     "projection, but all the distortion coefficients (the "
+                     "PVi_m headers) on the longitude axis are zero.", method,
+                     class, status );
+               ret = 0;
+
+               for( m = 1; m <= ubnd_lon; m++ ) {
+                  if( s != ' ' ) {
+                     sprintf( template, "PV%d_%d%c", lonax, m, s );
+                  } else {
+                     sprintf( template, "PV%d_%d", lonax, m );
+                  }
+                  astClearCard( this );
+                  if( FindKeyCard( this, template, method, class, status ) ) {
+                     DeleteCard( this, method, class, status );
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Return  the result. */
+   return astOK ? ret : 0;
+}
+
+static int GoodWarns( const char *value, int *status ){
+/*
+*  Name:
+*     GoodWarns
+
+*  Purpose:
+*     Checks a string to ensure it is a legal list of warning conditions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int GoodWarns( const char *value, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function checks the supplied string to ensure it contains a space
+*     separated list of zero or more recognised warning conditions. An
+*     error is reported if it does not.
+
+*  Parameters:
+*     value
+*        The string to check.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero is returned if the supplied string is not a legal list of
+*     conditions, or if an error has already occurred. One is returned
+*     otherwise.
+*/
+
+/* Local Variables: */
+   char *b;              /* Pointer to next buffer element */
+   const char *c  ;      /* Pointer to next character */
+   char buf[100];        /* Buffer for condition name */
+   int inword;           /* Are we in a word? */
+   int n;                /* Number of conditions supplied */
+   int ret;              /* Returned value */
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Report an error and return if the pointer is null. */
+   if( !value ){
+      astError( AST__ATTIN, "astSetWarnings(fitschan): Null pointer "
+                "supplied for the Warnings attribute." , status);
+      return ret;
+   }
+
+/* Initialise things */
+   inword = 0;
+   buf[ 0 ] = ' ';
+   b = buf + 1;
+   n = 0;
+   ret = 1;
+
+/* Loop round each character in the supplied string. */
+   for( c = value ; c < value + strlen( value ) + 1; c++ ){
+
+/* Have we found the first space or null following a word? */
+      if( ( !(*c) || isspace( *c ) ) && inword ){
+
+/* Add a space to the end of the buffer and terminate it. */
+         *(b++) = ' ';
+         *b = 0;
+
+/* Check the word is legal by searching for it in the string of all
+   conditions, which should be lower case and have spaces at start and end.
+   The word in the buffer is delimited by spaces and so it will not match
+   a substring within a condition. If it is legal increment the number of
+   conditions found. */
+         if( strstr( ALLWARNINGS, buf ) ){
+            n++;
+
+/* Otherwise, report an error and break. */
+         } else {
+            ret = 0;
+            *(--b) = 0;
+            astError( AST__ATTIN, "astSetWarnings(fitschan): Unknown "
+                      "condition '%s' specified when setting the Warnings "
+                      "attribute.", status, buf + 1 );
+            break;
+         }
+
+/* Reset the pointer to the next character in the buffer, retaining the
+   initial space in the buffer. */
+         b = buf + 1;
+
+/* Indicate we are no longer in a word. */
+         inword = 0;
+
+/* Have we found the first non-space, non-null character following a space? */
+      } else if( *c && !isspace( *c ) && !inword ){
+
+/* Note we are now in a word. */
+         inword = 1;
+      }
+
+/* If we are in a word, copy the lowercase character to the buffer. */
+      if( inword ) *(b++) = tolower( *c );
+   }
+   return ret;
+}
+
+static AstMapping *GrismSpecWcs( char *algcode, FitsStore *store, int i,
+                                 char s, AstSpecFrame *specfrm,
+                                 const char *method, const char *class, int *status ) {
+/*
+*  Name:
+*     GrismSpecWcs
+
+*  Purpose:
+*     Create a Mapping describing a FITS-WCS grism-dispersion algorithm
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstMapping *GrismSpecWcs( char *algcode, FitsStore *store, int i, char s,
+*                               AstSpecFrame *specfrm, const char *method,
+*                               const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function uses the contents of the supplied FitsStore to create
+*     a Mapping which goes from Intermediate World Coordinate (known as "w"
+*     in the context of FITS-WCS paper III) to the spectral system
+*     described by the supplied SpecFrame.
+*
+*     The returned Mapping implements the grism "GRA" and "GRI" algorithms
+*     described in FITS-WCS paper III.
+
+*  Parameters:
+*     algcode
+*        Pointer to a string holding the code for the required algorithm
+*        ("-GRA" or "-GRI").
+*     store
+*        Pointer to the FitsStore structure holding the values to use for
+*        the WCS keywords.
+*     i
+*        The zero-based index of the spectral axis within the FITS header
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     specfrm
+*        Pointer to the SpecFrame. This specifies the "S" system - the
+*        system in which the CRVAL kewyords (etc) are specified.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a Mapping, or NULL if an error occurs.
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;
+   AstMapping *gmap;
+   AstMapping *map1;
+   AstMapping *map2;
+   AstMapping *map2a;
+   AstMapping *map2b;
+   AstMapping *ret;
+   AstMapping *smap;
+   AstSpecFrame *wfrm;
+   double crv;
+   double dg;
+   double gcrv;
+   double pv;
+   double wcrv;
+
+/* Check the global status. */
+   ret = NULL;
+   if( !astOK ) return ret;
+
+/* The returned Mapping will be a CmpMap including a GrismMap. This
+   GrismMap will produced wavelength as output. We also need the Mapping
+   from wavelength to the system represented by the supplied SpecFrame.
+   To get this, we first create a copy of the supplied SpecFrame (in order
+   to inherit the standard of rest, epoch, etc), and set its System to
+   wavlength in vacuum (for "-GRI") or air (for "-GRA"), and then use
+   astConvert to get the Mapping from the SpecFrame system to relevant
+   form of wavelength. */
+   wfrm = astCopy( specfrm );
+   astSetSystem( wfrm, strcmp( algcode, "-GRI" )?AST__AIRWAVE:AST__WAVELEN );
+   astSetUnit( wfrm, 0, "m" );
+   fs = astConvert( specfrm, wfrm, "" );
+   if( fs ) {
+      smap = astGetMapping( fs, AST__BASE, AST__CURRENT );
+      fs = astAnnul( fs );
+
+/* Get the CRVAL value for the spectral axis (this will be in the S system). */
+      crv = GetItem( &(store->crval), i, 0, s, NULL, method, class, status );
+      if( crv == AST__BAD ) crv = 0.0;
+
+/* Convert it to the wavelength system (vacuum or air) in metres. */
+      astTran1( smap, 1, &crv, 1, &wcrv );
+
+/* Create a GrismMap, and then use the projection parameters stored in
+   the FitsStore to set its attributes (convert degrees values to radians
+   and supply the defaults specified in FITS-WCS paper III). The FITS
+   paper specifies units in which these parameters should be stored in a
+   FITS header - distances are in metres and angles in degrees. */
+      gmap = (AstMapping *) astGrismMap( "", status );
+      pv = GetItem( &(store->pv), i, 0, s, NULL, method, class, status );
+      astSetGrismG( gmap, ( pv != AST__BAD )?pv:0.0 );
+      pv = GetItem( &(store->pv), i, 1, s, NULL, method, class, status );
+      astSetGrismM( gmap, ( pv != AST__BAD )?(int) ( pv + 0.5 ):0);
+      pv = GetItem( &(store->pv), i, 2, s, NULL, method, class, status );
+      astSetGrismAlpha( gmap, ( pv != AST__BAD )?pv*AST__DD2R:0.0 );
+      pv = GetItem( &(store->pv), i, 3, s, NULL, method, class, status );
+      astSetGrismNR( gmap, ( pv != AST__BAD )?pv:1.0 );
+      pv = GetItem( &(store->pv), i, 4, s, NULL, method, class, status );
+      astSetGrismNRP( gmap, ( pv != AST__BAD )?pv:0.0 );
+      pv = GetItem( &(store->pv), i, 5, s, NULL, method, class, status );
+      astSetGrismEps( gmap, ( pv != AST__BAD )?pv*AST__DD2R:0.0 );
+      pv = GetItem( &(store->pv), i, 6, s, NULL, method, class, status );
+      astSetGrismTheta( gmap, ( pv != AST__BAD )?pv*AST__DD2R:0.0 );
+
+/* Store the reference wavelength found above as an attribute of the
+   GrismMap. */
+      astSetGrismWaveR( gmap, wcrv );
+
+/* Invert the GrismMap to get the (Wavelength -> grism parameter) Mapping, and
+   then combine it with the (S -> Wavelength) Mapping to get the (S -> grism
+   parameter) Mapping. */
+      astInvert( gmap );
+      map1 = (AstMapping *) astCmpMap( smap, gmap, 1, "", status );
+
+/* Convert the reference point value from wavelength to grism parameter. */
+      astTran1( gmap, 1, &wcrv, 1, &gcrv );
+
+/* Find the rate of change of grism parameter with respect to the S
+   system at the reference point, dg/dS. */
+      dg = astRate( map1, &crv, 0, 0 );
+      if( dg != AST__BAD && dg != 0.0 ) {
+
+/* FITS-WCS paper II requires headers to be constructed so that dS/dw = 1.0
+   at the reference point. Therefore dg/dw = dg/dS. Create a WinMap which
+   scales and shifts the "w" value to get the grism parameter value. */
+         map2a = (AstMapping *) astZoomMap( 1, dg, "", status );
+         map2b = (AstMapping *) astShiftMap( 1, &gcrv, "", status );
+         map2 = (AstMapping *) astCmpMap( map2a, map2b, 1, "", status );
+         map2a = astAnnul( map2a );
+         map2b = astAnnul( map2b );
+
+/* The Mapping to be returned is the concatenation of the above Mapping
+   (from w to g) with the Mapping from g to S. */
+         astInvert( map1 );
+         ret = (AstMapping *) astCmpMap( map2, map1, 1, "", status );
+         map2 = astAnnul( map2 );
+      }
+      map1 = astAnnul( map1 );
+      smap = astAnnul( smap );
+      gmap = astAnnul( gmap );
+   }
+   wfrm = astAnnul( wfrm );
+
+/* Return the result */
+   return ret;
+}
+
+static int KeyFields( AstFitsChan *this, const char *filter, int maxfld,
+                    int *ubnd, int *lbnd, int *status ){
+
+/*
+*+
+*  Name:
+*     astKeyFields
+
+*  Purpose:
+*     Find the ranges taken by integer fields within the keyword names
+*     in a FitsChan.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int astKeyFields( AstFitsChan *this, const char *filter, int maxfld,
+*                       int *ubnd, int *lbnd )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function returns the number of cards within a FitsChan which
+*     refer to keywords which match the supplied filter template. If the
+*     filter contains any integer field specifiers (e.g. "%d", "%3d", etc),
+*     it also returns the upper and lower bounds found for the integer
+*     fields.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     filter
+*        The filter string.
+*     maxfld
+*        The size of the "ubnd" and "lbnd" arrays.
+*     ubnd
+*        A pointer to an integer array in which to return the
+*        upper bound found for each integer field in the filter.
+*        They are stored in the order in which they occur in the filter.
+*        If the filter contains too many fields to fit in the supplied
+*        array, the excess trailing fields are ignored.
+*     lbnd
+*        A pointer to an integer array in which to return the
+*        lower bound found for each integer field in the filter.
+
+*  Returned Value:
+*     astKeyFields()
+*        The total number of cards matching the supplied filter in the
+*        FitsChan.
+
+*  Filter Syntax:
+*     -  The criteria for a keyword name to match a filter template are
+*     as follows:
+*     -  All characters in the template other than "%" (and the field width
+*     and type specifiers which follow a "%") must be matched by an
+*     identical character in the test string.
+      -  If a "%" occurs in the template, then the next character in the
+*     template should be a single digit specifying a field width. If it is
+*     zero, then the test string may contain zero or more matching characters.
+*     Otherwise, the test string must contain exactly the specified number
+*     of matching characters (i.e. 1 to 9). The field width digit may be
+*     omitted, in which case the test string must contain one or more matching
+*     characters. The next character in the template specifies the type of
+*     matching characters and must be one of "d", "c" or "f". Decimal digits
+*     are matched by "d", all upper (but not lower) case alphabetical
+*     characters are matched by "c", and all characters which may legally be
+*     found within a FITS keyword name are matched by "f".
+
+*  Examples:
+*     -  The filter "CRVAL1" accepts the single keyword CRVAL1.
+*     -  The filter "CRVAL%1d" accepts the single keyword CRVAL0, CRVAL1,
+*     CRVAL2, up to CRVAL9.
+*     -  The filter "CRVAL%d" accepts any keyword consisting of the string
+*     "CRVAL" followed by any integer value.
+*     -  The filter "CR%0s1" accepts any keyword starting with the string "CR"
+*     and ending with the character "1" (including CR1).
+
+*  Notes:
+*     -  The entire FitsChan is searched, irrespective of the setting of
+*     the Card attribute.
+*     -  If "maxfld" is supplied as zero, "ubnd" and "lbnd" are ignored,
+*     but the number of matching cards is still returned as the function value.
+*     -  If no matching cards are found in the FitsChan, or if there are no
+*     integer fields in the filter, then the lower and upper bounds are
+*     returned as zero and -1 (i.e. reversed).
+*     -  If an error has already occured, or if this function should fail
+*     for any reason, a value of zero is returned for the function value,
+*     and the lower and upper bounds are set to zero and -1.
+*-
+*/
+
+/* Local Variables: */
+   const char *class;     /* Object class */
+   const char *method;    /* Method name */
+   int *fields;           /* Pointer to array of field values */
+   int i;                 /* Field index */
+   int icard;             /* Index of current card on entry */
+   int nmatch;            /* No. of matching cards */
+   int nf;                /* No. of integer fields in the filter */
+   int nfld;              /* No. of integer fields in current keyword name */
+
+/* Initialise the returned values. */
+   nmatch = 0;
+   for( i = 0; i < maxfld; i++ ){
+      lbnd[ i ] = 0;
+      ubnd[ i ] = -1;
+   }
+   nf = 0;
+
+/* Check the global error status. */
+   if ( !astOK || !filter ) return nf;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Store the method name and object class for use in error messages. */
+   method = "astKeyFields";
+   class = astGetClass( this );
+
+/* Count the number of integer fields in the filter string. */
+   nf = CountFields( filter, 'd', method, class, status );
+
+/* If this is larger than the supplied arrays, use the size of the arrays
+   instead. */
+   if( nf > maxfld ) nf = maxfld;
+
+/* Allocate memory to hold the integer field values extracted from
+   each matching keyword. */
+   fields = (int *) astMalloc( sizeof( int )*(size_t) nf );
+
+/* Save the current card index, and rewind the FitsChan. */
+   icard = astGetCard( this );
+   astClearCard( this );
+
+/* Check that the FitsChan is not empty and the pointer can be used. */
+   if( !astFitsEof( this ) && astOK ){
+
+/* Initialise the returned bounds. Any excess elements in the array are left
+   at the previously initialised values. */
+      for( i = 0; i < nf; i++ ){
+         lbnd[ i ] = INT_MAX;
+         ubnd[ i ] = -INT_MAX;
+      }
+
+/* Initialise the number of matching keywords. */
+      nmatch = 0;
+
+/* Loop round all the cards in the FitsChan. */
+      while( !astFitsEof( this ) && astOK ){
+
+/* If the current keyword name matches the filter, update the returned
+   bounds and increment the number of matches. */
+         if( Match( CardName( this, status ), filter, nf, fields, &nfld,
+                    method, class, status ) ){
+            for( i = 0; i < nf; i++ ){
+               if( fields[ i ] > ubnd[ i ] ) ubnd[ i ] = fields[ i ];
+               if( fields[ i ] < lbnd[ i ] ) lbnd[ i ] = fields[ i ];
+            }
+            nmatch++;
+         }
+
+/* Move on to the next card. */
+         MoveCard( this, 1, method, class, status );
+      }
+
+/* If bounds were not found, returned 0 and -1. */
+      for( i = 0; i < nf; i++ ){
+         if( lbnd[ i ] == INT_MAX ){
+            lbnd[ i ] = 0;
+            ubnd[ i ] = -1;
+         }
+      }
+   }
+
+/* Reinstate the original current card index. */
+   astSetCard( this, icard );
+
+/* Free the memory used to hold the integer field values extracted from
+   each matching keyword. */
+   fields = (int *) astFree( (void *) fields );
+
+/* If an error has occurred, returned no matches and reversed bounds. */
+   if( !astOK ){
+      nmatch = 0;
+      for( i = 0; i < maxfld; i++ ){
+         lbnd[ i ] = 0;
+         ubnd[ i ] = -1;
+      }
+   }
+
+/* Returned the answer. */
+   return nmatch;
+}
+
+static int FindFits( AstFitsChan *this, const char *name,
+                     char card[ AST__FITSCHAN_FITSCARDLEN + 1 ], int inc, int *status ){
+
+/*
+*++
+*  Name:
+c     astFindFits
+f     AST_FINDFITS
+
+*  Purpose:
+*     Find a FITS card in a FitsChan by keyword.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     int astFindFits( AstFitsChan *this, const char *name, char card[ 81 ],
+c                      int inc )
+f     RESULT = AST_FINDFITS( THIS, NAME, CARD, INC, STATUS )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function searches for a card in a FitsChan by keyword. The
+*     search commences at the current card (identified by the Card
+*     attribute) and ends when a card is found whose FITS keyword
+*     matches the template supplied, or when the last card in the
+*     FitsChan has been searched.
+*
+*     If the search is successful (i.e. a card is found which matches
+c     the template), the contents of the card are (optionally)
+f     the template), the contents of the card are
+*     returned and the Card attribute is adjusted to identify the card
+*     found or, if required, the one following it. If the search is
+c     not successful, the function returns zero and the Card attribute
+f     not successful, the function returns .FALSE. and the Card attribute
+*     is set to the "end-of-file".
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string containing a
+f        A character string containing a
+*        template for the keyword to be found. In the simplest case,
+*        this should simply be the keyword name (the search is case
+*        insensitive and trailing spaces are ignored). However, this
+*        template may also contain "field specifiers" which are
+*        capable of matching a range of characters (see the "Keyword
+*        Templates" section for details). In this case, the first card
+*        with a keyword which matches the template will be found. To
+*        find the next FITS card regardless of its keyword, you should
+*        use the template "%f".
+c     card
+f     CARD = CHARACTER * ( 80 ) (Returned)
+c        An array of at least 81 characters (to allow room for a
+c        terminating null)
+f        A character variable with at least 80 characters
+*        in which the FITS card which is found will be returned.  If
+c        the search is not successful (or a NULL pointer is given), a
+f        the search is not successful, a
+*        card will not be returned.
+c     inc
+f     INC = LOGICAL (Given)
+c        If this value is zero (and the search is successful), the
+f        If this value is .FALSE. (and the search is successful), the
+*        FitsChan's Card attribute will be set to the index of the card
+c        that was found. If it is non-zero, however, the Card
+f        that was found. If it is .TRUE., however, the Card
+*        attribute will be incremented to identify the card which
+*        follows the one found.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astFindFits()
+f     AST_FINDFITS = LOGICAL
+c        One if the search was successful, otherwise zero.
+f        .TRUE. if the search was successful, otherwise .FALSE..
+
+*  Notes:
+*     - The search always starts with the current card, as identified
+*     by the Card attribute. To ensure you search the entire contents
+*     of a FitsChan, you should first clear the Card attribute (using
+c     astClear). This effectively "rewinds" the FitsChan.
+f     AST_CLEAR). This effectively "rewinds" the FitsChan.
+*     - If a search is unsuccessful, the Card attribute is set to the
+*     "end-of-file" (i.e. to one more than the number of cards in the
+*     FitsChan). No error occurs.
+c     - A value of zero will be returned if this function is invoked
+f     - A value of .FALSE. will be returned if this function is invoked
+*     with the AST error status set, or if it should fail for any
+*     reason.
+
+*  Examples:
+c     result = astFindFits( fitschan, "%f", card, 1 );
+f     RESULT = AST_FINDFITS( FITSCHAN, '%f', CARD, .TRUE., STATUS )
+*        Returns the current card in a FitsChan and advances the Card
+*        attribute to identify the card that follows (the "%f"
+*        template matches any keyword).
+c     result = astFindFits( fitschan, "BITPIX", card, 1 );
+f     RESULT = AST_FINDFITS( FITSCHAN, 'BITPIX', CARD, .TRUE., STATUS )
+*        Searches a FitsChan for a FITS card with the "BITPIX" keyword
+*        and returns that card. The Card attribute is then incremented
+*        to identify the card that follows it.
+c     result = astFindFits( fitschan, "COMMENT", NULL, 0 );
+f     RESULT = AST_FINDFITS( FITSCHAN, 'COMMENT', CARD, .FALSE., STATUS )
+*        Sets the Card attribute of a FitsChan to identify the next
+c        COMMENT card (if any). The card itself is not returned.
+f        COMMENT card (if any) and returns that card.
+c     result = astFindFits( fitschan, "CRVAL%1d", card, 1 );
+f     RESULT = AST_FINDFITS( FITSCHAN, 'CRVAL%1d', CARD, .TRUE., STATUS )
+*        Searches a FitsChan for the next card with a keyword of the
+*        form "CRVALi" (for example, any of the keywords "CRVAL1",
+*        "CRVAL2" or "CRVAL3" would be matched). The card found (if
+*        any) is returned, and the Card attribute is then incremented
+*        to identify the following card (ready to search for another
+*        keyword with the same form, perhaps).
+
+*  Keyword Templates:
+*     The templates used to match FITS keywords are normally composed
+*     of literal characters, which must match the keyword exactly
+*     (apart from case). However, a template may also contain "field
+*     specifiers" which can match a range of possible characters. This
+*     allows you to search for keywords that contain (for example)
+*     numbers, where the digits comprising the number are not known in
+*     advance.
+*
+*     A field specifier starts with a "%" character. This is followed
+*     by an optional single digit (0 to 9) specifying a field
+*     width. Finally, there is a single character which specifies the
+
+*     type of character to be matched, as follows:
+*
+*     - "c": matches all upper case letters,
+*     - "d": matches all decimal digits,
+*     - "f": matches all characters which are permitted within a FITS
+*     keyword (upper case letters, digits, underscores and hyphens).
+*
+*     If the field width is omitted, the field specifier matches one
+*     or more characters. If the field width is zero, it matches zero
+*     or more characters. Otherwise, it matches exactly the number of
+
+*     characters specified. In addition to this:
+*
+*     - The template "%f" will match a blank FITS keyword consisting
+*     of 8 spaces (as well as matching all other keywords).
+*     - A template consisting of 8 spaces will match a blank keyword
+*     (only).
+*
+
+*     For example:
+*
+*     - The template "BitPix" will match the keyword "BITPIX" only.
+*     - The template "crpix%1d" will match keywords consisting of
+*     "CRPIX" followed by one decimal digit.
+*     - The template "P%c" will match any keyword starting with "P"
+*     and followed by one or more letters.
+*     - The template "E%0f" will match any keyword beginning with "E".
+*     - The template "%f" will match any keyword at all (including a
+*     blank one).
+*--
+*/
+
+/* Local Variables: */
+   char *c;               /* Pointer to next character to check */
+   char *lname;           /* Pointer to copy of name without trailing spaces */
+   const char *class;     /* Object class */
+   const char *method;    /* Calling method */
+   int ret;               /* Was a card found? */
+
+/* Check the global status, and supplied keyword name. */
+   if( !astOK ) return 0;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Store the calling method and object class. */
+   method = "astFindFits";
+   class = astGetClass( this );
+
+/* Get a local copy of the keyword template. */
+   lname = (char *) astStore( NULL, (void *) name, strlen(name) + 1 );
+
+/* Terminate it to exclude trailing spaces. */
+   c = lname + strlen(lname) - 1;
+   while( *c == ' ' && c >= lname ) *(c--) = 0;
+
+/* Use the private FindKeyCard function to find the card and make it the
+   current card. Always use the supplied current card (if any) if the
+   template is "%f" or "%0f". */
+   if ( !strcmp( lname, "%f" ) || !strcmp( lname, "%0f" ) ){
+      ret = astFitsEof( this ) ? 0 : 1;
+   } else {
+      ret = FindKeyCard( this, lname, method, class, status );
+   }
+
+/* Only proceed if the card was found. */
+   if( ret && astOK ){
+
+/* Format the current card if a destination string was supplied. */
+      if( card ) FormatCard( this, card, method, status );
+
+/* Increment the current card pointer if required. */
+      if( inc ) MoveCard( this, 1, method, class, status );
+
+/* Indicate that a card has been formatted. */
+      ret = 1;
+   }
+
+/* Free the memory holding the local copy of the keyword template. */
+   lname = (char *) astFree( (void *) lname );
+
+/* If an errror has occurred, return zero. */
+   if( !astOK ) ret = 0;
+
+/* Return the answer. */
+   return ret;
+}
+
+static int FindKeyCard( AstFitsChan *this, const char *name,
+                        const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     FindKeyCard
+
+*  Purpose:
+*     Find the next card refering to given keyword.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int FindKeyCard( AstFitsChan *this, const char *name,
+*                      const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Finds the next card which refers to the supplied keyword and makes
+*     it the current card. The search starts with the current card and ends
+*     when it reaches the last card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        Pointer to a string holding the keyword template (using the
+*        syntax expected by the Match function).
+*     method
+*        Pointer to string holding name of calling method.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if a card was found refering to the given
+*     keyword. Otherwise zero is returned.
+
+*  Notes:
+*     -  If a NULL pointer is supplied for "name" then the current card
+*     is left unchanged.
+*     -  The current card is set to NULL (end-of-file) if no card can be
+*     found for the supplied keyword.
+*/
+
+/* Local Variables: */
+   int nfld;             /* Number of fields in keyword template */
+   int ret;              /* Was a card found? */
+
+/* Check the global status, and supplied keyword name. */
+   if( !astOK || !name ) return 0;
+
+/* Indicate that no card has been found yet. */
+   ret = 0;
+
+/* Search forward through the list until all cards have been checked. */
+   while( !astFitsEof( this ) && astOK ){
+
+/* Break out of the loop if the keyword name from the current card matches
+   the supplied keyword name. */
+      if( Match( CardName( this, status ), name, 0, NULL, &nfld, method, class, status ) ){
+         ret = 1;
+         break;
+
+/* Otherwise, move the current card on to the next card. */
+      } else {
+         MoveCard( this, 1, method, class, status );
+      }
+   }
+
+/* Return. */
+   return ret;
+}
+
+static double *FitLine( AstMapping *map, double *g, double *g0, double *w0,
+                        double dim, double *tol, int *status ){
+/*
+*  Name:
+*     FitLine
+
+*  Purpose:
+*     Check a Mapping for linearity.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     double *FitLine( AstMapping *map, double *g, double *g0, double *w0,
+*                      double dim, double *tol, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function applies the supplied Mapping to a set of points along
+*     a straight line in the input space. It checks to see if the transformed
+*     positions also lie on a straight line (in the output space). If so,
+*     it returns the vector along this line in the output space which
+*     corresponds to a unit vector along the line in the input space. If
+*     not, a NULL pointer is returned.
+*
+*     The returned vector is found by doing a least squares fit.
+
+*  Parameters:
+*     map
+*        A pointer to the Mapping to test. The number of outputs must be
+*        greater than or equal to the number of inputs.
+*     g
+*        A pointer to an array holding a unit vector within the input space
+*        defining the straight line to be checked. The number of elements
+*        within this array should equal the number of inputs for "map".
+*     g0
+*        A pointer to an array holding a position within the input space
+*        giving the central position of the vector "g". The number of elements
+*        within this array should equal the number of inputs for "map".
+*     w0
+*        A pointer to an array holding a vector within the output space
+*        which corresponds to "g0". The number of elements within this array
+*        should equal the number of outputs for "map".
+*     dim
+*        The length of the pixel axis, or AST__BAD if unknown.
+*     tol
+*        Pointer to an array holding the tolerance for equality on each
+*        output axis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to dynamically allocated memory holding the required vector
+*     in the output space. The number of elements in this vector will equal
+*     the number of outputs for "map". The memory should be freed using
+*     astFree when no longer needed. If the Mapping is not linear, NULL
+*     is returned.
+
+*  Notes:
+*     -  NULL is returned if an error occurs.
+*/
+
+/* Local Constants: */
+#define NPO2 50
+#define NP (2*NPO2+1)
+
+/* Local Variables: */
+   AstPointSet *pset1;
+   AstPointSet *pset2;
+   double **ptr1;
+   double **ptr2;
+   double *offset;
+   double *pax;
+   double *ret;
+   double *voffset;
+   double dax;
+   double denom;
+   double gap;
+   double sd2;
+   double sd;
+   double sdw;
+   double sw;
+   double wmax;
+   double wmin;
+   int i;
+   int j;
+   int n;
+   int nin;
+   int nout;
+   int ok;
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status and supplied axis size. */
+   if( !astOK || dim == 0.0 ) return ret;
+
+/* Get the number of inputs and outputs for the Mapping. Return if the
+   number of outputs is smaller than the number of inputs. */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+   if( nout < nin ) return ret;
+
+/* Check the supplied position is good on all axes. */
+   for( j = 0; j < nout; j++ ) {
+      if( w0[ j ] == AST__BAD ) return ret;
+   }
+
+/* We use NP points in the fit. If a value for "dim" has been supplied,
+   we use points evenly distributed over the whole axis. If not, we use
+   a gap of 1.0 (corresponds to an axis length of 100 pixels).
+   Choose the gap. */
+   gap = ( dim != AST__BAD ) ? dim/NP : 1.0;
+
+/* Create PointSets to hold the input and output positions. */
+   pset1 = astPointSet( NP, nin, "", status );
+   ptr1 = astGetPoints( pset1 );
+   pset2 = astPointSet( NP, nout, "", status );
+   ptr2 = astGetPoints( pset2 );
+
+/* Allocate the returned array. */
+   ret = astMalloc( sizeof( double )*(size_t) nout );
+
+/* Allocate workspace to hold the constant offsets of the fit. */
+   offset = astMalloc( sizeof( double )*(size_t) nout );
+   voffset = astMalloc( sizeof( double )*(size_t) nout );
+
+/* Indicate we have not yet got a usable returned vector. */
+   ok = 0;
+
+/* Check we can use the pointers safely. */
+   if( astOK ) {
+
+/* Set up the input positions: NP evenly spaced points along a line with
+   unit direction vector given by "g", centred at position given by "g0". */
+      for( j = 0; j < nin; j++ ) {
+         pax = ptr1[ j ];
+         dax = g[ j ]*gap;
+         for( i = -NPO2; i <= NPO2; i++ ) *(pax++) = g0[ j ] + dax*i;
+      }
+
+/* Transform these positions into the output space. */
+      (void) astTransform( map, pset1, 1, pset2 );
+
+/* Loop over all output axes, finding the component of the returned vector. */
+      ok = 1;
+      for( j = 0; j < nout; j++ ) {
+         pax = ptr2[ j ];
+
+/* Now loop over all the transformed points to form the other required
+   sums. We also form the sums needed to estimate the variance in the
+   calculated offset. */
+         sdw = 0.0;
+         sw = 0.0;
+         sd = 0.0;
+         sd2 = 0.0;
+         n = 0;
+         wmax = -DBL_MAX;
+         wmin = DBL_MAX;
+         for( i = -NPO2; i <= NPO2; i++, pax++ ) {
+            if( *pax != AST__BAD ) {
+
+/* Increment the required sums. */
+               sdw += i*(*pax);
+               sw += (*pax);
+               sd += i;
+               sd2 += i*i;
+               n++;
+               if( *pax > wmax ) wmax = *pax;
+               if( *pax < wmin ) wmin = *pax;
+            }
+         }
+
+/* If a reasonable number of good points were found, find the component of
+   the returned vector (excluding a scale factor of 1/gap). */
+         denom = sd2*n - sd*sd;
+         if( n > NP/4 && denom != 0.0 ) {
+
+/* Find the constant scale factor to return for this axis. If the axis
+   value is constant, return zero. */
+            if( wmax > wmin ) {
+               ret[ j ] = (sdw*n - sw*sd)/denom;
+            } else {
+               ret[ j ] = 0.0;
+            }
+
+/* Now find the constant offset for this axis. */
+            offset[ j ] = (sw*sd2 - sdw*sd)/denom;
+         } else {
+            ok = 0;
+            break;
+         }
+      }
+
+/* Now check that the fit is good enough. Each axis is checked separately.
+   All axes must be good. */
+      if( ok ) {
+         for( j = 0; j < nout; j++ ) {
+
+/* Store the axis values implied by the linear fit in the now un-needed ptr1[0]
+   array. */
+            pax = ptr1[ 0 ];
+            for( i = -NPO2; i <= NPO2; i++, pax++ ) {
+               *pax = i*ret[ j ] + offset[ j ];
+            }
+
+/* Test the fit to see if we beleive that the mapping is linear. If
+   it is, scale the returned value from units of "per gap" to units of
+   "per pixel". Otherwise,indicate that he returned vector is unusable. */
+            if( FitOK( NP, ptr2[ j ], ptr1[ 0 ], tol[ j ], status ) ) {
+               ret[ j ] /= gap;
+            } else {
+               ok = 0;
+               break;
+            }
+         }
+      }
+   }
+
+/* Annul the PointSets. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* Free memory. */
+   offset = astFree( offset );
+   voffset = astFree( voffset );
+
+/* If an error has occurred, or if the returned vector is unusable,
+   free any returned memory */
+   if( !astOK || !ok ) ret = astFree( ret );
+
+/* Return the answer. */
+   return ret;
+
+/* Undefine local constants: */
+#undef NP
+#undef NPO2
+}
+
+static int FitsEof( AstFitsChan *this, int *status ){
+
+/*
+*+
+*  Name:
+*     astFitsEof
+
+*  Purpose:
+*     See if the FitsChan is at "end-of-file".
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int astFitsEof( AstFitsChan *this )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     A value of zero is returned if any more cards remain to be read from the
+*     FitsChan. Otherwise a value of 1 is returned. Thus, it is
+*     equivalent to testing the FitsChan for an "end-of-file" condition.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+
+*  Returned Value:
+*     One if no more cards remain to be read, otherwise zero.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*-
+*/
+
+/* Check the supplied object. */
+   if( !this ) return 1;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* If no more cards remain to be read, the current card pointer in the
+   FitsChan will be NULL. Return an appropriate integer value. */
+   return  this->card ? 0 : 1;
+}
+
+static int FitsSof( AstFitsChan *this, int *status ){
+
+/*
+*+
+*  Name:
+*     FitsSof
+
+*  Purpose:
+*     See if the FitsChan is at "start-of-file".
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int FitsSof( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A value of 1 is returned if the current card is the first card in
+*     the FitsChan. Otherwise a value of zero is returned.  This function
+*     is much more efficient than "astGetCard(this) <= 1" .
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the current card is the first card.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*     - A non-zero value is returned if the FitsChan is empty.
+*-
+*/
+
+/* Return if no FitsChan was supplied, or if the FitsChan is empty. */
+   if ( !this || !this->head ) return 1;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* If the current card is at the head of the linked list, it is the first
+   card. */
+   return  this->card == this->head;
+}
+
+/*
+*++
+*  Name:
+c     astGetFits<X>
+f     AST_GETFITS<X>
+
+*  Purpose:
+*     Get a named keyword value from a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     int astGetFits<X>( AstFitsChan *this, const char *name, <X>type *value )
+f     RESULT = AST_GETFITS<X>( THIS, NAME, VALUE, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This is a family of functions which gets a value for a named keyword,
+*     or the value of the current card, from a FitsChan using one of several
+*     different data types. The data type of the returned value is selected
+*     by replacing <X> in the function name by one of the following strings
+*     representing the recognised FITS data types:
+*
+*     - CF - Complex floating point values.
+*     - CI - Complex integer values.
+*     - F  - Floating point values.
+*     - I  - Integer values.
+*     - L  - Logical (i.e. boolean) values.
+*     - S  - String values.
+*     - CN - A "CONTINUE" value, these are treated like string values, but
+*            are encoded without an equals sign.
+*
+*     The data type of the "value"
+c     parameter
+f     argument
+
+*     depends on <X> as follows:
+*
+c     - CF - "double *" (a pointer to a 2 element array to hold the real and
+c            imaginary parts of the complex value).
+c     - CI - "int *" (a pointer to a 2 element array to hold the real and
+c            imaginary parts of the complex value).
+c     - F  - "double *".
+c     - I  - "int *".
+c     - L  - "int *".
+c     - S  - "char **" (a pointer to a static "char" array is returned at the
+c            location given by the "value" parameter, Note, the stored string
+c            may change on subsequent invocations of astGetFitsS so a
+c            permanent copy should be taken of the string if necessary).
+c     - CN - Like"S".
+f     - CF - DOUBLE PRECISION(2) (a 2 element array to hold the real and
+f            imaginary parts of the complex value).
+f     - CI - INTEGER(2) (a 2 element array to hold the real and imaginary
+f            parts of the complex value).
+f     - F  - DOUBLE PRECISION.
+f     - I  - INTEGER
+f     - L  - LOGICAL
+f     - S  - CHARACTER
+f     - CN - CHARACTER
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string
+f        A character string
+*        containing the FITS keyword name. This may be a complete FITS
+*        header card, in which case the keyword to use is extracted from
+*        it. No more than 80 characters are read from this string. If
+c        NULL
+f        a single dot '.'
+*        is supplied, the value of the current card is returned.
+c     value
+f     VALUE = <X>type (Returned)
+c        A pointer to a
+f        A
+*        buffer to receive the keyword value. The data type depends on <X>
+*        as described above. The conents of the buffer on entry are left
+*        unchanged if the keyword is not found.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetFits<X><X>()
+f     AST_GETFITS<X> = LOGICAL
+c        A value of zero
+f        .FALSE.
+*        is returned if the keyword was not found in the FitsChan (no error
+*        is reported). Otherwise, a value of
+c        one
+f        .TRUE.
+*        is returned.
+
+*  Notes:
+*     -  If a name is supplied, the card following the current card is
+*     checked first. If this is not the required card, then the rest of the
+*     FitsChan is searched, starting with the first card added to the
+*     FitsChan. Therefore cards should be accessed in the order they are
+*     stored in the FitsChan (if possible) as this will minimise the time
+*     spent searching for cards.
+*     -  If the requested card is found, it becomes the current card,
+*     otherwise the current card is left pointing at the "end-of-file".
+*     -  If the stored keyword value is not of the requested type, it is
+*     converted into the requested type.
+*     -  If the keyword is found in the FitsChan, but has no associated
+*     value, an error is reported. If necessary, the
+c     astTestFits
+f     AST_TESTFITS
+*     function can be used to determine if the keyword has a defined
+*     value in the FitsChan prior to calling this function.
+*     -  An error will be reported if the keyword name does not conform
+*     to FITS requirements.
+c     -  Zero
+*     -  .FALSE.
+*     is returned as the function value if an error has already occurred,
+*     or if this function should fail for any reason.
+*     - The FITS standard says that string keyword values should be
+*     padded with trailing spaces if they are shorter than 8 characters.
+*     For this reason, trailing spaces are removed from the string
+*     returned by
+c     astGetFitsS
+f     AST_GETFITSS
+*     if the original string (including any trailing spaces) contains 8
+*     or fewer characters. Trailing spaces are not removed from longer
+*     strings.
+*--
+*/
+
+/* Define a macro which expands to the implementation of the astGetFits<X>
+   routine for a given data type. */
+#define MAKE_FGET(code,ctype,ftype) \
+static int GetFits##code( AstFitsChan *this, const char *name, ctype value, int *status ){ \
+\
+/* Local Variables: */ \
+   const char *class;     /* Object class */ \
+   const char *method;    /* Calling method */ \
+   char *lcom;            /* Supplied keyword comment */ \
+   char *lname;           /* Supplied keyword name */ \
+   char *lvalue;          /* Supplied keyword value */ \
+   char *string;          /* Pointer to returned string value */ \
+   char *c;               /* Pointer to next character */ \
+   int cl;                /* Length of string value */ \
+   int ret;               /* The returned value */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Ensure the source function has been called */ \
+   ReadFromSource( this, status ); \
+\
+/* Store the calling method and object class. */ \
+   method = "astGetFits"#code; \
+   class = astGetClass( this ); \
+\
+/* Initialise the returned value. */ \
+   ret = 0; \
+\
+/* Extract the keyword name from the supplied string. */ \
+   if( name ) { \
+      (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status ); \
+   } else { \
+      lname = NULL; \
+      lvalue = NULL; \
+      lcom = NULL; \
+   } \
+\
+/* Attempt to find a card in the FitsChan refering to this keyword, \
+   and make it the current card. Only proceed if a card was found. No \
+   need to do the search if the value of the current card is required. */ \
+   if( !lname || SearchCard( this, lname, method, class, status ) ){ \
+\
+/* Convert the stored data value to the requested type, and store it in \
+   the supplied buffer. */ \
+      if( !CnvValue( this, ftype, 0, value, method, status ) && astOK ) { \
+         astError( AST__FTCNV, "%s(%s): Cannot convert FITS keyword " \
+                   "'%s' to %s.", status, method, class, \
+                   CardName( this, status ), type_names[ ftype ] ); \
+      } \
+\
+/* If the returned value is a string containing 8 or fewer characters, \
+   replace trailing spaces with null characters. */ \
+      if( astOK ) { \
+         if( ftype == AST__STRING ) { \
+            string = *( (char **) value ); \
+            if( string ) { \
+               cl =strlen( string ); \
+               if( cl <= 8 ) { \
+                  c = string + cl - 1; \
+                  while( *c == ' ' && c > string ) { \
+                     *c = 0; \
+                     c--; \
+                  } \
+               } \
+            } \
+         } \
+\
+/* Indicate that a value is available. */ \
+         ret = 1; \
+      } \
+\
+   } \
+\
+/* Context error message. */ \
+   if( !astOK && lname && *lname ) { \
+      astError( astStatus, "%s(%s): Cannot get value for FITS keyword " \
+                   "'%s'.", status, method, class, lname ); \
+   } \
+\
+/* Release the memory used to hold keyword name, value and comment strings. */ \
+   lname = (char *) astFree( (void *) lname ); \
+   lvalue = (char *) astFree( (void *) lvalue ); \
+   lcom = (char *) astFree( (void *) lcom ); \
+\
+/* Return the answer. */ \
+   return ret; \
+\
+}
+
+/* Use the above macro to give defintions for the astGetFits<X> method
+   for each FITS data type. */
+MAKE_FGET(CF,double *,AST__COMPLEXF)
+MAKE_FGET(CI,int *,AST__COMPLEXI)
+MAKE_FGET(F,double *,AST__FLOAT)
+MAKE_FGET(I,int *,AST__INT)
+MAKE_FGET(L,int *,AST__LOGICAL)
+MAKE_FGET(S,char **,AST__STRING)
+MAKE_FGET(CN,char **,AST__CONTINUE)
+#undef MAKE_FGET
+
+static int FitsGetCom( AstFitsChan *this, const char *name,
+                       char **comment, int *status ){
+
+/*
+*+
+*  Name:
+*     astFitsGetCom
+
+*  Purpose:
+*     Get a keyword comment from a FitsChan.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int astFitsGetCom( AstFitsChan *this, const char *name,
+*                        char **comment )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function gets the comment associated with the next occurrence of
+*     a named keyword in a FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        A pointer to a
+*        string holding the keyword name. This may be a complete FITS
+*        header card, in which case the keyword to use is extracted from
+*        it. No more than 80 characters are read from this string.
+*     comment
+*        A pointer to a location at which to return a pointer to a string
+*        holding the keyword comment. Note, the stored string will change on
+*        subsequent invocations of astFitsGetCom so a permanent copy
+*        should be taken of the string if necessary.
+
+*  Returned Value:
+*     astFitsGetCom()
+*        A value of zero is returned if the keyword was not found before
+*        the end of the FitsChan was reached (no error is reported).
+*        Otherwise, a value of one is returned.
+
+*  Notes:
+*     -  If a NULL pointer is supplied for "name" then the comment from
+*     the current card is returned.
+*     -  The returned value is obtained from the next card refering to
+*     the required keyword, starting the search with the current card.
+*     Any cards occuring before the current card are not seached. If
+*     the entire contents of the FitsChan must be searched, then ensure
+*     the current card is the first card in the FitsChan by clearing the Card
+*     attribute. This effectively "rewinds" the FitsChan.
+*     -  The current card is updated to become the card following the one
+*     read by this function. If the card read by this function is the
+*     last one in the FitsChan, then the current card is left pointing at the
+*     "end-of-file".
+*     -  An error will be reported if the keyword name does not conform
+*     to FITS requirements.
+*     -  A NULL pointer is returned for the comment string if the keyword
+*     has no comment.
+*     -  Zero is returned as the function value if an error has already
+*     occurred, or if this function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS     /* Declare the thread specific global data */
+   const char *method;    /* Calling method */
+   const char *class;     /* Object class */
+   char *lcom;            /* Supplied keyword comment */
+   char *lname;           /* Supplied keyword name */
+   char *lvalue;          /* Supplied keyword value */
+   int ret;               /* The returned value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Initialise the returned value. */
+   ret = 0;
+
+/* Store the method name and object class. */
+   method = "astFitsGetCom";
+   class = astGetClass( this );
+
+/* Extract the keyword name from the supplied string (if supplied). */
+   if( name ){
+      (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status );
+   } else {
+      lname = NULL;
+      lcom = NULL;
+      lvalue = NULL;
+   }
+
+/* Find the next card in the FitsChan refering to this keyword. This will
+   be the current card if no keyword name was supplied. The matching card
+   is made the current card. Only proceed if a card was found. */
+   if( FindKeyCard( this, lname, method, class, status ) ){
+
+/* Copy the comment into a static buffer, and return a pointer to it. */
+      if( CardComm( this, status ) ){
+         (void) strncpy( fitsgetcom_sval, CardComm( this, status ), AST__FITSCHAN_FITSCARDLEN );
+         fitsgetcom_sval[ AST__FITSCHAN_FITSCARDLEN ] = 0;
+         if( comment ) *comment = fitsgetcom_sval;
+      } else {
+         if( comment ) *comment = NULL;
+      }
+
+/* Move on to the next card. */
+      MoveCard( this, 1, method, class, status );
+
+/* Indicate that a value is available. */
+      if( astOK ) ret = 1;
+   }
+
+/* Release the memory used to hold keyword name, value and comment strings. */
+   lname = (char *) astFree( (void *) lname );
+   lvalue = (char *) astFree( (void *) lvalue );
+   lcom = (char *) astFree( (void *) lcom );
+
+/* Return the answer. */
+   return ret;
+}
+
+static int SetFits( AstFitsChan *this, const char *keyname, void *value,
+                    int type, const char *comment, int overwrite, int *status ){
+
+/*
+*  Name:
+*     SetFits
+
+*  Purpose:
+*     Store a keyword value of any type in a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int SetFits( AstFitsChan *this, const char *keyname, void *value,
+*                  int type, const char *comment, int overwrite, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function stores the supplied value for the supplied keyword
+*     in the supplied FitsChan, assuming it is of the supplied data type.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        A pointer to a string holding the keyword name.
+*     value
+*        A pointer to a buffer holding the keyword value. For strings,
+*        the buffer should hold the address of a pointer to the character
+*        string.
+*     type
+*        The keyword type.
+*     comment
+*        A pointer to a string holding a comment to associated with the
+*        keyword. If a NULL pointer or a blank string is supplied, then
+*        any comment included in the string supplied for the "name" parameter
+*        is used instead. If "name" contains no comment, then any existing
+*        comment in the card being over-written is retained, or a NULL
+*        pointer is stored if a new card is being inserted. If the data
+*        value being stored for the card is the same as the card being
+*        over-written, then any existing comment is retained.
+*     overwrite
+*        If non-zero, the new card formed from the supplied keyword name,
+*        value and comment string over-writes the current card, and the
+*        current card is incremented to refer to the next card. If zero, the
+*        new card is inserted in front of the current card and the current
+*        card is left unchanged. In either case, if the current card on
+*        entry points to the "end-of-file", the new card is appended to the
+*        end of the list.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 0 is returned if the value could not be stored for any
+*     reason. A value of 1 is returned otherwise.
+
+*  Notes:
+*     -  Nothing is stored in the FitsChan and a value of zero is returned
+*     (but no error is reported) if an AST__FLOAT value is supplied equal
+*     to AST__BAD.
+*/
+
+/* Local Variables: */
+   const char *cval;
+   const char *ecval;
+   double dval;
+   double ecdval[ 2 ];
+   double edval;
+   int ecival[ 2 ];
+   int eival;
+   int ival;
+   int ret;
+
+/* Check the global status, and the supplied pointer. */
+   if( !astOK || !value ) return 0;
+
+/* Initialise the returned value to indicate that the supplied name was
+   stored. */
+   ret = 1;
+
+/* Check each data type in turn. */
+   if( type == AST__FLOAT ){
+      dval = *( (double *) value );
+      if( dval != AST__BAD ) {
+
+/* If the data value has not changed, and the card has a coment,
+   set the comment pointer NULL so that the existing comment will be
+   retained. */
+         if( overwrite && CnvValue( this, type, 0, &edval, "SetFits",
+                                    status ) &&
+             CardComm( this, status ) ) {
+            if( astEQUAL( edval, dval ) ) comment = NULL;
+         }
+         astSetFitsF( this, keyname, dval, comment, overwrite );
+      } else {
+         ret = 0;
+      }
+   } else if( type == AST__STRING ){
+      cval = *( (char **) value);
+      if( cval ){
+
+/* If the data value has not changed, retain the original comment. */
+         if( overwrite && CnvValue( this, type, 0, &ecval, "SetFits",
+                                    status ) &&
+             CardComm( this, status ) ) {
+            if( Similar( ecval, cval, status ) ) comment = NULL;
+         }
+
+/* Ignore comments if they are identical to the keyword value. */
+         if( comment && !strcmp( cval, comment ) ) comment = NULL;
+         astSetFitsS( this, keyname, cval, comment, overwrite );
+      } else {
+         ret = 0;
+      }
+   } else if( type == AST__CONTINUE ){
+      cval = *( (char **) value);
+      if( cval ){
+         astSetFitsCN( this, keyname, cval, comment, overwrite );
+      } else {
+         ret = 0;
+      }
+   } else if( type == AST__COMMENT ){
+      astSetFitsCom( this, keyname, comment, overwrite );
+   } else if( type == AST__INT ){
+      ival = *( (int *) value );
+
+/* If the data value has not changed, retain the original comment. */
+      if( overwrite && CnvValue( this, type, 0, &eival, "SetFits",
+                                 status ) &&
+         CardComm( this, status ) ) {
+         if( eival == ival ) comment = NULL;
+      }
+      astSetFitsI( this, keyname, ival, comment, overwrite );
+   } else if( type == AST__COMPLEXF ){
+      if( ( (double *) value )[0] != AST__BAD &&
+          ( (double *) value )[1] != AST__BAD ) {
+
+/* If the data value has not changed, retain the original comment. */
+         if( overwrite && CnvValue( this, type, 0, ecdval, "SetFits",
+                                    status ) &&
+             CardComm( this, status ) ) {
+            if( astEQUAL( ecdval[ 0 ], ( (double *) value )[ 0 ] ) &&
+                astEQUAL( ecdval[ 1 ], ( (double *) value )[ 1 ] ) ) comment = NULL;
+         }
+         astSetFitsCF( this, keyname, (double *) value, comment, overwrite );
+      } else {
+         ret = 0;
+      }
+   } else if( type == AST__COMPLEXI ){
+
+/* If the data value has not changed, retain the original comment. */
+      if( overwrite && CnvValue( this, type, 0, ecival, "SetFits",
+                                 status ) &&
+          CardComm( this, status ) ) {
+         if( ecival[ 0 ] == ( (int *) value )[ 0 ] &&
+             ecival[ 1 ] == ( (int *) value )[ 1 ] ) comment = NULL;
+      }
+      astSetFitsCI( this, keyname, (int *) value, comment, overwrite );
+   } else if( type == AST__LOGICAL ){
+      ival = ( *( (int *) value ) != 0 );
+
+/* If the data value has not changed, retain the original comment. */
+      if( overwrite && CnvValue( this, type, 0, &eival, "SetFits",
+                                 status ) &&
+          CardComm( this, status ) ) {
+         if( eival == ival ) comment = NULL;
+      }
+      astSetFitsL( this, keyname, ival, comment, overwrite );
+   } else if( type == AST__UNDEF ){
+      if( overwrite && CardType( this, status ) == AST__UNDEF && CardComm( this, status ) ) {
+         comment = NULL;
+      }
+      astSetFitsU( this, keyname, comment, overwrite );
+   }
+   return ret;
+}
+
+/*
+*++
+*  Name:
+c     astSetFits<X>
+f     AST_SETFITS<X>
+
+*  Purpose:
+*     Store a keyword value in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     void astSetFits<X>( AstFitsChan *this, const char *name, <X>type value,
+c                         const char *comment, int overwrite )
+f     CALL AST_SETFITS<X>( THIS, NAME, VALUE, COMMENT, OVERWRITE, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This is a family of functions which store values for named keywords
+f     This is a family of routines which store values for named keywords
+*     within a FitsChan at the current card position. The supplied keyword
+*     value can either over-write an existing keyword value, or can be
+*     inserted as a new header card into the FitsChan.
+*
+c     The keyword data type is selected by replacing <X> in the function name
+f     The keyword data type is selected by replacing <X> in the routine name
+*     by one of the following strings representing the recognised FITS data
+
+*     types:
+*
+*     - CF - Complex floating point values.
+*     - CI - Complex integer values.
+*     - F  - Floating point values.
+*     - I  - Integer values.
+*     - L  - Logical (i.e. boolean) values.
+*     - S  - String values.
+*     - CN - A "CONTINUE" value, these are treated like string values, but
+*            are encoded without an equals sign.
+*
+
+*     The data type of the "value" parameter depends on <X> as follows:
+*
+c     - CF - "double *" (a pointer to a 2 element array holding the real and
+c            imaginary parts of the complex value).
+c     - CI - "int *" (a pointer to a 2 element array holding the real and
+c            imaginary parts of the complex value).
+c     - F  - "double".
+c     - I  - "int".
+c     - L  - "int".
+c     - S  - "const char *".
+c     - CN - "const char *".
+*
+f     - CF - DOUBLE PRECISION(2) (a 2 element array holding the real and
+f            imaginary parts of the complex value).
+f     - CI - INTEGER(2) (a 2 element array holding the real and imaginary
+f            parts of the complex value).
+f     - F  - DOUBLE PRECISION.
+f     - I  - INTEGER
+f     - L  - LOGICAL
+f     - S  - CHARACTER
+f     - CN - CHARACTER
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string
+f        A character string
+*        containing the FITS keyword name. This may be a complete FITS
+*        header card, in which case the keyword to use is extracted from
+*        it. No more than 80 characters are read from this string.
+c     value
+f     VALUE = <X>type (Given)
+*        The keyword value to store with the named keyword. The data type
+*        of this parameter depends on <X> as described above.
+c     comment
+f     COMMENT = CHARACTER * ( * ) (Given)
+c        A pointer to a null terminated string
+f        A string
+*        holding a comment to associated with the keyword.
+c        If a NULL pointer or
+f        If
+*        a blank string is supplied, then any comment included in the string
+*        supplied for the
+c        "name" parameter is used instead. If "name"
+f        NAME parameter is used instead. If NAME
+*        contains no comment, then any existing comment in the card being
+*        over-written is retained. Otherwise, no comment is stored with
+*        the card.
+c     overwrite
+f     OVERWRITE = LOGICAL (Given)
+c        If non-zero,
+f        If .TRUE.,
+*        the new card formed from the supplied keyword name, value and comment
+*        string over-writes the current card, and the current card is
+*        incremented to refer to the next card (see the "Card" attribute). If
+c        zero,
+f        .FALSE.,
+*        the new card is inserted in front of the current card and the current
+*        card is left unchanged. In either case, if the current card on entry
+*        points to the "end-of-file", the new card is appended to the end of
+*        the list.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - The
+c     function astSetFitsU
+f     routine AST_SETFITSU
+*     can be used to indicate that no value is associated with a keyword.
+*     - The
+c     function astSetFitsCM
+f     routine AST_SETFITSCM
+*     can be used to store a pure comment card (i.e. a card with a blank
+*     keyword).
+*     -  To assign a new value for an existing keyword within a FitsChan,
+c     first find the card describing the keyword using astFindFits, and
+c     then use one of the astSetFits<X> family to over-write the old value.
+f     first find the card describing the keyword using AST_FINDFITS, and
+f     then use one of the AST_SETFITS<X> family to over-write the old value.
+*     -  If, on exit, there are no cards following the card written by
+c     this function, then the current card is left pointing at the
+f     this routine, then the current card is left pointing at the
+*     "end-of-file".
+*     -  An error will be reported if the keyword name does not conform
+*     to FITS requirements.
+*--
+*/
+
+/* Define a macro which expands to the implementation of the astSetFits<X>
+   routine for a given data type. */
+#define MAKE_FSET(code,ctype,ftype,valexp) \
+static void SetFits##code( AstFitsChan *this, const char *name, ctype value, const char *comment, int overwrite, int *status ) { \
+\
+/* Local variables: */ \
+   const char *class;     /* Object class */ \
+   const char *method;    /* Calling method */ \
+   const char *com;       /* Comment to use */ \
+   char *lcom;            /* Supplied keyword comment */ \
+   char *lname;           /* Supplied keyword name */ \
+   char *lvalue;          /* Supplied keyword value */ \
+   int free_com;          /* Should com be freed before returned? */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Ensure the source function has been called */ \
+   ReadFromSource( this, status ); \
+\
+/* Store the object clas and calling method. */ \
+   class = astGetClass( this ); \
+   method = "astSetFits"#code; \
+\
+/* Extract the keyword name from the supplied string. */ \
+   (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status ); \
+\
+/* Initialise a pointer to the comment to be stored. If the supplied \
+   comment is blank, use the comment given with "name". */ \
+   com = ChrLen( comment, status ) ? comment : lcom; \
+\
+/* If the comment is still blank, use the existing comment if we are \
+   over-writing, or a NULL pointer otherwise. */ \
+   free_com = 0; \
+   if( !ChrLen( com, status ) ) { \
+      com = NULL; \
+      if( overwrite ) { \
+         if( CardComm( this, status ) ){ \
+            com = (const char *) astStore( NULL, (void *) CardComm( this, status ), \
+                                           strlen( CardComm( this, status ) ) + 1 ); \
+            free_com = 1; \
+         } \
+      } \
+   } \
+\
+/* Insert the new card. */ \
+   InsCard( this, overwrite, lname, ftype, valexp, com, method, class, status ); \
+\
+/* Release the memory used to hold keyword name, value and comment strings. */ \
+   lname = (char *) astFree( (void *) lname ); \
+   lvalue = (char *) astFree( (void *) lvalue ); \
+   lcom = (char *) astFree( (void *) lcom ); \
+\
+/* Release the memory holding the stored comment string, so long as it was \
+   allocated within this function. */ \
+   if( free_com ) com = (const char *) astFree( (void *) com ); \
+\
+}
+
+/* Use the above macro to give defintions for the astSetFits<X> method
+   for each FITS data type. */
+MAKE_FSET(I,int,AST__INT,(void *)&value)
+MAKE_FSET(F,double,AST__FLOAT,(void *)&value)
+MAKE_FSET(S,const char *,AST__STRING,(void *)value)
+MAKE_FSET(CN,const char *,AST__CONTINUE,(void *)value)
+MAKE_FSET(CF,double *,AST__COMPLEXF,(void *)value)
+MAKE_FSET(CI,int *,AST__COMPLEXI,(void *)value)
+MAKE_FSET(L,int,AST__LOGICAL,(void *)&value)
+#undef MAKE_FSET
+
+static void SetFitsU( AstFitsChan *this, const char *name, const char *comment,
+                      int overwrite, int *status ) {
+
+/*
+*++
+*  Name:
+c     astSetFitsU
+f     AST_SETFITSU
+
+*  Purpose:
+*     Store an undefined keyword value in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     void astSetFitsU( AstFitsChan *this, const char *name,
+c                       const char *comment, int overwrite )
+f     CALL AST_SETFITSU( THIS, NAME, COMMENT, OVERWRITE, STATUS )
+
+*  Description:
+*     This
+c     function
+f     routine
+*     stores an undefined value for a named keyword within
+*     a FitsChan at the current card position. The new undefined value
+*     can either over-write an existing keyword value, or can be inserted
+*     as a new header card into the FitsChan.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string
+f        A character string
+*        containing the FITS keyword name. This may be a complete FITS
+*        header card, in which case the keyword to use is extracted from
+*        it. No more than 80 characters are read from this string.
+c     comment
+f     COMMENT = CHARACTER * ( * ) (Given)
+c        A pointer to a null terminated string
+f        A string
+*        holding a comment to associated with the keyword.
+c        If a NULL pointer or
+f        If
+*        a blank string is supplied, then any comment included in the string
+*        supplied for the
+c        "name" parameter is used instead. If "name"
+f        NAME parameter is used instead. If NAME
+*        contains no comment, then any existing comment in the card being
+*        over-written is retained. Otherwise, no comment is stored with
+*        the card.
+c     overwrite
+f     OVERWRITE = LOGICAL (Given)
+c        If non-zero,
+f        If .TRUE.,
+*        the new card formed from the supplied keyword name and comment
+*        string over-writes the current card, and the current card is
+*        incremented to refer to the next card (see the "Card" attribute). If
+c        zero,
+f        .FALSE.,
+*        the new card is inserted in front of the current card and the current
+*        card is left unchanged. In either case, if the current card on entry
+*        points to the "end-of-file", the new card is appended to the end of
+*        the list.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     -  If, on exit, there are no cards following the card written by
+*     this function, then the current card is left pointing at the
+*     "end-of-file".
+*     -  An error will be reported if the keyword name does not conform
+*     to FITS requirements.
+*--
+*/
+
+/* Local variables: */
+   const char *class;     /* Object class */
+   const char *method;    /* Calling method */
+   const char *com;       /* Comment to use */
+   char *lcom;            /* Supplied keyword comment */
+   char *lname;           /* Supplied keyword name */
+   char *lvalue;          /* Supplied keyword value */
+   int free_com;          /* Should com be freed before returned? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Store the object clas and calling method. */
+   class = astGetClass( this );
+   method = "astSetFitsU";
+
+/* Extract the keyword name from the supplied string. */
+   (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status );
+
+/* Initialise a pointer to the comment to be stored. If the supplied
+   comment is blank, use the comment given with "name". */
+   com = ChrLen( comment, status ) ? comment : lcom;
+
+/* If the comment is still blank, use the existing comment if we are
+   over-writing, or a NULL pointer otherwise. */
+   free_com = 0;
+   if( !ChrLen( com, status ) ) {
+      com = NULL;
+      if( overwrite ) {
+         if( CardComm( this, status ) ){
+            com = (const char *) astStore( NULL, (void *) CardComm( this, status ),
+                                           strlen( CardComm( this, status ) ) + 1 );
+            free_com = 1;
+         }
+      }
+   }
+
+/* Insert the new card. */
+   InsCard( this, overwrite, lname, AST__UNDEF, NULL, com, method, class,
+            status );
+
+/* Release the memory used to hold keyword name, value and comment strings. */
+   lname = (char *) astFree( (void *) lname );
+   lvalue = (char *) astFree( (void *) lvalue );
+   lcom = (char *) astFree( (void *) lcom );
+
+/* Release the memory holding the stored comment string, so long as it was
+   allocated within this function. */
+   if( free_com ) com = (const char *) astFree( (void *) com );
+}
+
+static void SetFitsCM( AstFitsChan *this, const char *comment,
+                       int overwrite, int *status ) {
+
+/*
+*++
+*  Name:
+c     astSetFitsCM
+f     AST_SETFITSCM
+
+*  Purpose:
+*     Store a comment card in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     void astSetFitsCM( AstFitsChan *this, const char *comment,
+c                        int overwrite )
+f     CALL AST_SETFITSCM( THIS, COMMENT, OVERWRITE, STATUS )
+
+*  Description:
+*     This
+c     function
+f     routine
+*     stores a comment card ( i.e. a card with no keyword name or equals
+*     sign) within a FitsChan at the current card position. The new card
+*     can either over-write an existing card, or can be inserted as a new
+*     card into the FitsChan.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     comment
+f     COMMENT = CHARACTER * ( * ) (Given)
+c        A pointer to a null terminated string
+f        A string
+*        holding the text of the comment card.
+c        If a NULL pointer or
+f        If
+*        a blank string is supplied, then a totally blank card is produced.
+c     overwrite
+f     OVERWRITE = LOGICAL (Given)
+c        If non-zero,
+f        If .TRUE.,
+*        the new card over-writes the current card, and the current card is
+*        incremented to refer to the next card (see the "Card" attribute). If
+c        zero,
+f        .FALSE.,
+*        the new card is inserted in front of the current card and the current
+*        card is left unchanged. In either case, if the current card on entry
+*        points to the "end-of-file", the new card is appended to the end of
+*        the list.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     -  If, on exit, there are no cards following the card written by
+*     this function, then the current card is left pointing at the
+*     "end-of-file".
+*--
+*/
+
+/* Just call astSetFitsCom with a blank keyword name. */
+   astSetFitsCom( this, "", comment, overwrite );
+}
+
+static void SetFitsCom( AstFitsChan *this, const char *name,
+                        const char *comment, int overwrite, int *status ){
+
+/*
+*+
+*  Name:
+*     astSetFitsCom
+
+*  Purpose:
+*     Store a comment for a keyword in a FitsChan.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void astSetFitsCom( AstFitsChan *this, const char *name,
+*                         const char *comment, int overwrite )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function replaces the comment within an existing card, or
+*     stores a new comment card within a FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        A pointer to a
+*        string holding the keyword name. This may be a complete FITS
+*        header card, in which case the keyword to use is extracted from
+*        it. No more than 80 characters are read from this string.
+*     comment
+*        A pointer to a
+*        string holding a comment to associated with the keyword.
+*        If a NULL or
+*        blank string is supplied, any existing comment associated with
+*        the keyword is removed.
+*     overwrite
+*        If non-zero, the new comment replaces the comment in the current
+*        card, and the current card is then incremented to refer to the next
+*        card. If zero, a new comment card is inserted in front of the current
+*        card and the current card is left unchanged. In either case, if the
+*        current card on entry points to the "end-of-file", the new card is
+*        appended to the end of the list.
+
+*  Notes:
+*     -  When replacing an existing comment, any existing keyword value is
+*     retained only if the supplied keyword name is the same as the keyword
+*     name in the current card. If the keyword names are different, then
+*     the new name replaces the old name, and any existing keyword data value
+*     is deleted. The card thus becomes a comment card with the supplied
+*     keyword name and comment, but no data value.
+*     -  If, on exit, there are no cards following the card written by
+*     this function, then the current card is left pointing at the
+*     "end-of-file".
+*     -  The current card can be set explicitly before calling this function
+*     either by assigning a value to the Card attribute (if the index of the
+*     required card is already known), or using astFindFits (if only the
+*     keyword name is known).
+*     -  An error will be reported if the keyword name does not conform
+*     to FITS requirements.
+*-
+*/
+
+/* Local variables: */
+   const char *class;     /* Pointer to object class string */
+   const char *method;    /* Pointer to calling method string */
+   const char *cname;     /* The existing keyword name */
+   const char *com;       /* The comment to use */
+   char *lcom;            /* Supplied keyword comment */
+   char *lname;           /* Supplied keyword name */
+   char *lvalue;          /* Supplied keyword value */
+   void *old_data;        /* Pointer to the old data value */
+   void *data;            /* Pointer to data value to be stored */
+   size_t size;           /* The size of the data value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialisation */
+   size = 0;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Store the calling method and object class. */
+   method = "astSetFitsCom";
+   class = astGetClass( this );
+
+/* Extract the keyword name, etc, from the supplied string. */
+   (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status );
+
+/* If a blank comment has been supplied, use NULL instead. */
+   com = ChrLen( comment, status )? comment : NULL;
+
+/* If we are inserting a new card, or over-writing an old card with a
+   different name, create and store a comment card with the given keyword
+   name and comment, but no data value. */
+   cname = CardName( this, status );
+   if( !overwrite || !cname || strcmp( lname, cname ) ){
+      InsCard( this, overwrite, lname, AST__COMMENT, NULL, com, method, class, status );
+
+/* If we are overwriting an existing keyword comment, use the data type
+   and value from the existing current card. Note, we have to take a copy
+   of the old data value because InsCard over-writes by deleting the old
+   card and then inserting a new one. */
+   } else {
+      old_data = CardData( this, &size, status );
+      data = astStore( NULL, old_data, size );
+      InsCard( this, 1, lname, CardType( this, status ), data, com, method, class, status );
+      data = astFree( data );
+   }
+
+/* Release the memory used to hold keyword name, value and comment strings. */
+   lname = (char *) astFree( (void *) lname );
+   lvalue = (char *) astFree( (void *) lvalue );
+   lcom = (char *) astFree( (void *) lcom );
+}
+
+static void FixNew( AstFitsChan *this, int flag, int remove,
+                    const char *method, const char *class, int *status ){
+
+/*
+*
+*  Name:
+*     FixNew
+
+*  Purpose:
+*     Remove "new" flags from the whole FitsChan, and optionally remove
+*     "new" cards.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void FixNew( AstFitsChan *this, int flag, int remove,
+*                  const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function searches the entire FitsChan for cards which are
+*     marked as new using the supplied flag (NEW1 or NEW2). If "remove"
+*     is non-zero, these cards are completely removed from the FitsChan
+*     (not just marked as used). If "remove" is zero, they are retained
+*     and the specified flag is cleared.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     flag
+*        The flag to use; NEW1 or NEW2.
+*     remove
+*        Remove flagged cards from the FitsChan?
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function attempts to execute even if an error has occurred.
+*     - If any cards are removed, the current Card is left at "end-of-file"
+*       on exit. If no cards are removed, the original current card is
+*       retained.
+*-
+*/
+
+/* Local Variables: */
+   int *flags;             /* Pointer to flags mask for the current card */
+   int icard;              /* Index of current card on entry */
+   int ndeleted;           /* Number of cards deleted by this call */
+
+/* Return if no FitsChan was supplied, or if the FitsChan is empty. */
+   if ( !this || !this->head ) return;
+
+/* Save the current card index, and rewind the FitsChan. */
+   icard = astGetCard( this );
+   astClearCard( this );
+
+/* Indicate no cards have yet been deleted. */
+   ndeleted = 0;
+
+/* Loop through the list of FitsCards in the FitsChan until the final
+   card is reached. */
+   while( astOK && this->card ){
+
+/* Get a pointer to the flags mask for this card. */
+      flags = CardFlags( this, status );
+
+/* See if the Card has been marked with the requeste new flag. */
+      if( flags && ( (*flags) & flag ) ) {
+
+/* If requested, remove the card. This will automatically move the
+   current card on to the next card. */
+         if( remove ){
+            DeleteCard( this, method, class, status );
+            ndeleted++;
+
+/* Otherwise, clear the flag. */
+         } else {
+            *flags = (*flags) & ~flag;
+
+/* Increment the card count and move on to the next card. */
+            MoveCard( this, 1, method, class, status );
+         }
+
+/* Move on to the next card if this card is not marked with the requested
+   new flag. */
+      } else {
+         MoveCard( this, 1, method, class, status );
+      }
+   }
+
+/* If no cards were removed, we can safely re-instate the original
+   current card. Otherwise, the current card is left at "end-of-file". */
+   if( ndeleted == 0 ) astSetCard( this, icard );
+
+/* Return */
+   return;
+}
+
+static void FixUsed( AstFitsChan *this, int reset, int used, int remove,
+                     const char *method, const char *class, int *status ){
+
+/*
+*
+*  Name:
+*     FixUsed
+
+*  Purpose:
+*     Remove "provisionally used" flags from the whole FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void FixUsed( AstFitsChan *this, int reset, int used, int remove,
+*                   const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function searches the entire FitsChan for cards which are
+*     marked as "provisionally used". The "provisionally used" flag is
+*     cleared for each such card. In addition, if "used" is non-zero then
+*     each such card is flagged as having been "definitely used". If
+*     "remove" is non-zero, then all "provisionally used" cards are deleted
+*     from the FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     reset
+*        Set all cards so that they are neither provisionally used or
+*        definitely used. In this case neither the "used" nor the
+*        "remove" parameter are accssed.
+*     used
+*        Have the provisionally used cards definitely been used?
+*     remove
+*        Should provisionally used cards be deleted?
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function attempts to execute even if an error has occurred.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Declare the thread specific global data */
+   FitsCard *card0;        /* Pointer to current FitsCard */
+   int *flags;             /* Pointer to flags mask for the current card */
+   int old_ignore_used;    /* Original value of variable ignore_used */
+   int old_status;         /* Original inherited status value */
+   int rep;                /* Original error reporting flag */
+
+/* Return if no FitsChan was supplied, or if the FitsChan is empty. */
+   if ( !this || !this->head ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Temporarily clear any bad status value and supress error reporting in
+   this function. */
+   old_status = astStatus;
+   astClearStatus;
+   rep = astReporting( 0 );
+
+/* Indicate that we should not skip over cards marked as having been
+   read. */
+   old_ignore_used = ignore_used;
+   ignore_used = 0;
+
+/* Save a pointer to the current card, and the reset the current card to
+   be the first card. */
+   card0 = this->card;
+   astClearCard( this );
+
+/* Loop through the list of FitsCards in the FitsChan until the final
+   card is reached. */
+   while( this->card ){
+
+/* Get a pointer to the flags mask for this card. */
+      flags = CardFlags( this, status );
+
+/* Reset both used flags if required. */
+      if( reset ) {
+         *flags = (*flags) & ~PROVISIONALLY_USED;
+         *flags = (*flags) & ~USED;
+         MoveCard( this, 1, method, class, status );
+
+/* Otherwise perform the actions indicated by parameters "used" and
+   "remove". */
+      } else {
+
+/* See if the Card has been provisionally used. */
+         if( flags && ( (*flags) & PROVISIONALLY_USED ) ) {
+
+/* Clear the provisionally used flag. */
+            *flags = (*flags) & ~PROVISIONALLY_USED;
+
+/* If required, set the definitely used flag. */
+            if( used ) *flags = (*flags) | USED;
+
+/* If required, delete the card. The next card is made current. If we are
+   about to delete the original current card, we need to update the
+   pointer to the card to be made current at the end of this function.
+   If we end up back at the head of the chain, indicate that we have
+   reached the end of file by setting card0 NULL.  */
+            if( remove ) {
+               if( card0 == this->card && card0 ) {
+                  card0 = ( (FitsCard *) this->card )->next;
+                  if( (void *) card0 == this->head ) card0 = NULL;
+               }
+               DeleteCard( this, method, class, status );
+
+/* Otherwise, just move on to the next card. */
+            } else {
+               MoveCard( this, 1, method, class, status );
+            }
+
+/* If this card has not bee provisionally used, move on to the next card. */
+         } else {
+            MoveCard( this, 1, method, class, status );
+         }
+      }
+   }
+
+/* Re-instate the original current card. */
+   this->card = card0;
+
+/* If this card is now flagged as definitely used, move forward to the
+   next un-used card. */
+   flags = CardFlags( this, status );
+   if( flags && (*flags & USED ) ) {
+      ignore_used = 1;
+      MoveCard( this, 1, method, class, status );
+   }
+
+/* Re-instate the original flag indicating if cards marked as having been
+   read should be skipped over. */
+   ignore_used = old_ignore_used;
+
+/* Re-instate the original status value and error reporting condition. */
+   astReporting( rep );
+   astSetStatus( old_status );
+}
+
+static void FormatCard( AstFitsChan *this, char *buf, const char *method, int *status ){
+
+/*
+*
+*  Name:
+*     FormatCard
+
+*  Purpose:
+*     Formats the current card.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void FormatCard( AstFitsChan *this, char *buf, const char *method, int *status )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function write the current card into the supplied character
+*     buffer as a complete FITS header card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     buf
+*        A character string into which the header card is written. This
+*        should be at least 81 characters long. The returned string is
+*        padded with spaces upto column 80. A terminating null character
+*        is added.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  An error is reported if the requested header card does not conform to
+*     FITS standards.
+*
+*/
+
+/* Local Variables: */
+   const char *com;            /* Pointer to comment string to use */
+   int comlen;                 /* Length of comment string */
+   int comstart;               /* Column in which to start comment */
+   int i;                      /* Loop counter for characters */
+   int len;                    /* Output string length */
+   int digits;                 /* No. of digits to use when formatting floating point values */
+   int type;                   /* Card data type */
+
+/* Check the global error status, and check the current card is defined. */
+   if ( !astOK || astFitsEof( this ) ) return;
+
+/* Get a pointer to the comment to use and determine its length. */
+   com = CardComm( this, status );
+   comlen = ChrLen( com, status );
+
+/* Copy the keyword name to the start of the output buffer, and store
+   its length. */
+   len = (int) strlen( strcpy( buf, CardName( this, status ) ) );
+
+/* Pad the name with spaces up to column 8. */
+   while ( len < FITSNAMLEN ) buf[ len++ ] = ' ';
+
+/* If the card contains a keyword value... */
+   type = CardType( this, status );
+   if( type != AST__COMMENT ){
+
+/* Get the number of digits to use when formatting floating point values. */
+      digits = astGetFitsDigits( this );
+
+/* Put an equals sign in column 9 (or a space if the keyword is a CONTINUE
+   card), followed by a space in column 10. */
+      buf[ len++ ] = ( type == AST__CONTINUE ) ? ' ' : '=';
+      buf[ len++ ] = ' ';
+
+/* Format and store the keyword value, starting at column 11 and update the
+   output string length. */
+      len += EncodeValue( this, buf + len, FITSNAMLEN + 3, digits,
+                          method, status );
+
+/* If there is a comment, determine which column it should start in so that
+   it ends in column 80. */
+      if( com ){
+         comstart = AST__FITSCHAN_FITSCARDLEN - ( comlen - 2 ) + 1;
+
+/* Adjust the starting column to 32 if possible, avoiding over-writing
+   the value, or running off the end of the card unless this is
+   unavoidable. */
+         if ( comstart > FITSCOMCOL ) comstart = FITSCOMCOL;
+         if ( comstart < len + 2 ) comstart = len + 2;
+
+/* Pad the output buffer with spaces up to the start of the comment. */
+         while ( len < comstart - 1 ) buf[ len++ ] = ' ';
+
+/* Then append "/ " to introduce the comment, truncating if the card
+   length forces this. */
+         for ( i = 0; ( i < 2 ) && ( len < AST__FITSCHAN_FITSCARDLEN ); i++ ) {
+            buf[ len++ ] = "/ "[ i ];
+         }
+      }
+   }
+
+/* Append any comment, truncating it if the card length forces
+   this. */
+   if ( com ) {
+      for ( i = 0; com[ i ] && ( len < AST__FITSCHAN_FITSCARDLEN ); i++ ) {
+         buf[ len++ ] = com[ i ];
+      }
+   }
+
+/* Pad with spaces up to the end of the card. */
+   while ( len < AST__FITSCHAN_FITSCARDLEN ) buf[ len++ ] = ' ';
+
+/* Terminate it. */
+   buf[ AST__FITSCHAN_FITSCARDLEN ] = 0;
+}
+
+static int FullForm( const char *list, const char *test, int abbrev, int *status ){
+/*
+*  Name:
+*     FullForm
+
+*  Purpose:
+*     Identify the full form of an option string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int FullForm( const char *list, const char *test, int abbrev, int *status )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function identifies a supplied test option within a supplied
+*     list of valid options, and returns the index of the option within
+*     the list. The test option may be abbreviated, and case is
+*     insignificant.
+
+*  Parameters:
+*     list
+*        A list of space separated option strings.
+*     test
+*        A candidate option string.
+*     abbrev
+*        1 if abbreviations are to be accepted. Zero otherwise.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The index of the identified option within the supplied list, starting
+*     at zero. -1 is returned if the option is not recognised, and (if
+*     abbrev is 1 ) -2 if the option is ambiguous (no errors are reported
+*     in these cases). If abbrev is zero, the returned index will be the
+*     index of the first matching string.
+*
+
+*  Notes:
+*     -  A value of -1 is returned if an error has already occurred, or
+*     if this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   char *context;          /* Context used by strtok_r */
+   char *llist;            /* Pointer to a local copy of the options list */
+   char *option;           /* Pointer to the start of the next option */
+   int i;                  /* Current option index */
+   int len;                /* Length of supplied option */
+   int nmatch;             /* Number of matching options */
+   int ret;                /* The returned index */
+
+/* Initialise the answer to indicate that the option has not been
+   identified. */
+   ret = -1;
+
+/* Avoid compiler warnings. */
+   context = NULL;
+
+/* Check global status. */
+   if( !astOK ) return ret;
+
+/* Take a local copy of the supplied options list. This is necessary since
+   "strtok" modified the string by inserting null characters. */
+   llist = (char *) astStore( NULL, (void *) list, strlen(list) + 1 );
+   if( astOK ){
+
+/* Save the number of characters in the supplied test option (excluding
+   trailing spaces). */
+      len = ChrLen( test, status );
+
+/* Compare the supplied test option against each of the known options in
+   turn. Count the number of matches. */
+      nmatch = 0;
+#if HAVE_STRTOK_R
+      option = strtok_r( llist, " ", &context );
+#else
+      option = strtok( llist, " " );
+#endif
+      i = 0;
+      while( option ){
+
+/* If every character in the supplied label matches the corresponding
+   character in the current test label we have a match. Increment the
+   number of matches and save the current item index. If abbreviation is
+   not allowed ensure that the lengths of the strings are equal. */
+         if( !Ustrncmp( test, option, len, status ) && ( abbrev ||
+             len == ChrLen( option, status ) ) ) {
+            nmatch++;
+            ret = i;
+            if( !abbrev ) break;
+         }
+
+/* Get a pointer to the next option. */
+#if HAVE_STRTOK_R
+         option = strtok_r( NULL, " ", &context );
+#else
+         option = strtok( NULL, " " );
+#endif
+         i++;
+      }
+
+/* Return -1 if no match was found. */
+      if( !nmatch ){
+         ret = -1;
+
+/* Return -2 if the option was ambiguous. */
+      } else if( abbrev && nmatch > 1 ){
+         ret = -2;
+      }
+
+/* Free the local copy of the options list. */
+      llist = (char *) astFree( (void *) llist );
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static const char *GetAllWarnings( AstFitsChan *this, int *status ){
+
+/*
+*+
+*  Name:
+*     astGetAllWarnings
+
+*  Purpose:
+*     Return a list of all condition names.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     const char *GetAllWarnings( AstFitsChan *this )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function returns a space separated lits of the condition names
+*     currently recognized by the Warnings attribute.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+
+*  Returned Value:
+*     A pointer to a static string holding the condition names.
+
+*  Notes:
+*     - This routine does not check the inherited status.
+*-
+*/
+
+/* Return the result. */
+   return ALLWARNINGS;
+}
+const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected astGetAttrib
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a FitsChan, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the FitsChan, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the FitsChan. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+   const char *result;           /* Pointer value to return */
+   int ival;                     /* Integer attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_object;
+
+/* Card. */
+/* ----- */
+   if ( !strcmp( attrib, "card" ) ) {
+      ival = astGetCard( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* CardComm. */
+/* --------- */
+   } else if ( !strcmp( attrib, "cardcomm" ) ) {
+      result = astGetCardComm( this );
+
+/* CardName. */
+/* --------- */
+   } else if ( !strcmp( attrib, "cardname" ) ) {
+      result = astGetCardName( this );
+
+/* CardType. */
+/* --------- */
+   } else if ( !strcmp( attrib, "cardtype" ) ) {
+      ival = astGetCardType( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Encoding. */
+/* --------- */
+   } else if ( !strcmp( attrib, "encoding" ) ) {
+      ival = astGetEncoding( this );
+      if ( astOK ) {
+         if( ival == NATIVE_ENCODING ){
+            result = NATIVE_STRING;
+         } else if( ival == FITSPC_ENCODING ){
+            result = FITSPC_STRING;
+         } else if( ival == FITSIRAF_ENCODING ){
+            result = FITSIRAF_STRING;
+         } else if( ival == FITSAIPS_ENCODING ){
+            result = FITSAIPS_STRING;
+         } else if( ival == FITSAIPSPP_ENCODING ){
+            result = FITSAIPSPP_STRING;
+         } else if( ival == FITSCLASS_ENCODING ){
+            result = FITSCLASS_STRING;
+         } else if( ival == FITSWCS_ENCODING ){
+            result = FITSWCS_STRING;
+         } else if( ival == DSS_ENCODING ){
+            result = DSS_STRING;
+         } else {
+            result = UNKNOWN_STRING;
+         }
+      }
+
+/* CDMatrix */
+/* -------- */
+   } else if ( !strcmp( attrib, "cdmatrix" ) ) {
+      ival = astGetCDMatrix( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* DefB1950 */
+/* -------- */
+   } else if ( !strcmp( attrib, "defb1950" ) ) {
+      ival = astGetDefB1950( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* TabOK */
+/* ----- */
+   } else if ( !strcmp( attrib, "tabok" ) ) {
+      ival = astGetTabOK( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* CarLin */
+/* ------ */
+   } else if ( !strcmp( attrib, "carlin" ) ) {
+      ival = astGetCarLin( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* PolyTan */
+/* ------- */
+   } else if ( !strcmp( attrib, "polytan" ) ) {
+      ival = astGetPolyTan( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Iwc */
+/* --- */
+   } else if ( !strcmp( attrib, "iwc" ) ) {
+      ival = astGetIwc( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Clean */
+/* ----- */
+   } else if ( !strcmp( attrib, "clean" ) ) {
+      ival = astGetClean( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* FitsAxisOrder. */
+/* -------------- */
+   } else if ( !strcmp( attrib, "fitsaxisorder" ) ) {
+      result = astGetFitsAxisOrder( this );
+
+/* FitsDigits. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "fitsdigits" ) ) {
+      ival = astGetFitsDigits( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Ncard. */
+/* ------ */
+   } else if ( !strcmp( attrib, "ncard" ) ) {
+      ival = astGetNcard( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Nkey. */
+/* ----- */
+   } else if ( !strcmp( attrib, "nkey" ) ) {
+      ival = astGetNkey( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* AllWarnings */
+/* ----------- */
+   } else if ( !strcmp( attrib, "allwarnings" ) ) {
+      result = astGetAllWarnings( this );
+
+/* Warnings. */
+/* -------- */
+   } else if ( !strcmp( attrib, "warnings" ) ) {
+      result = astGetWarnings( this );
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetCard( AstFitsChan *this, int *status ){
+
+/*
+*+
+*  Name:
+*     astGetCard
+
+*  Purpose:
+*     Get the value of the Card attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int astGetCard( AstFitsChan *this )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function returns the value of the Card attribute for the supplied
+*     FitsChan. This is the index of the next card to be read from the
+*     FitsChan. The index of the first card is 1. If there are no more
+*     cards to be read, a value one greater than the number of cards in the
+*     FitsChan is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+
+*  Returned Value:
+*     The index of the next card to be read.
+
+*  Notes:
+*     - A value of zero will be returned if the current card is not defined.
+*     - This function attempts to execute even if an error has occurred.
+*-
+*/
+
+/* Local Variables: */
+   const char *class;      /* Pointer to class string */
+   const char *method;     /* Pointer to method string */
+   FitsCard *card0;        /* Pointer to current FitsCard */
+   int index;              /* Index of next FitsCard */
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Return if no FitsChan was supplied, or if the FitsChan is empty. */
+   if ( !this || !this->head ) return 0;
+
+/* Store the method and object class. */
+   method = "astGetCard";
+   class = astGetClass( this );
+
+/* Save a pointer to the current card, and the reset the current card to
+   be the first card. */
+   card0 = this->card;
+   astClearCard( this );
+
+/* Count through the list of FitsCards in the FitsChan until the original
+   current card is reached. If the current card is not found (for instance
+   if it has been marked as deleted and we are currently skipping such cards),
+   this->card will be left null (end-of-file). */
+   index = 1;
+   while( this->card != card0 && astOK && this->card ){
+
+/* Increment the card count and move on to the next card. */
+      index++;
+      MoveCard( this, 1, method, class, status );
+   }
+
+/* Return the card index. */
+   return index;
+}
+
+static const char *GetCardComm( AstFitsChan *this, int *status ){
+/*
+*+
+*  Name:
+*     GetCardComm
+
+*  Purpose:
+*     Get the value of the CardComm attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     const char *astGetCardComm( AstFitsChan *this)
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function returns the value of the CardComm attribute for the
+*     supplied FitsChan. This is the comment for the current card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+
+*  Returned Value:
+*     A pointer to a static string holding the comment. A zero-length
+*     string is returned if the card has no comment.
+
+*  Notes:
+*     - A value of NULL will be returned if an error has already
+*     occurred, or if this function should fail for any reason.
+*-
+*/
+
+/* Local Variables */
+   const char *result = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Get the comment for the current card. */
+   result = CardComm( this, status );
+
+/* Return a zero-length string if the card has no comment. */
+   if( astOK && !result ) result = "";
+
+/* Return the comment. */
+   return result;
+}
+
+static const char *GetCardName( AstFitsChan *this, int *status ){
+/*
+*+
+*  Name:
+*     GetCardName
+
+*  Purpose:
+*     Get the value of the CardName attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     const char *astGetCardName( AstFitsChan *this)
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function returns the value of the CardName attribute for the
+*     supplied FitsChan. This is the keyword name for the current card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+
+*  Returned Value:
+*     A pointer to a static string holding the keyword name.
+
+*  Notes:
+*     - A value of NULL will be returned if an error has already
+*     occurred, or if this function should fail for any reason.
+*-
+*/
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Return the keyword name of the current card. */
+   return CardName( this, status );
+}
+
+static int GetCardType( AstFitsChan *this, int *status ){
+/*
+*+
+*  Name:
+*     GetCardType
+
+*  Purpose:
+*     Get the value of the CardType attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int astGetCardType( AstFitsChan *this )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function returns the value of teh CardType attribute for the supplied
+*     FitsChan. This is the data type of the keyword value for the current card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+
+*  Returned Value:
+*     An integer representing the data type of the current card.
+
+*  Notes:
+*     - A value of AST__NOTYPE will be returned if an error has already
+*     occurred, or if this function should fail for any reason.
+*-
+*/
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Return the data type of the current card. */
+   return CardType( this, status );
+}
+
+static int GetFull( AstChannel *this_channel, int *status ) {
+/*
+*  Name:
+*     GetFull
+
+*  Purpose:
+*     Obtain the value of the Full attribute for a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int GetFull( AstChannel *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected astGetFull
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function return the integer value of the Full attribute for
+*     a FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Full attribute value.
+
+*  Notes:
+*     - This function modifies the default Full value from 0 to -1 for
+*     the benefit of the FitsChan class. This prevents non-essential
+*     information being written by the astWrite method unless it is
+*     requested by explicitlt setting a Full value.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+   int result;                   /* Result value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* If the Full attribute us set, obtain its value using the parent class
+   method. */
+   if ( astTestFull( this ) ) {
+      result = (* parent_getfull)( this_channel, status );
+
+/* Otherwise, supply a default value of -1. */
+   } else {
+      result = -1;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static FitsCard *GetLink( FitsCard *card, int next, const char *method,
+                          const char *class, int *status ){
+/*
+*  Name:
+*     GetLink
+
+*  Purpose:
+*     Get a pointer to the next or previous card in the list.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     FitsCard *GetLink( FitsCard *card, int next, const char *method,
+*                        const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns the a pointer to either the next or previous FitsCard
+*     structure in the circular linked list of such structures stored in a
+*     FitsChan. A check is performed to ensure that the forward and
+*     backward links from the supplied card are consistent and an error
+*     is reported if they are not (so long as no previous error has been
+*     reported). Memory corruption can result in inconsistent links
+*     which can result in infinite loops if an attempt is made to scan the
+*     list.
+
+*  Parameters:
+*     card
+*        The current card.
+*     next
+*        If non-zero, a pointer to the "next" card is returned. Otherwise
+*        a pointer to the "previous" card is returned.
+*     method
+*        Pointer to string holding the name of the calling method.
+*     class
+*        Pointer to string holding the object class.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the required card, or NULL if an error occurs.
+
+*  Notes:
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   FitsCard *ret;               /* Pointer to the returned card */
+
+/* Check that the "next" link from the previous card points back to
+   the current card, and that the "prev" link from the next card points
+   back to the current card. */
+   if( card && ( card->prev->next != card ||
+                 card->next->prev != card ) ){
+
+/* Report an error so long as no previous error has been reported, and
+   return a NULL pointer. */
+      if( astOK ){
+         astError( AST__FCRPT, "%s(%s): A corrupted %s object has been "
+                   "supplied.", status, method, class, class );
+      }
+      ret = NULL;
+
+/* If the links are good, return a pointer to the required card. */
+   } else {
+      ret = next ? card->next : card->prev;
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static int GetNcard( AstFitsChan *this, int *status ){
+
+/*
+*+
+*  Name:
+*     astGetNcard
+
+*  Purpose:
+*     Get the value of the Ncard attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int astGetNcard( AstFitsChan *this )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function returns the value of the Ncard attribute for the supplied
+*     FitsChan. This is the number of cards currently in the FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+
+*  Returned Value:
+*     The number of cards currently in the FitsChan.
+
+*  Notes:
+*     - A value of zero will be returned if an error has already
+*     occurred, or if this function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   const char *class;      /* Pointer to class string */
+   const char *method;     /* Pointer to method string */
+   FitsCard *card0;        /* Pointer to current card on entry */
+   int ncard;              /* Number of cards so far */
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Return zero if an error has already occurred, or no FitsChan was supplied,
+   or the FitsChan is empty. */
+   if ( !astOK || !this || !this->head ) return 0;
+
+/* Store the method and object class. */
+   method = "astGetNcard";
+   class = astGetClass( this );
+
+/* Save a pointer to the current card, and then reset the current card to
+   be the first card. */
+   card0 = this->card;
+   astClearCard( this );
+
+/* Count through the cards in the FitsChan until the end of file is reached. */
+   ncard = 0;
+   while( astOK && this->card ){
+
+/* Increment the card count and move on to the next card. */
+      ncard++;
+      MoveCard( this, 1, method, class, status );
+   }
+
+/* Reset the current card to be the original current card. */
+   this->card = card0;
+
+/* Return the result. */
+   return astOK ? ncard : 0;
+}
+
+static int GetNkey( AstFitsChan *this, int *status ){
+
+/*
+*+
+*  Name:
+*     astGetNkey
+
+*  Purpose:
+*     Get the value of the Nkey attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int astGetNkey( AstFitsChan *this )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function returns the value of the Nkey attribute for the supplied
+*     FitsChan. This is the number of unique keywords currently in the
+*     FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+
+*  Returned Value:
+*     The number of  unique keywords currently in the FitsChan.
+
+*  Notes:
+*     - A value of zero will be returned if an error has already
+*     occurred, or if this function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *km;          /* KeyMap holding unique keyword names */
+   FitsCard *card0;        /* Pointer to current card on entry */
+   const char *class;      /* Pointer to class string */
+   const char *method;     /* Pointer to method string */
+   int nkey;               /* Returned Nkey value */
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Return zero if an error has already occurred, or no FitsChan was supplied,
+   or the FitsChan is empty. */
+   if ( !astOK || !this || !this->head ) return 0;
+
+/* Store the method and object class. */
+   method = "astGetNkey";
+   class = astGetClass( this );
+
+/* Create an empty KeyMap to hold the unused keyword names */
+   km = astKeyMap( " ", status );
+
+/* Save a pointer to the current card, and then reset the current card to
+   be the first card. */
+   card0 = this->card;
+   astClearCard( this );
+
+/* Loop through the cards in the FitsChan until the end of file is reached. */
+   while( astOK && this->card ){
+
+/* Get the keyword name for the current card and add it to the keymap. */
+      astMapPut0I( km, CardName( this, status ), 0, NULL );
+
+/* Move on to the next unused card. */
+      MoveCard( this, 1, method, class, status );
+   }
+
+/* Reset the current card to be the original current card. */
+   this->card = card0;
+
+/* Get the number of keywords. */
+   nkey = astMapSize( km );
+
+/* Annull the KeyMap . */
+   km = astAnnul( km );
+
+/* Return the result. */
+   return astOK ? nkey : 0;
+}
+
+static void GetNextData( AstChannel *this_channel, int skip, char **name,
+                         char **val, int *status ) {
+/*
+*  Name:
+*     GetNextData
+
+*  Purpose:
+*     Read the next item of data from a data source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void GetNextData( AstChannel *this, int skip, char **name, char **val )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected
+*     astGetNextData method inherited from the Channel class).
+
+*  Description:
+*     This function reads the next item of input data from a data
+*     source associated with a FitsChan and returns the result.  It
+*     decodes the data item and returns name/value pairs ready for
+*     use.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     skip
+*        A non-zero value indicates that a new Object is to be read,
+*        and that all input data up to the next "Begin" item are to be
+*        skipped in order to locate it. This is useful if the data
+*        source contains AST objects interspersed with other data (but
+*        note that these other data cannot appear inside AST Objects,
+*        only between them).
+*
+*        A zero value indicates that all input data are significant
+*        and the next item will therefore be read and an attempt made
+*        to interpret it whatever it contains. Any other data
+*        inter-mixed with AST Objects will then result in an error.
+*     name
+*        An address at which to store a pointer to a null-terminated
+*        dynamically allocated string containing the name of the next
+*        item in the input data stream. This name will be in lower
+*        case with no surrounding white space.  It is the callers
+*        responsibilty to free the memory holding this string (using
+*        astFree) when it is no longer required.
+*
+*        A NULL pointer value will be returned (without error) to
+*        indicate when there are no further input data items to be
+*        read.
+*     val
+*        An address at which to store a pointer to a null-terminated
+*        dynamically allocated string containing the value associated
+*        with the next item in the input data stream. No case
+*        conversion is performed on this string and all white space is
+*        potentially significant.  It is the callers responsibilty to
+*        free the memory holding this string (using astFree) when it
+*        is no longer required.
+*
+*        The returned pointer will be NULL if an Object data item is
+*        read (see the "Data Representation" section).
+
+*  Data Representation:
+
+*     The returned data items fall into the following categories:
+*
+*     - Begin: Identified by the name string "begin", this indicates
+*     the start of an Object definition. The associated value string
+*     gives the class name of the Object being defined.
+*
+*     - IsA: Identified by the name string "isa", this indicates the
+*     end of the data associated with a particular class structure
+*     within the definiton of a larger Object. The associated value
+*     string gives the name of the class whose data have just been
+*     read.
+*
+*     - End: Identified by the name string "end", this indicates the
+*     end of the data associated with a complete Object
+*     definition. The associated value string gives the class name of
+*     the Object whose definition is being ended.
+*
+*     - Non-Object: Identified by any other name string plus a
+*     non-NULL "val" pointer, this gives the value of a non-Object
+*     structure component (instance variable). The name identifies
+*     which instance variable it is (within the context of the class
+*     whose data are being read) and the value is encoded as a string.
+*
+*     - Object: Identified by any other name string plus a NULL "val"
+*     pointer, this identifies the value of an Object structure
+*     component (instance variable).  The name identifies which
+*     instance variable it is (within the context of the class whose
+*     data are being read) and the value is given by subsequent data
+*     items (so the next item should be a "Begin" item).
+
+*  Notes:
+*     - NULL pointer values will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Constants: */
+#define BUFF_LEN 100             /* Length of formatting buffer */
+
+/* Local Variables: */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+   char *keyword;                /* Pointer to current keyword string */
+   char *newdata;                /* Pointer to stripped string value */
+   char *upq;                    /* Pointer to unprequoted string */
+   char buff[ BUFF_LEN + 1 ];    /* Buffer for formatting values */
+   const char *class;            /* Pointer to object class */
+   const char *method;           /* Pointer to method name */
+   int cont;                     /* String ends with an ampersand? */
+   int done;                     /* Data item found? */
+   int freedata;                 /* Should the data pointer be freed? */
+   int i;                        /* Loop counter for keyword characters */
+   int len;                      /* Length of current keyword */
+   int nc;                       /* Number of characters read by "astSscanf" */
+   int nn;                       /* No. of characters after UnPreQuoting */
+   int type;                     /* Data type code */
+   void *data;                   /* Pointer to current data value */
+
+/* Initialise the returned pointer values. */
+   *name = NULL;
+   *val = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Store the method name and object class. */
+   method = "astRead";
+   class = astGetClass( this );
+
+/* Loop to consider successive cards stored in the FitsChan (starting
+   at the "current" card) until a valid data item is read or "end of
+   file" is reached. Also quit the loop if an error occurs. */
+   done = 0;
+   newdata = NULL;
+   while ( !done && !astFitsEof( this ) && astOK ){
+
+/* Obtain the keyword string, data type code and data value pointer
+   from the current card. */
+      keyword = CardName( this, status );
+      type = CardType( this, status );
+      data = CardData( this, NULL, status );
+
+/* Mark all cards as having been used unless we are skipping over cards which
+   may not be related to AST. */
+      if( !skip ) MarkCard( this, status );
+
+/* Ignore comment cards. */
+      if ( type != AST__COMMENT ) {
+
+/* Native encoding requires trailing white space to be removed from
+   string values (so that null strings can be distinguished from blank
+   strings). Do this now. */
+         freedata = 0;
+         if ( ( type == AST__STRING || type == AST__CONTINUE ) && data ){
+            newdata = (char *) astStore( NULL, data, strlen( (char *) data ) + 1 );
+            if( newdata ){
+               newdata[ ChrLen( data, status ) ] = 0;
+               data = (void *) newdata;
+               freedata = 1;
+            }
+         }
+
+/* Obtain the keyword length and test the card to identify the type of
+   AST data item (if any) that it represents. */
+         len = (int) strlen( keyword );
+
+/* "Begin" item. */
+/* ------------- */
+
+/* This is identified by a string value and a keyword of the form
+   "BEGASTxx", where "xx" are characters encoding a sequence
+   number. */
+         if ( ( type == AST__STRING ) &&
+              ( nc = 0,
+                ( 0 == astSscanf( keyword, "BEGAST"
+                                        "%*1[" SEQ_CHARS "]"
+                                        "%*1[" SEQ_CHARS "]%n", &nc ) )
+                && ( nc >= len ) ) ) {
+
+/* Note we have found a data item. */
+            done = 1;
+
+/* Set the returned name to "begin" and extract the associated class
+   name from the string value. Store both of these in dynamically
+   allocated strings. */
+            *name = astString( "begin", 5 );
+            *val = UnPreQuote( (const char *) data, status );
+
+/* Indicate that the current card has been used. */
+            MarkCard( this, status );
+
+/* The "begin" item will be preceded by a header of COMMENT cards. Mark
+   them as having been used. */
+            ComBlock( this, -1, method, class, status );
+
+/* "IsA" item. */
+/* ----------- */
+
+/* This is identified by a string value and a keyword of the form
+   "ISAxx", where "xx" are characters encoding a sequence
+   number. Don't accept the item if we are skipping over cards looking
+   for a "Begin" item. */
+         } else if ( !skip &&
+                     ( type == AST__STRING ) &&
+                     ( nc = 0,
+                       ( 0 == astSscanf( keyword,
+                                      "ISA"
+                                      "%*1[" SEQ_CHARS "]"
+                                      "%*1[" SEQ_CHARS "]%n", &nc ) )
+                       && ( nc >= len ) ) ) {
+
+/* Note we have found a data item. */
+            done = 1;
+
+/* Set the returned name to "isa" and extract the associated class
+   name from the string value. Store both of these in dynamically
+   allocated strings. */
+            *name = astString( "isa", 3 );
+            *val = UnPreQuote( (const char *) data, status );
+
+/* "End" item. */
+/* ----------- */
+
+/* This is identified by a string value and a keyword of the form
+   "ENDASTxx", where "xx" are characters encoding a sequence
+   number. Don't accept the item if we are skipping over cards looking
+   for a "Begin" item. */
+         } else if ( !skip &&
+                     ( type == AST__STRING ) &&
+                     ( nc = 0,
+                       ( 0 == astSscanf( keyword,
+                                      "ENDAST"
+                                      "%*1[" SEQ_CHARS "]"
+                                      "%*1[" SEQ_CHARS "]%n", &nc ) )
+                       && ( nc >= len ) ) ) {
+
+/* Note we have found a data item. */
+            done = 1;
+
+/* Set the returned name to "end" and extract the associated class
+   name from the string value. Store both of these in dynamically
+   allocated strings. */
+            *name = astString( "end", 3 );
+            *val = UnPreQuote( (const char *) data, status );
+
+/* The "end" item eill be followed by a footer of COMMENT cards. Mark
+   these cards as having been used. */
+            ComBlock( this, 1, method, class, status );
+
+/* Object or data item. */
+/* -------------------- */
+
+/* These are identified by a string, int, or double value, and a
+   keyword ending in two characters encoding a sequence number. Don't
+   accept the item if we are skipping over cards looking for a "Begin"
+   item. */
+         } else if ( !skip &&
+                     ( ( type == AST__STRING ) ||
+                       ( type == AST__INT ) ||
+                       ( type == AST__FLOAT ) ) &&
+                     ( len > 2 ) &&
+                     strchr( SEQ_CHARS, keyword[ len - 1 ] ) &&
+                     strchr( SEQ_CHARS, keyword[ len - 2 ] ) ) {
+
+/* Note we have found a data item. */
+            done = 1;
+
+/* Set the returned name by removing the last two characters from the
+   keyword and converting to lower case. Store this in a dynamically
+   allocated string. */
+            *name = astString( keyword, len - 2 );
+            for ( i = 0; ( *name )[ i ]; i++ ) {
+               ( *name )[ i ] = tolower( ( *name )[ i ] );
+            }
+
+/* Classify the data type. */
+            switch ( type ) {
+
+/* If the value is a string, test if it is zero-length. If so, this
+   "null" value indicates an Object data item (whose definition
+   follows), so leave the returned value pointer as NULL. Otherwise,
+   we have a string data item, so extract its value and store it in a
+   dynamically allocated string. */
+            case AST__STRING:
+               if ( *( (char *) data ) ) {
+
+/* A long string value may be continued on subsequent CONTINUE cards. See
+   if the current string may be continued. This is the case if the final
+   non-blank character (before UnPreQuoting) is an ampersand. */
+                  cont = ( ((char *) data)[ ChrLen( data, status ) - 1 ] == '&' );
+
+/* If the string does not end with an ampersand, just UnPreQUote it and
+   return a copy. */
+                  if( !cont ) {
+                     *val = UnPreQuote( (const char *) data, status );
+
+/* Otherwise, initialise the returned string to hold a copy of the keyword
+   value. */
+                  } else {
+                     nc = strlen( (const char *) data );
+                     *val = astStore( NULL, (const char *) data, nc + 1 );
+
+/* Loop round reading any subsequent CONTINUE cards. Leave the loop when
+   the end-of-file is hit, or an error occurs. */
+                     while( cont && MoveCard( this, 1, method, class, status ) &&
+                            astOK ){
+
+/* See if this is a CONTINUE card. If so, get its data pointer. */
+                        if( CardType( this, status ) == AST__CONTINUE ){
+                           data = CardData( this, NULL, status );
+
+/* See if the CONTINUE card ends with an ampersand (i.e. if there is
+   a possibility of there being any remaining CONTINUE cards). */
+                           cont = ( ( (char *) data)[ ChrLen( data, status ) - 1 ] == '&' );
+
+/* UnPreQUote it. */
+                           upq = UnPreQuote( (const char *) data, status );
+                           if( !astOK ) break;
+
+/* Expand the memory for the returned string to hold the new string. */
+                           nn = strlen( upq );
+                           *val = astRealloc( *val, nc + nn );
+                           if( !astOK ) break;
+
+/* Copy the new string into the expanded memory, so that the first
+   character of the new string over-writes the trailing ampersand
+   currently in the buffer. */
+                           strcpy( *val + nc - 1, upq );
+
+/* Release the memory holding the UnPreQUoted string . */
+                           upq = astFree( upq );
+
+/* Update the current length of the returned string. */
+                           nc += nn - 1;
+
+/* Mark the current card as having been read. */
+                           MarkCard( this, status );
+
+/* Report an error if this is not a CONTINUE card. */
+                        } else {
+                           astError( AST__BADIN, "%s(%s): One or more "
+                                     "FITS \"CONTINUE\" cards are missing "
+                                     "after the card for keyword \"%s\".", status,
+                                     method, class, keyword );
+                        }
+                     }
+                  }
+               }
+               break;
+
+/* If the value is an int, format it and store the result in a
+   dynamically allocated string. */
+            case AST__INT:
+               (void) sprintf( buff, "%d", *( (int *) data ) );
+               *val = astString( buff, (int) strlen( buff ) );
+               break;
+
+/* If the value is a double, format it and store the result in a
+   dynamically allocated string. */
+            case AST__FLOAT:
+               (void) sprintf( buff, "%.*g", DBL_DIG, *( (double *) data ) );
+               CheckZero( buff,  *( (double *) data ), 0, status );
+               *val = astString( buff, (int) strlen( buff ) );
+               break;
+            }
+
+/* Anything else. */
+/* -------------- */
+
+/* If the input line didn't match any of the above and the "skip" flag
+   is not set, then report an error.. */
+         } else if ( !skip ) {
+            astError( AST__BADIN,
+                      "%s(%s): Cannot interpret the input data given by "
+                      "FITS keyword \"%s\".", status, method, class, keyword );
+         }
+
+/* Free any memory used to hold stripped string data. */
+         if( freedata ) newdata = (char *) astFree( (void *) newdata );
+      }
+
+/* Increment the current card. */
+      MoveCard( this, 1, method, class, status );
+   }
+
+/* If an error occurred, ensure that any allocated memory is freed and
+   that NULL pointer values are returned. */
+   if ( !astOK ) {
+      *name = astFree( *name );
+      *val = astFree( *val );
+   }
+
+/* Undefine macros local to this function. */
+#undef BUFF_LEN
+}
+
+static int GetSkip( AstChannel *this_channel, int *status ) {
+/*
+*  Name:
+*     GetSkip
+
+*  Purpose:
+*     Obtain the value of the Skip attribute for a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int GetSkip( AstChannel *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected astGetSkip
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function return the (boolean) integer value of the Skip
+*     attribute for a FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Skip attribute value.
+
+*  Notes:
+*     - This function modifies the default Skip value from 0 to 1 for
+*     the benefit of the FitsChan class. This default value allows the
+*     astRead method to skip over unrelated FITS keywords when
+*     searching for the next Object to read.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+   int result;                   /* Result value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* If the Skip attribute us set, obtain its value using the parent class
+   method. */
+   if ( astTestSkip( this ) ) {
+      result = (* parent_getskip)( this_channel, status );
+
+/* Otherwise, supply a default value of 1. */
+   } else {
+      result = 1;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetValue( AstFitsChan *this, const char *keyname, int type,
+                     void *value, int report, int mark, const char *method,
+                     const char *class, int *status ){
+/*
+*  Name:
+*     GetValue
+
+*  Purpose:
+*     Obtain a FITS keyword value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int GetValue( AstFitsChan *this, const char *keyname, int type, void *value,
+*                   int report, int mark, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function gets a value for the specified keyword from the
+*     supplied FitsChan, and stores it in the supplied buffer. Optionally,
+*     the keyword is marked as having been read into an AST object so that
+*     it is not written out when the FitsChan is deleted.
+
+*  Parameters:
+*     this
+*        A pointer to the FitsChan containing the keyword values to be
+*        read.
+*     keyname
+*        A pointer to a string holding the keyword name.
+*     type
+*        The FITS data type in which to return the keyword value. If the
+*        stored value is not of the requested type, it is converted if
+*        possible.
+*     value
+*        A pointer to a buffer of suitable size to receive the keyword
+*        value. The supplied value is left unchanged if the keyword is
+*        not found.
+*     report
+*        Should an error be reported if the keyword cannot be found, or
+*        cannot be converted to the requested type?
+*     mark
+*        Should the card be marked as having been used?
+*     method
+*        A string holding the name of the calling method.
+*     class
+*        A string holding the object class.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the keyword does not exist in "this", or cannot be
+*     converted to the requested type. One is returned otherwise.
+
+*  Notes:
+*     - An error is reported if the keyword value is undefined.
+*     - A value of zero is returned if an error has already occurred,
+*     or if an error occurs within this function.
+*/
+
+/* Local Variables: */
+   int icard;                         /* Current card index */
+   int ret;                           /* Returned value */
+
+/* Check the status */
+   if( !astOK ) return 0;
+
+/* Save the current card index. */
+   icard = astGetCard( this );
+
+/* Attempt to find the supplied keyword. */
+   ret = SearchCard( this, keyname, method, class, status );
+
+/* If the keyword was found, convert the current card's data value and copy
+   it to the supplied buffer. */
+   if( ret ){
+      if( CnvValue( this, type, 0, value, method, status ) ) {
+
+/* If required, mark it as having been read into an AST object. */
+         if( mark ) MarkCard( this, status );
+
+/* If the value is undefined, report an error if "report" is non-zero. */
+         if( type == AST__UNDEF && report && astOK ) {
+            ret = 0;
+            astError( AST__FUNDEF, "%s(%s): FITS keyword \"%s\" has no value.",
+                      status, method, class, keyname );
+         }
+
+/* If the value could not be converted to the requested data, type report
+   an error if reporting is enabled. */
+      } else {
+         ret = 0;
+         if( report && astOK ){
+            astError( AST__FTCNV, "%s(%s): Cannot convert FITS keyword '%s' to %s.",
+                      status, method, class, keyname, type_names[ type ] );
+         }
+      }
+
+/* If the keyword was not found, report an error if "report" is non-zero. */
+   } else if( report && astOK ){
+      astError( AST__BDFTS, "%s(%s): Unable to find a value for FITS "
+                "keyword \"%s\".", status, method, class, keyname );
+   }
+
+/* Reinstate the original current card index. */
+   astSetCard( this, icard );
+
+/* If an error has occurred, return 0. */
+   if( !astOK ) ret = 0;
+
+/* Return the result. */
+   return ret;
+}
+
+static int GetValue2( AstFitsChan *this1, AstFitsChan *this2, const char *keyname,
+                      int type, void *value, int report, const char *method,
+                      const char *class, int *status ){
+/*
+*  Name:
+*     GetValue2
+
+*  Purpose:
+*     Obtain a FITS keyword value from one of two FitsChans.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int GetValue2( AstFitsChan *this1, AstFitsChan *this2, const char *keyname,
+*                    int type, void *value, int report, const char *method,
+*                    const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function attempts to get a value for the specified keyword from
+*     the first supplied FitsChan. If this fails (due to the FitsChan not
+*     containing a value for the ketword) then an attempt is made to get
+*     a value for the keyword from the second supplied FitsChan.
+
+*  Parameters:
+*     this1
+*        A pointer to the first FitsChan to be used.
+*     this2
+*        A pointer to the second FitsChan to be used.
+*     keyname
+*        A pointer to a string holding the keyword name.
+*     type
+*        The FITS data type in which to return the keyword value. If the
+*        stored value is not of the requested type, it is converted if
+*        possible.
+*     value
+*        A pointer to a buffer of suitable size to receive the keyword
+*        value. The supplied value is left unchanged if the keyword is
+*        not found.
+*     report
+*        Should an error be reported if the keyword cannot be found, or
+*        cannot be converted to the requested type?
+*     method
+*        A string holding the name of the calling method.
+*     class
+*        A string holding the object class.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the keyword does not exist in either FitsChan, or cannot be
+*     converted to the requested type. One is returned otherwise.
+
+*  Notes:
+*     -  A value of zero is returned if an error has already occurred,
+*     or if an error occurs within this function.
+*     -  If the card is found in the first FitsChan, it is not marked as
+*     having been used. If the card is found in the second FitsChan, it is
+*     marked as having been used.
+*/
+
+/* Local Variables: */
+   int ret;                           /* Returned value */
+
+/* Check the status */
+   if( !astOK ) return 0;
+
+/* Try the first FitsChan. If this fails try the second. Do not report
+   an error if the keyword is not found in the first FitsChan (this will
+   be done, if required, once the second FitsChan has been searched). */
+   ret = GetValue( this1, keyname, type, value, 0, 0, method, class, status );
+   if( ! ret ) {
+      ret = GetValue( this2, keyname, type, value, report, 1, method, class, status );
+   }
+
+/* If an error has occurred, return 0. */
+   if( !astOK ) ret = 0;
+
+/* Return the result. */
+   return ret;
+}
+
+static int HasAIPSSpecAxis( AstFitsChan *this, const char *method,
+                            const char *class, int *status ){
+
+/*
+*  Name:
+*     HasAIPSSpecAxis
+
+*  Purpose:
+*     Does the FitsChan contain an AIPS spectral CTYPE keyword?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     int HasAIPSSpecAxis( AstFitsChan *this, const char *method,
+*                          const char *class, int *status  )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function returns a non-zero value if the FitsCHan contains a
+*     CTYPE value which conforms to the non-standard system used by AIPS.
+
+*  Parameters:
+*     this
+*        A pointer to the FitsChan to be used.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if an AIPS spectral CTYPE keyword was found.
+*/
+
+/* Local Variables: */
+   char *assys;                   /* AIPS standard of rest type */
+   char *astype;                  /* AIPS spectral type */
+   char *cval;                    /* Pointer to character string */
+   int j;                         /* Current axis index */
+   int jhi;                       /* Highest axis index with a CTYPE */
+   int jlo;                       /* Lowest axis index with a CTYPE */
+   int ret;                       /* Returned value */
+
+/* Initialise */
+   ret = 0;
+
+/* Check the status */
+   if( !astOK ) return ret;
+
+/* If the FitsChan contains any CTYPE values, convert the bounds from
+   one-based to zero-based, and loop round them all. */
+   if( astKeyFields( this, "CTYPE%1d", 1, &jhi, &jlo ) ) {
+      jlo--;
+      jhi--;
+      for( j = jlo; j <= jhi; j++ ) {
+
+/* Get the next CTYPE value. If found, see if it is an AIPS spectral
+   CTYPE value. */
+         if( GetValue( this, FormatKey( "CTYPE", j + 1, -1, ' ', status ),
+                       AST__STRING, (void *) &cval, 0, 0, method,
+                       class, status ) ){
+            if( IsAIPSSpectral( cval, &astype, &assys, status ) ) {
+               ret = 1;
+               break;
+            }
+         }
+      }
+   }
+
+/* If an error has occurred, return 0. */
+   if( !astOK ) ret = 0;
+
+/* Return the result. */
+   return ret;
+}
+
+static int HasCard( AstFitsChan *this, const char *name,
+                    const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     HasCard
+
+*  Purpose:
+*     Check if the FitsChan contains a specified keyword.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int HasCard( AstFitsChan *this, const char *name,
+*                  const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns a non-zero value if the FitsChan contains the given keyword,
+*     and zero otherwise. The current card is unchanged.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        Pointer to a string holding the keyword name.
+*     method
+*        Pointer to string holding name of calling method.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if a card was found refering to the given
+*     keyword. Otherwise zero is returned.
+*/
+
+/* Check the supplied pointers (we can rely on astMapHasKey to check the
+   inherited status). */
+   if( !name || !this || !this->keywords ) return 0;
+
+/* Search the KeyMap holding the keywords currently in the FitsChan,
+   returning non-zero if the keyword was found. A KeyMap is used because
+   it uses a hashing algorithm to find the entries and is therefore a lot
+   quicker than searching through the list of linked FitsCards. */
+   return astMapHasKey( this->keywords, name );
+}
+void astInitFitsChanVtab_(  AstFitsChanVtab *vtab, const char *name, int *status ) {
+
+/*
+*+
+*  Name:
+*     astInitFitsChanVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a FitsChan.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void astInitFitsChanVtab( AstFitsChanVtab *vtab, const char *name )
+
+*  Class Membership:
+*     FitsChan vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the FitsChan class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstChannelVtab *channel;      /* Pointer to Channel component of Vtab */
+   char buf[ 100 ];              /* Buffer large enough to store formatted INT_MAX */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitChannelVtab( (AstChannelVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAFitsChan) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstChannelVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->PutCards = PutCards;
+   vtab->PutFits = PutFits;
+   vtab->DelFits = DelFits;
+   vtab->GetTables = GetTables;
+   vtab->PutTables = PutTables;
+   vtab->PutTable = PutTable;
+   vtab->TableSource = TableSource;
+   vtab->SetTableSource = SetTableSource;
+   vtab->RemoveTables = RemoveTables;
+   vtab->PurgeWCS = PurgeWCS;
+   vtab->RetainFits = RetainFits;
+   vtab->FindFits = FindFits;
+   vtab->KeyFields = KeyFields;
+   vtab->ReadFits = ReadFits;
+   vtab->ShowFits = ShowFits;
+   vtab->WriteFits = WriteFits;
+   vtab->EmptyFits = EmptyFits;
+   vtab->FitsEof = FitsEof;
+   vtab->GetFitsCF = GetFitsCF;
+   vtab->GetFitsCI = GetFitsCI;
+   vtab->GetFitsF = GetFitsF;
+   vtab->GetFitsI = GetFitsI;
+   vtab->GetFitsL = GetFitsL;
+   vtab->TestFits = TestFits;
+   vtab->GetFitsS = GetFitsS;
+   vtab->GetFitsCN = GetFitsCN;
+   vtab->FitsGetCom = FitsGetCom;
+   vtab->SetFitsCom = SetFitsCom;
+   vtab->SetFitsCF = SetFitsCF;
+   vtab->SetFitsCI = SetFitsCI;
+   vtab->SetFitsF = SetFitsF;
+   vtab->SetFitsI = SetFitsI;
+   vtab->SetFitsL = SetFitsL;
+   vtab->SetFitsU = SetFitsU;
+   vtab->SetFitsS = SetFitsS;
+   vtab->SetFitsCN = SetFitsCN;
+   vtab->SetFitsCM = SetFitsCM;
+   vtab->ClearCard = ClearCard;
+   vtab->TestCard = TestCard;
+   vtab->SetCard = SetCard;
+   vtab->GetCard = GetCard;
+   vtab->ClearFitsDigits = ClearFitsDigits;
+   vtab->TestFitsDigits = TestFitsDigits;
+   vtab->SetFitsDigits = SetFitsDigits;
+   vtab->GetFitsDigits = GetFitsDigits;
+   vtab->ClearFitsAxisOrder = ClearFitsAxisOrder;
+   vtab->TestFitsAxisOrder = TestFitsAxisOrder;
+   vtab->SetFitsAxisOrder = SetFitsAxisOrder;
+   vtab->GetFitsAxisOrder = GetFitsAxisOrder;
+   vtab->ClearDefB1950 = ClearDefB1950;
+   vtab->TestDefB1950 = TestDefB1950;
+   vtab->SetDefB1950 = SetDefB1950;
+   vtab->GetDefB1950 = GetDefB1950;
+   vtab->ClearTabOK = ClearTabOK;
+   vtab->TestTabOK = TestTabOK;
+   vtab->SetTabOK = SetTabOK;
+   vtab->GetTabOK = GetTabOK;
+   vtab->ClearCarLin = ClearCarLin;
+   vtab->TestCarLin = TestCarLin;
+   vtab->SetCarLin = SetCarLin;
+   vtab->GetCarLin = GetCarLin;
+   vtab->ClearPolyTan = ClearPolyTan;
+   vtab->TestPolyTan = TestPolyTan;
+   vtab->SetPolyTan = SetPolyTan;
+   vtab->GetPolyTan = GetPolyTan;
+   vtab->ClearIwc = ClearIwc;
+   vtab->TestIwc = TestIwc;
+   vtab->SetIwc = SetIwc;
+   vtab->GetIwc = GetIwc;
+   vtab->ClearWarnings = ClearWarnings;
+   vtab->TestWarnings = TestWarnings;
+   vtab->SetWarnings = SetWarnings;
+   vtab->GetWarnings = GetWarnings;
+   vtab->GetCardType = GetCardType;
+   vtab->GetCardName = GetCardName;
+   vtab->GetCardComm = GetCardComm;
+   vtab->GetNcard = GetNcard;
+   vtab->GetNkey = GetNkey;
+   vtab->GetAllWarnings = GetAllWarnings;
+   vtab->ClearEncoding = ClearEncoding;
+   vtab->TestEncoding = TestEncoding;
+   vtab->SetEncoding = SetEncoding;
+   vtab->GetEncoding = GetEncoding;
+   vtab->ClearClean = ClearClean;
+   vtab->TestClean = TestClean;
+   vtab->SetClean = SetClean;
+   vtab->GetClean = GetClean;
+   vtab->ClearCDMatrix = ClearCDMatrix;
+   vtab->TestCDMatrix = TestCDMatrix;
+   vtab->SetCDMatrix = SetCDMatrix;
+   vtab->GetCDMatrix = GetCDMatrix;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   channel = (AstChannelVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+   parent_write = channel->Write;
+   channel->Write = Write;
+   parent_read = channel->Read;
+   channel->Read = Read;
+   parent_getskip = channel->GetSkip;
+   channel->GetSkip = GetSkip;
+   parent_getfull = channel->GetFull;
+   channel->GetFull = GetFull;
+   channel->WriteBegin = WriteBegin;
+   channel->WriteIsA = WriteIsA;
+   channel->WriteEnd = WriteEnd;
+   channel->WriteInt = WriteInt;
+   channel->WriteDouble = WriteDouble;
+   channel->WriteString = WriteString;
+   channel->WriteObject = WriteObject;
+   channel->GetNextData = GetNextData;
+   parent_setsourcefile = channel->SetSourceFile;
+   channel->SetSourceFile = SetSourceFile;
+
+/* Declare the class dump, copy and delete functions.*/
+   astSetDump( vtab, Dump, "FitsChan", "I/O channels to FITS files" );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+
+/* Max number of characters needed to format an int. */
+   LOCK_MUTEX4
+   sprintf( buf, "%d", INT_MAX );
+   int_dig = strlen( buf );
+
+/* Create a pair of MJD TimeFrames which will be used for converting to and
+   from TDB. */
+   astBeginPM;
+   if( !tdbframe ) tdbframe = astTimeFrame( "system=MJD,timescale=TDB", status );
+   if( !timeframe ) timeframe = astTimeFrame( "system=MJD", status );
+   astEndPM;
+   UNLOCK_MUTEX4
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void InsCard( AstFitsChan *this, int overwrite, const char *name,
+                     int type, void *data, const char *comment,
+                     const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     InsCard
+
+*  Purpose:
+*     Inserts a card into a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void InsCard( AstFitsChan *this, int overwrite, const char *name,
+*                   int type, void *data, const char *comment,
+*                   const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Either appends a new card to a FitsChan, or over-writes an existing
+*     card, holding the supplied keyword name, value and comment.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan containing the filters to apply to the
+*        keyword name. If a NULL pointer is supplied, no filtering is applied.
+*     overwrite
+*        If non-zero, the new card over-writes the current card given by
+*        the "Card" attribute, and the current card is incremented so
+*        that it refers to the next card. Otherwise, the new card is
+*        inserted in front of the current card and the current card is
+*        left unchanged.
+*     name
+*        Pointer to a string holding the keyword name of the new card.
+*     type
+*        An integer value representing the data type of the keyword.
+*     data
+*        Pointer to the data associated with the keyword.
+*     comment
+*        Pointer to a null-terminated string holding a comment.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  An error is reported if an attempt is made to change the data type
+*     of an existing card.
+*     -  If a type of AST__COMMENT is supplied, then any data value (of any
+*     type) associated with an existing card is left unchanged.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS     /* Declare the thread specific global data */
+   int flags;             /* Flags to assign to new card */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* If the current card is to be over-written, delete the current card (the
+   next card in the list, if any, will become the new current card). */
+   if( overwrite ) DeleteCard( this, method, class, status );
+
+/* If requested, set both NEW flags for the new card. */
+   flags = ( mark_new ) ? ( NEW1 | NEW2 ): 0;
+
+/* Insert the new card into the list, just before the current card. */
+   NewCard( this, name, type, data, comment, flags, status );
+}
+
+static int IRAFFromStore( AstFitsChan *this, FitsStore *store,
+                          const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     IRAFFromStore
+
+*  Purpose:
+*     Store WCS keywords in a FitsChan using FITS-IRAF encoding.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     int IRAFFromStore( AstFitsChan *this, FitsStore *store,
+*                        const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function copies the WCS information stored in the supplied
+*     FitsStore into the supplied FitsChan, using FITS-IRAF encoding.
+*
+*     IRAF encoding is like FITS-WCS encoding but with the following
+*     restrictions:
+*
+*     1) The celestial projection must not have any projection parameters
+*     which are not set to their default values. The one exception to this
+*     is that SIN projections are acceptable if the associated projection
+*     parameter PV<axlat>_1 is zero and PV<axlat>_2 = cot( reference point
+*     latitude). This is encoded using the string "-NCP". The SFL projection
+*     is encoded using the string "-GLS". Note, the original IRAF WCS
+*     system only recognised a small subset of the currently available
+*     projections, but some more recent IRAF-like software recognizes some
+*     of the new projections included in the FITS-WCS encoding.
+*
+*     2) The celestial axes must be RA/DEC, galactic or ecliptic.
+*
+*     3) LONPOLE and LATPOLE cannot be used.
+*
+*     4) Only primary axis descriptions are written out.
+*
+*     5) RADECSYS is used in place of RADESYS.
+*
+*     6) PC/CDELT keywords are not allowed (CD must be used)
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        Pointer to the FitsStore.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if succesfull, and zero is returned
+*     otherwise.
+*/
+
+/* Local Variables: */
+   char *comm;         /* Pointer to comment string */
+   char *cval;         /* Pointer to string keyword value */
+   char combuf[80];    /* Buffer for FITS card comment */
+   char lattype[MXCTYPELEN];/* Latitude axis CTYPE */
+   char lontype[MXCTYPELEN];/* Longitude axis CTYPE */
+   char s;             /* Co-ordinate version character */
+   char sign[2];       /* Fraction's sign character */
+   double cdelt;       /* A CDELT value */
+   double fd;          /* Fraction of a day */
+   double mjd99;       /* MJD at start of 1999 */
+   double p1, p2;      /* Projection parameters */
+   double val;         /* General purpose value */
+   int axlat;          /* Index of latitude FITS WCS axis */
+   int axlon;          /* Index of longitude FITS WCS axis */
+   int axspec;         /* Index of spectral FITS WCS axis */
+   int i;              /* Axis index */
+   int ihmsf[ 4 ];     /* Hour, minute, second, fractional second */
+   int iymdf[ 4 ];     /* Year, month, date, fractional day */
+   int j;              /* Axis index */
+   int jj;             /* SlaLib status */
+   int naxis;          /* No. of axes */
+   int ok;             /* Is FitsSTore OK for IRAF encoding? */
+   int prj;            /* Projection type */
+   int ret;            /* Returned value. */
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* First check that the values in the FitsStore conform to the
+   requirements of the IRAF encoding. Assume they do to begin with. */
+   ok = 1;
+
+/* Just do primary axes. */
+   s = ' ';
+
+/* Look for the primary celestial and spectral axes. */
+   FindLonLatSpecAxes( store, s, &axlon, &axlat, &axspec, method, class, status );
+
+/* If both longitude and latitude axes are present and thereis no
+   spectral axis...*/
+   if( axlon >= 0 && axlat >= 0 ) {
+
+/* Get the CTYPE values for both axes. */
+      cval = GetItemC( &(store->ctype), axlon, 0, s, NULL, method, class, status );
+      if( !cval ) return ret;
+      strcpy( lontype, cval );
+      cval = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
+      if( !cval ) return ret;
+      strcpy( lattype, cval );
+
+/* Extract the projection type as specified by the last 4 characters
+   in the CTYPE keyword value. */
+      prj = astWcsPrjType( lattype + 4 );
+
+/* Check the projection type is OK. Assume not initially. */
+      ok = 0;
+
+/* FITS-IRAF cannot handle the AST-specific TPN projection. */
+      if( prj == AST__TPN ||  prj == AST__WCSBAD ) {
+         ok = 0;
+
+/* SIN projections are handled later. */
+      } else if( prj != AST__SIN ){
+
+/* There must be no projection parameters. */
+         if( GetMaxJM( &(store->pv), ' ', status ) == -1 ) ok = 1;
+
+/* Change the new SFL projection code to to the older equivalent GLS */
+         if( prj == AST__SFL ){
+            (void) strcpy( lontype + 4, "-GLS" );
+            (void) strcpy( lattype + 4, "-GLS" );
+         }
+
+/* SIN projections are only acceptable if the associated projection
+   parameters are both zero, or if the first is zero and the second
+   = cot( reference point latitude )  (the latter case is equivalent to
+   the old NCP projection). */
+      } else {
+         p1 = GetItem( &( store->pv ), axlat, 1, s, NULL, method, class, status );
+         p2 = GetItem( &( store->pv ), axlat, 2, s, NULL, method, class, status );
+         if( p1 == AST__BAD ) p1 = 0.0;
+         if( p2 == AST__BAD ) p2 = 0.0;
+         val = GetItem( &( store->crval ), axlat, 0, s, NULL, method, class, status );
+         if( val != AST__BAD ) {
+            if( p1 == 0.0 ) {
+               if( p2 == 0.0 ) {
+                  ok = 1;
+               } else if( fabs( p2 ) >= 1.0E14 && val == 0.0 ){
+                  ok = 1;
+                  (void) strcpy( lontype + 4, "-NCP" );
+                  (void) strcpy( lattype + 4, "-NCP" );
+               } else if( fabs( p2*tan( AST__DD2R*val ) - 1.0 )
+                          < 0.01 ){
+                  ok = 1;
+                  (void) strcpy( lontype + 4, "-NCP" );
+                  (void) strcpy( lattype + 4, "-NCP" );
+               }
+            }
+         }
+      }
+
+/* Identify the celestial coordinate system from the first 4 characters of the
+   longitude CTYPE value. Only RA, galactic longitude, and ecliptic
+   longitude can be stored using FITS-IRAF. */
+      if( strncmp( lontype, "RA--", 4 ) &&
+          strncmp( lontype, "GLON", 4 ) &&
+          strncmp( lontype, "ELON", 4 ) ) ok = 0;
+
+/* If the physical Frame requires a LONPOLE or LATPOLE keyword, it cannot
+   be encoded using FITS-IRAF. */
+      if( GetItem( &(store->latpole), 0, 0, s, NULL, method, class, status )
+          != AST__BAD ||
+          GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status )
+          != AST__BAD ) ok = 0;
+
+/* If there are no celestial axes, the physical Frame can be written out
+   using FITS-IRAF. */
+   } else {
+      ok = 1;
+   }
+
+/* Save the number of axes */
+   naxis = GetMaxJM( &(store->crpix), ' ', status ) + 1;
+
+/* If this is different to the value of NAXIS abort since this encoding
+   does not support WCSAXES keyword. */
+   if( naxis != store->naxis ) ok = 0;
+
+/* Return if the FitsStore does not conform to IRAF encoding. */
+   if( !ok ) return ret;
+
+/* Get and save CRPIX for all pixel axes. These are required, so return
+   if they are not available. */
+   for( i = 0; i < naxis; i++ ){
+      val = GetItem( &(store->crpix), 0, i, s, NULL, method, class, status );
+      if( val == AST__BAD ) return ret;
+      sprintf( combuf, "Reference pixel on axis %d", i + 1 );
+      SetValue( this, FormatKey( "CRPIX", i + 1, -1, s, status ), &val, AST__FLOAT,
+                combuf, status );
+   }
+
+/* Get and save CRVAL for all intermediate axes. These are required, so return
+   if they are not available. */
+   for( j = 0; j < naxis; j++ ){
+      val = GetItem( &(store->crval), j, 0, s, NULL, method, class, status );
+      if( val == AST__BAD ) return ret;
+      sprintf( combuf, "Value at ref. pixel on axis %d", j + 1 );
+      SetValue( this, FormatKey( "CRVAL", j + 1, -1, s, status ), &val, AST__FLOAT,
+                combuf, status );
+   }
+
+/* Get and save CTYPE for all intermediate axes. These are required, so return
+   if they are not available. Use the potentially modified versions saved
+   above for the celestial axes. */
+   for( i = 0; i < naxis; i++ ){
+      if( i == axlat ) {
+         cval = lattype;
+      } else if( i == axlon ) {
+         cval = lontype;
+      } else {
+         cval = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+         if( !cval ) return ret;
+      }
+      if( strlen(cval) > 4 && !strcmp( cval + 4, "-TAB" ) ) return ret;
+      comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
+      if( !comm ) {
+         sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
+         comm = combuf;
+      }
+      SetValue( this, FormatKey( "CTYPE", i + 1, -1, s, status ), &cval, AST__STRING,
+                comm, status );
+   }
+
+/* CD matrix (the product of the CDELT and PC matrices). */
+   for( i = 0; i < naxis; i++ ){
+      cdelt = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
+      if( cdelt == AST__BAD ) cdelt = 1.0;
+      for( j = 0; j < naxis; j++ ){
+         val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
+         if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
+         val *= cdelt;
+         if( val != 0.0 ) {
+             SetValue( this, FormatKey( "CD", i + 1, j + 1, s, status ), &val,
+                       AST__FLOAT, "Transformation matrix element", status );
+         }
+      }
+   }
+
+/* Get and save CUNIT for all intermediate axes. These are NOT required, so
+   do not return if they are not available. */
+   for( i = 0; i < naxis; i++ ){
+      cval = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
+      if( cval ) {
+         sprintf( combuf, "Units for axis %d", i + 1 );
+         SetValue( this, FormatKey( "CUNIT", i + 1, -1, s, status ), &cval, AST__STRING,
+                   combuf, status );
+      }
+   }
+
+/* Get and save RADECSYS. This is NOT required, so do not return if it is
+   not available. */
+   cval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
+   if( cval ) SetValue( this, "RADECSYS", &cval, AST__STRING,
+                        "Reference frame for RA/DEC values", status );
+
+/* Reference equinox */
+   val = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );
+   if( val != AST__BAD ) SetValue( this, "EQUINOX", &val, AST__FLOAT,
+                                   "Epoch of reference equinox", status );
+
+/* Date of observation */
+   val = GetItem( &(store->mjdobs), 0, 0, ' ', NULL, method, class, status );
+   if( val != AST__BAD ) {
+
+/* The format used for the DATE-OBS keyword depends on the value of the
+   keyword. For DATE-OBS < 1999.0, use the old "dd/mm/yy" format.
+   Otherwise, use the new "ccyy-mm-ddThh:mm:ss[.ssss]" format. */
+      palCaldj( 99, 1, 1, &mjd99, &jj );
+      if( val < mjd99 ) {
+         palDjcal( 0, val, iymdf, &jj );
+         sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
+                  iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
+      } else {
+         palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
+         palDd2tf( 3, fd, sign, ihmsf );
+         sprintf( combuf, "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d.%3.3d",
+                  iymdf[0], iymdf[1], iymdf[2], ihmsf[0], ihmsf[1],
+                  ihmsf[2], ihmsf[3] );
+      }
+
+/* Now store the formatted string in the FitsChan. */
+      cval = combuf;
+      SetValue( this, "DATE-OBS", (void *) &cval, AST__STRING,
+                "Date of observation", status );
+   }
+
+/* If we get here we have succeeded. */
+   ret = 1;
+
+/* Return zero or ret depending on whether an error has occurred. */
+   return astOK ? ret : 0;
+}
+
+static int IsMapLinear( AstMapping *smap, const double lbnd_in[],
+                        const double ubnd_in[], int coord_out, int *status ) {
+/*
+*  Name:
+*     IsMapLinear
+
+*  Purpose:
+*     See if a specified Mapping output is linearly related to the
+*     Mapping inputs.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int IsMapLinear( AstMapping *smap, const double lbnd_in[],
+*                      const double ubnd_in[], int coord_out, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns a flag indicating if the specified output of the supplied
+*     Mapping is a linear function of the Mapping inputs. A set of output
+*     positions are created which are evenly spaced along the specified
+*     output coordinate. The spacing is chosen so that the entire range
+*     of the output coordinate is covered in 20 steps. The other output
+*     coordinates are held fixed at arbitrary values (actually, values
+*     at which the specified output coordinate achieves its minimum value).
+*     This set of output positions is transformed into the corresponding
+*     set of input coordinates using the inverse of the supplied Mapping.
+*     A least squares linear fit is then made which models each input
+*     coordinate as a linear function of the specified output coordinate.
+*     The residual at every point in this fit must be less than some
+*     small fraction of the total range of the corresponding input
+*     coordinate for the Mapping to be considered linear.
+
+*  Parameters:
+*     smap
+*        Pointer to the Mapping.
+*     lbnd_in
+*        Pointer to an array of double, with one element for each
+*        Mapping input coordinate. This should contain the lower bound
+*        of the input box in each input dimension.
+*     ubnd_in
+*        Pointer to an array of double, with one element for each
+*        Mapping input coordinate. This should contain the upper bound
+*        of the input box in each input dimension.
+*     coord_out
+*        The zero-based index of the Mapping output which is to be checked.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the specified Mapping output is linear. Zero otherwise.
+*/
+
+/* Local Constants: */
+#define NP 20
+
+/* Local Variables: */
+   AstMapping *map;
+   AstPointSet *pset1;
+   AstPointSet *pset2;
+   double **ptr1;
+   double **ptr2;
+   double *p;
+   double *s;
+   double *xl;
+   double c;
+   double d;
+   double delta;
+   double in_lbnd;
+   double in_ubnd;
+   double lbnd_out;
+   double m;
+   double p0;
+   double pv;
+   double sn;
+   double sp;
+   double sps;
+   double ss2;
+   double ss;
+   double sv;
+   double tol;
+   double ubnd_out;
+   int *ins;
+   int boxok;
+   int i;
+   int j;
+   int nin;
+   int nout;
+   int oldrep;
+   int ret;
+
+/* Initialise */
+   ret = 0;
+
+/* Check inherited status */
+   if( !astOK ) return ret;
+
+/* Attempt to split off the required output (in case any of the other
+   outputs are associated with Mappings that do not have an inverse). */
+   astInvert( smap );
+   ins = astMapSplit( smap, 1, &coord_out, &map );
+   astInvert( smap );
+
+/* If successful, check that the output is fed by only one input. */
+   if( ins ) {
+      if( astGetNin( map ) == 1 ) {
+
+/* If so, invert the map so that it goes from pixel to wcs, and then
+   modify the supplied arguments so that they refer to the single required
+   axis. */
+         astInvert( map );
+         lbnd_in += coord_out;
+         ubnd_in += coord_out;
+         coord_out = 0;
+
+/* If the output was fed by more than one input, annul the split mapping
+   and use the supplied nmapping. */
+      } else {
+         (void) astAnnul( map );
+         map = astClone( smap );
+      }
+      ins = astFree( ins );
+
+/* If the supplied Mapping could not be split, use the supplied nmapping. */
+   } else {
+      map = astClone( smap );
+   }
+
+/* Check the Mapping is defined in both directions. */
+   if( astGetTranForward( map ) && astGetTranInverse( map ) ) {
+
+/* Allocate resources. */
+      nin = astGetNin( map );
+      nout = astGetNout( map );
+      xl = astMalloc( sizeof( double )*(size_t) nin );
+      pset1 = astPointSet( NP, nin, "", status );
+      ptr1 = astGetPoints( pset1 );
+      pset2 = astPointSet( NP, nout, "", status );
+      ptr2 = astGetPoints( pset2 );
+
+/* Call astMapBox in a new error reporting context. */
+      boxok = 0;
+      if( astOK ) {
+
+/* Temporarily switch off error reporting so that no report is made if
+   astMapBox cannot find a bounding box (which can legitimately happen with
+   some non-linear Mappings). */
+         oldrep = astReporting( 0 );
+
+/* Find the upper and lower bounds on the specified Mapping output. This also
+   returns the input coords of a point at which the required output has its
+   lowest value. */
+         astMapBox( map, lbnd_in, ubnd_in, 1, coord_out, &lbnd_out, &ubnd_out,
+                    xl, NULL );
+
+/* If the box could not be found, clear the error status and pass on. */
+         if( !astOK ) {
+            astClearStatus;
+
+/* If the box was found OK, flag this and check if the bounds are equal.
+   If so we cannot use them. In this case create new bounds. */
+         } else {
+            boxok = 1;
+            if( astEQUAL( lbnd_out, ubnd_out ) ) {
+               m = 0.5*( lbnd_out + ubnd_out );
+               if( fabs( m ) > 1.0E-15 ) {
+                  lbnd_out = 0.9*m;
+                  ubnd_out = 1.1*m;
+               } else {
+                  lbnd_out = -1.0;
+                  ubnd_out = 1.0;
+               }
+            }
+         }
+
+/* Re-instate error reporting. */
+         astReporting( oldrep );
+      }
+
+/* Check pointers can be used safely and a box was obtained. */
+      if( astOK && boxok ) {
+
+/* Transform the input position returned by astMapBox using the supplied
+   Mapping to get the corresponding output position. Fill all unused
+   elements of the PointSet with AST__BAD. */
+         for( i = 0; i < nin; i++ ){
+            p = ptr1[ i ];
+            *(p++) = xl[ i ];
+            for( j = 1; j < NP; j++ ) *(p++) = AST__BAD;
+         }
+         (void) astTransform( map, pset1, 1, pset2 );
+
+/* Now create a set of NP points evenly spaced in output coordinates. The
+   first point is at the output position found above. Each subsequent
+   point is incremented by a fixed amount along the specified output
+   coordinate (the values on all other output coordinates is held fixed). */
+         delta = ( ubnd_out - lbnd_out )/ ( NP - 1 );
+         for( i = 0; i < nout; i++ ){
+            p = ptr2[ i ];
+            if( i == coord_out ) {
+               for( j = 0; j < NP; j++ ) *(p++) = lbnd_out + j*delta;
+            } else {
+               p0 = p[ 0 ];
+               for( j = 0; j < NP; j++ ) *(p++) = p0;
+            }
+         }
+
+/* Transform these output positions into input positions using the
+   inverse Mapping. */
+         (void) astTransform( map, pset2, 0, pset1 );
+
+/* Do a least squares fit to each input coordinate. Each fit gives the
+   corresponding input coordinate value as a linear function of the
+   specified output coordinate value. Note, linear function should never
+   produce bad values so abort if a bad value is found. */
+         ret = 1;
+         s = ptr2[ coord_out ];
+         for( i = 0; i < nin; i++ ) {
+            p = ptr1[ i ];
+
+/* Form the required sums. Also find the largest and smallest input
+   coordinate value achieved. */
+            sp = 0.0;
+            ss = 0.0;
+            sps = 0.0;
+            sn = 0.0;
+            ss2 = 0.0;
+            in_lbnd = DBL_MAX;
+            in_ubnd = DBL_MIN;
+            for( j = 0; j < NP; j++ ) {
+               sv = s[ j ];
+               pv = p[ j ];
+               if( pv != AST__BAD && sv != AST__BAD ) {
+                  sp += pv;
+                  ss += sv;
+                  sps += pv*sv;
+                  sn += 1.0;
+                  ss2 += sv*sv;
+                  if( pv < in_lbnd ) in_lbnd = pv;
+                  if( pv > in_ubnd ) in_ubnd = pv;
+               } else {
+                  sn = 0.0;
+                  break;
+               }
+            }
+
+/* Ignore input axes which are independant of the output axis. */
+            if( !astEQUAL( in_lbnd, in_ubnd ) ) {
+
+/* Calculate the constants "input coord = m*output coord + c" */
+               d = ss*ss - sn*ss2;
+               if( sn > 0.0 && d != 0.0 ) {
+                  m = ( sp*ss - sps*sn )/d;
+                  c = ( sps*ss - sp*ss2 )/d;
+
+/* Subtract off the fit value form the "p" values to get the residuals of
+   the fit. */
+                  for( j = 0; j < NP; j++ ) p[ j ] -= m*s[ j ] + c;
+
+/* We now do a least squares fit to the residuals. This second fit is done
+   because the first least squares fit sometimes leaves the residuals with a
+   distinct non-zero gradient. We do not need to worry about bad values
+   here since we have checked above that there are no bad values. Also we
+   do not need to recalculate sums which only depend on the "s" values since
+   they have not changed. */
+                  sp = 0.0;
+                  sps = 0.0;
+                  for( j = 0; j < NP; j++ ) {
+                     pv = p[ j ];
+                     sp += pv;
+                     sps += pv*s[ j ];
+                  }
+
+/* Find the constants in "input residual = m*output coord + c" equation. */
+                  m = ( sp*ss - sps*sn )/d;
+                  c = ( sps*ss - sp*ss2 )/d;
+
+/* Subtract off the fit value form the "p residuals" values to get the
+   residual redisuals of the fit. */
+                  for( j = 0; j < NP; j++ ) p[ j ] -= m*s[ j ] + c;
+
+/* The requirement for a linear relationship is that the absolute residual
+   between the input coord produced by the above linear fit and the input
+   coord produced by the actual Mapping should be less than some small
+   fraction of the total range of input coord value, at every point. Test
+   this. */
+                  tol = 1.0E-7*( in_ubnd - in_lbnd );
+                  for( j = 0; j < NP; j++ ) {
+                     if( fabs( p[ j ] ) > tol ) {
+                        ret = 0;
+                        break;
+                     }
+                  }
+               } else {
+                  ret = 0;
+               }
+            }
+            if( !ret ) break;
+         }
+      }
+
+/* Free resources. */
+      pset1 = astAnnul( pset1 );
+      pset2 = astAnnul( pset2 );
+      xl = astFree( xl );
+   }
+   map = astAnnul( map );
+
+/* Return the answer. */
+   return ret;
+}
+
+static AstMapping *IsMapTab1D( AstMapping *map, double scale, const char *unit,
+                               AstFrame *wcsfrm, double *dim, int iax,
+                               int iwcs, AstFitsTable **table, int *icolmain,
+                               int *icolindex, int *interp, int *status ){
+/*
+*  Name:
+*     IsMapTab1D
+
+*  Purpose:
+*     See if a specified Mapping output is related to a single Mapping input
+*     via a FITS -TAB algorithm.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstMapping *IsMapTab1D( AstMapping *map, double scale, const char *unit,
+*                             AstFrame *wcsfrm, double *dim, int iax,
+*                             int iwcs, AstFitsTable **table, int *icolmain,
+*                             int *icolindex, int *interp, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A specified axis of the supplied Mapping is tested to see if it
+*     can be represented by the -TAB alogirithm described in FITS-WCS
+*     paper III. If the test is passed, a Mapping is returned from the
+*     specified WCS axis to the corresponding psi axis. A FitsTable is
+*     also created holding the information to be stored in the
+*     corresponding FITS binary table.
+*
+*     Note, when creating a -TAB header, AST uses grid coords for the psi
+*     axis. See FITS-WCS paper III section 6.1.2 for a definition of the
+*     psi axes.
+
+*  Parameters:
+*     map
+*        Pointer to the Mapping from pixel coords to WCS coords.
+*     scale
+*        A scale factor by which to multiply the axis values stored in the
+*        returned FitsTable. Note, the returned Mapping is unaffected by
+*        this scaling factor.
+*     unit
+*        Pointer to the unit string to store with the coords column. If
+*        NULL, the unit string is extracted form the supplied WCS Frame.
+*     wcsfrm
+*        Pointer to a Frame describing WCS coords.
+*     dim
+*        An array holding the array dimensions in pixels. AST__BAD should
+*        be supplied for any unknown dimensions.
+*     iax
+*        The zero-based index of the Mapping output which is to be checked.
+*     iwcs
+*        The zero-based index of the corresponding FITS WCS axis.
+*     table
+*        Pointer to a location holding a pointer to the FitsTable describing
+*        the -TAB look-up table. If "*table" is NULL on entry, a new
+*        FitsTable will be created and returned, otherwise the supplied
+*        FitsTable is used.
+*     icolmain
+*        The one-based index of the column within "*table" that holds the
+*        main data array.
+*     icolindex
+*        The one-based index of the column within "*table" that holds the
+*        index vector. Returned equal to -1 if no index is added to the
+*        table (i.e. if the index is a unt index).
+*     interp
+*        The interpolation method (0=linear, other=nearest neighbour).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If the specified "map" output can be described using the -TAB
+*     algorithm of FITS-WCS paper III, then a 1-input/1-output Mapping
+*     from the specified WCS axis to the corresponding psi axis (which is
+*     assumed to be equal to grid coords) is returned. NULL is returned
+*     otherwise, of if an error occurs.
+*/
+
+/* Local Variables: */
+   AstCmpMap *cm;          /* CmpMap pointer */
+   AstMapping **map_list;  /* Mapping array pointer */
+   AstMapping *postmap;    /* Total Mapping after LutMap */
+   AstMapping *premap;     /* Total Mapping before LutMap */
+   AstMapping *ret;        /* Returned WCS axis Mapping */
+   AstMapping *tmap;       /* Temporary Mapping */
+   AstPermMap *pm;         /* PermMap pointer */
+   char cellname[ 20 ];    /* Buffer for cell name */
+   char colname[ 20 ];     /* Buffer for column name */
+   double *lut;            /* Pointer to table of Y values */
+   double *work1;          /* Pointer to work array */
+   double *work2;          /* Pointer to work array */
+   double inc;             /* X increment between table entries */
+   double start;           /* X value at first table entry */
+   double v[ 2 ];          /* Y values at start and end of interval */
+   double x[ 2 ];          /* X values at start and end of interval */
+   int *ins;               /* Array of "map" input indices */
+   int *invert_list;       /* Invert array pointer */
+   int *outs;              /* Array of "map" output indices */
+   int dims[ 2 ];          /* Dimensions of the tab coords array */
+   int iin;                /* Index of Mapping input */
+   int ilut;               /* Index of the LutMap within the mappings list */
+   int imap;               /* Index of current Mapping in list */
+   int iout;               /* Index of Mapping output */
+   int jout;               /* Index of Mapping output */
+   int nin;                /* Number of Mapping inputs */
+   int nlut;               /* Number of elements in "lut" array */
+   int nmap;               /* Number of Mappings in the list */
+   int nout;               /* Number of Mapping outputs */
+   int ok;                 /* Were columns added to the table? */
+   int old_invert;         /* Original value for Mapping's Invert flag */
+   int outperm;            /* Index of input that feeds the single output */
+
+/* Initialise */
+   ret = NULL;
+   *icolmain = -1;
+   *icolindex = -1;
+   *interp = 0;
+
+/* Check inherited status */
+   if( !astOK ) return ret;
+
+/* Ensure we have aunit string. */
+   if( !unit ) unit = astGetUnit( wcsfrm, iax );
+
+/* Check that the requested mapping output is fed by only one mapping
+   input, identify that input, and extract the input->output mapping from
+   the total mapping. Since astMapSplit splits off a specified input, we
+   need to invert the Mapping first so we can split off a specified output.  */
+   astInvert( map );
+   ins = astMapSplit( map, 1, &iax, &ret );
+   astInvert( map );
+
+/* If the Mapping could not be split, try a different approach in which
+   each input is checked in turn to see if it feeds the specified output. */
+   if( !ins ) {
+
+/* Loop round each input of "map". */
+      nin = astGetNin( map );
+      for( iin = 0; iin < nin && !ins; iin++ ) {
+
+/* Attempt to find a group of outputs (of "map") that are fed by just
+   this one input. */
+         outs = astMapSplit( map, 1, &iin, &ret );
+
+/* If successful, "ret" will be a Mapping with one input corresponding to
+   input "iin" of "map, and one or more outputs. We loop round these
+   outputs to see if any of them correspond to output "iax" of "map". */
+         if( outs ) {
+            nout = astGetNout( ret );
+            for( iout = 0; iout < nout; iout++ ) {
+               if( outs[ iout ] == iax ) break;
+            }
+
+/* Did input "iin" feed the output "iax" (and possibly other outputs)? */
+            if( iout < nout ) {
+
+/* The "ret" Mapping is now a 1-input (pixel) N-output (WCS) Mapping in which
+   output "iout" corresponds to output "iax" of Mapping. To be compatible
+   with the previous approach, we want "ret" to be a 1-input  (WCS) to
+   1-output (pixel) Mapping in which the input corresponds to output
+   "iax" of Mapping. To get "ret" into this form, we first append a PermMap
+   to "ret" that selects a single output ("iout"), and then invert the
+   whole CmpMap. */
+               for( jout = 0; jout < nout; jout++ ) {
+                  outs[ jout ] = -1;
+               }
+               outs[ iout ] = 0;
+               outperm = iout;
+
+               pm = astPermMap( nout, outs, 1, &outperm, NULL, "", status );
+               cm = astCmpMap( ret, pm, 1, " ", status );
+               (void) astAnnul( ret );
+               pm = astAnnul( pm );
+               ret = (AstMapping *) cm;
+               astInvert( ret );
+
+/* The earlier approach leves ins[ 0 ] holding the index of the input to
+   "map" that feeds output iax. Ensure we have this too. */
+               ins = outs;
+               ins[ 0 ] = iin;
+
+/* Free resources if the current input did not feed the required output. */
+            } else {
+               outs = astFree( outs );
+               ret = astAnnul( ret );
+            }
+         }
+      }
+   }
+
+/* If the Mapping still could not be split, try again on a copy of the
+   Mapping in which all PermMaps provide an alternative implementation of
+   the astMapSplit method. */
+   if( !ins ) {
+      astInvert( map );
+      tmap = astCopy( map );
+      ChangePermSplit( tmap, status );
+      ins = astMapSplit( tmap, 1, &iax, &ret );
+      tmap = astAnnul( tmap );
+      astInvert( map );
+   }
+
+/* Assume the Mapping cannot be represented by -TAB */
+   ok = 0;
+
+/* Check a Mapping was returned by astMapSplit. If so, it will be the
+   mapping from the requested output of "map" (the WCS axis) to the
+   corresponding input(s) (grid axes). Check only one "map" input feeds the
+   requested output. */
+   if( ins && ret && astGetNout( ret ) == 1 ) {
+
+/* Invert the Mapping so that the input is grid coord and the output is
+   WCS coord. */
+      astInvert( ret );
+
+/* We now search the "ret" mapping for a non-inverted LutMap, splitting ret
+   up into three serial components: 1) the mappings before the LutMap, 2) the
+   LutMap itself, and 3) the mappings following the LutMap. First, decompose
+   the mapping into a list of series mappings. */
+      map_list = NULL;
+      invert_list = NULL;
+      nmap = 0;
+      astMapList( ret, 1, astGetInvert( ret ), &nmap, &map_list,
+                  &invert_list );
+
+/* Search the list for a non-inverted LutMap. */
+      ilut = -1;
+      for( imap = 0; imap < nmap; imap++ ) {
+         if( astIsALutMap( map_list[ imap ] ) && !(invert_list[ imap ]) ) {
+            ilut = imap;
+            break;
+         }
+      }
+
+/* If a LutMap was found, combine all Mappings before the LutMap into a
+   single Mapping. Remember to set the Mapping Invert flags temporarily to
+   the values used within the CmpMap. */
+      if( ilut >= 0 ) {
+         premap = (AstMapping *) astUnitMap( 1, " ", status );
+         for( imap = 0; imap < ilut; imap++ ) {
+            old_invert = astGetInvert( map_list[ imap ] );
+            astSetInvert( map_list[ imap ], invert_list[ imap ] );
+            tmap = (AstMapping *) astCmpMap( premap,  map_list[ imap ], 1,
+                                             " ", status );
+            astSetInvert( map_list[ imap ], old_invert );
+            (void) astAnnul( premap );
+            premap = tmap;
+         }
+
+/* Also combine all Mappings after the LutMap into a single Mapping, setting
+   the Mapping Invert flags temporarily to the values used within the
+   CmpMap. */
+         postmap = (AstMapping *) astUnitMap( 1, " ", status );
+         for( imap = ilut + 1; imap < nmap; imap++ ) {
+            old_invert = astGetInvert( map_list[ imap ] );
+            astSetInvert( map_list[ imap ], invert_list[ imap ] );
+            tmap = (AstMapping *) astCmpMap( postmap,  map_list[ imap ], 1,
+                                             " ", status );
+            astSetInvert( map_list[ imap ], old_invert );
+            (void) astAnnul( postmap );
+            postmap = tmap;
+         }
+
+/* Get the table of values, and other attributes, from the LutMap. */
+         lut = astGetLutMapInfo( map_list[ ilut ], &start, &inc, &nlut );
+         *interp = astGetLutInterp(  map_list[ ilut ] );
+
+/* If required, create a FitsTable to hold the returned table info. */
+         if( ! *table ) *table = astFitsTable( NULL, "", status );
+         ok = 1;
+
+/* Define the properties of the column in the FitsTable that holds the main
+   coordinate array. Points on a WCS axis are described by a single value
+   (wavelength, frequency, or whatever), but the coords array has to be
+   2-dimensional, with an initial degenerate axis, as required by FITS-WCS
+   paper III. */
+         dims[ 0 ] = 1;
+         dims[ 1 ] = nlut;
+         sprintf( colname, "COORDS%d", iwcs + 1 );
+         astAddColumn( *table, colname, AST__DOUBLETYPE, 2, dims, unit );
+
+/* Get the one-based index of the column just added to the table. */
+         *icolmain = astGetNcolumn( *table );
+
+/* Get workspace. */
+         work1 = astMalloc( nlut*sizeof( double ) );
+         if( astOK ) {
+
+/* Transform the LutMap table values using the post-lutmap mapping to
+   get the list of WCS values in AST units. */
+            astTran1( postmap, nlut, lut, 1, work1 );
+
+/* Convert them to FITS units (e.g. celestial axis values should be
+   converted from radians to degrees). */
+            for( ilut = 0; ilut < nlut; ilut++ ) work1[ ilut ] *= scale;
+
+/* Store them in row 1, column COORDS, in the FitsTable. */
+            sprintf( cellname, "COORDS%d(1)", iwcs + 1 );
+            astMapPut1D( *table, cellname, nlut, work1, NULL );
+
+/* Create an array holding the LutMap input value at the centre of each
+   table entry. Re-use the "lut" array since we no longer need it. */
+            for( ilut = 0; ilut < nlut; ilut++ ) {
+               lut[ ilut ] = start + ilut*inc;
+            }
+
+/* Transform this array using the inverted pre-lutmap mapping to get the
+   list of grid coord. */
+            astTran1( premap, nlut, lut, 0, work1 );
+
+/* Test this list to see if they form a unit index (i.e. index(i) == i+1 ).
+   (not the "+1" is due to the fact that "i" is zero based). */
+            for( ilut = 0; ilut < nlut; ilut++ ) {
+               if( fabs( work1[ ilut ] - ilut - 1.0 ) > 1.0E-6 ) break;
+            }
+
+/* if it is not a unit index, we add the index to the table. */
+            if( ilut < nlut ) {
+
+/* Define the properties of the column in the FitsTable that holds the
+   indexing vector. */
+               sprintf( colname, "INDEX%d", iwcs + 1 );
+               astAddColumn( *table, colname, AST__DOUBLETYPE, 1, &nlut, " " );
+
+/* Get the one-based index of the column just added to the table. */
+               *icolindex = astGetNcolumn( *table );
+
+/* Store the values in the column. */
+               sprintf( cellname, "INDEX%d(1)", iwcs + 1 );
+               astMapPut1D( *table, cellname, nlut, work1, NULL );
+            }
+         }
+
+/* Free resources. */
+         work1 = astFree( work1 );
+         lut = astFree( lut );
+         premap = astAnnul( premap );
+         postmap = astAnnul( postmap );
+
+/* If no LutMap was found in the Mapping, then we can create a FitsTable
+   by sampling the full WCS Mapping at selected input (i.e. grid)
+   positions. But we can only do this if we know the number of pixels
+   along the WCS axis. */
+      } else if( dim[ ins[ 0 ] ] != AST__BAD ) {
+
+/* Create two works array each holding a single value. The first holds
+   the grid coords at which the samples are taken. The second holds the
+   WCS coords at the sampled positions. These arrays are expanded as
+   required within function AdaptLut. */
+         work1 = astMalloc( sizeof( double ) );
+         work2 = astMalloc( sizeof( double ) );
+         if( astOK ) {
+
+/* Get the WCS values at the centres of the first and last pixel on
+   the WCS axis. */
+            x[ 0 ] = 1.0;
+            x[ 1 ] = dim[ ins[ 0 ] ];
+            astTran1( ret, 2, x, 1, v );
+
+/* Put the lower limit into the work arrays. */
+            work1[ 0 ] = x[ 0 ];
+            work2[ 0 ] = v[ 0 ];
+            nlut = 1;
+
+/* Expand the arrays by sampling the WCS axis adaptively so that
+   more samples occur where the WCS value is changing most rapidly.
+   We require the maximum error introduced by the table to be 0.25 pixels. */
+            AdaptLut( ret, 3, 0.25, x[ 0 ], x[ 1 ], v[ 0 ], v[ 1 ],
+                      &work1, &work2, &nlut, status );
+
+/* Create a FitsTable to hold the returned table info. */
+            if( ! *table ) *table = astFitsTable( NULL, "", status );
+            ok = 1;
+
+/* Define the properties of the column in the FitsTable that holds the main
+   coordinate array. */
+            sprintf( colname, "COORDS%d", iwcs + 1 );
+            dims[ 0 ] = 1;
+            dims[ 1 ] = nlut;
+            astAddColumn( *table, colname, AST__DOUBLETYPE, 2, dims, unit );
+            *icolmain = astGetNcolumn( *table );
+
+/* Convert the axis values to FITS units (e.g. celestial axis values should be
+   converted from radians to degrees). */
+            for( ilut = 0; ilut < nlut; ilut++ ) work2[ ilut ] *= scale;
+
+/* Store the scaled axis values in row 1 of the column. */
+            sprintf( cellname, "COORDS%d(1)", iwcs + 1 );
+            astMapPut1D( *table, cellname, nlut, work2, NULL );
+
+/* Test the index vector to see if they form a unit index (i.e. index(i) ==
+   i+1 ). If not the "+1" is due to the fact that "i" is zero based). If not, store
+   them as the index vector in the FitsTable. */
+            for( ilut = 0; ilut < nlut; ilut++ ) {
+               if( fabs( work1[ ilut ] - ilut - 1.0 ) > 1.0E-6 ) break;
+            }
+
+/* If the index vector is not a unit index, define the properties of the
+   column in the FitsTable that holds the indexing vector. Then store values
+   in row 1 of the column. */
+            if( ilut < nlut ) {
+               sprintf( colname, "INDEX%d", iwcs + 1 );
+               astAddColumn( *table, colname, AST__DOUBLETYPE, 1, &nlut, " " );
+               *icolindex = astGetNcolumn( *table );
+               sprintf( cellname, "INDEX%d(1)", iwcs + 1 );
+               astMapPut1D( *table, cellname, nlut, work1, NULL );
+            }
+         }
+
+/* Free resources */
+         work1 = astFree( work1 );
+         work2 = astFree( work2 );
+      }
+
+/* If columns were added to the table, invert the returned Mapping again
+   so that the input is wcs coord and the output is grid coord. Otherwise,
+   annul the returned Mapping. */
+      if( ok ) {
+         astInvert( ret );
+      } else {
+         ret = astAnnul( ret );
+      }
+
+/* Loop to annul all the Mapping pointers in the list. */
+      for ( imap = 0; imap < nmap; imap++ ) map_list[ imap ] = astAnnul( map_list[ imap ] );
+
+/* Free the dynamic arrays. */
+      map_list = astFree( map_list );
+      invert_list = astFree( invert_list );
+   }
+
+/* Free resources. */
+   ins = astFree( ins );
+
+/* If an error occurred, free the returned Mapping. */
+   if( !astOK ) ret = astAnnul( ret );
+
+/* Return the result. */
+   return ret;
+}
+
+static AstMapping *IsMapTab2D( AstMapping *map, double scale, const char *unit,
+                               AstFrame *wcsfrm, double *dim, int iax1,
+                               int iax2, int iwcs1, int iwcs2,
+                               AstFitsTable **table, int *icolmain1,
+                               int *icolmain2, int *icolindex1,
+                               int *icolindex2, int *max1, int *max2,
+                               int *interp1, int *interp2, int *status ){
+/*
+*  Name:
+*     IsMapTab2D
+
+*  Purpose:
+*     See if a specified pair of Mapping outputs are related to a pair of
+*     Mapping inputs via a FITS -TAB algorithm.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstMapping *IsMapTab2D( AstMapping *map, double scale, const char *unit,
+*                             AstFrame *wcsfrm, double *dim, int iax1,
+*                             int iax2, int iwcs1, int iwcs2,
+*                             AstFitsTable **table, int *icolmain1,
+*                             int *icolmain2, int *icolindex1,
+*                             int *icolindex2, int *max1, int *max2,
+*                             int *interp1, int *interp2, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A specified pair of outputs axes of the supplied Mapping are tested
+*     to see if they can be represented by the -TAB alogirithm described in
+*     FITS-WCS paper III. If the test is passed, a Mapping is returned from
+*     the specified WCS axes to the corresponding psi axes. A FitsTable is
+*     also created holding the information to be stored in the corresponding
+*     FITS binary table. Note, when creating a header, AST assumes a unit
+*     transformaton between psi axes and grid axes (psi axes are defined
+*     in FITS-WCS paper III section 6.1.2).
+
+*  Parameters:
+*     map
+*        Pointer to the Mapping from pixel coords to WCS coords.
+*     scale
+*        A scale factor by which to multiply the axis values stored in the
+*        returned FitsTable. Note, the returned Mapping is unaffected by
+*        this scaling factor.
+*     unit
+*        A unit string for the axis values. If supplied, the same
+*        string is stored for both axes. If NULL, the unit strings are
+*        extracted from the relavent axes of the supplied WCS Frame.
+*     wcsfrm
+*        Pointer to a Frame describing WCS coords.
+*     dim
+*        An array holding the array dimensions in pixels. AST__BAD should
+*        be supplied for any unknown dimensions.
+*     iax1
+*        The zero-based index of the first Mapping output which is to be
+*        checked.
+*     iax2
+*        The zero-based index of the second Mapping output which is to be
+*        checked.
+*     iwcs1
+*        The zero-based index of the FITS WCS axis corresponding to "iax1".
+*     iwcs2
+*        The zero-based index of the FITS WCS axis corresponding to "iax2".
+*     table
+*        Pointer to a location holding a pointer to the FitsTable describing
+*        the -TAB look-up table. If "*table" is NULL on entry, a new
+*        FitsTable will be created and returned, otherwise the supplied
+*        FitsTable is used.
+*     icolmain1
+*        The one-based index of the column within "*table" that holds the
+*        main coord array for the first Mapping output.
+*     icolmain2
+*        The one-based index of the column within "*table" that holds the
+*        main coord array for the second Mapping output.
+*     icolindex1
+*        The one-based index of the column within "*table" that holds the
+*        index vector for the first Mapping output. Returned equal to -1
+*        if no index is added to the table (e.g. because the index is a
+*        unit index).
+*     icolindex2
+*        The one-based index of the column within "*table" that holds the
+*        index vector for the second Mapping output. Returned equal to -1
+*        if no index is added to the table (e.g. because the index is a
+*        unit index).
+*     max1
+*        The one-based index of the dimension describing the first Mapping
+*        output within the main coord array specified by "icolmain1".
+*     max2
+*        The one-based index of the dimension describing the second Mapping
+*        output within the main coord array specified by "icolmain1".
+*     interp1
+*        The interpolation method (0=linear, other=nearest neighbour) for
+*        the first mapping output.
+*     interp2
+*        The interpolation method (0=linear, other=nearest neighbour) for
+*        the second mapping output.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If the specified "map" outputs can be described using the -TAB
+*     algorithm of FITS-WCS paper III, then a 2-input/2-output Mapping
+*     from the specified WCS axes to the corresponding psi axes (i.e.
+*     grid axes) is returned. NULL is returned otherwise, of if an error
+*     occurs.
+*/
+
+/* Local Variables: */
+   AstMapping *ret1;       /* WCS->IWC Mapping for first output */
+   AstMapping *ret2;       /* WCS->IWC Mapping for second output */
+   AstMapping *ret;        /* Returned WCS axis Mapping */
+   AstMapping *tmap;
+   AstPermMap *pm;
+   int *pix_axes;          /* Zero-based indices of corresponding pixel axes */
+   int wcs_axes[ 2 ];      /* Zero-based indices of selected WCS axes */
+   int inperm[ 1 ];
+   int outperm[ 2 ];
+
+/* Initialise */
+   ret = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return ret;
+
+/* First see if the two required Mapping outputs are separable, in which case
+   they can be described by two 1D tables. */
+   ret1 = IsMapTab1D( map, scale, unit, wcsfrm, dim, iax1, iwcs1, table, icolmain1,
+                      icolindex1, interp1, status );
+   ret2 = IsMapTab1D( map, scale, unit, wcsfrm, dim, iax2, iwcs2, table, icolmain2,
+                      icolindex2, interp2, status );
+
+/* If both outputs are seperable... */
+   if( ret1 && ret2 ) {
+
+/* Both axes are stored as the first dimension in the corresponding main
+   coords array. */
+      *max1 = 1;
+      *max2 = 1;
+
+/* Get a Mapping from the required pair of WCS axes to the corresponding
+   pair of grid axes. First try to split the supplied grid->wcs mapping. */
+      wcs_axes[ 0 ] = iax1;
+      wcs_axes[ 1 ] = iax2;
+
+      astInvert( map );
+      pix_axes = astMapSplit( map, 2, wcs_axes, &ret );
+      astInvert( map );
+
+      if( pix_axes ) {
+         pix_axes = astFree( pix_axes );
+         if( astGetNout( ret ) > 2 ) {
+            ret = astAnnul( ret );
+
+/* If the two output WCS axes are fed by the same grid axis, we need to
+   add another pixel axis to form the pair. */
+         } else if( astGetNout( ret ) == 1 ) {
+            inperm[ 0 ] = 0;
+            outperm[ 0 ] = 0;
+            outperm[ 1 ] = 0;
+            pm = astPermMap( 1, inperm, 2, outperm, NULL, " ", status );
+            tmap = (AstMapping *) astCmpMap( ret, pm, 1, " ", status );
+            ret = astAnnul( ret );
+            pm = astAnnul( pm );
+            ret = tmap;
+         }
+      }
+
+/* If this was unsuccessful, combine the Mappings returned by IsMapTab1D.
+   We only do this if the above astMapSplit call failed, since the IsMapTab1D
+   mappings may well not be independent of each other, and we may end up
+   sticking together in parallel two mappings that are basically the same
+   except for ending with PermMapa that select different axes. Is is hard
+   then to simplify such a parallel CmpMap back into the simpler form
+   that uses only one of the two identical mappings, without a PermMap. */
+      if( !ret ) {
+         ret = (AstMapping *) astCmpMap( ret1, ret2, 0, " ", status );
+      }
+
+/* Free resources. */
+      ret1 = astAnnul( ret1 );
+      ret2 = astAnnul( ret2 );
+
+/* If only one output is separable, remove the corresponding columns from
+   the returned table. */
+   } else if( ret1 ) {
+      ret1 = astAnnul( ret1 );
+      astRemoveColumn( *table, astColumnName( *table, *icolmain1 ) );
+      if( icolindex1 >= 0 ) astRemoveColumn( *table, astColumnName( *table, *icolindex1 ) );
+   } else if( ret2 ) {
+      ret2 = astAnnul( ret2 );
+      astRemoveColumn( *table, astColumnName( *table, *icolmain2 ) );
+      if( icolindex1 >= 0 ) astRemoveColumn( *table, astColumnName( *table, *icolindex2 ) );
+   }
+
+/* If the required Mapping outputs were not separable, create a single
+   2D coords array describing both outputs. */
+   if( !ret ) {
+
+/* TO BE DONE... Until then non-separable Mappings will result in a
+   failure to create a -TAB header. No point in doing this until AST has
+   an N-dimensional LutMap class (otherwise AST could never read the
+   resulting FITS header). */
+   }
+
+/* If an error occurred, free the returned Mapping. */
+   if( !astOK ) ret = astAnnul( ret );
+
+/* Return the result. */
+   return ret;
+}
+
+static int IsAIPSSpectral( const char *ctype, char **wctype, char **wspecsys, int *status ){
+/*
+*  Name:
+*     IsAIPSSpectral
+
+*  Purpose:
+*     See if a given CTYPE value describes a FITS-AIPS spectral axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int IsAIPSSpectral( const char *ctype, char **wctype, char **wspecsys, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The given CTYPE value is checked to see if it conforms to the
+*     requirements of a spectral axis CTYPE value as specified by
+*     FITS-AIPS encoding. If so, the equivalent FITS-WCS CTYPE and
+*     SPECSYS values are returned.
+
+*  Parameters:
+*     ctype
+*        Pointer to a null terminated string holding the CTYPE value to
+*        check.
+*     wctype
+*        The address of a location at which to return a pointer to a
+*        static string holding the corresponding FITS-WCS CTYPE value. A
+*        NULL pointer is returned if the supplied CTYPE string is not an
+*        AIPS spectral CTYPE value.
+*     wspecsys
+*        The address of a location at which to return a pointer to a
+*        static string holding the corresponding FITS-WCS SPECSYS value. A
+*        NULL pointer is returned if the supplied CTYPE string is not an
+*        AIPS spectral CTYPE value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Retuned Value:
+*     Non-zero fi the supplied CTYPE was an AIPS spectral CTYPE value.
+
+*  Note:
+*     - These translations are also used by the FITS-CLASS encoding.
+*/
+
+/* Local Variables: */
+   int ret;
+
+/* Initialise */
+   ret = 0;
+   *wctype = NULL;
+   *wspecsys = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* If the used length of the string is not 8, then it is not an AIPS spectral axis. */
+   if( astChrLen( ctype ) == 8 ) {
+
+/* Translate AIPS spectral CTYPE values to FITS-WCS paper III equivalents.
+   These are of the form AAAA-BBB, where "AAAA" can be "FREQ", "VELO" (=VRAD!)
+   or "FELO" (=VOPT-F2W), and BBB can be "LSR", "LSD", "HEL" (=*Bary*centric!)
+   or "GEO". */
+      if( !strncmp( ctype, "FREQ", 4 ) ){
+         *wctype = "FREQ    ";
+      } else if( !strncmp( ctype, "VELO", 4 ) ){
+         *wctype = "VRAD    ";
+      } else if( !strncmp( ctype, "FELO", 4 ) ){
+         *wctype = "VOPT-F2W";
+      } else if( !strncmp( ctype, "WAVELENG", 8 ) ){
+         *wctype = "WAVE    ";
+      }
+      if( !strncmp( ctype + 4, "-LSR", 4 ) ){
+         *wspecsys = "LSRK";
+      } else if( !strncmp( ctype + 4, "LSRK", 4 ) ){
+         *wspecsys = "LSRK";
+      } else if( !strncmp( ctype + 4, "-LSD", 4 ) ){
+         *wspecsys = "LSRD";
+      } else if( !strncmp( ctype + 4, "-HEL", 4 ) ){
+         *wspecsys = "BARYCENT";
+      } else if( !strncmp( ctype + 4, "-EAR", 4 ) || !strncmp( ctype + 4, "-GEO", 4 ) ){
+         *wspecsys = "GEOCENTR";
+      } else if( !strncmp( ctype + 4, "-OBS", 4 ) || !strncmp( ctype + 4, "-TOP", 4 ) ){
+         *wspecsys = "TOPOCENT";
+      }
+      if( *wctype && *wspecsys ) {
+         ret = 1;
+      } else {
+         *wctype = NULL;
+         *wspecsys = NULL;
+      }
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static int IsSkyOff( AstFrameSet *fset, int iframe, int *status ){
+/*
+*  Name:
+*     IsSkyOff
+
+*  Purpose:
+*     See if a given Frame contains an offset SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int IsSkyOff( AstFrameSet *fset, int iframe, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns a flag indicating if the specified Frame within the
+*     supplied FrameSet is, or contains, a SkyFrame that represents
+*     offset coordinates. This is the case if the Frame is a SkyFrame
+*     and its SkyRefIs attribute is "Pole" or "Origin", or is a CmpFrame
+*     containing such a SkyFrame.
+
+*  Parameters:
+*     fset
+*        The FrameSet.
+*     iframe
+*        Index of the Frame to check within "fset"
+*     status
+*        Pointer to the inherited status variable.
+
+*  Retuned Value:
+*     +1 if the Frame is an offset SkyFrame. Zero otherwise.
+
+*  Notes:
+*     - Zero is returned if an error has already occurred.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   const char *skyrefis;
+   int oldrep;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a pointer to the required Frame in the FrameSet */
+   frm = astGetFrame( fset, iframe );
+
+/* Since the current Frame may not contain a SkyFrame, we temporarily
+   switch off error reporting. */
+   oldrep = astReporting( 0 );
+
+/* Get the SkyRefIs attribute value. */
+   skyrefis = astGetC( frm, "SkyRefIs" );
+
+/* If it is "Pole" or "Origin", return 1. */
+   if( skyrefis && ( !Ustrcmp( skyrefis, "POLE", status ) ||
+                     !Ustrcmp( skyrefis, "ORIGIN", status ) ) ) result = 1;
+
+/* Cancel any error and switch error reporting back on again. */
+   astClearStatus;
+   astReporting( oldrep );
+
+/* Annul the Frame pointer. */
+   frm = astAnnul( frm );
+
+/* Return the result. */
+   return result;
+}
+
+static const char *IsSpectral( const char *ctype, char stype[5], char algcode[5], int *status ) {
+/*
+*  Name:
+*     IsSpectral
+
+*  Purpose:
+*     See if a given FITS-WCS CTYPE value describes a spectral axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     char *IsSpectral( const char *ctype, char stype[5], char algcode[5], int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The given CTYPE value is checked to see if it conforms to the
+*     requirements of a spectral axis CTYPE value as specified by
+*     FITS-WCS paper 3. If so, the spectral system and algorithm codes
+*     are extracted from it and returned, together with the default units
+*     for the spectral system.
+
+*  Parameters:
+*     ctype
+*        Pointer to a null terminated string holding the CTYPE value to
+*        check.
+*     stype
+*        An array in which to return the null-terminated spectral system type
+*        (e.g. "FREQ", "VELO", "WAVE", etc). A null string is returned if
+*        the CTYPE value does not describe a spectral axis.
+*     algcode
+*        An array in which to return the null-terminated algorithm code
+*        (e.g. "-LOG", "", "-F2W", etc). A null string is returned if the
+*        spectral axis is linear. A null string is returned if the CTYPE
+*        value does not describe a spectral axis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Retuned Value:
+*     A point to a static string holding the default units associated
+*     with the spectral system specified by the supplied CTYPE value.
+*     NULL is returned if the CTYPE value does not describe a spectral
+*     axis.
+
+*  Notes:
+*     - The axis is considered to be a spectral axis if the first 4
+*     characters form one of the spectral system codes listed in FITS-WCS
+*     paper 3. The algorithm code is not checked, except to ensure that
+*     it begins with a minus sign, or is blank.
+*     - A NULL pointer is returned if an error has already occurred.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   int ctype_len;
+
+/* Initialise */
+   stype[ 0 ] = 0;
+   algcode[ 0 ] = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise more stuff */
+   isspectral_ret = NULL;
+
+/* If the length of the string is less than 4, then it is not a spectral
+   axis. */
+   ctype_len = strlen( ctype );
+   if( ctype_len >= 4 ) {
+
+/* Copy the first 4 characters (the coordinate system described by the
+   axis) into a null-terminated buffer. */
+      strncpy( stype, ctype, 4 );
+      stype[ 4 ] = 0;
+      stype[ astChrLen( stype ) ] = 0;
+
+/* Copy any remaining characters (the algorithm code) into a null-terminated
+   buffer. Only copy a maximum of 4 characters. */
+      if( ctype_len > 4 ) {
+         if( ctype_len <= 8 ) {
+            strcpy( algcode, ctype + 4 );
+         } else {
+            strncpy( algcode, ctype + 4, 4 );
+            algcode[ 4 ] = 0;
+         }
+         algcode[ astChrLen( algcode ) ] = 0;
+      } else {
+         algcode[ 0 ] = 0;
+      }
+
+/* See if the first 4 characters of the CTYPE value form one of the legal
+   spectral coordinate type codes listed in FITS-WCS Paper III. Also note
+   the default units associated with the system. */
+      if( !strcmp( stype, "FREQ" ) ) {
+         isspectral_ret = "Hz";
+      } else if( !strcmp( stype, "ENER" ) ) {
+         isspectral_ret = "J";
+      } else if( !strcmp( stype, "WAVN" ) ) {
+         isspectral_ret = "/m";
+      } else if( !strcmp( stype, "VRAD" ) ) {
+         isspectral_ret = "m/s";
+      } else if( !strcmp( stype, "WAVE" ) ) {
+         isspectral_ret = "m";
+      } else if( !strcmp( stype, "VOPT" ) ) {
+         isspectral_ret = "m/s";
+      } else if( !strcmp( stype, "ZOPT" ) ) {
+         isspectral_ret = "";
+      } else if( !strcmp( stype, "AWAV" ) ) {
+         isspectral_ret = "m";
+      } else if( !strcmp( stype, "VELO" ) ) {
+         isspectral_ret = "m/s";
+      } else if( !strcmp( stype, "BETA" ) ) {
+         isspectral_ret = "";
+      }
+
+/* Also check that the remaining part of CTYPE (the algorithm code) begins
+   with a minus sign or is blank. */
+      if( algcode[ 0 ] != '-' && strlen( algcode ) > 0 ) isspectral_ret = NULL;
+   }
+
+/* Return null strings if the axis is not a spectral axis. */
+   if( ! isspectral_ret ) {
+      stype[ 0 ] = 0;
+      algcode[ 0 ] = 0;
+   }
+
+/* Return the result. */
+   return isspectral_ret;
+}
+
+static AstMapping *LinearWcs( FitsStore *store, int i, char s,
+                              const char *method, const char *class, int *status ) {
+/*
+*  Name:
+*     LinearWcs
+
+*  Purpose:
+*     Create a Mapping describing a FITS-WCS linear algorithm
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstMapping *LinearWcs( FitsStore *store, int i, char s,
+*                            const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function uses the contents of the supplied FitsStore to create
+*     a Mapping which goes from Intermediate World Coordinate (known as "w"
+*     in the context of FITS-WCS paper III) to a linearly related axis.
+*
+*     The returned Mapping is a ShiftMap which simply adds on the value of
+*     CRVALi.
+
+*  Parameters:
+*     store
+*        Pointer to the FitsStore structure holding the values to use for
+*        the WCS keywords.
+*     i
+*        The zero-based index of the spectral axis within the FITS header
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a Mapping, or NULL if an error occurs.
+*/
+
+/* Local Variables: */
+   AstMapping *ret;
+   double crv;
+
+/* Check the global status. */
+   ret = NULL;
+   if( !astOK ) return ret;
+
+/* Get the CRVAL value for the specified axis. */
+   crv = GetItem( &(store->crval), i, 0, s, NULL, method, class, status );
+   if( crv == AST__BAD ) crv = 0.0;
+
+/* Create a 1D ShiftMap which adds this value onto the IWCS value. */
+   if( crv != 0.0 ) {
+      ret = (AstMapping *) astShiftMap( 1, &crv, "", status );
+   } else {
+      ret = (AstMapping *) astUnitMap( 1, "", status );
+   }
+   return ret;
+}
+
+static AstMapping *LogAxis( AstMapping *map, int iax, int nwcs, double *lbnd_p,
+                            double *ubnd_p, double crval, int *status ){
+/*
+*  Name:
+*     LogAxes
+
+*  Purpose:
+*     Test a Frame axis to see if it logarithmically spaced in pixel coords.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstMapping *LogAxis( AstMapping *map, int iax, int nwcs, double *lbnd_p,
+*                          double *ubnd_p, double crval )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A specified axis of the supplied Mappinhg is tested to see if it
+*     corresponds to the form
+*
+*        S = Sr.exp( w/Sr )
+*
+*     where "w" is one of the Mapping inputs, "S" is the specified
+*     Mapping output, and "Sr" is the supplied value of "crval". This
+*     is the form for a FITS log axis as defined in FITS-WCS paper III.
+*
+*     If the above test is passed, a Mapping is returned from "S" to "w"
+*     (the inverseof the above expression).
+
+*  Parameters:
+*     map
+*        Pointer to the Mapping. This will usually be a Mapping from
+*        pixel coords to WCS coords.
+*     iax
+*        The index of the output of "map" which correspoinds to "S".
+*     nwcs
+*        The number of outputs from "map".
+*     lbnd_p
+*        Pointer to an array of double, with one element for each
+*        Mapping input coordinate. This should contain the lower bound
+*        of the input pixel box in each input dimension.
+*     ubnd_p
+*        Pointer to an array of double, with one element for each
+*        Mapping input coordinate. This should contain the upper bound
+*        of the input pixel box in each input dimension.
+*     crval
+*        The reference value ("Sr") to use. Must not be zero.
+
+*  Returned Value:
+*     If the specified axis is logarithmically spaced, a Mapping with
+*     "nwcs" inputs and "nwcs" outputs is returned. This Mapping transforms
+
+*     its "iax"th input using the transformation:
+*
+*        w = Sr.Log( S/Sr )
+*
+*     (where "S" is the Mapping is the "iax"th input and "w" is the
+*     "iax"th output). Other inputs are copied to the corresponding
+*     output without change. NULL is returned if the specified axis is
+*     not logarithmically spaced.
+*/
+
+/* Local Variables: */
+   AstMapping *result;     /* Returned Mapping */
+   AstMapping *tmap0;      /* A temporary Mapping */
+   AstMapping *tmap1;      /* A temporary Mapping */
+   AstMapping *tmap2;      /* A temporary Mapping */
+   AstMapping *tmap3;      /* A temporary Mapping */
+   AstMapping *tmap4;      /* A temporary Mapping */
+   const char *fexps[ 1 ]; /* Forward MathMap expressions */
+   const char *iexps[ 1 ]; /* Inverse MathMap expressions */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the inherited status and crval value. */
+   if( !astOK || crval == 0.0 ) return result;
+
+/* If the "log" algorithm is appropriate, the supplied axis (s) is related
+   to pixel coordinate (p) by s = Sr.EXP( a*p - b ). If this is the case,
+   then the log of s will be linearly related to pixel coordinates. To test
+   this, we create a CmpMap which produces log(s). */
+   fexps[ 0 ] = "logs=log(s)";
+   iexps[ 0 ] = "s=exp(logs)";
+   tmap1 = (AstMapping *) astMathMap( 1, 1, 1, fexps, 1, iexps,
+                                      "simpfi=1,simpif=1", status );
+   tmap2 = AddUnitMaps( tmap1, iax, nwcs, status );
+   tmap0 = (AstMapping *) astCmpMap( map, tmap2, 1, "", status );
+   tmap2 = astAnnul( tmap2 );
+
+/* See if this Mapping is linear. */
+   if( IsMapLinear( tmap0, lbnd_p, ubnd_p, iax, status ) ) {
+
+/* Create the Mapping which defines the IWC axis. This is the Mapping from
+   WCS to IWCS - "W = Sr.log( S/Sr )". Other axes are left unchanged by the
+   Mapping. The IWC axis has the same axis index as the WCS axis. */
+      tmap2 = (AstMapping *) astZoomMap( 1, 1.0/crval, "", status );
+      tmap3 = (AstMapping *) astCmpMap( tmap2, tmap1, 1, "", status );
+      tmap2 = astAnnul( tmap2 );
+      tmap2 = (AstMapping *) astZoomMap( 1, crval, "", status );
+      tmap4 = (AstMapping *) astCmpMap( tmap3, tmap2, 1, "", status );
+      tmap3 = astAnnul( tmap3 );
+      tmap2 = astAnnul( tmap2 );
+      result = AddUnitMaps( tmap4, iax, nwcs, status );
+      tmap4 = astAnnul( tmap4 );
+   }
+
+/* Free resources. */
+   tmap0 = astAnnul( tmap0 );
+   tmap1 = astAnnul( tmap1 );
+
+/* Return the result. */
+   return result;
+}
+
+static AstMapping *LogWcs( FitsStore *store, int i, char s,
+                           const char *method, const char *class, int *status ) {
+/*
+*  Name:
+*     LogWcs
+
+*  Purpose:
+*     Create a Mapping describing a FITS-WCS logarithmic algorithm
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstMapping *LogWcs( FitsStore *store, int i, char s,
+*                         const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function uses the contents of the supplied FitsStore to create
+*     a Mapping which goes from Intermediate World Coordinate (known as "w"
+*     in the context of FITS-WCS paper III) to a logarthmic version of w
+
+*     called "S" given by:
+*
+*     S = Sr.exp( w/Sr )
+*
+*     where Sr is the value of S corresponding to w=0.
+
+*  Parameters:
+*     store
+*        Pointer to the FitsStore structure holding the values to use for
+*        the WCS keywords.
+*     i
+*        The zero-based index of the axis within the FITS header
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a Mapping, or NULL if an error occurs.
+*/
+
+/* Local Variables: */
+   AstMapping *ret;
+   char forexp[ 12 + DBL_DIG*2 ];
+   char invexp[ 12 + DBL_DIG*2 ];
+   const char *fexps[ 1 ];
+   const char *iexps[ 1 ];
+   double crv;
+
+/* Check the global status. */
+   ret = NULL;
+   if( !astOK ) return ret;
+
+/* Get the CRVAL value for the specified axis. Use a default of zero. */
+   crv = GetItem( &(store->crval), i, 0, s, NULL, method, class, status );
+   if( crv == AST__BAD ) crv = 0.0;
+
+/* Create the MathMap, if possible. */
+   if( crv != 0.0 ) {
+      sprintf( forexp, "s=%.*g*exp(w/%.*g)", DBL_DIG, crv, DBL_DIG, crv );
+      sprintf( invexp, "w=%.*g*log(s/%.*g)", DBL_DIG, crv, DBL_DIG, crv );
+      fexps[ 0 ] = forexp;
+      iexps[ 0 ] = invexp;
+      ret = (AstMapping *) astMathMap( 1, 1, 1, fexps, 1, iexps, "simpfi=1,simpif=1", status );
+   }
+
+/* Return the result */
+   return ret;
+}
+
+static int LooksLikeClass( AstFitsChan *this, const char *method,
+                           const char *class, int *status ){
+
+/*
+*  Name:
+*     LooksLikeClass
+
+*  Purpose:
+*     Does the FitsChan seem to use FITS-CLASS encoding?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int LooksLikeClass( AstFitsChan *this, const char *method,
+*                         const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns non-zero if the supplied FitsChan probably uses FITS-CLASS
+*     encoding. This is the case if it contains a DELTAV keyword and a
+*     keyword of the form VELO-xxx", where xxx is one of the accepted
+*     standards of rest, or "VLSR".
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the encoding in use lookslike FITS-CLASS.
+*/
+
+/* Local Variables... */
+   int ret;            /* Returned value */
+
+/* Initialise */
+   ret = 0;
+
+/* Check the global status. */
+   if( !astOK ) return ret;
+
+/* See if there is a "DELTAV" card, and a "VELO-xxx" or "VLSR" card. */
+   if( astKeyFields( this, "DELTAV", 0, NULL, NULL ) && (
+          astKeyFields( this, "VLSR", 0, NULL, NULL ) ||
+          astKeyFields( this, "VELO-OBS", 0, NULL, NULL ) ||
+          astKeyFields( this, "VELO-HEL", 0, NULL, NULL ) ||
+          astKeyFields( this, "VELO-EAR", 0, NULL, NULL ) ||
+          astKeyFields( this, "VELO-LSR", 0, NULL, NULL ) ) ) {
+      ret = 1;
+   }
+
+/* Return  the result. */
+   return ret;
+}
+
+static void MakeBanner( const char *prefix, const char *middle,
+                        const char *suffix,
+                        char banner[ AST__FITSCHAN_FITSCARDLEN -
+                                     FITSNAMLEN + 1 ], int *status ) {
+/*
+*  Name:
+*     MakeBanner
+
+*  Purpose:
+*     Create a string containing a banner comment.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void MakeBanner( const char *prefix, const char *middle,
+*                      const char *suffix,
+*                      char banner[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ], int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function creates a string which can be written as a FITS
+*     comment card to produce a banner heading (or tail) for an AST
+*     Object when it is written to a FitsChan. The banner will occupy
+*     the maximum permitted width for text in a FITS comment card.
+
+*  Parameters:
+*     prefix
+*        A pointer to a constant null-terminated string containing the
+*        first part of the text to appear in the banner.
+*     middle
+*        A pointer to a constant null-terminated string containing the
+*        second part of the text to appear in the banner.
+*     suffix
+*        A pointer to a constant null-terminated string containing the
+*        third part of the text to appear in the banner.
+*     banner
+*        A character array to receive the null-terminated result string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The text to appear in the banner is constructed by
+*     concatenating the three input strings supplied.
+*/
+
+/* Local Variables: */
+   char token[] = "AST";         /* Identifying token */
+   int i;                        /* Loop counter for input characters */
+   int len;                      /* Number of output characters */
+   int ltok;                     /* Length of token string */
+   int mxlen;                    /* Maximum permitted output characters */
+   int start;                    /* Column number where text starts */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Calculate the maximum number of characters that the output banner
+   can hold and the length of the token string. */
+   mxlen = AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN;
+   ltok = (int) strlen( token );
+
+/* Calculate the column in which to start the text, so that it is
+   centred in the banner (with 4 non-text characters on each side). */
+   start = ltok + 2 + ( mxlen - ltok - 1 -
+                        (int) ( strlen( prefix ) +
+                                strlen( middle ) +
+                                strlen( suffix ) ) - 1 - ltok ) / 2;
+   if ( start < ltok + 2 ) start = ltok + 2;
+
+/* Start building the banner with the token string. */
+   len = 0;
+   for ( i = 0; token[ i ] && ( len < mxlen ); i++ ) {
+      banner[ len++ ] = token[ i ];
+   }
+
+/* Then pad with spaces up to the start of the text. */
+   while ( len < start - 1 ) banner[ len++ ] = ' ';
+
+/* Insert the prefix data, truncating it if it is too long. */
+   for ( i = 0; prefix[ i ] && ( len < mxlen - ltok - 1 ); i++ ) {
+      banner[ len++ ] = prefix[ i ];
+   }
+
+/* Insert the middle data, truncating it if it is too long. */
+   for ( i = 0; middle[ i ] && ( len < mxlen - ltok - 1 ); i++ ) {
+      banner[ len++ ] = middle[ i ];
+   }
+
+/* Insert the suffix data, truncating it if it is too long. */
+   for ( i = 0; suffix[ i ] && ( len < mxlen - ltok - 1 ); i++ ) {
+      banner[ len++ ] = suffix[ i ];
+   }
+
+/* Pad the end of the text with spaces. */
+   while ( len < mxlen - ltok ) banner[ len++ ] = ' ';
+
+/* Finish the banner with the token string. */
+   for ( i = 0; token[ i ] && ( len < mxlen ); i++ ) {
+      banner[ len++ ] = token[ i ];
+   }
+
+/* Terminate the output string. */
+   banner[ len ] = '\0';
+}
+
+static AstMapping *MakeColumnMap( AstFitsTable *table, const char *col,
+                                  int isindex, int interp, const char *method,
+                                  const char *class, int *status ){
+/*
+*  Name:
+*     MakeColumnMap
+
+*  Purpose:
+*     Create a Mapping describing a look-up table supplied in a cell of a
+*     FITS binary table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstMapping *MakeColumnMap( AstFitsTable *table, const char *col,
+*                                int isindex, int interp, const char *method,
+*                                const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns a Mapping representing the array of values
+*     stored in row 1 of a named column of a supplied FitsTable. The
+*     array of values is treated as a look-up table following the prescription
+*     of FITS-WCS paper III (the "-TAB" algorithm). If the array has (N+1)
+*     dimensions (where N is one or more), the returned Mapping has N
+*     inputs and N outputs. The inputs correspond to FITS GRID coords
+*     within the array. FITS-WCS paper III requires that the first dimension
+*     in the array has a length of "N" and contains the N output values
+*     at each input values.
+
+*  Parameters:
+*     table
+*        Pointer to the Fitstable.
+*     col
+*        A string holding the name of the column to use.
+*     isindex
+*        Non-zero if the column hold an index array, zero if it holds a
+*        coordinate array.
+*     interp
+*        The value to use for the Interp attribute of the LutMap. A value
+*        of zero tells the LutMap class to use linear interpolation. Other
+*        values tell the LutMap class to use nearest neighbour interpolation.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the Mapping, or NULL if an error occurs.
+*/
+
+/* Local Variables: */
+   AstMapping *result;
+   char *key;
+   double *lut;
+   int *dims;
+   int ndim;
+   int nel;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the inherited status */
+   if( !astOK ) return result;
+
+/* Get the number of dimensions spanned by the value in the named column. */
+   ndim = astGetColumnNdim( table, col );
+
+/* First deal with index vectors. */
+   if( isindex ) {
+
+/* FITS-WCS paper II mandates that index arrays must be 1-dimensional. */
+      if( ndim != 1 && astOK ) {
+         astError( AST__BADTAB, "%s(%s): Column '%s' has %d dimensions but it "
+                   "holds an index vector and should therefore be 1-dimensional.",
+                   status, method, class, col, ndim );
+      }
+
+/* Get the length of the index vector. */
+      nel = astGetColumnLength( table, col );
+
+/* Allocate memory to hold the array values, and to hold the cell key. */
+      lut = astMalloc( nel*sizeof( double ) );
+      key = astMalloc( strlen( col ) + 5 );
+      if( astOK ) {
+
+/* Create the key for the table cell holding the required array. FITS-WCS
+   paper III mandates that tables always occur in the first row of the
+   table (and that the table only has one row). Ignore trailing spaces in
+   the column name. */
+         sprintf( key, "%.*s(1)", (int) astChrLen( col ), col );
+
+/* Copy the array values into the above memory. */
+         if( astMapGet1D( table, key, nel, &nel, lut ) ) {
+
+/* Create a 1D LutMap. FITS-WCS paper III (sec 6.1.2) mandates that the input
+   corresponds to FITS grid coord (i.e. 1.0 at the centre of the first entry).
+   Ensure the LutMap uses linear interpolation. */
+            result = (AstMapping *) astLutMap( nel, lut, 1.0, 1.0,
+                                               "LutInterp=%d", status, interp );
+
+/* Report an error if the table cell was empty. */
+         } else if( astOK ) {
+            astError( AST__BADTAB, "%s(%s): Row 1 of the binary table "
+                      "contains no value for column '%s'.", status, method,
+                      class, col );
+         }
+      }
+
+/* Free memory. */
+      lut = astFree( lut );
+      key = astFree( key );
+
+/* Now deal with coordinate arrays. */
+   } else {
+
+/* Get the shape of the array. */
+      dims = astMalloc( sizeof( int )*ndim );
+      astColumnShape( table, col, ndim, &ndim, dims );
+
+/* For coordinate arrays, check the length of the first axis is "ndim-1", as
+   required by FITS-WCS paper III. */
+      if( astOK && dims[ 0 ] != ndim - 1 && !isindex ) {
+         astError( AST__BADTAB, "%s(%s): The first dimension of the coordinate "
+                   "array has length %d (should be %d since the array has %d "
+                   "dimensions).", status, method, class, dims[ 0 ], ndim - 1,
+                   ndim );
+      }
+
+/* We can currently only handle 1D look-up tables. These are stored in
+   notionally two-dimensional arrays in which the first dimension is
+   degenarate (i.e. spans only a single element). */
+      if( ndim > 2 ) {
+         if( astOK ) astError( AST__INTER, "%s(%s): AST can currently only "
+                               "handle 1-dimensional coordinate look-up tables "
+                               "(the supplied table has %d dimensions).", status,
+                               method, class, ndim - 1 );
+
+/* Handle 1-dimensional  look-up tables. */
+      } else if( astOK ){
+
+/* Allocate memory to hold the array values, and to hold the cell key. */
+         lut = astMalloc( dims[ 1 ]*sizeof( double ) );
+         key = astMalloc( strlen( col ) + 5 );
+         if( astOK ) {
+
+/* Create the key for the table cell holding the required array. FITS-WCS
+   paper III mandates that tables always occur in the first row of the
+   table (and that the table only has one row). Ignore trailing spaces in
+   the column name. */
+            sprintf( key, "%.*s(1)", (int) astChrLen( col ), col );
+
+/* Copy the array values into the above memory. */
+            if( astMapGet1D( table, key, dims[ 1 ], dims, lut ) ) {
+
+/* Create a 1D LutMap. FITS-WCS paper III (sec 6.1.2) mandates that the input
+   corresponds to FITS grid coord (i.e. 1.0 at the centre of the first entry).
+   Ensure the LutMap uses linear interpolation. */
+               result = (AstMapping *) astLutMap( dims[ 1 ], lut, 1.0, 1.0,
+                                                  "LutInterp=%d", status,
+                                                  interp );
+
+/* Report an error if the table cell was empty. */
+            } else if( astOK ) {
+               astError( AST__BADTAB, "%s(%s): Row 1 of the binary table "
+                         "contains no value for column '%s'.", status, method,
+                         class, col );
+            }
+         }
+
+/* Free memory. */
+         lut = astFree( lut );
+         key = astFree( key );
+      }
+      dims = astFree( dims );
+   }
+
+/* Issue a context message and annul the returned Mapping if an error
+   has occurred. */
+   if( !astOK ) {
+      astError( astStatus, "%s(%s): Cannot read a look-up table for a "
+                "tabular WCS axis from column '%s' of a FITS binary table.",
+                status, method, class, col );
+      result = astAnnul( result );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstFrameSet *MakeFitsFrameSet( AstFitsChan *this, AstFrameSet *fset,
+                                      int ipix, int iwcs, int encoding,
+                                      const char *method, const char *class,
+                                      int *status ) {
+/*
+*  Name:
+*     MakeFitsFrameSet
+
+*  Purpose:
+*     Create a FrameSet which conforms to the requirements of the FITS-WCS
+*     papers.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstFrameSet *MakeFitsFrameSet( AstFitsChan *this, AstFrameSet *fset,
+*                                    int ipix, int iwcs, int encoding,
+*                                    const char *method, const char *class,
+*                                    int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function constructs a new FrameSet holding the pixel and WCS
+*     Frames from the supplied FrameSet, but optionally extends the WCS
+*     Frame to include any extra axes needed to conform to the FITS model.
+
+*     Currently, this function does the following:
+*
+*     - if the WCS Frame contains a 1D spectral Frame with a defined celestial
+*     reference position (SpecFrame attributes RefRA and RefDec), then
+*     it ensures that the WCS Frame also contains a pair of celestial
+*     axes (such axes are added if they do not already exist within the
+*     supplied WCS Frame). The pixel->WCS Mapping is adjusted accordingly.
+*
+*     - if the WCS Frame contains a spectral axis and a pair of celestial
+*     axes, then the SpecFrame attributes RefRA and RefDec are set to the
+*     reference position defined by the celestial axes. The pixel->WCS
+*     Mapping is adjusted accordingly.
+*
+*     - NULL is returned if the WCS Frame contains more than one spectral
+*     axis.
+*
+*     - NULL is returned if the WCS Frame contains more than one pair of
+*     celestial axes.
+*
+*     - Any isolated sky axes (i.e. not contained within a SkyFrame) are
+*     re-mapped from radians into degrees.
+
+*  Parameters:
+*     this
+*        The FitsChan.
+*     fset
+*        The FrameSet to check.
+*     ipix
+*        The index of the FITS pixel Frame within "fset".
+*     iwcs
+*        The index of the WCS Frame within "fset".
+*     encoding
+*        The encoding in use.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A new FrameSet which confoms to the requirements of the FITS-WCS
+*     papers. The base Frame in this FrameSet will be the FITS pixel
+*     Frame, and the current Frame will be the WCS Frame. NULL is
+*     returned if an error has already occurred, or if the FrameSet cannot
+*     be produced for any reason.
+*/
+
+/* Local Variables: */
+   AstFitsChan *fc;        /* Pointer to temporary FitsChan */
+   AstFrame *pframe;       /* Pointer to the primary Frame */
+   AstFrame *pixfrm;       /* Pointer to the FITS pixel Frame */
+   AstFrame *tfrm0;        /* Pointer to a temporary Frame */
+   AstFrame *tfrm;         /* Pointer to a temporary Frame */
+   AstFrame *wcsfrm;       /* Pointer to the FITS WCS Frame */
+   AstFrameSet *ret;       /* The returned FrameSet */
+   AstFrameSet *tfs;       /* Pointer to a temporary FrameSet */
+   AstMapping *map1;       /* Pointer to pre-WcsMap Mapping */
+   AstMapping *map3;       /* Pointer to post-WcsMap Mapping */
+   AstMapping *map;        /* Pointer to the pixel->wcs Mapping */
+   AstMapping *remap;      /* Total Mapping from internal to external units */
+   AstMapping *smap;       /* Simplified Mapping */
+   AstMapping *tmap0;      /* Pointer to a temporary Mapping */
+   AstMapping *tmap1;      /* Pointer to a temporary Mapping */
+   AstMapping *tmap2;      /* Pointer to a temporary Mapping */
+   AstMapping *tmap;       /* Pointer to a temporary Mapping */
+   AstMapping *umap;       /* 1D Mapping from internal to external units */
+   AstSpecFrame *skyfrm;   /* Pointer to the SkyFrame within WCS Frame */
+   AstSpecFrame *specfrm;  /* Pointer to the SpecFrame within WCS Frame */
+   AstWcsMap *map2;        /* Pointer to WcsMap */
+   char card[ AST__FITSCHAN_FITSCARDLEN + 1 ]; /* A FITS header card */
+   char equinox_attr[ 13 ];/* Name of Equinox attribute for sky axes */
+   char system_attr[ 12 ]; /* Name of System attribute for sky axes */
+   const char *eqn;        /* Pointer to original sky Equinox value */
+   const char *extunit;    /* External units string */
+   const char *intunit;    /* Internal units string */
+   const char *skysys;     /* Pointer to original sky System value */
+   double con;             /* Constant axis value */
+   double reflat;          /* Celestial latitude at reference point */
+   double reflon;          /* Celestial longitude at reference point */
+   int *perm;              /* Pointer to axis permutation array */
+   int iax;                /* Axis inex */
+   int icurr;              /* Index of original current Frame in returned FrameSet */
+   int ilat;               /* Celestial latitude index within WCS Frame */
+   int ilon;               /* Celestial longitude index within WCS Frame */
+   int npix;               /* Number of pixel axes */
+   int nwcs;               /* Number of WCS axes */
+   int ok;                 /* Is the supplied FrameSet usable? */
+   int paxis;              /* Axis index within the primary Frame */
+   int rep;                /* Was error reporting switched on? */
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Get copies of the pixel Frame, the WCS Frame and the Mapping. */
+   tfrm = astGetFrame( fset, ipix );
+   pixfrm = astCopy( tfrm );
+   tfrm = astAnnul( tfrm );
+   tfrm = astGetFrame( fset, iwcs );
+   wcsfrm = astCopy( tfrm );
+   tfrm = astAnnul( tfrm );
+   tmap = astGetMapping( fset, ipix, iwcs );
+   map = astCopy( tmap );
+   tmap = astAnnul( tmap );
+
+/* Store the number of pixel and WCS axes. */
+   npix = astGetNaxes( pixfrm );
+   nwcs = astGetNaxes( wcsfrm );
+
+/* Search the WCS Frame for SkyFrames and SpecFrames. */
+   umap = NULL;
+   remap = NULL;
+   specfrm = NULL;
+   skyfrm = NULL;
+   ok = 1;
+   ilat = -1;
+   ilon = -1;
+   for( iax = 0; iax < nwcs; iax++ ) {
+
+/* Obtain a pointer to the primary Frame containing the current WCS axis. */
+      astPrimaryFrame( wcsfrm, iax, &pframe, &paxis );
+
+/* If the current axis is a SpecFrame, save a pointer to it. If we have already
+   found a SpecFrame, abort. */
+      if( astIsASpecFrame( pframe ) ) {
+         if( specfrm ) {
+            ok = 0;
+            break;
+         }
+         specfrm = astClone( pframe );
+
+/* If the current axis is a SkyFrame, save a pointer to it, and its WCS
+   index. If we have already found a different SkyFrame, abort. */
+      } else if( IsASkyFrame( pframe ) ) {
+         if( skyfrm ) {
+            if( pframe != (AstFrame *) skyfrm ) {
+               ok = 0;
+               break;
+            }
+         } else {
+            skyfrm = astClone( pframe );
+         }
+         if( paxis == 0 ) {
+            ilon = iax;
+         } else {
+            ilat = iax;
+         }
+
+/* If the internal and external units differ, attempt to remap the axis
+   into its external units. */
+      } else {
+
+/* Get the string describing the external units (the "Unit" attribute). */
+         extunit = astGetUnit( pframe, paxis );
+
+/* Get the string describing the internal units (the "InternalUnit"
+   attribute). */
+         intunit = astGetInternalUnit( pframe, paxis );
+
+/* If they are the same, we do not need to modify this axis. */
+         if( astOK && strcmp( extunit, intunit ) ){
+
+/* Otherwise, get the mapping from the internal units to the external
+   units, if possible. Ignore any error reported by unitmapper. */
+            rep = astReporting( 0 );
+            umap = astUnitMapper( intunit, extunit, NULL, NULL );
+            if( !astOK ) astClearStatus;
+            astReporting( rep );
+
+            if( !umap ) {
+
+/* If the above failed, ensure that the external units are the same as
+   the internal units (except that internal radians are converted to
+   external degrees). */
+               if( !strcmp( intunit, "rad" ) ) {
+                  umap = (AstMapping *) astZoomMap( 1, AST__DR2D, " ", status );
+                  extunit = "deg";
+               } else {
+                  extunit = intunit;
+               }
+
+               astSetUnit( wcsfrm, iax, extunit );
+            }
+         }
+      }
+
+/* If no change is needed for the mapping for this axis, use a UnitMap. */
+      if( !umap ) umap = (AstMapping *) astUnitMap( 1, " ", status );
+
+/* Extend the parallel CmpMap to encompass the current axis. */
+      if( remap ) {
+         tmap = (AstMapping *) astCmpMap( remap, umap, 0, " ", status );
+         (void) astAnnul( remap );
+         remap = tmap;
+      } else {
+         remap = astClone( umap );
+      }
+
+/* Free resources. */
+      umap = astAnnul( umap );
+      pframe = astAnnul( pframe );
+   }
+
+/* See if the pixel->wcs mapping needs to be modified to take account of
+   any changes to axis units. */
+   smap = astSimplify( remap );
+   if( ! astIsAUnitMap( smap ) ) {
+      tmap = (AstMapping *) astCmpMap( map, remap, 1, " ", status );
+      map = astAnnul( map );
+      remap = astAnnul( remap );
+      map = tmap;
+   }
+
+/* If the supplied FrameSet is usable... */
+   if( ok ) {
+
+/* If we did not find a SpecFrame, return a FrameSet made from the base
+   and current Frames in the supplied FrameSet. */
+      if( !specfrm ) {
+         ret = astFrameSet( pixfrm, "", status );
+         astAddFrame( ret, AST__BASE, map, wcsfrm );
+
+/* If we have a SpecFrame, proceed. */
+      } else {
+
+/* Check that both the RefRA and RefDec attributes of the SpecFrame are set.
+   If not, return a FrameSet made from the base and current Frames in the
+   supplied FrameSet. Also do this if the original WCS Frame contains 3
+   or more axes (since it is almost always inappropriate to add extra sky
+   axes in such circumestances). But if the other axes form a skyfram,
+   then we need to make sure they use the right refrence point. */
+         if( !astTestRefRA( specfrm ) || !astTestRefDec( specfrm ) ||
+             ( nwcs > 2 && !skyfrm ) ) {
+            ret = astFrameSet( pixfrm, "", status );
+            astAddFrame( ret, AST__BASE, map, wcsfrm );
+
+/* If we have a celestial reference position for the spectral axis, ensure
+   it is described correctly by a pair of celestial axes. */
+         } else {
+
+/* If the WCS Frame does not contain any celestial axes, we add some now. */
+            if( !skyfrm ) {
+
+/* The easiest way to create the required mapping from pixel to celestial
+   to create a simple FITS header and read it in via a FitsChan to create a
+   FrameSet. */
+               fc = astFitsChan( NULL, NULL, "", status );
+               astPutFits( fc, "CRPIX1  = 0", 0 );
+               astPutFits( fc, "CRPIX2  = 0", 0 );
+               astPutFits( fc, "CDELT1  = 0.0003", 0 );
+               astPutFits( fc, "CDELT2  = 0.0003", 0 );
+               astPutFits( fc, "CTYPE1  = 'RA---TAN'", 0 );
+               astPutFits( fc, "CTYPE2  = 'DEC--TAN'", 0 );
+               astPutFits( fc, "RADESYS = 'FK5'", 0 );
+               astPutFits( fc, "EQUINOX = 2000.0", 0 );
+               sprintf( card, "CRVAL1  = %.*g", DBL_DIG,
+                        AST__DR2D*astGetRefRA( specfrm ) );
+               astPutFits( fc, card, 0 );
+               sprintf( card, "CRVAL2  = %.*g", DBL_DIG,
+                        AST__DR2D*astGetRefDec( specfrm ) );
+               astPutFits( fc, card, 0 );
+               sprintf( card, "MJD-OBS = %.*g", DBL_DIG,
+                        TDBConv( astGetEpoch( specfrm ), AST__UTC, 1,
+                                 "astWrite", "FitsChan", status ) );
+               astPutFits( fc, card, 0 );
+               astClearCard( fc );
+               tfs = astRead( fc );
+               if( tfs ) {
+
+/* Create the new pixel->wcs Mapping. First get the 2-input,2-output
+   Mapping between pixel and sky coords from the above FrameSet. Then add
+   this Mapping in parallel with the original pixel->wcs Mapping. */
+                  tmap0 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
+                  tmap1 = (AstMapping *) astCmpMap( map, tmap0, 0, "", status );
+                  tmap0 = astAnnul( tmap0 );
+
+/* We now have a (npix+2)-input,(nwcs+2)-output Mapping. We now add a
+   PermMap in series with this which feeds the constant value 0.0 (the
+   CRPIX value in the above set of FITS headers) into the 2 pixel axes
+   corresponding to RA and Dec. This PermMap has npix-inputs and (npix+2)
+   outputs. The total Mapping then has npix inputs and (nwcs+2) outputs. */
+                  perm = astMalloc( sizeof( int )*(size_t) ( npix + 2 ) );
+                  if( astOK ) {
+                     for( iax = 0; iax < npix; iax++ ) perm[ iax ] = iax;
+                     perm[ npix ] = -1;
+                     perm[ npix + 1 ] = -1;
+                     con = 0.0;
+                     tmap0 = (AstMapping *) astPermMap( npix, perm, npix + 2, perm, &con, "", status );
+                     tmap2 = (AstMapping *) astCmpMap( tmap0, tmap1, 1, "", status );
+                     tmap0 = astAnnul( tmap0 );
+                     tmap1 = astAnnul( tmap1 );
+
+/* We now create the new WCS Frame with the extra RA and Dec axes. This
+   is just a CmpFrame made up of the original WCS Frame and the new
+   SkyFrame. */
+                     tfrm = astGetFrame( tfs, AST__CURRENT );
+                     tfrm0 = (AstFrame *) astCmpFrame( wcsfrm, tfrm, "", status );
+                     tfrm = astAnnul( tfrm );
+
+/* Construct the returned FrameSet. */
+                     ret = astFrameSet( pixfrm, "", status );
+                     astAddFrame( ret, AST__BASE, tmap2, tfrm0 );
+                     tmap2 = astAnnul( tmap2 );
+                     tfrm0 = astAnnul( tfrm0 );
+
+/* Free remaining resources. */
+                     perm = astFree( perm );
+                  }
+                  tfs = astAnnul( tfs );
+               }
+               fc = astAnnul( fc );
+
+/* If the WCS Frame does contain celestial axes we make sure that the
+   SpecFrame uses the same reference point. */
+            } else {
+
+/* The returned FrameSet has no extra Frames (although some attributes
+   may be changed) so just create a new FrameSet equaivalent to the supplied
+   FrameSet. */
+               tfs = astFrameSet( pixfrm, "", status );
+               astAddFrame( tfs, AST__BASE, map, wcsfrm );
+
+/* The RefRA and RefDec attributes of the SpecFrame must be set in FK5
+   J2000. Therefore we need to know the celestial reference point in
+   FK5 J2000. Modify the SkyFrame within the FrameSet to represent FK5
+   J2000, noting the original sky system and equinox first so that they
+   can be re-instated (if set) later on. */
+               sprintf( system_attr, "System(%d)", ilon + 1 );
+               if( astTest( tfs, system_attr ) ) {
+                  skysys = astGetC( tfs, system_attr );
+               } else {
+                  skysys = NULL;
+               }
+               astSetC( tfs, system_attr, "FK5" );
+               sprintf( equinox_attr, "Equinox(%d)", ilon + 1 );
+               if( astTest( tfs, equinox_attr ) ) {
+                  eqn = astGetC( tfs, equinox_attr );
+               } else {
+                  eqn = NULL;
+               }
+               astSetC( tfs, equinox_attr, "J2000" );
+
+/* The reference point for the celestial axes is defined by the WcsMap
+   contained within the Mapping. Split the mapping up into a list of serial
+   component mappings, and locate the first WcsMap in this list. The first
+   Mapping returned by this call is the result of compounding all the
+   Mappings up to (but not including) the WcsMap, the second returned Mapping
+   is the (inverted) WcsMap, and the third returned Mapping is anything
+   following the WcsMap. Only proceed if one and only one WcsMap is found. */
+               tmap0 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
+               if( SplitMap( tmap0, astGetInvert( tmap0 ), ilon, ilat, &map1, &map2, &map3, status ) ){
+
+/* The reference point in the celestial coordinate system is found by
+   transforming the fiducial point in native spherical co-ordinates
+   into absolute physical coordinates using map3. */
+                  if( GetFiducialWCS( map2, map3, ilon,  ilat, &reflon, &reflat, status ) ){
+
+/* Use reflon and reflat (which represent FK5 J2000 RA and Dec) to set
+   the values of the SpecFrame RefRA and RefDec attributes. Format the
+   values first so that we can use the FrameSet astSetC method, and so
+   maintain the FrameSet integrity. Use "tfs" rather than "wcsfrm" when
+   calling astFormat, as "wcsfrm" is not affected by the above change
+   to the current frame of "tfs" (i.e. astAddFrame takes a deep copy of the
+   supplied Frame). */
+                     astSetC( tfs, "RefRA", astFormat( tfs, ilon, reflon ) );
+                     astSetC( tfs, "RefDec", astFormat( tfs, ilat, reflat ) );
+
+/* If succesfull, return a pointer to the FrameSet. */
+                     if( astOK ) ret = astClone( tfs );
+                  }
+
+/* Release resources. */
+                  map1 = astAnnul( map1 );
+                  map2 = astAnnul( map2 );
+                  map3 = astAnnul( map3 );
+
+/* If no WcsMap was found, the celestial axes have no reference point and
+   so we can retain the original spectral reference point, so just return
+   the temporary FrameSet. */
+               } else if( astOK ) {
+                  ret = astClone( tfs );
+               }
+               tmap0 = astAnnul( tmap0 );
+
+/* Re-instate the original sky system and equinox. */
+               if( skysys ) astSetC( tfs, system_attr, skysys );
+               if( eqn ) astSetC( tfs, equinox_attr, eqn );
+
+/* Release resources. */
+               tfs = astAnnul( tfs );
+            }
+         }
+      }
+   }
+
+/* Add a new current Frame into the FrameSet which increases the chances of
+   the requested encoding being usable. The index of the original current
+   Frame is returned, or AST__NOFRAME if no new Frame was added. */
+   icurr = AddEncodingFrame( this, ret, encoding, method, class, status );
+
+/* If a new Frame was added, remove the original current Frame. */
+   if( icurr != AST__NOFRAME ) astRemoveFrame( ret, icurr );
+
+/* Free resources. */
+   if( specfrm ) specfrm = astAnnul( specfrm );
+   if( skyfrm ) skyfrm = astAnnul( skyfrm );
+   pixfrm = astAnnul( pixfrm );
+   wcsfrm = astAnnul( wcsfrm );
+   map = astAnnul( map );
+
+/* Return NULL if an error has occurred. */
+   if( !astOK && ret ) ret = astAnnul( ret );
+
+/* Return the result. */
+   return ret;
+}
+
+static void MakeIndentedComment( int indent, char token,
+                                 const char *comment, const char *data,
+                                 char string[ AST__FITSCHAN_FITSCARDLEN -
+                                              FITSNAMLEN + 1 ], int *status ) {
+/*
+*  Name:
+*     MakeIndentedComment
+
+*  Purpose:
+*     Create a comment string containing an indentation bar.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void MakeIndentedComment( int indent, char token,
+*                               const char *comment, const char *data,
+*                               char string[ AST__FITSCHAN_FITSCARDLEN -
+*                                            FITSNAMLEN + 1 ], int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function creates a string that may be used as text in a
+*     FITS comment card. The string contains a textual comment
+*     preceded by a bar (a line of characters) whose length can be
+*     used to indicate a level of indentation (in the absence of any
+*     way of indenting FITS keywords).
+
+*  Parameters:
+*     indent
+*        The level of indentation, in characters.
+*     token
+*        The character used to form the indentation bar.
+*     comment
+*        A pointer to a constant null-terminated string containing the text
+*        of the comment to be included.
+*     data
+*        A pointer to a constant null-terminated string containing any
+*        textual data to be appended to the comment.
+*     string
+*        A character array to receive the output string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*    - The comment text that appears in the output string is formed by
+*   concatenating the "comment" and "data" strings.
+*/
+
+/* Local Variables: */
+   int i;                        /* Loop counter for input characters */
+   int len;                      /* Number of output characters */
+   int mxlen;                    /* Maximum length of output string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Calculate the maximum number of characters that the output string
+   can accommodate. */
+   mxlen = AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN;
+
+/* Start the string with "indent" copies of the token character, but
+   without exceeding the output string length. */
+   len = 0;
+   while ( ( len < indent ) && ( len < mxlen ) ) string[ len++ ] = token;
+
+/* Pad with spaces up to the start of the comment, if necessary. */
+   while ( len < ( FITSCOMCOL - FITSNAMLEN - 1 ) ) {
+      string[ len++ ] = ' ';
+   }
+
+/* Add "/ " to introduce the comment (strictly not necessary as the
+   whole card will be a comment, but it matches the other non-comment
+   cards). Truncate if necessary. */
+   for ( i = 0; ( i < 2 ) && ( len < mxlen ); i++ ) {
+      string[ len++ ] = "/ "[ i ];
+   }
+
+/* Append the comment string, truncating it if it is too long. */
+   for ( i = 0; comment[ i ] && ( len < mxlen ); i++ ) {
+      string[ len++ ] = comment[ i ];
+   }
+
+/* Append the data string, again truncating if too long. */
+   for ( i = 0; data[ i ] && ( len < mxlen ); i++ ) {
+      string[ len++ ] = data[ i ];
+   }
+
+/* Terminate the output string. */
+   string[ len ] = '\0';
+}
+
+static void MakeIntoComment( AstFitsChan *this, const char *method,
+                             const char *class, int *status ){
+
+/*
+*  Name:
+*     MakeIntoComment
+
+*  Purpose:
+*     Convert a card into a FITS COMMENT card.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void MakeIntoComment( AstFitsChan *this, const char *method,
+*                           const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function formats the card stored just prior to the current card,
+*     and re-stores it as a COMMENT card. It is used (when writing an Object
+*     to a FitsChan) to output values that are not "set" and which are
+*     therefore provided for information only, and should not be read back.
+*     the COMMENT card has the effect of "commenting out" the value.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     method
+*        Calling method.
+*     class
+*        Object class.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   char card[ AST__FITSCHAN_FITSCARDLEN + 1 ]; /* Character buffer for FITS card data */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Move the current card backwards by one card. */
+   MoveCard( this, -1, method, class, status );
+
+/* Format the new current card. */
+   FormatCard( this, card, method, status );
+
+/* Write the resulting string to the FitsChan as the contents of a COMMENT
+   card, overwriting the existing card. The current card is incremented
+   by this call so that it refers to the same card as on entry. */
+   astSetFitsCom( this, "COMMENT", card, 1 );
+}
+
+static int MakeIntWorld( AstMapping *cmap, AstFrame *fr, int *wperm, char s,
+                         FitsStore *store, double *dim,
+                         const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     MakeIntWorld
+
+*  Purpose:
+*     Create FITS header values which map grid into intermediate world
+*     coords.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int MakeIntWorld( AstMapping *cmap, AstFrame *fr, int *wperm, char s,
+*                       FitsStore *store, double *dim,
+*                       const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function adds values to the supplied FitsStore which describe
+*     the transformation from grid (pixel) coords to intermediate world
+*     coords. The values added to the FitsStore correspond to the CRPIXj,
+*     PCi_j, CDELTi and WCSAXES keywords, and are determined by examining the
+*     suppliedMapping, which must be linear with an optional shift of
+*     origin (otherwise a value of zero is returned).
+*
+*     Much of the complication in the algorithm arises from the need to
+*     support cases where the supplied Mapping has more outputs than
+*     inputs. In these case we add some "degenerate" axes to the grid
+*     coord system, choosing their unit vectors to be orthogonal to all
+*     the other grid axes. It is assumed that degenerate axes will never
+*     be used to find a position other than at the axis value of 1.0.
+*
+*     NOTE, appropriate values for CRVAL keywords should have been stored
+*     in the FitsStore before calling this function (since this function may
+*     modify them).
+
+*  Parameters:
+*     cmap
+*        A pointer to a Mapping which transforms grid coordinates into
+*        intermediate world coordinates. The number of outputs must be
+*        greater than or equal to the number of inputs.
+*     fr
+*        Pointer to the final WCS coordinate Frame.
+*     wperm
+*        Pointer to an array of integers with one element for each axis of
+*        the "fr" Frame. Each element holds the zero-based index of the
+*        FITS-WCS axis (i.e. the value of "i" in the keyword names "CTYPEi",
+*        "CDi_j", etc) which describes the Frame axis.
+*     s
+*        The co-ordinate version character. A space means the primary
+*        axis descriptions. Otherwise the supplied character should be
+*        an upper case alphabetical character ('A' to 'Z').
+*     store
+*        A pointer to the FitsStore into which the calculated CRPIX and
+*        CDi_j values are to be put.
+*     dim
+*        An array holding the image dimensions in pixels. AST__BAD can be
+*        supplied for any unknwon dimensions.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if the CRPIX and CDi_j values are
+*     succesfully calculated. Zero is returned otherwise.
+
+*  Notes:
+*     -  Zero is returned if an error occurs.
+*/
+
+/* Local Variables: */
+   AstFrame *pfrm;
+   AstFrame *sfrm;
+   AstMapping *map;
+   AstPointSet *psetw;
+   AstPointSet *psetg;
+   double **fullmat;
+   double **partmat;
+   double **ptrg;
+   double **ptrw;
+   double *c;
+   double *cdelt;
+   double *cdmat;
+   double *colvec;
+   double *d;
+   double *g;
+   double *g0;
+   double *m;
+   double *mat;
+   double *tol;
+   double *w0;
+   double *y;
+   double cd;
+   double crp;
+   double crv;
+   double cv;
+   double det;
+   double err;
+   double k;
+   double mxcv;
+   double skydiag1;
+   double skydiag0;
+   double val;
+   int *iw;
+   int *lin;
+   int *pperm;
+   int *skycol;
+   int i;
+   int ii;
+   int j;
+   int jax;
+   int jj;
+   int nin;
+   int nout;
+   int nwcs;
+   int paxis;
+   int ret;
+   int sing;
+   int skycol0;
+   int skycol1;
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Simplify the supplied Mapping to reduce rounding errors when
+   transforming points. */
+   map = astSimplify( cmap );
+
+/* Get the number of inputs and outputs for the Mapping. Return if the
+   number of outputs is smaller than the number of inputs. */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+   if( nout < nin ) return ret;
+
+/* Note the number of final World Coordinate axes (not necessarily the
+   same as "nout", since some intermediate axes may be discarded by a
+   later PermMap. */
+   nwcs = astGetNaxes( fr );
+
+/* Allocate work space. */
+   g = astMalloc( sizeof(double)*(size_t) nin );
+   g0 = astMalloc( sizeof(double)*(size_t) nin );
+   w0 = astMalloc( sizeof(double)*(size_t) nout );
+   tol = astMalloc( sizeof(double)*(size_t) nout );
+   partmat = astMalloc( sizeof(double *)*(size_t) nout );
+   lin = astMalloc( sizeof(int)*(size_t) nout );
+   pperm = astMalloc( sizeof(int)*(size_t) nout );
+   skycol = astMalloc( sizeof(int)*(size_t) nout );
+   cdmat = astMalloc( sizeof(double)*(size_t) (nout*nout) );
+   cdelt = astMalloc( sizeof(double)*(size_t) nout );
+
+/* For safety, initialise all other pointers. */
+   if( partmat ) for( j = 0; j < nout; j++ ) partmat[ j ] = NULL;
+   fullmat = NULL;
+
+/* Create a PointSet to hold an input (grid) position for each axis, plus
+   an extra one. Create two other PointSets to hold corresponding
+   output (IWC) coordinates. */
+   psetg = astPointSet( nin + 1, nin, "", status );
+   ptrg = astGetPoints( psetg );
+   psetw = astPointSet( nin + 1, nout, "", status );
+   ptrw = astGetPoints( psetw );
+
+/* Check the pointers can be used safely. */
+   if( astOK ) {
+
+/* Assume success. */
+      ret = 1;
+
+/* The next section finds a 'root' grid position for which the
+   corresponding IWC coordinates are all good. It also finds these IWC
+   coordinates, together with the IWC coordinates of "nin" points which
+   are a unit distance away from the root grid position along each
+   grid axis. It also finds an estimate of the rounding error in each
+   Mapping output.
+   ================================================================= */
+      ret = FindBasisVectors( map, nin, nout, dim, psetg, psetw, status );
+
+/* Save the grid root position in "g0". */
+      for( j = 0; j < nin; j++ ) g0[ j ] = ptrg[ j ][ 0 ];
+
+/* Save the transformed root position in "w0". This is the grid root
+   position represented as a vector within the Intermediate World
+   Coordinate system. */
+      for( j = 0; j < nout; j++ ) {
+         w0[ j ] = ptrw[ j ][ 0 ];
+
+/* Find the tolerance for positions on the j'th IWC axis. This is one
+   tenth of the largest change in the j'th IWC axis value caused by
+   moving out 1 pixel along any grid axis. */
+         tol[ j ] = 0.0;
+         for( i = 0; i < nin; i++ ) {
+            err = fabs( ptrw[ j ][ i + 1 ] - w0[ j ] );
+            if( err > tol[ j ] ) tol[ j ] = err;
+         }
+         tol[ j ] *= 0.1;
+
+/* If the tolerance is zero (e.g. as is produced for degenerate axes),
+   then use a tolerance equal to a very small fraction of hte degenerate
+   axis value. If the axis value is zero use a fixed small value. */
+         if( tol[ j ] == 0.0 ) tol[ j ] = w0[ j ]*DBL_EPSILON*1.0E5;
+         if( tol[ j ] == 0.0 ) tol[ j ] = sqrt( DBL_MIN );
+      }
+
+/* The next section finds the CD matrix.
+   ===================================== */
+
+/* Initialise the CD matrix elements to "all missing". */
+      for( i = 0; i < nout*nout; i++ ) cdmat[ i ] = AST__BAD;
+
+/* The elements of column "j" of the CD matrix form a vector (in Intermediate
+   World Coords) which corresponds to a unit vector along grid axis "j".
+   We now find these vectors for all the grid axes represented by the
+   inputs to the supplied Mapping. */
+      for( i = 0; i < nin && ret; i++ ) {
+
+/* Form a unit vector along the current input axis. */
+         for( ii = 0; ii < nin; ii++ ) g[ ii ] = 0.0;
+         g[ i ] = 1.0;
+
+/* Fit a straight line (within IWC) to the current input axis of the Mapping.
+   The IWC vector corresponding to a unit vector along the current input axis
+   is returned if the Mapping is linear. A NULL pointer is returned if the
+   Mapping is not linear. */
+         partmat[ i ] = FitLine( map, g, g0, w0, dim[ i ], tol, status );
+
+/* If unsuccesful, indicate failure and break out of the loop. */
+         if( !partmat[ i ] ) {
+            ret = 0;
+            break;
+         }
+      }
+
+/* If the number of outputs for "map" is larger than the number of inputs,
+   then we will still be missing some column vectors for the CDi_j matrix
+   (which has to be square). We invent these such that the they are
+   orthogonal to all the other column vectors. Only do this if the
+   Mapping is linear. */
+      if( ret ) {
+         fullmat = OrthVectorSet( nout, nin, partmat, status );
+         if( !fullmat ) ret = 0;
+      }
+
+/* Check everything is OK. */
+      if( ret ) {
+
+/* Check that the full matrix is invertable, and if not, see if there is
+   any way to make it invertable. */
+         MakeInvertable( fullmat, nout, dim, status );
+
+/* Set up an array holding index of the Mapping output corresponding to
+   each IWC axis (the inverse of "wperm"). Also look for matching pairs of
+   celestial WCS axes. For the first such pair, note the corresponding
+   column indices and the diagonal element of the matrix which gives the
+   scaling for the axis (taking account of the permutation of WCS axes).
+   Also note if the Mapping from intermediate world coords to final world
+   coords is linear for each axis (this is assumed to be the case if the
+   axis is part of a simple Frame). */
+         sfrm = NULL;
+         skydiag0 = AST__BAD;
+         skydiag1 = AST__BAD;
+         skycol0 = -1;
+         skycol1 = -1;
+         for( i = 0; i < nout; i++ ) {
+            pperm[ wperm[ i ] ] = i;
+            astPrimaryFrame( fr, i, &pfrm, &paxis );
+            if( IsASkyFrame( pfrm ) ) {
+               skycol[ wperm[ i ] ] = paxis + 1;
+               lin[ i ] = 0;
+               if( !sfrm ) {
+                  sfrm = astClone( pfrm );
+                  skycol0 = wperm[ i ];
+                  skydiag0 = fullmat[ skycol0 ][ i ];
+               } else if( sfrm == pfrm ) {
+                  skycol1 = wperm[ i ];
+                  skydiag1 = fullmat[ skycol1 ][ i ];
+               }
+            } else {
+               skycol[ wperm[ i ] ] = 0;
+               lin[ i ] = !strcmp( astGetClass( pfrm ), "Frame" );
+            }
+            pfrm = astAnnul( pfrm );
+         }
+         if( sfrm ) sfrm = astAnnul( sfrm );
+
+/* We now have the complete CDi_j matrix. Now to find the CRPIX values.
+   These are the grid coords of the reference point (which corresponds to
+   the origin of Intermediate World Coords). The "w0" array currently holds
+   the position of the root position, as a position within IWC, and the
+   "g0" array holds the corresponding position in grid coordinates. We
+   also have IWC vectors which correspond to unit vectors on each grid
+   axis. The CRPIX values are defined by the matrix equation
+        w0 = fullmat*( g0 - crpix )
+   The "g0" array only contains "nin" values. If nout>nin, then the
+   missing g0 values will be assumed to be zero when we come to find the
+   CRPIX values below.
+   We use palDmat to solve this system of simultaneous equations to get
+   crpix. The "y" array initially holds "w0" but is over-written to hold
+   "g0 - crpix". */
+         mat = astMalloc( sizeof( double )*(size_t)( nout*nout ) );
+         y = astMalloc( sizeof( double )*(size_t) nout );
+         iw = astMalloc( sizeof( int )*(size_t) nout );
+         if( astOK ) {
+            m = mat;
+            for( i = 0; i < nout; i++ ) {
+               for( j = 0; j < nout; j++ ) *(m++) = fullmat[ j ][ i ];
+               y[ i ] = w0[ i ];
+            }
+            palDmat( nout, mat, y, &det, &sing, iw );
+         }
+         mat = astFree( mat );
+         iw = astFree( iw );
+
+/* Loop round all axes, storing the column vector pointer. */
+         for( j = 0; j < nout; j++ ) {
+            colvec = fullmat[ j ];
+
+/* Get the CRPIX values from the "y" vector created above by palDmat.
+   First deal with axes for which there are Mapping inputs. */
+            if( j < nin ) {
+               crp = g0[ j ] - y[ j ];
+
+/* If this is a grid axis which has been created to represent a "missing"
+   input to the mapping, we need to add on 1.0 to the crpix value found
+   above. This is because the "w0" vector corresponds to a value of zero
+   on any missing axes, but the FITS grid value for any missing axes is
+   1.0. */
+            } else {
+               crp = 1.0 - y[ j ];
+            }
+
+/* Store the CD and CRPIX values for axes which correspond to inputs
+   of "map". The CD matrix elements are stored in an array and are
+   converted later to the corresponding PC and CDELT values. */
+            if( j < nin || crp == 0.0 ) {
+               for( i = 0; i < nout; i++ ) {
+                  cdmat[ wperm[ i ]*nout+j ] = colvec[ i ] ;
+               }
+               SetItem( &(store->crpix), 0, j, s, crp, status );
+
+/* The length of the unit vector along any "degenerate" axes was fixed
+   arbitrarily at 1.0 by the call to OrthVectorSet. We can probably
+   choose a more appropriate vector length. The choice shouldn't make any
+   difference to the transformation, but an appropriate value will look
+   more natural to human readers. */
+            } else {
+
+/* First, try to arrange for longitude/latitude axis pairs to have the same
+   scale. Do we have a matching pair of celestial axes? */
+               k = AST__BAD;
+               if( skydiag0 != AST__BAD && skydiag1 != AST__BAD ) {
+
+/* Is the current column the one which corresponds to the first celestial
+   axis, and does the other sky column correspond to a Mapping input? */
+                  if( skycol0 == j && skycol1 < nin ) {
+
+/* If so, scale this column so that its diagonal element is the negative
+   of the diagonal element of the other axis. This is on the assumption that
+   the scales on the two axes should be equal, and that longitude increases
+   east whilst latitude increases north, and that the CD matrix does not
+   introduce an axis permutation. */
+                     if( skydiag0 != 0.0 ) k = -skydiag1/skydiag0;
+
+/* Now see if the current column the one which corresponds to the second
+   celestial axis. Do the same as above. */
+                  } else if( skycol1 == j && skycol0 < nin ) {
+                     if( skydiag1 != 0.0 ) k = -skydiag0/skydiag1;
+
+/* If neither of the above conditions was met, assume a diagonal element
+   value of 1.0 degrees for latitude axes, and -1.0 degrees for longitude
+   axes. */
+                  }
+               }
+
+/* If this failed, the next choice is to arrange for diagonally opposite
+   elements to be equal and opposite in value. Look for the element of the
+   column which has the largest diagonally opposite element, and choose a
+   scaling factor which makes this column element equal to the negative value
+   of its diagonally opposite element. Be careful to take axis permutations
+   into account when finding the value of the diagonal element. */
+               if( k == AST__BAD ) {
+                  mxcv = 0.0;
+                  ii = pperm[ j ];
+                  for( i = 0; i < nout; i++ ) {
+                     jj = wperm[ i ];
+                     if( jj < nin ) {
+                        cv = fullmat[ jj ][ ii ];
+                        if( !astEQUAL( colvec[ i ], 0.0 ) && fabs( cv ) > mxcv ) {
+                           mxcv = fabs( cv );
+                           k = -cv/colvec[ i ];
+                        }
+                     }
+                  }
+               }
+
+/* If still no scaling factor is available, use a scaling factor which
+   produces a diagonal element of 1.0 degree if the corresponding row is a
+   sky latitude axis, -1.0 degree of sky longitude axes, and 1.0 for other
+   axes. */
+               if( k == AST__BAD && colvec[ pperm[ j ] ] != 0.0 ) {
+                  if( skycol[ j ] ) {
+                     k = AST__DD2R/colvec[ pperm[ j ] ];
+                     if( skycol[ j ] == 1 ) k = -k;
+                  } else {
+                     k = 1.0/colvec[ pperm[ j ] ];
+                  }
+               }
+
+/* If we still do not have a scaling, use 1.0 (no scaling). */
+               if( k == AST__BAD ) k = 1.0;
+
+/* Now scale and store the column elements. */
+               for( i = 0; i < nout; i++ ) {
+                  cdmat[ wperm[ i ]*nout+j ] = k*colvec[ i ];
+               }
+
+/* Find the corresponding modified CRPIX value and store it. */
+               crp = 1.0 + ( crp - 1.0 )/k;
+               SetItem( &(store->crpix), 0, j, s, crp, status );
+            }
+
+/* Free resources */
+            if( pfrm ) pfrm = astAnnul( pfrm );
+         }
+
+/* Any "degenerate" axes added in the above process for which the
+   intermediate->world mapping is linear, and which depend only on one
+   pixel axis, can be adjusted so that the reference point is at grid
+   coord 1.0. */
+         for( i = 0; i < nout; i++ ) {
+            if( lin[ i ] ) {
+
+/* Check only one pixel axis contributes to this intermediate world axis
+   and find which one it is. */
+               jax = -1;
+               for( j = 0; j < nout; j++ ) {
+                  if( !astEQUAL( fullmat[ j ][ i ], 0.0 ) ) {
+                     if( jax == -1 ) {
+                        jax = j;
+                     } else {
+                        jax = -1;
+                        break;
+                     }
+                  }
+               }
+
+/* We only adjust values for "degenerate" axes. */
+               if( jax >= nin ) {
+
+/* Check that this pixel axis only contributes to the single world axis
+   currently being considered. */
+                  for( ii = 0; ii < nout; ii++ ) {
+                     if( ii != i ) {
+                        if( !astEQUAL( fullmat[ jax ][ ii ], 0.0 ) ) {
+                           jax = -1;
+                           break;
+                        }
+                     }
+                  }
+                  if( jax != -1 ) {
+
+/* Get the original CRVAL, CRPIX and CD values. Check they are defined.*/
+                     crv = GetItem( &(store->crval), wperm[ i ], 0, s, NULL,
+                                    method, class, status );
+                     cd = cdmat[ wperm[ i ]*nout + jax ];
+                     crp = GetItem( &(store->crpix), 0, jax, s, NULL, method, class, status );
+                     if( crv != AST__BAD && crp != AST__BAD &&
+                         cd != AST__BAD ) {
+
+/* Modify the CRPIX to be 1.0 and modify the CRVAL value accordingly. */
+                        SetItem( &(store->crpix), 0, jax, s, 1.0, status );
+                        SetItem( &(store->crval), wperm[ i ], 0, s,
+                                 cd*( 1.0 - crp ) + crv, status );
+                     }
+                  }
+               }
+            }
+         }
+
+/* Finally, if there are fewer input axes than output axes, put a value for
+   the WCSAXES keyword into the store. */
+         if( nin < nwcs ) SetItem( &(store->wcsaxes), 0, 0, s, nwcs, status );
+
+/* Release resources. */
+         y = astFree( y );
+      }
+
+/* Produce and store PC and CDELT values from the above CD matrix */
+      SplitMat( nout, cdmat, cdelt, status );
+      c = cdmat;
+      d = cdelt;
+      for( i = 0; i < nout; i++ ){
+         for( j = 0; j < nout; j++ ){
+            val = *(c++);
+            if( i == j ){
+               if( astEQUAL( val, 1.0 ) ) val = AST__BAD;
+            } else {
+               if( astEQUAL( val, 0.0 ) ) val = AST__BAD;
+            }
+            if( val != AST__BAD ) SetItem( &(store->pc), i, j, s, val, status );
+         }
+         SetItem( &(store->cdelt), i, 0, s, *(d++), status );
+      }
+   }
+
+/* Annul pointsets. */
+   psetg = astAnnul( psetg );
+   psetw = astAnnul( psetw );
+
+/* Free other resources*/
+   map = astAnnul( map );
+   if( fullmat ) for( j = 0; j < nout; j++ ) fullmat[ j ] = astFree( fullmat[ j ] );
+   if( partmat ) for( j = 0; j < nout; j++ ) partmat[ j ] = astFree( partmat[ j ] );
+   fullmat = astFree( fullmat );
+   partmat = astFree( partmat );
+   cdmat = astFree( cdmat );
+   cdelt = astFree( cdelt );
+   g = astFree( g );
+   g0 = astFree( g0 );
+   w0 = astFree( w0 );
+   tol = astFree( tol );
+   lin = astFree( lin );
+   skycol = astFree( skycol );
+   pperm = astFree( pperm );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) ret = 0;
+
+/* Return the answer. */
+   return ret;
+}
+
+static void MakeInvertable( double **fullmat, int n, double *dim, int *status ){
+/*
+*  Name:
+*     MakeInvertable
+
+*  Purpose:
+*     Modify a supplied square CD matrix if possible to make it invertable.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void MakeInvertable( double **fullmat, int n, double *dim, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A search is made for matrix inputs that have no effect on any
+*     matrix outputs. if any such matrix inputs are associated with
+*     degenerate pixel axes (i.e. pixel axes that span only a single
+*     pixel), then the matrix input should always have the value zero and
+*     so the corresponding diagonal element of the matrix can be set to
+*     1.0 without changing and of the outputs.
+
+*  Parameters:
+*     fullmat
+*        A pointer to an array with "n" elements corresponding to the n
+*        inputs of the matrix, each element being a pointer to an array
+*        with "n" elements corresponding to the n outputs of the matrix.
+*     n
+*        The number of inputs and outputs for the square matrix.
+*     dim
+*        Pointer to an array of "n" input (i.e. pixel) axis dimensions.
+*        Individual elements will be AST__BAD if dimensions are not known.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int i;          /* Input index */
+   int j;          /* Output index */
+   int unused;     /* Does the current input have no effect on any output? */
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Look for any inputs that have no effect on any of the outputs. If such
+   an input is associated with a degenerate grid axis (i.e. a grid axis
+   with a dimension of 1.0), then the input value will always be zero and
+   so the corresponding diagonal element of the matrix can eb set to 1.0
+   without affecting the output value (which will always be zero since
+   zero times anything is zero). Loop over all inputs. */
+   for( i = 0; i < n; i++ ) {
+
+/* Assume this input has no effect on any output. */
+      unused = 1;
+
+/* Loop over all outputs. */
+      for( j = 0; j < n; j++ ) {
+
+/* If the corresponding matrix term is non-zero, the the input will have
+   an effect on the output, so set the unused flag false and break out of
+   the output loop. */
+         if( fullmat[ i ][ j ] != 0.0 ) {
+            unused = 0;
+            break;
+         }
+      }
+
+/* If the input is unused, and it is associated with a degenerate pixel
+   axis, we can set the corresponding diagonal element of the matrix to
+   1.0. */
+      if( unused && dim[ i ] == 1.0 ) fullmat[ i ][ i ] = 1.0;
+   }
+}
+#if defined(THREAD_SAFE)
+
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstFitsChan *this;         /* Pointer to FitsChan structure */
+   int result;                /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NUL. */
+   if( ! this_object ) return result;
+
+/* Obtain a pointers to the FitsChan structure. */
+   this = (AstFitsChan *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->keyseq, mode, extra, fail );
+   if( !result ) result = astManageLock( this->keywords, mode, extra, fail );
+   return result;
+}
+#endif
+
+static int Match( const char *test, const char *temp, int maxfld, int *fields,
+                  int *nfld, const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     Match
+
+*  Purpose:
+*     Sees if a test keyword name matches a template.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int Match( const char *test, const char *temp, int maxfld, int *fields,
+*                int *nfld, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     All characters in the template other than "%" (and the field width
+*     and type specifiers which follow a "%") must be matched by an
+*     identical character (ignoring case) in the test string. If a "%" occurs
+*     in the template, then the next character in the template should be a
+*     single digit specifying a field width. If it is zero, then the test
+*     string may contain zero or more matching characters. Otherwise,
+*     the test string must contain exactly the specified number of matching
+*     characters (i.e. 1 to 9). The field width digit may be omitted, in
+*     which case the test string must contain one or more matching
+*     characters. The next character in the template specifies the type of
+*     matching characters and must be one of "d", "c" or "f". Decimal digits
+*     are matched by "d", all upper (but not lower) case alphabetical
+*     characters are matched by "c", and all characters which are legal within
+*     a FITS keyword (i.e. upper case letters, digits, underscores and
+*     hyphens) are matched by "f".
+
+*  Parameters:
+*     test
+*        Pointer to a null terminated string holding the keyword name to
+*        be tested.
+*     temp
+*        Pointer to a null terminated string holding the template.
+*     maxfld
+*        The maximum number of integer field values which should be
+*        returned in "fields".
+*     fields
+*        A pointer to an array of at least "maxfld" integers. This is
+*        returned holding the values of any integer fields specified
+*        in the template. The values are extracted from the test string,
+*        and stored in the order they appear in the template string.
+*     nfld
+*        Pointer to a location at which is returned the total number of
+*        integer fields in the test string. This may be more than the
+*        number returned in "fields" if "maxfld" is smaller than "*nfld".
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero is returned if the test string does not match the template
+*     string, and one is returned if it does.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS     /* Declare the thread specific global data */
+   char type;             /* Field type specifier */
+   const char *a;         /* Pointer to next test character */
+   const char *b;         /* Pointer to next template character */
+   int extend;            /* Can the width of the first field be extended? */
+   int i;                 /* Field index */
+   int match;             /* Does "test" match "temp"? */
+   int nfret;             /* No. of fields returned */
+   int tmp;               /* Field value */
+
+/* Check global status. */
+   if( !astOK ) return 0;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* On the first entry to this function, indicate that no integer fields
+   have yet been returned, and save a pointer to the start of the template
+   string. */
+   if( !match_nentry ) {
+      *nfld = 0;
+      match_template = temp;
+   }
+
+/* Increment the number of entries into this function. */
+   match_nentry++;
+
+/* Initialise pointers to the start of each string. */
+   a = test;
+   b = temp;
+
+/* Initialise the returned flag to indicate that the two strings do not
+   match. */
+   match = 0;
+
+/* Check that the initial part of the test string can match the first
+   field in the template. */
+   if( MatchFront( a, b, &type, &extend, &match_na, &match_nb, method, class, match_template, status ) ){
+
+/* If it does, increment the pointers to skip over the characters
+   used up in the comparison. */
+      a += match_na;
+      b += match_nb;
+
+/* If the ends of both strings have been reached, they match. */
+      if( *a == 0 && *b == 0 ){
+         match = 1;
+
+/* Otherwise, if the end of the template has been reached but there are
+   still characters to be read from the test string, we could still have
+   a match if all the remaining test characters match an extandable field. */
+      } else if( *b == 0 && *a != 0 && extend ){
+
+/* Loop until all the matching characters have been read from the end of
+   the test string. */
+         while( *a != 0 && MatchChar( *a, type, method, class, match_template, status ) ) a++;
+
+/* If we reached the end of the test string, we have a match. */
+         if( *a == 0 ) match = 1;
+
+/* Otherwise, we need to carry on checking the remaining fields. */
+      } else {
+
+/* Call this function recursively to see if the remainder of the
+   strings match. */
+         if( Match( a, b, maxfld, fields, nfld, method, class, status ) ){
+            match = 1;
+
+/* If the remainder of the strings do not match, we may be able to make
+   them match by using up some extra test characters on the first field.
+   This can only be done if the first field has an unspecified field width,
+   and if the next test character if of a type which matches the first
+   field in the template. */
+         } else if( extend ){
+
+/* Loop until all the suitable characters have been read from the
+   test string. Break out of the loop early if we find a field width
+   which results in the whole string matching. */
+            while( MatchChar( *a, type, method, class, match_template, status ) ){
+               a++;
+               if( Match( a, b, maxfld, fields, nfld, method, class, status ) ){
+                  match = 1;
+                  break;
+               }
+            }
+         }
+      }
+   }
+
+/* If the strings match and the leading field is an integer, decode
+   the field and store it in the supplied array (if there is room). */
+   if( match && type == 'd' && a > test ){
+      if( *nfld < maxfld ){
+         sprintf( match_fmt, "%%%dd", (int) ( a - test ) );
+         astSscanf( test, match_fmt, fields + *nfld );
+      }
+      (*nfld)++;
+   }
+
+/* Decrement the number of entries into this function. */
+   match_nentry--;
+
+/* If we are leaving this function for the last time, reverse the
+   order of the returned integer fields so that they are returned
+   in the same order that they occur in the template. */
+   if( !match_nentry ){
+      nfret = ( *nfld < maxfld ) ? (*nfld) : maxfld;
+      match_pa = fields;
+      match_pb = fields + nfret - 1;
+      for( i = 0; i < nfret/2; i++ ){
+         tmp = *match_pa;
+         *(match_pa++) = *match_pb;
+         *(match_pb--) = tmp;
+      }
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static int MatchChar( char test, char type, const char *method,
+                      const char *class, const char *template, int *status ){
+/*
+*  Name:
+*     MatchChar
+
+*  Purpose:
+*     See if a given character is of a specified type.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int MatchChar( char test, char type, const char *method,
+*                    const char *class, const char *template, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function checks that the supplied test character belongs
+*     to the set of characters specified by the parameter "type".
+
+*  Parameters:
+*     test
+*        The character to test.
+*     type
+*        The character specifying the set of acceptable characters. This
+*        should be one of the field type characters accepted by function
+*        Match (e.g. "d", "c" or "f").
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     template
+*        Pointer to the start of the whole template string, for use in error
+*        messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero is returned if the test character does not belongs to the
+*     specified character set, and one is returned if it does.
+
+*  Notes:
+*     -  An error is reported if the type specifier is not legal.
+*     -  Zero is returned if an error has already occurred, or if ths
+*     function fails for any reason.
+*/
+
+/* Local Variables: */
+   int ret;            /* Returned flag */
+
+/* Check global status. */
+   ret = 0;
+   if( !astOK ) return ret;
+
+/* Check for "d" specifiers (digits). */
+   if( type == 'd' ){
+      ret = isdigit( (int) test );
+
+/* Check for "c" specifiers (upper case letters). */
+   } else if( type == 'c' ){
+      ret = isupper( (int) test );
+
+/* Check for "s" specifiers (any legal FITS keyword character). */
+   } else if( type == 'f' ){
+      ret = isFits( (int) test );
+
+/* Report an error for any other specifier. */
+   } else if( astOK ){
+      ret = 0;
+      astError( AST__BDFMT, "%s(%s): Illegal field type or width "
+                "specifier '%c' found in filter template '%s'.", status,
+                method, class, type, template );
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static int MatchFront( const char *test, const char *temp, char *type,
+                       int *extend, int *ntest, int *ntemp,
+                       const char *method, const char *class,
+                       const char *template, int *status ){
+/*
+*  Name:
+*     MatchFront
+
+*  Purpose:
+*     Sees if the start of a test string matches the start of a template.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int MatchFront( const char *test, const char *temp, char *type,
+*                     int *extend, int *ntest, int *ntemp,
+*                     const char *method, const char *class,
+*                     const char *template )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function looks for a match between the first field in the
+*     template string and the string at the start of the test string,
+*     using the syntax described in function Match.
+
+*  Parameters:
+*     test
+*        Pointer to a null terminated string holding the keyword name to
+*        be tested.
+*     temp
+*        Pointer to a null terminated string holding the template.
+*     type
+*        Pointer to a location at which to return a character specifying the
+*        sort of field that was matched. This will be one of the legal field
+*        types accepted by Match (e.g. "d", "c" or "f"), or null (zero) if
+*        the first field in the template string was a literal character (i.e.
+*        did not start with a "%").
+*     extend
+*        Pointer to a location at which to return a flag which will be non-zero
+*        if the further test characters could be matched by the first field in
+*        the template. This will be the case if the template field only
+*        specifies a minimum number of matching characters (i.e. if the field
+*        width can be extended). For instance, "%d" can be extended, but "%1d"
+*        cannot.
+*     ntest
+*        Pointer to a location at which to return the number of characters
+*        matched in the test string. This will be the minimum number allowed
+*        by the template field.
+*     ntemp
+*        Pointer to a location at which to return the number of characters
+*        read from the template string (i.e. the number of characters in the
+*        field specification).
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     template
+*        Pointer to the start of the whole template string, for use in error
+*        messages.
+
+*  Returned Value:
+*     Zero is returned if the test string starts with fewer than the
+*     minimum number of characters matching the template string, and one
+*     is returned if it does.
+
+*  Notes:
+*     -  Zero is returned if an error has already occurred, or if this
+*     function fails for any reason.
+*/
+
+/* Local Variables: */
+   const char *a;     /* Pointer to next test character */
+   const char *b;     /* Pointer to next template character */
+   int i;             /* Character index */
+   int match;         /* Does "test" match "temp"? */
+
+/* Check global status. */
+   if( !astOK ) return 0;
+
+/* Initialise pointers to the start of each string. */
+   a = test;
+   b = temp;
+
+/* Initialise the returned value to indicate that the strings match. */
+   match = 1;
+
+/* If the current character in the template is not a % sign, it must
+   match the current character in the test string (except for case). */
+   if( *b != '%' ){
+      if( toupper( (int) *b ) != toupper( (int) *a ) ) {
+         match = 0;
+
+/* If the characters match, return all the required information. */
+      } else {
+         *type = 0;
+         *extend = 0;
+         *ntest = 1;
+         *ntemp = 1;
+      }
+
+/* If the current character of the template is a %, we need to match
+   a field. */
+   } else {
+      *ntemp = 3;
+
+/* The next character in the template string determines the field width.
+   Get the lowest number of characters which must match in the test string,
+   and set a flag indicating if this lowest limit can be extended. */
+      b++;
+      if( *b == '0' ){
+         *ntest = 0;
+         *extend = 1;
+      } else if( *b == '1' ){
+         *ntest = 1;
+         *extend = 0;
+      } else if( *b == '2' ){
+         *ntest = 2;
+         *extend = 0;
+      } else if( *b == '3' ){
+         *ntest = 3;
+         *extend = 0;
+      } else if( *b == '4' ){
+         *ntest = 4;
+         *extend = 0;
+      } else if( *b == '5' ){
+         *ntest = 5;
+         *extend = 0;
+      } else if( *b == '6' ){
+         *ntest = 6;
+         *extend = 0;
+      } else if( *b == '7' ){
+         *ntest = 7;
+         *extend = 0;
+      } else if( *b == '8' ){
+         *ntest = 8;
+         *extend = 0;
+      } else if( *b == '9' ){
+         *ntest = 9;
+         *extend = 0;
+
+/* If no field width was given, one or more test characters are matched.
+   Step back a character so that the current character will be re-used as
+   the type specifier. */
+      } else {
+         *ntest = 1;
+         *extend = 1;
+         b--;
+         (*ntemp)--;
+      }
+
+/* The next template character gives the type of character which should
+   be matched. */
+      b++;
+      *type = *b;
+
+/* Report an error if the template string ended within the field
+   specifier. */
+      if( !*b ){
+         match = 0;
+         astError( AST__BDFMT, "%s(%s): Incomplete field specifier found "
+                   "at end of filter template '%s'.", status, method, class,
+                   template );
+
+/* Otherwise, check that the test string starts with the minimum allowed
+   number of characters matching the specified type. */
+      } else {
+         for( i = 0; i < *ntest; i++ ){
+            if( !MatchChar( *a, *type, method, class, template, status ) ){
+               match = 0;
+               break;
+            }
+            a++;
+         }
+      }
+   }
+
+/* Return the answer. */
+   return match;
+}
+
+static void MarkCard( AstFitsChan *this, int *status ){
+
+/*
+*  Name:
+*     MarkCard
+
+*  Purpose:
+*     Mark the current card as having been read into an AST object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void MarkCard( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The current card is marked as having been "provisionally used" in
+*     the construction of an AST object. If the Object is constructed
+*     succesfully, such cards are marked as having been definitely used,
+*     and they are then considered to have been removed from the FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan containing the list of cards.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  The card remains the current card even though it is now marked
+*     as having been read.
+*/
+   int flags;
+
+/* Return if the global error status has been set, or the current card
+   is not defined. */
+   if( !astOK || !this->card ) return;
+
+/* Set the PROVISIONALLY_USED flag in the current card, but only if the
+   PROTECTED flag is not set. */
+   flags = ( (FitsCard *) this->card )->flags;
+   if( !( flags & PROTECTED ) ) {
+      ( (FitsCard *) this->card )->flags = flags | PROVISIONALLY_USED;
+   }
+}
+
+static int MoveCard( AstFitsChan *this, int move, const char *method,
+                      const char *class, int *status ){
+
+/*
+*  Name:
+*     MoveCard
+
+*  Purpose:
+*     Move the current card a given number of cards forward or backwards.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int MoveCard( AstFitsChan *this, int move, const char *method,
+*                    const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The current card is increment by the given number of cards, ignoring
+*     cards which have been read into an AST object if the ignore_used flag
+*     is set non-zero.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan containing the list of cards.
+*     move
+*        The number of cards by which to move the current card. Positive
+*        values move towards the end-of-file. Negative values move
+*        towards the start of the file (i.e. the list head).
+*     method
+*        Pointer to string holding name of calling method.
+*     class
+*        Pointer to string holding object class.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of cards actually moved. This may not always be equal to
+*     the requested number (for instance, if the end or start of the
+*     FitsChan is encountered first).
+
+*  Notes:
+*     -  If the end-of-file is reached before the required number of
+*     cards have been skipped, the current card is set NULL, to indicate
+*     an end-of-file condition.
+*     -  If the start of the file is reached before the required number of
+*     cards have been skipped, the current card is left pointing to the
+*     first usable card.
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Declare the thread specific global data */
+   FitsCard *card;         /* The current card */
+   FitsCard *card0;        /* The previous non-deleted card */
+   int moved;              /* The number of cards moved by so far */
+
+/* Return if the supplied object is NULL or the FitsChan is
+   empty, or zero movement is requested. */
+   if( !this || !this->head || !move ) return 0;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Get a pointer to the current card. */
+   card = (FitsCard *) this->card;
+
+/* Initialise the number of cards moved so far. */
+   moved = 0;
+
+/* First deal with positive movements (towards the end-of-file). */
+   if( move > 0 ){
+
+/* Loop round moving on to the next card until the correct number of
+   moves have been made, or the end-of-file is reached. */
+      while( moved < move && card ){
+
+/* Get a pointer to the next card in the list, reporting an error if the
+   links are inconsistent. */
+         card = GetLink( card, NEXT, method, class, status );
+
+/* If we have moved past the last card and are now pointing back at the
+   list head, then indicate that we are at end-of-file by setting the
+   card pointer NULL. */
+         if( (void *) card == this->head ){
+            card = NULL;
+
+/* Otherwise, increment the number of cards moved. We ignore cards which
+   have been read into an AST object if the external "ignore_used" flag is
+   set. */
+         } else if( card ){
+            if( !CARDUSED(card) ) moved++;
+         }
+      }
+
+/* Now deal with negative movements (towards the list head), so long as
+   we are not currently at the list head. */
+   } else if( (void *) card != this->head ){
+
+/* If we are currently at end-of-file, replace the NULL pointer for the
+   current card with a pointer to the list head. The first step backwards
+   will make the last card the current card. */
+      if( !card ) card = (FitsCard *) this->head;
+
+/* Loop round until the correct number of cards have been moved. */
+      while( moved < -move && card ){
+
+/* If cards which have been read into an AST object are to be included in the
+   count of moved cards, get a pointer to the previous card in the list,
+   reporting an error if the links are inconsistent. */
+         if( !ignore_used ){
+            card = GetLink( card, PREVIOUS, method, class, status );
+
+/* If cards which have been read into an AST object are to be ignored... */
+         } else {
+
+/* We need to find the previous card which has not been read into an AST
+   object. We do not search beyond the start of the list. */
+            card0 = GetLink( card, PREVIOUS, method, class, status );
+            while( card0 && CARDUSED(card0) && (void *) card0 != this->head ){
+               card0 = GetLink( card0, PREVIOUS, method, class, status );
+            }
+
+/* If no such card was found we leave the card where it is. */
+            if( card0 && ( card0->flags & USED ) ) {
+               break;
+
+/* Otherwise, move back to card found above. */
+            } else {
+               card = card0;
+            }
+         }
+
+/* Increment the number of cards moved. */
+         moved++;
+
+/* If the current card is the list head, break out of the loop. */
+         if( (void *) card == this->head ) break;
+      }
+   }
+
+/* Store the new current card. */
+   this->card = (void *) card;
+
+/* Return the answer. */
+   return moved;
+}
+
+static double NearestPix( AstMapping *map, double val, int axis, int *status ){
+/*
+*  Name:
+*     NearestPix
+
+*  Purpose:
+*     Find an axis value which corresponds to an integer pixel value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     double NearestPix( AstMapping *map, double val, int axis, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The supplied axis value is transformed using the inverse of the
+*     supplied Mapping (other axes are given the value AST__BAD). The
+*     resulting axis values are rounded to the nearest whole number, and
+*     then transformed back using the supplied Mapping in the forward
+*     direction. If the nominated axis value is good, it is returned as
+*     the function value, otherwise the supplied value is returned unchanged.
+
+*  Parameters:
+*     map
+*        A Mapping (usually the input coordinates will correspond to
+*        pixel coordinates).
+*     val
+*        A value for one of the outputs of the "map" Mapping.
+*     axis
+*        The index of the Mapping output to which "val" refers.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Retuned Value:
+*     The modified output axis value.
+*/
+
+/* Local Variables: */
+   AstMapping *tmap;       /* Mapping to be used */
+   AstPointSet *pset1;     /* Pixel coords PointSet */
+   AstPointSet *pset2;     /* WCS coords PointSet */
+   double **ptr1;          /* Pointer to data in pset1 */
+   double **ptr2;          /* Pointer to data in pset2 */
+   double result;          /* Returned value */
+   int *ins;               /* Array holding input axis indices */
+   int i;                  /* Loop count */
+   int nin;                /* Number of Mapping inputs */
+   int nout;               /* Number of Mapping outputs */
+
+/* Initialise. */
+   result = val;
+
+/* Check inherited status, and that the supplied value is good. */
+   if( !astOK || result == AST__BAD ) return result;
+
+/* If the supplied Mapping has no inverse, trying splitting off the
+   transformation for the required axis, which may have an inverse.
+   If succesful, use the 1-in,1-out Mapping returned by astMapSPlit
+   instead of the supplied Mapping, and adjust the axis index accordingly. */
+   if( !astGetTranInverse( map ) ) {
+      astInvert( map );
+      ins = astMapSplit( map, 1, &axis, &tmap );
+      if( tmap ) {
+         astInvert( tmap );
+         axis = 0;
+      } else {
+         tmap = astClone( map );
+      }
+      ins = astFree( ins );
+      astInvert( map );
+   } else {
+      tmap = astClone( map );
+   }
+
+/* If the Mapping still has no inverse, return the supplied value
+   unchanged. */
+   if( astGetTranInverse( tmap ) ) {
+
+/* Get the number of input and output coordinates. */
+      nin = astGetNin( tmap );
+      nout = astGetNout( tmap );
+
+/* Create PointSets to hold a single input position and the corresponding
+   output position. */
+      pset1 = astPointSet( 1, nin, "", status );
+      ptr1 = astGetPoints( pset1 );
+      pset2 = astPointSet( 1, nout, "", status );
+      ptr2 = astGetPoints( pset2 );
+      if( astOK ) {
+
+/* Assign AST__BAD values to all output axes, except for the specified
+   axis, which is given the supplied axis value. */
+         for( i = 0; i < nout; i++ ) ptr2[ i ][ 0 ] = AST__BAD;
+         ptr2[ axis ][ 0 ] = val;
+
+/* Transform this output position into an input position. */
+         (void) astTransform( tmap, pset2, 0, pset1 );
+
+/* Round all good axis values in the resulting input position to the nearest
+   integer. */
+         for( i = 0; i < nin; i++ ) {
+            if( ptr1[ i ][ 0 ] != AST__BAD ) {
+               ptr1[ i ][ 0 ] = (int) ( ptr1[ i ][ 0 ] + 0.5 );
+            }
+         }
+
+/* Transform this input position back into output coords. */
+         (void) astTransform( tmap, pset1, 1, pset2 );
+
+/* If the resulting axis value is good, return it. */
+         if( ptr2[ axis ] [ 0 ] != AST__BAD ) result = ptr2[ axis ] [ 0 ];
+      }
+
+/* Free resources. */
+      pset1 = astAnnul( pset1 );
+      pset2 = astAnnul( pset2 );
+   }
+   tmap = astAnnul( tmap );
+
+/* Return the result. */
+   return result;
+}
+
+static void NewCard( AstFitsChan *this, const char *name, int type,
+                     const void *data, const char *comment, int flags,
+                     int *status ){
+
+/*
+*  Name:
+*     NewCard
+
+*  Purpose:
+*     Insert a new card in front of the current card.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void NewCard( AstFitsChan *this, const char *name, int type,
+*                   const void *data, const char *comment, int flags,
+*                   int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The supplied keyword name, data type and value, and comment are
+*     stored in a new FitsCard structure, and this structure is
+*     inserted into the circular linked list stored in the supplied
+*     FitsChan. It is inserted in front of the current card.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan containing the list of cards.
+*     name
+*        Pointer to a string holding the keyword name of the new card.
+*     type
+*        An integer value representing the data type of the keyword.
+*     data
+*        Pointer to the data associated with the keyword.
+*     comment
+*        Pointer to a null-terminated string holding a comment.
+*     flags
+*        The flags to assign to the card.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  The new card is inserted into the list in front of the current card,
+*     so that the "next" link from the new card points to the current card.
+*     If the FitsChan is currently at end-of-file (indicated by a NULL
+*     pointer being stored for the current card), then the card is appended
+*     to the end of the list. The pointer to the current card is left
+*     unchanged.
+*     -  Keyword names are converted to upper case before being stored.
+*     -  Any trailing white space in a string value is saved as supplied.
+*     -  Logical values are converted to zero or one before being stored.
+*     -  The "comment" and/or "data" pointers may be supplied as NULL.
+*/
+
+/* Local Variables: */
+   FitsCard *new;             /* Pointer to the new card */
+   FitsCard *prev;            /* Pointer to the previous card in the list */
+   char *b;                   /* Pointer to next stored character */
+   const char *a;             /* Pointer to next supplied character */
+   int lval;                  /* Logical data value restricted to 0 or 1 */
+   int nc;                    /* No. of characters to store */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get memory to hold the new FitsCard structure. */
+   new = (FitsCard *) astMalloc( sizeof( FitsCard ) );
+
+/* Check the pointer can be used. */
+   if( astOK ){
+
+/* Copy the keyword name, converting to upper case. */
+      a = name;
+      b = new->name;
+      while( *a ) *(b++) = (char) toupper( (int) *(a++) );
+      *b = 0;
+
+/* Ensure that a KeyMap exists to hold the keywords currently in the
+   FitsChan. */
+      if( !this->keywords ) this->keywords = astKeyMap( " ", status );
+
+/* Add the keyword name to the KeyMap. The value associated with the
+   KeyMap entry is not used and is set arbitrarily to zero. */
+      astMapPut0I( this->keywords, new->name, 0, NULL );
+
+/* Copy the data type. */
+      new->type = type;
+
+/* Copy any data (ignore any data supplied for an UNDEF value). */
+      if( data && type != AST__UNDEF ){
+
+/* Logical values are converted to zero or one before being stored. */
+         if( type == AST__LOGICAL ){
+            lval = *( (int *) data ) ? 1 : 0;
+            new->size = sizeof( int );
+            new->data = astStore( NULL, (void *) &lval, sizeof( int ) );
+
+/* String values... */
+         } else if( type == AST__STRING || type == AST__CONTINUE ){
+
+/* Find the number of characters excluding the trailing null character. */
+            nc = strlen( data );
+
+/* Store the string, reserving room for a terminating null. */
+            new->size = (size_t)( nc + 1 );
+            new->data = astStore( NULL, (void *) data, (size_t)( nc + 1 ) );
+
+/* Terminate it. */
+            ( (char *) new->data)[ nc ] = 0;
+
+/* Other types are stored as supplied. */
+         } else if( type == AST__INT ){
+            new->size = sizeof( int );
+            new->data = astStore( NULL, (void *) data, sizeof( int ) );
+         } else if( type == AST__FLOAT ){
+            new->size = sizeof( double );
+            new->data = astStore( NULL, (void *) data, sizeof( double ) );
+         } else if( type == AST__COMPLEXF ){
+            if( *( (double *) data ) != AST__BAD ) {
+               new->size = 2*sizeof( double );
+               new->data = astStore( NULL, (void *) data, 2*sizeof( double ) );
+            } else {
+               nc = strlen( BAD_STRING );
+               new->size = (size_t)( nc + 1 );
+               new->data = astStore( NULL, BAD_STRING, (size_t)( nc + 1 ) );
+               ( (char *) new->data)[ nc ] = 0;
+            }
+         } else if( type == AST__COMPLEXI ){
+            new->size = 2*sizeof( int );
+            new->data = astStore( NULL, (void *) data, 2*sizeof( int ) );
+         } else {
+            new->size = 0;
+            new->data = NULL;
+         }
+      } else {
+         new->size = 0;
+         new->data = NULL;
+      }
+
+/* Find the first non-blank character in the comment, and find the used
+   length of the remaining string. We retain leading and trailing white
+   space if the card is a COMMENT card. */
+      if( comment ){
+         a = comment;
+         if( type != AST__COMMENT ) {
+            while( isspace( *a ) ) a++;
+            nc = ChrLen( a, status );
+         } else {
+            nc = strlen( a );
+         }
+      } else {
+         nc = 0;
+      }
+
+/* Copy any comment, excluding leading and trailing white space unless
+   this is a COMMENT card */
+      if( nc > 0 ){
+         new->comment = astStore( NULL, (void *) a, (size_t)( nc + 1 ) );
+         ( (char *) new->comment)[ nc ] = 0;
+      } else {
+         new->comment = NULL;
+      }
+
+/* Set the supplied flag values. */
+      new->flags = flags;
+
+/* Insert the copied card into the list, in front of the current card. If
+   the current card is the list head, make the new card the list head. */
+      if( this->card ){
+         prev = ( ( FitsCard *) this->card )->prev;
+         ( ( FitsCard *) this->card )->prev = new;
+         new->prev = prev;
+         prev->next = new;
+         new->next = (FitsCard *) this->card;
+         if( this->card == this->head ) this->head = (void *) new;
+
+/* If the FitsChan is at end-of-file, append the new card to the end of
+   the list (i.e. insert it just before the list head). */
+      } else {
+         if( this->head ){
+            prev = ( (FitsCard *) this->head )->prev;
+            ( (FitsCard *) this->head )->prev = new;
+            new->prev = prev;
+            prev->next = new;
+            new->next = (FitsCard *) this->head;
+
+/* If there are no cards in the list, start a new list. */
+         } else {
+            new->prev = new;
+            new->next = new;
+            this->head = (void *) new;
+            this->card = NULL;
+         }
+      }
+   }
+
+/* Return. */
+   return;
+}
+
+static AstMapping *NonLinSpecWcs( AstFitsChan *this, char *algcode,
+                                  FitsStore *store, int i, char s,
+                                  AstSpecFrame *specfrm, const char *method,
+                                  const char *class, int *status ) {
+
+/*
+*  Name:
+*     NonLinSpecWcs
+
+*  Purpose:
+*     Create a Mapping describing a FITS-WCS non-linear spectral algorithm
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     AstMapping *NonLinSpecWcs( AstFitsChan *this, char *algcode,
+*                                FitsStore *store, int i, char s,
+*                                AstSpecFrame *specfrm, const char *method,
+*                                const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function uses the contents of the supplied FitsStore to create
+*     a Mapping which goes from Intermediate World Coordinate (known as "w"
+*     in the context of FITS-WCS paper III) to the spectral system
+*     described by the supplied SpecFrame.
+*
+*     The returned Mapping implements the non-linear "X2P" algorithms
+*     described in FITS-WCS paper III. The axis is linearly sampled in
+*     system "X" but expressed in some other system (specified by the
+*     supplied SpecFrame).
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     algcode
+*        Pointer to a string holding the non-linear "-X2P" code for the
+*        required algorithm. This includes aleading "-" character.
+*     store
+*        Pointer to the FitsStore structure holding the values to use for
+*        the WCS keywords.
+*     i
+*        The zero-based index of the spectral axis within the FITS header
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     specfrm
+*        Pointer to the SpecFrame. This specified the "S" system - the
+*        system in which the CRVAL kewyords (etc) are specified.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a Mapping, or NULL if an error occurs.
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;
+   AstMapping *map1;
+   AstMapping *ret;
+   AstSpecFrame *xfrm;
+   AstMapping *map2;
+   char buf[ 100 ];
+   char pc;
+   double crv;
+   double ds;
+   double in_a;
+   double in_b;
+   double out_a;
+   double out_b;
+   int ok;
+   int s_sys;
+
+/* Check the global status. */
+   ret = NULL;
+   if( !astOK ) return ret;
+
+/* Identify the spectral "X" system within the "X2P" algorithm code, and
+   create a SpecFrame describing the X system ("X" is the system in
+   which the axis is linearly sampled). This is done by copying the
+   supplied SpecFrame and then setting its System attribute. Copying
+   the supplied SpecFrame ensures that all the other attributes (RestFreq,
+   etc.) are set correctly. */
+   ok = 1;
+   xfrm = astCopy( specfrm );
+   if( algcode[ 1 ] == 'F' ) {
+      astSetSystem( xfrm, AST__FREQ );
+      astSetUnit( xfrm, 0, "Hz" );
+   } else if( algcode[ 1 ] == 'W' ) {
+      astSetSystem( xfrm, AST__WAVELEN );
+      astSetUnit( xfrm, 0, "m" );
+   } else if( algcode[ 1 ] == 'V' ) {
+      astSetSystem( xfrm, AST__VREL );
+      astSetUnit( xfrm, 0, "m/s" );
+   } else if( algcode[ 1 ] == 'A' ) {
+      astSetSystem( xfrm, AST__AIRWAVE );
+      astSetUnit( xfrm, 0, "m" );
+   } else {
+      ok = 0;
+   }
+
+/* If the X system was identified, find a Mapping from the "S" (specfrm)
+   system to the X system. */
+   map1 = NULL;
+   if( ok ) {
+      ok = 0;
+      fs = astConvert( specfrm, xfrm, "" );
+      if( fs ) {
+         map1 = astGetMapping( fs, AST__BASE, AST__CURRENT );
+         fs = astAnnul( fs );
+         ok = 1;
+      }
+
+/* Issue a warning if the "P" system is not the correct one for the given
+   "S" system. We can however continue, sine AST interprets illegal "P"
+   systems correctly. */
+      pc = 0;
+      s_sys = astGetSystem( specfrm );
+      if( s_sys == AST__FREQ || s_sys == AST__ENERGY ||
+          s_sys == AST__WAVENUM ||  s_sys == AST__VRADIO ) {
+         pc = 'F';
+      } else if( s_sys == AST__WAVELEN || s_sys == AST__VOPTICAL ||
+                 s_sys == AST__REDSHIFT ){
+         pc = 'W';
+      } else if( s_sys == AST__AIRWAVE ) {
+         pc = 'A';
+      } else if( s_sys == AST__BETA || s_sys == AST__VREL ) {
+         pc = 'V';
+      } else if( astOK ) {
+         pc = algcode[ 3 ];
+         astError( AST__INTER, "%s: Function NonLinSpecWcs does not yet "
+                   "support spectral axes of type %s (internal AST "
+                   "programming error).", status, method, astGetC( specfrm, "System" ) );
+      }
+      if( algcode[ 3 ] != pc ) {
+         sprintf( buf, "The spectral CTYPE value %s%s is not legal - "
+                 "using %s%.3s%c instead.", astGetC( specfrm, "System" ),
+                 algcode,  astGetC( specfrm, "System" ), algcode, pc );
+         Warn( this, "badctype", buf, method, class, status );
+      }
+   }
+
+/* If succesfull, use this Mapping to find the reference value (CRVAL)
+   in the "X" system. */
+   if( ok ) {
+
+/* Get the CRVAL value for the spectral axis (this will be in the S system). */
+      crv = GetItem( &(store->crval), i, 0, s, NULL, method, class, status );
+      if( crv == AST__BAD ) crv = 0.0;
+
+/* Convert it to the X system. */
+      astTran1( map1, 1, &crv, 1, &crv );
+
+/* Invert this Mapping so that it forward transformation goes from X to S. */
+      astInvert( map1 );
+
+/* Find the rate of change of S with respect to X (dS/dX) at the reference
+   point (x = crv). */
+      ds = astRate( map1, &crv, 0, 0 );
+      if( ds != AST__BAD && ds != 0.0 ) {
+
+/* FITS-WCS paper III says that dS/dw must be 1.0 at the reference point.
+   Therefore dX/dw = dX/dS at the reference point. Also, since the spectral
+   axis is linear in X, dX/dw must be constant. Therefore the Mapping from
+   IWC to X is a WinMap which scales the IWC axis ("w") by dX/dw and adds
+   on the X value at the reference point. */
+         if( crv != 0.0 ) {
+            in_a = 0.0;
+            out_a = crv;
+            in_b = crv*ds;
+            out_b = 2.0*crv;
+            map2 = (AstMapping *) astWinMap( 1, &in_a, &in_b, &out_a, &out_b, "", status );
+         } else {
+            map2 = (AstMapping *) astZoomMap( 1, 1.0/ds, "", status );
+         }
+
+/* The Mapping to be returned is the concatenation of the above Mapping
+   (from w to X) with the Mapping from X to S. */
+         ret = (AstMapping *) astCmpMap( map2, map1, 1, "", status );
+         map1 = astAnnul( map1 );
+         map2 = astAnnul( map2 );
+      }
+   }
+   xfrm = astAnnul( xfrm );
+
+/* Return the result */
+   return ret;
+}
+
+static double *OrthVector( int n, int m, double **in, int *status ){
+/*
+*  Name:
+*     OrthVector
+
+*  Purpose:
+*     Find a unit vector which is orthogonal to a set of supplied vectors.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     double *OrthVector( int n, int m, double **in, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A set of M vectors is supplied, each vector being N-dimensional.
+*     It is assumed that M < N and that the supplied vectors span M
+*     axes within the N dimensional space. An N-dimensional unit vector is
+*     returned which is orthogonal to all the supplied vectors.
+*
+*     The required vector is orthogonal to all the supplied vectors.
+*     Therefore the dot product of the required vector with each of the
+*     supplied vectors must be zero. This gives us M equations of the
+
+*     form:
+*
+*     a1*r1 + a2*r2 + a3*r3 + .... + aN*rN = 0.0
+*     b1*r1 + b2*r2 + b3*r3 + .... + bN*rN = 0.0
+*     ...
+*
+*     where (a1,a2,..,aN), (b1,b2,..,bN), ... are the supplied vectors
+*     and (r1,r2,...,rN) is the required vector. Since M is less
+*     than N the system of linear simultaneous equations is under
+*     specified and we need to assign arbitrary values to some of the
+*     components of the required vector in order to allow the equations
+*     to be solved. We arbitrarily assume that 1 element of the required
+*     vector has value 1.0 and (N-M-1) have value zero. The selection of
+*     *which* elements to set constant is based on the magnitudes of the
+*     columns of coefficients (a1,b1...), (a2,b2,...), etc. The M components
+*     of the required vector which are *not* set constant are the ones which
+*     have coefficient columns with the *largest* magnitude. This choice is
+*     made in order to minimise the risk of the remaining matrix of
+*     coefficients being singular (for instance, if a component of the
+*     required vector has a coefficient of zero in every supplied vector
+*     then the column magnitude will be zero and that component will be
+*     set to 1.0). After choosing the M largest columns, the largest
+*     remaining column is assigned a value of 1.0 in the required vector,
+*     and all other columns are assigned the value zero in the required
+
+*     vector. This means that the above equations becomes:
+*
+*     a1*r1 + a2*r2 + a3*r3 + .... + aM*rM = -aM+1
+*     b1*r1 + b2*r2 + b3*r3 + .... + bM*rM = -bM+1
+*     ...
+*
+*     Where the indices are now not direct indices into the supplied and
+*     returned vectors, but indices into an array of indices which have
+*     been sorted into column magnitude order. This is now a set of MxM
+
+*     simultaneous linear equations which we can solve using palDmat:
+*
+*     MAT.R = V
+*
+*     where MAT is the the matrix of columns (coefficients) on the left
+*     hand side of the above set of simultaneous equations, R is the
+*     required vector (just the components which have *not* been set
+*     constant), and V is a constant vector equal to the column of values
+*     on the right hand side in the above set of simultaneous equations.
+*     The palDmat function solves this equation to obtain R.
+
+*  Parameters:
+*     n
+*        The number of dimensions
+*     m
+*        The number of supplied vectors.
+*     in
+*        A pointer to an array with "m" elements, each element being a
+*        pointer to an array with "n" elements. Each of these "n" element
+*        array holds one of the supplied vectors.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The pointer to some newly allocated memory holding the returned N
+*     dimensional unit vector. The memory should be freed using astFree when
+*     no longer needed.
+
+*  Notes:
+*     -  NULL is returned if an error occurs.
+*     -  NULL is returned (without error) if the required vector cannot
+*     be found (.e.g becuase the supplied M vectors span less than M axes).
+*/
+
+/* Local Variables: */
+   double *colmag;
+   double *d;
+   double *e;
+   double *mat;
+   double *mel;
+   double *ret;
+   double *rhs;
+   double det;
+   double sl;
+   int *colperm;
+   int *iw;
+   int done;
+   int i;
+   int ih;
+   int ii;
+   int il;
+   int j;
+   int sing;
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Return if any of the M supplied vectors are NULL. */
+   for( i = 0; i < m; i++ ) {
+      if( !in[ i ] ) return ret;
+   }
+
+/* Allocate rquired memory. */
+   ret = astMalloc( sizeof( double )*(size_t) n );
+   rhs = astMalloc( sizeof( double )*(size_t) m );
+   mat = astMalloc( sizeof( double )*(size_t) m*m );
+   iw = astMalloc( sizeof( int )*(size_t) m );
+   colmag = astMalloc( sizeof( double )*(size_t) n );
+   colperm = astMalloc( sizeof( int )*(size_t) n );
+
+/* Check memory can be used safely. */
+   if( astOK ) {
+
+/* Find the magnitude of each column of coefficients in the full set of
+   simultaneous linear equations (before setting any components of the
+   required vector constant). Also initialise the column permutation array
+   to indicate that the columns are in their original order. The outer
+   loop loops through the columns and the inner loop loops through rows
+   (i.e. equations). */
+      for( i = 0; i < n; i++ ) {
+         colperm[ i ] = i;
+         colmag[ i ] = 0.0;
+         for( j = 0; j < m; j++ ) {
+            colmag[ i ] += in[ j ][ i ]*in[ j ][ i ];
+         }
+      }
+
+/* Now re-arrange the column indices within the permutation array so that
+   they are in order of decreasing ciolumn magnitude (i.e. colperm[0] will
+   be left holding the index of the column with the largest magnitude). A
+   simple bubble sort is used. */
+      ii = 1;
+      done = 0;
+      while( !done ) {
+         done = 1;
+         for( i = ii; i < n; i++ ) {
+            ih = colperm[ i ];
+            il = colperm[ i - 1 ];
+            if( colmag[ ih ] > colmag[ il ] ) {
+               colperm[ i ] = il;
+               colperm[ i - 1 ] = ih;
+               done = 0;
+            }
+         }
+         ii++;
+      }
+
+/* The first M elements in "colperm" now hold the indices of the
+   columns which are to be used within the MAT matrix, the next element
+   of "colperm" hold the index of the column which is to be included in the
+   V vector (other elements hold the indices of the columns which are
+   being ignored because they will be mutiplied by a value of zero - the
+   assumed value of the corresponding components of the returned vector). We
+   now copy the these values into arrays which can be passed to palDmat.
+   First, initialise a pointer used to step through the mat array. */
+      mel = mat;
+
+/* Loop through all the supplied vectors. Get a pointer to the first
+   element of the vector. */
+      for( i = 0; i < m; i++ ) {
+         d = in[ i ];
+
+/* Copy the required M elements of this supplied vector into the work array
+   which will be passed to palDmat. */
+         for( j = 0; j < m; j++ ) *(mel++) = d[ colperm[ j ] ];
+
+/* Put the next right-hand side value into the "rhs" array. */
+         rhs[ i ] = -d[ colperm[ m ] ];
+      }
+
+/* Use palDmat to find the first M elements of the returned array. These
+   are stored in "rhs", over-writing the original right-hand side values. */
+      palDmat( m, mat, rhs, &det, &sing, iw );
+
+/* If the supplied vectors span fewer than M axes, the above call will fail.
+   In this case, annul the returned vector. */
+      if( sing != 0 ) {
+         ret = astFree( ret );
+
+/* If succesful, copy the M elements of the solution vector into the
+   required M elements of the returned vector. Also find the squared length
+   of the vector. */
+      } else {
+         sl = 0.0;
+         e = rhs;
+         for( j = 0; j < m; j++ ) {
+            sl += (*e)*(*e);
+            ret[ colperm[ j ] ] = *(e++);
+         }
+
+/* Put 1.0 into the next element of the returned vector. */
+         sl += 1.0;
+         ret[ colperm[ m ] ] = 1.0;
+
+/* Fill up the rest of the returned vector with zeros. */
+         for( j = m + 1; j < n; j++ ) ret[ colperm[ j ] ] = 0.0;
+
+/* Normalise the returned vector so that it is a unit vector.Also ensure
+   that any zeros are "+0.0" insteasd of "-0.0". */
+         e = ret;
+         sl = sqrt( sl );
+         for( j = 0; j < n; e++,j++ ) {
+            *e /= sl;
+            if( *e == 0.0 ) *e = 0.0;
+         }
+      }
+   }
+
+/* Free workspace. */
+   rhs = astFree( rhs );
+   mat = astFree( mat );
+   iw = astFree( iw );
+   colmag = astFree( colmag );
+   colperm = astFree( colperm );
+
+/* Free the returned vector if an error has occurred. */
+   if( !astOK ) ret = astFree( ret );
+
+/* Return the answer. */
+   return ret;
+}
+
+static double **OrthVectorSet( int n, int m, double **in, int *status ){
+/*
+*  Name:
+*     OrthVectorSet
+
+*  Purpose:
+*     Find a set of mutually orthogonal vectors.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     double **OrthVectorSet( int n, int m, double **in, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A set of M vectors is supplied, each vector being N-dimensional.
+*     It is assumed that the supplied vectors span M axes within the
+*     N dimensional space. A pointer to a set of N vectors is returned.
+*     The first M returned vectors are copies of the M supplied vectors.
+*     The remaining returned vectors are unit vectors chosen to be
+*     orthogonal to all other vectors in the returned set.
+
+*  Parameters:
+*     n
+*        The number of dimensions
+*     m
+*        The number of supplied vectors.
+*     in
+*        A pointer to an array with "m" elements, each element being a
+*        pointer to an array with "n" elements. Each of these "n" element
+*        array holds one of the supplied vectors.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The pointer to some newly allocated memory holding the returned N
+*     vectors. The pointer locates an array of N elements, each of which
+*     is a pointer to an array holding the N elements of a single vector.
+*     The memory (including the inner pointers) should be freed using
+*     astFree when no longer needed.
+
+*  Notes:
+*     -  NULL is returned if an error occurs.
+*     -  NULL is returned (without error) if the required vectors cannot
+*     be found (e.g. becuase the supplied M vectors span less than M axes).
+*/
+
+/* Local Variables: */
+   double **ret;
+   int i;
+   int bad;
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Allocate required memory. */
+   ret = astMalloc( sizeof( double * )*(size_t) n );
+
+/* Check memory can be used safely. */
+   bad = 0;
+   if( astOK ) {
+
+/* Copy the supplied vectors into the returned array. */
+      for( i = 0; i < m; i++ ) {
+         ret[ i ] = astStore( NULL, in[ i ], sizeof( double )*n );
+      }
+
+/* For the remaining vectors, find a vector which is orthogonal to all
+   the vectors currently in the returned set. */
+      for( ; i < n; i++ ) {
+         ret[ i ] = OrthVector( n, i, ret, status );
+         if( !ret[ i ] ) bad = 1;
+      }
+   }
+
+/* Free the returned vectors if an error has occurred. */
+   if( bad || !astOK ) {
+      for( i = 0; ret && i < n; i++ ) ret[ i ] = astFree( ret[ i ] );
+      ret = astFree( ret );
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static AstMapping *OtherAxes( AstFitsChan *this, AstFrameSet *fs, double *dim,
+                              int *wperm, char s, FitsStore *store,
+                              double *crvals, int *axis_done,
+                              const char *method, const char *class,
+                              int *status ){
+
+/*
+*  Name:
+*     OtherAxes
+
+*  Purpose:
+*     Add values to a FitsStore describing unknown axes in a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     AstMapping *OtherAxes( AstFitsChan *this, AstFrameSet *fs, double *dim,
+*                            int *wperm, char s, FitsStore *store,
+*                            double *crvals, int *axis_done,
+*                            const char *method, const char *class,
+*                            int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     FITS WCS keyword values are added to the supplied FitsStore which
+*     describe any as yet undescribed axes in the supplied FrameSet. These
+*     axes are assumed to be linear and to follow the conventions
+*     of FITS-WCS paper I (if in fact they are not linear, then the
+*     grid->iwc mapping determined by MakeIntWorld will not be linear and
+*     so the axes will be rejected).
+*
+*     Note, this function does not store values for keywords which define
+*     the transformation from pixel coords to Intermediate World Coords
+*     (CRPIX, PC and CDELT), but a Mapping is returned which embodies these
+*     values. This Mapping is from the current Frame in the FrameSet (WCS
+*     coords) to a Frame representing IWC. The IWC Frame has the same number
+*     of axes as the WCS Frame which may be greater than the number of base
+*     Frame (i.e. pixel) axes.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     fs
+*        Pointer to the FrameSet. The base Frame should represent FITS pixel
+*        coordinates, and the current Frame should represent FITS WCS
+*        coordinates. The number of base Frame axes should not exceed the
+*        number of current Frame axes.
+*     dim
+*        An array holding the image dimensions in pixels. AST__BAD can be
+*        supplied for any unknwon dimensions.
+*     wperm
+*        Pointer to an array of integers with one element for each axis of
+*        the current Frame. Each element holds the zero-based
+*        index of the FITS-WCS axis (i.e. the value of "i" in the keyword
+*        names "CTYPEi", "CRVALi", etc) which describes the Frame axis.
+*     s
+*        The co-ordinate version character. A space means the primary
+*        axis descriptions. Otherwise the supplied character should be
+*        an upper case alphabetical character ('A' to 'Z').
+*     store
+*        The FitsStore in which to store the FITS WCS keyword values.
+*     crvals
+*        Pointer to an array holding the default CRVAL value for each
+*        axis in the WCS Frame.
+*     axis_done
+*        An array of flags, one for each Frame axis, which indicate if a
+*        description of the corresponding axis has yet been stored in the
+*        FitsStore.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If any axis descriptions were added to the FitsStore, a Mapping from
+*     the current Frame of the supplied FrameSet, to the IWC Frame is returned.
+*     Otherwise, a UnitMap is returned. Note, the Mapping only defines the IWC
+*     transformation for the described axes. Any other (previously
+*     described) axes are passed unchanged by the returned Mapping.
+*/
+
+/* Local Variables: */
+   AstFitsTable *table;    /* Pointer to structure holding -TAB table info */
+   AstFrame *wcsfrm;       /* WCS Frame within FrameSet */
+   AstMapping *axmap;      /* Mapping from WCS to IWC */
+   AstMapping *map;        /* FITS pixel->WCS Mapping */
+   AstMapping *ret;        /* Returned Mapping */
+   AstMapping *tmap0;      /* Pointer to a temporary Mapping */
+   AstMapping *tmap1;      /* Pointer to a temporary Mapping */
+   AstPermMap *pm;         /* PermMap pointer */
+   AstPointSet *pset1;     /* PointSet holding central pixel position */
+   AstPointSet *pset2;     /* PointSet holding reference WCS position */
+   char buf[80];           /* Text buffer */
+   const char *lab;        /* Pointer to axis Label */
+   const char *sym;        /* Pointer to axis Symbol */
+   double **ptr1;          /* Pointer to data for pset1 */
+   double **ptr2;          /* Pointer to data for pset2 */
+   double *lbnd_p;         /* Pointer to array of lower pixel bounds */
+   double *ubnd_p;         /* Pointer to array of upper pixel bounds */
+   double crval;           /* The value for the FITS CRVAL keyword */
+   int *inperm;            /* Pointer to permutation array for input axes */
+   int *outperm;           /* Pointer to permutation array for output axes */
+   int extver;             /* Table version number for -TAB headers */
+   int fits_i;             /* FITS WCS axis index */
+   int i;                  /* Loop count */
+   int iax;                /* WCS Frame axis index */
+   int icolindex;          /* Index of table column holding index vector */
+   int icolmain;           /* Index of table column holding main coord array */
+   int interp;             /* Interpolation method for look-up tables */
+   int log_axis;           /* Is the axis logarithmically spaced? */
+   int nother;             /* Number of axes still to be described */
+   int npix;               /* Number of pixel axes */
+   int nwcs;               /* Number of WCS axes */
+   int tab_axis;           /* Can the axis be described by the -TAB algorithm? */
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Get the number of WCS axes. */
+   nwcs = astGetNaxes( fs );
+
+/* Count the number of WCS axes which have not yet been described. */
+   nother = 0;
+   for( iax = 0; iax < nwcs; iax++ ) {
+      if( ! axis_done[ iax ] ) nother++;
+   }
+
+/* Only proceed if there are some axes to described. */
+   if( nother ) {
+
+/* Get a pointer to the WCS Frame. */
+      wcsfrm = astGetFrame( fs, AST__CURRENT );
+
+/* Get a pointer to the pixel->wcs Mapping. */
+      map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+
+/* Store the number of pixel and WCS axes. */
+      npix = astGetNin( fs );
+      nwcs = astGetNout( fs );
+
+/* Store the upper and lower pixel bounds. */
+      lbnd_p = astMalloc( sizeof( double )*(size_t) npix );
+      ubnd_p = astMalloc( sizeof( double )*(size_t) npix );
+      if( astOK ) {
+         for( iax = 0; iax < npix; iax++ ) {
+            lbnd_p[ iax ] = 1.0;
+            ubnd_p[ iax ] = ( dim[ iax ] != AST__BAD ) ? dim[ iax ] : 500;
+         }
+      }
+
+/* Transform the central pixel coords into WCS coords */
+      pset1 = astPointSet( 1, npix, "", status );
+      ptr1 = astGetPoints( pset1 );
+      pset2 = astPointSet( 1, nwcs, "", status );
+      ptr2 = astGetPoints( pset2 );
+      if( astOK ) {
+         for( iax = 0; iax < npix; iax++ ) {
+            ptr1[ iax ][ 0 ] = ( dim[ iax ] != AST__BAD ) ? floor( 0.5*dim[ iax ] ) : 1.0;
+         }
+         (void) astTransform( map, pset1, 1, pset2 );
+      }
+
+/* Loop round all WCS axes, producing descriptions of any axes which have not
+   yet been described. */
+      for( iax = 0; iax < nwcs && astOK; iax++ ) {
+         if( ! axis_done[ iax ] ) {
+
+/* Get the (one-based) FITS WCS axis index to use for this Frame axis. */
+            fits_i = wperm[ iax ];
+
+/* Use the supplied default CRVAL value. If bad, use the WCS value
+   corresponding to the central pixel found above (if this value is bad,
+   abort). */
+            crval = crvals ? crvals[ iax ] : AST__BAD;
+            if( crval == AST__BAD ) crval = ptr2[ iax ][ 0 ];
+            if( crval == AST__BAD ) {
+               break;
+            } else {
+               SetItem( &(store->crval), fits_i, 0, s, crval, status );
+            }
+
+/* Initialise flags indicating the type of the axis. */
+            log_axis = 0;
+            tab_axis = 0;
+
+/* Get the table version number to use if we end up using the -TAB
+   algorithm. This is the set value of the TabOK attribute (if positive). */
+            extver = astGetTabOK( this );
+
+/* See if the axis is linear. If so, create a ShiftMap which subtracts off
+   the CRVAL value. */
+
+            if( IsMapLinear( map, lbnd_p, ubnd_p, iax, status ) ) {
+               crval = -crval;
+               tmap0 = (AstMapping *) astShiftMap( 1, &crval, "", status );
+               axmap = AddUnitMaps( tmap0, iax, nwcs, status );
+               tmap0 = astAnnul( tmap0 );
+               crval = -crval;
+
+/* If it is not linear, see if it is logarithmic. If the "log" algorithm is
+   appropriate (as defined in FITS-WCS paper III), the supplied Frame (s) is
+   related to pixel coordinate (p) by
+      s = Sr.EXP( a*p - b ). If this
+   is the case, the log of s will be linearly related to pixel coordinates.
+   Test this. If the test is passed a Mapping is returned from WCS to IWC. */
+            } else if( (axmap = LogAxis( map, iax, nwcs, lbnd_p, ubnd_p,
+                                         crval, status ) ) ) {
+               log_axis = 1;
+
+/* If it is not linear or logarithmic, and the TabOK attribute is
+   non-zero, describe it using the -TAB algorithm. */
+            } else if( extver > 0 ){
+
+/* Get any pre-existing FitsTable from the FitsStore. This is the table
+   in which the tabular data will be stored (if the Mapping can be expressed
+   in -TAB form). */
+               if( !astMapGet0A( store->tables, AST_TABEXTNAME, &table ) ) table = NULL;
+
+/* See if the Mapping can be expressed in -TAB form. */
+               tmap0 = IsMapTab1D( map, 1.0, NULL, wcsfrm, dim, iax, fits_i,
+                                   &table, &icolmain, &icolindex, &interp,
+                                   status );
+               if( tmap0 ) {
+                  tab_axis = 1;
+
+/* The values stored in the table index vector are GRID coords. So we
+   need to ensure that IWC are equivalent to GRID coords. So set CRVAL
+   to zero. */
+                  crval = 0.0;
+
+/* Store TAB-specific values in the FitsStore. First the name of the
+   FITS binary table extension holding the coordinate info. */
+                  SetItemC( &(store->ps), fits_i, 0, s, AST_TABEXTNAME, status );
+
+/* Next the table version number. This is the set (positive) value for the
+   TabOK attribute. */
+                  SetItem( &(store->pv), fits_i, 1, s, extver, status );
+
+/* Also store the table version in the binary table header. */
+                  astSetFitsI( table->header, "EXTVER", extver,
+                               "Table version number", 0 );
+
+/* Next the name of the table column containing the main coords array. */
+                  SetItemC( &(store->ps), fits_i, 1, s,
+                            astColumnName( table, icolmain ), status );
+
+/* Next the name of the column containing the index array */
+                  if( icolindex >= 0 ) SetItemC( &(store->ps), fits_i, 2, s,
+                                  astColumnName( table, icolindex ), status );
+
+/* The interpolation method (an AST extension to the published -TAB
+   algorithm, communicated through the QVi_4a keyword). */
+                  SetItem( &(store->pv), fits_i, 4, s, interp, status );
+
+/* Also store the FitsTable itself in the FitsStore. */
+                  astMapPut0A( store->tables, AST_TABEXTNAME, table, NULL );
+
+/* Create the WCS -> IWC Mapping (AST uses grid coords as IWC coords for
+   the -TAB algorithm). First, get a Mapping that combines the TAB axis
+   Mapping( tmap0) in parallel with one or two UnitMaps in order to put
+   the TAB axis at the required index. */
+                  tmap1 = AddUnitMaps( tmap0, iax, nwcs, status );
+
+/* Now get a PermMap that permutes the WCS axes into the FITS axis order. */
+                  inperm = astMalloc( sizeof( double )*nwcs );
+                  outperm = astMalloc( sizeof( double )*nwcs );
+                  if( astOK ) {
+                     for( i = 0; i < nwcs; i++ ) {
+                        inperm[ i ] = wperm[ i ];
+                        outperm[ wperm[ i ] ] = i;
+                     }
+                  }
+                  pm = astPermMap( nwcs, inperm, nwcs, outperm, NULL, "",
+                                   status );
+
+/* Combine these two Mappings in series, to get the Mapping from WCS to
+   IWC. */
+                  axmap = (AstMapping *) astCmpMap( pm, tmap1, 1, " ",
+                                                    status );
+
+/* Free resources. */
+                  inperm = astFree( inperm );
+                  outperm = astFree( outperm );
+                  pm = astAnnul( pm );
+                  tmap0 = astAnnul( tmap0 );
+                  tmap1 = astAnnul( tmap1 );
+               }
+               if( table ) table = astAnnul( table );
+            }
+
+/* If the axis cannot be described by any of the above methods, we
+   pretend it is linear. This will generate a non-linear PIXEL->IWC
+   mapping later (in MakeIntWorld) which will cause the write operation
+   to fail. */
+            if( !axmap ) {
+               crval = -crval;
+               tmap0 = (AstMapping *) astShiftMap( 1, &crval, "", status );
+               axmap = AddUnitMaps( tmap0, iax, nwcs, status );
+               tmap0 = astAnnul( tmap0 );
+               crval = -crval;
+            }
+
+/* Combine the Mapping for this axis in series with those of earlier axes. */
+            if( ret ) {
+               tmap0 = (AstMapping *) astCmpMap( ret, axmap, 1, "", status );
+               (void) astAnnul( ret );
+               ret = tmap0;
+            } else {
+               ret = astClone( axmap );
+            }
+
+/* Get axis label and symbol. */
+            sym =  astGetSymbol( wcsfrm, iax );
+            lab =  astGetLabel( wcsfrm, iax );
+
+/* The axis symbols are taken as the CTYPE values. Append "-LOG" or "-TAB" if
+   the axis is logarithmic or tabular. */
+            if( sym && strlen( sym ) ) {
+               (void) sprintf( buf, "%s", sym );
+            } else {
+               (void) sprintf( buf, "AXIS%d", iax + 1 );
+            }
+            if( log_axis ) {
+               SetAlgCode( buf, "-LOG", status );
+            } else if( tab_axis ) {
+               SetAlgCode( buf, "-TAB", status );
+            }
+            SetItemC( &(store->ctype), fits_i, 0, s, buf, status );
+
+/* The axis labels are taken as the comment for the CTYPE keywords and as
+   the CNAME keyword (but only if a label has been set and is different to
+   the symbol). */
+            if( lab && lab[ 0 ] && astTestLabel( wcsfrm, iax ) && strcmp( sym, lab ) ) {
+               SetItemC( &(store->ctype_com), fits_i, 0, s, (char *) lab, status );
+               SetItemC( &(store->cname), fits_i, 0, s, (char *) lab, status );
+            } else {
+               sprintf( buf, "Type of co-ordinate on axis %d", iax + 1 );
+               SetItemC( &(store->ctype_com), fits_i, 0, s, buf, status );
+            }
+
+/* If a value has been set for the axis units, use it as CUNIT. */
+            if( astTestUnit( wcsfrm, iax ) ){
+               SetItemC( &(store->cunit), fits_i, 0, s, (char *) astGetUnit( wcsfrm, iax ), status );
+            }
+
+/* Indicate this axis has now been described. */
+            axis_done[ iax ] = 1;
+
+/* Release Resources. */
+            axmap = astAnnul( axmap );
+         }
+      }
+
+/* Release Resources. */
+      wcsfrm = astAnnul( wcsfrm );
+      map = astAnnul( map );
+      pset1 = astAnnul( pset1 );
+      pset2 = astAnnul( pset2 );
+      lbnd_p = astFree( lbnd_p );
+      ubnd_p = astFree( ubnd_p );
+   }
+
+/* If we have a Mapping to return, simplify it. Otherwise, create
+   a UnitMap to return. */
+   if( ret ) {
+      tmap0 = ret;
+      ret = astSimplify( tmap0 );
+      tmap0 =  astAnnul( tmap0 );
+   } else {
+      ret = (AstMapping *) astUnitMap( nwcs, "", status );
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static int PCFromStore( AstFitsChan *this, FitsStore *store,
+                        const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     PCFromStore
+
+*  Purpose:
+*     Store WCS keywords in a FitsChan using FITS-PC encoding.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int PCFromStore( AstFitsChan *this, FitsStore *store,
+*                      const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function copies the WCS information stored in the supplied
+*     FitsStore into the supplied FitsChan, using FITS-PC encoding.
+*
+*     Zero is returned if the primary axis descriptions cannot be produced.
+*     Whether or not secondary axis descriptions can be produced does not
+*     effect the returned value (i.e. failure to produce a specific set of
+*     secondary axes does not prevent other axis descriptions from being
+*     produced).
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        Pointer to the FitsStore.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if succesfull, and zero is returned
+*     otherwise.
+*/
+
+/* Local Variables: */
+   char *comm;         /* Pointer to comment string */
+   char *cval;         /* Pointer to string keyword value */
+   char combuf[80];    /* Buffer for FITS card comment */
+   char keyname[10];   /* Buffer for keyword name string */
+   char primsys[20];   /* Buffer for primnary RADECSYS value */
+   char type[MXCTYPELEN];/* Buffer for CTYPE value */
+   char s;             /* Co-ordinate version character */
+   char sign[2];       /* Fraction's sign character */
+   char sup;           /* Upper limit on s */
+   double *c;          /* Pointer to next array element */
+   double *d;          /* Pointer to next array element */
+   double *matrix;     /* Pointer to Frame PC/CD matrix */
+   double *primpc;     /* Pointer to primary PC/CD matrix */
+   double fd;          /* Fraction of a day */
+   double mjd99;       /* MJD at start of 1999 */
+   double primdt;      /* Primary mjd-obs value */
+   double primeq;      /* Primary equinox value */
+   double primln;      /* Primary lonpole value */
+   double primlt;      /* Primary latpole value */
+   double primpv[10];  /* Primary projection parameter values */
+   double val;         /* General purpose value */
+   int axlat;          /* Index of latitude FITS WCS axis */
+   int axlon;          /* Index of longitude FITS WCS axis */
+   int axspec;         /* Index of spectral FITS WCS axis */
+   int i;              /* Axis index */
+   int ihmsf[ 4 ];     /* Hour, minute, second, fractional second */
+   int is;             /* Co-ordinate version index */
+   int iymdf[ 4 ];     /* Year, month, date, fractional day */
+   int j;              /* Axis index */
+   int jj;             /* SlaLib status */
+   int m;              /* Parameter index */
+   int maxm;           /* Upper limit on m */
+   int naxis;          /* No. of axes */
+   int nc;             /* Length of string */
+   int ok;             /* Frame written out succesfully? */
+   int prj;            /* Projection type */
+   int ret;            /* Returned value. */
+
+/* Initialise */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Find the number of co-ordinate versions in the FitsStore. FITS-PC
+   can only encode 10 axis descriptions (including primary). */
+   sup = GetMaxS( &(store->crval), status );
+   if( sup > 'I' ) return ret;
+
+/* Initialise */
+   primdt = AST__BAD;
+   primeq = AST__BAD;
+   primln = AST__BAD;
+   primlt = AST__BAD;
+
+/* Loop round all co-ordinate versions (0-9) */
+   primpc = NULL;
+   for( s = ' '; s <= sup && astOK; s++ ){
+      is = s - 'A' + 1;
+
+/* Assume the Frame can be created succesfully. */
+      ok = 1;
+
+/* Save the number of wcs axes */
+      val = GetItem( &(store->wcsaxes), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) {
+         naxis = (int) ( val + 0.5 );
+         SetValue( this, FormatKey( "WCSAXES", -1, -1, s, status ),
+                   &naxis, AST__INT, "Number of WCS axes", status );
+      } else {
+         naxis = GetMaxJM( &(store->crpix), s, status ) + 1;
+      }
+
+/* PC matrix:
+   --------- */
+
+/* This encoding does not allow the PC matrix to be specified for each
+   version - instead they all share the primary PC matrix. Therefore we
+   need to check that all versions can use the primary PC matrix. Allocate
+   memory to hold the PC matrix for this version. */
+      matrix = (double *) astMalloc( sizeof(double)*naxis*naxis );
+      if( matrix ){
+
+/* Fill these array with the values supplied in the FitsStore. */
+         c = matrix;
+         for( i = 0; i < naxis; i++ ){
+            for( j = 0; j < naxis; j++ ){
+               *c = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
+               if( *c == AST__BAD ) *c = ( i == j ) ? 1.0 : 0.0;
+               c++;
+            }
+         }
+
+/* If we are currently processing the primary axis description, take
+   a copy of the PC matrix. */
+         if( s == ' ' ) {
+            primpc = (double *) astStore(  NULL, (void *) matrix,
+                                           sizeof(double)*naxis*naxis );
+
+/* Store each matrix element in turn. */
+            c = matrix;
+            for( i = 0; i < naxis; i++ ){
+               for( j = 0; j < naxis; j++ ){
+
+/* Set the element bad if it takes its default value. */
+                  val = *(c++);
+                  if( i == j ){
+                     if( astEQUAL( val, 1.0 ) ) val = AST__BAD;
+                  } else {
+                     if( astEQUAL( val, 0.0 ) ) val = AST__BAD;
+                  }
+
+/* Only store elements which do not take their default values. */
+                  if( val != AST__BAD ){
+                     sprintf( keyname, "PC%.3d%.3d", i + 1, j + 1 );
+                     SetValue( this, keyname, &val, AST__FLOAT, NULL, status );
+                  }
+               }
+            }
+
+/* For secondary axis descriptions, a check is made that the PC values are
+   the same as the primary PC values stored earlier. If not, the current
+   Frame cannot be stored as a secondary axis description so continue on
+   to the next Frame. */
+         } else {
+            if( primpc ){
+               c = matrix;
+               d = primpc;
+               for( i = 0; i < naxis; i++ ){
+                  for( j = 0; j < naxis; j++ ){
+                     if( !astEQUAL( *c, *d ) ){
+                        ok = 0;
+                     } else {
+                        c++;
+                        d++;
+                     }
+                  }
+               }
+
+/* Continue with the next Frame if the PC matrix for this Frame is different
+   to the primary PC matrix. */
+               if( !ok ) goto next;
+            }
+         }
+         matrix = (double *) astFree( (void *) matrix );
+      }
+
+/* CDELT:
+   ------ */
+      for( i = 0; i < naxis; i++ ){
+         val = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+            goto next;
+         }
+         sprintf( combuf, "Pixel scale on axis %d", i + 1 );
+         if( s == ' ' ) {
+            sprintf( keyname, "CDELT%d", i + 1 );
+         } else {
+            sprintf( keyname, "C%dELT%d", is, i + 1 );
+         }
+         SetValue( this, keyname, &val, AST__FLOAT, combuf, status );
+      }
+
+/* CRPIX:
+   ------ */
+      for( j = 0; j < naxis; j++ ){
+         val = GetItem( &(store->crpix), 0, j, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+            goto next;
+         }
+         sprintf( combuf, "Reference pixel on axis %d", j + 1 );
+         if( s == ' ' ) {
+            sprintf( keyname, "CRPIX%d", j + 1 );
+         } else {
+            sprintf( keyname, "C%dPIX%d", is, j + 1 );
+         }
+         SetValue( this, keyname, &val, AST__FLOAT, combuf, status );
+      }
+
+/* CRVAL:
+   ------ */
+      for( i = 0; i < naxis; i++ ){
+         val = GetItem( &(store->crval), i, 0, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+            goto next;
+         }
+         sprintf( combuf, "Value at ref. pixel on axis %d", i + 1 );
+         if( s == ' ' ) {
+            sprintf( keyname, "CRVAL%d", i + 1 );
+         } else {
+            sprintf( keyname, "C%dVAL%d", is, i + 1 );
+         }
+         SetValue( this, keyname, &val, AST__FLOAT, combuf, status );
+      }
+
+/* CTYPE:
+   ------ */
+      for( i = 0; i < naxis; i++ ){
+         cval = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+         nc = strlen( cval );
+         if( !cval || ( nc > 4 && !strcmp( cval + 4, "-TAB" ) ) ) {
+            ok = 0;
+            goto next;
+         }
+         comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
+         if( !comm ) {
+            sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
+            comm = combuf;
+         }
+         if( s == ' ' ) {
+            sprintf( keyname, "CTYPE%d", i + 1 );
+         } else {
+            sprintf( keyname, "C%dYPE%d", is, i + 1 );
+         }
+
+/* FITS-PC cannot handle celestial axes of type "xxLT" or "xxLN".
+   Neither can it handle the "-TAB". */
+         if( ( nc > 2 && !strncmp( cval + 2, "LT-", 3 ) ) ||
+             ( nc > 2 && !strncmp( cval + 2, "LN-", 3 ) ) ||
+             ( nc > 4 && !strncmp( cval + 4, "-TAB", 4 ) ) ){
+            ok = 0;
+            goto next;
+         }
+
+/* Extract the projection type as specified by the last 4 characters
+   in the CTYPE keyword value. This will be AST__WCSBAD for non-celestial
+   axes. */
+         prj = astWcsPrjType( cval + 4 );
+
+/* Change the new SFL projection code to to the older equivalent GLS */
+         if( prj == AST__SFL ) {
+            strcpy( type, cval );
+            (void) strcpy( type + 4, "-GLS" );
+            cval = type;
+         }
+
+/* FITS-PC cannot handle the AST-specific TPN projection. */
+         if( prj == AST__TPN ) {
+            ok = 0;
+            goto next;
+         }
+
+/* Store the CTYPE value */
+         SetValue( this, keyname, &cval, AST__STRING, comm, status );
+      }
+
+/* Get and save CUNIT for all intermediate axes. These are NOT required, so
+   do not pass on if they are not available. */
+      for( i = 0; i < naxis; i++ ){
+         cval = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
+         if( cval ) {
+            sprintf( combuf, "Units for axis %d", i + 1 );
+            if( s == ' ' ) {
+               sprintf( keyname, "CUNIT%d", i + 1 );
+            } else {
+               sprintf( keyname, "C%dNIT%d", is, i + 1 );
+            }
+            SetValue( this, keyname, &cval, AST__STRING, combuf, status );
+         }
+      }
+
+/* Get and save RADESYS. This is NOT required, so do not pass on if it is
+   not available. If RADECSYS is provided for a secondary axis, it must
+   be the same as the primary axis RADECSYS value. If it is not, pass on to
+   the next Frame. */
+      cval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
+      if( cval ) {
+         if( s == ' ' ) {
+            strcpy( primsys, cval );
+            SetValue( this, "RADECSYS", &cval, AST__STRING,
+                      "Reference frame for RA/DEC values", status );
+         } else if( strcmp( cval, primsys ) ) {
+            ok = 0;
+            goto next;
+         }
+      }
+
+/* Reference equinox. This is NOT required, so do not pass on if it is
+   not available. If equinox is provided for a secondary axis, it must
+   be the same as the primary axis equinox value. If it is not, pass on to
+   the next Frame. */
+      val = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );
+      if( s == ' ' ) {
+         primeq = val;
+         if( val != AST__BAD ) SetValue( this, "EQUINOX", &val, AST__FLOAT,
+                                         "Epoch of reference equinox", status );
+      } else if( !astEQUAL( val, primeq ) ){
+         ok = 0;
+         goto next;
+      }
+
+/* Latitude of native north pole. This is NOT required, so do not pass on
+   if it is not available. If latpole is provided for a secondary axis, it
+   must be the same as the primary axis value. If it is not, pass on to
+   the next Frame. */
+      val = GetItem( &(store->latpole), 0, 0, s, NULL, method, class, status );
+      if( s == ' ' ) {
+         primlt = val;
+         if( val != AST__BAD ) SetValue( this, "LATPOLE", &val, AST__FLOAT,
+                                         "Latitude of native north pole", status );
+      } else if( !EQUALANG( val, primlt ) ){
+         ok = 0;
+         goto next;
+      }
+
+/* Longitude of native north pole. This is NOT required, so do not pass on
+   if it is not available. If lonpole is provided for a secondary axis, it
+   must be the same as the primary axis value. If it is not, pass on to
+   the next Frame. */
+      val = GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status );
+      if( s == ' ' ) {
+         primln = val;
+         if( val != AST__BAD ) SetValue( this, "LONGPOLE", &val, AST__FLOAT,
+                                         "Longitude of native north pole", status );
+      } else if( !EQUALANG( val, primln ) ){
+         ok = 0;
+         goto next;
+      }
+
+/* Date of observation. This is NOT required, so do not pass on if it is
+   not available. If mjd-obs is provided for a secondary axis, it must be
+   the same as the primary axis value. If it is not, pass on to the next
+   Frame. */
+      val = GetItem( &(store->mjdobs), 0, 0, s, NULL, method, class, status );
+      if( s == ' ' ) {
+         primdt = val;
+         if( val != AST__BAD ) {
+            SetValue( this, "MJD-OBS", &val, AST__FLOAT,
+                      "Modified Julian Date of observation", status );
+
+/* The format used for the DATE-OBS keyword depends on the value of the
+   keyword. For DATE-OBS < 1999.0, use the old "dd/mm/yy" format.
+   Otherwise, use the new "ccyy-mm-ddThh:mm:ss[.ssss]" format. */
+            palCaldj( 99, 1, 1, &mjd99, &jj );
+            if( val < mjd99 ) {
+               palDjcal( 0, val, iymdf, &jj );
+               sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
+                        iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
+            } else {
+               palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
+               palDd2tf( 3, fd, sign, ihmsf );
+               sprintf( combuf, "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d.%3.3d",
+                        iymdf[0], iymdf[1], iymdf[2], ihmsf[0], ihmsf[1],
+                        ihmsf[2], ihmsf[3] );
+            }
+
+/* Now store the formatted string in the FitsChan. */
+            cval = combuf;
+            SetValue( this, "DATE-OBS", &cval, AST__STRING,
+                      "Date of observation", status );
+         }
+      } else if( !astEQUAL( val, primdt ) ){
+         ok = 0;
+         goto next;
+      }
+
+/* Look for the celestial and spectral axes. */
+      FindLonLatSpecAxes( store, s, &axlon, &axlat, &axspec, method, class, status );
+
+/* If both longitude and latitude axes are present ...*/
+      if( axlon >= 0 && axlat >= 0 ) {
+
+/* Get the CTYPE values for the latitude axis. */
+         cval = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
+
+/* Extract the projection type as specified by the last 4 characters
+   in the CTYPE keyword value. */
+         prj = ( cval ) ? astWcsPrjType( cval + 4 ) : AST__WCSBAD;
+
+/* Projection parameters. If provided for a secondary axis, they must be
+   the same as the primary axis value. If it is not, pass on to the next
+   Frame. PC encoding ignores parameters associated with the longitude
+   axis. The old PC TAN projection did not have any parameters.
+   Pass on if a TAN projection with parameters is found.  The number of
+   parameters was limited to 10. Pass on if more than 10 are supplied. */
+         maxm = GetMaxJM( &(store->pv), ' ', status );
+         for( i = 0; i < naxis; i++ ){
+            if( i != axlon ) {
+               for( m = 0; m <= maxm; m++ ){
+                  val = GetItem( &(store->pv), i, m, s, NULL, method, class, status );
+                  if( s == ' ' ){
+                     if( val != AST__BAD ) {
+                        if( i != axlat || prj == AST__TAN || m >= 10 ){
+                           ok = 0;
+                           goto next;
+                        } else {
+                           SetValue( this, FormatKey( "PROJP", m, -1, ' ', status ), &val,
+                                     AST__FLOAT, "Projection parameter", status );
+                        }
+                     }
+                     if( i == axlat && m < 10 ) primpv[m] = val;
+                  } else {
+                     if( ( ( i != axlat || m >= 10 ) && val != AST__BAD ) ||
+                         ( i == axlat && m < 10 && !astEQUAL( val, primpv[m] ) ) ){
+                        ok = 0;
+                        goto next;
+                     }
+                  }
+               }
+            }
+         }
+      }
+
+/* See if a Frame was sucessfully written to the FitsChan. */
+next:
+      ok = ok && astOK;
+
+/* If so, indicate we have something to return. */
+      if( ok ) ret = 1;
+
+/* Clear any error status so we can continue to produce the next Frame.
+   Retain the error if the primary axes could not be produced. After the
+   primary axes, do the A axes. */
+      if( s != ' ' ) {
+         astClearStatus;
+      } else {
+         s = 'A' - 1;
+      }
+
+/* Remove the secondary "new" flags from the FitsChan. This flag is
+   associated with cards which have been added to the FitsChan during
+   this pass through the main loop in this function. If the Frame was
+   written out succesfully, just clear the flags. If anything went wrong
+   with this Frame, remove the flagged cards from the FitsChan. */
+      FixNew( this, NEW2, !ok, method, class, status );
+
+/* Set the current card so that it points to the last WCS-related keyword
+   in the FitsChan (whether previously read or not). */
+      FindWcs( this, 1, 1, 0, method, class, status );
+   }
+
+/* Annul the array holding the primary PC matrix. */
+   primpc = (double *) astFree( (void *) primpc );
+
+/* Return zero or ret depending on whether an error has occurred. */
+   return astOK ? ret : 0;
+}
+
+static void PreQuote( const char *value,
+                      char string[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 3 ], int *status ) {
+
+/*
+*  Name:
+*     PreQuote
+
+*  Purpose:
+*     Pre-quote FITS character data.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void PreQuote( const char *value,
+*                    char string[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 3 ] )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function processes a string value in such a way that it can
+*     be stored as a FITS character value (associated with a keyword)
+*     and later retrieved unchanged, except for possible truncation.
+*
+*     This pre-processing is necessary because FITS does not regard
+*     trailing white space as significant, so it is lost. This
+*     function adds double quote (") characters around the string if
+*     it is necessary in order to prevent this loss. These quotes are
+*     also added to zero-length strings and to strings that are
+*     already quoted (so that the original quotes are not lost when
+*     they are later un-quoted).
+*
+*     This function will silently truncate any string that is too long
+*     to be stored as a FITS character value, but will ensure that the
+*     maximum number of characters are retained, taking account of any
+*     quoting required.
+
+*  Parameters:
+*     value
+*        Pointer to a constant null-terminated string containing the
+*        input character data to be quoted. All white space is
+*        significant.
+*     string
+*        A character array into which the result string will be
+*        written, with a terminating null. The maximum number of
+*        characters from the input string that can be accommodated in
+*        this is (AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 4), but this
+*        will be reduced if quoting is necessary.
+
+*  Notes:
+*     - The UnPreQuote function should be used to reverse the effect
+*     of this function on a string (apart from any truncation).
+*/
+
+/* Local Variables: */
+   int dq;                       /* Number of double quotes needed */
+   int dquotes;                  /* Final number of double quotes */
+   int i;                        /* Loop counter for input characters */
+   int j;                        /* Counter for output characters */
+   int nc;                       /* Number of characters to be accommodated */
+   int sq;                       /* Number of single quotes needed */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise, setting the default number of double quotes (which
+   applies to a zero-length string) to 2. */
+   dquotes = 2;
+   nc = 0;
+   sq = 0;
+
+/* Loop to consider each input character to see if it will fit into
+   the result string. */
+   for ( i = 0; value[ i ]; i++ ) {
+
+/* If a single quote character is to be included, count it. When the
+   string is encoded as FITS character data, these quotes will be
+   doubled, so will increase the overall string length by one. */
+      if ( value[ i ] == '\'' ) sq++;
+
+/* See how many double quotes are needed around the string (0 or
+   2). These are needed if there is trailing white space that needs
+   protecting (this is not significant in FITS and will be removed),
+   or if the string already has quotes at either end (in which case an
+   extra set is needed to prevent the original ones being removed when
+   it is later un-quoted). Note we do not need to double existing
+   double quote characters within the string, because the position of
+   the ends of the string are known (from the quoting supplied by
+   FITS) so only the first and last characters need be inspected when
+   un-quoting the string.
+   In assessing the number of double quotes, assume the string will be
+   truncated after the current character. */
+      dq = ( isspace( value[ i ] ) ||
+             ( ( value[ 0 ] == '"' ) && ( value[ i ] == '"' ) ) ) ? 2 : 0;
+
+/* See if the length of the resulting string, including the current
+   character and all necessary quotes, is too long. If so, give up
+   here. */
+      if ( ( nc + 1 + dq + sq ) >
+           ( AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 4 ) ) break;
+
+/* If the string is not too long, accept the character and note the
+   number of double quotes needed. */
+      nc = i + 1;
+      dquotes = dq;
+   }
+
+/* If double quotes are needed, insert the opening quote into the
+   output string. */
+   j = 0;
+   if ( dquotes ) string[ j++ ] = '"';
+
+/* Follow this with the maximum number of input string characters that
+   can be accommodated. */
+   for ( i = 0; i < nc; i++ ) string[ j++ ] = value[ i ];
+
+/* Append the closing quote if necessary and terminate the output
+   string. */
+   if ( dquotes ) string[ j++ ] = '"';
+   string[ j ] = '\0';
+}
+
+static void PurgeWCS( AstFitsChan *this, int *status ){
+
+/*
+*++
+*  Name:
+c     astPurgeWCS
+f     AST_PURGEWCS
+
+*  Purpose:
+*     Delete all cards in the FitsChan describing WCS information.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+c     void astPurgeWCS( AstFitsChan *this )
+f     CALL AST_PURGEWCS( THIS, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function
+f     This routine
+*     deletes all cards in a FitsChan that relate to any of the recognised
+*     WCS encodings. On exit, the current card is the first remaining card
+*     in the FitsChan.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+*--
+*/
+
+/* Local Variables: */
+   AstObject *obj;
+   int oldclean;
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Ensure the Clean attribute is set so that WCS keywords are removed
+   even if an error occurs. */
+   if( astTestClean( this ) ) {
+      oldclean = astGetClean( this );
+      astSetClean( this, 1 );
+   } else {
+      astSetClean( this, 1 );
+      oldclean = -1;
+   }
+
+/* Loop round attempting to read AST objects form the FitsChan. This will
+   flag cards as used that are involved in the creation of these object
+   (including NATIVE encodings). Ignore any error that ocurs whilst doing
+   this. */
+   astClearCard( this );
+   if( astOK ) {
+      int oldreporting = astReporting( 0 );
+      obj = astRead( this );
+      while( obj ) {
+         obj = astAnnul( obj );
+         astClearCard( this );
+         obj = astRead( this );
+      }
+      if( !astOK ) astClearStatus;
+      astReporting( oldreporting );
+   }
+
+/* We now loop round to remove any spurious WCS-related cards left in the
+   FitsChan that did not form part of a complete WCS encoding. Find the
+   first WCS-related card left in the FitsChan. */
+   FindWcs( this, 0, 0, 1, "DeleteWcs", "FitsChan", status );
+
+/* Loop round marking each WCS-related card as used until none are left */
+   while( this->card && astOK ) {
+
+/* Mark the current card as having been read. */
+      ( (FitsCard*) this->card )->flags = USED;
+
+/* Find the next WCS-related card. */
+      FindWcs( this, 0, 0, 0, "DeleteWcs", "FitsChan", status );
+   }
+
+/* Rewind the FitsChan. */
+   astClearCard( this );
+
+/* Reset the Clean attribute. */
+   if( oldclean == -1 ) {
+      astClearClean( this );
+   } else {
+      astSetClean( this, oldclean );
+   }
+
+}
+
+static void PutCards( AstFitsChan *this, const char *cards, int *status ) {
+
+/*
+*++
+*  Name:
+c     astPutCards
+f     AST_PUTCARDS
+
+*  Purpose:
+*     Store a set of FITS header cards in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     void astPutCards( AstFitsChan *this, const char *cards )
+f     CALL AST_PUTCARDS( THIS, CARDS, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function
+f     This routine
+*     stores a set of FITS header cards in a FitsChan. The cards are
+*     supplied concatenated together into a single character string.
+*     Any existing cards in the FitsChan are removed before the new cards
+*     are added. The FitsChan is "re-wound" on exit by clearing its Card
+*     attribute. This means that a subsequent invocation of
+c     astRead
+f     AST_READ
+*     can be made immediately without the need to re-wind the FitsChan
+*     first.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     cards
+f     CARDS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string
+f        A character string
+*        containing the FITS cards to be stored. Each individual card
+*        should occupy 80 characters in this string, and there should be
+*        no delimiters, new lines, etc, between adjacent cards. The final
+*        card may be less than 80 characters long.
+c        This is the format produced by the fits_hdr2str function in the
+c        CFITSIO library.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - An error will result if the supplied string contains any cards
+*     which cannot be interpreted.
+*--
+*/
+
+/* Local Variables: */
+   const char *a;         /* Pointer to start of next card */
+   int clen;              /* Length of supplied string */
+   int i;                 /* Card index */
+   int ncard;             /* No. of cards supplied */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Empty the FitsChan. */
+   astEmptyFits( this );
+
+/* Loop round the supplied string in 80 character segments, inserting
+   each segment into the FitsChan as a header card. Allow the last card
+   to be less than 80 characters long. */
+   clen = strlen( cards );
+   ncard = clen/80;
+   if( ncard*80 < clen ) ncard++;
+   a = cards;
+   for( i = 0; i < ncard; i++, a += 80 ) astPutFits( this, a, 1 );
+
+/* Rewind the FitsChan. */
+   astClearCard( this );
+}
+
+static void PutFits( AstFitsChan *this, const char card[ AST__FITSCHAN_FITSCARDLEN + 1 ],
+                     int overwrite, int *status ){
+
+/*
+*++
+*  Name:
+c     astPutFits
+f     AST_PUTFITS
+
+*  Purpose:
+*     Store a FITS header card in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     void astPutFits( AstFitsChan *this, const char card[ 80 ],
+c                      int overwrite )
+f     CALL AST_PUTFITS( THIS, CARD, OVERWRITE, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function stores a FITS header card in a FitsChan. The card
+f     This routine stores a FITS header card in a FitsChan. The card
+*     is either inserted before the current card (identified by the
+*     Card attribute), or over-writes the current card, as required.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     card
+f     CARD = CHARACTER * ( 80 ) (Given)
+c        Pointer to a possibly null-terminated character string
+c        containing the FITS card to be stored. No more than 80
+c        characters will be used from this string (or fewer if a null
+c        occurs earlier).
+f        A character string string containing the FITS card to be
+f        stored. No more than 80 characters will be used from this
+f        string.
+c     overwrite
+f     OVERWRITE = LOGICAL (Given)
+c        If this value is zero, the new card is inserted in front of
+f        If this value is .FALSE., the new card is inserted in front of
+*        the current card in the FitsChan (as identified by the
+c        initial value of the Card attribute). If it is non-zero, the
+f        initial value of the Card attribute). If it is .TRUE., the
+*        new card replaces the current card. In either case, the Card
+*        attribute is then incremented by one so that it subsequently
+*        identifies the card following the one stored.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - If the Card attribute initially points at the "end-of-file"
+*     (i.e. exceeds the number of cards in the FitsChan), then the new
+*     card is appended as the last card in the FitsChan.
+*     - An error will result if the supplied string cannot be interpreted
+*     as a FITS header card.
+*--
+*/
+
+/* Local Variables: */
+   char *comment;         /* The keyword comment */
+   char *name;            /* The keyword name */
+   char *value;           /* The keyword value */
+   const char *class;     /* Object class */
+   const char *method;    /* Current method */
+   double cfval[2];       /* Complex floating point keyword value */
+   double fval;           /* floating point keyword value */
+   int cival[2];          /* Complex integer keyword value */
+   int ival;              /* Integer keyword value */
+   int len;               /* No. of characters to read from the value string */
+   int nc;                /* No. of characters read from value string */
+   int type;              /* Keyword data type */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Store the current method, and the class of the supplied object for use
+   in error messages.*/
+   method = "astPutFits";
+   class = astGetClass( this );
+
+/* Split the supplied card up into name, value and commment strings, and
+   get pointers to local copies of them. The data type associated with the
+   keyword is returned. */
+   type = Split( this, card, &name, &value, &comment, method, class, status );
+
+/* Check that the pointers can be used. */
+   if( astOK ){
+
+/* Initialise the number of characters read from the value string. */
+      nc = 0;
+
+/* Store the number of characters in the value string. */
+      len = strlen( value );
+
+/* Read and store floating point values from the value string. NB, this
+   list is roughly in the order of descreasing frequency of use (i.e.
+   most FITS keywords are simple floating point values, the next most
+   common are strings, etc). */
+      if( type == AST__FLOAT ){
+         if( 1 == astSscanf( value, " %lf %n", &fval, &nc ) && nc >= len ){
+            astSetFitsF( this, name, fval, comment, overwrite );
+         } else {
+            astError( AST__BDFTS, "%s(%s): Unable to read a floating point "
+                      "FITS keyword value.", status, method, class );
+         }
+
+/* Read and store string values from the value string. */
+      } else if( type == AST__STRING ){
+         astSetFitsS( this, name, value, comment, overwrite );
+
+/* Read and store string values from the value string. */
+      } else if( type == AST__CONTINUE ){
+         astSetFitsCN( this, name, value, comment, overwrite );
+
+/* Store comment card. */
+      } else if( type == AST__COMMENT ){
+         astSetFitsCom( this, name, comment, overwrite );
+
+/* Read and store integer values from the value string. */
+      } else if( type == AST__INT ){
+         if( 1 == astSscanf( value, " %d %n", &ival, &nc ) && nc >= len ){
+            astSetFitsI( this, name, ival, comment, overwrite );
+         } else {
+            astError( AST__BDFTS, "%s(%s): Unable to read an integer FITS "
+                      "keyword value.", status, method, class );
+         }
+
+/* Read and store logical values from the value string. */
+      } else if( type == AST__LOGICAL ){
+         astSetFitsL( this, name, (*value == 'T'), comment, overwrite );
+
+/* Read and store undefined values from the value string. */
+      } else if( type == AST__UNDEF ){
+         astSetFitsU( this, name, comment, overwrite );
+
+/* Read and store complex floating point values from the value string. */
+      } else if( type == AST__COMPLEXF ){
+         if( 2 == astSscanf( value, " %lf %lf %n", cfval, cfval + 1, &nc ) &&
+             nc >= len ){
+            astSetFitsCF( this, name, cfval, comment, overwrite );
+         } else {
+            astError( AST__BDFTS, "%s(%s): Unable to read a complex pair "
+                      "of floating point FITS keyword values.", status, method, class );
+         }
+
+/* Read and store complex integer values from the value string. */
+      } else if( type == AST__COMPLEXI ){
+         if( 2 == astSscanf( value, " %d %d %n", cival, cival + 1, &nc ) &&
+             nc >= len ){
+            astSetFitsCI( this, name, cival, comment, overwrite );
+         } else {
+            astError( AST__BDFTS, "%s(%s): Unable to read a complex pair "
+                      "of integer FITS keyword values.", status, method, class );
+         }
+
+/* Report an error for any other type. */
+      } else {
+         astError( AST__INTER, "%s: AST internal programming error - "
+                   "FITS data-type '%d' not yet supported.", status, method, type );
+      }
+
+/* Give a context message if an error occurred. */
+      if( !astOK ){
+         astError( astStatus, "%s(%s): Unable to store the following FITS "
+                   "header card:\n%s\n", status, method, class, card );
+      }
+   }
+
+/* Free the memory used to hold the keyword name, comment and value
+   strings. */
+   (void) astFree( (void *) name );
+   (void) astFree( (void *) comment );
+   (void) astFree( (void *) value );
+}
+
+static void PutTable( AstFitsChan *this, AstFitsTable *table,
+                      const char *extnam, int *status ) {
+
+/*
+*++
+*  Name:
+c     astPutTable
+f     AST_PUTTABLE
+
+*  Purpose:
+*     Store a single FitsTable in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     void astPutTable( AstFitsChan *this, AstFitsTable *table,
+c                       const char *extnam )
+f     CALL AST_PUTTABLE( THIS, TABLE, EXTNAM, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function
+f     This routine
+*     allows a representation of a single FITS binary table to be
+*     stored in a FitsChan. For instance, this may provide the coordinate
+*     look-up tables needed subequently when reading FITS-WCS headers
+*     for axes described using the "-TAB" algorithm. Since, in general,
+*     the calling application may not know which tables will be needed -
+*     if any - prior to calling
+c     astRead, the astTablesSource function
+f     AST_READ, the AST_TABLESOURCE routine
+*     provides an alternative mechanism in which a caller-supplied
+*     function is invoked to store a named table in the FitsChan.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     table
+f     TABLE = INTEGER (Given)
+*        Pointer to a FitsTable to be added to the FitsChan. If a FitsTable
+*        with the associated extension name already exists in the FitsChan,
+*        it is replaced with the new one. A deep copy of the FitsTable is
+*        stored in the FitsChan, so any subsequent changes made to the
+*        FitsTable will have no effect on the behaviour of the FitsChan.
+c     extnam
+f     EXTNAM = CHARACTER * ( * ) (Given)
+*        The name of the FITS extension associated with the table.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Tables stored in the FitsChan may be retrieved using
+c     astGetTables.
+f     AST_GETTABLES.
+c     - The astPutTables method can add multiple FitsTables in a single call.
+f     - The AST_PUTTABLES method can add multiple FitsTables in a single call.
+*--
+*/
+
+/* Local Variables: */
+   AstObject *ft;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Create a KeyMap to hold the tables within the FitsChan, if this has not
+   already been done. */
+   if( !this->tables ) this->tables = astKeyMap( " ", status );
+
+/* Store a copy of the FitsTable in the FitsChan. */
+   ft = astCopy( table );
+   astMapPut0A( this->tables, extnam, ft, NULL );
+   ft = astAnnul( ft );
+}
+
+static void PutTables( AstFitsChan *this, AstKeyMap *tables, int *status ) {
+
+/*
+*++
+*  Name:
+c     astPutTables
+f     AST_PUTTABLES
+
+*  Purpose:
+*     Store one or more FitsTables in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     void astPutTables( AstFitsChan *this, AstKeyMap *tables )
+f     CALL AST_PUTTABLES( THIS, TABLES, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function
+f     This routine
+*     allows representations of one or more FITS binary tables to be
+*     stored in a FitsChan. For instance, these may provide the coordinate
+*     look-up tables needed subequently when reading FITS-WCS headers
+*     for axes described using the "-TAB" algorithm. Since, in general,
+*     the calling application may not know which tables will be needed -
+*     if any - prior to calling
+c     astRead, the astTablesSource function
+f     AST_READ, the AST_TABLESOURCE routine
+*     provides an alternative mechanism in which a caller-supplied
+*     function is invoked to store a named table in the FitsChan.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     tables
+f     TABLES = INTEGER (Given)
+*        Pointer to a KeyMap holding the tables that are to be added
+*        to the FitsChan. Each entry should hold a scalar value which is a
+*        pointer to a FitsTable to be added to the FitsChan. Any unusable
+*        entries are ignored. The key associated with each entry should be
+*        the name of the FITS binary extension from which the table was
+*        read. If a FitsTable with the associated key already exists in the
+*        FitsChan, it is replaced with the new one. A deep copy of each
+*        usable FitsTable is stored in the FitsChan, so any subsequent
+*        changes made to the FitsTables will have no effect on the
+*        behaviour of the FitsChan.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Tables stored in the FitsChan may be retrieved using
+c     astGetTables.
+f     AST_GETTABLES.
+*     - The tables in the supplied KeyMap are added to any tables already
+*     in the FitsChan.
+c     - The astPutTable
+f     - The AST_PUTTABLE
+*     method provides a simpler means of adding a single table to a FitsChan.
+*--
+*/
+
+/* Local Variables: */
+   AstObject *obj;
+   const char *key;
+   int ientry;
+   int nentry;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Loop through all entries in the supplied KeyMap. */
+   nentry = astMapSize( tables );
+   for( ientry = 0; ientry < nentry; ientry++ ) {
+      key = astMapKey( tables, ientry );
+
+/* Ignore entries that do not contain AST Object pointers, or are not
+   scalar. */
+      if( astMapType( tables, key ) == AST__OBJECTTYPE &&
+          astMapLength( tables, key ) == 1 ) {
+
+/* Get the pointer, amd ignore it if it is not a FitsTable. */
+         astMapGet0A( tables, key, &obj );
+         if( astIsAFitsTable( obj ) ) {
+
+/* Store it in the FitsChan. */
+            astPutTable( this, (AstFitsTable *) obj, key );
+         }
+
+/* Annul the Object pointer. */
+         obj = astAnnul( obj );
+      }
+   }
+}
+
+static AstObject *Read( AstChannel *this_channel, int *status ) {
+/*
+*  Name:
+*     Read
+
+*  Purpose:
+*     Read an Object from a Channel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstObject *Read( AstChannel *this_channel, int *status )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the astRead method
+*     inherited from the Channel class).
+
+*  Description:
+*     This function reads an Object from a FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object. This will always be a FrameSet.
+
+*  Notes:
+*     -  The pixel Frame is given a title of "Pixel Coordinates", and
+*     each axis in the pixel Frame is given a label of the form "Pixel
+*     axis <n>", where <n> is the axis index (starting at one).
+*     -  The FITS CTYPE keyword values are used to set the labels for any
+*     non-celestial axes in the physical coordinate Frames, and the FITS
+*     CUNIT keywords are used to set the corresponding units strings.
+*     -  On exit, the pixel Frame is the base Frame, and the physical
+*     Frame derived from the primary axis descriptions is the current Frame.
+*     -  Extra Frames are added to hold any secondary axis descriptions. All
+*     axes within such a Frame refer to the same coordinate version ('A',
+*     'B', etc).
+*     -  For foreign encodings, the first card in the FitsChan must be
+*     the current card on entry (otherwise a NULL pointer is returned),
+*     and the FitsChan is left at end-of-file on exit.
+*     -  For the Native encoding, reading commences from the current card
+*     on entry (which need not be the first in the FitsChan), and the
+*     current Card on exit is the first card following the last one read
+*     (or end-of-file).
+*/
+
+/* Local Variables: */
+   AstObject *new;               /* Pointer to returned Object */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+   FitsStore *store;             /* Intermediate storage for WCS information */
+   const char *method;           /* Pointer to string holding calling method */
+   const char *class;            /* Pointer to string holding object class */
+   int encoding;                 /* The encoding scheme */
+   int remove;                   /* Remove used cards? */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Store the calling method, and object class. */
+   method = "astRead";
+   class = astGetClass( this );
+
+/* Get the encoding scheme used by the FitsChan. */
+   encoding = astGetEncoding( this );
+
+/* If we are reading from a FitsChan in which AST objects are encoded using
+   native AST-specific keywords, use the Read method inherited from the
+   Channel class. */
+   if( encoding == NATIVE_ENCODING ){
+      new = (*parent_read)( this_channel, status );
+
+/* Indicate that used cards should be removed from the FitsChan. */
+      remove = 1;
+
+/* If we are reading from a FitsChan in which AST objects are encoded using
+   any of the other supported encodings, the header may only contain a
+   single FrameSet. */
+   } else {
+      remove = 0;
+
+/* Only proceed if the FitsChan is at start-of-file. */
+      if( !astTestCard( this ) && astOK ){
+
+/* Extract the required information from the FITS header into a standard
+   intermediary structure called a FitsStore. */
+         store = FitsToStore( this, encoding, method, class, status );
+
+/* Now create a FrameSet from this FitsStore. */
+         new = FsetFromStore( this, store, method, class, status );
+
+/* Release the resources used by the FitsStore. */
+         store = FreeStore( store, status );
+
+/* Indicate that used cards should be retained in the FitsChan. */
+         remove = 0;
+
+/* If no object is being returned, rewind the fitschan in order to
+   re-instate the original current Card. */
+         if( !new ) {
+            astClearCard( this );
+
+/*  Otherwise, ensure the current card is at "end-of-file". */
+         } else {
+            astSetCard( this, INT_MAX );
+         }
+      }
+   }
+
+/* If an error occurred, clean up by deleting the new Object and
+   return a NULL pointer. */
+   if ( !astOK ) new = astDelete( new );
+
+/* If no object is being returned, clear the "provisionally used" flags
+   associated with cards which were read. We do not do this if the user
+   wants to clean WCS cards from the FitsChan even if an error occurs. */
+   if( !new && !astGetClean( this ) ) {
+      FixUsed( this, 0, 0, 0, method, class, status );
+
+/*  Otherwise, indicate that all the "provisionally used" cards have been
+    "definitely used". If native encoding was used, these cards are
+    totally removed from the FitsChan. */
+   } else {
+      FixUsed( this, 0, 1, remove, method, class, status );
+   }
+
+/* Return the pointer to the new Object. */
+   return new;
+}
+
+static double *ReadCrval( AstFitsChan *this, AstFrame *wcsfrm, char s,
+                          const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     ReadCrval
+
+*  Purpose:
+*     Obtain the reference point from the supplied FitsChan  in the
+*     supplied WCS Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     double *ReadCrval( AstFitsChan *this, AstFrame *wcsfrm, char s,
+*                        const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The original reference point in the "s" coordinate description is read
+*     from the CRVAL keywords in the supplied FitsChan, and the original
+*     FrameSet is re-read from the FitsChan. If possible, the reference
+*     position is then converted from the "s" coordinate description to the
+*     supplied WCS Frame, and a pointer to an array holding the axis
+*     values for the transformed reference point is returned.
+
+*  Parameters:
+*     this
+*        The FitsChan.
+*     wcsfrm
+*        The WCS Frame in the FitsChan being written to.
+*     s
+*        The co-ordinate version character. A space means the primary
+*        axis descriptions. Otherwise the supplied character should be
+*        an upper case alphabetical character ('A' to 'Z').
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array holding the reference
+*     point in the supplied WCS Frame. NULL is returned if is is not
+*     possible to determine the reference point for any reason (for
+*     instance, if the FitsChan does not contain values for the CRVAL
+*     keywords).
+*/
+
+/* Local Variables: */
+   AstFitsChan *fc;          /* A copy of the supplied FitsChan */
+   AstFrame *tfrm;           /* Temporary Frame pointer */
+   AstFrameSet *fs;          /* The FITS FrameSet */
+   AstFrameSet *tfs;         /* FrameSet connecting FITS and supplied WCS Frame */
+   const char *id;           /* Pointer to Object "Id" string */
+   char buf[ 11 ];           /* FITS keyword template buffer */
+   double *crval;            /* CRVAL keyword values in supplied FitsChan */
+   double *ret;              /* Returned array */
+   int hii;                  /* Highest found FITS axis index */
+   int iax;                  /* Axis index (zero based) */
+   int ifr;                  /* Frames index */
+   int loi;                  /* Lowest found FITS axis index */
+   int nax;                  /* Axis count */
+   int nfr;                  /* No. of Frames in FITS FrameSet */
+   int ok;                   /* Were CRVAL values found? */
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* We want to re-create the original FrameSet represented by the original
+   contents of the supplied FitsChan. Some of the contents of the
+   FitsChan will already have been marked as "having been read" and so
+   will be ignored if we attempt to read a FrameSet directly from the
+   supplied FitsChan. Therefore we take a deep copy of the supplied
+   FitsChan and clear all the "previusly read" flags in the copy. */
+   fc = astCopy( this );
+   astClearEncoding( fc );
+   FixUsed( fc, 1, 0, 0, method, class, status );
+
+/* Copy the CRVAL values for the "s" axis descriptions into a dynamically
+   allocated array ("crval"). */
+   if( s == ' ' ) {
+      strcpy( buf, "CRVAL%d" );
+   } else {
+      sprintf( buf, "CRVAL%%d%c", s );
+   }
+   if( astKeyFields( fc, buf, 1, &hii, &loi ) > 0 ) {
+      crval = astMalloc( sizeof( double )*(size_t) hii );
+      ok = 1;
+      for( iax = 0; iax < hii; iax++ ){
+         ok = ok && GetValue( fc, FormatKey( "CRVAL", iax + 1, -1, s, status ),
+                              AST__FLOAT, (void *) (crval + iax), 0, 0, method,
+                              class, status );
+      }
+   } else {
+      crval = NULL;
+      ok = 0;
+   }
+
+/* If the CRVAL values were obtained succesfully, attempt to read a FrameSet
+   from the FitsChan copy. Do it in a new error report context so that we
+   can annull any error when the FrameSet is read. */
+   if( ok ) {
+      int oldreporting = astReporting( 0 );
+      astClearCard( fc );
+      fs = astRead( fc );
+      if( fs ) {
+
+/* We want to find a conversion from the Frame in this FrameSet which
+   represents the FITS-WCS "s" coordinate descriptions and the supplied WCS
+   Frame. So first find the Frame which has its Ident attribute set to
+   "s" and make it the current Frame. */
+         nfr = astGetNframe( fs );
+         for( ifr = 1; ifr <= nfr; ifr++ ) {
+            astSetCurrent( fs, ifr );
+            tfrm = astGetFrame( fs, ifr );
+            id = astTestIdent( tfrm ) ? astGetIdent( tfrm ) : NULL;
+            tfrm = astAnnul( tfrm );
+            if( id && strlen( id ) == 1 && id[ 0 ] == s ) break;
+         }
+
+/* Check a Frame was found, and that we have CRVAL values for all axes in
+   the Frame. */
+         if( ifr <= nfr && astGetNaxes( fs ) == hii ) {
+
+/* Attempt to find a conversion route from the Frame found above to the
+   supplied WCS Frame. */
+            tfs = astConvert( fs, wcsfrm, astGetDomain( wcsfrm ) );
+            if( tfs ) {
+
+/* Allocate memory to hold the returned reference point. */
+               nax = astGetNaxes( wcsfrm );
+               ret = astMalloc( sizeof( double )*(size_t) nax );
+
+/* Transform the original reference position from the "s" Frame to the
+   supplied WCS Frame using the Mapping returned by astConvert. */
+               astTranN( tfs, 1, hii, 1, crval, 1, nax, 1, ret );
+
+/* Free resources. */
+               tfs = astAnnul( tfs );
+            }
+         }
+
+/* Free resources. */
+         fs = astAnnul( fs );
+
+/* Annul any error that occurred reading the FitsChan. */
+      } else if( !astOK ) {
+         astClearStatus;
+      }
+
+/* Re-instate error reporting. */
+      astReporting( oldreporting );
+   }
+
+/* Free resources. */
+   if( crval ) crval = astFree( crval );
+   fc = astAnnul( fc );
+
+/* If an error occurred, free the returned array. */
+   if( !astOK ) ret = astFree( ret );
+
+/* Return the result. */
+   return ret;
+}
+
+static void ReadFits( AstFitsChan *this, int *status ){
+
+/*
+*++
+*  Name:
+c     astReadFits
+f     AST_READFITS
+
+*  Purpose:
+*     Read cards into a FitsChan from the source function.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+c     void astReadFits( AstFitsChan *this )
+f     CALL AST_READFITS( THIS, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function
+f     This routine
+*     reads cards from the source function that was specified when the
+*     FitsChan was created, and stores them in the FitsChan. This
+*     normally happens once-only, when the FitsChan is accessed for the
+*     first time.
+c     This function
+f     This routine
+*     provides a means of forcing a re-read of the external source, and
+*     may be useful if (say) new cards have been deposited into the
+*     external source. Any newcards read from the source are appended to
+*     the end of the current contents of the FitsChan.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - This function returns without action if no source function was
+*     specified when the FitsChan was created.
+*     - The SourceFile attribute is ignored by this
+c     function.
+f     routine.
+*     New cards are read from the source file whenever a new value is
+*     assigned to the SourceFile attribute.
+
+*--
+*/
+
+/* Check the inherited status */
+   if( !astOK ) return;
+
+/* If no source function is available, re-instate any saved source
+   function pointer. */
+   if( !this->source ) {
+      this->source = this->saved_source;
+      this->saved_source = NULL;
+   }
+
+/* Call the source function. */
+   ReadFromSource( this, status );
+}
+
+static void ReadFromSource( AstFitsChan *this, int *status ){
+
+/*
+*  Name:
+*     ReadFromSource
+
+*  Purpose:
+*     Fill the FitsChan by reading cards from the source function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void ReadFromSource( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The source function specified when the FitsChan was created is
+*     called repeatedly until it returns a NULL pointer. The string
+*     returned by each such call is assumed to be a FITS header card,
+*     and is stored in the FitsChan using astPutFits.
+*
+*     If no source function was provided, the FitsChan is left as supplied.
+*     This is different to a standard Channel, which tries to read data
+*     from standard input if no source function is provided.
+*
+*     This function should be called at the start of most public or protected
+*     FitsChan functions, and most private functions that are used to override
+*     methods inherited form the Channel class. Previously, this function
+*     was called only once, from the FitsChan initialiser (astInitFitschan).
+*     However, calling it from astInitFitsChan means that application code
+*     cannot use the astPutChannelData function with a FitsChan, since the
+*     source function would already have been called by the time the
+*     FitsChan constructor returned (and thus before astPutChannelData
+*     could have been called). In order to ensure that the source
+*     function is called only once, this function now nullifies the source
+*     function pointer after its first use.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  The new cards are appended to the end of the FitsChan.
+*     -  The first of the new cards is made the current card on exit. If no
+*     source function is supplied, the current card is left unchanged.
+*/
+
+/* Local Variables: */
+   const char *(* source)( void ); /* Pointer to source function */
+   const char *card;               /* Pointer to externally-read header card */
+   int icard;                      /* Current card index on entry */
+
+/* Check the global status. */
+   if( !astOK || !this ) return;
+
+/* Only proceed if source function and wrapper were supplied when the FitsChan
+   was created and are still available. */
+   if( this->source && this->source_wrap ){
+
+/* Save the source function pointer and then nullify the pointer in the
+   FitsChan structure. This avoids infinte loops. */
+      source = this->source;
+      this->source = NULL;
+
+/* Save the source fubnction pointer in the FitsChan so that it can be
+   re-instated if required (e.g. by astReadFits). */
+      this->saved_source = source;
+
+/* Ensure the FitsChan is at end-of-file. This will result in the
+   new cards being appended to the end of the FitsChan. */
+      astSetCard( this, INT_MAX );
+
+/* Store the current card index. */
+      icard = astGetCard( this );
+
+/* Obtain the first header card from the source function. This is an
+   externally supplied function which may not be thread-safe, so lock a
+   mutex first. Also store the channel data pointer in a global variable
+   so that it can be accessed in the source function using macro
+   astChannelData. */
+      astStoreChannelData( this );
+      LOCK_MUTEX2;
+      card = ( *this->source_wrap )( source, status );
+      UNLOCK_MUTEX2;
+
+/* Loop until a NULL pointer is returned by the source function, or an
+   error occurs. */
+      while( card && astOK ){
+
+/* Store the card in the FitsChan. */
+         astPutFits( this, card, 0 );
+
+/* Free the memory holding the header card. */
+         card = (char *) astFree( (void *) card );
+
+/* Obtain the next header card. Also store the channel data pointer in a
+   global variable so that it can be accessed in the source function using
+   macro astChannelData. */
+         astStoreChannelData( this );
+         LOCK_MUTEX2;
+         card = ( *this->source_wrap )( source, status );
+         UNLOCK_MUTEX2;
+      }
+
+/* Set the current card index so that the first of the new cards will be the
+   next card to be read from the FitsChan. */
+      astSetCard( this, icard );
+   }
+}
+
+static void RemoveTables( AstFitsChan *this, const char *key, int *status ){
+
+/*
+*++
+*  Name:
+c     astRemoveTables
+f     AST_REMOVETABLES
+
+*  Purpose:
+*     Remove one or more tables from a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     void astRemoveTables( AstFitsChan *this, const char *key )
+f     CALL AST_REMOVETABLES( THIS, KEY, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function
+f     This routine
+*     removes the named tables from the FitsChan, it they exist (no error
+*     is reported if any the tables do not exist).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The key indicating which tables to exist. A single key or a
+*        comma-separated list of keys can be supplied. If a blank string
+*        is supplied, all tables are removed.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+*--
+*/
+
+/* Local variables: */
+   char **words;
+   int itable;
+   int ntable;
+
+/* Return if the global error status has been set, or the FitsChan
+   contains no tables KeyMap. */
+   if( !astOK || !this->tables ) return;
+
+/* If the string is blank, remove all tables. */
+   if( astChrLen( key ) == 0 ) {
+      ntable = astMapSize( this->tables );
+      for( itable = 0; itable < ntable; itable++ ) {
+         astMapRemove( this->tables, astMapKey( this->tables, itable ) );
+      }
+
+/* Otherwise, split the supplied comma-separated string up into individual
+   items. */
+   } else {
+      words = astChrSplitC( key, ',', &ntable );
+
+/* Attempt to remove each one, and then free the string. */
+      if( astOK ) {
+         for( itable = 0; itable < ntable; itable++ ) {
+            astMapRemove( this->tables, words[ itable ] );
+            words[ itable ] = astFree( words[ itable ] );
+         }
+      }
+
+/* Free the list. */
+      words = astFree( words );
+   }
+}
+
+static void RetainFits( AstFitsChan *this, int *status ){
+
+/*
+*++
+*  Name:
+c     astRetainFits
+f     AST_RETAINFITS
+
+*  Purpose:
+*     Indicate that the current card in a FitsChan should be retained.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+c     void astRetainFits( AstFitsChan *this )
+f     CALL AST_RETAINFITS( THIS, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function
+f     This routine
+*     stores a flag with the current card in the FitsChan indicating that
+*     the card should not be removed from the FitsChan when an Object is
+*     read from the FitsChan using
+c     astRead.
+f     AST_READ.
+*
+*     Cards that have not been flagged in this way are removed when a
+*     read operation completes succesfully, but only if the card was used
+*     in the process of creating the returned AST Object. Any cards that
+*     are irrelevant to the creation of the AST Object are retained whether
+*     or not they are flagged.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - This function returns without action if the FitsChan is
+*     initially positioned at the "end-of-file" (i.e. if the Card
+*     attribute exceeds the number of cards in the FitsChan).
+*     - The current card is not changed by this function.
+*--
+*/
+
+/* Local variables: */
+   int flags;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Return if the global error status has been set, or the current card
+   is not defined. */
+   if( !astOK || !this->card ) return;
+
+/* Set the PROTECTED flag in the current card. */
+   flags = ( (FitsCard *) this->card )->flags;
+   ( (FitsCard *) this->card )->flags = flags | PROTECTED;
+}
+
+static void RoundFString( char *text, int width, int *status ){
+/*
+*  Name:
+*     RoundString
+
+*  Purpose:
+*     Modify a formatted floating point number to round out long
+*     sequences of zeros or nines.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void RoundFString( char *text, int width )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The supplied string is assumed to be a valid decimal representation of
+*     a floating point number. It is searched for sub-strings consisting
+*     of NSEQ or more adjacent zeros, or NSEQ or more adjacent nines. If found
+*     the string is modified to represent the result of rounding the
+*     number to remove the sequence of zeros or nines.
+
+*  Parameters:
+*     text
+*        The formatted number. Modified on exit to round out long
+*        sequences of zeros or nines. The returned string is right justified.
+*     width
+*        The minimum field width to use. The value is right justified in
+*        this field width. Ignored if zero.
+*/
+
+/* Local Constants: */
+#define NSEQ  4    /* No. of adjacent 0's or 9's to produce rounding */
+
+/* Local Variables: */
+   char *a;
+   char *c;
+   char *dot;
+   char *exp;
+   char *last;
+   char *start;
+   char *end;
+   int i;
+   int neg;
+   int nnine;
+   int nonzero;
+   int nzero;
+   int replace;
+   int started;
+   int len;
+   int bu;
+   int nls;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Save the original length of the text. */
+   len = strlen( text );
+
+/* Locate the start of any exponent string. */
+   exp = strpbrk( text, "dDeE" );
+
+/* First check for long strings of adjacent zeros.
+   =============================================== */
+
+/* Indicate that we have not yet found a decimal point in the string. */
+   dot = NULL;
+
+/* The "started" flag controls whether *leading* zeros should be removed
+   if there are more than NSEQ of them. They are only removed if there is an
+   exponent. */
+   started = ( exp != NULL );
+
+/* We are not currently replacing digits with zeros. */
+   replace = 0;
+
+/* We have not yet found any adjacent zeros. */
+   nzero = 0;
+
+/* We have no evidence yet that the number is non-zero. */
+   nonzero = 0;
+
+/* Loop round the supplied text string. */
+   c = text;
+   while( *c && c != exp ){
+
+/* If this is a zero, increment the number of adjacent zeros found, so
+   long as we have previously found a non-zero digit (or there is an
+   exponent). If this is the NSEQ'th adjacent zero, indicate that
+   subsequent digits should be replaced by zeros. */
+      if( *c == '0' ){
+         if( started && ++nzero >= NSEQ ) replace = 1;
+
+/* Note if the number contains a decimal point. */
+      } else if( *c == '.' ){
+         dot = c;
+
+/* If this character is a non-zero digit, indicate that we have found a
+   non-zero digit. If we have previously found a long string of adjacent
+   zeros, replace the digit by '0'. Otherwise, reset the count of
+   adjacent zeros, and indicate the final number is non-zero. */
+      } else if( *c != ' ' && *c != '+' && *c != '-' ){
+         started = 1;
+         if( replace ) {
+            *c = '0';
+         } else {
+            nzero = 0;
+            nonzero = 1;
+         }
+      }
+
+/* Move on to the next character. */
+      c++;
+   }
+
+/* If the final number is zero, just return the most simple decimal zero
+   value. */
+   if( !nonzero ) {
+      strcpy( text, "0.0" );
+
+/* Otherwise, we remove any trailing zeros which occur to the right of a
+   decimal point. */
+   } else if( dot ) {
+
+/* Find the last non-zero digit. */
+      while( c-- > text && *c == '0' );
+
+/* If any trailing zeros were found... */
+      if( c > text ) {
+
+/* Retain one trailing zero after a decimal point. */
+         if( *c == '.' ) c++;
+
+/* We put a terminator following the last non-zero character. The
+   terminator is the exponent, if there was one, or a null character.
+   Remember to update the pointer to the start of the exponent. */
+         c++;
+         if( exp ) {
+            a = exp;
+            exp = c;
+            while( ( *(c++) = *(a++) ) );
+         } else {
+            *c = 0;
+         }
+      }
+   }
+
+/* Next check for long strings of adjacent nines.
+   ============================================= */
+
+/* We have not yet found any adjacent nines. */
+   nnine = 0;
+
+/* We have not yet found a non-nine digit. */
+   a = NULL;
+
+/* We have not yet found a non-blank character */
+   start = NULL;
+   last = NULL;
+
+/* Number is assumed positive. */
+   neg = 0;
+
+/* Indicate that we have not yet found a decimal point in the string. */
+   dot = NULL;
+
+/* Loop round the supplied text string. */
+   c = text;
+   while( *c && c != exp ){
+
+/* Note the address of the first non-blank character. */
+      if( !start && *c != ' ' ) start = c;
+
+/* If this is a nine, increment the number of adjacent nines found. */
+      if( *c == '9' ){
+         ++nnine;
+
+/* Note if the number contains a decimal point. */
+      } else if( *c == '.' ){
+         dot = c;
+
+/* Note if the number is negative. */
+      } else if( *c == '-' ){
+         neg = 1;
+
+/* If this character is a non-nine digit, and we have not had a long
+   sequence of 9's, reset the count of adjacent nines, and update a pointer
+   to "the last non-nine digit prior to a long string of nines". */
+      } else if( *c != ' ' && *c != '+' ){
+         if( nnine < NSEQ ) {
+            nnine = 0;
+            a = c;
+         }
+      }
+
+/* Note the address of the last non-blank character. */
+      if( *c != ' ' ) last = c;
+
+/* Move on to the next character. */
+      c++;
+   }
+
+/* If a long string of adjacent nines was found... */
+   if( nnine >= NSEQ ) {
+      c = NULL;
+
+/* If we found at least one non-nine digit. */
+      if( a ) {
+
+/* "a" points to the last non-nine digit before the first of the group of 9's.
+   Increment this digit by 1. Since we know the digit is not a nine, there
+   is no danger of a carry. */
+         *a = *a + 1;
+
+/* Fill with zeros up to the decimal point, or to  the end if there is no
+   decimal point. */
+         c = a + 1;
+         if( dot ) {
+            while( c < dot ) *(c++) = '0';
+         } else {
+            while( *c ) *(c++) = '0';
+         }
+
+/* Now make "c" point to the first character for the terminator. This is
+   usually the character following the last non-nine digit. However, if
+   the last non-nine digit appears immediately before a decimal point, then
+   we append ".0" to the string before appending the terminator. */
+         if( *c == '.' ) {
+            *(++c) = '0';
+            c++;
+         }
+
+/* If all digits were nines, the rounded number will occupy one more
+   character than the supplied number. We can only do the rounding if there
+   is a spare character (i.e.a space) in the supplied string. */
+      } else if( last - start + 1 < len ) {
+
+/* Put the modified text at the left of the available space. */
+         c = text;
+
+/* Start with a minus sing if needed, followed by the leading "1" (caused
+   by the overflow from the long string of 9's). */
+         if( neg ) *(c++) = '-';
+         *(c++) = '1';
+
+/* Now find the number of zeros to place after the leading "1". This is
+   the number of characters in front of the terminator marking the end of
+   the integer part of the number. */
+         if( dot ) {
+            nzero = dot - start;
+         } else if( exp ) {
+            nzero = exp - start;
+         } else {
+            nzero = last - start;
+         }
+
+/* If the number is negative, the above count will include the leading
+   minus sign, which is not a digit. So reduce the count by one. */
+         if( neg ) nzero--;
+
+/* Now put in the correct number of zeros. */
+         for( i = 0; i < nzero; i++ ) *(c++) = '0';
+
+/* If the original string containsed a decimal point, make sure the
+   returned string also contains one. */
+         if( dot ) {
+            *(c++) = '.';
+            if( *c ) *(c++) = '0';
+         }
+      }
+
+/* We put a terminator following the last non-zero character. The
+   terminator is the exponent, if there was one, or a null character. */
+      if( c ) {
+         if( exp ) {
+            while( ( *(c++) = *(exp++) ) );
+         } else {
+            *c = 0;
+         }
+      }
+   }
+
+/* Right justify the returned string in the original field width. */
+   end = text + len;
+   c = text + strlen( text );
+   if( c != end ) {
+      while( c >= text ) *(end--) = *(c--);
+      while( end >= text ) *(end--) = ' ';
+   }
+
+/* If a minimum field width was given, shunt the text to the left in
+   order to reduce the used field width to the specified value. This
+   requires there to be some leading spaces (because we do not want to
+   loose any non-blank characters from the left hand end of the string).
+   If there are insufficient leading spaces to allow the field width to
+   be reduced to the specified value, then reduce the field width as far
+   as possible. First find the number of spaces we would like to remove
+   from the front of the string (in order to reduce the used width to the
+   specified value). */
+   bu = len - width;
+
+/* If we need to remove any leading spaces... */
+   if( width > 0 && bu > 0 ) {
+
+/* Find the number of leading spaces which are available to be removed. */
+      c = text - 1;
+      while( *(++c) == ' ' );
+      nls = c - text;
+
+/* If there are insufficient leading spaces, just use however many there
+   are. */
+      if( bu > nls ) bu = nls;
+
+/* Shift the string. */
+      c = text;
+      a = c + bu;
+      while( ( *(c++) = *(a++) ) );
+   }
+
+/* Undefine local constants. */
+#undef NSEQ
+}
+
+static int SAOTrans( AstFitsChan *this, AstFitsChan *out, const char *method,
+                     const char *class, int *status ){
+/*
+*  Name:
+*     SAOTrans
+
+*  Purpose:
+*     Translate an SAO encoded header into a TPN encoded header.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int SAOTrans( AstFitsChan *this, AstFitsChan *out, const char *method,
+*                   const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Search "this" for keywords that give a description of a distorted
+*     TAN projection using the SAO representation and, if found, write
+*     keywords to "out" that describe an equivalent projection using TPN
+*     representation. The definition of the SAO polynomial is taken from
+*     the platepos.c file included in Doug Mink's WCSTools.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan to read.
+*     out
+*        Pointer to a FitsCHan in which to store translated keywords.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if "this" contained an SAO encoded header. Zero otherwise.
+
+*/
+
+#define NC 13
+
+/* Local Variables: */
+   char keyname[10];
+   double co[ 2 ][ NC ];
+   double pv;
+   int i;
+   int is_sao;
+   int m;
+   int ok;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Check there are exactly two CTYPE keywords in the header. */
+   if( 2 == astKeyFields( this, "CTYPE%d", 0, NULL, NULL ) ){
+
+/* Initialise all cooefficients. */
+      memset( co, 0, sizeof( co ) );
+
+/* Get the required SAO keywords. */
+      is_sao = 1;
+      ok = 1;
+      for( i = 0; i < 2 && ok && is_sao; i++ ) {
+
+         ok = 0;
+         for( m = 0; m < NC; m++ ) {
+
+/* Get the value of the next "COi_j" keyword. If any of the first 3 values
+   are missing on either axis, we assume this is not an SAO header. */
+            sprintf( keyname, "CO%d_%d", i + 1, m + 1 );
+            if( !GetValue( this, keyname, AST__FLOAT, &co[ i ][ m ], 0, 1, method,
+                           class, status ) ) {
+               if( m < 3 ) is_sao = 0;
+               break;
+            }
+
+/* Check that we have at least one non-zero coefficient (excluding the
+   first constant term ). */
+            if( co[ i ][ m ] != 0.0 && m > 0 ) ok = 1;
+         }
+      }
+
+/* If this is an SAO header..  */
+      if( is_sao ) {
+
+/* Issue a warning if all coefficients for this axis are zero. */
+         if( !ok ) {
+            Warn( this, "badpv", "This FITS header describes an SAO encoded "
+                  "distorted TAN projection, but all the distortion "
+                  "coefficients for at least one axis are zero.", method, class,
+                  status );
+
+/* Otherwise, calculate and store the equivalent PV projection parameters. */
+         } else {
+            pv = co[ 0 ][ 0 ];
+            if( pv != AST__BAD ) SetValue( out, "PV1_0", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = co[ 0 ][ 1 ];
+            if( pv != AST__BAD ) SetValue( out, "PV1_1", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = co[ 0 ][ 2 ];
+            if( pv != AST__BAD ) SetValue( out, "PV1_2", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 0 ][ 3 ] != AST__BAD ) pv += co[ 0 ][ 3 ];
+            if( co[ 0 ][ 10 ] != AST__BAD ) pv += co[ 0 ][ 10 ];
+            if( pv != AST__BAD ) SetValue( out, "PV1_4", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = co[ 0 ][ 5 ];
+            if( pv != AST__BAD ) SetValue( out, "PV1_5", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 0 ][ 4 ] != AST__BAD ) pv += co[ 0 ][ 4 ];
+            if( co[ 0 ][ 10 ] != AST__BAD ) pv += co[ 0 ][ 10 ];
+            if( pv != AST__BAD ) SetValue( out, "PV1_6", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 0 ][ 6 ] != AST__BAD ) pv += co[ 0 ][ 6 ];
+            if( co[ 0 ][ 11 ] != AST__BAD ) pv += co[ 0 ][ 11 ];
+            if( pv != AST__BAD ) SetValue( out, "PV1_7", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 0 ][ 8 ] != AST__BAD ) pv += co[ 0 ][ 8 ];
+            if( co[ 0 ][ 12 ] != AST__BAD ) pv += co[ 0 ][ 12 ];
+            if( pv != AST__BAD ) SetValue( out, "PV1_8", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 0 ][ 9 ] != AST__BAD ) pv += co[ 0 ][ 9 ];
+            if( co[ 0 ][ 11 ] != AST__BAD ) pv += co[ 0 ][ 11 ];
+            if( pv != AST__BAD ) SetValue( out, "PV1_9", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 0 ][ 7 ] != AST__BAD ) pv += co[ 0 ][ 7 ];
+            if( co[ 0 ][ 12 ] != AST__BAD ) pv += co[ 0 ][ 12 ];
+            if( pv != AST__BAD ) SetValue( out, "PV1_10", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = co[ 1 ][ 0 ];
+            if( pv != AST__BAD ) SetValue( out, "PV2_0", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = co[ 1 ][ 2 ];
+            if( pv != AST__BAD ) SetValue( out, "PV2_1", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = co[ 1 ][ 1 ];
+            if( pv != AST__BAD ) SetValue( out, "PV2_2", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 1 ][ 4 ] != AST__BAD ) pv += co[ 1 ][ 4 ];
+            if( co[ 1 ][ 10 ] != AST__BAD ) pv += co[ 1 ][ 10 ];
+            if( pv != AST__BAD ) SetValue( out, "PV2_4", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = co[ 1 ][ 5 ];
+            if( pv != AST__BAD ) SetValue( out, "PV2_5", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 1 ][ 3 ] != AST__BAD ) pv += co[ 1 ][ 3 ];
+            if( co[ 1 ][ 10 ] != AST__BAD ) pv += co[ 1 ][ 10 ];
+            if( pv != AST__BAD ) SetValue( out, "PV2_6", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 1 ][ 7 ] != AST__BAD ) pv += co[ 1 ][ 7 ];
+            if( co[ 1 ][ 12 ] != AST__BAD ) pv += co[ 1 ][ 12 ];
+            if( pv != AST__BAD ) SetValue( out, "PV2_7", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 1 ][ 9 ] != AST__BAD ) pv += co[ 1 ][ 9 ];
+            if( co[ 1 ][ 11 ] != AST__BAD ) pv += co[ 1 ][ 11 ];
+            if( pv != AST__BAD ) SetValue( out, "PV2_8", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 1 ][ 8 ] != AST__BAD ) pv += co[ 1 ][ 8 ];
+            if( co[ 1 ][ 12 ] != AST__BAD ) pv += co[ 1 ][ 12 ];
+            if( pv != AST__BAD ) SetValue( out, "PV2_9", &pv,
+                                           AST__FLOAT, NULL, status );
+
+            pv = 0.0;
+            if( co[ 1 ][ 6 ] != AST__BAD ) pv += co[ 1 ][ 6 ];
+            if( co[ 1 ][ 11 ] != AST__BAD ) pv += co[ 1 ][ 11 ];
+            if( pv != AST__BAD ) SetValue( out, "PV2_10", &pv,
+                                           AST__FLOAT, NULL, status );
+
+/* From an example header provided by Bill Joye, it seems that the SAO
+   polynomial includes the rotation and scaling effects of the CD matrix.
+   Therefore we mark as read all CDi_j, CDELT and CROTA values. Without
+   this, the rotation and scaling would be applied twice. First, mark the
+   original values as having been used, no matter which FitsChan they are
+   in. */
+            GetValue( this, "CD1_1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "CD1_2", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "CD2_1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "CD2_2", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "PC1_1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "PC1_2", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "PC2_1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "PC2_2", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "CDELT1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "CDELT2", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "CROTA1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( this, "CROTA2", AST__FLOAT, &pv, 0, 1, method, class, status );
+
+            GetValue( out, "CD1_1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "CD1_2", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "CD2_1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "CD2_2", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "PC1_1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "PC1_2", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "PC2_1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "PC2_2", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "CDELT1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "CDELT2", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "CROTA1", AST__FLOAT, &pv, 0, 1, method, class, status );
+            GetValue( out, "CROTA2", AST__FLOAT, &pv, 0, 1, method, class, status );
+
+/* Now store new default values in the returned FitsChan. */
+            pv = 1.0;
+            SetValue( out, "PC1_1", &pv, AST__FLOAT, NULL,
+                      status );
+            SetValue( out, "PC2_2", &pv, AST__FLOAT, NULL,
+                      status );
+            SetValue( out, "CDELT1", &pv, AST__FLOAT, NULL,
+                      status );
+            SetValue( out, "CDELT2", &pv, AST__FLOAT, NULL,
+                      status );
+
+            pv = 0.0;
+            SetValue( out, "PC1_2", &pv, AST__FLOAT, NULL,
+                      status );
+            SetValue( out, "PC2_1", &pv, AST__FLOAT, NULL,
+                      status );
+
+/* Indicate we have converted an SAO header. */
+            result = 1;
+         }
+      }
+   }
+
+/* Return a flag indicating if an SAO header was found. */
+   return result;
+}
+#undef NC
+
+static int SearchCard( AstFitsChan *this, const char *name,
+                       const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     SearchCard
+
+*  Purpose:
+*     Search the whole FitsChan for a card refering to given keyword.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int SearchCard( AstFitsChan *this, const char *name,
+*                     const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Searches the whole FitsChan for a card refering to the supplied keyword,
+*     and makes it the current card. The card following the current card is
+*     checked first. If this is not the required card, then a search is
+*     performed starting with the first keyword in the FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        Pointer to a string holding the keyword name.
+*     method
+*        Pointer to string holding name of calling method.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if a card was found refering to the given
+*     keyword. Otherwise zero is returned.
+
+*  Notes:
+*     -  If a NULL pointer is supplied for "name" then the current card
+*     is left unchanged.
+*     -  The current card is set to NULL (end-of-file) if no card can be
+*     found for the supplied keyword.
+*/
+
+/* Local Variables: */
+   int ret;              /* Was a card found? */
+
+/* Check the global status, and supplied keyword name. */
+   if( !astOK || !name ) return 0;
+
+/* Indicate that no card has been found yet. */
+   ret = 0;
+
+/* The required card is very often the next card in the FitsChan, so check the
+   next card, and only search the entire FitsChan if the check fails. */
+   MoveCard( this, 1, method, class, status );
+   if( !astFitsEof( this ) &&
+       !Ustrncmp( CardName( this, status ), name, FITSNAMLEN, status ) ){
+      ret = 1;
+
+/* If the next card is not the required card, rewind the FitsChan back to
+   the first card. */
+   } else {
+      astClearCard( this );
+
+/* Attempt to find the supplied keyword, searching from the first card. */
+      ret = FindKeyCard( this, name, method, class, status );
+   }
+
+/* Return. */
+   return ret;
+}
+
+static void SetAlgCode( char *buf, const char *algcode, int *status ){
+/*
+*  Name:
+*     SetAlgCode
+
+*  Purpose:
+*    Create a non-linear CTYPE string from a system code and an algorithm
+*    code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void SetAlgCode( char *buf, const char *algcode, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     FITS-WCS paper 1 says that non-linear axes must have a CTYPE of the
+*     form "4-3" (e.g. "VRAD-TAB"). This function handles the truncation
+*     of long system codes, or the padding of short system codes.
+
+*  Parameters:
+*     buf
+*        A buffer in which is stored the system code. Modified on exit to
+*        hold the combined CTYPE value. It should have a length long
+*        enough to hold the system code and the algorithm code.
+*     algcode
+*        Pointer to a string holding the algorithm code (with a leading
+*        "-", e.g. "-TAB").
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int nc;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Pad the supplied string to at least 4 characters using "-" characters. */
+   nc = strlen( buf );
+   while( nc < 4 ) buf[ nc++ ] = '-';
+
+/* Insert the null-terminated code at position 4. */
+   strcpy( buf + 4, algcode );
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the astSetAttrib protected
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function assigns an attribute value for a FitsChan, the
+*     attribute and its value being specified by means of a string of
+
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+   const char *class;            /* Object class */
+   int ival;                     /* Integer attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+   int offset;                   /* Offset of attribute string */
+   int warn;                     /* Offset of Warnings string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Obtain the object class. */
+   class = astGetClass( this );
+
+/* Card. */
+/* ----- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "card= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetCard( this, ival );
+
+/* Encoding. */
+/* --------- */
+   } else if( nc = 0,
+        ( 0 == astSscanf( setting, "encoding=%n%*[^\n]%n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      nc = ChrLen( setting + ival, status );
+      if( !Ustrncmp( setting + ival, NATIVE_STRING, nc, status ) ){
+         astSetEncoding( this, NATIVE_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSPC_STRING, nc, status ) ){
+         astSetEncoding( this, FITSPC_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSPC_STRING2, nc, status ) ){
+         astSetEncoding( this, FITSPC_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSWCS_STRING, nc, status ) ){
+         astSetEncoding( this, FITSWCS_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSWCS_STRING2, nc, status ) ){
+         astSetEncoding( this, FITSWCS_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSIRAF_STRING, nc, status ) ){
+         astSetEncoding( this, FITSIRAF_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSIRAF_STRING2, nc, status ) ){
+         astSetEncoding( this, FITSIRAF_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSAIPS_STRING, nc, status ) ){
+         astSetEncoding( this, FITSAIPS_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSAIPS_STRING2, nc, status ) ){
+         astSetEncoding( this, FITSAIPS_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSAIPSPP_STRING, nc, status ) ){
+         astSetEncoding( this, FITSAIPSPP_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSAIPSPP_STRING2, nc, status ) ){
+         astSetEncoding( this, FITSAIPSPP_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSCLASS_STRING, nc, status ) ){
+         astSetEncoding( this, FITSCLASS_ENCODING );
+      } else if( !Ustrncmp( setting + ival, FITSCLASS_STRING2, nc, status ) ){
+         astSetEncoding( this, FITSCLASS_ENCODING );
+      } else if( !Ustrncmp( setting + ival, DSS_STRING, nc, status ) ){
+         astSetEncoding( this, DSS_ENCODING );
+      } else {
+         astError( AST__BADAT, "astSet(%s): Unknown encoding system '%s' "
+                   "requested for a %s.", status, class, setting + ival, class );
+      }
+
+/* FitsDigits. */
+/* ----------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "fitsdigits= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetFitsDigits( this, ival );
+
+/* FitsAxisOrder. */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "fitsaxisorder=%n%*[^\n]%n",
+                                 &offset, &nc ) )
+               && ( nc >= len ) ) {
+      astSetFitsAxisOrder( this, setting + offset );
+
+/* CDMatrix */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "cdmatrix= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetCDMatrix( this, ival );
+
+/* DefB1950 */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "defb1950= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetDefB1950( this, ival );
+
+/* TabOK */
+/* ----- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "tabok= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetTabOK( this, ival );
+
+/* CarLin */
+/* ------ */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "carlin= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetCarLin( this, ival );
+
+/* PolyTan */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "polytan= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetPolyTan( this, ival );
+
+/* Iwc */
+/* --- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "iwc= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetIwc( this, ival );
+
+/* Clean */
+/* ----- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "clean= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetClean( this, ival );
+
+/* Warnings. */
+/* -------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "warnings=%n%*[^\n]%n", &warn, &nc ) )
+               && ( nc >= len ) ) {
+      astSetWarnings( this, setting + warn );
+
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* If the attribute was not recognised, use this macro to report an error
+   if a read-only attribute has been specified. */
+   } else if ( MATCH( "ncard" ) ||
+               MATCH( "cardtype" ) ||
+               MATCH( "cardcomm" ) ||
+               MATCH( "cardname" ) ||
+               MATCH( "nkey" ) ||
+               MATCH( "allwarnings" ) ){
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SetCard( AstFitsChan *this, int icard, int *status ){
+
+/*
+*+
+*  Name:
+*     astSetCard
+
+*  Purpose:
+*     Set the value of the Card attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void astSetCard( AstFitsChan *this, int icard )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function sets the value of the Card attribute for the supplied
+*     FitsChan. This is the index of the next card to be read from the
+*     FitsChan. If a value of 1 or less is supplied, the first card in
+*     the FitsChan will be read next. If a value greater than the number
+*     of cards in the FitsChan is supplied, the FitsChan will be left in an
+*     "end-of-file" condition, in which no further read operations can be
+*     performed.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     icard
+*        The index of the next card to read.
+
+*  Notes:
+*     -  This function attempts to execute even if an error has occurred.
+*-
+*/
+
+/* Check the supplied object. */
+   if ( !this ) return;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Rewind the FitsChan. */
+   astClearCard( this );
+
+/* Move forward the requested number of cards. */
+   MoveCard( this, icard - 1, "astSetCard", astGetClass( this ), status );
+
+/* Return. */
+   return;
+}
+
+static void SetItem( double ****item, int i, int jm, char s, double val, int *status ){
+/*
+*  Name:
+*     SetItem
+
+*  Purpose:
+*     Store a value for a axis keyword value in a FitStore structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void SetItem( double ****item, int i, int jm, char s, double val, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The supplied keyword value is stored in the specified array,
+*     at a position indicated by the axis and co-ordinate version.
+*     The array is created or extended as necessary to make room for
+*     the new value. Any old value is over-written.
+
+*  Parameters:
+*     item
+*        The address of the pointer within the FitsStore which locates the
+*        arrays of values for the required keyword (eg &(store->crval) ).
+*        The array located by the supplied pointer contains a vector of
+*        pointers. Each of these pointers is associated with a particular
+*        co-ordinate version (s), and locates an array of pointers for that
+*        co-ordinate version. Each such array of pointers has an element
+*        for each intermediate axis number (i), and the pointer locates an
+*        array of axis keyword values. These arrays of keyword values have
+*        one element for every pixel axis (j) or projection parameter (m).
+*     i
+*        The zero based intermediate axis index in the range 0 to 98. Set
+*        this to zero for keywords (e.g. CRPIX) which are not indexed by
+*        intermediate axis number.
+*     jm
+*        The zero based pixel axis index (in the range 0 to 98) or parameter
+*        index (in the range 0 to WCSLIB__MXPAR-1). Set this to zero for
+*        keywords (e.g. CRVAL) which are not indexed by either pixel axis or
+*        parameter number.
+*     val
+*        The keyword value to store.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int el;               /* Array index */
+   int nel;              /* Number of elements in array */
+   int si;               /* Integer co-ordinate version index */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Convert the character co-ordinate version into an integer index, and
+   check it is within range. The primary axis description (s=' ') is
+   given index zero. 'A' is 1, 'B' is 2, etc. */
+   if( s == ' ' ) {
+      si = 0;
+   } else if( islower(s) ){
+      si = (int) ( s - 'a' ) + 1;
+   } else {
+      si = (int) ( s - 'A' ) + 1;
+   }
+   if( si < 0 || si > 26 ) {
+      astError( AST__INTER, "SetItem(fitschan): AST internal error; "
+                "co-ordinate version '%c' ( char(%d) ) is invalid.", status, s, s );
+
+/* Check the intermediate axis index is within range. */
+   } else if( i < 0 || i > 98 ) {
+      astError( AST__INTER, "SetItem(fitschan): AST internal error; "
+                "intermediate axis index %d is invalid.", status, i );
+
+/* Check the pixel axis or parameter index is within range. */
+   } else if( jm < 0 || jm > 99 ) {
+      astError( AST__INTER, "SetItem(fitschan): AST internal error; "
+                "pixel axis or parameter index %d is invalid.", status, jm );
+
+/* Otherwise proceed... */
+   } else {
+
+/* Store the current number of coordinate versions in the supplied array */
+      nel = astSizeOf( (void *) *item )/sizeof(double **);
+
+/* If required, extend the array located by the supplied pointer so that
+   it is long enough to hold the specified co-ordinate version. */
+      if( nel < si + 1 ){
+         *item = (double ***) astGrow( (void *) *item, si + 1,
+                                      sizeof(double **) );
+
+/* Check the pointer can be used. */
+         if( astOK ){
+
+/* Initialise the new elements to hold NULL. Note, astGrow may add more
+   elements to the array than is actually needed, so use the actual current
+   size of the array as implied by astSize rather than the index si. */
+            for( el = nel;
+                 el < astSizeOf( (void *) *item )/sizeof(double **);
+                 el++ ) (*item)[el] = NULL;
+         }
+      }
+
+/* If the above went OK... */
+      if( astOK ){
+
+/* Store the currrent number of intermediate axes in the supplied array */
+         nel = astSizeOf( (void *) (*item)[si] )/sizeof(double *);
+
+/* If required, extend the array so that it is long enough to hold the
+   specified intermediate axis. */
+         if( nel < i + 1 ){
+            (*item)[si] = (double **) astGrow( (void *) (*item)[si], i + 1,
+                                      sizeof(double *) );
+
+/* Check the pointer can be used. */
+            if( astOK ){
+
+/* Initialise the new elements to hold NULL. */
+               for( el = nel;
+                    el < astSizeOf( (void *) (*item)[si] )/sizeof(double *);
+                    el++ ) (*item)[si][el] = NULL;
+            }
+         }
+
+/* If the above went OK... */
+         if( astOK ){
+
+/* Store the current number of pixel axis or parameter values in the array. */
+            nel = astSizeOf( (void *) (*item)[si][i] )/sizeof(double);
+
+/* If required, extend the array so that it is long enough to hold the
+   specified axis. */
+            if( nel < jm + 1 ){
+               (*item)[si][i] = (double *) astGrow( (void *) (*item)[si][i],
+                                                    jm + 1, sizeof(double) );
+
+/* Check the pointer can be used. */
+               if( astOK ){
+
+/* Initialise the new elements to hold AST__BAD. */
+                  for( el = nel;
+                       el < astSizeOf( (void *) (*item)[si][i] )/sizeof(double);
+                       el++ ) (*item)[si][i][el] = AST__BAD;
+               }
+            }
+
+/* If the above went OK, store the supplied keyword value. */
+            if( astOK ) (*item)[si][i][jm] = val;
+         }
+      }
+   }
+}
+
+static void SetItemC( char *****item, int i, int jm, char s, const char *val,
+                      int *status ){
+/*
+*  Name:
+*     SetItemC
+
+*  Purpose:
+*     Store a character string for an axis keyword value in a FitStore
+*     structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void SetItemC( char *****item, int i, int jm, char s, const char *val,
+*                    int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The supplied keyword string value is stored in the specified array,
+*     at a position indicated by the axis and co-ordinate version.
+*     The array is created or extended as necessary to make room for
+*     the new value. Any old value is over-written.
+
+*  Parameters:
+*     item
+*        The address of the pointer within the FitsStore which locates the
+*        arrays of values for the required keyword (eg &(store->ctype) ).
+*        The array located by the supplied pointer contains a vector of
+*        pointers. Each of these pointers is associated with a particular
+*        co-ordinate version (s), and locates an array of pointers for that
+*        co-ordinate version. Each such array of pointers has an element
+*        for each intermediate axis number (i), and the pointer locates an
+*        array of axis keyword string pointers. These arrays of keyword
+*        string pointers have one element for every pixel axis (j) or
+*        projection parameter (m).
+*     i
+*        The zero based intermediate axis index in the range 0 to 98. Set
+*        this to zero for keywords (e.g. RADESYS) which are not indexed by
+*        intermediate axis number.
+*     jm
+*        The zero based pixel axis index (in the range 0 to 98) or parameter
+*        index (in the range 0 to WCSLIB__MXPAR-1). Set this to zero for
+*        keywords (e.g. CTYPE) which are not indexed by either pixel axis or
+*        parameter number.
+*     val
+*        The keyword string value to store. A copy of the supplied string
+*        is taken.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int el;               /* Array index */
+   int nel;              /* Number of elements in array */
+   int si;               /* Integer co-ordinate version index */
+
+/* Check the inherited status and the supplied pointer. */
+   if( !astOK || !val ) return;
+
+/* Convert the character co-ordinate version into an integer index, and
+   check it is within range. The primary axis description (s=' ') is
+   given index zero. 'A' is 1, 'B' is 2, etc. */
+   if( s == ' ' ) {
+      si = 0;
+   } else if( islower(s) ){
+      si = (int) ( s - 'a' ) + 1;
+   } else {
+      si = (int) ( s - 'A' ) + 1;
+   }
+   if( si < 0 || si > 26 ) {
+      astError( AST__INTER, "SetItemC(fitschan): AST internal error; "
+                "co-ordinate version '%c' ( char(%d) ) is invalid.", status, s, s );
+
+/* Check the intermediate axis index is within range. */
+   } else if( i < 0 || i > 98 ) {
+      astError( AST__INTER, "SetItemC(fitschan): AST internal error; "
+                "intermediate axis index %d is invalid.", status, i );
+
+/* Check the pixel axis or parameter index is within range. */
+   } else if( jm < 0 || jm > 99 ) {
+      astError( AST__INTER, "SetItemC(fitschan): AST internal error; "
+                "pixel axis or parameter index %d is invalid.", status, jm );
+
+/* Otherwise proceed... */
+   } else {
+
+/* Store the current number of coordinate versions in the supplied array */
+      nel = astSizeOf( (void *) *item )/sizeof(char ***);
+
+/* If required, extend the array located by the supplied pointer so that
+   it is long enough to hold the specified co-ordinate version. */
+      if( nel < si + 1 ){
+         *item = (char ****) astGrow( (void *) *item, si + 1,
+                                      sizeof(char ***) );
+
+/* Check the pointer can be used. */
+         if( astOK ){
+
+/* Initialise the new elements to hold NULL. Note, astGrow may add more
+   elements to the array than is actually needed, so use the actual current
+   size of the array as implied by astSize rather than the index si. */
+            for( el = nel;
+                 el < astSizeOf( (void *) *item )/sizeof(char ***);
+                 el++ ) (*item)[el] = NULL;
+         }
+      }
+
+/* If the above went OK... */
+      if( astOK ){
+
+/* Store the currrent number of intermediate axes in the supplied array */
+         nel = astSizeOf( (void *) (*item)[si] )/sizeof(char **);
+
+/* If required, extend the array so that it is long enough to hold the
+   specified intermediate axis. */
+         if( nel < i + 1 ){
+            (*item)[si] = (char ***) astGrow( (void *) (*item)[si], i + 1,
+                                              sizeof(char **) );
+
+/* Check the pointer can be used. */
+            if( astOK ){
+
+/* Initialise the new elements to hold NULL. */
+               for( el = nel;
+                    el < astSizeOf( (void *) (*item)[si] )/sizeof(char **);
+                    el++ ) (*item)[si][el] = NULL;
+            }
+         }
+
+/* If the above went OK... */
+         if( astOK ){
+
+/* Store the current number of pixel axis or parameter values in the array. */
+            nel = astSizeOf( (void *) (*item)[si][i] )/sizeof(char *);
+
+/* If required, extend the array so that it is long enough to hold the
+   specified axis. */
+            if( nel < jm + 1 ){
+               (*item)[si][i] = (char **) astGrow( (void *) (*item)[si][i],
+                                                    jm + 1, sizeof(char *) );
+
+/* Check the pointer can be used. */
+               if( astOK ){
+
+/* Initialise the new elements to hold NULL. */
+                  for( el = nel;
+                       el < astSizeOf( (void *) (*item)[si][i] )/sizeof(char *);
+                       el++ ) (*item)[si][i][el] = NULL;
+               }
+            }
+
+/* If the above went OK... */
+            if( astOK ){
+
+/* Store a copy of the supplied string, using any pre-allocated memory. */
+               (*item)[si][i][jm] = (char *) astStore( (void *) (*item)[si][i][jm],
+                                                       (void *) val,
+                                                       strlen( val ) + 1 );
+            }
+         }
+      }
+   }
+}
+
+static void SetSourceFile( AstChannel *this_channel, const char *source_file,
+                           int *status ) {
+/*
+*  Name:
+*     SetSourceFile
+
+*  Purpose:
+*     Set a new value for the SourceFile attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void SetSourceFile( AstChannel *this, const char *source_file,
+*                         int *status )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the astSetSourceFile
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function stores the supplied string as the new value for the
+*     SourceFile attribute. In addition, it also attempts to open the
+*     file, read FITS headers from it and append them to the end of the
+*     FitsChan. It then closes the SourceFile.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     source_file
+*        The new attribute value. Should be the path to an existing text
+*        file, holding FITS headers (one per line)
+*     status
+*        Inherited status pointer.
+
+*/
+
+/* Local Constants: */
+#define ERRBUF_LEN 80
+
+/* Local Variables: */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+   FILE *fd;                     /* Descriptor for source file */
+   char *errstat;                /* Pointer for system error message */
+   char card[ AST__FITSCHAN_FITSCARDLEN + 2 ]; /* Buffer for source line */
+   char errbuf[ ERRBUF_LEN ];    /* Buffer for system error message */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Invoke the parent astSetSourceFile method to store  the supplied
+   string in the Channel structure. */
+   (*parent_setsourcefile)( this_channel, source_file, status );
+
+/* Attempt to open the file. */
+   fd = NULL;
+   if( astOK ) {
+      fd = fopen( source_file, "r" );
+      if( !fd ) {
+         if ( errno ) {
+#if HAVE_STRERROR_R
+            strerror_r( errno, errbuf, ERRBUF_LEN );
+            errstat = errbuf;
+#else
+            errstat = strerror( errno );
+#endif
+            astError( AST__RDERR, "astSetSourceFile(%s): Failed to open input "
+                      "SourceFile '%s' - %s.", status, astGetClass( this ),
+                      source_file, errstat );
+         } else {
+            astError( AST__RDERR, "astSetSourceFile(%s): Failed to open input "
+                      "SourceFile '%s'.", status, astGetClass( this ),
+                      source_file );
+         }
+      }
+   }
+
+/* Move the FitsChan to EOF */
+   astSetCard( this, INT_MAX );
+
+/* Read each line from the file, remove trailing space, and append to the
+   FitsChan. */
+   while( astOK && fgets( card, AST__FITSCHAN_FITSCARDLEN + 2, fd ) ) {
+      card[ astChrLen( card ) ] = 0;
+      astPutFits( this, card, 0 );
+   }
+
+/* Close the source file. */
+   if( fd ) fclose( fd );
+
+}
+
+static void SetTableSource( AstFitsChan *this,
+                            void (*tabsource)( void ),
+                            void (*tabsource_wrap)( void (*)( void ),
+                                                    AstFitsChan *, const char *,
+                                                    int, int, int * ),
+                            int *status ){
+
+/*
+*+
+*  Name:
+*     astSetTableSource
+
+*  Purpose:
+*     Register source and wrapper function for accessing tables in FITS files.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void astSetTableSource( AstFitsChan *this,
+*                             void (*tabsource)( void ),
+*                             void (*tabsource_wrap)( void (*)( void ),
+*                                                     AstFitsChan *, const char *,
+*                                                     int, int, int * ),
+*                             int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function registers a table source function and its wrapper. A
+*     wrapper function exists to adapt the API of the table source
+*     function to the needs of different languages. The wrapper is called
+*     from the FitsChan code. The wrapper then adjusts the arguments as
+*     required and then calls the actualy table source function.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     tabsource
+*        Pointer to the table source function. The API for this function
+*        will depend on the language, and so is cast to void here. It
+*        should be cast to the required form within the wrapper function.
+*     tabsource_wrap
+*        The wrapper function.
+*-
+*/
+
+/* Local Variables: */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+   this->tabsource = tabsource;
+   this->tabsource_wrap = tabsource_wrap;
+}
+
+static void SetValue( AstFitsChan *this, const char *keyname, void *value,
+                      int type, const char *comment, int *status ){
+
+/*
+*  Name:
+*     SetValue
+
+*  Purpose:
+*     Save a FITS keyword value, over-writing any existing keyword value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void SetValue( AstFitsChan *this, char *keyname, void *value,
+*                    int type, const char *comment, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function saves a keyword value as a card in the supplied
+*     FitsChan. Comment cards are always inserted in-front of the current
+*     card. If the keyword is not a comment card, any existing value
+*     for the keyword is over-written with the new value (even if it is
+*     marked as having been read). Otherwise, (i.e. if it is not a comment
+*     card, and no previous value exists) it is inserted in front
+*     of the current card.
+
+*  Parameters:
+*     this
+*        A pointer to the FitsChan.
+*     keyname
+*        A pointer to a string holding the keyword name.
+*     value
+*        A pointer to a buffer holding the keyword value. For strings,
+*        the buffer should hold a pointer to the character string.
+*     type
+*        The FITS data type of the supplied keyword value.
+*     comment
+*        A comment to store with the keyword.
+*     status
+*        Pointer to the inherited status variable.
+
+*   Notes:
+*     -  Nothing is stored if a NULL pointer is supplied for "value".
+*     -  If the keyword has a value of AST__BAD then nothing is stored,
+*     and an error is reported.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS     /* Declare the thread specific global data */
+   FitsCard *card;        /* Pointer to original current card */
+   const char *class;     /* Class name to include in error messages */
+   const char *method;    /* Method name to include in error messages */
+   int newcard;           /* Has the original current card been deleted? */
+   int old_ignore_used;   /* Original setting of external ignore_used variable */
+   int stored;            /* Has the keyword been stored? */
+
+/* Check the status and supplied value pointer. */
+   if( !astOK || !value ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Set up the method and class names for inclusion in error mesages. */
+   method = "astWrite";
+   class = astGetClass( this );
+
+/* Comment card are always inserted in-front of the current card. */
+   if ( type == AST__COMMENT ) {
+      SetFits( this, keyname, value, type, comment, 0, status );
+
+/* Otherwise... */
+   } else {
+
+/* Report an error if a bad value is stored for a keyword. */
+      if( type == AST__FLOAT ){
+         if( *( (double *) value ) == AST__BAD && astOK ) {
+            astError( AST__BDFTS, "%s(%s): The required FITS keyword "
+                      "\"%s\" is indeterminate.", status, method, class, keyname );
+         }
+      }
+
+/* Save a pointer to the current card. */
+      card = (FitsCard *) this->card;
+
+/* Indicate that we should not skip over cards marked as having been
+   read. */
+      old_ignore_used = ignore_used;
+      ignore_used = 0;
+
+/* Indicate that we have not yet stored the keyword value. */
+      stored = 0;
+
+/* Attempt to find a card refering to the supplied keyword. If one is
+   found, it becomes the current card. */
+      if( SearchCard( this, keyname, "astWrite", astGetClass( this ), status ) ){
+
+/* If the card which was current on entry to this function will be
+   over-written, we will need to take account of this when re-instating the
+   original current card. Make a note of this. */
+         newcard = ( card == (FitsCard *) this->card );
+
+/* Replace the current card with a card holding the supplied information. */
+         SetFits( this, keyname, value, type, comment, 1, status );
+         stored = 1;
+
+/* If we have just replaced the original current card, back up a card
+   so that the replacement card becomes the current card. */
+         if( newcard ) {
+            MoveCard( this, -1, "astWrite", astGetClass( this ), status );
+
+/* Otherwise, re-instate the original current card. */
+         } else {
+            this->card = (void *) card;
+         }
+      }
+
+/* If the keyword has not yet been stored (i.e. if it did not exist in the
+   FitsChan), re-instate the original current card and insert the new card
+   before the original current card, leaving the current card unchanged. */
+      if( !stored ) {
+         this->card = (void *) card;
+         SetFits( this, keyname, value, type, comment, 0, status );
+      }
+
+/* Re-instate the original flag indicating if cards marked as having been
+   read should be skipped over. */
+      ignore_used = old_ignore_used;
+   }
+}
+
+static void Shpc1( double xmin, double xmax, int n, double *d, double *w,
+                   int *status ){
+/*
+*  Name:
+*     Shpc1
+
+*  Purpose:
+*     Modifies a one-dimensional polynomial to scale the polynomial argument.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void Shpc1( double xmin, double xmax, int n, double *d, double *w,
+*                 int *status )
+
+*  Description:
+*     Given the coefficients of a one-dimensional polynomial P(u) defined on a
+*     unit interval (i.e. -1 <= u <= +1 ), find the coefficients of another
+*     one-dimensional polynomial Q(x) where:
+*
+*       Q(x) = P(u)
+*       u = ( 2*x - ( xmax + xmin ) ) / ( xmax - xmin )
+*
+*     That is, u is a scaled version of x, such that the unit interval in u
+*     maps onto (xmin:xmax) in x.
+
+*  Parameters:
+*     xmin
+*        X value corresponding to u = -1
+*     xmax
+*        X value corresponding to u = +1
+*     n
+*        One more than the maximum power of u within P.
+*     d
+*        An array of n elements supplied holding the coefficients of P such
+*        that the coefficient of (u^i) is held in element (i).
+*     w
+*        An array of n elements returned holding the coefficients of Q such
+*        that the coefficient of (x^i) is held in element (i).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*    - Vaguely inspired by the Numerical Recipes routine "pcshft". But the
+*    original had bugs, so I wrote this new version from first principles.
+
+*/
+
+/* Local Variables: */
+   double b;
+   double a;
+   int j;
+   int i;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Get the scale and shift terms so that u = a*x + b */
+   a = 2.0/( xmax - xmin );
+   b = ( xmin + xmax )/( xmin - xmax );
+
+/* Initialise the returned coeffs */
+   for( i = 0; i < n; i++ ) w[ i ] = 0.0;
+
+/* The supplied Polynomial is
+
+   P(u) = d0 + d1*u + d2*u^2 + ...
+
+        = d0 + u*( d1 + u*( d2 + ... u*( d{n-1} ) ) )  . . . . . (1)
+
+        = d0 + (a*x+b)*( d1 + (a*x+b)*( d2 + ... (a*x+b)*( d[n-1] ) ) )
+
+   The inner-most parenthesised expression is a polynomial of order zero
+   (a constant - d[n-1]). Store the coefficients of this zeroth order
+   polynomial in the returned array. The "w" array is used to hold the
+   coefficients of Q, i.e. coefficients of powers of "x", not "u", but
+   since the inner-most polynomial is a constant, it makes no difference
+   (x^0 == u^0 == 1). */
+   w[ 0 ] = d[ n - 1 ];
+
+/* Now loop through each remaining level of parenthetic nesting in (1). At
+   each level, the parenthesised expression represents a polynomial of order
+   "i". At the end of each pass though this loop, the returned array "w"
+   holds the coefficients of this "i"th order polynomial. So on the last
+   loop, i = n-1, "w" holds the required coefficients of Q. */
+   for( i = 1; i < n; i++ ) {
+
+/* If "R" is the polynomial at the "i-1"th level of nesting (the
+   coefficiemts of which are currently held in "w"), and "S" is the
+   polynomial at the "i"th level of nesting, we can see from (1) that:
+
+   S = d[ n - 1 - i ] + u*R
+
+   Substituting for "u", this becomes
+
+   S = d[ n - 1 - i ] + ( a*x + b )*R
+     = d[ n - 1 - i ] + a*R*x + b*R
+
+   Looking at each of these three terms in reverse order:
+
+   1) The "b*R" term is implemented by simply scaling the current contents
+   of the "w" array by "b"; in the "a*R*x" term.
+
+   2) In "a*R*x", the effect of multiplying by "x" is to move the existing
+   coefficients in "w" up one element. We then multiply the shifted
+   coefficients by "a" and add them onto the coefficients produced at
+   step 1) above.
+
+   We know that "w" still contains the initial zeros at indices higher than
+   "i" so we only need to scale the bottom "i" elements. We do not do the
+   zeroth term in this loop since there is no lower term to shift up into
+   it. */
+
+      for( j = i; j > 0; j-- ){
+         w[ j ] = b*w[ j ] + a*w[ j - 1 ];
+      }
+
+/* Now do the zeroth term. Scale the existing zeroth term by "b" as
+   required by step 1) and add on the first term, the constant
+   "d[ n - 1 - i ]". Step 2) is a no-op, since in effect the value of
+   "w[-1]" is zero. */
+      w[ 0 ] = d[ n - i - 1 ] + b*w[ 0 ];
+   }
+
+}
+
+static void ShowFits( AstFitsChan *this, int *status ){
+
+/*
+*++
+*  Name:
+c     astShowFits
+f     AST_SHOWFITS
+
+*  Purpose:
+*     Display the contents of a FitsChan on standard output.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+c     void astShowFits( AstFitsChan *this )
+f     CALL AST_SHOWFITS( THIS, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function
+f     This routine
+*     formats and displays all the cards in a FitsChan on standard output.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Declare the thread specific global data */
+   char card[ AST__FITSCHAN_FITSCARDLEN + 1]; /* Buffer for header card */
+   int icard;                   /* Current card index on entry */
+   int old_ignore_used;         /* Original value of external variable ignore_used */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Store the current card index. */
+   icard = astGetCard( this );
+
+/* Indicate that cards which have been read into an AST object should skipped
+   over by the functions which navigate the linked list of cards. */
+   old_ignore_used = ignore_used;
+   ignore_used = 1;
+
+/* Ensure that the first card in the FitsChan will be the next one to be
+   read. */
+   astSetCard( this, 1 );
+
+/* Loop round obtaining and writing out each card, until all cards have been
+   processed. */
+   while( !astFitsEof( this ) && astOK ){
+
+/* Get the current card, and display it. The call to astFindFits increments
+   the current card. */
+      if( astFindFits( this, "%f", card, 1 ) ) printf( "%s\n", card );
+   }
+
+/* Re-instate the original flag indicating if cards marked as having been
+   read should be skipped over. */
+   ignore_used = old_ignore_used;
+
+/* Set the current card index back to what it was on entry. */
+   astSetCard( this, icard );
+
+}
+
+static int Similar( const char *str1, const char *str2, int *status ){
+/*
+*  Name:
+*     Similar
+
+*  Purpose:
+*     Are two string effectively the same to human readers?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void Similar( const char *str1, const char *str2, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function returns a non-zero value if the two supplied strings
+*     are equivalent to a human reader. This is assumed to be the case if
+*     the strings are equal apart from leading and trailing white space,
+*     multiple embedded space, and case.
+
+*  Parameters:
+*     str1
+*        The first string
+*     str2
+*        The second string
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the two supplied strings are equivalent, and zero
+*     otherwise.
+*/
+
+/* Local Variables: */
+   const char *ea;         /* Pointer to end of string a */
+   const char *eb;         /* Pointer to end of string b */
+   const char *a;          /* Pointer to next character in string a */
+   const char *b;          /* Pointer to next character in string b */
+   int result;             /* Are the two strings equivalent? */
+   int ss;                 /* Skip subsequent spaces? */
+
+/* Initialise */
+   result = 0;
+
+/* Check the status and supplied value pointer. */
+   if( !astOK ) return result;
+
+/* Initialise pointers into the two strings. */
+   a = str1;
+   b = str2;
+
+/* Get a pointer to the character following the last non-blank character in
+   each string. */
+   ea = a + ChrLen( a, status ) - 1;
+   eb = b + ChrLen( b, status ) - 1;
+
+/* Set a flag indicating that spaces before the next non-blank character
+   should be ignored. */
+   ss = 1;
+
+/* Compare the strings. */
+   while( 1 ){
+
+/* Move on to the next significant character in both strings. */
+      while( a < ea && *a == ' ' && ss ) a++;
+      while( b < eb && *b == ' ' && ss ) b++;
+
+/* If one string has been exhausted but the other has not, the strings
+   are not equivalent. */
+      if( ( a < ea && b == eb ) || ( a == ea && b < eb ) ) {
+         break;
+
+/* If both strings have been exhausted simultaneously, the strings
+   are equivalent. */
+      } else if( b == eb && a == ea ) {
+         result = 1;
+         break;
+
+/* If neither string has been exhausted, compare the current character
+   for equality, ignoring case. Break if they are different. */
+      } else if( tolower( *a ) != tolower( *b ) ){
+         break;
+
+/* If the two characters are both spaces, indicate that subsequent spaces
+   should be skipped. */
+      } else if( *a == ' ' ) {
+         ss = 1;
+
+/* If the two characters are not spaces, indicate that subsequent spaces
+   should not be skipped. */
+      } else {
+         ss = 0;
+      }
+
+/* Move on to the next characters. */
+      a++;
+      b++;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void SinkWrap( void (* sink)( const char * ), const char *line, int *status ) {
+/*
+*  Name:
+*     SinkWrap
+
+*  Purpose:
+*     Wrapper function to invoke a C FitsChan sink function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void SinkWrap( void (* sink)( const char * ), const char *line, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function invokes the sink function whose pointer is
+*     supplied in order to write an output line to an external data
+*     store.
+
+*  Parameters:
+*     sink
+*        Pointer to a sink function, whose single parameter is a
+*        pointer to a const, null-terminated string containing the
+*        text to be written, and which returns void. This is the form
+*        of FitsChan sink function employed by the C language interface
+*        to the AST library.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the sink function. */
+   ( *sink )( line );
+}
+
+static AstMapping *SIPMapping( double *dim, FitsStore *store, char s,
+                               int naxes, const char *method,
+                               const char *class, int *status ){
+/*
+*  Name:
+*     SIPMapping
+
+*  Purpose:
+*     Create a Mapping descriping "-SIP" (Spitzer) distortion.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     AstMapping *SIPMapping( double *dim, FitsStore *store, char s, int naxes,
+*                             const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function uses the values in the supplied FitsStore to create a
+*     Mapping which implements the "-SIP" distortion code. This is the
+
+*     code used by the Spitzer project and is described in:
+*
+*     http://irsa.ipac.caltech.edu/data/SPITZER/docs/files/spitzer/shupeADASS.pdf
+*
+*     SIP distortion can only be applied to axes 0 and 1. Other axes are
+*     passed unchanged by the returned Mapping.
+
+*  Parameters:
+*     dim
+*        The dimensions of the array in pixels. AST__BAD is stored for
+*        each value if dimensions are not known.
+*     store
+*        A structure containing information about the requested axis
+*        descriptions derived from a FITS header.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     naxes
+*        The number of intermediate world coordinate axes (WCSAXES).
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the Mapping.
+*/
+
+/* Local Variables: */
+   AstMapping   *ret;        /* Pointer to the returned Mapping */
+   AstPolyMap *pmap;         /* PolyMap describing the distortion */
+   AstPolyMap *pmap2;        /* New PolyMap describing the distortion */
+   double ****item;          /* Address of FitsStore item to use */
+   double *c;                /* Pointer to start of coefficient description */
+   double *coeff_f;          /* Array of coeffs. for forward transformation */
+   double *coeff_i;          /* Array of coeffs. for inverse transformation */
+   double cof;               /* Coefficient value */
+   double lbnd[ 2 ];         /* Lower bounds of fitted region */
+   double ubnd[ 2 ];         /* Upper bounds of fitted region */
+   int def;                  /* Is transformation defined? */
+   int iin;                  /* Input (u or v) index */
+   int iout;                 /* Output (U or V) index */
+   int ncoeff_f;             /* No. of coeffs. for forward transformation */
+   int ncoeff_i;             /* No. of coeffs. for inverse transformation */
+   int p;                    /* Power of u or U */
+   int pmax;                 /* Max power of u or U */
+   int q;                    /* Power of v or V */
+   int qmax;                 /* Max power of v or V */
+
+/* Initialise the pointer to the returned Mapping. */
+   ret = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return ret;
+
+/* Store coefficients of the forward transformation:
+   ================================================ */
+
+/* Indicate that we have as yet no coefficients for the forward polynomials. */
+   ncoeff_f = 0;
+
+/* Indicate that we do not yet have any evidence that the forward
+   transformation is defined. */
+   def = 0;
+
+/* Allocate workspace to hold descriptions of (initially) 20 coefficients used
+   within the forward polynomials. */
+   coeff_f = astMalloc( sizeof( double )*20 );
+
+/* Store the coefficients of the polynomial which produces each output
+   axis (U or V) in turn. */
+   for( iout = 0; iout < 2; iout++ ){
+
+/* Get a pointer to the FitsStore item holding the values defining this
+   output. */
+      item = ( iout == 0 ) ? &(store->asip) : &(store->bsip);
+
+/* Get the largest powers used of u and v. */
+      pmax = GetMaxI( item, s, status );
+      qmax = GetMaxJM( item, s, status );
+
+/* Loop round all combination of powers. */
+      for( p = 0; p <= pmax; p++ ){
+         for( q = 0; q <= qmax; q++ ){
+
+/* Get the polynomial coefficient for this combination of powers. */
+            cof = GetItem( item, p, q, s, NULL, method, class, status );
+
+/* If there is no coefficient for this combination of powers, use a value
+   of zero. Otherwise indicate we have found at least one coefficient. */
+            if( cof == AST__BAD ) {
+               cof = 0.0;
+            } else {
+               def = 1;
+            }
+
+/* The distortion polynomial gives a correction to be added on to the
+   input value. On the other hand, the returned Mapping is a direct
+   transformation from input to output. Therefore increment the coefficient
+   value by 1 for the term which corresponds to the current output axis. */
+            if( p == ( 1 - iout ) && q == iout ) cof += 1.0;
+
+/* If the coefficient is not zero, store it in the array of coefficient
+   descriptions. */
+            if( cof != 0.0 ) {
+
+/* Increment the number of coefficients for the forward polynomials. */
+               ncoeff_f++;
+
+/* Ensure the "coeff_f" array is large enough to hold the new coefficient. */
+               coeff_f = astGrow( coeff_f, sizeof( double )*4, ncoeff_f );
+               if( astOK ) {
+
+/* Store it. Each coefficient is described by 4 values (since we have 2
+   inputs to the Mapping). The first is the coefficient value, the second
+   is the (1-based) index of the output to which the coefficient relates.
+   The next is the power of input 0, and the last one is the power of input 1. */
+                  c = coeff_f + 4*( ncoeff_f - 1 );
+                  c[ 0 ] = cof;
+                  c[ 1 ] = iout + 1;
+                  c[ 2 ] = p;
+                  c[ 3 ] = q;
+               }
+            }
+         }
+      }
+   }
+
+/* If no coefficients were supplied in the FitsStore, the forward
+   transformation is undefined. */
+   if( !def ) ncoeff_f = 0;
+
+/* Store coefficients of the inverse transformation:
+   ================================================ */
+
+/* Indicate that we have as yet no coefficients for the inverse polynomials. */
+   ncoeff_i = 0;
+
+/* Indicate that we do not yet have any evidence that the forward
+   transformation is defined. */
+   def = 0;
+
+/* Allocate workspace to hold descriptions of (initially) 20 coefficients used
+   within the inverse polynomials. */
+   coeff_i = astMalloc( sizeof( double )*20 );
+
+/* Store the coefficients of the polynomial which produces each input
+   axis (u or v) in turn. */
+   for( iin = 0; iin < 2; iin++ ){
+
+/* Get a pointer to the FitsStore item holding the values defining this
+   output. */
+      item = ( iin == 0 ) ? &(store->apsip) : &(store->bpsip);
+
+/* Get the largest powers used of U and V. */
+      pmax = GetMaxI( item, s, status );
+      qmax = GetMaxJM( item, s, status );
+
+/* Loop round all combination of powers. */
+      for( p = 0; p <= pmax; p++ ){
+         for( q = 0; q <= qmax; q++ ){
+
+/* Get the polynomial coefficient for this combination of powers. */
+            cof = GetItem( item, p, q, s, NULL, method, class, status );
+
+/* If there is no coefficient for this combination of powers, use a value
+   of zero. Otherwise indicate we have found at least one coefficient. */
+            if( cof == AST__BAD ) {
+               cof = 0.0;
+            } else {
+               def = 1;
+            }
+
+/* The distortion polynomial gives a correction to be added on to the
+   output value. On the other hand, the returned Mapping is a direct
+   transformation from output to input. Therefore increment the coefficient
+   value by 1 for the term which corresponds to the current input axis. */
+            if( p == ( 1 - iin ) && q == iin ) cof += 1.0;
+
+/* If the coefficient is not zero, store it in the array of coefficient
+   descriptions. */
+            if( cof != 0.0 ) {
+
+/* Increment the number of coefficients for the inverse polynomials. */
+               ncoeff_i++;
+
+/* Ensure the "coeff_i" array is large enough to hold the new coefficient. */
+               coeff_i = astGrow( coeff_i, sizeof( double )*4, ncoeff_i );
+               if( astOK ) {
+
+/* Store it. Each coefficient is described by 4 values (since we have 2
+   outputs to the Mapping). The first is the coefficient value, the second
+   is the (1-based) index of the input to which the coefficient relates. The
+   next is the power of output 0, and the last one is the power of output 1. */
+                  c = coeff_i + 4*( ncoeff_i - 1 );
+                  c[ 0 ] = cof;
+                  c[ 1 ] = iin + 1;
+                  c[ 2 ] = p;
+                  c[ 3 ] = q;
+               }
+            }
+         }
+      }
+   }
+
+/* If no coefficients were supplied in the FitsStore, the forward
+   transformation is undefined. */
+   if( !def ) ncoeff_i = 0;
+
+/* Create the returned Mapping:
+   ============================ */
+
+/* If neither transformation is defined, create a UnitMap. */
+   if( ncoeff_f == 0 && ncoeff_i == 0 ){
+      ret = (AstMapping *) astUnitMap( naxes, "", status );
+
+/* Otherwise, create a PolyMap to describe axes 0 and 1. */
+   } else {
+      pmap = astPolyMap( 2, 2, ncoeff_f, coeff_f, ncoeff_i, coeff_i, "", status );
+
+/* The inverse transformations supplied within SIP headers are often
+   inaccurate. So replace any existing inverse by sampling the supplied
+   transformation, and fitting a polynomial to the sampled positions. If
+   the fit fails to reach 0.01 pixel accuracy, forget it and rely on the
+   (slower) iterative inverse provided by the PolyMap class. Do the fit
+   over an area three times the size of the image to provide accurate
+   values outside the image.*/
+      lbnd[ 0 ] = ( dim[ 0 ] != AST__BAD ) ? -dim[ 0 ] : -1000.0;
+      lbnd[ 1 ] = ( dim[ 1 ] != AST__BAD ) ? -dim[ 1 ] : -1000.0;
+      ubnd[ 0 ] = ( dim[ 0 ] != AST__BAD ) ? 2*dim[ 0 ] : 2000.0;
+      ubnd[ 1 ] = ( dim[ 1 ] != AST__BAD ) ? 2*dim[ 1 ] : 2000.0;
+      pmap2 = astPolyTran( pmap, (ncoeff_f == 0), 0.0001, 0.01, 7, lbnd,
+                           ubnd );
+      if( pmap2 ) {
+         (void) astAnnul( pmap );
+         pmap = pmap2;
+      } else {
+         astSet( pmap, "IterInverse=1,NiterInverse=6,TolInverse=1.0E-8",
+                 status );
+      }
+
+/* Add the above Mapping in parallel with a UnitMap which passes any
+   other axes unchanged. */
+      ret = AddUnitMaps( (AstMapping *) pmap, 0, naxes, status );
+      pmap = astAnnul( pmap );
+   }
+
+/* Free resources. */
+   coeff_f = astFree( coeff_f );
+   coeff_i = astFree( coeff_i );
+
+/* Return the result. */
+   return ret;
+}
+
+static void SkyPole( AstWcsMap *map2, AstMapping *map3, int ilon, int ilat,
+                     int *wperm, char s, FitsStore *store, const char *method,
+                     const char *class, int *status ){
+/*
+*  Name:
+*     SkyPole
+
+*  Purpose:
+*     Put values for FITS keywords LONPOLE and LATPOLE into a FitsStore.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void SkyPole( AstWcsMap *map2, AstMapping *map3, int ilon, int ilat,
+*                   int *wperm, char s, FitsStore *store, const char *method,
+*                   const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function calculates values for the LONPOLE and LATPOLE FITS
+*     keywords and stores them in the supplied FitsStore. LONPOLE and
+*     LATPOLE are the longitude and latitude of the celestial north pole
+*     in native spherical coordinates.
+
+*  Parameters:
+*     map2
+*        Pointer to the Mapping from Intermediate World Coordinates to Native
+*        Spherical Coordinates.
+*     map3
+*        Pointer to the Mapping from Native Spherical Coordinates to celestial
+*        coordinates.
+*     ilon
+*        Zero-based index of longitude output from "map3".
+*     ilat
+*        Zero-based index of latitude output from "map3".
+*     wperm
+*        Pointer to an array of integers with one element for each axis of
+*        the current Frame. Each element holds the zero-based
+*        index of the FITS-WCS axis (i.e. the value of "i" in the keyword
+*        names "CTYPEi", "CRVALi", etc) which describes the Frame axis.
+*     s
+*        The co-ordinate version character. A space means the primary
+*        axis descriptions. Otherwise the supplied character should be
+*        an upper case alphabetical character ('A' to 'Z').
+*     store
+*        The FitsStore in which to store the FITS WCS keyword values.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;      /* PointSet holding intermediate wcs coords */
+   AstPointSet *pset2;      /* PointSet holding final WCS coords */
+   double **ptr1;           /* Pointer to coordinate data */
+   double **ptr2;           /* Pointer to coordinate data */
+   double alpha0;           /* Long. of fiducial point in standard system */
+   double alphap;           /* Celestial longitude of native north pole */
+   double deflonpole;       /* Default value for lonpole */
+   double delta0;           /* Lat. of fiducial point in standard system */
+   double latpole;          /* Native latitude of celestial north pole */
+   double lonpole;          /* Native longitude of celestial north pole */
+   double phi0;             /* Native longitude at fiducial point */
+   double theta0;           /* Native latitude at fiducial point */
+   int axlat;               /* Index of latitude output from "map2" */
+   int axlon;               /* Index of longitude output from "map2" */
+   int fits_ilat;           /* FITS WCS axis index for latitude axis */
+   int fits_ilon;           /* FITS WCS axis index for longitude axis */
+   int iax;                 /* Axis index */
+   int nax;                 /* Number of IWC axes */
+   int nax2;                /* Number of WCS axes */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Store the indices of the native longitude and latitude outputs of the
+   WcsMap. */
+   axlon = astGetWcsAxis( map2, 0 );
+   axlat = astGetWcsAxis( map2, 1 );
+
+/* Store the indices of the FITS WCS axes for longitude and latitude */
+   fits_ilon = wperm[ ilon ];
+   fits_ilat = wperm[ ilat ];
+
+/* To find the longitude and latitude of the celestial north pole in native
+   spherical coordinates, we will transform the coords of the celestial north
+   pole into spherical cords using the inverse of "map2", and if the resulting
+   native spherical coords differ from the default values of LONPOLE and
+   LATPOLE, we store them in the FitsStore. However, for zenithal projections,
+   any value can be used simply by introducing an extra rotation into the
+   (X,Y) projection plane. If values have been set in the WcsMap (as
+   projection parameters PVi_3 and PVi_4 for longitude axis "i") uses
+   them. Otherwise, set the values bad to indicate that the default values
+   should be used. Note, these projection parameters are used for other
+   purposes in a TPN projection. */
+   lonpole = AST__BAD;
+   latpole = AST__BAD;
+   if( astIsZenithal( map2 ) ) {
+      if( astGetWcsType( map2 ) != AST__TPN ) {
+         lonpole = astTestPV( map2, axlon, 3 ) ? astGetPV( map2, axlon, 3 )
+                                                : AST__BAD;
+         latpole = astTestPV( map2, axlon, 4 ) ? astGetPV( map2, axlon, 4 )
+                                                : AST__BAD;
+      }
+
+/* For non-zenithal projections, do the full calculation. */
+   } else {
+
+/* Allocate resources. */
+      nax = astGetNin( map2 );
+      pset1 = astPointSet( 1, nax, "", status );
+      ptr1 = astGetPoints( pset1 );
+      nax2 = astGetNout( map3 );
+      pset2 = astPointSet( 1, nax2, "", status );
+      ptr2 = astGetPoints( pset2 );
+      if( astOK ) {
+
+/* Calculate the longitude and latitude of the celestial north pole
+   in native spherical coordinates (using the inverse of map3). These
+   values correspond to the LONPOLE and LATPOLE keywords. */
+         for( iax = 0; iax < nax2; iax++ ) ptr2[ iax ][ 0 ] = 0.0;
+         ptr2[ ilat ][ 0 ] = AST__DPIBY2;
+         (void) astTransform( map3, pset2, 0, pset1 );
+
+/* Retrieve the latitude and longitude (in the standard system) of the
+   fiducial point (i.e. CRVAL), in radians. */
+         delta0 = GetItem( &(store->crval), fits_ilat, 0, s, NULL, method, class, status );
+         if( delta0 == AST__BAD ) delta0 = 0.0;
+         delta0 *= AST__DD2R;
+         alpha0 = GetItem( &(store->crval), fits_ilon, 0, s, NULL, method, class, status );
+         if( alpha0 == AST__BAD ) alpha0 = 0.0;
+         alpha0 *= AST__DD2R;
+
+/* The default value of the LATPOLE is defined by equation 8 of FITS-WCS
+   paper II (taking the +ve signs). Find this value. */
+         if( WcsNatPole( NULL, map2, alpha0, delta0, 999.0, ptr1[ axlon ],
+                         &alphap, &latpole, status ) ){
+
+/* If the default value is defined, compare it to the latitude of the
+   north pole found above. If they are equal use a bad value instead to
+   prevent an explicit keyword from being added to the FitsChan. */
+            if( EQUALANG( ptr1[ axlat ][ 0 ], latpole ) ) {
+               latpole = AST__BAD;
+            } else {
+               latpole = ptr1[ axlat ][ 0 ];
+            }
+
+/* If the default value is not defined, always store an explicit LATPOLE
+   value. */
+         } else {
+            latpole = ptr1[ axlat ][ 0 ];
+         }
+
+/* The default LONPOLE value is zero if the celestial latitude at the
+   fiducial point is greater than or equal to the native latitude at the
+   fiducial point. Otherwise, the default is (+ or -) 180 degrees. If LONPOLE
+   takes the default value, replace it with AST__BAD to prevent an explicit
+   keyword being stored in the FitsChan. */
+         GetFiducialNSC( map2, &phi0, &theta0, status );
+         lonpole = palDranrm( ptr1[ axlon ][ 0 ] );
+         if( delta0 >= theta0 ){
+            deflonpole = 0.0;
+         } else {
+            deflonpole = AST__DPI;
+         }
+         if( EQUALANG( lonpole, deflonpole ) ) lonpole = AST__BAD;
+      }
+
+/* Convert from radians to degrees. */
+      if( lonpole != AST__BAD ) lonpole *= AST__DR2D;
+      if( latpole != AST__BAD ) latpole *= AST__DR2D;
+
+/* Free resources. */
+      pset1 = astAnnul( pset1 );
+      pset2 = astAnnul( pset2 );
+   }
+
+/* Store these values. */
+   SetItem( &(store->lonpole), 0, 0, s, lonpole, status );
+   SetItem( &(store->latpole), 0, 0, s, latpole, status );
+
+/* FITS-WCS paper 2 recommends putting a copy of LONPOLE and LATPOLE in
+   projection parameters 3 and 4 associated with the longitude axis. Only do
+   this if the projection is not TPN (since this projection uses these
+   parameters for other purposes). */
+   if( astGetWcsType( map2 ) != AST__TPN ) {
+      SetItem( &(store->pv), fits_ilon, 3, s, lonpole, status );
+      SetItem( &(store->pv), fits_ilon, 4, s, latpole, status );
+   }
+}
+
+static int SkySys( AstFitsChan *this, AstSkyFrame *skyfrm, int wcstype,
+                   int wcsproj, FitsStore *store, int axlon, int axlat, char s,
+                   int isoff, const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     SkySys
+
+*  Purpose:
+*     Return FITS-WCS values describing a sky coordinate system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int SkySys( AstFitsChan *this, AstSkyFrame *skyfrm, int wcstype,
+*                 int wcsproj, FitsStore *store, int axlon, int axlat, char s,
+*                 int isoff, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function sets values for the following FITS-WCS keywords
+*     within the supplied FitsStore structure: CTYPE, CNAME, RADESYS, EQUINOX,
+*     MJDOBS, CUNIT, OBSGEO-X/Y/Z. The values are derived from the supplied
+*     SkyFrame and WcsMap.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     skyfrm
+*        A pointer to the SkyFrame to be described.
+*     wcstype
+*        The type of WCS: 0 = TAB, 1 = WcsMap projection.
+*     wcsproj
+*        An identifier for the type of WCS projection to use. Should be
+*        one of the values defined by the WcsMap class. Only used if "wcstype"
+*        is 1.
+*     store
+*        A pointer to the FitsStore structure in which to store the
+*        results.
+*     axlon
+*        The index of the FITS WCS longitude axis (i.e. the value of "i"
+*        in "CTYPEi").
+*     axlat
+*        The index of the FITS WCS latitude axis (i.e. the value of "i"
+*        in "CTYPEi").
+*     s
+*        Co-ordinate version character.
+*     isoff
+*        If greater than zero, the description to add to the FitsStore
+*        should describe offset coordinates. If less than zero, the
+*        description to add to the FitsStore should describe absolute
+*        coordinates but should include the SkyRefIs, SkyRef and SkyRefP
+*        attributes. If zero, ignore all offset coordinate info. The
+*        absolute value indicates the nature of the reference point:
+*        1 == "pole", 2 == "origin", otherwise "ignored".
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Are the keywords values in the FitsStore usable?
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS     /* Declare the thread specific global data */
+   char *label;             /* Pointer to axis label string */
+   char attr[20];           /* Buffer for AST attribute name */
+   char com[80];            /* Buffer for keyword comment */
+   char lattype[MXCTYPELEN];/* Latitude axis CTYPE value */
+   char lontype[MXCTYPELEN];/* Longitude axis CTYPE value */
+   const char *latsym;      /* SkyFrame latitude axis symbol */
+   const char *lonsym;      /* SkyFrame longitude axis symbol */
+   const char *prj_name;    /* Pointer to projection name string */
+   const char *skyref;      /* Formatted SkyRef position */
+   const char *skyrefis;    /* SkyRefIs value */
+   const char *sys;         /* Celestal coordinate system */
+   const char *timesys;     /* Timescale specified in FitsChan */
+   double ep;               /* Epoch of observation in required timescale (MJD) */
+   double ep_tdb;           /* Epoch of observation in TDB timescale (MJD) */
+   double ep_utc;           /* Epoch of observation in UTC timescale (MJD) */
+   double eq;               /* Epoch of reference equinox (MJD) */
+   double geolat;           /* Geodetic latitude of observer (radians) */
+   double geolon;           /* Geodetic longitude of observer (radians) */
+   double h;                /* Geodetic altitude of observer (metres) */
+   double skyref_lat;       /* SkyRef latitude value (rads) */
+   double skyrefp_lat;      /* SkyRefP latitude value (rads) */
+   double skyref_lon;       /* SkyRef longitude value (rads) */
+   double skyrefp_lon;      /* SkyRefP longitude value (rads) */
+   double xyz[3];           /* Geocentric position vector (in m) */
+   int defdate;             /* Can the date keywords be defaulted? */
+   int i;                   /* Character count */
+   int isys;                /* Celestial coordinate system */
+   int latax;               /* Index of latitude axis in SkyFrame */
+   int lonax;               /* Index of longitude axis in SkyFrame */
+   int ok;                  /* Do axis symbols conform to FITS-WCS CTYPE form? */
+   int old_ignore_used;     /* Original setting of external ignore_used variable */
+   int ret;                 /* Returned flag */
+
+/* Check the status. */
+   if( !astOK ) return 0;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Check we have a SkyFrame. */
+   if( !IsASkyFrame( skyfrm ) ) return 0;
+
+/* Initialise */
+   ret = 1;
+
+/* Get the equinox, epoch of observation, and system of the SkyFrame. The epoch
+   is in TDB. It is assumed the Equinox is in UTC. */
+   eq = astGetEquinox( skyfrm );
+   sys = astGetC( skyfrm, "system" );
+   ep_tdb = astTestEpoch( skyfrm ) ? astGetEpoch( skyfrm ) : AST__BAD;
+
+/* Convert the epoch to UTC. */
+   ep_utc = TDBConv( ep_tdb, AST__UTC, 1, method, class, status );
+
+/* See if the FitsChan contains a value for the TIMESYS keyword (include
+   previously used cards in the search). If so, and if it is not UTC, convert
+   the epoch to the specified time scale, and store a TIMESYS value in the
+   FitsStore. */
+   old_ignore_used = ignore_used;
+   ignore_used = 0;
+   if( GetValue( this, "TIMESYS", AST__STRING, (void *) &timesys, 0, 0, method,
+                 class, status ) && strcmp( timesys, "UTC" ) ) {
+      ep = TDBConv( ep_tdb, TimeSysToAst( this, timesys, method, class,
+                                          status ),
+                    1, method, class, status );
+      SetItemC( &(store->timesys), 0, 0, s, timesys, status );
+
+/* If no TIMESYS keyword was found in the FitsChan, or the timesys was
+   UTC, we use the UTC epoch value found above. In this case no TIMESYS value
+   need be stored in the FitsSTore since UTC is the default for TIMESYS. */
+   } else {
+      ep = ep_utc;
+   }
+
+/* Reinstate the original value for the flag that indicates whether keywords
+   in the FitsChan that have been used previously should be ignored. */
+   ignore_used = old_ignore_used;
+
+/* The MJD-OBS and DATE-OBS keywords default to the epoch of the
+   reference equinox if not supplied. Therefore MJD-OBS and DATE-OBS do
+   not need to be stored in the FitsChan if the epoch of observation is
+   the same as the epoch of the reference equinox. This can avoid
+   producing FITS headers which say unlikely things like
+   DATE-OBS = "01/01/50". Set a flag indicating if MJD-OBS and DATE-OBS
+   can be defaulted. */
+   defdate = astEQUAL( ep_utc, eq );
+
+/* Convert the equinox to a Julian or Besselian epoch. Also get the
+   reference frame and standard system. */
+   if( !Ustrcmp( sys, "FK4", status ) ){
+      eq = palEpb( eq );
+      isys = RADEC;
+      SetItemC( &(store->radesys), 0, 0, s, "FK4", status );
+   } else if( !Ustrcmp( sys, "FK4_NO_E", status ) || !Ustrcmp( sys, "FK4-NO-E", status ) ){
+      eq = palEpb( eq );
+      isys = RADEC;
+      SetItemC( &(store->radesys), 0, 0, s, "FK4-NO-E", status );
+   } else if( !Ustrcmp( sys, "FK5", status ) ){
+      eq = palEpj( eq );
+      isys = RADEC;
+      SetItemC( &(store->radesys), 0, 0, s, "FK5", status );
+   } else if( !Ustrcmp( sys, "ICRS", status ) ){
+      eq = AST__BAD;
+      isys = RADEC;
+      SetItemC( &(store->radesys), 0, 0, s, "ICRS", status );
+   } else if( !Ustrcmp( sys, "GAPPT", status ) ||
+              !Ustrcmp( sys, "Apparent", status ) ||
+              !Ustrcmp( sys, "Geocentric", status ) ){
+      eq = AST__BAD;
+      isys = RADEC;
+      SetItemC( &(store->radesys), 0, 0, s, "GAPPT", status );
+   } else if( !Ustrcmp( sys, "Helioecliptic", status ) ){
+      eq = AST__BAD;
+      isys = HECLIP;
+   } else if( !Ustrcmp( sys, "Galactic", status ) ){
+      eq = AST__BAD;
+      isys = GALAC;
+   } else if( !Ustrcmp( sys, "Supergalactic", status ) ){
+      eq = AST__BAD;
+      isys = SUPER;
+   } else if( !Ustrcmp( sys, "AzEl", status ) ){
+      eq = AST__BAD;
+      isys = AZEL;
+   } else {
+      eq = AST__BAD;
+      isys = NOCEL;
+   }
+
+/* Store these values. Only store the date if it does not take its
+   default value. */
+   SetItem( &(store->equinox), 0, 0, s, eq, status );
+   if( !defdate ) SetItem( &(store->mjdobs), 0, 0, ' ', ep, status );
+
+/* Only proceed if we have usable values */
+   if( astOK ) {
+
+/* Get the indices of the latitude and longitude axes within the
+   SkyFrame. */
+      latax = astGetLatAxis( skyfrm );
+      lonax = 1 - latax;
+
+/* The first 4 characters in CTYPE are determined by the celestial coordinate
+   system and the second 4 by the projection type. If we are describing
+   offset coordinates, then use "OFLN" and "OFLT. Otherwise use the
+   standard FITS-WCS name of the system. */
+      if( isoff > 0 ){
+         strcpy( lontype, "OFLN" );
+         strcpy( lattype, "OFLT" );
+      } else if( isys == RADEC ){
+         strcpy( lontype, "RA--" );
+         strcpy( lattype, "DEC-" );
+      } else if( isys == ECLIP ){
+         strcpy( lontype, "ELON" );
+         strcpy( lattype, "ELAT" );
+      } else if( isys == HECLIP ){
+         strcpy( lontype, "HLON" );
+         strcpy( lattype, "HLAT" );
+      } else if( isys == GALAC ){
+         strcpy( lontype, "GLON" );
+         strcpy( lattype, "GLAT" );
+      } else if( isys == SUPER ){
+         strcpy( lontype, "SLON" );
+         strcpy( lattype, "SLAT" );
+      } else if( isys == AZEL ){
+         strcpy( lontype, "AZ--" );
+         strcpy( lattype, "EL--" );
+
+/* For unknown systems, use the axis symbols within CTYPE if they conform
+   to the requirement of FITS-WCS (i.e. "xxLN/xxLT" or "xLON/xLAT") or use
+   "UNLN/UNLT" otherwise. */
+      } else {
+         latsym = astGetSymbol( skyfrm, latax );
+         lonsym = astGetSymbol( skyfrm, lonax );
+         if( astOK ) {
+
+            ok = 0;
+            if( strlen( latsym ) == 4 && strlen( lonsym ) == 4 ) {
+               if( !strcmp( latsym + 2, "LT" ) &&
+                   !strcmp( lonsym + 2, "LN" ) &&
+                   !strncmp( latsym, lonsym, 2 ) ) {
+                  ok = 1;
+               } else if( !strcmp( latsym + 1, "LAT" ) &&
+                   !strcmp( lonsym + 1, "LON" ) &&
+                   !strncmp( latsym, lonsym, 1 ) ) {
+                  ok = 1;
+               }
+            }
+
+            if( !ok ) {
+               latsym = "UNLT";
+               lonsym = "UNLN";
+            }
+
+            strncpy( lontype, lonsym, 4 );
+            for( i = strlen( lonsym ); i < 4; i++ ) {
+               lontype[ i ] = '-';
+            }
+            strncpy( lattype, latsym, 4 );
+            for( i = strlen( latsym ); i < 4; i++ ) {
+               lattype[ i ] = '-';
+            }
+         }
+      }
+
+/* Store the projection strings. */
+      prj_name = ( wcstype == 0 ) ? "-TAB" : astWcsPrjName( wcsproj );
+      if( astOK ) {
+         strcpy( lontype + 4, prj_name );
+         strcpy( lattype + 4, prj_name );
+      }
+
+/* Store the total CTYPE strings */
+      SetItemC( &(store->ctype), axlon, 0, s, lontype, status );
+      SetItemC( &(store->ctype), axlat, 0, s, lattype, status );
+
+/* Store offset coord information. */
+      if( isoff ) {
+
+/* If the description is for offset coords store suitable comments for
+   the CTYPE keywords. */
+         if( isoff > 0 ) {
+            skyref = astGetC( skyfrm, "SkyRef" );
+
+            sprintf( attr, "Symbol(%d)", axlon + 1 );
+            sprintf( com, "%s offset from %s",astGetC( skyfrm, attr )+1, skyref );
+            SetItemC( &(store->ctype_com), axlon, 0, s, com, status );
+
+            sprintf( attr, "Symbol(%d)", axlat + 1 );
+            sprintf( com, "%s offset from %s",astGetC( skyfrm, attr )+1, skyref );
+            SetItemC( &(store->ctype_com), axlat, 0, s, com, status );
+
+/* If the description is for absolute coords store the SkyFrame attribute
+   values in AST-specific keywords. */
+         } else {
+            sprintf( attr, "SkyRef(%d)", axlon + 1 );
+            skyref_lon = astGetD( skyfrm, attr );
+            sprintf( attr, "SkyRef(%d)", axlat + 1 );
+            skyref_lat = astGetD( skyfrm, attr );
+
+            sprintf( attr, "SkyRefP(%d)", axlon + 1 );
+            skyrefp_lon = astGetD( skyfrm, attr );
+            sprintf( attr, "SkyRefP(%d)", axlat + 1 );
+            skyrefp_lat = astGetD( skyfrm, attr );
+
+            skyrefis = (isoff < -2) ? "IGNORED" :
+                       ( (isoff < -1) ? "ORIGIN" : "POLE" );
+
+            SetItemC( &(store->skyrefis), 0, 0, s, skyrefis, status );
+            if( astTest( skyfrm, "SkyRef(1)" ) ) {
+               SetItem( &(store->skyref), axlon, 0, s, skyref_lon, status );
+               SetItem( &(store->skyref), axlat, 0, s, skyref_lat, status );
+            }
+            if( astTest( skyfrm, "SkyRefP(1)" ) ) {
+               SetItem( &(store->skyrefp), axlon, 0, s, skyrefp_lon, status );
+               SetItem( &(store->skyrefp), axlat, 0, s, skyrefp_lat, status );
+            }
+         }
+      }
+
+/* If the Label attribute  has been set for an axis, use it as the CTYPE
+   comment and CNAME value. */
+      if( astTestLabel( skyfrm, latax ) ) {
+         label = (char *) astGetLabel( skyfrm, latax );
+         SetItemC( &(store->ctype_com), axlat, 0, s, label, status );
+         SetItemC( &(store->cname), axlat, 0, s, label, status );
+      }
+      if( astTestLabel( skyfrm, lonax ) ) {
+         label = (char *) astGetLabel( skyfrm, lonax );
+         SetItemC( &(store->ctype_com), axlon, 0, s, label, status );
+         SetItemC( &(store->cname), axlon, 0, s, label, status );
+      }
+
+/* Nullify any CUNITS strings for the longitude and latitude axes (they
+   always take the default value of degrees). */
+      SetItemC( &(store->cunit), axlat, 0, s, NULL, status );
+      SetItemC( &(store->cunit), axlon, 0, s, NULL, status );
+   }
+
+/* Store the Domain name as the WCSNAME keyword (if set). */
+   if( astTestDomain( skyfrm ) ) {
+      SetItemC( &(store->wcsname), 0, 0, s, (char *) astGetDomain( skyfrm ), status );
+   }
+
+/* Store the observer's position if set (needed for definition of AzEl
+   systems). */
+   if( astTestObsLon( skyfrm ) && astTestObsLat( skyfrm ) && s == ' ' ) {
+      geolon = astGetObsLon( skyfrm );
+      geolat = astGetObsLat( skyfrm );
+      h = astGetObsAlt( skyfrm );
+      if( geolat != AST__BAD && geolon != AST__BAD && h != AST__BAD ) {
+         eraGd2gc( 1, geolon, geolat, h, xyz );
+         SetItem( &(store->obsgeox), 0, 0, ' ', xyz[0], status );
+         SetItem( &(store->obsgeoy), 0, 0, ' ', xyz[1], status );
+         SetItem( &(store->obsgeoz), 0, 0, ' ', xyz[2], status );
+      }
+   }
+   if( !astOK ) ret = 0;
+   return ret;
+}
+
+static char *SourceWrap( const char *(* source)( void ), int *status ) {
+/*
+*  Name:
+*     SourceWrap
+
+*  Purpose:
+*     Wrapper function to invoke a C FitsChan source function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     char *SourceWrap( const char *, int *status(* source)( void ) )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function invokes the source function whose pointer is
+*     supplied in order to read the next input line from an external
+*     data store. It then returns a pointer to a dynamic string
+*     containing a copy of the text that was read.
+
+*  Parameters:
+*     source
+*        Pointer to a source function, with no parameters, that
+*        returns a pointer to a const, null-terminated string
+*        containing the text that it read. This is the form of FitsChan
+*        source function employed by the C language interface to the
+*        AST library.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated, null terminated string
+*     containing a copy of the text that was read. This string must be
+*     freed by the caller (using astFree) when no longer required.
+*
+*     A NULL pointer will be returned if there is no more input text
+*     to read.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   char *result;                 /* Pointer value to return */
+   const char *line;             /* Pointer to input line */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the source function to read the next input line and return a
+   pointer to the resulting string. */
+   line = ( *source )();
+
+/* If a string was obtained, make a dynamic copy of it and save the
+   resulting pointer. */
+   if ( line ) result = astString( line, (int) strlen( line ) );
+
+/* Return the result. */
+   return result;
+}
+
+static AstMapping *SpectralAxes( AstFitsChan *this, AstFrameSet *fs,
+                                 double *dim, int *wperm,
+                                 char s, FitsStore *store, double *crvals,
+                                 int *axis_done, const char *method,
+                                 const char *class, int *status ){
+
+/*
+*  Name:
+*     SpectralAxes
+
+*  Purpose:
+*     Add values to a FitsStore describing spectral axes in a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     AstMapping *SpectralAxes( AstFitsChan *this, AstFrameSet *fs,
+*                               double *dim, int *wperm,
+*                               char s, FitsStore *store, double *crvals,
+*                               int *axis_done, const char *method,
+*                               const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The current Frame of the supplied FrameSet is searched for spectral
+*     axes. If any are found, FITS WCS keyword values describing the axis
+*     are added to the supplied FitsStore, if possible (the conventions
+*     of FITS-WCS paper III are used). Note, this function does not store
+*     values for keywords which define the transformation from pixel
+*     coords to Intermediate World Coords (CRPIX, PC and CDELT), but a
+*     Mapping is returned which embodies these values. This Mapping is
+*     from the current Frame in the FrameSet (WCS coords) to a Frame
+*     representing IWC. The IWC Frame has the same number of axes as the
+*     WCS Frame which may be greater than the number of base Frame (i.e.
+*     pixel) axes.
+*
+*     If a spectral axis is found, the RafRA and RefDec attributes of the
+*     SpecFrame describing the axis are ignored: it is assumed that the
+*     WCS Frame also contains a pair of celestial axes which will result
+*     in appropriate celestial reference values being stored in the
+*     FitsStore (this asumption should be enforced by calling function
+*     MakeFitsFrameSet prior to calling this function).
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     fs
+*        Pointer to the FrameSet. The base Frame should represent FITS pixel
+*        coordinates, and the current Frame should represent FITS WCS
+*        coordinates. The number of base Frame axes should not exceed the
+*        number of current Frame axes. The spectral Unit in the returned
+*        FrameSet will always be linearly related to the default Units for
+*        the spectral System in use by the axis. If this requires a
+*        change to the existing spectral Unit, the integrity of the
+*        FrameSet will be maintained by suitable adjustments to the Mappings
+*        within the FrameSet.
+*     dim
+*        An array holding the image dimensions in pixels. AST__BAD can be
+*        supplied for any unknwon dimensions.
+*     wperm
+*        Pointer to an array of integers with one element for each axis of
+*        the current Frame. Each element holds the zero-based
+*        index of the FITS-WCS axis (i.e. one les than the value of "i" in
+*        the keyword names "CTYPEi", "CRVALi", etc) which describes the
+*        Frame axis.
+*     s
+*        The co-ordinate version character. A space means the primary
+*        axis descriptions. Otherwise the supplied character should be
+*        an upper case alphabetical character ('A' to 'Z').
+*     store
+*        The FitsStore in which to store the FITS WCS keyword values.
+*     crvals
+*        Pointer to an array holding the default CRVAL value for each
+*        axis in the WCS Frame.
+*     axis_done
+*        An array of flags, one for each Frame axis, which indicate if a
+*        description of the corresponding axis has yet been stored in the
+*        FitsStore.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If a spectral axis was found which can be described using the
+*     conventions of FITS-WCS paper III, then a Mapping from the current Frame
+*     of the supplied FrameSet, to the IWC Frame is returned. Otherwise,
+*     a UnitMap is returned. Note, the Mapping only defines the IWC
+*     transformation for spectral axes. Any non-spectral axes are passed
+*     unchanged by the returned Mapping.
+*/
+
+/* Local Variables: */
+   AstFitsTable *table;    /* Pointer to structure holding -TAB table info */
+   AstFrame *pframe;       /* Primary Frame containing current WCS axis*/
+   AstFrame *tfrm1;        /* A temporary Frame */
+   AstFrame *tfrm;         /* A temporary Frame */
+   AstFrame *wcsfrm;       /* WCS Frame within FrameSet */
+   AstFrameSet *tfs;       /* A temporary FrameSet */
+   AstGrismMap *gmap;      /* GrismMap defining the spectral axis */
+   AstMapping *axmap;      /* Mapping from WCS to IWC */
+   AstMapping *map;        /* Pixel -> WCS mapping */
+   AstMapping *ret;        /* Returned Mapping */
+   AstMapping *tmap0;      /* A temporary Mapping */
+   AstMapping *tmap1;      /* A temporary Mapping */
+   AstMapping *tmap2;      /* A temporary Mapping */
+   AstMapping *tmap3;      /* A temporary Mapping */
+   AstMapping *tmap4;      /* A temporary Mapping */
+   AstMapping *tmap5;      /* A temporary Mapping */
+   AstMapping *tmap6;      /* A temporary Mapping */
+   AstPermMap *pm;         /* PermMap pointer */
+   AstSpecFrame *specfrm;  /* The SpecFrame defining current WCS axis */
+   char *cname;            /* Pointer to CNAME value */
+   char ctype[ MXCTYPELEN ]; /* The value for the FITS CTYPE keyword */
+   char lin_unit[ 20 ];    /* Linear spectral Units being used */
+   char orig_system[ 40 ]; /* Value of System attribute for current WCS axis */
+   char system_attr[ 10 ]; /* Name of System attribute for current WCS axis */
+   char unit_attr[ 10 ];   /* Name of Unit attribute for current WCS axis */
+   const char *cval;       /* Pointer to temporary character string */
+   const char *x_sys[ 4 ]; /* Basic spectral systems */
+   double *lbnd_p;         /* Pointer to array of lower pixel bounds */
+   double *ubnd_p;         /* Pointer to array of upper pixel bounds */
+   double crval;           /* The value for the FITS CRVAL keyword */
+   double dgbyds;          /* Rate of change of grism parameter wrt "S" at ref. point */
+   double dsbydx;          /* Rate of change of "S" wrt "X" at ref. point */
+   double geolat;          /* Geodetic latitude of observer (radians) */
+   double geolon;          /* Geodetic longitude of observer (radians) */
+   double gval;            /* Value of grism parameter at reference point  */
+   double h;               /* Geodetic altitude of observer (metres) */
+   double imagfreq;        /* Image sideband equivalent to the rest frequency (Hz) */
+   double lbnd_s;          /* Lower bound on spectral axis */
+   double pv;              /* Value of projection parameter */
+   double restfreq;        /* Rest frequency (Hz) */
+   double ubnd_s;          /* Upper bound on spectral axis */
+   double vsource;         /* Rel.vel. of source (m/s) */
+   double xval;            /* Value of "X" system at reference point  */
+   double xyz[3];          /* Geocentric position vector (in m) */
+   double zsource;         /* Redshift of source */
+   int *inperm;            /* Pointer to permutation array for input axes */
+   int *outperm;           /* Pointer to permutation array for output axes */
+   int extver;             /* Table version number for -TAB headers */
+   int fits_i;             /* FITS WCS axis index for current WCS axis */
+   int iax;                /* Axis index */
+   int icolindex;          /* Index of table column holding index vector */
+   int icolmain;           /* Index of table column holding main coord array */
+   int interp;             /* INterpolation method for look-up tables */
+   int ix;                 /* System index */
+   int j;                  /* Loop count */
+   int npix;               /* Number of pixel axes */
+   int nwcs;               /* Number of WCS axes */
+   int paxis;              /* Axis index within primary Frame */
+   int sourcevrf;          /* Rest Frame in which SourceVel is accesed */
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Every supported spectral system is linearly related to one of the
+   following four systems. */
+   x_sys[ 0 ] = "FREQ";
+   x_sys[ 1 ] = "WAVE";
+   x_sys[ 2 ] = "AWAV";
+   x_sys[ 3 ] = "VELO";
+
+/* Get a pointer to the WCS Frame. */
+   wcsfrm = astGetFrame( fs, AST__CURRENT );
+
+/* Store the number of pixel and WCS axes. */
+   npix = astGetNin( fs );
+   nwcs = astGetNout( fs );
+
+/* Store the upper and lower pixel bounds. */
+   lbnd_p = astMalloc( sizeof( double )*(size_t) npix );
+   ubnd_p = astMalloc( sizeof( double )*(size_t) npix );
+   if( astOK ) {
+      for( iax = 0; iax < npix; iax++ ) {
+         lbnd_p[ iax ] = 1.0;
+         ubnd_p[ iax ] = ( dim[ iax ] != AST__BAD ) ? dim[ iax ] : 500;
+      }
+   }
+
+/* Check each axis in the WCS Frame to see if it is a spectral axis. */
+   axmap = NULL;
+   for( iax = 0; iax < nwcs; iax++ ) {
+
+/* Obtain a pointer to the primary Frame containing the current WCS axis. */
+      astPrimaryFrame( wcsfrm, iax, &pframe, &paxis );
+
+/* If the current axis belongs to a SpecFrame, we have found a spectral
+   axis. */
+      if( astIsASpecFrame( pframe ) ) {
+         specfrm = (AstSpecFrame *) pframe;
+
+/* Note the (zero-based) FITS WCS axis index to be used for the current
+   Frame axis. */
+         fits_i = wperm[ iax ];
+
+/* Note the name and original value of the System attribute for the spectral
+   axis within the FrameSet current Frame. */
+         sprintf( system_attr, "System(%d)", iax + 1 );
+         cval = astGetC( wcsfrm, system_attr );
+         if( cval ) strcpy( orig_system, cval );
+
+/* Note the name of the Unit attribute for the spectral axis within the
+   FrameSet current Frame. */
+         sprintf( unit_attr, "Unit(%d)", iax + 1 );
+
+/* Get a pointer to the Mapping from FITS pixel coordinates to SpecFrame. */
+         map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+
+/* Find the bounds of the Spectral axis over the volume of the pixel grid. */
+         astMapBox( map, lbnd_p, ubnd_p, 1, iax, &lbnd_s, &ubnd_s,
+                    NULL, NULL );
+
+/* The Unit attribute of a SpecFrame can be set to arbitrary non-linear
+   functions of standard linear spectral units.  FITS-WCS paper III requires
+   CRVAL etc to be given in linear units. So first we ensure that we have a
+   SpecFrame with linear Units. Create a copy of the SpecFrame and clear
+   its Unit attribute (this ensures the copy has the default linear units).
+   Then find a Mapping from the original spectral units to the default
+   linear units. If the conversion is possible, see if the Mapping
+   between the units is linear. If it is, then the original Unit attribute
+   of the SpecFrame is OK (i.e. the units are linear). If not, clear
+   the Unit attribute of the spectral axis in the FrameSet so that it
+   uses the default linear units (retaining the original value so that it
+   can be re-instated later). Using the clear method on the FrameSet
+   pointer rather than the SpecFrame pointer causes the SpecFrame to be
+   re-mapped within the FrameSet to maintain its correct relationship with
+   the other Frames in the FrameSet. Also update the pixel->spectrum
+   Mapping to take account of the change in units and re-calculate the new
+   bounds on the spectral axis. Also update any supplied CRVAL value for
+   the spectral axis. */
+         tfrm = astCopy( specfrm );
+         astClearUnit( tfrm, 0 );
+         tfs = astConvert( specfrm, tfrm, "" );
+         tfrm = astAnnul( tfrm );
+         if( tfs ) {
+            crval = crvals ? crvals[ iax ] : AST__BAD;
+            tmap1 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
+            tfs = astAnnul( tfs );
+            if( !IsMapLinear( tmap1, &lbnd_s, &ubnd_s, 0, status ) ) {
+               astClear( fs, unit_attr );
+               (void) astAnnul( map );
+               map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+               astMapBox( map, lbnd_p, ubnd_p, 1, iax, &lbnd_s, &ubnd_s,
+                          NULL, NULL );
+               astTran1( tmap1, 1, &crval, 1, &crval );
+            }
+            tmap1 = astAnnul( tmap1 );
+
+/* Note the linear spectral Unit currently in use. */
+            cval = astGetUnit( specfrm, 0 );
+            if( cval ) strcpy( lin_unit, cval );
+
+/* For some of the algorithms, the reference value CRVAL is arbitrary.
+   For these algorithms we choose to use the supplied default CRVAL value.
+   If no default CRVAL value was suppllied, we use the mid spectral value
+   if the size of the spectral axis was given, or the lower bound (i.e.
+   pixel 1) if the size of the spectral axis was not given. */
+            if( crval == AST__BAD ) {
+               if( dim[ iax ] != AST__BAD ) {
+                  crval = 0.5*( lbnd_s + ubnd_s );
+               } else {
+                  crval = lbnd_s;
+               }
+            }
+
+/* Modify this crval value so that it correpsonds to an integer pixel
+   coordinate. */
+            crval = NearestPix( map, crval, iax, status );
+
+/* We now check to see if the Mapping from pixel coords -> linear spectral
+   coords corresponds to one of the algorithms supported in FITS-WCS paper
+   III. First check for the "linear" algorithm in which the linear spectral
+   coordinate given by the SpecFrame is related linearly to the pixel
+   coords. */
+            ctype[ 0 ] = 0;
+            if( IsMapLinear( map, lbnd_p, ubnd_p, iax, status ) ) {
+
+/* The CTYPE value is just the spectral system. */
+               strcpy( ctype, orig_system );
+
+/* Create the Mapping which defines the spectral IWC axis. This is
+   initially the Mapping from WCS to IWCS - it subtracts the CRVAL value
+   from the spectral WCS value to get the spectral IWC value (other
+   non-spectral axes are left unchanged by this Mapping). This results
+   in the spectral IWC axis having the same axis index as the spectral
+   WCS axis. */
+               crval = -crval;
+               tmap0 = (AstMapping *) astShiftMap( 1, &crval, "", status );
+               crval = -crval;
+               axmap = AddUnitMaps( tmap0, iax, nwcs, status );
+               tmap0 = astAnnul( tmap0 );
+            }
+
+/* If the "linear" algorithm above is inappropriate, see if the "non-linear"
+   algorithm defined in FITS-WCS paper III can be used, in which pixel
+   coords are linearly related to some spectral system (called "X") other
+   than the one represented by the supplied SpecFrame (called "S"). */
+            if( !ctype[ 0 ] ) {
+
+/* Loop round each of the 4 allowed X systems. All other spectral systems
+   are linearly related to one of these 4 systems and so do not need to be
+   tested. */
+               for( ix = 0; ix < 4 && !ctype[ 0 ]; ix++ ) {
+
+/* Set the system of the spectral WCS axis to the new trial X system. Clear
+   the Unit attribute to ensure we are using the default linear units.
+   Using the FrameSet pointer "fs" ensures that the Mappings within the
+   FrameSet are modified to maintain the correct inter-Frame relationships. */
+                  astSetC( fs, system_attr, x_sys[ ix ] );
+                  astClear( fs, unit_attr );
+
+/* Now we check to see if the current X system is linearly related to
+   pixel coordinates. */
+                  tmap3 = astGetMapping( fs, AST__BASE, AST__CURRENT );
+                  if( IsMapLinear( tmap3, lbnd_p, ubnd_p, iax, status ) ) {
+
+/* CTYPE: First 4 characters specify the "S" system. */
+                     strcpy( ctype, orig_system );
+
+/* The non-linear algorithm code to be appended to the "S" system is of the
+   form "-X2P" ("P" is the system which is linearly related to "S"). */
+                     if( !strcmp( x_sys[ ix ], "FREQ" ) ) {
+                        strcpy( ctype + 4, "-F2" );
+                     } else if( !strcmp( x_sys[ ix ], "WAVE" ) ) {
+                        strcpy( ctype + 4, "-W2" );
+                     } else if( !strcmp( x_sys[ ix ], "AWAV" ) ) {
+                        strcpy( ctype + 4, "-A2" );
+                     } else {
+                        strcpy( ctype + 4, "-V2" );
+                     }
+                     if( !strcmp( orig_system, "FREQ" ) ||
+                         !strcmp( orig_system, "ENER" ) ||
+                         !strcmp( orig_system, "WAVN" ) ||
+                         !strcmp( orig_system, "VRAD" ) ) {
+                        strcpy( ctype + 7, "F" );
+                     } else if( !strcmp( orig_system, "WAVE" ) ||
+                                !strcmp( orig_system, "VOPT" ) ||
+                                !strcmp( orig_system, "ZOPT" ) ) {
+                        strcpy( ctype + 7, "W" );
+                     } else if( !strcmp( orig_system, "AWAV" ) ) {
+                        strcpy( ctype + 7, "A" );
+                     } else {
+                        strcpy( ctype + 7, "V" );
+                     }
+
+/* Create a Mapping which gives S as a function of X. */
+                     tfrm = astCopy( specfrm );
+                     astSetC( tfrm, "System(1)", orig_system );
+                     astSetC( tfrm, "Unit(1)", lin_unit );
+                     tfs = astConvert( specfrm, tfrm, "" );
+                     tmap5 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
+                     tfs = astAnnul( tfs );
+                     tfrm = astAnnul( tfrm );
+
+/* Use the inverse of this Mapping to get the X value at the reference S
+   value. */
+                     astTran1( tmap5, 1, &crval, 0, &xval );
+
+/* Also use it to get the rate of change of S with respect to X at the
+   reference point. */
+                     dsbydx = astRate( tmap5, &xval, 0, 0 );
+
+/* Create the Mapping which defines the spectral IWC axis. This is the
+   Mapping from WCS to IWC - it first converts from S to X, then subtracts
+   the X reference value value, and then scales the axis to ensure that
+   the rate of change of S with respect to IWC is unity (as required by
+   FITS-WCS paper III). Other non-spectral axes are left unchanged by
+   the Mapping. The spectral IWC axis has the same axis index as the
+   spectral WCS axis. */
+                     xval = -xval;
+                     tmap2 = (AstMapping *) astShiftMap( 1, &xval, "", status );
+                     astInvert( tmap5 );
+                     tmap0 = (AstMapping *) astCmpMap( tmap5, tmap2, 1, "", status );
+                     tmap5 = astAnnul( tmap5 );
+                     tmap2 = astAnnul( tmap2 );
+                     tmap2 = (AstMapping *) astZoomMap( 1, dsbydx, "", status );
+                     tmap1 = (AstMapping *) astCmpMap( tmap0, tmap2, 1, "", status );
+                     tmap0 = astAnnul( tmap0 );
+                     tmap2 = astAnnul( tmap2 );
+                     axmap = AddUnitMaps( tmap1, iax, nwcs, status );
+                     tmap1 = astAnnul( tmap1 );
+                  }
+                  tmap3 = astAnnul( tmap3 );
+
+/* Re-instate the original system and unit attributes for the spectral axis. */
+                  astSetC( fs, system_attr, orig_system );
+                  astSetC( fs, unit_attr, lin_unit );
+               }
+            }
+
+/* If the "non-linear" algorithm above is inappropriate, see if the
+   "log-linear" algorithm defined in FITS-WCS paper III can be used, in
+   which the spectral axis is logarithmically spaced in the spectral
+   system given by the SpecFrame. */
+            if( !ctype[ 0 ] ) {
+
+/* If the "log-linear" algorithm is appropriate, the supplied SpecFrame (s)
+   is related to pixel coordinate (p) by s = Sr.EXP( a*p - b ). If this
+   is the case, then the log of s will be linearly related to pixel
+   coordinates. Test this. If the test is passed a Mapping is returned from
+   WCS to IWC. */
+               axmap = LogAxis( map, iax, nwcs, lbnd_p, ubnd_p, crval, status );
+
+/* If the axis is logarithmic... */
+               if( axmap ) {
+
+/* CTYPE: First 4 characters specify the "S" system. */
+                  strcpy( ctype, orig_system );
+
+/* The rest is "-LOG". */
+                  strcpy( ctype + 4, "-LOG" );
+               }
+            }
+
+/* If the "log-linear" algorithm above is inappropriate, see if the "grism"
+   algorithm defined in FITS-WCS paper III can be used, in which pixel
+   coords are related to wavelength using a grism dispersion function,
+   implemented in AST by a GrismMap. GrismMaps produce either vacuum
+   wavelength or air wavelength as output. Temporarily set the SpecFrame
+   to these two systems in turn before we do the check for a GrismMap. */
+            for( ix = 0; ix < 2 && !ctype[ 0 ]; ix++ ) {
+               astSetC( fs, system_attr, ( ix == 0 ) ? "WAVE" : "AWAV" );
+               astSetC( fs, unit_attr, "m" );
+
+/* Get the simplified Mapping from pixel to wavelength. If the Mapping is
+   a CmpMap containing a GrismMap, and if the output of the GrismMap is
+   scaled by a neighbouring ZoomMap (e.g. into different wavelength units),
+   then the GrismMap will be modified to incorporate the effect of the
+   ZoomMap, and the ZoomMap will be removed. */
+               tmap2 = astGetMapping( fs, AST__BASE, AST__CURRENT );
+               tmap1 = astSimplify( tmap2 );
+               tmap2 = astAnnul( tmap2 );
+
+/* Analyse this Mapping to see if the iax'th output is created diretcly by a
+   GrismMap (i.e. the output of theGrismMap must not subsequently be
+   modified by some other Mapping). If so, ExtractGrismMap returns a pointer
+   to the GrismMap as its function value, and also returns "tmap2" as a copy
+   of tmap1 in which the GrismMap has been replaced by a UnitMap. */
+               gmap = ExtractGrismMap( tmap1, iax, &tmap2, status );
+               if( gmap ) {
+
+/* The Mapping without the GrismMap must be linear on the spectral axis. */
+                  if( IsMapLinear( tmap2, lbnd_p, ubnd_p, iax, status ) ) {
+
+/* Get the reference wavelength (in "m") stored in the GrismMap. */
+                     crval = astGetGrismWaveR( gmap );
+
+/* Save a copy of the current Wavelength (in "m") SpecFrame. */
+                     tfrm1 = astCopy( specfrm );
+
+/* Re-instate the original System and Unit attributes for the SpecFrame. */
+                     astSetC( fs, system_attr, orig_system );
+                     astSetC( fs, unit_attr, lin_unit );
+
+/* Find the Mapping from the original "S" system to wavelength (in "m"). */
+                     tfs = astConvert( specfrm, tfrm1, "" );
+                     tfrm1 = astAnnul( tfrm1 );
+                     if( tfs ) {
+                        tmap3 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
+                        tfs = astAnnul( tfs );
+
+/* Use the inverse of this Mapping to convert the reference value from
+   wavelength to the "S" system. */
+                        astTran1( tmap3, 1, &crval, 0, &crval );
+
+/* Concatenate the "S"->wavelength Mapping with the inverse GrismMap (from
+   wavelength to grism parameter), to get the "S" -> "grism parameter"
+   Mapping. */
+                        astInvert( gmap );
+                        tmap4 = (AstMapping *) astCmpMap( tmap3, gmap, 1, "", status );
+                        tmap3 = astAnnul( tmap3 );
+
+/* Use this Mapping to find the grism parameter at the reference point. */
+                        astTran1( tmap4, 1, &crval, 1, &gval );
+
+/* Also use it to find the rate of change of grism parameter with respect
+   to "S" at the reference point. */
+                        dgbyds = astRate( tmap4, &crval, 0, 0 );
+
+/* FITS-WCS paper III required ds/dw to be unity at the reference point.
+   Therefore the rate of change of grism parameter with respect to IWC ("w")
+   is equal to the rate of change of grism parameter with respect to "S"
+   (at the reference point). The mapping from "w" to grism parameter is a
+   ZoomMap which scales "w" by "dgbyds" followed by a ShiftMap which adds
+   on "gval". */
+                        tmap5 = (AstMapping *) astZoomMap( 1, dgbyds, "", status );
+                        tmap6 = (AstMapping *) astShiftMap( 1, &gval, "", status );
+                        tmap3 = (AstMapping *) astCmpMap( tmap5, tmap6, 1, "", status );
+                        tmap5 = astAnnul( tmap5 );
+                        tmap6 = astAnnul( tmap6 );
+
+/* Create the Mapping which defines the spectral IWC axis. This is the
+   Mapping from WCS "S" to IWCS "w", formed by combining the Mapping from
+   "S" to grism parameter (tmap4), with the Mapping from grism parameter to
+   "w" (inverse of tmap3). Other non-spectral axes are left unchanged by the
+   Mapping. The spectral IWC axis has the same axis index as the spectral
+   WCS axis. */
+                        astInvert( tmap3 );
+                        tmap5 = (AstMapping *) astCmpMap( tmap4, tmap3, 1, "", status );
+                        tmap3 = astAnnul( tmap3 );
+                        tmap4 = astAnnul( tmap4 );
+                        axmap = AddUnitMaps( tmap5, iax, nwcs, status );
+                        tmap5 = astAnnul( tmap5 );
+
+/* CTYPE: First 4 characters specify the "S" system. */
+                        strcpy( ctype, orig_system );
+
+/* Last 4 characters are "-GRA" or "-GRI". */
+                        strcpy( ctype + 4, ( ix == 0 ) ? "-GRI" : "-GRA"  );
+
+/* Store values for the projection parameters in the FitsStore. Ignore
+   parameters which are set to the default values defined in FITS-WCS
+   paper III. */
+                        pv = astGetGrismG( gmap );
+                        if( pv != 0 ) SetItem( &(store->pv), fits_i, 0, s, pv, status );
+                        pv = (double) astGetGrismM( gmap );
+                        if( pv != 0 ) SetItem( &(store->pv), fits_i, 1, s, pv, status );
+                        pv = astGetGrismAlpha( gmap );
+                        if( pv != 0 ) SetItem( &(store->pv), fits_i, 2, s, pv*AST__DR2D, status );
+                        pv = astGetGrismNR( gmap );
+                        if( pv != 1.0 ) SetItem( &(store->pv), fits_i, 3, s, pv, status );
+                        pv = astGetGrismNRP( gmap );
+                        if( pv != 0 ) SetItem( &(store->pv), fits_i, 4, s, pv, status );
+                        pv = astGetGrismEps( gmap );
+                        if( pv != 0 ) SetItem( &(store->pv), fits_i, 5, s, pv*AST__DR2D, status );
+                        pv = astGetGrismTheta( gmap );
+                        if( pv != 0 ) SetItem( &(store->pv), fits_i, 6, s, pv*AST__DR2D, status );
+                     }
+                  }
+
+/* Release resources. */
+                  tmap2 = astAnnul( tmap2 );
+                  gmap = astAnnul( gmap );
+               }
+
+/* Release resources. */
+               tmap1 = astAnnul( tmap1 );
+
+/* Re-instate the original System and Unit attributes for the SpecFrame. */
+               astSetC( fs, system_attr, orig_system );
+               astSetC( fs, unit_attr, lin_unit );
+            }
+
+/* If none of the above algorithms are appropriate, we must resort to
+   using the -TAB algorithm, in which the Mapping is defined by a look-up
+   table. Check the TabOK attribute to see -TAB is to be supported. */
+            extver = astGetTabOK( this );
+            if( !ctype[ 0 ] && extver > 0 ) {
+
+/* Get any pre-existing FitsTable from the FitsStore. This is the table
+   in which the tabular data will be stored (if the Mapping can be expressed
+   in -TAB form). */
+               if( !astMapGet0A( store->tables, AST_TABEXTNAME, &table ) ) table = NULL;
+
+/* See if the Mapping can be expressed in -TAB form. */
+               tmap0 = IsMapTab1D( map, 1.0, NULL, wcsfrm, dim, iax, fits_i, &table,
+                                   &icolmain, &icolindex, &interp, status );
+               if( tmap0 ) {
+
+/* CTYPE: First 4 characters specify the "S" system. Last 4 characters are
+   "-TAB". */
+                  strcpy( ctype, orig_system );
+                  strcpy( ctype + 4, "-TAB" );
+
+/* The values stored in the table index vector are GRID coords. So we
+   need to ensure that IWC are equivalent to GRID coords. So set CRVAL
+   to zero. First store the original CRVAL value (which gives the
+   observation centre) in AXREF. */
+                  SetItem( &(store->axref), fits_i, 0, s, crval, status );
+                  crval = 0.0;
+
+/* Store TAB-specific values in the FitsStore. First the name of the
+   FITS binary table extension holding the coordinate info. */
+                  SetItemC( &(store->ps), fits_i, 0, s, AST_TABEXTNAME, status );
+
+/* Next the table version number. This is the set (positive) value for the
+   TabOK attribute. */
+                  SetItem( &(store->pv), fits_i, 1, s, extver, status );
+
+/* Also store the table version in the binary table header. */
+                  astSetFitsI( table->header, "EXTVER", extver,
+                               "Table version number", 0 );
+
+/* Next the name of the table column containing the main coords array. */
+                  SetItemC( &(store->ps), fits_i, 1, s,
+                            astColumnName( table, icolmain ), status );
+
+/* Next the name of the column containing the index array */
+                  if( icolindex >= 0 ) SetItemC( &(store->ps), fits_i, 2, s,
+                                  astColumnName( table, icolindex ), status );
+
+/* The interpolation method (an AST extension to the published -TAB
+   algorithm, communicated through the QVi_4a keyword). */
+                  SetItem( &(store->pv), fits_i, 4, s, interp, status );
+
+/* Also store the FitsTable itself in the FitsStore. */
+                  astMapPut0A( store->tables, AST_TABEXTNAME, table, NULL );
+
+/* Create the WCS -> IWC Mapping (AST uses grid coords as IWC coords for
+   the -TAB algorithm). First, get a Mapping that combines the TAB axis
+   Mapping( tmap0) in parallel with one or two UnitMaps in order to put
+   the TAB axis at the required index. */
+                  tmap1 = AddUnitMaps( tmap0, iax, nwcs, status );
+
+/* Now get a PermMap that permutes the WCS axes into the FITS axis order. */
+                  inperm = astMalloc( sizeof( double )*nwcs );
+                  outperm = astMalloc( sizeof( double )*nwcs );
+                  if( astOK ) {
+                     for( j = 0; j < nwcs; j++ ) {
+                        inperm[ j ] = wperm[ j ];
+                        outperm[ wperm[ j ] ] = j;
+                     }
+                  }
+                  pm = astPermMap( nwcs, inperm, nwcs, outperm, NULL, "",
+                                   status );
+
+/* Combine these two Mappings in series, to get the Mapping from WCS to
+   IWC. */
+                  axmap = (AstMapping *) astCmpMap( pm, tmap1, 1, " ",
+                                                    status );
+
+/* Free resources. */
+                  inperm = astFree( inperm );
+                  outperm = astFree( outperm );
+                  pm = astAnnul( pm );
+                  tmap0 = astAnnul( tmap0 );
+                  tmap1 = astAnnul( tmap1 );
+               }
+               if( table ) table = astAnnul( table );
+            }
+
+/* If this axis is a usable spectral axis... */
+            if( ctype[ 0 ] ) {
+
+/* Add the Mapping for this axis in series with any existing result Mapping. */
+               if( ret ) {
+                  tmap0 = (AstMapping *) astCmpMap( ret, axmap, 1, "", status );
+                  (void) astAnnul( ret );
+                  ret = tmap0;
+               } else {
+                  ret = astClone( axmap );
+               }
+               axmap = astAnnul( axmap );
+
+/* Store values for CTYPE, CRVAL and CUNIT in the FitsStore. */
+               SetItemC( &(store->ctype), fits_i, 0, s, ctype, status );
+               SetItem( &(store->crval), fits_i, 0, s, crval, status );
+               SetItemC( &(store->cunit), fits_i, 0, s, lin_unit, status );
+
+/* If the axis label has been set, use it as the CTYPE comment and CNAME
+   value. */
+               if( astTestLabel( specfrm, 0 ) ) {
+                  cname = (char *) astGetLabel( specfrm, 0 );
+                  SetItemC( &(store->ctype_com), fits_i, 0, s, cname, status );
+                  SetItemC( &(store->cname), fits_i, 0, s, cname, status );
+               }
+
+/* Store values for the other FITS-WCS keywords which describe the
+   spectral system. Only store values which have been explicitly set in
+   the SpecFrame, which are different to the default values defined by
+   FITS-WCS paper III (if any), and which are not bad. */
+               if( astTestObsLon( specfrm ) && astTestObsLat( specfrm ) &&
+                   s == ' ' ) {
+                  geolon = astGetObsLon( specfrm );
+                  geolat = astGetObsLat( specfrm );
+                  h = astGetObsAlt( specfrm );
+                  if( geolat != AST__BAD && geolon != AST__BAD && h != AST__BAD ) {
+                     eraGd2gc( 1, geolon, geolat, h, xyz );
+                     SetItem( &(store->obsgeox), 0, 0, ' ', xyz[0], status );
+                     SetItem( &(store->obsgeoy), 0, 0, ' ', xyz[1], status );
+                     SetItem( &(store->obsgeoz), 0, 0, ' ', xyz[2], status );
+                  }
+               }
+               if( astTestRestFreq( specfrm ) ) {
+                  restfreq = astGetRestFreq( specfrm );
+                  if( restfreq != AST__BAD ) {
+                     if( !strcmp( orig_system, "WAVE" ) ||
+                         !strcmp( orig_system, "VOPT" ) ||
+                         !strcmp( orig_system, "ZOPT" ) ||
+                         !strcmp( orig_system, "AWAV" ) ) {
+                        SetItem( &(store->restwav), 0, 0, s, AST__C/restfreq, status );
+                     } else {
+                        SetItem( &(store->restfrq), 0, 0, s, restfreq, status );
+                     }
+                  }
+                  if( astIsADSBSpecFrame( specfrm ) ) {
+                     imagfreq = astGetImagFreq( (AstDSBSpecFrame *) specfrm );
+                     if( imagfreq != AST__BAD ) {
+                        SetItem( &(store->imagfreq), 0, 0, s, imagfreq, status );
+                     }
+                  }
+               }
+               cval = GetFitsSor( astGetC( specfrm, "StdOfRest" ), status );
+               if( cval ) SetItemC( &(store->specsys), 0, 0, s, cval, status );
+               if( astTestSourceVel( specfrm ) ) {
+                  vsource = astGetSourceVel( specfrm );
+                  if( vsource != AST__BAD && fabs( vsource ) < AST__C ) {
+                     zsource = sqrt( (AST__C + vsource)/
+                                     (AST__C - vsource) ) - 1.0;
+                     SetItem( &(store->zsource), 0, 0, s, zsource, status );
+                     cval = GetFitsSor( astGetC( specfrm, "SourceVRF" ), status );
+                     if( cval ) SetItemC( &(store->ssyssrc), 0, 0, s, cval, status );
+                  }
+               } else {
+                  vsource = AST__BAD;
+               }
+
+/* Store the VELOSYS value (not strictly needed since it can be
+   determined from the other values, but FITS-WCS paper III says it can be
+   useful). We temporarily change the source velocity to be zero m/s
+   in the main rest frame (StdOfRest) (unless the main rest frame is
+   already the source rest frame). We then change the source rest
+   frame to topocentric and get the source velocity (i.e. the velocity of
+   the main rest Frame) in the topocentric system. We then re-instate the
+   original attribute values if they were set. */
+               if( astGetStdOfRest( specfrm ) != AST__SCSOR ) {
+                  sourcevrf = astGetSourceVRF( specfrm );
+                  astSetSourceVRF( specfrm, astGetStdOfRest( specfrm ) );
+                  astSetSourceVel( specfrm, 0.0 );
+               } else {
+                  vsource = AST__BAD;
+                  sourcevrf = AST__NOSOR;
+               }
+               astSetSourceVRF( specfrm, AST__TPSOR );
+               SetItem( &(store->velosys), 0, 0, s,
+                        astGetSourceVel( specfrm ), status );
+               if( vsource != AST__BAD ){
+                  astSetSourceVRF( specfrm, sourcevrf );
+                  astSetSourceVel( specfrm, vsource );
+               }
+
+/* Indicate that this axis has been described. */
+               axis_done[ iax ] = 1;
+            }
+
+/* Release resources. */
+            map = astAnnul( map );
+         }
+      }
+      pframe = astAnnul( pframe );
+   }
+
+/* Release resources. */
+   lbnd_p = astFree( lbnd_p );
+   ubnd_p = astFree( ubnd_p );
+   wcsfrm = astAnnul( wcsfrm );
+
+/* If we have a Mapping to return, simplify it. Otherwise, create
+   a UnitMap to return. */
+   if( ret ) {
+      tmap0 = ret;
+      ret = astSimplify( tmap0 );
+      tmap0 =  astAnnul( tmap0 );
+   } else {
+      ret = (AstMapping *) astUnitMap( nwcs, "", status );
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static AstFitsChan *SpecTrans( AstFitsChan *this, int encoding,
+                               const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     SpecTrans
+
+*  Purpose:
+*     Translated non-standard WCS FITS headers into equivalent standard
+*     ones.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstFitsChan *SpecTrans( AstFitsChan *this, int encoding,
+*                             const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function checks the supplied FitsChan for selected
+*     non-standard WCS keywords and, if found, stores equivalent
+*     standard keywords in a newly created FitsChan which is returned as
+*     the function value. All the original keywords are marked
+*     as having been used, so that they are not written out when the
+*     FitsChan is deleted.
+*
+
+*     At the moment, the non-standard keywords checked for are:
+*
+*     1) RADECSYS is renamed as RADESYS
+*
+*     2) LONGPOLE is renamed as LONPOLE
+*
+*     3) CDjjjiii and CDj_i are converted to PCi_j (with unit CDELT)
+*
+*     4) CROTAj are converted to PCi_j
+*
+*     5) PROJPi are converted to PV<axlat>_i
+*
+*     6) CmVALi are converted to CRVALis (s=A,B,,, for m=1,2...). This
+*        is also done for CmPIXi, CmYPEi, and CmNITi. CmELTi is converted
+*        to a CDj_is array.
+*
+*     7) EQUINOX keywords with string values equal to a date preceded
+*        by the letter B or J (eg "B1995.0"). These are converted to the
+*        corresponding Julian floating point value without any epoch
+*        specifier.
+*
+*     8) EPOCH values are converted into Julian EQUINOX values (but only
+*        if the FitsChan does not already contain an EQUINOX value).
+*
+*     9) DATE-OBS values are converted into MJD-OBS values (but only
+*        if the FitsChan does not already contain an MJD-OBS value).
+*
+*     10) EQUINOX or EPOCH keywords with value zero  are converted to
+*         B1950.
+*
+*     11) The AIPS NCP and GLS projections are converted into equivalent SIN
+*         or SFL projections.
+*
+*     12) The IRAF "ZPX" projection. If the last 4 chacaters of CTYPEi
+
+*       (i = 1, naxis) are "-ZPX", then:
+*	- "ZPX" is replaced by "ZPN" within the CTYPEi value
+*	- A distortion code of "-ZPX" is appended to the end of the CTYPEi
+*       value (this is used later by the DistortMaps function).
+*       - If the FitsChan contains no PROJP keywords, then projection
+*       parameter valus are read from any WATi_nnn keywords, and
+*       corresponding PV keywords are added to the FitsChan.
+*
+*     13) The IRAF "TNX" projection. If the last 4 chacaters of CTYPEi
+
+*       (i = 1, naxis) are "-TNX", then:
+*	- "TNX" is replaced by "TAN" within the CTYPEi value (the distorted
+*       TAN projection included in a pre-final version of FITS-WCS is still
+*       supported by AST using the WcsMap AST__TPN projection).
+*       - If the FitsChan contains no PROJP keywords, then projection
+*       parameter valus are read from any WATi_nnn keywords, and
+*       corresponding PV keywords are added to the FitsChan.
+*       - If the TNX projection cannot be converted exactly into a TAN
+*       projection, ASTWARN keywords are added to the FitsChan
+*       containing a warning message. The calling application can (if it
+*       wants to) check for this keyword, and report its contents to the
+*       user.
+*
+*     14) Keywords relating to the IRAF "mini-WCS" system are removed.
+*       This is the IRAF equivalent of the AST native encoding. Mini-WCS
+*       keywords are removed in order to avoid confusion arising between
+*       potentially inconsistent encodings.
+*
+*     15) "QV" parameters for TAN projections (as produced by AUTOASTROM)
+*       or "-TAB" (as produced by FitsChan) are renamed to "PV".
+*
+*     16) RESTFREQ is converted to RESTFRQ.
+*
+*     17) the "-WAV", "-FRQ" and "-VEL" CTYPE algorithms included in an
+*       early draft of FITS-WCS paper III are translated to the
+*       corresponding modern "-X2P" form.
+*
+*     18) AIPS spectral CTYPE values are translated to FITS-WCS paper III
+*     equivalents.
+*
+*     19) AIPS spectral keywords OBSRA and OBSDEC are used to create a
+*     pair of celestial axes with reference point at the specified
+*     (OBSRA,OBSDEC) position. This is only done if the header does not
+*     already contain a pair of celestial axes.
+*
+*     20) Common case insensitive CUNIT values: "Hz", "Angstrom", "km/s",
+*     "M/S"
+*
+*     21) Various translations specific to the FITS-CLASS encoding.
+*
+*     22) SAO distorted TAN projections (uses COi_j keywords to store
+*     polynomial coefficients) are converted to TPN projections.
+
+*     23) CTYPE == "LAMBDA" changed to CTYPE = "WAVE"
+*
+*     24) if the projection is TAN and the PolyTan attribute is non-zero,
+*     or if the projection is TPV (produced by SCAMP), the projection is
+*     changed to TPN (the AST code for the draft FITS-WCS paper II
+*     conventions for a distorted TAN projection).
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     encoding
+*        The FitsChan encoding in use.
+*     method
+*        Pointer to string holding name of calling method.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new FitsChan containing the keywords which
+*     constitute the standard equivalents to any non-standard keywords in
+*     the supplied FitsChan. A NULL pointer is returned if there are no
+*     non-standard keywords in the supplied FitsChan.
+*/
+
+/* Local Variables: */
+   AstFitsChan *ret;              /* The returned FitsChan */
+   char *assys;                   /* AIPS standad of rest type */
+   char *astype;                  /* AIPS spectral type */
+   char *comm;                    /* Pointer to comment string */
+   char *cval;                    /* Pointer to character string */
+   char *start;                   /* Pointer to start of projp term */
+   char *watmem;                  /* Pointer to total WAT string */
+   char bj;                       /* Besselian/Julian indicator */
+   char format[ 50 ];             /* scanf format string */
+   char keyname[ FITSNAMLEN + 5 ];/* General keyword name + formats */
+   char lattype[MXCTYPELEN];      /* CTYPE value for latitude axis */
+   char lontype[MXCTYPELEN];      /* CTYPE value for longitude axis */
+   char prj[6];                   /* Spatial projection string */
+   char s;                        /* Co-ordinate version character */
+   char spectype[MXCTYPELEN];     /* CTYPE value for spectral axis */
+   char sprj[6];                  /* Spectral projection string */
+   char ss;                       /* Co-ordinate version character */
+   char template[ FITSNAMLEN + 1 ];/* General keyword name template */
+   double *cvals;                 /* PVi_m values for TPN projection */
+   double cdelti;                 /* CDELT for longitude axis */
+   double cdeltj;                 /* CDELT for latitude axis */
+   double cosrota;                /* Cos( CROTA ) */
+   double crota;                  /* CROTA Value */
+   double dval;                   /* General floating value */
+   double lambda;                 /* Ratio of CDELTs */
+   double projp;                  /* Projection parameter value */
+   double rowsum2;                /* Sum of squared CDi_j row elements */
+   double sinrota;                /* Sin( CROTA ) */
+   double sinval;                 /* Sin( dec ref ) */
+   int *mvals;                    /* "m" index of each PVi_m value */
+   int axlat;                     /* Index of latitude axis */
+   int axlon;                     /* Index of longitude axis */
+   int diag;                      /* Sign of diagonal CDi_j element */
+   int dim;                       /* Length of pixel axis */
+   int gotpcij;                   /* Does FitsChan contain any PCi_j keywords? */
+   int i,j;                       /* Indices */
+   int iaxis;                     /* Axis index */
+   int icoeff;                    /* Index of next PVi_m value */
+   int iproj;                     /* Projection parameter index */
+   int jhi;                       /* Highest axis index */
+   int jlo;                       /* Lowest axis index */
+   int lbnd[ 2 ];                 /* Lower index bounds */
+   int m;                         /* Co-ordinate version index */
+   int naxis;                     /* Number of axes */
+   int nc;                        /* Length of string */
+   int ncoeff;                    /* Number of PVi_m values */
+   int ok;                        /* Can projection be represented in FITS-WCS?*/
+   int shifted;                   /* Non-zero if there is an origin shift */
+   int tlbnd[ 2 ];                /* Lower index bounds */
+   int tubnd[ 2 ];                /* Upper index bounds */
+   int ubnd[ 2 ];                 /* Upper index bounds */
+   int use_projp;                 /* Use PROJP keywors in favour of PV keywords? */
+   size_t size;                   /* Length of string value */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise to avoid compiler warnings. */
+   size = 0;
+   prj[ 0 ] = 0;
+
+/* Create the returned FitsChan. */
+   ret = astFitsChan( NULL, NULL, "", status );
+
+/* Loop round all axis descriptions, starting with primary (' '). */
+   for( s = 'A' - 1; s <= 'Z' && astOK; s++ ){
+      if( s == 'A' - 1 ) s = ' ';
+
+/* Find the number of axes by finding the highest axis number in any
+   CRPIXi keyword name. Pass on if there are no axes for this axis
+   description. */
+      if( s != ' ' ) {
+         sprintf( template, "CRPIX%%d%c", s );
+      } else {
+         strcpy( template, "CRPIX%d" );
+      }
+      if( !astKeyFields( this, template, 1, &naxis, lbnd ) ) {
+         if( s == ' ' ) s = 'A' - 1;
+         continue;
+      }
+
+/* Find the longitude and latitude axes by examining the CTYPE values.
+   They are marked as read. Such markings are only provisional, and they
+   can be read again any number of times until the current astRead
+   operation is completed. Also note the projection type. */
+      j = 0;
+      axlon = -1;
+      axlat = -1;
+      while( j < naxis && astOK ){
+         if( GetValue2( ret, this, FormatKey( "CTYPE", j + 1, -1, s, status ),
+                       AST__STRING, (void *) &cval, 0, method,
+                       class, status ) ){
+            nc = strlen( cval );
+            if( !strncmp( cval, "RA--", 4 ) ||
+                !strncmp( cval, "AZ--", 4 ) ||
+                ( nc > 1 && !strncmp( cval + 1, "LON", 3 ) ) ||
+                ( nc > 2 && !strncmp( cval + 2, "LN", 2 ) ) ) {
+               axlon = j;
+               strncpy( prj, cval + 4, 4 );
+               strncpy( lontype, cval, 10 );
+               prj[ 4 ] = 0;
+            } else if( !strncmp( cval, "DEC-", 4 ) ||
+                !strncmp( cval, "EL--", 4 ) ||
+                ( nc > 1 && !strncmp( cval + 1, "LAT", 3 ) ) ||
+                ( nc > 2 && !strncmp( cval + 2, "LT", 2 ) ) ) {
+               axlat = j;
+               strncpy( prj, cval + 4, 4 );
+               strncpy( lattype, cval, 10 );
+               prj[ 4 ] = 0;
+
+/* Check for spectral algorithms from early drafts of paper III */
+            } else {
+               sprj[ 0 ] = '-';
+               if( ( nc > 4 && !strncmp( cval + 4, "-WAV", 4 ) ) ) {
+                  sprj[ 1 ] = 'W';
+               } else if( ( nc > 4 && !strncmp( cval + 4, "-FRQ", 4 ) ) ) {
+                  sprj[ 1 ] = 'F';
+               } else if( ( nc > 4 && !strncmp( cval + 4, "-VEL", 4 ) ) ) {
+                  sprj[ 1 ] = 'V';
+               } else {
+                  sprj[ 0 ] = 0;
+               }
+               if( *sprj ) {
+                  sprj[ 2 ] = '2';
+                  if( !strncmp( cval, "WAVE", 4 ) ) {
+                     sprj[ 3 ] = 'W';
+                  } else if( !strncmp( cval, "FREQ", 4 ) ) {
+                     sprj[ 3 ] = 'F';
+                  } else if( !strncmp( cval, "VELO", 4 ) ) {
+                     sprj[ 3 ] = 'V';
+                  } else if( !strncmp( cval, "VRAD", 4 ) ) {
+                     sprj[ 3 ] = 'F';
+                  } else if( !strncmp( cval, "VOPT", 4 ) ) {
+                     sprj[ 3 ] = 'W';
+                  } else if( !strncmp( cval, "ZOPT", 4 ) ) {
+                     sprj[ 3 ] = 'W';
+                  } else if( !strncmp( cval, "ENER", 4 ) ) {
+                     sprj[ 3 ] = 'F';
+                  } else if( !strncmp( cval, "WAVN", 4 ) ) {
+                     sprj[ 3 ] = 'F';
+                  } else if( !strncmp( cval, "BETA", 4 ) ) {
+                     sprj[ 3 ] = 'V';
+                  } else {
+                     sprj[ 0 ] = 0;
+                  }
+               }
+               if( *sprj ) {
+                  strcpy( spectype, cval );
+                  if( sprj[ 1 ] == sprj[ 3 ] ) {
+                     strcpy( sprj, strlen( cval ) > 8 ? "----" : "    " );
+                  } else {
+                     sprj[ 4 ] = 0;
+                  }
+                  strncpy( spectype + 4, sprj, 4 );
+                  cval = spectype;
+                  SetValue( ret, FormatKey( "CTYPE", j + 1, -1, s, status ),
+                           (void *) &cval, AST__STRING, NULL, status );
+               }
+            }
+            j++;
+         } else {
+            break;
+         }
+      }
+
+/* RADECSYS keywords
+   ----------------- */
+      if( s == ' ' ) {
+         if( GetValue2( ret, this, "RADECSYS", AST__STRING, (void *) &cval, 0, method,
+                       class, status ) ){
+            if( encoding == FITSPC_ENCODING || encoding == FITSIRAF_ENCODING ){
+               SetValue( ret, "RADESYS", (void *) &cval, AST__STRING,
+                         CardComm( this, status ), status );
+            }
+         }
+
+/* LONGPOLE keywords
+   ----------------- */
+         if( GetValue2( ret, this, "LONGPOLE", AST__FLOAT, (void *) &dval, 0, method,
+                       class, status ) ){
+            if( encoding == FITSPC_ENCODING || encoding == FITSIRAF_ENCODING ){
+               SetValue( ret, "LONPOLE", (void *) &dval, AST__FLOAT,
+                         CardComm( this, status ), status );
+            }
+         }
+      }
+
+/* Zero CDELT values.
+   ----------------- */
+
+/* Check there are some CDELT keywords... */
+      if( s != ' ' ) {
+         sprintf( template, "CDELT%%d%c", s );
+      } else {
+         strcpy( template, "CDELT%d" );
+      }
+      if( astKeyFields( this, template, 0, NULL, NULL ) ){
+
+/* Do each row in the matrix. */
+         for( j = 0; j < naxis; j++ ){
+
+/* Get the CDELT value for this row. */
+            GetValue2( ret, this, FormatKey( "CDELT", j + 1, -1, s, status ), AST__FLOAT,
+                       (void *) &cdeltj, 0, method, class, status );
+
+/* If CDELT is zero, use 1.0E-6 of the corresponding CRVAL value
+   instead, or 1.0 if CRVAL is zero. Otherwise, the zeros could cause the
+   matrix to be non-invertable. The Mapping could then not be simplified
+   or used by a Plot. CDELT values of zero are usually used to indicate
+   "redundant" axes. For instance, a 2D image may be stored as a 3D cube
+   with a single plane with the "redundant" 3rd axis used to specify the
+   wavelength of the filter. The actual value used for CDELT shouldn't
+   matter since the axis only spans a single pixel anyway. */
+            if( cdeltj == 0.0 ){
+               GetValue2( ret, this, FormatKey( "CDELT", j + 1, -1, s, status ), AST__FLOAT,
+                         (void *) &dval, 1, method, class, status );
+               cdeltj = 1.0E-6*dval;
+               if( cdeltj == 0.0 ) cdeltj = 1.0;
+               SetValue( ret, FormatKey( "CDELT", j + 1, -1, s, status ), (void *) &cdeltj,
+                         AST__FLOAT, NULL, status );
+            }
+         }
+      }
+
+/* Following conversions produce PCi_j keywords. Only do them if there
+   are currently no PCi_j keywords in the header. */
+      if( s != ' ' ) {
+         sprintf( template, "PC%%d_%%d%c", s );
+      } else {
+         strcpy( template, "PC%d_%d" );
+      }
+      gotpcij = astKeyFields( this, template, 0, NULL, NULL );
+      if( !gotpcij ){
+
+/* CDjjjiii
+   -------- */
+         if( s == ' ' && astKeyFields( this, "CD%3d%3d", 0, NULL, NULL ) ){
+
+/* Do each row in the matrix. */
+            for( j = 0; j < naxis; j++ ){
+
+/* Do each column in the matrix. */
+               for( i = 0; i < naxis; i++ ){
+
+/* Get the CDjjjiii matrix element */
+                  sprintf( keyname, "CD%.3d%.3d", j + 1, i + 1 );
+                  if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
+                                method, class, status ) ){
+
+/* If found, save it with name PCj_i, and ensure the default value of 1.0
+   is used for CDELT. */
+                     if( encoding == FITSIRAF_ENCODING ){
+                        SetValue( ret, FormatKey( "PC", j + 1, i + 1, ' ', status ),
+                                  (void *) &dval, AST__FLOAT, NULL, status );
+                        dval = 1.0;
+                        SetValue( ret, FormatKey( "CDELT", j + 1, -1, s, status ),
+                                  (void *) &dval, AST__FLOAT, NULL, status );
+                        gotpcij = 1;
+                     }
+                  }
+               }
+            }
+         }
+
+/* CDj_i
+   ---- */
+         if( s != ' ' ) {
+            sprintf( template, "CD%%d_%%d%c", s );
+         } else {
+            strcpy( template, "CD%d_%d" );
+         }
+         if( !gotpcij && astKeyFields( this, template, 0, NULL, NULL ) ){
+
+/* Do each row in the matrix. */
+            for( j = 0; j < naxis; j++ ){
+
+/* First find the sum of the squared elements in the row. and note the
+   sign of the diagonal element. */
+               rowsum2 = 0.0;
+               diag = +1;
+               for( i = 0; i < naxis; i++ ){
+                  if( GetValue2( ret, this, FormatKey( "CD", j + 1, i + 1, s, status ),
+                                  AST__FLOAT, (void *) &dval, 0, method, class, status ) ){
+                     rowsum2 += dval*dval;
+                     if( i == j ) diag = ( dval >= 0.0 ) ? +1 : -1;
+                  }
+               }
+
+/* The CDELT value for this row will be the length of the row vector. This means that
+   each row will be a unit vector when converted to PCi_j form, and the CDELT will
+   give a real indication of the pixel size. Ensure that the diagonal
+   PCi+j element has a positive sign. */
+               cdelti = sqrt( rowsum2 )*diag;
+               SetValue( ret, FormatKey( "CDELT", j + 1, -1, s, status ),
+                         (void *) &cdelti, AST__FLOAT, NULL, status );
+
+/* Do each column in the matrix. */
+               for( i = 0; i < naxis; i++ ){
+
+/* Get the CDj_i matrix element (note default value for all CD elements
+   is zero (even diagonal elements!). */
+                  if( !GetValue2( ret, this, FormatKey( "CD", j + 1, i + 1, s, status ),
+                                  AST__FLOAT, (void *) &dval, 0, method, class, status ) ){
+                     dval = 0.0;
+                  }
+
+/* Divide by the rows cdelt value and save it with name PCj_i. */
+                  if( cdelti != 0.0 ) dval /= cdelti;
+                  SetValue( ret, FormatKey( "PC", j + 1, i + 1, s, status ),
+                            (void *) &dval, AST__FLOAT, NULL, status );
+                  gotpcij = 1;
+               }
+            }
+         }
+
+/* PCjjjiii and CROTAi keywords
+   ---------------------------- */
+
+/* Check there are some CDELT keywords... */
+         if( s != ' ' ) {
+            sprintf( template, "CDELT%%d%c", s );
+         } else {
+            strcpy( template, "CDELT%d" );
+         }
+         if( !gotpcij && astKeyFields( this, template, 0, NULL, NULL ) ){
+
+/* See if there is a CROTA keyword. Try to read values for both axes
+   since they are sometimes both included. This ensures they will not be
+   included in the output when the FitsChan is deleted. Read the latitude
+   axis second in order to give it priority in cases where both are
+   present. */
+            crota = AST__BAD;
+            GetValue2( ret, this, FormatKey( "CROTA", axlon + 1, -1, s, status ),
+                       AST__FLOAT, (void *) &crota, 0, method, class, status );
+            GetValue2( ret, this, FormatKey( "CROTA", axlat + 1, -1, s, status ),
+                       AST__FLOAT, (void *) &crota, 0, method, class, status );
+
+/* If there are any PCjjjiii keywords, rename them as PCj_i. */
+            if( s == ' ' && astKeyFields( this, "PC%3d%3d", 0, NULL, NULL ) ){
+
+/* Do each row in the matrix. */
+               for( j = 0; j < naxis; j++ ){
+
+/* Do each column in the matrix. */
+                  for( i = 0; i < naxis; i++ ){
+
+/* Get the PCiiijjj matrix element */
+                     sprintf( keyname, "PC%.3d%.3d", j + 1, i + 1 );
+                     if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
+                                   method, class, status ) ){
+                     } else if( i == j ) {
+                        dval = 1.0;
+                     } else {
+                        dval = 0.0;
+                     }
+
+/* Store it as PCi_j */
+                     SetValue( ret, FormatKey( "PC", j + 1, i + 1, ' ', status ),
+                               (void *) &dval, AST__FLOAT, NULL, status );
+                     gotpcij = 1;
+                  }
+               }
+
+/* If there is a CROTA value and no PCjjjii keywords, create a PCj_i
+   matrix from the CROTA values. We need to have latitude and longitude
+   axes for this.  */
+            } else if( s == ' ' && axlat != -1 && axlon != -1 && crota != AST__BAD ){
+
+/* Get the sin and cos of CROTA */
+               cosrota = cos( crota*AST__DD2R );
+               sinrota = sin( crota*AST__DD2R );
+
+/* Get the CDELT values for the longitude and latitude axes. */
+               if( GetValue2( ret, this, FormatKey( "CDELT", axlat + 1, -1, ' ', status ),
+                             AST__FLOAT, (void *) &cdeltj, 1, method,
+                             class, status ) &&
+                   GetValue2( ret, this, FormatKey( "CDELT", axlon + 1, -1, ' ', status ),
+                             AST__FLOAT, (void *) &cdelti, 1, method,
+                             class, status ) ){
+
+/* Save the ratio, needed below. */
+                  lambda = cdeltj/cdelti;
+
+/* Save a corresponding set of PCi_j keywords in the FitsChan. First do
+   the diagonal terms. */
+                  for( i = 0; i < naxis; i++ ){
+                     if( i == axlat ) {
+                        dval = cosrota;
+                     } else if( i == axlon ) {
+                        dval = cosrota;
+                     } else {
+                        dval = 1.0;
+                     }
+                     SetValue( ret, FormatKey( "PC", i + 1, i + 1, ' ', status ),
+                               (void *) &dval, AST__FLOAT, NULL, status );
+                     gotpcij = 1;
+                  }
+
+/* Now do the non-zero off-diagonal terms. */
+                  dval = sinrota/lambda;
+                  SetValue( ret, FormatKey( "PC", axlat + 1, axlon + 1, ' ', status ),
+                            (void *) &dval, AST__FLOAT, NULL, status );
+                  dval = -sinrota*lambda;
+                  SetValue( ret, FormatKey( "PC", axlon + 1, axlat + 1, ' ', status ),
+                            (void *) &dval, AST__FLOAT, NULL, status );
+               }
+            }
+         }
+      }
+
+/* Conversion of old PROJP, etc, is done once on the "primary" pass. */
+      if( s == ' ' ) {
+
+/* PROJP keywords
+   -------------- */
+         if( astKeyFields( this, "PROJP%d", 1, ubnd, lbnd ) && axlat != -1 ) {
+
+/* Some people produce headers with both PROJP and PV. Even worse, the
+   PROJP and PV values are sometimes inconsistent. In this case we trust
+   the PV values rather than the PROJP values, but only if the PV values
+   are not obviously incorrect for some reason. In particularly, we check
+   that, *if* either PVi_1 or PVi_2 (where i=longitude axis) is present,
+   then PVi_0 is also present. Conversely we check that if PVi_0 is
+   present then at least one of PVi_1 or PVi_2 is present. */
+            use_projp = 1;
+            if( axlat != -1 &&
+                astKeyFields( this, "PV%d_%d", 2, tubnd, tlbnd ) ){
+               use_projp = 0;
+
+/* Are there any PV values for the longitude axis? */
+               if( tlbnd[ 0 ] <= axlon + 1 && axlon + 1 <= tubnd[ 0 ] ) {
+
+/* Are either PVi_1 or PVi_2 available? */
+                  if( HasCard( this, FormatKey( "PV", axlon + 1, 1, ' ', status ),
+                               method, class, status ) ||
+                      HasCard( this, FormatKey( "PV", axlon + 1, 2, ' ', status ),
+                               method, class, status ) ) {
+
+/* If so use PROJP if PVi_0 is not also available. */
+                     if( !HasCard( this, FormatKey( "PV", axlon + 1, 0, ' ', status ),
+                                   method, class, status ) ) use_projp = 1;
+
+/* If neither PVi_1 or PVi_2 are available, use PROJP if PVi_0 is
+   available. */
+                  } else if( HasCard( this, FormatKey( "PV", axlon + 1, 0, ' ', status ),
+                                      method, class, status ) ) {
+                     use_projp = 1;
+                  }
+               }
+            }
+
+/* Translate PROJP to PV if required. */
+            if( use_projp ) {
+               for( i = lbnd[ 0 ]; i <= ubnd[ 0 ]; i++ ){
+                  if( GetValue2( ret, this, FormatKey( "PROJP", i, -1, ' ', status ),
+                                AST__FLOAT, (void *) &dval, 0, method, class, status ) &&
+                      ( encoding == FITSPC_ENCODING ||
+                        encoding == FITSIRAF_ENCODING ) ){
+                     SetValue( ret, FormatKey( "PV", axlat + 1, i, ' ', status ),
+                               (void *) &dval, AST__FLOAT, CardComm( this, status ), status );
+                  }
+               }
+            }
+         }
+
+/* CmVALi keywords
+   --------------- */
+         if( astKeyFields( this, "C%1dVAL%d", 2, ubnd, lbnd ) ){
+            ss = 'A';
+            for( m = lbnd[ 0 ]; m <= ubnd[ 0 ]; m++ ){
+               for( i = lbnd[ 1 ]; i <= ubnd[ 1 ]; i++ ){
+                  sprintf( keyname, "C%dVAL%d", m, i );
+                  if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
+                                method, class, status ) &&
+                      ( encoding == FITSPC_ENCODING ||
+                        encoding == FITSIRAF_ENCODING ) ){
+                     sprintf( keyname, "CRVAL%d%c", i, ss );
+                     SetValue( ret, keyname, (void *) &dval, AST__FLOAT,
+                               CardComm( this, status ), status );
+                  }
+               }
+               ss++;
+            }
+         }
+
+/* CmPIXi keywords
+   --------------- */
+         if( astKeyFields( this, "C%1dPIX%d", 2, ubnd, lbnd ) ){
+            ss = 'A';
+            for( m = lbnd[ 0 ]; m <= ubnd[ 0 ]; m++ ){
+               for( i = lbnd[ 1 ]; i <= ubnd[ 1 ]; i++ ){
+                  sprintf( keyname, "C%dPIX%d", m, i );
+                  if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
+                                method, class, status ) &&
+                      ( encoding == FITSPC_ENCODING ||
+                        encoding == FITSIRAF_ENCODING ) ){
+                     sprintf( keyname, "CRPIX%d%c", i, ss );
+                     SetValue( ret, keyname, (void *) &dval, AST__FLOAT,
+                               CardComm( this, status ), status );
+                  }
+               }
+               ss++;
+            }
+         }
+
+/* CmYPEi keywords
+   --------------- */
+         if( astKeyFields( this, "C%1dYPE%d", 2, ubnd, lbnd ) ){
+            ss = 'A';
+            for( m = lbnd[ 0 ]; m <= ubnd[ 0 ]; m++ ){
+               for( i = lbnd[ 1 ]; i <= ubnd[ 1 ]; i++ ){
+                  sprintf( keyname, "C%dYPE%d", m, i );
+                  if( GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0,
+                                method, class, status ) &&
+                      ( encoding == FITSPC_ENCODING ||
+                        encoding == FITSIRAF_ENCODING ) ){
+                     sprintf( keyname, "CTYPE%d%c", i, ss );
+                     SetValue( ret, keyname, (void *) &cval, AST__STRING,
+                               CardComm( this, status ), status );
+                  }
+               }
+               ss++;
+            }
+         }
+
+/* CmNITi keywords
+   --------------- */
+         if( astKeyFields( this, "C%1dNIT%d", 2, ubnd, lbnd ) ){
+            ss = 'A';
+            for( m = lbnd[ 0 ]; m <= ubnd[ 0 ]; m++ ){
+               for( i = lbnd[ 1 ]; i <= ubnd[ 1 ]; i++ ){
+                  sprintf( keyname, "C%dNIT%d", m, i );
+                  if( GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0,
+                                method, class, status ) &&
+                      ( encoding == FITSPC_ENCODING ||
+                        encoding == FITSIRAF_ENCODING ) ){
+                     sprintf( keyname, "CUNIT%d%c", i, ss );
+                     SetValue( ret, keyname, (void *) &cval, AST__STRING,
+                               CardComm( this, status ), status );
+                  }
+               }
+               ss++;
+            }
+         }
+
+/* CmELTi keywords
+   --------------- */
+         if( astKeyFields( this, "C%1dELT%d", 2, ubnd, lbnd ) ){
+            ss = 'A';
+            for( m = lbnd[ 0 ]; m <= ubnd[ 0 ]; m++ ){
+
+/* Create a PCj_is matrix by copying the PCjjjiii values and rename CmELTi as
+   CDELTis. */
+
+/* Do each row in the matrix. */
+               for( j = 0; j < naxis; j++ ){
+
+/* Get the CDELT value for this row. Report an error if not present. */
+                  sprintf( keyname, "C%dELT%d", m, j + 1 );
+                  GetValue2( ret, this, keyname, AST__FLOAT, (void *) &cdeltj, 1,
+                             method, class, status );
+
+/* If CDELT is zero, use one hundredth of the corresponding CRVAL value
+   instead, or 1.0 if CRVAL is zero. Otherwise, the zeros could cause the
+   matrix to be non-invertable. The Mapping could then not be simplified
+   or used by a Plot. CDELT values of zero are usually used to indicate
+   "redundant" axes. For instance, a 2D image may be stored as a 3D cube
+   with a single plane with the "redundant" 3rd axis used to specify the
+   wavelength of the filter. The actual value used for CDELT shouldn't
+   matter since the axis only spans a single pixel anyway. */
+                  if( cdeltj == 0.0 ){
+                     GetValue2( ret, this, FormatKey( "CRVAL", j + 1, -1, ss, status ), AST__FLOAT,
+                               (void *) &dval, 1, method, class, status );
+                     cdeltj = 0.01*dval;
+                     if( cdeltj == 0.0 ) cdeltj = 1.0;
+                  }
+
+/* Save it as CDELTis */
+                  sprintf( keyname, "CDELT%d%c", j + 1, ss );
+                  SetValue( ret, keyname, (void *) &cdeltj, AST__FLOAT,
+                            CardComm( this, status ), status );
+
+/* Do each column in the matrix. */
+                  for( i = 0; i < naxis; i++ ){
+
+/* Get the PCiiijjj matrix element */
+                     sprintf( keyname, "PC%.3d%.3d", j + 1, i + 1 );
+                     if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
+                                   method, class, status ) ){
+                     } else if( i == j ) {
+                        dval = 1.0;
+                     } else {
+                        dval = 0.0;
+                     }
+
+/* Store it as PCi_js. */
+                     SetValue( ret, FormatKey( "PC", j + 1, i + 1, ss, status ),
+                               (void *) &dval, AST__FLOAT, NULL, status );
+                  }
+               }
+               ss++;
+            }
+         }
+
+/* EPOCH keywords
+   ------------ */
+
+/* Get any EPOCH card, marking it as read. */
+         if( GetValue2( ret, this, "EPOCH", AST__FLOAT, (void *) &dval, 0, method,
+                          class, status ) ){
+
+/* Convert values of zero to B1950. */
+            if( dval == 0.0 ) dval = 1950.0;
+
+/* Save a new EQUINOX card in the FitsChan, so long as there is not
+   already one there. */
+            if( !GetValue2( ret, this, "EQUINOX", AST__STRING, (void *) &cval, 0,
+                            method, class, status ) ){
+               SetValue( ret, "EQUINOX", (void *) &dval, AST__FLOAT,
+                         "Reference equinox", status );
+            }
+         }
+
+/* String EQUINOX values
+   ---------------------
+   If found, EQUINOX will be used in favour of any EPOCH value found
+   above. */
+         if( GetValue2( ret, this, "EQUINOX", AST__STRING, (void *) &cval, 0, method,
+                        class, status ) ){
+
+/* Note the first character. */
+            bj = cval[ 0 ];
+
+/* If it is "B" or "J", read a floating value from the rest */
+            if( bj == 'B' || bj == 'J' ) {
+               if( 1 == astSscanf( cval + 1, " %lf ", &dval ) ){
+
+/* If it is a Besselian epoch, convert to Julian. */
+                  if( bj == 'B' ) dval = palEpj( palEpb2d( dval ) );
+
+/* Replace the original EQUINOX card. */
+                  SetValue( ret, "EQUINOX", (void *) &dval, AST__FLOAT,
+                            CardComm( this, status ), status );
+               }
+            }
+         }
+
+/* EQUINOX = 0.0 keywords
+   ---------------------- */
+         if( GetValue2( ret, this, "EQUINOX", AST__FLOAT, (void *) &dval, 0, method,
+                       class, status ) ){
+            if( dval == 0.0 ){
+               dval = 1950.0;
+               SetValue( ret, "EQUINOX", (void *) &dval, AST__FLOAT,
+                         CardComm( this, status ), status );
+            }
+         }
+      }
+
+/* DATE-OBS keywords
+   ---------------- */
+
+/* Read any DATE-OBS card. This prevents it being written out when the
+   FitsChan is deleted.  */
+      if( s == ' ' ) {
+         strcpy( keyname, "DATE-OBS" );
+         if( GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0, method,
+                       class, status ) ){
+
+/* Ignore DATE-OBS values if the header contains an MJD-OBS value */
+            strcpy( keyname, "MJD-OBS" );
+            if( !GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
+                           method, class, status ) ){
+
+/* Get the corresponding mjd-obs value, checking that DATE-OBS is valid. */
+               dval = DateObs( cval, status );
+               if( dval != AST__BAD ){
+                  SetValue( ret, keyname, (void *) &dval, AST__FLOAT,
+                            "Date of observation", status );
+               }
+            }
+         }
+      }
+
+/* Things specific to the CLASS encoding
+   ------------------------------------- */
+      if( encoding == FITSCLASS_ENCODING ) ClassTrans( this, ret, axlat,
+                                                       axlon, method, class, status );
+
+/* Convert SAO distorted TAN headers to TPN distorted TAN headers.
+   -------------------------------------------------------------- */
+      if( s == ' ' && !Ustrcmp( prj, "-TAN", status ) ){
+
+/* Translate the COi_m keywords into PV i+m keywords. */
+         if( SAOTrans( this, ret, method, class, status ) ) {
+
+/* Change the CTYPE projection form TAN to TPV. */
+            strcpy( prj, "-TPN" );
+            strcpy( lontype + 4, "-TPN" );
+            cval = lontype;
+            SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
+                         (void *) &cval, AST__STRING, NULL, status );
+            strcpy( lattype + 4, "-TPN" );
+            cval = lattype;
+            SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
+                      (void *) &cval, AST__STRING, NULL, status );
+         }
+      }
+
+/* AIPS "NCP" projections
+   --------------------- */
+
+/* Compare the projection type with "-NCP" */
+      if( !Ustrcmp( prj, "-NCP", status ) ) {
+
+/* Get the latitude reference value, and take is cot. */
+         GetValue2( ret, this, FormatKey( "CRVAL", axlat + 1, -1, s, status ),
+                   AST__FLOAT, (void *) &dval, 1, method, class, status );
+         sinval = sin( dval*AST__DD2R );
+         if( sinval != 0.0 ) {
+            dval = cos( dval*AST__DD2R )/sinval;
+
+/* Replace NCP with SIN in the CTYPE values. */
+            strcpy( lontype + 4, "-SIN" );
+            cval = lontype;
+            SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
+                      (void *) &cval, AST__STRING, NULL, status );
+            strcpy( lattype + 4, "-SIN" );
+            cval = lattype;
+            SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
+                      (void *) &cval, AST__STRING, NULL, status );
+
+/* Store the new projection parameters using names suitable to FITS_WCS
+   encoding. */
+            SetValue( ret, FormatKey( "PV", axlat + 1, 2, s, status ),
+                      (void *) &dval, AST__FLOAT, NULL, status );
+            dval = 0.0;
+            SetValue( ret, FormatKey( "PV", axlat + 1, 1, s, status ),
+                      (void *) &dval, AST__FLOAT, NULL, status );
+         }
+      }
+
+/* CLASS "ATF" projections
+   ---------------------- */
+
+/* Replace ATF with AIT in the CTYPE values. */
+      if( !Ustrcmp( prj, "-ATF", status ) ) {
+         strcpy( lontype + 4, "-AIT" );
+         cval = lontype;
+         SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
+                   (void *) &cval, AST__STRING, NULL, status );
+         strcpy( lattype + 4, "-AIT" );
+         cval = lattype;
+         SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
+                   (void *) &cval, AST__STRING, NULL, status );
+      }
+
+/* AIPS "GLS" projections
+   --------------------- */
+
+/* Compare the projection type with "-GLS" */
+      if( !Ustrcmp( prj, "-GLS", status ) ) {
+
+/* Convert to "-SFL" */
+         strcpy( lontype + 4, "-SFL" );
+         cval = lontype;
+         SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
+                   (void *) &cval, AST__STRING, NULL, status );
+         strcpy( lattype + 4, "-SFL" );
+         cval = lattype;
+         SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
+                   (void *) &cval, AST__STRING, NULL, status );
+
+/* FITS-WCS paper 2 (sec. 6.1.4) describes how to handle AIPS GLS
+   projections, but requires that the axes are not rotated. Instead, we
+   modify the native latitude at the fiducial point, theta_0, as is done
+   in wcslib function celfix in file wcsfix.c (see also FITS-WCS paper
+   II sec. 2.5). We only need to change theta_0 if the CRVAL position is
+   not the celestial origin. */
+         shifted = 0;
+         sprintf( keyname, "CRVAL%d", axlon + 1 );
+         if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
+                        method, class, status ) ){
+            if( dval != 0.0 ) shifted = 1;
+         }
+         sprintf( keyname, "CRVAL%d", axlat + 1 );
+         if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0,
+                        method, class, status ) ){
+            if( dval != 0.0 ) shifted = 1;
+         }
+
+         if( 0 && shifted ) {
+            SetValue( ret, FormatKey( "PV", axlon + 1, 2, s, status ),
+                      (void *) &dval, AST__FLOAT, NULL, status );
+            dval = 0.0;
+            SetValue( ret, FormatKey( "PV", axlon + 1, 1, s, status ),
+                      (void *) &dval, AST__FLOAT, NULL, status );
+            dval = 1.0;
+            SetValue( ret, FormatKey( "PV", axlon + 1, 0, s, status ),
+                      (void *) &dval, AST__FLOAT, NULL, status );
+         }
+      }
+
+/* Rename any "QV" projection parameters to "PV" (such as used by
+   -TAB to indicate the interpolation method, or by the internal
+   -TPN projection to indicate distortion coefficients).
+   ------------------------------------------------------------ */
+
+/* Rewind the FitsChan. */
+      astClearCard( this );
+
+/* Search the FitsChan for QV cards. */
+      if( s != ' ' ) {
+         sprintf( template, "QV%%d_%%d%c", s );
+      } else {
+         strcpy( template, "QV%d_%d" );
+      }
+      while( FindKeyCard( this, template, method, class, status ) && astOK ) {
+
+/* If the projection name is "TAN", replace TAN with TPN in the CTYPE values. */
+         if( !Ustrcmp( prj, "-TAN", status ) ){
+            strcpy( prj, "-TPN" );
+            strcpy( lontype + 4, "-TPN" );
+            cval = lontype;
+            SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
+                      (void *) &cval, AST__STRING, NULL, status );
+            strcpy( lattype + 4, "-TPN" );
+            cval = lattype;
+            SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
+                      (void *) &cval, AST__STRING, NULL, status );
+         }
+
+/* Indicate that the QV card has been consumed. */
+         MarkCard( this, status );
+
+/* Get the keyword name and change it from QV to PV. */
+         strcpy( keyname, CardName( this, status ) );
+         keyname[ 0 ] ='P';
+
+/* Store the new PV card so long as there it is not already present in the
+   FitsChan. */
+         if( !GetValue2( ret, this, keyname, AST__FLOAT, (void *) &cval, 0,
+                        method, class, status ) ){
+            SetValue( ret, keyname, CardData( this, &size, status ), AST__FLOAT,
+                      CardComm( this, status ), status );
+         }
+
+/* Move on to the next card. */
+         MoveCard( this, 1, method, class, status );
+      }
+
+
+
+/* Change any TAN projection to TPN projection if the PolyTan attribute
+   is non-zero. Also change any TPV projection to TPN projection.
+   --------------------------------------------------- */
+      if( ( !Ustrcmp( prj, "-TAN", status ) &&
+            GetUsedPolyTan( this, ret, axlat + 1, axlon + 1, s, method, class, status ) ) ||
+          !Ustrcmp( prj, "-TPV", status ) ){
+         strcpy( prj, "-TPN" );
+         strcpy( lontype + 4, "-TPN" );
+         cval = lontype;
+         SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, s, status ),
+                   (void *) &cval, AST__STRING, NULL, status );
+         strcpy( lattype + 4, "-TPN" );
+         cval = lattype;
+         SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, s, status ),
+                   (void *) &cval, AST__STRING, NULL, status );
+      }
+
+
+
+/* IRAF "ZPX" projections
+   --------------------- */
+      if( s == ' ' && !Ustrcmp( prj, "-ZPX", status ) ) {
+
+/* Replace "ZPX" with "ZPN-ZPX" (i.e. ZPN projection with ZPX distortion
+   code) in the CTYPE values. */
+         strcpy( lontype + 4, "-ZPN-ZPX" );
+         cval = lontype;
+         SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, ' ', status ),
+                   (void *) &cval, AST__STRING, NULL, status );
+         strcpy( lattype + 4, "-ZPN-ZPX" );
+         cval = lattype;
+         SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, ' ', status ),
+                   (void *) &cval, AST__STRING, NULL, status );
+
+/* Check latitude then longitude axes */
+         for( i = 0; i < 2; i++ ){
+            iaxis = i ? axlat : axlon;
+
+/* Concatenate all the IRAF "WAT" keywords together for this axis. These
+   keywords are marked as having been used, so that they are not written
+   out when the FitsChan is deleted. */
+            watmem = ConcatWAT( this, iaxis, method, class, status );
+
+/* Search the total WAT string for any projp terms. */
+            if( watmem ){
+               for( iproj = 0; iproj < 10 && astOK; iproj++ ) {
+                  sprintf( format, "projp%d=", iproj );
+                  start = strstr( watmem, format );
+                  if( start ) {
+                     sprintf( format, "projp%d=%%lf", iproj );
+                     if( astSscanf( start, format, &projp ) ){
+                        SetValue( ret, FormatKey( "PV", axlat + 1, iproj, ' ', status ),
+                                  (void *) &projp, AST__FLOAT,
+                                  "ZPN projection parameter", status );
+                     }
+                  }
+               }
+
+/*  Release the memory used to hold the concatenated WAT keywords. */
+               watmem = (char *) astFree( (void *) watmem );
+            }
+         }
+
+/* IRAF "TNX" projections
+   --------------------- */
+      } else if( s == ' ' && !Ustrcmp( prj, "-TNX", status ) ) {
+
+/* Replace TNX with TPN in the CTYPE values. */
+         strcpy( lontype + 4, "-TPN" );
+         cval = lontype;
+         SetValue( ret, FormatKey( "CTYPE", axlon + 1, -1, ' ', status ),
+                   (void *) &cval, AST__STRING, NULL, status );
+         strcpy( lattype + 4, "-TPN" );
+         cval = lattype;
+         SetValue( ret, FormatKey( "CTYPE", axlat + 1, -1, ' ', status ),
+                   (void *) &cval, AST__STRING, NULL, status );
+
+/* Check latitude then longitude axes */
+         for( i = 0; i < 2; i++ ){
+            iaxis = i ? axlat : axlon;
+
+/* Concatenate all the IRAF "WAT" keywords together for this axis. These
+   keywords are marked as having been used, so that they are not written
+   out when the FitsChan is deleted. */
+            watmem = ConcatWAT( this, iaxis, method, class, status );
+
+/* Extract the polynomial coefficients from the concatenated WAT string.
+   These are returned in the form of a list of PVi_m values for a TPN
+   projection. */
+            ncoeff = WATCoeffs( watmem, i, &cvals, &mvals, &ok, status );
+
+/* If we can handle the TNX projection, store the PV values in the FitsChan. */
+            if( ok ) {
+               for( icoeff = 0; icoeff < ncoeff; icoeff++ ) {
+                  SetValue( ret, FormatKey( "PV", iaxis + 1, mvals[ icoeff ],
+                                            ' ', status ),
+                           (void *) (cvals + icoeff), AST__FLOAT,
+                           "TAN projection parameter", status );
+               }
+
+/* If the TNX cannot be represented in FITS-WCS (within our restrictions), add
+   warning keywords to the FitsChan. */
+            } else {
+               Warn( this, "tnx", "This FITS header includes, or was "
+                     "derived from, a TNX projection which requires "
+                     "unsupported IRAF-specific corrections. The WCS "
+                     "information may therefore be incorrect.", method, class, status );
+            }
+
+/*  Release the memory used to hold the concatenated WAT keywords. */
+            watmem = (char *) astFree( (void *) watmem );
+         }
+      }
+
+/* MSX CAR projections.
+   ------------------- */
+      if( !Ustrcmp( prj, "-CAR", status ) && !astGetCarLin( this ) ) {
+
+/* The CAR projection has valid projection plane points only for native
+   longitudes in the range [-180,+180]. The reference pixel (CRPIX) is at
+   native longitude zero.  We need to check that the reference point is not
+   so far outside the image that the entire image lies outside the range
+   [-180,+180]. If it is, we modify the CRPIX value by the number of
+   pixels corresponding to 360 degres of longitude in order to bring the
+   array into the valid domain ([-180,+180]) of the projection. */
+         if( GetValue2( ret, this, FormatKey( "CDELT", axlon + 1, -1, s, status ),
+                        AST__FLOAT, (void *) &cdelti, 1, method, class, status ) &&
+             GetValue2( ret, this, FormatKey( "CRPIX", axlon + 1, -1, s, status ),
+                        AST__FLOAT, (void *) &dval, 0, method, class, status ) ) {
+
+/* We check if the mid point of the array is in the valiud longitude range. Use the
+   bottom left corner as a fallback if the image size is unknown. */
+            if( !GetValue( this, FormatKey( "NAXIS", axlon + 1, -1, ' ', status ),
+                           AST__INT, &dim, 0, 0, method, class, status ) ) {
+               dim = 0;
+            }
+
+            if( cdelti != 0.0 ) {
+               double offset = 0.5*( dim + 1 );
+               dval = offset + AST__DR2D*palDrange( AST__DD2R*( dval - offset )*cdelti )/cdelti;
+               SetValue( ret, FormatKey( "CRPIX", axlon + 1, -1, s, status ),
+                         (void *) &dval, AST__FLOAT, CardComm( this, status ), status );
+            }
+         }
+      }
+
+/* Replace RESTFREQ by RESTFRQ.
+   ---------------------------- */
+
+/* Get any RESTFREQ card, marking it as read. */
+      if( s != ' ' ) {
+         sprintf( keyname, "RESTFREQ%c", s );
+      } else {
+         strcpy( keyname, "RESTFREQ" );
+      }
+      if( GetValue2( ret, this, keyname, AST__FLOAT, (void *) &dval, 0, method,
+                     class, status ) ){
+
+/* Look for "MHz" and "GHz" within the comment. If found scale the value
+   into Hz. */
+         comm = CardComm( this, status );
+         if( comm ) {
+            if( strstr( comm, "GHz" ) ) {
+               dval *= 1.0E9;
+               comm = "[Hz] Rest Frequency";
+            } else if( strstr( comm, "MHz" ) ) {
+               dval *= 1.0E6;
+               comm = "[Hz] Rest Frequency";
+            }
+         }
+
+/* Save a new RESTFRQ card in the FitsChan, so long as there is not
+   already one there. */
+         if( s != ' ' ) {
+            sprintf( keyname, "RESTFRQ%c", s );
+         } else {
+            strcpy( keyname, "RESTFRQ" );
+         }
+         if( !GetValue2( ret, this, keyname, AST__STRING, (void *) &cval, 0,
+                         method, class, status ) ){
+            SetValue( ret, keyname, (void *) &dval, AST__FLOAT, comm, status );
+         }
+      }
+
+/* Translate AIPS spectral CTYPE values to FITS-WCS paper III equivalents.
+   These are of the form AAAA-BBB, where "AAAA" can be "FREQ", "VELO" (=VRAD!)
+   or "FELO" (=VOPT-F2W), and BBB can be "LSR", "LSD", "HEL" (=*Bary*centric!)
+   or "GEO". Also convert "LAMBDA" to "WAVE". */
+      for( j = 0; j < naxis; j++ ) {
+         if( GetValue2( ret, this, FormatKey( "CTYPE", j + 1, -1, s, status ),
+                       AST__STRING, (void *) &cval, 0, method,
+                       class, status ) ){
+            if( IsAIPSSpectral( cval, &astype, &assys, status ) ) {
+               SetValue( ret, FormatKey( "CTYPE", j + 1, -1, s, status ),
+                         (void *) &astype, AST__STRING, NULL, status );
+               SetValue( ret, "SPECSYS", (void *) &assys, AST__STRING, NULL, status );
+               break;
+            } else if( !strcmp( cval, "LAMBDA  " ) ) {
+               cval = "WAVE";
+               SetValue( ret, FormatKey( "CTYPE", j + 1, -1, s, status ),
+                         (void *) &cval, AST__STRING, NULL, status );
+               break;
+            }
+         }
+      }
+
+/* Common case insensitive CUNIT values: "Hz", "Angstrom", "km/s", "M/S" */
+      if( s != ' ' ) {
+         sprintf( template, "CUNIT%%d%c", s );
+      } else {
+         strcpy( template, "CUNIT%d" );
+      }
+      if( astKeyFields( this, template, 1, &jhi, &jlo ) ){
+
+/* Convert keyword indices from 1-based to 0-base, and loop round them all. */
+         jhi--;
+         jlo--;
+         for( j = jlo; j <= jhi; j++ ){
+            char *keynam;
+            keynam =  FormatKey( "CUNIT", j + 1, -1, s, status );
+            if( GetValue2( ret, this, keynam, AST__STRING, (void *) &cval, 0,
+                           method, class, status ) ){
+               size_t nc = astChrLen( cval );
+               if( nc == 0 ) {
+                  cval = NULL;
+               } else if( !Ustrcmp( cval, "Hz", status ) ) {
+                  cval = "Hz";
+               } else if( !Ustrcmp( cval, "Angstrom", status ) ) {
+                  cval = "Angstrom";
+               } else if( !Ustrcmp( cval, "km/s", status ) ) {
+                  cval = "km/s";
+               } else if( !Ustrcmp( cval, "m/s", status ) ) {
+                  cval = "m/s";
+               } else {
+                  cval = NULL;
+               }
+               if( cval ) SetValue( ret, keynam, (void *) &cval, AST__STRING, NULL, status );
+            }
+         }
+      }
+
+/* After doing the primary axis descriptions, prepare to do the "A"
+   description. */
+      if( s == ' ' ) s = 'A' - 1;
+   }
+
+/* IRAF mini-WCS keywords
+   ---------------------- */
+
+/* Rewind the FitsChan to search from the first card. */
+   astClearCard( this );
+
+/* Search forward through until all cards have been checked. */
+   while( !astFitsEof( this ) && astOK ){
+
+/* Check to see if the keyword name from the current card matches
+   any of the known mini-WCS keywords. If so, mark the card as read. */
+      if( Match( CardName( this, status ), "WAT%d_%d", 0, NULL, &m, method, class, status ) ||
+          Match( CardName( this, status ), "LTM%d_%d", 0, NULL, &m, method, class, status ) ||
+          Match( CardName( this, status ), "LTV%d", 0, NULL, &m, method, class, status ) ||
+          Match( CardName( this, status ), "WSV%d_LEN", 0, NULL, &m, method, class, status ) ||
+          Match( CardName( this, status ), "WSV%d_%d", 0, NULL, &m, method, class, status ) ){
+          MarkCard( this, status );
+      }
+
+/* Now move the current card on to the next card. */
+      MoveCard( this, 1, method, class, status );
+   }
+
+/* Delete the returned FitsChan if it is empty. */
+   if( ret && !astGetNcard( ret ) ) ret = (AstFitsChan *) astDelete( ret );
+
+/* Return. */
+   return ret;
+}
+
+int Split( AstFitsChan *this, const char *card, char **name, char **value,
+           char **comment, const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     Split
+
+*  Purpose:
+*     Extract the keyword name, value and comment from a FITS header card.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int Split( AstFitsChan *this, const char *card, char **name, char **value,
+*                char **comment, const char *method, const char *class, int *status  )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     The name, value and comment (if present) are extracted from the
+*     supplied card text and returned.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsCHan.
+*     card
+*        Pointer to a string holding the FITS header card.
+*     name
+*        Pointer to a location at which to return the pointer to a string
+*        holding the keyword name.
+*     value
+*        Pointer to a location at which to return the pointer to a string
+*        holding the keyword value.
+*     comment
+*        Pointer to a location at which to return the pointer to a string
+*        holding the keyword comment.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned value:
+*     -  An integer identifying the data type of the keyword value. This
+*     will be one of the values AST__UNDEF, AST__COMMENT, AST__INT,
+*     AST__STRING, AST__CONTINUE, AST__FLOAT, AST__COMPLEXI or AST__COMPLEXF
+*     defined in fitschan.h.
+
+*  Notes:
+*     -  If the keyword value is a string, then the returned value does not
+*     include the delimiting quotes, and pairs of adjacent quotes within the
+*     string are replaced by single quotes.
+*     -  A maximum of 80 characters are read from the supplied card, so the
+*     string does not need to be null terminated unless less than 80
+*     characters are to be read.
+*     -  The memory holding the three strings "name", "value" and "comment"
+*     should be released when no longer needed using astFree.
+*     -  NULL pointers and a data type of AST__COMMENT are returned if an
+*     error has already occurred, or if this function fails for any reason.
+*/
+
+/* Local Variables: */
+   char *c;                   /* Pointer to returned comment string */
+   char *dd;                  /* Pointer to intermediate character */
+   char *slash;               /* Pointer to comment character */
+   char *v;                   /* Pointer to returned value string */
+   char buf[255];             /* Buffer for warning text */
+   const char *d;             /* Pointer to first comment character */
+   const char *v0;            /* Pointer to first non-blank value character */
+   double fi, fr;             /* Values read from value string */
+   int badval;                /* Is the keyword value illegal? */
+   int blank_name;            /* Is keyword name blank? */
+   int cont;                  /* Is this a continuation card? */
+   int i;                     /* Character index */
+   int ii, ir;                /* Values read from value string */
+   int iopt;                  /* Index of option within list */
+   int len;                   /* Used length of value string */
+   int lq;                    /* Was previous character an escaping quote? */
+   int nch;                   /* No. of characters used */
+   int ndig;                  /* No. of digits in the formatted integer */
+   int type;                  /* Keyword data type */
+   size_t nc;                 /* Number of character in the supplied card */
+   size_t ncc;                /* No. of characters in the comment string */
+   size_t ncv;                /* No. of characters in the value string */
+
+/* Initialise the returned pointers. */
+   *name = NULL;
+   *value = NULL;
+   *comment = NULL;
+   type = AST__COMMENT;
+
+/* Check the global status. */
+   if( !astOK ) return type;
+
+/* Assume initially that the keyword value is legal. */
+   badval = 0;
+
+/* Store the number of characters to be read from the supplied card. This
+   is not allowed to be more than the length of a FITS header card. */
+   nc = 0;
+   while( nc < AST__FITSCHAN_FITSCARDLEN && card[ nc ] ) nc++;
+
+/* Reduce the number of characters to read so that any non-printing
+   characters such as new-lines at the end of the string are ignored. */
+   while( nc > 0 && !isprint( card[ nc - 1 ] ) ) nc--;
+
+/* Allocate memory for a copy of the keyword name plus a terminating
+   null character. */
+   *name = (char *) astMalloc( ( 1 + FITSNAMLEN )*sizeof(char) );
+
+/* Check the pointer can be used. */
+   if( astOK ){
+
+/* Initialise the name string by filling it with spaces, and terminating it. */
+      for( i = 0; i < FITSNAMLEN; i++ ) (*name)[ i ] = ' ';
+      (*name)[ FITSNAMLEN ] = 0;
+
+/* Copy the the keyword name, ensuring that no more than FITSNAMLEN (8)
+   characters are copied. */
+      strncpy( *name, card, ( nc > FITSNAMLEN ) ? FITSNAMLEN : nc );
+
+/* If there is no keyword name, flag that we have a blank name which will
+   be treated as a comment card. */
+      if( strspn( *name, " " ) == strlen( *name ) ){
+         blank_name = 1;
+
+/* If the card contains a keyword name, replace any white space with
+   nulls. */
+      } else {
+         blank_name = 0;
+         dd = *name + strlen( *name ) - 1;
+         while( isspace( *dd ) ) *(dd--) = 0;
+      }
+
+/* Check the keyword name is legal. */
+      CheckFitsName( this, *name, method, class, status );
+
+/* Allocate memory to hold the keyword value and comment strings. */
+      *value = (char *) astMalloc( sizeof(char)*( 2 + nc ) );
+      *comment = (char *) astMalloc( sizeof(char)*( 1 + nc ) );
+
+/* Check the pointers can be used. */
+      if( astOK ){
+
+/* Check for CONTINUE cards. These have keyword CONTINUE but have a space
+   instead of an equals sign in column 9. They must also have a single quote
+   in column 11. */
+         cont = ( !Ustrcmp( *name, "CONTINUE", status ) &&
+                  nc > FITSNAMLEN + 3 &&
+                  card[ FITSNAMLEN ] == ' ' &&
+                  card[ FITSNAMLEN + 2 ] == '\'' );
+
+/* If column 9 does not contain an equals sign (but is not a CONTINUE card), or if
+   the keyword is "HISTORY", "COMMENT" or blank, then columns 9 to the end are
+   comment characters, and the value string is null. */
+         if( ( nc <= FITSNAMLEN || card[ FITSNAMLEN ] != '='
+                                || !Ustrcmp( *name, "HISTORY", status )
+                                || !Ustrcmp( *name, "COMMENT", status )
+                                || blank_name ) && !cont ){
+            (*value)[ 0 ] = 0;
+            if( nc > FITSNAMLEN ){
+               (void) strncpy( *comment, card + FITSNAMLEN,
+                               nc - FITSNAMLEN );
+               (*comment)[ nc - FITSNAMLEN ] = 0;
+            } else {
+               (*comment)[ 0 ] = 0;
+            }
+
+/* Otherwise there is a value field. */
+         } else {
+
+/* Find the first non-blank character in the value string. */
+            v0 = card + FITSNAMLEN + 1;
+            while( (size_t)(v0 - card) < nc &&
+                   isspace( (int) *v0 ) ) v0++;
+
+/* Store pointers to the start of the returned value and comment strings. */
+            v = *value;
+            c = *comment;
+
+/* If the first character in the value string is a single quote, the value is
+   a string. In this case the value ends at the first non-escaped single
+   quote. */
+            if( *v0 == '\''){
+               type = cont ? AST__CONTINUE : AST__STRING;
+
+/* We want to copy the string value, without the delimiting quotes, to the
+   returned value string. Single quotes within the string are represented
+   by two adjacent quotes, so we also need to check for these and replace
+   them by one quote in the returned string. First initialise a pointer
+   to the first character after the opening quote, and set a flag
+   indicating that (for the purposes of identifying pairs of adjacent
+   quotes within the string) the previous character was not a quote. */
+               d = v0 + 1;
+               lq = 0;
+
+/* Loop round each remaining character in the supplied card. */
+               while( (size_t)(d - card) < nc ){
+
+/* If the current character is a single quote... */
+                  if( *d == '\'' ){
+
+/* If the previous character was also a single quote then the quote does
+   not mark the end of the string, but is a quote to be included literally
+   in the value. Copy the quote to the returned string and clear the flag
+   to indicate that the pair of adjacent quotes is now complete. */
+                    if( lq ){
+                       *(v++) = '\'';
+                       lq = 0;
+
+/* If the last character was not a quote, then set the flag for the next
+   pass through the loop, but do not copy the quote to the returned string
+   since it will either be a quote escaping a following adjacent quote, or
+   a quote to mark the end of the string. */
+                    } else {
+                       lq = 1;
+                    }
+
+/* If the current character is not a quote... */
+                  } else {
+
+/* If the previous character was a quote, then we have found a single
+   isolated quote which therefore marks the end of the string value.
+   The pointer "d" is left pointing to the first character
+   after the terminating quote. */
+                     if( lq ){
+                        break;
+
+/* If the last character was not a quote, copy it to the returned string. */
+                     } else {
+                        *(v++) = *d;
+                     }
+                  }
+                  d++;
+               }
+
+/* Terminate the returned value string. */
+               *v = 0;
+
+/* Now deal with logical and numerical values. */
+            } else {
+
+/* The end of the value field is marked by the first "/". Find the number
+   of characters in the value field. Pointer "d" is left pointing to the
+   first character in the comment (if any). Only use "/" characters which
+   occur within the first nc characters, and do not occur wiuthin the
+   keyword name (not strictly legal, but a warning will have been issued
+   by CheckFitsName in such cases). */
+               d = strchr( card + FITSNAMLEN, '/' );
+               if( !d || ( d - card ) >= nc ){
+                  ncv = nc - FITSNAMLEN - 1;
+                  d = NULL;
+               } else {
+                  ncv = (size_t)( d - card ) - FITSNAMLEN - 1;
+               }
+
+/* Copy the value string to the returned string. */
+               if( ncv == 0 ){
+                  *v = 0;
+               } else {
+                  strncpy( v, card + FITSNAMLEN + 1, ncv );
+                  v[ ncv ] = ' ';
+                  v[ ncv + 1 ] = 0;
+               }
+
+/* Find the first non-blank character in the value string. */
+               v0 = v;
+               while( *v0 && isspace( (int) *v0 ) ) v0++;
+
+/* See if the value string is one of the following strings (optionally
+   abbreviated and case insensitive): YES, NO, TRUE, FALSE. */
+               iopt = FullForm( "YES NO TRUE FALSE", v0, 1, status );
+
+/* Return the single character "T" or "F" at the start of the value string
+   if the value matches one of the above strings. */
+               if( iopt == 0 || iopt == 2 ) {
+                  type = AST__LOGICAL;
+                  strcpy ( v, "T" );
+               } else if( iopt == 1 || iopt == 3 ) {
+                  type = AST__LOGICAL;
+                  strcpy ( v, "F" );
+
+/* If it does not match, see if the value is numerical. */
+               } else {
+
+/* Save the length of the value string excluding trailing blanks. */
+                  len = ChrLen( v, status );
+
+/* If the entire string is blank, the value type is UNDEF. */
+                  if( len == 0 ) {
+                     type = AST__UNDEF;
+
+/* If there are no dots (decimal points) or exponents (D or E) in the value... */
+                  } else if( !strpbrk( v, ".EeDd" ) ){
+
+/* First attempt to read two integers from the string (separated by white
+   space). */
+                     if( nch = 0,
+                         ( 2 == astSscanf( v, " %d %d%n", &ir, &ii, &nch ) ) &&
+                         ( nch >= len ) ) {
+                        type = AST__COMPLEXI;
+
+/* If that failed, attempt to read a single integer from the string. */
+                     } else if( nch = 0,
+                         ( 1 == astSscanf( v, " %d%n", &ir, &nch ) ) &&
+                         ( nch >= len ) ) {
+                        type = AST__INT;
+                     }
+
+/* If there are dots (decimal points) in the value... */
+                  } else {
+
+/* First attempt to read two doubles from the string (separated by white
+   space). */
+                     if( nch = 0,
+                         ( 2 == astSscanf( v, " %lf %lf%n", &fr, &fi, &nch ) ) &&
+                         ( nch >= len ) ) {
+                        type = AST__COMPLEXF;
+
+/* If that failed, attempt to read a single double from the string. */
+                     } else if( nch = 0,
+                         ( 1 == astSscanf( v, " %lf%n", &fr, &nch ) ) &&
+                         ( nch >= len ) ) {
+                        type = AST__FLOAT;
+                     }
+
+/* If both the above failed, it could be because the string contains a
+   "D" exponent (which is probably valid FITS) instead of an "E" exponent.
+   Replace any "D" in the string with "e" and try again. */
+                     if( type == AST__COMMENT && astOK ) {
+
+/* Replace "d" and "D" by "e" (if this doesn't produce a readable floating
+   point value then the value string will not be used, so it is safe to
+   do the replacement in situ). */
+                        for( i = 0; i < len; i++ ) {
+                           if( v[ i ] == 'd' || v[ i ] == 'D' ) v[ i ] = 'e';
+                        }
+
+/* Attempt to read two doubles from the edited string (separated by white
+   space). */
+                        if( nch = 0,
+                          ( 2 == astSscanf( v, " %lf %lf%n", &fr, &fi, &nch ) ) &&
+                          ( nch >= len ) ) {
+                           type = AST__COMPLEXF;
+
+/* If that failed, attempt to read a single double from the edited string. */
+                        } else if( nch = 0,
+                            ( 1 == astSscanf( v, " %lf%n", &fr, &nch ) ) &&
+                            ( nch >= len ) ) {
+                           type = AST__FLOAT;
+                        }
+                     }
+                  }
+               }
+
+/* If the value type could not be determined, indicate that a warning
+   should be issued. */
+               if( type == AST__COMMENT && astOK ) {
+                  badval = 1;
+                  (*value)[ 0 ] = 0;
+                  (*comment)[ 0 ] = 0;
+                  d = NULL;
+               }
+            }
+
+/* Find the number of characters in the comment. Pointer "d" should point to
+   the first character following the value string. */
+            if( d ){
+               ncc = nc - (size_t)( d - card );
+            } else {
+               ncc = 0;
+            }
+
+/* Copy the remainder of the card to the returned comment string. */
+            if( astOK && ncc > 0 ){
+               strncpy( c, d, ncc );
+               c[ ncc ] = 0;
+
+/* Find the start of the comment (indicated by the first "/" after the
+   value string). */
+               slash = strchr( c, '/' );
+
+/* Temporarily terminate the string at the slash. */
+               if( slash ) *slash = 0;
+
+/* Shuffle the characters following the slash down to the
+   start of the returned string. */
+               if( slash ){
+                  ncc -= (size_t)( slash - c ) + 1;
+                  d = slash + 1;
+                  for( i = 0; i < 1 + (int) ncc; i++ ) *(c++) = *(d++);
+               }
+
+/* If there is no comment string, return a null string. */
+            } else {
+               *c = 0;
+            }
+         }
+      }
+   }
+
+/* Truncate the returned string to avoid wasting space. */
+   if( *name ) *name = (char *) astRealloc( (void *) *name, strlen( *name ) + 1 );
+   if( *comment ) *comment = (char *) astRealloc( (void *) *comment, strlen( *comment ) + 1 );
+   if( *value ) *value = (char *) astRealloc( (void *) *value, strlen( *value ) + 1 );
+
+/* If the value is deemed to be integer, check that the number of digits
+   in the formatted value does not exceed the capacity of an int. This may
+   be the case if there are too many digits in the integer for an "int" to
+   hold. In this case, change the data type to float. */
+   if( *value && type == AST__INT ) {
+      ndig = 0;
+      c = *value;
+      while( *c ) {
+         if( isdigit( *(c++) ) ) ndig++;
+      }
+      if( ndig >= int_dig ) type = AST__FLOAT;
+   }
+
+/* If an error occurred, free the returned strings and issue a context message. */
+   if( !astOK ){
+      *name = (char *) astFree( (void *) *name );
+      *value = (char *) astFree( (void *) *value );
+      *comment = (char *) astFree( (void *) *comment );
+      type = AST__COMMENT;
+      astError( astStatus, "%s(%s): Unable to store the following FITS "
+                "header card:\n%.*s\n", status, method, class,
+                AST__FITSCHAN_FITSCARDLEN, card );
+
+/* If a bad keyword value was encountered, issue a warning. Remember that
+   "card" may not be null terminated, so ensure that only one header is
+   included from "card". */
+   } else if( badval ){
+      snprintf( buf, sizeof(buf), "The keyword value is illegal in "
+                "'%.*s'", AST__FITSCHAN_FITSCARDLEN, card );
+      Warn( this, "badkeyvalue", buf, method, class, status );
+   }
+
+/* Return the data type. */
+   return type;
+}
+
+static int SplitMap( AstMapping *map, int invert, int ilon, int ilat,
+                     AstMapping **map1, AstWcsMap **map2, AstMapping **map3, int *status ){
+/*
+*  Name:
+*     SplitMap
+
+*  Purpose:
+*     Locate a WCS projection within a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int SplitMap( AstMapping *map, int invert, int ilon, int ilat,
+*                   AstMapping **map1, AstWcsMap **map2, AstMapping **map3, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     If possible, the supplied Mapping is decomposed into three component
+*     mappings to be compounded in series. To be acceptable, the second of
+*     these three Mappings must be an inverted WcsMap with a non-zero
+*     FITSProj attribute value, and there must not be such a WcsMap in
+*     either of the other two Mappings. If it is not possible to produce
+*     such a group of three Mappings, then a zero function value is returned,
+*     together with three NULL Mapping pointers. All the mappings before the
+*     WcsMap are compounded together and returned as "map1". The inverse of
+*     the WcsMap itself is returned as "map2", and any remaining Mappings
+*     are compounded together and returned as "map3".
+*
+*     The search algorithm allows for an arbitrary combination of series and
+*     parallel CmpMaps.
+
+*  Parameters:
+*     map
+*        A pointer to the Mapping from pixel to physical coordinates.
+*     invert
+*        The value of the Invert attribute to use with "map" (the value
+*        returned by astGetInvert is not used).
+*     ilon
+*        Index of mapping output which is connected to the longitude axis.
+*     ilat
+*        Index of mapping output which is connected to the latitude axis.
+*     map1
+*        A location at which to return a pointer to the Mapping from pixel
+*        to intermediate world coordinates.
+*     map2
+*        A location at which to return a pointer to the Mapping from
+*        intermediate world coordinates to native spherical coordinates. This
+*        will be an inverted WcsMap with non-zero FITSProj attribute value.
+*     map3
+*        A location at which to return a pointer to the Mapping from
+*        native spherical coordinates to physical coordinates.
+*     dep
+*        The address of an integer holding the current depth of recursion
+*        into this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a suitable WcsMap was found, zero otherwise.
+
+*  Notes:
+*     -  The returned Mappings contain independant copies of the relevant
+*     components of the supplied Mapping and can be modified without
+*     changing the supplied Mapping.
+*     -  NULL pointers will be returned for all Mappings if no WcsMap
+*     can be found in the supplied Mapping.
+*     -  A pointer to a UnitMap will be returned for map1 if no mappings
+*     exist before the WcsMap.
+*     -  A pointer to a UnitMap will be returned for map3 if no mappings
+*     exist after the WcsMap.
+*     -  NULL pointers will be returned for all Mappings and a function
+*     value of zero will be returned if an error has occurred, or if this
+*     function should fail for any reason.
+*/
+
+/* Local Variables */
+   AstFitsChan *fc;        /* Pointer to temporary FitsChan */
+   AstFrameSet *tfs;       /* Temporary FrameSet */
+   AstMapping *mapa;       /* Pre-wcs Mapping */
+   AstMapping *mapc;       /* Post-wcs Mapping */
+   AstMapping *tmap1;      /* Temporary Mapping */
+   AstMapping *tmap2;      /* Temporary Mapping */
+   AstPointSet *pset1;     /* Pixel positions */
+   AstPointSet *pset2;     /* WCS positions */
+   AstWcsMap  *mapb;       /* WcsMap */
+   char card[ AST__FITSCHAN_FITSCARDLEN + 1 ]; /* Buffer for header card */
+   double **ptr1;          /* Pointer to pixel axis values */
+   double **ptr2;          /* Pointer to WCS axis values */
+   double *iwc_origin;     /* Array holding IWC at pixel origin */
+   double *pix_origin;     /* Array holding pixel coords at pixel origin */
+   double *w1;             /* Pointer to work space */
+   int i;                  /* Loop index */
+   int npix;               /* Number of pixel axes */
+   int nwcs;               /* Number of WCS axes */
+   int ret;                /* Was a non-linear Mapping found? */
+
+/* Initialise */
+   *map1 = NULL;
+   *map2 = NULL;
+   *map3 = NULL;
+   ret = 0;
+
+/* Check the global status. */
+   if( !astOK ) return ret;
+
+/* Call SplitMap2 to do the work. SplitMap2 does not check that the
+   WcsMap is an *inverted* WcsMap, neither does it check that there
+   are no WcsMaps in either map1 or map3. */
+   if( SplitMap2( map, invert, map1, map2, map3, status ) ) {
+
+/* Check that the WcsMap is inverted. */
+      if( astGetInvert( *map2 ) ) {
+
+/* Check that map 1 does not contain a WcsMap with non-zero FITSProj
+   attribute. */
+         if( !SplitMap2( *map1, astGetInvert( *map1 ), &mapa, &mapb, &mapc,
+                         status ) ) {
+
+/* Check that map 3 does not contain a WcsMap with non-zero FITSProj
+   attribute. */
+            if( !SplitMap2( *map3, astGetInvert( *map3 ), &mapa, &mapb, &mapc,
+                            status ) ) {
+
+/* If so, the three Mappings are OK. */
+               ret = 1;
+            } else {
+               mapa = astAnnul( mapa );
+               mapb = astAnnul( mapb );
+               mapc = astAnnul( mapc );
+            }
+         } else {
+            mapa = astAnnul( mapa );
+            mapb = astAnnul( mapb );
+            mapc = astAnnul( mapc );
+         }
+      }
+   }
+
+/* If the above failed to find a suitable WcsMap, we now consider cases
+   where the pixel->WCS mapping is linear. We can invent a CAR projection
+   WcsMap for such cases. We use a ShiftMap to move the origin of the
+   longitude IWC axis to a sensible value (it is left at zero otherwise).
+   We cannot do this with the latitude axis since pre-FITS-WCS fits
+   readers could not handle the resulting rotation from native to celestial
+   coords. */
+   if( !ret && astGetIsLinear( map ) ) {
+      nwcs = astGetNout( map );
+      npix = astGetNin( map );
+      iwc_origin = astMalloc( sizeof( double )*nwcs );
+      pix_origin = astMalloc( sizeof( double )*npix );
+      if( astOK ) {
+         for( i = 0; i < npix; i++ ) pix_origin[ i ] = 0.0;
+         astTranN( map, 1, npix, 1, pix_origin, 1, nwcs, 1, iwc_origin );
+         for( i = 0; i < nwcs; i++ ) {
+            if( i != ilon ) {
+               iwc_origin[ i ] = 0.0;
+            } else {
+               iwc_origin[ i ] *= -1;
+            }
+         }
+         mapa = (AstMapping *) astShiftMap( nwcs, iwc_origin, "", status );
+         *map1 = (AstMapping *) astCmpMap( map, mapa, 1, "", status );
+         *map2 = astWcsMap( nwcs, AST__CAR, ilon + 1, ilat + 1, "Invert=1", status );
+         astInvert( mapa );
+         *map3 = astClone( mapa );
+         mapa = astAnnul( mapa );
+         ret = 1;
+      }
+      iwc_origin = astFree( iwc_origin );
+      pix_origin = astFree( pix_origin );
+   }
+
+/* If the above failed to find a suitable WcsMap, we now consider cases
+   where the output (long,lat) values are constants supplied by a
+   final PermMap. We can invent a WcsMap for such cases. */
+   if( !ret ) {
+
+/* Transform two arbitrary pixel positions into the WCS Frame. */
+      npix = astGetNin( map );
+      nwcs = astGetNout( map );
+      pset1 = astPointSet( 2, npix, "", status );
+      pset2 = astPointSet( 2, nwcs, "", status );
+      ptr1 = astGetPoints( pset1 );
+      ptr2 = astGetPoints( pset2 );
+      w1 = astMalloc( sizeof( double )*(size_t) nwcs );
+      if( astOK ) {
+         for( i = 0; i < npix; i++ ) {
+            ptr1[ i ][ 0 ] = 1.0;
+            ptr1[ i ][ 1 ] = 1000.0;
+         }
+         (void) astTransform( map, pset1, 1, pset2 );
+
+/* If the two wcs positions have equal longitude and latitude values,
+   assume that the output longitude and latitude axes are assigned
+   constant values by the Mapping. */
+         if( ptr2[ ilon ][ 0 ] == ptr2[ ilon ][ 1 ] &&
+             ptr2[ ilon ][ 0 ] != AST__BAD &&
+             ptr2[ ilat ][ 0 ] == ptr2[ ilat ][ 1 ] &&
+             ptr2[ ilat ][ 0 ] != AST__BAD ) {
+
+/* Create a set of Mappings to return, including a WcsMap, which result in
+   these constant latitude and longitude values. We do this by creating a
+   FITS-WCS header and reading the FrameSet from it. Keywords which are not
+   important to the final mappings are given arbitrary values. */
+            fc = astFitsChan( NULL, NULL, "", status );
+            for( i = 0; i < nwcs; i++ ) {
+               sprintf( card, "CRPIX%d  = 0", i + 1 );
+               astPutFits( fc, card, 0 );
+               sprintf( card, "CDELT%d  = 0.0003", i + 1 );
+               astPutFits( fc, card, 0 );
+               if( i == ilon ) {
+                  sprintf( card, "CTYPE%d  = 'RA---TAN'", i + 1 );
+               } else if( i == ilat ) {
+                  sprintf( card, "CTYPE%d  = 'DEC--TAN'", i + 1 );
+               } else {
+                  sprintf( card, "CTYPE%d  = 'DUMMY'", i + 1 );
+               }
+               astPutFits( fc, card, 0 );
+               if( i == ilon ) {
+                  sprintf( card, "CRVAL%d  = %.*g", i + 1, DBL_DIG, AST__DR2D*ptr2[ ilon ][ 0 ] );
+               } else if( i == ilat ) {
+                  sprintf( card, "CRVAL%d  = %.*g", i + 1, DBL_DIG, AST__DR2D*ptr2[ ilat ][ 0 ] );
+               } else {
+                  sprintf( card, "CRVAL%d  = 0.0", i + 1 );
+               }
+               astPutFits( fc, card, 0 );
+            }
+            astClearCard( fc );
+            tfs = astRead( fc );
+            if( tfs ) {
+
+/* Use SplitMap to get the required Mapings from the FrameSet. */
+               tmap2 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
+               SplitMap( tmap2, astGetInvert( tmap2 ), 0, 1, &tmap1, map2,
+                         map3, status );
+               tmap1 = astAnnul( tmap1 );
+               tmap2 = astAnnul( tmap2 );
+
+/* Create a ShiftMap which subtract the constant longitude and latitude
+   values off the inputs. */
+               for( i = 0; i < nwcs; i++ ) w1[ i ] = 0.0;
+               w1[ ilon ] = -ptr2[ ilon ][ 0 ];
+               w1[ ilat ] = -ptr2[ ilat ][ 0 ];
+               tmap1 = (AstMapping *) astShiftMap( nwcs, w1, "", status );
+
+/* Compose this with the supplied Mapping. This results in the celestial
+   outputs being zero. This gives the required "map1". */
+               *map1 = (AstMapping *) astCmpMap( map, tmap1, 1, "", status );
+
+/* Indicate success.*/
+               ret = 1;
+
+/* Free resources. */
+               tmap1 = astAnnul( tmap1 );
+               tfs = astAnnul( tfs );
+            }
+            fc = astAnnul( fc );
+         }
+      }
+
+/* Free resources */
+      pset1 = astAnnul( pset1 );
+      pset2 = astAnnul( pset2 );
+      w1 = astFree( w1 );
+   }
+   if( !ret ) {
+      if( *map1 ) *map1 = astAnnul( *map1 );
+      if( *map2 ) *map2 = astAnnul( *map2 );
+      if( *map3 ) *map3 = astAnnul( *map3 );
+   }
+   return ret;
+}
+
+static int SplitMap2( AstMapping *map, int invert, AstMapping **map1,
+                      AstWcsMap **map2, AstMapping **map3, int *status ){
+/*
+*  Name:
+*     SplitMap2
+
+*  Purpose:
+*     Locate a WCS projection within a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int SplitMap2( AstMapping *map, int invert, AstMapping **map1,
+*                    AstWcsMap **map2, AstMapping **map3, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     If possible, the supplied Mapping is decomposed into three component
+*     mappings to be compounded in series. To be acceptable, the second of
+*     these three Mappings must be a WcsMap with a non-zero FITSProj value.
+*     If it is not possible to produce such a group of three Mappings, then a
+*     zero function value is returned, together with three NULL Mapping
+*     pointers. All the mappings before the WcsMap are compounded together
+*     and returned as "map1". The WcsMap itself is returned as "map2", and
+*     any remaining Mappings are compounded together and returned as "map3".
+*
+*     The search algorithm allows for an arbitrary combination of series and
+*     parallel CmpMaps.
+
+*  Parameters:
+*     map
+*        A pointer to the Mapping from pixel to physical coordinates.
+*     invert
+*        The value of the Invert attribute to use with "map" (the value
+*        returned by astGetInvert is not used).
+*     map1
+*        A location at which to return a pointer to the Mapping from pixel
+*        to intermediate world coordinates.
+*     map2
+*        A location at which to return a pointer to the Mapping from relative
+*        physical coordinates to native spherical coordinates. This will
+*        be a WcsMap, and it will have a non-zero FITSProj value.
+*     map3
+*        A location at which to return a pointer to the Mapping from
+*        native spherical coordinates to physical coordinates.
+*     dep
+*        The address of an integer holding the current depth of recursion
+*        into this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a suitable WcsMap was found, zero otherwise.
+
+*  Notes:
+*     -  The returned Mappings contain independant copies of the relevant
+*     components of the supplied Mapping and can be modified without
+*     changing the supplied Mapping.
+*     -  NULL pointers will be returned for all Mappings if no WcsMap
+*     with anon-zero FITSProj value can be found in the supplied Mapping.
+*     -  A pointer to a UnitMap will be returned for map1 if no mappings
+*     exist before the WcsMap.
+*     -  A pointer to a UnitMap will be returned for map3 if no mappings
+*     exist after the WcsMap.
+*     -  NULL pointers will be returned for all Mappings and a function
+*     value of zero will be returned if an error has occurred, or if this
+*     function should fail for any reason.
+*     - "*map1" and "*map3" may contain WcsMaps, but they will have zero
+*     values for their FITSProj values.
+*/
+
+/* Local Variables */
+   AstMapping **map_list;  /* Mapping array pointer */
+   AstMapping *mapa;       /* Pre-wcs Mapping */
+   AstWcsMap *mapb;        /* WcsMap */
+   AstMapping *mapc;       /* Post-wcs Mapping */
+   AstMapping *temp;       /* Intermediate Mapping */
+   const char *class;      /* Pointer to class of supplied Mapping */
+   double pv;              /* Projection parameter value */
+   int *invert_list;       /* Invert array pointer */
+   int axis;               /* No. of axes in whole Mapping */
+   int axlat;              /* Index of latitude axis */
+   int axlon;              /* Index of longitude axis */
+   int haswcs;             /* Was a usable inverted WcsMap found? */
+   int imap;               /* Index of current Mapping in list */
+   int i;                  /* axis index */
+   int m;                  /* Parameter index */
+   int nax;                /* No. of axes in Mapping */
+   int nmap;               /* Number of Mappings in the list */
+   int ret;                /* Was a non-linear Mapping found? */
+   int wcsaxis;            /* Index of first WcsMap axis */
+
+/* Initialise */
+   *map1 = NULL;
+   *map2 = NULL;
+   *map3 = NULL;
+   ret = 0;
+
+/* Check the global status. */
+   if( !astOK ) return ret;
+
+/* Get the class of the Mapping. */
+   class = astGetClass( map );
+
+/* If the supplied Mapping is a CmpMap... */
+   wcsaxis = -1;
+   if( !strcmp( class, "CmpMap" ) ){
+
+/* Decompose the Mapping into a sequence of Mappings to be applied in
+   series and an associated list of Invert flags. */
+      map_list = NULL;
+      invert_list = NULL;
+      nmap = 0;
+      astMapList( map, 1, invert, &nmap, &map_list, &invert_list );
+
+/* If there is more than one Mapping, this must be a series CmpMap. */
+      if( nmap > 1 && astOK ){
+
+/* Initialise the returned pre-wcs Mapping to be a UnitMap. */
+         if( invert == astGetInvert( map ) ){
+            *map1 = (AstMapping *) astUnitMap( astGetNin( map ), "", status );
+         } else {
+            *map1 = (AstMapping *) astUnitMap( astGetNout( map ), "", status );
+         }
+
+/* Indicate we have not yet found  a WcsMap. */
+         ret = 0;
+
+/* Process each series Mapping. */
+         for( imap = 0; imap < nmap; imap++ ){
+
+/* If we have not yet found a WcsMap... */
+            if( !ret ){
+
+/* Search this Mapping for a WcsMap. */
+               ret = SplitMap2( map_list[ imap ], invert_list[ imap ], &mapa,
+                                map2, map3, status );
+
+/* If no WcsMap was found, use the whole mapping as part of the
+   pre-wcs Mapping. */
+               if( !ret ){
+                  mapa = astCopy( map_list[ imap ] );
+                  astSetInvert( mapa, invert_list[ imap ] );
+               }
+
+/* Add the pre-wcs mapping to the cumulative pre-wcs CmpMap. */
+               temp = (AstMapping *) astCmpMap( *map1, mapa, 1, "", status );
+               *map1 = astAnnul( *map1 );
+               mapa = astAnnul( mapa );
+               *map1 = temp;
+
+/* If we have previously found a WcsMap, use the whole mapping
+   as part of the post-wcs mapping. */
+            } else {
+               mapc = astCopy( map_list[ imap ] );
+               astSetInvert( mapc, invert_list[ imap ] );
+               temp = (AstMapping *) astCmpMap( *map3, mapc, 1, "", status );
+               *map3 = astAnnul( *map3 );
+               mapc = astAnnul( mapc );
+               *map3 = temp;
+            }
+         }
+
+/* If there is only one Mapping, this must be a parallel CmpMap. */
+      } else {
+
+/* Annul the Mapping pointer in the series list created above, and free the
+   dynamic arrays. */
+         map_list[ 0 ] = astAnnul( map_list[ 0 ] );
+         map_list = astFree( map_list );
+         invert_list = astFree( invert_list );
+         nmap = 0;
+
+/* Decompose the Mapping into a sequence of Mappings to be applied in
+   parallel and an associated list of Invert flags. */
+         astMapList( map, 0, invert, &nmap, &map_list, &invert_list );
+
+/* Process each parallel Mapping. */
+         axis = 0;
+         for( imap = 0; imap < nmap && astOK; imap++ ){
+
+/* See if this Mapping contains a usable WcsMap. Only do the search
+   if no such WcsMap has already been found, since only the first is usable. */
+            if( !ret ) {
+
+/* Search this Mapping for a WcsMap. */
+               haswcs = SplitMap2( map_list[ imap ], invert_list[ imap ], &mapa,
+                                  &mapb, &mapc, status );
+
+/* Note if we have found a usable WcsMap, and its first axis index. */
+               if( haswcs ){
+                  ret = 1;
+                  wcsaxis = axis;
+               }
+
+/* If a WcsMap has already been found, the mapping cannot contain a
+   usable WcsMap. */
+            } else {
+               haswcs = 0;
+            }
+
+/* If the Mapping did not contain a usable WcsMap, use the whole mapping as
+   part of the pre-wcs Mapping, and create a UnitMap as part of the post-wcs
+   mapping. */
+            if( !haswcs ){
+               mapa = astCopy( map_list[ imap ] );
+               astSetInvert( mapa, invert_list[ imap ] );
+               nax = astGetNout( mapa );
+               mapc = (AstMapping *) astUnitMap( nax, "", status );
+            }
+
+/* Increment the index of the first axis in the next Mapping. */
+            axis += astGetNout( mapa );
+
+/* Add the pre-wcs mapping in parallel with the cumulative pre-wcs CmpMap. */
+            if( *map1 ){
+               temp = (AstMapping *) astCmpMap( *map1, mapa, 0, "", status );
+               *map1 = astAnnul( *map1 );
+               mapa = astAnnul( mapa );
+               *map1 = temp;
+            } else {
+               *map1 = mapa;
+            }
+
+/* Add the post-wcs mapping in parallel with the cumulative post-wcs CmpMap. */
+            if( *map3 ){
+               temp = (AstMapping *) astCmpMap( *map3, mapc, 0, "", status );
+               *map3 = astAnnul( *map3 );
+               mapc = astAnnul( mapc );
+               *map3 = temp;
+            } else {
+               *map3 = mapc;
+            }
+         }
+
+/* If a usable WcsMap was found, create a new one which has all the same
+   properties, but with enough axes to join the pre and post wcs Mappings
+   together. Ensure the correct axes are used for longitude and latitude,
+   and copy the projection parameters. */
+         if( ret ){
+            axlat = astGetWcsAxis( mapb, 1 );
+            axlon = astGetWcsAxis( mapb, 0 );
+            *map2 = astWcsMap( axis, astGetWcsType( mapb ),
+                               axlon + wcsaxis + 1,
+                               axlat + wcsaxis + 1, "", status );
+            for( i = 0; i < astGetNin( mapb ); i++ ){
+               for( m = 0; m < WCSLIB_MXPAR; m++ ){
+                  if( astTestPV( mapb, i, m ) ) {
+                     pv = astGetPV( mapb, i, m );
+                     if( pv != AST__BAD ) astSetPV( *map2, i + wcsaxis, m, pv );
+                  }
+               }
+            }
+            astInvert( *map2 );
+            mapb = astAnnul( mapb );
+         }
+      }
+
+/* Loop to annul all the Mapping pointers in the list. */
+      for ( imap = 0; imap < nmap; imap++ ) map_list[ imap ] = astAnnul( map_list[ imap ] );
+
+/* Free the dynamic arrays. */
+      map_list = astFree( map_list );
+      invert_list = astFree( invert_list );
+
+/* If the supplied Mapping is not a CmpMap, see if it is a WcsMap with a
+   non-zero FITSProj value. If so, take a copy and set its invert attribute
+   correctly. Also create UnitMaps for the pre and post wcs mappings. */
+   } else if( astOK && !strcmp( class, "WcsMap" ) && astGetFITSProj( map ) ){
+      ret = 1;
+      nax = astGetNin( map );
+      *map1 = (AstMapping *) astUnitMap( nax, "", status );
+      *map2 = astCopy( map );
+      astSetInvert( *map2, invert );
+      *map3 = (AstMapping *) astUnitMap( nax, "", status );
+   }
+
+/* If an error has occurred, or if no suitable WcsMap was found, annul any
+   Mappings. */
+   if( !astOK || !ret ){
+      ret = 0;
+      if( *map1 ) *map1 = astAnnul( *map1 );
+      if( *map2 ) *map2 = astAnnul( *map2 );
+      if( *map3 ) *map3 = astAnnul( *map3 );
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static int SplitMat( int naxis, double *matrix, double *cdelt, int *status ){
+/*
+*  Name:
+*     SplitMat
+
+*  Purpose:
+*     Factorises a single "CD"-style matrix into a diagonal CDELT matrix
+*     and a "PC"-style matrix.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int SplitMat( int naxis, double *matrix, double *cdelt, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function splits up the supplied CD matrix into separate PC and
+*     CDELT matrices. The product of the returned matrices (CDELT.PC)
+*     equals the supplied CD matrix. The CDELT values are chosen so that
+*     the corresponding row of the PC matrix represents a unit vector.
+*     The signs of the CDELT values are chosen so that the diagonal terms
+*     of the PC matrix are all positive.
+*
+
+*  Parameters:
+*     naxis
+*        The number of axes.
+*     matrix
+*        A pointer to an array of naxis*naxis elements. On entry this holds
+*        the "CD" matrix. On exit, it is modified to represent the "PC"
+*        matrix.
+*     cdelt
+*        A pointer to an array of naxis elements. On exit this holds the CDELT
+*        values for each axis (i.e. the diagonal terms of the CDELT matrix).
+*     status
+*        Pointer to the inherited status variable.
+
+* Returned Value:
+*     Zero is returned if any bad values are found in the supplied
+*     matrix, or if an error has already occurred. One is returned otherwise.
+*/
+
+/* Local Variables: */
+   int i;
+   int j;
+   int ok;
+   double *a;
+   int dineg;
+   double s2;
+   double cdlt;
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* Assume success. */
+   ok = 1;
+
+/* Loop round every row in the matrix. Get a pointer to the first element
+   in the row. */
+   for( i = 0; i < naxis; i++ ){
+      a = matrix + i*naxis;
+
+/* Note the sign of the diagonal term (i.e. the i'th element) of this row. */
+      dineg = ( a[ i ] < 0.0 );
+
+/* Get the magnitude of the vector represented by this row. This is the
+   CDELT value for the row. BAD values cause the whole function to return. */
+      s2 = 0.0;
+      for( j = 0; j < naxis; j++ ){
+         if( *a == AST__BAD )  {
+            ok = 0;
+            break;
+         }
+         s2 += (*a)*(*a);
+         a++;
+      }
+      if( !ok ) break;
+      cdlt = sqrt( astMAX( 0.0, s2 ) );
+
+/* If the diagonal term for this row of the matrix is negative, make
+   the CDELT value negative instead. This means that the diagonal term in
+   the final PC matrix will be positive. */
+      if( dineg ) cdlt = -cdlt;
+
+/* Store the CDELT value. */
+      cdelt[ i ] = cdlt;
+
+/* The row of the PC matrix is obtained by dividing the original row by
+   the CDELT value. Set to zero any PC values which are less than 1.0E-7
+   (such values may be produced by rounding errors). */
+      a = matrix + i*naxis;
+      for( j = 0; j < naxis; j++ ) {
+         if( cdlt != 0.0 ){
+            *a /= cdlt;
+            if( fabs( *a ) < 1.E-7 ) *a = 0.0;
+         } else {
+            *a = 0.0;
+         }
+         a++;
+      }
+   }
+   return ok;
+}
+static void TableSource( AstFitsChan *this,
+                         void (* tabsource)( AstFitsChan *, const char *,
+                                             int, int, int * ),
+                         int *status ){
+
+/*
+*++
+*  Name:
+c     astTableSource
+f     AST_TABLESOURCE
+
+*  Purpose:
+c     Register a source function for accessing tables in FITS files.
+f     Register a source routine for accessing tables in FITS files.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+c     void astTableSource( AstFitsChan *this,
+c                          void (* tabsource)( AstFitsChan *, const char *,
+c                                              int, int, int * ) )
+f     CALL AST_TABLESOURCE( THIS, TABSOURCE, STATUS )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+c     This function can be used to register a call-back function
+f     This routine can be used to register a call-back routine
+*     with a FitsChan. The registered
+c     function
+f     routine
+*     is called when-ever the FitsChan needs to read information from a
+*     binary table contained within a FITS file. This occurs if the
+c     astRead
+f     AST_READ
+*     function is invoked to read a FrameSet from a set of FITS headers
+*     that use the "-TAB" algorithm to describe one or more axes. Such
+*     axes use a FITS binary table to store a look-up table of axis values.
+*     The FitsChan will fail to read such axes unless the "TabOK" attribute
+*     is set to a non-zero positive integer value. The table containing the
+*     axis values must be made available to the FitsChan either by storing
+*     the table contents in the FitsChan (using
+c     astPutTables or astPutTable) prior to invoking astRead
+f     AST_PUTTABLES or AST_PUTTABLE) prior to invoking AST_READ
+*     or by registering a call-back
+c     function using astTableSource.
+f     routine using AST_TABLESOURCE.
+*     The first method is possibly simpler, but requires that the name of
+*     the extension containing the table be known in advance. Since the
+*     table name is embedded in the FITS headers, the name is often not
+*     known in advance. If a call-back is registered, the FitsChan will
+*     determine the name of the required table and invoke the call-back
+c     function
+f     routine
+*     to supply the table at the point where it is needed (i.e. within
+c     the astRead method).
+f     the AST_READ method).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     tabsource
+f     TABSOURCE = SUBROUTINE (Given)
+c        Pointer to the table source function to use.
+f        The table source routine to use.
+*        It takes five arguments - the first is a pointer to the
+*        FitsChan, the second is a string holding the name of the
+*        FITS extension containing the required binary table ("EXTNAME"),
+*        the third is the integer FITS "EXTVER" header value for the
+*        required extension, the fourth is the integer FITS "EXTLEVEL"
+*        header value for the required extension, and the fifth is
+c        a pointer to
+*        the inherited integer status value.
+*
+*        The call-back should read the entire contents (header and data)
+*        of the binary table in the named extension of the external FITS
+*        file, storing the contents in a newly created FitsTable object. It
+*        should then store this FitsTable in the FitsChan using the
+c        astPutTables or astPutTable
+f        AST_PUTTABLES or AST_PUTTABLE
+*        method, and finally annull its local copy of the FitsTable pointer.
+*        If the table cannot be read for any reason, or if any other
+*        error occurs, it should return a non-zero integer for the final
+*        (third) argument.
+*
+c        If "tabsource" is NULL,
+f        If TABSOURCE is AST_NULL,
+*        any registered call-back function will be removed.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+c     - Application code can pass arbitrary data (such as file
+c     descriptors, etc) to the table source function using the
+c     astPutChannelData function. The source function should use
+c     the astChannelData macro to retrieve this data.
+f     - The name  of the routine supplied for the TABSOURCE
+f     argument should appear in an EXTERNAL statement in the Fortran
+f     routine which invokes AST_TABLESOURCE. However, this is not generally
+f     necessary for the null routine AST_NULL (so long as the AST_PAR
+f     include file has been used).
+f     - Note that the null routine AST_NULL (one underscore) is
+f     different to AST__NULL (two underscores), which is the null Object
+f     pointer.
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Register the supplied source function, using the wrapper function
+   appropriate for calling C table source functions. */
+   astSetTableSource( this, (void (*)( void )) tabsource, TabSourceWrap );
+}
+
+static AstMapping *TabMapping( AstFitsChan *this, FitsStore *store, char s,
+                               int **tabaxis, const char *method,
+                               const char *class, int *status ) {
+
+/*
+*  Name:
+*     TabMapping
+
+*  Purpose:
+*     Create a Mapping that performs any -TAB look-ups for all WCS axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstMapping *TabMapping( AstFitsChan *this, FitsStore *store, char s,
+*                             int **tabaxis, const char *method,
+*                             const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function returns a Mapping that has "nwcs" inputs and outputs,
+*     where "nwcs" is the number of FITS WCS axes defined in the supplied
+*     FitsStore. The inputs and outputs are in the same order as the
+*     CTYPEi keywords in the FitsStore. The forward transformation of the
+*     Mapping converts positions from the axes defined by the CRVALi keywords
+*     to the WCS axes. This transformation will be a UnitMap except for
+*     any axes that are described using the "-TAB" algorithm. For "-TAB"
+*     axes, the transformation will implement the relevant coordinate
+*     look-up function.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        Pointer to the FitsStore structure holding the values to use for
+*        the WCS keywords.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     tabaxis
+*        Address of a location at which to store a pointer to an array of
+*        flags, one for each output of the returned Mapping. A flag will
+*        be non-zero if the corresponding output of the returned Mapping
+*        corresponds to a -TAB axis. A NULL pointer is returned if the
+*        returned Mapping is NULL.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a Mapping. A NULL pointer is returned if the FitsChan does
+*     not support the -TAB algorithm (i.e. if the value of the TabOK
+*     attribute is zero or negative), or if no axes use the "-TAB" algorithm.
+*/
+
+/* Local Variables: */
+   AstFitsTable *table;
+   AstKeyMap *used_tables;
+   AstMapping *tmap1;
+   AstMapping *tmap2;
+   AstMapping *indexmap;
+   AstMapping *tmap0;
+   AstMapping *ret;
+   AstPermMap *pm;
+   char name[21];
+   const char *indexcol;
+   const char *extname;
+   const char *cval;
+   const char *ctype;
+   const char *coordscol;
+   double dval;
+   int *marray;
+   int *permin;
+   int *permout;
+   int extlevel;
+   int extver;
+   int iaxis;
+   int iiaxis;
+   int ikey;
+   int interp;
+   int ival;
+   int maxis;
+   int mdim;
+   int nkey;
+   int nperm;
+   int unit;
+   int wcsaxes;
+
+/* Initialise */
+   ret = NULL;
+   *tabaxis = NULL;
+   extname = NULL;
+   tmap0 = NULL;
+   tmap2 = NULL;
+
+/* Check the global status. */
+   if( !astOK ) return ret;
+
+/* Obtain the number of physical axes in the header. If the WCSAXES header
+   was specified, use it. Otherwise assume it is the same as the number
+   of pixel axes. */
+   dval = GetItem( &(store->wcsaxes), 0, 0, s, NULL, method, class, status );
+   if( dval != AST__BAD ) {
+      wcsaxes = (int) dval + 0.5;
+   } else {
+      wcsaxes = store->naxis;
+   }
+
+/* If the FitsChan does not support the -TAB algorithm, return a NULL
+   pointer. */
+   if( astGetTabOK( this ) > 0 ) {
+
+/* Create a KeyMap to hold a list of the used extension names. */
+      used_tables = astKeyMap( " ", status );
+
+/* Allocate memory to indicate if each WCS axis is described by a -TAB
+   algorithm or not. Initialiss it to zero. */
+      *tabaxis = astCalloc( wcsaxes, sizeof( int ) );
+
+/* Allocate memory to hold the FITS-WCS axis index corresponding to each
+   input of the "tmap0" Mapping. Indicate that as yet, not values are
+   stored in this array. Also allocate memory for the inverse of this
+   permutation array. */
+      permout = astMalloc( wcsaxes*sizeof( int ) );
+      permin = astMalloc( wcsaxes*sizeof( int ) );
+      nperm = 0;
+      if( astOK ) {
+
+/* Initialise the permutation arrays. */
+         for( iaxis = 0; iaxis < wcsaxes; iaxis++ ) {
+            permout[ iaxis ] = permin[ iaxis ] = -1;
+         }
+
+/* Otherwise, loop round all FITS WCS axis indices present in the FitsStore. */
+         for( iaxis = 0; iaxis < wcsaxes; iaxis++ ) {
+
+/* If the current FITS WCS axis is already included in the returned
+   Mapping, skip it. This will be the case if the axis uses the same
+   coordinate array as an earlier axis since all FITS WCS axes associated
+   with a coordinate array are processed together. */
+            if( permin[ iaxis ] == -1 ) {
+
+/* See if this WCS axis uses the -TAB algorithm. */
+               ctype = GetItemC( &(store->ctype), iaxis, 0, s, NULL, method,
+                                 class, status );
+               if( ctype && strlen(ctype) > 4 && !strncmp( ctype + 4, "-TAB", 4 ) ) {
+
+/* Get the name of the FITS binary table extension holding the coordinate
+   info. No default, so report an error if not present. */
+                  sprintf( name, "PS%d_0%c", iaxis + 1, s );
+                  extname = GetItemC( &(store->ps), iaxis, 0, s, name, method,
+                                      class, status );
+
+/* Get the extension version and level. */
+                  dval = GetItem( &(store->pv), iaxis, 1, s, NULL, method,
+                                    class, status );
+                  extver = ( dval != AST__BAD ) ? (int) dval : 1;
+                  dval = GetItem( &(store->pv), iaxis, 2, s, NULL, method,
+                                    class, status );
+                  extlevel = ( dval != AST__BAD ) ? (int) dval : 1;
+
+/* Get the FITS binary table. This will invoke any supplied table source
+   function, and put a copy of the table into the FitsChan structure.
+   Report an error if the table can not be obtained. */
+                  table = GetNamedTable( this, extname, extver, extlevel, 1,
+                                         method, status );
+
+/* Add this extension name to a list of used extensions. */
+                  astMapPut0I( used_tables, extname, 1, NULL );
+
+/* Get the name of the table column containing the main coords array. No
+   default so report error if not present. Report an error if the column
+   is not present in the table. */
+                  sprintf( name, "PS%d_1%c", iaxis + 1, s );
+                  coordscol = GetItemC( &(store->ps), iaxis, 1, s, name, method,
+                                        class, status );
+                  if( !astHasColumn( table, coordscol ) && astOK ) {
+                     astError( AST__BADTAB, "%s(%s): Unable to find the "
+                               "coordinate array for FITS-WCS axis %d (type %s): "
+                               "column '%s' cannot be found in table '%s'.", status,
+                               method, class, iaxis + 1, ctype, coordscol, extname );
+                  }
+
+/* Get the number of dimensions spanned by the coordinate array. Report
+   an error if the coordinate array has only one axis (FITS-WCS paper III
+   requires it to have at leats two axes). */
+                  mdim = astGetColumnNdim( table, coordscol );
+                  if( mdim == 1 && astOK ) {
+                     astError( AST__BADTAB, "%s(%s): Unable to use the "
+                               "coordinate array for FITS-WCS axis %d (type %s): "
+                               "column '%s' in table '%s' has one axis but at "
+                               "least two are required.", status, method, class,
+                                iaxis + 1, ctype, coordscol, extname );
+                  }
+
+/* Allocate memory to hold the FITS-WCS axis corresponding to each dimension
+   of the coordinate array. Initialise it to hold -1 (i.e. "no matching
+   FITS-WCS axis yet found") at every element. */
+                  marray = astMalloc( mdim*sizeof( int ) );
+                  if( astOK ) {
+                     for( maxis = 0; maxis < mdim; maxis++ ) {
+                        marray[ maxis ] = -1;
+                     }
+
+/* Loop round each dimension of the coordinate array, storing the index
+   of the corresponding FITS-WCS axis in "marray". We omit the first axis
+   (axis 0) since FITS-WCS Paper III defines it is a "conventional" axis
+   used to enumerate the planes of coordinate values. */
+                     for( maxis = 1; maxis < mdim && astOK ; maxis++ ) {
+
+/* Each axis of the coordinate array (except axis 0) must have one, and only
+   one, corresponding FITS-WCS axis. Check each FITS-WCS axis to find one
+   that uses the same table and column as the "iaxis" axis, and which
+   corresponds to axis "maxis" of the coordinate array. */
+                        for( iiaxis = 0; iiaxis < wcsaxes; iiaxis++ ) {
+                           cval = GetItemC( &(store->ps), iiaxis, 0, s, NULL,
+                                            method, class, status );
+                           if( cval && !strcmp( cval, extname ) ) {
+                              cval= GetItemC( &(store->ps), iiaxis, 1, s, NULL,
+                                              method, class, status );
+                              if( cval && !strcmp( cval, coordscol ) ) {
+                                 dval = GetItem( &(store->pv), iiaxis, 3, s,
+                                                 NULL, method, class, status );
+                                 if( dval != AST__BAD ) {
+                                    ival = (int)( dval + 0.5 );
+                                 } else {
+                                    ival = 1;
+                                 }
+                                 if( ival == maxis ) {
+
+/* Arrive here if the "iiaxis" FITS-WCS axis uses the same table and column
+   as "iaxis", and corresponds to the "maxis" axis in the coordinate
+   array. If this is the first matching FITS-WCS axis, store its index. */
+                                    if( marray[ maxis ] == -1 ) {
+                                       marray[ maxis ] = iiaxis;
+
+/* If a matching FITS-WCS axis has already been found, report an error. */
+                                    } else if( astOK ) {
+                                       astError( AST__BADTAB, "%s(%s): Unable to use "
+                                                 "the coordinate array for FITS-WCS "
+                                                 "axis %d (type %s): more than one "
+                                                 "intermediate WCS axis is mapped onto "
+                                                 " dimension %d of the coordinate "
+                                                 "array in column '%s' of table '%s'.",
+                                                 status, method, class, iaxis + 1,
+                                                 ctype, maxis, coordscol, extname );
+                                    }
+                                 }
+                              }
+                           }
+                        }
+                     }
+
+/* Check that every dimension of the coordinate array (except the first) has
+   a corresponding FITS-WCS axis. */
+                     for( maxis = 1; maxis < mdim && astOK ; maxis++ ) {
+                        if( marray[ maxis ] == -1 ) {
+                           astError( AST__BADTAB, "%s(%s): Unable to use the "
+                                     "coordinate array for FITS-WCS axis %d (type "
+                                     "%s): no intermediate WCS axis is mapped onto "
+                                     " dimension %d of the coordinate array in column "
+                                     " '%s' of table '%s'.", status, method, class,
+                                     iaxis + 1, ctype, maxis, coordscol, extname );
+                        }
+                     }
+
+/* Now we know which FITS-WCS axis corresponds to each dimension of the
+   coordinate array. We now need to form a parallel CmpMap (compound Mapping)
+   by gathering together the indexing vectors for each dimension of the
+   coordinates array. Each indexing vector is represented by an inverted
+   1D LutMap - dimensions that do not have an indexing vector are
+   represented using a UnitMap. */
+                     indexmap = NULL;
+                     unit = 1;
+                     for( maxis = 1; maxis < mdim && astOK ; maxis++ ) {
+
+/* Get the name of the column containing the index array. Defaults is to
+   use a unit index, so do not report an error if not present. */
+                        indexcol = GetItemC( &(store->ps), marray[ maxis ], 2,
+                                             s, NULL, method, class, status );
+
+/* If the table contains an index vector, create a LutMap from it, then
+   invert it. */
+                        if( indexcol ) {
+                           tmap1 = MakeColumnMap( table, indexcol, 1, 0,
+                                                  method, class, status );
+                           astInvert( tmap1 );
+                           unit = 0;
+
+/* If the table does not contain an index vector, use a UnitMap. */
+                        } else {
+                           tmap1 = (AstMapping *) astUnitMap( 1, " ", status );
+                        }
+
+/* Combine the index Mapping for this dimension in parallel with the
+   Mapping for all earlier dimensions. */
+                        if( indexmap ) {
+                           tmap2 = (AstMapping *) astCmpMap( indexmap, tmap1,
+                                                             0, " ", status );
+                           indexmap = astAnnul( indexmap );
+                           tmap1 = astAnnul( tmap1 );
+                           indexmap = tmap2;
+                        } else {
+                           indexmap = tmap1;
+                        }
+                     }
+
+/* Get the interpolation method to use for the main coordinate array.
+   This is an extension to the published -TAB algorithm in which the
+   QVi_4a keyword is assumed to hold zero for linear interpolation (the
+   default) and non-zero for nearest neighbour interpolation. The QVi_4a
+   keyword will be translated to PVi_4a by the SpecTrans function. */
+                     dval = GetItem( &(store->pv), iaxis, 4, s,
+                                     NULL, method, class, status );
+                     if( dval != AST__BAD ) {
+                        interp = (int)( dval + 0.5 );
+                     } else {
+                        interp = 0;
+                     }
+
+/* Make a Mapping from the main coordinate array, and then if required
+   append it in series to the end of the index Mapping created above. */
+                     tmap1 = MakeColumnMap( table, coordscol, 0, interp,
+                                            method, class, status );
+                     if( ! unit ) {
+                        tmap2 = (AstMapping *) astCmpMap( indexmap, tmap1, 1,
+                                                          " ", status );
+                     } else {
+                        tmap2 = astClone( tmap1 );
+                     }
+                     indexmap = astAnnul( indexmap );
+                     tmap1 = astAnnul( tmap1 );
+
+/* Extend the array that holds the zero-based FITS-WCS axis index
+   corresponding to each input of the extended "tmap0" mapping. Also create
+   the inverse permutation (i.e. zero-based "tmap0" input indexed by
+   zero-based FITS-WCS axis index). */
+                     for( maxis = 1; maxis < mdim; maxis++ ) {
+                        permout[ nperm ] = marray[ maxis ];
+                        permin[ marray[ maxis ] ] = nperm++;
+                     }
+
+/*  Free resources. */
+                     marray = astFree( marray );
+                  }
+
+/* Annul the table pointer. */
+                  table = astAnnul( table );
+
+/* Clear the CTYPE algorithm code to indicate that the axis should be
+   considered to be linear from now on. This means that the following
+   functions will create a Mapping from pixel to psi (the system in which
+   the CRVAL values are defined when using -TAB). The psi axes will then
+   be mapping into the CS axes using the Mappign returned by this function. */
+                  strncpy( name, ctype, 4 );
+                  strcpy( name + 4, ctype + 8 );
+                  SetItemC( &(store->ctype), iaxis, 0, s, name, status );
+
+/* Set the returned flag for this axis. */
+                  (*tabaxis)[ iaxis ] = 1;
+
+/* If the FITS WCS axis "iaxis" does not use a -TAB algorithm, describe
+   it in the returned Mapping using a 1D UnitMap. */
+               } else {
+                  tmap2 = (AstMapping *) astUnitMap( 1, " ", status );
+
+/* Extend the array that holds the zero-based FITS-WCS axis index
+   corresponding to each input of the extended "tmap0" mapping. Also create
+   the inverse permutation (i.e. zero-based "tmap0" input indexed by
+   zero-based FITS-WCS axis index). */
+                  permout[ nperm ] = iaxis;
+                  permin[ iaxis ] = nperm++;
+               }
+
+/* Append the Mapping describing the FITS WCS axis "iaxis" in parallel to any
+   Mappings created for earlier "iaxis" axes. */
+               if( tmap0 ) {
+                  tmap1 = (AstMapping *) astCmpMap( tmap0, tmap2, 0, " ", status );
+                  tmap0 = astAnnul( tmap0 );
+                  tmap2 = astAnnul( tmap2 );
+                  tmap0 = tmap1;
+               } else {
+                  tmap0 = tmap2;
+               }
+            }
+         }
+
+/* If no -TAB axes were found, just return a NULL pointer. */
+         if( extname && astOK ) {
+
+/* Do a sanity check on the permutation arrays. */
+            for( iaxis = 0; iaxis < wcsaxes; iaxis++ ) {
+               if( permin[ iaxis ] < 0 || permin[ iaxis ] >= wcsaxes ||
+                   permout[ permin[ iaxis ] ] != iaxis ) {
+                  astError( AST__INTER, "%s(%s): Invalid permutation "
+                            "arrays in function TabMapping (internal AST "
+                            "progranmming error).", status, method, class );
+                  break;
+               }
+            }
+
+/* Sandwich the "tmap0" Mapping in series between two PermMaps to create a
+   Mapping in which the inputs and outputs correspond to FITS WCS axis
+   numbering. */
+            pm = astPermMap( wcsaxes, permin, wcsaxes, permout, NULL, " ",
+                             status  );
+            tmap1 = (AstMapping *) astCmpMap( pm, tmap0, 1, " ", status );
+            astInvert( pm );
+            tmap2 = (AstMapping *) astCmpMap( tmap1, pm, 1, " ", status );
+            pm = astAnnul( pm );
+            tmap1 = astAnnul( tmap1 );
+
+/* Simplify the returned Mapping. */
+            ret = astSimplify( tmap2 );
+            tmap2 = astAnnul( tmap2 );
+         }
+
+/* Free remaining resources */
+         tmap0 = astAnnul( tmap0 );
+      }
+      permout = astFree( permout );
+      permin = astFree( permin );
+
+/* Remove all used tables from the FitsChan now that they have been used. */
+      nkey = astMapSize( used_tables );
+      for( ikey = 0; ikey < nkey; ikey++ ) {
+         astRemoveTables( this, astMapKey( used_tables, ikey ) );
+      }
+
+/* Delete the KeyMap holding the used table names. */
+      used_tables = astAnnul( used_tables );
+
+/* If we are not returning a Mapping, ensure we do not return any axis
+   flags either. */
+      if( !ret ) *tabaxis = astFree( *tabaxis );
+   }
+
+/* Return the result */
+   return ret;
+}
+static void TabSourceWrap( void (*tabsource)( void  ),
+                           AstFitsChan *this, const char *extname,
+                           int extver, int extlevel, int *status ){
+
+/*
+*  Name:
+*     TabSourceWrap
+
+*  Purpose:
+*     Wrapper function to invoke the C table source function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void TabSourceWrap( void (*tabsource)( void ),
+*                         AstFitsChan *this, const char *extname,
+*                         int extver, int extlevel, int *status )
+
+*  Class Membership:
+*     Channel member function.
+
+*  Description:
+*     This function invokes the table source function whose pointer is
+*     supplied in order to read a named FITS binary table from an external
+*     FITS file.
+
+*  Parameters:
+*     tabsource
+*        Pointer to the C tab source function.
+*     this
+*        Pointer to the FitsChan. The reference count for the FitsChan is
+*        decremented by this function (this behaviour is imposed by
+*        restrictions in the equivalent Fortran wrapper function).
+*     extname
+*        Pointer to the string holding the name of the FITS extension
+*        from which a table is to be read.
+*     extver
+*        The integer "EXTVER" value for the required extension.
+*     extlevel
+*        The integer "EXTLEVEL" value for the required extension.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFitsChan *this_id;
+   int lstat;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get an external identifier for the FitsChan. Could use astClone here
+   to avoid this function anulling the supplied pointer, but the F77 wrapper
+   cannot use the protected version of astClone, so for consistency we do
+   not use it here either. */
+   this_id = astMakeId( this );
+
+/* Invoke the table source function (casting it to the C API first) to
+   read the table, and store it in the FitsChan. */
+   ( *( void (*)( struct AstFitsChan *, const char *, int, int, int * ) )tabsource )( this_id, extname, extver, extlevel, &lstat );
+
+/* Free the FitsChan identifier (this annuls the supplied "this" pointer). */
+   this_id = astAnnulId( this_id );
+
+/* Report an error if the source function failed. */
+   if( !lstat ) {
+      astError( AST__NOTAB, "astRead(%s): The table source function failed to read "
+                "a binary table from extension %s in an external FITS file.",
+                status, astGetClass( this ), extname );
+   }
+}
+
+static double TDBConv( double mjd, int timescale, int fromTDB,
+                       const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     TDBConv
+
+*  Purpose:
+*     Convert an MJD between the TDB time scale and another timescale.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     double TDBConv( double mjd, int timescale, int fromTDB,
+*                     const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function converts the supplied mjd value to or from the TDB
+*     timescale.
+
+*  Parameters:
+*     mjd
+*        The input MJD value.
+*     timescale
+*        The other timescale.
+*     fromTDB
+*        Indicates the direction of the required conversion. If non-zero,
+*        the supplied "mjd" value should be in the TDB timescale, and the
+*        returned value will be in the timescale specified by "timescale".
+*        If zero, the supplied "mjd" value should be in the timescale
+*        specified by "timescale", and the returned value will be in the
+*        TDB timescale.
+*     method
+*        The calling method. Used only in error messages.
+*     class
+*        The object class. Used only in error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The converted MJD value, or AST__BAD if an error occurs.
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;    /* Mapping from supplied timescale to TDB */
+   double ret;         /* The returned value */
+
+/* Initialise */
+   ret = AST__BAD;
+
+/* Check inherited status and supplied TDB value. */
+   if( !astOK || mjd == AST__BAD ) return ret;
+
+/* Return the supplied value if no conversion is needed. */
+   if( timescale == AST__TDB ) {
+      ret = mjd;
+
+/* Otherwise, do the conversion. */
+   } else {
+
+/* Lock the timeframes for use by the current thread, waiting if they are
+   currently locked by another thread. */
+      astManageLock( timeframe, AST__LOCK, 1, NULL );
+      astManageLock( tdbframe, AST__LOCK, 1, NULL );
+
+/* Set the required timescale. */
+      astSetTimeScale( timeframe, timescale );
+
+/* Get the Mapping between the two timescales, and use it to convert the
+   suipplied value. */
+      fs = astConvert( tdbframe, timeframe, "" );
+      astTran1( fs, 1, &mjd, fromTDB, &ret );
+      fs = astAnnul( fs );
+
+/* Unlock the timeframes. */
+      astManageLock( timeframe, AST__UNLOCK, 1, NULL );
+      astManageLock( tdbframe, AST__UNLOCK, 1, NULL );
+   }
+
+/* Return the result */
+   return ret;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the astTestAttrib protected
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a FitsChan's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_object;
+
+/* Card. */
+/* ----- */
+   if ( !strcmp( attrib, "card" ) ) {
+      result = astTestCard( this );
+
+/* Encoding. */
+/* --------- */
+   } else if ( !strcmp( attrib, "encoding" ) ) {
+      result = astTestEncoding( this );
+
+/* FitsAxisOrder. */
+/* -------------- */
+   } else if ( !strcmp( attrib, "fitsaxisorder" ) ) {
+      result = astTestFitsAxisOrder( this );
+
+/* FitsDigits. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "fitsdigits" ) ) {
+      result = astTestFitsDigits( this );
+
+/* DefB1950. */
+/* --------- */
+   } else if ( !strcmp( attrib, "defb1950" ) ) {
+      result = astTestDefB1950( this );
+
+/* TabOK. */
+/* ------ */
+   } else if ( !strcmp( attrib, "tabok" ) ) {
+      result = astTestTabOK( this );
+
+/* CDMatrix. */
+/* --------- */
+   } else if ( !strcmp( attrib, "cdmatrix" ) ) {
+      result = astTestCDMatrix( this );
+
+/* CarLin. */
+/* --------- */
+   } else if ( !strcmp( attrib, "carlin" ) ) {
+      result = astTestCarLin( this );
+
+/* PolyTan */
+/* ------- */
+   } else if ( !strcmp( attrib, "polytan" ) ) {
+      result = astTestPolyTan( this );
+
+/* Iwc. */
+/* ---- */
+   } else if ( !strcmp( attrib, "iwc" ) ) {
+      result = astTestIwc( this );
+
+/* Clean. */
+/* ------ */
+   } else if ( !strcmp( attrib, "clean" ) ) {
+      result = astTestClean( this );
+
+/* Warnings. */
+/* -------- */
+   } else if ( !strcmp( attrib, "warnings" ) ) {
+      result = astTestWarnings( this );
+
+/* If the name is not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then return
+   zero. */
+   } else if ( !strcmp( attrib, "ncard" ) ||
+               !strcmp( attrib, "nkey" ) ||
+               !strcmp( attrib, "cardtype" ) ||
+               !strcmp( attrib, "cardcomm" ) ||
+               !strcmp( attrib, "cardname" ) ||
+               !strcmp( attrib, "allwarnings" ) ){
+      result = 0;
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static int TestCard( AstFitsChan *this, int *status ){
+
+/*
+*+
+*  Name:
+*     astTestCard
+
+*  Purpose:
+*     Test the Card attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int astTestCard( AstFitsChan *this )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function tests the Card attribute for the supplied FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+
+*  Returned Value:
+*     If the Card attribute has its "cleared" value (i.e. if the first card
+*     in the FitsChan will be the next one to be read), then zero is returned,
+*     otherwise 1 is returned.
+*-
+*/
+
+/* Local Variables: */
+   int card;               /* The original value of Card */
+   int ret;                /* The returned flag */
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Get the current value of Card. */
+   card = astGetCard( this );
+
+/* Temporarily clear Card. */
+   astClearCard( this );
+
+/* See if the original Card is equal to the cleared card, and set the
+   returned flag appropriately. Re-instate the original value of card is
+   required.*/
+   if( astGetCard( this ) == card ) {
+      ret = 0;
+   } else {
+      astSetCard( this, card );
+      ret = 1;
+   }
+
+/* Return the flag. */
+   return ret;
+}
+
+static int TestFits( AstFitsChan *this, const char *name, int *there,
+                     int *status ){
+
+/*
+*++
+*  Name:
+c     astTestFits
+f     AST_TESTFITS
+
+*  Purpose:
+*     See if a named keyword has a defined value in a FitsChan.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+
+c     int astTestFits( AstFitsChan *this, const char *name, int *there )
+f     RESULT = AST_TESTFITS( THIS, NAME, THERE, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+*     This function serches for a named keyword in a FitsChan. If found,
+*     and if the keyword has a value associated with it, a
+c     non-zero
+f     .TRUE.
+*     value is returned. If the keyword is not found, or if it does not
+*     have an associated value, a
+c     zero
+f     .FALSE.
+*     value is returned.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string
+f        A character string
+*        containing the FITS keyword name. This may be a complete FITS
+*        header card, in which case the keyword to use is extracted from
+*        it. No more than 80 characters are read from this string.
+c     there
+f     THERE = LOGICAL (Returned)
+c        Pointer to an integer which will be returned holding a non-zero
+c        value if the keyword was found in the header, and zero otherwise.
+f        A value of .TRUE. will be returned if the keyword was found in the
+f        header, and .FALSE. otherwise.
+*        This parameter allows a distinction to be made between the case
+*        where a keyword is not present, and the case where a keyword is
+*        present but has no associated value.
+c        A NULL pointer may be supplied if this information is not
+c        required.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astTestFits()
+f     AST_TESTFITS = LOGICAL
+*        A value of zero
+f        .FALSE.
+*        is returned if the keyword was not found in the FitsChan or has
+*        no associated value. Otherwise, a value of
+c        one
+f        .TRUE.
+*        is returned.
+
+*  Notes:
+*     -  The current card is left unchanged by this function.
+*     -  The card following the current card is checked first. If this is
+*     not the required card, then the rest of the FitsChan is searched,
+*     starting with the first card added to the FitsChan. Therefore cards
+*     should be accessed in the order they are stored in the FitsChan (if
+*     possible) as this will minimise the time spent searching for cards.
+*     -  An error will be reported if the keyword name does not conform
+*     to FITS requirements.
+c     -  Zero
+f     -  .FALSE.
+*     is returned as the function value if an error has already occurred,
+*     or if this function should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   const char *class;     /* Object class */
+   const char *method;    /* Calling method */
+   char *lcom;            /* Supplied keyword comment */
+   char *lname;           /* Supplied keyword name */
+   char *lvalue;          /* Supplied keyword value */
+   int icard;             /* Current card index on entry */
+   int ret;               /* The returned value */
+
+/* Initialise */
+   if( there ) *there = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Store the calling method and object class. */
+   method = "astTestFits";
+   class = astGetClass( this );
+
+/* Initialise the returned value. */
+   ret = 0;
+
+/* Extract the keyword name from the supplied string. */
+   (void) Split( this, name, &lname, &lvalue, &lcom, method, class, status );
+
+/* Store the current card index. */
+   icard = astGetCard( this );
+
+/* Attempt to find a card in the FitsChan refering to this keyword,
+   and make it the current card. Only proceed if a card was found. */
+   if( SearchCard( this, lname, method, class, status ) ){
+
+/* Indicate the card has been found. */
+      if( there ) *there = 1;
+
+/* If the cards data type is no undefined, return 1. */
+      if( CardType( this, status ) != AST__UNDEF ) ret = 1;
+   }
+
+/* Re-instate the original current card index. */
+   astSetCard( this, icard );
+
+/* Release the memory used to hold keyword name, value and comment strings. */
+   lname = (char *) astFree( (void *) lname );
+   lvalue = (char *) astFree( (void *) lvalue );
+   lcom = (char *) astFree( (void *) lcom );
+
+/* Return the answer. */
+   return ret;
+}
+
+static void TidyOffsets( AstFrameSet *fset, int *status ) {
+/*
+*  Name:
+*     TidyOffsets
+
+*  Purpose:
+*     Remove un-needed offset coordinate Frames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void TidyOffsets( AstFrameSet *fset, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FITS header stores offset sky coordinates as two alternaive axis
+*     descriptions - one giving the offset axes and one giving the absolute
+*     axes. But AST can hold both forms in a single SkyFrame. This function
+*     removes the FITS Frames describing offset axes from the FrameSet.
+*     The remaining absolute Frame is then used to describe both absolute
+*     and offset.
+
+*  Parameters:
+*     fset
+*        A FrameSet holding the Frames read from a FITS-WCS Header.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstFrame *pfrm;
+   const char *dom;
+   const char *skyrefis;
+   int hasabs;
+   int hasoff;
+   int iax;
+   int icurr;
+   int icurr_is_offset;
+   int ifrm;
+   int nax;
+   int nfrm;
+   int pax;
+   int remove;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Note the original current Frame index. */
+   icurr = astGetCurrent( fset );
+
+/* Assume the current Frame is not an offset frame until proven
+   otherwise. */
+   icurr_is_offset = 0;
+
+/* Does the FrameSet contain any Frames holding sky offsets? Such Frames
+   should have been given a Domain of SKY_OFFSETS within function
+   WcsSkyFrame. Loop round all Frames, checking each one. Also note if
+   the FrameSet contains any (absolute) SKY frames. Also set the SkyRefIs
+   attribute for any absolute SkyFrames that were marked with domains
+   SKY_POLE or SKY_OFFSET in WcsSkyFrame. */
+   hasabs = 0;
+   hasoff = 0;
+   nfrm = astGetNframe( fset );
+   for( ifrm = 1; ifrm <= nfrm; ifrm++ ){
+      skyrefis = NULL;
+      frm = astGetFrame( fset, ifrm );
+      nax = astGetNaxes( frm );
+      for( iax = 0; iax < nax; iax++ ) {
+         astPrimaryFrame( frm, iax, &pfrm, &pax );
+         if( IsASkyFrame( pfrm ) ) {
+            dom = astGetDomain( pfrm );
+            if( dom ) {
+               if( !strcmp( dom, "SKY_OFFSETS" ) ){
+                  hasoff = 1;
+                  if( ifrm == icurr ) icurr_is_offset = 1;
+                  iax = nax;
+               } else if( !strcmp( dom, "SKY" ) ){
+                  hasabs = 1;
+                  iax = nax;
+               } else if( !strcmp( dom, "SKY_POLE" ) ){
+                  hasabs = 1;
+                  skyrefis = "POLE";
+                  iax = nax;
+               } else if( !strcmp( dom, "SKY_ORIGIN" ) ){
+                  hasabs = 1;
+                  skyrefis = "ORIGIN";
+                  iax = nax;
+               }
+            }
+         }
+         pfrm = astAnnul( pfrm );
+      }
+      frm = astAnnul( frm );
+
+      if( skyrefis ) {
+         astSetI( fset, "Current", ifrm);
+         astSetC( fset, "SkyRefIs", skyrefis );
+         astSetI( fset, "Current", icurr );
+      }
+   }
+
+/* If one or more absolute sky frames were found, then remove any offset
+   sky frames. Clear the Ident attribute (that holds the FITS-WCS alternate
+   axis description character) for any absoute Frames. */
+   if( hasabs && hasoff ) {
+
+      for( ifrm = nfrm; ifrm > 0; ifrm-- ) {
+         remove = 0;
+         frm = astGetFrame( fset, ifrm );
+         nax = astGetNaxes( frm );
+         for( iax = 0; iax < nax; iax++ ) {
+            astPrimaryFrame( frm, iax, &pfrm, &pax );
+            if( IsASkyFrame( pfrm ) ) {
+               dom = astGetDomain( pfrm );
+               if( dom ) {
+                  if( !strcmp( dom, "SKY_OFFSETS" ) ){
+                     remove = 1;
+                     iax = nax;
+
+                  } else if( !strcmp( dom, "SKY_POLE" ) ||
+                             !strcmp( dom, "SKY_ORIGIN" ) ){
+                     astClearIdent( frm );
+                     astClearDomain( pfrm );
+
+/* If we will be deleting the original current Frame (because it is an
+   offset Frame), then mark the first absolute Frame as the new current
+   Frame. */
+                     if( icurr_is_offset ) {
+                        astSetCurrent( fset, ifrm );
+                        icurr_is_offset = 0;
+                     }
+                     iax = nax;
+                  }
+               }
+            }
+            pfrm = astAnnul( pfrm );
+         }
+         frm = astAnnul( frm );
+
+         if( remove ) astRemoveFrame( fset, ifrm );
+      }
+   }
+}
+
+static AstTimeScaleType TimeSysToAst( AstFitsChan *this, const char *timesys,
+                                      const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     TimeSysToAst
+
+*  Purpose:
+*     Convert a FITS TIMESYS value to an AST TimeFrame timescale value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     AstTimeScaleType TimeSysToAst( AstFitsChan *this, const char *timesys,
+*                                    const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function returns the value used by the AST TimeFrame class to
+*     represent the timescale specified by the "timesys" parameter, which
+*     should hold the value of a FITS TIMESYS keyword. The TIMESYS
+*     convention was introduced as part of the Y2K DATE-OBS changes, and
+*     is not currently part of the published FITS-WCS conventions.
+*
+*     If the requested timescale is not supported by AST, then a warning is
+*     added to the FitsChan and a value of AST__UTC is returned (but no
+*     error is reported).
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     timesys
+*        Pointer to the string holding the TIMESYS value. A NULL pointer
+*        returns the default timescale of UTC.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The equivalent AstTimeScaleType value.
+*/
+
+/* Local Variables: */
+   AstTimeScaleType result;  /* The returned timescale */
+   char buf[ 200 ];          /* Buffer for warning message */
+
+/* Initialise */
+   result = AST__UTC;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+   if( !timesys ) {
+      result = AST__UTC;
+   } else if( !strcmp( timesys, "UTC" ) ) {
+      result = AST__UTC;
+   } else if( !strcmp( timesys, "UT" ) ) {
+      result = AST__UTC;
+      Warn( this, "badval", "The original FITS header contained a value of UT "
+            "for keyword TIMESYS which is being interpreted as UTC.", method,
+            class, status );
+   } else if( !strcmp( timesys, "TAI" ) ) {
+      result = AST__TAI;
+   } else if( !strcmp( timesys, "IAT" ) ) {
+      result = AST__TAI;
+   } else if( !strcmp( timesys, "ET" ) ) {
+      result = AST__TT;
+      Warn( this, "badval", "The original FITS header contained a value of ET "
+            "for keyword TIMESYS. TT will be used instead.", method, class, status );
+   } else if( !strcmp( timesys, "TT" ) ) {
+      result = AST__TT;
+   } else if( !strcmp( timesys, "TDT" ) ) {
+      result = AST__TT;
+   } else if( !strcmp( timesys, "TDB" ) ) {
+      result = AST__TDB;
+   } else if( !strcmp( timesys, "TCG" ) ) {
+      result = AST__TCG;
+   } else if( !strcmp( timesys, "TCB" ) ) {
+      result = AST__TCB;
+   } else {
+      result = AST__UTC;
+      sprintf( buf, "The original FITS header contained a value of %s for "
+               "keyword TIMESYS. AST does not support this timescale so "
+               "UTC will be used instead.", timesys );
+      Warn( this, "badval", buf, method, class, status );
+   }
+
+/* Return the result */
+   return result;
+}
+
+static char *UnPreQuote( const char *string, int *status ) {
+/*
+*  Name:
+*     UnPreQuote
+
+*  Purpose:
+*     Reverse the pre-quoting of FITS character data.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     char *UnPreQuote( const char *string, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function reverses the effect of the PreQuote function on a
+*     string (apart from any loss of data due to truncation). It
+*     should be used to recover the original character data from the
+*     pre-quoted version of a string retrieved from a FITS character
+*     value associated with a keyword.
+
+*  Parameters:
+*     string
+*        Pointer to a constant null-terminated string containing the
+*        pre-quoted character data.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a dynamically allocated null-terminated string
+*     containing the un-quoted character data. The memory holding this
+*     string should be freed by the caller (using astFree) when no
+*     longer required.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked wth the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   char *result;                 /* Pointer value to return */
+   int i1;                       /* Offset of first useful character */
+   int i2;                       /* Offest of last useful character */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise to use the first and last characters in the input
+   string. */
+   i1 = 0;
+   i2 = strlen( string ) - 1;
+
+/* If the string contains at least 2 characters, check if the first
+   and last characters are double quotes ("). If so, adjust the
+   offsets to exclude them. */
+   if ( ( i2 > i1 ) &&
+        ( string[ i1 ] == '"' ) && ( string[ i2 ] == '"' ) ) {
+      i1++;
+      i2--;
+   }
+
+/* Make a dynamically allocated copy of the useful part of the
+   string. */
+   result = astString( string + i1, i2 - i1 + 1 );
+
+/* Return the answer. */
+   return result;
+}
+
+static int Use( AstFitsChan *this, int set, int helpful, int *status ) {
+
+/*
+*  Name:
+*     Use
+
+*  Purpose:
+*     Decide whether to write a value to a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int Use( AstFitsChan *this, int set, int helpful, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     This function decides whether a value supplied by a class "Dump"
+*     function, via a call to one of the astWrite... protected
+*     methods, should actually be written to a FitsChan.
+*
+*     This decision is based on the settings of the "set" and
+*     "helpful" flags supplied to the astWrite... method, plus the
+*     attribute settings of the FitsChan.
+
+*  Parameters:
+*     this
+*        A pointer to the FitsChan.
+*     set
+*        The "set" flag supplied.
+*     helpful
+*        The "helpful" value supplied.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the value should be written out, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   int full;                     /* Full attribute value */
+   int result;                   /* Result value to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* If "set" is non-zero, then so is the result ("set" values must
+   always be written out). */
+   result = ( set != 0 );
+
+/* Otherwise, obtain the value of the FitsChan's Full attribute. */
+   if ( !set ) {
+      full = astGetFull( this );
+
+/* If Full is positive, display all values, if zero, display only
+   "helpful" values, if negative, display no (un-"set") values. */
+      if ( astOK ) result = ( ( helpful && ( full > -1 ) ) || ( full > 0 ) );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int Ustrcmp( const char *a, const char *b, int *status ){
+/*
+*  Name:
+*     Ustrcmp
+
+*  Purpose:
+*     A case blind version of strcmp.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int Ustrcmp( const char *a, const char *b, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns 0 if there are no differences between the two strings, and 1
+*     otherwise. Comparisons are case blind.
+
+*  Parameters:
+*     a
+*        Pointer to first string.
+*     b
+*        Pointer to second string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the strings match, otherwise one.
+
+*  Notes:
+*     -  This function does not consider the sign of the difference between
+*     the two strings, whereas "strcmp" does.
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   const char *aa;         /* Pointer to next "a" character */
+   const char *bb;         /* Pointer to next "b" character */
+   int ret;                /* Returned value */
+
+/* Initialise the returned value to indicate that the strings match. */
+   ret = 0;
+
+/* Initialise pointers to the start of each string. */
+   aa = a;
+   bb = b;
+
+/* Loop round each character. */
+   while( 1 ){
+
+/* We leave the loop if either of the strings has been exhausted. */
+      if( !(*aa ) || !(*bb) ){
+
+/* If one of the strings has not been exhausted, indicate that the
+   strings are different. */
+         if( *aa || *bb ) ret = 1;
+
+/* Break out of the loop. */
+         break;
+
+/* If neither string has been exhausted, convert the next characters to
+   upper case and compare them, incrementing the pointers to the next
+   characters at the same time. If they are different, break out of the
+   loop. */
+      } else {
+         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
+            ret = 1;
+            break;
+         }
+      }
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static int Ustrncmp( const char *a, const char *b, size_t n, int *status ){
+/*
+*  Name:
+*     Ustrncmp
+
+*  Purpose:
+*     A case blind version of strncmp.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int Ustrncmp( const char *a, const char *b, size_t n, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     Returns 0 if there are no differences between the first "n"
+*     characters of the two strings, and 1 otherwise. Comparisons are
+*     case blind.
+
+*  Parameters:
+*     a
+*        Pointer to first string.
+*     b
+*        Pointer to second string.
+*     n
+*        The maximum number of characters to compare.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the strings match, otherwise one.
+
+*  Notes:
+*     -  This function does not consider the sign of the difference between
+*     the two strings, whereas "strncmp" does.
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   const char *aa;         /* Pointer to next "a" character */
+   const char *bb;         /* Pointer to next "b" character */
+   int i;                  /* Character index */
+   int ret;                /* Returned value */
+
+/* Initialise the returned value to indicate that the strings match. */
+   ret = 0;
+
+/* Initialise pointers to the start of each string. */
+   aa = a;
+   bb = b;
+
+/* Compare up to "n" characters. */
+   for( i = 0; i < (int) n; i++ ){
+
+/* We leave the loop if either of the strings has been exhausted. */
+      if( !(*aa ) || !(*bb) ){
+
+/* If one of the strings has not been exhausted, indicate that the
+   strings are different. */
+         if( *aa || *bb ) ret = 1;
+
+/* Break out of the loop. */
+         break;
+
+/* If neither string has been exhausted, convert the next characters to
+   upper case and compare them, incrementing the pointers to the next
+   characters at the same time. If they are different, break out of the
+   loop. */
+      } else {
+         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
+            ret = 1;
+            break;
+         }
+      }
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static void Warn( AstFitsChan *this, const char *condition, const char *text,
+                  const char*method, const char *class, int *status ){
+/*
+*  Name:
+*     Warn
+
+*  Purpose:
+*     Store warning cards in a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int Warn( AstFitsChan *this, const char *condition, const char *text,
+*               const char*method, const char *class, int *status );
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     If the Warnings attribute indicates that occurences of the specified
+*     condition should be reported, the supplied text is split into lines
+*     and stored in the FitsChan as a series of ASTWARN cards, in front
+*     of the current card. If the specified condition is not being reported,
+*     this function returns without action.
+
+*  Parameters:
+*     this
+*        The FitsChan. If NULL, this function returns without action.
+*     condition
+*        Pointer to a string holding a lower case condition name.
+*     text
+*        Pointer to a string holding the text of the warning.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   char buff[ AST__FITSCHAN_FITSCARDLEN + 1 ]; /* Buffer for new card text */
+   const char *a;        /* Pointer to 1st character in next card */
+   const char *b;        /* Pointer to terminating null character */
+   const char *c;        /* Pointer to last character in next card */
+   int exists;           /* Has the supplied warning already been issued? */
+   int icard;            /* Index of original card */
+   int nc;               /* No. of characters in next card */
+
+/* Check the inherited status, warning text, FitsChan and Clean attribute. */
+   if( !astOK || !text || !text[0] || !this || astGetClean( this ) ) return;
+
+/* Ignore the warning if the supplied condition is not contained within
+   the list of conditions to be reported in this way (given by the
+   Warnings attribute). */
+   if( FullForm( astGetWarnings( this ), condition, 0, status ) >= 0 ){
+
+/* If found, store the warning in the parent Channel structure. */
+      astAddWarning( this, 1, "%s", method, status, text );
+
+/* For historical reasons, warnings are also stored in the FitsChan as a
+   set of FITS cards... First save the current card index, and rewind the
+   FitsChan. */
+      icard = astGetCard( this );
+      astClearCard( this );
+
+/* Break the supplied text into lines and check the FitsChan to see if
+   a block of adjacent ASTWARN cards with these lines already exist
+   within the FitsChan. Assume they do until proven otherwise. */
+      exists = 1;
+      a = text;
+      b = a + strlen( text );
+      while( a < b ){
+
+/* Each card contains about 60 characters of the text. Get a pointer to
+   the nominal last character in the next card. */
+         c = a + 60;
+
+/* If this puts the last character beyond the end of the text, use the
+   last character before the null as the last character in the card. */
+         if( c >= b ) {
+            c = b - 1;
+
+/* Otherwise, if the last character is not a space, move the last
+   character backwards to the first space. This avoids breaking words
+   across cards. */
+         } else {
+            while( !isspace( *c ) && c > a ) c--;
+         }
+
+/* Copy the text into a null terminated buffer. */
+         nc = c - a + 1;
+         strncpy( buff, a, nc );
+         buff[ nc ] = 0;
+
+/* If this is the first line, search the entire FitsChan for an ASTWARN card
+   with this text. If not, indiate that the supplied text needs to be
+   stored in the FitsChan, and break out of the loop. */
+         if( a == text ) {
+            exists = 0;
+            while( !exists &&
+                   FindKeyCard( this, "ASTWARN", method, class, status ) ) {
+               if( !strcmp( (const char *) CardData( this, NULL, status ), buff ) ) {
+                  exists = 1;
+               }
+               MoveCard( this, 1, method, class, status );
+            }
+            if( !exists ) break;
+
+/* If this is not the first line, see if the next card in the FitsChan is
+   an ASTWARN card with this text. If not, indiate that the supplied text
+   needs to be stored in the FitsChan, and break out of the loop. */
+         } else {
+            if( !strcmp( CardName( this, status ), "ASTWARN" ) &&
+                !strcmp( (const char *) CardData( this, NULL, status ), buff ) ) {
+               MoveCard( this, 1, method, class, status );
+            } else {
+               exists = 0;
+               break;
+            }
+         }
+
+/* Set the start of the next bit of the text. */
+         a = c + 1;
+      }
+
+/* Reinstate the original current card index. */
+      astSetCard( this, icard );
+
+/* We only add new cards to the FitsChan if they do not already exist. */
+      if( !exists ) {
+
+/* Break the text into lines using the same algorithm as above, and store
+   each line as a new ASTWARN card. Start with a blank ASTWARN card. */
+         astSetFitsS( this, "ASTWARN", " ", NULL, 0 );
+
+/* Loop until the entire text has been written out. */
+         a = text;
+         b = a + strlen( text );
+         while( a < b ){
+
+/* Each card contains about 60 characters of the text. Get a pointer to
+   the nominal last character in the next card. */
+            c = a + 60;
+
+/* If this puts the last character beyond the end of the text, use the
+   last character before the null as the last character in the card. */
+            if( c >= b ) {
+               c = b - 1;
+
+/* Otherwise, if the last character is not a space, move the last
+   character backwards to the first space. This avoids breaking words
+   across cards. */
+            } else {
+               while( !isspace( *c ) && c > a ) c--;
+            }
+
+/* Copy the text into a null terminated buffer. */
+            nc = c - a + 1;
+            strncpy( buff, a, nc );
+            buff[ nc ] = 0;
+
+/* Store the buffer as the next card. */
+            astSetFitsS( this, "ASTWARN", buff, NULL, 0 );
+
+/* Set the start of the next bit of the text. */
+            a = c + 1;
+         }
+
+/* Include a final blank card. */
+         astSetFitsS( this, "ASTWARN", " ", NULL, 0 );
+      }
+   }
+}
+
+static int WATCoeffs( const char *watstr, int iaxis, double **cvals,
+                      int **mvals, int *ok, int *status ){
+/*
+*  Name:
+*     WATCoeffs
+
+*  Purpose:
+*     Get the polynomial coefficients from the lngcor or latcor component
+*     of an IRAF WAT string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int WATCoeffs( const char *watstr, int iaxis, double **cvals,
+*                    int **mvals, int *ok, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function extracts the polynomial coefficients from a supplied
+*     string containing the concatenated values of a set of IRAF "WAT"
+*     keywords, such as used for the IRAF-specific TNX and ZPX projections.
+*     The coefficients are returned in the form of a set of PVi_m values
+*     for a TPN projection.
+
+*  Parameters:
+*     watstr
+*        The concatentated WAT keyword values.
+*     iaxis
+*        Zero based index of the axis to which the WAT keywords refer (0
+*        or 1).
+*     cvals
+*        Location at which to return a pointer to a dynamically allocated
+*        list of coefficient values, or NULL if no lngcor/latcor values
+*        were found in the WAT string. Free using astFree.
+*     mvals
+*        Location at which to return a pointer to a dynamically allocated
+*        list of coefficient indices, or NULL if no lngcor/latcor values
+*        were found in the WAT string. Free using astFree.
+*     ok
+*        Pointer to an in which is returned set to zero if the polynomial
+*        in the supplied WAT string cannot be represented using TPN form.
+*        Non-zero otherwise.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The size of the returned cvals and mvals arrays.
+
+*/
+
+/* Local Variables: */
+   char **w1;
+   char **w2;
+   double *coeff;
+   double *pc;
+   int result;
+   double dval;
+   double etamax;
+   double etamin;
+   double ximax;
+   double ximin;
+   int cheb;
+   int etaorder;
+   int iword;
+   int m;
+   int mn;
+   int nword;
+   int order;
+   int porder;
+   int xiorder;
+   int ires;
+
+/* The number of lngcor/latcor values needed for each order. */
+   static const int nab[] = {1,3,6,10,15,21,28,36};
+
+/* Initialise the pointer to the returned Mapping. */
+   result = 0;
+   *mvals = NULL;
+   *cvals = NULL;
+   *ok = 1;
+
+/* Other initialisation to avoid compiler warnings. */
+   etamin = 0.0;
+   etamax = 0.0;
+   ximax = 0.0;
+   ximin = 0.0;
+   order = 0;
+
+/* Check the global status. */
+   if ( !astOK || !watstr ) return result;
+
+/* Look for cor = "..." and extract the "..." string. */
+   w1 = astChrSplitRE( watstr, "cor *= *\"(.*)\"", &nword, NULL );
+   if( w1 ) {
+
+/* Split the "..." string into words. */
+      w2 = astChrSplit( w1[ 0 ], &nword );
+      if( w2 ) {
+
+/* Initialise flags. */
+         cheb = 0;
+         xiorder = 0;
+         etaorder = 0;
+         coeff = NULL;
+         porder = -1;
+
+/* Loop round each word. Break early if we find that the projection
+   cannot be represented as a TPN projection. */
+         for( iword = 0; iword < nword && *ok; iword++ ) {
+
+/* Convert the word to double. */
+            dval = astChr2Double( w2[ iword ] );
+            if( dval == AST__BAD ) {
+               astError( AST__BDFTS, "astRead(FitsChan): Failed to read a "
+                         "numerical value from sub-string \"%s\" found in "
+                         "an IRAF \"WAT...\" keyword.", status,  w2[ iword ] );
+               break;
+            }
+
+/* The first value gives the correction surface type. We can only handle type
+   1 (chebyshev) or 3 (simple polynomial). */
+            if( iword == 0 ){
+               if( dval == 1.0 ) {
+                  cheb = 1;
+               } else if( dval == 2.0 ) {
+                  *ok = 0;
+               }
+
+/* The second and third numbers gives the orders of the polynomial in X
+   and Y. We can only handle cases in which the orders are the same on
+   both axes, and greater than 0 and less than 8. Store a pointer to the
+   first TAN projection parameter index to use. */
+            } else if( iword == 1 ){
+               order = dval;
+               porder = order - 1;
+
+            } else if( iword == 2 ){
+               if( dval - 1 != porder || dval < 0 || dval > 7 ) *ok = 0;
+
+/* The fourth number defines the type of cross-terms. We can only handle
+   type 2 (half-cross terms). */
+            } else if( iword == 3 ){
+               if( dval != 2.0 ) *ok = 0;
+
+/* We now know the maximum number of co-efficients that may be needed.
+   Allocate memory to hold them, and fill it with zeros. They are
+   stored in this array as if full cross-terms have been supplied (the
+   unspecified coefficients retain their initialised value of zero).  */
+               coeff = astCalloc( order*order, sizeof( double ) );
+               if( !astOK ) break;
+
+/* The next 4 numbers describe the region of validity of the fits in IRAF's
+   xi and eta space, e.g. ximin, ximax, etamin, etamax. We only uses
+   these if we have a chebyshev polynomial. */
+            } else if( iword == 4 ) {
+               ximin = dval;
+
+            } else if( iword == 5 ) {
+               ximax = dval;
+
+            } else if( iword == 6 ) {
+               etamin = dval;
+
+            } else if( iword == 7 ) {
+               etamax = dval;
+
+/* The remaining terms are the coefficients of the polynomial terms. */
+            } else if( iword > 7 ){
+
+/* Store the coefficient in the array. They are stored so that power of
+   xi increases fastest. */
+               coeff[ xiorder + order*etaorder ] = dval;
+
+/* Increment the powers of the next coefficient. We know we only have half
+   cross-terms, so the maximum power of xi decreases from order to zero
+   as we move through the list of coefficients. */
+               if( ++xiorder == order - etaorder ) {
+                  xiorder = 0;
+                  etaorder++;
+               }
+            }
+         }
+
+/* Check that all the required co-efficients were found */
+         if( porder == -1 || nword != 8 + nab[ porder ] ) *ok = 0;
+
+/* If we can handle the projection, proceed. */
+         if( *ok && astOK ) {
+
+/* If the coefficients were supplied in chebyshev form, convert to simple
+   form. */
+            if( cheb ) {
+               double *tcoeff = coeff;
+               coeff = Cheb2Poly( tcoeff, order, order, ximin,
+                                  ximax, etamin, etamax, status );
+               tcoeff = astFree( tcoeff );
+            }
+
+/* The polynomials provide a "correction* to be added to the supplied X and
+   Y values. Therefore increase the linear co-efficients by 1 on the axis
+   that is being calculated. */
+            coeff[ iaxis ? order : 1 ] += 1.0;
+
+/* Loop round all coefficients, keeping track of the power of xi and eta
+   for the current coefficient. */
+            pc = coeff;
+            for( etaorder = 0; etaorder < order; etaorder++ ) {
+               for( xiorder = 0; xiorder < order; xiorder++,pc++ ) {
+
+/* Skip coefficients that have their default values (zero, except for the
+   linear coefficients which default to 1.0). */
+                  mn = xiorder + etaorder;
+                  if( *pc != ( mn == 1 ? 1.0 : 0.0 ) ) {
+
+/* Find the "m" index of the PVi_m FITS keyword for the current
+   coefficient. */
+                     m = mn*( 1 + mn )/2 + mn/2;
+                     m += iaxis ? xiorder : etaorder;
+
+/* Append the PV and m values to the ends of the returned arrays. */
+                     ires = result++;
+                     *cvals = astGrow( *cvals, sizeof( double ), result );
+                     *mvals = astGrow( *mvals, sizeof( int ), result );
+                     if( astOK ) {
+                        (*cvals)[ ires ] = *pc;
+                        (*mvals)[ ires ] = m;
+                     }
+                  }
+               }
+            }
+
+/* Free coefficients arrays */
+            coeff = astFree( coeff );
+         }
+
+/* Free resources */
+         w2 = astFree( w2 );
+      }
+      w1 = astFree( w1 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstMatrixMap *WcsCDeltMatrix( FitsStore *store, char s, int naxes,
+                                     const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     WcsCDeltMatrix
+
+*  Purpose:
+*     Create a MatrixMap representing the CDELT scaling.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     AstMatrixMap *WcsCDeltMatrix( FitsStore *store, char s, int naxes,
+*                                   const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A diagonal MatrixMap representing the FITS "CDELT" keywords is
+*     returned.
+
+*  Parameters:
+*     store
+*        A structure containing values for FITS keywords relating to
+*        the World Coordinate System.
+*     s
+*        A character s identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     naxes
+*        The number of intermediate world coordinate axes (WCSAXES).
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the created MatrixMap or a NULL pointer if an
+*     error occurred.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *new;       /* The created MatrixMap */
+   double *el;              /* Pointer to next matrix element */
+   double *mat;             /* Pointer to matrix array */
+   int i;                   /* Pixel axis index */
+
+/* Initialise/ */
+   new = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Allocate memory for the diagonal matrix elements. */
+   mat = (double *) astMalloc( sizeof(double)*naxes );
+   if( astOK ){
+
+/* Fill the matrix diagonal with values from the FitsStore. */
+      el = mat;
+      for( i = 0; i < naxes; i++ ){
+
+/* Get the CDELTi value for this axis. Missing terms can be defaulted so
+   do not report an error if the required value is not present in the
+   FitsStore. */
+         *el = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
+
+/* Missing terms default to to 1.0. */
+         if( *el == AST__BAD ) *el = 1.0;
+
+/* Move on to the next matrix element. */
+         el++;
+      }
+
+/* Create the diagional matrix. */
+      new = astMatrixMap( naxes, naxes, 1, mat, "", status );
+
+/* Report an error if the inverse transformation is undefined. */
+      if( !astGetTranInverse( new ) && astOK ) {
+        astError( AST__BDFTS, "%s(%s): Unusable CDELT values found "
+                  "in the FITS-WCS header - one or more values are zero.", status, method, class );
+      }
+
+/* Release the memory used to hold the matrix. */
+      mat = (double *) astFree( (void *) mat );
+   }
+
+/* If an error has occurred, attempt to annul the returned MatrixMap. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the MatrixMap. */
+   return new;
+}
+
+static AstMapping *WcsCelestial( AstFitsChan *this, FitsStore *store, char s,
+                                 AstFrame **frm, AstFrame *iwcfrm, double *reflon, double *reflat,
+                                 AstSkyFrame **reffrm, AstMapping **tabmap,
+                                 int *tabaxis, const char *method,
+                                 const char *class, int *status ){
+/*
+*  Name:
+*     WcsCelestial
+
+*  Purpose:
+*     Create a Mapping from intermediate world coords to celestial coords
+*     as described in a FITS header.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     AstMapping *WcsCelestial( AstFitsChan *this, FitsStore *store, char s,
+*                               AstFrame **frm, AstFrame *iwcfrm, double *reflon, double *reflat,
+*                               AstSkyFrame **reffrm, , AstMapping **tabmap,
+*                               int *tabaxis, const char *method,
+*                               const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function interprets the contents of the supplied FitsStore
+*     structure, looking for world coordinate axes which describe positions
+*     on the sky. If a pair of such longitude/latitude axes is found, a
+*     Mapping is returned which transforms the corresponding intermediate
+*     world coordinates to celestial world coordinates (this mapping leaves
+*     any other axes unchanged). It also, modifies the supplied Frame to
+*     describe the axes (again, other axes are left unchanged). If no
+*     pair of celestial axes is found, a UnitMap is returned, and the
+*     supplied Frame is left unchanged.
+
+*  Parameters:
+*     this
+*        The FitsChan.
+*     store
+*        A structure containing information about the requested axis
+*        descriptions derived from a FITS header.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     frm
+*        The address of a location at which to store a pointer to the
+*        Frame describing the world coordinate axes.
+*     iwcfrm
+*        A pointer to the Frame describing the intermediate world coordinate
+*        axes. The properties of this Frame may be changed on exit.
+*     reflon
+*        Address of a location at which to return the celestial longitude
+*        at the reference point. It is returned as AST__BAD if no
+*        celestial coordinate frame is found.
+*     reflat
+*        Address of a location at which to return the celestial latitude
+*        at the reference point. It is returned as AST__BAD if no
+*        celestial coordinate frame is found.
+*     reffrm
+*        Address of a location at which to return a pointer to a SkyFrame
+*        which define the reference values returned in reflon and reflat.
+*        It is returned as NULL if no celestial coordinate frame is found.
+*     tabmap
+*        Address of a pointer to a Mapping describing any -TAB
+*        transformations to be applied to the results of the Mapping returned
+*        by this function. If any celestial axes are found, the supplied
+*        Mapping is modified so that the celestial axes produce values in
+*        radians rather than degrees. NULL if no axes are described by -TAB.
+*     tabaxis
+*        Pointer to an array of flags, one for each WCS axis, indicating
+*        if the corresponding WCS axis is described by the -TAB algorithm.
+*        NULL if no axes are described by -TAB.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the Mapping.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS        /* Declare the thread specific global data */
+   AstFrame *ofrm;           /* Pointer to a Frame */
+   AstMapping *map1;         /* Pointer to a Mapping */
+   AstMapping *map2;         /* Pointer to a Mapping */
+   AstMapping *map3;         /* Pointer to a Mapping */
+   AstMapping *map4;         /* Pointer to a Mapping */
+   AstMapping *ret;          /* Pointer to the returned Mapping */
+   AstMapping *newmap;       /* Modified PIXEL->IWC Mapping */
+   AstMapping *shiftmap;     /* ShiftMap from IWC to PPC */
+   AstSkyFrame *sfrm;        /* Pointer to a SkyFrame */
+   char *ctype;              /* Pointer to CTYPE string */
+   char *keyname;            /* Pointer to keyword name string */
+   char buf[300];            /* Text buffer */
+   char latctype[MXCTYPELEN];/* Latitude CTYPE keyword value */
+   char latkey[10];          /* Latitude CTYPE keyword name */
+   char lattype[4];          /* Buffer for celestial system */
+   char lonctype[MXCTYPELEN];/* Longitude CTYPE keyword value */
+   char lonkey[10];          /* Longitude CTYPE keyword name */
+   char lontype[4];          /* Buffer for celestial system */
+   double *shifts;           /* Array holding axis shifts */
+   double *ina;              /* Pointer to memory holding input position A */
+   double *inb;              /* Pointer to memory holding input position B */
+   double *mat;              /* Pointer to data for deg->rad scaling matrix */
+   double *outa;             /* Pointer to memory holding output position A */
+   double *outb;             /* Pointer to memory holding output position B */
+   double latval;            /* CRVAL for latitude axis */
+   double lonval;            /* CRVAL for longitude axis */
+   double pv;                /* Projection parameter value */
+   double x0;                /* IWC X at the projection fiducial point */
+   double y0;                /* IWC Y at the projection fiducial point */
+   int *axes;                /* Point to a list of axis indices */
+   int axlat;                /* Index of latitude physical axis */
+   int axlon;                /* Index of longitude physical axis */
+   int carlin;               /* Assume native and WCS axes are the same? */
+   int ctlen;                /* Length of CTYPE string */
+   int gotax;                /* Celestial axis found? */
+   int i;                    /* Loop count */
+   int j;                    /* Axis index */
+   int latprj;               /* Latitude projection type identifier */
+   int lonprj;               /* Longitude projection type identifier */
+   int m;                    /* Parameter index */
+   int mxpar_lat;            /* Max. projection parameter index on lat axis */
+   int mxpar_lon;            /* Max. projection parameter index on lon axis */
+   int naxes;                /* Number of axes */
+   int nc;                   /* String length */
+   int np;                   /* Max parameter index */
+   int prj;                  /* Projection type identifier */
+
+/* Initialise the returned values. */
+   ret = NULL;
+   *reflon = AST__BAD;
+   *reflat = AST__BAD;
+   *reffrm = NULL;
+
+/* Other initialisation to avoid compiler warnings. */
+   map1 = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return ret;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Get the number of physical axes. */
+   naxes = astGetNaxes( *frm );
+
+/* See if CAR projections should be interpreted in the old fashioned way
+   (i.e native coords are always the same as WCS coords, so no need for
+   any rotation). */
+   carlin = astGetCarLin( this );
+
+/* The first major section sees if the physical axes include a pair of
+   longitude/latitude celestial axes.
+   ================================================================= */
+
+/* We have not yet found any celestial axes. */
+   axlon = -1;
+   axlat = -1;
+   latprj = AST__WCSBAD;
+   lonprj = AST__WCSBAD;
+   prj = AST__WCSBAD;
+
+/* First, we examine the CTYPE values in the FitsStore to determine
+   which axes are the longitude and latitude axes, and what the celestial
+   co-ordinate system and projection are. Loop round the physical axes,
+   getting each CTYPE value. */
+   for( i = 0; i < naxes && astOK; i++ ){
+      keyname =  FormatKey( "CTYPE", i + 1, -1, s, status );
+      ctype = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+
+/* Issue a warning if no CTYPE value was found. */
+      if( !ctype ) {
+         sprintf( buf, "Axis type keywords (CTYPE, etc) were not found "
+                  "for one or more axes in the original FITS header. These "
+                  "axes will be assumed to be linear." );
+         Warn( this, "noctype", buf, method, class, status );
+      } else {
+
+/* See if this is a longitude axis (e.g. if the first 4 characters of CTYPE
+   are "RA--" or "xLON" or "yzLN" ). If so, store the value of "x" or "yz"
+   (or "EQU" for equatorial coordinates) in variable "type" to indicate which
+   coordinate system is being used. */
+         nc = strlen( ctype );
+         gotax = 0;
+         if( !strcmp( ctype, "RA" ) || !strncmp( ctype, "RA--", 4 ) ){
+            strcpy( wcscelestial_type, "EQU" );
+            gotax = 1;
+         } else if( !strcmp( ctype, "AZ" ) || !strncmp( ctype, "AZ--", 4 ) ){
+            strcpy( wcscelestial_type, "AZL" );
+            gotax = 1;
+         } else if( nc > 1 && ( !strcmp( ctype + 1, "LON" ) ||
+                                !strncmp( ctype + 1, "LON-", 4 ) ) ){
+            wcscelestial_type[ 0 ] = ctype[ 0 ];
+            wcscelestial_type[ 1 ] = 0;
+            gotax = 1;
+         } else if( nc > 2 && ( !strcmp( ctype + 2, "LN" ) ||
+                                !strncmp( ctype + 2, "LN-", 3 ) ) ){
+            wcscelestial_type[ 0 ] = ctype[ 0 ];
+            wcscelestial_type[ 1 ] = ctype[ 1 ];
+            wcscelestial_type[ 2 ] = 0;
+            gotax = 1;
+         }
+
+/* If this is a longitude axis... */
+         if( gotax ){
+
+/* Check that this is the first longitude axis to be found. */
+            if( axlon == -1 ){
+
+/* Find the projection type as specified by the last 4 characters
+   in the CTYPE keyword value. AST__WCSBAD is stored in "prj" if the
+   last 4 characters do not specify a known WCS projection, but no error
+   is reported. Assume simple linear axes if no projection code is
+   supplied. Note, AST__WCSBAD is used to indicate a TAB header. */
+               ctlen = strlen( ctype );
+               if( ctlen > 4 ) {
+                  prj = astWcsPrjType( ctype + ctlen - 4 );
+               } else if( tabmap && *tabmap ) {
+                  prj = AST__WCSBAD;
+               } else {
+                  prj = AST__CAR;
+                  carlin = 1;
+               }
+
+/* Report an error if the projection is unknown. */
+               if( prj == AST__WCSBAD && ctlen > 4 ){
+                  astError( AST__BDFTS, "%s(%s): FITS keyword '%s' refers to "
+                        "an unknown projection type '%s'.", status, method, class,
+                         keyname, ctype + ctlen - 4 );
+                  break;
+               }
+
+/* Store the index of the longitude axis, type of longitude, etc. */
+               axlon = i;
+               strcpy( lontype, wcscelestial_type );
+               strcpy( lonkey, keyname );
+               strcpy( lonctype, ctype );
+               lonprj = prj;
+
+/* If another longitude axis has already been found, report an error. */
+            } else {
+               astError( AST__BDFTS, "%s(%s): FITS keywords '%s' (='%s') "
+                  "and '%s' (='%s') both describe celestial longitude axes.", status,
+                  method, class, keyname, ctype, lonkey, lonctype );
+               break;
+            }
+         }
+
+/* Do the same for the latitude axis, checking for "DEC-" and "xLAT" and
+  "yzLT". */
+         gotax = 0;
+         if( !strcmp( ctype, "DEC" ) || !strncmp( ctype, "DEC-", 4 ) ){
+            strcpy( wcscelestial_type, "EQU" );
+            gotax = 1;
+         } else if( !strcmp( ctype, "EL" ) || !strncmp( ctype, "EL--", 4 ) ){
+            strcpy( wcscelestial_type, "AZL" );
+            gotax = 1;
+         } else if( !strcmp( ctype + 1, "LAT" ) || !strncmp( ctype + 1, "LAT-", 4 ) ){
+            wcscelestial_type[ 0 ] = ctype[ 0 ];
+            wcscelestial_type[ 1 ] = 0;
+            gotax = 1;
+         } else if( !strcmp( ctype + 2, "LT" ) || !strncmp( ctype + 2, "LT-", 3 ) ){
+            wcscelestial_type[ 0 ] = ctype[ 0 ];
+            wcscelestial_type[ 1 ] = ctype[ 1 ];
+            wcscelestial_type[ 2 ] = 0;
+            gotax = 1;
+         }
+         if( gotax ){
+            if( axlat == -1 ){
+               ctlen = strlen( ctype );
+               if( ctlen > 4 ) {
+                  prj = astWcsPrjType( ctype + ctlen - 4 );
+               } else if( tabmap && *tabmap ) {
+                  prj = AST__WCSBAD;
+               } else {
+                  prj = AST__CAR;
+                  carlin = 1;
+               }
+
+               if( prj == AST__WCSBAD && ctlen > 4 ){
+                  astError( AST__BDFTS, "%s(%s): FITS keyword '%s' refers to "
+                        "an unknown projection type '%s'.", status, method, class,
+                         keyname, ctype + ctlen - 4 );
+                  break;
+               }
+               axlat = i;
+               strcpy( lattype, wcscelestial_type );
+               strcpy( latkey, keyname );
+               strcpy( latctype, ctype );
+               latprj = prj;
+            } else {
+               astError( AST__BDFTS, "%s(%s): FITS keywords '%s' (='%s') "
+                  "and '%s' (='%s') both describe celestial latitude axes.", status,
+                  method, class, keyname, ctype, latkey, latctype );
+               break;
+            }
+         }
+      }
+   }
+
+/* Check the above went OK */
+   if( astOK ){
+
+/* If both longitude and latitude axes were found... */
+      if( axlat != -1 && axlon != -1 ){
+
+/* Report an error if they refer to different celestial coordinate systems. */
+         if( strcmp( lattype, lontype ) ){
+            astError( AST__BDFTS, "%s(%s): FITS keywords '%s' and '%s' "
+                      "indicate different celestial coordinate systems "
+                      "('%s' and '%s').", status, method, class, latkey, lonkey,
+                      latctype, lonctype );
+
+/* Otherwise report an error if longitude and latitude axes use different
+   projections. */
+         } else if( lonprj != latprj ){
+            astError( AST__BDFTS, "%s(%s): FITS keywords '%s' and '%s' "
+                      "indicate different projections ('%s' and '%s').", status,
+                      method, class, latkey, lonkey, latctype, lonctype );
+         }
+
+/* If only one axis has been provided without the other (e.g. longitude but no
+   latitude), report an error. */
+      } else if( axlat != -1 && prj != AST__WCSBAD ){
+         astError( AST__BDFTS, "%s(%s): A latitude axis ('%s') was found "
+                   "without a corresponding longitude axis.", status, method, class,
+                   latctype );
+      } else if( axlon != -1 && prj != AST__WCSBAD ){
+         astError( AST__BDFTS, "%s(%s): A longitude axis ('%s') was found "
+                   "without a corresponding latitude axis.", status, method, class,
+                   lonctype );
+      }
+   }
+
+/* If a pair of matching celestial axes was not found, return a UnitMap
+   and leave the Frame unchanged.
+   ===================================================================== */
+   if( axlat == -1 || axlon == -1 ) {
+      ret = (AstMapping *) astUnitMap( naxes, "", status );
+
+/* The rest of this function deals with creating a Mapping from
+   intermediate world coords to celestial coords, and modifying the
+   Frame appropriately.
+   ===================================================================== */
+   } else if( astOK ) {
+
+/* Create a MatrixMap which scales the intermediate world coordinate axes
+   corresponding to the longitude and latitude axes from degrees to radians.
+   Only do this if a projection was supplied. */
+      if( latprj != AST__WCSBAD ) {
+         mat = (double *) astMalloc( sizeof(double)*naxes );
+         if( mat ){
+            for( i = 0; i < naxes; i++ ){
+               if( i == axlat || i == axlon ){
+                  mat[ i ] = AST__DD2R;
+               } else {
+                  mat[ i ] = 1.0;
+               }
+            }
+            map1 = (AstMapping *) astMatrixMap( naxes, naxes, 1, mat, "", status );
+            mat = (double *) astFree( (void *) mat );
+         }
+      } else {
+         map1 = (AstMapping *) astUnitMap( naxes, " ", status );
+      }
+
+/* If the projection is a CAR projection, but the CarLin attribute is
+   set, then we consider the CAR projection to be a simple linear mapping
+   of pixel coords to celestial coords. Do this by using a WcsMap with no
+   projection. All axes will then be treated as linear and non-celestial.
+   If no projection was specified (i.e. if prj == AST__WCSBAD, as is the
+   case when using -TAB for instance) then do the same but use a UnitMap
+   instead of a WcsMap. */
+      map3 = NULL;
+      if( ( latprj == AST__CAR && carlin ) || latprj == AST__WCSBAD ) {
+         if( latprj == AST__CAR ) {
+            map2 = (AstMapping *) astWcsMap( naxes, AST__WCSBAD, axlon + 1,
+                                             axlat + 1, "", status );
+         } else {
+            map2 = (AstMapping *) astUnitMap( naxes, "", status );
+         }
+
+/* Now create a WinMap which adds on the CRVAL values to each axis. */
+         ina = astMalloc( sizeof(double)*naxes );
+         inb = astMalloc( sizeof(double)*naxes );
+         outa = astMalloc( sizeof(double)*naxes );
+         outb = astMalloc( sizeof(double)*naxes );
+         if( astOK ) {
+            for( i = 0; i < naxes; i++ ) {
+               ina[ i ] = 0.0;
+               inb[ i ] = 1.0;
+               outa[ i ] = 0.0;
+               outb[ i ] = 1.0;
+            }
+            lonval = GetItem( &(store->crval), axlon, 0, s, NULL, method, class, status );
+            if( lonval != AST__BAD ) {
+
+/* For recognised projections the CRVAL value is required to be degrees,
+   so convert to radians. For other algorithms (e.g. -TAB) the CRVAL
+   values are in unknown units so retain their original scaling. */
+               *reflon = ( latprj == AST__CAR ) ? lonval*AST__DD2R : lonval;
+
+               outa[ axlon ] += *reflon;
+               outb[ axlon ] += *reflon;
+            } else {
+               outa[ axlon ] = AST__BAD;
+               outb[ axlon ] = AST__BAD;
+            }
+
+            latval = GetItem( &(store->crval), axlat, 0, s, NULL, method, class, status );
+            if( latval != AST__BAD ) {
+               *reflat = ( latprj == AST__CAR ) ? latval*AST__DD2R : latval;
+               outa[ axlat ] += *reflat;
+               outb[ axlat ] += *reflat;
+            } else {
+               outa[ axlat ] = AST__BAD;
+               outb[ axlat ] = AST__BAD;
+            }
+
+            map3 = (AstMapping *) astWinMap( naxes, ina, inb, outa, outb, "", status );
+
+         }
+         ina = astFree( ina );
+         inb = astFree( inb );
+         outa = astFree( outa );
+         outb = astFree( outb );
+
+/* Otherwise, create a WcsMap with the specified projection. The WcsMap
+   is equivalent to a unit mapping for all axes other than "axlat" and
+   "axlon". */
+      } else {
+
+/* Get the highest index ("m" value) of any supplied PVi_m projection
+   parameters (on any axes). */
+         np = GetMaxJM( &(store->pv), s, status );
+
+/* Create the WcsMap */
+         map2 = (AstMapping *) astWcsMap( naxes, latprj, axlon + 1,
+                                          axlat + 1, "", status );
+
+/* If the FITS header contains any projection parameters, store them in
+   the WcsMap. */
+         mxpar_lat = astGetPVMax( map2, axlat );
+         mxpar_lon = astGetPVMax( map2, axlon );
+         for( m = 0; m <= np; m++ ){
+            pv = GetItem( &(store->pv), axlat, m, s, NULL, method, class, status );
+            if( pv != AST__BAD ) {
+               if( m <= mxpar_lat ) {
+                  astSetPV( map2, axlat, m, pv );
+               } else {
+                  sprintf( buf, "Projection parameter PV%d_%d found, "
+                           "but is not used by %s projections.", axlat + 1,
+                           m, astWcsPrjName( astGetWcsType( map2 ) ) );
+                  Warn( this, "badpv", buf, method, class, status );
+               }
+            }
+            pv = GetItem( &(store->pv), axlon, m, s, NULL, method, class, status );
+            if( pv != AST__BAD ) {
+               if( m <= mxpar_lon ) {
+                  astSetPV( map2, axlon, m, pv );
+               } else {
+                  sprintf( buf, "Projection parameter PV%d_%d found, "
+                           "but is not used by %s projections.", axlon + 1,
+                           m, astWcsPrjName( astGetWcsType( map2 ) ) );
+                  Warn( this, "badpv", buf, method, class, status );
+               }
+            }
+         }
+
+/* Invert the WcsMap to get a DEprojection. */
+         astInvert( map2 );
+
+/* Now produce a Mapping which converts the axes holding "Native Spherical
+   Coords" into "Celestial Coords", leaving all other axes unchanged. */
+         map3 = WcsNative( this, store, s, (AstWcsMap *) map2, -1, -1,
+                           method, class, status );
+
+/* Retrieve and store the reference longitude and latitude. */
+         *reflon = GetItem( &(store->crval), axlon, 0, s, NULL, method, class, status );
+         if( *reflon != AST__BAD ) *reflon *= AST__DD2R;
+         *reflat = GetItem( &(store->crval), axlat, 0, s, NULL, method, class, status );
+         if( *reflat != AST__BAD ) *reflat *= AST__DD2R;
+      }
+
+/* If projection parameter PVi_0a for the longitude axis "i" is non-zero,
+   then there is a shift of origin between Intermediate World Coords, IWC,
+   (the CRPIXi values correspond to the origin of IWC), and Projection Plane
+   Coords, PPC (these are the cartesian coordinates used by the WcsMap).
+   This shift of origin results in the fiducial point specified by the
+   CRVALi values mapping onto the pixel reference point specified by the
+   CRPIXj values. In this case we need to add a Mapping which implements
+   the shift of origin. Note, the AST-specific "TPN" projection cannot use
+   this convention since it uses PVi_0 to hold a polynomial correction term. */
+      if( latprj != AST__WCSBAD && astGetWcsType( map2 ) != AST__TPN &&
+          astGetPV( map2, axlon, 0 ) != 0.0 ) {
+
+/* Find the projection plane coords corresponding to the fiducial point
+   of the projection. This is done by using the inverse WcsMap to convert
+   the native spherical coords at the fiducial point into PPC (x,y), which
+   are returned in units of radians (not degrees). */
+         GetFiducialPPC( (AstWcsMap *) map2, &x0, &y0, status );
+         if( x0 != AST__BAD && y0 != AST__BAD ) {
+
+/* Allocate resources. */
+            shifts = astMalloc( sizeof( double )*(size_t) naxes );
+
+/* Check pointers can be used safely. */
+            if( astOK ) {
+
+/* Create a Mapping (a ShiftMap) from IWC to PPC. */
+               for( i = 0; i < naxes; i++ ) shifts[ i ] = 0.0;
+               shifts[ axlon ] = x0;
+               shifts[ axlat ] = y0;
+               shiftmap = (AstMapping *) astShiftMap( naxes, shifts, "", status );
+
+/* Produce a CmpMap which combines "map1" (which converts degrees to
+   radians on the celestial axes) with the above ShiftMap. */
+               newmap = (AstMapping *) astCmpMap( map1, shiftmap, 1, "", status );
+
+/* Annul the component Mappings and use the new one in place of map1. */
+               shiftmap = astAnnul( shiftmap );
+               map1 = astAnnul( map1 );
+               map1 = newmap;
+            }
+
+/* Free resources. */
+            shifts = astFree( shifts );
+         }
+      }
+
+/* Now concatenate the Mappings to produce the returned Mapping. */
+      map4 = (AstMapping *) astCmpMap( map1, map2, 1, "", status );
+      ret = (AstMapping *) astCmpMap( map4, map3, 1, "", status );
+
+/* Annul the component Mappings. */
+      map1 = astAnnul( map1 );
+      map2 = astAnnul( map2 );
+      map3 = astAnnul( map3 );
+      map4 = astAnnul( map4 );
+
+/* We now make changes to the supplied Frame so that the longitude and
+   latitude axes are described by a SkyFrame. First create an appropriate
+   SkyFrame. */
+      sfrm = WcsSkyFrame( this, store, s, prj, wcscelestial_type, axlon,
+                          axlat, method, class, status );
+
+/* The values currently stored in *reflat and *reflon are the CRVAL
+   values. In some circumstances, these may not be the original values in
+   the supplied header but may have been translated within the SpecTrans
+   function as part of the process of translating an old unsupported
+   projection into a new supported projection. Since the returned RefLat
+   and RefLon values may be used to set the reference position for a
+   SpecFrame, we should return the original values rather than the
+   translated values. The original values will have been stored (within
+   SpecTrans) in the FitsChan as keywords RFVALi. If such keywords can
+   be found, use their values in preference to the currently stored CRVAL
+   values.*/
+      if( GetValue( this, FormatKey( "RFVAL", axlon + 1, -1, s, status ),
+                    AST__FLOAT, (void *) &lonval, 0, 0, method, class, status ) &&
+          GetValue( this, FormatKey( "RFVAL", axlat + 1, -1, s, status ),
+                    AST__FLOAT, (void *) &latval, 0, 0, method, class, status ) ) {
+         *reflon = lonval*AST__DD2R;
+         *reflat = latval*AST__DD2R;
+      }
+
+/* Store the reflon and reflat values as the SkyRef position in the
+   SkyFrame, and set SkyRefIs to "ignore" so that the SkyFrame continues
+   to represent absolute celestial coords. Do not change the SkyFrame if
+   it already had a set reference posiiton. */
+      if( ! astTestSkyRef( sfrm, 0 ) ) {
+         if( *reflon != AST__BAD && *reflat != AST__BAD ) {
+            astSetSkyRef( sfrm, 0, *reflon );
+            astSetSkyRef( sfrm, 1, *reflat );
+            astSet( sfrm, "SkyRefIs=Ignored", status );
+         }
+      }
+
+/* Return a clone of this SkyFrame as the reference Frame. */
+      *reffrm = astClone( sfrm );
+
+/* Create a Frame by picking all the other (non-celestial) axes from the
+   supplied Frame. */
+      axes = astMalloc( naxes*sizeof( int ) );
+      if( axes ) {
+         j = 0;
+         for( i = 0; i < naxes; i++ ) {
+            if( i != axlat && i != axlon ) axes[ j++ ] = i;
+         }
+
+/* If there were no other axes, replace the supplied Frame with the skyframe. */
+         if( j == 0 ) {
+            (void) astAnnul( *frm );
+            *frm = (AstFrame *) astClone( sfrm );
+
+/* Otherwise pick the other axes from the supplied Frame */
+         } else {
+            ofrm = astPickAxes( *frm, j, axes, NULL );
+
+/* Replace the supplied Frame with a CmpFrame made up of this Frame and
+   the SkyFrame. */
+            (void) astAnnul( *frm );
+            *frm = (AstFrame *) astCmpFrame( ofrm, sfrm, "", status );
+            ofrm = astAnnul( ofrm );
+         }
+
+/* Permute the axis order to put the longitude and latitude axes back in
+   their original position. The SkyFrame will have the default axis
+   ordering (lon=axis 0, lat = axis 1). */
+         j = 0;
+         for( i = 0; i < naxes; i++ ) {
+            if( i == axlat ) {
+               axes[ i ] = naxes - 1;
+            } else if( i == axlon ) {
+               axes[ i ] = naxes - 2;
+            } else {
+               axes[ i ] = j++;
+            }
+         }
+         astPermAxes( *frm, axes );
+
+/* Free the axes array. */
+         axes= astFree( axes );
+      }
+
+/* Set the units in the supplied IWC Frame for the longitude and latitude
+   axes. Unless using -TAB, these are degrees (the conversion from degs to
+   rads is part of the Mapping from IWC to WCS). If using -TAB the units
+   are unknown. */
+      if( !tabaxis || !tabaxis[ axlon ] ) astSetUnit( iwcfrm, axlon, "deg" );
+      if( !tabaxis || !tabaxis[ axlat ] ) astSetUnit( iwcfrm, axlat, "deg" );
+
+/* Modify any supplied tabmap so that the celestial outputs create
+   radians rather than degrees (but only if the celestial axes are
+   generated by the -TAB algorithm). */
+      if( tabaxis && tabaxis[ axlon ] && tabaxis[ axlat ] ) {
+         mat = (double *) astMalloc( sizeof(double)*naxes );
+         if( mat ){
+            for( i = 0; i < naxes; i++ ){
+               if( i == axlat || i == axlon ){
+                  mat[ i ] = AST__DD2R;
+               } else {
+                  mat[ i ] = 1.0;
+               }
+            }
+            map1 = (AstMapping *) astMatrixMap( naxes, naxes, 1, mat, "", status );
+            mat = (double *) astFree( (void *) mat );
+            map2 = (AstMapping *) astCmpMap( *tabmap, map1, 1, " ", status );
+            map1 = astAnnul( map1 );
+            (void) astAnnul( *tabmap );
+            *tabmap = map2;
+         }
+
+/* Also modify the returned reflon and reflat values to transform them
+   using the tabmap. Also transform the reference position in the SkyFrame. */
+         if( *reflon != AST__BAD && *reflat != AST__BAD ) {
+            ina = astMalloc( sizeof(double)*naxes );
+            outa = astMalloc( sizeof(double)*naxes );
+            if( astOK ) {
+               for( i = 0; i < naxes; i++ ) ina[ i ] = 0.0;
+               ina[ axlat ] = *reflat;
+               ina[ axlon ] = *reflon;
+               astTranN( *tabmap, 1, naxes, 1, ina, 1, naxes, 1, outa );
+               *reflon = outa[ axlon ];
+               *reflat = outa[ axlat ];
+            }
+            ina = astFree( ina );
+            outa = astFree( outa );
+
+/* Store this transformed reference position in the SkyFrame. */
+            astSetSkyRef( sfrm, 0, *reflon );
+            astSetSkyRef( sfrm, 1, *reflat );
+            astSet( sfrm, "SkyRefIs=Ignored", status );
+         }
+      }
+
+/* If the header contains AXREF values for both lon and lat axes, use
+   them as the sky reference position in preferences to the values
+   derived form the CRVAL values. AXREF keywords are created by the
+   astWrite method for axes described by -TAB algorithm that have no inverse
+   transformation. */
+      if( GetValue( this, FormatKey( "AXREF", axlon + 1, -1, s, status ),
+                    AST__FLOAT, (void *) &lonval, 0, 0, method, class, status ) &&
+          GetValue( this, FormatKey( "AXREF", axlat + 1, -1, s, status ),
+                    AST__FLOAT, (void *) &latval, 0, 0, method, class, status ) ) {
+         *reflon = lonval*AST__DD2R;
+         *reflat = latval*AST__DD2R;
+         astSetSkyRef( sfrm, 0, *reflon );
+         astSetSkyRef( sfrm, 1, *reflat );
+         astSet( sfrm, "SkyRefIs=Ignored", status );
+      }
+
+/* Free resources. */
+      sfrm = astAnnul( sfrm );
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static void WcsFcRead( AstFitsChan *fc, AstFitsChan *fc2, FitsStore *store,
+                       const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     WcsFcRead
+
+*  Purpose:
+*     Extract WCS information from a supplied FitsChan using a FITSWCS
+*     encoding, and store it in the supplied FitsStore.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void WcsFcRead( AstFitsChan *fc, AstFitsChan *fc2, FitsStore *store,
+*                     const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function extracts FITSWCS keywords from the supplied FitsChan,
+*     and stores the corresponding WCS information in the supplied FitsStore.
+
+*  Parameters:
+*     fc
+*        Pointer to the FitsChan containing the cards read from the
+*        original FITS header. This should not include any un-used
+*        non-standard keywords.
+*     fc2
+*        Pointer to a second FitsChan. If a card read from "fc" fails to
+*        be converted to its correct data type, a warning is only issued
+*        if there is no card for this keyword in "fc2". "fc2" may be NULL
+*        in which case a warning is always issued.
+*     store
+*        Pointer to the FitsStore structure.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   char buf[200];     /* Buffer for warning message */
+   char *cval;        /* String keyword value */
+   char *keynam;      /* Pointer to current keyword name */
+   char s;            /* Co-ordinate version character */
+   double dval;       /* Floating point keyword value */
+   int fld[2];        /* Integer field values from keyword name */
+   int jm;            /* Pixel axis or projection parameter index */
+   int i;             /* Intermediate axis index */
+   int mark;          /* Non-zero if card should be removed once used */
+   int nfld;          /* Number of integer fields in test string */
+   int ok;            /* Was value converted succesfully? */
+   int type;          /* Keyword data type */
+   int undef;         /* Is an undefined keyword value acceptable? */
+   void *item;        /* Pointer to item to get/put */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Ensure the FitsChan is re-wound. */
+   astClearCard( fc );
+
+/* Loop round all the cards in the FitsChan obtaining the keyword name for
+   each card. Note, the single "=" is correct in the following "while"
+   statement. */
+   s = 0;
+   jm = -1;
+   i = -1;
+   type = AST__NOTYPE;
+   while( (keynam = CardName( fc, status )) ){
+      item = NULL;
+
+/* Assume the card is to be consumed by the reading process. This means
+   the card will be marked as used and effectively excluded from the header.
+   Keywords which supply observation details that do not depend on the
+   mapping from pixel to WCS axes, or on the nature of the WCS axes,
+   are not removed as they may be needed for other, non-WCS related,
+   purposes. */
+      mark = 1;
+
+/* For most keywords, if the keyword is present in the header it must
+   have a definded value. However, some keywords are read from the header
+   but not actually used for anything. This is done to ensure that the
+   keyword is stripped from the header. It is acceptable for such
+   keywords to have an undefined value. Initialise a flag indicating that
+   the next keyword read is not allowed to have an undefined value. */
+      undef = 0;
+
+/* Is this a primary CRVAL keyword? */
+      if( Match( keynam, "CRVAL%d", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->crval);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary CRVAL keyword? */
+      } else if( Match( keynam, "CRVAL%d%1c", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->crval);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary CRPIX keyword? */
+      } else if( Match( keynam, "CRPIX%d", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->crpix);
+         type = AST__FLOAT;
+         i = 0;
+         jm = fld[ 0 ] - 1;
+         s = ' ';
+
+/* Is this a secondary CRPIX keyword? */
+      } else if( Match( keynam, "CRPIX%d%1c", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->crpix);
+         type = AST__FLOAT;
+         i = 0;
+         jm = fld[ 0 ] - 1;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary CDELT keyword? */
+      } else if( Match( keynam, "CDELT%d", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->cdelt);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary CDELT keyword? */
+      } else if( Match( keynam, "CDELT%d%1c", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->cdelt);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary CTYPE keyword? If so, store the associated comment. */
+      } else if( Match( keynam, "CTYPE%d", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->ctype);
+         type = AST__STRING;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = ' ';
+         SetItemC( &(store->ctype_com), i, 0, ' ', CardComm( fc, status ), status );
+
+/* Is this a secondary CTYPE keyword? If so, store the associated comment. */
+      } else if( Match( keynam, "CTYPE%d%1c", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->ctype);
+         type = AST__STRING;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+         SetItemC( &(store->ctype_com), i, 0, s, CardComm( fc, status ), status );
+
+/* Is this a primary CNAME keyword? */
+      } else if( Match( keynam, "CNAME%d", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->cname);
+         type = AST__STRING;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary CNAME keyword? */
+      } else if( Match( keynam, "CNAME%d%1c", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->cname);
+         type = AST__STRING;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary CUNIT keyword? */
+      } else if( Match( keynam, "CUNIT%d", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->cunit);
+         type = AST__STRING;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary CUNIT keyword? */
+      } else if( Match( keynam, "CUNIT%d%1c", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->cunit);
+         type = AST__STRING;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary PC keyword? */
+      } else if( Match( keynam, "PC%d_%d", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->pc);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = fld[ 1 ] - 1;
+         s = ' ';
+
+/* Is this a secondary PC keyword? */
+      } else if( Match( keynam, "PC%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->pc);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = fld[ 1 ] - 1;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary PV keyword? */
+      } else if( Match( keynam, "PV%d_%d", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->pv);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = fld[ 1 ];
+         s = ' ';
+
+/* Is this a secondary PV keyword? */
+      } else if( Match( keynam, "PV%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->pv);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = fld[ 1 ];
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary PS keyword? */
+      } else if( Match( keynam, "PS%d_%d", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->ps);
+         type = AST__STRING;
+         i = fld[ 0 ] - 1;
+         jm = fld[ 1 ];
+         s = ' ';
+
+/* Is this a secondary PS keyword? */
+      } else if( Match( keynam, "PS%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->ps);
+         type = AST__STRING;
+         i = fld[ 0 ] - 1;
+         jm = fld[ 1 ];
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary RADESYS keyword? */
+      } else if( Match( keynam, "RADESYS", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->radesys);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary RADESYS keyword? */
+      } else if( Match( keynam, "RADESYS%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->radesys);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary EQUINOX keyword? */
+      } else if( Match( keynam, "EQUINOX", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->equinox);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary EQUINOX keyword? */
+      } else if( Match( keynam, "EQUINOX%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->equinox);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary LATPOLE keyword? */
+      } else if( Match( keynam, "LATPOLE", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->latpole);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary LATPOLE keyword? */
+      } else if( Match( keynam, "LATPOLE%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->latpole);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary LONPOLE keyword? */
+      } else if( Match( keynam, "LONPOLE", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->lonpole);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary LONPOLE keyword? */
+      } else if( Match( keynam, "LONPOLE%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->lonpole);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary WXSAXES keyword? */
+      } else if( Match( keynam, "WCSAXES", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->wcsaxes);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary WCSAXES keyword? */
+      } else if( Match( keynam, "WCSAXES%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->wcsaxes);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary DUT1 keyword? */
+      } else if( Match( keynam, "DUT1", 0, fld, &nfld, method, class, status ) ){
+         mark = 0;
+         item = &(store->dut1);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a primary MJD-OBS keyword? */
+      } else if( Match( keynam, "MJD-OBS", 0, fld, &nfld, method, class, status ) ){
+         mark = 0;
+         item = &(store->mjdobs);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a primary WCSNAME keyword? */
+      } else if( Match( keynam, "WCSNAME", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->wcsname);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary WCSNAME keyword? */
+      } else if( Match( keynam, "WCSNAME%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->wcsname);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary SPECSYS keyword? */
+      } else if( Match( keynam, "SPECSYS", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->specsys);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary SPECSYS keyword? */
+      } else if( Match( keynam, "SPECSYS%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->specsys);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary SSYSSRC keyword? */
+      } else if( Match( keynam, "SSYSSRC", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->ssyssrc);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary SSYSSRC keyword? */
+      } else if( Match( keynam, "SSYSSRC%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->ssyssrc);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary ZSOURCE keyword? */
+      } else if( Match( keynam, "ZSOURCE", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->zsource);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary ZSOURCE keyword? */
+      } else if( Match( keynam, "ZSOURCE%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->zsource);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary VELOSYS keyword? */
+      } else if( Match( keynam, "VELOSYS", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->velosys);
+         type = AST__FLOAT;
+         undef = 1;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary VELOSYS keyword? */
+      } else if( Match( keynam, "VELOSYS%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->velosys);
+         type = AST__FLOAT;
+         undef = 1;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary RESTFRQ keyword? */
+      } else if( Match( keynam, "RESTFRQ", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->restfrq);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary RESTFRQ keyword? */
+      } else if( Match( keynam, "RESTFRQ%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->restfrq);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary RESTWAV keyword? */
+      } else if( Match( keynam, "RESTWAV", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->restwav);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary RESTWAV keyword? */
+      } else if( Match( keynam, "RESTWAV%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->restwav);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary IMAGFREQ keyword? */
+      } else if( Match( keynam, "IMAGFREQ", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->imagfreq);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a primary SKYREF keyword? */
+      } else if( Match( keynam, "SREF%d", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->skyref);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary SKYREF keyword? */
+      } else if( Match( keynam, "SREF%d%1c", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->skyref);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary SKYREFP keyword? */
+      } else if( Match( keynam, "SREFP%d", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->skyrefp);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary SKYREFP keyword? */
+      } else if( Match( keynam, "SREFP%d%1c", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->skyrefp);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary SKYREFIS keyword? */
+      } else if( Match( keynam, "SREFIS", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->skyrefis);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary SKYREFIS keyword? */
+      } else if( Match( keynam, "SREFIS%1c", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->skyrefis);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary AXREF keyword? */
+      } else if( Match( keynam, "AXREF%d", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->axref);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = ' ';
+
+/* Is this a secondary AXREF keyword? */
+      } else if( Match( keynam, "AXREF%d%1c", 1, fld, &nfld, method, class, status ) ){
+         item = &(store->axref);
+         type = AST__FLOAT;
+         i = fld[ 0 ] - 1;
+         jm = 0;
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a MJD-AVG keyword? */
+      } else if( Match( keynam, "MJD-AVG", 0, fld, &nfld, method, class, status ) ){
+         mark = 0;
+         item = &(store->mjdavg);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a OBSGEO-X keyword? */
+      } else if( Match( keynam, "OBSGEO-X", 0, fld, &nfld, method, class, status ) ){
+         mark = 0;
+         item = &(store->obsgeox);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a OBSGEO-Y keyword? */
+      } else if( Match( keynam, "OBSGEO-Y", 0, fld, &nfld, method, class, status ) ){
+         mark = 0;
+         item = &(store->obsgeoy);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a OBSGEO-Z keyword? */
+      } else if( Match( keynam, "OBSGEO-Z", 0, fld, &nfld, method, class, status ) ){
+         mark = 0;
+         item = &(store->obsgeoz);
+         type = AST__FLOAT;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Is this a TIMESYS keyword? */
+      } else if( Match( keynam, "TIMESYS", 0, fld, &nfld, method, class, status ) ){
+         item = &(store->timesys);
+         type = AST__STRING;
+         i = 0;
+         jm = 0;
+         s = ' ';
+
+/* Following keywords are used to describe "-SIP" distortion as used by
+   the Spitzer project... */
+
+/* Is this a primary A keyword? */
+      } else if( Match( keynam, "A_%d_%d", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->asip);
+         type = AST__FLOAT;
+         i = fld[ 0 ];
+         jm = fld[ 1 ];
+         s = ' ';
+
+/* Is this a secondary A keyword? */
+      } else if( Match( keynam, "A_%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->asip);
+         type = AST__FLOAT;
+         i = fld[ 0 ];
+         jm = fld[ 1 ];
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary B keyword? */
+      } else if( Match( keynam, "B_%d_%d", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->bsip);
+         type = AST__FLOAT;
+         i = fld[ 0 ];
+         jm = fld[ 1 ];
+         s = ' ';
+
+/* Is this a secondary B keyword? */
+      } else if( Match( keynam, "B_%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->bsip);
+         type = AST__FLOAT;
+         i = fld[ 0 ];
+         jm = fld[ 1 ];
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary AP keyword? */
+      } else if( Match( keynam, "AP_%d_%d", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->apsip);
+         type = AST__FLOAT;
+         i = fld[ 0 ];
+         jm = fld[ 1 ];
+         s = ' ';
+
+/* Is this a secondary AP keyword? */
+      } else if( Match( keynam, "AP_%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->apsip);
+         type = AST__FLOAT;
+         i = fld[ 0 ];
+         jm = fld[ 1 ];
+         s = keynam[ strlen( keynam ) - 1 ];
+
+/* Is this a primary BP keyword? */
+      } else if( Match( keynam, "BP_%d_%d", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->bpsip);
+         type = AST__FLOAT;
+         i = fld[ 0 ];
+         jm = fld[ 1 ];
+         s = ' ';
+
+/* Is this a secondary BP keyword? */
+      } else if( Match( keynam, "BP_%d_%d%1c", 2, fld, &nfld, method, class, status ) ){
+         item = &(store->bpsip);
+         type = AST__FLOAT;
+         i = fld[ 0 ];
+         jm = fld[ 1 ];
+         s = keynam[ strlen( keynam ) - 1 ];
+      }
+
+/* If this keyword was recognized, store it in the FitsStore, and mark it
+   as having been read. */
+      if( item ){
+         ok = 1;
+         if( type == AST__FLOAT ){
+            if( CnvValue( fc, AST__FLOAT, undef, &dval, method, status ) ) {
+               SetItem( (double ****) item, i, jm, s, dval, status );
+               if( mark ) MarkCard( fc, status );
+            } else {
+               ok = 0;
+            }
+         } else {
+            if( CnvValue( fc, AST__STRING, undef, &cval, method, status ) ) {
+               cval[ astChrLen( cval ) ] = 0;  /* Exclude trailing spaces */
+               SetItemC( (char *****) item, i, jm, s, cval, status );
+               if( mark ) MarkCard( fc, status );
+            } else {
+              ok = 0;
+            }
+         }
+
+/* Issue a warning if the value could not be converted to the expected
+   type. */
+         if( !ok ) {
+
+/* First check that the keyword is not included in "fc2". */
+            if( !HasCard( fc2, keynam, method, class, status ) ) {
+               sprintf( buf, "The original FITS header contained a value for "
+                        "keyword %s which could not be converted to a %s.",
+                        keynam, ( type==AST__FLOAT ? "floating point number":
+                        "character string" ) );
+               Warn( fc, "badval", buf, "astRead", "FitsChan", status );
+            }
+         }
+      }
+
+/* Move on to the next card. */
+      MoveCard( fc, 1, method, class, status );
+   }
+}
+
+static int WcsFromStore( AstFitsChan *this, FitsStore *store,
+                         const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     WcsFromStore
+
+*  Purpose:
+*     Store WCS keywords in a FitsChan using FITS-WCS encoding.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int WcsFromStore( AstFitsChan *this, FitsStore *store,
+*                       const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function copies the WCS information stored in the supplied
+*     FitsStore into the supplied FitsChan, using FITS-WCS encoding.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     store
+*        Pointer to the FitsStore.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A value of 1 is returned if succesfull, and zero is returned
+*     otherwise.
+*/
+
+/* Local Variables: */
+   char *comm;         /* Pointer to comment string */
+   char *cval;         /* Pointer to string keyword value */
+   char parprefix[3];  /* Prefix for projection parameter keywords */
+   char combuf[80];    /* Buffer for FITS card comment */
+   char s;             /* Co-ordinate version character */
+   char sign[2];       /* Fraction's sign character */
+   char sup;           /* Upper limit on s */
+   char type[MXCTYPELEN];/* Buffer for CTYPE value */
+   double cdl;         /* CDELT value */
+   double fd;          /* Fraction of a day */
+   double mjd99;       /* MJD at start of 1999 */
+   double val;         /* General purpose value */
+   int *tabaxis;       /* Flags WCS axes that use -TAB algorithm */
+   int i;              /* Axis index */
+   int ihmsf[ 4 ];     /* Hour, minute, second, fractional second */
+   int iymdf[ 4 ];     /* Year, month, date, fractional day */
+   int j;              /* Axis index */
+   int jj;             /* SlaLib status */
+   int m;              /* Parameter index */
+   int maxm;           /* Upper limit on m */
+   int naxis;          /* Value of NAXIS keyword */
+   int nc;             /* Length of STYPE string */
+   int nwcs;           /* No. of WCS axes */
+   int ok;             /* Frame created succesfully? */
+   int prj;            /* Projection type */
+   int ret;            /* Returned value */
+
+/* Initialise */
+   ret = 0;
+
+/* Other initialisation to avoid compiler warnings. */
+   tabaxis = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* If the FitsChan contains a value for the NAXIS keyword, note it.
+   Otherwise store -1. */
+   if( !astGetFitsI( this, "NAXIS", &naxis ) ) naxis = -1;
+
+/* Find the last WCS related card. */
+   FindWcs( this, 1, 1, 0, method, class, status );
+
+/* Loop round all co-ordinate versions */
+   sup = GetMaxS( &(store->crval), status );
+   for( s = ' '; s <= sup && astOK; s++ ){
+
+/* For alternate axes, skip this axis description if there is no CRPIX1 or
+   CRVAL1 value. This avoids partial axis descriptions being written out. */
+      if( s != ' ' ) {
+         if( GetItem( &(store->crpix), 0, 0, s, NULL, method, class, status ) ==
+             AST__BAD ||
+             GetItem( &(store->crval), 0, 0, s, NULL, method, class, status ) ==
+             AST__BAD ) {
+            ok = 0;
+            goto next;
+         }
+      }
+
+/* Assume the Frame can be created succesfully. */
+      ok = 1;
+
+/* Save the number of wcs axes. If a value for WCSAXES has been set, or
+   if the number of axes is not the same as specified in the NAXIS keyword,
+   store a WCSAXES keyword. */
+      val = GetItem( &(store->wcsaxes), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) {
+         nwcs = (int) ( val + 0.5 );
+      } else {
+         nwcs = GetMaxJM( &(store->crpix), s, status ) + 1;
+         if( nwcs != 0 && nwcs != naxis ) val = (double) nwcs;
+      }
+      if( val != AST__BAD ) {
+         SetValue( this, FormatKey( "WCSAXES", -1, -1, s, status ),
+                   &nwcs, AST__INT, "Number of WCS axes", status );
+      }
+
+/* Get and save WCSNAME. This is NOT required, so do not return if it is
+   not available. If the WCS is 1-d, only store WCSNAME if its value is
+   different to the CTYPE1 value. */
+      cval = GetItemC( &(store->wcsname), 0, 0, s, NULL, method, class, status );
+      if( cval && nwcs == 1 ) {
+         comm = GetItemC( &(store->ctype), 0, 0, s, NULL, method, class, status );
+         if( comm && Similar( comm, cval, status ) ) cval = NULL;
+      }
+      if( cval ) SetValue( this, FormatKey( "WCSNAME", -1, -1, s, status ), &cval,
+                           AST__STRING, "Reference name for the coord. frame", status );
+
+/* The prefix for numerical projection parameters is usually "PV". */
+      strcpy( parprefix, "PV" );
+
+/* Keywords common to all axis types... */
+
+/* Get and save CRPIX for all pixel axes. These are required, so pass on
+   if they are not available. */
+      for( i = 0; i < nwcs; i++ ) {
+         val = GetItem( &(store->crpix), 0, i, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+            goto next;
+         }
+         sprintf( combuf, "Reference pixel on axis %d", i + 1 );
+         SetValue( this, FormatKey( "CRPIX", i + 1, -1, s, status ), &val, AST__FLOAT,
+                   combuf, status );
+       }
+
+/* Get and save CRVAL for all WCS axes. These are required, so
+   pass on if they are not available. */
+      for( i = 0; i < nwcs; i++ ) {
+         val = GetItem( &(store->crval), i, 0, s, NULL, method, class, status );
+         if( val == AST__BAD ) {
+            ok = 0;
+            goto next;
+         }
+         sprintf( combuf, "Value at ref. pixel on axis %d", i + 1 );
+         SetValue( this, FormatKey( "CRVAL", i + 1, -1, s, status ), &val, AST__FLOAT,
+                   combuf, status );
+      }
+
+/* Allocate memory to indicate if each WCS axis is described by a -TAB
+   algorithm or not. Initialiss it to zero. */
+      tabaxis = astCalloc( nwcs, sizeof( int ) );
+
+/* Get and save CTYPE for all WCS axes. These are required, so
+   pass on if they are not available. */
+      for( i = 0; i < nwcs; i++ ) {
+         cval = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+         if( !cval ) {
+            ok = 0;
+            goto next;
+         }
+         comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
+         if( !comm ) {
+            sprintf( combuf, "Type of co-ordinate on axis %d", i + 1 );
+            comm = combuf;
+         }
+
+/* Extract the projection type as specified by the last 4 characters
+   in the CTYPE keyword value. This will be AST__WCSBAD for non-celestial
+   axes. Note, CTYPE can be more than 8 characters long. */
+         nc = strlen( cval );
+         prj = ( nc > 4 ) ? astWcsPrjType( cval + nc - 4 ) : AST__WCSBAD;
+
+/* If the projection type is "TPN" (an AST-specific code) convert it to
+   standard FITS-WCS code "TAN" and change the prefix for projection
+   parameters from "PV" to "QV". AST will do the inverse conversions when
+   reading such a header. Non-AST software will simply ignore the QV
+   terms and interpret the header as a simple TAN projection. */
+         if( prj == AST__TPN ) {
+            strcpy( parprefix, "QV" );
+            strcpy( type, cval );
+            (void) strcpy( type + nc - 4, "-TAN" );
+            cval = type;
+         }
+
+/* Note if the axis is described by the -TAB algorithm. */
+         tabaxis[ i ] = ( prj == AST__WCSBAD && strlen( cval ) >= 8 &&
+                          !strncmp( cval + 4, "-TAB", 4 ) );
+
+/* Store the (potentially modified) CTYPE value. */
+         SetValue( this, FormatKey( "CTYPE", i + 1, -1, s, status ), &cval, AST__STRING,
+                   comm, status );
+      }
+
+/* Get and save CNAME for all WCS axes. These are NOT required, so
+   do not pass on if they are not available. Do not include a CNAME
+   keyword if its value equals the commen or value of the corresponding
+   CTYPE keyword. */
+      for( i = 0; i < nwcs; i++ ) {
+         cval = GetItemC( &(store->cname), i, 0, s, NULL, method, class, status );
+         if( cval ) {
+            comm = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+            if( !comm || strcmp( comm, cval ) ) {
+               comm = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
+               if( !comm || strcmp( comm, cval ) ) {
+                  sprintf( combuf, "Description of axis %d", i + 1 );
+                  SetValue( this, FormatKey( "CNAME", i + 1, -1, s, status ), &cval,
+                            AST__STRING, combuf, status );
+               }
+            }
+         }
+      }
+
+/* Now choose whether to produce CDi_j or CDELT/PCi_j keywords. */
+      if( astGetCDMatrix( this ) ) {
+
+/* CD matrix. Multiply the row of the PC matrix by the CDELT value. */
+         for( i = 0; i < nwcs; i++ ) {
+            cdl = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
+            if( cdl == AST__BAD ) cdl = 1.0;
+            for( j = 0; j < nwcs; j++ ){
+               val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
+               if( val == AST__BAD ) val = ( i == j ) ? 1.0 : 0.0;
+               val *= cdl;
+               if( val != 0.0 ) {
+                   SetValue( this, FormatKey( "CD", i + 1, j + 1, s, status ), &val,
+                             AST__FLOAT, "Transformation matrix element", status );
+               }
+            }
+         }
+
+/* If producing PC/CDELT keywords... */
+      } else {
+
+/* CDELT keywords. */
+         for( i = 0; i < nwcs; i++ ) {
+            val = GetItem( &(store->cdelt), i, 0, s, NULL, method, class, status );
+            if( val == AST__BAD ) {
+               ok = 0;
+               goto next;
+            }
+            sprintf( combuf, "Pixel size on axis %d", i + 1 );
+            SetValue( this, FormatKey( "CDELT", i + 1, -1, s, status ), &val, AST__FLOAT,
+                      combuf, status );
+         }
+
+/* PC matrix. */
+         for( i = 0; i < nwcs; i++ ) {
+            for( j = 0; j < nwcs; j++ ){
+               val = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
+               if( val != AST__BAD ) {
+                  if( i == j ) {
+                     if( astEQUAL( val, 1.0 ) ) val = AST__BAD;
+                  } else {
+                     if( astEQUAL( val, 0.0 ) ) val = AST__BAD;
+                  }
+               }
+               if( val != AST__BAD ) {
+                  SetValue( this, FormatKey( "PC", i + 1, j + 1, s, status ), &val,
+                            AST__FLOAT, "Transformation matrix element", status );
+               }
+            }
+         }
+      }
+
+/* Get and save CUNIT for all WCS axes. These are NOT required, so
+   do not pass on if they are not available. */
+      for( i = 0; i < nwcs; i++ ) {
+         cval = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
+         if( cval ) {
+            sprintf( combuf, "Units for axis %d", i + 1 );
+            SetValue( this, FormatKey( "CUNIT", i + 1, -1, s, status ), &cval, AST__STRING,
+                      combuf, status );
+         }
+      }
+
+/* Get and save AXREF for all WCS axes. These are NOT required, so do not
+   pass on if they are not available. Note, AXREF is a non-standard keyword
+   used by AST to communicate the reference position on any axes described
+   by the -TAB algorithm and which has no inverse transformation. For all
+   other cases, the reference position corresponds to the values of CRVAL. */
+      for( i = 0; i < nwcs; i++ ) {
+         val = GetItem( &(store->axref), i, 0, s, NULL, method, class, status );
+         if( val != AST__BAD ) {
+            sprintf( combuf, "Reference WCS value on axis %d", i + 1 );
+            SetValue( this, FormatKey( "AXREF", i + 1, -1, s, status ), &val, AST__FLOAT,
+                      combuf, status );
+         }
+      }
+
+/* Get and save SREFIS. This is NOT required, so do not return if it is
+   not available. Note, SREFIS is a non-standard keyword used by AST to
+   communicate the SkyRefIs attribute in the original SkyFrame. */
+      cval = GetItemC( &(store->skyrefis), 0, 0, s, NULL, method, class, status );
+      if( cval ) SetValue( this, FormatKey( "SREFIS", -1, -1, s, status ), &cval,
+                           AST__STRING, "Is SkyRef used as pole or origin?", status );
+
+/* Get and save SREF for all WCS axes. These are NOT required, so do not
+   pass on if they are not available. Note, SREF is a non-standard keyword
+   used by AST to communicate the SkyRef position on any axes described
+   by a offset SkyFrame. */
+      for( i = 0; i < nwcs; i++ ) {
+         val = GetItem( &(store->skyref), i, 0, s, NULL, method, class, status );
+         if( val != AST__BAD ) {
+            sprintf( combuf, "Sky reference position on axis %d", i + 1 );
+            SetValue( this, FormatKey( "SREF", i + 1, -1, s, status ), &val, AST__FLOAT,
+                      combuf, status );
+         }
+      }
+
+/* Get and save SREFP for all WCS axes. These are NOT required, so do not
+   pass on if they are not available. Note, SREFP is a non-standard keyword
+   used by AST to communicate the SkyRefP position on any axes described
+   by a offset SkyFrame. */
+      for( i = 0; i < nwcs; i++ ) {
+         val = GetItem( &(store->skyrefp), i, 0, s, NULL, method, class, status );
+         if( val != AST__BAD ) {
+            sprintf( combuf, "Sky primary meridian position on axis %d", i + 1 );
+            SetValue( this, FormatKey( "SREFP", i + 1, -1, s, status ), &val, AST__FLOAT,
+                      combuf, status );
+         }
+      }
+
+/* Date of observation (only allowed for primary axis descriptions). */
+      if( s == ' ' ) {
+         val = GetItem( &(store->mjdobs), 0, 0, s, NULL, method, class, status );
+         if( val != AST__BAD ) {
+            SetValue( this, FormatKey( "MJD-OBS", -1, -1, s, status ),
+                      &val, AST__FLOAT, "Modified Julian Date of observation", status );
+
+/* The format used for the DATE-OBS keyword depends on the value of the
+   keyword. For DATE-OBS < 1999.0, use the old "dd/mm/yy" format.
+   Otherwise, use the new "ccyy-mm-ddThh:mm:ss[.ssss]" format. */
+            palCaldj( 99, 1, 1, &mjd99, &jj );
+            if( val < mjd99 ) {
+               palDjcal( 0, val, iymdf, &jj );
+               sprintf( combuf, "%2.2d/%2.2d/%2.2d", iymdf[ 2 ], iymdf[ 1 ],
+                        iymdf[ 0 ] - ( ( iymdf[ 0 ] > 1999 ) ? 2000 : 1900 ) );
+            } else {
+               palDjcl( val, iymdf, iymdf+1, iymdf+2, &fd, &jj );
+               palDd2tf( 3, fd, sign, ihmsf );
+               sprintf( combuf, "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d.%3.3d",
+                        iymdf[0], iymdf[1], iymdf[2], ihmsf[0], ihmsf[1],
+                        ihmsf[2], ihmsf[3] );
+            }
+
+/* Now store the formatted string in the FitsChan. */
+            cval = combuf;
+            SetValue( this, "DATE-OBS", (void *) &cval, AST__STRING,
+                      "Date of observation", status );
+         }
+         val = GetItem( &(store->mjdavg), 0, 0, ' ', NULL, method, class, status );
+         if( val != AST__BAD ) SetValue( this, "MJD-AVG", &val, AST__FLOAT,
+                                         "Average Modified Julian Date of observation", status );
+
+/* Store the timescale in TIMESYS. */
+         cval = GetItemC( &(store->timesys), 0, 0, s, NULL, method, class, status );
+         if( cval ) SetValue( this, "TIMESYS", &cval, AST__STRING,
+                              "Timescale for MJD-OBS/MJD-AVG values", status );
+      }
+
+/* Numerical projection parameters */
+      maxm = GetMaxJM( &(store->pv), s, status );
+      for( i = 0; i < nwcs; i++ ){
+         for( m = 0; m <= maxm; m++ ){
+            val = GetItem( &(store->pv), i, m, s, NULL, method, class, status );
+            if( val != AST__BAD ) {
+
+/* If the axis uses the "TAB" algorithm, there may be a PVi_4a parameter
+   in the FitsStore. This is an AST extension to the published -TAB
+   algorithm, and is used to hold the interpolation method. To avoid
+   clashing with any standard use of PV1_4a, rename it to QVi_4a. The
+   default is zero (linear interpolation) so do not write the QV value
+   if it zero. */
+               if( m == 4 && tabaxis[ i ] ) {
+                  if( val != 0.0 ) {
+                     SetValue( this, FormatKey( "QV", i + 1, m, s, status ),
+                               &val, AST__FLOAT, "Use nearest neighbour "
+                               "interpolation", status );
+                  }
+
+/* Just store the parameters for other type of axes. */
+               } else {
+                  SetValue( this, FormatKey( parprefix, i + 1, m, s, status ), &val,
+                            AST__FLOAT, "Projection parameter", status );
+               }
+            }
+         }
+      }
+
+/* String projection parameters */
+      maxm = GetMaxJMC( &(store->ps), s, status );
+      for( i = 0; i < nwcs; i++ ){
+         for( m = 0; m <= maxm; m++ ){
+            cval = GetItemC( &(store->ps), i, m, s, NULL, method, class, status );
+            if( cval ) {
+               SetValue( this, FormatKey( "PS", i + 1, m, s, status ), &cval,
+                         AST__STRING, "Projection parameter", status );
+            }
+         }
+      }
+
+/* Keywords specific to celestial axes... */
+
+/* Get and save RADESYS. This is NOT required, so do not return if it is
+   not available. */
+      cval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
+      if( cval ) SetValue( this, FormatKey( "RADESYS", -1, -1, s, status ), &cval,
+                           AST__STRING, "Reference frame for RA/DEC values", status );
+
+/* Reference equinox */
+      val = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) SetValue( this, FormatKey( "EQUINOX", -1, -1, s, status ),
+                                      &val, AST__FLOAT,
+                                      "[yr] Epoch of reference equinox", status );
+
+/* Latitude of native north pole */
+      val = GetItem( &(store->latpole), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) SetValue( this, FormatKey( "LATPOLE", -1, -1, s, status ),
+                                      &val, AST__FLOAT,
+                                      "[deg] Latitude of native north pole", status );
+
+/* Longitude of native north pole */
+      val = GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) SetValue( this, FormatKey( "LONPOLE", -1, -1, s, status ),
+                                      &val, AST__FLOAT,
+                                      "[deg] Longitude of native north pole", status );
+
+/* Keywords specific to spectral axes... */
+
+/* SPECSYS - the standard of rest for the spectral axis */
+      cval = GetItemC( &(store->specsys), 0, 0, s, NULL, method, class, status );
+      if( cval ) SetValue( this, FormatKey( "SPECSYS", -1, -1, s, status ), &cval,
+                           AST__STRING, "Standard of rest for spectral axis", status );
+
+/* SSYSSRC - the standard of rest in which ZSOURCE is stored. */
+      cval = GetItemC( &(store->ssyssrc), 0, 0, s, NULL, method, class, status );
+      if( cval ) SetValue( this, FormatKey( "SSYSSRC", -1, -1, s, status ), &cval,
+                           AST__STRING, "Standard of rest for source redshift", status );
+
+/* ZSOURCE - topocentric optical velocity of source */
+      val = GetItem( &(store->zsource), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) SetValue( this, FormatKey( "ZSOURCE", -1, -1, s, status ),
+                                      &val, AST__FLOAT, "[] Redshift of source", status );
+
+/* VELOSYS - topocentric apparent radial velocity of the standard of rest. */
+      val = GetItem( &(store->velosys), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) SetValue( this, FormatKey( "VELOSYS", -1, -1, s, status ),
+                                      &val, AST__FLOAT, "[m/s] Topo. apparent velocity of rest frame", status );
+
+/* RESTFRQ - rest frequency */
+      val = GetItem( &(store->restfrq), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) SetValue( this, FormatKey( "RESTFRQ", -1, -1, s, status ),
+                                      &val, AST__FLOAT, "[Hz] Rest frequency", status );
+
+/* RESTWAV - rest wavelength */
+      val = GetItem( &(store->restwav), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) SetValue( this, FormatKey( "RESTWAV", -1, -1, s, status ),
+                                      &val, AST__FLOAT, "[m] Rest wavelength", status );
+
+/* The image frequency corresponding to the rest frequency (only used for
+   double sideband data). This is not part of the FITS-WCS standard but
+   is added for the benefit of JACH. */
+      val = GetItem( &(store->imagfreq), 0, 0, s, NULL, method, class, status );
+      if( val != AST__BAD ) {
+         SetValue( this, "IMAGFREQ", &val, AST__FLOAT, "[Hz] Image frequency", status );
+      }
+
+/* OBSGEO-X/Y/Z - observer's geocentric coords. Note, these always refer
+   to the primary axes. */
+      if( s == ' ' ) {
+         val = GetItem( &(store->obsgeox), 0, 0, s, NULL, method, class, status );
+         if( val != AST__BAD ) SetValue( this, "OBSGEO-X", &val, AST__FLOAT, "[m] Observatory geocentric X", status );
+         val = GetItem( &(store->obsgeoy), 0, 0, s, NULL, method, class, status );
+         if( val != AST__BAD ) SetValue( this, "OBSGEO-Y", &val, AST__FLOAT, "[m] Observatory geocentric Y", status );
+         val = GetItem( &(store->obsgeoz), 0, 0, s, NULL, method, class, status );
+         if( val != AST__BAD ) SetValue( this, "OBSGEO-Z", &val, AST__FLOAT, "[m] Observatory geocentric Z", status );
+      }
+
+/* See if a Frame was sucessfully written to the FitsChan. */
+next:
+      ok = ok && astOK;
+
+/* If so, indicate we have something to return. */
+      if( ok ) ret = 1;
+
+/* If we are producing secondary axes, clear any error status so we can
+   continue to produce the next Frame. Retain the error if the primary axes
+   could not be produced. After the primary axes, do the A axes. */
+      if( s != ' ' ) {
+         astClearStatus;
+      } else {
+         s = 'A' - 1;
+      }
+
+/* Remove the secondary "new" flags from the FitsChan. This flag is
+   associated with cards which have been added to the FitsChan during
+   this pass through the main loop in this function. If the Frame was
+   written out succesfully, just clear the flags. If anything went wrong
+   with this Frame, remove the flagged cards from the FitsChan. */
+      FixNew( this, NEW2, !ok, method, class, status );
+
+/* Set the current card so that it points to the last WCS-related keyword
+   in the FitsChan (whether previously read or not). */
+      FindWcs( this, 1, 1, 0, method, class, status );
+
+/* Free resources. */
+      tabaxis = astFree( tabaxis );
+   }
+
+/* Return zero or ret depending on whether an error has occurred. */
+   return astOK ? ret : 0;
+}
+
+static AstMapping *WcsIntWorld( AstFitsChan *this, FitsStore *store, char s,
+                                int naxes, const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     WcsIntWorld
+
+*  Purpose:
+*     Create a Mapping from pixel coords to intermediate world coords.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     AstMapping *WcsIntWorld( AstFitsChan *this, FitsStore *store, char s,
+*                              int naxes, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function interprets the contents of the supplied FitsStore
+*     structure, and creates a Mapping which describes the transformation
+*     from pixel coordinates to intermediate world coordinates, using the
+*     FITS World Coordinate System conventions. This is a general linear
+*     transformation described by the CRPIXj, PCi_j and CDELTi keywords.
+
+*  Parameters:
+*     this
+*        The FitsChan. ASTWARN cards may be added to this FitsChan if any
+*        anomalies are found in the keyword values in the FitsStore.
+*     store
+*        A structure containing information about the requested axis
+*        descriptions derived from a FITS header.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     naxes
+*        The number of intermediate world coordinate axes (WCSAXES).
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the Mapping.
+*/
+
+/* Local Variables: */
+   AstMapping   *mapd1;      /* Pointer to first distortion Mapping */
+   AstMapping   *mapd2;      /* Pointer to second distortion Mapping */
+   AstMapping   *mapd3;      /* Pointer to third distortion Mapping */
+   AstMapping   *mapd4;      /* Pointer to fourth distortion Mapping */
+   AstMapping   *map0;       /* Pointer to a Mapping */
+   AstMapping   *map1;       /* Pointer to a Mapping */
+   AstMapping   *ret;        /* Pointer to the returned Mapping */
+
+/* Initialise the pointer to the returned Mapping. */
+   ret = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return ret;
+
+/* First of all, check the CTYPE keywords to see if they contain any known
+   distortion codes (following the syntax described in FITS-WCS paper IV).
+   If so, Mappings are returned which represents the distortions to be
+   applied at each point in the chain of Mappings produced by this function.
+   Any distortion codes are removed from the CTYPE values in the FitsStore. */
+   DistortMaps( this, store, s, naxes, &mapd1, &mapd2, &mapd3, &mapd4, method,
+                class, status );
+
+/* If distortion is to be applied now, initialise the returned Mapping to
+   be the distortion. */
+   if( mapd1 ) ret = mapd1;
+
+/* Try to create a WinMap which translates the pixel coordinates so
+   that they are refered to an origin at the reference pixel. This
+   subtracts the value of CRPIXi from axis i. */
+   map1 = (AstMapping *) WcsShift( store, s, naxes, method, class, status );
+
+/* Combine this with any previous Mapping. */
+   if( ret ) {
+      map0 = (AstMapping *) astCmpMap( ret, map1, 1, "", status );
+      ret = astAnnul( ret );
+      map1 = astAnnul( map1 );
+      ret = map0;
+   } else {
+      ret = map1;
+   }
+
+/* If distortion is to be applied now, combine the two Mappings. */
+   if( mapd2 ) {
+      map0 = (AstMapping *) astCmpMap( ret, mapd2, 1, "", status );
+      ret = astAnnul( ret );
+      mapd2 = astAnnul( mapd2 );
+      ret = map0;
+   }
+
+/* Now try to create a MatrixMap to implement the PC matrix. Combine it
+   with the above Mapping. Add a Warning if this mapping cannot be inverted. */
+   map1 = (AstMapping *) WcsPCMatrix( store, s, naxes, method, class, status );
+   if( !astGetTranInverse( map1 ) ) {
+      Warn( this, "badmat", "The pixel rotation matrix in the original FITS "
+            "header (specified by CD or PC keywords) could not be inverted. "
+            "This may be because the matrix contains rows or columns which "
+            "are entirely zero.", method, class, status );
+   }
+   map0 = (AstMapping *) astCmpMap( ret, map1, 1, "", status );
+   ret = astAnnul( ret );
+   map1 = astAnnul( map1 );
+   ret = map0;
+
+/* If distortion is to be applied now, combine the two Mappings. */
+   if( mapd3 ) {
+      map0 = (AstMapping *) astCmpMap( ret, mapd3, 1, "", status );
+      ret = astAnnul( ret );
+      mapd3 = astAnnul( mapd3 );
+      ret = map0;
+   }
+
+/* Now try to create a diagonal MatrixMap to implement the CDELT scaling.
+   Combine it with the above Mapping.  */
+   map1 = (AstMapping *) WcsCDeltMatrix( store, s, naxes, method, class, status );
+   map0 = (AstMapping *) astCmpMap( ret, map1, 1, "", status );
+   ret = astAnnul( ret );
+   map1 = astAnnul( map1 );
+   ret = map0;
+
+/* If distortion is to be applied now, combine the two Mappings. */
+   if( mapd4 ) {
+      map0 = (AstMapping *) astCmpMap( ret, mapd4, 1, "", status );
+      ret = astAnnul( ret );
+      mapd4 = astAnnul( mapd4 );
+      ret = map0;
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static AstMapping *WcsMapFrm( AstFitsChan *this, FitsStore *store, char s,
+                              AstFrame **frm, const char *method,
+                              const char *class, int *status ){
+
+/*
+*  Name:
+*     WcsMapFrm
+
+*  Purpose:
+*     Create a Mapping and Frame for the WCS transformations described in a
+*     FITS header.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     AstMapping *WcsMapFrm( AstFitsChan *this, FitsStore *store, char s,
+*                            AstFrame **frm, const char *method,
+*                            const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function interprets the contents of the supplied FitsStore
+*     structure, and creates a Mapping which describes the transformation
+*     from pixel coordinates to world coordinates, using the FITS World
+*     Coordinate System conventions. It also creates a Frame describing
+*     the world coordinate axes.
+
+*  Parameters:
+*     this
+*        The FitsChan.
+*     store
+*        A structure containing information about the requested axis
+*        descriptions derived from a FITS header.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     frm
+*        The address of a location at which to store a pointer to the
+*        Frame describing the world coordinate axes. If the Iwc attribute
+*        is non-zero, then this is actually a FrameSet in which the current
+*        Frame is the required WCS system. The FrameSet also contains one
+*        other Frame which defines the FITS IWC system.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the Mapping.
+*/
+
+/* Local Variables: */
+   AstFrame *iwcfrm;         /* Frame defining IWC system */
+   AstFrameSet *fs;          /* Pointer to returned FrameSet */
+   AstMapping *map10;        /* Pointer to a Mapping */
+   AstMapping *map1;         /* Pointer to a Mapping */
+   AstMapping *map2;         /* Pointer to a Mapping */
+   AstMapping *map3;         /* Pointer to a Mapping */
+   AstMapping *map4;         /* Pointer to a Mapping */
+   AstMapping *map5;         /* Pointer to a Mapping */
+   AstMapping *map6;         /* Pointer to a Mapping */
+   AstMapping *map7;         /* Pointer to a Mapping */
+   AstMapping *map8;         /* Pointer to a Mapping */
+   AstMapping *map9;         /* Pointer to a Mapping */
+   AstMapping *ret;          /* Pointer to the returned Mapping */
+   AstMapping *tabmap;       /* Mapping from psi to WCS (paper III - 6.1.2) */
+   AstSkyFrame *reffrm;      /* SkyFrame defining reflon and reflat */
+   char id[2];               /* ID string for returned Frame */
+   char iwc[5];              /* Domain name for IWC Frame */
+   const char *cc;           /* Pointer to Domain */
+   double dut1;              /* UT1-UTC correction in days */
+   double dval;              /* Temporary double value */
+   double reflat;            /* Reference celestial latitude */
+   double reflon;            /* Reference celestial longitude */
+   int *tabaxis;             /* Flags indicating -TAB axes */
+   int wcsaxes;              /* Number of physical axes */
+
+/* Initialise the pointer to the returned Mapping. */
+   ret = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return ret;
+
+/* Identify any axes that use the -TAB algoritm code described in FITS-WCS
+   paper III, and convert their CTYPE values to describe linear axes
+   (i.e. just remove "-TAB" from the CTYPE value). This also returns a
+   Mapping (which includes one or more LutMaps) that should be applied to
+   the resulting linear axis values in order to generate the final WCS
+   axis values. A NULL pointer is returned if no axes use -TAB. */
+   tabmap = TabMapping( this, store, s, &tabaxis, method, class, status );
+
+/* Obtain the number of physical axes in the header. If the WCSAXES header
+   was specified, use it. Otherwise assume it is the same as the number
+   of pixel axes. */
+   dval = GetItem( &(store->wcsaxes), 0, 0, s, NULL, method, class, status );
+   if( dval != AST__BAD ) {
+      wcsaxes = (int) dval + 0.5;
+   } else {
+      wcsaxes = store->naxis;
+   }
+
+/* Create a simple Frame to represent IWC coords. */
+   iwcfrm = astFrame( wcsaxes, "Title=FITS Intermediate World Coordinates", status );
+   strcpy( iwc, "IWC" );
+   iwc[ 3 ]= s;
+   iwc[ 4 ]= 0;
+   astSetDomain( iwcfrm, iwc );
+
+/* Create a simple Frame which will be used as the initial representation
+   for the physical axes. This Frame will be changed later (or possibly
+   replaced by a Frame of another class) when we know what type of
+   physical axes we are dealing with. Set its Domain to "AST_FITSCHAN"
+   This value is used to identify axes which have not been changed,
+   and will be replaced before returning the final FrameSet. */
+   *frm = astFrame( wcsaxes, "Domain=AST_FITSCHAN", status );
+
+/* Store the coordinate version character as the Ident attribute for the
+   returned Frame. */
+   id[ 0 ] = s;
+   id[ 1 ] = 0;
+   astSetIdent( *frm, id );
+
+/* Create a Mapping which goes from pixel coordinates to what FITS-WCS
+   paper I calls "intermediate world coordinates". This stage is the same
+   for all axes. It uses the CRPIXj, PCi_j and CDELTi headers (and
+   distortion codes from the CTYPE keywords). */
+   map1 = WcsIntWorld( this, store, s, wcsaxes, method, class, status );
+
+/* The conversion from intermediate world coordinates to the final world
+   coordinates depends on the type of axis being converted (as specified
+   by its CTYPE keyword). Check for each type of axis for which known
+   conventions exist... */
+
+/* Celestial coordinate axes. The following call returns a Mapping which
+   transforms any celestial coordinate axes from intermediate world
+   coordinates to the final celestial coordinates. Other axes are left
+   unchanged by the Mapping. It also modifies the Frame so that a
+   SkyFrame is used to describe the celestial axes. */
+   map2 = WcsCelestial( this, store, s, frm, iwcfrm, &reflon, &reflat,
+                        &reffrm, &tabmap, tabaxis, method, class, status );
+
+/* Spectral coordinate axes. The following call returns a Mapping which
+   transforms any spectral coordinate axes from intermediate world
+   coordinates to the final spectral coordinates. Other axes are left
+   unchanged by the Mapping. It also modifies the Frame so that a
+   SpecFrame is used to describe the spectral axes. */
+   map3 = WcsSpectral( this, store, s, frm, iwcfrm, reflon, reflat, reffrm,
+                       method, class, status );
+
+/* Any axes which were not recognized by the above calls are assumed to
+   be linear. Create a Mapping which adds on the reference value for such
+   axes, and modify the Frame to desribe the axes. */
+   map4 = WcsOthers( this, store, s, frm, iwcfrm, method, class, status );
+
+/* If the Frame still has the Domain "AST_FITSCHAN", clear it. */
+   cc = astGetDomain( *frm );
+   if( cc && !strcmp( cc, "AST_FITSCHAN" ) ) astClearDomain( *frm );
+
+/* Concatenate the Mappings and simplify the result. */
+   map5 = (AstMapping *) astCmpMap( map1, map2, 1, "", status );
+   map6 = (AstMapping *) astCmpMap( map5, map3, 1, "", status );
+   map7 = (AstMapping *) astCmpMap( map6, map4, 1, "", status );
+   if( tabmap ) {
+      map8 = (AstMapping *) astCmpMap( map7, tabmap, 1, "", status );
+   } else {
+      map8 = astClone( map7 );
+   }
+
+   ret = astSimplify( map8 );
+
+/* Ensure that the coordinate version character is stored as the Ident
+   attribute for the returned Frame (the above calls may have changed it). */
+   astSetIdent( *frm, id );
+
+/* Set the DUT1 value. Note, the JACH store DUT1 in units of days in their
+   FITS headers, so convert from days to seconds. May need to do somthing
+   about this if the forthcoming FITS-WCS paper 5 (time axes) defines DUT1
+   to be in seconds. */
+   dut1 = GetItem( &(store->dut1), 0, 0, s, NULL, method, class, status );
+   if( dut1 != AST__BAD ) astSetDut1( *frm, dut1*SPD );
+
+/* The returned Frame is actually a FrameSet in which the current Frame
+   is the required WCS Frame. The FrameSet contains one other Frame,
+   which is the Frame representing IWC. Create a FrameSet containing these
+   two Frames. */
+   if( astGetIwc( this ) ) {
+      fs = astFrameSet( iwcfrm, "", status );
+      astInvert( map1 );
+      map9 = (AstMapping *) astCmpMap( map1, ret, 1, "", status );
+      astInvert( map1 );
+      map10 = astSimplify( map9 );
+      astAddFrame( fs, AST__BASE, map10, *frm );
+
+/* Return this FrameSet instead of the Frame. */
+      *frm = astAnnul( *frm );
+      *frm = (AstFrame *) fs;
+
+/* Free resources */
+      map9 = astAnnul( map9 );
+      map10 = astAnnul( map10 );
+   }
+
+/* Annull temporary resources. */
+   if( reffrm ) reffrm = astAnnul( reffrm );
+   if( tabmap ) tabmap = astAnnul( tabmap );
+   tabaxis = astFree( tabaxis );
+   iwcfrm = astAnnul( iwcfrm );
+   map1 = astAnnul( map1 );
+   map2 = astAnnul( map2 );
+   map3 = astAnnul( map3 );
+   map4 = astAnnul( map4 );
+   map5 = astAnnul( map5 );
+   map6 = astAnnul( map6 );
+   map7 = astAnnul( map7 );
+   map8 = astAnnul( map8 );
+
+/* Annul thre returned objects if an error has occurred. */
+   if( !astOK ) {
+      ret = astAnnul( ret );
+      *frm = astAnnul( *frm );
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static AstMatrixMap *WcsPCMatrix( FitsStore *store, char s, int naxes,
+                                  const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     WcsPCMatrix
+
+*  Purpose:
+*     Create a MatrixMap representing the PC matrix.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     AstMatrixMap *WcsPCMatrix( FitsStore *store, char s, int naxes,
+*                                const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A MatrixMap representing the FITS "PC" matrix is returned.
+
+*  Parameters:
+*     store
+*        A structure containing values for FITS keywords relating to
+*        the World Coordinate System.
+*     s
+*        A character s identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     naxes
+*        The number of intermediate world coordinate axes (WCSAXES).
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the created MatrixMap or a NULL pointer if an
+*     error occurred.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *new;       /* The created MatrixMap */
+   double *el;              /* Pointer to next matrix element */
+   double *mat;             /* Pointer to matrix array */
+   int i;                   /* Pixel axis index */
+   int j;                   /* Intermediate axis index. */
+
+/* Initialise/ */
+   new = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Allocate memory for the matrix. */
+   mat = (double *) astMalloc( sizeof(double)*naxes*naxes );
+   if( astOK ){
+
+/* Fill the matrix with values from the FitsStore. */
+      el = mat;
+      for( i = 0; i < naxes; i++ ){
+         for( j = 0; j < naxes; j++ ){
+
+/* Get the PCj_i value for this axis. Missing terms can be defaulted so
+   do not report an error if the required value is not present in the
+   FitsStore. */
+            *el = GetItem( &(store->pc), i, j, s, NULL, method, class, status );
+
+/* Diagonal terms default to to 1.0, off-diagonal to zero. */
+            if( *el == AST__BAD ) *el = ( i == j ) ? 1.0: 0.0;
+
+/* Move on to the next matrix element. */
+            el++;
+         }
+      }
+
+/* Create the matrix. */
+      new = astMatrixMap( naxes, naxes, 0, mat, "", status );
+
+/* Report an error if the inverse transformation is undefined. */
+      if( !astGetTranInverse( new ) && astOK ) {
+        astError( AST__BDFTS, "%s(%s): Unusable rotation matrix (PC or CD) found "
+                  "in the FITS-WCS header - the matrix cannot be inverted.", status, method, class );
+      }
+
+/* Release the memory used to hold the matrix. */
+      mat = (double *) astFree( (void *) mat );
+   }
+
+/* If an error has occurred, attempt to annul the returned MatrixMap. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the MatrixMap. */
+   return new;
+}
+
+static AstMapping *WcsNative( AstFitsChan *this, FitsStore *store, char s,
+                              AstWcsMap *wcsmap, int fits_ilon, int fits_ilat,
+                              const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     WcsNative
+
+*  Purpose:
+*     Create a CmpMap which transforms Native Spherical Coords to
+*     Celestial Coords.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     AstMapping *WcsNative( AstFitsChan *this, FitsStore *store, char s,
+*                            AstWcsMap *wcsmap, int fits_ilon, int fits_ilat,
+*                            const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A CmpMap is created which rotates the supplied Native Spherical Coords
+*     into Celestial Coords in the standard system specified by the CTYPE
+*     keywords. Any non-celestial axes are left unchanged.
+*
+*     At the highest level, the returned CmpMap is made up of the following
+
+*     Mappings in series (if celestial long/lat axes are present):
+*        1 - A PermMap which rearranges the axes so that the longitude axis is
+*            axis 0, the latitude axis is axis 1, and all other axes are
+*            stored at higher indices, starting at axis 2.
+*        2 - A CmpMap which converts the values on axes 0 and 1 from Native
+*            Spherical to Celestial coordinates, leaving all other axes
+*            unchanged.
+*        3 - A PermMap which rearranges the axes to put the longitude and
+*            latitude axes back in their original places. This is just the
+*            inverse of the PermMap used at stage 1 above.
+*
+*     The CmpMap used at stage 2 above, is made up of two Mappings in
+
+*     parallel:
+*         4 - A CmpMap which maps axes 0 and 1 from Native Spherical to
+*             Celestial coordinates.
+*         5 - A UnitMap which passes on the values to axes 2, 3, etc,
+*             without change.
+*
+*     The CmpMap used at stage 4 above, is made up of the following Mappings
+
+*     in series:
+*         6 - A SphMap which converts the supplied spherical coordinates into
+*             Cartesian Coordinates.
+*         7 - A MatrixMap which rotates the Cartesian coordinates from the
+*             Native to the Celestial system.
+*         8 - A SphMap which converts the resulting Cartesian coordinates back
+*             to spherical coordinates.
+
+*  Parameters:
+*     this
+*        The FitsChan in which to store any warning cards. If NULL, no
+*        warnings are stored.
+*     store
+*        A structure containing values for FITS keywords relating to
+*        the World Coordinate System.
+*     s
+*        Co-ordinate version character to use (space means primary axes).
+*     wcsmap
+*        A mapping describing the deprojection which is being used. This is
+*        needed in order to be able to locate the fiducial point within the
+*        Native Speherical Coordinate system, since it varies from projection
+*        to projection.
+*     fits_ilon
+*        The zero-based FITS WCS axis index corresponding to celestial
+*        longitude (i.e. one less than the value of "i" in the keyword
+*        names "CTYPEi", "CRVALi", etc). If -1 is supplied, the index of
+*        the longitude axis in the supplied WcsMap is used.
+*     fits_ilat
+*        The zero-based FITS WCS axis index corresponding to celestial
+*        latitude (i.e. one less than the value of "i" in the keyword
+*        names "CTYPEi", "CRVALi", etc). If -1 is supplied, the index of
+*        the latitude axis in the supplied WcsMap is used.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the created CmpMap or a NULL pointer if an error occurred.
+
+*  Notes:
+*     -  The local variable names correspond to the notation in the papers
+*     by Greisen & Calabretta describing the FITS WCS system.
+*/
+
+/* Local Variables: */
+   AstCmpMap *cmpmap;         /* A CmpMap */
+   AstMapping *new;           /* The returned CmpMap */
+   AstMatrixMap *matmap2;     /* Another MatrixMap */
+   AstMatrixMap *matmap;      /* A MatrixMap */
+   AstPermMap *permmap;       /* A PermMap */
+   AstSphMap *sphmap;         /* A SphMap */
+   AstUnitMap *unitmap;       /* A UnitMap */
+   char buf[150];             /* Message buffer */
+   double alpha0;             /* Long. of fiduaicl point in standard system */
+   double alphap;             /* Long. of native nth pole in standard system */
+   double axis[3];            /* Vector giving the axis of rotation */
+   double delta0;             /* Lat. of fiducial point in standard system */
+   double deltap;             /* Lat. of native nth pole in standard system */
+   double latpole;            /* Lat. of native nth pole in standard system if deltap undefined */
+   double phip;               /* Long. of standard nth pole in native system */
+   double phi0;               /* Native longitude at fiducial point */
+   double theta0;             /* Native latitude at fiducial point */
+   int *inperm;               /* Pointer to array of output axis indices */
+   int *outperm;              /* Pointer to array of input axis indices */
+   int axlat;                 /* Index of latitude physical axis */
+   int axlon;                 /* Index of longitude physical axis */
+   int i;                     /* Loop count */
+   int nax_rem;               /* No. of non-astrometric axes */
+   int naxis;                 /* No. of axes. */
+   int new_axlat;             /* Index of lat. physical axis after perming */
+   int tpn;                   /* Is this a TPN projection? */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the returned CmpMap pointer. */
+   new = NULL;
+
+/* Store the number of axes in a local variable. */
+   naxis = astGetNin( wcsmap );
+
+/* Get the indices of the celestial axes. */
+   axlon = astGetWcsAxis( wcsmap, 0 );
+   axlat = astGetWcsAxis( wcsmap, 1 );
+
+/* If the corresponding FITS axis indices were not supplied, use the
+   WcsMap axes found above. */
+   if( fits_ilon == -1 ) fits_ilon = axlon;
+   if( fits_ilat == -1 ) fits_ilat = axlat;
+
+/* If there is no longitude or latitude axis, or if we have a
+   non-celestial projection, just return a UnitMap. */
+   if( axlon == axlat || astGetWcsType( wcsmap ) == AST__WCSBAD ){
+      new = (AstMapping *) astUnitMap( naxis, "", status );
+
+/* If there is a lon/lat axis pair, create the inperm and outperm arrays
+   which will be needed later to create the PermMap which reorganises
+   the axes so that axis zero is the longitude axis and axis 1 is the
+   latitude axis. */
+   } else {
+
+/* Get storage for the two arrays. */
+      inperm = (int *) astMalloc( sizeof( int )*(size_t)naxis );
+      outperm = (int *) astMalloc( sizeof( int )*(size_t)naxis );
+      if( astOK ){
+
+/* Initialise an array holding the indices of the input axes which are copied
+   to each output axis. Initially assume that there is no re-arranging of
+   the axes. */
+         for( i = 0; i < naxis; i++ ) outperm[ i ] = i;
+
+/* Swap the longitude axis and axis 0. */
+         i = outperm[ axlon ];
+         outperm[ axlon ] = outperm[ 0 ];
+         outperm[ 0 ] = i;
+
+/* If axis 0 was originally the latitude axis, the latitude axis will now
+   be where the longitude axis was originally (because of the above axis
+   swap). */
+         if( axlat == 0 ) {
+            new_axlat = axlon;
+         } else {
+            new_axlat = axlat;
+         }
+
+/* Swap the latitude axis and axis 1. */
+         i = outperm[ new_axlat ];
+         outperm[ new_axlat ] = outperm[ 1 ];
+         outperm[ 1 ] = i;
+
+/* Create the array holding the output axis index corresponding to
+   each input axis. */
+         for( i = 0; i < naxis; i++ ) inperm[ outperm[ i ] ] = i;
+      }
+
+/* Store the latitude and longitude (in the standard system) of the fiducial
+   point, in radians. */
+      delta0 = GetItem( &(store->crval), fits_ilat, 0, s, NULL, method, class, status );
+      if( delta0 == AST__BAD ) delta0 = 0.0;
+      delta0 *= AST__DD2R;
+      alpha0 = GetItem( &(store->crval), fits_ilon, 0, s, NULL, method, class, status );
+      if( alpha0 == AST__BAD ) alpha0 = 0.0;
+      alpha0 *= AST__DD2R;
+
+/* Limit the latitude to the range +/- PI/2, issuing a warning if the
+   supplied CRVAL value is outside this range. The "alphap" variable is used
+   as workspace here. */
+      alphap = palDrange( delta0 );
+      delta0 = alphap;
+      if ( delta0 > AST__DPIBY2 ){
+         delta0 = AST__DPIBY2;
+      } else if ( delta0 < -AST__DPIBY2 ){
+         delta0 = -AST__DPIBY2;
+      }
+      if( alphap != delta0 ) {
+         sprintf( buf, "The original FITS header specified a fiducial "
+                  "point with latitude %.*g. A value of %.*g is being used "
+                  "instead. ", DBL_DIG, alphap*AST__DR2D, DBL_DIG,
+                  delta0*AST__DR2D );
+         Warn( this, "badlat", buf, method, class, status );
+      }
+
+/* Set a flag indicating if we have a TPN projection. The handling or
+   projection parameters  is different for TPN projections.  */
+      tpn = ( astGetWcsType( wcsmap ) == AST__TPN );
+
+/* Store the radian values of the FITS keywords LONPOLE and LATPOLE. Defaults
+   will be used if either of these items was not supplied. These keyword
+   values may be stored in projection parameters PVi_3a and PVi_4a for
+   longitude axis "i" - in which case the "PV" values take precedence over
+   the "LONPOLE" and "LATPOLE" values. Do not do this for TPN projections
+   since they use these projection parameters to specify correcton terms. */
+      if( astTestPV( wcsmap, axlon, 3 ) && !tpn ) {
+         phip = astGetPV( wcsmap, axlon, 3 );
+      } else {
+         phip = GetItem( &(store->lonpole), 0, 0, s, NULL, method, class, status );
+         if( phip != AST__BAD && !tpn ) astSetPV( wcsmap, axlon, 3, phip );
+      }
+      if( phip != AST__BAD ) phip *= AST__DD2R;
+      if( astTestPV( wcsmap, axlon, 4 ) && !tpn ) {
+         latpole = astGetPV( wcsmap, axlon, 4 );
+      } else {
+         latpole = GetItem( &(store->latpole), 0, 0, s, NULL, method, class, status );
+         if( latpole != AST__BAD && !tpn ) astSetPV( wcsmap, axlon, 4, latpole );
+      }
+      if( latpole != AST__BAD ) latpole *= AST__DD2R;
+
+/* Find the standard Celestial Coordinates of the north pole of the Native
+   Spherical Coordinate system. Report an error if the position was not
+   defined. */
+      if( !WcsNatPole( this, wcsmap, alpha0, delta0, latpole, &phip, &alphap,
+                       &deltap, status ) && astOK ){
+         astError( AST__BDFTS, "%s(%s): Conversion from FITS WCS native "
+                   "coordinates to celestial coordinates is ill-conditioned.", status,
+                   method, class );
+      }
+
+/* Create the SphMap which converts spherical coordinates to Cartesian
+   coordinates (stage 6 in the prologue). This asumes that axis 0 is the
+   longitude axis, and axis 1 is the latitude axis. This will be ensured
+   by a PermMap created later. Indicate that the SphMap will only be used
+   to transform points on a unit sphere. This enables a forward SphMap
+   to be combined with an inverse SphMap into a UnitMap, and thus aids
+   simplification. */
+      sphmap = astSphMap( "UnitRadius=1", status );
+      astInvert( sphmap );
+
+/* Set the PolarLong attribute of the SphMap so that a longitude of phi0 (the
+   native longitude of the fiducial point) is returned by the inverse
+   transformation (cartesian->spherical) at either pole. */
+      GetFiducialNSC( wcsmap, &phi0, &theta0, status );
+      astSetPolarLong( sphmap, phi0 );
+
+/* Create a unit MatrixMap to be the basis of the MatrixMap which rotates
+   Native Spherical Coords to Celestial Coords (stage 7 in the prologue). */
+      matmap = astMatrixMap( 3, 3, 2, NULL, "", status );
+
+/* Modify the above MatrixMap so that it rotates the Cartesian position vectors
+   by -phip (i.e. LONPOLE) about the Z axis. This puts the north pole of the
+   standard system at zero longitude in the rotated system. Then annul the
+   original MatrixMap and use the new one instead. */
+      axis[ 0 ] = 0;
+      axis[ 1 ] = 0;
+      axis[ 2 ] = 1;
+      matmap2 = astMtrRot( matmap, -phip, axis );
+      matmap = astAnnul( matmap );
+      matmap = matmap2;
+
+/* Now modify the above MatrixMap so that it rotates the Cartesian position
+   vectors by -(PI/2-deltap) about the Y axis. This puts the north pole of
+   the standard system as 90 degrees latitude in the rotated system. Then annul
+   the original MatrixMap and use the new one instead. */
+      axis[ 0 ] = 0;
+      axis[ 1 ] = 1;
+      axis[ 2 ] = 0;
+      matmap2 = astMtrRot( matmap, deltap - AST__DPIBY2, axis );
+      matmap = astAnnul( matmap );
+      matmap = matmap2;
+
+/* Finally modify the above MatrixMap so that it rotates the Cartesian position
+   vectors (PI+alphap) about the Z axis. This puts the primary meridian of the
+   standard system at zero longitude in the rotated system. This results in the
+   rotated system being coincident with the standard system. */
+      axis[ 0 ] = 0;
+      axis[ 1 ] = 0;
+      axis[ 2 ] = 1;
+      matmap2 = astMtrRot( matmap, AST__DPI + alphap, axis );
+      matmap = astAnnul( matmap );
+      matmap = matmap2;
+
+/* Combine the SphMap (stage 6) and MatrixMap (stage 7) in series. */
+      cmpmap = astCmpMap( sphmap, matmap, 1, "", status );
+      sphmap = astAnnul( sphmap );
+      matmap = astAnnul( matmap );
+
+/* Create a new SphMap which converts Cartesian coordinates to spherical
+   coordinates (stage 8 in the prologue). Indicate that the SphMap will
+   only be used to transform points on a unit sphere. */
+      sphmap = astSphMap( "UnitRadius=1", status );
+
+/* Set the PolarLong attribute of the SphMap so that a longitude of alpha0
+   (the celestial longitude of the fiducial point) is returned by the
+   forward transformation (cartesian->spherical) at either pole. */
+      astSetPolarLong( sphmap, alpha0 );
+
+/* Add it to the compound mapping. The CmpMap then corresponds to stage 4
+   in the prologue. Annul the constituent mappings. */
+      new = (AstMapping *) astCmpMap( cmpmap, sphmap, 1, "", status );
+      cmpmap = astAnnul( cmpmap );
+      sphmap = astAnnul( sphmap );
+
+/* If there are any remaining axes (i.e. axes which do not describe a
+   Celestial coordinate system), create a UnitMap which passes on their
+   values unchanged (stage 5 in the prologue), and add it the CmpMap,
+   putting it in parallel with the earlier mappings. The resulting CmpMap
+   then corresponds to stage 2 in the prologue. Note, the axis numbering
+   used by this UnitMap needs to take account of the fact that it is only
+   applied to the non-celestial axes. The axes are re-ordered by the
+   PermMap described at stage 1 in the prologue. */
+      nax_rem = naxis - 2;
+      if( nax_rem > 0 ){
+         unitmap = astUnitMap( nax_rem, "", status );
+         cmpmap = astCmpMap( new, unitmap, 0, "", status );
+         new = astAnnul( new );
+         unitmap = astAnnul( unitmap );
+         new = (AstMapping *) cmpmap;
+      }
+
+/* Now we need to ensure that axes 0 and 1 correspond to longitude and
+   latitude. If this is already the case, then the CmpMap can be returned
+   as it is. Otherwise, a PermMap needs to be created to rearrange the
+   axes. */
+      if( axlon != 0 || axlat != 1 ){
+
+/* Create the PermMap using the inperm and outperm arrays created earlier.
+   This is the mapping described as stage 1 in the prologue. */
+         permmap = astPermMap( naxis, inperm, naxis, outperm, NULL, "", status );
+
+/* Compound this PermMap and the CmpMap corresponding to stage 2 (created
+   earlier) in series. */
+         cmpmap = astCmpMap( permmap, new, 1, "", status );
+         new = astAnnul( new );
+         new = (AstMapping *) cmpmap;
+
+/* Now invert the PermMap, so that it re-arranges the axes back into
+   their original order. This is the mapping described as stage 3 in
+   the prologue. */
+         astInvert( permmap );
+
+/* And finally.... add this inverted PermMap onto the end of the CmpMap. */
+         cmpmap = astCmpMap( new, permmap, 1, "", status );
+         permmap = astAnnul( permmap );
+         new = astAnnul( new );
+         new = (AstMapping *) cmpmap;
+      }
+
+/* Free the temporary arrays. */
+      inperm = (int *) astFree( (void *) inperm );
+      outperm = (int *) astFree( (void *) outperm );
+   }
+
+/* If an error has occurred, attempt to annul the new CmpMap. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the CmpMap. */
+   return new;
+}
+
+static int WcsNatPole( AstFitsChan *this, AstWcsMap *wcsmap, double alpha0,
+                       double delta0, double latpole, double *phip,
+                       double *alphap, double *deltap, int *status ){
+
+/*
+*  Name:
+*     WcsNatPole
+
+*  Purpose:
+*     Find the celestial coordinates of the north pole of the Native Spherical
+*     Coordinate system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     int WcsNatPole( AstFitsChan *this, AstWcsMap *wcsmap, double alpha0,
+*                     double delta0, double latpole, double *phip,
+*                     double *alphap, double *deltap, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     The supplied WcsMap converts projected positions given in
+*     "Projection Plane Coords" to positions in the "Native Spherical
+*     Coordinate" system. This function finds the pole of this spherical
+*     coordinate system in terms of the standard celestial coordinate
+*     system to which the CRVALi, LONPOLE and LATPOLE keywords refer (this
+*     system should be identified by characters 5-8 of the CTYPEi
+*     keywords). It also supplies a default value for LONPOLE if no value
+*     has been supplied explicitly in the FITS header.
+*
+*     This function implements equations 8, 9 and 10 from the FITS-WCS paper
+*     II by Calabretta & Greisen (plus the associated treatment of special
+*     cases). The paper provides more detailed documentation for the
+*     mathematics implemented by this function.
+
+*  Parameters:
+*     this
+*        The FitsChan in which to store any warning cards. If NULL, no
+*        warnings are stored.
+*     wcsmap
+*        A mapping describing the deprojection being used (i.e. the
+*        mapping from Projection Plane Coords to Native Spherical Coords).
+*     alpha0
+*        The longitude of the fiducial point in the standard celestial
+*        coordinate frame (in radians). Note, this fiducial point does
+*        not necessarily correspond to the point given by keywords CRPIXj.
+*     delta0
+*        The celestial latitude (radians) of the fiducial point.
+*     latpole
+*        The value of FITS keyword LATPOLE, converted to radians, or the
+*        symbolic constant AST__BAD if the keyword was not supplied.
+*     phip
+*        Pointer to a location at which is stored the longitude of the north
+*        pole of the standard Celestial coordinate system, as measured in
+*        the Native Spherical Coordinate system, in radians. This should be
+*        supplied holding the radian equivalent of the value of the FITS
+*        keyword LONPOLE, or the symbolic constant AST__BAD if the keyword was
+*        not supplied (in which case a default value will be returned at the
+*        given location).
+*     alphap
+*        Pointer to a location at which to store the calculated longitude
+*        of the Native North Pole, in radians.
+*     deltap
+*        Pointer to a location at which to store the calculated latitude
+*        of the Native North Pole, in radians.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A status: non-zero for success, zero if the position of the native
+*     north pole is undefined.
+
+*  Notes:
+*     -  Certain combinations of keyword values result in the latitude of
+*     the Native North Pole being undefined. In these cases, a value of
+*     0 is returned for the function value, but no error is reported.
+*     -  All angular values used by this function are in radians.
+*     -  A value of 0 is returned if an error has already occurred.
+*/
+
+/* Local Variables: */
+   char buf[150];                  /* Buffer for warning message */
+   double cos_theta0;              /* Cosine of theta0 */
+   double cos_phip;                /* Cosine of (phip - phi0) */
+   double cos_delta0;              /* Cosine of delta0 */
+   double cos_deltap;              /* Cosine of deltap */
+   double deltap_1;                /* First possible value for deltap */
+   double deltap_2;                /* Second possible value for deltap */
+   double sin_theta0;              /* Sine of theta0 */
+   double sin_phip;                /* Sine of (phip - phi0) */
+   double sin_delta0;              /* Sine of delta0 */
+   double sin_deltap;              /* Sine of deltap */
+   double t0, t1, t2, t3, t4;      /* Intermediate values */
+   double phi0, theta0;            /* Native coords of fiducial point */
+
+/* Check the global status. */
+   if ( !astOK ) return 0;
+
+/* Get the longitude and latitude of the fiducial point in the native
+   spherical coordinate frame (in radians). */
+   GetFiducialNSC( wcsmap, &phi0, &theta0, status );
+
+/* If no value was supplied for the FITS keyword LONPOLE, set up a default
+   value such that the celestial latitude increases in the same direction
+   as the native latitude at the fiducial; point. */
+   if( *phip == AST__BAD ){
+      if( delta0 >= theta0 ){
+         *phip = 0.0;
+      } else {
+         *phip = AST__DPI;
+      }
+
+/* Issue a warning that a default lonpole value has been adopted. */
+      sprintf( buf, "The original FITS header did not specify the "
+               "longitude of the native north pole. A default value "
+               "of %.8g degrees was assumed.", (*phip)*AST__DR2D );
+      Warn( this, "nolonpole", buf, "astRead", "FitsChan", status );
+   }
+
+/* If the fiducial point is coincident with the Native North Pole, then the
+   Native North Pole must have the same coordinates as the fiducial
+   point. Tests for equality include some tolerance to allow for rounding
+   errors. */
+   sin_theta0 = sin( theta0 );
+   if( astEQUAL( sin_theta0, 1.0 ) ){
+      *alphap = alpha0;
+      *deltap = delta0;
+
+/* If the fiducial point is concident with the Native South Pole, then the
+   Native North Pole must have the coordinates of the point diametrically
+   opposite the fiducial point. */
+   } else if( astEQUAL( sin_theta0, -1.0 ) ){
+      *alphap = alpha0 + AST__DPI;
+      *deltap = -delta0;
+
+/* For all other cases, go through the procedure described in the WCS paper
+   by Greisen & Calabretta, to find the position of the Native North Pole.
+   First store some useful values. */
+   } else {
+      cos_theta0 = cos( theta0 );
+      cos_delta0 = cos( delta0 );
+      cos_phip = cos( *phip - phi0 );
+      sin_delta0 = sin( delta0 );
+      sin_phip = sin( *phip - phi0 );
+
+/* Next, find the two possible values for the latitude of the Native
+   North Pole (deltap). If any stage of this transformation is
+   indeterminate, return zero (except for the single special case noted
+   in item 6 para. 2 of the WCS paper, for which LATPOLE specifies the
+   values to be used). */
+      t0 = cos_theta0*cos_phip;
+      if( fabs( t0 ) < TOL2 && fabs( sin_theta0 ) < TOL2 ){
+         if( latpole != AST__BAD ) {
+            *deltap = latpole;
+         } else {
+            return 0;
+         }
+      } else {
+         t1 = atan2( sin_theta0, t0 );
+         t2 = cos_theta0*cos_phip;
+         t2 *= t2;
+         t2 += sin_theta0*sin_theta0;
+         if( t2 <= DBL_MIN ){
+            return 0;
+         } else {
+            t3 = sin_delta0/sqrt( t2 );
+            if( fabs( t3 ) > 1.0 + TOL1 ){
+               return 0;
+            } else {
+               if( t3 < -1.0 ){
+                  t4 = AST__DPI;
+               } else if( t3 > 1.0 ){
+                  t4 = 0.0;
+               } else {
+                  t4 = acos( t3 );
+               }
+               deltap_1 = palDrange( t1 + t4 );
+               deltap_2 = palDrange( t1 - t4 );
+
+/* Select which of these two values of deltap to use. Values outside the
+   range +/- PI/2 cannot be used. If both values are within this range
+   use the value which is closest to the supplied value of latpole (or
+   use the northern most value if the LATPOLE keyword was not supplied. */
+               if( fabs( deltap_1 ) > AST__DPIBY2 + TOL2 ){
+                  *deltap = deltap_2;
+               } else if( fabs( deltap_2 ) > AST__DPIBY2 + TOL2 ){
+                  *deltap = deltap_1;
+               } else {
+                  if( latpole != AST__BAD ){
+                     if( fabs( deltap_1 - latpole ) <
+                         fabs( deltap_2 - latpole ) ){
+                        *deltap = deltap_1;
+                     } else {
+                        *deltap = deltap_2;
+                     }
+                  } else {
+                     if( deltap_1 > deltap_2 ){
+                        *deltap = deltap_1;
+                     } else {
+                        *deltap = deltap_2;
+                     }
+
+/* Issue a warning that a default latpole value has been adopted. */
+                     sprintf( buf, "The original FITS header did not specify "
+                              "the latitude of the native north pole. A "
+                              "default value of %.8g degrees was assumed.",
+                              (*deltap)*AST__DR2D );
+                     Warn( this, "nolatpole", buf, "astRead", "FitsChan", status );
+                  }
+               }
+               if( fabs( *deltap  ) > AST__DPIBY2 + TOL2 ) {
+                  return 0;
+               } else if( *deltap < -AST__DPIBY2 ){
+                  *deltap = -AST__DPIBY2;
+               } else if( *deltap > AST__DPIBY2 ){
+                  *deltap = AST__DPIBY2;
+               }
+            }
+         }
+      }
+
+/* If a valid value for the latitude (deltap) has been found, find the
+   longitude of the Native North Pole. */
+      if( *deltap != AST__BAD ) {
+         if( fabs( cos_delta0) > TOL2 ){
+            cos_deltap = cos( *deltap );
+            sin_deltap = sin( *deltap );
+            if( fabs( cos_deltap ) > TOL2 ){
+               t1 = sin_phip*cos_theta0/cos_delta0;
+               t2 = ( sin_theta0 - sin_deltap*sin_delta0 )
+                    /( cos_delta0*cos_deltap );
+               if( ( fabs( t1 ) > TOL2 ) || ( fabs( t2 ) > TOL2 ) ){
+                  *alphap = alpha0 - atan2( t1, t2 );
+               } else {
+                  *alphap = alpha0;
+               }
+            } else if( sin_deltap > 0.0 ){
+               *alphap = alpha0 + (*phip - phi0) - AST__DPI;
+            } else {
+               *alphap = alpha0 - (*phip - phi0);
+            }
+         } else {
+            *alphap = alpha0;
+         }
+      } else {
+         *alphap = AST__BAD;
+      }
+   }
+
+/* Return a success status if valid latitude and longitude values were
+   found. */
+   return (*deltap) != AST__BAD && (*alphap) != AST__BAD ;
+}
+
+static AstMapping *WcsOthers( AstFitsChan *this, FitsStore *store, char s,
+                              AstFrame **frm,  AstFrame *iwcfrm, const char *method,
+                              const char *class, int *status ){
+
+/*
+*  Name:
+*     WcsOthers
+
+*  Purpose:
+*     Create a Mapping from intermediate world coords to any axes
+*     which are not covered by specialised conventions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     AstMapping *WcsOthers( AstFitsChan *this, FitsStore *store, char s,
+*                            AstFrame **frm,  AstFrame *iwcfrm, const char *method,
+*                            const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function interprets the contents of the supplied FitsStore
+*     structure, looking for world coordinate axes for which no
+*     description has yet been added to the supplied Frame . It is
+*     assumed that any such axes are simple linear axes. It returns a
+*     Mapping which simply adds on the CRVAL values to such axes.
+*     It also modifies the supplied Frame to describe the axes.
+
+*  Parameters:
+*     this
+*        The FitsChan.
+*     store
+*        A structure containing information about the requested axis
+*        descriptions derived from a FITS header.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     frm
+*        The address of a location at which to store a pointer to the
+*        Frame describing the world coordinate axes.
+*     iwcfrm
+*        A pointer to the Frame describing the intermediate world coordinate
+*        axes. The properties of this Frame may be changed on exit.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the Mapping.
+*/
+
+/* Local Variables: */
+   AstFrame *pfrm;           /* Pointer to primary Frame */
+   AstFrame *pfrm2;          /* Pointer to primary Frame */
+   AstMapping *map1;         /* Pointer to a Mapping */
+   AstMapping *map2;         /* Pointer to a Mapping */
+   AstMapping *ret;          /* The returned Mapping */
+   char **comms;             /* Pointer to array of CTYPE commments */
+   char buf[ 100 ];          /* Buffer for textual attribute value */
+   char buf2[ 100 ];         /* Buffer for textual attribute value */
+   char buf3[ 20 ];          /* Buffer for default CTYPE value */
+   char *newdom;             /* Pointer to new Domain value */
+   const char *ckeyval;      /* Pointer to character keyword value */
+   int i;                    /* Axis index */
+   int j;                    /* Axis index */
+   int len;                  /* Used length of string */
+   int naxes;                /* no. of axes in Frame */
+   int nother;               /* The number of "other" axes */
+   int paxis;                /* Primary axis index */
+   int usecom;               /* Use CTYPE comments as axis Labels? */
+
+/* Initialise the pointer to the returned Mapping. */
+   ret = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return ret;
+
+/* Get the number of physical axes. */
+   naxes = astGetNaxes( *frm );
+
+/* Assume we will use CTYPE comments as the axis labels. */
+   usecom = 1;
+
+/* Initialise the count of "other" axes. */
+   nother = 0;
+
+/* Get the comments associated with the CTYPE keywords for all "other"
+   axes. */
+   comms = astMalloc( naxes*sizeof( char * ) );
+   if( comms ) {
+
+/* Loop round all axes in the Frame, and initialise the pointer to its
+   comment. */
+      for( i = 0; i < naxes; i++ ){
+         comms[ i ] = NULL;
+
+/* Get the Domain for the primary frame containing the axis. This will be
+   "AST_FITSCHAN" if the axis has not yet been recognised (this Domain is
+   set up by WcsMapFrm). Only consider the axis further if the Domain has
+   not been changed. */
+         astPrimaryFrame( *frm, i, &pfrm, &paxis );
+         if( !strcmp( astGetDomain( pfrm ), "AST_FITSCHAN" ) ) {
+
+/* Increment the count of "other" axes. */
+            nother++;
+
+/* Get the comment associated with the CTYPE header. */
+            ckeyval = GetItemC( &(store->ctype_com), i, 0, s, NULL, method, class, status );
+
+/* If this axis has no CTYPE comment, we will use CTYPE values as axis
+   labels (if given, the CNAME keyword take precedence). */
+            if( !ckeyval || astChrLen( ckeyval ) == 0  ) {
+               usecom = 0;
+
+/* If the CTYPE comment for this axis is the same as any other comment, we
+   will use CTYPE values as axis labels. */
+            } else {
+               for( j = 0; j < nother - 1; j++ ) {
+                  if( comms[ j ]  && !strcmp( ckeyval, comms[ j ] ) ) {
+                     usecom = 0;
+                     break;
+                  }
+               }
+            }
+
+/* If we are still using comments as axis labels, store a copy of it in the
+   workspace. */
+            if( usecom ) comms[ i ] = astStore( NULL, ckeyval,
+                                                strlen( ckeyval ) + 1 );
+         }
+         pfrm = astAnnul( pfrm );
+      }
+
+/* Free the workspace holding comments. */
+      for( i = 0; i < naxes; i++ ) comms[ i ] = astFree( comms[ i ] );
+      comms = astFree( comms );
+   }
+
+/* If there are no "other" axes, just return a UnitMap. */
+   if( nother == 0 ) {
+      ret = (AstMapping *) astUnitMap( naxes, "", status );
+
+/* Otherwise... */
+   } else {
+
+/* If we have only a single other axis, use CTYPE value instead of
+   comment. */
+      if( nother == 1 ) usecom = 0;
+
+/* Not yet started a new Domain value to replace "AST_FITSCHAN". */
+      newdom = NULL;
+      pfrm2 = NULL;
+
+/* Check each axis of the Frame looking for axes which have not yet been
+   recognised. */
+      for( i = 0; i < naxes; i++ ) {
+
+/* Get the Domain for the primary frame containing the axis. This will be
+   "AST_FITSCHAN" if the axis has not yet been recognised (this Domain is
+   set up by WcsMapFrm). Only consider the axis further if the Domain has
+   not been changed. */
+         astPrimaryFrame( *frm, i, &pfrm, &paxis );
+         if( !strcmp( astGetDomain( pfrm ), "AST_FITSCHAN" ) ) {
+
+/* Save a pointer to the primary Frame which we will use to set the
+   Domain of the primary Frame. */
+            if( !pfrm2 ) pfrm2 = astClone( pfrm );
+
+/* Get the CTYPE value. Use a default of "AXISn". */
+            ckeyval = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+            if( !ckeyval ) {
+               sprintf( buf3, "AXIS%d", i + 1 );
+               ckeyval = buf3;
+            }
+
+/* If the CTYPE value ends with "-LOG", assume it is a logarithmically spaced
+   axis. Get the Mapping from IWC to WCS. Reduce the used length of the
+   CTYPE string to exlude any trailing "-LOG" string. */
+            len = strlen( ckeyval );
+            if( len > 3 && !strcmp( ckeyval + len - 4, "-LOG" ) ){
+               map1 = LogWcs( store, i, s, method, class, status );
+               sprintf( buf2, "%.*s", len - 4, ckeyval );
+
+/* Otherwise, assume the axis is linearly spaced. */
+            } else {
+               map1 = LinearWcs( store, i, s, method, class, status );
+               sprintf( buf2, "%.*s", len, ckeyval );
+            }
+
+/* Append the CTYPE value to the final Domain value for the primary Frame. */
+            if( ckeyval && astChrLen( ckeyval ) > 0 ) {
+               if( newdom ) {
+                  sprintf( buf, "%s-%s", newdom, buf2 );
+               } else {
+                  sprintf( buf, "%s", buf2 );
+                  newdom = buf;
+               }
+            }
+
+/* Now modify the axis in the Frame to have appropriate values for the
+   Unit, Label and Symbol attributes. Also set the Unit attribute for
+   the corresponding axis in the IWC Frame. */
+            if( ckeyval ) astSetSymbol( *frm, i, buf2 );
+            ckeyval = GetItemC( &(store->cname), i, 0, s, NULL, method, class, status );
+            if( !ckeyval && usecom ) ckeyval = GetItemC( &(store->ctype_com),
+                                                   i, 0, s, NULL, method, class, status );
+            if( !ckeyval ) ckeyval = buf2;
+            if( ckeyval ) astSetLabel( *frm, i, ckeyval );
+            ckeyval = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
+            if( ckeyval ) {
+               astSetUnit( *frm, i, ckeyval );
+               astSetUnit( iwcfrm, i, ckeyval );
+            }
+
+/* If this axis has been described by an earlier function (because it
+   uses specialised conventions such as those described in FITS-WCS papers
+   II or III), then create a UnitMap for this axis. */
+         } else {
+            map1 = (AstMapping *) astUnitMap( 1, "", status );
+         }
+
+/* Annul the pointer to the primary Frame containing the current axis. */
+         pfrm = astAnnul( pfrm );
+
+/* Add the Mapping for this axis in parallel with the current "running sum"
+   Mapping (if any). */
+         if( ret ) {
+            map2 = (AstMapping *) astCmpMap( ret, map1, 0, "", status );
+            ret = astAnnul( ret );
+            map1 = astAnnul( map1 );
+            ret = map2;
+         } else {
+            ret = map1;
+         }
+      }
+
+/* Set the Domain name for the primary Frame. It is currently set to
+   AST_FITSCHAN. We replace it with a value formed by concatenating the
+   CTYPE values of its axes. */
+      if( pfrm2 ) {
+         if( newdom && astChrLen( newdom ) > 0 ) {
+            astSetDomain( pfrm2, newdom );
+         } else {
+            astClearDomain( pfrm2 );
+         }
+         pfrm2 = astAnnul( pfrm2 );
+      }
+
+/* If the header contained a WCSNAME keyword, use it as the Domain name for
+   the Frame. Also use it to create a title. */
+      ckeyval = GetItemC( &(store->wcsname), 0, 0, s, NULL, method, class, status );
+      if( ckeyval ){
+         astSetDomain( *frm, ckeyval );
+         sprintf( buf, "%s coordinates", ckeyval );
+         astSetTitle( *frm, buf );
+      }
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static AstWinMap *WcsShift( FitsStore *store, char s, int naxes,
+                            const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     WcsShift
+
+*  Purpose:
+*     Create a WinMap which shifts pixels coordinates so that their origin
+*     is at the reference pixel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     AstWinMap *WcsShift( FitsStore *store, char s, int naxes,
+*                          const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     A WinMap is created which implements a shift of origin by subtracting
+*     the reference pixel coordinates (CRPIXi) from the input pixel
+*     coordinates.
+
+*  Parameters:
+*     store
+*        A structure containing values for FITS keywords relating to
+*        the World Coordinate System.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     naxes
+*        The number of intermediate world coordinate axes (WCSAXES).
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the created WinMap or a NULL pointer if an
+*     error occurred.
+
+*  Notes:
+*     -  If an error occurs, a NULL pointer is returned.
+*/
+
+/* Local Variables: */
+   AstWinMap *new;                 /* The created WinMap */
+   int j;                          /* Pixel axis index */
+   double crpix;                   /* CRPIX keyword value */
+   double *c1_in;                  /* Input corner 1 */
+   double *c2_in;                  /* Input corner 2 */
+   double *c1_out;                 /* Output corner 1 */
+   double *c2_out;                 /* Output corner 2 */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the returned WinMap pointer. */
+   new = NULL;
+
+/* Allocate memory to hold the two corners, in both input and output
+   coordinates. */
+   c1_in = (double *) astMalloc( sizeof( double )*(size_t) naxes );
+   c1_out = (double *) astMalloc( sizeof( double )*(size_t) naxes );
+   c2_in = (double *) astMalloc( sizeof( double )*(size_t) naxes );
+   c2_out = (double *) astMalloc( sizeof( double )*(size_t) naxes );
+
+/* Check these pointers can be used. */
+   if( astOK ){
+
+/* Set up two arbitrary corners in the input coordinate system, and the
+   corresponding values with the CRPIX values subtracted off. */
+      for( j = 0; j < naxes; j++ ){
+
+/* Get the CRPIX value for this axis. */
+         crpix = GetItem( &(store->crpix), 0, j, s, NULL, method, class, status );
+         if( crpix == AST__BAD ) crpix = 0.0;
+
+/* Store the corner co-ordinates. */
+         c1_in[ j ] = 0.0;
+         c2_in[ j ] = 1.0;
+         c1_out[ j ] = -crpix;
+         c2_out[ j ] = 1.0 - crpix;
+      }
+
+/* Create the WinMap. */
+      new = astWinMap( naxes, c1_in, c2_in, c1_out, c2_out, "", status );
+
+/* If an error has occurred, attempt to annul the new WinMap. */
+      if( !astOK ) new = astAnnul( new );
+   }
+
+/* Free the memory holding the corners. */
+   c1_in = (double *) astFree( (void *) c1_in );
+   c1_out = (double *) astFree( (void *) c1_out );
+   c2_in = (double *) astFree( (void *) c2_in );
+   c2_out = (double *) astFree( (void *) c2_out );
+
+/* Return the WinMap. */
+   return new;
+}
+
+static AstSkyFrame *WcsSkyFrame( AstFitsChan *this, FitsStore *store, char s,
+                                 int prj, char *sys, int axlon, int axlat,
+                                 const char *method, const char *class, int *status ){
+
+/*
+*  Name:
+*     WcsSkyFrame
+
+*  Purpose:
+*     Create a SkyFrame to describe a WCS celestial coordinate system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     AstSkyFrame *WcsSkyFrame( AstFitsChan this, FitsStore *store, char s, int prj,
+*                               char *sys, int axlon, int axlat, const char *method,
+*                               const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A SkyFrame is returned describing the celestial coordinate system
+*     described by a FITS header. The axes are *not* permuted in the
+*     returned Frame (that is, axis 0 is longitude and axis 1 is latitude
+*     in the returned SkyFrame, no matter what values are supplied for
+*     "axlat" and "axlon").
+
+*  Parameters:
+*     this
+*        The FitsChan from which the keywords were read. Warning messages
+*        may be added to this FitsChan.
+*     store
+*        A structure containing values for FITS keywords relating to
+*        the World Coordinate System.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     prj
+*        An integer code for the WCS projection being used.
+*     sys
+*        A pointer to a string identifying the celestial co-ordinate system
+*        implied by the CTYPE values in the FitsStore. This will be "EQU" (for
+*        equatorial), or a one or two character code extracted from the
+*        CTYPE values.
+*     axlon
+*        Zero based index of the longitude axis in the FITS header.
+*     axlat
+*        Zero based index of the latitude axis in the FITS header.
+*     method
+*        The calling method. Used only in error messages.
+*     class
+*        The object class. Used only in error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the SkyFrame.
+
+*  Notes:
+*     -  A NULL pointer is returned if an error has already occurred, or
+*     if this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *ret;              /* Returned Frame */
+   char *ckeyval;                 /* Pointer to string item value */
+   char *lattype;                 /* Pointer to latitude CTYPE value */
+   char *lontype;                 /* Pointer to longitude CTYPE value */
+   char bj;                       /* Besselian/Julian selector */
+   char buf[300];                 /* Text buffer */
+   char sym[10];                  /* Axis symbol */
+   double dval;                   /* Floating point attribute value */
+   double eqmjd;                  /* MJD equivalent of equinox */
+   double equinox;                /* EQUINOX value */
+   double geolat;                 /* Observer's geodetic latitude */
+   double geolon;                 /* Observer's geodetic longitude */
+   double h;                      /* Observer's geodetic height */
+   double mjdobs;                 /* MJD-OBS value */
+   double obsgeo[ 3 ];            /* Observer's Cartesian position */
+   int radesys;                   /* RADESYS value */
+   int report;                    /* Report unknown lon/lat system? */
+
+/* Initialise. */
+   ret = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Get the RADESYS keyword from the header, and identify the value.
+   Store a integer value identifying the system. Report an error if an
+   unrecognised system is supplied. Store NORADEC if the keyword was
+   not supplied. */
+   ckeyval = GetItemC( &(store->radesys), 0, 0, s, NULL, method, class, status );
+   radesys = NORADEC;
+   if( ckeyval ){
+      if( !strncmp( ckeyval, "FK4 ", 4 ) ||
+          !strcmp( ckeyval, "FK4" ) ){
+         radesys = FK4;
+      } else if( !strncmp( ckeyval, "FK4-NO-E", 8 ) ){
+         radesys = FK4NOE;
+      } else if( !strncmp( ckeyval, "FK5 ", 4 ) ||
+                 !strcmp( ckeyval, "FK5" ) ){
+         radesys = FK5;
+      } else if( !strncmp( ckeyval, "ICRS ", 5 ) ||
+                 !strcmp( ckeyval, "ICRS" ) ){
+         radesys = ICRS;
+      } else if( !strncmp( ckeyval, "GAPPT ", 6 ) ||
+                 !strcmp( ckeyval, "GAPPT" ) ){
+         radesys = GAPPT;
+      } else if( astOK ){
+         astError( AST__BDFTS, "%s(%s): FITS keyword '%s' has the "
+                   "unrecognised value '%s'.", status, method, class,
+                   FormatKey( "RADESYS", -1, -1, s, status ), ckeyval );
+      }
+   } else {
+      radesys = NORADEC;
+   }
+
+/* Get the value of the EQUINOX keyword. */
+   equinox = GetItem( &(store->equinox), 0, 0, s, NULL, method, class, status );
+
+/* For FK4 and FK4-NO-E any supplied equinox value is Besselian. For all
+   other systems, the equinox value is Julian. */
+   bj = 0;
+   if( equinox != AST__BAD ){
+      if( radesys == FK4 || radesys == FK4NOE ){
+         bj = 'B';
+      } else if( radesys != NORADEC ) {
+         bj = 'J';
+
+/* If no RADESYS was suppied, but an equinox was, use the IAU 1984 rule
+   to determine the default RADESYS and equinox type. */
+      } else {
+         if( equinox < 1984.0 ){
+            radesys = FK4;
+            bj = 'B';
+         } else {
+            radesys = FK5;
+            bj = 'J';
+         }
+
+/* If an equatorial system is being used, give a warning that a default RADESYS
+   value is being used. */
+         if( !strcmp( sys, "EQU" ) ){
+            sprintf( buf, "The original FITS header did not specify the "
+                     "RA/DEC reference frame. A default value of %s was "
+                     "assumed.", ( radesys == FK4 ) ? "FK4" : "FK5" );
+            Warn( this, "noradesys", buf, method, class, status );
+         }
+      }
+
+/* If no equinox was supplied, use a default equinox value depending
+   on the frame of reference. For FK4-based systems, use B1950. */
+   } else {
+      if( radesys == FK4 || radesys == FK4NOE ){
+         equinox = 1950.0;
+         bj = 'B';
+
+/* For FK5-based systems, use J2000. */
+      } else if( radesys == FK5 ){
+         equinox = 2000.0;
+         bj = 'J';
+
+/* If no RADESYS or EQUINOX was supplied, assume either FK4 B1950 or ICRS -
+   as decided by attribute DefB1950 (GAPPT and ICRS do not use EQUINOX). */
+      } else if( radesys == NORADEC ) {
+         if( astGetDefB1950( this ) ) {
+            equinox = 1950.0;
+            bj = 'B';
+            radesys = FK4;
+         } else {
+            radesys = ICRS;
+         }
+         if( !strcmp( sys, "EQU" ) ){
+            sprintf( buf, "The original FITS header did not specify the "
+                     "RA/DEC reference frame. A default value of %s was "
+                     "assumed.", ( radesys == FK4 ) ? "FK4" : "ICRS" );
+            Warn( this, "noradesys", buf, method, class, status );
+         }
+      }
+
+/* If we have an equatorial or ecliptic system, issue a warning that a default
+   equinox has been adopted. */
+      if( ( !strcmp( sys, "EQU" ) && radesys != ICRS && radesys != GAPPT ) ||
+          !strcmp( sys, "ECL" ) ){
+         sprintf( buf, "The original FITS header did not specify the "
+                  "reference equinox. A default value of %c%.8g was "
+                  "assumed.", bj, equinox );
+         Warn( this, "noequinox", buf, method, class, status );
+      }
+   }
+
+/* Convert the equinox to a Modified Julian Date. */
+   if( equinox != AST__BAD ) {
+      if( bj == 'B' ) {
+         eqmjd = palEpb2d( equinox );
+      } else {
+         eqmjd = palEpj2d( equinox );
+      }
+   } else {
+      eqmjd = AST__BAD;
+   }
+
+/* Get a value for the Epoch attribute. If no value is available, use
+   EQUINOX and issue a warning. */
+   mjdobs = ChooseEpoch( this, store, s, method, class, status );
+   if( mjdobs == AST__BAD ) {
+      mjdobs = eqmjd;
+      if( mjdobs != AST__BAD ) {
+         sprintf( buf, "The original FITS header did not specify the "
+                  "date of observation. A default value of %c%.8g was "
+                  "assumed.", bj, equinox );
+         Warn( this, "nomjd-obs", buf, method, class, status );
+      }
+   }
+
+/* Create a SkyFrame for the specified system. */
+   if( !strcmp( sys, "E" ) ){
+      ret = astSkyFrame( "System=Ecliptic", status );
+   } else if( !strcmp( sys, "H" ) ){
+      ret = astSkyFrame( "System=Helioecliptic", status );
+   } else if( !(strcmp( sys, "G" ) ) ){
+      ret = astSkyFrame( "System=Galactic", status );
+   } else if( !(strcmp( sys, "S" ) ) ){
+      ret = astSkyFrame( "System=Supergalactic", status );
+   } else if( !(strcmp( sys, "AZL" ) ) ){
+      ret = astSkyFrame( "System=AzEl", status );
+   } else if( !(strcmp( sys, "EQU" ) ) ){
+
+/* For equatorial systems, the specific system is given by the RADESYS
+   value. */
+      if( radesys == FK4 ){
+         ret = astSkyFrame( "System=FK4", status );
+      } else if( radesys == FK4NOE ){
+         ret = astSkyFrame( "System=FK4-NO-E", status );
+      } else if( radesys == FK5 ){
+         ret = astSkyFrame( "System=FK5", status );
+      } else if( radesys == ICRS ){
+         ret = astSkyFrame( "System=ICRS", status );
+      } else if( radesys == GAPPT ){
+         ret = astSkyFrame( "System=GAPPT", status );
+      } else if( astOK ){
+         astError( AST__INTER, "%s(%s): Internal AST programming "
+                   "error - FITS equatorial coordinate system type %d "
+                   "not yet supported in WcsSkyFrame.", status, method, class, radesys );
+      }
+
+/* If an unknown celestial co-ordinate system was specified by the CTYPE
+   keywords, add warning messages to the FitsChan and treat the axes as
+   a general spherical coordinate system. */
+   } else if( astOK ){
+      report = 1;
+      ret = astSkyFrame( "System=UNKNOWN", status );
+      strcpy( sym, sys );
+      if( strlen( sys ) == 1 ) {
+         strcpy( sym + 1, "LON" );
+         astSetSymbol( ret, 0, sym );
+         strcpy( sym + 1, "LAT" );
+         astSetSymbol( ret, 1, sym );
+      } else {
+         strcpy( sym + 2, "LN" );
+         astSetSymbol( ret, 0, sym );
+         strcpy( sym + 2, "LT" );
+         astSetSymbol( ret, 1, sym );
+
+/* The code "OF" is used by AST to describe offset sky coordinates. Set
+   the Domain to SKY_OFFSETS in these cases, so that we can identify
+   these Frames later. */
+         if( !strcmp( sys, "OF" ) ) {
+            astSetDomain( ret, "SKY_OFFSETS" );
+            report = 0;
+         }
+      }
+
+      if( report ) {
+         lontype = GetItemC( &(store->ctype), axlon, 0, s, NULL, method, class, status );
+         lattype = GetItemC( &(store->ctype), axlat, 0, s, NULL, method, class, status );
+         if( lontype && lattype ){
+            sprintf( buf, "This FITS header contains references to an unknown "
+                     "spherical co-ordinate system specified in the values "
+                     "%s and %s. It may not be possible to convert to "
+                     "other standard co-ordinate systems.", lontype, lattype );
+            Warn( this, "badcel", buf, method, class, status );
+         }
+      }
+   }
+
+/* If a skyFrame was created... */
+   if( ret ){
+
+/* Store the projection description. */
+      if( prj != AST__WCSBAD ) astSetProjection( ret, astWcsPrjDesc( prj )  );
+
+/* Store the epoch of the observation in the SkyFrame. */
+      if( mjdobs != AST__BAD ) astSetEpoch( ret, mjdobs );
+
+/* For equatorial and ecliptic systems, store the epoch of the reference
+   equinox in the SkyFrame. */
+      if( ( !strcmp( sys, "EQU" ) || !strcmp( sys, "ECL" ) ) &&
+          equinox != AST__BAD ) astSetEquinox( ret, eqmjd );
+
+/* If either of the CNAME keywords is set, use it as the axis label. */
+      ckeyval = GetItemC( &(store->cname), axlon, 0, s, NULL, method, class, status );
+      if( ckeyval ) astSetLabel( ret, 0, ckeyval );
+      ckeyval = GetItemC( &(store->cname), axlat, 0, s, NULL, method, class, status );
+      if( ckeyval ) astSetLabel( ret, 1, ckeyval );
+
+/* Observer's position (from primary axis descriptions). Get the OBSGEO-X/Y/Z
+   keywords, convert to geodetic longitude and latitude and store as the
+   SpecFrame's ObsLat, ObsLon and ObsAlt attributes. */
+      obsgeo[ 0 ] = GetItem( &(store->obsgeox), 0, 0, ' ', NULL, method, class, status );
+      obsgeo[ 1 ] = GetItem( &(store->obsgeoy), 0, 0, ' ', NULL, method, class, status );
+      obsgeo[ 2 ] = GetItem( &(store->obsgeoz), 0, 0, ' ', NULL, method, class, status );
+      if( obsgeo[ 0 ] != AST__BAD &&
+          obsgeo[ 1 ] != AST__BAD &&
+          obsgeo[ 2 ] != AST__BAD ) {
+         eraGc2gd( 1, obsgeo, &geolon, &geolat, &h );
+         astSetObsLat( ret, geolat );
+         astSetObsLon( ret, geolon );
+         astSetObsAlt( ret, h );
+      }
+
+/* Store values for the reference point in the SkyFrame. */
+      dval = GetItem( &(store->skyref), axlon, 0, s, NULL, method, class, status );
+      if( dval != AST__BAD ) astSetSkyRef( ret, 0, dval );
+      dval = GetItem( &(store->skyref), axlat, 0, s, NULL, method, class, status );
+      if( dval != AST__BAD ) astSetSkyRef( ret, 1, dval );
+
+      dval = GetItem( &(store->skyrefp), axlon, 0, s, NULL, method, class, status );
+      if( dval != AST__BAD ) astSetSkyRefP( ret, 0, dval );
+      dval = GetItem( &(store->skyrefp), axlat, 0, s, NULL, method, class, status );
+      if( dval != AST__BAD ) astSetSkyRefP( ret, 1, dval );
+
+/* We cannot store the SkyRefIs value yet since this needs to be done
+   after the SkyFrame has been added into the FrameSet, so that the Frame
+   will be remapped to represent the intended offsets. SO instance, mark
+   the Frame by setting the domain to "SKY_POLE" or "SKY_ORIGIN". This
+   odd Domain value will be cleared later in TidyOffsets. */
+      ckeyval = GetItemC( &(store->skyrefis), 0, 0, s, NULL, method, class, status );
+      if( ckeyval ) {
+         if( !Ustrcmp( "POLE", ckeyval, status ) ) {
+            astSetDomain( ret, "SKY_POLE" );
+         } else if( !Ustrcmp( "ORIGIN", ckeyval, status ) ) {
+            astSetDomain( ret, "SKY_ORIGIN" );
+         }
+      }
+   }
+
+/* If an error has occurred, annul the Frame. */
+   if( !astOK ) ret = astAnnul( ret );
+
+/* Return the Frame. */
+   return ret;
+}
+
+static AstMapping *WcsSpectral( AstFitsChan *this, FitsStore *store, char s,
+                                AstFrame **frm, AstFrame *iwcfrm, double reflon, double reflat,
+                                AstSkyFrame *reffrm, const char *method,
+                                const char *class, int *status ){
+
+/*
+*  Name:
+*     WcsSpectral
+
+*  Purpose:
+*     Create a Mapping from intermediate world coords to spectral coords
+*     as described in a FITS header.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+
+*     AstMapping *WcsSpectral( AstFitsChan *this, FitsStore *store, char s,
+*                              AstFrame **frm, AstFrame *iwcfrm, double reflon,
+*                              double reflat, AstSkyFrame *reffrm,
+*                              const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function interprets the contents of the supplied FitsStore
+*     structure, looking for world coordinate axes which describe positions
+*     in a spectrum. If such an axis is found, a Mapping is returned which
+*     transforms the corresponding intermediate world coordinates to
+*     spectral world coordinates (this mapping leaves any other axes
+*     unchanged). It also, modifies the supplied Frame to describe the
+*     axis (again, other axes are left unchanged). If no spectral axis
+*     is found, a UnitMap is returned, and the supplied Frame is left
+*     unchanged.
+
+*  Parameters:
+*     this
+*        The FitsChan.
+*     store
+*        A structure containing information about the requested axis
+*        descriptions derived from a FITS header.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     frm
+*        The address of a location at which to store a pointer to the
+*        Frame describing the world coordinate axes.
+*     iwcfrm
+*        A pointer to the Frame describing the intermediate world coordinate
+*        axes. The properties of this Frame may be changed on exit.
+*     reflon
+*        The reference celestial longitude, in the frame given by reffrm.
+*     reflat
+*        The reference celestial latitude, in the frame given by reffrm.
+*     reffrm
+*        The SkyFrame defining reflon and reflat.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the Mapping.
+*/
+
+/* Local Variables: */
+   AstFrame *ofrm;        /* Pointer to a Frame */
+   AstMapping *map1;      /* Pointer to Mapping */
+   AstMapping *map2;      /* Pointer to Mapping */
+   AstMapping *ret;       /* Pointer to the returned Mapping */
+   AstSpecFrame *specfrm; /* Pointer to a SpecFrame */
+   char algcode[ 5 ];     /* Displayed spectral type string */
+   char stype[ 5 ];       /* Displayed spectral type string */
+   const char *cname;     /* Pointer to CNAME value */
+   const char *ctype;     /* Pointer to CTYPE value */
+   const char *cunit;     /* Pointer to CUNIT value */
+   const char *defunit;   /* Default unit string */
+   const char *specsys;   /* Pointer to SPECSYS value */
+   const char *ssyssrc;   /* Pointer to SSYSSRC value */
+   double geolat;         /* Observer's geodetic latitude */
+   double geolon;         /* Observer's geodetic longitude */
+   double h;              /* Observer's geodetic height */
+   double mjd;            /* Modified Julian Date */
+   double obscentre;      /* Spectral value at observation centre */
+   double obsgeo[ 3 ];    /* Observer's Cartesian position */
+   double restfrq;        /* RESTFRQ keyword value */
+   double vsource;        /* Source velocity */
+   int *axes;             /* Pointer to axis permutation array */
+   int i;                 /* Axis index */
+   int j;                 /* Loop count */
+   int k;                 /* Loop count */
+   int kk;                /* Loop count */
+   int naxes;             /* No. of axes in Frame */
+
+/* Initialise the pointer to the returned Mapping. */
+   ret = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return ret;
+
+/* Get the number of physical axes. */
+   naxes = astGetNaxes( *frm );
+
+/* An array to hold a list of axis selections. */
+   axes = astMalloc( naxes*sizeof( int ) );
+
+/* Loop round checking each axis. */
+   defunit = NULL;
+   map1 = NULL;
+   for( i = 0; i < naxes && astOK; i++ ) {
+
+/* Get the CTYPE value. Pass on to the next axis if no CTYPE is available. */
+      ctype = GetItemC( &(store->ctype), i, 0, s, NULL, method, class, status );
+      if( ctype ) {
+
+/* See if this CTYPE describes a spectral axis, and if so, extract the
+   system code, the algorithm code and get the default units. */
+         defunit = IsSpectral( ctype, stype, algcode, status );
+
+/* Skip to the next axis if the system type was not a spectral system
+   type. */
+         if( defunit ) {
+
+/* Create a SpecFrame or DSBSpecFrame with this system (the FITS type codes
+   are also legal SpecFrame System values). We use astSetC rather than
+   astSetSystem because astSetC translates string values into the
+   corresponding integer system identifiers. */
+            if( GetItem( &(store->imagfreq), 0, 0, s, NULL, method,
+                         class, status ) == AST__BAD ) {
+               specfrm = astSpecFrame( "", status );
+            } else {
+               specfrm = (AstSpecFrame *) astDSBSpecFrame( "", status );
+            }
+            astSetC( specfrm, "System", stype );
+
+/* Set the reference position (attributes RefRA and RefDec), if known. */
+            if( reffrm ) astSetRefPos( specfrm, reffrm, reflon, reflat );
+
+/* Set the SpecFrame units. Use the value of the CUNIT FITS keyword for this
+   axis if available, otherwise use the default units for the system, noted
+   above. */
+            cunit = GetItemC( &(store->cunit), i, 0, s, NULL, method, class, status );
+            if( !cunit ) cunit = defunit;
+            astSetUnit( specfrm, 0, cunit );
+
+/* Set the axis unit in the IWC Frame. */
+            astSetUnit( iwcfrm, i, cunit );
+
+/* Get a value for the Epoch attribute (the date of observation). */
+            mjd = ChooseEpoch( this, store, s, method, class, status );
+            if( mjd != AST__BAD ) astSetEpoch( specfrm, mjd );
+
+/* Set the rest frequency. Use the RESTFRQ keyword (assumed to be in Hz),
+   or (if RESTFRQ is not available), RESTWAV (assumes to be in m). */
+            restfrq = GetItem( &(store->restfrq), 0, 0, s, NULL, method, class, status );
+            if( restfrq == AST__BAD ) {
+               restfrq = GetItem( &(store->restwav), 0, 0, s, NULL, method, class, status );
+               if( restfrq != AST__BAD ) restfrq = AST__C/restfrq;
+            }
+            astSetRestFreq( specfrm, restfrq );
+
+/* Observer's position (from primary axis descriptions). Get the OBSGEO-X/Y/Z
+   keywords, convert to geodetic longitude and latitude and store as the
+   SpecFrame's ObsLat, ObsLon and ObsAlt attributes. */
+            obsgeo[ 0 ] = GetItem( &(store->obsgeox), 0, 0, ' ', NULL, method, class, status );
+            obsgeo[ 1 ] = GetItem( &(store->obsgeoy), 0, 0, ' ', NULL, method, class, status );
+            obsgeo[ 2 ] = GetItem( &(store->obsgeoz), 0, 0, ' ', NULL, method, class, status );
+            if( obsgeo[ 0 ] != AST__BAD &&
+                obsgeo[ 1 ] != AST__BAD &&
+                obsgeo[ 2 ] != AST__BAD ) {
+               eraGc2gd( 1, obsgeo, &geolon, &geolat, &h );
+               astSetObsLat( specfrm, geolat );
+               astSetObsLon( specfrm, geolon );
+               astSetObsAlt( specfrm, h );
+            }
+
+/* Source velocity rest frame */
+            ssyssrc = GetItemC( &(store->ssyssrc), 0, 0, s, NULL, method, class, status );
+            if( ssyssrc ) astSetC( specfrm, "SourceVRF", ssyssrc );
+
+/* Source velocity. Use the ZSOURCE keyword and convert from redshift to
+   velocity. */
+            vsource = GetItem( &(store->zsource), 0, 0, s, NULL, method, class, status );
+            if( vsource != AST__BAD ) {
+               vsource += 1.0;
+               vsource *= vsource;
+               vsource = AST__C*( vsource - 1.0  )/( vsource + 1.0 );
+               astSetSourceVel( specfrm, vsource );
+            }
+
+/* Reference frame. If the SPECSYS keyword is set, use it (the FITS codes
+   are also legal SpecFrame StdOfRest values). We use astSetC rather than
+   astSetSystem because astSetC translates string values into the
+   corresponding integer system identifiers. */
+            specsys = GetItemC( &(store->specsys), 0, 0, s, NULL, method, class, status );
+            if( specsys ) astSetC( specfrm, "StdOfRest", specsys );
+
+/* Axis label. If the CNAME keyword is set, use it as the axis label. */
+            cname = GetItemC( &(store->cname), i, 0, s, NULL, method, class, status );
+            if( cname ) astSetLabel( specfrm, 0, cname );
+
+/* If the header contains an AXREF value for the spectral axis, use it as the
+   observation centre in preferences to the CRVAL value. AXREF keywords are
+   created by the astWrite method for axes described by -TAB algorithm that
+   have no inverse transformation. */
+            obscentre = GetItem( &(store->axref), i, 0, s, NULL, method,
+                                  class, status );
+            if( obscentre == AST__BAD ) {
+               obscentre = GetItem( &(store->crval), i, 0, s, NULL, method,
+                                    class, status );
+            }
+
+/* Now do the extra stuff needed if we are creating a dual sideband
+   SpecFrame. */
+            if( astIsADSBSpecFrame( specfrm ) ) {
+               DSBSetUp( this, store, (AstDSBSpecFrame *) specfrm, s,
+                         obscentre, method, class, status );
+            }
+
+/* Now branch for each type of algorithm code. Each case returns a 1D
+   Mapping which converts IWC value into the specified Spectral system. */
+
+/* Linear */
+            if( strlen( algcode ) == 0 ) {
+               map1 = LinearWcs( store, i, s, method, class, status );
+
+/* Log-Linear */
+            } else if( !strcmp( "-LOG", algcode ) ) {
+               map1 = LogWcs( store, i, s, method, class, status );
+
+/* Non-Linear */
+            } else if( algcode[ 0 ] == '-' && algcode[ 2 ] == '2' ) {
+               map1 = NonLinSpecWcs( this, algcode, store, i, s, specfrm, method, class, status );
+
+/* Grism */
+            } else if( !strcmp( "-GRI", algcode ) ||
+                       !strcmp( "-GRA", algcode ) ) {
+               map1 = GrismSpecWcs( algcode, store, i, s, specfrm, method, class, status );
+            } else {
+               map1 = NULL;
+            }
+            if( map1 == NULL && astOK ) {
+               specfrm = astAnnul( specfrm );
+               astError( AST__BDFTS, "%s(%s): Cannot implement spectral "
+                         "algorithm code '%s' specified in FITS keyword '%s'.", status,
+                         method, class, ctype + 4, FormatKey( "CTYPE", i + 1, -1, s, status ) );
+               astError( AST__BDFTS, "%s(%s): Unknown algorithm code or "
+                         "unusable parameter values.", status, method, class );
+               break;
+            }
+
+/* Create a Frame by picking all the other (non-spectral) axes from the
+   supplied Frame. */
+            j = 0;
+            for( k = 0; k < naxes; k++ ) {
+               if( k != i ) axes[ j++ ] = k;
+            }
+
+/* If there were no other axes, replace the supplied Frame with the
+   specframe. */
+            if( j == 0 ) {
+               (void) astAnnul( *frm );
+               *frm = (AstFrame *) specfrm;
+
+/* Otherwise pick the other axes from the supplied Frame */
+            } else {
+               ofrm = astPickAxes( *frm, j, axes, NULL );
+
+/* Replace the supplied Frame with a CmpFrame made up of this Frame and
+   the SpecFrame. */
+               (void) astAnnul( *frm );
+               *frm = (AstFrame *) astCmpFrame( ofrm, specfrm, "", status );
+               ofrm = astAnnul( ofrm );
+               specfrm = astAnnul( specfrm );
+            }
+
+/* Permute the axis order to put the spectral axis back in its original
+   position. */
+            j = 0;
+            for( kk = 0; kk < naxes; kk++ ) {
+               if( kk == i ) {
+                  axes[ kk ] = naxes - 1;
+               } else {
+                  axes[ kk ] = j++;
+               }
+            }
+            astPermAxes( *frm, axes );
+         }
+      }
+
+/* If this axis is not a spectral axis, create a UnitMap (the Frame is left
+   unchanged). */
+      if( !map1 && astOK ) map1 = (AstMapping *) astUnitMap( 1, "", status );
+
+/* Add the Mapping for this axis in parallel with the Mappings for
+   previous axes. */
+      if( ret ) {
+         map2 = (AstMapping *) astCmpMap( ret, map1, 0, "", status );
+         ret = astAnnul( ret );
+         map1 = astAnnul( map1 );
+         ret = map2;
+      } else {
+         ret = map1;
+         map1 = NULL;
+      }
+   }
+
+/* Free the axes array. */
+   axes= astFree( axes );
+
+/* Return the result. */
+   return ret;
+}
+
+static void WcsToStore( AstFitsChan *this, AstFitsChan *trans,
+                        FitsStore *store, const char *method,
+                        const char *class, int *status ){
+
+/*
+*  Name:
+*     WcsToStore
+
+*  Purpose:
+*     Extract WCS information from the supplied FitsChan using a FITSWCS
+*     encoding, and store it in the supplied FitsStore.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     void WcsToStore( AstFitsChan *this, AstFitsChan *trans,
+*                      FitsStore *store, const char *method,
+*                      const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     A FitsStore is a structure containing a generalised represention of
+*     a FITS WCS FrameSet. Functions exist to convert a FitsStore to and
+*     from a set of FITS header cards (using a specified encoding), or
+*     an AST FrameSet. In other words, a FitsStore is an encoding-
+*     independant intermediary staging post between a FITS header and
+*     an AST FrameSet.
+*
+*     This function extracts FITSWCS keywords from the supplied FitsChan(s),
+*     and stores the corresponding WCS information in the supplied FitsStore.
+*     Keywords will be searched for first in "trans", and then, if they
+*     are not found in "trans", they will be searched for in "this".
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan containing the cards read from the
+*        original FITS header. This may include non-standard keywords.
+*     trans
+*        Pointer to a FitsChan containing cards representing standard
+*        translations of any non-standard keywords in "this". A NULL
+*        pointer indicates that "this" contains no non-standard keywords.
+*     store
+*        Pointer to the FitsStore structure.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Read all usable cards out of the main FitsChan, into the FitsStore. */
+   WcsFcRead( this, trans, store, method, class, status );
+
+/* If a FitsChan containing standard translations was supplied, read all
+   cards out of it, into the FitsStore, potentially over-writing the
+   non-standard values stored in the previous call to WcsFcRead. */
+   if( trans ) WcsFcRead( trans, NULL, store, method, class, status );
+}
+
+static int WorldAxes( AstFitsChan *this, AstMapping *cmap, double *dim, int *perm,
+                      int *status ){
+
+/*
+*  Name:
+*     WorldAxes
+
+*  Purpose:
+*     Associate final world axes with pixel axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+
+*     int WorldAxes( AstFitsChan *this, AstMapping *cmap, double *dim, int *perm,
+*                    int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function finds the association between the axes of the final
+*     world coordinate system, and those of the pixel coordinate
+*     system. This may not simply be a 1-to-1 association because the
+*     Mapping may include a PermMap. Each output axis is associated with
+*     the input axis which is most nearly aligned with it.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     cmap
+*        Pointer to the Mapping from pixel coordinates to final world
+*        coordinates.
+*     dim
+*        Pointer to an array with one element for each input of "map",
+*        supplied holding the no. of pixels in the data cube along the axis, or
+*        AST__BAD If unknown.
+*     perm
+*        Pointer to an array with one element for each output of "map".
+*        On exit, each element of this array holds the zero-based index of the
+*        "corresponding" (i.e. most nearly parallel) pixel axis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero for success - zero for failure.
+*/
+
+/* Local Variables: */
+   AstMapping *smap;
+   AstMapping *map;
+   AstPointSet *pset1;
+   AstPointSet *pset2;
+   double **ptr2;
+   double **ptr1;
+   double *dw;
+   double *g0;
+   double *nwt;
+   double *ntn;
+   double *tn;
+   double *wt;
+   double *w0;
+   double dg;
+   double s;
+   double sj;
+   double tnmin;
+   double wtmax;
+   int *outs;
+   int i2;
+   int i;
+   int imin;
+   int j2;
+   int j;
+   int jmin;
+   int nin;
+   int nout;
+   int nouts;
+   int nused;
+   int ret;
+   int retain;
+   int used;
+
+/* Initialise returned value */
+   ret = 0;
+
+/* Other initialisation to avoid compiler warnings. */
+   retain = 0;
+
+/* Check the status */
+   if( !astOK ) return ret;
+
+/* Simplfy the Mapping. */
+   map = astSimplify( cmap );
+
+/* Get the number of inputs and outputs for the Mapping. */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+
+/* Initialise "perm". */
+   for( i = 0; i < nout; i++ ) perm[ i ] = i;
+
+/* First deal with Mappings that are defined in both directions. */
+   if( astGetTranForward( map ) && astGetTranInverse( map ) ) {
+
+/* Use FindBasisVectors to find an input position which coresponds to a
+   good output position. Store it in a dynamic array pointed to by "g0". */
+      pset1 = astPointSet( nin+1, nin, "", status );
+      pset2 = astPointSet( nin+1, nout, "", status );
+      if( FindBasisVectors( map, nin, nout, dim, pset1, pset2, status ) ) {
+         g0 = astMalloc( sizeof(double)*nin );
+         ptr1 = astGetPoints( pset1 );
+         if( astOK ) {
+            for( j = 0; j < nin; j++ ) g0[ j ] = ptr1[ j ][ 0 ];
+         }
+         pset1 = astAnnul( pset1 );
+         pset2 = astAnnul( pset2 );
+
+/* If no basis vectors found, return. */
+      } else {
+         pset1 = astAnnul( pset1 );
+         pset2 = astAnnul( pset2 );
+         return ret;
+      }
+
+/* Create Pointset to hold two input (pixel) points. */
+      pset1 = astPointSet( 2, nin, "", status );
+      ptr1 = astGetPoints( pset1 );
+
+/* Create a Pointset to hold the same number of output (world) points. */
+      pset2 = astPointSet( 2, nout, "", status );
+      ptr2 = astGetPoints( pset2 );
+
+/* Allocate memory to use as work space */
+      w0 = astMalloc( sizeof(double)*nout );
+      dw = astMalloc( sizeof(double)*nout );
+      tn = astMalloc( sizeof(double)*nout*nin );
+      wt = astMalloc( sizeof(double)*nout*nin );
+
+/* Check that the pointers can be used. */
+      if( astOK ) {
+
+/* Transform the grid position found above, plus a position 1 pixel away
+   along all pixel axes, into world coords. Also set up "dw" to hold
+   "a small increment" along each world axis. */
+         for( j = 0; j < nin; j++ ) {
+            ptr1[ j ] [ 0 ] = g0[ j ];
+            ptr1[ j ] [ 1 ] = g0[ j ] + 1.0;
+         }
+         (void) astTransform( map, pset1, 1, pset2 );
+         for( i = 0; i < nout; i++ ) {
+            w0[ i ] = ptr2[ i ] [ 0 ];
+            if( w0[ i ] != AST__BAD && ptr2[ i ] [ 1 ] != AST__BAD ) {
+               dw[ i ] = fabs( 0.1*( ptr2[ i ] [ 1 ] - w0[ i ] ) );
+               if( dw[ i ] <= fabs( 0.001*w0[ i ] ) ) {
+                  if( w0[ i ] != 0.0 ) {
+                     dw[ i ] = fabs( 0.001*w0[ i ] );
+                  } else {
+                     dw[ i ] = 1.0;
+                  }
+               }
+            } else {
+               dw[ i ] = AST__BAD;
+            }
+         }
+
+/* Any PermMap in the mapping may result in the the "inverse transformation"
+   not being a true inverse of the forward transformation (for instance,
+   constant values fed in for degenerate axis would have this effect). To
+   ensure that "g0" and "w0" are corresponding positions, transform the
+   "w0" position back into grid coords and use the resulting grid position
+   as "g0". */
+         (void) astTransform( map, pset2, 0, pset1 );
+         for( j = 0; j < nin; j++ ) {
+            g0[ j ] = ptr1[ j ] [ 0 ];
+         }
+
+/* In the next loop we find the tan of the angle between each WCS axis and
+   each of the pixel axes. Loop round each WCS axis. */
+         for( i = 0; i < nout; i++ ) {
+
+/* Initialise the tan values for this WCS axis to AST__BAD. */
+            ntn = tn + i*nin;
+            nwt = wt + i*nin;
+            for( j = 0; j < nin; j++ ) ntn[ j ] = AST__BAD;
+
+/* As a side issue, initialise the pixel axis assigned to each WCS axis
+   to -1, to indicate that no grid axis has yet been associated with this
+   WCS axis. */
+            perm[ i ] = -1;
+
+/* Skip over this axis if the increment is bad. */
+            if( dw[ i ] != AST__BAD ) {
+
+/* Store a WCS position which is offset from the "w0" position by a small
+   amount along the current WCS axis. The first position in "ptr2" is
+   currently "w0". */
+               ptr2[ i ][ 0 ] += dw[ i ];
+
+/* Transform this position into grid coords. */
+               (void) astTransform( map, pset2, 0, pset1 );
+
+/* Re-instate the original "w0" values within "ptr2", ready for the next
+   WCS axis. */
+               ptr2[ i ][ 0 ] = w0[ i ];
+
+/* Consider each pixel axis in turn as a candidate for being assigned to
+   the current WCS axis. */
+               for( j = 0; j < nin; j++ ) {
+
+/* Find the tan of the angle between the current ("i"th) WCS axis and the
+   current ("j"th) pixel axis. This gets stored in tn[j+nin*i]. A
+   corresponding weight for each angle is stored in nwt[j+nin*i]. This
+   is the length of the projection of the vector onto the "j"th pixel
+   axis.  */
+                  s = 0.0;
+                  sj = 0.0;
+                  for( j2 = 0; j2 < nin; j2++ ) {
+                     if( ptr1[ j2 ][ 0 ] != AST__BAD ) {
+                         dg = ptr1[ j2 ][ 0 ] - g0[ j2 ];
+                         if( j2 != j ) {
+                            s += dg*dg;
+                         } else {
+                            sj = fabs( dg );
+                         }
+                     } else {
+                         s = AST__BAD;
+                         break;
+                     }
+                  }
+                  if( s != AST__BAD && sj != 0.0 ) {
+                     ntn[ j ] = sqrt( s )/sj;
+                     nwt[ j ] = sj;
+                  }
+               }
+            }
+         }
+
+/* Loop until every grid axes has been assigned to a WCS axis. */
+         while( 1 ) {
+
+/* Pass through the array of tan values, finding the smallest. Note the
+   pixel and WCS axis for which the smallest tan value occurs. If the tan
+   values are equal, favour the one with highest weight. */
+            ntn = tn;
+            nwt = wt;
+            tnmin = AST__BAD;
+            wtmax = AST__BAD;
+            imin = 0;
+            jmin = 0;
+            for( i = 0; i < nout; i++ ) {
+               for( j = 0; j < nin; j++ ) {
+                  if( *ntn != AST__BAD ) {
+                     if( tnmin == AST__BAD || *ntn < tnmin ) {
+                        tnmin = *ntn;
+                        wtmax = *nwt;
+                        imin = i;
+                        jmin = j;
+                     } else if( astEQUAL( *ntn, tnmin ) && *nwt > wtmax ) {
+                        wtmax = *nwt;
+                        imin = i;
+                        jmin = j;
+                     }
+                  }
+                  ntn++;
+                  nwt++;
+               }
+            }
+
+/* Check we found a usable minimum tan value */
+            if( tnmin != AST__BAD ) {
+
+/* Assign the pixel axis to the WCS axis. */
+               perm[ imin ] = jmin;
+
+/* Set bad all the tan values for this pixel and WCS axis pair. This ensures
+   that the pixel axis will not be assigned to another WCS axis, and that
+   the WCS will not have another pixel axis assigned to it. */
+               ntn = tn;
+               for( i = 0; i < nout; i++ ) {
+                  for( j = 0; j < nin; j++ ) {
+                     if( i == imin || j == jmin ) *ntn = AST__BAD;
+                     ntn++;
+                  }
+               }
+
+/* Leave the loop if no more good tan values were found. */
+            } else {
+               break;
+            }
+         }
+
+/* The above process may have left some WCS axes with out any assigned
+   pixel axis. We assign the remaining pixel arbitrarily to such axes,
+   starting with the first remaining pixel axis. Find the lowest unused
+   pixel axis. */
+         for( j = 0; j < nin; j++ ) {
+            used = 0;
+            for( i = 0; i < nout; i++ ) {
+               if( perm[ i ] == j ) {
+                  used = 1;
+                  break;
+               }
+            }
+            if( !used ) break;
+         }
+
+/* Now check each WCS axis looking for outputs which were not assigned a
+   pixel axis in the above process. */
+         for( i = 0; i < nout; i++ ) {
+            if( perm[ i ] == -1 ) {
+
+/* Use the next unused axis value. */
+               perm[ i ] = j++;
+
+/* Find the next unused axis value. */
+               for( ; j < nin; j++ ) {
+                  used = 0;
+                  for( i2 = 0; i2 < nout; i2++ ) {
+                     if( perm[ i2 ] == j ) {
+                        used = 1;
+                        break;
+                     }
+                  }
+                  if( !used ) break;
+               }
+            }
+         }
+
+/* Indicate success. */
+         if( astOK ) ret = 1;
+      }
+
+/* Free resources. */
+      pset1 = astAnnul( pset1 );
+      pset2 = astAnnul( pset2 );
+      g0 = astFree( g0 );
+      w0 = astFree( w0 );
+      tn = astFree( tn );
+      wt = astFree( wt );
+      dw = astFree( dw );
+
+/* Now, if we can use the TAB algorithm, deal with Mappings that are defined only in the forward direction. */
+   } else if( astGetTranForward( map ) && astGetTabOK( this ) > 0 ) {
+
+/* Assume success. */
+      ret = 1;
+
+/* Initialise to indicate no outputs have yet been assigned. */
+      for( i = 0; i < nout; i++ ) perm[ i ] = -1;
+
+/* Find the output associated with each input. */
+      for( j = 0; j < nin; j++ ) {
+
+/* Attempt to split off the current input. */
+         outs = astMapSplit( map, 1, &j, &smap );
+
+/* If successfull, store the index of the corresponding input for each
+   output. */
+         if( outs && smap ) {
+            nouts = astGetNout( smap );
+            for( i = 0; i < nouts; i++ ) {
+               if( perm[ outs[ i ] ] == -1 ) {
+                  perm[ outs[ i ] ] = j;
+               } else {
+                  ret = 0;
+               }
+            }
+         }
+
+/* Free resources. */
+         outs = astFree( outs );
+         if( smap ) smap = astAnnul( smap );
+      }
+
+/* Check all outputs were assigned . */
+      for( i = 0; i < nout && ret; i++ ) {
+         if( perm[ i ] == -1 ) ret = 0;
+      }
+
+/* If succesful, attempt to remove any duplicates from the "perm" array
+   (i.e. inputs that supply more than one output). First get a list of
+   the inputs that are currently unused (i.e. do not appear in "perm"). */
+      if( ret ) {
+
+/* Check each input. */
+         for( j = 0; j < nin; j++ ) {
+
+/* See how many outputs are fed by this input. */
+            nused = 0;
+            for( i = 0; i < nout; i++ ) {
+               if( perm[ i ] ==  j ) nused++;
+            }
+
+/* If it used more than once, we need to remove all but one of the
+   occurrences. */
+            if( nused > 1 ) {
+
+/* Choose the occurrence to retain. If the output with the same index as
+   the input is one of them, use it. Otherwise, use the first occurrence. */
+               if( perm[ j ] == j ) {
+                  retain = j;
+               } else {
+                  for( i = 0; i < nout; i++ ) {
+                     if( perm[ i ] ==  j ) {
+                        retain = i;
+                        break;
+                     }
+                  }
+               }
+
+/* Loop round all occurrences of this input again. */
+               for( i = 0; i < nout && ret; i++ ) {
+                  if( perm[ i ] ==  j ) {
+
+/* Replace all occurrences, except for the one being retained. */
+                     if( i != retain ) {
+
+/* Replace it with the next unused input. */
+                        for( j2 = 0; j2 < nin; j2++ ) {
+                           used = 0;
+                           for( i2 = 0; i2 < nout; i2++ ) {
+                              if( perm[ i2 ] == j2 ) {
+                                 used = 1;
+                                 break;
+                              }
+                           }
+                           if( ! used ) {
+                              perm[ i ] = j2;
+                              break;
+                           }
+                        }
+
+/* If there were no unused inputs, we cannot do it. */
+                        if( used ) ret = 0;
+                     }
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Free resources. */
+   map = astAnnul( map );
+
+/* Return the result. */
+   return ret;
+}
+
+static int Write( AstChannel *this_channel, AstObject *object, int *status ) {
+/*
+*  Name:
+*     Write
+
+*  Purpose:
+*     Write an Object to a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     int Write( AstChannel *this, AstObject *object, int *status )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the astWrite method
+*     inherited from the Channel class).
+
+*  Description:
+*     This function writes an Object to a FitsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     object
+*        Pointer to the Object which is to be written.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of Objects written to the FitsChan by this invocation of
+*     astWrite.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the AST error status set, or if it should fail for any
+*     reason.
+*     - The Base Frame in the FrameSet is used as the pixel Frame, and
+*     the Current Frame is used to create the primary axis descriptions.
+*     Attempts are made to create secondary axis descriptions for any
+*     other Frames in the FrameSet (up to a total of 26).
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+   FitsStore *store;             /* Intermediate storage for WCS information */
+   char banner[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ]; /* Buffer for begin/end banner */
+   const char *class;            /* Pointer to string holding object class */
+   const char *method;           /* Pointer to string holding calling method */
+   double *dim;                  /* Pointer to array of axis dimensions */
+   int card0;                    /* Index of original current card */
+   int comm;                     /* Value of Comm attribute */
+   int encoding;                 /* FITS encoding scheme to use */
+   int i;                        /* Axis index */
+   int naxis;                    /* No. of pixel axes */
+   int ret;                      /* Number of objects read */
+
+/* Initialise. */
+   ret = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_channel);
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Ensure the source function has been called */
+   ReadFromSource( this, status );
+
+/* Store the calling method, and object class. */
+   method = "astWrite";
+   class = astGetClass( this );
+
+/* The original current card is re-instated at the end if no object
+   is written. Save its index. */
+   card0 = astGetCard( this );
+
+/* Indicate that all cards added to the FitsCHan by this call should be
+   marked as "new". */
+   mark_new = 1;
+
+/* Get the encoding scheme used by the FitsChan. */
+   encoding = astGetEncoding( this );
+
+/* First deal with cases where we are writing to a FitsChan in which AST
+   objects are encoded using native AST-specific keywords... */
+   if( encoding == NATIVE_ENCODING ){
+
+/* Increment the nesting level which keeps track of recursive
+   invocations of this function. */
+      write_nest++;
+
+/* Initialise the current indentation level for top-level objects. */
+      if ( !write_nest ) current_indent = 0;
+
+/* Obtain the value of the Comm attribute. */
+      comm = astGetComment( this );
+
+/* If this is the top-level invocation (i.e. we are about to write out
+   a new top-level Object), then prefix it with a blank FITS line and
+   an appropriate banner of FITS comments, unless comments have been
+   suppressed. */
+      if ( !write_nest && comm ) {
+         astSetFitsCom( this, "        ", "", 0 );
+         MakeBanner(
+"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++",
+                     "", "", banner, status );
+         astSetFitsCom( this, "COMMENT", banner, 0 );
+         if( astIsAFrameSet( object ) ) {
+            MakeBanner( "WCS information in AST format", "", "", banner, status );
+            astSetFitsCom( this, "COMMENT", banner, 0 );
+            MakeBanner( "See http://www.starlink.ac.uk/ast/", "", "", banner, status );
+            astSetFitsCom( this, "COMMENT", banner, 0 );
+         }
+         MakeBanner( HEADER_TEXT, astGetClass( object ), " object", banner, status );
+         astSetFitsCom( this, "COMMENT", banner, 0 );
+         MakeBanner(
+"................................................................",
+                     "", "", banner, status );
+         astSetFitsCom( this, "COMMENT", banner, 0 );
+      }
+
+/* Invoke the parent astWrite method to write out the Object data. */
+      (*parent_write)( this_channel, object, status );
+
+/* Append a banner of FITS comments to the object data, as above, if
+   necessary. */
+      if ( !write_nest && comm ) {
+         MakeBanner(
+"................................................................",
+                     "", "", banner, status );
+         astSetFitsCom( this, "COMMENT", banner, 0 );
+         MakeBanner( FOOTER_TEXT, astGetClass( object ), " object", banner, status );
+         astSetFitsCom( this, "COMMENT", banner, 0 );
+         MakeBanner(
+"----------------------------------------------------------------",
+                     "", "", banner, status );
+         astSetFitsCom( this, "COMMENT", banner, 0 );
+      }
+
+/* Return the nesting level to its previous value. */
+      write_nest--;
+
+/* Indicate that an object has been written. */
+      ret = 1;
+
+/* Now deal with cases where we are writing to a FitsChan in which AST
+   objects are encoded using any of the supported foreign encodings... */
+   } else {
+
+/* Only proceed if the supplied object is a FrameSet. */
+      if( astIsAFrameSet( object ) ){
+
+/* Note the number of pixel (i.e. Base Frame) axes, and allocate memory to
+   hold the image dimensions. */
+         naxis = astGetNin( (AstFrameSet *) object );
+         dim = (double *) astMalloc( sizeof(double)*naxis );
+         if( dim ){
+
+/* Note the image dimensions, if known. If not, store AST__BAD values. */
+            for( i = 0; i < naxis; i++ ){
+               if( !astGetFitsF( this, FormatKey( "NAXIS", i + 1, -1, ' ', status ),
+                                 dim + i ) ) dim[ i ] = AST__BAD;
+            }
+
+/* Extract the required information from the FrameSet into a standard
+   intermediary structure called a FitsStore. The indices of any
+   celestial axes are returned. */
+            store = FsetToStore( this, (AstFrameSet *) object, naxis, dim,
+                                 encoding, method, class, status );
+
+/* If the FrameSet cannot be described in terms of any of the supported
+   FITS encodings, a null pointer will have been returned. */
+            if( store ){
+
+/* Now put header cards describing the contents of the FitsStore into the
+   supplied FitsChan, using the requested encoding. Zero or one is
+   returned depending on whether the information could be encoded. */
+               ret = FitsFromStore( this, store, encoding, dim,
+                                    (AstFrameSet *) object, method, class, status );
+
+/* Release the resources used by the FitsStore. */
+               store = FreeStore( store, status );
+
+/* If the Object was written to the FitsChan, set the current card to
+   end-of-file. */
+               if( ret ) astSetCard( this, INT_MAX );
+            }
+
+/* Free workspace holding image dimensions */
+            dim = (double *) astFree( (void *) dim );
+         }
+      }
+   }
+
+/* If an error has occurred, return zero and remove any new cards added
+   to the FitsCHan by this call. */
+   if( !astOK ) ret = 0;
+
+/* Clear the new flag associated with cards which have been added to the
+   FitsChan as a result of this function. If the object was not added
+   succesfully to the FitsChan, remove any cards which were added before
+   the error was discovered. */
+   FixNew( this, NEW1, !ret, method, class, status );
+   FixNew( this, NEW2, !ret, method, class, status );
+
+/* Indicate that all cards added to the FitsChan from now on should not be
+   marked as "new". */
+   mark_new = 0;
+
+/* If no object was written, re-instate the original current card. */
+   if( !ret ) astSetCard( this, card0 );
+
+/* Return the answer. */
+   return ret;
+}
+
+static void WriteBegin( AstChannel *this_channel, const char *class,
+                        const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteBegin
+
+*  Purpose:
+*     Write a "Begin" data item to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void WriteBegin( AstChannel *this, const char *class,
+*                      const char *comment )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected astWriteBegin
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function writes a "Begin" data item to the data sink
+*     associated with a FitsChan, so as to begin the output of a new
+*     Object definition.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     class
+*        Pointer to a constant null-terminated string containing the
+*        name of the class to which the Object belongs.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the "Begin"
+*        item. Normally, this will describe the purpose of the Object.
+
+*  Notes:
+*     - The comment supplied may not actually be used, depending on
+*     the nature of the FitsChan supplied.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
+   char buff[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ];
+                                 /* Character buffer */
+   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_channel);
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Increment the indentation level for comments. */
+   current_indent += INDENT_INC;
+
+/* If we are not beginning a top-level Object definition, and helpful
+   information has not been suppressed, generate an indented comment
+   to mark the "Begin" item and write it to the FitsChan as a comment
+   card with a blank keyword. */
+   if ( write_nest && ( astGetFull( this ) >= 0 ) ) {
+      MakeIndentedComment( current_indent, '+', "Beginning of ", class, buff, status );
+      astSetFitsCom( this, "        ", buff, 0 );
+   }
+
+/* Create a unique FITS keyword for this "Begin" item, basing it on
+   "BEGAST". */
+   CreateKeyword( this, "BEGAST", keyword, status );
+
+/* Generate a pre-quoted version of the class name. */
+   PreQuote( class, buff, status );
+
+/* Write the "Begin" item to the FitsChan as a keyword and string
+   value. */
+   astSetFitsS( this, keyword, buff,
+                     astGetComment( this ) ? comment : NULL, 0 );
+
+/* Clear the count of items written. */
+   items_written = 0;
+}
+
+static void WriteDouble( AstChannel *this_channel, const char *name,
+                         int set, int helpful,
+                         double value, const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteDouble
+
+*  Purpose:
+*     Write a double value to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void WriteDouble( AstChannel *this, const char *name,
+*                       int set, int helpful,
+*                       double value, const char *comment )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected
+*     astWriteDouble method inherited from the Channel class).
+
+*  Description:
+*     This function writes a named double value, representing the
+*     value of a class instance variable, to the data sink associated
+*     with a FitsChan. It is intended for use by class "Dump"
+*     functions when writing out class information which will
+*     subsequently be re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        FitsChan's Full attribute is set - either to permit all
+*        values to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        The value to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the FitsChan supplied and the setting of its
+*        Comm attribute.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
+   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_channel);
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Use the "set" and "helpful" flags, along with the FitsChan's
+   attributes to decide whether this value should actually be
+   written. */
+   if ( Use( this, set, helpful, status ) ) {
+
+/* Create a unique FITS keyword from the name supplied. */
+      CreateKeyword( this, name, keyword, status );
+
+/* Write the value to the FitsChan as a keyword and value */
+      astSetFitsF( this, keyword, value,
+                       astGetComment( this ) ? comment : NULL, 0 );
+
+/* If the value is not "set", replace the card just written by a COMMENT
+   card containing the text of the card as the comment. */
+      if( !set ) MakeIntoComment( this, "astWrite", astGetClass( this ), status );
+
+/* Increment the count of items written. */
+      items_written++;
+   }
+}
+
+static void WriteEnd( AstChannel *this_channel, const char *class, int *status ) {
+/*
+*  Name:
+*     WriteEnd
+
+*  Purpose:
+*     Write an "End" data item to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void WriteEnd( AstChannel *this, const char *class )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected astWriteEnd
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function writes an "End" data item to the data sink
+*     associated with a FitsChan. This item delimits the end of an
+*     Object definition.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     class
+*        Pointer to a constant null-terminated string containing the
+*        class name of the Object whose definition is being terminated
+*        by the "End" item.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
+   char buff[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ];
+                                 /* Character buffer */
+   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_channel);
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Create a unique FITS keyword for this "End" item, basing it on
+   "ENDAST". */
+   CreateKeyword( this, "ENDAST", keyword, status );
+
+/* Generate a pre-quoted version of the class name. */
+   PreQuote( class, buff, status );
+
+/* Write the "End" item to the FitsChan as a keyword and string
+   value. */
+   astSetFitsS( this, keyword, buff,
+                     astGetComment( this ) ? "End of object definition" : NULL,
+                     0 );
+
+/* If we are not ending a top-level Object definition, and helpful
+   information has not been suppressed, generate an indented comment
+   to mark the "End" item and write it to the FitsChan as a comment
+   card with a blank keyword. */
+   if ( write_nest && ( astGetFull( this ) >= 0 ) ) {
+      MakeIndentedComment( current_indent, '-', "End of ", class, buff, status );
+      astSetFitsCom( this, "        ", buff, 0 );
+   }
+
+/* Decrement the indentation level for comments. */
+   current_indent -= INDENT_INC;
+}
+
+static void WriteFits( AstFitsChan *this, int *status ){
+
+/*
+*++
+*  Name:
+c     astWriteFits
+f     AST_WRITEFITS
+
+*  Purpose:
+*     Write out all cards in a FitsChan to the sink function.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+c     void astWriteFits( AstFitsChan *this )
+f     CALL AST_WRITEFITS( THIS, STATUS )
+
+*  Class Membership:
+*     FitsChan method.
+
+*  Description:
+c     This function
+f     This routine
+*     writes out all cards currently in the FitsChan. If the SinkFile
+*     attribute is set, they will be written out to the specified sink file.
+*     Otherwise, they will be written out using the sink function specified
+*     when the FitsChan was created. All cards are then deleted from the
+*     FitsChan.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsChan.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - If the SinkFile is unset, and no sink function is available, this
+*     method simply empties the FitsChan, and is then equivalent to
+c     astEmptyFits.
+f     AST_EMPTYFITS.
+*     - This method attempt to execute even if an error has occurred
+*     previously.
+*--
+*/
+
+/* Ensure a FitsChan was supplied. */
+   if( this ) {
+
+/* Ensure the source function has been called */
+      ReadFromSource( this, status );
+
+/* We can usefully use the local destructor function to do the work,
+   since it only frees resources used within teh FitsChan, rather than
+   freeing the FitsChan itself. */
+      Delete( (AstObject *) this, status );
+   }
+}
+
+static void WriteInt( AstChannel *this_channel, const char *name,
+                      int set, int helpful,
+                      int value, const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteInt
+
+*  Purpose:
+*     Write an int value to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void WriteInt( AstChannel *this, const char *name,
+*                    int set, int helpful,
+*                    int value, const char *comment )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected
+*     astWriteInt method inherited from the Channel class).
+
+*  Description:
+*     This function writes a named int value, representing the
+*     value of a class instance variable, to the data sink associated
+*     with a FitsChan. It is intended for use by class "Dump"
+*     functions when writing out class information which will
+*     subsequently be re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        FitsChan's Full attribute is set - either to permit all
+*        values to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        The value to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the FitsChan supplied and the setting of its
+*        Comm attribute.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
+   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_channel);
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Use the "set" and "helpful" flags, along with the FitsChan's
+   attributes to decide whether this value should actually be
+   written. */
+   if ( Use( this, set, helpful, status ) ) {
+
+/* Create a unique FITS keyword from the name supplied. */
+      CreateKeyword( this, name, keyword, status );
+
+/* Write the value to the FitsChan as a keyword and value */
+      astSetFitsI( this, keyword, value,
+                   astGetComment( this ) ? comment : NULL, 0 );
+
+/* If the value is not "set", replace the card just written by a COMMENT
+   card containing the text of the card as the comment. */
+      if( !set ) MakeIntoComment( this, "astWrite", astGetClass( this ), status );
+
+/* Increment the count of items written. */
+      items_written++;
+   }
+}
+
+static void WriteIsA( AstChannel *this_channel, const char *class,
+                      const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteIsA
+
+*  Purpose:
+*     Write an "IsA" data item to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void WriteIsA( AstChannel *this, const char *class,
+*                    const char *comment )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected astWriteIsA
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function writes an "IsA" data item to the data sink
+*     associated with a FitsChan. This item delimits the end of the
+*     data associated with the instance variables of a class, as part
+*     of an overall Object definition.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     class
+*        Pointer to a constant null-terminated string containing the
+*        name of the class whose data are terminated by the "IsA"
+*        item.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the "IsA"
+*        item. Normally, this will describe the purpose of the class
+*        whose data are being terminated.
+
+*  Notes:
+*     - The comment supplied may not actually be used, depending on
+*     the nature of the FitsChan supplied.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
+   char buff[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN + 1 ];
+                                 /* Character buffer */
+   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_channel);
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Output an "IsA" item only if there has been at least one item
+   written since the last "Begin" or "IsA" item, or if the Full
+   attribute for the Channel is greater than zero (requesting maximum
+   information). */
+   if ( items_written || astGetFull( this ) > 0 ) {
+
+/* Create a unique FITS keyword for this "IsA" item, basing it on
+   "ISA". */
+      CreateKeyword( this, "ISA", keyword, status );
+
+/* Generate a pre-quoted version of the class name. */
+      PreQuote( class, buff, status );
+
+/* Write the "IsA" item to the FitsChan as a keyword and string
+   value. */
+      astSetFitsS( this, keyword, buff,
+                        astGetComment( this ) ? comment : NULL, 0 );
+
+/* If helpful information has not been suppressed, generate an
+   indented comment to mark the "IsA" item and write it to the
+   FitsChan as a comment card with a blank keyword. */
+      if ( astGetFull( this ) >= 0 ) {
+         MakeIndentedComment( current_indent, '.', "Class boundary", "",
+                              buff, status );
+         astSetFitsCom( this, "        ", buff, 0 );
+      }
+   }
+
+/* Clear the count of items written. */
+   items_written = 0;
+}
+
+static void WriteObject( AstChannel *this_channel, const char *name,
+                         int set, int helpful,
+                         AstObject *value, const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteObject
+
+*  Purpose:
+*     Write an Object value to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void WriteObject( AstChannel *this, const char *name,
+*                       int set, int helpful,
+*                       AstObject *value, const char *comment )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected
+*     astWriteObject method inherited from the Channel class).
+
+*  Description:
+*     This function writes a named Object value, representing the
+*     value of a class instance variable, to the data sink associated
+*     with a FitsChan. It is intended for use by class "Dump"
+*     functions when writing out class information which will
+*     subsequently be re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        FitsChan's Full attribute is set - either to permit all
+*        values to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        A pointer to the Object to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the FitsChan supplied and the setting of its
+*        Comm attribute.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
+   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_channel);
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Use the "set" and "helpful" flags, along with the FitsChan's
+   attributes to decide whether this value should actually be
+   written. */
+   if ( Use( this, set, helpful, status ) ) {
+
+/* Create a unique FITS keyword from the name supplied. */
+      CreateKeyword( this, name, keyword, status );
+
+/* Write the value to the FitsChan as a keyword and a blank string value,
+   not pre-quoted (this "null" value indicates that an Object description
+   follows). */
+      astSetFitsS( this, keyword, "",
+                        astGetComment( this ) ? comment : NULL, 0 );
+
+/* If the value is "set", write out the Object description. */
+      if ( set ) {
+         astWrite( this, value );
+
+/* If the value is not set, replace the card just written to the FitsChan
+   by COMENT card containing the keyword and blank string value (do not
+   write out the Object description). */
+      } else {
+         MakeIntoComment( this, "astWrite", astGetClass( this ), status );
+      }
+
+/* Increment the count of items written. */
+      items_written++;
+   }
+}
+
+static void WriteToSink( AstFitsChan *this, int *status ){
+/*
+*  Name:
+*     WriteToSink
+
+*  Purpose:
+*     Write the contents of the FitsChan out to the sink file or function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void WriteToSink( AstFitsChan *this, int *status )
+
+*  Class Membership:
+*     FitsChan member function.
+
+*  Description:
+*     If the SinkFile attribute is set, each card in the FitsChan is
+*     written out to the sink file. Otherwise, the cards are passed in
+*     turn to the sink function specified when the FitsChan was created.
+*     If no sink function was provided, the cards are not written out.
+*     Cards marked as having been read into an AST object are not written
+*     out.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  The current card is left unchanged.
+*/
+
+/* Local Constants: */
+#define ERRBUF_LEN 80
+
+/* Local Variables: */
+   FILE *fd;                    /* File descriptor for sink file */
+   astDECLARE_GLOBALS           /* Declare the thread specific global data */
+   char *errstat;               /* Pointer for system error message */
+   char card[ AST__FITSCHAN_FITSCARDLEN + 1]; /* Buffer for header card */
+   char errbuf[ ERRBUF_LEN ];   /* Buffer for system error message */
+   const char *sink_file;       /* Path to output sink file */
+   int icard;                   /* Current card index on entry */
+   int old_ignore_used;         /* Original value of external variable ignore_used */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* If the SinkFile attribute is set, open the file. */
+   fd = NULL;
+   if( astTestSinkFile( this ) ) {
+      sink_file = astGetSinkFile( this );
+      fd = fopen( sink_file, "w" );
+      if( !fd ) {
+         if ( errno ) {
+#if HAVE_STRERROR_R
+            strerror_r( errno, errbuf, ERRBUF_LEN );
+            errstat = errbuf;
+#else
+            errstat = strerror( errno );
+#endif
+            astError( AST__WRERR, "astDelete(%s): Failed to open output "
+                      "SinkFile '%s' - %s.", status, astGetClass( this ),
+                      sink_file, errstat );
+         } else {
+            astError( AST__WRERR, "astDelete(%s): Failed to open output "
+                      "SinkFile '%s'.", status, astGetClass( this ),
+                      sink_file );
+         }
+      }
+   }
+
+/* Only proceed if a file was opened, or sink function and wrapper were supplied. */
+   if( fd || ( this->sink && this->sink_wrap ) ){
+
+/* Store the current card index. */
+      icard = astGetCard( this );
+
+/* Indicate that cards which have been read into an AST object should skipped
+   over by the functions which navigate the linked list of cards. */
+      old_ignore_used = ignore_used;
+      ignore_used = 1;
+
+/* Ensure that the first card in the FitsChan will be the next one to be
+   read. */
+      astSetCard( this, 1 );
+
+/* Loop round obtaining and writing out each card, until all cards have been
+   processed. */
+      while( !astFitsEof( this ) && astOK ){
+
+/* Get the current card, and write it out through the sink function.
+   The call to astFindFits increments the current card. */
+         if( astFindFits( this, "%f", card, 1 ) ) {
+
+/* If s sink file was opened, write the card out to it. */
+            if( fd ) {
+               fprintf( fd, "%s\n", card );
+
+/* Otherwise, use the isnk function. The sink function is an externally
+   supplied function which may not be thread-safe, so lock a mutex first.
+   Also store the channel data pointer in a global variable so that it can
+   be accessed in the sink function using macro astChannelData. */
+            } else {
+               astStoreChannelData( this );
+               LOCK_MUTEX3;
+               ( *this->sink_wrap )( *this->sink, card, status );
+               UNLOCK_MUTEX3;
+            }
+         }
+      }
+
+/* Re-instate the original flag indicating if cards marked as having been
+   read should be skipped over. */
+      ignore_used = old_ignore_used;
+
+/* Set the current card index back to what it was on entry. */
+      astSetCard( this, icard );
+   }
+
+/* Close the sink file. */
+   if( fd ) fclose( fd );
+}
+
+static void WriteString( AstChannel *this_channel, const char *name,
+                         int set, int helpful,
+                         const char *value, const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteString
+
+*  Purpose:
+*     Write a string value to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     void WriteString( AstChannel *this, const char *name,
+*                       int set, int helpful,
+*                       const char *value, const char *comment )
+
+*  Class Membership:
+*     FitsChan member function (over-rides the protected
+*     astWriteString method inherited from the Channel class).
+
+*  Description:
+*     This function writes a named string value, representing the
+*     value of a class instance variable, to the data sink associated
+*     with a FitsChan. It is intended for use by class "Dump"
+*     functions when writing out class information which will
+*     subsequently be re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        FitsChan's Full attribute is set - either to permit all
+*        values to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        Pointer to a constant null-terminated string containing the
+*        value to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the FitsChan supplied and the setting of its
+*        Comm attribute.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure. */
+   char *c;                      /* Pointer to next buffer character */
+   char buff1[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 3 ]; /* Buffer for a single substring */
+   char buff2[ AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 3 ]; /* Buffer for pre-quoted string */
+   char cc;                      /* Next character */
+   char keyword[ FITSNAMLEN + 1 ]; /* Buffer for FITS keyword */
+   const char *start;            /* Pointer to start of substring */
+   int first;                    /* Is this the first sub-string? */
+   int nc;                       /* No. of available columns remaining */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_channel);
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_channel;
+
+/* Use the "set" and "helpful" flags, along with the FitsChan's
+   attributes to decide whether this value should actually be
+   written. */
+   if ( Use( this, set, helpful, status ) ) {
+
+/* Create a unique FITS keyword from the name supplied. */
+      CreateKeyword( this, name, keyword, status );
+
+/* Store a pointer to the start of the next sub-string (i.e. the
+   beggining of the string), and then loop round until the end of the
+   string is reached. */
+      start = value;
+      first = 1;
+      while( *start && astOK ){
+
+/* Store the number of characters available in the 80 column header card
+   for the next substring, leaving room for the "= " string at the start,
+   and the delimiting quotes. Also reserve 2 characters to allow for the
+   possibility of double quotes being needed to protect trailing white space
+   (see function PreQuote). */
+         nc = AST__FITSCHAN_FITSCARDLEN - FITSNAMLEN - 6;
+
+/* If this is the first sub-string reserve room for any comment. */
+         if( first ){
+            if( comment && comment[0] ) nc -= ChrLen( comment, status ) + 3;
+
+/* If the first card will be turned into a comment card, we need to leave room
+   for the keyword name and equals sign, etc, within the 80 columns. */
+            if( !set ) nc -= FITSNAMLEN + 5;
+         }
+
+/* We need to check the sub-string for single quotes since these will
+   take up 2 characters each instead of 1 when encoded since single quotes
+   within a string are doubled. Search through from the starting
+   character, copying the sub-string into a buffer, and reducing the number
+   of available characters remaining in the card for each character. */
+         c = buff1;
+         while( *start && nc > 0 ){
+            cc = *(start++);
+            *(c++) = cc;
+            if( cc == '\'' ) {
+               nc -= 2;
+            } else {
+               nc -= 1;
+            }
+         }
+
+/* If the last character in the substring was a single quote, there may
+   not have been room for the extra quote which is added when the
+   sub-string is encoded. In this case we need to back up a character in
+   order to remove the single quote frin this substring and move it into
+   the next sub-string. */
+         if( nc < 0 ){
+            start--;
+            c--;
+         }
+
+/* If the supplied value has not been exhausted, append an ampersand to
+   the string. In this case we need to move the last character in the
+   substring into the next substring to make room for the ampersand. */
+         if( *start ) {
+            start--;
+            c--;
+            *(c++) = '&';
+         }
+
+/* Terminate the buffer. */
+         *c = 0;
+
+/* The FITS standard considers trailing white space is be insignificant,
+   and so we need to guard against external applications throwing away
+   significant trailing white space. This is done by encosing the string,
+   including trailing white space, in double quotes. */
+         PreQuote( buff1, buff2, status );
+
+/* On the first pass through this loop, write the value to the FitsChan as
+   a keyword and value */
+         if( first ){
+            astSetFitsS( this, keyword, buff2,
+                         astGetComment( this ) ? comment : NULL, 0 );
+
+/* If the value is not "set", replace the card just written by a COMMENT
+   card containing the text of the card as the comment. */
+            if( !set ) MakeIntoComment( this, "astWrite", astGetClass( this ), status );
+
+/* On subsequent passes through the loop, store the string using a CONTINUE
+   keyword, with type AST__CONTINUE (this type is like AST__STRING but is
+   formatted without an equals sign). */
+         } else {
+            astSetFitsCN( this, "CONTINUE", buff2, NULL, 0 );
+         }
+         first = 0;
+      }
+
+/* Increment the count of items written. */
+      items_written++;
+   }
+}
+
+static AstMapping *ZPXMapping( AstFitsChan *this, FitsStore *store, char s,
+                               int naxes, int zpxaxes[2], const char *method,
+                               const char *class, int *status ){
+/*
+*  Name:
+*     ZPXMapping
+
+*  Purpose:
+*     Create a Mapping descriping "-ZPX" (IRAF) distortion.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     AstMapping *ZPXMapping( AstFitsChan *this, FitsStore *store, char s,
+*                             int naxes, int zpxaxes[2], const char *method,
+*                             const char *class, int *status )
+
+*  Class Membership:
+*     FitsChan
+
+*  Description:
+*     This function uses the values in the supplied FitsStore to create a
+*     Mapping which implements the "-ZPX" distortion code, produced by
+*     the IRAF project. See:
+*
+*     http://iraf.noao.edu/projects/ccdmosaic/zpx.html
+*
+*     Note, the Mapping created by this function implements the "lngcor"
+*     and "latcor" corrections described in the WAT... keywords. The
+*     basic ZPN projection code is handled in the normal way, as any
+*     other projection is handled.
+
+*  Parameters:
+*     store
+*        A structure containing information about the requested axis
+*        descriptions derived from a FITS header.
+*     s
+*        A character identifying the co-ordinate version to use. A space
+*        means use primary axis descriptions. Otherwise, it must be an
+*        upper-case alphabetical characters ('A' to 'Z').
+*     naxes
+*        The number of intermediate world coordinate axes (WCSAXES).
+*     zpxaxes
+*        The zero-based indices of the two IWC axes that use the ZPX projection.
+*     method
+*        A pointer to a string holding the name of the calling method.
+*        This is used only in the construction of error messages.
+*     class
+*        A pointer to a string holding the class of the object being
+*        read. This is used only in the construction of error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the Mapping.
+*/
+
+/* Local Variables: */
+   AstMapping   *ret;
+   char *watstr;
+   double *cvals[ 2 ];
+   int *mvals[ 2 ];
+   int ncoeff[ 2 ];
+   int i;
+   int icoeff;
+   int ok;
+
+/* Initialise the pointer to the returned Mapping. */
+   ret = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return ret;
+
+/* Check both axes */
+   for( i = 0; i < 2; i++ ){
+      mvals[ i ] = NULL;
+      cvals[ i ] = NULL;
+      ncoeff[ i ] = 0;
+
+/* Concatenate all the IRAF "WAT" keywords together for this axis. These
+   keywords are marked as having been used, so that they are not written
+   out when the FitsChan is deleted. */
+      watstr = ConcatWAT( this, zpxaxes[ i ], method, class, status );
+
+/* Extract the polynomial coefficients from the concatenated WAT string.
+   These are returned in the form of a list of PVi_m values for a TPN
+   projection. */
+      ncoeff[ i ] = WATCoeffs( watstr, i, cvals + i, mvals + i, &ok, status );
+
+/* If the current axis of the ZPX projection uses features not supported
+   by AST, do not do any more axes. */
+      if( !ok ) break;
+
+/* Free the WAT string. */
+      watstr = astFree( watstr );
+   }
+
+/* If we can handle the ZPX projection, store the polynomial coefficients in
+   a new inverted TPN WcsMap. This WcsMap is used as a correction to the ZPN
+   WcsMap to be created later, therefore set its FITSProj value to zero so
+   that it is not used as the FITS projection when written out via
+   astWrite. Also set TPNTan to zero to indicate that the TAN part of the
+   TPN projection should not be used (i.e. just use the polynomial part). */
+   if( ok && astOK ) {
+
+      if( ncoeff[ 0 ] || ncoeff[ 1 ] ) {
+         ret = (AstMapping *) astWcsMap( naxes, AST__TPN, zpxaxes[ 0 ] + 1,
+                                         zpxaxes[ 1 ] + 1, "Invert=1",
+                                         status );
+         astSetFITSProj( ret, 0 );
+         astSetTPNTan( ret, 0 );
+         for( i = 0; i < 2; i++ ){
+            for( icoeff = 0; icoeff < ncoeff[ i ]; icoeff++ ) {
+               astSetPV( ret, zpxaxes[ i ], (mvals[ i ])[ icoeff ],
+                         (cvals[ i ])[ icoeff ] );
+            }
+         }
+
+      } else {
+         ret = (AstMapping *) astUnitMap( naxes, " ", status );
+      }
+
+/* If the TNX cannot be represented in FITS-WCS (within our restrictions), add
+   warning keywords to the FitsChan. */
+   } else {
+      Warn( this, "zpx", "This FITS header includes, or was "
+            "derived from, a ZPX projection which requires "
+            "unsupported IRAF-specific corrections. The WCS "
+            "information may therefore be incorrect.", method, class,
+            status );
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Card. */
+/* ===== */
+
+/*
+*att++
+*  Name:
+*     Card
+
+*  Purpose:
+*     Index of current FITS card in a FitsChan.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute gives the index of the "current" FITS header card
+*     within a FitsChan, the first card having an index of 1. The
+c     choice of current card affects the behaviour of functions that
+f     choice of current card affects the behaviour of routines that
+c     access the contents of the FitsChan, such as astDelFits,
+c     astFindFits and astPutFits.
+f     access the contents of the FitsChan, such as AST_DELFITS,
+f     AST_FINDFITS and AST_PUTFITS.
+*
+*     A value assigned to Card will position the FitsChan at any
+*     desired point, so that a particular card within it can be
+*     accessed. Alternatively, the value of Card may be enquired in
+*     order to determine the current position of a FitsChan.
+*
+*     The default value of Card is 1. This means that clearing
+c     this attribute (using astClear) effectively "rewinds" the
+f     this attribute (using AST_CLEAR) effectively "rewinds" the
+*     FitsChan, so that the first card is accessed next.  If Card is
+*     set to a value which exceeds the total number of cards in the
+*     FitsChan (as given by its Ncard attribute), it is regarded as
+*     pointing at the "end-of-file". In this case, the value returned
+*     in response to an enquiry is always one more than the number of
+*     cards in the FitsChan.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+
+/* Encoding. */
+/* ========= */
+
+/*
+*att++
+*  Name:
+*     Encoding
+
+*  Purpose:
+*     System for encoding Objects as FITS headers.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies the encoding system to use when AST
+*     Objects are stored as FITS header cards in a FitsChan. It
+c     affects the behaviour of the astWrite and astRead functions when
+f     affects the behaviour of the AST_WRITE and AST_READ routines when
+*     they are used to transfer any AST Object to or from an external
+*     representation consisting of FITS header cards (i.e. whenever a
+*     write or read operation is performed using a FitsChan as the I/O
+*     Channel).
+*
+*     There are several ways (conventions) by which coordinate system
+*     information may be represented in the form of FITS headers and
+*     the Encoding attribute is used to specify which of these should
+*     be used. The encoding options available are outlined in the
+*     "Encodings Available" section below, and in more detail in the
+*     sections which follow.
+*
+*     Encoding systems differ in the range of possible Objects
+*     (e.g. classes) they can represent, in the restrictions they
+*     place on these Objects (e.g. compatibility with some
+*     externally-defined coordinate system model) and in the number of
+*     Objects that can be stored together in any particular set of
+*     FITS header cards (e.g. multiple Objects, or only a single
+*     Object). The choice of encoding also affects the range of
+*     external applications which can potentially read and interpret
+*     the FITS header cards produced.
+*
+*     The encoding options available are not necessarily mutually
+*     exclusive, and it may sometimes be possible to store multiple
+*     Objects (or the same Object several times) using different
+*     encodings within the same set of FITS header cards. This
+*     possibility increases the likelihood of other applications being
+*     able to read and interpret the information.
+*
+*     By default, a FitsChan will attempt to determine which encoding
+*     system is already in use, and will set the default Encoding
+*     value accordingly (so that subsequent I/O operations adopt the
+*     same conventions). It does this by looking for certain critical
+*     FITS keywords which only occur in particular encodings. For
+*     details of how this works, see the "Choice of Default Encoding"
+*     section below. If you wish to ensure that a particular encoding
+*     system is used, independently of any FITS cards already present,
+*     you should set an explicit Encoding value yourself.
+
+*  Encodings Available:
+*     The Encoding attribute can take any of the following (case
+*     insensitive) string values to select the corresponding encoding
+
+*     system:
+*
+*     - "DSS": Encodes coordinate system information in FITS header
+*     cards using the convention developed at the Space Telescope
+*     Science Institute (STScI) for the Digitised Sky Survey (DSS)
+*     astrometric plate calibrations. The main advantages of this
+*     encoding are that FITS images which use it are widely available
+*     and it is understood by a number of important and
+*     well-established astronomy applications. For further details,
+*     see the section "The DSS Encoding" below.
+*
+*     - "FITS-WCS": Encodes coordinate system information in FITS
+*     header cards using the conventions described in the FITS
+*     world coordinate system (FITS-WCS) papers by E.W. Greisen,
+*     M. Calabretta, et al. The main advantages of this encoding are that
+*     it should be understood by any FITS-WCS compliant application and
+*     is likely to be adopted widely for FITS data in future. For further
+*     details, see the section "The FITS-WCS Encoding" below.
+*
+*     - "FITS-PC": Encodes coordinate system information in FITS
+*     header cards using the conventions described in an earlier draft
+*     of the FITS world coordinate system papers by E.W. Greisen and
+*     M. Calabretta. This encoding uses a combination of CDELTi and
+*     PCiiijjj keywords to describe the scale and rotation of the pixel
+*     axes. This encoding is included to support existing data and
+*     software which uses these now superceded conventions. In general,
+*     the "FITS-WCS" encoding (which uses CDi_j or PCi_j keywords to
+*     describe the scale and rotation) should be used in preference to
+*     "FITS-PC".
+*
+*     - "FITS-IRAF": Encodes coordinate system information in FITS
+*     header cards using the conventions described in the document
+*     "World Coordinate Systems Representations Within the FITS
+*     Format" by R.J. Hanisch and D.G. Wells, 1988.  This encoding is
+*     currently employed by the IRAF data analysis facility, so its
+*     use will facilitate data exchange with IRAF. Its main advantages
+*     are that it is a stable convention which approximates to a
+*     subset of the propsed FITS-WCS encoding (above). This makes it
+*     suitable as an interim method for storing coordinate system
+*     information in FITS headers until the FITS-WCS encoding becomes
+*     stable. Since many datasets currently use the FITS-IRAF
+*     encoding, conversion of data from FITS-IRAF to the final form of
+*     FITS-WCS is likely to be well supported.
+*
+*     - "FITS-AIPS": Encodes coordinate system information in FITS
+*     header cards using the conventions originally introduced by the
+*     AIPS data analysis facility. This is base on the use of CDELTi and
+*     CROTAi keuwords to desribe the scale and rotation of each axis.
+*     These conventions have been superceded but are still widely used.
+*
+*     - "FITS-AIPS++": Encodes coordinate system information in FITS
+*     header cards using the conventions used by the AIPS++ project.
+*     This is an extension of FITS-AIPS which includes some of the
+*     features of FITS-IRAF and FITS-PC.
+*
+*     - "FITS-CLASS": Encodes coordinate system information in FITS
+*     header cards using the conventions used by the CLASS project.
+*     CLASS is a software package for reducing single-dish radio and
+*     sub-mm spectroscopic data. See the section "CLASS FITS format" at
+*     http://www.iram.fr/IRAMFR/GILDAS/doc/html/class-html/.
+*
+*     - "NATIVE": Encodes AST Objects in FITS header cards using a
+*     convention which is private to the AST library (but adheres to
+*     the general FITS standard) and which uses FITS keywords that
+*     will not clash with other encoding systems. The main advantages
+*     of this are that any class of AST Object may be encoded, and any
+*     (reasonable) number of Objects may be stored sequentially in the
+*     same FITS header. This makes FITS headers an almost loss-less
+*     communication path for passing AST Objects between applications
+*     (although all such applications must, of course, make use of the
+*     AST library to interpret the information). For further details,
+*     see the section "The NATIVE Encoding" below.
+
+*  Choice of Default Encoding:
+*     If the Encoding attribute of a FitsChan is not set, the default
+*     value it takes is determined by the presence of certain critical
+*     FITS keywords within the FitsChan. The sequence of decisions
+
+*     used to arrive at the default value is as follows:
+*
+*     - If the FitsChan contains any keywords beginning with the
+*     string "BEGAST", then NATIVE encoding is used,
+*     - Otherwise, FITS-CLASS is used if the FitsChan contains a DELTAV
+*     keyword and a keyword of the form VELO-xxx, where xxx indicates one
+*     of the rest frames used by class (e.g. "VELO-LSR"), or "VLSR".
+*     - Otherwise, if the FitsChan contains a CTYPE keyword which
+*     represents a spectral axis using the conventions of the AIPS and
+*     AIPS++ projects (e.g. "FELO-LSR", etc), then one of FITS-AIPS or
+*     FITS-AIPS++ encoding is used. FITS-AIPS++ is used if any of the
+*     keywords CDi_j, PROJP, LONPOLE or LATPOLE are
+*     found in the FitsChan. Otherwise FITS-AIPS is used.
+*     - Otherwise, if the FitsChan contains a keyword of the form
+*     "PCiiijjj", where "i" and "j" are single digits, then
+*     FITS-PC encoding is used,
+*     - Otherwise, if the FitsChan contains a keyword of the form
+*     "CDiiijjj", where "i" and "j" are single digits, then
+*     FITS-IRAF encoding is used,
+*     - Otherwise, if the FitsChan contains a keyword of the form
+*     "CDi_j", and at least one of RADECSYS, PROJPi, or CjVALi
+*     where "i" and "j" are single digits, then FITS-IRAF encoding is
+*     used.
+*     - Otherwise, if the FitsChan contains any keywords of the form
+*     PROJPi, CjVALi or RADECSYS, where "i" and "j" are single digits,
+*     then FITS-PC encoding is used.
+*     - Otherwise, if the FitsChan contains a keyword of the form
+*     CROTAi, where "i" is a single digit, then FITS-AIPS encoding is
+*     used.
+*     - Otherwise, if the FitsChan contains a keyword of the form
+*     CRVALi, where "i" is a single digit, then FITS-WCS encoding is
+*     used.
+*     - Otherwise, if the FitsChan contains the "PLTRAH" keyword, then
+*     DSS encoding is used,
+*     - Otherwise, if none of these conditions is met (as would be the
+*     case when using an empty FitsChan), then NATIVE encoding is
+*     used.
+*
+*     Except for the NATIVE and DSS encodings, all the above checks
+*     also require that the header contains at least one CTYPE, CRPIX and
+*     CRVAL keyword (otherwise the checking process continues to the next
+*     case).
+*
+*     Setting an explicit value for the Encoding attribute always
+*     over-rides this default behaviour.
+*
+*     Note that when writing information to a FitsChan, the choice of
+*     encoding will depend greatly on the type of application you
+*     expect to be reading the information in future. If you do not
+*     know this, there may sometimes be an advantage in writing the
+*     information several times, using a different encoding on each
+*     occasion.
+
+*  The DSS Encoding:
+*     The DSS encoding uses FITS header cards to store a multi-term
+*     polynomial which relates pixel positions on a digitised
+*     photographic plate to celestial coordinates (right ascension and
+*     declination). This encoding may only be used to store a single
+*     AST Object in any set of FITS header cards, and that Object must
+*     be a FrameSet which conforms to the STScI/DSS coordinate system
+*     model (this means the Mapping which relates its base and current
+*     Frames must include either a DssMap or a WcsMap with type
+*     AST__TAN or AST__TPN).
+*
+c     When reading a DSS encoded Object (using astRead), the FitsChan
+f     When reading a DSS encoded Object (using AST_READ), the FitsChan
+*     concerned must initially be positioned at the first card (its
+*     Card attribute must equal 1) and the result of the read, if
+*     successful, will always be a pointer to a FrameSet. The base
+*     Frame of this FrameSet represents DSS pixel coordinates, and the
+*     current Frame represents DSS celestial coordinates. Such a read
+*     is always destructive and causes the FITS header cards required
+*     for the construction of the FrameSet to be removed from the
+*     FitsChan, which is then left positioned at the "end-of-file". A
+*     subsequent read using the same encoding will therefore not
+*     return another FrameSet, even if the FitsChan is rewound.
+*
+c     When astWrite is used to store a FrameSet using DSS encoding,
+f     When AST_WRITE is used to store a FrameSet using DSS encoding,
+*     an attempt is first made to simplify the FrameSet to see if it
+*     conforms to the DSS model.  Specifically, the current Frame must
+*     be a FK5 SkyFrame; the projection must be a tangent plane
+*     (gnomonic) projection with polynomial corrections conforming to
+*     DSS requirements, and north must be parallel to the second base
+*     Frame axis.
+*
+*     If the simplification process succeeds, a description of the
+*     FrameSet is written to the FitsChan using appropriate DSS FITS
+*     header cards. The base Frame of the FrameSet is used to form the
+*     DSS pixel coordinate system and the current Frame gives the DSS
+*     celestial coordinate system.  A successful write operation will
+*     over-write any existing DSS encoded data in the FitsChan, but
+*     will not affect other (non-DSS) header cards. If a destructive
+*     read of a DSS encoded Object has previously occurred, then an
+*     attempt will be made to store the FITS header cards back in
+*     their original locations.
+*
+*     If an attempt to simplify a FrameSet to conform to the DSS model
+*     fails (or if the Object supplied is not a FrameSet), then no
+c     data will be written to the FitsChan and astWrite will return
+f     data will be written to the FitsChan and AST_WRITE will return
+*     zero. No error will result.
+
+*  The FITS-WCS Encoding:
+*     The FITS-WCS convention uses FITS header cards to describe the
+*     relationship between pixels in an image (not necessarily
+*     2-dimensional) and one or more related "world coordinate systems".
+*     The FITS-WCS encoding may only be used to store a single AST Object
+*     in any set of FITS header cards, and that Object must be a FrameSet
+*     which conforms to the FITS-WCS model (the FrameSet may, however,
+*     contain multiple Frames which will be result in multiple FITS
+*     "alternate axis descriptions"). Details of the use made by this
+*     Encoding of the conventions described in the FITS-WCS papers are
+*     given in the appendix "FITS-WCS Coverage" of this document. A few
+*     main points are  described below.
+*
+*     The rotation and scaling of the intermediate world coordinate system
+*     can be specified using either "CDi_j" keywords, or "PCi_j" together
+*     with "CDELTi" keywords. When writing a FrameSet to a FitsChan, the
+*     the value of the CDMatrix attribute of the FitsChan determines
+*     which system is used.
+*
+*     In addition, this encoding supports the "TAN with polynomial correction
+*     terms" projection which was included in a draft of the FITS-WCS paper,
+*     but was not present in the final version. A "TAN with polynomial
+*     correction terms" projection is represented using a WcsMap with type
+*     AST__TPN (rather than AST__TAN which is used to represent simple
+*     TAN projections). When reading a FITS header, a CTYPE keyword value
+*     including a "-TAN" code results in an AST__TPN projection if there are
+*     any projection parameters (given by the PVi_m keywords) associated with
+*     the latitude axis, or if there are projection parameters associated
+*     with the longitude axis for m greater than 4. When writing a
+*     FrameSet to a FITS header, an AST__TPN projection gives rise to a
+*     CTYPE value including the normal "-TAN" code, but the projection
+*     parameters are stored in keywords with names "QVi_m", instead of the
+*     usual "PVi_m". Since these QV parameters are not part of the
+*     FITS-WCS standard they will be ignored by other non-AST software,
+*     resulting in the WCS being interpreted as a simple TAN projection
+*     without any corrections. This should be seen as an interim solution
+*     until such time as an agreed method for describing projection
+*     distortions within FITS-WCS has been published.
+*
+*     AST extends the range of celestial coordinate systems which may be
+*     described using this encoding by allowing the inclusion of
+*     "AZ--" and "EL--" as the coordinate specification within CTYPE
+*     values. These form a longitude/latitude pair of axes which describe
+*     azimuth and elevation. The geographic position of the observer
+*     should be supplied using the OBSGEO-X/Y/Z keywords described in FITS-WCS
+*     paper III. Currently, a simple model is used which includes diurnal
+*     aberration, but ignores atmospheric refraction, polar motion, etc.
+*     These may be added in a later release.
+*
+*     If an AST SkyFrame that represents offset rather than absolute
+*     coordinates (see attribute SkyRefIs) is written to a FitsChan using
+*     FITS-WCS encoding, two alternate axis descriptions will be created.
+*     One will describe the offset coordinates, and will use "OFLN" and
+*     "OFLT" as the axis codes in the CTYPE keywords. The other will
+*     describe absolute coordinates as specified by the System attribute
+*     of the SkyFrame, using the usual CTYPE codes ("RA--"/"DEC-", etc).
+*     In addition, the absolute coordinates description will contain
+*     AST-specific keywords (SREF1/2, SREFP1/2 and SREFIS) that allow the
+*     header to be read back into AST in order to reconstruct the original
+*     SkyFrame.
+*
+c     When reading a FITS-WCS encoded Object (using astRead), the FitsChan
+f     When reading a FITS-WCS encoded Object (using AST_READ), the FitsChan
+*     concerned must initially be positioned at the first card (its
+*     Card attribute must equal 1) and the result of the read, if
+*     successful, will always be a pointer to a FrameSet. The base
+*     Frame of this FrameSet represents FITS-WCS pixel coordinates,
+*     and the current Frame represents the physical coordinate system
+*     described by the FITS-WCS primary axis descriptions. If
+*     secondary axis descriptions are also present, then the FrameSet
+*     may contain additional (non-current) Frames which represent
+*     these.  Such a read is always destructive and causes the FITS
+*     header cards required for the construction of the FrameSet to be
+*     removed from the FitsChan, which is then left positioned at the
+*     "end-of-file". A subsequent read using the same encoding will
+*     therefore not return another FrameSet, even if the FitsChan is
+*     rewound.
+*
+c     When astWrite is used to store a FrameSet using FITS-WCS
+f     When AST_WRITE is used to store a FrameSet using FITS-WCS
+*     encoding, an attempt is first made to simplify the FrameSet to
+*     see if it conforms to the FITS-WCS model. If this simplification
+*     process succeeds (as it often should, as the model is reasonably
+*     flexible), a description of the FrameSet is written to the
+*     FitsChan using appropriate FITS header cards. The base Frame of
+*     the FrameSet is used to form the FITS-WCS pixel coordinate
+*     system and the current Frame gives the physical coordinate
+*     system to be described by the FITS-WCS primary axis
+*     descriptions.  Any additional Frames in the FrameSet may be used
+*     to construct secondary axis descriptions, where appropriate.
+*
+*     A successful write operation will over-write any existing
+*     FITS-WCS encoded data in the FitsChan, but will not affect other
+*     (non-FITS-WCS) header cards. If a destructive read of a FITS-WCS
+*     encoded Object has previously occurred, then an attempt will be
+*     made to store the FITS header cards back in their original
+*     locations. Otherwise, the new cards will be inserted following
+*     any other FITS-WCS related header cards present or, failing
+*     that, in front of the current card (as given by the Card
+*     attribute).
+*
+*     If an attempt to simplify a FrameSet to conform to the FITS-WCS
+*     model fails (or if the Object supplied is not a FrameSet), then
+c     no data will be written to the FitsChan and astWrite will
+f     no data will be written to the FitsChan and AST_WRITE will
+*     return zero. No error will result.
+
+*  The FITS-IRAF Encoding:
+*     The FITS-IRAF encoding can, for most purposes, be considered as
+*     a subset of the FITS-WCS encoding (above), although it differs
+*     in the details of the FITS keywords used. It is used in exactly
+*     the same way and has the same restrictions, but with the
+
+*     addition of the following:
+*
+*     - The only celestial coordinate systems that may be represented
+*     are equatorial, galactic and ecliptic,
+*     - Sky projections can be represented only if any associated
+*     projection parameters are set to their default values.
+*     - Secondary axis descriptions are not supported, so when writing
+*     a FrameSet to a FitsChan, only information from the base and
+*     current Frames will be stored.
+*
+*     Note that this encoding is provided mainly as an interim measure to
+*     provide a more stable alternative to the FITS-WCS encoding until the
+*     FITS standard for encoding WCS information is finalised.  The name
+*     "FITS-IRAF" indicates the general keyword conventions used and does
+*     not imply that this encoding will necessarily support all features of
+*     the WCS scheme used by IRAF software. Nevertheless, an attempt has
+*     been made to support a few such features where they are known to be
+*     used by important sources of data.
+*
+*     When writing a FrameSet using the FITS-IRAF encoding, axis rotations
+*     are specified by a matrix of FITS keywords of the form "CDi_j", where
+*     "i" and "j" are single digits. The alternative form "CDiiijjj", which
+*     is also in use, is recognised when reading an Object, but is never
+*     written.
+*
+*     In addition, the experimental IRAF "ZPX" and "TNX" sky projections will
+*     be accepted when reading, but will never be written (the corresponding
+*     FITS "ZPN" or "distorted TAN" projection being used instead). However,
+*     there are restrictions on the use of these experimental projections.
+*     For "ZPX", longitude and latitude correction surfaces (appearing as
+*     "lngcor" or "latcor" terms in the IRAF-specific "WAT" keywords) are
+*     not supported. For "TNX" projections, only cubic surfaces encoded as
+*     simple polynomials with "half cross-terms" are supported. If an
+*     un-usable "TNX" or "ZPX" projection is encountered while reading
+*     from a FitsChan, a simpler form of TAN or ZPN projection is used
+*     which ignores the unsupported features and may therefore be
+*     inaccurate. If this happens, a warning message is added to the
+*     contents of the FitsChan as a set of cards using the keyword "ASTWARN".
+*
+*     You should not normally attempt to mix the foreign FITS encodings within
+*     the same FitsChan, since there is a risk that keyword clashes may occur.
+
+*  The FITS-PC Encoding:
+*     The FITS-PC encoding can, for most purposes, be considered as
+*     equivalent to the FITS-WCS encoding (above), although it differs
+*     in the details of the FITS keywords used. It is used in exactly
+*     the same way and has the same restrictions.
+
+*  The FITS-AIPS Encoding:
+*     The FITS-AIPS encoding can, for most purposes, be considered as
+*     equivalent to the FITS-WCS encoding (above), although it differs
+*     in the details of the FITS keywords used. It is used in exactly
+*     the same way and has the same restrictions, but with the
+
+*     addition of the following:
+*
+*     - The only celestial coordinate systems that may be represented
+*     are equatorial, galactic and ecliptic,
+*     - Spectral axes can only be represented if they represent
+*     frequency, radio velocity or optical velocity, and are linearly
+*     sampled in frequency. In addition, the standard of rest
+*     must be LSRK, LSRD, barycentric or geocentric.
+*     - Sky projections can be represented only if any associated
+*     projection parameters are set to their default values.
+*     - The AIT, SFL and MER projections can only be written if the CRVAL
+*     keywords are zero for both longitude and latitude axes.
+*     - Secondary axis descriptions are not supported, so when writing
+*     a FrameSet to a FitsChan, only information from the base and
+*     current Frames will be stored.
+*     - If there are more than 2 axes in the base and current Frames, any
+*     rotation must be restricted to the celestial plane, and must involve
+*     no shear.
+
+*  The FITS-AIPS++ Encoding:
+*     The FITS-AIPS++ encoding is based on the FITS-AIPS encoding, but
+*     includes some features of the FITS-IRAF and FITS-PC encodings.
+*     Specifically, any celestial projections supported by FITS-PC may be
+*     used, including those which require parameterisation, and the axis
+*     rotation and scaling may be specified using CDi_j keywords. When
+*     writing a FITS header, rotation will be specified using CROTA/CDELT
+*     keywords if possible, otherwise CDi_j keywords will be used instead.
+
+*  The FITS-CLASS Encoding:
+*     The FITS-CLASS encoding uses the conventions of the CLASS project.
+*     These are described in the section "Developer Manual"/"CLASS FITS
+
+*     Format" contained in the CLASS documentation at:
+*
+*     http://www.iram.fr/IRAMFR/GILDAS/doc/html/class-html/class.html.
+*
+
+*     This encoding is similar to FITS-AIPS with the following restrictions:
+*
+*     - When a SpecFrame is created by reading a FITS-CLASS header, the
+*       attributes describing the observer's position (ObsLat, ObsLon and
+*       ObsAlt) are left unset because the CLASS encoding does not specify
+*       these values. Conversions to or from the topocentric standard of rest
+*       will therefore be inaccurate (typically by up to about 0.5 km/s)
+*       unless suitable values are assigned to these attributes after the
+*       FrameSet has been created.
+*     - When writing a FrameSet to a FITS-CLASS header, the current Frame
+*       in the FrameSet must have at least 3 WCS axes, of which one must be
+*       a linear spectral axis. The spectral axis in the created header will
+*       always describe frequency. If the spectral axis in the supplied
+*       FrameSet refers to some other system (e.g. radio velocity, etc),
+*       then it will be converted to frequency.
+*     - There must be a pair of celestial axes - either (RA,Dec) or
+*       (GLON,GLAT). RA and Dec must be either FK4/B1950 or FK5/J2000.
+*     - A limited range of projection codes (TAN, ARC, STG, AIT, SFL, SIN)
+*       can be used. For AIT and SFL, the reference point must be at the
+*       origin of longitude and latitude. For SIN, the associated projection
+*       parameters must both be zero.
+*     - No rotation of the celestial axes is allowed, unless the spatial
+*       axes are degenerate (i.e. cover only a single pixel).
+*     - The frequency axis in the created header will always describe
+*       frequency in the source rest frame. If the supplied FrameSet uses
+*       some other standard of rest then suitable conversion will be applied.
+*     - The source velocity must be defined. In other words, the SpecFrame
+*       attributes SourceVel and SourceVRF must have been assigned values.
+*     - The frequency axis in a FITS-CLASS header does not represent
+*       absolute frequency, but instead represents offsets from the rest
+*       frequency in the standard of rest of the source.
+*
+*     When writing a FrameSet out using FITS-CLASS encoding, the current
+*     Frame may be temporarily modified if this will allow the header
+*     to be produced. If this is done, the associated pixel->WCS Mapping
+*     will also be modified to take account of the changes to the Frame.
+*     The modifications performed include re-ordering axes (WCS axes, not
+*     pixel axes), changing spectral coordinate system and standard of
+*     rest, changing the celestial coordinate system and reference equinox,
+*     and changing axis units.
+
+*  The NATIVE Encoding:
+*     The NATIVE encoding may be used to store a description of any
+*     class of AST Object in the form of FITS header cards, and (for
+*     most practical purposes) any number of these Object descriptions
+*     may be stored within a single set of FITS cards. If multiple
+*     Object descriptions are stored, they are written and read
+*     sequentially. The NATIVE encoding makes use of unique FITS
+*     keywords which are designed not to clash with keywords that have
+*     already been used for other purposes (if a potential clash is
+*     detected, an alternative keyword is constructed to avoid the
+*     clash).
+*
+*     When reading a NATIVE encoded object from a FitsChan (using
+c     astRead), FITS header cards are read, starting at the current
+f     AST_READ), FITS header cards are read, starting at the current
+*     card (as determined by the Card attribute), until the start of
+*     the next Object description is found. This description is then
+*     read and converted into an AST Object, for which a pointer is
+*     returned. Such a read is always destructive and causes all the
+*     FITS header cards involved in the Object description to be
+*     removed from the FitsChan, which is left positioned at the
+*     following card.
+*
+*     The Object returned may be of any class, depending on the
+*     description that was read, and other AST routines may be used to
+*     validate it (for example, by examining its Class or ID attribute
+c     using astGetC). If further NATIVE encoded Object descriptions
+f     using AST_GETC). If further NATIVE encoded Object descriptions
+c     exist in the FitsChan, subsequent calls to astRead will return
+f     exist in the FitsChan, subsequent calls to AST_READ will return
+*     the Objects they describe in sequence (and destroy their
+*     descriptions) until no more remain between the current card and
+*     the "end-of-file".
+*
+c     When astWrite is used to write an Object using NATIVE encoding,
+f     When AST_WRITE is used to write an Object using NATIVE encoding,
+*     a description of the Object is inserted immediately before the
+*     current card (as determined by the Card attribute).  Multiple
+*     Object descriptions may be written in this way and are stored
+*     separately (and sequentially if the Card attribute is not
+*     modified between the writes). A write operation using the NATIVE
+*     encoding does not over-write previously written Object
+*     descriptions. Note, however, that subsequent behaviour is
+*     undefined if an Object description is written inside a
+*     previously-written description, so this should be avoided.
+*
+*     When an Object is written to a FitsChan using NATIVE encoding,
+c     astWrite should (barring errors) always transfer data and
+f     AST_WRITE should (barring errors) always transfer data and
+*     return a value of 1.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(FitsChan,Encoding,encoding,UNKNOWN_ENCODING)
+astMAKE_SET(FitsChan,Encoding,int,encoding,(
+   value == NATIVE_ENCODING ||
+   value == FITSPC_ENCODING ||
+   value == FITSWCS_ENCODING ||
+   value == FITSIRAF_ENCODING ||
+   value == FITSAIPS_ENCODING ||
+   value == FITSAIPSPP_ENCODING ||
+   value == FITSCLASS_ENCODING ||
+   value == DSS_ENCODING ? value :
+   (astError( AST__BADAT, "astSetEncoding: Unknown encoding system %d "
+              "supplied.", status, value ), UNKNOWN_ENCODING )))
+astMAKE_TEST(FitsChan,Encoding,( this->encoding != UNKNOWN_ENCODING ))
+
+/* DefB1950 */
+/* ======== */
+
+/*
+*att++
+*  Name:
+*     DefB1950
+
+*  Purpose:
+*     Use FK4 B1950 as defaults?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which specifies a default equinox
+*     and reference frame to use when reading a FrameSet from a FitsChan
+*     with a foreign (i.e. non-native) encoding. It is only used if the FITS
+*     header contains RA and DEC axes but contains no information about the
+*     reference frame or equinox. If this is the case, then values of FK4 and
+*     B1950 are assumed if the DefB1950 attribute has a non-zero value and
+*     ICRS is assumed if DefB1950 is zero. The default value for DefB1950
+*     depends on the value of the Encoding attribute: for FITS-WCS encoding
+*     the default is zero, and for all other encodings it is one.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(FitsChan,DefB1950,defb1950,-1)
+astMAKE_GET(FitsChan,DefB1950,int,1,(this->defb1950 == -1 ? (astGetEncoding(this)== FITSWCS_ENCODING?0:1): this->defb1950))
+astMAKE_SET(FitsChan,DefB1950,int,defb1950,( value ? 1 : 0 ))
+astMAKE_TEST(FitsChan,DefB1950,( this->defb1950 != -1 ))
+
+/* TabOK */
+/* ===== */
+
+/*
+*att++
+*  Name:
+*     TabOK
+
+*  Purpose:
+*     Should the FITS-WCS -TAB algorithm be recognised?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute is an integer value which indicates if the "-TAB"
+*     algorithm, defined in FITS-WCS paper III, should be supported by
+*     the FitsChan. The default value is zero. A zero or negative value
+*     results in no support for -TAB axes (i.e. axes that have "-TAB"
+*     in their CTYPE keyword value). In this case, the
+c     astWrite
+f     AST_WRITE
+*     method will return zero if the write operation would required the
+*     use of the -TAB algorithm, and the
+c     astRead
+f     AST_READ
+*     method will return
+c     a NULL pointer
+f     AST__NULL
+*     if any axis in the supplied header uses the -TAB algorithm.
+
+*     If TabOK is set to a non-zero positive integer, these methods will
+*     recognise and convert axes described by the -TAB algorithm, as
+*     follows:
+*
+c     The astWrite
+f     The AST_WRITE
+*     method will generate headers that use the -TAB algorithm (if
+*     possible) if no other known FITS-WCS algorithm can be used to
+*     describe the supplied FrameSet. This will result in a table of
+*     coordinate values and index vectors being stored in the FitsChan.
+*     After the write operation, the calling application should check to
+*     see if such a table has been stored in the FitsChan. If so, the
+*     table should be retrived from the FitsChan using the
+c     astGetTables
+f     AST_GETTABLES
+*     method, and the data (and headers) within it copied into a new
+*     FITS binary table extension. See
+c     astGetTables
+f     AST_GETTABLES
+*     for more information. The FitsChan uses a FitsTable object to store
+*     the table data and headers. This FitsTable will contain the required
+*     columns and headers as described by FITS-WCS paper III - the
+*     coordinates array will be in a column named "COORDS", and the index
+*     vector(s) will be in columns named "INDEX<i>" (where <i> is the index
+*     of the corresponding FITS WCS  axis). Note, index vectors are only
+*     created if required. The EXTNAME value will be set to the value of the
+*     AST__TABEXTNAME constant (currently "WCS-TAB"). The EXTVER header
+*     will be set to the positive integer value assigned to the TabOK
+*     attribute. No value will be stored for the EXTLEVEL header, and should
+*     therefore be considered to default to 1.
+*
+c     The astRead
+f     The AST_READ
+*     method will generate a FrameSet from headers that use the -TAB
+*     algorithm so long as the necessary FITS binary tables are made
+*     available. There are two ways to do this: firstly, if the application
+*     knows which FITS binary tables will be needed, then it can create a
+*     Fitstable describing each such table and store it in the FitsChan
+*     (using method
+c     astPutTables or astPutTable) before invoking the astRead method.
+f     AST_PUTTABLES or AST_PUTTABLE) before invoking the AST_READ method.
+*     Secondly, if the application does not know which FITS binary tables
+*     will be needed by
+c     astRead,
+f     AST_READ,
+*     then it can register a call-back function with the FitsChan using
+*     method
+c     astTableSource.
+f     AST_TABLESOURCE.
+*     This call-back function will be called from within
+c     astRead
+f     AST_READ
+*     if and when a -TAB header is encountered. When called, its arguments
+*     will give the name, version and level of the FITS extension containing
+*     a required table. The call-back function should read this table from
+*     an external FITS file, and create a corresponding FitsTable which
+*     it should then return to
+c     astRead. Note, currently astRead
+f     AST_READ. Note, currently AST_READ
+*     can only handle -TAB headers that describe 1-dimensional (i.e.
+*     separable) axes.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(FitsChan,TabOK,tabok,-INT_MAX)
+astMAKE_GET(FitsChan,TabOK,int,0,(this->tabok == -INT_MAX ? 0 : this->tabok))
+astMAKE_SET(FitsChan,TabOK,int,tabok,value)
+astMAKE_TEST(FitsChan,TabOK,( this->tabok != -INT_MAX ))
+
+/* CarLin */
+/* ====== */
+
+/*
+*att++
+*  Name:
+*     CarLin
+
+*  Purpose:
+*     Ignore spherical rotations on CAR projections?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which specifies how FITS "CAR"
+*     (plate carree, or "Cartesian") projections should be treated when
+*     reading a FrameSet from a foreign encoded FITS header. If zero (the
+*     default), it is assumed that the CAR projection conforms to the
+*     conventions described in the FITS world coordinate system (FITS-WCS)
+*     paper II "Representation of Celestial Coordinates in FITS" by
+*     M. Calabretta & E.W. Greisen. If CarLin is non-zero, then these
+*     conventions are ignored, and it is assumed that the mapping from pixel
+*     coordinates to celestial coordinates is a simple linear transformation
+*     (hence the attribute name "CarLin"). This is appropriate for some older
+*     FITS data which claims to have a "CAR" projection, but which in fact do
+*     not conform to the conventions of the FITS-WCS paper.
+*
+*     The FITS-WCS paper specifies that headers which include a CAR projection
+*     represent a linear mapping from pixel coordinates to "native spherical
+*     coordinates", NOT celestial coordinates. An extra mapping is then
+*     required from native spherical to celestial. This mapping is a 3D
+*     rotation and so the overall Mapping from pixel to celestial coordinates
+*     is NOT linear. See the FITS-WCS papers for further details.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(FitsChan,CarLin,carlin,-1)
+astMAKE_GET(FitsChan,CarLin,int,1,(this->carlin == -1 ? 0 : this->carlin))
+astMAKE_SET(FitsChan,CarLin,int,carlin,( value ? 1 : 0 ))
+astMAKE_TEST(FitsChan,CarLin,( this->carlin != -1 ))
+
+/* PolyTan */
+/* ======= */
+
+/*
+*att++
+*  Name:
+*     PolyTan
+
+*  Purpose:
+*     Use PVi_m keywords to define distorted TAN projection?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute is a boolean value which specifies how FITS "TAN"
+*     projections should be treated when reading a FrameSet from a foreign
+*     encoded FITS header. If zero, the projection is assumed to conform
+*     to the published FITS-WCS standard. If positive, the convention
+*     for a distorted TAN projection included in an early draft version
+*     of FITS-WCS paper II are assumed. In this convention the
+*     coefficients of a polynomial distortion to be applied to
+*     intermediate world coordinates are specified by the PVi_m keywords.
+*     This convention was removed from the paper before publication and so
+*     does not form part of the standard. Indeed, it is incompatible with
+*     the published standard because it re-defines the meaning of the
+*     first five PVi_m keywords on the longitude axis, which are reserved
+*     by the published standard for other purposes. However, headers that
+*     use this convention are still to be found, for instance the SCAMP
+*     utility (http://www.astromatic.net/software/scamp) creates them.
+*
+*     The default value for the PolyTan attribute is -1. A negative
+*     values causes the used convention to depend on the contents
+*     of the FitsChan. If the FitsChan contains any PVi_m keywords for
+*     the latitude axis, or if it contains PVi_m keywords for the
+*     longitude axis with "m" greater than 4, then the distorted TAN
+*     convention is used. Otherwise, the standard convention is used.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(FitsChan,PolyTan,polytan,-INT_MAX)
+astMAKE_SET(FitsChan,PolyTan,int,polytan,value)
+astMAKE_TEST(FitsChan,PolyTan,( this->polytan != -INT_MAX ))
+astMAKE_GET(FitsChan,PolyTan,int,-1,(this->polytan == -INT_MAX ? -1 : this->polytan))
+
+/* Iwc */
+/* === */
+
+/*
+*att++
+*  Name:
+*     Iwc
+
+*  Purpose:
+*     Include a Frame representing FITS-WCS intermediate world coordinates?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which is used when a FrameSet is
+*     read from a FitsChan with a foreign FITS encoding (e.g. FITS-WCS) using
+c     astRead.
+f     AST_READ.
+*     If it has a non-zero value then the returned FrameSet will include
+*     Frames representing "intermediate world coordinates" (IWC). These
+*     Frames will have Domain name "IWC" for primary axis descriptions, and
+*     "IWCa" for secondary axis descriptions, where "a" is replaced by
+*     the single alternate axis description character, as used in the
+*     FITS-WCS header. The default value for "Iwc" is zero.
+*
+*     FITS-WCS paper 1 defines IWC as a Cartesian coordinate system with one
+*     axis for each WCS axis, and is the coordinate system produced by the
+*     rotation matrix (represented by FITS keyword PCi_j, CDi_j, etc).
+*     For instance, for a 2-D FITS-WCS header describing projected
+*     celestial longitude and latitude, the intermediate world
+*     coordinates represent offsets in degrees from the reference point
+*     within the plane of projection.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(FitsChan,Iwc,iwc,-1)
+astMAKE_GET(FitsChan,Iwc,int,1,(this->iwc == -1 ? 0 : this->iwc))
+astMAKE_SET(FitsChan,Iwc,int,iwc,( value ? 1 : 0 ))
+astMAKE_TEST(FitsChan,Iwc,( this->iwc != -1 ))
+
+/*
+*att++
+*  Name:
+*     CDMatrix
+
+*  Purpose:
+*     Use CDi_j keywords to represent pixel scaling, rotation, etc?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which specifies how the linear
+*     transformation from pixel coordinates to intermediate world
+*     coordinates should be represented within a FitsChan when using
+*     FITS-WCS encoding. This transformation describes the scaling,
+*     rotation, shear, etc., of the pixel axes.
+*
+*     If the attribute has a non-zero value then the transformation is
+*     represented by a set of CDi_j keywords representing a square matrix
+*     (where "i" is the index of an intermediate world coordinate axis
+*     and "j" is the index of a pixel axis). If the attribute has a zero
+*     value the transformation is represented by a set of PCi_j keywords
+*     (which also represent a square matrix) together with a corresponding
+*     set of CDELTi keywords representing the axis scalings. See FITS-WCS
+*     paper II "Representation of Celestial Coordinates in FITS" by
+*     M. Calabretta & E.W. Greisen, for a complete description of these two
+*     schemes.
+*
+*     The default value of the CDMatrix attribute is determined by the
+*     contents of the FitsChan at the time the attribute is accessed. If
+*     the FitsChan contains any CDi_j keywords then the default value is
+*     non-zero. Otherwise it is zero. Note, reading a FrameSet from a
+*     FitsChan will in general consume any CDi_j keywords present in the
+*     FitsChan. Thus the default value for CDMatrix following a read will
+*     usually be zero, even if the FitsChan originally contained some
+*     CDi_j keywords. This behaviour is similar to that of the Encoding
+*     attribute, the default value for which is determined by the contents
+*     of the FitsChan at the time the attribute is accessed. If you wish
+*     to retain the original value of the CDMatrix attribute (that is,
+*     the value before reading the FrameSet) then you should enquire the
+*     default value before doing the read, and then set that value
+*     explicitly.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(FitsChan,CDMatrix,cdmatrix,-1)
+astMAKE_SET(FitsChan,CDMatrix,int,cdmatrix,( value ? 1 : 0 ))
+astMAKE_TEST(FitsChan,CDMatrix,( this->cdmatrix != -1 ))
+
+/* Clean */
+/* ===== */
+
+/*
+*att++
+*  Name:
+*     Clean
+
+*  Purpose:
+*     Remove cards used whilst reading even if an error occurs?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute indicates whether or not cards should be removed from
+*     the FitsChan if an error occurs within
+c     astRead.
+f     AST_READ.
+*     A succesful read on a FitsChan always results in the removal of
+*     the cards which were involved in the description of the returned
+*     Object. However, in the event of an error during the read (for instance
+*     if the cards in the FitsChan have illegal values, or if some required
+*     cards are missing) no cards will be removed from the FitsChan if
+*     the Clean attribute is zero (the default). If Clean is non-zero then
+*     any cards which were used in the aborted attempt to read an object
+*     will be removed.
+*
+*     This provides a means of "cleaning" a FitsChan of WCS related cards
+*     which works even in the event of the cards not forming a legal WCS
+*     description.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(FitsChan,Clean,clean,-1)
+astMAKE_SET(FitsChan,Clean,int,clean,( value ? 1 : 0 ))
+astMAKE_TEST(FitsChan,Clean,( this->clean != -1 ))
+
+/*
+*att++
+*  Name:
+*     FitsAxisOrder
+
+*  Purpose:
+*     Frame title.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies the order for the WCS axes in any new
+*     FITS-WCS headers created using the
+c     astWrite
+f     AST_WRITE
+*     method.
+*
+*     The value of the FitsAxisOrder attribute can be either "<auto>"
+*     (the default value), "<copy>" or a space-separated list of axis
+*     symbols:
+*
+*     "<auto>": causes the WCS axis order to be chosen automatically so that
+*     the i'th WCS axis in the new FITS header is the WCS axis which is
+*     more nearly parallel to the i'th pixel axis.
+*
+*     "<copy>": causes the WCS axis order to be set so that the i'th WCS
+*     axis in the new FITS header is the i'th WCS axis in the current
+*     Frame of the FrameSet being written out to the header.
+*
+*     "Sym1 Sym2...": the space-separated list is seached in turn for
+*     the Symbol attribute of each axis in the current Frame of the
+*     FrameSet. The order in which these Symbols occur within the
+*     space-separated list defines the order of the WCS axes in the
+*     new FITS header. An error is reported if Symbol for a current
+*     Frame axis is not present in the supplied list. However, no error
+*     is reported if the list contains extra words that do not correspond
+*     to the Symbol of any current Frame axis.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(FitsChan,FitsAxisOrder,fitsaxisorder,astFree( this->fitsaxisorder ))
+astMAKE_GET(FitsChan,FitsAxisOrder,const char *,NULL,(this->fitsaxisorder ? this->fitsaxisorder : "<auto>" ))
+astMAKE_SET(FitsChan,FitsAxisOrder,const char *,fitsaxisorder,astStore( this->fitsaxisorder, value, strlen( value ) + (size_t) 1 ))
+astMAKE_TEST(FitsChan,FitsAxisOrder,( this->fitsaxisorder != NULL ))
+
+/* FitsDigits. */
+/* =========== */
+
+/*
+*att++
+*  Name:
+*     FitsDigits
+
+*  Purpose:
+*     Digits of precision for floating point FITS values.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute gives the number of significant decimal digits to
+*     use when formatting floating point values for inclusion in the
+*     FITS header cards within a FitsChan.
+*
+*     By default, a positive value is used which results in no loss of
+c     information, assuming that the value's precision is double.
+f     information, assuming that the value is double precision.
+*     Usually, this causes no problems.
+*
+*     However, to adhere strictly to the recommendations of the FITS
+*     standard, the width of the formatted value (including sign,
+*     decimal point and exponent) ought not to be more than 20
+*     characters. If you are concerned about this, you should set
+*     FitsDigits to a negative value, such as -15. In this case, the
+*     absolute value (+15) indicates the maximum number of significant
+*     digits to use, but the actual number used may be fewer than this
+*     to ensure that the FITS recommendations are satisfied. When
+*     using this approach, the resulting number of significant digits
+*     may depend on the value being formatted and on the presence of
+*     any sign, decimal point or exponent.
+*
+*     The value of this attribute is effective when FITS header cards
+*     are output, either using
+c     astFindFits or by the action of the FitsChan's sink function
+f     AST_FINDFITS or by the action of the FitsChan's sink routine
+*     when it is finally deleted.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(FitsChan,FitsDigits,fitsdigits,DBL_DIG)
+astMAKE_GET(FitsChan,FitsDigits,int,DBL_DIG,this->fitsdigits)
+astMAKE_SET(FitsChan,FitsDigits,int,fitsdigits,value)
+astMAKE_TEST(FitsChan,FitsDigits,( this->fitsdigits != DBL_DIG ))
+
+/* CardComm */
+/* ======== */
+
+/*
+*att++
+*  Name:
+*     CardComm
+
+*  Purpose:
+*     The comment for the current card in a FitsChan.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String, read-only.
+
+*  Description:
+*     This attribute gives the comment for the current card of the
+*     FitsChan. A zero-length string is returned if the card has no comment.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+
+/* CardName */
+/* ======== */
+
+/*
+*att++
+*  Name:
+*     CardName
+
+*  Purpose:
+*     The keyword name of the current card in a FitsChan.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String, read-only.
+
+*  Description:
+*     This attribute gives the name of the keyword for the
+*     current card of the FitsChan.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+
+/* CardType */
+/* ======== */
+
+/*
+*att++
+*  Name:
+*     CardType
+
+*  Purpose:
+*     The data type of the current card in a FitsChan.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute gives the data type of the keyword value for the
+*     current card of the FitsChan. It will be one of the following
+*     integer constants: AST__NOTYPE, AST__COMMENT, AST__INT, AST__FLOAT,
+*     AST__STRING, AST__COMPLEXF, AST__COMPLEXI, AST__LOGICAL,
+*     AST__CONTINUE, AST__UNDEF.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+
+/* Ncard */
+/* ===== */
+
+/*
+*att++
+*  Name:
+*     Ncard
+
+*  Purpose:
+*     Number of FITS header cards in a FitsChan.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute gives the total number of FITS header cards
+*     stored in a FitsChan. It is updated as cards are added or
+*     deleted.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+
+/* Nkey */
+/* ==== */
+
+/*
+*att++
+*  Name:
+*     Nkey
+
+*  Purpose:
+*     Number of unique FITS keywords in a FitsChan.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute gives the total number of unique FITS keywords
+*     stored in a FitsChan. It is updated as cards are added or
+*     deleted. If no keyword occurrs more than once in the FitsChan, the
+*     Ncard and Nkey attributes will be equal. If any keyword occurrs
+*     more than once, the Nkey attribute value will be smaller than
+*     the Ncard attribute value.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+
+/* Warnings. */
+/* ======== */
+
+/*
+*att++
+*  Name:
+*     Warnings
+
+*  Purpose:
+*     Controls the issuing of warnings about various conditions.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String
+
+*  Description:
+*     This attribute controls the issuing of warnings about selected
+*     conditions when an Object or keyword is read from or written to a
+*     FitsChan. The value supplied for the Warnings attribute should
+*     consist of a space separated list of condition names (see the
+*     AllWarnings attribute for a list of the currently defined names).
+*     Each name indicates a condition which should be reported. The default
+*     value for Warnings is the string "BadKeyName BadKeyValue Tnx Zpx
+*     BadCel BadMat BadPV BadCTYPE".
+*
+*     The text of any warning will be stored within the FitsChan in the
+*     form of one or more new header cards with keyword ASTWARN. If
+*     required, applications can check the FitsChan for ASTWARN cards
+c     (using astFindFits) after the call to astRead or astWrite has been
+f     (using AST_FINDFITS) after the call to AST_READ or AST_WRITE has been
+*     performed, and report the text of any such cards to the user. ASTWARN
+*     cards will be propagated to any output header unless they are
+c     deleted from the FitsChan using astDelFits.
+f     deleted from the FitsChan using astDelFits.
+
+*  Notes:
+*     This attribute only controls the warnings that are to be stored as
+*     a set of header cards in the FitsChan as described above. It has no
+*     effect on the storage of warnings in the parent Channel structure.
+*     All warnings are stored in the parent Channel structure, from where
+*     they can be retrieved using the
+c     astWarnings
+f     AST_WARNINGS
+*     function.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+
+/* Clear the Warnings value by freeing the allocated memory and assigning
+   a NULL pointer. */
+astMAKE_CLEAR(FitsChan,Warnings,warnings,astFree( this->warnings ))
+
+/* If the Warnings value is not set, supply a default in the form of a
+   pointer to the constant string "BadKeyName BadKeyValue Tnx Zpx BadCel BadMat BadCTYPE". */
+astMAKE_GET(FitsChan,Warnings,const char *,NULL,( this->warnings ? this->warnings :
+                                                            "BadKeyName BadKeyValue Tnx Zpx BadPV BadCel BadMat BadCTYPE" ))
+
+/* Set a Warnings value by freeing any previously allocated memory, allocating
+   new memory, storing the string and saving the pointer to the copy.
+   First check that the list does not contain any unknown conditions. If
+   it does, an error is reported by GoodWarns and the current attribute value
+   is retained. */
+astMAKE_SET(FitsChan,Warnings,const char *,warnings,( GoodWarns( value, status ) ?
+                                astStore( this->warnings, value, strlen( value ) + (size_t) 1 ) :
+                                this->warnings))
+
+/* The Warnings value is set if the pointer to it is not NULL. */
+astMAKE_TEST(FitsChan,Warnings,( this->warnings != NULL ))
+
+/* AllWarnings. */
+/* ============ */
+
+/*
+*att++
+*  Name:
+*     AllWarnings
+
+*  Purpose:
+*     A list of all currently available condition names.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String, read-only
+
+*  Description:
+*     This read-only attribute is a space separated list of all the conditions
+*     names recognized by the Warnings attribute. The names are listed
+*     below.
+
+*  Conditions:
+*     The following conditions are currently recognised (all are
+*     case-insensitive):
+*
+*     - "BadCel": This condition arises when reading a FrameSet from a
+*     non-Native encoded FitsChan if an unknown celestial co-ordinate
+*     system is specified by the CTYPE keywords.
+*
+*     - "BadCTYPE": This condition arises when reading a FrameSet from a
+*     non-Native encoded FitsChan if an illegal algorithm code is specified
+*     by a CTYPE keyword, and the illegal code can be converted to an
+*     equivalent legal code.
+*
+*     - "BadKeyName": This condition arises if a FITS keyword name is
+*     encountered that contains an illegal character (i.e. one not allowed
+*     by the FITS standard).
+*
+*     - "BadKeyValue": This condition arises if the value of a FITS keyword
+*     cannot be determined from the content of the header card.
+*
+*     - "BadLat": This condition arises when reading a FrameSet from a
+*     non-Native encoded FitsChan if the latitude of the reference point
+*     has an absolute value greater than 90 degrees. The actual absolute
+*     value used is set to exactly 90 degrees in these cases.
+*
+*     - "BadMat": This condition arises if the matrix describing the
+*     transformation from pixel offsets to intermediate world coordinates
+*     cannot be inverted. This matrix describes the scaling, rotation, shear,
+*     etc., applied to the pixel axes, and is specified by keywords such as
+*     PCi_j, CDi_j, CROTA, etc. For example, the matrix will not be invertable
+*     if any rows or columns consist entirely of zeros. The FITS-WCS Paper I
+*     "Representation of World Coordinates in FITS" by Greisen & Calabretta
+*     requires that this matrix be invertable. Many operations (such as
+*     grid plotting) will not be possible if the matrix cannot be inverted.
+*
+*     - "BadPV": This condition arises when reading a FrameSet from a
+*     non-Native encoded FitsChan. It is issued if a PVi_m header is found
+*     that refers to a projection parameter that is not used by the
+*     projection type specified by CTYPE, or the PV values are otherwise
+*     inappropriate for the projection type.
+*
+*     - "BadVal": This condition arises when reading a FrameSet from a
+*     non-Native encoded FitsChan if it is not possible to convert the
+*     value of a FITS keywords to the expected type. For instance, this
+*     can occur if the FITS header contains a string value for a keyword
+*     which should have a floating point value, or if the keyword has no
+*     value at all (i.e. is a comment card).
+*
+*     - "Distortion": This condition arises when reading a FrameSet from a
+*     non-Native encoded FitsChan if any of the CTYPE keywords specify an
+*     unsupported distortion code using the "4-3-3" format specified in
+*     FITS-WCS paper IV. Such distortion codes are ignored.
+*
+*     - "NoCTYPE": This condition arises if a default CTYPE value is used
+c     within astRead, due to no value being present in the supplied FitsChan.
+f     within AST_READ, due to no value being present in the supplied FitsChan.
+*     This condition is only tested for when using non-Native encodings.
+*
+*     - "NoEquinox": This condition arises if a default equinox value is used
+c     within astRead, due to no value being present in the supplied FitsChan.
+f     within AST_READ, due to no value being present in the supplied FitsChan.
+*     This condition is only tested for when using non-Native encodings.
+*
+*     - "NoRadesys": This condition arises if a default reference frame is
+c     used for an equatorial co-ordinate system within astRead, due to no
+f     used for an equatorial co-ordinate system within AST_READ, due to no
+*     value being present in the supplied FitsChan. This condition is only
+*     tested for when using non-Native encodings.
+*
+*     - "NoLonpole": This condition arises if a default value is used for
+c     the LONPOLE keyword within astRead, due to no value being present
+f     the LONPOLE keyword within AST_READ, due to no value being present
+*     in the supplied FitsChan. This condition is only tested for when
+*     using non-Native encodings.
+*
+*     - "NoLatpole": This condition arises if a default value is used for
+c     the LATPOLE keyword within astRead, due to no value being present
+f     the LATPOLE keyword within AST_READ, due to no value being present
+*     in the supplied FitsChan. This condition is only tested for when
+*     using non-Native encodings.
+*
+*     - "NoMjd-obs": This condition arises if a default value is used for
+c     the date of observation within astRead, due to no value being present
+f     the date of observation within AST_READ, due to no value being present
+*     in the supplied FitsChan. This condition is only tested for when using
+*     non-Native encodings.
+*
+*     - "Tnx": This condition arises if a FrameSet is read from a FITS
+*     header containing an IRAF "TNX" projection which includes terms
+*     not supproted by AST. Such terms are ignored and so the resulting
+*     FrameSet may be inaccurate.
+*
+*     - "Zpx": This condition arises if a FrameSet is read from a FITS
+*     header containing an IRAF "ZPX" projection which includes "lngcor"
+*     or "latcor" correction terms. These terms are not supported by AST
+*     and are ignored. The resulting FrameSet may therefore be inaccurate.
+
+*  Applicability:
+*     FitsChan
+*        All FitsChans have this attribute.
+*att--
+*/
+
+/* Copy constructor. */
+/* ----------------- */
+
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for FitsChan objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for FitsChan objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the FitsChan to be copied.
+*     objout
+*        Pointer to the FitsChan being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The source and sink functions are not propagated (i.e. the
+*     pointers are set NULL in the output FitsChan).
+*     - This constructor makes a deep copy, including a copy of the
+*     keyword values.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS        /* Declare the thread specific global data */
+   const char *class;        /* Pointer to object class */
+   AstFitsChan *in;          /* Pointer to input FitsChan */
+   AstFitsChan *out;         /* Pointer to output FitsChan */
+   int *flags;
+   int icard;
+   int old_ignore_used;      /* Original value of external variable ignore_used */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(objin);
+
+/* Obtain pointers to the input and output FitsChans. */
+   in = (AstFitsChan *) objin;
+   out = (AstFitsChan *) objout;
+
+/* Nullify all pointers in the output FitsChan so that the input
+   data will not be deleted in the event of an error occurring. */
+   out->card = NULL;
+   out->head = NULL;
+   out->keyseq = NULL;
+   out->keywords = NULL;
+   out->source = NULL;
+   out->saved_source = NULL;
+   out->source_wrap = NULL;
+   out->sink = NULL;
+   out->sink_wrap = NULL;
+   out->warnings = NULL;
+   out->tabsource = NULL;
+   out->tabsource_wrap = NULL;
+
+/* Store the object class. */
+   class = astGetClass( in );
+
+/* Ensure all cards are copied, including those already read by astRead. */
+   old_ignore_used = ignore_used;
+   ignore_used = 0;
+
+/* Save the current card index in the input FitsChan. */
+   icard = astGetCard( in );
+
+/* Rewind the input FitsChan. */
+   astClearCard( in );
+
+/* Copy all the FitsCard structures from input to output. */
+   while( !astFitsEof( in ) && astOK ){
+
+/* Get a pointer to the flags mask for this card. */
+      flags = CardFlags( in, status );
+
+/* Store a new card in the output, holding the same information as the
+   input card. */
+      NewCard( out, CardName( in, status ), CardType( in, status ), CardData( in, NULL, status ),
+               CardComm( in, status ), (flags?(*flags):0), status );
+
+/* Move on to the next input card. */
+      MoveCard( in, 1, "astCopy", class, status );
+   }
+
+/* Set the current card in both input and output to the current input
+   card on entry. */
+   astSetCard( in, icard );
+   astSetCard( out, icard );
+
+/* Copy the list of keyword sequence numbers used. */
+   if( in->keyseq ) out->keyseq = astCopy( in->keyseq );
+
+/* Copy the Warnings attribute value */
+   if( in->warnings ) out->warnings = astStore( NULL, in->warnings,
+                                                strlen( in->warnings ) + 1 );
+
+/* Copy any tables currently in the FitsChan structure. */
+   if( in->tables ) out->tables = astCopy( in->tables );
+
+/* Reinstate the original setting of the external ignore_used variable. */
+   ignore_used = old_ignore_used;
+
+/* If an error occurred, delete the contents of the output Object. */
+   if( !astOK ) Delete( objout, status );
+}
+
+/* Destructor. */
+/* ----------- */
+
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for FitsChan objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for FitsChan objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the FitsChan to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstFitsChan *this;            /* Pointer to FitsChan */
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) obj;
+
+/* Write out the contents of the FitsChan using the sink function
+   provided when it was created. */
+   WriteToSink( this, status );
+
+/* Remove all cards from the FitsChan. */
+   EmptyFits( this, status );
+}
+
+/* Dump function. */
+/* -------------- */
+
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for FitsChan objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the FitsChan class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsChan whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstFitsChan *this;            /* Pointer to the FitsChan structure */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   const char *class;            /* Object class */
+   const char *sval;             /* Pointer to string value */
+   int cardtype;                 /* Keyword data type */
+   int flags;                    /* Keyword flags */
+   int icard;                    /* Index of current card */
+   int ival;                     /* Integer value */
+   int ncard;                    /* No. of cards dumped so far */
+   int old_ignore_used;          /* Original value of external variable ignore_used */
+   int set;                      /* Attribute value set? */
+   void *data;                   /* Pointer to keyword data value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the FitsChan structure. */
+   this = (AstFitsChan *) this_object;
+
+/* Store the object class. */
+   class = astGetClass( this );
+
+/* Save the index of ht ecurrent card. */
+   icard = astGetCard( this );
+
+/* Write out values representing the instance variables for the
+   FitsChan class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* Card. */
+/* ----- */
+   astWriteInt( channel, "Card", 1, 1, icard, "Index of current card" );
+
+/* Encoding. */
+/* --------- */
+   set = TestEncoding( this, status );
+   ival = set ? GetEncoding( this, status ) : astGetEncoding( this );
+   if( ival > UNKNOWN_ENCODING && ival <= MAX_ENCODING ) {
+      astWriteString( channel, "Encod", set, 1, xencod[ival], "Encoding system" );
+   } else {
+      astWriteString( channel, "Encod", set, 1, UNKNOWN_STRING, "Encoding system" );
+   }
+
+/* FitsAxisOrder. */
+/* -------------- */
+   set = TestFitsAxisOrder( this, status );
+   sval = set ? GetFitsAxisOrder( this, status ) : astGetFitsAxisOrder( this );
+   astWriteString( channel, "FAxOrd", set, 1, sval,
+                      "Order of WCS axes in new FITS headers" );
+
+/* FitsDigits. */
+/* ----------- */
+   set = TestFitsDigits( this, status );
+   ival = set ? GetFitsDigits( this, status ) : astGetFitsDigits( this );
+   astWriteInt( channel, "FitsDg", set, 1, ival, "No. of digits for floating point values" );
+
+/* DefB1950 */
+/* -------- */
+   set = TestDefB1950( this, status );
+   ival = set ? GetDefB1950( this, status ) : astGetDefB1950( this );
+   astWriteInt( channel, "DfB1950", set, 1, ival, (ival ? "Default to FK4 B1950": "Default to ICRS") );
+
+/* TabOK */
+/* ----- */
+   set = TestTabOK( this, status );
+   ival = set ? GetTabOK( this, status ) : astGetTabOK( this );
+   astWriteInt( channel, "TabOK", set, 1, ival, ( ival > 0 ? "EXTVER value for -TAB headers": "Do not support -TAB CTYPE codes") );
+
+/* CDMatrix */
+/* -------- */
+   set = TestCDMatrix( this, status );
+   ival = set ? GetCDMatrix( this, status ) : astGetCDMatrix( this );
+   astWriteInt( channel, "CdMat", set, 1, ival, (ival ? "Use CD Matrix":"Use PC matrix") );
+
+/* CarLin */
+/* ------ */
+   set = TestCarLin( this, status );
+   ival = set ? GetCarLin( this, status ) : astGetCarLin( this );
+   astWriteInt( channel, "CarLin", set, 1, ival, (ival ? "Use simple linear CAR projections": "Use full FITS-WCS CAR projections") );
+
+/* PolyTan */
+/* ------- */
+   set = TestPolyTan( this, status );
+   ival = set ? GetPolyTan( this, status ) : astGetPolyTan( this );
+   astWriteInt( channel, "PolyTan", set, 0, ival, (ival ? "Use distorted TAN convention": "Use standard TAN convention") );
+
+/* Iwc */
+/* --- */
+   set = TestIwc( this, status );
+   ival = set ? GetIwc( this, status ) : astGetIwc( this );
+   astWriteInt( channel, "Iwc", set, 1, ival, (ival ? "Include an IWC Frame": "Do not include an IWC Frame") );
+
+/* Clean */
+/* ----- */
+   set = TestClean( this, status );
+   ival = set ? GetClean( this, status ) : astGetClean( this );
+   astWriteInt( channel, "Clean", set, 0, ival, "Always remove used cards?" );
+
+/* Warnings. */
+/* --------- */
+   set = TestWarnings( this, status );
+   sval = set ? GetWarnings( this, status ) : astGetWarnings( this );
+   astWriteString( channel, "Warn", set, 1, sval, "Warnings to be reported" );
+
+/* Now do instance variables which are not attributes. */
+/* =================================================== */
+
+/* Ensure all cards are copied, including those already read by astRead. */
+   old_ignore_used = ignore_used;
+   ignore_used = 0;
+
+/* Rewind the FitsChan. */
+   astClearCard( this );
+
+/* Dump each card. */
+   ncard = 1;
+   while( !astFitsEof( this ) && astOK ){
+
+/* Write out the keyword name. */
+      if( CardName( this, status ) ){
+         (void) sprintf( buff, "Nm%d", ncard );
+         astWriteString( channel, buff, 1, 1, CardName( this, status ),
+                         "FITS keyword name" );
+      }
+
+/* Write out the keyword type. */
+      cardtype = CardType( this, status );
+      (void) sprintf( buff, "Ty%d", ncard );
+      astWriteString( channel, buff, 1, 1, type_names[ cardtype ],
+                      "FITS keyword data type" );
+
+/* Write out the flag values if any are non-zero. */
+      flags = *CardFlags( this, status );
+      if( flags ){
+         (void) sprintf( buff, "Fl%d", ncard );
+         astWriteInt( channel, buff, 1, 1, flags, "FITS keyword flags" );
+      }
+
+/* Write out the data value, if defined, using the appropriate data type. */
+      data = CardData( this, NULL, status );
+      if( data && cardtype != AST__UNDEF ){
+         if( cardtype == AST__FLOAT ){
+            (void) sprintf( buff, "Dt%d", ncard );
+            astWriteDouble( channel, buff, 1, 1, *( (double *) data ),
+                            "FITS keyword value" );
+         } else if( cardtype == AST__STRING || cardtype == AST__CONTINUE ){
+            (void) sprintf( buff, "Dt%d", ncard );
+            astWriteString( channel, buff, 1, 1, (char *) data,
+                            "FITS keyword value" );
+         } else if( cardtype == AST__INT ){
+            (void) sprintf( buff, "Dt%d", ncard );
+            astWriteInt( channel, buff, 1, 1, *( (int *) data ),
+                         "FITS keyword value" );
+         } else if( cardtype == AST__LOGICAL ){
+            (void) sprintf( buff, "Dt%d", ncard );
+            astWriteInt( channel, buff, 1, 1, *( (int *) data ),
+                         "FITS keyword value" );
+         } else if( cardtype == AST__COMPLEXF ){
+            (void) sprintf( buff, "Dr%d", ncard );
+            astWriteDouble( channel, buff, 1, 1, *( (double *) data ),
+                            "FITS keyword real value" );
+            (void) sprintf( buff, "Di%d", ncard );
+            astWriteDouble( channel, buff, 1, 1, *( ( (double *) data ) + 1 ),
+                            "FITS keyword imaginary value" );
+         } else if( cardtype == AST__COMPLEXI ){
+            (void) sprintf( buff, "Dr%d", ncard );
+            astWriteInt( channel, buff, 1, 1, *( (int *) data ),
+                         "FITS keyword real value" );
+            (void) sprintf( buff, "Di%d", ncard );
+            astWriteInt( channel, buff, 1, 1, *( ( (int *) data ) + 1 ),
+                         "FITS keyword imaginary value" );
+         }
+      }
+
+/* Write out the keyword comment. */
+      if( CardComm( this, status ) ){
+         (void) sprintf( buff, "Cm%d", ncard );
+         astWriteString( channel, buff, 1, 1, CardComm( this, status ),
+                         "FITS keyword comment" );
+      }
+
+/* Move on to the next card. */
+      ncard++;
+      MoveCard( this, 1, "astDump", class, status );
+   }
+
+/* Dump any FitTables. */
+   if( this->tables ) {
+      astWriteObject( channel, "Tables", 1, 1, this->tables,
+                      "A KeyMap holding associated binary tables" );
+   }
+
+/* Reinstate the original setting of the external ignore_used variable. */
+   ignore_used = old_ignore_used;
+
+/* Reinstate the original current card. */
+   astSetCard( this, icard );
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+
+/* Implement the astIsAFitsChan and astCheckFitsChan functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(FitsChan,Channel)
+astMAKE_CHECK(FitsChan)
+AstFitsChan *astFitsChan_( const char *(* source)( void ),
+                           void (* sink)( const char * ),
+                           const char *options, int *status, ...) {
+
+/*
+*++
+*  Name:
+c     astFitsChan
+f     AST_FITSCHAN
+
+*  Purpose:
+*     Create a FitsChan.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "fitschan.h"
+c     AstFitsChan *astFitsChan( const char *(* source)( void ),
+c                               void (* sink)( const char * ),
+c                               const char *options, ... )
+f     RESULT = AST_FITSCHAN( SOURCE, SINK, OPTIONS, STATUS )
+
+*  Class Membership:
+*     FitsChan constructor.
+
+*  Description:
+*     This function creates a new FitsChan and optionally initialises
+*     its attributes.
+*
+*     A FitsChan is a specialised form of Channel which supports I/O
+*     operations involving the use of FITS (Flexible Image Transport
+*     System) header cards. Writing an Object to a FitsChan (using
+c     astWrite) will, if the Object is suitable, generate a
+f     AST_WRITE) will, if the Object is suitable, generate a
+*     description of that Object composed of FITS header cards, and
+*     reading from a FitsChan will create a new Object from its FITS
+*     header card description.
+*
+*     While a FitsChan is active, it represents a buffer which may
+*     contain zero or more 80-character "header cards" conforming to
+*     FITS conventions. Any sequence of FITS-conforming header cards
+*     may be stored, apart from the "END" card whose existence is
+*     merely implied.  The cards may be accessed in any order by using
+*     the FitsChan's integer Card attribute, which identifies a "current"
+*     card, to which subsequent operations apply. Searches
+c     based on keyword may be performed (using astFindFits), new
+c     cards may be inserted (astPutFits, astPutCards, astSetFits<X>) and
+c     existing ones may be deleted (astDelFits) or changed (astSetFits<X>).
+f     based on keyword may be performed (using AST_FINDFITS), new
+f     cards may be inserted (AST_PUTFITS, AST_PUTCARDS, AST_SETFITS<X>) and
+f     existing ones may be deleted (AST_DELFITS) or changed (AST_SETFITS<X>).
+*
+*     When you create a FitsChan, you have the option of specifying
+*     "source" and "sink" functions which connect it to external data
+*     stores by reading and writing FITS header cards. If you provide
+*     a source function, it is used to fill the FitsChan with header cards
+*     when it is accessed for the first time. If you do not provide a
+*     source function, the FitsChan remains empty until you explicitly enter
+c     data into it (e.g. using astPutFits, astPutCards, astWrite
+f     data into it (e.g. using AST_PUTFITS, AST_PUTCARDS, AST_WRITE
+*     or by using the SourceFile attribute to specifying a text file from
+*     which headers should be read). When the FitsChan is deleted, any
+*     remaining header cards in the FitsChan can be saved in either of
+*     two ways: 1) by specifying a value for the SinkFile attribute (the
+*     name of a text file to which header cards should be written), or 2)
+*     by providing a sink function (used to to deliver header cards to an
+*     external data store). If you do not provide a sink function or a
+*     value for SinkFile, any header cards remaining when the FitsChan
+*     is deleted will be lost, so you should arrange to extract them
+*     first if necessary
+c     (e.g. using astFindFits or astRead).
+f     (e.g. using AST_FINDFITS or AST_READ).
+*
+*     Coordinate system information may be described using FITS header
+*     cards using several different conventions, termed
+*     "encodings". When an AST Object is written to (or read from) a
+*     FitsChan, the value of the FitsChan's Encoding attribute
+*     determines how the Object is converted to (or from) a
+*     description involving FITS header cards. In general, different
+*     encodings will result in different sets of header cards to
+*     describe the same Object. Examples of encodings include the DSS
+*     encoding (based on conventions used by the STScI Digitised Sky
+*     Survey data), the FITS-WCS encoding (based on a proposed FITS
+*     standard) and the NATIVE encoding (a near loss-less way of
+*     storing AST Objects in FITS headers).
+*
+*     The available encodings differ in the range of Objects they can
+*     represent, in the number of Object descriptions that can coexist
+*     in the same FitsChan, and in their accessibility to other
+*     (external) astronomy applications (see the Encoding attribute
+*     for details). Encodings are not necessarily mutually exclusive
+*     and it may sometimes be possible to describe the same Object in
+*     several ways within a particular set of FITS header cards by
+*     using several different encodings.
+*
+c     The detailed behaviour of astRead and astWrite, when used with
+f     The detailed behaviour of AST_READ and AST_WRITE, when used with
+*     a FitsChan, depends on the encoding in use. In general, however,
+c     all use of astRead is destructive, so that FITS header cards
+f     all use of AST_READ is destructive, so that FITS header cards
+*     are consumed in the process of reading an Object, and are
+*     removed from the FitsChan (this deletion can be prevented for
+*     specific cards by calling the
+c     astRetainFits function).
+f     AST_RETAINFITS routine).
+*
+*     If the encoding in use allows only a single Object description
+*     to be stored in a FitsChan (e.g. the DSS, FITS-WCS and FITS-IRAF
+c     encodings), then write operations using astWrite will
+f     encodings), then write operations using AST_WRITE will
+*     over-write any existing Object description using that
+*     encoding. Otherwise (e.g. the NATIVE encoding), multiple Object
+*     descriptions are written sequentially and may later be read
+*     back in the same sequence.
+
+*  Parameters:
+c     source
+f     SOURCE = FUNCTION (Given)
+c        Pointer to a source function which takes no arguments and
+c        returns a pointer to a null-terminated string. This function
+c        will be used by the FitsChan to obtain input FITS header
+c        cards. On each invocation, it should read the next input card
+c        from some external source (such as a FITS file), and return a
+c        pointer to the (null-terminated) contents of the card. It
+c        should return a NULL pointer when there are no more cards to
+c        be read.
+c
+c        If "source" is NULL, the FitsChan will remain empty until
+c        cards are explicitly stored in it (e.g. using astPutCards,
+c        astPutFits or via the SourceFile attribute).
+f        A source routine, which is a function taking two arguments: a
+f        character argument of length 80 to contain a FITS card, and an
+f        integer error status argument. It should return an integer value.
+f        This function will be used by the FitsChan to obtain input
+f        FITS header cards. On each invocation, it should read the
+f        next input card from some external source (such as a FITS
+f        file), and return the contents of the card via its character
+f        argument. It should return a function result of one unless
+f        there are no more cards to be read, in which case it should
+f        return zero. If an error occurs, it should set its error
+f        status argument to an error value before returning.
+f
+f        If the null routine AST_NULL is supplied as the SOURCE value,
+f        the FitsChan will remain empty until cards are explicitly
+f        stored in it (e.g. using AST_PUTCARDS, AST_PUTFITS or via the
+f        SourceFile attribute).
+c     sink
+f     SINK = SUBROUTINE (Given)
+c        Pointer to a sink function that takes a pointer to a
+c        null-terminated string as an argument and returns void.  If
+c        no value has been set for the SinkFile attribute, this
+c        function will be used by the FitsChan to deliver any FITS
+c        header cards it contains when it is finally deleted. On
+c        each invocation, it should deliver the contents of the character
+c        string passed to it as a FITS header card to some external
+c        data store (such as a FITS file).
+f        A sink routine, which is a subroutine which takes two
+f        arguments: a character argument of length 80 to contain a
+f        FITS card, and an integer error status argument. If no
+f        value has been set for the SinkFile attribute, this routine
+f        will be used by the FitsChan to deliver any FITS header cards
+f        it contains when it is finally deleted. On each invocation,
+f        it should deliver the contents of the character string passed
+f        to it as a FITS header card to some external data store (such
+f        as a FITS file).  If an error occurs, it should set its error
+f        status argument to an error value before returning.
+*
+c        If "sink" is NULL,
+f        If the null routine AST_NULL is supplied as the SINK value,
+*        and no value has been set for the SinkFile attribute, the
+*        contents of the FitsChan will be lost when it is deleted.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new FitsChan. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new FitsChan. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+*
+*        Note, the FITSCHAN_OPTIONS environment variable may be used
+*        to specify default options for all newly created FitsChans.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astFitsChan()
+f     AST_FITSCHAN = INTEGER
+*        A pointer to the new FitsChan.
+
+*  Notes:
+f     - The names of the routines supplied for the SOURCE and SINK
+f     arguments should appear in EXTERNAL statements in the Fortran
+f     routine which invokes AST_FITSCHAN. However, this is not generally
+f     necessary for the null routine AST_NULL (so long as the AST_PAR
+f     include file has been used).
+c     - No FITS "END" card will be written via the sink function. You
+f     - No FITS "END" card will be written via the sink routine. You
+*     should add this card yourself after the FitsChan has been
+*     deleted.
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+f     - Note that the null routine AST_NULL (one underscore) is
+f     different to AST__NULL (two underscores), which is the null Object
+f     pointer.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFitsChan *new;             /* Pointer to new FitsChan */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the FitsChan, allocating memory and initialising the
+   virtual function table as well if necessary. This interface is for
+   use by other C functions within AST, and uses the standard "wrapper"
+   functions included in this class. */
+   new = astInitFitsChan( NULL, sizeof( AstFitsChan ), !class_init,
+                          &class_vtab, "FitsChan", source, SourceWrap,
+                          sink, SinkWrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Apply any default options specified by "<class>_OPTIONS" environment
+   variable. */
+      astEnvSet( new );
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   FitsChan's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new FitsChan. */
+   return new;
+}
+
+AstFitsChan *astFitsChanId_( const char *(* source)( void ),
+                             void (* sink)( const char * ),
+                             const char *options, ... ) {
+
+/*
+*  Name:
+*     astFitsChanId_
+
+*  Purpose:
+*     Create a FitsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstFitsChan *astFitsChanId_( const char *(* source)( void ),
+*                                  void (* sink)( const char * ),
+*                                  const char *options, ... )
+
+*  Class Membership:
+*     FitsChan constructor.
+
+*  Description:
+*     This function implements the external (public) C interface to the
+*     astFitsChan constructor function. Another function (astFitsChanForId)
+*     should be called to create a FitsChan for use within other languages.
+*     Both functions return an ID value (instead of a true C pointer) to
+*     external users, and must be provided because astFitsChan_ has a variable
+*     argument list which cannot be encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astFitsChan_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astFitsChan_.
+
+*  Returned Value:
+*     The ID value associated with the new FitsChan.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFitsChan *new;             /* Pointer to new FitsChan */
+   va_list args;                 /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the FitsChan, allocating memory and initialising the
+   virtual function table as well if necessary. This interface is for
+   use by external C functions and uses the standard "wrapper"
+   functions included in this class. */
+   new = astInitFitsChan( NULL, sizeof( AstFitsChan ), !class_init,
+                          &class_vtab, "FitsChan", source, SourceWrap,
+                          sink, SinkWrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Apply any default options specified by "<class>_OPTIONS" environment
+   variable. */
+      astEnvSet( new );
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   FitsChan's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new FitsChan. */
+   return astMakeId( new );
+}
+
+AstFitsChan *astFitsChanForId_( const char *(* source)( void ),
+                              char *(* source_wrap)( const char *(*)( void ), int * ),
+                              void (* sink)( const char * ),
+                              void (* sink_wrap)( void (*)( const char * ),
+                                                  const char *, int * ),
+                              const char *options, ... ) {
+
+/*
+*+
+*  Name:
+*     astFitsChanFor
+
+*  Purpose:
+*     Initialise a FitsChan from a foreign language interface.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstFitsChan *astFitsChanFor( const char *(* source)( void ),
+*                                char *(* source_wrap)( const char *(*)
+*                                                       ( void ), int * ),
+*                                void (* sink)( const char * ),
+*                                void (* sink_wrap)( void (*)( const char * ),
+*                                                    const char *, int * ),
+*                                const char *options, ... )
+
+*  Class Membership:
+*     FitsChan constructor.
+
+*  Description:
+*     This function creates a new FitsChan from a foreign language
+*     interface and optionally initialises its attributes.
+*
+*     A FitsChan implements FITS input/output for the AST library.
+*     Writing an Object to a FitsChan (using astWrite) will generate a
+*     textual representation of that Object in terms of FITS header cards,
+*     and reading from a FitsChan (using astRead) will create a new Object
+*     from its FITS representation.
+*
+*     Normally, when you use a FitsChan, you should provide "source"
+*     and "sink" functions which connect it to an external data store
+*     by reading and writing the resulting text. This function also
+*     requires you to provide "wrapper" functions which will invoke
+*     the source and sink functions.
+
+*  Parameters:
+*     source
+*        Pointer to a "source" function which will be used to obtain
+*        FITS header cards. Generally, this will be obtained by
+*        casting a pointer to a source function which is compatible
+*        with the "source_wrap" wrapper function (below). The pointer
+*        should later be cast back to its original type by the
+*        "source_wrap" function before the function is invoked.
+*
+*        If "source" is NULL, the FitsChan will remain empty until
+*        cards are added explicitly (e.g. using astPutCards or astPutFits).
+*     source_wrap
+*        Pointer to a function which can be used to invoke the
+*        "source" function supplied (above). This wrapper function is
+*        necessary in order to hide variations in the nature of the
+*        source function, such as may arise when it is supplied by a
+*        foreign (non-C) language interface.
+*
+*        The single parameter of the "source_wrap" function is a
+*        pointer to the "source" function, and it should cast this
+*        function pointer (as necessary) and invoke the function with
+*        appropriate arguments to obtain the next FITS header card.
+*        The "source_wrap" function should then return a pointer
+*        to a dynamically allocated, null terminated string containing
+*        the text that was read. The string will be freed (using
+*        astFree) when no longer required and the "source_wrap"
+*        function need not concern itself with this. A NULL pointer
+*        should be returned if there is no more input to read.
+*
+*        If "source" is NULL, the FitsChan will remain empty until
+*        cards are added explicitly (e.g. using astPutCards or astPutFits).
+*     sink
+*        Pointer to a "sink" function which will be used to deliver
+*        FITS header cards. Generally, this will be obtained by
+*        casting a pointer to a sink function which is compatible with
+*        the "sink_wrap" wrapper function (below). The pointer should
+*        later be cast back to its original type by the "sink_wrap"
+*        function before the function is invoked.
+*
+*        If "sink" is NULL, the contents of the FitsChan will not be
+*        written out before being deleted.
+*     sink_wrap
+*        Pointer to a function which can be used to invoke the "sink"
+*        function supplied (above). This wrapper function is necessary
+*        in order to hide variations in the nature of the sink
+*        function, such as may arise when it is supplied by a foreign
+*        (non-C) language interface.
+*
+*        The first parameter of the "sink_wrap" function is a pointer
+*        to the "sink" function, and the second parameter is a pointer
+*        to a const, null-terminated character string containing the
+*        text to be written.  The "sink_wrap" function should cast the
+*        "sink" function pointer (as necessary) and invoke the
+*        function with appropriate arguments to deliver the line of
+*        output text. The "sink_wrap" function then returns void.
+*
+*        If "sink_wrap" is NULL, the contents of the FitsChan will not be
+*        written out before being deleted.
+*     options
+*        Pointer to a null-terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new FitsChan. The syntax used is identical to
+*        that for the astSet function and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of additional arguments may follow it in
+*        order to supply values to be substituted for these
+*        specifiers. The rules for supplying these are identical to
+*        those for the astSet function (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     astFitsChanFor()
+*        A pointer to the new FitsChan.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*     - This function is only available through the public interface
+*     to the FitsChan class (not the protected interface) and is
+*     intended solely for use in implementing foreign language
+*     interfaces to this class.
+*-
+
+*  Implememtation Notes:
+*     - This function behaves exactly like astFitsChanId_, in that it
+*     returns ID values and not true C pointers, but it has two
+*     additional arguments. These are pointers to the "wrapper
+*     functions" which are needed to accommodate foreign language
+*     interfaces.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFitsChan *new;             /* Pointer to new FitsChan */
+   va_list args;                 /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise the FitsChan, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitFitsChan( NULL, sizeof( AstFitsChan ), !class_init,
+                          &class_vtab, "FitsChan", source, source_wrap,
+                          sink, sink_wrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Apply any default options specified by "<class>_OPTIONS" environment
+   variable. */
+      astEnvSet( new );
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   FitsChan's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new FitsChan. */
+   return astMakeId( new );
+}
+
+AstFitsChan *astInitFitsChan_( void *mem, size_t size, int init,
+                               AstFitsChanVtab *vtab, const char *name,
+                               const char *(* source)( void ),
+                               char *(* source_wrap)( const char *(*)( void ), int * ),
+                               void (* sink)( const char * ),
+                               void (* sink_wrap)( void (*)( const char * ),
+                                                   const char *, int * ), int *status ) {
+
+/*
+*+
+*  Name:
+*     astInitFitsChan
+
+*  Purpose:
+*     Initialise a FitsChan.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstFitsChan *astInitFitsChan_( void *mem, size_t size, int init,
+*                            AstFitsChanVtab *vtab, const char *name,
+*                            const char *(* source)( void ),
+*                            char *(* source_wrap)( const char *(*)( void ), int * ),
+*                            void (* sink)( const char * ),
+*                            void (* sink_wrap)( void (*)( const char * ),
+*                                                   const char *, int * ) )
+
+*  Class Membership:
+*     FitsChan initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new FitsChan object. It allocates memory (if
+*     necessary) to accommodate the FitsChan plus any additional data
+*     associated with the derived class.  It then initialises a
+*     FitsChan structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual
+*     function table for a FitsChan at the start of the memory passed
+*     via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the FitsChan is to be
+*        initialised.  This must be of sufficient size to accommodate
+*        the FitsChan data (sizeof(FitsChan)) plus any data used by the
+*        derived class. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the FitsChan (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the FitsChan structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*     init
+*        A boolean flag indicating if the FitsChan's virtual function
+*        table is to be initialised. If this value is non-zero, the
+*        virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new FitsChan.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*     source
+*        Pointer to a "source" function which will be used to obtain
+*        FITS header cards. Generally, this will be obtained by
+*        casting a pointer to a source function which is compatible
+*        with the "source_wrap" wrapper function (below). The pointer
+*        should later be cast back to its original type by the
+*        "source_wrap" function before the function is invoked.
+*
+*        If "source" is NULL, the FitsChan will remain empty until
+*        cards are added explicitly (e.g. using astPutCards or astPutFits).
+*     source_wrap
+*        Pointer to a function which can be used to invoke the
+*        "source" function supplied (above). This wrapper function is
+*        necessary in order to hide variations in the nature of the
+*        source function, such as may arise when it is supplied by a
+*        foreign (non-C) language interface.
+*
+*        The single parameter of the "source_wrap" function is a
+*        pointer to the "source" function, and it should cast this
+*        function pointer (as necessary) and invoke the function with
+*        appropriate arguments to obtain the next FITS header card.
+*        The "source_wrap" function should then return a pointer
+*        to a dynamically allocated, null terminated string containing
+*        the text that was read. The string will be freed (using
+*        astFree) when no longer required and the "source_wrap"
+*        function need not concern itself with this. A NULL pointer
+*        should be returned if there is no more input to read.
+*
+*        If "source" is NULL, the FitsChan will remain empty until
+*        cards are added explicitly (e.g. using astPutCards or astPutFits).
+*     sink
+*        Pointer to a "sink" function which will be used to deliver
+*        FITS header cards. Generally, this will be obtained by
+*        casting a pointer to a sink function which is compatible with
+*        the "sink_wrap" wrapper function (below). The pointer should
+*        later be cast back to its original type by the "sink_wrap"
+*        function before the function is invoked.
+*
+*        If "sink" is NULL, the contents of the FitsChan will not be
+*        written out before being deleted.
+*     sink_wrap
+*        Pointer to a function which can be used to invoke the "sink"
+*        function supplied (above). This wrapper function is necessary
+*        in order to hide variations in the nature of the sink
+*        function, such as may arise when it is supplied by a foreign
+*        (non-C) language interface.
+*
+*        The first parameter of the "sink_wrap" function is a pointer
+*        to the "sink" function, and the second parameter is a pointer
+*        to a const, null-terminated character string containing the
+*        text to be written.  The "sink_wrap" function should cast the
+*        "sink" function pointer (as necessary) and invoke the
+*        function with appropriate arguments to deliver the line of
+*        output text. The "sink_wrap" function then returns void.
+*
+*        If "sink_wrap" is NULL, the contents of the FitsChan will not be
+*        written out before being deleted.
+
+*  Returned Value:
+*     A pointer to the new FitsChan.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFitsChan *new;              /* Pointer to new FitsChan */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitFitsChanVtab( vtab, name );
+
+/* Initialise a Channel structure (the parent class) as the first
+   component within the FitsChan structure, allocating memory if
+   necessary. I am not sure why FitsChan has its own source_wrap and
+   sink_wrap items, rather than just using those inherited from Channel.
+   It may be possible to do away with the fitschan wrappers and just use
+   the channel wrapper, but I have not yet tried this. Old mail from RFWS
+   suggests that it may be because the F77 FitsChan source and sink
+   interfaces handle fixed length strings (80 characters), whereas
+   Channel sournce and sink handle variable length strings. This needs
+   investigating. */
+   new = (AstFitsChan *) astInitChannel( mem, size, 0,
+                                         (AstChannelVtab *) vtab, name,
+                                         NULL, NULL, NULL, NULL );
+   if ( astOK ) {
+
+/* Initialise the FitsChan data. */
+/* ---------------------------- */
+      new->head = NULL;
+      new->card = NULL;
+      new->keyseq = NULL;
+      new->keywords = NULL;
+      new->defb1950 = -1;
+      new->tabok = -INT_MAX;
+      new->cdmatrix = -1;
+      new->carlin = -1;
+      new->polytan = -INT_MAX;
+      new->iwc = -1;
+      new->clean = -1;
+      new->fitsdigits = DBL_DIG;
+      new->fitsaxisorder = NULL;
+      new->encoding = UNKNOWN_ENCODING;
+      new->warnings = NULL;
+      new->tables = NULL;
+
+/* Save the pointers to the source and sink functions and the wrapper
+   functions that invoke them. */
+      new->source = source;
+      new->saved_source = NULL;
+      new->source_wrap = source_wrap;
+      new->sink = sink;
+      new->sink_wrap = sink_wrap;
+      new->tabsource = NULL;
+      new->tabsource_wrap = NULL;
+
+/* Rewind the FitsChan so that the next read operation will return the
+   first card. */
+      new->card = new->head;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+AstFitsChan *astLoadFitsChan_( void *mem, size_t size,
+                               AstFitsChanVtab *vtab, const char *name,
+                               AstChannel *channel, int *status ) {
+
+/*
+*+
+*  Name:
+*     astLoadFitsChan
+
+*  Purpose:
+*     Load a FitsChan.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fitschan.h"
+*     AstFitsChan *astLoadFitsChan( void *mem, size_t size,
+*                                   AstFitsChanVtab *vtab, const char *name,
+*                                   AstChannel *channel )
+
+*  Class Membership:
+*     FitsChan loader.
+
+*  Description:
+*     This function is provided to load a new FitsChan using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     FitsChan structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a FitsChan at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the FitsChan is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        FitsChan data (sizeof(FitsChan)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the FitsChan (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the FitsChan structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstFitsChan) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new FitsChan. If this is NULL, a pointer
+*        to the (static) virtual function table for the FitsChan class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "FitsChan" is used instead.
+
+*  Returned Value:
+*     A pointer to the new FitsChan.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+#define KEY_LEN 50              /* Maximum length of a keyword */
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
+   AstFitsChan *new;            /* Pointer to the new FitsChan */
+   char *comment;               /* Pointer to keyword comment */
+   char *keynm;                 /* Keyword name */
+   char *text;                  /* Textual version of integer value */
+   char buff[ KEY_LEN + 1 ];    /* Buffer for keyword string */
+   double dval[2];              /* Double precision data values */
+   int flags;                   /* Keyword flags */
+   int free_data;               /* Should data memory be freed? */
+   int ival[2];                 /* Integer data values */
+   int ncard;                   /* No. of FitsCards read so far */
+   int type;                    /* Keyword type */
+   void *data;                  /* Pointer to keyword data value */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this FitsChan. In this case the
+   FitsChan belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstFitsChan );
+      vtab = &class_vtab;
+      name = "FitsChan";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitFitsChanVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built FitsChan. */
+   new = astLoadChannel( mem, size, (AstChannelVtab *) vtab, name,
+                         channel );
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "FitsChan" );
+
+/* Initialise the KeyMap holding the keywords in the FitsChan. */
+      new->keywords = NULL;
+
+/* Initialise the list of keyword sequence numbers. */
+      new->keyseq = NULL;
+
+/* Set the pointers to the source and sink functions, and their
+   wrapper functions, to NULL (we cannot restore these since they
+   refer to process-specific addresses). */
+      new->source = NULL;
+      new->saved_source = NULL;
+      new->source_wrap = NULL;
+      new->sink = NULL;
+      new->sink_wrap = NULL;
+      new->tabsource = NULL;
+      new->tabsource_wrap = NULL;
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* Encoding. */
+/* --------- */
+      text = astReadString( channel, "encod", UNKNOWN_STRING );
+      if( text && strcmp( text, UNKNOWN_STRING ) ) {
+         new->encoding = FindString( MAX_ENCODING + 1, xencod, text,
+                                     "the FitsChan component 'Encod'",
+                                     "astRead", astGetClass( channel ), status );
+      } else {
+         new->encoding = UNKNOWN_ENCODING;
+      }
+      if ( TestEncoding( new, status ) ) SetEncoding( new, new->encoding, status );
+      text = astFree( text );
+
+/* FitsAxisOrder. */
+/* -------------- */
+      new->fitsaxisorder = astReadString( channel, "faxord", NULL );
+
+/* FitsDigits. */
+/* ----------- */
+      new->fitsdigits = astReadInt( channel, "fitsdg", DBL_DIG );
+      if ( TestFitsDigits( new, status ) ) SetFitsDigits( new, new->fitsdigits, status );
+
+/* DefB1950 */
+/* -------- */
+      new->defb1950 = astReadInt( channel, "dfb1950", -1 );
+      if ( TestDefB1950( new, status ) ) SetDefB1950( new, new->defb1950, status );
+
+/* TabOK */
+/* ----- */
+      new->tabok = astReadInt( channel, "tabok", -INT_MAX );
+      if ( TestTabOK( new, status ) ) SetTabOK( new, new->tabok, status );
+
+/* CDMatrix */
+/* -------- */
+      new->cdmatrix = astReadInt( channel, "cdmat", -1 );
+      if ( TestCDMatrix( new, status ) ) SetCDMatrix( new, new->cdmatrix, status );
+
+/* CarLin */
+/* ------ */
+      new->carlin = astReadInt( channel, "carlin", -1 );
+      if ( TestCarLin( new, status ) ) SetCarLin( new, new->carlin, status );
+
+/* PolyTan */
+/* ------- */
+      new->polytan = astReadInt( channel, "polytan", -1 );
+      if ( TestPolyTan( new, status ) ) SetPolyTan( new, new->polytan, status );
+
+/* Iwc */
+/* --- */
+      new->iwc = astReadInt( channel, "iwc", -1 );
+      if ( TestIwc( new, status ) ) SetIwc( new, new->iwc, status );
+
+/* Clean */
+/* ----- */
+      new->clean = astReadInt( channel, "clean", -1 );
+      if ( TestClean( new, status ) ) SetClean( new, new->clean, status );
+
+/* Warnings. */
+/* --------- */
+      new->warnings = astReadString( channel, "warn", NULL );
+
+/* Card. */
+/* ----- */
+
+/* Initialise the index of the card to be read next. */
+      ncard = 1;
+      new->card = NULL;
+      new->head = NULL;
+
+/* Load each card. */
+      type = AST__NOTYPE + 1;
+      while( type != AST__NOTYPE && astOK ){
+
+/* Get the keyword type. */
+         (void) sprintf( buff, "ty%d", ncard );
+         text = astReadString( channel, buff, " " );
+         if( strcmp( text, " " ) ) {
+            type = FindString( 9, type_names, text,
+                               "a FitsChan keyword data type",
+                               "astRead", astGetClass( channel ), status );
+         } else {
+            type = AST__NOTYPE;
+         }
+         text = astFree( text );
+
+/* Only proceed if the keyword type was found. */
+         if( type != AST__NOTYPE ){
+
+/* Get the keyword name. Use a default blank name. */
+            (void) sprintf( buff, "nm%d", ncard );
+            keynm = astReadString( channel, buff, "        " );
+
+/* Get the data value, using the appropriate data type, unless the
+   keyword is a comment keyword or is undefined. */
+            free_data = 0;
+            if( type == AST__FLOAT ){
+               (void) sprintf( buff, "dt%d", ncard );
+               dval[ 0 ] = astReadDouble( channel, buff, AST__BAD );
+               data = (void *) dval;
+            } else if( type == AST__STRING || type == AST__CONTINUE ){
+               (void) sprintf( buff, "dt%d", ncard );
+               data = (void *) astReadString( channel, buff, "" );
+               free_data = 1;
+            } else if( type == AST__INT ){
+               (void) sprintf( buff, "dt%d", ncard );
+               ival[ 0 ] = astReadInt( channel, buff, 0 );
+               data = (void *) ival;
+            } else if( type == AST__LOGICAL ){
+               (void) sprintf( buff, "dt%d", ncard );
+               ival[ 0 ] = astReadInt( channel, buff, 0 );
+               data = (void *) ival;
+            } else if( type == AST__COMPLEXF ){
+               (void) sprintf( buff, "dr%d", ncard );
+               dval[ 0 ] = astReadDouble( channel, buff, AST__BAD );
+               (void) sprintf( buff, "di%d", ncard );
+               dval[ 1 ] = astReadDouble( channel, buff, AST__BAD );
+               data = (void *) dval;
+            } else if( type == AST__COMPLEXI ){
+               (void) sprintf( buff, "dr%d", ncard );
+               ival[ 0 ] = astReadInt( channel, buff, 0 );
+               (void) sprintf( buff, "di%d", ncard );
+               ival[ 1 ] = astReadInt( channel, buff, 0 );
+               data = (void *) ival;
+            } else {
+               data = NULL;
+            }
+
+/* Get the keyword flags (only written by versions of AST later than
+   V1.4). These are packed into an int. */
+            (void) sprintf( buff, "fl%d", ncard );
+            flags = astReadInt( channel, buff, 0 );
+
+/* If the flags were not found, use the keyword deletion flag written by
+   AST V1.4 and earlier. */
+            if( !flags ) {
+               (void) sprintf( buff, "dl%d", ncard );
+               flags = astReadInt( channel, buff, 0 );
+            }
+
+/* Get the keyword comment. */
+            (void) sprintf( buff, "cm%d", ncard );
+            comment = astReadString( channel, buff, NULL );
+
+/* Append a new card to the output FitsChan. */
+            NewCard( new, keynm, type, data, comment, flags, status );
+
+/* Free the character strings, and data (if required). */
+            comment = (char *) astFree( (void *) comment );
+            keynm = (char *) astFree( (void *) keynm );
+            if( free_data ) data = astFree( data );
+         }
+
+/* Move on to the next card. */
+         ncard++;
+      }
+
+/* Set up the current card index. */
+      astSetCard( new, astReadInt( channel, "card", 0 ) );
+
+/* Load any FitTables. */
+      new->tables =  astReadObject( channel, "tables", NULL );
+   }
+
+/* If an error occurred, clean up by deleting the new FitsChan. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the new FitsChan pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+void astWriteFits_( AstFitsChan *this, int *status ){
+   if( !this ) return;
+   (**astMEMBER(this,FitsChan,WriteFits))(this, status );
+}
+
+void astReadFits_( AstFitsChan *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,ReadFits))(this, status );
+}
+
+void astEmptyFits_( AstFitsChan *this, int *status ){
+   if( !this ) return;
+   (**astMEMBER(this,FitsChan,EmptyFits))(this, status );
+}
+
+void astShowFits_( AstFitsChan *this, int *status ){
+   if( !this ) return;
+   (**astMEMBER(this,FitsChan,ShowFits))(this, status );
+}
+
+void astPutCards_( AstFitsChan *this, const char *cards, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,PutCards))(this,cards, status );
+}
+
+void astPutFits_( AstFitsChan *this, const char *card, int overwrite, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,PutFits))(this,card,overwrite, status );
+}
+
+void astDelFits_( AstFitsChan *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,DelFits))(this, status );
+}
+
+void astPurgeWCS_( AstFitsChan *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,PurgeWCS))(this, status );
+}
+
+AstKeyMap *astGetTables_( AstFitsChan *this, int *status ){
+   if( !astOK ) return NULL;
+   return (**astMEMBER(this,FitsChan,GetTables))(this, status );
+}
+
+void astPutTables_( AstFitsChan *this, AstKeyMap *tables, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,PutTables))(this, tables, status );
+}
+
+void astPutTable_( AstFitsChan *this, AstFitsTable *table, const char *extnam,
+                   int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,PutTable))(this, table, extnam, status );
+}
+
+void astRemoveTables_( AstFitsChan *this, const char *key, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,RemoveTables))(this, key, status );
+}
+
+void astRetainFits_( AstFitsChan *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,RetainFits))(this, status );
+}
+
+int astFitsEof_( AstFitsChan *this, int *status ){
+   if( !this ) return 1;
+   return (**astMEMBER(this,FitsChan,FitsEof))( this, status );
+}
+
+void astSetFitsCom_( AstFitsChan *this, const char *name,
+                         const char *comment, int overwrite, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetFitsCom))( this, name, comment, overwrite, status );
+}
+
+void astSetFitsI_( AstFitsChan *this, const char *name, int value,
+                     const char *comment, int overwrite, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetFitsI))( this, name, value, comment, overwrite, status );
+}
+
+void astSetFitsF_( AstFitsChan *this, const char *name, double value,
+                       const char *comment, int overwrite, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetFitsF))( this, name, value, comment, overwrite, status );
+}
+
+void astSetFitsS_( AstFitsChan *this, const char *name, const char *value,
+                        const char *comment, int overwrite, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetFitsS))( this, name, value, comment, overwrite, status );
+}
+
+void astSetFitsCN_( AstFitsChan *this, const char *name, const char *value,
+                    const char *comment, int overwrite, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetFitsCN))( this, name, value, comment, overwrite, status );
+}
+
+void astSetFitsCF_( AstFitsChan *this, const char *name, double *value,
+                          const char *comment, int overwrite, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetFitsCF))( this, name, value, comment, overwrite, status );
+}
+
+void astSetFitsCI_( AstFitsChan *this, const char *name, int *value,
+                          const char *comment, int overwrite, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetFitsCI))( this, name, value, comment, overwrite, status );
+}
+
+void astSetFitsL_( AstFitsChan *this, const char *name, int value,
+                         const char *comment, int overwrite, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetFitsL))( this, name, value, comment, overwrite, status );
+}
+
+void astSetFitsU_( AstFitsChan *this, const char *name, const char *comment,
+                   int overwrite, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetFitsU))( this, name, comment, overwrite, status );
+}
+
+void astSetFitsCM_( AstFitsChan *this, const char *comment, int overwrite, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetFitsCM))( this, comment, overwrite, status );
+}
+
+void astClearCard_( AstFitsChan *this, int *status ){
+   if( !this ) return;
+   (**astMEMBER(this,FitsChan,ClearCard))( this, status );
+}
+
+void astSetCard_( AstFitsChan *this, int card, int *status ){
+   if( !this ) return;
+   (**astMEMBER(this,FitsChan,SetCard))( this, card, status );
+}
+
+int astTestCard_( AstFitsChan *this, int *status ){
+   if( !this ) return 0;
+   return (**astMEMBER(this,FitsChan,TestCard))( this, status );
+}
+
+int astGetCard_( AstFitsChan *this, int *status ){
+   if( !this ) return 0;
+   return (**astMEMBER(this,FitsChan,GetCard))( this, status );
+}
+
+int astGetNcard_( AstFitsChan *this, int *status ){
+   if( !this ) return 0;
+   return (**astMEMBER(this,FitsChan,GetNcard))( this, status );
+}
+
+int astGetCardType_( AstFitsChan *this, int *status ){
+   if( !this ) return AST__NOTYPE;
+   return (**astMEMBER(this,FitsChan,GetCardType))( this, status );
+}
+
+const char *astGetCardComm_( AstFitsChan *this, int *status ){
+   if( !this ) return NULL;
+   return (**astMEMBER(this,FitsChan,GetCardComm))( this, status );
+}
+
+const char *astGetCardName_( AstFitsChan *this, int *status ){
+   if( !this ) return NULL;
+   return (**astMEMBER(this,FitsChan,GetCardName))( this, status );
+}
+
+int astGetNkey_( AstFitsChan *this, int *status ){
+   if( !this ) return 0;
+   return (**astMEMBER(this,FitsChan,GetNkey))( this, status );
+}
+
+int astGetClean_( AstFitsChan *this, int *status ){
+   if( !this ) return 0;
+   return (**astMEMBER(this,FitsChan,GetClean))( this, status );
+}
+
+const char *astGetAllWarnings_( AstFitsChan *this, int *status ){
+   if( !this ) return NULL;
+   return (**astMEMBER(this,FitsChan,GetAllWarnings))( this, status );
+}
+
+int astGetFitsCF_( AstFitsChan *this, const char *name, double *value, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,GetFitsCF))( this, name, value, status );
+}
+
+int astGetFitsCI_( AstFitsChan *this, const char *name, int *value, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,GetFitsCI))( this, name, value, status );
+}
+
+int astGetFitsF_( AstFitsChan *this, const char *name, double *value, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,GetFitsF))( this, name, value, status );
+}
+
+int astGetFitsI_( AstFitsChan *this, const char *name, int *value, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,GetFitsI))( this, name, value, status );
+}
+
+int astGetFitsL_( AstFitsChan *this, const char *name, int *value, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,GetFitsL))( this, name, value, status );
+}
+
+int astTestFits_( AstFitsChan *this, const char *name, int *there, int *status ){
+   if( there ) *there = 0;
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,TestFits))( this, name, there, status );
+}
+
+int astGetFitsS_( AstFitsChan *this, const char *name, char **value, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,GetFitsS))( this, name, value, status );
+}
+
+int astGetFitsCN_( AstFitsChan *this, const char *name, char **value, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,GetFitsCN))( this, name, value, status );
+}
+
+int astFitsGetCom_( AstFitsChan *this, const char *name, char **comment, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,FitsGetCom))( this, name, comment, status );
+}
+
+int astKeyFields_( AstFitsChan *this, const char *filter, int maxfld,
+                int *ubnd, int *lbnd, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,KeyFields))( this, filter, maxfld,
+           ubnd, lbnd, status );
+}
+
+int astFindFits_( AstFitsChan *this, const char *name, char *card, int inc, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,FindFits))( this, name, card, inc, status );
+}
+
+int astGetEncoding_( AstFitsChan *this, int *status ){
+   if( !astOK ) return UNKNOWN_ENCODING;
+   return (**astMEMBER(this,FitsChan,GetEncoding))( this, status );
+}
+
+int astGetCDMatrix_( AstFitsChan *this, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,FitsChan,GetCDMatrix))( this, status );
+}
+void astSetTableSource_( AstFitsChan *this,
+                         void (*tabsource)( void ),
+                         void (*tabsource_wrap)( void (*)( void ),
+                                                 AstFitsChan *, const char *,
+                                                 int, int, int * ),
+                         int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,SetTableSource))( this, tabsource,
+                                                tabsource_wrap, status );
+}
+void astTableSource_( AstFitsChan *this,
+                      void (* tabsource)( AstFitsChan *, const char *,
+                                          int, int, int * ),
+                      int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,FitsChan,TableSource))( this, tabsource, status );
+}
+
+/*
+ * A diagnostic function which lists the contents of a FitsChan to
+ * standard output.
+ */
+
+/*
+static void ListFC( AstFitsChan *, const char * );
+
+static void ListFC( AstFitsChan *this, const char *ttl ) {
+   FitsCard *cardo;
+   char card[ 81 ];
+   printf("%s\n----------------------------------------\n", ttl );
+   cardo = (FitsCard *) this->card;
+   astClearCard( this );
+   while( !astFitsEof( this ) && astOK ){
+      FormatCard( this, card, "List" );
+      if( this->card == cardo ) {
+         printf( "%s   <<<<< currrent card <<<<< \n", card );
+      } else {
+         printf( "%s\n", card );
+      }
+      MoveCard( this, 1, "List", "FitsChan" );
+   }
+   this->card = cardo;
+}
+*/
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ast/fitschan.h b/ast/fitschan.h
new file mode 100644
index 0000000..ba197df
--- /dev/null
+++ b/ast/fitschan.h
@@ -0,0 +1,871 @@
+#if !defined( FITSCHAN_INCLUDED ) /* Include this file only once */
+#define FITSCHAN_INCLUDED
+/*
+*+
+*  Name:
+*     fitschan.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the FitsChan class.
+
+*  Invocation:
+*     #include "fitschan.h"
+
+*  Description:
+*     This include file defines the interface to the FitsChan class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The FitsChan class provides facilities for reading and writing AST
+*     Objects in the form of FITS header cards.
+
+*  Inheritance:
+*     The FitsChan class inherits from the Channel class.
+
+*  Macros:
+
+*     Protected:
+*        AST__NOTYPE
+*           Integer dentifier for an illegal FITS data type.
+*        AST__COMMENT
+*           Integer dentifier for a FITS comment keyword.
+*        AST__INT
+*           Integer dentifier for the integer FITS data type.
+*        AST__FLOAT
+*           Integer dentifier for the floating point FITS data type.
+*        AST__STRING
+*           Integer dentifier for the string FITS data type.
+*        AST__CONTINUE
+*           Integer dentifier for the continuation string FITS data type.
+*        AST__COMPLEXF
+*           Integer dentifier for the complex floating point FITS data type.
+*        AST__COMPLEXI
+*           Integer dentifier for the complex integer FITS data type.
+*        AST__LOGICAL
+*           Integer dentifier for the logical FITS data type.
+*        AST__UNDEF
+*           Integer dentifier for undefined FITS data type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     11-DEC-1996 (DSB):
+*        Original version.
+*     30-Jun-1997 (DSB):
+*        Changed character pointer argument to character array in PutFits.
+*     26-SEP-1997 (DSB):
+*        Added CDMatrix attribute.
+*     21-OCT-1997 (DSB):
+*        o  Renamed astFields as astKeyFields.
+*     1-APR-2000 (DSB):
+*        Changes for CDi_j based FITS-WCS standard.
+*     18-MAY-2000 (DSB):
+*        Added Warnings attribute.
+*     4-APR-2001 (DSB):
+*        Added AllWarnings attribute.
+*     20-FEB-2002 (DSB):
+*        Added CarLin attribute.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitFitsChanVtab method.
+*     13-FEB-2003 (DSB):
+*        Added Clean attribute.
+*     19-MAR-2004 (DSB):
+*        Added astPutCards function.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "channel.h"             /* I/O channels (parent class) */
+#include "pointset.h"            /* Defines AST__BAD */
+#include "keymap.h"              /* Defines the AstKeyMap type */
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros. */
+/* ------- */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+#define AST__NOTYPE       -1
+#define AST__COMMENT       0
+#define AST__INT           1
+#define AST__FLOAT         2
+#define AST__STRING        3
+#define AST__COMPLEXF      4
+#define AST__COMPLEXI      5
+#define AST__LOGICAL       6
+#define AST__CONTINUE      7
+#define AST__UNDEF         8
+
+#if defined(astCLASS)            /* Protected */
+
+/* Define constants used to size global arrays in this module. */
+#define AST__FITSCHAN_FITSCARDLEN        80
+#define AST__FITSCHAN_GETATTRIB_BUFF_LEN 50
+
+#endif
+
+/* The EXTNAME value for FITS binary tables used to store coordinate arrays for
+   the -TAB algorithm. */
+#define AST_TABEXTNAME "WCS-TAB"
+
+/* Type Definitions. */
+/* ================= */
+
+/* FitsChan structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+struct AstFitsChan;
+typedef struct AstFitsChan {
+
+/* Attributes inherited from the parent class. */
+   AstChannel channel;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int encoding;    /* System for encoding AST objects ito FITS headers */
+   int defb1950;    /* Use FK4 B1950 as defaults? */
+   int tabok;       /* Support -TAB algorithm? */
+   int cdmatrix;    /* Use a CD matrix in FITS-WCS Encoding? */
+   int polytan;     /* Use distorted TAN convention? */
+   int carlin;      /* Use linear CAR mappings? */
+   int iwc;         /* Include an IWC Frame? */
+   int clean;       /* Remove used cards even if an error occurs? */
+   int fitsdigits;  /* No. of decmial places in formatted floating point keyword values */
+   char *fitsaxisorder; /* Pointer to a string defining WCS axis order */
+   char *warnings;  /* Pointer to a string containing warning conditions */
+   void *card;      /* Pointer to next FitsCard to be read */
+   void *head;      /* Pointer to first FitsCard in the circular linked list */
+   AstKeyMap *keyseq;   /* List of keyword sequence numbers used */
+   AstKeyMap *keywords; /* A KeyMap holding the keywords in the FitsChan */
+   AstKeyMap *tables;   /* A KeyMap holding the binary tables in the FitsChan */
+
+   const char *(* source)( void ); /* Pointer to source function */
+   const char *(* saved_source)( void ); /* Pointer to saved source function */
+   char *(* source_wrap)( const char *(*)( void ), int * );
+                                   /* Source wrapper function pointer */
+
+   void (* sink)( const char * );  /* Pointer to sink function */
+   void (* sink_wrap)( void (*)( const char * ), const char *, int * );
+                                   /* Sink wrapper function pointer */
+
+   void (* tabsource)( void );     /* Pointer to table source function */
+   void (* tabsource_wrap)( void (*)( void ), struct AstFitsChan *, const char *, int, int, int * );
+                                   /* Table source wrapper function pointer */
+
+} AstFitsChan;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* The virtual function table makes a forward reference to the
+   AstFitsTable structure which is not defined until "fitstable.h" is
+   included (below). Hence make a preliminary definition available
+   now. */
+struct AstFitsTable;
+
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstFitsChanVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstChannelVtab channel_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstKeyMap *(* GetTables)( AstFitsChan *, int * );
+   int (* FindFits)( AstFitsChan *, const char *, char [81], int, int * );
+   int (* FitsEof)( AstFitsChan *, int * );
+   int (* FitsGetCom)( AstFitsChan *, const char *, char **, int * );
+   int (* GetFitsCF)( AstFitsChan *, const char *, double *, int * );
+   int (* GetFitsCI)( AstFitsChan *, const char *, int *, int * );
+   int (* GetFitsCN)( AstFitsChan *, const char *, char **, int * );
+   int (* GetFitsF)( AstFitsChan *, const char *, double *, int * );
+   int (* GetFitsI)( AstFitsChan *, const char *, int *, int * );
+   int (* GetFitsL)( AstFitsChan *, const char *, int *, int * );
+   int (* GetFitsS)( AstFitsChan *, const char *, char **, int * );
+   int (* KeyFields)( AstFitsChan *, const char *, int, int *, int *, int * );
+   int (* TestFits)( AstFitsChan *, const char *, int *, int * );
+   void (* DelFits)( AstFitsChan *, int * );
+   void (* Empty)( AstFitsChan *, int * );
+   void (* ReadFits)( AstFitsChan *, int * );
+   void (* WriteFits)( AstFitsChan *, int * );
+   void (* EmptyFits)( AstFitsChan *, int * );
+   void (* ShowFits)( AstFitsChan *, int * );
+   void (* PurgeWCS)( AstFitsChan *, int * );
+   void (* PutCards)( AstFitsChan *, const char *, int * );
+   void (* PutFits)( AstFitsChan *, const char [81], int, int * );
+   void (* PutTable)( AstFitsChan *, struct AstFitsTable *, const char *, int * );
+   void (* PutTables)( AstFitsChan *, AstKeyMap *, int * );
+   void (* RemoveTables)( AstFitsChan *, const char *, int * );
+   void (* RetainFits)( AstFitsChan *, int * );
+   void (* SetFitsCF)( AstFitsChan *, const char *, double *, const char *, int, int * );
+   void (* SetFitsCI)( AstFitsChan *, const char *, int *, const char *, int, int * );
+   void (* SetFitsCM)( AstFitsChan *, const char *, int, int * );
+   void (* SetFitsCN)( AstFitsChan *, const char *, const char *, const char *, int, int * );
+   void (* SetFitsCom)( AstFitsChan *, const char *, const char *, int, int * );
+   void (* SetFitsF)( AstFitsChan *, const char *, double, const char *, int, int * );
+   void (* SetFitsI)( AstFitsChan *, const char *, int, const char *, int, int * );
+   void (* SetFitsL)( AstFitsChan *, const char *, int, const char *, int, int * );
+   void (* SetFitsS)( AstFitsChan *, const char *, const char *, const char *, int, int * );
+   void (* SetFitsU)( AstFitsChan *, const char *, const char *, int, int * );
+
+   int (* GetCard)( AstFitsChan *, int * );
+   int (* TestCard)( AstFitsChan *, int * );
+   void (* SetCard)( AstFitsChan *, int, int * );
+   void (* ClearCard)( AstFitsChan *, int * );
+
+   int (* GetFitsDigits)( AstFitsChan *, int * );
+   int (* TestFitsDigits)( AstFitsChan *, int * );
+   void (* SetFitsDigits)( AstFitsChan *, int, int * );
+   void (* ClearFitsDigits)( AstFitsChan *, int * );
+
+   const char *(* GetFitsAxisOrder)( AstFitsChan *, int * );
+   int (* TestFitsAxisOrder)( AstFitsChan *, int * );
+   void (* SetFitsAxisOrder)( AstFitsChan *, const char *, int * );
+   void (* ClearFitsAxisOrder)( AstFitsChan *, int * );
+
+   int (* GetDefB1950)( AstFitsChan *, int * );
+   int (* TestDefB1950)( AstFitsChan *, int * );
+   void (* SetDefB1950)( AstFitsChan *, int, int * );
+   void (* ClearDefB1950)( AstFitsChan *, int * );
+
+   int (* GetTabOK)( AstFitsChan *, int * );
+   int (* TestTabOK)( AstFitsChan *, int * );
+   void (* SetTabOK)( AstFitsChan *, int, int * );
+   void (* ClearTabOK)( AstFitsChan *, int * );
+
+   int (* GetCarLin)( AstFitsChan *, int * );
+   int (* TestCarLin)( AstFitsChan *, int * );
+   void (* SetCarLin)( AstFitsChan *, int, int * );
+   void (* ClearCarLin)( AstFitsChan *, int * );
+
+   int (* GetNcard)( AstFitsChan *, int * );
+
+   int (* GetCardType)( AstFitsChan *, int * );
+   const char *(* GetCardName)( AstFitsChan *, int * );
+   const char *(* GetCardComm)( AstFitsChan *, int * );
+
+   int (* GetNkey)( AstFitsChan *, int * );
+
+   int (* GetEncoding)( AstFitsChan *, int * );
+   int (* TestEncoding)( AstFitsChan *, int * );
+   void (* SetEncoding)( AstFitsChan *, int, int * );
+   void (* ClearEncoding)( AstFitsChan *, int * );
+
+   const char *(* GetAllWarnings)( AstFitsChan *, int * );
+
+   const char *(* GetWarnings)( AstFitsChan *, int * );
+   int (* TestWarnings)( AstFitsChan *, int * );
+   void (* ClearWarnings)( AstFitsChan *, int * );
+   void (* SetWarnings)( AstFitsChan *, const char *, int * );
+
+   int (* GetClean)( AstFitsChan *, int * );
+   int (* TestClean)( AstFitsChan *, int * );
+   void (* SetClean)( AstFitsChan *, int, int * );
+   void (* ClearClean)( AstFitsChan *, int * );
+
+   int (* GetCDMatrix)( AstFitsChan *, int * );
+   int (* TestCDMatrix)( AstFitsChan *, int * );
+   void (* SetCDMatrix)( AstFitsChan *, int, int * );
+   void (* ClearCDMatrix)( AstFitsChan *, int * );
+
+   int (* GetPolyTan)( AstFitsChan *, int * );
+   int (* TestPolyTan)( AstFitsChan *, int * );
+   void (* SetPolyTan)( AstFitsChan *, int, int * );
+   void (* ClearPolyTan)( AstFitsChan *, int * );
+
+   int (* GetIwc)( AstFitsChan *, int * );
+   int (* TestIwc)( AstFitsChan *, int * );
+   void (* SetIwc)( AstFitsChan *, int, int * );
+   void (* ClearIwc)( AstFitsChan *, int * );
+
+   void (* SetTableSource)( AstFitsChan *,
+                            void (*)( void ),
+                            void (*)( void (*)( void ),
+                                      AstFitsChan *, const char *, int,
+                                      int, int * ),
+                            int * );
+
+   void (* TableSource)( AstFitsChan *,
+                         void (*)( AstFitsChan *, const char *, int, int,
+                                   int * ),
+                         int * );
+
+} AstFitsChanVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstFitsChanGlobals {
+   AstFitsChanVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ AST__FITSCHAN_GETATTRIB_BUFF_LEN + 1 ];
+   char CnvType_Text[ AST__FITSCHAN_FITSCARDLEN + 1 ];
+   char CnvType_Text0[ AST__FITSCHAN_FITSCARDLEN + 1 ];
+   char CnvType_Text1[ AST__FITSCHAN_FITSCARDLEN + 1 ];
+   int Items_Written;
+   int Write_Nest;
+   int Current_Indent;
+   int Ignore_Used;
+   int Mark_New;
+   int CreateKeyword_Seq_Nchars;
+   char FormatKey_Buff[ 10 ];
+   char FitsGetCom_Sval[ AST__FITSCHAN_FITSCARDLEN + 1 ];
+   const char *IsSpectral_Ret;
+   char Match_Fmt[ 10 ];
+   const char *Match_Template;
+   int *Match_PA;
+   int *Match_PB;
+   int Match_NA;
+   int Match_NB;
+   int Match_Nentry;
+   char WcsCelestial_Type[ 4 ];
+} AstFitsChanGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(FitsChan)          /* Check class membership */
+astPROTO_ISA(FitsChan)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstFitsChan *astFitsChan_( const char *(*)( void ), void (*)( const char * ),
+                          const char *, int *, ...);
+#else
+AstFitsChan *astFitsChanId_( const char *(*)( void ), void (*)( const char * ),
+                            const char *, ... )__attribute__((format(printf,3,4)));
+AstFitsChan *astFitsChanForId_( const char *(*)( void ),
+                              char *(*)( const char *(*)( void ), int * ),
+                              void (*)( const char * ),
+                              void (*)( void (*)( const char * ),
+                                        const char *, int * ),
+                              const char *, ... );
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstFitsChan *astInitFitsChan_( void *, size_t, int, AstFitsChanVtab *,
+                               const char *,
+                               const char *(*)( void ),
+                               char *(*)( const char *(*)( void ), int * ),
+                               void (*)( const char * ),
+                               void (*)( void (*)( const char * ), const char *, int * ), int * );
+
+/* Vtab initialiser. */
+void astInitFitsChanVtab_( AstFitsChanVtab *, const char *, int * );
+
+
+
+/* Loader. */
+AstFitsChan *astLoadFitsChan_( void *, size_t, AstFitsChanVtab *,
+                               const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitFitsChanGlobals_( AstFitsChanGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+   AstKeyMap *astGetTables_( AstFitsChan *, int * );
+   int  astFindFits_( AstFitsChan *, const char *, char [81], int, int * );
+   int  astGetFitsCF_( AstFitsChan *, const char *, double *, int * );
+   int  astGetFitsCI_( AstFitsChan *, const char *, int *, int * );
+   int  astGetFitsCN_( AstFitsChan *, const char *, char **, int * );
+   int  astGetFitsF_( AstFitsChan *, const char *, double *, int * );
+   int  astGetFitsI_( AstFitsChan *, const char *, int *, int * );
+   int  astGetFitsL_( AstFitsChan *, const char *, int *, int * );
+   int  astGetFitsS_( AstFitsChan *, const char *, char **, int * );
+   int  astTestFits_( AstFitsChan *, const char *, int *, int * );
+   void astDelFits_( AstFitsChan *, int * );
+   void astReadFits_( AstFitsChan *, int * );
+   void astWriteFits_( AstFitsChan *, int * );
+   void astEmptyFits_( AstFitsChan *, int * );
+   void astShowFits_( AstFitsChan *, int * );
+   void astPurgeWCS_( AstFitsChan *, int * );
+   void astPutCards_( AstFitsChan *, const char *, int * );
+   void astPutFits_( AstFitsChan *, const char [81], int, int * );
+   void astPutTable_( AstFitsChan *, struct AstFitsTable *, const char *, int * );
+   void astPutTables_( AstFitsChan *, AstKeyMap *, int * );
+   void astRemoveTables_( AstFitsChan *, const char *, int * );
+   void astRetainFits_( AstFitsChan *, int * );
+   void astSetFitsCF_( AstFitsChan *, const char *, double *, const char *, int, int * );
+   void astSetFitsCI_( AstFitsChan *, const char *, int *, const char *, int, int * );
+   void astSetFitsCM_( AstFitsChan *, const char *, int, int * );
+   void astSetFitsCN_( AstFitsChan *, const char *, const char *, const char *, int, int * );
+   void astSetFitsF_( AstFitsChan *, const char *, double, const char *, int, int * );
+   void astSetFitsI_( AstFitsChan *, const char *, int, const char *, int, int * );
+   void astSetFitsL_( AstFitsChan *, const char *, int, const char *, int, int * );
+   void astSetFitsS_( AstFitsChan *, const char *, const char *, const char *, int, int * );
+   void astSetFitsU_( AstFitsChan *, const char *, const char *, int, int * );
+
+   void astTableSource_( AstFitsChan *,
+                         void (*)( AstFitsChan *, const char *, int, int, int * ),
+                         int * );
+
+
+
+# if defined(astCLASS)  || defined(astFORTRAN77)         /* Protected or F77 interface */
+   void astSetTableSource_( AstFitsChan *,
+                            void (*)( void ),
+                            void (*)( void (*)( void ),
+                                      AstFitsChan *, const char *, int,
+                                      int, int * ),
+                            int * );
+
+#endif
+
+# if defined(astCLASS)           /* Protected */
+
+   int  astFitsEof_( AstFitsChan *, int * );
+   int  astFitsGetCom_( AstFitsChan *, const char *, char **, int * );
+   void astSetFitsCom_( AstFitsChan *, const char *, const char *, int, int * );
+
+   int  astKeyFields_( AstFitsChan *, const char *, int, int *, int *, int * );
+
+   int astGetCard_( AstFitsChan *, int * );
+   int astTestCard_( AstFitsChan *, int * );
+   void astSetCard_( AstFitsChan *, int, int * );
+   void astClearCard_( AstFitsChan *, int * );
+
+   int astGetDefB1950_( AstFitsChan *, int * );
+   int astTestDefB1950_( AstFitsChan *, int * );
+   void astSetDefB1950_( AstFitsChan *, int, int * );
+   void astClearDefB1950_( AstFitsChan *, int * );
+
+   int astGetTabOK_( AstFitsChan *, int * );
+   int astTestTabOK_( AstFitsChan *, int * );
+   void astSetTabOK_( AstFitsChan *, int, int * );
+   void astClearTabOK_( AstFitsChan *, int * );
+
+   int astGetCDMatrix_( AstFitsChan *, int * );
+   int astTestCDMatrix_( AstFitsChan *, int * );
+   void astSetCDMatrix_( AstFitsChan *, int, int * );
+   void astClearCDMatrix_( AstFitsChan *, int * );
+
+   int astGetPolyTan_( AstFitsChan *, int * );
+   int astTestPolyTan_( AstFitsChan *, int * );
+   void astSetPolyTan_( AstFitsChan *, int, int * );
+   void astClearPolyTan_( AstFitsChan *, int * );
+
+   int astGetCarLin_( AstFitsChan *, int * );
+   int astTestCarLin_( AstFitsChan *, int * );
+   void astSetCarLin_( AstFitsChan *, int, int * );
+   void astClearCarLin_( AstFitsChan *, int * );
+
+   int astGetIwc_( AstFitsChan *, int * );
+   int astTestIwc_( AstFitsChan *, int * );
+   void astSetIwc_( AstFitsChan *, int, int * );
+   void astClearIwc_( AstFitsChan *, int * );
+
+   int astGetClean_( AstFitsChan *, int * );
+   int astTestClean_( AstFitsChan *, int * );
+   void astSetClean_( AstFitsChan *, int, int * );
+   void astClearClean_( AstFitsChan *, int * );
+
+   int astGetFitsDigits_( AstFitsChan *, int * );
+   int astTestFitsDigits_( AstFitsChan *, int * );
+   void astSetFitsDigits_( AstFitsChan *, int, int * );
+   void astClearFitsDigits_( AstFitsChan *, int * );
+
+   const char *astGetFitsAxisOrder_( AstFitsChan *, int * );
+   int astTestFitsAxisOrder_( AstFitsChan *, int * );
+   void astSetFitsAxisOrder_( AstFitsChan *, const char *, int * );
+   void astClearFitsAxisOrder_( AstFitsChan *, int * );
+
+   const char *astGetAllWarnings_( AstFitsChan *, int * );
+
+   const char *astGetWarnings_( AstFitsChan *, int * );
+   int astTestWarnings_( AstFitsChan *, int * );
+   void astClearWarnings_( AstFitsChan *, int * );
+   void astSetWarnings_( AstFitsChan *, const char *, int * );
+
+   int astGetNcard_( AstFitsChan *, int * );
+
+   int astGetCardType_( AstFitsChan *, int * );
+   const char *astGetCardName_( AstFitsChan *, int * );
+   const char *astGetCardComm_( AstFitsChan *, int * );
+
+   int astGetNkey_( AstFitsChan *, int * );
+
+   int astGetEncoding_( AstFitsChan *, int * );
+   int astTestEncoding_( AstFitsChan *, int * );
+   void astSetEncoding_( AstFitsChan *, int, int * );
+   void astClearEncoding_( AstFitsChan *, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckFitsChan(this) astINVOKE_CHECK(FitsChan,this,0)
+#define astVerifyFitsChan(this) astINVOKE_CHECK(FitsChan,this,1)
+
+/* Test class membership. */
+#define astIsAFitsChan(this) astINVOKE_ISA(FitsChan,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astFitsChan astINVOKE(F,astFitsChan_)
+#else
+#define astFitsChan astINVOKE(F,astFitsChanId_)
+#define astFitsChanFor astINVOKE(F,astFitsChanForId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitFitsChan(mem,size,init,vtab,name,source,sourcewrap,sink,sinkwrap) \
+astINVOKE(O,astInitFitsChan_(mem,size,init,vtab,name,source,sourcewrap,sink,sinkwrap,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitFitsChanVtab(vtab,name) astINVOKE(V,astInitFitsChanVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadFitsChan(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadFitsChan_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+
+/* More include files. */
+/* =================== */
+/* The interface to the FitsTable class must be included here (after the
+   type definitions for the FitsChan class) because "fitstable.h" itself
+   includes this file ("fitschan.h"), although "fitschan.h" refers to the
+   AstFitsTable structure above. This seems a little strange at first,
+   but is simply analogous to making a forward reference to a
+   structure type when recursively defining a normal C structure
+   (except that here the definitions happen to be in separate include
+   files). */
+#include "fitstable.h"
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckFitsChan to validate FitsChan pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#define astPutFits(this,card,overwrite) \
+astINVOKE(V,astPutFits_(astCheckFitsChan(this),card,overwrite,STATUS_PTR))
+
+#define astPutCards(this,cards) \
+astINVOKE(V,astPutCards_(astCheckFitsChan(this),cards,STATUS_PTR))
+
+#define astDelFits(this) \
+astINVOKE(V,astDelFits_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astPurgeWCS(this) \
+astINVOKE(V,astPurgeWCS_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astGetTables(this) \
+astINVOKE(O,astGetTables_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astPutTable(this,table,extnam) \
+astINVOKE(V,astPutTable_(astCheckFitsChan(this),astCheckFitsTable(table),extnam,STATUS_PTR))
+
+#define astPutTables(this,tables) \
+astINVOKE(V,astPutTables_(astCheckFitsChan(this),astCheckKeyMap(tables),STATUS_PTR))
+
+#define astRemoveTables(this,key) \
+astINVOKE(V,astRemoveTables_(astCheckFitsChan(this),key,STATUS_PTR))
+
+#define astRetainFits(this) \
+astINVOKE(V,astRetainFits_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astFindFits( this, name, card, inc ) \
+astINVOKE(V,astFindFits_(astCheckFitsChan(this),name,card,inc,STATUS_PTR))
+
+#define astSetFitsI(this,name,value,comment,overwrite) \
+astINVOKE(V,astSetFitsI_(astCheckFitsChan(this),name,value,comment,overwrite,STATUS_PTR))
+
+#define astSetFitsF(this,name,value,comment,overwrite) \
+astINVOKE(V,astSetFitsF_(astCheckFitsChan(this),name,value,comment,overwrite,STATUS_PTR))
+
+#define astSetFitsS(this,name,value,comment,overwrite) \
+astINVOKE(V,astSetFitsS_(astCheckFitsChan(this),name,value,comment,overwrite,STATUS_PTR))
+
+#define astSetFitsCN(this,name,value,comment,overwrite) \
+astINVOKE(V,astSetFitsCN_(astCheckFitsChan(this),name,value,comment,overwrite,STATUS_PTR))
+
+#define astSetFitsCI(this,name,value,comment,overwrite) \
+astINVOKE(V,astSetFitsCI_(astCheckFitsChan(this),name,value,comment,overwrite,STATUS_PTR))
+
+#define astSetFitsCF(this,name,value,comment,overwrite) \
+astINVOKE(V,astSetFitsCF_(astCheckFitsChan(this),name,value,comment,overwrite,STATUS_PTR))
+
+#define astSetFitsL(this,name,value,comment,overwrite) \
+astINVOKE(V,astSetFitsL_(astCheckFitsChan(this),name,value,comment,overwrite,STATUS_PTR))
+
+#define astSetFitsU(this,name,comment,overwrite) \
+astINVOKE(V,astSetFitsU_(astCheckFitsChan(this),name,comment,overwrite,STATUS_PTR))
+
+#define astSetFitsCM(this,comment,overwrite) \
+astINVOKE(V,astSetFitsCM_(astCheckFitsChan(this),comment,overwrite,STATUS_PTR))
+
+#define astGetFitsCF(this,name,value) \
+astINVOKE(V,astGetFitsCF_(astCheckFitsChan(this),name,value,STATUS_PTR))
+
+#define astGetFitsCI(this,name,value) \
+astINVOKE(V,astGetFitsCI_(astCheckFitsChan(this),name,value,STATUS_PTR))
+
+#define astGetFitsF(this,name,value) \
+astINVOKE(V,astGetFitsF_(astCheckFitsChan(this),name,value,STATUS_PTR))
+
+#define astGetFitsI(this,name,value) \
+astINVOKE(V,astGetFitsI_(astCheckFitsChan(this),name,value,STATUS_PTR))
+
+#define astGetFitsL(this,name,value) \
+astINVOKE(V,astGetFitsL_(astCheckFitsChan(this),name,value,STATUS_PTR))
+
+#define astTestFits(this,name,there) \
+astINVOKE(V,astTestFits_(astCheckFitsChan(this),name,there,STATUS_PTR))
+
+#define astGetFitsS(this,name,value) \
+astINVOKE(V,astGetFitsS_(astCheckFitsChan(this),name,value,STATUS_PTR))
+
+#define astGetFitsCN(this,name,value) \
+astINVOKE(V,astGetFitsCN_(astCheckFitsChan(this),name,value,STATUS_PTR))
+
+#define astReadFits(this) \
+astINVOKE(V,astReadFits_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astWriteFits(this) \
+astINVOKE(V,astWriteFits_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astEmptyFits(this) \
+astINVOKE(V,astEmptyFits_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astShowFits(this) \
+astINVOKE(V,astShowFits_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astTableSource(this,tabsource) \
+astINVOKE(V,astTableSource_(astCheckFitsChan(this),tabsource,STATUS_PTR))
+
+
+#if defined(astCLASS) || defined(astFORTRAN77) /* Protected or F77 interface */
+
+#define astSetTableSource(this,tabsource,tabsource_wrap) \
+astINVOKE(V,astSetTableSource_(astCheckFitsChan(this),tabsource,tabsource_wrap,STATUS_PTR))
+
+#endif
+
+
+#if defined(astCLASS)            /* Protected */
+
+#define astFitsEof(this) \
+astINVOKE(V,astFitsEof_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astFitsGetCom(this,name,comment) \
+astINVOKE(V,astFitsGetCom_(astCheckFitsChan(this),name,comment,STATUS_PTR))
+
+#define astSetFitsCom(this,name,comment,overwrite) \
+astINVOKE(V,astSetFitsCom_(astCheckFitsChan(this),name,comment,overwrite,STATUS_PTR))
+
+#define astKeyFields(this,filter,maxfld,ubnd,lbnd) \
+astINVOKE(V,astKeyFields_(astCheckFitsChan(this),filter,maxfld,ubnd,lbnd,STATUS_PTR))
+
+#define astClearCard(this) \
+astINVOKE(V,astClearCard_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetCard(this) \
+astINVOKE(V,astGetCard_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetCard(this,card) \
+astINVOKE(V,astSetCard_(astCheckFitsChan(this),card,STATUS_PTR))
+#define astTestCard(this) \
+astINVOKE(V,astTestCard_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearDefB1950(this) \
+astINVOKE(V,astClearDefB1950_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetDefB1950(this) \
+astINVOKE(V,astGetDefB1950_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetDefB1950(this,defb950) \
+astINVOKE(V,astSetDefB1950_(astCheckFitsChan(this),defb950,STATUS_PTR))
+#define astTestDefB1950(this) \
+astINVOKE(V,astTestDefB1950_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearTabOK(this) \
+astINVOKE(V,astClearTabOK_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetTabOK(this) \
+astINVOKE(V,astGetTabOK_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetTabOK(this,tabok) \
+astINVOKE(V,astSetTabOK_(astCheckFitsChan(this),tabok,STATUS_PTR))
+#define astTestTabOK(this) \
+astINVOKE(V,astTestTabOK_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearCDMatrix(this) \
+astINVOKE(V,astClearCDMatrix_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetCDMatrix(this) \
+astINVOKE(V,astGetCDMatrix_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetCDMatrix(this,cdmatrix) \
+astINVOKE(V,astSetCDMatrix_(astCheckFitsChan(this),cdmatrix,STATUS_PTR))
+#define astTestCDMatrix(this) \
+astINVOKE(V,astTestCDMatrix_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearPolyTan(this) \
+astINVOKE(V,astClearPolyTan_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetPolyTan(this) \
+astINVOKE(V,astGetPolyTan_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetPolyTan(this,value) \
+astINVOKE(V,astSetPolyTan_(astCheckFitsChan(this),value,STATUS_PTR))
+#define astTestPolyTan(this) \
+astINVOKE(V,astTestPolyTan_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearCarLin(this) \
+astINVOKE(V,astClearCarLin_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetCarLin(this) \
+astINVOKE(V,astGetCarLin_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetCarLin(this,carln) \
+astINVOKE(V,astSetCarLin_(astCheckFitsChan(this),carln,STATUS_PTR))
+#define astTestCarLin(this) \
+astINVOKE(V,astTestCarLin_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearClean(this) \
+astINVOKE(V,astClearClean_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetClean(this) \
+astINVOKE(V,astGetClean_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetClean(this,value) \
+astINVOKE(V,astSetClean_(astCheckFitsChan(this),value,STATUS_PTR))
+#define astTestClean(this) \
+astINVOKE(V,astTestClean_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearFitsDigits(this) \
+astINVOKE(V,astClearFitsDigits_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetFitsDigits(this) \
+astINVOKE(V,astGetFitsDigits_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetFitsDigits(this,fitsdigits) \
+astINVOKE(V,astSetFitsDigits_(astCheckFitsChan(this),fitsdigits,STATUS_PTR))
+#define astTestFitsDigits(this) \
+astINVOKE(V,astTestFitsDigits_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearFitsAxisOrder(this) \
+astINVOKE(V,astClearFitsAxisOrder_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetFitsAxisOrder(this) \
+astINVOKE(V,astGetFitsAxisOrder_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetFitsAxisOrder(this,fitsaxisorder) \
+astINVOKE(V,astSetFitsAxisOrder_(astCheckFitsChan(this),fitsaxisorder,STATUS_PTR))
+#define astTestFitsAxisOrder(this) \
+astINVOKE(V,astTestFitsAxisOrder_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearWarnings(this) \
+astINVOKE(V,astClearWarnings_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetWarnings(this) \
+astINVOKE(V,astGetWarnings_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetWarnings(this,warnings) \
+astINVOKE(V,astSetWarnings_(astCheckFitsChan(this),warnings,STATUS_PTR))
+#define astTestWarnings(this) \
+astINVOKE(V,astTestWarnings_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astGetAllWarnings(this) \
+astINVOKE(V,astGetAllWarnings_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astGetCardType(this) \
+astINVOKE(V,astGetCardType_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astGetCardName(this) \
+astINVOKE(V,astGetCardName_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astGetCardComm(this) \
+astINVOKE(V,astGetCardComm_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astGetNcard(this) \
+astINVOKE(V,astGetNcard_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astGetNkey(this) \
+astINVOKE(V,astGetNkey_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearEncoding(this) \
+astINVOKE(V,astClearEncoding_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetEncoding(this) \
+astINVOKE(V,astGetEncoding_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetEncoding(this,encoding) \
+astINVOKE(V,astSetEncoding_(astCheckFitsChan(this),encoding,STATUS_PTR))
+#define astTestEncoding(this) \
+astINVOKE(V,astTestEncoding_(astCheckFitsChan(this),STATUS_PTR))
+
+#define astClearIwc(this) \
+astINVOKE(V,astClearIwc_(astCheckFitsChan(this),STATUS_PTR))
+#define astGetIwc(this) \
+astINVOKE(V,astGetIwc_(astCheckFitsChan(this),STATUS_PTR))
+#define astSetIwc(this,iwc) \
+astINVOKE(V,astSetIwc_(astCheckFitsChan(this),iwc,STATUS_PTR))
+#define astTestIwc(this) \
+astINVOKE(V,astTestIwc_(astCheckFitsChan(this),STATUS_PTR))
+
+#endif
+
+#endif
+
+
+
+
+
diff --git a/ast/fitstable.c b/ast/fitstable.c
new file mode 100644
index 0000000..148431c
--- /dev/null
+++ b/ast/fitstable.c
@@ -0,0 +1,3006 @@
+/*
+*class++
+*  Name:
+*     FitsTable
+
+*  Purpose:
+*     A representation of a FITS binary table.
+
+*  Constructor Function:
+c     astFitsTable
+f     AST_FITSTABLE
+
+*  Description:
+*     The FitsTable class is a representation of a FITS binary table. It
+*     inherits from the Table class. The parent Table is used to hold the
+*     binary data of the main table, and a FitsChan (encapsulated within
+*     the FitsTable) is used to hold the FITS header.
+*
+*     Note - it is not recommended to use the FitsTable class to store
+*     very large tables.
+*
+*     FitsTables are primarily geared towards the needs of the "-TAB"
+*     algorithm defined in FITS-WCS paper 2, and so do not support all
+*     features of FITS binary tables. In particularly, they do not
+*     provide any equivalent to the following features of FITS binary
+*     tables: "heap" data (i.e. binary data following the main table),
+*     columns holding complex values, columns holding variable length
+*     arrays, scaled columns, column formats, columns holding bit values,
+*     8-byte integer values or logical values.
+
+*  Inheritance:
+*     The FitsTable class inherits from the Table class.
+
+*  Attributes:
+*     The FitsTable class does not define any new attributes beyond
+*     those which are applicable to all Tables.
+
+*  Functions:
+c     In addition to those functions applicable to all Tables, the
+c     following functions may also be applied to all FitsTables:
+f     In addition to those routines applicable to all Tables, the
+f     following routines may also be applied to all FitsTables:
+*
+c     - astColumnNull: Get/set the null value for a column of a FitsTable
+c     - astColumnSize: Get number of bytes needed to hold a full column of data
+c     - astGetColumnData: Retrieve all the data values stored in a column
+c     - astGetTableHeader: Get the FITS headers from a FitsTable
+c     - astPutColumnData: Store data values in a column
+c     - astPutTableHeader: Store FITS headers within a FitsTable
+f     - AST_COLUMNNULL: Get/set the null value for a column of a FitsTable
+f     - AST_COLUMNSIZE: Get number of bytes needed to hold a full column of data
+f     - AST_GETCOLUMNDATA: Retrieve all the data values stored in a column
+f     - AST_GETTABLEHEADER: Get the FITS headers from a FitsTable
+f     - AST_PUTCOLUMNDATA: Store data values in a column
+f     - AST_PUTTABLEHEADER: Store FITS headers within a FitsTable
+
+*  Copyright:
+*     Copyright (C) 2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     25-NOV-2010 (DSB):
+*        Original version.
+*     2-OCT-2012 (DSB):
+*        Check for Infs as well as NaNs.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS FitsTable
+
+/* The KeyMap key use to store the null value for a column. */
+#define NULLKEY "Null"
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "table.h"               /* Tables (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "pointset.h"            /* For astCheckNaN(F) functions */
+#include "fitstable.h"           /* Interface definition for this class */
+
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_equal)( AstObject *, AstObject *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+static void (* parent_addcolumn)( AstTable *, const char *, int, int, int *, const char *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(FitsTable)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(FitsTable,Class_Init)
+#define class_vtab astGLOBAL(FitsTable,Class_Vtab)
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstFitsTableVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstFitsTable *astFitsTableId_( void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstFitsChan *GetTableHeader( AstFitsTable *, int * );
+static char *MakeKey( const char *, int, char *, int, int * );
+static int ColumnNull( AstFitsTable *, const char *, int, int, int *, int *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetObjSize( AstObject *, int * );
+static size_t ColumnSize( AstFitsTable *, const char *, int * );
+static void AddColumn( AstTable *, const char *, int, int, int *, const char *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void CopyStrings( int, size_t, const char *, char *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void GenerateColumns( AstFitsTable *, AstFitsChan *, int * );
+static void GetColumnData( AstFitsTable *, const char *, float, double, size_t, void *, int *, int * );
+static void PurgeHeader( AstFitsTable *, int * );
+static void PutColumnData( AstFitsTable *, const char *, int, size_t, void *, int * );
+static void PutTableHeader( AstFitsTable *, AstFitsChan *, int * );
+static void UpdateHeader( AstFitsTable *, const char *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+
+static void AddColumn( AstTable *this, const char *name, int type,
+                       int ndim, int *dims, const char *unit, int *status ) {
+/*
+*  Name:
+*     AddColumn
+
+*  Purpose:
+*     Add a new column definition to a FitsTable.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     void AddColumn( AstTable *this, const char *name, int type, int ndim,
+*                     int *dims, const char *unit, int *status )
+
+*  Class Membership:
+*     FitsTable member function (over-rides the astAddColumn method
+*     inherited from the Table class).
+
+*  Description:
+*     Adds the definition of a new column to the supplied table. Initially,
+*     the column contains a null value for every row. Values may be added
+*     subsequently using the methods of the KeyMap class.
+*
+*     The FitsTable class extend the method inherited from the parent
+*     Table class in order to prevent the addition of columns with properties
+*     not supported by FITS binary tables.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     name
+*        The column name. Trailing spaces are ignored (all other spaces
+*        are significant). The supplied string is converted to upper case.
+*     type
+*        The data type associated with the column. One of AST__INTTYPE
+*        (for integer), AST__SINTTYPE (for short int), AST__BYTETYPE (for
+*        unsigned bytes - i.e. unsigned chars), AST__DOUBLETYPE (for double
+*        precision floating point), AST__FLOATTYPE (for single precision
+*        floating point), AST__STRINGTYPE (for character string). Note,
+*        pointers and undefined values cannot be stored in a FitsTable
+*        column.
+*     ndim
+*        The number of dimensions spanned by the values stored in a single
+*        cell of the column. Zero if the column holds scalar values.
+*     dims
+*        An array holding the the lengths of each of the axes spanned by
+*        the values stored in a single cell of the column. Ignored if the
+*        column holds scalara values.
+*     unit
+*        A string specifying the units of the column. Supply a blank
+*        string if the column is unitless.
+*     status
+*        Pointer to the inherited status.
+
+*  Notes:
+*     - This function returns without action if a column already exists in
+*     the Table with the supplied name and properties. However an error is
+*     reported if any of the properties differ.
+*/
+
+/* Local Variables: */
+   const char *text;     /* Data type string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Report an error if the supplied data type is supported by the Table
+   class but not by the FitsTable class. */
+   if( type == AST__OBJECTTYPE ) {
+      text = "Object pointer";
+
+   } else if( type == AST__POINTERTYPE ) {
+      text = "generic pointer";
+
+   } else if( type == AST__UNDEFTYPE ) {
+      text = "undefined type";
+
+   } else {
+      text = NULL;
+   }
+
+   if( text ) {
+      astError( AST__NAXIN, "astAddColumn(%s): Bad data type (%s) supplied "
+                "for new column %s. The %s class does not support %s "
+                "columns.", status, astGetClass( this ), text, name,
+                astGetClass( this ), text );
+
+/* Otherwise, invoke the parent method to add the column. */
+   } else {
+      (*parent_addcolumn)( this, name, type, ndim, dims, unit, status );
+   }
+}
+
+static int ColumnNull( AstFitsTable *this, const char *column, int set,
+                       int newval, int *wasset, int *hasnull, int *status ){
+/*
+*++
+*  Name:
+c     astColumnNull
+f     AST_COLUMNNULL
+
+*  Purpose:
+*     Get or set the null value for an integer column of a FITS table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     int astColumnNull( AstFitsTable *this, const char *column, int set,
+c                        int newval, int *wasset, int *hasnull )
+f     RESULT = AST_COLUMNNULL( THIS, COLUMN, SET, NEWVAL, WASSET, HASNULL,
+f                              STATUS )
+
+*  Class Membership:
+*     FitsTable method.
+
+*  Description:
+*     This function allows a null value to be stored with a named
+*     integer-valued column in a FitsTable. The supplied null value is
+*     assigned to the TNULLn keyword in the FITS header associated with
+*     the FitsTable. A value in the named column is then considered to be
+*     null if 1) it equals the null value supplied to this function, or
+*     2) no value has yet been stored in the cell.
+*
+*     As well as setting a new null value, this function also returns the
+*     previous null value. If no null value has been set previously, a
+*     default value will be returned. This default will be an integer
+*     value that does not currently occur anywhere within the named column.
+*     If no such value can be found, what happens depends on whether the
+*     column contains any cells in which no values have yet been stored.
+*     If so, an error will be reported. Otherwise (i.e. if there are no
+*     null values in the column), an arbitrary value of zero will be
+*     returned as the function value, and no TNULLn keyword will be
+*     stored in the FITS header.
+*
+*     A flag is returned indicating if the returned null value was set
+*     explicitly by a previous call to this function, or is a default
+*     value.
+*
+*     A second flag is returned indicating if the named column contains
+*     any null values (i.e. values equal to the supplied null value, or
+*     cells to which no value has yet been assigned).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     column
+f     COLUMN = CHARACTER * ( * ) (Given)
+*        The character string holding the name of the column. Trailing
+*        spaces are ignored.
+c     set
+f     SET = LOGICAL (Given)
+c        If non-zero, the value supplied for parameter "newval"
+f        If .TRUE., the value supplied for argument NEWVAL
+*        will be stored as the current null value, replacing any value
+*        set by a previous call to this function.
+c        If zero, the value supplied for parameter "newval"
+f        If .FALSE., the value supplied for argument NEWVAL
+*        is ignored and the current null value is left unchanged.
+c     newval
+f     NEWVAL = INTEGER (Given)
+*        The new null value to use. Ignored if
+c        "set" is zero.
+f        SET is .FALSE.
+*        An error will be reported if the supplied value is outside the
+*        range of values that can be stored in the integer data type
+*        associated with the column.
+c     wasset
+f     WASSET = LOGICAL (Returned)
+c        Pointer to an int that will be returned non-zero
+f        .TRUE. will be returned
+*        if the returned null value was set previously via an
+*        earlier invocation of this function.
+c        Zero
+f        .FALSE.
+*        is returned otherwise. If the named column does not exist, or an
+*        error occurs, a value of
+c        zero is returned.
+f        .FALSE. is returned.
+c     hasnull
+f     HASNULL = LOGICAL (Returned)
+c        Pointer to an int that will be returned non-zero
+f        .TRUE. will be returned
+*        if and only if the named column currently contains any values
+*        equal to the null value on exit (i.e.
+c        "newval" if "set" is non-zero,
+f        NEWVAL if SET is .TRUE.
+*        or the returned function value otherwise), or contains any empty
+*        cells. If the named column does not exist, or an error occurs, a
+*        value of
+c        zero is returned.
+f        .FALSE. is returned.
+c        If a NULL pointer is supplied for "hasnull", no check on the
+c        presence of null values will be performed.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astColumnNull()
+f     AST_COLUMNNULL = INTEGER
+*        The null value that was in use on entry to this function. If a
+*        null value has been set by a previous invocation of this
+*        function, it will be returned. Otherwise, if
+c        "set" is non-zero, the supplied "newval"
+f        SET is .TRUE., the supplied NEWVAL
+*        value is returned. Otherwise, a default value is chosen (if
+*        possible) that does not currently occur in the named column. If
+*        all available values are in use in the column, an error is
+*        reported if and only if the column contains any empty cells.
+*        Otherwise, a value of zero is returned. A value of zero is also
+*        returned if the named column does not exist, or an error occurs.
+
+*  Notes:
+*     - The FITS binary table definition allows only integer-valued
+*     columns to have an associated null value. This routine will return
+*     without action if the column is not integer-valued.
+
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *col_km;      /* KeyMap holding named column definition */
+   AstKeyMap *cols;        /* KeyMap holding all column definitions */
+   char key[ AST__MXCOLKEYLEN + 1 ]; /* Current cell key string */
+   int *cell;              /* Pointer to array of cell values */
+   int foundhi;            /* Has an occurrence of "nullhi" been found yet? */
+   int foundlo;            /* Has an occurrence of "nulllo" been found yet? */
+   int gotresult;          /* Has a usable value been put into "result"? */
+   int idim;               /* Index of current axis in each column's value */
+   int iel;                /* Index of current element within cell value */
+   int imax;               /* Maximum storable value */
+   int imin;               /* Minimum storable value */
+   int irow;               /* Index of current row in table */
+   int ndim;               /* Number of axes in each column's value */
+   int nel;                /* Total number of values in each cell */
+   int nrow;               /* Number of rows in table */
+   int null;               /* The null value on exit */
+   int nullfound;          /* Has a null value been found in the column yet? */
+   int nullhi;             /* Higher candidate default null value */
+   int nulllo;             /* Lower candidate default null value */
+   int result;             /* Returned value */
+   int type;               /* Column data type */
+
+/* Initialise */
+   result = 0;
+   *wasset = 0;
+   if( hasnull ) *hasnull = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Store the max and min integer values that can be store din the column
+   data type. */
+   type = astGetColumnType( this, column );
+   if( type == AST__BYTETYPE ) {
+      imin = 0;
+      imax = UCHAR_MAX;
+
+   } else if( type == AST__SINTTYPE ) {
+      imin = SHRT_MIN;
+      imax = SHRT_MAX;
+
+   } else if( type == AST__INTTYPE ) {
+      imin = INT_MIN;
+      imax = INT_MAX;
+
+   } else {
+      imax = 0;
+      imin = 0;
+   }
+
+/* Check the named column contains integer values of any length. */
+   if( imax > imin ) {
+
+/* Get the KeyMap holding information about all columns. */
+      cols = astColumnProps( this );
+
+/* Get the KeyMap holding information about the named column. */
+      if( astMapGet0A( cols, column, &col_km ) ) {
+
+/* If the column definition already includes a null value, put it into
+   "result". Also store the "*wasset" flag that indicates if the returned
+   null value is a default value or not. */
+         *wasset = astMapGet0I( col_km, NULLKEY, &result );
+
+/* If a new null value is to be established... */
+         if( set ) {
+
+/* If there was no previously set null value, return the new null value
+   as the function value. */
+            if( ! *wasset ) result = newval;
+
+/* Indicate we now know what the returned function value is. */
+            gotresult = 1;
+
+/* Save the null value that will be in use when this function exits. */
+            null = newval;
+
+/* Check the supplied value is in range. If so store it in the column
+   keymap. Otherwise report an error. */
+            if( null >= imin && null <= imax ) {
+               astMapPut0I( col_km, NULLKEY, null, NULL );
+
+            } else if( astOK ) {
+               astError( AST__BADNULL, "astColumnNull(%s): Supplied null "
+                         "value (%d) is outside the range of integers "
+                         "that can be stored in column '%s'.", status,
+                         astGetClass( this ), newval, column );
+            }
+
+/* If no new null value was supplied, the null value on exit will be the
+   previously set value, if any. */
+         } else {
+            null = result;
+            gotresult = *wasset;
+         }
+
+/* The rest is only needed if we need to find a default result value, or if
+   we need to check if there are any null values in the table. */
+         if( !gotresult || hasnull ) {
+
+/* Get the total number of values in each cell of the column. */
+            nel = astGetColumnLength( this, column );
+
+/* Allocate memory to hold the values in a single cell of the column,
+   stored as ints. */
+            cell = astMalloc( nel*sizeof( int ) );
+
+/* No null values found yet. */
+            nullfound = 0;
+
+/* On the first pass round the following loop, we search for occurrences
+   of the highest and lowest integer values allowed in the column. If no
+   such occurrences are found we use one or the other as the default null
+   value. If occurrences of both of these values are found, we change the
+   values and start the search again. */
+            nullhi = imax;
+            nulllo = imin;
+            foundlo = 0;
+            foundhi = 0;
+
+/* Loop round all rows in the Table. */
+            nrow = astGetNrow( this );
+            for( irow = 1; irow <= nrow && astOK; irow++ ) {
+
+/* Format the cell name. */
+               (void) MakeKey( column, irow, key, AST__MXCOLKEYLEN + 1,
+                               status );
+
+/* Attempt to get the values in the cell */
+               if( astMapGet1I( this, key, nel, &nel, cell ) ) {
+
+/* Get the number of dimensions. */
+                  ndim = astGetColumnNdim( this, column );
+
+/* If we know what the null value is on exit, check the cell for such null
+   values (but only if the caller want s to know). Skip this check after the
+   first null is found. */
+                  if( gotresult ) {
+                     if( ! nullfound ) {
+                        for( idim = 0; idim < ndim; idim++ ) {
+                           if( cell[ idim ] == null ) {
+                              nullfound = 1;
+                              break;
+                           }
+                        }
+                     }
+
+/* If we do not yet know what the returned value is, we try to find an
+   integer value within the allowed data range that is not currently used in
+   the column. For the moment, this is a no-brain algorithm that will
+   become untenable for large tables. Need to fix it when it is apparent
+   that it is causing a problem. */
+                  } else if( nulllo <= nullhi ) {
+
+/* See if the current cell contains any occurrences of either of the
+   two currently nominated null values. Is so, increment the matched
+   nominated null value, and start again at row 1. */
+                     for( iel = 0; iel < nel; iel++ ) {
+
+                        if( cell[ iel ] == nulllo ) {
+                           foundlo = 1;
+                        } else if( cell[ iel ] == nullhi ) {
+                           foundhi = 1;
+                        }
+
+                        if( foundlo && foundhi ) {
+                           nullhi--;
+                           nulllo++;
+                           irow = 0;
+                           foundlo = 0;
+                           foundhi = 0;
+                           continue;
+                        }
+
+                     }
+                  }
+
+/* If the column does not contain anything in the current cell, we know
+   there is at least one null value in the column, so store a non-zero value
+   in the returned flag. */
+               } else {
+                  nullfound = 1;
+               }
+
+/* If we now have a value for the result and know that there are nulls in
+   the column, we can leave the loop. */
+               if( gotresult && nullfound ) break;
+            }
+
+/* Return the "null found" flag if required. */
+            if( hasnull ) *hasnull = nullfound;
+
+/* If we have not yet stored the default null value to be returned as the
+   function value, do so now. If no unused value could be found, and
+   there are missing cells in the table, report an error. */
+            if( !gotresult ) {
+               if( !foundhi ) {
+                  result = nullhi;
+
+               } else if( !foundlo ) {
+                  result = nulllo;
+
+               } else if( nullfound && astOK ) {
+                  astError( AST__BADNULL, "astColumnNull(%s): Cannot find "
+                            "an unused value to use as the null value in "
+                            "column '%s'.", status, astGetClass( this ),
+                            column );
+               }
+            }
+
+/* Free resources */
+            cell = astFree( cell );
+         }
+         col_km = astAnnul( col_km );
+      }
+      cols = astAnnul( cols );
+   }
+
+/* Return null values if an error occurred. */
+   if( !astOK ) {
+      result = 0;
+      *wasset = 0;
+      if( hasnull ) *hasnull = 0;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static size_t ColumnSize( AstFitsTable *this, const char *column, int *status ){
+/*
+*++
+*  Name:
+c     astColumnSize
+f     AST_COLUMNSIZE
+
+*  Purpose:
+*     Get the number of bytes needed to hold a full column of data.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     size_t astColumnSize( AstFitsTable *this, const char *column,
+c                           int *hasnull )
+f     RESULT = AST_COLUMNSIZE( THIS, COLUMN, STATUS )
+
+*  Class Membership:
+*     FitsTable method.
+
+*  Description:
+*     This function returns the number of bytes of memory that must be
+*     allocated prior to retrieving the data from a column using
+c     astGetColumnData.
+f     AST_GETCOLUMNDATA.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     column
+f     COLUMN = CHARACTER * ( * ) (Given)
+*        The character string holding the name of the column. Trailing
+*        spaces are ignored.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astColumnNull()
+f     AST_COLUMNNULL = INTEGER
+*        The number of bytes required to store the column data.
+
+*  Notes:
+*     - An error will be reported if the named column does not exist in
+*     the FitsTable.
+*     - Zero will be returned as the function value in an error occurs.
+
+*--
+*/
+
+/* Local Variables: */
+   size_t result;          /* Returned value */
+   int type;               /* Column data type */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Find the number of bytes needed to hold a single element of the value
+   in a column cell. */
+   type = astGetColumnType( this, column );
+   if( type == AST__INTTYPE ) {
+      result = sizeof( int );
+
+   } else if(  type == AST__DOUBLETYPE ){
+      result = sizeof( double );
+
+   } else if(  type == AST__STRINGTYPE ){
+      result = astGetColumnLenC( this, column )*sizeof( char );
+
+   } else if(  type == AST__FLOATTYPE ){
+      result = sizeof( float );
+
+   } else if(  type == AST__SINTTYPE ){
+      result = sizeof( short int );
+
+   } else if(  type == AST__BYTETYPE ){
+      result = sizeof( char );
+
+   } else if( astOK ) {
+      astError( AST__INTER, "astColumnSize(%s): Unsupported column type "
+                "%d (internal AST programming error).", status,
+                astGetClass( this ), type );
+   }
+
+/* Multiply it by the number of elements per value. */
+   result *= astGetColumnLength( this, column );
+
+/* Multiply it by the number of values per column (i.e. the number of rows). */
+   result *= astGetNrow( this );
+
+/* Return zero if an error occurred. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static void CopyStrings( int nval, size_t nb, const char *cbuf, char *pout,
+                         int *status ){
+/*
+*  Name:
+*     CopyStrings
+
+*  Purpose:
+*     Remove terminating nulls from an array of fixed-length strings.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void CopyStrings( int nval, size_t nb, const char *cbuf, char *pout,
+*                       int *status )
+
+*  Description:
+*     This function copies null terminated strings from "cbuf" to "pout",
+*     removing the terminating nulls in the process. Thus each output string
+*     is one character shorter than the corresponding input string.
+
+*  Parameters:
+*     nval
+*        The number of strings to copy.
+*     nb
+*        The maximum length of each string, excluding trailing null.
+*     cbuf
+*        The input array holding "nval" adjacent strings, each occupying
+*        ( nb + 1 ) characters (the last one is the trailing null).
+*     pout
+*        The output array to which "nval" adjacent strings are written,
+*        each occupying ( nb ) characters (i.e. no trailing null).
+*     status
+*        Pointer to inherited status.
+
+*/
+
+/* Local Variables: */
+   int i;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Copy the first "nb" characters of each string. */
+   for( i = 0; i < nval; i++ ) {
+      memcpy( pout, cbuf, nb );
+
+/* Increment the pointer to the start of the next output string. */
+      pout += nb;
+
+/* Increment the pointer to the start of the next input string. */
+      cbuf += nb + 1;
+   }
+
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two FitsTables are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitstable.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     FitsTable member function (over-rides the astEqual protected
+*     method inherited from the astTable class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two FitsTables are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a FitsTable).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the FitsTables are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFitsTable *that;
+   AstFitsTable *this;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two FitsTable structures. */
+   this = (AstFitsTable *) this_object;
+   that = (AstFitsTable *) that_object;
+
+/* Check the second object is a FitsTable. We know the first is a
+   FitsTable since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAFitsTable( that ) ) {
+
+/* Check the FitsTables are equal when compared as Tables. */
+      if( (*parent_equal)( this_object, that_object, status ) ) {
+
+/* Check the headers are equal.  */
+         result = astEqual( this->header, that->header );
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static void GenerateColumns( AstFitsTable *this, AstFitsChan *header,
+                             int *status ) {
+/*
+*  Name:
+*     GenerateColumns
+
+*  Purpose:
+*     Add new column definitions to a FitsTable as defined by a FITS
+*     header.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     void GenerateColumns( AstFitsTable *this, AstFitsChan *header,
+*                           int *status )
+
+*  Class Membership:
+*     FitsTable member function
+
+*  Description:
+*     For each binary table column defined in the supplied FITS header,
+*     this function adds an equivalent column to the FitsTable.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsTable.
+*     header
+*        Pointer to a FitsChan holding the column definitions.
+*     status
+*        Pointer to the inherited status.
+
+*/
+
+/* Local Variables: */
+   char *cval;
+   char *name;
+   char *p;
+   char *unit;
+   char buff[ 50 ];
+   char code;
+   char keyword[ 20 ];
+   double dval;
+   int *dims;
+   int icol;
+   int idim;
+   int ival;
+   int nc;
+   int ncol;
+   int ndim;
+   int nel;
+   int repeat;
+   int type;
+   int wasset;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise */
+   type = AST__BADTYPE;
+
+/* Get the number of columns defined in the header. */
+   if( !astGetFitsI( header, "TFIELDS", &ncol ) ) ncol = 0;
+
+/* Add a column definition to the FitsTable for each column in the header. */
+   for( icol = 0; icol < ncol; icol++ ) {
+
+/* Get the TFORMi keyword that defines the column data type and shape from
+   the header. Report an error if it is missing. */
+      sprintf( keyword, "TFORM%d", icol + 1 );
+      if( !astGetFitsS( header, keyword, &cval ) && astOK ) {
+         astError( AST__NOFTS, "astFitsTable: Supplied FITS binary table "
+                   "header does not contain the required keyword '%s'.",
+                   status, keyword );
+      }
+
+/* Extract the repeat count and data type code from the TFORM string. */
+      if( sscanf( cval, "%d%n", &repeat, &nc ) == 0 ) {
+         repeat = 1;
+         nc = 0;
+      } else if( repeat < 0 && astOK ) {
+         astError( AST__BDFTS, "astFitsTable: Keyword '%s' in supplied FITS "
+                   "binary table header has unsupported value '%s'.", status,
+                   keyword, cval );
+      }
+      code = cval[ nc ];
+
+/* Get the corresponding KeyMap data type. Report an error if the FITS
+   data type is not supported by the KeyMap class. */
+      if( code == 'B' ) {
+         type = AST__BYTETYPE;
+
+      } else if( code == 'I' ) {
+         type = AST__SINTTYPE;
+
+      } else if( code == 'J' ) {
+         type = AST__INTTYPE;
+
+      } else if( code == 'D' ) {
+         type = AST__DOUBLETYPE;
+
+      } else if( code == 'E' ) {
+         type = AST__FLOATTYPE;
+
+      } else if( code == 'A' ) {
+         type = AST__STRINGTYPE;
+
+      } else if( astOK ){
+         astError( AST__BDFTS, "astFitsTable: Keyword '%s' in supplied FITS "
+                   "binary table header has unsupported value '%s'.", status,
+                   keyword, cval );
+      }
+
+/* The TTYPEi keyword gives the column name. Create a column name based
+   on the index of the column. */
+      sprintf( keyword, "TTYPE%d", icol + 1 );
+      if( !astGetFitsS( header, keyword, &cval ) ) {
+         sprintf( buff, "FCOLUMN%d", icol + 1 );
+         cval = buff;
+      }
+      name = astStore( NULL, cval, strlen( cval ) + 1 );
+
+/* Column units. */
+      sprintf( keyword, "TUNIT%d", icol + 1 );
+      if( !astGetFitsS( header, keyword, &cval ) ) {
+         buff[ 0 ] = 0;
+         cval = buff;
+      }
+      unit = astStore( NULL, cval, strlen( cval ) + 1 );
+
+/* Column shape is defined by the TDIMi keyword - in the form
+   "(i,j,k,...)". where i, j, k ... are the dimensions. If it is missing
+   then the field is assumed to be a 1D vector with the length specified by
+   the repeat count in the TFORMn keyword, or a scalar (if repeat cound
+   is one). */
+      sprintf( keyword, "TDIM%d", icol + 1 );
+      if( astGetFitsS( header, keyword, &cval ) ) {
+
+/* Count the commas in the keyword value. This equals one less than the
+   number of dimensions. */
+         ndim = 1;
+         p = cval;
+         while( *p ) {
+            if( *(p++) == ',' ) ndim++;
+         }
+
+/* Allocate memory for the dimensions. */
+         dims = astMalloc( ndim*sizeof( int ) );
+
+/* Find each dimension and copy it into the above memory. Also find the
+   total number of elements (nel). */
+         nel = 1;
+         idim = 0;
+         p = cval;
+         if( *p == '(' ) p++;
+         while( sscanf( p, "%d%n", dims + idim, &nc ) ) {
+            nel *= dims[ idim ];
+            idim++;
+            p += nc;
+            if( *p == ',' ) p++;
+         }
+
+/* For strings, the first TDIM value gives the length of the string, so
+   reduce the number of dimensions by one. */
+         if( type == AST__STRINGTYPE ) {
+            ndim--;
+            dims++;
+         }
+
+      } else {
+         nel = repeat;
+         if( nel == 1 ) {
+            ndim = 0;
+            dims = NULL;
+         } else {
+            ndim = 1;
+            dims = astMalloc( sizeof( int ) );
+            if( dims ) *dims = nel;
+         }
+      }
+
+/* Check the total number of elements equal the repeat count from the
+   TFORM keyword. */
+      if( repeat != nel && astOK ) {
+
+         sprintf( keyword, "TFORM%d", icol + 1 );
+         astGetFitsS( header, keyword, &cval );
+         strcpy( buff, cval );
+
+         sprintf( keyword, "TDIM%d", icol + 1 );
+         if( !astGetFitsS( header, keyword, &cval ) ) cval = " ";
+
+         astError( AST__BDFTS, "astFitsTable: Supplied FITS binary table "
+                   "header contains inconsistent TFORM (%s) and TDIM (%s) "
+                   "keywords for field %d.", status, buff, cval, icol + 1 );
+      }
+
+/* Check any TSCALi value is 1.0 */
+      sprintf( keyword, "TSCAL%d", icol + 1 );
+      if( astGetFitsF( header, keyword, &dval ) && dval != 1.0 && astOK ) {
+         astError( AST__BDFTS, "astFitsTable: Supplied FITS binary table "
+                   "header contains scaled columns which are not "
+                   "supported by AST.", status );
+      }
+
+/* Check any TZEROi value is 0.0 */
+      sprintf( keyword, "TSCAL%d", icol + 1 );
+      if( astGetFitsF( header, keyword, &dval ) && dval != 0.0 && astOK ) {
+         astError( AST__BDFTS, "astFitsTable: Supplied FITS binary table "
+                   "header contains scaled columns which are not "
+                   "supported by AST.", status );
+      }
+
+/* Add the column to the table. */
+      astAddColumn( this, name, type, ndim, dims, unit );
+
+/* Set the null value, if present. */
+      sprintf( keyword, "TNULL%d", icol + 1 );
+      if( astGetFitsI( header, keyword, &ival ) ) {
+         (void) astColumnNull( this, name, 1, ival, &wasset, NULL );
+      }
+
+/* Free resources. */
+      dims = astFree( dims - ( ( type == AST__STRINGTYPE ) ? 1 : 0 ) );
+      name = astFree( name );
+      unit = astFree( unit );
+
+   }
+}
+
+static void GetColumnData( AstFitsTable *this, const char *column,
+                           float fnull, double dnull, size_t mxsize,
+                           void *coldata, int *nelem, int *status ){
+/*
+*++
+*  Name:
+c     astGetColumnData
+f     AST_GETCOLUMNDATA
+
+*  Purpose:
+*     Retrieve all the data values stored in a column.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     void astGetColumnData( AstFitsTable *this, const char *column,
+c                            float fnull, double dnull, size_t mxsize,
+c                            void *coldata, int *nelem )
+f     CALL AST_GETCOLUMNDATA( THIS, COLUMN, RNULL, DNULL, MXSIZE,
+f                             COLDATA, NELEM, STATUS )
+
+*  Class Membership:
+*     FitsTable method.
+
+*  Description:
+c     This function
+f     This routine
+*     copies all data values from a named column into a supplied buffer
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsTable.
+c     column
+f     COLUMN = CHARACTER * ( * ) (Given)
+*        The character string holding the name of the column. Trailing
+*        spaces are ignored.
+c     fnull
+f     RNULL = REAL (Given)
+*        The value to return in
+c        "coldata"
+f        COLDATA
+*        for any cells for which no value has been stored in the
+*        FitsTable. Ignored if the column's data type is not
+*        AST__FLOATTYPE. Supplying
+c        AST__NANF
+f        AST__NANR
+*        will cause a single precision IEEE NaN value to be used.
+c     dnull
+f     DNULL = REAL (Given)
+*        The value to return in
+c        "coldata"
+f        COLDATA
+*        for any cells for which no value has been stored in the
+*        FitsTable. Ignored if the column's data type is not
+*        AST__DOUBLETYPE. Supplying AST__NAN will cause a double precision
+*        IEEE NaN value to be used.
+c     mxsize
+f     MXSIZE = INTEGER (Given)
+*        The size of the
+c        "coldata"
+f        COLDATA
+*        array, in bytes. The amount of memory needed to hold the data
+*        from a column may be determined using
+c        astColumnSize.
+f        AST_COLUMNSIZE.
+*        If the supplied array is too small to hold all the column data,
+*        trailing column values will be omitted from the returned array,
+*        but no error will be reported.
+c     coldata
+f     COLDATA( * ) = BYTE (Given)
+c        A pointer to an
+f        An
+*        area of memory in which to return the data
+*        values currently stored in the column. The values are stored in
+*        row order. If the column holds non-scalar values, the elements
+*        of each value are stored in "Fortran" order. No data type
+*        conversion is performed - the data type of each returned value
+*        is the data type associated with the column when the column was
+*        added to the table. If the column holds strings, the returned
+*        strings will be null terminated. Any excess room at the end of
+*        the array will be left unchanged.
+c     nelem
+f     NELEM = INTEGER (Return)
+*        The number of elements returned in the
+c        "coldata"
+f        COLDATA
+*        array. This is the product of the number of rows returned and
+*        the number of elements in each column value.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+f     - The RNULL and DNULL arguments
+c     - The "fnull" and "dnull" parameters
+*     specify the value to be returned for any empty cells within columns
+*     holding floating point values. For columns holding integer values,
+*     the value returned for empty cells is the value returned by the
+c     astColumNull function.
+f     AST_COLUMNNULL functiom.
+*     For columns holding string values, the ASCII NULL character is returned
+*     for empty cells.
+*--
+*/
+
+/* Local Variables: */
+   char *cbuf;       /* Array of strings returned by astMapGet1C */
+   char key[ AST__MXCOLKEYLEN + 1 ]; /* Current cell key string */
+   int iel;          /* Index of current element */
+   int irow;         /* Index of value being copied */
+   int nel;          /* No. of elements per value */
+   int nrow;         /* No. of values to copy */
+   int nval;         /* Number of values read from KeyMap entry */
+   int ok;           /* Was the value found in the KeyMap? */
+   int type;         /* Data type */
+   int wasset;       /* Was the integer null value set explicitly? */
+   size_t nb;        /* No. of bytes for a single element of a value */
+   size_t nbv;       /* No. of bytes per value */
+   void *pnull;      /* Pointer to a buffer holding a null value */
+   void *pout;       /* Pointer to next output element */
+
+/* Initialise */
+   *nelem = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise */
+   nb = 0;
+
+/* Find the number of bytes needed to hold a single element of the value
+   in a column cell. */
+   type = astGetColumnType( this, column );
+   if( type == AST__INTTYPE ) {
+      nb = sizeof( int );
+
+   } else if( type == AST__DOUBLETYPE ){
+      nb = sizeof( double );
+
+   } else if( type == AST__STRINGTYPE ){
+      nb = astGetColumnLenC( this, column )*sizeof( char );
+
+   } else if( type == AST__FLOATTYPE ){
+      nb = sizeof( float );
+
+   } else if( type == AST__SINTTYPE ){
+      nb = sizeof( short int );
+
+   } else if( type == AST__BYTETYPE ){
+      nb = sizeof( char );
+
+   } else if( astOK ) {
+      astError( AST__INTER, "astGetColumnData(%s): Unsupported column type "
+                "%d (internal AST programming error).", status,
+                astGetClass( this ), type );
+   }
+
+/* Get the number of elements per value, and the number of bytes per value. */
+   nel = astGetColumnLength( this, column );
+   nbv = nb*nel;
+
+/* Initialise a pointer to the next element of the output array to write to. */
+   pout = coldata;
+
+/* Get the number of rows in the table. */
+   nrow = astGetNrow( this );
+
+/* For string columns, the buffer returned by astMapGet1C will include a
+   null character at the end of each string. This is not required for the
+   fixed-length string format used by FITS binary tables, so for each row we
+   produce a copy of the string returned by astMapGet1C excluding the
+   trailing nulls. Allocate a buffer to receive the string returned by
+   astMapGet1C. */
+   if(  type == AST__STRINGTYPE ) {
+      cbuf = astMalloc( ( nb + 1 )*nel );
+   } else {
+      cbuf = NULL;
+   }
+
+/* If required, substitute NaN values for the supplied null values. */
+   fnull = astCheckNaNF( fnull );
+   dnull = astCheckNaN( dnull );
+
+/* Indicate we have not yet determined a null value for the column */
+   pnull = NULL;
+
+/* Reduce the number of rows to be returned if the returned array is too
+   small to hold all rows. */
+   if( mxsize < nbv*nrow ) nrow = mxsize/nbv;
+
+/* Loop round the returned rows rows. */
+   for( irow = 1; irow <= nrow; irow++ ) {
+
+/* Format the cell name. */
+      (void) MakeKey( column, irow, key, AST__MXCOLKEYLEN + 1,
+                      status );
+
+/* Get the values in the current cell of the column, using its native
+   data type. For floating point, convert any NaNs into the appropriate
+   null value (do not need to do this if the null value is itself NaN). */
+      if( type == AST__INTTYPE ) {
+         ok = astMapGet1I( this, key, nel, &nval, pout );
+
+      } else if(  type == AST__DOUBLETYPE ){
+         ok = astMapGet1D( this, key, nel, &nval, pout );
+
+         if( ok && astISFINITE(dnull) ) {
+            for( iel = 0; iel < nel; iel++ ) {
+               if( !astISFINITE( ((double *)pout)[ iel ] ) ) {
+                  ((double *)pout)[ iel ] = dnull;
+               }
+            }
+         }
+
+      } else if(  type == AST__FLOATTYPE ){
+         ok = astMapGet1F( this, key, nel, &nval, pout );
+
+         if( ok && astISFINITE(fnull) ) {
+            for( iel = 0; iel < nel; iel++ ) {
+               if( !astISFINITE( ((float *)pout)[ iel ] ) ) {
+                  ((float *)pout)[ iel ] = fnull;
+               }
+            }
+         }
+
+      } else if(  type == AST__SINTTYPE ){
+         ok = astMapGet1S( this, key, nel, &nval, pout );
+
+      } else if(  type == AST__BYTETYPE ){
+         ok = astMapGet1B( this, key, nel, &nval, pout );
+
+      } else if(  type == AST__STRINGTYPE ){
+         ok = astMapGet1C( this, key, nb + 1, nel, &nval, cbuf );
+
+/* Copy the strings returned by astMapGet1C into the returned array,
+   omitting the trailing null at the end of each string. */
+         CopyStrings( nval, nb, cbuf, pout, status );
+
+      } else {
+         ok = 0;
+      }
+
+/* If the cell could not be found, return a suitable number of column null
+   values. */
+      if( !ok ) {
+
+/* Determine the null value to use, if this has not already been done. */
+         if( !pnull ) {
+
+/* Allocate a buffer to hold a single null value */
+            pnull = astMalloc( nb );
+            if( astOK ) {
+
+/* Copy the appropriate null value into the buffer allocated above. */
+               if( type == AST__INTTYPE ) {
+                  *( (int *) pnull ) = astColumnNull( this, column, 0, 0,
+                                                      &wasset, NULL );
+               } else if(  type == AST__DOUBLETYPE ){
+                  *( (double *) pnull ) = dnull;
+
+               } else if(  type == AST__FLOATTYPE ){
+                  *( (float *) pnull ) = fnull;
+
+               } else if(  type == AST__STRINGTYPE ){
+                  memset( pnull, 0, nb );
+
+               } else if(  type == AST__SINTTYPE ){
+                  *( (short int *) pnull ) = astColumnNull( this, column, 0, 0,
+                                                            &wasset, NULL );
+               } else if(  type == AST__BYTETYPE ){
+                  *( (unsigned char *) pnull ) = astColumnNull( this, column, 0, 0,
+                                                                &wasset, NULL );
+               }
+            }
+         }
+
+/* Append the right number of nulls to the returned array. */
+         for( iel = 0; iel < nel; iel++ ) {
+            memcpy( pout, pnull, nb );
+            pout += nb;
+         }
+
+/* If the cell was found in the table, just increment the pointer to the next
+   returned value. */
+      } else {
+         pout += nbv;
+      }
+   }
+
+/* Free resources. */
+   cbuf = astFree( cbuf );
+   pnull = astFree( pnull );
+
+/* Return the number of returned elements. */
+   *nelem = nel*nrow;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitstable.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     FitsTable member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied FitsTables,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsTable.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The FitsTable size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFitsTable *this;            /* Pointer to FitsTable structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the FitsTable structure. */
+   this = (AstFitsTable *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent Table class, and
+   then add on any components of the class structure defined by this class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astGetObjSize( this->header );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static AstFitsChan *GetTableHeader( AstFitsTable *this, int *status ) {
+/*
+*++
+*  Name:
+c     astGetTableHeader
+f     AST_GetTableHeader
+
+*  Purpose:
+*     Get the FITS headers from a FitsTable.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     AstFitsChan *astGetTableHeader( AstFitsTable *this )
+f     RESULT = AST_GETTABLEHEADER( THIS, STATUS )
+
+*  Class Membership:
+*     FitsTable method.
+
+*  Description:
+*     This function returns a pointer to a FitsChan holding copies of
+*     the FITS headers associated with a FitsTable.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsTable.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetTableHeader()
+f     AST_GetTableHeader = INTEGER
+*        A pointer to a deep copy of the FitsChan stored within the
+*        FitsTable.
+
+*  Notes:
+*     - The returned pointer should be annulled using
+c     astAnnul
+f     AST_ANNUL
+*     when it is no longer needed.
+*     - Changing the contents of the returned FitsChan will have no effect
+*     on the FitsTable. To modify the FitsTable, the modified FitsChan must
+*     be stored in the FitsTable using
+c     astPutTableHeader.
+f     AST_PUTTABLEHEADER.
+
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Ensure the fixed value headers are up-to-date in the FitsChan stored
+   in the FitsTable. */
+   UpdateHeader( this, "astGetTableHeader", status );
+
+/* Reset the current card to the first card. */
+   astClearCard( this->header );
+
+/* Return a deep copy of the FitsChan. */
+   return astCopy( this->header );
+}
+
+void astInitFitsTableVtab_(  AstFitsTableVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitFitsTableVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a FitsTable.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fitstable.h"
+*     void astInitFitsTableVtab( AstFitsTableVtab *vtab, const char *name )
+
+*  Class Membership:
+*     FitsTable vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the FitsTable class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstTableVtab *table;          /* Pointer to Table component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitTableVtab( (AstTableVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAFitsTable) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstTableVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->GetTableHeader = GetTableHeader;
+   vtab->PutTableHeader = PutTableHeader;
+   vtab->ColumnNull = ColumnNull;
+   vtab->ColumnSize = ColumnSize;
+   vtab->GetColumnData = GetColumnData;
+   vtab->PutColumnData = PutColumnData;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   table = (AstTableVtab *) vtab;
+
+   parent_equal = object->Equal;
+   object->Equal = Equal;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_addcolumn = table->AddColumn;
+   table->AddColumn = AddColumn;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "FitsTable", "FITS binary table" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static char *MakeKey( const char *column, int irow, char *buf, int len,
+                      int *status ){
+/*
+*  Name:
+*     MakeKey
+
+*  Purpose:
+*     Construct a key for a column cell from a column name and row number.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitstable.h"
+*     char *MakeKey( const char *column, int irow, char *buf, int len,
+*                    int *status )
+
+*  Class Membership:
+*     FitsTable member function
+
+*  Description:
+*     This function constructs a key for a column cell from a column name
+*     and row number. An error is reported if the buffer is too short.
+
+*  Parameters:
+*     column
+*        Pointer to the column name. Trailing white space is ignored.
+*     irow
+*        One-based index of the row.
+*     buf
+*        Pointer to a buffer in which to store the returned key.
+*     len
+*        The length of the buffer.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A copy of "buf".
+
+*/
+
+/* Local Variables: */
+   char *result;
+   char rbuf[ 40 ];
+   int collen;
+   int nc;
+
+/* Initialise. */
+   result = buf;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Format the column number. */
+   nc = sprintf( rbuf, "%d", irow );
+
+/* Get the used length of the column name (i.e. excluding trailing white
+   space). */
+   collen = astChrLen( column );
+
+/* For the total length of the returned string. */
+   nc += collen + 3;
+
+/* If the buffer is large enough, store the returned string. */
+   if( len >= nc ) {
+      sprintf( buf, "%.*s(%s)", collen, column, rbuf );
+   } else {
+      astError( AST__INTER, "MakeKey(FitsTable): Internal buffer is too "
+                "short to hold Table cell name '%.*s(%s)' (internal AST "
+                "programming error).", status, collen, column, rbuf );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     FitsTable member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstFitsTable *this;         /* Pointer to FitsTable structure */
+   int result;             /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the FitsTable structure. */
+   this = (AstFitsTable *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->header, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static void PurgeHeader( AstFitsTable *this, int *status ) {
+/*
+*  Name:
+*     PurgeHeader
+
+*  Purpose:
+*     Remove fixed-value keywords from the table header.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void PurgeHeader( AstFitsTable *this, int *status )
+
+*  Description:
+*     This function ensures that the headers that are determined by the
+*     table contents or by the FITS standard do not exist in the header
+*     of the supplied FitsTable.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsTable.
+*     status
+*        Pointer to inherited status.
+
+*/
+
+/* Local Constants: */
+#define nfixed 14  /* Number of fixed-value keywords to check for */
+
+/* Local Variables: */
+   int ifixed;
+
+/* A list of FITS keywords that have values that are fixed by the FITS
+   standard or by the contents of the Table. */
+   const char *fixed[] = { "XTENSION", "BITPIX", "NAXIS", "NAXIS1",
+                           "NAXIS2", "PCOUNT", "GCOUNT", "TFIELDS",
+                           "TFORM%d", "TTYPE%d", "TNULL%d", "THEAP",
+                           "TDIM%d", "TUNIT%d" };
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Remove headers that have fixed values. */
+   for( ifixed = 0; ifixed < nfixed; ifixed++ ) {
+      astClearCard( this->header );
+      while( astFindFits( this->header, fixed[ ifixed ], NULL, 0 ) ) {
+         astDelFits( this->header );
+      }
+   }
+
+/* Undefine local constants */
+#undef nfixed
+}
+
+static void PutColumnData( AstFitsTable *this, const char *column,
+                           int clen, size_t size, void *coldata, int *status ){
+/*
+*++
+*  Name:
+c     astPutColumnData
+f     AST_PUTCOLUMNDATA
+
+*  Purpose:
+*     Store new data values for all rows of a column.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     void astPutColumnData( AstFitsTable *this, const char *column,
+c                            int clen, size_t size, void *coldata )
+f     CALL AST_PUTCOLUMNDATA( THIS, COLUMN, CLEN, SIZE, COLDATA, STATUS )
+
+*  Class Membership:
+*     FitsTable method.
+
+*  Description:
+c     This function
+f     This routine
+*     copies data values from a supplied buffer into a named column. The
+*     first element in the buffer becomes the first element in the first
+*     row of the column. If the buffer does not completely fill the
+*     column, then any trailing rows are filled with null values.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsTable.
+c     column
+f     COLUMN = CHARACTER * ( * ) (Given)
+*        The character string holding the name of the column. Trailing
+*        spaces are ignored.
+c     clen
+f     CLEN = INTEGER (Given)
+*        If the column holds character strings, then this must be set to
+*        the length of each fixed length string in the supplied array.
+*        This is often determined by the appropriate TFORMn keyword in
+*        the binary table header. The supplied value is ignored if the
+*        column does not hold character data.
+c     size
+f     SIZE = INTEGER (Given)
+*        The size of the
+c        "coldata"
+f        COLDATA
+*        array, in bytes. This should be an integer multiple of the
+*        number of bytes needed to hold the full vector value stored in a
+*        single cell of the column. An error is reported if this is not
+*        the case.
+c     coldata
+f     COLDATA( * ) = BYTE (Given)
+c        A pointer to an
+f        An
+*        area of memory holding the data to copy into the column. The values
+*        should be stored in row order. If the column holds non-scalar values,
+*        the elements of each value should be stored in "Fortran" order. No
+*        data type conversion is performed.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Local Variables: */
+   char key[ AST__MXCOLKEYLEN + 1 ]; /* Current cell key string */
+   char **carray;    /* Pointer to array of null terminated string pointers */
+   int irow;         /* Index of value being copied */
+   int iel;          /* Index of current element */
+   int nel;          /* No. of elements per value */
+   int nrow;         /* No. of values to copy */
+   int type;         /* Data type */
+   size_t nb;        /* No. of bytes for a single element of a value */
+   size_t nbv;       /* No. of bytes per value */
+   void *pin;        /* Pointer to next input array element */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise */
+   nb = 0;
+
+/* Find the number of bytes in the supplied array holding a single element
+   of the value in a column cell. */
+   type = astGetColumnType( this, column );
+   if( type == AST__INTTYPE ) {
+      nb = sizeof( int );
+
+   } else if(  type == AST__DOUBLETYPE ){
+      nb = sizeof( double );
+
+   } else if(  type == AST__STRINGTYPE ){
+      nb = clen*sizeof( char );
+
+   } else if(  type == AST__FLOATTYPE ){
+      nb = sizeof( float );
+
+   } else if(  type == AST__SINTTYPE ){
+      nb = sizeof( short int );
+
+   } else if(  type == AST__BYTETYPE ){
+      nb = sizeof( char );
+
+   } else if( astOK ) {
+      astError( AST__INTER, "astPutColumnData(%s): Unsupported column type "
+                "%d (internal AST programming error).", status,
+                astGetClass( this ), type );
+   }
+
+/* Get the number of elements per value, and the number of bytes (in the
+   supplied array) per value. */
+   nel = astGetColumnLength( this, column );
+   nbv = nb*nel;
+
+/* Initialise a pointer to the next element of the supplied array to read. */
+   pin = coldata;
+
+/* Get the number of rows to copy from the supplied array. */
+   nrow = nbv ? size / nbv : 0;
+
+/* As yet we have no array of null terminated character pointers. */
+   carray = NULL;
+
+/* Report an error if the supplied array does not hold an exact number of
+   column cells. */
+   if( nrow*nbv != size && astOK ) {
+      astError( AST__BADSIZ, "astPutColumnData(%s): The supplied array size "
+                "(%d bytes) is not an exact multiple of the size of one "
+                "column value (%d bytes).", status, astGetClass( this ),
+                (int) size, (int) nbv );
+   }
+
+/* Loop round the rows to be copied. */
+   for( irow = 1; irow <= nrow; irow++ ) {
+
+/* Format the cell name. */
+      (void) MakeKey( column, irow, key, AST__MXCOLKEYLEN + 1,
+                      status );
+
+/* Put the next value into the current cell of the column, using its native
+   data type. Skip floating point values that are entirely NaN. */
+      if( type == AST__INTTYPE ) {
+         astMapPut1I( this, key, nel, pin, NULL );
+
+      } else if(  type == AST__DOUBLETYPE ){
+         for( iel = 0; iel < nel; iel++ ) {
+            if( astISFINITE( ((double *)pin)[ iel ] ) ) {
+               astMapPut1D( this, key, nel, pin, NULL );
+               break;
+            }
+         }
+
+      } else if(  type == AST__FLOATTYPE ){
+         for( iel = 0; iel < nel; iel++ ) {
+            if( astISFINITE( ((double *)pin)[ iel ] ) ) {
+               astMapPut1F( this, key, nel, pin, NULL );
+               break;
+            }
+         }
+
+      } else if(  type == AST__SINTTYPE ){
+         astMapPut1S( this, key, nel, pin, NULL );
+
+      } else if(  type == AST__BYTETYPE ){
+         astMapPut1B( this, key, nel, pin, NULL );
+
+/* If each cell in the column holds an array of strings, we need to
+   convert the fixed length strings in the supplied array into an array
+   of pointers to null terminated strings. */
+      } else if(  type == AST__STRINGTYPE ){
+         carray = astStringArray( pin, nel, clen );
+         astMapPut1C( this, key, nel, (const char ** ) carray, NULL );
+         carray = astFree( carray );
+      }
+
+/* Increment the pointer to the next input value. */
+      pin += nbv;
+   }
+
+/* Remove any remaining cells already present in this column. */
+   nrow = astGetNrow( this );
+   for( ; irow <= nrow; irow++ ) {
+      (void) MakeKey( column, irow, key, AST__MXCOLKEYLEN + 1,
+                      status );
+      astMapRemove( this, key );
+   }
+}
+
+static void PutTableHeader( AstFitsTable *this, AstFitsChan *header,
+                            int *status ) {
+/*
+*++
+*  Name:
+c     astPutTableHeader
+f     AST_PUTTABLEHEADER
+
+*  Purpose:
+*     Store new FITS headers in a FitsTable.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     void astPutTableHeader( AstFitsTable *this, AstFitsChan *header )
+f     CALL AST_PUTTABLEHEADER( THIS, HEADER, STATUS )
+
+*  Class Membership:
+*     FitsTable method.
+
+*  Description:
+c     This function
+f     This routine
+*     stores new FITS headers in the supplied FitsTable. Any existing
+*     headers are first deleted.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FitsTable.
+c     header
+f     HEADER = INTEGER (Given)
+*        Pointer to a FitsChan holding the headers for the FitsTable.
+*        A deep copy of the supplied FitsChan is stored in the FitsTable,
+*        replacing the current FitsChan in the Fitstable. Keywords that
+*        are fixed either by the properties of the Table, or by the FITS
+*        standard, are removed from the copy (see "Notes:" below).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - The attributes of the supplied FitsChan, together with any source
+*     and sink functions associated with the FitsChan, are copied to the
+*     FitsTable.
+*     - Values for the following keywords are generated automatically by
+*     the FitsTable (any values for these keywords in the supplied
+*     FitsChan will be ignored): "XTENSION", "BITPIX", "NAXIS", "NAXIS1",
+*     "NAXIS2", "PCOUNT", "GCOUNT", "TFIELDS", "TFORM%d", "TTYPE%d",
+*     "TNULL%d", "THEAP", "TDIM%d".
+
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Annul the existing FitsChan. */
+   (void) astAnnul( this->header );
+
+/* Store a deep copy of the supplied FitsChan in the FitsTable. */
+   this->header = astCopy( header );
+
+/* Remove headers that have fixed values. */
+   PurgeHeader( this, status );
+}
+
+static void UpdateHeader( AstFitsTable *this, const char *method,
+                          int *status ) {
+/*
+*  Name:
+*     UpdateHeader
+
+*  Purpose:
+*     Ensure FITS headers accurately describe the current table contents.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void UpdateHeader( AstFitsTable *this, const char *method,
+*                        int *status )
+
+*  Description:
+*     This function ensures that the FITS headers that are determined by
+*     the table contents or by the FITS standard are up to date.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsTable.
+*     method
+*        Pointer to a string holding the name of the method to include in
+*        error messages.
+*     status
+*        Pointer to inherited status.
+
+*/
+
+/* Local Variables: */
+   char *dimbuf;
+   char buf[ 20 ];
+   char code;
+   char keyword[ 14 ];
+   const char *unit;
+   const char *name;
+   int *dims;
+   int hasNull;
+   int icol;
+   int idim;
+   int nc;
+   int ncol;
+   int ndim;
+   int nel;
+   int null;
+   int rowsize;
+   int set;
+   int slen;
+   int type;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Remove any existing headers that will have values stored for them by
+   this function. */
+   PurgeHeader( this, status );
+
+/* Store headers that have fixed values regardless of the contents of the
+   table. Rewind the FitsChan first so they go at the start of the header. */
+   astClearCard( this->header );
+   astSetFitsS( this->header, "XTENSION", "BINTABLE", NULL, 0 );
+   astSetFitsI( this->header, "BITPIX", 8, NULL, 0 );
+   astSetFitsI( this->header, "NAXIS", 2, NULL, 0 );
+   astSetFitsI( this->header, "PCOUNT", 0, NULL, 0 );
+   astSetFitsI( this->header, "GCOUNT", 1, NULL, 0 );
+
+/* The number of columns. */
+   ncol = astGetNcolumn( this );
+   astSetFitsI( this->header, "TFIELDS", ncol, NULL, 0 );
+
+/* Add column-specific keywords, one for each column in the Table. */
+   dims = NULL;
+   dimbuf = NULL;
+   rowsize = 0;
+   for( icol = 1; icol <= ncol && astOK; icol++ ){
+
+/* Get the name, type and shape of the current column. */
+      name = astColumnName( this, icol );
+      nel = astGetColumnLength( this, name );
+      type = astGetColumnType( this, name );
+      unit = astGetColumnUnit( this, name );
+      ndim = astGetColumnNdim( this, name );
+      dims = astGrow( dims, ndim, sizeof( int ) );
+      if( astOK ) {
+         astColumnShape( this, name, ndim, &ndim, dims );
+
+/* Get the FITS code that describes the column data type. Also increment
+   the number of bytes (rowsize) used to describe a whole row. */
+         slen = 0;
+         code = ' ';
+         if( type == AST__BYTETYPE ) {
+            code = 'B';
+            rowsize += nel;
+
+         } else if( type == AST__SINTTYPE ) {
+            code = 'I';
+            rowsize += 2*nel;
+
+         } else if( type == AST__INTTYPE ) {
+            code = 'J';
+            rowsize += 4*nel;
+
+         } else if( type == AST__DOUBLETYPE ) {
+            code = 'D';
+            rowsize += 8*nel;
+
+         } else if( type == AST__FLOATTYPE ) {
+            code = 'E';
+            rowsize += 4*nel;
+
+         } else if( type == AST__STRINGTYPE ) {
+            code = 'A';
+
+/* Use the maximum length of the strings in the current column (excluding
+   null) to scale the FITS repeat count. */
+            slen = astGetColumnLenC( this, name );
+            nel *= slen;
+            rowsize += nel;
+
+/* Report an error if the data type is not supported by FITS. */
+         } else if( astOK ) {
+            astError( AST__INTER, "%s(%s): Illegal type %d for column '%s' "
+                      "in a %s (internal AST programming error).", status,
+                      method, astGetClass( this ), type, name,
+                      astGetClass( this ) );
+         }
+
+/* Start the TFORMn keyword value. This is the number of values in each
+   cell, and is not needed if the cell contains only one value. */
+         nc = sprintf( buf, "%d", nel );
+
+/* Add the data type code to complete the TFORMn value, and store it in
+   the FitsChan. */
+         sprintf( buf + nc, "%c", code );
+         sprintf( keyword, "TFORM%d", icol );
+         astSetFitsS( this->header, keyword, buf, NULL, 0 );
+
+/* Column name. */
+         sprintf( keyword, "TTYPE%d", icol );
+         astSetFitsS( this->header, keyword, name, NULL, 0 );
+
+/* Column units. */
+         if( astChrLen( unit ) ) {
+            sprintf( keyword, "TUNIT%d", icol );
+            astSetFitsS( this->header, keyword, unit, NULL, 0 );
+         }
+
+/* Column null value (integer columns only). Only store a TNULLn keyword
+   if the NULL attribute has been set for the column, or if the column
+   contains missing (i.e. null) values. */
+         if( type == AST__BYTETYPE || type == AST__SINTTYPE ||
+             type == AST__INTTYPE ) {
+            null = astColumnNull( this, name, 0, 0, &set, &hasNull );
+            if( set || hasNull ) {
+               sprintf( keyword, "TNULL%d", icol );
+               astSetFitsI( this->header, keyword, null, NULL, 0 );
+            }
+         }
+
+/* Array dimensions (only needed for non-scalars). */
+         if( ndim > 0 ) {
+            dimbuf = astGrow( dimbuf, ndim, 15 );
+            if( astOK ) {
+
+/* For strings, the first dimension is the length of the fixed-length
+   strings that make up the array. */
+               if( type != AST__STRINGTYPE ) {
+                  nc = sprintf( dimbuf, "(%d", dims[ 0 ] );
+               } else {
+                  nc = sprintf( dimbuf, "(%d,%d", slen, dims[ 0 ] );
+               }
+
+/* Append the second and subsequent dimensions to the buffer. */
+               for( idim = 1; idim < ndim; idim++ ) {
+                  nc += sprintf( dimbuf + nc, ",%d", dims[ idim ] );
+               }
+               sprintf( dimbuf + nc, ")" );
+
+/* Store the buffered string as the value for keyword TDIMn in the
+   FitsChan. */
+               sprintf( keyword, "TDIM%d", icol );
+               astSetFitsS( this->header, keyword, dimbuf, NULL, 0 );
+            }
+         }
+      }
+   }
+
+/* Insert the NAXISi keywords into the header, following the NAXIS value
+   (i.e. starting at card 4). */
+   astSetCard( this->header, 4 );
+   astSetFitsI( this->header, "NAXIS1", rowsize, NULL, 0 );
+   astSetFitsI( this->header, "NAXIS2", astGetNrow( this ), NULL, 0 );
+
+/* Free resources. */
+   dims = astFree( dims );
+   dimbuf = astFree( dimbuf );
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for FitsTable objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for FitsTable objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the component
+*     Mappings within the FitsTable.
+*/
+
+/* Local Variables: */
+   AstFitsTable *in;                /* Pointer to input FitsTable */
+   AstFitsTable *out;               /* Pointer to output FitsTable */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output FitsTables. */
+   in = (AstFitsTable *) objin;
+   out = (AstFitsTable *) objout;
+
+/* Make copies of the component Tables and store pointers to them in the
+   output FitsTable structure. */
+   out->header = astCopy( in->header );
+}
+
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for FitsTable objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for FitsTable objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstFitsTable *this;              /* Pointer to FitsTable */
+
+/* Obtain a pointer to the FitsTable structure. */
+   this = (AstFitsTable *) obj;
+
+/* Annul the pointers to the component Tables. */
+   this->header = astAnnul( this->header );
+
+}
+
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for FitsTable objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the FitsTable class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the FitsTable whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFitsTable *this;             /* Pointer to the FitsTable structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FitsTable structure. */
+   this = (AstFitsTable *) this_object;
+
+/* Write out values representing the instance variables for the FitsTable
+   class.  Note, the primitive data in the FitsTable will be written out
+   by the parent Table Dump function. This function deals just with the
+   extra information held in the FitsTable structure. */
+
+/* Write out the FITS header. */
+   astWriteObject( channel, "Header", 1, 0, this->header, "FITS headers" );
+}
+
+
+
+
+
+
+
+
+
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAFitsTable and astCheckFitsTable functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(FitsTable,Table)
+astMAKE_CHECK(FitsTable)
+
+AstFitsTable *astFitsTable_( void *header_void, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astFitsTable
+f     AST_FITSTABLE
+
+*  Purpose:
+*     Create a FitsTable.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "fitstable.h"
+c     AstFitsTable *astFitsTable( AstFitsChan *header, const char *options, ... )
+f     RESULT = AST_FITSTABLE( HEADER, OPTIONS, STATUS )
+
+*  Class Membership:
+*     FitsTable constructor.
+
+*  Description:
+*     This function creates a new FitsTable and optionally initialises
+*     its attributes.
+*
+*     The FitsTable class is a representation of a FITS binary table. It
+*     inherits from the Table class. The parent Table is used to hold the
+*     binary data of the main table, and a FitsChan is used to hold the FITS
+*     header. Note, there is no provision for binary data following the main
+*     table (such data is referred to as a "heap" in the FITS standard).
+*
+*     Note - it is not recommended to use the FitsTable class to store
+*     very large tables.
+
+*  Parameters:
+c     header
+f     HEADER = INTEGER (Given)
+*        Pointer to an optional FitsChan containing headers to be stored
+*        in the FitsTable.
+c        NULL
+f        AST__NULL
+*        may be supplied if the new FitsTable is to be left empty. If
+*        supplied, and if the headers describe columns of a FITS binary
+*        table, then equivalent (empty) columns are added to the FitsTable.
+*        Each column has the same index in the FitsTable that it has in
+*        the supplied header.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new FitsTable. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new FitsTable. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astFitsTable()
+f     AST_FITSTABLE = INTEGER
+*        A pointer to the new FitsTable.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list described above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFitsTable *new;            /* Pointer to new FitsTable */
+   AstFitsChan *header;          /* Pointer to header FitsChan */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate pointers to the header FitsChan provided. */
+   header = header_void ? astCheckFitsChan( header_void ) : NULL;
+
+/* Initialise the FitsTable, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitFitsTable( NULL, sizeof( AstFitsTable ), !class_init,
+                           &class_vtab, "FitsTable", header );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new FitsTable's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new FitsTable. */
+   return new;
+}
+
+AstFitsTable *astFitsTableId_( void *header_void, const char *options, ... ) {
+/*
+*  Name:
+*     astFitsTableId_
+
+*  Purpose:
+*     Create a FitsTable.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fitstable.h"
+*     AstFitsTable *astFitsTableId_( AstFitsChan *header, const char *options, ... )
+
+*  Class Membership:
+*     FitsTable constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astFitsTable constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astFitsTable_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astFitsTable_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astFitsTable_.
+
+*  Returned Value:
+*     The ID value associated with the new FitsTable.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFitsChan *header;          /* Genuine C poitner to header FitsChan */
+   AstFitsTable *new;            /* Pointer to new FitsTable */
+   int *status;                  /* Pointer to inherited status value */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a FitsChan pointer from any ID supplied and validate the
+   pointer to ensure it identifies a valid FitsChan. */
+   header = header_void ? astCheckFitsChan( astMakePointer( header_void ) ) : NULL;
+
+/* Initialise the FitsTable, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitFitsTable( NULL, sizeof( AstFitsTable ), !class_init,
+                           &class_vtab, "FitsTable", header );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new FitsTable's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new FitsTable. */
+   return astMakeId( new );
+}
+
+AstFitsTable *astInitFitsTable_( void *mem, size_t size, int init,
+                                 AstFitsTableVtab *vtab, const char *name,
+                                 AstFitsChan *header, int *status ) {
+/*
+*+
+*  Name:
+*     astInitFitsTable
+
+*  Purpose:
+*     Initialise a FitsTable.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fitstable.h"
+*     AstFitsTable *astInitFitsTable( void *mem, size_t size, int init,
+*                                     AstFitsTableVtab *vtab, const char *name,
+*                                     AstFitsChan *header )
+
+*  Class Membership:
+*     FitsTable initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new FitsTable object. It allocates memory (if necessary) to accommodate
+*     the FitsTable plus any additional data associated with the derived class.
+*     It then initialises a FitsTable structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a FitsTable at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the FitsTable is to be initialised.
+*        This must be of sufficient size to accommodate the FitsTable data
+*        (sizeof(FitsTable)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the FitsTable (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the FitsTable
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the FitsTable's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new FitsTable.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     header
+*        If not NULL, a FitsChan that is used to populate the FitsTable
+*        with headers and columns.
+
+*  Returned Value:
+*     A pointer to the new FitsTable.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFitsTable *new;              /* Pointer to new FitsTable */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitFitsTableVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Initialise a Table structure (the parent class) as the first component
+   within the FitsTable structure, allocating memory if necessary. Specify that
+   the Table should be defined in both the forward and inverse directions. */
+   new = (AstFitsTable *) astInitTable( mem, size, 0, (AstTableVtab *) vtab,
+                                     name );
+   if ( astOK ) {
+
+/* Initialise the FitsTable data. */
+/* ---------------------------- */
+      new->header = astFitsChan( NULL, NULL, " ", status );
+
+/* If a header was supplied, add equivalent columns to the FitsTable, and
+   store the header. */
+      if( header ) {
+         GenerateColumns( new, header, status );
+         PutTableHeader( new, header, status );
+      }
+
+/* If an error occurred, clean up by deleting the new FitsTable. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new FitsTable. */
+   return new;
+}
+
+AstFitsTable *astLoadFitsTable_( void *mem, size_t size, AstFitsTableVtab *vtab,
+                                 const char *name, AstChannel *channel,
+                                 int *status ) {
+/*
+*+
+*  Name:
+*     astLoadFitsTable
+
+*  Purpose:
+*     Load a FitsTable.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fitstable.h"
+*     AstFitsTable *astLoadFitsTable( void *mem, size_t size, AstFitsTableVtab *vtab,
+*                                     const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     FitsTable loader.
+
+*  Description:
+*     This function is provided to load a new FitsTable using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     FitsTable structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a FitsTable at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the FitsTable is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        FitsTable data (sizeof(FitsTable)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the FitsTable (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the FitsTable structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstFitsTable) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new FitsTable. If this is NULL, a pointer
+*        to the (static) virtual function table for the FitsTable class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "FitsTable" is used instead.
+
+*  Returned Value:
+*     A pointer to the new FitsTable.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFitsTable *new;                /* Pointer to the new FitsTable */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this FitsTable. In this case the
+   FitsTable belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstFitsTable );
+      vtab = &class_vtab;
+      name = "FitsTable";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitFitsTableVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built FitsTable. */
+   new = astLoadTable( mem, size, (AstTableVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "FitsTable" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* FitsChan holding table headers. */
+      new->header = astReadObject( channel, "header", NULL );
+
+/* If an error occurred, clean up by deleting the new FitsTable. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new FitsTable pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+AstFitsChan *astGetTableHeader_( AstFitsTable *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,FitsTable,GetTableHeader))(this,status);
+}
+
+void astPutTableHeader_( AstFitsTable *this, AstFitsChan *header, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsTable,PutTableHeader))(this,header,status);
+}
+
+int astColumnNull_( AstFitsTable *this, const char *column, int set,
+                    int newval, int *wasset, int *hasnull, int *status ){
+   *wasset = 0;
+   if( hasnull ) *hasnull = 0;
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,FitsTable,ColumnNull))(this,column,set,newval,wasset,hasnull,status);
+}
+
+size_t astColumnSize_( AstFitsTable *this, const char *column, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,FitsTable,ColumnSize))(this,column,status);
+}
+
+void astGetColumnData_( AstFitsTable *this, const char *column, float fnull,
+                        double dnull, size_t mxsize, void *coldata, int *nelem,
+                        int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsTable,GetColumnData))(this,column,fnull,dnull,mxsize,
+                                               coldata,nelem,status);
+}
+
+void astPutColumnData_( AstFitsTable *this, const char *column, int clen,
+                        size_t size, void *coldata, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,FitsTable,PutColumnData))(this,column,clen,size,coldata,status);
+}
+
+
+
+
+
+
+
+
diff --git a/ast/fitstable.h b/ast/fitstable.h
new file mode 100644
index 0000000..a2633b6
--- /dev/null
+++ b/ast/fitstable.h
@@ -0,0 +1,235 @@
+#if !defined( FITSTABLE_INCLUDED )  /* Include this file only once */
+#define FITSTABLE_INCLUDED
+/*
+*+
+*  Name:
+*     fitstable.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the FitsTable class.
+
+*  Invocation:
+*     #include "fitstable.h"
+
+*  Description:
+*     This include file defines the interface to the FitsTable class and
+*     provides the type definitions, function prototypes and macros,
+*     etc. needed to use this class.
+
+*  Inheritance:
+*     The FitsTable class inherits from the Table class.
+
+*  Copyright:
+*     Copyright (C) 2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     25-NOV-2010 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "table.h"               /* Parent class */
+#include "fitschan.h"
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* FitsTable structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstFitsTable {
+
+/* Attributes inherited from the parent class. */
+   AstTable table;              /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstFitsChan *header;         /* FitsChan containing table headers */
+} AstFitsTable;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstFitsTableVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstTableVtab table_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstFitsChan *(* GetTableHeader)( AstFitsTable *, int * );
+   void (* PutTableHeader)( AstFitsTable *, AstFitsChan *, int * );
+   int (* ColumnNull)( AstFitsTable *, const char *, int, int, int *, int *, int * );
+   size_t (* ColumnSize)( AstFitsTable *, const char *, int * );
+   void (* GetColumnData)( AstFitsTable *, const char *, float, double, size_t, void *, int *, int * );
+   void (* PutColumnData)( AstFitsTable *, const char *, int, size_t, void *, int * );
+
+} AstFitsTableVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstFitsTableGlobals {
+   AstFitsTableVtab Class_Vtab;
+   int Class_Init;
+} AstFitsTableGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitFitsTableGlobals_( AstFitsTableGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(FitsTable)           /* Check class membership */
+astPROTO_ISA(FitsTable)             /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstFitsTable *astFitsTable_( void *, const char *, int *, ...);
+#else
+AstFitsTable *astFitsTableId_( void *, const char *, ... )__attribute__((format(printf,2,3)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstFitsTable *astInitFitsTable_( void *, size_t, int, AstFitsTableVtab *,
+                                 const char *, AstFitsChan *, int * );
+
+/* Vtab initialiser. */
+void astInitFitsTableVtab_( AstFitsTableVtab *, const char *, int * );
+
+/* Loader. */
+AstFitsTable *astLoadFitsTable_( void *, size_t, AstFitsTableVtab *,
+                                 const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+AstFitsChan *astGetTableHeader_( AstFitsTable *, int * );
+void astPutTableHeader_( AstFitsTable *, AstFitsChan *, int * );
+int astColumnNull_( AstFitsTable *, const char *, int, int, int *, int *, int * );
+size_t astColumnSize_( AstFitsTable *, const char *, int * );
+void astGetColumnData_( AstFitsTable *, const char *, float, double, size_t, void *, int *, int * );
+void astPutColumnData_( AstFitsTable *, const char *, int, size_t, void *, int * );
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckFitsTable(this) astINVOKE_CHECK(FitsTable,this,0)
+#define astVerifyFitsTable(this) astINVOKE_CHECK(FitsTable,this,1)
+
+/* Test class membership. */
+#define astIsAFitsTable(this) astINVOKE_ISA(FitsTable,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astFitsTable astINVOKE(F,astFitsTable_)
+#else
+#define astFitsTable astINVOKE(F,astFitsTableId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitFitsTable(mem,size,init,vtab,name,header) \
+astINVOKE(O,astInitFitsTable_(mem,size,init,vtab,name,header,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitFitsTableVtab(vtab,name) astINVOKE(V,astInitFitsTableVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadFitsTable(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadFitsTable_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckFitsTable to validate FitsTable pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#define astGetTableHeader(this) \
+astINVOKE(O,astGetTableHeader_(astCheckFitsTable(this),STATUS_PTR))
+#define astPutTableHeader(this,header) \
+astINVOKE(V,astPutTableHeader_(astCheckFitsTable(this),astCheckFitsChan(header),STATUS_PTR))
+#define astColumnNull(this,column,set,newval,wasset,hasnull) \
+astINVOKE(V,astColumnNull_(astCheckFitsTable(this),column,set,newval,wasset,hasnull,STATUS_PTR))
+#define astColumnSize(this,column) \
+astINVOKE(V,astColumnSize_(astCheckFitsTable(this),column,STATUS_PTR))
+#define astGetColumnData(this,column,fnull,dnull,mxsize,coldata,nelem) \
+astINVOKE(V,astGetColumnData_(astCheckFitsTable(this),column,fnull,dnull,mxsize,coldata,nelem,STATUS_PTR))
+#define astPutColumnData(this,column,clen,size,coldata) \
+astINVOKE(V,astPutColumnData_(astCheckFitsTable(this),column,clen,size,coldata,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#endif
+
+#endif
diff --git a/ast/fkeymap.c b/ast/fkeymap.c
new file mode 100644
index 0000000..d09ec1a
--- /dev/null
+++ b/ast/fkeymap.c
@@ -0,0 +1,1400 @@
+/*
+*+
+*  Name:
+*     fkeymap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST KeyMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the KeyMap class.
+
+*  Routines Defined:
+*     AST_ISAKEYMAP
+*     AST_KEYMAP
+*     AST_MAPCOPY
+*     AST_MAPPUT0<X>
+*     AST_MAPPUT1<X>
+*     AST_MAPPUTU
+*     AST_MAPGET0<X>
+*     AST_MAPGET1<X>
+*     AST_MAPGETELEM<X>
+*     AST_MAPPUTELEM<X>
+*     AST_MAPREMOVE
+*     AST_MAPRENAME
+*     AST_MAPSIZE
+*     AST_MAPLENGTH
+*     AST_MAPLENC
+*     AST_MAPTYPE
+*     AST_MAPKEY
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     13-NOV-2004 (DSB):
+*        Original version.
+*     5-JUN-2006 (DSB):
+*        Added support for single precision entries.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "keymap.h"                 /* C interface to the KeyMap class */
+
+F77_LOGICAL_FUNCTION(ast_isakeymap)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAKEYMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAKeyMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_keymap)(    CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_KEYMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astKeyMap( "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_mapput0a)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              INTEGER(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT0A", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut0A( astI2P( *THIS ), key, astI2P( *VALUE ), comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapput0c)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              CHARACTER(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(VALUE)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_CHARACTER(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *value, *key;
+
+   astAt( "AST_MAPPUT0C", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      value = astString( VALUE, VALUE_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut0C( astI2P( *THIS ), key, value, comment );
+      astFree( key );
+      astFree( value );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapput0i)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              INTEGER(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT0I", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut0I( astI2P( *THIS ), key, *VALUE, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapput0s)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              WORD(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_WORD(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT0W", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut0S( astI2P( *THIS ), key, *VALUE, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapput0b)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              UBYTE(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_UBYTE(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT0B", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut0B( astI2P( *THIS ), key, *VALUE, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapput0d)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              DOUBLE(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_DOUBLE(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT0D", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut0D( astI2P( *THIS ), key, *VALUE, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapput0r)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              REAL(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_REAL(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT0R", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut0F( astI2P( *THIS ), key, *VALUE, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapput1a)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              INTEGER(SIZE),
+                              INTEGER_ARRAY(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(SIZE)
+   GENPTR_INTEGER_ARRAY(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+   AstObject **values;
+   int i;
+
+   astAt( "AST_MAPPUT1A", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+
+      values = astMalloc( sizeof( AstObject * )*(size_t)( *SIZE ));
+      if( astOK ) {
+         for( i = 0; i < *SIZE; i++ ) {
+            values[ i ] = astI2P( VALUE[ i ] );
+         }
+      }
+
+      astMapPut1A( astI2P( *THIS ), key, *SIZE, values, comment );
+      astFree( values );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapput1c)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              INTEGER(SIZE),
+                              CHARACTER_ARRAY(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(VALUE)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(SIZE)
+   GENPTR_CHARACTER_ARRAY(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+   const char **values;
+   int i;
+
+   astAt( "AST_MAPPUT1C", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+
+      values = astMalloc( sizeof( const char * )*(size_t)( *SIZE ));
+      if( astOK ) {
+         for( i = 0; i < *SIZE; i++ ) {
+            values[ i ] = astString( VALUE + i*VALUE_length, VALUE_length );
+         }
+      }
+
+      astMapPut1C( astI2P( *THIS ), key, *SIZE, values, comment );
+
+      if( astOK ) {
+         for( i = 0; i < *SIZE; i++ ) astFree( (void *) values[ i ] );
+      }
+      astFree( (void *) values );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+
+
+F77_SUBROUTINE(ast_mapput1i)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              INTEGER(SIZE),
+                              INTEGER_ARRAY(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(SIZE)
+   GENPTR_INTEGER_ARRAY(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT1I", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut1I( astI2P( *THIS ), key, *SIZE, VALUE, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+
+F77_SUBROUTINE(ast_mapput1s)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              INTEGER(SIZE),
+                              WORD_ARRAY(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(SIZE)
+   GENPTR_WORD_ARRAY(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT1W", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut1S( astI2P( *THIS ), key, *SIZE, VALUE, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapput1b)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              INTEGER(SIZE),
+                              UBYTE_ARRAY(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(SIZE)
+   GENPTR_UBYTE_ARRAY(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT1B", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut1B( astI2P( *THIS ), key, *SIZE, VALUE, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+
+F77_SUBROUTINE(ast_mapput1d)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              INTEGER(SIZE),
+                              DOUBLE_ARRAY(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(SIZE)
+   GENPTR_DOUBLE_ARRAY(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT1D", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut1D( astI2P( *THIS ), key, *SIZE, VALUE, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapput1r)( INTEGER(THIS),
+                              CHARACTER(KEY),
+                              INTEGER(SIZE),
+                              REAL_ARRAY(VALUE),
+                              CHARACTER(COMMENT),
+                              INTEGER(STATUS)
+                              TRAIL(KEY)
+                              TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(SIZE)
+   GENPTR_REAL_ARRAY(VALUE)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUT1R", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPut1F( astI2P( *THIS ), key, *SIZE, VALUE, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_SUBROUTINE(ast_mapputu)( INTEGER(THIS),
+                             CHARACTER(KEY),
+                             CHARACTER(COMMENT),
+                             INTEGER(STATUS)
+                             TRAIL(KEY)
+                             TRAIL(COMMENT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_CHARACTER(COMMENT)
+   char *comment, *key;
+
+   astAt( "AST_MAPPUTU", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      comment = astString( COMMENT, COMMENT_length );
+      astMapPutU( astI2P( *THIS ), key, comment );
+      astFree( key );
+      astFree( comment );
+   )
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget0i)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    INTEGER(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGET0I", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet0I( astI2P( *THIS ), key, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget0s)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    WORD(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_WORD(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGET0W", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet0S( astI2P( *THIS ), key, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget0b)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    UBYTE(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_UBYTE(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGET0W", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet0B( astI2P( *THIS ), key, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget0d)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    DOUBLE(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_DOUBLE(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGET0D", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet0D( astI2P( *THIS ), key, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget0r)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    REAL(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_REAL(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGET0R", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet0F( astI2P( *THIS ), key, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget0c)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    CHARACTER(VALUE),
+                                    INTEGER(L),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY)
+                                    TRAIL(VALUE) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_CHARACTER(VALUE)
+   GENPTR_INTEGER(L)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+   const char *value;
+   int i;
+
+   astAt( "AST_MAPGET0C", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      value = NULL;
+      RESULT = astMapGet0C( astI2P( *THIS ), key, &value ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+      i = 0;
+      if( value ) {
+         for( ; value[ i ] && ( i < VALUE_length ); i++ ) {
+            VALUE[ i ] = value[ i ];
+         }
+         *L = i;
+      } else {
+         *L = 0;
+      }
+
+      if( VALUE ) {
+         for( ; i < VALUE_length; i++ ) {
+            VALUE[ i ] = ' ';
+         }
+      }
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget0a)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    INTEGER(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+   AstObject *value;
+
+   astAt( "AST_MAPGET0A", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet0A( astI2P( *THIS ), key, &value ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+      *VALUE = astP2I( value );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget1i)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    INTEGER(MXVAL),
+                                    INTEGER(NVAL),
+                                    INTEGER_ARRAY(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(MXVAL)
+   GENPTR_INTEGER(NVAL)
+   GENPTR_INTEGER_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGET1I", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet1I( astI2P( *THIS ), key, *MXVAL, NVAL, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+
+F77_LOGICAL_FUNCTION(ast_mapget1d)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    INTEGER(MXVAL),
+                                    INTEGER(NVAL),
+                                    DOUBLE_ARRAY(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(MXVAL)
+   GENPTR_INTEGER(NVAL)
+   GENPTR_DOUBLE_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGET1D", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet1D( astI2P( *THIS ), key, *MXVAL, NVAL, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget1s)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    INTEGER(MXVAL),
+                                    INTEGER(NVAL),
+                                    WORD_ARRAY(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(MXVAL)
+   GENPTR_INTEGER(NVAL)
+   GENPTR_WORD_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGET1W", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet1S( astI2P( *THIS ), key, *MXVAL, NVAL, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget1b)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    INTEGER(MXVAL),
+                                    INTEGER(NVAL),
+                                    UBYTE_ARRAY(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(MXVAL)
+   GENPTR_INTEGER(NVAL)
+   GENPTR_UBYTE_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGET1B", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet1B( astI2P( *THIS ), key, *MXVAL, NVAL, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapget1r)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    INTEGER(MXVAL),
+                                    INTEGER(NVAL),
+                                    REAL_ARRAY(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(MXVAL)
+   GENPTR_INTEGER(NVAL)
+   GENPTR_REAL_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGET1R", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet1F( astI2P( *THIS ), key, *MXVAL, NVAL, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+
+F77_LOGICAL_FUNCTION(ast_mapget1a)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    INTEGER(MXVAL),
+                                    INTEGER(NVAL),
+                                    INTEGER_ARRAY(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(MXVAL)
+   GENPTR_INTEGER(NVAL)
+   GENPTR_INTEGER_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+   AstObject **values;
+   int i;
+
+   astAt( "AST_MAPGET1A", NULL, 0 );
+   astWatchSTATUS(
+      values = astMalloc( sizeof( AstObject *)*(size_t) *MXVAL );
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet1A( astI2P( *THIS ), key, *MXVAL, NVAL, values ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+      if( astOK ) {
+         for( i = 0; i < *NVAL; i++ ) VALUE[ i ] = astP2I( values[ i ] );
+      }
+      astFree( values );
+
+   )
+   return RESULT;
+}
+
+
+F77_LOGICAL_FUNCTION(ast_mapget1c)( INTEGER(THIS),
+                                    CHARACTER(KEY),
+                                    INTEGER(MXVAL),
+                                    INTEGER(NVAL),
+                                    CHARACTER_ARRAY(VALUE),
+                                    INTEGER(STATUS)
+                                    TRAIL(KEY)
+                                    TRAIL(VALUE) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(MXVAL)
+   GENPTR_INTEGER(NVAL)
+   GENPTR_CHARACTER_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+   char *values, *c, *d;
+   int i, j, term;
+
+   astAt( "AST_MAPGET1C", NULL, 0 );
+   astWatchSTATUS(
+      values = astMalloc( sizeof( char )*(size_t) (*MXVAL)*( VALUE_length + 1 ) );
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGet1C( astI2P( *THIS ), key, VALUE_length + 1, *MXVAL,
+                            NVAL, values ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+
+/* Loop round each string value returned in the array */
+      if( astOK ) {
+         c = values;
+         d = VALUE;
+         for( i = 0; i < *NVAL; i++ ) {
+
+/* Loop round each of character in the "i"th element of the returned
+   array. Copy characters from the work array until a terminating null is
+   found. Replace this null by a space and replace all subsequent
+   characters by spaces up to the end of the returned array element. */
+            term = 0;
+            for( j = 0; j < VALUE_length; j++, d++, c++ ) {
+               if( term ) {
+                  *d = ' ';
+               } else if( (*d = *c) == 0 ) {
+                  *d = ' ';
+                  term = 1;
+               }
+            }
+
+/* Skip over the extra character at the end of each element in the work
+   array. */
+            c++;
+         }
+      }
+      astFree( values );
+   )
+   return RESULT;
+}
+
+
+F77_SUBROUTINE(ast_mapremove)( INTEGER(THIS),
+                               CHARACTER(KEY),
+                               INTEGER(STATUS)
+                               TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   char *key;
+
+   astAt( "AST_MAPREMOVE", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      astMapRemove( astI2P( *THIS ), key );
+      astFree( key );
+   )
+}
+
+F77_SUBROUTINE(ast_maprename)( INTEGER(THIS),
+                               CHARACTER(OLDKEY),
+                               CHARACTER(NEWKEY),
+                               INTEGER(STATUS)
+                               TRAIL(OLDKEY)
+                               TRAIL(NEWKEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(OLDKEY)
+   GENPTR_CHARACTER(NEWKEY)
+   char *oldkey, *newkey;
+
+   astAt( "AST_MAPRENAME", NULL, 0 );
+   astWatchSTATUS(
+      oldkey = astString( OLDKEY, OLDKEY_length );
+      newkey = astString( NEWKEY, NEWKEY_length );
+      astMapRename( astI2P( *THIS ), oldkey, newkey );
+      astFree( oldkey );
+      astFree( newkey );
+   )
+}
+
+F77_SUBROUTINE(ast_mapcopy)( INTEGER(THIS),
+                             INTEGER(THAT),
+                             INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(THAT)
+
+   astAt( "AST_MAPCOPY", NULL, 0 );
+   astWatchSTATUS(
+      astMapCopy( astI2P( *THIS ), astI2P( *THAT ) );
+   )
+}
+
+F77_INTEGER_FUNCTION(ast_mapsize)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_MAPSIZE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astMapSize( astI2P( *THIS ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_maplength)( INTEGER(THIS),
+                                     CHARACTER(KEY),
+                                     INTEGER(STATUS)
+                                     TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   F77_INTEGER_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPLENGTH", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapLength( astI2P( *THIS ), key );
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_maplenc)( INTEGER(THIS),
+                                   CHARACTER(KEY),
+                                   INTEGER(STATUS)
+                                   TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   F77_INTEGER_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPLENGTH", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapLenC( astI2P( *THIS ), key );
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_maptype)( INTEGER(THIS),
+                                   CHARACTER(KEY),
+                                   INTEGER(STATUS)
+                                   TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   F77_INTEGER_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPTYPE", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapType( astI2P( *THIS ), key );
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_maphaskey)( INTEGER(THIS),
+                                     CHARACTER(KEY),
+                                     INTEGER(STATUS)
+                                     TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPHASKEY", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapHasKey( astI2P( *THIS ), key ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapdefined)( INTEGER(THIS),
+                                      CHARACTER(KEY),
+                                      INTEGER(STATUS)
+                                      TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPDEFINED", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapDefined( astI2P( *THIS ), key ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+/* NO_CHAR_FUNCTION indicates that the f77.h method of returning a
+   character result doesn't work, so add an extra argument instead and
+   wrap this function up in a normal FORTRAN 77 function (in the file
+   keymap.f). */
+#if NO_CHAR_FUNCTION
+F77_SUBROUTINE(ast_mapkey_a)( CHARACTER(RESULT),
+#else
+F77_SUBROUTINE(ast_mapkey)( CHARACTER_RETURN_VALUE(RESULT),
+#endif
+                          INTEGER(THIS),
+                          INTEGER(INDEX),
+#if NO_CHAR_FUNCTION
+                          INTEGER(STATUS)
+                          TRAIL(RESULT) ) {
+#else
+                          INTEGER(STATUS) ) {
+#endif
+   GENPTR_CHARACTER(RESULT)
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(INDEX)
+   const char *result;
+   int i;
+
+   astAt( "AST_MAPKEY", NULL, 0 );
+   astWatchSTATUS(
+      result = astMapKey( astI2P( *THIS ), *INDEX - 1 );
+      i = 0;
+      if ( astOK ) {             /* Copy result */
+         for ( ; result[ i ] && i < RESULT_length; i++ ) {
+            RESULT[ i ] = result[ i ];
+         }
+      }
+      while ( i < RESULT_length ) RESULT[ i++ ] = ' '; /* Pad with blanks */
+   )
+}
+
+
+F77_LOGICAL_FUNCTION(ast_mapgetelemi)( INTEGER(THIS),
+                                       CHARACTER(KEY),
+                                       INTEGER(ELEM),
+                                       INTEGER_ARRAY(VALUE),
+                                       INTEGER(STATUS)
+                                       TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_INTEGER_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGETELEMI", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGetElemI( astI2P( *THIS ), key, *ELEM - 1, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+
+F77_LOGICAL_FUNCTION(ast_mapgetelemd)( INTEGER(THIS),
+                                       CHARACTER(KEY),
+                                       INTEGER(ELEM),
+                                       DOUBLE_ARRAY(VALUE),
+                                       INTEGER(STATUS)
+                                       TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_DOUBLE_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGETELEMD", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGetElemD( astI2P( *THIS ), key, *ELEM - 1, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapgetelems)( INTEGER(THIS),
+                                       CHARACTER(KEY),
+                                       INTEGER(ELEM),
+                                       WORD_ARRAY(VALUE),
+                                       INTEGER(STATUS)
+                                       TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_WORD_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGETELEMW", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGetElemS( astI2P( *THIS ), key, *ELEM - 1, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapgetelemb)( INTEGER(THIS),
+                                       CHARACTER(KEY),
+                                       INTEGER(ELEM),
+                                       UBYTE_ARRAY(VALUE),
+                                       INTEGER(STATUS)
+                                       TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_UBYTE_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGETELEMB", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGetElemB( astI2P( *THIS ), key, *ELEM - 1, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_mapgetelemr)( INTEGER(THIS),
+                                       CHARACTER(KEY),
+                                       INTEGER(ELEM),
+                                       REAL_ARRAY(VALUE),
+                                       INTEGER(STATUS)
+                                       TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_REAL_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+
+   astAt( "AST_MAPGETELEMR", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGetElemF( astI2P( *THIS ), key, *ELEM - 1, VALUE ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+   )
+   return RESULT;
+}
+
+
+F77_LOGICAL_FUNCTION(ast_mapgetelema)( INTEGER(THIS),
+                                       CHARACTER(KEY),
+                                       INTEGER(ELEM),
+                                       INTEGER_ARRAY(VALUE),
+                                       INTEGER(STATUS)
+                                       TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_INTEGER_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+   AstObject *ptr;
+
+   astAt( "AST_MAPGETELEMA", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGetElemA( astI2P( *THIS ), key, *ELEM - 1, &ptr ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+      if( astOK ) *VALUE = astP2I( ptr );
+   )
+   return RESULT;
+}
+
+
+F77_LOGICAL_FUNCTION(ast_mapgetelemc)( INTEGER(THIS),
+                                       CHARACTER(KEY),
+                                       INTEGER(ELEM),
+                                       CHARACTER_ARRAY(VALUE),
+                                       INTEGER(STATUS)
+                                       TRAIL(KEY)
+                                       TRAIL(VALUE) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_CHARACTER_ARRAY(VALUE)
+   F77_LOGICAL_TYPE(RESULT);
+   char *key;
+   char *values, *c, *d;
+   int j;
+
+   astAt( "AST_MAPGETELEMC", NULL, 0 );
+   astWatchSTATUS(
+      values = astMalloc( sizeof( char )*(size_t) ( VALUE_length + 1 ) );
+      key = astString( KEY, KEY_length );
+      RESULT = astMapGetElemC( astI2P( *THIS ), key, VALUE_length + 1, *ELEM - 1,
+                               values ) ? F77_TRUE : F77_FALSE;
+      astFree( key );
+
+/* Copy characters from the work array until a terminating null is
+   found. Replace this null by a space and replace all subsequent
+   characters by spaces up to the end of the returned array element. */
+      if( astOK ) {
+         c = values;
+         d = VALUE;
+
+         for( j = 0; j < VALUE_length; j++, d++, c++ ) {
+            if( (*d = *c) == 0 ) {
+               *d = ' ';
+               break;
+            }
+         }
+
+         for( ; j < VALUE_length; j++, d++ ) *d = ' ';
+
+      }
+      astFree( values );
+   )
+   return RESULT;
+}
+
+
+F77_SUBROUTINE(ast_mapputelemi)( INTEGER(THIS),
+                                 CHARACTER(KEY),
+                                 INTEGER(ELEM),
+                                 INTEGER(VALUE),
+                                 INTEGER(STATUS)
+                                 TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_INTEGER(VALUE)
+   char *key;
+
+   astAt( "AST_MAPPUTELEMI", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      astMapPutElemI( astI2P( *THIS ), key, *ELEM - 1, *VALUE );
+      astFree( key );
+   )
+}
+
+
+F77_SUBROUTINE(ast_mapputelems)( INTEGER(THIS),
+                                 CHARACTER(KEY),
+                                 INTEGER(ELEM),
+                                 WORD(VALUE),
+                                 INTEGER(STATUS)
+                                 TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_WORD(VALUE)
+   char *key;
+
+   astAt( "AST_MAPPUTELEMW", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      astMapPutElemS( astI2P( *THIS ), key, *ELEM - 1, *VALUE );
+      astFree( key );
+   )
+}
+
+F77_SUBROUTINE(ast_mapputelemb)( INTEGER(THIS),
+                                 CHARACTER(KEY),
+                                 INTEGER(ELEM),
+                                 UBYTE(VALUE),
+                                 INTEGER(STATUS)
+                                 TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_UBYTE(VALUE)
+   char *key;
+
+   astAt( "AST_MAPPUTELEMB", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      astMapPutElemB( astI2P( *THIS ), key, *ELEM - 1, *VALUE );
+      astFree( key );
+   )
+}
+
+F77_SUBROUTINE(ast_mapputelemd)( INTEGER(THIS),
+                                 CHARACTER(KEY),
+                                 INTEGER(ELEM),
+                                 DOUBLE(VALUE),
+                                 INTEGER(STATUS)
+                                 TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_DOUBLE(VALUE)
+   char *key;
+
+   astAt( "AST_MAPPUTELEMD", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      astMapPutElemD( astI2P( *THIS ), key, *ELEM - 1, *VALUE );
+      astFree( key );
+   )
+}
+
+F77_SUBROUTINE(ast_mapputelemr)( INTEGER(THIS),
+                                 CHARACTER(KEY),
+                                 INTEGER(ELEM),
+                                 REAL(VALUE),
+                                 INTEGER(STATUS)
+                                 TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_REAL(VALUE)
+   char *key;
+
+   astAt( "AST_MAPPUTELEMR", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      astMapPutElemF( astI2P( *THIS ), key, *ELEM - 1, *VALUE );
+      astFree( key );
+   )
+}
+
+
+F77_SUBROUTINE(ast_mapputelema)( INTEGER(THIS),
+                                 CHARACTER(KEY),
+                                 INTEGER(ELEM),
+                                 INTEGER(VALUE),
+                                 INTEGER(STATUS)
+                                 TRAIL(KEY) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_INTEGER(VALUE)
+   char *key;
+
+   astAt( "AST_MAPPUTELEMA", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      astMapPutElemA( astI2P( *THIS ), key, *ELEM - 1, astI2P( *VALUE ) );
+      astFree( key );
+   )
+}
+
+
+F77_SUBROUTINE(ast_mapputelemc)( INTEGER(THIS),
+                                 CHARACTER(KEY),
+                                 INTEGER(ELEM),
+                                 CHARACTER(VALUE),
+                                 INTEGER(STATUS)
+                                 TRAIL(KEY)
+                                 TRAIL(VALUE) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(KEY)
+   GENPTR_INTEGER(ELEM)
+   GENPTR_CHARACTER(VALUE)
+   char *key;
+   char *value;
+
+   astAt( "AST_MAPPUTELEMC", NULL, 0 );
+   astWatchSTATUS(
+      key = astString( KEY, KEY_length );
+      value = astString( VALUE, VALUE_length );
+      astMapPutElemC( astI2P( *THIS ), key, *ELEM - 1, value );
+      astFree( key );
+      astFree( value );
+   )
+}
+
diff --git a/ast/flutmap.c b/ast/flutmap.c
new file mode 100644
index 0000000..4ff5f0a
--- /dev/null
+++ b/ast/flutmap.c
@@ -0,0 +1,107 @@
+/*
+*+
+*  Name:
+*     flutmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST LutMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the LutMap class.
+
+*  Routines Defined:
+*     AST_ISALUTMAP
+*     AST_LUTMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     8-JUL-1997 (RFWS):
+*        Original version.
+*-
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "lutmap.h"              /* C interface to the LutMap class */
+
+F77_LOGICAL_FUNCTION(ast_isalutmap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISALUTMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsALutMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_lutmap)( INTEGER(NLUT),
+                                  DOUBLE_ARRAY(LUT),
+                                  DOUBLE(START),
+                                  DOUBLE(INC),
+                                  CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NLUT)
+   GENPTR_DOUBLE_ARRAY(LUT)
+   GENPTR_DOUBLE(START)
+   GENPTR_DOUBLE(INC)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+
+   astAt( "AST_LUTMAP", NULL, 0 );
+   astWatchSTATUS(
+      char *options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astLutMap( *NLUT, LUT, *START, *INC, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fluxframe.c b/ast/fluxframe.c
new file mode 100644
index 0000000..ab8a954
--- /dev/null
+++ b/ast/fluxframe.c
@@ -0,0 +1,4490 @@
+/*
+*class++
+*  Name:
+*     FluxFrame
+
+*  Purpose:
+*     Measured flux description.
+
+*  Constructor Function:
+c     astFluxFrame
+f     AST_FLUXFRAME
+
+*  Description:
+*     A FluxFrame is a specialised form of one-dimensional Frame which
+*     represents various systems used to represent the signal level in an
+*     observation. The particular coordinate system to be used is specified
+*     by setting the FluxFrame's System attribute qualified, as necessary, by
+*     other attributes such as the units, etc (see the description of the
+*     System attribute for details).
+*
+*     All flux values are assumed to be measured at the same frequency or
+*     wavelength (as given by the SpecVal attribute). Thus this class is
+*     more appropriate for use with images rather than spectra.
+
+*  Inheritance:
+*     The FluxFrame class inherits from the Frame class.
+
+*  Attributes:
+*     In addition to those attributes common to all Frames, every
+*     FluxFrame also has the following attributes:
+*
+*     - SpecVal: The spectral position at which the flux values are measured.
+
+*  Functions:
+c     The FluxFrame class does not define any new functions beyond those
+f     The FluxFrame class does not define any new routines beyond those
+*     which are applicable to all Frames.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     6-DEC-2004 (DSB):
+*        Original version.
+*     14-DEC-2004 (DSB):
+*        Added AST__SBRIGHT and AST__SBRIGHTW systems.
+*     7-DEC-2005 (DSB):
+*        Free memory allocated by calls to astReadString.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     31-JAN-2007 (DSB):
+*        Modified so that a FluxFrame can be used as a template to find a
+*        FluxFrame contained within a CmpFrame. This involves changes in
+*        Match and the removal of the local versions of SetMaxAxes and
+*        SetMinAxes.
+*     3-SEP-2007 (DSB):
+*        In SubFrame, since AlignSystem is extended by the FluxFrame class
+*        it needs to be cleared before invoking the parent SubFrame
+*        method in cases where the result Frame is not a FluxFrame.
+*     2-OCT-2007 (DSB):
+*        In Overlay, clear AlignSystem as well as System before calling
+*        the parent overlay method.
+*     29-APR-2011 (DSB):
+*        Prevent astFindFrame from matching a subclass template against a
+*        superclass target.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS FluxFrame
+
+/* Define the first and last acceptable System values. */
+#define FIRST_SYSTEM AST__FLUXDEN
+#define LAST_SYSTEM AST__SBRIGHTW
+
+/* Define other numerical constants for use in this module. */
+#define GETATTRIB_BUFF_LEN 50
+#define GETLABEL_BUFF_LEN 200
+#define GETSYMBOL_BUFF_LEN 20
+#define GETTITLE_BUFF_LEN 200
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "unit.h"                /* Units management facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "frame.h"               /* Parent Frame class */
+#include "fluxframe.h"           /* Interface definition for this class */
+#include "mapping.h"             /* Coordinate Mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "zoommap.h"             /* Scaling Mappings */
+#include "specframe.h"           /* Spectral Frames */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <math.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are used or extended by this
+   class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstSystemType (* parent_getalignsystem)( AstFrame *, int * );
+static AstSystemType (* parent_getsystem)( AstFrame *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static const char *(* parent_getdomain)( AstFrame *, int * );
+static const char *(* parent_getlabel)( AstFrame *, int, int * );
+static const char *(* parent_getsymbol)( AstFrame *, int, int * );
+static const char *(* parent_gettitle)( AstFrame *, int * );
+static const char *(* parent_getunit)( AstFrame *, int, int * );
+static int (* parent_match)( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int (* parent_subframe)( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_setunit)( AstFrame *, int, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_overlay)( AstFrame *, const int *, AstFrame *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static void (* parent_setsystem)( AstFrame *, AstSystemType, int * );
+static void (* parent_clearsystem)( AstFrame *, int * );
+static void (* parent_clearunit)( AstFrame *, int, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->GetLabel_Buff[ 0 ] = 0; \
+   globals->GetSymbol_Buff[ 0 ] = 0; \
+   globals->GetTitle_Buff[ 0 ] = 0; \
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(FluxFrame)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(FluxFrame,Class_Init)
+#define class_vtab astGLOBAL(FluxFrame,Class_Vtab)
+#define getattrib_buff astGLOBAL(FluxFrame,GetAttrib_Buff)
+#define getlabel_buff astGLOBAL(FluxFrame,GetLabel_Buff)
+#define getsymbol_buff astGLOBAL(FluxFrame,GetSymbol_Buff)
+#define gettitle_buff astGLOBAL(FluxFrame,GetTitle_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Buffers for strings returned by various functions. */
+static char getattrib_buff[ AST__FLUXFRAME_GETATTRIB_BUFF_LEN + 1 ];
+static char getlabel_buff[ AST__FLUXFRAME_GETLABEL_BUFF_LEN + 1 ];
+static char getsymbol_buff[ AST__FLUXFRAME_GETSYMBOL_BUFF_LEN + 1 ];
+static char gettitle_buff[ AST__FLUXFRAME_GETTITLE_BUFF_LEN + 1 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstFluxFrameVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static int GetObjSize( AstObject *, int * );
+static AstSpecFrame *GetSpecFrame( AstFluxFrame *, int * );
+static AstSystemType DensitySystem( AstSystemType, int * );
+static AstSystemType GetAlignSystem( AstFrame *, int * );
+static AstSystemType GetDensitySystem( AstFluxFrame *, int * );
+static AstSystemType SystemCode( AstFrame *, const char *, int * );
+static AstSystemType ValidateSystem( AstFrame *, AstSystemType, const char *, int * );
+static const char *DefUnit( AstSystemType, const char *, const char *, int * );
+static const char *DensityUnit( AstSystemType, int * );
+static const char *FluxSystemString( AstSystemType, int * );
+static const char *GetDensityUnit( AstFluxFrame *, int * );
+static const char *GetDomain( AstFrame *, int * );
+static const char *GetLabel( AstFrame *, int, int * );
+static const char *GetSymbol( AstFrame *, int, int * );
+static const char *GetTitle( AstFrame *, int * );
+static const char *GetUnit( AstFrame *, int, int * );
+static const char *SystemLabel( AstSystemType, int * );
+static const char *SystemString( AstFrame *, AstSystemType, int * );
+static int GetActiveUnit( AstFrame *, int * );
+static int MakeFluxMapping( AstFluxFrame *, AstFluxFrame *, AstSystemType, AstMapping **, int * );
+static int Match( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int SubFrame( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int TestActiveUnit( AstFrame *, int * );
+static int UnitsOK( AstSystemType, const char *, int, const char *, const char *, int * );
+static void ClearUnit( AstFrame *, int, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void Overlay( AstFrame *, const int *, AstFrame *, int * );
+static void SetUnit( AstFrame *, int, const char *, int * );
+
+static AstSystemType GetSystem( AstFrame *, int * );
+static void SetSystem( AstFrame *, AstSystemType, int * );
+static void ClearSystem( AstFrame *, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+static double GetSpecVal( AstFluxFrame *, int * );
+static int TestSpecVal( AstFluxFrame *, int * );
+static void ClearSpecVal( AstFluxFrame *, int * );
+static void SetSpecVal( AstFluxFrame *, double, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astClearAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     FluxFrame, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to the FluxFrame structure */
+   int len;                      /* Length of attrib string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_object;
+
+/* Obtain the length of the "attrib" string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* SpecVal. */
+/* -------- */
+   if ( !strcmp( attrib, "specval" ) ) {
+      astClearSpecVal( this );
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     ClearSystem
+
+*  Purpose:
+*     Clear the System attribute for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     void ClearSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astClearSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the System attribute for a FluxFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to FluxFrame structure */
+   AstSystemType newsys;         /* System after clearing */
+   AstSystemType oldsys;         /* System before clearing */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_frame;
+
+/* Save the original system */
+   oldsys = astGetSystem( this_frame );
+
+/* Use the parent ClearSystem method to clear the System value. */
+   (*parent_clearsystem)( this_frame, status );
+
+/* Get the default System. */
+   newsys = astGetSystem( this_frame );
+
+/* If the system has actually changed. */
+   if( newsys != oldsys ) {
+
+/* Changing the System value will in general require the Units to change
+   as well. If the used has previously specified the units to be used with
+   the new system, then re-instate them (they are stored in the "usedunits"
+   array in the FluxFrame structure). Otherwise, clear the units so that
+   the default units will eb used with the new System. */
+      if( (int) newsys < this->nuunits && this->usedunits &&
+          this->usedunits[ (int) newsys ] ) {
+         (*parent_setunit)( this_frame, 0, this->usedunits[ (int) newsys ], status );
+      } else {
+         (*parent_clearunit)( this_frame, 0, status );
+      }
+
+/* Also, clear all attributes which have system-specific defaults. */
+      astClearLabel( this_frame, 0 );
+      astClearSymbol( this_frame, 0 );
+      astClearTitle( this_frame );
+   }
+
+}
+
+static void ClearUnit( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     ClearUnit
+
+*  Purpose:
+*     Clear the value of the Unit string for a FluxFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     void ClearUnit( AstFrame *this_frame, int axis )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astClearUnit method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function clears the Unit string for a specified axis of a
+*     FluxFrame. It also clears the UsedUnit item in the FluxFrame
+*     structure corresponding to the current System.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     axis
+*        The number of the axis (zero-based).
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to the FluxFrame structure */
+   int system;                   /* The FluxFrame's System value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_frame;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astClearUnit" );
+
+/* Clear the UsedUnit item for the current System, if current set. */
+   system = (int) astGetSystem( this );
+   if( system < this->nuunits && this->usedunits ) {
+      this->usedunits[ system ] = astFree( this->usedunits[ system ] );
+   }
+
+/* Use the parent method to clear the Unit attribute of the axis. */
+   (*parent_clearunit)( this_frame, axis, status );
+}
+
+static const char *DefUnit( AstSystemType system, const char *method,
+                            const char *class, int *status ){
+/*
+*  Name:
+*     DefUnit
+
+*  Purpose:
+*     Return the default units for a flux coordinate system type.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *DefUnit( AstSystemType system, const char *method,
+*                          const char *class, int *status )
+
+*  Class Membership:
+*     FluxFrame member function.
+
+*  Description:
+*     This function returns a textual representation of the default
+*     units associated with the specified flux coordinate system.
+
+*  Parameters:
+*     system
+*        The flux coordinate system.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     As tring describing the default units. This string follows the
+*     units syntax described in FITS WCS paper I "Representations of world
+*     coordinates in FITS" (Greisen & Calabretta).
+
+*  Notes:
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Value to return */
+
+/* Initialize */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get an identifier for the default units. */
+   if( system == AST__FLUXDEN ) {
+      result = "W/m^2/Hz";
+
+   } else if( system == AST__FLUXDENW ) {
+      result = "W/m^2/Angstrom";
+
+   } else if( system == AST__SBRIGHT ) {
+      result = "W/m^2/Hz/arcmin**2";
+
+   } else if( system == AST__SBRIGHTW ) {
+      result = "W/m^2/Angstrom/arcmin**2";
+
+/* Report an error if the coordinate system was not recognised. */
+   } else {
+      astError( AST__SCSIN, "%s(%s): Corrupt %s contains illegal System "
+                "identification code (%d).", status, method, class, class,
+                (int) system );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType DensitySystem( AstSystemType sys, int *status ) {
+/*
+*  Name:
+*     DensitySystem
+
+*  Purpose:
+*     Obtain the System describing the spectral density for a FluxFrame
+*     system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     AstSystemType DensitySystem( AstSystemType sys, int *status )
+
+*  Class Membership:
+*     FluxFrame member function.
+
+*  Description:
+*     This function returns AST__FREQ if the FluxFrame system describes
+*     a quantity measured per unit frequency, and returns AST__WAVELEN if
+*     the FluxFrame system describes a quantity measured per unit wavelength.
+
+*  Parameters:
+*     sys
+*        A System value appropriate to a FluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The density System value.
+
+*  Notes:
+*     - AST__BADSYSTEM is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;         /* Value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Categorise the supplied system. */
+   if( sys == AST__FLUXDEN || sys == AST__SBRIGHT ) {
+      result = AST__FREQ;
+
+   } else if( sys == AST__FLUXDENW || sys == AST__SBRIGHTW ) {
+      result = AST__WAVELEN;
+
+   } else if( astOK ) {
+      astError( AST__INTER, "DensitySystem(FluxFrame): The "
+                "DensitySystem method does not yet support "
+                "FluxFrame system %d (AST internal programming error).", status,
+                sys );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *DensityUnit( AstSystemType sys, int *status ) {
+/*
+*  Name:
+*     DensityUnit
+
+*  Purpose:
+*     Obtain the default units for the spectral density of a FluxFrame
+*     system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *DensityUnit( AstSystemType sys, int *status )
+
+*  Class Membership:
+*     FluxFrame member function.
+
+*  Description:
+*     This function returns "Hz" if the FluxFrame system describes
+*     a quantity measured per unit frequency, and returns "Angstrom" if
+*     the FluxFrame system describes a quantity measured per unit wavelength.
+
+*  Parameters:
+*     sys
+*        A FluxFrame system value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the Unit value.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Categorise the supplied FluxFrame system. */
+   if( sys == AST__FLUXDEN || sys == AST__SBRIGHT ) {
+      result = "Hz";
+
+   } else if( sys == AST__FLUXDENW || sys == AST__SBRIGHTW ) {
+      result = "Angstrom";
+
+   } else if( astOK ) {
+      astError( AST__INTER, "DensityUnit(FluxFrame): The DensityUnit "
+                "method does not yet support FluxFrame system %d (AST "
+                "internal programming error).", status, sys );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *FluxSystemString( AstSystemType system, int *status ) {
+/*
+*  Name:
+*     FluxSystemString
+
+*  Purpose:
+*     Convert a FluxFrame coordinate system type code into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *FluxSystemString( AstSystemType system, int *status )
+
+*  Class Membership:
+*     FluxFrame member function
+
+*  Description:
+*     This function converts a FluxFrame coordinate system type code
+*     (System attribute value) into a string suitable for use as an
+*     external representation of the coordinate system type.
+
+*  Parameters:
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" value against each possibility and convert to a
+   string pointer. */
+   switch ( system ) {
+
+   case AST__FLUXDEN:
+      result = "FLXDN";
+      break;
+
+   case AST__FLUXDENW:
+      result = "FLXDNW";
+      break;
+
+   case AST__SBRIGHT:
+      result = "SFCBR";
+      break;
+
+   case AST__SBRIGHTW:
+      result = "SFCBRW";
+      break;
+
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied FluxFrame,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;         /* Pointer to FluxFrame structure */
+   int result;                /* Result value to return */
+   int i;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   if( this && this->usedunits ) {
+      for( i = 0; i < this->nuunits; i++ ) {
+         result += astTSizeOf( this->usedunits[ i ] );
+      }
+      result += astTSizeOf( this->usedunits );
+   }
+
+   result += astGetObjSize( this->specframe );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetActiveUnit( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetActiveUnit
+
+*  Purpose:
+*     Obtain the value of the ActiveUnit flag for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     int GetActiveUnit( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astGetActiveUnit protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function returns the value of the ActiveUnit flag for a
+*    FluxFrame, which is always 1.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The value to use for the ActiveUnit flag (1).
+
+*/
+   return 1;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the protected astGetAttrib
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a FluxFrame, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - The returned string pointer may point at memory allocated
+*     within the FluxFrame, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the FluxFrame. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFluxFrame *this;           /* Pointer to the FluxFrame structure */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Attribute value */
+   int len;                      /* Length of attrib string */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* SpecVal */
+/* ------- */
+   if ( !strcmp( attrib, "specval" ) ) {
+      dval = astGetSpecVal( this );
+      if ( astOK ) {
+         if( dval != AST__BAD ) {
+            (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+            result = getattrib_buff;
+         } else {
+            result = "<bad>";
+         }
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType GetDensitySystem( AstFluxFrame *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetDensitySystem
+
+*  Purpose:
+*     Obtain the System describing the spectral density of a FluxFrame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     AstSystemType astGetDensitySystem( AstFluxFrame *this )
+
+*  Class Membership:
+*     FluxFrame method.
+
+*  Description:
+*     This function returns AST__FREQ if the FluxFrame system describes
+*     a quantity measured per unit frequency, and returns AST__WAVELEN if
+*     the FluxFrame system describes a quantity measured per unit wavelength.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+
+*  Returned Value:
+*     The System value.
+
+*  Notes:
+*     - AST__BADSYSTEM is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BADSYSTEM;
+
+/* Get the FluxFrame system and categorise it. */
+   return DensitySystem( astGetSystem( this ), status );
+}
+
+static const char *GetDensityUnit( AstFluxFrame *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetDensityUnit
+
+*  Purpose:
+*     Obtain the default units for the spectral density of a FluxFrame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *astGetDensityUnit( AstFluxFrame *this )
+
+*  Class Membership:
+*     FluxFrame method.
+
+*  Description:
+*     This function returns "Hz" if the FluxFrame system describes
+*     a quantity measured per unit frequency, and returns "Angstrom" if
+*     the FluxFrame system describes a quantity measured per unit wavelength.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the Unit value.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get the FluxFrame system and categorise it. */
+   return DensityUnit( astGetSystem( this ), status );
+}
+
+static const char *GetDomain( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetDomain
+
+*  Purpose:
+*     Obtain a pointer to the Domain attribute string for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *GetDomain( AstFrame *this, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astGetDomain protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function returns a pointer to the Domain attribute string
+*    for a FluxFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a constant null-terminated string containing the
+*     Domain value.
+
+*  Notes:
+*     - The returned pointer or the string it refers to may become
+*     invalid following further invocation of this function or
+*     modification of the FluxFrame.
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to FluxFrame structure */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_frame;
+
+/* If a Domain attribute string has been set, invoke the parent method
+   to obtain a pointer to it. */
+   if ( astTestDomain( this ) ) {
+      result = (*parent_getdomain)( this_frame, status );
+
+/* Otherwise, provide a pointer to a suitable default string. */
+   } else {
+      result = "FLUX";
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetLabel( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetLabel
+
+*  Purpose:
+*     Access the Label string for a FluxFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *GetLabel( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astGetLabel method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Label string for a specified axis
+*     of a FluxFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated character string containing the
+*     requested information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstMapping *map;              /* Mapping between units */
+   AstSystemType system;         /* Code identifying type of flux coordinates */
+   char *new_lab;                /* Modified label string */
+   const char *result;           /* Pointer to label string */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise. */
+   result = NULL;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astGetLabel" );
+
+/* Check if a value has been set for the required axis label string. If so,
+   invoke the parent astGetLabel method to obtain a pointer to it. */
+   if ( astTestLabel( this, axis ) ) {
+      result = (*parent_getlabel)( this, axis, status );
+
+/* Otherwise, identify the flux coordinate system described by the
+   FluxFrame. */
+   } else {
+      system = astGetSystem( this );
+
+/* If OK, supply a pointer to a suitable default label string. */
+      if ( astOK ) {
+         result = strcpy( getlabel_buff, SystemLabel( system, status ) );
+         getlabel_buff[ 0 ] = toupper( getlabel_buff[ 0 ] );
+
+/* Modify this default to take account of the current value of the Unit
+   attribute, if set. */
+         if( astTestUnit( this, axis ) ) {
+
+/* Find a Mapping from the default Units for the current System, to the
+   units indicated by the Unit attribute. This Mapping is used to modify
+   the existing default label appropriately. For instance, if the default
+   units is "Jy" and the actual units is "log(Jy)", then the default label
+   of "Flux density" is changed to "log( Flux density )". */
+            map = astUnitMapper( DefUnit( system, "astGetLabel",
+                                          astGetClass( this ), status ),
+                                 astGetUnit( this, axis ), result,
+                                 &new_lab );
+            if( new_lab ) {
+               result = strcpy( getlabel_buff, new_lab );
+               new_lab = astFree( new_lab );
+            }
+
+/* Annul the unused Mapping. */
+            if( map ) map = astAnnul( map );
+
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSpecFrame *GetSpecFrame( AstFluxFrame *this, int *status ) {
+/*
+*  Name:
+*     GetSpecFrame
+
+*  Purpose:
+*     Get a pointer to a SpecFrame associated with a FluxFrame
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     AstSpecFrame *GetSpecFrame( AstFluxFrame *this, int *status )
+
+*  Class Membership:
+*     FluxFrame member function
+
+*  Description:
+*     This function returns a SpecFrame describing the spectral system in
+*     which the FluxFrame's SpecVal attribute is stored. A default
+*     SpecFrame is created and returned if the no SpecFrame was supplied
+*     when the FluxFrame was created.
+
+*  Parameters:
+*     this
+*        The FluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the SpecFrame. It should be freed using astAnnul when no
+*     longer needed.
+
+*  Notes:
+*     - A NULL pointer value is returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the FluxFrame contains a SpecFrame, return a clone of its pointer. */
+   if( this->specframe ) {
+      result = astClone( this->specframe );
+
+/* Otherwise, create a SpecFrame appropriate to the FluxFrames System. */
+   } else {
+      result = astSpecFrame( "", status );
+      astSetSystem( result, astGetDensitySystem( this ) );
+      astSetUnit( result, 0, astGetDensityUnit( this ) );
+   }
+
+/* Annul the result if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result pointer. */
+   return result;
+}
+
+static const char *GetSymbol( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetSymbol
+
+*  Purpose:
+*     Obtain a pointer to the Symbol string for a FluxFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *GetSymbol( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astGetSymbol method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Symbol string for a specified axis
+*     of a FluxFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated character string containing the
+*     requested information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstMapping *map;              /* Mapping between units */
+   AstSystemType system;         /* Code identifying type of sky coordinates */
+   char *new_sym;                /* Modified symbol string */
+   const char *result;           /* Pointer to symbol string */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise. */
+   result = NULL;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astGetSymbol" );
+
+/* Check if a value has been set for the required axis symbol string. If so,
+   invoke the parent astGetSymbol method to obtain a pointer to it. */
+   if ( astTestSymbol( this, axis ) ) {
+      result = (*parent_getsymbol)( this, axis, status );
+
+/* Otherwise, identify the flux coordinate system described by the FluxFrame. */
+   } else {
+      system = astGetSystem( this );
+
+/* If OK, supply a pointer to a suitable default Symbol string. */
+      if ( astOK ) {
+
+         if( system == AST__FLUXDEN ) {
+	    result = "S_nu";
+
+         } else if( system == AST__FLUXDENW ) {
+	    result = "S_lambda";
+
+         } else if( system == AST__SBRIGHT ) {
+	    result = "mu_nu";
+
+         } else if( system == AST__SBRIGHTW ) {
+	    result = "mu_lambda";
+
+/* Report an error if the coordinate system was not recognised. */
+         } else {
+	    astError( AST__SCSIN, "astGetSymbol(%s): Corrupt %s contains "
+		      "invalid System identification code (%d).", status,
+                      astGetClass( this ), astGetClass( this ), (int) system );
+         }
+
+/* Modify this default to take account of the current value of the Unit
+   attribute, if set. */
+         if( astTestUnit( this, axis ) ) {
+
+/* Find a Mapping from the default Units for the current System, to the
+   units indicated by the Unit attribute. This Mapping is used to modify
+   the existing default symbol appropriately. For instance, if the default
+   units is "Jy" and the actual units is "log(Jy)", then the default symbol
+   of "S_nu" is changed to "log( S_nu )". */
+            map = astUnitMapper( DefUnit( system, "astGetSymbol",
+                                          astGetClass( this ), status ),
+                                 astGetUnit( this, axis ), result,
+                                 &new_sym );
+            if( new_sym ) {
+               result = strcpy( getsymbol_buff, new_sym );
+               new_sym = astFree( new_sym );
+            }
+
+/* Annul the unused Mapping. */
+            if( map ) map = astAnnul( map );
+
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType GetAlignSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetAlignSystem
+
+*  Purpose:
+*     Obtain the AlignSystem attribute for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     AstSystemType GetAlignSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astGetAlignSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the AlignSystem attribute for a FluxFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The AlignSystem value.
+
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to FluxFrame structure */
+   AstSystemType result;         /* Value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_frame;
+
+/* If a AlignSystem attribute has been set, invoke the parent method to obtain
+   it. */
+   if ( astTestAlignSystem( this ) ) {
+      result = (*parent_getalignsystem)( this_frame, status );
+
+/* Otherwise, provide a suitable default. */
+   } else {
+      result = AST__FLUXDEN;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType GetSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetSystem
+
+*  Purpose:
+*     Obtain the System attribute for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     AstSystemType GetSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astGetSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the System attribute for a FluxFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System value.
+
+*  Notes:
+*     - AST__BADSYSTEM is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to FluxFrame structure */
+   AstMapping *map;              /* Pointer to unit Mapping */
+   AstSystemType i;              /* System to check */
+   AstSystemType result;         /* Value to return */
+   const char *units;            /* FluxFrame units */
+   int unitSet;                  /* Has a value been supplied for Unit? */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_frame;
+
+/* See if a value has been assigned to the Unit attribute. */
+   unitSet = astTestUnit( this_frame, 0 );
+
+/* If a System attribute has been set, invoke the parent method to obtain
+   it. */
+   if ( astTestSystem( this ) ) {
+      result = (*parent_getsystem)( this_frame, status );
+
+/* Otherwise, if the Unit attribute has been set, provide a suitable default
+   system based on the units. */
+   } else if( unitSet ){
+
+/* Loop round each known system value. If a Mapping can be found from the
+   current units to the default units for the system, then use the system as
+   the default system. */
+      units = astGetUnit( this_frame, 0 );
+      for( i = FIRST_SYSTEM; i <= LAST_SYSTEM; i++ ) {
+         map = astUnitMapper( units, DefUnit( i, "astGetSystem",
+                                    astGetClass( this ), status ), NULL, NULL );
+         if( map ) {
+            map = astAnnul( map );
+            result = i;
+            break;
+         }
+      }
+
+/* Otherwise, report an error. */
+      if( result == AST__BADSYSTEM && astOK ) {
+         astError( AST__BADUN, "astGetSystem(%s): The current units (%s) "
+                   "cannot be used with any of the supported flux systems.", status,
+                   astGetClass( this ), astGetUnit( this_frame, 0 ) );
+      }
+
+/* Otherwise, provide a suitable default based on the units. */
+   } else {
+      result = AST__FLUXDEN;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetTitle( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetTitle
+
+*  Purpose:
+*     Obtain a pointer to the Title string for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *GetTitle( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astGetTitle method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Title string for a FluxFrame.
+*     A pointer to a suitable default string is returned if no Title value has
+*     previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a null-terminated character string containing the requested
+*     information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFluxFrame *this;           /* Pointer to FluxFrame structure */
+   AstSpecFrame *sf;             /* Pointer to SpecFrame structure */
+   const char *result;           /* Pointer to result string */
+   const char *sv;               /* Formatted SpecVal string */
+   const char *su;               /* Units string */
+   double specval;               /* SpecVal value */
+   int pos;                      /* Buffer position to enter text */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_frame);
+
+/* Initialise. */
+   result = NULL;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_frame;
+
+/* See if a Title string has been set. If so, use the parent astGetTitle
+   method to obtain a pointer to it. */
+   if ( astTestTitle( this ) ) {
+      result = (*parent_gettitle)( this_frame, status );
+
+/* Otherwise, we will generate a default Title string. */
+   } else {
+
+/* Classify the coordinate system type and create an appropriate Title
+   string.  */
+      if ( astOK ) {
+         result = gettitle_buff;
+
+/* Begin with the system's default label. */
+         pos = sprintf( gettitle_buff, "%s", SystemLabel( astGetSystem( this ), status ) );
+         gettitle_buff[ 0 ] = toupper( gettitle_buff[ 0 ] );
+
+/* Append the spectral position, if known. */
+         specval = astGetSpecVal( this );
+         sf = GetSpecFrame( this, status );
+         if( specval != AST__BAD && sf ) {
+            sv = astFormat( sf, 0, specval );
+            su = astGetUnit( sf, 0 );
+            pos += sprintf( gettitle_buff + pos, " at = %s %s", sv, su );
+         }
+         sf = astAnnul( sf );
+      }
+   }
+
+/* If an error occurred, clear the returned pointer value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetUnit( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetUnit
+
+*  Purpose:
+*     Obtain a pointer to the Unit string for a FluxFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *GetUnit( AstFrame *this_frame, int axis )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astGetUnit method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Unit string for a specified axis
+*     of a FluxFrame. If the Unit attribute has not been set for the axis, a
+*     pointer to a suitable default string is returned instead.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     axis
+*        The number of the axis (zero-based) for which information is required.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the Unit value.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to the FluxFrame structure */
+   AstSystemType system;         /* The FluxFrame's System value */
+   const char *result;           /* Pointer value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_frame;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astGetUnit" );
+
+/* If a value has been set for the Unit attribute, use the parent
+   GetUnit method to return a pointer to the required Unit string. */
+   if( astTestUnit( this, axis ) ){
+      result = (*parent_getunit)( this_frame, axis, status );
+
+/* Otherwise, identify the flux coordinate system described by the
+   FluxFrame. */
+   } else {
+      system = astGetSystem( this );
+
+/* Return a string describing the default units. */
+      result = DefUnit( system, "astGetUnit", astGetClass( this ), status );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+void astInitFluxFrameVtab_(  AstFluxFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitFluxFrameVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a FluxFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     void astInitFluxFrameVtab( AstFluxFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     FluxFrame vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the FluxFrame class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrameVtab *frame;          /* Pointer to Frame component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitFrameVtab( (AstFrameVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAFluxFrame) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstFrameVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->GetDensitySystem = GetDensitySystem;
+   vtab->GetDensityUnit = GetDensityUnit;
+
+   vtab->ClearSpecVal = ClearSpecVal;
+   vtab->TestSpecVal = TestSpecVal;
+   vtab->GetSpecVal = GetSpecVal;
+   vtab->SetSpecVal = SetSpecVal;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   frame = (AstFrameVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_getdomain = frame->GetDomain;
+   frame->GetDomain = GetDomain;
+
+   parent_getsystem = frame->GetSystem;
+   frame->GetSystem = GetSystem;
+   parent_setsystem = frame->SetSystem;
+   frame->SetSystem = SetSystem;
+   parent_clearsystem = frame->ClearSystem;
+   frame->ClearSystem = ClearSystem;
+
+   parent_getalignsystem = frame->GetAlignSystem;
+   frame->GetAlignSystem = GetAlignSystem;
+
+   parent_getlabel = frame->GetLabel;
+   frame->GetLabel = GetLabel;
+
+   parent_getsymbol = frame->GetSymbol;
+   frame->GetSymbol = GetSymbol;
+
+   parent_gettitle = frame->GetTitle;
+   frame->GetTitle = GetTitle;
+
+   parent_clearunit = frame->ClearUnit;
+   frame->ClearUnit = ClearUnit;
+
+   parent_getunit = frame->GetUnit;
+   frame->GetUnit = GetUnit;
+
+   parent_setunit = frame->SetUnit;
+   frame->SetUnit = SetUnit;
+
+   parent_match = frame->Match;
+   frame->Match = Match;
+
+   parent_overlay = frame->Overlay;
+   frame->Overlay = Overlay;
+
+   parent_subframe = frame->SubFrame;
+   frame->SubFrame = SubFrame;
+
+/* Store replacement pointers for methods which will be over-ridden by new
+   member functions implemented here. */
+   frame->GetActiveUnit = GetActiveUnit;
+   frame->TestActiveUnit = TestActiveUnit;
+   frame->ValidateSystem = ValidateSystem;
+   frame->SystemString = SystemString;
+   frame->SystemCode = SystemCode;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "FluxFrame", "Description of flux values" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;       /* Pointer to FluxFrame structure */
+   int result;               /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->specframe, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static int MakeFluxMapping( AstFluxFrame *target, AstFluxFrame *result,
+                            AstSystemType align_sys, AstMapping **map, int *status ) {
+/*
+*  Name:
+*     MakeFluxMapping
+
+*  Purpose:
+*     Generate a Mapping between two FluxFrames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     int MakeFluxMapping( AstFluxFrame *target, AstFluxFrame *result,
+*                          AstSystemType align_sys, MakeFluAstMapping **map, int *status )
+
+*  Class Membership:
+*     FluxFrame member function.
+
+*  Description:
+*     This function takes two FluxFrames and generates a Mapping that
+*     converts between them, taking account of differences in their
+*     coordinate systems, reference frequency, etc.
+*
+*     In order to cut down the number of transformations to be considered,
+*     the scheme works by first converting from the target frame to an
+*     "alignment" Frame, using the attributes of the target to define the
+*     transformation. A transformation is then found from the alignment
+*     frame to the required result Frame,  using the attributes of the
+*     result to define the transformation. The alignment Frame is
+*     described by the supplied parameter "align_sys".
+
+*  Parameters:
+*     target
+*        Pointer to the first FluxFrame.
+*     result
+*        Pointer to the second FluxFrame.
+*     align_sys
+*        The flux system in which to align the two FluxFrames.
+*     map
+*        Pointer to a location which is to receive a pointer to the
+*        returned Mapping. The forward transformation of this Mapping
+*        will convert from "target" coordinates to "result"
+*        coordinates, and the inverse transformation will convert in
+*        the opposite direction (all coordinate values in radians).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the Mapping could be generated, or zero if the two
+*     FluxFrames are sufficiently un-related that no meaningful Mapping
+*     can be produced (in which case a NULL Mapping pointer will be
+*     returned).
+
+*  Notes:
+*     A value of zero is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;
+   AstMapping *map1;
+   AstMapping *map2;
+   AstMapping *map3;
+   AstMapping *map4;
+   AstMapping *map5;
+   AstMapping *smap;
+   AstMapping *smap_in;
+   AstMapping *smap_out;
+   AstMapping *tmap;
+   AstSpecFrame *sfin1;
+   AstSpecFrame *sfin2;
+   AstSpecFrame *sfout1;
+   AstSpecFrame *sfout2;
+   AstSystemType rsys_in;
+   AstSystemType rsys_out;
+   AstSystemType sys_in;
+   AstSystemType sys_out;
+   double specval2;
+   double specval;
+   double specval_in;
+   double specval_out;
+   double zoom;
+   int match;
+   int sb_in;
+   int sb_out;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise the returned values. */
+   match = 0;
+   *map = NULL;
+
+/* Initialise to avoid compiler warnings. */
+   map1 = NULL;
+   map2 = NULL;
+   map3 = NULL;
+
+/* Note the target and result System */
+   rsys_in = astGetSystem( target );
+   rsys_out = astGetSystem( result );
+
+/* First get a Mapping which converts from the units used in the target
+   to the default units associated with the target's system.
+   ---------------------------------------------------------------------- */
+   map1 = astUnitMapper( astGetUnit( target, 0 ),
+                         DefUnit( rsys_in, "MakeFluxMapping", "FluxFrame", status ),
+                         NULL, NULL );
+
+/* If the target system is surface brightness, change it to the
+   corresponding flux density system. We are effectively converting from
+   surface brightness to the flux density normalised to unit area. Also
+   set flags indicating if the systems are surface brightness systems. */
+   if( rsys_in == AST__SBRIGHT ) {
+      sys_in = AST__FLUXDEN;
+      sb_in = 1;
+
+   } else if( rsys_in == AST__SBRIGHTW ) {
+      sys_in = AST__FLUXDENW;
+      sb_in = 1;
+
+   } else {
+      sys_in = rsys_in;
+      sb_in = 0;
+   }
+
+/* Likewise if the result system is surface brightness, change it to the
+   corresponding flux density system. */
+   if( rsys_out == AST__SBRIGHT ) {
+      sys_out = AST__FLUXDEN;
+      sb_out = 1;
+
+   } else if( rsys_out == AST__SBRIGHTW ) {
+      sys_out = AST__FLUXDENW;
+      sb_out = 1;
+
+   } else {
+      sys_out = rsys_out;
+      sb_out = 0;
+   }
+
+/* Assume at this point in the chain of coversions that we have target values
+   in some form of flux density system (either frequency or wavelength). The
+   precise units do not matter at this point (so long as they are
+   dimensionally correct for describing the relevant form of flux density).
+   When other systems are added (e.g. antenna temperature), some code
+   will have to come before this point which produces a Mapping from (e.g.)
+   antenna temperature to flux density. */
+
+
+/* Get a Mapping from the default units for the input flux density system
+   to the default units for the output flux density system.
+   ---------------------------------------------------------------------- */
+
+/* If one but not both of the systems represent surface brightness, then
+   we cannot form a Mapping. */
+   if( sb_in != sb_out ) {
+      zoom = AST__BAD;
+
+/* If the input and output flux density systems are the same, then the
+   required Mapping is a UnitMap. */
+   } else if( sys_in == sys_out ) {
+      zoom = 1.0;
+
+/* Otherwise, the required Mapping is a zoom map in which the scale factor is
+   the rate of change of the input spectral system with respect to the output
+   spectral system, at the position given by the SpecVal attribute (we
+   cannot do the conversion if the SpecVal values in the target and result
+   differ). Each spectral system is either wavelength (in Angstrom) or
+   frequency (in Hz), depending on whether the associated flux density
+   system is "per Angstrom" or "per Hertz". The SpecVal value may be
+   stored in some other system, so the first job is to create SpecFrames
+   with the required system and units from the SpecFrames encapsulated
+   within the target and result FluxFrames. Take deep copies of the two
+   SpecFrames, and set their systems and units. */
+   } else {
+      sfin1 = GetSpecFrame( target, status );
+      sfin2 = astCopy( sfin1 );
+      astSetSystem( sfin2, DensitySystem( sys_in, status ) );
+      astSetUnit( sfin2, 0, DensityUnit( sys_in, status ) );
+
+      sfout1 = GetSpecFrame( result, status );
+      sfout2 = astCopy( sfout1 );
+      astSetSystem( sfout2, DensitySystem( sys_out, status ) );
+      astSetUnit( sfout2, 0, DensityUnit( sys_out, status ) );
+
+/* Indicate we do not yet have a zoom factor */
+      zoom = AST__BAD;
+
+/* Get the Mapping from output to input spectral coordinate system */
+      fs = astConvert( sfout2, sfin2, "" );
+      if( fs ) {
+         tmap = astGetMapping( fs, AST__BASE, AST__CURRENT );
+         fs = astAnnul( fs );
+
+/* Simplify the Mapping. */
+         smap = astSimplify( tmap );
+         tmap = astAnnul( tmap );
+
+/* We first need to transform the two SpecVal attributes into the input
+   coordinate system of the "smap" Mapping (i.e. the standardised result
+   FluxFrame), and check they are the same. For this we need the Mappings
+   from the SpecFrames stored in the FluxFrames to the modified copies
+   created above. */
+         fs = astConvert( sfin1, sfin2, "" );
+         if( fs ) {
+            smap_in = astGetMapping( fs, AST__BASE, AST__CURRENT );
+            fs = astAnnul( fs );
+         } else {
+            smap_in = NULL;
+         }
+
+         fs = astConvert( sfout1, sfout2, "" );
+         if( fs ) {
+            smap_out = astGetMapping( fs, AST__BASE, AST__CURRENT );
+            fs = astAnnul( fs );
+         } else {
+            smap_out = NULL;
+         }
+
+/* Convert the target's SpecVal into the standardised target system */
+         specval = astGetSpecVal( target );
+         astTran1( smap_in, 1, &specval, 1, &specval2 );
+
+/* Now convert it into the standardised result system. Note, we need to
+   use "smap" in the inverse direction for this. */
+         astTran1( smap, 1, &specval2, 0, &specval_in );
+
+/* Convert the results's SpecVal into the standardised result system */
+         specval = astGetSpecVal( result );
+         astTran1( smap_out, 1, &specval, 1, &specval_out );
+
+/* Check they are equal and good. */
+         if( astEQUALS( specval_in, specval_out, 1.0E8 ) && specval_in != AST__BAD ) {
+
+/* If the siSimplified Mapping is a UnitMap the required rate of change
+   factor is 1.0. If it resuts in a ZoomMap, the required factor is
+   the zoom factor in the ZoomMap. */
+            if( astIsAUnitMap( smap ) ) {
+               zoom = 1.0;
+
+            } else if( astIsAZoomMap( smap ) ) {
+               zoom = astGetZoom( smap );
+
+/* For any other type of Mapping, we must determine the rate of change factor
+   by differentiating the Mapping at the SpecVal position. */
+            } else {
+               specval = 0.5*( specval_in  + specval_out );
+               zoom = astRate( smap, &specval, 0, 0 );
+            }
+         }
+
+/* Free resources */
+         if( smap_in ) smap_in = astAnnul( smap_in );
+         if( smap_out ) smap_out = astAnnul( smap_out );
+         smap = astAnnul( smap );
+      }
+
+      sfout1 = astAnnul( sfout1 );
+      sfin1 = astAnnul( sfin1 );
+      sfout2 = astAnnul( sfout2 );
+      sfin2 = astAnnul( sfin2 );
+   }
+
+/* Create the required zoom map if a scaling factor was found. */
+   if( zoom != AST__BAD ) {
+      map2 = (AstMapping *) astZoomMap( 1, fabs( zoom ), "", status );
+   } else {
+      map2 = NULL;
+   }
+
+/* Now get a Mapping which converts from the default units associated with
+   the results's system, to the units used in the result.
+   ----------------------------------------------------------------------- */
+   map3 = astUnitMapper( DefUnit( rsys_out, "MakeFluxMapping", "FluxFrame", status ),
+                         astGetUnit( result, 0 ), NULL, NULL );
+
+/* Indicate a match was found and combine all Mapings in series. */
+   if( map1 && map2 && map3 ) {
+      match = 1;
+      map4 = (AstMapping *) astCmpMap( map1, map2, 1, "", status );
+      map5 = (AstMapping *) astCmpMap( map4, map3, 1, "", status );
+
+/* Return the simplified Mapping. */
+      *map = astSimplify( map5 );
+
+/* Free resources. */
+      map4 = astAnnul( map4 );
+      map5 = astAnnul( map5 );
+   }
+
+/* Free resources. */
+   if( map1 ) map1 = astAnnul( map1 );
+   if( map2 ) map2 = astAnnul( map2 );
+   if( map3 ) map3 = astAnnul( map3 );
+
+/* If an error occurred, annul the returned Mapping and clear the returned
+   values. */
+   if ( !astOK ) {
+      *map = astAnnul( *map );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static int Match( AstFrame *template_frame, AstFrame *target, int matchsub,
+                  int **template_axes, int **target_axes, AstMapping **map,
+                  AstFrame **result, int *status ) {
+/*
+*  Name:
+*     Match
+
+*  Purpose:
+*     Determine if conversion is possible between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     int Match( AstFrame *template, AstFrame *target, int matchsub,
+*                int **template_axes, int **target_axes,
+*                AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the protected astMatch method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function matches a "template" FluxFrame to a "target" Frame and
+*     determines whether it is possible to convert coordinates between them.
+*     If it is, a mapping that performs the transformation is returned along
+*     with a new Frame that describes the coordinate system that results when
+*     this mapping is applied to the "target" coordinate system. In addition,
+*     information is returned to allow the axes in this "result" Frame to be
+*     associated with the corresponding axes in the "target" and "template"
+*     Frames from which they are derived.
+
+*  Parameters:
+*     template
+*        Pointer to the template FluxFrame. This describes the coordinate
+*        system (or set of possible coordinate systems) into which we wish to
+*        convert our coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate system in
+*        which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case.
+*     template_axes
+*        Address of a location where a pointer to int will be returned if the
+*        requested coordinate conversion is possible. This pointer will point
+*        at a dynamically allocated array of integers with one element for each
+*        axis of the "result" Frame (see below). It must be freed by the caller
+*        (using astFree) when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element of this
+*        array will return the index of the template FluxFrame axis from
+*        which it is derived. If it is not derived from any template
+*        FluxFrame axis, a value of -1 will be returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned if the
+*        requested coordinate conversion is possible. This pointer will point
+*        at a dynamically allocated array of integers with one element for each
+*        axis of the "result" Frame (see below). It must be freed by the caller
+*        (using astFree) when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element of this
+*        array will return the index of the target Frame axis from which it
+*        is derived. If it is not derived from any target Frame axis, a value
+*        of -1 will be returned instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will be
+*        returned if the requested coordinate conversion is possible. If
+*        returned, the forward transformation of this Mapping may be used to
+*        convert coordinates between the "target" Frame and the "result"
+*        Frame (see below) and the inverse transformation will convert in the
+*        opposite direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be returned
+*        if the requested coordinate conversion is possible. If returned, this
+*        Frame describes the coordinate system that results from applying the
+*        returned Mapping (above) to the "target" coordinate system. In
+*        general, this Frame will combine attributes from (and will therefore
+*        be more specific than) both the target and the template Frames. In
+*        particular, when the template allows the possibility of transformaing
+*        to any one of a set of alternative coordinate systems, the "result"
+*        Frame will indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate conversion is
+*     possible. Otherwise zero is returned (this will not in itself result in
+*     an error condition).
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+
+*  Implementation Notes:
+*     This implementation addresses the matching of a FluxFrame class
+*     object to any other class of Frame. A FluxFrame will match any class
+*     of FluxFrame (i.e. possibly from a derived class) but will not match
+*     a less specialised class of Frame.
+*/
+
+/* Local Variables: */
+   AstFrame *frame0;             /* Pointer to Frame underlying axis 0 */
+   AstFluxFrame *template;       /* Pointer to template FluxFrame structure */
+   int iaxis0;                   /* Axis index underlying axis 0 */
+   int iaxis;                    /* Axis index */
+   int match;                    /* Coordinate conversion possible? */
+   int target_axis0;             /* Index of FluxFrame axis in the target */
+   int target_naxes;             /* Number of target axes */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the template FluxFrame structure. */
+   template = (AstFluxFrame *) template_frame;
+
+/* Obtain the number of axes in the target Frame. */
+   target_naxes = astGetNaxes( target );
+
+/* The first criterion for a match is that the template matches as a
+   Frame class object. This ensures that the number of axes (1) and
+   domain, etc. of the target Frame are suitable. Invoke the parent
+   "astMatch" method to verify this. */
+   match = (*parent_match)( template_frame, target, matchsub,
+                            template_axes, target_axes, map, result, status );
+
+/* If a match was found, annul the returned objects, which are not
+   needed, but keep the memory allocated for the axis association
+   arrays, which we will re-use. */
+   if ( astOK && match ) {
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+   }
+
+/* If OK so far, obtain pointers to the primary Frames which underlie
+   all target axes. Stop when a FluxFrame axis is found. */
+   if ( match && astOK ) {
+      match = 0;
+      for( iaxis = 0; iaxis < target_naxes; iaxis++ ) {
+         astPrimaryFrame( target, iaxis, &frame0, &iaxis0 );
+         if( astIsAFluxFrame( frame0 ) ) {
+            frame0 = astAnnul( frame0 );
+            target_axis0 = iaxis;
+            match = 1;
+            break;
+         } else {
+            frame0 = astAnnul( frame0 );
+         }
+      }
+   }
+
+/* Check at least one FluxFrame axis was found it the target. Store the
+   axis associataions. */
+   if( match && astOK ) {
+      (*template_axes)[ 0 ] = 0;
+      (*target_axes)[ 0 ] = target_axis0;
+
+/* Use the target's "astSubFrame" method to create a new Frame (the
+   result Frame) with copies of the target axes in the required
+   order. This process also overlays the template attributes on to the
+   target Frame and returns a Mapping between the target and result
+   Frames which effects the required coordinate conversion. */
+      match = astSubFrame( target, template, 1, *target_axes, *template_axes,
+                           map, result );
+   }
+
+/* If an error occurred, or conversion to the result Frame's
+   coordinate system was not possible, then free all memory, annul the
+   returned objects, and reset the returned value. */
+   if ( !astOK || !match ) {
+      *template_axes = astFree( *template_axes );
+      *target_axes = astFree( *target_axes );
+      if( *map ) *map = astAnnul( *map );
+      if( *result ) *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static void Overlay( AstFrame *template, const int *template_axes,
+                     AstFrame *result, int *status ) {
+/*
+*  Name:
+*     Overlay
+
+*  Purpose:
+*     Overlay the attributes of a template FluxFrame on to another Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     void Overlay( AstFrame *template, const int *template_axes,
+*                   AstFrame *result, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the protected astOverlay method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function overlays attributes of a FluxFrame (the "template") on to
+*     another Frame, so as to over-ride selected attributes of that second
+*     Frame. Normally only those attributes which have been specifically set
+*     in the template will be transferred. This implements a form of
+*     defaulting, in which a Frame acquires attributes from the template, but
+*     retains its original attributes (as the default) if new values have not
+*     previously been explicitly set in the template.
+*
+*     Note that if the result Frame is a FluxFrame and a change of flux
+*     coordinate system occurs as a result of overlaying its System
+*     attribute, then some of its original attribute values may no
+*     longer be appropriate (e.g. the Title, or attributes describing
+*     its axes). In this case, these will be cleared before overlaying
+*     any new values.
+
+*  Parameters:
+*     template
+*        Pointer to the template FluxFrame, for which values should have been
+*        explicitly set for any attribute which is to be transferred.
+*     template_axes
+*        Pointer to an array of int, with one element for each axis of the
+*        "result" Frame (see below). For each axis in the result frame, the
+*        corresponding element of this array should contain the (zero-based)
+*        index of the template axis to which it corresponds. This array is used
+*        to establish from which template axis any axis-dependent attributes
+*        should be obtained.
+*
+*        If any axis in the result Frame is not associated with a template
+*        axis, the corresponding element of this array should be set to -1.
+*
+*        If a NULL pointer is supplied, the template and result axis
+*        indices are assumed to be identical.
+*     result
+*        Pointer to the Frame which is to receive the new attribute values.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  In general, if the result Frame is not from the same class as the
+*     template FluxFrame, or from a class derived from it, then attributes may
+*     exist in the template FluxFrame which do not exist in the result Frame.
+*     In this case, these attributes will not be transferred.
+*/
+
+
+/* Local Variables: */
+   AstFluxFrame *resff;          /* Result FluxFrame */
+   AstFluxFrame *tmpff;          /* Template FluxFrame */
+   AstSystemType new_alignsystem;/* Code identifying alignment coords */
+   AstSystemType new_system;     /* Code identifying new cordinates */
+   AstSystemType old_system;     /* Code identifying old coordinates */
+   const char *method;           /* Pointer to method string */
+   const char *new_class;        /* Pointer to template class string */
+   const char *old_class;        /* Pointer to result class string */
+   int fluxframe;                /* Result Frame is a FluxFrame? */
+   int resetSystem;              /* Was the template System value cleared? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise strings used in error messages. */
+   new_class = astGetClass( template );
+   old_class = astGetClass( result );
+   method = "astOverlay";
+
+/* Get the old and new systems. */
+   old_system = astGetSystem( result );
+   new_system = astGetSystem( template );
+
+/* If the result Frame is a FluxFrame, we must test to see if overlaying its
+   System attribute will change the type of coordinate system it describes.
+   Determine the value of this attribute for the result and template
+   FluxFrames. */
+   resetSystem = 0;
+   fluxframe = astIsAFluxFrame( result );
+   if( fluxframe ) {
+
+/* If the coordinate system will change, any value already set for the result
+   FluxFrame's Title will no longer be appropriate, so clear it. */
+      if ( new_system != old_system ) {
+         astClearTitle( result );
+
+/* If the systems have the same default units, we can retain the current
+   Unit value. */
+         if( strcmp( DefUnit( new_system, method, new_class, status ),
+                     DefUnit( old_system, method, old_class, status ) ) ) {
+            astClearUnit( result, 0 );
+         }
+
+/* If necessary, clear inappropriate values for all those axis attributes
+   whose access functions are over-ridden by this class (these access functions
+   will then provide suitable defaults appropriate to the new coordinate system
+   instead). */
+         astClearLabel( result, 0 );
+         astClearSymbol( result, 0 );
+      }
+
+/* Transfer the default SpecVal value and the SpecFrame. */
+      resff = (AstFluxFrame *) result;
+      tmpff = (AstFluxFrame *) template;
+      resff->defspecval = tmpff->defspecval;
+      if( resff->specframe ) (void) astAnnul( resff->specframe );
+      resff->specframe = tmpff->specframe ? astCopy( tmpff->specframe ) : NULL;
+
+/* If the result Frame is not a FluxFrame, we must temporarily clear the
+   System and AlignSystem values since the values used by this class are only
+   appropriate to this class. */
+   } else {
+      if( astTestSystem( template ) ) {
+         astClearSystem( template );
+
+         new_alignsystem = astGetAlignSystem( template );
+         astClearAlignSystem( template );
+
+         resetSystem = 1;
+      }
+   }
+
+/* Invoke the parent class astOverlay method to transfer attributes inherited
+   from the parent class. */
+   (*parent_overlay)( template, template_axes, result, status );
+
+/* Reset the System and AlignSystem values if necessary */
+   if( resetSystem ) {
+      astSetSystem( template, new_system );
+      astSetAlignSystem( template, new_alignsystem );
+   }
+
+/* Check if the result Frame is a FluxFrame or from a class derived from
+   FluxFrame. If not, we cannot transfer FluxFrame attributes to it as it is
+   insufficiently specialised. In this case simply omit these attributes. */
+   if ( fluxframe && astOK ) {
+
+/* Define macros that test whether an attribute is set in the template and,
+   if so, transfers its value to the result. */
+#define OVERLAY(attribute) \
+   if ( astTest##attribute( template ) ) { \
+      astSet##attribute( result, astGet##attribute( template ) ); \
+   }
+
+/* Use the macro to transfer each FluxFrame attribute in turn. */
+      OVERLAY(SpecVal)
+
+
+   }
+
+/* Undefine macros local to this function. */
+#undef OVERLAY
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (extends the astSetAttrib method inherited from
+*     the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a FluxFrame, the attribute
+*     and its value being specified by means of a string of the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in lower
+*     case with no white space present. The value to the right of the "="
+*     should be a suitable textual representation of the value to be assigned
+*     and this will be interpreted according to the attribute's data type.
+*     White space surrounding the value is only significant for string
+*     attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This protected method is intended to be invoked by the Object astSet
+*     method and makes additional attributes accessible to it.
+*/
+
+/* Local Vaiables: */
+   AstFluxFrame *this;           /* Pointer to the FluxFrame structure */
+   double dval;                  /* Floating point attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* No. of characters read */
+   int ulen;                     /* Used length of setting string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Obtain the used length of the setting string. */
+   ulen = astChrLen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* SpecVal. */
+/* -------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "specval= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetSpecVal( this, dval );
+
+/* Pass any unrecognised setting to the parent method for further
+   interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SetSystem( AstFrame *this_frame, AstSystemType newsys, int *status ) {
+/*
+*  Name:
+*     SetSystem
+
+*  Purpose:
+*     Set the System attribute for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     void SetSystem( AstFrame *this_frame, AstSystemType newsys, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astSetSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function sets the System attribute for a FluxFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     newsys
+*        The new System value to be stored.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to FluxFrame structure */
+   AstSystemType oldsys;         /* Original System value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_frame;
+
+/* Save the original System value */
+   oldsys = astGetSystem( this_frame );
+
+/* Use the parent SetSystem method to store the new System value. */
+   (*parent_setsystem)( this_frame, newsys, status );
+
+/* If the system has changed... */
+   if( oldsys != newsys ) {
+
+/* Changing the System value will in general require the Units to change
+   as well. If the user has previously specified the units to be used with
+   the new system, then re-instate them (they are stored in the "usedunits"
+   array in the FluxFrame structure). Otherwise, clear the units so that
+   the default units will eb used with the new System. */
+      if( (int) newsys < this->nuunits && this->usedunits &&
+          this->usedunits[ (int) newsys ] ) {
+         (*parent_setunit)( this_frame, 0, this->usedunits[ (int) newsys ], status );
+      } else {
+         (*parent_clearunit)( this_frame, 0, status );
+      }
+
+/* Also, clear all attributes which have system-specific defaults. */
+      astClearLabel( this_frame, 0 );
+      astClearSymbol( this_frame, 0 );
+      astClearTitle( this_frame );
+   }
+}
+
+static void SetUnit( AstFrame *this_frame, int axis, const char *value, int *status ) {
+/*
+*  Name:
+*     SetUnit
+
+*  Purpose:
+*     Set a pointer to the Unit string for a FluxFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     void SetUnit( AstFrame *this_frame, int axis, const char *value )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astSetUnit method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function stores a pointer to the Unit string for a specified axis
+*     of a FluxFrame. It also stores the string in the "usedunits" array
+*     in the FluxFrame structure, in the element associated with the
+*     current System.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     axis
+*        The number of the axis (zero-based) for which information is required.
+*     unit
+*        The new string to store.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to the FluxFrame structure */
+   AstSystemType system;         /* The FluxFrame's System value */
+   int i;                        /* Loop counter */
+   int isystem;                  /* The FluxFrame's System value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Use the parent SetUnit method to store the value in the Axis
+   structure */
+   (*parent_setunit)( this_frame, axis, value, status );
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_frame;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astSetUnit" );
+
+/* If the new units are appropriate for the current System, store the
+   supplied value as the UsedUnit for the current System. First ensure the
+   array is big enough. Free any previous value stored for the current
+   system. */
+   system = astGetSystem( this );
+   if( UnitsOK( system, value, 0, "astSetUnit", astGetClass( this ), status ) ) {
+      isystem = (int) astGetSystem( this );
+      if( isystem >= this->nuunits ) {
+         this->usedunits = astGrow( this->usedunits, isystem + 1,
+                                    sizeof(char *) );
+         if( astOK ) {
+            for( i = this->nuunits; i < isystem + 1; i++ ) this->usedunits[ i ] = NULL;
+            this->nuunits = isystem + 1;
+         }
+      }
+
+/* Now store a copy of the value, if it is different to the stored string. */
+      if( astOK && ( !this->usedunits[ isystem ] ||
+                     strcmp( this->usedunits[ isystem ], value ) ) ) {
+         this->usedunits[ isystem ] = astStore( this->usedunits[ isystem ],
+                                               value, strlen( value ) + 1 );
+      }
+
+/* If the new units are not appropriate for the current System, clear the
+   System value. Use the parent ClearSystem function since the
+   astClearSystem implemented by this class will clear the units. */
+   } else {
+      (*parent_clearsystem)( this_frame, status );
+   }
+
+}
+
+static int SubFrame( AstFrame *target_frame, AstFrame *template,
+                     int result_naxes, const int *target_axes,
+                     const int *template_axes, AstMapping **map,
+                     AstFrame **result, int *status ) {
+/*
+*  Name:
+*     SubFrame
+
+*  Purpose:
+*     Select axes from a FluxFrame and convert to the new coordinate
+*     system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     int SubFrame( AstFrame *target, AstFrame *template,
+*                   int result_naxes, const int *target_axes,
+*                   const int *template_axes, AstMapping **map,
+*                   AstFrame **result, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the protected astSubFrame
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function selects a requested sub-set (or super-set) of the axes
+*     from a "target" FluxFrame and creates a new Frame with copies of
+*     the selected axes assembled in the requested order. It then
+*     optionally overlays the attributes of a "template" Frame on to the
+*     result. It returns both the resulting Frame and a Mapping that
+*     describes how to convert between the coordinate systems described by
+*     the target and result Frames. If necessary, this Mapping takes
+*     account of any differences in the Frames' attributes due to the
+*     influence of the template.
+
+*  Parameters:
+*     target
+*        Pointer to the target FluxFrame, from which axes are to be
+*        selected.
+*     template
+*        Pointer to the template Frame, from which new attributes for the
+*        result Frame are to be obtained. Optionally, this may be NULL, in
+*        which case no overlaying of template attributes will be performed.
+*     result_naxes
+*        Number of axes to be selected from the target Frame. This number may
+*        be greater than or less than the number of axes in this Frame (or
+*        equal).
+*     target_axes
+*        Pointer to an array of int with result_naxes elements, giving a list
+*        of the (zero-based) axis indices of the axes to be selected from the
+*        target FluxFrame. The order in which these are given determines
+*        the order in which the axes appear in the result Frame. If any of the
+*        values in this array is set to -1, the corresponding result axis will
+*        not be derived from the target Frame, but will be assigned default
+*        attributes instead.
+*     template_axes
+*        Pointer to an array of int with result_naxes elements. This should
+*        contain a list of the template axes (given as zero-based axis indices)
+*        with which the axes of the result Frame are to be associated. This
+*        array determines which axes are used when overlaying axis-dependent
+*        attributes of the template on to the result. If any element of this
+*        array is set to -1, the corresponding result axis will not receive any
+*        template attributes.
+*
+*        If the template argument is given as NULL, this array is not used and
+*        a NULL pointer may also be supplied here.
+*     map
+*        Address of a location to receive a pointer to the returned Mapping.
+*        The forward transformation of this Mapping will describe how to
+*        convert coordinates from the coordinate system described by the target
+*        FluxFrame to that described by the result Frame. The inverse
+*        transformation will convert in the opposite direction.
+*     result
+*        Address of a location to receive a pointer to the result Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if coordinate conversion is possible
+*     between the target and the result Frame. Otherwise zero is returned and
+*     *map and *result are returned as NULL (but this will not in itself
+*     result in an error condition). In general, coordinate conversion should
+*     always be possible if no template Frame is supplied but may not always
+*     be possible otherwise.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+
+*  Implementation Notes:
+*     -  This implementation addresses the selection of axes from a
+*     FluxFrame object. This results in another object of the same class
+*     only if the single FluxFrame axis is selected exactly once.
+*     Otherwise, the result is a Frame class object which inherits the
+*     FluxFrame's axis information (if appropriate) but none of the other
+*     properties of a FluxFrame.
+*     -  In the event that a FluxFrame results, the returned Mapping will
+*     take proper account of the relationship between the target and result
+*     coordinate systems.
+*     -  In the event that a Frame class object results, the returned Mapping
+*     will only represent a selection/permutation of axes.
+
+*  Implementation Deficiencies:
+*     -  Any axis selection is currently permitted. Probably this should be
+*     restricted so that each axis can only be selected once. The
+*     astValidateAxisSelection method will do this but currently there are bugs
+*     in the CmpFrame class that cause axis selections which will not pass this
+*     test. Install the validation when these are fixed.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *target;      /* Pointer to the FluxFrame structure */
+   AstFluxFrame *temp;        /* Pointer to copy of target FluxFrame */
+   AstSystemType align_sys;   /* System in which to align the FluxFrames */
+   int match;                 /* Coordinate conversion is possible? */
+   int report;                /* Report errors if FluxFrames cannot be aligned? */
+
+/* Initialise the returned values. */
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the target FluxFrame structure. */
+   target = (AstFluxFrame *) target_frame;
+
+/* Result is a FluxFrame. */
+/* -------------------------- */
+/* Check if the result Frame is to have one axis obtained by selecting
+   the single target FluxFrame axis. If so, the result will also be
+   a FluxFrame. */
+   if ( ( result_naxes == 1 ) && ( target_axes[ 0 ] == 0 ) ) {
+
+/* Form the result from a copy of the target. */
+      *result = astCopy( target );
+
+/* Initialise a flag to indicate that MakeFluxMapping should not report
+   errors if no Mapping can be created. */
+      report = 0;
+
+/* If required, overlay the template attributes on to the result FluxFrame.
+   Also get the system in which to align the two FluxFrames. These are the
+   values from the template (if there is a template). */
+      if ( template ) {
+         astOverlay( template, template_axes, *result );
+         if( astIsAFluxFrame( template ) ) {
+            align_sys = astGetAlignSystem( template );
+
+/* Since we now know that both the template and target are FluxFrames, it
+   should usually be possible to convert betwen them. If conversion is
+   *not* possible then the user will probably be interested in knowing the
+   reason why conversion is not possible. Therefore, indicate that
+   MakeFluxMapping should report errors if no Mapping can be created. */
+            report = 1;
+
+         } else {
+            align_sys = astGetAlignSystem( target );
+         }
+
+/* If no template was supplied, align in the System of the target. */
+      } else {
+         align_sys = astGetSystem( target );
+      }
+
+/* Generate a Mapping that takes account of changes in the coordinate system
+   between the target FluxFrame and the result FluxFrame. If this Mapping can
+   be generated, set "match" to indicate that coordinate conversion is
+   possible. If the template is a fluxframe, report errors if a match is not
+   possible. */
+      match = ( MakeFluxMapping( target, (AstFluxFrame *) *result,
+                align_sys, map, status ) != 0 );
+
+/* Result is not a FluxFrame. */
+/* ------------------------------ */
+/* In this case, we select axes as if the target were from the Frame
+   class.  However, since the resulting data will then be separated
+   from their enclosing FluxFrame, default attribute values may differ
+   if the methods for obtaining them were over-ridden by the FluxFrame
+   class. To overcome this, we ensure that these values are explicitly
+   set for the result Frame (rather than relying on their defaults). */
+   } else {
+
+/* Make a temporary copy of the target FluxFrame. We will explicitly
+   set the attribute values in this copy so as not to modify the original. */
+      temp = astCopy( target );
+
+/* Define a macro to test if an attribute is set. If not, set it
+   explicitly to its default value. */
+#define SET(attribute) \
+   if ( !astTest##attribute( temp ) ) { \
+      astSet##attribute( temp, astGet##attribute( temp ) ); \
+   }
+
+/* Set attribute values which apply to the Frame as a whole and which
+   we want to retain, but whose defaults are over-ridden by the
+   FluxFrame class. */
+      SET(Domain)
+      SET(Title)
+
+/* Define a macro to test if an attribute is set for axis zero (the only
+   axis of a FluxFrame). If not, set it explicitly to its default value. */
+#define SET_AXIS(attribute) \
+   if ( !astTest##attribute( temp, 0 ) ) { \
+      astSet##attribute( temp, 0, \
+                         astGet##attribute( temp, 0 ) ); \
+   }
+
+/* Use this macro to set explicit values for all the axis attributes
+   for which the FluxFrame class over-rides the default value. */
+      SET_AXIS(Label)
+      SET_AXIS(Symbol)
+      SET_AXIS(Unit)
+
+/* Clear attributes which have an extended range of values allowed by
+   this class. */
+      astClearSystem( temp );
+      astClearAlignSystem( temp );
+
+/* Invoke the astSubFrame method inherited from the Frame class to
+   produce the result Frame by selecting the required set of axes and
+   overlaying the template Frame's attributes. */
+      match = (*parent_subframe)( (AstFrame *) temp, template,
+                                  result_naxes, target_axes, template_axes,
+                                  map, result, status );
+
+/* Delete the temporary copy of the target FluxFrame. */
+      temp = astDelete( temp );
+   }
+
+/* If an error occurred or no match was found, annul the returned
+   objects and reset the returned result. */
+   if ( !astOK || !match ) {
+      if( *map ) *map = astAnnul( *map );
+      if( *result ) *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+
+/* Undefine macros local to this function. */
+#undef SET
+#undef SET_AXIS
+}
+
+static AstSystemType SystemCode( AstFrame *this, const char *system, int *status ) {
+/*
+*  Name:
+*     SystemCode
+
+*  Purpose:
+*     Convert a string into a coordinate system type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     AstSystemType SystemCode( AstFrame *this, const char *system, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astSystemCode method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a string used for the external
+*     description of a coordinate system into a FluxFrame
+*     coordinate system type code (System attribute value). It is the
+*     inverse of the astSystemString function.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     system
+*        Pointer to a constant null-terminated string containing the
+*        external description of the sky coordinate system.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System type code.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM is returned if the sky coordinate
+*     system description was not recognised. This does not produce an
+*     error.
+*     - A value of AST__BADSYSTEM is also returned if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;      /* Result value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" string against each possibility and assign the
+   result. */
+   if ( astChrMatch( "FLXDN", system ) ) {
+      result = AST__FLUXDEN;
+
+   } else if ( astChrMatch( "FLXDNW", system ) ) {
+      result = AST__FLUXDENW;
+
+   }else if ( astChrMatch( "SFCBR", system ) ) {
+      result = AST__SBRIGHT;
+
+   } else if ( astChrMatch( "SRCBR", system ) ) {
+      result = AST__SBRIGHTW;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *SystemLabel( AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SystemLabel
+
+*  Purpose:
+*     Return a label for a coordinate system type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *SystemLabel( AstSystemType system, int *status )
+
+*  Class Membership:
+*     FluxFrame member function.
+
+*  Description:
+*     This function converts a FluxFrame coordinate system type code
+*     (System attribute value) into a descriptive string for human readers.
+
+*  Parameters:
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the sky coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" value against each possibility and convert to a
+   string pointer. */
+   switch ( system ) {
+
+   case AST__FLUXDEN:
+      result = "flux density";
+      break;
+
+   case AST__FLUXDENW:
+      result = "flux wavelength density";
+      break;
+
+   case AST__SBRIGHT:
+      result = "surface brightness";
+      break;
+
+   case AST__SBRIGHTW:
+      result = "surface brightness (per wavelength)";
+      break;
+
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static const char *SystemString( AstFrame *this, AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SystemString
+
+*  Purpose:
+*     Convert a coordinate system type code into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     const char *SystemString( AstFrame *this, AstSystemType system, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astSystemString method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a FluxFrame coordinate system type code
+*     (System attribute value) into a string suitable for use as an
+*     external representation of the coordinate system type.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+   return FluxSystemString( system, status );
+}
+
+static int TestActiveUnit( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     TestActiveUnit
+
+*  Purpose:
+*     Test the ActiveUnit flag for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     int TestActiveUnit( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astTestActiveUnit protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function test the value of the ActiveUnit flag for a FluxFrame,
+*    which is always "unset".
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The result of the test (0).
+
+*/
+   return 0;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a FluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astTestAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a FluxFrame's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to the FluxFrame structure */
+   int len;                      /* Length of attrib string */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* SpecVal. */
+/* -------- */
+   if ( !strcmp( attrib, "specval" ) ) {
+      result = astTestSpecVal( this );
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static int UnitsOK( AstSystemType system, const char *units, int report,
+                    const char *method, const char *class, int *status ) {
+/*
+*  Name:
+*     UnitsOK
+
+*  Purpose:
+*     Check if a units string is appropriate for the current System.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     int UnitsOK( AstSystemType system, const char *units, int report,
+*                  const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     FluxFrame member function
+
+*  Description:
+*     This function returns a non-zero value if the supplied units string
+*     can be mapped to the defaultunits for the current System in the
+*     supplied FluxFrame.
+
+*  Parameters:
+*     system
+*        The system type to check.
+*     unit
+*        The units string to check.
+*     report
+*        Should an error be reported if the units and system are
+*        inconsistent?
+*     method
+*        String holding a method name to be used in error messages.
+*     class
+*        String holding a class name to be used in error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns Value:
+*     Non-zero if the units string can be used to describe the current
+*     flux System. Zero otherwise.
+
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   int result;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get the Mapping from the default units for the supplied system to the
+   supplied Units. */
+   map = astUnitMapper( DefUnit( system, method, class, status ), units, NULL, NULL );
+
+/* If a Mapping was found succesfully, annul it and return non-zero.
+   Otherwise return zero. */
+   if( map ) {
+      result = 1;
+      map = astAnnul( map );
+
+   } else {
+      result = 0;
+
+/* Report an error if required. */
+      if( report && astOK ) {
+         astError( AST__BADUN, "%s(%s): The units (%s) and system (%s) "
+                   "within the supplied %s are inconsistent.", status, method,
+                   class, units, FluxSystemString( system, status ), class );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int ValidateSystem( AstFrame *this, AstSystemType system, const char *method, int *status ) {
+/*
+*
+*  Name:
+*     ValidateSystem
+
+*  Purpose:
+*     Validate a value for a Frame's System attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     int ValidateSystem( AstFrame *this, AstSystemType system,
+*                         const char *method, int *status )
+
+*  Class Membership:
+*     FluxFrame member function (over-rides the astValidateSystem method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of the supplied system value.
+*     If the value is valid, it is returned unchanged. Otherwise, an
+*     error is reported and a value of AST__BADSYSTEM is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The system value to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The validated system value.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;              /* Validated system value */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the value is out of bounds, report an error. */
+   if ( system < FIRST_SYSTEM || system > LAST_SYSTEM ) {
+         astError( AST__AXIIN, "%s(%s): Bad value (%d) given for the System "
+                   "or AlignSystem attribute of a %s.", status, method,
+                   astGetClass( this ), (int) system, astGetClass( this ) );
+
+/* Otherwise, return the supplied value. */
+   } else {
+      result = system;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/*
+*att++
+*  Name:
+*     SpecVal
+
+*  Purpose:
+*     The spectral position at which flux values are measured.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute specifies the spectral position (frequency, wavelength,
+*     etc.), at which the values described by the FluxFrame are measured.
+*     It is used when determining the Mapping between between FluxFrames.
+*
+*     The default value and spectral system used for this attribute are
+*     both specified when the FluxFrame is created.
+
+*  Applicability:
+*     FluxFrame
+*        All FluxFrames have this attribute.
+
+*att--
+*/
+astMAKE_CLEAR(FluxFrame,SpecVal,specval,AST__BAD)
+astMAKE_GET(FluxFrame,SpecVal,double,AST__BAD,((this->specval!=AST__BAD)?this->specval:this->defspecval))
+astMAKE_SET(FluxFrame,SpecVal,double,specval,value)
+astMAKE_TEST(FluxFrame,SpecVal,( this->specval != AST__BAD ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for FluxFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for FluxFrame objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *in;             /* Pointer to input FluxFrame */
+   AstFluxFrame *out;            /* Pointer to output FluxFrame */
+   char *usedunit;               /* Pointer to an element of usedunits array */
+   int i;                        /* Loop count */
+   int nused;                    /* Size of "usedunits" array */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output FluxFrames. */
+   in = (AstFluxFrame *) objin;
+   out = (AstFluxFrame *) objout;
+
+/* Nullify the pointers stored in the output object since these will
+   currently be pointing at the input data (since the output is a simple
+   byte-for-byte copy of the input). Otherwise, the input data could be
+   freed by accidient if the output object is deleted due to an error
+   occuring in this function. */
+   out->usedunits = NULL;
+   out->specframe = NULL;
+
+/* Store the last used units in the output SpecMap. */
+   if( in && in->usedunits ) {
+      nused = in->nuunits;
+      out->usedunits = astMalloc( nused*sizeof( char * ) );
+      if( out->usedunits ) {
+         for( i = 0; i < nused; i++ ) {
+            usedunit = in->usedunits[ i ];
+            if( usedunit ) {
+               out->usedunits[ i ] = astStore( NULL, usedunit,
+                                               strlen( usedunit ) + 1 );
+            } else {
+               out->usedunits[ i ] = NULL;
+            }
+         }
+      }
+   }
+
+/* Copy the SpecFrame */
+   if( in->specframe ) out->specframe = astCopy( in->specframe );
+
+/* If an error has occurred, free the output resources. */
+   if( !astOK ) Delete( (AstObject *) out, status );
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for FluxFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for FluxFrame objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;
+   int i;
+
+/* Release the memory referred to in the FluxFrame structure. */
+   this = (AstFluxFrame *) obj;
+   if( this && this->usedunits ) {
+      for( i = 0; i < this->nuunits; i++ ) {
+         this->usedunits[ i ] = astFree( this->usedunits[ i ] );
+      }
+      this->usedunits = astFree( this->usedunits );
+   }
+
+/* Annulthe SpecFrame. */
+   if( this->specframe ) this->specframe = astAnnul( this->specframe );
+
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for FluxFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the FluxFrame class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the FluxFrame whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *this;           /* Pointer to the FluxFrame structure */
+   char buff[ 20 ];              /* Buffer for item name */
+   char comm[ 50 ];              /* Buffer for comment */
+   double dval;                  /* Double value */
+   int i;                        /* Loop count */
+   int j;                        /* Loop count */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FluxFrame structure. */
+   this = (AstFluxFrame *) this_object;
+
+/* Write out values representing the instance variables for the
+   FluxFrame class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* SpecVal. */
+/* -------- */
+   set = TestSpecVal( this, status );
+   dval = set ? GetSpecVal( this, status ) : astGetSpecVal( this );
+   if( dval != AST__BAD ) {
+      astWriteDouble( channel, "SpcVl", set, 0, dval, "Spectral position" );
+   }
+
+/* The SpecFrame */
+/* ------------- */
+   if( this->specframe ) {
+      astWriteObject( channel, "SpcFr", 1, 0, this->specframe, "SpcVl coord system" );
+   }
+
+/* Default SpecVal. */
+/* ---------------- */
+   if( this->defspecval != AST__BAD ) {
+      astWriteDouble( channel, "DfSpc", 1, 0, this->defspecval, "Default spectral position" );
+   }
+
+/* UsedUnits */
+/* --------- */
+   if( this->usedunits ) {
+      for( i = 0; i < this->nuunits; i++ ) {
+         if( this->usedunits[ i ] ) {
+            sprintf( buff, "U%s", astSystemString( this, (AstSystemType) i ));
+            for( j = 2; j < strlen( buff ); j++ ) buff[ j ] = tolower( buff[ j ] );
+            sprintf( comm, "Preferred units for %s", SystemLabel( (AstSystemType) i, status ) );
+            astWriteString( channel, buff, 1, 0, this->usedunits[ i ], comm );
+         }
+      }
+   }
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAFluxFrame and astCheckFluxFrame functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(FluxFrame,Frame)
+astMAKE_CHECK(FluxFrame)
+
+AstFluxFrame *astFluxFrame_( double specval, void *specfrm_void,
+                             const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astFluxFrame
+
+*  Purpose:
+*     Create a FluxFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     AstFluxFrame *astFluxFrame( double specval, AstSpecFrame *specfrm,
+*                                 const char *options, ..., int *status )
+
+*  Class Membership:
+*     FluxFrame constructor.
+
+*  Description:
+*     This function creates a new FluxFrame and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     specval
+*        The spectral value to which the flux values refer, given in the
+*        spectral coordinate system specified by "specfrm". The value
+*        supplied for the "specval" parameter becomes the default value for
+*        the SpecVal attribute.
+*     specfrm
+*        A pointer to a SpecFrame describing the spectral coordinate system
+*        in which the "specval" parameter is given. A deep copy of this object
+*        is taken, so any subsequent changes to the SpecFrame using the
+*        supplied pointer will have no effect on the new FluxFrame.
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new FluxFrame. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new FluxFrame.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic FluxFrame constructor which
+*     is available via the protected interface to the FluxFrame class.
+*     A public interface is provided by the astFluxFrameId_ function.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMapping *um;               /* Mapping from default to actual units */
+   AstFluxFrame *new;            /* Pointer to new FluxFrame */
+   AstSpecFrame *sfrm;           /* Pointer to SpecFrame */
+   AstSystemType s;              /* System */
+   const char *u;                /* Units string */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the SpecFrame structures provided. */
+   sfrm = specfrm_void ? astCheckSpecFrame( specfrm_void ) : NULL;
+
+/* Initialise the FluxFrame, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitFluxFrame( NULL, sizeof( AstFluxFrame ), !class_init,
+                           &class_vtab, "FluxFrame", specval, sfrm );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new FluxFrame's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* Check the Units are appropriate for the System. */
+      u = astGetUnit( new, 0 );
+      s = astGetSystem( new );
+      um = astUnitMapper( DefUnit( s, "astFluxFrame", "FluxFrame", status ),
+                          u, NULL, NULL );
+      if( um ) {
+         um = astAnnul( um );
+      } else {
+         astError( AST__BADUN, "astFluxFrame: Inappropriate units (%s) "
+                   "specified for a %s axis.", status, u, SystemLabel( s, status ) );
+      }
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new FluxFrame. */
+   return new;
+}
+
+AstFluxFrame *astInitFluxFrame_( void *mem, size_t size, int init,
+                                 AstFluxFrameVtab *vtab, const char *name,
+                                 double specval, AstSpecFrame *specfrm, int *status ) {
+/*
+*+
+*  Name:
+*     astInitFluxFrame
+
+*  Purpose:
+*     Initialise a FluxFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     AstFluxFrame *astInitFluxFrame( void *mem, size_t size, int init,
+*                                     AstFrameVtab *vtab, const char *name,
+*                                     double specval, AstSpecFrame *specfrm)
+
+*  Class Membership:
+*     FluxFrame initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new FluxFrame object. It allocates memory (if
+*     necessary) to accommodate the FluxFrame plus any additional data
+*     associated with the derived class. It then initialises a
+*     FluxFrame structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual function
+*     table for a FluxFrame at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the FluxFrame is to be
+*	created. This must be of sufficient size to accommodate the
+*	FluxFrame data (sizeof(FluxFrame)) plus any data used by
+*	the derived class. If a value of NULL is given, this function
+*	will allocate the memory itself using the "size" parameter to
+*	determine its size.
+*     size
+*        The amount of memory used by the FluxFrame (plus derived
+*	class data). This will be used to allocate memory if a value of
+*	NULL is given for the "mem" parameter. This value is also stored
+*	in the FluxFrame structure, so a valid value must be supplied
+*	even if not required for allocating memory.
+*     init
+*        A logical flag indicating if the FluxFrame's virtual function
+*	table is to be initialised. If this value is non-zero, the
+*	virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*	associated with the new FluxFrame.
+*     name
+*        Pointer to a constant null-terminated character string which
+*	contains the name of the class to which the new object belongs
+*	(it is this pointer value that will subsequently be returned by
+*	the astGetClass method).
+*     specval
+*        The spectral value to which the flux values refer, given in the
+*        spectral coordinate system specified by "specfrm". The value
+*        supplied for the "specval" parameter becomes the default value for
+*        the SpecVal attribute. May be AST__BAD.
+*     specfrm
+*        A pointer to a SpecFrame describing the spectral coordinate system
+*        in which the "specval" parameter is given. A deep copy of this object
+*        is taken, so any subsequent changes to the SpecFrame using the
+*        supplied pointer will have no effect on the new FluxFrame. Should
+*        be NULL if "specval" is AST__BAD.
+
+*  Returned Value:
+*     A pointer to the new FluxFrame.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFluxFrame *new;        /* Pointer to the new FluxFrame */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitFluxFrameVtab( vtab, name );
+
+/* Initialise a 1D Frame structure (the parent class) as the first component
+   within the FluxFrame structure, allocating memory if necessary. */
+   new = (AstFluxFrame *) astInitFrame( mem, size, 0,
+                                        (AstFrameVtab *) vtab, name, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the FluxFrame data. */
+/* ----------------------------- */
+/* Initialise all attributes to their "undefined" values. */
+      new->specval = AST__BAD;
+      new->defspecval = specval;
+      new->specframe = specfrm ? astCopy( specfrm ) : NULL;
+      new->nuunits = 0;
+      new->usedunits = NULL;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstFluxFrame *astLoadFluxFrame_( void *mem, size_t size, AstFluxFrameVtab *vtab,
+                                 const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadFluxFrame
+
+*  Purpose:
+*     Load a FluxFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "fluxframe.h"
+*     AstFluxFrame *astLoadFluxFrame( void *mem, size_t size, AstFluxFrameVtab *vtab,
+*                                      const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     FluxFrame loader.
+
+*  Description:
+*     This function is provided to load a new FluxFrame using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     FluxFrame structure in this memory, using data read from the
+*     input Channel.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the FluxFrame is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        FluxFrame data (sizeof(FluxFrame)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the FluxFrame (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the FluxFrame structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstFluxFrame) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new FluxFrame. If this is NULL, a pointer
+*        to the (static) virtual function table for the FluxFrame class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "FluxFrame" is used instead.
+
+*  Returned Value:
+*     A pointer to the new FluxFrame.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFluxFrame *new;            /* Pointer to the new FluxFrame */
+   char buff[ 20 ];              /* Buffer for item name */
+   char *sval;                   /* Pointer to string value */
+   int i;                        /* Loop count */
+   int j;                        /* Get a pointer to the thread specific global data structure. */
+
+/* Loop count */
+   int sys;                      /* System value */
+
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this FluxFrame. In this case the
+   FluxFrame belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstFluxFrame );
+      vtab = &class_vtab;
+      name = "FluxFrame";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitFluxFrameVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built FluxFrame. */
+   new = astLoadFrame( mem, size, (AstFrameVtab *) vtab, name,
+                       channel );
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+       astReadClassData( channel, "FluxFrame" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Default SpecVal */
+/* --------------- */
+      new->defspecval = astReadDouble( channel, "dfspc", AST__BAD );
+
+/* SpecFrame  */
+/* ---------- */
+      new->specframe = astReadObject( channel, "spcfr", NULL );
+
+/* SpecVal */
+/* ------- */
+      new->specval = astReadDouble( channel, "spcvl", AST__BAD );
+      if ( TestSpecVal( new, status ) ) SetSpecVal( new, new->specval, status );
+
+/* UsedUnits */
+/* --------- */
+      new->nuunits = 0;
+      new->usedunits = NULL;
+      for( sys = FIRST_SYSTEM; sys <= LAST_SYSTEM; sys++ ) {
+         sprintf( buff, "u%s", astSystemString( new, (AstSystemType) sys ));
+         for( j = 0; j < strlen( buff ); j++ ) buff[ j ] = tolower( buff[ j ] );
+         sval = astReadString( channel, buff, NULL );
+         if( sval ) {
+            if( (int) sys >= new->nuunits ) {
+               new->usedunits = astGrow( new->usedunits, sys + 1,
+                                          sizeof(char *) );
+               if( astOK ) {
+                  for( i = new->nuunits; i < sys + 1; i++ ) new->usedunits[ i ] = NULL;
+                  new->nuunits = sys + 1;
+               }
+            } else {
+               new->usedunits[ sys ] = astFree( new->usedunits[ sys ] );
+            }
+            if( astOK ) {
+               new->usedunits[ sys ] = astStore( new->usedunits[ sys ],
+                                                 sval, strlen( sval ) + 1 );
+            }
+            sval = astFree( sval );
+         }
+      }
+
+/* If an error occurred, clean up by deleting the new FluxFrame. */
+       if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new FluxFrame pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+AstSystemType astGetDensitySystem_( AstFluxFrame *this, int *status ){
+   if ( !astOK ) return AST__BADSYSTEM;
+   return (**astMEMBER(this,FluxFrame,GetDensitySystem))(this, status );
+}
+
+const char *astGetDensityUnit_( AstFluxFrame *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,FluxFrame,GetDensityUnit))(this, status );
+}
+
+
+/* Special public interface functions. */
+/* =================================== */
+/* These provide the public interface to certain special functions
+   whose public interface cannot be handled using macros (such as
+   astINVOKE) alone. In general, they are named after the
+   corresponding protected version of the function, but with "Id"
+   appended to the name. */
+
+/* Public Interface Function Prototypes. */
+/* ------------------------------------- */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstFluxFrame *astFluxFrameId_( double, void *, const char *, ... );
+
+/* Special interface function implementations. */
+/* ------------------------------------------- */
+AstFluxFrame *astFluxFrameId_( double specval, void *specfrm_void,
+                               const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astFluxFrame
+f     AST_FLUXFRAME
+
+*  Purpose:
+*     Create a FluxFrame.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "fluxframe.h"
+c     AstFluxFrame *astFluxFrame( double specval, AstSpecFrame *specfrm,
+c                                 const char *options, ... )
+f     RESULT = AST_FLUXFRAME( SPECVAL, SPECFRM, OPTIONS, STATUS )
+
+*  Class Membership:
+*     FluxFrame constructor.
+
+*  Description:
+*     This function creates a new FluxFrame and optionally initialises
+*     its attributes.
+*
+*     A FluxFrame is a specialised form of one-dimensional Frame which
+*     represents various systems used to represent the signal level in an
+*     observation. The particular coordinate system to be used is specified
+*     by setting the FluxFrame's System attribute qualified, as necessary, by
+*     other attributes such as the units, etc (see the description of the
+*     System attribute for details).
+*
+*     All flux values are assumed to be measured at the same frequency or
+*     wavelength (as given by the SpecVal attribute). Thus this class is
+*     more appropriate for use with images rather than spectra.
+
+*  Parameters:
+c     specval
+f     SPECVAL = DOUBLE PRECISION (Given)
+*        The spectral value to which the flux values refer, given in the
+*        spectral coordinate system specified by
+c        "specfrm". The value supplied for the "specval"
+f        SPECFRM. The value supplied for the SPECVAL
+*        parameter becomes the default value for the SpecVal attribute.
+*        A value of AST__BAD may be supplied if the spectral position is
+*        unknown, but this may result in it not being possible for the
+c        astConvert
+f        AST_CONVERT
+*        function to determine a Mapping between the new FluxFrame and
+*        some other FluxFrame.
+c     specfrm
+f     SPECFRM = INTEGER (Given)
+*        A pointer to a SpecFrame describing the spectral coordinate system
+*        in which the
+c        "specval"
+f        SPECVAL
+*        parameter is given. A deep copy of this object is taken, so any
+*        subsequent changes to the SpecFrame using the supplied pointer will
+*        have no effect on the new FluxFrame.
+c        A NULL pointer can be supplied if AST__BAD is supplied for "specval".
+f        AST__NULL can be supplied if AST__BAD is supplied for SPECVAL.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new FluxFrame. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new FluxFrame. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astFluxFrame()
+f     AST_FLUXFRAME = INTEGER
+*        A pointer to the new FluxFrame.
+
+*  Notes:
+*     - When conversion between two FluxFrames is requested (as when
+c     supplying FluxFrames to astConvert),
+f     supplying FluxFrames AST_CONVERT),
+*     account will be taken of the nature of the flux coordinate systems
+*     they represent, together with any qualifying attribute values, including
+*     the AlignSystem attribute. The results will therefore fully reflect the
+*     relationship between positions measured in the two systems. In addition,
+*     any difference in the Unit attributes of the two systems will also be
+*     taken into account.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astFluxFrame constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astFluxFrame_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - The variable argument list also prevents this function from
+*     invoking astFluxFrame_ directly, so it must be a
+*     re-implementation of it in all respects, except for the final
+*     conversion of the result to an ID value.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMapping *um;               /* Mapping from default to actual units */
+   AstFluxFrame *new;            /* Pointer to new FluxFrame */
+   AstSpecFrame *sfrm;           /* Pointer to SpecFrame */
+   AstSystemType s;              /* System */
+   const char *u;                /* Units string */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the SpecFrame structures provided. */
+   sfrm = specfrm_void ? astCheckSpecFrame( astMakePointer( specfrm_void ) ) : NULL;
+
+/* Initialise the FluxFrame, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitFluxFrame( NULL, sizeof( AstFluxFrame ), !class_init,
+                           &class_vtab, "FluxFrame", specval, sfrm );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new FluxFrame's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* Check the Units are appropriate for the System. */
+      u = astGetUnit( new, 0 );
+      s = astGetSystem( new );
+      um = astUnitMapper( DefUnit( s, "astFluxFrame", "FluxFrame", status ),
+                          u, NULL, NULL );
+      if( um ) {
+         um = astAnnul( um );
+      } else {
+         astError( AST__BADUN, "astFluxFrame: Inappropriate units (%s) "
+                   "specified for a %s axis.", status, u, SystemLabel( s, status ) );
+      }
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new FluxFrame. */
+   return astMakeId( new );
+}
+
+
+
+
+
+
+
+
+
diff --git a/ast/fluxframe.h b/ast/fluxframe.h
new file mode 100644
index 0000000..c030142
--- /dev/null
+++ b/ast/fluxframe.h
@@ -0,0 +1,267 @@
+#if !defined( FLUXFRAME_INCLUDED ) /* Include this file only once */
+#define FLUXFRAME_INCLUDED
+/*
+*+
+*  Name:
+*     fluxframe.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the FluxFrame class.
+
+*  Invocation:
+*     #include "fluxframe.h"
+
+*  Description:
+*     This include file defines the interface to the FluxFrame class
+*     and provides the type definitions, function prototypes and
+*     macros, etc. needed to use this class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     1-DEC-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+#include "frame.h"               /* Parent Frame class */
+#include "specframe.h"           /* Spectral coordinate systems */
+
+/* Macros. */
+/* ======= */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Values used to represent different System attribute values. */
+#define AST__FLUXDEN       1
+#define AST__FLUXDENW      2
+#define AST__SBRIGHT       3
+#define AST__SBRIGHTW      4
+
+/* Define constants used to size global arrays in this module. */
+#define AST__FLUXFRAME_GETATTRIB_BUFF_LEN 50
+#define AST__FLUXFRAME_GETLABEL_BUFF_LEN 200
+#define AST__FLUXFRAME_GETSYMBOL_BUFF_LEN 20
+#define AST__FLUXFRAME_GETTITLE_BUFF_LEN 200
+
+#endif
+
+/* Type Definitions. */
+/* ================= */
+
+/* FluxFrame structure. */
+/* ------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstFluxFrame {
+
+/* Attributes inherited from the parent class. */
+   AstFrame frame;               /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double specval;               /* Spectral position */
+   double defspecval;            /* Default spectral position */
+   AstSpecFrame *specframe;       /* SpecFrame describing specval & defspecval */
+   int nuunits;                  /* Size of usedunits array */
+   char **usedunits;             /* Last used units for each system */
+} AstFluxFrame;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all objects in the
+   class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstFluxFrameVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstFrameVtab frame_vtab;      /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+   AstSystemType (* GetDensitySystem)( AstFluxFrame *, int * );
+   const char *(* GetDensityUnit)( AstFluxFrame *, int * );
+
+   double (* GetSpecVal)( AstFluxFrame *, int * );
+   int (* TestSpecVal)( AstFluxFrame *, int * );
+   void (* ClearSpecVal)( AstFluxFrame *, int * );
+   void (* SetSpecVal)( AstFluxFrame *, double, int * );
+
+} AstFluxFrameVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstFluxFrameGlobals {
+   AstFluxFrameVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ AST__FLUXFRAME_GETATTRIB_BUFF_LEN + 1 ];
+   char GetLabel_Buff[ AST__FLUXFRAME_GETLABEL_BUFF_LEN + 1 ];
+   char GetSymbol_Buff[ AST__FLUXFRAME_GETSYMBOL_BUFF_LEN + 1 ];
+   char GetTitle_Buff[ AST__FLUXFRAME_GETTITLE_BUFF_LEN + 1 ];
+} AstFluxFrameGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(FluxFrame)         /* Check class membership */
+astPROTO_ISA(FluxFrame)           /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+AstFluxFrame *astFluxFrame_( double, void *, const char *, int *, ...);
+#else
+AstFluxFrame *astFluxFrameId_( double, void *, const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstFluxFrame *astInitFluxFrame_( void *, size_t, int,
+                                 AstFluxFrameVtab *,
+                                 const char *, double, AstSpecFrame *, int * );
+
+/* Vtab initialiser. */
+void astInitFluxFrameVtab_( AstFluxFrameVtab *, const char *, int * );
+
+/* Loader. */
+AstFluxFrame *astLoadFluxFrame_( void *, size_t,
+                                 AstFluxFrameVtab *,
+                                 const char *, AstChannel *channel, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitFluxFrameGlobals_( AstFluxFrameGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+#if defined(astCLASS)            /* Protected */
+
+AstSystemType astGetDensitySystem_( AstFluxFrame *, int * );
+const char *astGetDensityUnit_( AstFluxFrame *, int * );
+
+double astGetSpecVal_( AstFluxFrame *, int * );
+int astTestSpecVal_( AstFluxFrame *, int * );
+void astClearSpecVal_( AstFluxFrame *, int * );
+void astSetSpecVal_( AstFluxFrame *, double, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckFluxFrame(this) astINVOKE_CHECK(FluxFrame,this,0)
+#define astVerifyFluxFrame(this) astINVOKE_CHECK(FluxFrame,this,1)
+
+/* Test class membership. */
+#define astIsAFluxFrame(this) astINVOKE_ISA(FluxFrame,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+#define astFluxFrame astINVOKE(F,astFluxFrame_)
+#else
+#define astFluxFrame astINVOKE(F,astFluxFrameId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitFluxFrame(mem,size,init,vtab,name,specval,specfrm) \
+astINVOKE(O,astInitFluxFrame_(mem,size,init,vtab,name,specval,astCheckSpecFrame(specfrm),STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitFluxFrameVtab(vtab,name) astINVOKE(V,astInitFluxFrameVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadFluxFrame(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadFluxFrame_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+
+/* None. */
+
+/* Interfaces to protected member functions. */
+/* ----------------------------------------- */
+/* Here we make use of astCheckFluxFrame to validate FluxFrame pointers
+   before use. This provides a contextual error report if a pointer to
+   the wrong sort of object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+
+#define astGetDensitySystem(this) astINVOKE(V,astGetDensitySystem_(astCheckFluxFrame(this),STATUS_PTR))
+#define astGetDensityUnit(this) astINVOKE(V,astGetDensityUnit_(astCheckFluxFrame(this),STATUS_PTR))
+
+#define astGetSpecVal(this) astINVOKE(V,astGetSpecVal_(astCheckFluxFrame(this),STATUS_PTR))
+#define astTestSpecVal(this) astINVOKE(V,astTestSpecVal_(astCheckFluxFrame(this),STATUS_PTR))
+#define astClearSpecVal(this) astINVOKE(V,astClearSpecVal_(astCheckFluxFrame(this),STATUS_PTR))
+#define astSetSpecVal(this,value) astINVOKE(V,astSetSpecVal_(astCheckFluxFrame(this),value,STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/fmapping.c b/ast/fmapping.c
new file mode 100644
index 0000000..16f2b75
--- /dev/null
+++ b/ast/fmapping.c
@@ -0,0 +1,771 @@
+/*
+*+
+*  Name:
+*     fmapping.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Mapping class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Mapping class.
+
+*  Routines Defined:
+*     AST_DECOMPOSE
+*     AST_INVERT
+*     AST_ISAMAPPING
+*     AST_LINEARMAPPING
+*     AST_REBIN<X>
+*     AST_REBINSEQ<X>
+*     AST_MAPBOX
+*     AST_MAPSPLIT
+*     AST_RATE
+*     AST_REMOVEREGIONS
+*     AST_RESAMPLE<X>
+*     AST_SIMPLIFY
+*     AST_TRAN1
+*     AST_TRAN2
+*     AST_TRANGRID
+*     AST_TRANN
+*     AST_RATE
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     11-JUL-1996 (RFWS):
+*        Original version.
+*     13-DEC-1996 (RFWS)
+*        Added AST_SIMPLIFY.
+*     28-MAY-1998 (RFWS):
+*        Added AST_MAPBOX.
+*     12-NOV-1998 (RFWS):
+*        Added AST_RESAMPLE<X>.
+*     22-NOV-2000 (DSB):
+*        Pass the "flags" argument by reference instead of by value in the
+*        MAKE_AST_RESAMPLE_UINTERP macro.
+*     9-JAN-2001 (DSB):
+*        Changed in and out arguments for TranN from type "double (*)[]"
+*        to "double *".
+*     26-SEP-2001 (DSB):
+*        Added AST_DECOMPOSE.
+*     16-JUL-2003 (DSB):
+*        Added AST_RATE.
+*     30-JUN-2005 (DSB):
+*        Added AST_REBIN<X>.
+*     1-SEP-2005 (DSB):
+*        Added AST_REBINSEQ<X>.
+*     8-MAR-2006 (DSB):
+*        Added AST_TRANGRID.
+*     5-MAY-2009 (DSB):
+*        Added AST_REMOVEREGIONS.
+*     4-MAY-2010 (DSB):
+*        Add support for AST__VARWGT flag to AST_REBINSEQ<X>.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "mapping.h"             /* C interface to the Mapping class */
+
+#include <stdint.h>
+
+/* Module Variables. */
+/* ================= */
+/* Pointer to user-supplied (FORTRAN 77) interpolation function for
+   use by AST_RESAMPLE<X>. */
+static void (* ast_resample_FINTERP)();
+
+/* Interpolation function interface. */
+/* ================================= */
+/* These functions are associated with allowing FORTRAN 77
+   implementations of interpolation functions to be passed to
+   AST_RESAMPLE<X> via the FORTRAN 77 interface and then to be invoked
+   when necessary by the C code in the main implementation of
+   astResample<X>. */
+
+/* Define a macro which defines an interface function called
+   ast_resample_uinterp<X> for a specific data type.
+
+   The resulting function has a suitable interface to allow it to be
+   passed as an interpolation function to the C interface of
+   astResample<X> in the case where the "interp" parameter is set to
+   AST__UINTERP. In turn, it invokes the equivalent user-supplied
+   FORTRAN 77 interpolation function, a pointer to which should
+   previously have been stored in the static variable
+   "ast_resample_FINTERP". */
+#define MAKE_AST_RESAMPLE_UINTERP(X,Xtype,Ftype) \
+static void ast_resample_uinterp##X( int ndim, \
+                                     const int lbnd[], const int ubnd[], \
+                                     const Xtype in[], const Xtype in_var[], \
+                                     int npoint, const int offset[], \
+                                     const double *const coords[], \
+                                     const double params[], int flags, \
+                                     Xtype badval, \
+                                     Xtype *out, Xtype *out_var, \
+                                     int *nbad ) { \
+   DECLARE_INTEGER(STATUS); \
+   int *status; \
+\
+/* Get a pointer to the inherited staus value. */ \
+   status = astGetStatusPtr; \
+\
+/* Obtain the C status and then invoke the FORTRAN 77 interpolation \
+   function via the stored pointer. Note that the "coords" array we \
+   pass to FORTRAN has to be a contiguous 2-d array, so we must \
+   de-reference one level of pointer compared to the C case. */ \
+   STATUS = astStatus; \
+   ( *ast_resample_FINTERP )( INTEGER_ARG(&ndim), \
+                              INTEGER_ARRAY_ARG(lbnd), \
+                              INTEGER_ARRAY_ARG(ubnd), \
+                              Ftype##_ARRAY_ARG(in), \
+                              Ftype##_ARRAY_ARG(in_var), \
+                              INTEGER_ARG(&npoint), \
+                              INTEGER_ARRAY_ARG(offset), \
+                              DOUBLE_ARRAY_ARG(coords[ 0 ]), \
+                              DOUBLE_ARRAY_ARG(params), \
+                              INTEGER_ARG(&flags), \
+                              Ftype##_ARG(&badval), \
+                              Ftype##_ARRAY_ARG(out), \
+                              Ftype##_ARRAY_ARG(out_var), \
+                              INTEGER_ARG(nbad), \
+                              INTEGER_ARG(&STATUS) ); \
+\
+/* Set the C status to the returned FORTRAN 77 status. */ \
+   astSetStatus( STATUS ); \
+}
+
+/* Invoke the above macro to define an interface function for each
+   required data type. */
+MAKE_AST_RESAMPLE_UINTERP(D,double,DOUBLE)
+MAKE_AST_RESAMPLE_UINTERP(F,float,REAL)
+MAKE_AST_RESAMPLE_UINTERP(I,int,INTEGER)
+MAKE_AST_RESAMPLE_UINTERP(UI,unsigned int,INTEGER)
+MAKE_AST_RESAMPLE_UINTERP(K,INT_BIG,INTEGER8)
+MAKE_AST_RESAMPLE_UINTERP(UK,UINT_BIG,INTEGER8)
+MAKE_AST_RESAMPLE_UINTERP(S,short int,WORD)
+MAKE_AST_RESAMPLE_UINTERP(US,unsigned short int,UWORD)
+MAKE_AST_RESAMPLE_UINTERP(B,signed char,BYTE)
+MAKE_AST_RESAMPLE_UINTERP(UB,unsigned char,UBYTE)
+
+/* Undefine the macro. */
+#undef MAKE_AST_RESAMPLE_UINTERP
+
+/* Define a function called ast_resample_ukern1 which has a suitable
+   interface to allow it to be passed as an interpolation function to
+   the C interface of astResample<X> in the case where the "interp"
+   parameter is set to AST__UKERN1. In turn, it invokes the equivalent
+   user-supplied FORTRAN 77 interpolation function, a pointer to which
+   should previously have been stored in the static variable
+   "ast_resample_FINTERP". */
+static void ast_resample_ukern1( double offset, const double params[],
+                                 int flags, double *value ) {
+   DECLARE_INTEGER(STATUS);
+   int *status;
+
+/* Obtain the C status and then invoke the FORTRAN 77 interpolation
+   function via the stored pointer. */
+   status = astGetStatusPtr;
+   STATUS = astStatus;
+   ( *ast_resample_FINTERP )( DOUBLE_ARG(&offset),
+                              DOUBLE_ARRAY_ARG(params),
+                              INTEGER_ARG(&flags),
+                              DOUBLE_ARG(value),
+                              INTEGER_ARG(&STATUS) );
+
+/* Set the C status to the returned FORTRAN 77 status. */
+   astSetStatus( STATUS );
+}
+
+/* FORTRAN interface functions. */
+/* ============================ */
+/* These functions implement the remainder of the FORTRAN interface. */
+
+F77_SUBROUTINE(ast_decompose)( INTEGER(THIS),
+                               INTEGER(MAP1),
+                               INTEGER(MAP2),
+                               LOGICAL(SERIES),
+                               INTEGER(INVERT1),
+                               INTEGER(INVERT2),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(MAP1)
+   GENPTR_INTEGER(MAP2)
+   GENPTR_LOGICAL(SERIES)
+   GENPTR_INTEGER(INVERT1)
+   GENPTR_INTEGER(INVERT2)
+   AstMapping *map1;
+   AstMapping *map2;
+   int series;
+
+   astAt( "AST_DECOMPOSE", NULL, 0 );
+   astWatchSTATUS(
+      astDecompose( astI2P( *THIS ), &map1, &map2, &series, INVERT1, INVERT2 );
+      *MAP1 = astP2I( map1 );
+      *MAP2 = astP2I( map2 );
+      *SERIES = ( series )?F77_TRUE:F77_FALSE;
+   )
+}
+
+F77_SUBROUTINE(ast_invert)( INTEGER(THIS),
+                            INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_INVERT", NULL, 0 );
+   astWatchSTATUS(
+      astInvert( astI2P( *THIS ) );
+   )
+}
+
+F77_LOGICAL_FUNCTION(ast_isamapping)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAMAPPING", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAMapping( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_linearapprox)( INTEGER(THIS),
+                                        DOUBLE_ARRAY(LBND),
+                                        DOUBLE_ARRAY(UBND),
+                                        DOUBLE(TOL),
+                                        DOUBLE_ARRAY(FIT),
+                                        INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(LBND)
+   GENPTR_DOUBLE_ARRAY(UBND)
+   GENPTR_DOUBLE(TOL)
+   GENPTR_DOUBLE_ARRAY(FIT)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_LINEARAPPROX", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astLinearApprox( astI2P( *THIS ), LBND, UBND, *TOL, FIT );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_quadapprox)( INTEGER(THIS),
+                                      DOUBLE_ARRAY(LBND),
+                                      DOUBLE_ARRAY(UBND),
+                                      INTEGER(NX),
+                                      INTEGER(NY),
+                                      DOUBLE_ARRAY(FIT),
+                                      DOUBLE(RMS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(LBND)
+   GENPTR_DOUBLE_ARRAY(UBND)
+   GENPTR_INTEGER(NX)
+   GENPTR_INTEGER(NY)
+   GENPTR_DOUBLE_ARRAY(FIT)
+   GENPTR_DOUBLE(RMS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_QUADAPPROX", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astQuadApprox( astI2P( *THIS ), LBND, UBND, *NX, *NY, FIT, RMS );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_mapbox)( INTEGER(THIS),
+                            DOUBLE_ARRAY(LBND_IN),
+                            DOUBLE_ARRAY(UBND_IN),
+                            LOGICAL(FORWARD),
+                            INTEGER(COORD_OUT),
+                            DOUBLE(LBND_OUT),
+                            DOUBLE(UBND_OUT),
+                            DOUBLE_ARRAY(XL),
+                            DOUBLE_ARRAY(XU),
+                            INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(LBND_IN)
+   GENPTR_DOUBLE_ARRAY(UBND_IN)
+   GENPTR_LOGICAL(FORWARD)
+   GENPTR_INTEGER(COORD_OUT)
+   GENPTR_DOUBLE(LBND_OUT)
+   GENPTR_DOUBLE(UBND_OUT)
+   GENPTR_DOUBLE_ARRAY(XL)
+   GENPTR_DOUBLE_ARRAY(XU)
+   double lbnd_out;
+   double ubnd_out;
+
+   astAt( "AST_MAPBOX", NULL, 0 );
+   astWatchSTATUS(
+      astMapBox( astI2P( *THIS ), LBND_IN, UBND_IN, F77_ISTRUE( *FORWARD ),
+                 *COORD_OUT, &lbnd_out, &ubnd_out, XL, XU );
+      *LBND_OUT = lbnd_out;
+      *UBND_OUT = ubnd_out;
+   )
+}
+
+/* AST_RESAMPLE<X> requires a function for each possible data type, so
+   define it via a macro. */
+#define MAKE_AST_RESAMPLE(f,F,Ftype,X,Xtype) \
+F77_INTEGER_FUNCTION(ast_resample##f)( INTEGER(THIS), \
+                                       INTEGER(NDIM_IN), \
+                                       INTEGER_ARRAY(LBND_IN), \
+                                       INTEGER_ARRAY(UBND_IN), \
+                                       Ftype##_ARRAY(IN), \
+                                       Ftype##_ARRAY(IN_VAR), \
+                                       INTEGER(INTERP), \
+                                       void (* FINTERP)(), \
+                                       DOUBLE_ARRAY(PARAMS), \
+                                       INTEGER(FLAGS), \
+                                       DOUBLE(TOL), \
+                                       INTEGER(MAXPIX), \
+                                       Ftype(BADVAL), \
+                                       INTEGER(NDIM_OUT), \
+                                       INTEGER_ARRAY(LBND_OUT), \
+                                       INTEGER_ARRAY(UBND_OUT), \
+                                       INTEGER_ARRAY(LBND), \
+                                       INTEGER_ARRAY(UBND), \
+                                       Ftype##_ARRAY(OUT), \
+                                       Ftype##_ARRAY(OUT_VAR), \
+                                       INTEGER(STATUS) ) { \
+   GENPTR_INTEGER(THIS) \
+   GENPTR_INTEGER(NDIM_IN) \
+   GENPTR_INTEGER_ARRAY(LBND_IN) \
+   GENPTR_INTEGER_ARRAY(UBND_IN) \
+   GENPTR_##Ftype##_ARRAY(IN) \
+   GENPTR_##Ftype##_ARRAY(IN_VAR) \
+   GENPTR_INTEGER(INTERP) \
+   GENPTR_DOUBLE_ARRAY(PARAMS) \
+   GENPTR_INTEGER(FLAGS) \
+   GENPTR_DOUBLE(TOL) \
+   GENPTR_INTEGER(MAXPIX) \
+   GENPTR_##Ftype(BADVAL) \
+   GENPTR_INTEGER(NDIM_OUT) \
+   GENPTR_INTEGER_ARRAY(LBND_OUT) \
+   GENPTR_INTEGER_ARRAY(UBND_OUT) \
+   GENPTR_INTEGER_ARRAY(LBND) \
+   GENPTR_INTEGER_ARRAY(UBND) \
+   GENPTR_##Ftype##_ARRAY(OUT) \
+   GENPTR_##Ftype##_ARRAY(OUT_VAR) \
+   GENPTR_INTEGER(STATUS) \
+\
+   void (* finterp)(); \
+   Xtype *out_var; \
+   const Xtype *in_var; \
+   F77_INTEGER_TYPE RESULT; \
+\
+   astAt( "AST_RESAMPLE"#F, NULL, 0 ); \
+   astWatchSTATUS( \
+\
+/* If *INTERP is set to a value that requires a user-supplied \
+   interpolation function, then store a pointer to the supplied \
+   FORTRAN 77 version of this function and use the appropriate C \
+   wrapper function (defined above) to invoke it. */ \
+      if ( *INTERP == AST__UINTERP ) { \
+         ast_resample_FINTERP = FINTERP; \
+         finterp = (void (*)()) ast_resample_uinterp##X; \
+      } else if ( *INTERP == AST__UKERN1 ) { \
+         ast_resample_FINTERP = FINTERP; \
+         finterp = (void (*)()) ast_resample_ukern1; \
+      } else { \
+         ast_resample_FINTERP = NULL; \
+         finterp = NULL; \
+      } \
+\
+/* If the AST__USEVAR flag is set, use the input and output variance \
+   arrays, otherwise pass NULL pointers. */ \
+      in_var = out_var = NULL; \
+      if ( AST__USEVAR & *FLAGS ) { \
+         in_var = (const Xtype *) IN_VAR; \
+         out_var = (Xtype *) OUT_VAR; \
+      } \
+      RESULT = astResample##X( astI2P( *THIS ), *NDIM_IN, \
+                               LBND_IN, UBND_IN, (const Xtype *) IN, in_var, \
+                               *INTERP, finterp, PARAMS, *FLAGS, \
+                               *TOL, *MAXPIX, *BADVAL, \
+                               *NDIM_OUT, LBND_OUT, UBND_OUT, \
+                               LBND, UBND, (Xtype *) OUT, out_var ); \
+   ) \
+   return RESULT; \
+}
+
+/* Invoke the above macro to define a function for each data
+   type. Include synonyms for some functions. */
+MAKE_AST_RESAMPLE(d,D,DOUBLE,D,double)
+MAKE_AST_RESAMPLE(r,R,REAL,F,float)
+MAKE_AST_RESAMPLE(i,I,INTEGER,I,int)
+MAKE_AST_RESAMPLE(ui,UI,INTEGER,UI,unsigned int)
+MAKE_AST_RESAMPLE(k,K,INTEGER8,K,INT_BIG)
+MAKE_AST_RESAMPLE(uk,UK,INTEGER8,UK,UINT_BIG)
+MAKE_AST_RESAMPLE(s,S,WORD,S,short int)
+MAKE_AST_RESAMPLE(us,US,UWORD,US,unsigned short int)
+MAKE_AST_RESAMPLE(w,W,WORD,S,short int)
+MAKE_AST_RESAMPLE(uw,UW,UWORD,US,unsigned short int)
+MAKE_AST_RESAMPLE(b,B,BYTE,B,signed char)
+MAKE_AST_RESAMPLE(ub,UB,UBYTE,UB,unsigned char)
+#undef MAKE_AST_RESAMPLE
+
+/* AST_REBIN<X> requires a function for each possible data type, so
+   define it via a macro. */
+#define MAKE_AST_REBIN(f,F,Ftype,X,Xtype) \
+F77_SUBROUTINE(ast_rebin##f)( INTEGER(THIS), \
+                              DOUBLE(WLIM), \
+                              INTEGER(NDIM_IN), \
+                              INTEGER_ARRAY(LBND_IN), \
+                              INTEGER_ARRAY(UBND_IN), \
+                              Ftype##_ARRAY(IN), \
+                              Ftype##_ARRAY(IN_VAR), \
+                              INTEGER(INTERP), \
+                              DOUBLE_ARRAY(PARAMS), \
+                              INTEGER(FLAGS), \
+                              DOUBLE(TOL), \
+                              INTEGER(MAXPIX), \
+                              Ftype(BADVAL), \
+                              INTEGER(NDIM_OUT), \
+                              INTEGER_ARRAY(LBND_OUT), \
+                              INTEGER_ARRAY(UBND_OUT), \
+                              INTEGER_ARRAY(LBND), \
+                              INTEGER_ARRAY(UBND), \
+                              Ftype##_ARRAY(OUT), \
+                              Ftype##_ARRAY(OUT_VAR), \
+                              INTEGER(STATUS) ) { \
+   GENPTR_INTEGER(THIS) \
+   GENPTR_DOUBLE(WLIM) \
+   GENPTR_INTEGER(NDIM_IN) \
+   GENPTR_INTEGER_ARRAY(LBND_IN) \
+   GENPTR_INTEGER_ARRAY(UBND_IN) \
+   GENPTR_##Ftype##_ARRAY(IN) \
+   GENPTR_##Ftype##_ARRAY(IN_VAR) \
+   GENPTR_INTEGER(INTERP) \
+   GENPTR_DOUBLE_ARRAY(PARAMS) \
+   GENPTR_INTEGER(FLAGS) \
+   GENPTR_DOUBLE(TOL) \
+   GENPTR_INTEGER(MAXPIX) \
+   GENPTR_##Ftype(BADVAL) \
+   GENPTR_INTEGER(NDIM_OUT) \
+   GENPTR_INTEGER_ARRAY(LBND_OUT) \
+   GENPTR_INTEGER_ARRAY(UBND_OUT) \
+   GENPTR_INTEGER_ARRAY(LBND) \
+   GENPTR_INTEGER_ARRAY(UBND) \
+   GENPTR_##Ftype##_ARRAY(OUT) \
+   GENPTR_##Ftype##_ARRAY(OUT_VAR) \
+   GENPTR_INTEGER(STATUS) \
+\
+   Xtype *out_var; \
+   const Xtype *in_var; \
+\
+   astAt( "AST_REBIN"#F, NULL, 0 ); \
+   astWatchSTATUS( \
+\
+/* If the AST__USEVAR flag is set, use the input and output variance \
+   arrays, otherwise pass NULL pointers. */ \
+      in_var = out_var = NULL; \
+      if ( AST__USEVAR & *FLAGS ) { \
+         in_var = (const Xtype *) IN_VAR; \
+         out_var = (Xtype *) OUT_VAR; \
+      } \
+      astRebin##X( astI2P( *THIS ), *WLIM, *NDIM_IN, \
+                   LBND_IN, UBND_IN, (const Xtype *) IN, in_var, \
+                   *INTERP, PARAMS, *FLAGS, \
+                   *TOL, *MAXPIX, *BADVAL, \
+                   *NDIM_OUT, LBND_OUT, UBND_OUT, \
+                   LBND, UBND, (Xtype *) OUT, out_var ); \
+   ) \
+}
+
+/* Invoke the above macro to define a function for each data
+   type. Include synonyms for some functions. */
+MAKE_AST_REBIN(d,D,DOUBLE,D,double)
+MAKE_AST_REBIN(r,R,REAL,F,float)
+MAKE_AST_REBIN(i,I,INTEGER,I,int)
+MAKE_AST_REBIN(b,B,BYTE,B,signed char)
+MAKE_AST_REBIN(ub,UB,UBYTE,UB,unsigned char)
+#undef MAKE_AST_REBIN
+
+/* AST_REBINSEQ<X> requires a function for each possible data type, so
+   define it via a macro. */
+#define MAKE_AST_REBINSEQ(f,F,Ftype,X,Xtype) \
+F77_SUBROUTINE(ast_rebinseq##f)( INTEGER(THIS), \
+                              DOUBLE(WLIM), \
+                              INTEGER(NDIM_IN), \
+                              INTEGER_ARRAY(LBND_IN), \
+                              INTEGER_ARRAY(UBND_IN), \
+                              Ftype##_ARRAY(IN), \
+                              Ftype##_ARRAY(IN_VAR), \
+                              INTEGER(INTERP), \
+                              DOUBLE_ARRAY(PARAMS), \
+                              INTEGER(FLAGS), \
+                              DOUBLE(TOL), \
+                              INTEGER(MAXPIX), \
+                              Ftype(BADVAL), \
+                              INTEGER(NDIM_OUT), \
+                              INTEGER_ARRAY(LBND_OUT), \
+                              INTEGER_ARRAY(UBND_OUT), \
+                              INTEGER_ARRAY(LBND), \
+                              INTEGER_ARRAY(UBND), \
+                              Ftype##_ARRAY(OUT), \
+                              Ftype##_ARRAY(OUT_VAR), \
+                              DOUBLE_ARRAY(WEIGHTS), \
+                              INTEGER8(NUSED), \
+                              INTEGER(STATUS) ) { \
+   GENPTR_INTEGER(THIS) \
+   GENPTR_DOUBLE(WLIM) \
+   GENPTR_INTEGER(NDIM_IN) \
+   GENPTR_INTEGER_ARRAY(LBND_IN) \
+   GENPTR_INTEGER_ARRAY(UBND_IN) \
+   GENPTR_##Ftype##_ARRAY(IN) \
+   GENPTR_##Ftype##_ARRAY(IN_VAR) \
+   GENPTR_INTEGER(INTERP) \
+   GENPTR_DOUBLE_ARRAY(PARAMS) \
+   GENPTR_INTEGER(FLAGS) \
+   GENPTR_DOUBLE(TOL) \
+   GENPTR_INTEGER(MAXPIX) \
+   GENPTR_##Ftype(BADVAL) \
+   GENPTR_INTEGER(NDIM_OUT) \
+   GENPTR_INTEGER_ARRAY(LBND_OUT) \
+   GENPTR_INTEGER_ARRAY(UBND_OUT) \
+   GENPTR_INTEGER_ARRAY(LBND) \
+   GENPTR_INTEGER_ARRAY(UBND) \
+   GENPTR_##Ftype##_ARRAY(OUT) \
+   GENPTR_##Ftype##_ARRAY(OUT_VAR) \
+   GENPTR_DOUBLE_ARRAY(WEIGHTS) \
+   GENPTR_INTEGER8(NUSED) \
+   GENPTR_INTEGER(STATUS) \
+\
+   Xtype *out_var; \
+   const Xtype *in_var; \
+   int64_t nused; \
+\
+   astAt( "AST_REBINSEQ"#F, NULL, 0 ); \
+   astWatchSTATUS( \
+\
+/* We need the input variances if the AST__USEVAR or AST__VARWGT flag is \
+   set. Otherwise use a NULL pointer for the input variances. */ \
+      if ( AST__USEVAR & *FLAGS || AST__VARWGT & *FLAGS ) { \
+         in_var = (const Xtype *) IN_VAR; \
+      } else { \
+         in_var = NULL; \
+      } \
+\
+/* We need the output variances if the AST__USEVAR or AST__GENVAR flag is \
+   set. Otherwise use a NULL pointer for the output variances. */ \
+      if ( AST__USEVAR & *FLAGS || AST__GENVAR & *FLAGS ) { \
+         out_var = (Xtype *) OUT_VAR; \
+      } else { \
+         out_var = NULL; \
+      } \
+\
+      nused = *NUSED; \
+      astRebinSeq##X( astI2P( *THIS ), *WLIM, *NDIM_IN, \
+                   LBND_IN, UBND_IN, (const Xtype *) IN, in_var, \
+                   *INTERP, PARAMS, *FLAGS, \
+                   *TOL, *MAXPIX, *BADVAL, \
+                   *NDIM_OUT, LBND_OUT, UBND_OUT, \
+                   LBND, UBND, (Xtype *) OUT, out_var, WEIGHTS, \
+                   &nused ); \
+      *NUSED = nused; \
+   ) \
+} \
+
+/* Invoke the above macro to define a function for each data
+   type. Include synonyms for some functions. */
+MAKE_AST_REBINSEQ(d,D,DOUBLE,D,double)
+MAKE_AST_REBINSEQ(r,R,REAL,F,float)
+MAKE_AST_REBINSEQ(i,I,INTEGER,I,int)
+MAKE_AST_REBINSEQ(b,B,BYTE,B,signed char)
+MAKE_AST_REBINSEQ(ub,UB,UBYTE,UB,unsigned char)
+#undef MAKE_AST_REBINSEQ
+
+F77_INTEGER_FUNCTION(ast_removeregions)( INTEGER(THIS),
+                                         INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_REMOVEREGIONS", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astRemoveRegions( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_simplify)( INTEGER(THIS),
+                                    INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_SIMPLIFY", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astSimplify( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_tran1)( INTEGER(THIS),
+                           INTEGER(NPOINT),
+                           DOUBLE(XIN),
+                           LOGICAL(FORWARD),
+                           DOUBLE(XOUT),
+                           INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(NPOINT)
+   GENPTR_DOUBLE(XIN)
+   GENPTR_LOGICAL(FORWARD)
+   GENPTR_DOUBLE(XOUT)
+
+   astAt( "AST_TRAN1", NULL, 0 );
+   astWatchSTATUS(
+      astTran1( astI2P( *THIS ), *NPOINT, XIN, F77_ISTRUE( *FORWARD ), XOUT );
+   )
+}
+
+F77_SUBROUTINE(ast_tran2)( INTEGER(THIS),
+                           INTEGER(NPOINT),
+                           DOUBLE(XIN),
+                           DOUBLE(YIN),
+                           LOGICAL(FORWARD),
+                           DOUBLE(XOUT),
+                           DOUBLE(YOUT),
+                           INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(NPOINT)
+   GENPTR_DOUBLE(XIN)
+   GENPTR_DOUBLE(YIN)
+   GENPTR_LOGICAL(FORWARD)
+   GENPTR_DOUBLE(XOUT)
+   GENPTR_DOUBLE(YOUT)
+
+   astAt( "AST_TRAN2", NULL, 0 );
+   astWatchSTATUS(
+      astTran2( astI2P( *THIS ), *NPOINT, XIN, YIN,
+                F77_ISTRUE( *FORWARD ), XOUT, YOUT );
+   )
+}
+
+F77_SUBROUTINE(ast_trangrid)( INTEGER(THIS),
+                              INTEGER(NCOORD_IN),
+                              INTEGER_ARRAY(LBND),
+                              INTEGER_ARRAY(UBND),
+                              DOUBLE(TOL),
+                              INTEGER(MAXPIX),
+                              LOGICAL(FORWARD),
+                              INTEGER(NCOORD_OUT),
+                              INTEGER(OUTDIM),
+                              DOUBLE_ARRAY(OUT),
+                              INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(NCOORD_IN)
+   GENPTR_INTEGER_ARRAY(LBND)
+   GENPTR_INTEGER_ARRAY(UBND)
+   GENPTR_DOUBLE(TOL)
+   GENPTR_INTEGER(MAXPIX)
+   GENPTR_LOGICAL(FORWARD)
+   GENPTR_INTEGER(NCOORD_OUT)
+   GENPTR_INTEGER(OUTDIM)
+   GENPTR_DOUBLE_ARRAY(OUT)
+
+   astAt( "AST_TRANGRID", NULL, 0 );
+   astWatchSTATUS(
+      astTranGrid( astI2P( *THIS ), *NCOORD_IN, LBND, UBND, *TOL, *MAXPIX,
+                   F77_ISTRUE( *FORWARD ), *NCOORD_OUT, *OUTDIM, OUT );
+   )
+}
+
+F77_SUBROUTINE(ast_trann)( INTEGER(THIS),
+                           INTEGER(NPOINT),
+                           INTEGER(NCOORD_IN),
+                           INTEGER(INDIM),
+                           DOUBLE_ARRAY(IN),
+                           LOGICAL(FORWARD),
+                           INTEGER(NCOORD_OUT),
+                           INTEGER(OUTDIM),
+                           DOUBLE_ARRAY(OUT),
+                           INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(NPOINT)
+   GENPTR_INTEGER(NCOORD_IN)
+   GENPTR_INTEGER(INDIM)
+   GENPTR_DOUBLE_ARRAY(IN)
+   GENPTR_LOGICAL(FORWARD)
+   GENPTR_INTEGER(NCOORD_OUT)
+   GENPTR_INTEGER(OUTDIM)
+   GENPTR_DOUBLE_ARRAY(OUT)
+
+   astAt( "AST_TRANN", NULL, 0 );
+   astWatchSTATUS(
+      astTranN( astI2P( *THIS ), *NPOINT, *NCOORD_IN, *INDIM,
+                (const double *)IN, F77_ISTRUE( *FORWARD ),
+                *NCOORD_OUT, *OUTDIM, OUT );
+   )
+}
+
+F77_DOUBLE_FUNCTION(ast_rate)( INTEGER(THIS),
+                               DOUBLE_ARRAY(AT),
+                               INTEGER(AX1),
+                               INTEGER(AX2),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(AX1)
+   GENPTR_INTEGER(AX2)
+   GENPTR_DOUBLE_ARRAY(AT)
+   F77_DOUBLE_TYPE(RESULT);
+
+   astAt( "AST_RATE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astRate( astI2P( *THIS ), AT, *AX1, *AX2 );
+   )
+   return RESULT;
+}
+
+
+F77_SUBROUTINE(ast_mapsplit)( INTEGER(THIS),
+                              INTEGER(NIN),
+                              INTEGER_ARRAY(IN),
+                              INTEGER_ARRAY(OUT),
+                              INTEGER(MAP),
+                              INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(NIN)
+   GENPTR_INTEGER_ARRAY(IN)
+   GENPTR_INTEGER_ARRAY(OUT)
+   GENPTR_INTEGER(MAP)
+   AstMapping *map;
+
+   astAt( "AST_MAPSPLIT", NULL, 0 );
+   astWatchSTATUS(
+      astMapSplit( astI2P( *THIS ), *NIN, IN, OUT, &map );
+      *MAP = astP2I( map );
+   )
+}
+
diff --git a/ast/fmathmap.c b/ast/fmathmap.c
new file mode 100644
index 0000000..88ac236
--- /dev/null
+++ b/ast/fmathmap.c
@@ -0,0 +1,122 @@
+/*
+*+
+*  Name:
+*     fmathmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST MathMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the MathMap class.
+
+*  Routines Defined:
+*     AST_ISAMATHMAP
+*     AST_MATHMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     3-SEP-1999 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "mathmap.h"             /* C interface to the MathMap class */
+
+F77_LOGICAL_FUNCTION(ast_isamathmap)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAMATHMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAMathMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_mathmap)( INTEGER(NIN),
+                                   INTEGER(NOUT),
+                                   INTEGER(NFWD),
+                                   CHARACTER_ARRAY(FWD),
+                                   INTEGER(NINV),
+                                   CHARACTER_ARRAY(INV),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(FWD)
+                                   TRAIL(INV)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NIN)
+   GENPTR_INTEGER(NOUT)
+   GENPTR_INTEGER(NFWD)
+   GENPTR_CHARACTER_ARRAY(FWD)
+   GENPTR_INTEGER(NINV)
+   GENPTR_CHARACTER_ARRAY(INV)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char **fwd;
+   char **inv;
+   char *options;
+   int i;
+
+   astAt( "AST_MATHMAP", NULL, 0 );
+   astWatchSTATUS(
+      fwd = astStringArray( FWD, *NFWD, FWD_length );
+      inv = astStringArray( INV, *NINV, INV_length );
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astMathMap( *NIN, *NOUT,
+                                   *NFWD, (const char **) fwd,
+                                   *NINV, (const char **) inv,
+                                   "%s", options ) );
+      astFree( options );
+      astFree( inv );
+      astFree( fwd );
+   )
+   return RESULT;
+}
diff --git a/ast/fmatrixmap.c b/ast/fmatrixmap.c
new file mode 100644
index 0000000..8fff328
--- /dev/null
+++ b/ast/fmatrixmap.c
@@ -0,0 +1,110 @@
+/*
+*+
+*  Name:
+*     fmatrixmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST MatrixMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the MatrixMap class.
+
+*  Routines Defined:
+*     AST_ISAMATRIXMAP
+*     AST_MATRIXMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     14-NOV-1996 (DSB):
+*        Original version.
+*     3-JUN-1997 (DSB):
+*        AST_MTRROT and AST_MTRMULT removed.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "matrixmap.h"           /* C interface to the MatrixMap class */
+
+F77_LOGICAL_FUNCTION(ast_isamatrixmap)( INTEGER(THIS),
+                                        INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAMATRIXMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAMatrixMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_matrixmap)( INTEGER(NIN),
+                                     INTEGER(NOUT),
+                                     INTEGER(FORM),
+                                     DOUBLE_ARRAY(MATRIX),
+                                     CHARACTER(OPTIONS),
+                                     INTEGER(STATUS)
+                                     TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NIN)
+   GENPTR_INTEGER(NOUT)
+   GENPTR_INTEGER(FORM)
+   GENPTR_DOUBLE_ARRAY(MATRIX)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_MATRIXMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astMatrixMap( *NIN, *NOUT, *FORM, MATRIX,
+                                     "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fnormmap.c b/ast/fnormmap.c
new file mode 100644
index 0000000..f7fa127
--- /dev/null
+++ b/ast/fnormmap.c
@@ -0,0 +1,101 @@
+/*
+*+
+*  Name:
+*     fnormmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST NormMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the NormMap class.
+
+*  Routines Defined:
+*     AST_ISANORMMAP
+*     AST_NORMMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S.Berry (Starlink)
+
+*  History:
+*     11-JUL-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "normmap.h"             /* C interface to the NormMap class */
+
+F77_LOGICAL_FUNCTION(ast_isanormmap)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astWatchSTATUS(
+   astAt( "AST_ISANORMMAP", NULL, 0 );
+      RESULT = astIsANormMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_normmap)( INTEGER(FRAME),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_NORMMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astNormMap( astI2P( *FRAME ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fnullregion.c b/ast/fnullregion.c
new file mode 100644
index 0000000..6e7d61f
--- /dev/null
+++ b/ast/fnullregion.c
@@ -0,0 +1,104 @@
+/*
+*+
+*  Name:
+*     fnullregion.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST NullRegion class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the NullRegion class.
+
+*  Routines Defined:
+*     AST_ISANULLREGION
+*     AST_NULLREGION
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     12-OCT-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "nullregion.h"          /* C interface to the NullRegion class */
+
+
+F77_LOGICAL_FUNCTION(ast_isanullregion)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISANULLREGION", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsANullRegion( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_nullregion)( INTEGER(FRAME),
+                                      INTEGER(UNC),
+                                      CHARACTER(OPTIONS),
+                                      INTEGER(STATUS)
+                                      TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_INTEGER(UNC)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_NULLREGION", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+      RESULT = astP2I( astNullRegion( astI2P( *FRAME ), astI2P( *UNC ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fobject.c b/ast/fobject.c
new file mode 100644
index 0000000..c718986
--- /dev/null
+++ b/ast/fobject.c
@@ -0,0 +1,674 @@
+/*
+*+
+*  Name:
+*     fobject.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Object class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Object class.
+
+*  Routines Defined:
+*     AST_ANNUL
+*     AST_BEGIN
+*     AST_CLEAR
+*     AST_CLONE
+*     AST_COPY
+*     AST_DELETE
+*     AST_END
+*     AST_ESCAPES
+*     AST_EXEMPT
+*     AST_EXPORT
+*     AST_GET(C,D,I,L,R)
+*     AST_ISAOBJECT
+*     AST_NULL
+*     AST_SET
+*     AST_SET(C,D,I,L,R)
+*     AST_SHOW
+*     AST_VERSION
+*     AST_LISTISSUED   (only if macro DEBUG is defined)
+*     AST_SETWATCHID   (only if macro DEBUG is defined)
+*     AST_TUNE
+*     AST_TUNEC
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     20-JUN-1996 (RFWS):
+*        Original version.
+*     9-SEP-1996 (RFWS):
+*        Added AST_SHOW.
+*     11-DEC-1996 (RFWS):
+*        Added AST_NULL.
+*     14-JUL-1997 (RFWS):
+*        Add AST_EXEMPT function.
+*     30-APR-2003 (DSB):
+*        Add AST_VERSION function.
+*     7-FEB-2004 (DSB):
+*        Add AST_ESCAPES function.
+*     27-JAN-2005 (DSB):
+*        Added AST_LISTISSUED and AST_SETWATCHID so that DEBUG facilities
+*        can be used from fortran.
+*     7-FEB-2006 (DSB):
+*        Added AST_TUNE.
+*     1-MAR-2006 (DSB):
+*        Replace astSetPermMap within DEBUG blocks by astBeginPM/astEndPM.
+*     13-OCT-2011 (DSB):
+*        Added AST_TUNEC.
+*-
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include <string.h>
+
+/* Configuration results. */
+/* ---------------------- */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* AST headers */
+/* ----------- */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "object.h"              /* C interface to the Object class */
+
+F77_SUBROUTINE(ast_annul)( INTEGER(THIS),
+                           INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_ANNUL", NULL, 0 );
+   astWatchSTATUS(
+      *THIS = astP2I( astAnnul( astI2P( *THIS ) ) );
+   )
+}
+
+F77_SUBROUTINE(ast_begin)( INTEGER(STATUS) ) {
+
+   astAt( "AST_BEGIN", NULL, 0 );
+   astWatchSTATUS(
+      int dummy = *status; /* Avoid "unused variable 'status'" messages */
+      *status = dummy;
+      astBegin;
+   )
+}
+
+F77_SUBROUTINE(ast_clear)( INTEGER(THIS),
+                           CHARACTER(ATTRIB),
+                           INTEGER(STATUS)
+                           TRAIL(ATTRIB) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(ATTRIB)
+   char *attrib;
+
+   astAt( "AST_CLEAR", NULL, 0 );
+   astWatchSTATUS(
+      attrib = astString( ATTRIB, ATTRIB_length );
+      astClear( astI2P( *THIS ), attrib );
+      astFree( attrib );
+   )
+}
+
+F77_INTEGER_FUNCTION(ast_clone)( INTEGER(THIS),
+                                 INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_CLONE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astClone( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_version)( ) {
+   int status_value = 0;
+   int *STATUS = &status_value;
+   int *status = &status_value;
+   astAt( "AST_VERSION", NULL, 0 );
+   return astVersion;
+}
+
+F77_INTEGER_FUNCTION(ast_escapes)( INTEGER(NEWVAL),
+                                   INTEGER(STATUS) ) {
+   GENPTR_INTEGER(NEWVAL)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_ESCAPES", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astEscapes( *NEWVAL );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_copy)( INTEGER(THIS),
+                                INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_COPY", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astCopy( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_delete)( INTEGER(THIS),
+                            INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_DELETE", NULL, 0 );
+   astWatchSTATUS(
+      *THIS = astP2I( astDelete( astI2P( *THIS ) ) );
+   )
+}
+
+F77_SUBROUTINE(ast_end)( INTEGER(STATUS) ) {
+
+   astAt( "AST_END", NULL, 0 );
+   astWatchSTATUS(
+      astEnd;
+   )
+}
+
+F77_SUBROUTINE(ast_exempt)( INTEGER(THIS),
+                            INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_EXEMPT", NULL, 0 );
+   astWatchSTATUS(
+      astExempt( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_export)( INTEGER(THIS),
+                            INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_EXPORT", NULL, 0 );
+   astWatchSTATUS(
+      astExport( astI2P( *THIS ) );
+   )
+}
+
+#define MAKE_GETX(name,code,TYPE,CODE,type) \
+F77_##TYPE##_FUNCTION(ast_get##code)( INTEGER(THIS), \
+                                      CHARACTER(ATTRIB), \
+                                      INTEGER(STATUS) \
+                                      TRAIL(ATTRIB) ) { \
+   GENPTR_INTEGER(THIS) \
+   GENPTR_CHARACTER(ATTRIB) \
+   F77_##TYPE##_TYPE(RESULT); \
+   char *attrib; \
+\
+   astAt( name, NULL, 0 ); \
+   astWatchSTATUS( \
+      attrib = astString( ATTRIB, ATTRIB_length ); \
+      RESULT = astGet##CODE( astI2P( *THIS ), attrib ); \
+      astFree( attrib ); \
+   ) \
+   return RESULT; \
+}
+
+MAKE_GETX("AST_GETD",d,DOUBLE,D,double)
+MAKE_GETX("AST_GETI",i,INTEGER,L,long)
+MAKE_GETX("AST_GETR",r,REAL,D,double)
+
+/* NO_CHAR_FUNCTION indicates that the f77.h method of returning a
+   character result doesn't work, so add an extra argument instead and
+   wrap this function up in a normal FORTRAN 77 function (in the file
+   object.f). */
+#if NO_CHAR_FUNCTION
+F77_SUBROUTINE(ast_getc_a)( CHARACTER(RESULT),
+#else
+F77_SUBROUTINE(ast_getc)( CHARACTER_RETURN_VALUE(RESULT),
+#endif
+                          INTEGER(THIS),
+                          CHARACTER(ATTRIB),
+                          INTEGER(STATUS)
+#if NO_CHAR_FUNCTION
+                          TRAIL(RESULT)
+#endif
+                          TRAIL(ATTRIB) ) {
+   GENPTR_CHARACTER(RESULT)
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(ATTRIB)
+   char *attrib;
+   const char *result;
+   int i;
+
+   astAt( "AST_GETC", NULL, 0 );
+   astWatchSTATUS(
+      attrib = astString( ATTRIB, ATTRIB_length );
+      result = astGetC( astI2P( *THIS ), attrib );
+      i = 0;
+      if ( astOK ) {             /* Copy result */
+         for ( ; result[ i ] && i < RESULT_length; i++ ) {
+            RESULT[ i ] = result[ i ];
+         }
+      }
+      while ( i < RESULT_length ) RESULT[ i++ ] = ' '; /* Pad with blanks */
+      astFree( attrib );
+   )
+}
+
+F77_LOGICAL_FUNCTION(ast_getl)( INTEGER(THIS),
+                                CHARACTER(ATTRIB),
+                                INTEGER(STATUS)
+                                TRAIL(ATTRIB) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(ATTRIB)
+   F77_LOGICAL_TYPE(RESULT);
+   char *attrib;
+
+   astAt( "AST_GETL", NULL, 0 );
+   astWatchSTATUS(
+      attrib = astString( ATTRIB, ATTRIB_length );
+      RESULT = astGetL( astI2P( *THIS ), attrib ) ? F77_TRUE : F77_FALSE;
+      astFree( attrib );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_isaobject)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAOBJECT", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAObject( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_null)( void ) {}
+
+/* Omit the C variable length argument list here. */
+F77_SUBROUTINE(ast_set)( INTEGER(THIS),
+                         CHARACTER(SETTING),
+                         INTEGER(STATUS)
+                         TRAIL(SETTING) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(SETTING)
+   char *setting;
+   int i;
+   int quoted;
+
+   astAt( "AST_SET", NULL, 0 );
+   astWatchSTATUS(
+      setting = astString( SETTING, SETTING_length );
+
+/* Truncate the string to exclude any trailing spaces. */
+      astChrTrunc( setting );
+
+/* Change ',' to '\n' (which is what astSet normally does to its second
+   argument internally to separate the fields). This then allows "setting"
+   to be provided as an additional string value to be formatted using "%s".
+   This avoids interpretation of its contents (e.g. '%') as C format
+   specifiers. */
+      if ( astOK ) {
+         quoted = 0;
+         for ( i = 0; setting[ i ]; i++ ) {
+            if( !quoted ) {
+               if ( setting[ i ] == ',' ) {
+                  setting[ i ] = '\n';
+               } else if( setting[ i ] == '"' ) {
+                  quoted = 1;
+               }
+            } else if( setting[ i ] == '"' ){
+               quoted = 0;
+            }
+         }
+      }
+      astSet( astI2P( *THIS ), "%s", setting );
+      astFree( setting );
+   )
+}
+
+#define MAKE_SETX(name,code,TYPE,CODE,type) \
+F77_SUBROUTINE(ast_set##code)( INTEGER(THIS), \
+                               CHARACTER(ATTRIB), \
+                               TYPE(VALUE), \
+                               INTEGER(STATUS) \
+                               TRAIL(ATTRIB) ) { \
+   GENPTR_INTEGER(THIS) \
+   GENPTR_CHARACTER(ATTRIB) \
+   GENPTR_##TYPE(VALUE) \
+   char *attrib; \
+\
+   astAt( name, NULL, 0 ); \
+   astWatchSTATUS( \
+      attrib = astString( ATTRIB, ATTRIB_length ); \
+      astSet##CODE( astI2P( *THIS ), attrib, *VALUE ); \
+      astFree( attrib ); \
+   ) \
+}
+
+MAKE_SETX("AST_SETD",d,DOUBLE,D,double)
+MAKE_SETX("AST_SETR",r,REAL,D,double)
+MAKE_SETX("AST_SETI",i,INTEGER,L,long)
+
+F77_SUBROUTINE(ast_setc)( INTEGER(THIS),
+                          CHARACTER(ATTRIB),
+                          CHARACTER(VALUE),
+                          INTEGER(STATUS)
+                          TRAIL(ATTRIB)
+                          TRAIL(VALUE) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(ATTRIB)
+   GENPTR_CHARACTER(VALUE)
+   char *attrib, *value;
+
+   astAt( "AST_SETC", NULL, 0 );
+   astWatchSTATUS(
+      attrib = astString( ATTRIB, ATTRIB_length );
+      value = astString( VALUE, VALUE_length );
+
+/* Truncate the strings to exclude any trailing spaces. */
+      astChrTrunc( attrib );
+      astChrTrunc( value );
+
+      astSetC( astI2P( *THIS ), attrib, value );
+      astFree( attrib );
+      astFree( value );
+   )
+}
+
+F77_SUBROUTINE(ast_setl)( INTEGER(THIS),
+                          CHARACTER(ATTRIB),
+                          LOGICAL(VALUE),
+                          INTEGER(STATUS)
+                          TRAIL(ATTRIB) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(ATTRIB)
+   GENPTR_LOGICAL(VALUE)
+   char *attrib;
+
+   astAt( "AST_SETL", NULL, 0 );
+   astWatchSTATUS(
+      attrib = astString( ATTRIB, ATTRIB_length );
+      astSetI( astI2P( *THIS ), attrib, F77_ISTRUE( *VALUE ) ? 1 : 0 );
+      astFree( attrib );
+   )
+}
+
+F77_SUBROUTINE(ast_show)( INTEGER(THIS),
+                          INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_SHOW", NULL, 0 );
+   astWatchSTATUS(
+      astShow( astI2P( *THIS ) );
+   )
+}
+
+F77_LOGICAL_FUNCTION(ast_test)( INTEGER(THIS),
+                                CHARACTER(ATTRIB),
+                                INTEGER(STATUS)
+                                TRAIL(ATTRIB) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(ATTRIB)
+   F77_LOGICAL_TYPE(RESULT);
+   char *attrib;
+
+   astAt( "AST_TEST", NULL, 0 );
+   astWatchSTATUS(
+      attrib = astString( ATTRIB, ATTRIB_length );
+      RESULT = astTest( astI2P( *THIS ), attrib ) ? F77_TRUE : F77_FALSE;
+      astFree( attrib );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_hasattribute)( INTEGER(THIS),
+                                        CHARACTER(ATTRIB),
+                                        INTEGER(STATUS)
+                                        TRAIL(ATTRIB) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(ATTRIB)
+   F77_LOGICAL_TYPE(RESULT);
+   char *attrib;
+
+   astAt( "AST_HASATTRIBUTE", NULL, 0 );
+   astWatchSTATUS(
+      attrib = astString( ATTRIB, ATTRIB_length );
+      RESULT = astHasAttribute( astI2P( *THIS ), attrib ) ? F77_TRUE : F77_FALSE;
+      astFree( attrib );
+   )
+   return RESULT;
+}
+
+
+F77_LOGICAL_FUNCTION(ast_same)( INTEGER(THIS),
+                                INTEGER(THAT),
+                                INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(THAT)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_SAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astSame( astI2P( *THIS ), astI2P( *THAT ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+
+#ifdef MEM_DEBUG
+
+F77_SUBROUTINE(ast_beginpm)( void ) {
+   int status = 0;
+   astBeginPM_( &status );
+}
+
+F77_SUBROUTINE(ast_endpm)( void ) {
+   int status = 0;
+   astEndPM_( &status );
+}
+
+F77_SUBROUTINE(ast_activememory)( CHARACTER(TEXT)
+                                  TRAIL(TEXT) ) {
+   GENPTR_CHARACTER(TEXT)
+   char *text;
+   int status_value;
+   int *status = &status_value;
+   *status = 0;
+   astBeginPM;
+   text = astString( TEXT, TEXT_length );
+   astEndPM;
+   astActiveMemory( text );
+   astFree( text );
+}
+
+F77_SUBROUTINE(ast_watchmemory)( INTEGER(ID) ) {
+   GENPTR_INTEGER(ID)
+   astWatchMemory( *ID );
+}
+
+F77_SUBROUTINE(ast_flushmemory)( INTEGER(LEAK) ) {
+   GENPTR_INTEGER(LEAK)
+   int status = 0;
+   astFlushMemory_( *LEAK, &status );
+}
+
+#else
+
+F77_SUBROUTINE(ast_activememory)( CHARACTER(TEXT)
+                                  TRAIL(TEXT) ) {
+   GENPTR_CHARACTER(TEXT)
+}
+
+F77_SUBROUTINE(ast_watchmemory)( INTEGER(ID) ) {
+   GENPTR_INTEGER(ID)
+}
+
+F77_SUBROUTINE(ast_flushmemory)( INTEGER(LEAK) ) {
+   GENPTR_INTEGER(LEAK)
+}
+
+F77_SUBROUTINE(ast_beginpm)( void ) {
+}
+
+F77_SUBROUTINE(ast_endpm)( void ) {
+}
+
+
+
+
+#endif
+
+
+F77_INTEGER_FUNCTION(ast_tune)( CHARACTER(NAME),
+                                INTEGER(VALUE),
+                                INTEGER(STATUS)
+                                TRAIL(NAME) ) {
+   GENPTR_INTEGER(VALUE)
+   GENPTR_CHARACTER(NAME)
+   F77_INTEGER_TYPE(RESULT);
+   char *name;
+
+   astAt( "AST_TUNE", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      RESULT = astTune( name, *VALUE );
+      name = astFree( name );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_tunec)( CHARACTER(NAME),
+                           CHARACTER(VALUE),
+                           CHARACTER(BUFF),
+                           INTEGER(STATUS)
+                           TRAIL(NAME)
+                           TRAIL(VALUE)
+                           TRAIL(BUFF) ) {
+   GENPTR_CHARACTER(NAME)
+   GENPTR_CHARACTER(VALUE)
+   GENPTR_CHARACTER(BUFF)
+   char *name;
+   char *value;
+   char *buff;
+
+   astAt( "AST_TUNEC", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      value = astString( VALUE, VALUE_length );
+      if( value && !strcmp( value, AST__TUNULLC ) ) value = astFree( value );
+      buff = astMalloc( BUFF_length + 1 );
+
+      astTuneC( name, value, buff,  BUFF_length + 1 );
+
+      int i = 0;
+      if( astOK ) {
+         for ( ; buff[ i ] && i < BUFF_length; i++ ) {
+            BUFF[ i ] = buff[ i ];
+         }
+      }
+      while ( i < BUFF_length ) BUFF[ i++ ] = ' '; /* Pad with blanks */
+
+      buff = astFree( buff );
+      name = astFree( name );
+      value = astFree( value );
+   )
+}
+
+F77_LOGICAL_FUNCTION(ast_chrsub)( CHARACTER(TEST),
+                                  CHARACTER(PATTERN),
+                                  CHARACTER(RESULT),
+                                  INTEGER(STATUS)
+                                  TRAIL(TEST)
+                                  TRAIL(PATTERN)
+                                  TRAIL(RESULT) ) {
+   GENPTR_CHARACTER(TEST)
+   GENPTR_CHARACTER(PATTERN)
+   GENPTR_CHARACTER(RESULT)
+   F77_LOGICAL_TYPE(MATCH);
+
+   char *test, *pattern, *result;
+   int i;
+
+   astAt( "AST_CHRSUB", NULL, 0 );
+   astWatchSTATUS(
+      test = astString( TEST, TEST_length );
+      pattern = astString( PATTERN, PATTERN_length );
+
+      if( pattern ) {
+         test[ astChrLen( test ) ] = 0;
+         pattern[ astChrLen( pattern ) ] = 0;
+      }
+
+      result = astChrSub( test, pattern, NULL, 0 );
+
+      i = 0;
+      if( result ) {
+         MATCH = F77_TRUE;
+         for ( ; result[ i ] && i < RESULT_length; i++ ) {
+            RESULT[ i ] = result[ i ];
+         }
+         result = astFree( result );
+      } else {
+         MATCH = F77_FALSE;
+      }
+      while ( i < RESULT_length ) RESULT[ i++ ] = ' '; /* Pad with blanks */
+
+      test = astFree( test );
+      pattern = astFree( pattern );
+   )
+   return MATCH;
+}
+
+
+F77_LOGICAL_FUNCTION(ast_equal)( INTEGER(THIS),
+                                 INTEGER(THAT),
+                                 INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(THAT)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_EQUAL", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astEqual( astI2P( *THIS ), astI2P( *THAT ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+
+
diff --git a/ast/fpcdmap.c b/ast/fpcdmap.c
new file mode 100644
index 0000000..bd5a335
--- /dev/null
+++ b/ast/fpcdmap.c
@@ -0,0 +1,103 @@
+/*
+*+
+*  Name:
+*     fpcdmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST PcdMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the PcdMap class.
+
+*  Routines Defined:
+*     AST_ISAPCDMAP
+*     AST_PCDMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     19-MAY-1999 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "pcdmap.h"              /* C interface to the PcdMap class */
+
+F77_LOGICAL_FUNCTION(ast_isapcdmap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAPCDMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAPcdMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_pcdmap)( DOUBLE(DISCO),
+                                  DOUBLE_ARRAY(PCDCEN),
+                                  CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_DOUBLE(DISCO)
+   GENPTR_DOUBLE_ARRAY(PCDCEN)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_PCDMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astPcdMap( *DISCO, PCDCEN, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fpermmap.c b/ast/fpermmap.c
new file mode 100644
index 0000000..d280958
--- /dev/null
+++ b/ast/fpermmap.c
@@ -0,0 +1,110 @@
+/*
+*+
+*  Name:
+*     fpermmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST PermMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the PermMap class.
+
+*  Routines Defined:
+*     AST_ISAPERMMAP
+*     AST_PERMMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     26-SEP-1996 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "permmap.h"             /* C interface to the PermMap class */
+
+F77_LOGICAL_FUNCTION(ast_isapermmap)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAPERMMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAPermMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_permmap)( INTEGER(NIN),
+                                   INTEGER_ARRAY(INPERM),
+                                   INTEGER(NOUT),
+                                   INTEGER_ARRAY(OUTPERM),
+                                   DOUBLE_ARRAY(CONSTANT),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NIN)
+   GENPTR_INTEGER_ARRAY(INPERM)
+   GENPTR_INTEGER(NOUT)
+   GENPTR_INTEGER_ARRAY(OUTPERM)
+   GENPTR_DOUBLE_ARRAY(CONSTANT)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_PERMMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astPermMap( *NIN, INPERM, *NOUT, OUTPERM, CONSTANT,
+                                   "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fplot.c b/ast/fplot.c
new file mode 100644
index 0000000..a5e0b47
--- /dev/null
+++ b/ast/fplot.c
@@ -0,0 +1,683 @@
+/*
+*+
+*  Name:
+*     fplot.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Plot class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Plot class.
+
+*  Routines Defined:
+*     AST_BORDER
+*     AST_BOUNDINGBOX
+*     AST_CLIP
+*     AST_CURVE
+*     AST_GENCURVE
+*     AST_GRID
+*     AST_GRIDLINE
+*     AST_ISAPLOT
+*     AST_MARK
+*     AST_PLOT
+*     AST_POLYCURVE
+*     AST_REGIONOUTLINE
+*     AST_TEXT
+*     AST_GRFSET
+*     AST_GRFPUSH
+*     AST_GRFPOP
+*     AST_STRIPESCAPES
+*     AST_GETGRFCONTEXT
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2007 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     23-OCT-1996 (DSB):
+*        Original version.
+*     14-NOV-1996 (DSB):
+*        Method names shortened. CrvBreak removed.
+*     21-NOV-1996 (DSB):
+*        Method names changed, CLIP argument NBND removed.
+*     18-DEC-1996 (DSB):
+*        Argument UP changed to single precision and NCOORD removed
+*        in AST_TEXT.
+*     11-AUG-1998 (DSB):
+*        Added AST_POLYCURVE.
+*     9-JAN-2001 (DSB):
+*        Change argument "in" for astMark and astPolyCurve from type
+*        "const double (*)[]" to "const double *".
+*     13-JUN-2001 (DSB):
+*        Modified to add support for astGenCurve, astGrfSet, astGrfPop,
+*        astGrfPush and EXTERNAL grf functions.
+*     14-AUG-2002 (DSB):
+*        Added AST_BOUNDINGBOX.
+*     8-JAN-2003 (DSB):
+*        Include "string.h".
+*     10-JUL-2006 (DSB):
+*        Add AST_STRIPESCAPES
+*     21-JUN-2007 (DSB):
+*        - Avoid use of protected astGetGrfContext function.
+*        - Change data type of GrfContext from integer to AST Object pointer.
+*     29-JUN-2007 (DSB):
+*        Added astGetGrfCOntext and removed astSetGrfContext.
+*     30-AUG-2007 (DSB):
+*        Use astGrfConID to get the identifier for the graphics context
+*        KeyMap.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+#define MXSTRLEN 80              /* String length at which truncation starts
+                                    within pgqtxt and pgptxt. */
+/* Header files. */
+/* ============= */
+#include "string.h"
+#include "ast_err.h"             /* AST error codes */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "plot.h"                /* C interface to the Plot class */
+#include "grf.h"                 /* Low-level graphics interface */
+
+/* Prototypes for external functions. */
+/* ================================== */
+/* This is the null function defined by the FORTRAN interface in
+fobject.c. */
+F77_SUBROUTINE(ast_null)( void );
+
+static int FGAttrWrapper( AstPlot *, int, double, double *, int );
+static int FGBBufWrapper( AstPlot * );
+static int FGEBufWrapper( AstPlot * );
+static int FGFlushWrapper( AstPlot * );
+static int FGLineWrapper( AstPlot *, int, const float *, const float * );
+static int FGMarkWrapper( AstPlot *, int, const float *, const float *, int );
+static int FGTextWrapper( AstPlot *, const char *, float, float, const char *, float, float );
+static int FGTxExtWrapper( AstPlot *, const char *, float, float, const char *, float, float, float *, float * );
+static int FGCapWrapper( AstPlot *, int, int );
+static int FGQchWrapper( AstPlot *, float *, float * );
+static int FGScalesWrapper( AstPlot *, float *, float * );
+
+F77_LOGICAL_FUNCTION(ast_isaplot)( INTEGER(THIS),
+                                   INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAPLOT", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAPlot( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_plot)( INTEGER(FRAME),
+                                REAL_ARRAY(GRAPHBOX),
+                                DOUBLE_ARRAY(BASEBOX),
+                                CHARACTER(OPTIONS),
+                                INTEGER(STATUS)
+                                TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_REAL_ARRAY(GRAPHBOX)
+   GENPTR_DOUBLE_ARRAY(BASEBOX)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_PLOT", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astPlot( astI2P( *FRAME ), GRAPHBOX, BASEBOX,
+                                "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_border)( INTEGER(THIS),
+                                  INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_BORDER", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astBorder( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_boundingbox)( INTEGER(THIS),
+                          REAL_ARRAY(LBND),
+                          REAL_ARRAY(UBND),
+                          INTEGER(STATUS) ){
+   GENPTR_INTEGER(THIS)
+   GENPTR_REAL_ARRAY(LBND)
+   GENPTR_REAL_ARRAY(UBND)
+
+   astAt( "AST_BOUNDINGBOX", NULL, 0 );
+   astWatchSTATUS(
+      astBoundingBox( astI2P( *THIS ), LBND, UBND );
+   )
+}
+
+F77_SUBROUTINE(ast_clip)( INTEGER(THIS),
+                          INTEGER(IFRAME),
+                          DOUBLE_ARRAY(LBND),
+                          DOUBLE_ARRAY(UBND),
+                          INTEGER(STATUS) ){
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(IFRAME)
+   GENPTR_DOUBLE_ARRAY(LBND)
+   GENPTR_DOUBLE_ARRAY(UBND)
+
+   astAt( "AST_CLIP", NULL, 0 );
+   astWatchSTATUS(
+      astClip( astI2P( *THIS ), *IFRAME, LBND, UBND );
+   )
+}
+
+F77_SUBROUTINE(ast_gridline)( INTEGER(THIS),
+                              INTEGER(AXIS),
+                              DOUBLE_ARRAY(START),
+                              DOUBLE(LENGTH),
+                              INTEGER(STATUS) ){
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(AXIS)
+   GENPTR_DOUBLE_ARRAY(START)
+   GENPTR_DOUBLE(LENGTH)
+
+   astAt( "AST_GRIDLINE", NULL, 0 );
+   astWatchSTATUS(
+      astGridLine( astI2P( *THIS ), *AXIS, START, *LENGTH );
+   )
+}
+
+F77_SUBROUTINE(ast_curve)( INTEGER(THIS),
+                           DOUBLE_ARRAY(START),
+                           DOUBLE_ARRAY(FINISH),
+                           INTEGER(STATUS) ){
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE_ARRAY(START)
+   GENPTR_DOUBLE_ARRAY(FINISH)
+
+   astAt( "AST_CURVE", NULL, 0 );
+   astWatchSTATUS(
+      astCurve( astI2P( *THIS ), START, FINISH );
+   )
+}
+
+F77_SUBROUTINE(ast_grid)( INTEGER(THIS),
+                          INTEGER(STATUS) ){
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_GRID", NULL, 0 );
+   astWatchSTATUS(
+      astGrid( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_mark)( INTEGER(THIS),
+                          INTEGER(NMARK),
+                          INTEGER(NCOORD),
+                          INTEGER(INDIM),
+                          DOUBLE_ARRAY(IN),
+                          INTEGER(TYPE),
+                          INTEGER(STATUS) ){
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(NMARK)
+   GENPTR_INTEGER(NCOORD)
+   GENPTR_INTEGER(INDIM)
+   GENPTR_DOUBLE_ARRAY(IN)
+   GENPTR_INTEGER(TYPE)
+
+   astAt( "AST_MARK", NULL, 0 );
+   astWatchSTATUS(
+      astMark( astI2P( *THIS ), *NMARK, *NCOORD, *INDIM,
+               (const double *)IN, *TYPE );
+   )
+}
+
+F77_SUBROUTINE(ast_polycurve)( INTEGER(THIS),
+                               INTEGER(NPOINT),
+                               INTEGER(NCOORD),
+                               INTEGER(INDIM),
+                               DOUBLE_ARRAY(IN),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(NPOINT)
+   GENPTR_INTEGER(NCOORD)
+   GENPTR_INTEGER(INDIM)
+   GENPTR_DOUBLE_ARRAY(IN)
+
+   astAt( "AST_POLYCURVE", NULL, 0 );
+   astWatchSTATUS(
+      astPolyCurve( astI2P( *THIS ), *NPOINT, *NCOORD, *INDIM,
+                (const double *)IN );
+   )
+}
+
+F77_SUBROUTINE(ast_regionoutline)( INTEGER(THIS),
+                                   INTEGER(REGION),
+                                   INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(REGION)
+
+   astAt( "AST_REGIONOUTLINE", NULL, 0 );
+   astWatchSTATUS(
+      astRegionOutline( astI2P( *THIS ), astI2P( *REGION ) );
+   )
+}
+
+F77_SUBROUTINE(ast_gencurve)( INTEGER(THIS),
+                              INTEGER(MAP),
+                              INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(MAP)
+
+   astAt( "AST_GENCURVE", NULL, 0 );
+   astWatchSTATUS(
+      astGenCurve( astI2P( *THIS ), astI2P( *MAP ) );
+   )
+}
+
+F77_SUBROUTINE(ast_text)( INTEGER(THIS),
+                          CHARACTER(TEXT),
+                          DOUBLE_ARRAY(POS),
+                          REAL_ARRAY(UP),
+                          CHARACTER(JUST),
+                          INTEGER(STATUS)
+                          TRAIL(TEXT)
+                          TRAIL(JUST) ){
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(TEXT)
+   GENPTR_DOUBLE_ARRAY(POS)
+   GENPTR_REAL_ARRAY(UP)
+   GENPTR_CHARACTER(JUST)
+   char *text, *just;
+
+   astAt( "AST_TEXT", NULL, 0 );
+   astWatchSTATUS(
+      text = astString( TEXT, TEXT_length );
+      just = astString( JUST, JUST_length );
+      astText( astI2P( *THIS ), text, POS, UP, just );
+      (void) astFree( (void *) text );
+      (void) astFree( (void *) just );
+   )
+}
+
+F77_SUBROUTINE(ast_grfpush)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   astAt( "AST_GRFPUSH", NULL, 0 );
+   astWatchSTATUS(
+      astGrfPush( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_grfpop)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   astAt( "AST_GRFPOP", NULL, 0 );
+   astWatchSTATUS(
+      astGrfPop( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_bbuf)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   astAt( "AST_BBUF", NULL, 0 );
+   astWatchSTATUS(
+      astBBuf( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_ebuf)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   astAt( "AST_EBUF", NULL, 0 );
+   astWatchSTATUS(
+      astEBuf( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_grfset)( INTEGER(THIS), CHARACTER(NAME),
+                               AstGrfFun FUN, INTEGER(STATUS)
+                               TRAIL(NAME) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   char *name;
+   AstGrfFun fun;
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   int ifun;               /* Index into grf function list */
+   AstGrfWrap wrapper;     /* Wrapper function for C Grf routine*/
+
+   method = "AST_GRFSET";
+   class = "Plot";
+
+   astAt( method, NULL, 0 );
+   astWatchSTATUS(
+
+/* Set the function pointer to NULL if a pointer to
+   the null routine AST_NULL has been supplied. */
+      fun = FUN;
+      if ( fun == (AstGrfFun) F77_EXTERNAL_NAME(ast_null) ) {
+         fun = NULL;
+      }
+
+      name = astString( NAME, NAME_length );
+      astGrfSet( astI2P( *THIS ), name, fun );
+
+      ifun = astGrfFunID( name, method, class );
+
+      if( ifun == AST__GATTR ) {
+         wrapper = (AstGrfWrap) FGAttrWrapper;
+
+      } else if( ifun == AST__GBBUF ) {
+         wrapper = (AstGrfWrap) FGBBufWrapper;
+
+      } else if( ifun == AST__GEBUF ) {
+         wrapper = (AstGrfWrap) FGEBufWrapper;
+
+      } else if( ifun == AST__GFLUSH ) {
+         wrapper = (AstGrfWrap) FGFlushWrapper;
+
+      } else if( ifun == AST__GLINE ) {
+         wrapper = (AstGrfWrap) FGLineWrapper;
+
+      } else if( ifun == AST__GMARK ) {
+         wrapper = (AstGrfWrap) FGMarkWrapper;
+
+      } else if( ifun == AST__GTEXT ) {
+         wrapper = (AstGrfWrap) FGTextWrapper;
+
+      } else if( ifun == AST__GCAP ) {
+         wrapper = (AstGrfWrap) FGCapWrapper;
+
+      } else if( ifun == AST__GTXEXT ) {
+         wrapper = (AstGrfWrap) FGTxExtWrapper;
+
+      } else if( ifun == AST__GQCH ) {
+         wrapper = (AstGrfWrap) FGQchWrapper;
+
+      } else if( ifun == AST__GSCALES ) {
+         wrapper = (AstGrfWrap) FGScalesWrapper;
+
+      } else {
+         wrapper = (AstGrfWrap) FGFlushWrapper;
+         if( astOK ) astError( AST__INTER, "%s(%s): AST internal programming "
+                   "error - Grf function id %d not yet supported.", status,
+                   method, class, ifun );
+      }
+      astGrfWrapper( astI2P( *THIS ), name, wrapper );
+   )
+}
+
+static int FGAttrWrapper( AstPlot *this, int attr, double value,
+                          double *old_value, int prim ) {
+   DECLARE_DOUBLE(OLDVAL);
+   int ret;
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+
+   if ( !astOK ) return 0;
+
+   GRFCON = astP2I( astGrfConID( this ) );
+   ret = ( *(int (*)( INTEGER(grfcon), INTEGER(attr), DOUBLE(value),
+                      DOUBLE(old_value), INTEGER(prim) ))
+                  this->grffun[ AST__GATTR ])(  INTEGER_ARG(&GRFCON),
+                                                INTEGER_ARG(&attr),
+                                                DOUBLE_ARG(&value),
+                                                DOUBLE_ARG(&OLDVAL),
+                                                INTEGER_ARG(&prim) );
+   if( old_value ) *old_value = OLDVAL;
+   return ret;
+}
+
+static int FGBBufWrapper( AstPlot *this ) {
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+   if ( !astOK ) return 0;
+   GRFCON = astP2I( astGrfConID( this ) );
+   return ( *(int (*)(INTEGER(grfcon))) this->grffun[ AST__GBBUF ])(INTEGER_ARG(&GRFCON));
+}
+
+static int FGEBufWrapper( AstPlot *this ) {
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+   if ( !astOK ) return 0;
+   GRFCON = astP2I( astGrfConID( this ) );
+   return ( *(int (*)(INTEGER(grfcon))) this->grffun[ AST__GEBUF ])(INTEGER_ARG(&GRFCON));
+}
+
+static int FGFlushWrapper( AstPlot *this ) {
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+   if ( !astOK ) return 0;
+   GRFCON = astP2I( astGrfConID( this ) );
+   return ( *(int (*)(INTEGER(grfcon))) this->grffun[ AST__GFLUSH ])(INTEGER_ARG(&GRFCON));
+}
+
+static int FGLineWrapper( AstPlot *this, int n, const float *x,
+                          const float *y ) {
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+   if ( !astOK ) return 0;
+   GRFCON = astP2I( astGrfConID( this ) );
+   return ( *(int (*)( INTEGER(grfcon), INTEGER(n), REAL_ARRAY(x), REAL_ARRAY(y) ))
+                  this->grffun[ AST__GLINE ])(  INTEGER_ARG(&GRFCON),
+                                                INTEGER_ARG(&n),
+                                                REAL_ARRAY_ARG(x),
+                                                REAL_ARRAY_ARG(y) );
+}
+
+static int FGMarkWrapper( AstPlot *this, int n, const float *x,
+                          const float *y, int type ) {
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+   if ( !astOK ) return 0;
+   GRFCON = astP2I( astGrfConID( this ) );
+   return ( *(int (*)( INTEGER(grfcon), INTEGER(n), REAL_ARRAY(x), REAL_ARRAY(y),
+                       INTEGER(type) ))
+                  this->grffun[ AST__GMARK ])(  INTEGER_ARG(&GRFCON),
+                                                INTEGER_ARG(&n),
+                                                REAL_ARRAY_ARG(x),
+                                                REAL_ARRAY_ARG(y),
+                                                INTEGER_ARG(&type) );
+}
+
+static int FGTextWrapper( AstPlot *this, const char *text, float x, float y,
+                          const char *just, float upx, float upy ) {
+
+   DECLARE_CHARACTER(LTEXT,MXSTRLEN);
+   DECLARE_CHARACTER(LJUST,MXSTRLEN);
+   int ftext_length;
+   int fjust_length;
+
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+   if ( !astOK ) return 0;
+   GRFCON = astP2I( astGrfConID( this ) );
+
+   ftext_length = strlen( text );
+   if( ftext_length > LTEXT_length ) ftext_length = LTEXT_length;
+   astStringExport( text, LTEXT, ftext_length );
+
+   fjust_length = strlen( just );
+   if( fjust_length > LJUST_length ) fjust_length = LJUST_length;
+   astStringExport( just, LJUST, fjust_length );
+
+   return ( *(int (*)( INTEGER(grfcon), CHARACTER(LTEXT), REAL(x), REAL(y),
+                       CHARACTER(LJUST), REAL(upx), REAL(upy)
+                       TRAIL(ftext) TRAIL(fjust) ) )
+                  this->grffun[ AST__GTEXT ])(
+                                      INTEGER_ARG(&GRFCON),
+                                      CHARACTER_ARG(LTEXT),
+                                      REAL_ARG(&x),
+                                      REAL_ARG(&y),
+                                      CHARACTER_ARG(LJUST),
+                                      REAL_ARG(&upx),
+                                      REAL_ARG(&upy)
+                                      TRAIL_ARG(ftext)
+                                      TRAIL_ARG(fjust) );
+}
+
+static int FGCapWrapper( AstPlot *this, int cap, int value ) {
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+   if ( !astOK ) return 0;
+   GRFCON = astP2I( astGrfConID( this ) );
+   return ( *(int (*)( INTEGER(grfcon), INTEGER(cap), INTEGER(value) ) )
+                  this->grffun[ AST__GCAP ])(
+                                      INTEGER_ARG(&GRFCON),
+                                      INTEGER_ARG(&cap),
+                                      INTEGER_ARG(&value) );
+}
+
+static int FGTxExtWrapper( AstPlot *this, const char *text, float x, float y,
+                           const char *just, float upx, float upy, float *xb,
+                           float *yb ) {
+   DECLARE_CHARACTER(LTEXT,MXSTRLEN);
+   DECLARE_CHARACTER(LJUST,MXSTRLEN);
+   int ftext_length;
+   int fjust_length;
+
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+   if ( !astOK ) return 0;
+   GRFCON = astP2I( astGrfConID( this ) );
+
+   ftext_length = strlen( text );
+   if( ftext_length > LTEXT_length ) ftext_length = LTEXT_length;
+   astStringExport( text, LTEXT, ftext_length );
+
+   fjust_length = strlen( just );
+   if( fjust_length > LJUST_length ) fjust_length = LJUST_length;
+   astStringExport( just, LJUST, fjust_length );
+
+   return ( *(int (*)( INTEGER(grfcon), CHARACTER(LTEXT), REAL(x), REAL(y),
+                       CHARACTER(LJUST), REAL(upx), REAL(upy),
+                       REAL_ARRAY(xb), REAL_ARRAY(yb) TRAIL(ftext)
+                       TRAIL(fjust) ) )
+                  this->grffun[ AST__GTXEXT ])(
+                                                INTEGER_ARG(&GRFCON),
+                                                CHARACTER_ARG(LTEXT),
+                                                REAL_ARG(&x),
+                                                REAL_ARG(&y),
+                                                CHARACTER_ARG(LJUST),
+                                                REAL_ARG(&upx),
+                                                REAL_ARG(&upy),
+                                                REAL_ARRAY_ARG(xb),
+                                                REAL_ARRAY_ARG(yb)
+                                                TRAIL_ARG(ftext)
+                                                TRAIL_ARG(fjust) );
+}
+
+static int FGQchWrapper( AstPlot *this, float *chv, float *chh ) {
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+   if ( !astOK ) return 0;
+   GRFCON = astP2I( astGrfConID( this ) );
+   return ( *(int (*)( INTEGER(grfcon), REAL(chv), REAL(chh) ) )
+                  this->grffun[ AST__GQCH ])( INTEGER_ARG(&GRFCON), REAL_ARG(chv), REAL_ARG(chh) );
+}
+
+static int FGScalesWrapper( AstPlot *this, float *alpha, float *beta ) {
+   F77_INTEGER_TYPE(GRFCON);
+   int *status = astGetStatusPtr;
+   if ( !astOK ) return 0;
+   GRFCON = astP2I( astGrfConID( this ) );
+   return ( *(int (*)( INTEGER(grfcon), REAL(alpha), REAL(beta) ) )
+                  this->grffun[ AST__GSCALES ])( INTEGER_ARG(&GRFCON), REAL_ARG(alpha), REAL_ARG(beta) );
+}
+
+
+/* NO_CHAR_FUNCTION indicates that the f77.h method of returning a
+   character result doesn't work, so add an extra argument instead and
+   wrap this function up in a normal FORTRAN 77 function (in the file
+   plot.f). */
+#if NO_CHAR_FUNCTION
+F77_SUBROUTINE(ast_stripescapes_a)( CHARACTER(RESULT),
+#else
+F77_SUBROUTINE(ast_stripescapes)( CHARACTER_RETURN_VALUE(RESULT),
+#endif
+                          CHARACTER(TEXT),
+                          INTEGER(STATUS)
+#if NO_CHAR_FUNCTION
+                          TRAIL(RESULT)
+#endif
+                          TRAIL(TEXT) ) {
+   GENPTR_CHARACTER(RESULT)
+   GENPTR_CHARACTER(TEXT)
+   char *text;
+   const char *result;
+   int i;
+
+   astAt( "AST_STRIPESCAPES", NULL, 0 );
+   astWatchSTATUS(
+      text = astString( TEXT, TEXT_length );
+      result = astStripEscapes( text );
+      i = 0;
+      if ( astOK ) {             /* Copy result */
+         for ( ; result[ i ] && i < RESULT_length; i++ ) {
+            RESULT[ i ] = result[ i ];
+         }
+      }
+      while ( i < RESULT_length ) RESULT[ i++ ] = ' '; /* Pad with blanks */
+      astFree( text );
+   )
+}
+
+F77_LOGICAL_FUNCTION(ast_getgrfcontext)( INTEGER(THIS),
+                                         INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETGRFCONTEXT", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astGetGrfContext( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+
+
+
diff --git a/ast/fplot3d.c b/ast/fplot3d.c
new file mode 100644
index 0000000..4246f50
--- /dev/null
+++ b/ast/fplot3d.c
@@ -0,0 +1,107 @@
+/*
+*+
+*  Name:
+*     fplot3d.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Plot3D class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Plot3D class.
+
+*  Routines Defined:
+*     AST_ISAPLOT3D
+*     AST_PLOT3D
+
+*  Copyright:
+*     Copyright (C) 2007 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     6-JUN-2007 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "plot3d.h"           /* C interface to the Plot3D class */
+
+F77_LOGICAL_FUNCTION(ast_isaplot3d)( INTEGER(THIS),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAPLOT3D", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAPlot3D( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_plot3d)( INTEGER(FRAME),
+                                REAL_ARRAY(GRAPHBOX),
+                                DOUBLE_ARRAY(BASEBOX),
+                                CHARACTER(OPTIONS),
+                                INTEGER(STATUS)
+                                TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_REAL_ARRAY(GRAPHBOX)
+   GENPTR_DOUBLE_ARRAY(BASEBOX)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_PLOT3D", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astPlot3D( astI2P( *FRAME ), GRAPHBOX, BASEBOX,
+                                "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
diff --git a/ast/fpointlist.c b/ast/fpointlist.c
new file mode 100644
index 0000000..9644fdc
--- /dev/null
+++ b/ast/fpointlist.c
@@ -0,0 +1,117 @@
+/*
+*+
+*  Name:
+*     fpointlist.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST PointList class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the PointList class.
+
+*  Routines Defined:
+*     AST_ISAPOINTLIST
+*     AST_POINTLIST
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     23-AUG-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "pointlist.h"           /* C interface to the PointList class */
+
+F77_LOGICAL_FUNCTION(ast_isapointlist)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAPOINTLIST", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAPointList( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_pointlist)( INTEGER(FRAME),
+                                     INTEGER(NPNT),
+                                     INTEGER(COORD),
+                                     INTEGER(INDIM),
+                                     DOUBLE_ARRAY(POINTS),
+                                     INTEGER(UNC),
+                                     CHARACTER(OPTIONS),
+                                     INTEGER(STATUS)
+                                     TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_INTEGER(NPNT)
+   GENPTR_INTEGER(COORD)
+   GENPTR_INTEGER(INDIM)
+   GENPTR_DOUBLE_ARRAY(POINTS)
+   GENPTR_INTEGER(UNC)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_POINTLIST", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+      RESULT = astP2I( astPointList( astI2P( *FRAME ), *NPNT, *COORD,
+                                     *INDIM, POINTS, astI2P( *UNC ), "%s",
+                                     options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+
+
+
diff --git a/ast/fpolygon.c b/ast/fpolygon.c
new file mode 100644
index 0000000..12247d7
--- /dev/null
+++ b/ast/fpolygon.c
@@ -0,0 +1,226 @@
+/*
+*+
+*  Name:
+*     fpolygon.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Polygon class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Polygon class.
+
+*  Routines Defined:
+*     AST_ISAPOLYGON
+*     AST_POLYGON
+*     AST_DOWNSIZE
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     27-OCT-2004 (DSB):
+*        Original version.
+*     28-MAY-2009 (DSB):
+*        Added AST_DOWNSIZE.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "polygon.h"             /* C interface to the Polygon class */
+
+F77_LOGICAL_FUNCTION(ast_isapolygon)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAPOLYGON", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAPolygon( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_polygon)( INTEGER(FRAME),
+                                   INTEGER(NPNT),
+                                   INTEGER(INDIM),
+                                   DOUBLE_ARRAY(POINTS),
+                                   INTEGER(UNC),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME)
+   GENPTR_INTEGER(NPNT)
+   GENPTR_INTEGER(INDIM)
+   GENPTR_DOUBLE_ARRAY(POINTS)
+   GENPTR_INTEGER(UNC)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_POLYGON", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+      RESULT = astP2I( astPolygon( astI2P( *FRAME ), *NPNT, *INDIM, POINTS,
+                                   astI2P( *UNC ), "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_downsize)( INTEGER(THIS),
+                                    DOUBLE(MAXERR),
+                                    INTEGER(MAXVERT),
+                                    INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE(MAXERR)
+   GENPTR_INTEGER(MAXVERT)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_DOWNSIZE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astDownsize( astI2P( *THIS ), *MAXERR, *MAXVERT ) );
+   )
+   return RESULT;
+}
+
+
+/* AST_OUTLINE<X> requires a function for each possible data type, so
+   define it via a macro. */
+#define MAKE_AST_OUTLINE(f,F,Ftype,X,Xtype) \
+F77_INTEGER_FUNCTION(ast_outline##f)( Ftype(VALUE), \
+                                      INTEGER(OPER), \
+                                      Ftype##_ARRAY(ARRAY), \
+                                      INTEGER_ARRAY(LBND), \
+                                      INTEGER_ARRAY(UBND), \
+                                      DOUBLE(MAXERR), \
+                                      INTEGER(MAXVERT), \
+                                      INTEGER_ARRAY(INSIDE), \
+                                      LOGICAL(STARPIX), \
+                                      INTEGER(STATUS) ) { \
+   GENPTR_##Ftype(VALUE) \
+   GENPTR_INTEGER(OPER) \
+   GENPTR_##Ftype##_ARRAY(ARRAY) \
+   GENPTR_INTEGER_ARRAY(LBND) \
+   GENPTR_INTEGER_ARRAY(UBND) \
+   GENPTR_DOUBLE(MAXERR) \
+   GENPTR_INTEGER(MAXVERT) \
+   GENPTR_INTEGER_ARRAY(INSIDE) \
+   GENPTR_LOGICAL(STARPIX) \
+   GENPTR_INTEGER(STATUS) \
+\
+   F77_INTEGER_TYPE RESULT; \
+\
+   astAt( "AST_OUTLINE"#F, NULL, 0 ); \
+   astWatchSTATUS( \
+      RESULT = astP2I( astOutline##X( *VALUE, *OPER, (Xtype *) ARRAY, LBND, \
+                                       UBND, *MAXERR, *MAXVERT, INSIDE, \
+                                       F77_ISTRUE( *STARPIX ) ? 1 : 0 ) ); \
+   ) \
+   return RESULT; \
+}
+
+/* Invoke the above macro to define a function for each data
+   type. Include synonyms for some functions. */
+MAKE_AST_OUTLINE(d,D,DOUBLE,D,double)
+MAKE_AST_OUTLINE(r,R,REAL,F,float)
+MAKE_AST_OUTLINE(i,I,INTEGER,I,int)
+MAKE_AST_OUTLINE(ui,UI,INTEGER,UI,unsigned int)
+MAKE_AST_OUTLINE(s,S,WORD,S,short int)
+MAKE_AST_OUTLINE(us,US,UWORD,US,unsigned short int)
+MAKE_AST_OUTLINE(w,W,WORD,S,short int)
+MAKE_AST_OUTLINE(uw,UW,UWORD,US,unsigned short int)
+MAKE_AST_OUTLINE(b,B,BYTE,B,signed char)
+MAKE_AST_OUTLINE(ub,UB,UBYTE,UB,unsigned char)
+#undef MAKE_AST_OUTLINE
+
+
+/* AST_CONVEX<X> requires a function for each possible data type, so
+   define it via a macro. */
+#define MAKE_AST_CONVEX(f,F,Ftype,X,Xtype) \
+F77_INTEGER_FUNCTION(ast_convex##f)( Ftype(VALUE), \
+                                      INTEGER(OPER), \
+                                      Ftype##_ARRAY(ARRAY), \
+                                      INTEGER_ARRAY(LBND), \
+                                      INTEGER_ARRAY(UBND), \
+                                      LOGICAL(STARPIX), \
+                                      INTEGER(STATUS) ) { \
+   GENPTR_##Ftype(VALUE) \
+   GENPTR_INTEGER(OPER) \
+   GENPTR_##Ftype##_ARRAY(ARRAY) \
+   GENPTR_INTEGER_ARRAY(LBND) \
+   GENPTR_INTEGER_ARRAY(UBND) \
+   GENPTR_LOGICAL(STARPIX) \
+   GENPTR_INTEGER(STATUS) \
+\
+   F77_INTEGER_TYPE RESULT; \
+\
+   astAt( "AST_CONVEX"#F, NULL, 0 ); \
+   astWatchSTATUS( \
+      RESULT = astP2I( astConvex##X( *VALUE, *OPER, (Xtype *) ARRAY, LBND, \
+                                     UBND, F77_ISTRUE( *STARPIX ) ? 1 : 0 ) ); \
+   ) \
+   return RESULT; \
+}
+
+/* Invoke the above macro to define a function for each data
+   type. Include synonyms for some functions. */
+MAKE_AST_CONVEX(d,D,DOUBLE,D,double)
+MAKE_AST_CONVEX(r,R,REAL,F,float)
+MAKE_AST_CONVEX(i,I,INTEGER,I,int)
+MAKE_AST_CONVEX(ui,UI,INTEGER,UI,unsigned int)
+MAKE_AST_CONVEX(s,S,WORD,S,short int)
+MAKE_AST_CONVEX(us,US,UWORD,US,unsigned short int)
+MAKE_AST_CONVEX(w,W,WORD,S,short int)
+MAKE_AST_CONVEX(uw,UW,UWORD,US,unsigned short int)
+MAKE_AST_CONVEX(b,B,BYTE,B,signed char)
+MAKE_AST_CONVEX(ub,UB,UBYTE,UB,unsigned char)
+#undef MAKE_AST_CONVEX
+
+
diff --git a/ast/fpolymap.c b/ast/fpolymap.c
new file mode 100644
index 0000000..6e3fb55
--- /dev/null
+++ b/ast/fpolymap.c
@@ -0,0 +1,140 @@
+/*
+*+
+*  Name:
+*     fpolymap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST PolyMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the PolyMap class.
+
+*  Routines Defined:
+*     AST_ISAPOLYMAP
+*     AST_POLYMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     27-SEP-2003 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "polymap.h"             /* C interface to the PolyMap class */
+
+F77_LOGICAL_FUNCTION(ast_isapolymap)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAPOLYMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAPolyMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_polymap)( INTEGER(NIN),
+                                   INTEGER(NOUT),
+                                   INTEGER(NCOEFF_F),
+                                   DOUBLE_ARRAY(COEFF_F),
+                                   INTEGER(NCOEFF_I),
+                                   DOUBLE_ARRAY(COEFF_I),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NIN)
+   GENPTR_INTEGER(NOUT)
+   GENPTR_INTEGER(NCOEFF_F)
+   GENPTR_DOUBLE_ARRAY(COEFF_F)
+   GENPTR_INTEGER(NCOEFF_I)
+   GENPTR_DOUBLE_ARRAY(COEFF_I)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_POLYMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astPolyMap( *NIN, *NOUT, *NCOEFF_F, COEFF_F, *NCOEFF_I,
+                                   COEFF_I, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+
+F77_INTEGER_FUNCTION(ast_polytran)( INTEGER(THIS),
+                                    LOGICAL(FORWARD),
+                                    DOUBLE(ACC),
+                                    DOUBLE(MAXACC),
+                                    INTEGER(MAXORDER),
+                                    DOUBLE_ARRAY(LBND),
+                                    DOUBLE_ARRAY(UBND),
+                                    INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_LOGICAL(FORWARD)
+   GENPTR_DOUBLE(ACC)
+   GENPTR_DOUBLE(MAXACC)
+   GENPTR_INTEGER(MAXORDER)
+   GENPTR_DOUBLE_ARRAY(LBND)
+   GENPTR_DOUBLE_ARRAY(UBND)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_POLYTRAN", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astPolyTran( astI2P( *THIS ), F77_ISTRUE( *FORWARD ),
+                                    *ACC, *MAXACC, *MAXORDER, LBND, UBND ) );
+   )
+   return RESULT;
+}
+
+
diff --git a/ast/fprism.c b/ast/fprism.c
new file mode 100644
index 0000000..7dc70b7
--- /dev/null
+++ b/ast/fprism.c
@@ -0,0 +1,105 @@
+/*
+*+
+*  Name:
+*     fprism.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Prism class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Prism class.
+
+*  Routines Defined:
+*     AST_ISAPRISM
+*     AST_PRISM
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     10-JAN-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "prism.h"           /* C interface to the Prism class */
+
+
+F77_LOGICAL_FUNCTION(ast_isaprism)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAPRISM", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAPrism( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_prism)( INTEGER(REG1),
+                                 INTEGER(REG2),
+                                 CHARACTER(OPTIONS),
+                                 INTEGER(STATUS)
+                                 TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(REG1)
+   GENPTR_INTEGER(REG2)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_PRISM", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+      RESULT = astP2I( astPrism( astI2P( *REG1 ), astI2P( *REG2 ),
+                                 "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/frame.c b/ast/frame.c
new file mode 100644
index 0000000..9cab3b6
--- /dev/null
+++ b/ast/frame.c
@@ -0,0 +1,15978 @@
+/*
+*class++
+*  Name:
+*     Frame
+
+*  Purpose:
+*     Coordinate system description.
+
+*  Constructor Function:
+c     astFrame
+f     AST_FRAME
+
+*  Description:
+*     This class is used to represent coordinate systems. It does this
+*     in rather the same way that a frame around a graph describes the
+*     coordinate space in which data are plotted. Consequently, a
+*     Frame has a Title (string) attribute, which describes the
+*     coordinate space, and contains axes which in turn hold
+*     information such as Label and Units strings which are used for
+*     labelling (e.g.) graphical output. In general, however, the
+*     number of axes is not restricted to two.
+*
+*     Functions are available for converting Frame coordinate values
+*     into a form suitable for display, and also for calculating
+*     distances and offsets between positions within the Frame.
+*
+*     Frames may also contain knowledge of how to transform to and
+*     from related coordinate systems.
+
+*  Inheritance:
+*     The Frame class inherits from the Mapping class.
+
+*  Attributes:
+*     In addition to those attributes common to all Mappings, every
+*     Frame also has the following attributes (if the Frame has only one
+*     axis, the axis specifier can be omited from the following attribute
+*     names):
+*
+*     - AlignSystem: Coordinate system used to align Frames
+*     - Bottom(axis): Lowest axis value to display
+*     - Digits/Digits(axis): Number of digits of precision
+*     - Direction(axis): Display axis in conventional direction?
+*     - Domain: Coordinate system domain
+*     - Dut1: Difference between the UT1 and UTC timescale
+*     - Epoch: Epoch of observation
+*     - Format(axis): Format specification for axis values
+*     - InternalUnit(axis): Physical units for unformated axis values
+*     - Label(axis): Axis label
+*     - MatchEnd: Match trailing axes?
+*     - MaxAxes: Maximum number of Frame axes to match
+*     - MinAxes: Minimum number of Frame axes to match
+*     - Naxes: Number of Frame axes
+*     - NormUnit(axis): Normalised physical units for formatted axis values
+*     - ObsAlt: Geodetic altitude of observer
+*     - ObsLat: Geodetic latitude of observer
+*     - ObsLon: Geodetic longitude of observer
+*     - Permute: Permute axis order?
+*     - PreserveAxes: Preserve axes?
+*     - Symbol(axis): Axis symbol
+*     - System: Coordinate system used to describe the domain
+*     - Title: Frame title
+*     - Top(axis): Highest axis value to display
+*     - Unit(axis): Physical units for formatted axis values
+
+*  Functions:
+c     In addition to those functions applicable to all Mappings, the
+c     following functions may also be applied to all Frames:
+f     In addition to those routines applicable to all Mappings, the
+f     following routines may also be applied to all Frames:
+*
+c     - astAngle: Calculate the angle subtended by two points at a third point
+c     - astAxAngle: Find the angle from an axis, to a line through two points
+c     - astAxDistance: Calculate the distance between two axis values
+c     - astAxNorm: Normalises an array of axis values
+c     - astAxOffset: Calculate an offset along an axis
+c     - astConvert: Determine how to convert between two coordinate systems
+c     - astDistance: Calculate the distance between two points in a Frame
+c     - astFindFrame: Find a coordinate system with specified characteristics
+c     - astFormat: Format a coordinate value for a Frame axis
+c     - astGetActiveUnit: Determines how the Unit attribute will be used
+c     - astIntersect: Find the intersection between two geodesic curves
+c     - astMatchAxes: Find any corresponding axes in two Frames
+c     - astNorm: Normalise a set of Frame coordinates
+c     - astOffset: Calculate an offset along a geodesic curve
+c     - astOffset2: Calculate an offset along a geodesic curve in a 2D Frame
+c     - astPermAxes: Permute the order of a Frame's axes
+c     - astPickAxes: Create a new Frame by picking axes from an existing one
+c     - astResolve: Resolve a vector into two orthogonal components
+c     - astSetActiveUnit: Specify how the Unit attribute should be used
+c     - astUnformat: Read a formatted coordinate value for a Frame axis
+f     - AST_ANGLE: Find the angle subtended by two points at a third point
+f     - AST_AXANGLE: Find the angle from an axis, to a line through two points
+f     - AST_AXDISTANCE: Calculate the distance between two axis values
+f     - AST_AXNORM: Normalises an array of axis values
+f     - AST_AXOFFSET: Calculate an offset along an axis
+f     - AST_CONVERT: Determine how to convert between two coordinate systems
+f     - AST_DISTANCE: Calculate the distance between two points in a Frame
+f     - AST_FINDFRAME: Find a coordinate system with specified characteristics
+f     - AST_FORMAT: Format a coordinate value for a Frame axis
+f     - AST_GETACTIVEUNIT: Determines how the Unit attribute will be used
+f     - AST_INTERSECT: Find the intersection between two geodesic curves
+f     - AST_MATCHAXES: Find any corresponding axes in two Frames
+f     - AST_NORM: Normalise a set of Frame coordinates
+f     - AST_OFFSET: Calculate an offset along a geodesic curve
+f     - AST_OFFSET2: Calculate an offset along a geodesic curve in a 2D Frame
+f     - AST_PERMAXES: Permute the order of a Frame's axes
+f     - AST_PICKAXES: Create a new Frame by picking axes from an existing one
+f     - AST_RESOLVE: Resolve a vector into two orthogonal components
+f     - AST_SETACTIVEUNIT: Specify how the Unit attribute should be used
+f     - AST_UNFORMAT: Read a formatted coordinate value for a Frame axis
+
+*  Notes:
+*     - When used as a Mapping, a Frame implements a unit (null)
+*     transformation in both the forward and inverse directions
+*     (equivalent to a UnitMap). The Nin and Nout attribute values are
+*     both equal to the number of Frame axes.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: B.S. Berry (Starlink)
+
+*  History:
+*     1-MAR-1996 (RFWS):
+*        Original version.
+*     4-JUN-1996 (RFWS):
+*        Added the CleanDomain function to fold all Domain strings to
+*        upper case and remove white space.
+*     12-JUL-1996 (RFWS):
+*        Over-ride the astReportPoints method to provide
+*        Frame-specific formatting.
+*     11-SEP-1996 (RFWS):
+*        Added astGap (written by DSB).
+*     10-JUN-1997 (RFWS):
+*        Re-implemented astConvert and astFindFrame.
+*     1-SEP-1997 (RFWS):
+*        Added missing return statement in astAbbrev_.
+*     14-NOV-1997 (RFWS):
+*        Fixed wrong amount of memory allocated in ValidateAxisSelection.
+*     20-NOV-1997 (RFWS):
+*        Updated astConvert prologue.
+*     22-DEC-1997 (RFWS):
+*        Updated astConvert prologue again.
+*     15-FEB-1998 (RFWS):
+*        Added astUnformat.
+*     2-MAR-1999 (RFWS);
+*        Fixed missing STATUS arguments in examples for AST_FINDFRAME
+*        prologue.
+*     18-JUL-1999 (RFWS):
+*        Fixed memory leak in ConvertX.
+*     21-JUN-2001 (DSB):
+*        Added methods astAngle and astOffset2.
+*     29-AUG-2001 (DSB):
+*        Added methods astAxDistance and astAxOffset.
+*     4-SEP-2001 (DSB):
+*        Added method astResolve.
+*     9-SEP-2001 (DSB):
+*        Added method astBear.
+*     21-SEP-2001 (DSB):
+*        Replaced astBear with astAxAngle.
+*     10-OCT-2002 (DSB):
+*        Added Top and Bottom.
+*     15-NOV-2002 (DSB):
+*        Moved the System and Epoch attributes from the SkyFrame class to
+*        this class. Added virtual method astValidateSystem, astSystemString,
+*        astSystemCode. Added attribute AlignSystem.
+*     17-DEC-2002 (DSB):
+*        Added the GetActiveUnit, TestActiveUnit and SetActiveUnit functions.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitFrameVtab
+*        method.
+*     15-SEP-2003 (DSB):
+*        Allow Frame attribute names to include an axis specifier within
+*        GetAttrib, SetAttrib, TestAttrib and ClearAttrib (eg "Domain(1)"
+*        is now accepted as equivalent to "Domain").
+*     24-JAN-2004 (DSB):
+*        o  Added astFields.
+*        o  Added argument "fmt" to Abbrev.
+*     24-MAR-2004 (DSB):
+*        Add protected function astIsUnitFrame.
+*     7-SEP-2004 (DSB):
+*        Modified SetUnit to exclude any trailing spaces
+*     8-SEP-2004 (DSB):
+*        - Added astResolvePoints.
+*        - Override astEqual.
+*     29-NOV-2004 (DSB):
+*        - Set/Get/Test/ClearAttrib: Allow axis specifier to be omitted from
+*        axis attribute names if the Frame only has one axis.
+*     2-FEB-2005 (DSB):
+*        - Avoid using astStore to allocate more storage than is supplied
+*        in the "data" pointer. This can cause access violations since
+*        astStore will then read beyond the end of the "data" area.
+*     17-FEB-2005 (DSB):
+*        - Change use of ActiveUnit flag so that both target and template
+*        Frames must have active units in order for the Mapping to take
+*        account of differences in units. Previously, the test was based
+*        on the template Frame alone.
+*     23-MAR-2005 (DSB):
+*        - GetActiveUnit: Always return zero if the Frame contains any
+*        SkyAxes.
+*     5-APR-2005 (DSB):
+*        Correct error checking in Clear/Get/Set/TestAttrib.
+*     12-MAY-2005 (DSB):
+*        Added astNormBox method.
+*     16-JUN-2005 (DSB):
+*        Added documentation for the TimeFrame class.
+*     12-AUG-2005 (DSB):
+*        Added ObsLat and ObsLon attributes.
+*     1-MAR-2006 (DSB):
+*        Replace astSetPermMap within DEBUG blocks by astBeginPM/astEndPM.
+*     15-MAY-2006 (DSB):
+*        Remove unused global variable parent_equal.
+*     26-JUN-2006 (DSB):
+*        Document the use of the Direction attribute by the Plot class.
+*     30-JUN-2006 (DSB):
+*        Allow astAbbrev to have a null "str1" value.
+*     16-AUG-2006 (DSB):
+*        Correct "Class Applicability" to "Applicability".
+*     5-OCT-2006 (DSB):
+*        Increase the number of digits used when formating a ObsLon or
+*        ObsLat value in GetAttrib.
+*     14-OCT-2006 (DSB):
+*        - Add Dut1 attribute
+*     26-JAN-2007 (DSB):
+*        Fix bug in NewUnit that causes segvio when changing axis symbols
+*        to accomodate changes in units.
+*     17-MAY-2007 (DSB):
+*        Added read-only attribute NormUnit.
+*     21-MAY-2007 (DSB):
+*        Use rather than ignore the value returned by astTestAxisDigits in
+*        TestAttrib.
+*     25-JUN-2007 (DSB):
+*        Documentation typos.
+*     26-NOV-2007 (DSB):
+*        In Clear/Get/Set/TestAttrib, include any appropriate axis index in
+*        the attribute name when attempting to get the attribute value from
+*        the primary frame
+*     17-NOV-2008 (DSB):
+*        Correct parent class in invocation of astMAKE_ISA.
+*     14-JAN-2009 (DSB):
+*        Added astIntersect.
+*     18-MAR-2009 (DSB):
+*        Fixed bug in LineCrossing.
+*     18-JUN-2000 (DSB):
+*        Added ObsAlt attribute.
+*     28-SEP-2009 (DSB):
+*        Added astMatchAxes method.
+*     22-MAR-2011 (DSB):
+*        Add astFrameGrid method.
+*     29-APR-2011 (DSB):
+*        Prevent astFindFrame from matching a subclass template against a
+*        superclass target.
+*     11-APR-2012 (DSB):
+*        Change astValidateAxis so that it can permute in either direction.
+*     29-APR-2013 (DSB):
+*        Added protected methods astSetFrameVariants and astGetFrameVariants.
+*     1-MAY-2013 (DSB):
+*        Override the astDoNotSimplify method to indicate that Frames should
+*        always be simplified. This is mainly because the STC class uses the
+*        Ident attribute with Frames, and preventing such frames from
+*        simplifying (which is what the parent astDoNotSimplify method does)
+*        causes the STC tester in the ast_tester directory to fail.
+*     10-FEB-2015 (DSB):
+*        When checking attribute settings for attribute names that end with
+*        an axis index, stop looking for the axis index when the first equals
+*        sign is encountered.
+*     17-APR-2015 (DSB):
+*        Added astCentre.
+*     27-APR-2015 (DSB):
+*        Added read-only attribute InternalUnit.
+*     26-OCT-2016 (DSB):
+*        Added method astAxNorm.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Frame
+
+/* Define numerical constants for use in this module. */
+#define LABEL_BUFF_LEN 100       /* Max length of default axis Label string */
+#define SYMBOL_BUFF_LEN 50       /* Max length of default axis Symbol string */
+#define TITLE_BUFF_LEN 100       /* Max length of default title string */
+#define GETATTRIB_BUFF_LEN 50    /* Max length of string returned by GetAttrib */
+#define ASTFMTDECIMALYR_BUFF_LEN 50    /* Max length of string returned by GetAttrib */
+#define ASTFORMATID_MAX_STRINGS 50     /* Number of string values buffer by astFormatID*/
+
+
+/* Define the first and last acceptable System values. */
+#define FIRST_SYSTEM AST__CART
+#define LAST_SYSTEM AST__CART
+
+/* Define macros to implement methods for accessing axis attributes. */
+/*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Implement a method to clear an attribute value for a Frame axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frame.h"
+*     MAKE_CLEAR(attribute)
+
+*  Class Membership:
+*     Defined by the Frame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstFrame *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( AstFrame *this, int axis )
+*
+*     which implement a method for clearing an attribute value for a specified
+*     axis of a Frame.
+
+*  Parameters:
+*     attribute
+*        The name of the attribute to be cleared, as it appears in the
+*        function name (e.g. Label in "astClearLabel").
+
+*  Notes:
+*     -  This macro assumes the existence of a method of the form:
+*
+*        void astClearAxis<Attribute>( AstAxis *this )
+*
+*     which clears the required attribute for an Axis object.
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attribute) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attribute( AstFrame *this, int axis, int *status ) { \
+   AstAxis *ax;                  /* Pointer to Axis object */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index and obtain a pointer to the required Axis. */ \
+   (void) astValidateAxis( this, axis, 1, "astClear" #attribute ); \
+   ax = astGetAxis( this, axis ); \
+\
+/* Clear the Axis attribute. */ \
+   astClearAxis##attribute( ax ); \
+\
+/* Annul the Axis pointer. */ \
+   ax = astAnnul( ax ); \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attribute##_( AstFrame *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,Frame,Clear##attribute))( this, axis, status ); \
+}
+
+/*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Implement a method to get an attribute value for a Frame axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+#     #include "frame.h"
+*     MAKE_GET(attribute,type,bad_value,default,assign_default)
+
+*  Class Membership:
+*     Defined by the Frame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( AstFrame *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        Type astGet<Attribute>_( AstFrame *this, int axis )
+*
+*     which implement a method for getting an attribute value for a specified
+*     axis of a Frame.
+
+*  Parameters:
+*     attribute
+*        The name of the attribute whose value is to be obtained, as
+*        it appears in the function name (e.g. Label in
+*        "astGetLabel").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set, or if
+*        the function fails.
+*     default
+*        A boolean (int) constant that indicates whether a new default value
+*        should be returned by the method if the requested attribute has not
+*        been set for the axis. If this value is zero, the axis default will
+*        be used instead.
+*     assign_default
+*        An expression that evaluates to the new default value to be assigned.
+*        This value is ignored if "default" is zero, but a valid (e.g.
+*        constant) value should nevertheless be supplied.
+
+*  Notes:
+*     -  This macro assumes the existence of a method of the form:
+*
+*           <Type> astGetAxis<Attribute>( AstAxis *this )
+*
+*     which gets the required attribute for an Axis object.
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attribute,type,bad_value,default,assign_default) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type Get##attribute( AstFrame *this, int axis, int *status ) { \
+   AstAxis *ax;                  /* Pointer to Axis object */ \
+   int digits_set;               /* Axis Digits attribute set? */ \
+   int old_axis;                 /* Original (un-permuted) axis index */ \
+   type result;                  /* Result to be returned */ \
+\
+/* Initialise. */ \
+   result = (bad_value); \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate and permute the axis index and obtain a pointer to the required \
+   Axis. */ \
+   old_axis = axis; \
+   axis = astValidateAxis( this, axis, 1, "astGet" #attribute ); \
+   ax = astGetAxis( this, old_axis ); \
+\
+/* Since the Axis is "managed" by the enclosing Frame, we next test if any \
+   Axis attributes which may affect the result are undefined (i.e. have not \
+   been explicitly set). If so, we over-ride them, giving them temporary \
+   values dictated by the Frame. Only the Digits attribute is relevant \
+   here. */ \
+   digits_set = astTestAxisDigits( ax ); \
+   if ( !digits_set ) astSetAxisDigits( ax, astGetDigits( this ) ); \
+\
+/* If the default value is to be over-ridden, test if the Axis attribute has \
+   been set. Then, if required, obtain the attribute value from the Axis. */ \
+   if ( !(default) || astTestAxis##attribute( ax ) ) { \
+      result = astGetAxis##attribute( ax ); \
+\
+/* If required, assign the new default value. */ \
+   } else { \
+      result = (assign_default); \
+   } \
+\
+/* Clear Axis attributes which were temporarily over-ridden above and annul \
+   the Axis pointer. */ \
+   if ( !digits_set ) astClearAxisDigits( ax ); \
+   ax = astAnnul( ax ); \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+type astGet##attribute##_( AstFrame *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,Frame,Get##attribute))( this, axis, status ); \
+}
+
+/*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Implement a method to set an attribute value for a Frame axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frame.h"
+*     MAKE_SET(attribute,type)
+
+*  Class Membership:
+*     Defined by the Frame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstFrame *this, int axis, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( AstFrame *this, int axis, <Type> value )
+*
+*     which implement a method for setting an attribute value for a specified
+*     axis of a Frame.
+
+*  Parameters:
+*      attribute
+*         The name of the attribute to be set, as it appears in the
+*         function name (e.g. Label in "astSetLabel").
+*      type
+*         The C type of the attribute.
+
+*  Notes:
+*     -  This macro assumes the existence of a method of the form:
+*
+*           void astSetAxis<Attribute>( AstAxis *this, <Type> value )
+*
+*     which sets the required attribute for an Axis object.
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attribute,type) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attribute( AstFrame *this, int axis, type value, int *status ) { \
+   AstAxis *ax;                  /* Pointer to Axis object */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index and obtain a pointer to the required Axis. */ \
+   (void) astValidateAxis( this, axis, 1, "astSet" #attribute ); \
+   ax = astGetAxis( this, axis ); \
+\
+/* Set the Axis attribute value. */ \
+   astSetAxis##attribute( ax, value ); \
+\
+/* Annul the Axis pointer. */ \
+   ax = astAnnul( ax ); \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attribute##_( AstFrame *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,Frame,Set##attribute))( this, axis, value, status ); \
+}
+
+/*
+*  Name:
+*     MAKE_TEST
+
+*  Purpose:
+*     Implement a method to test if an attribute has been set for a Frame axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frame.h"
+*     MAKE_TEST(attribute)
+
+*  Class Membership:
+*     Defined by the Frame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static int Test<Attribute>( AstFrame *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        int astTest<Attribute>_( AstFrame *this, int axis )
+*
+*     which implement a method for testing if an attribute has been set for a
+*     specified axis of a Frame.
+
+*  Parameters:
+*      attribute
+*         The name of the attribute to be tested, as it appears in the
+*         function name (e.g. Label in "astTestLabel").
+
+*  Notes:
+*     -  This macro assumes the existence of a method of the form:
+*
+*           void astTestAxis<Attribute>( AstAxis *this )
+*
+*     which tests the required attribute for an Axis object.
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_TEST(attribute) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static int Test##attribute( AstFrame *this, int axis, int *status ) { \
+   AstAxis *ax;                  /* Pointer to Axis object */ \
+   int result;                   /* Value to be returned */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Validate the axis index and obtain a pointer to the required Axis. */ \
+   (void) astValidateAxis( this, axis, 1, "astTest" #attribute ); \
+   ax = astGetAxis( this, axis ); \
+\
+/* Test if the attribute has been set. */ \
+   result = astTestAxis##attribute( ax ); \
+\
+/* Annul the Axis pointer. */ \
+   ax = astAnnul( ax ); \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+int astTest##attribute##_( AstFrame *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,Frame,Test##attribute))( this, axis, status ); \
+}
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "pointset.h"            /* Sets of points */
+#include "unitmap.h"             /* Unit Mapping */
+#include "permmap.h"             /* Coordinate permutation Mapping */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "axis.h"                /* Coordinate Axis */
+#include "skyaxis.h"             /* Sky coordinate axes */
+#include "skyframe.h"            /* Celestial coordinate frames */
+#include "channel.h"             /* I/O channels */
+#include "frame.h"               /* Interface definition for this class */
+#include "frameset.h"            /* Collections of Frames */
+#include "cmpframe.h"            /* Compound Frames */
+#include "pal.h"                 /* SLALIB library interface */
+#include "unit.h"                /* Units identification and mapping */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <limits.h>
+#include <math.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static void (* parent_cleanattribs)( AstObject *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Define a variable to hold a SkyFrame which will be used for formatting
+   and unformatting ObsLat and ObsLon values. */
+static AstSkyFrame *skyframe;
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->AstFormatID_Init = 0; \
+   globals->AstFormatID_Istr = 0; \
+   globals->Label_Buff[ 0 ] = 0; \
+   globals->Symbol_Buff[ 0 ] = 0; \
+   globals->Title_Buff[ 0 ] = 0; \
+   globals->AstFmtDecimalYr_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Frame)
+
+#define class_init astGLOBAL(Frame,Class_Init)
+#define class_vtab astGLOBAL(Frame,Class_Vtab)
+#define getattrib_buff astGLOBAL(Frame,GetAttrib_Buff)
+#define astformatid_strings astGLOBAL(Frame,AstFormatID_Strings)
+#define astformatid_istr astGLOBAL(Frame,AstFormatID_Istr)
+#define astformatid_init astGLOBAL(Frame,AstFormatID_Init)
+#define label_buff astGLOBAL(Frame,Label_Buff)
+#define symbol_buff astGLOBAL(Frame,Symbol_Buff)
+#define title_buff astGLOBAL(Frame,Title_Buff)
+#define astfmtdecimalyr_buff astGLOBAL(Frame,AstFmtDecimalYr_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Buffer returned by GetAttrib. */
+static char getattrib_buff[ GETATTRIB_BUFF_LEN + 1 ];
+
+/* Strings returned by astFormatID */
+static char *astformatid_strings[ ASTFORMATID_MAX_STRINGS ];
+
+/* Offset of next string in "AstFormatID_Strings" */
+static int astformatid_istr;
+
+/* "AstFormatID_Strings" array initialised? */
+static int astformatid_init;
+
+/* Default Label string buffer */
+static char label_buff[ LABEL_BUFF_LEN + 1 ];
+
+/* Default Symbol buffer */
+static char symbol_buff[ SYMBOL_BUFF_LEN + 1 ];
+
+/* Default Title string buffer */
+static char title_buff[ TITLE_BUFF_LEN + 1 ];
+
+/* Buffer for result string */
+static char astfmtdecimalyr_buff[ ASTFMTDECIMALYR_BUFF_LEN + 1 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstFrameVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstAxis *GetAxis( AstFrame *, int, int * );
+static AstFrame *PickAxes( AstFrame *, int, const int[], AstMapping **, int * );
+static AstFrameSet *Convert( AstFrame *, AstFrame *, const char *, int * );
+static AstFrameSet *ConvertX( AstFrame *, AstFrame *, const char *, int * );
+static AstFrameSet *FindFrame( AstFrame *, AstFrame *, const char *, int * );
+static void MatchAxes( AstFrame *, AstFrame *, int *, int * );
+static void MatchAxesX( AstFrame *, AstFrame *, int *, int * );
+static AstLineDef *LineDef( AstFrame *, const double[2], const double[2], int * );
+static AstPointSet *FrameGrid( AstFrame *, int, const double *, const double *, int * );
+static AstPointSet *ResolvePoints( AstFrame *, const double [], const double [], AstPointSet *, AstPointSet *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static char *CleanDomain( char *, int * );
+static const char *Abbrev( AstFrame *, int, const char *, const char *, const char *, int * );
+static const char *Format( AstFrame *, int, double, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static const char *GetDefaultLabel( int, int * );
+static const char *GetDefaultSymbol( AstFrame *, int, int * );
+static const char *GetDefaultTitle( AstFrame *, int * );
+static const char *GetDomain( AstFrame *, int * );
+static const char *GetFormat( AstFrame *, int, int * );
+static const char *GetInternalUnit( AstFrame *, int, int * );
+static const char *GetLabel( AstFrame *, int, int * );
+static const char *GetNormUnit( AstFrame *, int, int * );
+static const char *GetSymbol( AstFrame *, int, int * );
+static const char *GetTitle( AstFrame *, int * );
+static const char *GetUnit( AstFrame *, int, int * );
+static const int *GetPerm( AstFrame *, int * );
+static double Angle( AstFrame *, const double[], const double[], const double[], int * );
+static double AxAngle( AstFrame *, const double[], const double[], int, int * );
+static double AxDistance( AstFrame *, int, double, double, int * );
+static double AxOffset( AstFrame *, int, double, double, int * );
+static double Distance( AstFrame *, const double[], const double[], int * );
+static double Centre( AstFrame *, int, double, double, int * );
+static double Gap( AstFrame *, int, double, int *, int * );
+static double Offset2( AstFrame *, const double[2], double, double, double[2], int * );
+static int AxIn( AstFrame *, int, double, double, double, int, int * );
+static int ConsistentMaxAxes( AstFrame *, int, int * );
+static int ConsistentMinAxes( AstFrame *, int, int * );
+static int DefaultMaxAxes( AstFrame *, int * );
+static int DefaultMinAxes( AstFrame *, int * );
+static int DoNotSimplify( AstMapping *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int Fields( AstFrame *, int, const char *, const char *, int, char **, int *, double *, int * );
+static int GetDigits( AstFrame *, int * );
+static int GetDirection( AstFrame *, int, int * );
+static int GetIsLinear( AstMapping *, int * );
+static int GetIsSimple( AstMapping *, int * );
+static int LineContains( AstFrame *, AstLineDef *, int, double *, int * );
+static int LineCrossing( AstFrame *, AstLineDef *, AstLineDef *, double **, int * );
+static int GetObjSize( AstObject *, int * );
+static void AxNorm( AstFrame *, int, int, int, double *, int * );
+static void CleanAttribs( AstObject *, int * );
+static void LineOffset( AstFrame *, AstLineDef *, double, double, double[2], int * );
+
+static double GetTop( AstFrame *, int, int * );
+static int TestTop( AstFrame *, int, int * );
+static void ClearTop( AstFrame *, int, int * );
+static void SetTop( AstFrame *, int, double, int * );
+
+static double GetBottom( AstFrame *, int, int * );
+static int TestBottom( AstFrame *, int, int * );
+static void ClearBottom( AstFrame *, int, int * );
+static void SetBottom( AstFrame *, int, double, int * );
+
+static AstSystemType GetSystem( AstFrame *, int * );
+static int TestSystem( AstFrame *, int * );
+static void ClearSystem( AstFrame *, int * );
+static void SetSystem( AstFrame *, AstSystemType, int * );
+
+static AstSystemType GetAlignSystem( AstFrame *, int * );
+static int TestAlignSystem( AstFrame *, int * );
+static void ClearAlignSystem( AstFrame *, int * );
+static void SetAlignSystem( AstFrame *, AstSystemType, int * );
+
+static double GetEpoch( AstFrame *, int * );
+static int TestEpoch( AstFrame *, int * );
+static void ClearEpoch( AstFrame *, int * );
+static void SetEpoch( AstFrame *, double, int * );
+
+static double GetObsLat( AstFrame *, int * );
+static int TestObsLat( AstFrame *, int * );
+static void ClearObsLat( AstFrame *, int * );
+static void SetObsLat( AstFrame *, double, int * );
+
+static double GetObsLon( AstFrame *, int * );
+static int TestObsLon( AstFrame *, int * );
+static void ClearObsLon( AstFrame *, int * );
+static void SetObsLon( AstFrame *, double, int * );
+
+static double GetObsAlt( AstFrame *, int * );
+static int TestObsAlt( AstFrame *, int * );
+static void ClearObsAlt( AstFrame *, int * );
+static void SetObsAlt( AstFrame *, double, int * );
+
+static double GetDut1( AstFrame *, int * );
+static int TestDut1( AstFrame *, int * );
+static void ClearDut1( AstFrame *, int * );
+static void SetDut1( AstFrame *, double, int * );
+
+static int GetActiveUnit( AstFrame *, int * );
+static int TestActiveUnit( AstFrame *, int * );
+static void SetActiveUnit( AstFrame *, int, int * );
+
+static AstFrameSet *GetFrameVariants( AstFrame *, int * );
+static void SetFrameVariants( AstFrame *, AstFrameSet *, int * );
+
+static int GetFrameFlags( AstFrame *, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+static int GetMatchEnd( AstFrame *, int * );
+static int GetMaxAxes( AstFrame *, int * );
+static int GetMinAxes( AstFrame *, int * );
+static int GetNaxes( AstFrame *, int * );
+static int GetNin( AstMapping *, int * );
+static int GetNout( AstMapping *, int * );
+static int GetPermute( AstFrame *, int * );
+static int GetPreserveAxes( AstFrame *, int * );
+static int Match( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int SubFrame( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestDigits( AstFrame *, int * );
+static int TestDirection( AstFrame *, int, int * );
+static int TestDomain( AstFrame *, int * );
+static int TestFormat( AstFrame *, int, int * );
+static int TestLabel( AstFrame *, int, int * );
+static int TestMatchEnd( AstFrame *, int * );
+static int TestMaxAxes( AstFrame *, int * );
+static int TestMinAxes( AstFrame *, int * );
+static int TestPermute( AstFrame *, int * );
+static int TestPreserveAxes( AstFrame *, int * );
+static int TestSymbol( AstFrame *, int, int * );
+static int TestTitle( AstFrame *, int * );
+static int TestUnit( AstFrame *, int, int * );
+static int IsUnitFrame( AstFrame *, int * );
+static int Unformat( AstFrame *, int, const char *, double *, int * );
+static int ValidateAxis( AstFrame *, int, int, const char *, int * );
+static AstSystemType ValidateSystem( AstFrame *, AstSystemType, const char *, int * );
+static AstSystemType SystemCode( AstFrame *, const char *, int * );
+static const char *SystemString( AstFrame *, AstSystemType, int * );
+static void AddUnderscores( char *, int * );
+static void CheckPerm( AstFrame *, const int *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearDigits( AstFrame *, int * );
+static void ClearDirection( AstFrame *, int, int * );
+static void ClearDomain( AstFrame *, int * );
+static void ClearFormat( AstFrame *, int, int * );
+static void ClearLabel( AstFrame *, int, int * );
+static void ClearMatchEnd( AstFrame *, int * );
+static void ClearMaxAxes( AstFrame *, int * );
+static void ClearMinAxes( AstFrame *, int * );
+static void ClearPermute( AstFrame *, int * );
+static void ClearPreserveAxes( AstFrame *, int * );
+static void ClearSymbol( AstFrame *, int, int * );
+static void ClearTitle( AstFrame *, int * );
+static void ClearUnit( AstFrame *, int, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void Intersect( AstFrame *, const double[2], const double[2], const double[2], const double[2], double[2], int * );
+static void Norm( AstFrame *, double[], int * );
+static void NormBox( AstFrame *, double[], double[], AstMapping *, int * );
+static void Offset( AstFrame *, const double[], const double[], double, double[], int * );
+static void Overlay( AstFrame *, const int *, AstFrame *, int * );
+static void PermAxes( AstFrame *, const int[], int * );
+static void PrimaryFrame( AstFrame *, int, AstFrame **, int *, int * );
+static void ReportPoints( AstMapping *, int, AstPointSet *, AstPointSet *, int * );
+static void Resolve( AstFrame *, const double [], const double [], const double [], double [], double *, double *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetAxis( AstFrame *, int, AstAxis *, int * );
+static void SetDigits( AstFrame *, int, int * );
+static void SetDirection( AstFrame *, int, int, int * );
+static void SetDomain( AstFrame *, const char *, int * );
+static void SetFormat( AstFrame *, int, const char *, int * );
+static void SetFrameFlags( AstFrame *, int, int * );
+static void SetLabel( AstFrame *, int, const char *, int * );
+static void SetMatchEnd( AstFrame *, int, int * );
+static void SetMaxAxes( AstFrame *, int, int * );
+static void SetMinAxes( AstFrame *, int, int * );
+static void SetPermute( AstFrame *, int, int * );
+static void SetPreserveAxes( AstFrame *, int, int * );
+static void SetSymbol( AstFrame *, int, const char *, int * );
+static void SetTitle( AstFrame *, const char *, int * );
+static void SetUnit( AstFrame *, int, const char *, int * );
+static void NewUnit( AstAxis *, const char *, const char *, const char *, const char *, int * );
+static void ValidateAxisSelection( AstFrame *, int, const int *, const char *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+static const char *Abbrev( AstFrame *this, int axis,  const char *fmt,
+                           const char *str1, const char *str2, int *status ) {
+/*
+*+
+*  Name:
+*     astAbbrev
+
+*  Purpose:
+*     Abbreviate a formatted Frame axis value by skipping leading fields.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     const char *astAbbrev( AstFrame *this, int axis, const char *fmt,
+*                            const char *str1, const char *str2 )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function compares two Frame axis values that have been
+*     formatted (using astFormat) and determines if they have any
+*     redundant leading fields (i.e. leading fields in common which
+*     can be suppressed when tabulating the values or plotting them on
+*     the axis of a graph).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the Frame axis for which the values have been
+*        formatted (axis numbering starts at zero for the first axis).
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format specification used to format the two values.
+*     str1
+*        Pointer to a constant null-terminated string containing the
+*        first formatted value. If this is null, the returned pointer
+*        points to the start of the final field in str2.
+*     str2
+*        Pointer to a constant null-terminated string containing the
+*        second formatted value.
+
+*  Returned Value:
+*     A pointer into the "str2" string which locates the first
+*     character in the first field that differs between the two
+*     formatted values.
+*
+*     If the two values have no leading fields in common, the returned
+*     value will point at the start of string "str2". If the two
+*     values are equal, it will point at the terminating null at the
+*     end of this string.
+
+*  Notes:
+*     - This function assumes that the format specification used was
+*     the same when both values were formatted and that they both
+*     apply to the same Frame axis.
+*     - A pointer to the start of "str2" will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   const char *result;           /* Result pointer to return */
+
+/* Initialise. */
+   result = str2;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate the axis index and obtain a pointer to the required
+   Axis. */
+   (void) astValidateAxis( this, axis, 1, "astAbbrev" );
+   ax = astGetAxis( this, axis );
+
+/* Invoke the Axis astAxisAbbrev method to perform the processing. */
+   result = astAxisAbbrev( ax, fmt, str1, str2 );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = str2;
+
+/* Return the result. */
+   return result;
+}
+
+static void AddUnderscores( char *string, int *status ) {
+/*
+*  Name:
+*     AddUnderscores
+
+*  Purpose:
+*     Add underscores to a string in place of white space.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void AddUnderscores( char *string, int *status )
+
+*  Class Membership:
+*     Frame member function.
+
+*  Description:
+*     This function changes all white space characters in a string into
+*     the underscore character '_'.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     string
+*        Pointer to the null terminated string to be processed.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables. */
+   int i;                        /* Loop counter for characters */
+
+/* Inspect each character in the string. */
+   for ( i = 0; string[ i ]; i++ ) {
+
+/* If it is a white space character, replace it with an underscore. */
+      if ( isspace( string[ i ] ) ) string[ i ] = '_';
+   }
+}
+
+static double Angle( AstFrame *this, const double a[],
+                     const double b[], const double c[], int *status ) {
+/*
+*++
+*  Name:
+c     astAngle
+f     AST_ANGLE
+
+*  Purpose:
+*     Calculate the angle subtended by two points at a third point.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     double astAngle( AstFrame *this, const double a[], const double b[],
+c                      const double c[] )
+f     RESULT = AST_ANGLE( THIS, A, B, C, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function
+f     This routine
+*     finds the angle at point B between the line joining points A and B,
+*     and the line joining points C and B. These lines will in fact be
+*     geodesic curves appropriate to the Frame in use. For instance, in
+*     SkyFrame, they will be great circles.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     a
+f     A( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the first point.
+c     b
+f     B( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the second point.
+c     c
+f     C( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the third point.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astAngle
+f     AST_ANGLE = DOUBLE PRECISION
+*        The angle in radians, from the line AB to the line CB. If the
+*        Frame is 2-dimensional, it will be in the range $\pm \pi$,
+*        and positive rotation is in the same sense as rotation from
+*        the positive direction of axis 2 to the positive direction of
+*        axis 1. If the Frame has more than 2 axes, a positive value will
+*        always be returned in the range zero to $\pi$.
+
+*  Notes:
+*     - A value of AST__BAD will also be returned if points A and B are
+*     co-incident, or if points B and C are co-incident.
+*     - A value of AST__BAD will also be returned if this function is
+c     invoked with the AST error status set, or if it should fail for
+f     invoked with STATUS set to an error value, or if it should fail for
+*     any reason.
+*--
+*/
+
+/* Local Variables: */
+   double *ab;                   /* Pointer to vector AB */
+   double *cb;                   /* Pointer to vector CB */
+   double cos;                   /* cosine of required angle */
+   double anga;                  /* Angle from +ve Y to the line BA */
+   double angc;                  /* Angle from +ve Y to the line BC */
+   double result;                /* Result value to return */
+   double sla;                   /* Squared length of vector AB */
+   double slc;                   /* Squared length of vector CB */
+   double sp;                    /* Scalar product of AB and CB */
+   int axis;                     /* Axis index */
+   int naxes;                    /* Number of Frame axes */
+   int ok;                       /* Supplied points OK? */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Assume everything is ok */
+   ok = 1;
+
+/* Obtain the number of Frame axes. */
+   naxes = astGetNaxes( this );
+
+/* Obtain workspace. */
+   ab = (double *) astMalloc( sizeof(double)*naxes );
+   cb = (double *) astMalloc( sizeof(double)*naxes );
+
+/* Check all positions are good, and form the vectors from b to a, and
+   from b to c. Also find the squared length of each vector. */
+   if( astOK ) {
+      sla = 0.0;
+      slc = 0.0;
+      for( axis = 0; axis < naxes; axis++ ) {
+         if( a[ axis ] == AST__BAD || b[ axis ] == AST__BAD ||
+             c[ axis ] == AST__BAD ) {
+            ok = 0;
+            break;
+         } else {
+            ab[ axis ] = a[ axis ] - b[ axis ];
+            cb[ axis ] = c[ axis ] - b[ axis ];
+            sla += ( ab[ axis ] )*( ab[ axis ] );
+            slc += ( cb[ axis ] )*( cb[ axis ] );
+         }
+      }
+
+/* Check that neither of the vectors have zero length. */
+      if( sla == 0 || slc == 0 ) ok = 0;
+
+/* Only proceed if these checks were passed. */
+      if ( ok ) {
+
+/* Deal first with 2-dimensional Frames. */
+         if( naxes == 2 ) {
+
+/* Find the angle from +ve Y to the line BA. */
+            anga = atan2( ab[ 0 ], ab[ 1 ] );
+
+/* Find the angle from +ve Y to the line BC. */
+            angc = atan2( cb[ 0 ], cb[ 1 ] );
+
+/* Find the difference, folded into the range +/- PI. */
+            result = palDrange( angc - anga );
+
+/* Now deal with Frames with more than 2 axes. */
+         } else {
+
+/* Form the scalar product of the two vectors. */
+            sp = 0.0;
+            for( axis = 0; axis < naxes; axis++ ) {
+               sp += ab[ axis ]*cb[ axis ];
+            }
+
+/* Derive the required angle from the normalized scalar product. */
+            cos = sp/sqrt( sla*slc );
+            if( cos > 1.0 ) {
+               cos = 1.0;
+            } else if( cos < -1.0 ) {
+               cos = -1.0;
+            }
+            result =acos( cos );
+         }
+      }
+   }
+
+/* Free the work space. */
+   ab = (double *) astFree( (void *) ab );
+   cb = (double *) astFree( (void *) cb );
+
+/* Return the result. */
+   return result;
+}
+
+static double AxAngle( AstFrame *this, const double a[], const double b[], int axis, int *status ) {
+/*
+*++
+*  Name:
+c     astAxAngle
+f     AST_AXANGLE
+
+*  Purpose:
+*     Returns the angle from an axis, to a line through two points.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     double astAxAngle( AstFrame *this, const double a[], const double b[], int axis )
+f     RESULT = AST_AXANGLE( THIS, A, B, AXIS, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function
+f     This routine
+*     finds the angle, as seen from point A, between the positive
+*     direction of a specified axis, and the geodesic curve joining point
+*     A to point B.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     a
+f     A( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the first point.
+c     b
+f     B( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the second point.
+c     axis
+f     AXIS = INTEGER (Given)
+*        The number of the Frame axis from which the angle is to be
+*        measured (axis numbering starts at 1 for the first axis).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astAxAngle
+f     AST_AXANGLE = DOUBLE PRECISION
+*        The angle in radians, from the positive direction of the
+*        specified axis, to the line AB. If the Frame is 2-dimensional,
+*        it will be in the range [-PI/2,+PI/2], and positive rotation is in
+*        the same sense as rotation from the positive direction of axis 2
+*        to the positive direction of axis 1. If the Frame has more than 2
+*        axes, a positive value will always be returned in the range zero
+*        to PI.
+
+*  Notes:
+c     - The geodesic curve used by this function is the path of
+f     - The geodesic curve used by this routine is the path of
+*     shortest distance between two points, as defined by the
+c     astDistance function.
+f     AST_DISTANCE function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value, or if the require
+*     position angle is undefined.
+*--
+*/
+
+/* Local Variables: */
+   double *aa;                   /* Pointer to third point */
+   double ab;                    /* Absolute value of component */
+   double mxab;                  /* Largest absolute value of component */
+   double result;                /* The returned value */
+   int iaxis;                    /* Axis index */
+   int naxes;                    /* Number of Frame axes */
+   int ok;                       /* Are values ok to use? */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxAngle" );
+
+/* Obtain the number of Frame axes. */
+   naxes = astGetNaxes( this );
+
+/* Obtain workspace. */
+   aa = (double *) astMalloc( sizeof(double)*naxes );
+
+/* Create a position which is offset slightly from point A in the
+   positive direction of the specified axis. Also get the largest absolute
+   value of any component of the vector AB. */
+   if( astOK ) {
+      ok = 1;
+      mxab = 0.0;
+
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+         if( a[ iaxis ] != AST__BAD && b[ iaxis ] != AST__BAD ) {
+            aa[ iaxis ] = a[ iaxis ];
+            ab = fabs( a[ iaxis ] - b[ iaxis ] );
+            if( ab > mxab ) mxab = ab;
+         } else {
+            ok = 0;
+            break;
+         }
+      }
+
+      if( ok ) {
+
+         if( a[ axis - 1 ] != 0.0 ) {
+            aa[ axis - 1 ] += 10000.0*DBL_EPSILON*fabs( a[ axis - 1 ] );
+
+         } else if( b[ axis - 1 ] != 0.0 ) {
+            aa[ axis - 1 ] = 10000.0*DBL_EPSILON*fabs( b[ iaxis - 1 ] );
+
+         } else if( mxab != 0.0 ) {
+            aa[ axis - 1 ] = 10000.0*DBL_EPSILON*mxab;
+
+         } else {
+            aa[ axis - 1 ] = 1.0;
+         }
+
+/* Use astAngle to get the required angle. */
+         result = astAngle( this, aa, a, b );
+      }
+   }
+
+/* Free the workspace. */
+   aa = (double *) astFree( (void *) aa );
+
+/* Return the result. */
+   return result;
+
+}
+
+static double AxDistance( AstFrame *this, int axis, double v1, double v2, int *status ) {
+/*
+*++
+*  Name:
+c     astAxDistance
+f     AST_AXDISTANCE
+
+*  Purpose:
+*     Find the distance between two axis values.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     double astAxDistance( AstFrame *this, int axis, double v1, double v2 )
+f     RESULT = AST_AXDISTANCE( THIS, AXIS, V1, V2, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function returns a signed value representing the axis increment
+f     This routine returns a signed value representing the axis increment
+*     from axis value v1 to axis value v2.
+*
+*     For a simple Frame, this is a trivial operation returning the
+*     difference between the two axis values. But for other derived classes
+*     of Frame (such as a SkyFrame) this is not the case.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     axis
+f     AXIS = INTEGER (Given)
+*        The index of the axis to which the supplied values refer. The
+*        first axis has index 1.
+c     v1
+f     V1 = DOUBLE PRECISION (Given)
+*        The first axis value.
+c     v2
+f     V2 = DOUBLE PRECISION (Given)
+*        The second axis value.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astAxDistance
+f     AST_AXDISTANCE = DOUBLE PRECISION
+*        The distance from the first to the second axis value.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input values has this value.
+*     - A "bad" value will also be returned if this function is
+c     invoked with the AST error status set, or if it should fail for
+f     invoked with STATUS set to an error value, or if it should fail for
+*     any reason.
+*--
+
+*  Implementation Deficiencies;
+*     - The protected interface for this function uses 1-based axis
+*     numbering (like the public interface), rather than the more usual
+*     zero-based system used by all other protected interfaces. There is
+*     no real reason for this, and it should be changed at some time.
+
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   double result;                /* The returned answer */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate the axis index and obtain a pointer to the required Axis. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxDistance" );
+   ax = astGetAxis( this, axis - 1 );
+
+/* Use the AxisDistance method associated with the Axis. */
+   if( astOK ) result = astAxisDistance( ax, v1, v2 );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* Return the result. */
+   return result;
+
+}
+
+static void AxNorm( AstFrame *this, int axis, int oper, int nval,
+                      double *values, int *status ){
+/*
+*++
+*  Name:
+c     astAxNorm
+f     AST_AXNORM
+
+*  Purpose:
+*     Normalise an array of axis values.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     void astAxNorm( AstFrame *this, int axis, int oper, int nval,
+c                     double *values, int *status )
+f     CALL AST_AXNORM( THIS, AXIS, OPER, NVAL, VALUES, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function
+f     This routine
+*     modifies a supplied array of axis values so that they are normalised
+*     in the manner indicated by
+c     parameter "oper".
+f     argument OPER.
+*
+*     No normalisation is possible for a simple Frame and so the supplied
+*     values are returned unchanged. However, this may not be the case for
+*     specialised sub-classes of Frame. For instance, a SkyFrame has a
+*     discontinuity at zero longitude and so a longitude value can be
+*     expressed in the range [-Pi,+PI] or the range [0,2*PI]. See the
+*     "Applicability:" section below for details.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     axis
+f     AXIS = INTEGER (Given)
+*        The index of the axis to which the supplied values refer. The
+*        first axis has index 1.
+c     oper
+f     OPER = INTEGER (Given)
+*        Indicates the type of normalisation to be applied. If zero is
+*        supplied, the normalisation will be the same as that performed by
+c        function astNorm.
+f        routine AST_NORM.
+*        If 1 is supplied, the normalisation will be chosen automatically
+*        so that the resulting list has the smallest range.
+c     nval
+f     NVAL = INTEGER (Given)
+*        The number of points in the values array.
+c     values
+f     VALUES( NVAL ) = DOUBLE PRECISION (Given and Returned)
+*        On entry, the axis values to be normalised. Modified on exit to
+*        hold the normalised values.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     SkyFrame
+c        If "oper"
+f        If OPER
+*        is 0, longitude values are returned in the range [0,2*PI].
+c        If "oper"
+f        If OPER
+*        is 1, longitude values are returned in either the range
+*        [0,2*PI] or [-PI,PI]. The choice is made so that that the
+*        resulting list has the smallest range. Latitude values are
+*        always returned in the range [-PI,PI].
+*     All other classes of Frame
+*        The supplied axis values are returned unchanged.
+
+*--
+
+*  Implementation Deficiencies;
+*     - The protected interface for this function uses 1-based axis
+*     numbering (like the public interface), rather than the more usual
+*     zero-based system used by all other protected interfaces. There is
+*     no real reason for this, and it should be changed at some time.
+
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the axis index and obtain a pointer to the required Axis. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxNorm" );
+   ax = astGetAxis( this, axis - 1 );
+
+/* Validate ther "oper" value. */
+   if( ( oper < 0 || oper > 1 ) && astOK ) {
+      astError( AST__OPRIN, "astAxNorm(%s): Invalid operation %d.", status,
+                astGetClass( this ), oper );
+   }
+
+/* Use the AxisNormValues method associated with the Axis. */
+   if( astOK ) astAxisNormValues( ax, oper, nval, values );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+}
+
+static int AxIn( AstFrame *this, int axis, double lo, double hi, double val,
+                 int closed, int *status ){
+/*
+*+
+*  Name:
+*     astAxIn
+
+*  Purpose:
+*     Test if an axis value lies within a given interval.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astAxIn( AstFrame *this, int axis, double lo, double hi, double val,
+*                  int closed )
+
+*  Class Membership:
+*     Frame member function.
+
+*  Description:
+*     This function returns non-zero if a given axis values lies within a
+*     given axis interval.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The index of the axis. The first axis has index 0.
+*     lo
+*        The lower axis limit of the interval.
+*     hi
+*        The upper axis limit of the interval.
+*     val
+*        The axis value to be tested.
+*     closed
+*        If non-zero, then the lo and hi axis values are themselves
+*        considered to be within the interval. Otherwise they are outside.
+
+*  Returned Value:
+*     Non-zero if the test value is inside the interval.
+
+*  Applicability:
+*     Frame
+*        Uses simple Euclidean test
+*     SkyFrame
+*        All angles which are numerically between "lo" and "hi" are within
+*        the interval. Angle outside this range are also within the interval
+*        if they can be brought into the range by addition or subtraction
+*        of a multiple of 2.PI.
+*-
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   int result;                   /* Returned value */
+
+/* For speed, omit the astOK check and axis validation (since this is
+   protected code, AST should get it right). Obtain a pointer to the
+   required Axis. */
+   ax = astGetAxis( this, axis );
+
+/* Use the AxisIn method associated with the Axis. */
+   result = ax ? astAxisIn( ax, lo, hi, val, closed ) : 0;
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* Return the result. */
+   return result;
+}
+
+static double AxOffset( AstFrame *this, int axis, double v1, double dist, int *status ) {
+/*
+*++
+*  Name:
+c     astAxOffset
+f     AST_AXOFFSET
+
+*  Purpose:
+*     Add an increment onto a supplied axis value.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     double astAxOffset( AstFrame *this, int axis, double v1, double dist )
+f     RESULT = AST_AXOFFSET( THIS, AXIS, V1, DIST, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function returns an axis value formed by adding a signed axis
+f     This routine returns an axis value formed by adding a signed axis
+*     increment onto a supplied axis value.
+*
+*     For a simple Frame, this is a trivial operation returning the
+*     sum of the two supplied values. But for other derived classes
+*     of Frame (such as a SkyFrame) this is not the case.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     axis
+f     AXIS = INTEGER (Given)
+*        The index of the axis to which the supplied values refer. The
+*        first axis has index 1.
+c     v1
+f     V1 = DOUBLE PRECISION (Given)
+*        The original axis value.
+c     dist
+f     DIST = DOUBLE PRECISION (Given)
+*        The axis increment to add to the original axis value.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astAxOffset
+f     AST_AXOFFSET = DOUBLE PRECISION
+*        The incremented axis value.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input values has this value.
+*     - A "bad" value will also be returned if this function is
+c     invoked with the AST error status set, or if it should fail for
+f     invoked with STATUS set to an error value, or if it should fail for
+*     any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   double result;                /* The returned answer */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate the axis index and obtain a pointer to the required Axis. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxOffset" );
+   ax = astGetAxis( this, axis - 1 );
+
+/* Use the AxisOffset method associated with the Axis. */
+   if( astOK ) result = astAxisOffset( ax, v1, dist );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* Return the result. */
+   return result;
+
+}
+
+static double Centre( AstFrame *this, int axis, double value, double gap, int *status ) {
+/*
+*+
+*  Name:
+*     astCentre
+
+*  Purpose:
+*     Find a "nice" central value for tabulating Axis values.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     double astCentre( AstFrame *this, int axis, double value, double gap )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns an axis value which produces a nice formatted
+*     value suitable for a major tick mark on a plot axis, close to the
+*     supplied axis value.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the axis (zero-based) for which a gap is to be found.
+*     value
+*        An arbitrary axis value in the section that is being plotted.
+*     gap
+*        The gap size.
+
+*  Returned Value:
+*     The nice central axis value.
+
+*  Notes:
+*     - A value of zero is returned if the supplied gap size is zero.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   double result;                /* The nice central value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Validate the axis index and obtain a pointer to the required
+   Axis. */
+   (void) astValidateAxis( this, axis, 1, "astCentre" );
+   ax = astGetAxis( this, axis );
+
+/* Find the nice central value. */
+   result = astAxisCentre( ax, value, gap );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static void CheckPerm( AstFrame *this, const int *perm, const char *method, int *status ) {
+/*
+*+
+*  Name:
+*     astCheckPerm
+
+*  Purpose:
+*     Check that an array contains a valid permutation.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astCheckPerm( AstFrame *this, const int *perm, const char *method )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function checks the validity of a permutation array that
+*     will be used to permute the order of a Frame's axes. If the
+*     permutation specified by the array is not valid, an error is
+*     reported and the global error status is set. Otherwise, the
+*     function returns without further action.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     perm
+*        Pointer to an array of integers with the same number of
+*        elements as there are axes in the Frame. For each axis, the
+*        corresponding integer gives the (zero based) axis index to be
+*        used to identify the information for that axis (using the
+*        un-permuted axis numbering). To be valid, the integers in
+*        this array should therefore all lie in the range zero to
+*        (naxes-1) inclusive, where "naxes" is the number of Frame
+*        axes, and each value should occur exactly once.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate a permutation array. This method name is used
+*        solely for constructing error messages.
+
+*  Notes:
+*     - Error messages issued by this function refer to the external
+*     (public) numbering system used for axes (which is one-based),
+*     whereas zero-based axis indices are used internally.
+*-
+*/
+
+/* Local Variables: */
+   int *there;                   /* Pointer to temporary array */
+   int axis;                     /* Loop counter for axes */
+   int naxes;                    /* Number of Frame axes */
+   int valid;                    /* Permutation array is valid? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise. */
+   valid = 1;
+
+/* Obtain the number of Frame axes and allocate a temporary array of integers
+   with the same number of elements. */
+   naxes = astGetNaxes( this );
+   there = astMalloc( sizeof( int ) * (size_t) naxes );
+   if ( astOK ) {
+
+/* Initialise the temporary array to zero. */
+      for ( axis = 0; axis < naxes; axis++ ) there[ axis ] = 0;
+
+/* Scan the permutation array, checking that each permuted axis index it
+   contains is within the correct range. Note an error and quit checking
+   if an invalid value is found. */
+      for ( axis = 0; axis < naxes; axis++ ) {
+         if ( ( perm[ axis ] < 0 ) || ( perm[ axis ] >= naxes ) ) {
+            valid = 0;
+            break;
+
+/* Use the temporary array to count how many times each valid axis index
+   occurs. */
+	 } else {
+            there[ perm[ axis ] ]++;
+	 }
+      }
+
+/* If all the axis indices were within range, check to ensure that each value
+   occurred only once. */
+      if ( valid ) {
+         for ( axis = 0; axis < naxes; axis++ ) {
+
+/* Note an error and quit checking if any value did not occur exactly once. */
+            if ( there[ axis ] != 1 ) {
+               valid = 0;
+               break;
+	    }
+	 }
+      }
+   }
+
+/* Free the temporary array. */
+   there = astFree( there );
+
+/* If an invalid permutation was detected and no other error has
+   occurred, then report an error (note we convert to one-based axis
+   numbering in the error message). */
+   if ( !valid && astOK ) {
+      astError( AST__PRMIN, "%s(%s): Invalid axis permutation array.", status,
+                method, astGetClass( this ) );
+      astError( AST__PRMIN, "Each axis index should lie in the range 1 to %d "
+                "and should occur only once.", status, naxes );
+   }
+}
+
+static void CleanAttribs( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     CleanAttribs
+
+*  Purpose:
+*     Clear any invalid set attribute values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void CleanAttribs( AstObject *this_object, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the protected astCleanAttribs
+*     method inherited from the Object class).
+
+*  Description:
+*     This function clears any attributes that are currently set to
+*     invalid values in thr supplied object.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be cleaned.
+
+*/
+
+/* Local Variables; */
+   AstAxis *ax;
+   AstFrame *this;
+   int i;
+   int nax;
+   int reporting;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Get a pointer to the Frame structure. */
+   this = (AstFrame *) this_object;
+
+/* Defer error reporting, as required by the astCLEAN_ATTRIB macro. */
+   reporting = astReporting( 0 );
+
+/* Clean attributes in any Objects contained within "this". */
+   nax = astGetNaxes( this );
+   for( i = 0; i < nax; i++ ) {
+      ax = astGetAxis( this, i );
+      astCleanAttribs( ax );
+      ax = astAnnul( ax );
+   }
+
+/* Clean attributes of this class. */
+   astCLEAN_ATTRIB(System)
+   astCLEAN_ATTRIB(AlignSystem)
+
+/* Re-establish error reporting. */
+   astReporting( reporting );
+
+/* Invoke the method inherited form the parent to clean attributes
+   defined by the parent class. */
+   (*parent_cleanattribs)( this_object, status );
+}
+
+static char *CleanDomain( char *domain, int *status ) {
+/*
+*  Name:
+*     CleanDomain
+
+*  Purpose:
+*     Clean a Domain string and convert to upper case.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     char *CleanDomain( char *domain, int *status )
+
+*  Class Membership:
+*     Frame member function.
+
+*  Description:
+*     This function removes all white space from a string and converts
+*     other characters to upper case. It is intended for cleaning up
+*     values supplied for the Domain attribute of a Frame.
+
+*  Parameters:
+*     domain
+*        Pointer to the null terminated Domain string to be modified.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The pointer value "domain" is always returned (even under error
+*     conditions).
+*/
+
+/* Local Variables: */
+   int i;                        /* Loop counter for characters */
+   int j;                        /* Good character count */
+
+/* Check the global error status. */
+   if ( !astOK ) return domain;
+
+/* Eliminate white space characters and convert the rest to upper
+   case. */
+   for ( i = j = 0; domain[ i ]; i++ ) {
+      if ( !isspace( domain[ i ] ) ) domain[ j++ ] = toupper( domain[ i ] );
+   }
+   domain[ j ] = '\0';
+
+/* Return the string pointer. */
+   return domain;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     Frame, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis */
+   AstFrame *pfrm;               /* Pointer to primary Frame containing axis */
+   AstFrame *this;               /* Pointer to the Frame structure */
+   char pfrm_attrib[ 100 ];      /* Primary Frame attribute */
+   char *axis_attrib;            /* Pointer to axis attribute name */
+   const char *old_attrib;       /* Pointer to supplied attribute name string */
+   int axis;                     /* Frame axis number */
+   int axis_nc;                  /* No. characters in axis attribute name */
+   int free_axis_attrib;         /* Should axis_attrib be freed? */
+   int has_axis;                 /* Does attrib name include axis specifier? */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* No. characters read by astSscanf */
+   int oldrep;                   /* Original error reporting state */
+   int paxis;                    /* Axis index within primary frame */
+   int used;                     /* Could the setting string be used? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Frame structure. */
+   this = (AstFrame *) this_object;
+
+/* Set a flag indicating if the attribute name includes an axis
+   specifier. */
+   has_axis = ( strchr( attrib, '(' ) != NULL );
+
+/* A flag indicating that we do not need to free the axis_attrib memory. */
+   free_axis_attrib = 0;
+
+/* Initialise things to avoid compiler warnings. */
+   axis_attrib = NULL;
+   old_attrib = NULL;
+
+/* Jump back to here if we are trying the same attribute but with an explicit
+   axis "(1)" added to the end of the name. */
+L1:
+
+/* Obtain the length of the "attrib" string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Digits. */
+/* ------- */
+   if ( !strcmp( attrib, "digits" ) ) {
+      astClearDigits( this );
+
+/* Digits(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "digits(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+
+/* There is no function to clear the Digits attribute for an axis
+   directly, so obtain a pointer to the Axis and use this to clear the
+   attribute. */
+      (void) astValidateAxis( this, axis - 1, 1, "astClearDigits(axis)" );
+      ax = astGetAxis( this, axis - 1 );
+      astClearAxisDigits( ax );
+      ax = astAnnul( ax );
+
+/* Direction(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "direction(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearDirection( this, axis - 1 );
+
+/* Epoch. */
+/* ------ */
+   } else if ( !strcmp( attrib, "epoch" ) ) {
+      astClearEpoch( this );
+
+/* Top(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "top(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearTop( this, axis - 1 );
+
+/* Bottom(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "bottom(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearBottom( this, axis - 1 );
+
+/* Domain. */
+/* ------- */
+   } else if ( !strcmp( attrib, "domain" ) ) {
+      astClearDomain( this );
+
+/* Format(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "format(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearFormat( this, axis - 1 );
+
+/* Label(axis). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "label(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearLabel( this, axis - 1 );
+
+/* MatchEnd. */
+/* --------- */
+   } else if ( !strcmp( attrib, "matchend" ) ) {
+      astClearMatchEnd( this );
+
+/* MaxAxes. */
+/* -------- */
+   } else if ( !strcmp( attrib, "maxaxes" ) ) {
+      astClearMaxAxes( this );
+
+/* MinAxes. */
+/* -------- */
+   } else if ( !strcmp( attrib, "minaxes" ) ) {
+      astClearMinAxes( this );
+
+/* Permute. */
+/* -------- */
+   } else if ( !strcmp( attrib, "permute" ) ) {
+      astClearPermute( this );
+
+/* PreserveAxes. */
+/* ------------- */
+   } else if ( !strcmp( attrib, "preserveaxes" ) ) {
+      astClearPreserveAxes( this );
+
+/* Symbol(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "symbol(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearSymbol( this, axis - 1 );
+
+/* System. */
+/* ------- */
+   } else if ( !strcmp( attrib, "system" ) ) {
+      astClearSystem( this );
+
+/* AlignSystem. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "alignsystem" ) ) {
+      astClearAlignSystem( this );
+
+/* Title. */
+/* ------ */
+   } else if ( !strcmp( attrib, "title" ) ) {
+      astClearTitle( this );
+
+/* Unit(axis). */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "unit(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearUnit( this, axis - 1 );
+
+/* ObsLat. */
+/* ------- */
+   } else if ( !strcmp( attrib, "obslat" ) ) {
+      astClearObsLat( this );
+
+/* ObsLon. */
+/* ------- */
+   } else if ( !strcmp( attrib, "obslon" ) ) {
+      astClearObsLon( this );
+
+/* ObsAlt. */
+/* ------- */
+   } else if ( !strcmp( attrib, "obsalt" ) ) {
+      astClearObsAlt( this );
+
+/* Dut1 */
+/* --- */
+   } else if ( !strcmp( attrib, "dut1" ) ) {
+      astClearDut1( this );
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Test if the attribute name matches any of the read-only attributes
+   of this class. If it does, then report an error. */
+   } else if ( !strcmp( attrib, "naxes" ) ||
+               !strncmp( attrib, "normunit", 8 ) ||
+               !strncmp( attrib, "internalunit", 12 ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Other axis attributes. */
+/* ---------------------- */
+/* If the attribute was not identified above, but appears to refer to
+   a Frame axis, then it may refer to an Axis object of a derived type
+   (which has additional attributes not recognised here). */
+   } else if( !free_axis_attrib && ( nc = 0,
+                 ( 1 == astSscanf( attrib, "%*[^()]%n(%d)%n",
+                                      &axis_nc, &axis, &nc ) )
+               && ( nc >= len ) ) ) {
+
+/* Validate the axis index and extract the attribute name. */
+      (void) astValidateAxis( this, axis - 1, 1, "astClear" );
+      axis_attrib = astString( attrib, axis_nc );
+
+/* Obtain a pointer to the Axis object. */
+      ax = astGetAxis( this, axis - 1 );
+      if( astOK ) {
+
+/* Assume that we will be able to use the attribute name. */
+         used = 1;
+
+/* Temporarily switch off error reporting so that if the following attempt
+   to access the axis attribute fails, we can try to interpret the
+   attribute name as an attribute of the primary Frame containing the
+   specified axis. Any errors reported in this context will simply be
+   ignored, in particularly they are not deferred for later delivery. */
+         oldrep = astReporting( 0 );
+
+/* Use the Axis astClearAttrib method to clear the attribute value. */
+         astClearAttrib( ax, axis_attrib );
+
+/* If the above call failed with a status of AST__BADAT, indicating that
+   the attribute name was not recognised, clear the status so that we can
+   try to interpret the attribute name as an attribute of the primary Frame
+   containing the specified axis. */
+         if( astStatus == AST__BADAT ) {
+            astClearStatus;
+
+/* Find the primary Frame containing the specified axis. */
+            astPrimaryFrame( this, axis - 1, &pfrm, &paxis );
+
+/* Only attempt to use the primary Frame if it is not the same as "this"
+   - otherwise we could end up in an infinite loop. */
+            if( pfrm != this ) {
+
+/* astPrimaryFrame returns the original - unpermuted - axis index within
+   the primary Frame. So we need to take into account any axis permutation
+   which has been applied to the primary Frame when forming the attribute name
+   to use below. Find the permuted (external) axis index which corresponds to
+   the internal (unpermuted) axis index "paxis". */
+               paxis = astValidateAxis( pfrm, paxis, 0, "astClear" );
+
+/* Modify the attribute name to refer to the axis numbering of the
+   primary frame. */
+               sprintf( pfrm_attrib, "%s(%d)", axis_attrib, paxis + 1 );
+
+/* Attempt to clear the attribute as an attribute of the primary Frame. */
+               astClearAttrib( pfrm, pfrm_attrib );
+
+/* If this failed, clear the status and indicate that we have not managed to
+   use the attribute name. */
+               if( !astOK ) {
+                  astClearStatus;
+                  used = 0;
+               }
+
+            } else {
+               used = 0;
+            }
+
+/* If not found attempt to clear the attribute value in the Axis, omitting
+   the axis index. */
+            if( ! used ) {
+               astClearAttrib( pfrm, axis_attrib );
+               if( !astOK ) {
+                  astClearStatus;
+               } else {
+                  used = 1;
+               }
+            }
+
+/* Annul the primary Frame pointer. */
+            pfrm = astAnnul( pfrm );
+         }
+
+/* Re-instate the original error reporting state. */
+         astReporting( oldrep );
+
+/* If we could not use the attribute name, attempt to clear the axis
+   attribute again, this time retaining the error report. This is done
+   to ensure the user gets an appropriate error message. */
+         if( !used ) astClearAttrib( ax, axis_attrib );
+      }
+
+/* Annul the Axis pointer and free the memory holding the attribute
+   name. */
+      ax = astAnnul( ax );
+      axis_attrib = astFree( axis_attrib );
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is still not recognised, and the Frame has only 1 axis,
+   and the attribute name does not already include an axis specifier, try
+   again after appending "(1)" to the end of the attribute name. */
+   } else if( !has_axis && astGetNaxes( this ) == 1 ) {
+
+/* Take a copy of the supplied name, allowing 3 extra characters for the
+   axis specifier "(1)". */
+      axis_attrib = astMalloc( len + 4 );
+      if( axis_attrib ) memcpy( axis_attrib, attrib, len );
+
+/* Indicate we should free the axis_attrib memory. */
+      free_axis_attrib = 1;
+
+/* Add in the axis specifier. */
+      strcpy( axis_attrib + len, "(1)" );
+
+/* Use the new attribute name instead of the supplied name. */
+      old_attrib = attrib;
+      attrib = axis_attrib;
+
+/* Indicate the attribute name now has an axis specifier. */
+      has_axis = 1;
+
+/* Jump back to try interpreting the new attribute name. */
+      goto L1;
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute name is still not recognised, pass it on to the parent
+   method for further interpretation. First re-instate the original attrib
+   name string if it was changed above. */
+   } else {
+      if( free_axis_attrib ) {
+         attrib = old_attrib;
+         axis_attrib = astFree( axis_attrib );
+      }
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearUnit( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     ClearUnit
+
+*  Purpose:
+*     Clear the Unit attribute of a Frame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void ClearUnit( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function clears the Unit value for an axis of a Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the axis (zero-based) for which the Unit value is to
+*        be cleared.
+*     unit
+*        The new value to be set.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void.
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   const char *units;            /* Pointer to units string */
+   char *old_units;              /* Pointer to copy of original units */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astSetUnit" );
+
+/* Do nothing more if the attribute is already cleared. */
+   if( astTestUnit( this, axis ) ) {
+
+/* Obtain a pointer to the required Axis. */
+      ax = astGetAxis( this, axis );
+
+/* Save a copy of the old units. */
+      units = astGetAxisUnit( ax );
+      old_units = astStore( NULL, units, strlen( units ) + 1 );
+
+/* Clear the Axis Unit attribute value, and then get a pointer to the
+   new default Units string. */
+      astClearAxisUnit( ax );
+      units = astGetUnit( this, axis );
+
+/* The new unit may require the Label and/or Symbol to be changed, but
+   only if the Frames ActiveUnit flag is set. */
+      if( astGetActiveUnit( this ) ) NewUnit( ax, old_units, units,
+                                              "astSetUnit", astGetClass( this ), status );
+
+/* Free resources. */
+      old_units = astFree( old_units );
+
+/* Annul the Axis pointer. */
+      ax = astAnnul( ax );
+   }
+}
+
+static int ConsistentMaxAxes( AstFrame *this, int value, int *status ) {
+/*
+*  Name:
+*     ConsistentMaxAxes
+
+*  Purpose:
+*     Ensure a consistent value when setting the MaxAxes attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int ConsistentMaxAxes( AstFrame *this, int value, int *status )
+
+*  Class Membership:
+*     Frame member function.
+
+*  Description:
+*     This function accepts a value which is to be set for a Frame's MaxAxes
+*     attribute and returns an appropriately adjusted value to be assigned
+*     to the Frame structure's max_axes component. If necessary, the Frame's
+*     MinAxes attribute is adjusted to remain consistent with the new MaxAxes
+*     value (but note that the MaxAxes value itself is not assigned by this
+*     function).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     value
+*        The new value being set for the MaxAxes attribute.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The value to be assigned to the max_axes component.
+
+*  Notes:
+*     - A value of -INT_MAX will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return -INT_MAX;
+
+/* Ensure that the result value isn't negative. */
+   result = ( value >= 0 ) ? value : 0;
+
+/* Check if the MinAxes attribute is set. If not, its default value will be
+   consistent with the MaxAxes value (the DefaultMinAxes function ensures
+   this). Otherwise, obtain its value to check for consistency. */
+   if ( astTestMinAxes( this ) ) {
+
+/* If necessary, set a new MinAxes value to prevent it exceeding the MaxAxes
+   value about to be returned. */
+      if ( astGetMinAxes( this ) > result ) astSetMinAxes( this, result );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = -INT_MAX;
+
+/* Return the result. */
+   return result;
+}
+
+static int ConsistentMinAxes( AstFrame *this, int value, int *status ) {
+/*
+*  Name:
+*     ConsistentMinAxes
+
+*  Purpose:
+*     Ensure a consistent value when setting the MinAxes attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int ConsistentMinAxes( AstFrame *this, int value, int *status )
+
+*  Class Membership:
+*     Frame member function.
+
+*  Description:
+*     This function accepts a value which is to be set for a Frame's MinAxes
+*     attribute and returns an appropriately adjusted value to be assigned
+*     to the Frame structure's min_axes component. If necessary, the Frame's
+*     MaxAxes attribute is adjusted to remain consistent with the new MinAxes
+*     value (but note that the MinAxes value itself is not assigned by this
+*     function).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     value
+*        The new value being set for the MinAxes attribute.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The value to be assigned to the min_axes component.
+
+*  Notes:
+*     - A value of -INT_MAX will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return -INT_MAX;
+
+/* Ensure that the result value isn't negative. */
+   result = ( value >= 0 ) ? value : 0;
+
+/* Check if the MaxAxes attribute is set. If not, its default value will be
+   consistent with the MinAxes value (the DefaultMaxAxes function ensures
+   this). Otherwise, obtain its value to check for consistency. */
+   if ( astTestMaxAxes( this ) ) {
+
+/* If necessary, set a new MaxAxes value to prevent it being less than the
+   MinAxes value about to be returned. */
+      if ( astGetMaxAxes( this ) < result ) astSetMaxAxes( this, result );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = -INT_MAX;
+
+/* Return the result. */
+   return result;
+}
+
+static AstFrameSet *Convert( AstFrame *from, AstFrame *to,
+                             const char *domainlist, int *status ) {
+/*
+*++
+*  Name:
+c     astConvert
+f     AST_CONVERT
+
+*  Purpose:
+*     Determine how to convert between two coordinate systems.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     AstFrameSet *astConvert( AstFrame *from, AstFrame *to,
+c                              const char *domainlist )
+f     RESULT = AST_CONVERT( FROM, TO, DOMAINLIST, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function compares two Frames and determines whether it is
+*     possible to convert between the coordinate systems which they
+*     represent. If conversion is possible, it returns a FrameSet
+*     which describes the conversion and which may be used (as a
+*     Mapping) to transform coordinate values in either direction.
+*
+*     The same function may also be used to determine how to convert
+*     between two FrameSets (or between a Frame and a FrameSet, or
+*     vice versa). This mode is intended for use when (for example)
+*     two images have been calibrated by attaching a FrameSet to each.
+c     astConvert might then be used to search for a
+f     AST_CONVERT might then be used to search for a
+*     celestial coordinate system that both images have in common, and
+*     the result could then be used to convert between the pixel
+*     coordinates of both images -- having effectively used their
+*     celestial coordinate systems to align them.
+*
+*     When using FrameSets, there may be more than one possible
+*     intermediate coordinate system in which to perform the
+*     conversion (for instance, two FrameSets might both have
+*     celestial coordinates, detector coordinates, pixel coordinates,
+*     etc.). A comma-separated list of coordinate system domains may
+*     therefore be given which defines a priority order to use when
+*     selecting the intermediate coordinate system.  The path used for
+*     conversion must go via an intermediate coordinate system whose
+*     Domain attribute matches one of the domains given. If conversion
+*     cannot be achieved using the first domain, the next one is
+*     considered, and so on, until success is achieved.
+
+*  Parameters:
+c     from
+f     FROM = INTEGER (Given)
+*        Pointer to a Frame which represents the "source" coordinate
+*        system.  This is the coordinate system in which you already
+*        have coordinates available.
+*
+*        If a FrameSet is given, its current Frame (as determined by
+*        its Current attribute) is taken to describe the source
+*        coordinate system. Note that the Base attribute of this
+*        FrameSet may be modified by this function to indicate which
+*        intermediate coordinate system was used (see under
+*        "FrameSets" in the "Applicability" section for details).
+c     to
+f     TO = INTEGER (Given)
+*        Pointer to a Frame which represents the "destination"
+*        coordinate system. This is the coordinate system into which
+*        you wish to convert your coordinates.
+*
+*        If a FrameSet is given, its current Frame (as determined by
+*        its Current attribute) is taken to describe the destination
+*        coordinate system. Note that the Base attribute of this
+*        FrameSet may be modified by this function to indicate which
+*        intermediate coordinate system was used (see under
+*        "FrameSets" in the "Applicability" section for details).
+c     domainlist
+f     DOMAINLIST = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string containing a
+f        A character string containing a
+*        comma-separated list of Frame domains. This may be used to
+*        define a priority order for the different intermediate
+*        coordinate systems that might be used to perform the
+*        conversion.
+*
+*        The function will first try to obtain a conversion by making
+*        use only of an intermediate coordinate system whose Domain
+*        attribute matches the first domain in this list. If this
+*        fails, the second domain in the list will be used, and so on,
+*        until conversion is achieved. A blank domain (e.g. two
+*        consecutive commas) indicates that all coordinate systems
+*        should be considered, regardless of their domains.
+*
+*        This list is case-insensitive and all white space is ignored.
+*        If you do not wish to restrict the domain in this way,
+c        you should supply an empty string. This is normally
+f        you should supply a blank string. This is normally
+*        appropriate if either of the source or destination coordinate
+*        systems are described by Frames (rather than FrameSets),
+*        since there is then usually only one possible choice of
+*        intermediate coordinate system.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astConvert()
+f     AST_CONVERT = INTEGER
+*        If the requested coordinate conversion is possible, the
+*        function returns a pointer to a FrameSet which describes the
+*        conversion. Otherwise, a null Object pointer (AST__NULL) is
+*        returned without error.
+*
+*        If a FrameSet is returned, it will contain two Frames. Frame
+*        number 1 (its base Frame) will describe the source coordinate
+c        system, corresponding to the "from" parameter. Frame number 2
+f        system, corresponding to the FROM argument. Frame number 2
+*        (its current Frame) will describe the destination coordinate
+c        system, corresponding to the "to" parameter. The Mapping
+f        system, corresponding to the TO argument. The Mapping
+*        which inter-relates these two Frames will perform the
+*        required conversion between their respective coordinate
+*        systems.
+*
+*        Note that a FrameSet may be used both as a Mapping and as a
+*        Frame.  If the result is used as a Mapping (e.g. with
+c        astTran2), then it provides a means of converting coordinates
+f        AST_TRAN2), then it provides a means of converting coordinates
+*        from the source to the destination coordinate system (or
+*        vice versa if its inverse transformation is selected). If it
+*        is used as a Frame, its attributes will describe the
+*        destination coordinate system.
+
+*  Applicability:
+*     DSBSpecFrame
+*        If the AlignSideBand attribute is non-zero, alignment occurs in the
+*        upper sideband expressed within the spectral system and standard of
+*        rest given by attributes AlignSystem and AlignStdOfRest. If
+*        AlignSideBand is zero, the two DSBSpecFrames are aligned as if
+*        they were simple SpecFrames (i.e. the SideBand is ignored).
+*     Frame
+*        This function applies to all Frames. Alignment occurs within the
+*        coordinate system given by attribute AlignSystem.
+*     FrameSet
+c        If either of the "from" or "to" parameters is a pointer to a
+f        If either of the FROM or TO arguments is a pointer to a
+c        FrameSet, then astConvert will attempt to convert from the
+f        FrameSet, then AST_CONVERT will attempt to convert from the
+c        coordinate system described by the current Frame of the "from"
+f        coordinate system described by the current Frame of the FROM
+c        FrameSet to that described by the current Frame of the "to"
+f        FrameSet to that described by the current Frame of the TO
+*        FrameSet.
+*
+*        To achieve this, it will consider all of the Frames within
+*        each FrameSet as a possible way of reaching an intermediate
+*        coordinate system that can be used for the conversion. There
+*        is then the possibility that more than one conversion path
+*        may exist and, unless the choice is sufficiently restricted
+c        by the "domainlist" string, the sequence in which the Frames
+f        by the DOMAINLIST string, the sequence in which the Frames
+*        are considered can be important. In this case, the search
+*        for a conversion path proceeds as follows:
+c        - Each field in the "domainlist" string is considered in turn.
+f        - Each field in the DOMAINLIST string is considered in turn.
+*        - The Frames within each FrameSet are considered in a
+*        specific order: (1) the base Frame is always considered
+*        first, (2) after this come all the other Frames in
+*        Frame-index order (but omitting the base and current Frames),
+*        (3) the current Frame is always considered last.  However, if
+*        either FrameSet's Invert attribute is set to a non-zero value
+*        (so that the FrameSet is inverted), then its Frames are
+*        considered in reverse order. (Note that this still means that
+*        the base Frame is considered first and the current Frame
+*        last, because the Invert value will also cause these Frames
+*        to swap places.)
+*        - All source Frames are first considered (in the appropriate
+*        order) for conversion to the first destination Frame. If no
+*        suitable intermediate coordinate system emerges, they are
+*        then considered again for conversion to the second
+*        destination Frame (in the appropriate order), and so on.
+*        - Generally, the first suitable intermediate coordinate
+*        system found is used. However, the overall Mapping between
+*        the source and destination coordinate systems is also
+*        examined.  Preference is given to cases where both the
+*        forward and inverse transformations are defined (as indicated
+*        by the TranForward and TranInverse attributes). If only one
+*        transformation is defined, the forward one is preferred.
+*        - If the domain of the intermediate coordinate system matches
+c        the current "domainlist" field, the conversion path is
+f        the current DOMAINLIST field, the conversion path is
+c        accepted. Otherwise, the next "domainlist" field is considered
+f        accepted. Otherwise, the next DOMAINLIST field is considered
+*        and the process repeated.
+*
+*        If conversion is possible, the Base attributes of the two
+*        FrameSets will be modified on exit to identify the Frames
+*        used to access the intermediate coordinate system which was
+*        finally accepted.
+*
+*        Note that it is possible to force a particular Frame within a
+*        FrameSet to be used as the basis for the intermediate
+*        coordinate system, if it is suitable, by (a) focussing
+*        attention on
+c        it by specifying its domain in the "domainlist" string, or (b)
+f        it by specifying its domain in the DOMAINLIST string, or (b)
+*        making it the base Frame, since this is always considered
+*        first.
+*     SpecFrame
+*        Alignment occurs within the spectral system and standard of rest
+*        given by attributes AlignSystem and AlignStdOfRest.
+*     TimeFrame
+*        Alignment occurs within the time system and time scale given by
+*        attributes AlignSystem and AlignTimeScale.
+
+*  Examples:
+c     cvt = astConvert( a, b, "" );
+f     CVT = AST_CONVERT( A, B, ' ', STATUS )
+*        Attempts to convert between the coordinate systems represented
+c        by "a" and "b" (assumed to be Frames). If successful, a FrameSet
+f        by A and B (assumed to be Frames). If successful, a FrameSet
+c        is returned via the "cvt" pointer which may be used to apply the
+f        is returned via the CVT pointer which may be used to apply the
+c        conversion to sets of coordinates (e.g. using astTran2).
+f        conversion to sets of coordinates (e.g. using AST_TRAN2).
+c     cvt = astConvert( astSkyFrame(""), astSkyFrame("Equinox=2005"), "" );
+f     CVT = AST_CONVERT( AST_SKYFRAME( ' ', STATUS ), AST_SKYFRAME( 'Equinox=2005', STATUS ), ' ', STATUS )
+*        Creates a FrameSet which describes precession in the default
+*        FK5 celestial coordinate system between equinoxes J2000 (also
+c        the default) and J2005. The returned "cvt" pointer may then
+f        the default) and J2005. The returned CVT pointer may then
+c        be passed to astTran2 to apply this precession correction to
+f        be passed to AST_TRAN2 to apply this precession correction to
+*        any number of coordinate values given in radians.
+*
+*        Note that the returned FrameSet also contains information
+*        about how to format coordinate values. This means that
+*        setting its Report attribute to 1 is a simple way to obtain
+*        printed output (formatted in sexagesimal notation) to show
+*        the coordinate values before and after conversion.
+c     cvt = astConvert( a, b, "sky,detector," );
+f     CVT = AST_CONVERT( A, B, 'SKY,DETECTOR,', STATUS )
+*        Attempts to convert between the coordinate systems
+c        represented by the current Frames of "a" and "b"
+f        represented by the current Frames of A and B
+*        (now assumed to be FrameSets), via the intermediate "SKY"
+*        coordinate system.  This, by default, is the Domain
+*        associated with a celestial coordinate system represented by
+*        a SkyFrame.
+*
+*        If this fails (for example, because either FrameSet lacks
+*        celestial coordinate information), then the user-defined
+*        "DETECTOR" coordinate system is used instead. If this also
+*        fails, then all other possible ways of achieving conversion
+*        are considered before giving up.
+*
+c        The returned pointer "cvt" indicates whether conversion was
+f        The returned pointer CVT indicates whether conversion was
+*        possible and will have the value AST__NULL if it was not. If
+c        conversion was possible, "cvt" will point at a new FrameSet
+f        conversion was possible, CVT will point at a new FrameSet
+*        describing the conversion.
+*
+*        The Base attributes of the two FrameSets
+c        will be set by astConvert to indicate which of their Frames was
+f        will be set by AST_CONVERT to indicate which of their Frames was
+*        used for the intermediate coordinate system. This means that
+*        you can subsequently determine which coordinate system was
+*        used by enquiring the Domain attribute of either base Frame.
+
+*  Notes:
+*     -  The Mapping represented by the returned FrameSet results in
+*     alignment taking place in the coordinate system specified by the
+c     AlignSystem attribute of the "to" Frame. See the description of the
+f     AlignSystem attribute of the TO Frame. See the description of the
+*     AlignSystem attribute for further details.
+*     - When aligning (say) two images, which have been calibrated by
+*     attaching FrameSets to them, it is usually necessary to convert
+*     between the base Frames (representing "native" pixel
+*     coordinates) of both FrameSets. This may be achieved by
+*     inverting the FrameSets (e.g. using astInvert) so as to
+*     interchange their base and current Frames before using
+*     astConvert.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+* Implementation Notes:
+*    This function is simply a wrap-up for the protected astConvertX
+*    method which performs the required processing but swaps the order
+*    of the first two arguments. This is a trick to allow the
+*    astConvert method to be over-ridden by derived classes on the
+*    basis of the class of either of the first two arguments.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Invoke the "astConvertX" method with the first two arguments
+   swapped. */
+   return astConvertX( to, from, domainlist );
+}
+
+static AstFrameSet *ConvertX( AstFrame *to, AstFrame *from,
+                              const char *domainlist, int *status ) {
+/*
+*+
+*  Name:
+*     astConvertX
+
+*  Purpose:
+*     Determine how to convert between two coordinate systems.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstFrameSet *astConvertX( AstFrame *to, AstFrame *from,
+*                               const char *domainlist )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function performs the processing for the public astConvert
+*     method and has exactly the same interface except that the order
+*     of the first two arguments is swapped. This is a trick to allow
+*     the astConvert method to be over-ridden by derived classes on
+*     the basis of the class of either of its first two arguments.
+*
+*     See the astConvert method for details of the interface.
+*-
+
+*  Implementation Deficiencies:
+*     - This function's job is basically to negotiate with two Frames
+*     to try and find a mutually agreeable coordinate system for
+*     conversion.  Ideally, it should be able to juggle the number of
+*     axes, etc. to do this.  At present, however, the implementation
+*     is much simpler. This is adequate for the Frame classes which
+*     exist at the time of writing, but the implementation may need
+*     beefing up in future.
+*     - One likely problem is with attributes which default in both
+*     the source and destination Frames. This means they also default
+*     in the common coordinate system. If these default values were to
+*     differ when matching different target Frames, however, we would
+*     be in trouble, because the common coordinate system would not
+*     then be remaining constant. The longer-term solution to this is
+*     probably to provide some mechanism to "fix" all attribute values
+*     for a Frame, by taking any attributes that are un-set and
+*     explicitly setting a firm value (equal to the default) so they
+*     cannot then change.
+*/
+
+/* Local Variables: */
+   AstFrameSet *result;          /* Pointer to Mapping to be returned */
+   AstFrame *ftmp;                /* Pointer to returned Frame */
+   AstMapping **map1_address;    /* Location of first Mapping pointer */
+   AstMapping **map2_address;    /* Location of second Mapping pointer */
+   AstMapping *common0;          /* Initial common coordinate system */
+   AstMapping *common1;          /* Improved common coordinate system */
+   AstMapping *common2;          /* Final common coordinate system */
+   AstMapping *frame1;           /* Pointer to Frame for first match */
+   AstMapping *frame2;           /* Pointer to Frame for second match */
+   AstMapping *from_map;         /* Pointer to "from" Mapping */
+   AstMapping *map;              /* Pointer to conversion Mapping */
+   AstMapping *result_map;       /* Pointer to result Mapping */
+   AstMapping *tmp;              /* Temporary Mapping pointer */
+   AstMapping *to_map;           /* Pointer to "to" Mapping */
+   char *domain;                 /* Pointer to result domain */
+   char *domain_end;             /* Pointer to null at end of domain */
+   char *domainlist_copy;        /* Pointer to copy of domainlist */
+   int *axes1;                   /* Pointer to axis assignments */
+   int *axes2;                   /* Pointer to axis assignments */
+   int best_score;               /* Score assigned to best match */
+   int icom;                     /* Common coordinate system loop counter */
+   int match1;                   /* First match succeeded? */
+   int match2;                   /* Second match succeeded? */
+   int match;                    /* Overall match found? */
+   int perfect;                  /* Perfect match found? */
+   int score;                    /* Score assigned to match */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialisation. */
+   result_map = NULL;
+
+/* Make a temporary copy of the domains list. */
+   domainlist_copy = astStore( NULL, domainlist,
+                               strlen( domainlist ) + (size_t) 1 );
+   if ( astOK ) {
+
+/* Loop to inspect each comma-separated field in the domains list
+   until an error occurs, all the domains are used up, or a match is
+   found. */
+      domain = domainlist_copy;
+      match = 0;
+      while ( astOK && domain && !match ) {
+
+/* Change the comma at the end of each field to a null to terminate
+   the domain. Then convert the domain to upper case and eliminate
+   white space. */
+         if ( ( domain_end = strchr( domain, ',' ) ) ) *domain_end = '\0';
+         CleanDomain( domain, status );
+
+/* For any given domain, we will ignore imperfect matches in favour of
+   better ones by assigning a score to each match. Initialise the best
+   score value for the current domain. */
+         best_score = -1;
+
+/* Loop to consider both the "to" and "from" Frames in turn as the
+   basis of a possible common coordinate system. Quit looping early if
+   an error occurs or a perfect match is found. */
+         perfect = 0;
+         for ( icom = 0; astOK && !perfect && ( icom <= 1 ); icom++ ) {
+
+/* Make a copy of the Frame representing the initial guess at a common
+   coordinate system. We will use this to probe the other
+   Frame. Ensure that axes are not preserved (so that we convert to
+   the common axis number/order). */
+            common0 = astCopy( icom ? from : to );
+            astSetPreserveAxes( common0, 0 );
+
+/* Also, if the current domain is not blank, set the Domain attribute (so
+   we will only find coordinate systems which match the current
+   "domainlist" field). */
+            if ( *domain ) astSetDomain( common0, domain );
+
+/* Obtain a pointer to the other Frame. */
+            frame1 = astClone( icom ? to : from );
+
+/* Set the address at which to store the resulting Mapping pointer. */
+            map1_address = icom ? &to_map : &from_map;
+
+/* See if conversion from "frame1" to the common coordinate system is
+   possible.  If successful, this results in a new approximation
+   ("common1") to the possible common coordinate system. */
+            match1 = astMatch( common0, frame1, 1, &axes1, &axes2,
+                               map1_address, &ftmp );
+            common1 = (AstMapping *) ftmp;
+
+/* If successful, free memory allocated for the axis association
+   arrays, which are not needed. */
+            if ( astOK && match1 ) {
+               axes1 = astFree( axes1 );
+               axes2 = astFree( axes2 );
+
+/* Using the improved approximation to the common coordinate system,
+   now test if conversion from the alternative Frame "frame2" is
+   possible. */
+               frame2 = astClone( icom ? from : to );
+               map2_address = icom ? &from_map : &to_map;
+               astSetPreserveAxes( common1, 0 );
+               match2 = astMatch( common1, frame2, 1, &axes1, &axes2,
+                                  map2_address, &ftmp );
+               common2 = (AstMapping *) ftmp;
+
+/* If successful, free memory allocated for the axis association
+   arrays, which are not needed. */
+               if ( astOK && match2 ) {
+                  axes1 = astFree( axes1 );
+                  axes2 = astFree( axes2 );
+
+/* Invert the "to" Mapping and concatenate the two Mappings to
+   describe the conversion between the "from" and "to" Frames. Then
+   simplify the result. */
+                  astInvert( to_map );
+                  tmp = (AstMapping *) astCmpMap( from_map, to_map, 1, "", status );
+                  map = astSimplify( tmp );
+                  tmp = astAnnul( tmp );
+
+/* Assign a score that favours Mappings with both transformations
+   available over those with only one, and Mappings with only a
+   forward transformation over those with only an inverse
+   transformation. */
+                  score = ( astGetTranForward( map ) ? 2 : 0 ) +
+                          ( astGetTranInverse( map ) ? 1 : 0 );
+
+/* If the new score is better than the previous one (or is the first
+   one), note that we have a possible match. */
+                  if ( astOK && ( score > best_score ) ) {
+                     match = 1;
+
+/* Update the best score and note if it indicates a perfect match (in
+   which case we can stop searching at this point). */
+                     best_score = score;
+                     perfect = ( best_score >= 3 );
+
+/* Annul any previous result Mapping pointer and replace it with this
+   better one. */
+                     if ( result_map ) result_map = astAnnul( result_map );
+                     result_map = astClone( map );
+                  }
+
+/* Annul pointers to all the intermediate Objects. */
+                  map = astAnnul( map );
+                  common2 = astAnnul( common2 );
+                  *map2_address = astAnnul( *map2_address );
+               }
+               frame2 = astAnnul( frame2 );
+               common1 = astAnnul( common1 );
+               *map1_address = astAnnul( *map1_address );
+            }
+            frame1 = astAnnul( frame1 );
+            common0 = astAnnul( common0 );
+         }
+
+/* Go on to consider the next field in the domains list. */
+         domain = domain_end ? domain_end + 1 : NULL;
+      }
+   }
+
+/* Free the domain list copy. */
+   domainlist_copy = astFree( domainlist_copy );
+
+/* If returning a result, build the result FrameSet. Then annul the
+   result Mapping pointer. */
+   if ( result_map ) {
+      result = astFrameSet( from, "", status );
+      astAddFrame( result, AST__BASE, result_map, to );
+      result_map = astAnnul( result_map );
+   }
+
+/* If an error occurred, annul the result FrameSet pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int DefaultMaxAxes( AstFrame *this, int *status ) {
+/*
+*  Name:
+*     DefaultMaxAxes
+
+*  Purpose:
+*     Obtain the MaxAxes attribute from a Frame structure, with defaulting.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int DefaultMaxAxes( AstFrame *this, int *status )
+
+*  Class Membership:
+*     Frame member function.
+
+*  Description:
+*     This function inspects the max_axes component of a Frame structure and
+*     derives a value for the MaxAxes attribute. If the component's value
+*     indicates that the attribute has not been set, a suitable default is
+*     returned which is consistent with the state of the Frames's MinAxes
+*     attribute.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The value to be used for the MaxAxes attribute.
+*/
+
+/* Local Variables. */
+   int result;                   /* Result to be returned */
+   int min_axes;                 /* Value of MinAxes attribute */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* If the Frame's max_axes component is set, return its value. */
+   if ( this->max_axes != -INT_MAX ) {
+      result = this->max_axes;
+
+/* Otherwise, the preferred default value is the number of Frame axes. */
+   } else {
+      result = astGetNaxes( this );
+
+/* Before returning this value, check if the MinAxes attribute is set. If it
+   is, obtain its value. */
+      if ( astTestMinAxes( this ) ) {
+         min_axes = astGetMinAxes( this );
+
+/* If necessary, increase the MaxAxes default value so that it is not less than
+   MinAxes. */
+         if ( result < min_axes ) result = min_axes;
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int DefaultMinAxes( AstFrame *this, int *status ) {
+/*
+*  Name:
+*     DefaultMinAxes
+
+*  Purpose:
+*     Obtain the MinAxes attribute from a Frame structure, with defaulting.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int DefaultMinAxes( AstFrame *this, int *status )
+
+*  Class Membership:
+*     Frame member function.
+
+*  Description:
+*     This function inspects the min_axes component of a Frame structure and
+*     derives a value for the MinAxes attribute. If the component's value
+*     indicates that the attribute has not been set, a suitable default is
+*     returned which is consistent with the state of the Frames's MaxAxes
+*     attribute.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The value to be used for the MinAxes attribute.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result to be returned */
+   int max_axes;                 /* Value of MaxAxes attribute */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* If the Frame's min_axes component is set, return its value. */
+   if ( this->min_axes != -INT_MAX ) {
+      result = this->min_axes;
+
+/* Otherwise, the preferred default value is the number of Frame axes. */
+   } else {
+      result = astGetNaxes( this );
+
+/* Before returning this value, check if the MaxAxes attribute is set. If it
+   is, obtain its value. */
+      if ( astTestMaxAxes( this ) ) {
+         max_axes = astGetMaxAxes( this );
+
+/* If necessary, reduce the MinAxes default value so that it does not exceed
+   MaxAxes. */
+         if ( result > max_axes ) result = max_axes;
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static double Distance( AstFrame *this,
+                        const double point1[], const double point2[], int *status ) {
+/*
+*++
+*  Name:
+c     astDistance
+f     AST_DISTANCE
+
+*  Purpose:
+*     Calculate the distance between two points in a Frame.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     double astDistance( AstFrame *this,
+c                         const double point1[], const double point2[] )
+f     RESULT = AST_DISTANCE( THIS, POINT1, POINT2, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function finds the distance between two points whose Frame
+*     coordinates are given. The distance calculated is that along
+*     the geodesic curve that joins the two points.
+*
+*     For example, in a basic Frame, the distance calculated will be
+*     the Cartesian distance along the straight line joining the two
+*     points. For a more specialised Frame describing a sky coordinate
+*     system, however, it would be the distance along the great circle
+*     passing through two sky positions.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     point1
+f     POINT1( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the first point.
+c     point2
+f     POINT2( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        containing the coordinates of the second point.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astDistance
+f     AST_DISTANCE = DOUBLE PRECISION
+*        The distance between the two points.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input coordinates has this value.
+*     - A "bad" value will also be returned if this function is
+c     invoked with the AST error status set, or if it should fail for
+f     invoked with STATUS set to an error value, or if it should fail for
+*     any reason.
+*--
+*/
+
+/* Local Variables: */
+   double delta;                 /* Separation along an axis */
+   double result;                /* Result value to return */
+   int axis;                     /* Loop counter for axes */
+   int naxes;                    /* Number of Frame axes */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain the number of Frame axes. */
+   naxes = astGetNaxes( this );
+   if ( astOK ) {
+
+/* Loop to determine the Cartesian distance between the points. */
+      result = 0.0;
+      for ( axis = 0; axis < naxes; axis++ ) {
+
+/* If any of the coordinates supplied is bad, set the distance to be
+   bad and quit looping. */
+         if ( ( point1[ axis ] == AST__BAD ) ||
+              ( point2[ axis ] == AST__BAD ) ) {
+            result = AST__BAD;
+            break;
+
+/* Otherwise, accumulate the sum of squared separations along each
+   axis. */
+         } else {
+            delta = point1[ axis ] - point2[ axis ];
+            result += ( delta * delta );
+         }
+      }
+
+/* Take the square root to find the distance (if valid). */
+      if ( result != AST__BAD ) result = sqrt( result );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int DoNotSimplify( AstMapping *this, int *status ) {
+/*
+*  Name:
+*     DoNotSimplify
+
+*  Purpose:
+*     Check if a Mapping is appropriate for simplification.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int DoNotSImplify( AstMapping *this );
+
+*  Class Membership:
+*     CmpMap member function (over-rides the astDoNotSimplify protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns a flag indivating if the supplied Mapping is
+*     appropriate for simplification.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the supplied Mapping is not appropriate for
+*     simplification, and zero otherwise.
+
+*  Notes:
+*     - A value of 0 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+
+*/
+
+/* Unlike basic Mappings, Frames that have a set value for the Ident
+   can be simplified. So always return zero. */
+   return 0;
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two Frames are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the astEqual protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two Frames are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Frame.
+*     that
+*        Pointer to the second Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the Frames are equivalent, zero otherwise.
+
+*  Notes:
+*     - The two Frames are considered equivalent if the Mapping between
+*     them is a UnitMap.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *that;            /* Pointer to the second Frame structure */
+   AstFrame *this;            /* Pointer to the first Frame structure */
+   AstFrameSet *fs;           /* FrameSet connecting the two Frames */
+   AstMapping *map1;          /* Mapping connecting the two Frames */
+   AstMapping *map2;          /* Simplified mapping connecting two Frames */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Checks that the second object is of the same class as the first . */
+   if( !strcmp( astGetClass( this_object ), astGetClass( that_object ) ) ){
+
+/* Obtain pointers to the two Frame structures. */
+      this = (AstFrame *) this_object;
+      that = (AstFrame *) that_object;
+
+/* Get the Mapping between them, and see if it is a UnitMap. */
+      fs = astConvert( that, this, "" );
+      if( fs ) {
+         map1 = astGetMapping( fs, AST__BASE, AST__CURRENT );
+         map2 = astSimplify( map1 );
+         result = astIsAUnitMap( map2 );
+         map1 = astAnnul( map1 );
+         map2 = astAnnul( map2 );
+         fs = astAnnul( fs );
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int Fields( AstFrame *this, int axis, const char *fmt,
+                   const char *str, int maxfld, char **fields,
+                   int *nc, double *val, int *status ) {
+/*
+*+
+*  Name:
+*     astFields
+
+*  Purpose:
+*     Identify numerical fields within a formatted Axis value.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astFields( AstFrame *this, int axis, const char *fmt,
+*                    const char *str, int maxfld, char **fields,
+*                    int *nc, double *val )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function identifies the numerical fields within a Frame axis
+*     value that has been formatted using astAxisFormat. It assumes that
+*     the value was formatted using the supplied format string. It also
+*     returns the equivalent floating point value.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the Frame axis for which the values have been
+*        formatted (axis numbering starts at zero for the first axis).
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format used when creating "str".
+*     str
+*        Pointer to a constant null-terminated string containing the
+*        formatted value.
+*     maxfld
+*        The maximum number of fields to identify within "str".
+*     fields
+*        A pointer to an array of at least "maxfld" character pointers.
+*        Each element is returned holding a pointer to the start of the
+*        corresponding field  in "str" (in the order in which they occur
+*        within "str"), or NULL if no corresponding field can be found.
+*     nc
+*        A pointer to an array of at least "maxfld" integers. Each
+*        element is returned holding the number of characters in the
+*        corresponding field, or zero if no corresponding field can be
+*        found.
+*     val
+*        Pointer to a location at which to store the value
+*        equivalent to the returned field values. If this is NULL,
+*        it is ignored.
+
+*  Returned Value:
+*     The number of fields succesfully identified and returned.
+
+*  Notes:
+*     - Leading and trailing spaces are ignored.
+*     - If the formatted value is not consistent with the supplied format
+*     string, then a value of zero will be returned, "fields" will be
+*     returned holding NULLs, "nc" will be returned holding zeros, and
+*     "val" is returned holding VAL__BAD.
+*     - Fields are counted from the start of the formatted string. If the
+*     string contains more than "maxfld" fields, then trailing fields are
+*     ignored.
+*     - If this function is invoked with the global error status set, or
+*     if it should fail for any reason, then a value of zero will be returned
+*     as the function value, and "fields", "nc" and "val"  will be returned
+*     holding their supplied values
+*-
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   int result;                   /* Result field count to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate the axis index and obtain a pointer to the required
+   Axis. */
+   (void) astValidateAxis( this, axis, 1, "astFields" );
+   ax = astGetAxis( this, axis );
+
+/* Invoke the Axis astAxisFields method to perform the processing. */
+   result = astAxisFields( ax, fmt, str, maxfld, fields, nc, val );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static AstFrameSet *FindFrame( AstFrame *target, AstFrame *template,
+                               const char *domainlist, int *status ) {
+/*
+*++
+*  Name:
+c     astFindFrame
+f     AST_FINDFRAME
+
+*  Purpose:
+*     Find a coordinate system with specified characteristics.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     AstFrameSet *astFindFrame( AstFrame *target, AstFrame *template,
+c                                const char *domainlist )
+f     RESULT = AST_FINDFRAME( TARGET, TEMPLATE, DOMAINLIST, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function uses a "template" Frame to search another Frame
+*     (or FrameSet) to identify a coordinate system which has a
+*     specified set of characteristics. If a suitable coordinate
+*     system can be found, the function returns a pointer to a
+*     FrameSet which describes the required coordinate system and how
+*     to convert coordinates to and from it.
+*
+*     This function is provided to help answer general questions about
+*     coordinate systems, such as typically arise when coordinate
+*     information is imported into a program as part of an initially
+*     unknown dataset. For example:
+*     - Is there a wavelength scale?
+*     - Is there a 2-dimensional coordinate system?
+*     - Is there a celestial coordinate system?
+*     - Can I plot the data in ecliptic coordinates?
+*
+*     You can also use this function as a means of reconciling a
+*     user's preference for a particular coordinate system (for
+*     example, what type of axes to draw) with what is actually
+*     possible given the coordinate information available.
+*
+*     To perform a search, you supply a "target" Frame (or FrameSet)
+*     which represents the set of coordinate systems to be searched.
+*     If a basic Frame is given as the target, this set of coordinate
+*     systems consists of the one described by this Frame, plus all
+*     other "virtual" coordinate systems which can potentially be
+*     reached from it by applying built-in conversions (for example,
+*     any of the celestial coordinate conversions known to the AST
+*     library would constitute a "built-in" conversion). If a FrameSet
+*     is given as the target, the set of coordinate systems to be
+*     searched consists of the union of those represented by all the
+*     individual Frames within it.
+*
+*     To select from this large set of possible coordinate systems,
+*     you supply a "template" Frame which is an instance of the type
+*     of Frame you are looking for. Effectively, you then ask the
+*     function to "find a coordinate system that looks like this".
+*
+*     You can make your request more or less specific by setting
+*     attribute values for the template Frame. If a particular
+*     attribute is set in the template, then the function will only
+*     find coordinate systems which have exactly the same value for
+*     that attribute.  If you leave a template attribute un-set,
+*     however, then the function has discretion about the value the
+*     attribute should have in any coordinate system it finds. The
+*     attribute will then take its value from one of the actual
+*     (rather than virtual) coordinate systems in the target. If the
+*     target is a FrameSet, its Current attribute will be modified to
+*     indicate which of its Frames was used for this purpose.
+*
+*     The result of this process is a coordinate system represented by
+*     a hybrid Frame which acquires some attributes from the template
+*     (but only if they were set) and the remainder from the
+*     target. This represents the "best compromise" between what you
+*     asked for and what was available. A Mapping is then generated
+*     which converts from the target coordinate system to this hybrid
+*     one, and the returned FrameSet encapsulates all of this
+*     information.
+
+*  Parameters:
+c     target
+f     TARGET = INTEGER (Given)
+*        Pointer to the target Frame (or FrameSet).
+*
+*        Note that if a FrameSet is supplied (and a suitable
+*        coordinate system is found), then its Current attribute will
+*        be modified to indicate which Frame was used to obtain
+*        attribute values which were not specified by the template.
+*        This Frame will, in some sense, represent the "closest"
+*        non-virtual coordinate system to the one you requested.
+c     template
+f     TEMPLATE = INTEGER (Given)
+*        Pointer to the template Frame, which should be an instance of
+*        the type of Frame you wish to find. If you wanted to find a
+*        Frame describing a celestial coordinate system, for example,
+*        then you might use a SkyFrame here. See the "Examples"
+*        section for more ideas.
+c     domainlist
+f     DOMAINLIST = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string containing a
+f        A character string containing a
+*        comma-separated list of Frame domains. This may be used to
+*        establish a priority order for the different types of
+*        coordinate system that might be found.
+*
+*        The function will first try to find a suitable coordinate
+*        system whose Domain attribute equals the first domain in this
+*        list. If this fails, the second domain in the list will be
+*        used, and so on, until a result is obtained. A blank domain
+*        (e.g. two consecutive commas) indicates that any coordinate
+*        system is acceptable (subject to the template) regardless of
+*        its domain.
+*
+*        This list is case-insensitive and all white space is ignored.
+*        If you do not wish to restrict the domain in this way,
+c        you should supply an empty string.
+f        you should supply a blank string.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astFindFrame()
+f     AST_FINDFRAME = INTEGER
+*        If the search is successful, the function returns a pointer
+*        to a FrameSet which contains the Frame found and a
+*        description of how to convert to (and from) the coordinate
+*        system it represents. Otherwise, a null Object pointer
+*        (AST__NULL) is returned without error.
+*
+*        If a FrameSet is returned, it will contain two Frames. Frame
+*        number 1 (its base Frame) represents the target coordinate
+*        system and will be the same as the (base Frame of the)
+*        target. Frame number 2 (its current Frame) will be a Frame
+*        representing the coordinate system which the function
+*        found. The Mapping which inter-relates these two Frames will
+*        describe how to convert between their respective coordinate
+*        systems.
+*
+*        Note that a FrameSet may be used both as a Mapping and as a
+*        Frame. If the result is used as a Mapping (e.g. with
+*        astTran2), then it provides a means of converting coordinates
+*        from the target coordinate system into the new coordinate
+*        system that was found (and vice versa if its inverse
+*        transformation is selected). If it is used as a Frame, its
+*        attributes will describe the new coordinate system.
+
+*  Applicability:
+*     Frame
+*        This function applies to all Frames.
+*     FrameSet
+*        If the target is a FrameSet, the possibility exists that
+*        several of the Frames within it might be matched by the
+*        template.  Unless the choice is sufficiently restricted by
+c        the "domainlist" string, the sequence in which Frames are
+f        the DOMAINLIST string, the sequence in which Frames are
+*        searched can then become important. In this case, the search
+*        proceeds as follows:
+c        - Each field in the "domainlist" string is considered in turn.
+f        - Each field in the DOMAINLIST string is considered in turn.
+*        - An attempt is made to match the template to each of the
+*        target's Frames in the order: (1) the current Frame, (2) the
+*        base Frame, (3) each remaining Frame in the order of being
+*        added to the target FrameSet.
+*        - Generally, the first match found is used. However, the
+*        Mapping between the target coordinate system and the
+*        resulting Frame is also examined. Preference is given to
+*        cases where both the forward and inverse transformations are
+*        defined (as indicated by the TranForward and TranInverse
+*        attributes). If only one transformation is defined, the
+*        forward one is preferred.
+*        - If a match is found and the domain of the resulting Frame also
+c        matches the current "domainlist" field, it is
+f        matches the current DOMAINLIST field, it is
+c        accepted. Otherwise, the next "domainlist" field is considered
+f        accepted. Otherwise, the next DOMAINLIST field is considered
+*        and the process repeated.
+*
+*        If a suitable coordinate system is found, the Current
+*        attribute of the target FrameSet will be modified on exit to
+*        identify the Frame whose match with the target was eventually
+*        accepted.
+
+*  Examples:
+c     result = astFindFrame( target, astFrame( 3, "" ), "" );
+f     RESULT = AST_FINDFRAME( TARGET, AST_FRAME( 3, ' ', STATUS ), ' ', STATUS )
+*        Searches for a 3-dimensional coordinate system in the target
+*        Frame (or FrameSet). No attributes have been set in the
+c        template Frame (created by astFrame), so no restriction has
+f        template Frame (created by AST_FRAME), so no restriction has
+*        been placed on the required coordinate system, other than
+*        that it should have 3 dimensions. The first suitable Frame
+c        found will be returned as part of the "result" FrameSet.
+f        found will be returned as part of the RESULT FrameSet.
+c     result = astFindFrame( target, astSkyFrame( "" ), "" );
+f     RESULT = AST_FINDFRAME( TARGET, AST_SKYFRAME( ' ', STATUS ), ' ', STATUS )
+*        Searches for a celestial coordinate system in the target
+*        Frame (or FrameSet). The type of celestial coordinate system
+c        is unspecified, so astFindFrame will return the first one
+f        is unspecified, so AST_FINDFRAME will return the first one
+c        found as part of the "result" FrameSet. If the target is
+f        found as part of the RESULT FrameSet. If the target is
+*        a FrameSet, then its Current attribute will be updated to
+*        identify the Frame that was used.
+*
+*        If no celestial coordinate system can be found, a value of
+*        AST__NULL will be returned without error.
+c     result = astFindFrame( target, astSkyFrame( "MaxAxes=100" ), "" );
+f     RESULT = AST_FINDFRAME( TARGET, AST_SKYFRAME( 'MaxAxes=100', STATUS ), ' ', STATUS )
+*        This is like the last example, except that in the event of the
+*        target being a CmpFrame, the component Frames encapsulated by the
+*        CmpFrame will be searched for a SkyFrame. If found, the returned
+*        Mapping will included a PermMap which selects the required axes
+*        from the target CmpFrame.
+*
+*        This is acomplished by setting the MaxAxes attribute of the
+*        template SkyFrame to a large number (larger than or equal to the
+*        number of axes in the target CmpFrame). This allows the SkyFrame
+*        to be used as a match for Frames containing from 2 to 100 axes.
+c     result = astFindFrame( target, astSkyFrame( "System=FK5" ), "" );
+f     RESULT = AST_FINDFRAME( TARGET, AST_SKYFRAME( 'System=FK5', STATUS ), ' ', STATUS )
+*        Searches for an equatorial (FK5) coordinate system in the
+*        target. The Equinox value for the coordinate system has not
+*        been specified, so will be obtained from the target. If the
+*        target is a FrameSet, its Current attribute will be updated
+*        to indicate which SkyFrame was used to obtain this value.
+c     result = astFindFrame( target, astFrame( 2, "" ), "sky,pixel," );
+f     RESULT = AST_FINDFRAME( TARGET, AST_FRAME( 2, ' ', STATUS ), 'SKY,PIXEL,', STATUS )
+*        Searches for a 2-dimensional coordinate system in the
+*        target. Initially, a search is made for a suitable coordinate
+*        system whose Domain attribute has the value "SKY". If this
+*        search fails, a search is then made for one with the domain
+*        "PIXEL". If this also fails, then any 2-dimensional
+c        coordinate system is returned as part of the "result"
+f        coordinate system is returned as part of the RESULT
+*        FrameSet.
+*
+*        Only if no 2-dimensional coordinate systems can be reached by
+*        applying built-in conversions to any of the Frames in the
+*        target will a value of AST__NULL be returned.
+c     result = astFindFrame( target, astFrame( 1, "Domain=WAVELENGTH" ), "" );
+f     RESULT = AST_FINDFRAME( TARGET, AST_FRAME( 1, 'Domain=WAVELENGTH', STATUS ), ' ', STATUS )
+*        Searches for any 1-dimensional coordinate system in the
+*        target which has the domain "WAVELENGTH".
+c     result = astFindFrame( target, astFrame( 1, "" ), "wavelength" );
+f     RESULT = AST_FINDFRAME( TARGET, AST_FRAME( 1, ' ', STATUS ), 'WAVELENGTH', STATUS )
+*        This example has exactly the same effect as that above. It
+*        illustrates the equivalence of the template's Domain attribute
+c        and the fields in the "domainlist" string.
+f        and the fields in the DOMAINLIST string.
+c     result = astFindFrame( target, astFrame( 1, "MaxAxes=3" ), "" );
+f     RESULT = AST_FINDFRAME( TARGET, AST_FRAME( 1, 'MaxAxes=3', STATUS ), ' ', STATUS )
+*        This is a more advanced example which will search for any
+*        coordinate system in the target having 1, 2 or 3
+c        dimensions. The Frame returned (as part of the "result"
+f        dimensions. The Frame returned (as part of the RESULT
+*        FrameSet) will always be 1-dimensional, but will be related
+*        to the coordinate system that was found by a suitable Mapping
+*        (e.g. a PermMap) which simply extracts the first axis.
+*
+*        If we had wanted a Frame representing the actual (1, 2 or
+*        3-dimensional) coordinate system found, we could set the
+*        PreserveAxes attribute to a non-zero value in the template.
+c     result = astFindFrame( target, astSkyFrame( "Permute=0" ), "" );
+f     RESULT = AST_FINDFRAME( TARGET, AST_SKYFRAME( 'Permute=0', STATUS ), ' ', STATUS )
+*        Searches for any celestial coordinate system in the target,
+*        but only finds one if its axes are in the conventional
+*        (longitude,latitude) order and have not been permuted
+c        (e.g. with astPermAxes).
+f        (e.g. with AST_PERMAXES).
+
+*  Notes:
+*     -  The Mapping represented by the returned FrameSet results in
+*     alignment taking place in the coordinate system specified by the
+c     AlignSystem attribute of the "template" Frame. See the description
+f     AlignSystem attribute of the TEMPLATE Frame. See the description
+*     of the AlignSystem attribute for further details.
+*     - Beware of setting the Domain attribute of the template and then
+c     using a "domainlist" string which does not include the template's domain
+f     using a DOMAINLIST string which does not include the template's domain
+*     (or a blank field). If you do so, no coordinate system will be
+*     found.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  More on Using Templates:
+*     A Frame (describing a coordinate system) will be found by this
+*     function if (a) it is "matched" by the template you supply, and
+c     (b) the value of its Domain attribute appears in the "domainlist"
+f     (b) the value of its Domain attribute appears in the DOMAINLIST
+*     string (except that a blank field in this string permits any
+*     domain). A successful match by the template depends on a number
+*     of criteria, as outlined below:
+*     - In general, a template will only match another Frame which
+*     belongs to the same class as the template, or to a derived (more
+*     specialised) class. For example, a SkyFrame template will match
+*     any other SkyFrame, but will not match a basic
+*     Frame. Conversely, a basic Frame template will match any class
+*     of Frame.
+*     - The exception to this is that a Frame of any class can be used to
+*     match a CmpFrame, if that CmpFrame contains a Frame of the same
+*     class as the template. Note however, the MaxAxes and MinAxes
+*     attributes of the template must be set to suitable values to allow
+*     it to match the CmpFrame. That is, the MinAxes attribute must be
+*     less than or equal to the number of axes in the target, and the MaxAxes
+*     attribute must be greater than or equal to the number of axes in
+*     the target.
+*     - If using a CmpFrame as a template frame, the MinAxes and MaxAxes
+*     for the template are determined by the MinAxes and MaxAxes values of
+*     the component Frames within the template. So if you want a template
+*     CmpFrame to be able to match Frames with different numbers of axes,
+*     then you must set the MaxAxes and/or MinAxes attributes in the component
+*     template Frames, before combining them together into the template
+*     CmpFrame.
+*     - If a template has a value set for any of its main attributes, then
+*     it will only match Frames which have an identical value for that
+*     attribute (or which can be transformed, using a built-in
+*     conversion, so that they have the required value for that
+*     attribute). If any attribute in the template is un-set, however,
+*     then Frames are matched regardless of the value they may have
+*     for that attribute. You may therefore make a template more or
+*     less specific by choosing the attributes for which you set
+*     values. This requirement does not apply to 'descriptive' attributes
+*     such as titles, labels, symbols, etc.
+*     - An important application of this principle involves the Domain
+*     attribute. Setting the Domain attribute of the template has the
+*     effect of restricting the search to a particular type of Frame
+*     (with the domain you specify).  Conversely, if the Domain
+*     attribute is not set in the template, then the domain of the
+*     Frame found is not relevant, so all Frames are searched.  Note
+*     that the
+c     "domainlist" string provides an alternative way of restricting the
+f     DOMAINLIST string provides an alternative way of restricting the
+*     search in the same manner, but is a more convenient interface if
+*     you wish to search automatically for another domain if the first
+*     search fails.
+*     - Normally, a template will only match a Frame which has the
+*     same number of axes as itself. However, for some classes of
+*     template, this default behaviour may be changed by means of the
+*     MinAxes, MaxAxes and MatchEnd attributes. In addition, the
+*     behaviour of a template may be influenced by its Permute and
+*     PreserveAxes attributes, which control whether it matches Frames
+*     whose axes have been permuted, and whether this permutation is
+*     retained in the Frame which is returned (as opposed to returning
+*     the axes in the order specified in the template, which is the
+*     default behaviour). You should consult the descriptions of these
+*     attributes for details of this more advanced use of templates.
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *frame;              /* Pointer to result Frame */
+   AstFrameSet *result;          /* Pointer to result FrameSet */
+   AstMapping *map;              /* Pointer to result Mapping */
+   AstMapping *tmp;              /* Temporary Mapping pointer */
+   char *domain_copy;            /* Pointer to copy of result domain */
+   char *domainlist_copy;        /* Pointer to copy of domains list */
+   const char *domain;           /* Pointer to result Domain value */
+   int *target_axes;             /* Pointer to target axis assignments */
+   int *template_axes;           /* Pointer to template axis assignments */
+   int i;                        /* Loop counter for characters */
+   int j;                        /* Character index */
+   int match;                    /* Template matched target? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Allocate space to store a copy of the domains list, with added
+   commas. */
+   domainlist_copy = astMalloc( strlen( domainlist ) + (size_t) 3 );
+   if ( astOK ) {
+
+/* Make a copy of the domains list, with an extra comma added at each
+   end. Also remove all white space and convert to upper case. */
+      domainlist_copy[ 0 ] = ',';
+      for ( i = 0, j = 1; domainlist[ i ]; i++ ) {
+         if ( !isspace( domainlist[ i ] ) ) {
+            domainlist_copy[ j++ ] = toupper( domainlist[ i ] );
+         }
+      }
+      domainlist_copy[ j++ ] = ',';
+      domainlist_copy[ j ] = '\0';
+
+/* Invoke the protected astMatch method associated with the template
+   Frame. This matches the template to the target and returns
+   information about how to convert between the target and the Frame
+   it found (if any). */
+      match = astMatch( template, target, 0,
+                        &template_axes, &target_axes, &map, &frame );
+
+/* If successful, obtain a pointer to the Domain string of the result
+   Frame. Allocate space for a copy of this string, with added
+   commas. */
+      if ( match && astOK ) {
+         domain = astGetDomain( frame );
+         if ( astOK ) {
+            domain_copy = astMalloc( strlen( domain ) + (size_t) 3 );
+            if ( astOK ) {
+
+/* Make a copy of the domain, adding an extra comma at each end. */
+               domain_copy[ 0 ] = ',';
+               for ( i = 0, j = 1; domain[ i ]; i++ ) {
+                  domain_copy[ j++ ] = domain[ i ];
+               }
+               domain_copy[ j++ ] = ',';
+               domain_copy[ j ] = '\0';
+
+/* Test if the domain appears in the domains list (with added
+   commas). If not, test if a blank domain (which permits the result
+   Frame to have any Domain) appears instead. */
+               if ( strstr( domainlist_copy, domain_copy ) ||
+                    strstr( domainlist_copy, ",," ) ) {
+
+/* If the result Frame is acceptable, simplify the result Mapping. */
+                  tmp = astSimplify( map );
+                  map = astAnnul( map );
+                  map = tmp;
+
+/* Build the result FrameSet. */
+                  result = astFrameSet( target, "", status );
+                  astAddFrame( result, AST__BASE, map, frame );
+               }
+            }
+
+/* Free the copy of the result domain. */
+            domain_copy = astFree( domain_copy );
+         }
+
+/* Free space and annul pointers allocated by astMatch. */
+         template_axes = astFree( template_axes );
+         target_axes = astFree( target_axes );
+         map = astAnnul( map );
+         frame = astAnnul( frame );
+      }
+   }
+
+/* Free the copy of the domains list. */
+   domainlist_copy = astFree( domainlist_copy );
+
+/* If an error occurred, annul any result pointer. */
+   if ( !astOK && result ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+const char *astFmtDecimalYr_( double year, int digits, int *status ) {
+/*
+*+
+*  Name:
+*     astFmtDecimalYr
+
+*  Purpose:
+*     Format an epoch expressed in years as a decimal string.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     const char *astFmtDecimalYr( double year, int digits )
+
+*  Class Membership:
+*     Frame member function.
+
+*  Description:
+*     This function formats an epoch expressed in years as a decimal string
+*     and returns a pointer to the result. It is intended for formatting
+*     Frame Epoch values, etc, for display.
+
+*  Parameters:
+*     year
+*        The epoch to be formatted.
+*     digits
+*        The number of digits of precision required.
+
+*  Returned Value:
+*     Pointer to a null terminated string containing the formatted value.
+
+*  Notes:
+*     - The result string is stored in static memory and may be
+*     over-written by a subsequent invocation of this function.
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   int nc;                       /* Number of characters in buffer */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Limit the precision to what is meaningful. */
+   digits = ( digits > DBL_DIG ) ? DBL_DIG : digits;
+
+/* Format the year value. Use "g" format to avoid buffer overflow and
+   to get useful diagnostic output if a silly value is given. */
+   nc = sprintf( astfmtdecimalyr_buff, "%#.*g", digits, year );
+
+/* Loop to remove redundant zeros from the end of the result. */
+   while ( astfmtdecimalyr_buff[ --nc ] == '0' ) astfmtdecimalyr_buff[ nc ] = '\0';
+
+/* If the last character is now a decimal point, put back one zero. */
+   if ( astfmtdecimalyr_buff[ nc ] == '.' ) {
+      astfmtdecimalyr_buff[ ++nc ] = '0';
+      astfmtdecimalyr_buff[ ++nc ] = '\0';
+   }
+
+/* Return the result. */
+   return astfmtdecimalyr_buff;
+}
+
+static const char *Format( AstFrame *this, int axis, double value, int *status ) {
+/*
+*+
+*  Name:
+*     astFormat
+
+*  Purpose:
+*     Format a coordinate value for a Frame axis.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     const char *astFormat( AstFrame *this, int axis, double value )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns a pointer to a string containing the
+*     formatted (character) version of a coordinate value for a Frame
+*     axis. The formatting applied is determined by the Frame's
+*     attributes and, in particular, by any Format attribute string
+*     that has been set for the axis. A suitable default format will
+*     be applied if necessary.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the Frame axis for which formatting is to be
+*        performed (axis numbering starts at zero for the first axis).
+*     value
+*        The coordinate value to be formatted.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the formatted
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Frame, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Frame. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic astFormat method available
+*     via the protected interface to the Frame class. The public
+*     interface to this method is provided by the astFormatId_
+*     function.
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   const char *result;           /* Pointer value to return */
+   int digits_set;               /* Axis Digits attribute set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Validate the axis index and obtain a pointer to the required Axis. */
+   (void) astValidateAxis( this, axis, 1, "astFormat" );
+   ax = astGetAxis( this, axis );
+
+/* Test if any Axis attributes which may affect the result are undefined (i.e.
+   have not been explicitly set). If so, we over-ride them, giving them
+   temporary values dictated by the Frame. Only the Digits attribute is
+   relevant here. */
+   digits_set = astTestAxisDigits( ax );
+   if ( !digits_set ) astSetAxisDigits( ax, astGetDigits( this ) );
+
+/* Format the value. */
+   result = astAxisFormat( ax, value );
+
+/* Clear any Axis attributes that were temporarily over-ridden. */
+   if ( !digits_set ) astClearAxisDigits( ax );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *FrameGrid( AstFrame *this, int size, const double *lbnd,
+                               const double *ubnd, int *status ){
+/*
+*+
+*  Name:
+*     astFrameGrid
+
+*  Purpose:
+*     Return a grid of points covering a rectangular area of a Frame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstPointSet *astFrameGrid( AstFrame *this_frame, int size,
+*                                const double *lbnd, const double *ubnd )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns a PointSet containing positions spread
+*     approximately evenly throughtout a specified rectangular area of
+*     the Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     size
+*        The preferred number of points in the returned PointSet. The
+*        actual number of points in the returned PointSet may be
+*        different, but an attempt is made to stick reasonably closely to
+*        the supplied value.
+*     lbnd
+*        Pointer to an array holding the lower bound of the rectangular
+*        area on each Frame axis. The array should have one element for
+*        each Frame axis.
+*     ubnd
+*        Pointer to an array holding the upper bound of the rectangular
+*        area on each Frame axis. The array should have one element for
+*        each Frame axis.
+
+*  Returned Value:
+*     A pointer to a new PointSet holding the grid of points.
+
+*  Notes:
+*     - A NULL pointer is returned if an error occurs.
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *result;
+   const char *unit;
+   double **ptr;
+   double *gmean;
+   double *step;
+   int *maxi;
+   int *nsame;
+   int *ntick;
+   int *pi;
+   int bad;
+   int iax;
+   int ipp;
+   int jax;
+   int naxes;
+   int np;
+   int ntick0;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the number of axes in the Frame. */
+   naxes = astGetNaxes( this );
+
+/* Allocate an array to hold the number of ticks along each axis. */
+   ntick = astMalloc( sizeof(int)*naxes );
+
+/* Allocate an array to hold the geometric mean of the lengths of the
+   axes that have the same units as the current axis. */
+   gmean = astMalloc( naxes*sizeof(double) );
+
+/* Allocate an array to hold the number of axes that share the same unit. */
+   nsame = astMalloc( naxes*sizeof(int) );
+   if( astOK ) {
+
+/* For each axis, find the total number of axes in the Frame that have
+   the same unit string. Also, find the product of the lengths of these
+   axes. */
+      bad = 0;
+      for( iax = 0; iax < naxes; iax++ ) {
+         nsame[ iax ] = 1;
+
+         if( ubnd[ iax ] == AST__BAD &&
+             lbnd[ iax ] == AST__BAD ) {
+            bad = 1;
+            break;
+         }
+
+         gmean[ iax ] = ubnd[ iax ] - lbnd[ iax ];
+         unit = astGetUnit( this, iax );
+         for( jax = 0; jax < naxes; jax++ ) {
+            if( jax != iax ) {
+               if( astOK && !strcmp( unit, astGetUnit( this, jax ) ) ) {
+                  nsame[ iax ]++;
+                  gmean[ iax ] *= ubnd[ jax ] - lbnd[ jax ];
+               }
+            }
+         }
+      }
+
+/* Do nothing if any bad bounds were supplied, or if the size is less
+   than 1. */
+      if( !bad && size >= 1 ) {
+
+/* Get the nominal number of ticks per axis. */
+         ntick0 = pow( size, 1.0/(double)naxes );
+         if( ntick0 < 2 ) ntick0 = 2;
+
+/* Convert the dimension products into geometric means. */
+         for( iax = 0; iax < naxes; iax++ ) {
+            gmean[ iax ] = pow( fabs(gmean[ iax ]), 1.0/(double)nsame[ iax ] );
+         }
+
+/* The number of ticks to use on each axis is equal to the nominal number
+   multiplied by the ratio of the axis length to the geometric mean of the
+   axis lengths that sahare the same unit string. This gives more ticks
+   on the longer axes within any group of common-unit axes, whilst
+   retaining the overall number of ticks (roughly). Also find the total
+   number of points. */
+         np = 1;
+         for( iax = 0; iax < naxes; iax++ ) {
+            ntick[ iax ] = ntick0*( ubnd[ iax ] - lbnd[ iax ] )/gmean[ iax ];
+            if( ntick[ iax ] < 2 ) ntick[ iax ] = 2;
+            np *= ntick[ iax ];
+         }
+
+/* Create a PointSet large enough to hold this many points. */
+         result = astPointSet( np, naxes, " ", status );
+         ptr = astGetPoints( result );
+
+/* Allocate memory to hold the max indices on each axis. */
+         maxi = astMalloc( sizeof( int )*(size_t) naxes );
+
+/* Allocate memory to hold the indices of the current position.*/
+         pi = astMalloc( sizeof( int )*(size_t) naxes );
+
+/* Allocate memory to hold the step size for each axis. */
+         step = astMalloc( sizeof( double )*(size_t) naxes );
+         if( astOK ) {
+
+/* For every axis, set up the step size, initialise the current position to
+   the lower bound, and store a modified upper limit which includes some
+   safety marging to allow for rounding errors. */
+            for( iax = 0; iax < naxes; iax++ ) {
+               step[ iax ] = ( ubnd[ iax ] - lbnd[ iax ] )/( ntick[ iax ] - 1 );
+               pi[ iax ] = 0;
+               maxi[ iax ] = ntick[ iax ] - 1;
+            }
+
+/* Initialise the index of the next position to store. */
+            ipp = 0;
+
+/* Loop round adding points to the array until the whole volume has been
+   done. */
+            iax = 0;
+            while( iax < naxes ) {
+
+/* Add the current point to the supplied array, and increment the index of
+   the next point to add. */
+               for( iax = 0; iax < naxes; iax++ ) {
+                  ptr[ iax ][ ipp ] = lbnd[ iax ] + pi[ iax ]*step[ iax ];
+               }
+               ipp++;
+
+/* We now move the current position on to the next sample */
+               iax = 0;
+               while( iax < naxes ) {
+                  pi[ iax ]++;
+                  if( pi[ iax ] > maxi[ iax ] ) {
+                     pi[ iax ] = 0;
+                     iax++;
+                  } else {
+                     break;
+                  }
+               }
+            }
+         }
+
+/* Free resources. */
+         maxi = astFree( maxi );
+         pi = astFree( pi );
+         step = astFree( step );
+
+/* Report error if supplied values were bad. */
+      } else if( astOK ) {
+         if( bad ) {
+            astError( AST__ATTIN, "astFrameGrid(%s): One of more of the "
+                      "supplied bounds is AST__BAD (programming error).",
+                      status, astGetClass( this ) );
+         } else if( size < 1 ) {
+            astError( AST__ATTIN, "astFrameGrid(%s): The supplied grid "
+                      "size (%d) is invalid (programming error).",
+                      status, astGetClass( this ), size );
+         }
+      }
+   }
+
+/* Free resources. */
+   ntick = astFree( ntick );
+   nsame = astFree( nsame );
+   gmean = astFree( gmean );
+
+/* Annul the returned PointSet if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the PointSet holding the grid. */
+   return result;
+}
+
+static double Gap( AstFrame *this, int axis, double gap, int *ntick, int *status ) {
+/*
+*+
+*  Name:
+*     astGap
+
+*  Purpose:
+*     Find a "nice" gap for tabulating Frame axis values.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     double astGap( AstFrame *this, int axis, double gap, int *ntick )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns a gap size which produces a nicely spaced
+*     series of formatted values for a Frame axis, the returned gap
+*     size being as close as possible to the supplied target gap
+*     size. It also returns a convenient number of divisions into
+*     which the gap can be divided.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the axis (zero-based) for which a gap is to be found.
+*     gap
+*        The target gap size.
+*     ntick
+*        Address of an int in which to return a convenient number of
+*        divisions into which the gap can be divided.
+
+*  Returned Value:
+*     The nice gap size.
+
+*  Notes:
+*     - A value of zero is returned if the target gap size is zero.
+*     - A negative gap size is returned if the supplied gap size is negative.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   double result;                /* The nice gap value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Validate the axis index and obtain a pointer to the required
+   Axis. */
+   (void) astValidateAxis( this, axis, 1, "astGap" );
+   ax = astGetAxis( this, axis );
+
+/* Find the gap. */
+   result = astAxisGap( ax, gap, ntick );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetActiveUnit( AstFrame *this, int *status ){
+/*
+*++
+*  Name:
+c     astGetActiveUnit
+f     AST_GETACTIVEUNIT
+
+*  Purpose:
+*     Determines how the Unit attribute will be used.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     int astGetActiveUnit( AstFrame *this )
+f     RESULT = AST_GETACTIVEUNIT( THIS, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns the current value of the ActiveUnit flag for a Frame. See
+c     the description of the astSetActiveUnit function
+f     the description of the AST_SETACTIVEUNIT routine
+*     for a description of the ActiveUnit flag.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetActiveUnit
+f     AST_GETACTIVEUNIT = LOGICAL
+*        The current value of the ActiveUnit flag.
+
+*  Notes:
+c     - A zero value will be returned if this function is
+c     invoked with the AST error status set, or if it should fail for
+f     - A value of .FALSE. will be returned if this function is
+f     invoked with STATUS set to an error value, or if it should fail for
+*     any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstAxis *ax;        /* Pointer to axis structure */
+   int i;              /* Index of axis in Frame */
+   int has_skyaxis;    /* Does Frame contain any SkyAxes? */
+   int nax;            /* Number of axes in Frame */
+   int result;         /* The returned value */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* See if the Frame contains a SkyAxis. */
+   has_skyaxis = 0;
+   nax = astGetNaxes( this );
+   for( i = 0; i < nax; i++ ) {
+      ax = astGetAxis( this, i );
+      if( astIsASkyAxis( ax ) ) has_skyaxis = 1;
+      ax = astAnnul( ax );
+   }
+
+/* If the Frame contains a SkyAxis the ActiveUnit flag is always zero. */
+   if( !has_skyaxis ) {
+
+/* Otherwise, get the value from the Frame. If it has not yet been assigned a
+   value return the value zero. */
+      result = this->active_unit;
+      if( result == -INT_MAX ) result = 0;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a Frame, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - The returned string pointer may point at memory allocated
+*     within the Frame, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Frame. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstAxis *ax;                  /* Pointer to Axis */
+   AstFrame *pfrm;               /* Pointer to primary Frame containing axis */
+   AstFrame *this;               /* Pointer to the Frame structure */
+   AstSystemType system;         /* System code */
+   char pfrm_attrib[ 100 ];      /* Primary Frame attribute */
+   char *axis_attrib;            /* Pointer to axis attribute name */
+   const char *old_attrib;       /* Pointer to supplied attribute name string */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Double attibute value */
+   double epoch;                 /* Epoch attribute value (as MJD) */
+   int axis;                     /* Frame axis number */
+   int axis_nc;                  /* No. characters in axis attribute name */
+   int digits;                   /* Digits attribute value */
+   int direction;                /* Direction attribute value */
+   int free_axis_attrib;         /* Should axis_attrib be freed? */
+   int has_axis;                 /* Does attrib name include axis specifier? */
+   int len;                      /* Length of attrib string */
+   int match_end;                /* MatchEnd attribute value */
+   int max_axes;                 /* MaxAxes attribute value */
+   int min_axes;                 /* MinAxes attribute value */
+   int naxes;                    /* Naxes attribute value */
+   int nc;                       /* No. characters read by astSscanf */
+   int oldrep;                   /* Original error reporting state */
+   int paxis;                    /* Axis index within primary frame */
+   int permute;                  /* Permute attribute value */
+   int preserve_axes;            /* PreserveAxes attribute value */
+   int used;                     /* Could the setting string be used? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the Frame structure. */
+   this = (AstFrame *) this_object;
+
+/* Set a flag indicating if the attribute name includes an axis
+   specifier. */
+   has_axis = ( strchr( attrib, '(' ) != NULL );
+
+/* A flag indicating that we do not need to free the axis_attrib memory. */
+   free_axis_attrib = 0;
+
+/* Initialise things to avoid compiler warnings. */
+   axis_attrib = NULL;
+   old_attrib = NULL;
+
+/* Jump back to here if we are trying the same attribute but with an explicit
+   axis "(1)" added to the end of the name. */
+L1:
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Save the number of axes in the Frame for later use. */
+   naxes = astGetNaxes( this );
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* Digits. */
+/* ------- */
+   if ( !strcmp( attrib, "digits" ) ) {
+      digits = astGetDigits( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", digits );
+         result = getattrib_buff;
+      }
+
+/* Digits(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "digits(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+
+/* There is no function to obtain the Digits attribute value for an
+   axis directly, so obtain a pointer to the Axis and use this to
+   obtain the value. Use the Frame's Digits attribute instead if the
+   Axis attribute value is not set. */
+      (void) astValidateAxis( this, axis - 1, 1, "astGetDigits(axis)" );
+      ax = astGetAxis( this, axis - 1 );
+      if ( astTestAxisDigits( ax ) ) {
+         digits = astGetAxisDigits( ax );
+      } else {
+         digits = astGetDigits( this );
+      }
+      ax = astAnnul( ax );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", digits );
+         result = getattrib_buff;
+      }
+
+
+/* Direction(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "direction(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      direction = astGetDirection( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", direction );
+         result = getattrib_buff;
+      }
+
+/* Epoch. */
+/* ------ */
+   } else if ( !strcmp( attrib, "epoch" ) ) {
+      epoch = astGetEpoch( this );
+      if ( astOK ) {
+
+/* Format the Epoch as decimal years. Use a Besselian epoch if it will
+   be less than 1984.0, otherwise use a Julian epoch. */
+         result = astFmtDecimalYr( ( epoch < palEpj2d( 1984.0 ) ) ?
+                                   palEpb( epoch ) : palEpj( epoch ), DBL_DIG );
+      }
+
+/* Top(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "top(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = astGetTop( this, axis -1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Bottom(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "bottom(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = astGetBottom( this, axis -1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Domain. */
+/* ------- */
+   } else if ( !strcmp( attrib, "domain" ) ) {
+      result = astGetDomain( this );
+
+/* Format(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "format(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astGetFormat( this, axis - 1 );
+
+/* Label(axis). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "label(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astGetLabel( this, axis - 1 );
+
+/* MatchEnd. */
+/* --------- */
+   } else if ( !strcmp( attrib, "matchend" ) ) {
+      match_end = astGetMatchEnd( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", match_end );
+         result = getattrib_buff;
+      }
+
+/* MaxAxes. */
+/* -------- */
+   } else if ( !strcmp( attrib, "maxaxes" ) ) {
+      max_axes = astGetMaxAxes( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", max_axes );
+         result = getattrib_buff;
+      }
+
+/* MinAxes. */
+/* -------- */
+   } else if ( !strcmp( attrib, "minaxes" ) ) {
+      min_axes = astGetMinAxes( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", min_axes );
+         result = getattrib_buff;
+      }
+
+/* Naxes. */
+/* -----_ */
+   } else if ( !strcmp( attrib, "naxes" ) ) {
+      (void) sprintf( getattrib_buff, "%d", naxes );
+      result = getattrib_buff;
+
+/* Permute. */
+/* -------- */
+   } else if ( !strcmp( attrib, "permute" ) ) {
+      permute = astGetPermute( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", permute );
+         result = getattrib_buff;
+      }
+
+/* PreserveAxes. */
+/* ------------- */
+   } else if ( !strcmp( attrib, "preserveaxes" ) ) {
+      preserve_axes = astGetPreserveAxes( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", preserve_axes );
+         result = getattrib_buff;
+      }
+
+/* Symbol(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "symbol(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astGetSymbol( this, axis - 1 );
+
+/* AlignSystem. */
+/* ------------ */
+/* Obtain the AlignSystem code and convert to a string. */
+   } else if ( !strcmp( attrib, "alignsystem" ) ) {
+      system = astGetAlignSystem( this );
+      if ( astOK ) {
+         result = astSystemString( this, system );
+
+/* Report an error if the value was not recognised. */
+         if ( !result ) {
+            astError( AST__SCSIN,
+                     "astGetAttrib(%s): Corrupt %s contains invalid "
+                     "AlignSystem identification code (%d).", status,
+                     astGetClass( this ), astGetClass( this ), (int) system );
+         }
+      }
+
+/* System. */
+/* ------- */
+/* Obtain the System code and convert to a string. */
+   } else if ( !strcmp( attrib, "system" ) ) {
+      system = astGetSystem( this );
+      if ( astOK ) {
+         result = astSystemString( this, system );
+
+/* Report an error if the value was not recognised. */
+         if ( !result ) {
+            astError( AST__SCSIN,
+                     "astGetAttrib(%s): Corrupt %s contains invalid "
+                     "System identification code (%d).", status,
+                     astGetClass( this ), astGetClass( this ), (int) system );
+         }
+      }
+
+/* Title. */
+/* ------ */
+   } else if ( !strcmp( attrib, "title" ) ) {
+      result = astGetTitle( this );
+
+/* Unit(axis). */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "unit(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astGetUnit( this, axis - 1 );
+
+/* NormUnit(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "normunit(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astGetNormUnit( this, axis - 1 );
+
+/* InternalUnit(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "internalunit(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astGetInternalUnit( this, axis - 1 );
+
+/* ObsLat. */
+/* ------- */
+   } else if ( !strcmp( attrib, "obslat" ) ) {
+      dval = astGetObsLat( this );
+      if ( astOK ) {
+
+/* If not already created, create an FK5 J2000 SkyFrame which will be used
+   for formatting and unformatting ObsLon and ObsLat values. */
+         if( !skyframe ) {
+            astBeginPM;
+            skyframe = astSkyFrame("system=FK5,equinox=J2000,format(2)=dms.2", status );
+            astEndPM;
+         }
+
+/* Display absolute value preceded by "N" or "S" as appropriate. */
+         if( dval < 0 ) {
+            (void) sprintf( getattrib_buff, "S%s",  astFormat( skyframe, 1, -dval ) );
+         } else {
+            (void) sprintf( getattrib_buff, "N%s",  astFormat( skyframe, 1, dval ) );
+         }
+         result = getattrib_buff;
+      }
+
+/* ObsLon. */
+/* ------- */
+   } else if ( !strcmp( attrib, "obslon" ) ) {
+      dval = astGetObsLon( this );
+      if ( astOK ) {
+
+/* Put into range +/- PI. */
+         dval = palDrange( dval );
+
+/* If not already created, create an FK5 J2000 SkyFrame which will be used
+   for formatting and unformatting ObsLon and ObsLat values. */
+         if( !skyframe ) {
+            astBeginPM;
+            skyframe = astSkyFrame( "system=FK5,equinox=J2000,format(2)=dms.2", status );
+            astEndPM;
+         }
+
+/* Display absolute value preceded by "E" or "W" as appropriate. */
+         if( dval < 0 ) {
+            (void) sprintf( getattrib_buff, "W%s",  astFormat( skyframe, 1, -dval ) );
+         } else {
+            (void) sprintf( getattrib_buff, "E%s",  astFormat( skyframe, 1, dval ) );
+         }
+         result = getattrib_buff;
+
+      }
+
+/* ObsAlt. */
+/* ------- */
+   } else if ( !strcmp( attrib, "obsalt" ) ) {
+      dval = astGetObsAlt( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Dut1. */
+/* ---- */
+   } else if ( !strcmp( attrib, "dut1" ) ) {
+      dval = astGetDut1( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Other axis attributes. */
+/* ---------------------- */
+/* If the attribute was not identified above, but appears to refer to
+   a Frame axis, then it may refer to an Axis object of a derived type
+   (which has additional attributes not recognised here). */
+   } else if ( !free_axis_attrib && ( nc = 0,
+               ( 1 == astSscanf( attrib, "%*[^()]%n(%d)%n",
+                                      &axis_nc, &axis, &nc ) )
+               && ( nc >= len ) ) ) {
+
+/* Validate the axis index and extract the attribute name. */
+      (void) astValidateAxis( this, axis - 1, 1, "astGet" );
+      axis_attrib = astString( attrib, axis_nc );
+
+/* Obtain a pointer to the Axis object. */
+      ax = astGetAxis( this, axis - 1 );
+      if( astOK ) {
+
+/* Assume that we will be able to use the attribute name. */
+         used = 1;
+
+/* Temporarily switch off error reporting so that if the following attempt
+   to access the axis attribute fails, we can try to interpret the
+   attribute name as an attribute of the primary Frame containing the
+   specified axis. Any errors reported in this context will simply be
+   ignored, in particularly they are not deferred for later delivery. */
+         oldrep = astReporting( 0 );
+
+/* Use the Axis astGetAttrib method to obtain the result. */
+         result = astGetAttrib( ax, axis_attrib );
+
+/* If the above call failed with a status of AST__BADAT, indicating that
+   the attribute name was not recognised, clear the status so that we can
+   try to interpret the attribute name as an attribute of the primary Frame
+   containing the specified axis. */
+         if( astStatus == AST__BADAT ) {
+            astClearStatus;
+
+/* Find the primary Frame containing the specified axis. */
+            astPrimaryFrame( this, axis - 1, &pfrm, &paxis );
+
+/* Only attempt to use the primary Frame if it is not the same as "this"
+   - otherwise we could end up in an infinite loop. */
+            if( pfrm != this ) {
+
+/* astPrimaryFrame returns the original - unpermuted - axis index within
+   the primary Frame. So we need to take into account any axis permutation
+   which has been applied to the primary Frame when forming the attribute name
+   to use below. Find the permuted (external) axis index which corresponds to
+   the internal (unpermuted) axis index "paxis". */
+               paxis = astValidateAxis( pfrm, paxis, 0, "astGet" );
+
+/* Modify the attribute name to refer to the axis numbering of the
+   primary frame. */
+               sprintf( pfrm_attrib, "%s(%d)", axis_attrib, paxis + 1 );
+
+/* Attempt to use the Axis astGetAttrib method to obtain the result. */
+               result = astGetAttrib( pfrm, pfrm_attrib );
+
+/* If this failed, clear the status and indicate that we have not managed to
+   use the attribute name. */
+               if( !astOK ) {
+                  astClearStatus;
+                  used = 0;
+               }
+
+            } else {
+               used =  0;
+            }
+
+/* If not found attempt to get the attribute value from the Axis, omitting
+   the axis index. */
+            if( ! used ) {
+               result = astGetAttrib( pfrm, axis_attrib );
+               if( !astOK ) {
+                  astClearStatus;
+               } else {
+                  used = 1;
+               }
+            }
+
+/* Annul the primary Frame pointer. */
+            pfrm = astAnnul( pfrm );
+         }
+
+/* Re-instate the original error reporting state. */
+         astReporting( oldrep );
+
+/* If we could not use the attribute name, attempt to get the axis
+   attribute again, this time retaining the error report. This is done
+   to ensure the user gets an appropriate error message. */
+         if( !used ) result = astGetAttrib( ax, axis_attrib );
+      }
+
+/* Annul the Axis pointer and free the memory holding the attribute
+   name. */
+      ax = astAnnul( ax );
+      axis_attrib = astFree( axis_attrib );
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is still not recognised, and the Frame has only 1 axis,
+   and the attribute name does not already include an axis specifier, try
+   again after appending "(1)" to the end of the attribute name. */
+   } else if( !has_axis && naxes == 1 ) {
+
+/* Take a copy of the supplied name, allowing 3 extra characters for the
+   axis specifier "(1)". */
+      axis_attrib = astMalloc( len + 4 );
+      if( axis_attrib ) memcpy( axis_attrib, attrib, len );
+
+/* Indicate we should free the axis_attrib memory. */
+      free_axis_attrib = 1;
+
+/* Add in the axis specifier. */
+      strcpy( axis_attrib + len, "(1)" );
+
+/* Use the new attribute name instead of the supplied name. */
+      old_attrib = attrib;
+      attrib = axis_attrib;
+
+/* Indicate the attribute name now has an axis specifier. */
+      has_axis = 1;
+
+/* Jump back to try interpreting the new attribute name. */
+      goto L1;
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute name is still not recognised, pass it on to the parent
+   method for further interpretation. First re-instate the original attrib
+   name string if it was changed above. */
+   } else {
+      if( free_axis_attrib ) {
+         attrib = old_attrib;
+         axis_attrib = astFree( axis_attrib );
+      }
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstAxis *GetAxis( AstFrame *this, int axis, int *status ) {
+/*
+*+
+*  Name:
+*     astGetAxis
+
+*  Purpose:
+*     Obtain a pointer to a specified Axis from a Frame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstAxis *astGetAxis( AstFrame *this, int axis )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns a pointer to the Axis object associated
+*     with one of the axes of a Frame. This object describes the
+*     quantity which is represented along that axis.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the axis (zero-based) for which an Axis pointer is
+*        required.
+
+*  Returned Value:
+*     A pointer to the requested Axis object.
+
+*  Notes:
+*     - The reference count of the requested Axis object will be
+*     incremented by one to reflect the additional pointer returned by
+*     this function.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstAxis *result;              /* Pointer to Axis */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise. */
+   result = NULL;
+
+/* Validate and permute the axis index. */
+   axis = astValidateAxis( this, axis, 1, "astGetAxis" );
+
+/* If OK, clone the required Axis pointer. */
+   if ( astOK ) result = astClone( this->axis[ axis ] );
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetDefaultLabel( int axis, int *status ) {
+/*
+*  Name:
+*     GetDefaultLabel
+
+*  Purpose:
+*     Return a pointer to a default axis Label string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     const char *GetDefaultLabel( int axis, int *status )
+
+*  Class Membership:
+*     Frame member function
+
+*  Description:
+*     This function returns a pointer to a string holding a default axis
+*     Label value.
+
+*  Parameters:
+*     axis
+*        Zero based axis index.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a static null-terminated string containing the attribute
+*     value.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Format the axis index, putting the string in a global buffer. */
+   (void) sprintf( label_buff, "Axis %d", axis + 1 );
+
+/* Return a pointer to the global buffer. */
+   return label_buff;
+}
+
+static const char *GetDefaultSymbol( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetDefaultSymbol
+
+*  Purpose:
+*     Return a pointer to a default axis Symbol string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     const char *GetDefaultSymbol( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     Frame member function
+
+*  Description:
+*     This function returns a pointer to a string holding a default axis
+*     Symbol value.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        Zero based axis index.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a static null-terminated string containing the attribute
+*     value.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Note we use "sprintf" once to determine how many characters are
+   produced by the "%d" format string and then limit the number of
+   characters used from the Domain string in the second invocation of
+   "sprintf" so that the total length of the default Symbol string
+   does not exceed SYMBOL_BUFF_LEN characters. */
+   (void) sprintf( symbol_buff, "%.*s%d",
+                   SYMBOL_BUFF_LEN - sprintf( symbol_buff, "%d", axis + 1 ),
+                   astTestDomain( this ) ? astGetDomain( this ) : "x",
+                   axis + 1 );
+
+/* Use the AddUnderscores function to replace any white space in the Symbol
+   string with underscore characters. */
+   AddUnderscores( symbol_buff, status );
+
+/* Return a pointer to the global buffer. */
+   return symbol_buff;
+}
+
+static const char *GetDefaultTitle( AstFrame *this, int *status ) {
+/*
+*  Name:
+*     GetDefaultTitle
+
+*  Purpose:
+*     Return a pointer to a default Title string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     const char *GetDefaultTitle( AstFrame *this, int *status )
+
+*  Class Membership:
+*     Frame member function
+
+*  Description:
+*     This function returns a pointer to a string holding a default Title value.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a static null-terminated string containing the attribute
+*     value.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Create the Title value and put it in the global buffer. */
+   (void) sprintf( title_buff, "%d-d coordinate system", astGetNaxes( this ) );
+
+/* Return a pointer to the global buffer. */
+   return title_buff;
+}
+
+static int GetFrameFlags( AstFrame *this, int *status ){
+/*
+*+
+*  Name:
+*     astGetFrameFlags
+
+*  Purpose:
+*     Return the bit mask of flags associated with a Frame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int *astGetFrameFlags( astFrame *this )
+
+*  Class Membership:
+*     Frame virtual function.
+
+*  Description:
+*     This function returns a bit mask holding the current set of flags
+*     associated with a Frame. See astSetFrameFlags for details of these
+*     flags.
+
+*  Parameters:
+*     this
+*        The Frame.
+
+*  Returned Value:
+*     The bit mask.
+
+*  Notes:
+*     - Zero is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Return the result. */
+   return this->flags;
+}
+
+static int GetIsLinear( AstMapping *this_mapping, int *status ){
+/*
+*  Name:
+*     GetIsLinear
+
+*  Purpose:
+*     Return the value of the IsLinear attribute for a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GetIsLinear( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the protected astGetIsLinear
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns the value of the IsLinear attribute for a
+*     Frame, which is always one because a Frame is treated like a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   return 1;
+}
+
+static int GetIsSimple( AstMapping *this_mapping, int *status ){
+/*
+*  Name:
+*     GetIsSimple
+
+*  Purpose:
+*     Return the value of the IsSimple attribute for a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GetIsSimple( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the protected astGetIsSimple
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns the value of the IsSimple attribute for a
+*     Frame, which is always zero because Frames are not immutable (unlike
+*     non-Frame Mappings).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   return 0;
+}
+
+static int GetNaxes( AstFrame *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNaxes
+
+*  Purpose:
+*     Determine how many axes a Frame has.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astGetNaxes( AstFrame *this )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns the number of axes for a Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+
+*  Returned Value:
+*     The number of Frame axes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Return the number of Frame axes. */
+   return this->naxes;
+}
+
+static int GetNin( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     GetNin
+
+*  Purpose:
+*     Get the number of input coordinates for a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int GetNin( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the astGetNin method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function returns the number of input coordinate values
+*     required per point by a Frame, when used as a Mapping (i.e. the
+*     number of dimensions of the space in which input points reside).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Number of coordinate values required.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *this;               /* Pointer to Frame structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Frame structure. */
+   this = (AstFrame *) this_mapping;
+
+/* Return the number of Frame axes. */
+   result = astGetNaxes( this );
+
+/* Return the result. */
+   return result;
+}
+
+static int GetNout( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     GetNout
+
+*  Purpose:
+*     Get the number of output coordinates for a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int GetNout( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the astGetNout method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function returns the number of output coordinate values
+*     generated per point by a Frame, when used as a Mapping (i.e. the
+*     number of dimensions of the space in which output points
+*     reside).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Number of coordinate values generated.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *this;               /* Pointer to Frame structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Frame structure. */
+   this = (AstFrame *) this_mapping;
+
+/* Return the number of Frame axes. */
+   result = astGetNaxes( this );
+
+/* Return the result. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied Frame,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *this;            /* Pointer to Frame structure */
+   int axis;                  /* Axis index */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the FrameSet structure. */
+   this = (AstFrame *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by this class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astGetObjSize( this->variants );
+   result += astTSizeOf( this->domain );
+   result += astTSizeOf( this->title );
+   result += astTSizeOf( this->axis );
+   result += astTSizeOf( this->perm );
+
+   for ( axis = 0; axis < this->naxes; axis++ ) {
+      result += astGetObjSize( this->axis[ axis ] );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const int *GetPerm( AstFrame *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetPerm
+
+*  Purpose:
+*     Access the axis permutation array for a Frame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     const int *astGetPerm( AstFrame *this )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns a pointer to the axis permutation array
+*     for a Frame. This array constitutes a lookup-table that converts
+*     between an axis number supplied externally and the corresponding
+*     index in the Frame's internal axis arrays.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+
+*  Returned Value:
+*     Pointer to the Frame's axis permutation array (a constant array
+*     of int).  Each element of this contains the (zero-based)
+*     internal axis index to be used in place of the external index
+*     which is used to address the permutation array. If the Frame has
+*     zero axes, this pointer will be NULL.
+
+*  Notes:
+*     - This protected method is provided to assist class
+*     implementations which need to implement axis-dependent
+*     extensions to Frame methods, and which therefore need to know
+*     how a Frames's external axis index is converted for internal
+*     use.
+*     - The pointer returned by this function gives direct access to
+*     data internal to the Frame object. It remains valid only so long
+*     as the Frame exists. The permutation array contents may be
+*     modified by other functions which operate on the Frame and this
+*     may render the returned pointer invalid.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Return a pointer to the axis permutation array. */
+   return this->perm;
+}
+
+static AstFrameSet *GetFrameVariants( AstFrame *this, int *status ){
+/*
+*+
+*  Name:
+*     astGetFrameVariants
+
+*  Purpose:
+*     Returns the FrameSet holding the available Frame variants.
+
+*  Type:
+*     Protected  virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstFrameSet *astGetFrameVariants( AstFrame *this )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns a pointer to any FrameSet previously stored
+*     in the Frame using method astSetVariants.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+
+*  Returned Value:
+*     astGetFrameVariants
+*        A pointer to the FrameSet. It should be annulled using astAnnul
+*        when no longer needed. NULL will be returned if no FrameSet is
+*        stored in the Frame.
+
+*  Notes:
+*     - A NULL value will be returned if this function is invoked with the
+*     AST error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrameSet *result;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a clone of any FrameSet pointer. */
+   if( this->variants ) result = astClone( this->variants );
+
+/* Return the result. */
+   return result;
+}
+
+void astInitFrameVtab_(  AstFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitFrameVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Frame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astInitFrameVtab( AstFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Frame vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Frame class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAFrame ) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->Abbrev = Abbrev;
+   vtab->CheckPerm = CheckPerm;
+   vtab->ClearDigits = ClearDigits;
+   vtab->ClearDirection = ClearDirection;
+   vtab->ClearDomain = ClearDomain;
+   vtab->ClearFormat = ClearFormat;
+   vtab->ClearLabel = ClearLabel;
+   vtab->ClearMatchEnd = ClearMatchEnd;
+   vtab->ClearMaxAxes = ClearMaxAxes;
+   vtab->ClearMinAxes = ClearMinAxes;
+   vtab->ClearPermute = ClearPermute;
+   vtab->ClearPreserveAxes = ClearPreserveAxes;
+   vtab->ClearSymbol = ClearSymbol;
+   vtab->ClearTitle = ClearTitle;
+   vtab->ClearUnit = ClearUnit;
+   vtab->Convert = Convert;
+   vtab->ConvertX = ConvertX;
+   vtab->Angle = Angle;
+   vtab->Distance = Distance;
+   vtab->Fields = Fields;
+   vtab->FindFrame = FindFrame;
+   vtab->MatchAxes = MatchAxes;
+   vtab->MatchAxesX = MatchAxesX;
+   vtab->Format = Format;
+   vtab->Centre = Centre;
+   vtab->Gap = Gap;
+   vtab->GetAxis = GetAxis;
+   vtab->GetDigits = GetDigits;
+   vtab->GetDirection = GetDirection;
+   vtab->GetDomain = GetDomain;
+   vtab->GetFormat = GetFormat;
+   vtab->GetLabel = GetLabel;
+   vtab->GetMatchEnd = GetMatchEnd;
+   vtab->GetMaxAxes = GetMaxAxes;
+   vtab->GetMinAxes = GetMinAxes;
+   vtab->GetNaxes = GetNaxes;
+   vtab->GetPerm = GetPerm;
+   vtab->GetPermute = GetPermute;
+   vtab->GetPreserveAxes = GetPreserveAxes;
+   vtab->GetSymbol = GetSymbol;
+   vtab->GetTitle = GetTitle;
+   vtab->GetUnit = GetUnit;
+   vtab->GetInternalUnit = GetInternalUnit;
+   vtab->GetNormUnit = GetNormUnit;
+   vtab->Intersect = Intersect;
+   vtab->IsUnitFrame = IsUnitFrame;
+   vtab->Match = Match;
+   vtab->Norm = Norm;
+   vtab->NormBox = NormBox;
+   vtab->AxDistance = AxDistance;
+   vtab->AxNorm = AxNorm;
+   vtab->AxOffset = AxOffset;
+   vtab->AxIn = AxIn;
+   vtab->AxAngle = AxAngle;
+   vtab->FrameGrid = FrameGrid;
+   vtab->Offset = Offset;
+   vtab->Offset2 = Offset2;
+   vtab->Resolve = Resolve;
+   vtab->ResolvePoints = ResolvePoints;
+   vtab->LineDef = LineDef;
+   vtab->LineContains = LineContains;
+   vtab->LineCrossing = LineCrossing;
+   vtab->LineOffset = LineOffset;
+   vtab->Overlay = Overlay;
+   vtab->PermAxes = PermAxes;
+   vtab->PickAxes = PickAxes;
+   vtab->PrimaryFrame = PrimaryFrame;
+   vtab->SetAxis = SetAxis;
+   vtab->SetDigits = SetDigits;
+   vtab->SetDirection = SetDirection;
+   vtab->SetDomain = SetDomain;
+   vtab->SetFormat = SetFormat;
+   vtab->SetLabel = SetLabel;
+   vtab->SetMatchEnd = SetMatchEnd;
+   vtab->SetMaxAxes = SetMaxAxes;
+   vtab->SetMinAxes = SetMinAxes;
+   vtab->SetPermute = SetPermute;
+   vtab->SetPreserveAxes = SetPreserveAxes;
+   vtab->SetSymbol = SetSymbol;
+   vtab->SetTitle = SetTitle;
+   vtab->SetUnit = SetUnit;
+   vtab->SubFrame = SubFrame;
+   vtab->TestDigits = TestDigits;
+   vtab->TestDirection = TestDirection;
+   vtab->TestDomain = TestDomain;
+   vtab->TestFormat = TestFormat;
+   vtab->TestLabel = TestLabel;
+   vtab->TestMatchEnd = TestMatchEnd;
+   vtab->TestMaxAxes = TestMaxAxes;
+   vtab->TestMinAxes = TestMinAxes;
+   vtab->TestPermute = TestPermute;
+   vtab->TestPreserveAxes = TestPreserveAxes;
+   vtab->TestSymbol = TestSymbol;
+   vtab->TestTitle = TestTitle;
+   vtab->TestUnit = TestUnit;
+   vtab->Unformat = Unformat;
+   vtab->ValidateAxis = ValidateAxis;
+   vtab->ValidateAxisSelection = ValidateAxisSelection;
+   vtab->ValidateSystem = ValidateSystem;
+   vtab->SystemString = SystemString;
+   vtab->SystemCode = SystemCode;
+
+   vtab->GetFrameFlags = GetFrameFlags;
+   vtab->SetFrameFlags = SetFrameFlags;
+
+   vtab->TestActiveUnit = TestActiveUnit;
+   vtab->GetActiveUnit = GetActiveUnit;
+   vtab->SetActiveUnit = SetActiveUnit;
+
+   vtab->GetFrameVariants = GetFrameVariants;
+   vtab->SetFrameVariants = SetFrameVariants;
+
+   vtab->ClearSystem = ClearSystem;
+   vtab->GetSystem = GetSystem;
+   vtab->SetSystem = SetSystem;
+   vtab->TestSystem = TestSystem;
+
+   vtab->ClearAlignSystem = ClearAlignSystem;
+   vtab->GetAlignSystem = GetAlignSystem;
+   vtab->SetAlignSystem = SetAlignSystem;
+   vtab->TestAlignSystem = TestAlignSystem;
+
+   vtab->ClearTop = ClearTop;
+   vtab->GetTop = GetTop;
+   vtab->SetTop = SetTop;
+   vtab->TestTop = TestTop;
+
+   vtab->ClearBottom = ClearBottom;
+   vtab->GetBottom = GetBottom;
+   vtab->SetBottom = SetBottom;
+   vtab->TestBottom = TestBottom;
+
+   vtab->ClearEpoch = ClearEpoch;
+   vtab->GetEpoch = GetEpoch;
+   vtab->SetEpoch = SetEpoch;
+   vtab->TestEpoch = TestEpoch;
+
+   vtab->ClearObsLat = ClearObsLat;
+   vtab->TestObsLat = TestObsLat;
+   vtab->GetObsLat = GetObsLat;
+   vtab->SetObsLat = SetObsLat;
+
+   vtab->ClearObsLon = ClearObsLon;
+   vtab->TestObsLon = TestObsLon;
+   vtab->GetObsLon = GetObsLon;
+   vtab->SetObsLon = SetObsLon;
+
+   vtab->ClearObsAlt = ClearObsAlt;
+   vtab->TestObsAlt = TestObsAlt;
+   vtab->GetObsAlt = GetObsAlt;
+   vtab->SetObsAlt = SetObsAlt;
+
+   vtab->ClearDut1 = ClearDut1;
+   vtab->GetDut1 = GetDut1;
+   vtab->SetDut1 = SetDut1;
+   vtab->TestDut1 = TestDut1;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_cleanattribs = object->CleanAttribs;
+   object->CleanAttribs = CleanAttribs;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   mapping = (AstMappingVtab *) vtab;
+
+   object->Equal = Equal;
+   mapping->GetIsLinear = GetIsLinear;
+   mapping->GetIsSimple = GetIsSimple;
+   mapping->GetNin = GetNin;
+   mapping->GetNout = GetNout;
+   mapping->ReportPoints = ReportPoints;
+   mapping->Transform = Transform;
+   mapping->MapSplit = MapSplit;
+   mapping->DoNotSimplify = DoNotSimplify;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "Frame", "Coordinate system description" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void Intersect( AstFrame *this, const double a1[2],
+                       const double a2[2], const double b1[2],
+                       const double b2[2], double cross[2],
+                       int *status ) {
+/*
+*++
+*  Name:
+c     astIntersect
+f     AST_INTERSECT
+
+*  Purpose:
+*     Find the point of intersection between two geodesic curves.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     void astIntersect( AstFrame *this, const double a1[2],
+c                        const double a2[2], const double b1[2],
+c                        const double b2[2], double cross[2] )
+f     CALL AST_INTERSECT( THIS, A1, A2, B1, B2, CROSS, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function
+f     This routine
+*     finds the coordinate values at the point of intersection between
+*     two geodesic curves. Each curve is specified by two points on
+*     the curve.  It can only be used with 2-dimensional Frames.
+*
+*     For example, in a basic Frame, it will find the point of
+*     intersection between two straight lines. But for a SkyFrame it
+*     will find an intersection of two great circles.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     a1
+f     A1( 2 ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). This should contain the coordinates of the
+*        first point on the first geodesic curve.
+c     a2
+f     A2( 2 ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). This should contain the coordinates of a
+*        second point on the first geodesic curve. It should not be
+*        co-incident with the first point.
+c     b1
+f     B1( 2 ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). This should contain the coordinates of the
+*        first point on the second geodesic curve.
+c     b2
+f     B2( 2 ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). This should contain the coordinates of a
+*        second point on the second geodesic curve. It should not be
+*        co-incident with the first point.
+c     cross
+f     CROSS( 2 ) = DOUBLE PRECISION (Returned)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        in which the coordinates of the required intersection will
+*        be returned.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - For SkyFrames each curve will be a great circle, and in general
+*     each pair of curves will intersect at two diametrically opposite
+*     points on the sky. The returned position is the one which is
+*     closest to point
+c     "a1".
+f     A1.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value, or if the two
+*     points defining either geodesic are co-incident, or if the two
+*     curves do not intersect.
+c     - The geodesic curve used by this function is the path of
+f     - The geodesic curve used by this routine is the path of
+*     shortest distance between two points, as defined by the
+c     astDistance function.
+f     AST_DISTANCE function.
+*     - An error will be reported if the Frame is not 2-dimensional.
+*--
+*/
+
+/* Local Variables: */
+   double ca;                    /* Y axis intercept of line a */
+   double cb;                    /* Y axis intercept of line b */
+   double dxa;                   /* X range spanned by line a */
+   double dxb;                   /* X range spanned by line b */
+   double ma;                    /* Gradient of line a */
+   double mb;                    /* Gradient of line b */
+   int naxes;                    /* Number of Frame axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialize bad values. */
+   cross[ 0 ] = AST__BAD;
+   cross[ 1 ] = AST__BAD;
+
+/* Determine the number of Frame axes. */
+   naxes = astGetNaxes( this );
+
+/* Report an error if the Frame is not 2 dimensional. */
+   if( naxes != 2 && astOK ) {
+      astError( AST__NAXIN, "astIntersect(%s): Invalid number of Frame axes (%d)."
+                " astIntersect can only be used with 2 dimensonal Frames.", status,
+                astGetClass( this ), naxes );
+   }
+
+/* Check that all supplied values are OK. */
+   if ( ( a1[ 0 ] != AST__BAD ) && ( a1[ 1 ] != AST__BAD ) &&
+        ( a2[ 0 ] != AST__BAD ) && ( a2[ 1 ] != AST__BAD ) &&
+        ( b1[ 0 ] != AST__BAD ) && ( b1[ 1 ] != AST__BAD ) &&
+        ( b2[ 0 ] != AST__BAD ) && ( b2[ 1 ] != AST__BAD ) ) {
+
+/* Find the x increments spanned by the two lines. */
+
+/* Check the first line is not vertical. */
+      dxa = a2[ 0 ] - a1[ 0 ];
+      dxb = b2[ 0 ] - b1[ 0 ];
+      if( dxa != 0.0 ) {
+
+/* Find the gradient and Y axis intercept of the first line. */
+         ma = ( a2[ 1 ] - a1[ 1 ] )/dxa;
+         ca = a1[ 1 ] - a1[ 0 ]*ma;
+
+/* Check the second line is not vertical. */
+         if( dxb != 0.0 ) {
+
+/* Find the gradient and Y axis intercept of the second line. */
+            mb = ( b2[ 1 ] - b1[ 1 ] )/dxb;
+            cb = b1[ 1 ] - b1[ 0 ]*mb;
+
+/* Check the lines are not parallel. */
+            if( ma != mb ) {
+
+/* Find the intersection of the two lines. */
+               cross[ 0 ] = ( cb -ca )/( ma - mb );
+               cross[ 1 ] = ( ( ma + mb )*cross[ 0 ] + ca + cb )/2;
+            }
+
+/* If the second line is vertical but the first is not. */
+         } else if( b1[ 1 ] != b2[ 1 ] ){
+            cross[ 0 ] = b1[ 0 ];
+            cross[ 1 ] = ma*b1[ 0 ] + ca;
+         }
+
+/* First line is vertical but second is not. */
+      } else if( dxb != 0.0 && a1[ 1 ] != a2[ 1 ] ){
+
+/* Find the gradient and Y axis intercept of the second line. */
+         mb = ( b2[ 1 ] - b1[ 1 ] )/dxb;
+         cb = b1[ 1 ] - b1[ 0 ]*mb;
+
+/* Find the intercection. */
+         cross[ 0 ] = a1[ 0 ];
+         cross[ 1 ] = mb*a1[ 0 ] + cb;
+      }
+   }
+}
+
+static int IsUnitFrame( AstFrame *this, int *status ){
+/*
+*+
+*  Name:
+*     astIsUnitFrame
+
+*  Purpose:
+*     Is this Frame equivalent to a UnitMap?
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astIsUnitFrame( AstFrame *this )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns a flag indicating if the supplied Frame is
+*     equivalent to a UnitMap when treated as a Mapping (note, the Frame
+*     class inherits from Mapping and therefore every Frame is also a Mapping).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+
+*  Returned Value:
+*     A non-zero value is returned if the supplied Frame is equivalent to
+*     a UnitMap when treated as a Mapping.
+
+*-
+*/
+
+/* Check the local error status. */
+   if( !astOK ) return 0;
+
+/* The base Frame class is always equivalent to a UnitMap. */
+   return 1;
+}
+
+static int LineContains( AstFrame *this, AstLineDef *l, int def, double *point, int *status ) {
+/*
+*+
+*  Name:
+*     astLineContains
+
+*  Purpose:
+*     Determine if a line contains a point.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astLineContains( AstFrame *this, AstLineDef *l, int def, double *point )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function determines if the supplied point is on the supplied
+*     line within the supplied Frame. The start point of the line is
+*     considered to be within the line, but the end point is not. The tests
+*     are that the point of closest approach of the line to the point should
+*     be between the start and end, and that the distance from the point to
+*     the point of closest aproach should be less than 1.0E-7 of the length
+*     of the line.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     l
+*        Pointer to the structure defining the line.
+*     def
+*        Should be set non-zero if the "point" array was created by a
+*        call to astLineCrossing (in which case it may contain extra
+*        information following the axis values),and zero otherwise.
+*     point
+*        Point to an array containing the axis values of the point to be
+*        tested, possibly followed by extra cached information (see "def").
+
+*  Returned Value:
+*     A non-zero value is returned if the line contains the point.
+
+*  Notes:
+*     - The pointer supplied for "l" should have been created using the
+*     astLineDef method. These structures contained cached information about
+*     the lines which improve the efficiency of this method when many
+*     repeated calls are made. An error will be reported if the structure
+*     does not refer to the Frame specified by "this".
+*     - Zero will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   int result;
+   double dx, dy, p;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check that the line refers to the supplied Frame. */
+   if( l->frame != this ) {
+      astError( AST__INTER, "astLineContains(%s): The supplied line does "
+                "not relate to the supplied %s (AST internal programming "
+                "error).", status, astGetClass( this ), astGetClass( this ) );
+
+/* If the point is good, find the offsets from the start of the line. */
+   } else if( point[ 0 ] != AST__BAD && point[ 1 ] != AST__BAD ) {
+      dx = point[ 0 ] - l->start[ 0 ];
+      dy = point[ 1 ] - l->start[ 1 ];
+
+/* Check the nearest point on the line is between the start and end.
+   Exclude the end point. */
+      p = dx*l->dir[ 0 ] + dy*l->dir[ 1 ];
+      if( p >= 0.0 && p < l->length ) {
+
+/* Check the distance from the point to the nearest point on the line is not
+   further than 1.0E-7 of the length of the line. */
+         if( fabs( dx*l->q[ 0 ] + dy*l->q[ 1 ] ) <= 1.0E-7*l->length ) {
+            result = 1;
+         }
+      }
+   }
+
+/* Return zero if an error occurred. */
+   if( !astOK ) result = 0;
+
+/* Return a pointer to the output structure. */
+   return result;
+}
+
+static int LineCrossing( AstFrame *this, AstLineDef *l1, AstLineDef *l2,
+                         double **cross, int *status ) {
+/*
+*+
+*  Name:
+*     astLineCrossing
+
+*  Purpose:
+*     Determine if two lines cross.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astLineCrossing( AstFrame *this, AstLineDef *l1, AstLineDef *l2,
+*                          double **cross )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function determines if the two suplied line segments cross,
+*     and if so returns the axis values at the point where they cross.
+*     A flag is also returned indicating if the crossing point occurs
+*     within the length of both line segments, or outside one or both of
+*     the line segments.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     l1
+*        Pointer to the structure defining the first line.
+*     l2
+*        Pointer to the structure defining the second line.
+*     cross
+*        Pointer to a location at which to put a pointer to a dynamically
+*        alocated array containing the axis values at the crossing. If
+*        NULL is supplied no such array is returned. Otherwise, the returned
+*        array should be freed using astFree when no longer needed. If the
+*        lines are parallel (i.e. do not cross) then AST__BAD is returned for
+*        all axis values. Note usable axis values are returned even if the
+*        lines cross outside the segment defined by the start and end points
+*        of the lines. The order of axes in the returned array will take
+*        account of the current axis permutation array if appropriate. Note,
+*        sub-classes such as SkyFrame may append extra values to the end
+*        of the basic frame axis values. A NULL pointer is returned if an
+*        error occurs.
+
+*  Returned Value:
+*     A non-zero value is returned if the lines cross at a point which is
+*     within the [start,end) segment of the lines that are flagged as
+*     finite (if a line is marked as infinite any crossing is assumed to
+*     be within the bounds of the line). If the crossing point is outside
+*     this segment on either (inifinte) line, or if the lines are parallel,
+*     zero is returned. Note, the start point is considered to be inside
+*     the length of the segment, but the end point is outside.
+
+*  Notes:
+*     - The pointers supplied for "l1" and "l2" should have been created
+*     using the astLineDef method. These structures contained cached
+*     information about the lines which improve the efficiency of this method
+*     when many repeated calls are made. An error will be reported if
+*     either structure does not refer to the Frame specified by "this".
+*     - Zero will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   double *crossing;          /* Returned array */
+   double den;                /* Denominator */
+   double dx;                 /* Offset in start X values */
+   double dy;                 /* Offset in start Y values */
+   double t1;                 /* Distance from start of line 1 to crossing */
+   double t2;                 /* Distance from start of line 2 to crossing */
+   int result;                /* Returned value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise. */
+   result = 0;
+   crossing = astMalloc( sizeof(double)*2 );
+
+/* Check that both lines refer to the supplied Frame. */
+   if( l1->frame != this ) {
+      astError( AST__INTER, "astLineCrossing(%s): First supplied line does "
+                "not relate to the supplied %s (AST internal programming "
+                "error).", status, astGetClass( this ), astGetClass( this ) );
+
+   } else if( l2->frame != this ) {
+      astError( AST__INTER, "astLineCrossing(%s): Second supplied line does "
+                "not relate to the supplied %s (AST internal programming "
+                "error).", status, astGetClass( this ), astGetClass( this ) );
+
+   } else if( crossing ){
+
+/* Each of the lines can be represented as "p = start + t.v" where start is
+   the start position, v is the unit vector pointing from start to end,
+   and t is the scalar distance from the start position. So to find the
+   intersection put "start1 + t1.v1 = start2 + t2.v2" and solve for t1
+   and t2. */
+      den = (l1->dir[ 0 ])*(l2->dir[ 1 ]) - (l2->dir[ 0 ])*(l1->dir[ 1 ]);
+      if( den != 0.0 ) {
+         dx = l2->start[ 0 ] - l1->start[ 0 ];
+         dy = l2->start[ 1 ] - l1->start[ 1 ];
+         t1 = ( l2->dir[ 1 ]*dx - l2->dir[ 0 ]*dy )/den;
+         t2 = ( l1->dir[ 1 ]*dx - l1->dir[ 0 ]*dy )/den;
+
+/* Store the crossing point, using the smaller t value to redue error. */
+         if( fabs( t1 ) < fabs( t2 ) ) {
+            crossing[ 0 ] = l1->start[ 0 ] + t1*l1->dir[ 0 ];
+            crossing[ 1 ] = l1->start[ 1 ] + t1*l1->dir[ 1 ];
+         } else {
+            crossing[ 0 ] = l2->start[ 0 ] + t2*l2->dir[ 0 ];
+            crossing[ 1 ] = l2->start[ 1 ] + t2*l2->dir[ 1 ];
+         }
+
+/* See if the intersection is within the length of both lines (excluding
+   the end points). If a line is flagged as infinite, set the "t" parameter
+   to zero to make it look like the crossing is within the line. */
+         if( l1->infinite ) t1 = 0.0;
+         if( l2->infinite ) t2 = 0.0;
+
+         if( t1 >= 0.0 && t1 < l1->length &&
+             t2 >= 0.0 && t2 < l2->length ) result = 1;
+
+      } else {
+         crossing[ 0 ] = AST__BAD;
+         crossing[ 1 ] = AST__BAD;
+      }
+   }
+
+/* Return zero if an error occurred. */
+   if( !astOK ) {
+      crossing = astFree( crossing );
+      result = 0;
+   }
+
+/* Return the crossing pointer. */
+   if( cross ) {
+      *cross = crossing;
+   } else if( crossing ){
+      crossing = astFree( crossing );
+   }
+
+/* Return a pointer to the output structure. */
+   return result;
+}
+
+static AstLineDef *LineDef( AstFrame *this, const double start[2],
+                            const double end[2], int *status ) {
+/*
+*+
+*  Name:
+*     astLineDef
+
+*  Purpose:
+*     Creates a structure describing a line segment in a 2D Frame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstLineDef *astLineDef( AstFrame *this, const double start[2],
+*                             const double end[2] )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function creates a structure containing information describing a
+*     given line segment within the supplied 2D Frame. This may include
+*     information which allows other methods such as astLineCrossing to
+*     function more efficiently. Thus the returned structure acts as a
+*     cache to store intermediate values used by these other methods.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame. Must have 2 axes.
+*     start
+*        An array of 2 doubles marking the start of the line segment.
+*     end
+*        An array of 2 doubles marking the end of the line segment.
+
+*  Returned Value:
+*     Pointer to the memory structure containing the description of the
+*     line. This structure should be freed using astFree when no longer
+*     needed. A NULL pointer is returned (without error) if any of the
+*     supplied axis values are AST__BAD.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstLineDef *result;           /* Pointer to output structure */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check the Frame has 2 axes. */
+   if( astGetNaxes( this ) != 2 ) {
+      astError( AST__INTER, "astLineDef(%s): The supplied %s is not 2 "
+                "dimensional (internal AST proramming error).", status,
+                 astGetClass( this ), astGetClass( this ) );
+   }
+
+/* Check the axis values are good */
+   if( start[ 0 ] != AST__BAD && start[ 1 ] != AST__BAD &&
+       end[ 0 ] != AST__BAD && end[ 1 ] != AST__BAD ) {
+
+/* Allocate memory for the returned structure. */
+      result = astMalloc( sizeof( AstLineDef ) );
+      if( result ) {
+
+/* Store the supplied axis values in the returned structure. */
+         result->start[ 0 ] = start[ 0 ];
+         result->start[ 1 ] = start[ 1 ];
+         result->end[ 0 ] = end[ 0 ];
+         result->end[ 1 ] = end[ 1 ];
+
+/* Store the length of the line segment. */
+         result->length = astDistance( this, start, end );
+
+/* Store a unit vector pointing from the start to the end. */
+         if( result->length > 0.0 ) {
+            result->dir[ 0 ] = ( end[ 0 ] - start[ 0 ] )/result->length;
+            result->dir[ 1 ] = ( end[ 1 ] - start[ 1 ] )/result->length;
+         } else {
+            result->dir[ 0 ] = 1.0;
+            result->dir[ 1 ] = 0.0;
+         }
+
+/* Store a unit vector perpendicular to the line, such that the vector
+   points to the left, as vewied from the observer, when moving from the
+   start to the end of the line. */
+         result->q[ 0 ] = -result->dir[ 1 ];
+         result->q[ 1 ] = result->dir[ 0 ];
+
+/* Store a pointer to the defining Frame. */
+         result->frame = this;
+
+/* Indicate that the line is considered to be terminated at the start and
+   end points. */
+         result->infinite = 0;
+      }
+   }
+
+/* Free the returned pointer if an error occurred. */
+   if( !astOK ) result = astFree( result );
+
+/* Return a pointer to the output structure. */
+   return result;
+}
+
+static void LineOffset( AstFrame *this, AstLineDef *line, double par,
+                        double prp, double point[2], int *status ){
+/*
+*+
+*  Name:
+*     astLineOffset
+
+*  Purpose:
+*     Find a position close to a line.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void LineOffset( AstFrame *this, AstLineDef *line, double par,
+*                      double prp, double point[2] )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns a position formed by moving a given distance along
+*     the supplied line, and then a given distance away from the supplied line.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     line
+*        Pointer to the structure defining the line.
+*     par
+*        The distance to move along the line from the start towards the end.
+*     prp
+*        The distance to move at right angles to the line. Positive
+*        values result in movement to the left of the line, as seen from
+*        the observer, when moving from start towards the end.
+
+*  Notes:
+*     - The pointer supplied for "line" should have been created using the
+*     astLineDef method. This structure contains cached information about the
+*     line which improves the efficiency of this method when many repeated
+*     calls are made. An error will be reported if the structure does not
+*     refer to the Frame specified by "this".
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Check that the line refers to the supplied Frame. */
+   if( line->frame != this ) {
+      astError( AST__INTER, "astLineOffset(%s): The supplied line does "
+                "not relate to the supplied %s (AST internal programming "
+                "error).", status, astGetClass( this ), astGetClass( this ) );
+
+/* This implementation uses simple flat geometry. */
+   } else {
+      point[ 0 ] = line->start[ 0 ] + par*line->dir[ 0 ] + prp*line->q[ 0 ];
+      point[ 1 ] = line->start[ 1 ] + par*line->dir[ 1 ] + prp*line->q[ 1 ];
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     CmpMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstFrame *this;       /* Pointer to Frame structure */
+   int i;                /* Loop count */
+   int result;           /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the Frame structure. */
+   this = (AstFrame *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                          fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   for( i = 0; i < this->naxes; i++ ) {
+      if( !result ) result = astManageLock( this->axis[ i ], mode, extra,
+                                            fail );
+   }
+   if( this->variants && !result ) result = astManageLock( this->variants, mode,
+                                                           extra, fail );
+
+   return result;
+
+}
+#endif
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     Frame method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing Frame. This is only possible if the specified inputs
+*     correspond to some subset of the Frame outputs. That is, there
+*     must exist a subset of the Frame outputs for which each output
+*     depends only on the selected Frame inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied Frame, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame to be split (the Frame is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied Frame, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied Frame has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied Frame. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   int *result;         /* Returned pointer */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Pick the selected axes from the Frame. */
+   *map = (AstMapping *) astPickAxes( (AstFrame *) this_map, nin, in, NULL );
+
+/* Return a copy of the supplied axis array.*/
+   result = astStore( NULL, in, sizeof( int )*(size_t) nin );
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static int Match( AstFrame *template, AstFrame *target, int matchsub,
+                  int **template_axes, int **target_axes,
+                  AstMapping **map, AstFrame **result, int *status ) {
+/*
+*+
+*  Name:
+*     astMatch
+
+*  Purpose:
+*     Determine if conversion is possible between two coordinate systems.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astMatch( AstFrame *template, AstFrame *target, int matchsub,
+*                   int **template_axes, int **target_axes,
+*                   AstMapping **map, AstFrame **result )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function matches a "template" frame to a "target" frame and
+*     determines whether it is possible to convert coordinates between
+*     them.  If it is, a mapping that performs the transformation is
+*     returned along with a new Frame that describes the coordinate
+*     system that results when this mapping is applied to the "target"
+*     coordinate system. In addition, information is returned to allow
+*     the axes in this "result" Frame to be associated with the
+*     corresponding axes in the "target" and "template" Frames from
+*     which they are derived.
+
+*  Parameters:
+*     template
+*        Pointer to the template Frame. This describes the coordinate
+*        system (or set of possible coordinate systems) into which we
+*        wish to convert our coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate
+*        system in which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case (i.e. if the
+*        target is of a more specialised class than the template). In
+*        this latter case, the target is cast down to the class of the
+*        template. NOTE, this argument is handled by the global method
+*        wrapper function "astMatch_", rather than by the class-specific
+*        implementations of this method.
+*     template_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the (zero-based) index of the
+*        template Frame axis from which it is derived. If it is not
+*        derived from any template frame axis, a value of -1 will be
+*        returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the (zero-based) index of the
+*        target Frame axis from which it is derived. If it is not
+*        derived from any target Frame axis, a value of -1 will be
+*        returned instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will
+*        be returned if the requested coordinate conversion is
+*        possible. If returned, the forward transformation of this
+*        Mapping may be used to convert coordinates between the
+*        "target" Frame and the "result" Frame (see below) and the
+*        inverse transformation will convert in the opposite
+*        direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be
+*        returned if the requested coordinate conversion is
+*        possible. If returned, this Frame describes the coordinate
+*        system that results from applying the returned Mapping
+*        (above) to the "target" coordinate system. In general, this
+*        Frame will combine attributes from (and will therefore be
+*        more specific than) both the target and the template
+*        Frames. In particular, when the template allows the
+*        possibility of transformaing to any one of a set of
+*        alternative coordinate systems, the "result" Frame will
+*        indicate which of the alternatives was used.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate
+*     conversion is possible. Otherwise zero is returned (this will
+*     not in itself result in an error condition).
+
+*  Notes:
+*     - By default, the "result" frame will have its number of axes
+*     and axis order determined by the "template" Frame. However, if
+*     the PreserveAxes attribute of the template frame is non-zero,
+*     then the axis count and axis order of the "target" frame will be
+*     used instead.
+*     - The template_axes and target_axes arrays are provided so that
+*     if the caller needs to permute the target and/or template axes
+*     before invoking this function, it is possible to deduce how the
+*     result axes should be permuted so as to correspond with the
+*     original template/target axis order.
+*     - For result axes that do not correspond with a template and/or
+*     target axis (where a value of -1 is returned in the
+*     template_axes and/or target_axes arrays), the caller has no
+*     clear way of knowing where these axes should appear in any
+*     permuted order. In this case, the relative position of these
+*     axes within the result Frame (with respect to axes that do have
+*     template/target axis associations) will be used to convey this
+*     information. Such axes should be taken to be associated either
+*     with the next preceding or following axis (depending on the
+*     AST__MATCHEND flag of the template frame) which does have an
+*     association.
+*     - If the result Frame has zero axes, then NULL pointer values
+*     will be returned for *template_axes and *target_axes.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     This implementation addresses the matching of a Frame class
+*     object to other types of Frames (i.e. the target may be from a
+*     derived class). A Frame will match any other type of Frame with
+*     an acceptable number of axes but will not distinguish axis order
+*     (i.e. it will match the axes in whatever order they are
+*     given). If the template Frame has a value set for its Domain
+*     attribute, then it will only match another Frame with the same
+*     Domain.
+*/
+
+/* Local Variables: */
+   char *template_domain;        /* Pointer to copy of template domain */
+   const char *ptr;              /* Pointer to domain string */
+   const char *target_domain;    /* Pointer to target domain string */
+   int match;                    /* Template matches target? */
+   int match_end;                /* Match final axes of target? */
+   int max_axes;                 /* Maximum acceptable number of axes */
+   int min_axes;                 /* Minimum acceptable nu,ber of axes */
+   int preserve_axes;            /* Preserve target axes? */
+   int result_axis;              /* Loop counter for result axes */
+   int result_naxes;             /* Number of result axes */
+   int target_naxes;             /* Number of target axes */
+   int template_naxes;           /* Number of template axes */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* The first requirement for a match is that the target object is a Frame. This
+   is already known to be true, as it forms part of the argument validation
+   for this function. */
+
+/* The second requirement is that the number of target axes is acceptable.
+   Obtain the number of target axes and the minimum and maximum number of axes
+   that the template will match. */
+   target_naxes = astGetNaxes( target );
+   min_axes = astGetMinAxes( template );
+   max_axes = astGetMaxAxes( template );
+
+/* Test if the number of target axes is acceptable. */
+   if ( astOK ) {
+      match = ( ( target_naxes >= min_axes ) && ( target_naxes <= max_axes ) );
+   }
+
+/* The third requirement is that if the template has its Domain
+   attribute defined, then the target must also have the same Domain
+   (although it need not be set - the default will do). First check if
+   the template has a domain. */
+   if ( astOK && match ) {
+      if ( astTestDomain( template ) ) {
+
+/* Obtain a pointer to the template domain. Then allocate memory and
+   make a copy of it (this is necessary as we will next inquire the
+   domain of the target and may over-write the buffer holding the
+   template's domain). */
+         ptr = astGetDomain( template );
+         if ( astOK ) {
+            template_domain = astStore( NULL, ptr,
+                                        strlen( ptr ) + (size_t) 1 );
+
+/* Obtain a pointer to the target domain. */
+            target_domain = astGetDomain( target );
+
+/* Compare the domain strings for equality. Then free the memory
+   allocated above. */
+            match = astOK && !strcmp( template_domain, target_domain );
+            template_domain = astFree( template_domain );
+         }
+      }
+   }
+
+/* If the template matches, obtain the values of the template's PreserveAxes
+   and MatchEnd attributes and determine the number of template axes. */
+   if ( astOK && match ) {
+      preserve_axes = astGetPreserveAxes( template );
+      match_end = astGetMatchEnd( template );
+      template_naxes = astGetNaxes( template );
+
+/* If the PreserveAxes attribute is non-zero, the target axes should be
+   preserved, so the number of result axes equals the number of target axes.
+   Otherwise the number of template axes is used. */
+      result_naxes = preserve_axes ? target_naxes : template_naxes;
+
+/* Allocate memory for the arrays of axis associations to be returned. */
+      *template_axes = astMalloc( sizeof( int ) * (size_t) result_naxes );
+      *target_axes = astMalloc( sizeof( int ) * (size_t) result_naxes );
+      if ( astOK ) {
+
+/* Loop through each of the result axes. */
+         for ( result_axis = 0; result_axis < result_naxes; result_axis++ ) {
+
+/* Set up the axis associations. By default, associate the first result axis
+   with the first template/target axis. */
+            (*template_axes)[ result_axis ] = result_axis;
+            (*target_axes)[ result_axis ] = result_axis;
+
+/* However, if the MatchEnd attribute is non-zero, associate the last result
+   axis with the last template/target axis (this only makes a difference if
+   there is a difference in the number of axes). */
+            if ( match_end ) {
+               (*template_axes)[ result_axis ] +=
+                                               template_naxes - result_naxes;
+               (*target_axes)[ result_axis ] += target_naxes - result_naxes;
+            }
+
+/* If any of the associations would be with a template/target axis that doesn't
+   exist, then use an axis index of -1 for the association instead. */
+            if ( ( (*template_axes)[ result_axis ] < 0 ) ||
+                 ( (*template_axes)[ result_axis ] >= template_naxes ) ) {
+               (*template_axes)[ result_axis ] = -1;
+	    }
+            if ( ( (*target_axes)[ result_axis ] < 0 ) ||
+                 ( (*target_axes)[ result_axis ] >= target_naxes ) ) {
+               (*target_axes)[ result_axis ] = -1;
+	    }
+         }
+
+/* Use the target's astSubFrame method to select the required axes from it,
+   overlaying the template's attributes on to the resulting Frame. This process
+   also generates the required Mapping between the target and result Frames. */
+         match = astSubFrame( target, template,
+                              result_naxes, *target_axes, *template_axes,
+                              map, result );
+      }
+   }
+
+/* If an error occurred, free any allocated memory and reset the result. */
+   if ( !astOK || !match ) {
+      *template_axes = astFree( *template_axes );
+      *target_axes = astFree( *target_axes );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static void MatchAxes( AstFrame *frm1, AstFrame *frm2, int *axes,
+                       int *status ) {
+/*
+*++
+*  Name:
+c     astMatchAxes
+f     AST_MATCHAXES
+
+*  Purpose:
+*     Find any corresponding axes in two Frames.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     void astMatchAxes( AstFrame *frm1, AstFrame *frm2, int *axes )
+f     CALL AST_MATCHAXES( FRM1, FRM2, AXES, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function looks for corresponding axes within two supplied
+*     Frames. An array of integers is returned that contains an element
+*     for each axis in the second supplied Frame. An element in this array
+*     will be set to zero if the associated axis within the second Frame
+*     has no corresponding axis within the first Frame. Otherwise, it
+*     will be set to the index (a non-zero positive integer) of the
+*     corresponding axis within the first supplied Frame.
+
+*  Parameters:
+c     frm1
+f     FRM1 = INTEGER (Given)
+*        Pointer to the first Frame.
+c     frm2
+f     FRM2 = INTEGER (Given)
+*        Pointer to the second Frame.
+c     axes
+f     AXES = INTEGER( * ) (Returned)
+c        Pointer to an
+f        An
+*        integer array in which to return the indices of the axes (within
+*        the first Frame) that correspond to each axis within the second
+*        Frame. Axis indices start at 1. A value of zero will be stored
+*        in the returned array for each axis in the second Frame that has
+*        no corresponding axis in the first Frame.
+*
+*        The number of elements in this array must be greater than or
+*        equal to the number of axes in the second Frame.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Frame
+*        This function applies to all Frames.
+
+*  Notes:
+*     -  Corresponding axes are identified by the fact that a Mapping can
+*     be found between them using
+c     astFindFrame or astConvert.
+f     AST_FINDFRAME or AST_CONVERT.
+*     Thus, "corresponding axes" are not necessarily identical. For
+*     instance, SkyFrame axes in two Frames will match even if they
+*     describe different celestial coordinate systems
+*--
+
+* Implementation Notes:
+*    This function is simply a wrap-up for the protected astMatchAxesX
+*    method which performs the required processing but swaps the order
+*    of the first two arguments. This is a trick to allow the
+*    astMatchAxes method to be over-ridden by derived classes on the
+*    basis of the class of either of the first two arguments.
+*
+*    In practice, each class that represents an encapsulated Frame (e.g.
+*    FrameSet, Region, etc) should over-ride this method, extracting a
+*    Frame from the supplied "frm1" pointer, and then invoking
+*    astMatchAxesX.
+
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the "astMatchAxesX" method with the first two arguments
+   swapped. */
+   astMatchAxesX( frm2, frm1, axes );
+}
+
+static void MatchAxesX( AstFrame *frm2, AstFrame *frm1, int *axes,
+                        int *status ) {
+/*
+*+
+*  Name:
+*     astMatchAxesX
+
+*  Purpose:
+*     Find any corresponding axes in two Frames.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astMatchAxesX( AstFrame *frm2, AstFrame *frm1, int *axes )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function performs the processing for the public astMatchAxes
+*     method and has exactly the same interface except that the order
+*     of the first two arguments is swapped. This is a trick to allow
+*     the astMatchAxes method to be over-ridden by derived classes on
+*     the basis of the class of either of its first two arguments.
+*
+*     See the astMatchAxes method for details of the interface.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *pfrm;
+   AstFrame *resfrm;
+   AstMapping *resmap;
+   int *frm1_axes;
+   int *pfrm_axes;
+   int ifirst;
+   int max_axes;
+   int min_axes;
+   int nax2;
+   int pax;
+   int preserve_axes;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Temporarily ensure that the PreserveAxes attribute is non-zero in
+   the second supplied Frame. This means thte result Frame returned by
+   astMatch below will have the axis count and order of the target Frame
+   (i.e. "pfrm"). */
+   if( astTestPreserveAxes( frm1 ) ) {
+      preserve_axes = astGetPreserveAxes( frm1 ) ? 1 : 0;
+   } else {
+      preserve_axes = -1;
+   }
+   astSetPreserveAxes( frm1, 1 );
+
+/* Temporarily ensure that the MaxAxes and MinAxes attributes in the
+   second supplied Frame are set so the Frame can be used as a template
+   in astMatch for matching any number of axes. */
+   if( astTestMaxAxes( frm1 ) ) {
+      max_axes = astGetMaxAxes( frm1 );
+   } else {
+      max_axes = -1;
+   }
+   astSetMaxAxes( frm1, 10000 );
+
+   if( astTestMinAxes( frm1 ) ) {
+      min_axes = astGetMinAxes( frm1 );
+   } else {
+      min_axes = -1;
+   }
+   astSetMinAxes( frm1, 1 );
+
+/* Get the number of axes in the frm2 Frame. */
+   nax2 = astGetNaxes( frm2 );
+
+/* Loop round the axes in the frm2 Frame. */
+   for( ifirst = 0; ifirst < nax2; ifirst++ ) {
+
+/* Identify the primary Frame defining the current axis in the frm2
+   Frame. */
+      astPrimaryFrame( frm2, ifirst, &pfrm, &pax );
+
+/* Attempt to find a sub-frame within the frm1 Frame that corresponds to
+   this primary Frame. */
+      if( astMatch( frm1, pfrm, 1, &frm1_axes, &pfrm_axes, &resmap, &resfrm ) ) {
+
+/* Store the one-based index within "frm1" of the corresponding axis. */
+         axes[ ifirst ] = frm1_axes[ pax ] + 1;
+
+/* Free resources */
+         frm1_axes = astFree( frm1_axes );
+         pfrm_axes = astFree( pfrm_axes );
+         resmap = astAnnul( resmap );
+         resfrm = astAnnul( resfrm );
+
+/* If no corresponding axis was found store zero in the returned array. */
+      } else {
+         axes[ ifirst ] = 0;
+      }
+
+/* Free resouces. */
+      pfrm = astAnnul( pfrm );
+   }
+
+/* Re-instate the original attribute values in the frm1 Frame. */
+   if( preserve_axes == -1 ) {
+      astClearPreserveAxes( frm1 );
+   } else {
+      astSetPreserveAxes( frm1, preserve_axes );
+   }
+
+   if( max_axes == -1 ) {
+      astClearMaxAxes( frm1 );
+   } else {
+      astSetMaxAxes( frm1, max_axes );
+   }
+
+   if( min_axes == -1 ) {
+      astClearMinAxes( frm1 );
+   } else {
+      astSetMinAxes( frm1, min_axes );
+   }
+}
+
+static void NewUnit( AstAxis *ax, const char *old_units, const char *new_units,
+                     const char *method, const char *class, int *status ) {
+/*
+*  Name:
+*     NewUnit
+
+*  Purpose:
+*     Modify an Axis Label and Symbol to reflect a new Unit value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void NewUnit( AstAxis *ax, const char *old_units, const char *new_units,
+*                   const char *method, const char *class )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function modifies the Label and Symbol attributes of an Axis
+*     to reflect a new Unit value. This function should only be called if
+*     the ActiveUnit flag of the parent Frame is non-zero (this is not
+*     checked within this function).
+*
+*     If the axis has a set label, then we may be able to modify it to
+*     correctly describe the axis in the supplied new units. For instance,
+*     if the original units were "Hz", the original label was "frequency",
+*     and the new units are "log(Hz)", then the label is modified to become
+*     "log( frequency )".
+*
+*     The Axis Format attribute is cleared if the supplied units are
+*     different to the old units (because any set format is probably not
+*     going to be appropriate for a new system of units.
+
+*  Parameters:
+*     ax
+*        Pointer to the Axis.
+*     old_units
+*        The original units value.
+*     new_units
+*        The new units value.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis selection. This method name is used
+*        solely for constructing error messages.
+*     class
+*        Pointer to a constant null-terminated character string
+*        containing the name of the class upon which this function
+*        was invoked. This is used solely for constructing error messages.
+
+*  Returned Value:
+*     void.
+*/
+
+/* Local Variables: */
+   AstMapping *map;              /* Pointer to units Mapping */
+   char *new_lab;                /* Pointer to new axis label */
+   char *new_sym;                /* Pointer to new axis symbol */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Check that the axis label is set. We relay on sub-classes to return
+   appropriate default labels if the label is not set. */
+   if( astTestAxisLabel( ax ) ) {
+
+/* See if it is possible to map the old units into the new units.
+   If it is, then a Mapping is returned together with an appropriately
+   modified label. */
+      map = astUnitMapper( old_units, new_units, astGetAxisLabel( ax ),
+                           &new_lab );
+
+/* If succesfull, annul the Mapping (which we do not need), and store the
+   modified label in the Axis, finally freeing the memory used to hold
+   the modified label. */
+      if( map ) {
+         map = astAnnul( map );
+         if( new_lab ) {
+            astSetAxisLabel( ax, new_lab );
+            new_lab = astFree( new_lab );
+         }
+      }
+   }
+
+/* Do the same for the axis symbol. */
+   if( astTestAxisSymbol( ax ) ) {
+      map = astUnitMapper( old_units, new_units, astGetAxisSymbol( ax ),
+                           &new_sym );
+      if( map ) {
+         map = astAnnul( map );
+         if( new_sym ) {
+            astSetAxisSymbol( ax, new_sym );
+            new_sym = astFree( new_sym );
+         }
+      }
+   }
+
+/* If succesful, clear the axis format if the new and old units are
+   different. */
+   if( astOK ) {
+      if( strcmp( old_units, new_units ) ) astClearAxisFormat( ax );
+   }
+
+}
+
+static void Norm( AstFrame *this, double value[], int *status ) {
+/*
+*++
+*  Name:
+c     astNorm
+f     AST_NORM
+
+*  Purpose:
+*     Normalise a set of Frame coordinates.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     void astNorm( AstFrame *this, double value[] )
+f     CALL AST_NORM( THIS, VALUE, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function normalises a set of Frame coordinate values which
+f     This routine normalises a set of Frame coordinate values which
+*     might be unsuitable for display (e.g. may lie outside the
+*     expected range) into a set of acceptable values suitable for
+*     display.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     value
+f     VALUE( * ) = DOUBLE PRECISION (Given and Returned)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        coordinate values representing a point in the space which the
+*        Frame describes. If these values lie outside the expected
+*        range for the Frame, they will be replaced with more
+*        acceptable (normalised) values. Otherwise, they will be
+*        returned unchanged.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - For some classes of Frame, whose coordinate values are not
+*     constrained, this function will never modify the values
+*     supplied. However, for Frames whose axes represent cyclic
+*     quantities (such as angles or positions on the sky), coordinates
+*     will typically be wrapped into an appropriate standard range,
+*     such as zero to 2*pi.
+*     - The NormMap class is a Mapping which can be used to normalise a
+*     set of points using the
+c     astNorm function
+f     AST_NORM routine
+*     of a specified Frame.
+*     - It is intended to be possible to put any set of coordinates
+*     into a form suitable for display by using this function to
+*     normalise them, followed by appropriate formatting
+c     (using astFormat).
+f     (using AST_FORMAT).
+*--
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   int axis;                     /* Loop counter for axes */
+   int naxes;                    /* Number of Frame axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain the number of Frame axes. */
+   naxes = astGetNaxes( this );
+
+/* Loop to process the coordinate for each axis in turn. */
+   for ( axis = 0; axis < naxes; axis++ ) {
+
+/* Obtain a pointer to the relevant Frame Axis. */
+      ax = astGetAxis( this, axis );
+
+/* Normalise the coordinate for this axis. */
+      astAxisNorm( ax, value + axis );
+
+/* Annul the pointer to the Axis. */
+      ax = astAnnul( ax );
+
+/* Quit looping if an error occurs. */
+      if ( !astOK ) break;
+   }
+}
+
+static void NormBox( AstFrame *this, double lbnd[], double ubnd[],
+                     AstMapping *reg, int *status ) {
+/*
+*+
+*  Name:
+*     astNormBox
+
+*  Purpose:
+*     Extend a box to include effect of any singularities in the Frame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astNormBox( AstFrame *this, double lbnd[], double ubnd[],
+*                      AstMapping *reg )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function modifies a supplied box to include the effect of any
+*     singularities in the co-ordinate system represented by the Frame.
+*     For a normal Cartesian coordinate system, the box will be returned
+*     unchanged. Other classes of Frame may do other things. For instance,
+*     a SkyFrame will check to see if the box contains either the north
+*     or south pole and extend the box appropriately.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     lbnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        lower axis bounds for the box. They will be modified on exit
+*        to include the effect of any singularities within the box.
+*     ubnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        upper axis bounds for the box. They will be modified on exit
+*        to include the effect of any singularities within the box.
+*     reg
+*        A Mapping which should be used to test if any singular points are
+*        inside or outside the box. The Mapping should leave an input
+*        position unchanged if the point is inside the box, and should
+*        set all bad if the point is outside the box.
+*-
+*/
+
+/* This base class returns the box limits unchanged. */
+}
+
+static double Offset2( AstFrame *this, const double point1[2], double angle,
+                     double offset, double point2[2], int *status ){
+/*
+*++
+*  Name:
+c     astOffset2
+f     AST_OFFSET2
+
+*  Purpose:
+*     Calculate an offset along a geodesic curve in a 2D Frame.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     double astOffset2( AstFrame *this, const double point1[2], double angle,
+c                        double offset, double point2[2] );
+f     RESULT = AST_OFFSET2( THIS, POINT1, ANGLE, OFFSET, POINT2, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function finds the Frame coordinate values of a point which
+f     This routine finds the Frame coordinate values of a point which
+*     is offset a specified distance along the geodesic curve at a
+*     given angle from a specified starting point. It can only be
+*     used with 2-dimensional Frames.
+*
+*     For example, in a basic Frame, this offset will be along the
+*     straight line joining two points. For a more specialised Frame
+*     describing a sky coordinate system, however, it would be along
+*     the great circle passing through two sky positions.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     point1
+f     POINT1( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). This should contain the coordinates of the
+*        point marking the start of the geodesic curve.
+c     angle
+f     ANGLE = DOUBLE PRECISION (Given)
+*        The angle (in radians) from the positive direction of the second
+*        axis, to the direction of the required position, as seen from
+*        the starting position. Positive rotation is in the sense of
+*        rotation from the positive direction of axis 2 to the positive
+*        direction of axis 1.
+c     offset
+f     OFFSET = DOUBLE PRECISION
+*        The required offset from the first point along the geodesic
+*        curve. If this is positive, it will be in the direction of the
+*        given angle. If it is negative, it will be in the opposite
+*        direction.
+c     point2
+f     POINT2( * ) = DOUBLE PRECISION (Returned)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        in which the coordinates of the required point will be returned.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astOffset2
+f     AST_OFFSET2 = DOUBLE PRECISION
+*        The direction of the geodesic curve at the end point. That is, the
+*        angle (in radians) between the positive direction of the second
+*        axis and the continuation of the geodesic curve at the requested
+*        end point. Positive rotation is in the sense of rotation from
+*        the positive direction of axis 2 to the positive direction of axis
+*        1.
+
+*  Notes:
+c     - The geodesic curve used by this function is the path of
+f     - The geodesic curve used by this routine is the path of
+*     shortest distance between two points, as defined by the
+c     astDistance function.
+f     AST_DISTANCE function.
+*     - An error will be reported if the Frame is not 2-dimensional.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*--
+*/
+
+/* Local Variables: */
+   int naxes;                    /* Number of Frame axes */
+   double result;                /* Returned value */
+
+/* Check the global error status. */
+   result = AST__BAD;
+   if ( !astOK ) return result;
+
+/* Initialize bad values. */
+   point2[ 0 ] = AST__BAD;
+   point2[ 1 ] = AST__BAD;
+
+/* Determine the number of Frame axes. */
+   naxes = astGetNaxes( this );
+
+/* Report an error if the Frame is not 2 dimensional. */
+   if( naxes != 2 && astOK ) {
+      astError( AST__NAXIN, "astOffset2(%s): Invalid number of Frame axes (%d)."
+                " astOffset2 can only be used with 2 dimensonal Frames.", status,
+                astGetClass( this ), naxes );
+   }
+
+/* Check the supplied values. */
+   if ( astOK && point1[ 0 ] != AST__BAD && point1[ 1 ] != AST__BAD &&
+        angle != AST__BAD && offset != AST__BAD ) {
+
+/* Store the results. */
+      point2[ 0 ] = point1[ 0 ] + sin( angle )*offset;
+      point2[ 1 ] = point1[ 1 ] + cos( angle )*offset;
+
+/* The position angle of the curve does not vary in cartesian coordinates */
+      result = angle;
+
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static void Offset( AstFrame *this, const double point1[],
+                    const double point2[], double offset, double point3[], int *status ) {
+/*
+*++
+*  Name:
+c     astOffset
+f     AST_OFFSET
+
+*  Purpose:
+*     Calculate an offset along a geodesic curve.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     void astOffset( AstFrame *this,
+c                     const double point1[], const double point2[],
+c                     double offset, double point3[] )
+f     CALL AST_OFFSET( THIS, POINT1, POINT2, OFFSET, POINT3, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function finds the Frame coordinate values of a point which
+f     This routine finds the Frame coordinate values of a point which
+*     is offset a specified distance along the geodesic curve between
+*     two other points.
+*
+*     For example, in a basic Frame, this offset will be along the
+*     straight line joining two points. For a more specialised Frame
+*     describing a sky coordinate system, however, it would be along
+*     the great circle passing through two sky positions.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     point1
+f     POINT1( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). This should contain the coordinates of the
+*        point marking the start of the geodesic curve.
+c     point2
+f     POINT2( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis.
+*        This should contain the coordinates of the point marking the
+*        end of the geodesic curve.
+c     offset
+f     OFFSET = DOUBLE PRECISION
+*        The required offset from the first point along the geodesic
+*        curve. If this is positive, it will be towards the second
+*        point. If it is negative, it will be in the opposite
+*        direction. This offset need not imply a position lying
+*        between the two points given, as the curve will be
+*        extrapolated if necessary.
+c     point3
+f     POINT3( * ) = DOUBLE PRECISION (Returned)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        in which the coordinates of the required point will be returned.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+c     - The geodesic curve used by this function is the path of
+f     - The geodesic curve used by this routine is the path of
+*     shortest distance between two points, as defined by the
+c     astDistance function.
+f     AST_DISTANCE function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*     - "Bad" coordinate values will also be returned if the two
+*     points supplied are coincident (or otherwise fail to uniquely
+*     specify a geodesic curve) but the requested offset is non-zero.
+*--
+*/
+
+/* Local Variables: */
+   double delta;                 /* Displacement along axis */
+   double dist;                  /* Distance between points */
+   double fract;                 /* Fraction of distance required */
+   int axis;                     /* Loop counter for axes */
+   int naxes;                    /* Number of Frame axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Determine the number of Frame axes. */
+   naxes = astGetNaxes( this );
+   if ( astOK ) {
+
+/* Loop to determine the Cartesian distance between points 1 and 2. */
+      dist = 0.0;
+      for ( axis = 0; axis < naxes; axis++ ) {
+
+/* If any of the coordinates supplied is bad, set the distance to be
+   bad and quit looping. */
+         if ( ( point1[ axis ] == AST__BAD ) ||
+              ( point2[ axis ] == AST__BAD ) ) {
+            dist = AST__BAD;
+            break;
+
+/* Otherwise, accumulate the sum of squared displacements along each
+   axis. */
+         } else {
+            delta = point1[ axis ] - point2[ axis ];
+            dist += ( delta * delta );
+         }
+      }
+
+/* Take the square root to find the distance (if valid). */
+      if ( dist != AST__BAD ) dist = sqrt( dist );
+
+/* If the distance between the points cannot be found, or the distance
+   is zero but the required offset is non-zero, then set the result
+   coordinates to be bad. */
+      if ( ( dist == AST__BAD ) ||
+           ( ( dist == 0.0 ) && ( offset != 0.0 ) ) ) {
+         for ( axis = 0; axis < naxes; axis++ ) {
+            point3[ axis ] = AST__BAD;
+         }
+
+/* Otherwise, calculate what fraction of the distance between the
+   points we need to move, and apply this fraction of the displacement
+   along each axis. */
+      } else {
+         fract = ( dist == 0.0 ) ? 0.0 : offset / dist;
+         for ( axis = 0; axis < naxes; axis++ ) {
+            point3[ axis ] = point1[ axis ] +
+                             fract * ( point2[ axis ] - point1[ axis ] );
+         }
+      }
+   }
+}
+
+static void Overlay( AstFrame *template, const int *template_axes,
+                     AstFrame *result, int *status ) {
+/*
+*+
+*  Name:
+*     astOverlay
+
+*  Purpose:
+*     Overlay the attributes of a template Frame on to another Frame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astOverlay( AstFrame *template, const int *template_axes,
+*                      AstFrame *result )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function overlays attributes of one Frame (the "template") on to
+*     another Frame, so as to over-ride selected attributes of that second
+*     Frame. Normally only those attributes which have been specifically set
+*     in the template will be transferred. This implements a form of
+*     defaulting, in which a Frame acquires attributes from the template, but
+*     retains its original attributes (as the default) if new values have not
+*     previously been explicitly set in the template.
+
+*  Parameters:
+*     template
+*        Pointer to the template Frame, for which values should have been
+*        explicitly set for any attribute which is to be transferred.
+*     template_axes
+*        Pointer to an array of int, with one element for each axis of the
+*        "result" Frame (see below). For each axis in the result frame, the
+*        corresponding element of this array should contain the (zero-based)
+*        index of the template axis to which it corresponds. This array is used
+*        to establish from which template axis any axis-dependent attributes
+*        should be obtained.
+*
+*        If any axis in the result Frame is not associated with a template
+*        axis, the corresponding element of this array should be set to -1.
+*
+*        If a NULL pointer is supplied, the template and result axis
+*        indices are assumed to be identical.
+*     result
+*        Pointer to the Frame which is to receive the new attribute values.
+*-
+*/
+
+/* Local Variables: */
+   AstAxis *result_ax;           /* Pointer to result Axis object */
+   AstAxis *template_ax;         /* Pointer to template Axis object */
+   AstSystemType sys;            /* System value */
+   int result_axis;              /* Loop counter for result Frame axes */
+   int result_naxes;             /* Number of result Frame axes */
+   int template_axis;            /* Index of template Frame axis */
+   int template_naxes;           /* Number of template Frame axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Define a macro that tests whether an attribute is set in the template and,
+   if so, transfers its value to the target. */
+#define OVERLAY(attribute) \
+   if ( astTest##attribute( template ) ) { \
+      astSet##attribute( result, astGet##attribute( template ) ); \
+   }
+
+/* Use the macro to transfer each Frame attribute in turn. */
+   OVERLAY(Dut1);
+   OVERLAY(Digits);
+   OVERLAY(Domain);
+   OVERLAY(Epoch);
+   OVERLAY(Title);
+   OVERLAY(ObsLat)
+   OVERLAY(ObsLon)
+   OVERLAY(ObsAlt)
+
+/* Transfer the ActiveUnit flag. */
+   astSetActiveUnit( result, astGetActiveUnit( template ) );
+
+/* Only overlay the System and AlignSystem attribute if the values are
+   valid for the result class. */
+   if( astTestSystem( template ) ) {
+      sys = astGetSystem( template );
+      if( astValidateSystem( result, sys, "astOverlay" ) ) {
+         astSetSystem( result, sys );
+      }
+   }
+
+   if( astTestAlignSystem( template ) ) {
+      sys = astGetAlignSystem( template );
+      if( astValidateSystem( result, sys, "astOverlay" ) ) {
+         astSetAlignSystem( result, sys );
+      }
+   }
+
+/* Now transfer attributes associated with individual axes. Obtain the number
+   of axes in the template and result Frames. */
+   template_naxes = astGetNaxes( template );
+   result_naxes = astGetNaxes( result );
+   if ( astOK ) {
+
+/* Loop through all the axes in the result Frame and determine to which
+   template axis each one corresponds. Check that the resulting axis index is
+   valid. If not, then the axis will not receive new attributes. */
+      for ( result_axis = 0; result_axis < result_naxes; result_axis++ ) {
+         template_axis = template_axes ? template_axes[ result_axis ] : result_axis;
+         if ( ( template_axis >= 0 ) && ( template_axis < template_naxes ) ) {
+
+/* Obtain pointers to the relevant Axis objects of each Frame and use the
+   astAxisOverlay method of the template Axis to overlay attributes on to
+   the result Axis. Annul the Axis pointers afterwards. */
+            template_ax = astGetAxis( template, template_axis );
+            result_ax = astGetAxis( result, result_axis );
+            astAxisOverlay( template_ax, result_ax );
+            template_ax = astAnnul( template_ax );
+            result_ax = astAnnul( result_ax );
+
+/* Quit looping if an error occurs. */
+            if ( !astOK ) break;
+         }
+      }
+   }
+
+/* Undefine macros local to this function. */
+#undef OVERLAY
+}
+
+static void PermAxes( AstFrame *this, const int perm[], int *status ) {
+/*
+*+
+*  Name:
+*     astPermAxes
+
+*  Purpose:
+*     Permute the order of a Frame's axes.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astPermAxes( AstFrame *this, const int perm[] )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function permutes the order in which a Frame's axes occur.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     perm
+*        An array of int (with one element for each axis of the Frame)
+*        which lists the axes in their new order. Each element of this
+*        array should be a (zero-based) axis index identifying the
+*        axes according to their old (un-permuted) order.
+
+*  Notes:
+*     - Only genuine permutations of the axis order are permitted, so
+*     each axis must be referenced exactly once in the "perm" array.
+*     - If more than one axis permutation is applied to a Frame, the
+*     effects are cumulative.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic astPermAxes method which is
+*     available via the protected interface to the Frame class. A
+*     public interface to this method is provided by the
+*     astPermAxesId_ function.
+*/
+
+/* Local Variables: */
+   int *old;                     /* Pointer to copy of old permutation array */
+   int axis;                     /* Loop counter for Frame axes */
+   int naxes;                    /* Number of Frame axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the permutation array, to check that it describes a genuine
+   permutation. */
+   astCheckPerm( this, perm, "astPermAxes" );
+
+/* Obtain the number of Frame axes. */
+   naxes = astGetNaxes( this );
+
+/* Allocate memory and use it to store a copy of the old permutation array for
+   the Frame. */
+   old = astStore( NULL, this->perm, sizeof( int ) * (size_t) naxes );
+
+/* Apply the new axis permutation cumulatively to the old one and store the
+   result in the Frame. */
+   if ( astOK ) {
+      for ( axis = 0; axis < naxes; axis++ ) {
+         this->perm[ axis ] = old[ perm[ axis ] ];
+      }
+   }
+
+/* Free the temporary copy of the old array. */
+   old = astFree( old );
+}
+
+static AstFrame *PickAxes( AstFrame *this, int naxes, const int axes[],
+                           AstMapping **map, int *status ) {
+/*
+*+
+*  Name:
+*     astPickAxes
+
+*  Purpose:
+*     Create a new Frame by picking axes from an existing one.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstFrame *PickAxes( AstFrame *this, int naxes, const int axes[],
+*                         AstMapping **map )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function creates a new Frame whose axes are copies of axes
+*     picked from an existing Frame. Other Frame attributes are also
+*     copied to the new Frame. Zero or more of the original axes may
+*     be picked in any order, but each can be used only
+*     once. Additional axes (with default characteristics) may be
+*     included in the new Frame if required.
+*
+*     Optionally, a Mapping that converts between the original Frame's
+*     axes and those of the new Frame may also be returned.
+
+*  Parameters:
+*     this
+*        Pointer to the original Frame.
+*     naxes
+*        The number of axes required in the new Frame.
+*     axes
+*        Pointer to an array of int with naxes elements. This should
+*        contain (zero based) axis indices specifying the axes which
+*        are to be included in the new Frame, in the order
+*        required. Each axis index may occur only once.
+*
+*        If additional (default) axes are also to be included, the
+*        corresponding elements of this array should be set to -1.
+*     map
+*        Address of a location to receive a pointer to a new
+*        Mapping. This will be a PermMap (or a UnitMap as a special
+*        case) that describes the axis permutation that has taken
+*        place between the original and new Frames.  The forward
+*        transformation will convert from the original Frame's axes to
+*        the new one's, and vice versa.
+*
+*        If this Mapping is not required, a NULL value may be supplied
+*        for this parameter.
+
+*  Returned Value:
+*     Pointer to the new Frame.
+
+*  Notes:
+*     - The class of object returned may differ from that of the
+*     original Frame, depending on which axes are selected. For
+*     example, if a single axis is picked from a SkyFrame (which
+*     always has two axes), the resulting Frame cannot be a valid
+*     SkyFrame, so will revert to the parent class (Frame) instead.
+*     - The new Frame contains a deep copy of all the data selected
+*     from the original Frame. Modifying the new Frame will therefore
+*     not affect the original one.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic astPickAxes method
+*     available via the protected interface to the Frame class. The
+*     public interface to this method is provided by the
+*     astPickAxesId_ function.
+*/
+
+/* Local Variables: */
+   AstFrame *frame;              /* Pointer to Frame to be returned */
+   AstMapping *mapping;          /* Pointer to Mapping to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the returned pointers. */
+   frame = NULL;
+   if ( map ) *map = NULL;
+
+/* Check that a valid set of axes is being selected . */
+   astValidateAxisSelection( this, naxes, axes, "astPickAxes" );
+
+/* Create the required new Frame by selecting the axes. This also returns a
+   Mapping which transforms between the original Frame and the new one. */
+   astSubFrame( this, NULL, naxes, axes, NULL, &mapping, &frame );
+   if ( astOK ) {
+
+/* Return the Mapping pointer if required. */
+      if ( map ) {
+         *map = mapping;
+
+/* Otherwise annul the Mapping. If an error occurs, also annul the Frame. */
+      } else {
+         mapping = astAnnul( mapping );
+         if ( !astOK ) frame = astAnnul( frame );
+      }
+   }
+
+/* Return the pointer to the new Frame. */
+   return frame;
+}
+
+static void PrimaryFrame( AstFrame *this, int axis1,
+                          AstFrame **frame, int *axis2, int *status ) {
+/*
+*+
+*  Name:
+*     astPrimaryFrame
+
+*  Purpose:
+*     Uniquely identify a primary Frame and one of its axes.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astPrimaryFrame( AstFrame *this, int axis1, AstFrame **frame,
+*                           int *axis2 )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function returns information about the underlying (primary) Frame
+*     corresponding to a specified axis, when given what may be a compound
+*     Frame composed of more than one simpler one.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis1
+*        An axis index (zero-based) identifying the Frame axis for which
+*        information is required.
+*     frame
+*        Address of a location to receive a pointer to the underlying (primary)
+*        frame to which the requested axis belongs (i.e. this will not be a
+*        compound Frame).
+*     axis2
+*        Pointer to an int which is to receive the (zero-based) axis
+*        index within "frame" which identifies the axis being referred
+*        to, using the axis order that applied when the primary Frame
+*        was originally constructed (i.e. this function undoes all
+*        subsequent axis pemutations and the effects of combining
+*        Frames, in order to reveal the original underlying axis
+*        order).
+
+*  Notes:
+*     -  This protected method is provided so that class implementations can
+*     distinguish the axes of frames from one another (e.g. can distinguish
+*     a longitude axis as being different from a latitide axis) even after
+*     their order has been permuted and they have been combined with axes from
+*     other Frames.
+*     -  The reference count of the primary Frame will be incremented by one to
+*     reflect the new pointer returned.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise the returned values. */
+   *frame = NULL;
+   *axis2 = 0;
+
+/* Validate and permute the axis index supplied. */
+   axis1 = astValidateAxis( this, axis1, 1, "astPrimaryFrame" );
+
+/* Since "this" is a primary Frame (i.e. is not compound), simply clone a
+   pointer to it. */
+   if ( astOK ) *frame = astClone( this );
+
+/* Return the permuted axis index, which refers to the original axis order. */
+   if ( astOK ) *axis2 = axis1;
+}
+
+double astReadDateTime_( const char *value, int *status ) {
+/*
+*+
+*  Name:
+*     astReadDateTime
+
+*  Purpose:
+*     Read a date/time string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     double astReadDateTime( const char *value )
+
+*  Class Membership:
+*     Frame member function.
+
+*  Description:
+*     This function reads a date/time string in a variety of formats and
+*     returns the resulting time as a Modified Julian Date. If the string
+*     cannot be interpreted as a date/time or contains invalid values, an
+*     error is reported.
+
+*  Parameters:
+*     value
+*        Pointer to a null terminated string containing the value to be read.
+
+*  Returned Value:
+*     The time as a Modified Julian date.
+
+*  Date/Time Formats:
+*     The date/time formats supported by this function are listed below. These
+*     are interpreted in a case-insensitive manner and the function is
+*     generally flexible about the presence of additional white space and the
+*     use of alternative field delimiters.
+*
+*     Besselian Epoch
+*        Expressed in decimal years, with or without decimal places
+*        ("B1950" or "B1976.13", for example).
+*     Julian Epoch
+*        Expressed in decimal years, with or without decimal places
+*        ("J2000" or "J2100.9", for example).
+*     Year
+*        Decimal years, with or without decimal places ("1996.8" for example).
+*        Such values are interpreted as a Besselian epoch (see above) if less
+*        than 1984.0 and as a Julian epoch otherwise.
+*     Julian Date
+*        With or without decimal places ("JD 2454321.9" for example).
+*     Modified Julian Date
+*        With or without decimal places ("MJD 54321.4" for example).
+*     Gregorian Calendar Date
+*        With the month expressed as an integer or 3-character
+*        abbreviation, and with optional decimal places to represent a
+*        fraction of a day ("1996-10-2" or "1996-Oct-2.6" for
+*        example). If no fractional part of a day is given, the time
+*        refers to the start of the day (zero hours).
+*     Gregorian Date and Time
+*        Any calendar date (as above) but with a fraction of a day expressed
+*        as hours, minutes and seconds ("1996-Oct-2 12:13:56.985" for example).
+*        The date and time can be separated by a space or by a "T" (as used
+*        by ISO8601).
+
+*  Notes:
+*     -  The date/time value is interpreted as a calendar date and time, not
+*     linked to any particular time system. Thus, interpretation of hours,
+*     minutes and seconds is done in the obvious manner assuming 86400 seconds
+*     in a day. No allowance for is made, for instance, for leap seconds or for
+*     the varying length of a second in some time systems.
+*     -  A value of AST__BAD is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*-
+*/
+
+/* Local Vaiables: */
+   char cmonth[ 4 ];             /* Buffer for name of month */
+   char sep1[ 2 ];               /* Year/month separator string */
+   char sep2[ 2 ];               /* Month/day separator string */
+   char sep3[ 2 ];               /* Hour/minute separator string */
+   char sep4[ 2 ];               /* Minute/second separator string */
+   char *cc;                     /* Pointer to copy of remaining text */
+   const char *v;                /* Pointer into value string */
+   const char *p;                /* Pointer to date/time separator */
+   double day;                   /* Day number plus fraction of whole day */
+   double epoch;                 /* Epoch stored as decimal years */
+   double hms;                   /* Hours, min & sec as fraction of a day */
+   double jd;                    /* Julian Date */
+   double mjd;                   /* Modified Julian Date */
+   double result;                /* Result to be returned */
+   double sec;                   /* Seconds and fractions of a second */
+   int hour;                     /* Number of hours */
+   int iday;                     /* Number of whole days */
+   int l;                        /* Length of string remaining */
+   int len;                      /* Length of string */
+   int match;                    /* Date/time string has correct form? */
+   int minute;                   /* Number of minutes */
+   int month;                    /* Number of months */
+   int nc;                       /* Number of characters read from string */
+   int stat;                     /* Status return from SLALIB functions */
+   int year;                     /* Number of years */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Obtain the length of the input string. */
+   len = (int) strlen( value );
+
+/* Attempt to read the string using each recognised format in turn. */
+
+/* Besselian epoch in decimal years (e.g. "B1950.0"). */
+/* ================================================== */
+   if ( nc = 0,
+        ( 1 == astSscanf( value, " %*1[Bb] %lf %n", &epoch, &nc ) )
+        && ( nc >= len ) ) {
+
+/* Convert to Modified Julian Date. */
+      result = palEpb2d( epoch );
+
+/* Julian epoch in decimal years (e.g. "J2000.0"). */
+/* =============================================== */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( value, " %*1[Jj] %lf %n", &epoch, &nc ) )
+               && ( nc >= len ) ) {
+
+/* Convert to Modified Julian Date. */
+      result = palEpj2d( epoch );
+
+/* Decimal years (e.g. "1976.2"). */
+/* ============================== */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( value, " %lf %n", &epoch, &nc ) )
+               && ( nc >= len ) ) {
+
+/* Convert to Modified Julian Date, treating the epoch as Julian or Besselian
+   depending on whether it is 1984.0 or later. */
+      result = ( epoch < 1984.0 ) ? palEpb2d( epoch ) : palEpj2d( epoch );
+
+/* Modified Julian Date (e.g. "MJD 54321.0"). */
+/* ============================================ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( value, " %*1[Mm] %*1[Jj] %*1[Dd] %lf %n",
+                              &mjd, &nc ) ) && ( nc >= len ) ) {
+
+/* Use the result directly. */
+      result = mjd;
+
+/* Julian Date (e.g. "JD 2454321.5"). */
+/* ==================================== */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( value, " %*1[Jj] %*1[Dd] %lf %n",
+                              &jd, &nc ) ) && ( nc >= len ) ) {
+
+/* Convert to Modified Julian Date. */
+      result = jd - 2400000.5;
+
+/* Gregorian calendar date (e.g. "1996-10-2" or "1996-Oct-2"). */
+/* =========================================================== */
+/* This format also allows day fractions expressed as decimal days, e.g:
+
+      "1996-Oct-2.5001"
+
+   or as hours, minutes and seconds, e.g:
+
+      "1996-Oct-2 12:14:30.52"
+
+   Various alternative field delimiters are also allowed. */
+   } else {
+
+/* Note that the method used to parse this format relies heavily on
+   conditional execution controlled by "&&" and "||" operators. Initialise
+   the variables used. */
+      v = value;
+      l = len;
+      *cmonth = '\0';
+      year = month = iday = hour = minute = 0;
+      day = sec = 0.0;
+
+/* Identify the year and month. */
+/* ---------------------------- */
+/* Try to match an initial " 1996 - 10 -" or " 1996 10 " or similar. */
+      match =
+         ( nc = 0, ( 4 == astSscanf( v, " %d %1[:/-] %2d %1[:/-]%n",
+                                  &year, sep1, &month, sep2, &nc ) ) );
+      match = match ||
+         ( nc = 0, ( 4 == astSscanf( v, " %d%1[ ] %2d%1[ ]%n",
+                                  &year, sep1, &month, sep2, &nc ) ) );
+
+/* If that failed, allow " 1996 - Oct -" or " 1996 Oct " or similar. */
+      match = match ||
+         ( nc = 0, ( 4 == astSscanf( v,
+                                  " %d %1[:/-] %3[ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                                  "abcdefghijklmnopqrstuvwxyz] %1[:/-]%n",
+                                  &year, sep1, cmonth, sep2, &nc ) ) );
+      match = match ||
+         ( nc = 0, ( 4 == astSscanf( v,
+                                  " %d%1[ ] %3[ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                                  "abcdefghijklmnopqrstuvwxyz]%1[ ]%n",
+                                  &year, sep1, cmonth, sep2, &nc ) ) );
+
+/* Alternative field separators are permitted above, but ensure that
+   they are both the same. */
+      match = match && ( *sep1 == *sep2 );
+
+/* Identify the day and fraction of day. */
+/*-------------------------------------- */
+/* If the above matched correctly, modify the string pointer "v" to
+   the next character to be interpreted and decrement the remaining
+   string length. */
+      if ( match ) {
+         v += nc;
+         l -= nc;
+
+/* ISO8601 format uses the litter T as a delimiter between the date and time.
+   If there is a T in the remaining string, take a copy and change the T to
+   a space. */
+         p = strchr( v, 'T' );
+         if( p ) {
+            cc = astStore( NULL, v, l + 1 );
+            cc[ p - v ] = ' ';
+            v = cc;
+         } else {
+            cc = NULL;
+         }
+
+/* We now try to match the following characters but without reading
+   any values.  This is done to ensure the string has the correct form
+   (e.g. exclude "-" signs and exponents in numbers, which are
+   otherwise hard to detect). */
+
+/* Try to match " 12.3456 " or similar. */
+         match =
+            ( nc = 0, ( 0 == astSscanf( v, " %*2[0123456789].%*[0123456789] %n",
+                                     &nc ) )
+                      && ( nc == l ) );
+
+/* If that failed, then try to match " 12. " or similar. */
+         match = match ||
+            ( nc = 0, ( 0 == astSscanf( v, " %*2[0123456789]. %n", &nc ) )
+                      && ( nc == l ) );
+
+/* If that also failed, then try to match just " 12 " or similar. */
+         match = match ||
+            ( nc = 0, ( 0 == astSscanf( v, " %*2[0123456789] %n", &nc ) )
+                      && ( nc == l ) );
+
+/* If any of the above patterns matched, now read the data (the day number)
+   as a double value. */
+         if ( match ) {
+            match = ( nc = 0, ( 1 == astSscanf( v, " %lf %n", &day, &nc ) )
+                              && ( nc == l ) );
+
+/* If none of the above matched, then look to see if the day fraction has been
+   given in hours, minutes and seconds by trying to match " 12 03 : 45 :" or
+   " 12 13 45 " or similar. */
+         } else {
+            match =
+               ( nc = 0, ( 5 == astSscanf( v,
+                                        " %2d%*1[ ] %2d %1[:/-] %2d %1[:/-]%n",
+                                        &iday, &hour, sep3, &minute, sep4,
+                                        &nc ) ) );
+            match = match ||
+               ( nc = 0, ( 5 == astSscanf( v, " %2d%*1[ ] %2d%1[ ] %2d%1[ ]%n",
+                                        &iday, &hour, sep3, &minute, sep4,
+                                        &nc ) ) );
+
+/* Alternative field separators are permitted above, but ensure that
+   they are both the same. */
+            match = match && ( *sep3 == *sep4 );
+
+/* If the day number was read as an integer, convert it to double. */
+            if ( match ) day = (double) iday;
+
+/* If no match, see if we can get a match without a trailing seconds field. */
+            if( !match ) {
+               match =
+                  ( nc = 0, ( 4 == astSscanf( v,
+                                        " %2d%*1[ ] %2d %1[:/-] %2d %n",
+                                        &iday, &hour, sep3, &minute, &nc ) &&
+                                        ( nc == l ) ) );
+               match = match ||
+                  ( nc = 0, ( 4 == astSscanf( v, " %2d%*1[ ] %2d%1[ ] %2d %n",
+                                        &iday, &hour, sep3, &minute, &nc ) &&
+                                        ( nc == l ) ) );
+
+/* If the day number was read as an integer, convert it to double. */
+               if ( match ) day = (double) iday;
+
+/* Otherwise, identify the seconds field. */
+/* -------------------------------------- */
+/* If hours and minutes fields have been matched, now look for the
+   final seconds (and fractions of seconds) field. This is similar to
+   the day/fraction field (see earlier) in that we first check that it
+   has the correct form before reading its value. */
+
+/* Adjust the string pointer and remaining string length. */
+            } else {
+               v += nc;
+               l -= nc;
+
+/* Try to match " 12.3456 " or similar. */
+               match =
+                  ( nc = 0, ( 0 == astSscanf( v,
+                                          " %*2[0123456789].%*[0123456789] %n",
+                                           &nc ) )
+                            && ( nc == l ) );
+
+/* If that failed, then try to match " 12. " or similar. */
+               match = match ||
+                  ( nc = 0, ( 0 == astSscanf( v, " %*2[0123456789]. %n", &nc ) )
+                            && ( nc == l ) );
+
+/* If that also failed, then try to match just " 12 " or similar. */
+               match = match ||
+                  ( nc = 0, ( 0 == astSscanf( v, " %*2[0123456789] %n", &nc ) )
+                            && ( nc == l ) );
+
+/* If any of the above patterns matched, now read the data (the number of
+   seconds) as a double value. */
+               if ( match ) {
+                  match = ( nc = 0, ( 1 == astSscanf( v, " %lf %n", &sec, &nc ) )
+                                    && ( nc == l ) );
+               }
+            }
+         }
+
+/* Free resources */
+         if( cc ) cc = astFree( cc );
+
+      }
+
+/* Interpret the values that were read. */
+/* ------------------------------------ */
+/* We execute this if all of the above text matching was successful,
+   transferred the required number of data values, and consumed the
+   entire input string. */
+      if ( match ) {
+
+/* See if the month was given as a character string (e.g. "Oct") instead of
+   a number. If so, define local variables for use in converting it. */
+         if ( *cmonth ) {
+            char lcmonth[ 4 ];      /* Lower case copy of month string */
+            const char *ptr;        /* Pointer result from look up */
+            const char *table =     /* Month look up table */
+                       "jan feb mar apr may jun jul aug sep oct nov dec";
+            int i;                  /* Loop counter for characters */
+
+/* Convert the month string to lower case. */
+            for ( i = 0; cmonth[ i ]; i++ ) {
+               lcmonth[ i ] = tolower( cmonth[ i ] );
+            }
+            lcmonth[ i ] = '\0';
+
+/* Look the month up in the table of months and generate the required month
+   number. */
+            if ( ( ptr = strstr( table, lcmonth ) ) ) {
+               month = 1 + ( ptr - table ) / 4;
+
+/* If the lookup failed, report an error. */
+   	    } else {
+               astError( AST__DTERR, "Month value \"%s\" is invalid.", status,
+                         cmonth );
+            }
+         }
+
+/* If OK, extract the integral day number and convert years, months and days
+   to a Modified Julian Date. */
+         if ( astOK ) {
+            iday = (int) day;
+            palCaldj( year, month, iday, &mjd, &stat );
+
+/* Examine the return status from the conversion and report an appropriate
+   error if necessary. */
+            switch ( stat ) {
+            case 1:
+               astError( AST__DTERR, "Year value (%d) is invalid.", status, year );
+               break;
+            case 2:
+               astError( AST__DTERR, "Month value (%d) is invalid.", status, month );
+               break;
+            case 3:
+               astError( AST__DTERR, "Day value (%.*g) is invalid.", status, DBL_DIG,
+                         day );
+               break;
+
+/* If conversion to MJD was successful, add any fractional part of a day to the
+   result. */
+            default:
+               mjd += ( day - (double) iday );
+
+/* Convert hours, minutes and seconds to a fraction of a day (this will give
+   zero if none of these quantities was supplied). */
+               palDtf2d( hour, minute, sec, &hms, &stat );
+
+/* Examine the return status from the conversion and report an appropriate
+   error if necessary. */
+               switch ( stat ) {
+               case 1:
+                  astError( AST__DTERR, "Hour value (%d) is invalid.", status, hour );
+                  break;
+               case 2:
+                  astError( AST__DTERR, "Minute value (%d) is invalid.", status,
+                            minute );
+                  break;
+               case 3:
+                  astError( AST__DTERR, "Seconds value (%.*g) is invalid.", status,
+                            DBL_DIG, sec );
+                  break;
+
+/* Add the fraction of a day derived from hours, minutes and seconds fields to
+   the result. */
+               default:
+                  mjd += hms;
+                  break;
+               }
+               break;
+            }
+
+/* Return the result, if no error occurred. */
+            if ( astOK ) result = mjd;
+         }
+
+/* If none of the supported date/time formats matched, then report an error. */
+      } else {
+         astError( AST__DTERR, "Date/time does not have the correct form." , status);
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void ReportPoints( AstMapping *this_mapping, int forward,
+                          AstPointSet *in_points, AstPointSet *out_points, int *status ) {
+/*
+*  Name:
+*     ReportPoints
+
+*  Purpose:
+*     Report the effect of transforming a set of points using a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void ReportPoints( AstMapping *this, int forward,
+*                        AstPointSet *in_points, AstPointSet *out_points, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the protected astReportPoints
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function reports the coordinates of a set of points before
+*     and after being transformed by a Frame, by writing them to
+*     standard output.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     forward
+*        A non-zero value indicates that the Frame's forward
+*        coordinate transformation has been applied, while a zero
+*        value indicates the inverse transformation.
+*     in_points
+*        Pointer to a PointSet which is associated with the
+*        coordinates of a set of points before the Frame was applied.
+*     out_points
+*        Pointer to a PointSet which is associated with the
+*        coordinates of the same set of points after the Frame has
+*        been applied.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *this;               /* Pointer to the Frame structure */
+   double **ptr_in;              /* Pointer to array of input data pointers */
+   double **ptr_out;             /* Pointer to array of output data pointers */
+   int coord;                    /* Loop counter for coordinates */
+   int ncoord_in;                /* Number of input coordinates per point */
+   int ncoord_out;               /* Number of output coordinates per point */
+   int npoint;                   /* Number of points to report */
+   int npoint_in;                /* Number of input points */
+   int npoint_out;               /* Number of output points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Frame structure. */
+   this = (AstFrame *) this_mapping;
+
+/* Obtain the numbers of points and coordinates associated with each
+   PointSet. */
+   npoint_in = astGetNpoint( in_points );
+   npoint_out = astGetNpoint( out_points );
+   ncoord_in = astGetNcoord( in_points );
+   ncoord_out = astGetNcoord( out_points );
+
+/* Obtain the pointers that give access to the coordinate data
+   associated with each PointSet. */
+   ptr_in = astGetPoints( in_points );
+   ptr_out = astGetPoints( out_points );
+
+/* In the event that both PointSets don't contain equal numbers of
+   points (this shouldn't actually happen), simply use the minimum
+   number. */
+   npoint = ( npoint_in < npoint_out ) ? npoint_in : npoint_out;
+
+/* Loop to report the effect of the transformation on each point in
+   turn. */
+   for ( point = 0; point < npoint; point++ ) {
+
+/* Report the input coordinates (in parentheses and separated by
+   commas). Format each value for display using the Frame's astFormat
+   method. */
+      printf( "(" );
+      for ( coord = 0; coord < ncoord_in; coord++ ) {
+         printf( "%s%s", coord ? ", " : "",
+                 astFormat( this, coord, ptr_in[ coord ][ point ] ) );
+      }
+
+/* Similarly report the output coordinates. */
+      printf( ") --> (" );
+      for ( coord = 0; coord < ncoord_out; coord++ ) {
+         printf( "%s%s", coord ? ", " : "",
+                 astFormat( this, coord, ptr_out[ coord ][ point ] ) );
+      }
+      printf( ")\n" );
+   }
+}
+
+static void Resolve( AstFrame *this, const double point1[],
+                       const double point2[], const double point3[],
+                       double point4[], double *d1, double *d2, int *status ){
+/*
+*++
+*  Name:
+c     astResolve
+f     AST_RESOLVE
+
+*  Purpose:
+*     Resolve a vector into two orthogonal components
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     void astResolve( AstFrame *this, const double point1[],
+c                      const double point2[], const double point3[],
+c                      double point4[], double *d1, double *d2 );
+f     CALL AST_RESOLVE( THIS, POINT1, POINT2, POINT3, POINT4, D1, D2,
+f                       STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function resolves a vector into two perpendicular components.
+f     This routine resolves a vector into two perpendicular components.
+*     The vector from point 1 to point 2 is used as the basis vector.
+*     The vector from point 1 to point 3 is resolved into components
+*     parallel and perpendicular to this basis vector. The lengths of the
+*     two components are returned, together with the position of closest
+*     aproach of the basis vector to point 3.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     point1
+f     POINT1( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). This marks the start of the basis vector,
+*        and of the vector to be resolved.
+c     point2
+f     POINT2( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the basis vector.
+c     point3
+f     POINT3( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the vector to be
+*        resolved.
+c     point4
+f     POINT4( * ) = DOUBLE PRECISION (Returned)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        in which the coordinates of the point of closest approach of the
+*        basis vector to point 3 will be returned.
+c     d1
+f     D1 = DOUBLE PRECISION (Returned)
+c        The address of a location at which to return the distance from
+f        The distance from
+*        point 1 to point 4 (that is, the length of the component parallel
+*        to the basis vector). Positive values are in the same sense as
+*        movement from point 1 to point 2.
+c     d2
+f     D2 = DOUBLE PRECISION (Returned)
+c        The address of a location at which to return the distance from
+f        The distance from
+*        point 4 to point 3 (that is, the length of the component
+*        perpendicular to the basis vector). The value is always positive.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+c     - Each vector used in this function is the path of
+f     - Each vector used in this routine is the path of
+*     shortest distance between two points, as defined by the
+c     astDistance function.
+f     AST_DISTANCE function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value, or if the required
+*     output values are undefined.
+*--
+*/
+
+/* Local Variables: */
+   double bv;                    /* Length of basis vector */
+   double c;                     /* Component length */
+   double dp;                    /* Dot product */
+   int axis;                     /* Loop counter for axes */
+   int naxes;                    /* Number of Frame axes */
+   int ok;                       /* OK to proceed? */
+
+/* Check the global error status. */
+   *d1 = AST__BAD;
+   *d2 = AST__BAD;
+   if ( !astOK ) return;
+
+/* Determine the number of Frame axes. */
+   naxes = astGetNaxes( this );
+
+/* Initialize bad values, and check if the supplied vectors are good. */
+   ok = 1;
+   for( axis = 0; axis < naxes; axis++ ){
+      point4[ axis ] = AST__BAD;
+      if( point1[ axis ] == AST__BAD ||
+          point2[ axis ] == AST__BAD ||
+          point3[ axis ] == AST__BAD ) ok = 0;
+   }
+
+/* Check the supplied values. */
+   if ( ok ) {
+
+/* Find the dot product of the basis vector with the vector joining point 1
+   and point 3. At the same time form the squared length of the basis
+   vector. */
+      dp = 0.0;
+      bv = 0.0;
+      for( axis = 0; axis < naxes; axis++ ){
+         c = point2[ axis ] - point1[ axis ];
+         dp += c * ( point3[ axis ] - point1[ axis ] );
+         bv += c * c;
+      }
+
+/* Check the basis vector does not have zero length, and convert the
+   squared length into a length. */
+      if( bv > 0.0 ) {
+         bv = sqrt( bv );
+
+/* The dot product is the required distance d1 multiplied by the length
+   of the basis vector. Form the distance d1. */
+         *d1 = dp/bv;
+
+/* Offset away from point 1 towards point 2 by a distance of d1. */
+         for( axis = 0; axis < naxes; axis++ ){
+            point4[ axis ] = point1[ axis ] +
+                             (*d1/bv)*( point2[ axis ] - point1[ axis ] );
+         }
+
+/* Finally, form the required length d2. */
+         *d2 = 0.0;
+         for( axis = 0; axis < naxes; axis++ ){
+            c = ( point3[ axis ] - point4[ axis ] );
+            *d2 += c*c;
+         }
+         *d2 = sqrt( *d2 );
+
+      }
+   }
+
+   return;
+
+}
+
+static AstPointSet *ResolvePoints( AstFrame *this, const double point1[],
+                                   const double point2[], AstPointSet *in,
+                                   AstPointSet *out, int *status ) {
+/*
+*+
+*  Name:
+*     astResolvePoints
+
+*  Purpose:
+*     Resolve a set of vectors into orthogonal components
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstPointSet *astResolvePoints( AstFrame *this, const double point1[],
+*                                    const double point2[], AstPointSet *in,
+*                                    AstPointSet *out )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function takes a Frame and a set of vectors encapsulated
+*     in a PointSet, and resolves each one into two orthogonal components,
+*     returning these two components in another PointSet.
+*
+*     This is exactly the same as the public astResolve method, except
+*     that this method allows many vectors to be processed in a single call,
+*     thus reducing the computational cost of overheads of many
+*     individual calls to astResolve.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the start of the basis vector,
+*        and of the vectors to be resolved.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the basis vector.
+*     in
+*        Pointer to the PointSet holding the ends of the vectors to be
+*        resolved.
+*     out
+*        Pointer to a PointSet which will hold the length of the two
+*        resolved components. A NULL value may also be given, in which
+*        case a new PointSet will be created by this function.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet. The first axis will
+*     hold the lengths of the vector components parallel to the basis vector.
+*     These values will be signed (positive values are in the same sense as
+*     movement from point 1 to point 2. The second axis will hold the lengths
+*     of the vector components perpendicular to the basis vector. These
+*     values will be signed only if the Frame is 2-dimensional, in which
+*     case a positive value indicates that rotation from the basis vector
+*     to the tested vector is in the same sense as rotation from the first
+*     to the second axis of the Frame.
+
+*  Notes:
+*     - The number of coordinate values per point in the input
+*     PointSet must match the number of axes in the supplied Frame.
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points and 2 coordinate values per point.
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*     - We assume flat geometry throughout this function. Other classes,
+*     (e.g. SkyFrame) will override this method using more appropriate
+*     geometry.
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   double **ptr_in;              /* Pointers to input axis values */
+   double **ptr_out;             /* Pointers to returned axis values */
+   double *basisv;               /* Pointer to array holding basis vector */
+   double *d1;                   /* Pointer to next parallel component value */
+   double *d2;                   /* Pointer to next perpendicular component value */
+   double *ip;                   /* Pointer to next input axis value */
+   double bv;                    /* Length of basis vector */
+   double c;                     /* Constant value */
+   double d;                     /* Component length */
+   double dp;                    /* Dot product */
+   double x1;                    /* First axis of basis vector */
+   double x2;                    /* First axis of test vector */
+   double y1;                    /* Second axis of basis vector */
+   double y2;                    /* Second axis of test vector */
+   int axis;                     /* Loop counter for axes */
+   int ipoint;                   /* Index of next point */
+   int nax;                      /* Number of Frame axes */
+   int ncoord_in;                /* Number of input PointSet coordinates */
+   int ncoord_out;               /* Number of coordinates in output PointSet */
+   int npoint;                   /* Number of points to transform */
+   int npoint_out;               /* Number of points in output PointSet */
+   int ok;                       /* OK to proceed? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain the number of axes in the Frame. */
+   nax = astGetNaxes( this );
+
+/* Obtain the number of input vectors to resolve and the number of coordinate
+   values per vector. */
+   npoint = astGetNpoint( in );
+   ncoord_in = astGetNcoord( in );
+
+/* If OK, check that the number of input coordinates matches the number
+   required by the Frame. Report an error if these numbers do not match. */
+   if ( astOK && ( ncoord_in != nax ) ) {
+      astError( AST__NCPIN, "astResolvePoints(%s): Bad number of coordinate "
+                "values (%d) in input %s.", status, astGetClass( this ), ncoord_in,
+                astGetClass( in ) );
+      astError( AST__NCPIN, "The %s given requires %d coordinate value(s) for "
+                "each input point.", status, astGetClass( this ), nax );
+   }
+
+/* If still OK, and a non-NULL pointer has been given for the output PointSet,
+   then obtain the number of points and number of coordinates per point for
+   this PointSet. */
+   if ( astOK && out ) {
+      npoint_out = astGetNpoint( out );
+      ncoord_out = astGetNcoord( out );
+
+/* Check that the dimensions of this PointSet are adequate to accommodate the
+   output coordinate values and report an error if they are not. */
+      if ( astOK ) {
+         if ( npoint_out < npoint ) {
+            astError( AST__NOPTS, "astResolvePoints(%s): Too few points (%d) in "
+                      "output %s.", status, astGetClass( this ), npoint_out,
+                      astGetClass( out ) );
+            astError( AST__NOPTS, "The %s needs space to hold %d transformed "
+                      "point(s).", status, astGetClass( this ), npoint );
+         } else if ( ncoord_out < 2 ) {
+            astError( AST__NOCTS, "astResolvePoints(%s): Too few coordinate "
+                      "values per point (%d) in output %s.", status,
+                      astGetClass( this ), ncoord_out, astGetClass( out ) );
+            astError( AST__NOCTS, "The %s supplied needs space to store 2 "
+                      "coordinate value(s) per transformed point.", status,
+                      astGetClass( this ) );
+         }
+      }
+   }
+
+/* If all the validation stages are passed successfully, and a NULL output
+   pointer was given, then create a new PointSet to encapsulate the output
+   coordinate data. */
+   if ( astOK ) {
+      if ( !out ) {
+         result = astPointSet( npoint, 2, "", status );
+
+/* Otherwise, use the PointSet supplied. */
+      } else {
+         result = out;
+      }
+   }
+
+/* Get pointers to the input and output axis values */
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Store points to the first two axis arrays in the returned PointSet. */
+   d1 = ptr_out[ 0 ];
+   d2 = ptr_out[ 1 ];
+
+/* Allocate work space. */
+   basisv = astMalloc( sizeof( double )*(size_t) nax );
+
+/* If the Frame has only one axis, then the supplied basic vector is
+   irrelevant - the returned perpendicular distances are always zero and
+   the returned parallel distances are just the distances from point1
+   to each input point. */
+   if( nax < 2 && basisv ) {
+      ip = ptr_in[ 0 ];
+      for( ipoint = 0; ipoint < npoint; ipoint++, d1++, d2++, ip++ ) {
+         *d1 = astAxDistance( this, 1, point1[0], *ip );
+         *d2 = 0.0;
+      }
+
+/* Now deal with Frames which have 2 or more axes */
+   } else if( basisv ){
+
+/* Check if the supplied positions defining the basis vector are good.
+   Store the basis vector, and get its squared length. */
+      ok = 1;
+      bv = 0.0;
+      for( axis = 0; axis < nax; axis++ ){
+         if( point1[ axis ] == AST__BAD ||
+             point2[ axis ] == AST__BAD ) {
+            ok = 0;
+            break;
+         } else {
+            basisv[ axis ] =  point2[ axis ] - point1[ axis ];
+            bv += basisv[ axis ]*basisv[ axis ];
+         }
+      }
+
+/* Check the basis vector does not have zero length, and convert the
+   squared length into a length. */
+      if( ok && bv > 0.0 ) {
+         bv = sqrt( bv );
+      } else {
+         ok = 0;
+      }
+
+/* Store points to the first two axis arrays in the returned PointSet. */
+      d1 = ptr_out[ 0 ];
+      d2 = ptr_out[ 1 ];
+
+/* Check supplied values can be used */
+      if( ok ) {
+
+/* Loop round each supplied vector. */
+         for( ipoint = 0; ipoint < npoint; ipoint++, d1++, d2++ ) {
+
+/* Find the dot product of the basis vector with the vector joining point 1
+   and the end of the current vector. */
+            ok = 1;
+            dp = 0.0;
+            for( axis = 0; axis < nax; axis++ ){
+               d = ptr_in[ axis ][ ipoint ] - point1[ axis ];
+               if( d != AST__BAD ) {
+                  dp += basisv[ axis ] * d;
+               } else {
+                  ok = 0;
+                  break;
+               }
+            }
+
+/* If this input position is good... */
+            if( ok ) {
+
+/* The dot product is the required parallel component length multiplied by the
+   length of the basis vector. Form the distance d1. */
+               *d1 = dp/bv;
+
+/* Offset away from point 1 towards point 2 by a distance of d1, and form the
+   required length d2. */
+               c = *d1/bv;
+               if( nax > 2 ) {
+                  *d2 = 0.0;
+                  for( axis = 0; axis < nax; axis++ ){
+                     d = ptr_in[ axis ][ ipoint ] -
+                         ( point1[ axis ] + c*basisv[ axis ] );
+                     *d2 += d*d;
+                  }
+                  *d2 = sqrt( *d2 );
+
+/* If the Frame is 2 dimensional, we can give a sign the the perpendicular
+   component. */
+               } else {
+                  x1 = c*basisv[ 0 ];
+                  y1 = c*basisv[ 1 ];
+                  x2 = ptr_in[ 0 ][ ipoint ] - ( point1[ 0 ] + x1 );
+                  y2 = ptr_in[ 1 ][ ipoint ] - ( point1[ 1 ] + y1 );
+                  *d2 = sqrt( x2*x2 + y2*y2 );
+                  if( x1*y2 - x2*y1 < 0.0 ) *d2 = -(*d2);
+               }
+
+/* If this input vector is bad, put bad values in the output */
+            } else {
+               *d1 = AST__BAD;
+               *d2 = AST__BAD;
+            }
+         }
+
+/* If supplied values cannot be used, fill the returned PointSet with bad
+   values */
+      } else {
+         for( ipoint = 0; ipoint < npoint; ipoint++, d1++, d2++ ) {
+            *d1 = AST__BAD;
+            *d2 = AST__BAD;
+         }
+      }
+   }
+
+/* Free resources */
+   basisv = astFree( basisv );
+
+/* Annul the returned PointSet if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static void SetActiveUnit( AstFrame *this, int value, int *status ){
+/*
+*++
+*  Name:
+c     astSetActiveUnit
+f     AST_SETACTIVEUNIT
+
+*  Purpose:
+*     Specify how the Unit attribute should be used.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     void astSetActiveUnit( AstFrame *this, int value )
+f     CALL AST_SETACTIVEUNIT( THIS, VALUE, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function
+f     This routine
+*     sets the current value of the ActiveUnit flag for a Frame, which
+*     controls how the Frame behaves when it is used (by
+c     astFindFrame or astConvert)
+f     AST_FINDFRAME or AST_CONVERT)
+*     to match another Frame. If the ActiveUnit flag is set in both
+*     template and target Frames then the returned Mapping takes into account
+*     any differences in axis units. The default value for simple Frames is
+*     zero, which preserves the behaviour of versions of AST prior to
+*     version 2.0.
+*
+*     If the ActiveUnit flag of either Frame is
+c     zero,
+f     .FALSE.,
+*     then the Mapping will ignore any difference in the Unit attributes of
+*     corresponding template and target axes. In this mode, the Unit
+*     attributes are purely descriptive commentary for the benefit of
+*     human readers and do not influence the Mappings between Frames.
+*     This is the behaviour which all Frames had in older version of AST,
+*     prior to the introduction of this attribute.
+*
+*     If the ActiveUnit flag of both Frames is
+c     non-zero,
+f     .TRUE.,
+*     then the Mapping from template to target will take account of any
+*     difference in the axis Unit attributes, where-ever possible. For
+*     instance, if corresponding target and template axes have Unit strings of
+*     "km" and "m", then the FrameSet class will use a ZoomMap to connect
+*     them which introduces a scaling of 1000. If no Mapping can be found
+*     between the corresponding units string, then an error is reported.
+*     In this mode, it is assumed that values of the Unit attribute conform
+*     to the syntax for units strings described in the FITS WCS Paper I
+*     "Representations of world coordinates in FITS" (Greisen & Calabretta).
+*     Particularly, any of the named unit symbols, functions, operators or
+*     standard multiplier prefixes listed within that paper can be used within
+*     a units string. A units string may contain symbols for unit which are
+*     not listed in the FITS paper, but transformation to any other units
+*     will then not be possible (except to units which depend only on the
+*     same unknown units - thus "flops" can be transformed to "Mflops"
+*     even though "flops" is not a standard FITS unit symbol).
+*
+*     A range of common non-standard variations of unit names and multiplier
+*     prefixes are also allowed, such as adding an "s" to the end of Angstrom,
+*     using a lower case "a" at the start of "angstrom", "micron" instead of
+*     "um", "sec" instead of "s", etc.
+*
+c     If the ActiveUnit flag is non-zero, setting a new Unit value for an
+f     If the ActiveUnit flag is .TRUE., setting a new Unit value for an
+*     axis may also change its Label and Symbol attributes. For instance, if
+*     an axis has Unit "Hz" and Label "frequency", then changing its Unit to
+*     "log(Hz)" will change its Label to "log( frequency )". In addition,
+*     the Axis Format attribute will be cleared when-ever a new value
+*     is assigned to the Unit attribute.
+*
+c     Note, if a non-zero value is set for the ActiveUnit flag, then changing a
+f     Note, if a .TRUE. value is set for the ActiveUnit flag, then changing a
+*     Unit value for the current Frame within a FrameSet will result in the
+*     Frame being re-mapped (that is, the Mappings which define the
+*     relationships between Frames within the FrameSet will be modified to
+*     take into account the change in Units).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     value
+f     VALUE = LOGICAL (Given)
+*        The new value to use.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     SkyFrame
+c        The ActiveUnit flag for a SkyFrame is always 0 (any value
+c        supplied using this function is ignored).
+f        The ActiveUnit flag for a SkyFrame is always .FALSE. (any value
+f        supplied using this routine is ignored).
+*     SpecFrame
+c        The ActiveUnit flag for a SpecFrame is always 1 (any value
+c        supplied using this function is ignored).
+f        The ActiveUnit flag for a SpecFrame is always .TRUE. (any value
+f        supplied using this routine is ignored).
+*     FluxFrame
+c        The ActiveUnit flag for a FluxFrame is always 1 (any value
+c        supplied using this function is ignored).
+f        The ActiveUnit flag for a FluxFrame is always .TRUE. (any value
+f        supplied using this routine is ignored).
+*     CmpFrame
+c        The default ActiveUnit flag for a CmpFrame is 1 if both of the
+c        component Frames are using active units, and zero otherwise. When
+f        The default ActiveUnit flag for a CmpFrame is .TRUE. if both of the
+f        component Frames are using active units, and .FALSE. otherwise. When
+*        a new value is set for the ActiveUnit flag, the flag value
+*        is propagated to the component Frames. This change will be
+*        reflected through all references to the component Frames, not
+*        just those encapsulated within the CmpFrame.
+*     Region:
+*        Regions always use active units if possible.
+
+*  Notes:
+*     - The ActiveUnit flag resembles a Frame attribute, except that it
+*     cannot be tested or cleared, and it cannot be accessed using the
+c     generic astGet<X> and astSet<X> functions.
+f     generic AST_GET<X> and AST_SET<X> routines.
+c     - The astGetActiveUnit function can be used to retrieve the current
+f     - The AST_GETACTIVEUNIT routine can be used to retrieve the current
+*     value of the ActiveUnit flag.
+
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store a value of 1 for the Frame component if the supplied value is
+   non-zero. */
+   this->active_unit = ( value ) ? 1 : 0;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the astSetAttrib method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a Frame, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*/
+
+/* Local Vaiables: */
+   AstAxis *ax;                  /* Pointer to Axis */
+   AstFrame *pfrm;               /* Pointer to primary Frame containing axis */
+   AstFrame *this;               /* Pointer to the Frame structure */
+   AstSystemType system_code;    /* System code */
+   char pfrm_attrib[ 100 ];      /* Primary Frame attribute */
+   char *pfrm_setting;           /* Primary Frame attribute */
+   char *axis_setting;           /* Pointer to axis attribute setting string */
+   const char *equals;           /* Pointer to equals sign */
+   const char *old_setting;      /* Pointer to supplied setting string */
+   const char *op;               /* Pointer to opening parenthesis */
+   double dval;                  /* Double attibute value */
+   double mjd;                   /* Epoch as a Modified Julian Date */
+   int axis;                     /* Index for the Frame axis */
+   int axis_nc;                  /* No. characters in axis attribute name */
+   int axis_value;               /* Offset of value to be assigned to axis */
+   int digits;                   /* Number of digits of precision */
+   int direction;                /* Axis direction flag */
+   int domain;                   /* Offset of Domain string */
+   int epoch;                    /* Offset of Epoch string */
+   int format;                   /* Offset of axis Format string */
+   int free_axis_setting;        /* Should axis_setting be freed? */
+   int has_axis;                 /* Does setting include an axis specifier? */
+   int ival;                     /* Integer attribute value */
+   int label;                    /* Offset of axis Label string */
+   int len;                      /* Length of setting string */
+   int match_end;                /* Match final axes of target? */
+   int max_axes;                 /* Maximum number of axes matched */
+   int min_axes;                 /* Minimum number of axes matched */
+   int nc;                       /* Number of characters read by astSscanf */
+   int off2;                     /* Modified offset of attribute value */
+   int off;                      /* Offset of attribute value */
+   int oldrep;                   /* Original error reporting state */
+   int paxis;                    /* Axis index within primary frame */
+   int permute;                  /* Permute axes in order to match? */
+   int preserve_axes;            /* Preserve matched target axes? */
+   int sign;                     /* Sign of longitude value */
+   int symbol;                   /* Offset of axis Symbol string */
+   int system;                   /* Offset of System string */
+   int title;                    /* Offset of Title string */
+   int unit;                     /* Offset of axis Unit string */
+   int used;                     /* Could the setting string be used? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Frame structure. */
+   this = (AstFrame *) this_object;
+
+/* Find the offset to the first equal sign in the setting string. */
+   equals = strchr( setting, '=' );
+
+/* Set a flag indicating if the attribute name includes an axis
+   specifier. */
+   op = strchr( setting, '(' );
+   has_axis = ( !op || op > equals ) ? 0 : 1;
+
+/* A flag indicating that we do not need to free the axis_setting memory. */
+   free_axis_setting = 0;
+
+/* Initialise things to avoid compiler warnings. */
+   axis_setting = NULL;
+   old_setting = NULL;
+
+/* Jump back to here if we are trying the same attribute setting but with
+   an explicit axis "(1)" added to the attribute name. */
+L1:
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* Digits. */
+/* ------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "digits= %d %n", &digits, &nc ) )
+        && ( nc >= len ) ) {
+      astSetDigits( this, digits );
+
+/* Digits(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "digits(%d)= %d %n",
+                              &axis, &digits, &nc ) )
+               && ( nc >= len ) ) {
+
+/* There is no function to set the Digits attribute value for an axis
+   directly, so obtain a pointer to the Axis and use this to set the
+   attribute. */
+      (void) astValidateAxis( this, axis - 1, 1, "astSetDigits(axis)" );
+      ax = astGetAxis( this, axis - 1 );
+      astSetAxisDigits( ax, digits );
+      ax = astAnnul( ax );
+
+/* Direction(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "direction(%d)= %d %n",
+                              &axis, &direction, &nc ) )
+               && ( nc >= len ) ) {
+      astSetDirection( this, axis - 1, direction );
+
+/* Epoch. */
+/* ------ */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "epoch=%n%*[^\n]%n", &epoch, &nc ) )
+        && ( nc >= len ) ) {
+
+/* Convert the Epoch value to a Modified Julian Date before use. */
+      mjd = astReadDateTime( setting + epoch );
+      if ( astOK ) {
+         astSetEpoch( this, mjd );
+
+/* Report contextual information if the conversion failed. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid epoch value "
+                   "\"%s\" given for coordinate system.", status,
+                   astGetClass( this ), setting + epoch );
+      }
+
+/* Top(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "top(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetTop( this, axis - 1, dval );
+
+/* Bottom(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "bottom(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetBottom( this, axis - 1, dval );
+
+/* Domain. */
+/* ------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "domain=%n%*[^\n]%n", &domain, &nc ) )
+               && ( nc >= len ) ) {
+      astSetDomain( this, setting + domain );
+
+/* Format(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "format(%d)=%n%*[^\n]%n",
+                              &axis, &format, &nc ) )
+               && ( nc >= len ) ) {
+      astSetFormat( this, axis - 1, setting + format );
+
+/* Label(axis). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "label(%d)=%n%*[^\n]%n",
+                              &axis, &label, &nc ) )
+               && ( nc >= len ) ) {
+      astSetLabel( this, axis - 1, setting + label );
+
+/* MatchEnd. */
+/* --------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "matchend= %d %n", &match_end, &nc ) )
+               && ( nc >= len ) ) {
+      astSetMatchEnd( this, match_end );
+
+/* MaxAxes. */
+/* -------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "maxaxes= %d %n", &max_axes, &nc ) )
+               && ( nc >= len ) ) {
+      astSetMaxAxes( this, max_axes );
+
+/* MinAxes. */
+/* -------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "minaxes= %d %n", &min_axes, &nc ) )
+               && ( nc >= len ) ) {
+      astSetMinAxes( this, min_axes );
+
+/* Permute. */
+/* -------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "permute= %d %n", &permute, &nc ) )
+               && ( nc >= len ) ) {
+      astSetPermute( this, permute );
+
+/* PreserveAxes. */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "preserveaxes= %d %n",
+                              &preserve_axes, &nc ) )
+               && ( nc >= len ) ) {
+      astSetPreserveAxes( this, preserve_axes );
+
+/* Symbol(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "symbol(%d)=%n%*[^\n]%n",
+                              &axis, &symbol, &nc ) )
+               && ( nc >= len ) ) {
+      astSetSymbol( this, axis - 1, setting + symbol );
+
+/* AlignSystem. */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "alignsystem= %n%*s %n", &system, &nc ) )
+               && ( nc >= len ) ) {
+
+/* Convert the string to a System code before use. */
+      system_code = astSystemCode( this, system + setting );
+      if ( system_code != AST__BADSYSTEM ) {
+         astSetAlignSystem( this, system_code );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN,
+                   "astSetAttrib(%s): Invalid AlignSystem description \"%s\".", status,
+                   astGetClass( this ), system + setting );
+      }
+
+/* System. */
+/* ------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "system= %n%*s %n", &system, &nc ) )
+               && ( nc >= len ) ) {
+
+/* Convert the string to a System code before use. */
+      system_code = astSystemCode( this, system + setting );
+      if ( system_code != AST__BADSYSTEM ) {
+         astSetSystem( this, system_code );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN,
+                   "astSetAttrib(%s): Invalid System description \"%s\".", status,
+                   astGetClass( this ), system + setting );
+      }
+
+/* Title. */
+/* ------ */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "title=%n%*[^\n]%n", &title, &nc ) )
+               && ( nc >= len ) ) {
+      astSetTitle( this, setting + title );
+
+/* Unit(axis). */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "unit(%d)=%n%*[^\n]%n",
+                              &axis, &unit, &nc ) )
+               & ( nc >= len ) ) {
+      astSetUnit( this, axis - 1, setting + unit );
+
+/* ObsLat. */
+/* ------- */
+   } else if ( nc = 0,
+              ( 0 == astSscanf( setting, "obslat=%n%*s %n", &off, &nc ) )
+              && ( nc >= 7 ) ) {
+
+/* If the first character in the value string is "N" or "S", remember the
+   sign of the value and skip over the sign character. Default is north
+   (+ve). */
+      off2 = off;
+      if( setting[ off ] == 'N' || setting[ off ] == 'n' ) {
+         off2++;
+         sign = +1;
+      } else if( setting[ off ] == 'S' || setting[ off ] == 's' ) {
+         off2++;
+         sign = -1;
+      } else {
+         sign = +1;
+      }
+
+/* If not already created, create an FK5 J2000 SkyFrame which will be used
+   for formatting and unformatting ObsLon and ObsLat values. */
+      if( !skyframe ) {
+         astBeginPM;
+         skyframe = astSkyFrame( "system=FK5,equinox=J2000,format(2)=dms.2", status );
+         astEndPM;
+      }
+
+/* Convert the string to a radians value before use. */
+      ival = astUnformat( skyframe, 1, setting + off2, &dval );
+      if ( ival == astChrLen( setting ) - off2  ) {
+         astSetObsLat( this, dval*sign );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid value for "
+                   "ObsLat (observers latitude) \"%s\".", status, astGetClass( this ),
+                   setting + off );
+      }
+
+/* ObsLon. */
+/* ------- */
+   } else if ( nc = 0,
+              ( 0 == astSscanf( setting, "obslon=%n%*s %n", &off, &nc ) )
+              && ( nc >= 7 ) ) {
+
+/* If the first character in the value string is "E" or "W", remember the
+   sign of the value and skip over the sign character. Default is east
+   (+ve). */
+      off2 = off;
+      if( setting[ off ] == 'E' || setting[ off ] == 'e' ) {
+         off2++;
+         sign = +1;
+      } else if( setting[ off ] == 'W' || setting[ off ] == 'w' ) {
+         off2++;
+         sign = -1;
+      } else {
+         sign = +1;
+      }
+
+/* If not already created, create an FK5 J2000 SkyFrame which will be used
+   for formatting and unformatting ObsLon and ObsLat values. */
+      if( !skyframe ) {
+         astBeginPM;
+         skyframe = astSkyFrame( "system=FK5,equinox=J2000,format(2)=dms.2", status );
+         astEndPM;
+      }
+
+/* Convert the string to a radians value before use. */
+      ival = astUnformat( skyframe, 1, setting + off2, &dval );
+      if ( ival == astChrLen( setting ) - off2  ) {
+         astSetObsLon( this, dval*sign );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid value for "
+                   "ObsLon (observers longitude) \"%s\".", status, astGetClass( this ),
+                   setting + off );
+      }
+
+/* ObsAlt. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "obsalt= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetObsAlt( this, dval );
+
+/* Dut1. */
+/* ---- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "dut1= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetDut1( this, dval );
+
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* Use this macro to report an error if a read-only attribute has been
+   specified. */
+   } else if ( MATCH( "naxes" ) ||
+               !strncmp( setting, "normunit", 8 ) ||
+               !strncmp( setting, "internalunit", 12 ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Other axis attributes. */
+/* ---------------------- */
+/* If the attribute was not identified above, but appears to refer to
+   a Frame axis, then it may refer to an Axis object of a derived type
+   (which has additional attributes not recognised here). */
+   } else if ( !free_axis_setting && ( nc = 0,
+               ( 1 == astSscanf( setting, "%*[^()=]%n(%d)%n=%*[^\n]%n",
+                                       &axis_nc, &axis, &axis_value, &nc ) )
+               && ( nc >= len ) ) ) {
+
+/* Validate the axis index and copy the attribute setting string. */
+      (void) astValidateAxis( this, axis - 1, 1, "astSet" );
+      axis_setting = astString( setting, len );
+      if ( astOK ) {
+
+/* Over-write the axis index in the copy with the value to be
+   assigned. */
+         (void) strcpy( axis_setting + axis_nc, setting + axis_value );
+
+/* Obtain a pointer to the Axis object. */
+         ax = astGetAxis( this, axis - 1 );
+         if( astOK ) {
+
+/* Assume that we will be able to use the setting. */
+            used = 1;
+
+/* Temporarily switch off error reporting so that if the following attempt
+   to access the axis attribute fails, we can try to interpret the
+   attribute name as an attribute of the primary Frame containing the
+   specified axis. Any errors reported in this context will simply be
+   ignored, in particularly they are not deferred for later delivery. */
+            oldrep = astReporting( 0 );
+
+/* Use the Axis astSetAttrib method
+   to set the value. */
+            astSetAttrib( ax, axis_setting );
+
+/* If the above call failed with a status of AST__BADAT, indicating that
+   the attribute name was not recognised, clear the status so that we can
+   try to interpret the attribute name as an attribute of the primary Frame
+   containing the specified axis. */
+            if( astStatus == AST__BADAT ) {
+               astClearStatus;
+
+/* Find the primary Frame containing the specified axis. */
+               astPrimaryFrame( this, axis - 1, &pfrm, &paxis );
+
+/* Only attempt to use the primary Frame if it is not the same as "this"
+   - otherwise we could end up in an infinite loop. */
+               if( pfrm != this ) {
+
+/* astPrimaryFrame returns the original - unpermuted - axis index within
+   the primary Frame. So we need to take into account any axis permutation
+   which has been applied to the primary Frame when forming the attribute name
+   to use below. Find the permuted (external) axis index which corresponds to
+   the internal (unpermuted) axis index "paxis". */
+                  paxis = astValidateAxis( pfrm, paxis, 0, "astSet" );
+
+/* Modify the attribute name to refer to the axis numbering of the
+   primary frame. */
+                  sprintf( pfrm_attrib, "%.*s(%d)", axis_nc, setting, paxis + 1 );
+
+/* Create a setting string in which the attribute name refers to the axis
+   numbering of the primary frame. */
+                  pfrm_setting = NULL;
+                  nc = 0;
+                  pfrm_setting = astAppendString( pfrm_setting, &nc, pfrm_attrib );
+                  pfrm_setting = astAppendString( pfrm_setting, &nc, setting + axis_value );
+
+/* Attempt to set the attribute within the primary Frame. */
+                  astSetAttrib( pfrm, pfrm_setting );
+
+/* Free the memory. */
+                  pfrm_setting = astFree( pfrm_setting );
+
+/* If this failed, clear the status and indicate that we have not managed to
+   use the attribute setting. */
+                  if( !astOK ) {
+                     astClearStatus;
+                     used = 0;
+                  }
+
+               } else {
+                  used = 0;
+               }
+
+/* If not found attempt to set the attribute value in the Axis, omitting
+   the axis index. */
+               if( ! used ) {
+                  astSetAttrib( pfrm, axis_setting );
+                  if( !astOK ) {
+                     astClearStatus;
+                  } else {
+                     used = 1;
+                  }
+               }
+
+/* Free the setting string, and annul the primary Frame pointer. */
+               pfrm = astAnnul( pfrm );
+            }
+
+/* Re-instate the original error reporting state. */
+            astReporting( oldrep );
+
+/* If we could not use the setting, attempt to set the axis attribute again,
+   this time retaining the error report. This is done to ensure the user
+   gets an appropriate error message. */
+            if( !used ) astSetAttrib( ax, axis_setting );
+         }
+
+/* Annul the Axis pointer and free the memory holding the attribute
+   setting. */
+         ax = astAnnul( ax );
+      }
+      axis_setting = astFree( axis_setting );
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is still not recognised, and the Frame has only 1 axis,
+   and the attribute name does not already include an axis specifier, try
+   again after appending "(1)" to the end of the attribute name. */
+   } else if( !has_axis && astGetNaxes( this ) == 1 && equals ) {
+
+/* Take a copy of the supplied setting, allowing 3 extra characters for the
+   axis specifier "(1)". */
+      axis_setting = astMalloc( len + 4 );
+      if( axis_setting ) memcpy( axis_setting, setting, len );
+
+/* Indicate we should free the axis_setting memory. */
+      free_axis_setting = 1;
+
+/* Add in the axis specifier. */
+      strcpy( axis_setting + ( equals - setting ), "(1)" );
+
+/* Add in the equals sign and attribute value. */
+      strcpy( axis_setting + ( equals - setting ) + 3, equals );
+
+/* Use the new setting instead of the supplied setting. */
+      old_setting = setting;
+      setting = axis_setting;
+
+/* Indicate the setting now has an axis specifier. */
+      has_axis = 1;
+
+/* Jump back to try interpreting the new setting string. */
+      goto L1;
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. First re-instate the original setting
+   string if it was changed above. */
+   } else {
+      if( free_axis_setting ) {
+         setting = old_setting;
+         axis_setting = astFree( axis_setting );
+         free_axis_setting = 0;
+      }
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+   if( free_axis_setting ) axis_setting = astFree( axis_setting );
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+static void SetAxis( AstFrame *this, int axis, AstAxis *newaxis, int *status ) {
+/*
+*+
+*  Name:
+*     astSetAxis
+
+*  Purpose:
+*     Set a new Axis for a Frame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astSetAxis( AstFrame *this, int axis, AstAxis *newaxis )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function allows a new Axis object to be associated with one
+*     of the axes of a Frame, replacing the previous one. Each Axis
+*     object contains a description of the quantity represented along
+*     one of the Frame's axes, so this function allows this
+*     description to be exchanged for another one.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The index (zero-based) of the axis whose associated Axis object is to
+*        be replaced.
+*     newaxis
+*        Pointer to the new Axis object.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate and permute the axis index supplied. */
+   axis = astValidateAxis( this, axis, 1, "astSetAxis" );
+
+/* If OK, annul the Frame's pointer to the old Axis object and clone a pointer
+   to the new one to replace it. */
+   if ( astOK ) {
+      this->axis[ axis ] = astAnnul( this->axis[ axis ] );
+      this->axis[ axis ] = astClone( newaxis );
+   }
+}
+
+static void SetFrameFlags( AstFrame *this, int flags, int *status ){
+/*
+*+
+*  Name:
+*     astSetFrameFlags
+
+*  Purpose:
+*     Store a new bit mask of flags in a Frame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astSetFrameFlags( astFrame *this, int flags )
+
+*  Class Membership:
+*     Frame member function.
+
+*  Description:
+*     This function stores a new set of flags in a Frame. The flags can
+*     be retrieved using astGetFrameFlags.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     flags
+*        A bit mask holding the flags. Currently, the following bits are
+*        used:
+*
+*        0 - Used to indicate if the Frame is currently involved in an
+*        attempt to restore the integrity of a FrameSet following
+*        changes to the attribute values of the Frame.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Assign the new bit mask. */
+   this->flags = flags;
+}
+
+static void SetFrameVariants( AstFrame *this, AstFrameSet *variants, int *status ){
+/*
+*+
+*  Name:
+*     astSetFrameVariants
+
+*  Purpose:
+*     Store a FrameSet holding alternative Frame properties.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astSetVariants( AstFrame *this, AstFrameSet *variants )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function adds sets of alternative Frame properties to a Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     variants
+*        Pointer to a FrameSet in which each Frame is of the same class
+*        and dimensionality as "this" and all Frames have unique Domain
+*        names.
+
+*  Notes:
+*     - A clone of the supplied FrameSet pointer is stored in the Frame.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Annul any variants FrameSet already stored in the Frame. */
+   if( this->variants ) this->variants = astAnnul( this->variants );
+
+/* Store a clone of ht esupplied FrameSet pointer. */
+   if( variants ) this->variants = astClone( variants );
+
+}
+
+static void SetUnit( AstFrame *this, int axis, const char *unit, int *status ) {
+/*
+*  Name:
+*     SetUnit
+
+*  Purpose:
+*     Set a value for the Unit attribute of a Frame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void SetUnit( AstFrame *this, int axis, const char *unit, int *status )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function sets the Unit value for a Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the axis (zero-based) for which the Unit value is to
+*        be set.
+*     unit
+*        The new value to be set.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void.
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   char *c;                      /* Copy of supplied string */
+   const char *oldunit;          /* Pointer to old units string */
+   int l;                        /* Used length of supplied string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a copy of the supplied string which excludes trailing spaces. */
+   l = astChrLen( unit );
+   c = astStore( NULL, unit, (size_t) (l + 1) );
+   if( astOK ) {
+      c[ l ] = 0;
+
+/* Validate the axis index and obtain a pointer to the required Axis. */
+   (void) astValidateAxis( this, axis, 1, "astSetUnit" );
+      ax = astGetAxis( this, axis );
+
+/* The new unit may require the Label and/or Symbol to be changed, but
+   only if the Frames ActiveUnit flag is set. */
+      if( astGetActiveUnit( this ) ) {
+
+/* Get the existing Axis unit, using the astGetUnit method (rather than
+   astGetAxisUnit) in order to get any default value in the case where
+   the Unit attribute is not set. */
+         oldunit = astGetUnit( this, axis );
+
+/* Assign the new Unit value. This modifies labels and/or Symbols if
+   necessary. */
+         NewUnit( ax, oldunit, c, "astSetUnit", astGetClass( this ), status );
+      }
+
+/* Set the Axis Unit attribute value. */
+      astSetAxisUnit( ax, c );
+
+/* Annul the Axis pointer. */
+      ax = astAnnul( ax );
+   }
+
+/* Free the string copy */
+   c = astFree( c );
+
+}
+
+static int SubFrame( AstFrame *target, AstFrame *template,
+                     int result_naxes, const int *target_axes,
+                     const int *template_axes, AstMapping **map,
+                     AstFrame **result, int *status ) {
+/*
+*+
+*  Name:
+*     astSubFrame
+
+*  Purpose:
+*     Select axes from a Frame and convert to the new coordinate system.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astSubFrame( AstFrame *target, AstFrame *template,
+*                      int result_naxes, const int *target_axes,
+*                      const int *template_axes, AstMapping **map,
+*                      AstFrame **result )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function selects a requested sub-set (or super-set) of the axes from
+*     a "target" Frame and creates a new Frame with copies of the selected
+*     axes assembled in the requested order. It then optionally overlays the
+*     attributes of a "template" Frame on to the result. It returns both the
+*     resulting Frame and a Mapping that describes how to convert between the
+*     coordinate systems described by the target and result Frames. If
+*     necessary, this Mapping takes account of any differences in the Frames'
+*     attributes due to the influence of the template.
+
+*  Parameters:
+*     target
+*        Pointer to the target Frame, from which axes are to be selected.
+*     template
+*        Pointer to the template Frame, from which new attributes for the
+*        result Frame are to be obtained. Optionally, this may be NULL, in
+*        which case no overlaying of template attributes will be performed.
+*     result_naxes
+*        Number of axes to be selected from the target Frame. This number may
+*        be greater than or less than the number of axes in this Frame (or
+*        equal).
+*     target_axes
+*        Pointer to an array of int with result_naxes elements, giving a list
+*        of the (zero-based) axis indices of the axes to be selected from the
+*        target Frame. The order in which these are given determines the order
+*        in which the axes appear in the result Frame. If any of the values in
+*        this array is set to -1, the corresponding result axis will not be
+*        derived from the target Frame, but will be assigned default attributes
+*        instead.
+*     template_axes
+*        Pointer to an array of int with result_naxes elements. This should
+*        contain a list of the template axes (given as zero-based axis indices)
+*        with which the axes of the result Frame are to be associated. This
+*        array determines which axes are used when overlaying axis-dependent
+*        attributes of the template on to the result. If any element of this
+*        array is set to -1, the corresponding result axis will not receive any
+*        template attributes.
+*
+*        If the template argument is given as NULL, this array is not used and
+*        a NULL pointer may also be supplied here.
+*     map
+*        Address of a location to receive a pointer to the returned Mapping.
+*        The forward transformation of this Mapping will describe how to
+*        convert coordinates from the coordinate system described by the target
+*        Frame to that described by the result Frame. The inverse
+*        transformation will convert in the opposite direction.
+*     result
+*        Address of a location to receive a pointer to the result Frame.
+
+*  Returned Value:
+*     A non-zero value is returned if coordinate conversion is
+*     possible between the target and the result Frame. Otherwise zero
+*     is returned and *map and *result are returned as NULL (but this
+*     will not in itself result in an error condition). In general,
+*     coordinate conversion should always be possible if no template
+*     Frame is supplied but may not always be possible otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+
+*  Implementation Deficiencies:
+*     - Any axis selection is currently permitted. Probably this
+*     should be restricted so that each axis can only be selected
+*     once. The astValidateAxisSelection method will do this but
+*     currently there are bugs in the CmpFrame class that cause axis
+*     selections which will not pass this test. Install the validation
+*     when these are fixed.
+*-
+
+*  Implementation Notes:
+*     - This implementation addresses the selection of axes from a
+*     Frame class object. This simply results in another object of the
+*     same class and a Mapping which describes an axis permutation (or
+*     a unit Mapping as a special case). Changes of Frame attributes
+*     have no significance for coordinate values in this class, so do
+*     not affect the Mapping returned.
+*/
+
+/* Local Variables: */
+   AstAxis *newaxis;             /* Pointer to new Axis object */
+   AstFrame *tempframe;          /* Pointer to temporary Frame */
+   AstMapping *aumap;            /* A units Mapping for a single axis */
+   AstMapping *numap;            /* The new total units Mapping */
+   AstMapping *umap;             /* The total units Mapping */
+   int *inperm;                  /* Pointer to permutation array */
+   int *outperm;                 /* Pointer to permutation array */
+   int match;                    /* Coordinate conversion possible? */
+   int result_axis;              /* Result Frame axis index */
+   int target_axis;              /* Target Frame axis index */
+   int target_naxes;             /* Number of target Frame axes */
+   int unit;                     /* Unit Mapping appropriate? */
+   int uunit;                    /* Is the "umap" Mapping a UnitMap? */
+
+/* Initialise the returned values. */
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain the number of target Frame axes. */
+   target_naxes = astGetNaxes( target );
+
+/* Ensure we do not attempt to use a negative number of result axes. */
+   if ( result_naxes < 0 ) result_naxes = 0;
+
+/* Create a temporary new Frame with the required number of axes. This will
+   have a default Axis object associated with each of its axes. We will
+   replace these where necessary with copies of the actual Axis objects we
+   require. */
+   tempframe = astFrame( result_naxes, "", status );
+
+/* Allocate memory to store two permutation arrays. These will be used to
+   construct the Mapping that relates the target and result Frames. */
+   inperm = astMalloc( sizeof( int ) * (size_t) target_naxes );
+   outperm = astMalloc( sizeof( int ) * (size_t) result_naxes );
+   if ( astOK ) {
+
+/* Initialise the array that associates each target axis with the corresponding
+   result axis (filling it with the value -1 initially signifies no
+   associations). */
+      for ( target_axis = 0; target_axis < target_naxes; target_axis++ ) {
+         inperm[ target_axis ] = -1;
+      }
+
+/* Loop through each axis in the result Frame and obtain the index of the axis
+   in the target Frame from which it is to be derived. */
+      for ( result_axis = 0; result_axis < result_naxes; result_axis++ ) {
+         target_axis = target_axes[ result_axis ];
+
+/* Check if the resulting axis index is valid. If not, this result axis is not
+   to be derived from any target axis, and it will therefore be left with its
+   default attributes. Make an entry in the appropriate permutation array to
+   indicate that this result axis is unassociated. */
+         if ( ( target_axis < 0 ) || ( target_axis >= target_naxes ) ) {
+            outperm[ result_axis ] = -1;
+
+/* Otherwise, obtain a pointer to the target Axis object and modify the
+   temporary Frame so that its axis is associated with the same Axis object.
+   Annul the Axis pointer afterwards. */
+         } else {
+            newaxis = astGetAxis( target, target_axis );
+            astSetAxis( tempframe, result_axis, newaxis );
+            newaxis = astAnnul( newaxis );
+
+/* Update both permutation arrays to record the association between the target
+   and result axes. */
+            outperm[ result_axis ] = target_axis;
+            inperm[ target_axis ] = result_axis;
+         }
+
+/* Quit looping if an error occurs. */
+         if ( !astOK ) break;
+      }
+
+/* So far, we have only modified pointers in the temporary Frame to refer to
+   the target Frame's Axis objects. Since we will next modify these objects'
+   attributes, we must make a deep copy of the entire temporary Frame so that
+   we do not modify the target's axes. This copy now becomes our result Frame.
+   Annul the temporary one. */
+      if ( astOK ) {
+         *result = astCopy( tempframe );
+         tempframe = astAnnul( tempframe );
+
+/* Invoke the target "astOverlay" method to overlay any remaining
+   attributes from the target Frame which are not associated with
+   individual axes (e.g.  the Frame's Title and Domain). */
+         astOverlay( target, target_axes, *result );
+
+/* If a template Frame was supplied, also invoke its astOverlay method to
+   overlay its attributes on the result Frame. (Note that in this particular
+   case this has no effect other than transferring attributes. In general,
+   however, i.e. in derived classes, this process is vital to determining the
+   mapping below, whose main purpose is to convert between the target and
+   result Frames. These will have different attributes as a result of the
+   influence that the template has here.) */
+         if ( template ) astOverlay( template, template_axes, *result );
+
+/* We will next generate the Mapping that relates the target and result
+   Frames. If appropriate this should be a unit Mapping (UnitMap), so test if
+   the number of axes in both Frames is equal. */
+         unit = ( target_naxes == result_naxes );
+
+/* If so, check the contents of one of the permutation arrays to see if all
+   result axes are associated with the corresponding target axis (the converse
+   then also follows). If not, note this fact and quit checking. */
+         if ( unit ) {
+            for ( result_axis = 0; result_axis < result_naxes;
+                                   result_axis++ ) {
+               if ( outperm[ result_axis ] != result_axis ) {
+                  unit = 0;
+                  break;
+	       }
+	    }
+	 }
+
+/* If a unit Mapping is appropriate, then construct it. */
+         if ( unit ) {
+            *map = (AstMapping *) astUnitMap( result_naxes, "", status );
+
+/* Otherwise, construct a Mapping describing the axis permutation we have
+   produced. */
+         } else {
+            *map = (AstMapping *) astPermMap( target_naxes, inperm,
+                                              result_naxes, outperm, NULL,
+                                              "", status );
+         }
+
+/* Note that coordinate conversion is possible. */
+         match = 1;
+
+/* If the ActiveUnit flag in both template and result Frame is non-zero, we
+   now modify the Mapping to take account of any differences in the Units
+   attributes of the target and results Frames. */
+         if( template && astGetActiveUnit( template ) &&
+                         astGetActiveUnit( *result ) ) {
+
+/* Loop round the axes of the results Frame, accumulating a parallel CmpMap
+   ("umap") in which each Mapping is the 1-D Mapping which transforms the
+   Units of the corresponding target axis into the Units of the results
+   axis. */
+            umap = NULL;
+            uunit = 1;
+            for( result_axis = 0; result_axis < result_naxes; result_axis++ ) {
+
+/* Find the index of the corresponding target axis. */
+               if( unit ) {
+                  target_axis = result_axis;
+               } else {
+                  target_axis = outperm[ result_axis ];
+               }
+
+/* Get the Unit string for both axes, and attempt to find a Mapping which
+   transforms values in the target units into the corresponding value in the
+   results units. If this results axis does not have a corresponding
+   target axis, then indicate that no units mapping can be found. */
+               if( target_axis > -1 ) {
+                  aumap = astUnitMapper( astGetUnit( target, target_axis ),
+                                         astGetUnit( *result, result_axis ),
+                                         NULL, NULL );
+               } else {
+                  aumap = NULL;
+               }
+
+/* If no Mapping could be found, annull the Mapping and leave the loop.
+   Otherwise, see if the Mapping is a UnitMap. If not, set a flag to indicate
+   that we have at least one non-unit map. */
+               if( !aumap ) {
+                  if( umap ) umap = astAnnul( umap );
+                  match = 0;
+                  break;
+               } else {
+                  if( !astIsAUnitMap( aumap ) ) uunit = 0;
+               }
+
+/* Add this Mapping into the parallel CmpMap. */
+               if( umap ) {
+                  numap = (AstMapping *) astCmpMap( umap, aumap, 0, "", status );
+                  umap = astAnnul( umap );
+                  aumap = astAnnul( aumap );
+                  umap = numap;
+               } else {
+                  umap = aumap;
+               }
+            }
+
+/* If the resulting CmpMap is not just a UnitMap, add it in series with
+   the current results mapping, and then simplify it. */
+            if( !uunit && umap ) {
+               numap = (AstMapping *) astCmpMap( *map, umap, 1, "", status );
+               (void) astAnnul( *map );
+               *map = numap;
+            }
+
+/* Annul the CmpMap containing the units Mappings. */
+            if( umap ) umap = astAnnul( umap );
+
+/* If the units could not bve matched annul the returned mapping. */
+            if( !match && *map ) *map = astAnnul( *map );
+         }
+      }
+   }
+
+/* Free the memory used for the permutation arrays. */
+   inperm = astFree( inperm );
+   outperm = astFree( outperm );
+
+/* If an error occurred, annul the returned objects and reset the returned
+   value. */
+   if ( !astOK ) {
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static AstSystemType SystemCode( AstFrame *this, const char *system, int *status ) {
+/*
+*+
+*  Name:
+*     astSystemCode
+
+*  Purpose:
+*     Convert a string into a coordinate system type code.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstSystemType SystemCode( AstFrame *this, const char *system )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function converts a string used for the external description of
+*     a coordinate system into a Frame coordinate system type code (System
+*     attribute value). It is the inverse of the astSystemString function.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        Pointer to a constant null-terminated string containing the
+*        external description of the coordinate system.
+
+*  Returned Value:
+*     The System type code.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM is returned if the coordinate system
+*     description was not recognised. This does not produce an error.
+*     - A value of AST__BADSYSTEM is also returned if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSystemType result;      /* Result value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" string against each possibility and assign the
+   result. The basic Frame class only supports a single system
+   "Cartesian". */
+   if ( astChrMatch( "Cartesian", system ) ) {
+      result = AST__CART;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *SystemString( AstFrame *this, AstSystemType system, int *status ) {
+/*
+*+
+*  Name:
+*     astSystemString
+
+*  Purpose:
+*     Convert a coordinate system type code into a string.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     const char *astSystemString( AstFrame *this, AstSystemType system )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function converts a Frame coordinate system type code
+*     (System attribute value) into a string suitable for use as an
+*     external representation of the coordinate system type.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The coordinate system type code.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*-
+*/
+
+/* Local Variables: */
+   const char *result;        /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" value against each possibility and convert to a
+   string pointer. (Where possible, return the same string as would be
+   used in the FITS WCS representation of the coordinate system). A basic
+   Frame only allows a single System value, "Cartesian". */
+   switch ( system ) {
+   case AST__CART:
+      result = "Cartesian";
+      break;
+   }
+
+/* Return the result pointer. */
+   return result;
+
+}
+
+static int TestActiveUnit( AstFrame *this, int *status ){
+/*
+*+
+*  Name:
+*     astTestActiveUnit
+
+*  Purpose:
+*     Determines if the ActiveUnit flag is set.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astTestActiveUnit( AstFrame *this )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function tests the current value of the ActiveUnit flag for a
+*     Frame. See the description of the astSetActiveUnit function for a
+*     description of the ActiveUnit flag.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+
+*  Returned Value:
+*     Non-zero if the flag has been set. Zero otherwise.
+
+*  Notes:
+*     - A zero value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*--
+*/
+
+/* Local Variables: */
+   int result;         /* The returned value */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Return the result. */
+   return ( this->active_unit != -INT_MAX );
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a Frame's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis */
+   AstFrame *pfrm;               /* Pointer to primary Frame containing axis */
+   AstFrame *this;               /* Pointer to the Frame structure */
+   char pfrm_attrib[ 100 ];      /* Primary Frame attribute */
+   char *axis_attrib;            /* Pointer to axis attribute name */
+   const char *old_attrib;       /* Pointer to supplied attribute name string */
+   int axis;                     /* Frame axis number */
+   int axis_nc;                  /* No. characters in axis attribute name */
+   int free_axis_attrib;         /* Should axis_attrib be freed? */
+   int has_axis;                 /* Does attrib name include axis specifier? */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* No. characters read by astSscanf */
+   int oldrep;                   /* Original error reporting state */
+   int paxis;                    /* Axis index within primary frame */
+   int result;                   /* Result value to return */
+   int used;                     /* Could the setting string be used? */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Frame structure. */
+   this = (AstFrame *) this_object;
+
+/* Set a flag indicating if the attribute name includes an axis
+   specifier. */
+   has_axis = ( strchr( attrib, '(' ) != NULL );
+
+/* A flag indicating that we do not need to free the axis_attrib memory. */
+   free_axis_attrib = 0;
+
+/* Initialise things to avoid compiler warnings. */
+   axis_attrib = NULL;
+   old_attrib = NULL;
+
+/* Jump back to here if we are trying the same attribute but with an explicit
+   axis "(1)" added to the end of the name. */
+L1:
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Digits. */
+/* ------- */
+   if ( !strcmp( attrib, "digits" ) ) {
+      result = astTestDigits( this );
+
+/* Digits(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "digits(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+
+/* There is no function to test the Digits attribute for an axis
+   directly, so obtain a pointer to the Axis and use this to test the
+   attribute. */
+      (void) astValidateAxis( this, axis - 1, 1, "astTestDigits(axis)" );
+      ax = astGetAxis( this, axis - 1 );
+      result = astTestAxisDigits( ax );
+      ax = astAnnul( ax );
+
+/* Direction(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "direction(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestDirection( this, axis - 1 );
+
+/* Epoch. */
+/* ------ */
+   } else if ( !strcmp( attrib, "epoch" ) ) {
+      result = astTestEpoch( this );
+
+/* Bottom(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "bottom(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestBottom( this, axis - 1 );
+
+/* Top(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "top(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestTop( this, axis - 1 );
+
+/* Domain. */
+/* ------- */
+   } else if ( !strcmp( attrib, "domain" ) ) {
+      result = astTestDomain( this );
+
+/* Format(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "format(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestFormat( this, axis - 1 );
+
+/* Label(axis). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "label(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestLabel( this, axis - 1 );
+
+/* MatchEnd. */
+/* --------- */
+   } else if ( !strcmp( attrib, "matchend" ) ) {
+      result = astTestMatchEnd( this );
+
+/* MaxAxes. */
+/* -------- */
+   } else if ( !strcmp( attrib, "maxaxes" ) ) {
+      result = astTestMaxAxes( this );
+
+/* MinAxes. */
+/* -------- */
+   } else if ( !strcmp( attrib, "minaxes" ) ) {
+      result = astTestMinAxes( this );
+
+/* Permute. */
+/* -------- */
+   } else if ( !strcmp( attrib, "permute" ) ) {
+      result = astTestPermute( this );
+
+/* PreserveAxes. */
+/* ------------- */
+   } else if ( !strcmp( attrib, "preserveaxes" ) ) {
+      result = astTestPreserveAxes( this );
+
+/* Symbol(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "symbol(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestSymbol( this, axis - 1 );
+
+/* AlignSystem. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "alignsystem" ) ) {
+      result = astTestAlignSystem( this );
+
+/* System. */
+/* ------- */
+   } else if ( !strcmp( attrib, "system" ) ) {
+      result = astTestSystem( this );
+
+/* Title. */
+/* ------ */
+   } else if ( !strcmp( attrib, "title" ) ) {
+      result = astTestTitle( this );
+
+/* Unit(axis). */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "unit(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestUnit( this, axis - 1 );
+
+/* ObsLat. */
+/* ------- */
+   } else if ( !strcmp( attrib, "obslat" ) ) {
+      result = astTestObsLat( this );
+
+/* ObsLon. */
+/* ------- */
+   } else if ( !strcmp( attrib, "obslon" ) ) {
+      result = astTestObsLon( this );
+
+/* ObsAlt. */
+/* ------- */
+   } else if ( !strcmp( attrib, "obsalt" ) ) {
+      result = astTestObsAlt( this );
+
+/* Dut1. */
+/* ---- */
+   } else if ( !strcmp( attrib, "dut1" ) ) {
+      result = astTestDut1( this );
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Test if the attribute name matches any of the read-only attributes
+   of this class. If it does, then return zero. */
+   } else if ( !strcmp( attrib, "naxes" ) ||
+               !strncmp( attrib, "normunit", 8 ) ||
+               !strncmp( attrib, "internalunit", 12 ) ) {
+      result = 0;
+
+/* Other axis attributes. */
+/* ---------------------- */
+/* If the attribute was not identified above, but appears to refer to
+   a Frame axis, then it may refer to an Axis object of a derived type
+   (which has additional attributes not recognised here). */
+   } else if ( !free_axis_attrib && ( nc = 0,
+               ( 1 == astSscanf( attrib, "%*[^()]%n(%d)%n",
+                                      &axis_nc, &axis, &nc ) )
+               && ( nc >= len ) ) ) {
+
+/* Validate the axis index and extract the attribute name. */
+      (void) astValidateAxis( this, axis - 1, 1, "astTest" );
+      axis_attrib = astString( attrib, axis_nc );
+
+/* Obtain a pointer to the Axis object. */
+      ax = astGetAxis( this, axis - 1 );
+      if( astOK ) {
+
+/* Assume that we will be able to use the attribute name. */
+         used = 1;
+
+/* Temporarily switch off error reporting so that if the following attempt
+   to access the axis attribute fails, we can try to interpret the
+   attribute name as an attribute of the primary Frame containing the
+   specified axis. Any errors reported in this context will simply be
+   ignored, in particularly they are not deferred for later delivery. */
+         oldrep = astReporting( 0 );
+
+/* Use the Axis astTestAttrib method to test the attribute value. */
+         result = astTestAttrib( ax, axis_attrib );
+
+/* If the above call failed with a status of AST__BADAT, indicating that
+   the attribute name was not recognised, clear the status so that we can
+   try to interpret the attribute name as an attribute of the primary Frame
+   containing the specified axis. */
+         if( astStatus == AST__BADAT ) {
+            astClearStatus;
+
+/* Find the primary Frame containing the specified axis. */
+            astPrimaryFrame( this, axis - 1, &pfrm, &paxis );
+
+/* Only attempt to use the primary Frame if it is not the same as "this"
+   - otherwise we could end up in an infinite loop. */
+            if( pfrm != this ) {
+
+/* astPrimaryFrame returns the original - unpermuted - axis index within
+   the primary Frame. So we need to take into account any axis permutation
+   which has been applied to the primary Frame when forming the attribute name
+   to use below. Find the permuted (external) axis index which corresponds to
+   the internal (unpermuted) axis index "paxis". */
+               paxis = astValidateAxis( pfrm, paxis, 0, "astTest" );
+
+/* Modify the attribute name to refer to the axis numbering of the
+   primary frame. */
+               sprintf( pfrm_attrib, "%s(%d)", axis_attrib, paxis + 1 );
+
+/* Attempt to test the attribute as an attribute of the primary Frame. */
+               result = astTestAttrib( pfrm, pfrm_attrib );
+
+/* If this failed, clear the status and indicate that we have not managed to
+   use the attribute name. */
+               if( !astOK ) {
+                  astClearStatus;
+                  used = 0;
+               }
+
+            } else {
+               used = 0;
+            }
+
+/* If not found attempt to test the attribute value in the Axis, omitting
+   the axis index. */
+            if( ! used ) {
+               result = astTestAttrib( pfrm, axis_attrib );
+               if( !astOK ) {
+                  astClearStatus;
+               } else {
+                  used = 1;
+               }
+            }
+
+/* Annul the primary Frame pointer. */
+            pfrm = astAnnul( pfrm );
+         }
+
+/* Re-instate the original error reporting state. */
+         astReporting( oldrep );
+
+/* If we could not use the attribute name, attempt to test the axis
+   attribute again, this time retaining the error report. This is done
+   to ensure the user gets an appropriate error message. */
+         if( !used ) result = astTestAttrib( ax, axis_attrib );
+      }
+
+/* Annul the Axis pointer and free the memory holding the attribute
+   name. */
+      ax = astAnnul( ax );
+      axis_attrib = astFree( axis_attrib );
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is still not recognised, and the Frame has only 1 axis,
+   and the attribute name does not already include an axis specifier, try
+   again after appending "(1)" to the end of the attribute name. */
+   } else if( !has_axis && astGetNaxes( this ) == 1 ) {
+
+/* Take a copy of the supplied name, allowing 3 extra characters for the
+   axis specifier "(1)". */
+      axis_attrib = astMalloc( len + 4 );
+      if( axis_attrib ) memcpy( axis_attrib, attrib, len );
+
+/* Indicate we should free the axis_attrib memory. */
+      free_axis_attrib = 1;
+
+/* Add in the axis specifier. */
+      strcpy( axis_attrib + len, "(1)" );
+
+/* Use the new attribute name instead of the supplied name. */
+      old_attrib = attrib;
+      attrib = axis_attrib;
+
+/* Indicate the attribute name now has an axis specifier. */
+      has_axis = 1;
+
+/* Jump back to try interpreting the new attribute name. */
+      goto L1;
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute name is still not recognised, pass it on to the parent
+   method for further interpretation. First re-instate the original attrib
+   name string if it was changed above. */
+   } else {
+      if( free_axis_attrib ) {
+         attrib = old_attrib;
+         axis_attrib = astFree( axis_attrib );
+      }
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Use a Frame to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     Frame member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a Frame and a set of points encapsulated in a
+*     PointSet and transforms the points so as to perform the identity
+*     transformation (i.e. simply copies the coordinate values).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation. In this case, both transformations are equivalent.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the Frame being applied. This number
+*     will be equal to the number of Frame axes.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstFrame *this;               /* Pointer to the Frame structure */
+   AstPointSet *result;          /* Pointer value to be returned */
+   AstUnitMap *unitmap;          /* Pointer to temporary UnitMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the Frame structure. */
+   this = (AstFrame *) this_mapping;
+
+/* Create a unit Mapping with one coordinate for each Frame axis. */
+   unitmap = astUnitMap( astGetNaxes( this ), "", status );
+
+/* Use the Mapping to transform (i.e. copy) the coordinate values. */
+   result = astTransform( unitmap, in, forward, out );
+
+/* Annul the Mapping. */
+   unitmap = astAnnul( unitmap );
+
+/* If an error occurred and a new PointSet may have been created, then annul
+   the result. In any case, ensure that a NULL pointer is returned. */
+   if ( !astOK ) {
+      if ( !out ) result = astAnnul( result );
+      result = NULL;
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static int Unformat( AstFrame *this, int axis, const char *string,
+                     double *value, int *status ) {
+/*
+*+
+*  Name:
+*     astUnformat
+
+*  Purpose:
+*     Read a formatted coordinate value for a Frame axis.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astUnformat( AstFrame *this, int axis, const char *string,
+*                      double *value )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function reads a formatted coordinate value for a Frame
+*     axis (supplied as a string) and returns the equivalent numerical
+*     value as a double. It also returns the number of characters read
+*     from the string.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the Frame axis for which the coordinate value
+*        is to be read (axis numbering starts at zero for the first
+*        axis).
+*     string
+*        Pointer to a constant null-terminated string containing the
+*        formatted coordinate value.
+*     value
+*        Pointer to a double in which the coordinate value read will be
+*        returned.
+
+*  Returned Value:
+*     The number of characters read from the string to obtain the
+*     coordinate value.
+
+*  Notes:
+*     - Any white space at the beginning of the string will be
+*     skipped, as also will any trailing white space following the
+*     coordinate value read. The function's return value will reflect
+*     this.
+*     - A function value of zero (and no coordinate value) will be
+*     returned, without error, if the string supplied does not contain
+*     a suitably formatted value.
+*     - The string "<bad>" is recognised as a special case and will
+*     generate the value AST__BAD, without error. The test for this
+*     string is case-insensitive and permits embedded white space.
+*     - A function result of zero will be returned and no coordinate
+*     value will be returned via the "value" pointer if this function
+*     is invoked with the global error status set, or if it should
+*     fail for any reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic astUnformat method
+*     available via the protected interface to the Frame class. The
+*     public interface to this method is provided by the
+*     astUnformatId_ function.
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   const char *label;            /* Pointer to axis label string */
+   double coord;                 /* Coordinate value read */
+   int digits_set;               /* Axis Digits attribute set? */
+   int nc;                       /* Number of characters read */
+   int status_value;             /* AST error status */
+
+/* Initialise. */
+   nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return nc;
+
+/* Validate the axis index and obtain a pointer to the required Axis. */
+   (void) astValidateAxis( this, axis, 1, "astUnformat" );
+   ax = astGetAxis( this, axis );
+
+/* Test if any Axis attributes which may affect the result are
+   undefined (i.e. have not been explicitly set). If so, we over-ride
+   them, giving them temporary values dictated by the Frame. Only the
+   Digits attribute is potentially relevant here. */
+   digits_set = astTestAxisDigits( ax );
+   if ( !digits_set ) astSetAxisDigits( ax, astGetDigits( this ) );
+
+/* Read the coordinate value. */
+   if ( astOK ) {
+      nc = astAxisUnformat( ax, string, &coord );
+
+/* If an error occurred, save and temporarily clear the global error
+   status while the axis Label string is obtained. Then restore the
+   original error status value afterwards. */
+      if ( !astOK ) {
+         status_value = astStatus;
+         astClearStatus;
+         label = astGetLabel( this, axis );
+         astSetStatus( status_value );
+
+/* Report a contextual error message containing the axis label. */
+         astError( status_value, "%s(%s): Unable to read \"%s\" value.", status,
+                   "astUnformat", astGetClass( this ), label );
+      }
+   }
+
+/* Clear any Axis attributes that were temporarily over-ridden. */
+   if ( !digits_set ) astClearAxisDigits( ax );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* If an error occurred, clear the count of characters read. */
+   if ( !astOK ) {
+      nc = 0;
+
+/* Otherwise, if characters were read, return the coordinate value. */
+   } else if ( nc ) {
+      *value = coord;
+   }
+
+/* Return the number of characters read. */
+   return nc;
+}
+
+static int ValidateAxis( AstFrame *this, int axis, int fwd, const char *method,
+                         int *status ) {
+/*
+*+
+*  Name:
+*     astValidateAxis
+
+*  Purpose:
+*     Validate and permute a Frame's axis index.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astValidateAxis( AstFrame *this, int axis, int fwd,
+*                          const char *method )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function checks the validity of an index (zero-based) which
+*     is to be used to address one of the coordinate axes of a
+*     Frame. If the index is valid, it is permuted using the axis
+*     permutation array associated with the Frame and the (zero-based)
+*     permuted axis index is returned.  This gives the location of the
+*     required axis information within the Frame's internal arrays. If
+*     the axis index supplied is not valid, an error is reported and
+*     the global error status is set.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The axis index (zero-based) to be checked. To be valid, it
+*        must lie between zero and (naxes-1) inclusive, where "naxes"
+*        is the number of coordinate axes associated with the Frame.
+*     fwd
+*        If non-zero, the suppplied axis index is assumed to be an
+*        "external" axis index, and the corresponding "internal" axis index
+*        is returned as the function value. Otherwise, the suppplied axis
+*        index is assumed to be an "internal" axis index, and the
+*        corresponding "external" axis index is returned as the function
+*        value.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+
+*  Returned Value:
+*     The permuted axis index - either "internal" or "external" as
+*     specified by "fwd".
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*     - Error messages issued by this function refer to the public
+*     numbering system used for axes which is one-based (zero-based axis
+*     indices are used internally).
+*-
+*/
+
+/* Local Variables: */
+   const int *perm;              /* Pointer to axis permutation array */
+   int naxes;                    /* Number of Frame axes */
+   int result;                   /* Permuted axis index */
+
+/* Initialise. */
+   result = 0;
+
+/* Determine the number of Frame axes. */
+   naxes = astGetNaxes( this );
+   if ( astOK ) {
+
+/* If the Frame has no axes, report an error (note we convert to
+   one-based axis numbering in the error message). */
+      if ( naxes == 0 ) {
+         astError( AST__AXIIN, "%s(%s): Invalid attempt to use an axis index "
+                   "(%d) for a %s which has no axes.", status, method,
+                   astGetClass( this ), axis + 1, astGetClass( this ) );
+
+/* Otherwise, check the axis index for validity and report an error if
+   it is not valid (again, use one-based axis numbering). */
+      } else if ( ( axis < 0 ) || ( axis >= naxes ) ) {
+         astError( AST__AXIIN, "%s(%s): Axis index (%d) invalid - it should "
+                   "be in the range 1 to %d.", status, method, astGetClass( this ),
+                   axis + 1, naxes );
+
+/* If the axis index was valid, obtain the axis permutation array and
+   use this to generate the permuted axis value. */
+      } else {
+         perm = astGetPerm( this );
+         if( perm ) {
+
+/* External to internal is a simple look-up. */
+            if( fwd ) {
+               result = perm[ axis ];
+
+/* Internal to external requires a search through the permutation array. */
+            } else {
+               for( result = 0; result < naxes; result++ ) {
+                  if( perm[ result ] == axis ) break;
+               }
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void ValidateAxisSelection( AstFrame *this, int naxes, const int *axes,
+                                   const char *method, int *status ) {
+/*
+*+
+*  Name:
+*     astValidateAxisSelection
+
+*  Purpose:
+*     Check that a set of axes selected from a Frame is valid.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void astValidateAxisSelection( AstFrame *this, int naxes,
+*                                    const int *axes, const char *method )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function checks the validity of an array of (zero-based)
+*     axis indices that specify a set of axes to be selected from a
+*     Frame. To be valid, no axis should be selected more than
+*     once. In assessing this, any axis indices that do not refer to
+*     valid Frame axes (e.g. are set to -1) are ignored.
+*
+*     If the axis selection is valid, this function returns without further
+*     action. Otherwise, an error is reported and the global error status is
+*     set.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     naxes
+*        The number of axes to be selected (may be zero).
+*     axes
+*        Pointer to an array of int with naxes elements that contains the
+*        (zero based) axis indices to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis selection. This method name is used
+*        solely for constructing error messages.
+*-
+*/
+
+/* Local Variables: */
+   int *count;                   /* Pointer to temporary array of counts */
+   int axis;                     /* Loop counter for selected axes */
+   int frame_axis;               /* Loop counter for Frame axes */
+   int frame_naxes;              /* Number of Frame axes */
+   int valid;                    /* Axis selection valid? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Check to see if no axes have been selected. If so, there is nothing to
+   do. */
+   if ( naxes ) {
+
+/* Initialise. */
+      valid = 1;
+
+/* Obtain the number of Frame axes and allocate an array of int with
+   one element for each Frame axis. This will store a count of the
+   number of times each axis is selected. */
+      frame_naxes = astGetNaxes( this );
+      count = astMalloc( sizeof( int ) * (size_t) frame_naxes );
+      if ( astOK ) {
+
+/* Initialise the array of counts to zero. */
+         for ( frame_axis = 0; frame_axis < frame_naxes; frame_axis++ ) {
+            count[ frame_axis ] = 0;
+         }
+
+/* Loop through each selected axis. */
+         for ( axis = 0; axis < naxes; axis++ ) {
+            frame_axis = axes[ axis ];
+
+/* Check if the selected axis index is valid for the Frame. If so, increment
+   the selection count for that Frame axis. */
+            if ( ( frame_axis >= 0 ) && ( frame_axis < frame_naxes ) ) {
+               count[ frame_axis ]++;
+            }
+         }
+
+/* Loop through the count array and check that no Frame axis was selected
+   more than once. If it was, clear the "valid" flag and quit checking. */
+         for ( frame_axis = 0; frame_axis < frame_naxes; frame_axis++ ) {
+            if ( count[ frame_axis ] > 1 ) {
+               valid = 0;
+               break;
+            }
+         }
+      }
+
+/* Free the temporary count array. */
+      count = astFree( count );
+
+/* If no error has occurred, but the axis selection is not valid, then report
+   an error. */
+      if ( astOK && !valid ) {
+         astError( AST__SELIN, "%s(%s): Invalid axis selection - each axis "
+                   "may be selected only once.", status, method, astGetClass( this ) );
+      }
+   }
+}
+
+static int ValidateSystem( AstFrame *this, AstSystemType system, const char *method, int *status ) {
+/*
+*+
+*  Name:
+*     astValidateSystem
+
+*  Purpose:
+*     Validate a value for a Frame's System attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int astValidateSystem( AstFrame *this, AstSystemType system,
+*                            const char *method )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function checks the validity of the supplied system value.
+*     If the value is valid, it is returned unchanged. Otherwise, an
+*     error is reported and a value of AST__BADSYSTEM is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The system value to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+
+*  Returned Value:
+*     The validated system value.
+
+*  Notes:
+*     - A value of AST_BADSYSTEM will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSystemType result;              /* Validated system value */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the value is out of bounds, report an error. */
+   if ( system < FIRST_SYSTEM || system > LAST_SYSTEM ) {
+         astError( AST__AXIIN, "%s(%s): Bad value (%d) given for the System "
+                   "or AlignSystem attribute of a %s.", status, method,
+                   astGetClass( this ), (int) system, astGetClass( this ) );
+
+/* Otherwise, return the supplied value. */
+   } else {
+      result = system;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   the axes of a Frame using the private macros defined for this
+   purpose at the start of this file. */
+
+/*
+*att++
+*  Name:
+*     Naxes
+
+*  Purpose:
+*     Number of Frame axes.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This is a read-only attribute giving the number of axes in a
+*     Frame (i.e. the number of dimensions of the coordinate space
+*     which the Frame describes). This value is determined when the
+*     Frame is created.
+
+*  Applicability:
+*     Frame
+*        All Frames have this attribute.
+*     FrameSet
+*        The Naxes attribute of a FrameSet is the same as that of its
+*        current Frame (as specified by the Current attribute).
+*     CmpFrame
+*        The Naxes attribute of a CmpFrame is equal to the sum of the
+*        Naxes values of its two component Frames.
+*att--
+*/
+
+
+/*
+*att++
+*  Name:
+*     Direction(axis)
+
+*  Purpose:
+*     Display axis in conventional direction?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which suggests how the axes of
+*     a Frame should be displayed (e.g.) in graphical output. By
+*     default, it has the value one, indicating that they should be
+*     shown in the conventional sense (increasing left to right for an
+*     abscissa, and bottom to top for an ordinate). If set to zero,
+*     this attribute indicates that the direction should be reversed,
+*     as would often be done for an astronomical magnitude or a right
+*     ascension axis.
+
+*  Applicability:
+*     Frame
+*        The default Direction value supplied by the Frame class is 1,
+*        indicating that all axes should be displayed in the
+*        conventional direction.
+*     SkyFrame
+*        The SkyFrame class re-defines the default Direction value to
+*        suggest that certain axes (e.g. right ascension) should be
+*        plotted in reverse when appropriate.
+*     FrameSet
+*        The Direction attribute of a FrameSet axis is the same as
+*        that of its current Frame (as specified by the Current
+*        attribute).
+*     Plot
+*        The Direction attribute of the base Frame in a Plot is set to
+*        indicate the sense of the two graphics axes, as implied by the
+*        graphics bounding box supplied when the Plot was created.
+
+*  Notes:
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the Frame axis to which it
+*     applies.
+*     - The Direction attribute does not directly affect the behaviour
+*     of the AST library. Instead, it serves as a hint to applications
+*     programs about the orientation in which they may wish to display
+*     any data associated with the Frame. Applications are free to
+*     ignore this hint if they wish.
+*att--
+*/
+/* This simply provides an interface to the Axis methods for accessing
+   the Direction flag. */
+MAKE_CLEAR(Direction)
+MAKE_GET(Direction,int,0,0,0)
+MAKE_SET(Direction,int)
+MAKE_TEST(Direction)
+
+/*
+*att++
+*  Name:
+*     Dut1
+
+*  Purpose:
+*     The UT1-UTC correction.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute is used when calculating the Local Apparent Sidereal
+*     Time corresponding to SkyFrame's Epoch value (used when converting
+*     positions to or from the "AzEl" system). It should be set to the
+*     difference, in seconds, between the UT1 and UTC timescales at the
+*     moment in time represented by the SkyFrame's Epoch attribute. The
+*     value to use is unpredictable and depends on changes in the earth's
+*     rotation speed. Values for UT1-UTC can be obtained from the
+*     International Earth Rotation and Reference Systems Service
+*     (IERS) at http://www.iers.org/.
+*
+*     Currently, the correction is always less than 1 second. This is
+*     ensured by the occasional introduction of leap seconds into the UTC
+*     timescale. Therefore no great error will usually result if no value
+*     is assigned to this attribute (in which case a default value of
+*     zero is used). However, it is possible that a decision may be taken
+*     at some time in the future to abandon the introduction of leap
+*     seconds, in which case the DUT correction could grow to significant
+*     sizes.
+
+*  Applicability:
+*     Frame
+*        All Frames have this attribute.
+
+*att--
+*/
+/* The UT1-UTC correction, in seconds. Has a value of AST__BAD when not set
+   yielding a default value of 0.0. */
+astMAKE_CLEAR(Frame,Dut1,dut1,AST__BAD)
+astMAKE_GET(Frame,Dut1,double,0.0,(this->dut1 == AST__BAD ? 0.0 : this->dut1))
+astMAKE_SET(Frame,Dut1,double,dut1,value)
+astMAKE_TEST(Frame,Dut1,( this->dut1 != AST__BAD ))
+
+
+
+/*
+*att++
+*  Name:
+*     Epoch
+
+*  Purpose:
+*     Epoch of observation.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute is used to qualify the coordinate systems described by
+*     a Frame, by giving the moment in time when the coordinates are known
+*     to be correct. Often, this will be the date of observation, and is
+*     important in cases where coordinates systems move with respect to each
+*     other over the course of time.
+*
+*     The Epoch attribute is stored as a Modified Julian Date, but
+*     when setting its value it may be given in a variety of
+*     formats. See the "Input Formats" section (below) for details.
+*     Strictly, the Epoch value should be supplied in the TDB timescale,
+*     but for some purposes (for instance, for converting sky positions
+*     between different types of equatorial system) the timescale is not
+*     significant, and UTC may be used.
+
+*  Input Formats:
+*     The formats accepted when setting an Epoch value are listed
+*     below. They are all case-insensitive and are generally tolerant
+*     of extra white space and alternative field delimiters:
+*
+*     - Besselian Epoch: Expressed in decimal years, with or without
+*     decimal places ("B1950" or "B1976.13" for example).
+*
+*     - Julian Epoch: Expressed in decimal years, with or without
+*     decimal places ("J2000" or "J2100.9" for example).
+*
+*     - Year: Decimal years, with or without decimal places ("1996.8"
+*     for example).  Such values are interpreted as a Besselian epoch
+*     (see above) if less than 1984.0 and as a Julian epoch otherwise.
+*
+*     - Julian Date: With or without decimal places ("JD 2454321.9" for
+*     example).
+*
+*     - Modified Julian Date: With or without decimal places
+*     ("MJD 54321.4" for example).
+*
+*     - Gregorian Calendar Date: With the month expressed either as an
+*     integer or a 3-character abbreviation, and with optional decimal
+*     places to represent a fraction of a day ("1996-10-2" or
+*     "1996-Oct-2.6" for example). If no fractional part of a day is
+*     given, the time refers to the start of the day (zero hours).
+*
+*     - Gregorian Date and Time: Any calendar date (as above) but with
+*     a fraction of a day expressed as hours, minutes and seconds
+*     ("1996-Oct-2 12:13:56.985" for example). The date and time can be
+*     separated by a space or by a "T" (as used by ISO8601 format).
+
+*  Output Format:
+*     When enquiring Epoch values, the format used is the "Year"
+*     format described under "Input Formats". This is a value in
+*     decimal years which will be a Besselian epoch if less than
+*     1984.0 and a Julian epoch otherwise.  By omitting any character
+*     prefix, this format allows the Epoch value to be obtained as
+*     either a character string or a floating point value.
+
+*  Applicability:
+*     Frame
+*        All Frames have this attribute. The basic Frame class provides
+*        a default of J2000.0 (Julian) but makes no use of the Epoch value.
+*        This is because the Frame class does not distinguish between
+*        different Cartesian coordinate systems (see the System attribute).
+*     CmpFrame
+*        The default Epoch value for a CmpFrame is selected as follows;
+*        if the Epoch attribute has been set in the first component Frame
+*        then the Epoch value from the first component Frame is used as
+*        the default for the CmpFrame. Otherwise, if the Epoch attribute has
+*        been set in the second component Frame then the Epoch value from the
+*        second component Frame is used as the default for the CmpFrame.
+*        Otherwise, the default Epoch value from the first component
+*        Frame is used as the default for the CmpFrame. When the Epoch
+*        attribute of a CmpFrame is set or cleared, it is also set or
+*        cleared in the two component Frames.
+*     FrameSet
+*        The Epoch attribute of a FrameSet is the same as that of its current
+*        Frame (as specified by the Current attribute).
+*     SkyFrame
+*        The coordinates of sources within a SkyFrame can change with time
+*        for various reasons, including: (i) changing aberration of light
+*        caused by the observer's velocity (e.g. due to the Earth's motion
+*        around the Sun), (ii) changing gravitational deflection by the Sun
+*        due to changes in the observer's position with time, (iii) fictitious
+*        motion due to rotation of non-inertial coordinate systems (e.g. the
+*        old FK4 system), and (iv) proper motion of the source itself (although
+*        this last effect is not handled by the SkyFrame class because it
+*        affects individual sources rather than the coordinate system as
+*        a whole).
+*
+*        The default Epoch value in a SkyFrame is B1950.0 (Besselian) for the
+*        old FK4-based coordinate systems (see the System attribute) and
+*        J2000.0 (Julian) for all others.
+*
+*        Care must be taken to distinguish the Epoch value, which relates to
+*        motion (or apparent motion) of the source, from the superficially
+*        similar Equinox value. The latter is used to qualify a coordinate
+*        system which is itself in motion in a (notionally) predictable way
+*        as a result of being referred to a slowly moving reference plane
+*        (e.g. the equator).
+*
+*        See the description of the System attribute for details of which
+*        qualifying attributes apply to each celestial coordinate system.
+*     TimeFrame
+*        A TimeFrame describes a general time axis and so cannot be completely
+*        characterised by a single Epoch value. For this reason the TimeFrame
+*        class makes no use of the Epoch attribute. However, user code can
+*        still make use of the attribute if necessary to represent a "typical"
+*        time spanned by the TimeFrame. The default Epoch value for a TimeFrame
+*        will be the TDB equivalent of the current value of the TimeFrame's
+*        TimeOrigin attribute. If no value has been set for TimeOrigin,
+*        then the default Epoch value is J2000.0.
+*att--
+*/
+/* Clear the Epoch value by setting it to AST__BAD. */
+astMAKE_CLEAR(Frame,Epoch,epoch,AST__BAD)
+
+/* Provide a default value of J2000.0 setting. */
+astMAKE_GET(Frame,Epoch,double,AST__BAD,(
+           ( this->epoch != AST__BAD ) ? this->epoch : palEpj2d( 2000.0 )))
+
+/* Allow any Epoch value to be set. */
+astMAKE_SET(Frame,Epoch,double,epoch,value)
+
+/* An Epoch value is set if it is not equal to AST__BAD. */
+astMAKE_TEST(Frame,Epoch,( this->epoch != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     Top(axis)
+
+*  Purpose:
+*     Highest axis value to display
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute gives the highest axis value to be displayed (for
+c     instance, by the astGrid method).
+f     instance, by the AST_GRID method).
+
+*  Applicability:
+*     Frame
+*        The default supplied by the Frame class is to display all axis
+*        values, without any limit.
+*     SkyFrame
+*        The SkyFrame class re-defines the default Top value to +90 degrees
+*        for latitude axes, and 180 degrees for co-latitude axes. The
+*        default for longitude axes is to display all axis values.
+
+*  Notes:
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the Frame axis to which it
+*     applies.
+*att--
+*/
+/* This simply provides an interface to the Axis methods for accessing
+   the Top value. */
+MAKE_CLEAR(Top)
+MAKE_GET(Top,double,DBL_MAX,0,DBL_MAX)
+MAKE_SET(Top,double)
+MAKE_TEST(Top)
+
+/*
+*att++
+*  Name:
+*     Bottom(axis)
+
+*  Purpose:
+*     Lowest axis value to display
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute gives the lowest axis value to be displayed (for
+c     instance, by the astGrid method).
+f     instance, by the AST_GRID method).
+
+*  Applicability:
+*     Frame
+*        The default supplied by the Frame class is to display all axis
+*        values, without any limit.
+*     SkyFrame
+*        The SkyFrame class re-defines the default Bottom value to -90 degrees
+*        for latitude axes, and 0 degrees for co-latitude axes. The
+*        default for longitude axes is to display all axis values.
+
+*  Notes:
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the Frame axis to which it
+*     applies.
+*att--
+*/
+/* This simply provides an interface to the Axis methods for accessing
+   the Bottom value. */
+MAKE_CLEAR(Bottom)
+MAKE_GET(Bottom,double,-DBL_MAX,0,-DBL_MAX)
+MAKE_SET(Bottom,double)
+MAKE_TEST(Bottom)
+
+/*
+*att++
+*  Name:
+*     Format(axis)
+
+*  Purpose:
+*     Format specification for axis values.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies the format to be used when displaying
+*     coordinate values associated with a particular Frame axis
+*     (i.e. to convert values from binary to character form). It is
+c     interpreted by the astFormat function and determines the
+f     interpreted by the AST_FORMAT function and determines the
+*     formatting which it applies.
+*
+*     If no Format value is set for a Frame axis, a default value is
+*     supplied instead. This is based on the value of the Digits, or
+*     Digits(axis), attribute and is chosen so that it displays the
+*     requested number of digits of precision.
+
+*  Applicability:
+*     Frame
+*        The Frame class interprets this attribute as a format
+*        specification string to be passed to the C "printf" function
+*        (e.g. "%1.7G") in order to format a single coordinate value
+*        (supplied as a double precision number).
+c
+c        When supplying a value for this attribute, beware that the
+c        "%" character may be interpreted directly as a format
+c        specification by some printf-like functions (such as
+c        astSet). You may need to double it (i.e. use "%%") to avoid
+c        this.
+*     SkyFrame
+*        The SkyFrame class re-defines the syntax and default value of
+*        the Format string to allow the formatting of sexagesimal
+*        values as appropriate for the particular celestial coordinate
+*        system being represented. The syntax of SkyFrame Format
+*        strings is described (below) in the "SkyFrame Formats"
+*        section.
+*     FrameSet
+*        The Format attribute of a FrameSet axis is the same as that
+*        of its current Frame (as specified by the Current
+*        attribute). Note that the syntax of the Format string is also
+*        determined by the current Frame.
+*     TimeFrame
+*        The TimeFrame class extends the syntax of the Format string to
+*        allow the formatting of TimeFrame axis values as Gregorian calendar
+*        dates and times. The syntax of TimeFrame Format strings is described
+*        (below) in the "TimeFrame Formats" section.
+
+*  SkyFrame Formats:
+*     The Format string supplied for a SkyFrame should contain zero or
+*     more of the following characters. These may occur in any order,
+*     but the following is recommended for clarity:
+*
+*     - "+": Indicates that a plus sign should be prefixed to positive
+*     values. By default, no plus sign is used.
+*
+*     - "z": Indicates that leading zeros should be prefixed to the
+*     value so that the first field is of constant width, as would be
+*     required in a fixed-width table (leading zeros are always
+*     prefixed to any fields that follow). By default, no leading
+*     zeros are added.
+*
+*     - "i": Use the standard ISO field separator (a colon) between
+*     fields. This is the default behaviour.
+*
+*     - "b": Use a blank to separate fields.
+*
+*     - "l": Use a letter ("h"/"d", "m" or "s" as appropriate) to
+*     separate fields.
+*
+*     - "g": Use a letter and symbols to separate fields ("h"/"d", "m" or "s",
+*     etc, as appropriate), but include escape sequences in the formatted
+*     value so that the Plot class will draw the separators as small
+*     super-scripts.
+c     The default escape sequences are optimised for the pgplot graphics
+c     package, but new escape sequences may be specified using function
+c     astSetSkyDelim.
+*
+*     - "d": Include a degrees field. Expressing the angle purely in
+*     degrees is also the default if none of "h", "m", "s" or "t" are
+*     given.
+*
+*     - "h": Express the angle as a time and include an hours field
+*     (where 24 hours correspond to 360 degrees). Expressing the angle
+*     purely in hours is also the default if "t" is given without
+*     either "m" or "s".
+*
+*     - "m": Include a minutes field. By default this is not included.
+*
+*     - "s": Include a seconds field. By default this is not included.
+*     This request is ignored if "d" or "h" is given, unless a minutes
+*     field is also included.
+*
+*     - "t": Express the angle as a time (where 24 hours correspond to
+*     360 degrees). This option is ignored if either "d" or "h" is
+*     given and is intended for use where the value is to be expressed
+*     purely in minutes and/or seconds of time (with no hours
+*     field). If "t" is given without "d", "h", "m" or "s" being
+*     present, then it is equivalent to "h".
+*
+*     - ".": Indicates that decimal places are to be given for the
+*     final field in the formatted string (whichever field this
+*     is). The "." should be followed immediately by an unsigned
+*     integer which gives the number of decimal places required, or by an
+*     asterisk. If an asterisk is supplied, a default number of decimal
+*     places is used which is based on the value of the Digits
+*     attribute.
+*
+*     All of the above format specifiers are case-insensitive. If
+*     several characters make conflicting requests (e.g. if both "i"
+*     and "b" appear), then the character occurring last takes
+*     precedence, except that "d" and "h" always override "t".
+*
+*     If the format string starts with a percentage sign (%), then the
+*     whole format string is assumed to conform to the syntax defined by
+*     the Frame class, and the axis values is formated as a decimal
+*     radians value.
+
+*  TimeFrame Formats:
+*     The Format string supplied for a TimeFrame should either use the
+*     syntax defined by the base Frame class (i.e. a C "printf" format
+*     string), or the extended "iso" syntax described below (the default
+*     value is inherited from the Frame class):
+*
+*     - C "printf" syntax: If the Format string is a C "printf" format
+*     description such as "%1.7G", the TimeFrame axis value will be
+*     formatted without change as a floating point value using this format.
+*     The formatted string will thus represent an offset from the zero point
+*     specified by the TimeFrame's TimeOrigin attribute, measured in
+*     units given by the TimeFrame's Unit attribute.
+*
+*     - "iso" syntax: This is used to format a TimeFrame axis value as a
+*     Gregorian date followed by an optional time of day. If the Format
+*     value commences with the string "iso" then the TimeFrame axis value
+*     will be converted to an absolute MJD, including the addition of the
+*     current TimeOrigin value, and then formatted as a Gregorian date
+*     using the format "yyyy-mm-dd". Optionally, the Format value may
+*     include an integer precision following the "iso" specification (e.g.
+*     "iso.2"), in which case the time of day will be appended to the
+*     formatted date (if no time of day is included, the date field is
+*     rounded to the nearest day). The integer value in the Format string
+*     indicates the number of decimal places to use in the seconds field. For
+*     instance, a Format value of "iso.0" produces a time of day of the form
+*     "hh:mm:ss", and a Format value of "iso.2" produces a time of day of the
+*     form "hh:mm:ss.ss". The date and time fields will be separated by a
+*     space unless 'T' is appended to the end of string, in which case
+*     the letter T (upper case) will be used as the separator. The value of
+*     the Digits attribute is ignored when using this "iso" format.
+
+*  Notes:
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the Frame axis to which it
+*     applies.
+*att--
+*/
+/* This simply provides an interface to the Axis methods for accessing
+   the Format string. */
+MAKE_CLEAR(Format)
+MAKE_GET(Format,const char *,NULL,0,0)
+MAKE_SET(Format,const char *)
+MAKE_TEST(Format)
+
+/*
+*att++
+*  Name:
+*     Label(axis)
+
+*  Purpose:
+*     Axis label.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies a label to be attached to each axis of
+*     a Frame when it is represented (e.g.) in graphical output.
+*
+*     If a Label value has not been set for a Frame axis, then a
+*     suitable default is supplied.
+
+*  Applicability:
+*     Frame
+*        The default supplied by the Frame class is the string "Axis
+*        <n>", where <n> is 1, 2, etc. for each successive axis.
+*     SkyFrame
+*        The SkyFrame class re-defines the default Label value
+*        (e.g. to "Right ascension" or "Galactic latitude") as
+*        appropriate for the particular celestial coordinate system
+*        being represented.
+*     TimeFrame
+*        The TimeFrame class re-defines the default Label value as
+*        appropriate for the particular time system being represented.
+*     FrameSet
+*        The Label attribute of a FrameSet axis is the same as that of
+*        its current Frame (as specified by the Current attribute).
+
+*  Notes:
+*     - Axis labels are intended purely for interpretation by human
+*     readers and not by software.
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the Frame axis to which it
+*     applies.
+*att--
+*/
+/* This provides an interface to the Axis methods for accessing the
+   Label string, but provides an alternative default Label based on
+   the axis number.  This default string is written to the static
+   "label_buff" buffer and a pointer to this is returned if
+   required. */
+MAKE_CLEAR(Label)
+MAKE_GET(Label,const char *,NULL,1,GetDefaultLabel( axis, status ))
+MAKE_SET(Label,const char *)
+MAKE_TEST(Label)
+
+/*
+*att++
+*  Name:
+*     Symbol(axis)
+
+*  Purpose:
+*     Axis symbol.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies a short-form symbol to be used to
+*     represent coordinate values for a particular axis of a
+*     Frame. This might be used (e.g.) in algebraic expressions where
+*     a full description of the axis would be inappropriate. Examples
+*     include "RA" and "Dec" (for Right Ascension and Declination).
+*
+*     If a Symbol value has not been set for a Frame axis, then a
+*     suitable default is supplied.
+
+*  Applicability:
+*     Frame
+*        The default Symbol value supplied by the Frame class is the
+*        string "<Domain><n>", where <n> is 1, 2, etc. for successive
+*        axes, and <Domain> is the value of the Frame's Domain
+*        attribute (truncated if necessary so that the final string
+*        does not exceed 15 characters). If no Domain value has been
+*        set, "x" is used as the <Domain> value in constructing this
+*        default string.
+*     SkyFrame
+*        The SkyFrame class re-defines the default Symbol value
+*        (e.g. to "RA" or "Dec") as appropriate for the particular
+*        celestial coordinate system being represented.
+*     TimeFrame
+*        The TimeFrame class re-defines the default Symbol value as
+*        appropriate for the particular time system being represented.
+*     FrameSet
+*        The Symbol attribute of a FrameSet axis is the same as that
+*        of its current Frame (as specified by the Current attribute).
+
+*  Notes:
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the Frame axis to which it
+*     applies.
+*att--
+*/
+/* This provides an interface to the Axis methods for accessing the
+   Symbol string, but provides an alternative default Symbol based on
+   the axis number and the Frame's Domain (if defined, otherwise "x"
+   is used). This default string is written to the static
+   "symbol_buff" buffer and a pointer to this is returned if
+   required. */
+MAKE_CLEAR(Symbol)
+MAKE_GET(Symbol,const char *,NULL,1,GetDefaultSymbol( this, axis, status ) )
+MAKE_SET(Symbol,const char *)
+MAKE_TEST(Symbol)
+
+/*
+*att++
+*  Name:
+*     Unit(axis)
+
+*  Purpose:
+*     Physical units for formatted axis values
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute contains a textual representation of the physical
+*     units used to represent formatted coordinate values on a particular
+*     axis of a Frame.
+c     The astSetActiveUnit function controls how the Unit values
+f     The AST_SETACTIVEUNIT routine controls how the Unit values
+*     are used.
+
+*  Applicability:
+*     Frame
+*        The default supplied by the Frame class is an empty string.
+*     SkyFrame
+*        The SkyFrame class re-defines the default Unit value (e.g. to
+*        "hh:mm:ss.sss") to describe the character string returned by
+c        the astFormat function when formatting coordinate values.
+f        the AST_FORMAT function when formatting coordinate values.
+*     SpecFrame
+*        The SpecFrame class re-defines the default Unit value so that it
+*        is appropriate for the current System value. See the System
+*        attribute for details. An error will be reported if an attempt
+*        is made to use an inappropriate Unit.
+*     TimeFrame
+*        The TimeFrame class re-defines the default Unit value so that it
+*        is appropriate for the current System value. See the System
+*        attribute for details. An error will be reported if an attempt
+*        is made to use an inappropriate Unit (e.g. "km").
+*     FrameSet
+*        The Unit attribute of a FrameSet axis is the same as that of
+*        its current Frame (as specified by the Current attribute).
+
+*  Notes:
+*     - This attribute described the units used when an axis value is
+*     formatted into a string using
+c     astFormat.
+f     AST_FORMAT.
+*     In some cases these units may be different to those used to represent
+*     floating point axis values within application code (for instance a
+*     SkyFrame always uses radians to represent floating point axis values).
+*     The InternalUnit attribute described the units used for floating
+*     point values.
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the Frame axis to which it
+*     applies.
+*att--
+*/
+/* This simply provides an interface to the Axis methods for accessing
+   the Unit string. */
+MAKE_GET(Unit,const char *,NULL,0,0)
+MAKE_TEST(Unit)
+
+/*
+*att++
+*  Name:
+*     NormUnit(axis)
+
+*  Purpose:
+*     Normalised physical units for formatted axis values
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String, read-only.
+
+*  Description:
+*     The value of this read-only attribute is derived from the current
+*     value of the Unit attribute. It will represent an equivalent system
+*     of units to the Unit attribute, but will potentially be simplified.
+*     For instance, if Unit is set to "s*(m/s)", the NormUnit value will
+*     be "m". If no simplification can be performed, the value of the
+*     NormUnit attribute will equal that of the Unit attribute.
+
+*  Applicability:
+*     Frame
+*        All Frames have this attribute.
+
+*  Notes:
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the Frame axis to which it
+*     applies.
+*att--
+*/
+/* This simply provides an interface to the Axis methods for accessing
+   the NormUnit string. */
+MAKE_GET(NormUnit,const char *,NULL,0,0)
+
+/*
+*att++
+*  Name:
+*     InternalUnit(axis)
+
+*  Purpose:
+*     Physical units for unformated axis values
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String, read-only.
+
+*  Description:
+*     This read-only attribute contains a textual representation of the
+*     physical units used to represent unformatted (i.e. floating point)
+*     values on a particular axis of a Frame, typically handled internally
+*     within application code. In most cases, the value of the InternalUnit
+*     attribute will be the same as Unit attribute (i.e. formatted and
+*     unformatted axis values will normally use the same system of units).
+*     The main exception to this is the SkyFrame class, which represents
+*     unformatted axis values in radians, regardless of the current
+*     setting of the Unit attribute.
+
+*  Applicability:
+*     Frame
+*        All Frames have this attribute.
+
+*  Notes:
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the Frame axis to which it
+*     applies.
+*att--
+*/
+/* If the Axis structure provides a value for InternalUnit, then use
+   that value. Otherwise, use a default equal to the value of the Unit
+   attribute for the axis. */
+MAKE_GET(InternalUnit,const char *,NULL,1,astGetUnit(this,axis))
+
+/* Implement member functions to access the attributes associated with
+   the Frame as a whole using the macros defined for this purpose in
+   the "object.h" file. */
+
+/*
+*att++
+*  Name:
+*     Digits/Digits(axis)
+
+*  Purpose:
+*     Number of digits of precision.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute specifies how many digits of precision are
+*     required by default when a coordinate value is formatted for a
+c     Frame axis (e.g. using astFormat). Its value may be set either
+f     Frame axis (e.g. using AST_FORMAT). Its value may be set either
+*     for a Frame as a whole, or (by subscripting the attribute name
+*     with the number of an axis) for each axis individually. Any
+*     value set for an individual axis will over-ride the value for
+*     the Frame as a whole.
+*
+*     Note that the Digits value acts only as a means of determining a
+*     default Format string. Its effects are over-ridden if a Format
+*     string is set explicitly for an axis. However, if the Format
+*     attribute specifies the precision using the string ".*", then
+*     the Digits attribute is used to determine the number of decimal
+*     places to produce.
+
+*  Applicability:
+*     Frame
+*        The default Digits value supplied by the Frame class is 7. If
+*        a value less than 1 is supplied, then 1 is used instead.
+*     FrameSet
+*        The Digits attribute of a FrameSet (or one of its axes) is
+*        the same as that of its current Frame (as specified by the
+*        Current attribute).
+*     Plot
+*        The default Digits value used by the Plot class when drawing
+*        annotated axis labels is the smallest value which results in all
+*        adjacent labels being distinct.
+*     TimeFrame
+*        The Digits attribute is ignored when a TimeFrame formats a value
+*        as a date and time string (see the Format attribute).
+*att--
+*/
+/* Clear the Digits value by setting it to -INT_MAX. */
+astMAKE_CLEAR(Frame,Digits,digits,-INT_MAX)
+
+/* Supply a default of 7 digits if no value has been set. */
+astMAKE_GET(Frame,Digits,int,0,( ( this->digits != -INT_MAX ) ? this->digits :
+                                                                7 ))
+
+/* Constrain the Digits value being set to be at least 1. */
+astMAKE_SET(Frame,Digits,int,digits,( value > 1 ? value : 1 ))
+
+/* The Digits value is set if it is not -INT_MAX. */
+astMAKE_TEST(Frame,Digits,( this->digits != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     MatchEnd
+
+*  Purpose:
+*     Match trailing axes?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which controls how a Frame
+c     behaves when it is used (by astFindFrame) as a template to match
+f     behaves when it is used (by AST_FINDFRAME) as a template to match
+*     another (target) Frame. It applies only in the case where a
+*     match occurs between template and target Frames with different
+*     numbers of axes.
+*
+*     If the MatchEnd value of the template Frame is zero, then the
+*     axes which occur first in the target Frame will be matched and
+*     any trailing axes (in either the target or template) will be
+*     disregarded. If it is non-zero, the final axes in each Frame
+*     will be matched and any un-matched leading axes will be
+*     disregarded instead.
+
+*  Applicability:
+*     Frame
+*        The default MatchEnd value for a Frame is zero, so that
+*        trailing axes are disregarded.
+*     FrameSet
+*        The MatchEnd attribute of a FrameSet is the same as that of
+*        its current Frame (as specified by the Current attribute).
+*att--
+*/
+/* Clear the MatchEnd value by setting it to -INT_MAX. */
+astMAKE_CLEAR(Frame,MatchEnd,match_end,-INT_MAX)
+
+/* Supply a default of 0 if no MatchEnd value has been set. */
+astMAKE_GET(Frame,MatchEnd,int,0,( ( this->match_end != -INT_MAX ) ?
+                                   this->match_end : 0 ))
+
+/* Set a MatchEnd value of 1 if any non-zero value is supplied. */
+astMAKE_SET(Frame,MatchEnd,int,match_end,( value != 0 ))
+
+/* The MatchEnd value is set if it is not -INT_MAX. */
+astMAKE_TEST(Frame,MatchEnd,( this->match_end != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     MaxAxes
+
+*  Purpose:
+*     Maximum number of Frame axes to match.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute controls how a Frame behaves when it is used (by
+c     astFindFrame) as a template to match another (target) Frame. It
+f     AST_FINDFRAME) as a template to match another (target) Frame. It
+*     specifies the maximum number of axes that the target Frame may
+*     have in order to match the template.
+*
+*     Normally, this value will equal the number of Frame axes, so
+*     that a template Frame will only match another Frame with the
+*     same number of axes as itself. By setting a different value,
+*     however, the matching process may be used to identify Frames
+*     with specified numbers of axes.
+
+*  Applicability:
+*     Frame
+*        The default MaxAxes value for a Frame is equal to the number
+*        of Frame axes (Naxes attribute).
+*     CmpFrame
+*        The MaxAxes attribute of a CmpFrame defaults to a large number
+*        (1000000) which is much larger than any likely number of axes in
+*        a Frame. Combined with the MinAxes default of zero (for a
+*        CmpFrame), this means that the default behaviour for a CmpFrame
+*        is to match any target Frame that consists of a subset of the
+*        axes in the template CmpFrame. To change this so that a CmpFrame
+*        will only match Frames that have the same number of axes, you
+*        should set the CmpFrame MaxAxes and MinAxes attributes to the
+*        number of axes in the CmpFrame.
+*     FrameSet
+*        The MaxAxes attribute of a FrameSet is the same as that of
+*        its current Frame (as specified by the Current attribute).
+
+*  Notes:
+*     - When setting a MaxAxes value, the value of the MinAxes
+*     attribute may also be silently changed so that it remains
+*     consistent with (i.e. does not exceed) the new value. The
+*     default MaxAxes value may also be reduced to remain consistent
+*     with the MinAxes value.
+*     - If a template Frame is used to match a target with a different
+*     number of axes, the MatchEnd attribute of the template is used
+*     to determine how the individual axes of each Frame should match.
+*att--
+*/
+/* Clear the MaxAxes value by setting it to -INT_MAX. */
+astMAKE_CLEAR(Frame,MaxAxes,max_axes,-INT_MAX)
+
+/* Use the DefaultMaxAxes and ConsistentMaxAxes functions (defined earlier) for
+   the Get and Set operations to ensure that MinAxes and MaxAxes values remain
+   consistent. */
+astMAKE_GET(Frame,MaxAxes,int,0,DefaultMaxAxes( this, status ))
+astMAKE_SET(Frame,MaxAxes,int,max_axes,ConsistentMaxAxes( this, value, status ))
+
+/* The MaxAxes value is set if it is not -INT_MAX. */
+astMAKE_TEST(Frame,MaxAxes,( this->max_axes != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     MinAxes
+
+*  Purpose:
+*     Minimum number of Frame axes to match.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute controls how a Frame behaves when it is used (by
+c     astFindFrame) as a template to match another (target) Frame. It
+f     AST_FINDFRAME) as a template to match another (target) Frame. It
+*     specifies the minimum number of axes that the target Frame may
+*     have in order to match the template.
+*
+*     Normally, this value will equal the number of Frame axes, so
+*     that a template Frame will only match another Frame with the
+*     same number of axes as itself. By setting a different value,
+*     however, the matching process may be used to identify Frames
+*     with specified numbers of axes.
+
+*  Applicability:
+*     Frame
+*        The default MinAxes value for a Frame is equal to the number
+*        of Frame axes (Naxes attribute).
+*     CmpFrame
+*        The MinAxes attribute of a CmpFrame defaults to zero. Combined
+*        with the MaxAxes default of 1000000 (for a CmpFrame), this means
+*        that the default behaviour for a CmpFrame is to match any target
+*        Frame that consists of a subset of the axes in the template
+*        CmpFrame. To change this so that a CmpFrame will only match Frames
+*        that have the same number of axes, you should set the CmpFrame
+*        MinAxes and MaxAxes attributes to the number of axes in the CmpFrame.
+*     FrameSet
+*        The MinAxes attribute of a FrameSet is the same as that of
+*        its current Frame (as specified by the Current attribute).
+
+*  Notes:
+*     - When setting a MinAxes value, the value of the MaxAxes
+*     attribute may also be silently changed so that it remains
+*     consistent with (i.e. is not less than) the new value. The
+*     default MinAxes value may also be reduced to remain consistent
+*     with the MaxAxes value.
+*     - If a template Frame is used to match a target with a different
+*     number of axes, the MatchEnd attribute of the template is used
+*     to determine how the individual axes of each Frame should match.
+*att--
+*/
+/* Clear the MinAxes value by setting it to -INT_MAX. */
+astMAKE_CLEAR(Frame,MinAxes,min_axes,-INT_MAX)
+
+/* Use the DefaultMinAxes and ConsistentMinAxes functions (defined earlier) for
+   the Get and Set operations to ensure that MinAxes and MaxAxes values remain
+   consistent. */
+astMAKE_GET(Frame,MinAxes,int,0,DefaultMinAxes( this, status ))
+astMAKE_SET(Frame,MinAxes,int,min_axes,ConsistentMinAxes( this, value, status ))
+
+/* The MinAxes value is set if it is not -INT_MAX. */
+astMAKE_TEST(Frame,MinAxes,( this->min_axes != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     Domain
+
+*  Purpose:
+*     Coordinate system domain.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute contains a string which identifies the physical
+*     domain of the coordinate system that a Frame describes.
+*
+*     The Domain attribute also controls how a Frame behaves when it is
+c     used (by astFindFrame) as a template to match another (target)
+f     used (by AST_FINDFRAME) as a template to match another (target)
+*     Frame. It does this by specifying the Domain that the target
+*     Frame should have in order to match the template. If the Domain
+*     value in the template Frame is set, then only targets with the
+*     same Domain value will be matched. If the template's Domain
+*     value is not set, however, then the target's Domain will be
+*     ignored.
+
+*  Applicability:
+*     Frame
+*        The default Domain value supplied by the Frame class is an
+*        empty string.
+*     SkyFrame
+*        The SkyFrame class re-defines the default Domain value to be
+*        "SKY".
+*     CmpFrame
+*        The CmpFrame class re-defines the default Domain value to be
+*        of the form "<dom1>-<dom2>", where <dom1> and <dom2> are the
+*        Domains of the two component Frames. If both these Domains are
+*        blank, then the string "CMP" is used as the default Domain name.
+*     FrameSet
+*        The Domain attribute of a FrameSet is the same as that of its
+*        current Frame (as specified by the Current attribute).
+*     SpecFrame
+*        The SpecFrame class re-defines the default Domain value to be
+*        "SPECTRUM".
+*     DSBSpecFrame
+*        The DSBSpecFrame class re-defines the default Domain value to be
+*        "DSBSPECTRUM".
+*     FluxFrame
+*        The FluxFrame class re-defines the default Domain value to be
+*        "FLUX".
+*     SpecFluxFrame
+*        The FluxFrame class re-defines the default Domain value to be
+*        "SPECTRUM-FLUX".
+*     TimeFrame
+*        The TimeFrame class re-defines the default Domain value to be
+*        "TIME".
+
+*  Notes:
+*     - All Domain values are converted to upper case and white space
+*     is removed before use.
+*att--
+*/
+/* Clear the Domain value by freeing the allocated memory and
+   assigning a NULL pointer. */
+astMAKE_CLEAR(Frame,Domain,domain,astFree( this->domain ))
+
+/* If the Domain value is not set, supply a default in the form of a
+   pointer to the constant string "". */
+astMAKE_GET(Frame,Domain,const char *,NULL,( this->domain ? this->domain :
+                                                            "" ))
+
+/* Set a Domain value by freeing any previously allocated memory,
+   allocating new memory, storing the string, removing white space,
+   converting to upper case and saving the pointer to the cleaned
+   copy. */
+astMAKE_SET(Frame,Domain,const char *,domain,CleanDomain(
+                                                astStore( this->domain,
+                                    value, strlen( value ) + (size_t) 1 ), status ))
+
+/* The Domain value is set if the pointer to it is not NULL. */
+astMAKE_TEST(Frame,Domain,( this->domain != NULL ))
+
+/*
+*att++
+*  Name:
+*     Permute
+
+*  Purpose:
+*     Permute axis order?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute is a boolean value which controls how a Frame
+c     behaves when it is used (by astFindFrame) as a template to match
+f     behaves when it is used (by AST_FINDFRAME) as a template to match
+*     another (target) Frame. It specifies whether the axis order of
+*     the target Frame may be permuted in order to obtain a match.
+*
+*     If the template's Permute value is zero, it will match a target
+*     only if it can do so without changing the order of its
+*     axes. Otherwise, it will attempt to permute the target's axes as
+*     necessary.
+*
+*     The default value is 1, so that axis permutation will be attempted.
+
+*  Applicability:
+*     Frame
+*        All Frames have this attribute. However, the Frame class
+*        effectively ignores this attribute and behaves as if it has
+*        the value 1. This is because the axes of a basic Frame are
+*        not distinguishable and will always match any other Frame
+*        whatever their order.
+*     SkyFrame
+*        Unlike a basic Frame, the SkyFrame class makes use of this
+*        attribute.
+*     FrameSet
+*        The Permute attribute of a FrameSet is the same as that of
+*        its current Frame (as specified by the Current attribute).
+*att--
+*/
+/* Clear the Permute value by setting it to -INT_MAX. */
+astMAKE_CLEAR(Frame,Permute,permute,-INT_MAX)
+
+/* Supply a default of 1 if no Permute value has been set. */
+astMAKE_GET(Frame,Permute,int,0,( ( this->permute != -INT_MAX ) ?
+                                  this->permute : 1 ))
+
+/* Set a Permute value of 1 if any non-zero value is supplied. */
+astMAKE_SET(Frame,Permute,int,permute,( value != 0 ))
+
+/* The Permute value is set if it is not -INT_MAX. */
+astMAKE_TEST(Frame,Permute,( this->permute != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     PreserveAxes
+
+*  Purpose:
+*     Preserve axes?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls how a Frame behaves when it is used (by
+c     astFindFrame) as a template to match another (target) Frame. It
+f     AST_FINDFRAME) as a template to match another (target) Frame. It
+*     determines which axes appear (and in what order) in the "result"
+*     Frame produced.
+*
+*     If PreserveAxes is zero in the template Frame, then the result
+*     Frame will have the same number (and order) of axes as the
+*     template. If it is non-zero, however, the axes of the target
+*     Frame will be preserved, so that the result Frame will have the
+*     same number (and order) of axes as the target.
+*
+*     The default value is zero, so that target axes are not preserved
+*     and the result Frame resembles the template.
+
+*  Applicability:
+*     Frame
+*        All Frames have this attribute.
+*     FrameSet
+*        The PreserveAxes attribute of a FrameSet is the same as that
+*        of its current Frame (as specified by the Current attribute).
+*att--
+*/
+/* Clear the PreserveAxes value by setting it to -INT_MAX. */
+astMAKE_CLEAR(Frame,PreserveAxes,preserve_axes,-INT_MAX)
+
+/* Supply a default of 0 if no PreserveAxes value has been set. */
+astMAKE_GET(Frame,PreserveAxes,int,0,( ( this->preserve_axes != -INT_MAX ) ?
+                                       this->preserve_axes : 0 ))
+
+/* Set a PreserveAxes value of 1 if any non-zero value is supplied. */
+astMAKE_SET(Frame,PreserveAxes,int,preserve_axes,( value != 0 ))
+
+/* The PreserveAxes value is set if it is not -INT_MAX. */
+astMAKE_TEST(Frame,PreserveAxes,( this->preserve_axes != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     AlignSystem
+
+*  Purpose:
+*     Coordinate system in which to align the Frame.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute controls how a Frame behaves when it is used (by
+c     astFindFrame or astConvert) as a template to match another (target)
+f     AST_FINDFRAME or AST_CONVERT) as a template to match another (target)
+*     Frame. It identifies the coordinate system in which the two Frames
+*     will be aligned by the match.
+*
+*     The values which may be assigned to this attribute, and its default
+*     value, depend on the class of Frame and are described in the
+*     "Applicability" section below. In general, the AlignSystem attribute
+*     will accept any of the values which may be assigned to the System
+*     attribute.
+*
+c     The Mapping returned by AST_FINDFRAME or AST_CONVERT will use the
+f     The Mapping returned by astFindFrame or astConvert will use the
+*     coordinate system specified by the AlignSystem attribute as an
+*     intermediate coordinate system. The total returned Mapping will first
+*     map positions from the first Frame into this intermediate coordinate
+*     system, using the attributes of the first Frame. It will then map
+*     these positions from the intermediate coordinate system into the
+*     second Frame, using the attributes of the second Frame.
+
+*  Applicability:
+*     Frame
+*        The AlignSystem attribute for a basic Frame always equals "Cartesian",
+*        and may not be altered.
+*     CmpFrame
+*        The AlignSystem attribute for a CmpFrame always equals "Compound",
+*        and may not be altered.
+*     FrameSet
+*        The AlignSystem attribute of a FrameSet is the same as that of its
+*        current Frame (as specified by the Current attribute).
+*     SkyFrame
+*        The default AlignSystem attribute for a SkyFrame is "ICRS".
+*     SpecFrame
+*        The default AlignSystem attribute for a SpecFrame is "Wave"
+*        (wavelength).
+*     TimeFrame
+*        The default AlignSystem attribute for a TimeFrame is "MJD".
+*att--
+*/
+/* Clear the AlignSystem value by setting it to AST__BADSYSTEM. */
+astMAKE_CLEAR(Frame,AlignSystem,alignsystem,AST__BADSYSTEM)
+
+/* Provide a default AlignSystem of AST__CART. */
+astMAKE_GET(Frame,AlignSystem,AstSystemType,AST__BADSYSTEM,(
+            ( this->alignsystem == AST__BADSYSTEM ) ? AST__CART : this->alignsystem ) )
+
+/* Validate the AlignSystem value being set and retain the original if the
+   supplied value is not recognized. */
+astMAKE_SET(Frame,AlignSystem,AstSystemType,alignsystem,(
+           (astValidateSystem( this, value, "astSetAlignSystem" ) != AST__BADSYSTEM) ?
+            value : this->alignsystem ))
+
+/* The AlignSystem value is set if it is not AST__BADSYSTEM. */
+astMAKE_TEST(Frame,AlignSystem,( this->alignsystem != AST__BADSYSTEM ))
+
+/*
+*att++
+*  Name:
+*     System
+
+*  Purpose:
+*     Coordinate system used to describe positions within the domain
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     In general it is possible for positions within a given physical
+*     domain to be described using one of several different coordinate
+*     systems. For instance, the SkyFrame class can use galactic
+*     coordinates, equatorial coordinates, etc, to describe positions on
+*     the sky. As another example, the SpecFrame class can use frequency,
+*     wavelength, velocity, etc, to describe a position within an
+*     electromagnetic spectrum. The System attribute identifies the particular
+*     coordinate system represented by a Frame. Each class of Frame
+*     defines a set of acceptable values for this attribute, as listed
+*     below (all are case insensitive). Where more than one alternative
+*     System value is shown, the first of will be returned when an
+*     enquiry is made.
+
+*  Applicability:
+*     Frame
+*        The System attribute for a basic Frame always equals "Cartesian",
+*        and may not be altered.
+*     CmpFrame
+*        The System attribute for a CmpFrame always equals "Compound",
+*        and may not be altered. In addition, the CmpFrame class allows
+*        the System attribute to be referenced for a component Frame by
+*        including the index of an axis within the required component
+*        Frame. For instance, "System(3)" refers to the System attribute
+*        of the component Frame which includes axis 3 of the CmpFrame.
+*     FrameSet
+*        The System attribute of a FrameSet is the same as that of its
+*        current Frame (as specified by the Current attribute).
+*     SkyFrame
+*        The SkyFrame class supports the following System values and
+*        associated celestial coordinate systems:
+*
+*        - "AZEL": Horizon coordinates. The longitude axis is azimuth
+*        such that geographic north has an azimuth of zero and geographic
+*        east has an azimuth of +PI/2 radians. The zenith has elevation
+*        +PI/2. When converting to and from other celestial coordinate
+*        systems, no corrections are applied for atmospheric refraction
+*        or polar motion (however, a correction for diurnal aberattion is
+*        applied). Note, unlike most other
+*        celestial coordinate systems, this system is right handed. Also,
+*        unlike other SkyFrame systems, the AzEl system is sensitive to
+*        the timescale in which the Epoch value is supplied. This is
+*        because of the gross diurnal rotation which this system undergoes,
+*        causing a small change in time to translate to a large rotation.
+*        When converting to or from an AzEl system, the Epoch value for
+*        both source and destination SkyFrames should be supplied in the
+*        TDB timescale. The difference between TDB and TT is between 1
+*        and 2 milliseconds, and so a TT value can usually be supplied in
+*        place of a TDB value. The TT timescale is related to TAI via
+*        TT = TAI + 32.184 seconds.
+*
+*        - "ECLIPTIC": Ecliptic coordinates (IAU 1980), referred to the
+*        ecliptic and mean equinox specified by the qualifying Equinox
+*        value.
+*
+*        - "FK4": The old FK4 (barycentric) equatorial coordinate system,
+*        which should be qualified by an Equinox value. The underlying
+*        model on which this is based is non-inertial and rotates slowly
+*        with time, so for accurate work FK4 coordinate systems should
+*        also be qualified by an Epoch value.
+*
+*        - "FK4-NO-E" or "FK4_NO_E": The old FK4 (barycentric) equatorial
+*        system but without the "E-terms of aberration" (e.g. some radio
+*        catalogues). This coordinate system should also be qualified by
+*        both an Equinox and an Epoch value.
+*
+*        - "FK5" or "EQUATORIAL": The modern FK5 (barycentric) equatorial
+*        coordinate system. This should be qualified by an Equinox value.
+*
+*        - "GALACTIC": Galactic coordinates (IAU 1958).
+*
+*        - "GAPPT", "GEOCENTRIC" or "APPARENT": The geocentric apparent
+*        equatorial coordinate system, which gives the apparent positions
+*        of sources relative to the true plane of the Earth's equator and
+*        the equinox (the coordinate origin) at a time specified by the
+*        qualifying Epoch value. (Note that no Equinox is needed to
+*        qualify this coordinate system because no model "mean equinox"
+*        is involved.)  These coordinates give the apparent right
+*        ascension and declination of a source for a specified date of
+*        observation, and therefore form an approximate basis for
+*        pointing a telescope. Note, however, that they are applicable to
+*        a fictitious observer at the Earth's centre, and therefore
+*        ignore such effects as atmospheric refraction and the (normally
+*        much smaller) aberration of light due to the rotational velocity
+*        of the Earth's surface.  Geocentric apparent coordinates are
+*        derived from the standard FK5 (J2000.0) barycentric coordinates
+*        by taking account of the gravitational deflection of light by
+*        the Sun (usually small), the aberration of light caused by the
+*        motion of the Earth's centre with respect to the barycentre
+*        (larger), and the precession and nutation of the Earth's spin
+*        axis (normally larger still).
+*
+*        - "HELIOECLIPTIC": Ecliptic coordinates (IAU 1980), referred to the
+*        ecliptic and mean equinox of J2000.0, in which an offset is added to
+*        the longitude value which results in the centre of the sun being at
+*        zero longitude at the date given by the Epoch attribute. Attempts to
+*        set a value for the Equinox attribute will be ignored, since this
+*        system is always referred to J2000.0.
+*
+*        - "ICRS": The Internation Celestial Reference System, realised
+*        through the Hipparcos catalogue. Whilst not an equatorial system
+*        by definition, the ICRS is very close to the FK5 (J2000) system
+*        and is usually treated as an equatorial system. The distinction
+*        between ICRS and FK5 (J2000) only becomes important when accuracies
+*        of 50 milli-arcseconds or better are required. ICRS need not be
+*        qualified by an Equinox value.
+*
+*        - "J2000": An equatorial coordinate system based on the mean
+*        dynamical equator and equinox of the J2000 epoch. The dynamical
+*        equator and equinox differ slightly from those used by the FK5
+*        model, and so a "J2000" SkyFrame will differ slightly from an
+*        "FK5(Equinox=J2000)" SkyFrame. The J2000 System need not be
+*        qualified by an Equinox value
+*
+*        - "SUPERGALACTIC": De Vaucouleurs Supergalactic coordinates.
+*
+*        - "UNKNOWN": Any other general spherical coordinate system. No
+*        Mapping can be created between a pair of SkyFrames if either of the
+*        SkyFrames has System set to "Unknown".
+*
+*        Currently, the default System value is "ICRS". However, this
+*        default may change in future as new astrometric standards
+*        evolve. The intention is to track the most modern appropriate
+*        standard. For this reason, you should use the default only if
+*        this is what you intend (and can tolerate any associated slight
+*        change in future). If you intend to use the ICRS system
+*        indefinitely, then you should specify it explicitly.
+*     SpecFrame
+*        The SpecFrame class supports the following System values and
+*        associated spectral coordinate systems (the default is "WAVE" -
+*        wavelength). They are all defined in FITS-WCS paper III:
+*
+*        - "FREQ": Frequency (GHz)
+*        - "ENER" or "ENERGY": Energy (J)
+*        - "WAVN" or "WAVENUM": Wave-number (1/m)
+*        - "WAVE" or "WAVELEN": Vacuum wave-length (Angstrom)
+*        - "AWAV" or "AIRWAVE": Wave-length in air (Angstrom)
+*        - "VRAD" or "VRADIO": Radio velocity (km/s)
+*        - "VOPT" or "VOPTICAL": Optical velocity (km/s)
+*        - "ZOPT" or "REDSHIFT": Redshift (dimensionless)
+*        - "BETA": Beta factor (dimensionless)
+*        - "VELO" or "VREL": Apparent radial ("relativistic") velocity (km/s)
+*
+*        The default value for the Unit attribute for each system is shown
+*        in parentheses. Note that the default value for the ActiveUnit flag
+c        is non-zero
+f        is .TRUE.
+*        for a SpecFrame, meaning that changes to the Unit attribute for
+*        a SpecFrame will result in the SpecFrame being re-mapped within
+*        its enclosing FrameSet in order to reflect the change in units
+c        (see astSetActiveUnit function for further information).
+f        (see AST_SETACTIVEUNIT routine for further information).
+*     TimeFrame
+*        The TimeFrame class supports the following System values and
+*        associated coordinate systems (the default is "MJD"):
+*
+*        - "MJD": Modified Julian Date (d)
+*        - "JD": Julian Date (d)
+*        - "JEPOCH": Julian epoch (yr)
+*        - "BEPOCH": Besselian (yr)
+*
+*        The default value for the Unit attribute for each system is shown
+*        in parentheses. Strictly, these systems should not allow changes
+*        to be made to the units. For instance, the usual definition of
+*        "MJD" and "JD" include the statement that the values will be in
+*        units of days. However, AST does allow the use of other units
+*        with all the above supported systems (except BEPOCH), on the
+*        understanding that conversion to the "correct" units involves
+*        nothing more than a simple scaling (1 yr = 365.25 d, 1 d = 24 h,
+*        1 h = 60 min, 1 min = 60 s). Besselian epoch values are defined
+*        in terms of tropical years of 365.2422 days, rather than the
+*        usual Julian year of 365.25 days. Therefore, to avoid any
+*        confusion, the Unit attribute is automatically cleared to "yr" when
+*        a System value of BEPOCH System is selected, and an error is
+*        reported if any attempt is subsequently made to change the Unit
+*        attribute.
+*
+*        Note that the default value for the ActiveUnit flag
+c        is non-zero
+f        is .TRUE.
+*        for a TimeFrame, meaning that changes to the Unit attribute for
+*        a TimeFrame will result in the TimeFrame being re-mapped within
+*        its enclosing FrameSet in order to reflect the change in units
+c        (see astSetActiveUnit function for further information).
+f        (see AST_SETACTIVEUNIT routine for further information).
+*     FluxFrame
+*        The FluxFrame class supports the following System values and
+*        associated systems for measuring observed value:
+*
+*        - "FLXDN": Flux per unit frequency (W/m^2/Hz)
+*        - "FLXDNW": Flux per unit wavelength (W/m^2/Angstrom)
+*        - "SFCBR": Surface brightness in frequency units (W/m^2/Hz/arcmin**2)
+*        - "SFCBRW": Surface brightness in wavelength units (W/m^2/Angstrom/arcmin**2)
+*
+*        The above lists specified the default units for each System. If an
+*        explicit value is set for the Unit attribute but no value is set
+*        for System, then the default System value is determined by the Unit
+*        string (if the units are not appropriate for describing any of the
+*        supported Systems then an error will be reported when an attempt is
+*        made to access the System value). If no value has been specified for
+*        either Unit or System, then System=FLXDN and Unit=W/m^2/Hz are
+*        used.
+*att--
+*/
+/* Clear the System value by setting it to AST__BADSYSTEM. */
+astMAKE_CLEAR(Frame,System,system,AST__BADSYSTEM)
+
+/* Provide a default coordinate system of AST__CART. */
+astMAKE_GET(Frame,System,AstSystemType,AST__BADSYSTEM,(
+            ( this->system == AST__BADSYSTEM ) ? AST__CART : this->system ) )
+
+/* Validate the System value being set and retain the original if the
+   supplied value is not recognized. */
+astMAKE_SET(Frame,System,AstSystemType,system,(
+           (astValidateSystem( this, value, "astSetSystem" ) != AST__BADSYSTEM) ?
+            value : this->system ))
+
+/* The System value is set if it is not AST__BADSYSTEM. */
+astMAKE_TEST(Frame,System,( this->system != AST__BADSYSTEM ))
+
+/*
+*att++
+*  Name:
+*     Title
+
+*  Purpose:
+*     Frame title.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute holds a string which is used as a title in (e.g.)
+*     graphical output to describe the coordinate system which a Frame
+*     represents. Examples might be "Detector Coordinates" or
+*     "Galactic Coordinates".
+*
+*     If a Title value has not been set for a Frame, then a suitable
+*     default is supplied, depending on the class of the Frame.
+
+*  Applicability:
+*     Frame
+*        The default supplied by the Frame class is "<n>-d coordinate
+*        system", where <n> is the number of Frame axes (Naxes
+*        attribute).
+*     CmpFrame
+*        The CmpFrame class re-defines the default Title value to be
+*        "<n>-d compound coordinate system", where <n> is the number
+*        of CmpFrame axes (Naxes attribute).
+*     FrameSet
+*        The Title attribute of a FrameSet is the same as that of its
+*        current Frame (as specified by the Current attribute).
+
+*  Notes:
+*     - A Frame's Title is intended purely for interpretation by human
+*     readers and not by software.
+*att--
+*/
+/* Clear the Title value by freeing the allocated memory and assigning
+   a NULL pointer. */
+astMAKE_CLEAR(Frame,Title,title,astFree( this->title ))
+
+/* If the Title value is not set, write a default based on the number of Frame
+   axes into the static "title_buff" buffer, and return a pointer to this
+   buffer. */
+astMAKE_GET(Frame,Title,const char *,NULL,( this->title ?
+                                            this->title : GetDefaultTitle( this, status ) ))
+
+/* Set a Title value by freeing any previously allocated memory, allocating
+   new memory, storing the string and saving the pointer to the copy. */
+astMAKE_SET(Frame,Title,const char *,title,astStore( this->title, value,
+                                           strlen( value ) + (size_t) 1 ))
+
+/* The Title value is set if the pointer to it is not NULL. */
+astMAKE_TEST(Frame,Title,( this->title != NULL ))
+
+/*
+*att++
+*  Name:
+*     ObsLat
+
+*  Purpose:
+*     The geodetic latitude of the observer
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies the geodetic latitude of the observer, in
+*     degrees, relative to the IAU 1976 reference ellipsoid. The basic Frame
+*     class makes no use of this attribute, but specialised subclasses of
+*     Frame may use it. For instance, the SpecFrame, SkyFrame and TimeFrame
+*     classes use it. The default value is zero.
+*
+*     The value is stored internally in radians, but is converted to and
+*     from a degrees string for access. Some example input formats are:
+*     "22:19:23.2", "22 19 23.2", "22:19.387", "22.32311", "N22.32311",
+*     "-45.6", "S45.6". As indicated, the sign of the latitude can
+*     optionally be indicated using characters "N" and "S" in place of the
+*     usual "+" and "-". When converting the stored value to a string, the
+*     format "[s]dd:mm:ss.ss" is used, when "[s]" is "N" or "S".
+
+*  Applicability:
+*     Frame
+*        All Frames have this attribute.
+*     SpecFrame
+*        Together with the ObsLon, Epoch, RefRA and RefDec attributes,
+*        it defines the Doppler shift introduced by the observers diurnal
+*        motion around the earths axis, which is needed when converting to
+*        or from the topocentric standard of rest. The maximum velocity
+*        error which can be caused by an incorrect value is 0.5 km/s. The
+*        default value for the attribute is zero.
+*     TimeFrame
+*        Together with the ObsLon attribute, it is used when converting
+*        between certain time scales (TDB, TCB, LMST, LAST)
+
+*att--
+*/
+/* The geodetic latitude of the observer (radians). Clear the ObsLat value by
+   setting it to AST__BAD, returning zero as the default value. Any value is
+   acceptable. */
+astMAKE_CLEAR(Frame,ObsLat,obslat,AST__BAD)
+astMAKE_GET(Frame,ObsLat,double,0.0,((this->obslat!=AST__BAD)?this->obslat:0.0))
+astMAKE_SET(Frame,ObsLat,double,obslat,value)
+astMAKE_TEST(Frame,ObsLat,(this->obslat!=AST__BAD))
+
+
+/*
+*att++
+*  Name:
+*     ObsAlt
+
+*  Purpose:
+*     The geodetic altitude of the observer
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute specifies the geodetic altitude of the observer, in
+*     metres, relative to the IAU 1976 reference ellipsoid. The basic Frame
+*     class makes no use of this attribute, but specialised subclasses of
+*     Frame may use it. For instance, the SpecFrame, SkyFrame and TimeFrame
+*     classes use it. The default value is zero.
+
+*  Applicability:
+*     Frame
+*        All Frames have this attribute.
+*     SpecFrame
+*        Together with the ObsLon, Epoch, RefRA and RefDec attributes,
+*        it defines the Doppler shift introduced by the observers diurnal
+*        motion around the earths axis, which is needed when converting to
+*        or from the topocentric standard of rest. The maximum velocity
+*        error which can be caused by an incorrect value is 0.5 km/s. The
+*        default value for the attribute is zero.
+*     TimeFrame
+*        Together with the ObsLon attribute, it is used when converting
+*        between certain time scales (TDB, TCB, LMST, LAST)
+
+*att--
+*/
+/* The geodetic altitude of the observer (metres). Clear the ObsAlt value by
+   setting it to AST__BAD, returning zero as the default value. Any value is
+   acceptable. */
+astMAKE_CLEAR(Frame,ObsAlt,obsalt,AST__BAD)
+astMAKE_GET(Frame,ObsAlt,double,0.0,((this->obsalt!=AST__BAD)?this->obsalt:0.0))
+astMAKE_SET(Frame,ObsAlt,double,obsalt,value)
+astMAKE_TEST(Frame,ObsAlt,(this->obsalt!=AST__BAD))
+
+
+/*
+*att++
+*  Name:
+*     ObsLon
+
+*  Purpose:
+*     The geodetic longitude of the observer
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies the geodetic (or equivalently, geocentric)
+*     longitude of the observer, in degrees, measured positive eastwards.
+*     See also attribute ObsLat. The basic Frame class makes no use of this
+*     attribute, but specialised subclasses of Frame may use it. For instance,
+*     the SpecFrame, SkyFrame and TimeFrame classes use it. The default value
+*     is zero.
+*
+*     The value is stored internally in radians, but is converted to and
+*     from a degrees string for access. Some example input formats are:
+*     "155:19:23.2", "155 19 23.2", "155:19.387", "155.32311", "E155.32311",
+*     "-204.67689", "W204.67689". As indicated, the sign of the longitude can
+*     optionally be indicated using characters "E" and "W" in place of the
+*     usual "+" and "-". When converting the stored value to a string, the
+*     format "[s]ddd:mm:ss.ss" is used, when "[s]" is "E" or "W" and the
+*     numerical value is chosen to be less than 180 degrees.
+
+*  Applicability:
+*     Frame
+*        All Frames have this attribute.
+*     SpecFrame
+*        Together with the ObsLon, Epoch, RefRA and RefDec attributes,
+*        it defines the Doppler shift introduced by the observers diurnal
+*        motion around the earths axis, which is needed when converting to
+*        or from the topocentric standard of rest. The maximum velocity
+*        error which can be caused by an incorrect value is 0.5 km/s. The
+*        default value for the attribute is zero.
+*     TimeFrame
+*        Together with the ObsLon attribute, it is used when converting
+*        between certain time scales (TDB, TCB, LMST, LAST)
+
+*att--
+*/
+/* The geodetic longitude of the observer (radians). Clear the ObsLon value by
+   setting it to AST__BAD, returning zero as the default value. Any value is
+   acceptable. */
+astMAKE_CLEAR(Frame,ObsLon,obslon,AST__BAD)
+astMAKE_GET(Frame,ObsLon,double,0.0,((this->obslon!=AST__BAD)?this->obslon:0.0))
+astMAKE_SET(Frame,ObsLon,double,obslon,value)
+astMAKE_TEST(Frame,ObsLon,(this->obslon!=AST__BAD))
+
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Frame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Frame objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstFrame *in;                 /* Pointer to input Frame */
+   AstFrame *out;                /* Pointer to output Frame */
+   int axis;                     /* Loop counter for axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Frames. */
+   in = (AstFrame *) objin;
+   out = (AstFrame *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output Frame. */
+   out->axis = NULL;
+   out->domain = NULL;
+   out->perm = NULL;
+   out->title = NULL;
+   out->variants = NULL;
+
+/* If necessary, allocate memory in the output Frame and store a copy of the
+   input Title and Domain strings. */
+   if ( in->title ) out->title = astStore( NULL, in->title,
+                                           strlen( in->title ) + (size_t) 1 );
+   if ( in->domain ) out->domain = astStore( NULL, in->domain,
+                                             strlen( in->domain ) +
+                                             (size_t) 1 );
+
+/* Allocate memory to hold the output Frame's Axis object pointers and its axis
+   permutation array. */
+   out->axis = astMalloc( sizeof( AstAxis * ) * (size_t) in->naxes );
+   out->perm = astMalloc( sizeof( int ) * (size_t) in->naxes );
+
+/* Make a copy of each of the input Frame's Axis objects, storing the pointer
+   to each new Axis in the memory just allocated. Also copy the axis
+   permutation array. */
+   if ( astOK ) {
+      for ( axis = 0; axis < in->naxes; axis++ ) {
+         out->axis[ axis ] = astCopy( in->axis[ axis ] );
+         out->perm[ axis ] = in->perm[ axis ];
+      }
+
+/* If an error occurred while copying the Axis objects, then loop through the
+   resulting array of pointers and make sure that all of them are properly
+   annulled. */
+      if ( !astOK ) {
+         for ( axis = 0; axis < in->naxes; axis++ ) {
+            out->axis[ axis ] = astAnnul( out->axis[ axis ] );
+         }
+      }
+   }
+
+/* Other remaining objects */
+   if( in->variants ) out->variants = astCopy( in->variants );
+
+/* If an error occurred, free any allocated memory. */
+   if ( !astOK ) {
+      out->axis = astFree( out->axis );
+      out->domain = astFree( out->domain );
+      out->perm = astFree( out->perm );
+      out->title = astFree( out->title );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Frame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Frame objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstFrame *this;               /* Pointer to Frame */
+   int axis;                     /* Loop counter for Frame axes */
+
+/* Obtain a pointer to the Frame structure. */
+   this = (AstFrame *) obj;
+
+/* Free the memory used for the Title and Domain strings if necessary. */
+   this->title = astFree( this->title );
+   this->domain = astFree( this->domain );
+
+/* If memory has been allocated to store pointers to the Frame's Axis objects,
+   annul each of these pointers and then free the memory. */
+   if ( this->axis ) {
+      for ( axis = 0; axis < this->naxes; axis++ ) {
+         this->axis[ axis ] = astAnnul( this->axis[ axis ] );
+      }
+      this->axis = astFree( this->axis );
+   }
+
+/* Free memory used for the axis permutation array if necessary. */
+   this->perm = astFree( this->perm );
+
+/* Other objects. */
+    if( this->variants ) this->variants = astAnnul( this->variants );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Frame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Frame class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define COMMENT_LEN 150          /* Maximum length of a comment string */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis */
+   AstFrame *cfrm;               /* Pointer to FrameSet's current Frame */
+   AstFrame *this;               /* Pointer to the Frame structure */
+   AstSystemType system;         /* System code */
+   char comment[ COMMENT_LEN + 1 ]; /* Buffer for comment strings */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keywords */
+   const char *sval;             /* Pointer to string value */
+   const char *lab;              /* Pointer to unit label */
+   const int *perm;              /* Pointer to axis permutation array */
+   double dval;                  /* Double attibute value */
+   int *invperm;                 /* Pointer to inverse permutation array */
+   int axis;                     /* Loop counter for Frame axes */
+   int bessyr;                   /* Format as Besselian years (else Julian) */
+   int digits_set;               /* Digits set explicitly for any axis? */
+   int full;                     /* Full attribute value */
+   int full_set;                 /* Full attribute set? */
+   int helpful;                  /* Helpful to show value even if not set? */
+   int isFrame;                  /* Is this a simple Frame? */
+   int ival;                     /* Integer value */
+   int naxes;                    /* Number of Frame axes */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Frame structure. */
+   this = (AstFrame *) this_object;
+
+/* Determine the number of Frame axes and a pointer to the Frame's
+   axis permutation array (using methods, to allow for any over-ride
+   by a derived class). */
+   naxes = astGetNaxes( this );
+   perm = astGetPerm( this );
+
+/* Some default attribute values are not helpful for a simple Frame. Note
+   if this is a simple Frame, or if it is a FrameSet with a simple Frame
+   as its current Frame., or if it is a CmpFrame. */
+   if( !strcmp( astGetClass( this ), "Frame" ) ) {
+      isFrame = 1;
+   } else if( astIsAFrameSet( this ) ) {
+      cfrm = astGetFrame( (AstFrameSet *) this, AST__CURRENT );
+      isFrame = !strcmp( astGetClass( cfrm ), "Frame" );
+      cfrm = astAnnul( cfrm );
+   } else if( astIsACmpFrame( this ) ) {
+      isFrame = 1;
+   } else {
+      isFrame = 0;
+   }
+
+/* Allocate memory to hold an inverse axis permutation array and
+   generate this array from the forward permutation values. This will
+   be used to determine which axis should be enquired about (using
+   possibly over-ridden methods) to obtain data to correspond with a
+   particular internal value (i.e. instance variable) relating to an
+   axis. This step is needed so that the effect of any axis
+   permutation can be un-done before values are written out, as output
+   values are written by this function in un-permuted order. */
+   invperm = astMalloc( sizeof( int ) * (size_t) naxes );
+   if ( astOK ) {
+      for ( axis = 0; axis < naxes; axis++ ) invperm[ perm[ axis ] ] = axis;
+
+/* Write out values representing the instance variables for the Frame
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Title. */
+/* ------ */
+      set = TestTitle( this, status );
+      sval = set ? GetTitle( this, status ) : astGetTitle( this );
+      astWriteString( channel, "Title", set, 1, sval,
+                      "Title of coordinate system" );
+
+/* Naxes. */
+/* ------ */
+      set = ( this->naxes != 0 );
+      ival = set ? this->naxes : naxes;
+      astWriteInt( channel, "Naxes", set, 1, ival,
+                   "Number of coordinate axes" );
+
+/* Domain. */
+/* ------- */
+      set = TestDomain( this, status );
+      sval = set ? GetDomain( this, status ) : astGetDomain( this );
+
+/* Don't show an un-set Domain value if it is blank. */
+      helpful = ( sval && *sval );
+      astWriteString( channel, "Domain", set, helpful, sval,
+                      "Coordinate system domain" );
+
+/* Epoch. */
+/* ------ */
+      set = TestEpoch( this, status );
+      dval = set ? GetEpoch( this, status ) : astGetEpoch( this );
+
+/* Convert MJD to Besselian or Julian years, depending on the value. */
+      bessyr = ( dval < palEpj2d( 1984.0 ) );
+      dval = bessyr ? palEpb( dval ) : palEpj( dval );
+      astWriteDouble( channel, "Epoch", set, !isFrame, dval,
+                      bessyr ? "Besselian epoch of observation" :
+                               "Julian epoch of observation" );
+
+/* Label. */
+/* ------ */
+/* This, and some other, attributes are stored internally by the
+   Frame's Axis objects, but are "re-packaged" by the Frame class to
+   appear as Frame attributes. We treat them here like Frame
+   attributes that are "un-set". There is a Label value for each Frame
+   axis. */
+      for ( axis = 0; axis < naxes; axis++ ) {
+
+/* The inverse axis permutation array is used to obtain the axis index
+   for astGetLabel. This reverses the effect of the Frame's axis
+   permutation array and yields a default value appropriate to the
+   axis with internal index "axis". */
+         sval = astGetLabel( this, invperm[ axis ] );
+
+/* Create keyword and comment strings appropriate to each axis
+   (converting to 1-based axis numbering) and write out the Label
+   values. */
+         (void) sprintf( key, "Lbl%d", axis + 1 );
+         (void) sprintf( comment, "Label for axis %d", axis + 1 );
+         astWriteString( channel, key, 0, 1, sval, comment );
+      }
+
+/* Symbol. */
+/* ------- */
+/* There is a Symbol value for each Frame axis. These are handled in
+   the same way as the Label values. */
+      for ( axis = 0; axis < naxes; axis++ ) {
+         sval = astGetSymbol( this, invperm[ axis ] );
+         (void) sprintf( key, "Sym%d", axis + 1 );
+         (void) sprintf( comment, "Symbol for axis %d", axis + 1 );
+         astWriteString( channel, key, 0, 0, sval, comment );
+      }
+
+/* System. */
+/* ------- */
+      set = TestSystem( this, status );
+      system = set ? GetSystem( this, status ) : astGetSystem( this );
+
+/* If set, convert explicitly to a string for the external representation. */
+      if ( set ) {
+         if ( astOK ) {
+            sval = astSystemString( this, system );
+
+/* Report an error if the System value was not recognised. */
+            if ( !sval ) {
+               astError( AST__SCSIN,
+                        "astWrite(%s): Corrupt %s contains invalid "
+                        "System identification code (%d).", status,
+                        astGetClass( channel ), astGetClass( this ),
+                        (int) system );
+            }
+         }
+
+/* If not set, use astGetAttrib which returns a string value using
+   (possibly over-ridden) methods. */
+      } else {
+         sval = astGetAttrib( this_object, "system" );
+      }
+
+/* Write out the value. */
+      astWriteString( channel, "System", set, !isFrame, sval,
+                      "Coordinate system type" );
+
+/* AlignSystem. */
+/* ------------ */
+      set = TestAlignSystem( this, status );
+      system = set ? GetAlignSystem( this, status ) : astGetAlignSystem( this );
+
+/* If set, convert explicitly to a string for the external representation. */
+      if ( set ) {
+         if ( astOK ) {
+            sval = astSystemString( this, system );
+
+/* Report an error if the AlignSystem value was not recognised. */
+            if ( !sval ) {
+               astError( AST__SCSIN,
+                        "astWrite(%s): Corrupt %s contains invalid "
+                        "AlignSystem identification code (%d).", status,
+                        astGetClass( channel ), astGetClass( this ),
+                        (int) system );
+            }
+         }
+
+/* If not set, use astGetAttrib which returns a string value using
+   (possibly over-ridden) methods. */
+      } else {
+         sval = astGetAttrib( this_object, "alignsystem" );
+      }
+
+/* Write out the value. */
+      astWriteString( channel, "AlSys", set, 0, sval,
+                      "Alignment coordinate system" );
+
+/* Unit. */
+/* ----- */
+/* There is a Unit value for each axis. */
+      for ( axis = 0; axis < naxes; axis++ ) {
+         sval = astGetUnit( this, invperm[ axis ] );
+
+/* Get any label associated with the unit string. */
+         lab = astUnitLabel( sval );
+
+/* Construct a comment including the above label (but only if it is not
+   the same as the unit string) . */
+         if( lab && strcmp( lab, sval ) ) {
+            (void) sprintf( comment, "Units for axis %d (%s)", axis + 1, lab );
+         } else {
+            (void) sprintf( comment, "Units for axis %d", axis + 1 );
+         }
+
+/* Show the Unit value if it is not blank. */
+         helpful = ( sval && *sval );
+         (void) sprintf( key, "Uni%d", axis + 1 );
+         astWriteString( channel, key, 0, helpful, sval, comment );
+      }
+
+/* Digits. */
+/* ------- */
+/* There is a Digits value for each axis... */
+      digits_set = 0;
+      for ( axis = 0; axis < naxes; axis++ ) {
+
+/* Obtain the axis Digits value, using the Frame's Digits value as a
+   default. */
+         ax = astGetAxis( this, invperm[ axis ] );
+         set = astTestAxisDigits( ax );
+         ival = set ? astGetAxisDigits( ax ) : astGetDigits( this );
+         ax = astAnnul( ax );
+
+/* Show the value if it is set for the axis (i.e. if it differs from
+   the default for the whole Frame) and note if any such value is
+   set. */
+         helpful = set;
+         if ( set ) digits_set = 1;
+         (void) sprintf( key, "Dig%d", axis + 1 );
+         (void) sprintf( comment, "Individual precision for axis %d",
+                         axis + 1 );
+         astWriteInt( channel, key, 0, helpful, ival, comment );
+      }
+
+/* There is also a Digits value for the Frame as a whole... */
+      set = TestDigits( this, status );
+
+/* Show the value (even if not set) if an explicit Digits value has
+   been set for any axis (above). */
+      helpful = digits_set;
+      ival = set ? GetDigits( this, status ) : astGetDigits( this );
+      astWriteInt( channel, "Digits", set, helpful, ival,
+                   "Default formatting precision" );
+
+/* Format. */
+/* ------- */
+/* There is a Format value for each axis. */
+      for ( axis = 0; axis < naxes; axis++ ) {
+         sval = astGetFormat( this, invperm[ axis ] );
+
+/* Show the Format value if the Digits value is set for an individual
+   axis. */
+         ax = astGetAxis( this, invperm[ axis ] );
+         helpful = astTestAxisDigits( ax );
+         ax = astAnnul( ax );
+         (void) sprintf( key, "Fmt%d", axis + 1 );
+         (void) sprintf( comment, "Format specifier for axis %d", axis + 1 );
+         astWriteString( channel, key, 0, helpful, sval, comment );
+      }
+
+/* Direction. */
+/* ---------- */
+/* There is a Direction value for each axis. */
+      for ( axis = 0; axis < naxes; axis++ ) {
+         ival = astGetDirection( this, invperm[ axis ] );
+
+/* Show the value if it is zero. */
+         helpful = ( ival == 0 );
+         (void) sprintf( key, "Dir%d", axis + 1 );
+         (void) sprintf( comment,
+                         ival ? "Plot axis %d in conventional direction" :
+                                "Plot axis %d in reverse direction",
+                         axis + 1 );
+         astWriteInt( channel, key, 0, helpful, ival, comment );
+      }
+
+/* Bottom. */
+/* ------- */
+/* There is a Bottom value for each axis. */
+      for ( axis = 0; axis < naxes; axis++ ) {
+         dval = astGetBottom( this, invperm[ axis ] );
+
+/* Show the value if it is zero. */
+         helpful = ( dval != -DBL_MAX );
+         (void) sprintf( key, "Bot%d", axis + 1 );
+         astWriteDouble( channel, key, 0, helpful, dval, "Lowest legal axis value");
+      }
+
+/* Top. */
+/* ------- */
+/* There is a Top value for each axis. */
+      for ( axis = 0; axis < naxes; axis++ ) {
+         dval = astGetTop( this, invperm[ axis ] );
+
+/* Show the value if it is zero. */
+         helpful = ( dval != DBL_MAX );
+         (void) sprintf( key, "Top%d", axis + 1 );
+         astWriteDouble( channel, key, 0, helpful, dval, "Highest legal axis value");
+      }
+
+/* PreserveAxes. */
+/* ------------- */
+      set = TestPreserveAxes( this, status );
+      ival = set ? GetPreserveAxes( this, status ) : astGetPreserveAxes( this );
+      astWriteInt( channel, "Presrv", set, 0, ival,
+                   ival ? "Preserve target axes" :
+                          "Don't preserve target axes" );
+
+/* Permute. */
+/* -------- */
+      set = TestPermute( this, status );
+      ival = set ? GetPermute( this, status ) : astGetPermute( this );
+      astWriteInt( channel, "Permut", set, 0, ival,
+                   ival ? "Axes may be permuted to match" :
+                          "Axes may not be permuted match" );
+
+/* MinAxes. */
+/* -------- */
+      set = TestMinAxes( this, status );
+      ival = set ? GetMinAxes( this, status ) : astGetMinAxes( this );
+      astWriteInt( channel, "MinAx", set, 0, ival,
+                   "Minimum number of axes to match" );
+
+/* MaxAxes. */
+/* -------- */
+      set = TestMaxAxes( this, status );
+      ival = set ? GetMaxAxes( this, status ) : astGetMaxAxes( this );
+      astWriteInt( channel, "MaxAx", set, 0, ival,
+                   "Maximum number of axes to match" );
+
+/* MatchEnd. */
+/* --------- */
+      set = TestMatchEnd( this, status );
+      ival = set ? GetMatchEnd( this, status ) : astGetMatchEnd( this );
+      astWriteInt( channel, "MchEnd", set, 0, ival,
+                   ival ? "Match final target axes" :
+                          "Match initial target axes" );
+
+/* ObsLat. */
+/* ------- */
+   set = TestObsLat( this, status );
+   dval = set ? GetObsLat( this, status ) : astGetObsLat( this );
+   astWriteDouble( channel, "ObsLat", set, 0, dval, "Observers geodetic latitude (rads)" );
+
+/* ObsLon. */
+/* ------- */
+   set = TestObsLon( this, status );
+   dval = set ? GetObsLon( this, status ) : astGetObsLon( this );
+   astWriteDouble( channel, "ObsLon", set, 0, dval, "Observers geodetic longitude (rads)" );
+
+/* ObsAlt. */
+/* ------- */
+   set = TestObsAlt( this, status );
+   dval = set ? GetObsAlt( this, status ) : astGetObsAlt( this );
+   astWriteDouble( channel, "ObsAlt", set, 0, dval, "Observers geodetic altitude (metres)" );
+
+/* Dut1*/
+/* ---- */
+   set = TestDut1( this, status );
+   dval = set ? GetDut1( this, status ) : astGetDut1( this );
+   astWriteDouble( channel, "Dut1", set, 0, dval, "UT1-UTC in seconds" );
+
+
+/* ActiveUnit. */
+/* ----------- */
+      if( astTestActiveUnit( this ) ) {
+         ival = astGetActiveUnit( this );
+         astWriteInt( channel, "ActUnt", 1, 0, ival,
+                      ival ? "Unit strings affects alignment" :
+                             "Unit strings do not affect alignment" );
+      }
+
+/* Axis permutation array. */
+/* ----------------------- */
+/* Write out the axis permutation array value for each axis,
+   converting to 1-based axis numbering. */
+      for ( axis = 0; axis < this->naxes; axis++ ) {
+         set = ( this->perm[ axis ] != axis );
+         ival = this->perm[ axis ] + 1;
+
+/* Create a keyword and comment appropriate to the axis. */
+         (void) sprintf( key, "Prm%d", axis + 1 );
+         if ( set ) {
+            (void) sprintf( comment,
+                            "Axis %d permuted to use internal axis %d",
+                            axis + 1, ival );
+         } else {
+            (void) sprintf( comment, "Axis %d not permuted", axis + 1 );
+         }
+         astWriteInt( channel, key, set, 0, ival, comment );
+      }
+
+/* Axis Objects. */
+/* ------------- */
+/* Temporarily set the Channel's Full attribute to -1 (unless it is +1
+   to start with), remembering the original setting. This prevents any
+   unnecessary "un-set" Axis values being output that would otherwise
+   simply duplicate the Frame's attributes which have already been
+   written. "Set" Axis values are still written, however (and all
+   values are written if Full is set to 1). */
+      full_set = astTestFull( channel );
+      full = astGetFull( channel );
+      if ( full <= 0 ) astSetFull( channel, -1 );
+
+/* Handle each axis in turn. */
+      for ( axis = 0; axis < this->naxes; axis++ ) {
+
+/* Create a keyword and comment appropriate to the axis (converting to
+   1-based axis numbering). */
+         (void) sprintf( key, "Ax%d", axis + 1 );
+         (void) sprintf( comment, "Axis number %d", axis + 1 );
+
+/* Write out the axis Object description. */
+         astWriteObject( channel, key, 1, 0, this->axis[ axis ], comment );
+      }
+
+/* Restore the Channel's original Full attribute setting. */
+      if ( full_set ) {
+         astSetFull( channel, full );
+      } else {
+         astClearFull( channel );
+      }
+
+/* Free the inverse axis permutation array. */
+      invperm = astFree( invperm );
+
+/* Variants */
+/* ------- */
+      if( this->variants ) astWriteObject( channel, "Vrnts", 1, 0,
+                                           this->variants, "Variant Frames" );
+   }
+
+/* Undefine macros local to this function. */
+#undef COMMENT_LEN
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAFrame and astCheckFrame functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Frame,Mapping)
+astMAKE_CHECK(Frame)
+
+AstFrame *astFrame_( int naxes, const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astFrame
+
+*  Purpose:
+*     Create a Frame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstFrame *astFrame( int naxes, const char *options, int *status, ... )
+
+*  Class Membership:
+*     Frame constructor.
+
+*  Description:
+*     This function creates a new Frame and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     naxes
+*        The number of Frame axes.
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new Frame. The syntax used is the same as
+*        for the astSet method and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of arguments may follow it in order to
+*        supply values to be substituted for these specifiers. The
+*        rules for supplying these are identical to those for the
+*        astSet method (and for the C "printf" function).
+
+*  Returned Value:
+*     A pointer to the new Frame.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic Frame constructor which is
+*     available via the protected interface to the Frame class.  A
+*     public interface is provided by the astFrameId_ function.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *new;                /* Pointer to new Frame */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the Frame, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitFrame( NULL, sizeof( AstFrame ), !class_init, &class_vtab,
+                       "Frame", naxes );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Frame's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Frame. */
+   return new;
+}
+
+AstFrame *astInitFrame_( void *mem, size_t size, int init,
+                         AstFrameVtab *vtab, const char *name,
+                         int naxes, int *status ) {
+/*
+*+
+*  Name:
+*     astInitFrame
+
+*  Purpose:
+*     Initialise a Frame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstFrame *astInitFrame( void *mem, size_t size, int init,
+*                             AstFrameVtab *vtab, const char *name,
+*                             int naxes )
+
+*  Class Membership:
+*     Frame initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Frame object. It allocates memory (if necessary) to accommodate
+*     the Frame plus any additional data associated with the derived class.
+*     It then initialises a Frame structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a Frame at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Frame is to be created. This
+*        must be of sufficient size to accommodate the Frame data
+*        (sizeof(Frame)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Frame (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Frame
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Frame's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Frame.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     naxes
+*        The number of Frame axes.
+
+*  Returned Value:
+*     A pointer to the new Frame.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *new;                /* Pointer to new Frame */
+   int axis;                     /* Loop counter for Frame axes */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitFrameVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the number of axes for validity, reporting an error if necessary. */
+   if ( naxes < 0 ) {
+      astError( AST__NAXIN, "astInitFrame(%s): Number of axes (%d) is "
+                "invalid - this number should not be negative.", status, name, naxes );
+
+/* Initialise a Mapping structure (the parent class) as the first
+   component within the Frame structure, allocating memory if
+   necessary. Set the number of input/output coordinates to zero (the
+   astGetNin and astGetNout methods are over-ridden by the Frame class
+   to provide values for these that are equal to the number of Frame
+   axes). */
+   } else {
+      new = (AstFrame *) astInitMapping( mem, size, 0,
+                                         (AstMappingVtab *) vtab, name,
+                                         0, 0, 1, 1 );
+
+      if ( astOK ) {
+
+/* Initialise the Frame data. */
+/* ----------------------------- */
+/* Set the number of Frame axes. */
+         new->naxes = naxes;
+
+/* Initialise all attributes to their "undefined" values. */
+         new->digits = -INT_MAX;
+         new->domain = NULL;
+         new->epoch = AST__BAD;
+         new->match_end = -INT_MAX;
+         new->max_axes = -INT_MAX;
+         new->min_axes = -INT_MAX;
+         new->permute = -INT_MAX;
+         new->preserve_axes = -INT_MAX;
+         new->title = NULL;
+         new->system = AST__BADSYSTEM;
+         new->alignsystem = AST__BADSYSTEM;
+         new->active_unit = -INT_MAX;
+         new->obsalt = AST__BAD;
+         new->obslat = AST__BAD;
+         new->obslon = AST__BAD;
+         new->dut1 = AST__BAD;
+         new->flags = 0;
+         new->variants = NULL;
+
+/* Allocate memory to store pointers to the Frame's Axis objects and to store
+   its axis permutation array. */
+         new->axis = astMalloc( sizeof( AstAxis * ) * (size_t) naxes );
+         new->perm = astMalloc( sizeof( int ) * (size_t) naxes );
+
+/* Create a new Axis object to describe each axis of the Frame and store the
+   resulting pointers in the memory allocated above. Also initialise the
+   axis permutation array so that the axes appear in their natural order. */
+         if ( astOK ) {
+            for ( axis = 0; axis < naxes; axis++ ) {
+               new->axis[ axis ] = astAxis( "", status );
+               new->perm[ axis ] = axis;
+	    }
+
+/* If an error occurred while creating the Axis objects, scan through the array
+   of pointers to them again to ensure that they are all correctly annulled. */
+            if ( !astOK ) {
+               for ( axis = 0; axis < naxes; axis++ ) {
+                  new->axis[ axis ] = astAnnul( new->axis[ axis ] );
+   	       }
+	    }
+	 }
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstFrame *astLoadFrame_( void *mem, size_t size,
+                         AstFrameVtab *vtab, const char *name,
+                         AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadFrame
+
+*  Purpose:
+*     Load a Frame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstFrame *astLoadFrame( void *mem, size_t size,
+*                              AstFrameVtab *vtab, const char *name,
+*                              AstChannel *channel )
+
+*  Class Membership:
+*     Frame loader.
+
+*  Description:
+*     This function is provided to load a new Frame using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Frame structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Frame at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Frame is to be loaded.
+*        This must be of sufficient size to accommodate the Frame data
+*        (sizeof(Frame)) plus any data used by derived classes. If a
+*        value of NULL is given, this function will allocate the
+*        memory itself using the "size" parameter to determine its
+*        size.
+*     size
+*        The amount of memory used by the Frame (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Frame structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstFrame) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Frame. If this is NULL, a pointer to
+*        the (static) virtual function table for the Frame class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Frame" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Frame.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstFrame *new;                /* Pointer to the new Frame */
+   char *sval;                   /* Pointer to string value */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keywords */
+   double dval;                  /* DOuble attribute value */
+   int axis;                     /* Loop counter for axes */
+   int ival;                     /* Integer value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Frame. In this case the
+   Frame belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstFrame );
+      vtab = &class_vtab;
+      name = "Frame";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitFrameVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Frame. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Assign values for transient components that are not included in the
+   Frame dump */
+   new->flags = 0;
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Frame" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Naxes. */
+/* ------ */
+/* Obtain the number of Frame axes and allocate memory for the arrays
+   which hold axis information. */
+      new->naxes = astReadInt( channel, "naxes", 0 );
+      if ( new->naxes < 0 ) new->naxes = 0;
+      new->perm = astMalloc( sizeof( int ) * (size_t) new->naxes );
+      new->axis = astMalloc( sizeof( AstAxis * ) * (size_t) new->naxes );
+
+/* If an error occurred, ensure that any allocated memory is freed. */
+      if ( !astOK ) {
+         new->perm = astFree( new->perm );
+         new->axis = astFree( new->axis );
+
+/* Otherwise, initialise the array of Axis pointers. */
+      } else {
+         for ( axis = 0; axis < new->naxes; axis++ ) new->axis[ axis ] = NULL;
+
+/* Now obtain those input values which are required for each axis... */
+         for ( axis = 0; axis < new->naxes; axis++ ) {
+
+/* Axis object. */
+/* ------------ */
+/* This must be read first, so that it can hold the other axis values
+   obtained below. */
+
+/* Create a keyword appropriate to this axis. */
+            (void) sprintf( key, "ax%d", axis + 1 );
+
+/* Read the Axis object. If none was read, provide a default Axis
+   instead. */
+            new->axis[ axis ] = astReadObject( channel, key, NULL );
+            if ( !new->axis[ axis ] ) new->axis[ axis ] = astAxis( "", status );
+
+/* Label. */
+/* ------ */
+/* Read the Label string for each axis. If a value is obtained, use
+   it to set the Label attribute for the axis. Free the memory holding
+   the string when no longer needed. */
+            (void) sprintf( key, "lbl%d", axis + 1 );
+            sval = astReadString( channel, key, NULL );
+            if ( sval ) {
+               astSetAxisLabel( new->axis[ axis ], sval );
+               sval = astFree( sval );
+            }
+
+/* Symbol. */
+/* ------- */
+            (void) sprintf( key, "sym%d", axis + 1 );
+            sval = astReadString( channel, key, NULL );
+            if ( sval ) {
+               astSetAxisSymbol( new->axis[ axis ], sval );
+               sval = astFree( sval );
+            }
+
+/* Format. */
+/* ------- */
+            (void) sprintf( key, "fmt%d", axis + 1 );
+            sval = astReadString( channel, key, NULL );
+            if ( sval ) {
+               astSetAxisFormat( new->axis[ axis ], sval );
+               sval = astFree( sval );
+            }
+
+/* Unit. */
+/* ----- */
+            (void) sprintf( key, "uni%d", axis + 1 );
+            sval = astReadString( channel, key, NULL );
+            if ( sval ) {
+               astSetAxisUnit( new->axis[ axis ], sval );
+               sval = astFree( sval );
+            }
+
+/* Direction. */
+/* ---------- */
+            (void) sprintf( key, "dir%d", axis + 1 );
+            ival = astReadInt( channel, key, -INT_MAX );
+            if ( ival != -INT_MAX ) {
+               astSetAxisDirection( new->axis[ axis ], ival );
+            }
+
+/* Top. */
+/*----- */
+            (void) sprintf( key, "top%d", axis + 1 );
+            dval = astReadDouble( channel, key, AST__BAD );
+            if ( dval != AST__BAD ) {
+               astSetAxisTop( new->axis[ axis ], dval );
+            }
+
+/* Bottom. */
+/*----- -- */
+            (void) sprintf( key, "bot%d", axis + 1 );
+            dval = astReadDouble( channel, key, AST__BAD );
+            if ( dval != AST__BAD ) {
+               astSetAxisBottom( new->axis[ axis ], dval );
+            }
+
+/* Digits. */
+/* ------- */
+            (void) sprintf( key, "dig%d", axis + 1 );
+            ival = astReadInt( channel, key, -INT_MAX );
+            if ( ival != -INT_MAX ) {
+               astSetAxisDigits( new->axis[ axis ], ival );
+            }
+
+/* Axis permutation array. */
+/* ----------------------- */
+/* Convert from 1-based to zero-based axis numbering at this
+   point. The default is the "un-permuted" value. */
+            sprintf( key, "prm%d", axis + 1 );
+            new->perm[ axis ] = astReadInt( channel, key, axis + 1 ) - 1;
+
+/* Quit looping if an error occurs. */
+            if ( !astOK ) break;
+         }
+
+/* The remaining values are not associated with particular axes... */
+
+/* Title. */
+/* ------ */
+         new->title = astReadString( channel, "title", NULL );
+
+/* Domain. */
+/* ------- */
+         new->domain = astReadString( channel, "domain", NULL );
+
+/* Epoch. */
+/* ------ */
+/* Interpret this as Besselian or Julian depending on its value. */
+         new->epoch = astReadDouble( channel, "epoch", AST__BAD );
+         if ( TestEpoch( new, status ) ) {
+            SetEpoch( new, ( new->epoch < 1984.0 ) ? palEpb2d( new->epoch ) :
+                                                     palEpj2d( new->epoch ), status );
+         }
+
+/* Digits. */
+/* ------- */
+/* This is the value that applies to the Frame as a whole. */
+         new->digits = astReadInt( channel, "digits", -INT_MAX );
+         if ( TestDigits( new, status ) ) SetDigits( new, new->digits, status );
+
+/* PreserveAxes. */
+/* ------------- */
+         new->preserve_axes = astReadInt( channel, "presrv", -INT_MAX );
+         if ( TestPreserveAxes( new, status ) ) {
+            SetPreserveAxes( new, new->preserve_axes, status );
+         }
+
+/* Permute. */
+/* -------- */
+         new->permute = astReadInt( channel, "permut", -INT_MAX );
+         if ( TestPermute( new, status ) ) SetPermute( new, new->permute, status );
+
+/* MinAxes. */
+/* -------- */
+         new->min_axes = astReadInt( channel, "minax", -INT_MAX );
+         if ( TestMinAxes( new, status ) ) SetMinAxes( new, new->min_axes, status );
+
+/* MaxAxes. */
+/* -------- */
+         new->max_axes = astReadInt( channel, "maxax", -INT_MAX );
+         if ( TestMaxAxes( new, status ) ) SetMaxAxes( new, new->max_axes, status );
+
+/* MatchEnd. */
+/* --------- */
+         new->match_end = astReadInt( channel, "mchend", -INT_MAX );
+         if ( TestMatchEnd( new, status ) ) SetMatchEnd( new, new->match_end, status );
+
+/* ObsLat. */
+/* ------- */
+         new->obslat = astReadDouble( channel, "obslat", AST__BAD );
+         if ( TestObsLat( new, status ) ) SetObsLat( new, new->obslat, status );
+
+/* ObsLon. */
+/* ------- */
+         new->obslon = astReadDouble( channel, "obslon", AST__BAD );
+         if ( TestObsLon( new, status ) ) SetObsLon( new, new->obslon, status );
+
+/* ObsAlt. */
+/* ------- */
+         new->obsalt = astReadDouble( channel, "obsalt", AST__BAD );
+         if ( TestObsAlt( new, status ) ) SetObsAlt( new, new->obsalt, status );
+
+/* Dut1. */
+/* ---- */
+         new->dut1 = astReadDouble( channel, "dut1", AST__BAD );
+         if ( TestDut1( new, status ) ) SetDut1( new, new->dut1, status );
+
+/* ActiveUnit. */
+/* ----------- */
+         new->active_unit = astReadInt( channel, "actunt", -INT_MAX );
+         if ( TestActiveUnit( new, status ) ) SetActiveUnit( new, new->active_unit, status );
+
+/* System. */
+/* ------- */
+/* Set the default and read the external representation as a string. */
+         new->system = AST__BADSYSTEM;
+         sval = astReadString( channel, "system", NULL );
+
+/* If a value was read, convert from a string to a System code. */
+         if ( sval ) {
+            if ( astOK ) {
+               new->system = astSystemCode( new, sval );
+
+/* Report an error if the value wasn't recognised. */
+               if ( new->system == AST__BADSYSTEM ) {
+                  astError( AST__ATTIN,
+                            "astRead(%s): Invalid System description "
+                            "\"%s\".", status, astGetClass( channel ), sval );
+               }
+            }
+
+/* Free the string value. */
+            sval = astFree( sval );
+         }
+
+/* AlignSystem. */
+/* ------------ */
+/* Set the default and read the external representation as a string. */
+         new->alignsystem = AST__BADSYSTEM;
+         sval = astReadString( channel, "alsys", NULL );
+
+/* If a value was read, convert from a string to a System code. */
+         if ( sval ) {
+            if ( astOK ) {
+               new->alignsystem = astSystemCode( new, sval );
+
+/* Report an error if the value wasn't recognised. */
+               if ( new->alignsystem == AST__BADSYSTEM ) {
+                  astError( AST__ATTIN,
+                            "astRead(%s): Invalid AlignSystem description "
+                            "\"%s\".", status, astGetClass( channel ), sval );
+               }
+            }
+
+/* Free the string value. */
+            sval = astFree( sval );
+         }
+
+/* Variants. */
+/* -------- */
+         new->variants = astReadObject( channel, "vrnts", NULL );
+      }
+
+/* If an error occurred, clean up by deleting the new Frame. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Frame pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+const char *astAbbrev_( AstFrame *this, int axis, const char *fmt,
+                        const char *str1, const char *str2, int *status ) {
+   if ( !astOK ) return str2;
+   return (**astMEMBER(this,Frame,Abbrev))( this, axis, fmt, str1, str2, status );
+}
+int astFields_( AstFrame *this, int axis, const char *fmt,
+                const char *str, int maxfld, char **fields,
+                int *nc, double *val, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,Fields))( this, axis, fmt, str, maxfld, fields, nc, val, status );
+}
+void astCheckPerm_( AstFrame *this, const int *perm, const char *method, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,CheckPerm))( this, perm, method, status );
+}
+
+AstPointSet *astResolvePoints_( AstFrame *this, const double point1[],
+                                const double point2[], AstPointSet *in,
+                                AstPointSet *out, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Frame,ResolvePoints))( this, point1, point2, in, out, status );
+}
+AstLineDef *astLineDef_( AstFrame *this, const double start[2],
+                             const double end[2], int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Frame,LineDef))( this, start, end, status );
+}
+int astLineCrossing_( AstFrame *this, AstLineDef *l1, AstLineDef *l2,
+                      double **cross, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,LineCrossing))( this, l1, l2, cross, status );
+}
+void astLineOffset_( AstFrame *this, AstLineDef *line, double par, double prp,
+                     double point[2], int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,LineOffset))( this, line, par, prp, point, status );
+}
+int astLineContains_( AstFrame *this, AstLineDef *l, int def, double *point, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,LineContains))( this, l, def, point, status );
+}
+AstFrameSet *astConvert_( AstFrame *from, AstFrame *to,
+                          const char *domainlist, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(from,Frame,Convert))( from, to, domainlist, status );
+}
+AstFrameSet *astConvertX_( AstFrame *to, AstFrame *from,
+                           const char *domainlist, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(to,Frame,ConvertX))( to, from, domainlist, status );
+}
+double astAngle_( AstFrame *this, const double a[], const double b[],
+                  const double c[], int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,Frame,Angle))( this, a, b, c, status );
+}
+int astGetActiveUnit_( AstFrame *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,GetActiveUnit))( this, status );
+}
+int astTestActiveUnit_( AstFrame *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,TestActiveUnit))( this, status );
+}
+void astSetActiveUnit_( AstFrame *this, int value, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,SetActiveUnit))( this, value, status );
+}
+double astDistance_( AstFrame *this,
+                     const double point1[], const double point2[], int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,Frame,Distance))( this, point1, point2, status );
+}
+AstFrameSet *astFindFrame_( AstFrame *target, AstFrame *template,
+                            const char *domainlist, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(target,Frame,FindFrame))( target, template, domainlist, status );
+}
+void astMatchAxes_( AstFrame *frm1, AstFrame *frm2, int *axes, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(frm1,Frame,MatchAxes))( frm1, frm2, axes, status );
+}
+void astMatchAxesX_( AstFrame *frm2, AstFrame *frm1, int *axes, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(frm2,Frame,MatchAxesX))( frm2, frm1, axes, status );
+}
+const char *astFormat_( AstFrame *this, int axis, double value, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Frame,Format))( this, axis, value, status );
+}
+double astCentre_( AstFrame *this, int axis, double value, double gap, int *status ) {
+   if ( !astOK ) return 0.0;
+   return (**astMEMBER(this,Frame,Centre))( this, axis, value, gap, status );
+}
+double astGap_( AstFrame *this, int axis, double gap, int *ntick, int *status ) {
+   if ( !astOK ) return 0.0;
+   return (**astMEMBER(this,Frame,Gap))( this, axis, gap, ntick, status );
+}
+AstAxis *astGetAxis_( AstFrame *this, int axis, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Frame,GetAxis))( this, axis, status );
+}
+int astGetNaxes_( AstFrame *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,GetNaxes))( this, status );
+}
+const int *astGetPerm_( AstFrame *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Frame,GetPerm))( this, status );
+}
+AstFrameSet *astGetFrameVariants_( AstFrame *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Frame,GetFrameVariants))( this, status );
+}
+void astSetFrameVariants_( AstFrame *this, AstFrameSet *variants, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,SetFrameVariants))( this, variants, status );
+}
+
+
+int astMatch_( AstFrame *this, AstFrame *target, int matchsub,
+               int **template_axes, int **target_axes,
+               AstMapping **map, AstFrame **result, int *status ) {
+
+   AstFrame *super_this;
+   const char *dom;
+   int match;
+
+   if ( !astOK ) return 0;
+
+   match = (**astMEMBER(this,Frame,Match))( this, target, matchsub,
+                                            template_axes, target_axes,
+                                            map, result, status );
+
+/* If the template ("this") could not be used to probe the target, it may
+   be because the template class is a more specialised form of the target
+   class. E.g. a SkyFrame cannot directly be used to probe a Frame, but a
+   Frame *can* be used to probe a SkyFrame. This means (for instance),
+   that a basic Frame with Domain FRED cannot be aligned (using astConvert)
+   with a CmpFrame with Domain FRED. This sort of alignment is often
+   useful, so we try now to use the supplied template to probe a modified
+   form of the target that has been cast into the same class as the
+   template. This is only possible if the template class is a sub-class of
+   the target class. Attempt to do the cast. */
+   if( ! match && matchsub ) {
+      super_this = (AstFrame *) astCast( this, target );
+
+/* If the cast was  possible, fix the template Domain since the parent
+   class may provide a different default Domain, and then invoke the Match
+   method appropriate to the new template class (i.e. the target class). */
+      if( super_this ) {
+         if( astTestDomain( target ) ) {
+            dom = astGetDomain( this );
+            if( astChrLen( dom ) > 0 ) astSetDomain( super_this, dom );
+         }
+         match = (**astMEMBER(super_this,Frame,Match))( super_this, target,
+                                                        matchsub, template_axes,
+                                                        target_axes, map,
+                                                        result, status );
+         super_this = astAnnul( super_this );
+      }
+   }
+
+   return match;
+}
+
+
+int astIsUnitFrame_( AstFrame *this, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,IsUnitFrame))( this, status );
+}
+void astNorm_( AstFrame *this, double value[], int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,Norm))( this, value, status );
+}
+void astNormBox_( AstFrame *this, double lbnd[], double ubnd[], AstMapping *reg, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,NormBox))( this, lbnd, ubnd, reg, status );
+}
+double astAxDistance_( AstFrame *this, int axis, double v1, double v2, int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,Frame,AxDistance))( this, axis, v1, v2, status );
+}
+void astAxNorm_( AstFrame *this, int axis, int oper, int nval, double *values,
+                 int *status ){
+   if ( !astOK ) return;
+   return (**astMEMBER(this,Frame,AxNorm))( this, axis, oper, nval, values, status );
+}
+double astAxOffset_( AstFrame *this, int axis, double v1, double dist, int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,Frame,AxOffset))( this, axis, v1, dist, status );
+}
+
+
+AstPointSet *astFrameGrid_( AstFrame *this, int size, const double *lbnd,
+                            const double *ubnd, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Frame,FrameGrid))( this, size, lbnd, ubnd, status );
+}
+
+
+void astOffset_( AstFrame *this, const double point1[], const double point2[],
+                 double offset, double point3[], int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,Offset))( this, point1, point2, offset, point3, status );
+}
+double astAxAngle_( AstFrame *this, const double a[2], const double b[2],
+                    int axis, int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,Frame,AxAngle))( this, a, b, axis, status );
+}
+double astOffset2_( AstFrame *this, const double point1[2], double angle,
+                 double offset, double point2[2], int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,Frame,Offset2))( this, point1, angle, offset, point2, status );
+}
+void astIntersect_( AstFrame *this, const double a1[2],
+                    const double a2[2], const double b1[2],
+                    const double b2[2], double cross[2],
+                    int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,Intersect))( this, a1, a2, b1, b2, cross, status );
+}
+void astOverlay_( AstFrame *template, const int *template_axes,
+                  AstFrame *result, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(template,Frame,Overlay))( template, template_axes, result, status );
+}
+void astPermAxes_( AstFrame *this, const int perm[], int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,PermAxes))( this, perm, status );
+}
+AstFrame *astPickAxes_( AstFrame *this, int naxes, const int axes[],
+                        AstMapping **map, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Frame,PickAxes))( this, naxes, axes, map, status );
+}
+void astPrimaryFrame_( AstFrame *this, int axis1,
+                      AstFrame **frame, int *axis2, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,PrimaryFrame))( this, axis1, frame, axis2, status );
+}
+void astResolve_( AstFrame *this, const double point1[], const double point2[],
+                 const double point3[], double point4[], double *d1,
+                 double *d2, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,Resolve))( this, point1, point2, point3, point4, d1, d2, status );
+}
+void astSetAxis_( AstFrame *this, int axis, AstAxis *newaxis, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,SetAxis))( this, axis, newaxis, status );
+}
+void astSetUnit_( AstFrame *this, int axis, const char *value, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,SetUnit))( this, axis, value, status );
+}
+void astClearUnit_( AstFrame *this, int axis, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,ClearUnit))( this, axis, status );
+}
+int astSubFrame_( AstFrame *target, AstFrame *template, int result_naxes,
+                  const int *target_axes, const int *template_axes,
+                  AstMapping **map, AstFrame **result, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(target,Frame,SubFrame))( target, template, result_naxes,
+                                                target_axes, template_axes,
+                                                map, result, status );
+}
+int astUnformat_( AstFrame *this, int axis, const char *string,
+                  double *value, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,Unformat))( this, axis, string, value, status );
+}
+int astValidateAxis_( AstFrame *this, int axis, int fwd, const char *method, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,ValidateAxis))( this, axis, fwd, method, status );
+}
+void astValidateAxisSelection_( AstFrame *this, int naxes, const int *axes,
+                                const char *method, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,ValidateAxisSelection))( this, naxes, axes,
+                                                    method, status );
+}
+AstSystemType astValidateSystem_( AstFrame *this, AstSystemType system, const char *method, int *status ) {
+   if ( !astOK ) return AST__BADSYSTEM;
+   return (**astMEMBER(this,Frame,ValidateSystem))( this, system, method, status );
+}
+AstSystemType astSystemCode_( AstFrame *this, const char *system, int *status ) {
+   if ( !astOK ) return AST__BADSYSTEM;
+   return (**astMEMBER(this,Frame,SystemCode))( this, system, status );
+}
+const char *astSystemString_( AstFrame *this, AstSystemType system, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Frame,SystemString))( this, system, status );
+}
+int astAxIn_( AstFrame *this, int axis, double lo, double hi, double val,
+              int closed, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,AxIn))( this, axis, lo, hi, val, closed, status );
+}
+int astGetFrameFlags_( AstFrame *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Frame,GetFrameFlags))( this, status );
+}
+void astSetFrameFlags_( AstFrame *this, int value, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Frame,SetFrameFlags))( this, value, status );
+}
+
+
+/* Special public interface functions. */
+/* =================================== */
+/* These provide the public interface to certain special functions
+   whose public interface cannot be handled using macros (such as
+   astINVOKE) alone. In general, they are named after the
+   corresponding protected version of the function, but with "Id"
+   appended to the name. */
+
+/* Public Interface Function Prototypes. */
+/* ------------------------------------- */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstFrame *PickAxesId_( AstFrame *, int, const int[], AstMapping **, int * );
+AstFrame *astFrameId_( int, const char *, ... );
+const char *astFormatId_( AstFrame *, int, double, int * );
+int astUnformatId_( AstFrame *, int, const char *, double *, int * );
+void astPermAxesId_( AstFrame *, const int[], int * );
+
+/* Special interface function implementations. */
+/* ------------------------------------------- */
+const char *astFormatId_( AstFrame *this, int axis, double value, int *status ) {
+/*
+*++
+*  Name:
+c     astFormat
+f     AST_FORMAT
+
+*  Purpose:
+*     Format a coordinate value for a Frame axis.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     const char *astFormat( AstFrame *this, int axis, double value )
+f     RESULT = AST_FORMAT( THIS, AXIS, VALUE, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function returns a pointer to a string containing the
+f     This function returns a character string containing the
+*     formatted (character) version of a coordinate value for a Frame
+*     axis. The formatting applied is determined by the Frame's
+*     attributes and, in particular, by any Format attribute string
+*     that has been set for the axis. A suitable default format (based
+*     on the Digits attribute value) will be applied if necessary.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (given)
+*        Pointer to the Frame.
+c     axis
+f     AXIS = INTEGER (Given)
+*        The number of the Frame axis for which formatting is to be
+*        performed (axis numbering starts at 1 for the first axis).
+c     value
+f     VALUE = DOUBLE PRECISION (Given)
+*        The coordinate value to be formatted.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astFormat()
+c        A pointer to a null-terminated string containing the formatted
+c        value.
+f     AST_FORMAT = CHARACTER * ( AST__SZCHR )
+f        The formatted value.
+
+*  Notes:
+c     - The returned pointer is guaranteed to remain valid and the
+c     string to which it points will not be over-written for a total
+c     of 50 successive invocations of this function. After this, the
+c     memory containing the string may be re-used, so a copy of the
+c     string should be made if it is needed for longer than this.
+c     - A formatted value may be converted back into a numerical (double)
+c     value using astUnformat.
+f     - A formatted value may be converted back into a numerical
+f     (double precision) value using AST_UNFORMAT.
+c     - A NULL pointer will be returned if this function is invoked
+c     with the AST error status set, or if it should fail for any
+c     reason.
+f     - A blank string will be returned if this function is invoked
+f     with STATUS set to an error value, or if it should fail for any
+f     reason.
+*--
+
+*  Implementation Notes:
+*     This function implements the public interface for the astFormat
+*     method. It is identical to astFormat_ except that:
+*
+*     - The axis index is decremented by 1 before use. This allows the
+*     public interface to use 1-based axis numbers (whereas internally
+*     axis numbers are zero-based).
+*
+*     - The returned string value is buffered in dynamically allocated
+*     memory so that it will remain valid for a guaranteed number of
+*     function invocations.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   const char *fvalue;           /* Pointer to formatted value */
+   const char *result;           /* Pointer value to return */
+   int i;                        /* Loop counter for initialisation */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* If the "astformatid_strings" array has not been initialised, fill it with NULL
+   pointers. */
+   if ( !astformatid_init ) {
+      astformatid_init = 1;
+      for ( i = 0; i < ASTFORMATID_MAX_STRINGS; i++ ) astformatid_strings[ i ] = NULL;
+   }
+
+/* Invoke the normal astFormat_ function to obtain a pointer to the
+   required formatted value, adjusting the axis index to become
+   zero-based. */
+   fvalue = astFormat( this, axis - 1, value );
+
+/* If OK, store a copy of the resulting string in dynamically allocated memory,
+   putting a pointer to the copy into the next element of the "astformatid_strings"
+   array.  (This process also de-allocates any previously allocated memory pointed
+   at by this "astformatid_strings" element, so the earlier string is effectively
+   replaced by the new one.) */
+   if ( astOK ) {
+      astBeginPM;
+      astformatid_strings[ astformatid_istr ] = astStore( astformatid_strings[ astformatid_istr ], fvalue,
+                                  strlen( fvalue ) + (size_t) 1 );
+      astEndPM;
+
+/* If OK, return a pointer to the copy and increment "astformatid_istr" to use
+   the next element of "astformatid_strings" on the next invocation. Recycle
+   "astformatid_istr" to zero when all elements have been used. */
+      if ( astOK ) {
+         result = astformatid_strings[ astformatid_istr++ ];
+         if ( astformatid_istr == ( ASTFORMATID_MAX_STRINGS - 1 ) ) astformatid_istr = 0;
+      }
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+AstFrame *astFrameId_( int naxes, const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astFrame
+f     AST_FRAME
+
+*  Purpose:
+*     Create a Frame.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     AstFrame *astFrame( int naxes, const char *options, ... )
+f     RESULT = AST_FRAME( NAXES, OPTIONS, STATUS )
+
+*  Class Membership:
+*     Frame constructor.
+
+*  Description:
+*     This function creates a new Frame and optionally initialises its
+*     attributes.
+*
+*     A Frame is used to represent a coordinate system. It does this
+*     in rather the same way that a frame around a graph describes the
+*     coordinate space in which data are plotted. Consequently, a
+*     Frame has a Title (string) attribute, which describes the
+*     coordinate space, and contains axes which in turn hold
+*     information such as Label and Units strings which are used for
+*     labelling (e.g.) graphical output. In general, however, the
+*     number of axes is not restricted to two.
+*
+*     Functions are available for converting Frame coordinate values
+*     into a form suitable for display, and also for calculating
+*     distances and offsets between positions within the Frame.
+*
+*     Frames may also contain knowledge of how to transform to and
+*     from related coordinate systems.
+
+*  Parameters:
+c     naxes
+f     NAXES = INTEGER (Given)
+*        The number of Frame axes (i.e. the number of dimensions of
+*        the coordinate space which the Frame describes).
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Frame. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Frame. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astFrame()
+f     AST_FRAME = INTEGER
+*        A pointer to the new Frame.
+
+*  Examples:
+c     frame = astFrame( 2, "Title=Energy Spectrum: Plot %d", n );
+c        Creates a new 2-dimensional Frame and initialises its Title
+c        attribute to the string "Energy Spectrum: Plot <n>", where
+c        <n> takes the value of the int variable "n".
+c     frame = astFrame( 2, "Label(1)=Energy, Label(2)=Response" );
+c        Creates a new 2-dimensional Frame and initialises its axis
+c        Label attributes to suitable string values.
+f     FRAME = AST_FRAME( 2, 'Title=Energy Spectrum', STATUS );
+f        Creates a new 2-dimensional Frame and initialises its Title
+f        attribute to the string "Energy Spectrum".
+f     FRAME = AST_FRAME( 2, 'Label(1)=Energy, Label(2)=Response', STATUS );
+f        Creates a new 2-dimensional Frame and initialises its axis
+f        Label attributes to suitable string values.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astFrame constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astFrame_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - The variable argument list also prevents this function from
+*     invoking astFrame_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *new;                /* Pointer to new Frame */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the Frame, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitFrame( NULL, sizeof( AstFrame ), !class_init, &class_vtab,
+                       "Frame", naxes );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Frame's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new Frame. */
+   return astMakeId( new );
+}
+
+void astPermAxesId_( AstFrame *this, const int perm[], int *status ) {
+/*
+*++
+*  Name:
+c     astPermAxes
+f     AST_PERMAXES
+
+*  Purpose:
+*     Permute the axis order in a Frame.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     void astPermAxes( AstFrame *this, const int perm[] )
+f     CALL AST_PERMAXES( THIS, PERM, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function permutes the order in which a Frame's axes occur.
+f     This routine permutes the order in which a Frame's axes occur.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     perm
+f     PERM( * ) = INTEGER (Given)
+*        An array with one element for each axis of the Frame (Naxes
+*        attribute). This should list the axes in their new order,
+*        using the original axis numbering (which starts at 1 for the
+*        first axis).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Only genuine permutations of the axis order are permitted, so
+c     each axis must be referenced exactly once in the "perm" array.
+f     each axis must be referenced exactly once in the PERM array.
+*     - If successive axis permutations are applied to a Frame, then
+*     the effects are cumulative.
+*--
+
+*  Implementation Notes:
+*     This function implements the public interface for the
+*     astPermAxes method. It is identical to astPermAxes_ except that
+*     the axis numbers in the "perm" array are decremented by 1 before
+*     use. This is to allow the public interface to use one-based axis
+*     numbering (internally, zero-based axis numbering is used).
+*/
+
+/* Local Variables: */
+   int *perm1;                   /* Pointer to modified perm array */
+   int axis;                     /* Loop counter for Frame axes */
+   int naxes;                    /* Number of Frame axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain the number of Frame axes. */
+   naxes = astGetNaxes( this );
+
+/* Allocate an array to hold a modified version of the "perm"
+   array. */
+   perm1 = astMalloc( sizeof( int ) * (size_t) naxes );
+   if ( astOK ) {
+
+/* Make a modified copy of the "perm" array by subtracting one from
+   each element. This allows the public interface to use one-based
+   axis numbering, whereas all internal code is zero-based. */
+      for ( axis = 0; axis < naxes; axis++ ) perm1[ axis ] = perm[ axis ] - 1;
+
+/* Invoke the normal astPermAxes_ function to permute the Frame's axes. */
+      astPermAxes( this, perm1 );
+   }
+
+/* Free the temporary array. */
+   perm1 = astFree( perm1 );
+}
+
+AstFrame *astPickAxesId_( AstFrame *this, int naxes, const int axes[],
+                          AstMapping **map, int *status ) {
+/*
+*++
+*  Name:
+c     astPickAxes
+f     AST_PICKAXES
+
+*  Purpose:
+*     Create a new Frame by picking axes from an existing one.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     AstFrame *astPickAxes( AstFrame *this, int naxes, const int axes[],
+c                            AstMapping **map )
+f     RESULT = AST_PICKAXES( THIS, NAXES, AXES, MAP, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function creates a new Frame whose axes are copied from an
+*     existing Frame along with other Frame attributes, such as its
+*     Title. Any number (zero or more) of the original Frame's axes
+*     may be copied, in any order, and additional axes with default
+*     attributes may also be included in the new Frame.
+*
+c     Optionally, a Mapping that converts between the coordinate
+f     A Mapping that converts between the coordinate
+*     systems described by the two Frames will also be returned.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the original Frame.
+c     naxes
+f     NAXES = INTEGER (Given)
+*        The number of axes required in the new Frame.
+c     axes
+f     AXES( NAXES ) = INTEGER (Given)
+c        An array, with "naxes" elements, which lists the axes to be
+f        An array which lists the axes to be
+*        copied. These should be given in the order required in the
+*        new Frame, using the axis numbering in the original Frame
+*        (which starts at 1 for the first axis). Axes may be selected
+*        in any order, but each may only be used once.  If additional
+*        (default) axes are also to be included, the corresponding
+*        elements of this array should be set to zero.
+c     map
+f     MAP = INTEGER (Returned)
+c        Address of a location in which to return a pointer to a new
+f        A pointer to a new
+*        Mapping. This will be a PermMap (or a UnitMap as a special
+*        case) that describes the axis permutation that has taken
+*        place between the original and new Frames. The Mapping's
+*        forward transformation will convert coordinates from the
+*        original Frame into the new one, and vice versa.
+c
+c        If this Mapping is not required, a NULL value may be supplied
+c        for this parameter.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astPickAxes()
+f     AST_PICKAXES = INTEGER
+*        A pointer to the new Frame.
+
+*  Applicability:
+*     Frame
+*        This function applies to all Frames. The class of Frame returned
+*        may differ from that of the original Frame, depending on which
+*        axes are selected. For example, if a single axis is picked from a
+*        SkyFrame (which must always have two axes) then the resulting
+*        Frame cannot be a valid SkyFrame, so will revert to the parent
+*        class (Frame) instead.
+*     FrameSet
+*        Using this function on a FrameSet is identical to using it on
+*        the current Frame in the FrameSet. The returned Frame will not
+*        be a FrameSet.
+*     Region
+*        If this function is used on a Region, an attempt is made to
+*        retain the bounds information on the selected axes. If
+*        succesful, the returned Frame will be a Region of some class.
+*        Otherwise, the returned Frame is obtained by calling this
+*        function on the Frame represented by the supplied Region (the
+*        returned Frame will then not be a Region). In order to be
+*        succesful, the selected axes in the Region must be independent
+*        of the others. For instance, a Box can be split in this way but
+*        a Circle cannot. Another requirement for success is that no
+*        default axes are added (that is, the
+c        "axes"
+f        AXES
+*        array must not contain any zero values.
+
+*  Notes:
+c     - The new Frame will contain a "deep" copy (c.f. astCopy) of all
+f     - The new Frame will contain a "deep" copy (c.f. AST_COPY) of all
+*     the data selected from the original Frame. Modifying any aspect
+*     of the new Frame will therefore not affect the original one.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     This function implements the public interface for the
+*     astPickAxes method. It is identical to astPickAxes_ except for
+*     the following:
+*
+*     - The axis numbers in the "axes" array are decremented by 1 before
+*     use. This is to allow the public interface to use one-based axis
+*     numbering (internally, zero-based axis numbering is used).
+*
+*     - An ID value is returned via the "map" parameter (if used)
+*     instead of a true C pointer. This is required because this
+*     conversion cannot be performed by the macro that invokes the
+*     function.
+*/
+
+/* Local Variables: */
+   AstFrame *result;             /* Pointer to result Frame */
+   int *axes1;                   /* Pointer to modified axes array */
+   int axis;                     /* Loop counter for axes */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Allocate an array to hold a modified version of the "axes" array
+   (check that "naxes" is valid first - if not, this error will be
+   reported by astPickAxes_ below). */
+   axes1 = ( naxes >= 0 ) ? astMalloc( sizeof( int ) * (size_t) naxes ) :
+                            NULL;
+   if ( astOK ) {
+
+/* Make a modified copy of the "axes" array by subtracting one from
+   each element. This allows the public interface to use one-based
+   axis numbering, whereas all internal code is zero-based. */
+      for ( axis = 0; axis < naxes; axis++ ) axes1[ axis ] = axes[ axis ] - 1;
+
+/* Invoke the normal astPickAxes_ function to select the required axes. */
+      result = astPickAxes( this, naxes, axes1, map );
+   }
+
+/* Free the temporary array. */
+   axes1 = astFree( axes1 );
+
+/* If required, return an ID value for the Mapping. */
+   if ( map ) *map = astMakeId( *map );
+
+/* Return the result. */
+   return result;
+}
+
+int astUnformatId_( AstFrame *this, int axis, const char *string,
+                    double *value, int *status ) {
+/*
+*++
+*  Name:
+c     astUnformat
+f     AST_UNFORMAT
+
+*  Purpose:
+*     Read a formatted coordinate value for a Frame axis.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frame.h"
+c     int astUnformat( AstFrame *this, int axis, const char *string,
+c                      double *value )
+f     RESULT = AST_UNFORMAT( THIS, AXIS, STRING, VALUE, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function reads a formatted coordinate value (given as a
+c     character string) for a Frame axis and returns the equivalent
+c     numerical (double) value. It also returns the number of
+c     characters read from the string.
+f     This function reads a formatted coordinate value (given as a
+f     character string) for a Frame axis and returns the equivalent
+f     numerical (double precision) value. It also returns the number
+f     of characters read from the string.
+*
+c     The principle use of this function is in decoding user-supplied
+c     input which contains formatted coordinate values. Free-format
+c     input is supported as far as possible. If input is ambiguous, it
+c     is interpreted with reference to the Frame's attributes (in
+c     particular, the Format string associated with the Frame's
+c     axis). This function is, in essence, the inverse of astFormat.
+f     The principle use of this function is in decoding user-supplied
+f     input which contains formatted coordinate values. Free-format
+f     input is supported as far as possible. If input is ambiguous, it
+f     is interpreted with reference to the Frame's attributes (in
+f     particular, the Format string associated with the Frame's
+f     axis). This function is, in essence, the inverse of AST_FORMAT.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Frame.
+c     axis
+f     AXIS = INTEGER (Given)
+*        The number of the Frame axis for which a coordinate value is to
+*        be read (axis numbering starts at 1 for the first axis).
+c     string
+f     STRING = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string containing the
+c        formatted coordinate value.
+f        A character string containing the formatted coordinate value.
+*        This string may contain additional information following the
+*        value to be read, in which case reading stops at the first
+*        character which cannot be interpreted as part of the value.
+*        Any white space before or after the value is discarded.
+c     value
+f     VALUE = DOUBLE PRECISION (Returned)
+c        Pointer to a double in which the coordinate value read will be
+c        returned.
+f        The coordinate value read.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astUnformat()
+f     AST_UNFORMAT = INTEGER
+*        The number of characters read from the string in order to
+*        obtain the coordinate value. This will include any white
+*        space which occurs before or after the value.
+
+*  Applicability:
+*     Frame
+*        This function applies to all Frames. See the "Frame Input
+*        Format" section below for details of the input formats
+*        accepted by a basic Frame.
+*     SkyFrame
+*        The SkyFrame class re-defines the input format to be suitable
+*        for representing angles and times, with the resulting
+*        coordinate value returned in radians.  See the "SkyFrame
+*        Input Format" section below for details of the formats
+*        accepted.
+*     FrameSet
+*        The input formats accepted by a FrameSet are determined by
+*        its current Frame (as specified by the Current attribute).
+
+*  Frame Input Format
+*     The input format accepted for a basic Frame axis is as follows:
+*     - An optional sign, followed by:
+*     - A sequence of one or more digits possibly containing a decimal point,
+*     followed by:
+*     - An optional exponent field.
+*     - The exponent field, if present, consists of "E" or "e"
+*     followed by a possibly signed integer.
+*
+*     Examples of acceptable Frame input formats include:
+*     - 99
+*     - 1.25
+*     - -1.6
+*     - 1E8
+*     - -.99e-17
+*     - <bad>
+
+*  SkyFrame Input Format
+*     The input format accepted for a SkyFrame axis is as follows:
+*     - An optional sign, followed by between one and three fields
+*     representing either degrees, arc-minutes, arc-seconds or hours,
+*     minutes, seconds (e.g. "-12 42 03").
+*     - Each field should consist of a sequence of one or more digits,
+*     which may include leading zeros. At most one field may contain a
+*     decimal point, in which case it is taken to be the final field
+*     (e.g. decimal degrees might be given as "124.707", while degrees
+*     and decimal arc-minutes might be given as "-13 33.8").
+*     - The first field given may take any value, allowing angles and
+*     times outside the conventional ranges to be
+*     represented. However, subsequent fields must have values of less
+*     than 60 (e.g. "720 45 31" is valid, whereas "11 45 61" is not).
+*     - Fields may be separated by white space or by ":" (colon), but
+*     the choice of separator must be used consistently throughout the
+*     value. Additional white space may be present around fields and
+*     separators (e.g. "- 2: 04 : 7.1").
+*     - The following field identification characters may be used as
+*     separators to replace either of those above (or may be appended
+*     to the final field), in order to identify the field to which
+*     they are appended: "d"---degrees; "h"---hours; "m"---minutes of
+*     arc or time; "s"---seconds of arc or time; "'" (single
+*     quote)---minutes of arc; """ (double quote)---seconds of arc.
+*     Either lower or upper case may be used.  Fields must be given in
+*     order of decreasing significance (e.g. "-11D 3' 14.4"" or
+*     "22h14m11.2s").
+*     - The presence of any of the field identification characters
+*     "d", "'" (single quote) or """ (double quote) indicates that the
+*     value is to be interpreted as an angle. Conversely, the presence
+*     of "h" indicates that it is to be interpreted as a time (with 24
+*     hours corresponding to 360 degrees). Incompatible angle/time
+*     identification characters may not be mixed (e.g. "10h14'3"" is
+*     not valid).  The remaining field identification characters and
+*     separators do not specify a preference for an angle or a time
+*     and may be used with either.
+c     - If no preference for an angle or a time is expressed anywhere
+c     within the value, it is interpreted as an angle if the Format
+c     attribute string associated with the SkyFrame axis generates an
+c     angle and as a time otherwise. This ensures that values produced
+c     by astFormat are correctly interpreted by astUnformat.
+f     - If no preference for an angle or a time is expressed anywhere
+f     within the value, it is interpreted as an angle if the Format
+f     attribute string associated with the SkyFrame axis generates an
+f     angle and as a time otherwise. This ensures that values produced
+f     by AST_FORMAT are correctly interpreted by AST_UNFORMAT.
+*     - Fields may be omitted, in which case they default to zero. The
+*     remaining fields may be identified by using appropriate field
+*     identification characters (see above) and/or by adding extra
+*     colon separators (e.g. "-05m13s" is equivalent to "-:05:13"). If
+*     a field is not identified explicitly, it is assumed that
+*     adjacent fields have been given, after taking account of any
+*     extra separator characters (e.g. "14:25.4s" specifies minutes
+*     and seconds, while "14::25.4s" specifies degrees and seconds).
+c     - If fields are omitted in such a way that the remaining ones
+c     cannot be identified uniquely (e.g. "01:02"), then the first
+c     field (either given explicitly or implied by an extra leading
+c     colon separator) is taken to be the most significant field that
+c     astFormat would produce when formatting a value (using the
+c     Format attribute associated with the SkyFrame axis).  By
+c     default, this means that the first field will normally be
+c     interpreted as degrees or hours. However, if this does not
+c     result in consistent field identification, then the last field
+c     (either given explicitly or implied by an extra trailing colon
+c     separator) is taken to to be the least significant field that
+c     astFormat would produce.
+f     - If fields are omitted in such a way that the remaining ones
+f     cannot be identified uniquely (e.g. "01:02"), then the first
+f     field (either given explicitly or implied by an extra leading
+f     colon separator) is taken to be the most significant field that
+f     AST_FORMAT would produce when formatting a value (using the
+f     Format attribute associated with the SkyFrame axis).  By
+f     default, this means that the first field will normally be
+f     interpreted as degrees or hours. However, if this does not
+f     result in consistent field identification, then the last field
+f     (either given explicitly or implied by an extra trailing colon
+f     separator) is taken to to be the least significant field that
+f     AST_FORMAT would produce.
+*
+c     This final convention is intended to ensure that values formatted
+c     by astFormat which contain less than three fields will be
+c     correctly interpreted if read back using astUnformat, even if
+c     they do not contain field identification characters.
+f     This final convention is intended to ensure that values formatted
+f     by AST_FORMAT which contain less than three fields will be
+f     correctly interpreted if read back using AST_UNFORMAT, even if
+f     they do not contain field identification characters.
+*
+*     Examples of acceptable SkyFrame input formats (with
+*     interpretation in parentheses) include:
+*     - -14d 13m 22.2s (-14d 13' 22.2")
+*     - + 12:34:56.7 (12d 34' 56.7" or 12h 34m 56.7s)
+*     - 001 : 02 : 03.4 (1d 02' 03.4" or 1h 02m 03.4s)
+*     - 22h 30 (22h 30m 00s)
+*     - 136::10" (136d 00' 10" or 136h 00m 10s)
+*     - -14M 27S (-0d 14' 27" or -0h 14m 27s)
+*     - -:14: (-0d 14' 00" or -0h 14m 00s)
+*     - -::4.1 (-0d 00' 04.1" or -0h 00m 04.1s)
+*     - .9" (0d 00' 00.9")
+*     - d12m (0d 12' 00")
+*     - H 12:22.3s (0h 12m 22.3s)
+*     - <bad> (AST__BAD)
+*
+*     Where alternative interpretations are shown, the choice of angle or
+*     time depends on the associated Format(axis) attribute.
+
+*  Notes:
+*     - A function value of zero (and no coordinate value) will be
+*     returned, without error, if the string supplied does not contain
+*     a suitably formatted value.
+c     - Beware that it is possible for a formatting error part-way
+c     through an input string to terminate input before it has been
+c     completely read, but to yield a coordinate value that appears
+c     valid. For example, if a user types "1.5r6" instead of "1.5e6",
+c     the "r" will terminate input, giving an incorrect coordinate
+c     value of 1.5. It is therefore most important to check the return
+c     value of this function to ensure that the correct number of
+c     characters have been read.
+f     - Beware that it is possible for a formatting error part-way
+f     through an input string to terminate input before it has been
+f     completely read, but to yield a coordinate value that appears
+f     valid. For example, if a user types "1.5R6" instead of "1.5E6",
+f     the "R" will terminate input, giving an incorrect coordinate
+f     value of 1.5. It is therefore most important to check the return
+f     value of this function to ensure that the correct number of
+f     characters have been read.
+*     - An error will result if a value is read which appears to have
+*     the correct format, but which cannot be converted into a valid
+*     coordinate value (for instance, because the value of one or more
+*     of its fields is invalid).
+*     - The string "<bad>" is recognised as a special case and will
+*     yield the coordinate value AST__BAD without error. The test for
+*     this string is case-insensitive and also permits embedded white
+*     space.
+c     - A function result of zero will be returned and no coordinate
+c     value will be returned via the "value" pointer if this function
+c     is invoked with the AST error status set, or if it should fail
+c     for any reason.
+f     - A function result of zero will be returned and no coordinate
+f     value will be returned via the VALUE argument if this function
+f     is invoked with the AST error status set, or if it should fail
+f     for any reason.
+*--
+
+*  Implementation Notes:
+*     This function implements the public interface for the
+*     astUnformat method. It is identical to astUnformat_ except that:
+*
+*     - The axis index is decremented by 1 before use. This allows the
+*     public interface to use 1-based axis numbers (whereas internally
+*     axis numbers are zero-based).
+*/
+
+/* Invoke the normal astUnformat_ function, adjusting the axis index
+   to become zero-based. */
+   return astUnformat( this, axis - 1, string, value );
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ast/frame.h b/ast/frame.h
new file mode 100644
index 0000000..3f5c3c0
--- /dev/null
+++ b/ast/frame.h
@@ -0,0 +1,1437 @@
+#if !defined( FRAME_INCLUDED )   /* Include this file only once */
+#define FRAME_INCLUDED
+/*
+*+
+*  Name:
+*     frame.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Frame class.
+
+*  Invocation:
+*     #include "frame.h"
+
+*  Description:
+*     This include file defines the interface to the Frame class and
+*     provides the type definitions, function prototypes and macros, etc.
+*     needed to use this class.
+*
+*     A Frame object encapsulates information about a coordinate
+*     system, including its axes. It may also act as a "template" to
+*     be matched against another Frame object. This process determines
+*     whether it is possible to perform a coordinate transformation
+*     between the two coordinates systems they describe.
+
+*  Inheritance:
+*     The Frame class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     Nin (integer, readonly)
+*        The Frame class sets this value to be equal to the number of
+*        Frame axes.
+*     Nout (integer, readonly)
+*        The Frame class sets this value to be equal to the number of
+*        Frame axes.
+
+*  New Attributes Defined:
+*     AlignSystem (string)
+*        This attribute takes a value to identify the coordinate system
+*        in which the Frame should be aligned with other Frames.
+*     Digits [or Digits(axis)] (integer)
+*        Specifies how many digits of precision are required by
+*        default when a coordinate value for a Frame is formatted
+*        (e.g. using the astFormat method). The Digits value acts as a
+*        default only and is over-ridden if a Format string is
+*        specified for an axis explicitly.
+*
+*        The default Digits value for a Frame is 7. This attribute
+*        normally applies to all the Frame's axes, but reference may
+*        be made to a particular axis by adding an axis subscript
+*        (e.g. Digits(1)). If a value is set for an individual axis,
+*        it will over-ride the Digits value for the Frame as a whole
+*        when formatting values for that axis.
+*     Direction(axis) (integer)
+*        A boolean value which specifies how coordinate values for
+*        each Frame axis should be displayed (e.g. in graphs). By
+*        default, it has the value one, indicating that they should be
+*        shown in the conventional sense (i.e. increasing left to
+*        right for an abscissa and bottom to top for an ordinate). If
+*        set to zero, this attribute indicates that the direction
+*        should be reversed (as would often be done for an
+*        astronomical magnitude or a right ascension axis, for
+*        example).
+*     Epoch (double)
+*        This value is used to qualify coordinate systems by
+*        giving the moment in time when the coordinates are known to
+*        be correct. Often, this will be the date of observation.
+*     Format(axis) (string)
+*        Specifies the format to be used to display coordinate values
+*        for each Frame axis (i.e. to convert them from binary to
+*        character form). The interpretation of this string (e.g. by
+*        derived classes) is left to the astFormat method which, in
+*        turn will invoke the astAxisFormat for the Axis object that
+*        describes each axis. By default, the Frame class supplies an
+*        Axis class object for each axis and this will interpret this
+*        parameter as a C "printf" format string which should be
+*        capable of formatting a single coordinate value stored as a
+*        double (e.g. "%1.7G"). If no Format string is set, the
+*        default format is based on the value of the Digits attribute.
+*     Label(axis) (string)
+*        Specifies the label to be attached to each Frame axis when it
+*        is represented in (e.g.) a graph. It is intended purely for
+*        interpretation by human readers and not by software. The
+*        default supplied by the Frame class is the string "Axis <n>",
+*        where <n> is 1, 2, etc. for each successive axis.
+*     MatchEnd (integer)
+*        A boolean value that controls how a Frame behaves when used
+*        as a template to match another Frame. If it is zero and a
+*        template Frame matches a target frame which has a different
+*        number of axes, then the axes wich occur first in the target
+*        frame will be matched and any trailing axes in either the
+*        target or template will be discarded (if necessary). If it is
+*        non-zero, however, the last axes in each frame will be
+*        matched and any un-matched leading axes will be discarded
+*        instead. The default value supplied by the Frame class is
+*        zero.
+*     MaxAxes (integer)
+*        Specifies the maximum number of axes in a target Frame that
+*        can be matched when using the Frame as a template. Normally,
+*        by default, this value is equal to the number of Frame axes,
+*        so that a Frame will only match another Frame with the same
+*        number of axes as itself. By setting a different value,
+*        however, Frames with different numbers of axes may be matched
+*        (the MatchEnd attribute then determines which of the
+*        individual axes are matched).
+*
+*        When setting this value, the value of the MinAxes attribute
+*        may be silently changed so that it remains consistent with
+*        (i.e. does not exceed) the new value. The default value may
+*        also be reduced if necessary to remain consistent with the
+*        MinAxes value.
+*     MinAxes (integer)
+*        Specifies the minimum number of axes in a target Frame that
+*        can be matched when using the Frame as a template. Normally,
+*        by default, this value is equal to the number of Frame axes,
+*        so that a Frame will only match another Frame with the same
+*        number of axes as itself. By setting a different value,
+*        however, Frames with different numbers of axes may be matched
+*        (the MatchEnd attribute then determines which of the
+*        individual axes are matched).
+*
+*        When setting this value, the value of the MaxAxes attribute
+*        may be silently changed so that it remains consistent with
+*        (i.e. is not less than) the new value. The default value may
+*        also be reduced if necessary to remain consistent with the
+*        MaxAxes value.
+*     Domain (string)
+*        A string which may be used to identify the physical domain to
+*        which a Frame applies and used as an additional key when
+*        matching a target Frame with a template. If the Domain
+*        attribute in the template Frame is set, then only target
+*        frames with the same Domain value will be matched. If a
+*        Domain is not set in the template Frame, the target Frame's
+*        Domain value will be ignored and has no effect on
+*        matching. The default value supplied by the Frame class is an
+*        empty string. Domain values are automatically converted to
+*        upper case and all white space is removed before use.
+*     Naxes (integer)
+*        A read-only attribute that gives the number of axes in a
+*        Frame (i.e.  the number of dimensions of the space which the
+*        Frame describes). This value is determined when the Frame is
+*        created.
+*     Permute (integer)
+*        A boolean value which specifies whether the axis order of a
+*        target Frame may be permuted in order to obtain a match with
+*        a template. If this value is set to zero in the template
+*        Frame, it will only match a target if it can do so without
+*        changing the order of its axes. The default value supplied by
+*        the Frame class is 1 (i.e. allow axis permutations).
+*     PreserveAxes (integer)
+*        A boolean value which determines how the "result" Frame is
+*        produced whan a target frame is matched by a template. If
+*        this value is zero in the template Frame, then the result
+*        Frame will have the same number of axes as the template. If
+*        it is non-zero, however, the axes of the target Frame will be
+*        preserved, so that the result Frame will have the same number
+*        of axes as the target. The default supplied by the Frame
+*        class is zero (i.e. target axes are not preserved).
+*
+*        The main use for this attribute is when the MaxAxes and/or
+*        MinAxes attributes have been set to search for a Frame which
+*        may have a different number of axes from the template. For
+*        example, if a 2-dimensional template Frame matches a
+*        3-dimensional target Frame, then by default the result is
+*        2-dimensional and the last axis (normally) will be
+*        discarded. However, if the template's PreserveAxes value is
+*        non-zero, the result will instead be 3-dimensional to
+*        correspond with the target Frame.
+*     Symbol(axis) (string)
+*        Specifies the symbol to be used to represent coordinate
+*        values for each Frame axis in "short form", such as in
+*        algebraic expressions where a full description of the axis
+*        would be inappropriate. Examples include "RA" and "Dec" (for
+*        Right Ascension and Declination).
+*
+*        The default supplied by the Frame class is the string
+*        "<Domain><n>", where <n> is 1, 2, etc. for successive axes,
+*        and <Domain> is the value of the Frame's Domain attribute
+*        (with any white space replaced by underscores and truncated
+*        if necessary so that the final string does not exceed 15
+*        characters). If no Domain value has been set, "x" is used as
+*        the <Domain> value in constructing this default string.
+*     System (string)
+*        This attribute takes a value to identify the coordinate system
+*        used to describe positions within the domain of the Frame.
+*     Title (string)
+*        Specifies a string to be used as a title on (e.g.) graphs to
+*        describe the coordinate system which the Frame
+*        represents. Examples would be "Detector Coordinates" or
+*        "Galactic Coordinates". This string is intended solely for
+*        interpretation by human readers and not by software. The
+*        default supplied by the Frame class is "<n>-D Coordinate
+*        System", where <n> is the number of Frame axes.
+*     Unit(axis) (string)
+*        Describes the units used to represent coordinate values on
+*        each Frame axis. The default supplied by the Frame class is
+*        an empty string.
+
+*  Methods Over-Ridden:
+*     Public:
+*        astGetNin
+*           Get the number of input coordinates for a Frame.
+*        astGetNout
+*           Get the number of output coordinates for a Frame.
+*        astTransform
+*           Use a Frame to transform a set of points.
+
+*     Protected:
+*        astClearAttrib
+*           Clear an attribute value for a Frame.
+*        astGetAttrib
+*           Get an attribute value for a Frame.
+*        astReportPoints
+*           Report the effect of transforming a set of points using a Frame.
+*        astSetAttrib
+*           Set an attribute value for a Frame.
+*        astTestAttrib
+*           Test if an attribute value has been set for a Frame.
+
+*  New Methods Defined:
+*     Public:
+*        astAngle
+*           Calculate the angle between three points.
+*        astAxAngle
+*           Find the angle from an axis to a line through two points.
+*        astAxDistance
+*           Calculate the distance between two axis values
+*        astAxOffset
+*           Calculate an offset along an axis
+*        astConvert
+*           Determine how to convert between two coordinate systems.
+*        astDistance
+*           Calculate the distance between two points.
+*        astFindFrame
+*           Find a coordinate system with specified characteristics
+*        astFormat
+*           Format a coordinate value for a Frame axis.
+*        astNorm
+*           Normalise a set of Frame coordinates.
+*        astOffset
+*           Calculate an offset along a geodesic curve.
+*        astOffset2
+*           Calculate an offset along a geodesic curve for a 2D Frame.
+*        astPermAxes
+*           Permute the order of a Frame's axes.
+*        astPickAxes
+*           Create a new Frame by picking axes from an existing one.
+*        astResolve
+*           Resolve a vector into two orthogonal components.
+*        astUnformat
+*           Read a formatted coordinate value for a Frame axis.
+
+*     Protected:
+*        astAbbrev
+*           Abbreviate a formatted Frame axis value by skipping
+*           leading fields.
+*        astCheckPerm
+*           Check that an array contains a valid permutation.
+*        astClearDigits
+*           Clear the Digits attribute for a Frame.
+*        astClearDirection
+*           Clear the Direction attribute for a Frame axis.
+*        astClearDomain
+*           Clear the Domain attribute for a Frame.
+*        astClearFormat
+*           Clear the Format attribute for a Frame axis.
+*        astClearLabel
+*           Clear the Label attribute for a Frame axis.
+*        astClearMatchEnd
+*           Clear the MatchEnd attribute for a Frame.
+*        astClearMaxAxes
+*           Clear the MaxAxes attribute for a Frame.
+*        astClearMinAxes
+*           Clear the MinAxes attribute for a Frame.
+*        astClearPermute
+*           Clear the Permute attribute for a Frame.
+*        astClearPreserveAxes
+*           Clear the PreserveAxes attribute for a Frame.
+*        astClearSymbol
+*           Clear the Symbol attribute for a Frame axis.
+*        astClearSystem
+*           Clear the value of the System attribute for a Frame.
+*        astClearTitle
+*           Clear the Title attribute for a Frame.
+*        astClearUnit
+*           Clear the Unit attribute for a Frame axis.
+*        astConvertX
+*           Determine how to convert between two coordinate systems.
+*        astFields
+*           Identify the fields within a formatted Frame axis value.
+*        astCentre
+*           Find a "nice" central value for tabulating Frame axis values.
+*        astGap
+*           Find a "nice" gap for tabulating Frame axis values.
+*        astGetAxis
+*           Obtain a pointer to a specified Axis from a Frame.
+*        astGetDigits
+*           Get the value of the Digits attribute for a Frame.
+*        astGetDirection
+*           Get the value of the Direction attribute for a Frame axis.
+*        astGetDomain
+*           Get a pointer to the Domain attribute for a Frame.
+*        astGetFormat
+*           Get a pointer to the Format attribute for a Frame axis.
+*        astGetLabel
+*           Get a pointer to the Label attribute for a Frame axis.
+*        astGetMatchEnd
+*           Get the value of the MatchEnd attribute for a Frame.
+*        astGetMaxAxes
+*           Get the value of the MaxAxes attribute for a Frame.
+*        astGetMinAxes
+*           Get the value of the MinAxes attribute for a Frame.
+*        astGetNaxes
+*           Determine how many axes a Frame has.
+*        astGetPerm
+*           Access the axis permutation array for a Frame.
+*        astGetPermute
+*           Get the value of the Permute attribute for a Frame.
+*        astGetPreserveAxes
+*           Get the value of the PreserveAxes attribute for a Frame.
+*        astGetSymbol
+*           Get a pointer to the Symbol attribute for a Frame axis.
+*        astGetSystem
+*           Get the value of the System attribute for a Frame.
+*        astGetTitle
+*           Get a pointer to the Title attribute for a Frame.
+*        astGetUnit
+*           Get a pointer to the Unit attribute for a Frame axis.
+*        astIsUnitFrame
+*           Returns a flag indicating if a Frame is equivalent to a UnitMap.
+*        astMatch
+*           Determine if conversion is possible between two coordinate systems.
+*        astOverlay
+*           Overlay the attributes of a template Frame on to another Frame.
+*        astPrimaryFrame
+*           Uniquely identify a primary Frame and one of its axes.
+*        astResolvePoints
+*           Resolve many vectors into two orthogonal components.
+*        astSetAxis
+*           Set a new Axis for a Frame.
+*        astSetDigits
+*           Set the value of the Digits attribute for a Frame.
+*        astSetDirection
+*           Set the value of the Direction attribute for a Frame axis.
+*        astSetDomain
+*           Set the value of the Domain attribute for a Frame.
+*        astSetFormat
+*           Set the value of the Format attribute for a Frame axis.
+*        astSetLabel
+*           Set the value of the Label attribute for a Frame axis.
+*        astSetMatchEnd
+*           Set the value of the MatchEnd attribute for a Frame.
+*        astSetMaxAxes
+*           Set the value of the MaxAxes attribute for a Frame.
+*        astSetMinAxes
+*           Set the value of the MinAxes attribute for a Frame.
+*        astSetPermute
+*           Set the value of the Permute attribute for a Frame.
+*        astSetPreserveAxes
+*           Set the value of the PreserveAxes attribute for a Frame.
+*        astSetSymbol
+*           Set the value of the Symbol attribute for a Frame axis.
+*        astSetSystem
+*           Set the value of the System attribute for a Frame.
+*        astSetTitle
+*           Set the value of the Title attribute for a Frame.
+*        astSetUnit
+*           Set the value of the Unit attribute for a Frame axis.
+*        astSubFrame
+*           Select axes from a Frame and convert to the new coordinate system.
+*        astTestDigits
+*           Test whether a value has been set for the Digits attribute of a
+*           Frame.
+*        astTestDirection
+*           Test whether a value has been set for the Direction attribute of a
+*           Frame axis.
+*        astTestDomain
+*           Test whether a value has been set for the Domain attribute of a
+*           Frame.
+*        astTestFormat
+*           Test whether a value has been set for the Format attribute of a
+*           Frame axis.
+*        astTestLabel
+*           Test whether a value has been set for the Label attribute of a
+*           Frame axis.
+*        astTestMatchEnd
+*           Test whether a value has been set for the MatchEnd attribute of a
+*           Frame.
+*        astTestMaxAxes
+*           Test whether a value has been set for the MaxAxes attribute of a
+*           Frame.
+*        astTestMinAxes
+*           Test whether a value has been set for the MinAxes attribute of a
+*           Frame.
+*        astTestPermute
+*           Test whether a value has been set for the Permute attribute of a
+*           Frame.
+*        astTestPreserveAxes
+*           Test whether a value has been set for the PreserveAxes attribute of
+*           a Frame.
+*        astTestSymbol
+*           Test whether a value has been set for the Symbol attribute of a
+*           Frame axis.
+*        astTestSystem
+*           Test whether a value has been set for the System attribute of a
+*           Frame.
+*        astTestTitle
+*           Test whether a value has been set for the Title attribute of a
+*           Frame.
+*        astTestUnit
+*           Test whether a value has been set for the Unit attribute of a Frame
+*           axis.
+*        astValidateAxis
+*           Validate and permute a Frame's axis index.
+*        astValidateAxisSelection
+*           Check that a set of axes selected from a Frame is valid.
+*        astValidateSystem
+*           Validate a Frame's System attribute.
+*        astSystemString
+*           Return a string representation of a System code.
+*        astSystemCode
+*           Return a code for a string representation of a System value
+
+*  Other Class Functions:
+*     Public:
+*        astFrame
+*           Create a Frame.
+*        astIsAFrame
+*           Test class membership.
+
+*     Protected:
+*        astCheckFrame
+*           Validate class membership.
+*        astInitFrame
+*           Initialise a Frame.
+*        astInitFrameVtab
+*           Initialise the virtual function table for the Frame class.
+*        astLoadFrame
+*           Load a Frame.
+
+*  Macros:
+*     Public:
+*        None.
+
+*     Protected:
+*        AST__BADSYSTEM
+*           A "bad" (undefined) value for the System attribute.
+
+*  Type Definitions:
+*     Public:
+*        AstFrame
+*           Frame object type.
+
+*     Protected:
+*        AstFrameVtab
+*           Frame virtual function table type.
+*        AstSystemType
+*           Enumerated type used for the System attribute.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: B.S. Berry (Starlink)
+
+*  History:
+*     1-MAR-1996 (RFWS):
+*        Original version.
+*     25-APR-1996 (RFWS):
+*        Tidied up, etc.
+*     11-SEP-1996 (RFWS):
+*        Added astGap (written by DSB).
+*     10-JUN-1997 (RFWS):
+*        Revised astConvert and added astFindFrame.
+*     15-FEB-1998 (RFWS):
+*        Added astUnformat.
+*     21-JUN-2001 (DSB):
+*        Added astAngle and astOffset2.
+*     29-AUG-2001 (DSB):
+*        Added astAxDistance and astAxOffset.
+*     4-SEP-2001 (DSB):
+*        Added astResolve.
+*     9-SEP-2001 (DSB):
+*        Added astBear.
+*     21-SEP-2001 (DSB):
+*        Replace astBear with astAxAngle.
+*     15-NOV-2002 (DSB):
+*        Moved System and Epoch attributes from SkyFrame into this class.
+*        Added AlignSystem attribute.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitFrameVtab method.
+*     24-JAN-2004 (DSB):
+*        o  Added astFields.
+*        o  Added argument "fmt" to astAbbrev.
+*     24-JUN-2004 (DSB):
+*        Remove unused entry "void (* SetMatchRange)( AstFrame *, int, int );"
+*        from AstFrameVtab structure.
+*     9-NOV-2004 (DSB):
+*        Added protected astIsAUnitFrame method.
+*     12-AUG-2005 (DSB):
+*        Added ObsLat and ObsLon attributes.
+*     14-OCT-2006 (DSB):
+*        Added dut1 to the Frame structure.
+*        Added Dut1 accessor methods.
+*     17-MAY-2007 (DSB):
+*        Added NormUnit attribute.
+*     14-JAN-2009 (DSB):
+*        Added astIntersect method.
+*     18-JUN-2009 (DSB):
+*        Added ObsAlt attribute.
+*     17-APR-2015 (DSB):
+*        Added astCentre.
+*     27-APR-2015 (DSB):
+*        Added InternalUnit attribute.
+*     26-OCT-2016 (DSB):
+*        Added method astAxNorm.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+#include "axis.h"                /* Coordinate Axis class */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#include <stddef.h>
+
+/* Macros. */
+/* ------- */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* A bad value for the System attribute. */
+#define AST__BADSYSTEM -1
+
+/* The legal System values recognized by this class of Frame. */
+#define AST__CART 0
+
+/* Flag bitmasks for use with astSetFrameFlags. */
+# define AST__INTFLAG 1  /* FrameSet integrity is currently being restored */
+
+/* Define constants used to size global arrays in this module. */
+#define AST__FRAME_LABEL_BUFF_LEN 100       /* Max length of default axis Label string */
+#define AST__FRAME_SYMBOL_BUFF_LEN 50       /* Max length of default axis Symbol string */
+#define AST__FRAME_TITLE_BUFF_LEN 100       /* Max length of default title string */
+#define AST__FRAME_GETATTRIB_BUFF_LEN 50    /* Max length of string returned by GetAttrib */
+#define AST__FRAME_ASTFMTDECIMALYR_BUFF_LEN 50    /* Max length of string returned by GetAttrib */
+#define AST__FRAME_ASTFORMATID_MAX_STRINGS 50     /* Number of string values buffer by astFormatID*/
+
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* Integer type used to store the System attribute values. */
+typedef int AstSystemType;
+
+/* Frame structure. */
+/* ------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstFrame {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstAxis **axis;               /* Pointer to array of Axis objects */
+   char *domain;                 /* Pointer to Domain string */
+   char *title;                  /* Pointer to Title string */
+   double epoch;                 /* Epoch as Modified Julian Date */
+   double obslat;                /* Geodetic latitude of observer */
+   double obslon;                /* Geodetic longitude of observer */
+   double obsalt;                /* Height above reference spheroid (geodetic, metres) */
+   double dut1;                  /* UT1-UTC in seconds */
+   int *perm;                    /* Pointer to axis permutation array */
+   int digits;                   /* Default digits of precision */
+   int match_end;                /* Match final axes of target? */
+   int active_unit;              /* Use Unit when aligning Frames? */
+   int max_axes;                 /* Minimum no. axes matched */
+   int min_axes;                 /* Max. no. axes matched */
+   int naxes;                    /* Number of axes */
+   int permute;                  /* Permute axes in order to match? */
+   int preserve_axes;            /* Preserve target axes? */
+   AstSystemType system;         /* Code identifying coordinate system */
+   AstSystemType alignsystem;    /* Code for Alignment coordinate system */
+   int flags;                    /* Bit mask containing various protected flags */
+   struct AstFrameSet *variants; /* FrameSet defining alternative properties for the Frame */
+} AstFrame;
+
+/* Cached Line structure. */
+/* ---------------------- */
+/* This structure contains information describing a line segment within a
+   2D Frame. It is used by other classes to store intermediate cached values
+   relating to the line in order to speed up repeated operations on the
+   line. */
+
+typedef struct AstLineDef {
+   AstFrame *frame;            /* Pointer to Frame in which the line is defined */
+   double length;              /* Line length */
+   int infinite;               /* Disregard the start and end of the line? */
+   double start[2];            /* Frame axis values at line start */
+   double end[2];              /* Frame axis values at line end */
+   double dir[2];              /* Unit vector defining line direction */
+   double q[2];                /* Unit vector perpendicular to line */
+} AstLineDef;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* The virtual function table makes a forward reference to the
+   AstFrameSet structure which is not defined until "frameset.h" is
+   included (below). Hence make a preliminary definition available
+   now. */
+struct AstFrameSet;
+
+/* This table contains all information that is the same for all objects in the
+   class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstFrameVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping;       /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstAxis *(* GetAxis)( AstFrame *, int, int * );
+   AstFrame *(* PickAxes)( AstFrame *, int, const int[], AstMapping **, int * );
+   AstLineDef *(* LineDef)( AstFrame *, const double[2], const double[2], int * );
+   AstPointSet *(* ResolvePoints)( AstFrame *, const double [], const double [], AstPointSet *, AstPointSet *, int * );
+   const char *(* Abbrev)( AstFrame *, int, const char *, const char *, const char *, int * );
+   const char *(* Format)( AstFrame *, int, double, int * );
+   const char *(* GetDomain)( AstFrame *, int * );
+   const char *(* GetFormat)( AstFrame *, int, int * );
+   const char *(* GetLabel)( AstFrame *, int, int * );
+   const char *(* GetSymbol)( AstFrame *, int, int * );
+   const char *(* GetTitle)( AstFrame *, int * );
+   const char *(* GetInternalUnit)( AstFrame *, int, int * );
+   const char *(* GetNormUnit)( AstFrame *, int, int * );
+   const char *(* GetUnit)( AstFrame *, int, int * );
+   const int *(* GetPerm)( AstFrame *, int * );
+   double (* Angle)( AstFrame *, const double[], const double[], const double[], int * );
+   double (* Distance)( AstFrame *, const double[], const double[], int * );
+   double (* Centre)( AstFrame *, int, double, double, int * );
+   double (* Gap)( AstFrame *, int, double, int *, int * );
+   int (* Fields)( AstFrame *, int, const char *, const char *, int, char **, int *, double *, int * );
+   double (* AxDistance)( AstFrame *, int, double, double, int * );
+   void (* AxNorm)( AstFrame *, int, int, int, double *, int * );
+   double (* AxOffset)( AstFrame *, int, double, double, int * );
+   int (* AxIn)( AstFrame *, int, double, double, double, int, int * );
+   int (* GetDigits)( AstFrame *, int * );
+   int (* GetDirection)( AstFrame *, int, int * );
+   int (* GetMatchEnd)( AstFrame *, int * );
+   int (* GetMaxAxes)( AstFrame *, int * );
+   int (* GetMinAxes)( AstFrame *, int * );
+   int (* GetNaxes)( AstFrame *, int * );
+   int (* GetPermute)( AstFrame *, int * );
+   int (* GetPreserveAxes)( AstFrame *, int * );
+   int (* IsUnitFrame)( AstFrame *, int * );
+   int (* LineCrossing)( AstFrame *, AstLineDef *, AstLineDef *, double **, int * );
+   int (* LineContains)( AstFrame *, AstLineDef *, int, double *, int * );
+   int (* Match)( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+   int (* SubFrame)( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+   int (* TestDigits)( AstFrame *, int * );
+   int (* TestDirection)( AstFrame *, int, int * );
+   int (* TestDomain)( AstFrame *, int * );
+   int (* TestFormat)( AstFrame *, int, int * );
+   int (* TestLabel)( AstFrame *, int, int * );
+   int (* TestMatchEnd)( AstFrame *, int * );
+   int (* TestMaxAxes)( AstFrame *, int * );
+   int (* TestMinAxes)( AstFrame *, int * );
+   int (* TestPermute)( AstFrame *, int * );
+   int (* TestPreserveAxes)( AstFrame *, int * );
+   int (* TestSymbol)( AstFrame *, int, int * );
+   int (* TestTitle)( AstFrame *, int * );
+   int (* TestUnit)( AstFrame *, int, int * );
+   int (* Unformat)( AstFrame *, int, const char *, double *, int * );
+   int (* ValidateAxis)( AstFrame *, int, int, const char *, int * );
+   AstSystemType (* ValidateSystem)( AstFrame *, AstSystemType, const char *, int * );
+   AstSystemType (* SystemCode)( AstFrame *, const char *, int * );
+   const char *(* SystemString)( AstFrame *, AstSystemType, int * );
+   struct AstFrameSet *(* Convert)( AstFrame *, AstFrame *, const char *, int * );
+   struct AstFrameSet *(* ConvertX)( AstFrame *, AstFrame *, const char *, int * );
+   struct AstFrameSet *(* FindFrame)( AstFrame *, AstFrame *, const char *, int * );
+   void (* MatchAxes)( AstFrame *, AstFrame *, int[], int * );
+   void (* MatchAxesX)( AstFrame *, AstFrame *, int[], int * );
+   void (* CheckPerm)( AstFrame *, const int *, const char *, int * );
+   void (* ClearDigits)( AstFrame *, int * );
+   void (* ClearDirection)( AstFrame *, int, int * );
+   void (* ClearDomain)( AstFrame *, int * );
+   void (* ClearFormat)( AstFrame *, int, int * );
+   void (* ClearLabel)( AstFrame *, int, int * );
+   void (* ClearMatchEnd)( AstFrame *, int * );
+   void (* ClearMaxAxes)( AstFrame *, int * );
+   void (* ClearMinAxes)( AstFrame *, int * );
+   void (* ClearPermute)( AstFrame *, int * );
+   void (* ClearPreserveAxes)( AstFrame *, int * );
+   void (* ClearSymbol)( AstFrame *, int, int * );
+   void (* ClearTitle)( AstFrame *, int * );
+   void (* ClearUnit)( AstFrame *, int, int * );
+   void (* Intersect)( AstFrame *, const double[2], const double[2], const double[2], const double[2], double[2], int * );
+   void (* Norm)( AstFrame *, double[], int * );
+   void (* NormBox)( AstFrame *, double *, double *, AstMapping *, int * );
+   void (* Offset)( AstFrame *, const double[], const double[], double, double[], int * );
+   double (* AxAngle)( AstFrame *, const double[2], const double[2], int, int * );
+   double (* Offset2)( AstFrame *, const double[2], double, double, double[2], int * );
+   void (* Overlay)( AstFrame *, const int *, AstFrame *, int * );
+   void (* PermAxes)( AstFrame *, const int[], int * );
+   void (* PrimaryFrame)( AstFrame *, int, AstFrame **, int *, int * );
+   void (* Resolve)( AstFrame *, const double [], const double [], const double [], double [], double *, double *, int * );
+   void (* SetAxis)( AstFrame *, int, AstAxis *, int * );
+   void (* SetDigits)( AstFrame *, int, int * );
+   void (* SetDirection)( AstFrame *, int, int, int * );
+   void (* SetDomain)( AstFrame *, const char *, int * );
+   void (* SetFormat)( AstFrame *, int, const char *, int * );
+   void (* SetLabel)( AstFrame *, int, const char *, int * );
+   void (* SetMatchEnd)( AstFrame *, int, int * );
+   void (* SetMaxAxes)( AstFrame *, int, int * );
+   void (* SetMinAxes)( AstFrame *, int, int * );
+   void (* SetPermute)( AstFrame *, int, int * );
+   void (* SetPreserveAxes)( AstFrame *, int, int * );
+   void (* SetSymbol)( AstFrame *, int, const char *, int * );
+   void (* SetTitle)( AstFrame *, const char *, int * );
+   void (* SetUnit)( AstFrame *, int, const char *, int * );
+   void (* ValidateAxisSelection)( AstFrame *, int, const int *, const char *, int * );
+   void (* LineOffset)( AstFrame *, AstLineDef *, double, double, double[2], int * );
+   AstPointSet *(* FrameGrid)( AstFrame *, int, const double *, const double *, int * );
+   struct AstFrameSet *(* GetFrameVariants)( AstFrame *, int * );
+   void (* SetFrameVariants)( AstFrame *, struct AstFrameSet *, int * );
+
+   double (* GetTop)( AstFrame *, int, int * );
+   int (* TestTop)( AstFrame *, int, int * );
+   void (* ClearTop)( AstFrame *, int, int * );
+   void (* SetTop)( AstFrame *, int, double, int * );
+
+   double (* GetBottom)( AstFrame *, int, int * );
+   int (* TestBottom)( AstFrame *, int, int * );
+   void (* ClearBottom)( AstFrame *, int, int * );
+   void (* SetBottom)( AstFrame *, int, double, int * );
+
+   AstSystemType (* GetSystem)( AstFrame *, int * );
+   int (* TestSystem)( AstFrame *, int * );
+   void (* ClearSystem)( AstFrame *, int * );
+   void (* SetSystem)( AstFrame *, AstSystemType, int * );
+
+   AstSystemType (* GetAlignSystem)( AstFrame *, int * );
+   int (* TestAlignSystem)( AstFrame *, int * );
+   void (* ClearAlignSystem)( AstFrame *, int * );
+   void (* SetAlignSystem)( AstFrame *, AstSystemType, int * );
+
+   double (* GetEpoch)( AstFrame *, int * );
+   int (* TestEpoch)( AstFrame *, int * );
+   void (* ClearEpoch)( AstFrame *, int * );
+   void (* SetEpoch)( AstFrame *, double, int * );
+
+   int (* TestActiveUnit)( AstFrame *, int * );
+   int (* GetActiveUnit)( AstFrame *, int * );
+   void (* SetActiveUnit)( AstFrame *, int, int * );
+
+   double (* GetObsLon)( AstFrame *, int * );
+   int (* TestObsLon)( AstFrame *, int * );
+   void (* ClearObsLon)( AstFrame *, int * );
+   void (* SetObsLon)( AstFrame *, double, int * );
+
+   double (* GetObsLat)( AstFrame *, int * );
+   int (* TestObsLat)( AstFrame *, int * );
+   void (* ClearObsLat)( AstFrame *, int * );
+   void (* SetObsLat)( AstFrame *, double, int * );
+
+   double (* GetObsAlt)( AstFrame *, int * );
+   int (* TestObsAlt)( AstFrame *, int * );
+   void (* ClearObsAlt)( AstFrame *, int * );
+   void (* SetObsAlt)( AstFrame *, double, int * );
+
+   double (* GetDut1)( AstFrame *, int * );
+   int (* TestDut1)( AstFrame *, int * );
+   void (* ClearDut1)( AstFrame *, int * );
+   void (* SetDut1)( AstFrame *, double, int * );
+
+   void (* SetFrameFlags)( AstFrame *, int, int * );
+   int (* GetFrameFlags)( AstFrame *, int * );
+
+} AstFrameVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstFrameGlobals {
+   AstFrameVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ AST__FRAME_GETATTRIB_BUFF_LEN + 1 ];
+   char *AstFormatID_Strings[ AST__FRAME_ASTFORMATID_MAX_STRINGS ];
+   int AstFormatID_Istr;
+   int AstFormatID_Init;
+   char Label_Buff[ AST__FRAME_LABEL_BUFF_LEN + 1 ];
+   char Symbol_Buff[ AST__FRAME_SYMBOL_BUFF_LEN + 1 ];
+   char Title_Buff[ AST__FRAME_TITLE_BUFF_LEN + 1 ];
+   char AstFmtDecimalYr_Buff[ AST__FRAME_ASTFMTDECIMALYR_BUFF_LEN + 1 ];
+} AstFrameGlobals;
+
+#endif
+#endif
+
+/* More include files. */
+/* =================== */
+/* The interface to the FrameSet class must be included here (after
+   the type definitions for the Frame class) because "frameset.h"
+   itself includes this file ("frame.h"), although "frameset.h" refers
+   to the AstFrameSet structure above. This seems a little strange at
+   first, but is simply analogous to making a forward reference to a
+   structure type when recursively defining a normal C structure
+   (except that here the definitions happen to be in separate include
+   files). */
+#include "frameset.h"
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Frame)            /* Check class membership */
+astPROTO_ISA(Frame)              /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+AstFrame *astFrame_( int, const char *, int *, ...);
+#else
+AstFrame *astFrameId_( int, const char *, ... )__attribute__((format(printf,2,3)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstFrame *astInitFrame_( void *, size_t, int, AstFrameVtab *, const char *,
+                         int, int * );
+
+/* Vtab initialiser. */
+void astInitFrameVtab_( AstFrameVtab *, const char *, int * );
+
+/* Loader. */
+AstFrame *astLoadFrame_( void *, size_t, AstFrameVtab *,
+                         const char *, AstChannel *channel, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitFrameGlobals_( AstFrameGlobals * );
+#endif
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+AstFrameSet *astConvert_( AstFrame *, AstFrame *, const char *, int * );
+AstFrameSet *astFindFrame_( AstFrame *, AstFrame *, const char *, int * );
+double astAngle_( AstFrame *, const double[], const double[], const double[], int * );
+double astAxAngle_( AstFrame *, const double[2], const double[2], int, int * );
+double astAxDistance_( AstFrame *, int, double, double, int * );
+double astAxOffset_( AstFrame *, int, double, double, int * );
+double astDistance_( AstFrame *, const double[], const double[], int * );
+double astOffset2_( AstFrame *, const double[2], double, double, double[2], int * );
+int astGetActiveUnit_( AstFrame *, int * );
+void astAxNorm_( AstFrame *, int, int, int, double *, int * );
+void astIntersect_( AstFrame *, const double[2], const double[2], const double[2], const double[2], double[2], int * );
+void astMatchAxes_( AstFrame *, AstFrame *, int[], int * );
+void astNorm_( AstFrame *, double[], int * );
+void astOffset_( AstFrame *, const double[], const double[], double, double[], int * );
+void astResolve_( AstFrame *, const double [], const double [], const double [], double [], double *, double *, int * );
+void astSetActiveUnit_( AstFrame *, int, int * );
+AstFrameSet *astGetFrameVariants_( AstFrame *, int * );
+void astSetFrameVariants_( AstFrame *, AstFrameSet *, int * );
+
+#if defined(astCLASS)            /* Protected */
+void astNormBox_( AstFrame *, double *, double *, AstMapping *, int * );
+AstFrame *astPickAxes_( AstFrame *, int, const int[], AstMapping **, int * );
+const char *astFormat_( AstFrame *, int, double, int * );
+int astUnformat_( AstFrame *, int, const char *, double *, int * );
+void astPermAxes_( AstFrame *, const int[], int * );
+#else
+AstFrame *astPickAxesId_( AstFrame *, int, const int[], AstMapping **, int * );
+const char *astFormatId_( AstFrame *, int, double, int * );
+int astUnformatId_( AstFrame *, int, const char *, double *, int * );
+void astPermAxesId_( AstFrame *, const int[], int * );
+#endif
+
+#if defined(astCLASS)            /* Protected */
+int astAxIn_( AstFrame *, int, double, double, double, int, int * );
+AstAxis * astGetAxis_( AstFrame *, int, int * );
+AstFrameSet *astConvertX_( AstFrame *, AstFrame *, const char *, int * );
+void astMatchAxesX_( AstFrame *, AstFrame *, int[], int * );
+AstLineDef *astLineDef_( AstFrame *, const double[2], const double[2], int * );
+AstPointSet *astResolvePoints_( AstFrame *, const double [], const double [], AstPointSet *, AstPointSet *, int * );
+const char *astAbbrev_( AstFrame *, int, const char *, const char *, const char *, int * );
+const char *astGetDomain_( AstFrame *, int * );
+const char *astGetFormat_( AstFrame *, int, int * );
+const char *astGetLabel_( AstFrame *, int, int * );
+const char *astGetSymbol_( AstFrame *, int, int * );
+const char *astGetTitle_( AstFrame *, int * );
+const char *astGetUnit_( AstFrame *, int, int * );
+const char *astGetInternalUnit_( AstFrame *, int, int * );
+const char *astGetNormUnit_( AstFrame *, int, int * );
+const int *astGetPerm_( AstFrame *, int * );
+double astCentre_( AstFrame *, int, double, double, int * );
+double astGap_( AstFrame *, int, double, int *, int * );
+int astFields_( AstFrame *, int, const char *, const char *, int, char **, int *, double *, int * );
+int astGetDigits_( AstFrame *, int * );
+int astGetDirection_( AstFrame *, int, int * );
+int astGetMatchEnd_( AstFrame *, int * );
+int astGetMaxAxes_( AstFrame *, int * );
+int astGetMinAxes_( AstFrame *, int * );
+int astGetNaxes_( AstFrame *, int * );
+int astGetPermute_( AstFrame *, int * );
+int astGetPreserveAxes_( AstFrame *, int * );
+int astIsUnitFrame_( AstFrame *, int * );
+int astLineCrossing_( AstFrame *, AstLineDef *, AstLineDef *, double **, int * );
+int astLineContains_( AstFrame *, AstLineDef *, int, double *, int * );
+int astMatch_( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+int astSubFrame_( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+int astTestDigits_( AstFrame *, int * );
+int astTestDirection_( AstFrame *, int, int * );
+int astTestDomain_( AstFrame *, int * );
+int astTestFormat_( AstFrame *, int, int * );
+int astTestLabel_( AstFrame *, int, int * );
+int astTestMatchEnd_( AstFrame *, int * );
+int astTestMaxAxes_( AstFrame *, int * );
+int astTestMinAxes_( AstFrame *, int * );
+int astTestPermute_( AstFrame *, int * );
+int astTestPreserveAxes_( AstFrame *, int * );
+int astTestSymbol_( AstFrame *, int, int * );
+int astTestTitle_( AstFrame *, int * );
+int astTestUnit_( AstFrame *, int, int * );
+int astValidateAxis_( AstFrame *, int, int, const char *, int * );
+AstSystemType astValidateSystem_( AstFrame *, AstSystemType, const char *, int * );
+AstSystemType astSystemCode_( AstFrame *, const char *, int * );
+const char *astSystemString_( AstFrame *, AstSystemType, int * );
+void astCheckPerm_( AstFrame *, const int *, const char *, int * );
+void astClearDigits_( AstFrame *, int * );
+void astClearDirection_( AstFrame *, int, int * );
+void astClearDomain_( AstFrame *, int * );
+void astClearFormat_( AstFrame *, int, int * );
+void astClearLabel_( AstFrame *, int, int * );
+void astClearMatchEnd_( AstFrame *, int * );
+void astClearMaxAxes_( AstFrame *, int * );
+void astClearMinAxes_( AstFrame *, int * );
+void astClearPermute_( AstFrame *, int * );
+void astClearPreserveAxes_( AstFrame *, int * );
+void astClearSymbol_( AstFrame *, int, int * );
+void astClearTitle_( AstFrame *, int * );
+void astClearUnit_( AstFrame *, int, int * );
+void astOverlay_( AstFrame *, const int *, AstFrame *, int * );
+void astPrimaryFrame_( AstFrame *, int, AstFrame **, int *, int * );
+void astSetAxis_( AstFrame *, int, AstAxis *, int * );
+void astSetDigits_( AstFrame *, int, int * );
+void astSetDirection_( AstFrame *, int, int, int * );
+void astSetDomain_( AstFrame *, const char *, int * );
+void astSetFormat_( AstFrame *, int, const char *, int * );
+void astSetLabel_( AstFrame *, int, const char *, int * );
+void astSetMatchEnd_( AstFrame *, int, int * );
+void astSetMaxAxes_( AstFrame *, int, int * );
+void astSetMinAxes_( AstFrame *, int, int * );
+void astSetPermute_( AstFrame *, int, int * );
+void astSetPreserveAxes_( AstFrame *, int, int * );
+void astSetSymbol_( AstFrame *, int, const char *, int * );
+void astSetTitle_( AstFrame *, const char *, int * );
+void astSetUnit_( AstFrame *, int, const char *, int * );
+void astValidateAxisSelection_( AstFrame *, int, const int *, const char *, int * );
+double astReadDateTime_( const char *, int * );
+const char *astFmtDecimalYr_( double, int, int * );
+void astLineOffset_( AstFrame *, AstLineDef *, double, double, double[2], int * );
+AstPointSet *astFrameGrid_( AstFrame *, int, const double *, const double *, int * );
+
+double astGetTop_( AstFrame *, int, int * );
+int astTestTop_( AstFrame *, int, int * );
+void astClearTop_( AstFrame *, int, int * );
+void astSetTop_( AstFrame *, int, double, int * );
+
+double astGetBottom_( AstFrame *, int, int * );
+int astTestBottom_( AstFrame *, int, int * );
+void astClearBottom_( AstFrame *, int, int * );
+void astSetBottom_( AstFrame *, int, double, int * );
+
+AstSystemType astGetSystem_( AstFrame *, int * );
+int astTestSystem_( AstFrame *, int * );
+void astClearSystem_( AstFrame *, int * );
+void astSetSystem_( AstFrame *, AstSystemType, int * );
+
+AstSystemType astGetAlignSystem_( AstFrame *, int * );
+int astTestAlignSystem_( AstFrame *, int * );
+void astClearAlignSystem_( AstFrame *, int * );
+void astSetAlignSystem_( AstFrame *, AstSystemType, int * );
+
+double astGetEpoch_( AstFrame *, int * );
+int astTestEpoch_( AstFrame *, int * );
+void astClearEpoch_( AstFrame *, int * );
+void astSetEpoch_( AstFrame *, double, int * );
+
+double astGetObsLon_( AstFrame *, int * );
+int astTestObsLon_( AstFrame *, int * );
+void astClearObsLon_( AstFrame *, int * );
+void astSetObsLon_( AstFrame *, double, int * );
+
+double astGetObsLat_( AstFrame *, int * );
+int astTestObsLat_( AstFrame *, int * );
+void astClearObsLat_( AstFrame *, int * );
+void astSetObsLat_( AstFrame *, double, int * );
+
+double astGetObsAlt_( AstFrame *, int * );
+int astTestObsAlt_( AstFrame *, int * );
+void astClearObsAlt_( AstFrame *, int * );
+void astSetObsAlt_( AstFrame *, double, int * );
+
+double astGetDut1_( AstFrame *, int * );
+int astTestDut1_( AstFrame *, int * );
+void astClearDut1_( AstFrame *, int * );
+void astSetDut1_( AstFrame *, double, int * );
+
+int astTestActiveUnit_( AstFrame *, int * );
+
+void astSetFrameFlags_( AstFrame *, int, int * );
+int astGetFrameFlags_( AstFrame *, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class to make
+   them easier to invoke (e.g. to avoid type mis-matches when passing pointers
+   to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them to
+   validate their own arguments. We must use a cast when passing object
+   pointers (so that they can accept objects from derived classes). */
+
+/* Check class membership. */
+#define astCheckFrame(this) astINVOKE_CHECK(Frame,this,0)
+#define astVerifyFrame(this) astINVOKE_CHECK(Frame,this,1)
+
+/* Test class membership. */
+#define astIsAFrame(this) astINVOKE_ISA(Frame,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+#define astFrame astINVOKE(F,astFrame_)
+#else
+#define astFrame astINVOKE(F,astFrameId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitFrame(mem,size,init,vtab,name,naxes) \
+astINVOKE(O,astInitFrame_(mem,size,init,vtab,name,naxes,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitFrameVtab(vtab,name) astINVOKE(V,astInitFrameVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadFrame(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadFrame_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckFrame to validate Frame pointers before
+   use. This provides a contextual error report if a pointer to the
+   wrong sort of Object is supplied. */
+#define astConvert(from,to,domainlist) \
+astINVOKE(O,astConvert_(astCheckFrame(from),astCheckFrame(to),domainlist,STATUS_PTR))
+#define astAngle(this,a,b,c) \
+astINVOKE(V,astAngle_(astCheckFrame(this),a,b,c,STATUS_PTR))
+#define astDistance(this,point1,point2) \
+astINVOKE(V,astDistance_(astCheckFrame(this),point1,point2,STATUS_PTR))
+#define astFindFrame(target,template,domainlist) \
+astINVOKE(O,astFindFrame_(astCheckFrame(target),astCheckFrame(template),domainlist,STATUS_PTR))
+#define astMatchAxes(frm1,frm2,axes) \
+astINVOKE(V,astMatchAxes_(astCheckFrame(frm1),astCheckFrame(frm2),axes,STATUS_PTR))
+#define astNorm(this,value) \
+astINVOKE(V,astNorm_(astCheckFrame(this),value,STATUS_PTR))
+#define astAxDistance(this,axis,v1,v2) \
+astINVOKE(V,astAxDistance_(astCheckFrame(this),axis,v1,v2,STATUS_PTR))
+#define astAxNorm(this,axis,oper,nval,values) \
+astINVOKE(V,astAxNorm_(astCheckFrame(this),axis,oper,nval,values,STATUS_PTR))
+#define astAxOffset(this,axis,v1,dist) \
+astINVOKE(V,astAxOffset_(astCheckFrame(this),axis,v1,dist,STATUS_PTR))
+#define astOffset(this,point1,point2,offset,point3) \
+astINVOKE(V,astOffset_(astCheckFrame(this),point1,point2,offset,point3,STATUS_PTR))
+#define astAxAngle(this,a,b,axis) \
+astINVOKE(V,astAxAngle_(astCheckFrame(this),a,b,axis,STATUS_PTR))
+#define astIntersect(this,a1,a2,b1,b2,cross) \
+astINVOKE(V,astIntersect_(astCheckFrame(this),a1,a2,b1,b2,cross,STATUS_PTR))
+#define astOffset2(this,point1,angle,offset,point2) \
+astINVOKE(V,astOffset2_(astCheckFrame(this),point1,angle,offset,point2,STATUS_PTR))
+#define astResolve(this,point1,point2,point3,point4,d1,d2) \
+astINVOKE(V,astResolve_(astCheckFrame(this),point1,point2,point3,point4,d1,d2,STATUS_PTR))
+#define astGetActiveUnit(this) \
+astINVOKE(V,astGetActiveUnit_(astCheckFrame(this),STATUS_PTR))
+#define astSetActiveUnit(this,value) \
+astINVOKE(V,astSetActiveUnit_(astCheckFrame(this),value,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#define astGetFrameVariants(this) \
+astINVOKE(O,astGetFrameVariants_(astCheckFrame(this),STATUS_PTR))
+#define astSetFrameVariants(this,variants) \
+astINVOKE(V,astSetFrameVariants_(astCheckFrame(this),astCheckFrameSet(variants),STATUS_PTR))
+#define astNormBox(this,lbnd,ubnd,reg) \
+astINVOKE(V,astNormBox_(astCheckFrame(this),lbnd,ubnd,astCheckMapping(reg),STATUS_PTR))
+#define astFormat(this,axis,value) \
+astINVOKE(V,astFormat_(astCheckFrame(this),axis,value,STATUS_PTR))
+#define astPermAxes(this,perm) \
+astINVOKE(V,astPermAxes_(astCheckFrame(this),perm,STATUS_PTR))
+#define astPickAxes(this,naxes,axes,map) \
+astINVOKE(O,astPickAxes_(astCheckFrame(this),naxes,axes,(AstMapping **)(map),STATUS_PTR))
+#define astUnformat(this,axis,string,value) \
+astINVOKE(V,astUnformat_(astCheckFrame(this),axis,string,value,STATUS_PTR))
+#else
+#define astFormat(this,axis,value) \
+astINVOKE(V,astFormatId_(astCheckFrame(this),axis,value,STATUS_PTR))
+#define astPermAxes(this,perm) \
+astINVOKE(V,astPermAxesId_(astCheckFrame(this),perm,STATUS_PTR))
+#define astPickAxes(this,naxes,axes,map) \
+astINVOKE(O,astPickAxesId_(astCheckFrame(this),naxes,axes,(AstMapping **)(map),STATUS_PTR))
+#define astUnformat(this,axis,string,value) \
+astINVOKE(V,astUnformatId_(astCheckFrame(this),axis,string,value,STATUS_PTR))
+#endif
+
+#if defined(astCLASS)            /* Protected */
+#define astAxIn(this,axis,lo,hi,val,closed) \
+astINVOKE(V,astAxIn_(astCheckFrame(this),axis,lo,hi,val,closed,STATUS_PTR))
+#define astAbbrev(this,axis,fmt,str1,str2) \
+astINVOKE(V,astAbbrev_(astCheckFrame(this),axis,fmt,str1,str2,STATUS_PTR))
+#define astFields(this,axis,fmt,str,maxfld,fields,nc,val) \
+astINVOKE(V,astFields_(astCheckFrame(this),axis,fmt,str,maxfld,fields,nc,val,STATUS_PTR))
+#define astCheckPerm(this,perm,method) \
+astINVOKE(V,astCheckPerm_(astCheckFrame(this),perm,method,STATUS_PTR))
+#define astResolvePoints(this,p1,p2,in,out) \
+astINVOKE(O,astResolvePoints_(astCheckFrame(this),p1,p2,astCheckPointSet(in),((out)?astCheckPointSet(out):NULL),STATUS_PTR))
+#define astLineDef(this,p1,p2) \
+astINVOKE(V,astLineDef_(astCheckFrame(this),p1,p2,STATUS_PTR))
+#define astLineOffset(this,line,par,prp,point) \
+astINVOKE(V,astLineOffset_(astCheckFrame(this),line,par,prp,point,STATUS_PTR))
+#define astFrameGrid(this,size,lbnd,ubnd) \
+astINVOKE(O,astFrameGrid_(astCheckFrame(this),size,lbnd,ubnd,STATUS_PTR))
+#define astLineCrossing(this,l1,l2,cross) \
+astINVOKE(V,astLineCrossing_(astCheckFrame(this),l1,l2,cross,STATUS_PTR))
+#define astLineContains(this,l,def,point) \
+astINVOKE(V,astLineContains_(astCheckFrame(this),l,def,point,STATUS_PTR))
+#define astClearDigits(this) \
+astINVOKE(V,astClearDigits_(astCheckFrame(this),STATUS_PTR))
+#define astClearDirection(this,axis) \
+astINVOKE(V,astClearDirection_(astCheckFrame(this),axis,STATUS_PTR))
+#define astClearDomain(this) \
+astINVOKE(V,astClearDomain_(astCheckFrame(this),STATUS_PTR))
+#define astClearFormat(this,axis) \
+astINVOKE(V,astClearFormat_(astCheckFrame(this),axis,STATUS_PTR))
+#define astClearLabel(this,axis) \
+astINVOKE(V,astClearLabel_(astCheckFrame(this),axis,STATUS_PTR))
+#define astClearMatchEnd(this) \
+astINVOKE(V,astClearMatchEnd_(astCheckFrame(this),STATUS_PTR))
+#define astClearMaxAxes(this) \
+astINVOKE(V,astClearMaxAxes_(astCheckFrame(this),STATUS_PTR))
+#define astClearMinAxes(this) \
+astINVOKE(V,astClearMinAxes_(astCheckFrame(this),STATUS_PTR))
+#define astClearPermute(this) \
+astINVOKE(V,astClearPermute_(astCheckFrame(this),STATUS_PTR))
+#define astClearPreserveAxes(this) \
+astINVOKE(V,astClearPreserveAxes_(astCheckFrame(this),STATUS_PTR))
+#define astClearSymbol(this,axis) \
+astINVOKE(V,astClearSymbol_(astCheckFrame(this),axis,STATUS_PTR))
+#define astClearTitle(this) \
+astINVOKE(V,astClearTitle_(astCheckFrame(this),STATUS_PTR))
+#define astClearUnit(this,axis) \
+astINVOKE(V,astClearUnit_(astCheckFrame(this),axis,STATUS_PTR))
+#define astConvertX(to,from,domainlist) \
+astINVOKE(O,astConvertX_(astCheckFrame(to),astCheckFrame(from),domainlist,STATUS_PTR))
+#define astCentre(this,axis,value,gap) \
+astINVOKE(V,astCentre_(astCheckFrame(this),axis,value,gap,STATUS_PTR))
+#define astGap(this,axis,gap,ntick) \
+astINVOKE(V,astGap_(astCheckFrame(this),axis,gap,ntick,STATUS_PTR))
+#define astGetAxis(this,axis) \
+astINVOKE(O,astGetAxis_(astCheckFrame(this),axis,STATUS_PTR))
+#define astGetDigits(this) \
+astINVOKE(V,astGetDigits_(astCheckFrame(this),STATUS_PTR))
+#define astGetDirection(this,axis) \
+astINVOKE(V,astGetDirection_(astCheckFrame(this),axis,STATUS_PTR))
+#define astGetDomain(this) \
+astINVOKE(V,astGetDomain_(astCheckFrame(this),STATUS_PTR))
+#define astGetFormat(this,axis) \
+astINVOKE(V,astGetFormat_(astCheckFrame(this),axis,STATUS_PTR))
+#define astGetLabel(this,axis) \
+astINVOKE(V,astGetLabel_(astCheckFrame(this),axis,STATUS_PTR))
+#define astGetMatchEnd(this) \
+astINVOKE(V,astGetMatchEnd_(astCheckFrame(this),STATUS_PTR))
+#define astGetMaxAxes(this) \
+astINVOKE(V,astGetMaxAxes_(astCheckFrame(this),STATUS_PTR))
+#define astGetMinAxes(this) \
+astINVOKE(V,astGetMinAxes_(astCheckFrame(this),STATUS_PTR))
+#define astGetNaxes(this) \
+astINVOKE(V,astGetNaxes_(astCheckFrame(this),STATUS_PTR))
+#define astGetPerm(this) \
+astINVOKE(V,astGetPerm_(astCheckFrame(this),STATUS_PTR))
+#define astGetPermute(this) \
+astINVOKE(V,astGetPermute_(astCheckFrame(this),STATUS_PTR))
+#define astGetPreserveAxes(this) \
+astINVOKE(V,astGetPreserveAxes_(astCheckFrame(this),STATUS_PTR))
+#define astGetSymbol(this,axis) \
+astINVOKE(V,astGetSymbol_(astCheckFrame(this),axis,STATUS_PTR))
+#define astGetTitle(this) \
+astINVOKE(V,astGetTitle_(astCheckFrame(this),STATUS_PTR))
+#define astGetUnit(this,axis) \
+astINVOKE(V,astGetUnit_(astCheckFrame(this),axis,STATUS_PTR))
+#define astGetNormUnit(this,axis) \
+astINVOKE(V,astGetNormUnit_(astCheckFrame(this),axis,STATUS_PTR))
+#define astGetInternalUnit(this,axis) \
+astINVOKE(V,astGetInternalUnit_(astCheckFrame(this),axis,STATUS_PTR))
+#define astMatch(template,target,matchsub,template_axes,target_axes,map,result) \
+astINVOKE(V,astMatch_(astCheckFrame(template),astCheckFrame(target),matchsub,template_axes,target_axes,(AstMapping **)(map),(AstFrame **)(result),STATUS_PTR))
+#define astIsUnitFrame(this) \
+astINVOKE(V,astIsUnitFrame_(astCheckFrame(this),STATUS_PTR))
+#define astOverlay(template,template_axes,result) \
+astINVOKE(V,astOverlay_(astCheckFrame(template),template_axes,astCheckFrame(result),STATUS_PTR))
+#define astPrimaryFrame(this,axis1,frame,axis2) \
+astINVOKE(V,astPrimaryFrame_(astCheckFrame(this),axis1,(AstFrame **)(frame),axis2,STATUS_PTR))
+#define astSetAxis(this,axis,newaxis) \
+astINVOKE(V,astSetAxis_(astCheckFrame(this),axis,astCheckAxis(newaxis),STATUS_PTR))
+#define astSetDigits(this,digits) \
+astINVOKE(V,astSetDigits_(astCheckFrame(this),digits,STATUS_PTR))
+#define astSetDirection(this,axis,direction) \
+astINVOKE(V,astSetDirection_(astCheckFrame(this),axis,direction,STATUS_PTR))
+#define astSetDomain(this,domain) \
+astINVOKE(V,astSetDomain_(astCheckFrame(this),domain,STATUS_PTR))
+#define astSetFormat(this,axis,format) \
+astINVOKE(V,astSetFormat_(astCheckFrame(this),axis,format,STATUS_PTR))
+#define astSetLabel(this,axis,label) \
+astINVOKE(V,astSetLabel_(astCheckFrame(this),axis,label,STATUS_PTR))
+#define astSetMatchEnd(this,value) \
+astINVOKE(V,astSetMatchEnd_(astCheckFrame(this),value,STATUS_PTR))
+#define astSetMaxAxes(this,value) \
+astINVOKE(V,astSetMaxAxes_(astCheckFrame(this),value,STATUS_PTR))
+#define astSetMinAxes(this,value) \
+astINVOKE(V,astSetMinAxes_(astCheckFrame(this),value,STATUS_PTR))
+#define astSetPermute(this,value) \
+astINVOKE(V,astSetPermute_(astCheckFrame(this),value,STATUS_PTR))
+#define astSetPreserveAxes(this,value) \
+astINVOKE(V,astSetPreserveAxes_(astCheckFrame(this),value,STATUS_PTR))
+#define astSetSymbol(this,axis,symbol) \
+astINVOKE(V,astSetSymbol_(astCheckFrame(this),axis,symbol,STATUS_PTR))
+#define astSetTitle(this,title) \
+astINVOKE(V,astSetTitle_(astCheckFrame(this),title,STATUS_PTR))
+#define astSetUnit(this,axis,unit) \
+astINVOKE(V,astSetUnit_(astCheckFrame(this),axis,unit,STATUS_PTR))
+#define astSubFrame(target,template,result_naxes,target_axes,template_axes,map,result) \
+astINVOKE(V,astSubFrame_(astCheckFrame(target),template?astCheckFrame(template):NULL,result_naxes,target_axes,template_axes,(AstMapping **)(map),(AstFrame **)(result),STATUS_PTR))
+#define astTestDigits(this) \
+astINVOKE(V,astTestDigits_(astCheckFrame(this),STATUS_PTR))
+#define astTestDirection(this,axis) \
+astINVOKE(V,astTestDirection_(astCheckFrame(this),axis,STATUS_PTR))
+#define astTestDomain(this) \
+astINVOKE(V,astTestDomain_(astCheckFrame(this),STATUS_PTR))
+#define astTestFormat(this,axis) \
+astINVOKE(V,astTestFormat_(astCheckFrame(this),axis,STATUS_PTR))
+#define astTestLabel(this,axis) \
+astINVOKE(V,astTestLabel_(astCheckFrame(this),axis,STATUS_PTR))
+#define astTestMatchEnd(this) \
+astINVOKE(V,astTestMatchEnd_(astCheckFrame(this),STATUS_PTR))
+#define astTestMaxAxes(this) \
+astINVOKE(V,astTestMaxAxes_(astCheckFrame(this),STATUS_PTR))
+#define astTestMinAxes(this) \
+astINVOKE(V,astTestMinAxes_(astCheckFrame(this),STATUS_PTR))
+#define astTestPermute(this) \
+astINVOKE(V,astTestPermute_(astCheckFrame(this),STATUS_PTR))
+#define astTestPreserveAxes(this) \
+astINVOKE(V,astTestPreserveAxes_(astCheckFrame(this),STATUS_PTR))
+#define astTestSymbol(this,axis) \
+astINVOKE(V,astTestSymbol_(astCheckFrame(this),axis,STATUS_PTR))
+#define astTestTitle(this) \
+astINVOKE(V,astTestTitle_(astCheckFrame(this),STATUS_PTR))
+#define astTestUnit(this,axis) \
+astINVOKE(V,astTestUnit_(astCheckFrame(this),axis,STATUS_PTR))
+#define astValidateAxis(this,axis,fwd,method) \
+astINVOKE(V,astValidateAxis_(astCheckFrame(this),axis,fwd,method,STATUS_PTR))
+#define astValidateAxisSelection(this,naxes,axes,method) \
+astINVOKE(V,astValidateAxisSelection_(astCheckFrame(this),naxes,axes,method,STATUS_PTR))
+
+#define astMatchAxesX(frm2,frm1,axes) \
+astINVOKE(V,astMatchAxesX_(astCheckFrame(frm2),astCheckFrame(frm1),axes,STATUS_PTR))
+
+#define astFmtDecimalYr(year,digits) astFmtDecimalYr_(year,digits,STATUS_PTR)
+#define astReadDateTime(value) astReadDateTime_(value,STATUS_PTR)
+
+#define astValidateSystem(this,system,method) \
+astINVOKE(V,astValidateSystem_(astCheckFrame(this),system,method,STATUS_PTR))
+#define astSystemString(this,system) \
+astINVOKE(V,astSystemString_(astCheckFrame(this),system,STATUS_PTR))
+#define astSystemCode(this,system) \
+astINVOKE(V,astSystemCode_(astCheckFrame(this),system,STATUS_PTR))
+
+#define astClearTop(this,axis) \
+astINVOKE(V,astClearTop_(astCheckFrame(this),axis,STATUS_PTR))
+#define astGetTop(this,axis) \
+astINVOKE(V,astGetTop_(astCheckFrame(this),axis,STATUS_PTR))
+#define astSetTop(this,axis,value) \
+astINVOKE(V,astSetTop_(astCheckFrame(this),axis,value,STATUS_PTR))
+#define astTestTop(this,axis) \
+astINVOKE(V,astTestTop_(astCheckFrame(this),axis,STATUS_PTR))
+
+#define astClearBottom(this,axis) \
+astINVOKE(V,astClearBottom_(astCheckFrame(this),axis,STATUS_PTR))
+#define astGetBottom(this,axis) \
+astINVOKE(V,astGetBottom_(astCheckFrame(this),axis,STATUS_PTR))
+#define astSetBottom(this,axis,value) \
+astINVOKE(V,astSetBottom_(astCheckFrame(this),axis,value,STATUS_PTR))
+#define astTestBottom(this,axis) \
+astINVOKE(V,astTestBottom_(astCheckFrame(this),axis,STATUS_PTR))
+
+#define astClearSystem(this) \
+astINVOKE(V,astClearSystem_(astCheckFrame(this),STATUS_PTR))
+#define astGetSystem(this) \
+astINVOKE(V,astGetSystem_(astCheckFrame(this),STATUS_PTR))
+#define astSetSystem(this,value) \
+astINVOKE(V,astSetSystem_(astCheckFrame(this),value,STATUS_PTR))
+#define astTestSystem(this) \
+astINVOKE(V,astTestSystem_(astCheckFrame(this),STATUS_PTR))
+
+#define astClearAlignSystem(this) \
+astINVOKE(V,astClearAlignSystem_(astCheckFrame(this),STATUS_PTR))
+#define astGetAlignSystem(this) \
+astINVOKE(V,astGetAlignSystem_(astCheckFrame(this),STATUS_PTR))
+#define astSetAlignSystem(this,value) \
+astINVOKE(V,astSetAlignSystem_(astCheckFrame(this),value,STATUS_PTR))
+#define astTestAlignSystem(this) \
+astINVOKE(V,astTestAlignSystem_(astCheckFrame(this),STATUS_PTR))
+
+#define astClearEpoch(this) \
+astINVOKE(V,astClearEpoch_(astCheckFrame(this),STATUS_PTR))
+#define astGetEpoch(this) \
+astINVOKE(V,astGetEpoch_(astCheckFrame(this),STATUS_PTR))
+#define astSetEpoch(this,value) \
+astINVOKE(V,astSetEpoch_(astCheckFrame(this),value,STATUS_PTR))
+#define astTestEpoch(this) \
+astINVOKE(V,astTestEpoch_(astCheckFrame(this),STATUS_PTR))
+
+#define astGetObsLon(this) \
+astINVOKE(V,astGetObsLon_(astCheckFrame(this),STATUS_PTR))
+#define astTestObsLon(this) \
+astINVOKE(V,astTestObsLon_(astCheckFrame(this),STATUS_PTR))
+#define astClearObsLon(this) \
+astINVOKE(V,astClearObsLon_(astCheckFrame(this),STATUS_PTR))
+#define astSetObsLon(this,value) \
+astINVOKE(V,astSetObsLon_(astCheckFrame(this),value,STATUS_PTR))
+
+#define astGetObsLat(this) \
+astINVOKE(V,astGetObsLat_(astCheckFrame(this),STATUS_PTR))
+#define astTestObsLat(this) \
+astINVOKE(V,astTestObsLat_(astCheckFrame(this),STATUS_PTR))
+#define astClearObsLat(this) \
+astINVOKE(V,astClearObsLat_(astCheckFrame(this),STATUS_PTR))
+#define astSetObsLat(this,value) \
+astINVOKE(V,astSetObsLat_(astCheckFrame(this),value,STATUS_PTR))
+
+#define astGetObsAlt(this) \
+astINVOKE(V,astGetObsAlt_(astCheckFrame(this),STATUS_PTR))
+#define astTestObsAlt(this) \
+astINVOKE(V,astTestObsAlt_(astCheckFrame(this),STATUS_PTR))
+#define astClearObsAlt(this) \
+astINVOKE(V,astClearObsAlt_(astCheckFrame(this),STATUS_PTR))
+#define astSetObsAlt(this,value) \
+astINVOKE(V,astSetObsAlt_(astCheckFrame(this),value,STATUS_PTR))
+
+#define astClearDut1(this) \
+astINVOKE(V,astClearDut1_(astCheckFrame(this),STATUS_PTR))
+#define astGetDut1(this) \
+astINVOKE(V,astGetDut1_(astCheckFrame(this),STATUS_PTR))
+#define astSetDut1(this,value) \
+astINVOKE(V,astSetDut1_(astCheckFrame(this),value,STATUS_PTR))
+#define astTestDut1(this) \
+astINVOKE(V,astTestDut1_(astCheckFrame(this),STATUS_PTR))
+
+#define astTestActiveUnit(this) \
+astINVOKE(V,astTestActiveUnit_(astCheckFrame(this),STATUS_PTR))
+
+#define astSetFrameFlags(this,flags) \
+astINVOKE(V,astSetFrameFlags_(astCheckFrame(this),flags,STATUS_PTR))
+#define astGetFrameFlags(this) \
+astINVOKE(V,astGetFrameFlags_(astCheckFrame(this),STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/frameset.c b/ast/frameset.c
new file mode 100644
index 0000000..71914aa
--- /dev/null
+++ b/ast/frameset.c
@@ -0,0 +1,13096 @@
+/*
+*class++
+*  Name:
+*     FrameSet
+
+*  Purpose:
+*     Set of inter-related coordinate systems.
+
+*  Constructor Function:
+c     astFrameSet
+f     AST_FRAMESET
+
+*  Description:
+*     A FrameSet consists of a set of one or more Frames (which
+*     describe coordinate systems), connected together by Mappings
+*     (which describe how the coordinate systems are inter-related). A
+*     FrameSet makes it possible to obtain a Mapping between any pair
+*     of these Frames (i.e. to convert between any of the coordinate
+*     systems which it describes).  The individual Frames are
+*     identified within the FrameSet by an integer index, with Frames
+*     being numbered consecutively from one as they are added to the
+*     FrameSet.
+*
+*     Every FrameSet has a "base" Frame and a "current" Frame (which
+*     are allowed to be the same). Any of the Frames may be nominated
+*     to hold these positions, and the choice is determined by the
+*     values of the FrameSet's Base and Current attributes, which hold
+*     the indices of the relevant Frames.  By default, the first Frame
+*     added to a FrameSet is its base Frame, and the last one added is
+*     its current Frame.
+*
+*     The base Frame describes the "native" coordinate system of
+*     whatever the FrameSet is used to calibrate (e.g. the pixel
+*     coordinates of an image) and the current Frame describes the
+*     "apparent" coordinate system in which it should be viewed
+*     (e.g. displayed, etc.). Any further Frames represent a library
+*     of alternative coordinate systems, which may be selected by
+*     making them current.
+*
+*     When a FrameSet is used in a context that requires a Frame,
+*     (e.g. obtaining its Title value, or number of axes), the current
+*     Frame is used. A FrameSet may therefore be used in place of its
+*     current Frame in most situations.
+*
+*     When a FrameSet is used in a context that requires a Mapping,
+*     the Mapping used is the one between its base Frame and its
+*     current Frame. Thus, a FrameSet may be used to convert "native"
+*     coordinates into "apparent" ones, and vice versa. Like any
+c     Mapping, a FrameSet may also be inverted (see astInvert), which
+f     Mapping, a FrameSet may also be inverted (see AST_INVERT), which
+*     has the effect of interchanging its base and current Frames and
+*     hence of reversing the Mapping between them.
+*
+*     Regions may be added into a FrameSet (since a Region is a type of
+*     Frame), either explicitly or as components within CmpFrames. In this
+*     case the Mapping between a pair of Frames within a FrameSet will
+*     include the effects of the clipping produced by any Regions included
+*     in the path between the Frames.
+
+*  Inheritance:
+*     The FrameSet class inherits from the Frame class.
+
+*  Attributes:
+*     In addition to those attributes common to all Frames, every
+*     FrameSet also has the following attributes:
+*
+*     - AllVariants: List of all variant mappings stored with current Frame
+*     - Base: FrameSet base Frame index
+*     - Current: FrameSet current Frame index
+*     - Nframe: Number of Frames in a FrameSet
+*     - Variant: Name of variant mapping in use by current Frame
+
+*     Every FrameSet also inherits any further attributes that belong
+*     to its current Frame, regardless of that Frame's class. (For
+*     example, the Equinox attribute, defined by the SkyFrame class, is
+*     inherited by any FrameSet which has a SkyFrame as its current
+*     Frame.) The set of attributes belonging to a FrameSet may therefore
+*     change when a new current Frame is selected.
+
+*  Functions:
+c     In addition to those functions applicable to all Frames, the
+c     following functions may also be applied to all FrameSets:
+f     In addition to those routines applicable to all Frames, the
+f     following routines may also be applied to all FrameSets:
+*
+c     - astAddFrame: Add a Frame to a FrameSet to define a new coordinate
+c     system
+c     - astAddVariant: Add a variant Mapping to the current Frame
+c     - astGetFrame: Obtain a pointer to a specified Frame in a FrameSet
+c     - astGetMapping: Obtain a Mapping between two Frames in a FrameSet
+c     - astMirrorVariants: Make the current Frame mirror variant Mappings in another Frame
+c     - astRemapFrame: Modify a Frame's relationship to the other Frames in a
+c     FrameSet
+c     - astRemoveFrame: Remove a Frame from a FrameSet
+f     - AST_ADDFRAME: Add a Frame to a FrameSet to define a new coordinate
+f     system
+f     - AST_ADDVARIANT: Add a variant Mapping to the current Frame
+f     - AST_GETFRAME: Obtain a pointer to a specified Frame in a FrameSet
+f     - AST_GETMAPPING: Obtain a Mapping between two Frames in a FrameSet
+f     - AST_MIRRORVARIANTS: Make the current Frame mirror variant Mappings in another Frame
+f     - AST_REMAPFRAME: Modify a Frame's relationship to the other Frames in a
+f     FrameSet
+f     - AST_REMOVEFRAME: Remove a Frame from a FrameSet
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     16-FEB-1996 (RFWS):
+*        Original version.
+*     5-JUN-1996 (RFWS):
+*        Tidied up, etc.
+*     2-JUL-1996 (RFWS):
+*        Fixed bug in astRemoveFrame which caused the base/current
+*        Frame index to be wrong.
+*     12-JUL-1996 (RFWS):
+*        Over-ride the astReportPoints method to provide
+*        Frame-specific formatting.
+*     12-AUG-1996 (RFWS):
+*        Upgraded to provide a public interface, plus improvements to
+*        astAlign and the handling of nodes as Frames are
+*        added/removed.
+*     11-SEP-1996 (RFWS):
+*        Added Gap.
+*     25-SEP-1996 (RFWS):
+*        Added I/O facilities.
+*     30-MAY-1997 (RFWS):
+*        Add special treatment for the ID attribute (which is not
+*        derived from the current Frame).
+*     10-JUN-1997 (RFWS):
+*        Rationalised the astConvert implementation.
+*     11-JUN-1997 (RFWS):
+*        Added the FindFrame implementation.
+*     27-JUN-1997 (RFWS):
+*        Fixed bug which caused certain Mapping attributes to be
+*        handled by the current Frame instead of by the member
+*        functions defined by this class.
+*     3-JUL-1997 (RFWS):
+*        Fixed bug: failing to extend the invert array in
+*        astRemapFrame.
+*     10-JUL-1997 (RFWS):
+*        Over-ride the astSimplify method.
+*     14-NOV-1997 (RFWS):
+*        Fixed error in loop implementing search over domains in
+*        FindFrame.
+*     20-NOV-1997 (RFWS):
+*        Fixed bug in default Base and Current attribute values when a
+*        FrameSet has been inverted.
+*     20-NOV-1997 (RFWS):
+*        Modified astConvert to use the current Frame of the "to"
+*        FrameSet as the destination coordinate system (instead of the
+*        base Frame) and to modify its Base attribute instead of its
+*        Current attribute.
+*     22-DEC-1997 (RFWS):
+*        Further modified astConvert to convert from the Current Frame
+*        of the "from" FrameSet and to modify its Base
+*        attribute. Frame search order also reversed if the Invert
+*        attribute is non-zero for either FrameSet.
+*     19-JAN-1998 (RFWS):
+*        Installed the TidyNodes function.
+*     20-JAN-1998 (RFWS):
+*        Implemented preservation of FrameSet integrity when attribute
+*        values associated with the current Frame are modified.
+*     24-FEB-1998 (RFWS):
+*        Added the ForceCopy function to allow integrity to be preserved
+*        when there are multiple references to the same Frame.
+*     25-FEB-1998 (RFWS):
+*        Over-ride the astUnformat method.
+*     24-MAR-1998 (RFWS):
+*        Fixed unterminated comment causing problems in CombineMaps.
+*     6-APR-1998 (RFWS):
+*        Fixed another unterminated comment in CombineMaps.
+*     27-MAY-1998 (RFWS):
+*        Fixed bug: failure to record new invert flag value after
+*        simplifying a CmpMap in TidyNodes.
+*     17-DEC-2002 (DSB):
+*        Override accessors for Frame attributes Top, Bottom, Epoch,
+*        System, AlignSystem and ActiveUnit.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitFrameSetVtab
+*        method.
+*     24-JAN-2004 (DSB):
+*        o  Override the astFields method.
+*        o  Add argument "fmt" to Abbrev.
+*     23-MAR-2004 (DSB):
+*        Modified astGetMapping and Span to include the clipping effect of
+*        any Regions in the path between the two supplied Frames.
+*     24-AUG-2004 (DSB):
+*        - Override various methods inherited from Frame (astAngle,
+*	astAxAngle, astAxDistance, astAxOffset, astCheckPerm, astOffset2,
+*	astResolve, astSystemCode, astSystemString, astValidateSystem,
+*	astValidateAxisSelection). These should have been overridden a
+*       long time ago!
+*     8-SEP-2004 (DSB):
+*       Override astResolvePoints.
+*     12-MAY-2005 (DSB):
+*        Override astNormBox method.
+*     12-AUG-2005 (DSB):
+*        Override ObsLat and ObsLon accessor methods.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     15-MAY-2006 (DSB):
+*        Override astEqual.
+*     30-JUN-2006 (DSB):
+*        Allow astAbbrev to have a null "str1" value.
+*     22-JUN-2007 (DSB):
+*        Modify VSet to avoid using the args va_list twice since the
+*        first use (by the parent VSet function) invalidates the va_list
+*        causing a segvio to be generated by the second use (when
+*        formatting an error message).
+*     11-JAN-2008 (DSB):
+*        Override the astRate method.
+*     17-NOV-2008 (DSB):
+*        Correct parent class in invocation of astMAKE_ISA.
+*     14-JAN-2009 (DSB):
+*        Override the astIntersect method.
+*     18-JUN-2009 (DSB):
+*        Override ObsAlt accessor methods.
+*     30-OCT-2009 (DSB):
+*        Make the Ident attribute relate to the FrameSet, not the current
+*        Frame.
+*     22-MAR-2011 (DSB):
+*        Override astFrameGrid method.
+*     29-APR-2011 (DSB):
+*        Prevent astFindFrame from matching a subclass template against a
+*        superclass target.
+*     2-SEP-2011 (DSB):
+*        Fix FrameSet implememntation of astEqual (mapping comparison
+*        tests were logically inverted).
+*     3-OCT-2012 (DSB):
+*        Fix bug in AppendAxes that could cause internal Mappings to
+*        be inverted unintentionally when astAddFrame is called with
+*        iframe=AST__ALLFRAMES.
+*     29-APR-2013 (DSB):
+*        Added attributes AllVariants and Variant. Also added methods
+*        astAddVariant and astMirrorVariants.
+*     25-SEP-2014 (DSB):
+*        Allow Base and Current attributes to be specified by giving a
+*        Domain name.
+*     17-APR-2015 (DSB):
+*        Added Centre.
+*     28-APR-2015 (DSB):
+*        astAdFrame now takes deep copies of the supplied mapping and
+*        frame, rather than just cloning their pointers. So the modified
+*        FrameSet is now independent of the supplied Mapping and Frame
+*        objects.
+*     26-OCT-2016 (DSB):
+*        Override the AxNorm method.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS FrameSet
+
+#define GETALLVARIANTS_BUFF_LEN 200
+
+/*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Define a function to clear an attribute value for a FrameSet.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     MAKE_CLEAR(attribute)
+
+*  Class Membership:
+*     Defined by the FrameSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Clear<Attribute>( AstFrame *this )
+*
+*     that clears the value of a specified attribute for the current Frame
+*     of a FrameSet (this). This function is intended to over-ride the
+*     astClear<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attribute) \
+static void Clear##attribute( AstFrame *this_frame, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *fr;                 /* Pointer to current Frame */ \
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the FrameSet structure. */ \
+   this = (AstFrameSet *) this_frame; \
+\
+/* Obtain a pointer to the current Frame and invoke its astClear<Attribute> \
+   method. Annul the Frame pointer afterwards. */ \
+   fr = astGetFrame( this, AST__CURRENT ); \
+   astClear##attribute( fr ); \
+   fr = astAnnul( fr ); \
+}
+
+/*
+*  Name:
+*     MAKE_CLEAR_AXIS
+
+*  Purpose:
+*     Define a function to clear an attribute value for a FrameSet axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     MAKE_CLEAR_AXIS(attribute)
+
+*  Class Membership:
+*     Defined by the FrameSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Clear<Attribute>( AstFrame *this, int axis )
+*
+*     that clears the value of a specified attribute for an axis of
+*     the current Frame of a FrameSet (this). This function is
+*     intended to over-ride the astClear<Attribute> method inherited
+*     from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR_AXIS(attribute) \
+static void Clear##attribute( AstFrame *this_frame, int axis, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *fr;                 /* Pointer to current Frame */ \
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the FrameSet structure. */ \
+   this = (AstFrameSet *) this_frame; \
+\
+/* Validate the axis index supplied. */ \
+   (void) astValidateAxis( this, axis, 1, "astClear" #attribute ); \
+\
+/* Obtain a pointer to the FrameSet's current Frame and invoke its \
+   astClear<Attribute> method. Annul the Frame pointer afterwards. */ \
+   fr = astGetFrame( this, AST__CURRENT ); \
+   astClear##attribute( fr, axis ); \
+   fr = astAnnul( fr ); \
+}
+
+/*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Define a function to get an attribute value for a FrameSet.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     MAKE_GET(attribute,type)
+
+*  Class Membership:
+*     Defined by the FrameSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static <type> Get<Attribute>( AstFrame *this )
+*
+*     that gets the value of a specified attribute for the current Frame
+*     of a FrameSet (this). This function is intended to over-ride the
+*     astGet<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     type
+*        The C type of the attribute.
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attribute,type) \
+static type Get##attribute( AstFrame *this_frame, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *fr;                 /* Pointer to current Frame */ \
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */ \
+   type result;                  /* Value to return */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (type) 0; \
+\
+/* Obtain a pointer to the FrameSet structure. */ \
+   this = (AstFrameSet *) this_frame; \
+\
+/* Obtain a pointer to the current Frame and invoke its \
+   astGet<Attribute> method.  Annul the Frame pointer afterwards. */ \
+   fr = astGetFrame( this, AST__CURRENT ); \
+   result = astGet##attribute( fr ); \
+   fr = astAnnul( fr ); \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = (type) 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/*
+*  Name:
+*     MAKE_GET_AXIS
+
+*  Purpose:
+*     Define a function to get an attribute value for a FrameSet axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     MAKE_GET_AXIS(attribute,type)
+
+*  Class Membership:
+*     Defined by the FrameSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static <type> Get<Attribute>( AstFrame *this, int axis )
+*
+*     that gets the value of a specified attribute for an axis of the
+*     current Frame of a FrameSet (this). This function is intended to
+*     over-ride the astGet<Attribute> method inherited from the Frame
+*     class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     type
+*        The C type of the attribute.
+*/
+
+/* Define the macro. */
+#define MAKE_GET_AXIS(attribute,type) \
+static type Get##attribute( AstFrame *this_frame, int axis, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *fr;                 /* Pointer to current Frame */ \
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */ \
+   type result;                  /* Value to return */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (type) 0; \
+\
+/* Obtain a pointer to the FrameSet structure. */ \
+   this = (AstFrameSet *) this_frame; \
+\
+/* Validate the axis index supplied. */ \
+   (void) astValidateAxis( this, axis, 1, "astGet" #attribute ); \
+\
+/* Obtain a pointer to the FrameSet's current Frame and invoke its \
+   astGet<Attribute> method.  Annul the Frame pointer afterwards. */ \
+   fr = astGetFrame( this, AST__CURRENT ); \
+   result = astGet##attribute( fr, axis ); \
+   fr = astAnnul( fr ); \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = (type) 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Define a function to set an attribute value for a FrameSet.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     MAKE_SET(attribute,type)
+
+*  Class Membership:
+*     Defined by the FrameSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Set<Attribute>( AstFrame *this, <type> value )
+*
+*     that sets the value of a specified attribute for the current Frame
+*     of a FrameSet (this). This function is intended to over-ride the
+*     astSet<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     type
+*        The C type of the attribute.
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attribute,type) \
+static void Set##attribute( AstFrame *this_frame, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *fr;                 /* Pointer to current Frame */ \
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the FrameSet structure. */ \
+   this = (AstFrameSet *) this_frame; \
+\
+/* Obtain a pointer to the FrameSet's current Frame and invoke its \
+   astSet<Attribute> method.  Annul the Frame pointer afterwards. */ \
+   fr = astGetFrame( this, AST__CURRENT ); \
+   astSet##attribute( fr, value ); \
+   fr = astAnnul( fr ); \
+}
+
+/*
+*  Name:
+*     MAKE_SET_AXIS
+
+*  Purpose:
+*     Define a function to set an attribute value for a FrameSet axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     MAKE_SET_AXIS(attribute,type)
+
+*  Class Membership:
+*     Defined by the FrameSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Set<Attribute>( AstFrame *this, int axis, <type> value )
+*
+*     that sets the value of a specified attribute for an axis of the
+*     current Frame of a FrameSet (this). This function is intended to
+*     over-ride the astSet<Attribute> method inherited from the Frame
+*     class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     type
+*        The C type of the attribute.
+*/
+
+/* Define the macro. */
+#define MAKE_SET_AXIS(attribute,type) \
+static void Set##attribute( AstFrame *this_frame, int axis, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *fr;                 /* Pointer to current Frame */ \
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the FrameSet structure. */ \
+   this = (AstFrameSet *) this_frame; \
+\
+/* Validate the axis index supplied. */ \
+   (void) astValidateAxis( this, axis, 1, "astSet" #attribute ); \
+\
+/* Obtain a pointer to the FrameSet's current Frame and invoke its \
+   astSet<Attribute> method.  Annul the Frame pointer afterwards. */ \
+   fr = astGetFrame( this, AST__CURRENT ); \
+   astSet##attribute( fr, axis, value ); \
+   fr = astAnnul( fr ); \
+}
+
+/*
+*  Name:
+*     MAKE_TEST
+
+*  Purpose:
+*     Define a function to test if an attribute value is set for a FrameSet.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     MAKE_TEST(attribute)
+
+*  Class Membership:
+*     Defined by the FrameSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static int Test<Attribute>( AstFrame *this )
+*
+*     that returns a boolean result (0 or 1) to indicate if the value
+*     of a specified attribute for the current Frame of a FrameSet
+*     (this) is set. This function is intended to over-ride the
+*     astTest<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*/
+
+/* Define the macro. */
+#define MAKE_TEST(attribute) \
+static int Test##attribute( AstFrame *this_frame, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *fr;                 /* Pointer to current Frame */ \
+   AstFrameSet *this;            /* Pointer to FrameSet structure */ \
+   int result;                   /* Result to return */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Obtain a pointer to the FrameSet structure. */ \
+   this = (AstFrameSet *) this_frame; \
+\
+/* Obtain a pointer to the FrameSet's current Frame and invoke its \
+   astTest<Attribute> method.  Annul the Frame pointer afterwards. */ \
+   fr = astGetFrame( this, AST__CURRENT ); \
+   result = astTest##attribute( fr ); \
+   fr = astAnnul( fr ); \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/*
+*  Name:
+*     MAKE_TEST_AXIS
+
+*  Purpose:
+*     Define a function to test if an attribute value is set for a FrameSet
+*     axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     MAKE_TEST_AXIS(attribute)
+
+*  Class Membership:
+*     Defined by the FrameSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static int Test<Attribute>( AstFrame *this, int axis )
+*
+*     that returns a boolean result (0 or 1) to indicate if the value
+*     of a specified attribute for an axis of the current Frame of a
+*     FrameSet (this) is set. This function is intended to over-ride
+*     the astTest<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*/
+
+/* Define the macro. */
+#define MAKE_TEST_AXIS(attribute) \
+static int Test##attribute( AstFrame *this_frame, int axis, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *fr;                 /* Pointer to current Frame */ \
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */ \
+   int result;                   /* Value to return */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Obtain a pointer to the FrameSet structure. */ \
+   this = (AstFrameSet *) this_frame; \
+\
+/* Validate the axis index supplied. */ \
+   (void) astValidateAxis( this, axis, 1, "astTest" #attribute ); \
+\
+/* Obtain a pointer to the FrameSet's current Frame and invoke its \
+   astTest<Attribute> method.  Annul the Frame pointer afterwards. */ \
+   fr = astGetFrame( this, AST__CURRENT ); \
+   result = astTest##attribute( fr, axis ); \
+   fr = astAnnul( fr ); \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "mapping.h"             /* Coordinate Mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "permmap.h"             /* Coordinate permutation Mappings */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "frame.h"               /* Parent Frame class */
+#include "frameset.h"            /* Interface definition for this class */
+#include "cmpframe.h"            /* Compound coordinate frames */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static void (* parent_clear)( AstObject *, const char *, int * );
+static int (* parent_getusedefs)( AstObject *, int * );
+static void (* parent_vset)( AstObject *, const char *, char **, va_list, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->Integrity_Frame = NULL; \
+   globals->Integrity_Method = ""; \
+   globals->Integrity_Lost = 0; \
+   globals->GetAllVariants_Buff[ 0 ] = 0; \
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(FrameSet)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(FrameSet,Class_Init)
+#define class_vtab astGLOBAL(FrameSet,Class_Vtab)
+#define getattrib_buff astGLOBAL(FrameSet,GetAttrib_Buff)
+#define integrity_frame astGLOBAL(FrameSet,Integrity_Frame)
+#define integrity_method astGLOBAL(FrameSet,Integrity_Method)
+#define integrity_lost astGLOBAL(FrameSet,Integrity_Lost)
+#define getallvariants_buff astGLOBAL(FrameSet,GetAllVariants_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Buffer returned by GetAttrib. */
+static char getattrib_buff[ AST__FRAMESET_GETATTRIB_BUFF_LEN + 1 ];
+
+/* Variables associated with preserving FrameSet integrity. */
+static AstFrame *integrity_frame = NULL; /* Pointer to copy of current Frame */
+static const char *integrity_method = ""; /* Name of method being used */
+static int integrity_lost = 0;   /* Current Frame modified? */
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstFrameSetVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+/* String buffers. */
+static char getallvariants_buff[ AST__FRAMESET_GETALLVARIANTS_BUFF_LEN + 1 ];
+
+#endif
+
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstAxis *GetAxis( AstFrame *, int, int * );
+static AstFrame *GetFrame( AstFrameSet *, int, int * );
+static AstFrame *PickAxes( AstFrame *, int, const int[], AstMapping **, int * );
+static AstFrameSet *Convert( AstFrame *, AstFrame *, const char *, int * );
+static AstFrameSet *ConvertX( AstFrame *, AstFrame *, const char *, int * );
+static AstFrameSet *FindFrame( AstFrame *, AstFrame *, const char *, int * );
+static AstLineDef *LineDef( AstFrame *, const double[2], const double[2], int * );
+static AstMapping *CombineMaps( AstMapping *, int, AstMapping *, int, int, int * );
+static AstMapping *GetMapping( AstFrameSet *, int, int, int * );
+static AstMapping *RemoveRegions( AstMapping *, int * );
+static AstMapping *Simplify( AstMapping *, int * );
+static AstObject *Cast( AstObject *, AstObject *, int * );
+static AstPointSet *FrameGrid( AstFrame *, int, const double *, const double *, int * );
+static AstPointSet *ResolvePoints( AstFrame *, const double [], const double [], AstPointSet *, AstPointSet *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstSystemType SystemCode( AstFrame *, const char *, int * );
+static AstSystemType ValidateSystem( AstFrame *, AstSystemType, const char *, int * );
+static const char *Abbrev( AstFrame *, int, const char *, const char *, const char *, int * );
+static const char *Format( AstFrame *, int, double, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static const char *GetDomain( AstFrame *, int * );
+static const char *GetFormat( AstFrame *, int, int * );
+static const char *GetLabel( AstFrame *, int, int * );
+static const char *GetSymbol( AstFrame *, int, int * );
+static const char *GetTitle( AstFrame *, int * );
+static const char *GetUnit( AstFrame *, int, int * );
+static const char *GetAllVariants( AstFrameSet *, int * );
+static const char *SystemString( AstFrame *, AstSystemType, int * );
+static const int *GetPerm( AstFrame *, int * );
+static double Angle( AstFrame *, const double[], const double[], const double[], int * );
+static double AxAngle( AstFrame *, const double[], const double[], int, int * );
+static double AxDistance( AstFrame *, int, double, double, int * );
+static double AxOffset( AstFrame *, int, double, double, int * );
+static double Distance( AstFrame *, const double[], const double[], int * );
+static double Centre( AstFrame *, int, double, double, int * );
+static double Gap( AstFrame *, int, double, int *, int * );
+static double Offset2( AstFrame *, const double[2], double, double, double[2], int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int Fields( AstFrame *, int, const char *, const char *, int, char **, int *, double *, int * );
+static int ForceCopy( AstFrameSet *, int, int * );
+static int GetActiveUnit( AstFrame *, int * );
+static int GetBase( AstFrameSet *, int * );
+static int GetCurrent( AstFrameSet *, int * );
+static int GetDigits( AstFrame *, int * );
+static int GetDirection( AstFrame *, int, int * );
+static int GetIsLinear( AstMapping *, int * );
+static int GetMatchEnd( AstFrame *, int * );
+static int GetMaxAxes( AstFrame *, int * );
+static int GetMinAxes( AstFrame *, int * );
+static int GetNaxes( AstFrame *, int * );
+static int GetNframe( AstFrameSet *, int * );
+static int GetNin( AstMapping *, int * );
+static int GetNout( AstMapping *, int * );
+static int GetObjSize( AstObject *, int * );
+static int GetPermute( AstFrame *, int * );
+static int GetPreserveAxes( AstFrame *, int * );
+static int GetTranForward( AstMapping *, int * );
+static int GetTranInverse( AstMapping *, int * );
+static int GetVarFrm( AstFrameSet *, int, int * );
+static int IsUnitFrame( AstFrame *, int * );
+static int LineContains( AstFrame *, AstLineDef *, int, double *, int * );
+static int LineCrossing( AstFrame *, AstLineDef *, AstLineDef *, double **, int * );
+static int Match( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int Span( AstFrameSet *, AstFrame **, int, int, int, AstMapping **, int *, int * );
+static int SubFrame( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int TestActiveUnit( AstFrame *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestBase( AstFrameSet *, int * );
+static int TestCurrent( AstFrameSet *, int * );
+static int TestDigits( AstFrame *, int * );
+static int TestDirection( AstFrame *, int, int * );
+static int TestDomain( AstFrame *, int * );
+static int TestFormat( AstFrame *, int, int * );
+static int TestLabel( AstFrame *, int, int * );
+static int TestMatchEnd( AstFrame *, int * );
+static int TestMaxAxes( AstFrame *, int * );
+static int TestMinAxes( AstFrame *, int * );
+static int TestPermute( AstFrame *, int * );
+static int TestPreserveAxes( AstFrame *, int * );
+static int TestSymbol( AstFrame *, int, int * );
+static int TestTitle( AstFrame *, int * );
+static int TestUnit( AstFrame *, int, int * );
+static int Unformat( AstFrame *, int, const char *, double *, int * );
+static int ValidateAxis( AstFrame *, int, int, const char *, int * );
+static int ValidateFrameIndex( AstFrameSet *, int, const char *, int * );
+static void AddFrame( AstFrameSet *, int, AstMapping *, AstFrame *, int * );
+static void AppendAxes( AstFrameSet *, AstFrame *, int * );
+static void AxNorm( AstFrame *, int, int, int, double *, int * );
+static void CheckPerm( AstFrame *, const int *, const char *, int * );
+static void Clear( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearBase( AstFrameSet *, int * );
+static void ClearCurrent( AstFrameSet *, int * );
+static void ClearDigits( AstFrame *, int * );
+static void ClearDirection( AstFrame *, int, int * );
+static void ClearDomain( AstFrame *, int * );
+static void ClearFormat( AstFrame *, int, int * );
+static void ClearLabel( AstFrame *, int, int * );
+static void ClearMatchEnd( AstFrame *, int * );
+static void ClearMaxAxes( AstFrame *, int * );
+static void ClearMinAxes( AstFrame *, int * );
+static void ClearPermute( AstFrame *, int * );
+static void ClearPreserveAxes( AstFrame *, int * );
+static void ClearSymbol( AstFrame *, int, int * );
+static void ClearTitle( AstFrame *, int * );
+static void ClearUnit( AstFrame *, int, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void Intersect( AstFrame *, const double[2], const double[2], const double[2], const double[2], double[2], int * );
+static void LineOffset( AstFrame *, AstLineDef *, double, double, double[2], int * );
+static void MatchAxes( AstFrame *, AstFrame *, int *, int * );
+static void MatchAxesX( AstFrame *, AstFrame *, int *, int * );
+static void Norm( AstFrame *, double[], int * );
+static void NormBox( AstFrame *, double[], double[], AstMapping *, int * );
+static void Offset( AstFrame *, const double[], const double[], double, double[], int * );
+static void Overlay( AstFrame *, const int *, AstFrame *, int * );
+static void PermAxes( AstFrame *, const int[], int * );
+static void PrimaryFrame( AstFrame *, int, AstFrame **, int *, int * );
+static void RecordIntegrity( AstFrameSet *, int * );
+static void AddVariant( AstFrameSet *, AstMapping *, const char *, int * );
+static void MirrorVariants( AstFrameSet *, int, int * );
+static void RemapFrame( AstFrameSet *, int, AstMapping *, int * );
+static void RemoveFrame( AstFrameSet *, int, int * );
+static void RemoveMirrors( AstFrameSet *, int, int * );
+static void ReportPoints( AstMapping *, int, AstPointSet *, AstPointSet *, int * );
+static void Resolve( AstFrame *, const double [], const double [], const double [], double [], double *, double *, int * );
+static void RestoreIntegrity( AstFrameSet *, int * );
+static void SetActiveUnit( AstFrame *, int, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetAxis( AstFrame *, int, AstAxis *, int * );
+static void SetBase( AstFrameSet *, int, int * );
+static void SetCurrent( AstFrameSet *, int, int * );
+static void SetDigits( AstFrame *, int, int * );
+static void SetDirection( AstFrame *, int, int, int * );
+static void SetDomain( AstFrame *, const char *, int * );
+static void SetFormat( AstFrame *, int, const char *, int * );
+static void SetLabel( AstFrame *, int, const char *, int * );
+static void SetMatchEnd( AstFrame *, int, int * );
+static void SetMaxAxes( AstFrame *, int, int * );
+static void SetMinAxes( AstFrame *, int, int * );
+static void SetPermute( AstFrame *, int, int * );
+static void SetPreserveAxes( AstFrame *, int, int * );
+static void SetSymbol( AstFrame *, int, const char *, int * );
+static void SetTitle( AstFrame *, const char *, int * );
+static void SetUnit( AstFrame *, int, const char *, int * );
+static void TidyNodes( AstFrameSet *, int * );
+static void VSet( AstObject *, const char *, char **, va_list, int * );
+static void ValidateAxisSelection( AstFrame *, int, const int *, const char *, int * );
+
+static double GetBottom( AstFrame *, int, int * );
+static int TestBottom( AstFrame *, int, int * );
+static void ClearBottom( AstFrame *, int, int * );
+static void SetBottom( AstFrame *, int, double, int * );
+
+static double GetTop( AstFrame *, int, int * );
+static int TestTop( AstFrame *, int, int * );
+static void ClearTop( AstFrame *, int, int * );
+static void SetTop( AstFrame *, int, double, int * );
+
+static double GetEpoch( AstFrame *, int * );
+static int TestEpoch( AstFrame *, int * );
+static void ClearEpoch( AstFrame *, int * );
+static void SetEpoch( AstFrame *, double, int * );
+
+static double GetObsAlt( AstFrame *, int * );
+static int TestObsAlt( AstFrame *, int * );
+static void ClearObsAlt( AstFrame *, int * );
+static void SetObsAlt( AstFrame *, double, int * );
+
+static double GetObsLat( AstFrame *, int * );
+static int TestObsLat( AstFrame *, int * );
+static void ClearObsLat( AstFrame *, int * );
+static void SetObsLat( AstFrame *, double, int * );
+
+static double GetObsLon( AstFrame *, int * );
+static int TestObsLon( AstFrame *, int * );
+static void ClearObsLon( AstFrame *, int * );
+static void SetObsLon( AstFrame *, double, int * );
+
+static int GetUseDefs( AstObject *, int * );
+
+static AstSystemType GetSystem( AstFrame *, int * );
+static int TestSystem( AstFrame *, int * );
+static void ClearSystem( AstFrame *, int * );
+static void SetSystem( AstFrame *, AstSystemType, int * );
+
+static AstSystemType GetAlignSystem( AstFrame *, int * );
+static int TestAlignSystem( AstFrame *, int * );
+static void ClearAlignSystem( AstFrame *, int * );
+static void SetAlignSystem( AstFrame *, AstSystemType, int * );
+
+static const char *GetVariant( AstFrameSet *, int * );
+static int TestVariant( AstFrameSet *, int * );
+static void ClearVariant( AstFrameSet *, int * );
+static void SetVariant( AstFrameSet *, const char *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+static const char *Abbrev( AstFrame *this_frame, int axis, const char *fmt,
+                           const char *str1, const char *str2, int *status ) {
+/*
+*  Name:
+*     Abbrev
+
+*  Purpose:
+*     Abbreviate a formatted FrameSet axis value by skipping leading fields.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     const char *Abbrev( AstFrame *this, int axis, const char *fmt,
+*                         const char *str1, const char *str2, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astAbbrev
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function compares two FrameSet axis values that have been
+*     formatted (using astFormat) and determines if they have any
+*     redundant leading fields (i.e. leading fields in common which
+*     can be suppressed when tabulating the values or plotting them on
+*     the axis of a graph).
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     axis
+*        The number of the FrameSet axis for which the values have
+*        been formatted (axis numbering starts at zero for the first
+*        axis).
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format specification used to format the two values.
+*     str1
+*        Pointer to a constant null-terminated string containing the
+*        first formatted value. If this is null, the returned pointer
+*        points to the start of the final field in str2.
+*     str2
+*        Pointer to a constant null-terminated string containing the
+*        second formatted value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer into the "str2" string which locates the first
+*     character in the first field that differs between the two
+*     formatted values.
+*
+*     If the two values have no leading fields in common, the returned
+*     value will point at the start of string "str2". If the two
+*     values are equal, it will point at the terminating null at the
+*     end of this string.
+
+*  Notes:
+*     - This function assumes that the format specification used was
+*     the same when both values were formatted and that they both
+*     apply to the same FrameSet axis.
+*     - A pointer to the start of "str2" will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   const char *result;           /* Pointer value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return str2;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astAbbrev" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astAbbrev method to perform the processing. Annul the Frame
+   pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astAbbrev( fr, axis, fmt, str1, str2 );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = str2;
+
+/* Return the result. */
+   return result;
+}
+
+static void AddFrame( AstFrameSet *this, int iframe, AstMapping *map,
+                      AstFrame *frame, int *status ) {
+/*
+*++
+*  Name:
+c     astAddFrame
+f     AST_ADDFRAME
+
+*  Purpose:
+*     Add a Frame to a FrameSet to define a new coordinate system.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     void astAddFrame( AstFrameSet *this, int iframe, AstMapping *map,
+c                       AstFrame *frame )
+f     CALL AST_ADDFRAME( THIS, IFRAME, MAP, FRAME, STATUS )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+c     This function adds a new Frame and an associated Mapping to a
+f     This routine adds a new Frame and an associated Mapping to a
+*     FrameSet so as to define a new coordinate system, derived from
+*     one which already exists within the FrameSet. The new Frame then
+*     becomes the FrameSet's current Frame.
+*
+c     This function
+f     This routine
+*     may also be used to merge two FrameSets, or to append extra axes
+*     to every Frame in a FrameSet.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FrameSet.
+c     iframe
+f     IFRAME = INTEGER (Given)
+*        The index of the Frame within the FrameSet which describes
+*        the coordinate system upon which the new one is to be based.
+*        This value should lie in the range from 1 to the number of
+*        Frames already in the FrameSet (as given by its Nframe
+*        attribute). As a special case, AST__ALLFRAMES may be supplied,
+*        in which case the axes defined by the supplied Frame are appended
+*        to every Frame in the FrameSet (see the Notes section for details).
+c     map
+f     MAP = INTEGER (Given)
+*        Pointer to a Mapping which describes how to convert
+*        coordinates from the old coordinate system (described by the
+c        Frame with index "iframe") into coordinates in the new
+f        Frame with index IFRAME) into coordinates in the new
+*        system. The Mapping's forward transformation should perform
+*        this conversion, and its inverse transformation should
+*        convert in the opposite direction. The supplied Mapping is ignored
+c        if parameter "iframe"is equal to AST__ALLFRAMES.
+f        if parameter IFRAME is equal to AST__ALLFRAMES.
+c     frame
+f     FRAME = INTEGER (Given)
+*        Pointer to a Frame that describes the new coordinate system.
+*        Any class of Frame may be supplied (including Regions and
+*        FrameSets).
+*
+c        This function may also be used to merge two FrameSets by
+c        supplying a pointer to a second FrameSet for this parameter
+f        This routine may also be used to merge two FrameSets by
+f        supplying a pointer to a second FrameSet for this argument
+*        (see the Notes section for details).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Deep copies of the supplied
+c     "mapping" and "frame"
+f     MAPPING and FRAME
+*     objects are stored within the modified FrameSet. So any changes made
+*     to the FrameSet after calling this method will have no effect on the
+*     supplied Mapping and Frame objects.
+*     - A value of AST__BASE or AST__CURRENT may be given for the
+c     "iframe" parameter to specify the base Frame or the current
+f     IFRAME argument to specify the base Frame or the current
+*     Frame respectively.
+c     - This function sets the value of the Current attribute for the
+f     - This routine sets the value of the Current attribute for the
+*     FrameSet so that the new Frame subsequently becomes the current
+*     Frame.
+*     - The number of input coordinate values accepted by the supplied
+*     Mapping (its Nin attribute) must match the number of axes in the
+c     Frame identified by the "iframe" parameter. Similarly, the
+f     Frame identified by the IFRAME argument. Similarly, the
+*     number of output coordinate values generated by this Mapping
+*     (its Nout attribute) must match the number of axes in the new
+*     Frame.
+*     - As a special case, if a pointer to a FrameSet is given for the
+c     "frame" parameter, this is treated as a request to merge a pair of
+f     FRAME argument, this is treated as a request to merge a pair of
+*     FrameSets.  This is done by appending all the new Frames (in the
+c     "frame" FrameSet) to the original FrameSet, while preserving
+f     FRAME FrameSet) to the original FrameSet, while preserving
+*     their order and retaining all the inter-relationships
+*     (i.e. Mappings) between them. The two sets of Frames are
+*     inter-related within the merged FrameSet by using the Mapping
+*     supplied. This should convert between the Frame identified by
+c     the "iframe" parameter (in the original FrameSet) and the current
+c     Frame of the "frame" FrameSet. This latter Frame becomes the
+f     the IFRAME argument (in the original FrameSet) and the current
+f     Frame of the FRAME FrameSet. This latter Frame becomes the
+*     current Frame in the merged FrameSet.
+*     - As another special case, if a value of AST__ALLFRAMES is supplied
+*     for parameter
+c     "iframe",
+f     IFRAME,
+*     then the supplied Mapping is ignored, and the axes defined by the
+*     supplied Frame are appended to each Frame in the FrameSet. In detail,
+*     each Frame in the FrameSet is replaced by a CmpFrame containing the
+*     original Frame and the Frame specified by parameter
+c     "frame".
+f     FRAME.
+*     In addition, each Mapping in the FrameSet is replaced by a CmpMap
+*     containing the original Mapping and a UnitMap in parallel. The Nin and
+*     Nout attributes of the UnitMap are set equal to the number of axes
+*     in the supplied Frame. Each new CmpMap is simplified using
+c     astSimplify
+f     AST_SIMPLIFY
+*     before being stored in the FrameSet.
+
+
+
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to Frame identified by "iframe" */
+   AstFrameSet *frameset;        /* Pointer to new FrameSet (if given) */
+   AstMapping *inode_map;        /* Temporarily saved Mapping pointer */
+   AstMapping *next_map;         /* Temporarily saved Mapping pointer */
+   int current;                  /* Current Frame index in merged FrameSet */
+   int current_node;             /* Node number for current Frame */
+   int ifr;                      /* Loop counter for Frames */
+   int inode;                    /* Loop counter for nodes */
+   int inode_invert;             /* Temporarily saved invert flag value */
+   int inode_link;               /* Temporarily saved link value */
+   int naxes;                    /* Number of Frame axes */
+   int ncoord;                   /* Number of Mapping coordinates per point */
+   int next;                     /* Number of next node in path */
+   int next_invert;              /* Temporarily saved invert flag value */
+   int next_link;                /* Temporarily saved link value */
+   int nframe;                   /* Number of Frames in merged FrameSet */
+   int nnode;                    /* Number of nodes in merged FrameSet */
+   int node_zero;                /* Location of "node zero" after merging */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* First handle cases where we are appending axes to the existing
+   Frames in a FrameSet. */
+   if( iframe == AST__ALLFRAMES ) {
+      AppendAxes( this, frame, status );
+      return;
+   }
+
+/* Now handle cases where we are adding a new Frame into the FrameSet.
+   Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   inode_map = NULL;
+   next_map = NULL;
+   inode_invert = 0;
+   next = 0;
+   next_invert = 0;
+   next_link = 0;
+
+/* Validate and translate the Frame index supplied. */
+   iframe = astValidateFrameIndex( this, iframe, "astAddFrame" );
+
+/* Obtain a pointer to the Frame from which the new coordinate system
+   will be derived and determine how many axes it has. Annul the Frame
+   pointer afterwards. */
+   if ( astOK ) {
+      fr = astGetFrame( this, iframe );
+      naxes = astGetNaxes( fr );
+      fr = astAnnul( fr );
+
+/* Obtain the number of input coordinate values per point for the
+   Mapping supplied and check that this matches the number of axes
+   obtained above. Report an error if it does not. */
+      ncoord = astGetNin( map );
+      if ( astOK && ( naxes != ncoord ) ) {
+         astError( AST__NCPIN, "astAddFrame(%s): Bad number of %s input "
+                   "coordinate values (%d).", status, astGetClass( this ),
+                   astGetClass( map ), ncoord );
+         astError( AST__NCPIN, "The %s given should accept %d coordinate "
+                   "value%s for each input point.", status, astGetClass( map ), naxes,
+                   ( naxes == 1 ) ? "" : "s" );
+      }
+   }
+
+/* Similarly, obtain the number of output coordinate values per point
+   for the Mapping and check that this equals the number of axes for
+   the Frame supplied. Report an error if necessary. */
+   if ( astOK ) {
+      ncoord = astGetNout( map );
+      naxes = astGetNaxes( frame );
+      if ( astOK && ( ncoord != naxes ) ) {
+         astError( AST__NCPIN, "astAddFrame(%s): Bad number of %s output "
+                   "coordinate values (%d).", status, astGetClass( this ),
+                   astGetClass( map ), ncoord );
+         astError( AST__NCPIN, "The %s given should generate %d "
+                   "coordinate value%s for each output point.", status,
+                   astGetClass( map ), naxes, ( naxes == 1 ) ? "" : "s" );
+      }
+   }
+
+/* Normal Frame supplied. */
+/* ====================== */
+/* Check that the Frame supplied is not a FrameSet (handling a
+   FrameSet is a special case which is addressed later). */
+   if ( !astIsAFrameSet( frame ) && astOK ) {
+
+/* Increase the size of the FrameSet's arrays to accommodate one new
+   Frame. */
+      this->frame = astGrow( this->frame, this->nframe + 1,
+                             sizeof( AstFrame * ) );
+      this->varfrm = astGrow( this->varfrm, this->nframe + 1,
+                             sizeof( int ) );
+      this->node = astGrow( this->node, this->nframe + 1, sizeof( int ) );
+      this->map = astGrow( this->map, this->nnode, sizeof( AstMapping * ) );
+      this->link = astGrow( this->link, this->nnode, sizeof( int ) );
+      this->invert = astGrow( this->invert, this->nnode, sizeof( int ) );
+      if ( astOK ) {
+
+/* Copy pointers to the Frame and Mapping supplied and store these pointers
+   in the FrameSet arrays. */
+         this->frame[ this->nframe ] = astCopy( frame );
+         this->map[ this->nnode - 1 ] = astCopy( map );
+
+/* Indicate the Frame does not reflect the variant Mappings of any other
+   Frame. */
+         this->varfrm[ this->nframe ] = 0;
+
+/* Associate the Frame with the Mapping via the "node" array. */
+         this->node[ this->nframe ] = this->nnode;
+
+/* Add a "link" value which identifies the node from which the new
+   node is derived and store the current value of the Invert attribute
+   for the Mapping. */
+         this->link[ this->nnode - 1 ] = this->node[ iframe - 1 ];
+         this->invert[ this->nnode - 1 ] = astGetInvert( map );
+
+/* If successful, increment the FrameSet's Frame and node counts and
+   set the Current attribute so that the new Frame becomes the current
+   Frame. */
+         if ( astOK ) {
+            this->nframe++;
+            this->nnode++;
+            astSetCurrent( this, this->nframe );
+
+/* If an error occurred while filling the FrameSet's arrays, clear any values
+   that may have been added, annulling any copied pointers. */
+         } else {
+            this->frame[ this->nframe ] =
+                  astAnnul( this->frame[ this->nframe ] );
+            this->node[ this->nframe ] = -1;
+            this->map[ this->nnode - 1 ] =
+                  astAnnul( this->map[ this->nnode - 1 ] );
+            this->link[ this->nnode - 1 ] = -1;
+	 }
+      }
+
+/* FrameSet supplied. */
+/* ================== */
+/* If the Frame supplied is a FrameSet, we handle this as a special
+   case by merging the two FrameSets (so that the final result
+   contains references to all the Frames from both FrameSets). */
+   } else if ( astOK ) {
+
+/* Obtain a pointer to the FrameSet structure containing the new Frame
+   references and calculate how many Frames and nodes the combined
+   FrameSet will contain. */
+      frameset = (AstFrameSet *) frame;
+      nframe = this->nframe + frameset->nframe;
+      nnode = this->nnode + frameset->nnode;
+
+/* Extend the original FrameSet's arrays to accommodate the new Frames
+   and nodes. */
+      this->frame = astGrow( this->frame, nframe, sizeof( AstFrame * ) );
+      this->varfrm = astGrow( this->varfrm, nframe, sizeof( int ) );
+      this->node = astGrow( this->node, nframe, sizeof( int ) );
+      this->map = astGrow( this->map, nnode - 1, sizeof( AstMapping * ) );
+      this->link = astGrow( this->link, nnode - 1, sizeof( int ) );
+      this->invert = astGrow( this->invert, nnode - 1, sizeof( int ) );
+
+/* If OK, loop to transfer the new Frame data into the new array
+   elements, cloning each Frame pointer. Increment each "node" value
+   to allow for the new node numbering which will apply when the new
+   node data is appended to the new arrays. */
+      if ( astOK ) {
+         for ( ifr = 1; ifr <= frameset->nframe; ifr++ ) {
+            this->frame[ this->nframe + ifr - 1 ] =
+               astCopy( frameset->frame[ ifr - 1 ] );
+            this->node[ this->nframe + ifr - 1 ] =
+               frameset->node[ ifr - 1 ] + this->nnode;
+            if( frameset->varfrm[ ifr - 1 ] > 0 ) {
+               this->varfrm[ this->nframe + ifr - 1 ] =
+                  frameset->varfrm[ ifr - 1 ] + this->nframe;
+            } else {
+               this->varfrm[ this->nframe + ifr - 1 ] = 0;
+            }
+         }
+
+/* Similarly, transfer the new node data, cloning each Mapping
+   pointer. Increment each "link" value to allow for the new node
+   numbering. */
+         for ( inode = 1; inode < frameset->nnode; inode++ ) {
+            this->map[ this->nnode + inode - 1 ] =
+               astCopy( frameset->map[ inode - 1 ] );
+            this->link[ this->nnode + inode - 1 ] =
+               frameset->link[ inode - 1 ] + this->nnode;
+            this->invert[ this->nnode + inode - 1 ] =
+               frameset->invert[ inode - 1 ];
+         }
+
+/* In transferring the node data (above), we left an empty array
+   element which will later be filled with data corresponding to node
+   zero in the new FrameSet (there are no data to be copied for this
+   node). Initialise the data for this element to null values. */
+         this->map[ this->nnode - 1 ] = NULL;
+         this->link[ this->nnode - 1 ] = -1;
+         this->invert[ this->nnode - 1 ] = -1;
+
+/* Determine which is the current Frame in the new FrameSet and
+   convert this into the corresponding Frame number in the combined
+   one. */
+         current = astGetCurrent( frameset ) + this->nframe;
+
+/* We must now form a new link between this Frame and Frame "iframe"
+   (using the Mapping supplied) in order to inter-relate the Frames
+   from the two FrameSets. However, this cannot be done immediately
+   because in general the node corresponding to Frame "current" will
+   already have a link pointing to another node. Moreover, the node
+   which was originally node zero (in the new FrameSet) still has
+   no data in our merged FrameSet.
+
+   To overcome this, we must re-structure the links within the
+   transferred data.  We do this by starting at the node corresponding
+   to Frame "current" and working back through each link until the
+   original node zero is reached. At each step along this path, we
+   reverse the direction of the link. This involves shifting the
+   associated data by one step along the path, so that it becomes
+   associated with the next node. This results in the final
+   (initialised-to-null) node acquiring some data, and the starting
+   node being left free to receive our new link.
+
+   We compensate for reversing the links by reversing the sense of the
+   "invert" flag associated with each Mapping along the path, so that
+   the overall structure of the FrameSet is unchanged. */
+
+/* Identify the starting node (the one corresponding to Frame
+   "current"). */
+         if ( astOK ) {
+            current_node = this->node[ current - 1 ];
+
+/* Obtain the value which a "link" element will now have if it
+   originally identified node zero in the new FrameSet. We will use
+   this value to detect the end of the path. */
+            node_zero = this->nnode;
+
+/* If we are not already at "node zero", save the data for the current
+   node. */
+            if ( current_node != node_zero ) {
+               inode_map = this->map[ current_node - 1 ];
+               inode_link = this->link[ current_node - 1 ];
+               inode_invert = this->invert[ current_node - 1 ];
+
+/* Reset the node's data to null values (pending setting up the new
+   link using the Mapping supplied). */
+               this->map[ current_node - 1 ] = NULL;
+               this->link[ current_node - 1 ] = -1;
+               this->invert[ current_node - 1 ] = -1;
+
+/* Identify the next node in the path. */
+               next = inode_link;
+            }
+
+/* Follow the path until "node zero" is reached. */
+            inode = current_node;
+            while( inode != node_zero ) {
+
+/* If the next node on the path is not "node zero", save its data
+   (because we are about to write over it). */
+               if ( next != node_zero ) {
+                  next_map = this->map[ next - 1 ];
+                  next_link = this->link[ next - 1 ];
+                  next_invert = this->invert[ next - 1 ];
+               }
+
+/* Reverse the link from the current node to the "next" node. This
+   involves transferring the "map" and "invert" values to the "next"
+   node and inverting the sense of the latter to compensate. Make the
+   "next" node point back to the current one. */
+               this->map[ next - 1 ] = inode_map;
+               this->link[ next - 1 ] = inode;
+               this->invert[ next - 1 ] = !inode_invert;
+
+/* Move on to consider the next node. */
+               inode = next;
+
+/* If we have not reached "node zero" yet, transfer the node data we
+   saved above into the variables from which it will be transferred to
+   the following node on the next pass through this loop. */
+               if ( inode != node_zero ) {
+                  inode_map = next_map;
+                  inode_link = next_link;
+                  inode_invert = next_invert;
+
+/* Identify the node that follows the next one. */
+                  next = inode_link;
+               }
+            }
+
+/* Once the necessary links have been re-structured, establish the new
+   link that inter-relates the Frames from the two FrameSets. */
+            this->map[ current_node - 1 ] = astCopy( map );
+            this->link[ current_node - 1 ] = this->node[ iframe - 1 ];
+            this->invert[ current_node - 1 ] = astGetInvert( map );
+         }
+
+/* If successful, update the Frame and node counts and make the
+   appropriate Frame current. */
+         if ( astOK ) {
+            this->nframe = nframe;
+            this->nnode = nnode;
+            astSetCurrent( this, current );
+
+/* If an error occurred, loop through all the new Frame and node array
+   elements and clear them, ensuring that any remaining Object
+   pointers are annulled. */
+         } else {
+            for ( ifr = 1; ifr <= frameset->nframe; ifr++ ) {
+               this->frame[ this->nframe + ifr - 1 ] =
+                  astAnnul( this->frame[ this->nframe + ifr - 1 ] );
+               this->node[ this->nframe + ifr - 1 ] = -1;
+               this->varfrm[ this->nframe + ifr - 1 ] = 0;
+            }
+            for ( inode = 0; inode < frameset->nnode; inode++ ) {
+               this->map[ this->nnode + inode - 1 ] =
+                  astAnnul( this->map[ this->nnode + inode - 1 ] );
+               this->link[ this->nnode + inode - 1 ] = -1;
+               this->invert[ this->nnode + inode - 1 ] = -1;
+            }
+         }
+      }
+   }
+}
+
+static void AddVariant( AstFrameSet *this, AstMapping *map,
+                        const char *name, int *status ) {
+/*
+*++
+*  Name:
+c     astAddVariant
+f     AST_ADDVARIANT
+
+*  Purpose:
+*     Store a new variant Mapping for the current Frame in a FrameSet.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     void astAddVariant( AstFrameSet *this, AstMapping *map,
+c                         const char *name )
+f     CALL AST_ADDVARIANT( THIS, MAP, NAME, STATUS )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+c     This function
+f     This routine
+*     allows a new variant Mapping to be stored with the current Frame
+*     in a FrameSet. See the "Variant" attribute for more details. It can
+*     also be used to rename the currently selected variant Mapping.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FrameSet.
+c     map
+f     MAP = INTEGER (Given)
+*        Pointer to a Mapping which describes how to convert
+*        coordinates from the current Frame to the new variant of the
+*        current Frame. If
+c        NULL
+f        AST__NULL
+*        is supplied, then the name associated with the currently selected
+*        variant of the current Frame is set to the value supplied for
+c        "name", but no new variant is added.
+f        NAME, but no new variant is added.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+*        The name to associate with the new variant Mapping (or the currently
+*        selected variant Mapping if
+c        "map" is NULL).
+f        MAP is AST__NULL).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - The newly added Variant becomes the current variant on exit (this is
+*     equivalent to setting the Variant attribute to the value supplied for
+c     "name).
+f     NAME).
+*     - An error is reported if a variant with the supplied name already
+*     exists in the current Frame.
+*     - An error is reported if the current Frame is a mirror for the
+*     variant Mappings in another Frame. This is only the case if the
+c     astMirrorVariants function
+f     AST_MIRRORVARIANTS routine
+*     has been called to make the current Frame act as a mirror.
+
+*--
+*/
+
+/* Local Variables: */
+   AstCmpMap *map2;
+   AstFrame *frm;
+   AstFrame *tfrm;
+   AstFrame *vfrm;
+   AstFrameSet *tfs;
+   AstFrameSet *vfs;
+   AstMapping *map1;
+   AstMapping *map3;
+   char *myname;
+   const char *dom;
+   int icur;
+   int ifrm;
+   int new;
+   int nfrm;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the one-based index of the current Frame. */
+   icur = astGetCurrent( this );
+
+/* Report an error if the current Frame is just a mirror. */
+   if( this->varfrm[ icur - 1 ] > 0 && astOK ) {
+      astError( AST__MIRRO, "astAddVariant(%s): Illegal attempt to "
+                "add a variant Mapping to a mirror Frame (programming "
+                "error).", status, astGetClass( this ) );
+   }
+
+/* Get a copy of the supplied string and clean it. */
+   myname = astStore( NULL, name, strlen( name ) + 1 );
+   astRemoveLeadingBlanks( myname );
+   astChrCase( NULL, myname, 1, 0 );
+   if( astOK ) {
+      myname[ astChrLen( myname ) ] = 0;
+
+/* Get the Variants FrameSet for the current Frame in "this". */
+      frm = astGetFrame( this, icur );
+      vfs = astGetFrameVariants( frm );
+
+/* If current Frame of this has no Variant FrameSet, create a Variants
+   FrameSet containing a copy of the current Frame (retain its Domain
+   as the default variant name). */
+      if( !vfs ) {
+         tfrm = astCopy( frm );
+         vfs = astFrameSet( tfrm, " ", status );
+         tfrm = astAnnul( tfrm );
+         new = 1;
+      } else {
+         new = 0;
+      }
+
+/* Check the Variants FrameSet does not already contain a Frame with
+   a Domain equal to the supplied name. */
+      nfrm = astGetNframe( vfs );
+      for( ifrm = 0; ifrm < nfrm && astOK; ifrm++ ) {
+         vfrm = astGetFrame( vfs, ifrm + 1 );
+         dom = astGetDomain( vfrm );
+         if( astOK && !strcmp( dom, myname ) ) {
+            astError( AST__BDVNM, "astAddVariant(%s): Cannot add a "
+                      "variant %s Frame with name '%s' because one "
+                      "already exists in the %s (programming "
+                      "error).", status, astGetClass( this ),
+                      astGetDomain( frm ), myname,  astGetClass( this ) );
+         }
+         vfrm = astAnnul( vfrm );
+      }
+
+/* If no Mapping was supplied, just set the name of the currently
+   selected variant. The names are stored in the Domain attribute of
+   the Frames in the variants FrameSet, so set teh DOmain for the current
+   Frame. */
+      if( !map ){
+         vfrm = astGetFrame( vfs, AST__CURRENT );
+         astSetDomain( vfrm, name );
+         vfrm = astAnnul( vfrm );
+
+/* If a Mapping was supplied.... */
+      } else {
+
+/* Get the Mapping from the current Frame in the variants FrameSet to the
+   current Frame in "this". Temporarily match the Domains so that
+   astConvert can work. */
+         vfrm = astGetFrame( vfs, AST__CURRENT );
+         dom = astGetDomain( frm );
+         if( dom ) dom = astStore( NULL, dom, strlen( dom ) + 1 );
+         astSetDomain( frm, astGetDomain( vfrm ) );
+         tfs = astConvert( vfrm, frm, "" );
+         astSetDomain( frm, dom );
+         if( tfs ) {
+            map1 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
+            tfs = astAnnul( tfs );
+
+/* Concatenate it with the supplied Mapping to get the mapping from the
+   current Frame in the Variants FrameSet to the new variant Frame. */
+            map2 = astCmpMap( map1, map, 1, " ", status );
+            map3 = astSimplify( map2 );
+
+/* Add a copy of parent Frame into Variants FrameSet, using the above
+   mapping to connect it to the original current Variants Frame. Set
+   its Domain to the supplied name. Re-instate the original current Frame
+   afterwards. Remove the variant frame info before adding it. */
+            (void) astAnnul( vfrm );
+            vfrm = astCopy( frm );
+            astSetFrameVariants( vfrm, NULL );
+            astSetDomain( vfrm, name );
+            icur = astGetCurrent( vfs );
+            astAddFrame( vfs, AST__CURRENT, map3, vfrm );
+            astSetCurrent( vfs, icur );
+
+/* Free resources. */
+            map1 = astAnnul( map1 );
+            map2 = astAnnul( map2 );
+            map3 = astAnnul( map3 );
+
+/* Report an error if a Mapping cannot be found from the new variant Frame
+   to the current Frame in "this". */
+         } else if( astOK ) {
+            astError( AST__INTER, "astAddVariant(%s): Cannot convert "
+                      "from a %s with Domain '%s' to a %s with Domain "
+                      "'%s' (internal programming error).", status,
+                      astGetClass( this ), astGetClass( vfrm ),
+                      astGetDomain( vfrm ), astGetClass( frm ),
+                      astGetDomain( frm ) );
+         }
+
+/* Free resources. */
+         dom = astFree( (void *) dom );
+         vfrm = astAnnul( vfrm );
+      }
+
+/* If all is well, and the Variants FrameSet is new, store a pointer to
+   it in the current Frame of "this". */
+      if( new ) astSetFrameVariants( frm, vfs );
+
+/* Make the new Variant the current variant. */
+      if( map ) astSetVariant( this, name );
+
+/* Free remaining resources. */
+      frm = astAnnul( frm );
+      vfs = astAnnul( vfs );
+   }
+   myname = astFree( myname );
+}
+
+static double Angle( AstFrame *this_frame, const double a[],
+                     const double b[], const double c[], int *status ) {
+/*
+*  Name:
+*     Angle
+
+*  Purpose:
+*     Calculate the angle subtended by two points at a third point.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     double Angle( AstFrame *this, const double a[], const double b[],
+*                   const double c[], int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astAngle
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function finds the angle at point B between the line joining points
+*     A and B, and the line joining points C and B. These lines will in fact be
+*     geodesic curves appropriate to the Frame in use. For instance, in
+*     SkyFrame, they will be great circles.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     a
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the first point.
+*     b
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the second point.
+*     c
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the third point.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     astAngle
+*        The angle in radians, from the line AB to the line CB. If the
+*        Frame is 2-dimensional, it will be in the range $\pm \pi$,
+*        and positive rotation is in the same sense as rotation from
+*        the positive direction of axis 2 to the positive direction of
+*        axis 1. If the Frame has more than 2 axes, a positive value will
+*        always be returned in the range zero to $\pi$.
+
+*  Notes:
+*     - A value of AST__BAD will also be returned if points A and B are
+*     co-incident, or if points B and C are co-incident.
+*     - A value of AST__BAD will also be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astAngle method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astAngle( fr, a, b, c );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static void AppendAxes( AstFrameSet *this, AstFrame *frame, int *status ) {
+/*
+*  Name:
+*     AppendAxes
+
+*  Purpose:
+*     Append axes to every Frame in a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void AppendAxes( AstFrameSet *this, AstFrame *frame, int *status )
+
+*  Class Membership:
+*     FrameSet member function
+
+*  Description:
+*     This function replaces every Frame in the FrameSet with a CmpFrame
+*     holding the original Frame and the supplied Frame. It also replaces
+*     every Mapping in the FrameSet with a parallel CmpMap holding the
+*     original Mapping and a UnitMap. The Nin and Nout attributes of every
+*     UnitMap are equal to the number of axes in the supplied Frame. Each
+*     CmpMap is simplified before being stored in the FrameSet.
+
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     frame
+*        Pointer to a Frame holding the new axes to add to every Frame in
+*        the FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *frm;             /* Pointer to new Frame */
+   AstCmpMap *map;               /* UnitMap to new Mapping */
+   AstUnitMap *umap;             /* UnitMap to feed the new axes */
+   int iframe;                   /* Frame index */
+   int imap;                     /* Mapping index */
+   int inv_orig;                 /* Original value of Invert attribute */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Loop round every Frame in the FrameSet. */
+   for ( iframe = 0; iframe < this->nframe; iframe++ ) {
+
+/* Create a CmpFrame holding the original Frame and the new Frame. */
+      frm = astCmpFrame( this->frame[ iframe ], frame, " ", status );
+
+/* Annul the original Frame pointer and store the new CmpFrame pointer. */
+      (void) astAnnul( this->frame[ iframe ] );
+      this->frame[ iframe ] = (AstFrame *) frm;
+   }
+
+/* Create a UnitMap with the number of inputs and outputs equal to the
+   number of axes in the supplied Frame. */
+   umap = astUnitMap( astGetNaxes( frame ), " ", status );
+
+/* Loop round every Mapping in the FrameSet. */
+   for ( imap = 0; imap < this->nnode - 1; imap++ ) {
+
+/* The Invert attribute of the Mapping may have been changed via a
+   different pointer since it was first added into the FrameSet. To
+   ensure that the FrameSet continues to behave as was originally
+   intended, we set the Invert attribute back to the value it had when
+   the Mapping was first added into the FrameSet. First, note the
+   current value of the Invert flag so that it can be re-instated later. */
+      inv_orig = astGetInvert( this->map[ imap ] );
+      astSetInvert( this->map[ imap ], this->invert[ imap ] );
+
+/*  Create a parallel CmpMap holding the original Mapping and the UnitMap. */
+      map = astCmpMap( this->map[ imap ], umap, 0, " ", status );
+
+/* Re-instate the original value for the Invert flag, and then annul the
+   original Mapping pointer. */
+      astSetInvert( this->map[ imap ], inv_orig );
+      (void) astAnnul( this->map[ imap ] );
+
+/* Simplify the new Mapping, and store it in the FrameSet. */
+      this->map[ imap ] = astSimplify( map );
+
+/* Store a copy of the Invert attribute that should be used with this
+   Mapping within the FrameSet (just in case it  is modified via some
+   excternal reference). */
+      this->invert[ imap ] = astGetInvert( this->map[ imap ] );
+
+/* Annul the un-simplified Mapping pointer. */
+      map = astAnnul( map );
+   }
+
+/* Annul the UnitMap pointer. */
+   umap = astAnnul( umap );
+}
+
+static double AxAngle( AstFrame *this_frame, const double a[], const double b[], int axis, int *status ) {
+/*
+*  Name:
+*     AxAngle
+
+*  Purpose:
+*     Returns the angle from an axis, to a line through two points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     double AxAngle( AstFrame *this, const double a[], const double b[], int axis, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astAxAngle
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function finds the angle, as seen from point A, between the positive
+*     direction of a specified axis, and the geodesic curve joining point
+*     A to point B.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     a
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the first point.
+*     b
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the second point.
+*     axis
+*        The number of the Frame axis from which the angle is to be
+*        measured (one-based).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*        The angle in radians, from the positive direction of the
+*        specified axis, to the line AB. If the Frame is 2-dimensional,
+*        it will be in the range $\pm \pi$, and positive rotation is in
+*        the same sense as rotation from the positive direction of axis 2
+*        to the positive direction of axis 1. If the Frame has more than 2
+*        axes, a positive value will always be returned in the range zero
+*        to $\pi$.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value, or if the require
+*     position angle is undefined.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxAngle" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astAxAngle method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astAxAngle( fr, a, b, axis );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static double AxDistance( AstFrame *this_frame, int axis, double v1, double v2, int *status ) {
+/*
+*  Name:
+*     AxDistance
+
+*  Purpose:
+*     Find the distance between two axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     double AxDistance( AstFrame *this, int axis, double v1, double v2, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astAxDistance
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a signed value representing the axis increment
+*     from axis value v1 to axis value v2.
+*
+*     For a simple Frame, this is a trivial operation returning the
+*     difference between the two axis values. But for other derived classes
+*     of Frame (such as a SkyFrame) this is not the case.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The index of the axis to which the supplied values refer. The
+*        first axis has index 1.
+*     v1
+*        The first axis value.
+*     v2
+*        The second axis value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The distance between the two axis values.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input vaues has this value.
+*     - A "bad" value will also be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxDistance" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astAxDistance method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astAxDistance( fr, axis, v1, v2 );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static void AxNorm( AstFrame *this_frame, int axis, int oper, int nval,
+                    double *values, int *status ){
+/*
+*  Name:
+*     AxNorm
+
+*  Purpose:
+*     Normalise an array of axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void AxNorm( AstFrame *this, int axis, int oper, int nval,
+*                  double *values, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astAxNorm
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function modifies a supplied array of axis values so that
+*     they are normalised in the manner indicated by parameter "oper".
+*
+*     No normalisation is possible for a simple Frame and so the supplied
+*     values are returned unchanged. However, this may not be the case for
+*     specialised sub-classes of Frame. For instance, a SkyFrame has a
+*     discontinuity at zero longitude and so a longitude value can be
+*     expressed in the range [-Pi,+PI] or the range [0,2*PI].
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The index of the axis to which the supplied values refer. The
+*        first axis has index 1.
+*     oper
+*        Indicates the type of normalisation to be applied. If zero is
+*        supplied, the normalisation will be the same as that performed by
+*        function astNorm. If 1 is supplied, the normalisation will be
+*        chosen automatically so that the resulting list has the smallest
+*        range.
+*     nval
+*        The number of points in the values array.
+*     values
+*        On entry, the axis values to be normalised. Modified on exit to
+*        hold the normalised values.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxNorm" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astAxNorm method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astAxNorm( fr, axis, oper, nval, values );
+   fr = astAnnul( fr );
+}
+
+static double AxOffset( AstFrame *this_frame, int axis, double v1, double dist, int *status ) {
+/*
+*  Name:
+*     AxOffset
+
+*  Purpose:
+*     Add an increment onto a supplied axis value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     double AxOffset( AstFrame *this, int axis, double v1, double dist, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astAxOffset
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns an axis value formed by adding a signed axis
+*     increment onto a supplied axis value.
+*
+*     For a simple Frame, this is a trivial operation returning the
+*     sum of the two supplied values. But for other derived classes
+*     of Frame (such as a SkyFrame) this is not the case.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The index of the axis to which the supplied values refer. The
+*        first axis has index 1.
+*     v1
+*        The original axis value.
+*     dist
+*        The axis increment to add to the original axis value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The incremented axis value.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input vaues has this value.
+*     - A "bad" value will also be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxOffset" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astAxOffset method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astAxOffset( fr, axis, v1, dist );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static AstObject *Cast( AstObject *this_object, AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Cast
+
+*  Purpose:
+*     Cast an Object into an instance of a sub-class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstObject *Cast( AstObject *this, AstObject *obj, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astCast
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a deep copy of an ancestral component of the
+*     supplied object. The required class of the ancestral component is
+*     specified by another object. Specifically, if "this" and "new" are
+*     of the same class, a copy of "this" is returned. If "this" is an
+*     instance of a subclass of "obj", then a copy of the component
+*     of "this" that matches the class of "obj" is returned. Otherwise,
+*     a NULL pointer is returned without error.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be cast.
+*     obj
+*        Pointer to an Object that defines the class of the returned Object.
+*        The returned Object will be of the same class as "obj".
+
+*  Returned Value:
+*     A pointer to the new Object. NULL if "this" is not a sub-class of
+*     "obj", or if an error occurs.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables; */
+   AstFrame *cfrm;
+   AstObject *new;
+   astDECLARE_GLOBALS
+   int generation_gap;
+
+/* Initialise */
+   new = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* See how many steps up the class inheritance ladder it is from "obj"
+   to this class (FrameSet). A positive value is returned if FrameSet
+   is a sub-class of "obj". A negative value is returned if "obj" is
+   a sub-class of FrameSet. Zero is returned if "obj" is a FrameSet.
+   AST__COUSIN is returned if "obj" is not on the same line of descent
+   as FrameSet. */
+   generation_gap = astClassCompare( (AstObjectVtab *) &class_vtab,
+                                     astVTAB( obj ) );
+
+/* If "obj" is a FrameSet or a sub-class of FrameSet, we can cast by
+   truncating the vtab for "this" so that it matches the vtab of "obJ",
+   and then taking a deep copy of "this". */
+   if( generation_gap <= 0 && generation_gap != AST__COUSIN ) {
+      new = astCastCopy( this_object, obj );
+
+/* If "obj" is not a FrameSet or a sub-class of FrameSet (e.g. a Frame or
+   some sub-class of Frame), we attempt to cast the current Frame into
+   the class indicated by "obj". */
+   } else {
+      cfrm = astGetFrame( (AstFrameSet *) this_object, AST__CURRENT );
+      new = astCast( cfrm, obj );
+      cfrm = astAnnul( cfrm );
+   }
+
+/* Return the new pointer. */
+   return new;
+}
+
+static double Centre( AstFrame *this_frame, int axis, double value, double gap, int *status ) {
+/*
+*  Name:
+*     Centre
+
+*  Purpose:
+*     Find a "nice" central value for tabulating Frame axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     double  Centre( AstFrame *this_frame, int axis, double value,
+*                     double gap, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astCentre method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns an axis value which produces a nice formatted
+*     value suitable for a major tick mark on a plot axis, close to the
+*     supplied axis value.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the axis (zero-based) for which a central value
+*        is to be found.
+*     value
+*        An arbitrary axis value in the section that is being plotted.
+*     gap
+*        The gap size.
+
+*  Returned Value:
+*     The nice central axis value.
+
+*  Notes:
+*     - A value of zero is returned if the supplied gap size is zero.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astCentre" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astCentre method to obtain the required value. Annul the
+   Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astCentre( fr, axis, value, gap );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static void CheckPerm( AstFrame *this_frame, const int *perm, const char *method, int *status ) {
+/*
+*  Name:
+*     CheckPerm
+
+*  Purpose:
+*     Check that an array contains a valid permutation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void CheckPerm( AstFrame *this, const int *perm, const char *method, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astCheckPerm
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of a permutation array that
+*     will be used to permute the order of a Frame's axes. If the
+*     permutation specified by the array is not valid, an error is
+*     reported and the global error status is set. Otherwise, the
+*     function returns without further action.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     perm
+*        Pointer to an array of integers with the same number of
+*        elements as there are axes in the Frame. For each axis, the
+*        corresponding integer gives the (zero based) axis index to be
+*        used to identify the information for that axis (using the
+*        un-permuted axis numbering). To be valid, the integers in
+*        this array should therefore all lie in the range zero to
+*        (naxes-1) inclusive, where "naxes" is the number of Frame
+*        axes, and each value should occur exactly once.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate a permutation array. This method name is used
+*        solely for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Error messages issued by this function refer to the external
+*     (public) numbering system used for axes (which is one-based),
+*     whereas zero-based axis indices are used internally.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astCheckPerm method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astCheckPerm( fr, perm, method );
+   fr = astAnnul( fr );
+
+}
+
+static void Clear( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     Clear
+
+*  Purpose:
+*     Clear attribute values for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void Clear( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the public astClear method
+*     inherited from the Object class).
+
+*  Description:
+*     This function clears the values of a specified set of attributes
+*     for a FrameSet. Clearing an attribute cancels any value that has
+*     previously been explicitly set for it, so that the standard
+*     default attribute value will subsequently be used instead. This
+*     also causes the astTest function to return the value zero for
+*     the attribute, indicating that no value has been set.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     attrib
+*        Pointer to a null-terminated character string containing a
+*        comma-separated list of the names of the attributes to be
+*        cleared.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function preserves the integrity of the FrameSet (if
+*     possible) by appropriately remapping its current Frame to take
+*     account of its changed attribute values.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFrame *save_frame;         /* Saved pointer to integrity Frame */
+   AstFrameSet *this;            /* Pointer to FrameSet structure */
+   const char *save_method;      /* Saved pointer to method name */
+   int ok;                       /* Status OK? */
+   int save_lost;                /* Saved integrity modified flag */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_object;
+
+/* To allow this function to be invoked recursively, we first save any
+   existing FrameSet integrity information in local variables. */
+   save_frame = integrity_frame;
+   save_lost= integrity_lost;
+   save_method = integrity_method;
+
+/* Set the name of the method being used (for use in error
+   messages). */
+   integrity_method = "astClear";
+
+/* Record the initial integrity state of the FrameSet. */
+   RecordIntegrity( this, status );
+
+/* Invoke the parent astClear method to clear the FrameSet's attribute
+   values and note if this succeeds. */
+   (*parent_clear)( this_object, attrib, status );
+   ok = astOK;
+
+/* Restore the FrameSet's integrity. */
+   RestoreIntegrity( this, status );
+
+/* If integrity could not be restored, then add contextual error
+   information. */
+   if ( !astOK && ok ) {
+      astError( astStatus, "Unable to accommodate clearing the \"%s\" "
+                           "attribute(s).", status, attrib );
+   }
+
+/* Restore any saved FrameSet integrity information. */
+   integrity_frame = save_frame;
+   integrity_lost = save_lost;
+   integrity_method = save_method;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void ClearAttrib( AstObject *this, const char *attrib )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astClearAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     FrameSet, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* We first handle attributes that apply to the FrameSet as a whole
+   (rather than to the current Frame). */
+
+/* Base. */
+/* ----- */
+   if ( !strcmp( attrib, "base" ) ) {
+      astClearBase( this );
+
+/* Current. */
+/* -------- */
+/* Since this determines the choice of current Frame, we must restore
+   the integrity state of the FrameSet before changing this attribute
+   and record the new integrity state afterwards. */
+   } else if ( !strcmp( attrib, "current" ) ) {
+      RestoreIntegrity( this, status );
+      astClearCurrent( this );
+      RecordIntegrity( this, status );
+
+/* ID. */
+/* --- */
+   } else if ( !strcmp( attrib, "id" ) ) {
+      astClearID( this );
+
+/* Ident. */
+/* ------ */
+   } else if ( !strcmp( attrib, "ident" ) ) {
+      astClearIdent( this );
+
+/* Invert. */
+/* ------- */
+/* Since this affects the choice of current Frame, we must restore the
+   integrity state of the FrameSet before changing this attribute and
+   record the new integrity state afterwards. */
+   } else if ( !strcmp( attrib, "invert" ) ) {
+      RestoreIntegrity( this, status );
+      astClearInvert( this );
+      RecordIntegrity( this, status );
+
+/* Report. */
+/* ------- */
+   } else if ( !strcmp( attrib, "report" ) ) {
+      astClearReport( this );
+
+/* Variant. */
+/* -------- */
+   } else if ( !strcmp( attrib, "variant" ) ) {
+      astClearVariant( this );
+
+/* If the name was not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then report an
+   error. */
+   } else if ( !strcmp( attrib, "allvariants" ) ||
+               !strcmp( attrib, "class" ) ||
+               !strcmp( attrib, "nframe" ) ||
+               !strcmp( attrib, "nin" ) ||
+               !strcmp( attrib, "nobject" ) ||
+               !strcmp( attrib, "nout" ) ||
+               !strcmp( attrib, "refcount" ) ||
+               !strcmp( attrib, "tranforward" ) ||
+               !strcmp( attrib, "traninverse" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Pass unrecognised attributes on to the FrameSet's current Frame for
+   further interpretation. */
+   } else {
+
+/* Force a copy to be made of the current Frame, if needed, to make it
+   independent of other Frames within the FrameSet. */
+      (void) ForceCopy( this, AST__CURRENT, status );
+
+/* Obtain a pointer to the current Frame and invoke its astClearAttrib
+   method. Annul the Frame pointer afterwards. */
+      fr = astGetFrame( this, AST__CURRENT );
+      astClearAttrib( fr, attrib );
+      fr = astAnnul( fr );
+
+/* Note that the current Frame has been modified. */
+      integrity_lost = 1;
+   }
+}
+
+static void ClearBase( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearBase
+
+*  Purpose:
+*     Clear the value of the Base attribute of a FrameSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void astClearBase( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function clears the value of the Base attribute of a
+*     FrameSet. This value is an index that identifies the base Frame
+*     for the FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                    /* FrameSet is inverted? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Determine if the FrameSet has been inverted. */
+   invert = astGetInvert( this );
+
+/* If it has not been inverted, clear the base Frame index, otherwise
+   clear the current Frame index instead. */
+   if ( astOK ) *( invert ? &this->current : &this->base ) = -INT_MAX;
+}
+
+static void ClearCurrent( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearCurrent
+
+*  Purpose:
+*     Clear the value of the Current attribute for a FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int astClearCurrent( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function clears the value of the Current attribute for a
+*     FrameSet. This attribute is an index that identifies the current
+*     Frame for the FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                   /* FrameSet is inverted? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Determine if the FrameSet has been inverted. */
+   invert = astGetInvert( this );
+
+/* If it has not been inverted, clear the current frame index,
+   otherwise clear the base Frame index instead. */
+   if ( astOK ) *( invert ? &this->base : &this->current ) = -INT_MAX;
+}
+
+static void ClearVariant( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearVariant
+
+*  Purpose:
+*     Clear the value of the Variant attribute of a FrameSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void astClearVariant( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function clears the value of the Variant attribute of a
+*     FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   int icur;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the one-based index of the Frame to use. */
+   icur = GetVarFrm( this, astGetCurrent( this ), status );
+
+/* Get a pointer to the current Frame in the FrameSet. */
+   frm = astGetFrame( this, icur );
+
+/* Replace any Variants FrameSet in the Frame with a NULL pointer. */
+   astSetFrameVariants( frm, NULL );
+
+/* Annul the current Frame pointer. */
+   frm = astAnnul( frm );
+
+}
+
+static AstMapping *CombineMaps( AstMapping *mapping1, int invert1,
+                                AstMapping *mapping2, int invert2,
+                                int series, int *status ) {
+/*
+*  Name:
+*     CombineMaps
+
+*  Purpose:
+*     Combine two Mappings with specified Invert flags into a CmpMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstMapping *CombineMaps( AstMapping *mapping1, int invert1,
+*                              AstMapping *mapping2, int invert2,
+*                              int series )
+
+*  Class Membership:
+*     FrameSet member function.
+
+*  Description:
+*     This function combines two Mappings into a CmpMap (compound
+*     Mapping) as if their Invert flags were set to specified values
+*     when the CmpMap is created. However, the individual Mappings are
+*     returned with their Invert flag values unchanged from their
+*     original state.
+
+*  Parameters:
+*     mapping1
+*        Pointer to the first Mapping.
+*     invert1
+*        The (boolean) Invert flag value required for the first Mapping.
+*     mapping2
+*        Pointer to the second Mapping.
+*     invert2
+*        The (boolean) Invert flag value required for the second Mapping.
+*     series
+*        Whether the Mappings are to be combined in series (as opposed to
+*        in parallel).
+
+*  Returned Value:
+*     A pointer to the resulting compound Mapping (a CmpMap).
+
+*  Notes:
+*     - This function is a wrap-up for the astCmpMap constructor and
+*     temporarily assigns the required Invert flag values while
+*     creating the required CmpMap. However, it also takes account of
+*     the possibility that the two Mapping pointers supplied may point
+*     at the same Mapping.
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map1;             /* First temporary Mapping pointer */
+   AstMapping *map2;             /* Second temporary Mapping pointer */
+   AstMapping *result;           /* Pointer to result Mapping */
+   int copy;                     /* Copy needed? */
+   int inv1;                     /* First original Invert flag value */
+   int inv2;                     /* Second original Invert flag value */
+   int set1;                     /* First Invert flag originally set? */
+   int set2;                     /* Second Invert flag originally set? */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Limit incoming values to 0 or 1. */
+   invert1 = ( invert1 != 0 );
+   invert2 = ( invert2 != 0 );
+
+/* Obtain the Invert flag values for each Mapping. */
+   inv1 = astGetInvert( mapping1 );
+   inv2 = astGetInvert( mapping2 );
+
+/* Also determine if these values are explicitly set. */
+   set1 = astTestInvert( mapping1 );
+   set2 = astTestInvert( mapping2 );
+
+/* If both Mappings are actually the same but we need different Invert
+   flag values to be set, then this can only be achieved by making a
+   copy. Note if this is necessary. */
+   copy = ( ( mapping1 == mapping2 ) && ( invert1 != invert2 ) );
+
+/* Clone the first Mapping pointer. Do likewise for the second but
+   make a copy instead if necessary. */
+   map1 = astClone( mapping1 );
+   map2 = copy ? astCopy( mapping2 ) : astClone( mapping2 );
+
+/* If the Invert value for the first Mapping needs changing, make the
+   change. */
+   if ( invert1 != inv1 ) {
+      if ( invert1 ) {
+         astSetInvert( map1, 1 );
+      } else {
+         astClearInvert( map1 );
+      }
+   }
+
+/* Similarly, change the Invert flag for the second Mapping if
+   necessary. */
+   if ( invert2 != inv2 ) {
+      if ( invert2 ) {
+         astSetInvert( map2, 1 );
+      } else {
+         astClearInvert( map2 );
+      }
+   }
+
+/* Combine the two Mappings into a CmpMap. */
+   result = (AstMapping *) astCmpMap( map1, map2, series, "", status );
+
+/* If the first Mapping's Invert value was changed, restore it to its
+   original state. */
+   if ( invert1 != inv1 ) {
+      if ( set1 ) {
+         astSetInvert( map1, inv1 );
+      } else {
+         astClearInvert( map1 );
+      }
+   }
+
+/* Similarly, restore the second Mapping's Invert value if
+   necessary. This step is not needed, however, if a copy was made. */
+   if ( ( invert2 != inv2 ) && !copy ) {
+      if ( set2 ) {
+         astSetInvert( map2, inv2 );
+      } else {
+         astClearInvert( map2 );
+      }
+   }
+
+/* Annul the temporary Mapping pointers. */
+   map1 = astAnnul( map1 );
+   map2 = astAnnul( map2 );
+
+/* If an error occurred, then annul the result pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstFrameSet *Convert( AstFrame *from, AstFrame *to,
+                             const char *domainlist, int *status ) {
+/*
+*  Name:
+*     Convert
+
+*  Purpose:
+*     Determine how to convert between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstFrameSet *Convert( AstFrame *from, AstFrame *to,
+*                           const char *domainlist, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the public astConvert
+*     method inherited fromm the Frame class).
+
+*  Description:
+*     This function compares two FrameSets and determines whether it
+*     is possible to convert between the coordinate systems which
+*     their current Frames represent. If conversion is possible, it
+*     returns a FrameSet which describes the conversion and which may
+*     be used (as a Mapping) to transform coordinate values in either
+*     direction.
+*
+*     If conversion is possible, the Base attributes of both FrameSets
+*     will be modified on exit to identify the Frames which were used
+*     as the intermediate coordinate system.
+
+*  Parameters:
+*     from
+*        Pointer to a FrameSet whose current Frame represents the
+*        "source" coordinate system.  Note that the Base attribute of
+*        the FrameSet may be modified by this function.
+*     to
+*        Pointer to a FrameSet whose current Frame represents the
+*        "destination" coordinate system.  Note that the Base
+*        attribute of the FrameSet may be modified by this function.
+*     domainlist
+*        Pointer to a null-terminated character string containing a
+*        comma-separated list of Frame domains. This may be used to
+*        define a priority order for the different intermediate
+*        coordinate systems that might be used to perform the
+*        conversion.
+*
+*        The function will first try to obtain a conversion by making
+*        use only of intermediate Frames whose Domain attribute
+*        matches the first domain in this list. If this fails, the
+*        second domain in the list will be used, and so on, until
+*        conversion is achieved. A blank domain (e.g. two consecutive
+*        commas) indicates that all Frames should be considered,
+*        regardless of their Domain attributes. The list is
+*        case-insensitive and all white space is ignored.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*        If the requested coordinate conversion is possible, the
+*        function returns a pointer to a FrameSet which describes the
+*        conversion. Otherwise, a null Object pointer (AST__NULL) is
+*        returned without error.
+*
+*        If a FrameSet is returned, it will contain two Frames. Frame
+*        number 1 (its base Frame) will describe the source coordinate
+*        system, corresponding to the "from" parameter. Frame number 2
+*        (its current Frame) will describe the destination coordinate
+*        system, corresponding to the "to" parameter. The Mapping
+*        which inter-relates these Frames will perform the required
+*        conversion between the two coordinate systems.
+
+*  Notes:
+*     - Either of the "from" and "to" pointers may identify a basic
+*     Frame instead of a FrameSet, in which case the function behaves
+*     as if it were a FrameSet containing only a single Frame.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+
+* Implementation Notes:
+*    - This function is simply a wrap-up for the ConvertX function
+*    which performs the required processing but swaps the order of the
+*    first two arguments. This is a trick to allow the astConvert
+*    method to be over-ridden by derived classes on the basis of the
+*    class of either of the first two arguments.
+*/
+
+/* Check the inherited status. */
+   if ( !astOK ) return NULL;
+
+/* Invoke the private "ConvertX" member function with the first two
+   arguments swapped. */
+   return ConvertX( to, from, domainlist, status );
+}
+
+static AstFrameSet *ConvertX( AstFrame *to, AstFrame *from,
+                              const char *domainlist, int *status ) {
+/*
+*  Name:
+*     ConvertX
+
+*  Purpose:
+*     Determine how to convert between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstFrameSet *ConvertX( AstFrame *to, AstFrame *from,
+*                            const char *domainlist )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected "astConvertX"
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function performs the processing for the public astConvert
+*     method (as inherited from the Frame class and over-ridden by the
+*     FrameSet class) and has exactly the same interface except that
+*     the order of the first two arguments is swapped. This is a trick
+*     to allow the astConvert method to be over-ridden by derived
+*     classes on the basis of the class of either of its first two
+*     arguments.
+*
+*     See the astConvert method for details of the interface.
+*/
+
+/* Local Variables: */
+   AstFrame *from_frame;         /* Pointer to "from" Frame */
+   AstFrame *to_frame;           /* Pointer to "to" Frame */
+   AstFrameSet *cvt;             /* Pointer to conversion FrameSet */
+   AstFrameSet *result;          /* Pointer to FrameSet to be returned */
+   AstMapping *from_map;         /* Pointer to "from" Mapping */
+   AstMapping *map;              /* Pointer to conversion Mapping */
+   AstMapping *result_map;       /* Pointer to result Mapping */
+   AstMapping *tmp;              /* Temporary Mapping pointer */
+   AstMapping *to_map;           /* Pointer to "to" Mapping */
+   char *domain;                 /* Pointer to individual domain string */
+   char *domain_end;             /* Pointer to final null of domain string */
+   char *domainlist_copy;        /* Pointer to copy of domains list */
+   int *from_order;              /* List of Frame indices in search order */
+   int *to_order;                /* List of Frame indices in search order */
+   int best_score;               /* Score from best match */
+   int from_base;                /* Index of "from" base Frame */
+   int from_current;             /* Index of "from" current Frame */
+   int from_index;               /* Index of "from" Frame */
+   int from_isframe;             /* "from" is a Frame (not a FrameSet)? */
+   int from_nframe;              /* Number of "from" Frames */
+   int from_number;              /* Loop counter for "from" Frames */
+   int iframe_from;              /* Index of best "from" Frame */
+   int iframe_to;                /* Index of best "to" Frame */
+   int match;                    /* Possible match found? */
+   int n;                        /* Count of Frames */
+   int perfect;                  /* Perfect match found? */
+   int score;                    /* Score from latest match */
+   int to_base;                  /* Index of "to" base Frame */
+   int to_current;               /* Index of "to" current Frame */
+   int to_index;                 /* Index of "to" Frame */
+   int to_isframe;               /* "to" is a Frame (not a FrameSet)? */
+   int to_nframe;                /* Number of "to" Frames */
+   int to_number;                /* Loop counter for "to" Frames */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   result_map = NULL;
+   iframe_from = 0;
+   iframe_to = 0;
+
+/* Determine the number of Frames in "from" and the indices of its
+   base and current Frames. Use values of 1 if "from" is a Frame and
+   not a FrameSet. */
+   from_isframe = !astIsAFrameSet( from );
+   from_nframe = from_isframe ? 1 : astGetNframe( from );
+   from_base = from_isframe ? 1 : astGetBase( from );
+   from_current = from_isframe ? 1 : astGetCurrent( from );
+
+/* Obtain similar values for "to". */
+   to_isframe = !astIsAFrameSet( to );
+   to_nframe = to_isframe ? 1 : astGetNframe( to );
+   to_base = to_isframe ? 1 : astGetBase( to );
+   to_current = to_isframe ? 1 : astGetCurrent( to );
+
+/* Allocate memory for arrays which will hold the indices of "from"
+   and "to" Frames. */
+   from_order = astMalloc( sizeof( int ) * (size_t) from_nframe );
+   to_order = astMalloc( sizeof( int ) * (size_t) to_nframe );
+
+/* Make a temporary copy of the domains list. */
+   domainlist_copy = astStore( NULL, domainlist,
+                               strlen( domainlist ) + (size_t) 1 );
+   if ( astOK ) {
+
+/* Fill the "from_order" array with the indices of all the Frames in
+   "from", in the order in which they will be used for searching. Use
+   the base Frame first. */
+      n = 0;
+      from_order[ n++ ] = from_base;
+
+/* Then add all the "from" Frames in the appropriate order, omitting
+   the base and current Frames. */
+      if ( !astGetInvert( from ) ) {
+         for ( from_index = 1; from_index <= from_nframe; from_index++ ) {
+            if ( ( from_index != from_base ) &&
+                 ( from_index != from_current ) ) {
+               from_order[ n++ ] = from_index;
+            }
+         }
+      } else {
+         for ( from_index = from_nframe; from_index >= 1; from_index-- ) {
+            if ( ( from_index != from_base ) &&
+                 ( from_index != from_current ) ) {
+               from_order[ n++ ] = from_index;
+            }
+         }
+      }
+
+/* Finish with the current Frame, if different from the base Frame. */
+      if ( from_current != from_base ) from_order[ n++ ] = from_current;
+
+/* Repeat this process for the "to" Frame. */
+      n = 0;
+      to_order[ n++ ] = to_base;
+      if ( !astGetInvert( to ) ) {
+         for ( to_index = 1; to_index <= to_nframe; to_index++ ) {
+            if ( ( to_index != to_base ) && ( to_index != to_current ) ) {
+               to_order[ n++ ] = to_index;
+            }
+         }
+      } else {
+         for ( to_index = to_nframe; to_index >= 1; to_index-- ) {
+            if ( ( to_index != to_base ) && ( to_index != to_current ) ) {
+               to_order[ n++ ] = to_index;
+            }
+         }
+      }
+      if ( to_current != to_base ) to_order[ n++ ] = to_current;
+
+/* Loop to inspect each comma-separated field in the domains list
+   until an error occurs, all the domains are used up, or a match is
+   found. */
+      domain = domainlist_copy;
+      match = 0;
+      while ( astOK && domain && !match ) {
+
+/* Change the comma at the end of each field to a null to terminate
+   the domain. */
+         if ( ( domain_end = strchr( domain, ',' ) ) ) *domain_end = '\0';
+
+/* For any given domain, we will ignore imperfect matches in favour of
+   better ones by assigning a score to each match. Initialise the best
+   score value for the current domain. */
+         best_score = -1;
+
+/* Loop through each Frame in "to". Quit looping early if an error
+   occurs or a perfect match is found. */
+         perfect = 0;
+         for ( to_number = 0;
+               astOK && !perfect && ( to_number < to_nframe );
+               to_number++ ) {
+
+/* Permute the "to" Frame number into a Frame index to implement the
+   required search order, and obtain a pointer to the required "to"
+   Frame. */
+            to_index = to_order[ to_number ];
+            to_frame = to_isframe ? astClone( to ) :
+                                    astGetFrame( to, to_index );
+
+/* Loop through each Frame in "from". Quit looping early if an error
+   occurs or a perfect match is found. */
+            for ( from_number = 0;
+                  astOK && !perfect && ( from_number < from_nframe );
+                  from_number++ ) {
+
+/* Permute the "from" Frame number into a Frame index to implement the
+   required search order, and obtain a pointer to the required "from"
+   Frame. */
+               from_index = from_order[ from_number ];
+               from_frame = from_isframe ? astClone( from ) :
+                                           astGetFrame( from, from_index );
+
+/* Attempt to obtain a FrameSet which describes the conversion between
+   the selected "from" and "to" Frames and test if successful. If so,
+   we have a potential route to construct the overall Mapping we
+   want. */
+               cvt = astConvert( from_frame, to_frame, domain );
+               if ( astOK && cvt ) {
+
+/* Extract the required Mapping from the returned FrameSet. */
+                  map = astGetMapping( cvt, AST__BASE, AST__CURRENT );
+
+/* If necessary, prefix the Mapping between the "from" current Frame
+   and the individual "from" Frame we have selected. */
+                  if ( from_index != from_current ) {
+                     from_map = astGetMapping( from, AST__CURRENT,
+                                               from_index );
+                     tmp = (AstMapping *) astCmpMap( from_map, map, 1, "", status );
+                     from_map = astAnnul( from_map );
+                     map = astAnnul( map );
+                     map = tmp;
+                  }
+
+/* Similarly, if necessary, append the Mapping between the selected
+   "to" Frame and the "to" current Frame. */
+                  if ( to_index != to_current ) {
+                     to_map = astGetMapping( to, to_index, AST__CURRENT );
+                     tmp = (AstMapping *) astCmpMap( map, to_map, 1, "", status );
+                     to_map = astAnnul( to_map );
+                     map = astAnnul( map );
+                     map = tmp;
+                  }
+
+/* Simplify the resulting overall Mapping (this is done here because
+   it may sometimes affect the attribute values used to assign a score
+   below). */
+                  tmp = astSimplify( map );
+                  map = astAnnul( map );
+                  map = tmp;
+
+/* Assign a score that favours Mappings with both transformations
+   available over those with only one, and Mappings with only a
+   forward transformation over those with only an inverse
+   transformation. */
+                  score = ( astGetTranForward( map ) ? 2 : 0 ) +
+                          ( astGetTranInverse( map ) ? 1 : 0 );
+
+/* If the new score is better than the previous one (or is the first
+   one), note that we have a possible match. */
+                  if ( astOK && ( score > best_score ) ) {
+                     match = 1;
+
+/* Update the best score and note if it indicates a perfect match (in
+   which case we can stop searching at this point). */
+                     best_score = score;
+                     perfect = ( best_score >= 3 );
+
+/* Annul any previous result Mapping pointer and replace it with this
+   better one. */
+                     if ( result_map ) result_map = astAnnul( result_map );
+                     result_map = astClone( map );
+
+/* Note which "from" and "to" Frames were used. */
+                     iframe_from = from_index;
+                     iframe_to = to_index;
+                  }
+
+/* Annul pointers to the intermediate Objects. */
+                  map = astAnnul( map );
+                  cvt = astAnnul( cvt );
+               }
+               from_frame = astAnnul( from_frame );
+            }
+            to_frame = astAnnul( to_frame );
+         }
+
+/* Go on to consider the next field in the domains list. */
+         domain = domain_end ? domain_end + 1 : NULL;
+      }
+   }
+
+/* Free the memory allocated for temporary arrays. */
+   domainlist_copy = astFree( domainlist_copy );
+   from_order = astFree( from_order );
+   to_order = astFree( to_order );
+
+/* If a result is being returned, then obtain a pointer to the current
+   "from" Frame and use it to start constructing the result
+   FrameSet. */
+   if ( result_map ) {
+      from_frame = from_isframe ? astClone( from ) :
+                                  astGetFrame( from, AST__CURRENT );
+      result = astFrameSet( from_frame, "", status );
+      from_frame = astAnnul( from_frame );
+
+/* Similarly. obtain a pointer to the current "to" frame and add it to
+   the result FrameSet (related to the base Frame by the result
+   Mapping). */
+      to_frame = to_isframe ? astClone( to ) :
+                              astGetFrame( to, AST__CURRENT );
+      astAddFrame( result, AST__BASE, result_map, to_frame );
+      to_frame = astAnnul( to_frame );
+
+/* Annul the result Mapping pointer. */
+      result_map = astAnnul( result_map );
+   }
+
+/* If successful, and a FrameSet is being returned, then set the base
+   Frames of "from" and "to" (if they are FrameSets) to indicate the
+   route used to generate the result Mapping. */
+   if ( astOK && result ) {
+      if ( !from_isframe ) astSetBase( from, iframe_from );
+      if ( !to_isframe ) astSetBase( to, iframe_to );
+   }
+
+/* If an error occurred, annul the returned FrameSet pointer. */
+   if ( !astOK && result ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static double Distance( AstFrame *this_frame,
+                        const double point1[], const double point2[], int *status ) {
+/*
+*  Name:
+*     Distance
+
+*  Purpose:
+*     Calculate the distance between two points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     double Distance( AstFrame *this,
+*                      const double point1[], const double point2[], int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astDistance
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function finds the distance between two points whose
+*     FrameSet coordinates are given. The distance calculated is that
+*     along the geodesic curve that joins the two points.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     point1
+*        An array of double, with one element for each FrameSet axis
+*        containing the coordinates of the first point.
+*     point2
+*        An array of double, with one element for each FrameSet axis
+*        containing the coordinates of the second point.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The distance between the two points.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input coordinates has this value.
+*     - A "bad" value will also be returned if this function is
+*     invoked with the AST error status set or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astDistance method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astDistance( fr, point1, point2 );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two FrameSets are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astEqual protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two FrameSets are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first FrameSet.
+*     that
+*        Pointer to the second FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the FrameSets are equivalent, zero otherwise.
+
+*  Notes:
+*     - The two FrameSets are considered equivalent if all the encapsulated
+*     Frames are equal and all the encapsulated Mappings are equal.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrameSet *that;            /* Pointer to the second FrameSet structure */
+   AstFrameSet *this;            /* Pointer to the first FrameSet structure */
+   int i;                        /* Loop index */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Checks that the second object is of the same class as the first . */
+   if( !strcmp( astGetClass( this_object ), astGetClass( that_object ) ) ){
+
+/* Obtain pointers to the two FrameSet structures. */
+      this = (AstFrameSet *) this_object;
+      that = (AstFrameSet *) that_object;
+
+/* Check the number of nodes and frames are equal. Also check the indices
+   of the base and current Frames are equal */
+      if( this->nframe == that->nframe &&
+          this->nnode == that->nnode &&
+          this->base == that->base &&
+          this->current == that->current ) {
+
+/* Check the Frames and nodes are equal. */
+          result = 1;
+          for ( i = 0; i < this->nframe; i++ ) {
+             if( !astEqual( this->frame[ i ], that->frame[ i ] ) ||
+                 this->node[ i ] != that->node[ i ] ){
+                result = 0;
+                break;
+             }
+          }
+
+/* Check the Mappings, links and invert flags are equal. */
+         if( result ) {
+            for ( i = 0; i < this->nnode - 1; i++ ) {
+               if( !astEqual( this->map[ i ], that->map[ i ] ) ||
+                   this->link[ i ] != that->link[ i ] ||
+                   this->invert[ i ] != that->invert[ i ] ) {
+                  result = 0;
+                  break;
+               }
+            }
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int Fields( AstFrame *this_frame, int axis, const char *fmt,
+                   const char *str, int maxfld, char **fields,
+                   int *nc, double *val, int *status ) {
+/*
+*+
+*  Name:
+*     astFields
+
+*  Purpose:
+*     Identify numerical fields within a formatted FrameSet axis value.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int astFields( AstFrame *this, int axis, const char *fmt,
+*                    const char *str, int maxfld, char **fields,
+*                    int *nc, double *val )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astFields
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function identifies the numerical fields within a FrameSet axis
+*     value that has been formatted using astAxisFormat. It assumes that
+*     the value was formatted using the supplied format string. It also
+*     returns the equivalent floating point value.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     axis
+*        The number of the FrameSet axis for which the values have been
+*        formatted (axis numbering starts at zero for the first axis).
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format used when creating "str".
+*     str
+*        Pointer to a constant null-terminated string containing the
+*        formatted value.
+*     maxfld
+*        The maximum number of fields to identify within "str".
+*     fields
+*        A pointer to an array of at least "maxfld" character pointers.
+*        Each element is returned holding a pointer to the start of the
+*        corresponding field  in "str" (in the order in which they occur
+*        within "str"), or NULL if no corresponding field can be found.
+*     nc
+*        A pointer to an array of at least "maxfld" integers. Each
+*        element is returned holding the number of characters in the
+*        corresponding field, or zero if no corresponding field can be
+*        found.
+*     val
+*        Pointer to a location at which to store the value
+*        equivalent to the returned field values. If this is NULL,
+*        it is ignored.
+
+*  Returned Value:
+*     The number of fields succesfully identified and returned.
+
+*  Notes:
+*     - Leading and trailing spaces are ignored.
+*     - If the formatted value is not consistent with the supplied format
+*     string, then a value of zero will be returned, "fields" will be
+*     returned holding NULLs, "nc" will be returned holding zeros, and
+*     "val" is returned holding VAL__BAD.
+*     - Fields are counted from the start of the formatted string. If the
+*     string contains more than "maxfld" fields, then trailing fields are
+*     ignored.
+*     - If this function is invoked with the global error status set, or
+*     if it should fail for any reason, then a value of zero will be returned
+*     as the function value, and "fields", "nc" and "val"  will be returned
+*     holding their supplied values
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   int result;                   /* Result field count to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astFields" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astFields method to perform the processing. Annul the Frame
+   pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astFields( fr, axis, fmt, str, maxfld, fields, nc, val );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static AstFrameSet *FindFrame( AstFrame *target_frame, AstFrame *template,
+                               const char *domainlist, int *status ) {
+/*
+*  Name:
+*     FindFrame
+
+*  Purpose:
+*     Find a coordinate system with specified characteristics.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstFrameSet *FindFrame( AstFrame *target, AstFrame *template,
+*                             const char *domainlist, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astFindFrame method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function uses a "template" Frame to search a FrameSet to
+*     identify a coordinate system which has a specified set of
+*     characteristics. If a suitable coordinate system can be found,
+*     the function returns a pointer to a FrameSet which describes the
+*     required coordinate system and how to convert coordinates to and
+*     from it.
+
+*  Parameters:
+*     target
+*        Pointer to the target FrameSet.  Note that if a suitable
+*        coordinate system is found, then the FrameSet's Current
+*        attribute will be modified to indicate which Frame was used
+*        to obtain attribute values which were not specified by the
+*        template.
+*     template
+*        Pointer to the template Frame, which should be an instance of
+*        the type of Frame you wish to find.
+*     domainlist
+*        Pointer to a null-terminated character string containing a
+*        comma-separated list of Frame domains. This may be used to
+*        establish a priority order for the different types of
+*        coordinate system that might be found.
+*
+*        The function will first try to find a suitable coordinate
+*        system whose Domain attribute equals the first domain in this
+*        list. If this fails, the second domain in the list will be
+*        used, and so on, until a result is obtained. A blank domain
+*        (e.g. two consecutive commas) indicates that any coordinate
+*        system is acceptable (subject to the template) regardless of
+*        its domain.
+*
+*        This list is case-insensitive and all white space is ignored.
+*        If you do not wish to restrict the domain in this way, you
+*        should supply an empty string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If the search is successful, the function returns a pointer to a
+*     FrameSet which contains the Frame found and a description of how
+*     to convert to (and from) the coordinate system it
+*     represents. Otherwise, a null Object pointer (AST__NULL) is
+*     returned without error.
+*
+*     If a FrameSet is returned, it will contain two Frames. Frame
+*     number 1 (its base Frame) represents the target coordinate
+*     system and will be the same as the (base Frame of the)
+*     target. Frame number 2 (its current Frame) will be a Frame
+*     representing the coordinate system which the function found. The
+*     Mapping which inter-relates these two Frames will describe how
+*     to convert between their respective coordinate systems.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *base_frame;         /* Pointer to target base Frame */
+   AstFrame *frame;              /* Pointer to result Frame */
+   AstFrame *selected_frame;     /* Pointer to selected target Frame */
+   AstFrameSet *found;           /* FrameSet pointer (result of search) */
+   AstFrameSet *result;          /* Pointer to result FrameSet */
+   AstFrameSet *target;          /* Pointer to target FrameSet structure */
+   AstMapping *map;              /* Pointer to result Mapping */
+   AstMapping *prefix;           /* Pointer to prefix Mapping */
+   AstMapping *tmp;              /* Temporary Mapping pointer */
+   char *domain;                 /* Pointer to individual domain field */
+   char *domain_end;             /* Pointer to null at end of domain */
+   char *domainlist_copy;        /* Pointer to copy of domains list */
+   int *target_order;            /* Array of indices defining search order */
+   int match;                    /* Match obtained? */
+   int n;                        /* Count of target_order elements */
+   int target_base;              /* Index of target base Frame */
+   int target_current;           /* Index of target current Frame */
+   int target_index;             /* Index of selected target Frame */
+   int target_nframe;            /* Number of Frames in target FrameSet */
+   int target_number;            /* Loop index for search */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   target_index = 0;
+
+/* Obtain a pointer to the target FrameSet structure. */
+   target = (AstFrameSet *) target_frame;
+
+/* Determine the number of Frames in the target FrameSet and the
+   indices of the current and base Frames. */
+   target_nframe = astGetNframe( target );
+   target_current = astGetCurrent( target );
+   target_base = astGetBase( target );
+
+/* Allocate an array to hold a list of all the target Frame indices. */
+   target_order = astMalloc( sizeof( int ) * (size_t) target_nframe );
+
+/* Make a temporary copy of the domains list. */
+   domainlist_copy = astStore( NULL, domainlist,
+                               strlen( domainlist ) + (size_t) 1 );
+   if ( astOK ) {
+
+/* Form a list of the indices of all the Frames in the target in the
+   order they will be searched for a match. Add the current Frame
+   index first. */
+      n = 0;
+      target_order[ n++ ] = target_current;
+
+/* Follow this by the base Frame index, if different. */
+      if ( target_base != target_current ) target_order[ n++ ] = target_base;
+
+/* Then add all the remaining target Frame indices. */
+      for ( target_index = 1; target_index <= target_nframe; target_index++ ) {
+         if ( ( target_index != target_current ) &&
+              ( target_index != target_base ) ) {
+            target_order[ n++ ] = target_index;
+         }
+      }
+
+/* Loop to inspect each comma-separated field in the domains list
+   until an error occurs, all the domains are used up, or a match is
+   found. */
+      domain = domainlist_copy;
+      match = 0;
+      while ( astOK && domain && !match ) {
+
+/* Change the comma at the end of each field to a null to terminate
+   the domain. */
+         if ( ( domain_end = strchr( domain, ',' ) ) ) *domain_end = '\0';
+
+/* Loop to try and match each target Frame in turn, in the order
+   identified above. Quit the loop early if an error occurs or a match
+   is found. */
+         for ( target_number = 0;
+               astOK && !match && ( target_number < target_nframe );
+               target_number++ ) {
+
+/* Permute the target Frame number into a Frame index to implement the
+   required search order. Then obtain a pointer to the selected target
+   Frame. */
+            target_index = target_order[ target_number ];
+            selected_frame = astGetFrame( target, target_index );
+
+/* Search the target Frame using the template supplied, together with
+   the current domain. */
+            found = astFindFrame( selected_frame, template, domain );
+
+/* Note if a match is found, and extract pointers to the conversion
+   Mapping and the result Frame from the FrameSet produced. */
+            if ( astOK && found ) {
+               match = 1;
+               map = astGetMapping( found, AST__BASE, AST__CURRENT );
+               frame = astGetFrame( found, AST__CURRENT );
+
+/* Obtain a pointer to the Mapping between the target base Frame and
+   the selected target Frame, and prefix this Mapping to the one
+   obtained above. */
+               prefix = astGetMapping( target, AST__BASE, target_index );
+               tmp = (AstMapping *) astCmpMap( prefix, map, 1, "", status );
+               prefix = astAnnul( prefix );
+               map = astAnnul( map );
+               map = tmp;
+
+/* Simplify the resulting Mapping. */
+               tmp = astSimplify( map );
+               map = astAnnul( map );
+               map = tmp;
+
+/* Obtain a pointer to the target base Frame, and use this to start
+   building the result FrameSet. */
+               base_frame = astGetFrame( target, AST__BASE );
+               result = astFrameSet( base_frame, "", status );
+               base_frame = astAnnul( base_frame );
+
+/* Add the result Frame, which is related to the base Frame by the
+   result Mapping. */
+               astAddFrame( result, AST__BASE, map, frame );
+
+/* Annul pointers to all intermediate Objects. */
+               map = astAnnul( map );
+               frame = astAnnul( frame );
+               found = astAnnul( found );
+            }
+            selected_frame = astAnnul( selected_frame );
+         }
+
+/* Go on to consider the next field in the domains list. */
+         domain = domain_end ? domain_end + 1 : NULL;
+      }
+   }
+
+/* Free the temporary arrays. */
+   target_order = astFree( target_order );
+   domainlist_copy = astFree( domainlist_copy );
+
+/* If a result is being returned, set the current Frame of the target
+   to indicate where the result Frame was found. */
+   if ( astOK && result ) astSetCurrent( target, target_index );
+
+/* If an error occurred, annul any result FrameSet pointer. */
+   if ( !astOK && result ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *FrameGrid( AstFrame *this_frame, int size, const double *lbnd,
+                               const double *ubnd, int *status ){
+/*
+*  Name:
+*     FrameGrid
+
+*  Purpose:
+*     Return a grid of points covering a rectangular area of a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstPointSet *FrameGrid( AstFrame *this_frame, int size,
+*                             const double *lbnd, const double *ubnd,
+*                             int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astFrameGrid
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a PointSet containing positions spread
+*     approximately evenly throughtout a specified rectangular area of
+*     the Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     size
+*        The preferred number of points in the returned PointSet. The
+*        actual number of points in the returned PointSet may be
+*        different, but an attempt is made to stick reasonably closely to
+*        the supplied value.
+*     lbnd
+*        Pointer to an array holding the lower bound of the rectangular
+*        area on each Frame axis. The array should have one element for
+*        each Frame axis.
+*     ubnd
+*        Pointer to an array holding the upper bound of the rectangular
+*        area on each Frame axis. The array should have one element for
+*        each Frame axis.
+
+*  Returned Value:
+*     A pointer to a new PointSet holding the grid of points.
+
+*  Notes:
+*     - A NULL pointer is returned if an error occurs.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   AstPointSet *result;          /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astFrameGrid method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astFrameGrid( fr, size, lbnd, ubnd );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int ForceCopy( AstFrameSet *this, int iframe, int *status ) {
+/*
+*  Name:
+*     ForceCopy
+
+*  Purpose:
+*     Force a copy to be made of a Frame, if necessary.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int ForceCopy( AstFrameSet *this, int iframe, int *status )
+
+*  Class Membership:
+*     FrameSet member function.
+
+*  Description:
+*     This function examines a Frame within a FrameSet, identified by its
+*     Frame index. If the same Frame is found to be referenced a second time
+*     within the FrameSet, then the original reference is replaced with an
+*     independent copy of the Frame.
+*
+*     This process supports the preservation of FrameSet integrity in cases
+*     where the same Frame is referenced more than once. After using this
+*     function, the nominated Frame's attributes may be modified without
+*     danger of affecting other parts of the FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     iframe
+*        The index of the Frame to be examined.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a copy of the nominated Frame was made, otherwise zero.
+
+*  Notes:
+*     - Using this function a second time on the same Frame will have no
+*     effect, since the first usage will make the Frame independent of any
+*     other Frames within the FrameSet.
+*     - A value of zero will be returned if this function is invoked with
+*     the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Declare the thread specific global data */
+   AstFrame *frame;             /* Pointer to Frame */
+   AstFrame *tmp;               /* Temporary Frame pointer */
+   int ifr;                     /* Loop counter for Frames */
+   int result;                  /* Value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Validate and translate the Frame index supplied. */
+   iframe = astValidateFrameIndex( this, iframe, integrity_method );
+
+/* If OK, obtain the corresponding Frame pointer (don't clone it). */
+   if ( astOK ) {
+      frame = this->frame[ iframe - 1 ];
+
+/* Loop to inspect each Frame in the FrameSet. */
+      for ( ifr = 1; ifr <= this->nframe; ifr++ ) {
+
+/* If the same Frame is referenced anywhere else, then make a copy of it. */
+         if ( ( ifr != iframe ) && ( this->frame[ ifr - 1 ] == frame ) ) {
+            tmp = astCopy( frame );
+
+/* If successful, replace the original reference to the Frame with a pointer
+   to this copy and annul the original pointer. */
+            if ( astOK ) {
+               this->frame[ iframe - 1 ] = tmp;
+               frame = astAnnul( frame );
+
+/* Set the returned result. */
+               if ( astOK ) result = 1;
+            }
+
+/* Quit looping once a copy has been made. */
+            break;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *Format( AstFrame *this_frame, int axis, double value, int *status ) {
+/*
+*  Name:
+*     Format
+
+*  Purpose:
+*     Format a coordinate value for a FrameSet axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     const char *Format( AstFrame *this, int axis, double value, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astFormat method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to a string containing the
+*     formatted (character) version of a coordinate value for a
+*     FrameSet axis. The formatting applied is that specified by a
+*     previous invocation of the astSetFormat method. A suitable
+*     default format is applied if necessary.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     axis
+*        The number of the axis (zero-based) for which formatting is
+*        to be performed.
+*     value
+*        The coordinate value to be formatted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the formatted
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the FrameSet object, or at static memory. The contents of
+*     the string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or deletion
+*     of the FrameSet. A copy of the string should therefore be made
+*     if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   const char *result;           /* Pointer value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astFormat" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astFormat method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astFormat( fr, axis, value );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static double Gap( AstFrame *this_frame, int axis, double gap, int *ntick, int *status ) {
+/*
+*  Name:
+*     Gap
+
+*  Purpose:
+*     Find a "nice" gap for tabulating FrameSet axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     double Gap( AstFrame *this, int axis, double gap, int *ntick, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astGap method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a gap size which produces a nicely spaced
+*     series of formatted values for a FrameSet axis, the returned gap
+*     size being as close as possible to the supplied target gap
+*     size. It also returns a convenient number of divisions into
+*     which the gap can be divided.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     axis
+*        The number of the axis (zero-based) for which a gap is to be found.
+*     gap
+*        The target gap size.
+*     ntick
+*        Address of an int in which to return a convenient number of
+*        divisions into which the gap can be divided.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The nice gap size.
+
+*  Notes:
+*     - A value of zero is returned if the target gap size is zero.
+*     - A negative gap size is returned if the supplied gap size is negative.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   double result;                /* Gap value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astGap" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astGap method to obtain the required gap value. Annul the
+   Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astGap( fr, axis, gap, ntick );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied FrameSet,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrameSet *this;         /* Pointer to FrameSet structure */
+   int result;                /* Result value to return */
+   int iframe;                /* Loop counter for Frames */
+   int inode;                 /* Loop counter for nodes */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the FrameSet structure. */
+   this = (AstFrameSet *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   for ( iframe = 0; iframe < this->nframe; iframe++ ) {
+      result += astGetObjSize( this->frame[ iframe ] );
+   }
+
+   for ( inode = 0; inode < this->nnode - 1; inode++ ) {
+      result += astGetObjSize( this->map[ inode ] );
+   }
+
+   result += astTSizeOf( this->frame );
+   result += astTSizeOf( this->varfrm );
+   result += astTSizeOf( this->node );
+   result += astTSizeOf( this->map );
+   result += astTSizeOf( this->link );
+   result += astTSizeOf( this->invert );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astGetAttrib
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a FrameSet, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the FrameSet, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the FrameSet. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   const char *result;           /* Pointer value to return */
+   int base;                     /* Base attribute value */
+   int current;                  /* Current attribute value */
+   int invert;                   /* Invert attribute value */
+   int nframe;                   /* Nframe attribute value */
+   int nin;                      /* Nin attribute value */
+   int nobject;                  /* Nobject attribute value */
+   int nout;                     /* Nout attribute value */
+   int ref_count;                /* RefCount attribute value */
+   int report;                   /* Report attribute value */
+   int tranforward;              /* TranForward attribute value */
+   int traninverse;              /* TranInverse attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* We first handle attributes that apply to the FrameSet as a whole
+   (rather than to the current Frame). */
+
+/* AllVariants. */
+/* ------------ */
+   if ( !strcmp( attrib, "allvariants" ) ) {
+      result = astGetAllVariants( this );
+
+/* Base. */
+/* ----- */
+   } else if ( !strcmp( attrib, "base" ) ) {
+      base = astGetBase( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", base );
+         result = getattrib_buff;
+      }
+
+/* Class. */
+/* ------ */
+   } else if ( !strcmp( attrib, "class" ) ) {
+      result = astGetClass( this );
+
+/* Current. */
+/* -------- */
+   } else if ( !strcmp( attrib, "current" ) ) {
+      current = astGetCurrent( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", current );
+         result = getattrib_buff;
+      }
+
+/* ID. */
+/* --- */
+   } else if ( !strcmp( attrib, "id" ) ) {
+      result = astGetID( this );
+
+/* Ident. */
+/* ------ */
+   } else if ( !strcmp( attrib, "ident" ) ) {
+      result = astGetIdent( this );
+
+/* Invert. */
+/* ------- */
+   } else if ( !strcmp( attrib, "invert" ) ) {
+      invert = astGetInvert( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", invert );
+         result = getattrib_buff;
+      }
+
+/* Nframe. */
+/* ------- */
+   } else if ( !strcmp( attrib, "nframe" ) ) {
+      nframe = astGetNframe( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", nframe );
+         result = getattrib_buff;
+      }
+
+/* Nin. */
+/* ---- */
+   } else if ( !strcmp( attrib, "nin" ) ) {
+      nin = astGetNin( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", nin );
+         result = getattrib_buff;
+      }
+
+/* Nobject. */
+/* -------- */
+   } else if ( !strcmp( attrib, "nobject" ) ) {
+      nobject = astGetNobject( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", nobject );
+         result = getattrib_buff;
+      }
+
+/* Nout. */
+/* ----- */
+   } else if ( !strcmp( attrib, "nout" ) ) {
+      nout = astGetNout( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", nout );
+         result = getattrib_buff;
+      }
+
+/* RefCount. */
+/* --------- */
+   } else if ( !strcmp( attrib, "refcount" ) ) {
+      ref_count = astGetRefCount( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ref_count );
+         result = getattrib_buff;
+      }
+
+/* Report. */
+/* ------- */
+   } else if ( !strcmp( attrib, "report" ) ) {
+      report = astGetReport( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", report );
+         result = getattrib_buff;
+      }
+
+/* TranForward. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "tranforward" ) ) {
+      tranforward = astGetTranForward( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", tranforward );
+         result = getattrib_buff;
+      }
+
+/* TranInverse. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "traninverse" ) ) {
+      traninverse = astGetTranInverse( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", traninverse );
+         result = getattrib_buff;
+      }
+
+/* Variant. */
+/* -------- */
+   } else if ( !strcmp( attrib, "variant" ) ) {
+      result = astGetVariant( this );
+
+/* Pass unrecognised attributes on to the FrameSet's current Frame for
+   further interpretation. */
+   } else {
+
+/* Obtain a pointer to the current Frame and invoke its astGetAttrib
+   method. Annul the Frame pointer afterwards. */
+      fr = astGetFrame( this, AST__CURRENT );
+      result = astGetAttrib( fr, attrib );
+      fr = astAnnul( fr );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static AstAxis *GetAxis( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetAxis
+
+*  Purpose:
+*     Obtain a pointer to a specified Axis from a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstAxis *GetAxis( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astGetAxis method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Axis object associated
+*     with one of the axes of the current Frame of a FrameSet. This
+*     object describes the quantity which is represented along that
+*     axis.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     axis
+*        The number of the axis (zero-based) for which an Axis pointer
+*        is required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the requested Axis object.
+
+*  Notes:
+*     - The reference count of the requested Axis object will be
+*     incremented by one to reflect the additional pointer returned by
+*     this function.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstAxis *result;              /* Pointer to Axis */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astGetAxis" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astGetAxis method to obtain the required Axis
+   pointer. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astGetAxis( fr, axis );
+   fr = astAnnul( fr );
+
+/* If an error occurred, annul the result. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int GetBase( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetBase
+
+*  Purpose:
+*     Obtain the value of the Base attribute for a FrameSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int astGetBase( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function returns the value of the Base attribute for a
+*     FrameSet. This value is an index that identifies the base Frame
+*     in the FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+
+*  Returned Value:
+*     The Base attribute value.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                   /* FrameSet is inverted? */
+   int result;                   /* Value to return */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Determine if the FrameSet has been inverted. */
+   invert = astGetInvert( this );
+
+/* If it has not been inverted, return the base Frame index, otherwise
+   return the index of the current Frame instead. Provide defaults if
+   necessary. */
+   if ( astOK ) {
+      if ( !invert ) {
+         result = ( this->base != -INT_MAX ) ? this->base : 1;
+      } else {
+         result = ( this->current != -INT_MAX ) ? this->current :
+                                                  astGetNframe( this );
+      }
+   }
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetCurrent( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetCurrent
+
+*  Purpose:
+*     Obtain the value of the Current attribute for a FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int astGetCurrent( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function returns the value of the Current attribute for a
+*     FrameSet.  This attribute is an index that identifies the
+*     current Frame in a FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+
+*  Returned Value:
+*     Value of the Current attribute.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                   /* FrameSet is inverted? */
+   int result;                   /* Value to return */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Determine if the FrameSet has been inverted. */
+   invert = astGetInvert( this );
+
+/* If it has not been inverted, return the current Frame index,
+   otherwise return the index of the base Frame instead. Provide
+   defaults if necessary. */
+   if ( astOK ) {
+      if ( !invert ) {
+         result = ( this->current != -INT_MAX ) ? this->current :
+                                                  astGetNframe( this );
+      } else {
+         result = ( this->base != -INT_MAX ) ? this->base : 1;
+      }
+   }
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static AstFrame *GetFrame( AstFrameSet *this, int iframe, int *status ) {
+/*
+*++
+*  Name:
+c     astGetFrame
+f     AST_GETFRAME
+
+*  Purpose:
+*     Obtain a pointer to a specified Frame in a FrameSet.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     AstFrame *astGetFrame( AstFrameSet *this, int iframe )
+f     RESULT = AST_GETFRAME( THIS, IFRAME, STATUS )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function returns a pointer to a specified Frame in a
+*     FrameSet.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FrameSet.
+c     iframe
+f     IFRAME = INTEGER (Given)
+*        The index of the required Frame within the FrameSet.  This
+*        value should lie in the range from 1 to the number of Frames
+*        in the FrameSet (as given by its Nframe attribute).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetFrame()
+f     AST_GETFRAME = INTEGER
+*        A pointer to the requested Frame.
+
+*  Notes:
+*     - A value of AST__BASE or AST__CURRENT may be given for the
+c     "iframe" parameter to specify the base Frame or the current
+f     IFRAME argument to specify the base Frame or the current
+*     Frame respectively.
+*     - This function increments the RefCount attribute of the
+*     selected Frame by one.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *result;             /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate and translate the Frame index supplied. */
+   iframe = astValidateFrameIndex( this, iframe, "astGetFrame" );
+
+/* If OK, clone a pointer to the requested Frame. */
+   if ( astOK ) result = astClone( this->frame[ iframe - 1 ] );
+
+/* Return the result. */
+   return result;
+}
+
+static int GetIsLinear( AstMapping *this_mapping, int *status ){
+/*
+*  Name:
+*     GetIsLinear
+
+*  Purpose:
+*     Return the value of the IsLinear attribute for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GetIsLinear( AstMapping *this, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astGetIsLinear
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns the value of the IsLinear attribute for a
+*     FrameSet, which is the IsLinear value of he base->current Mapping.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+*/
+/* Local Variables: */
+   AstMapping *map;
+   int result;
+
+/* Check global status */
+   if( !astOK ) return 0;
+
+/* Get the Mapping. */
+   map = astGetMapping( (AstFrameSet *) this_mapping, AST__BASE,
+                        AST__CURRENT );
+
+/* Get its IsLinear attribute value. */
+   result = astGetIsLinear( map );
+
+/* Free the Mapping. */
+   map = astAnnul( map );
+
+/* Return the result. */
+   return result;
+}
+
+static AstMapping *GetMapping( AstFrameSet *this, int iframe1, int iframe2, int *status ) {
+/*
+*++
+*  Name:
+c     astGetMapping
+f     AST_GETMAPPING
+
+*  Purpose:
+*     Obtain a Mapping that converts between two Frames in a FrameSet.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     AstMapping *astGetMapping( AstFrameSet *this, int iframe1, int iframe2 )
+f     RESULT = AST_GETMAPPING( THIS, IFRAME1, IFRAME2, STATUS )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function returns a pointer to a Mapping that will convert
+*     coordinates between the coordinate systems represented by two
+*     Frames in a FrameSet.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FrameSet.
+c     iframe1
+f     IFRAME1 = INTEGER (Given)
+*        The index of the first Frame in the FrameSet. This Frame describes
+*        the coordinate system for the "input" end of the Mapping.
+c     iframe2
+f     IFRAME2 = INTEGER (Given)
+*        The index of the second Frame in the FrameSet. This Frame
+*        describes the coordinate system for the "output" end of the
+*        Mapping.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetMapping()
+f     AST_GETMAPPING = INTEGER
+*        Pointer to a Mapping whose forward transformation converts
+*        coordinates from the first coordinate system to the second
+*        one, and whose inverse transformation converts coordinates in
+*        the opposite direction.
+
+*  Notes:
+*     - The returned Mapping will include the clipping effect of any
+*     Regions which occur on the path between the two supplied Frames
+*     (this includes the two supplied Frames themselves).
+c     - The values given for the "iframe1" and "iframe2" parameters
+f     - The values given for the IFRAME1 and IFRAME2 arguments
+*     should lie in the range from 1 to the number of Frames in the
+*     FrameSet (as given by its Nframe attribute). A value of
+*     AST__BASE or AST__CURRENT may also be given to identify the
+*     FrameSet's base Frame or current Frame respectively.  It is
+c     permissible for both these parameters to have the same value, in
+f     permissible for both these arguments to have the same value, in
+*     which case a unit Mapping (UnitMap) is returned.
+*     - It should always be possible to generate the Mapping
+*     requested, but this does necessarily guarantee that it will be
+*     able to perform the required coordinate conversion. If
+*     necessary, the TranForward and TranInverse attributes of the
+*     returned Mapping should be inspected to determine if the
+*     required transformation is available.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Temporary pointer to Frame */
+   AstFrame **frames;            /* Pointer to array of Frames */
+   AstMapping **path;            /* Pointer to array of conversion Mappings */
+   AstMapping *copy;             /* Pointer to copy of Mapping */
+   AstMapping *result;           /* Result pointer to be returned */
+   AstMapping *tmp;              /* Temporary pointer for joining Mappings */
+   int *forward;                 /* Pointer to array of Mapping directions */
+   int ipath;                    /* Loop counter for conversion path steps */
+   int iframe;                   /* Frame index */
+   int inode;                    /* Node index */
+   int npath;                    /* Number of steps in conversion path */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate and translate the Frame indices supplied. */
+   iframe1 = astValidateFrameIndex( this, iframe1, "astGetMapping" );
+   iframe2 = astValidateFrameIndex( this, iframe2, "astGetMapping" );
+
+/* Allocate memory to hold an array of Mapping pointers and associated
+   direction flags - a maximum of one element for each Mapping and one
+   for each Frame in the FrameSet. */
+   path = astMalloc( sizeof( AstMapping * ) * (size_t) ( this->nnode - 1 +
+                                                         this->nframe ) );
+   forward = astMalloc( sizeof( int ) * (size_t) ( this->nnode - 1 +
+                                                   this->nframe ) );
+
+/* Allocate memory to hold a list of the Frame pointers (if any) associated
+   with each node. */
+   frames = astMalloc( sizeof( AstFrame * )  * (size_t) ( this->nnode ) );
+
+/* If OK, set up an array of Frame pointers indexed by node index. If a
+   node has no associated Frame store a NULL pointer. This is needed so
+   that we can find Frame pointers quickly within the Span function. Note,
+   we simply copy the pointers rather than cloning them, so they do not
+   need to be annulled when finished with. */
+   if ( astOK ) {
+      for( inode = 0; inode < this->nnode; inode++ ) frames[ inode ] = NULL;
+      for( iframe = 0; iframe < this->nframe; iframe++ ) {
+         frames[ this->node[ iframe ] ] = this->frame[ iframe ];
+      }
+
+/* Obtain the Mapping pointers and direction flags needed to convert
+   coordinates between the nodes associated with the two specified
+   Frames. */
+      npath = Span( this, frames, this->node[ iframe1 - 1 ],
+                    this->node[ iframe2 - 1 ], -1, path, forward, status ) - 1;
+
+/* If this failed, it indicates a corrupt FrameSet object, so report
+   an error. */
+      if ( npath < 0 ) {
+         astError( AST__FRSIN, "astGetMapping(%s): Invalid or corrupt "
+                   "%s - could not find conversion path between Frames "
+                   "%d and %d.", status, astGetClass( this ), astGetClass( this ),
+                   iframe1, iframe2 );
+
+/* If the conversion path is of zero length (i.e. the two Frames are
+   the same) then we will return a Mapping which is equivalent to the
+   Frame. Most classes of Frame are equivalent to a UnitMap. However, we do
+   not hard-wire this equivalence since some classes of Frame (e.g. Regions
+   or CmpFrames containing Regions) do not correspond to a UnitMap. Instead
+   we use the astIsUnitFrame method on the Frame to determine if the
+   Frame is equivalent to a UnitMap.Is os, create a suitable UnitMap. If
+   not, return the Frame itself (a form of Mapping). */
+      } else if ( npath == 0 ) {
+         fr = astGetFrame( this, iframe1 );
+         if( astIsUnitFrame( fr ) ){
+            result = (AstMapping *) astUnitMap( astGetNaxes( fr ), "", status );
+         } else {
+            result = (AstMapping *) astClone( fr );
+         }
+         fr = astAnnul( fr );
+
+/* If the conversion path involves at least one non-trivial Mapping,
+   make a copy of the first Mapping, inverting the copy if
+   necessary. */
+      } else {
+         result = astCopy( path[ 0 ] );
+         if ( !forward[ 0 ] ) astInvert( result );
+
+/* Now loop to concatenate any further Mappings. First make a copy of
+   each additional Mapping and invert the copy if necessary. */
+         for ( ipath = 1; ipath < npath; ipath++ ) {
+            copy = astCopy( path[ ipath ] );
+            if ( !forward[ ipath ] ) astInvert( copy );
+
+/* Concatenate the copy with the result so far, then annul the pointer
+   to the copy and save the pointer to the new result. */
+            tmp = (AstMapping *) astCmpMap( result, copy, 1, "", status );
+            result = astAnnul( result );
+            copy = astAnnul( copy );
+            result = tmp;
+         }
+      }
+   }
+
+/* Free the memory allocated for the conversion path information. */
+   path = astFree( path );
+   forward = astFree( forward );
+   frames = astFree( frames );
+
+/* If an error occurred, annul the returned Mapping. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int GetNaxes( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetNaxes
+
+*  Purpose:
+*     Determine how many axes a FrameSet has.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int GetNaxes( AstFrame *this, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astGetNaxes method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns the number of axes for a FrameSet. This is equal
+*     to the number of axes in its current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of FrameSet axes (zero or more).
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame. */
+   fr = astGetFrame( this, AST__CURRENT );
+
+/* Obtain the number of axes in this Frame. */
+   result = astGetNaxes( fr );
+
+/* Annul the current Frame pointer. */
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetNframe( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNframe
+
+*  Purpose:
+*     Determine the number of Frames in a FrameSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int astGetNframe( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function returns the number of Frames in a FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+
+*  Returned Value:
+*     The number of Frames in the FrameSet (always 1 or more).
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Return the Frame count. */
+   return this->nframe;
+}
+
+static int GetNin( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     GetNin
+
+*  Purpose:
+*     Get the number of input coordinates for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int GetNin( AstMapping *this, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astGetNin method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns the number of input coordinate values
+*     required per point by a FrameSet when used to transform a set of
+*     points (i.e. the number of dimensions of the space in which the
+*     input points reside).
+*
+*     The value returned is equal to the number of axes in the
+*     FrameSet's base Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Number of coordinate values required.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to base Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_mapping;
+
+/* Obtain a pointer to the FrameSet's base Frame. */
+   fr = astGetFrame( this, AST__BASE );
+
+/* Obtain the number of axes in this Frame. */
+   result = astGetNaxes( fr );
+
+/* Annul the base Frame pointer. */
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetNout( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     GetNout
+
+*  Purpose:
+*     Get the number of output coordinates for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int GetNout( AstMapping *this, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astGetNout method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns the number of output coordinate values
+*     generated per point by a FrameSet when used to transform a set
+*     of points (i.e. the number of dimensions of the space in which
+*     the output points reside).
+*
+*     The value returned is equal to the number of axes in the
+*     FrameSet's current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Number of coordinate values generated.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Return the number of axes in the FrameSet's current Frame. */
+   return GetNaxes( (AstFrame *) this_mapping, status );
+}
+
+static const int *GetPerm( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetPerm
+
+*  Purpose:
+*     Access the axis permutation array for the current Frame of a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     const int *GetPerm( AstFrame *this, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astGetPerm protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the axis permutation array
+*     for the current Frame of a FrameSet. This array constitutes a
+*     lookup-table that converts between an axis number supplied
+*     externally and the corresponding index in the Frame's internal
+*     axis arrays.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the current Frame's axis permutation array (a
+*     constant array of int).  Each element of this contains the
+*     (zero-based) internal axis index to be used in place of the
+*     external index which is used to address the permutation
+*     array. If the current Frame has zero axes, this pointer will be
+*     NULL.
+
+*  Notes:
+*     - The pointer returned by this function gives direct access to
+*     data internal to the Frame object. It remains valid only so long
+*     as the Frame exists. The permutation array contents may be
+*     modified by other functions which operate on the Frame and this
+*     may render the returned pointer invalid.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to FrameSet structure */
+   const int *result;            /* Result pointer value */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and then obtain a
+   pointer to its axis permutation array. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astGetPerm( fr );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetTranForward( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     GetTranForward
+
+*  Purpose:
+*     Determine if a FrameSet defines a forward coordinate transformation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int GetTranForward( AstMapping *this )
+
+*  Class Membership:
+*     Frameset member function (over-rides the astGetTranForward
+*     protected method inherited from the Frame class).
+
+*  Description:
+*     This function returns a value indicating whether a FrameSet is
+*     able to perform a coordinate transformation in the "forward"
+*     direction.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+
+*  Returned Value:
+*     Zero if the forward coordinate transformation is not defined, or
+*     1 if it is.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   AstMapping *map;              /* Pointer to base->current Mapping */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_mapping;
+
+/* Obtain the Mapping between the base and current Frames in the
+   FrameSet (note this takes account of whether the FrameSet has been
+   inverted). */
+   map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Determine whether the required transformation is defined. */
+   result = astGetTranForward( map );
+
+/* Annul the Mapping pointer. */
+   map = astAnnul( map );
+
+/* If an error occurred, clear the returned result. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetTranInverse( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     GetTranInverse
+
+*  Purpose:
+*     Determine if a FrameSet defines an inverse coordinate transformation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int GetTranInverse( AstMapping *this )
+
+*  Class Membership:
+*     Frameset member function (over-rides the astGetTranInverse
+*     protected method inherited from the Frame class).
+
+*  Description:
+*     This function returns a value indicating whether a FrameSet is
+*     able to perform a coordinate transformation in the "inverse"
+*     direction.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+
+*  Returned Value:
+*     Zero if the inverse coordinate transformation is not defined, or
+*     1 if it is.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   AstMapping *map;              /* Pointer to base->current Mapping */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_mapping;
+
+/* Obtain the Mapping between the base and current Frames in the
+   FrameSet (note this takes account of whether the FrameSet has been
+   inverted). */
+   map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Determine whether the required transformation is defined. */
+   result = astGetTranInverse( map );
+
+/* Annul the Mapping pointer. */
+   map = astAnnul( map );
+
+/* If an error occurred, clear the returned result. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetUseDefs( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetUseDefs
+
+*  Purpose:
+*     Get the value of the UseDefs attribute for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int GetUseDefs( AstObject *this_object, int *status ) {
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astGetUseDefs
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the value of the UseDefs attribute for a FrameSet,
+*     supplying a suitable default.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - The USeDefs value.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   int result;                   /* Value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_object;
+
+/* If the UseDefs value for the FrameSet has been set explicitly, use the
+   Get method inherited from the parent Frame class to get its value> */
+   if( astTestUseDefs( this ) ) {
+      result = (*parent_getusedefs)( this_object, status );
+
+/* Otherwise, supply a default value equal to the UseDefs value of the
+   current Frame. */
+   } else {
+      fr = astGetFrame( this, AST__CURRENT );
+      result = astGetUseDefs( fr );
+      fr = astAnnul( fr );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetVarFrm( AstFrameSet *this, int iframe, int *status ) {
+/*
+*  Name:
+*     GetVarFrm
+
+*  Purpose:
+*     Get the index of the variants Frame for a nominated Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int GetVarFrm( AstFrameSet *this, int iframe, int *status ) {
+
+*  Class Membership:
+*     Private function.
+
+*  Description:
+*     This function returns the index of the variants Frame associated
+*     with a nominated mirror Frame. See astMirrorVariants.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     iframe
+*        The one-based index of the nominated Frame that may potentially be
+*        a mirror for the variant Mappings in another Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The one-based Frame index of the Frame that defines the variant
+*     Mappings associated with Frame "iframe". This will be the same as
+*     "iframe" unless the nominated Frame is a mirror for another Frame.
+*/
+
+/* Local Variables: */
+   int result;                   /* Value to return */
+
+/* Initialise. */
+   result = AST__NOFRAME;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise the returned value. */
+   result = iframe;
+
+/* If the nominated Frame is mirroring another Frame, return the index of
+   the mirrored Frame. Walk up the chain until we reach a Frame which is
+   not a mirror for another Frame. */
+   while( this->varfrm[ result - 1 ] > 0 ) {
+      if( this->varfrm[ result - 1 ] == result ) {
+         astError( AST__INTER, "GetVarFrm(FrameSet): FrameSet is corrupt "
+                   "(internal programming error).", status );
+         break;
+      } else {
+         result = this->varfrm[ result - 1 ];
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetVariant( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetVariant
+
+*  Purpose:
+*     Obtain the value of the Variant attribute for a FrameSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     const char *astGetVariant( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function returns the value of the Variant attribute for a
+*     FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+
+*  Returned Value:
+*     The Variant attribute value.
+
+*  Notes:
+*     - A NULL value will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstFrame *vfs;
+   const char *result;
+   int icur;
+   int iuse;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the one-based index of the current Frame. */
+   icur = astGetCurrent( this );
+
+/* The current Frame may mirror the variant Mappings in another Frame,
+   rather than defining any variant Mappings itself. Get the one-based
+   index of the Frame that defines the variant Mappings to use. */
+   iuse = GetVarFrm( this, icur, status );
+
+/* Get a pointer to the Variants FrameSet in the used Frame. */
+   frm = astGetFrame( this, iuse );
+   vfs = astGetFrameVariants( frm );
+
+/* If the current Frame has no Variants FrameSet, return the Domain name
+   of the current Frame. */
+   if( !vfs ) {
+      result = astGetDomain( this );
+
+/* Otherwise, return the Domain name of the current Frame in the Variants
+   FrameSet. Then annul the Variants FrameSet pointer. */
+   } else {
+      result = astGetDomain( vfs );
+      vfs = astAnnul( vfs );
+   }
+
+/* Annul the current Frame pointer. */
+   frm = astAnnul( frm );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+void astInitFrameSetVtab_(  AstFrameSetVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitFrameSetVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void astInitFrameSetVtab( AstFrameSetVtab *vtab, const char *name )
+
+*  Class Membership:
+*     FrameSet vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the FrameSet class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrameVtab *frame;          /* Pointer to Frame component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitFrameVtab( (AstFrameVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAFrameSet) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstFrameVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->AddFrame = AddFrame;
+   vtab->AddVariant = AddVariant;
+   vtab->ClearBase = ClearBase;
+   vtab->ClearCurrent = ClearCurrent;
+   vtab->GetBase = GetBase;
+   vtab->GetCurrent = GetCurrent;
+   vtab->GetFrame = GetFrame;
+   vtab->GetMapping = GetMapping;
+   vtab->GetNframe = GetNframe;
+   vtab->GetAllVariants = GetAllVariants;
+   vtab->MirrorVariants = MirrorVariants;
+   vtab->RemapFrame = RemapFrame;
+   vtab->RemoveFrame = RemoveFrame;
+   vtab->SetBase = SetBase;
+   vtab->SetCurrent = SetCurrent;
+   vtab->TestBase = TestBase;
+   vtab->TestCurrent = TestCurrent;
+   vtab->ValidateFrameIndex = ValidateFrameIndex;
+
+   vtab->ClearVariant = ClearVariant;
+   vtab->GetVariant = GetVariant;
+   vtab->SetVariant = SetVariant;
+   vtab->TestVariant = TestVariant;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+
+   parent_clear = object->Clear;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+   object->Clear = Clear;
+
+   parent_vset = object->VSet;
+   object->VSet = VSet;
+
+   parent_getusedefs = object->GetUseDefs;
+   object->GetUseDefs = GetUseDefs;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   mapping = (AstMappingVtab *) vtab;
+   frame = (AstFrameVtab *) vtab;
+
+   object->ClearAttrib = ClearAttrib;
+   object->GetAttrib = GetAttrib;
+   object->SetAttrib = SetAttrib;
+   object->TestAttrib = TestAttrib;
+
+   object->GetUseDefs = GetUseDefs;
+   object->Equal = Equal;
+   object->Cast = Cast;
+
+   mapping->GetIsLinear = GetIsLinear;
+   mapping->GetNin = GetNin;
+   mapping->GetNout = GetNout;
+   mapping->GetTranForward = GetTranForward;
+   mapping->GetTranInverse = GetTranInverse;
+   mapping->Rate = Rate;
+   mapping->ReportPoints = ReportPoints;
+   mapping->RemoveRegions = RemoveRegions;
+   mapping->Simplify = Simplify;
+   mapping->Transform = Transform;
+   mapping->MapSplit = MapSplit;
+
+   frame->Abbrev = Abbrev;
+   frame->Angle = Angle;
+   frame->AxAngle = AxAngle;
+   frame->AxDistance = AxDistance;
+   frame->AxNorm = AxNorm;
+   frame->AxOffset = AxOffset;
+   frame->CheckPerm = CheckPerm;
+   frame->ClearDigits = ClearDigits;
+   frame->ClearDirection = ClearDirection;
+   frame->ClearDomain = ClearDomain;
+   frame->ClearFormat = ClearFormat;
+   frame->ClearLabel = ClearLabel;
+   frame->ClearMatchEnd = ClearMatchEnd;
+   frame->ClearMaxAxes = ClearMaxAxes;
+   frame->ClearMinAxes = ClearMinAxes;
+   frame->ClearPermute = ClearPermute;
+   frame->ClearPreserveAxes = ClearPreserveAxes;
+   frame->ClearSymbol = ClearSymbol;
+   frame->ClearTitle = ClearTitle;
+   frame->ClearUnit = ClearUnit;
+   frame->Convert = Convert;
+   frame->ConvertX = ConvertX;
+   frame->Distance = Distance;
+   frame->Fields = Fields;
+   frame->FindFrame = FindFrame;
+   frame->Format = Format;
+   frame->FrameGrid = FrameGrid;
+   frame->Centre = Centre;
+   frame->Gap = Gap;
+   frame->GetAxis = GetAxis;
+   frame->GetDigits = GetDigits;
+   frame->GetDirection = GetDirection;
+   frame->GetDomain = GetDomain;
+   frame->GetFormat = GetFormat;
+   frame->GetLabel = GetLabel;
+   frame->GetMatchEnd = GetMatchEnd;
+   frame->GetMaxAxes = GetMaxAxes;
+   frame->GetMinAxes = GetMinAxes;
+   frame->GetNaxes = GetNaxes;
+   frame->GetPerm = GetPerm;
+   frame->GetPermute = GetPermute;
+   frame->GetPreserveAxes = GetPreserveAxes;
+   frame->GetSymbol = GetSymbol;
+   frame->GetTitle = GetTitle;
+   frame->GetUnit = GetUnit;
+   frame->Intersect = Intersect;
+   frame->IsUnitFrame = IsUnitFrame;
+   frame->LineContains = LineContains;
+   frame->LineCrossing = LineCrossing;
+   frame->LineDef = LineDef;
+   frame->LineOffset = LineOffset;
+   frame->Match = Match;
+   frame->MatchAxes = MatchAxes;
+   frame->MatchAxesX = MatchAxesX;
+   frame->Norm = Norm;
+   frame->NormBox = NormBox;
+   frame->Offset = Offset;
+   frame->Offset2 = Offset2;
+   frame->Overlay = Overlay;
+   frame->PermAxes = PermAxes;
+   frame->PickAxes = PickAxes;
+   frame->PrimaryFrame = PrimaryFrame;
+   frame->Resolve = Resolve;
+   frame->ResolvePoints = ResolvePoints;
+   frame->SetAxis = SetAxis;
+   frame->SetDigits = SetDigits;
+   frame->SetDirection = SetDirection;
+   frame->SetDomain = SetDomain;
+   frame->SetFormat = SetFormat;
+   frame->SetLabel = SetLabel;
+   frame->SetMatchEnd = SetMatchEnd;
+   frame->SetMaxAxes = SetMaxAxes;
+   frame->SetMinAxes = SetMinAxes;
+   frame->SetPermute = SetPermute;
+   frame->SetPreserveAxes = SetPreserveAxes;
+   frame->SetSymbol = SetSymbol;
+   frame->SetTitle = SetTitle;
+   frame->SetUnit = SetUnit;
+   frame->SubFrame = SubFrame;
+   frame->SystemCode = SystemCode;
+   frame->SystemString = SystemString;
+   frame->TestDigits = TestDigits;
+   frame->TestDirection = TestDirection;
+   frame->TestDomain = TestDomain;
+   frame->TestFormat = TestFormat;
+   frame->TestLabel = TestLabel;
+   frame->TestMatchEnd = TestMatchEnd;
+   frame->TestMaxAxes = TestMaxAxes;
+   frame->TestMinAxes = TestMinAxes;
+   frame->TestPermute = TestPermute;
+   frame->TestPreserveAxes = TestPreserveAxes;
+   frame->TestSymbol = TestSymbol;
+   frame->TestTitle = TestTitle;
+   frame->TestUnit = TestUnit;
+   frame->Unformat = Unformat;
+   frame->ValidateAxis = ValidateAxis;
+   frame->ValidateAxisSelection = ValidateAxisSelection;
+   frame->ValidateSystem = ValidateSystem;
+
+   frame->GetActiveUnit = GetActiveUnit;
+   frame->SetActiveUnit = SetActiveUnit;
+   frame->TestActiveUnit = TestActiveUnit;
+
+   frame->GetTop = GetTop;
+   frame->SetTop = SetTop;
+   frame->TestTop = TestTop;
+   frame->ClearTop = ClearTop;
+
+   frame->GetBottom = GetBottom;
+   frame->SetBottom = SetBottom;
+   frame->TestBottom = TestBottom;
+   frame->ClearBottom = ClearBottom;
+
+   frame->GetEpoch = GetEpoch;
+   frame->SetEpoch = SetEpoch;
+   frame->TestEpoch = TestEpoch;
+   frame->ClearEpoch = ClearEpoch;
+
+   frame->GetSystem = GetSystem;
+   frame->SetSystem = SetSystem;
+   frame->TestSystem = TestSystem;
+   frame->ClearSystem = ClearSystem;
+
+   frame->GetAlignSystem = GetAlignSystem;
+   frame->SetAlignSystem = SetAlignSystem;
+   frame->TestAlignSystem = TestAlignSystem;
+   frame->ClearAlignSystem = ClearAlignSystem;
+
+   frame->ClearObsLat = ClearObsLat;
+   frame->TestObsLat = TestObsLat;
+   frame->GetObsLat = GetObsLat;
+   frame->SetObsLat = SetObsLat;
+
+   frame->ClearObsAlt = ClearObsAlt;
+   frame->TestObsAlt = TestObsAlt;
+   frame->GetObsAlt = GetObsAlt;
+   frame->SetObsAlt = SetObsAlt;
+
+   frame->ClearObsLon = ClearObsLon;
+   frame->TestObsLon = TestObsLon;
+   frame->GetObsLon = GetObsLon;
+   frame->SetObsLon = SetObsLon;
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "FrameSet",
+               "Set of inter-related coordinate systems" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void Intersect( AstFrame *this_frame, const double a1[2],
+                       const double a2[2], const double b1[2],
+                       const double b2[2], double cross[2],
+                       int *status ) {
+/*
+*  Name:
+*     Intersect
+
+*  Purpose:
+*     Find the point of intersection between two geodesic curves.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void Intersect( AstFrame *this_frame, const double a1[2],
+*                      const double a2[2], const double b1[2],
+*                      const double b2[2], double cross[2],
+*                      int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astIntersect method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function finds the coordinate values at the point of
+*     intersection between two geodesic curves. Each curve is specified
+*     by two points on the curve.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     a1
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a point on the first
+*        geodesic curve.
+*     a2
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a second point on the
+*        first geodesic curve.
+*     b1
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a point on the second
+*        geodesic curve.
+*     b2
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a second point on
+*        the second geodesic curve.
+*     cross
+*        An array of double, with one element for each Frame axis
+*        in which the coordinates of the required intersection
+*        point will be returned. These will be AST__BAD if the curves do
+*        not intersect.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*     - For SkyFrames each curve will be a great circle, and in general
+*     each pair of curves will intersect at two diametrically opposite
+*     points on the sky. The returned position is the one which is
+*     closest to point "a1".
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astIntersect method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astIntersect( fr, a1, a2, b1, b2, cross );
+   fr = astAnnul( fr );
+
+}
+
+static int IsUnitFrame( AstFrame *this_frame, int *status ){
+/*
+*  Name:
+*     IsUnitFrame
+
+*  Purpose:
+*     Is this Frame equivalent to a UnitMap?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int IsUnitFrame( AstFrame *this, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astIsUnitFrame
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied Frame is
+*     equivalent to a UnitMap when treated as a Mapping (note, the Frame
+*     class inherits from Mapping and therefore every Frame is also a Mapping).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the supplied Frame is equivalent to
+*     a UnitMap when treated as a Mapping.
+
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to FrameSet's current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   int result;                   /* Result to be returned */
+
+/* Initialise the returned value. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame. */
+   fr = astGetFrame( this, AST__CURRENT );
+
+/* Invoke the astIsUnitFrame method for this Frame. */
+   result = astIsUnitFrame( fr );
+
+/* Annul the Frame pointer. */
+   fr = astAnnul( fr );
+
+/* If an error occurred, clean up by clearing the returned result. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int LineContains( AstFrame *this_frame, AstLineDef *l, int def, double *point, int *status ) {
+/*
+*  Name:
+*     LineContains
+
+*  Purpose:
+*     Determine if a line contains a point.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int LineContains( AstFrame *this, AstLineDef *l, int def, double *point, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astLineContains
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function determines if the supplied point is on the supplied
+*     line within the supplied Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     l
+*        Pointer to the structure defining the line.
+*     def
+*        Should be set non-zero if the "point" array was created by a
+*        call to astLineCrossing (in which case it may contain extra
+*        information following the axis values),and zero otherwise.
+*     point
+*        Point to an array containing the axis values of the point to be
+*        tested, possibly followed by extra cached information (see "def").
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the line contains the point.
+
+*  Notes:
+*     - The pointer supplied for "l" should have been created using the
+*     astLineDef method. These structures contained cached information about
+*     the lines which improve the efficiency of this method when many
+*     repeated calls are made. An error will be reported if the structure
+*     does not refer to the Frame specified by "this".
+*     - Zero will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   int result;                   /* Returned value */
+
+/* Initialise */
+   result =0;
+
+/* Obtain a pointer to the FrameSet's current Frame and then invoke the
+   method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( (AstFrameSet *) this_frame, AST__CURRENT );
+   result = astLineContains( fr, l, def, point );
+   fr = astAnnul( fr );
+
+/* Return the result. */
+   return result;
+}
+
+static int LineCrossing( AstFrame *this_frame, AstLineDef *l1, AstLineDef *l2,
+                         double **cross, int *status ) {
+/*
+*  Name:
+*     LineCrossing
+
+*  Purpose:
+*     Determine if two lines cross.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int LineCrossing( AstFrame *this, AstLineDef *l1, AstLineDef *l2,
+*                       double **cross, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astLineCrossing
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function determines if the two suplied line segments cross,
+*     and if so returns the axis values at the point where they cross.
+*     A flag is also returned indicating if the crossing point occurs
+*     within the length of both line segments, or outside one or both of
+*     the line segments.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     l1
+*        Pointer to the structure defining the first line.
+*     l2
+*        Pointer to the structure defining the second line.
+*     cross
+*        Pointer to a location at which to put a pointer to a dynamically
+*        alocated array containing the axis values at the crossing. If
+*        NULL is supplied no such array is returned. Otherwise, the returned
+*        array should be freed using astFree when no longer needed. If the
+*        lines are parallel (i.e. do not cross) then AST__BAD is returned for
+*        all axis values. Note usable axis values are returned even if the
+*        lines cross outside the segment defined by the start and end points
+*        of the lines. The order of axes in the returned array will take
+*        account of the current axis permutation array if appropriate. Note,
+*        sub-classes such as SkyFrame may append extra values to the end
+*        of the basic frame axis values. A NULL pointer is returned if an
+*        error occurs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the lines cross at a point which is
+*     within the [start,end) segment of both lines. If the crossing point
+*     is outside this segment on either line, or if the lines are parallel,
+*     zero is returned. Note, the start point is considered to be inside
+*     the length of the segment, but the end point is outside.
+
+*  Notes:
+*     - The pointers supplied for "l1" and "l2" should have been created
+*     using the astLineDef method. These structures contained cached
+*     information about the lines which improve the efficiency of this method
+*     when many repeated calls are made. An error will be reported if
+*     either structure does not refer to the Frame specified by "this".
+*     - Zero will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   int result;                   /* Returned value */
+
+/* Initialise */
+   result =0;
+
+/* Obtain a pointer to the FrameSet's current Frame and then invoke the
+   method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( (AstFrameSet *) this_frame, AST__CURRENT );
+   result = astLineCrossing( fr, l1, l2, cross );
+   fr = astAnnul( fr );
+
+/* Return the result. */
+   return result;
+}
+
+static AstLineDef *LineDef( AstFrame *this_frame, const double start[2],
+                            const double end[2], int *status ) {
+/*
+*  Name:
+*     LineDef
+
+*  Purpose:
+*     Creates a structure describing a line segment in a 2D Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstLineDef *LineDef( AstFrame *this, const double start[2],
+*                             const double end[2], int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astLineDef
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function creates a structure containing information describing a
+*     given line segment within the supplied 2D Frame. This may include
+*     information which allows other methods such as astLineCrossing to
+*     function more efficiently. Thus the returned structure acts as a
+*     cache to store intermediate values used by these other methods.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame. Must have 2 axes.
+*     start
+*        An array of 2 doubles marking the start of the line segment.
+*     end
+*        An array of 2 doubles marking the end of the line segment.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the memory structure containing the description of the
+*     line. This structure should be freed using astFree when no longer
+*     needed. A NULL pointer is returned (without error) if any of the
+*     supplied axis values are AST__BAD.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstLineDef *result;           /* Returned value */
+
+/* Initialise */
+   result = NULL;
+
+/* Obtain a pointer to the FrameSet's current Frame and then invoke the
+   method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( (AstFrameSet *) this_frame, AST__CURRENT );
+   result = astLineDef( fr, start, end );
+   fr = astAnnul( fr );
+
+/* Return the result. */
+   return result;
+}
+
+static void LineOffset( AstFrame *this_frame, AstLineDef *line, double par,
+                        double prp, double point[2], int *status ){
+/*
+*  Name:
+*     LineOffset
+
+*  Purpose:
+*     Find a position close to a line.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     void LineOffset( AstFrame *this, AstLineDef *line, double par,
+*                      double prp, double point[2], int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astLineOffset
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a position formed by moving a given distance along
+*     the supplied line, and then a given distance away from the supplied line.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     line
+*        Pointer to the structure defining the line.
+*     par
+*        The distance to move along the line from the start towards the end.
+*     prp
+*        The distance to move at right angles to the line. Positive
+*        values result in movement to the left of the line, as seen from
+*        the observer, when moving from start towards the end.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The pointer supplied for "line" should have been created using the
+*     astLineDef method. This structure contains cached information about the
+*     line which improves the efficiency of this method when many repeated
+*     calls are made. An error will be reported if the structure does not
+*     refer to the Frame specified by "this".
+*/
+
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+
+/* Obtain a pointer to the FrameSet's current Frame and then invoke the
+   method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( (AstFrameSet *) this_frame, AST__CURRENT );
+   astLineOffset( fr, line, par, prp, point );
+   fr = astAnnul( fr );
+}
+
+static const char *GetAllVariants( AstFrameSet *this, int *status ) {
+/*
+*  Name:
+*     GetAllVariants
+
+*  Purpose:
+*     Get a pointer to a list of the variant Mappings for the current Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     const char *getAllVariants( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet member function.
+
+*  Description:
+*     This function returns a space separated list of names for all the
+*     variant Mappings associated with the current Frame. See attribute
+*     "Variant". If the current Frame has no variant Mappings, the return
+*     value contains just the Domain name of the current Frame in the
+*     supplied FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the list.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the FrameSet, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Frame. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   AstFrame *frm;
+   AstFrame *vfrm;
+   AstFrameSet *vfs;
+   const char *dom;
+   const char *result;
+   int ifrm;
+   int nc;
+   int icur;
+   int nfrm;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS( this );
+
+/* Get the one-based index of the Frame that defines the available
+   variant Mappings. */
+   icur = GetVarFrm( this, astGetCurrent( this ), status );
+
+/* Get the variants FrameSet from the Frame selected above. */
+   frm = astGetFrame( this, icur );
+   vfs = astGetFrameVariants( frm );
+
+/* If the Frame does not have a variants FrameSet, just return the DOmain
+   name from the current Frame. */
+   if( !vfs ) {
+      result = astGetDomain( this );
+
+/* If a variants FrameSet was found, form a space sperated list of the
+   Domain names in the FrameSet, stored in the static "getallvariants_buff"
+   string. */
+   } else if( astOK ){
+      nc = 0;
+
+      nfrm = astGetNframe( vfs );
+      for( ifrm = 0; ifrm < nfrm; ifrm++ ) {
+         vfrm = astGetFrame( vfs, ifrm + 1 );
+         dom = astGetDomain( vfrm );
+         if( astOK ){
+            if( ( nc + strlen(dom) + 1 ) < GETALLVARIANTS_BUFF_LEN ) {
+               nc += sprintf( getallvariants_buff + nc, "%s ", dom  );
+            } else {
+               astError( AST__INTER, "astGetAllVariants(%s): Buffer "
+                         "overflow - too many variants.", status,
+                         astGetClass(this) );
+            }
+         }
+         vfrm = astAnnul( vfrm );
+      }
+
+/* Remove the final space. */
+      getallvariants_buff[ nc - 1 ] = 0;
+
+/* Return a pointer to the buffer. */
+      result = getallvariants_buff;
+
+/* Annul the pointer to the variants FrameSet. */
+      vfs = astAnnul( vfs );
+   }
+
+/* Free the pointer to the current Frame. */
+   frm = astAnnul( frm );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstFrameSet *this;    /* Pointer to FrameSet structure */
+   int i;                /* Loop count */
+   int result;           /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the FrameSet structure. */
+   this = (AstFrameSet *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   for( i = 0; i < this->nframe; i++ ) {
+      if( !result ) result = astManageLock( this->frame[ i ], mode,
+                                            extra, fail );
+   }
+
+   for ( i = 0; i < this->nnode - 1; i++ ) {
+      if( !result ) result = astManageLock( this->map[ i ], mode, extra,
+                                            fail );
+   }
+
+   return result;
+
+}
+#endif
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     FrameSet method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing FrameSet. This is only possible if the specified inputs
+*     correspond to some subset of the FrameSet outputs. That is, there
+*     must exist a subset of the FrameSet outputs for which each output
+*     depends only on the selected FrameSet inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied FrameSet, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet to be split (the FrameSet is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied FrameSet, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied FrameSet has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied FrameSet. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstMapping *bcmap;   /* Base->current Mapping */
+   int *result;         /* Returned pointer */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the Mapping from base to current Frame and try to split it. */
+   bcmap = astGetMapping( (AstFrameSet *) this_map, AST__BASE, AST__CURRENT );
+   result = astMapSplit( bcmap, nin, in, map );
+   bcmap = astAnnul( bcmap );
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static int Match( AstFrame *this_frame, AstFrame *target, int matchsub,
+                  int **template_axes, int **target_axes,
+                  AstMapping **map, AstFrame **result, int *status ) {
+/*
+*  Name:
+*     Match
+
+*  Purpose:
+*     Determine if conversion is possible between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int Match( AstFrame *template, AstFrame *target, int matchsub,
+*                int **template_axes, int **target_axes,
+*                AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astMatch
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function matches the current Frame of a "template" FrameSet
+*     to a "target" frame and determines whether it is possible to
+*     convert coordinates between them.  If it is, a Mapping that
+*     performs the transformation is returned along with a new Frame
+*     that describes the coordinate system that results when this
+*     Mapping is applied to the current Frame of the target
+*     FrameSet. In addition, information is returned to allow the axes
+*     in this "result" Frame to be associated with the corresponding
+*     axes in the target and template Frames from which they are
+*     derived.
+
+*  Parameters:
+*     template
+*        Pointer to the template FrameSet, whose current Frame
+*        describes the coordinate system (or set of possible
+*        coordinate systems) into which we wish to convert our
+*        coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate
+*        system in which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case (i.e. if the
+*        target is of a more specialised class than the template). In
+*        this latter case, the target is cast down to the class of the
+*        template. NOTE, this argument is handled by the global method
+*        wrapper function "astMatch_", rather than by the class-specific
+*        implementations of this method.
+*     template_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the index of the axis in the
+*        template FrameSet's current Frame from which it is
+*        derived. If it is not derived from any template FrameSet
+*        axis, a value of -1 will be returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the index of the target Frame axis
+*        from which it is derived. If it is not derived from any
+*        target Frame axis, a value of -1 will be returned instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will
+*        be returned if the requested coordinate conversion is
+*        possible. If returned, the forward transformation of this
+*        Mapping may be used to convert coordinates between the target
+*        Frame and the result Frame (see below) and the inverse
+*        transformation will convert in the opposite direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be
+*        returned if the requested coordinate conversion is
+*        possible. If returned, this Frame describes the coordinate
+*        system that results from applying the returned Mapping
+*        (above) to the "target" coordinate system. In general, this
+*        Frame will combine attributes from (and will therefore be
+*        more specific than) both the target Frame and the current
+*        Frame of the template FrameSet. In particular, when the
+*        template allows the possibility of transformaing to any one
+*        of a set of alternative coordinate systems, the "result"
+*        Frame will indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate
+*     conversion is possible. Otherwise zero is returned (this will
+*     not in itself result in an error condition).
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to FrameSet's current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   int match;                    /* Result to be returned */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame. */
+   fr = astGetFrame( this, AST__CURRENT );
+
+/* Invoke the astMatch method for this Frame. */
+   match =astMatch( fr, target, matchsub, template_axes, target_axes,
+                    map, result );
+
+/* Annul the Frame pointer. */
+   fr = astAnnul( fr );
+
+/* If an error occurred, clean up by freeing any allocated memory,
+   annulling returned objects and clearing the returned result. */
+   if ( !astOK ) {
+      *template_axes = astFree( *template_axes );
+      *target_axes = astFree( *target_axes );
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static void MatchAxes( AstFrame *frm1_frame, AstFrame *frm2, int *axes,
+                       int *status ) {
+/*
+*  Name:
+*     MatchAxes
+
+*  Purpose:
+*     Find any corresponding axes in two Frames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void MatchAxes( AstFrame *frm1, AstFrame *frm2, int *axes )
+*                     int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astMatchAxes
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function looks for corresponding axes within two supplied
+*     Frames. An array of integers is returned that contains an element
+*     for each axis in the second supplied Frame. An element in this array
+*     will be set to zero if the associated axis within the second Frame
+*     has no corresponding axis within the first Frame. Otherwise, it
+*     will be set to the index (a non-zero positive integer) of the
+*     corresponding axis within the first supplied Frame.
+
+*  Parameters:
+*     frm1
+*        Pointer to the first Frame.
+*     frm2
+*        Pointer to the second Frame.
+*     axes
+*        Pointer to an
+*        integer array in which to return the indices of the axes (within
+*        the second Frame) that correspond to each axis within the first
+*        Frame. Axis indices start at 1. A value of zero will be stored
+*        in the returned array for each axis in the first Frame that has
+*        no corresponding axis in the second Frame.
+*
+*        The number of elements in this array must be greater than or
+*        equal to the number of axes in the first Frame.
+*     status
+*        Pointer to inherited status value.
+
+*  Notes:
+*     -  Corresponding axes are identified by the fact that a Mapping
+*     can be found between them using astFindFrame or astConvert. Thus,
+*     "corresponding axes" are not necessarily identical. For instance,
+*     SkyFrame axes in two Frames will match even if they describe
+*     different celestial coordinate systems
+*/
+
+/* Local Variables: */
+   AstFrame *frm1;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the current Frame in the FrameSet. */
+   frm1 = astGetFrame( (AstFrameSet *) frm1_frame, AST__CURRENT );
+
+/* Invoke the astMatchAxesX on the second Frame. */
+   astMatchAxesX( frm2, frm1, axes );
+
+/* Free resources */
+   frm1 = astAnnul( frm1 );
+}
+
+static void MatchAxesX( AstFrame *frm2_frame, AstFrame *frm1, int *axes,
+                        int *status ) {
+/*
+*  Name:
+*     MatchAxesX
+
+*  Purpose:
+*     Find any corresponding axes in two Frames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void MatchAxesX( AstFrame *frm2, AstFrame *frm1, int *axes )
+*                      int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astMatchAxesX
+*     method inherited from the Frame class).
+
+*     This function looks for corresponding axes within two supplied
+*     Frames. An array of integers is returned that contains an element
+*     for each axis in the second supplied Frame. An element in this array
+*     will be set to zero if the associated axis within the second Frame
+*     has no corresponding axis within the first Frame. Otherwise, it
+*     will be set to the index (a non-zero positive integer) of the
+*     corresponding axis within the first supplied Frame.
+
+*  Parameters:
+*     frm2
+*        Pointer to the second Frame.
+*     frm1
+*        Pointer to the first Frame.
+*     axes
+*        Pointer to an integer array in which to return the indices of
+*        the axes (within the first Frame) that correspond to each axis
+*        within the second Frame. Axis indices start at 1. A value of zero
+*        will be stored in the returned array for each axis in the second
+*        Frame that has no corresponding axis in the first Frame.
+*
+*        The number of elements in this array must be greater than or
+*        equal to the number of axes in the second Frame.
+*     status
+*        Pointer to inherited status value.
+
+*  Notes:
+*     -  Corresponding axes are identified by the fact that a Mapping
+*     can be found between them using astFindFrame or astConvert. Thus,
+*     "corresponding axes" are not necessarily identical. For instance,
+*     SkyFrame axes in two Frames will match even if they describe
+*     different celestial coordinate systems
+*/
+
+/* Local Variables: */
+   AstFrame *frm2;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the current Frame in the FrameSet. */
+   frm2 = astGetFrame( (AstFrameSet *) frm2_frame, AST__CURRENT );
+
+/* Invoke the astMatchAxesX on the current Frame. */
+   astMatchAxesX( frm2, frm1, axes );
+
+/* Free resources */
+   frm2 = astAnnul( frm2 );
+}
+
+static void MirrorVariants( AstFrameSet *this, int iframe, int *status ) {
+/*
+*++
+*  Name:
+c     astMirrorVariants
+f     AST_MIRRORVARIANTS
+
+*  Purpose:
+*     Make the current Frame mirror the variant Mappings in another Frame.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     void astMirrorVariants( AstFrameSet *this, int iframe )
+f     CALL AST_MIRRORVARIANTS( THIS, IFRAME, STATUS )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+c     This function
+f     This routine
+*     indicates that all access to the Variant attribute of the current
+*     Frame should should be forwarded to some other nominated Frame in
+*     the FrameSet. For instance, if a value is set subsequently for the
+*     Variant attribute of the current Frame, the current Frame will be left
+*     unchanged and the setting is instead applied to the nominated Frame.
+*     Likewise, if the value of the Variant attribute is requested, the
+*     value returned is the value stored for the nominated Frame rather
+*     than the current Frame itself.
+*
+*     This provides a mechanism for propagating the effects of variant
+*     Mappings around a FrameSet. If a new Frame is added to a FrameSet
+*     by connecting it to an pre-existing Frame that has two or more variant
+*     Mappings, then it may be appropriate to set the new Frame so that it
+*     mirrors the variants Mappings of the pre-existing Frame. If this is
+*     done, then it will be possible to select a specific variant Mapping
+*     using either the pre-existing Frame or the new Frame.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FrameSet.
+c     iframe
+f     IFRAME = INTEGER (Given)
+*        The index of the Frame within the FrameSet which is to be
+*        mirrored by the current Frame. This value should lie in the range
+*        from 1 to the number of Frames in the FrameSet (as given by its
+*        Nframe attribute). If AST__NOFRAME is supplied (or the current
+*        Frame is specified), then any mirroring established by a previous
+*        call to this
+c        function
+f        routine
+*        is disabled.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Mirrors can be chained. That is, if Frame B is set to be a mirror
+*     of Frame A, and Frame C is set to be a mirror of Frame B, then
+*     Frame C will act as a mirror of Frame A.
+*     - Variant Mappings cannot be added to the current Frame if it is
+*     mirroring another Frame. So calls to the
+c     astAddVariant function
+f     AST_ADDVARIANT routine
+*     will cause an error to be reported if the current Frame is
+*     mirroring another Frame.
+*     - A value of AST__BASE may be given for the
+c     "iframe" parameter
+f     IFRAME argument
+*     to specify the base Frame.
+*     - Any variant Mappings explicitly added to the current Frame using
+c     astAddVariant
+f     AST_ADDVARIANT
+*     will be ignored if the current Frame is mirroring another Frame.
+
+*--
+*/
+
+/* Local Variables: */
+   int icur;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the current Frame index. */
+   icur = astGetCurrent( this );
+
+/* If AST__NOFRAME, disable any mirroring. */
+   if( iframe == AST__NOFRAME ) {
+      this->varfrm[ icur - 1 ] = 0;
+
+/* Otherwise, validate and translate the Frame index supplied. */
+   } else {
+      iframe = astValidateFrameIndex( this, iframe, "astMirrorVariants" );
+
+/* If the current Frame has been specified, disable any mirroring. */
+      if( iframe == icur ) {
+         this->varfrm[ icur - 1 ] = 0;
+
+/* Otherwise, store the one-based variants frame index. */
+      } else {
+         this->varfrm[ icur - 1 ] = iframe;
+      }
+   }
+}
+
+static void Norm( AstFrame *this_frame, double value[], int *status ) {
+/*
+*  Name:
+*     Norm
+
+*  Purpose:
+*     Normalise a set of FrameSet coordinates.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void Norm( AstAxis *this, double value[], int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astNorm method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a set of coordinate values for the
+*     current Frame of a FrameSet, which might potentially be
+*     unsuitable for display to a user (for instance, may lie outside
+*     the expected range of values) into a set of acceptable
+*     alternative values suitable for display.
+*
+*     Typically, for Frames whose axes represent cyclic values (such
+*     as angles or positions on the sky), this function wraps an
+*     arbitrary set of coordinates, so that they lie within the first
+*     cycle (say zero to 2*pi or -pi/2 to +pi/2). For Frames with
+*     ordinary linear axes, without constraints, this function will
+*     typically return the original coordinate values unchanged.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     value
+*        An array of double, with one element for each FrameSet axis.
+*        This should contain the initial set of coordinate values,
+*        which will be modified in place.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to the current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astNorm method to obtain the new values. Annul the Frame
+   pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astNorm( fr, value );
+   fr = astAnnul( fr );
+}
+
+static void NormBox( AstFrame *this_frame, double lbnd[], double ubnd[],
+                     AstMapping *reg, int *status ) {
+/*
+*  Name:
+*     NormBox
+
+*  Purpose:
+*     Extend a box to include effect of any singularities in the Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void astNormBox( AstFrame *this, double lbnd[], double ubnd[],
+*                      AstMapping *reg, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astNormBox method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function modifies a supplied box to include the effect of any
+*     singularities in the co-ordinate system represented by the Frame.
+*     For a normal Cartesian coordinate system, the box will be returned
+*     unchanged. Other classes of Frame may do other things. For instance,
+*     a SkyFrame will check to see if the box contains either the north
+*     or south pole and extend the box appropriately.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     lbnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        lower axis bounds for the box. They will be modified on exit
+*        to include the effect of any singularities within the box.
+*     ubnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        upper axis bounds for the box. They will be modified on exit
+*        to include the effect of any singularities within the box.
+*     reg
+*        A Mapping which should be used to test if any singular points are
+*        inside or outside the box. The Mapping should leave an input
+*        position unchanged if the point is inside the box, and should
+*        set all bad if the point is outside the box.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to the current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astNormBox method to obtain the new values. Annul the Frame
+   pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astNormBox( fr, lbnd, ubnd, reg );
+   fr = astAnnul( fr );
+}
+
+static void Offset( AstFrame *this_frame, const double point1[],
+                    const double point2[], double offset, double point3[], int *status ) {
+/*
+*  Name:
+*     Offset
+
+*  Purpose:
+*     Calculate an offset along a geodesic curve.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void Offset( AstFrame *this,
+*                  const double point1[], const double point2[],
+*                  double offset, double point3[], int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astOffset
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function finds the FrameSet coordinate values of a point
+*     which is offset a specified distance along the geodesic curve
+*     between two other points.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     point1
+*        An array of double, with one element for each FrameSet axis.
+*        This should contain the coordinates of the point marking the
+*        start of the geodesic curve.
+*     point2
+*        An array of double, with one element for each FrameSet axis
+*        This should contain the coordinates of the point marking the
+*        end of the geodesic curve.
+*     offset
+*        The required offset from the first point along the geodesic
+*        curve. If this is positive, it will be towards the second
+*        point. If it is negative, it will be in the opposite
+*        direction. This offset need not imply a position lying
+*        between the two points given, as the curve will be
+*        extrapolated if necessary.
+*     point3
+*        An array of double, with one element for each FrameSet axis
+*        in which the coordinates of the required point will be
+*        returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*     - "Bad" coordinate values will also be returned if the two
+*     points supplied are coincident (or otherwise fail to uniquely
+*     specify a geodesic curve) but the requested offset is non-zero.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astOffset method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astOffset( fr, point1, point2, offset, point3 );
+   fr = astAnnul( fr );
+}
+
+static double Offset2( AstFrame *this_frame, const double point1[2],
+                       double angle, double offset, double point2[2], int *status ){
+/*
+*  Name:
+*     Offset2
+
+*  Purpose:
+*     Calculate an offset along a geodesic curve in a 2D Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     double Offset2( AstFrame *this, const double point1[2], double angle,
+*                     double offset, double point2[2], int *status );
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astOffset2
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function finds the Frame coordinate values of a point which
+*     is offset a specified distance along the geodesic curve at a
+*     given angle from a specified starting point. It can only be
+*     used with 2-dimensional Frames.
+*
+*     For example, in a basic Frame, this offset will be along the
+*     straight line joining two points. For a more specialised Frame
+*     describing a sky coordinate system, however, it would be along
+*     the great circle passing through two sky positions.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This should contain the coordinates of the
+*        point marking the start of the geodesic curve.
+*     angle
+*        The angle (in radians) from the positive direction of the second
+*        axis, to the direction of the required position, as seen from
+*        the starting position. Positive rotation is in the sense of
+*        rotation from the positive direction of axis 2 to the positive
+*        direction of axis 1.
+*     offset
+*        The required offset from the first point along the geodesic
+*        curve. If this is positive, it will be in the direction of the
+*        given angle. If it is negative, it will be in the opposite
+*        direction.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        in which the coordinates of the required point will be returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The direction of the geodesic curve at the end point. That is, the
+*     angle (in radians) between the positive direction of the second
+*     axis and the continuation of the geodesic curve at the requested
+*     end point. Positive rotation is in the sense of rotation from
+*     the positive direction of axis 2 to the positive direction of axis 1.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - An error will be reported if the Frame is not 2-dimensional.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astOffset2 method for this Frame. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astOffset2( fr, point1, angle, offset, point2 );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static void Overlay( AstFrame *template_frame, const int *template_axes,
+                     AstFrame *result, int *status ) {
+/*
+*  Name:
+*     Overlay
+
+*  Purpose:
+*     Overlay the attributes of a template FrameSet on to another Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void Overlay( AstFrame *template, const int *template_axes,
+*                   AstFrame *result, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astOverlay
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function overlays attributes from the current Frame of a
+*     FrameSet on to another Frame, so as to over-ride selected
+*     attributes of that second Frame. Normally only those attributes
+*     which have been specifically set in the template will be
+*     transferred. This implements a form of defaulting, in which a
+*     Frame acquires attributes from the template, but retains its
+*     original attributes (as the default) if new values have not
+*     previously been explicitly set in the template.
+
+*  Parameters:
+*     template
+*        Pointer to the template FrameSet, for whose current Frame
+*        values should have been explicitly set for any attribute
+*        which is to be transferred.
+*     template_axes
+*        Pointer to an array of int, with one element for each axis of
+*        the "result" Frame (see below). For each axis in the result
+*        frame, the corresponding element of this array should contain
+*        the (zero-based) index of the axis in the current Frame of
+*        the template FrameSet to which it corresponds. This array is
+*        used to establish from which template Frame axis any
+*        axis-dependent attributes should be obtained.
+*
+*        If any axis in the result Frame is not associated with a
+*        template Frame axis, the corresponding element of this array
+*        should be set to -1.
+*
+*        If a NULL pointer is supplied, the template and result axis
+*        indices are assumed to be identical.
+*     result
+*        Pointer to the Frame which is to receive the new attribute values.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *template;        /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   template = (AstFrameSet *) template_frame;
+
+/* Obtain a pointer to the current Frame and invoke its astOverlay
+   method to overlay its attributes. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( template, AST__CURRENT );
+   astOverlay( fr, template_axes, result );
+   fr = astAnnul( fr );
+}
+
+static void PermAxes( AstFrame *this_frame, const int perm[], int *status ) {
+/*
+*  Name:
+*     PermAxes
+
+*  Purpose:
+*     Permute the order of a FrameSet's axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void PermAxes( AstFrame *this, const int perm[], int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astPermAxes method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function permutes the order in which the axes in the
+*     current Frame of a FrameSet occur.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     perm
+*        An array of int (with one element for each axis of the
+*        FrameSet's current Frame) which lists the axes in their new
+*        order. Each element of this array should be a (zero-based)
+*        axis index identifying the axes according to their old
+*        (un-permuted) order.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Only genuine permutations of the axis order are permitted, so
+*     each axis must be referenced exactly once in the "perm" array.
+*     - If more than one axis permutation is applied to the same Frame
+*     in a FrameSet, the effects are cumulative.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   AstPermMap *map;              /* Pointer to axis permutation Mapping */
+   int *invperm;                 /* Pointer to inverse permutation array */
+   int axis;                     /* Loop counter for axes */
+   int naxes;                    /* Number of FrameSet axes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the permutation array, to check that it describes a
+   genuine permutation. */
+   astCheckPerm( this, perm, "astPermAxes" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astPermAxes method to permute its axes. Annul the Frame
+   pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astPermAxes( fr, perm );
+   fr = astAnnul( fr );
+
+/* Obtain the number of axes in the FrameSet's current Frame and allocate
+   memory to hold an inverse permutation array. */
+   naxes =  astGetNaxes( this );
+   invperm = astMalloc( sizeof( int ) * (size_t) naxes );
+
+/* Fill the inverse permutation array with values that will invert the
+   axis permutation supplied. */
+   if ( astOK ) {
+      for ( axis = 0; axis < naxes; axis++ ) invperm[ perm[ axis ] ] = axis;
+
+/* Create a PermMap that will permute coordinate values in the same way as
+   the current Frame's axes have been permuted. */
+      map = astPermMap( naxes, invperm, naxes, perm, NULL, "", status );
+
+/* Modify the Frame's relationship to the rest of the Frames in the
+   FrameSet so that the correct coordinate values remain associated
+   with the permuted axes. */
+      astRemapFrame( this, AST__CURRENT, map );
+
+/* Annul the PermMap and free the inverse permutation array. */
+      map = astAnnul( map );
+   }
+   invperm = astFree( invperm );
+}
+
+static AstFrame *PickAxes( AstFrame *this_frame, int naxes, const int axes[],
+                           AstMapping **map, int *status ) {
+/*
+*  Name:
+*     PickAxes
+
+*  Purpose:
+*     Create a new Frame by picking axes from a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstFrame *PickAxes( AstFrame *this, int naxes, const int axes[],
+*                         AstMapping **map, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astPickAxes protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function creates a new Frame whose axes are copies of axes
+*     picked from the current Frame of an existing FrameSet. Other
+*     Frame attributes are also copied from this current Frame to the
+*     new Frame. Zero or more of the original axes may be picked in
+*     any order, but each can be used only once. Additional axes (with
+*     default characteristics) may be included in the new Frame if
+*     required.
+*
+*     Optionally, a Mapping that converts between the original Frame's
+*     axes and those of the new Frame may also be returned.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     naxes
+*        The number of axes required in the new Frame.
+*     axes
+*        Pointer to an array of int with naxes elements. This should
+*        contain (zero based) axis indices specifying the axes which
+*        are to be included in the new Frame, in the order
+*        required. Each axis index may occur only once.
+*
+*        If additional (default) axes are also to be included, the
+*        corresponding elements of this array should be set to -1.
+*     map
+*        Address of a location to receive a pointer to a new
+*        Mapping. This will be a PermMap (or a UnitMap as a special
+*        case) that describes the axis permutation that has taken
+*        place between the current Frame of the FrameSet and the new
+*        Frame.  The forward transformation will convert from the
+*        original FrameSet's axes to the new one's, and vice versa.
+*
+*        If this Mapping is not required, a NULL value may be supplied
+*        for this parameter.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the new Frame.
+
+*  Notes:
+*     - The class of object returned may differ from that of the
+*     original current Frame, depending on which axes are
+*     selected. For example, if a single axis is picked from a
+*     SkyFrame (which always has two axes), the resulting Frame cannot
+*     be a valid SkyFrame, so will revert to the parent class (Frame)
+*     instead.
+*     - The new Frame contains a deep copy of all the data selected
+*     from the original current Frame. Modifying the new Frame will
+*     therefore not affect the FrameSet or the Frames it contains.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrame *frame;              /* Pointer to Frame to be returned */
+   AstFrameSet *this;            /* Pointer to FrameSet structure */
+
+/* Initialise the returned pointers. */
+   if ( map ) *map = NULL;
+   frame = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return frame;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Check that a valid set of axes is being selected . */
+   astValidateAxisSelection( this, naxes, axes, "astPickAxes" );
+
+/* Obtain a pointer to the FrameSet's current Frame and use its
+   astPickAxes method to obtain the required new Frame and
+   Mapping. Annul the current Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   frame = astPickAxes( fr, naxes, axes, map );
+   fr = astAnnul( fr );
+
+/* If an error occurred, annul the Mapping pointer (if requested) and
+   the new Frame pointer. */
+   if ( !astOK ) {
+      if ( map ) *map = astAnnul( *map );
+      frame = astAnnul( frame );
+   }
+
+/* Return the pointer to the new Frame. */
+   return frame;
+}
+
+static void PrimaryFrame( AstFrame *this_frame, int axis1,
+                          AstFrame **frame, int *axis2, int *status ) {
+/*
+*  Name:
+*     PrimaryFrame
+
+*  Purpose:
+*     Uniquely identify a primary Frame and one of its axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void PrimaryFrame( AstFrame *this, int axis1, AstFrame **frame,
+*                        int *axis2, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected
+*     astPrimaryFrame method inherited from the Frame class).
+
+*  Description:
+*     This function returns information about the underlying (primary)
+*     Frame corresponding to a specified axis of the current Frame of
+*     a FrameSet, when this current Frame may be a compound Frame
+*     composed of more than one simpler one.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     axis1
+*        An axis index (zero-based) identifying the axis of the
+*        FrameSet's current Frame for which information is required.
+*     frame
+*        Address of a location to receive a pointer to the underlying
+*        (primary) frame to which the requested axis belongs
+*        (i.e. this will not be a compound Frame).
+*     axis2
+*        Pointer to an int which is to receive the axis index within
+*        "frame" which identifies the axis being referred to, using
+*        the axis order that applied when the primary Frame was
+*        originally constructed (i.e. this function undoes all
+*        subsequent axis pemutations and the effects of combining
+*        Frames, in order to reveal the original underlying axis
+*        order).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This protected method is provided so that class
+*     implementations can distinguish the axes of Frames from one
+*     another (e.g. can distinguish a longitude axis as being
+*     different from a latitide axis) even after their order has been
+*     permuted and they have been combined with axes from other
+*     Frames.
+*     - The reference count of the primary Frame will be incremented
+*     by one to reflect the new pointer returned.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Initialise the returned values. */
+   *frame = NULL;
+   *axis2 = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index supplied. */
+   (void) astValidateAxis( this, axis1, 1, "astPrimaryFrame" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke its
+   astPrimaryFrame method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astPrimaryFrame( fr, axis1, frame, axis2 );
+   fr = astAnnul( fr );
+
+/* If an error occurred, annul the returned object and clear the
+   returned axis value. */
+   if ( !astOK ) {
+      *frame = astAnnul( *frame );
+      *axis2 = 0;
+   }
+}
+
+static double Rate( AstMapping *this_mapping, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astRate method
+*     inherited from the Frame class).
+
+*     This function evaluates the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+*
+*     The result is estimated by interpolating the function using a
+*     fourth order polynomial in the neighbourhood of the specified
+*     position. The size of the neighbourhood used is chosen to minimise
+*     the RMS residual per unit length between the interpolating
+*     polynomial and the supplied Mapping function.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     astRate()
+*        The rate of change of Mapping output "ax1" with respect to input
+*        "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*        calculated.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   AstMapping *map;              /* Pointer to the base->current Mapping */
+   double result;                /* Returned rate of change */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_mapping;
+
+/* Obtain the Mapping between the base and current Frames in the
+   FrameSet (note this takes account of whether the FrameSet has been
+   inverted). */
+   map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Invoke the astRate method on the Mapping. */
+   result = astRate( map, at, ax1, ax2 );
+
+/* Annul the Mapping pointer. */
+   map = astAnnul( map );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static void RecordIntegrity( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     RecordIntegrity
+
+*  Purpose:
+*     Record the current integrity state of a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void RecordIntegrity( AstFrameSet *this, int *status )
+
+*  Class Membership:
+*     FrameSet member function.
+
+*  Description:
+*     This function makes a record of the current integrity state of a
+*     FrameSet by taking a copy of its current Frame (it stores a
+*     pointer to this copy in a static variable). If the current Frame
+*     is subsequently modified, the RestoreIntegrity function can then
+*     attempt to restore the FrameSet's integrity to this recorded
+*     state by appropriately remapping its current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*-
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFrame *current;            /* Pointer to current Frame */
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise the record of the FrameSet's integrity. */
+   integrity_frame = NULL;
+   integrity_lost = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet's current Frame. */
+   current = astGetFrame( this, AST__CURRENT );
+
+/* Make a copy of this Frame, storing its pointer. */
+   integrity_frame = astCopy( current );
+
+/* Annul the current Frame pointer. */
+   current = astAnnul( current );
+}
+
+static void RemapFrame( AstFrameSet *this, int iframe, AstMapping *map, int *status ) {
+/*
+*++
+*  Name:
+c     astRemapFrame
+f     AST_REMAPFRAME
+
+*  Purpose:
+*     Modify a Frame's relationship to other Frames in a FrameSet.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     void astRemapFrame( AstFrameSet *this, int iframe, AstMapping *map )
+f     CALL AST_REMAPFRAME( THIS, IFRAME, MAP, STATUS )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+c     This function modifies the relationship (i.e. Mapping) between a
+f     This routine modifies the relationship (i.e. Mapping) between a
+*     specified Frame in a FrameSet and the other Frames in that
+*     FrameSet.
+*
+*     Typically, this might be required if the FrameSet has been used
+*     to calibrate (say) an image, and that image is re-binned. The
+*     Frame describing the image will then have undergone a coordinate
+*     transformation, and this should be communicated to the associated
+c     FrameSet using this function.
+f     FrameSet using this routine.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FrameSet.
+c     iframe
+f     IFRAME = INTEGER (Given)
+*        The index within the FrameSet of the Frame to be modified.
+*        This value should lie in the range from 1 to the number of
+*        Frames in the FrameSet (as given by its Nframe attribute).
+c     map
+f     MAP = INTEGER (Given)
+*        Pointer to a Mapping whose forward transformation converts
+*        coordinate values from the original coordinate system
+*        described by the Frame to the new one, and whose inverse
+*        transformation converts in the opposite direction.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - A value of AST__BASE or AST__CURRENT may be given for the
+c     "iframe" parameter to specify the base Frame or the current
+f     IFRAME argument to specify the base Frame or the current
+*     Frame respectively.
+*     - The relationship between the selected Frame and any other
+c     Frame within the FrameSet will be modified by this function,
+f     Frame within the FrameSet will be modified by this routine,
+*     but the relationship between all other Frames in the FrameSet
+*     remains unchanged.
+*     - The number of input coordinate values accepted by the Mapping
+*     (its Nin attribute) and the number of output coordinate values
+*     generated (its Nout attribute) must be equal and must match the
+*     number of axes in the Frame being modified.
+*     - If a simple change of axis order is required, then the
+c     astPermAxes function may provide a more straightforward method
+f     AST_PERMAXES routine may provide a more straightforward method
+*     of making the required changes to the FrameSet.
+c     - This function cannot be used to change the number of Frame
+f     - This routine cannot be used to change the number of Frame
+*     axes. To achieve this, a new Frame must be added to the FrameSet
+c     (astAddFrame) and the original one removed if necessary
+c     (astRemoveFrame).
+f     (AST_ADDFRAME) and the original one removed if necessary
+f     (AST_REMOVEFRAME).
+*     - Any variant Mappings associated with the remapped Frame (except
+*     for the current variant) will be lost as a consequence of calling this
+*     method (see attribute "Variant").
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to Frame */
+   int icur;                     /* Index of original current Frame */
+   int naxes;                    /* Number of Frame axes */
+   int nin;                      /* Number of Mapping input coordinates */
+   int nout;                     /* Number of Mapping output coordinates */
+   int varfrm;                   /* The index of the variants frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate and translate the Frame index supplied. */
+   iframe = astValidateFrameIndex( this, iframe, "astRemapFrame" );
+
+/* Variant Mappings from a source node to a destination node are stored
+   within the Frame object associated with the destination node. But
+   remapping a Frame causes the Frame to be dissociated from its original
+   node, and associated with a new node, leaving the original node
+   without any Frame in which to store its variant mappings. So we are
+   forced to remove the variant Mappings if the Frame is remapped. We do
+   this by clearing the Variant attribute before the Frame is remapped.
+   This will leave the current variant as the sole Mapping between the
+   original source and destination nodes. However, if the Frame being
+   remapped is just a mirror for another Frame, then we do not need to
+   do this since the Frame being mirrored is not itself being remapped
+   and so can retain its variant mappings. So we temporarily prevent the
+   remapped Frame from acting as a mirror before we clear the Variant
+   attribute. */
+   icur = astGetCurrent( this );
+   astSetCurrent( this, iframe );
+
+   varfrm = this->varfrm[ iframe - 1 ];
+   this->varfrm[ iframe - 1 ] = 0;
+
+   astClearVariant( this );
+
+   this->varfrm[ iframe - 1 ] = varfrm;
+   astSetCurrent( this, icur );
+
+/* Obtain the number of input and output coordinates per point for the
+   Mapping supplied. */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+
+/* Obtain a pointer to the specified Frame and determine how many axes
+   it has. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, iframe );
+   naxes = astGetNaxes( fr );
+   fr = astAnnul( fr );
+
+/* Check that the number of input coordinates matches the number of
+   Frame axes and report an error if necessary. */
+   if ( astOK ) {
+      if ( nin != naxes ) {
+         astError( AST__NCPIN, "astRemapFrame(%s): Bad number of %s input "
+                   "coordinate values (%d).", status, astGetClass( this ),
+                   astGetClass( map ), nin );
+         astError( AST__NCPIN, "The %s given should accept %d coordinate "
+                   "value%s for each input point.", status, astGetClass( map ), naxes,
+                   ( naxes == 1 ) ? "" : "s" );
+
+/* Similarly, check that the number of output coordinates matches the
+   number of Frame axes. */
+      } else if ( nout != naxes ) {
+         astError( AST__NCPIN, "astRemapFrame(%s): Bad number of %s output "
+                   "coordinate values (%d).", status, astGetClass( this ),
+                   astGetClass( map ), nout );
+         astError( AST__NCPIN, "The %s given should generate %d "
+                   "coordinate value%s for each output point.", status,
+                   astGetClass( map ), naxes, ( naxes == 1 ) ? "" : "s" );
+      }
+   }
+
+/* If there is more than one Frame present in the FrameSet, extend the
+   FrameSet arrays to hold a new node. */
+   if ( astOK && ( this->nframe > 1 ) ) {
+      this->map = astGrow( this->map, this->nnode, sizeof( AstMapping * ) );
+      this->link = astGrow( this->link, this->nnode, sizeof( int ) );
+      this->invert = astGrow( this->invert, this->nnode, sizeof( int ) );
+
+/* Clone and store a pointer to the Mapping. */
+      if ( astOK ) {
+         this->map[ this->nnode - 1 ] = astClone( map );
+
+/* Add a new "link" element showing that the new node is derived from
+   that of the old Frame and store the current value of the Invert
+   attribute for the Mapping. */
+         this->link[ this->nnode - 1 ] = this->node[ iframe - 1 ];
+         this->invert[ this->nnode - 1 ] = astGetInvert( map );
+
+/* Increment the node count and associate the modified Frame with the
+   new node. */
+         if ( astOK ) {
+            this->nnode++;
+            this->node[ iframe - 1 ] = this->nnode - 1;
+
+/* Tidy the resulting set of nodes, because the node originally
+   referenced by the Frame may no longer be needed. This also
+   simplifies any compound Mapping which may result if this node is
+   removed. */
+            TidyNodes( this, status );
+         }
+      }
+   }
+}
+
+static void RemoveFrame( AstFrameSet *this, int iframe, int *status ) {
+/*
+*++
+*  Name:
+c     astRemoveFrame
+f     AST_REMOVEFRAME
+
+*  Purpose:
+*     Remove a Frame from a FrameSet.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     void astRemoveFrame( AstFrameSet *this, int iframe )
+f     CALL AST_REMOVEFRAME( THIS, IFRAME, STATUS )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+c     This function removes a Frame from a FrameSet. All other Frames
+f     This routine removes a Frame from a FrameSet. All other Frames
+*     in the FrameSet have their indices re-numbered from one (if
+*     necessary), but are otherwise unchanged.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the FrameSet.
+c     iframe
+f     IFRAME = INTEGER (Given)
+*        The index within the FrameSet of the Frame to be removed.
+*        This value should lie in the range from 1 to the number of
+*        Frames in the FrameSet (as given by its Nframe attribute).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Removing a Frame from a FrameSet does not affect the
+*     relationship between other Frames in the FrameSet, even if they
+*     originally depended on the Frame being removed.
+*     - The number of Frames in a FrameSet cannot be reduced to zero.
+*     An error will result if an attempt is made to remove the only
+*     remaining Frame.
+*     - A value of AST__BASE or AST__CURRENT may be given for the
+c     "iframe" parameter to specify the base Frame or the current
+f     IFRAME argument to specify the base Frame or the current
+*     Frame respectively.
+*     - If a FrameSet's base or current Frame is removed, the Base or
+*     Current attribute (respectively) of the FrameSet will have its
+*     value cleared, so that another Frame will then assume its role
+*     by default.
+*     - If any other Frame is removed, the base and current Frames
+*     will remain the same. To ensure this, the Base and/or Current
+*     attributes of the FrameSet will be changed, if necessary, to
+*     reflect any change in the indices of these Frames.
+*--
+*/
+
+/* Local Variables: */
+   int ifr;                      /* Loop counter for Frames */
+   int ii;                       /* Base/current Frame index */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate and translate the Frame index supplied. */
+   iframe = astValidateFrameIndex( this, iframe, "astRemoveFrame" );
+   if ( astOK ) {
+
+/* Reject any attempt to remove the final Frame from the FrameSet. */
+      if ( this->nframe == 1 ) {
+         astError( AST__REMIN, "astRemoveFrame(%s): Invalid attempt to "
+                   "remove the only Frame in a %s.", status, astGetClass( this ),
+                   astGetClass( this ) );
+
+/* If OK, annul the pointer to the selected Frame. */
+      } else {
+         this->frame[ iframe - 1 ] = astAnnul( this->frame[ iframe - 1 ] );
+
+/* Ensure that the variant Mappings in the Frame being removed are not
+   mirrored by any other Frames in the FrameSet. */
+         RemoveMirrors( this, iframe, status );
+
+/* Any Frames that are mirroring variants in Frames higher than the
+   removed Frame need to have their mirror frame indices decremented. */
+         for ( ifr = 1; ifr <= this->nframe; ifr++ ) {
+            if( this->varfrm[ ifr - 1 ] > iframe ) this->varfrm[ ifr - 1 ]--;
+         }
+
+/* Loop to move all subsequent Frame pointers down in the FrameSet's
+   "frame" array to close the resulting gap. Also move the associated
+   "node" and "varfrm" array contents in the same way. */
+         for ( ifr = iframe; ifr < this->nframe; ifr++ ) {
+            this->frame[ ifr - 1 ] = this->frame[ ifr ];
+            this->node[ ifr - 1 ] = this->node[ ifr ];
+            this->varfrm[ ifr - 1 ] = this->varfrm[ ifr ];
+         }
+         this->frame[ this->nframe - 1 ] = NULL;
+         this->node[ this->nframe - 1 ] = -1;
+         this->varfrm[ this->nframe - 1 ] = 0;
+
+/* Decrement the Frame count. */
+         this->nframe--;
+
+/* Tidy the nodes in the FrameSet. */
+         TidyNodes( this, status );
+
+/* If the Base attribute is set and the removed Frame was the base
+   Frame, then clear the attribute value so that a new base Frame will
+   be selected by default. */
+         if ( astTestBase( this ) ) {
+            ii = astGetBase( this );
+            if ( iframe == ii ) {
+               astClearBase( this );
+
+/* If the index of the removed Frame is smaller than the base Frame
+   index, then decrement the Base attribute so that the same base
+   Frame will be used in future. */
+            } else if ( iframe < ii ) {
+               astSetBase( this, ii - 1 );
+            }
+         }
+
+/* Repeat the above procedure for the current Frame. */
+         if ( astTestCurrent( this ) ) {
+            ii = astGetCurrent( this );
+            if ( iframe == ii ) {
+               astClearCurrent( this );
+            } else if ( iframe < ii ) {
+               astSetCurrent( this, ii - 1 );
+            }
+         }
+      }
+   }
+}
+
+static void RemoveMirrors( AstFrameSet *this, int iframe, int *status ) {
+/*
+*  Name:
+*     RemoveMirrors
+
+*  Purpose:
+*     Ensure no other Frames act as mirrors for a specified Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void RemoveMirrors( AstFrameSet *this, int iframe, int *status )
+
+*  Class Membership:
+*     Private function.
+
+*  Description:
+*     This function searchs the FrameSet for Frames that are currently
+*     acting as mirrors for the variant Mappings in the Frame with index
+*     "iframe", and disables mirroring in any found Frames. It should be
+*     used when "iframe" has its variant Mappings removed.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     iframe
+*        One-based index of a Frame that has had its variant Mappings
+*        removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int *frmlist;
+   int ifr;
+   int nfrm;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Iniitalise a list to hold the indices of the FRames that mirror
+   "iframe". */
+   nfrm = 0;
+   frmlist = NULL;
+
+/* Check each Frame in the FrameSet. */
+   for( ifr = 1; ifr <= this->nframe; ifr++ ) {
+
+/* Get the index of the Frame that defines the variant Mappings to use
+   with Frame "ifr". If this is "iframe", then add "ifr" to the list of
+   Frames that need to be "de-mirrored". We cannot "de-mirror" the Frame
+   immediately as doing so may break a chain of mirrors, resulting in the
+   Frames higher up the chain no longer being associated with "iframe". */
+      if( GetVarFrm( this, ifr, status ) == iframe ) {
+         frmlist = astGrow( frmlist, nfrm + 1, sizeof( *frmlist ) );
+         if( astOK ) frmlist[ nfrm++ ] = ifr;
+      }
+   }
+
+/* Loop round all the Frames found above that mirror "iframe". */
+   for( ifr = 0; ifr < nfrm; ifr++ ) {
+
+/* Indicate that the Frame no longer mirrors any other Frame. */
+      this->varfrm[ frmlist[ ifr ] - 1 ] = 0;
+   }
+
+/* Free the list. */
+   frmlist = astFree( frmlist );
+}
+
+static AstMapping *RemoveRegions( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     RemoveRegions
+
+*  Purpose:
+*     Remove any Regions from a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstMapping *RemoveRegions( AstMapping *this, int *status )
+
+*  Class Membership:
+*     FrameSet method (over-rides the astRemoveRegions method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function searches the supplied Mapping (which may be a
+*     compound Mapping such as a FrameSet) for any component Mappings
+*     that are instances of the AST Region class. It then creates a new
+*     Mapping from which all Regions have been removed. If a Region
+*     cannot simply be removed (for instance, if it is a component of a
+*     parallel FrameSet), then it is replaced with an equivalent UnitMap
+*     in the returned Mapping.
+*
+*     The implementation provided by the FrameSet class invokes the
+*     astRemoveRegions method on all the component Frames and Mappings,
+*     and joins the results together into a new FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the modified mapping.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame **newfrms;           /* Array of new Frames */
+   AstFrameSet *new;             /* Pointer to new FrameSet */
+   AstFrameSet *this;            /* Pointer to FrameSet structure */
+   AstMapping **newmaps;         /* Array of new Mappings */
+   AstMapping *result;           /* Result pointer to return */
+   int changed;                  /* Has any mapping been changed? */
+   int i;                        /* Loop count */
+   int nax;                      /* Number of Frame axes */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the FrameSet. */
+   this = (AstFrameSet *) this_mapping;
+
+/* Allocate arrays to hold the modified Mapping and Frame pointers. */
+   newmaps = astMalloc( sizeof( AstMapping *)*( this->nnode - 1 ) );
+   newfrms = astMalloc( sizeof( AstFrame *)*( this->nframe ) );
+   if( astOK ) {
+
+/* Invoke the astRemoveRegions method on all the component Mappings. */
+      changed = 0;
+      for( i = 0; i < this->nnode - 1; i++ ) {
+         newmaps[ i ] = astRemoveRegions( this->map[ i ] );
+
+/* Note if any Mapping was changed. */
+         if( newmaps[ i ] != this->map[ i ] ) {
+            changed = 1;
+
+/* The implementation of the astRemoveRegions method provided by the
+   Region class returns a Frame rather than a UnitMap. But we need
+   Mappings here, not Frames. So if the new Mapping is a Frame, replace
+   it with an equivalent UnitMap. */
+            if( astIsAFrame( newmaps[ i ] ) ) {
+               nax = astGetNin( newmaps[ i ] );
+               (void) astAnnul( newmaps[ i ] );
+               newmaps[ i ] = (AstMapping *) astUnitMap( nax, " ", status );
+            }
+         }
+      }
+
+/* Invoke the astRemoveRegions method on all the component Frames. */
+      for( i = 0; i < this->nframe; i++ ) {
+         newfrms[ i ] = astRemoveRegions( this->frame[ i ] );
+
+/* Note if any Frame was changed. */
+         if( newfrms[ i ] != this->frame[ i ] ) changed = 1;
+      }
+
+/* If no component was modified, just return a clone of the supplied
+   pointer. */
+      if( ! changed ) {
+         result = astClone( this );
+
+/* Otherwise, we need to create a new FrameSet to return. We take a deep
+   copy of the supplied FrameSet and then modify the Mappings and Frames
+   so that we retain any extra information in the supplied FrameSet. */
+      } else {
+         new = astCopy( this );
+
+         for( i = 0; i < this->nnode - 1; i++ ) {
+            (void) astAnnul( new->map[ i ] );
+            new->map[ i ] = astClone( newmaps[ i ] );
+         }
+
+         for( i = 0; i < this->nframe; i++ ) {
+            (void) astAnnul( new->frame[ i ] );
+            new->frame[ i ] = astClone( newfrms[ i ] );
+         }
+
+         result = (AstMapping *) new;
+      }
+
+/* Free resources. */
+      for( i = 0; i < this->nnode - 1; i++ ) {
+         newmaps[ i ] = astAnnul( newmaps[ i ] );
+      }
+
+      for( i = 0; i < this->nframe; i++ ) {
+         newfrms[ i ] = astAnnul( newfrms[ i ] );
+      }
+
+   }
+
+   newfrms = astFree( newfrms );
+   newmaps = astFree( newmaps );
+
+/* Annul the returned Mapping if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void ReportPoints( AstMapping *this_mapping, int forward,
+                          AstPointSet *in_points, AstPointSet *out_points, int *status ) {
+/*
+*  Name:
+*     ReportPoints
+
+*  Purpose:
+*     Report the effect of transforming a set of points using a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void ReportPoints( AstMapping *this, int forward,
+*                        AstPointSet *in_points, AstPointSet *out_points, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astReportPoints
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function reports the coordinates of a set of points before
+*     and after being transformed by a FrameSet, by writing them to
+*     standard output.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     forward
+*        A non-zero value indicates that the FrameSet's forward
+*        coordinate transformation has been applied, while a zero
+*        value indicates the inverse transformation.
+*     in_points
+*        Pointer to a PointSet which is associated with the
+*        coordinates of a set of points before the FrameSet was
+*        applied.
+*     out_points
+*        Pointer to a PointSet which is associated with the
+*        coordinates of the same set of points after the FrameSet has
+*        been applied.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *base_frame;         /* Pointer to current Frame */
+   AstFrame *current_frame;      /* Pointer to base Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   double **ptr_in;              /* Pointer to array of input data pointers */
+   double **ptr_out;             /* Pointer to array of output data pointers */
+   int coord;                    /* Loop counter for coordinates */
+   int ncoord_in;                /* Number of input coordinates per point */
+   int ncoord_out;               /* Number of output coordinates per point */
+   int npoint;                   /* Number of points to report */
+   int npoint_in;                /* Number of input points */
+   int npoint_out;               /* Number of output points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_mapping;
+
+/* Obtain the numbers of points and coordinates associated with each
+   PointSet. */
+   npoint_in = astGetNpoint( in_points );
+   npoint_out = astGetNpoint( out_points );
+   ncoord_in = astGetNcoord( in_points );
+   ncoord_out = astGetNcoord( out_points );
+
+/* Obtain the pointers that give access to the coordinate data
+   associated with each PointSet. */
+   ptr_in = astGetPoints( in_points );
+   ptr_out = astGetPoints( out_points );
+
+/* In the event that both PointSets don't contain equal numbers of
+   points (this shouldn't actually happen), simply use the minimum
+   number. */
+   npoint = ( npoint_in < npoint_out ) ? npoint_in : npoint_out;
+
+/* Obtain pointers to the FrameSet's base and current Frames. */
+   base_frame = astGetFrame( this, AST__BASE );
+   current_frame = astGetFrame( this, AST__CURRENT );
+
+/* Loop to report the effect of the transformation on each point in
+   turn. */
+   if ( astOK ) {
+      for ( point = 0; point < npoint; point++ ) {
+
+/* Report the input coordinates (in parentheses and separated by
+   commas). Format each value for display using the appropriate
+   Frame's astFormat method. */
+         printf( "(" );
+         for ( coord = 0; coord < ncoord_in; coord++ ) {
+            printf( "%s%s", coord ? ", " : "",
+                    astFormat( forward ? base_frame : current_frame,
+                               coord, ptr_in[ coord ][ point ] ) );
+         }
+
+/* Similarly report the output coordinates, this time formatting
+   values using the other Frame's astFormat method. */
+         printf( ") --> (" );
+         for ( coord = 0; coord < ncoord_out; coord++ ) {
+            printf( "%s%s", coord ? ", " : "",
+                    astFormat( forward ? current_frame : base_frame,
+                               coord, ptr_out[ coord ][ point ] ) );
+         }
+         printf( ")\n" );
+      }
+   }
+
+/* Annul the Frame pointers. */
+   base_frame = astAnnul( base_frame );
+   current_frame = astAnnul( current_frame );
+}
+
+static void Resolve( AstFrame *this_frame, const double point1[],
+                     const double point2[], const double point3[],
+                     double point4[], double *d1, double *d2, int *status ){
+/*
+*  Name:
+*     Resolve
+
+*  Purpose:
+*     Resolve a vector into two orthogonal components
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void Resolve( AstFrame *this, const double point1[],
+*                   const double point2[], const double point3[],
+*                   double point4[], double *d1, double *d2, int *status );
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astResolve
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function resolves a vector into two perpendicular components.
+*     The vector from point 1 to point 2 is used as the basis vector.
+*     The vector from point 1 to point 3 is resolved into components
+*     parallel and perpendicular to this basis vector. The lengths of the
+*     two components are returned, together with the position of closest
+*     aproach of the basis vector to point 3.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the start of the basis vector,
+*        and of the vector to be resolved.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the basis vector.
+*     point3
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the vector to be
+*        resolved.
+*     point4
+*        An array of double, with one element for each Frame axis
+*        in which the coordinates of the point of closest approach of the
+*        basis vector to point 3 will be returned.
+*     d1
+*        The address of a location at which to return the distance from
+*        point 1 to point 4 (that is, the length of the component parallel
+*        to the basis vector). Positive values are in the same sense as
+*        movement from point 1 to point 2.
+*     d2
+*        The address of a location at which to return the distance from
+*        point 4 to point 3 (that is, the length of the component
+*        perpendicular to the basis vector). The value is always positive.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Each vector used in this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value, or if the required
+*     output values are undefined.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astResolve method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astResolve( fr, point1, point2, point3, point4, d1, d2 );
+   fr = astAnnul( fr );
+
+}
+
+static AstPointSet *ResolvePoints( AstFrame *this_frame, const double point1[],
+                                   const double point2[], AstPointSet *in,
+                                   AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     ResolvePoints
+
+*  Purpose:
+*     Resolve a set of vectors into orthogonal components
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstPointSet *astResolvePoints( AstFrame *this, const double point1[],
+*                                    const double point2[], AstPointSet *in,
+*                                    AstPointSet *out )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astResolvePoints method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function takes a Frame and a set of vectors encapsulated
+*     in a PointSet, and resolves each one into two orthogonal components,
+*     returning these two components in another PointSet.
+*
+*     This is exactly the same as the public astResolve method, except
+*     that this method allows many vectors to be processed in a single call,
+*     thus reducing the computational cost of overheads of many
+*     individual calls to astResolve.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the start of the basis vector,
+*        and of the vectors to be resolved.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the basis vector.
+*     in
+*        Pointer to the PointSet holding the ends of the vectors to be
+*        resolved.
+*     out
+*        Pointer to a PointSet which will hold the length of the two
+*        resolved components. A NULL value may also be given, in which
+*        case a new PointSet will be created by this function.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet. The first axis will
+*     hold the lengths of the vector components parallel to the basis vector.
+*     These values will be signed (positive values are in the same sense as
+*     movement from point 1 to point 2. The second axis will hold the lengths
+*     of the vector components perpendicular to the basis vector. These
+*     values will always be positive.
+
+*  Notes:
+*     - The number of coordinate values per point in the input
+*     PointSet must match the number of axes in the supplied Frame.
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points and 2 coordinate values per point.
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstFrameSet *this;            /* Pointer to FrameSet structure */
+   AstFrame *fr;                 /* Pointer to current Frame */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astResolvePoints method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astResolvePoints( this, point1, point2, in, out );
+   fr = astAnnul( fr );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+
+}
+
+static void RestoreIntegrity( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     RestoreIntegrity
+
+*  Purpose:
+*     Restore a previous integrity state for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void RestoreIntegrity( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet member function.
+
+*  Description:
+*     This function restores a FrameSet to a previous integrity state,
+*     as recorded (in static variables) by a previous invocation of
+*     the RecordIntegrity function. It does this by appropriately
+*     remapping the FrameSet's current Frame, if this appears
+*     necessary.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+
+*  Notes:
+*     - The previous record of the FrameSet's integrity state (as
+*     recorded by RecordIntegrity) is deleted by this function, even
+*     if it is invoked with the global error status set.
+*     - An error will result if the previous integrity state cannot be
+*     restored.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFrame *current;            /* Pointer to current Frame */
+   AstFrameSet *cvt;             /* Pointer to conversion FrameSet */
+   AstMapping *map;              /* Pointer to conversion Mapping */
+   int flags;                    /* Flags associated with current frame */
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Check that a previous record of the FrameSet's integrity state has
+   been made. Do not modify the FrameSet if it appears that the
+   previous integrity state has not been lost (i.e. that the current
+   Frame has not been modified), nor if there is only one Frame
+   present. Check the global error status. */
+   if ( integrity_frame && integrity_lost && ( astGetNframe( this ) > 1 ) &&
+        astOK ) {
+
+/* Obtain a pointer to the current Frame. */
+      current = astGetFrame( this, AST__CURRENT );
+
+/* Since we need to obtain a conversion between the recorded copy of
+   this Frame and the current one, we must match their Domain
+   attributes (otherwise conversion cannot be performed). Do this by
+   changing the recorded copy as necessary. */
+      if ( astTestDomain( current ) ) {
+         astSetDomain( integrity_frame, astGetDomain( current ) );
+      } else {
+         astClearDomain( integrity_frame );
+      }
+
+/* Temporarily set both Frames AST__INTFLAG flag to indicate that the
+   following call to astConvert is part of the process of restoring a
+   FrameSet's integrity. Some classes of Frame (e.g. DSBSpecFrames) may
+   choose to return a different Mapping in this case. */
+      astSetFrameFlags( integrity_frame, astGetFrameFlags( integrity_frame )
+                                         | AST__INTFLAG );
+      flags = astGetFrameFlags( current );
+      astSetFrameFlags( current, flags | AST__INTFLAG );
+
+/* Obtain the required conversion FrameSet, restore the original frame
+   flags and annul the current Frame pointer. */
+      cvt = astConvert( integrity_frame, current, "" );
+      astSetFrameFlags( current, flags );
+      current = astAnnul( current );
+
+/* If no conversion could be found, then the FrameSet's integrity
+   state cannot be restored, so report an error. */
+      if ( !cvt ) {
+         if( astOK ) {
+            astError( AST__ILOST, "%s(%s): Cannot maintain %s integrity.", status,
+                      integrity_method, astGetClass( this ),
+                      astGetClass( this ) );
+         }
+
+/* Otherwise, obtain a pointer to the conversion Mapping. */
+      } else {
+         map = astGetMapping( cvt, AST__BASE, AST__CURRENT );
+
+/* If the Mapping is not a UnitMap (i.e. a null Mapping), then use it
+   to remap the FrameSet's current Frame. */
+         if ( strcmp( astGetClass( map ), "UnitMap" ) ) {
+            astRemapFrame( this, AST__CURRENT, map );
+         }
+
+/* Annul the conversion Mapping and Frameset pointers. */
+         map = astAnnul( map );
+         cvt = astAnnul( cvt );
+      }
+   }
+
+/* Delete the recorded integrity information by annulling the original
+   copy of the current Frame (thus deleting it) and resetting the
+   associated modification flag. */
+   if ( integrity_frame ) integrity_frame = astAnnul( integrity_frame );
+   integrity_lost = 0;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     FrameSet member function (extends the astSetAttrib method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function assigns an attribute value for a FrameSet, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     setting
+*        Pointer to a null terminated string specifying the new
+*        attribute value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Attributes:
+*     The set of attribute values is not fixed and is determined by
+*     the current Frame. In addition, the FrameSet class defines the
+*     following attributes:
+*
+*        Base (integer)
+*        Current (integer)
+
+*  Notes:
+*     - This protected method is intended to be invoked by the Object
+*     astSet method and makes additional attributes accessible to it.
+*     - All attribute settings passed to this function are simply
+*     passed on to the corresponding method for the FrameSet's current
+*     Frame.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to a Frame within the FrameSet */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   const char *dom;              /* Pointer to Domain string */
+   int base;                     /* Base attribute value */
+   int base_off;                 /* Offset of Base value string */
+   int current;                  /* Current attribute value */
+   int current_off;              /* Offset of Current value string */
+   int id;                       /* Offset of ID string */
+   int invert;                   /* Invert attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+   int nfrm;                     /* Number of Frames in FrameSet */
+   int report;                   /* Report attribute value */
+   int variant;                  /* Offset of Variant string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* We first handle attributes that apply to the FrameSet as a whole
+   (rather than to the current Frame). */
+
+/* Base. */
+/* ----- */
+/* Read as an integer. */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "base= %d %n", &base, &nc ) )
+          && ( nc >= len ) ) {
+      astSetBase( this, base );
+
+/* Also allow a string. */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "base= %n%*s %n", &base_off, &nc ) )
+                 && ( nc >= len ) ) {
+
+/* Check for "AST__CURRENT" or "Current". */
+      if ( astChrMatch( "AST__CURRENT", setting + base_off ) ||
+           astChrMatch( "Current", setting + base_off ) ) {
+         astSetBase( this, AST__CURRENT );
+
+/* Check for "AST__BASE" or "Base" (possible, although not very
+   useful). */
+      } else if ( astChrMatch( "AST__BASE", setting + base_off ) ||
+                  astChrMatch( "Base", setting + base_off ) ) {
+
+/* If the FrameSet contains a Frame with the given Domain name, make it
+   the base Frame. */
+      } else {
+         nfrm = astGetNframe( this );
+         for( base = 1; base <= nfrm; base++ ) {
+            fr = astGetFrame( this, base );
+            dom = astGetDomain( fr );
+            fr = astAnnul( fr );
+            if( astChrMatch( dom, setting + base_off ) ) break;
+         }
+
+         if( base <= nfrm ) {
+            astSetBase( this, base );
+
+/* Report an error if the value wasn't recognised. */
+         } else {
+            astError( AST__ATTIN, "astSetAttrib(%s): Invalid index value for "
+                      "Base Frame \"%s\".", status,
+                      astGetClass( this ),  setting + base_off );
+         }
+      }
+
+/* Current. */
+/* -------- */
+/* Since this determines the choice of current Frame, we must restore
+   the integrity state of the FrameSet before changing this attribute
+   and record the new integrity state afterwards. */
+
+/* Read as an integer. */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "current= %d %n", &current, &nc ) )
+                 && ( nc >= len ) ) {
+      RestoreIntegrity( this, status );
+      astSetCurrent( this, current );
+      RecordIntegrity( this, status );
+
+/* Also allow a string. */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "current= %n%*s %n",
+                              &current_off, &nc ) )
+                 && ( nc >= len ) ) {
+
+/* Check for "AST__BASE" or "Base". */
+      if ( astChrMatch( "AST__BASE", setting + current_off ) ||
+           astChrMatch( "Base", setting + current_off ) ) {
+         RestoreIntegrity( this, status );
+         astSetCurrent( this, AST__BASE );
+         RecordIntegrity( this, status );
+
+/* Check for "AST__CURRENT" or "Current" (possible, although not very
+   useful). */
+      } else if ( astChrMatch( "AST__CURRENT", setting + current_off ) ||
+                  astChrMatch( "Current", setting + current_off ) ) {
+
+/* If the FrameSet contains a Frame with the given Domain name, make it
+   the current Frame. */
+      } else {
+         nfrm = astGetNframe( this );
+         for( current = 1; current <= nfrm; current++ ) {
+            fr = astGetFrame( this, current );
+            dom = astGetDomain( fr );
+            fr = astAnnul( fr );
+            if( astChrMatch( dom, setting + current_off ) ) break;
+         }
+
+         if( current <= nfrm ) {
+            RestoreIntegrity( this, status );
+            astSetCurrent( this, current );
+            RecordIntegrity( this, status );
+
+/* Report an error if the value wasn't recognised. */
+         } else {
+            astError( AST__ATTIN, "astSetAttrib(%s): Invalid index value for "
+                      "Current Frame \"%s\".", status,
+                      astGetClass( this ),  setting + current_off );
+         }
+      }
+
+/* ID. */
+/* --- */
+   } else if ( nc = 0, ( 0 == astSscanf( setting, "id=%n%*[^\n]%n", &id, &nc ) )
+                       && ( nc >= len ) ) {
+      astSetID( this, setting + id );
+
+/* Ident. */
+/* ------ */
+   } else if ( nc = 0, ( 0 == astSscanf( setting, "ident=%n%*[^\n]%n", &id, &nc ) )
+                       && ( nc >= len ) ) {
+      astSetIdent( this, setting + id );
+
+/* Invert. */
+/* ------- */
+/* Since this affects the choice of current Frame, we must restore the
+   integrity state of the FrameSet before changing this attribute and
+   record the new integrity state afterwards. */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "invert= %d %n", &invert, &nc ) )
+        && ( nc >= len ) ) {
+      RestoreIntegrity( this, status );
+      astSetInvert( this, invert );
+      RecordIntegrity( this, status );
+
+/* Report. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "report= %d %n", &report, &nc ) )
+        && ( nc >= len ) ) {
+      astSetReport( this, report );
+
+/* Variant. */
+/* -------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "variant=%n%*[^\n]%n", &variant, &nc ) )
+               && ( nc >= len ) ) {
+      astSetVariant( this, setting + variant );
+
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* If the attribute was not recognised, use this macro to report an error
+   if a read-only attribute has been specified. */
+   } else if ( MATCH( "allvariants" ) ||
+               MATCH( "class" ) ||
+               MATCH( "nframe" ) ||
+               MATCH( "nin" ) ||
+               MATCH( "nobject" ) ||
+               MATCH( "nout" ) ||
+               MATCH( "refcount" ) ||
+               MATCH( "tranforward" ) ||
+               MATCH( "traninverse" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Pass unrecognised settings on to the FrameSet's current Frame for
+   further interpretation. */
+   } else {
+
+/* Force a copy to be made of the current Frame, if needed, to make it
+   independent of other Frames within the FrameSet. */
+      (void) ForceCopy( this, AST__CURRENT, status );
+
+/* Obtain a pointer to the current Frame and invoke its astSetAttrib
+   method. Annul the Frame pointer afterwards. */
+      fr = astGetFrame( this, AST__CURRENT );
+      astSetAttrib( fr, setting );
+      fr = astAnnul( fr );
+
+/* Note that the current Frame has been modified. */
+      integrity_lost = 1;
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+static void SetAxis( AstFrame *this_frame, int axis, AstAxis *newaxis, int *status ) {
+/*
+*  Name:
+*     SetAxis
+
+*  Purpose:
+*     Set a new Axis for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void SetAxis( AstFrame *this, int axis, AstAxis *newaxis, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astSetAxis method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function allows a new Axis object to be associated with one
+*     of the axes of the current Frame in a FrameSet, replacing the
+*     previous one. Each Axis object contains a description of the
+*     quantity represented along one of the Frame's axes, so this
+*     function allows this description to be exchanged for another
+*     one.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     axis
+*        The index (zero-based) of the axis whose associated Axis
+*        object is to be replaced.
+*     newaxis
+*        Pointer to the new Axis object.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index supplied. */
+   (void) astValidateAxis( this, axis, 1, "astSetAxis" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astSetAxis method to assign the new Axis object. Annul the
+   Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astSetAxis( fr, axis, newaxis );
+   fr = astAnnul( fr );
+}
+
+static void SetBase( AstFrameSet *this, int iframe, int *status ) {
+/*
+*+
+*  Name:
+*     astSetBase
+
+*  Purpose:
+*     Set a value for the Base attribute of a FrameSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void astSetBase( AstFrameSet *this, int iframe )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function sets a value for the Base attribute of a FrameSet. This
+*     value is an index that identifies the base Frame for the FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     iframe
+*        Value to be set for the Base attribute.
+
+*  Notes:
+*     - A value of AST__BASE or AST__CURRENT may be given for the
+*     "iframe" parameter to identify the base Frame or the current
+*     Frame respectively.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                    /* FrameSet is inverted? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate and translate the Frame index supplied. */
+   iframe = astValidateFrameIndex( this, iframe, "astSetBase" );
+
+/* Determine if the FrameSet has been inverted. */
+   invert = astGetInvert( this );
+
+/* If it has not been inverted, set the base Frame index, otherwise
+   set the current Frame index instead. */
+   if ( astOK ) *( invert ? &this->current : &this->base ) = iframe;
+}
+
+static void SetCurrent( AstFrameSet *this, int iframe, int *status ) {
+/*
+*+
+*  Name:
+*     astSetCurrent
+
+*  Purpose:
+*     Set a value for the Current attribute of a FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int astSetCurrent( AstFrameSet *this, int iframe )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function sets a value for the Current attribute of a
+*     FrameSet. This attribute is an index that identifies the current
+*     Frame for the FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     iframe
+*        Value to be set for the Current attribute.
+
+*  Notes:
+*     - A value of AST__BASE or AST__CURRENT may be given for the
+*     "iframe" parameter to identify the base Frame or the current
+*     Frame respectively.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                   /* FrameSet is inverted? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate and translate the Frame index supplied. */
+   iframe = astValidateFrameIndex( this, iframe, "astSetCurrent" );
+
+/* Determine if the FrameSet has been inverted. */
+   invert = astGetInvert( this );
+
+/* If it has not been inverted, set the current frame index, otherwise
+   set the base Frame index instead. */
+   if ( astOK ) *( invert ? &this->base : &this->current ) = iframe;
+}
+
+static void SetVariant( AstFrameSet *this, const char *variant, int *status ) {
+/*
+*+
+*  Name:
+*     astSetVariant
+
+*  Purpose:
+*     Set a value for the Variant attribute of a FrameSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void astSetVariant( AstFrameSet *this,  const char *variant )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function sets a value for the Variant attribute of a FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     variant
+*        Value to be set for the Variant attribute.
+
+*  Notes:
+*     - An error will be reported if the supplied variant name cannot be
+*     found in the Variants FrameSet associated with the current Frame.
+
+*-
+*/
+
+/* Local Variables: */
+   AstCmpMap *map6;
+   AstCmpMap *map5;
+   AstCmpMap *map4;
+   AstFrame *frm;
+   AstFrame *vfrm;
+   AstFrameSet *tfs;
+   AstFrameSet *vfs;
+   AstMapping *map0;
+   AstMapping *map2;
+   AstMapping *map3;
+   AstMapping *map1;
+   char *myvar;
+   const char *dom;
+   int icur;
+   int ifrm;
+   int inode;
+   int inv0;
+   int inv;
+   int nfrm;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a copy of the supplied string and clean it. */
+   myvar = astStore( NULL, variant, strlen( variant ) + 1 );
+   astRemoveLeadingBlanks( myvar );
+   astChrCase( NULL, myvar, 1, 0 );
+   if( astOK ) {
+      myvar[ astChrLen( myvar ) ] = 0;
+
+/* Get the one-based index of the Frame that defines the available
+   variant Mappings. */
+      icur = GetVarFrm( this, astGetCurrent( this ), status );
+
+/* Get the variants FrameSet from the Frame selected above. */
+      frm = astGetFrame( this, icur );
+      vfs = astGetFrameVariants( frm );
+
+/* If there is no variants FrameSet in the Frame, the only allowed value
+   for "Variant" is the Domain name of the current Frame. */
+      if( ! vfs ) {
+         dom = astGetDomain( this );
+         if( astOK && strcmp( myvar, dom ) ) {
+            astError( AST__ATTIN, "astSetVariant(%s): Unknown Frame "
+                      "variant '%s' requested.", status, astGetClass( this ),
+                      myvar );
+         }
+
+/* If there is a variants FrameSet in the Frame... */
+      } else {
+
+/* Find the index of the Frame in the Variants FrameSet that has a Domain
+   equal to myvar. */
+         nfrm = astGetNframe( vfs );
+         for( ifrm = 0; ifrm < nfrm; ifrm++ ) {
+            vfrm = astGetFrame( vfs, ifrm + 1 );
+            dom = astGetDomain( vfrm );
+            vfrm = astAnnul( vfrm );
+            if( !astOK || !strcmp( myvar, dom ) ) break;
+         }
+
+/* Report an error if no such Frame found. */
+         if( ifrm == nfrm && astOK ) {
+            astError( AST__ATTIN, "astSetVariant(%s): Unknown Frame "
+                      "variant '%s' requested - available variants are "
+                      "'%s'.", status, astGetClass(this), myvar,
+                      astGetAllVariants(this) );
+
+/* Otherwise, get a Mapping from the current Frame in "this" to the
+   currently selected Variant Frame. We cannot assume that they are the
+   same as attributes of the current Frame (e.g. System) may have been
+   changed since the variant was added. If the required Frame is already
+   the current Frame, there is nothing more to do since the required
+   variant is already selected. */
+         } else if( ifrm + 1 != astGetCurrent( vfs ) ){
+            vfrm = astGetFrame( vfs, AST__CURRENT );
+            dom = astGetDomain( frm );
+            if( dom ) dom = astStore( NULL, dom, strlen( dom ) + 1 );
+            astSetDomain( frm, astGetDomain( vfrm ) );
+            tfs = astConvert( frm, vfrm, "" );
+            astSetDomain( frm, dom );
+            if( tfs ) {
+               map1 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
+               tfs = astAnnul( tfs );
+               vfrm = astAnnul( vfrm );
+
+/* Get the Mapping from the original Variant Frame to the requested variant
+   Frame. */
+               map2 = astGetMapping( vfs, AST__CURRENT, ifrm + 1 );
+
+/* Get a Mapping from the new variant Frame to the current Frame in "this". */
+               vfrm = astGetFrame( vfs, ifrm + 1 );
+               astSetDomain( frm, astGetDomain( vfrm ) );
+               tfs = astConvert( vfrm, frm, "" );
+               astSetDomain( frm, dom );
+               if( tfs ) {
+                  map3 = astGetMapping( tfs, AST__BASE, AST__CURRENT );
+                  tfs = astAnnul( tfs );
+
+/* Concatentate the three Mappings, to get the Mapping from the old
+   variant Frame to the new variant Frame. */
+                  map4 = astCmpMap( map1, map2, 1, " ", status );
+                  map5 = astCmpMap( map4, map3, 1, " ", status );
+
+/* Now we modify the Mapping in the FrameSet. First get the index of the node
+   with which the Frame is associated. */
+                  inode = this->node[ icur - 1 ];
+
+/* Get the Mapping that generates the node values, and its Invert flag. */
+                  map0 = this->map[ inode - 1 ];
+                  inv0 = this->invert[ inode - 1 ];
+
+/* Temporarily reset the invert flag in the Mapping to account for any
+   changes made to the Mapping via other pointers. */
+                  inv = astGetInvert( map0 );
+                  astSetInvert( map0, inv0 );
+
+/* Concatentate with "map5" to get the Mapping form the the parent node
+   to the new variant of the current node. */
+                  map6 = astCmpMap( map0, map5, 1, " ", status );
+
+/* Simplify it and use it to replace the Mapping in the FrameSet structure. */
+                  this->map[ inode - 1 ] = astSimplify( map6 );
+                  this->invert[ inode - 1 ] = astGetInvert( this->map[ inode - 1 ] );
+
+/* Re-instate the original Invert flag and free the old Mapping pointer. */
+                  astSetInvert( map0, inv );
+                  map0 = astAnnul( map0 );
+
+/* Make the variant Frame the current Frame within the Variants FrameSet. */
+                  astSetCurrent( vfs, ifrm + 1 );
+
+/* Free resources. */
+                  map6 = astAnnul( map6 );
+                  map5 = astAnnul( map5 );
+                  map4 = astAnnul( map4 );
+                  map3 = astAnnul( map3 );
+
+/* Report an error if a Mapping cannot be found from the new variant Frame
+   to the current Frame in "this". */
+               } else if( astOK ) {
+                  astError( AST__INTER, "astSetVariant(%s): Cannot convert "
+                            "from a %s with Domain '%s' to a %s with Domain "
+                            "'%s' (internal programming error).", status,
+                            astGetClass( this ), astGetClass( vfrm ),
+                            astGetDomain( vfrm ), astGetClass( frm ),
+                            astGetDomain( frm ) );
+               }
+
+/* Free resources. */
+               map2 = astAnnul( map2 );
+               map1 = astAnnul( map1 );
+
+/* Report an error if a Mapping cannot be found from the current Frame in
+   "this" to the current Variant Frame. */
+            } else if( astOK ) {
+               astError( AST__INTER, "astSetVariant(%s): Cannot convert "
+                         "from a %s with Domain '%s' to a %s with Domain "
+                         "'%s' (internal programming error).", status,
+                         astGetClass( this ), astGetClass( frm ),
+                         astGetDomain( frm ), astGetClass( vfrm ),
+                         astGetDomain( vfrm ) );
+            }
+
+/* Free resources. */
+            vfrm = astAnnul( vfrm );
+            dom = astFree( (void *) dom );
+         }
+
+/* Annul the pointer to the Variants FrameSet. */
+         vfs = astAnnul( vfs );
+      }
+
+/* Annul the pointer to the current Frame in "this". */
+      frm = astAnnul( frm );
+   }
+
+/* Free the memory holding the cleaned variant name. */
+   myvar = astFree( myvar );
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify the Mappings in a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     FrameSet method (over-rides the astSimplify method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function simplifies the Mappings in a FrameSet to eliminate
+*     redundant computational steps, or to merge separate steps which
+*     can be performed more efficiently in a single operation.
+
+*  Parameters:
+*     this
+*        Pointer to the original FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A new pointer to the (possibly simplified) FrameSet. If
+*     simplification was not possible, this will be a cloned pointer
+*     to the original FrameSet.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrameSet *new;             /* Pointer to new (simpler?) FrameSet */
+   AstFrameSet *this;            /* Pointer to original FrameSet structure */
+   AstMapping *map;              /* Pointer to Mapping */
+   AstMapping *result;           /* Result pointer to return */
+   AstMapping *tmp;              /* Temporary Mapping pointer */
+   int inode;                    /* Loop counter for FrameSet nodes */
+   int inv;                      /* Mapping Invert attribute value */
+   int invert;                   /* Invert flag value */
+   int set;                      /* Invert attribute set? */
+   int simpler;                  /* Simplification achieved? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_mapping;
+
+/* Make a copy of the FrameSet, since we may alter it (this is a deep
+   copy, which is a minor limitation of the current implementation). */
+   new = astCopy( this );
+
+/* Loop to examine each of the Mappings between the Frames in the
+   copy. */
+   simpler = 0;
+   for ( inode = 1; astOK && ( inode < new->nnode ); inode++ ) {
+
+/* Obtain the Mapping pointer and associated invert flag. */
+      map = new->map[ inode - 1 ];
+      invert = new->invert[ inode - 1 ];
+
+/* Determine if the Mapping's Invert attribute is set, and obtain its
+   value. */
+      set = astTestInvert( map );
+      inv = astGetInvert( map );
+
+/* If necessary, set the required value for the Invert attribute. */
+      if ( inv != invert ) astSetInvert( map, invert );
+
+/* Simplify the Mapping. */
+      tmp = astSimplify( map );
+
+/* If necessary, restore the original state of the Mapping's Invert
+   attribute. */
+      if ( inv != invert ) {
+         if ( set ) {
+            astSetInvert( map, inv );
+         } else {
+            astClearInvert( map );
+         }
+      }
+
+/* Test if simplification was performed. */
+      if ( astOK ) {
+         if ( tmp != map ) {
+
+/* If so, annul the original Mapping pointer and substitute the new
+   one. Also set a new invert flag to accompany it. */
+            (void) astAnnul( new->map[ inode - 1 ] );
+            new->map[ inode - 1 ] = astClone( tmp );
+            new->invert[ inode - 1 ] = astGetInvert( tmp );
+
+/* Note if any Mapping within the FrameSet is simplified. */
+            simpler = 1;
+         }
+      }
+
+/* Annul the pointer to the simplified Mapping. */
+      tmp = astAnnul( tmp );
+   }
+
+/* If simplification was possible, clone a pointer to the new
+   FrameSet. Otherwise clone a pointer to the original one. */
+   if ( astOK ) result = astClone( simpler ? new : this );
+
+/* Annul the new FrameSet pointer. */
+   new = astAnnul( new );
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int Span( AstFrameSet *this, AstFrame **frames, int inode1, int inode2,
+                 int avoid, AstMapping **map, int *forward, int *status ) {
+/*
+*  Name:
+*     Span
+
+*  Purpose:
+*     Find a path between two nodes in a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int Span( AstFrameSet *this, AstFrame **frames, int inode1, int inode2,
+*               int avoid, AstMapping **map, int *forward, int *status )
+
+*  Class Membership:
+*     FrameSet member function.
+
+*  Description:
+*     This function searches a FrameSet to identify a path between two
+*     specified nodes. It returns an array of pointers to each Mapping
+*     in the path, along with direction information, so that an
+*     overall Mapping between the two nodes can be constructed.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     frames
+*        Pointer to an array of Frame pointers, indexed by node index.
+*        Nodes which have no associated Frame will have a NULL pointer
+*        stored in this array.
+*     inode1
+*        Zero based index of the starting node.
+*     inode2
+*        Zero based index of the ending node.
+*     avoid
+*        Zero based index which identifies a node which is to be
+*        avoided (i.e. the initial step in the path should not be via
+*        this node). This value is required because the function
+*        invokes itself recursively; it provides a mechanism to
+*        prevent searches proceeding back down paths that have already
+*        been searched. External callers should provide a value of -1,
+*        which indicates that all possible paths should initially be
+*        explored.
+*     map
+*        Pointer to the start of an array that will be filled with a
+*        series of pointers to Mappings which must be applied in turn
+*        in order to transform between the two Frames. External
+*        callers should ensure that this array contains at least as many
+*        elements as there are Mappings and Frames in the FrameSet (one less
+*        than the number of nodes plus the number of Frames).
+*
+*        Note that the pointers are simply copies of addresses from
+*        the FrameSet's "map" array. They are not cloned, so should
+*        not be annulled by the caller.
+*     forward
+*        Pointer to the start of an array of int that will be filled
+*        with boolean flags (0 or 1) to indicate whether the forward
+*        (as opposed to the inverse) transformation should be used for
+*        each Mapping returned in order to effect the transformation
+*        between the starting and ending nodes. This array should be the
+*        same size as the "map" array.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The function returns one more than the number of Mappings
+*     required to perform the transformation, or zero if it was not
+*     possible to find a path between the two nodes.
+
+*  Notes:
+*     - If a node has an associated Frame, the Frame usually represents a
+*     UnitMap and so can be ignored. The exception is if the Frame is
+*     actually a Region (or a CmpFrame containing a Region), in which case
+*     it represents a Mapping which returns bad values if the input position
+*     is outside the region. This form of Mapping should not be ignored, and
+*     so the returned list of Mappings includes the effect of any Frames
+*     along the path which are not equivalent to a UnitMap. This
+*     equivalence is determined by invoking the astSimplify method on the
+*     Frame.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*     - On the assumption that the FrameSet has been consistently
+*     constructed, there should be exactly one path between any pair
+*     of its nodes.  It should not, therefore, ever fail to find a
+*     path except when invoked recursively to explore a subset of the
+*     FrameSet's nodes (this should not be visible to an external
+*     caller).  Failure to find a path does not in itself result in an
+*     error condition.
+*/
+
+/* Local Variables: */
+   AstFrame *frame;          /* Pointer to Frame associated with inode1 */
+   int fwd;                  /* Forward Mapping identified? */
+   int inode;                /* Loop counter for nodes */
+   int inv;                  /* Inverse Mapping identified? */
+   int invert;               /* Original Mapping Invert value */
+   int nextra;               /* No. of extra Mappings to add to path */
+   int result;               /* Count of mappings (to be returned) */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* See if the two nodes are the same. */
+   result = ( inode1 == inode2 );
+
+/* If so, we need to consider the Mapping represented by any Frame
+   associated with the node. Most classes of Frames are equivalent to a
+   UnitMap and so can be ignored. But some (e.g. the Region class) are not
+   equivalent to a UnitMap and so needs to be included in the returned
+   Mapping list. */
+   if( result ) {
+      result = 1;
+
+/* If inode1 is associated with a Frame, which is not equivalent to a
+   UnitMap, add the Frame as the first Mapping into the returned list. The
+   "forward" value is irrelevant since the forward and inverse transformations
+   of Frames are the same. */
+      frame = frames[ inode1 ];
+      if( frame ) {
+         if( !astIsUnitFrame( frame ) ) {
+            result++;
+            *map = (AstMapping *) frame;
+            *forward = 1;
+         }
+      }
+
+/* If the nodes are different, we now attempt to find the next step in
+   the path between them. Loop through all available nodes looking for
+   the next one to transform to (i.e. one that is directly related by
+   a Mapping to our starting node). */
+   } else {
+      for ( inode = 0; inode < this->nnode; inode++ ) {
+
+/* Do not consider node "avoid". This prevents us re-tracing our steps
+   backwards when this function is invoked recursively. */
+         if ( inode != avoid ) {
+
+/* Test if inode is derived from inode1 (if so, the Mapping associated
+   with inode will convert from inode1 to inode when applied in the
+   forward direction). */
+            fwd = ( inode > 0 ) && ( this->link[ inode - 1 ] == inode1 );
+
+/* Test if inode1 is derived from inode (if so, the Mapping associated
+   with inode1 will convert from inode1 to inode when applied in the
+   inverse direction). */
+            inv = ( inode1 > 0 ) && ( this->link[ inode1 - 1 ] == inode );
+
+/* If the nodes are directly related, we try to find a path from inode to
+   inode2 without going back through inode1. */
+            if ( fwd || inv ) {
+
+/* If node1 is associated with a Frame, we need to include the Frame
+   as a Mapping in the returned list unless the Frame is equivalent to a
+   UnitMap. Note the number of slots to be reserved for node1 when we call
+   Span recursively below. */
+               nextra = 1;
+               frame = frames[ inode1 ];
+               if( frame && !astIsUnitFrame( frame ) ) nextra = 2;
+
+/* Invoke this function recursively to try and find a path from inode
+   to inode2 without going back through inode1. If this is possible, a
+   non-zero result will be returned. Store the returned Mappings and
+   direction information in the arrays supplied, but leave extra space to
+   insert information about the Mapping between nodes inode1 and inode. */
+               result = Span( this, frames, inode, inode2, inode1,
+                              map + nextra, forward + nextra, status );
+
+/* If a path was found, increment the Mapping count to account for the
+   one that transforms between nodes inode1 and inode and insert
+   information for this Mapping into the output arrays. */
+               if ( result ) {
+                  result++;
+                  nextra--;
+                  map[ nextra ] = this->map[ ( fwd ? inode : inode1 ) - 1 ];
+                  forward[ nextra ] = fwd;
+
+/* Obtain the original value of the Invert attribute for the Mapping
+   between nodes inode1 and inode (recorded when the Mapping was first
+   added to the FrameSet). Test if this value has now changed. If so,
+   some external code has inverted the Mapping via another pointer, so
+   invert the returned direction information to compensate for
+   this. */
+                  invert = this->invert[ ( fwd ? inode : inode1 ) - 1 ];
+                  if ( invert != astGetInvert( map[ nextra ] ) ) {
+                     forward[ nextra ] = !forward[ nextra ];
+                  }
+
+/* If inode1 is associated with a non-unit Frame Mapping, add the Frame
+   Mapping in as the first Mapping in the returned list. The "forward" value
+   is irrelevant since the forward and inverse transformations of Frames
+   are the same. */
+                  if( nextra ) {
+                     result++;
+                     *map = (AstMapping *) frame;
+                     *forward = 1;
+                  }
+
+/* Quit searching once a path has been found. */
+                  break;
+	       }
+	    }
+         }
+      }
+   }
+
+/* Return the result, which is one more than the number of mappings
+   found (i.e. steps in the path), or zero if no path was found (this
+   should only occur when invoked recursively to explore an
+   unsuccessful sub-path). */
+   return result;
+}
+
+static int SubFrame( AstFrame *this_frame, AstFrame *template,
+                     int result_naxes,
+                     const int *target_axes, const int *template_axes,
+                     AstMapping **map, AstFrame **result, int *status ) {
+/*
+*  Name:
+*     SubFrame
+
+*  Purpose:
+*     Select axes from a FrameSet and convert to the new coordinate system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int SubFrame( AstFrame *target, AstFrame *template, int result_naxes,
+*                   const int *target_axes, const int *template_axes,
+*                   AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astSubFrame
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function selects a requested sub-set (or super-set) of the
+*     axes from the current Frame of a "target" FrameSet and creates a
+*     new Frame with copies of the selected axes assembled in the
+*     requested order. It then optionally overlays the attributes of a
+*     "template" Frame on to the result. It returns both the resulting
+*     Frame and a Mapping that describes how to convert between the
+*     coordinate systems described by the current Frame of the target
+*     FrameSet and the result Frame. If necessary, this Mapping takes
+*     account of any differences in the Frames' attributes due to the
+*     influence of the template.
+
+*  Parameters:
+*     target
+*        Pointer to the target FrameSet, from whose current Frame the
+*        axes are to be selected.
+*     template
+*        Pointer to the template Frame, from which new attributes for
+*        the result Frame are to be obtained. Optionally, this may be
+*        NULL, in which case no overlaying of template attributes will
+*        be performed.
+*     result_naxes
+*        Number of axes to be selected from the target FrameSet. This
+*        number may be greater than or less than the number of axes in
+*        the FrameSet's current Frame (or equal).
+*     target_axes
+*        Pointer to an array of int with result_naxes elements, giving
+*        a list of the (zero-based) axis indices of the axes to be
+*        selected from the current Frame of the target FrameSet. The
+*        order in which these are given determines the order in which
+*        the axes appear in the result Frame. If any of the values in
+*        this array is set to -1, the corresponding result axis will
+*        not be derived from the target FrameSet, but will be assigned
+*        default attributes instead.
+*     template_axes
+*        Pointer to an array of int with result_naxes elements. This
+*        should contain a list of the template axes (given as
+*        zero-based axis indices) with which the axes of the result
+*        Frame are to be associated. This array determines which axes
+*        are used when overlaying axis-dependent attributes of the
+*        template on to the result. If any element of this array is
+*        set to -1, the corresponding result axis will not receive any
+*        template attributes.
+*
+*        If the template argument is given as NULL, this array is not
+*        used and a NULL pointer may also be supplied here.
+*     map
+*        Address of a location to receive a pointer to the returned
+*        Mapping.  The forward transformation of this Mapping will
+*        describe how to convert coordinates from the coordinate
+*        system described by the current Frame of the target FrameSet
+*        to that described by the result Frame. The inverse
+*        transformation will convert in the opposite direction.
+*     result
+*        Address of a location to receive a pointer to the result Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if coordinate conversion is
+*     possible between the current Frame of the target FrameSet and
+*     the result Frame. Otherwise zero is returned and *map and
+*     *result are returned as NULL (but this will not in itself result
+*     in an error condition). In general, coordinate conversion should
+*     always be possible if no template Frame is supplied but may not
+*     always be possible otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to FrameSet's current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   int match;                    /* Result to be returned */
+
+/* Initialise. */
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame. */
+   fr = astGetFrame( this, AST__CURRENT );
+
+/* Invoke the astSubFrame method for this Frame. */
+   match = astSubFrame( fr, template, result_naxes, target_axes, template_axes,
+                        map, result );
+
+/* Annul the Frame pointer. */
+   fr = astAnnul( fr );
+
+/* If an error occurred, clean up by annulling any returned objects and clear
+   the returned result. */
+   if ( !astOK ) {
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static AstSystemType SystemCode( AstFrame *this_frame, const char *system, int *status ) {
+/*
+*  Name:
+*     SystemCode
+
+*  Purpose:
+*     Convert a string into a coordinate system type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstSystemType SystemCode( AstFrame *this, const char *system, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astSystemCode
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function converts a string used for the external description of
+*     a coordinate system into a Frame coordinate system type code (System
+*     attribute value). It is the inverse of the astSystemString function.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        Pointer to a constant null-terminated string containing the
+*        external description of the coordinate system.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System type code.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM is returned if the coordinate system
+*     description was not recognised. This does not produce an error.
+*     - A value of AST__BADSYSTEM is also returned if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;      /* Result value to return */
+   AstFrame *fr;              /* Pointer to FrameSet's current Frame */
+   AstFrameSet *this;         /* Pointer to the FrameSet structure */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astSystemCode method for this Frame. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astSystemCode( fr, system );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BADSYSTEM;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *SystemString( AstFrame *this_frame, AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SystemString
+
+*  Purpose:
+*     Convert a coordinate system type code into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     const char *SystemString( AstFrame *this, AstSystemType system, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astSystemString
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function converts a Frame coordinate system type code
+*     (System attribute value) into a string suitable for use as an
+*     external representation of the coordinate system type.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to FrameSet's current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astSystemString method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astSystemString( fr, system );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result pointer. */
+   return result;
+
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astTestAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a FrameSet's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* We first handle attributes that apply to the FrameSet as a whole
+   (rather than to the current Frame). */
+
+/* Base. */
+/* ----- */
+   if ( !strcmp( attrib, "base" ) ) {
+      result = astTestBase( this );
+
+/* Current. */
+/* -------- */
+   } else if ( !strcmp( attrib, "current" ) ) {
+      result = astTestCurrent( this );
+
+/* ID. */
+/* --- */
+   } else if ( !strcmp( attrib, "id" ) ) {
+      result = astTestID( this );
+
+/* Ident. */
+/* ------ */
+   } else if ( !strcmp( attrib, "ident" ) ) {
+      result = astTestIdent( this );
+
+/* Invert. */
+/* ------- */
+   } else if ( !strcmp( attrib, "invert" ) ) {
+      result = astTestInvert( this );
+
+/* Report. */
+/* ------- */
+   } else if ( !strcmp( attrib, "report" ) ) {
+      result = astTestReport( this );
+
+/* Variant. */
+/* -------- */
+   } else if ( !strcmp( attrib, "variant" ) ) {
+      result = astTestVariant( this );
+
+/* If the name is not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then return
+   zero. */
+   } else if ( !strcmp( attrib, "allvariants" ) ||
+               !strcmp( attrib, "class" ) ||
+               !strcmp( attrib, "nframe" ) ||
+               !strcmp( attrib, "nin" ) ||
+               !strcmp( attrib, "nobject" ) ||
+               !strcmp( attrib, "nout" ) ||
+               !strcmp( attrib, "refcount" ) ||
+               !strcmp( attrib, "tranforward" ) ||
+               !strcmp( attrib, "traninverse" ) ) {
+      result = 0;
+
+/* Pass unrecognised attributes on to the FrameSet's current Frame for
+   further interpretation. */
+   } else {
+
+/* Obtain a pointer to the current Frame and invoke its astTestAttrib
+   method. Annul the Frame pointer afterwards. */
+      fr = astGetFrame( this, AST__CURRENT );
+      result = astTestAttrib( fr, attrib );
+      fr = astAnnul( fr );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int TestBase( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astTestBase
+
+*  Purpose:
+*     Determine if a value has been set for the Base attribute of a FrameSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int astTestBase( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function returns a boolean result to indicate if a value
+*     has been set for the Base attribute of a FrameSet. This
+*     attribute is an index that identifies the base Frame in the
+*     FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+
+*  Returned Value:
+*     Zero or 1, depending on whether a value has been set.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                   /* FrameSet is inverted? */
+   int result;                   /* Value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Determine if the FrameSet has been inverted. */
+   invert = astGetInvert( this );
+
+/* If it has not been inverted, test the base Frame index, otherwise
+   test the index of the current Frame instead. */
+   if ( astOK ) {
+      if ( !invert ) {
+         result = ( this->base != -INT_MAX );
+      } else {
+         result = ( this->current != -INT_MAX );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int TestCurrent( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astTestCurrent
+
+*  Purpose:
+*     Test if a value has been set for the Current attribute of a FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int astTestCurrent( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function returns a boolean result to indicate whether a
+*     value has been set for the Current attribute of a FrameSet.
+*     This attribute is an index that identifies the current Frame in
+*     a FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+
+*  Returned Value:
+*     Zero or 1, depending on whether a value has been set.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                   /* FrameSet is inverted? */
+   int result;                   /* Value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Determine if the FrameSet has been inverted. */
+   invert = astGetInvert( this );
+
+/* If it has not been inverted, test the current Frame index,
+   otherwise test the index of the base Frame instead. */
+   if ( astOK ) {
+      if ( !invert ) {
+         result = ( this->current != -INT_MAX );
+      } else {
+         result = ( this->base != -INT_MAX );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int TestVariant( AstFrameSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astTestVariant
+
+*  Purpose:
+*     Determine if a value has been set for the Variant attribute of a FrameSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int astTestVariant( AstFrameSet *this )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function returns a boolean result to indicate if a value
+*     has been set for the Variant attribute of a FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+
+*  Returned Value:
+*     Zero or 1, depending on whether a value has been set.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstFrameSet *vfs;
+   int result;
+   int icur;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the one-based index of the Frame to use. */
+   icur = GetVarFrm( this, astGetCurrent( this ), status );
+
+/* Get a pointer to the Variants FrameSet in the current Frame. */
+   frm = astGetFrame( this, icur );
+   vfs = astGetFrameVariants( frm );
+
+/* If it is null, return zero, otherwise 1. */
+   result = vfs ? 1 : 0;
+
+/* Annul pointers. */
+   if( vfs ) vfs = astAnnul( vfs );
+   frm = astAnnul( frm );
+
+/* Return the result. */
+   return result;
+}
+
+static void TidyNodes( AstFrameSet *this, int *status ) {
+/*
+*  Name:
+*     TidyNodes
+
+*  Purpose:
+*     Tidy the nodes in a FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void TidyNodes( AstFrameSet *this, int *status )
+
+*  Class Membership:
+*     FrameSet member function.
+
+*  Description:
+*     This function tidies the nodes in a FrameSet, removing any that
+*     are unnecessary or represent dead-ends. It should be used after
+*     any changes have been made to a FrameSet that may have reduced
+*     the number of references to any of its nodes (either by Frames
+*     or by other nodes).
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstMapping *newmap;           /* Pointer to simplified Mapping */
+   AstMapping *tmpmap;           /* Pointer to new compound Mapping */
+   int ifr;                      /* Loop counter for Frames */
+   int inode;                    /* Loop counter for nodes */
+   int last_link[ 2 ];           /* Last nodes to reference via "link" array */
+   int link_ref;                 /* Number of "link" array references */
+   int needed;                   /* Node still required? */
+   int next;                     /* Node which references the removed one */
+   int remove;                   /* Node to be removed */
+   int suspect;                  /* Loop counter for testing nodes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Loop to search for unnecessary nodes until no more are found. */
+   needed = 0;
+   while ( !needed ) {
+
+/* Inspect each node (including node zero, which does not actually
+   have a Mapping associated with it) to see how many times it is
+   referenced. */
+      for ( suspect = 0; suspect < this->nnode; suspect++ ) {
+         link_ref = 0;
+
+/* Test for at least one reference from within the "node" array which
+   associates Frames with particular nodes. */
+         for ( ifr = 1; ifr <= this->nframe; ifr++ ) {
+            if ( ( needed = ( this->node[ ifr - 1 ] == suspect ) ) ) break;
+         }
+
+/* If no references were found above, look for references in the
+   "link" array that inter-connects all the nodes. */
+         if ( !needed ) {
+            for ( inode = 1; inode < this->nnode; inode ++ ) {
+               if ( this->link[ inode - 1 ] == suspect ) {
+
+/* Node zero must be retained if it has more than two links
+   referencing it, while other nodes only require more than one. */
+                  if ( ( needed = ( link_ref >= ( suspect ? 1 : 2 ) ) ) ) break;
+
+/* Remember (up to) the first two nodes which reference the current one. */
+                  last_link[ link_ref++ ] = inode;
+               }
+            }
+         }
+
+/* If there were insufficient references to retain this node, we must
+   now decide why it should be removed. */
+         if ( !needed ) {
+
+/* If there is no Frame associated with a node and there are less than
+   two links to it (for node zero), or less then one link (for other
+   nodes), then the there is no route to anything else via this node.
+   It is a dead-end. */
+            if ( link_ref < ( suspect ? 1 : 2 ) ) {
+
+/* To tidy up, we remove the affected node or, for node zero, the
+   remaining one that references it. Annul the Mapping associated with
+   the node being removed. */
+               remove = suspect ? suspect : last_link[ 0 ];
+               this->map[ remove - 1 ] = astAnnul( this->map[ remove - 1 ] );
+
+/* If an unnecessary node is not a dead-end, then it is a redundant
+   node which simply joins two Mappings. */
+            } else {
+
+/* To tidy up, we remove the affected node or, for node zero, the
+   first one that references it. */
+               remove = suspect ? suspect : last_link[ 0 ];
+
+/* We then produce a compound Mapping which spans the gap by
+   concatenating the Mappings associated with the node being removed
+   and the remaining one which references it. For node zero, the first
+   of these Mappings must be inverted because there are no out-going
+   Mappings from node zero. */
+               next = suspect ? last_link[ 0 ] : last_link[ 1 ];
+               tmpmap = CombineMaps( this->map[ remove - 1 ],
+                                     this->invert[ remove - 1 ] != !suspect,
+                                     this->map[ next - 1 ],
+                                     this->invert[ next - 1 ], 1, status );
+
+/* Simplify this compound Mapping. */
+               newmap = astSimplify( tmpmap );
+               tmpmap = astAnnul( tmpmap );
+
+/* Annul the individual Mapping pointers. */
+               this->map[ remove - 1 ] = astAnnul( this->map[ remove - 1 ] );
+               this->map[ next - 1 ] = astAnnul( this->map[ next - 1 ] );
+
+/* Install the new compound Mapping and its Invert flag. */
+               this->map[ next - 1 ] = newmap;
+               this->invert[ next - 1 ] = astGetInvert( newmap );
+
+/* Transfer the "link" value from the removed node to the one which
+   takes its place. */
+               this->link[ next - 1 ] = this->link[ remove - 1 ];
+            }
+
+/* Loop to move all subsequent node data down in the "map", "invert"
+   and "link" arrays to close the gap where a node has been
+   removed. */
+            for ( inode = remove; inode < this->nnode - 1; inode ++ ) {
+               this->map [ inode - 1 ] = this->map[ inode ];
+               this->link [ inode - 1 ] = this->link[ inode ];
+               this->invert[ inode - 1 ] = this->invert[ inode ];
+            }
+            this->map[ this->nnode - 2 ] = NULL;
+            this->link[ this->nnode - 2 ] = -1;
+            this->invert[ this->nnode - 2 ] = -1;
+
+/* Decrement the node count. */
+            this->nnode--;
+
+/* Loop to adjust each entry in the "node" array for the change in
+   node numbering, re-directing references to the removed node towards
+   the new node zero. */
+            for ( ifr = 1; ifr <= this->nframe; ifr++ ) {
+               if ( this->node[ ifr - 1 ] > remove ) {
+                  this->node[ ifr - 1 ]--;
+               } else if ( this->node[ ifr - 1 ] == remove ) {
+                  this->node[ ifr - 1 ] = 0;
+               }
+            }
+
+/* Similarly adjust each entry in the "link" array. */
+            for ( inode = 1; inode < this->nnode; inode++ ) {
+               if ( this->link[ inode - 1 ] > remove ) {
+                  this->link[ inode - 1 ]--;
+               } else if ( this->link[ inode - 1 ] == remove ) {
+                  this->link[ inode - 1 ] = 0;
+               }
+            }
+
+/* Once a node has been removed, other nodes (perhaps already tested)
+   may no longer be needed, so quit the testing loop and start testing
+   again with node zero. The process terminates when no more
+   unnecessary nodes can be found. */
+            break;
+         }
+      }
+   }
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the astTransform method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function takes a FrameSet and a set of points encapsulated
+*     in a PointSet, and applies either the forward or inverse
+*     coordinate transformation (if defined by the FrameSet) to the
+*     points. The forward transformation converts between the
+*     FrameSet's base Frame and its current Frame, while the inverse
+*     transformation converts in the opposite direction.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     - An error will result if the FrameSet supplied does not define
+*     the requested coordinate transformation (either forward or
+*     inverse).
+*     - The number of coordinate values per point in the input
+*     PointSet must match the number of input coordinates for the
+*     FrameSet being applied (or number of output coordinates if the
+*     inverse transformation is requested).  This will be equal to the
+*     number of axes in the FrameSet's base Frame (or the current
+*     Frame for the inverse transformation).
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points and coordinate values per point to
+*     accommodate the result (e.g. the number of FrameSet output
+*     coordinates, or number of input coordinates if the inverse
+*     transformation is requested). Any excess space will be ignored.
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   AstMapping *map;              /* Pointer to the base->current Mapping */
+   AstPointSet *result;          /* Pointer value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_mapping;
+
+/* Obtain the Mapping between the base and current Frames in the
+   FrameSet (note this takes account of whether the FrameSet has been
+   inverted). */
+   map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Apply the Mapping to the input PointSet. */
+   result = astTransform( map, in, forward, out );
+
+/* Annul the Mapping pointer. */
+   map = astAnnul( map );
+
+/* If an error has occurred and a new PointSet may have been created, then
+   clean up by annulling it. In any case, ensure that a NULL result is
+   returned.*/
+   if ( !astOK ) {
+      if ( !out ) result = astAnnul( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static int Unformat( AstFrame *this_frame, int axis, const char *string,
+                     double *value, int *status ) {
+/*
+*  Name:
+*     Unformat
+
+*  Purpose:
+*     Read a formatted coordinate value for a FrameSet axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int Unformat( AstFrame *this, int axis, const char *string,
+*                   double *value, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the public astUnformat
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function reads a formatted coordinate value for a FrameSet
+*     axis (supplied as a string) and returns the equivalent numerical
+*     value as a double. It also returns the number of characters read
+*     from the string.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     axis
+*        The number of the FrameSet axis for which the coordinate
+*        value is to be read (axis numbering starts at zero for the
+*        first axis).
+*     string
+*        Pointer to a constant null-terminated string containing the
+*        formatted coordinate value.
+*     value
+*        Pointer to a double in which the coordinate value read will be
+*        returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of characters read from the string to obtain the
+*     coordinate value.
+
+*  Notes:
+*     - Any white space at the beginning of the string will be
+*     skipped, as also will any trailing white space following the
+*     coordinate value read. The function's return value will reflect
+*     this.
+*     - A function value of zero (and no coordinate value) will be
+*     returned, without error, if the string supplied does not contain
+*     a suitably formatted value.
+*     - The string "<bad>" is recognised as a special case and will
+*     generate the value AST__BAD, without error. The test for this
+*     string is case-insensitive and permits embedded white space.
+*     - A function result of zero will be returned and no coordinate
+*     value will be returned via the "value" pointer if this function
+*     is invoked with the global error status set, or if it should
+*     fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   double coord;                 /* Coordinate value read */
+   int nc;                       /* Number of characters read */
+
+/* Initialise. */
+   nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return nc;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astUnformat" );
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astUnformat method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   nc = astUnformat( fr, axis, string, &coord );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the number of characters read. */
+   if ( !astOK ) {
+      nc = 0;
+
+/* Otherwise, if characters were read, return the coordinate value. */
+   } else if ( nc ) {
+      *value = coord;
+   }
+
+/* Return the number of characters read. */
+   return nc;
+}
+
+static int ValidateAxis( AstFrame *this_frame, int axis, int fwd,
+                         const char *method, int *status ) {
+/*
+*  Name:
+*     ValidateAxis
+
+*  Purpose:
+*     Validate and permute a FrameSet's axis index.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int ValidateAxis( AstFrame *this, int axis, int fwd, const char *method,
+*                       int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected
+*     astValidateAxis method inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of an index (zero-based) which
+*     is to be used to address one of the coordinate axes of the
+*     current Frame in a FrameSet. If the index is valid, it is
+*     permuted using the axis permutation array associated with the
+*     FrameSet's current Frame and the (zero-based) permuted axis
+*     index is returned.  This gives the index the axis had when the
+*     Frame was first created. If the axis index supplied is not
+*     valid, an error is reported and the global error status is set.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     axis
+*        The axis index (zero-based) to be checked. To be valid, it
+*        must lie between zero and (naxes-1) inclusive, where "naxes"
+*        is the number of coordinate axes associated with the
+*        FrameSet's current Frame.
+*     fwd
+*        If non-zero, the suppplied axis index is assumed to be an
+*        "external" axis index, and the corresponding "internal" axis index
+*        is returned as the function value. Otherwise, the suppplied axis
+*        index is assumed to be an "internal" axis index, and the
+*        corresponding "external" axis index is returned as the function
+*        value.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The permuted axis index - either "internal" or "external" as
+*     specified by "fwd".
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   int naxes;                    /* Number of FrameSet axes */
+   int result;                   /* Permuted axis index */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Determine the number of FrameSet axes. */
+   naxes = astGetNaxes( this );
+   if ( astOK ) {
+
+/* If the FrameSet has no axes, report an error (convert to 1-based
+   axis numbering for the benefit of the public interface). */
+      if ( naxes == 0 ) {
+         astError( AST__AXIIN, "%s(%s): Invalid attempt to use an axis index "
+                   "(%d) for a %s which has no axes.", status, method,
+                   astGetClass( this ), axis + 1, astGetClass( this ) );
+
+/* Otherwise, check the axis index for validity and report an error if
+   it is not valid (again, convert to 1-based axis numbering). */
+      } else if ( ( axis < 0 ) || ( axis >= naxes ) ) {
+         astError( AST__AXIIN, "%s(%s): Axis index (%d) invalid - it should "
+                   "be in the range 1 to %d.", status, method, astGetClass( this ),
+                   axis + 1, naxes );
+
+/* If the axis index was valid, obtain a pointer to the FrameSet's
+   current Frame and invoke this Frame's astValidateAxis method to
+   obtain the permuted axis index. Annul the Frame pointer
+   afterwards. */
+      } else {
+         fr = astGetFrame( this, AST__CURRENT );
+         result = astValidateAxis( fr, axis, fwd, "astValidateAxis" );
+         fr = astAnnul( fr );
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static void ValidateAxisSelection( AstFrame *this_frame, int naxes,
+                                   const int *axes, const char *method, int *status ) {
+/*
+*  Name:
+*     ValidateAxisSelection
+
+*  Purpose:
+*     Check that a set of axes selected from a Frame is valid.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void ValidateAxisSelection( AstFrame *this, int naxes,
+*                                 const int *axes, const char *method, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astValidateAxisSelection
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of an array of (zero-based)
+*     axis indices that specify a set of axes to be selected from a
+*     Frame. To be valid, no axis should be selected more than
+*     once. In assessing this, any axis indices that do not refer to
+*     valid Frame axes (e.g. are set to -1) are ignored.
+*
+*     If the axis selection is valid, this function returns without further
+*     action. Otherwise, an error is reported and the global error status is
+*     set.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     naxes
+*        The number of axes to be selected (may be zero).
+*     axes
+*        Pointer to an array of int with naxes elements that contains the
+*        (zero based) axis indices to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis selection. This method name is used
+*        solely for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke this
+   Frame's astValidateAxisSelection method. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   astValidateAxisSelection( fr, naxes, axes, method );
+   fr = astAnnul( fr );
+
+}
+
+static int ValidateFrameIndex( AstFrameSet *this, int iframe,
+                               const char *method, int *status ) {
+/*
+*+
+*  Name:
+*     astValidateFrameIndex
+
+*  Purpose:
+*     Validate a FrameSet Frame index number.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int astValidateFrameIndex( AstFrameSet *this, int iframe,
+*                                const char *method )
+
+*  Class Membership:
+*     FrameSet method.
+
+*  Description:
+*     This function checks a (one-based) FrameSet Frame index for
+*     validity. If it is not valid, an error is reported. Otherwise,
+*     the function returns the Frame index value, having translated
+*     the special values AST__CURRENT and AST__BASE into valid Frame
+*     indices if necessary.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     iframe
+*        The Frame index. To be valid this should lie in the range 1
+*        to the number of Frames in the FrameSet.  In addition, the
+*        values AST__CURRENT and AST__BASE may be given to indicate
+*        the "current" and "base" Frames. These values will be
+*        translated into the acceptable range.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate a Frame index. This method name is used solely
+*        for constructing error messages.
+
+*  Returned Value:
+*     The validated (one-based) Frame index.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason
+*     (e.g. if the Frame index is invalid).
+*-
+*/
+
+/* Local Variables: */
+   int nframe;                   /* Number of Frames */
+   int result;                   /* Returned index value */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check if the base or current Frame was specified and retrieve the
+   required Frame index from the FrameSet. */
+   if ( iframe == AST__BASE ) {
+      result = astGetBase( this );
+   } else if ( iframe == AST__CURRENT ) {
+      result = astGetCurrent( this );
+
+/* Otherwise, determine how many Frames there are in the FrameSet. */
+   } else {
+      nframe = astGetNframe( this );
+      if ( astOK ) {
+
+/* Check that the supplied index is within range and report an error
+   if it is not. */
+         if ( ( iframe < 1 ) || ( iframe > nframe ) ) {
+            astError( AST__FRMIN, "%s(%s): Invalid Frame index (%d) given.", status,
+                      method, astGetClass( this ), iframe );
+            astError( AST__FRMIN, "This value should be in the range 1 to "
+                      "%d (or AST__CURRENT or AST__BASE).", status, nframe );
+
+/* If OK, return the validated index value. */
+         } else {
+            result = iframe;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int ValidateSystem( AstFrame *this_frame, AstSystemType system, const char *method, int *status ) {
+/*
+*  Name:
+*     ValidateSystem
+
+*  Purpose:
+*     Validate a value for a Frame's System attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     int ValidateSystem( AstFrame *this, AstSystemType system,
+*                         const char *method, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astValidateSystem
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of the supplied system value.
+*     If the value is valid, it is returned unchanged. Otherwise, an
+*     error is reported and a value of AST__BADSYSTEM is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The system value to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The validated system value.
+
+*  Notes:
+*     - A value of AST_BADSYSTEM will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSystemType result;      /* Validated system value */
+   AstFrame *fr;              /* Pointer to FrameSet's current Frame */
+   AstFrameSet *this;         /* Pointer to the FrameSet structure */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_frame;
+
+/* Obtain a pointer to the FrameSet's current Frame and invoke the
+   astValidateSystem method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this, AST__CURRENT );
+   result = astValidateSystem( this, system, method );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BADSYSTEM;
+
+/* Return the result. */
+   return result;
+}
+
+static void VSet( AstObject *this_object, const char *settings,
+                  char **text, va_list args, int *status ) {
+/*
+*  Name:
+*     VSet
+
+*  Purpose:
+*     Set values for a FrameSet's attributes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void VSet( AstObject *this, const char *settings, char **text,
+*                va_list args, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astVSet
+*     method inherited from the Object class).
+
+*  Description:
+*     This function assigns a set of attribute values for a FrameSet,
+*     the attributes and their values being specified by means of a
+*     string containing a comma-separated list of the form:
+*
+*        "attribute1 = value1, attribute2 = value2, ... "
+*
+*     Here, "attribute" specifies an attribute name and the value to
+*     the right of each "=" sign should be a suitable textual
+*     representation of the value to be assigned to that
+*     attribute. This will be interpreted according to the attribute's
+*     data type.
+*
+*     The string supplied may also contain "printf"-style format
+*     specifiers identified by a "%" sign in the usual way. If
+*     present, these will be substituted by values supplied as
+*     optional arguments (as a va_list variable argument list), using
+*     the normal "printf" rules, before the string is used.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet.
+*     settings
+*        Pointer to a null-terminated string containing a
+*        comma-separated list of attribute settings.
+*     text
+*        Pointer to a location at which to return a pointer to dynamic
+*        memory holding a copy of the expanded setting string. This memory
+*        should be freed using astFree when no longer needed. If a NULL
+*        pointer is supplied, no string is created.
+*     args
+*        The variable argument list which contains values to be
+*        substituted for any "printf"-style format specifiers that
+*        appear in the "settings" string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function preserves the integrity of the FrameSet (if
+*     possible) by appropriately remapping its current Frame to take
+*     account of its changed attribute values.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstFrame *save_frame;         /* Saved pointer to integrity Frame */
+   AstFrameSet *this;            /* Pointer to FrameSet structure */
+   char *fulltext;               /* Pointer to expanded text string */
+   const char *save_method;      /* Saved pointer to method name */
+   int len;                      /* Length of settings string */
+   int ok;                       /* Status OK? */
+   int save_lost;                /* Saved integrity modified flag */
+
+/* Initialise */
+   if( text ) *text = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain the length of the "settings" string and test it is not
+   zero. If it is, there is nothing more to do. */
+   len = (int) strlen( settings );
+   if ( len != 0 ) {
+
+/* Obtain a pointer to the FrameSet structure. */
+      this = (AstFrameSet *) this_object;
+
+/* This function may be invoked recursively (because astConvert,
+   below, constructs a FrameSet and may require that its attributes be
+   set). To allow this, we first save any existing FrameSet integrity
+   information in local variables. */
+      save_frame = integrity_frame;
+      save_lost = integrity_lost;
+      save_method = integrity_method;
+
+/* Set the name of the method being used (for use in error
+   messages). */
+      integrity_method = "astSet";
+
+/* Record the initial integrity state of the FrameSet. */
+      RecordIntegrity( this, status );
+
+/* Invoke the parent astVSet method to set the FrameSet's attribute
+   values and note if this succeeds. */
+      (*parent_vset)( this_object, settings, &fulltext, args, status );
+      ok = astOK;
+
+/* Restore the FrameSet's integrity. */
+      RestoreIntegrity( this, status );
+
+/* If integrity could not be restored, then add contextual error
+   information. */
+      if ( !astOK && ok ) {
+
+/* Display the message. */
+         astError( astStatus, "Unable to accommodate the attribute setting "
+                               "\"%s\".", status, fulltext );
+      }
+
+/* Restore any saved FrameSet integrity information. */
+      integrity_frame = save_frame;
+      integrity_lost = save_lost;
+      integrity_method = save_method;
+
+/* If the full text of the setting string is not needed, free it.
+   Otherwise return it. */
+      if( text ) {
+         *text = fulltext;
+      } else {
+         fulltext = astFree( fulltext );
+      }
+   }
+}
+
+/* FrameSet Attributes. */
+/* -------------------- */
+/*
+*att++
+*  Name:
+*     AllVariants
+
+*  Purpose:
+*     A list of the variant Mappings associated with the current Frame.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String, read-only.
+
+*  Description:
+*     This attribute gives a space separated list of the names of all the
+*     variant Mappings associated with the current Frame (see attribute
+*     "Variant"). If the current Frame has no variant Mappings, then the
+*     list will hold a single entry equal to the Domain name of the
+*     current Frame.
+
+*  Applicability:
+*     FrameSet
+*        All FrameSets have this attribute.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     Base
+
+*  Purpose:
+*     FrameSet base Frame index.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute gives the index of the Frame which is to be
+*     regarded as the "base" Frame within a FrameSet. The default is
+*     the first Frame added to the FrameSet when it is created (this
+*     Frame always has an index of 1).
+*
+*     When setting a new value for this attribute, a string may be
+*     supplied instead of an integer index. In this case a search
+*     is made within the FrameSet for a Frame that has its Domain
+*     attribute value equal to the supplied string (the comparison is
+*     case-insensitive). If found, the Frame is made the base Frame.
+*     Otherwise an error is reported.
+
+*  Applicability:
+*     FrameSet
+*        All FrameSets have this attribute.
+
+*  Notes:
+*     - Inverting a FrameSet (inverting the boolean sense of its
+c     Invert attribute, with the astInvert function for example) will
+f     Invert attribute, with the AST_INVERT routine for example) will
+*     interchange the values of its Base and Current attributes.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     Current
+
+*  Purpose:
+*     FrameSet current Frame index.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute gives the index of the Frame which is to be
+*     regarded as the "current" Frame within a FrameSet. The default
+*     is the most recent Frame added to the FrameSet (this Frame
+*     always has an index equal to the FrameSet's Nframe attribute).
+*
+*     When setting a new value for this attribute, a string may be
+*     supplied instead of an integer index. In this case a search
+*     is made within the FrameSet for a Frame that has its Domain
+*     attribute value equal to the supplied string (the comparison is
+*     case-insensitive). If found, the Frame is made the current Frame.
+*     Otherwise an error is reported.
+
+*  Applicability:
+*     FrameSet
+*        All FrameSets have this attribute.
+
+*  Notes:
+*     - Inverting a FrameSet (inverting the boolean sense of its
+c     Invert attribute, with the astInvert function for example) will
+f     Invert attribute, with the AST_INVERT routine for example) will
+*     interchange the values of its Base and Current attributes.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     Nframe
+
+*  Purpose:
+*     Number of Frames in a FrameSet.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute gives the number of Frames in a FrameSet. This
+*     value will change as Frames are added or removed, but will
+*     always be at least one.
+
+*  Applicability:
+*     FrameSet
+*        All FrameSets have this attribute.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     Variant
+
+*  Purpose:
+*     Indicates which variant of the current Frame is to be used.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute can be used to change the Mapping that connects the
+*     current Frame to the other Frames in the FrameSet. By default, each
+*     Frame in a FrameSet is connected to the other Frames by a single
+*     Mapping that can only be changed by using the
+c     astRemapFrame
+f     AST_REMAPFRAME
+*     method. However, it is also possible to associate multiple Mappings
+*     with a Frame, each Mapping having an identifying name. If this is
+*     done, the "Variant" attribute can be set to indicate the name of
+*     the Mapping that is to be used with the current Frame.
+*
+*     A possible (if unlikely) use-case is to create a FrameSet that can
+*     be used to describe the WCS of an image formed by co-adding images
+*     of two different parts of the sky. In such an image, each pixel contains
+*     flux from two points on the sky.and so the WCS for the image should
+*     ideally contain one pixel Frame and two SkyFrames - one describing
+*     each of the two co-added images. There is nothing to prevent a
+*     FrameSet containing two explicit SkyFrames, but the problem then arises
+*     of how to distinguish between them. The two primary characteristics of
+*     a Frame that distinguishes it from other Frames are its class and its
+*     Domain attribute value. The class of a Frame cannot be changed, but we
+*     could in principle use two different Domain values to distinguish the
+*     two SkyFrames. However, in practice it is not uncommon for application
+*     software to assume that SkyFrames will have the default Domain value
+*     of "SKY". That is, instead of searching for Frames that have a class
+*     of "SkyFrame", such software searches for Frames that have a Domain
+*     of "SKY". To alleviate this problem, it is possible to add a single
+*     SkyFrame to the FrameSet, but specifying two alternate Mappings to
+*     use with the SkyFrame. Setting the "Variant" attribute to the name
+*     of one or the other of these alternate Mappings will cause the
+*     SkyFrame to be remapped within the FrameSet so that it uses the
+*     specified Mapping. The same facility can be used with any class of
+*     Frame, not just SkyFrames.
+*
+*     To use this facility, the Frame should first be added to the
+*     FrameSet in the usual manner using the
+c     astAddFrame method. By default, the Mapping supplied to astAddFrame
+f     AST_ADDFRAME method. By default, the Mapping supplied to AST_ADDVARIANT
+*     is assigned a name equal to the Domain name of the Frame. To assign a
+*     different name to it, the
+c     astAddVariant
+f     AST_ADDVARIANT
+*     method should then be called specifying the required name and a NULL
+*     Mapping. The
+c     astAddVariant
+f     AST_ADDVARIANT
+*     method should then be called repeatedly to add each required extra
+*     Mapping to the current Frame, supplying a unique name for each one.
+*
+*     Each Frame in a FrameSet can have its own set of variant Mappings.
+*     To control the Mappings in use with a specific Frame, you need first
+*     to make it the current Frame in the FrameSet.
+*
+*     The
+c     astMirrorVariants function
+f     AST_MIRRORVARIANTS routine
+*     allows the effects of variant Mappings associated with a nominated
+*     Frame to be propagated to other Frames in the FrameSet.
+*
+*     Once this has been done, setting a new value for the "Variant"
+*     attribute of a FrameSet will cause the current Frame in the
+*     FrameSet to be remapped to use the specified variant Mapping. An
+*     error will be reported if the current Frame has no variant Mapping
+*     with the supplied name.
+*
+*     Getting the value of the "Variant" attribute will return the name
+*     of the variant Mapping currently in use with the current Frame. If
+*     the Frame has no variant Mappings, the value will default to the
+*     Domain name of the current Frame.
+*
+*     Clearing the "Variant" attribute will have the effect of removing
+*     all variant Mappings (except for the currently selected Mapping) from
+*     the current Frame.
+*
+*     Testing the "Variant" attribute will return
+c     a non-zero value
+f     .TRUE.
+*     if the current Frame contains any variant Mappings, and
+c     zero
+f     .FALSE.
+*     otherwise.
+*
+*     A complete list of the names associated with all the available
+*     variant Mappings in the current Frame can be obtained from the
+*     AllVariants attribute.
+*
+*     If a Frame with variant Mappings is remapped using the
+c     astRemapFrame
+f     AST_REMAPFRAME
+*     method, the currently selected variant Mapping is used by
+c     astRemapFrame
+f     AST_REMAPFRAME
+*     and the other variant Mappings are removed from the Frame.
+
+*  Applicability:
+*     FrameSet
+*        All FrameSets have this attribute.
+*att--
+*/
+
+/* Access to attributes of the current Frame. */
+/* ------------------------------------------ */
+/* Use the macros defined at the start of this file to implement
+   private member functions that give access to the attributes of the
+   current Frame of a FrameSet and its axes. These functions over-ride
+   the attribute access methods inherited from the Frame class. */
+
+/* Clear, Get, Set and Test axis-independent Frame attributes. */
+MAKE_CLEAR(Digits)
+MAKE_CLEAR(Domain)
+MAKE_CLEAR(MatchEnd)
+MAKE_CLEAR(MaxAxes)
+MAKE_CLEAR(MinAxes)
+MAKE_CLEAR(Permute)
+MAKE_CLEAR(PreserveAxes)
+MAKE_CLEAR(Title)
+
+MAKE_GET(Digits,int)
+MAKE_GET(Domain,const char *)
+MAKE_GET(MatchEnd,int)
+MAKE_GET(MaxAxes,int)
+MAKE_GET(MinAxes,int)
+MAKE_GET(Permute,int)
+MAKE_GET(PreserveAxes,int)
+MAKE_GET(Title,const char *)
+MAKE_SET(Digits,int)
+MAKE_SET(Domain,const char *)
+MAKE_SET(MatchEnd,int)
+MAKE_SET(MaxAxes,int)
+MAKE_SET(MinAxes,int)
+MAKE_SET(Permute,int)
+MAKE_SET(PreserveAxes,int)
+MAKE_SET(Title,const char *)
+MAKE_TEST(Digits)
+MAKE_TEST(Domain)
+MAKE_TEST(MatchEnd)
+MAKE_TEST(MaxAxes)
+MAKE_TEST(MinAxes)
+MAKE_TEST(Permute)
+MAKE_TEST(PreserveAxes)
+MAKE_TEST(Title)
+
+MAKE_GET(ActiveUnit,int)
+MAKE_SET(ActiveUnit,int)
+MAKE_TEST(ActiveUnit)
+
+MAKE_GET(System,AstSystemType)
+MAKE_SET(System,AstSystemType)
+MAKE_TEST(System)
+MAKE_CLEAR(System)
+
+MAKE_GET(AlignSystem,AstSystemType)
+MAKE_SET(AlignSystem,AstSystemType)
+MAKE_TEST(AlignSystem)
+MAKE_CLEAR(AlignSystem)
+
+MAKE_GET(Epoch,double)
+MAKE_SET(Epoch,double)
+MAKE_TEST(Epoch)
+MAKE_CLEAR(Epoch)
+
+MAKE_GET(ObsLon,double)
+MAKE_SET(ObsLon,double)
+MAKE_TEST(ObsLon)
+MAKE_CLEAR(ObsLon)
+
+MAKE_GET(ObsLat,double)
+MAKE_SET(ObsLat,double)
+MAKE_TEST(ObsLat)
+MAKE_CLEAR(ObsLat)
+
+MAKE_GET(ObsAlt,double)
+MAKE_SET(ObsAlt,double)
+MAKE_TEST(ObsAlt)
+MAKE_CLEAR(ObsAlt)
+
+/* Clear, Get, Set and Test axis-dependent Frame attributes. */
+MAKE_CLEAR_AXIS(Direction)
+MAKE_CLEAR_AXIS(Format)
+MAKE_CLEAR_AXIS(Label)
+MAKE_CLEAR_AXIS(Symbol)
+MAKE_CLEAR_AXIS(Unit)
+MAKE_GET_AXIS(Direction,int)
+MAKE_GET_AXIS(Format,const char *)
+MAKE_GET_AXIS(Label,const char *)
+MAKE_GET_AXIS(Symbol,const char *)
+MAKE_GET_AXIS(Unit,const char *)
+MAKE_SET_AXIS(Direction,int)
+MAKE_SET_AXIS(Format,const char *)
+MAKE_SET_AXIS(Label,const char *)
+MAKE_SET_AXIS(Symbol,const char *)
+MAKE_SET_AXIS(Unit,const char *)
+MAKE_TEST_AXIS(Direction)
+MAKE_TEST_AXIS(Format)
+MAKE_TEST_AXIS(Label)
+MAKE_TEST_AXIS(Symbol)
+MAKE_TEST_AXIS(Unit)
+
+MAKE_GET_AXIS(Bottom,double)
+MAKE_SET_AXIS(Bottom,double)
+MAKE_TEST_AXIS(Bottom)
+MAKE_CLEAR_AXIS(Bottom)
+
+MAKE_GET_AXIS(Top,double)
+MAKE_SET_AXIS(Top,double)
+MAKE_TEST_AXIS(Top)
+MAKE_CLEAR_AXIS(Top)
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for FrameSet objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for FrameSet objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstFrameSet *in;              /* Pointer to input FrameSet */
+   AstFrameSet *out;             /* Pointer to output FrameSet */
+   int iframe;                   /* Loop counter for Frames */
+   int inode;                    /* Loop counter for nodes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output FrameSets. */
+   in = (AstFrameSet *) objin;
+   out = (AstFrameSet *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output FrameSet. */
+   out->frame = NULL;
+   out->varfrm = NULL;
+   out->node = NULL;
+   out->map = NULL;
+   out->link = NULL;
+   out->invert = NULL;
+
+/* Allocate memory in the output FrameSet to store the Frame and node
+   information and copy scalar information across. */
+   out->frame = astMalloc( sizeof( AstFrame * ) * (size_t) in->nframe );
+   out->varfrm = astStore( NULL, in->varfrm, sizeof( int ) *
+                                         (size_t) in->nframe );
+   out->node = astStore( NULL, in->node, sizeof( int ) *
+                                         (size_t) in->nframe );
+   out->map = astMalloc( sizeof( AstMapping * ) * (size_t) ( in->nnode - 1 ) );
+   out->link = astStore( NULL, in->link, sizeof( int ) *
+                                         (size_t) ( in->nnode - 1 ) );
+   out->invert = astStore( NULL, in->invert, sizeof( int ) *
+                                         (size_t) ( in->nnode - 1 ) );
+
+/* If OK, make copies of each input Frame and Mapping and store the
+   resulting pointers in the output FrameSet. */
+   if ( astOK ) {
+      for ( iframe = 0; iframe < in->nframe; iframe++ ) {
+         out->frame[ iframe ] = astCopy( in->frame[ iframe ] );
+      }
+      for ( inode = 0; inode < in->nnode - 1; inode++ ) {
+         out->map[ inode ] = astCopy( in->map[ inode ] );
+      }
+
+/* If an error occurred while copying any of these objects, clean up
+   by looping through the arrays of pointers again and annulling them
+   all. */
+      if ( !astOK ) {
+         for ( iframe = 0; iframe < in->nframe; iframe++ ) {
+            out->frame[ iframe ] = astAnnul( out->frame[ iframe ] );
+         }
+         for ( inode = 0; inode < in->nnode - 1; inode++ ) {
+            out->map[ inode ] = astAnnul( out->map[ inode ] );
+         }
+      }
+   }
+
+/* If an error occurred, clean up by freeing all memory allocated above. */
+   if ( !astOK ) {
+      out->frame = astFree( out->frame );
+      out->varfrm = astFree( out->varfrm );
+      out->node = astFree( out->node );
+      out->map = astFree( out->map );
+      out->link = astFree( out->link );
+      out->invert = astFree( out->invert );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for FrameSet objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for FrameSet objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstFrameSet *this;               /* Pointer to FrameSet */
+   int iframe;                      /* Loop counter for Frames */
+   int inode;                       /* Loop counter for nodes */
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) obj;
+
+/* Annul all Frame pointers and clear the node numbers associated with
+   them. */
+   for ( iframe = 0; iframe < this->nframe; iframe++ ) {
+      this->frame[ iframe ] = astAnnul( this->frame[ iframe ] );
+      this->node[ iframe ] = 0;
+   }
+
+/* Annul all Mapping pointers and clear the links between pairs of
+   nodes and the associated Mapping Invert information. */
+   for ( inode = 0; inode < this->nnode - 1; inode++ ) {
+      this->map[ inode ] = astAnnul( this->map[ inode ] );
+      this->link[ inode ] = 0;
+      this->invert[ inode ] = 0;
+   }
+
+/* Free all allocated memory. */
+   this->frame = astFree( this->frame );
+   this->varfrm = astFree( this->varfrm );
+   this->node = astFree( this->node );
+   this->map = astFree( this->map );
+   this->link = astFree( this->link );
+   this->invert = astFree( this->invert );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for FrameSet objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the FrameSet class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the FrameSet whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define COMMENT_LEN 150          /* Maximum length of a comment string */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstFrameSet *this;            /* Pointer to the FrameSet structure */
+   char comment[ COMMENT_LEN + 1 ]; /* Buffer for comment string */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword string */
+   int ifr;                      /* Loop counter for Frames */
+   int inode;                    /* Loop counter for nodes */
+   int invert;                   /* Invert attribute value */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstFrameSet *) this_object;
+
+/* Determine if the FrameSet is inverted. This is required so that the
+   effects of inversion can be un-done to obtain information about the
+   "true" Base and Current Frames. (The values written are "internal"
+   values that are not affected by the Invert setting.) */
+   invert = astGetInvert( this );
+
+/* Write out values representing the instance variables for the
+   FrameSet class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Nframe. */
+/* ------- */
+   set = ( this->nframe != 0 );
+   ival = set ? this->nframe : astGetNframe( this );
+   astWriteInt( channel, "Nframe", set, 1, ival,
+                "Number of Frames in FrameSet" );
+
+/* Base. */
+/* ----- */
+   set = ( this->base != -INT_MAX );
+   ival = set ? this->base : ( !invert ? astGetBase( this ) :
+                                         astGetCurrent( this ) );
+   astWriteInt( channel, "Base", set, 1, ival, "Index of base Frame" );
+
+/* Current. */
+/* -------- */
+   set = ( this->current != -INT_MAX );
+   ival = set ? this->current : ( !invert ? astGetCurrent( this ) :
+                                            astGetBase( this ) );
+   astWriteInt( channel, "Currnt", set, 1, ival, "Index of current Frame" );
+
+/* Number of nodes. */
+/* ---------------- */
+   ival = this->nnode;
+   set = ( ival != this->nframe );
+   astWriteInt( channel, "Nnode", set, 0, ival,
+                "Number of nodes in FrameSet" );
+
+/* Node index for each Frame. */
+/* -------------------------- */
+/* There is a node value for each Frame in the FrameSet. */
+   for ( ifr = 1; ifr <= this->nframe; ifr++ ) {
+
+/* Convert node numbering to start at 1 for the external
+   representation. Regard a node number as "set" if it differs from
+   the Frame number. */
+      ival = this->node[ ifr - 1 ] + 1;
+      set = ( ival != ifr );
+
+/* Create a suitable keyword and comment. */
+      (void) sprintf( key, "Nod%d", ifr );
+      (void) sprintf( comment,
+                      "Frame %d is associated with node %d", ifr, ival );
+
+/* Write out the value. */
+      astWriteInt( channel, key, set, 0, ival, comment );
+   }
+
+/* Index of variants Frame for each Frame. */
+/* --------------------------------------- */
+   for ( ifr = 1; ifr <= this->nframe; ifr++ ) {
+
+/* Retain the one-based Frame index values in varfrm. Regard a number as
+   "set" if it is greater than zero. */
+      ival = this->varfrm[ ifr - 1 ];
+      set = ( ival > 0 );
+
+/* Create a suitable keyword and comment. */
+      (void) sprintf( key, "VFr%d", ifr );
+      (void) sprintf( comment,
+                      "Frame %d inherits variants from Frame %d", ifr, ival );
+
+/* Write out the value. */
+      astWriteInt( channel, key, set, 0, ival, comment );
+   }
+
+/* Links between nodes. */
+/* -------------------- */
+/* Each node in the FrameSet (except the first) has a link to another
+   node from which it is derived. */
+   for ( inode = 1; inode < this->nnode; inode++ ) {
+
+/* Convert node numbering to start at 1 (as above). */
+      ival = this->link[ inode - 1 ] + 1;
+      (void) sprintf( key, "Lnk%d", inode + 1 );
+      (void) sprintf( comment,
+                      "Node %d is derived from node %d", inode + 1, ival );
+      astWriteInt( channel, key, 1, 0, ival, comment );
+
+/* Inversion flags. */
+/* ---------------- */
+/* Each node with a link has a value which the Invert attribute of the
+   associated Mapping should have when the transformation from the
+   parent node to the node in question is required. */
+      ival = this->invert[ inode - 1 ];
+
+/* Regard the value as set only if the Mapping's inverse
+   transformation is required. */
+      set = ( ival != 0 );
+      (void) sprintf( key, "Inv%d", inode + 1 );
+      astWriteInt( channel, key, set, 0, ival,
+                   ival ? "The inverse mapping is used" :
+                          "The forward mapping is used" );
+   }
+
+/* Frame objects. */
+/* -------------- */
+/* Output an Object description for each Frame in the FrameSet. */
+   for ( ifr = 1; ifr <= this->nframe; ifr++ ) {
+      (void) sprintf( key, "Frm%d", ifr );
+      (void) sprintf( comment, "Frame number %d", ifr );
+      astWriteObject( channel, key, 1, 1, this->frame[ ifr - 1 ],
+                      comment );
+   }
+
+/* Mapping objects. */
+/* ---------------- */
+/* Output an Object description for each Mapping in the FrameSet. */
+   for ( inode = 1; inode < this->nnode; inode++ ) {
+      (void) sprintf( key, "Map%d", inode + 1 );
+      (void) sprintf( comment, "Mapping between nodes %d and %d",
+                      this->link[ inode - 1 ] + 1, inode + 1 );
+      astWriteObject( channel, key, 1, 1, this->map[ inode - 1 ], comment );
+   }
+
+/* Undefine macros local to this function. */
+#undef COMMENT_LEN
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAFrameSet and astCheckFrameSet functions using
+   the macros defined for this purpose in the "object.h" header
+   file. */
+astMAKE_ISA(FrameSet,Frame)
+astMAKE_CHECK(FrameSet)
+
+AstFrameSet *astFrameSet_( void *frame_void, const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astFrameSet
+
+*  Purpose:
+*     Create a FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstFrameSet *astFrameSet( AstFrame *frame, const char *options, int *status, ... )
+
+*  Class Membership:
+*     FrameSet constructor.
+
+*  Description:
+*     This function creates a new FrameSet and optionally initialises
+*     its attributes.
+
+*  Parameters:
+*     frame
+*        Pointer to the initial Frame. If this is not a FrameSet, the
+*        new FrameSet will be initialised to contain this Frame alone.
+*
+*        If it is a FrameSet, the new FrameSet will be initialised to
+*        contain the same Frames (and Mappings) and to have the same
+*        attribute values as the one supplied. This is similar to
+*        making a copy, except that the Frames (and Mappings)
+*        contained in the original FrameSet are not themselves copied,
+*        but are shared by both FrameSets.
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new FrameSet. The syntax used is the same as
+*        for the astSet method and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of arguments may follow it in order to
+*        supply values to be substituted for these specifiers. The
+*        rules for supplying these are identical to those for the
+*        astSet method (and for the C "printf" function).
+
+*  Returned Value:
+*     A pointer to the new FrameSet.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic FrameSet constructor which
+*     is available via the protected interface to the FrameSet class.
+*     A public interface is provided by the astFrameSetId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "frame" parameter is of type (void *) and is converted and
+*     validated within the function itself.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstFrameSet *new;             /* Pointer to new FrameSet */
+   va_list args;                 /* Variable argument list */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain and validate a pointer to the Frame structure provided. */
+   frame = astCheckFrame( frame_void );
+   if ( astOK ) {
+
+/* Initialise the FrameSet, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitFrameSet( NULL, sizeof( AstFrameSet ), !class_init,
+                             &class_vtab, "FrameSet", frame );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   FrameSet's attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new FrameSet. */
+   return new;
+}
+
+AstFrameSet *astInitFrameSet_( void *mem, size_t size, int init,
+                               AstFrameSetVtab *vtab, const char *name,
+                               AstFrame *frame, int *status ) {
+/*
+*+
+*  Name:
+*     astInitFrameSet
+
+*  Purpose:
+*     Initialise a FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstFrameSet *astInitFrameSet( void *mem, size_t size, int init,
+*                                   AstFrameSetVtab *vtab, const char *name,
+*                                   AstFrame *frame )
+
+*  Class Membership:
+*     FrameSet initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new FrameSet object. It allocates memory (if
+*     necessary) to accommodate the FrameSet plus any additional data
+*     associated with the derived class.  It then initialises a
+*     FrameSet structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual
+*     function table for a FrameSet at the start of the memory passed
+*     via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the FrameSet is to be
+*        created. This must be of sufficient size to accommodate the
+*        FrameSet data (sizeof(FrameSet)) plus any data used by the
+*        derived class. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the FrameSet (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the FrameSet structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*     init
+*        A logical flag indicating if the FrameSet's virtual function
+*        table is to be initialised. If this value is non-zero, the
+*        virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new FrameSet.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*     frame
+*        Pointer to the initial Frame (or FrameSet).
+
+*  Returned Value:
+*     A pointer to the new FrameSet.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrameSet *new;                /* Pointer to new FrameSet */
+   AstFrameSet *old;                /* Pointer to original FrameSet */
+   int iframe;                      /* Loop counter for Frames */
+   int inode;                       /* Loop counter for nodes */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitFrameSetVtab( vtab, name );
+
+/* Initialise a Frame structure (the parent class) as the first
+   component within the FrameSet structure, allocating memory if
+   necessary. Give this Frame zero axes, as all axis information for
+   the FrameSet will be derived from the Frames it contains. */
+   new = (AstFrameSet *) astInitFrame( mem, size, 0,
+                                       (AstFrameVtab *) vtab, name, 0 );
+
+   if ( astOK ) {
+
+/* Initialise the FrameSet data. */
+/* ----------------------------- */
+
+/* Normal Frame supplied. */
+/* ---------------------- */
+/* Check that the Frame supplied is not a FrameSet (initialising using
+   a FrameSet is a special case which is handled below).  If not, we
+   initialise the new FrameSet to refer to a single Frame. */
+      if ( !astIsAFrameSet( frame ) && astOK ) {
+
+/* Allocate memory for the arrays of Frame information. */
+         new->frame = astMalloc( sizeof( AstFrame * ) );
+         new->node = astMalloc( sizeof( int ) );
+         new->varfrm = astMalloc( sizeof( int ) );
+
+/* The node arrays are not required until at least two Frames are
+   present. */
+         new->map = NULL;
+         new->link = NULL;
+         new->invert = NULL;
+
+/* If OK, initialise these arrays, thus adding the Frame to the
+   FrameSet. */
+         if ( astOK ) {
+            new->frame[ 0 ] = astClone( frame );
+            new->node[ 0 ] = 0;
+            new->varfrm[ 0 ] = 0;
+            new->nframe = 1;
+            new->nnode = 1;
+
+/* Initialise the FrameSet attributes to their undefined states. */
+            new->base = -INT_MAX;
+            new->current = -INT_MAX;
+         }
+
+/* FrameSet supplied. */
+/* ------------------ */
+/* If a FrameSet was supplied, we will initialise the new FrameSet to
+   refer to the same Frame and Mapping information (this is similar to
+   making a copy, except that we clone all the pointers, instead of
+   copying the Objects they refer to). */
+      } else if ( astOK ) {
+
+/* Obtain a pointer to the original FrameSet structure. */
+         old = (AstFrameSet *) frame;
+
+/* Allocate memory in the new FrameSet to store the Frame and node
+   information and copy any scalar information across. */
+         new->frame = astMalloc( sizeof( AstFrame * ) * (size_t) old->nframe );
+         new->node = astStore( NULL, old->node,
+                               sizeof( int ) * (size_t) old->nframe );
+         new->varfrm = astStore( NULL, old->varfrm,
+                               sizeof( int ) * (size_t) old->nframe );
+         new->map = astMalloc( sizeof( AstMapping * ) *
+                               (size_t) ( old->nnode - 1 ) );
+         new->link = astStore( NULL, old->link,
+                               sizeof( int ) * (size_t) ( old->nnode - 1 ) );
+         new->invert = astStore( NULL, old->invert,
+                                 sizeof( int ) * (size_t) ( old->nnode - 1 ) );
+
+/* If OK, clone the pointer to each Frame and Mapping referenced by
+   the original FrameSet and store the resulting pointers in the new
+   FrameSet. */
+         if ( astOK ) {
+            for ( iframe = 0; iframe < old->nframe; iframe++ ) {
+               new->frame[ iframe ] = astClone( old->frame[ iframe ] );
+            }
+            for ( inode = 0; inode < old->nnode - 1; inode++ ) {
+               new->map[ inode ] = astClone( old->map[ inode ] );
+            }
+
+/* If an error occurred while cloning any of these pointers, clean up
+   by looping through the arrays of cloned pointers again and
+   annulling them all. */
+            if ( !astOK ) {
+               for ( iframe = 0; iframe < old->nframe; iframe++ ) {
+                  new->frame[ iframe ] = astAnnul( new->frame[ iframe ] );
+               }
+               for ( inode = 0; inode < old->nnode - 1; inode++ ) {
+                  new->map[ inode ] = astAnnul( new->map[ inode ] );
+               }
+            }
+         }
+
+/* If an error occurred, clean up by freeing all memory allocated
+   above. */
+         if ( !astOK ) {
+            new->frame = astFree( new->frame );
+            new->node = astFree( new->node );
+            new->varfrm = astFree( new->varfrm );
+            new->map = astFree( new->map );
+            new->link = astFree( new->link );
+            new->invert = astFree( new->invert );
+         }
+
+/* Copy the Frame and node counts across. */
+         new->nframe = old->nframe;
+         new->nnode = old->nnode;
+
+/* Copy the other FrameSet attributes across. */
+         new->base = old->base;
+         new->current = old->current;
+
+/* Transfer any other inherited attribute values that relate to the
+   FrameSet itself (rather than the enclosed Frames). */
+        if ( astTestInvert( old ) ) astSetInvert( new, astGetInvert( old ) );
+      }
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef TRANSFER
+}
+
+AstFrameSet *astLoadFrameSet_( void *mem, size_t size,
+                               AstFrameSetVtab *vtab, const char *name,
+                               AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadFrameSet
+
+*  Purpose:
+*     Load a FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     AstFrameSet *astLoadFrameSet( void *mem, size_t size,
+*                                   AstFrameSetVtab *vtab, const char *name,
+*                                   AstChannel *channel )
+
+*  Class Membership:
+*     FrameSet loader.
+
+*  Description:
+*     This function is provided to load a new FrameSet using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     FrameSet structure in this memory, using data read from the
+*     input Channel.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the FrameSet is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        FrameSet data (sizeof(FrameSet)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the FrameSet (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the FrameSet structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstFrameSet) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new FrameSet. If this is NULL, a pointer
+*        to the (static) virtual function table for the FrameSet class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "FrameSet" is used instead.
+
+*  Returned Value:
+*     A pointer to the new FrameSet.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstFrameSet *new;             /* Pointer to the new FrameSet */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword string */
+   int ifr;                      /* Get a pointer to the thread specific global data structure. */
+
+/* Loop counter for Frames */
+   int inode;                    /* Loop counter for nodes */
+
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this FrameSet. In this case the
+   FrameSet belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstFrameSet );
+      vtab = &class_vtab;
+      name = "FrameSet";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitFrameSetVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built FrameSet. */
+   new = astLoadFrame( mem, size, (AstFrameVtab *) vtab, name,
+                       channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "FrameSet" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Nframe. */
+/* ------- */
+      new->nframe = astReadInt( channel, "nframe", 1 );
+      if ( new->nframe < 0 ) new->nframe = 1;
+
+/* Number of nodes. */
+/* ---------------- */
+      new->nnode = astReadInt( channel, "nnode", new->nframe );
+      if ( new->nnode < 1 ) new->nnode = 1;
+
+/* Allocate memory to hold Frame and node information. */
+      new->frame = astMalloc( sizeof( AstFrame *) * (size_t) new->nframe );
+      new->node = astMalloc( sizeof( int ) * (size_t) new->nframe );
+      new->varfrm = astMalloc( sizeof( int ) * (size_t) new->nframe );
+      new->link = astMalloc( sizeof( int ) * (size_t) ( new->nnode - 1 ) );
+      new->invert = astMalloc( sizeof( int ) * (size_t) ( new->nnode - 1 ) );
+      new->map = astMalloc( sizeof( AstMapping * ) *
+                            (size_t) ( new->nnode - 1 ) );
+
+/* If an error occurs, ensure that all allocated memory is freed. */
+      if ( !astOK ) {
+         new->frame = astFree( new->frame );
+         new->node = astFree( new->node );
+         new->varfrm = astFree( new->varfrm );
+         new->link = astFree( new->link );
+         new->invert = astFree( new->invert );
+         new->map = astFree( new->map );
+
+/* Otherwise, initialise the arrays which will hold Object pointers. */
+      } else {
+         for ( ifr = 1; ifr <= new->nframe; ifr++ ) {
+            new->frame[ ifr - 1 ] = NULL;
+         }
+         for ( inode = 1; inode < new->nnode; inode++ ) {
+            new->map[ inode - 1 ] = NULL;
+         }
+
+/* Read Frame data... */
+         for ( ifr = 1; ifr <= new->nframe; ifr++ ) {
+
+/* Frame objects. */
+/* -------------- */
+/* Create the required keyword and then read the Frame. */
+            (void) sprintf( key, "frm%d", ifr );
+            new->frame[ ifr - 1 ] = astReadObject( channel, key, NULL );
+
+/* Node index for each Frame. */
+/* -------------------------- */
+            (void) sprintf( key, "nod%d", ifr );
+            new->node[ ifr - 1 ] = astReadInt( channel, key, ifr ) - 1;
+
+/* Index of variants Frame. */
+/* ------------------------ */
+            (void) sprintf( key, "vfr%d", ifr );
+            new->varfrm[ ifr - 1 ] = astReadInt( channel, key, 0 );
+         }
+
+/* Read node data... */
+         for ( inode = 1; inode < new->nnode; inode++ ) {
+
+/* Links between nodes. */
+/* -------------------- */
+            (void) sprintf( key, "lnk%d", inode + 1 );
+            new->link[ inode - 1 ] = astReadInt( channel, key, 0 ) - 1;
+
+/* Inversion flags. */
+/* ---------------- */
+            (void) sprintf( key, "inv%d", inode + 1 );
+            new->invert[ inode - 1 ] = astReadInt( channel, key, 0 );
+
+/* Mapping objects. */
+/* ---------------- */
+            (void) sprintf( key, "map%d", inode + 1 );
+            new->map[ inode - 1 ] = astReadObject( channel, key, NULL );
+         }
+
+/* Read remaining data... */
+
+/* Base. */
+/* ----- */
+         new->base = astReadInt( channel, "base", -INT_MAX );
+         if ( new->base < 1 ) new->base = -INT_MAX;
+
+/* Current. */
+/* -------- */
+         new->current = astReadInt( channel, "currnt", -INT_MAX );
+         if ( new->base < 1 ) new->base = -INT_MAX;
+      }
+
+/* If an error occurred, clean up by deleting the new FrameSet. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new FrameSet pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+void astAddFrame_( AstFrameSet *this, int iframe, AstMapping *map,
+                   AstFrame *frame, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,AddFrame))( this, iframe, map, frame, status );
+}
+void astClearBase_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,ClearBase))( this, status );
+}
+void astClearCurrent_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,ClearCurrent))( this, status );
+}
+void astClearVariant_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,ClearVariant))( this, status );
+}
+int astGetBase_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,FrameSet,GetBase))( this, status );
+}
+int astGetCurrent_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,FrameSet,GetCurrent))( this, status );
+}
+const char *astGetVariant_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,FrameSet,GetVariant))( this, status );
+}
+AstFrame *astGetFrame_( AstFrameSet *this, int iframe, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,FrameSet,GetFrame))( this, iframe, status );
+}
+AstMapping *astGetMapping_( AstFrameSet *this, int iframe1, int iframe2, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,FrameSet,GetMapping))( this, iframe1, iframe2, status );
+}
+int astGetNframe_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,FrameSet,GetNframe))( this, status );
+}
+void astRemapFrame_( AstFrameSet *this, int iframe, AstMapping *map, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,RemapFrame))( this, iframe, map, status );
+}
+void astAddVariant_( AstFrameSet *this, AstMapping *map, const char *name, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,AddVariant))( this, map, name, status );
+}
+void astMirrorVariants_( AstFrameSet *this, int iframe, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,MirrorVariants))( this, iframe, status );
+}
+void astRemoveFrame_( AstFrameSet *this, int iframe, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,RemoveFrame))( this, iframe, status );
+}
+void astSetBase_( AstFrameSet *this, int ibase, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,SetBase))( this, ibase, status );
+}
+void astSetCurrent_( AstFrameSet *this, int icurrent, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,SetCurrent))( this, icurrent, status );
+}
+void astSetVariant_( AstFrameSet *this, const char *variant, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,FrameSet,SetVariant))( this, variant, status );
+}
+int astTestBase_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,FrameSet,TestBase))( this, status );
+}
+int astTestCurrent_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,FrameSet,TestCurrent))( this, status );
+}
+int astTestVariant_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,FrameSet,TestVariant))( this, status );
+}
+int astValidateFrameIndex_( AstFrameSet *this, int iframe,
+                            const char *method, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,FrameSet,ValidateFrameIndex))( this, iframe,
+                                                           method, status );
+}
+const char *astGetAllVariants_( AstFrameSet *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,FrameSet,GetAllVariants))( this, status );
+}
+
+/* Special public interface functions. */
+/* =================================== */
+/* These provide the public interface to certain special functions
+   whose public interface cannot be handled using macros (such as
+   astINVOKE) alone. In general, they are named after the
+   corresponding protected version of the function, but with "Id"
+   appended to the name. */
+
+/* Public Interface Function Prototypes. */
+/* ------------------------------------- */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstFrameSet *astFrameSetId_( void *, const char *, ... );
+
+/* Special interface function implementations. */
+/* ------------------------------------------- */
+AstFrameSet *astFrameSetId_( void *frame_void, const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astFrameSet
+f     AST_FRAMESET
+
+*  Purpose:
+*     Create a FrameSet.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "frameset.h"
+c     AstFrameSet *astFrameSet( AstFrame *frame, const char *options, ... )
+f     RESULT = AST_FRAMESET( FRAME, OPTIONS, STATUS )
+
+*  Class Membership:
+*     FrameSet constructor.
+
+*  Description:
+*     This function creates a new FrameSet and optionally initialises
+*     its attributes.
+*
+*     A FrameSet consists of a set of one or more Frames (which
+*     describe coordinate systems), connected together by Mappings
+*     (which describe how the coordinate systems are inter-related). A
+*     FrameSet makes it possible to obtain a Mapping between any pair
+*     of these Frames (i.e. to convert between any of the coordinate
+*     systems which it describes).  The individual Frames are
+*     identified within the FrameSet by an integer index, with Frames
+*     being numbered consecutively from one as they are added to the
+*     FrameSet.
+*
+*     Every FrameSet has a "base" Frame and a "current" Frame (which
+*     are allowed to be the same). Any of the Frames may be nominated
+*     to hold these positions, and the choice is determined by the
+*     values of the FrameSet's Base and Current attributes, which hold
+*     the indices of the relevant Frames.  By default, the first Frame
+*     added to a FrameSet is its base Frame, and the last one added is
+*     its current Frame.
+*
+*     The base Frame describes the "native" coordinate system of
+*     whatever the FrameSet is used to calibrate (e.g. the pixel
+*     coordinates of an image) and the current Frame describes the
+*     "apparent" coordinate system in which it should be viewed
+*     (e.g. displayed, etc.). Any further Frames represent a library
+*     of alternative coordinate systems, which may be selected by
+*     making them current.
+*
+*     When a FrameSet is used in a context that requires a Frame,
+*     (e.g. obtaining its Title value, or number of axes), the current
+*     Frame is used. A FrameSet may therefore be used in place of its
+*     current Frame in most situations.
+*
+*     When a FrameSet is used in a context that requires a Mapping,
+*     the Mapping used is the one between its base Frame and its
+*     current Frame. Thus, a FrameSet may be used to convert "native"
+*     coordinates into "apparent" ones, and vice versa. Like any
+c     Mapping, a FrameSet may also be inverted (see astInvert), which
+f     Mapping, a FrameSet may also be inverted (see AST_INVERT), which
+*     has the effect of interchanging its base and current Frames and
+*     hence of reversing the Mapping between them.
+*
+*     Regions may be added into a FrameSet (since a Region is a type of
+*     Frame), either explicitly or as components within CmpFrames. In this
+*     case the Mapping between a pair of Frames within a FrameSet will
+*     include the effects of the clipping produced by any Regions included
+*     in the path between the Frames.
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        Pointer to the first Frame to be inserted into the
+*        FrameSet. This initially becomes both the base and the
+*        current Frame. (Further Frames may be added using the
+c        astAddFrame function.)
+f        AST_ADDFRAME routine.)
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new FrameSet. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new FrameSet. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astFrameSet()
+f     AST_FRAMESET
+*        A pointer to the new FrameSet.
+
+*  Notes:
+c     - If a pointer to an existing FrameSet is given for the "frame"
+c     parameter, then the new FrameSet will (as a special case) be
+f     - If a pointer to an existing FrameSet is given for the FRAME
+f     argument, then the new FrameSet will (as a special case) be
+*     initialised to contain the same Frames and Mappings, and to have
+*     the same attribute values, as the one supplied. This process is
+c     similar to making a copy of a FrameSet (see astCopy), except
+f     similar to making a copy of a FrameSet (see AST_COPY), except
+*     that the Frames and Mappings contained in the original are not
+*     themselves copied, but are shared by both FrameSets.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astFrameSet constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astFrameSet_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - Because no checking or casting of arguments is performed
+*     before the function is invoked, the "frame" parameter is of type
+*     (void *) and is converted from an ID value to a pointer and
+*     validated within the function itself.
+*     - The variable argument list also prevents this function from
+*     invoking astFrameSet_ directly, so it must be a
+*     re-implementation of it in all respects, except for the
+*     conversions between IDs and pointers on input/output of Objects.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstFrameSet *new;             /* Pointer to new FrameSet */
+   va_list args;                 /* Variable argument list */
+
+   int *status;
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain the Frame pointer from the ID supplied and validate the
+   pointer to ensure it identifies a valid Frame. */
+   frame = astVerifyFrame( astMakePointer( frame_void ) );
+   if ( astOK ) {
+
+/* Initialise the FrameSet, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitFrameSet( NULL, sizeof( AstFrameSet ), !class_init,
+                             &class_vtab, "FrameSet", frame );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   FrameSet's attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return an ID value for the new FrameSet. */
+   return astMakeId( new );
+}
+
+
+
diff --git a/ast/frameset.h b/ast/frameset.h
new file mode 100644
index 0000000..1f33054
--- /dev/null
+++ b/ast/frameset.h
@@ -0,0 +1,709 @@
+/*
+*+
+*  Name:
+*     frameset.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the FrameSet class.
+
+*  Invocation:
+*     #include "frameset.h"
+
+*  Description:
+*     This include file defines the interface to the FrameSet class and
+*     provides the type definitions, function prototypes and macros, etc.
+*     needed to use this class.
+*
+*     A FrameSet consists of a set of one or more Frames, which are
+*     inter-related by Mappings in such a way that it is possible to
+*     obtain a Mapping between any pair of the Frames. The Frames are
+*     identified by an integer index, with Frames being numbered
+*     consecutively from one as they are added to the FrameSet.
+*
+*     At any time, there is a "base" Frame and a "current" Frame
+*     (which are allowed to be the same). Any of the Frames may be
+*     nominated to hold these positions, and the choice is determined
+*     by the values of the FrameSet's Base and Current attributes
+*     which hold the indices of the relevant Frames.  By default, the
+*     first Frame added to a FrameSet is its base Frame, and the last
+*     one added is its current Frame.
+*
+*     The base Frame describes the "native" coordinate system of
+*     whatever the FrameSet is used to calibrate (e.g. the pixel
+*     coordinates of an image) and the current Frame describes the
+*     "apparent" coordinate system in which it should be viewed
+*     (e.g. displayed, etc.). The other Frames represent alternative
+*     coordinate systems which may be selected by making them current.
+*
+*     When Frame methods are invoked on a FrameSet (e.g. to obtain a
+*     Title value or to determine the number of axes), they are
+*     applied to the current Frame. Thus, a FrameSet may be used in
+*     place of its current Frame in most situations.
+*
+*     When Mapping methods are invoked on a FrameSet, the Mapping used
+*     is the one between its base Frame and its current Frame. Thus, a
+*     FrameSet may be used to convert "native" coordinates into
+*     "apparent" ones, and vice versa. A FrameSet may also be
+*     inverted, which has the effect of interchanging its base and
+*     current Frames (and hence of reversing the Mapping between
+*     them).
+*
+*     The FrameSet class also defines methods of its own, which are
+*     used to manage the Frames and Mappings that it contains and to
+*     convert between coordinate systems described by different
+*     FrameSets.
+
+*  Inheritance:
+*     The FrameSet class inherits from the Frame class.
+
+*  Attributes Over-Ridden:
+*     Digits (integer)
+*     Direction(axis) (integer)
+*     Domain (string)
+*     Format(axis) (string)
+*     Label(axis) (string)
+*     MatchEnd (integer)
+*     MaxAxes (integer)
+*     MinAxes (integer)
+*     Naxes (integer)
+*     Permute (integer)
+*     PreserveAxes (integer)
+*     Symbol(axis) (string)
+*     Title (string)
+*     Unit(axis) (string)
+*        The FrameSet acquires all of these attributes from its
+*        current Frame, so their meanings, values and defaults are
+*        determined by this Frame and may change if a different
+*        current Frame is selected.
+*     Nin (integer)
+*     Nout (integer)
+*     TranForward (integer)
+*     TranInverse (integer)
+*        The FrameSet interprets all of these as applying to the
+*        Mapping that converts coordinates between its base Frame and
+*        its current Frame, so their values may change if a different
+*        base or current Frame is selected.
+*     Invert (integer)
+*     Report (integer)
+*        The FrameSet interprets these as applying to the Mapping that
+*        converts coordinates between its base Frame and its current
+*        Frame, but their values are not affected by selecing a
+*        different base or current Frame.
+
+*  New Attributes Defined:
+*     Base (integer)
+*        The (one-based) index of the Frame which is to be regarded as
+*        the base Frame in the FrameSet. By default, this is the first
+*        Frame added to the FrameSet (i.e. when it was created),
+*        unless the Frameset has been inverted, in which case it is
+*        the last Frame added. Inverting a FrameSet interchanges the
+*        values of its Base and Current attributes.
+*     Current (integer)
+*        The (one-based) index of the Frame which is to be regarded as
+*        the current Frame in the FrameSet. By default, this is the
+*        last Frame added to the FrameSet, unless the Frameset has
+*        been inverted, in which case it is the first Frame added
+*        (i.e. when the FrameSet was created). Inverting a FrameSet
+*        interchanges the values of its Base and Current attributes.
+*     Nframe (integer)
+*        A read-only value giving the number of Frames in a
+*        FrameSet. This value will change as Frames are added or
+*        removed.
+
+*  Methods Over-Ridden:
+*     Public:
+*        astClear
+*           Clear attribute values for a FrameSet.
+*        astConvert
+*           Determine how to convert between two coordinate systems.
+*        astDistance
+*           Calculate the distance between two points.
+*        astFindFrame
+*           Find a coordinate system with specified characteristics
+*        astFormat
+*           Format a coordinate value for a FrameSet axis.
+*        astGetAxis
+*           Obtain a pointer to a specified Axis from a FrameSet.
+*        astGetNaxes
+*           Determine how many axes a FrameSet has.
+*        astGetNin
+*           Get the number of input coordinates for a FrameSet.
+*        astGetNout
+*           Get the number of output coordinates for a FrameSet.
+*        astNorm
+*           Normalise a set of FrameSet coordinates.
+*        astOffset
+*           Calculate an offset along a geodesic curve.
+*        astPermAxes
+*           Permute the order of a FrameSet's axes.
+*        astPickAxes
+*           Create a new Frame by picking axes from a FrameSet.
+*        astSetAxis
+*           Set a new Axis for a FrameSet.
+*        astSimplify
+*           Simplify the Mappings in a FrameSet.
+*        astTransform
+*           Transform a set of points.
+*        astUnformat
+*           Read a formatted coordinate value for a FrameSet axis.
+*
+*     Protected:
+*        astAbbrev
+*           Abbreviate a formatted FrameSet axis value by skipping leading
+*           fields.
+*        astClearDigits
+*           Clear the value of the Digits attribute for a FrameSet.
+*        astClearDirection
+*           Clear the value of the Direction attribute for a FrameSet axis.
+*        astClearDomain
+*           Clear the value of the Domain attribute for a FrameSet.
+*        astClearFormat
+*           Clear the value of the Format attribute for a FrameSet axis.
+*        astClearLabel
+*           Clear the value of the Label attribute for a FrameSet axis.
+*        astClearMatchEnd
+*           Clear the value of the MatchEnd attribute for a FrameSet.
+*        astClearMaxAxes
+*           Clear the value of the MaxAxes attribute for a FrameSet.
+*        astClearMinAxes
+*           Clear the value of the MinAxes attribute for a FrameSet.
+*        astClearPermute
+*           Clear the value of the Permute attribute for a FrameSet.
+*        astClearPreserveAxes
+*           Clear the value of the PreserveAxes attribute for a FrameSet.
+*        astClearSymbol
+*           Clear the value of the Symbol attribute for a FrameSet axis.
+*        astClearTitle
+*           Clear the value of the Title attribute for a FrameSet.
+*        astClearUnit
+*           Clear the value of the Unit attribute for a FrameSet axis.
+*        astConvertX
+*           Determine how to convert between two coordinate systems.
+*        astGap
+*           Find a "nice" gap for tabulating FrameSet axis values.
+*        astGetDigits
+*           Get the value of the Digits attribute for a FrameSet.
+*        astGetDirection
+*           Get the value of the Direction attribute for a FrameSet axis.
+*        astGetDomain
+*           Get the value of the Domain attribute for a FrameSet.
+*        astGetFormat
+*           Get the value of the Format attribute for a FrameSet axis.
+*        astGetLabel
+*           Get the value of the Label attribute for a FrameSet axis.
+*        astGetMatchEnd
+*           Get the value of the MatchEnd attribute for a FrameSet.
+*        astGetMaxAxes
+*           Get the value of the MaxAxes attribute for a FrameSet.
+*        astGetMinAxes
+*           Get the value of the MinAxes attribute for a FrameSet.
+*        astGetPerm
+*           Access the axis permutation array for the current Frame of
+*           a FrameSet.
+*        astGetPermute
+*           Get the value of the Permute attribute for a FrameSet.
+*        astGetPreserveAxes
+*           Get the value of the PreserveAxes attribute for a FrameSet.
+*        astGetSymbol
+*           Get the value of the Symbol attribute for a FrameSet axis.
+*        astGetTitle
+*           Get the value of the Title attribute for a FrameSet.
+*        astGetTranForward
+*           Determine if a Mapping can perform a "forward" coordinate
+*           transformation.
+*        astGetTranInverse
+*           Determine if a Mapping can perform an "inverse" coordinate
+*           transformation.
+*        astGetUnit
+*           Get the value of the Unit attribute for a FrameSet axis.
+*        astMatch
+*           Determine if conversion is possible between two coordinate systems.
+*        astOverlay
+*           Overlay the attributes of a template FrameSet on to another Frame.
+*        astPrimaryFrame
+*           Uniquely identify a primary Frame and one of its axes.
+*        astReportPoints
+*           Report the effect of transforming a set of points using a FrameSet.
+*        astSetAttrib
+*           Set an attribute value for a FrameSet.
+*        astSetDigits
+*           Set the value of the Digits attribute for a FrameSet.
+*        astSetDirection
+*           Set the value of the Direction attribute for a FrameSet axis.
+*        astSetDomain
+*           Set the value of the Domain attribute for a FrameSet.
+*        astSetFormat
+*           Set the value of the Format attribute for a FrameSet axis.
+*        astSetLabel
+*           Set the value of the Label attribute for a FrameSet axis.
+*        astSetMatchEnd
+*           Set the value of the MatchEnd attribute for a FrameSet.
+*        astSetMaxAxes
+*           Set the value of the MaxAxes attribute for a FrameSet.
+*        astSetMinAxes
+*           Set the value of the MinAxes attribute for a FrameSet.
+*        astSetPermute
+*           Set the value of the Permute attribute for a FrameSet.
+*        astSetPreserveAxes
+*           Set the value of the PreserveAxes attribute for a FrameSet.
+*        astSetSymbol
+*           Set the value of the Symbol attribute for a FrameSet axis.
+*        astSetTitle
+*           Set the value of the Title attribute for a FrameSet.
+*        astSetUnit
+*           Set the value of the Unit attribute for a FrameSet axis.
+*        astSubFrame
+*           Select axes from a FrameSet and convert to the new coordinate
+*           system.
+*        astTestDigits
+*           Test if a value has been set for the Digits attribute of a
+*           FrameSet.
+*        astTestDirection
+*           Test if a value has been set for the Direction attribute of a
+*           FrameSet axis.
+*        astTestDomain
+*           Test if a value has been set for the Domain attribute of a
+*           FrameSet.
+*        astTestFormat
+*           Test if a value has been set for the Format attribute of a
+*           FrameSet axis.
+*        astTestLabel
+*           Test if a value has been set for the Label attribute of a
+*           FrameSet axis.
+*        astTestMatchEnd
+*           Test if a value has been set for the MatchEnd attribute of a
+*           FrameSet.
+*        astTestMaxAxes
+*           Test if a value has been set for the MaxAxes attribute of a
+*           FrameSet.
+*        astTestMinAxes
+*           Test if a value has been set for the MinAxes attribute of a
+*           FrameSet.
+*        astTestPermute
+*           Test if a value has been set for the Permute attribute of a
+*           FrameSet.
+*        astTestPreserveAxes
+*           Test if a value has been set for the PreserveAxes attribute of a
+*           FrameSet.
+*        astTestSymbol
+*           Test if a value has been set for the Symbol attribute of a
+*           FrameSet axis.
+*        astTestTitle
+*           Test if a value has been set for the Title attribute of a FrameSet.
+*        astTestUnit
+*           Test if a value has been set for the Unit attribute of a FrameSet
+*           axis.
+*        astValidateAxis
+*           Validate and permute a FrameSet's axis index.
+*        astVSet
+*           Set values for a FrameSet's attributes.
+
+*  New Methods Defined:
+*     Public:
+*        astAddFrame
+*           Add a Frame to a FrameSet to define a new coordinate system.
+*        astGetFrame
+*           Obtain a pointer to a specified Frame in a FrameSet.
+*        astGetMapping
+*           Obtain a Mapping between two Frames in a FrameSet.
+*        astRemapFrame
+*           Modify a Frame's relationshp to the other Frames in a FrameSet.
+*        astRemoveFrame
+*           Remove a Frame from a FrameSet.
+*
+*     Protected:
+*        astClearBase
+*           Clear the value of the Base attribute for a FrameSet.
+*        astClearCurrent
+*           Clear the value of the Current attribute for a FrameSet.
+*        astGetBase
+*           Obtain the value of the Base attribute for a FrameSet.
+*        astGetCurrent
+*           Obtain the value of the Current attribute for a FrameSet.
+*        astGetNframe
+*           Determine the number of Frames in a FrameSet.
+*        astSetBase
+*           Set the value of the Base attribute for a FrameSet.
+*        astSetCurrent
+*           Set the value of the Current attribute for a FrameSet.
+*        astTestBase
+*           Test if a value has been set for the Base attribute of a FrameSet.
+*        astTestCurrent
+*           Test if a value has been set for the Current attribute of a
+*           FrameSet.
+*        astValidateFrameIndex
+*           Validate a FrameSet Frame index number.
+
+*  Other Class Functions:
+*     Public:
+*        astFrameSet
+*           Create a FrameSet.
+*        astIsAFrameSet
+*           Test class membership.
+*
+*     Protected:
+*        astCheckFrameSet
+*           Validate class membership.
+*        astInitFrameSet
+*           Initialise a FrameSet.
+*        astInitFrameSetVtab
+*           Initialise the virtual function table for the FrameSet class.
+*        astLoadFrameSet
+*           Load a FrameSet.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstFrameSet
+*           FrameSet object type.
+
+*     Protected:
+*        AstFrameSetVtab
+*           FrameSet virtual function table type.
+
+*  Macros:
+*     Public:
+*        AST__BASE
+*           Expands to a constant int that may be used as a Frame index to
+*           refer to a FrameSet's base Frame.
+*        AST__CURRENT
+*           Expands to a constant int that may be used as a Frame index to
+*           refer to a FrameSet's current Frame.
+*        AST__NOFRAME
+*           Expands to a constant int that is guaranteed not to be valid when
+*           used as a Frame index for a FrameSet.
+*
+*     Protected:
+*        None.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     16-FEB-1996 (RFWS):
+*        Original version.
+*     5-JUN-1996 (RFWS):
+*        Tidied up, etc.
+*     12-AUG-1996 (RFWS):
+*        Added support for the public interface.
+*     25-SEP-1996 (RFWS):
+*        Added I/O facilities.
+*     20-JAN-1998 (RFWS):
+*        Implemented preservation of FrameSet integrity when attribute
+*        values associated with the current Frame are modified.
+*     25-FEB-1998 (RFWS):
+*        Over-ride the astUnformat method.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitFrameSetVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "frame.h"               /* Parent Frame class */
+
+/* Note that the usual setting of the FRAMESET_INCLUDED flag, which
+   prevents this file being included more than once, must be deferred
+   until after including the "frame.h" file. This is because "frame.h"
+   needs to include the present interface definition (as a form of
+   "forward reference") in order to have access to FrameSets
+   itself. */
+#if !defined( FRAMESET_INCLUDED )
+#define FRAMESET_INCLUDED
+
+/* Macros. */
+/* ======= */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+#define AST__BASE (0)            /* Identify base Frame */
+#define AST__CURRENT (-1)        /* Identify current Frame */
+#define AST__NOFRAME (-99)       /* An invalid Frame index */
+#define AST__ALLFRAMES (-199)    /* A value representing all Frames */
+#define AST__FRAMESET_GETALLVARIANTS_BUFF_LEN 200 /* Length for AllVariants buffer */
+#define AST__FRAMESET_GETATTRIB_BUFF_LEN 200 /* Length for GetAtribb buffer */
+
+/* Type Definitions. */
+/* ================= */
+/* FrameSet structure. */
+/* ------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstFrameSet {
+
+/* Attributes inherited from the parent class. */
+   AstFrame parent;              /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstFrame **frame;             /* Array of Frame pointers */
+   AstMapping **map;             /* Array of Mapping pointers */
+   int *varfrm;                  /* Array of variants Frames indices */
+   int *invert;                  /* Array of Mapping Invert values */
+   int *link;                    /* Parent node index for each node */
+   int *node;                    /* Index of node associated with Frame */
+   int base;                     /* Index of base Frame */
+   int current;                  /* Index of current Frame */
+   int nframe;                   /* Number of Frames */
+   int nnode;                    /* Number of nodes */
+} AstFrameSet;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all objects in the
+   class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstFrameSetVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstFrameVtab frame_vtab;      /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstFrame *(* GetFrame)( AstFrameSet *, int, int * );
+   AstMapping *(* GetMapping)( AstFrameSet *, int, int, int * );
+   int (* GetBase)( AstFrameSet *, int * );
+   int (* GetCurrent)( AstFrameSet *, int * );
+   int (* GetNframe)( AstFrameSet *, int * );
+   int (* TestBase)( AstFrameSet *, int * );
+   int (* TestCurrent)( AstFrameSet *, int * );
+   int (* ValidateFrameIndex)( AstFrameSet *, int, const char *, int * );
+   void (* AddFrame)( AstFrameSet *, int, AstMapping *, AstFrame *, int * );
+   void (* AddVariant)( AstFrameSet *, AstMapping *, const char *, int * );
+   void (* MirrorVariants)( AstFrameSet *, int, int * );
+   void (* ClearBase)( AstFrameSet *, int * );
+   void (* ClearCurrent)( AstFrameSet *, int * );
+   void (* RemapFrame)( AstFrameSet *, int, AstMapping *, int * );
+   void (* RemoveFrame)( AstFrameSet *, int, int * );
+   void (* SetBase)( AstFrameSet *, int, int * );
+   void (* SetCurrent)( AstFrameSet *, int, int * );
+   void (* ClearVariant)( AstFrameSet *, int * );
+   const char *(* GetVariant)( AstFrameSet *, int * );
+   void (* SetVariant)( AstFrameSet *, const char *, int * );
+   int (* TestVariant)( AstFrameSet *, int * );
+   const char *(* GetAllVariants)( AstFrameSet *, int * );
+} AstFrameSetVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstFrameSetGlobals {
+   AstFrameSetVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ AST__FRAMESET_GETATTRIB_BUFF_LEN + 1 ];
+   char GetAllVariants_Buff[ AST__FRAMESET_GETALLVARIANTS_BUFF_LEN + 1 ];
+   AstFrame *Integrity_Frame;
+   const char *Integrity_Method;
+   int Integrity_Lost;
+} AstFrameSetGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(FrameSet)         /* Check class membership */
+astPROTO_ISA(FrameSet)           /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+AstFrameSet *astFrameSet_( void *, const char *, int *, ...);
+#else
+AstFrameSet *astFrameSetId_( void *, const char *, ... )__attribute__((format(printf,2,3)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstFrameSet *astInitFrameSet_( void *, size_t, int, AstFrameSetVtab *,
+                               const char *, AstFrame *, int * );
+
+/* Vtab initialiser. */
+void astInitFrameSetVtab_( AstFrameSetVtab *, const char *, int * );
+
+/* Loader. */
+AstFrameSet *astLoadFrameSet_( void *, size_t, AstFrameSetVtab *,
+                               const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitFrameSetGlobals_( AstFrameSetGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+AstFrame *astGetFrame_( AstFrameSet *, int, int * );
+AstMapping *astGetMapping_( AstFrameSet *, int, int, int * );
+void astAddFrame_( AstFrameSet *, int , AstMapping *, AstFrame *, int * );
+void astAddVariant_( AstFrameSet *, AstMapping *, const char *, int * );
+void astMirrorVariants_( AstFrameSet *, int, int * );
+void astRemapFrame_( AstFrameSet *, int, AstMapping *, int * );
+void astRemoveFrame_( AstFrameSet *, int, int * );
+
+#if defined(astCLASS)            /* Protected */
+const char *astGetAllVariants_( AstFrameSet *, int * );
+int astGetBase_( AstFrameSet *, int * );
+int astGetCurrent_( AstFrameSet *, int * );
+int astGetNframe_( AstFrameSet *, int * );
+int astTestBase_( AstFrameSet *, int * );
+int astTestCurrent_( AstFrameSet *, int * );
+int astValidateFrameIndex_( AstFrameSet *, int, const char *, int * );
+void astClearBase_( AstFrameSet *, int * );
+void astClearCurrent_( AstFrameSet *, int * );
+void astSetBase_( AstFrameSet *, int, int * );
+void astSetCurrent_( AstFrameSet *, int, int * );
+void astClearVariant_( AstFrameSet *, int * );
+const char *astGetVariant_( AstFrameSet *, int * );
+void astSetVariant_( AstFrameSet *, const char *, int * );
+int astTestVariant_( AstFrameSet *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class to make
+   them easier to invoke (e.g. to avoid type mis-matches when passing pointers
+   to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them to
+   validate their own arguments. We must use a cast when passing object
+   pointers (so that they can accept objects from derived classes). */
+
+/* Check class membership. */
+#define astCheckFrameSet(this) astINVOKE_CHECK(FrameSet,this,0)
+#define astVerifyFrameSet(this) astINVOKE_CHECK(FrameSet,this,1)
+
+/* Test class membership. */
+#define astIsAFrameSet(this) astINVOKE_ISA(FrameSet,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+#define astFrameSet astINVOKE(F,astFrameSet_)
+#else
+#define astFrameSet astINVOKE(F,astFrameSetId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitFrameSet(mem,size,init,vtab,name,frame) \
+astINVOKE(O,astInitFrameSet_(mem,size,init,vtab,name,astCheckFrame(frame),STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitFrameSetVtab(vtab,name) astINVOKE(V,astInitFrameSetVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadFrameSet(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadFrameSet_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckFrameSet to validate FrameSet pointers before
+   use. This provides a contextual error report if a pointer to the wrong sort
+   of object is supplied. */
+#define astAddFrame(this,iframe,map,frame) \
+astINVOKE(V,astAddFrame_(astCheckFrameSet(this),iframe,(((iframe)!=AST__ALLFRAMES)?astCheckMapping(map):NULL),astCheckFrame(frame),STATUS_PTR))
+#define astAddVariant(this,map,name) \
+astINVOKE(V,astAddVariant_(astCheckFrameSet(this),map?astCheckMapping(map):NULL,name,STATUS_PTR))
+#define astMirrorVariants(this,iframe) \
+astINVOKE(V,astMirrorVariants_(astCheckFrameSet(this),iframe,STATUS_PTR))
+#define astGetFrame(this,iframe) \
+astINVOKE(O,astGetFrame_(astCheckFrameSet(this),iframe,STATUS_PTR))
+#define astGetMapping(this,iframe1,iframe2) \
+astINVOKE(O,astGetMapping_(astCheckFrameSet(this),iframe1,iframe2,STATUS_PTR))
+#define astRemapFrame(this,iframe,map) \
+astINVOKE(V,astRemapFrame_(astCheckFrameSet(this),iframe,astCheckMapping(map),STATUS_PTR))
+#define astRemoveFrame(this,iframe) \
+astINVOKE(V,astRemoveFrame_(astCheckFrameSet(this),iframe,STATUS_PTR))
+
+/* Interfaces to protected member functions. */
+/* ----------------------------------------- */
+#if defined(astCLASS)            /* Protected */
+#define astClearBase(this) \
+astINVOKE(V,astClearBase_(astCheckFrameSet(this),STATUS_PTR))
+#define astClearCurrent(this) \
+astINVOKE(V,astClearCurrent_(astCheckFrameSet(this),STATUS_PTR))
+#define astGetBase(this) \
+astINVOKE(V,astGetBase_(astCheckFrameSet(this),STATUS_PTR))
+#define astGetCurrent(this) \
+astINVOKE(V,astGetCurrent_(astCheckFrameSet(this),STATUS_PTR))
+#define astGetNframe(this) \
+astINVOKE(V,astGetNframe_(astCheckFrameSet(this),STATUS_PTR))
+#define astSetBase(this,ibase) \
+astINVOKE(V,astSetBase_(astCheckFrameSet(this),ibase,STATUS_PTR))
+#define astSetCurrent(this,icurrent) \
+astINVOKE(V,astSetCurrent_(astCheckFrameSet(this),icurrent,STATUS_PTR))
+#define astTestBase(this) \
+astINVOKE(V,astTestBase_(astCheckFrameSet(this),STATUS_PTR))
+#define astTestCurrent(this) \
+astINVOKE(V,astTestCurrent_(astCheckFrameSet(this),STATUS_PTR))
+#define astValidateFrameIndex(this,iframe,method) \
+astINVOKE(V,astValidateFrameIndex_(astCheckFrameSet(this),iframe,method,STATUS_PTR))
+#define astClearVariant(this) \
+astINVOKE(V,astClearVariant_(astCheckFrameSet(this),STATUS_PTR))
+#define astGetVariant(this) \
+astINVOKE(V,astGetVariant_(astCheckFrameSet(this),STATUS_PTR))
+#define astSetVariant(this,variant) \
+astINVOKE(V,astSetVariant_(astCheckFrameSet(this),variant,STATUS_PTR))
+#define astTestVariant(this) \
+astINVOKE(V,astTestVariant_(astCheckFrameSet(this),STATUS_PTR))
+#define astGetAllVariants(this) \
+astINVOKE(V,astGetAllVariants_(astCheckFrameSet(this),STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/fratemap.c b/ast/fratemap.c
new file mode 100644
index 0000000..62588ab
--- /dev/null
+++ b/ast/fratemap.c
@@ -0,0 +1,106 @@
+/*
+*+
+*  Name:
+*     fratemap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST RateMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the RateMap class.
+
+*  Routines Defined:
+*     AST_ISARATEMAP
+*     AST_RATEMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S.Berry (Starlink)
+
+*  History:
+*     10-FEB-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "ratemap.h"             /* C interface to the RateMap class */
+
+F77_LOGICAL_FUNCTION(ast_isaratemap)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astWatchSTATUS(
+   astAt( "AST_ISARATEMAP", NULL, 0 );
+      RESULT = astIsARateMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_ratemap)( INTEGER(MAP),
+                                   INTEGER(AX1),
+                                   INTEGER(AX2),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(MAP)
+   GENPTR_INTEGER(AX1)
+   GENPTR_INTEGER(AX2)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_RATEMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astRateMap( astI2P( *MAP ), *AX1, *AX2,
+                                  "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fregion.c b/ast/fregion.c
new file mode 100644
index 0000000..2c3b4ef
--- /dev/null
+++ b/ast/fregion.c
@@ -0,0 +1,297 @@
+/*
+*+
+*  Name:
+*     fregion.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Region class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Region class.
+
+*  Routines Defined:
+*     AST_NEGATE
+*     AST_ISAREGION
+*     AST_MAPREGION
+*     AST_GETREGIONBOUNDS
+*     AST_GETREGIONFRAME
+*     AST_GETREGIONFRAMESET
+*     AST_OVERLAP
+*     AST_SETUNC
+*     AST_GETUNC
+*     AST_SHOWMESH
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-MAR-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory management facilities */
+#include "region.h"              /* C interface to the Region class */
+
+/* FORTRAN interface functions. */
+/* ============================ */
+/* These functions implement the remainder of the FORTRAN interface. */
+
+F77_SUBROUTINE(ast_negate)( INTEGER(THIS),
+                            INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_NEGATE", NULL, 0 );
+   astWatchSTATUS(
+      astNegate( astI2P( *THIS ) );
+   )
+}
+
+F77_SUBROUTINE(ast_setunc)( INTEGER(THIS),
+                            INTEGER(UNC),
+                            INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(UNC)
+
+   astAt( "AST_SETUNC", NULL, 0 );
+   astWatchSTATUS(
+      astSetUnc( astI2P( *THIS ), astI2P( *UNC ) );
+   )
+}
+
+F77_LOGICAL_FUNCTION(ast_isaregion)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAREGION", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsARegion( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_getregionframe)( INTEGER(THIS),
+                                          INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETREGIONFRAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astGetRegionFrame( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_getregionframeset)( INTEGER(THIS),
+                                             INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETREGIONFRAMESET", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astGetRegionFrameSet( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_getunc)( INTEGER(THIS),
+                                  LOGICAL(DEF),
+                                  INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_LOGICAL(DEF)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETUNC", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astGetUnc( astI2P( *THIS ), F77_ISTRUE( *DEF ) ? 1 : 0 ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_mapregion)( INTEGER(REG),
+                                     INTEGER(MAP),
+                                     INTEGER(FRM),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(REG)
+   GENPTR_INTEGER(MAP)
+   GENPTR_INTEGER(FRM)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_MAPREGION", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astMapRegion( astI2P( *REG ), astI2P( *MAP ),
+                                     astI2P( *FRM ) ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_overlap)( INTEGER(THIS),
+                                   INTEGER(THAT),
+                                   INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(THAT)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_OVERLAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astOverlap( astI2P( *THIS ), astI2P( *THAT ) );
+   )
+   return RESULT;
+}
+
+/* AST_MASK<X> requires a function for each possible data type, so
+   define it via a macro. */
+#define MAKE_AST_MASK(f,F,Ftype,X,Xtype) \
+F77_INTEGER_FUNCTION(ast_mask##f)( INTEGER(THIS), \
+                                   INTEGER(MAP), \
+                                   LOGICAL(INSIDE), \
+                                   INTEGER(NDIM), \
+                                   INTEGER_ARRAY(LBND), \
+                                   INTEGER_ARRAY(UBND), \
+                                   Ftype##_ARRAY(IN), \
+                                   Ftype(VAL), \
+                                   INTEGER(STATUS) ) { \
+   GENPTR_INTEGER(THIS) \
+   GENPTR_INTEGER(MAP) \
+   GENPTR_LOGICAL(INSIDE) \
+   GENPTR_INTEGER(NDIM) \
+   GENPTR_INTEGER_ARRAY(LBND) \
+   GENPTR_INTEGER_ARRAY(UBND) \
+   GENPTR_##Ftype##_ARRAY(IN) \
+   GENPTR_##Ftype(VAL) \
+   GENPTR_INTEGER(STATUS) \
+\
+   F77_INTEGER_TYPE RESULT; \
+\
+   astAt( "AST_MASK"#F, NULL, 0 ); \
+   astWatchSTATUS( \
+\
+      RESULT = astMask##X( astI2P( *THIS ), astI2P( *MAP ), \
+                           F77_ISTRUE( *INSIDE ) ? 1 : 0, *NDIM, \
+                           LBND, UBND, (Xtype *) IN, *VAL ); \
+   ) \
+   return RESULT; \
+}
+
+/* Invoke the above macro to define a function for each data
+   type. Include synonyms for some functions. */
+MAKE_AST_MASK(d,D,DOUBLE,D,double)
+MAKE_AST_MASK(r,R,REAL,F,float)
+MAKE_AST_MASK(i,I,INTEGER,I,int)
+MAKE_AST_MASK(ui,UI,INTEGER,UI,unsigned int)
+MAKE_AST_MASK(s,S,WORD,S,short int)
+MAKE_AST_MASK(us,US,UWORD,US,unsigned short int)
+MAKE_AST_MASK(w,W,WORD,S,short int)
+MAKE_AST_MASK(uw,UW,UWORD,US,unsigned short int)
+MAKE_AST_MASK(b,B,BYTE,B,signed char)
+MAKE_AST_MASK(ub,UB,UBYTE,UB,unsigned char)
+#undef MAKE_AST_MASK
+
+F77_SUBROUTINE(ast_getregionbounds)( INTEGER(THIS),
+                                     DOUBLE(LBND),
+                                     DOUBLE(UBND),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_DOUBLE(XOUT)
+   GENPTR_DOUBLE(YOUT)
+
+   astAt( "AST_GETREGIONBOUNDS", NULL, 0 );
+   astWatchSTATUS(
+      astGetRegionBounds( astI2P( *THIS ), LBND, UBND );
+   )
+}
+
+F77_SUBROUTINE(ast_showmesh)( INTEGER(THIS),
+                              LOGICAL(FORMAT),
+                              CHARACTER(TTL),
+                              INTEGER(STATUS)
+                              TRAIL(TTL) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_LOGICAL(FORMAT)
+   GENPTR_CHARACTER(TTL)
+   char *ttl;
+
+   astAt( "AST_SHOWMESH", NULL, 0 );
+   astWatchSTATUS(
+      ttl = astString( TTL, TTL_length );
+      astShowMesh( astI2P( *THIS ), F77_ISTRUE( *FORMAT ) ? 1 : 0, ttl );
+      ttl = astFree( ttl );
+   )
+}
+
+F77_SUBROUTINE(ast_getregionpoints)( INTEGER(THIS),
+                                     INTEGER(MAXPOINT),
+                                     INTEGER(MAXCOORD),
+                                     INTEGER(NPOINT),
+                                     DOUBLE_ARRAY(POINTS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(MAXPOINT)
+   GENPTR_INTEGER(MAXCOORD)
+   GENPTR_INTEGER(NPOINT)
+   GENPTR_DOUBLE_ARRAY(POINTS)
+
+   astAt( "AST_GETREGIONPOINT", NULL, 0 );
+   astWatchSTATUS(
+      astGetRegionPoints( astI2P( *THIS ), *MAXPOINT, *MAXCOORD, NPOINT,
+                          POINTS );
+   )
+}
+
+F77_SUBROUTINE(ast_getregionmesh)( INTEGER(THIS),
+                                   LOGICAL(SURFACE),
+                                   INTEGER(MAXPOINT),
+                                   INTEGER(MAXCOORD),
+                                   INTEGER(NPOINT),
+                                   DOUBLE_ARRAY(POINTS),
+                                   INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_LOGICAL(SURFACE)
+   GENPTR_INTEGER(MAXPOINT)
+   GENPTR_INTEGER(MAXCOORD)
+   GENPTR_INTEGER(NPOINT)
+   GENPTR_DOUBLE_ARRAY(POINTS)
+
+   astAt( "AST_GETREGIONMESH", NULL, 0 );
+   astWatchSTATUS(
+      astGetRegionMesh( astI2P( *THIS ), F77_ISTRUE( *SURFACE ), *MAXPOINT,
+                        *MAXCOORD, NPOINT, POINTS );
+   )
+}
+
+
diff --git a/ast/fselectormap.c b/ast/fselectormap.c
new file mode 100644
index 0000000..4ecc4a7
--- /dev/null
+++ b/ast/fselectormap.c
@@ -0,0 +1,115 @@
+/*
+*+
+*  Name:
+*     fselectormap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST SelectorMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the SelectorMap class.
+
+*  Routines Defined:
+*     AST_ISASELECTORMAP
+*     AST_SELECTORMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S.Berry (Starlink)
+
+*  History:
+*     14-MAR-2006 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "selectormap.h"         /* C interface to the SelectorMap class */
+
+F77_LOGICAL_FUNCTION(ast_isaselectormap)( INTEGER(THIS),
+                                        INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astWatchSTATUS(
+   astAt( "AST_ISASELECTORMAP", NULL, 0 );
+      RESULT = astIsASelectorMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_selectormap)( INTEGER(NREG),
+                                       INTEGER_ARRAY(REGS),
+                                       DOUBLE(BADVAL),
+                                       CHARACTER(OPTIONS),
+                                       INTEGER(STATUS)
+                                       TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NREG)
+   GENPTR_INTEGER_ARRAY(REGS)
+   GENPTR_DOUBLE(BADVAL)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+   AstObject **regs;
+
+   astAt( "AST_SELECTORMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+      regs = astMalloc( sizeof(AstObject *) * (*NREG) );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+
+         for ( i = 0; i < *NREG; i++ ) {
+            regs[ i ] = astI2P( REGS[ i ] );
+         }
+      }
+
+
+      RESULT = astP2I( astSelectorMap( *NREG, (void **) regs, *BADVAL, "%s",
+                                       options ) );
+      astFree( regs );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fshiftmap.c b/ast/fshiftmap.c
new file mode 100644
index 0000000..d876071
--- /dev/null
+++ b/ast/fshiftmap.c
@@ -0,0 +1,103 @@
+/*
+*+
+*  Name:
+*     fshiftmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST ShiftMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the ShiftMap class.
+
+*  Routines Defined:
+*     AST_ISASHIFTMAP
+*     AST_SHIFTMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     18-AUG-2003 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "shiftmap.h"            /* C interface to the ShiftMap class */
+
+F77_LOGICAL_FUNCTION(ast_isashiftmap)( INTEGER(THIS),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASHIFTMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAShiftMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_shiftmap)( INTEGER(NAXES),
+                                  DOUBLE(SHIFT),
+                                  CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NAXES)
+   GENPTR_DOUBLE(SHIFT)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_SHIFTMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astShiftMap( *NAXES, SHIFT, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fskyframe.c b/ast/fskyframe.c
new file mode 100644
index 0000000..ebdbaaa
--- /dev/null
+++ b/ast/fskyframe.c
@@ -0,0 +1,112 @@
+/*
+*+
+*  Name:
+*     fskyframe.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST SkyFrame class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the SkyFrame class.
+
+*  Routines Defined:
+*     AST_ISASKYFRAME
+*     AST_SKYFRAME
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     23-JUL-1996 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "skyframe.h"            /* C interface to the SkyFrame class */
+
+F77_LOGICAL_FUNCTION(ast_isaskyframe)( INTEGER(THIS),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASKYFRAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsASkyFrame( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_skyframe)( CHARACTER(OPTIONS),
+                                    INTEGER(STATUS)
+                                    TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_SKYFRAME", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astSkyFrame( "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_skyoffsetmap)( INTEGER(THIS),
+                                        INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_SKYOFFSETMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astSkyOffsetMap( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
diff --git a/ast/fslamap.c b/ast/fslamap.c
new file mode 100644
index 0000000..0fa631b
--- /dev/null
+++ b/ast/fslamap.c
@@ -0,0 +1,122 @@
+/*
+*+
+*  Name:
+*     fslamap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST SlaMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the SlaMap class.
+
+*  Routines Defined:
+*     AST_ISASLAMAP
+*     AST_SLAADD
+*     AST_SLAMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     28-MAY-1997 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "slamap.h"              /* C interface to the SlaMap class */
+
+F77_LOGICAL_FUNCTION(ast_isaslamap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASLAMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsASlaMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_slaadd)( INTEGER(THIS),
+                            CHARACTER(CVT),
+                            INTEGER(NARG),
+                            DOUBLE_ARRAY(ARGS),
+                            INTEGER(STATUS)
+                            TRAIL(CVT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(CVT)
+   GENPTR_INTEGER(NARG)
+   GENPTR_DOUBLE_ARRAY(ARGS)
+   char *cvt;
+
+   astAt( "AST_SLAADD", NULL, 0 );
+   astWatchSTATUS(
+      cvt = astString( CVT, CVT_length );
+      astSlaAdd( astI2P( *THIS ), cvt, *NARG, ARGS );
+      astFree( cvt );
+   )
+}
+
+F77_INTEGER_FUNCTION(ast_slamap)( INTEGER(FLAGS),
+                                  CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FLAGS)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_SLAMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astSlaMap( *FLAGS, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fspecfluxframe.c b/ast/fspecfluxframe.c
new file mode 100644
index 0000000..fb1c218
--- /dev/null
+++ b/ast/fspecfluxframe.c
@@ -0,0 +1,104 @@
+/*
+*+
+*  Name:
+*     fspecfluxframe.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST SpecFluxFrame class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the SpecFluxFrame class.
+
+*  Routines Defined:
+*     AST_SPECFLUXFRAME
+*     AST_ISASPECFLUXFRAME
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     8-DEC-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "specfluxframe.h"       /* C interface to the SpecFluxFrame class */
+
+F77_LOGICAL_FUNCTION(ast_isaspecfluxframe)( INTEGER(THIS),
+                                            INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASPECFLUXFRAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsASpecFluxFrame( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_specfluxframe)( INTEGER(FRAME1),
+                                         INTEGER(FRAME2),
+                                         CHARACTER(OPTIONS),
+                                         INTEGER(STATUS)
+                                         TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FRAME1)
+   GENPTR_INTEGER(FRAME2)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_SPECFLUXFRAME", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astSpecFluxFrame( astI2P( *FRAME1 ), astI2P( *FRAME2 ),
+                                    "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fspecframe.c b/ast/fspecframe.c
new file mode 100644
index 0000000..c45c7e3
--- /dev/null
+++ b/ast/fspecframe.c
@@ -0,0 +1,134 @@
+/*
+*+
+*  Name:
+*     fspecframe.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST SpecFrame class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the SpecFrame class.
+
+*  Routines Defined:
+*     AST_ISASPECFRAME
+*     AST_SPECFRAME
+*     AST_SETREFPOS
+*     AST_GETREFPOS
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     20-NOV-2002 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "specframe.h"           /* C interface to the SpecFrame class */
+
+F77_LOGICAL_FUNCTION(ast_isaspecframe)( INTEGER(THIS),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASPECFRAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsASpecFrame( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_specframe)( CHARACTER(OPTIONS),
+                                    INTEGER(STATUS)
+                                    TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_SPECFRAME", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astSpecFrame( "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_getrefpos)( INTEGER(THIS),
+                               INTEGER(FRM),
+                               DOUBLE(LON),
+                               DOUBLE(LAT),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(FRM)
+   GENPTR_DOUBLE(LON)
+   GENPTR_DOUBLE(LAT)
+
+   astAt( "AST_GETREFPOS", NULL, 0 );
+   astWatchSTATUS(
+      astGetRefPos( astI2P( *THIS ), astI2P( *FRM ), LON, LAT );
+   )
+}
+
+F77_SUBROUTINE(ast_setrefpos)( INTEGER(THIS),
+                               INTEGER(FRM),
+                               DOUBLE(LON),
+                               DOUBLE(LAT),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(FRM)
+   GENPTR_DOUBLE(LON)
+   GENPTR_DOUBLE(LAT)
+
+   astAt( "AST_SETREFPOS", NULL, 0 );
+   astWatchSTATUS(
+      astSetRefPos( astI2P( *THIS ), astI2P( *FRM ), *LON, *LAT );
+   )
+}
+
diff --git a/ast/fspecmap.c b/ast/fspecmap.c
new file mode 100644
index 0000000..f96db73
--- /dev/null
+++ b/ast/fspecmap.c
@@ -0,0 +1,124 @@
+/*
+*+
+*  Name:
+*     fspecmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST SpecMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the SpecMap class.
+
+*  Routines Defined:
+*     AST_ISASPECMAP
+*     AST_SPECADD
+*     AST_SPECMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     11-NOV-2002 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "specmap.h"             /* C interface to the SpecMap class */
+
+F77_LOGICAL_FUNCTION(ast_isaspecmap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASPECMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsASpecMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_specadd)( INTEGER(THIS),
+                            CHARACTER(CVT),
+                            INTEGER(NARG),
+                            DOUBLE_ARRAY(ARGS),
+                            INTEGER(STATUS)
+                            TRAIL(CVT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(CVT)
+   GENPTR_INTEGER(NARG)
+   GENPTR_DOUBLE_ARRAY(ARGS)
+   char *cvt;
+
+   astAt( "AST_SPECADD", NULL, 0 );
+   astWatchSTATUS(
+      cvt = astString( CVT, CVT_length );
+      astSpecAdd( astI2P( *THIS ), cvt, *NARG, ARGS );
+      astFree( cvt );
+   )
+}
+
+F77_INTEGER_FUNCTION(ast_specmap)( INTEGER(NIN),
+                                  INTEGER(FLAGS),
+                                  CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NIN)
+   GENPTR_INTEGER(FLAGS)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_SPECMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astSpecMap( *NIN, *FLAGS, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fsphmap.c b/ast/fsphmap.c
new file mode 100644
index 0000000..439fec9
--- /dev/null
+++ b/ast/fsphmap.c
@@ -0,0 +1,99 @@
+/*
+*+
+*  Name:
+*     fsphmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST SphMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the SphMap class.
+
+*  Routines Defined:
+*     AST_ISASPHMAP
+*     AST_SPHMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     25-OCT-1996 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "sphmap.h"              /* C interface to the SphMap class */
+
+F77_LOGICAL_FUNCTION(ast_isasphmap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASPHMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsASphMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_sphmap)( CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_SPHMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astSphMap( "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fstc.c b/ast/fstc.c
new file mode 100644
index 0000000..50c53bb
--- /dev/null
+++ b/ast/fstc.c
@@ -0,0 +1,114 @@
+/*
+*+
+*  Name:
+*     fstc.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Stc class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Stc class.
+
+*  Routines Defined:
+*     AST_ISASTC
+*     AST_GETSTCREGION
+*     AST_GETSTCCOORD
+*     AST_GETSTCNCOORD
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-NOV-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "stc.h"                 /* C interface to the Stc class */
+
+
+F77_INTEGER_FUNCTION(ast_getstcncoord)( INTEGER(THIS),
+                                        INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETSTCNCOORD", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astGetStcNCoord( astI2P( *THIS ) );
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_getstccoord)( INTEGER(THIS),
+                                       INTEGER(ICOORD),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(ICOORD)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETSTCCOORD", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astGetStcCoord( astI2P( *THIS ), *ICOORD ) );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_isastc)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASTC", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAStc( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_getstcregion)( INTEGER(THIS),
+                                        INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_INTEGER_TYPE(RESULT);
+
+   astAt( "AST_GETSTCREGION", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astP2I( astGetStcRegion( astI2P( *THIS ) ) );
+   )
+   return RESULT;
+}
+
+
diff --git a/ast/fstccatalogentrylocation.c b/ast/fstccatalogentrylocation.c
new file mode 100644
index 0000000..5c88aa0
--- /dev/null
+++ b/ast/fstccatalogentrylocation.c
@@ -0,0 +1,117 @@
+/*
+*+
+*  Name:
+*     fstccatalogentrylocation.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST StcCatalogEntryLocation class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the StcCatalogEntryLocation class.
+
+*  Routines Defined:
+*     AST_ISASTCCATALOGENTRYLOCATION
+*     AST_STCCATALOGENTRYLOCATION
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-NOV-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "stccatalogentrylocation.h" /* C interface to the StcCatalogEntryLocation class */
+
+
+F77_LOGICAL_FUNCTION(ast_isastccatalogentrylocation)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASTCCATALOGENTRYLOCATION", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAStcCatalogEntryLocation( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_stccatalogentrylocation)( INTEGER(REG),
+                               INTEGER(NCOORDS),
+                               INTEGER_ARRAY(COORDS),
+                               CHARACTER(OPTIONS),
+                               INTEGER(STATUS)
+                               TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(REG)
+   GENPTR_INTEGER(NCOORDS)
+   GENPTR_CHARACTER(OPTIONS)
+   GENPTR_INTEGER_ARRAY(COORDS)
+   AstKeyMap **coords;
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_STCCATALOGENTRYLOCATION", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+/* Convert supplied integers to pointers. */
+      coords = astMalloc( sizeof( AstKeyMap * )*(size_t)( *NCOORDS ));
+      if( astOK ) {
+         for( i = 0; i < *NCOORDS; i++ ) {
+            coords[ i ] = (AstKeyMap *) astMakePointer( astI2P( COORDS[ i ] ));
+         }
+      }
+
+      RESULT = astP2I( astStcCatalogEntryLocation( astI2P( *REG ), *NCOORDS,
+                                                   coords, "%s", options ) );
+      astFree( coords );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fstcobsdatalocation.c b/ast/fstcobsdatalocation.c
new file mode 100644
index 0000000..767ebb4
--- /dev/null
+++ b/ast/fstcobsdatalocation.c
@@ -0,0 +1,117 @@
+/*
+*+
+*  Name:
+*     fstcobsdatalocation.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST StcObsDataLocation class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the StcObsDataLocation class.
+
+*  Routines Defined:
+*     AST_ISASTCOBSDATALOCATION
+*     AST_STCOBSDATALOCATION
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-NOV-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "stcobsdatalocation.h" /* C interface to the StcObsDataLocation class */
+
+
+F77_LOGICAL_FUNCTION(ast_isastcobsdatalocation)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASTCOBSDATALOCATION", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAStcObsDataLocation( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_stcobsdatalocation)( INTEGER(REG),
+                               INTEGER(NCOORDS),
+                               INTEGER_ARRAY(COORDS),
+                               CHARACTER(OPTIONS),
+                               INTEGER(STATUS)
+                               TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(REG)
+   GENPTR_INTEGER(NCOORDS)
+   GENPTR_CHARACTER(OPTIONS)
+   GENPTR_INTEGER_ARRAY(COORDS)
+   AstKeyMap **coords;
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_STCOBSDATALOCATION", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+/* Convert supplied integers to pointers. */
+      coords = astMalloc( sizeof( AstKeyMap * )*(size_t)( *NCOORDS ));
+      if( astOK ) {
+         for( i = 0; i < *NCOORDS; i++ ) {
+            coords[ i ] = (AstKeyMap *) astMakePointer( astI2P( COORDS[ i ] ));
+         }
+      }
+
+      RESULT = astP2I( astStcObsDataLocation( astI2P( *REG ), *NCOORDS,
+                                              coords, "%s", options ) );
+      astFree( coords );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fstcresourceprofile.c b/ast/fstcresourceprofile.c
new file mode 100644
index 0000000..04484f3
--- /dev/null
+++ b/ast/fstcresourceprofile.c
@@ -0,0 +1,118 @@
+/*
+*+
+*  Name:
+*     fstcresourceprofile.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST StcResourceProfile class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the StcResourceProfile class.
+
+*  Routines Defined:
+*     AST_ISASTCRESOURCEPROFILE
+*     AST_STCRESOURCEPROFILE
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-NOV-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "object.h"              /* Basic AST Object management functions */
+#include "stcresourceprofile.h" /* C interface to the StcResourceProfile class */
+
+
+F77_LOGICAL_FUNCTION(ast_isastcresourceprofile)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASTCRESOURCEPROFILE", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAStcResourceProfile( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_stcresourceprofile)( INTEGER(REG),
+                               INTEGER(NCOORDS),
+                               INTEGER_ARRAY(COORDS),
+                               CHARACTER(OPTIONS),
+                               INTEGER(STATUS)
+                               TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(REG)
+   GENPTR_INTEGER(NCOORDS)
+   GENPTR_CHARACTER(OPTIONS)
+   GENPTR_INTEGER_ARRAY(COORDS)
+   AstKeyMap **coords;
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_STCRESOURCEPROFILE", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+/* Convert supplied integers to pointers. */
+      coords = astMalloc( sizeof( AstKeyMap * )*(size_t)( *NCOORDS ));
+      if( astOK ) {
+         for( i = 0; i < *NCOORDS; i++ ) {
+            coords[ i ] = (AstKeyMap *) astMakePointer( astI2P( COORDS[ i ] ));
+         }
+      }
+
+      RESULT = astP2I( astStcResourceProfile( astI2P( *REG ), *NCOORDS,
+                                                   coords, "%s", options ) );
+      astFree( coords );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fstcschan.c b/ast/fstcschan.c
new file mode 100644
index 0000000..9f47469
--- /dev/null
+++ b/ast/fstcschan.c
@@ -0,0 +1,131 @@
+/*
+*+
+*  Name:
+*     fstcschan.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST StcsChan class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the StcsChan class.
+
+*  Routines Defined:
+*     AST_STCSCHAN
+*     AST_ISASTCSCHAN
+
+*  Copyright:
+*     Copyright (C) 2008 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (JAC,UCLan)
+
+*  History:
+*     18-DEC-2008 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "channel.h"             /* Provides wrapper functions */
+#include "stcschan.h"             /* C interface to the StcsChan class */
+
+#include <stddef.h>
+
+/* Prototypes for external functions. */
+/* ================================== */
+/* This is the null function defined by the FORTRAN interface in fobject.c. */
+F77_SUBROUTINE(ast_null)( void );
+
+/* FORTRAN interface functions. */
+/* ============================ */
+/* These functions implement the remainder of the FORTRAN interface. */
+F77_INTEGER_FUNCTION(ast_stcschan)( void (* SOURCE)(),
+                                   void (* SINK)(),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   const char *(* source)( void );
+   int i;
+   void (* sink)( const char * );
+
+   astAt( "AST_STCSCHAN", NULL, 0 );
+   astWatchSTATUS(
+
+/* Set the source and sink function pointers to NULL if a pointer to
+   the null routine AST_NULL has been supplied. */
+      source = (const char *(*)( void )) SOURCE;
+      if ( source == (const char *(*)( void )) F77_EXTERNAL_NAME(ast_null) ) {
+         source = NULL;
+      }
+      sink = (void (*)( const char * )) SINK;
+      if ( sink == (void (*)( const char * )) F77_EXTERNAL_NAME(ast_null) ) {
+         sink = NULL;
+      }
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astStcsChanFor( source, astSourceWrap, sink, astSinkWrap,
+                                      "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_isastcschan)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASTCSCHAN", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAStcsChan( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+
+
+
+
diff --git a/ast/fstcsearchlocation.c b/ast/fstcsearchlocation.c
new file mode 100644
index 0000000..aa74baf
--- /dev/null
+++ b/ast/fstcsearchlocation.c
@@ -0,0 +1,117 @@
+/*
+*+
+*  Name:
+*     fstcsearchlocation.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST StcSearchLocation class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the StcSearchLocation class.
+
+*  Routines Defined:
+*     AST_ISASTCSEARCHLOCATION
+*     AST_STCSEARCHLOCATION
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-NOV-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "stcsearchlocation.h" /* C interface to the StcSearchLocation class */
+
+
+F77_LOGICAL_FUNCTION(ast_isastcsearchlocation)( INTEGER(THIS), INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISASTCSEARCHLOCATION", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAStcSearchLocation( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_stcsearchlocation)( INTEGER(REG),
+                               INTEGER(NCOORDS),
+                               INTEGER_ARRAY(COORDS),
+                               CHARACTER(OPTIONS),
+                               INTEGER(STATUS)
+                               TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(REG)
+   GENPTR_INTEGER(NCOORDS)
+   GENPTR_CHARACTER(OPTIONS)
+   GENPTR_INTEGER_ARRAY(COORDS)
+   AstKeyMap **coords;
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_STCSEARCHLOCATION", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+
+/* Convert supplied integers to pointers. */
+      coords = astMalloc( sizeof( AstKeyMap * )*(size_t)( *NCOORDS ));
+      if( astOK ) {
+         for( i = 0; i < *NCOORDS; i++ ) {
+            coords[ i ] = (AstKeyMap *) astMakePointer( astI2P( COORDS[ i ] ));
+         }
+      }
+
+      RESULT = astP2I( astStcSearchLocation( astI2P( *REG ), *NCOORDS,
+                                                   coords, "%s", options ) );
+      astFree( coords );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fswitchmap.c b/ast/fswitchmap.c
new file mode 100644
index 0000000..0159420
--- /dev/null
+++ b/ast/fswitchmap.c
@@ -0,0 +1,118 @@
+/*
+*+
+*  Name:
+*     fswitchmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST SwitchMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the SwitchMap class.
+
+*  Routines Defined:
+*     AST_ISASWITCHMAP
+*     AST_SWITCHMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S.Berry (Starlink)
+
+*  History:
+*     13-MAR-2006 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "switchmap.h"           /* C interface to the SwitchMap class */
+
+F77_LOGICAL_FUNCTION(ast_isaswitchmap)( INTEGER(THIS),
+                                        INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astWatchSTATUS(
+   astAt( "AST_ISASWITCHMAP", NULL, 0 );
+      RESULT = astIsASwitchMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_switchmap)( INTEGER(FSMAP),
+                                     INTEGER(ISMAP),
+                                     INTEGER(NROUTE),
+                                     INTEGER_ARRAY(ROUTEMAPS),
+                                     CHARACTER(OPTIONS),
+                                     INTEGER(STATUS)
+                                     TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FSMAP)
+   GENPTR_INTEGER(ISMAP)
+   GENPTR_INTEGER(NROUTE)
+   GENPTR_INTEGER_ARRAY(ROUTEMAPS)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+   AstObject **routemaps;
+
+   astAt( "AST_SWITCHMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+      routemaps = astMalloc( sizeof(AstObject *) * (*NROUTE) );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+
+         for ( i = 0; i < *NROUTE; i++ ) {
+            routemaps[ i ] = astI2P( ROUTEMAPS[ i ] );
+         }
+      }
+
+
+      RESULT = astP2I( astSwitchMap( astI2P( *FSMAP ), astI2P( *ISMAP ),
+                                     *NROUTE, (void **) routemaps, "%s",
+                                     options ) );
+      astFree( routemaps );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/ftable.c b/ast/ftable.c
new file mode 100644
index 0000000..5656531
--- /dev/null
+++ b/ast/ftable.c
@@ -0,0 +1,330 @@
+/*
+*+
+*  Name:
+*     ftable.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST Table class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the Table class.
+
+*  Routines Defined:
+*     AST_ISATABLE
+*     AST_TABLE
+
+*  Copyright:
+*     Copyright (C) 2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S.Berry (Starlink)
+
+*  History:
+*     22-NOV-2010 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "table.h"              /* C interface to the Table class */
+
+F77_LOGICAL_FUNCTION(ast_isatable)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astWatchSTATUS(
+   astAt( "AST_ISATABLE", NULL, 0 );
+      RESULT = astIsATable( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_table)( CHARACTER(OPTIONS),
+                                 INTEGER(STATUS)
+                                 TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_TABLE", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astTable( "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_addcolumn)( INTEGER(THIS),
+                               CHARACTER(NAME),
+                               INTEGER(TYPE),
+                               INTEGER(NDIM),
+                               INTEGER_ARRAY(DIMS),
+                               CHARACTER(UNIT),
+                               INTEGER(STATUS)
+                               TRAIL(NAME)
+                               TRAIL(UNIT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   GENPTR_INTEGER(TYPE)
+   GENPTR_INTEGER(NDIM)
+   GENPTR_INTEGER_ARRAY(DIMS)
+   GENPTR_CHARACTER(UNIT)
+   char *name, *unit;
+
+   astAt( "AST_ADDCOLUMN", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      unit = astString( UNIT, UNIT_length );
+      astAddColumn( astI2P( *THIS ), name, *TYPE, *NDIM, DIMS, unit );
+      astFree( name );
+      astFree( unit );
+   )
+}
+
+F77_SUBROUTINE(ast_addparameter)( INTEGER(THIS),
+                                  CHARACTER(NAME),
+                                  INTEGER(STATUS)
+                                  TRAIL(NAME) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   char *name;
+
+   astAt( "AST_ADDPARAMETER", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      astAddParameter( astI2P( *THIS ), name );
+      astFree( name );
+   )
+}
+
+F77_SUBROUTINE(ast_removecolumn)( INTEGER(THIS),
+                                  CHARACTER(NAME),
+                                  INTEGER(STATUS)
+                                  TRAIL(NAME) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   char *name;
+
+   astAt( "AST_REMOVECOLUMN", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      astRemoveColumn( astI2P( *THIS ), name );
+      astFree( name );
+   )
+}
+
+F77_SUBROUTINE(ast_removeparameter)( INTEGER(THIS),
+                                     CHARACTER(NAME),
+                                     INTEGER(STATUS)
+                                     TRAIL(NAME) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(NAME)
+   char *name;
+
+   astAt( "AST_REMOVEPARAMETER", NULL, 0 );
+   astWatchSTATUS(
+      name = astString( NAME, NAME_length );
+      astRemoveParameter( astI2P( *THIS ), name );
+      astFree( name );
+   )
+}
+
+F77_SUBROUTINE(ast_removerow)( INTEGER(THIS),
+                               INTEGER(INDEX),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(INDEX)
+
+   astAt( "AST_REMOVEROW", NULL, 0 );
+   astWatchSTATUS(
+      astRemoveRow( astI2P( *THIS ), *INDEX );
+   )
+}
+
+F77_SUBROUTINE(ast_purgerows)( INTEGER(THIS),
+                               INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+
+   astAt( "AST_PURGEROWS", NULL, 0 );
+   astWatchSTATUS(
+      astPurgeRows( astI2P( *THIS ) );
+   )
+}
+
+
+/* NO_CHAR_FUNCTION indicates that the f77.h method of returning a
+   character result doesn't work, so add an extra argument instead and
+   wrap this function up in a normal FORTRAN 77 function (in the file
+   keymap.f). */
+#if NO_CHAR_FUNCTION
+F77_SUBROUTINE(ast_columnname_a)( CHARACTER(RESULT),
+#else
+F77_SUBROUTINE(ast_columnname)( CHARACTER_RETURN_VALUE(RESULT),
+#endif
+                          INTEGER(THIS),
+                          INTEGER(INDEX),
+#if NO_CHAR_FUNCTION
+                          INTEGER(STATUS)
+                          TRAIL(RESULT) ) {
+#else
+                          INTEGER(STATUS) ) {
+#endif
+   GENPTR_CHARACTER(RESULT)
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(INDEX)
+   const char *result;
+   int i;
+
+   astAt( "AST_COLUMNNAME", NULL, 0 );
+   astWatchSTATUS(
+      result = astColumnName( astI2P( *THIS ), *INDEX );
+      i = 0;
+      if ( astOK ) {             /* Copy result */
+         for ( ; result[ i ] && i < RESULT_length; i++ ) {
+            RESULT[ i ] = result[ i ];
+         }
+      }
+      while ( i < RESULT_length ) RESULT[ i++ ] = ' '; /* Pad with blanks */
+   )
+}
+
+/* NO_CHAR_FUNCTION indicates that the f77.h method of returning a
+   character result doesn't work, so add an extra argument instead and
+   wrap this function up in a normal FORTRAN 77 function (in the file
+   keymap.f). */
+#if NO_CHAR_FUNCTION
+F77_SUBROUTINE(ast_parametername_a)( CHARACTER(RESULT),
+#else
+F77_SUBROUTINE(ast_parametername)( CHARACTER_RETURN_VALUE(RESULT),
+#endif
+                          INTEGER(THIS),
+                          INTEGER(INDEX),
+#if NO_CHAR_FUNCTION
+                          INTEGER(STATUS)
+                          TRAIL(RESULT) ) {
+#else
+                          INTEGER(STATUS) ) {
+#endif
+   GENPTR_CHARACTER(RESULT)
+   GENPTR_INTEGER(THIS)
+   GENPTR_INTEGER(INDEX)
+   const char *result;
+   int i;
+
+   astAt( "AST_PARAMETERNAME", NULL, 0 );
+   astWatchSTATUS(
+      result = astParameterName( astI2P( *THIS ), *INDEX );
+      i = 0;
+      if ( astOK ) {             /* Copy result */
+         for ( ; result[ i ] && i < RESULT_length; i++ ) {
+            RESULT[ i ] = result[ i ];
+         }
+      }
+      while ( i < RESULT_length ) RESULT[ i++ ] = ' '; /* Pad with blanks */
+   )
+}
+
+
+F77_SUBROUTINE(ast_columnshape)( INTEGER(THIS),
+                                 CHARACTER(COLUMN),
+                                 INTEGER(MXDIM),
+                                 INTEGER(NDIM),
+                                 INTEGER_ARRAY(DIMS),
+                                 INTEGER(STATUS)
+                                 TRAIL(COLUMN) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(COLUMN)
+   GENPTR_INTEGER(MXDIM)
+   GENPTR_INTEGER(NDIM)
+   GENPTR_INTEGER_ARRAY(DIMS)
+   char *column;
+
+   astAt( "AST_COLUMNSHAPE", NULL, 0 );
+   astWatchSTATUS(
+      column = astString( COLUMN, COLUMN_length );
+      astColumnShape( astI2P( *THIS ), column, *MXDIM, NDIM, DIMS );
+      astFree( column );
+   )
+}
+
+
+F77_LOGICAL_FUNCTION(ast_hascolumn)( INTEGER(THIS),
+                                     CHARACTER(COLUMN),
+                                     INTEGER(STATUS)
+                                     TRAIL(COLUMN) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(COLUMN)
+   F77_LOGICAL_TYPE(RESULT);
+   char *column;
+
+   astWatchSTATUS(
+   astAt( "AST_HASCOLUMN", NULL, 0 );
+      column = astString( COLUMN, COLUMN_length );
+      RESULT = astHasColumn( astI2P( *THIS ), column ) ? F77_TRUE : F77_FALSE;
+      astFree( column );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_hasparameter)( INTEGER(THIS),
+                                        CHARACTER(PARAM),
+                                        INTEGER(STATUS)
+                                        TRAIL(PARAM) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(PARAM)
+   F77_LOGICAL_TYPE(RESULT);
+   char *param;
+
+   astWatchSTATUS(
+   astAt( "AST_HASPARAMETER", NULL, 0 );
+      param = astString( PARAM, PARAM_length );
+      RESULT = astHasParameter( astI2P( *THIS ), param ) ? F77_TRUE : F77_FALSE;
+      astFree( param );
+   )
+   return RESULT;
+}
+
diff --git a/ast/ftimeframe.c b/ast/ftimeframe.c
new file mode 100644
index 0000000..f164c26
--- /dev/null
+++ b/ast/ftimeframe.c
@@ -0,0 +1,114 @@
+/*
+*+
+*  Name:
+*     ftimeframe.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST TimeFrame class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the TimeFrame class.
+
+*  Routines Defined:
+*     AST_ISATIMEFRAME
+*     AST_TIMEFRAME
+*     AST_CURRENTTIME
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     NG: Norman Gray (Starlink)
+
+*  History:
+*     02-AUG-2003 (NG):
+*        Original version, heavily based on fspecframe.c.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "timeframe.h"           /* C interface to the TimeFrame class */
+
+F77_LOGICAL_FUNCTION(ast_isatimeframe)( INTEGER(THIS),
+                                       INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISATIMEFRAME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsATimeFrame( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_timeframe)( CHARACTER(OPTIONS),
+                                    INTEGER(STATUS)
+                                    TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_TIMEFRAME", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astTimeFrame( "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_DOUBLE_FUNCTION(ast_currenttime)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_DOUBLE_TYPE(RESULT);
+
+   astAt( "AST_CURRENTTIME", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astCurrentTime( astI2P( *THIS ) );
+   )
+   return RESULT;
+}
+
+
diff --git a/ast/ftimemap.c b/ast/ftimemap.c
new file mode 100644
index 0000000..a9929f2
--- /dev/null
+++ b/ast/ftimemap.c
@@ -0,0 +1,122 @@
+/*
+*+
+*  Name:
+*     ftimemap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST TimeMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the TimeMap class.
+
+*  Routines Defined:
+*     AST_ISATIMEMAP
+*     AST_TIMEADD
+*     AST_TIMEMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     NG: Norman Gray (Starlink)
+
+*  History:
+*     08-Sep-2003 (NG):
+*        Original version (heavily based on fspecmap.c)
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "timemap.h"             /* C interface to the TimeMap class */
+
+F77_LOGICAL_FUNCTION(ast_isatimemap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISATIMEMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsATimeMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_SUBROUTINE(ast_timeadd)( INTEGER(THIS),
+                             CHARACTER(CVT),
+                             INTEGER(NARG),
+                             DOUBLE_ARRAY(ARGS),
+                             INTEGER(STATUS)
+                             TRAIL(CVT) ) {
+   GENPTR_INTEGER(THIS)
+   GENPTR_CHARACTER(CVT)
+   GENPTR_INTEGER(NARG)
+   GENPTR_DOUBLE_ARRAY(ARGS)
+   char *cvt;
+
+   astAt( "AST_TIMEADD", NULL, 0 );
+   astWatchSTATUS(
+      cvt = astString( CVT, CVT_length );
+      astTimeAdd( astI2P( *THIS ), cvt, *NARG, ARGS );
+      astFree( cvt );
+   )
+}
+
+F77_INTEGER_FUNCTION(ast_timemap)( INTEGER(FLAGS),
+                                  CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(FLAGS)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_TIMEMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astTimeMap( *FLAGS, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/ftranmap.c b/ast/ftranmap.c
new file mode 100644
index 0000000..155439b
--- /dev/null
+++ b/ast/ftranmap.c
@@ -0,0 +1,104 @@
+/*
+*+
+*  Name:
+*     ftranmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST TranMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the TranMap class.
+
+*  Routines Defined:
+*     AST_ISATRANMAP
+*     AST_TRANMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S.Berry (Starlink)
+
+*  History:
+*     10-FEB-2004 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "tranmap.h"              /* C interface to the TranMap class */
+
+F77_LOGICAL_FUNCTION(ast_isatranmap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astWatchSTATUS(
+   astAt( "AST_ISATRANMAP", NULL, 0 );
+      RESULT = astIsATranMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_tranmap)( INTEGER(MAP1),
+                                  INTEGER(MAP2),
+                                  CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(MAP1)
+   GENPTR_INTEGER(MAP2)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   int i;
+   char *options;
+
+   astAt( "AST_TRANMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astTranMap( astI2P( *MAP1 ), astI2P( *MAP2 ),
+                                  "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/funitmap.c b/ast/funitmap.c
new file mode 100644
index 0000000..986a82d
--- /dev/null
+++ b/ast/funitmap.c
@@ -0,0 +1,101 @@
+/*
+*+
+*  Name:
+*     funitmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST UnitMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the UnitMap class.
+
+*  Routines Defined:
+*     AST_ISAUNITMAP
+*     AST_UNITMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     25-SEP-1996 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "unitmap.h"             /* C interface to the UnitMap class */
+
+F77_LOGICAL_FUNCTION(ast_isaunitmap)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAUNITMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAUnitMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_unitmap)( INTEGER(NCOORD),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NCOORD)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_UNITMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astUnitMap( *NCOORD, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/funitnormmap.c b/ast/funitnormmap.c
new file mode 100644
index 0000000..8e88009
--- /dev/null
+++ b/ast/funitnormmap.c
@@ -0,0 +1,105 @@
+/*
+*+
+*  Name:
+*     fshiftmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST UnitNormMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the UnitNormMap class.
+
+*  Routines Defined:
+*     AST_ISAUNITNORMMAP
+*     AST_UNITNORMMAP
+
+*  Copyright:
+*     Copyright (C) 2016 AURA/LSST
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+*     TIMJ: Tim Jenness (LSST)
+
+*  History:
+*     18-AUG-2003 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "unitnormmap.h"         /* C interface to the UnitNormMap class */
+
+F77_LOGICAL_FUNCTION(ast_isaunitnormmap)( INTEGER(THIS),
+                                          INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAUNITNORMMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAUnitNormMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_unitnormmap)( INTEGER(NCOORDS),
+                                       DOUBLE(CENTRE),
+                                       CHARACTER(OPTIONS),
+                                       INTEGER(STATUS)
+                                       TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NCOORDS)
+   GENPTR_DOUBLE(CENTRE)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_UNITNORMMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astUnitNormMap( *NCOORDS, CENTRE, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fwcsmap.c b/ast/fwcsmap.c
new file mode 100644
index 0000000..0b1e3a3
--- /dev/null
+++ b/ast/fwcsmap.c
@@ -0,0 +1,108 @@
+/*
+*+
+*  Name:
+*     fwcsmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST WcsMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the WcsMap class.
+
+*  Routines Defined:
+*     AST_ISAWCSMAP
+*     AST_WCSMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     18-NOV-1996 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "wcsmap.h"              /* C interface to the WcsMap class */
+
+F77_LOGICAL_FUNCTION(ast_isawcsmap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAWCSMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAWcsMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_wcsmap)( INTEGER(NAXES),
+                                  INTEGER(TYPE),
+                                  INTEGER(LONAX),
+                                  INTEGER(LATAX),
+                                  CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NAXES)
+   GENPTR_INTEGER(TYPE)
+   GENPTR_INTEGER(LONAX)
+   GENPTR_INTEGER(LATAX)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_WCSMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astWcsMap( *NAXES, *TYPE, *LONAX, *LATAX,
+                       "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fwinmap.c b/ast/fwinmap.c
new file mode 100644
index 0000000..41338a0
--- /dev/null
+++ b/ast/fwinmap.c
@@ -0,0 +1,110 @@
+/*
+*+
+*  Name:
+*     fwinmap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST WinMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the WinMap class.
+
+*  Routines Defined:
+*     AST_ISAWINMAP
+*     AST_WINMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     23-OCT-1996 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "winmap.h"              /* C interface to the WinMap class */
+
+F77_LOGICAL_FUNCTION(ast_isawinmap)( INTEGER(THIS),
+                                     INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAWINMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAWinMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_winmap)( INTEGER(NAXES),
+                                  DOUBLE(C1_IN),
+                                  DOUBLE(C2_IN),
+                                  DOUBLE(C1_OUT),
+                                  DOUBLE(C2_OUT),
+                                  CHARACTER(OPTIONS),
+                                  INTEGER(STATUS)
+                                  TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NAXES)
+   GENPTR_DOUBLE(C1_IN)
+   GENPTR_DOUBLE(C2_IN)
+   GENPTR_DOUBLE(C1_OUT)
+   GENPTR_DOUBLE(C2_OUT)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_WINMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astWinMap( *NAXES, C1_IN, C2_IN, C1_OUT, C2_OUT,
+                                  "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/fxmlchan.c b/ast/fxmlchan.c
new file mode 100644
index 0000000..17efc50
--- /dev/null
+++ b/ast/fxmlchan.c
@@ -0,0 +1,130 @@
+/*
+*+
+*  Name:
+*     fxmlchan.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST XmlChan class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the XmlChan class.
+
+*  Routines Defined:
+*     AST_XMLCHAN
+*     AST_ISAXMLCHAN
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     21-OCT-2003 (DSB):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "channel.h"             /* Provides wrapper functions */
+#include "xmlchan.h"             /* C interface to the XmlChan class */
+
+#include <stddef.h>
+
+/* Prototypes for external functions. */
+/* ================================== */
+/* This is the null function defined by the FORTRAN interface in fobject.c. */
+F77_SUBROUTINE(ast_null)( void );
+
+/* FORTRAN interface functions. */
+/* ============================ */
+/* These functions implement the remainder of the FORTRAN interface. */
+F77_INTEGER_FUNCTION(ast_xmlchan)( void (* SOURCE)(),
+                                   void (* SINK)(),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   const char *(* source)( void );
+   int i;
+   void (* sink)( const char * );
+
+   astAt( "AST_XMLCHAN", NULL, 0 );
+   astWatchSTATUS(
+
+/* Set the source and sink function pointers to NULL if a pointer to
+   the null routine AST_NULL has been supplied. */
+      source = (const char *(*)( void )) SOURCE;
+      if ( source == (const char *(*)( void )) F77_EXTERNAL_NAME(ast_null) ) {
+         source = NULL;
+      }
+      sink = (void (*)( const char * )) SINK;
+      if ( sink == (void (*)( const char * )) F77_EXTERNAL_NAME(ast_null) ) {
+         sink = NULL;
+      }
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astXmlChanFor( source, astSourceWrap, sink, astSinkWrap,
+                                      "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
+
+F77_LOGICAL_FUNCTION(ast_isaxmlchan)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAXMLCHAN", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAXmlChan( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+
+
+
diff --git a/ast/fzoommap.c b/ast/fzoommap.c
new file mode 100644
index 0000000..d2c8e48
--- /dev/null
+++ b/ast/fzoommap.c
@@ -0,0 +1,103 @@
+/*
+*+
+*  Name:
+*     fzoommap.c
+
+*  Purpose:
+*     Define a FORTRAN 77 interface to the AST ZoomMap class.
+
+*  Type of Module:
+*     C source file.
+
+*  Description:
+*     This file defines FORTRAN 77-callable C functions which provide
+*     a public FORTRAN 77 interface to the ZoomMap class.
+
+*  Routines Defined:
+*     AST_ISAZOOMMAP
+*     AST_ZOOMMAP
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     18-JUL-1996 (RFWS):
+*        Original version.
+*/
+
+/* Define the astFORTRAN77 macro which prevents error messages from
+   AST C functions from reporting the file and line number where the
+   error occurred (since these would refer to this file, they would
+   not be useful). */
+#define astFORTRAN77
+
+/* Header files. */
+/* ============= */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* F77 <-> C support functions/macros */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory handling facilities */
+#include "zoommap.h"             /* C interface to the ZoomMap class */
+
+F77_LOGICAL_FUNCTION(ast_isazoommap)( INTEGER(THIS),
+                                      INTEGER(STATUS) ) {
+   GENPTR_INTEGER(THIS)
+   F77_LOGICAL_TYPE(RESULT);
+
+   astAt( "AST_ISAZOOMMAP", NULL, 0 );
+   astWatchSTATUS(
+      RESULT = astIsAZoomMap( astI2P( *THIS ) ) ? F77_TRUE : F77_FALSE;
+   )
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_zoommap)( INTEGER(NAXES),
+                                   DOUBLE(ZOOM),
+                                   CHARACTER(OPTIONS),
+                                   INTEGER(STATUS)
+                                   TRAIL(OPTIONS) ) {
+   GENPTR_INTEGER(NAXES)
+   GENPTR_DOUBLE(ZOOM)
+   GENPTR_CHARACTER(OPTIONS)
+   F77_INTEGER_TYPE(RESULT);
+   char *options;
+   int i;
+
+   astAt( "AST_ZOOMMAP", NULL, 0 );
+   astWatchSTATUS(
+      options = astString( OPTIONS, OPTIONS_length );
+
+/* Truncate the options string to exlucde any trailing spaces. */
+      astChrTrunc( options );
+
+/* Change ',' to '\n' (see AST_SET in fobject.c for why). */
+      if ( astOK ) {
+         for ( i = 0; options[ i ]; i++ ) {
+            if ( options[ i ] == ',' ) options[ i ] = '\n';
+         }
+      }
+      RESULT = astP2I( astZoomMap( *NAXES, *ZOOM, "%s", options ) );
+      astFree( options );
+   )
+   return RESULT;
+}
diff --git a/ast/globals.c b/ast/globals.c
new file mode 100644
index 0000000..5606b60
--- /dev/null
+++ b/ast/globals.c
@@ -0,0 +1,252 @@
+#if defined( THREAD_SAFE )
+
+#define astCLASS
+
+#include "globals.h"
+#include "error.h"
+#include <pthread.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/* Configuration results. */
+/* ---------------------- */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Select the appropriate memory management functions. These will be the
+   system's malloc, free and realloc unless AST was configured with the
+   "--with-starmem" option, in which case they will be the starmem
+   malloc, free and realloc. */
+#ifdef HAVE_STAR_MEM_H
+#  include <star/mem.h>
+#  define MALLOC starMalloc
+#  define FREE starFree
+#  define REALLOC starRealloc
+#else
+#  define MALLOC malloc
+#  define FREE free
+#  define REALLOC realloc
+#endif
+
+/* Module variables */
+/* ================ */
+
+/* A count of the number of thread-specific data structures created so
+   far. Create a mutex to serialise access to this static variable. */
+static int nthread = 0;
+static pthread_mutex_t nthread_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* External variables visible throughout AST */
+/* ========================================= */
+
+/* Set a flag indicating that the thread-specific data key has not yet
+   been created. */
+pthread_once_t starlink_ast_globals_initialised = PTHREAD_ONCE_INIT;
+
+/* Declare the pthreads key that will be associated with the thread-specific
+   data for each thread. */
+pthread_key_t starlink_ast_globals_key;
+
+/* Declare the pthreads key that will be associated with the thread-specific
+   status value for each thread. */
+pthread_key_t starlink_ast_status_key;
+
+
+/* Function definitions: */
+/* ===================== */
+
+
+void astGlobalsCreateKey_( void ) {
+/*
+*+
+*  Name:
+*     astGlobalsCreateKey_
+
+*  Purpose:
+*     Create the thread specific data key used for accessing global data.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "globals.h"
+*     astGlobalsCreateKey_()
+
+*  Description:
+*     This function creates the thread-specific data key. It is called
+*     once only by the pthread_once function, which is invoked via the
+*     astGET_GLOBALS(this) macro by each AST function that requires access to
+*     global data.
+
+*  Returned Value:
+*     Zero for success.
+
+*-
+*/
+
+/* Create the key used to access thread-specific global data values.
+   Report an error if it fails. */
+   if( pthread_key_create( &starlink_ast_globals_key, NULL ) ) {
+      fprintf( stderr, "ast: Failed to create Thread-Specific Data key" );
+
+/* If succesful, create the key used to access the thread-specific status
+   value. Report an error if it fails. */
+   } else if( pthread_key_create( &starlink_ast_status_key, NULL ) ) {
+      fprintf( stderr, "ast: Failed to create Thread-Specific Status key" );
+
+   }
+
+}
+
+AstGlobals *astGlobalsInit_( void ) {
+/*
+*+
+*  Name:
+*     astGlobalsInit
+
+*  Purpose:
+*     Create and initialise a structure holding thread-specific global
+*     data values.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "globals.h"
+*     AstGlobals *astGlobalsInit;
+
+*  Description:
+*     This function allocates memory to hold thread-specific global data
+*     for use throughout AST, and initialises it.
+
+*  Returned Value:
+*     Pointer to the structure holding global data values for the
+*     currently executing thread.
+
+*-
+*/
+
+/* Local Variables: */
+   AstGlobals *globals;
+   AstStatusBlock *status;
+
+/* Allocate memory to hold the global data values for the currently
+   executing thread. Use malloc rather than astMalloc (the AST memory
+   module uses global data managed by this module and so using astMalloc
+   could put us into an infinite loop). */
+   globals = MALLOC( sizeof( AstGlobals ) );
+
+   if ( !globals ){
+      fprintf( stderr, "ast: Failed to allocate memory to hold AST "
+               "global data values" );
+
+/* Initialise the global data values. */
+   } else {
+
+/* Each thread has a unique integer identifier. */
+      pthread_mutex_lock( &nthread_mutex );
+      globals->thread_identifier = nthread++;
+      pthread_mutex_unlock( &nthread_mutex );
+
+#define INIT(class) astInit##class##Globals_( &(globals->class) );
+      INIT( Error );
+      INIT( Memory );
+      INIT( Object );
+      INIT( Axis );
+      INIT( Mapping );
+      INIT( Frame );
+      INIT( Channel );
+      INIT( CmpMap );
+      INIT( KeyMap );
+      INIT( FitsChan );
+      INIT( FitsTable );
+      INIT( CmpFrame );
+      INIT( DSBSpecFrame );
+      INIT( FrameSet );
+      INIT( LutMap );
+      INIT( MathMap );
+      INIT( PcdMap );
+      INIT( PointSet );
+      INIT( SkyAxis );
+      INIT( SkyFrame );
+      INIT( SlaMap );
+      INIT( SpecFrame );
+      INIT( SphMap );
+      INIT( TimeFrame );
+      INIT( WcsMap );
+      INIT( ZoomMap );
+      INIT( FluxFrame );
+      INIT( SpecFluxFrame );
+      INIT( GrismMap );
+      INIT( IntraMap );
+      INIT( Plot );
+      INIT( Plot3D );
+      INIT( Region );
+      INIT( Xml );
+      INIT( XmlChan );
+      INIT( Box );
+      INIT( Circle );
+      INIT( CmpRegion );
+      INIT( DssMap );
+      INIT( Ellipse );
+      INIT( Interval );
+      INIT( MatrixMap );
+      INIT( NormMap );
+      INIT( NullRegion );
+      INIT( PermMap );
+      INIT( PointList );
+      INIT( PolyMap );
+      INIT( Polygon );
+      INIT( Prism );
+      INIT( RateMap );
+      INIT( SelectorMap );
+      INIT( ShiftMap );
+      INIT( SpecMap );
+      INIT( Stc );
+      INIT( StcCatalogEntryLocation );
+      INIT( StcObsDataLocation );
+      INIT( SwitchMap );
+      INIT( Table );
+      INIT( TimeMap );
+      INIT( TranMap );
+      INIT( UnitMap );
+      INIT( UnitNormMap );
+      INIT( WinMap );
+      INIT( StcResourceProfile );
+      INIT( StcSearchLocation );
+      INIT( StcsChan );
+#undef INIT
+
+/* Save the pointer as the value of the starlink_ast_globals_key
+   thread-specific data key. */
+      if( pthread_setspecific( starlink_ast_globals_key, globals ) ) {
+         fprintf( stderr, "ast: Failed to store Thread-Specific Data pointer." );
+
+/* We also take this opportunity to allocate and initialise the
+   thread-specific status value. */
+      } else {
+         status = MALLOC( sizeof( AstStatusBlock ) );
+         if( status ) {
+            status->internal_status = 0;
+            status->status_ptr = &( status->internal_status );
+
+/* If succesful, store the pointer to this memory as the value of the
+   status key for the currently executing thread. Report an error if
+   this fails. */
+            if( pthread_setspecific( starlink_ast_status_key, status ) ) {
+               fprintf( stderr, "ast: Failed to store Thread-Specific Status pointer." );
+            }
+
+         } else {
+            fprintf( stderr, "ast: Failed to allocate memory for Thread-Specific Status pointer." );
+         }
+      }
+   }
+
+/* Return a pointer to the data structure holding the global data values. */
+   return globals;
+}
+
+#endif
+
diff --git a/ast/globals.h b/ast/globals.h
new file mode 100644
index 0000000..5641a72
--- /dev/null
+++ b/ast/globals.h
@@ -0,0 +1,245 @@
+#if !defined( GLOBALS_INCLUDED )   /* Include this file only once */
+#define GLOBALS_INCLUDED 1
+
+/* If thread-safety is required... */
+#if defined( THREAD_SAFE ) && ( defined( astCLASS ) || defined( astFORTRAN77) )
+
+/* Include files: */
+/* ============== */
+
+/* AST includes */
+#include "axis.h"
+#include "box.h"
+#include "channel.h"
+#include "circle.h"
+#include "cmpframe.h"
+#include "cmpmap.h"
+#include "cmpregion.h"
+#include "dsbspecframe.h"
+#include "dssmap.h"
+#include "ellipse.h"
+#include "error.h"
+#include "fitschan.h"
+#include "fitstable.h"
+#include "fluxframe.h"
+#include "frame.h"
+#include "frameset.h"
+#include "grismmap.h"
+#include "interval.h"
+#include "intramap.h"
+#include "keymap.h"
+#include "lutmap.h"
+#include "mapping.h"
+#include "mathmap.h"
+#include "matrixmap.h"
+#include "memory.h"
+#include "normmap.h"
+#include "nullregion.h"
+#include "object.h"
+#include "pcdmap.h"
+#include "permmap.h"
+#include "plot.h"
+#include "plot3d.h"
+#include "pointlist.h"
+#include "pointset.h"
+#include "polygon.h"
+#include "polymap.h"
+#include "prism.h"
+#include "ratemap.h"
+#include "region.h"
+#include "selectormap.h"
+#include "shiftmap.h"
+#include "skyaxis.h"
+#include "skyframe.h"
+#include "slamap.h"
+#include "specfluxframe.h"
+#include "specframe.h"
+#include "specmap.h"
+#include "sphmap.h"
+#include "stc.h"
+#include "stccatalogentrylocation.h"
+#include "stcobsdatalocation.h"
+#include "stcresourceprofile.h"
+#include "stcsearchlocation.h"
+#include "stcschan.h"
+#include "switchmap.h"
+#include "table.h"
+#include "timeframe.h"
+#include "timemap.h"
+#include "tranmap.h"
+#include "unitmap.h"
+#include "unitnormmap.h"
+#include "wcsmap.h"
+#include "winmap.h"
+#include "xml.h"
+#include "xmlchan.h"
+#include "zoommap.h"
+
+
+
+/* System includes */
+#include <pthread.h>
+
+/* Macros */
+/* ====== */
+
+/* The name of the variable used to access thread-specific global data */
+#define AST__GLOBALS ast_globals
+
+/* Defines a macro that gives access to a specific global data item. */
+#define astGLOBAL(class,name) (AST__GLOBALS->class.name)
+
+
+/* Declares the pointer for the structure holding thread-specific values
+   for AST global data. */
+#define astDECLARE_GLOBALS AstGlobals *AST__GLOBALS;
+
+
+/* A macro that should be invoked in each function that refers to a
+   global data item. The "This" parameter should be a pointer to an
+   Object, or NULL. It ensures the thread-specific data key has been
+   created. It also allocates and initialises memory to hold the global
+   data. */
+#define astGET_GLOBALS(This) \
+\
+/* If the supplied Object pointer contains a pointer to the thread-specific \
+   data structure, return it. */ \
+   if( This && ((AstObject *)This)->globals ) { \
+      AST__GLOBALS = ((AstObject *)This)->globals; \
+\
+/* Otherwise, ensure that the thread specific data key has been created. */ \
+   } else if( pthread_once( &starlink_ast_globals_initialised, \
+                            astGlobalsCreateKey_ ) ) { \
+      AST__GLOBALS = NULL; \
+      fprintf( stderr, "Starlink AST package initialisation failed." ); \
+\
+/* If the current thread does not yet have a structure to hold \
+   thread-specific global data, create one now (initialising its \
+   contents) and associate it with the thread speciifc data key. */ \
+   } else if( ( AST__GLOBALS =  \
+                pthread_getspecific( starlink_ast_globals_key ) ) == NULL ) { \
+      AST__GLOBALS = astGlobalsInit_(); \
+      if( pthread_setspecific( starlink_ast_globals_key, AST__GLOBALS ) ) { \
+         fprintf( stderr, "Starlink AST failed to store Thread-Specific " \
+                  "Data pointer." ); \
+      } \
+   }
+
+
+/* A macro that expands to the value of a unique integer identifier for
+   the calling thread. */
+#define AST__THREAD_ID (AST__GLOBALS->thread_identifier) \
+
+
+#define astMAKE_INITGLOBALS(class) \
+\
+void astInit##class##Globals_( Ast##class##Globals *globals ){ \
+   GLOBAL_inits \
+}
+
+/* Type definitions */
+/* ================ */
+
+typedef struct AstGlobals {
+   int thread_identifier;
+   AstMemoryGlobals Memory;
+   AstErrorGlobals  Error;
+   AstObjectGlobals Object;
+   AstAxisGlobals Axis;
+   AstMappingGlobals Mapping;
+   AstFrameGlobals Frame;
+   AstChannelGlobals Channel;
+   AstCmpMapGlobals CmpMap;
+   AstKeyMapGlobals KeyMap;
+   AstFitsChanGlobals FitsChan;
+   AstFitsTableGlobals FitsTable;
+   AstCmpFrameGlobals CmpFrame;
+   AstDSBSpecFrameGlobals DSBSpecFrame;
+   AstFrameSetGlobals FrameSet;
+   AstLutMapGlobals LutMap;
+   AstMathMapGlobals MathMap;
+   AstPcdMapGlobals PcdMap;
+   AstPointSetGlobals PointSet;
+   AstSkyAxisGlobals SkyAxis;
+   AstSkyFrameGlobals SkyFrame;
+   AstSlaMapGlobals SlaMap;
+   AstSpecFrameGlobals SpecFrame;
+   AstSphMapGlobals SphMap;
+   AstTimeFrameGlobals TimeFrame;
+   AstWcsMapGlobals WcsMap;
+   AstZoomMapGlobals ZoomMap;
+   AstFluxFrameGlobals FluxFrame;
+   AstSpecFluxFrameGlobals SpecFluxFrame;
+   AstGrismMapGlobals GrismMap;
+   AstIntraMapGlobals IntraMap;
+   AstPlotGlobals Plot;
+   AstPlot3DGlobals Plot3D;
+   AstRegionGlobals Region;
+   AstBoxGlobals Box;
+   AstXmlGlobals Xml;
+   AstXmlChanGlobals XmlChan;
+   AstCircleGlobals Circle;
+   AstCmpRegionGlobals CmpRegion;
+   AstDssMapGlobals DssMap;
+   AstEllipseGlobals Ellipse;
+   AstIntervalGlobals Interval;
+   AstMatrixMapGlobals MatrixMap;
+   AstNormMapGlobals NormMap;
+   AstNullRegionGlobals NullRegion;
+   AstPermMapGlobals PermMap;
+   AstPointListGlobals PointList;
+   AstPolyMapGlobals PolyMap;
+   AstPolygonGlobals Polygon;
+   AstPrismGlobals Prism;
+   AstRateMapGlobals RateMap;
+   AstSelectorMapGlobals SelectorMap;
+   AstShiftMapGlobals ShiftMap;
+   AstSpecMapGlobals SpecMap;
+   AstStcGlobals Stc;
+   AstStcCatalogEntryLocationGlobals StcCatalogEntryLocation;
+   AstStcObsDataLocationGlobals StcObsDataLocation;
+   AstSwitchMapGlobals SwitchMap;
+   AstTableGlobals Table;
+   AstTimeMapGlobals TimeMap;
+   AstTranMapGlobals TranMap;
+   AstUnitMapGlobals UnitMap;
+   AstUnitNormMapGlobals UnitNormMap;
+   AstWinMapGlobals WinMap;
+   AstStcResourceProfileGlobals	StcResourceProfile;
+   AstStcSearchLocationGlobals StcSearchLocation;
+   AstStcsChanGlobals StcsChan;
+} AstGlobals;
+
+
+/* Externally declared variables */
+/* ============================= */
+
+
+/* The pthreads key that is associated with the thread-specific data for
+   each thread. Declared in global.c. */
+extern pthread_key_t starlink_ast_globals_key;
+
+/* The pthreads key that is associated with the thread-specific status
+   value for each thread. Declared in global.c. */
+extern pthread_key_t starlink_ast_status_key;
+
+/* This is a flag indicating that the thread-specific data key has not yet
+   been created. Declared in globals.c. */
+extern pthread_once_t starlink_ast_globals_initialised;
+
+/* Function Prototypes: */
+/* ==================== */
+
+void astGlobalsCreateKey_( void );
+AstGlobals *astGlobalsInit_( void );
+
+
+/* If thread-safety is not required, define some null macros. */
+#else
+
+#define astDECLARE_GLOBALS
+#define astGET_GLOBALS(This)
+#define astINIT_GLOBALS
+
+#endif
+#endif
diff --git a/ast/grf.h b/ast/grf.h
new file mode 100644
index 0000000..04294f3
--- /dev/null
+++ b/ast/grf.h
@@ -0,0 +1,110 @@
+#if !defined( GRF_INCLUDED ) /* Include this file only once */
+#define GRF_INCLUDED
+/*
+*+
+*  Name:
+*     grf.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the grf module
+
+*  Invocation:
+*     #include "grf.h"
+
+*  Description:
+*     This include file defines the interface to the grf module and
+*     provides the type definitions, function prototypes and macros, etc.
+*     needed to use this module.
+
+*  Inheritance:
+*     The grf module is not a class and does not inherit.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     27-JUN-1996 (DSB):
+*        Original version.
+*     25-OCT-1996 (DSB):
+*        Primatives macros defined, extra parameter added to astGAttr.
+*     17-FEB-2006 (DSB):
+*        Added GRF__ESH and GRF__ESG.
+*-
+*/
+
+/* Constant Values. */
+/* ================ */
+/* Values identifying different graphics attributes. */
+#define GRF__STYLE  0
+#define GRF__WIDTH  1
+#define GRF__SIZE   2
+#define GRF__FONT   3
+#define GRF__COLOUR 4
+
+/* Values identifying different graphics primatives. */
+#define GRF__TEXT 0
+#define GRF__LINE 1
+#define GRF__MARK 2
+
+/* The number of different graphics attributes */
+#define GRF__NATTR 5
+
+/* Values identifying capabilities */
+#define GRF__ESC     0
+#define GRF__MJUST   1
+#define GRF__SCALES  2
+
+/* Values identifying types of graphics escape sequence */
+#define GRF__ESPER 1
+#define GRF__ESSUP 2
+#define GRF__ESSUB 3
+#define GRF__ESGAP 4
+#define GRF__ESBAC 5
+#define GRF__ESSIZ 6
+#define GRF__ESWID 7
+#define GRF__ESFON 8
+#define GRF__ESCOL 9
+#define GRF__ESSTY 10
+#define GRF__ESPOP 11
+#define GRF__ESPSH 12
+#define GRF__ESH   13
+#define GRF__ESG   14
+
+/* Function prototypes. */
+/* ==================== */
+int astGAttr( int, double, double *, int );
+int astGScales( float *, float * );
+int astGBBuf( void );
+int astGEBuf( void );
+int astGFlush( void );
+int astGLine( int, const float *, const float * );
+int astGMark( int, const float *, const float *, int );
+int astGQch( float *, float * );
+int astGText( const char *, float, float, const char *, float, float );
+int astGTxExt( const char *, float, float, const char *, float, float, float *, float * );
+int astGCap( int, int );
+
+#endif
diff --git a/ast/grf3d.c b/ast/grf3d.c
new file mode 100644
index 0000000..c0c1a33
--- /dev/null
+++ b/ast/grf3d.c
@@ -0,0 +1,102 @@
+/*
+*  Name:
+*     grf3d.c
+
+*  Purpose:
+*     Implement the grf3D interface if no graphics system is available.
+
+*  Description:
+*     This file implements the low level 3D graphics functions required
+*     by the rest of AST. These implementations simply report an error
+*     when called.
+
+*  Inheritance:
+*     This module is not a class and does not inherit.
+
+*  Copyright:
+*     Copyright (C) 2007 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (JACH - UCLan)
+
+*  History:
+*     20-JUN-2007 (DSB):
+*        Original version.
+*/
+
+/* Header files */
+/* ============ */
+#include "grf3d.h"         /* Declare the functions in this module */
+#include "error.h"         /* AST error reporting facilities */
+#include "ast_err.h"       /* AST error codes */
+
+/* Function Prototypes */
+/* =================== */
+static void Report( const char * );
+
+/* Function definitions */
+/* ==================== */
+int astG3DCap( int cap, int value ){
+   return 0;
+}
+
+int astG3DFlush( void ){
+   Report( "astG3DFlush");
+   return 0;
+}
+
+int astG3DLine( int n, float *x, float *y, float *z  ){
+   Report( "astG3DLine" );
+   return 0;
+}
+
+int astG3DQch( float *ch ){
+   Report( "astG3DQch" );
+   return 0;
+}
+
+int astG3DMark( int n, float *x, float *y, float *z, int type, float norm[3] ){
+   Report( "astG3DMark" );
+   return 0;
+}
+
+int astG3DText( const char *text, float ref[3], const char *just,
+                float up[3], float norm[3] ){
+   Report( "astG3DText" );
+   return 0;
+}
+
+int astG3DTxExt( const char *text, float ref[3], const char *just,
+                 float up[3], float norm[3], float *xb, float *yb,
+                 float *zb, float bl[3] ){
+   Report( "astG3DTxExt" );
+   return 0;
+}
+
+int astG3DAttr( int attr, double value, double *old_value, int prim ){
+   Report( "astG3DAttr" );
+   return 0;
+}
+
+static void Report( const char *name ){
+   astError( AST__GRFER, "%s: No graphics facilities are available.", name );
+   astError( AST__GRFER, "Re-link using an option such as '-pgplot' with "
+             "the ast_link script." );
+}
diff --git a/ast/grf3d.h b/ast/grf3d.h
new file mode 100644
index 0000000..6ab7195
--- /dev/null
+++ b/ast/grf3d.h
@@ -0,0 +1,69 @@
+#if !defined( GRF3D_INCLUDED ) /* Include this file only once */
+#define GRF3D_INCLUDED
+/*
+*+
+*  Name:
+*     grf3d.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the grf3d module
+
+*  Invocation:
+*     #include "grf3d.h"
+
+*  Description:
+*     This include file defines the interface to the grf3d module and
+*     provides the type definitions, function prototypes and macros, etc.
+*     needed to use this module.
+
+*  Inheritance:
+*     The grf3d module is not a class and does not inherit.
+
+*  Copyright:
+*     Copyright (C) 2007 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (JACH - UCLan)
+
+*  History:
+*     20-JUN-2007 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include the 2D grf header file in order to inherit the GRF__ macros. */
+#include "grf.h"
+
+/* Function prototypes. */
+/* ==================== */
+int astG3DAttr( int, double, double *, int );
+int astG3DCap( int, int );
+int astG3DFlush( void );
+int astG3DLine( int, float *, float *, float * );
+int astG3DMark( int, float *, float *, float *, int, float[3] );
+int astG3DQch( float * );
+int astG3DText( const char *, float[3], const char *, float[3], float[3] );
+int astG3DTxExt( const char *, float[3], const char *, float[3], float[3], float *, float *, float *, float[3] );
+
+
+#endif
diff --git a/ast/grf3d_pgplot.c b/ast/grf3d_pgplot.c
new file mode 100644
index 0000000..ab25085
--- /dev/null
+++ b/ast/grf3d_pgplot.c
@@ -0,0 +1,3196 @@
+/*
+*  Name:
+*     grf3d_pgplot.c
+
+*  Purpose:
+*     Implement the grf3d interface using the PGPLOT graphics system.
+
+*  Description:
+*     This file implements the low level 3D graphics functions required
+*     by the rest of AST, by calling suitable PGPLOT functions (the
+*     FORTRAN PGPLOT interface is used).
+*
+*     This file can be used as a template for the development of
+*     similar implementations based on other graphics systems.
+*
+*     Unlike world coordinates used by the 2D grf interface, the 3D world
+*     coordinates used by the grf3D interface are assume to be equally scaled
+*     (that is, they are assumed to have the same units). Therefore this
+*     module has no equivalent to the astGScales function defined by the
+*     2D grf interface.
+
+*  Copyright:
+*     Copyright (C) 2007 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     20-JUN-2007 (DSB):
+*        Original version.
+*/
+
+
+/* Macros */
+/* ====== */
+#define MXDEV 16                 /* Max no of concurrent PGPLOT devices */
+#define MXSTRLEN 80              /* Max PGPLOT string length */
+#define CAMERA_OK 123456789      /* Flags that a Camera has been initialised */
+#define TWOPI 6.28318530718      /* 2*PI */
+#define MXSIDE 32                /* Max no of sides in a marker polygon */
+
+
+/* Header files. */
+/* ============= */
+/* AST header files */
+#include "grf3d.h"               /* The grf3D interface definition */
+#include "pg3d.h"                /* Other public functions in this module */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* C to FORTRAN interface functions */
+#include "memory.h"              /* Memory allocation facilities */
+#include "error.h"               /* Error reporting facilities */
+#include "pointset.h"            /* Defines AST__BAD */
+#include "ast_err.h"             /* AST error codes */
+
+/* System header files */
+#include <string.h>
+#include <math.h>
+#include <float.h>
+#include <limits.h>
+#include <stdio.h>
+
+
+/* Type definitions. */
+/* ================= */
+/* Structure defining the position and orientation of the camera in 3D
+   world coords. This is specific to the PGPLOT implementation. Other
+   implementations need not include any equivalent to this structure. */
+typedef struct camera {
+   float eye_vector[3];
+   float target_vector[3];
+   float up_vector[3];
+   float w2c_matrix[9];
+   float screen_distance;
+   int ok_flag;
+} Camera;
+
+
+/* Module variables. */
+/* ================= */
+/* One camera structure for each PGPLOT device identifier. PGPLOT allows
+   a maximum of 8 concurrent devices at the moment. Make the array twice
+   this size to allow for some future expansion in PGPLOT. Again, this is
+   specific to the PGPLOT implementation of the grf3D interface. */
+static Camera cameras[ MXDEV ];
+
+/* Function templates. */
+/* =================== */
+/* Templates for functions that are private to this module. */
+static Camera *getCamera( int );
+static float getCharHeight( void );
+static int Polygon( int, float *, float *, float *, float[3], float[3], float[3], float[3] );
+static int Text( int *, int, float[3], const char *, float[3], float[3], float[3]  );
+static int TextCam( Camera *, float[3], float[3], float[3], float[3] );
+static int TxExt( int *, int, float[3], const char *, float[3], float[3], float[3], float *, float *, float *, float[3] );
+static int getTextAxes( float[3], float[3], float[3], const char *, float[3], float[3], float[3], char[3] );
+static int transform( Camera *, int, float *, float *, float *, float *, float * );
+static int vectorNorm( float * );
+static float vectorModulus( float * );
+static void getSymbolList( const char *, int, int *, int * );
+static void vectorProduct( float *, float *, float * );
+static float dotProduct( float *, float * );
+static void vectorSub( float *, float *, float * );
+
+/* Templates for private functions that wrap PGPLOT Fortran routines. */
+static void ccgrsyds( int *, int *, const char *, int, int );
+static void ccgrsymk( int, int, int * );
+static void ccgrsyxd( int, int *, int * );
+static void ccpgline( int, float[], float[] );
+static void ccpgpoly( int, float[], float[] );
+static void ccpgqcf( int * );
+static void ccpgqcf(int *);
+static void ccpgqch( float * );
+static void ccpgqci( int * );
+static void ccpgqclp( int * );
+static void ccpgqid( int * );
+static void ccpgqls( int * );
+static void ccpgqlw( int * );
+static void ccpgqvsz( int, float *, float *, float *, float * );
+static void ccpgqwin( float *, float *, float *, float * );
+static void ccpgscf( int );
+static void ccpgsch( float );
+static void ccpgsci( int );
+static void ccpgsclp( int );
+static void ccpgsls( int );
+static void ccpgslw( int );
+static void ccpgswin( float, float, float, float );
+static void ccpgupdt( void );
+
+
+/* Templates for Fortran PGPLOT routines needed by this module. */
+F77_SUBROUTINE(grsyds)( INTEGER_ARRAY(list), INTEGER(nlist), CHARACTER(text), INTEGER(font) TRAIL(text) );
+F77_SUBROUTINE(grsymk)( INTEGER(type), INTEGER(font), INTEGER(symbol) );
+F77_SUBROUTINE(grsyxd)( INTEGER(symbol), INTEGER_ARRAY(xygrid), INTEGER(unused) );
+F77_SUBROUTINE(pgline)( INTEGER(N), REAL_ARRAY(X), REAL_ARRAY(Y) );
+F77_SUBROUTINE(pgpoly)( INTEGER(N), REAL_ARRAY(X), REAL_ARRAY(Y) );
+F77_SUBROUTINE(pgqcf)( INTEGER(ival) );
+F77_SUBROUTINE(pgqch)( REAL(rval) );
+F77_SUBROUTINE(pgqci)( INTEGER(ival) );
+F77_SUBROUTINE(pgqclp)( INTEGER(clip) );
+F77_SUBROUTINE(pgqid)( INTEGER(id) );
+F77_SUBROUTINE(pgqls)( INTEGER(ival) );
+F77_SUBROUTINE(pgqlw)( INTEGER(ival) );
+F77_SUBROUTINE(pgqvsz)( INTEGER(units), REAL(x1), REAL(x2), REAL(y1), REAL(y2) );
+F77_SUBROUTINE(pgqwin)( REAL(wx1), REAL(wx2), REAL(wy1), REAL(wy2) );
+F77_SUBROUTINE(pgscf)( INTEGER(ival) );
+F77_SUBROUTINE(pgsch)( REAL(rval) );
+F77_SUBROUTINE(pgsci)( INTEGER(ival) );
+F77_SUBROUTINE(pgsclp)( INTEGER(clip) );
+F77_SUBROUTINE(pgsls)( INTEGER(ival) );
+F77_SUBROUTINE(pgslw)( INTEGER(ival) );
+F77_SUBROUTINE(pgswin)( REAL(X1), REAL(X2), REAL(Y1), REAL(Y2) );
+F77_SUBROUTINE(pgupdt)( void );
+
+
+/* Public functions defined by the grf3D interface. */
+/* ================================================ */
+/* All implementations of the grf3d interface must provide implementations
+   of all the functions in this block. The corresponding templates are in
+   grf3d.h */
+
+
+int astG3DAttr( int attr, double value, double *old_value, int prim ){
+/*
+*+
+*  Name:
+*     astG3DAttr
+
+*  Purpose:
+*     Enquire or set a 3D graphics attribute value.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int int astG3DAttr( int attr, double value, double *old_value, int prim )
+
+*  Description:
+*     This function returns the current value of a specified 3D graphics
+*     attribute, and optionally establishes a new value. The supplied
+*     value is converted to an integer value if necessary before use.
+
+*  Parameters:
+*     attr
+*        An integer value identifying the required attribute. The
+*        following symbolic values are defined in grf3d.h:
+*
+*           GRF__STYLE  - Line style.
+*           GRF__WIDTH  - Line width.
+*           GRF__SIZE   - Character and marker size scale factor.
+*           GRF__FONT   - Character font.
+*           GRF__COLOUR - Colour index.
+*     value
+*        A new value to store for the attribute. If this is AST__BAD
+*        no value is stored.
+*     old_value
+*        A pointer to a double in which to return the attribute value.
+*        If this is NULL, no value is returned.
+*     prim
+*        The sort of graphics primitive to be drawn with the new attribute.
+*        Identified by the following values defined in grf.h:
+*           GRF__LINE
+*           GRF__MARK
+*           GRF__TEXT
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+
+*-
+*/
+
+   int ival;
+   float rval, dx, dy, deflw, x1, x2, y1, y2;
+
+/* If required retrieve the current line style, and set a new line style. */
+   if( attr == GRF__STYLE ){
+      ccpgqls( &ival );
+      if( old_value ) *old_value = (double) ival;
+
+      if( value != AST__BAD ){
+         ival = (int) ( value + 0.5 );
+         if( value < 0.0 ) ival -= 1;
+
+         ival = ( ival - 1 ) % 5;
+         ival += ( ival < 0 ) ? 6 : 1;
+
+         ccpgsls( ival );
+      }
+
+/* If required retrieve the current line width, and set a new line width.
+   Line width is stored in Plot as a scale factor (1.0 for the default line
+   width which is a fixed fraction of the diagonal of the view surface), but
+   pgplot stores it in units of 0.005 of an inch. */
+   } else if( attr == GRF__WIDTH ){
+
+/* Get the bounds of the view surface in inches. */
+      ccpgqvsz( 1, &x1, &x2, &y1, &y2 );
+
+/* Find the default line width in inches (i.e. 0.0005 of the length
+   of the view surface diagonal). */
+      dx = ( x1 - x2 );
+      dy = ( y1 - y2 );
+      deflw = 0.0005*sqrt( (double )( dx*dx + dy*dy ) );
+
+/* Get the current pgplot line width in units of 0.005 of an inch. */
+      ccpgqlw( &ival );
+
+/* If required, return the factor by which this exceeds the default line
+   width found above. */
+      if( old_value ) *old_value = (double)( ival )/( 200.0 * deflw );
+
+/* If a new line width has been provided, the pgplot line width needs to
+   be set to the corresponding absolute value. */
+      if( value != AST__BAD ){
+         ival = (int) ( 200.0*value*deflw );
+         if( ival < 1 ) {
+            ival = 1;
+         } else if( ival > 201 ){
+            ival = 201;
+         }
+         ccpgslw( ival );
+      }
+
+/* If required retrieve the current character size, and set a new size.
+   The attribute value should be a factor by which to multiply the
+   default character size. */
+   } else if( attr == GRF__SIZE ){
+      ccpgqch( &rval );
+      if( old_value ) *old_value = (double) rval;
+
+      if( value != AST__BAD ){
+         ccpgsch( (float) value );
+      }
+
+/* If required retrieve the current character font, and set a new font. */
+   } else if( attr == GRF__FONT ){
+      ccpgqcf( &ival );
+      if( old_value ) *old_value = (double) ival;
+
+      if( value != AST__BAD ){
+         ival = (int) ( value + 0.5 );
+         if( value < 0.0 ) ival -= 1;
+
+         ival = ( ival - 1 ) % 4;
+         ival += ( ival < 0 ) ? 5 : 1;
+         ccpgscf( ival );
+      }
+
+/* If required retrieve the current colour index, and set a new colour
+   index. */
+   } else if( attr == GRF__COLOUR ){
+      ccpgqci( &ival );
+      if( old_value ) *old_value = (double) ival;
+
+      if( value != AST__BAD ){
+         ival = (int) ( value + 0.5 );
+         if( ival < 0 ) ival = 1;
+         ccpgsci( ival );
+      }
+
+/* Give an error message for any other attribute value. */
+   } else {
+      astError( AST__GRFER, "astG3DAttr: Unknown graphics attribute '%d' "
+                "requested.", attr );
+      return 0;
+   }
+
+/* Return. */
+   return 1;
+}
+
+int astG3DCap( int cap, int value ){
+/*
+*+
+*  Name:
+*     astG3DCap
+
+*  Purpose:
+*     Indicate if this grf3d module has a given capability.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int astG3DCap( int cap, int value )
+
+*  Description:
+*     This function is called by the AST Plot class to determine if the
+*     grf3d module has a given capability, as indicated by the "cap"
+*     argument.
+
+*  Parameters:
+*     cap
+*        The capability being inquired about. This will be one of the
+*        following constants defined in grf3d.h:
+*
+*        GRF3D__ESC: This function should return a non-zero value if the
+*        astG3DText and astG3DTxExt functions can recognise and interpret
+*        graphics escape sequences within the supplied string. These
+*        escape sequences are described below. Zero should be returned
+*        if escape sequences cannot be interpreted (in which case the
+*        Plot class will interpret them itself if needed). The supplied
+*        "value" argument should be ignored only if escape sequences cannot
+*        be interpreted by astG3DText and astG3DTxExt. Otherwise, "value"
+*        indicates whether astG3DText and astG3DTxExt should interpret escape
+*        sequences in subsequent calls. If "value" is non-zero then
+*        escape sequences should be interpreted by astG3DText and
+*        astG3DTxExt. Otherwise, they should be drawn as literal text.
+
+*  Returned Value:
+*     The return value, as described above. Zero should be returned if
+*     the supplied capability is not recognised.
+
+*  Escape Sequences:
+*     Escape sequences are introduced into the text string by a percent
+*     "%" character. The following escape sequences are currently recognised
+*     ("..." represents a string of one or more decimal digits):
+*
+*       %%      - Print a literal "%" character (type GRF__ESPER ).
+*
+*       %^...+  - Draw subsequent characters as super-scripts. The digits
+*                 "..." give the distance from the base-line of "normal"
+*                 text to the base-line of the super-script text, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text (type GRF__ESSUP ).
+*       %^+     - Draw subsequent characters with the normal base-line.
+*
+*       %v...+  - Draw subsequent characters as sub-scripts. The digits
+*                 "..." give the distance from the base-line of "normal"
+*                 text to the base-line of the sub-script text, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text (type GRF__ESSUB ).
+*
+*       %v+     - Draw subsequent characters with the normal base-line
+*                 (equivalent to %^+).
+*
+*       %>...+  - Leave a gap before drawing subsequent characters.
+*                 The digits "..." give the size of the gap, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text (type GRF__ESGAP ).
+*
+*       %<...+  - Move backwards before drawing subsequent characters.
+*                 The digits "..." give the size of the movement, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text (type GRF_ESBAC).
+*
+*       %s...+  - Change the Size attribute for subsequent characters. The
+*                 digits "..." give the new Size as a fraction of the
+*                 "normal" Size, scaled so that a value of "100" corresponds
+*                 to 1.0  (type GRF__ESSIZ ).
+*
+*       %s+     - Reset the Size attribute to its "normal" value.
+*
+*       %w...+  - Change the Width attribute for subsequent characters. The
+*                 digits "..." give the new width as a fraction of the
+*                 "normal" Width, scaled so that a value of "100" corresponds
+*                 to 1.0  (type GRF__ESWID ).
+*
+*       %w+     - Reset the Size attribute to its "normal" value.
+*
+*       %f...+  - Change the Font attribute for subsequent characters. The
+*                 digits "..." give the new Font value  (type GRF__ESFON ).
+*
+*       %f+     - Reset the Font attribute to its "normal" value.
+*
+*       %c...+  - Change the Colour attribute for subsequent characters. The
+*                 digits "..." give the new Colour value  (type GRF__ESCOL ).
+*
+*       %c+     - Reset the Colour attribute to its "normal" value.
+*
+*       %t...+  - Change the Style attribute for subsequent characters. The
+*                 digits "..." give the new Style value  (type GRF__ESSTY ).
+*
+*       %t+     - Reset the Style attribute to its "normal" value.
+*
+*       %-      - Push the current graphics attribute values onto the top of
+*                 the stack - see "%+" (type GRF__ESPSH).
+*
+*       %+      - Pop attributes values of the top the stack - see "%-". If
+*                 the stack is empty, "normal" attribute values are restored
+*                 (type GRF__ESPOP).
+*
+*     The astFindEscape function (in libast.a) can be used to locate escape
+*     sequences within a text string. It has the following signature:
+*
+*     #include "plot.h"
+*     int astFindEscape( const char *text, int *type, int *value, int *nc )
+*
+*     Parameters:
+*        text
+*           Pointer to the string to be checked.
+*        type
+*           Pointer to a location at which to return the type of escape
+*           sequence. Each type is identified by a symbolic constant defined
+*           in grf.h and is indicated in the above section. The returned value
+*           is undefined if the supplied text does not begin with an escape
+*           sequence.
+*        value
+*           Pointer to a lcation at which to return the integer value
+*           associated with the escape sequence. All usable values will be
+*           positive. Zero is returned if the escape sequence has no associated
+*           integer. A value of -1 indicates that the attribute identified by
+*           "type" should be reset to its "normal" value (as established using
+*           the astG3DAttr function, etc). The returned value is undefined if
+*           the supplied text does not begin with an escape sequence.
+*        nc
+*           Pointer to a location at which to return the number of
+*           characters read by this call. If the text starts with an escape
+*           sequence, the returned value will be the number of characters in
+*           the escape sequence. Otherwise, the returned value will be the
+*           number of characters prior to the first escape sequence, or the
+*           length of the supplied text if no escape sequence is found.
+
+*     Returned Value:
+*        A non-zero value is returned if the supplied text starts with a
+*        graphics escape sequence, and zero is returned otherwise.
+
+*-
+*/
+
+   return 0;
+}
+
+int astG3DFlush( void ){
+/*
+*+
+*  Name:
+*     astG3DFlush
+
+*  Purpose:
+*     Flush all pending graphics to the output device.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int astG3DFlush( void )
+
+*  Description:
+*     This function ensures that the display device is up-to-date,
+*     by flushing any pending graphics to the output device.
+
+*  Parameters:
+*     None.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*-
+*/
+
+   ccpgupdt();
+   return 1;
+}
+
+int astG3DLine( int n, float *x, float *y, float *z ){
+/*
+*+
+*  Name:
+*     astG3DLine
+
+*  Purpose:
+*     Draw a polyline (i.e. a set of connected lines).
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int astG3DLine( int n, float *x, float *y, float *z )
+
+*  Description:
+*     This function displays lines joining the given positions.
+
+*  Parameters:
+*     n
+*        The number of positions to be joined together.
+*     x
+*        A pointer to an array holding the "n" x values.
+*     y
+*        A pointer to an array holding the "n" y values.
+*     z
+*        A pointer to an array holding the "n" z values.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - A camera must have been established prior to calling this
+*     function using either astG3DSetCamera or astG3DAutoCamera.
+*     -  Nothing is done if "n" is less than 2, or if a NULL pointer is
+*     given for either "x", "y" or "z".
+
+*-
+*/
+
+/* Local Variables: */
+   int clip;
+   int result = 0;
+   float *h, *r;
+
+/* Do nothing if we have less than 2 points, but do not indicate an error. */
+   if( n < 2 ){
+      result = 1;
+
+/* Check the pointers. */
+   } else if( x && y && z ) {
+
+/* Save the current clipping flag, and ensure clipping is off. */
+      ccpgqclp( &clip );
+      ccpgsclp( 0 );
+
+/* Allocate work space for the 2D world coordinate positions. */
+      h = astMalloc( sizeof( float )*(size_t) n );
+      r = astMalloc( sizeof( float )*(size_t) n );
+      if( astOK ) {
+
+/* Convert the supplied points from 3D world coordinates to 2D world
+   (i.e. screen) coordinates. If succesful, plot the lines. */
+         if( transform( NULL, n, x, y, z, h, r ) ) {
+            ccpgline( n, (float *) h, (float *) r );
+            result = 1;
+         }
+      }
+
+/* Free work space. */
+      h = astFree( h );
+      r = astFree( r );
+
+/* Re-instate original clipping flag. */
+      ccpgsclp( clip );
+
+   }
+   return result;
+}
+
+int astG3DMark( int n, float *x, float *y, float *z, int type,
+                float norm[3] ){
+/*
+*+
+*  Name:
+*     astG3DMark
+
+*  Purpose:
+*     Draw a set of markers.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astG3DMark( int n, float *x, float *y, float *z, int type,
+*                     float norm[3] )
+
+*  Description:
+*     This function draws markers centred at the given positions, on a
+*     plane with a specified normal vector.
+
+*  Parameters:
+*     n
+*        The number of markers to draw.
+*     x
+*        A pointer to an array holding the "n" x values.
+*     y
+*        A pointer to an array holding the "n" y values.
+*     z
+*        A pointer to an array holding the "n" z values.
+*     type
+*        An integer which can be used to indicate the type of marker symbol
+*        required. See the description of routine PGPT in the PGPLOT manual.
+*     norm
+*        The (x,y,z) components of a vector that is normal to the plane
+*        containing the marker. The given vector passes through the marker
+*        from the back to the front. If all components of this vector are
+*        zero, then a normal vector pointing from the position of the
+*        first marker towards the camera eye is used.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     -  Nothing is done if "n" is less than 1, or if a NULL pointer is
+*     given for "x", "y" or "z".
+
+*-
+*/
+
+/* local Variables: */
+   char just[3];
+   float ref[3];
+   float up[3];
+   float tx[3], ty[3], tz[3];
+   float vx[ MXSIDE ], vy[ MXSIDE ], vz[ MXSIDE ];
+   float ch, ang, dang;
+   int clip;
+   int font;
+   int i;
+   int nlist;
+   int symnum;
+   int ns;
+
+/* Return if any of the coordinate pointers is NULL. */
+   if( !x || !y || !z ) return 1;
+
+/* Initialise */
+   ns = 0;
+
+/* Unless the "norm" vector is parallel to the z axis, we use an up vector
+   that is parallel to the z axis. Otherwise we use an up vector paralle
+   to the x axis. */
+   if( norm[ 0 ] != 0.0 || norm[ 1 ] != 0.0 ) {
+      up[ 0 ] = 0.0;
+      up[ 1 ] = 0.0;
+      up[ 2 ] = 1.0;
+   } else {
+      up[ 0 ] = 1.0;
+      up[ 1 ] = 0.0;
+      up[ 2 ] = 0.0;
+   }
+
+/* Create unit vectors along the three axes of the text plane
+   coordinate system. */
+   ref[ 0 ] = x[ 0 ];
+   ref[ 1 ] = y[ 0 ];
+   ref[ 2 ] = z[ 0 ];
+   if( !getTextAxes( ref, up, norm, "CC", tx, ty, tz, just ) ) return 0;
+
+/* Calculate the pgplot symbol number for the given marker type. */
+   if( type > 0 ) {
+      if( type > 127 ) {
+         symnum = type;
+      } else {
+         ccpgqcf( &font );
+         ccgrsymk( type, font, &symnum );
+      }
+
+   } else if( type > -3 ) {
+      getSymbolList( ".", 1, &symnum, &nlist );
+
+/* Regular polygons - create an array of text plane coordinates for the
+   vertices of the polygon. */
+   } else {
+      symnum = type;
+
+/* Get the character height in world coordinate units. A PGPLOT
+   character height of 1.0 corresponds to 1/40 of the 2D window height. */
+      ch = getCharHeight();
+
+/* Limit the number of sides that can be produced. */
+      ns = -type;
+      if( ns > MXSIDE ) ns = MXSIDE;
+
+/* Calculate the angle subtended by each edge of the polygon. */
+      dang = TWOPI/ns;
+      ang = 0.0;
+
+/* Loop round each vertex. */
+      for( i = 0; i < ns; i++ ) {
+         vx[ i ] = ch*sin( ang );
+         vy[ i ] = ch*cos( ang );
+         vz[ i ] = 0.0;
+         ang += dang;
+      }
+   }
+
+/* Save the current clipping flag, and ensure clipping is off. */
+   ccpgqclp( &clip );
+   ccpgsclp( 0 );
+
+/* Draw each marker in turn. */
+   for( i = 0; i < n; i++ ) {
+
+/* Store the centre world coords */
+      ref[ 0 ] = x[ i ];
+      ref[ 1 ] = y[ i ];
+      ref[ 2 ] = z[ i ];
+
+/* Draw the symbol, and return if anything goes wrong. */
+      if( symnum >= 0 ) {
+         if( !Text( &symnum, 1, ref, "CC", tx, ty, tz ) ) return 0;
+
+      } else {
+         if( !Polygon( ns, vx, vy, vz, ref, tx, ty, tz ) ) return 0;
+
+      }
+
+   }
+
+/* Re-instate original clipping flag. */
+   ccpgsclp( clip );
+
+/* If we arrive here we have been succesful, so return a non-zero value. */
+   return 1;
+}
+
+int astG3DQch( float *ch ){
+/*
+*+
+*  Name:
+*     astG3DQch
+
+*  Purpose:
+*     Return the character height in world coordinates.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int astG3DQch( float *ch )
+
+*  Description:
+*     This function returns the height of characters drawn using astG3DText.
+
+*  Parameters:
+*     ch
+*        A pointer to the double which is to receive the height of
+*        characters drawn with astG3DText.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - Since the 3D world coordinate axes are assumed to be equally
+*     scaled, the height of text in world coordinate units is independent
+*     of the orientation of the text. Therefore, this function returns
+*     only one height value, unlike the equivalent 2D astGQch function
+*     that returns two heights.
+*-
+*/
+   *ch = getCharHeight();
+   return 1;
+}
+
+int astG3DText( const char *text, float ref[3], const char *just, float up[3],
+                float norm[3]  ){
+/*
+*+
+*  Name:
+*     astG3DText
+
+*  Purpose:
+*     Draw a character string.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int astG3DText( const char *text, float ref[3], const char *just,
+*                     float up[3], float norm[3] )
+
+*  Description:
+*     This function displays a character string at a given position
+*     on a given plane in 3D world coords, using a specified
+*     justification and up-vector.
+
+*  Parameters:
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     ref
+*        The reference (x,y,z) coordinates.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", or 'B' for "bottom", and specifies the
+*        vertical location of the reference position. Note, "bottom"
+*        corresponds to the base-line of normal text. Some characters
+*        (eg "y", "g", "p", etc) descend below the base-line. The second
+*        character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+*     up
+*        The (x,y,z) up-vector for the text. The actual up vector used is
+*        the projection of the supplied vector onto the plane specified by
+*        "norm".
+*     norm
+*        The (x,y,z) components of a vector that is normal to the plane
+*        containing the text. The given vector passes through the text
+*        from the back to the front. If all components of this vector are
+*        zero, then a normal vector pointing towards the camera eye is used.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     -  This routine does not recognise PGPLOT escape sequences.
+*     -  A NULL value for "just" causes a value of "CC" to be used.
+*-
+*/
+
+/* Local Constants: */
+#define MXLEN 256
+
+/* Local Variables: */
+   char newjust[3];
+   float tx[3], ty[3], tz[3];
+   int list[ MXLEN ];
+   int nlist;
+
+/* Convert the supplied string into a list of PGPLOT symbol numbers */
+   getSymbolList( text, MXLEN, &nlist, list );
+
+/* Create unit vectors along the three axes of the text plane
+   coordinate system. */
+   if( !getTextAxes( ref, up, norm, just, tx, ty, tz, newjust ) ) return 0;
+
+/* Draw the text. */
+   return Text( list, nlist, ref, newjust, tx, ty, tz );
+
+/* Clear local constants. */
+#undef MXLEN
+}
+
+int astG3DTxExt( const char *text, float ref[3], const char *just,
+                 float up[3], float norm[3], float *xb, float *yb,
+                 float *zb, float bl[3] ){
+/*
+*+
+*  Name:
+*     astG3DTxExt
+
+*  Purpose:
+*     Get the extent of a character string.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int astG3DTxExt( const char *text, float ref[3], const char *just,
+*                      float up[3], float norm[3], float *xb, float *yb,
+*                      float *zb, float bl[3] )
+
+*  Description:
+*     This function returns the corners of a box which would enclose the
+*     supplied character string if it were displayed using astG3DText.
+*
+*     The returned box INCLUDES any leading or trailing spaces.
+
+*  Parameters:
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     ref
+*        The reference (x,y,z) coordinates.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", 'B' for "baseline", or "M" for "bottom", and
+*        specifies the vertical location of the reference position. Note,
+*        "baseline" corresponds to the base-line of normal text. Some
+*        characters (eg "y", "g", "p", etc) descend below the base-line,
+*        and so "M" and "B" will produce different effects for such
+*        characters. The second character may be 'L' for "left", 'C' for
+*        "centre", or 'R' for "right", and specifies the horizontal
+*        location of the reference position. If the string has less than
+*        2 characters then 'C' is used for the missing characters.
+*     up
+*        The (x,y,z) up-vector for the text. The actual up vector used is
+*        the projection of the supplied vector onto the plane specified by
+*        "norm".
+*     norm
+*        The (x,y,z) components of a vector that is normal to the plane
+*        containing the text. The given vector passes through the text
+*        from the back to the front. If all components of this vector are
+*        zero, then a normal vector pointing towards the camera eye is used.
+*     xb
+*        An array of 4 elements in which to return the x coordinate of
+*        each corner of the bounding box.
+*     yb
+*        An array of 4 elements in which to return the y coordinate of
+*        each corner of the bounding box.
+*     zb
+*        An array of 4 elements in which to return the z coordinate of
+*        each corner of the bounding box.
+*     bl
+*        The 3D world coordinates at the left hand end of the text
+*        baseline.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     -  The order of the corners is anti-clockwise starting at the
+*        bottom left when viewing the text normally (i.e. face on).
+*     -  This routine does not recognise PGPLOT escape sequences.
+*     -  A NULL value for "just" causes a value of "CC" to be used.
+*-
+*/
+
+/* Local Constants: */
+#define MXLEN 256
+
+/* Local Variables: */
+   char newjust[3];
+   int i;
+   int list[ MXLEN ];
+   int nlist;
+   float tx[3], ty[3], tz[3];
+
+/* Initialise the returned values to indicate no box available. */
+   for( i = 0; i < 4; i++ ){
+      xb[ i ] = 0.0;
+      yb[ i ] = 0.0;
+      zb[ i ] = 0.0;
+   }
+
+/* Convert the supplied string into a list of symbol numbers */
+   getSymbolList( text, MXLEN, &nlist, list );
+
+/* Create unit vectors along the three axes of the text plane
+   coordinate system. */
+   if( !getTextAxes( ref, up, norm, just, tx, ty, tz, newjust ) ) return 0;
+
+/* Find the bounding box of this list of symbols. */
+   return TxExt( list, nlist, ref, newjust, tx, ty, tz, xb, yb, zb, bl );
+
+/* Clear local constants. */
+#undef MXLEN
+}
+
+
+/* Public functions specific to this PGPLOT implementation. */
+/* ======================================================== */
+/* Other implementations of the grf3d interface can ignore the following
+   functions. They provide control of the 3D view. */
+
+int PG3DSetCamera( float eye[3], float target[3], float up[3], float screen ){
+/*
+*+
+*  Name:
+*     PG3DSetCamera
+
+*  Purpose:
+*     Store new camera settings for the current PGPLOT device.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int PG3DSetCamera( float eye[3], float target[3], float up[3],
+*                        float screen )
+
+*  Description:
+*     This function stores new camera settings for the current PGPLOT
+*     device.
+*
+*     A "camera" describes the projection of the 3D world coordinate
+*     space onto a 2D "screen". This screen corresponds to the 2D viewing
+*     surface used by PGPLOT. The 2D window used by PGPLOT (as set by
+*     PGSWIN, etc) defines the bounds of the screen area that is visible
+*     in the PGPLOT viewport.
+*
+*     The 3D world coordinate axes (x,y,z) are such that if "z" is
+*     vertically upwards and "x" points to the right, then "y" goes
+*     out of the paper away from you. All 3 axes are assume to have equal
+*     scale.
+*
+*     A camera defines a second set of 3D axes (called "(u,v,w)") with
+*     origin at the 3D world coordinates given by "eye":
+*
+*     -  the "w" axis points towards the position given by "target"
+*     -  the "v" axis is perpendicular to the "w" axis and is in the plane
+*        spanned by the "w" axis and the supplied "up" vector
+*     -  the "u" axis is perpendicular to both "w" and "v" and points to
+*        the left when looking from the eye along the w axis with the v
+*        axis upwards
+*
+*     Thus the "v" axis is parallel to "vertically up" on the 2D screen,
+*     "u" is parallel to "horizontally to the left", and "w" is
+*     perpendicular to the screen, pointing towards the target.
+*
+*     The screen is a plane perpendicular to the "w" axis, at the "w" axis
+*     value given by "screen". A 2D cartesian coordinate system (h,r) is
+*     defined on the screen, with origin at the point where the "w" axis
+*     intersects the screen. The "h" (horizontal) axis is parallel to the
+*     "u" axis but points in the opposite direction (to the left), and the
+*     "r" (vertical) axis is parallel to the "v" axis. The (h,r) system is
+*     taken to be the same as the PGPLOT 2D world coordinate system, and
+*     PGSWIN can therefore be used to specify the rectangular area on the
+*     screen that is mapped onto the PGPLOT viewport.
+*
+*     It is assumed that all axes (x,y,z), (u,v,w) and (h,r) are measured
+*     in the same units.
+
+*  Parameters:
+*     eye
+*        The position vector of the camera's "eye", in 3D world coordinates.
+*     target
+*        The position vector of a point in 3D world coordinates that is
+*        at the centre of the camera's view. In other words, the camera is
+*        looking towards this point. Zero will be returned if the target
+*        is the same position as the eye.
+*     up
+*        A vector in 3D world coordinates that will appear vertically
+*        upwards when projected onto the screen. Zero will be returned if
+*        the up vector has zero length or is parallel to the line joining
+*        the eye and the target.
+*     screen
+*        The distance from the camera's eye to the projection screen. If
+*        this is zero, then an orthographic projection is used.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - Zero is returned if no PGPLOT device has been opened prior to
+*     calling this function.
+*-
+*/
+
+/* Local Variables: */
+   Camera *cam;
+   float *u, *v, *w;
+   int result = 0;
+
+/* Get a pointer to the Camera structure for the current PGPLOT device.
+   Return without action if no PGPLOT device is open. */
+   cam = getCamera( 0 );
+   if( cam ) {
+      result = 1;
+
+/* Store the supplied values in the camera. */
+      memcpy( cam->target_vector, target, 3*sizeof( float ) );
+      memcpy( cam->eye_vector, eye, 3*sizeof( float ) );
+      cam->screen_distance = screen;
+
+/* Get pointers to the three rows of the w2c_matrix. This is a 3x3 matrix that
+   rotates vectors in the (x,y,z) system into vectors in the (u,v,w)
+   system. Each row in the matrix is a unit vector along the u, v or w
+   axes. */
+      u = cam->w2c_matrix;
+      v = u + 3;
+      w = v + 3;
+
+/* The "w" axis points form the eye to the target, so get the vector from
+   the eye to the target and normalise it. */
+      vectorSub( target, eye, w );
+      if( ! vectorNorm( w ) ) result = 0;
+
+/* The "v" vector is in the plane spanned by the "w" axis and the "up"
+   vector. Get the normal to this plane, storing the result temporarily
+   in the "u" vector. . */
+      vectorProduct( w, up, u );
+
+/* The "v" vector is normal to the vector found above and is also normal
+   to the "w" axis. Get this vector and normalise it.  */
+      vectorProduct( u, w, v );
+      if( ! vectorNorm( v ) ) result = 0;
+
+/* The "u" vector is perpendicualr to both the "w" and "v" vectors. */
+   vectorProduct( v, w, u );
+      if( ! vectorNorm( u ) ) result = 0;
+
+/* Use "v" as the stored up vector (the supplied "up" vector is not
+   necesarily the same as "v"). */
+      memcpy( cam->up_vector, v, 3*sizeof( float ) );
+
+/* Se a flag that indicates that the Camera is usable. */
+      cam->ok_flag = result ? CAMERA_OK : CAMERA_OK/2;
+   }
+
+   return result;
+}
+
+int PG3DSetEye( float eye[3] ){
+/*
+*+
+*  Name:
+*     PG3DSetEye
+
+*  Purpose:
+*     Store a new camera eye position for the current PGPLOT device.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int PG3DSetEye( float eye[3] )
+
+*  Description:
+*     This function modifies the camera eye position for the current
+*     PGPLOT device. Other camera settings are left unchanged. See
+*     PG3DSetCamera for more details.
+
+*  Parameters:
+*     eye
+*        The position vector of the camera's "eye", in 3D world coordinates.
+*        Zero is returned if the new eye position is the same as the
+*        existing camera target position.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - Zero is returned if no PGPLOT device has been opened prior to
+*     calling this function.
+*     - This function can only be called to modify an existing Camera.
+*     Consequently it returns zero if a camera has not already been set
+*     for the current PGPLOT device by calling PG3DSetCamera.
+*-
+*/
+
+/* Local Variables: */
+   Camera *cam;
+   int result = 0;
+
+/* Get a pointer to the Camera structure for the current PGPLOT device.
+   Return without action if no PGPLOT device is open. */
+   cam = getCamera( 1 );
+   if( cam ) {
+
+/* If so, modify the camera values, using the supplied eye position but
+   retaining the other camera settings. */
+      result = PG3DSetCamera( eye, cam->target_vector, cam->up_vector,
+                                cam->screen_distance );
+   }
+
+   return result;
+}
+
+int PG3DRotateEye( int dir, float angle ){
+/*
+*+
+*  Name:
+*     PG3DRotateEye
+
+*  Purpose:
+*     Move the eye on a great circle around the current target position.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int PG3DRotateEye( int dir, float angle )
+
+*  Description:
+*     This function modifies the camera eye position for the current
+*     PGPLOT device. Other camera settings are left unchanged. See
+*     PG3DSetCamera for more details.
+*
+*     The eye is moved by a gven distance along an arc of a great circle
+*     centred on the current target position. The target position itself
+*     is left unchanged.
+
+*  Parameters:
+*     dir
+*        The direction in which to move the eye position:
+*        1 - Move eye upwards
+*        2 - Move eye downwards
+*        3 - Move eye left
+*        4 - Move eye right
+*     angle
+*        The arc-distance, in degrees, by which to move the eye.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - Zero is returned if no PGPLOT device has been opened prior to
+*     calling this function.
+*     - This function can only be called to modify an existing Camera.
+*     Consequently it returns zero if a camera has not already been set
+*     for the current PGPLOT device by calling PG3DSetCamera.
+*-
+*/
+
+/* Local Variables: */
+   Camera *cam;
+   int result = 0;
+   int i;
+   float e[3], f[3], emod, neweye[3], sina, cosa;
+
+/* Get a pointer to the Camera structure for the current PGPLOT device.
+   Return without action if no PGPLOT device is open. */
+   cam = getCamera( 1 );
+   if( cam ) {
+
+/* Get the cos and sine of the supplied angle. */
+      cosa = cos( angle*TWOPI/360 );
+      sina = sin( angle*TWOPI/360 );
+
+/* Get the vector from the target to the eye, get its modulus. */
+      vectorSub( cam->eye_vector, cam->target_vector, e );
+      emod = vectorModulus( e );
+
+/* If we are moving the eye upwards, find the new eye position. */
+      if( dir == 1 ) {
+         for( i = 0; i < 3; i++ ) {
+            neweye[ i ] = e[ i ]*cosa + emod*cam->up_vector[ i ]*sina +
+                          cam->target_vector[ i ];
+         }
+
+/* If we are moving the eye downwards, find the new eye position. */
+      } else if( dir == 2 ) {
+         for( i = 0; i < 3; i++ ) {
+            neweye[ i ] = e[ i ]*cosa - emod*cam->up_vector[ i ]*sina +
+                          cam->target_vector[ i ];
+         }
+
+/* If we are moving the eye left or right we need a vector in the plane
+   of rotation that is at right angles to "e", and points to the right
+   of the eye. */
+      } else {
+         vectorProduct( cam->up_vector, e, f );
+         vectorNorm( f );
+
+/* Get the new eye position. */
+         if( dir == 3 ) {
+            for( i = 0; i < 3; i++ ) {
+               neweye[ i ] = e[ i ]*cosa - emod*f[ i ]*sina + cam->target_vector[ i ];
+            }
+
+         } else {
+            for( i = 0; i < 3; i++ ) {
+               neweye[ i ] = e[ i ]*cosa + emod*f[ i ]*sina + cam->target_vector[ i ];
+            }
+         }
+      }
+
+/* Modify the camera eye vector, retaining the other camera settings. */
+      result = PG3DSetCamera( neweye, cam->target_vector, cam->up_vector,
+                              cam->screen_distance );
+   }
+
+   return result;
+}
+
+int PG3DForward( float distance ){
+/*
+*+
+*  Name:
+*     PG3DForward
+
+*  Purpose:
+*     Move the eye forward towards the target.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int PG3DForward( float distance )
+
+*  Description:
+*     This function modifies the camera eye position for the current
+*     PGPLOT device. Other camera settings are left unchanged. See
+*     PG3DSetCamera for more details.
+*
+*     The eye is moved forward by a given distance towards the target
+*     point, and the target point is also moved forward so that the
+*     distance between eye and target remains unchanged.
+
+*  Parameters:
+*     distance
+*        The distance to move the eye and target, given as a fraction of
+*        the distance between the eye and the target.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - Zero is returned if no PGPLOT device has been opened prior to
+*     calling this function.
+*     - This function can only be called to modify an existing Camera.
+*     Consequently it returns zero if a camera has not already been set
+*     for the current PGPLOT device by calling PG3DSetCamera.
+*-
+*/
+
+/* Local Variables: */
+   Camera *cam;
+   int result = 0;
+   int i;
+   float e[3], newtarg[3], neweye[3];
+
+/* Get a pointer to the Camera structure for the current PGPLOT device.
+   Return without action if no PGPLOT device is open. */
+   cam = getCamera( 1 );
+   if( cam ) {
+
+/* Get the vector from the eye to the target. */
+      vectorSub( cam->target_vector, cam->eye_vector, e );
+
+/* Find the new eye and target positions. */
+      for( i = 0; i < 3; i++ ){
+         neweye[ i ] = cam->eye_vector[ i ] + e[ i ]*distance;
+         newtarg[ i ] = cam->target_vector[ i ] + e[ i ]*distance;
+      }
+
+/* Modify the camera eye and target vectors, retaining the other camera
+   settings. */
+      result = PG3DSetCamera( neweye, newtarg, cam->up_vector,
+                              cam->screen_distance );
+   }
+
+   return result;
+}
+
+
+int PG3DFindNearest( int n, float *x, float *y, float *z, int *iclose ){
+/*
+*+
+*  Name:
+*     PG3DForward
+
+*  Purpose:
+*     Find the closest point to the eye.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int PG3DFindNearest( int n, float *x, float *y, float *z, int *iclose )
+
+*  Description:
+*     This function checks every supplied point and returns the index of
+*     the point that is closest to the eye.
+
+*  Parameters:
+*     n
+*        The number of points to check.
+*     x
+*        Pointer to an array of "n" X values.
+*     y
+*        Pointer to an array of "n" Y values.
+*     z
+*        Pointer to an array of "n" Z values.
+*     iclose
+*        Pointer to an int in which to return the index of hte nearest
+*        point.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - Zero is returned if no PGPLOT device has been opened prior to
+*     calling this function.
+*-
+*/
+
+/* Local Variables: */
+   Camera *cam;
+   int result = 0;
+   int i;
+   float c[3], v[3];
+   float d;
+   float dmin;
+
+   *iclose = 0;
+
+/* Get a pointer to the Camera structure for the current PGPLOT device.
+   Return without action if no PGPLOT device is open. */
+   cam = getCamera( 1 );
+   if( cam ) {
+      result = 1;
+
+/*  Loop through all the supplied positions. */
+      dmin = FLT_MAX;
+      for( i = 0; i < n; i++ ) {
+
+/* Get the required distance. */
+         v[ 0 ] = x[ i ];
+         v[ 1 ] = y[ i ];
+         v[ 2 ] = z[ i ];
+         vectorSub( v, cam->eye_vector, c );
+         d = vectorModulus( c );
+
+/* If this is the smallest distance so far, remember it. */
+         if( d < dmin ) {
+            dmin = d;
+            *iclose = i;
+         }
+      }
+   }
+
+   return result;
+}
+
+
+int PG3DSetTarget( float target[3] ){
+/*
+*+
+*  Name:
+*     PG3DSetTarget
+
+*  Purpose:
+*     Store a new camera target position for the current PGPLOT device.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int PG3DSetTarget( float target[3] )
+
+*  Description:
+*     This function modifies the camera target position for the current
+*     PGPLOT device. Other camera settings are left unchanged. See
+*     PG3DSetCamera for more details.
+
+*  Parameters:
+*     target
+*        The position vector of the camera's "target", in 3D world coordinates.
+*        Zero is returned if the new target position is the same as the
+*        existing camera eye position.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - Zero is returned if no PGPLOT device has been opened prior to
+*     calling this function.
+*     - This function can only be called to modify an existing Camera.
+*     Consequently it returns zero if a camera has not already been set
+*     for the current PGPLOT device by calling PG3DSetCamera.
+*-
+*/
+
+/* Local Variables: */
+   Camera *cam;
+   int result = 0;
+
+/* Get a pointer to the Camera structure for the current PGPLOT device.
+   Return without action if no PGPLOT device is open. */
+   cam = getCamera( 1 );
+   if( cam ) {
+
+/* If so, modify the camera values, using the supplied target position but
+   retaining the other camera settings. */
+      result = PG3DSetCamera( cam->eye_vector, target, cam->up_vector,
+                                cam->screen_distance );
+   }
+
+   return result;
+}
+
+
+int PG3DSetUp( float up[3] ){
+/*
+*+
+*  Name:
+*     PG3DSetUp
+
+*  Purpose:
+*     Store a new camera up vector for the current PGPLOT device.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int PG3DSetUp( float up[3] )
+
+*  Description:
+*     This function modifies the camera up vector for the current
+*     PGPLOT device. Other camera settings are left unchanged. See
+*     PG3DSetCamera for more details.
+
+*  Parameters:
+*     up
+*        The new up vector, in 3D world coordinates. Zero is returned if
+*        the new up vector is parallel to the line joining the eye and
+*        the target positions.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - Zero is returned if no PGPLOT device has been opened prior to
+*     calling this function.
+*     - This function can only be called to modify an existing Camera.
+*     Consequently it returns zero if a camera has not already been set
+*     for the current PGPLOT device by calling PG3DSetCamera.
+*-
+*/
+
+/* Local Variables: */
+   Camera *cam;
+   int result = 0;
+
+/* Get a pointer to the Camera structure for the current PGPLOT device.
+   Return without action if no PGPLOT device is open. */
+   cam = getCamera( 1 );
+   if( cam ) {
+
+/* If so, modify the camera values, using the supplied up vector but
+   retaining the other camera settings. */
+      result = PG3DSetCamera( cam->eye_vector, cam->target_vector, up,
+                                cam->screen_distance );
+   }
+
+   return result;
+}
+
+
+int PG3DSetScreen( float screen ){
+/*
+*+
+*  Name:
+*     PG3DSetScreen
+
+*  Purpose:
+*     Store a new camera screen distance for the current PGPLOT device.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int PG3DSetScreen( float screen )
+
+*  Description:
+*     This function modifies the camera screen distance for the current
+*     PGPLOT device. Other camera settings are left unchanged. See
+*     PG3DSetCamera for more details.
+
+*  Parameters:
+*     screen
+*        The distance from the camera's eye to the projection screen in
+*        3D world coordinate units. If this is zero, then an orthographic
+*        projection is used.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - Zero is returned if no PGPLOT device has been opened prior to
+*     calling this function.
+*     - This function can only be called to modify an existing Camera.
+*     Consequently it returns zero if a camera has not already been set
+*     for the current PGPLOT device by calling PG3DSetCamera.
+*-
+*/
+
+/* Local Variables: */
+   Camera *cam;
+   int result = 0;
+
+/* Get a pointer to the Camera structure for the current PGPLOT device.
+   Return without action if no PGPLOT device is open. */
+   cam = getCamera( 1 );
+   if( cam ) {
+
+/* If so, modify the camera values, using the supplied screen distance but
+   retaining the other camera settings. */
+      result = PG3DSetCamera( cam->eye_vector, cam->target_vector,
+                                cam->up_vector, screen );
+   }
+
+   return result;
+}
+
+int PG3DAutoCamera( float lbnd[3], float ubnd[3] ){
+/*
+*+
+*  Name:
+*     PG3DAutoCamera
+
+*  Purpose:
+*     Set up a default camera to view a given box of 3D world coords.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int PG3DAutoCamera( float lbnd[3], float ubnd[3] )
+
+*  Description:
+*     This function sets up the camera and the 2D PGPLOT window for the
+*     current device so that it produces a default view of a specified
+*     volume of 3D world coordinate space.
+
+*  Parameters:
+*     lbnd
+*        The lower bounds of the volume of 3D world coordinates that
+*        is to be visible using the camera and 2D PGPLOT window.
+*     ubnd
+*        The upper bounds of the volume of 3D world coordinates that
+*        is to be visible using the camera and 2D PGPLOT window.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     - Zero is returned if no PGPLOT device has been opened prior to
+*     calling this function.
+*-
+*/
+
+/* Local Variables: */
+   float target[3], eye[3], up[3], screen, dx, dy, dz, hlo, hhi, rlo, rhi;
+   float x[8], y[8], z[8], h[8], r[8];
+   Camera *cam;
+   int result = 0;
+   int i;
+
+/* Get a pointer to the Camera structure for the current PGPLOT device.
+   Return without action if no PGPLOT device is open. */
+   cam = getCamera( 0 );
+   if( cam ) {
+
+/* The target position (i.e. the position towards which the camera is
+   looking) is the middle of the volume. */
+      target[ 0 ] = 0.5*( lbnd[ 0 ] + ubnd[ 0 ] );
+      target[ 1 ] = 0.5*( lbnd[ 1 ] + ubnd[ 1 ] );
+      target[ 2 ] = 0.5*( lbnd[ 2 ] + ubnd[ 2 ] );
+
+/* The eye is slightly offset from a corner view. */
+      eye[ 0 ] = 0.85*ubnd[ 0 ] + 0.15*lbnd[ 0 ];
+      eye[ 1 ] = 0.75*ubnd[ 1 ] + 0.25*lbnd[ 1 ];
+      eye[ 2 ] = 0.75*ubnd[ 2 ] + 0.25*lbnd[ 2 ];
+
+/* The eye is seven times the size of the box away from the box centre. */
+      eye[ 0 ] = 7*(eye[ 0 ] - target[ 0 ] ) + target[ 0 ];
+      eye[ 1 ] = 7*(eye[ 1 ] - target[ 1 ] ) + target[ 1 ];
+      eye[ 2 ] = 7*(eye[ 2 ] - target[ 2 ] ) + target[ 2 ];
+
+/* The up vector is paralle to the Z axis. */
+      up[ 0 ] = 0.0;
+      up[ 1 ] = 0.0;
+      up[ 2 ] = 1.0;
+
+/* The screen is at the centre of the box. */
+      dx = eye[ 0 ] - target[ 0 ];
+      dy = eye[ 1 ] - target[ 1 ];
+      dz = eye[ 2 ] - target[ 2 ];
+      screen = sqrtf( dx*dx + dy*dy + dz*dz );
+
+/* Set the camera. */
+      if( PG3DSetCamera( eye, target, up, screen ) ) {
+
+/* Get the 3D World coords at the corners of the volume. */
+         x[ 0 ] = ubnd[ 0 ];
+         x[ 1 ] = ubnd[ 0 ];
+         x[ 2 ] = lbnd[ 0 ];
+         x[ 3 ] = lbnd[ 0 ];
+         x[ 4 ] = ubnd[ 0 ];
+         x[ 5 ] = ubnd[ 0 ];
+         x[ 6 ] = lbnd[ 0 ];
+         x[ 7 ] = lbnd[ 0 ];
+
+         y[ 0 ] = lbnd[ 1 ];
+         y[ 1 ] = ubnd[ 1 ];
+         y[ 2 ] = ubnd[ 1 ];
+         y[ 3 ] = lbnd[ 1 ];
+         y[ 4 ] = lbnd[ 1 ];
+         y[ 5 ] = ubnd[ 1 ];
+         y[ 6 ] = ubnd[ 1 ];
+         y[ 7 ] = lbnd[ 1 ];
+
+         z[ 0 ] = lbnd[ 2 ];
+         z[ 1 ] = lbnd[ 2 ];
+         z[ 2 ] = lbnd[ 2 ];
+         z[ 3 ] = lbnd[ 2 ];
+         z[ 4 ] = ubnd[ 2 ];
+         z[ 5 ] = ubnd[ 2 ];
+         z[ 6 ] = ubnd[ 2 ];
+         z[ 7 ] = ubnd[ 2 ];
+
+/* Transform these into screen coordinates. */
+         if( transform( cam, 8, x, y, z, h, r ) ) {
+
+/* Find the bounds in h and r of the projection of the volume. */
+            hlo = FLT_MAX;
+            hhi = -FLT_MAX;
+            rlo = FLT_MAX;
+            rhi = -FLT_MAX;
+
+            for( i = 0; i < 8; i++ ) {
+               if( h[ i ] < hlo ) hlo = h[ i ];
+               if( h[ i ] > hhi ) hhi = h[ i ];
+               if( r[ i ] < rlo ) rlo = r[ i ];
+               if( r[ i ] > rhi ) rhi = r[ i ];
+            }
+
+/* Extend these bounds by 5% at each end */
+            dx = 0.05*( hhi - hlo );
+            hhi += dx;
+            hlo -= dx;
+
+            dy = 0.05*( rhi - rlo );
+            rhi += dy;
+            rlo -= dy;
+
+/* If the box has non-zero area, set it as the 2D PGPLOT window, and
+   indicate success. */
+            if( rlo < rhi && hlo < hhi ) {
+               ccpgswin( hlo, hhi, rlo, rhi );
+               result = 1;
+            }
+         }
+      }
+   }
+   return result;
+}
+
+
+
+
+
+/* Private functions for this module */
+/* ================================= */
+
+static int TextCam( Camera *textcam, float ref[3], float tx[3], float ty[3],
+                    float tz[3] ){
+/*
+*  Name:
+*     TextCam
+
+*  Purpose:
+*     Create a Camera that converts 3D text plane coordinates into 2D world
+*     coordinates.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int TextCam( Camera *textcam, float ref[3], float tx[3], float ty[3],
+*                  float tz[3] )
+
+*  Description:
+*     This function initialises the contents of a supplied Camera
+*     structure so that the Camera describes the transformation from 3D
+*     "text plane" coordinates to 2D PGPLOT world coordinates. The text
+*     plane coordinate system is defined by three vectors along its x, y
+*     and z axes, and an origin position.
+*
+*     Text plane coordinates describe a plane upon which 2D graphics such
+*     as text is drawn. The X axis is parallel to the text base line, the
+*     Y axis is the text up vector, and the Z axis is perpendicular to
+*     the text, passing from the back of the text to the front of the text.
+
+*  Parameters:
+*     textcam
+*        The Camera structure which is to be modified.
+*     ref
+*        The (x,y,z) coordinates at the text plane origin.
+*     tx
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        X axis. This is parallel to the text base line.
+*     ty
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        Y axis. This is parallel to the projectionof ht eup vector on to
+*        the text plane.
+*     tz
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        Z axis. This is perpendicular to the text plane, passing from
+*        the back of the text to the front of the text.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*/
+
+
+/* Local Variables: */
+   Camera *cam;
+   float dx, dy, dz;
+   int i;
+   float a, b, c;
+
+/* Get the Camera for the current device identifier. Abort if no camera
+   is available. This camera describes the transformation from 3D world
+   coordinates (x,y,z) to 2D world coordinates (screen coordinates) (h,r). */
+   cam = getCamera( 1 );
+   if( !cam ) return 0;
+
+/* Create a Camera structure that describes the transformation from
+   text plane coordinates to 2D world coords, putting the origin of text
+   plane coordinates at the given reference position. */
+   dx = cam->eye_vector[ 0 ] - ref[ 0 ];
+   dy = cam->eye_vector[ 1 ] - ref[ 1 ];
+   dz = cam->eye_vector[ 2 ] - ref[ 2 ];
+
+   textcam->eye_vector[ 0 ] = tx[ 0 ]*dx + tx[ 1 ]*dy + tx[ 2 ]*dz;
+   textcam->eye_vector[ 1 ] = ty[ 0 ]*dx + ty[ 1 ]*dy + ty[ 2 ]*dz;
+   textcam->eye_vector[ 2 ] = tz[ 0 ]*dx + tz[ 1 ]*dy + tz[ 2 ]*dz;
+
+   for( i = 0; i < 8; i += 3 ) {
+      a = cam->w2c_matrix[ i ];
+      b = cam->w2c_matrix[ i + 1 ];
+      c = cam->w2c_matrix[ i + 2 ];
+      textcam->w2c_matrix[ i     ] = a*tx[ 0 ] + b*tx[ 1 ] + c*tx[ 2 ];
+      textcam->w2c_matrix[ i + 1 ] = a*ty[ 0 ] + b*ty[ 1 ] + c*ty[ 2 ];
+      textcam->w2c_matrix[ i + 2 ] = a*tz[ 0 ] + b*tz[ 1 ] + c*tz[ 2 ];
+   }
+
+   textcam->screen_distance = cam->screen_distance;
+   textcam->ok_flag = CAMERA_OK;
+
+   return 1;
+}
+
+static int Polygon( int nside, float *vx, float *vy, float *vz, float ref[3],
+                    float tx[3], float ty[3], float tz[3]  ){
+/*
+*  Name:
+*     Polygon
+
+*  Purpose:
+*     Draw a regular polygon.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int Polygon( int nside, float *vx, float *vy, float *vz, float ref[3],
+*                  float tx[3], float ty[3], float tz[3] )
+
+*  Description:
+*     This function draws a polygon centred at a given position on a
+*     given  plane in 3D world coords, using a specified up-vector. The
+*     polygon vertices are specified in text plane coordinates via vx,
+*     vy and vz.
+
+*  Parameters:
+*     nside
+*        Number of sides for the polygon. Numbers higher than 32 are
+*        treated as 32.
+*     vx
+*        Pointer to an array of "nside" text plane X axis values.
+*     vy
+*        Pointer to an array of "nside" text plane Y axis values.
+*     vz
+*        Pointer to an array of "nside" text plane Z axis values.
+*     ref
+*        The (x,y,z) coordinates at the polygon centre.
+*     tx
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        X axis. This is parallel to the text base line.
+*     ty
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        Y axis. This is parallel to the projectionof ht eup vector on to
+*        the text plane.
+*     tz
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        Z axis. This is perpendicular to the text plane, passing from
+*        the back of the text to the front of the text.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*/
+
+
+/* Local Variables: */
+   Camera *cam;
+   Camera newcam;
+   float h[ MXSIDE ], r[ MXSIDE ];
+
+/* Get the Camera for the current device identifier. Abort if no camera
+   is available. */
+   cam = getCamera( 1 );
+   if( !cam ) return 0;
+
+/* Check the number of points. */
+   if( nside > MXSIDE) return 0;
+
+/* Create a Camera structure that describes the transformation from
+   text plane coordinates to 2D world coords, putting the origin of text
+   plane coordinates at the given reference position. */
+   if( !TextCam( &newcam, ref, tx, ty, tz ) ) return 0;
+
+/* Transform the given text plane coordinates into 2D world coordinates. */
+   if( !transform( &newcam, nside, vx, vy, vz, h, r ) ) return 0;
+
+/* Draw the polygon. */
+   ccpgpoly( nside, h, r );
+
+/* If we get here we have succeeded so return a non-zero value. */
+   return 1;
+}
+
+static int Text( int *list, int nlist, float ref[3], const char *just,
+                 float tx[3], float ty[3], float tz[3] ){
+/*
+*  Name:
+*     Text
+
+*  Purpose:
+*     Draw a character string.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int Text( int *list, int nlist, float ref[3], const char *just,
+*               float tx[3], float ty[3], float tz[3] )
+
+*  Description:
+*     This function displays a symbol list at a given position on a given
+*     plane in 3D world coords, using a specified justification and up-vector.
+
+*  Parameters:
+*     list
+*        Pointer to an array of pgplot symbol values.
+*     nlist
+*        Length of the "list" array.
+*     ref
+*        The reference (x,y,z) coordinates.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", or 'B' for "bottom", and specifies the
+*        vertical location of the reference position. Note, "bottom"
+*        corresponds to the base-line of normal text. Some characters
+*        (eg "y", "g", "p", etc) descend below the base-line. The second
+*        character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+*     tx
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        X axis. This is parallel to the text base line.
+*     ty
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        Y axis. This is parallel to the projection of the up vector on to
+*        the text plane.
+*     tz
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        Z axis. This is perpendicular to the text plane, passing from
+*        the back of the text to the front of the text.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     -  This routine does not recognise PGPLOT escape sequences.
+*     -  A NULL value for "just" causes a value of "CC" to be used.
+*/
+
+/* Local Constants: */
+#define MXLEN 256
+
+/* Local Variables: */
+   Camera *cam;
+   Camera newcam;
+   float ch;
+   float tm, txc, tyc;
+   float txleft, tybase;
+   float xt[ 150 ], yt[ 150 ], zt[ 150 ], h[ 150 ], r[ 150 ];
+   float xb[3], yb[3], zb[3], bl[3];
+   int clip;
+   int i;
+   int j;
+   int k;
+   int unused;
+   int xygrid[ 300 ];
+
+/* If there is nothing to plot return without error. */
+   if( nlist == 0 ) return 1;
+
+/* Find the 3D world coordinates at the left hand end of the text
+   baseline. */
+   if( !TxExt( list, nlist, ref, just, tx, ty, tz, xb, yb, zb, bl ) ) return 0;
+
+/* Get the Camera for the current device identifier. Abort if no camera
+   is available. */
+   if( ! (cam = getCamera( 1 ) ) ) return 0;
+
+/* Create a Camera structure that describes the transformation from
+   text plane coordinates to 2D world coords. */
+   if( !TextCam( &newcam, ref, tx, ty, tz ) ) return 0;
+
+/* Save the current clipping flag, and ensure clipping is off. */
+   ccpgqclp( &clip );
+   ccpgsclp( 0 );
+
+/* Calculate the text plane X coord of the left hand edge of the first
+   character. */
+   txleft = tx[ 0 ]*( bl[ 0 ] - ref[ 0 ] ) +
+            tx[ 1 ]*( bl[ 1 ] - ref[ 1 ] ) +
+            tx[ 2 ]*( bl[ 2 ] - ref[ 2 ] );
+
+/* Calculate the text plane Y coord at the text baseline. */
+   tybase = ty[ 0 ]*( bl[ 0 ] - ref[ 0 ] ) +
+            ty[ 1 ]*( bl[ 1 ] - ref[ 1 ] ) +
+            ty[ 2 ]*( bl[ 2 ] - ref[ 2 ] );
+
+/* Get the character height in world coordinate units. A PGPLOT
+   character height of 1.0 corresponds to 1/40 of the 2D window height. */
+   ch = getCharHeight();
+
+/* Get the polylines that correspond to the first symbol. */
+   ccgrsyxd( list[ 0 ], xygrid, &unused );
+
+/* Create a linear transformation that maps the font grid coordinate
+   system used by grsyxd onto text plane coordinates. This transformation
+   will be different for each character in the string. The initial
+   transformation set up now is appropriate for the first character. The
+   mapping is:
+
+      Text_x = txc + tm*Font_x
+      Text_y = tyc + tm*Font_y
+
+*/
+   tm = ch/( xygrid[ 2 ] - xygrid[ 1 ] );
+   tyc = tybase - tm*xygrid[ 1 ];
+   txc = txleft - tm*xygrid[ 3 ];
+
+/* Loop round each symbol. */
+   for( i = 0; i < nlist; i++ ) {
+
+/* Loop round each polyline that forms a segment of the character */
+      k = 5;
+      while( 1 ) {
+
+/* Map the polyline vertices into text plane coordinates. */
+         j = 0;
+         while( j < 150  ){
+             if( xygrid[ k ] != -64 ) {
+                xt[ j ] = txc + tm*xygrid[ k++ ];
+                yt[ j ] = tyc + tm*xygrid[ k++ ];
+                zt[ j++ ] = 0.0;
+             } else {
+                break;
+             }
+         }
+
+/* Map the text plane coordinates into 2D world coordinates. */
+         if( j > 0 ) {
+            (void) transform( &newcam, j, xt, yt, zt, h, r );
+
+/* Draw the polyline. */
+            ccpgline( j, h, r );
+         }
+
+/* If this is the last segment in the character, pass on to the next
+   character. */
+         if( xygrid[ k + 1 ] == -64 ) break;
+
+/* Otherwise, skip over the end markers in the xygrid array, and go on to
+   plot the next polyline segment. */
+         k += 2;
+      }
+
+/* If this is not the last symbol... */
+      if( i != nlist - 1 ) {
+
+/* Set the text x value at which to place the left edge of the next
+   character. This is the right hand edge of the character just drawn. */
+         txleft += tm*( xygrid[ 4 ] - xygrid[ 3 ] );
+
+/* Get the polylines that correspond to the next symbol. */
+         ccgrsyxd( list[ i + 1 ], xygrid, &unused );
+
+/* Modify the transformation from font grid coords to text plane coords
+   so that it is appropriate for the next character in the string. */
+         txc = txleft - tm*xygrid[ 3 ];
+      }
+
+/* Next symbol. */
+   }
+
+/* Re-instate original clipping flag. */
+   ccpgsclp( clip );
+
+/* If we arrive here, we have been successful, so return a non-zero
+   value. */
+   return 1;
+
+/* Clear local constants. */
+#undef MXLEN
+}
+
+static int TxExt( int *list, int nlist, float ref[3], const char *just,
+                  float tx[3], float ty[3], float tz[3], float *xb, float *yb,
+                  float *zb, float bl[3] ){
+/*
+*  Name:
+*     TxExt
+
+*  Purpose:
+*     Get the extent of a character string.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int TxExt( int *list, int nlist, float ref[3], const char *just,
+*                float tx[3], float ty[3], float tz[3], float *xb, float *yb,
+*                float *zb, float bl[3] )
+
+*  Description:
+*     This function returns the corners of a box which would enclose the
+*     supplied symbol list if it were displayed using Text.
+*
+*     The returned box includes any leading or trailing spaces.
+
+*  Parameters:
+*     list
+*        Pointer to an array of pgplot symbol numbers.
+*     nlist
+*        The length of the "list" array.
+*     ref
+*        The reference (x,y,z) coordinates.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", 'B' for "baseline", or "M" for "bottom", and
+*        specifies the vertical location of the reference position. Note,
+*        "baseline" corresponds to the base-line of normal text. Some
+*        characters (eg "y", "g", "p", etc) descend below the base-line,
+*        and so "M" and "B" will produce different effects for such
+*        characters. The second character may be 'L' for "left", 'C' for
+*        "centre", or 'R' for "right", and specifies the horizontal
+*        location of the reference position. If the string has less than
+*        2 characters then 'C' is used for the missing characters.
+*     tx
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        X axis. This is parallel to the text base line.
+*     ty
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        Y axis. This is parallel to the projectionof ht eup vector on to
+*        the text plane.
+*     tz
+*        A unit vector (expressed in 3D world coords) along the text plane
+*        Z axis. This is perpendicular to the text plane, passing from
+*        the back of the text to the front of the text.
+*     xb
+*        An array of 4 elements in which to return the x coordinate of
+*        each corner of the bounding box.
+*     yb
+*        An array of 4 elements in which to return the y coordinate of
+*        each corner of the bounding box.
+*     zb
+*        An array of 4 elements in which to return the z coordinate of
+*        each corner of the bounding box.
+*     bl
+*        The 3D world coordinates at the left hand end of the text
+*        baseline.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     -  The order of the corners is anti-clockwise starting at the
+*        bottom left when viewing the text normally (i.e. face on).
+*     -  This routine does not recognise PGPLOT escape sequences.
+*     -  A NULL value for "just" causes a value of "CC" to be used.
+*/
+
+/* Local Constants: */
+#define MXLEN 256
+
+/* Local Variables: */
+   float ch;
+   float txlo, txhi, tylo, tyhi, tyzero;
+   float tm;
+   float w;
+   int i;
+   int unused;
+   int xygrid[ 300 ];
+   int gylo, gyhi, width;
+
+/* Initialise the returned values to indicate no box available. */
+   for( i = 0; i < 4; i++ ){
+      xb[ i ] = 0.0;
+      yb[ i ] = 0.0;
+      zb[ i ] = 0.0;
+   }
+
+/* If there is nothing to plot return without error. */
+   if( nlist == 0 ) return 1;
+
+/* We now find the bounding box of the text in "text plane coordinates".
+   These are (tx,ty,tz) axes that span the plane upon which the text is
+   writtens. The origin of (tx,ty,tz) is at the supplied 3D reference
+   position, the X axis increases along the text baseline, and Y axis
+   increases along the text up vector, and Z increases from the back
+   of the text to the front of the text  (all are measured in 3D world
+   coord units). We first find the bounds of the text in these text plane
+   coordinates, assuming that the bottom left of the text baseline is
+   placed at the given reference position (i.e. at the origiin of text
+   plane coordinates). */
+
+/* Get the character height in world coordinate units. A PGPLOT
+   character height of 1.0 corresponds to 1/40 of the 2D window height. */
+   ch = getCharHeight();
+
+/* Initialise the Y bounds of the text bounding box in grid coords. */
+   gylo = INT_MAX;
+   gyhi = -INT_MAX;
+
+/* Initialise things. */
+   width = 0;
+   tm = 1.0;
+
+/* Loop round each symbol. */
+   for( i = 0; i < nlist; i++ ) {
+
+/* Get the polylines that correspond to this symbol. */
+      ccgrsyxd( list[ i ], xygrid, &unused );
+
+/* If this is the first symbol, set the scaling factor that converts
+   grid units to text plane units. */
+      if( i == 0 ) tm = ch/( xygrid[ 2 ] - xygrid[ 1 ] );
+
+/* Note the highest and lowest y grid value. */
+      w = xygrid[ 2 ] - xygrid[ 1 ];
+      if( w > gyhi ) gyhi = w;
+
+      w = xygrid[ 0 ] - xygrid[ 1 ];
+      if( w < gylo ) gylo = w;
+
+/* Increment the total width of the string in grid units. */
+      width += xygrid[ 4 ] - xygrid[ 3 ];
+   }
+
+/* Set up the bounding box in text plane coordinates. */
+   txlo = 0.0;
+   txhi = width*tm;
+   tylo = gylo*tm;
+   tyhi = gyhi*tm;
+   tyzero = 0.0;
+
+/* Adjust the text plane bounding box to take account of the specified
+   text justification. The above process implicitly assumed a
+   justifiation of "BL". */
+   if( !just || just[ 0 ] == 'C' || just[ 0 ] == 0 ){
+      w = 0.5*( tyhi + tylo );
+      tylo -= w;
+      tyhi -= w;
+      tyzero -= w;
+
+   } else if( just[ 0 ] == 'T' ){
+      w = tyhi;
+      tylo -= w;
+      tyhi -= w;
+      tyzero -= w;
+
+   } else if( just[ 0 ] == 'M' ){
+      w = -tylo;
+      tylo += w;
+      tyhi += w;
+      tyzero += w;
+
+   } else if( just[ 0 ] != 'B' ) {
+      astError( AST__GRFER, "astG3DTxExt: Justification string '%s' "
+                "is invalid.", just );
+      return 0;
+   }
+
+   if( !just || just[ 1 ] == 'C' || just[ 1 ] == 0 ){
+      w = 0.5*( txhi + txlo );
+      txlo -= w;
+      txhi -= w;
+
+   } else if( just[ 1 ] == 'R' ){
+      w = txhi;
+      txlo -= w;
+      txhi -= w;
+
+   } else if( just[ 1 ] == 'L' ){
+      w = txlo;
+      txlo -= w;
+      txhi -= w;
+
+   } else {
+      astError( AST__GRFER, "astG3DTxExt: Justification string '%s' "
+                "is invalid.", just );
+      return 0;
+   }
+
+/* Use the supplied text plane axis vectors to transform the corners of
+   the text plane bounding box into 3D world coordinates. */
+   xb[ 0 ] = tx[ 0 ]*txlo + ty[ 0 ]*tylo + ref[ 0 ];
+   yb[ 0 ] = tx[ 1 ]*txlo + ty[ 1 ]*tylo + ref[ 1 ];
+   zb[ 0 ] = tx[ 2 ]*txlo + ty[ 2 ]*tylo + ref[ 2 ];
+
+   xb[ 1 ] = tx[ 0 ]*txhi + ty[ 0 ]*tylo + ref[ 0 ];
+   yb[ 1 ] = tx[ 1 ]*txhi + ty[ 1 ]*tylo + ref[ 1 ];
+   zb[ 1 ] = tx[ 2 ]*txhi + ty[ 2 ]*tylo + ref[ 2 ];
+
+   xb[ 2 ] = tx[ 0 ]*txhi + ty[ 0 ]*tyhi + ref[ 0 ];
+   yb[ 2 ] = tx[ 1 ]*txhi + ty[ 1 ]*tyhi + ref[ 1 ];
+   zb[ 2 ] = tx[ 2 ]*txhi + ty[ 2 ]*tyhi + ref[ 2 ];
+
+   xb[ 3 ] = tx[ 0 ]*txlo + ty[ 0 ]*tyhi + ref[ 0 ];
+   yb[ 3 ] = tx[ 1 ]*txlo + ty[ 1 ]*tyhi + ref[ 1 ];
+   zb[ 3 ] = tx[ 2 ]*txlo + ty[ 2 ]*tyhi + ref[ 2 ];
+
+/* Also transform the text plane coordinates at the bottom left of the
+   text baseline into 3D world coordinates. */
+   bl[ 0 ] = tx[ 0 ]*txlo + ty[ 0 ]*tyzero + ref[ 0 ];
+   bl[ 1 ] = tx[ 1 ]*txlo + ty[ 1 ]*tyzero + ref[ 1 ];
+   bl[ 2 ] = tx[ 2 ]*txlo + ty[ 2 ]*tyzero + ref[ 2 ];
+
+/* If we get here, we have been succesful, so return a non-zero value. */
+   return 1;
+
+/* Clear local constants. */
+#undef MXLEN
+}
+
+static float getCharHeight( void ){
+/*
+*  Name:
+*     getCharHeight
+
+*  Purpose:
+*     Get the current text height setting.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     float getCharHeight( void )
+
+*  Description:
+*     This function returns the PGPLOT character height, scaled into
+*     world coordinate units.
+
+*  Returned Value:
+*    The character height, in world coordinate units.
+
+*/
+
+/* Local Variables: */
+   float wx1, wx2, wy1, wy2;
+   float ch;
+
+/* Get the bounds of the PGPLTO 2D window. */
+   ccpgqwin( &wx1, &wx2, &wy1, &wy2 );
+
+/* Get the normalised PGPLOT character height. */
+   ccpgqch( &ch );
+
+/* A PGPLOT character height of 1.0 corresponds to 1/40 of the 2D window
+   height. Scale the normalised character height into world coordinate
+   units, and return it. */
+   return ch*fabs( wy1 - wy2 )/40.0;
+
+}
+
+static int getTextAxes( float ref[3], float up[3], float norm[3],
+                        const char *just, float tx[3], float ty[3],
+                        float tz[3], char newjust[3] ){
+/*
+*  Name:
+*     getTextAxes
+
+*  Purpose:
+*     Get unit vectors along the text plane coordinate axes.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int getTextAxes( float ref[3], float up[3], float norm[3],
+*                      const char *just, float tx[3], float ty[3],
+*                      float tz[3], char newjust[3] )
+
+*  Description:
+*     This function returns three unit vectors that define the axes of a
+*     3D Cartesian coordinate system known as "text plane coordinates".
+*     These axes span the plane upon which text (or other graphics) is to
+*     be written. The origin is at the supplied 3D reference position, the
+*     X axis increases along the text baseline, and Y axis increases along
+*     the text up vector, and Z increases from the back of the text to the
+*     front of the text (all are measured in 3D world  coord units).
+*
+*     The returned vectors are reversed if this will result in text
+*     appearing more "normal" (i.e. viewed from the front rather than
+*     the back, and viewed upright rather thna upside down). If the
+*     vectors are reversed, the justification string is also changed so
+*     that the text occupies the requested area on the screen.
+
+*  Parameters:
+*     ref
+*        The reference (x,y,z) coordinates.
+*     up
+*        The (x,y,z) up-vector for the text. The actual up vector used is
+*        the projection of the supplied vector onto the plane specified by
+*        "norm".
+*     norm
+*        The (x,y,z) components of a vector that is normal to the plane
+*        containing the text. The given vector passes through the text
+*        from the back to the front. If all components of this vector are
+*        zero, then a normal vector pointing towards the camera eye is used.
+*     just
+*        The requested text justification, as supplied to astG3DText.
+*     tx
+*        A unit vector along the text plane X axis.
+*     ty
+*        A unit vector along the text plane X axis.
+*     tz
+*        A unit vector along the text plane X axis.
+*     newjust
+*        The text justification to use.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+*/
+
+/* Local Variables: */
+   Camera *cam;
+   float eye[3];
+
+/* Initialise the returned justification to equal the supplied
+   justification, supplying defaults if required. . */
+   if( just ) {
+      strncpy( newjust, just, 2 );
+      if( !newjust[ 0 ] ) newjust[ 0 ] = 'C';
+      if( !newjust[ 1 ] ) newjust[ 1 ] = 'C';
+      newjust[ 2 ] = 0;
+   } else {
+      strcpy( newjust, "CC" );
+   }
+
+/* Get the Camera for the current device identifier. Abort if no camera
+   is available. */
+   if( !( cam = getCamera( 1 ) ) ) return 0;
+
+/* Calculate the vector from the reference position to the eye, and store
+   it in "eye". */
+   vectorSub( cam->eye_vector, ref, eye );
+
+/* Create unit vectors along the three axes of the text plane coordinate
+   system. These unit vectors are represented in terms of the 3D world
+   coordinate axes. The text Z axis is parallel to the supplied "norm"
+   vector. */
+   tz[ 0 ] = norm[ 0 ];
+   tz[ 1 ] = norm[ 1 ];
+   tz[ 2 ] = norm[ 2 ];
+
+/* Attempt to normalise the "tz" vector. If it has zero length, use the
+   offset from the reference point to the eye. */
+   if( ! vectorNorm( tz ) ){
+
+/* Use the "eye" vector calculated above as the text plane Z axis. */
+      tz[ 0 ] = eye[ 0 ];
+      tz[ 1 ] = eye[ 1 ];
+      tz[ 2 ] = eye[ 2 ];
+   }
+
+/* Find vectors along the text plane x and y axes. */
+   vectorProduct( up, tz, tx );
+   vectorProduct( tz, tx, ty );
+
+/* Normalise the three text plane axis vectors. If any vector has zero
+   length, abort. */
+   if( !vectorNorm( tx ) || !vectorNorm( ty ) || !vectorNorm( tz ) ) return 0;
+
+/* We now reverse text plane vectors if this will help ther text to be
+   viewed "normally" on the screen. If the existing vectors cause the
+   text to be viewed from the back rather than the front, reverse the tx
+   and tz vectors so that he text is viewed from the front. */
+   if( dotProduct( tz, eye ) < 0.0 ) {
+      tz[ 0 ] = -tz[ 0 ];
+      tz[ 1 ] = -tz[ 1 ];
+      tz[ 2 ] = -tz[ 2 ];
+      tx[ 0 ] = -tx[ 0 ];
+      tx[ 1 ] = -tx[ 1 ];
+      tx[ 2 ] = -tx[ 2 ];
+
+/* The text will have spun around the up vector (i.e. the ty axis), so
+   modify the horizontal justification  so that thex text occupies the same
+   area on the screen. */
+      if( newjust[ 1 ] == 'L' ) {
+         newjust[ 1 ] = 'R';
+      } else if( newjust[ 1 ] == 'R' ) {
+         newjust[ 1 ] = 'L';
+      }
+   }
+
+/* If the existing vectors cause the text to be viewed upside down, reverse
+   the tx and ty vectors so that he text is viewed right way up. */
+   if( dotProduct( ty, cam->up_vector ) < 0.0 ) {
+      ty[ 0 ] = -ty[ 0 ];
+      ty[ 1 ] = -ty[ 1 ];
+      ty[ 2 ] = -ty[ 2 ];
+      tx[ 0 ] = -tx[ 0 ];
+      tx[ 1 ] = -tx[ 1 ];
+      tx[ 2 ] = -tx[ 2 ];
+
+/* The text will have spun around the tz vector (i.e. the viewing vector),
+   so modify both vertical and horizontal justification so that the text
+   occupies the same area on the screen. */
+      if( newjust[ 0 ] == 'B' || newjust[ 0 ] == 'M' ) {
+         newjust[ 0 ] = 'T';
+      } else if( newjust[ 0 ] == 'T' ) {
+         newjust[ 0 ] = 'M';
+      }
+
+      if( newjust[ 1 ] == 'L' ) {
+         newjust[ 1 ] = 'R';
+      } else if( newjust[ 1 ] == 'R' ) {
+         newjust[ 1 ] = 'L';
+      }
+   }
+
+/* If we arraive here we have been succesful, so return a non-zero value. */
+   return 1;
+}
+
+static void getSymbolList( const char *text, int mxlen, int *nlist, int *list ){
+/*
+*  Name:
+*     getSymbolList
+
+*  Purpose:
+*     Get the extent of a character string.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     void getSymbolList( const char *text, int mxlen, int *nlist, int *list )
+
+*  Description:
+*     This function converts a supplied text string into a list of PGPLOT
+*     symbol numbers for the current PGPLOT font.
+
+*  Parameters:
+*     text
+*        Pointer to a null-terminated character string.
+*     mxlen
+*        The length of the "list" array.
+*     nlist
+*        Pointer to an integer in which to place the number of symbol
+*        values stored in the "list" array. This will be returned equal
+*        to zero if there are no non-blank characters in the supplied
+*        string. If there is one or more non-blank characters in "text",
+*        then the returned list will include any trailing spaces.
+*     list
+*        Pointer to an array in which to return the symbol numbers. The
+*        array should be at least "mxlen" elements long.
+*/
+
+
+/* Local Variables: */
+   int font;
+   int tlen;
+
+/* Assume we have no symbols. */
+   *nlist = 0;
+
+/* Check there is something to plot. */
+   if( astChrLen( text ) > 0 ) {
+
+/* Find the length of text that can be displayed. */
+      tlen = strlen( text );
+      if( tlen > mxlen ) tlen = mxlen;
+
+/* Get the current PGPLOT font. */
+      ccpgqcf( &font );
+
+/* Convert the supplied string into a list of symbol numbers */
+      ccgrsyds( list, nlist, text, tlen, font );
+   }
+}
+
+static Camera *getCamera( int check ){
+/*
+*+
+*  Name:
+*     getCamera
+
+*  Purpose:
+*     Return a pointer to the Camera structure for the current PGPLOT
+*     device.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     Camera getCamera( int check )
+
+*  Description:
+*     This function returns a pointer to a static structure that defines the
+*     position and orientation of the camera in 3D world coords. It can
+*     be used to transform positions from 3D world coordinates (x,y,z) to
+*     2D screen coordinates (h,r).
+
+*  Parameters:
+*     check
+*        If non-zero, a check will be made that the Camera has been
+*        initialised, and NULL will be returned if the Camera has not
+*        been initialsied. If "check" is zero, a pointer to the Camera
+*        is returned even if it has not been initialised.
+
+*  Returned Value:
+*     Pointer to the Camera, or NULL if an error occurs.
+*-
+*/
+
+/* Local Variables: */
+   int id;
+   Camera *cam = NULL;
+
+/* Get the pgplot current device identifier. Return NULL if no device is
+   currently open. */
+   ccpgqid( &id );
+   if( id > 0 && id <= MXDEV ) {
+
+/* Get a pointer to the required Camera structure. */
+      cam = cameras + id - 1;
+
+/* If required, check that the structure has been initialised. */
+      if( check && cam->ok_flag != CAMERA_OK ) cam = NULL;
+   }
+
+   return cam;
+}
+
+static int transform( Camera *cam, int n, float *x, float *y, float *z,
+                      float *h, float *r ){
+/*
+*+
+*  Name:
+*     transform
+
+*  Purpose:
+*     Transform positions from 3D world coords to 2D screen cooords.
+
+*  Synopsis:
+*     #include "grf3d.h"
+*     int transform( Camera *cam, int n, float *x, float *y, float *z,
+*                    float *h, float *r )
+
+*  Description:
+*     This function transforms a set of positions from 3D world
+*     coordinates (x,y,z) to 2D screen coordinates (h,r), using the
+*     supplied camera.
+
+*  Parameters:
+*     cam
+*        Pointer to a structure descibing the projection from 3D world
+*        coords to 2D screen coords. If NULL, the camera for the current
+*        PGPLOT device is used.
+*     n
+*        The number of positions to transform.
+*     x
+*        An array of "n" values for the "x" axis of the 3D world
+*        coordinate system.
+*     y
+*        An array of "n" values for the "y" axis of the 3D world
+*        coordinate system.
+*     z
+*        An array of "n" values for the "z" axis of the 3D world
+*        coordinate system.
+*     h
+*        An array to receive the "n" values for the "h" axis of the 2D
+*        screen coordinate system.
+*     r
+*        An array to receive the "n" values for the "r" axis of the 2D
+*        screen coordinate system.
+
+*  Returned Value:
+*     Zero if an error occurs. One otherwise.
+
+*-
+*/
+
+/* Local Variables: */
+   float dx, dy, dz, u, v, w, f;
+   int i;
+   int result = 0;
+
+/* If no camera was supplied use the camera for the current PGPLOT
+   device. */
+   if( ! cam ) cam = getCamera( 0 );
+
+/* Check we now have a usable camera */
+   if( cam && cam->ok_flag == CAMERA_OK ) {
+      result = 1;
+
+/* Loop round each position. */
+      for( i = 0; i < n; i++ ) {
+
+/* Offset from supplied position to the camera eye. */
+         dx = x[ i ] - (cam->eye_vector)[ 0 ];
+         dy = y[ i ] - (cam->eye_vector)[ 1 ];
+         dz = z[ i ] - (cam->eye_vector)[ 2 ];
+
+/* Get the representation of this vector in the (u,v,w) system. */
+         u = (cam->w2c_matrix)[ 0 ]*dx +
+             (cam->w2c_matrix)[ 1 ]*dy +
+             (cam->w2c_matrix)[ 2 ]*dz;
+
+         v = (cam->w2c_matrix)[ 3 ]*dx +
+             (cam->w2c_matrix)[ 4 ]*dy +
+             (cam->w2c_matrix)[ 5 ]*dz;
+
+         w = (cam->w2c_matrix)[ 6 ]*dx +
+             (cam->w2c_matrix)[ 7 ]*dy +
+             (cam->w2c_matrix)[ 8 ]*dz;
+
+/* Find the screen coords, using either a tangent plane or an
+   orothograhic projection. */
+         if( cam->screen_distance != 0.0 ) {
+            if( w != 0.0 ) {
+               f = cam->screen_distance/w;
+               h[ i ] = -f*u;
+               r[ i ] = f*v;
+            } else {
+               h[ i ] = FLT_MAX;
+               r[ i ] = FLT_MAX;
+            }
+         } else {
+            h[ i ] = -u;
+            r[ i ] = v;
+         }
+
+      }
+   }
+   return result;
+}
+
+
+/* Dot product of a pair of 3-vectors "a" and "b". */
+static float dotProduct( float *a, float *b ){
+   return a[ 0 ]*b[ 0 ] + a[ 1 ]*b[ 1 ] + a[ 2 ]*b[ 2 ];
+}
+
+/* Vector product of a pair of 3-vectors "a" and "b". */
+static void vectorProduct( float *a, float *b, float *c ){
+   c[ 0 ] = a[ 1 ]*b[ 2 ] - a[ 2 ]*b[ 1 ];
+   c[ 1 ] = a[ 2 ]*b[ 0 ] - a[ 0 ]*b[ 2 ];
+   c[ 2 ] = a[ 0 ]*b[ 1 ] - a[ 1 ]*b[ 0 ];
+}
+
+/* Vector from "b" to "a" (i.e. a minus b) . */
+static void vectorSub( float *a, float *b, float *c ){
+   c[ 0 ] = a[ 0 ] - b[ 0 ];
+   c[ 1 ] = a[ 1 ] - b[ 1 ];
+   c[ 2 ] = a[ 2 ] - b[ 2 ];
+}
+
+/* Normalises a vector to a unit length. Returns zero if the vector has
+   zero length, and 1 otherwise. */
+static int vectorNorm( float *a ){
+   float d;
+   d = vectorModulus( a );
+   if( d > 0.0 ) {
+      a[ 0 ] /= d;
+      a[ 1 ] /= d;
+      a[ 2 ] /= d;
+      return 1;
+   } else {
+      return 0;
+   }
+}
+
+/* Return the length of a vector. */
+static float vectorModulus( float *a ){
+   return sqrtf( a[ 0 ]*a[ 0 ] + a[ 1 ]*a[ 1 ] + a[ 2 ]*a[ 2 ] );
+}
+
+
+
+
+
+
+
+/* PGPLOT interface functions */
+/* ========================== */
+static void ccpgqclp(int *clip){
+   F77_INTEGER_TYPE CLIP;
+   F77_CALL(pgqclp)( INTEGER_ARG(&CLIP) );
+   *clip = (int) CLIP;
+}
+
+static void ccpgsclp(int clip){
+   F77_INTEGER_TYPE CLIP;
+   CLIP = (F77_INTEGER_TYPE) clip;
+   F77_CALL(pgsclp)( INTEGER_ARG(&CLIP) );
+}
+
+static void ccpgqid(int *id){
+   F77_INTEGER_TYPE ID;
+   F77_CALL(pgqid)( INTEGER_ARG(&ID) );
+   *id = (int) ID;
+}
+
+
+static void ccpgswin(float x1, float x2, float y1, float y2){
+   F77_REAL_TYPE X1;
+   F77_REAL_TYPE X2;
+   F77_REAL_TYPE Y1;
+   F77_REAL_TYPE Y2;
+
+   X1 = x1;
+   X2 = x2;
+   Y1 = y1;
+   Y2 = y2;
+
+   F77_CALL(pgswin)( REAL_ARG(&X1), REAL_ARG(&X2), REAL_ARG(&Y1),
+                     REAL_ARG(&Y2) );
+}
+
+static void ccpgline(int n, float xpts[], float ypts[] ){
+   F77_INTEGER_TYPE N;
+   F77_REAL_TYPE *XX;
+   F77_REAL_TYPE *YY;
+   int i;
+
+   XX = (F77_REAL_TYPE *) astMalloc( sizeof( F77_REAL_TYPE )*(size_t) n );
+   YY = (F77_REAL_TYPE *) astMalloc( sizeof( F77_REAL_TYPE )*(size_t) n );
+
+   if( astOK ){
+
+      for( i = 0; i < n; i++ ){
+         XX[ i ] = (F77_REAL_TYPE) xpts[ i ];
+         YY[ i ] = (F77_REAL_TYPE) ypts[ i ];
+      }
+
+      N = (F77_INTEGER_TYPE) n;
+
+      F77_CALL(pgline)( INTEGER_ARG(&N), REAL_ARRAY_ARG(XX),
+                        REAL_ARRAY_ARG(YY) );
+
+      XX = (F77_REAL_TYPE *) astFree( (void *) XX );
+      YY = (F77_REAL_TYPE *) astFree( (void *) YY );
+   }
+}
+
+static void ccpgpoly(int n, float xpts[], float ypts[] ){
+   F77_INTEGER_TYPE N;
+   F77_REAL_TYPE *XX;
+   F77_REAL_TYPE *YY;
+   int i;
+
+   XX = (F77_REAL_TYPE *) astMalloc( sizeof( F77_REAL_TYPE )*(size_t) n );
+   YY = (F77_REAL_TYPE *) astMalloc( sizeof( F77_REAL_TYPE )*(size_t) n );
+
+   if( astOK ){
+
+      for( i = 0; i < n; i++ ){
+         XX[ i ] = (F77_REAL_TYPE) xpts[ i ];
+         YY[ i ] = (F77_REAL_TYPE) ypts[ i ];
+      }
+
+      N = (F77_INTEGER_TYPE) n;
+
+      F77_CALL(pgpoly)( INTEGER_ARG(&N), REAL_ARRAY_ARG(XX),
+                        REAL_ARRAY_ARG(YY) );
+
+      XX = (F77_REAL_TYPE *) astFree( (void *) XX );
+      YY = (F77_REAL_TYPE *) astFree( (void *) YY );
+   }
+}
+
+static void ccpgqwin(float *x1, float *x2, float *y1, float *y2){
+   F77_REAL_TYPE X1;
+   F77_REAL_TYPE X2;
+   F77_REAL_TYPE Y1;
+   F77_REAL_TYPE Y2;
+
+   F77_CALL(pgqwin)( REAL_ARG(&X1), REAL_ARG(&X2), REAL_ARG(&Y1),
+                     REAL_ARG(&Y2) );
+   *x1 = (float) X1;
+   *x2 = (float) X2;
+   *y1 = (float) Y1;
+   *y2 = (float) Y2;
+}
+
+static void ccpgqch(float *ch){
+   F77_REAL_TYPE CH;
+   F77_CALL(pgqch)( REAL_ARG(&CH) );
+   *ch = (float) CH;
+}
+
+static void ccpgqcf(int *cf){
+   F77_INTEGER_TYPE CF;
+   F77_CALL(pgqcf)( INTEGER_ARG(&CF) );
+   *cf = (int) CF;
+}
+
+static void ccgrsyds( int *list, int *nlist, const char *text, int tlen,
+                      int font ){
+   F77_INTEGER_TYPE *LIST;
+   F77_INTEGER_TYPE NLIST;
+   DECLARE_CHARACTER(LTEXT,MXSTRLEN);
+   F77_INTEGER_TYPE FONT;
+   int ftext_length;
+   int i;
+
+   ftext_length = tlen;
+   if( ftext_length > LTEXT_length ) ftext_length = LTEXT_length;
+   astStringExport( text, LTEXT, ftext_length );
+
+   LIST = (F77_INTEGER_TYPE *) astMalloc( sizeof( F77_INTEGER_TYPE )*(size_t) ftext_length );
+
+   if( astOK ){
+
+      FONT = (F77_INTEGER_TYPE) font;
+
+      F77_CALL(grsyds)( INTEGER_ARRAY_ARG(LIST), INTEGER_ARG(&NLIST),
+                        CHARACTER_ARG(LTEXT), INTEGER_ARG(&FONT)
+                        TRAIL_ARG(ftext) );
+
+      *nlist = (int) NLIST;
+      for( i = 0; i < ftext_length; i++ ){
+         list[ i ] = (int) LIST[ i ];
+      }
+
+      LIST = (F77_INTEGER_TYPE *) astFree( (void *) LIST );
+   }
+}
+
+static void ccgrsymk( int type, int font, int *symbol ){
+   F77_INTEGER_TYPE TYPE;
+   F77_INTEGER_TYPE FONT;
+   F77_INTEGER_TYPE SYMBOL;
+
+   TYPE = (F77_INTEGER_TYPE) type;
+   FONT = (F77_INTEGER_TYPE) font;
+   F77_CALL(grsymk)( INTEGER_ARG(&TYPE), INTEGER_ARG(&FONT),
+                     INTEGER_ARG(&SYMBOL) );
+   *symbol = (int) SYMBOL;
+}
+
+
+static void ccgrsyxd( int symbol, int *xygrid, int *unused ){
+   F77_INTEGER_TYPE SYMBOL;
+   DECLARE_INTEGER_ARRAY(XYGRID,300);
+   F77_LOGICAL_TYPE UNUSED;
+   int i;
+
+   SYMBOL = (F77_INTEGER_TYPE) symbol;
+   F77_CALL(grsyxd)( INTEGER_ARG(&SYMBOL), INTEGER_ARRAY_ARG(XYGRID),
+                     LOGICAL_ARG(&UNUSED) );
+
+   *unused = ( UNUSED == F77_TRUE );
+   for( i = 0; i < 5; i++ ) xygrid[ i ] = (int) XYGRID[ i ];
+   for( ; i < 300; i++ ){
+      xygrid[ i ] = (int) XYGRID[ i ];
+      i++;
+      if( ( xygrid[ i ] = (int) XYGRID[ i ] ) == -64 ) break;
+   }
+}
+
+static void ccpgupdt( void ){
+   F77_CALL(pgupdt)();
+}
+
+static void ccpgqci(int *ci){
+   F77_INTEGER_TYPE CI;
+   F77_CALL(pgqci)( INTEGER_ARG(&CI) );
+   *ci = (int) CI;
+}
+
+static void ccpgqls(int *ls){
+   F77_INTEGER_TYPE LS;
+   F77_CALL(pgqls)( INTEGER_ARG(&LS) );
+   *ls = (int) LS;
+}
+
+static void ccpgqlw(int *lw){
+   F77_INTEGER_TYPE LW;
+   F77_CALL(pgqlw)( INTEGER_ARG(&LW) );
+   *lw = (int) LW;
+}
+
+static void ccpgscf(int cf){
+   F77_INTEGER_TYPE CF;
+   CF = (F77_INTEGER_TYPE) cf;
+   F77_CALL(pgscf)( INTEGER_ARG(&CF) );
+}
+
+static void ccpgsch(float ch){
+   F77_REAL_TYPE CH;
+   CH = (F77_REAL_TYPE) ch;
+   F77_CALL(pgsch)( REAL_ARG(&CH) );
+}
+
+static void ccpgsci(int ci){
+   F77_INTEGER_TYPE CI;
+   CI = (F77_INTEGER_TYPE) ci;
+   F77_CALL(pgsci)( INTEGER_ARG(&ci) );
+}
+
+static void ccpgsls(int ls){
+   F77_INTEGER_TYPE LS;
+   LS = (F77_INTEGER_TYPE) ls;
+   F77_CALL(pgsls)( INTEGER_ARG(&LS) );
+}
+
+static void ccpgslw(int lw){
+   F77_INTEGER_TYPE LW;
+   LW = (F77_INTEGER_TYPE) lw;
+   F77_CALL(pgslw)( INTEGER_ARG(&LW) );
+}
+
+static void ccpgqvsz(int units, float *x1, float *x2, float *y1, float *y2){
+   F77_INTEGER_TYPE UNITS;
+   F77_REAL_TYPE X1;
+   F77_REAL_TYPE X2;
+   F77_REAL_TYPE Y1;
+   F77_REAL_TYPE Y2;
+
+   UNITS = (F77_INTEGER_TYPE) units;
+   F77_CALL(pgqvsz)( INTEGER_ARG(&UNITS), REAL_ARG(&X1), REAL_ARG(&X2),
+                    REAL_ARG(&Y1), REAL_ARG(&Y2) );
+   *x1 = (float) X1;
+   *x2 = (float) X2;
+   *y1 = (float) Y1;
+   *y2 = (float) Y2;
+}
+
+
+
+/* Fortran interfaces for public functions in this module. */
+/* ======================================================= */
+
+
+F77_LOGICAL_FUNCTION(pg3d_findnearest)( INTEGER(N),
+                                        REAL_ARRAY(X),
+                                        REAL_ARRAY(Y),
+                                        REAL_ARRAY(Z),
+                                        INTEGER(ICLOSE) ){
+   GENPTR_INTEGER(N)
+   GENPTR_REAL_ARRAY(X)
+   GENPTR_REAL_ARRAY(Y)
+   GENPTR_REAL_ARRAY(Z)
+   GENPTR_INTEGER(ICLOSE)
+   return PG3DFindNearest( *N, X, Y, Z, ICLOSE ) ? F77_TRUE : F77_FALSE;
+}
+
+
+
+F77_LOGICAL_FUNCTION(pg3d_setcamera)( REAL_ARRAY(EYE),
+                                        REAL_ARRAY(TARGET),
+                                        REAL_ARRAY(UP),
+                                        REAL(SCREEN) ){
+   GENPTR_REAL_ARRAY(EYE)
+   GENPTR_REAL_ARRAY(TARGET)
+   GENPTR_REAL_ARRAY(UP)
+   GENPTR_REAL(SCREEN)
+   return PG3DSetCamera( EYE, TARGET, UP, *SCREEN ) ? F77_TRUE : F77_FALSE;
+}
+
+
+F77_LOGICAL_FUNCTION(pg3d_autocamera)( REAL_ARRAY(LBND),
+                                         REAL_ARRAY(UBND) ){
+   GENPTR_REAL_ARRAY(LBND)
+   GENPTR_REAL_ARRAY(UBND)
+   return PG3DAutoCamera( LBND, UBND ) ? F77_TRUE : F77_FALSE;
+}
+
+F77_LOGICAL_FUNCTION(pg3d_seteye)( REAL_ARRAY(EYE) ){
+   GENPTR_REAL_ARRAY(EYE)
+   return PG3DSetEye( EYE ) ? F77_TRUE : F77_FALSE;
+}
+
+F77_LOGICAL_FUNCTION(pg3d_setup)( REAL_ARRAY(UP) ){
+   GENPTR_REAL_ARRAY(UP)
+   return PG3DSetUp( UP ) ? F77_TRUE : F77_FALSE;
+}
+
+F77_LOGICAL_FUNCTION(pg3d_rotateeye)( INTEGER(DIR), REAL(ANGLE) ){
+   GENPTR_INTEGER(DIR)
+   GENPTR_REAL(ANGLE)
+   return PG3DRotateEye( *DIR, *ANGLE ) ? F77_TRUE : F77_FALSE;
+}
+
+F77_LOGICAL_FUNCTION(pg3d_forward)( REAL(DISTANCE) ){
+   GENPTR_REAL(DISTANCE)
+   return PG3DForward( *DISTANCE ) ? F77_TRUE : F77_FALSE;
+}
+
+F77_LOGICAL_FUNCTION(ast_g3dmark)( INTEGER(N),
+                                   REAL_ARRAY(X),
+                                   REAL_ARRAY(Y),
+                                   REAL_ARRAY(Z),
+                                   INTEGER(TYPE),
+                                   REAL_ARRAY(NORM)){
+   GENPTR_INTEGER(N)
+   GENPTR_REAL_ARRAY(X)
+   GENPTR_REAL_ARRAY(Y)
+   GENPTR_REAL_ARRAY(Z)
+   GENPTR_INTEGER(TYPE)
+   GENPTR_REAL_ARRAY(NORM)
+   return astG3DMark( *N, X, Y, Z, *TYPE, NORM ) ? F77_TRUE : F77_FALSE;
+
+}
+
+F77_LOGICAL_FUNCTION(ast_g3dline)( INTEGER(N),
+                                   REAL_ARRAY(X),
+                                   REAL_ARRAY(Y),
+                                   REAL_ARRAY(Z) ){
+   GENPTR_INTEGER(N)
+   GENPTR_REAL_ARRAY(X)
+   GENPTR_REAL_ARRAY(Y)
+   GENPTR_REAL_ARRAY(Z)
+   return astG3DLine( *N, X, Y, Z ) ? F77_TRUE : F77_FALSE;
+
+}
+
+
+F77_INTEGER_FUNCTION(ast_g3dtext)( CHARACTER(TEXT),
+                                   REAL_ARRAY(REF),
+                                   CHARACTER(JUST),
+                                   REAL_ARRAY(UP),
+                                   REAL_ARRAY(NORM)
+                                   TRAIL(TEXT)
+                                   TRAIL(JUST) ){
+   GENPTR_CHARACTER(TEXT)
+   GENPTR_REAL_ARRAY(REF)
+   GENPTR_CHARACTER(JUST)
+   GENPTR_REAL_ARRAY(UP)
+   GENPTR_REAL_ARRAY(NORM)
+   F77_INTEGER_TYPE(RESULT);
+   char *text, *just, *p;
+
+   text = astString( TEXT, TEXT_length );
+   just = astString( JUST, JUST_length );
+
+/* Ignore trailing spaces in the text */
+   p = text + TEXT_length;
+   while( !*p || *p == ' ' ) *(p--) = 0;
+
+   if( astOK ) {
+      RESULT = (F77_INTEGER_TYPE) astG3DText( text, REF, just, UP, NORM );
+   } else {
+      RESULT = 0;
+   }
+
+   (void) astFree( text );
+   (void) astFree( just );
+
+   return RESULT;
+}
+
+F77_INTEGER_FUNCTION(ast_g3dtxext)( CHARACTER(TEXT),
+                                    REAL_ARRAY(REF),
+                                    CHARACTER(JUST),
+                                    REAL_ARRAY(UP),
+                                    REAL_ARRAY(NORM),
+                                    REAL_ARRAY(XB),
+                                    REAL_ARRAY(YB),
+                                    REAL_ARRAY(ZB),
+                                    REAL_ARRAY(BL)
+                                    TRAIL(TEXT)
+                                    TRAIL(JUST) ){
+   GENPTR_CHARACTER(TEXT)
+   GENPTR_REAL_ARRAY(REF)
+   GENPTR_CHARACTER(JUST)
+   GENPTR_REAL_ARRAY(UP)
+   GENPTR_REAL_ARRAY(NORM)
+   GENPTR_REAL_ARRAY(XB)
+   GENPTR_REAL_ARRAY(YB)
+   GENPTR_REAL_ARRAY(ZB)
+   GENPTR_REAL_ARRAY(BL)
+   F77_INTEGER_TYPE(RESULT);
+   char *text, *just, *p;
+
+   text = astString( TEXT, TEXT_length );
+   just = astString( JUST, JUST_length );
+
+/* Ignore trailing spaces in the text */
+   p = text + TEXT_length;
+   while( !*p || *p == ' ' ) *(p--) = 0;
+
+   if( astOK ) {
+      RESULT = (F77_INTEGER_TYPE) astG3DTxExt( text, REF, just, UP, NORM,
+                                               XB, YB, ZB, BL );
+   } else {
+      RESULT = 0;
+   }
+
+   (void) astFree( text );
+   (void) astFree( just );
+
+   return RESULT;
+}
+
+
diff --git a/ast/grf_2.0.c b/ast/grf_2.0.c
new file mode 100644
index 0000000..170c191
--- /dev/null
+++ b/ast/grf_2.0.c
@@ -0,0 +1,101 @@
+/*
+*  Name:
+*     grf_2.0.c
+
+*  Purpose:
+*     Implement the grf module required by AST V2.0 if no graphics system
+*     is available.
+
+*  Description:
+*     This file implements the low level graphics functions required
+*     by the rest of AST V2.0, by reporting errors when called.
+
+*  Inheritance:
+*     This module is not a class and does not inherit.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     23-OCT-1996 (DSB):
+*        Original version.
+*     13-NOV-1996 (DSB):
+*        Modified to issue error messages using astError instead of printf.
+*     23-NOV-2004 (DSB):
+*        Renamed from grf_null.c
+*/
+
+/* Header files */
+/* ============ */
+#include "grf.h"           /* Declare the functions in this module */
+#include "error.h"         /* AST error reporting facilities */
+#include "ast_err.h"       /* AST error codes */
+
+/* Function Prototypes */
+/* =================== */
+static void Report( const char * );
+
+/* Function definitions */
+/* ==================== */
+int astGFlush( void ){
+   Report( "astGFlush");
+   return 0;
+}
+
+int astGLine( int n, const float *x, const float *y ){
+   Report( "astGLine" );
+   return 0;
+}
+
+int astGQch( float *chv, float *chh ){
+   Report( "astGQch" );
+   return 0;
+}
+
+int astGMark( int n, const float *x, const float *y, int type ){
+   Report( "astGMark" );
+   return 0;
+}
+
+int astGText( const char *text, float x, float y, const char *just,
+              float upx, float upy ){
+   Report( "astGText" );
+   return 0;
+}
+
+int astGTxExt( const char *text, float x, float y, const char *just,
+               float upx, float upy, float *xb, float *yb ){
+   Report( "astGTxExt" );
+   return 0;
+}
+
+int astGAttr( int attr, double value, double *old_value, int prim ){
+   Report( "astGAttr" );
+   return 0;
+}
+
+static void Report( const char *name ){
+   astError( AST__GRFER, "%s: No graphics facilities are available.", name );
+   astError( AST__GRFER, "Re-link using an option such as '-pgplot' with "
+             "the ast_link script." );
+}
diff --git a/ast/grf_3.2.c b/ast/grf_3.2.c
new file mode 100644
index 0000000..d6fc1a1
--- /dev/null
+++ b/ast/grf_3.2.c
@@ -0,0 +1,74 @@
+/*
+*  Name:
+*     grf_3.2.c
+
+*  Purpose:
+*     Implement the grf module required by AST V3.2 if no graphics system
+*     is available.
+
+*  Description:
+*     This file implements the low level graphics functions required
+*     by the rest of AST V3.2, except for those already defined in
+*     grf_2.0.c (i.e. those needed by AST V2.0). These implementations
+*     simply report an error when called.
+
+*  Inheritance:
+*     This module is not a class and does not inherit.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     23-NOV-2004 (DSB):
+*        Original version.
+*/
+
+/* Header files */
+/* ============ */
+#include "grf.h"           /* Declare the functions in this module */
+#include "error.h"         /* AST error reporting facilities */
+#include "ast_err.h"       /* AST error codes */
+
+/* Function Prototypes */
+/* =================== */
+static void Report( const char * );
+
+/* Function definitions */
+/* ==================== */
+int astGScales( float *alpha, float *beta ){
+   Report( "astGScales" );
+   return 0;
+}
+
+int astGCap( int cap, int value ){
+   return 0;
+}
+
+static void Report( const char *name ){
+   astError( AST__GRFER, "%s: The graphics facilities implement by %s "
+             "(introduced at AST V3.2) are needed but are unavailable.",
+             name, name );
+   astError( AST__GRFER, "Re-link using a suitable option such as '-pgplot' "
+             "with the ast_link script, or add an implementation of this "
+             "function to your 'grf' module." );
+}
diff --git a/ast/grf_5.6.c b/ast/grf_5.6.c
new file mode 100644
index 0000000..3a77f58
--- /dev/null
+++ b/ast/grf_5.6.c
@@ -0,0 +1,77 @@
+/*
+*  Name:
+*     grf_5.6.c
+
+*  Purpose:
+*     Implement the grf module required by AST V5.6 if no graphics system
+*     is available.
+
+*  Description:
+*     This file implements the low level graphics functions required
+*     by the rest of AST V5.6, except for those already defined in
+*     earlier grf_xxx.c files (i.e. those needed by ealier versions
+*     of AST). These implementations simply report an error when called.
+
+*  Inheritance:
+*     This module is not a class and does not inherit.
+
+*  Copyright:
+*     Copyright (C) 2011 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     4-MAR-2011 (DSB):
+*        Original version.
+*/
+
+/* Header files */
+/* ============ */
+#include "grf.h"           /* Declare the functions in this module */
+#include "error.h"         /* AST error reporting facilities */
+#include "ast_err.h"       /* AST error codes */
+
+/* Function Prototypes */
+/* =================== */
+static void Report( const char * );
+
+/* Function definitions */
+/* ==================== */
+int astGBBuf( void ){
+   Report( "astGBBuf" );
+   return 0;
+}
+
+int astGEBuf( void ){
+   Report( "astGEBuf" );
+   return 0;
+}
+
+static void Report( const char *name ){
+   astError( AST__GRFER, "%s: The graphics facilities implement by %s "
+             "(introduced at AST V5.6) are needed but are unavailable.",
+             name, name );
+   astError( AST__GRFER, "Re-link using a suitable option such as '-pgplot' "
+             "with the ast_link script, or add an implementation of this "
+             "function to your 'grf' module." );
+}
+
+
diff --git a/ast/grf_pgplot.c b/ast/grf_pgplot.c
new file mode 100644
index 0000000..d0b1193
--- /dev/null
+++ b/ast/grf_pgplot.c
@@ -0,0 +1,1494 @@
+/*
+*  Name:
+*     grf_pgplot.c
+
+*  Purpose:
+*     Implement the grf module using the PGPLOT graphics system.
+
+*  Description:
+*     This file implements the low level graphics functions required
+*     by the rest of AST, by calling suitable PGPLOT functions (the
+*     FORTRAN PGPLOT interface is used).
+*
+*     This file can be used as a template for the development of
+*     similar modules to support alternative graphics systems.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     27-JUN-1996 (DSB):
+*        Original version.
+*     13-NOV-1996 (DSB):
+*        Use C wrappers for PGPLOT functions.
+*     15-NOV-1996 (RFWS):
+*        Merged the C interface to PGPLOT into this file so that the
+*        interface functions can be static.
+*     7-OCT-1997 (DSB):
+*        Corrected astGText and astGTxExt, by including a check for
+*        reversed axes. Previously, the up-vector was used as supplied
+*        even if the axes had been reversed.
+*     15-OCT-1997 (DSB):
+*        o  Corrected astGText and astGTxExt to take account of non-equal
+*        scales on the two axes.
+*        o  Modified astGTxExt so that it includes any leading or trailing
+*        spaces in the returned box.
+*        o  Added astGAxScale.
+*     28-OCT-1998 (DSB):
+*        o  Changed interpretation of the Width attribute from inches, to
+*        a multiple of a small line width.
+*        o  Wrapper for pgplot F77 subroutine PGQVSZ added.
+*     30-JAN-2004 (DSB):
+*        o  Added GCap
+*        o  Renamed GAxScale as GScales
+*     4-MAR-2011 (DSB):
+*        Added astGBBuf and astGEBuf.
+*/
+
+/* Macros */
+/* ====== */
+#define MXSTRLEN 80              /* String length at which truncation starts
+                                    within pgqtxt and pgptxt. */
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+#include "f77.h"                 /* FORTRAN <-> C interface macros (SUN/209) */
+#include "c2f77.h"               /* C to FORTRAN interface functions */
+#include "pointset.h"            /* Defines AST__BAD */
+#include "memory.h"              /* Memory allocation facilities */
+#include "error.h"               /* Error reporting facilities */
+#include "grf.h"                 /* Interface to this module */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <string.h>
+
+/* Constants. */
+/* ========== */
+#define R2D 57.29578             /* Radians to degrees factor */
+
+/* Function Prototypes. */
+/* ==================== */
+/* These define a local C interface to the PGPLOT library. */
+static void ccpgline(int n, float xpts[], float ypts[] );
+static void ccpgpt(int n, float xpts[], float ypts[], int symbol);
+static void ccpgptxt(float x, float y, float angle, float fjust, char *text );
+static void ccpgqcf(int *cf);
+static void ccpgqch(float *ch);
+static void ccpgqci(int *ci);
+static void ccpglen(int units, char *text, float *xl, float *yl);
+static void ccpgqcs(int units, float *xch, float *ych);
+static void ccpgqls(int *ls);
+static void ccpgqlw(int *lw);
+static void ccpgqtbg(int *tbci);
+static void ccpgqtxt(float x, float y, float angle, float fjust, char *text, float xbox[], float ybox[]);
+static void ccpgqvp(int units, float *x1, float *x2, float *y1, float *y2);
+static void ccpgqvsz(int units, float *x1, float *x2, float *y1, float *y2);
+static void ccpgqwin(float *x1, float *x2, float *y1, float *y2);
+static void ccpgscf(int cf);
+static void ccpgsch(float ch);
+static void ccpgsci(int ci);
+static void ccpgsls(int ls);
+static void ccpgslw(int lw);
+static void ccpgstbg(int tbci);
+static void ccpgupdt( void );
+static void ccpgbbuf( void );
+static void ccpgebuf( void );
+
+/* These describe the native Fortran interface to the PGPLOT library. The
+   macros used come from the "f77.h" include file. */
+F77_SUBROUTINE(pgline)( INTEGER(n), REAL_ARRAY(x), REAL_ARRAY(y) );
+F77_SUBROUTINE(pgpt)( INTEGER(n), REAL_ARRAY(x), REAL_ARRAY(y), INTEGER(TYPE) );
+F77_SUBROUTINE(pgptxt)( REAL(x), REAL(y), REAL(angle), REAL(fjust), CHARACTER(text) TRAIL(text) );
+F77_SUBROUTINE(pgqcf)( INTEGER(ival) );
+F77_SUBROUTINE(pgqch)( REAL(rval) );
+F77_SUBROUTINE(pgqci)( INTEGER(ival) );
+F77_SUBROUTINE(pgqcs)( INTEGER(units), REAL(chv), REAL(chh) );
+F77_SUBROUTINE(pglen)( INTEGER(units), CHARACTER(text), REAL(xl), REAL(yl) TRAIL(text) );
+F77_SUBROUTINE(pgqls)( INTEGER(ival) );
+F77_SUBROUTINE(pgqlw)( INTEGER(ival) );
+F77_SUBROUTINE(pgqtbg)( INTEGER(tbg) );
+F77_SUBROUTINE(pgqtxt)( REAL(x), REAL(y), REAL(angle), REAL(fjust), CHARACTER(text), REAL_ARRAY(xbox), REAL_ARRAY(ybox) TRAIL(text) );
+F77_SUBROUTINE(pgqvp)( INTEGER(units), REAL(vx1), REAL(vx2), REAL(vy1), REAL(vy2) );
+F77_SUBROUTINE(pgqvsz)( INTEGER(units), REAL(x1), REAL(x2), REAL(y1), REAL(y2) );
+F77_SUBROUTINE(pgqwin)( REAL(wx1), REAL(wx2), REAL(wy1), REAL(wy2) );
+F77_SUBROUTINE(pgscf)( INTEGER(ival) );
+F77_SUBROUTINE(pgsch)( REAL(rval) );
+F77_SUBROUTINE(pgsci)( INTEGER(ival) );
+F77_SUBROUTINE(pgsls)( INTEGER(ival) );
+F77_SUBROUTINE(pgslw)( INTEGER(ival) );
+F77_SUBROUTINE(pgstbg)( INTEGER(tbg) );
+F77_SUBROUTINE(pgupdt)( );
+F77_SUBROUTINE(pgbbuf)( );
+F77_SUBROUTINE(pgebuf)( );
+
+/* Externally visible functions. */
+/* ============================= */
+/* These implement the "grf" interface in terms of the local C interface
+   to PGPLOT. */
+int astGBBuf( void ){
+/*
+*+
+*  Name:
+*     astGBBuf
+
+*  Purpose:
+*     Start a new graphics buffering context.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astGBBuf( void )
+
+*  Description:
+*     This function begins saving graphical output commands in an
+*     internal buffer; the commands are held until a matching astGEBuf
+*     call (or until the buffer is emptied by astGFlush). This can
+*     greatly improve the efficiency of some graphics systems. astGBBuf
+*     increments an internal counter, while astGEBuf decrements this
+*     counter and flushes the buffer to the output device when the
+*     counter drops to zero.  astGBBuf and astGEBuf calls should always
+*     be paired.
+
+*  Parameters:
+*     None.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*-
+*/
+   ccpgbbuf();
+   return 1;
+}
+
+int astGEBuf( void ){
+/*
+*+
+*  Name:
+*     astGEBuf
+
+*  Purpose:
+*     End a graphics buffering context.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astGEBuf( void )
+
+*  Description:
+*     This function marks the end of a batch of graphical output begun
+*     with the last call of astGBBuf.  astGBBuf and astGEBUF calls should
+*     always be paired. Each call to astGBBuf increments a counter, while
+*     each call to astGEBuf decrements the counter. When the counter
+*     reaches 0, the batch of output is written on the output device.
+
+*  Parameters:
+*     None.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*-
+*/
+   ccpgebuf();
+   return 1;
+}
+
+int astGFlush( void ){
+/*
+*+
+*  Name:
+*     astGFlush
+
+*  Purpose:
+*     Flush all pending graphics to the output device.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astGFlush( void )
+
+*  Description:
+*     This function ensures that the display device is up-to-date,
+*     by flushing any pending graphics to the output device.
+
+*  Parameters:
+*     None.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*-
+*/
+
+   ccpgupdt();
+   return 1;
+}
+
+int astGCap( int cap, int value ){
+/*
+*+
+*  Name:
+*     astGCap
+
+*  Purpose:
+*     Indicate if this grf module has a given capability.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astGCap( int cap, int value )
+
+*  Description:
+*     This function is called by the AST Plot class to determine if the
+*     grf module has a given capability, as indicated by the "cap"
+*     argument.
+
+*  Parameters:
+*     cap
+*        The capability being inquired about. This will be one of the
+*        following constants defined in grf.h:
+*
+*        GRF__SCALES: This function should return a non-zero value if
+*        it implements the astGScales function, and zero otherwise. The
+*        supplied "value" argument should be ignored.
+*
+*        GRF__MJUST: This function should return a non-zero value if
+*        the astGText and astGTxExt functions recognise "M" as a
+*        character in the justification string. If the first character of
+*        a justification string is "M", then the text should be justified
+*        with the given reference point at the bottom of the bounding box.
+*        This is different to "B" justification, which requests that the
+*        reference point be put on the baseline of the text, since some
+*        characters hang down below the baseline. If the astGText or
+*        astGTxExt function cannot differentiate between "M" and "B",
+*        then this function should return zero, in which case "M"
+*        justification will never be requested by Plot. The supplied
+*        "value" argument should be ignored.
+*
+*        GRF__ESC: This function should return a non-zero value if the
+*        astGText and astGTxExt functions can recognise and interpret
+*        graphics escape sequences within the supplied string. These
+*        escape sequences are described below. Zero should be returned
+*        if escape sequences cannot be interpreted (in which case the
+*        Plot class will interpret them itself if needed). The supplied
+*        "value" argument should be ignored only if escape sequences cannot
+*        be interpreted by astGText and astGTxExt. Otherwise, "value"
+*        indicates whether astGText and astGTxExt should interpret escape
+*        sequences in subsequent calls. If "value" is non-zero then
+*        escape sequences should be interpreted by astGText and
+*        astGTxExt. Otherwise, they should be drawn as literal text.
+
+*  Returned Value:
+*     The return value, as described above. Zero should be returned if
+*     the supplied capability is not recognised.
+
+*  Escape Sequences:
+*     Escape sequences are introduced into the text string by a percent
+*     "%" character. The following escape sequences are currently recognised
+*     ("..." represents a string of one or more decimal digits):
+*
+*       %%      - Print a literal "%" character (type GRF__ESPER ).
+*
+*       %^...+  - Draw subsequent characters as super-scripts. The digits
+*                 "..." give the distance from the base-line of "normal"
+*                 text to the base-line of the super-script text, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text (type GRF__ESSUP ).
+*       %^+     - Draw subsequent characters with the normal base-line.
+*
+*       %v...+  - Draw subsequent characters as sub-scripts. The digits
+*                 "..." give the distance from the base-line of "normal"
+*                 text to the base-line of the sub-script text, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text (type GRF__ESSUB ).
+*
+*       %v+     - Draw subsequent characters with the normal base-line
+*                 (equivalent to %^+).
+*
+*       %>...+  - Leave a gap before drawing subsequent characters.
+*                 The digits "..." give the size of the gap, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text (type GRF__ESGAP ).
+*
+*       %<...+  - Move backwards before drawing subsequent characters.
+*                 The digits "..." give the size of the movement, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text (type GRF_ESBAC).
+*
+*       %s...+  - Change the Size attribute for subsequent characters. The
+*                 digits "..." give the new Size as a fraction of the
+*                 "normal" Size, scaled so that a value of "100" corresponds
+*                 to 1.0  (type GRF__ESSIZ ).
+*
+*       %s+     - Reset the Size attribute to its "normal" value.
+*
+*       %w...+  - Change the Width attribute for subsequent characters. The
+*                 digits "..." give the new width as a fraction of the
+*                 "normal" Width, scaled so that a value of "100" corresponds
+*                 to 1.0  (type GRF__ESWID ).
+*
+*       %w+     - Reset the Size attribute to its "normal" value.
+*
+*       %f...+  - Change the Font attribute for subsequent characters. The
+*                 digits "..." give the new Font value  (type GRF__ESFON ).
+*
+*       %f+     - Reset the Font attribute to its "normal" value.
+*
+*       %c...+  - Change the Colour attribute for subsequent characters. The
+*                 digits "..." give the new Colour value  (type GRF__ESCOL ).
+*
+*       %c+     - Reset the Colour attribute to its "normal" value.
+*
+*       %t...+  - Change the Style attribute for subsequent characters. The
+*                 digits "..." give the new Style value  (type GRF__ESSTY ).
+*
+*       %t+     - Reset the Style attribute to its "normal" value.
+*
+*       %-      - Push the current graphics attribute values onto the top of
+*                 the stack - see "%+" (type GRF__ESPSH).
+*
+*       %+      - Pop attributes values of the top the stack - see "%-". If
+*                 the stack is empty, "normal" attribute values are restored
+*                 (type GRF__ESPOP).
+*
+*     The astFindEscape function (in libast.a) can be used to locate escape
+*     sequences within a text string. It has the following signature:
+*
+*     #include "plot.h"
+*     int astFindEscape( const char *text, int *type, int *value, int *nc )
+*
+*     Parameters:
+*        text
+*           Pointer to the string to be checked.
+*        type
+*           Pointer to a location at which to return the type of escape
+*           sequence. Each type is identified by a symbolic constant defined
+*           in grf.h and is indicated in the above section. The returned value
+*           is undefined if the supplied text does not begin with an escape
+*           sequence.
+*        value
+*           Pointer to a lcation at which to return the integer value
+*           associated with the escape sequence. All usable values will be
+*           positive. Zero is returned if the escape sequence has no associated
+*           integer. A value of -1 indicates that the attribute identified by
+*           "type" should be reset to its "normal" value (as established using
+*           the astGAttr function, etc). The returned value is undefined if
+*           the supplied text does not begin with an escape sequence.
+*        nc
+*           Pointer to a location at which to return the number of
+*           characters read by this call. If the text starts with an escape
+*           sequence, the returned value will be the number of characters in
+*           the escape sequence. Otherwise, the returned value will be the
+*           number of characters prior to the first escape sequence, or the
+*           length of the supplied text if no escape sequence is found.
+
+*     Returned Value:
+*        A non-zero value is returned if the supplied text starts with a
+*        graphics escape sequence, and zero is returned otherwise.
+
+*-
+*/
+
+   int result = 0;
+   if( cap == GRF__SCALES ) result = 1;
+   return result;
+}
+
+int astGLine( int n, const float *x, const float *y ){
+/*
+*+
+*  Name:
+*     astGLine
+
+*  Purpose:
+*     Draw a polyline (i.e. a set of connected lines).
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astGLine( int n, const float *x, const float *y )
+
+*  Description:
+*     This function displays lines joining the given positions.
+
+*  Parameters:
+*     n
+*        The number of positions to be joined together.
+*     x
+*        A pointer to an array holding the "n" x values.
+*     y
+*        A pointer to an array holding the "n" y values.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     -  Nothing is done if "n" is less than 2, or if a NULL pointer is
+*     given for either "x" or "y".
+
+*-
+*/
+
+   if( n > 1 && x && y ) ccpgline( n, (float *) x, (float *) y );
+   return 1;
+}
+
+int astGMark( int n, const float *x, const float *y, int type ){
+/*
+*+
+*  Name:
+*     astGMark
+
+*  Purpose:
+*     Draw a set of markers.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astGMark( int n, const float *x, const float *y, int type )
+
+*  Description:
+*     This function displays markers at the given positions.
+
+*  Parameters:
+*     n
+*        The number of markers to draw.
+*     x
+*        A pointer to an array holding the "n" x values.
+*     y
+*        A pointer to an array holding the "n" y values.
+*     type
+*        An integer which can be used to indicate the type of marker symbol
+*        required.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     -  Nothing is done if "n" is less than 1, or if a NULL pointer is
+*     given for either "x" or "y".
+
+*-
+*/
+
+   if( n > 0 && x && y ) ccpgpt( n, (float *) x, (float *) y, type );
+   return 1;
+}
+
+int astGText( const char *text, float x, float y, const char *just,
+              float upx, float upy ){
+/*
+*+
+*  Name:
+*     astGText
+
+*  Purpose:
+*     Draw a character string.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astGText( const char *text, float x, float y, const char *just,
+*                   float upx, float upy )
+
+*  Description:
+*     This function displays a character string at a given position
+*     using a specified justification and up-vector.
+
+*  Parameters:
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     x
+*        The reference x coordinate.
+*     y
+*        The reference y coordinate.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", or 'B' for "bottom", and specifies the
+*        vertical location of the reference position. Note, "bottom"
+*        corresponds to the base-line of normal text. Some characters
+*        (eg "y", "g", "p", etc) descend below the base-line. The second
+*        character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+*     upx
+*        The x component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        left to right on the screen.
+*     upy
+*        The y component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        bottom to top on the screen.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     -  Any graphics within the rotated box enclosing the text are erased.
+*     -  A NULL value for "just" causes a value of "CC" to be used.
+*     -  Both "upx" and "upy" being zero causes an error.
+*     -  Any unrecognised character in "just" causes an error.
+*-
+*/
+
+/* Local Variables: */
+   char lj[ 2 ];
+   float uplen, xbox[ 4 ], ybox[ 4 ];
+   float angle, fjust, hu, test, alpha, beta;
+   int i, tbg;
+
+/* Check that there is something to draw. */
+   if( text && text[ 0 ] != 0 ){
+
+/* Fill in any missing parts of the justification string. */
+      if( just ){
+         if( just[ 0 ] == 'T' || just[ 0 ] == 'C' || just[ 0 ] == 'B' ){
+            lj[ 0 ] = just[ 0 ];
+         } else {
+            astError( AST__GRFER, "astGText: Justification string '%s' is "
+                      "invalid.", just );
+            return 0;
+         }
+
+         if( just[ 1 ] == 'L' || just[ 1 ] == 'C' || just[ 1 ] == 'R' ){
+            lj[ 1 ] = just[ 1 ];
+         } else {
+            astError( AST__GRFER, "astGText: Justification string '%s' "
+                      "is invalid.", just );
+            return 0;
+         }
+
+      } else {
+         lj[ 0 ] = 'C';
+         lj[ 1 ] = 'C';
+      }
+
+/* Find the conversion factors between increment in world coordinate axes,
+   and the corresponding increments in millimetres ( Xmm = alpha*Xworld,
+   Ymm = beta*Yworld ). */
+      if( !astGScales( &alpha, &beta ) ) return 0;
+
+/* If either axis is reversed, reverse the supplied up-vector components
+   so that they refer to the world-coordinates axes. */
+      if( alpha < 0.0 ) upx = -upx;
+      if( beta < 0.0 ) upy = -upy;
+
+/* Get the angle between the text base-line and horizontal. */
+      angle = atan2( -(double) upx*alpha, (double) upy*beta )*R2D;
+
+/* Get the fractional horizontal justification as needed by PGPLOT. */
+      if( lj[ 1 ] == 'L' ) {
+         fjust = 0.0;
+      } else if( lj[ 1 ] == 'R' ) {
+         fjust = 1.0;
+      } else {
+         fjust = 0.5;
+      }
+
+/* Unless the requested justification is "Bottom", we need to adjust
+   the supplied reference position before we use it with PGPLOT because
+   PGPLOT assumes "Bottom" justification. */
+      if( lj[0] != 'B' ) {
+
+/* Get the bounding box of the string. Note, only the size of the box is
+   significant here, not its position. Also note, leading and trailing
+   spaces are not included in the bounding box. */
+         ccpgqtxt( x, y, angle, fjust, (char *) text, xbox, ybox );
+
+/* Normalise the up-vector in world coordinates. */
+         uplen = sqrt( (double) (upx*upx + upy*upy) );
+         if( uplen > 0.0 ){
+            upx /= uplen;
+            upy /= uplen;
+         } else {
+            astError( AST__GRFER, "astGText: Zero length up-vector supplied.");
+            return 0;
+         }
+
+/* Find the height of the text above the base-line. Note, the PGPLOT
+   manual is not clear about the order of the corners returned by
+   pgqtxt, so we have to find the largest distance between
+   the corners in the direction of the supplied up-vector. */
+         hu = 0.0;
+         for( i = 0; i < 4; i++ ){
+            test = upx*( xbox[ i ] - x ) + upy*( ybox[ i ] - y );
+            if( test > hu ) hu = test;
+         }
+
+/* Adjust the vertical position of the reference point, since PGPLOT
+   requires it to be at the bottom of the text. */
+         if( lj[ 0 ] == 'T' ){
+            x -= upx*hu;
+            y -= upy*hu;
+         } else if( lj[ 0 ] == 'C' ){
+            x -= 0.5*upx*hu;
+            y -= 0.5*upy*hu;
+         }
+      }
+
+/* Display the text, erasing any graphics. */
+      ccpgqtbg( &tbg );
+      ccpgstbg( 0 );
+      ccpgptxt( x, y, angle, fjust, (char *) text );
+      ccpgstbg( tbg );
+   }
+
+/* Return. */
+   return 1;
+}
+
+int astGScales( float *alpha, float *beta ){
+/*
+*+
+*  Name:
+*     astGScales
+
+*  Purpose:
+*     Get the axis scales.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astGScales( float *alpha, float *beta )
+
+*  Description:
+*     This function returns two values (one for each axis) which scale
+*     increments on the corresponding axis into a "normal" coordinate
+*     system in which:
+*        1 - The axes have equal scale in terms of (for instance)
+*            millimetres per unit distance.
+*        2 - X values increase from left to right.
+*        3 - Y values increase from bottom to top.
+
+*  Parameters:
+*     alpha
+*        A pointer to the location at which to return the scale for the
+*        X axis (i.e. Xnorm = alpha*Xworld).
+*     beta
+*        A pointer to the location at which to return the scale for the
+*        Y axis (i.e. Ynorm = beta*Yworld).
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*-
+*/
+
+/* Local Variables: */
+   float nx1, nx2, ny1, ny2, wx1, wx2, wy1, wy2;
+   int ret;
+
+/* Find the conversion factors between increment in world coordinate axes,
+   and the corresponding increments in millimetres ( Xmm = alpha*Xworld,
+   Ymm = beta*Yworld ). */
+   ccpgqvp( 2, &nx1, &nx2, &ny1, &ny2 );
+   ccpgqwin( &wx1, &wx2, &wy1, &wy2 );
+
+   if( wx2 != wx1 && wy2 != wy1 &&
+       nx2 != nx1 && ny2 != ny1 ) {
+      *alpha= ( nx2 - nx1 ) / ( wx2 - wx1 );
+      *beta = ( ny2 - ny1 ) / ( wy2 - wy1 );
+      ret = 1;
+   } else {
+      astError( AST__GRFER, "astGScales: The graphics window or viewport has zero size." );
+      ret = 0;
+   }
+
+   return ret;
+}
+
+int astGTxExt( const char *text, float x, float y, const char *just,
+               float upx, float upy, float *xb, float *yb ){
+/*
+*+
+*  Name:
+*     astGTxExt
+
+*  Purpose:
+*     Get the extent of a character string.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astGTxExt( const char *text, float x, float y, const char *just,
+*                   float upx, float upy, float *xb, float *yb )
+
+*  Description:
+*     This function returns the corners of a box which would enclose the
+*     supplied character string if it were displayed using astGText.
+*
+*     The returned box INCLUDES any leading or trailing spaces.
+
+*  Parameters:
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     x
+*        The reference x coordinate.
+*     y
+*        The reference y coordinate.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", or 'B' for "bottom", and specifies the
+*        vertical location of the reference position. Note, "bottom"
+*        corresponds to the base-line of normal text. Some characters
+*        (eg "y", "g", "p", etc) descend below the base-line. The second
+*        character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+*     upx
+*        The x component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        left to right on the screen.
+*     upy
+*        The y component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        bottom to top on the screen.
+*     xb
+*        An array of 4 elements in which to return the x coordinate of
+*        each corner of the bounding box.
+*     yb
+*        An array of 4 elements in which to return the y coordinate of
+*        each corner of the bounding box.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+*     -  The order of the corners is anti-clockwise (in world coordinates)
+*        starting at the bottom left.
+*     -  A NULL value for "just" causes a value of "CC" to be used.
+*     -  Both "upx" and "upy" being zero causes an error.
+*     -  Any unrecognised character in "just" causes an error.
+*     -  Zero is returned for all bounds of the box if an error occurs.
+
+*-
+*/
+
+/* Local Variables: */
+   char lj[ 2 ];
+   float udx, udy, vdx, vdy, vx, vy, uplen, xbox[ 4 ],
+         ybox[ 4 ], uxu, uyu, uxd, uyd, ux, uy;
+   float angle, width, test, xl, yl;
+   float alpha, beta, xc, yc, hu, hd, a, b;
+   int i;
+
+/* Initialise the returned values to indicate no box available. */
+   for( i = 0; i < 4; i++ ){
+      xb[ i ] = 0.0;
+      yb[ i ] = 0.0;
+   }
+
+/* Check that there is something to draw. */
+   if( text && text[ 0 ] != 0 ){
+
+/* Fill in any missing parts of the justification string. */
+      if( just ){
+         if( just[ 0 ] == 'T' || just[ 0 ] == 'C' || just[ 0 ] == 'B' ){
+            lj[ 0 ] = just[ 0 ];
+         } else {
+            astError( AST__GRFER, "astGTxExt: Justification string '%s' is "
+                      "invalid.", just );
+            return 0;
+         }
+
+         if( just[ 1 ] == 'L' || just[ 1 ] == 'C' || just[ 1 ] == 'R' ){
+            lj[ 1 ] = just[ 1 ];
+         } else {
+            astError( AST__GRFER, "astGTxExt: Justification string '%s' is "
+                      "invalid.", just );
+            return 0;
+         }
+
+      } else {
+         lj[ 0 ] = 'C';
+         lj[ 1 ] = 'C';
+      }
+
+/* Find the conversion factors between increment in world coordinate axes,
+   and the corresponding increments in millimetres ( Xmm = alpha*Xworld,
+   Ymm = beta*Yworld ). */
+      if( !astGScales( &alpha, &beta ) ) return 0;
+
+/* If either axis is reversed, reverse the supplied up-vector components
+   so that they refer to the world-coordinates axes. */
+      if( alpha < 0.0 ) upx = -upx;
+      if( beta < 0.0 ) upy = -upy;
+
+/* Convert the up-vector into millimetres. */
+      ux = alpha*upx;
+      uy = beta*upy;
+
+/* Normalise the up-vector to a length of 1 millimetre. */
+      uplen = sqrt( (double) (ux*ux + uy*uy) );
+      if( uplen > 0.0 ){
+         ux /= uplen;
+         uy /= uplen;
+      } else {
+         astError( AST__GRFER, "astGText: Zero length up-vector supplied.");
+         return 0;
+      }
+
+/* Form the base-line vector by rotating the up-vector by 90 degrees
+   clockwise. */
+      vx = uy;
+      vy = -ux;
+
+/* Get the angle between the text base-line and horizontal. */
+      angle = atan2( (double) vy, (double) vx )*R2D;
+
+/* Get the bounding box of the string drawn with its bottom left corner
+   at the origin. */
+      ccpgqtxt( 0.0, 0.0, angle, 0.0, (char *) text, xbox, ybox );
+
+/* Convert the returned bounding box world coordinates into millimetres. */
+      for( i = 0; i < 4; i++ ){
+         xbox[ i ] *= alpha;
+         ybox[ i ] *= beta;
+      }
+
+/* Find the height of the bounding box, in millimetres. Note,
+   the PGPLOT manual is not clear about the order of the corners
+   returned by pgqtxt, so we have to find the largest distance between
+   the corners in the direction of the supplied up-vector. The reference
+   point is on the text base-line which is not usually at the bottom of
+   the bounding box (some letters - like "y" - extend below the base-line).
+   Find the distance from the base-line to the top (hu) and bottom (hd)
+   of the bounding box. */
+      hu = -FLT_MAX;
+      hd = FLT_MAX;
+      for( i = 0; i < 4; i++ ){
+         test = ux*xbox[ i ] + uy*ybox[ i ];
+         if( test > hu ) hu = test;
+         if( test < hd ) hd = test;
+      }
+
+/* Get an up and a down vector scaled to the height/depth of the
+   bounding box above/below the text base-line . */
+      uxu = ux*hu;
+      uyu = uy*hu;
+      uxd = ux*hd;
+      uyd = uy*hd;
+
+/* The bounding box returned by pgqtxt does not include any leading or
+   trailing spaces. We need to include such spaces in the returned box.
+   To do this we get the length of the text string in millimetres
+   using pglen instead of using the bounding box returned by pgqtxt. */
+      ccpglen( 2, (char *) text, &xl, &yl );
+
+/* The abolute width of the string in millimetres may depend on the
+   up-vector. The values returned by pglen are for horizontal and
+   vertical text. Find the width using the supplied up-vector. */
+      a = uy*xl;
+      b = ux*yl;
+      width = sqrt( a*a + b*b );
+
+/* The pglen function returns a value which is slightly smaller than
+   the area cleared to hold the text when written using pgptxt. Increase
+   the text width so that it is about equal to the area cleared. */
+      width += 0.2*hu;
+
+/* Scale the base-line vector so that its length is equal to the width
+   of the bounding box (including spaces). */
+      vx *= width;
+      vy *= width;
+
+/* Convert the base-line vector back into world coordinates. */
+      vx /= alpha;
+      vy /= beta;
+
+/* Convert the up and down vectors into world coordinates. */
+      uxu /= alpha;
+      uyu /= beta;
+      uxd /= alpha;
+      uyd /= beta;
+
+/* Find the coordinates at the centre of the bounding box in world
+   coordinates. */
+      xc = x;
+      yc = y;
+
+      if( lj[0] == 'B' ) {
+         xc += 0.5*uxu;
+         yc += 0.5*uyu;
+      } else if( lj[0] == 'T' ) {
+         xc -= 0.5*uxu;
+         yc -= 0.5*uyu;
+      }
+
+      if( lj[1] == 'L' ) {
+         xc += 0.5*vx;
+         yc += 0.5*vy;
+      } else if( lj[1] == 'R' ) {
+         xc -= 0.5*vx;
+         yc -= 0.5*vy;
+      }
+
+/* Get the corners of the bounding box. */
+      vdx = 0.5*vx;
+      vdy = 0.5*vy;
+      udx = 0.5*uxu;
+      udy = 0.5*uyu;
+
+/* Bottom left corner... */
+      xb[ 0 ] = xc - vdx - udx + uxd;
+      yb[ 0 ] = yc - vdy - udy + uyd;
+
+/* Bottom right corner... */
+      xb[ 1 ] = xc + vdx - udx + uxd;
+      yb[ 1 ] = yc + vdy - udy + uyd;
+
+/* Top right corner... */
+      xb[ 2 ] = xc + vdx + udx;
+      yb[ 2 ] = yc + vdy + udy;
+
+/* Top left corner... */
+      xb[ 3 ] = xc - vdx + udx;
+      yb[ 3 ] = yc - vdy + udy;
+
+   }
+
+/* Return. */
+   return 1;
+}
+
+int astGQch( float *chv, float *chh ){
+/*
+*+
+*  Name:
+*     astGQch
+
+*  Purpose:
+*     Return the character height in world coordinates.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int astGQch( float *chv, float *chh )
+
+*  Description:
+*     This function returns the heights of characters drawn vertically and
+*     horizontally in world coordinates.
+
+*  Parameters:
+*     chv
+*        A pointer to the double which is to receive the height of
+*        characters drawn with a vertical baseline . This will be an
+*        increment in the X axis.
+*     chh
+*        A pointer to the double which is to receive the height of
+*        characters drawn with a horizontal baseline. This will be an
+*        increment in the Y axis.
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*-
+*/
+
+/* Local Variables: */
+   float vx1,vx2,vy1,vy2,wx1,wx2,wy1,wy2;
+
+/* Get the character height in normalised device coordinates */
+   ccpgqcs( 0, chv, chh );
+
+/* Get the bounds of the PGPLOT viewport in normalised device
+   coordinates. */
+   ccpgqvp( 0, &vx1, &vx2, &vy1, &vy2 );
+
+/* Get the bounds of the PGPLOT window in world coordinates. */
+   ccpgqwin( &wx1, &wx2, &wy1, &wy2 );
+
+/* Convert the text height from normalised device coordinates into world
+   coordinates for vertical text. Print an error message if the viewport
+   has zero size. */
+   if( vx1 != vx2 ){
+      *chv *= ( wx2 - wx1 )/( vx2 - vx1 );
+
+   } else {
+      astError( AST__GRFER, "astGQch: The graphics viewport has zero size "
+                "in the X direction.");
+      return 0;
+   }
+
+/* Convert the text height from normalised device coordinates into world
+   coordinates for horizontal text. Print an error message if the viewport
+   has zero size. */
+   if( vy1 != vy2 ){
+      *chh *= ( wy2 - wy1 )/( vy2 - vy1 );
+   } else {
+      astError( AST__GRFER, "astGQch: The graphics viewport has zero size "
+                "in the Y direction.");
+      return 0;
+   }
+
+/* Return. */
+   return 1;
+}
+
+int astGAttr( int attr, double value, double *old_value, int prim ){
+/*
+*+
+*  Name:
+*     astGAttr
+
+*  Purpose:
+*     Enquire or set a graphics attribute value.
+
+*  Synopsis:
+*     #include "grf.h"
+*     int int astGAttr( int attr, double value, double *old_value, int prim )
+
+*  Description:
+*     This function returns the current value of a specified graphics
+*     attribute, and optionally establishes a new value. The supplied
+*     value is converted to an integer value if necessary before use.
+
+*  Parameters:
+*     attr
+*        An integer value identifying the required attribute. The
+*        following symbolic values are defined in grf.h:
+*
+*           GRF__STYLE  - Line style.
+*           GRF__WIDTH  - Line width.
+*           GRF__SIZE   - Character and marker size scale factor.
+*           GRF__FONT   - Character font.
+*           GRF__COLOUR - Colour index.
+*     value
+*        A new value to store for the attribute. If this is AST__BAD
+*        no value is stored.
+*     old_value
+*        A pointer to a double in which to return the attribute value.
+*        If this is NULL, no value is returned.
+*     prim
+*        The sort of graphics primitive to be drawn with the new attribute.
+*        Identified by the following values defined in grf.h:
+*           GRF__LINE
+*           GRF__MARK
+*           GRF__TEXT
+
+*  Returned Value:
+*     A value of 0 is returned if an error occurs, and 1 is returned
+*     otherwise.
+
+*  Notes:
+
+*-
+*/
+
+   int ival;
+   float rval, dx, dy, deflw, x1, x2, y1, y2;
+
+/* If required retrieve the current line style, and set a new line style. */
+   if( attr == GRF__STYLE ){
+      ccpgqls( &ival );
+      if( old_value ) *old_value = (double) ival;
+
+      if( value != AST__BAD ){
+         ival = (int) ( value + 0.5 );
+         if( value < 0.0 ) ival -= 1;
+
+         ival = ( ival - 1 ) % 5;
+         ival += ( ival < 0 ) ? 6 : 1;
+
+         ccpgsls( ival );
+      }
+
+/* If required retrieve the current line width, and set a new line width.
+   Line width is stored in Plot as a scale factor (1.0 for the default line
+   width which is a fixed fraction of the diagonal of the view surface), but
+   pgplot stores it in units of 0.005 of an inch. */
+   } else if( attr == GRF__WIDTH ){
+
+/* Get the bounds of the view surface in inches. */
+      ccpgqvsz( 1, &x1, &x2, &y1, &y2 );
+
+/* Find the default line width in inches (i.e. 0.0005 of the length
+   of the view surface diagonal). */
+      dx = ( x1 - x2 );
+      dy = ( y1 - y2 );
+      deflw = 0.0005*sqrt( (double )( dx*dx + dy*dy ) );
+
+/* Get the current pgplot line width in units of 0.005 of an inch. */
+      ccpgqlw( &ival );
+
+/* If required, return the factor by which this exceeds the default line
+   width found above. */
+      if( old_value ) *old_value = (double)( ival )/( 200.0 * deflw );
+
+/* If a new line width has been provided, the pgplot line width needs to
+   be set to the corresponding absolute value. */
+      if( value != AST__BAD ){
+         ival = (int) ( 200.0*value*deflw );
+         if( ival < 1 ) {
+            ival = 1;
+         } else if( ival > 201 ){
+            ival = 201;
+         }
+         ccpgslw( ival );
+      }
+
+/* If required retrieve the current character size, and set a new size.
+   The attribute value should be a factor by which to multiply the
+   default character size. */
+   } else if( attr == GRF__SIZE ){
+      ccpgqch( &rval );
+      if( old_value ) *old_value = (double) rval;
+
+      if( value != AST__BAD ){
+         ccpgsch( (float) value );
+      }
+
+/* If required retrieve the current character font, and set a new font. */
+   } else if( attr == GRF__FONT ){
+      ccpgqcf( &ival );
+      if( old_value ) *old_value = (double) ival;
+
+      if( value != AST__BAD ){
+         ival = (int) ( value + 0.5 );
+         if( value < 0.0 ) ival -= 1;
+
+         ival = ( ival - 1 ) % 4;
+         ival += ( ival < 0 ) ? 5 : 1;
+         ccpgscf( ival );
+      }
+
+/* If required retrieve the current colour index, and set a new colour
+   index. */
+   } else if( attr == GRF__COLOUR ){
+      ccpgqci( &ival );
+      if( old_value ) *old_value = (double) ival;
+
+      if( value != AST__BAD ){
+         ival = (int) ( value + 0.5 );
+         if( ival < 0 ) ival = 1;
+         ccpgsci( ival );
+      }
+
+/* Give an error message for any other attribute value. */
+   } else {
+      astError( AST__GRFER, "astGAttr: Unknown graphics attribute '%d' "
+                "requested.", attr );
+      return 0;
+   }
+
+/* Return. */
+   return 1;
+}
+
+/* Local Functions. */
+/* ================ */
+/* These implement the local C interface to PGPLOT in terms of its
+   native Fortran interface. Only those PGPLOT functions used within
+   this module are included. */
+static void ccpgline(int n, float xpts[], float ypts[] ){
+   F77_INTEGER_TYPE N;
+   F77_REAL_TYPE *XX;
+   F77_REAL_TYPE *YY;
+   int i;
+
+   XX = (F77_REAL_TYPE *) astMalloc( sizeof( F77_REAL_TYPE )*(size_t) n );
+   YY = (F77_REAL_TYPE *) astMalloc( sizeof( F77_REAL_TYPE )*(size_t) n );
+
+   if( astOK ){
+
+      for( i = 0; i < n; i++ ){
+         XX[ i ] = (F77_REAL_TYPE) xpts[ i ];
+         YY[ i ] = (F77_REAL_TYPE) ypts[ i ];
+      }
+
+      N = (F77_INTEGER_TYPE) n;
+
+      F77_CALL(pgline)( INTEGER_ARG(&N), REAL_ARRAY_ARG(XX),
+                        REAL_ARRAY_ARG(YY) );
+
+      XX = (F77_REAL_TYPE *) astFree( (void *) XX );
+      YY = (F77_REAL_TYPE *) astFree( (void *) YY );
+   }
+}
+
+static void ccpgpt(int n, float xpts[], float ypts[], int symbol){
+   F77_INTEGER_TYPE N;
+   F77_REAL_TYPE *XX;
+   F77_REAL_TYPE *YY;
+   F77_INTEGER_TYPE SYMBOL;
+   int i;
+
+   XX = (F77_REAL_TYPE *) astMalloc( sizeof( F77_REAL_TYPE )*(size_t) n );
+   YY = (F77_REAL_TYPE *) astMalloc( sizeof( F77_REAL_TYPE )*(size_t) n );
+
+   if( astOK ){
+
+      for( i = 0; i < n; i++ ){
+         XX[ i ] = (F77_REAL_TYPE) xpts[ i ];
+         YY[ i ] = (F77_REAL_TYPE) ypts[ i ];
+      }
+
+      N = (F77_INTEGER_TYPE) n;
+      SYMBOL = (F77_INTEGER_TYPE) symbol;
+
+
+      F77_CALL(pgpt)( INTEGER_ARG(&N), REAL_ARRAY_ARG(XX),
+                      REAL_ARRAY_ARG(YY), INTEGER_ARG(&SYMBOL) );
+
+      XX = (F77_REAL_TYPE *) astFree( (void *) XX );
+      YY = (F77_REAL_TYPE *) astFree( (void *) YY );
+   }
+}
+
+static void ccpgptxt(float x, float y, float angle, float fjust, char *text ){
+   F77_REAL_TYPE X;
+   F77_REAL_TYPE Y;
+   F77_REAL_TYPE ANGLE;
+   F77_REAL_TYPE FJUST;
+   DECLARE_CHARACTER(LTEXT,MXSTRLEN);
+   int ftext_length;
+
+   X = (F77_REAL_TYPE) x;
+   Y = (F77_REAL_TYPE) y;
+   ANGLE = (F77_REAL_TYPE) angle;
+   FJUST = (F77_REAL_TYPE) fjust;
+
+   ftext_length = strlen( text );
+   if( ftext_length > LTEXT_length ) ftext_length = LTEXT_length;
+   astStringExport( text, LTEXT, ftext_length );
+
+   F77_CALL(pgptxt)( REAL_ARG(&X), REAL_ARG(&Y), REAL_ARG(&ANGLE),
+                     REAL_ARG(&FJUST), CHARACTER_ARG(LTEXT)
+                     TRAIL_ARG(ftext) );
+}
+
+static void ccpgqtxt(float x, float y, float angle, float fjust, char *text,
+                     float xbox[], float ybox[]){
+   F77_REAL_TYPE X;
+   F77_REAL_TYPE Y;
+   F77_REAL_TYPE ANGLE;
+   F77_REAL_TYPE FJUST;
+   DECLARE_CHARACTER(LTEXT,MXSTRLEN);
+   F77_REAL_TYPE XBOX[ 4 ];
+   F77_REAL_TYPE YBOX[ 4 ];
+   int i;
+   int ftext_length;
+
+   X = (F77_REAL_TYPE) x;
+   Y = (F77_REAL_TYPE) y;
+   ANGLE = (F77_REAL_TYPE) angle;
+   FJUST = (F77_REAL_TYPE) fjust;
+
+   ftext_length = strlen( text );
+   if( ftext_length > LTEXT_length ) ftext_length = LTEXT_length;
+   astStringExport( text, LTEXT, ftext_length );
+
+   F77_CALL(pgqtxt)( REAL_ARG(&X), REAL_ARG(&Y), REAL_ARG(&ANGLE),
+                     REAL_ARG(&FJUST), CHARACTER_ARG(LTEXT),
+                     REAL_ARRAY_ARG(XBOX), REAL_ARRAY_ARG(YBOX)
+                     TRAIL_ARG(ftext) );
+
+   for( i = 0; i < 4; i++ ){
+      xbox[ i ] = (float) XBOX[ i ];
+      ybox[ i ] = (float) YBOX[ i ];
+   }
+
+}
+
+static void ccpgqtbg(int *tbci){
+   F77_INTEGER_TYPE TBCI;
+   F77_CALL(pgqtbg)( INTEGER_ARG(&TBCI) );
+   *tbci = (int) TBCI;
+}
+
+static void ccpgstbg(int tbci){
+   F77_INTEGER_TYPE TBCI;
+   TBCI = (F77_INTEGER_TYPE) tbci;
+   F77_CALL(pgstbg)( INTEGER_ARG(&TBCI) );
+}
+
+static void ccpgqcs(int units, float *xch, float *ych){
+   F77_INTEGER_TYPE UNITS;
+   F77_REAL_TYPE XCH;
+   F77_REAL_TYPE YCH;
+   UNITS = (F77_INTEGER_TYPE) units;
+
+   F77_CALL(pgqcs)( INTEGER_ARG(&UNITS), REAL_ARG(&XCH), REAL_ARG(&YCH) );
+
+   *xch = (float) XCH;
+   *ych = (float) YCH;
+}
+
+static void ccpglen(int units, char *text, float *xl, float *yl ){
+   F77_INTEGER_TYPE UNITS;
+   F77_REAL_TYPE XL;
+   F77_REAL_TYPE YL;
+   DECLARE_CHARACTER(LTEXT,MXSTRLEN);
+   int ftext_length;
+
+   UNITS = (F77_INTEGER_TYPE) units;
+
+
+   ftext_length = strlen( text );
+   if( ftext_length > LTEXT_length ) ftext_length = LTEXT_length;
+   astStringExport( text, LTEXT, ftext_length );
+
+   F77_CALL(pglen)( INTEGER_ARG(&UNITS), CHARACTER_ARG(LTEXT),
+                    REAL_ARG(&XL), REAL_ARG(&YL) TRAIL_ARG(ftext) );
+
+   *xl = (float) XL;
+   *yl = (float) YL;
+}
+
+static void ccpgqvp(int units, float *x1, float *x2, float *y1, float *y2){
+   F77_INTEGER_TYPE UNITS;
+   F77_REAL_TYPE X1;
+   F77_REAL_TYPE X2;
+   F77_REAL_TYPE Y1;
+   F77_REAL_TYPE Y2;
+
+   UNITS = (F77_INTEGER_TYPE) units;
+   F77_CALL(pgqvp)( INTEGER_ARG(&UNITS), REAL_ARG(&X1), REAL_ARG(&X2),
+                    REAL_ARG(&Y1), REAL_ARG(&Y2) );
+   *x1 = (float) X1;
+   *x2 = (float) X2;
+   *y1 = (float) Y1;
+   *y2 = (float) Y2;
+}
+
+static void ccpgqvsz(int units, float *x1, float *x2, float *y1, float *y2){
+   F77_INTEGER_TYPE UNITS;
+   F77_REAL_TYPE X1;
+   F77_REAL_TYPE X2;
+   F77_REAL_TYPE Y1;
+   F77_REAL_TYPE Y2;
+
+   UNITS = (F77_INTEGER_TYPE) units;
+   F77_CALL(pgqvsz)( INTEGER_ARG(&UNITS), REAL_ARG(&X1), REAL_ARG(&X2),
+                    REAL_ARG(&Y1), REAL_ARG(&Y2) );
+   *x1 = (float) X1;
+   *x2 = (float) X2;
+   *y1 = (float) Y1;
+   *y2 = (float) Y2;
+}
+
+static void ccpgqwin(float *x1, float *x2, float *y1, float *y2){
+   F77_REAL_TYPE X1;
+   F77_REAL_TYPE X2;
+   F77_REAL_TYPE Y1;
+   F77_REAL_TYPE Y2;
+
+   F77_CALL(pgqwin)( REAL_ARG(&X1), REAL_ARG(&X2), REAL_ARG(&Y1),
+                     REAL_ARG(&Y2) );
+   *x1 = (float) X1;
+   *x2 = (float) X2;
+   *y1 = (float) Y1;
+   *y2 = (float) Y2;
+}
+
+static void ccpgqls(int *ls){
+   F77_INTEGER_TYPE LS;
+   F77_CALL(pgqls)( INTEGER_ARG(&LS) );
+   *ls = (int) LS;
+}
+
+static void ccpgsls(int ls){
+   F77_INTEGER_TYPE LS;
+   LS = (F77_INTEGER_TYPE) ls;
+   F77_CALL(pgsls)( INTEGER_ARG(&LS) );
+}
+
+static void ccpgqlw(int *lw){
+   F77_INTEGER_TYPE LW;
+   F77_CALL(pgqlw)( INTEGER_ARG(&LW) );
+   *lw = (int) LW;
+}
+
+static void ccpgslw(int lw){
+   F77_INTEGER_TYPE LW;
+   LW = (F77_INTEGER_TYPE) lw;
+   F77_CALL(pgslw)( INTEGER_ARG(&LW) );
+}
+
+static void ccpgqch(float *ch){
+   F77_REAL_TYPE CH;
+   F77_CALL(pgqch)( REAL_ARG(&CH) );
+   *ch = (float) CH;
+}
+
+static void ccpgsch(float ch){
+   F77_REAL_TYPE CH;
+   CH = (F77_REAL_TYPE) ch;
+   F77_CALL(pgsch)( REAL_ARG(&CH) );
+}
+
+static void ccpgqcf(int *cf){
+   F77_INTEGER_TYPE CF;
+   F77_CALL(pgqcf)( INTEGER_ARG(&CF) );
+   *cf = (int) CF;
+}
+
+static void ccpgscf(int cf){
+   F77_INTEGER_TYPE CF;
+   CF = (F77_INTEGER_TYPE) cf;
+   F77_CALL(pgscf)( INTEGER_ARG(&CF) );
+}
+
+static void ccpgqci(int *ci){
+   F77_INTEGER_TYPE CI;
+   F77_CALL(pgqci)( INTEGER_ARG(&CI) );
+   *ci = (int) CI;
+}
+
+static void ccpgsci(int ci){
+   F77_INTEGER_TYPE CI;
+   CI = (F77_INTEGER_TYPE) ci;
+   F77_CALL(pgsci)( INTEGER_ARG(&ci) );
+}
+
+static void ccpgupdt( void ){
+   F77_CALL(pgupdt)();
+}
+
+static void ccpgbbuf( void ){
+   F77_CALL(pgbbuf)();
+}
+
+static void ccpgebuf( void ){
+   F77_CALL(pgebuf)();
+}
diff --git a/ast/grismmap.c b/ast/grismmap.c
new file mode 100644
index 0000000..dfdd10b
--- /dev/null
+++ b/ast/grismmap.c
@@ -0,0 +1,2596 @@
+/*
+*class++
+*  Name:
+*     GrismMap
+
+*  Purpose:
+*     Transform 1-dimensional coordinates using a grism dispersion equation.
+
+*  Constructor Function:
+c     astGrismMap
+f     AST_GRISMMAP
+
+*  Description:
+*     A GrismMap is a specialised form of Mapping which transforms
+*     1-dimensional coordinates using the spectral dispersion equation
+*     described in FITS-WCS paper III "Representation of spectral
+*     coordinates in FITS". This describes the dispersion produced by
+*     gratings, prisms and grisms.
+*
+*     When initially created, the forward transformation of a GrismMap
+*     transforms input "grism parameter" values into output wavelength
+*     values. The "grism parameter" is a dimensionless value which is
+*     linearly related to position on the detector. It is defined in FITS-WCS
+*     paper III as "the offset on the detector from the point of intersection
+*     of the camera axis, measured in units of the effective local length".
+*     The units in which wavelength values are expected or returned is
+*     determined by the values supplied for the GrismWaveR, GrismNRP and
+*     GrismG attribute: whatever units are used for these attributes will
+*     also be used for the wavelength values.
+
+*  Inheritance:
+*     The GrismMap class inherits from the Mapping class.
+
+*  Attributes:
+*     In addition to those attributes common to all Mappings, every
+*     GrismMap also has the following attributes:
+*
+*     - GrismNR: The refractive index at the reference wavelength
+*     - GrismNRP: Rate of change of refractive index with wavelength
+*     - GrismWaveR: The reference wavelength
+*     - GrismAlpha: The angle of incidence of the incoming light
+*     - GrismG: The grating ruling density
+*     - GrismM: The interference order
+*     - GrismEps: The angle between the normal and the dispersion plane
+*     - GrismTheta: Angle between normal to detector plane and reference ray
+
+*  Functions:
+c     The GrismMap class does not define any new functions beyond those
+f     The GrismMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     18-JUN-2003 (DSB):
+*        Original version.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS GrismMap
+
+
+/*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Implement a method to clear an attribute value for a GrismMap.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     MAKE_CLEAR(class,attribute,component,assign)
+
+*  Class Membership:
+*     Defined by the GrismMap class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstGrismMap *this )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( AstGrismMap *this )
+*
+*     which implement a method for clearing a specified attribute value for
+*     a class. The derived constants stored in the GrismMap structure are
+*     updated after the attribute has been cleared.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class to which the attribute belongs.
+*     attribute
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. Label in "astClearLabel").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(class,attribute,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attribute( Ast##class *this, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Report an error if the object has been cloned (i.e. has a reference \
+   count that is greater than one). */ \
+   if( astGetRefCount( this ) > 1 ) { \
+      astError( AST__IMMUT, "astClear(%s): The " #attribute "attribute of " \
+                "the supplied %s cannot be cleared because the %s has " \
+                "been cloned (programming error).", status, \
+                astGetClass(this), astGetClass(this), astGetClass(this) ); \
+\
+/* Otherwise, assign the "clear" value in the structure component. */ \
+   } else { \
+      this->component = (assign); \
+   } \
+\
+/* Update the derived constants. */ \
+   UpdateConstants( this, status ); \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attribute##_( Ast##class *this, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,class,Clear##attribute))( this, status ); \
+}
+
+/*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Implement a method to set an attribute value for a GrismMap.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     astMAKE_SET(class,attribute,type,component,assign)
+
+*  Class Membership:
+*     Defined by the GrismMap class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstGrismMap *this, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( AstGrismMap *this, <Type> value )
+*
+*     which implement a method for setting a specified attribute value for a
+*     GrismMap. The derived constants stored in the GrismMap structure are
+*     updated after the attribute has been cleared.
+
+*  Parameters:
+*      class
+*         The name (not the type) of the class to which the attribute belongs.
+*      attribute
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. Label in "astSetLabel").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_SET(class,attribute,type,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attribute( Ast##class *this, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Report an error if the object has been cloned (i.e. has a reference \
+   count that is greater than one). */ \
+   if( astGetRefCount( this ) > 1 ) { \
+      astError( AST__IMMUT, "astSet(%s): The " #attribute "attribute of " \
+                "the supplied %s cannot be changed because the %s has " \
+                "been cloned (programming error).", status, \
+                astGetClass(this), astGetClass(this), astGetClass(this) ); \
+\
+/* Otherwise, store the new value in the structure component. */ \
+   } else { \
+      this->component = (assign); \
+   } \
+\
+/* Update the derived constants. */ \
+   UpdateConstants( this, status ); \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attribute##_( Ast##class *this, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,class,Set##attribute))( this, value, status ); \
+}
+
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory management facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "unitmap.h"             /* Unit Mappings */
+#include "channel.h"             /* I/O channels */
+#include "zoommap.h"             /* ZoomMap interface */
+#include "winmap.h"              /* WinMap interface */
+#include "grismmap.h"            /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+
+/* Macros which return the maximum and minimum of two values. */
+#define MAX(aa,bb) ((aa)>(bb)?(aa):(bb))
+#define MIN(aa,bb) ((aa)<(bb)?(aa):(bb))
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(GrismMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(GrismMap,Class_Init)
+#define class_vtab astGLOBAL(GrismMap,Class_Vtab)
+#define getattrib_buff astGLOBAL(GrismMap,GetAttrib_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstGrismMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstGrismMap *astGrismMapId_( const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static AstMapping *CanMerge( AstMapping *, int, AstMapping *, int, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void UpdateConstants( AstGrismMap *, int * );
+
+static double GetGrismNR( AstGrismMap *, int * );
+static int TestGrismNR( AstGrismMap *, int * );
+static void ClearGrismNR( AstGrismMap *, int * );
+static void SetGrismNR( AstGrismMap *, double, int * );
+
+static double GetGrismNRP( AstGrismMap *, int * );
+static int TestGrismNRP( AstGrismMap *, int * );
+static void ClearGrismNRP( AstGrismMap *, int * );
+static void SetGrismNRP( AstGrismMap *, double, int * );
+
+static double GetGrismWaveR( AstGrismMap *, int * );
+static int TestGrismWaveR( AstGrismMap *, int * );
+static void ClearGrismWaveR( AstGrismMap *, int * );
+static void SetGrismWaveR( AstGrismMap *, double, int * );
+
+static double GetGrismAlpha( AstGrismMap *, int * );
+static int TestGrismAlpha( AstGrismMap *, int * );
+static void ClearGrismAlpha( AstGrismMap *, int * );
+static void SetGrismAlpha( AstGrismMap *, double, int * );
+
+static double GetGrismG( AstGrismMap *, int * );
+static int TestGrismG( AstGrismMap *, int * );
+static void ClearGrismG( AstGrismMap *, int * );
+static void SetGrismG( AstGrismMap *, double, int * );
+
+static int GetGrismM( AstGrismMap *, int * );
+static int TestGrismM( AstGrismMap *, int * );
+static void ClearGrismM( AstGrismMap *, int * );
+static void SetGrismM( AstGrismMap *, int, int * );
+
+static double GetGrismEps( AstGrismMap *, int * );
+static int TestGrismEps( AstGrismMap *, int * );
+static void ClearGrismEps( AstGrismMap *, int * );
+static void SetGrismEps( AstGrismMap *, double, int * );
+
+static double GetGrismTheta( AstGrismMap *, int * );
+static int TestGrismTheta( AstGrismMap *, int * );
+static void ClearGrismTheta( AstGrismMap *, int * );
+static void SetGrismTheta( AstGrismMap *, double, int * );
+
+/* Member functions. */
+/* ================= */
+static AstMapping *CanMerge( AstMapping *map1, int inv1, AstMapping *map2,
+                             int inv2, int *status ){
+/*
+*
+*  Name:
+*     CanMerge
+
+*  Purpose:
+*     Checks if two GrismMaps can be merged.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     AstMapping *CanMerge( AstMapping *map1, int inv1, AstMapping *map2,
+*                           int inv2, int *status )
+
+*  Class Membership:
+*     GrismMap internal utility function.
+
+*  Description:
+*     This function checks the two supplied Mappings to see if they can
+*     be merged into a single Mapping. One of the two Mappings should be
+*     a GrismMap. If they can be merged, the Merged Mapping is returned
+*     as the function value. Otherwise NULL is returned.
+
+*  Parameters:
+*     map1
+*        A pointer to the first mapping.
+*     map2
+*        A pointer to the second mapping.
+*     inv1
+*        The invert flag to use with the first mapping.
+*     inv2
+*        The invert flag to use with the second mapping.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the merged Mapping if the supplied Mappings can be merged,
+*     NULL otherwise.
+
+*/
+
+/* Local Variables: */
+   AstGrismMap *gmap2;        /* Pointer to second GrismMap */
+   AstGrismMap *gmap;         /* Pointer to first GrismMap */
+   AstMapping *ret;           /* Returned merged Mapping */
+   double g;                  /* The value of the GrismG attribute */
+   double nrp;                /* The value of the GrismNRP attribute */
+   double waver;              /* The value of the GrismWaveR attribute */
+   double z;                  /* Wavelength scaling */
+   int invert_result;         /* Is "ret" the inverse of the required Mapping? */
+
+/* Initialise the returned value. */
+   ret = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   gmap = NULL;
+   invert_result = 0;
+
+/* Initialise the zoom factor of the adjacent ZoomMap to indicate
+   that we have not yet found an adjacent ZoomMap. */
+   z = AST__BAD;
+
+/* If the first Mapping is a GrismMap... */
+   if( !strcmp( "GrismMap", astGetClass( map1 ) ) ) {
+      gmap = (AstGrismMap *) map1;
+
+/* If the second Mapping is also a GrismMap, they can be merged into a
+   UnitMap if one GrismMap is the inverse of the other. */
+      if( !strcmp( "GrismMap", astGetClass( map2 ) ) ) {
+         gmap2 = (AstGrismMap *) map2;
+
+/* Check that the two GrismMaps have the same attribute values. */
+         if( astEQUAL( astGetGrismNR( gmap ), astGetGrismNR( gmap2 )) &&
+             astEQUAL( astGetGrismNRP( gmap ), astGetGrismNRP( gmap2 )) &&
+             astEQUAL( astGetGrismWaveR( gmap ), astGetGrismWaveR( gmap2 )) &&
+             astEQUAL( astGetGrismAlpha( gmap ), astGetGrismAlpha( gmap2 )) &&
+             astEQUAL( astGetGrismG( gmap ), astGetGrismG( gmap2 )) &&
+             astGetGrismM( gmap ) != astGetGrismM( gmap2 ) &&
+             astEQUAL( astGetGrismEps( gmap ), astGetGrismEps( gmap2 )) &&
+             astEQUAL( astGetGrismTheta( gmap ), astGetGrismTheta( gmap2 )) ){
+
+/* If so, check that the GrismMaps are applied in opposite senses. If so
+   we can cancel the two GrismMaps, so return a UnitMap. */
+            if( inv1 != inv2 ) ret = (AstMapping *) astUnitMap( 1, "", status );
+         }
+
+/* If the first Mapping is a GrismMap but the second one is not... */
+      } else {
+
+/* We can merge the GrismMap with the second Mapping if the GrismMap has
+   not been inverted (i.e. if the wavelength output produced by the
+   GrismMap is fed as input to the second Mapping), and if the second
+   Mapping is a ZoomMap. */
+         if( !inv1 ) {
+
+/* Indicate that any merged Mapping to be created later will not need to
+   be inverted. */
+            invert_result = 0;
+
+/* See if the second Mapping is a ZoomMap, and if so, get the zoom
+   factor. If the Invert attribute in the ZoomMap is not set to the
+   required value, invert the zoom factor. This gives us the required
+   *forward* transformation. */
+            if( !strcmp( "ZoomMap", astGetClass( map2 ) ) ) {
+               z = astGetZoom( (AstZoomMap *) map2 );
+               if( astGetInvert( map2 ) != inv2 && z != 0.0 ) z = 1.0/z;
+            }
+         }
+      }
+
+/* If the first Mapping is not a GrismMap, but the second one is... */
+   } else if( !strcmp( "GrismMap", astGetClass( map2 ) ) ) {
+      gmap = (AstGrismMap *) map2;
+
+/* We can merge the GrismMap with the first Mapping if the GrismMap has
+   been inverted (i.e. if the wavelength output produced by the first
+   Mapping is fed as input to the inverted GrismMap), and if the first
+   Mapping is a ZoomMap. */
+      if( inv2 ) {
+
+/* It is easier to consider pairs of Mappings in which an un-inverted
+   GrismMap is followed by a ZoomMap (as in the above case). For this
+   reason, we invert the Mappings here, so that the merged Mapping created
+   later will be in the inverse of the required Mapping. Indicate that the
+   merged Mapping will therefore need to be inverted before being returned. */
+         invert_result = 1;
+
+/* See if the first Mapping is a ZoomMap. If so, get the zoom factor. If the
+   Invert attribute in the ZoomMap is not set to the opposite of the required
+   value, invert the zoom factor. This gives us the required *inverse*
+   transformation. */
+         if( !strcmp( "ZoomMap", astGetClass( map1 ) ) ) {
+            z = astGetZoom( (AstZoomMap *) map1 );
+            if( astGetInvert( map1 ) == inv1 && z != 0.0 ) z = 1.0/z;
+         }
+      }
+   }
+
+/* If required, produce the merged Mapping by merging the forward
+   GrismMap with the following ZoomMap (and then invert the
+   resulting Mapping if it is in the wrong direction). */
+   if( !ret && z != AST__BAD && z != 0.0 ) {
+
+/* Ensure we have a forward GrismMap. */
+      ret = astCopy( gmap );
+      astSetInvert( ret, 0 );
+
+/* Get the required GrismMap attribute values. */
+      g = astGetGrismG( ret );
+      nrp = astGetGrismNRP( ret );
+      waver = astGetGrismWaveR( ret );
+
+/* The above code ensures that z is the zoom factor from the wavelength
+   produced by the forward GrismMap to the final (modified) wavelength units.
+   Set the new GrismMap attribute values. GrismG, GrismNRP and GrismWaveR have
+   units of length and are scaled to represent new length units using the
+   zoom factor found above. */
+      g /= z;
+      nrp /= z;
+      waver *= z;
+
+      astSetGrismG( ret, g );
+      astSetGrismNRP( ret, nrp );
+      astSetGrismWaveR( ret, waver );
+
+/* If required invert this GrismMap. */
+      if( invert_result ) astInvert( ret );
+
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a GrismMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     GrismMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     GrismMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the GrismMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstGrismMap *this;             /* Pointer to the GrismMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the GrismMap structure. */
+   this = (AstGrismMap *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+   if ( !strcmp( attrib, "grismnr" ) ) {
+      astClearGrismNR( this );
+
+   } else if ( !strcmp( attrib, "grismnrp" ) ) {
+      astClearGrismNRP( this );
+
+   } else if ( !strcmp( attrib, "grismwaver" ) ) {
+      astClearGrismWaveR( this );
+
+   } else if ( !strcmp( attrib, "grismalpha" ) ) {
+      astClearGrismAlpha( this );
+
+   } else if ( !strcmp( attrib, "grismg" ) ) {
+      astClearGrismG( this );
+
+   } else if ( !strcmp( attrib, "grismm" ) ) {
+      astClearGrismM( this );
+
+   } else if ( !strcmp( attrib, "grismeps" ) ) {
+      astClearGrismEps( this );
+
+   } else if ( !strcmp( attrib, "grismtheta" ) ) {
+      astClearGrismTheta( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two GrismMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     GrismMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two GrismMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a GrismMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the GrismMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstGrismMap *that;
+   AstGrismMap *this;
+   int nin;
+   int nout;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two GrismMap structures. */
+   this = (AstGrismMap *) this_object;
+   that = (AstGrismMap *) that_object;
+
+/* Check the second object is a GrismMap. We know the first is a
+   GrismMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAGrismMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two GrismMaps differ, it may still be possible
+   for them to be equivalent. First compare the GrismMaps if their Invert
+   flags are the same. In this case all the attributes of the two GrismMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+
+            if( astEQUAL( this->nr, that->nr ) &&
+                astEQUAL( this->nrp, that->nrp ) &&
+                astEQUAL( this->waver, that->waver ) &&
+                astEQUAL( this->alpha, that->alpha ) &&
+                astEQUAL( this->g, that->g ) &&
+                this->m == that->m &&
+                astEQUAL( this->eps, that->eps ) &&
+                astEQUAL( this->theta, that->theta ) &&
+                astEQUAL( this->k1, that->k1 ) &&
+                astEQUAL( this->k2, that->k2 ) &&
+                astEQUAL( this->k3, that->k3 ) ) {
+                result = 1;
+             }
+
+/* If the Invert flags for the two GrismMaps differ, the attributes of the two
+   GrismMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a GrismMap, Invert flags must be equal. */
+            result = 0;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a GrismMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     GrismMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a GrismMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the GrismMap.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the GrismMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the GrismMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstGrismMap *this;            /* Pointer to the GrismMap structure */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the GrismMap structure. */
+   this = (AstGrismMap *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+   if ( !strcmp( attrib, "grismnr" ) ) {
+      dval = astGetGrismNR( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+   } else if ( !strcmp( attrib, "grismnrp" ) ) {
+      dval = astGetGrismNRP( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+   } else if ( !strcmp( attrib, "grismwaver" ) ) {
+      dval = astGetGrismWaveR( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+   } else if ( !strcmp( attrib, "grismalpha" ) ) {
+      dval = astGetGrismAlpha( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+   } else if ( !strcmp( attrib, "grismg" ) ) {
+      dval = astGetGrismG( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+   } else if ( !strcmp( attrib, "grismm" ) ) {
+      dval = astGetGrismM( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+   } else if ( !strcmp( attrib, "grismeps" ) ) {
+      dval = astGetGrismEps( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+   } else if ( !strcmp( attrib, "grismtheta" ) ) {
+      dval = astGetGrismTheta( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astInitGrismMapVtab_(  AstGrismMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitGrismMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a GrismMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     void astInitGrismMapVtab( AstGrismMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     GrismMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the GrismMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAGrismMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->ClearGrismNR = ClearGrismNR;
+   vtab->GetGrismNR = GetGrismNR;
+   vtab->SetGrismNR = SetGrismNR;
+   vtab->TestGrismNR = TestGrismNR;
+
+   vtab->ClearGrismNRP = ClearGrismNRP;
+   vtab->GetGrismNRP = GetGrismNRP;
+   vtab->SetGrismNRP = SetGrismNRP;
+   vtab->TestGrismNRP = TestGrismNRP;
+
+   vtab->ClearGrismWaveR = ClearGrismWaveR;
+   vtab->GetGrismWaveR = GetGrismWaveR;
+   vtab->SetGrismWaveR = SetGrismWaveR;
+   vtab->TestGrismWaveR = TestGrismWaveR;
+
+   vtab->ClearGrismAlpha = ClearGrismAlpha;
+   vtab->GetGrismAlpha = GetGrismAlpha;
+   vtab->SetGrismAlpha = SetGrismAlpha;
+   vtab->TestGrismAlpha = TestGrismAlpha;
+
+   vtab->ClearGrismG = ClearGrismG;
+   vtab->GetGrismG = GetGrismG;
+   vtab->SetGrismG = SetGrismG;
+   vtab->TestGrismG = TestGrismG;
+
+   vtab->ClearGrismM = ClearGrismM;
+   vtab->GetGrismM = GetGrismM;
+   vtab->SetGrismM = SetGrismM;
+   vtab->TestGrismM = TestGrismM;
+
+   vtab->ClearGrismEps = ClearGrismEps;
+   vtab->GetGrismEps = GetGrismEps;
+   vtab->SetGrismEps = SetGrismEps;
+   vtab->TestGrismEps = TestGrismEps;
+
+   vtab->ClearGrismTheta = ClearGrismTheta;
+   vtab->GetGrismTheta = GetGrismTheta;
+   vtab->SetGrismTheta = SetGrismTheta;
+   vtab->TestGrismTheta = TestGrismTheta;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the class dump, copy and delete functions.*/
+   astSetDump( vtab, Dump, "GrismMap",
+               "Map 1-d coordinates using a spectral disperser" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a GrismMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     GrismMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated GrismMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated GrismMap with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated GrismMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the GrismMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        GrismMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated GrismMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *merged_map; /* Merger of two Mappings */
+   int i1;                 /* Lower index of the two GrismMaps being merged */
+   int i2;                 /* Upper index of the two GrismMaps being merged */
+   int i;                  /* Mapping index */
+   int result;             /* Result value to return */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   i1 = -1;
+   i2 = -1;
+
+/* See if the GrismMap can be merged with the Mappings on either side of it
+   in the list. This can only be done in series for a GrismMap. */
+/* ===================================================================== */
+   if( series ) {
+
+/* Set a flag indicating that we have not yet found a neighbour with which
+   the GrismMap can be merged. */
+      merged_map = NULL;
+
+/* First check the lower neighbour (if any). */
+      if( where > 0 ) {
+         i1 = where - 1;
+         i2 = where;
+         merged_map = CanMerge( ( *map_list )[ i1 ], (* invert_list)[ i1 ],
+                                ( *map_list )[ i2 ], (* invert_list)[ i2 ], status );
+      }
+
+/* If the GrismMap can not be merged with its lower neighbour, check its
+   upper neighbour (if any) in the same way. */
+      if( !merged_map && where < *nmap - 1 ) {
+         i1 = where;
+         i2 = where + 1;
+         merged_map = CanMerge( ( *map_list )[ i1 ], (* invert_list)[ i1 ],
+                                ( *map_list )[ i2 ], (* invert_list)[ i2 ], status );
+      }
+
+/* If either neighbour has passed these checks, replace the pair of
+   Mappings which have been merged with the single merged Mapping returned
+   above. */
+      if( merged_map ) {
+
+/* Annul the two Mappings. */
+         (void) astAnnul( ( *map_list )[ i1 ] );
+         (void) astAnnul( ( *map_list )[ i2 ] );
+
+/* Store a pointer for the merged Mapping in place of the first of the
+   two replaced Mappings. */
+         ( *map_list )[ i1 ] = merged_map;
+         ( *invert_list )[ i1 ] = astGetInvert( merged_map );
+
+/* Shuffle down the remaining Mappings to fill the hole left by the
+   second of the replaced Mappings. */
+         for ( i = i2 + 1; i < *nmap; i++ ) {
+            ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+            ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+         }
+
+/* Clear the vacated element at the end. */
+         ( *map_list )[ *nmap - 1 ] = NULL;
+         ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+         (*nmap)--;
+         result = i1;
+
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a GrismMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     GrismMap member function (over-rides the astSetAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a GrismMap, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the GrismMap.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstGrismMap *this;            /* Pointer to the GrismMap structure */
+   double dval;                  /* Attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the GrismMap structure. */
+   this = (AstGrismMap *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+   if (  nc = 0, ( 1 == astSscanf( setting, "grismnr= %lf %n", &dval, &nc ) ) && ( nc >= len ) ) {
+      astSetGrismNR( this, dval );
+
+   } else if (  nc = 0, ( 1 == astSscanf( setting, "grismnrp= %lf %n", &dval, &nc ) ) && ( nc >= len ) ) {
+      astSetGrismNRP( this, dval );
+
+   } else if (  nc = 0, ( 1 == astSscanf( setting, "grismwaver= %lf %n", &dval, &nc ) ) && ( nc >= len ) ) {
+      astSetGrismWaveR( this, dval );
+
+   } else if (  nc = 0, ( 1 == astSscanf( setting, "grismalpha= %lf %n", &dval, &nc ) ) && ( nc >= len ) ) {
+      astSetGrismAlpha( this, dval );
+
+   } else if (  nc = 0, ( 1 == astSscanf( setting, "grismg= %lf %n", &dval, &nc ) ) && ( nc >= len ) ) {
+      astSetGrismG( this, dval );
+
+   } else if (  nc = 0, ( 1 == astSscanf( setting, "grismm= %lf %n", &dval, &nc ) ) && ( nc >= len ) ) {
+      astSetGrismM( this, dval );
+
+   } else if (  nc = 0, ( 1 == astSscanf( setting, "grismeps= %lf %n", &dval, &nc ) ) && ( nc >= len ) ) {
+      astSetGrismEps( this, dval );
+
+   } else if (  nc = 0, ( 1 == astSscanf( setting, "grismtheta= %lf %n", &dval, &nc ) ) && ( nc >= len ) ) {
+      astSetGrismTheta( this, dval );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a GrismMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     GrismMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a GrismMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the GrismMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstGrismMap *this;             /* Pointer to the GrismMap structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the GrismMap structure. */
+   this = (AstGrismMap *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+   if ( !strcmp( attrib, "grismnr" ) ) {
+      result = astTestGrismNR( this );
+
+   } else if ( !strcmp( attrib, "grismnrp" ) ) {
+      result = astTestGrismNRP( this );
+
+   } else if ( !strcmp( attrib, "grismwaver" ) ) {
+      result = astTestGrismWaveR( this );
+
+   } else if ( !strcmp( attrib, "grismalpha" ) ) {
+      result = astTestGrismAlpha( this );
+
+   } else if ( !strcmp( attrib, "grismg" ) ) {
+      result = astTestGrismG( this );
+
+   } else if ( !strcmp( attrib, "grismm" ) ) {
+      result = astTestGrismM( this );
+
+   } else if ( !strcmp( attrib, "grismeps" ) ) {
+      result = astTestGrismEps( this );
+
+   } else if ( !strcmp( attrib, "grismtheta" ) ) {
+      result = astTestGrismTheta( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a GrismMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     GrismMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a GrismMap and a set of points encapsulated
+*     in a PointSet and transforms the points so as to apply the
+*     forward or inverse dispersal equation.
+
+*  Parameters:
+*     this
+*        Pointer to the GrismMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate
+*        transformation should be applied, while a zero value requests
+*        the inverse transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed
+*        (output) coordinate values. A NULL value may also be given,
+*        in which case a new PointSet will be created by this
+*        function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*     - The number of coordinate values per point in the input
+*     PointSet must equal 1.
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points (with 1 coordinate value per point)
+*     to accommodate the result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstGrismMap *map;             /* Pointer to GrismMap to be applied */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double sinbeta;               /* Sin( beta ) (see FITS-WCS paper III) */
+   double value_in;              /* Input coordinate value */
+   int npoint;                   /* Number of points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the GrismMap. */
+   map = (AstGrismMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform
+   member function inherited from the parent Mapping class. This
+   function validates all arguments and generates an output PointSet
+   if necessary, but does not actually transform any coordinate
+   values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points from the input PointSet and obtain
+   pointers for accessing the input and output coordinate values. */
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse mapping,
+   according to the direction specified and whether the mapping has
+   been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* If any of the parameters are undefined fill the output with bad
+   values (if possible). */
+   if( map->k1 == AST__BAD || map->k2 == AST__BAD || map->k3 == AST__BAD ) {
+      if( astOK ) {
+         for ( point = 0; point < npoint; point++ ) {
+            ptr_out[ 0 ][ point ] = AST__BAD;
+         }
+      }
+
+/* Otherwise... */
+   } else {
+
+/* Forward transformation. */
+/* ----------------------- */
+      if ( forward ) {
+
+/* Loop to transform each input point. */
+         for ( point = 0; point < npoint; point++ ) {
+
+/* Extract the input grism parameter value. */
+            value_in = ptr_in[ 0 ][ point ];
+
+/* Check for bad input coordinates and generate a bad result if necessary. */
+            if( value_in == AST__BAD || map->k2 == 0.0 ) {
+               ptr_out[ 0 ][ point ] = AST__BAD;
+
+/* Otherwise, apply the algorithm described in FITS-WCS paper III. */
+            } else {
+               ptr_out[ 0 ][ point ] = ( map->k1 + sin( atan( value_in ) + map->k3 ) )/map->k2;
+            }
+         }
+
+/* Inverse transformation. */
+/* ----------------------- */
+      } else {
+
+/* Loop to transform each input point. */
+         for ( point = 0; point < npoint; point++ ) {
+
+/* Extract the input wavelength value. */
+            value_in = ptr_in[ 0 ][ point ];
+
+/* Check for bad input coordinates and generate a bad result if necessary. */
+            if ( value_in == AST__BAD ) {
+               ptr_out[ 0 ][ point ] = AST__BAD;
+
+/* Otherwise, apply the algorithm described in FITS-WCS paper III. */
+            } else {
+               sinbeta = map->k2*value_in - map->k1;
+               if( sinbeta < -1.0 || sinbeta > 1.0 ) {
+                  ptr_out[ 0 ][ point ] = AST__BAD;
+               } else {
+                  ptr_out[ 0 ][ point ] = tan( asin( sinbeta ) - map->k3 );
+               }
+            }
+         }
+      }
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+
+static void UpdateConstants( AstGrismMap *this, int *status ){
+/*
+*  Name:
+*     UpdateConstants
+
+*  Purpose:
+*     Re-calculate the constants used within the transformation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     void UpdateConstants( AstGrismMap *this, int *status )
+
+*  Class Membership:
+*     GrismMap member function
+
+*  Description:
+*     This function re-calculates the constants used within the
+*     transformation on the basis of the current values of the
+*     GrismMap attributes. It should be called whenever a new value is
+*     set for an attribute, or an attribute is cleared.
+
+*  Parameters:
+*     this
+*        Pointer to the GrismMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double alpha;        /* The current vaue of the GrismAlpha attribute */
+   double coseps;       /* cos( eps ) */
+   double sinalpha;     /* sin( alpha ) */
+   double eps;          /* The current vaue of the GrismEps attribute */
+   double g;            /* The current vaue of the GrismG attribute */
+   double nr;           /* The current vaue of the GrismNR attribute */
+   double nrp;          /* The current vaue of the GrismNRP attribute */
+   double sinbeta_r;    /* sin( beta_r ) */
+   double theta;        /* The current vaue of the GrismTheta attribute */
+   double wave_r;       /* The current vaue of the GrismWaveR attribute */
+   int m;               /* The current vaue of the GrismM attribute */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the current attribute values. */
+   nr = astGetGrismNR( this );
+   nrp = astGetGrismNRP( this );
+   wave_r = astGetGrismWaveR( this );
+   alpha = astGetGrismAlpha( this );
+   g = astGetGrismG( this );
+   m = astGetGrismM( this );
+   eps = astGetGrismEps( this );
+   theta = astGetGrismTheta( this );
+
+/* Re-calculate the constants. */
+   coseps = cos( eps );
+   sinalpha = sin( alpha );
+
+   this->k1 = sinalpha*( nr - nrp*wave_r );
+
+   if( coseps != 0.0 ) {
+      this->k2 = ( g*m/coseps ) - nrp*sinalpha;
+
+      sinbeta_r = g*m*wave_r/coseps - nr*sinalpha;
+      if( sinbeta_r < -1.0 || sinbeta_r > 1.0 ) {
+         this->k3 = AST__BAD;
+      } else {
+         this->k3 = asin( sinbeta_r ) + theta;
+      }
+
+   } else {
+      this->k2 = AST__BAD;
+      this->k3 = AST__BAD;
+   }
+
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     GrismNR
+
+*  Purpose:
+*     The refractive index at the reference wavelength.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute holds refractive index of the grism material at the
+*     reference wavelength (given by attribute GrismWaveR). The default
+*     value is 1.0.
+*
+*     Note, the value of this attribute may changed only if the GrismMap
+*     has no more than one reference. That is, an error is reported if the
+*     GrismMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     GrismMap
+*        All GrismMaps have this attribute.
+
+*att--
+*/
+MAKE_CLEAR(GrismMap,GrismNR,nr,(AST__BAD))
+astMAKE_GET(GrismMap,GrismNR,double,1.0,( ( this->nr == AST__BAD ) ?
+                                      1.0 : this->nr ))
+MAKE_SET(GrismMap,GrismNR,double,nr,(value) )
+astMAKE_TEST(GrismMap,GrismNR,( this->nr != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     GrismNRP
+
+*  Purpose:
+*     The rate of change of refractive index with wavelength.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute holds the rate of change of the refractive index of the
+*     grism material with respect to wavelength at the reference wavelength
+*     (given by attribute GrismWaveR). The default value is 0.0 (the
+*     appropriate value for a pure grating disperser with no prism). The
+*     units of this attribute should be consistent with those of attributes
+*     GrismWaveR and GrismG.
+*
+*     Note, the value of this attribute may changed only if the GrismMap
+*     has no more than one reference. That is, an error is reported if the
+*     GrismMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     GrismMap
+*        All GrismMaps have this attribute.
+
+*att--
+*/
+MAKE_CLEAR(GrismMap,GrismNRP,nrp,(AST__BAD))
+astMAKE_GET(GrismMap,GrismNRP,double,0.0,( ( this->nrp == AST__BAD ) ?
+                                      0.0 : this->nrp ))
+MAKE_SET(GrismMap,GrismNRP,double,nrp,(value) )
+astMAKE_TEST(GrismMap,GrismNRP,( this->nrp != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     GrismWaveR
+
+*  Purpose:
+*     The reference wavelength.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute holds reference wavelength. The default value is
+*     5000 (Angstrom). The units of this attribute should be consistent with
+*     those of attributes GrismNRP and GrismG.
+*
+*     Note, the value of this attribute may changed only if the GrismMap
+*     has no more than one reference. That is, an error is reported if the
+*     GrismMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     GrismMap
+*        All GrismMaps have this attribute.
+
+*att--
+*/
+MAKE_CLEAR(GrismMap,GrismWaveR,waver,(AST__BAD))
+astMAKE_GET(GrismMap,GrismWaveR,double,5000.0,( ( this->waver == AST__BAD ) ?
+                                      5000.0 : this->waver ))
+MAKE_SET(GrismMap,GrismWaveR,double,waver,(value) )
+astMAKE_TEST(GrismMap,GrismWaveR,( this->waver != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     GrismAlpha
+
+*  Purpose:
+*     The angle of incidence of the incoming light on the grating surface.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute holds the angle between the incoming light and the
+*     normal to the grating surface, in radians. The default value is 0.
+*
+*     Note, the value of this attribute may changed only if the GrismMap
+*     has no more than one reference. That is, an error is reported if the
+*     GrismMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     GrismMap
+*        All GrismMaps have this attribute.
+
+*att--
+*/
+MAKE_CLEAR(GrismMap,GrismAlpha,alpha,(AST__BAD))
+astMAKE_GET(GrismMap,GrismAlpha,double,0.0,( ( this->alpha == AST__BAD ) ?
+                                       0.0 : this->alpha ))
+MAKE_SET(GrismMap,GrismAlpha,double,alpha,(value) )
+astMAKE_TEST(GrismMap,GrismAlpha,( this->alpha != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     GrismG
+
+*  Purpose:
+*     The grating ruling density.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute holds the number of grating rulings per unit length.
+*     The unit of length used should be consistent with the units used
+*     for attributes GrismWaveR and GrismNRP. The default value is 0.0.
+*     (the appropriate value for a pure prism disperser with no grating).
+*
+*     Note, the value of this attribute may changed only if the GrismMap
+*     has no more than one reference. That is, an error is reported if the
+*     GrismMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     GrismMap
+*        All GrismMaps have this attribute.
+
+*att--
+*/
+MAKE_CLEAR(GrismMap,GrismG,g,(AST__BAD))
+astMAKE_GET(GrismMap,GrismG,double,0.0,( ( this->g == AST__BAD ) ?
+                                      0.0 : this->g ))
+MAKE_SET(GrismMap,GrismG,double,g,(value) )
+astMAKE_TEST(GrismMap,GrismG,( this->g != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     GrismM
+
+*  Purpose:
+*     The interference order
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute holds the interference order being considered.
+*     The default value is 0.
+*
+*     Note, the value of this attribute may changed only if the GrismMap
+*     has no more than one reference. That is, an error is reported if the
+*     GrismMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     GrismMap
+*        All GrismMaps have this attribute.
+
+*att--
+*/
+MAKE_CLEAR(GrismMap,GrismM,m,(INT_MAX))
+astMAKE_GET(GrismMap,GrismM,int,0,( ( this->m == INT_MAX ) ?
+                                      0 : this->m ))
+MAKE_SET(GrismMap,GrismM,int,m,(value) )
+astMAKE_TEST(GrismMap,GrismM,( this->m != INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     GrismEps
+
+*  Purpose:
+*      The angle between the normal and the dispersion plane.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute holds the angle (in radians) between the normal to
+*     the grating or exit prism face, and the dispersion plane. The
+*     dispersion plane is the plane spanned by the incoming and outgoing
+*     ray. The default value is 0.0.
+*
+*     Note, the value of this attribute may changed only if the GrismMap
+*     has no more than one reference. That is, an error is reported if the
+*     GrismMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     GrismMap
+*        All GrismMaps have this attribute.
+
+*att--
+*/
+MAKE_CLEAR(GrismMap,GrismEps,eps,(AST__BAD))
+astMAKE_GET(GrismMap,GrismEps,double,0.0,( ( this->eps == AST__BAD ) ?
+                                      0.0 : this->eps ))
+MAKE_SET(GrismMap,GrismEps,double,eps,(value) )
+astMAKE_TEST(GrismMap,GrismEps,( this->eps != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     GrismTheta
+
+*  Purpose:
+*     Angle between normal to detector plane and reference ray.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute gives the angle of incidence of light of the
+*     reference wavelength (given by attribute GrismWaveR) onto the
+*     detector. Specifically, it holds the angle (in radians) between
+*     the normal to the detector plane and an incident ray at the reference
+*     wavelength. The default value is 0.0.
+*
+*     Note, the value of this attribute may changed only if the GrismMap
+*     has no more than one reference. That is, an error is reported if the
+*     GrismMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     GrismMap
+*        All GrismMaps have this attribute.
+
+*att--
+*/
+MAKE_CLEAR(GrismMap,GrismTheta,theta,(AST__BAD))
+astMAKE_GET(GrismMap,GrismTheta,double,0.0,( ( this->theta == AST__BAD ) ?
+                                      0.0 : this->theta ))
+MAKE_SET(GrismMap,GrismTheta,double,theta,(value) )
+astMAKE_TEST(GrismMap,GrismTheta,( this->theta != AST__BAD ))
+
+/* Copy constructor. */
+/* ----------------- */
+/* No copy constructor is needed, as a byte-by-byte copy suffices. */
+
+/* Destructor. */
+/* ----------- */
+/* No destructor is needed as no memory, etc. needs freeing. */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for GrismMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the GrismMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the GrismMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstGrismMap *this;            /* Pointer to the GrismMap structure */
+   double dval;                  /* Double value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the GrismMap structure. */
+   this = (AstGrismMap *) this_object;
+
+/* Write out values representing the instance variables for the
+   GrismMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+   set = TestGrismNR( this, status );
+   dval = set ? GetGrismNR( this, status ) : astGetGrismNR( this );
+   astWriteDouble( channel, "GrmNR", set, 1, dval, "Refractive index at the ref. wavelength" );
+
+   set = TestGrismNRP( this, status );
+   dval = set ? GetGrismNRP( this, status ) : astGetGrismNRP( this );
+   astWriteDouble( channel, "GrmNRP", set, 1, dval, "Rate of change of refractive index" );
+
+   set = TestGrismWaveR( this, status );
+   dval = set ? GetGrismWaveR( this, status ) : astGetGrismWaveR( this );
+   astWriteDouble( channel, "GrmWR", set, 1, dval, "Ref. wavelength" );
+
+   set = TestGrismAlpha( this, status );
+   dval = set ? GetGrismAlpha( this, status ) : astGetGrismAlpha( this );
+   astWriteDouble( channel, "GrmAlp", set, 1, dval, "Angle of incidence of incoming light" );
+
+   set = TestGrismG( this, status );
+   dval = set ? GetGrismG( this, status ) : astGetGrismG( this );
+   astWriteDouble( channel, "GrmG", set, 1, dval, "Grating ruling density" );
+
+   set = TestGrismM( this, status );
+   dval = set ? GetGrismM( this, status ) : astGetGrismM( this );
+   astWriteDouble( channel, "GrmM", set, 1, dval, "The interference order" );
+
+   set = TestGrismEps( this, status );
+   dval = set ? GetGrismEps( this, status ) : astGetGrismEps( this );
+   astWriteDouble( channel, "GrmEps", set, 1, dval, "Angle between grating normal and dispersion plane" );
+
+   set = TestGrismTheta( this, status );
+   dval = set ? GetGrismTheta( this, status ) : astGetGrismTheta( this );
+   astWriteDouble( channel, "GrmTh", set, 1, dval, "Angle between detector normal and reference ray" );
+
+}
+
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAGrismMap and astCheckGrismMap functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(GrismMap,Mapping)
+astMAKE_CHECK(GrismMap)
+
+AstGrismMap *astGrismMap_( const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astGrismMap
+f     AST_GRISMMAP
+
+*  Purpose:
+*     Create a GrismMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "grismmap.h"
+c     AstGrismMap *astGrismMap( const char *options, ... )
+f     RESULT = AST_GRISMMAP( OPTIONS, STATUS )
+
+*  Class Membership:
+*     GrismMap constructor.
+
+*  Description:
+*     This function creates a new GrismMap and optionally initialises
+*     its attributes.
+*
+*     A GrismMap is a specialised form of Mapping which transforms
+*     1-dimensional coordinates using the spectral dispersion equation
+*     described in FITS-WCS paper III "Representation of spectral
+*     coordinates in FITS". This describes the dispersion produced by
+*     gratings, prisms and grisms.
+*
+*     When initially created, the forward transformation of a GrismMap
+*     transforms input "grism parameter" values into output wavelength
+*     values. The "grism parameter" is a dimensionless value which is
+*     linearly related to position on the detector. It is defined in FITS-WCS
+*     paper III as "the offset on the detector from the point of intersection
+*     of the camera axis, measured in units of the effective local length".
+*     The units in which wavelength values are expected or returned is
+*     determined by the values supplied for the GrismWaveR, GrismNRP and
+*     GrismG attribute: whatever units are used for these attributes will
+*     also be used for the wavelength values.
+
+*  Parameters:
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new GrismMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new GrismMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGrismMap()
+f     AST_GRISMMAP = INTEGER
+*        A pointer to the new GrismMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstGrismMap *new;             /* Pointer to new GrismMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the GrismMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitGrismMap( NULL, sizeof( AstGrismMap ), !class_init,
+                          &class_vtab, "GrismMap" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   GrismMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new GrismMap. */
+   return new;
+}
+
+AstGrismMap *astGrismMapId_( const char *options, ... ) {
+/*
+*  Name:
+*     astGrismMapId_
+
+*  Purpose:
+*     Create a GrismMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     AstGrismMap *astGrismMapId( const char *options, int *status, ... )
+
+*  Class Membership:
+*     GrismMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astGrismMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astGrismMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astGrismMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astGrismMap_.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The ID value associated with the new GrismMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstGrismMap *new;             /* Pointer to new GrismMap */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the GrismMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitGrismMap( NULL, sizeof( AstGrismMap ), !class_init,
+                          &class_vtab, "GrismMap" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new GrismMap's
+   attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new GrismMap. */
+   return astMakeId( new );
+}
+
+AstGrismMap *astInitGrismMap_( void *mem, size_t size, int init,
+                               AstGrismMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitGrismMap
+
+*  Purpose:
+*     Initialise a GrismMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     AstGrismMap *astInitGrismMap( void *mem, size_t size, int init,
+*                                   AstGrismMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     GrismMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new GrismMap object. It allocates memory (if necessary) to accommodate
+*     the GrismMap plus any additional data associated with the derived class.
+*     It then initialises a GrismMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a GrismMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the GrismMap is to be initialised.
+*        This must be of sufficient size to accommodate the GrismMap data
+*        (sizeof(GrismMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the GrismMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the GrismMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the GrismMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new GrismMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+
+*  Returned Value:
+*     A pointer to the new GrismMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstGrismMap *new;              /* Pointer to new GrismMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitGrismMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the GrismMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstGrismMap *) astInitMapping( mem, size, 0,
+                                         (AstMappingVtab *) vtab, name,
+                                         1, 1, 1, 1 );
+   if ( astOK ) {
+
+/* Initialise the GrismMap data. */
+/* ---------------------------- */
+      new->nr    = AST__BAD;
+      new->nrp   = AST__BAD;
+      new->waver = AST__BAD;
+      new->alpha = AST__BAD;
+      new->g     = AST__BAD;
+      new->m     = INT_MAX;
+      new->eps   = AST__BAD;
+      new->theta = AST__BAD;
+
+/* Set up the other required derived constants. */
+      UpdateConstants( new, status );
+
+/* If an error occurred, clean up by deleting the new GrismMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new GrismMap. */
+   return new;
+}
+
+AstGrismMap *astLoadGrismMap_( void *mem, size_t size,
+                           AstGrismMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadGrismMap
+
+*  Purpose:
+*     Load a GrismMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "grismmap.h"
+*     AstGrismMap *astLoadGrismMap( void *mem, size_t size,
+*                               AstGrismMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     GrismMap loader.
+
+*  Description:
+*     This function is provided to load a new GrismMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     GrismMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a GrismMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the GrismMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        GrismMap data (sizeof(GrismMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the GrismMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the GrismMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstGrismMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new GrismMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the GrismMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "GrismMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new GrismMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstGrismMap *new;             /* Pointer to the new GrismMap */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this GrismMap. In this case the
+   GrismMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstGrismMap );
+      vtab = &class_vtab;
+      name = "GrismMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitGrismMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built GrismMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "GrismMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+      new->nr = astReadDouble( channel, "grmnr", AST__BAD );
+      if ( TestGrismNR( new, status ) ) SetGrismNR( new, new->nr, status );
+
+      new->nrp = astReadDouble( channel, "grmnrp", AST__BAD );
+      if ( TestGrismNRP( new, status ) ) SetGrismNRP( new, new->nrp, status );
+
+      new->waver = astReadDouble( channel, "grmwr", AST__BAD );
+      if ( TestGrismWaveR( new, status ) ) SetGrismWaveR( new, new->waver, status );
+
+      new->alpha = astReadDouble( channel, "grmalp", AST__BAD );
+      if ( TestGrismAlpha( new, status ) ) SetGrismAlpha( new, new->alpha, status );
+
+      new->g = astReadDouble( channel, "grmg", AST__BAD );
+      if ( TestGrismG( new, status ) ) SetGrismG( new, new->g, status );
+
+      new->m = astReadInt( channel, "grmm", INT_MAX );
+      if ( TestGrismM( new, status ) ) SetGrismM( new, new->m, status );
+
+      new->eps = astReadDouble( channel, "grmeps", AST__BAD );
+      if ( TestGrismEps( new, status ) ) SetGrismEps( new, new->eps, status );
+
+      new->theta = astReadDouble( channel, "grmth", AST__BAD );
+      if ( TestGrismTheta( new, status ) ) SetGrismTheta( new, new->theta, status );
+
+/* Set up the other required derived constants. */
+      UpdateConstants( new, status );
+   }
+
+/* If an error occurred, clean up by deleting the new GrismMap. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the new GrismMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions
+   defined by this class. Each simply checks the global error status
+   and then locates and executes the appropriate member function,
+   using the function pointer stored in the object's virtual function
+   table (this pointer is located using the astMEMBER macro defined in
+   "object.h").
+
+   Note that the member function may not be the one defined here, as
+   it may have been over-ridden by a derived class. However, it should
+   still have the same interface. */
+
+
+
+
+
diff --git a/ast/grismmap.h b/ast/grismmap.h
new file mode 100644
index 0000000..5655be0
--- /dev/null
+++ b/ast/grismmap.h
@@ -0,0 +1,353 @@
+#if !defined( GRISMMAP_INCLUDED ) /* Include this file only once */
+#define GRISMMAP_INCLUDED
+/*
+*+
+*  Name:
+*     grismmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the GrismMap class.
+
+*  Invocation:
+*     #include "grismmap.h"
+
+*  Description:
+*     This include file defines the interface to the GrismMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The GrismMap class implements Mappings which perform a "zoom"
+*     transformation by multiplying all coordinate values by the same
+*     scale factor (the inverse transformation is performed by
+*     dividing by this scale factor).
+
+*  Inheritance:
+*     The GrismMap class inherits from the Mapping class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     8-JUL-2003 (DSB):
+*        Initial version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* GrismMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstGrismMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double nr;
+   double nrp;
+   double waver;
+   double alpha;
+   double g;
+   int m;
+   double eps;
+   double theta;
+   double k1;
+   double k2;
+   double k3;
+
+} AstGrismMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstGrismMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   double (* GetGrismNR)( AstGrismMap *, int * );
+   int (* TestGrismNR)( AstGrismMap *, int * );
+   void (* ClearGrismNR)( AstGrismMap *, int * );
+   void (* SetGrismNR)( AstGrismMap *, double, int * );
+
+   double (* GetGrismNRP)( AstGrismMap *, int * );
+   int (* TestGrismNRP)( AstGrismMap *, int * );
+   void (* ClearGrismNRP)( AstGrismMap *, int * );
+   void (* SetGrismNRP)( AstGrismMap *, double, int * );
+
+   double (* GetGrismWaveR)( AstGrismMap *, int * );
+   int (* TestGrismWaveR)( AstGrismMap *, int * );
+   void (* ClearGrismWaveR)( AstGrismMap *, int * );
+   void (* SetGrismWaveR)( AstGrismMap *, double, int * );
+
+   double (* GetGrismAlpha)( AstGrismMap *, int * );
+   int (* TestGrismAlpha)( AstGrismMap *, int * );
+   void (* ClearGrismAlpha)( AstGrismMap *, int * );
+   void (* SetGrismAlpha)( AstGrismMap *, double, int * );
+
+   double (* GetGrismG)( AstGrismMap *, int * );
+   int (* TestGrismG)( AstGrismMap *, int * );
+   void (* ClearGrismG)( AstGrismMap *, int * );
+   void (* SetGrismG)( AstGrismMap *, double, int * );
+
+   int (* GetGrismM)( AstGrismMap *, int * );
+   int (* TestGrismM)( AstGrismMap *, int * );
+   void (* ClearGrismM)( AstGrismMap *, int * );
+   void (* SetGrismM)( AstGrismMap *, int, int * );
+
+   double (* GetGrismEps)( AstGrismMap *, int * );
+   int (* TestGrismEps)( AstGrismMap *, int * );
+   void (* ClearGrismEps)( AstGrismMap *, int * );
+   void (* SetGrismEps)( AstGrismMap *, double, int * );
+
+   double (* GetGrismTheta)( AstGrismMap *, int * );
+   int (* TestGrismTheta)( AstGrismMap *, int * );
+   void (* ClearGrismTheta)( AstGrismMap *, int * );
+   void (* SetGrismTheta)( AstGrismMap *, double, int * );
+
+} AstGrismMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstGrismMapGlobals {
+   AstGrismMapVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 101 ];
+} AstGrismMapGlobals;
+
+#endif
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(GrismMap)          /* Check class membership */
+astPROTO_ISA(GrismMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstGrismMap *astGrismMap_( const char *, int *, ...);
+#else
+AstGrismMap *astGrismMapId_( const char *, ... )__attribute__((format(printf,1,2)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstGrismMap *astInitGrismMap_( void *, size_t, int, AstGrismMapVtab *,
+                             const char *, int * );
+
+/* Vtab initialiser. */
+void astInitGrismMapVtab_( AstGrismMapVtab *, const char *, int * );
+
+/* Loader. */
+AstGrismMap *astLoadGrismMap_( void *, size_t, AstGrismMapVtab *,
+                             const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitGrismMapGlobals_( AstGrismMapGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+
+   double astGetGrismNR_( AstGrismMap *, int * );
+   int astTestGrismNR_( AstGrismMap *, int * );
+   void astClearGrismNR_( AstGrismMap *, int * );
+   void astSetGrismNR_( AstGrismMap *, double, int * );
+
+   double astGetGrismNRP_( AstGrismMap *, int * );
+   int astTestGrismNRP_( AstGrismMap *, int * );
+   void astClearGrismNRP_( AstGrismMap *, int * );
+   void astSetGrismNRP_( AstGrismMap *, double, int * );
+
+   double astGetGrismWaveR_( AstGrismMap *, int * );
+   int astTestGrismWaveR_( AstGrismMap *, int * );
+   void astClearGrismWaveR_( AstGrismMap *, int * );
+   void astSetGrismWaveR_( AstGrismMap *, double, int * );
+
+   double astGetGrismAlpha_( AstGrismMap *, int * );
+   int astTestGrismAlpha_( AstGrismMap *, int * );
+   void astClearGrismAlpha_( AstGrismMap *, int * );
+   void astSetGrismAlpha_( AstGrismMap *, double, int * );
+
+   double astGetGrismG_( AstGrismMap *, int * );
+   int astTestGrismG_( AstGrismMap *, int * );
+   void astClearGrismG_( AstGrismMap *, int * );
+   void astSetGrismG_( AstGrismMap *, double, int * );
+
+   int astGetGrismM_( AstGrismMap *, int * );
+   int astTestGrismM_( AstGrismMap *, int * );
+   void astClearGrismM_( AstGrismMap *, int * );
+   void astSetGrismM_( AstGrismMap *, int, int * );
+
+   double astGetGrismEps_( AstGrismMap *, int * );
+   int astTestGrismEps_( AstGrismMap *, int * );
+   void astClearGrismEps_( AstGrismMap *, int * );
+   void astSetGrismEps_( AstGrismMap *, double, int * );
+
+   double astGetGrismTheta_( AstGrismMap *, int * );
+   int astTestGrismTheta_( AstGrismMap *, int * );
+   void astClearGrismTheta_( AstGrismMap *, int * );
+   void astSetGrismTheta_( AstGrismMap *, double, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckGrismMap(this) astINVOKE_CHECK(GrismMap,this,0)
+#define astVerifyGrismMap(this) astINVOKE_CHECK(GrismMap,this,1)
+
+/* Test class membership. */
+#define astIsAGrismMap(this) astINVOKE_ISA(GrismMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astGrismMap astINVOKE(F,astGrismMap_)
+#else
+#define astGrismMap astINVOKE(F,astGrismMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitGrismMap(mem,size,init,vtab,name) \
+astINVOKE(O,astInitGrismMap_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitGrismMapVtab(vtab,name) astINVOKE(V,astInitGrismMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadGrismMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadGrismMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckGrismMap to validate GrismMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+
+#define astGetGrismNR(this) astINVOKE(V,astGetGrismNR_(astCheckGrismMap(this),STATUS_PTR))
+#define astTestGrismNR(this) astINVOKE(V,astTestGrismNR_(astCheckGrismMap(this),STATUS_PTR))
+#define astClearGrismNR(this) astINVOKE(V,astClearGrismNR_(astCheckGrismMap(this),STATUS_PTR))
+#define astSetGrismNR(this,value) astINVOKE(V,astSetGrismNR_(astCheckGrismMap(this),value,STATUS_PTR))
+
+#define astGetGrismNRP(this) astINVOKE(V,astGetGrismNRP_(astCheckGrismMap(this),STATUS_PTR))
+#define astTestGrismNRP(this) astINVOKE(V,astTestGrismNRP_(astCheckGrismMap(this),STATUS_PTR))
+#define astClearGrismNRP(this) astINVOKE(V,astClearGrismNRP_(astCheckGrismMap(this),STATUS_PTR))
+#define astSetGrismNRP(this,value) astINVOKE(V,astSetGrismNRP_(astCheckGrismMap(this),value,STATUS_PTR))
+
+#define astGetGrismWaveR(this) astINVOKE(V,astGetGrismWaveR_(astCheckGrismMap(this),STATUS_PTR))
+#define astTestGrismWaveR(this) astINVOKE(V,astTestGrismWaveR_(astCheckGrismMap(this),STATUS_PTR))
+#define astClearGrismWaveR(this) astINVOKE(V,astClearGrismWaveR_(astCheckGrismMap(this),STATUS_PTR))
+#define astSetGrismWaveR(this,value) astINVOKE(V,astSetGrismWaveR_(astCheckGrismMap(this),value,STATUS_PTR))
+
+#define astGetGrismAlpha(this) astINVOKE(V,astGetGrismAlpha_(astCheckGrismMap(this),STATUS_PTR))
+#define astTestGrismAlpha(this) astINVOKE(V,astTestGrismAlpha_(astCheckGrismMap(this),STATUS_PTR))
+#define astClearGrismAlpha(this) astINVOKE(V,astClearGrismAlpha_(astCheckGrismMap(this),STATUS_PTR))
+#define astSetGrismAlpha(this,value) astINVOKE(V,astSetGrismAlpha_(astCheckGrismMap(this),value,STATUS_PTR))
+
+#define astGetGrismG(this) astINVOKE(V,astGetGrismG_(astCheckGrismMap(this),STATUS_PTR))
+#define astTestGrismG(this) astINVOKE(V,astTestGrismG_(astCheckGrismMap(this),STATUS_PTR))
+#define astClearGrismG(this) astINVOKE(V,astClearGrismG_(astCheckGrismMap(this),STATUS_PTR))
+#define astSetGrismG(this,value) astINVOKE(V,astSetGrismG_(astCheckGrismMap(this),value,STATUS_PTR))
+
+#define astGetGrismM(this) astINVOKE(V,astGetGrismM_(astCheckGrismMap(this),STATUS_PTR))
+#define astTestGrismM(this) astINVOKE(V,astTestGrismM_(astCheckGrismMap(this),STATUS_PTR))
+#define astClearGrismM(this) astINVOKE(V,astClearGrismM_(astCheckGrismMap(this),STATUS_PTR))
+#define astSetGrismM(this,value) astINVOKE(V,astSetGrismM_(astCheckGrismMap(this),value,STATUS_PTR))
+
+#define astGetGrismEps(this) astINVOKE(V,astGetGrismEps_(astCheckGrismMap(this),STATUS_PTR))
+#define astTestGrismEps(this) astINVOKE(V,astTestGrismEps_(astCheckGrismMap(this),STATUS_PTR))
+#define astClearGrismEps(this) astINVOKE(V,astClearGrismEps_(astCheckGrismMap(this),STATUS_PTR))
+#define astSetGrismEps(this,value) astINVOKE(V,astSetGrismEps_(astCheckGrismMap(this),value,STATUS_PTR))
+
+#define astGetGrismTheta(this) astINVOKE(V,astGetGrismTheta_(astCheckGrismMap(this),STATUS_PTR))
+#define astTestGrismTheta(this) astINVOKE(V,astTestGrismTheta_(astCheckGrismMap(this),STATUS_PTR))
+#define astClearGrismTheta(this) astINVOKE(V,astClearGrismTheta_(astCheckGrismMap(this),STATUS_PTR))
+#define astSetGrismTheta(this,value) astINVOKE(V,astSetGrismTheta_(astCheckGrismMap(this),value,STATUS_PTR))
+
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/interval.c b/ast/interval.c
new file mode 100644
index 0000000..a25768d
--- /dev/null
+++ b/ast/interval.c
@@ -0,0 +1,4686 @@
+/*
+*class++
+*  Name:
+*     Interval
+
+*  Purpose:
+*     A region representing an interval on one or more axes of a Frame.
+
+*  Constructor Function:
+c     astInterval
+f     AST_INTERVAL
+
+*  Description:
+*     The Interval class implements a Region which represents upper
+*     and/or lower limits on one or more axes of a Frame. For a point to
+*     be within the region represented by the Interval, the point must
+*     satisfy all the restrictions placed on all the axes. The point is
+*     outside the region if it fails to satisfy any one of the restrictions.
+*     Each axis may have either an upper limit, a lower limit, both or
+*     neither. If both limits are supplied but are in reverse order (so
+*     that the lower limit is greater than the upper limit), then the
+*     interval is an excluded interval, rather than an included interval.
+*
+*     Note, The Interval class makes no allowances for cyclic nature of
+*     some coordinate systems (such as SkyFrame coordinates). A Box
+*     should usually be used in these cases since this requires the user
+*     to think about suitable upper and lower limits,
+
+*  Inheritance:
+*     The Interval class inherits from the Region class.
+
+*  Attributes:
+*     The Interval class does not define any new attributes beyond
+*     those which are applicable to all Regions.
+
+*  Functions:
+c     The Interval class does not define any new functions beyond those
+f     The Interval class does not define any new routines beyond those
+*     which are applicable to all Regions.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     29-OCT-2004 (DSB):
+*        Original version.
+*     19-APR-2006 (DSB):
+*        Negate the cached equivalent Box if the Interval has been negated.
+*     28-MAY-2007 (DSB):
+*        Re-implemented BndBaseMesh.
+*     20-JAN-2009 (DSB):
+*        Over-ride astRegBasePick.
+*     26-JAN-2009 (DSB):
+*        Over-ride astMapMerge.
+*     4-NOV42-2013 (DSB):
+*        - Change RegCentre so that it does not report an error for an unbounded
+*        Interval if the centre is merely being inquired rather than set. This is
+*        the documented behaviour of the astRegCentre method.
+*        - Modify RegPins so that it can handle uncertainty regions that straddle
+*        a discontinuity. Previously, such uncertainty Regions could have a huge
+*        bounding box resulting in matching region being far too big.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Interval
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "region.h"              /* Abstract coordinate regions (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "box.h"                 /* Box Regions */
+#include "nullregion.h"          /* Null Regions */
+#include "wcsmap.h"              /* Definitons of AST__DPI etc */
+#include "interval.h"            /* Interface definition for this class */
+#include "ellipse.h"             /* Interface definition for ellipse class */
+#include "mapping.h"             /* Position mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "cmpframe.h"            /* Compound Frames */
+#include "prism.h"               /* Prism regions */
+#include "pointlist.h"           /* Lists of points in a Frame */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstMapping *(* parent_simplify)( AstMapping *, int * );
+static int (* parent_overlap)( AstRegion *, AstRegion *, int * );
+static void (* parent_setregfs)( AstRegion *, AstFrame *, int * );
+static void (* parent_setunc)( AstRegion *, AstRegion *, int * );
+static void (* parent_resetcache)( AstRegion *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Interval)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Interval,Class_Init)
+#define class_vtab astGLOBAL(Interval,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstIntervalVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstInterval *astIntervalId_( void *, const double[], const double[], void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstBox *Cache( AstInterval *, int * );
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *BndBaseMesh( AstRegion *, double *, double *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstRegion *RegBasePick( AstRegion *this, int, const int *, int * );
+static AstRegion *GetDefUnc( AstRegion *, int * );
+static AstRegion *MergeInterval( AstInterval *, AstRegion *, int, int * );
+static double *RegCentre( AstRegion *this, double *, double **, int, int, int * );
+static int *OneToOne( AstMapping *, int * );
+static int GetBounded( AstRegion *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int Overlap( AstRegion *, AstRegion *, int * );
+static int RegPins( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+static int RegTrace( AstRegion *, int, double *, double **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void IntervalPoints( AstInterval *, double *, double *, int *);
+static void RegBaseBox( AstRegion *this, double *, double *, int * );
+static void ResetCache( AstRegion *this, int * );
+static void SetRegFS( AstRegion *, AstFrame *, int * );
+static void SetUnc( AstRegion *, AstRegion *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+
+static AstPointSet *BndBaseMesh( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     BndBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing points spread around part of the boundary
+*     of a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     AstPointSet *BndBaseMesh( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     Interval method (over-rides the astBndBaseMesh method inherited from
+*     the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a set of points on the
+*     boundary of the intersection between the supplied Region and the
+*     supplied box. The points refer to the base Frame of the
+*     encapsulated FrameSet. If the boundary of the supplied Region does
+*     not intersect the supplied box, then a PointSet containing a single
+*     bad point is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array holding the lower limits of the axis values
+*        within the required box.
+*     ubnd
+*        Pointer to an array holding the upper limits of the axis values
+*        within the required box.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. The axis values in this PointSet will have
+*     associated accuracies derived from the uncertainties which were
+*     supplied when the Region was created.
+*
+*    If the Region does not intersect the supplied box, the returned
+*    PointSet will contain a single point with a value of AST__BAD on
+*    every axis.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstBox *box;
+   AstFrame *bfrm;
+   AstInterval *this_interval;
+   AstMapping *map;
+   AstPointSet *result;
+   double *lbndb;
+   double *ubndb;
+   double **ptr;
+   int closed;
+   int i;
+   int nbase;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Store a pointer to the interval. */
+   this_interval = (AstInterval *) this;
+
+/* If the Interval is effectively a Box, invoke the astBndBaseMesh
+   function on the equivalent Box. A pointer to the equivalent Box will
+   be stored in the Interval structure. */
+   box = Cache( (AstInterval *) this, status );
+   if( box ) {
+      result = astBndBaseMesh( box, lbnd, ubnd );
+
+/* If the Interval is not equivalent to a Box (i.e. if one or more bounds
+   are missing)... */
+   } else {
+
+/* Find the base frame box that just encloses the supplied current Frame
+   box. */
+      map = astGetMapping( this->frameset, AST__CURRENT, AST__BASE );
+      nbase = astGetNout( map );
+      lbndb = astMalloc( sizeof(double)*nbase );
+      ubndb = astMalloc( sizeof(double)*nbase );
+      if( astOK ) {
+         for( i = 0; i < nbase; i++ ) {
+            astMapBox( map, lbnd, ubnd, 1, i, lbndb + i, ubndb + i,
+                       NULL, NULL );
+         }
+
+/* Create a Box that is like this Interval except that missing bounds are
+   inherited from the supplied limits. Check that the resulting box is
+   closed. */
+         closed = 1;
+         for( i = 0; i < nbase; i++ ) {
+            if( this_interval->ubnd[ i ] != DBL_MAX ) ubndb[ i ] = this_interval->ubnd[ i ];
+            if( this_interval->lbnd[ i ] != -DBL_MAX ) lbndb[ i ] = this_interval->lbnd[ i ];
+            if( lbndb[ i ] > ubndb[ i ] ) closed = 0;
+         }
+
+/* Cannot create the required mesh if the box is not closed. */
+         if( closed ) {
+
+/* Create the Box. */
+            bfrm = astGetFrame( this->frameset, AST__BASE );
+            box = astBox( bfrm, 1, lbndb, ubndb, NULL, "", status );
+
+/* Create the required mesh. */
+            result = astRegBaseMesh( box );
+
+/* Free resources */
+            bfrm = astAnnul( bfrm );
+            box = astAnnul( box );
+
+/* If the boundary of the supplied Region does not intersect the box,
+   return a PointSet containing a single bad position. */
+         } else {
+            result = astPointSet( 1, nbase, "", status );
+            ptr = astGetPoints( result );
+            if( astOK ) {
+               for( i = 0; i < nbase; i++ ) ptr[ i ][ 0 ] = AST__BAD;
+            }
+         }
+      }
+
+/* Free resources. */
+      map = astAnnul( map );
+      lbndb = astFree( lbndb );
+      ubndb = astFree( ubndb );
+   }
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the required pointer. */
+   return result;
+}
+
+static AstBox *Cache( AstInterval *this, int *status ){
+/*
+*  Name:
+*     Cache
+
+*  Purpose:
+*     Calculate intermediate values and cache them in the Interval structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     AstBox *Cache( AstInterval *this, int *status )
+
+*  Class Membership:
+*     Interval member function
+
+*  Description:
+*     This function uses the PointSet stored in the parent Region to calculate
+*     some intermediate values which are useful in other methods. These
+*     values are stored within the Interval structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Interval.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If the Interval is equivalent to a Box, then a pointer to the
+*     equivalent Box is returned. This is a copy of the pointer stored in
+*     the Interval structure and should not be annulled.
+
+*/
+
+/* Local Variables: */
+   AstBox *bbox;            /* Equivalent base Box */
+   AstFrame *bfrm;          /* Interval base Frame */
+   AstFrame *cfrm;          /* Interval current Frame */
+   AstRegion *map;          /* Interval base->current Mapping */
+   AstRegion *reg;          /* Pointer to this Region structure */
+   AstRegion *unc;          /* Pointer to uncertainty Region */
+   double **ptr;            /* Pointer to data holding all axis limits */
+   double *lbnd;            /* Pointer to array of lower axis limits */
+   double *ubnd;            /* Pointer to array of upper axis limits */
+   int i;                   /* Axis index */
+   int isBox;               /* Is this Interval equivalent to a Box? */
+   int nc;                  /* Number of base Frame axes */
+   int neg;                 /* Is the equivalent Box negated? */
+
+/* Check the global error status. Also return if the cached information
+   is up to date (i.e. not stale). */
+   if( !this->stale || !astOK ) return this->box;
+
+/* Get a pointer to the Region structure */
+   reg = (AstRegion *) this;
+
+/* The Interval structure contains a pointer to an equivalent Box
+   structure. This Box structure is created below if the Interval is
+   equivalent to a Box. Annul any previous box. */
+   if( this->box ) this->box = astAnnul( this->box );
+
+/* Get the number of axes in the base Frame of the FrameSet encapsulated
+   by the parent Region structure. */
+   nc = astGetNin( reg->frameset );
+
+/* Get a pointer to the array holding the axis limits held in the PointSet
+   encapsulated in the parent Region structure. */
+   ptr = astGetPoints( reg->points );
+
+/* Allocate memory to hold the limits organised per point rather than per
+   axis. */
+   lbnd = astMalloc( sizeof( double )*(size_t)nc );
+   ubnd = astMalloc( sizeof( double )*(size_t)nc );
+
+/* Check these pointers can be used safely. */
+   if( ubnd ) {
+
+/* See if the Interval is effectively a (possibly negated) Box. Assume it
+   is to begin with.  */
+      isBox = 1;
+
+/* Initialisation to prevent compiler warnings. */
+      neg = 0;
+
+/* Check the limits on every axis. */
+      for( i = 0; i < nc; i++ ) {
+
+/* Copy the axis limits into the allocated arrays (these are needed by the
+   Box constructor later on). */
+         lbnd[ i ] = ptr[ i ][ 0 ];
+         ubnd[ i ] = ptr[ i ][ 1 ];
+
+/* The Interval is not a Box if any axis limit is missing. In this case
+   use -DBL_MAX or +DBL_MAX as the limit to be stored in the Interval
+   structure. */
+         if( lbnd[ i ] == AST__BAD ) lbnd[ i ] = -DBL_MAX;
+         if( fabs( lbnd[ i ] ) == DBL_MAX ) isBox = 0;
+
+         if( ubnd[ i ] == AST__BAD ) ubnd[ i ] = DBL_MAX;
+         if( fabs( ubnd[ i ] ) == DBL_MAX ) isBox = 0;
+
+/* If this is the first axis, note if the axis interval is included or
+   excluded. This is determined by whether the "lower limit" is greater
+   than or less than the "upper limit". If the axis interval is excluded
+   (lower limit greater than upper limit), then any equivalent Box will be
+   a negated Box (i.e. will represent the outside of a box rather than
+   the inside). */
+         if( i == 0 ){
+            neg = ( lbnd[ i ] > ubnd[ i ]  );
+
+/* The Interval is not a Box if the limits for this axis are not the same
+   way round as those of the first axis. */
+         } else {
+
+            if( neg ) {
+               if( lbnd[ i ] < ubnd[ i ]  ) isBox = 0;
+            } else {
+               if( lbnd[ i ] > ubnd[ i ]  ) isBox = 0;
+            }
+
+         }
+      }
+
+/* If the Interval is effectively an unnegated Box, create the equivalent Box,
+   and store a pointer to it in the Interval structure. */
+      if( isBox && !neg ) {
+         bfrm = astGetFrame( reg->frameset, AST__BASE );
+         cfrm = astGetFrame( reg->frameset, AST__CURRENT );
+         map = astGetMapping( reg->frameset, AST__BASE, AST__CURRENT );
+         unc = astTestUnc( reg ) ? astGetUncFrm( reg, AST__BASE ) : NULL;
+
+         bbox = astBox( bfrm, 1, lbnd, ubnd, unc, "", status );
+         if( astIsAUnitMap( map ) ){
+            this->box = astClone( bbox );
+         } else {
+            this->box = astMapRegion( bbox, map, cfrm );
+         }
+
+         if( unc ) unc = astAnnul( unc );
+         cfrm = astAnnul( cfrm );
+         bfrm = astAnnul( bfrm );
+         map = astAnnul( map );
+         bbox = astAnnul( bbox );
+
+/* If the supplied Interval has been negated, negate the equivalent Box. */
+         if( astGetNegated( this ) ) astNegate( this->box );
+
+/* If the supplied Interval is closed, ensure the equivalent Box is closed. */
+         astSetClosed( this->box, astGetClosed( this ) );
+      }
+
+/* Store the axis limits in the Interval structure. */
+      if( this->lbnd ) astFree( this->lbnd );
+      if( this->ubnd ) astFree( this->ubnd );
+      this->lbnd = lbnd;
+      this->ubnd = ubnd;
+   }
+
+/* Indicate the cached information is no longer stale, and return a
+   pointer to any equivalent Box. */
+   this->stale = 0;
+   return this->box;
+}
+
+static int GetBounded( AstRegion *this, int *status ) {
+/*
+*  Name:
+*     GetBounded
+
+*  Purpose:
+*     Is the Region bounded?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     int GetBounded( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Interval method (over-rides the astGetBounded method inherited from
+*     the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the Region is bounded.
+*     The implementation provided by the base Region class is suitable
+*     for Region sub-classes representing the inside of a single closed
+*     curve (e.g. Circle, Interval, Box, etc). Other sub-classes (such as
+*     CmpRegion, PointList, etc ) may need to provide their own
+*     implementations.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the Region is bounded. Zero otherwise.
+
+*/
+
+/* Local Variables: */
+   int result;                /* Returned result */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* The unnegated Interval is bounded only if there is an equivalent Box
+   structure stored in the Interval structure. */
+   if( Cache( (AstInterval *) this, status ) ) result = 1;
+
+/* Return the required pointer. */
+   return result;
+}
+
+static AstRegion *GetDefUnc( AstRegion *this_region, int *status ) {
+/*
+*  Name:
+*     GetDefUnc
+
+*  Purpose:
+*     Obtain a pointer to the default uncertainty Region for a given Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     AstRegion *GetDefUnc( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Interval member function (over-rides the astGetDefUnc protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a pointer to a Region which represents the
+*     default uncertainty associated with a position on the boundary of the
+*     given Region. The returned Region refers to the base Frame within the
+*     FrameSet encapsulated by the supplied Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the Region. This should be annulled (using astAnnul)
+*     when no longer needed.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstBox *box;               /* Pointer to equivalent Box */
+   AstFrame *bfrm;            /* Base Frame of supplied Region */
+   AstInterval *this;         /* Pointer to Interval structure */
+   AstRegion *result;         /* Returned pointer */
+   double *lbnd;              /* Ptr. to array holding axis lower bounds */
+   double *ubnd;              /* Ptr. to array holding axis upper bounds */
+   double c;                  /* Central axis value */
+   double hw;                 /* Half width of uncertainty interval */
+   int i;                     /* Axis index */
+   int nax;                   /* Number of base Frame axes */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Interval structure. */
+   this = (AstInterval *) this_region;
+
+/* If this Interval is equivalent to a Box, get the default uncertainty
+   for the equivalent Box and return it. */
+   box = Cache( this, status );
+   if( box ) {
+      result = astGetDefUnc( box );
+
+/* Otherwise, we use a box covering 1.0E-6 of each axis interval, centred on
+   the origin. */
+   } else {
+
+/* Get a pointer to the base Frame. */
+      bfrm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Get the number of base Frame axes. */
+      nax = astGetNaxes( bfrm );
+
+/* Allocate arrays to hold the bounds of the uncertainty Box. */
+      lbnd = astMalloc( sizeof( double)*(size_t) nax );
+      ubnd = astMalloc( sizeof( double)*(size_t) nax );
+      if( astOK ) {
+
+/* Ensure cached information (e.g.bounds) is up to date. */
+         Cache( this, status );
+
+/* Do each axis in turn */
+         for( i = 0; i < nax; i++ ) {
+
+/* If this axis has both limits, use 1.0E-6 of the difference between the
+   limits. */
+            if( this->lbnd[ i ] != -DBL_MAX &&
+                this->ubnd[ i ] != DBL_MAX ) {
+               hw = fabs( 0.5E-6*(  this->ubnd[ i ] - this->lbnd[ i ] ) );
+               c = 0.5*(  this->ubnd[ i ] + this->lbnd[ i ] );
+               if( hw == 0.0 ) hw = c*0.5E-6;
+               ubnd[ i ] = c + hw;
+               lbnd[ i ] = c - hw;
+
+/* Otherwise use zero. */
+            } else {
+               ubnd[ i ] = 0.0;
+               lbnd[ i ] = 0.0;
+            }
+         }
+
+/* Create the Box. */
+         result = (AstRegion *) astBox( bfrm, 1, lbnd, ubnd, NULL, "", status );
+      }
+
+/* Free resources. */
+      lbnd = astFree( lbnd );
+      ubnd = astFree( ubnd );
+      bfrm = astAnnul( bfrm );
+   }
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the required pointer. */
+   return result;
+}
+
+void astInitIntervalVtab_(  AstIntervalVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitIntervalVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Interval.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     void astInitIntervalVtab( AstIntervalVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Interval vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Interval class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstRegionVtab *region;        /* Pointer to Region component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitRegionVtab( (AstRegionVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAInterval) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstRegionVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->IntervalPoints = IntervalPoints;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   region = (AstRegionVtab *) vtab;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_simplify = mapping->Simplify;
+   mapping->Simplify = Simplify;
+
+   parent_overlap = region->Overlap;
+   region->Overlap = Overlap;
+
+   parent_setregfs = region->SetRegFS;
+   region->SetRegFS = SetRegFS;
+
+   parent_resetcache = region->ResetCache;
+   region->ResetCache = ResetCache;
+
+   parent_setunc = region->SetUnc;
+   region->SetUnc = SetUnc;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   mapping->MapMerge = MapMerge;
+
+   region->RegCentre = RegCentre;
+   region->GetBounded = GetBounded;
+   region->GetDefUnc = GetDefUnc;
+   region->RegPins = RegPins;
+   region->RegTrace = RegTrace;
+   region->RegBaseMesh = RegBaseMesh;
+   region->BndBaseMesh = BndBaseMesh;
+   region->RegBaseBox = RegBaseBox;
+   region->RegBasePick = RegBasePick;
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "Interval", "Axis intervals" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+void IntervalPoints( AstInterval *this, double *lbnd, double *ubnd,
+                     int *status) {
+/*
+*+
+*  Name:
+*     astIntervalPoints
+
+*  Purpose:
+*     Return the defining points of a Interval.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "box.h"
+*     astIntervalPoints( AstInterval *this, double *lbnd, double *ubnd )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns the axis values at the points defining the
+*     supplied Interval.
+
+*  Parameters:
+*     this
+*        Pointer to the Interval.
+*     lbnd
+*        A pointer to an array in which to return the "lbnd" values
+*        supplied when the Interval was constructed. These are in the
+*        base Frame of the encapsilated FrameSet.
+*     ubnd
+*        A pointer to an array in which to return the "ubnd" values
+*        supplied when the Interval was constructed. These are in the
+*        base Frame of the encapsilated FrameSet.
+
+*  Notes:
+*     - It is assumed that the length of the supplied arrays is at least
+*     equal to the number of axes in the base frame of the encapsulated
+*     FrameSet.
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *pset;
+   double **ptr;
+   int nc;
+   int i;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the PointSet holding the points defining the Interval. */
+   pset = ((AstRegion *) this)->points;
+
+/* Get a pointer to the PointSet's data arrays. */
+   ptr = astGetPoints( pset );
+
+/* See how many axes each point in the PointSet has. */
+   nc = astGetNcoord( pset );
+
+/* Copy the axis values from the PointSet into the supplied arrays. */
+   for( i = 0; i < nc; i++ ) {
+      lbnd[ i ] = ptr[ i ] [ 0 ];
+      ubnd[ i ] = ptr[ i ] [ 1 ];
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     Interval member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstInterval *this;       /* Pointer to Interval structure */
+   int result;              /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the Interval structure. */
+   this = (AstInterval *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->box, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a Interval.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     Interval method (over-rides the protected astMapMerge method
+*     inherited from the Region class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated Interval in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated Interval with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated Interval which is to be merged with
+*        its neighbours. This should be a cloned copy of the Interval
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        Interval it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated Interval resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstInterval *oldint;  /* Pointer to supplied Interval */
+   AstMapping *map;      /* Pointer to adjacent Mapping */
+   AstMapping *new;      /* Simplified or merged Region */
+   int i1;               /* Index of first Mapping merged */
+   int i;                /* Loop counter */
+   int result;           /* Result value to return */
+
+/* Initialise. */
+   result = -1;
+   i1 = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Interval. */
+   oldint = (AstInterval *) this;
+
+/* First of all, see if the Interval can be replaced by a simpler Region,
+   without reference to the neighbouring Regions in the list.           */
+/* =====================================================================*/
+
+/* Try to simplify the Interval. If the pointer value has changed, we assume
+   some simplification took place. */
+   new = astSimplify( oldint );
+   if( new != (AstMapping *) oldint ) {
+
+/* Annul the Interval pointer in the list and replace it with the new Region
+   pointer, and indicate that the forward transformation of the returned
+   Region should be used (not really needed but keeps things clean). */
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = new;
+      ( *invert_list )[ where ] = 0;
+
+/* Return the index of the first modified element. */
+      result = where;
+
+/* If the Interval itself could not be simplified, see if it can be merged
+   with the Regions on either side of it in the list. We can only merge
+   in parallel. */
+/* =====================================================================*/
+   } else if( ! series ){
+      new = astAnnul( new );
+
+/* Attempt to merge the Interval with its lower neighbour (if any). */
+      if( where > 0 ) {
+         i1 = where - 1;
+         map = ( *map_list )[ where - 1 ];
+         if( astIsARegion( map ) ) {
+            new = (AstMapping *) MergeInterval( oldint, (AstRegion *) map,
+                                                0, status );
+         }
+      }
+
+/* If this did not produced a merged Region, attempt to merge the Interval
+   with its upper neighbour (if any). */
+      if( !new && where < *nmap - 1 ) {
+         i1 = where;
+         map = ( *map_list )[ where + 1 ];
+         if( astIsARegion( map ) ) {
+            new = (AstMapping *) MergeInterval( oldint, (AstRegion *) map,
+                                                1, status );
+         }
+      }
+
+/* If succesfull... */
+      if( new ){
+
+/* Annul the first of the two Mappings, and replace it with the merged
+   Region. Also clear the invert flag. */
+         (void) astAnnul( ( *map_list )[ i1 ] );
+         ( *map_list )[ i1 ] = new;
+         ( *invert_list )[ i1 ] = 0;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+         (void) astAnnul( ( *map_list )[ i1 + 1 ] );
+         for ( i = i1 + 2; i < *nmap; i++ ) {
+            ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+            ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+         }
+
+/* Clear the vacated element at the end. */
+         ( *map_list )[ *nmap - 1 ] = NULL;
+         ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+         ( *nmap )--;
+         result = i1;
+      }
+
+   } else {
+      new = astAnnul( new );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstRegion *MergeInterval( AstInterval *this, AstRegion *reg,
+                                 int intfirst, int *status ) {
+/*
+*  Name:
+*     MergeInterval
+
+*  Purpose:
+*     Attempt to merge a Interval with another Region to form a Region of
+*     higher dimensionality.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "box.h"
+*     AstRegion *MergeInterval( AstInterval *this, AstRegion *reg,
+*                               int intfirst, int *status )
+
+*  Class Membership:
+*     Interval member function.
+
+*  Description:
+*     This function attempts to combine the supplied Regions together
+*     into a Region of higher dimensionality.
+
+*  Parameters:
+*     this
+*        Pointer to a Interval.
+*     reg
+*        Pointer to another Region.
+*     intfirst
+*        If non-zero, then the Interval axes are put first in the new Region.
+*        Otherwise, the other Region's axes are put first.
+*     status
+*        Pointer to the inherited status value.
+
+*  Returned Value:
+*     A pointer to a new region, or NULL if the supplied Regions could
+*     not be merged.
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;           /* Pointer to base Frame for "result" */
+   AstFrame *cfrm;           /* Pointer to current Frame for "result" */
+   AstFrame *frm_reg;        /* Pointer to Frame from "reg" */
+   AstFrame *frm_this;       /* Pointer to Frame from "this" */
+   AstMapping *bcmap;        /* Base->current Mapping for "result" */
+   AstMapping *map_reg;      /* Base->current Mapping from "reg" */
+   AstMapping *map_this;     /* Base->current Mapping from "this" */
+   AstMapping *sbunc;        /* Simplified uncertainty */
+   AstPointSet *pset_new;    /* PointSet holding PointList axis values for new */
+   AstPointSet *pset_reg;    /* PointSet holding PointList axis values for reg */
+   AstRegion *bunc;          /* Base Frame uncertainty Region */
+   AstRegion *new;           /* Pointer to new Interval in base Frame */
+   AstRegion *result;        /* Pointer to returned Interval in current Frame */
+   AstRegion *unc_reg;       /* Current Frame uncertainty Region from "reg" */
+   AstRegion *unc_this;      /* Current Frame uncertainty Region from "this" */
+   double **ptr_new;         /* Pointers to arrays holding new axis values */
+   double **ptr_reg;         /* Pointers to arrays holding reg axis values */
+   double *centre;           /* Array to hold Interval centre axis values */
+   double *corner;           /* Array to hold Interval corner axis values */
+   double *lbnd;             /* Array to hold lower axis bounds */
+   double *lbnd_unc;         /* Array to hold uncertainty lower bounds */
+   double *p;                /* Pointer to next input value */
+   double *q;                /* Pointer to next output value */
+   double *ubnd;             /* Array to hold upper axis bounds */
+   double *ubnd_unc;         /* Array to hold uncertainty upper bounds */
+   double fac_reg;           /* Ratio of used to default MeshSize for "reg" */
+   double fac_this;          /* Ratio of used to default MeshSize for "this" */
+   double temp;              /* Temporary storage */
+   int i;                    /* Loop count */
+   int j;                    /* Loop count */
+   int msz_reg;              /* Original MeshSize for "reg" */
+   int msz_reg_set;          /* Was MeshSize originally set for "reg"? */
+   int msz_this;             /* Original MeshSize for "this" */
+   int msz_this_set;         /* Was MeshSize originally set for "this"? */
+   int nax;                  /* Number of axes in "result" */
+   int nax_reg;              /* Number of axes in "reg" */
+   int nax_this;             /* Number of axes in "this" */
+   int neg_reg;              /* Negated attribute value for other supplied Region */
+   int neg_this;             /* Negated attribute value for supplied Interval  */
+   int npnt;                 /* Number of points in PointList */
+   int ok;                   /* Can supplied Regions be merged? */
+
+/* Initialise */
+   result = NULL;
+   lbnd = NULL;
+   ubnd = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get the Closed attributes of the two Regions. They must be the same in
+   each Region if we are to merge the Regions. In addition, in order to
+   merge, either both Regions must have a defined uncertainty, or neither
+   Region must have a defined Uncertainty. */
+   if( astGetClosed( this ) == astGetClosed( reg ) &&
+       astTestUnc( this ) == astTestUnc( reg ) ) {
+
+/* Get the Nagated attributes of the two Regions. */
+      neg_this = astGetNegated( this );
+      neg_reg = astGetNegated( reg );
+
+/* Get the number of axes in the two supplied Regions. */
+      nax_reg = astGetNaxes( reg );
+      nax_this = astGetNaxes( this );
+
+/* If the Regions can be combined, get the number of axes the
+   combination will have. */
+      nax = nax_reg + nax_this;
+
+/* Get the base Frames from the two Region FrameSets, and combine them
+   into a single CmpFrame that will be used to create any new Region. */
+      frm_this = astGetFrame( ((AstRegion *) this)->frameset, AST__BASE );
+      frm_reg = astGetFrame( reg->frameset, AST__BASE );
+
+      if( intfirst ) {
+         bfrm = (AstFrame *) astCmpFrame( frm_this, frm_reg, "", status );
+      } else {
+         bfrm = (AstFrame *) astCmpFrame( frm_reg, frm_this, "", status );
+      }
+
+      frm_this = astAnnul( frm_this );
+      frm_reg = astAnnul( frm_reg );
+
+/* Indicate we do not yet have a merged Region. */
+      new = NULL;
+
+/* First attempt to merge with another Interval. The result will be an
+   Interval. Both Intervals must be un-negated. */
+      if( astIsAInterval( reg ) && !neg_this && !neg_reg ) {
+
+/* Allocate memory to store the bounds of the returned Interval. */
+         lbnd = astMalloc( sizeof( double )*(size_t) nax );
+         ubnd = astMalloc( sizeof( double )*(size_t) nax );
+
+/* Copy the limits from the supplied Intervals into the above arrays,
+   in the requested order. */
+         if( intfirst ) {
+            astIntervalPoints( this, lbnd, ubnd );
+            astIntervalPoints( reg, lbnd + nax_this, ubnd + nax_this );
+         } else {
+            astIntervalPoints( reg, lbnd, ubnd );
+            astIntervalPoints( this, lbnd + nax_reg, ubnd + nax_reg );
+         }
+
+/*  Create the new Interval, initially with no uncertainty. */
+         new = (AstRegion *) astInterval( bfrm, lbnd, ubnd, NULL, "",
+                                          status );
+
+/* Free resources .*/
+         lbnd = astFree( lbnd );
+         ubnd = astFree( ubnd );
+
+/* Now attempt to merge with a Box. The result will be an Interval. Both
+   Regions must be un-negated. */
+      } else if( astIsABox( reg ) && !neg_this && !neg_reg ) {
+
+/* Allocate memory to store the bounds of the returned Interval. */
+         lbnd = astMalloc( sizeof( double )*(size_t) nax );
+         ubnd = astMalloc( sizeof( double )*(size_t) nax );
+
+/* Get the bounds from the Interval and add them into the above arrays. */
+         if( intfirst ) {
+            astIntervalPoints( this, lbnd, ubnd );
+         } else {
+            astIntervalPoints( this, lbnd + nax_reg, ubnd + nax_reg );
+         }
+
+/* Copy the centre and corner from the supplied Box into the required part
+   of the above arrays. */
+         if( intfirst ) {
+            centre = lbnd + nax_this;
+            corner = ubnd + nax_this;
+         } else {
+            centre = lbnd;
+            corner = ubnd;
+         }
+         astBoxPoints( reg, centre, corner );
+
+/* Convert these centre and corner positions into upper and lower bounds. */
+         if( astOK ) {
+            for( i = 0; i < nax_reg; i++ ) {
+               centre[ i ] = 2*centre[ i ] - corner[ i ];
+               if( centre[ i ] > corner[ i ] ) {
+                  temp = centre[ i ];
+                  centre[ i ] = corner[ i ];
+                  corner[ i ] = temp;
+               }
+            }
+         }
+
+/*  Create the new Interval, initially with no uncertainty. */
+         new = (AstRegion *) astInterval( bfrm, lbnd, ubnd, NULL, "",
+                                          status );
+
+/* Free resources .*/
+         lbnd = astFree( lbnd );
+         ubnd = astFree( ubnd );
+
+/* Now attempt to merge with a NullRegion. The result will be an Interval.
+   The NullRegion must be negated and the Interval must not. */
+      } else if( astIsANullRegion( reg ) && !neg_this && neg_reg ) {
+
+/* Allocate memory to store the bounds of the returned Interval. */
+         lbnd = astMalloc( sizeof( double )*(size_t) nax );
+         ubnd = astMalloc( sizeof( double )*(size_t) nax );
+
+/* Copy the limits from the supplied Interval into the above arrays.
+   Store bad values for the other axes indicating they are unbounded. */
+         if( intfirst ) {
+            astIntervalPoints( this, lbnd, ubnd );
+            for( i = nax_this; i < nax; i++ ) {
+               lbnd[ i ] = AST__BAD;
+               ubnd[ i ] = AST__BAD;
+            }
+         } else {
+            for( i = 0; i < nax_reg; i++ ) {
+               lbnd[ i ] = AST__BAD;
+               ubnd[ i ] = AST__BAD;
+            }
+            astIntervalPoints( this, lbnd + nax_reg, ubnd + nax_reg );
+         }
+
+/*  Create the new Interval, initially with no uncertainty. */
+         new = (AstRegion *) astInterval( bfrm, lbnd, ubnd, NULL, "",
+                                          status );
+
+/* Free resources .*/
+         lbnd = astFree( lbnd );
+         ubnd = astFree( ubnd );
+
+/* Now attempt to merge with a PointList. The result will be a PointList.
+   Both Regions must be un-negated. */
+      } else if( astIsAPointList( reg ) && !neg_this && !neg_reg ) {
+
+/* We can only do this if the Interval has zero width on each axis (i.e.
+   represents a point). Get the Interval bounds.  */
+         lbnd = astMalloc( sizeof( double )*(size_t) nax_this );
+         ubnd = astMalloc( sizeof( double )*(size_t) nax_this );
+         astRegBaseBox( this, lbnd, ubnd );
+
+/* Get the size of the Interval's uncertainty region. */
+         lbnd_unc = astMalloc( sizeof( double )*(size_t) nax_this );
+         ubnd_unc = astMalloc( sizeof( double )*(size_t) nax_this );
+         bunc = astGetUncFrm( this, AST__BASE );
+         astGetRegionBounds( bunc, lbnd, ubnd );
+
+/* Set "ok" to zero if the Interval does not have zero width on any axis. Here
+   "zero width" means a width less than half the uncertainty on the axis.
+   We also replace the lower bound values in the "lbnd" array by the central
+   values in the Interval. */
+         if( astOK ) {
+            ok = 1;
+            for( i = 0; i < nax_this; i++ ) {
+               if( fabs( lbnd[ i ] - lbnd[ i ] ) >
+                   0.25*fabs( ubnd_unc[ i ] - lbnd_unc[ i ] ) ) {
+                  ok = 0;
+                  break;
+               } else {
+                  lbnd[ i ] = 0.5*( lbnd[ i ] + ubnd[ i ] );
+               }
+            }
+
+/* If the Interval is a point, we go on to create a new PointList. */
+            if( ok ) {
+
+/* Get a PointSet holding the axis values in the supplied PointList data.
+   Also get the number of points in the PointSet and pointers to the arrays
+   holding the axis values. */
+               astPointListPoints( reg, &pset_reg );
+               npnt = astGetNpoint( pset_reg );
+               ptr_reg = astGetPoints( pset_reg );
+
+/*  Create a new PointSet with room for the same number of points, but
+    with the extra required axes. Get pointers to its axis arrays. */
+               pset_new = astPointSet( npnt, nax, "", status );
+               ptr_new = astGetPoints( pset_new );
+
+/* Copy the PointList axis values into the new PointSet, and then include
+   the extra axis values defined by the Interval to each point. */
+               if( astOK ) {
+
+                  for( j = 0; j < nax_reg; j++ ) {
+                     p = ptr_reg[ j ];
+                     q = ptr_new[ intfirst ? nax_this + j : j ];
+                     for( i = 0; i < npnt; i++ ) *(q++) = *(p++);
+                  }
+
+                  for( j = 0; j < nax_this; j++ ) {
+                     p = lbnd + j;
+                     q = ptr_new[ intfirst ? j : nax_reg + j ];
+                     for( i = 0; i < npnt; i++ ) *(q++) = *p;
+                  }
+
+/*  Create the new PointList, initially with no uncertainty. */
+                  new = (AstRegion *) astPointList( bfrm, pset_new, NULL,
+                                                    "", status );
+               }
+
+/* Free resources .*/
+               pset_new = astAnnul( pset_new );
+               pset_reg = astAnnul( pset_reg );
+            }
+         }
+         lbnd = astFree( lbnd );
+         ubnd = astFree( ubnd );
+         lbnd_unc = astFree( lbnd_unc );
+         ubnd_unc = astFree( ubnd_unc );
+         bunc = astAnnul( bunc );
+
+      }
+
+/* If a new Region was created above, propagate remaining attributes of
+   the supplied Region to it. */
+      if( new ) {
+         astRegOverlay( new, this, 1 );
+
+/* The above Prism constructors create the Prism with the correct value
+   for the Nagated attribute (i.e. zero). Ensure the above call to
+   astRegOverlay has not changed this. */
+         astClearNegated( new );
+
+/* If both the supplied Regions have uncertainty, assign the new Region an
+   uncertainty. */
+         if( astTestUnc( this ) && astTestUnc( reg ) ) {
+
+/* Get the uncertainties from the two supplied Regions. */
+            unc_this = astGetUncFrm( this, AST__BASE );
+            unc_reg = astGetUncFrm( reg, AST__BASE );
+
+/* Combine them into a single Region (a Prism), in the correct order. */
+            if( intfirst ) {
+               bunc = (AstRegion *) astPrism( unc_this, unc_reg, "", status );
+            } else {
+               bunc = (AstRegion *) astPrism( unc_reg, unc_this, "", status );
+            }
+
+/* Attempt to simplify the Prism. */
+            sbunc = astSimplify( bunc );
+
+/* Use the simplified Prism as the uncertainty for the returned Region. */
+            astSetUnc( new, sbunc );
+
+/* Free resources. */
+            sbunc = astAnnul( sbunc );
+            bunc = astAnnul( bunc );
+            unc_reg = astAnnul( unc_reg );
+            unc_this = astAnnul( unc_this );
+         }
+
+/* Get the current Frames from the two Region FrameSets, and combine them
+   into a single CmpFrame. */
+         frm_this = astGetFrame( ((AstRegion *) this)->frameset, AST__CURRENT );
+         frm_reg = astGetFrame( reg->frameset, AST__CURRENT );
+
+         if( intfirst ) {
+            cfrm = (AstFrame *) astCmpFrame( frm_this, frm_reg, "", status );
+         } else {
+            cfrm = (AstFrame *) astCmpFrame( frm_reg, frm_this, "", status );
+         }
+
+/* Get the base -> current Mappings from the two Region FrameSets, and
+   combine them into a single parallel CmpMap that connects bfrm and cfrm. */
+         map_this = astGetMapping( ((AstRegion *) this)->frameset, AST__BASE,
+                                   AST__CURRENT );
+         map_reg = astGetMapping( reg->frameset, AST__BASE, AST__CURRENT );
+
+         if( intfirst ) {
+            bcmap = (AstMapping *) astCmpMap( map_this, map_reg, 0, "",
+                                              status );
+         } else {
+            bcmap = (AstMapping *) astCmpMap( map_reg, map_this, 0, "",
+                                              status );
+         }
+
+/* Map the new Region into the new current Frame. */
+         result = astMapRegion( new, bcmap, cfrm );
+
+/* The filling factor in the returned is the product of the filling
+   factors for the two supplied Regions. */
+         if( astTestFillFactor( reg ) || astTestFillFactor( this ) ) {
+            astSetFillFactor( result, astGetFillFactor( reg )*
+                                      astGetFillFactor( this ) );
+         }
+
+/* If the MeshSize value is set in either supplied Region, set a value
+   for the returned Region which scales the default value by the
+   product of the scaling factors for the two supplied Regions. First see
+   if either MeshSize value is set. */
+         msz_this_set = astTestMeshSize( this );
+         msz_reg_set = astTestMeshSize( reg );
+         if( msz_this_set || msz_reg_set ) {
+
+/* If so, get the two MeshSize values (one of which may be a default
+   value), and then clear them so that the default value will be returned
+   in future. */
+            msz_this = astGetMeshSize( this );
+            msz_reg = astGetMeshSize( reg );
+            astClearMeshSize( this );
+            astClearMeshSize( reg );
+
+/* Get the ratio of the used MeshSize to the default MeshSize for both
+   Regions. */
+            fac_this = (double)msz_this/(double)astGetMeshSize( this );
+            fac_reg = (double)msz_reg/(double)astGetMeshSize( reg );
+
+/* The MeshSize of the returned Returned is the default value scaled by
+   the product of the two ratios found above. */
+            astSetMeshSize( result, fac_this*fac_reg*astGetMeshSize( result ) );
+
+/* Re-instate the original MeshSize values for the supplied Regions (if
+   set) */
+            if( msz_this_set ) astSetMeshSize( this, msz_this );
+            if( msz_reg_set ) astSetMeshSize( reg, msz_reg );
+         }
+
+/* Free remaining resources */
+         frm_this = astAnnul( frm_this );
+         frm_reg = astAnnul( frm_reg );
+         map_this = astAnnul( map_this );
+         map_reg = astAnnul( map_reg );
+         bcmap = astAnnul( bcmap );
+         new = astAnnul( new );
+         cfrm = astAnnul( cfrm );
+      }
+      bfrm = astAnnul( bfrm );
+
+   }
+
+/* If an error has occurred, annul the returned pointer. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int *OneToOne( AstMapping *map, int *status ){
+/*
+*  Name:
+*     OneToOne
+
+*  Purpose:
+*     Does each output of the supplied Mapping depend on only one input?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     int OneToOne( AstMapping *map, int *status )
+
+*  Class Membership:
+*     Interval method
+
+*  Description:
+*     This function returns a flag indicating if the Mapping is 1-to-1.
+*     That is, if each output depends only on one input.
+
+*  Parameters:
+*     map
+*        Pointer to the Mapping.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If the Mapping is 1-to-1, a pointer to an array of ints is returned
+*     (NULL is returned otherwise). There is one int for each output of
+*     the supplied Mapping. The value of each int is the index of the
+*     corresponding input which feeds the output. The array should be
+*     freed using astFree when no longer needed.
+
+*/
+
+/* Local Variables: */
+   int *result;
+   const char *class;
+   int nout;
+   int i;
+   int *tt;
+   AstMapping *tmap;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the number of outputs for the Mapping. */
+   nout = astGetNout( map );
+
+/* The Mapping cannot be 1-to-1 if the number of inputs is different.*/
+   if( astGetNin( map ) == nout ) {
+
+/* Allocate an output array on the assumption that the Mapping is 1-to-1. */
+      result = astMalloc( sizeof( int )*(size_t) nout );
+      if( result ) {
+
+/* Check known specal cases for speed. */
+         class = astGetClass( map );
+         if( !strcmp( class, "WinMap" ) ||
+             !strcmp( class, "ZoomMap" ) ||
+             !strcmp( class, "UnitMap" ) ||
+             !strcmp( class, "ShiftMap" ) ){
+
+/* Each output is fed by the corresponding input for these classes of
+   Mapping. */
+            for( i = 0; i < nout; i++ ) result[ i ] = i;
+
+/* Now do the general case. */
+         } else {
+
+/* Loop round each input axis. */
+            for( i = 0; i < nout; i++ ) {
+
+/* Use astMapSplit to see if this input corresponds to a single output. */
+               tt = astMapSplit( map, 1, &i, &tmap );
+
+/* If not, annul the returned array and break. */
+               if( !tmap ) {
+                  result = astFree( result );
+                  break;
+
+/* If so, store the index of the corresponding input in the returned
+   array and free resources. */
+               } else {
+                 result[ tt[ 0 ] ] = i;
+                 tt = astFree( tt );
+                 if( astGetNout( tmap ) != 1 ) result = astFree( result );
+                 tmap = astAnnul( tmap );
+                 if( !result ) break;
+               }
+            }
+         }
+      }
+   }
+
+/* Return the result */
+   return result;
+}
+
+static int Overlap( AstRegion *this, AstRegion *that, int *status ){
+/*
+*  Name:
+*     Overlap
+
+*  Purpose:
+*     Test if two regions overlap each other.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     int Overlap( AstRegion *this, AstRegion *that, int *status )
+
+*  Class Membership:
+*     Interval member function (over-rides the astOverlap method inherited
+*     from the Region class).
+
+*  Description:
+*     This function returns an integer value indicating if the two
+*     supplied Regions overlap. The two Regions are converted to a commnon
+*     coordinate system before performing the check. If this conversion is
+*     not possible (for instance because the two Regions represent areas in
+*     different domains), then the check cannot be performed and a zero value
+*     is returned to indicate this.
+
+*  Parameters:
+*     this
+*        Pointer to the first Region.
+*     that
+*        Pointer to the second Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     astOverlap()
+*        A value indicating if there is any overlap between the two Regions.
+*        Possible values are:
+*
+*        0 - The check could not be performed because the second Region
+*            could not be mapped into the coordinate system of the first
+*            Region.
+*
+*        1 - There is no overlap between the two Regions.
+*
+*        2 - The first Region is completely inside the second Region.
+*
+*        3 - The second Region is completely inside the first Region.
+*
+*        4 - There is partial overlap between the two Regions.
+*
+*        5 - The Regions are identical.
+*
+*        6 - The second Region is the negation of the first Region.
+
+*  Notes:
+*     - The returned values 5 and 6 do not check the value of the Closed
+*     attribute in the two Regions.
+*     - A value of zero will be returned if this function is invoked with the
+*     AST error status set, or if it should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstFrameSet *fs;
+   AstMapping *map;
+   AstMapping *map1;
+   AstMapping *map2;
+   AstMapping *map3;
+   AstMapping *smap;
+   AstMapping *tmap;
+   AstPointSet *pset_that;
+   AstRegion *unc_temp;
+   AstRegion *unc_that;
+   AstRegion *unc_this;
+   double **ptr_that;
+   double **ptr_thato;
+   double **ptr_this;
+   double *lbndu_that;
+   double *lbndu_this;
+   double *ubndu_that;
+   double *ubndu_this;
+   double err;
+   double err_that;
+   double err_this;
+   double lb_that;
+   double lb_this;
+   double tmp;
+   double ub_that;
+   double ub_this;
+   int *outperm;
+   int ic;
+   int inc_that;
+   int inc_this;
+   int lb_equal;
+   int nc;
+   int neg_that;
+   int neg_this;
+   int ov;
+   int result;
+   int ub_equal;
+
+   static int newResult[ 5 ][ 5 ] = { { 1, 1, 1, 1, 1},
+                                      { 1, 2, 4, 4, 2},
+                                      { 1, 4, 3, 4, 3},
+                                      { 1, 4, 4, 4, 4},
+                                      { 1, 2, 3, 4, 5} };
+
+/* Initialise */
+   result = 0;
+
+/* Check the inherited status. */
+   if ( !astOK ) return result;
+
+/* If both Regions are Intervals, we provide a specialised implementation.
+   The implementation in the parent Region class assumes that at least one of
+   the two Regions can be represented using a finite mesh of points on the
+   boundary which is not the case with Intervals. The implementation in this
+   class sees if the Mapping between the base Frames of the Intervals allows
+   the axis limits to be transferred from one Frame to the other. */
+   if( astIsAInterval( this ) && astIsAInterval( that ) ) {
+
+/* Get a FrameSet which connects the Frame represented by the second Interval
+   to the Frame represented by the first Interval. Check that the conection is
+   defined. */
+      fs = astConvert( that, this, "" );
+      if( fs ) {
+
+/* Get a pointer to the Mapping from base to current Frame in the second
+   Interval */
+         map1 = astGetMapping( that->frameset, AST__BASE, AST__CURRENT );
+
+/* Get the Mapping from the current Frame of the second Interval to the
+   current Frame of the first Interval. */
+         map2 = astGetMapping( fs, AST__BASE, AST__CURRENT );
+
+/* Get a pointer to the Mapping from current to base Frame in the first
+   Interval. */
+         map3 = astGetMapping( this->frameset, AST__CURRENT, AST__BASE );
+
+/* Combine these Mappings to get the Mapping from the base Frame of the
+   second Interval to the base Frame of the first Interval. */
+         tmap = (AstMapping *) astCmpMap( map1, map2, 1, "", status );
+         map = (AstMapping *) astCmpMap( tmap, map3, 1, "", status );
+
+/* Simplify this Mapping. */
+         smap = astSimplify( map );
+
+/* We can only proceed if each output of the simplified Mapping depends
+   on only one input. Test this. */
+         outperm = OneToOne( smap, status );
+         if( outperm ){
+
+/* Get the uncertainty Regions for both Intervals, expressed in the base
+   Frames of the Intervals. */
+            unc_this = astGetUncFrm( this, AST__BASE );
+            unc_temp = astGetUncFrm( that, AST__BASE );
+
+/* Map the uncertainty Region for the second Interval from the base Frame
+   of the second Interval into the base Frame of the first Interval. */
+            frm = astGetFrame( this->frameset, AST__BASE );
+            unc_that = astMapRegion( unc_temp, smap, frm );
+
+/* Get the bounding boxes of the two uncertainty Regions in the base
+   Frame of the first Interval. */
+            nc = astGetNaxes( frm );
+            lbndu_this = astMalloc( sizeof( double )*(size_t)nc );
+            ubndu_this = astMalloc( sizeof( double )*(size_t)nc );
+            astGetRegionBounds( unc_this, lbndu_this, ubndu_this );
+
+            lbndu_that = astMalloc( sizeof( double )*(size_t)nc );
+            ubndu_that = astMalloc( sizeof( double )*(size_t)nc );
+            astGetRegionBounds( unc_that, lbndu_that, ubndu_that );
+
+/* Transform the PointSet holding the limits for the second Interval into
+   the Frame of the first Interval. */
+            pset_that = astTransform( smap, that->points, 1, NULL );
+
+/* Get pointers for accesing the limits of the two Intervals, expressed
+   in a common Frame (the base Frame of the first Interval). */
+            ptr_that = astGetPoints( pset_that );
+            ptr_thato = astGetPoints( that->points );
+            ptr_this = astGetPoints( this->points );
+            if( astOK ) {
+
+/* Check the limits on each base Frame axis in turn. */
+               for( ic = 0; ic < nc; ic++ ) {
+
+/* Get the widths of the two uncertainty boxes on this axis. */
+                  err_this = ubndu_this[ ic ] - lbndu_this[ ic ];
+                  err_that = ubndu_that[ ic ] - lbndu_that[ ic ];
+
+/* Add this together in quadrature to get the tolerance for two values on
+   the current axis to be considered equal. */
+                  err = sqrt( err_that*err_that + err_this*err_this );
+
+/* Get the limits on this axis from both Intervals. */
+                  lb_this = ptr_this[ ic ][ 0 ];
+                  ub_this = ptr_this[ ic ][ 1 ];
+                  lb_that = ptr_that[ ic ][ 0 ];
+                  ub_that = ptr_that[ ic ][ 1 ];
+
+/* The limits for "that" have been mapped, which may have resulted in
+   them being swapped. We need to unswap them in this case to prevent the
+   swapping being used as an indication of a desire to use an excluded
+   interval rather than an included interval. */
+                   if( lb_that != AST__BAD && ub_that != AST__BAD ) {
+                      if( ptr_thato[ ic ][ 0 ] < ptr_thato[ ic ][ 1 ] ) {
+                         if( lb_that > ub_that ) {
+                            tmp = lb_that;
+                            lb_that = ub_that;
+                            ub_that = tmp;
+                         }
+                      } else {
+                         if( lb_that < ub_that ) {
+                            tmp = lb_that;
+                            lb_that = ub_that;
+                            ub_that = tmp;
+                         }
+                      }
+                   }
+
+/* If the regions are not closed, reduce the limits by the smallest
+   amount possible. */
+                  if( !astGetClosed( that ) ) {
+                     if( lb_that != AST__BAD && lb_that < DBL_MAX )
+                         lb_that += DBL_EPSILON*fabs(lb_that);
+                     if( ub_that != AST__BAD && ub_that > -DBL_MAX )
+                         ub_that -= DBL_EPSILON*fabs(ub_that);
+                  }
+                  if( !astGetClosed( this ) ) {
+                     if( lb_this != AST__BAD && lb_this < DBL_MAX )
+                         lb_this += DBL_EPSILON*fabs(lb_this);
+                     if( ub_this != AST__BAD && ub_this > -DBL_MAX )
+                         ub_this -= DBL_EPSILON*fabs(ub_this);
+                  }
+
+/* Replace any missing limits with suitable extreme values */
+                  if( lb_this == AST__BAD ) lb_this = -DBL_MAX;
+                  if( ub_this == AST__BAD ) ub_this = DBL_MAX;
+                  if( lb_that == AST__BAD ) lb_that = -DBL_MAX;
+                  if( ub_that == AST__BAD ) ub_that = DBL_MAX;
+
+/* If the bounds are the wrong way round (indicating an excluded rather
+   than an included axis range), swap them. Also set a flag indicating if
+   the limits define an included or excluded range. */
+                  inc_this = ( lb_this <= ub_this );
+                  if( !inc_this ) {
+                     tmp = lb_this;
+                     lb_this = ub_this;
+                     ub_this = tmp;
+                  }
+
+                  inc_that = ( lb_that <= ub_that );
+                  if( !inc_that ) {
+                     tmp = lb_that;
+                     lb_that = ub_that;
+                     ub_that = tmp;
+                  }
+
+
+/* Are the lower limits from the two Intervals effectively equal? Take care
+   about DBL_MAX values causing overflow. */
+                  lb_equal = astEQUALS( lb_this, lb_that, 1.0E9 );
+
+                  if( !lb_equal && fabs(lb_this) != DBL_MAX &&
+                                   fabs(lb_that) != DBL_MAX ) {
+                     lb_equal = ( fabs( lb_this - lb_that) <= err );
+                  }
+
+/* Are the upper limits from the two Intervals effectively equal? */
+                  ub_equal = astEQUALS( ub_this, ub_that, 1.0E9 );
+                  if( !ub_equal && fabs(ub_this) != DBL_MAX &&
+                                   fabs(ub_that) != DBL_MAX ) {
+                     ub_equal = ( fabs( ub_this - ub_that) <= err );
+                  }
+
+
+
+/* If both the limits on this axis are effectively equal for the two Intervals,
+   set "ov" to 5 if both Interval ranges are inclusive or both are exclusive,
+   and set "ov" to 6 if one Interval range is exclusive and the other is
+   inclusive. */
+                  if( lb_equal && ub_equal ) {
+                     ov = ( inc_this == inc_that ) ? 5 : 6;
+
+/* See if the limits on this axis indicate overlap for the two Intervals. "ov"
+   is set to 1 if there is no overlap, 2 if the first Interval range is
+   completely inside the second Interval range, 3 if the second Interval
+   range is completely inside the first Interval range, and 4 if there is
+   partial overlap between the Interval ranges. */
+                  } else if( inc_this ) {
+                     if( inc_that ) {
+                        if( lb_that <= lb_this && ub_that >= ub_this ) {
+                           ov = 2;
+                        } else if( lb_that >= lb_this && ub_that <= ub_this ) {
+                           ov = 3;
+                        } else if( ub_that >= lb_this && lb_that <= ub_this ) {
+                           ov = 4;
+                        } else {
+                           ov = 1;
+                        }
+
+                     } else {
+
+                        if( lb_that <= lb_this && ub_that >= ub_this ) {
+                           ov = 1;
+                        } else if( lb_that >= ub_this || ub_that <= lb_this ) {
+                           ov = 2;
+                        } else if( lb_this == -DBL_MAX && ub_this == DBL_MAX ) {
+                           ov = 3;
+                        } else {
+                           ov = 4;
+                        }
+                     }
+
+                  } else {
+
+                     if( inc_that ) {
+                        if( lb_this <= lb_that && ub_this >= ub_that ) {
+                           ov = 1;
+                        } else if( lb_this >= ub_that || ub_this <= lb_that ) {
+                           ov = 3;
+                        } else if( lb_that == -DBL_MAX && ub_that == DBL_MAX ) {
+                           ov = 2;
+                        } else {
+                           ov = 4;
+                        }
+
+                     } else {
+                        ov = 4;
+                     }
+                  }
+
+/* The returned value is initialised on the basis of the first axis
+   overlap. */
+                  if( ic == 0 ) {
+                     result = ov;
+
+/* For subsequent axes, combine the old result value with the new ov value
+   to get the new result value. */
+                  } else {
+                     result = newResult[ result - 1 ][ ov - 1 ];
+                  }
+
+/* If we now know there is no overlap, there is no point in checking any
+   remaining axes. */
+                  if( result == 1 ) break;
+
+               }
+
+/* The above logic assumed that neither of the Intervals has been negated.
+   Decide on the value to return, taking into account whether either of
+   the Intervals has been negated. */
+               neg_this = astGetNegated( this );
+               neg_that = astGetNegated( that );
+
+               if( result == 1 ) {
+                  if( neg_this ) {
+                     result = neg_that ? 4 : 3;
+                  } else if( neg_that ){
+                     result = 2;
+                  }
+
+               } else if( result == 2) {
+                  if( neg_this ) {
+                     result = neg_that ? 3 : 4;
+                  } else if( neg_that ){
+                     result = 1;
+                  }
+
+               } else if( result == 3) {
+                  if( neg_this ) {
+                     result = neg_that ? 2 : 1;
+                  } else if( neg_that ){
+                     result = 4;
+                  }
+
+               } else if( result == 4) {
+                  result = 4;
+
+               } else if( result == 5) {
+                  if( neg_this ) {
+                     result = neg_that ? 5 : 6;
+                  } else if( neg_that ){
+                     result = 6;
+                  }
+               }
+            }
+
+/* Free resources. */
+            pset_that = astAnnul( pset_that );
+            unc_this = astAnnul( unc_this );
+            unc_that = astAnnul( unc_that );
+            unc_temp = astAnnul( unc_temp );
+            frm = astAnnul( frm );
+            lbndu_this = astFree( lbndu_this );
+            ubndu_this = astFree( ubndu_this );
+            lbndu_that = astFree( lbndu_that );
+            ubndu_that = astFree( ubndu_that );
+            outperm = astFree( outperm );
+         }
+
+         smap = astAnnul( smap );
+         map = astAnnul( map );
+         tmap = astAnnul( tmap );
+         map3 = astAnnul( map3 );
+         map2 = astAnnul( map2 );
+         map1 = astAnnul( map1 );
+         fs = astAnnul( fs );
+      }
+   }
+
+/* If overlap could not be determined using the above implementation, try
+   using the implementation inherited from the parent Region class. */
+   if( !result ) result = (*parent_overlap)( this, that, status );
+
+/* If not OK, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static void RegBaseBox( AstRegion *this_region, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     RegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     Interval member function (over-rides the astRegBaseBox protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstInterval *this;
+   int nax;
+   int i;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Interval structure */
+   this = (AstInterval *) this_region;
+
+/* Ensure the cached bounds are up to date. */
+   Cache( this, status );
+
+/* Copy the cached bounds into the supplied arrays. */
+   nax = astGetNin( this_region->frameset );
+   for( i = 0; i < nax; i++ ) {
+      lbnd[ i ] = this->lbnd[ i ];
+      ubnd[ i ] = this->ubnd[ i ];
+   }
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this_region, int *status ){
+/*
+*  Name:
+*     RegBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing a mesh of points on the boundary of a
+*     Region in its base Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     AstPointSet *astRegBaseMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Interval member function (over-rides the astRegBaseMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. The axis values in this PointSet will have
+*     associated accuracies derived from the accuracies which were
+*     supplied when the Region was created.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   AstBox *box;                  /* The equivalent Box */
+   AstPointSet *result;          /* Returned pointer */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the Interval is effectively a Box, invoke the astRegBaseMesh
+   function on the equivalent Box. A pointer to the equivalent Box will
+   be stored in the Interval structure. */
+   box = Cache( (AstInterval *) this_region, status );
+   if( box ) {
+      result = astRegBaseMesh( box );
+
+/* If the Interval is not equivalent to a Box, report an error. */
+   } else {
+      astError( AST__INTER, "astRegBaseMesh(%s): The %s given is "
+                "unbounded and therefore no boundary mesh can be "
+                "produced (internal AST programming error).", status,
+                astGetClass( this_region ), astGetClass( this_region ) );
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static AstRegion *RegBasePick( AstRegion *this_region, int naxes,
+                               const int *axes, int *status ){
+/*
+*  Name:
+*     RegBasePick
+
+*  Purpose:
+*     Return a Region formed by picking selected base Frame axes from the
+*     supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     AstRegion *RegBasePick( AstRegion *this, int naxes, const int *axes,
+*                             int *status )
+
+*  Class Membership:
+*     Interval member function (over-rides the astRegBasePick protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function attempts to return a Region that is spanned by selected
+*     axes from the base Frame of the encapsulated FrameSet of the supplied
+*     Region. This may or may not be possible, depending on the class of
+*     Region. If it is not possible a NULL pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     naxes
+*        The number of base Frame axes to select.
+*     axes
+*        An array holding the zero-based indices of the base Frame axes
+*        that are to be selected.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Region, or NULL if no region can be formed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;         /* The base Frame in the supplied Region */
+   AstFrame *frm;          /* The base Frame in the returned Region */
+   AstPointSet *pset;      /* Holds axis values defining the supplied Region */
+   AstRegion *bunc;        /* The uncertainty in the supplied Region */
+   AstRegion *result;      /* Returned Region */
+   AstRegion *unc;         /* The uncertainty in the returned Region */
+   double **ptr;           /* Holds axis values defining the supplied Region */
+   double *lbnd;           /* Base Frm lower bound axis values */
+   double *ubnd;           /* Base Frm upper bound axis values */
+   int i;                  /* Index of axis within returned Region */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the base Frame of the encapsulated FrameSet. */
+   bfrm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Create a Frame by picking the selected axes from the base Frame of the
+   encapsulated FrameSet. */
+   frm = astPickAxes( bfrm, naxes, axes, NULL );
+
+/* Get the uncertainty Region (if any) within the base Frame of the supplied
+   Region, and select the required axes from it. If the resulting Object
+   is not a Region, annul it so that the returned Region will have no
+   uncertainty. */
+   if( astTestUnc( this_region ) ) {
+      bunc = astGetUncFrm( this_region, AST__BASE );
+      unc = astPickAxes( bunc, naxes, axes, NULL );
+      bunc = astAnnul( bunc );
+
+      if( ! astIsARegion( unc ) ) unc = astAnnul( unc );
+
+   } else {
+      unc = NULL;
+   }
+
+/* Get pointers to the coordinate data in the parent Region structure. */
+   pset = this_region->points;
+   ptr = astGetPoints( pset );
+
+/* Get space to hold the limits of the Interval in the new Frame. */
+   lbnd = astMalloc( sizeof( *lbnd )*naxes );
+   ubnd = astMalloc( sizeof( *ubnd )*naxes );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Copy the limits for the selected axes into the arrays allocated above. */
+      for( i = 0; i < naxes; i++ ) {
+         lbnd[ i ] = ptr[ axes[ i ] ][ 0 ];
+         ubnd[ i ] = ptr[ axes[ i ] ][ 1 ];
+      }
+
+/* Create the new Interval. */
+      result = (AstRegion *) astInterval( frm, lbnd, ubnd, unc, "", status );
+
+   }
+
+/* Free resources */
+   frm = astAnnul( frm );
+   bfrm = astAnnul( bfrm );
+   if( unc ) unc = astAnnul( unc );
+   lbnd = astFree( lbnd );
+   ubnd = astFree( ubnd );
+
+/* Return a NULL pointer if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static double *RegCentre( AstRegion *this_region, double *cen, double **ptr,
+                          int index, int ifrm, int *status ){
+/*
+*  Name:
+*     RegCentre
+
+*  Purpose:
+*     Re-centre a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     double *RegCentre( AstRegion *this, double *cen, double **ptr,
+*                        int index, int ifrm, int *status )
+
+*  Class Membership:
+*     Interval member function (over-rides the astRegCentre protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function shifts the centre of the supplied Region to a
+*     specified position, or returns the current centre of the Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     cen
+*        Pointer to an array of axis values, giving the new centre.
+*        Supply a NULL value for this in order to use "ptr" and "index" to
+*        specify the new centre.
+*     ptr
+*        Pointer to an array of points, one for each axis in the Region.
+*        Each pointer locates an array of axis values. This is the format
+*        returned by the PointSet method astGetPoints. Only used if "cen"
+*        is NULL.
+*     index
+*        The index of the point within the arrays identified by "ptr" at
+*        which is stored the coords for the new centre position. Only used
+*        if "cen" is NULL.
+*     ifrm
+*        Should be AST__BASE or AST__CURRENT. Indicates whether the centre
+*        position is supplied and returned in the base or current Frame of
+*        the FrameSet encapsulated within "this".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If both "cen" and "ptr" are NULL then a pointer to a newly
+*     allocated dynamic array is returned which contains the centre
+*     coords of the Region. This array should be freed using astFree when
+*     no longer needed. If either of "ptr" or "cen" is not NULL, then a
+*     NULL pointer is returned.
+
+*  Notes:
+*    - Some Region sub-classes do not have a centre. Such classes will report
+*    an AST__INTER error code if this method is called with either "ptr" or
+*    "cen" not NULL. If "ptr" and "cen" are both NULL, then no error is
+*    reported if this method is invoked on a Region of an unsuitable class,
+*    but NULL is always returned.
+
+*/
+
+/* Local Variables: */
+   AstInterval *this;  /* Pointer to Interval structure */
+   AstBox *box;        /* Pointer to equivalent Box structure */
+   double **bptr;      /* Data pointers for Region PointSet */
+   double *lbnd;       /* Pointer to new lower bound values */
+   double *ubnd;       /* Pointer to new upper bound values */
+   double *result;     /* Returned pointer */
+   int i;              /* Coordinate index */
+   int nax;            /* Number of axes */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Interval structure. */
+   this = (AstInterval *) this_region;
+
+/* The Interval can only be re-centred if it is effectively a Box. */
+   box = Cache( (AstInterval *) this_region, status );
+   if( box ) {
+
+/* If the centre is being changed... */
+      if( cen || ptr ) {
+
+/* Set the new centre in the equivalent box. */
+         astRegCentre( box, cen, ptr, index, ifrm );
+
+/* Get the new base Frame bounds from the Box. */
+         nax = astGetNin( this_region->frameset );
+         lbnd = astMalloc( sizeof( double )*nax );
+         ubnd = astMalloc( sizeof( double )*nax );
+         astRegBaseBox( box, lbnd, ubnd );
+
+/* Store these bounds in the Interval structure. */
+         bptr = astGetPoints( this_region->points );
+         if( astOK ) {
+            for( i = 0; i < nax; i++ ) {
+               bptr[ i ][ 0 ] = lbnd[ i ];
+               bptr[ i ][ 1 ] = ubnd[ i ];
+            }
+         }
+
+/* Free resources. */
+         lbnd = astFree( lbnd );
+         ubnd = astFree( ubnd );
+
+/* If the centre is not being changed, just invoke the method on the
+   equivalent box. */
+      } else {
+         result = astRegCentre( box, NULL, NULL, 0, AST__BASE );
+      }
+
+/* If the Interval is not equivalent to a Box, report an error */
+   } else if( cen || ptr ) {
+      astError( AST__REGCN, "astRegCentre(%s): The supplied %s is not a "
+                "closed Interval and so cannot be re-centred.", status,
+                astGetClass( this ), astGetClass( this ) );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int RegPins( AstRegion *this_region, AstPointSet *pset, AstRegion *unc,
+                    int **mask, int *status ){
+/*
+*  Name:
+*     RegPins
+
+*  Purpose:
+*     Check if a set of points fall on the boundary of a given Interval.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+*                  int **mask, int *status )
+
+*  Class Membership:
+*     Interval member function (over-rides the astRegPins protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied set of
+*     points all fall on the boundary of the given Interval.
+*
+*     Some tolerance is allowed, as specified by the uncertainty Region
+*     stored in the supplied Interval "this", and the supplied uncertainty
+*     Region "unc" which describes the uncertainty of the supplied points.
+
+*  Parameters:
+*     this
+*        Pointer to the Interval.
+*     pset
+*        Pointer to the PointSet. The points are assumed to refer to the
+*        base Frame of the FrameSet encapsulated by "this".
+*     unc
+*        Pointer to a Region representing the uncertainties in the points
+*        given by "pset". The Region is assumed to represent the base Frame
+*        of the FrameSet encapsulated by "this". Zero uncertainity is assumed
+*        if NULL is supplied.
+*     mask
+*        Pointer to location at which to return a pointer to a newly
+*        allocated dynamic array of ints. The number of elements in this
+*        array is equal to the value of the Npoint attribute of "pset".
+*        Each element in the returned array is set to 1 if the
+*        corresponding position in "pset" is on the boundary of the Region
+*        and is set to zero otherwise. A NULL value may be supplied
+*        in which case no array is created. If created, the array should
+*        be freed using astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the points all fall on the boundary of the given
+*     Region, to within the tolerance specified. Zero otherwise.
+
+*/
+
+/* Local variables: */
+   AstBox *box;              /* The equivalent Box */
+   AstInterval *large_int;   /* Interval slightly larger than "this" */
+   AstInterval *small_int;   /* Interval slightly smaller than "this" */
+   AstInterval *this;        /* Pointer to the Interval structure. */
+   AstFrame *frm;            /* Base Frame in supplied Interval */
+   AstPointSet *ps1;         /* Points masked by larger Interval */
+   AstPointSet *ps2;         /* Points masked by larger and smaller Intervals */
+   AstRegion *tunc;          /* Uncertainity Region from "this" */
+   double **ptr;             /* Pointer to axis values in "ps2" */
+   double *large_lbnd;       /* Lower bounds of larger interval */
+   double *large_ubnd;       /* Upper bounds of larger interval */
+   double *lbnd_tunc;        /* Lower bounds of "this" uncertainty Region */
+   double *lbnd_unc;         /* Lower bounds of supplied uncertainty Region */
+   double *p;                /* Pointer to next axis value */
+   double *safe;             /* An interior point in "this" */
+   double *small_lbnd;       /* Lower bounds of smaller interval */
+   double *small_ubnd;       /* Upper bounds of smaller interval */
+   double *ubnd_tunc;        /* Upper bounds of "this" uncertainty Region */
+   double *ubnd_unc;         /* Upper bounds of supplied uncertainty Region */
+   double *wid;              /* Widths of "this" border */
+   double lb;                /* Lower bound */
+   double ub;                /* Upper bound */
+   double t;                 /* Swap space */
+   double w;                 /* Width */
+   int i;                    /* Axis index */
+   int j;                    /* Point index */
+   int nc;                   /* No. of axes in Interval base frame */
+   int np;                   /* No. of supplied points */
+   int result;               /* Returned flag */
+
+/* Initialise */
+   result = 0;
+   if( mask ) *mask = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a pointer to the Interval structure. */
+   this = (AstInterval *) this_region;
+
+/* If the Interval is effectively a Box, invoke the astRegPins function on
+   the equivalent Box. A pointer to the equivalent Box will be stored in the
+   Interval structure. */
+   box = Cache( this, status );
+   if( box ) return astRegPins( box, pset, unc, mask );
+
+/* Arrive here only if the Interval is not equivalent to a box (i.e. has
+   at least one infinite boundary). Get the number of base Frame axes in the
+   Interval, and check the supplied PointSet has the same number of axis
+   values per point. */
+   frm = astGetFrame( this_region->frameset, AST__BASE );
+   nc = astGetNaxes( frm );
+   if( astGetNcoord( pset ) != nc && astOK ) {
+      astError( AST__INTER, "astRegPins(%s): Illegal number of axis "
+                "values per point (%d) in the supplied PointSet - should be "
+                "%d (internal AST programming error).", status, astGetClass( this ),
+                astGetNcoord( pset ), nc );
+   }
+
+/* Get the number of axes in the uncertainty Region and check it is the
+   same as above. */
+   if( unc && astGetNaxes( unc ) != nc && astOK ) {
+      astError( AST__INTER, "astRegPins(%s): Illegal number of axes (%d) "
+                "in the supplied uncertainty Region - should be "
+                "%d (internal AST programming error).", status, astGetClass( this ),
+                astGetNaxes( unc ), nc );
+   }
+
+/* Get the centre of the region in the base Frame. We use this as a "safe"
+   interior point within the region. */
+   safe = astRegCentre( this, NULL, NULL, 0, AST__BASE );
+
+/* We now find the maximum distance on each axis that a point can be from
+   the boundary of the Interval for it still to be considered to be on the
+   boundary. First get the Region which defines the uncertainty within the
+   Interval being checked (in its base Frame), re-centre it on the interior
+   point found above (to avoid problems if the uncertainty region straddles
+   a discontinuity), and get its bounding box. */
+   tunc = astGetUncFrm( this, AST__BASE );
+   if( safe ) astRegCentre( tunc, safe, NULL, 0, AST__CURRENT );
+   lbnd_tunc = astMalloc( sizeof( double )*(size_t) nc );
+   ubnd_tunc = astMalloc( sizeof( double )*(size_t) nc );
+   astGetRegionBounds( tunc, lbnd_tunc, ubnd_tunc );
+
+/* Also get the Region which defines the uncertainty of the supplied
+   points and get its bounding box. First re-centre the uncertainty at the
+   interior position to avoid problems from uncertainties that straddle a
+   discontinuity. */
+   if( unc ) {
+      if( safe ) astRegCentre( unc, safe, NULL, 0, AST__CURRENT );
+      lbnd_unc = astMalloc( sizeof( double )*(size_t) nc );
+      ubnd_unc = astMalloc( sizeof( double )*(size_t) nc );
+      astGetRegionBounds( unc, lbnd_unc, ubnd_unc );
+   } else {
+      lbnd_unc = NULL;
+      ubnd_unc = NULL;
+   }
+
+/* The required border width for each axis is half of the total width of
+   the two bounding boxes. Use a zero sized box "unc" if no box was supplied. */
+   wid = astMalloc( sizeof( double )*(size_t) nc );
+   large_lbnd = astMalloc( sizeof( double )*(size_t) nc );
+   large_ubnd = astMalloc( sizeof( double )*(size_t) nc );
+   small_lbnd = astMalloc( sizeof( double )*(size_t) nc );
+   small_ubnd = astMalloc( sizeof( double )*(size_t) nc );
+   if( small_ubnd ) {
+      if( unc ) {
+         for( i = 0; i < nc; i++ ) {
+            wid[ i ] = 0.5*( fabs( astAxDistance( frm, i + 1, lbnd_tunc[ i ],
+                                                  ubnd_tunc[ i ] ) )
+                           + fabs( astAxDistance( frm, i + 1, lbnd_unc[ i ],
+                                                  ubnd_unc[ i ] ) ) );
+         }
+      } else {
+         for( i = 0; i < nc; i++ ) {
+            wid[ i ] = 0.5*fabs( astAxDistance( frm, i + 1, lbnd_tunc[ i ],
+                                                ubnd_tunc[ i ] ) );
+         }
+      }
+
+/* Create two new Intervals, one of which is larger than "this" by the widths
+   found above, and the other of which is smaller than "this" by the widths
+   found above. */
+      for( i = 0; i < nc; i++ ) {
+         lb = this->lbnd[ i ];
+         ub = this->ubnd[ i ];
+         if( lb > ub ) {
+            t = ub;
+            ub = lb;
+            lb = t;
+         }
+
+         w = fabs( wid[ i ] );
+         if( lb != -DBL_MAX ){
+            large_lbnd[ i ] = lb - w;
+            small_lbnd[ i ] = lb + w;
+         } else {
+            large_lbnd[ i ] = AST__BAD;
+            small_lbnd[ i ] = AST__BAD;
+         }
+
+         if( ub != DBL_MAX ){
+            large_ubnd[ i ] = ub + w;
+            small_ubnd[ i ] = ub - w;
+         } else {
+            large_ubnd[ i ] = AST__BAD;
+            small_ubnd[ i ] = AST__BAD;
+         }
+
+         if( small_lbnd[ i ] > small_ubnd[ i ] ) {
+            small_lbnd[ i ] = small_ubnd[ i ];
+         }
+      }
+
+      large_int = astInterval( frm, large_lbnd, large_ubnd, NULL, "", status );
+      small_int = astInterval( frm, small_lbnd, small_ubnd, NULL, "", status );
+
+/* Negate the smaller interval.*/
+      astNegate( small_int );
+
+/* Points are on the boundary of "this" if they are inside both the large
+   interval and the negated small interval. First transform the supplied
+   PointSet using the large interval, then transform them using the negated
+   smaller Interval. */
+      ps1 = astTransform( large_int, pset, 1, NULL );
+      ps2 = astTransform( small_int, ps1, 1, NULL );
+
+/* Get a point to the resulting axis values, and the number of axis
+   values per axis. */
+      ptr = astGetPoints( ps2 );
+      np = astGetNpoint( ps2 );
+
+/* If a mask array is to be returned, create one. */
+      if( mask ) {
+         *mask = astMalloc( sizeof(int)*(size_t) np );
+
+/* Check all the resulting points, setting mask values for all of them. */
+         if( astOK ) {
+
+/* Initialise the mask elements on the basis of the first axis values */
+            result = 1;
+            p = ptr[ 0 ];
+            for( j = 0; j < np; j++ ) {
+               if( *(p++) == AST__BAD ) {
+                  result = 0;
+                  (*mask)[ j ] = 0;
+               } else {
+                  (*mask)[ j ] = 1;
+               }
+            }
+
+/* Now check for bad values on other axes. */
+            for( i = 1; i < nc; i++ ) {
+               p = ptr[ i ];
+               for( j = 0; j < np; j++ ) {
+                  if( *(p++) == AST__BAD ) {
+                     result = 0;
+                     (*mask)[ j ] = 0;
+                  }
+               }
+            }
+         }
+
+/* If no output mask is to be made, we can break out of the check as soon
+   as the first bad value is found. */
+      } else if( astOK ) {
+         result = 1;
+         for( i = 0; i < nc && result; i++ ) {
+            p = ptr[ i ];
+            for( j = 0; j < np; j++ ) {
+               if( *(p++) == AST__BAD ) {
+                  result = 0;
+                  break;
+               }
+            }
+         }
+      }
+
+/* Free resources. */
+      large_int = astAnnul( large_int );
+      small_int = astAnnul( small_int );
+      ps1 = astAnnul( ps1 );
+      ps2 = astAnnul( ps2 );
+   }
+
+   tunc = astAnnul( tunc );
+   frm = astAnnul( frm );
+   lbnd_tunc = astFree( lbnd_tunc );
+   ubnd_tunc = astFree( ubnd_tunc );
+   if( unc ) lbnd_unc = astFree( lbnd_unc );
+   if( unc ) ubnd_unc = astFree( ubnd_unc );
+   wid = astFree( wid );
+   large_lbnd = astFree( large_lbnd );
+   large_ubnd = astFree( large_ubnd );
+   small_lbnd = astFree( small_lbnd );
+   small_ubnd = astFree( small_ubnd );
+   safe = astFree( safe );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) {
+      result = 0;
+      if( mask ) *mask = astAnnul( *mask );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int RegTrace( AstRegion *this_region, int n, double *dist, double **ptr,
+                     int *status ){
+/*
+*+
+*  Name:
+*     RegTrace
+
+*  Purpose:
+*     Return requested positions on the boundary of a 2D Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     int astTraceRegion( AstRegion *this, int n, double *dist, double **ptr );
+
+*  Class Membership:
+*     Interval member function (overrides the astTraceRegion method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function returns positions on the boundary of the supplied
+*     Region, if possible. The required positions are indicated by a
+*     supplied list of scalar parameter values in the range zero to one.
+*     Zero corresponds to some arbitrary starting point on the boundary,
+*     and one corresponds to the end (which for a closed region will be
+*     the same place as the start).
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     n
+*        The number of positions to return. If this is zero, the function
+*        returns without action (but the returned function value still
+*        indicates if the method is supported or not).
+*     dist
+*        Pointer to an array of "n" scalar parameter values in the range
+*        0 to 1.0.
+*     ptr
+*        A pointer to an array of pointers. The number of elements in
+*        this array should equal tthe number of axes in the Frame spanned
+*        by the Region. Each element of the array should be a pointer to
+*        an array of "n" doubles, in which to return the "n" values for
+*        the corresponding axis. The contents of the arrays are unchanged
+*        if the supplied Region belongs to a class that does not
+*        implement this method.
+
+*  Returned Value:
+*     Non-zero if the astTraceRegion method is implemented by the class
+*     of Region supplied, and zero if not.
+
+*-
+*/
+
+/* Local Variables; */
+   AstBox *box;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check inherited status. */
+   if( ! astOK ) return result;
+
+/* If the Interval is effectively a Box, invoke the astRegTrace function on
+   the equivalent Box. A pointer to the equivalent Box will be stored in the
+   Interval structure. */
+   box = Cache( (AstInterval *) this_region, status );
+   if( box ) result = astRegTrace( box, n, dist, ptr );
+
+/* Return the result. */
+   return result;
+}
+
+
+
+static void ResetCache( AstRegion *this, int *status ){
+/*
+*  Name:
+*     ResetCache
+
+*  Purpose:
+*     Clear cached information within the supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     void ResetCache( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Region member function (overrides the astResetCache method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function clears cached information from the supplied Region
+*     structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   if( this ) {
+      ( (AstInterval *) this )->stale = 1;
+      (*parent_resetcache)( this, status );
+   }
+}
+
+static void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status ) {
+/*
+*  Name:
+*     SetRegFS
+
+*  Purpose:
+*     Stores a new FrameSet in a Region
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     Interval method (over-rides the astSetRegFS method inherited from
+*     the Region class).
+
+*  Description:
+*     This function creates a new FrameSet and stores it in the supplied
+*     Region. The new FrameSet contains two copies of the supplied
+*     Frame, connected by a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     frm
+*        The Frame to use.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent method to store the FrameSet in the parent Region
+   structure. */
+   (* parent_setregfs)( this_region, frm, status );
+
+/* Indicate that the cached intermediate information is now stale and
+   should be recreated when next needed. */
+   astResetCache( this_region );
+}
+
+static void SetUnc( AstRegion *this, AstRegion *unc, int *status ){
+/*
+*  Name:
+*     SetUnc
+
+*  Purpose:
+*     Store uncertainty information in a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     void SetUnc( AstRegion *this, AstRegion *unc, int *status )
+
+*  Class Membership:
+*     Interval method (over-rides the astSetUnc method inherited from the
+*     Region class).
+
+*  Description:
+*     Each Region (of any class) can have an "uncertainty" which specifies
+*     the uncertainties associated with the boundary of the Region. This
+*     information is supplied in the form of a second Region. The uncertainty
+*     in any point on the boundary of a Region is found by shifting the
+*     associated "uncertainty" Region so that it is centred at the boundary
+*     point being considered. The area covered by the shifted uncertainty
+*     Region then represents the uncertainty in the boundary position.
+*     The uncertainty is assumed to be the same for all points.
+*
+*     The uncertainty is usually specified when the Region is created, but
+*     this function allows it to be changed at any time.
+
+*  Parameters:
+*     this
+*        Pointer to the Region which is to be assigned a new uncertainty.
+*     unc
+*        Pointer to the new uncertainty Region. This must be either a Box,
+*        a Circle or an Ellipse. A deep copy of the supplied Region will be
+*        taken, so subsequent changes to the uncertainty Region using the
+*        supplied pointer will have no effect on the Region "this".
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Invoke the astSetUnc method inherited from the parent Region class. */
+   (*parent_setunc)( this, unc, status );
+
+/* Indicate that the cached intermediate information is now stale and
+   should be recreated when next needed. */
+   astResetCache( this );
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify the Mapping represented by a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Interval method (over-rides the astSimplify method inherited
+*     from the Region class).
+
+*  Description:
+*     This function invokes the parent Region Simplify method, and then
+*     performs any further region-specific simplification.
+*
+*     If the Mapping from base to current Frame is not a UnitMap, this
+*     will include attempting to fit a new Region to the boundary defined
+*     in the current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the simplified Region. A cloned pointer to the
+*     supplied Region will be returned if no simplication could be
+*     performed.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstBox *box2;              /* Box used to determine 1-to-1 axis correspondance */
+   AstBox *box;               /* Box used to determine 1-to-1 axis correspondance */
+   AstInterval *this_interval;/* Pointer to Interval structure */
+   AstMapping *bfrm;          /* Pointer to base Frame in supplied Interval */
+   AstMapping *cfrm;          /* Pointer to current Frame in supplied Interval */
+   AstMapping *map;           /* Base -> current Mapping after parent simplification */
+   AstMapping *result;        /* Result pointer to return */
+   AstPointSet *pset2;        /* PointSet containing current Frame test points */
+   AstPointSet *pset3;        /* PointSet containing base Frame test points */
+   AstPointSet *psetb;        /* PointSet holding base positions */
+   AstPointSet *psetc;        /* PointSet holding current positions */
+   AstRegion *new;            /* Pointer to Region simplfied by parent class */
+   AstRegion *sreg;           /* Pointer to simplified Box */
+   AstRegion *this;           /* Pointer to supplied Region structure */
+   AstRegion *unc;            /* Pointer to uncertainty Region */
+   double **ptr2;             /* Pointer axis values in "pset2" */
+   double **ptr3;             /* Pointer axis values in "pset3" */
+   double **ptr;              /* Pointer to base Frame values defining Interval */
+   double **ptrb;             /* Pointer to "psetb" axis values */
+   double **sptr;             /* Pointer to simplified Interval bounds */
+   double *lbnd;              /* Pointer to array of base Frame lower bounds */
+   double *slbnd;             /* Pointer to array of current Frame lower bounds */
+   double *subnd;             /* Pointer to array of current Frame upper bounds */
+   double *ubnd;              /* Pointer to array of base Frame upper bounds */
+   double d;                  /* Distance between axis values */
+   double lb;                 /* Lower bound on axis values */
+   double lwid;               /* Axis width below the Interval lower limit */
+   double maxd;               /* Maximum currenrt Frame axis offset between test points */
+   double tmp;                /* Temporary storage for swapping variable values */
+   double ub;                 /* Upperbound on axis values */
+   double uwid;               /* Axis width above the Interval upper limit */
+   int bax;                   /* Base Frame axis index corresponding to "ic" */
+   int ic;                    /* Axis index */
+   int jc;                    /* Axis index */
+   int nc;                    /* No. of base Frame axis values per point */
+   int simpler;               /* Has some simplication taken place? */
+   int snc;                   /* No. of current Frame axis values per point */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the supplied Region structure. */
+   this = (AstRegion *) this_mapping;
+
+/* Get a pointer to the supplied Interval structure. */
+   this_interval = (AstInterval *) this;
+
+/* If this Interval is equivalent to a Box, use the astTransform method of
+   the equivalent Box. */
+   box = Cache( this_interval, status );
+   if( box ) {
+      result = astSimplify( box );
+
+/* Otherwise, we use a new implementation appropriate for unbounded
+   intervals. */
+   } else {
+
+/* Invoke the parent Simplify method inherited from the Region class. This
+   will simplify the encapsulated FrameSet and uncertainty Region. */
+      new = (AstRegion *) (*parent_simplify)( this_mapping, status );
+      if( new ) {
+
+/* Note if any simplification took place. This is assumed to be the case
+   if the pointer returned by the above call is different to the supplied
+   pointer. */
+         simpler = ( new != this );
+
+/* If the Mapping from base to current Frame is not a UnitMap, we attempt
+   to simplify the Interval by re-defining it within its current Frame. */
+         map = astGetMapping( new->frameset, AST__BASE, AST__CURRENT );
+         if( !astIsAUnitMap( map ) ){
+
+/* Take a copy of the Interval bounds (defined in the base Frame of the
+   Intervals FrameSet) and replace any missing limits with arbitrary
+   non-BAD values. This will give us a complete set of bounds defining a
+   box within the base Frame of the Interval. */
+            ptr = astGetPoints( new->points );
+            nc = astGetNcoord( new->points );
+
+            lbnd = astMalloc( sizeof( double )*(size_t) nc );
+            ubnd = astMalloc( sizeof( double )*(size_t) nc );
+
+            if( astOK ) {
+               for( ic = 0; ic < nc; ic++ ) {
+                  lbnd[ ic ] = ptr[ ic ][ 0 ];
+                  ubnd[ ic ] = ptr[ ic ][ 1 ];
+
+/* Ensure we have a good upper bound for this axis. */
+                  if( ubnd[ ic ] == AST__BAD ) {
+                     if( lbnd[ ic ] == AST__BAD ) {
+                        ubnd[ ic ] = 1.0;
+
+                     } else if( lbnd[ ic ] > 0.0 ) {
+                        ubnd[ ic ] = lbnd[ ic ]*1.01;
+
+                     } else if( lbnd[ ic ] < 0.0 ) {
+                        ubnd[ ic ] = lbnd[ ic ]*0.99;
+
+                     } else {
+                        ubnd[ ic ] = 1.0;
+                     }
+                  }
+
+/* Ensure we have a good lower bound for this axis. */
+                  if( lbnd[ ic ] == AST__BAD ) {
+                     if( ubnd[ ic ] > 0.0 ) {
+                        lbnd[ ic ] = ubnd[ ic ]*0.99;
+
+                     } else if( ubnd[ ic ] < 0.0 ) {
+                        lbnd[ ic ] = ubnd[ ic ]*1.01;
+
+                     } else {
+                        lbnd[ ic ] = 1.0;
+                     }
+                  }
+               }
+            }
+
+/* Transform the box corners found above into the current frame and then back
+   into the base Frame, and ensure that the box encloses both the original
+   and the new bounds. PermMaps with fewer outputs than inputs can cause the
+   resulting base Frame positions to differ significantly from the original. */
+            psetb =astPointSet( 2, nc,"", status );
+            ptrb =astGetPoints( psetb );
+            if( astOK ) {
+               for( ic = 0; ic < nc; ic++ ) {
+                  ptrb[ ic ][ 0 ] = lbnd[ ic ];
+                  ptrb[ ic ][ 1 ] = ubnd[ ic ];
+               }
+            }
+            psetc = astTransform( map, psetb, 1, NULL );
+            (void) astTransform( map, psetc, 0, psetb );
+            if( astOK ) {
+               for( ic = 0; ic < nc; ic++ ) {
+                  lb = ptrb[ ic ][ 0 ];
+                  if( lb != AST__BAD ) {
+                     if( lb < lbnd[ ic ] ) lbnd[ ic ] = lb;
+                     if( lb > ubnd[ ic ] ) ubnd[ ic ] = lb;
+                  }
+                  ub = ptrb[ ic ][ 1 ];
+                  if( ub != AST__BAD ) {
+                     if( ub < lbnd[ ic ] ) lbnd[ ic ] = ub;
+                     if( ub > ubnd[ ic ] ) ubnd[ ic ] = ub;
+                  }
+               }
+            }
+            psetb = astAnnul( psetb );
+            psetc = astAnnul( psetc );
+
+/* Limit this box to not exceed the limits imposed by the Interval.*/
+            Cache( this_interval, status );
+            for( ic = 0; ic < nc; ic++ ) {
+               lb = this_interval->lbnd[ ic ] ;
+               ub = this_interval->ubnd[ ic ] ;
+               if( lb <= ub ) {
+                  if( lbnd[ ic ] < lb ) {
+                     lbnd[ ic ] = lb;
+                  } else if( lbnd[ ic ] > ub ) {
+                     lbnd[ ic ] = ub;
+                  }
+                  if( ubnd[ ic ] < lb ) {
+                     ubnd[ ic ] = lb;
+                  } else if( ubnd[ ic ] > ub ) {
+                     ubnd[ ic ] = ub;
+                  }
+               } else {
+                  lwid = lb - lbnd[ ic ];
+                  uwid = ubnd[ ic ] - ub;
+                  if( lwid > uwid ) {
+                    if( lbnd[ ic ] > lb ) lbnd[ ic ] = lb;
+                    if( ubnd[ ic ] > lb ) ubnd[ ic ] = lb;
+                  } else {
+                    if( lbnd[ ic ] < ub ) lbnd[ ic ] = ub;
+                    if( ubnd[ ic ] < ub ) ubnd[ ic ] = ub;
+                  }
+               }
+
+/* Ensure the bounds are not equal */
+               if( lbnd[ ic ] == 0.0 && ubnd[ ic ] == 0.0 ) {
+                  ubnd[ ic ] = 1.0;
+
+               } else if( astEQUALS( lbnd[ ic ], ubnd[ ic ], 1.0E9 ) ) {
+                  ubnd[ ic ] = astMAX( ubnd[ ic ], lbnd[ ic ] )*( 1.0E6*DBL_EPSILON );
+               }
+            }
+
+/* Create a new Box representing the box found above. */
+            bfrm = astGetFrame( new->frameset, AST__BASE );
+            unc = astTestUnc( new ) ? astGetUncFrm( new, AST__BASE ) : NULL;
+            box = astBox( bfrm, 1, lbnd, ubnd, unc, "", status );
+            if( unc ) unc = astAnnul( unc );
+
+/* Modify this Box so that it has the same current Frame as this Interval. */
+            cfrm = astGetFrame( new->frameset, AST__CURRENT );
+            box2 = astMapRegion( box, map, cfrm );
+
+/* Try simplifying the Box. */
+            sreg = (AstRegion *) astSimplify( box2 );
+
+/* Only proceed if the Box was simplified */
+            if( sreg != (AstRegion *) box2 ) {
+
+/* If the simplified Box is a NullRegion return it. */
+               if( astIsANullRegion( sreg ) ) {
+                  (void) astAnnul( new );
+                  new = astClone( sreg );
+                  simpler = 1;
+
+/* If the simplified Box is a Box or an Interval... */
+               } else if( astIsABox( sreg ) || astIsAInterval( sreg ) ) {
+
+/* Get the bounds of the simplified Box. We assume that the base and
+   current Frames in the simplified Box are the same. */
+                  snc = astGetNin( sreg->frameset );
+                  slbnd = astMalloc( sizeof( double )*(size_t)snc );
+                  subnd = astMalloc( sizeof( double )*(size_t)snc );
+                  if(  astIsAInterval( sreg ) ) {
+                     sptr = astGetPoints( sreg->points );
+                     if( astOK ) {
+                        for( ic = 0; ic < snc; ic++ ) {
+                           slbnd[ ic ] = sptr[ ic ][ 0 ];
+                           subnd[ ic ] = sptr[ ic ][ 1 ];
+                        }
+                     }
+                  } else {
+                     astRegBaseBox( sreg, slbnd, subnd );
+                  }
+
+/* Now create a PointSet containing one point for each axis in the
+   current (or equivalently, base ) Frame of the simplified Box, plus an
+   extra point. */
+                  pset2 = astPointSet( snc + 1, snc, "", status );
+                  ptr2 = astGetPoints( pset2 );
+
+/* Put the lower bounds of the simplified Box into the first point in
+   this PointSet. The remaining points are displaced from this first point
+   along each axis in turn. The length of each displacement is determined
+   by the length of the box on the axis. */
+                  if( astOK ) {
+                     for( ic = 0; ic < snc; ic++ ) {
+                        for( jc = 0; jc < snc + 1; jc++ ) {
+                           ptr2[ ic ][ jc ] = slbnd[ ic ];
+                        }
+                        ptr2[ ic ][ ic + 1 ] = subnd[ ic ];
+                     }
+                  }
+
+/* Transform this PointSet into the base Frame of this Interval using the
+   inverse of the base->current Mapping. */
+                  pset3 = astTransform( map, pset2, 0, NULL );
+                  ptr3 = astGetPoints( pset3 );
+                  if( astOK ) {
+
+/* Now consider each axis of the Interval's current Frame (i.e. each base
+   Frame axis in the simplified Box). */
+                     for( ic = 0; ic < snc; ic++ ) {
+
+/* Given that the Box simplified succesfully, we know that there is a one
+   to one connection between the axes of the base and current Frame in this
+   Interval, but we do not yet know which base Frame axis corresponds to
+   which current Frame axis (and the number of base and current Frame axes
+   need not be equal). We have two points on a line parallel to current
+   Frame axis number "ic" (points zero and "ic+1" in "pset2"). Look at the
+   corresponding base Frame positions (in "pset3), and see which base Frame
+   axis they are parallel to. We look for the largest base Frame axis
+   increment (this allows small non-zero displacements to occur on the
+   other axes due to rounding errors). */
+                        maxd = -DBL_MAX;
+                        bax = -1;
+                        for( jc = 0; jc < nc; jc++ ) {
+                           d = fabs( astAxDistance( bfrm, jc + 1, ptr3[ jc ][ 0 ],
+                                                    ptr3[ jc ][ ic + 1 ] ) );
+                           if( d != AST__BAD && d > maxd ) {
+                              maxd = d;
+                              bax = jc;
+                           }
+                        }
+
+/* If the largest base Frame axis increment is zero, it must mean that
+   the current Frame axis is not present in the base Frame. The only
+   plausable cause of this is if the base->current Mapping contains a
+   PermMap which introduces an extra axis, in which case the axis will
+   have a fixed value (any other Mapping arrangement would have prevented
+   the Box from simplifying). Therefore, set upper and lower limits for
+   this axis to the same value. */
+                        if( maxd <= 0.0 ) {
+                           if( slbnd[ ic ] == AST__BAD ||
+                               subnd[ ic ] == AST__BAD ) {
+                              slbnd[ ic ] = AST__BAD;
+                           } else {
+                              slbnd[ ic ] = 0.5*( slbnd[ ic ] + subnd[ ic ] );
+                           }
+                           subnd[ ic ] = slbnd[ ic ];
+
+/* If we have found a base Frame axis which corresponds to the current
+   Frame axis "ic", then look to see which limits are specified for the
+   base Frame axis, and transfer missing limits to the current Frame. */
+                        } else {
+                           if( ptr[ bax ][ 0 ] == AST__BAD ) slbnd[ ic ] = AST__BAD;
+                           if( ptr[ bax ][ 1 ] == AST__BAD ) subnd[ ic ] = AST__BAD;
+
+/* If the original limits were equal, ensure the new limits are equal
+   (the code above modified the upper limit to ensure it was different to
+   the lower limit). */
+                           if( ptr[ bax ][ 1 ] == ptr[ bax ][ 0 ] ) {
+                              subnd[ ic ] = slbnd[ ic ];
+
+/* If the original interval was an inclusion (ubnd > lbnd), ensure the new
+   interval is also an inclusion by swapping the limits if required. */
+                           } else if( ptr[ bax ][ 1 ] > ptr[ bax ][ 0 ] ) {
+                              if( subnd[ ic ] < slbnd[ ic ] ) {
+                                 tmp = subnd[ ic ];
+                                 subnd[ ic ] = slbnd[ ic ];
+                                 slbnd[ ic ] = tmp;
+                              }
+
+/* If the original interval was an exclusion (ubnd < lbnd), ensure the new
+   interval is also an exlusion by swapping the limits if required. */
+                           } else if( ptr[ bax ][ 1 ] < ptr[ bax ][ 0 ] ) {
+                              if( subnd[ ic ] > slbnd[ ic ] ) {
+                                 tmp = subnd[ ic ];
+                                 subnd[ ic ] = slbnd[ ic ];
+                                 slbnd[ ic ] = tmp;
+                              }
+                           }
+                        }
+                     }
+
+/* Create the simplified Interval from the current Frame limits found
+   above, and use it in place of the original. */
+                     unc = astTestUnc( new ) ? astGetUncFrm( new, AST__CURRENT ) : NULL;
+                     (void) astAnnul( new );
+                     new = (AstRegion *) astInterval( cfrm, slbnd, subnd, unc, "", status );
+                     if( unc ) unc = astAnnul( unc );
+                     simpler = 1;
+                  }
+
+/* Free resources */
+                  pset2 = astAnnul( pset2 );
+                  pset3 = astAnnul( pset3 );
+                  slbnd = astFree( slbnd );
+                  subnd = astFree( subnd );
+               }
+            }
+
+/* Free resources */
+            bfrm = astAnnul( bfrm );
+            cfrm = astAnnul( cfrm );
+            box = astAnnul( box );
+            box2 = astAnnul( box2 );
+            sreg = astAnnul( sreg );
+            lbnd = astFree( lbnd );
+            ubnd = astFree( ubnd );
+         }
+
+/* Free resources */
+         map = astAnnul( map );
+
+/* If any simplification could be performed, copy Region attributes from
+   the supplied Region to the returned Region, and return a pointer to it.
+   If the supplied Region had no uncertainty, ensure the returned Region
+   has no uncertainty. Otherwise, return a clone of the supplied pointer. */
+         if( simpler ){
+            astRegOverlay( new, this, 1 );
+            result = (AstMapping *) new;
+         } else {
+            new = astAnnul( new );
+            result = astClone( this );
+         }
+      }
+   }
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a Interval to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     Interval member function (over-rides the astTransform protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function takes a Interval and a set of points encapsulated in a
+*     PointSet and transforms the points by setting axis values to
+*     AST__BAD for all points which are outside the region. Points inside
+*     the region are copied unchanged from input to output.
+
+*  Parameters:
+*     this
+*        Pointer to the Interval.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  The forward and inverse transformations are identical for a
+*     Region.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of axes in the Frame represented by the Interval.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstBox *box;                  /* Pointer to equivalent Box */
+   AstInterval *this;            /* Pointer to Interval structure */
+   AstPointSet *pset_tmp;        /* Pointer to PointSet holding base Frame positions*/
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstRegion *reg;               /* Pointer to Region structure */
+   AstRegion *unc;               /* Uncertainty Region */
+   double **ptr_lims;            /* Pointer to limits array */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double **ptr_tmp;             /* Pointer to base Frame coordinate data */
+   double *lbnd_unc;             /* Lower bounds of uncertainty Region */
+   double *ubnd_unc;             /* Upper bounds of uncertainty Region */
+   double lb;                    /* Base Frame axis lower bound */
+   double p;                     /* Input base Frame axis value */
+   double ub;                    /* Base Frame axis upper bound */
+   double wid;                   /* Half width of uncertainy Region */
+   int coord;                    /* Zero-based index for coordinates */
+   int ncoord_out;               /* No. of coordinates per output point */
+   int ncoord_tmp;               /* No. of coordinates per base Frame point */
+   int neg;                      /* Has the Region been negated? */
+   int npoint;                   /* No. of points */
+   int pass;                     /* Does this point pass the axis test? */
+   int point;                    /* Loop counter for points */
+   int setbad;                   /* Set the output point bad? */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain pointers to the Region and to the Interval. */
+   reg = (AstRegion *) this_mapping;
+   this = (AstInterval *) this_mapping;
+
+/* If this Interval is equivalent to a Box, use the astTransform method of
+   the equivalent Box. */
+   box = Cache( this, status );
+   if( box ) {
+      result = astTransform( box, in, forward, out );
+
+/* Otherwise, we use a new implementation appropriate for unbounded
+   intervals. */
+   } else {
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Region class. This function validates
+   all arguments and generates an output PointSet if necessary,
+   containing a copy of the input PointSet. */
+      result = (*parent_transform)( this_mapping, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* First use the encapsulated FrameSet to transform the supplied positions
+   from the current Frame in the encapsulated FrameSet (the Frame
+   represented by the Region), to the base Frame (the Frame in which the
+   Region is defined). This call also returns a pointer to the base Frame
+   of the encapsulated FrameSet. Note, the returned pointer may be a
+   clone of the "in" pointer, and so we must be carefull not to modify the
+   contents of the returned PointSet. */
+      pset_tmp = astRegTransform( reg, in, 0, NULL, NULL );
+
+/* Determine the numbers of points and coordinates per point from the base
+   Frame PointSet and obtain pointers for accessing the base Frame and output
+   coordinate values. */
+      npoint = astGetNpoint( pset_tmp );
+      ncoord_tmp = astGetNcoord( pset_tmp );
+      ptr_tmp = astGetPoints( pset_tmp );
+      ncoord_out = astGetNcoord( result );
+      ptr_out = astGetPoints( result );
+
+/* Get a pointer to the array of axis limits */
+      ptr_lims = astGetPoints( reg->points );
+
+/* See if the Region is negated. */
+      neg = astGetNegated( reg );
+
+/* Indicate we have not yet got the bounding box of the uncertainty
+   Region. */
+      lbnd_unc = NULL;
+      ubnd_unc = NULL;
+      unc = NULL;
+
+/* Perform coordinate arithmetic. */
+      if ( astOK ) {
+
+/* First deal with closed unnegated Intervals. */
+/* ------------------------------------------- */
+         if( astGetClosed( reg ) ) {
+            if( !neg ) {
+
+/* Loop round each point. */
+               for ( point = 0; point < npoint; point++ ) {
+
+/* Assume this point is inside the Region. We change this flag when we find
+   the first axis for which the point does not pass the axis test. */
+                  setbad = 0;
+
+/* Loop round each base Frame axis */
+                  Cache( this, status );
+                  for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+                     p = ptr_tmp[ coord ][ point ];
+                     lb = (this->lbnd)[ coord ];
+                     ub = (this->ubnd)[ coord ];
+
+/* If the limits are equal separate them slightly to give some tolerance. */
+                     if( lb == ub ) {
+
+/* If not yet done so, get the bounding box of the uncertainty Region in the
+   base Frame of the Interval */
+                        if( !unc ) {
+                           unc = astGetUncFrm( reg, AST__BASE );
+                           lbnd_unc = astMalloc( sizeof( double)*(size_t) ncoord_tmp );
+                           ubnd_unc = astMalloc( sizeof( double)*(size_t) ncoord_tmp );
+                           astGetRegionBounds( unc, lbnd_unc, ubnd_unc );
+                        }
+
+/* Set the gap between the limits to be equal to the uincertainty on this
+   axis. */
+                        if( astOK ) {
+                           wid = 0.5*( ubnd_unc[ coord ] - lbnd_unc[ coord ] );
+                           lb -= wid;
+                           ub += wid;
+                        }
+                     }
+
+/* Bad input points should always be bad in the output. */
+                     if( p == AST__BAD ) {
+                        setbad = 1;
+                        break;
+
+/* Does the current axis value pass the limits test for this axis? */
+                     } else if( lb <= ub ) {
+                        pass = ( lb <= p && p <= ub );
+                     } else {
+                        pass = ( p <= ub || lb <= p );
+                     }
+
+/* If this point does not pass the test for this axis, then indicate that
+   we should set the resulting output point bad and break since we now have
+   a definite value for the inside/outside flag. */
+                     if( !pass ) {
+                        setbad = 1;
+                        break;
+                     }
+                  }
+
+/* Set the axis values bad for this output point if required. */
+                  if( setbad ) {
+                     for ( coord = 0; coord < ncoord_out; coord++ ) {
+                        ptr_out[ coord ][ point ] = AST__BAD;
+                     }
+                  }
+               }
+
+/* Now deal with closed negated Intervals. */
+/* --------------------------------------- */
+            } else  {
+
+/* Loop round each point. */
+               for ( point = 0; point < npoint; point++ ) {
+
+/* Assume this point is outside the negated Region (i.e. inside the
+   unnegated Region). We change this flag when we find the first axis for
+   which the point passes the axis test. */
+                  setbad = 1;
+
+/* Loop round each base Frame axis */
+                  Cache( this, status );
+                  for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+                     p = ptr_tmp[ coord ][ point ];
+                     lb = (this->lbnd)[ coord ];
+                     ub = (this->ubnd)[ coord ];
+
+/* Bad input points should always be bad in the output. */
+                     if( p == AST__BAD ) {
+                        setbad = 1;
+                        break;
+
+/* Does the current axis value pass the limits test for this axis? */
+                     } else if( lb <= ub ) {
+                        pass = ( p <= lb || ub <= p );
+                     } else {
+                        pass = ( ub <= p && p <= lb );
+                     }
+
+/* If this point passes the test for this axis, then indicate that we should
+   not set the resulting output point bad and break since we now have a
+   definite value for the inside/outside flag. */
+                     if( pass ) {
+                        setbad = 0;
+                        break;
+                     }
+                  }
+
+/* Set the axis values bad for this output point if required. */
+                  if( setbad ) {
+                     for ( coord = 0; coord < ncoord_out; coord++ ) {
+                        ptr_out[ coord ][ point ] = AST__BAD;
+                     }
+                  }
+               }
+            }
+
+/* Now deal with open unnegated Intervals. */
+/* --------------------------------------- */
+         } else {
+            if( !neg ) {
+
+/* Loop round each point. */
+               for ( point = 0; point < npoint; point++ ) {
+
+/* Assume this point is inside the Region. We change this flag when we find
+   the first axis for which the point does not pass the axis test. */
+                  setbad = 0;
+
+/* Loop round each base Frame axis */
+                  Cache( this, status );
+                  for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+                     p = ptr_tmp[ coord ][ point ];
+                     lb = (this->lbnd)[ coord ];
+                     ub = (this->ubnd)[ coord ];
+
+/* Bad input points should always be bad in the output. */
+                     if( p == AST__BAD ) {
+                        setbad = 1;
+                        break;
+
+/* Does the current axis value pass the limits test for this axis? */
+                     } else if( lb <= ub ) {
+                        pass = ( lb < p && p < ub );
+                     } else {
+                        pass = ( p < ub || lb < p );
+                     }
+
+/* If this point does not pass the test for this axis, then indicate that
+   we should set the resulting output point bad and break since we now have
+   a definite value for the inside/outside flag. */
+                     if( !pass ) {
+                        setbad = 1;
+                        break;
+                     }
+                  }
+
+/* Set the axis values bad for this output point if required. */
+                  if( setbad ) {
+                     for ( coord = 0; coord < ncoord_out; coord++ ) {
+                        ptr_out[ coord ][ point ] = AST__BAD;
+                     }
+                  }
+               }
+
+/* Now deal with open negated Intervals. */
+/* ------------------------------------- */
+            } else  {
+
+/* Loop round each point. */
+               for ( point = 0; point < npoint; point++ ) {
+
+/* Assume this point is outside the negated Region (i.e. inside the
+   unnegated Region). We change this flag when we find the first axis for
+   which the point passes the axis test. */
+                  setbad = 1;
+
+/* Loop round each base Frame axis */
+                  Cache( this, status );
+                  for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+                     p = ptr_tmp[ coord ][ point ];
+                     lb = (this->lbnd)[ coord ];
+                     ub = (this->ubnd)[ coord ];
+
+/* If the limits are equal separate them slightly to give some tolerance. */
+                     if( lb == ub ) {
+
+/* If not yet done so, get the bounding box of the uncertainty Region in the
+   base Frame of the Interval */
+                        if( !unc ) {
+                           unc = astGetUncFrm( reg, AST__BASE );
+                           lbnd_unc = astMalloc( sizeof( double)*(size_t) ncoord_tmp );
+                           ubnd_unc = astMalloc( sizeof( double)*(size_t) ncoord_tmp );
+                           astGetRegionBounds( unc, lbnd_unc, ubnd_unc );
+                        }
+
+/* Set the gap between the limits to be equal to the uincertainty on this
+   axis. */
+                        if( astOK ) {
+                           wid = 0.5*( ubnd_unc[ coord ] - lbnd_unc[ coord ] );
+                           lb -= wid;
+                           ub += wid;
+                        }
+                     }
+
+/* Bad input points should always be bad in the output. */
+                     if( p == AST__BAD ) {
+                        setbad = 1;
+                        break;
+
+/* Does the current axis value pass the limits test for this axis? */
+                     } else if( lb <= ub ) {
+                        pass = ( p < lb || ub < p );
+                     } else {
+                        pass = ( ub < p && p < lb );
+                     }
+
+/* If this point passes the test for this axis, then indicate that we should
+   not set the resulting output point bad and break since we now have a
+   definite value for the inside/outside flag. */
+                     if( pass ) {
+                        setbad = 0;
+                        break;
+                     }
+                  }
+
+/* Set the axis values bad for this output point if required. */
+                  if( setbad ) {
+                     for ( coord = 0; coord < ncoord_out; coord++ ) {
+                        ptr_out[ coord ][ point ] = AST__BAD;
+                     }
+                  }
+               }
+            }
+         }
+      }
+
+/* Free resources */
+      pset_tmp = astAnnul( pset_tmp );
+      if( lbnd_unc ) lbnd_unc = astFree( lbnd_unc );
+      if( ubnd_unc ) ubnd_unc = astFree( ubnd_unc );
+      if( unc ) unc = astAnnul( unc );
+   }
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Interval objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Region objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstInterval *in;             /* Pointer to input Interval */
+   AstInterval *out;            /* Pointer to output Interval */
+   size_t nb;                   /* Number of bytes in limits array */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Intervals. */
+   in = (AstInterval *) objin;
+   out = (AstInterval *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output Interval. */
+   out->box = NULL;
+   out->lbnd = NULL;
+   out->ubnd = NULL;
+
+/* Note the number of bytes in each limits array */
+   nb = sizeof( double )*(size_t) astGetNin( ((AstRegion *) in)->frameset );
+
+/* Copy dynamic memory contents */
+   if( in->box ) out->box = astCopy( in->box );
+   out->lbnd = astStore( NULL, in->lbnd, nb );
+   out->ubnd = astStore( NULL, in->ubnd, nb );
+}
+
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Interval objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Interval objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstInterval *this;                 /* Pointer to Interval */
+
+/* Obtain a pointer to the Interval structure. */
+   this = (AstInterval *) obj;
+
+/* Annul all resources. */
+   if( this->box ) this->box = astAnnul( this->box );
+   this->lbnd = astFree( this->lbnd );
+   this->ubnd = astFree( this->ubnd );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Interval objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Interval class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Interval whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstInterval *this;                 /* Pointer to the Interval structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Interval structure. */
+   this = (AstInterval *) this_object;
+
+/* Write out values representing the instance variables for the
+   Interval class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* There are no values to write, so return without further action. */
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAInterval and astCheckInterval functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Interval,Region)
+astMAKE_CHECK(Interval)
+
+AstInterval *astInterval_( void *frame_void, const double lbnd[],
+                           const double ubnd[], AstRegion *unc,
+                           const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astInterval
+f     AST_INTERVAL
+
+*  Purpose:
+*     Create a Interval.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "interval.h"
+c     AstInterval *astInterval( AstFrame *frame, const double lbnd[],
+c                               const double ubnd[], AstRegion *unc,
+c                               const char *options, ... )
+f     RESULT = AST_INTERVAL( FRAME, LBND, UBND, UNC, OPTIONS, STATUS )
+
+*  Class Membership:
+*     Interval constructor.
+
+*  Description:
+*     This function creates a new Interval and optionally initialises its
+*     attributes.
+*
+*     A Interval is a Region which represents upper and/or lower limits on
+*     one or more axes of a Frame. For a point to be within the region
+*     represented by the Interval, the point must satisfy all the
+*     restrictions placed on all the axes. The point is outside the region
+*     if it fails to satisfy any one of the restrictions. Each axis may have
+*     either an upper limit, a lower limit, both or neither. If both limits
+*     are supplied but are in reverse order (so that the lower limit is
+*     greater than the upper limit), then the interval is an excluded
+*     interval, rather than an included interval.
+*
+*     At least one axis limit must be supplied.
+*
+*     Note, The Interval class makes no allowances for cyclic nature of
+*     some coordinate systems (such as SkyFrame coordinates). A Box
+*     should usually be used in these cases since this requires the user
+*     to think about suitable upper and lower limits,
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        A pointer to the Frame in which the region is defined. A deep
+*        copy is taken of the supplied Frame. This means that any
+*        subsequent changes made to the Frame using the supplied pointer
+*        will have no effect the Region.
+c     lbnd
+f     LBND( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute) containing the lower limits on each axis.
+*        Set a value to AST__BAD to indicate that the axis has no lower
+*        limit.
+c     ubnd
+f     UBND( * ) = DOUBLE PRECISION (Given)
+c        An array of double, with one element for each Frame axis
+f        An array with one element for each Frame axis
+*        (Naxes attribute) containing the upper limits on each axis.
+*        Set a value to AST__BAD to indicate that the axis has no upper
+*        limit.
+c     unc
+f     UNC = INTEGER (Given)
+*        An optional pointer to an existing Region which specifies the
+*        uncertainties associated with the boundary of the Interval being created.
+*        The uncertainty in any point on the boundary of the Interval is found by
+*        shifting the supplied "uncertainty" Region so that it is centred at
+*        the boundary point being considered. The area covered by the
+*        shifted uncertainty Region then represents the uncertainty in the
+*        boundary position. The uncertainty is assumed to be the same for
+*        all points.
+*
+*        If supplied, the uncertainty Region must be of a class for which
+*        all instances are centro-symetric (e.g. Box, Circle, Ellipse, etc.)
+*        or be a Prism containing centro-symetric component Regions. A deep
+*        copy of the supplied Region will be taken, so subsequent changes to
+*        the uncertainty Region using the supplied pointer will have no
+*        effect on the created Interval. Alternatively,
+f        a null Object pointer (AST__NULL)
+c        a NULL Object pointer
+*        may be supplied, in which case a default uncertainty is used
+*        equivalent to a box 1.0E-6 of the size of the Interval being created.
+*
+*        The uncertainty Region has two uses: 1) when the
+c        astOverlap
+f        AST_OVERLAP
+*        function compares two Regions for equality the uncertainty
+*        Region is used to determine the tolerance on the comparison, and 2)
+*        when a Region is mapped into a different coordinate system and
+*        subsequently simplified (using
+c        astSimplify),
+f        AST_SIMPLIFY),
+*        the uncertainties are used to determine if the transformed boundary
+*        can be accurately represented by a specific shape of Region.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Interval. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Interval. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astInterval()
+f     AST_INTERVAL = INTEGER
+*        A pointer to the new Interval.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstInterval *new;             /* Pointer to new Interval */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the supplied Frame structure. */
+   frame = astCheckFrame( frame_void );
+
+/* Initialise the Interval, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitInterval( NULL, sizeof( AstInterval ), !class_init,
+                          &class_vtab, "Interval", frame, lbnd, ubnd, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Interval's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Interval. */
+   return new;
+}
+
+AstInterval *astIntervalId_( void *frame_void, const double lbnd[],
+                             const double ubnd[], void *unc_void,
+                             const char *options, ... ) {
+/*
+*  Name:
+*     astIntervalId_
+
+*  Purpose:
+*     Create a Interval.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     AstInterval *astIntervalId_( AstFrame *frame, const double lbnd[],
+*                                  const double ubnd[], AstRegion *unc,
+*                                  const char *options, ... )
+
+*  Class Membership:
+*     Interval constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astInterval constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astInterval_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astInterval_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astInterval_.
+
+*  Returned Value:
+*     The ID value associated with the new Interval.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstInterval *new;             /* Pointer to new Interval */
+   AstRegion *unc;               /* Pointer to Region structure */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Frame pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Frame. */
+   frame = astVerifyFrame( astMakePointer( frame_void ) );
+
+/* Obtain a Region pointer from the supplied "unc" ID and validate the
+   pointer to ensure it identifies a valid Region . */
+   unc = unc_void ? astCheckRegion( astMakePointer( unc_void ) ) : NULL;
+
+/* Initialise the Interval, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitInterval( NULL, sizeof( AstInterval ), !class_init, &class_vtab,
+                        "Interval", frame, lbnd, ubnd, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Interval's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new Interval. */
+   return astMakeId( new );
+}
+
+AstInterval *astInitInterval_( void *mem, size_t size, int init, AstIntervalVtab *vtab,
+                               const char *name, AstFrame *frame,
+                               const double lbnd[], const double ubnd[],
+                               AstRegion *unc, int *status ) {
+/*
+*+
+*  Name:
+*     astInitInterval
+
+*  Purpose:
+*     Initialise a Interval.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     AstInterval *astInitInterval( void *mem, size_t size, int init, AstIntervalVtab *vtab,
+*                                   const char *name, AstFrame *frame,
+*                                   const double lbnd[], const double ubnd[],
+*                                   AstRegion *unc )
+
+*  Class Membership:
+*     Interval initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Interval object. It allocates memory (if necessary) to accommodate
+*     the Interval plus any additional data associated with the derived class.
+*     It then initialises a Interval structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a Interval at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Interval is to be initialised.
+*        This must be of sufficient size to accommodate the Interval data
+*        (sizeof(Interval)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Interval (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Interval
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Interval's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Interval.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     frame
+*        A pointer to the Frame in which the region is defined.
+*     lbnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the lower limits on each axis.
+*        Set a value to AST__BAD to indicate that the axis has no lower
+*        limit. Upper and ower limits can be reversed to create an
+*        excluded interval rather than an included interval.
+*     ubnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the upper limits on each axis.
+*        Set a value to AST__BAD to indicate that the axis has no upper
+*        limit.
+*     unc
+*        A pointer to a Region which specifies the uncertainty in the
+*        supplied positions (all points on the boundary of the new Interval
+*        being initialised are assumed to have the same uncertainty). A NULL
+*        pointer can be supplied, in which case default uncertainties equal to
+*        1.0E-6 of the dimensions of the new Interval's bounding box are used.
+*        If an uncertainty Region is supplied, it must be either a Box, a
+*        Circle or an Ellipse, and its encapsulated Frame must be related
+*        to the Frame supplied for parameter "frame" (i.e. astConvert
+*        should be able to find a Mapping between them). Two positions
+*        the "frame" Frame are considered to be co-incident if their
+*        uncertainty Regions overlap. The centre of the supplied
+*        uncertainty Region is immaterial since it will be re-centred on the
+*        point being tested before use. A deep copy is taken of the supplied
+*        Region.
+
+*  Returned Value:
+*     A pointer to the new Interval.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS        /* Pointer to thread-specific global data */
+   AstInterval *new;         /* Pointer to new Interval */
+   AstPointSet *pset;        /* PointSet to pass to Region initialiser */
+   double **ptr;             /* Pointer to coords data in pset */
+   int i;                    /* Axis index */
+   int nc;                   /* No. of axes */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitIntervalVtab( &class_vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Get the number of axis values required for each position. */
+   nc = astGetNaxes( frame );
+
+/* Create a PointSet to hold the upper and lower bounds, and get pointers to
+   the data arrays. */
+   pset = astPointSet( 2, nc, "", status );
+   ptr = astGetPoints( pset );
+   if( astOK ) {
+
+/* Copy the limits into the PointSet.  */
+      for( i = 0; i < nc; i++ ) {
+         ptr[ i ][ 0 ] = lbnd[ i ];
+         ptr[ i ][ 1 ] = ubnd[ i ];
+      }
+
+/* Initialise a Region structure (the parent class) as the first component
+   within the Interval structure, allocating memory if necessary. */
+      new = (AstInterval *) astInitRegion( mem, size, 0, (AstRegionVtab *) vtab,
+                                           name, frame, pset, unc );
+
+      if ( astOK ) {
+
+/* Initialise the Interval data. */
+/* ----------------------------- */
+         new->lbnd = NULL;
+         new->ubnd = NULL;
+         new->box = NULL;
+         new->stale = 1;
+
+/* If an error occurred, clean up by deleting the new Interval. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Free resources. */
+   pset = astAnnul( pset );
+
+/* Return a pointer to the new Interval. */
+   return new;
+}
+
+AstInterval *astLoadInterval_( void *mem, size_t size, AstIntervalVtab *vtab,
+                               const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadInterval
+
+*  Purpose:
+*     Load a Interval.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "interval.h"
+*     AstInterval *astLoadInterval( void *mem, size_t size, AstIntervalVtab *vtab,
+*                                   const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     Interval loader.
+
+*  Description:
+*     This function is provided to load a new Interval using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Interval structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Interval at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Interval is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Interval data (sizeof(Interval)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Interval (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Interval structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstInterval) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Interval. If this is NULL, a pointer
+*        to the (static) virtual function table for the Interval class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Interval" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Interval.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS             /* Pointer to thread-specific global data */
+   AstInterval *new;              /* Pointer to the new Interval */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Interval. In this case the
+   Interval belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstInterval );
+      vtab = &class_vtab;
+      name = "Interval";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitIntervalVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Interval. */
+   new = astLoadRegion( mem, size, (AstRegionVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Interval" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* There are no values to read. */
+/* ---------------------------- */
+      new->lbnd = NULL;
+      new->ubnd = NULL;
+      new->box = NULL;
+      new->stale = 1;
+
+/* If an error occurred, clean up by deleting the new Interval. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Interval pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+void astIntervalPoints_( AstInterval *this, double *lbnd, double *ubnd,
+                         int *status) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Interval,IntervalPoints))( this, lbnd, ubnd, status );
+   return;
+}
+
+
+
+
+
+
diff --git a/ast/interval.h b/ast/interval.h
new file mode 100644
index 0000000..3397e24
--- /dev/null
+++ b/ast/interval.h
@@ -0,0 +1,236 @@
+#if !defined( INTERVAL_INCLUDED ) /* Include this file only once */
+#define INTERVAL_INCLUDED
+/*
+*+
+*  Name:
+*     interval.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Interval class.
+
+*  Invocation:
+*     #include "interval.h"
+
+*  Description:
+*     This include file defines the interface to the Interval class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The Interval class implement a Region which represents a simple interval
+*     on each axis of the encapsulated Frame
+
+*  Inheritance:
+*     The Interval class inherits from the Region class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     1-NOV-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "region.h"              /* Coordinate regions (parent class) */
+#include "box.h"                 /* Closed box regions */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* Interval structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstInterval {
+
+/* Attributes inherited from the parent class. */
+   AstRegion region;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *lbnd;                 /* Lower limits */
+   double *ubnd;                 /* Lower limits */
+   AstBox *box;                  /* Equivalent Box */
+   int stale;                    /* Is cached information stale? */
+
+} AstInterval;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstIntervalVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstRegionVtab region_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   void (* IntervalPoints)( AstInterval *, double *, double *, int *);
+
+} AstIntervalVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstIntervalGlobals {
+   AstIntervalVtab Class_Vtab;
+   int Class_Init;
+} AstIntervalGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitIntervalGlobals_( AstIntervalGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Interval)          /* Check class membership */
+astPROTO_ISA(Interval)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstInterval *astInterval_( void *, const double[], const double[], AstRegion *, const char *, int *, ...);
+#else
+AstInterval *astIntervalId_( void *, const double[], const double[], AstRegion *, const char *, ... )__attribute__((format(printf,5,6)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstInterval *astInitInterval_( void *, size_t, int, AstIntervalVtab *,
+                               const char *, AstFrame *, const double[],
+                               const double[], AstRegion *, int * );
+
+/* Vtab initialiser. */
+void astInitIntervalVtab_( AstIntervalVtab *, const char *, int * );
+
+/* Loader. */
+AstInterval *astLoadInterval_( void *, size_t, AstIntervalVtab *,
+                               const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+void astIntervalPoints_( AstInterval *, double *, double *, int *);
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckInterval(this) astINVOKE_CHECK(Interval,this,0)
+#define astVerifyInterval(this) astINVOKE_CHECK(Interval,this,1)
+
+/* Test class membership. */
+#define astIsAInterval(this) astINVOKE_ISA(Interval,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astInterval astINVOKE(F,astInterval_)
+#else
+#define astInterval astINVOKE(F,astIntervalId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitInterval(mem,size,init,vtab,name,frame,lbnd,ubnd,unc) \
+astINVOKE(O,astInitInterval_(mem,size,init,vtab,name,frame,lbnd,ubnd,unc,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitIntervalVtab(vtab,name) astINVOKE(V,astInitIntervalVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadInterval(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadInterval_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckInterval to validate Interval pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astIntervalPoints(this,lbnd,ubnd) astINVOKE(V,astIntervalPoints_(astCheckInterval(this),lbnd,ubnd,STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/intramap.c b/ast/intramap.c
new file mode 100644
index 0000000..67eebb2
--- /dev/null
+++ b/ast/intramap.c
@@ -0,0 +1,2942 @@
+/*
+*class++
+*  Name:
+*     IntraMap
+
+*  Purpose:
+c     Map points using a private transformation function.
+f     Map points using a private transformation routine.
+
+*  Constructor Function:
+c     astIntraMap (also see astIntraReg)
+f     AST_INTRAMAP (also see AST_INTRAREG)
+
+*  Description:
+c     The IntraMap class provides a specialised form of Mapping which
+c     encapsulates a privately-defined coordinate transformation
+c     other AST Mapping. This allows you to create Mappings that
+c     perform any conceivable coordinate transformation.
+f     The IntraMap class provides a specialised form of Mapping which
+f     encapsulates a privately-defined coordinate transformation
+f     routine (e.g. written in Fortran) so that it may be used like
+f     any other AST Mapping. This allows you to create Mappings that
+f     perform any conceivable coordinate transformation.
+*
+*     However, an IntraMap is intended for use within a single program
+*     or a private suite of software, where all programs have access
+*     to the same coordinate transformation functions (i.e. can be
+*     linked against them). IntraMaps should not normally be stored in
+*     datasets which may be exported for processing by other software,
+*     since that software will not have the necessary transformation
+*     functions available, resulting in an error.
+*
+c     You must register any coordinate transformation functions to be
+c     used using astIntraReg before creating an IntraMap.
+f     You must register any coordinate transformation functions to be
+f     used using AST_INTRAREG before creating an IntraMap.
+
+*  Inheritance:
+*     The IntraMap class inherits from the Mapping class.
+
+*  Attributes:
+*     In addition to those attributes common to all Mappings, every
+*     IntraMap also has the following attributes:
+*
+*     - IntraFlag: IntraMap identification string
+
+*  Functions:
+c     The IntraMap class does not define any new functions beyond those
+f     The IntraMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     16-MAR-1998 (RFWS):
+*        Original version.
+*     15-SEP-1999 (RFWS):
+*        Added a "this" pointer to the external transformation function
+*        used by an IntraMap.
+*     20-JUN-2001 (DSB):
+*        Add an "astClone" call to prevent the pointer for "this" being
+*        annulled at the end of the Transform method.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitIntraMapVtab
+*        method.
+*     7-DEC-2005 (DSB):
+*        Free memory allocated by calls to astReadString.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     1-MAR-2006 (DSB):
+*        Replace astSetPermMap within DEBUG blocks by astBeginPM/astEndPM.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS IntraMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "unitmap.h"             /* Unit (identity) Mappings */
+#include "intramap.h"            /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Pointer to array of transformation function data. */
+static AstIntraMapTranData *tran_data = NULL;
+
+/* Number of transformation functions registered. */
+static int tran_nfun = 0;
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are used or extended by this
+   class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_getnin)( AstMapping *, int * );
+static int (* parent_getnout)( AstMapping *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(IntraMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(IntraMap,Class_Init)
+#define class_vtab astGLOBAL(IntraMap,Class_Vtab)
+
+
+/* A mutex used to serialise invocations of the IntraReg function (the
+   only function allowed to modify the contents of the static tran_data
+   array). */
+static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX1 pthread_mutex_lock( &mutex1 );
+#define UNLOCK_MUTEX1 pthread_mutex_unlock( &mutex1 );
+
+/* A mutex used to serialise invocations of extrnal transformation
+   functions (which may not be thread-safe). */
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstIntraMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#define LOCK_MUTEX1
+#define UNLOCK_MUTEX1
+
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstIntraMap *astIntraMapId_( const char *, int, int, const char *, ... );
+void astIntraRegFor_( const char *, int, int, void (* tran)( AstMapping *, int, int, const double *[], int, int, double *[] ), void (* tran_wrap)( void (*)( AstMapping *, int, int, const double *[], int, int, double *[] ), AstMapping *, int, int, const double *[], int, int, double *[], int * ), unsigned int, const char *, const char *, const char *, int * );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static char *CleanName( const char *, const char *, int * );
+static int GetObjSize( AstObject *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static const char *GetIntraFlag( AstIntraMap *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestIntraFlag( AstIntraMap *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearIntraFlag( AstIntraMap *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static void IntraReg( const char *, int, int, void (*)( AstMapping *, int, int, const double *[], int, int, double *[] ), void (*)( void (*)( AstMapping *, int, int, const double *[], int, int, double *[] ), AstMapping *, int, int, const double *[], int, int, double *[], int * ), unsigned int, const char *, const char *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetIntraFlag( AstIntraMap *, const char *, int * );
+static void TranWrap( void (*)( AstMapping *, int, int, const double *[], int, int, double *[] ), AstMapping *, int, int, const double *[], int, int, double *[], int * );
+
+/* Member functions. */
+/* ================= */
+static char *CleanName( const char *name, const char *caller, int *status ) {
+/*
+*  Name:
+*     CleanName
+
+*  Purpose:
+*     Clean (and validate) a transformation function name.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     char *CleanName( const char *name, const char *caller, int *status )
+
+*  Class Membership:
+*     IntraMap member function.
+
+*  Description:
+*     This function cleans a transformation function name by removing
+*     all white space. It returns a copy of the cleaned name held in
+*     dynamically allocated memory. If the name is entirely blank, an
+*     error is reported.
+
+*  Parameters:
+*     name
+*        Pointer to a null-terminated string containing the name to be
+*        cleaned.
+*     caller
+*        Pointer to a null-terminated string containing the name of
+*        the calling function. This is only used to construct error
+*        messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a dynamically-allocated null-terminated string
+*     containing the cleaned name. A NULL pointer is returned if the
+*     name was entirely blank.
+
+*  Notes:
+*     - The memory holding the returned string should be deallocated
+*     (using astFree) when no longer required.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   char *result;                 /* Pointer to result string */
+   int i;                        /* Loop counter for input characters */
+   int len;                      /* Length of name */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Determine the number of non-blank characters in the name supplied. */
+   len = 0;
+   for ( i = 0; name[ i ]; i++ ) if ( !isspace( (int) name[ i ] ) ) len++;
+
+/* If the name is entirely blank, then report an error. */
+   if ( !len ) {
+      astError( AST__ITFNI, "%s: Invalid blank transformation function name "
+                            "given.", status, caller );
+
+/* Otherwise, allocate memory to hold the cleaned name. */
+   } else {
+      result = astMalloc( (size_t) ( len + 1 ) );
+
+/* If OK, make a copy of the name with white space removed. */
+      if ( astOK ) {
+         len = 0;
+         for ( i = 0; name[ i ]; i++ ) {
+            if ( !isspace( (int) name[ i ] ) ) result[ len++ ] = name[ i ];
+         }
+
+/* Terminate the result string. */
+         result[ len ] = '\0';
+      }
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for an IntraMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     IntraMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for an
+*     IntraMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the IntraMap.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstIntraMap *this;            /* Pointer to the IntraMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the IntraMap structure. */
+   this = (AstIntraMap *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* IntraFlag. */
+/* ---------- */
+   if ( !strcmp( attrib, "intraflag" ) ) {
+      astClearIntraFlag( this );
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two IntraMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     IntraMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two IntraMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a IntraMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the IntraMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstIntraMap *that;
+   AstIntraMap *this;
+   int nin;
+   int nout;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two IntraMap structures. */
+   this = (AstIntraMap *) this_object;
+   that = (AstIntraMap *) that_object;
+
+/* Check the second object is a IntraMap. We know the first is a
+   IntraMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAIntraMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two IntraMaps differ, it may still be possible
+   for them to be equivalent. First compare the IntraMaps if their Invert
+   flags are the same. In this case all the attributes of the two IntraMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+
+            if( this->ifun == that->ifun &&
+                this->intraflag && that->intraflag &&
+                !strcmp( this->intraflag, that->intraflag ) ) {
+               result = 1;
+            }
+
+/* If the Invert flags for the two IntraMaps differ, the attributes of the two
+   IntraMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a IntraMap, Invert flags must be equal. */
+            result = 0;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     IntraMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied IntraMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the IntraMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstIntraMap *this;         /* Pointer to IntraMap structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the IntraMap structure. */
+   this = (AstIntraMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astTSizeOf( this->intraflag );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for an IntraMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     IntraMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a IntraMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the IntraMap.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the IntraMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the IntraMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstIntraMap *this;            /* Pointer to the IntraMap structure */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the IntraMap structure. */
+   this = (AstIntraMap *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* IntraFlag. */
+/* ---------- */
+   if ( !strcmp( attrib, "intraflag" ) ) {
+      result = astGetIntraFlag( this );
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astInitIntraMapVtab_(  AstIntraMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitIntraMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for an IntraMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     void astInitIntraMapVtab( AstIntraMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     IntraMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the IntraMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAIntraMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->ClearIntraFlag = ClearIntraFlag;
+   vtab->GetIntraFlag = GetIntraFlag;
+   vtab->SetIntraFlag = SetIntraFlag;
+   vtab->TestIntraFlag = TestIntraFlag;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Store pointers to inherited methods that will be invoked explicitly
+   by this class. */
+   parent_getnin = mapping->GetNin;
+   parent_getnout = mapping->GetNout;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "IntraMap",
+               "Map points using a private transformation function" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void IntraReg( const char *name, int nin, int nout,
+                      void (* tran)( AstMapping *, int, int, const double *[],
+                                     int, int, double *[] ),
+                      void (* tran_wrap)( void (*)( AstMapping *, int, int,
+                                                    const double *[], int, int,
+                                                    double *[] ),
+                                          AstMapping *, int, int,
+                                          const double *[], int, int,
+                                          double *[], int * ),
+                      unsigned int flags,
+                      const char *purpose, const char *author,
+                      const char *contact, int *status ) {
+/*
+*  Name:
+*     IntraReg
+
+*  Purpose:
+*     Register a transformation function for use by an IntraMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     void IntraReg( const char *name, int nin, int nout,
+*                    void (* tran)( AstMapping *, int, int, const double *[],
+*                                   int, int, double *[] ),
+*                    void (* tran_wrap)( void (*)( AstMapping *, int, int,
+*                                                  const double *[], int, int,
+*                                                  double *[] ),
+*                                        AstMapping *, int, int,
+*                                        const double *[], int, int,
+*                                        double *[], int * ),
+*                    unsigned int flags,
+*                    const char *purpose, const char *author,
+*                    const char *contact, int *status )
+
+*  Class Membership:
+*     IntraMap member function.
+
+*  Description:
+*     This function registers a transformation function which will
+*     later be used by an IntraMap and associates it with a name. It
+*     also stores related information which will be required by the
+*     IntraMap.
+
+*  Parameters:
+*     name
+*        Pointer to a null-terminated string containing the name to be
+*        used to identify the transformation function. This string is
+*        case sensitive. All white space is removed before use.
+*     nin
+*        The number of input coordinates per point (or AST__ANY if any
+*        number are allowed).
+*     nout
+*        The number of output coordinates per point (or AST__ANY if
+*        any number are allowed).
+*     tran
+*        Pointer to the transformation function to be registered.
+*        This may have any form of interface, which need not be known
+*        to the implementation of the IntraMap class. Instead, the
+*        method of invoking the transformation function should be
+*        encapsulated in the "tran_wrap" function (below).
+*     tran_wrap
+*        Pointer to a wrapper function appropriate to the transformation
+*        function (above). This wrapper function should have the same
+*        interface as astTranP (from the Mapping class), except that it takes
+*        a pointer to a function like "tran" as an additional first argument.
+*        The purpose of this wrapper is to invoke the transformation function
+*        via the pointer supplied, to pass it the necessary information
+*        derived from the remainder of its arguments, and then to return the
+*        results.
+*     flags
+*        This argument may be used to supply a set of flags which
+*        control the behaviour of any IntraMap which uses the
+*        registered transformation function. See the public interface
+*        for astIntraReg for details.
+*     purpose
+*        Pointer to a null-terminated string containing a short (one
+*        line) textual comment to describe the purpose of the
+*        transformation function.
+*     author
+*        Pointer to a null-terminated string containing the name of
+*        the author of the transformation function.
+*     contact
+*        Pointer to a null-terminated string containing contact
+*        details for the author of the function (e.g. an e-mail or WWW
+*        address).
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   char *clname;                 /* Pointer to cleaned name string */
+   int found;                    /* Transformation function found? */
+   int ifun;                     /* Loop counter for function information */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* This function modifies the global static tran_data array, so we use a
+   mutex to ensure that only one thread can run this function at any one
+   time. */
+   LOCK_MUTEX1;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Indicate that subsequent memory allocations may never be freed (other
+   than by any AST exit handler). */
+   astBeginPM;
+
+/* Clean (and validate) the name supplied. */
+   clname = CleanName( name, "astIntraReg", status );
+
+/* If OK, also validate the numbers of input and output
+   coordinates. Report an error if necessary. */
+   if ( astOK ) {
+      if ( ( nin < 0 ) && ( nin != AST__ANY ) ) {
+         astError( AST__BADNI, "astIntraReg(%s): Bad number of input "
+                               "coordinates (%d).", status, clname, nin );
+         astError( AST__BADNI, "This number should be zero or more (or "
+                               "AST__ANY)." , status);
+
+      } else if ( ( nout < 0 ) && ( nout != AST__ANY ) ) {
+         astError( AST__BADNO, "astIntraReg(%s): Bad number of output "
+                               "coordinates (%d).", status, clname, nout );
+         astError( AST__BADNO, "This number should be zero or more (or "
+                               "AST__ANY)." , status);
+      }
+   }
+
+/* Search the array of transformation function data to see if a
+   function with the same name has already been registered. */
+   if ( astOK ) {
+      found = 0;
+      for ( ifun = 0; ifun < tran_nfun; ifun++ ) {
+         if ( ( found = !strcmp( clname, tran_data[ ifun ].name ) ) ) break;
+      }
+
+/* If so, then check that the information supplied this time is
+   identical to that supplied before. If any discrepancy is found,
+   report an error. */
+      if ( found ) {
+         if ( ( nin != tran_data[ ifun ].nin ) ||
+              ( nout != tran_data[ ifun ].nout ) ||
+              ( tran != tran_data[ ifun ].tran ) ||
+              ( tran_wrap != tran_data[ ifun ].tran_wrap ) ||
+              ( flags != tran_data[ ifun ].flags ) ||
+              strcmp( purpose, tran_data[ ifun ].purpose ) ||
+              strcmp( author, tran_data[ ifun ].author ) ||
+              strcmp( contact, tran_data[ ifun ].contact ) ) {
+            astError( AST__MRITF, "astIntraReg: Invalid attempt to register "
+                                  "the transformation function name \"%s\" "
+                                  "multiple times.", status, clname );
+         }
+
+/* If this is a new function, extend the array of transformation
+   function data to accommodate it. */
+      } else {
+
+         tran_data = astGrow( tran_data, tran_nfun + 1, sizeof( AstIntraMapTranData ) );
+
+/* Store the information supplied. */
+         if ( astOK ) {
+            tran_data[ tran_nfun ].name = clname;
+            tran_data[ tran_nfun ].nin = nin;
+            tran_data[ tran_nfun ].nout = nout;
+            tran_data[ tran_nfun ].tran = tran;
+            tran_data[ tran_nfun ].tran_wrap = tran_wrap;
+            tran_data[ tran_nfun ].flags = flags;
+            tran_data[ tran_nfun ].purpose =
+               astStore( NULL, purpose, strlen( purpose ) + (size_t) 1 );
+            tran_data[ tran_nfun ].author =
+               astStore( NULL, author, strlen( author ) + (size_t) 1 );
+            tran_data[ tran_nfun ].contact =
+               astStore( NULL, contact, strlen( contact ) + (size_t) 1 );
+
+/* If successful, increment the count of transformation functions
+   registered. */
+            if ( astOK ) {
+               tran_nfun++;
+
+/* If an error occurred, free any memory that was allocated. */
+            } else {
+               tran_data[ tran_nfun ].name = NULL;
+               tran_data[ tran_nfun ].purpose =
+                  astFree( tran_data[ tran_nfun ].purpose );
+               tran_data[ tran_nfun ].author =
+                  astFree( tran_data[ tran_nfun ].author );
+               tran_data[ tran_nfun ].contact =
+                  astFree( tran_data[ tran_nfun ].contact );
+            }
+         }
+      }
+   }
+
+/* If an error occurred, free the memory holding the cleaned function
+   name. */
+   if ( !astOK ) clname = astFree( clname );
+
+/* Mark the end of the section in which memory allocations may never be
+   freed (other than by any AST exit handler). */
+   astEndPM;
+
+/* Unlock the mutex that ensures that only one thread can run this function
+   at any one time. */
+   UNLOCK_MUTEX1;
+
+}
+
+void astIntraReg_( const char *name, int nin, int nout,
+                   void (* tran)( AstMapping *, int, int, const double *[],
+                                  int, int, double *[] ),
+                   unsigned int flags, const char *purpose, const char *author,
+                   const char *contact, int *status ) {
+/*
+*++
+*  Name:
+c     astIntraReg
+f     AST_INTRAREG
+
+*  Purpose:
+c     Register a transformation function for use by an IntraMap.
+f     Register a transformation routine for use by an IntraMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "intramap.h"
+c     astIntraReg( const char *name, int nin, int nout,
+c                  void (* tran)( AstMapping *, int, int, const double *[],
+c                                 int, int, double *[] ),
+c                  unsigned int flags, const char *purpose, const char *author,
+c                  const char *contact )
+f     CALL AST_INTRAREG( NAME, NIN, NOUT, TRAN, FLAGS, PURPOSE, AUTHOR,
+f                        CONTACT, STATUS )
+
+*  Class Membership:
+*     IntraMap member function.
+
+*  Description:
+c     This function registers a privately-defined coordinate
+c     transformation function written in C so that it may be used to
+c     create an IntraMap. An IntraMap is a specialised form of Mapping
+c     which encapsulates the C function so that it may be used like
+c     any other AST Mapping. This allows you to create Mappings that
+c     perform any conceivable coordinate transformation.
+f     This function registers a privately-defined coordinate
+f     transformation routine written in Fortran so that it may be used
+f     to create an IntraMap. An IntraMap is a specialised form of
+f     Mapping which encapsulates the Fortran routine so that it may be
+f     used like any other AST Mapping. This allows you to create
+f     Mappings that perform any conceivable coordinate transformation.
+*
+c     Registration of relevant transformation functions is required
+c     before using the astIntraMap constructor function to create an
+c     IntraMap or reading an external representation of an IntraMap
+c     from a Channel.
+f     Registration of relevant transformation routines is required
+f     before using the AST_INTRAMAP constructor function to create an
+f     IntraMap or reading an external representation of an IntraMap
+f     from a Channel.
+
+*  Parameters:
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing a unique name
+c        to be associated with the transformation function in order to
+c        identify it. This name is case sensitive. All white space
+c        will be removed before use.
+f        A character string containing a unique name to be associated
+f        with the transformation routine in order to identify it. This
+f        name is case sensitive. All white space will be removed
+f        before use.
+c     nin
+f     NIN = INTEGER (Given)
+c        The number of input coordinates accepted by the
+c        transformation function (i.e. the number of dimensions of the
+c        space in which the input points reside). A value of AST__ANY
+c        may be given if the function is able to accommodate a
+c        variable number of input coordinates.
+f        The number of input coordinates accepted by the
+f        transformation routine (i.e. the number of dimensions of the
+f        space in which the input points reside). A value of AST__ANY
+f        may be given if the routine is able to accommodate a variable
+f        number of input coordinates.
+c     nout
+f     NOUT = INTEGER (Given)
+c        The number of output coordinates produced by the
+c        transformation function (i.e. the number of dimensions of the
+c        space in which the output points reside). A value of AST__ANY
+c        may be given if the function is able to produce a variable
+c        number of output coordinates.
+f        The number of output coordinates produced by the
+f        transformation routine (i.e. the number of dimensions of the
+f        space in which the output points reside). A value of AST__ANY
+f        may be given if the routine is able to produce a variable
+f        number of output coordinates.
+c     tran
+f     TRAN = SUBROUTINE (Given)
+c        Pointer to the transformation function to be registered.
+c        This function should perform whatever coordinate
+c        transformations are required and should have an interface
+c        like astTranP (q.v.).
+f        The transformation routine to be registered.  This routine
+f        should perform whatever coordinate transformations are
+f        required and should have an interface like AST_TRANN (q.v.).
+f
+f        This transformation routine must also appear in an EXTERNAL
+f        statement in the routine which calls AST_INTRAREG.
+c     flags
+f     FLAGS = INTEGER (Given)
+c        This value may be used to supply a set of flags which
+c        describe the transformation function and which may affect the
+c        behaviour of any IntraMap which uses it.  Often, a value of
+c        zero will be given here, but you may also supply the bitwise
+c        OR of a set of flags as described in the "Transformation
+c        Flags" section (below).
+f        This value may be used to supply a set of flags which
+f        describe the transformation routine and which may affect the
+f        behaviour of any IntraMap which uses it.  Often, a value of
+f        zero will be given here, but you may also supply the sum of a
+f        set of flags as described in the "Transformation Flags"
+f        section (below).
+c     purpose
+f     PURPOSE = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing a short (one
+c        line) textual comment to describe the purpose of the
+c        transformation function.
+f        A character string containing a short (one line) textual
+f        comment to describe the purpose of the transformation
+f        routine.
+c     author
+f     AUTHOR = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing the name of
+c        the author of the transformation function.
+f        A character string containing the name of the author of the
+f        transformation routine.
+c     contact
+f     CONTACT = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing contact
+c        details for the author of the transformation function
+c        (e.g. an e-mail or WWW address). If any IntraMap which uses
+c        this transformation function is exported as part of a dataset
+c        to an external user who does not have access to the function,
+c        then these contact details should allow them to obtain the
+c        necessary code.
+f        A character string containing contact details for the author
+f        of the transformation routine (e.g. an e-mail or WWW
+f        address). If any IntraMap which uses this transformation
+f        routine is exported as part of a dataset to an external user
+f        who does not have access to the routine, then these contact
+f        details should allow them to obtain the necessary code.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+c     - Beware that an external representation of an IntraMap (created
+c     by writing it to a Channel) will not include the coordinate
+c     transformation function which it uses, so will only refer to the
+c     function by its name (as assigned using astIntraReg).
+c     Consequently, the external representation cannot be utilised by
+c     another program unless that program has also registered the same
+c     transformation function with the same name using an identical
+c     invocation of astIntraReg. If no such registration has been
+c     performed, then attempting to read the external representation
+c     will result in an error.
+f     - Beware that an external representation of an IntraMap (created
+f     by writing it to a Channel) will not include the coordinate
+f     transformation routine which it uses, so will only refer to the
+f     routine by its name (as assigned using AST_INTRAREG).
+f     Consequently, the external representation cannot be utilised by
+f     another program unless that program has also registered the same
+f     transformation routine with the same name using an identical
+f     invocation of AST_INTRAREG. If no such registration has been
+f     performed, then attempting to read the external representation
+f     will result in an error.
+c     - You may use astIntraReg to register a transformation function
+c     with the same name more than once, but only if the arguments
+c     supplied are identical on each occasion (i.e there is no way of
+c     changing things once a function has been successfully registered
+c     under a given name, and attempting to do so will result in an
+c     error). This feature simply allows registration to be performed
+c     independently, but consistently, at several places within your
+c     program, without having to check whether it has already been
+c     done.
+f     - You may use AST_INTRAREG to register a transformation routine
+f     with the same name more than once, but only if the arguments
+f     supplied are identical on each occasion (i.e there is no way of
+f     changing things once a routine has been successfully registered
+f     under a given name, and attempting to do so will result in an
+f     error). This feature simply allows registration to be performed
+f     independently, but consistently, at several places within your
+f     program, without having to check whether it has already been
+f     done.
+c     - If an error occurs in the transformation function, this may be
+c     indicated by setting the AST error status to an error value
+c     (using astSetStatus) before it returns.  This will immediately
+c     terminate the current AST operation.  The error value AST__ITFER
+c     is available for this purpose, but other values may also be used
+c     (e.g. if you wish to distinguish different types of error).
+f     - If an error occurs in the transformation routine, this may be
+f     indicated by setting its STATUS argument to an error value
+f     before it returns.  This will immediately terminate the current
+f     AST operation.  The error value AST__ITFER is available for this
+f     purpose, but other values may also be used (e.g. if you wish to
+f     distinguish different types of error). The AST__ITFER error
+f     value is defined in the AST_ERR include file.
+
+*  Transformation Flags:
+c     The following flags are defined in the ``ast.h'' header file and
+c     allow you to provide further information about the nature of the
+c     transformation function. Having selected the set of flags which
+c     apply, you should supply the bitwise OR of their values as the
+c     ``flags'' argument to astIntraReg.
+f     The following flags are defined in the AST_PAR include file and
+f     allow you to provide further information about the nature of the
+f     transformation routine. Having selected the set of flags which
+f     apply, you should supply the sum of their values as the FLAGS
+f     argument to AST_INTRAREG.
+
+c     - AST__NOFWD: If this flag is set, it indicates that the
+c     transformation function does not implement a forward coordinate
+c     transformation. In this case, any IntraMap which uses it will
+c     have a TranForward attribute value of zero and the
+c     transformation function itself will not be invoked with its
+c     ``forward'' argument set to a non-zero value.  By default, it is
+c     assumed that a forward transformation is provided.
+f     - AST__NOFWD: If this flag is set, it indicates that the
+f     transformation routine does not implement a forward coordinate
+f     transformation. In this case, any IntraMap which uses it will
+f     have a TranForward attribute value of zero and the
+f     transformation routine itself will not be called with its
+f     FORWARD argument set to .TRUE.. By default, it is assumed that a
+f     forward transformation is provided.
+c     - AST__NOINV: If this flag is set, it indicates that the
+c     transformation function does not implement an inverse coordinate
+c     transformation. In this case, any IntraMap which uses it will
+c     have a TranInverse attribute value of zero and the
+c     transformation function itself will not be invoked with its
+c     ``forward'' argument set to zero.  By default, it is assumed
+c     that an inverse transformation is provided.
+f     - AST__NOINV: If this flag is set, it indicates that the
+f     transformation routine does not implement an inverse coordinate
+f     transformation. In this case, any IntraMap which uses it will
+f     have a TranInverse attribute value of zero and the
+f     transformation routine itself will not be called with its
+f     FORWARD argument set to .FALSE.. By default, it is assumed that
+f     an inverse transformation is provided.
+c     - AST__SIMPFI: You may set this flag if applying the
+c     transformation function's forward coordinate transformation,
+c     followed immediately by the matching inverse transformation,
+c     should always restore the original set of coordinates. It
+c     indicates that AST may replace such a sequence of operations by
+c     an identity Mapping (a UnitMap) if it is encountered while
+c     simplifying a compound Mapping (e.g. using astSimplify).  It is
+c     not necessary that both transformations have actually been
+c     implemented.
+f     - AST__SIMPFI: You may set this flag if applying the
+f     transformation routine's forward coordinate transformation,
+f     followed immediately by the matching inverse transformation,
+f     should always restore the original set of coordinates. It
+f     indicates that AST may replace such a sequence of operations by
+f     an identity Mapping (a UnitMap) if it is encountered while
+f     simplifying a compound Mapping (e.g. using AST_SIMPLIFY).  It is
+f     not necessary that both transformations have actually been
+f     implemented.
+c     - AST__SIMPIF: You may set this flag if applying the
+c     transformation function's inverse coordinate transformation,
+c     followed immediately by the matching forward transformation,
+c     should always restore the original set of coordinates. It
+c     indicates that AST may replace such a sequence of operations by
+c     an identity Mapping (a UnitMap) if it is encountered while
+c     simplifying a compound Mapping (e.g. using astSimplify).  It is
+c     not necessary that both transformations have actually been
+c     implemented.
+f     - AST__SIMPIF: You may set this flag if applying the
+f     transformation routine's inverse coordinate transformation,
+f     followed immediately by the matching forward transformation,
+f     should always restore the original set of coordinates. It
+f     indicates that AST may replace such a sequence of operations by
+f     an identity Mapping (a UnitMap) if it is encountered while
+f     simplifying a compound Mapping (e.g. using AST_SIMPLIFY).  It is
+f     not necessary that both transformations have actually been
+f     implemented.
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Register the transformation function together with the appropriate
+   wrapper function for the C language. */
+   IntraReg( name, nin, nout, tran, TranWrap, flags, purpose, author,
+             contact, status );
+}
+
+void astIntraRegFor_( const char *name, int nin, int nout,
+                      void (* tran)( AstMapping *, int, int, const double *[],
+                                     int, int, double *[] ),
+                      void (* tran_wrap)( void (*)( AstMapping *, int, int,
+                                                    const double *[], int, int,
+                                                    double *[] ),
+                                          AstMapping *, int, int,
+                                          const double *[], int, int,
+                                          double *[], int * ),
+                      unsigned int flags, const char *purpose,
+                      const char *author, const char *contact, int *status ) {
+/*
+*+
+*  Name:
+*     astIntraRegFor
+
+*  Purpose:
+*     Register a foreign language transformation function for an IntraMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     void astIntraRegFor( const char *name, int nin, int nout,
+*                          void (* tran)( AstMapping *, int, int,
+*                                         const double *[], int, int,
+*                                         double *[] ),
+*                          void (* tran_wrap)( void (*)( AstMapping *, int,
+*                                                        int, const double *[],
+*                                                        int, int,
+*                                                        double *[] ),
+*                                              AstMapping *, int, int,
+*                                              const double *[], int, int,
+*                                              double *[], int * ),
+*                          unsigned int flags, const char *purpose,
+*                          const char *author, const char *contact )
+
+*  Class Membership:
+*     IntraMap member function.
+
+*  Description:
+*     This function registers a transformation function provided by a
+*     foreign language interface which will later be used by an
+*     IntraMap, and associates it with a name. It also stores related
+*     information which will be required by the IntraMap.
+
+*  Parameters:
+*     name
+*        Pointer to a null-terminated string containing the name to be
+*        used to identify the transformation function. This string is
+*        case sensitive. All white space is removed before use.
+*     nin
+*        The number of input coordinates per point (or AST__ANY if any
+*        number are allowed).
+*     nout
+*        The number of output coordinates per point (or AST__ANY if
+*        any number are allowed).
+*     tran
+*        Pointer to the foreign language transformation function to be
+*        registered. This may have any form of interface, which need
+*        not be known to the implementation of the IntraMap
+*        class. Instead, the method of invoking the transformation
+*        function should be encapsulated in the "tran_wrap" function
+*        (below).
+*     tran_wrap
+*        Pointer to a wrapper function appropriate to the foreign
+*        language interface. This wrapper function should have the
+*        same interface as astTranP (from the Mapping class), except
+*        that it takes a pointer to a function like "tran" as an additional
+*        first argument. The purpose of this wrapper is to invoke the
+*        transformation function via the pointer supplied, to pass it the
+*        necessary information derived from the remainder of its arguments,
+*        and then to return the results.
+*     flags
+*        This argument may be used to supply a set of flags which
+*        control the behaviour of any IntraMap which uses the
+*        registered transformation function. See the description of
+*        astIntraReg for details.
+*     purpose
+*        Pointer to a null-terminated string containing a short (one
+*        line) textual comment to describe the purpose of the
+*        transformation function.
+*     author
+*        Pointer to a null-terminated string containing the name of
+*        the author of the transformation function.
+*     contact
+*        Pointer to a null-terminated string containing contact
+*        details for the author of the transformation function
+*        (e.g. an e-mail address or URL). If any IntraMap using this
+*        transformation function is exported as part of a dataset to
+*        an external user who does not have access to the function,
+*        then these contact details should allow them to obtain the
+*        necessary code.
+
+*  Notes:
+*     - This function is only available through the public interface
+*     to the IntraMap class (not the protected interface) and is
+*     intended solely for use in implementing foreign language
+*     interfaces to this class.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Register the transformation function together with the appropriate
+   wrapper function for the foreign language interface. */
+   IntraReg( name, nin, nout, tran, tran_wrap, flags, purpose, author,
+             contact, status );
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing an IntraMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     IntraMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated IntraMap in the sequence with its
+*     neighbours, so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated IntraMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated IntraMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the IntraMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        IntraMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated IntraMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstIntraMap *intramap1;       /* Pointer to first IntraMap */
+   AstIntraMap *intramap2;       /* Pointer to second IntraMap */
+   AstMapping *new;              /* Pointer to replacement Mapping */
+   const char *class;            /* Pointer to Mapping class string */
+   int imap1;                    /* Index of first IntraMap */
+   int imap2;                    /* Index of second IntraMap */
+   int imap;                     /* Loop counter for Mappings */
+   int invert1;                  /* Invert flag value (1st IntraMap) */
+   int invert2;                  /* Invert flag value (2nd IntraMap) */
+   int nin1;                     /* No. input coordinates (1st IntraMap) */
+   int nout2;                    /* No. output coordinates (2nd IntraMap) */
+   int result;                   /* Result value to return */
+   int simpler;                  /* Mappings simplified? */
+
+/* Initialise the returned result. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Further initialisation. */
+   new = NULL;
+   simpler = 0;
+   nin1 = -1;
+
+/* We will only handle the case of IntraMaps in series and will
+   consider merging the nominated IntraMap with the Mapping which
+   follows it. Check that there is such a Mapping. */
+   if ( series && ( ( where + 1 ) < *nmap ) ) {
+
+/* Obtain the indices of the two potential IntraMaps to be merged and
+   a pointer to the first one. */
+      imap1 = where;
+      imap2 = where + 1;
+      intramap1 = (AstIntraMap *) ( *map_list )[ imap1 ];
+
+/* Obtain the Class string of the second Mapping and determine if it
+   is an IntraMap. */
+      class = astGetClass( ( *map_list )[ imap2 ] );
+      if ( astOK && !strcmp( class, "IntraMap" ) ) {
+
+/* Obtain a pointer to the second IntraMap. */
+         intramap2 = (AstIntraMap *) ( *map_list )[ imap2 ];
+
+/* Check that the two IntraMaps use the same transformation function
+   and have the same IntraFlag string (if set). */
+         if ( ( intramap1->ifun == intramap2->ifun ) &&
+              !strcmp( intramap1->intraflag ? intramap1->intraflag : "",
+                       intramap2->intraflag ? intramap2->intraflag : "" ) ) {
+
+/* Determine the number of input coordinates that the first IntraMap
+   would have if its Invert attribute were set to the value of the
+   associated invert flag. Take account of the current Invert
+   attribute in obtaining this value. */
+            invert1 = ( *invert_list )[ imap1 ];
+            if ( astGetInvert( intramap1 ) ) {
+               nin1 = invert1 ? astGetNin( intramap1 ) :
+                                astGetNout( intramap1 );
+            } else {
+               nin1 = invert1 ? astGetNout( intramap1 ) :
+                                astGetNin( intramap1 );
+            }
+
+/* Similarly, determine the number of output coordinates that the
+   second IntraMap would have. */
+            invert2 = ( *invert_list )[ imap2 ];
+            if ( astGetInvert( intramap2 ) ) {
+               nout2 = invert2 ? astGetNout( intramap2 ) :
+                                 astGetNin( intramap2 );
+            } else {
+               nout2 = invert2 ? astGetNin( intramap2 ) :
+                                 astGetNout( intramap2 );
+            }
+
+/* Check that the effect of applying the two IntraMaps will be to
+   preserve the number of coordinates. */
+            if ( astOK && ( nin1 == nout2 ) ) {
+
+/* If so, check if the first transformation function is applied in the
+   forward direction and the second in the inverse direction. If so,
+   note if this configuration can be simplified. */
+               if ( !invert1 && invert2 ) {
+                  simpler = tran_data[ intramap1->ifun ].flags & AST__SIMPFI;
+
+/* Similarly, if the first transformation function is applied in the
+   inverse direction and the second in the forward direction, then
+   note if this configuration can be simplified. */
+               } else if ( invert1 && !invert2 ) {
+                  simpler = tran_data[ intramap1->ifun ].flags & AST__SIMPIF;
+               }
+            }
+         }
+      }
+
+/* If the two IntraMaps can be simplified, create a UnitMap to replace
+   them. */
+      if ( simpler ) {
+         new = (AstMapping *) astUnitMap( nin1, "", status );
+
+/* Annul the pointers to the IntraMaps. */
+         if ( astOK ) {
+            ( *map_list )[ imap1 ] = astAnnul( ( *map_list )[ imap1 ] );
+            ( *map_list )[ imap2 ] = astAnnul( ( *map_list )[ imap2 ] );
+
+/* Insert the pointer to the replacement Mapping and initialise its
+   invert flag. */
+            ( *map_list )[ imap1 ] = new;
+            ( *invert_list )[ imap1 ] = 0;
+
+/* Loop to close the resulting gap by moving subsequent elements down
+   in the arrays. */
+            for ( imap = imap2 + 1; imap < *nmap; imap++ ) {
+               ( *map_list )[ imap - 1 ] = ( *map_list )[ imap ];
+               ( *invert_list )[ imap - 1 ] = ( *invert_list )[ imap ];
+            }
+
+/* Clear the vacated elements at the end. */
+            ( *map_list )[ *nmap - 1 ] = NULL;
+            ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+            ( *nmap )--;
+            result = imap1;
+         }
+      }
+   }
+
+/* If an error occurred, clear the returned result. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for an IntraMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     IntraMap member function (over-rides the astSetAttrib method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a IntraMap, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the IntraMap.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Vaiables: */
+   AstIntraMap *this;            /* Pointer to the IntraMap structure */
+   int intraflag;                /* Offset of IntraFlag value in string */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the IntraMap structure. */
+   this = (AstIntraMap *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* IntraFlag. */
+/* ---------- */
+   if ( nc = 0,
+        ( 0 == astSscanf( setting, "intraflag=%n%*[^\n]%n", &intraflag, &nc ) )
+        && ( nc >= len ) ) {
+      astSetIntraFlag( this, setting + intraflag );
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for an IntraMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     IntraMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a IntraMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the IntraMap.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstIntraMap *this;            /* Pointer to the IntraMap structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the IntraMap structure. */
+   this = (AstIntraMap *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* IntraFlag. */
+/* ---------- */
+   if ( !strcmp( attrib, "intraflag" ) ) {
+      result = astTestIntraFlag( this );
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply an IntraMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     IntraMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a IntraMap and a set of points encapsulated
+*     in a PointSet and transforms the points using the transformation
+*     function associated with the IntraMap.
+
+*  Parameters:
+*     this
+*        Pointer to the IntraMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate
+*        transformation should be applied, while a zero value requests
+*        the inverse transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed
+*        (output) coordinate values. A NULL value may also be given,
+*        in which case a new PointSet will be created by this
+*        function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*     - The number of coordinate values per point in the input
+*     PointSet must match the number of coordinates for the IntraMap
+*     being applied.
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points and coordinate values per point to
+*     accommodate the result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstIntraMap *this;            /* Pointer to IntraMap structure */
+   AstMapping *id;               /* Public ID for the IntraMap supplied */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   const double **ptr_in;        /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   int ncoord_in;                /* Number of coordinates per input point */
+   int ncoord_out;               /* Number of coordinates per output point */
+   int npoint;                   /* Number of points */
+   int ok;                       /* AST status OK? */
+   int status_value;             /* AST status value */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_mapping);
+
+/* Obtain a pointer to the IntraMap structure. */
+   this = (AstIntraMap *) this_mapping;
+
+/* Apply the parent mapping using the stored pointer to the Transform
+   member function inherited from the parent Mapping class. This
+   function validates all arguments and generates an output PointSet
+   if necessary, but does not actually transform any coordinate
+   values. */
+   result = (*parent_transform)( this_mapping, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the
+   input and output PointSets and obtain pointers for accessing the
+   input and output coordinate values. */
+   npoint = astGetNpoint( in );
+   ncoord_in = astGetNcoord( in );
+   ncoord_out = astGetNcoord( result );
+   ptr_in = (const double **) astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse transformation,
+   according to the direction specified and whether the Mapping has
+   been inverted. */
+   if ( astGetInvert( this ) ) forward = !forward;
+
+/* Obtain a public (external) ID for the IntraMap. This will be
+   required (instead of a true C pointer) by the transformation function,
+   since it is user-written. Clone the IntraMap pointer so that the call
+   to astAnnulID later on does not annul the IntraMap pointer. */
+   id = (AstMapping *) astMakeId( astClone( this ) );
+
+/* Locate the transformation function data associated with the
+   IntraMap and use the wrapper function to invoke the transformation
+   function itself. */
+   if ( ( ok = astOK ) ) {
+      LOCK_MUTEX2;
+      ( *tran_data[ this->ifun ].tran_wrap )( tran_data[ this->ifun ].tran,
+                                              id, npoint, ncoord_in, ptr_in,
+                                              forward, ncoord_out, ptr_out,
+                                              status );
+      UNLOCK_MUTEX2;
+
+/* If an error occurred, report a contextual error message. To ensure
+   that the location of the error appears in the message, we first clear
+   the global status (which makes the error system think this is the
+   first report). */
+      if ( !( ok = astOK ) ) {
+         status_value = astStatus;
+         astClearStatus;
+         astError( status_value,
+                   "astTransform(%s): Error signalled by \"%s\" "
+                   "transformation function.", status,
+                   astGetClass( this ), tran_data[ this->ifun ].name );
+      }
+   }
+
+/* Annul the external identifier. */
+   id = astMakeId( astAnnulId( id ) );
+
+/* If an error occurred here, but earlier steps were successful, then
+   something has happened to the external ID, so report a contextual
+   error message. */
+   if ( !astOK && ok ) {
+      astError( astStatus,
+                "astTransform(%s): %s pointer corrupted by \"%s\" "
+                "transformation function.", status,
+                astGetClass( this ), astGetClass( this ),
+                tran_data[ this->ifun ].name );
+   }
+
+/* If an error occurred, clear the returned pointer. If a new output
+   PointSet has been created, then delete it. */
+   if ( !astOK ) {
+      result = ( result == out ) ? NULL : astDelete( result );
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static void TranWrap( void (* tran)( AstMapping *, int, int, const double *[],
+                                     int, int, double *[] ),
+                      AstMapping *this, int npoint, int ncoord_in,
+                      const double *ptr_in[], int forward, int ncoord_out,
+                      double *ptr_out[], int *status ) {
+/*
+*  Name:
+*     TranWrap
+
+*  Purpose:
+*     Wrapper function to invoke a C transformation function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void TranWrap( void (* tran)( AstMapping *, int, int, const double *[],
+*                                   int, int, double *[] ),
+*                    AstMapping *this, int npoint, int ncoord_in,
+*                    const double *ptr_in[], int forward, int ncoord_out,
+*                    double *ptr_out[], int *status )
+
+*  Class Membership:
+*     IntraMap member function.
+
+*  Description:
+*     This function invokes a C implementation of a transformation
+*     function (which resembles the astTranP function from the Mapping
+*     class).
+*
+*     This wrapper is essentially a dummy function for the C language.
+*     It may be replaced by alternative versions for foreign language
+*     interfaces, thus allowing transformation functions supplied by
+*     those languages to be invoked without knowledge of their
+*     interfaces.
+
+*  Parameters:
+*     tran
+*        Pointer to the transformation function to be invoked. This
+*        should resemble astTranP (but with the first argument
+*        omitted).
+*     this
+*        An external Mapping ID associated with the internal (true C) pointer
+*        for the IntraMap whose transformation is being evaluated.
+*     npoint
+*        The number of points to be transformed.
+*     ncoord_in
+*        The number of coordinates being supplied for each input point
+*        (i.e. the number of dimensions of the space in which the
+*        input points reside).
+*     ptr_in
+*        An array of pointers to double, with "ncoord_in"
+*        elements. Element "ptr_in[coord]" should point at the first
+*        element of an array of double (with "npoint" elements) which
+*        contain the values of coordinate number "coord" for each
+*        input (untransformed) point. The value of coordinate number
+*        "coord" for input point number "point" is therefore given by
+*        "ptr_in[coord][point]".
+*     forward
+*        A non-zero value indicates that the forward coordinate
+*        transformation is to be applied, while a zero value indicates
+*        that the inverse transformation should be used.
+*     ncoord_out
+*        The number of coordinates being generated for each output
+*        point (i.e. the number of dimensions of the space in which
+*        the output points reside). This need not be the same as
+*        "ncoord_in".
+*     ptr_out
+*        An array of pointers to double, with "ncoord_out"
+*        elements. Element "ptr_out[coord]" should point at the first
+*        element of an array of double (with "npoint" elements) into
+*        which the values of coordinate number "coord" for each output
+*        (transformed) point will be written.  The value of coordinate
+*        number "coord" for output point number "point" will therefore
+*        be found in "ptr_out[coord][point]".
+*     status
+*        Pointer to the inherited status value.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the transformation function. */
+   ( *tran )( this, npoint, ncoord_in, ptr_in, forward, ncoord_out, ptr_out );
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     IntraFlag
+
+*  Purpose:
+*     IntraMap identification string.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+c     This attribute allows an IntraMap to be flagged so that it is
+c     distinguishable from other IntraMaps. The transformation function
+c     associated with the IntraMap may then enquire the value of this
+c     attribute and adapt the transformation it provides according to the
+c     particular IntraMap involved.
+f     This attribute allows an IntraMap to be flagged so that it is
+f     distinguishable from other IntraMaps. The transformation routine
+f     associated with the IntraMap may then enquire the value of this
+f     attribute and adapt the transformation it provides according to the
+f     particular IntraMap involved.
+*
+c     Although this is a string attribute, it may often be useful to store
+c     numerical values here, encoded as a character string, and to use these
+c     as data within the transformation function. Note, however, that this
+c     mechanism is not suitable for transferring large amounts of data (more
+c     than about 1000 characters) to an IntraMap. For that purpose, global
+c     variables are recommended, although the IntraFlag value can be used to
+c     supplement this approach. The default IntraFlag value is an empty
+c     string.
+f     Although this is a string attribute, it may often be useful to store
+f     numerical values here, encoded as a character string, and to use these
+f     as data within the transformation routine. Note, however, that this
+f     mechanism is not suitable for transferring large amounts of data (more
+f     than about 1000 characters) to an IntraMap. For that purpose, global
+f     variables are recommended, although the IntraFlag value can be used to
+f     supplement this approach. The default IntraFlag value is an empty
+f     string.
+
+*  Applicability:
+*     IntraMap
+*        All IntraMaps have this attribute.
+
+*  Notes:
+c     - A pair of IntraMaps whose transformations may potentially cancel
+c     cannot be simplified to produce a UnitMap (e.g. using astSimplify)
+c     unless they have the same IntraFlag values. The test for equality is
+c     case-sensitive.
+f     - A pair of IntraMaps whose transformations may potentially cancel
+f     cannot be simplified to produce a UnitMap (e.g. using AST_SIMPLIFY)
+f     unless they have the same IntraFlag values. The test for equality is
+f     case-sensitive.
+*att--
+*/
+
+/* Clear the IntraFlag value by freeing the allocated memory and
+   assigning a NULL pointer. */
+astMAKE_CLEAR(IntraMap,IntraFlag,intraflag,astFree( this->intraflag ))
+
+/* Return a pointer to the IntraFlag value. */
+astMAKE_GET(IntraMap,IntraFlag,const char *,NULL,this->intraflag)
+
+/* Set a IntraFlag value by freeing any previously allocated memory,
+   allocating new memory, storing the string and saving the pointer to
+   the copy. */
+astMAKE_SET(IntraMap,IntraFlag,const char *,intraflag,astStore(
+                                                      this->intraflag, value,
+                                                      strlen( value ) +
+                                                      (size_t) 1 ))
+
+/* The IntraFlag value is set if the pointer to it is not NULL. */
+astMAKE_TEST(IntraMap,IntraFlag,( this->intraflag != NULL ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for IntraMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for IntraMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstIntraMap *in;              /* Pointer to input IntraMap */
+   AstIntraMap *out;             /* Pointer to output IntraMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output IntraMaps. */
+   in = (AstIntraMap *) objin;
+   out = (AstIntraMap *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output IntraMap. */
+   out->intraflag = NULL;
+
+/* If necessary, allocate memory in the output IntraMap and store a
+   copy of the input IntraFlag string. */
+   if ( in->intraflag ) out->intraflag = astStore( NULL, in->intraflag,
+                                                   strlen( in->intraflag ) +
+                                                   (size_t) 1 );
+
+/* If an error occurred, free any allocated memory. */
+   if ( !astOK ) {
+      out->intraflag = astFree( out->intraflag );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for IntraMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for IntraMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstIntraMap *this;            /* Pointer to IntraMap */
+
+/* Obtain a pointer to the IntraMap structure. */
+   this = (AstIntraMap *) obj;
+
+/* Free the memory used for the IntraFlag string if necessary. */
+   this->intraflag = astFree( this->intraflag );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for IntraMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the IntraMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the IntraMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS             /* Pointer to thread-specific global data */
+   AstIntraMap *this;             /* Pointer to the IntraMap structure */
+   const char *sval;              /* Pointer to string value */
+   int set;                       /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the IntraMap structure. */
+   this = (AstIntraMap *) this_object;
+
+/* Write out values representing the instance variables for the
+   IntraMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* Transformation function name. */
+/* ----------------------------- */
+   astWriteString( channel, "Fname", 1, 1, tran_data[ this->ifun ].name,
+                   "Name of transformation function" );
+
+/* IntraFlag string. */
+/* ----------------- */
+   set = TestIntraFlag( this, status );
+   sval = set ? GetIntraFlag( this, status ) : astGetIntraFlag( this );
+   astWriteString( channel, "Iflag", set, 0, sval,
+                   "IntraMap identification string" );
+
+/* Purpose string. */
+/* --------------- */
+   astWriteString( channel, "Purp", 1, 1, tran_data[ this->ifun ].purpose,
+                   "Purpose of function" );
+
+/* Author's name. */
+/* -------------- */
+   astWriteString( channel, "Auth", 1, 1, tran_data[ this->ifun ].author,
+                   "Author's name" );
+
+/* Contact details. */
+/* ---------------- */
+   astWriteString( channel, "Cntact", 1, 1, tran_data[ this->ifun ].contact,
+                   "Contact address" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAIntraMap and astCheckIntraMap functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(IntraMap,Mapping)
+astMAKE_CHECK(IntraMap)
+
+AstIntraMap *astIntraMap_( const char *name, int nin, int nout,
+                           const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astIntraMap
+f     AST_INTRAMAP
+
+*  Purpose:
+*     Create an IntraMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "intramap.h"
+c     AstIntraMap *astIntraMap( const char *name, int nin, int nout,
+c                               const char *options, ... )
+f     RESULT = AST_INTRAMAP( NAME, NIN, NOUT, OPTIONS, STATUS )
+
+*  Class Membership:
+*     IntraMap constructor.
+
+*  Description:
+*     This function creates a new IntraMap and optionally initialises
+*     its attributes.
+*
+c     An IntraMap is a specialised form of Mapping which encapsulates
+c     a privately-defined coordinate transformation function
+c     (e.g. written in C) so that it may be used like any other AST
+c     Mapping. This allows you to create Mappings that perform any
+c     conceivable coordinate transformation.
+f     An IntraMap is a specialised form of Mapping which encapsulates
+f     a privately-defined coordinate transformation routine
+f     (e.g. written in Fortran) so that it may be used like any other
+f     AST Mapping. This allows you to create Mappings that perform any
+f     conceivable coordinate transformation.
+*
+*     However, an IntraMap is intended for use within a single program
+*     or a private suite of software, where all programs have access
+*     to the same coordinate transformation functions (i.e. can be
+*     linked against them). IntraMaps should not normally be stored in
+*     datasets which may be exported for processing by other software,
+*     since that software will not have the necessary transformation
+*     functions available, resulting in an error.
+*
+c     You must register any coordinate transformation functions to be
+c     used using astIntraReg before creating an IntraMap.
+f     You must register any coordinate transformation functions to be
+f     used using AST_INTRAREG before creating an IntraMap.
+
+*  Parameters:
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing the name of
+c        the transformation function to use (which should previously
+c        have been registered using astIntraReg). This name is case
+c        sensitive. All white space will be removed before use.
+f        A character string containing the name of the transformation
+f        routine to use (which should previously have been registered
+f        using AST_INTRAREG). This name is case sensitive. All white
+f        space will be removed before use.
+c     nin
+f     NIN = INTEGER (Given)
+c        The number of input coordinates. This must be compatible with
+c        the number of input coordinates accepted by the
+c        transformation function (as specified when this function was
+c        registered using astIntraReg).
+f        The number of input coordinates. This must be compatible with
+f        the number of input coordinates accepted by the
+f        transformation routine (as specified when this routine was
+f        registered using AST_INTRAREG).
+c     nout
+f     NOUT = INTEGER (Given)
+c        The number of output coordinates. This must be compatible
+c        with the number of output coordinates produced by the
+c        transformation function (as specified when this function was
+c        registered using astIntraReg).
+f        The number of output coordinates. This must be compatible
+f        with the number of output coordinates produced by the
+f        transformation routine (as specified when this routine was
+f        registered using AST_INTRAREG).
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new IntraMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new IntraMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astIntraMap()
+f     AST_INTRAMAP = INTEGER
+*        A pointer to the new IntraMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstIntraMap *new;             /* Pointer to new IntraMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the IntraMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitIntraMap( NULL, sizeof( AstIntraMap ), !class_init,
+                          &class_vtab, "IntraMap", name, nin, nout );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   IntraMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new IntraMap. */
+   return new;
+}
+
+AstIntraMap *astIntraMapId_( const char *name, int nin, int nout,
+                             const char *options, ... ) {
+/*
+*  Name:
+*     astIntraMapId_
+
+*  Purpose:
+*     Create an IntraMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     AstIntraMap *astIntraMapId_( const char *name, int nin, int nout,
+*                                  const char *options, ... )
+
+*  Class Membership:
+*     IntraMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astIntraMap constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astIntraMap_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astIntraMap_ directly, so it must be a
+*     re-implementation of it in all respects, except for the final
+*     conversion of the result to an ID value.
+
+*  Parameters:
+*     As for astIntraMap_.
+
+*  Returned Value:
+*     The ID value associated with the new IntraMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstIntraMap *new;             /* Pointer to new IntraMap */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the IntraMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitIntraMap( NULL, sizeof( AstIntraMap ), !class_init,
+                          &class_vtab, "IntraMap", name, nin, nout );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   IntraMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new IntraMap. */
+   return astMakeId( new );
+}
+
+
+AstIntraMap *astInitIntraMap_( void *mem, size_t size, int init,
+                               AstIntraMapVtab *vtab, const char *name,
+                               const char *fname, int nin, int nout, int *status ) {
+/*
+*+
+*  Name:
+*     astInitIntraMap
+
+*  Purpose:
+*     Initialise an IntraMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     AstIntraMap *astInitIntraMap( void *mem, size_t size, int init,
+*                                   AstIntraMapVtab *vtab, const char *name,
+*                                   const char *fname, int nin, int nout )
+
+*  Class Membership:
+*     IntraMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new IntraMap object. It allocates memory (if
+*     necessary) to accommodate the IntraMap plus any additional data
+*     associated with the derived class.  It then initialises a
+*     IntraMap structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual
+*     function table for a IntraMap at the start of the memory passed
+*     via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the IntraMap is to be
+*        initialised.  This must be of sufficient size to accommodate
+*        the IntraMap data (sizeof(IntraMap)) plus any data used by
+*        the derived class. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the IntraMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the IntraMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*     init
+*        A logical flag indicating if the IntraMap's virtual function
+*        table is to be initialised. If this value is non-zero, the
+*        virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new IntraMap.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*     fname
+*        Pointer to a null-terminated string containing the name of
+*        the transformation function to be used, as previously
+*        registered using astIntraReg.
+*     nin
+*        The number of input coordinates.
+*     nout
+*        The number of output coordinates.
+
+*  Returned Value:
+*     A pointer to the new IntraMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS             /* Pointer to thread-specific global data */
+   AstIntraMap *new;              /* Pointer to new IntraMap */
+   char *clname;                  /* Cleaned transformation function name */
+   int found;                     /* Transformation function name found? */
+   int ifun;                      /* Loop counter for registered functions */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   found = 0;
+   ifun = 0;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitIntraMapVtab( vtab, name );
+
+/* Clean (and validate) the transformation function name supplied. */
+   clname = CleanName( fname, "astIntraMap", status );
+
+/* Search for a registered transformation function name which matches. */
+   if ( astOK ) {
+      found = 0;
+      for ( ifun = 0; ifun < tran_nfun; ifun++ ) {
+         if ( ( found = !strcmp( clname, tran_data[ ifun ].name ) ) ) break;
+      }
+   }
+
+/* Free the memory containing the cleaned name string. */
+   clname = astFree( clname );
+
+/* If no match was found, then report an error. */
+   if ( astOK ) {
+      if ( !found ) {
+         astError( AST__URITF, "astInitIntraMap(%s): The transformation "
+                               "function \"%s\" has not been registered using "
+                               "astIntraReg.", status, name, clname );
+
+/* Check that the number of input coordinates is compatible with the
+   number used by the transformation function (as specified when it
+   was registered). Report an error if necessary. */
+      } else {
+         if ( ( nin != tran_data[ ifun ].nin ) &&
+              ( tran_data[ ifun ].nin != AST__ANY ) ) {
+            astError( AST__BADNI, "astInitIntraMap(%s): Number of input "
+                                  "coordinates (%d) does not match the number "
+                                  "used by the \"%s\" transformation function "
+                                  "(%d).", status, name, nin, tran_data[ ifun ].name,
+                                  tran_data[ ifun ].nin  );
+
+/* Similarly check the number of output coordinates. */
+         } else if ( ( nout != tran_data[ ifun ].nout ) &&
+                     ( tran_data[ ifun ].nout != AST__ANY ) ) {
+            astError( AST__BADNO, "astInitIntraMap(%s): Number of output "
+                                  "coordinates (%d) does not match the number "
+                                  "used by the \"%s\" transformation function "
+                                  "(%d).", status, name, nout, tran_data[ ifun ].name,
+                                  tran_data[ ifun ].nout  );
+
+/* If OK, initialise a Mapping structure (the parent class) as the
+   first component within the IntraMap structure, allocating memory if
+   necessary (note that this also provides further checks on the
+   validity of "nin" and "nout"). Specify whether the forward and
+   inverse transformations are defined. */
+         } else {
+            new = (AstIntraMap *) astInitMapping( mem, size, 0,
+                     (AstMappingVtab *) vtab, name, nin, nout,
+                     ( ( tran_data[ ifun ].flags & AST__NOFWD ) == 0 ),
+                     ( ( tran_data[ ifun ].flags & AST__NOINV ) == 0 ) );
+
+            if ( astOK ) {
+
+/* Initialise the IntraMap data. */
+/* ---------------------------- */
+/* Initialise the IntraFlag string pointer. */
+               new->intraflag = NULL;
+
+/* Store the index used to access the transformation function data. */
+               new->ifun = ifun;
+
+/* If an error occurred, clean up by deleting the new IntraMap. */
+               if ( !astOK ) new = astDelete( new );
+            }
+         }
+      }
+   }
+
+/* Return a pointer to the new IntraMap. */
+   return new;
+}
+
+AstIntraMap *astLoadIntraMap_( void *mem, size_t size,
+                               AstIntraMapVtab *vtab, const char *name,
+                               AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadIntraMap
+
+*  Purpose:
+*     Load an IntraMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "intramap.h"
+*     AstIntraMap *astLoadIntraMap( void *mem, size_t size,
+*                                    AstIntraMapVtab *vtab, const char *name,
+*                                    AstChannel *channel )
+
+*  Class Membership:
+*     IntraMap loader.
+
+*  Description:
+*     This function is provided to load a new IntraMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     IntraMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for an IntraMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the IntraMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        IntraMap data (sizeof(IntraMap)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the IntraMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the IntraMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstIntraMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new IntraMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the IntraMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "IntraMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new IntraMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS             /* Pointer to thread-specific global data */
+   AstIntraMap *new;              /* Pointer to the new IntraMap */
+   char *author;                  /* Pointer to author's name string */
+   char *contact;                 /* Pointer to contact details string */
+   char *fname;                   /* Pointer to transformation function name */
+   char *purpose;                 /* Pointer to purpose comment string */
+   int found;                     /* Function name found? */
+   int ifun;                      /* Loop counter for registered functions */
+   int nin;                       /* Number of IntraMap input coordinates */
+   int nout;                      /* Number of IntraMap output coordinates */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this IntraMap. In this case the
+   IntraMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstIntraMap );
+      vtab = &class_vtab;
+      name = "IntraMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitIntraMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built IntraMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "IntraMap" );
+
+/* Now read each individual data item from this list. */
+
+/* Transformation function name. */
+/* ----------------------------- */
+      fname = astReadString( channel, "fname", "" );
+
+/* IntraFlag string. */
+/* ----------------- */
+      new->intraflag = astReadString( channel, "iflag", NULL );
+
+/* Purpose string. */
+/* --------------- */
+      purpose = astReadString( channel, "purp", "" );
+
+/* Author's name. */
+/* -------------- */
+      author = astReadString( channel, "auth", "" );
+
+/* Contact details. */
+/* ---------------- */
+      contact = astReadString( channel, "cntact", "" );
+
+/* If OK, search the array of transformation function data to see if
+   the required transformation function has been registered. */
+      if ( astOK ) {
+         found = 0;
+         for ( ifun = 0; ifun < tran_nfun; ifun++ ) {
+            if ( ( found = !strcmp( fname, tran_data[ ifun ].name ) ) ) break;
+         }
+
+/* If the transformation function has not been registered, report an
+   error explaining how to obtain it from the author and register
+   it. */
+         if ( !found ) {
+            astError( AST__URITF, "astLoadIntraMap(%s): An IntraMap was read "
+                                  "which uses an unknown transformation "
+                                  "function.", status, astGetClass( channel ) );
+            astError( AST__URITF, "This is a private extension to the AST "
+                                  "library: to handle it, you must obtain the "
+                                  "source code from its author." , status);
+            astError( AST__URITF, "You can then register it with AST in your "
+                                  "software by calling astIntraReg (see "
+                                  "SUN/211)." , status);
+            astError( AST__URITF, " " , status);
+            astError( AST__URITF, "   Function name:   \"%s\".", status, fname );
+            astError( AST__URITF, "   Purpose:         \"%s\".", status, purpose );
+            astError( AST__URITF, "   Author:          \"%s\".", status, author );
+            astError( AST__URITF, "   Contact address: \"%s\".", status, contact );
+            astError( AST__URITF, " " , status);
+
+/* Obtain the numbers of input and output coordinates for the
+   IntraMap. Use parent methods for this, since if any derived class
+   has overridden these methods it may depend on data that have not
+   yet been loaded. */
+         } else {
+            nin = ( *parent_getnin )( (AstMapping *) new, status );
+            nout = ( *parent_getnout )( (AstMapping *) new, status );
+            if ( astOK ) {
+
+/* Check that the numbers of coordinates are compatible with the
+   numbers used by the transformation function, as specified when it
+   was registered. */
+               if ( ( nin != tran_data[ ifun ].nin ) &&
+                    ( tran_data[ ifun ].nin != AST__ANY ) ) {
+                  astError( AST__BADNI, "astLoadIntraMap(%s): The number of "
+                                        "input coordinates for the IntraMap "
+                                        "read (%d) does not match the number "
+                                        "used by the registered \"%s\" "
+                                        "transformation function (%d).", status,
+                                        astGetClass( channel ), nin,
+                                        tran_data[ ifun ].name,
+                                        tran_data[ ifun ].nin  );
+               } else if ( ( nout != tran_data[ ifun ].nout ) &&
+                           ( tran_data[ ifun ].nout != AST__ANY ) ) {
+                  astError( AST__BADNO, "astLoadIntraMap(%s): The number of "
+                                        "output coordinates for the IntraMap "
+                                        "read (%d) does not match the number "
+                                        "used by the registered \"%s\" "
+                                        "transformation function (%d).", status,
+                                        astGetClass( channel ), nout,
+                                        tran_data[ ifun ].name,
+                                        tran_data[ ifun ].nout  );
+
+/* If OK, store the index used to access the transformation function
+   data. */
+               } else {
+                  new->ifun = ifun;
+               }
+            }
+         }
+      }
+
+/* Free strings allocated by astReadString. */
+      fname = astFree( fname );
+      purpose = astFree( purpose );
+      author = astFree( author );
+      contact = astFree( contact );
+
+/* If an error occurred, clean up by deleting the new IntraMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new IntraMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
diff --git a/ast/intramap.h b/ast/intramap.h
new file mode 100644
index 0000000..ac2d8cd
--- /dev/null
+++ b/ast/intramap.h
@@ -0,0 +1,344 @@
+#if !defined( INTRAMAP_INCLUDED ) /* Include this file only once */
+#define INTRAMAP_INCLUDED
+/*
+*+
+*  Name:
+*     intramap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the IntraMap class.
+
+*  Invocation:
+*     #include "intramap.h"
+
+*  Description:
+*     This include file defines the interface to the IntraMap class
+*     and provides the type definitions, function prototypes and
+*     macros, etc. needed to use this class.
+*
+*     The IntraMap class implements Mappings which transform
+*     coordinates using a privately-defined transformation function
+*     (e.g. written in C).
+
+*  Inheritance:
+*     The IntraMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     IntraFlag
+*        IntraMap identification string.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astMapMerge
+*           Simplify a sequence of Mappings containing an IntraMap.
+*        astTransform
+*           Transform a set of points using an IntraMap.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearIntraFlag
+*           Clear the IntraFlag attribute for an IntraMap.
+*        astGetIntraFlag
+*           Get the value of the IntraFlag attribute for an IntraMap.
+*        astSetIntraFlag
+*           Set the value of the IntraFlag attribute for an IntraMap.
+*        astTestIntraFlag
+*           Test whether a value has been set for the IntraFlag attribute of
+*           an IntraMap.
+
+*  Other Class Functions:
+*     Public:
+*        astIntraMap
+*           Create an IntraMap.
+*        astIntraReg
+*           Register a transformation function for use by an IntraMap.
+*        astIsAIntraMap
+*           Test class membership.
+*
+*     Protected:
+*        astCheckIntraMap
+*           Validate class membership.
+*        astInitIntraMap
+*           Initialise an IntraMap.
+*        astLoadIntraMap
+*           Load an IntraMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstIntraMap
+*           IntraMap object type.
+*
+*     Protected:
+*        AstIntraMapVtab
+*           IntraMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     24-MAR-1998 (RFWS):
+*        Original version.
+*     16-SEP-1999 (RFWS):
+*        Added the IntraFlag attribute and added a Mapping pointer as a new
+*        first argument to transformation functions.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitIntraMapVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macro Definitions. */
+/* ================== */
+#define AST__NOFWD (1U)          /* No forward transformation defined */
+#define AST__NOINV (2U)          /* No inverse transformation defined */
+#define AST__SIMPFI (4U)         /* Forward-inverse may be simplified */
+#define AST__SIMPIF (8U)         /* Inverse-forward may be simplified */
+
+#define AST__ANY (-66)           /* Allow any number of input/output coords */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* IntraMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstIntraMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   char *intraflag;              /* Pointer to identification string */
+   int ifun;                     /* Transformation function index */
+} AstIntraMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstIntraMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   const char *(* GetIntraFlag)( AstIntraMap *, int * );
+   int (* TestIntraFlag)( AstIntraMap *, int * );
+   void (* ClearIntraFlag)( AstIntraMap *, int * );
+   void (* SetIntraFlag)( AstIntraMap *, const char *, int * );
+} AstIntraMapVtab;
+
+
+/* Structure to hold data for transformation functions. */
+typedef struct AstIntraMapTranData {
+   void (* tran)( AstMapping *, int, int, const double *[], int, int, double *[] );
+                                 /* Pointer to transformation function */
+   void (* tran_wrap)( void (*)( AstMapping *, int, int, const double *[], int, int, double *[] ), AstMapping *, int, int, const double *[], int, int, double *[], int * );
+                                 /* Pointer to wrapper function */
+   char *author;                 /* Author's name */
+   char *contact;                /* Contact details (e.g. e-mail address) */
+   char *name;                   /* Function name (assigned by caller) */
+   char *purpose;                /* Comment string describing purpose */
+   int nin;                      /* Number of input coordinates per point */
+   int nout;                     /* Number of output coordinates per point */
+   unsigned int flags;           /* Flags to describe function behaviour */
+} AstIntraMapTranData;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstIntraMapGlobals {
+   AstIntraMapVtab Class_Vtab;
+   int Class_Init;
+} AstIntraMapGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(IntraMap)          /* Check class membership */
+astPROTO_ISA(IntraMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstIntraMap *astIntraMap_( const char *, int, int, const char *, int *, ...);
+#else
+AstIntraMap *astIntraMapId_( const char *, int, int, const char *, ... )__attribute__((format(printf,4,5)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstIntraMap *astInitIntraMap_( void *, size_t, int, AstIntraMapVtab *,
+                               const char *, const char *, int, int, int * );
+
+/* Vtab initialiser. */
+void astInitIntraMapVtab_( AstIntraMapVtab *, const char *, int * );
+
+/* Loader. */
+AstIntraMap *astLoadIntraMap_( void *, size_t, AstIntraMapVtab *,
+                               const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitIntraMapGlobals_( AstIntraMapGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+void astIntraReg_( const char *, int, int, void (*)( AstMapping *, int, int, const double *[], int, int, double *[] ), unsigned int, const char *, const char *, const char *, int * );
+
+#if defined(astCLASS)            /* Protected */
+const char *astGetIntraFlag_( AstIntraMap *, int * );
+int astTestIntraFlag_( AstIntraMap *, int * );
+void astClearIntraFlag_( AstIntraMap *, int * );
+void astSetIntraFlag_( AstIntraMap *, const char *, int * );
+
+#else                            /* Public only */
+void astIntraRegFor_( const char *, int, int, void (*)( AstMapping *, int, int, const double *[], int, int, double *[] ), void (*)( void (*)( AstMapping *, int, int, const double *[], int, int, double *[]), AstMapping *, int, int, const double *[], int, int, double *[], int * ), unsigned int, const char *, const char *, const char *, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckIntraMap(this) astINVOKE_CHECK(IntraMap,this,0)
+#define astVerifyIntraMap(this) astINVOKE_CHECK(IntraMap,this,1)
+
+/* Test class membership. */
+#define astIsAIntraMap(this) astINVOKE_ISA(IntraMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astIntraMap astINVOKE(F,astIntraMap_)
+#else
+#define astIntraMap astINVOKE(F,astIntraMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitIntraMap(mem,size,init,vtab,name,fname,nin,nout) \
+astINVOKE(O,astInitIntraMap_(mem,size,init,vtab,name,fname,nin,nout,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitIntraMapVtab(vtab,name) astINVOKE(V,astInitIntraMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadIntraMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadIntraMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckIntraMap to validate IntraMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#define astIntraReg(name,nin,nout,tran,flags,purpose,author,contact) \
+astIntraReg_(name,nin,nout,tran,flags,purpose,author,contact,STATUS_PTR)
+
+#if defined(astCLASS)            /* Protected */
+#define astClearIntraFlag(this) \
+astINVOKE(V,astClearIntraFlag_(astCheckIntraMap(this),STATUS_PTR))
+#define astGetIntraFlag(this) \
+astINVOKE(V,astGetIntraFlag_(astCheckIntraMap(this),STATUS_PTR))
+#define astSetIntraFlag(this,value) \
+astINVOKE(V,astSetIntraFlag_(astCheckIntraMap(this),value,STATUS_PTR))
+#define astTestIntraFlag(this) \
+astINVOKE(V,astTestIntraFlag_(astCheckIntraMap(this),STATUS_PTR))
+
+#else                            /* Public only */
+#define astIntraRegFor(name,nin,nout,tran,tran_wrap,flags,purpose,author,contact) \
+astIntraRegFor_(name,nin,nout,tran,tran_wrap,flags,purpose,author,contact,STATUS_PTR)
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/keymap.c b/ast/keymap.c
new file mode 100644
index 0000000..1578fe3
--- /dev/null
+++ b/ast/keymap.c
@@ -0,0 +1,10786 @@
+/*
+*class++
+*  Name:
+*     KeyMap
+
+*  Purpose:
+*     Store a set of key/value pairs.
+
+*  Constructor Function:
+c     astKeyMap
+f     AST_KEYMAP
+
+*  Description:
+*     The KeyMap class is used to store a set of values with associated keys
+*     which identify the values. The keys are strings. These may be case
+*     sensitive or insensitive as selected by the KeyCase attribute, and
+*     trailing spaces are ignored. The value associated with a key can be
+*     integer (signed 4 and 2 byte, or unsigned 1 byte), floating point
+*     (single or double precision),
+c     void pointer,
+*     character string or AST Object pointer. Each
+*     value can be a scalar or a one-dimensional vector. A KeyMap is
+*     conceptually similar to a Mapping in that a KeyMap transforms an
+*     input into an output - the input is the key, and the output is the
+*     value associated with the key. However, this is only a conceptual
+*     similarity, and it should be noted that the KeyMap class inherits from
+*     the Object class rather than the Mapping class. The methods of the
+*     Mapping class cannot be used with a KeyMap.
+
+*  Inheritance:
+*     The KeyMap class inherits from the Object class.
+
+*  Attributes:
+*     In addition to those attributes common to all Objects, every
+*     KeyMap also has the following attributes:
+*
+*     - KeyCase: Sets the case in which keys are stored
+*     - KeyError: Report an error if the requested key does not exist?
+*     - SizeGuess: The expected size of the KeyMap.
+*     - SortBy: Determines how keys are sorted in a KeyMap.
+*     - MapLocked: Prevent new entries being added to the KeyMap?
+
+*  Functions:
+c     In addition to those functions applicable to all Objects, the
+c     following functions may also be applied to all KeyMaps:
+f     In addition to those routines applicable to all Objects, the
+f     following routines may also be applied to all KeyMaps:
+*
+c     - astMapDefined: Does a KeyMap contain a defined value for a key?
+c     - astMapGet0<X>: Get a named scalar entry from a KeyMap
+c     - astMapGet1<X>: Get a named vector entry from a KeyMap
+c     - astMapGetElem<X>: Get an element of a named vector entry from a KeyMap
+c     - astMapHasKey: Does the KeyMap contain a named entry?
+c     - astMapKey: Return the key name at a given index in the KeyMap
+c     - astMapLenC: Get the length of a named character entry in a KeyMap
+c     - astMapLength: Get the length of a named entry in a KeyMap
+c     - astMapCopy: Copy entries from one KeyMap into another
+c     - astMapPut0<X>: Add a new scalar entry to a KeyMap
+c     - astMapPut1<X>: Add a new vector entry to a KeyMap
+c     - astMapPutElem<X>: Puts a value into a vector entry in a KeyMap
+c     - astMapPutU: Add a new entry to a KeyMap with an undefined value
+c     - astMapRemove: Removed a named entry from a KeyMap
+c     - astMapRename: Rename an existing entry in a KeyMap
+c     - astMapSize: Get the number of entries in a KeyMap
+c     - astMapType: Return the data type of a named entry in a map
+f     - AST_MAPDEFINED: Does a KeyMap contain a defined value for a key?
+f     - AST_MAPGET0<X>: Get a named scalar entry from a KeyMap
+f     - AST_MAPGET1<X>: Get a named vector entry from a KeyMap
+f     - AST_MAPGETELEM<X>: Get an element of a named vector entry from a KeyMap
+f     - AST_MAPHASKEY: Does the KeyMap contain a named entry?
+f     - AST_MAPKEY: Return the key name at a given index in the KeyMap
+f     - AST_MAPLENC: Get the length of a named character entry in a KeyMap
+f     - AST_MAPLENGTH: Get the length of a named entry in a KeyMap
+f     - AST_MAPCOPY: Copy entries from one KeyMap into another
+f     - AST_MAPPUT0<X>: Add a new scalar entry to a KeyMap
+f     - AST_MAPPUT1<X>: Add a new vector entry to a KeyMap
+f     - AST_MAPPUTELEM<X>: Puts a value into a vector entry in a KeyMap
+f     - AST_MAPPUTU: Add a new entry to a KeyMap with an undefined value
+f     - AST_MAPREMOVE: Removed a named entry from a KeyMap
+f     - AST_MAPRENAME: Rename an existing entry in a KeyMap
+f     - AST_MAPSIZE: Get the number of entries in a KeyMap
+f     - AST_MAPTYPE: Return the data type of a named entry in a map
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2008-2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: B.S. Berry (Starlink)
+
+*  History:
+*     12-NOV-2004 (DSB):
+*        Original version.
+*     5-JAN-2005 (DSB):
+*        Added astMapLenC method.
+*     17-JAN-2005 (DSB):
+*        Remove "void *" arithmetic.
+*     25-JAN-2005 (DSB):
+*        Added more DEBUG blocks
+*     30-SEP-2005 (DSB):
+*        Allow an integer to be read from a formatted floating point value.
+*     6-DEC-2005 (DSB):
+*        Remove astMapGet0C stuff from description of astMapGet1C.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     1-MAR-2006 (DSB):
+*        Replace astSetPermMap within DEBUG blocks by astBeginPM/astEndPM.
+*     5-JUN-2006 (DSB):
+*        Added support for single precision entries.
+*     30-NOV-2007 (DSB):
+*        Added SizeGuess attribute.
+*     4-DEC-2007 (DSB):
+*        Allow size of hash table to grow dynamically as more entries are
+*        added to the KeyMap.
+*     5-DEC-2007 (DSB):
+*        Ensure mapsize is always a power of 2.
+*     6-DEC-2007 (DSB):
+*        - Define the minium table size rather than the default SizeGuess
+*        value, and derive the default SizeGuess value from the minimum
+*        table size.
+*        - Use "&" rather than "%" to get the hash table index from the
+*        full width hash value (& may be faster than %).
+*     7-MAR-2008 (DSB):
+*         Added support for pointer ("P") entries.
+*     31-MAR-2009 (DSB):
+*         Remove rounding errors from formatted double values.
+*     27-APR-2009 (DSB):
+*         Added astMapGetElem<X>.
+*     1-SEP-2009 (DSB):
+*         Added KeyError attribute.
+*     12-FEB-2010 (DSB):
+*         When converting an entry value between double and string, treat
+*         "<bad>" as the formatted version of AST__BAD.
+*     3-MAR-2010 (DSB):
+*         Added astMapPutElem<X>.
+*     27-APR-2010 (DSB):
+*         Added MapLocked attribute.
+*     4-MAY-2010 (DSB):
+*         - Propagate MapLocked and KeyError attributes to any encapsulated
+*         KeyMaps.
+*         - Added astMapCopy method.
+*         - Added astMapPutU method and AST__UNDEFTYPE data type.
+*     11-AUG-2010 (DSB):
+*        Added SortBy attribute.
+*     12-AUG-2010 (DSB):
+*        Speed up access to large KeyMaps.
+*     13-AUG-2010 (DSB):
+*        - No need to sort all entries when doubling the table size since
+*        changing the table size does not change the linked list of sorted
+*        entries.
+*        - Initialise the sortby attribute to the cleared value, rather
+*        than the default value.
+*     2-OCT-2010 (DSB):
+*        Added support for short int valued entries.
+*     24-NOV-2010 (DSB):
+*        Fix memory leak in astMapPutElemC and astMapPutElemA.
+*     26-NOV-2010 (DSB):
+*        Added support for unsigned byte valued entries.
+*     3-DEC-2010 (DSB):
+*         Added KeyCase attribute.
+*     14-JAN-2011 (DSB):
+*         Fix bug that prevented zero length strings being stored in a
+*         keymap.
+*     17-SEP-2012 (DSB):
+*         Fix bug that prevented UNDEF entries from being read back in
+*         from a dump of a KeyMap.
+*     18-MAR-2013 (DSB):
+*         Added astMapDefined.
+*     18-JUL-2013 (DSB):
+*         Added SortBy options "KeyAgeUp" and "KeyAgeDown".
+*     9-SEP-2016 (DSB):
+*         Guard against memory corruption that could occur after making 
+*         50 (AST__KEYMAP_CONVERTVALUE_MAX_STRINGS) calls to put a string 
+*         into a KeyMap using astMapPutElemC.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS KeyMap
+
+/* Minimum size for the hash table. */
+#define MIN_TABLE_SIZE 16
+
+/* The maximum number of entries per element of the hash table. If this
+   value is exceeded the hash table will be doubled in size. */
+#define MAX_ENTRIES_PER_TABLE_ENTRY 10
+
+/* String used to represent the formatetd version of AST__BAD. */
+#define BAD_STRING "<bad>"
+
+/* Integer values to represent the different values of the SortBy attribute. */
+#define SORTBY_NONE 0
+#define SORTBY_AGEUP 1
+#define SORTBY_AGEDOWN 2
+#define SORTBY_KEYUP 3
+#define SORTBY_KEYDOWN 4
+#define SORTBY_KEYAGEUP 5
+#define SORTBY_KEYAGEDOWN 6
+
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory management facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* For AST__BAD */
+#include "channel.h"             /* I/O channels */
+#include "keymap.h"              /* Interface definition for this class */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Type Definitions */
+/* ================ */
+
+/* This structure is a AstMapEntry holding a scalar int */
+typedef struct Entry0I {
+   struct AstMapEntry entry; /* The parent Entry information */
+   int value;                /* The integer value */
+} Entry0I;
+
+/* This structure is a AstMapEntry holding a scalar double */
+typedef struct Entry0D {
+   struct AstMapEntry entry; /* The parent Entry information */
+   double value;             /* The floating point value */
+} Entry0D;
+
+/* This structure is a AstMapEntry holding a scalar short int */
+typedef struct Entry0S {
+   struct AstMapEntry entry; /* The parent Entry information */
+   short int value;          /* The short int value */
+} Entry0S;
+
+/* This structure is a AstMapEntry holding a scalar unsigned byte
+   (unsigned char) */
+typedef struct Entry0B {
+   struct AstMapEntry entry; /* The parent Entry information */
+   unsigned char value;      /* The byte value */
+} Entry0B;
+
+/* This structure is a AstMapEntry holding a scalar float */
+typedef struct Entry0F {
+   struct AstMapEntry entry; /* The parent Entry information */
+   float value;              /* The floating point value */
+} Entry0F;
+
+/* This structure is a AstMapEntry holding a scalar string */
+typedef struct Entry0C {
+   struct AstMapEntry entry; /* The parent Entry information */
+   const char *value;        /* The string pointer */
+} Entry0C;
+
+/* This structure is a AstMapEntry holding a scalar AST Object */
+typedef struct Entry0A {
+   struct AstMapEntry entry; /* The parent Entry information */
+   AstObject *value;         /* The Object pointer */
+   struct AstMapEntry *next; /* Pointer to next AST Object entry */
+   struct AstMapEntry *prev; /* Pointer to previous AST Object entry */
+} Entry0A;
+
+/* This structure is a AstMapEntry holding a scalar void pointer */
+typedef struct Entry0P {
+   struct AstMapEntry entry; /* The parent Entry information */
+   void *value;              /* The pointer */
+} Entry0P;
+
+/* This structure is a AstMapEntry holding a 1D array of ints */
+typedef struct Entry1I {
+   struct AstMapEntry entry; /* The parent Entry information */
+   int *value;               /* The integer values */
+} Entry1I;
+
+/* This structure is a AstMapEntry holding a 1D array of doubles */
+typedef struct Entry1D {
+   struct AstMapEntry entry; /* The parent Entry information */
+   double *value;            /* The floating point values */
+} Entry1D;
+
+/* This structure is a AstMapEntry holding a 1D array of short ints */
+typedef struct Entry1S {
+   struct AstMapEntry entry; /* The parent Entry information */
+   short int *value;         /* The short int values */
+} Entry1S;
+
+/* This structure is a AstMapEntry holding a 1D array of unsigned bytes */
+typedef struct Entry1B {
+   struct AstMapEntry entry; /* The parent Entry information */
+   unsigned char *value;     /* The byte values */
+} Entry1B;
+
+/* This structure is a AstMapEntry holding a 1D array of floats */
+typedef struct Entry1F {
+   struct AstMapEntry entry; /* The parent Entry information */
+   float *value;             /* The floating point values */
+} Entry1F;
+
+/* This structure is a AstMapEntry holding a 1D array of strings */
+typedef struct Entry1C {
+   struct AstMapEntry entry; /* The parent Entry information */
+   const char **value;       /* The string pointers */
+} Entry1C;
+
+/* This structure is a AstMapEntry holding a 1D array of AST Objects */
+typedef struct Entry1A {
+   struct AstMapEntry entry; /* The parent Entry information */
+   AstObject **value;        /* The Object pointers */
+   struct AstMapEntry *next; /* Pointer to next AST Object entry */
+   struct AstMapEntry *prev; /* Pointer to previous AST Object entry */
+} Entry1A;
+
+/* This structure is a AstMapEntry holding a 1D array of void pointers. */
+typedef struct Entry1P {
+   struct AstMapEntry entry; /* The parent Entry information */
+   void **value;             /* The pointers */
+} Entry1P;
+
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->ConvertValue_Init = 0; \
+   globals->ConvertValue_Istr = 0; \
+   globals->ConvertValue_Buff[ 0 ] = 0; \
+   globals->MapKey_Init = 0; \
+   globals->MapKey_Istr = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(KeyMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(KeyMap,Class_Init)
+#define class_vtab astGLOBAL(KeyMap,Class_Vtab)
+#define getattrib_buff astGLOBAL(KeyMap,GetAttrib_Buff)
+#define convertvalue_strings astGLOBAL(KeyMap,ConvertValue_Strings)
+#define convertvalue_istr astGLOBAL(KeyMap,ConvertValue_Istr)
+#define convertvalue_init astGLOBAL(KeyMap,ConvertValue_Init)
+#define convertvalue_buff astGLOBAL(KeyMap,ConvertValue_Buff)
+#define mapkey_strings astGLOBAL(KeyMap,MapKey_Strings)
+#define mapkey_istr astGLOBAL(KeyMap,MapKey_Istr)
+#define mapkey_init astGLOBAL(KeyMap,MapKey_Init)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Buffer returned by GetAttrib. */ \
+static char getattrib_buff[ AST__KEYMAP_GETATTRIB_BUFF_LEN + 1 ];
+
+/* Strings returned by ConvertValue */ \
+static char *convertvalue_strings[ AST__KEYMAP_CONVERTVALUE_MAX_STRINGS ];
+
+/* Offset of next string in "ConvertValue_Strings" */ \
+static int convertvalue_istr;
+
+/* "ConvertValue_Strings" array initialised? */ \
+static int convertvalue_init;
+
+/* ConvertValue string buffer */ \
+static char convertvalue_buff[ AST__KEYMAP_CONVERTVALUE_BUFF_LEN + 1 ];
+
+/* Strings returned by MapKey */ \
+static char *mapkey_strings[ AST__KEYMAP_MAPKEY_MAX_STRINGS ];
+
+/* Offset of next string in "MapKey_Strings" */ \
+static int mapkey_istr;
+
+/* "MapKey_Strings" array initialised? */ \
+static int mapkey_init;
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstKeyMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;         /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstKeyMap *astKeyMapId_( const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapEntry *AddTableEntry( AstKeyMap *, int, AstMapEntry *, int, int * );
+static AstMapEntry *CopyMapEntry( AstMapEntry *, int * );
+static AstMapEntry *FreeMapEntry( AstMapEntry *, int * );
+static AstMapEntry *RemoveTableEntry( AstKeyMap *, int, const char *, int * );
+static AstMapEntry *SearchTableEntry( AstKeyMap *, int, const char *, int * );
+static const char *ConvertKey( AstKeyMap *, const char *, char *, int, const char *, int * );
+static const char *GetKey( AstKeyMap *, int index, int * );
+static const char *MapIterate( AstKeyMap *, int, int * );
+static const char *MapKey( AstKeyMap *, int index, int * );
+static const char *SortByString( int, const char *, int * );
+static int CompareEntries( const void *, const void * );
+static int ConvertValue( void *, int, void *, int, int * );
+static int GetObjSize( AstObject *, int * );
+static int HashFun( const char *, int, unsigned long *, int * );
+static int KeyCmp( const char *, const char * );
+static int MapDefined( AstKeyMap *, const char *, int * );
+static int MapGet0A( AstKeyMap *, const char *, AstObject **, int * );
+static int MapGet0C( AstKeyMap *, const char *, const char **, int * );
+static int MapGet0D( AstKeyMap *, const char *, double *, int * );
+static int MapGet0S( AstKeyMap *, const char *, short int *, int * );
+static int MapGet0B( AstKeyMap *, const char *, unsigned char *, int * );
+static int MapGet0F( AstKeyMap *, const char *, float *, int * );
+static int MapGet0I( AstKeyMap *, const char *, int *, int * );
+static int MapGet0P( AstKeyMap *, const char *, void **, int * );
+static int MapGet1A( AstKeyMap *, const char *, int, int *, AstObject **, int * );
+static int MapGet1C( AstKeyMap *, const char *, int, int, int *, char *, int * );
+static int MapGet1D( AstKeyMap *, const char *, int, int *, double *, int * );
+static int MapGet1B( AstKeyMap *, const char *, int, int *, unsigned char *, int * );
+static int MapGet1S( AstKeyMap *, const char *, int, int *, short int *, int * );
+static int MapGet1F( AstKeyMap *, const char *, int, int *, float *, int * );
+static int MapGet1I( AstKeyMap *, const char *, int, int *, int *, int * );
+static int MapGet1P( AstKeyMap *, const char *, int, int *, void **, int * );
+static int MapGetElemA( AstKeyMap *, const char *, int, AstObject **, int * );
+static int MapGetElemC( AstKeyMap *, const char *, int, int, char *, int * );
+static int MapGetElemD( AstKeyMap *, const char *, int, double *, int * );
+static int MapGetElemB( AstKeyMap *, const char *, int, unsigned char *, int * );
+static int MapGetElemS( AstKeyMap *, const char *, int, short int *, int * );
+static int MapGetElemF( AstKeyMap *, const char *, int, float *, int * );
+static int MapGetElemI( AstKeyMap *, const char *, int, int *, int * );
+static int MapGetElemP( AstKeyMap *, const char *, int, void **, int * );
+static int MapHasKey( AstKeyMap *, const char *, int * );
+static int MapLenC( AstKeyMap *, const char *, int * );
+static int MapLength( AstKeyMap *, const char *, int * );
+static int MapSize( AstKeyMap *, int * );
+static int MapType( AstKeyMap *, const char *, int * );
+static int SortByInt( const char *, const char *, int * );
+static size_t SizeOfEntry( AstMapEntry *, int * );
+static void AddToObjectList( AstKeyMap *, AstMapEntry *, int * );
+static void AddToSortedList( AstKeyMap *, AstMapEntry *, int * );
+static void CheckCircle( AstKeyMap *, AstObject *, const char *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void CopyTableEntry( AstKeyMap *, AstKeyMap *, int, int * );
+static void Delete( AstObject *, int * );
+static void DoubleTableSize( AstKeyMap *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void DumpEntry( AstMapEntry *, AstChannel *, int, int * );
+static void FreeTableEntry( AstKeyMap *, int itab, int * );
+static void InitMapEntry( AstMapEntry *, int, int, int * );
+static void MapCopy( AstKeyMap *, AstKeyMap *, int * );
+static void MapPut0A( AstKeyMap *, const char *, AstObject *, const char *, int * );
+static void MapPut0C( AstKeyMap *, const char *, const char *, const char *, int * );
+static void MapPut0D( AstKeyMap *, const char *, double, const char *, int * );
+static void MapPut0B( AstKeyMap *, const char *, unsigned char, const char *, int * );
+static void MapPut0S( AstKeyMap *, const char *, short int, const char *, int * );
+static void MapPut0F( AstKeyMap *, const char *, float, const char *, int * );
+static void MapPut0I( AstKeyMap *, const char *, int, const char *, int * );
+static void MapPut0P( AstKeyMap *, const char *, void *, const char *, int * );
+static void MapPut1A( AstKeyMap *, const char *, int, AstObject *const [], const char *, int * );
+static void MapPut1C( AstKeyMap *, const char *, int, const char *const [], const char *, int * );
+static void MapPut1D( AstKeyMap *, const char *, int, const double *, const char *, int * );
+static void MapPut1B( AstKeyMap *, const char *, int, const unsigned char *, const char *, int * );
+static void MapPut1S( AstKeyMap *, const char *, int, const short int *, const char *, int * );
+static void MapPut1F( AstKeyMap *, const char *, int, const float *, const char *, int * );
+static void MapPut1I( AstKeyMap *, const char *, int, const int *, const char *, int * );
+static void MapPut1P( AstKeyMap *, const char *, int, void *const [], const char *, int * );
+static void MapPutElemA( AstKeyMap *, const char *, int, AstObject *, int * );
+static void MapPutElemC( AstKeyMap *, const char *, int, const char *, int * );
+static void MapPutElemD( AstKeyMap *, const char *, int, double, int * );
+static void MapPutElemB( AstKeyMap *, const char *, int, unsigned char, int * );
+static void MapPutElemS( AstKeyMap *, const char *, int, short int, int * );
+static void MapPutElemF( AstKeyMap *, const char *, int, float, int * );
+static void MapPutElemI( AstKeyMap *, const char *, int, int, int * );
+static void MapPutElemP( AstKeyMap *, const char *, int, void *, int * );
+static void MapPutU( AstKeyMap *, const char *, const char *, int * );
+static void MapRemove( AstKeyMap *, const char *, int * );
+static void MapRename( AstKeyMap *, const char *, const char *, int * );
+static void NewTable( AstKeyMap *, int, int * );
+static void RemoveFromSortedList( AstKeyMap *, AstMapEntry *, int * );
+static void RemoveFromObjectList( AstKeyMap *, AstMapEntry *, int * );
+static void SortEntries( AstKeyMap *, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+static int GetSizeGuess( AstKeyMap *, int * );
+static int TestSizeGuess( AstKeyMap *, int * );
+static void ClearSizeGuess( AstKeyMap *, int * );
+static void SetSizeGuess( AstKeyMap *, int, int * );
+
+static int GetSortBy( AstKeyMap *, int * );
+static int TestSortBy( AstKeyMap *, int * );
+static void ClearSortBy( AstKeyMap *, int * );
+static void SetSortBy( AstKeyMap *, int, int * );
+
+static int GetKeyError( AstKeyMap *, int * );
+static int TestKeyError( AstKeyMap *, int * );
+static void ClearKeyError( AstKeyMap *, int * );
+static void SetKeyError( AstKeyMap *, int, int * );
+
+static int GetKeyCase( AstKeyMap *, int * );
+static int TestKeyCase( AstKeyMap *, int * );
+static void ClearKeyCase( AstKeyMap *, int * );
+static void SetKeyCase( AstKeyMap *, int, int * );
+
+static int GetMapLocked( AstKeyMap *, int * );
+static int TestMapLocked( AstKeyMap *, int * );
+static void ClearMapLocked( AstKeyMap *, int * );
+static void SetMapLocked( AstKeyMap *, int, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+static AstMapEntry *AddTableEntry( AstKeyMap *this, int itab,
+                                   AstMapEntry *entry, int keymember,
+                                   int *status ){
+/*
+*  Name:
+*     AddTableEntry
+
+*  Purpose:
+*     Add an new entry to a linked-list of KeyMap entries.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     AstMapEntry *AddTableEntry( AstKeyMap *this, int itab,
+*                                 AstMapEntry *entry, int keymember,
+*                                 int *status ){
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function adds the supplied MapEntry to the head of the linked
+*     list of MapEntries stored at the specified entry of the hash table.
+*     If this results in the linked list having too many entries, then a
+*     new larger hash table is allocated and the entries in the existing
+*     table are moved into the new table.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     itab
+*        Index of the hash table element to be searched.
+*     entry
+*        Pointer to the MapEntry to be added.
+*     keymember
+*        A unique integer identifier for the key that increases
+*        monotonically with age of the key. If this is negative,
+*        the next available identifier will be used automatically.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A NULL pointer.
+
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Put a pointer to the MapEntry which is currently at the head of the
+   linked list in the "next" component of the supplied MapEntry. */
+   entry->next = this->table[ itab ];
+
+/* Store the supplied MapEntry pointer in the requested element of the
+   hash table. */
+   this->table[ itab ] = entry;
+
+/* Increment the length of linked list. */
+   this->nentry[ itab ]++;
+
+/* Each new entry added to the KeyMap has a unique member index that is
+   never re-used. */
+   entry->member = (this->member_count)++;
+
+/* Each key added to the  KeyMap also has a separate unique member index,
+   but this index is re-used each time the same key is added into the
+   KeyMap. So changing the value associated with a key does not cause the
+   keymember value to change. */
+   if( keymember >= 0 ) {
+      entry->keymember = keymember;
+   } else {
+      entry->keymember = (this->member_count)++;
+   }
+
+/* Insert the supplied MapEntry into a list sorted by key. */
+   AddToSortedList( this, entry, status );
+
+/* If the entry is of type AST__OBJECTTYPE, add it to the head of the
+   list of AST__OBJECTTYPE entries in the KeyMap. */
+   AddToObjectList( this, entry, status );
+
+/* If the population of this table entry is now too large, double the size
+   of the table, moving the table entries to appropriate places in the
+   new larger table. */
+   if( this->nentry[ itab ] > MAX_ENTRIES_PER_TABLE_ENTRY ) {
+      DoubleTableSize( this, status );
+   }
+
+/* Return a NULL pointer. */
+   return NULL;
+}
+
+static void AddToObjectList( AstKeyMap *this, AstMapEntry *entry, int *status ){
+/*
+*  Name:
+*     AddToObjectList
+
+*  Purpose:
+*     Add AST__OBJECTTYPE entries into a linked-list of such entries.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void AddToObjectList( AstKeyMap *this, AstMapEntry *entry, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     If the supplied MapEntry holds one or more pointers to AST Objects,
+*     then the entry is added to a linked list of such entries.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     entry
+*        Pointer to the MapEntry to be added.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   Entry0A *scalar;         /* Pointer to a scalar AST__OBJECTTYPE entry */
+   Entry1A *vector;         /* Pointer to a vector AST__OBJECTTYPE entry */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Do nothing if the entry does not hold AST Object pointers. */
+   if( entry->type == AST__OBJECTTYPE ) {
+
+/* If the list is not currently empty, add the new entry into the list. */
+      if( this->firstA ) {
+
+/* Store a pointer to the new entry as the previous link in the current
+   first entry in the list. */
+         if( this->firstA->nel == 0 ) {
+            scalar = (Entry0A *) this->firstA;
+            scalar->prev = entry;
+         } else {
+            vector = (Entry1A *) this->firstA;
+            vector->prev = entry;
+         }
+
+/* Store a pointer to the current first entry as the next link in the new
+   entry, and nullify the previus link. */
+         if( entry->nel == 0 ) {
+            scalar = (Entry0A *) entry;
+            scalar->next = this->firstA;
+            scalar->prev = NULL;
+         } else {
+            vector = (Entry1A *) entry;
+            vector->next = this->firstA;
+            vector->prev = NULL;
+         }
+
+/* If the list is currently empty, nullify both links in the entry. */
+      } else {
+         if( entry->nel == 0 ) {
+            scalar = (Entry0A *) entry;
+            scalar->next = NULL;
+            scalar->prev = NULL;
+         } else {
+            vector = (Entry1A *) entry;
+            vector->next = NULL;
+            vector->prev = NULL;
+         }
+
+      }
+
+/* Store the new entry as the first entry. */
+      this->firstA = entry;
+   }
+}
+
+static void AddToSortedList( AstKeyMap *this, AstMapEntry *entry, int *status ){
+/*
+*  Name:
+*     AddToSortedList
+
+*  Purpose:
+*     Add an entry into the linked-list of sorted KeyMap entries.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void AddToSortedList( AstKeyMap *this, AstMapEntry *entry, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function adds the supplied MapEntry into the linked list of
+*     sorted MapEntries at a position that maintains the sorted order.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     entry
+*        Pointer to the MapEntry to be added.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMapEntry *hi;         /* MapEntry at high end of current range */
+   AstMapEntry *lo;         /* MapEntry at low end of current range */
+   AstMapEntry *mid;        /* MapEntry at middle of current range */
+   int cmp;                 /* Result of comparing two entries */
+   int istep;               /* Step counter */
+   int nstep;               /* Number of entries in current range */
+   int sortby;              /* How to sort the keys */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the SortBy value. */
+   sortby = astGetSortBy( this );
+
+/* Do nothing if no sorting is required. */
+   if( sortby != SORTBY_NONE ) {
+
+/* Get pointers to the entries at the start and end of the sorted list. */
+      lo = this->first;
+      hi = lo ? lo->sprev : NULL;
+
+/* Store sortby value in the mapentry structures. */
+      if( lo ) lo->sortby = sortby;
+      if( hi ) hi->sortby = sortby;
+      entry->sortby = sortby;
+
+/* If the sorted list is empty, just store the supplied entry at the
+   head, and set the links to point back to itself. */
+      if( !lo ) {
+         this->first = entry;
+         entry->sprev = entry;
+         entry->snext = entry;
+
+/* If the new entry comes before the first entry or is equal to the first
+   entry, record it as the new first entry, and insert it into the linked
+   list before the original first entry. */
+      } else if( CompareEntries( &entry, &lo ) <= 0 ) {
+         this->first = entry;
+         entry->snext = lo;
+         entry->sprev = hi;
+         lo->sprev = entry;
+         hi->snext = entry;
+
+/* If the new entry comes after the last entry or is equal to the last
+   entry, insert it into the linked list after the last entry. */
+      } else if( CompareEntries( &entry, &hi ) >= 0 ) {
+         entry->snext = lo;
+         entry->sprev = hi;
+         lo->sprev = entry;
+         hi->snext = entry;
+
+/* If the list only contains two values, insert the new entry into the linked
+   list between the existing two entries. */
+      } else if( lo->snext == hi ) {
+         entry->snext = hi;
+         entry->sprev = lo;
+         lo->snext = entry;
+         hi->sprev = entry;
+
+/* Otherwise we do a binary chop within the existing sorted list to find the
+   correct position for the new entry. */
+      } else {
+
+/* Get a pointer to the entry mid way between the hi and lo entries. The
+   mid entry will be on the upper side of half way if there are an even
+   number of entries. */
+         nstep = this->nsorted/2;
+         mid = lo;
+         for( istep = 0; istep < nstep; istep++ ) mid = mid->snext;
+
+/* Loop until we have a pointer to the first entry which is equal to or
+   higher than the new entry. */
+         while( lo->snext != hi ) {
+
+/* The next step will be half the length of the previous step. Do not
+   allow the step size to fall to zero. */
+            nstep = ( nstep > 1 ) ? nstep/2 : 1;
+
+/* Compare the new entry with the current mid-way entry. */
+            mid->sortby = sortby;
+            cmp = CompareEntries( &entry, &mid );
+
+/* If the new entry comes before the mid entry, use the mid entry as the
+   next hi entry, and go down the list by the new step size to find the
+   new mid-way entry. */
+            if( cmp < 0 ) {
+               hi = mid;
+               for( istep = 0; istep < nstep; istep++ ) mid = mid->sprev;
+
+/* If the new entry comes after the mid entry, use the mid entry as the
+   next lo entry, and go up the list by the new step size to find the
+   new mid-way entry. */
+            } else if( cmp > 0 ) {
+               lo = mid;
+               for( istep = 0; istep < nstep; istep++ ) mid = mid->snext;
+
+/* If the new entry is equal to the mid entry, use the mid entry as hi
+   and set lo to the previous entry. This causes the loop to quit. */
+            } else {
+               hi = mid;
+               lo = mid->sprev;
+            }
+         }
+
+/* Insert the new entry into the list between lo and hi. */
+         entry->sprev = lo;
+         entry->snext = hi;
+         lo->snext = entry;
+         hi->sprev = entry;
+      }
+
+/* Increment the number of entries in the sorted list. */
+      (this->nsorted)++;
+   }
+}
+
+static void CheckCircle( AstKeyMap *this, AstObject *obj, const char *method, int *status ) {
+/*
+*  Name:
+*     CheckCircle
+
+*  Purpose:
+*     Check for circular dependencies between KeyMaps.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void CheckCircle( AstKeyMap *this, AstObject *obj, const char *method, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function checks that the given AstObject is not a KeyMap which
+*     contains "this". If it is, an error is reported.
+
+*  Parameters:
+*     this
+*        The KeyMap pointer.
+*     obj
+*        Pointer to the AstObject to be inserted into the KeyMap, or NULL.
+*     method
+*        Name of method to include in error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *keymap;       /* The KeyMap being added to "this" */
+   AstObject **vec;         /* Pointer to list of AstObject pointers */
+   const char *key;         /* The i'th key within second KeyMap */
+   int i;                   /* Index of entry within second KeyMap */
+   int j;                   /* Index within the vector of values */
+   int len;                 /* No. of AST pointers stored in the entry */
+   int nkey;                /* No. of entries in the second KeyMap */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Return if the AstObject is not a KeyMap. */
+   if( obj && astIsAKeyMap( obj ) ) {
+      keymap = (AstKeyMap *) obj;
+
+/* First check if the supplied Objects are the same. You cannot store a
+   KeyMap as an entry within itself. */
+      if( keymap == this ) {
+         astError( AST__KYCIR, "%s(%s): Cannot add a %s into another "
+                   "%s because they are same %s.", status, method,
+                   astGetClass( this ), astGetClass( this ),
+                   astGetClass( this ), astGetClass( this ) );
+
+/* Otherwise, loop through all the entries in the KeyMap looking for AstObject
+   entries. */
+      } else {
+         nkey = astMapSize( keymap );
+         for( i = 0; i < nkey && astOK; i++ ) {
+            key = astMapKey( keymap, i );
+            if( astMapType( keymap, key ) == AST__OBJECTTYPE ) {
+
+/* Find the number of AstObject pointers stored in this entry, and
+   allocate memory to store a copy of the every pointer. */
+               len = astMapLength( keymap, key );
+               vec = astMalloc( sizeof( AstObject *) * len );
+               if( vec ) {
+
+/* Extract pointers to the AstObjects at this entry, and loop round them. */
+                  astMapGet1A( keymap, key, len, &len, vec );
+                  for( j = 0; j < len; j++ ) {
+
+/* If this entry is a KeyMap, we need to check if is the same as "this"
+   or contains "this". */
+                     if( astIsAKeyMap( vec[ j ] ) ) {
+
+/* If it is the same as "this", report an error. */
+                        if( vec[ j ] == (AstObject *) this ) {
+                           astError( AST__KYCIR, "%s(%s): Cannot add a KeyMap "
+                                     "into another KeyMap because the first "
+                                     "KeyMap contains the second KeyMap.", status,
+                                     method, astGetClass( this ) );
+                           break;
+
+/* Otherwise, see if it contains "this". */
+                        } else {
+                           CheckCircle( this, vec[ j ], method, status );
+                        }
+                     }
+
+/* Free resources. */
+                     vec[ j ] = astAnnul( vec[ j ] );
+                  }
+                  vec = astFree( vec );
+               }
+            }
+         }
+      }
+   }
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     KeyMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     KeyMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstKeyMap *this;             /* Pointer to the KeyMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the KeyMap structure. */
+   this = (AstKeyMap *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* SizeGuess. */
+/* ---------- */
+   if ( !strcmp( attrib, "sizeguess" ) ) {
+      astClearSizeGuess( this );
+
+/* KeyError. */
+/* --------- */
+   } else if ( !strcmp( attrib, "keyerror" ) ) {
+      astClearKeyError( this );
+
+/* KeyCase. */
+/* --------- */
+   } else if ( !strcmp( attrib, "keycase" ) ) {
+      astClearKeyCase( this );
+
+/* MapLocked. */
+/* --------- */
+   } else if ( !strcmp( attrib, "maplocked" ) ) {
+      astClearMapLocked( this );
+
+/* SortBy. */
+/* ------- */
+   } else if ( !strcmp( attrib, "sortby" ) ) {
+      astClearSortBy( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearKeyCase( AstKeyMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearKeyCase
+
+*  Purpose:
+*     Clear the value of the KeyCase attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astSetKeyCase( AstKeyMap *this )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function Clears the KeyCase attribute of a KeyMap. It reports
+*     an error if the KeyMap contains any entries.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+
+*-
+*/
+
+/* Local Variables: */
+   int defval;                /* Default KeyCase value */
+   int itab;                  /* Index into hash table */
+   int oldval;                /* Old KeyCase value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Save the old value. */
+   oldval = astGetKeyCase( this );
+
+/* Clear it. */
+   this->keycase = -1;
+
+/* Get the default value. */
+   defval = astGetKeyCase( this );
+
+/* If the old value and the default value are not the same, we must check
+   that the KeyMap is empty. If not, restore the old value and report an
+   error. */
+   if( defval != oldval ) {
+      for( itab = 0; itab < this->mapsize; itab++ ) {
+         if( this->nentry[ itab ] > 0 ) {
+            this->keycase = oldval;
+            astError( AST__NOWRT, "astClearAttrib(KeyMap): Illegal attempt to "
+                      "clear the KeyCase attribute of a non-empty KeyMap.",
+                      status);
+            break;
+         }
+      }
+   }
+}
+
+static void ClearKeyError( AstKeyMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearKeyError
+
+*  Purpose:
+*     Clear the value of the KeyError attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astClearKeyError( AstKeyMap *this )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function clears the value of the KeyError attribute for a
+*     KeyMap. It clears the attribute recursively in any KeyMaps
+*     contained within the supplied KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+
+*-
+*/
+
+/* Local Variables: */
+   AstMapEntry *next;     /* Pointer to next Entry to copy */
+   AstObject **obj_list;  /* List of pointers to AST Object entries */
+   int i;                 /* Index into hash table */
+   int iel;               /* Index of current vector element */
+   int nel;               /* Number of elements in vector */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Clear the KeyError value in the supplied KeyMap. */
+   this->keyerror = -INT_MAX;
+
+/* Loop round each entry in the hash table. */
+   for( i = 0; i < this->mapsize; i++ ) {
+
+/* Get a pointer to the next KeyMap entry. */
+      next = this->table[ i ];
+
+/* Loop round all entries in this element of the hash table. */
+      while( next && astOK ) {
+
+/* If this entry has an Object data type, see if holds any KeyMaps. */
+         if( next->type == AST__OBJECTTYPE ) {
+
+/* Get the number of objects to check, and a pointer to the first. */
+            nel = next->nel;
+            if( nel == 0 ) {
+               obj_list = &( ((Entry0A *)next)->value );
+               nel = 1;
+            } else {
+               obj_list = ((Entry1A *)next)->value;
+            }
+
+/* Loop round checking all Objects. */
+            for( iel = 0; iel < nel; iel++ ) {
+
+/* If this Object is a KeyMap, clear its KeyError attribute. */
+               if( astIsAKeyMap( obj_list[ iel ] ) ) {
+                  astClearKeyError( (AstKeyMap *) obj_list[ iel ] );
+               }
+            }
+         }
+
+/* Get a pointer to the next entry. */
+         next = next->next;
+      }
+   }
+}
+
+static void ClearMapLocked( AstKeyMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearMapLocked
+
+*  Purpose:
+*     Clear the value of the MapLocked attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astClearMapLocked( AstKeyMap *this )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function clears the value of the MapLocked attribute for a
+*     KeyMap. It clears the attribute recursively in any KeyMaps
+*     contained within the supplied KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+
+*-
+*/
+
+/* Local Variables: */
+   AstMapEntry *next;     /* Pointer to next Entry to copy */
+   AstObject **obj_list;  /* List of pointers to AST Object entries */
+   int i;                 /* Index into hash table */
+   int iel;               /* Index of current vector element */
+   int nel;               /* Number of elements in vector */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Clear the MapLocked value in the supplied KeyMap. */
+   this->maplocked = -INT_MAX;
+
+/* Loop round each entry in the hash table. */
+   for( i = 0; i < this->mapsize; i++ ) {
+
+/* Get a pointer to the next KeyMap entry. */
+      next = this->table[ i ];
+
+/* Loop round all entries in this element of the hash table. */
+      while( next && astOK ) {
+
+/* If this entry has an Object data type, see if holds any KeyMaps. */
+         if( next->type == AST__OBJECTTYPE ) {
+
+/* Get the number of objects to check, and a pointer to the first. */
+            nel = next->nel;
+            if( nel == 0 ) {
+               obj_list = &( ((Entry0A *)next)->value );
+               nel = 1;
+            } else {
+               obj_list = ((Entry1A *)next)->value;
+            }
+
+/* Loop round checking all Objects. */
+            for( iel = 0; iel < nel; iel++ ) {
+
+/* If this Object is a KeyMap, clear its MapLocked attribute. */
+               if( astIsAKeyMap( obj_list[ iel ] ) ) {
+                  astClearMapLocked( (AstKeyMap *) obj_list[ iel ] );
+               }
+            }
+         }
+
+/* Get a pointer to the next entry. */
+         next = next->next;
+      }
+   }
+}
+
+static void ClearSizeGuess( AstKeyMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearSizeGuess
+
+*  Purpose:
+*     Clear the value of the SizeGuess attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astClearSizeGuess( AstKeyMap *this )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function clears the value of the SizeGuess attribute for a
+*     KeyMap. It reports an error if the KeyMap contains any entries.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+
+*-
+*/
+
+/* Local Variables: */
+   int empty;                 /* Is the KeyMap empty? */
+   int itab;                  /* Index into hash table */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* See if the KeyMap is empty. */
+   empty = 1;
+   for( itab = 0; itab < this->mapsize; itab++ ) {
+      if( this->nentry[ itab ] > 0 ) {
+         empty = 0;
+         break;
+      }
+   }
+
+/* If not report an error. */
+   if( !empty ) {
+      astError( AST__NOWRT, "astClearAttrib(KeyMap): Illegal attempt to "
+                "clear the SizeGuess attribute of a non-empty KeyMap." , status);
+
+/* Otherwise, store the "cleared" value and change the size of the hash
+   table. */
+   } else {
+      this->sizeguess = INT_MAX;
+      NewTable( this, MIN_TABLE_SIZE, status );
+   }
+}
+
+static void ClearSortBy( AstKeyMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearSortBy
+
+*  Purpose:
+*     Clear the value of the SortBy attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astClearSortBy( AstKeyMap *this )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function clears the value of the SortBy attribute for a
+*     KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+
+*-
+*/
+
+/* Local Variables: */
+   int oldval;            /* The old sortby value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the original SortBy value. */
+   oldval = astGetSortBy( this );
+
+/* Clear the SortBy value in the supplied KeyMap. */
+   this->sortby = -INT_MAX;
+
+/* If the value has changed, re-sort the keys. */
+   if( oldval != astGetSortBy( this ) ) SortEntries( this, status );
+}
+
+static int CompareEntries( const void *first_void, const void *second_void ) {
+/*
+*  Name:
+*     CompareEntries
+
+*  Purpose:
+*     Determine the sorting order of two mapEntries.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     int CompareEntries( const void *first, const void *second )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function returns a value indicating if the first MapEntry
+*     is less than, equal to, or greater than the second MapEntry using
+*     the indicated sorting method. It is designed for use with the
+*     "qsort" function, and therefore must used "void *" pointers.
+
+*  Parameters:
+*     first
+*        Pointer to the address of the first MapEntry.
+*     second
+*        Pointer to the address of the second MapEntry.
+
+*  Returned Value:
+*     -1 if "first" is less than "second". This implies that "first"
+*     should come before "second" in the sorted list.
+*
+*     0 if "first" is equal to "second".
+*
+*     +1 if "first" is greater than "second". This implies that "first"
+*     should come after "second" in the sorted list.
+
+*/
+
+/* Local Variables: */
+   AstMapEntry *first;   /* Pointer to first MapEntry structure */
+   AstMapEntry *second;  /* Pointer to second MapEntry structure */
+   int result;           /* Returned value */
+   int sortby;           /* Sorting method */
+
+/* Initialise returned value */
+   result = 0;
+
+/* Get pointers to the MapEntry structures, and get the sorting method. */
+   first = *( (AstMapEntry **) first_void );
+   second = *( (AstMapEntry **) second_void );
+   sortby = first->sortby;
+
+/* First handle sorting by increasing age of the value */
+   if( sortby == SORTBY_AGEUP ) {
+      if( first->member < second->member ) {
+         result = 1;
+      } else if( first->member > second->member ) {
+         result = -1;
+      } else {
+         result = 0;
+      }
+
+/* Next handle sorting by decreasing age of the value */
+   } else if( sortby == SORTBY_AGEDOWN ) {
+      if( first->member < second->member ) {
+         result = -1;
+      } else if( first->member > second->member ) {
+         result = 1;
+      } else {
+         result = 0;
+      }
+
+/* Next handle sorting by increasing age of the key */
+   } else if( sortby == SORTBY_KEYAGEUP ) {
+      if( first->keymember < second->keymember ) {
+         result = 1;
+      } else if( first->keymember > second->keymember ) {
+         result = -1;
+      } else {
+         result = 0;
+      }
+
+/* Next handle sorting by decreasing age of the key */
+   } else if( sortby == SORTBY_KEYAGEDOWN ) {
+      if( first->keymember < second->keymember ) {
+         result = -1;
+      } else if( first->keymember > second->keymember ) {
+         result = 1;
+      } else {
+         result = 0;
+      }
+
+/* Next handle sorting by increasing alphabetical position. */
+   } else if( sortby == SORTBY_KEYUP ) {
+      result = KeyCmp( first->key, second->key );
+
+/* Next handle sorting by decreasing alphabetical position. */
+   } else if( sortby == SORTBY_KEYDOWN ) {
+      result = KeyCmp( second->key, first->key );
+
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static const char *ConvertKey( AstKeyMap *this, const char *skey, char *keybuf,
+                               int blen, const char *method, int *status ){
+/*
+*  Name:
+*     ConvertValue
+
+*  Purpose:
+*     Convert the supplied key to upper case if required.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     const char *ConvertKey( AstKeyMap *this, const char *skey, char *keybuf,
+*                             int blen, const char *method, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function converts the supplied key string to uppercase if the
+*     KeyCase attribute it currently set to zero in the supplied KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     skey
+*        Pointer to the supplied key string.
+*     keybuf
+*        Pointer to a buffer in which to place the converted string. This
+*        will only be used if the supplied key string needs to be
+*        converted.
+*     blen
+*        The length of the "keybuf" buffer. This should include room for
+*        a terminating null character.
+*     method
+*        Pointer to a string holding the name of the method to include in
+*        any error message.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If the KeyMap's KeyCase attribute is currently set to a non-zero
+*     value, the returned value will be a copy of "skey". Otherwise it
+*     will be copy of "keybuf" (the buffer holding the upper case version
+*     of the supplied string).
+
+*  Notes:
+*     - The valeu of "skey" will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;
+   int len;
+
+/* Initialise. */
+   result = skey;
+
+/* Check the global error status and the supplied pointers. */
+   if( !astOK ) return result;
+
+/* If the KeyCase attribute is non-zero, return "skey". Otherwise, convert
+   the "skey" string to upper case and return "keybuf". Report an error if
+   the key is too long. */
+   if( !astGetKeyCase( this ) && astOK ) {
+      len = astChrLen( skey );
+      if( len >= blen ) {
+         astError( AST__BIGKEY, "%s(%s): Supplied key '%s' is too long "
+                   "(keys must be no more than %d characters long).",
+                   status, method, astGetClass( this ), skey, blen - 1 );
+      } else {
+         astChrCase( skey, keybuf, 1, blen );
+         result = keybuf;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int ConvertValue( void *raw, int raw_type, void *out, int out_type, int *status ) {
+/*
+*  Name:
+*     ConvertValue
+
+*  Purpose:
+*     Convert a value from one KeyMap data type to another.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     int ConvertValue( void *raw, int raw_type, void *out, int out_type, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function converts a supplied value from one KeyMap data type to
+*     another, if possible.
+
+*  Parameters:
+*     raw
+*        Pointer to input value.
+*     raw_type
+*        The data type of the input value.
+*     out
+*        Pointer to the location at which to store the output value. This
+*        may be NULL, in which case the conversion is still performed if
+*        possible, but the result of the conversion is thrown away. If the
+*        output value is a pointer to a string, it should not be modified
+*        by the caller in any way. Neither should it be freed by the caller.
+*     out_type
+*        The data type of the output value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the conversion was performed succesfully, otherwise zero.
+*     In the case of the output type being AST__STRINGTYPE, the returned
+*     non-zero value will be the length of the formatted string (including
+*     the terminating null character). This value will be returned correctly
+*     even if "out" is NULL.
+
+*  Notes:
+*     - Zero will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstObject *aval;              /* AstObject pointer value */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   const char *cval;             /* Pointer to string value */
+   const char *cvalue;           /* Pointer to output string value */
+   double dval;                  /* Double precision value */
+   float fval;                   /* Single precision value */
+   int i;                        /* Loop count */
+   int ival;                     /* Integer value */
+   int n1;                       /* Number of characters at reduced precision */
+   int n2;                       /* Number of characters at full precision */
+   int nc;                       /* Number of characters read from string */
+   int nval;                     /* Number of values read from string */
+   int result;                   /* Returned flag */
+   short int sval;               /* Short int value */
+   unsigned char bval;           /* Byte value */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status and the supplied pointers. */
+   if( !astOK || !raw ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* If the "convertvalue_strings" array has not been initialised, fill it with
+   NULL pointers. */
+   if( !convertvalue_init ) {
+      convertvalue_init = 1;
+      for( i = 0; i < AST__KEYMAP_CONVERTVALUE_MAX_STRINGS; i++ ) convertvalue_strings[ i ] = NULL;
+   }
+
+/* Assume conversion is possible */
+   result = 1;
+   cvalue = NULL;
+
+/* Do nothing if both data types are AST__UNDEFTYPE. */
+    if( raw_type == AST__UNDEFTYPE && out_type == AST__UNDEFTYPE ) {
+
+/* Indicate failure if one of the two types is AST__UNDEFTYPE and the
+   other is not. */
+    } else if( raw_type == AST__UNDEFTYPE || out_type == AST__UNDEFTYPE ) {
+      result = 0;
+
+/* Otherwise, consider conversion from "int". */
+    } else if( raw_type == AST__INTTYPE ) {
+      ival = *( (int *) raw );
+
+/* Consider conversion to "int". */
+      if( out_type == AST__INTTYPE ) {
+         if( out ) *( (int *) out ) = ival;
+
+/* Consider conversion to "short int". */
+      } else if( out_type == AST__SINTTYPE ) {
+         if( out ) *( (short int *) out ) = (short int) ival;
+
+/* Consider conversion to "byte". */
+      } else if( out_type == AST__BYTETYPE ) {
+         if( out ) *( (unsigned char *) out ) = (unsigned char) ival;
+
+/* Consider conversion to "float". */
+      } else if( out_type == AST__FLOATTYPE ) {
+         if( out ) *( (float *) out ) = (float) ival;
+
+/* Consider conversion to "double". */
+      } else if( out_type == AST__DOUBLETYPE ) {
+         if( out ) *( (double *) out ) = (double) ival;
+
+/* Consider conversion to "const char *". */
+      } else if( out_type == AST__STRINGTYPE ) {
+         (void) sprintf( convertvalue_buff, "%d", ival );
+         cvalue = convertvalue_buff;
+
+/* Consider conversion to "AstObject *". */
+      } else if( out_type == AST__OBJECTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "void *". */
+      } else if( out_type == AST__POINTERTYPE ) {
+         result = 0;
+
+/* Report an error if the data type is unknown. */
+      } else {
+         result = 0;
+         astError( AST__INTER, "ConvertValue(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   out_type );
+      }
+
+/* Otherwise, consider conversion from "short int". */
+    } else if( raw_type == AST__SINTTYPE ) {
+      sval = *( (short int *) raw );
+
+/* Consider conversion to "int". */
+      if( out_type == AST__INTTYPE ) {
+         if( out ) *( (int *) out ) = sval;
+
+/* Consider conversion to "short int". */
+      } else if( out_type == AST__SINTTYPE ) {
+         if( out ) *( (short int *) out ) = sval;
+
+/* Consider conversion to "byte". */
+      } else if( out_type == AST__BYTETYPE ) {
+         if( out ) *( (unsigned char *) out ) = sval;
+
+/* Consider conversion to "float". */
+      } else if( out_type == AST__FLOATTYPE ) {
+         if( out ) *( (float *) out ) = (float) sval;
+
+/* Consider conversion to "double". */
+      } else if( out_type == AST__DOUBLETYPE ) {
+         if( out ) *( (double *) out ) = (double) sval;
+
+/* Consider conversion to "const char *". */
+      } else if( out_type == AST__STRINGTYPE ) {
+         (void) sprintf( convertvalue_buff, "%d", (int) sval );
+         cvalue = convertvalue_buff;
+
+/* Consider conversion to "AstObject *". */
+      } else if( out_type == AST__OBJECTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "void *". */
+      } else if( out_type == AST__POINTERTYPE ) {
+         result = 0;
+
+/* Report an error if the data type is unknown. */
+      } else {
+         result = 0;
+         astError( AST__INTER, "ConvertValue(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   out_type );
+      }
+
+/* Otherwise, consider conversion from "byte". */
+    } else if( raw_type == AST__BYTETYPE ) {
+      bval = *( (unsigned char *) raw );
+
+/* Consider conversion to "int". */
+      if( out_type == AST__INTTYPE ) {
+         if( out ) *( (int *) out ) = bval;
+
+/* Consider conversion to "short int". */
+      } else if( out_type == AST__SINTTYPE ) {
+         if( out ) *( (short int *) out ) = bval;
+
+/* Consider conversion to "byte". */
+      } else if( out_type == AST__BYTETYPE ) {
+         if( out ) *( (unsigned char *) out ) = bval;
+
+/* Consider conversion to "float". */
+      } else if( out_type == AST__FLOATTYPE ) {
+         if( out ) *( (float *) out ) = (float) bval;
+
+/* Consider conversion to "double". */
+      } else if( out_type == AST__DOUBLETYPE ) {
+         if( out ) *( (double *) out ) = (double) bval;
+
+/* Consider conversion to "const char *". */
+      } else if( out_type == AST__STRINGTYPE ) {
+         (void) sprintf( convertvalue_buff, "%d", (int) bval );
+         cvalue = convertvalue_buff;
+
+/* Consider conversion to "AstObject *". */
+      } else if( out_type == AST__OBJECTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "void *". */
+      } else if( out_type == AST__POINTERTYPE ) {
+         result = 0;
+
+/* Report an error if the data type is unknown. */
+      } else {
+         result = 0;
+         astError( AST__INTER, "ConvertValue(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   out_type );
+      }
+
+/* Consider conversion from "double". */
+   } else if( raw_type == AST__DOUBLETYPE ) {
+      dval = *( (double *) raw );
+
+/* Consider conversion to "int". */
+      if( out_type == AST__INTTYPE ) {
+         if( out ) *( (int *) out ) = (int)( dval + 0.5 );
+
+/* Consider conversion to "short int". */
+      } else if( out_type == AST__SINTTYPE ) {
+         if( out ) *( (short int *) out ) = (int)( dval + 0.5 );
+
+/* Consider conversion to "byte". */
+      } else if( out_type == AST__BYTETYPE ) {
+         if( out ) *( (unsigned char *) out ) = (int)( dval + 0.5 );
+
+/* Consider conversion to "double". */
+      } else if( out_type == AST__DOUBLETYPE ) {
+         if( out ) *( (double *) out ) = dval;
+
+/* Consider conversion to "float". */
+      } else if( out_type == AST__FLOATTYPE ) {
+         if( out ) *( (float *) out ) = (float) dval;
+
+/* Consider conversion to "const char *". If reducing the number of
+   decimal places by two produces a saving of 10 or more characters,
+   assume the least significant two characters are rounding error. */
+      } else if( out_type == AST__STRINGTYPE ) {
+         if( dval != AST__BAD ) {
+            n1 = sprintf( convertvalue_buff, "%.*g", DBL_DIG - 2, dval );
+            n2 = sprintf( convertvalue_buff, "%.*g", DBL_DIG, dval );
+            if( n2 - n1 > 9 ) {
+               (void) sprintf( convertvalue_buff, "%.*g", DBL_DIG - 2, dval );
+            }
+            cvalue = convertvalue_buff;
+         } else {
+            cvalue = BAD_STRING;
+         }
+
+/* Consider conversion to "AstObject *". */
+      } else if( out_type == AST__OBJECTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "void *". */
+      } else if( out_type == AST__POINTERTYPE ) {
+         result = 0;
+
+/* Report an error if the data type is unknown. */
+      } else {
+         result = 0;
+         astError( AST__INTER, "ConvertValue(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   out_type );
+      }
+
+/* Consider conversion from "float". */
+   } else if( raw_type == AST__FLOATTYPE ) {
+      fval = *( (float *) raw );
+
+/* Consider conversion to "int". */
+      if( out_type == AST__INTTYPE ) {
+         if( out ) *( (int *) out ) = (int)( fval + 0.5 );
+
+/* Consider conversion to "short int". */
+      } else if( out_type == AST__SINTTYPE ) {
+         if( out ) *( (short int *) out ) = (int)( fval + 0.5 );
+
+/* Consider conversion to "byte". */
+      } else if( out_type == AST__BYTETYPE ) {
+         if( out ) *( (unsigned char *) out ) = (int)( fval + 0.5 );
+
+/* Consider conversion to "double". */
+      } else if( out_type == AST__DOUBLETYPE ) {
+         if( out ) *( (double *) out ) = (double) fval;
+
+/* Consider conversion to "float". */
+      } else if( out_type == AST__FLOATTYPE ) {
+         if( out ) *( (float *) out ) = fval;
+
+/* Consider conversion to "const char *". */
+      } else if( out_type == AST__STRINGTYPE ) {
+         (void) sprintf( convertvalue_buff, "%.*g", FLT_DIG, fval );
+         cvalue = convertvalue_buff;
+
+/* Consider conversion to "AstObject *". */
+      } else if( out_type == AST__OBJECTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "void *". */
+      } else if( out_type == AST__POINTERTYPE ) {
+         result = 0;
+
+/* Report an error if the data type is unknown. */
+      } else {
+         result = 0;
+         astError( AST__INTER, "ConvertValue(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   out_type );
+      }
+
+/* Consider conversion from "const char *". */
+   } else if( raw_type == AST__STRINGTYPE ) {
+      cval = *( (const char **) raw );
+
+/* Consider conversion to "int". */
+      if( out_type == AST__INTTYPE ) {
+         nc = 0;
+         nval = astSscanf( cval, " %d %n", &ival, &nc );
+         if( ( nval == 1 ) && ( nc >= (int) strlen( cval ) ) ) {
+            if( out ) *( (int *) out ) = ival;
+         } else {
+            nc = 0;
+            nval = astSscanf( cval, " %lf %n", &dval, &nc );
+            if( ( nval == 1 ) && ( nc >= (int) strlen( cval ) ) ) {
+               if( out ) *( (int *) out ) = (int) ( dval + 0.5 );
+            } else {
+               result = 0;
+            }
+         }
+
+/* Consider conversion to "short int". */
+      } else if( out_type == AST__SINTTYPE ) {
+         nc = 0;
+         nval = astSscanf( cval, " %d %n", &ival, &nc );
+         if( ( nval == 1 ) && ( nc >= (int) strlen( cval ) ) ) {
+            if( out ) *( (short int *) out ) = ival;
+         } else {
+            nc = 0;
+            nval = astSscanf( cval, " %lf %n", &dval, &nc );
+            if( ( nval == 1 ) && ( nc >= (int) strlen( cval ) ) ) {
+               if( out ) *( (short int *) out ) = (int) ( dval + 0.5 );
+            } else {
+               result = 0;
+            }
+         }
+
+/* Consider conversion to "byte". */
+      } else if( out_type == AST__BYTETYPE ) {
+         nc = 0;
+         nval = astSscanf( cval, " %d %n", &ival, &nc );
+         if( ( nval == 1 ) && ( nc >= (int) strlen( cval ) ) ) {
+            if( out ) *( (unsigned char *) out ) = ival;
+         } else {
+            nc = 0;
+            nval = astSscanf( cval, " %lf %n", &dval, &nc );
+            if( ( nval == 1 ) && ( nc >= (int) strlen( cval ) ) ) {
+               if( out ) *( (unsigned char *) out ) = (int) ( dval + 0.5 );
+            } else {
+               result = 0;
+            }
+         }
+
+/* Consider conversion to "double". */
+      } else if( out_type == AST__DOUBLETYPE ) {
+         nc = 0;
+         nval = astSscanf( cval, " " BAD_STRING " %n", &nc );
+         if( ( astSscanf( cval, " " BAD_STRING " %n", &nc ) == 0 ) &&
+             ( nc >= (int) strlen( cval ) ) ) {
+            if( out ) *( (double *) out ) = AST__BAD;
+
+         } else if( ( astSscanf( cval, " %lf %n", &dval, &nc ) == 1 ) &&
+                     ( nc >= (int) strlen( cval ) ) ) {
+            if( out ) *( (double *) out ) = dval;
+
+         } else {
+            result = 0;
+         }
+
+/* Consider conversion to "float". */
+      } else if( out_type == AST__FLOATTYPE ) {
+         nc = 0;
+         nval = astSscanf( cval, " %f %n", &fval, &nc );
+         if( ( nval == 1 ) && ( nc >= (int) strlen( cval ) ) ) {
+            if( out ) *( (float *) out ) = fval;
+         } else {
+            result = 0;
+         }
+
+/* Consider conversion to "const char *". */
+      } else if( out_type == AST__STRINGTYPE ) {
+         cvalue = cval;
+
+/* Consider conversion to "AstObject *". */
+      } else if( out_type == AST__OBJECTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "void *". */
+      } else if( out_type == AST__POINTERTYPE ) {
+         result = 0;
+
+/* Report an error if the data type is unknown. */
+      } else {
+         result = 0;
+         astError( AST__INTER, "ConvertValue(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   out_type );
+      }
+
+/* Consider conversion from "AstObject *". */
+   } else if( raw_type == AST__OBJECTTYPE ) {
+
+/* Consider conversion to "int". */
+      if( out_type == AST__INTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "short int". */
+      } else if( out_type == AST__SINTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "byte". */
+      } else if( out_type == AST__BYTETYPE ) {
+         result = 0;
+
+/* Consider conversion to "double". */
+      } else if( out_type == AST__DOUBLETYPE ) {
+         result = 0;
+
+/* Consider conversion to "float". */
+      } else if( out_type == AST__FLOATTYPE ) {
+         result = 0;
+
+/* Consider conversion to "const char *". */
+      } else if( out_type == AST__STRINGTYPE ) {
+         result = 0;
+
+/* Consider conversion to "AstObject *". */
+      } else if( out_type == AST__OBJECTTYPE ) {
+         aval = *( (AstObject **) raw );
+         if( out ) *( (AstObject **) out ) = aval ? astClone( aval ) : NULL;
+
+/* Consider conversion to "void *". */
+      } else if( out_type == AST__POINTERTYPE ) {
+         result = 0;
+
+/* Report an error if the data type is unknown. */
+      } else {
+         result = 0;
+         astError( AST__INTER, "ConvertValue(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   out_type );
+      }
+
+/* Consider conversion from "void *". */
+   } else if( raw_type == AST__POINTERTYPE ) {
+
+/* Consider conversion to "int". */
+      if( out_type == AST__INTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "short int". */
+      } else if( out_type == AST__SINTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "byte". */
+      } else if( out_type == AST__BYTETYPE ) {
+         result = 0;
+
+/* Consider conversion to "double". */
+      } else if( out_type == AST__DOUBLETYPE ) {
+         result = 0;
+
+/* Consider conversion to "float". */
+      } else if( out_type == AST__FLOATTYPE ) {
+         result = 0;
+
+/* Consider conversion to "const char *". */
+      } else if( out_type == AST__STRINGTYPE ) {
+         result = 0;
+
+/* Consider conversion to "AstObject *". */
+      } else if( out_type == AST__OBJECTTYPE ) {
+         result = 0;
+
+/* Consider conversion to "void *". */
+      } else if( out_type == AST__POINTERTYPE ) {
+         if( out ) *( (void **) out ) = *( (void **) raw );
+
+/* Report an error if the data type is unknown. */
+      } else {
+         result = 0;
+         astError( AST__INTER, "ConvertValue(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   out_type );
+      }
+
+/* Report an error if the data type is unknown. */
+   } else {
+      result = 0;
+      astError( AST__INTER, "ConvertValue(KeyMap): Illegal map entry data "
+                "type %d encountered (internal AST programming error).", status,
+                raw_type );
+   }
+
+/* If the output is a string, store a copy of the resulting string in
+   dynamically allocated memory, putting a pointer to the copy into the next
+   element of the "convertvalue_strings" array.  (This process also de-allocates
+   any previously allocated memory pointed at by this "convertvalue_strings"
+   element, so the earlier string is effectively replaced by the new
+   one.) */
+   if( out_type == AST__STRINGTYPE && astOK && result && cvalue ) {
+      result = strlen( cvalue ) + 1;
+
+      astBeginPM;
+      convertvalue_strings[ convertvalue_istr ] = astStore( convertvalue_strings[ convertvalue_istr ], cvalue,
+                                  (size_t) result );
+      astEndPM;
+
+/* If OK, return a pointer to the copy and increment "convertvalue_istr" to use the
+   next element of "convertvalue_strings" on the next invocation. Recycle "convertvalue_istr" to
+   zero when all elements have been used. */
+      if ( astOK ) {
+         if( out ) *( (const char **) out ) = convertvalue_strings[ convertvalue_istr++ ];
+         if( convertvalue_istr == ( AST__KEYMAP_CONVERTVALUE_MAX_STRINGS - 1 ) ) convertvalue_istr = 0;
+      }
+   }
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+
+static AstMapEntry *CopyMapEntry( AstMapEntry *in, int *status ){
+/*
+*  Name:
+*     CopyMapEntry
+
+*  Purpose:
+*     Produces a copy of the supplied KeyMap entry.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     AstMapEntry *CopyMapEntry( AstMapEntry *in, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function creates a deep copy of the supplied KeyMap entry.
+
+*  Parameters:
+*     in
+*        Pointer to the MapEntry to be copied. NULL may be supplied in
+*        which case NULL will be returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the new copy. The link to the next MapEntry in the
+*     linked list is set NULL in the returned copy.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapEntry *result;   /* Returned pointer */
+   AstObject **alist;     /* Pointer to list of AST object pointers */
+   AstObject *obj;        /* Pointer to AstObject value */
+   const char **slist;    /* Pointer to list of text pointers */
+   const char *text;      /* Pointer to text string */
+   int i;                 /* Loop count */
+   int nel;               /* No. of values in entry vector (0 => scalar) */
+   int type;              /* Entry data type */
+   size_t size;           /* Size of Entry structure */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status and the supplied pointer. */
+   if ( !astOK || !in ) return result;
+
+/* Get the size, data type and length of the MapEntry. */
+   size = SizeOfEntry( in, status );
+   nel = in->nel;
+   type = in->type;
+
+/* Do a byte-for-byte copy of the supplied MapEntry. */
+   result = astStore( NULL, in, size );
+
+/* Copy or nullify pointers in the AstMapEntry structure. */
+   result->next = NULL;
+   result->snext = NULL;
+   result->sprev = NULL;
+   text = in->key;
+   result->key = text ? astStore( NULL, text, strlen( text ) + 1 ) : NULL;
+   text = in->comment;
+   result->comment = text ? astStore( NULL, text, strlen( text ) + 1 ) : NULL;
+
+/* Nothing further to do for undefined values. */
+   if( type == AST__UNDEFTYPE ) {
+
+/* Next deal with string entries. */
+   } else if( type == AST__STRINGTYPE ) {
+
+/* Scalar valued entries... */
+      if( nel == 0 ) {
+
+/* Take a copy of the single string in the input entry. */
+         text = ( (Entry0C *) in )->value;
+         ( (Entry0C *) result )->value = text ? astStore( NULL, text,
+                                                strlen( text ) + 1 ) : NULL;
+/* Vector valued entries... */
+      } else {
+
+/* Allocate an array to store the string pointers. */
+         slist = astMalloc( sizeof(char *)*(size_t)nel );
+         ( (Entry1C *) result )->value = slist;
+
+/* Copy the strings. */
+         if( slist ) {
+            for( i = 0; i < nel; i++ ) {
+               text = ( (Entry1C *) in )->value[ i ];
+               slist[ i ] = text ? astStore( NULL, text, strlen( text ) + 1 ) : NULL;
+            }
+         }
+      }
+
+/* Similarly deal with AST Object entries. */
+   } else if( type == AST__OBJECTTYPE ) {
+      if( nel == 0 ) {
+         obj = ( (Entry0A *) in )->value;
+         ( (Entry0A *) result )->value = obj ? astCopy( obj ) : NULL;
+         ( (Entry0A *) result )->next = NULL;
+         ( (Entry0A *) result )->prev = NULL;
+      } else {
+         alist = astMalloc( sizeof(AstObject *)*(size_t)nel );
+         ( (Entry1A *) result )->value = alist;
+         if( alist ) {
+            for( i = 0; i < nel; i++ ) {
+               obj = ( (Entry1A *) in )->value[ i ];
+               alist[ i ] = obj ? astCopy( obj ) : NULL;
+            }
+            ( (Entry1A *) result )->next = NULL;
+            ( (Entry1A *) result )->prev = NULL;
+         }
+      }
+
+/* Now deal with integer entries. Scalar entries do not need any further
+   action. If this is a vector entry copy the values array. */
+   } else if( type == AST__INTTYPE ) {
+      if( nel > 0 ) {
+         ( (Entry1I *) result )->value = astStore( NULL,
+                                                  ( (Entry1I *) in )->value,
+                                                  sizeof( int )*(size_t)nel );
+      }
+
+/* Now deal with short int entries. Scalar entries do not need any further
+   action. If this is a vector entry copy the values array. */
+   } else if( type == AST__SINTTYPE ) {
+      if( nel > 0 ) {
+         ( (Entry1S *) result )->value = astStore( NULL,
+                                            ( (Entry1S *) in )->value,
+                                            sizeof( short int )*(size_t)nel );
+      }
+
+/* Now deal with byte entries. Scalar entries do not need any further
+   action. If this is a vector entry copy the values array. */
+   } else if( type == AST__BYTETYPE ) {
+      if( nel > 0 ) {
+         ( (Entry1B *) result )->value = astStore( NULL,
+                                            ( (Entry1B *) in )->value,
+                                            sizeof( unsigned char )*(size_t)nel );
+      }
+
+/* Similarly deal with floating point entries. */
+   } else if( type == AST__DOUBLETYPE ) {
+      if( nel > 0 ) {
+         ( (Entry1D *) result )->value = astStore( NULL,
+                                                  ( (Entry1D *) in )->value,
+                                                  sizeof( double )*(size_t)nel );
+      }
+
+   } else if( type == AST__FLOATTYPE ) {
+      if( nel > 0 ) {
+         ( (Entry1F *) result )->value = astStore( NULL,
+                                                  ( (Entry1F *) in )->value,
+                                                  sizeof( float )*(size_t)nel );
+      }
+
+/* Similarly deal with void pointer entries. */
+   } else if( type == AST__POINTERTYPE ) {
+      if( nel > 0 ) {
+         ( (Entry1P *) result )->value = astStore( NULL,
+                                                  ( (Entry1P *) in )->value,
+                                                  sizeof( void * )*(size_t)nel );
+      }
+
+/* Report an error if the data type is unknown. */
+   } else {
+      astError( AST__INTER, "CopyMapEntry(KeyMap): Illegal map entry data "
+                "type %d encountered (internal AST programming error).", status,
+                type );
+   }
+
+/* If an error has occurred, attempt to delete the returned MapEntry. */
+   if( !astOK ) result = FreeMapEntry( result, status );
+
+/* Return the result. */
+   return result;
+}
+
+static void CopyTableEntry( AstKeyMap *in, AstKeyMap *out, int itab, int *status ){
+/*
+*  Name:
+*     CopyTableEntry
+
+*  Purpose:
+*     Produces a deep copy of a hash table element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void CopyTableEntry( AstKeyMap *in, AstKeyMap *out, int itab, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function creates a deep copy of the linked-list of KeyMap entries
+*     stored in the specified element of the input KeyMaps hash table.
+
+*  Parameters:
+*     in
+*        Pointer to the input KeyMap.
+*     out
+*        Pointer to the output KeyMap.
+*     itab
+*        Index of the hash table element to be copied.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMapEntry **link;    /* Address to store foward link */
+   AstMapEntry *next;     /* Pointer to next Entry to copy */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* The "link" variable holds the address of the location at which the
+   pointer to the next copied MapEntry should be stored. Initialise this to
+   be the address of the required element of the output hash table. */
+   link = &( out->table[ itab ] );
+
+/* The "next" variable holds the address of the next MapEntry to be
+   copied. Initialise this to the MapEntry at the head of the linked list
+   associated with the specified index of the input KeyMaps hash table. */
+   next = in->table[ itab ];
+
+/* If the hash table element is empty, store a null pointer and pass on. */
+   if( !next ) {
+      out->table[ itab ] = NULL;
+
+/* Otherwise copy the liked list. */
+   } else {
+
+/* Loop round until we have copied all entries. */
+      while( next && astOK ) {
+
+/* Copy the next entry, storing the resulting pointer at the position
+   indicated by "link". */
+         *link = CopyMapEntry( next, status );
+
+/* If the entry is of type AST__OBJECTTYPE, add it to the head of the
+   list of AST__OBJECTTYPE entries in the output KeyMap. */
+         AddToObjectList( out, *link, status );
+
+/* Update "link" and "next" */
+         next = next->next;
+         link = &( (*link)->next );
+      }
+   }
+
+/* Set the number of entries in the output to be the same as the input. */
+   out->nentry[ itab ] = in->nentry[ itab ];
+
+/* If an error has occurred, attempt to delete the returned MapEntry. */
+   if( !astOK ) FreeTableEntry( out, itab, status );
+}
+
+static void DoubleTableSize( AstKeyMap *this, int *status ) {
+/*
+*  Name:
+*     DoubleTableSize
+
+*  Purpose:
+*     Double the size of the hash table in a KeyMap
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void DoubleTableSize( AstKeyMap *this, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function creates a new hash table which has twice as many
+*     elements as the current hash table, and moves all the entries out
+*     of the old table into the new table (at their new positions).
+
+*  Parameters:
+*     this
+*        The KeyMap pointer.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMapEntry **newtable;
+   AstMapEntry *next;
+   AstMapEntry *new_next;
+   int *newnentry;
+   int bitmask;
+   int i;
+   int newi;
+   int newmapsize;
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Determine the new hash table size. Since mapsize starts out as a power
+   of 2 (ensured by the NewTable function), the new mapsize will also be
+   a power of 2. Also, create a bit mask that can be used to zero the
+   upper bits in a full width hash value. */
+   newmapsize = 2*this->mapsize;
+   bitmask = newmapsize - 1;
+
+/* Create the new arrays, leaving the old arrays intact for the moment. */
+   newtable = astMalloc( newmapsize*sizeof( AstMapEntry * ) );
+   newnentry = astMalloc( newmapsize*sizeof( int ) );
+   if( astOK ) {
+
+/* Initialise the new table. */
+      for( i = 0; i < newmapsize; i++ ) {
+         newtable[ i ] = NULL;
+         newnentry[ i ] = 0;
+      }
+
+/* Loop round each of the existing table entries. */
+      for( i = 0; i < this->mapsize; i++ ) {
+
+/* The "next" variable holds the address of the next MapEntry to be
+   moved. Initialise this to the MapEntry at the head of the linked list
+   associated with the specified index of the input KeyMaps hash table. */
+         next = this->table[ i ];
+
+/* Loop round until we have moved all entries. */
+         while( next && astOK ) {
+
+/* Find the index within the new table at which to store this entry. */
+            newi = ( next->hash & bitmask );
+
+/* Save the pointer to the next entry following the current one in the
+   linked list. */
+            new_next = next->next;
+
+/* Put a pointer to the MapEntry which is currently at the head of the
+   linked list in the "next" component of the current MapEntry. */
+            next->next = newtable[ newi ];
+
+/* Store the supplied MapEntry pointer in the requested element of the
+   hash table. */
+            newtable[ newi ] = next;
+
+/* Increment the length of linked list. */
+            newnentry[ newi ]++;
+
+/* Use the pointer to the next map entry to be moved. */
+            next = new_next;
+         }
+      }
+   }
+
+/* If OK, delete the existing table and use the new table */
+   if( astOK ) {
+      this->mapsize = newmapsize;
+
+      (void) astFree( this->table );
+      this->table = newtable;
+
+      (void) astFree( this->nentry );
+      this->nentry = newnentry;
+
+/* If not OK, delete the new table. */
+   } else {
+      newtable = astFree( newtable );
+      newnentry = astFree( newnentry );
+   }
+}
+
+static void DumpEntry( AstMapEntry *entry, AstChannel *channel, int nentry, int *status ) {
+/*
+*  Name:
+*     DumpEntry
+
+*  Purpose:
+*     Dump a single AstMapEntry to a Channel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void DumpEntry( AstMapEntry *entry, AstChannel *channel, int nentry )
+
+*  Description:
+*     This function dumps the supplied MapEntry to the supplied Channel.
+
+*  Parameters:
+*     entry
+*        Pointer to the MapEntry whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     nentry
+*        The integer index value to use when describing the MapEntry in
+*        the dump.
+*/
+
+/* Local Variables: */
+   char buff[20];                /* Buffer for item names */
+   const char *com;              /* Pointer to comment string */
+   int index;                    /* Index into vector valued entry */
+   int nel;                      /* Number of elements in value */
+   int type;                     /* Entry data type */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Dump the entry key. */
+   (void) sprintf( buff, "Key%d", nentry );
+   astWriteString( channel, buff, 1, 1, entry->key, "Item name" );
+
+/* Dump the comment if not blank. */
+   if( entry->comment && *(entry->comment) ) {
+      (void) sprintf( buff, "Com%d", nentry );
+      astWriteString( channel, buff, 1, 1, entry->comment, "Item comment" );
+   }
+
+/* Get the data type and the length of the Entry. */
+   type = entry->type;
+   nel = entry->nel;
+
+/* Dump the entry data type. */
+   if( type == AST__STRINGTYPE ) {
+      com = "Item data type (string)";
+
+   } else if( type == AST__OBJECTTYPE ) {
+      com = "Item data type (AST Object)";
+
+   } else if( type == AST__INTTYPE ) {
+      com = "Item data type (int)";
+
+   } else if( type == AST__SINTTYPE ) {
+      com = "Item data type (short int)";
+
+   } else if( type == AST__BYTETYPE ) {
+      com = "Item data type (unsigned byte)";
+
+   } else if( type == AST__DOUBLETYPE ) {
+      com = "Item data type (double)";
+
+   } else if( type == AST__FLOATTYPE ) {
+      com = "Item data type (float)";
+
+   } else if( type == AST__POINTERTYPE ) {
+      com = "Item data type (pointer)";
+
+   } else if( type == AST__UNDEFTYPE ) {
+      com = "Item data type (undefined)";
+
+   } else {
+      com = "";
+      astError( AST__INTER, "DumpEntry(KeyMap): Illegal map entry data "
+                "type %d encountered (internal AST programming error).", status,
+                type );
+   }
+   (void) sprintf( buff, "Typ%d", nentry );
+   astWriteInt( channel, buff, 1, 1, entry->type, com );
+
+/* Dump the vector length */
+   if( entry->nel > 0 ) {
+      (void) sprintf( buff, "Nel%d", nentry );
+      astWriteInt( channel, buff, 1, 1, entry->nel, "Vector length" );
+   }
+
+/* First deal with integer entries. */
+   if( type == AST__INTTYPE ) {
+      if( entry->nel == 0 ) {
+         (void) sprintf( buff, "Val%d", nentry );
+         astWriteInt( channel, buff, 1, 1, ((Entry0I *)entry)->value, "Item value" );
+      } else {
+         com = "Item values";
+         for( index = 0; index < nel; index++ ){
+            (void) sprintf( buff, "V%d_%d", nentry, index + 1 );
+            astWriteInt( channel, buff, 1, 1, ((Entry1I *)entry)->value[ index ], com );
+            com = "";
+         }
+      }
+
+/* Similarly, deal with short int entries. */
+   } else if( type == AST__SINTTYPE ) {
+      if( entry->nel == 0 ) {
+         (void) sprintf( buff, "Val%d", nentry );
+         astWriteInt( channel, buff, 1, 1, (int) ((Entry0S *)entry)->value, "Item value" );
+      } else {
+         com = "Item values";
+         for( index = 0; index < nel; index++ ){
+            (void) sprintf( buff, "V%d_%d", nentry, index + 1 );
+            astWriteInt( channel, buff, 1, 1, (int) ((Entry1S *)entry)->value[ index ], com );
+            com = "";
+         }
+      }
+
+/* Similarly, deal with byte entries. */
+   } else if( type == AST__BYTETYPE ) {
+      if( entry->nel == 0 ) {
+         (void) sprintf( buff, "Val%d", nentry );
+         astWriteInt( channel, buff, 1, 1, (int) ((Entry0B *)entry)->value, "Item value" );
+      } else {
+         com = "Item values";
+         for( index = 0; index < nel; index++ ){
+            (void) sprintf( buff, "V%d_%d", nentry, index + 1 );
+            astWriteInt( channel, buff, 1, 1, (int) ((Entry1B *)entry)->value[ index ], com );
+            com = "";
+         }
+      }
+
+/* Similarly deal with floating point entries. */
+   } else if( type == AST__DOUBLETYPE ) {
+      if( entry->nel == 0 ) {
+         if( ((Entry0D *)entry)->value != AST__BAD ) {
+            (void) sprintf( buff, "Val%d", nentry );
+            astWriteDouble( channel, buff, 1, 1, ((Entry0D *)entry)->value, "Item value" );
+         }
+      } else {
+         com = "Item values";
+         for( index = 0; index < nel; index++ ){
+            if( ((Entry1D *)entry)->value[ index ] != AST__BAD ) {
+               (void) sprintf( buff, "V%d_%d", nentry, index + 1 );
+               astWriteDouble( channel, buff, 1, 1, ((Entry1D *)entry)->value[ index ], com );
+               com = "";
+            }
+         }
+      }
+
+/* Similarly deal with single precision floating point entries. */
+   } else if( type == AST__FLOATTYPE ) {
+      if( entry->nel == 0 ) {
+         (void) sprintf( buff, "Val%d", nentry );
+         astWriteDouble( channel, buff, 1, 1, (double) ((Entry0F *)entry)->value, "Item value" );
+      } else {
+         com = "Item values";
+         for( index = 0; index < nel; index++ ){
+            (void) sprintf( buff, "V%d_%d", nentry, index + 1 );
+            astWriteDouble( channel, buff, 1, 1, (double) ((Entry1F *)entry)->value[ index ], com );
+            com = "";
+         }
+      }
+
+/* Do the same for string values. */
+   } else if( type == AST__STRINGTYPE ) {
+      if( entry->nel == 0 ) {
+         (void) sprintf( buff, "Val%d", nentry );
+         astWriteString( channel, buff, 1, 1, ((Entry0C *)entry)->value, "Item value" );
+      } else {
+         com = "Item values";
+         for( index = 0; index < nel; index++ ){
+            (void) sprintf( buff, "V%d_%d", nentry, index + 1 );
+            astWriteString( channel, buff, 1, 1, ((Entry1C *)entry)->value[ index ], com );
+            com = "";
+         }
+      }
+
+/* Do the same for Object values. */
+   } else if( type == AST__OBJECTTYPE ) {
+      if( entry->nel == 0 ) {
+         if( ((Entry0A *)entry)->value ) {
+            (void) sprintf( buff, "Val%d", nentry );
+            astWriteObject( channel, buff, 1, 1, ((Entry0A *)entry)->value, "Item value" );
+         }
+      } else {
+         com = "Item values";
+         for( index = 0; index < nel; index++ ){
+            if( ((Entry1A *)entry)->value[ index ] ) {
+               (void) sprintf( buff, "V%d_%d", nentry, index + 1 );
+               astWriteObject( channel, buff, 1, 1, ((Entry1A *)entry)->value[ index ], com );
+               com = "";
+            }
+         }
+      }
+
+/* Void pointer values cannot be dumped. */
+   } else if( type == AST__POINTERTYPE ) {
+      astError( AST__INTER, "DumpEntry(KeyMap): Cannot dump KeyMaps in "
+                "which the values are arbitrary C pointers (possible "
+                "programming error)." , status);
+
+/* Nothing further to do for undefined values. */
+   } else if( type == AST__UNDEFTYPE ) {
+
+/* Report an error if the data type is unknown. */
+   } else if( astOK ) {
+      astError( AST__INTER, "DumpEntry(KeyMap): Illegal map entry data "
+                "type %d encountered (internal AST programming error).", status,
+                type );
+   }
+}
+
+static AstMapEntry *FreeMapEntry( AstMapEntry *in, int *status ){
+/*
+*  Name:
+*     FreeMapEntry
+
+*  Purpose:
+*     Frees the supplied KeyMap entry.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     AstMapEntry *FreeMapEntry( AstMapEntry *in, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function frees resources used by the supplied MapEntry, then
+*     frees the MapEntry structure itself and returns a NULL pointer.
+
+*  Parameters:
+*     in
+*        Pointer to the MapEntry to be freed. NULL may be supplied in
+*        which the function returns without action.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A NULL pointer.
+
+*  Notes:
+*     - It is the callers responsibility to ensure that any other MapEntry
+*     which refers to the supplied MapEntry (e.g. through the "next" link
+*     in the MapEntry structure) is modified to take account of the
+*     freeing of the supplied MapEntry.
+*     - This function attempts to execute even if it is invoked with the
+*     global error status set.
+*/
+
+/* Local Variables: */
+   AstObject **alist;     /* Pointer to list of AST object pointers */
+   AstObject *obj;        /* Pointer to AST object */
+   const char **slist;    /* Pointer to list of text pointers */
+   int i;                 /* Loop count */
+   int nel;               /* No. of values in entry vector (0 => scalar) */
+   int type;              /* Entry data type */
+
+/* Check the supplied pointer. */
+   if( !in ) return NULL;
+
+/* Get the data type and length of the MapEntry. */
+   nel = in->nel;
+   type = in->type;
+
+/* First deal with string entries. */
+   if( type == AST__STRINGTYPE ) {
+
+/* For scalar valued entries, free the single string in the input entry. */
+      if( nel == 0 ) {
+         ( (Entry0C *) in )->value = (const char *) astFree( ( void *) ( (Entry0C *) in )->value );
+
+/* For vector valued entries, free the strings, then free the array storing
+   the string pointers. */
+      } else {
+         slist = ( (Entry1C *) in )->value;
+         if( slist ) {
+            for( i = 0; i < nel; i++ ) slist[ i ] = astFree( (void *) slist[ i ] );
+            ( (Entry1C *) in )->value = astFree( (void *) slist );
+         }
+      }
+
+/* Similarly deal with AST Object entries. */
+   } else if( type == AST__OBJECTTYPE ) {
+      if( nel == 0 ) {
+         obj = ( (Entry0A *) in )->value;
+         if( obj ) ( (Entry0A *) in )->value = astAnnul( obj );
+         ( (Entry0A *) in )->next = NULL;
+         ( (Entry0A *) in )->prev = NULL;
+      } else {
+         alist = ( (Entry1A *) in )->value;
+         if( alist ) {
+            for( i = 0; i < nel; i++ ) {
+               if( alist[ i ] ) alist[ i ] = astAnnul( alist[ i ] );
+            }
+            ( (Entry1A *) in )->value = astFree( alist );
+            ( (Entry1A *) in )->next = NULL;
+            ( (Entry1A *) in )->prev = NULL;
+         }
+      }
+
+/* Now deal with integer entries. Scalar entries do not need any further
+   action. If this is a vector entry free the values array. */
+   } else if( type == AST__INTTYPE ) {
+      if( nel > 0 ) ( (Entry1I *) in )->value = astFree( ( (Entry1I *) in )->value );
+
+/* Now deal with short int entries. Scalar entries do not need any further
+   action. If this is a vector entry free the values array. */
+   } else if( type == AST__SINTTYPE ) {
+      if( nel > 0 ) ( (Entry1S *) in )->value = astFree( ( (Entry1S *) in )->value );
+
+/* Now deal with byte entries. Scalar entries do not need any further
+   action. If this is a vector entry free the values array. */
+   } else if( type == AST__BYTETYPE ) {
+      if( nel > 0 ) ( (Entry1B *) in )->value = astFree( ( (Entry1B *) in )->value );
+
+/* Similarly deal with void * pointer entries. */
+   } else if( type == AST__POINTERTYPE ) {
+      if( nel > 0 ) ( (Entry1P *) in )->value = astFree( ( (Entry1P *) in )->value );
+
+/* Similarly deal with floating point entries. */
+   } else if( type == AST__DOUBLETYPE ) {
+      if( nel > 0 ) ( (Entry1D *) in )->value = astFree( ( (Entry1D *) in )->value );
+
+/* Similarly deal with single precision floating point entries. */
+   } else if( type == AST__FLOATTYPE ) {
+      if( nel > 0 ) ( (Entry1F *) in )->value = astFree( ( (Entry1F *) in )->value );
+
+/* Nothing further to do for undefined values. */
+   } else if( type == AST__UNDEFTYPE ) {
+
+/* Report an error if the data type is unknown. */
+   } else {
+      astError( AST__INTER, "FreeMapEntry(KeyMap): Illegal map entry data "
+                "type %d encountered (internal AST programming error).", status,
+                type );
+   }
+
+/* Free or nullify pointers in the AstMapEntry structure. */
+   in->next = NULL;
+   in->snext = NULL;
+   in->sprev = NULL;
+   in->key = astFree( (void *) in->key );
+   in->comment = astFree( (void *) in->comment );
+
+/* Free the complete AstMapEntry structure. */
+   astFree( in );
+
+/* Return a NULL pointer. */
+   return NULL;
+}
+
+static void FreeTableEntry( AstKeyMap *this, int itab, int *status ){
+/*
+*  Name:
+*     FreeTableEntry
+
+*  Purpose:
+*     Frees the linked list of KeyMap entries stored in a given element of
+*     the hash table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void FreeTableEntry( AstKeyMap *this, int itab, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function frees resources used by all the MapEntries in the
+*     linked list associated with the specified element of the hash table
+*     of the supplied KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap
+*     itab
+*        Index of the hash table element to free.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function attempts to execute even if it is invoked with the
+*     global error status set.
+*/
+
+/* Local Variables: */
+   AstMapEntry *link;     /* Pointer the next but one MapEntry to be freed */
+   AstMapEntry *next;     /* Pointer the next MapEntry to be freed */
+
+/* Check it is safe to proceed. */
+   if( this && itab >= 0 && itab < this->mapsize ) {
+
+/* Store a pointer to the MapEntry which is to be freed next. */
+      next = this->table[ itab ];
+
+/* Loop round freeing all MapEntries in the linked list. */
+      while( next ) {
+
+/* Store a pointer to the MapEntry which will be freed after this one. */
+         link = next->next;
+
+/* Free this MapEntry */
+         (void) FreeMapEntry( next, status );
+
+/* Set up the next MapEntry to be freed. */
+         next = link;
+      }
+
+/* Store a NULL pointer in the table element. */
+      this->table[ itab ] = NULL;
+
+/* Sets the number of entries in this hash table element to zero. */
+      this->nentry[ itab ] = 0;
+   }
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     KeyMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a KeyMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the KeyMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the KeyMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstKeyMap *this;              /* Pointer to the KeyMap structure */
+   const char *result;           /* Pointer value to return */
+   int ival;                     /* Attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the KeyMap structure. */
+   this = (AstKeyMap *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* SizeGuess. */
+/* ---------- */
+   if ( !strcmp( attrib, "sizeguess" ) ) {
+      ival = astGetSizeGuess( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* KeyCase. */
+/* --------- */
+   } else if ( !strcmp( attrib, "keycase" ) ) {
+      ival = astGetKeyCase( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* KeyError. */
+/* --------- */
+   } else if ( !strcmp( attrib, "keyerror" ) ) {
+      ival = astGetKeyError( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* MapLocked. */
+/* --------- */
+   } else if ( !strcmp( attrib, "maplocked" ) ) {
+      ival = astGetMapLocked( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* SortBy. */
+/* --------- */
+   } else if ( !strcmp( attrib, "sortby" ) ) {
+      ival = astGetSortBy( this );
+      if ( astOK ) {
+         result = SortByString( ival, "astGetAttrib", status );
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     KeyMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied KeyMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstKeyMap *this;       /* Pointer to KeyMap structure */
+   AstMapEntry *next;     /* Pointer the next MapEntry */
+   AstObject **alist;     /* Pointer to list of AST object pointers */
+   AstObject *obj;        /* Pointer to AST object */
+   const char **slist;    /* Pointer to list of text pointers */
+   int i;                 /* Loop count */
+   int itab;              /* Table entry index */
+   int nel;               /* No. of values in entry vector (0 => scalar) */
+   int result;            /* Result value to return */
+   int type;              /* Entry data type */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the KeyMap structure. */
+   this = (AstKeyMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   for( itab = 0; itab < this->mapsize; itab++ ) {
+      next = this->table[ itab ];
+      while( next ) {
+         nel = next->nel;
+         type = next->type;
+
+         if( type == AST__STRINGTYPE ) {
+
+            if( nel == 0 ) {
+               result += astTSizeOf( ( void *) ( (Entry0C *) next )->value );
+
+            } else {
+               slist = ( (Entry1C *) next )->value;
+               if( slist ) {
+                  for( i = 0; i < nel; i++ ) result += astTSizeOf( (void *) slist[ i ] );
+                  result += astTSizeOf( (void *) slist );
+               }
+            }
+
+         } else if( type == AST__OBJECTTYPE ) {
+            if( nel == 0 ) {
+               obj = ( (Entry0A *) next )->value;
+               result += astGetObjSize( obj );
+            } else {
+               alist = ( (Entry1A *) next )->value;
+               if( alist ) {
+                  for( i = 0; i < nel; i++ ) {
+                     result += astGetObjSize( alist[ i ] );
+                  }
+                  result += astTSizeOf( alist );
+               }
+            }
+
+         } else if( type == AST__POINTERTYPE ) {
+            if( nel > 0 ) result += astTSizeOf( ( (Entry1P *) next )->value );
+
+         } else if( type == AST__INTTYPE ) {
+            if( nel > 0 ) result += astTSizeOf( ( (Entry1I *) next )->value );
+
+         } else if( type == AST__SINTTYPE ) {
+            if( nel > 0 ) result += astTSizeOf( ( (Entry1S *) next )->value );
+
+         } else if( type == AST__BYTETYPE ) {
+            if( nel > 0 ) result += astTSizeOf( ( (Entry1B *) next )->value );
+
+         } else if( type == AST__DOUBLETYPE ) {
+            if( nel > 0 ) result += astTSizeOf( ( (Entry1D *) next )->value );
+
+         } else if( type == AST__FLOATTYPE ) {
+            if( nel > 0 ) result += astTSizeOf( ( (Entry1F *) next )->value );
+
+         } else if( type == AST__UNDEFTYPE ) {
+
+         } else {
+            astError( AST__INTER, "astGetObjSize(KeyMap): Illegal map entry data "
+                      "type %d encountered (internal AST programming error).", status,
+                      type );
+         }
+
+         result += astTSizeOf( (void *) next->key );
+         result += astTSizeOf( (void *) next->comment );
+         result += astTSizeOf( next );
+
+         next = next->next;
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetKey( AstKeyMap *this, int index, int *status ) {
+/*
+*  Name:
+*     GetKey
+
+*  Purpose:
+*     Get the key at a given index within the KeyMap.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     const char *GetKey( AstKeyMap *this, int index, int *status )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function returns a string holding the key for the entry with
+*     the given index within the KeyMap. The index associated with a
+*     given key is determined by the current setting of the SortBy attribute.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     index
+*        The index into the KeyMap. The first entry has index zero, and the last
+*        has index "size-1", where "size" is the value returned by the
+*        astMapSize function. An error is reported if the supplied index is
+*        out of bounds.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*        A pointer to a null-terminated string containing the key.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the AST error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapEntry *entry;         /* Pointer to the entry */
+   const char *result;         /* Pointer value to return */
+   int ifirst;                 /* Index of first entry in this table element */
+   int ilast;                  /* Index of last entry in this table element */
+   int istep;                  /* Entry count */
+   int itab;                   /* Index into hash table */
+   int nstep;                  /* No. of entries to skip */
+   int sortby;                 /* The value of the SortBy attribute */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the SortBy value. */
+   sortby = astGetSortBy( this );
+
+/* First deal with unsorted keys. */
+   if( sortby == SORTBY_NONE ) {
+
+/* Loop round each entry in the hash table. */
+      ilast = -1;
+      for( itab = 0; itab < this->mapsize; itab++ ) {
+
+/* Find the index of the first and the last Entry in the linked list associated
+   with this element of the hash table. */
+         ifirst = ilast + 1;
+         ilast += this->nentry[ itab ];
+
+/* Pass on if we have not yet reached the element containing the required
+   key. */
+         if( ilast >= index ) {
+
+/* Find how many steps we need to proceed down the linked list to reach
+   the required index. */
+            nstep = index - ifirst;
+
+/* Make this many steps down the linked list.*/
+            entry = this->table[ itab ];
+            for( istep = 0; entry && istep < nstep; istep++ ) entry = entry->next;
+
+/* Return a pointer to the key string, and break out of the loop. */
+            if( entry ) result = entry->key;
+            break;
+
+         }
+      }
+
+/* Now deal with sorted keys. */
+   } else {
+
+/* Get a pointer to the first entry in the sorted list. */
+      entry = this->first;
+
+/* Move up the sorted list by the required number of entries. */
+      for( istep = 0; entry && istep < index; istep++ ) entry = entry->snext;
+
+/* Return a pointer to the key string. */
+      if( entry ) result = entry->key;
+   }
+
+/* Report an error if the element was not found. */
+   if( !result && astOK ) {
+      astError( AST__MPIND, "astMapKey(%s): Cannot find element "
+                "%d (zero-based) of the %s.", status, astGetClass( this ),
+                index, astGetClass( this ) );
+   }
+
+/* Return the result.*/
+   return result;
+}
+
+static int GetSizeGuess( AstKeyMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetSizeGuess
+
+*  Purpose:
+*     Get the value of the SizeGuess attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     int astGetSizeGuess( AstKeyMap *this )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function returns the value of the SizeGuess attribute for a
+*     KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+
+*  Returned Value:
+*     The value of the SizeGuess attribute.
+
+*  Notes:
+*     - A value of zero is returned if this function is invoked with the
+*     global error status set.
+
+*-
+*/
+
+/* Local Variables: */
+   int result;                /* Returned value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Return the attribute value using a default if not set. */
+   return ( this->sizeguess == INT_MAX ) ?
+           MIN_TABLE_SIZE*MAX_ENTRIES_PER_TABLE_ENTRY : this->sizeguess;
+}
+
+static int HashFun( const char *key, int bitmask, unsigned long *hash, int *status ){
+/*
+*  Name:
+*     HashFun
+
+*  Purpose:
+*     Returns an integer hash code for a string
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     int HashFun( const char *key, int bitmask, int *hash, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function returns an integer hash code for the supplied string,
+*     This is the value that isused as the index into the hash table for
+*     the specified key.
+
+*  Parameters:
+*     key
+*        Pointer to the string. Trailing spaces are ignored.
+*     bitmask
+*        A bit mask that is used to zero the upper bits of a full width
+*        hash value in order to produce the required array index. This
+*        should be one less than the length of the hash table.
+*     hash
+*        Pointer to a location at which to put the full width hash value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     An integer in the range zero to ( mapsize - 1 ).
+
+*  Notes:
+*     - A value of zero is returned if this function is invoked with the
+*     global error status set.
+*/
+
+/* Local Variables: */
+   int c;
+
+/* Check the local error status. */
+   if ( !astOK ) return 0;
+
+/* djb2: This hash function was first reported by Dan Bernstein many years
+   ago in comp.lang.c Each through the "hile" loop corresponds to
+   "hash = hash*33 + c ". Ignore spaces so that trailing spaces used to
+   pad F77 character variables will be ignored. */
+   *hash = 5381;
+   while( (c = *key++) ) {
+      if( c != ' ' ) {
+         *hash = ((*hash << 5) + *hash) + c;
+      }
+   }
+   return ( *hash & bitmask );
+}
+
+void astInitKeyMapVtab_(  AstKeyMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitKeyMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a KeyMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astInitKeyMapVtab( AstKeyMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     KeyMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the KeyMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitObjectVtab( (AstObjectVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAKeyMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstObjectVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+
+   vtab->MapGet0P = MapGet0P;
+   vtab->MapGet0A = MapGet0A;
+   vtab->MapGet0C = MapGet0C;
+   vtab->MapGet0D = MapGet0D;
+   vtab->MapGet0F = MapGet0F;
+   vtab->MapGet0I = MapGet0I;
+   vtab->MapGet0B = MapGet0B;
+   vtab->MapGet0S = MapGet0S;
+   vtab->MapGet1P = MapGet1P;
+   vtab->MapGet1A = MapGet1A;
+   vtab->MapGet1C = MapGet1C;
+   vtab->MapGet1D = MapGet1D;
+   vtab->MapGet1F = MapGet1F;
+   vtab->MapGet1I = MapGet1I;
+   vtab->MapGet1B = MapGet1B;
+   vtab->MapGet1S = MapGet1S;
+   vtab->MapGetElemP = MapGetElemP;
+   vtab->MapGetElemA = MapGetElemA;
+   vtab->MapGetElemC = MapGetElemC;
+   vtab->MapGetElemD = MapGetElemD;
+   vtab->MapGetElemF = MapGetElemF;
+   vtab->MapGetElemI = MapGetElemI;
+   vtab->MapGetElemS = MapGetElemS;
+   vtab->MapGetElemB = MapGetElemB;
+   vtab->MapPutElemP = MapPutElemP;
+   vtab->MapPutElemA = MapPutElemA;
+   vtab->MapPutElemC = MapPutElemC;
+   vtab->MapPutElemD = MapPutElemD;
+   vtab->MapPutElemF = MapPutElemF;
+   vtab->MapPutElemI = MapPutElemI;
+   vtab->MapPutElemS = MapPutElemS;
+   vtab->MapPutElemB = MapPutElemB;
+   vtab->MapPut0A = MapPut0A;
+   vtab->MapPut0P = MapPut0P;
+   vtab->MapPut0C = MapPut0C;
+   vtab->MapPut0D = MapPut0D;
+   vtab->MapPut0F = MapPut0F;
+   vtab->MapPut0I = MapPut0I;
+   vtab->MapPut0S = MapPut0S;
+   vtab->MapPut0B = MapPut0B;
+   vtab->MapPut1P = MapPut1P;
+   vtab->MapPut1A = MapPut1A;
+   vtab->MapPut1C = MapPut1C;
+   vtab->MapPut1D = MapPut1D;
+   vtab->MapPut1F = MapPut1F;
+   vtab->MapPut1I = MapPut1I;
+   vtab->MapPut1S = MapPut1S;
+   vtab->MapPut1B = MapPut1B;
+   vtab->MapPutU = MapPutU;
+   vtab->MapRemove = MapRemove;
+   vtab->MapRename = MapRename;
+   vtab->MapCopy = MapCopy;
+   vtab->MapDefined = MapDefined;
+   vtab->MapSize = MapSize;
+   vtab->MapLenC = MapLenC;
+   vtab->MapLength = MapLength;
+   vtab->MapType = MapType;
+   vtab->MapHasKey = MapHasKey;
+   vtab->MapKey = MapKey;
+   vtab->MapIterate = MapIterate;
+
+   vtab->ClearSizeGuess = ClearSizeGuess;
+   vtab->SetSizeGuess = SetSizeGuess;
+   vtab->GetSizeGuess = GetSizeGuess;
+   vtab->TestSizeGuess = TestSizeGuess;
+
+   vtab->ClearSortBy = ClearSortBy;
+   vtab->SetSortBy = SetSortBy;
+   vtab->GetSortBy = GetSortBy;
+   vtab->TestSortBy = TestSortBy;
+
+   vtab->ClearKeyError = ClearKeyError;
+   vtab->SetKeyError = SetKeyError;
+   vtab->GetKeyError = GetKeyError;
+   vtab->TestKeyError = TestKeyError;
+
+   vtab->ClearKeyCase = ClearKeyCase;
+   vtab->SetKeyCase = SetKeyCase;
+   vtab->GetKeyCase = GetKeyCase;
+   vtab->TestKeyCase = TestKeyCase;
+
+   vtab->ClearMapLocked = ClearMapLocked;
+   vtab->SetMapLocked = SetMapLocked;
+   vtab->GetMapLocked = GetMapLocked;
+   vtab->TestMapLocked = TestMapLocked;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+/* Declare the destructor, copy constructor and dump function. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "KeyMap", "Map of key/value pairs" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void InitMapEntry( AstMapEntry *entry, int type, int nel, int *status ){
+/*
+*  Name:
+*     InitMapEntry
+
+*  Purpose:
+*     initialise a MapEntry structure to null values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void InitMapEntry( AstMapEntry *entry, int type, int nel, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function initialises the contents of a MapEntry to null values.
+
+*  Parameters:
+*     this
+*        Pointer to the MapEntry.
+*     type
+*        The type of the MapEntry.
+*     nel
+*        The number of elements in the entry: 0 = scalar, >0 = vector.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Initialise all elements with in the MapEntry structure. */
+   entry->next = NULL;
+   entry->key = NULL;
+   entry->hash = 0;
+   entry->type = type;
+   entry->nel = nel;
+   entry->comment = NULL;
+   entry->defined = 0;
+   entry->snext = NULL;
+   entry->sprev = NULL;
+   entry->member = 0;
+   entry->keymember = 0;
+   entry->sortby = SORTBY_NONE;
+
+   if( type == AST__OBJECTTYPE ) {
+      if( nel == 0 ) {
+         ( (Entry0A *) entry )->next = NULL;
+         ( (Entry0A *) entry )->prev = NULL;
+      } else {
+         ( (Entry1A *) entry )->next = NULL;
+         ( (Entry1A *) entry )->prev = NULL;
+      }
+   }
+
+}
+
+static int KeyCmp( const char *key1, const char *key2 ) {
+/*
+*  Name:
+*     KeyCmp
+
+*  Purpose:
+*     Compares keys for equality.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     int KeyCmp( const char *key1, const char *key2 )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function compares two strings. It is like strcmp except that it
+*     ignores trailing spaces.
+
+*  Parameters:
+*     key1
+*        Pointer to first string.
+*     key2
+*        Pointer to second string.
+
+*  Returned Value:
+*     One if the keys differ. Zero if they are identical (except for
+*     trailing spaces).
+
+*/
+
+/* Local Variables: */
+   const char *k1;               /* Pointer to next "key1" character */
+   const char *k2;               /* Pointer to next "key2" character */
+   int result;                   /* Returned flag */
+
+/* Check the strings are deifned. */
+   if ( !key1 || !key2 ) return 0;
+
+/* Get pointers to the first characters to differ, or to the first null
+   character, which ever comes first. */
+   k1 = key1;
+   k2 = key2;
+   while( *k1 && ( *k1 == *k2 ) ) {
+      k1++;
+      k2++;
+   }
+
+/* If both characters are null, the strings are identical. If neither is null,
+   the string definitely differ. If one is null, we need to check if the
+   other one only has spaces to the end of the string. */
+   if( *k1 ) {
+      if( *k2 ) {
+         result = ( *k1 > *k2 ) ? 1 : -1;
+      } else {
+         while( *k1 == ' ' ) k1++;
+         result = ( *k1 == 0 ) ? 0 : 1;
+      }
+   } else {
+      if( *k2 ) {
+         while( *k2 == ' ' ) k2++;
+         result = ( *k2 == 0 ) ? 0 : -1;
+      } else {
+         result = 0;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     KeyMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstKeyMap *this;       /* Pointer to KeyMap structure */
+   AstMapEntry *next;     /* Pointer the next MapEntry */
+   AstObject **alist;     /* Pointer to list of AST object pointers */
+   AstObject *obj;        /* Pointer to AST object */
+   int i;                 /* Loop count */
+   int nel;               /* No. of values in entry vector (0 => scalar) */
+   int result;            /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the KeyMap structure. */
+   this = (AstKeyMap *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+
+   next = this->firstA;
+   while( next ) {
+      nel = next->nel;
+      if( nel == 0 ) {
+         obj = ( (Entry0A *) next )->value;
+         if( !result ) result = astManageLock( obj, mode, extra, fail );
+         next = ( (Entry0A *) next)->next;
+      } else {
+         alist = ( (Entry1A *) next )->value;
+         if( alist ) {
+            for( i = 0; i < nel; i++ ) {
+               if( !result ) result = astManageLock( alist[ i ], mode,
+                                                     extra, fail );
+            }
+         }
+         next = ( (Entry1A *) next)->next;
+      }
+   }
+
+   return result;
+
+}
+#endif
+
+static void MapCopy( AstKeyMap *this, AstKeyMap *that, int *status ) {
+/*
+*++
+*  Name:
+c     astMapCopy
+f     AST_MAPCOPY
+
+*  Purpose:
+*     Copy entries from one KeyMap into another.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "keymap.h"
+c     void astMapCopy( AstKeyMap *this, AstKeyMap *that )
+f     CALL AST_MAPCOPY( THIS, THAT, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+c     This function
+f     This routine
+*     copies all entries from one KeyMap into another.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the destination KeyMap.
+c     that
+f     THAT = INTEGER (Given)
+*        Pointer to the source KeyMap.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Entries from the source KeyMap will replace any existing entries in
+*     the destination KeyMap that have the same key.
+*     - The one exception to the above rule is that if a source entry
+*     contains a scalar KeyMap entry, and the destination contains a
+*     scalar KeyMap entry with the same key, then the source KeyMap entry
+*     will be copied into the destination KeyMap entry using this function,
+*     rather than simply replacing the destination KeyMap entry.
+*     - If the destination entry has a non-zero value for its MapLocked
+*     attribute, then an error will be reported if the source KeyMap
+*     contains any keys that do not already exist within the destination
+*     KeyMap.
+
+*--
+*/
+
+/* Local Variables: */
+   AstMapEntry *in_entry; /* Pointer to next source entry to copy */
+   AstMapEntry *out_entry;/* Pointer to existing destination entry */
+   AstObject *in_obj;     /* Pointer for source Object entry */
+   AstObject *out_obj;    /* Pointer for destination Object entry */
+   const char *key;       /* Key for current entry */
+   int i;                 /* Index into source hash table */
+   int itab;              /* Index of destination hash table element */
+   int keymember;         /* Identifier for key */
+   int merged;            /* Were source and destination KeyMaps merged? */
+   unsigned long hash;    /* Full width hash value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Loop round all entries in the source hash table. */
+   for( i = 0; i < that->mapsize; i++ ) {
+
+/* Get a pointer to the next source KeyMap entry. */
+      in_entry = that->table[ i ];
+
+/* Loop round all entries in this element of the source hash table. */
+      while( in_entry && astOK ) {
+
+/* Get its key. */
+         key = in_entry->key;
+
+/* Search for a destination entry with the same key. */
+         itab = HashFun( key, this->mapsize - 1, &hash, status );
+         out_entry = SearchTableEntry( this, itab, key, status );
+
+/* If the destination KeyMap does not contain an entry with the current
+   key, store a copy of the entry in the destination, or report an error
+   if the destination's MapLocked attribute is set. */
+         if( !out_entry ) {
+            if( astGetMapLocked( this ) ) {
+               astError( AST__BADKEY, "astMapCopy(%s): Failed to copy "
+                         "item \"%s\": \"%s\" is not a known item.", status,
+                         astGetClass( this ), key, key );
+            } else {
+               out_entry = CopyMapEntry( in_entry, status );
+               out_entry = AddTableEntry( this, itab, out_entry, -1, status );
+            }
+
+/* If the destination KeyMap contains an entry with the current key... */
+         } else {
+
+/* The usual thing is to just replace the existing entry in the
+   destination with a copy of the source entry. The one case where this is
+   not done is if both entries are scalar KeyMaps. In this case the source
+   KeyMap is merged into the destination KeyMap using this function. First
+   see if we have this situation, and if so, copy the entries from the
+   source KeyMap to the destination KeyMap. */
+            merged = 0;
+            if( in_entry->nel == 0 || in_entry->nel == 1 ) {
+               if( out_entry->nel == 0 || out_entry->nel == 1 ) {
+                  if( in_entry->type == AST__OBJECTTYPE &&
+                      out_entry->type == AST__OBJECTTYPE ) {
+
+                     if( in_entry->nel == 0 ) {
+                        in_obj = ((Entry0A *)in_entry)->value;
+                     } else {
+                        in_obj = (((Entry1A *)in_entry)->value)[ 0 ];
+                     }
+
+                     if( out_entry->nel == 0 ) {
+                        out_obj = ((Entry0A *)out_entry)->value;
+                     } else {
+                        out_obj = (((Entry1A *)out_entry)->value)[ 0 ];
+                     }
+
+                     if( astIsAKeyMap( in_obj ) &&
+                         astIsAKeyMap( out_obj ) ) {
+                        astMapCopy( (AstKeyMap *) out_obj,
+                                    (AstKeyMap *) in_obj );
+                        merged = 1;
+                     }
+                  }
+               }
+            }
+
+/* If the source and desination entries are not KeyMaps, then just remove
+   the entry in the desination KeyMap and add a copy of the source entry.
+   But retain the original keymember value since we are just changing the
+   value of an existing key. */
+            if( ! merged ) {
+               out_entry = RemoveTableEntry( this, itab, key, status );
+               keymember = out_entry->keymember;
+               (void) FreeMapEntry( out_entry, status );
+               out_entry = CopyMapEntry( in_entry, status );
+               out_entry = AddTableEntry( this, itab, out_entry, keymember, status );
+            }
+         }
+
+/* Update the address of the next MapEntry in the source. */
+         in_entry = in_entry->next;
+      }
+   }
+}
+
+static const char *MapKey( AstKeyMap *this, int index, int *status ) {
+/*
+*++
+*  Name:
+c     astMapKey
+f     AST_MAPKEY
+
+*  Purpose:
+*     Get the key at a given index within the KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "keymap.h"
+c     const char *astMapKey( AstKeyMap *this, int index )
+f     RESULT = AST_MAPKEY( THIS, INDEX, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function returns a string holding the key for the entry with
+*     the given index within the KeyMap.
+*
+*     This function is intended primarily as a means of iterating round all
+*     the elements in a KeyMap. For this purpose, the number of entries in
+*     the KeyMap should first be found using
+c     astMapSize
+f     AST_MAPSIZE
+*     and this function should then be called in a loop, with the index
+*     value going from
+c     zero to one less than the size of the KeyMap.
+f     one to the size of the KeyMap.
+*     The index associated with a given entry is determined by the SortBy
+*     attribute.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     index
+f     INDEX = INTEGER (Given)
+*        The index into the KeyMap. The first entry has index
+c        zero, and the last has index "size-1", where "size" is the value
+c        returned by the astMapSize function.
+f        one, and the last has index SIZE, the value returned by the
+f        AST_MAPSIZE function.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapKey()
+c        A pointer to a null-terminated string containing the key.
+f     AST_MAPKEY = CHARACTER * ( AST__SZCHR )
+f        The key value.
+
+*  Notes:
+c     - The returned pointer is guaranteed to remain valid and the
+c     string to which it points will not be over-written for a total
+c     of 50 successive invocations of this function. After this, the
+c     memory containing the string may be re-used, so a copy of the
+c     string should be made if it is needed for longer than this.
+c     - A NULL pointer will be returned if this function is invoked
+c     with the AST error status set, or if it should fail for any
+c     reason.
+f     - A blank string will be returned if this function is invoked
+f     with STATUS set to an error value, or if it should fail for any
+f     reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   const char *result;           /* Pointer value to return */
+   const char *value;            /* Pointer to key value */
+   int i;                        /* Loop counter for initialisation */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* If the "mapkey_strings" array has not been initialised, fill it with
+   NULL pointers. */
+   if ( !mapkey_init ) {
+      mapkey_init = 1;
+      for ( i = 0; i < AST__KEYMAP_MAPKEY_MAX_STRINGS; i++ ) mapkey_strings[ i ] = NULL;
+   }
+
+/* Obtain a pointer to the required key value. */
+   value = GetKey( this, index, status );
+
+/* If OK, store a copy of the resulting string in dynamically
+   allocated memory, putting a pointer to the copy into the next
+   element of the "mapkey_strings" array.  (This process also de-allocates
+   any previously allocated memory pointed at by this "mapkey_strings"
+   element, so the earlier string is effectively replaced by the new
+   one.) */
+   if ( astOK ) {
+      astBeginPM;
+      mapkey_strings[ mapkey_istr ] = astStore( mapkey_strings[ mapkey_istr ], value,
+                                  strlen( value ) + (size_t) 1 );
+      astEndPM;
+
+/* If OK, return a pointer to the copy and increment "mapkey_istr" to use the
+   next element of "mapkey_strings" on the next invocation. Recycle "mapkey_istr" to
+   zero when all elements have been used. */
+      if ( astOK ) {
+         result = mapkey_strings[ mapkey_istr++ ];
+         if ( mapkey_istr == ( AST__KEYMAP_MAPKEY_MAX_STRINGS - 1 ) ) mapkey_istr = 0;
+      }
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+/*
+*++
+*  Name:
+c     astMapPut0<X>
+f     AST_MAPPUT0<X>
+
+*  Purpose:
+*     Add a scalar value to a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "ast.h"
+c     void astMapPut0<X>( AstKeyMap *this, const char *key, <X>type value,
+c                         const char *comment );
+f     CALL AST_MAPPUT0<X>( THIS, KEY, VALUE, COMMENT, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+c     This is a set of functions
+f     This is a set of routine
+*     for adding scalar values to a KeyMap. You should use a
+c     function
+f     routine
+*     which matches the data type of the data you wish to add to the KeyMap
+*     by replacing <X> in the generic
+c     function name astMapPut0<X>
+f     routine name AST_MAPPUT0<X>
+*     by an appropriate 1-character type code (see the "Data Type Codes"
+*     section below for the code appropriate to each supported data type).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap in which to store the supplied value.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        A character string to be stored with the value, which can later
+*        be used to identify the value. Trailing spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+c     value
+f     VALUE = <X>type (Given)
+*        The value to be stored. The data type of this value should match the
+*        1-character type code appended to the
+c        function name (e.g. if you are using astMapPut0A, the type of this
+c        value should be "pointer to AstObject").
+f        routine name (e.g. if you are using AST_MAPPUT0A, the type of this
+f        value should be "integer pointer for an AstObject").
+c     comment
+f     COMMENT = CHARACTER * ( * ) (Given)
+f        A comment string to be stored with the value.
+c        A pointer to a null-terminated comment string to be stored with the
+c        value. A NULL pointer may be supplied, in which case no comment is
+c        stored.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - If the supplied key is already in use in the KeyMap, the new value
+*     will replace the old value.
+*     - If the stored value is an AST Object pointer, the Object's reference
+*     count is incremented by this call. Any subsequent changes made to
+*     the Object using the returned pointer will be reflected in any
+*     any other active pointers for the Object, including any obtained
+*     later using
+c     astMapget0A.
+f     AST_MAPGET0A.
+*     The reference count for the Object will be decremented when the
+*     KeyMap is destroyed, or the entry is removed or over-written with a
+*     different pointer.
+
+*  Data Type Codes:
+*     To select the appropriate
+c     function, you should replace <X> in the generic function name astMapPut0<X>
+f     routine, you should replace <X> in the generic routine name AST_MAPPUT0<X>
+*     with a 1-character data type code, so as to match the data type <X>type
+*     of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - I: int
+c     - C: "const" pointer to null terminated character string
+c     - A: Pointer to AstObject
+c     - P: Generic "void *" pointer
+c     - S: short int
+c     - B: unsigned byte (i.e. unsigned char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - C: CHARACTER
+f     - A: INTEGER used to identify an AstObject
+f     - S: INTEGER*2 (short integer)
+f     - B: Unsigned byte
+*
+c     For example, astMapPut0D would be used to store a "double" value,
+c     while astMapPut0I would be used to store an "int", etc.
+f     For example, AST_MAPPUT0D would be used to store a DOUBLE PRECISION value,
+f     while AST_MAPPUT0I would be used to store an INTEGER, etc.
+c
+c     Note that KeyMaps containing generic "void *" pointers cannot be
+c     written out using astShow or astWrite. An error will be reported if
+c     this is attempted.
+*--
+*/
+/* Define a macro to implement the function for a specific data type. */
+#define MAKE_MAPPUT0(X,Xtype,Itype,ValExp) \
+static void MapPut0##X( AstKeyMap *this, const char *skey, Xtype value, \
+                        const char *comment, int *status ) { \
+\
+/* Local Variables: */ \
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */ \
+   AstMapEntry *oldent;    /* Pointer to existing MapEntry */ \
+   Entry0##X *entry;       /* Structure holding the data for the new Entry */ \
+   const char *key;        /* Pointer to key string to use */ \
+   char *p;                /* Pointer to next key character */ \
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */ \
+   int itab;               /* Index of hash table element to use */ \
+   int keylen;             /* Length of supplied key string */ \
+   int keymember;          /* Identifier for existing key */ \
+   int there;              /* Did the entry already exist in the KeyMap? */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Perform any necessary checks on the supplied value to be stored. */ \
+   CHECK_##X \
+\
+/* Convert the supplied key to upper case if required. */ \
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapPut0" #X, \
+                     status ); \
+\
+/* Allocate memory for the new MapEntry. */ \
+   entry = astMalloc( sizeof( Entry0##X ) ); \
+   if( astOK ) { \
+\
+/* Initialise the new structure.*/ \
+      mapentry = (AstMapEntry *) entry; \
+      InitMapEntry( mapentry, Itype, 0, status ); \
+\
+/* Now store the new values. */ \
+      keylen = strlen( key ); \
+      mapentry->key = astStore( NULL, key, keylen + 1 ); \
+      if( comment ) mapentry->comment = astStore( NULL, comment, strlen( comment ) + 1 ); \
+      mapentry->defined = 1; \
+      entry->value = ValExp; \
+\
+/* Terminate the key string to exclude any trailing spaces. */ \
+      if( astOK ) { \
+         p = (char *) mapentry->key + keylen; \
+         while( --p >= mapentry->key ) { \
+            if( *p == ' ' ) { \
+               *p = 0; \
+            } else { \
+               break; \
+            } \
+         } \
+      } \
+\
+/* Use the hash function to determine the element of the hash table in \
+   which to store the new entry. */ \
+      itab = HashFun( mapentry->key, this->mapsize - 1, &(mapentry->hash), status ); \
+\
+/* Remove any existing entry with the given key from the table element. \
+   First save the key identifier. */ \
+      oldent = RemoveTableEntry( this, itab, mapentry->key, status ); \
+      if( oldent ) { \
+         keymember = oldent->keymember; \
+         oldent = FreeMapEntry( oldent, status ); \
+         there = 1; \
+      } else { \
+         keymember = -1; \
+         there = 0; \
+      } \
+\
+/* If the KeyMap is locked we report an error if an attempt is made to add a value for \
+   a new key. */ \
+      if( !there && astGetMapLocked( this ) ) { \
+         astError( AST__BADKEY, "astMapPut0" #X "(%s): Failed to add item \"%s\" to a KeyMap: " \
+                   "\"%s\" is not a known item.", status, astGetClass( this ), key, key ); \
+      } \
+\
+/* If all has gone OK, store the new entry at the head of the linked list \
+   associated with the selected table entry. */ \
+      if( astOK ) { \
+         mapentry = AddTableEntry( this, itab, mapentry, keymember, status ); \
+\
+/* If anything went wrong, try to delete the new entry. */ \
+      } else { \
+         mapentry = FreeMapEntry( mapentry, status ); \
+      } \
+   } \
+}
+
+/* Define macros which perform any necessary checks on the supplied value
+   to be stored. For Object entries, check that we are not adding a KeyMap
+   which already contains "this". This avoids circular dependencies.
+   Other types do not need any checks. */
+#define CHECK_A CheckCircle( this, value, "astMapPut0A", status );
+#define CHECK_I
+#define CHECK_D
+#define CHECK_F
+#define CHECK_C
+#define CHECK_P
+#define CHECK_S
+#define CHECK_B
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+MAKE_MAPPUT0(I,int,AST__INTTYPE,value)
+MAKE_MAPPUT0(D,double,AST__DOUBLETYPE,value)
+MAKE_MAPPUT0(F,float,AST__FLOATTYPE,value)
+MAKE_MAPPUT0(C,const char *,AST__STRINGTYPE,astStore(NULL,value,strlen(value)+1))
+MAKE_MAPPUT0(A,AstObject *,AST__OBJECTTYPE,(value?astClone(value):NULL))
+MAKE_MAPPUT0(P,void *,AST__POINTERTYPE,value)
+MAKE_MAPPUT0(S,short int,AST__SINTTYPE,value)
+MAKE_MAPPUT0(B,unsigned char,AST__BYTETYPE,value)
+
+/* Undefine the macro. */
+#undef MAKE_MAPPUT0
+#undef CHECK_A
+#undef CHECK_I
+#undef CHECK_S
+#undef CHECK_D
+#undef CHECK_F
+#undef CHECK_C
+#undef CHECK_P
+#undef CHECK_B
+
+/*
+*++
+*  Name:
+c     astMapPut1<X>
+f     AST_MAPPUT1<X>
+
+*  Purpose:
+*     Add a vector value to a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "ast.h"
+c     void astMapPut1<X>( AstKeyMap *this, const char *key, int size,
+c                         const <X>type value[], const char *comment );
+f     CALL AST_MAPPUT1<X>( THIS, KEY, SIZE, VALUE, COMMENT, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+c     This is a set of functions
+f     This is a set of routine
+*     for adding vector values to a KeyMap. You should use a
+c     function
+f     routine
+*     which matches the data type of the data you wish to add to the KeyMap
+*     by replacing <X> in the generic
+c     function name astMapPut1<X>
+f     routine name AST_MAPPUT1<X>
+*     by an appropriate 1-character type code (see the "Data Type Codes"
+*     section below for the code appropriate to each supported data type).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap in which to store the supplied values.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        A character string to be stored with the values, which can later
+*        be used to identify the values. Trailing spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+c     size
+f     SIZE = INTEGER (Given)
+*        The number of elements in the supplied array of values.
+c     value
+f     VALUE( * ) = <X>type (Given)
+*        The array of values to be stored. The data type of this value should
+*        match the 1-character type code appended to the
+c        function name (e.g. if you are using astMapPut1A, the type of this
+c        value should be "array of pointers to AstObject").
+f        routine name (e.g. if you are using AST_MAPPUT1A, the type of this
+f        value should be "integer pointer for an AstObject)".
+c     comment
+f     COMMENT = CHARACTER * ( * ) (Given)
+f        A comment string to be stored with the values.
+c        A pointer to a null-terminated comment string to be stored with the
+c        values. A NULL pointer may be supplied, in which case no comment is
+c        stored.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - If the supplied key is already in use in the KeyMap, the new values
+*     will replace the old values.
+
+*  Data Type Codes:
+*     To select the appropriate
+c     function, you should replace <X> in the generic function name astMapPut1<X>
+f     routine, you should replace <X> in the generic routine name AST_MAPPUT1<X>
+*     with a 1-character data type code, so as to match the data type <X>type
+*     of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - I: int
+c     - C: "const" pointer to null terminated character string
+c     - A: Pointer to AstObject
+c     - P: Generic "void *" pointer
+c     - S: short int
+c     - B: Unsigned byte (i.e. char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - C: CHARACTER
+f     - A: INTEGER used to identify an AstObject
+f     - S: INTEGER*2 (short integer)
+f     - B: Unsigned byte
+*
+c     For example, astMapPut1D would be used to store "double" values,
+c     while astMapPut1I would be used to store "int", etc.
+f     For example, AST_MAPPUT1D would be used to store DOUBLE PRECISION values,
+f     while AST_MAPPUT1I would be used to store INTEGER, etc.
+c
+c     Note that KeyMaps containing generic "void *" pointers cannot be
+c     written out using astShow or astWrite. An error will be reported if
+c     this is attempted.
+*--
+*/
+/* Define a macro to implement the function for a specific data type. */
+#define MAKE_MAPPUT1(X,Xtype,Itype,ValExp) \
+static void MapPut1##X( AstKeyMap *this, const char *skey, int size, \
+                        Xtype value[], const char *comment, \
+                        int *status ) { \
+\
+/* Local Variables: */ \
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */ \
+   AstMapEntry *oldent;    /* Pointer to existing MapEntry */ \
+   Entry1##X *entry;       /* Structure holding the data for the new Entry */ \
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */ \
+   const char *key;        /* Pointer to key string to use */ \
+   char *p;                /* Pointer to next key character */ \
+   int itab;               /* Index of hash table element to use */ \
+   int i;                  /* Loop count */ \
+   int keylen;             /* Length of supplied key string */ \
+   int keymember;          /* Identifier for existing key */ \
+   int there;              /* Did the entry already exist in the KeyMap? */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Perform any necessary checks on the supplied value to be stored. */ \
+   CHECK_##X \
+\
+/* Convert the supplied key to upper case if required. */ \
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapPut1" #X, \
+                     status ); \
+\
+/* Allocate memory for the new MapEntry. */ \
+   entry = astMalloc( sizeof( Entry1##X ) ); \
+   if( astOK ) { \
+\
+/* Initialise the new structure.*/ \
+      mapentry = (AstMapEntry *) entry; \
+      InitMapEntry( mapentry, Itype, size, status ); \
+\
+/* Now store the new values. */ \
+      keylen = strlen( key ); \
+      mapentry->key = astStore( NULL, key, keylen + 1 ); \
+      if( comment ) mapentry->comment = astStore( NULL, comment, strlen( comment ) + 1 ); \
+      mapentry->defined = 1; \
+      entry->value = astMalloc( sizeof( Xtype )*(size_t)size ); \
+\
+      if( astOK ) { \
+         for( i = 0; i < size; i++ ) { \
+            entry->value[ i ] = ValExp; \
+         } \
+\
+/* Terminate the key string to exclude any trailing spaces. */ \
+         p = (char *) mapentry->key + keylen; \
+         while( --p >= mapentry->key ) { \
+            if( *p == ' ' ) { \
+               *p = 0; \
+            } else { \
+               break; \
+            } \
+         } \
+      } \
+\
+/* Use the hash function to determine the element of the hash table in \
+   which to store the new entry. */ \
+      itab = HashFun( mapentry->key, this->mapsize - 1, &(mapentry->hash), status ); \
+\
+/* Remove any existing entry with the given key from the table element. \
+   First save the key identifier. */ \
+      oldent = RemoveTableEntry( this, itab, mapentry->key, status ); \
+      if( oldent ) { \
+         keymember = oldent->keymember; \
+         oldent = FreeMapEntry( oldent, status ); \
+         there = 1; \
+      } else { \
+         keymember = -1; \
+         there = 0; \
+      } \
+\
+/* If the KeyMap is locked we report an error if an attempt is made to add a value for \
+   a new key. */ \
+      if( !there && astGetMapLocked( this ) ) { \
+         astError( AST__BADKEY, "astMapPut1" #X "(%s): Failed to add item \"%s\" to a KeyMap: " \
+                   "\"%s\" is not a known item.", status, astGetClass( this ), key, key ); \
+      } \
+\
+/* If all has gone OK, store the new entry at the head of the linked list \
+   associated with the selected table entry. */ \
+      if( astOK ) { \
+         mapentry = AddTableEntry( this, itab, mapentry, keymember, status ); \
+\
+/* If anything went wrong, try to delete the new entry. */ \
+      } else { \
+         mapentry = FreeMapEntry( mapentry, status ); \
+      } \
+   } \
+}
+
+/* Define macros which perform any necessary checks on the supplied values
+   to be stored. For Object entries, check that we are not adding a KeyMap
+   which already contains "this". This avoids circular dependencies.
+   Other types do not need any checks. */
+#define CHECK_A \
+for( i = 0; i < size; i++ ) { \
+   CheckCircle( this, value[ i ], "astMapPut1A", status ); \
+}
+#define CHECK_I
+#define CHECK_S
+#define CHECK_B
+#define CHECK_D
+#define CHECK_F
+#define CHECK_C
+#define CHECK_P
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+MAKE_MAPPUT1(D,const double,AST__DOUBLETYPE,value[i])
+MAKE_MAPPUT1(F,const float,AST__FLOATTYPE,value[i])
+MAKE_MAPPUT1(I,const int,AST__INTTYPE,value[i])
+MAKE_MAPPUT1(C,const char *const,AST__STRINGTYPE,astStore(NULL,value[i],strlen(value[i])+1))
+MAKE_MAPPUT1(A,AstObject *const,AST__OBJECTTYPE,(value[i]?astClone(value[i]):NULL))
+MAKE_MAPPUT1(P,void *const,AST__POINTERTYPE,value[i])
+MAKE_MAPPUT1(S,const short int,AST__SINTTYPE,value[i])
+MAKE_MAPPUT1(B,const unsigned char,AST__BYTETYPE,value[i])
+
+/* Undefine the macro. */
+#undef MAKE_MAPPUT1
+#undef CHECK_A
+#undef CHECK_I
+#undef CHECK_B
+#undef CHECK_S
+#undef CHECK_D
+#undef CHECK_F
+#undef CHECK_C
+#undef CHECK_P
+
+void astMapPut1AId_( AstKeyMap *this, const char *skey, int size,
+                     AstObject *const value[], const char *comment,
+                     int *status ) {
+/*
+*  Name:
+*     astMapPut1AId_
+
+*  Purpose:
+*     Add a vector of AstObject pointers to a KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ast.h"
+*     void astMapPut1A( AstKeyMap *this, const char *key, int size,
+*                       AstObject *const value[], const char *comment )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This is the public implementation of the astMapPut1A function
+*     It is identical to astMapPut1A_ except that ID values are supplied
+*     via the "value" parameter instead of a true C pointers.
+
+*  Parameters:
+*     (see astMapPut1<X>)
+
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */
+   AstMapEntry *oldent;    /* Pointer to existing MapEntry */
+   AstObject *op;          /* Object pointer */
+   Entry1A *entry;         /* Structure holding the data for the new Entry */
+   char *p;                /* Pointer to next key character */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */ \
+   const char *key;        /* Pointer to key string to use */ \
+   int i;                  /* Loop count */
+   int itab;               /* Index of hash table element to use */
+   int keylen;             /* Length of supplied key string */
+   int keymember;          /* Identifier for existing key */
+   int there;              /* Did the entry already exist in the KeyMap? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapPut1A",
+                     status );
+
+/* Allocate memory for the new MapEntry. */
+   entry = astMalloc( sizeof( Entry1A ) );
+   if( astOK ) {
+
+/* Initialise the new structure.*/
+      mapentry = (AstMapEntry *) entry;
+      InitMapEntry( mapentry, AST__OBJECTTYPE, size, status );
+
+/* Now store the new values. */
+      keylen = strlen( key );
+      mapentry->key = astStore( NULL, key, keylen + 1 );
+      if( comment ) mapentry->comment = astStore( NULL, comment, strlen( comment ) + 1 );
+      mapentry->defined = 1;
+      entry->value = astMalloc( sizeof( AstObject * )*(size_t)size );
+
+      if( astOK ) {
+         for( i = 0; i < size; i++ ) {
+            op = value[ i ] ? astMakePointer( value[ i ] ) : NULL;
+            entry->value[ i ] = op ? astClone( op ) : NULL;
+         }
+
+/* Terminate the key string to exclude any trailing spaces. */ \
+         p = (char *) mapentry->key + keylen;
+         while( --p >= mapentry->key ) {
+            if( *p == ' ' ) {
+               *p = 0;
+            } else {
+               break;
+            }
+         }
+      }
+
+/* Use the hash function to determine the element of the hash table in
+   which to store the new entry. */
+      itab = HashFun( mapentry->key, this->mapsize - 1, &(mapentry->hash), status );
+
+/* Remove any existing entry with the given key from the table element. */
+      oldent = RemoveTableEntry( this, itab, mapentry->key, status );
+      if( oldent ) {
+         keymember = oldent->keymember;
+         oldent = FreeMapEntry( oldent, status );
+         there = 1;
+      } else {
+         there = 0;
+         keymember = -1;
+      }
+
+/* If the KeyMap is locked we report an error if an attempt is made to add a value for
+   a new key. */
+      if( !there && astGetMapLocked( this ) ) {
+         astError( AST__BADKEY, "astMapPut1A(%s): Failed to add item \"%s\" to a KeyMap: "
+                   "\"%s\" is not a known item.", status, astGetClass( this ), key, key );
+      }
+
+/* If all has gone OK, store the new entry at the head of the linked list
+   associated with the selected table entry. */
+      if( astOK ) {
+         mapentry = AddTableEntry( this, itab, mapentry, keymember, status );
+
+/* If anything went wrong, try to delete the new entry. */
+      } else {
+         mapentry = FreeMapEntry( mapentry, status );
+      }
+   }
+}
+
+static void MapPutU( AstKeyMap *this, const char *skey, const char *comment,
+                     int *status ) {
+/*
+*++
+*  Name:
+c     astMapPutU
+f     AST_MAPPUTU
+
+*  Purpose:
+*     Add an entry to a KeyMap with an undefined value.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "ast.h"
+c     void astMapPutU( AstKeyMap *this, const char *key, const char *comment );
+f     CALL AST_MAPPUTU( THIS, KEY, COMMENT, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+c     This function
+f     This routine
+*     adds a new entry to a KeyMap, but no value is stored with the
+*     entry. The entry therefore has a special data type represented by
+*     symbolic constant AST__UNDEFTYPE.
+*
+*     An example use is to add entries with undefined values to a KeyMap
+*     prior to locking them with the MapLocked attribute. Such entries
+*     can act as placeholders for values that can be added to the KeyMap
+*     later.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap in which to store the supplied value.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        A character string to be stored with the value, which can later
+*        be used to identify the value. Trailing spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+c     comment
+f     COMMENT = CHARACTER * ( * ) (Given)
+f        A comment string to be stored with the value.
+c        A pointer to a null-terminated comment string to be stored with the
+c        value. A NULL pointer may be supplied, in which case no comment is
+c        stored.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - If the supplied key is already in use in the KeyMap, the value
+*     associated with the key will be removed.
+
+*--
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */
+   AstMapEntry *oldent;    /* Pointer to existing MapEntry */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   const char *key;        /* Pointer to key string to use */
+   char *p;                /* Pointer to next key character */
+   int itab;               /* Index of hash table element to use */
+   int keylen;             /* Length of supplied key string */
+   int keymember;          /* Identifier for existing key */
+   int there;              /* Did the entry already exist in the KeyMap? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapPutU",
+                     status );
+
+/* Allocate memory for the new MapEntry. */
+   mapentry = astMalloc( sizeof( AstMapEntry ) );
+   if( astOK ) {
+
+/* Initialise the new structure.*/
+      InitMapEntry( mapentry, AST__UNDEFTYPE, 0, status );
+
+/* Now store the new values. */
+      keylen = strlen( key );
+      mapentry->key = astStore( NULL, key, keylen + 1 );
+      if( comment ) mapentry->comment = astStore( NULL, comment, strlen( comment ) + 1 );
+      mapentry->defined = 0;
+
+/* Terminate the key string to exclude any trailing spaces. */
+      if( astOK ) {
+         p = (char *) mapentry->key + keylen;
+         while( --p >= mapentry->key ) {
+            if( *p == ' ' ) {
+               *p = 0;
+            } else {
+               break;
+            }
+         }
+      }
+
+/* Use the hash function to determine the element of the hash table in
+   which to store the new entry. */
+      itab = HashFun( mapentry->key, this->mapsize - 1, &(mapentry->hash), status );
+
+/* Remove any existing entry with the given key from the table element. */
+      oldent = RemoveTableEntry( this, itab, mapentry->key, status );
+      if( oldent ) {
+         keymember = oldent->keymember;
+         oldent = FreeMapEntry( oldent, status );
+         there = 1;
+      } else {
+         keymember = -1;
+         there = 0;
+      }
+
+/* If the KeyMap is locked we report an error if an attempt is made to add a value for
+   a new key. */
+      if( !there && astGetMapLocked( this ) ) {
+         astError( AST__BADKEY, "astMapPutU(%s): Failed to add item \"%s\" to a KeyMap: "
+                   "\"%s\" is not a known item.", status, astGetClass( this ), key, key );
+      }
+
+/* If all has gone OK, store the new entry at the head of the linked list
+   associated with the selected table entry. */
+      if( astOK ) {
+         mapentry = AddTableEntry( this, itab, mapentry, keymember, status );
+
+/* If anything went wrong, try to delete the new entry. */
+      } else {
+         mapentry = FreeMapEntry( mapentry, status );
+      }
+   }
+}
+
+/*
+*++
+*  Name:
+c     astMapGet0<X>
+f     AST_MAPGET0<X>
+
+*  Purpose:
+*     Get a scalar value from a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "ast.h"
+c     int astMapGet0<X>( AstKeyMap *this, const char *key, <X>type *value );
+f     RESULT = AST_MAPGET0<X>( THIS, KEY, VALUE, STATUS )
+
+f     RESULT = AST_MAPGET0C( THIS, KEY, VALUE, L, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This is a set of functions for retrieving a scalar value from a KeyMap.
+*     You should replace <X> in the generic function name
+c     astMapGet0<X>
+f     AST_MAPGET0<X>
+*     by an appropriate 1-character type code (see the "Data Type Codes"
+*     section below for the code appropriate to each supported data type).
+*     The stored value is converted to the data type indiced by <X>
+*     before being returned (an error is reported if it is not possible to
+*     convert the stored value to the requested data type).
+f     Note, the version of this function which returns character strings,
+f     AST_MAPGET0C, has an extra parameter in which is returned the number
+f     of characters written into the supplied CHARACTER variable.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the value to be retrieved. Trailing
+*        spaces are ignored. The supplied string is converted to upper
+*        case before use if the KeyCase attribute is currently set to zero.
+c     value
+f     VALUE = <X>type (Returned)
+c        A pointer to a buffer in which to return the requested value.
+f        The requested value.
+*        If the requested key is not found, or if it is found but has an
+*        undefined value (see
+c        astMapPutU),
+f        AST_MAPPUTU),
+*        then the contents of the
+*        buffer on entry to this function will be unchanged on exit.
+c        For pointer types ("A" and "C"), the buffer should be a suitable
+c        pointer, and the address of this pointer should be supplied as the
+c        "value" parameter.
+f     L = INTEGER (Returned)
+f        This parameter is only present in the interface for the AST_MAPGET0C
+f        function. It is returned holding the number of characters
+f        written into the CHARACTER variable supplied for parameter VALUE.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapGet0<X>()
+f     AST_MAPGET0<X> = LOGICAL
+c        A non-zero value
+f        .TRUE.
+*        is returned if the requested key name was found, and does not have
+*        an undefined value (see
+c        astMapPutU). Zero
+f        AST_MAPPUTU). .FALSE.
+*        is returned otherwise.
+
+*  Notes:
+*     - No error is reported if the requested key cannot be found in the
+*     given KeyMap, but a
+c     zero
+f     .FALSE.
+*     value will be returned as the function value. The supplied buffer
+*     will be returned unchanged.
+*     - If the stored value is a vector value, then the first value in
+*     the vector will be returned.
+c     - A string pointer returned by astMapGet0C is guaranteed to remain valid
+c     and the string to which it points will not be over-written for a
+c     total of 50 successive invocations of this function. After this,
+c     the memory containing the string may be re-used, so a copy of
+c     the string should be made if it is needed for longer than this. The
+c     calling code should never attempt to free the returned pointer
+c     (for instance, using astFree).
+*     - If the returned value is an AST Object pointer, the Object's reference
+*     count is incremented by this call. Any subsequent changes made to
+*     the Object using the returned pointer will be reflected in any
+*     any other active pointers for the Object. The returned pointer
+*     should be annulled using
+c     astAnnul
+f     AST_ANNUL
+*     when it is no longer needed.
+
+*  Data Type Codes:
+*     To select the appropriate
+c     function, you should replace <X> in the generic function name astMapGet0<X>
+f     routine, you should replace <X> in the generic routine name AST_MAPGET0<X>
+*     with a 1-character data type code, so as to match the data type <X>type
+*     of the data you are processing, as follows:
+c     - F: float
+c     - D: double
+c     - I: int
+c     - C: "const" pointer to null terminated character string
+c     - A: Pointer to AstObject
+c     - P: Generic "void *" pointer
+c     - S: short int
+c     - B: Unsigned byte (i.e. word)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - C: CHARACTER
+f     - A: INTEGER used to identify an AstObject
+f     - S: INTEGER*2 (short integer)
+f     - B: Unsigned byte
+*
+c     For example, astMapGet0D would be used to get a "double" value,
+c     while astMapGet0I would be used to get an "int", etc.
+f     For example, AST_MAPGET0D would be used to get a DOUBLE PRECISION value,
+f     while AST_MAPGET0I would be used to get an INTEGER, etc.
+*--
+*/
+/* Define a macro to implement the function for a specific data type. */
+#define MAKE_MAPGET0(X,Xtype,Itype) \
+static int MapGet0##X( AstKeyMap *this, const char *skey, Xtype *value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */ \
+   const char *key;        /* Pointer to key string to use */ \
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */ \
+   int itab;               /* Index of hash table element to use */ \
+   int raw_type;           /* Data type of stored value */ \
+   int result;             /* Returned flag */ \
+   unsigned long hash;     /* Full width hash value */ \
+   void *raw;              /* Pointer to stored value */ \
+\
+/* Initialise */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Convert the supplied key to upper case if required. */ \
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapGet0" #X, \
+                     status ); \
+\
+/* Use the hash function to determine the element of the hash table in \
+   which the key will be stored. */ \
+   itab = HashFun( key, this->mapsize - 1, &hash, status ); \
+\
+/* Search the relevent table entry for the required MapEntry. */ \
+   mapentry = SearchTableEntry( this, itab, key, status ); \
+\
+/* Skip rest if the key was not found. */ \
+   if( mapentry ) { \
+\
+/* Get the address of the raw value, and its data type. */ \
+      raw_type = mapentry->type; \
+      if( raw_type == AST__INTTYPE ){ \
+         if( mapentry->nel == 0 ) { \
+            raw = &( ((Entry0I *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1I *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__SINTTYPE ){ \
+         if( mapentry->nel == 0 ) { \
+            raw = &( ((Entry0S *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1S *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__BYTETYPE ){ \
+         if( mapentry->nel == 0 ) { \
+            raw = &( ((Entry0B *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1B *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__DOUBLETYPE ){ \
+         if( mapentry->nel == 0 ) { \
+            raw = &( ((Entry0D *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1D *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__FLOATTYPE ){ \
+         if( mapentry->nel == 0 ) { \
+            raw = &( ((Entry0F *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1F *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__POINTERTYPE ){ \
+         if( mapentry->nel == 0 ) { \
+            raw = &( ((Entry0P *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1P *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__STRINGTYPE ){ \
+         if( mapentry->nel == 0 ) { \
+            raw = &( ((Entry0C *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1C *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__OBJECTTYPE ){ \
+         if( mapentry->nel == 0 ) { \
+            raw = &( ((Entry0A *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1A *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__UNDEFTYPE ){ \
+         raw = NULL; \
+\
+      } else { \
+         raw = NULL; \
+         astError( AST__INTER, "astMapGet0<X>(KeyMap): Illegal map entry data " \
+                   "type %d encountered (internal AST programming error).", status, \
+                   raw_type ); \
+      } \
+\
+/* Convert the value, storing the result the supplied buffer. Report an \
+   error if conversion is not possible. */ \
+      if( !raw ) { \
+         result = 0; \
+\
+      } else if( !ConvertValue( raw, raw_type, value, Itype, status ) && astOK ){ \
+         astError( AST__MPGER, "astMapGet0" #X "(%s): The value of KeyMap key " \
+                   "\"%s\" cannot be read using the requested data " \
+                   "type.", status,astGetClass( this ), key ); \
+\
+      } else { \
+         result = 1; \
+      } \
+\
+/* If the KeyError attribute is non-zero, report an error if the key is not \
+   found */ \
+   } else if( astGetKeyError( this ) && astOK ) { \
+      astError( AST__MPKER, "astMapGet0" #X "(%s): No value was found for " \
+                "%s in the supplied KeyMap.", status, astGetClass( this ), \
+                key ); \
+   } \
+\
+/* If an error occurred, return zero. */ \
+   if( !astOK ) result = 0; \
+\
+/* Return the result.*/ \
+   return result; \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+MAKE_MAPGET0(I,int,AST__INTTYPE)
+MAKE_MAPGET0(D,double,AST__DOUBLETYPE)
+MAKE_MAPGET0(F,float,AST__FLOATTYPE)
+MAKE_MAPGET0(C,const char *,AST__STRINGTYPE)
+MAKE_MAPGET0(A,AstObject *,AST__OBJECTTYPE)
+MAKE_MAPGET0(P,void *,AST__POINTERTYPE)
+MAKE_MAPGET0(S,short int,AST__SINTTYPE)
+MAKE_MAPGET0(B,unsigned char,AST__BYTETYPE)
+
+/* Undefine the macro. */
+#undef MAKE_MAPGET0
+
+int astMapGet0AId_( AstKeyMap *this, const char *skey, AstObject **value, int *status ) {
+/*
+*  Name:
+*     astMapGet0AId_
+
+*  Purpose:
+*     Get a scalar AstObject pointer from a KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ast.h"
+*     int astMapGet0A( AstKeyMap *this, const char *key, AstObject **value )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This is the public implementation of the astMapGet0A function
+*     It is identical to astMapGet0A_ except that an ID value is returned
+*     via the "value" parameter instead of a true C pointer. This is required
+*     because this conversion cannot be performed by the macro that invokes
+*     the function.
+
+*  Parameters:
+*     (see astMapGet0<X>)
+
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */ \
+   const char *key;        /* Pointer to key string to use */ \
+   int itab;               /* Index of hash table element to use */
+   int raw_type;           /* Data type of stored value */
+   int result;             /* Returned flag */
+   unsigned long hash;     /* Full width hash value */
+   void *raw;              /* Pointer to stored value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapGet0A",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. */
+   mapentry = SearchTableEntry( this, itab, key, status );
+
+/* Skip rest if the key was not found. */
+   if( mapentry ) {
+
+/* Get the address of the raw value, and its data type. */
+      raw_type = mapentry->type;
+      if( raw_type == AST__INTTYPE ){
+         if( mapentry->nel == 0 ) {
+            raw = &( ((Entry0I *)mapentry)->value );
+         } else {
+            raw = ((Entry1I *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__POINTERTYPE ){
+         if( mapentry->nel == 0 ) {
+            raw = &( ((Entry0P *)mapentry)->value );
+         } else {
+            raw = ((Entry1P *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__SINTTYPE ){
+         if( mapentry->nel == 0 ) {
+            raw = &( ((Entry0S *)mapentry)->value );
+         } else {
+            raw = ((Entry1S *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__BYTETYPE ){
+         if( mapentry->nel == 0 ) {
+            raw = &( ((Entry0B *)mapentry)->value );
+         } else {
+            raw = ((Entry1B *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__DOUBLETYPE ){
+         if( mapentry->nel == 0 ) {
+            raw = &( ((Entry0D *)mapentry)->value );
+         } else {
+            raw = ((Entry1D *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__FLOATTYPE ){
+         if( mapentry->nel == 0 ) {
+            raw = &( ((Entry0F *)mapentry)->value );
+         } else {
+            raw = ((Entry1F *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__STRINGTYPE ){
+         if( mapentry->nel == 0 ) {
+            raw = &( ((Entry0C *)mapentry)->value );
+         } else {
+            raw = ((Entry1C *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__OBJECTTYPE ){
+         if( mapentry->nel == 0 ) {
+            raw = &( ((Entry0A *)mapentry)->value );
+         } else {
+            raw = ((Entry1A *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__UNDEFTYPE ){
+         raw = NULL;
+
+      } else {
+         raw = NULL;
+         astError( AST__INTER, "astMapGet0<X>(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   raw_type );
+      }
+
+/* Convert the value, storing the result the supplied buffer. Report an
+   error if conversion is not possible. */
+      if( !raw ) {
+         result = 0;
+
+      } else if( !ConvertValue( raw, raw_type, value, AST__OBJECTTYPE, status ) && astOK ){
+         astError( AST__MPGER, "astMapGet0A(%s): The value of KeyMap key "
+                   "\"%s\" cannot be read using the requested data "
+                   "type.", status, astGetClass( this ), key );
+
+      } else {
+         result = 1;
+      }
+
+/* If the KeyError attribute is non-zero, report an error if the key is not
+   found */
+   } else if( astGetKeyError( this ) && astOK ) {
+      astError( AST__MPKER, "astMapGet0A(%s): No value was found for "
+                "%s in the supplied KeyMap.", status, astGetClass( this ),
+                key );
+   }
+
+/* If required, return an ID value for the Object. */
+   if( result && *value ) *value = astMakeId( *value );
+
+/* Return the result.*/
+   return result;
+}
+
+/*
+*++
+*  Name:
+c     astMapGet1<X>
+f     AST_MAPGET1<X>
+
+*  Purpose:
+*     Get a vector value from a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "ast.h"
+c     int astMapGet1<X>( AstKeyMap *this, const char *key, int mxval,
+c                        int *nval, <X>type *value )
+c     int astMapGet1C( AstKeyMap *this, const char *key, int l, int mxval,
+c                      int *nval, const char *value )
+f     RESULT = AST_MAPGET1<X>( THIS, KEY, MXVAL, NVAL, VALUE, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This is a set of functions for retrieving a vector value from a KeyMap.
+*     You should replace <X> in the generic function name
+c     astMapGet1<X>
+f     AST_MAPGET1<X>
+*     by an appropriate 1-character type code (see the "Data Type Codes"
+*     section below for the code appropriate to each supported data type).
+*     The stored value is converted to the data type indiced by <X>
+*     before being returned (an error is reported if it is not possible to
+*     convert the stored value to the requested data type).
+c     Note, the astMapGet1C function has an extra parameter "l" which
+c     specifies the maximum length of each string to be stored in the
+c     "value" buffer (see the "astMapGet1C" section below).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the value to be retrieved. Trailing
+*        spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+c     mxval
+f     MXVAL = INTEGER (Given)
+*        The number of elements in the
+c        "value" array.
+f        VALUE array.
+c     nval
+f     NVAL = INTEGER (Returned)
+c        The address of an integer in which to put the
+f        The
+*        number of elements stored in the
+c        "value" array.
+*        Any unused elements of the array are left unchanged.
+c     value
+f     VALUE( MXVAL ) = <X>type (Returned)
+c        A pointer to an array in which to return the requested values.
+f        The requested values.
+*        If the requested key is not found, or if it is found but has an
+*        undefined value (see
+c        astMapPutU),
+f        AST_MAPPUTU),
+*        then the contents of the
+*        buffer on entry to this function will be unchanged on exit.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapGet1<X>()
+f     AST_MAPGET1<X> = LOGICAL
+c        A non-zero value
+f        .TRUE.
+*        is returned if the requested key name was found, and does not have
+*        an undefined value (see
+c        astMapPutU). Zero
+f        AST_MAPPUTU). .FALSE.
+*        is returned otherwise.
+
+*  Notes:
+*     - No error is reported if the requested key cannot be found in the
+*     given KeyMap, but a
+c     zero
+f     .FALSE.
+*     value will be returned as the function value. The supplied array
+*     will be returned unchanged.
+*     - If the stored value is a scalar value, then the value will be
+*     returned in the first element of the supplied array, and
+c     "nval"
+f     NVAL
+*     will be returned set to 1.
+
+c  astMapGet1C:
+c     The "value" buffer supplied to the astMapGet1C function should be a
+c     pointer to a character array with "mxval*l" elements, where "l" is
+c     the maximum length of a string to be returned. The value of "l"
+c     should be supplied as an extra parameter following "key" when
+c     invoking astMapGet1C, and should include space for a terminating
+c     null character.
+
+*  Data Type Codes:
+*     To select the appropriate
+c     function, you should replace <X> in the generic function name astMapGet1<X>
+f     routine, you should replace <X> in the generic routine name AST_MAPGET1<X>
+*     with a 1-character data type code, so as to match the data type <X>type
+*     of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - I: int
+c     - C: "const" pointer to null terminated character string
+c     - A: Pointer to AstObject
+c     - P: Generic "void *" pointer
+c     - S: short int
+c     - B: Unsigned byte (i.e. char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - C: CHARACTER
+f     - A: INTEGER used to identify an AstObject
+f     - S: INTEGER*2 (short integer)
+f     - B: Unsigned byte
+*
+c     For example, astMapGet1D would be used to get "double" values, while
+c     astMapGet1I would be used to get "int" values, etc. For D or I, the
+c     supplied "value" parameter should be a pointer to an array of doubles
+c     or ints, with "mxval" elements. For C, the supplied "value" parameter
+c     should be a pointer to a character string with "mxval*l" elements.
+c     For A, the supplied "value" parameter should be a pointer to an
+c     array of AstObject pointers.
+f     For example, AST_MAPGET1D would be used to get DOUBLE PRECISION values,
+f     while AST_MAPGET1I would be used to get INTEGER values, etc.
+
+*--
+*/
+/* Define a macro to implement the function for a specific data type
+(excluding "C" since that needs an extra parameter). */
+#define MAKE_MAPGET1(X,Xtype,Itype) \
+static int MapGet1##X( AstKeyMap *this, const char *skey, int mxval, int *nval, Xtype *value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */ \
+   const char *key;        /* Pointer to key string to use */ \
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */ \
+   int i;                  /* Element index */ \
+   int itab;               /* Index of hash table element to use */ \
+   int nel;                /* Number of elements in raw vector */ \
+   int raw_type;           /* Data type of stored value */ \
+   int result;             /* Returned flag */ \
+   size_t raw_size;        /* Size of a single raw value */ \
+   unsigned long hash;     /* Full width hash value */ \
+   void *raw;              /* Pointer to stored value */ \
+\
+/* Initialise */ \
+   result = 0; \
+   *nval = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Convert the supplied key to upper case if required. */ \
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapGet1" #X, \
+                     status ); \
+\
+/* Use the hash function to determine the element of the hash table in \
+   which the key will be stored. */ \
+   itab = HashFun( key, this->mapsize - 1, &hash, status ); \
+\
+/* Search the relevent table entry for the required MapEntry. */ \
+   mapentry = SearchTableEntry( this, itab, key, status ); \
+\
+/* Skip rest if the key was not found. */ \
+   if( mapentry ) { \
+      result = 1; \
+\
+/* Get the address of the first raw value, and its data type. Also get \
+   the size of each element of the vector. */ \
+      nel = mapentry->nel; \
+      raw_type = mapentry->type; \
+      if( raw_type == AST__INTTYPE ){ \
+         raw_size = sizeof( int ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0I *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1I *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__DOUBLETYPE ){ \
+         raw_size = sizeof( double ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0D *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1D *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__SINTTYPE ){ \
+         raw_size = sizeof( short int ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0S *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1S *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__BYTETYPE ){ \
+         raw_size = sizeof( unsigned char ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0B *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1B *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__POINTERTYPE ){ \
+         raw_size = sizeof( void * ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0P *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1P *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__FLOATTYPE ){ \
+         raw_size = sizeof( float ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0F *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1F *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__STRINGTYPE ){ \
+         raw_size = sizeof( const char * ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0C *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1C *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__OBJECTTYPE ){ \
+         raw_size = sizeof( AstObject * ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0A *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1A *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__UNDEFTYPE ){ \
+         raw_size = 0; \
+         raw = NULL; \
+\
+      } else { \
+         raw_size = 0; \
+         raw = NULL; \
+         astError( AST__INTER, "astMapGet1<X>(KeyMap): Illegal map entry data " \
+                   "type %d encountered (internal AST programming error).", status, \
+                   raw_type ); \
+      } \
+\
+/* Treat scalars as single-value vectors. */ \
+      if( nel == 0 ) nel = 1; \
+\
+/* Ensure no more than "mxval" values are returned. */ \
+      if( nel > mxval ) nel = mxval; \
+\
+/* Return the number of values stored in the buffer. */ \
+      *nval = nel; \
+\
+/* Loop round all values in the vector. */ \
+      for( i = 0; i < nel && astOK; i++ ) { \
+\
+/* Convert the value, storing the result in the supplied buffer. Report an \
+   error if conversion is not possible. */ \
+         if( !raw ) { \
+            result = 0; \
+\
+         } else if( !ConvertValue( raw, raw_type, value + i, Itype, status ) && astOK ){ \
+            astError( AST__MPGER, "astMapGet1" #X "(%s): The value of " \
+                      "element %d of KeyMap key \"%s\" cannot be read using " \
+                      "the requested data type.", status,astGetClass( this ), i + 1, key ); \
+         } \
+\
+/* Increment the pointers to the next raw value. */ \
+         raw = (char *) raw + raw_size; \
+      } \
+\
+/* If the KeyError attribute is non-zero, report an error if the key is not \
+   found */ \
+   } else if( astGetKeyError( this ) && astOK ) { \
+      astError( AST__MPKER, "astMapGet1" #X "(%s): No value was found for " \
+                "%s in the supplied KeyMap.", status, astGetClass( this ), \
+                key ); \
+   } \
+\
+/* If an error occurred,return zero. */ \
+   if( !astOK ) result = 0; \
+\
+/* Return the result.*/ \
+   return result; \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type (except C which is done differently). */
+MAKE_MAPGET1(I,int,AST__INTTYPE)
+MAKE_MAPGET1(D,double,AST__DOUBLETYPE)
+MAKE_MAPGET1(F,float,AST__FLOATTYPE)
+MAKE_MAPGET1(A,AstObject *,AST__OBJECTTYPE)
+MAKE_MAPGET1(P,void *,AST__POINTERTYPE)
+MAKE_MAPGET1(S,short int,AST__SINTTYPE)
+MAKE_MAPGET1(B,unsigned char,AST__BYTETYPE)
+
+/* Undefine the macro. */
+#undef MAKE_MAPGET1
+
+
+static int MapGet1C( AstKeyMap *this, const char *skey, int l, int mxval,
+                     int *nval, char *value, int *status ) {
+/*
+*  Name:
+*     MapGet1C
+
+*  Purpose:
+*     Get a vector value from a KeyMap.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "ast.h"
+*     int MapGet1C( AstKeyMap *this, const char *key, int l, int mxval,
+*                   int *nval, char *value, int *status )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This is the implementation of astMapGet1<X> for <X> = "C". We
+*     cannot use the MAKE_MAPGET1 macro for this because the string
+*     version of this function has an extra parameter giving the maximum
+*     length of each string which can be stored in the supplied buffer.
+
+*  Parameters:
+*     (see astMapGet1<X>)
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */
+   char *val;              /* Pointer to next buffer element */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */ \
+   const char *cvalue;     /* Pointer to converted string */
+   const char *key;        /* Pointer to key string to use */ \
+   int i;                  /* Element index */
+   int itab;               /* Index of hash table element to use */
+   int nel;                /* Number of elements in raw vector */
+   int raw_type;           /* Data type of stored value */
+   int result;             /* Returned flag */
+   size_t raw_size;        /* Size of a single raw value */
+   unsigned long hash;     /* Full width hash value */
+   void *raw;              /* Pointer to stored value */
+
+/* Initialise */
+   result = 0;
+   *nval = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapGet1C",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. */
+   mapentry = SearchTableEntry( this, itab, key, status );
+
+/* Skip rest if the key was not found. */
+   if( mapentry ) {
+      result = 1;
+
+/* Get the address of the first raw value, and its data type. Also get
+   the size of each element of the vector. */
+      nel = mapentry->nel;
+      raw_type = mapentry->type;
+      if( raw_type == AST__INTTYPE ){
+         raw_size = sizeof( int );
+         if( nel == 0 ) {
+            raw = &( ((Entry0I *)mapentry)->value );
+         } else {
+            raw = ((Entry1I *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__POINTERTYPE ){
+         raw_size = sizeof( void * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0P *)mapentry)->value );
+         } else {
+            raw = ((Entry1P *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__DOUBLETYPE ){
+         raw_size = sizeof( double );
+         if( nel == 0 ) {
+            raw = &( ((Entry0D *)mapentry)->value );
+         } else {
+            raw = ((Entry1D *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__SINTTYPE ){
+         raw_size = sizeof( short int );
+         if( nel == 0 ) {
+            raw = &( ((Entry0S *)mapentry)->value );
+         } else {
+            raw = ((Entry1S *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__BYTETYPE ){
+         raw_size = sizeof( unsigned char );
+         if( nel == 0 ) {
+            raw = &( ((Entry0B *)mapentry)->value );
+         } else {
+            raw = ((Entry1B *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__FLOATTYPE ){
+         raw_size = sizeof( float );
+         if( nel == 0 ) {
+            raw = &( ((Entry0F *)mapentry)->value );
+         } else {
+            raw = ((Entry1F *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__STRINGTYPE ){
+         raw_size = sizeof( const char * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0C *)mapentry)->value );
+         } else {
+            raw = ((Entry1C *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__OBJECTTYPE ){
+         raw_size = sizeof( AstObject * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0A *)mapentry)->value );
+         } else {
+            raw = ((Entry1A *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__UNDEFTYPE ){
+         raw_size = 0;
+         raw = NULL;
+
+      } else {
+         raw_size = 0;
+         raw = NULL;
+         astError( AST__INTER, "astMapGet1C(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   raw_type );
+      }
+
+/* Treat scalars as single-value vectors. */
+      if( nel == 0 ) nel = 1;
+
+/* Ensure no more than "mxval" values are returned. */
+      if( nel > mxval ) nel = mxval;
+
+/* Return the number of values stored in the buffer. */
+      *nval = nel;
+
+/* Loop round all values in the vector. */
+      val = value;
+      for( i = 0; i < nel && astOK; i++ ) {
+
+/* Convert the value, storing the result in the supplied buffer. Report an
+   error if conversion is not possible. */
+         if( !raw ) {
+            result = 0;
+
+         } else if( !ConvertValue( raw, raw_type, &cvalue, AST__STRINGTYPE, status ) && astOK ){
+            astError( AST__MPGER, "astMapGet1C(%s): The value of "
+                      "element %d of KeyMap key \"%s\" cannot be read using "
+                      "the requested data type.", status,astGetClass( this ), i + 1, key );
+
+/* If succesful, copy the string into the supplied buffer, or as much of
+   it as will fit. Leave room for a trailing null character. */
+         } else {
+            strncpy( val, cvalue, l - 1 );
+            val[ l - 1 ] = 0;
+         }
+
+/* Increment the pointers to the next raw value and the next buffer
+   location. */
+         raw = (char *) raw + raw_size;
+         val += l;
+      }
+
+/* If the KeyError attribute is non-zero, report an error if the key is not
+   found */
+   } else if( astGetKeyError( this ) && astOK ) {
+      astError( AST__MPKER, "astMapGet1C(%s): No value was found for "
+                "%s in the supplied KeyMap.", status, astGetClass( this ),
+                key );
+   }
+
+/* If an error occurred,return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result.*/
+   return result;
+}
+
+int astMapGet1AId_( AstKeyMap *this, const char *skey, int mxval, int *nval,
+                    AstObject **value, int *status ) {
+/*
+*  Name:
+*     astMapGet1AId_
+
+*  Purpose:
+*     Get a vector of AstObject pointers from a KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ast.h"
+*     int astMapGet1A( AstKeyMap *this, const char *key, int mxval, int *nval,
+*                      AstObject **value )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This is the public implementation of the astMapGet1A function
+*     It is identical to astMapGet1A_ except that ID values are returned
+*     via the "value" parameter instead of a true C pointers. This is required
+*     because this conversion cannot be performed by the macro that invokes
+*     the function.
+
+*  Parameters:
+*     (see astMapGet1<X>)
+
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */
+   AstObject *avalue;      /* Pointer to AstObject */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */ \
+   const char *key;        /* Pointer to key string to use */ \
+   int i;                  /* Element index */
+   int itab;               /* Index of hash table element to use */
+   int nel;                /* Number of elements in raw vector */
+   int raw_type;           /* Data type of stored value */
+   int result;             /* Returned flag */
+   size_t raw_size;        /* Size of a single raw value */
+   unsigned long hash;     /* Full width hash value */
+   void *raw;              /* Pointer to stored value */
+
+/* Initialise */
+   result = 0;
+   *nval = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapGet1A",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. */
+   mapentry = SearchTableEntry( this, itab, key, status );
+
+/* Skip rest if the key was not found. */
+   if( mapentry ) {
+      result = 1;
+
+/* Get the address of the first raw value, and its data type. Also get
+   the size of each element of the vector. */
+      nel = mapentry->nel;
+      raw_type = mapentry->type;
+      if( raw_type == AST__INTTYPE ){
+         raw_size = sizeof( int );
+         if( nel == 0 ) {
+            raw = &( ((Entry0I *)mapentry)->value );
+         } else {
+            raw = ((Entry1I *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__DOUBLETYPE ){
+         raw_size = sizeof( double );
+         if( nel == 0 ) {
+            raw = &( ((Entry0D *)mapentry)->value );
+         } else {
+            raw = ((Entry1D *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__SINTTYPE ){
+         raw_size = sizeof( short int );
+         if( nel == 0 ) {
+            raw = &( ((Entry0S *)mapentry)->value );
+         } else {
+            raw = ((Entry1S *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__BYTETYPE ){
+         raw_size = sizeof( unsigned char );
+         if( nel == 0 ) {
+            raw = &( ((Entry0B *)mapentry)->value );
+         } else {
+            raw = ((Entry1B *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__POINTERTYPE ){
+         raw_size = sizeof( void * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0P *)mapentry)->value );
+         } else {
+            raw = ((Entry1P *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__FLOATTYPE ){
+         raw_size = sizeof( float );
+         if( nel == 0 ) {
+            raw = &( ((Entry0F *)mapentry)->value );
+         } else {
+            raw = ((Entry1F *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__STRINGTYPE ){
+         raw_size = sizeof( const char * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0C *)mapentry)->value );
+         } else {
+            raw = ((Entry1C *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__OBJECTTYPE ){
+         raw_size = sizeof( AstObject * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0A *)mapentry)->value );
+         } else {
+            raw = ((Entry1A *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__UNDEFTYPE ){
+         raw_size = 0;
+         raw = NULL;
+
+      } else {
+         raw_size = 0;
+         raw = NULL;
+         astError( AST__INTER, "astMapGet1A(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).",
+                   status, raw_type );
+      }
+
+/* Treat scalars as single-value vectors. */
+      if( nel == 0 ) nel = 1;
+
+/* Ensure no more than "mxval" values are returned. */
+      if( nel > mxval ) nel = mxval;
+
+/* Return the number of values stored in the buffer. */
+      *nval = nel;
+
+/* Loop round all values in the vector. */
+      for( i = 0; i < nel && astOK; i++ ) {
+
+/* Convert the value, storing the result in the supplied buffer. Report an
+   error if conversion is not possible. */
+         if( !raw ) {
+            result = 0;
+
+         } else if( !ConvertValue( raw, raw_type, &avalue, AST__OBJECTTYPE, status ) && astOK ){
+            astError( AST__MPGER, "astMapGet1A(%s): The value of "
+                      "element %d of KeyMap key \"%s\" cannot be read using "
+                      "the requested data type.", status, astGetClass( this ),
+                      i + 1, key );
+
+/* If succesful, return an ID value for the Object. */
+         } else {
+           value[ i ] = avalue ? astMakeId( avalue ) : NULL;
+         }
+
+/* Increment the pointers to the next raw value. */
+         raw = (char *) raw + raw_size;
+      }
+
+/* If the KeyError attribute is non-zero, report an error if the key is not
+   found */
+   } else if( astGetKeyError( this ) && astOK ) {
+      astError( AST__MPKER, "astMapGet1A(%s): No value was found for "
+                "%s in the supplied KeyMap.", status, astGetClass( this ),
+                key );
+   }
+
+/* If an error occurred,return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result.*/
+   return result;
+}
+
+/*
+*++
+*  Name:
+c     astMapGetElem<X>
+f     AST_MAPGETELEM<X>
+
+*  Purpose:
+*     Get a single element of a vector value from a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "ast.h"
+c     int astMapGetElem<X>( AstKeyMap *this, const char *key, int elem,
+c                           <X>type *value )
+c     int astMapGetElemC( AstKeyMap *this, const char *key, int l, int elem,
+c                         char *value )
+f     RESULT = AST_MAPGETELEM<X>( THIS, KEY, ELEM, VALUE, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This is a set of functions for retrieving a single element of a vector
+*     value from a KeyMap. You should replace <X> in the generic function name
+c     astMapGetElem<X>
+f     AST_MAPGETELEM<X>
+*     by an appropriate 1-character type code (see the "Data Type Codes"
+*     section below for the code appropriate to each supported data type).
+*     The stored value is converted to the data type indiced by <X>
+*     before being returned (an error is reported if it is not possible to
+*     convert the stored value to the requested data type).
+c     Note, the astMapGetElemC function has an extra parameter "l" which
+c     specifies the maximum length of the string to be stored in the
+c     "value" buffer (see the "astMapGetElemC" section below).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the value to be retrieved. Trailing
+*        spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+c     elem
+f     ELEM = INTEGER (Given)
+*        The index of the required vector element, starting at
+c        zero.
+f        one.
+*        An error will be reported if the value is outside the range of
+*        the vector.
+c     value
+f     VALUE = <X>type (Returned)
+c        A pointer to a buffer in which to return the requested value.
+f        The requested value.
+*        If the requested key is not found, or if it is found but has an
+*        undefined value (see
+c        astMapPutU),
+f        AST_MAPPUTU),
+*        then the contents of the
+*        buffer on entry to this function will be unchanged on exit.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapGetElem<X>()
+f     AST_MAPGETELEM<X> = LOGICAL
+c        A non-zero value
+f        .TRUE.
+*        is returned if the requested key name was found, and does not have
+*        an undefined value (see
+c        astMapPutU). Zero
+f        AST_MAPPUTU). .FALSE.
+*        is returned otherwise.
+
+*  Notes:
+*     - No error is reported if the requested key cannot be found in the
+*     given KeyMap, or if it has an undefined value, but a
+c     zero
+f     .FALSE.
+*     value will be returned as the function value.
+
+c  astMapGetElemC:
+c     The "value" buffer supplied to the astMapGetElemC function should be a
+c     pointer to a character array with "l" elements, where "l" is the
+c     maximum length of the string to be returned. The value of "l"
+c     should be supplied as an extra parameter following "key" when
+c     invoking astMapGetElemC, and should include space for a terminating
+c     null character.
+
+*  Data Type Codes:
+*     To select the appropriate
+c     function, you should replace <X> in the generic function name
+c     astMapGetElem<X>
+f     routine, you should replace <X> in the generic routine name
+f     AST_MAPGETELEM<X>
+*     with a 1-character data type code, so as to match the data type <X>type
+*     of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - I: int
+c     - C: "const" pointer to null terminated character string
+c     - A: Pointer to AstObject
+c     - P: Generic "void *" pointer
+c     - S: short int
+c     - B: Unsigned byte (i.e. char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - C: CHARACTER
+f     - A: INTEGER used to identify an AstObject
+f     - S: INTEGER*2 (short integer)
+f     - B: Unsigned byte
+*
+c     For example, astMapGetElemD would be used to get a "double" value, while
+c     astMapGetElemI would be used to get an "int" value, etc. For D or I, the
+c     supplied "value" parameter should be a pointer to a double or int. For
+c     C, the supplied "value" parameter should be a pointer to a character
+c     string with "l" elements. For A, the supplied "value" parameter should
+c     be a pointer to an AstObject pointer.
+f     For example, AST_MAPGETELEMD would be used to get a DOUBLE PRECISION
+f     value, while AST_MAPGETELEMI would be used to get an INTEGER value, etc.
+
+*--
+*/
+/* Define a macro to implement the function for a specific data type
+(excluding "C" since that needs an extra parameter). */
+#define MAKE_MAPGETELEM(X,Xtype,Itype) \
+static int MapGetElem##X( AstKeyMap *this, const char *skey, int elem, \
+                          Xtype *value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */ \
+   const char *key;        /* Pointer to key string to use */ \
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */ \
+   int itab;               /* Index of hash table element to use */ \
+   int nel;                /* Number of elements in raw vector */ \
+   int raw_type;           /* Data type of stored value */ \
+   int result;             /* Returned flag */ \
+   size_t raw_size;        /* Size of a single raw value */ \
+   unsigned long hash;     /* Full width hash value */ \
+   void *raw;              /* Pointer to stored value */ \
+\
+/* Initialise */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Convert the supplied key to upper case if required. */ \
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapGetElem" #X, \
+                     status ); \
+\
+/* Use the hash function to determine the element of the hash table in \
+   which the key will be stored. */ \
+   itab = HashFun( key, this->mapsize - 1, &hash, status ); \
+\
+/* Search the relevent table entry for the required MapEntry. */ \
+   mapentry = SearchTableEntry( this, itab, key, status ); \
+\
+/* Skip rest if the key was not found. */ \
+   if( mapentry ) { \
+      result = 1; \
+\
+/* Get the address of the first raw value, and its data type. Also get \
+   the size of each element of the vector. */ \
+      nel = mapentry->nel; \
+      raw_type = mapentry->type; \
+      if( raw_type == AST__INTTYPE ){ \
+         raw_size = sizeof( int ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0I *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1I *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__DOUBLETYPE ){ \
+         raw_size = sizeof( double ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0D *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1D *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__SINTTYPE ){ \
+         raw_size = sizeof( short int ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0S *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1S *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__BYTETYPE ){ \
+         raw_size = sizeof( unsigned char ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0B *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1B *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__POINTERTYPE ){ \
+         raw_size = sizeof( void * ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0P *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1P *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__FLOATTYPE ){ \
+         raw_size = sizeof( float ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0F *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1F *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__STRINGTYPE ){ \
+         raw_size = sizeof( const char * ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0C *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1C *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__OBJECTTYPE ){ \
+         raw_size = sizeof( AstObject * ); \
+         if( nel == 0 ) { \
+            raw = &( ((Entry0A *)mapentry)->value ); \
+         } else { \
+            raw = ((Entry1A *)mapentry)->value; \
+         } \
+\
+      } else if( raw_type == AST__UNDEFTYPE ){ \
+         raw = NULL; \
+\
+      } else { \
+         raw_size = 0; \
+         raw = NULL; \
+         astError( AST__INTER, "astMapGetElem<X>(KeyMap): Illegal map entry " \
+                   "data type %d encountered (internal AST programming " \
+                   "error).", status, raw_type ); \
+      } \
+\
+/* Treat scalars as single-value vectors. */ \
+      if( nel == 0 ) nel = 1; \
+\
+/* Ensure the requested element is within the bounds of the vector */ \
+      if( elem >= nel || elem < 0 ) { \
+         if( astOK ) { \
+            astError( AST__MPVIN, "astMapGetElem<X>(KeyMap): Illegal " \
+                      "zero-based vector index %d supplied for KeyMap " \
+                      "entry '%s' - the vector has %d elements.", status, \
+                      elem, key, nel ); \
+         } \
+\
+/* Get a pointer to the requested raw value. */ \
+      } else if( raw ) { \
+         raw = (char *) raw + elem*raw_size; \
+\
+/* Convert the requested value, storing the result in the supplied buffer. \
+   Report an error if conversion is not possible. */ \
+         if( !ConvertValue( raw, raw_type, value, Itype, status ) && astOK ){ \
+            astError( AST__MPGER, "astMapGetElem" #X "(%s): The value of " \
+                      "element %d of KeyMap key \"%s\" cannot be read using " \
+                      "the requested data type.", status, astGetClass( this ), \
+                      elem + 1, key ); \
+         } \
+      } \
+\
+/* If the KeyError attribute is non-zero, report an error if the key is not \
+   found */ \
+   } else if( astGetKeyError( this ) && astOK ) { \
+      astError( AST__MPKER, "astMapGetElem" #X "(%s): No value was found for " \
+                "%s in the supplied KeyMap.", status, astGetClass( this ), \
+                key ); \
+   } \
+\
+/* If an error occurred,return zero. */ \
+   if( !astOK ) result = 0; \
+\
+/* Return the result.*/ \
+   return result; \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type (except C which is done differently). */
+MAKE_MAPGETELEM(I,int,AST__INTTYPE)
+MAKE_MAPGETELEM(D,double,AST__DOUBLETYPE)
+MAKE_MAPGETELEM(F,float,AST__FLOATTYPE)
+MAKE_MAPGETELEM(A,AstObject *,AST__OBJECTTYPE)
+MAKE_MAPGETELEM(P,void *,AST__POINTERTYPE)
+MAKE_MAPGETELEM(S,short int,AST__SINTTYPE)
+MAKE_MAPGETELEM(B,unsigned char,AST__BYTETYPE)
+
+/* Undefine the macro. */
+#undef MAKE_MAPGETELEM
+
+
+static int MapGetElemC( AstKeyMap *this, const char *skey, int l, int elem,
+                        char *value, int *status ) {
+/*
+*  Name:
+*     MapGetElemC
+
+*  Purpose:
+*     Get a single element of a vector value from a KeyMap.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "ast.h"
+*     int MapGetElemC( AstKeyMap *this, const char *key, int l, int elem,
+*                      char *value, int *status )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This is the implementation of astMapGetElem<X> for <X> = "C". We
+*     cannot use the MAKE_MAPGETELEM macro for this because the string
+*     version of this function has an extra parameter giving the maximum
+*     length of each string which can be stored in the supplied buffer.
+
+*  Parameters:
+*     (see astMapGetElem<X>)
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */
+   const char *key;        /* Pointer to key string to use */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   const char *cvalue;     /* Pointer to converted string */
+   int itab;               /* Index of hash table element to use */
+   int nel;                /* Number of elements in raw vector */
+   int raw_type;           /* Data type of stored value */
+   int result;             /* Returned flag */
+   size_t raw_size;        /* Size of a single raw value */
+   unsigned long hash;     /* Full width hash value */
+   void *raw;              /* Pointer to stored value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapGetElemC",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. */
+   mapentry = SearchTableEntry( this, itab, key, status );
+
+/* Skip rest if the key was not found. */
+   if( mapentry ) {
+      result = 1;
+
+/* Get the address of the first raw value, and its data type. Also get
+   the size of each element of the vector. */
+      nel = mapentry->nel;
+      raw_type = mapentry->type;
+      if( raw_type == AST__INTTYPE ){
+         raw_size = sizeof( int );
+         if( nel == 0 ) {
+            raw = &( ((Entry0I *)mapentry)->value );
+         } else {
+            raw = ((Entry1I *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__POINTERTYPE ){
+         raw_size = sizeof( void * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0P *)mapentry)->value );
+         } else {
+            raw = ((Entry1P *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__DOUBLETYPE ){
+         raw_size = sizeof( double );
+         if( nel == 0 ) {
+            raw = &( ((Entry0D *)mapentry)->value );
+         } else {
+            raw = ((Entry1D *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__SINTTYPE ){
+         raw_size = sizeof( short int );
+         if( nel == 0 ) {
+            raw = &( ((Entry0S *)mapentry)->value );
+         } else {
+            raw = ((Entry1S *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__BYTETYPE ){
+         raw_size = sizeof( unsigned char );
+         if( nel == 0 ) {
+            raw = &( ((Entry0B *)mapentry)->value );
+         } else {
+            raw = ((Entry1B *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__FLOATTYPE ){
+         raw_size = sizeof( float );
+         if( nel == 0 ) {
+            raw = &( ((Entry0F *)mapentry)->value );
+         } else {
+            raw = ((Entry1F *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__STRINGTYPE ){
+         raw_size = sizeof( const char * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0C *)mapentry)->value );
+         } else {
+            raw = ((Entry1C *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__OBJECTTYPE ){
+         raw_size = sizeof( AstObject * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0A *)mapentry)->value );
+         } else {
+            raw = ((Entry1A *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__UNDEFTYPE ){
+         raw = NULL;
+
+      } else {
+         raw_size = 0;
+         raw = NULL;
+         astError( AST__INTER, "astMapGetElemC(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   raw_type );
+      }
+
+/* Treat scalars as single-value vectors. */
+      if( nel == 0 ) nel = 1;
+
+/* Ensure the requested element is within the bounds of the vector */
+      if( elem >= nel || elem < 0 ) {
+         if( astOK ) {
+            astError( AST__MPVIN, "astMapGetElemC(KeyMap): Illegal vector "
+                      "index %d supplied for KeyMap entry '%s' - should be "
+                      "in the range 1 to %d.", status, elem + 1, key, nel + 1 );
+         }
+
+/* Get a pointer to the requested raw value. */
+      } else if( raw ){
+         raw = (char *) raw + elem*raw_size;
+
+/* Convert the value, storing the result in the supplied buffer. Report an
+   error if conversion is not possible. */
+         if( !ConvertValue( raw, raw_type, &cvalue, AST__STRINGTYPE, status ) && astOK ){
+            astError( AST__MPGER, "astMapGetElemC(%s): The value of "
+                      "element %d of KeyMap key \"%s\" cannot be read using "
+                      "the requested data type.", status,astGetClass( this ),
+                      elem + 1, key );
+
+/* If succesful, copy the string into the supplied buffer, or as much of
+   it as will fit. Leave room for a trailing null character. */
+         } else {
+            strncpy( value, cvalue, l - 1 );
+            value[ l - 1 ] = 0;
+         }
+      }
+
+/* If the KeyError attribute is non-zero, report an error if the key is not
+   found */
+   } else if( astGetKeyError( this ) && astOK ) {
+      astError( AST__MPKER, "astMapGetElemC(%s): No value was found for "
+                "%s in the supplied KeyMap.", status, astGetClass( this ),
+                key );
+   }
+
+/* If an error occurred,return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result.*/
+   return result;
+}
+
+int astMapGetElemAId_( AstKeyMap *this, const char *skey, int elem,
+                       AstObject **value, int *status ) {
+/*
+*  Name:
+*     astMapGetElemAId_
+
+*  Purpose:
+*     Get a single element of a vector of AstObject pointers from a KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ast.h"
+*     int astMapGetElemA( AstKeyMap *this, const char *key, int elem,
+*                         AstObject **value )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This is the public implementation of the astMapGetElemA function
+*     It is identical to astMapGetElemA_ except that an ID value is returned
+*     via the "value" parameter instead of a true C pointer. This is required
+*     because this conversion cannot be performed by the macro that invokes
+*     the function.
+
+*  Parameters:
+*     (see astMapGet1<X>)
+
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */
+   AstObject *avalue;      /* Pointer to AstObject */
+   const char *key;        /* Pointer to key string to use */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   int itab;               /* Index of hash table element to use */
+   int nel;                /* Number of elements in raw vector */
+   int raw_type;           /* Data type of stored value */
+   int result;             /* Returned flag */
+   size_t raw_size;        /* Size of a single raw value */
+   unsigned long hash;     /* Full width hash value */
+   void *raw;              /* Pointer to stored value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapGetElemA",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. */
+   mapentry = SearchTableEntry( this, itab, key, status );
+
+/* Skip rest if the key was not found. */
+   if( mapentry ) {
+      result = 1;
+
+/* Get the address of the first raw value, and its data type. Also get
+   the size of each element of the vector. */
+      nel = mapentry->nel;
+      raw_type = mapentry->type;
+      if( raw_type == AST__INTTYPE ){
+         raw_size = sizeof( int );
+         if( nel == 0 ) {
+            raw = &( ((Entry0I *)mapentry)->value );
+         } else {
+            raw = ((Entry1I *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__SINTTYPE ){
+         raw_size = sizeof( short int );
+         if( nel == 0 ) {
+            raw = &( ((Entry0S *)mapentry)->value );
+         } else {
+            raw = ((Entry1S *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__BYTETYPE ){
+         raw_size = sizeof( unsigned char );
+         if( nel == 0 ) {
+            raw = &( ((Entry0B *)mapentry)->value );
+         } else {
+            raw = ((Entry1B *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__DOUBLETYPE ){
+         raw_size = sizeof( double );
+         if( nel == 0 ) {
+            raw = &( ((Entry0D *)mapentry)->value );
+         } else {
+            raw = ((Entry1D *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__POINTERTYPE ){
+         raw_size = sizeof( void * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0P *)mapentry)->value );
+         } else {
+            raw = ((Entry1P *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__FLOATTYPE ){
+         raw_size = sizeof( float );
+         if( nel == 0 ) {
+            raw = &( ((Entry0F *)mapentry)->value );
+         } else {
+            raw = ((Entry1F *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__STRINGTYPE ){
+         raw_size = sizeof( const char * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0C *)mapentry)->value );
+         } else {
+            raw = ((Entry1C *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__OBJECTTYPE ){
+         raw_size = sizeof( AstObject * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0A *)mapentry)->value );
+         } else {
+            raw = ((Entry1A *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__UNDEFTYPE ){
+         raw = NULL;
+
+      } else {
+         raw_size = 0;
+         raw = NULL;
+         astError( AST__INTER, "astMapGetElemA(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   raw_type );
+      }
+
+/* Treat scalars as single-value vectors. */
+      if( nel == 0 ) nel = 1;
+
+/* Ensure the requested element is within the bounds of the vector */
+      if( elem >= nel || elem < 0 ) {
+         if( astOK ) {
+            astError( AST__MPVIN, "astMapGetElemA(KeyMap): Illegal vector "
+                      "index %d supplied for KeyMap entry '%s' - should be "
+                      "in the range 1 to %d.", status, elem + 1, key, nel + 1 );
+         }
+
+/* Get a pointer to the requested raw value. */
+      } else if( raw ){
+         raw = (char *) raw + elem*raw_size;
+
+/* Convert the value, storing the result in the supplied buffer. Report an
+   error if conversion is not possible. */
+         if( !ConvertValue( raw, raw_type, &avalue, AST__OBJECTTYPE, status ) && astOK ){
+            astError( AST__MPGER, "astMapGetElemA(%s): The value of "
+                      "element %d of KeyMap key \"%s\" cannot be read using "
+                      "the requested data type.", status,astGetClass( this ),
+                      elem + 1, key );
+
+/* If succesful, return an ID value for the Object. */
+         } else {
+            *value = avalue ? astMakeId( avalue ) : NULL;
+         }
+      }
+
+/* If the KeyError attribute is non-zero, report an error if the key is not
+   found */
+   } else if( astGetKeyError( this ) && astOK ) {
+      astError( AST__MPKER, "astMapGetElemA(%s): No value was found for "
+                "%s in the supplied KeyMap.", status, astGetClass( this ),
+                key );
+   }
+
+/* If an error occurred,return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result.*/
+   return result;
+}
+
+static int MapDefined( AstKeyMap *this, const char *skey, int *status ) {
+/*
+*++
+*  Name:
+c     astMapDefined
+f     AST_MAPDEFINED
+
+*  Purpose:
+*     Check if a KeyMap contains a defined value for a key.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "ast.h"
+c     int astMapDefined( AstKeyMap *this, const char *key );
+f     RESULT = AST_MAPDEFINED( THIS, KEY, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function checks to see if a KeyMap contains a defined value for
+*     a given key. If the key is present in the KeyMap but has an
+*     undefined value it returns
+c     zero (unlike astMapHasKey which would return non-zero).
+f     .FALSE. (unlike AST_MAPHASKEY which would return .TRUE.).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the value to be retrieved. Trailing
+*        spaces are ignored. The supplied string is converted to upper
+*        case before use if the KeyCase attribute is currently set to zero.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapDefined()
+f     AST_MAPDEFINED = LOGICAL
+c        A non-zero value
+f        .TRUE.
+*        is returned if the requested key name is present in the KeyMap
+*        and has a defined value.
+
+*--
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */
+   const char *key;        /* Pointer to key string to use */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   int itab;               /* Index of hash table element to use */
+   int result;             /* Returned flag */
+   unsigned long hash;     /* Full width hash value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapDefined",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. */
+   mapentry = SearchTableEntry( this, itab, key, status );
+
+/* Skip rest if the key was not found. */
+   if( mapentry ) {
+
+/* Set the result depending on the entry data type. */
+      if( mapentry->type == AST__UNDEFTYPE ){
+         result = 0;
+      } else {
+         result = 1;
+      }
+
+/* If the KeyError attribute is non-zero, report an error if the key is not
+   found */
+   } else if( astGetKeyError( this ) && astOK ) {
+      astError( AST__MPKER, "astMapDefined(%s): No value was found for "
+                "%s in the supplied KeyMap.", status, astGetClass( this ),
+                key );
+   }
+
+/* If an error occurred, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result.*/
+   return result;
+}
+
+static int MapHasKey( AstKeyMap *this, const char *skey, int *status ) {
+/*
+*++
+*  Name:
+c     astMapHasKey
+f     AST_MAPHASKEY
+
+*  Purpose:
+*     Check if an entry with a given key exists in a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "keymap.h"
+c     int astMapHasKey( AstKeyMap *this, const char *key )
+f     RESULT = AST_MAPHASKEY( THIS, KEY, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function returns a flag indicating if the KeyMap contains an
+*     entry with the given key.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the KeyMap entry. Trailing spaces are
+*        ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapHasKey()
+f     AST_MAPHASKEY = LOGICAL
+c        Non-zero if the key was found, and zero otherwise.
+f        .TRUE. if the key was found, and .FALSE. otherwise.
+
+*  Notes:
+c     - A non-zero function value
+f     - .TRUE.
+*     is returned if the key exists but has an undefined value (that is,
+*     the returned value does not depend on whether the entry has a
+*     defined value or not). See also
+c     astMapDefined, which returns zero in such a case.
+f     AST_MAPDEFINED, which returns zero in such a case.
+*     - A function value of
+c     zero
+f     .FALSE.
+*     will be returned if an error has already occurred, or if this
+*     function should fail for any reason.
+
+*--
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to entry in linked list */
+   const char *key;        /* Pointer to key string to use */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   int itab;               /* Index of hash table element to use */
+   int result;             /* Returned value */
+   unsigned long hash;     /* Full width hash value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapHasKey",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. */
+   mapentry = SearchTableEntry( this, itab, key, status );
+
+/* Set a non-zero return value if the key was found. */
+   if( mapentry ) result = 1;
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+
+}
+
+static void MapRemove( AstKeyMap *this, const char *skey, int *status ) {
+/*
+*++
+*  Name:
+c     astMapRemove
+f     AST_MAPREMOVE
+
+*  Purpose:
+*     Removed a named entry from a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "keymap.h"
+c     void astMapRemove( AstKeyMap *this, const char *key )
+f     CALL AST_MAPREMOVE( THIS, KEY, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+c     This function
+f     This routine
+*     removes a named entry from a KeyMap. It returns without action if the
+*     KeyMap does not contain the specified key.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the value to be retrieved. Trailing
+*        spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+*--
+*/
+
+/* Local Variables: */
+   const char *key;        /* Pointer to key string to use */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   int itab;               /* Index of hash table element to use */
+   unsigned long hash;     /* Full width hash value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapRemove",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry and remove it. */
+   (void) FreeMapEntry( RemoveTableEntry( this, itab, key, status ), status );
+}
+
+static void MapRename( AstKeyMap *this, const char *soldkey, const char *snewkey,
+                       int *status ) {
+/*
+*++
+*  Name:
+c     astMapRename
+f     AST_MAPRENAME
+
+*  Purpose:
+*     Rename an existing KeyMap entry.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "keymap.h"
+c     void astMapRename( AstKeyMap *this, const char *oldkey, const char *newkey )
+f     CALL AST_MAPRENAME( THIS, OLDKEY, NEWKEY, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+c     This function
+f     This routine
+*     associated a new key with an existing entry in a KeyMap. It returns
+*     without action if the oldkey does not exist in the KeyMap.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     oldkey
+f     OLDKEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the entry to be renamed. Trailing
+*        spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+c     newkey
+f     NEKEY = CHARACTER * ( * ) (Given)
+*        The new character string to associated with the renamed entry.
+*        Trailing spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+*--
+*/
+
+/* Local Variables: */
+   AstMapEntry *entry;     /* Pointer to the entry being renamed */
+   AstMapEntry *oldent;    /* Pointer to old entry with new name */
+   const char *oldkey;     /* Pointer to key string to use */
+   char oldkeybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   const char *newkey;     /* Pointer to key string to use */
+   char newkeybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   char *p;                /* Pointer to next key character */
+   int itab;               /* Index of hash table element to use */
+   int keylen;             /* Length of supplied key string */
+   int keymember;          /* Identifier for new key */
+   int there;              /* Did the entry already exist in the KeyMap? */
+   unsigned long hash;     /* Full width hash value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Convert the supplied keys to upper case if required. */
+   oldkey = ConvertKey( this, soldkey, oldkeybuf, AST__MXKEYLEN + 1,
+                        "astMapRename", status );
+   newkey = ConvertKey( this, snewkey, newkeybuf, AST__MXKEYLEN + 1,
+                        "astMapRename", status );
+
+/* Do nothing if the keys are the same. */
+   if( strcmp( oldkey, newkey ) ){
+
+/* Use the hash function to determine the element of the hash table in
+   which the old key will be stored. */
+      itab = HashFun( oldkey, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. Remove it
+   from the list, but do not free it. */
+      entry = RemoveTableEntry( this, itab, oldkey, status );
+
+/* Skip rest if the key was not found. */
+      if( entry ) {
+
+/* Store the new key string, and terminate it to exclude any trailing
+   spaces. */
+         keylen = strlen( newkey );
+         entry->key = astStore( (void *) entry->key, newkey, keylen + 1 );
+         if( astOK ) {
+            p = (char *) entry->key + keylen;
+            while( --p >= entry->key ) {
+               if( *p == ' ' ) {
+                  *p = 0;
+               } else {
+                  break;
+               }
+            }
+         }
+
+/* Use the hash function to determine the element of the hash table in
+   which to store the entry with its new key. */
+         itab = HashFun( entry->key, this->mapsize - 1, &(entry->hash), status );
+
+/* Remove and free any existing entry with the given key from the table
+   element. */
+         oldent = RemoveTableEntry( this, itab, entry->key, status );
+         if( oldent ) {
+            keymember = oldent->keymember;
+            oldent = FreeMapEntry( oldent, status );
+            there = 1;
+         } else {
+            keymember = -1;
+            there = 0;
+         }
+
+/* If the KeyMap is locked we report an error if an attempt is made to
+   introduce a new key. */
+         if( !there && astGetMapLocked( this ) ) {
+            astError( AST__BADKEY, "astMapRename(%s): Failed to rename item "
+                      "\"%s\" in a KeyMap to \"%s\": \"%s\" is not a known "
+                      "item.", status, astGetClass( this ), oldkey, newkey,
+                      newkey );
+         }
+
+/* If all has gone OK, store the renamed entry at the head of the linked list
+   associated with the selected table entry. */
+         if( astOK ) {
+            entry = AddTableEntry( this, itab, entry, keymember, status );
+
+/* If anything went wrong, try to delete the renamed entry. */
+         } else {
+            entry = FreeMapEntry( entry, status );
+         }
+      }
+   }
+}
+
+static int MapSize( AstKeyMap *this, int *status ) {
+/*
+*++
+*  Name:
+c     astMapSize
+f     AST_MAPSIZE
+
+*  Purpose:
+*     Get the number of entries in a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "keymap.h"
+c     int astMapSize( AstKeyMap *this )
+f     RESULT = AST_MAPSIZE( THIS, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function returns the number of entries in a KeyMap.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapSize()
+f     AST_MAPSIZE = INTEGER
+*        The number of entries in the KeyMap.
+
+*  Notes:
+*     - A function value of zero will be returned if an error has already
+*     occurred, or if this function should fail for any reason.
+
+*--
+*/
+
+/* Local Variables: */
+   int itab;               /* Index of hash table element to use */
+   int result;             /* Returned value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Add up the number of entries in all elements of the hash table. */
+   for( itab = 0; itab < this->mapsize; itab++ ) result += this->nentry[ itab ];
+
+/* Return the result. */
+   return result;
+
+}
+
+static int MapLenC( AstKeyMap *this, const char *skey, int *status ) {
+/*
+*++
+*  Name:
+c     astMapLenC
+f     AST_MAPLENC
+
+*  Purpose:
+*     Get the number of characters in a character entry in a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "keymap.h"
+c     int astMapLenC( AstKeyMap *this, const char *key )
+f     RESULT = AST_MAPLENC( THIS, KEY, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function returns the minimum length which a character variable
+*     which must have in order to be able to store a specified entry in
+*     the supplied KeyMap. If the named entry is a vector entry, then the
+*     returned value is the length of the longest element of the vector
+*     value.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the KeyMap entry. Trailing
+*        spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapLenC()
+f     AST_MAPLENC = INTEGER
+*        The length (i.e. number of characters) of the longest formatted
+*        value associated with the named entry.
+c        This does not include the trailing null character.
+
+*  Notes:
+*     - A function value of zero will be returned without error if the
+*     named entry cannot be formatted as a character string.
+*     - A function value of zero will be returned if an error has already
+*     occurred, or if this function should fail for any reason.
+
+*--
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */
+   const char *key;        /* Pointer to key string to use */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   int i;                  /* Element index */
+   int itab;               /* Index of hash table element to use */
+   int l;                  /* Length of formatted vector element */
+   int nel;                /* Number of elements in raw vector */
+   int raw_type;           /* Data type of stored value */
+   int result;             /* Returned value */
+   size_t raw_size;        /* Size of a single raw value */
+   unsigned long hash;     /* Full width hash value */
+   void *raw;              /* Pointer to stored value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapLenC",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. */
+   mapentry = SearchTableEntry( this, itab, key, status );
+
+/* Skip rest if the key was not found. */
+   if( mapentry ) {
+
+/* Get the address of the first raw value, and its data type. Also get
+   the size of each element of the vector. */
+      nel = mapentry->nel;
+      raw_type = mapentry->type;
+      if( raw_type == AST__INTTYPE ){
+         raw_size = sizeof( int );
+         if( nel == 0 ) {
+            raw = &( ((Entry0I *)mapentry)->value );
+         } else {
+            raw = ((Entry1I *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__POINTERTYPE ){
+         raw_size = sizeof( void * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0P *)mapentry)->value );
+         } else {
+            raw = ((Entry1P *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__DOUBLETYPE ){
+         raw_size = sizeof( double );
+         if( nel == 0 ) {
+            raw = &( ((Entry0D *)mapentry)->value );
+         } else {
+            raw = ((Entry1D *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__SINTTYPE ){
+         raw_size = sizeof( short int );
+         if( nel == 0 ) {
+            raw = &( ((Entry0S *)mapentry)->value );
+         } else {
+            raw = ((Entry1S *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__BYTETYPE ){
+         raw_size = sizeof( unsigned char );
+         if( nel == 0 ) {
+            raw = &( ((Entry0B *)mapentry)->value );
+         } else {
+            raw = ((Entry1B *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__FLOATTYPE ){
+         raw_size = sizeof( float );
+         if( nel == 0 ) {
+            raw = &( ((Entry0F *)mapentry)->value );
+         } else {
+            raw = ((Entry1F *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__STRINGTYPE ){
+         raw_size = sizeof( const char * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0C *)mapentry)->value );
+         } else {
+            raw = ((Entry1C *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__OBJECTTYPE ){
+         raw_size = sizeof( AstObject * );
+         if( nel == 0 ) {
+            raw = &( ((Entry0A *)mapentry)->value );
+         } else {
+            raw = ((Entry1A *)mapentry)->value;
+         }
+
+      } else if( raw_type == AST__UNDEFTYPE ){
+         raw_size = 0;
+         raw = NULL;
+
+      } else {
+         raw_size = 0;
+         raw = NULL;
+         astError( AST__INTER, "astMapLenC(KeyMap): Illegal map entry data "
+                   "type %d encountered (internal AST programming error).", status,
+                   raw_type );
+      }
+
+/* Treat scalars as single-value vectors. */
+      if( nel == 0 ) nel = 1;
+
+/* Skip undefined values. */
+      if( raw ) {
+
+/* Initialise the maximum length of any formatted value in the entry. */
+         result= 0;
+
+/* Loop round all values in the vector. */
+         for( i = 0; i < nel && astOK; i++ ) {
+
+/* Go through the motions of formatting the value. We do not actually
+   need the formatted string (just its length) so we provide a NULL pointer
+   for the output buffer. The entry is ignored if it cannot be formatted.
+   Note, the length returned by ConvertValue includes the terminating null,
+   so decrement it first. */
+            l = ConvertValue( raw, raw_type, NULL, AST__STRINGTYPE, status );
+            if( --l > result ) result = l;
+
+/* Increment the pointer to the next raw value. */
+            raw = (char *) raw + raw_size;
+         }
+      }
+   }
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+
+}
+
+static int MapLength( AstKeyMap *this, const char *skey, int *status ) {
+/*
+*++
+*  Name:
+c     astMapLength
+f     AST_MAPLENGTH
+
+*  Purpose:
+*     Get the vector length of an entry in a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "keymap.h"
+c     int astMapLength( AstKeyMap *this, const char *key )
+f     RESULT = AST_MAPLENGTH( THIS, KEY, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function returns the vector length of a named entry in a KeyMap,
+*     (that is, how many values are associated with the entry).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the KeyMap entry. Trailing
+*        spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapLength()
+f     AST_MAPLENGTH = INTEGER
+*        The length of the entry. One for a scalar, greater than one for
+*        a vector. A value of zero is returned if the KeyMap does not
+*        contain the named entry.
+
+*  Notes:
+*     - A function value of zero will be returned if an error has already
+*     occurred, or if this function should fail for any reason.
+
+*--
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to entry in linked list */
+   const char *key;        /* Pointer to key string to use */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   int itab;               /* Index of hash table element to use */
+   int result;             /* Returned value */
+   unsigned long hash;     /* Full width hash value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapLength",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. */
+   mapentry = SearchTableEntry( this, itab, key, status );
+
+/* Skip rest if the key was not found. */
+   if( mapentry ) {
+
+/* Store the netry length */
+      result = mapentry->nel;
+
+/* Return 1 for a scalar. */
+      if( result == 0 ) result = 1;
+
+   }
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+
+}
+
+/*
+*++
+*  Name:
+c     astMapPutElem<X>
+f     AST_MAPPUTELEM<X>
+
+*  Purpose:
+*     Put a value into an element of a vector value in a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "ast.h"
+c     void astMapPutElem<X>( AstKeyMap *this, const char *key, int elem,
+c                            <X>type *value )
+f     CALL AST_MAPPUTELEM<X>( THIS, KEY, ELEM, VALUE, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This is a set of functions for storing a value in a single element of
+*     a vector value in a KeyMap. You should replace <X> in the generic
+*     function name
+c     astMapPutElem<X>
+f     AST_MAPPUTELEM<X>
+*     by an appropriate 1-character type code (see the "Data Type Codes"
+*     section below for the code appropriate to each supported data type).
+*     The supplied value is converted from the data type indicated by <X>
+*     to the data type of the KeyMap entry before being stored (an error
+*     is reported if it is not possible to convert the value to the
+*     required data type).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the value to be retrieved. Trailing
+*        spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+c     elem
+f     ELEM = INTEGER (Given)
+*        The index of the vector element to modify, starting at
+c        zero.
+f        one.
+c     value
+f     VALUE = <X>type (Given)
+*        The value to store.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     KeyMap
+*        If the
+c        "elem"
+f        ELEM
+*        index is outside the range of the vector, the length of
+*        the vector will be increased by one element and the supplied
+*        value will be stored at the end of the vector in the new element.
+*     Table
+*        If the
+c        "elem"
+f        ELEM
+*        index is outside the range of the vector, an error will be
+*        reported. The number of elements in each cell of a column is
+*        specified when the column is created using
+c        astAddColumn.
+f        AST_ADDCOLUMN.
+
+*  Notes:
+*     - If the entry originally holds a scalar value, it will be treated
+*     like a vector entry of length 1.
+*     - If the specified key cannot be found in the given KeyMap, or is
+*     found but has an undefined value, a new
+*     vector entry with the given name, and data type implied by <X>, is
+*     created and the supplied value is stored in its first entry.
+
+*  Data Type Codes:
+*     To select the appropriate
+c     function, you should replace <X> in the generic function name
+c     astMapPutElem<X>
+f     routine, you should replace <X> in the generic routine name
+f     AST_MAPPUTELEM<X>
+*     with a 1-character data type code, so as to match the data type <X>type
+*     of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - I: int
+c     - C: "const" pointer to null terminated character string
+c     - A: Pointer to AstObject
+c     - P: Generic "void *" pointer
+c     - S: short int
+c     - B: Unsigned byte (i.e. char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - C: CHARACTER
+f     - A: INTEGER used to identify an AstObject
+f     - S: INTEGER*2 (short integer)
+f     - B: BYTE (unsigned)
+*
+c     For example, astMapPutElemD would be used to put a "double" value, while
+c     astMapPutElemI would be used to put an "int" value, etc. For D or I, the
+c     supplied "value" parameter should be a double or int. For
+c     C, the supplied "value" parameter should be a pointer to a character
+c     string. For A, the supplied "value" parameter should be an AstObject
+c     pointer.
+f     For example, AST_MAPPUTELEMD would be used to put a DOUBLE PRECISION
+f     value, while AST_MAPPUTELEMI would be used to put an INTEGER value, etc.
+
+*--
+*/
+/* Define a macro to implement the function for a specific data type
+(excluding "C" since that needs an extra parameter). */
+#define MAKE_MAPPUTELEM(X,Xtype,Itype) \
+static void MapPutElem##X( AstKeyMap *this, const char *skey, int elem, \
+                           Xtype value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstMapEntry *mapentry;  /* Pointer to parent MapEntry structure */ \
+   const char *key;        /* Pointer to key string to use */ \
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */ \
+   int itab;               /* Index of hash table element to use */ \
+   int nel;                /* Number of elements in raw vector */ \
+   int new;                /* Was a new uninitialised element created? */ \
+   int raw_type;           /* Data type of stored value */ \
+   size_t raw_size;        /* Size of a single raw value */ \
+   unsigned long hash;     /* Full width hash value */ \
+   void *raw;              /* Pointer to stored value */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Perform any necessary checks on the supplied value to be stored. */ \
+   CHECK_##X \
+\
+/* Convert the supplied key to upper case if required. */ \
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapPutElem" #X, \
+                     status ); \
+\
+/* Use the hash function to determine the element of the hash table in \
+   which the key will be stored. */ \
+   itab = HashFun( key, this->mapsize - 1, &hash, status ); \
+\
+/* Search the relevent table entry for the required MapEntry. */ \
+   mapentry = SearchTableEntry( this, itab, key, status ); \
+\
+/* If the key was not found, or was found but has an undefined value, create \
+   a new one with a single element, \
+   and store the supplied value in it. */ \
+   if( !mapentry || mapentry->type == AST__UNDEFTYPE ) { \
+      astMapPut1##X( this, key, 1, &value, NULL ); \
+\
+/* If the key was found.... */ \
+   } else { \
+\
+/* Get the current length of the vector (0=>scalar), and the data type. */ \
+      nel = mapentry->nel; \
+      raw_type = mapentry->type; \
+\
+/* Do each data type in turn. */ \
+      if( raw_type == AST__INTTYPE ){ \
+\
+/* If the existing entry is scalar, create a new vector entry with the \
+   same name, value, data type and comment. Then get a pointer to the new \
+   entry, and indicate that we now have a vector entry of length 1. */ \
+         if( nel == 0 ) { \
+            astMapPut1I( this, key, 1, &( ((Entry0I *)mapentry)->value ), \
+                         mapentry->comment ); \
+            mapentry = SearchTableEntry( this, itab, key, status ); \
+            nel = 1; \
+         } \
+\
+/* Get the address of the first raw value in the vector. Also get \
+   the size of each element of the vector. */ \
+         raw = ((Entry1I *)mapentry)->value; \
+         raw_size = sizeof( int ); \
+\
+/* Handle other data type in the same way. */ \
+      } else if( raw_type == AST__SINTTYPE ){ \
+         if( nel == 0 ) { \
+            astMapPut1S( this, key, 1, &( ((Entry0S *)mapentry)->value ), \
+                         mapentry->comment ); \
+            mapentry = SearchTableEntry( this, itab, key, status ); \
+            nel = 1; \
+         } \
+         raw = ((Entry1S *)mapentry)->value; \
+         raw_size = sizeof( short int ); \
+\
+      } else if( raw_type == AST__BYTETYPE ){ \
+         if( nel == 0 ) { \
+            astMapPut1B( this, key, 1, &( ((Entry0B *)mapentry)->value ), \
+                         mapentry->comment ); \
+            mapentry = SearchTableEntry( this, itab, key, status ); \
+            nel = 1; \
+         } \
+         raw = ((Entry1B *)mapentry)->value; \
+         raw_size = sizeof( unsigned char ); \
+\
+      } else if( raw_type == AST__DOUBLETYPE ){ \
+         if( nel == 0 ) { \
+            astMapPut1D( this, key, 1, &( ((Entry0D *)mapentry)->value ), \
+                         mapentry->comment ); \
+            mapentry = SearchTableEntry( this, itab, key, status ); \
+            nel = 1; \
+         } \
+         raw = ((Entry1D *)mapentry)->value; \
+         raw_size = sizeof( double ); \
+\
+      } else if( raw_type == AST__POINTERTYPE ){ \
+         if( nel == 0 ) { \
+            astMapPut1P( this, key, 1, &( ((Entry0P *)mapentry)->value ), \
+                         mapentry->comment ); \
+            mapentry = SearchTableEntry( this, itab, key, status ); \
+            nel = 1; \
+         } \
+         raw = ((Entry1P *)mapentry)->value; \
+         raw_size = sizeof( void * ); \
+\
+      } else if( raw_type == AST__FLOATTYPE ){ \
+         if( nel == 0 ) { \
+            astMapPut1F( this, key, 1, &( ((Entry0F *)mapentry)->value ), \
+                         mapentry->comment ); \
+            mapentry = SearchTableEntry( this, itab, key, status ); \
+            nel = 1; \
+         } \
+         raw = ((Entry1F *)mapentry)->value; \
+         raw_size = sizeof( float ); \
+\
+      } else if( raw_type == AST__STRINGTYPE ){ \
+         if( nel == 0 ) { \
+            astMapPut1C( this, key, 1, &( ((Entry0C *)mapentry)->value ), \
+                         mapentry->comment ); \
+            mapentry = SearchTableEntry( this, itab, key, status ); \
+            nel = 1; \
+         } \
+         raw = ((Entry1C *)mapentry)->value; \
+         raw_size = sizeof( const char * ); \
+\
+      } else if( raw_type == AST__OBJECTTYPE ){ \
+         if( nel == 0 ) { \
+            astMapPut1A( this, key, 1, &( ((Entry0A *)mapentry)->value ), \
+                         mapentry->comment ); \
+            mapentry = SearchTableEntry( this, itab, key, status ); \
+            nel = 1; \
+         } \
+         raw = ((Entry1A *)mapentry)->value; \
+         raw_size = sizeof( AstObject * ); \
+\
+      } else { \
+         raw_size = 0; \
+         raw = NULL; \
+         astError( AST__INTER, "astMapPutElem<X>(KeyMap): Illegal map entry " \
+                   "data type %d encountered (internal AST programming " \
+                   "error).", status, raw_type ); \
+      } \
+\
+/* If the requested element is outside the bounds of the vector, extend \
+   the vector by one element. */ \
+      new = ( elem >= nel || elem < 0 ); \
+      if( new ) { \
+         elem = nel++; \
+         raw = astGrow( raw, nel, raw_size ); \
+         if( astOK ) { \
+            mapentry->nel = nel; \
+            if( raw_type == AST__INTTYPE ){ \
+               ((Entry1I *)mapentry)->value = (int *) raw; \
+            } else if( raw_type == AST__SINTTYPE ){ \
+               ((Entry1S *)mapentry)->value = (short int *) raw; \
+            } else if( raw_type == AST__BYTETYPE ){ \
+               ((Entry1B *)mapentry)->value = (unsigned char *) raw; \
+            } else if( raw_type == AST__DOUBLETYPE ){ \
+               ((Entry1D *)mapentry)->value = (double *) raw; \
+            } else if( raw_type == AST__POINTERTYPE ){ \
+               ((Entry1P *)mapentry)->value = (void *) raw; \
+            } else if( raw_type == AST__FLOATTYPE ){ \
+               ((Entry1F *)mapentry)->value = (float *) raw; \
+            } else if( raw_type == AST__STRINGTYPE ){ \
+               ((Entry1C *)mapentry)->value = (const char **) raw; \
+            } else if( raw_type == AST__OBJECTTYPE ){ \
+               ((Entry1A *)mapentry)->value = (AstObject **) raw; \
+            } \
+         } \
+      } \
+\
+/* Get a pointer to the requested element. */ \
+      if( astOK ) { \
+         raw = (char *) raw + elem*raw_size; \
+\
+/* Free any memory used by the value already in the requested element. */ \
+         if( ! new ) { \
+            if( raw_type == AST__STRINGTYPE ){ \
+               char **cp = (char **) raw; \
+               *cp = astFree( *cp ); \
+            } else if( raw_type == AST__OBJECTTYPE ){ \
+               AstObject **op = (AstObject **) raw; \
+               if( *op ) *op = astAnnul( *op ); \
+            } \
+         } \
+\
+/* Convert the supplied value, storing the result in the requested element. \
+   Report an error if conversion is not possible. */ \
+         if( !ConvertValue( &value, Itype, raw, raw_type, status ) && astOK ){ \
+            astError( AST__MPPER, "astMapPutElem" #X "(%s): The supplied " \
+                      "value cannot be converted to the data type of " \
+                      "KeyMap key \"%s\".", status, astGetClass( this ), \
+                      key ); \
+\
+/* For strings, the "raw" value is a copy of a pointer stored in the global \
+   "convertvalue_strings" array. These pointers should never be freed other \
+   than within the ConvertValue function (otherwise you can end up with \
+   spurious "invalid pointer" errors). But the "raw" value will be freed \
+   when as part of the KeyMap when the KeyMap is destroyed. So we replace \
+   the "raw" value with a new copy. */ \
+         } else if( raw_type == AST__STRINGTYPE ){ \
+            char **cp = (char **) raw; \
+            *cp = astStore( NULL, *cp, strlen( *cp ) + 1 ); \
+         } \
+      } \
+   } \
+}
+
+/* Define macros which perform any necessary checks on the supplied value
+   to be stored. For Object entries, check that we are not adding a KeyMap
+   which already contains "this". This avoids circular dependencies.
+   Other types do not need any checks. */
+#define CHECK_A CheckCircle( this, value, "astMapPutElemA", status );
+#define CHECK_I
+#define CHECK_B
+#define CHECK_S
+#define CHECK_D
+#define CHECK_F
+#define CHECK_C
+#define CHECK_P
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+MAKE_MAPPUTELEM(I,int,AST__INTTYPE)
+MAKE_MAPPUTELEM(D,double,AST__DOUBLETYPE)
+MAKE_MAPPUTELEM(F,float,AST__FLOATTYPE)
+MAKE_MAPPUTELEM(A,AstObject *,AST__OBJECTTYPE)
+MAKE_MAPPUTELEM(P,void *,AST__POINTERTYPE)
+MAKE_MAPPUTELEM(C,const char *,AST__STRINGTYPE)
+MAKE_MAPPUTELEM(S,short int,AST__SINTTYPE)
+MAKE_MAPPUTELEM(B,unsigned char,AST__BYTETYPE)
+
+/* Undefine the macro. */
+#undef MAKE_MAPPUTELEM
+#undef CHECK_A
+#undef CHECK_I
+#undef CHECK_B
+#undef CHECK_S
+#undef CHECK_D
+#undef CHECK_F
+#undef CHECK_C
+#undef CHECK_P
+
+
+static int MapType( AstKeyMap *this, const char *skey, int *status ) {
+/*
+*++
+*  Name:
+c     astMapType
+f     AST_MAPTYPE
+
+*  Purpose:
+*     Get the data type of an entry in a KeyMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "keymap.h"
+c     int astMapType( AstKeyMap *this, const char *key )
+f     RESULT = AST_MAPTYPE( THIS, KEY, STATUS )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function returns a value indicating the data type of a
+*     named entry in a KeyMap. This is the data type which was used when the
+*     entry was added to the KeyMap.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the KeyMap.
+c     key
+f     KEY = CHARACTER * ( * ) (Given)
+*        The character string identifying the KeyMap entry. Trailing
+*        spaces are ignored.
+*        The supplied string is converted to upper case before use if the
+*        KeyCase attribute is currently set to zero.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapType()
+f     AST_MAPTYPE = INTEGER
+*        One of AST__INTTYPE (for integer), AST__SINTTYPE (for
+c        short int),
+f        INTEGER*2),
+*        AST__BYTETYPE (for unsigned bytes
+c        - i.e. unsigned chars
+*        ) AST__DOUBLETYPE (for double
+*        precision floating point), AST__FLOATTYPE (for single
+*        precision floating point), AST__STRINGTYPE (for character string),
+*        AST__OBJECTTYPE (for AST Object pointer), AST__POINTERTYPE (for
+*        arbitrary C pointer) or AST__UNDEFTYPE (for undefined values
+*        created by
+c        astMapPutU).
+f        AST_MAPPUTU).
+*        AST__BADTYPE is returned if the supplied key is not found in the KeyMap.
+
+*  Notes:
+*     - A function value of AST__BADTYPE will be returned if an error has
+*     already occurred, or if this function should fail for any reason.
+
+*--
+*/
+
+/* Local Variables: */
+   AstMapEntry *mapentry;  /* Pointer to entry in linked list */
+   const char *key;        /* Pointer to key string to use */
+   char keybuf[ AST__MXKEYLEN + 1 ]; /* Buffer for upper cas key */
+   int itab;               /* Index of hash table element to use */
+   int result;             /* Returned value */
+   unsigned long hash;     /* Full width hash value */
+
+/* Initialise */
+   result = AST__BADTYPE;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Convert the supplied key to upper case if required. */
+   key = ConvertKey( this, skey, keybuf, AST__MXKEYLEN + 1, "astMapType",
+                     status );
+
+/* Use the hash function to determine the element of the hash table in
+   which the key will be stored. */
+   itab = HashFun( key, this->mapsize - 1, &hash, status );
+
+/* Search the relevent table entry for the required MapEntry. */
+   mapentry = SearchTableEntry( this, itab, key, status );
+
+/* Store the type if found. */
+   if( mapentry ) result = mapentry->type;
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) result = AST__BADTYPE;
+
+/* Return the result. */
+   return result;
+
+}
+
+static const char *MapIterate( AstKeyMap *this, int reset, int *status ) {
+/*
+*+
+*  Name:
+*     astMapIterate
+
+*  Purpose:
+*     Iterate through the keys in a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     const char *astMapIterate( AstKeyMap *this, int reset, int *status )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     If "reset" is non-zero, this function returns a pointer to a string
+*     holding the first key in the KeyMap. On subsequent invocation (if
+*     reset is zero) it returns a pointer to the next key in the KeyMap. The
+*     context is stored within the KeyMap structure, so calls on different
+*     KeyMaps can be mixed.
+*
+*     The order in which keys are returned is determined by the KeyMap
+*     SortBy attribute.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     reset
+*        If non-zero, return the first key in the KeyMap. Otherwise,
+*        returns the key following the one returned by the previous
+*        invocation of this function.
+
+*  Returned Value:
+*        A pointer to the null-terminated string holding the next key,
+*        or NULL if there are no more keys in the KeyMap. The returned
+*        string should NOT be freed or modified.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the AST error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapEntry *entry;         /* Pointer to the entry */
+   const char *key;            /* Pointer value to return */
+   int itab;                   /* Index into hash table */
+   int sortby;                 /* The value of the SortBy attribute */
+
+/* Initialise. */
+   key = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return key;
+
+/* Get the SortBy value. */
+   sortby = astGetSortBy( this );
+
+/* First deal with unsorted keys. */
+   if( sortby == SORTBY_NONE ) {
+
+/* Get the index of the hash table to check first. Also get a pointer to
+   the entry within the hash table to check next. */
+      if( reset ){
+         itab = 0;
+         entry = this->table[ 0 ];
+      } else {
+         itab = this->iter_itab;
+         entry = this->iter_entry;
+      }
+
+/* Move through elements of the hash table until we have a non-null entry. */
+      while( !entry && ++itab < this->mapsize ) {
+         entry = this->table[ itab ];
+      }
+
+/* Return a pointer to the key. */
+      if( entry ) {
+         key = entry->key;
+
+/* Move on to the next entry in the unsorted linked list, saving the context
+   in the KeyMap structure. */
+         this->iter_itab = itab;
+         this->iter_entry = entry->next;
+      }
+
+/* Now deal with sorted keys. */
+   } else {
+
+/* If starting from the beginning, use the "first" entry. Otherwise, use
+   the nxt entry. */
+      if( reset ) {
+         entry = this->first;
+      } else {
+         entry = this->iter_entry;
+      }
+
+/* If we have an entry, return a pointer to its key, and then update the
+   context to point to the next entry in the *sorted* list. */
+      if( entry ) {
+         key = entry->key;
+         this->iter_entry = entry->snext;
+      }
+   }
+
+/* If no more entries were found, reset the context in the KeyMap
+   structure. */
+   if( ! key ) {
+      this->iter_itab = 0;
+      this->iter_entry = NULL;
+   }
+
+/* Return the result.*/
+   return key;
+}
+
+static void NewTable( AstKeyMap *this, int size, int *status ){
+/*
+*  Name:
+*     NewTable
+
+*  Purpose:
+*     Create a new hash table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void NewTable( AstKeyMap *this, int size, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function removes any existing hash table and allocates memory
+*     for a new one of the specified size (except that the supplied size
+*     is modified to be the next higher power of 2). The table is
+*     initialised to indicate that it is empty.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     size
+*        The reuqired size of the hash table.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int i;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Ensure the table size is at least MIN_TABLE_SIZE and is a power of 2. */
+   if( size <= MIN_TABLE_SIZE ) {
+      size = MIN_TABLE_SIZE;
+   } else {
+      size = (int) ( 0.5 + pow( 2.0, ceil( log( size )/log( 2.0 ) ) ) );
+   }
+
+/* Remove any existing entries. */
+   for( i = 0; i < this->mapsize; i++ ) FreeTableEntry( this, i, status );
+
+/* Do nothing more if the table size is not changing. */
+   if( size != this->mapsize ) {
+
+/* Modify the size of the existing table. */
+      this->mapsize = size;
+      this->table = astGrow( this->table, size, sizeof( AstMapEntry * ) );
+      this->nentry = astGrow( this->nentry, size, sizeof( int ) );
+
+/* Initialise the new table. */
+      if( astOK ) {
+         for( i = 0; i < size; i++ ) {
+            this->table[ i ] = NULL;
+            this->nentry[ i ] = 0;
+         }
+      }
+   }
+}
+
+static void RemoveFromObjectList( AstKeyMap *this, AstMapEntry *entry,
+                                  int *status ){
+/*
+*  Name:
+*     RemoveFromObjectList
+
+*  Purpose:
+*     Remove an entry from the linked-list of AST__OBJECTTYPE entries.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void RemoveFromObjectList( AstKeyMap *this, AstMapEntry *entry,
+*                                int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function removes the supplied MapEntry from the linked list of
+*     AST__OBJECTTYPE entries.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     entry
+*        Pointer to the MapEntry to be removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMapEntry *a;          /* Previous entry */
+   AstMapEntry *b;          /* Next entry */
+   Entry0A *scalar;         /* Pointer to a scalar AST__OBJECTTYPE entry */
+   Entry1A *vector;         /* Pointer to a vector AST__OBJECTTYPE entry */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Do nothing if the entry does not hold AST Object pointers. */
+   if( entry->type == AST__OBJECTTYPE ) {
+
+/* Get pointers to the MapEntries before and after the entry being
+   removed. At the same time, nullify both pointers in the entry itself. */
+      if( entry->nel == 0 ) {
+         scalar = (Entry0A *) entry;
+         a = scalar->prev;
+         b = scalar->next;
+         scalar->prev = NULL;
+         scalar->next = NULL;
+      } else {
+         vector = (Entry1A *) entry;
+         a = vector->prev;
+         b = vector->next;
+         vector->prev = NULL;
+         vector->next = NULL;
+      }
+
+/* Set the forward link in the previous entry. */
+      if( a ) {
+         if( a->nel == 0 ) {
+            scalar = (Entry0A *) a;
+            scalar->next = b;
+         } else {
+            vector = (Entry1A *) a;
+            vector->next = b;
+         }
+
+/* If we are removing the list head, store the following entry as the new head. */
+      } else {
+         this->firstA = b;
+      }
+
+/* Set the backward link in the next entry. */
+      if( b ) {
+         if( b->nel == 0 ) {
+            scalar = (Entry0A *) b;
+            scalar->prev = a;
+         } else {
+            vector = (Entry1A *) b;
+            vector->prev = a;
+         }
+      }
+   }
+}
+
+static void RemoveFromSortedList( AstKeyMap *this, AstMapEntry *entry,
+                                  int *status ){
+/*
+*  Name:
+*     RemoveFromSortedList
+
+*  Purpose:
+*     Remove an entry from the linked-list of sorted KeyMap entries.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void RemoveFromSortedList( AstKeyMap *this, AstMapEntry *entry,
+*                                int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function removes the supplied MapEntry from the linked list of
+*     sorted MapEntries.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     entry
+*        Pointer to the MapEntry to be removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMapEntry *next;       /* Next higher MapEntry */
+   AstMapEntry *prev;       /* Next lower MapEntry */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get pointers to the entries on either side of the entry to be removed. */
+   next = entry->snext;
+   prev = entry->sprev;
+
+/* If the entry is not in the sorted list, abort. */
+   if( next && prev ) {
+
+/* Connect the previous to the next, bypassing the entry being removed. */
+      next->sprev = prev;
+      prev->snext = next;
+
+/* NULLify the next and previous entries stored in the entry being
+   removed. */
+      entry->snext = NULL;
+      entry->sprev = NULL;
+
+/* Decrement the number of entries in the sorted list. */
+      (this->nsorted)--;
+
+/* If the entry being removed is the first entry, store a pointer to the new
+   first entry. */
+      if( this->nsorted == 0 ) {
+         this->first = NULL;
+      } else if( entry == this->first ) {
+         this->first = next;
+      }
+   }
+}
+
+static AstMapEntry *RemoveTableEntry( AstKeyMap *this, int itab,
+                                      const char *key, int *status ){
+/*
+*  Name:
+*     RemoveTableEntry
+
+*  Purpose:
+*     Remove an entry from a linked-list of KeyMap entries.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     AstMapEntry *RemoveTableEntry( AstKeyMap *this, int itab,
+*                                    const char *key, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function removes any entries with the specified key from the
+*     linked-list of entries stored at the specified entry of the hash
+*     table. If the supplied key is found in the list, a pointer to the
+*     first removed entry is returned. Otherwise, a NULL pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     itab
+*        Index of the hash table element to be searched.
+*     key
+*        The key string to be searched for. Trailing spaces are ignored.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the removed Entry, or NULL if no matching entry found.
+
+*/
+
+/* Local Variables: */
+   AstMapEntry **link;    /* Address to store foward link */
+   AstMapEntry *next;     /* Pointer to next Entry to copy */
+   AstMapEntry *result;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* The "next" variable holds the address of the next MapEntry to be
+   checked. Initialise this to the MapEntry at the head of the linked
+   list associated with the supplied element of the hash table. */
+   next = this->table[ itab ];
+
+/* The "link" variable holds the address of the location at which the
+   pointer to the MapEntry following the removed MapEntry should be stored.
+   Initialise this to be the address of the hash table element. */
+   link = &( this->table[ itab ] );
+
+/* Loop round until we have checked all entries. */
+   while( next && astOK ) {
+
+/* If the key for the current entry macthes the supplied key... */
+      if( !KeyCmp( next->key, key ) ) {
+
+/* Remove the MapEntry from the list sorted by key. */
+         RemoveFromSortedList( this, next, status );
+
+/* If the entry is of type AST__OBJECTTYPE, remove it from the
+   list of AST__OBJECTTYPE entries. */
+         RemoveFromObjectList( this, next, status );
+
+/* Store a pointer to the next MapEntry in the list, replacing the
+   original pointer to the MapEntry which is being deleted. */
+         *link = next->next;
+
+/* Return a pointer to the first matching MapEntry. Free any subsequent
+   matching MapEntries.  */
+         if( result ) {
+            FreeMapEntry( next, status );
+         } else {
+            result = next;
+         }
+
+/* Decrement the number of entries in the linked list. */
+         this->nentry[ itab ]--;
+
+/* Set up the next MapEntry to be freed. */
+         next = *link;
+
+/* If the key for the current entry does not match the supplied key... */
+      } else {
+
+/* Update the address at which to store the pointer to the next MapEntry
+   in the list. */
+         link = &(next->next);
+
+/* Update the address of the next MapEntry in the list. */
+         next = next->next;
+      }
+   }
+
+/* Return the result */
+   return result;
+}
+
+static AstMapEntry *SearchTableEntry( AstKeyMap *this, int itab, const char *key, int *status ){
+/*
+*  Name:
+*     SearchTableEntry
+
+*  Purpose:
+*     Search an element of a has table for a given key.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     AstMapEntry *SearchTableEntry( AstKeyMap *this, int itab, const char *key, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function searches the specified element of the KeyMaps hash table
+*     until an element is found which has a key matching the supplied key.
+*     The address of this entry is returned. If no suitable entry is found,
+*     then NULL is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     itab
+*        The index of the hash table to be searched.
+*     key
+*        The key string to be searched for. Trailing spaces are ignored.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The address of the first MapEntry in the linked list which refers
+*     to the given key, or NULL if the key is not found.
+
+*/
+
+/* Local Variables: */
+   AstMapEntry *next;     /* Pointer to next Entry to check */
+   AstMapEntry *result;   /* Returned pointer */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* The "next" variable holds the address of the next MapEntry to be
+   checked. Initialise this to the supplied MapEntry. */
+   next = this->table[ itab ];
+
+/* Loop round until we have checked all entries. */
+   while( next ) {
+
+/* If the key for the current entry matches the supplied key, store the
+   MapEntry pointer and break. */
+      if( !KeyCmp( next->key, key ) ) {
+         result = next;
+         break;
+      }
+
+/* Update the address of the next MapEntry in the list. */
+      next = next->next;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     KeyMap member function (over-rides the astSetAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a KeyMap, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstKeyMap *this;             /* Pointer to the KeyMap structure */
+   int ival;                    /* Attribute value */
+   int len;                     /* Length of setting string */
+   int nc;                      /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the KeyMap structure. */
+   this = (AstKeyMap *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* SizeGuess. */
+/* ---------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "sizeguess= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetSizeGuess( this, ival );
+
+/* KeyCase. */
+/* --------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "keycase= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetKeyCase( this, ival );
+
+/* KeyError. */
+/* --------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "keyerror= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetKeyError( this, ival );
+
+/* MapLocked. */
+/* --------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "maplocked= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetMapLocked( this, ival );
+
+/* SortBy. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "sortby= %n%*s %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetSortBy( this, SortByInt( setting + ival, "astSetAttrib", status ) );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SetKeyCase( AstKeyMap *this, int keycase, int *status ) {
+/*
+*+
+*  Name:
+*     astSetKeyCase
+
+*  Purpose:
+*     Set the value of the KeyCase attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astSetKeyCase( AstKeyMap *this, int keycase )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function sets a new value for the KeyCase attribute of a
+*     KeyMap. It reports an error if the KeyMap contains any entries.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     keycase
+*        The new attribute value.
+
+*-
+*/
+
+/* Local Variables: */
+   int ok;                    /* Can the KeyCase value be changed? */
+   int itab;                  /* Index into hash table */
+   int newval;                /* New KeyCase value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Normalise the new value */
+   newval = keycase ? 1 : 0;
+
+/* If the KeyCase value is to be changed, see if the KeyMap is empty. */
+   ok = 1;
+   if( astGetKeyCase( this ) != newval ) {
+      for( itab = 0; itab < this->mapsize; itab++ ) {
+         if( this->nentry[ itab ] > 0 ) {
+            ok = 0;
+            break;
+         }
+      }
+   }
+
+/* If not report an error. */
+   if( !ok ) {
+      astError( AST__NOWRT, "astSetAttrib(KeyMap): Illegal attempt to "
+                "change the KeyCase attribute of a non-empty KeyMap." , status);
+
+/* Otherwise, store the new value. */
+   } else {
+      this->keycase = newval;
+   }
+}
+
+static void SetKeyError( AstKeyMap *this, int keyerror, int *status ) {
+/*
+*+
+*  Name:
+*     astSetKeyError
+
+*  Purpose:
+*     Set the value of the KeyError attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astSetKeyError( AstKeyMap *this, int keyerror )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function sets the value of the KeyError attribute for a
+*     KeyMap. It also sets the attribute recursively in any KeyMaps
+*     contained within the supplied KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     keyerror
+*        The new value for the attribute.
+*-
+*/
+
+/* Local Variables: */
+   AstMapEntry *next;     /* Pointer to next Entry to copy */
+   AstObject **obj_list;  /* List of pointers to AST Object entries */
+   int i;                 /* Index into hash table */
+   int iel;               /* Index of current vector element */
+   int nel;               /* Number of elements in vector */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Set the KeyError value in the supplied KeyMap. */
+   this->keyerror = keyerror ? 1 : 0;
+
+/* Loop round each entry in the hash table. */
+   for( i = 0; i < this->mapsize; i++ ) {
+
+/* Get a pointer to the next KeyMap entry. */
+      next = this->table[ i ];
+
+/* Loop round all entries in this element of the hash table. */
+      while( next && astOK ) {
+
+/* If this entry has an Object data type, see if holds any KeyMaps. */
+         if( next->type == AST__OBJECTTYPE ) {
+
+/* Get the number of objects to check, and a pointer to the first. */
+            nel = next->nel;
+            if( nel == 0 ) {
+               obj_list = &( ((Entry0A *)next)->value );
+               nel = 1;
+            } else {
+               obj_list = ((Entry1A *)next)->value;
+            }
+
+/* Loop round checking all Objects. */
+            for( iel = 0; iel < nel; iel++ ) {
+
+/* If this Object is a KeyMap, set its KeyError attribute. */
+               if( astIsAKeyMap( obj_list[ iel ] ) ) {
+                  astSetKeyError( (AstKeyMap *) obj_list[ iel ], keyerror );
+               }
+            }
+         }
+
+/* Get a pointer to the next entry. */
+         next = next->next;
+      }
+   }
+}
+
+static void SetMapLocked( AstKeyMap *this, int maplocked, int *status ) {
+/*
+*+
+*  Name:
+*     astSetMapLocked
+
+*  Purpose:
+*     Set the value of the MapLocked attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astSetMapLocked( AstKeyMap *this, int maplocked )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function sets the value of the MapLocked attribute for a
+*     KeyMap. It also sets the attribute recursively in any KeyMaps
+*     contained within the supplied KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     maplocked
+*        The new value for the attribute.
+*-
+*/
+
+/* Local Variables: */
+   AstMapEntry *next;     /* Pointer to next Entry to copy */
+   AstObject **obj_list;  /* List of pointers to AST Object entries */
+   int i;                 /* Index into hash table */
+   int iel;               /* Index of current vector element */
+   int nel;               /* Number of elements in vector */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Set the MapLocked value in the supplied KeyMap. */
+   this->maplocked = maplocked ? 1 : 0;
+
+/* Loop round each entry in the hash table. */
+   for( i = 0; i < this->mapsize; i++ ) {
+
+/* Get a pointer to the next KeyMap entry. */
+      next = this->table[ i ];
+
+/* Loop round all entries in this element of the hash table. */
+      while( next && astOK ) {
+
+/* If this entry has an Object data type, see if holds any KeyMaps. */
+         if( next->type == AST__OBJECTTYPE ) {
+
+/* Get the number of objects to check, and a pointer to the first. */
+            nel = next->nel;
+            if( nel == 0 ) {
+               obj_list = &( ((Entry0A *)next)->value );
+               nel = 1;
+            } else {
+               obj_list = ((Entry1A *)next)->value;
+            }
+
+/* Loop round checking all Objects. */
+            for( iel = 0; iel < nel; iel++ ) {
+
+/* If this Object is a KeyMap, set its MapLocked attribute. */
+               if( astIsAKeyMap( obj_list[ iel ] ) ) {
+                  astSetMapLocked( (AstKeyMap *) obj_list[ iel ], maplocked );
+               }
+            }
+         }
+
+/* Get a pointer to the next entry. */
+         next = next->next;
+      }
+   }
+}
+
+static void SetSizeGuess( AstKeyMap *this, int sizeguess, int *status ) {
+/*
+*+
+*  Name:
+*     astSetSizeGuess
+
+*  Purpose:
+*     Set the value of the SizeGuess attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astSetSizeGuess( AstKeyMap *this, int sizeguess )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function sets a new value for the SizeGuess attribute of a
+*     KeyMap. It reports an error if the KeyMap contains any entries.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     sizeguess
+*        The new attribute value.
+
+*-
+*/
+
+/* Local Variables: */
+   int empty;                 /* Is the KeyMap empty? */
+   int itab;                  /* Index into hash table */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* See if the KeyMap is empty. */
+   empty = 1;
+   for( itab = 0; itab < this->mapsize; itab++ ) {
+      if( this->nentry[ itab ] > 0 ) {
+         empty = 0;
+         break;
+      }
+   }
+
+/* If not report an error. */
+   if( !empty ) {
+      astError( AST__NOWRT, "astSetAttrib(KeyMap): Illegal attempt to "
+                "change the SizeGuess attribute of a non-empty KeyMap." , status);
+
+/* Otherwise, store the new value and change the size of the hash
+   table. */
+   } else {
+      this->sizeguess = sizeguess;
+      NewTable( this, sizeguess/MAX_ENTRIES_PER_TABLE_ENTRY, status );
+   }
+}
+
+static void SetSortBy( AstKeyMap *this, int sortby, int *status ) {
+/*
+*+
+*  Name:
+*     astSetSortBy
+
+*  Purpose:
+*     Set the value of the SortBy attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void astSetSortBy( AstKeyMap *this, int sortby )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function sets the value of the SortBy attribute for a
+*     KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     sortby
+*        The new value for the attribute.
+*-
+*/
+
+/* Local Variables: */
+   int oldval;            /* The old sortby value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the old SortBy value. */
+   oldval = astGetSortBy( this );
+
+/* Set the new SortBy value. */
+   this->sortby = sortby;
+
+/* If the value has changed, re-sort the keys. */
+   if( oldval != sortby ) SortEntries( this, status );
+
+}
+
+static size_t SizeOfEntry( AstMapEntry *entry, int *status ){
+/*
+*  Name:
+*     SizeOfEntry
+
+*  Purpose:
+*     Return the size of the supplied MapEntry structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     size_t SizeOfEntry( AstMapEntry *entry, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function returns the size of the supplied MapEntry structure.
+
+*  Parameters:
+*     entry
+*        Pointer to the MapEntry.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The size of the MapEntry structure. This does not include the size
+*     of any data for which pointers are stored in the MapEntry structure.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   size_t result;         /* Returned value */
+   int nel;               /* Entry length */
+   int type;              /* Data type */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status and the supplied pointer. */
+   if ( !astOK || !entry ) return result;
+
+/* Get the data type and length of the MapEntry. */
+   type = entry->type;
+   nel = entry->nel;
+
+/* Deal with each type. */
+   if( type == AST__STRINGTYPE ) {
+      result = ( nel == 0 ) ? sizeof( Entry0C ) : sizeof( Entry1C );
+
+   } else if( type == AST__OBJECTTYPE ) {
+      result = ( nel == 0 ) ? sizeof( Entry0A ) : sizeof( Entry1A );
+
+   } else if( type == AST__INTTYPE ) {
+      result = ( nel == 0 ) ? sizeof( Entry0I ) : sizeof( Entry1I );
+
+   } else if( type == AST__POINTERTYPE ) {
+      result = ( nel == 0 ) ? sizeof( Entry0P ) : sizeof( Entry1P );
+
+   } else if( type == AST__SINTTYPE ) {
+      result = ( nel == 0 ) ? sizeof( Entry0S ) : sizeof( Entry1S );
+
+   } else if( type == AST__BYTETYPE ) {
+      result = ( nel == 0 ) ? sizeof( Entry0B ) : sizeof( Entry1B );
+
+   } else if( type == AST__DOUBLETYPE ) {
+      result = ( nel == 0 ) ? sizeof( Entry0D ) : sizeof( Entry1D );
+
+   } else if( type == AST__FLOATTYPE ) {
+      result = ( nel == 0 ) ? sizeof( Entry0F ) : sizeof( Entry1F );
+
+   } else if( type == AST__UNDEFTYPE ) {
+      result = sizeof( AstMapEntry );
+
+/* Report an error if the data type is unknown. */
+   } else {
+      astError( AST__INTER, "SizeOfEntry(KeyMap): Illegal map entry data "
+                "type %d encountered (internal AST programming error).", status,
+                type );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int SortByInt( const char *sortby, const char *method, int *status ){
+/*
+*  Name:
+*     SortByInt
+
+*  Purpose:
+*     Get the integer associated with a string SortBy value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     int SortByInt( const char *sortby, const char *method, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function returns the integer associated with the supplied
+*     string SortBy value.
+
+*  Parameters:
+*     sortby
+*        Pointer to the string SortBy value (case insensitive).
+*     method
+*        Pointer to a string holding the name of the calling method for
+*        inclusion in error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The associated SortBy integer.
+
+*/
+
+/* Local Variables: */
+   int result;             /* The returned integer */
+
+/* Initialise. */
+   result = SORTBY_NONE;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check each known value. */
+   if( astChrMatch( sortby, "None" ) ) {
+      result = SORTBY_NONE;
+
+   } else if( astChrMatch( sortby, "AgeUp" ) ) {
+      result = SORTBY_AGEUP;
+
+   } else if( astChrMatch( sortby, "AgeDown" ) ) {
+      result = SORTBY_AGEDOWN;
+
+   } else if( astChrMatch( sortby, "KeyAgeUp" ) ) {
+      result = SORTBY_KEYAGEUP;
+
+   } else if( astChrMatch( sortby, "KeyAgeDown" ) ) {
+      result = SORTBY_KEYAGEDOWN;
+
+   } else if( astChrMatch( sortby, "KeyUp" ) ) {
+      result = SORTBY_KEYUP;
+
+   } else if( astChrMatch( sortby, "KeyDown" ) ) {
+      result = SORTBY_KEYDOWN;
+
+   } else {
+      astError( AST__INTER, "%s(KeyMap): Illegal SortBy value %s "
+                "encountered.", status, method, sortby );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *SortByString( int sortby, const char *method, int *status ){
+/*
+*  Name:
+*     SortByString
+
+*  Purpose:
+*     Get the string associated with an integer SortBy value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     const char *SortByString( int sortby, const char *method, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function returns the string associated with the supplied
+*     integer SortBy value.
+
+*  Parameters:
+*     sortby
+*        The integer SortBy value.
+*     method
+*        Pointer to a string holding the name of the calling method for
+*        inclusion in error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the associated SortBy string.
+
+*/
+
+/* Local Variables: */
+   const char *result;    /* The returned string */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check each value. */
+   if( sortby == SORTBY_NONE ) {
+      result = "None";
+
+   } else if( sortby == SORTBY_AGEUP ) {
+      result = "AgeUp";
+
+   } else if( sortby == SORTBY_AGEDOWN ) {
+      result = "AgeDown";
+
+   } else if( sortby == SORTBY_KEYAGEUP ) {
+      result = "KeyAgeUp";
+
+   } else if( sortby == SORTBY_KEYAGEDOWN ) {
+      result = "KeyAgeDown";
+
+   } else if( sortby == SORTBY_KEYUP ) {
+      result = "KeyUp";
+
+   } else if( sortby == SORTBY_KEYDOWN ) {
+      result = "KeyDown";
+
+   } else {
+      astError( AST__INTER, "%s(KeyMap): Illegal integer SortBy value %d "
+                "encountered (internal AST programming error).", status,
+                method, sortby );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void SortEntries( AstKeyMap *this, int *status ){
+/*
+*  Name:
+*     SortEntries
+
+*  Purpose:
+*     Ensure the entries in a KeyMap are sorted correctly.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     void SortEntries( AstKeyMap *this, int *status )
+
+*  Class Membership:
+*     KeyMap member function.
+
+*  Description:
+*     This function sorts all the entries in the supplied KeyMap in
+*     the manner indicated by the SortBy attribute value in the KeyMap.
+*     A double linked list is maintained indicating the ordering, with
+*     the first entry in the sorted list being pointed to by "this->first".
+*     Each entry contains "snext" and "sprev" pointers that point to the
+*     next and previous entries in the sorted list. The number of entries
+*     in the sorted list (which should usually equal the total number of
+*     entries currently in the KeyMap), is stored in "this->nsorted".
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMapEntry **ents;
+   AstMapEntry **pent;
+   AstMapEntry **a;
+   AstMapEntry **b;
+   AstMapEntry *entry;
+   int i;
+   int nent;
+   int sortby;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Empty the sorted list. */
+   this->nsorted = 0;
+   this->first = NULL;
+
+/* Get the SortBy value. */
+   sortby = astGetSortBy( this );
+
+/* Do nothing more if no sorting is required. */
+   if( sortby != SORTBY_NONE ) {
+
+/* Get the number of entries in the keyMap. */
+      nent = astMapSize( this );
+
+/* Only sort if the KeyMap is not empty. */
+      if( nent > 0 ) {
+
+/* Allocate an array with one element for each entry. Each element is a
+   pointer to a MapEntry structure. */
+         ents = astMalloc( sizeof( *ents )*nent );
+         if( astOK ) {
+
+/* Loop round all entries in the hash table. */
+            pent = ents;
+            for( i = 0; i < this->mapsize; i++ ) {
+
+/* Get a pointer to the next KeyMap entry. */
+               entry = this->table[ i ];
+
+/* Loop round all entries in this element of the hash table. */
+               while( entry ) {
+
+/* Store the sorting method in the MapEntry. */
+                  entry->sortby = sortby;
+
+/* Put a pointer to the MapEntry into the array. */
+                  *(pent++) = entry;
+
+/* Update the address of the next MapEntry in the source. */
+                  entry = entry->next;
+               }
+            }
+
+/* No need for sorting if there is only one entry. */
+            if( nent == 1 ) {
+               ents[ 0 ]->snext = ents[ 0 ];
+               ents[ 0 ]->sprev = ents[ 0 ];
+
+/* Sort the array of pointers if there is more than one entry... */
+            } else {
+               qsort( ents, nent, sizeof( *ents ), CompareEntries );
+
+/* Establish the double linked list. */
+               a = ents;
+               b = ents + 1;
+               for( i = 1; i < nent; i++ ) {
+                  (*b)->sprev = *a;
+                  (*a)->snext = *b;
+                  a = b++;
+               }
+
+               b = ents;
+               (*b)->sprev = *a;
+               (*a)->snext = *b;
+
+            }
+
+/* Store a pointer to the first entry in the sorted list. */
+            this->first = ents[ 0 ];
+
+/* Store the number of entrie sin the sorted list. */
+            this->nsorted = nent;
+         }
+
+/* Free resources. */
+         ents = astFree( ents );
+      }
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     KeyMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a KeyMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstKeyMap *this;             /* Pointer to the KeyMap structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the KeyMap structure. */
+   this = (AstKeyMap *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* SizeGuess. */
+/* ---------- */
+   if ( !strcmp( attrib, "sizeguess" ) ) {
+      result = astTestSizeGuess( this );
+
+/* KeyCase. */
+/* --------- */
+   } else if ( !strcmp( attrib, "keycase" ) ) {
+      result = astTestKeyCase( this );
+
+/* KeyError. */
+/* --------- */
+   } else if ( !strcmp( attrib, "keyerror" ) ) {
+      result = astTestKeyError( this );
+
+/* MapLocked. */
+/* --------- */
+   } else if ( !strcmp( attrib, "maplocked" ) ) {
+      result = astTestMapLocked( this );
+
+/* SortBy. */
+/* ------- */
+   } else if ( !strcmp( attrib, "sortby" ) ) {
+      result = astTestSortBy( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static int TestSizeGuess( AstKeyMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     astTestSizeGuess
+
+*  Purpose:
+*     Test the value of the SizeGuess attribute for a KeyMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     int astTestSizeGuess( AstKeyMap *this )
+
+*  Class Membership:
+*     KeyMap method.
+
+*  Description:
+*     This function returns a non-zero value if the SizeGuess attribute
+*     has been set in a KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap.
+
+*  Returned Value:
+*     Non-zero if the SizeGuess attribute is set.
+
+*  Notes:
+*     - A value of zero is returned if this function is invoked with the
+*     global error status set.
+
+*-
+*/
+
+/* Local Variables: */
+   int result;                /* Returned value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Return non-zero if the attribute is still set to its "not set" value. */
+   return ( this->sizeguess != INT_MAX );
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+
+/*
+*att++
+*  Name:
+*     SizeGuess
+
+*  Purpose:
+*     The expected size of the KeyMap.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This is attribute gives an estimate of the number of entries that
+*     will be stored in the KeyMap. It is used to tune the internal
+*     properties of the KeyMap for speed and efficiency. A larger value
+*     will result in faster access at the expense of increased memory
+*     requirements. However if the SizeGuess value is much larger than
+*     the actual size of the KeyMap, then there will be little, if any,
+*     speed gained by making the SizeGuess even larger. The default value
+*     is 300.
+*
+*     The value of this attribute can only be changed if the KeyMap is
+*     empty. Its value can be set conveniently when creating the KeyMap.
+*     An error will be reported if an attempt is made to set or clear the
+*     attribute when the KeyMap contains any entries.
+
+*  Applicability:
+*     KeyMap
+*        All KeyMaps have this attribute.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     KeyCase
+
+*  Purpose:
+*     Are keys case sensitive?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which controls how keys are
+*     used. If KeyCase is zero, then key strings supplied to any method
+*     are automatically converted to upper case before being used. If
+*     KeyCase is non-zero (the default), then supplied key strings are
+*     used without modification.
+*
+*     The value of this attribute can only be changed if the KeyMap is
+*     empty. Its value can be set conveniently when creating the KeyMap.
+*     An error will be reported if an attempt is made to change the
+*     attribute value when the KeyMap contains any entries.
+
+*  Applicability:
+*     KeyMap
+*        All KeyMaps have this attribute.
+*     Table
+*        The Table class over-rides this attribute by forcing it to zero.
+*        That is, keys within a Table are always case insensitive.
+*att--
+*/
+astMAKE_GET(KeyMap,KeyCase,int,1,(this->keycase == -1 ? 1 : this->keycase))
+astMAKE_TEST(KeyMap,KeyCase,( this->keycase != -1 ))
+
+/*
+*att++
+*  Name:
+*     KeyError
+
+*  Purpose:
+*     Report an error when getting the value of a non-existant KeyMap entry?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which controls how the
+c     astMapGet...
+f     AST_MAPGET...
+*     functions behave if the requested key is not found in the KeyMap.
+*     If KeyError is zero (the default), then these functions will return
+c     zero
+f     .FALSE.
+*     but no error will be reported. If KeyError is non-zero, then the
+*     same values are returned but an error is also reported.
+
+*  Notes:
+*     - When setting a new value for KeyError, the supplied value is
+*     propagated to any KeyMaps contained within the supplied KeyMap.
+*     - When clearing the KeyError attribute, the attribute is also
+*     cleared in any KeyMaps contained within the supplied KeyMap.
+
+*  Applicability:
+*     KeyMap
+*        All KeyMaps have this attribute.
+*att--
+*/
+astMAKE_GET(KeyMap,KeyError,int,0,( ( this->keyerror != -INT_MAX ) ?
+                                   this->keyerror : 0 ))
+astMAKE_TEST(KeyMap,KeyError,( this->keyerror != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     MapLocked
+
+*  Purpose:
+*     Prevent new entries being added to a KeyMap?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     If this boolean attribute is set to
+c     a non-zero value,
+f     .TRUE.,
+*     an error will be reported if an attempt is made to add a new entry
+*     to the KeyMap. Note, the value associated with any existing entries
+*     can still be changed, but no new entries can be stored in the KeyMap.
+*     The default value
+c     (zero)
+f     (.FALSE.)
+*     allows new entries to be added to the KeyMap.
+
+*  Notes:
+*     - When setting a new value for MapLocked, the supplied value is
+*     propagated to any KeyMaps contained within the supplied KeyMap.
+*     - When clearing the MapLocked attribute, the attribute is also
+*     cleared in any KeyMaps contained within the supplied KeyMap.
+
+*  Applicability:
+*     KeyMap
+*        All KeyMaps have this attribute.
+*att--
+*/
+astMAKE_GET(KeyMap,MapLocked,int,0,( ( this->maplocked != -INT_MAX ) ?
+                                   this->maplocked : 0 ))
+astMAKE_TEST(KeyMap,MapLocked,( this->maplocked != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     SortBy
+
+*  Purpose:
+*     Determines how keys are sorted in a KeyMap.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute determines the order in which keys are returned by the
+c     astMapKey
+f     AST_MAPKEY
+*     function. It may take the following values (the default is "None"):
+*
+*     - "None": The keys are returned in an arbitrary order. This is the
+*     fastest method as it avoids the need for a sorted list of keys to
+*     be maintained and used.
+*
+*     - "AgeDown": The keys are returned in the order in which values were
+*     stored in the KeyMap, with the key for the most recent value being
+*     returned last. If the value of an existing entry is changed, it goes
+*     to the end of the list.
+*
+*     - "AgeUp": The keys are returned in the order in which values were
+*     stored in the KeyMap, with the key for the most recent value being
+*     returned first. If the value of an existing entry is changed, it goes
+*     to the top of the list.
+*
+*     - "KeyAgeDown": The keys are returned in the order in which they
+*     were originally stored in the KeyMap, with the most recent key being
+*     returned last. If the value of an existing entry is changed, its
+*     position in the list does not change.
+*
+*     - "KeyAgeUp": The keys are returned in the order in which they
+*     were originally stored in the KeyMap, with the most recent key being
+*     returned first. If the value of an existing entry is changed, its
+*     position in the list does not change.
+*
+*     - "KeyDown": The keys are returned in alphabetical order, with "A..."
+*     being returned last.
+*
+*     - "KeyUp": The keys are returned in alphabetical order, with "A..."
+*     being returned first.
+
+*  Notes:
+*     - If a new value is assigned to SortBy (or if SortBy is cleared),
+*     all entries currently in the KeyMap are re-sorted according to the
+*     new SortBy value.
+
+*  Applicability:
+*     KeyMap
+*        All KeyMaps have this attribute.
+*att--
+*/
+astMAKE_GET(KeyMap,SortBy,int,SORTBY_NONE,( ( this->sortby != -INT_MAX ) ?
+                                           this->sortby : SORTBY_NONE ))
+astMAKE_TEST(KeyMap,SortBy,( this->sortby != -INT_MAX ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for KeyMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for KeyMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstKeyMap *in;                  /* Pointer to input KeyMap */
+   AstKeyMap *out;                 /* Pointer to output KeyMap */
+   int i;                       /* Index into hash table */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output KeyMap structures. */
+   in = (AstKeyMap *) objin;
+   out = (AstKeyMap *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output KeyMap. */
+   out->table = NULL;
+   out->nentry = NULL;
+   out->first = NULL;
+   out->firstA = NULL;
+
+/* Make copies of the table entries. */
+   out->table = astMalloc( sizeof( AstMapEntry * )*( out->mapsize ) );
+   out->nentry = astMalloc( sizeof( int )*( out->mapsize ) );
+
+   for( i = 0; i < out->mapsize; i++ ) CopyTableEntry( in, out, i, status );
+
+/* Create the required sorted key list in the new KeyMap. */
+   SortEntries( out, status );
+
+/* If an error occurred, clean up by freeing all memory allocated above. */
+   if ( !astOK ) {
+      for( i = 0; i < out->mapsize; i++ ) FreeTableEntry( out, i, status );
+      out->table = astFree( out->table );
+      out->nentry = astFree( out->nentry );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for KeyMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for KeyMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstKeyMap *this;             /* Pointer to the KeyMap structure */
+   int i;                    /* Loop count */
+
+/* Obtain a pointer to the KeyMap structure. */
+   this = (AstKeyMap *) obj;
+
+/* Free all allocated memory. */
+   for( i = 0; i < this->mapsize; i++ ) FreeTableEntry( this, i, status );
+
+/* Free memory used to hold tables. */
+   this->table = astFree( this->table );
+   this->nentry = astFree( this->nentry );
+
+/* Nullify other pointers. */
+   this->first = NULL;
+   this->firstA = NULL;
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for KeyMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the KeyMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the KeyMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstKeyMap *this;              /* Pointer to the KeyMap structure */
+   AstMapEntry *next;            /* Pointer to the next AstMapEntry to dump */
+   int i;                        /* Index into hash table */
+   int nentry;                   /* Number of entries dumped so far */
+   int set;                      /* Is attribute set? */
+   int ival;                     /* Attribute value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the KeyMap structure. */
+   this = (AstKeyMap *) this_object;
+
+/* Initialise the number of KeyMap entries dumped so far. */
+   nentry = 0;
+
+/* SizeGuess. */
+/* ---------- */
+   set = TestSizeGuess( this, status );
+   ival = set ? GetSizeGuess( this, status ) : astGetSizeGuess( this );
+   astWriteInt( channel, "SzGss", set, 0, ival, "Guess at KeyMap size" );
+
+/* SortBy. */
+/* ------- */
+   set = TestSortBy( this, status );
+   ival = set ? GetSortBy( this, status ) : astGetSortBy( this );
+   astWriteString( channel, "SortBy", set, 0, SortByString( ival, "astDump",
+                                                            status ),
+                   "Sorting scheme for keys" );
+
+/* KeyCase. */
+/* --------- */
+   set = TestKeyCase( this, status );
+   ival = set ? GetKeyCase( this, status ) : astGetKeyCase( this );
+   astWriteInt( channel, "KyCas", set, 0, ival, "Are keys case sensitive?" );
+
+/* KeyError. */
+/* --------- */
+   set = TestKeyError( this, status );
+   ival = set ? GetKeyError( this, status ) : astGetKeyError( this );
+   astWriteInt( channel, "KyErr", set, 0, ival, "Report non-existant keys?" );
+
+/* MapLocked. */
+/* --------- */
+   set = TestMapLocked( this, status );
+   ival = set ? GetMapLocked( this, status ) : astGetMapLocked( this );
+   astWriteInt( channel, "MpLck", set, 0, ival, "Prevent addition of new entries?" );
+
+/* MapSize. */
+/* -------- */
+   astWriteInt( channel, "MapSz", 1, 1, this->mapsize, "Size of hash table" );
+
+/* member count. */
+   astWriteInt( channel, "MemCnt", 1, 1, this->member_count, "Total member count" );
+
+/* Loop round each entry in the hash table. */
+   for( i = 0; i < this->mapsize; i++ ) {
+
+/* Get a pointer to the next KeyMap entry to dump. */
+      next = this->table[ i ];
+
+/* Loop round dumping all KeyMap entries in this element of the hash table. */
+      while( next && astOK ) {
+         DumpEntry( next, channel, ++nentry, status );
+
+/* Get a pointer to the next entry to dump. */
+         next = next->next;
+
+      }
+   }
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAKeyMap and astCheckKeyMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(KeyMap,Object)
+astMAKE_CHECK(KeyMap)
+
+AstKeyMap *astKeyMap_( const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astKeyMap
+f     AST_KEYMAP
+
+*  Purpose:
+*     Create a KeyMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "keymap.h"
+c     AstKeyMap *astKeyMap( const char *options, ... )
+f     RESULT = AST_KEYMAP( OPTIONS, STATUS )
+
+*  Class Membership:
+*     KeyMap constructor.
+
+*  Description:
+*     This function creates a new empty KeyMap and optionally initialises its
+*     attributes. Entries can then be added to the KeyMap using the
+c     astMapPut0<X> and astMapPut1<X> functions.
+f     AST_MAPPUT0<X> and AST_MAPPUT1<X> functions.
+*
+*     The KeyMap class is used to store a set of values with associated keys
+*     which identify the values. The keys are strings. These may be case
+*     sensitive or insensitive as selected by the KeyCase attribute, and
+*     trailing spaces are ignored. The value associated with a key can be
+*     integer (signed 4 and 2 byte, or unsigned 1 byte), floating point
+*     (single or double precision),
+c     void pointer,
+*     character string or AST Object pointer. Each
+*     value can be a scalar or a one-dimensional vector. A KeyMap is
+*     conceptually similar to a Mapping in that a KeyMap transforms an
+*     input into an output - the input is the key, and the output is the
+*     value associated with the key. However, this is only a conceptual
+*     similarity, and it should be noted that the KeyMap class inherits from
+*     the Object class rather than the Mapping class. The methods of the
+*     Mapping class cannot be used with a KeyMap.
+
+*  Parameters:
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new KeyMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new KeyMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astKeyMap()
+f     AST_MAP = INTEGER
+*        A pointer to the new KeyMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstKeyMap *new;                 /* Pointer to new KeyMap */
+   va_list args;                /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the KeyMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitKeyMap( NULL, sizeof( AstKeyMap ), !class_init, &class_vtab, "KeyMap" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new KeyMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new KeyMap. */
+   return new;
+}
+
+AstKeyMap *astKeyMapId_( const char *options, ... ) {
+/*
+*  Name:
+*     astKeyMapId_
+
+*  Purpose:
+*     Create a KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     AstKeyMap *astKeyMapId_( const char *options, ... )
+
+*  Class Membership:
+*     KeyMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astKeyMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astKeyMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astKeyMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astKeyMap_.
+
+*  Returned Value:
+*     The ID value associated with the new KeyMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstKeyMap *new;                 /* Pointer to new KeyMap */
+   va_list args;                /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the KeyMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitKeyMap( NULL, sizeof( AstKeyMap ), !class_init, &class_vtab, "KeyMap" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new KeyMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new KeyMap. */
+   return astMakeId( new );
+}
+
+AstKeyMap *astInitKeyMap_( void *mem, size_t size, int init, AstKeyMapVtab *vtab,
+                           const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitKeyMap
+
+*  Purpose:
+*     Initialise a KeyMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     AstKeyMap *astInitKeyMap( void *mem, size_t size, int init, AstKeyMapVtab *vtab,
+*                               const char *name )
+
+*  Class Membership:
+*     KeyMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new KeyMap object. It allocates memory (if necessary) to accommodate
+*     the KeyMap plus any additional data associated with the derived class.
+*     It then initialises a KeyMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a KeyMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the KeyMap is to be created. This
+*        must be of sufficient size to accommodate the KeyMap data
+*        (sizeof(KeyMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the KeyMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the KeyMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the KeyMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new KeyMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astClass
+*        method).
+
+*  Returned Value:
+*     A pointer to the new KeyMap.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *new;              /* Pointer to the new KeyMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitKeyMapVtab( vtab, name );
+
+/* Initialise an Object structure (the parent class) as the first component
+   within the KeyMap structure, allocating memory if necessary. */
+   new = (AstKeyMap *) astInitObject( mem, size, 0, (AstObjectVtab *) vtab,
+                                      name );
+
+   if ( astOK ) {
+
+/* Initialise the KeyMap data. */
+/* ---------------------------- */
+/* Initialise all attributes to their "undefined" values. */
+      new->sizeguess = INT_MAX;
+      new->mapsize = 0;
+      new->table = NULL;
+      new->nentry = NULL;
+      new->keycase = -1;
+      new->keyerror = -INT_MAX;
+      new->maplocked = -INT_MAX;
+      new->sortby = -INT_MAX;
+      new->first = NULL;
+      new->nsorted = 0;
+      new->member_count = 0;
+      new->firstA = NULL;
+      new->iter_itab = 0;
+      new->iter_entry = NULL;
+
+      NewTable( new, MIN_TABLE_SIZE, status );
+
+/* If an error occurred, clean up by deleting the new KeyMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new KeyMap. */
+   return new;
+}
+
+AstKeyMap *astLoadKeyMap_( void *mem, size_t size, AstKeyMapVtab *vtab,
+                           const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadKeyMap
+
+*  Purpose:
+*     Load a KeyMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "keymap.h"
+*     AstKeyMap *astLoadKeyMap( void *mem, size_t size, AstKeyMapVtab *vtab,
+*                               const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     KeyMap loader.
+
+*  Description:
+*     This function is provided to load a new KeyMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     KeyMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a KeyMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the KeyMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        KeyMap data (sizeof(KeyMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the KeyMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the KeyMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstKeyMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new KeyMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the KeyMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "KeyMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new KeyMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *new;           /* Pointer to the new KeyMap */
+   AstObject **alist;        /* Pointer to vector of entry values */
+   AstObject *aval;          /* AST Object value for an entry */
+   astDECLARE_GLOBALS        /* Pointer to thread-specific global data */
+   char *com;                /* Pointer to comment string for an entry */
+   char *key;                /* Pointer to key string for an entry */
+   char *sval;               /* String value for an entry */
+   char buff[ 30 ];          /* Buffer for key names */
+   const char **slist;       /* Pointer to vector of entry values */
+   double *dlist;            /* Pointer to vector of entry values */
+   double dval;              /* Floating point value for an entry */
+   float *flist;             /* Pointer to vector of entry values */
+   int *ilist;               /* Pointer to vector of entry values */
+   int index;                /* Index of next array element in a vector entry */
+   int ival;                 /* Integer value for an entry */
+   int mapsize;              /* Size for new hash table */
+   int nel;                  /* Vector length */
+   int nentry;               /* Number of KeyMap entries read so far */
+   int type;                 /* Data type for an entry */
+   short int *wlist;         /* Pointer to vector of entry values */
+   short int wval;           /* Short int value for an entry */
+   unsigned char *blist;     /* Pointer to vector of entry values */
+   unsigned char bval;       /* Byte value for an entry */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this KeyMap. In this case the
+   KeyMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstKeyMap );
+      vtab = &class_vtab;
+      name = "KeyMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitKeyMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built KeyMap. */
+   new = astLoadObject( mem, size, (AstObjectVtab *) vtab, name, channel );
+
+   if ( astOK ) {
+
+/* Inidicate the KeyMap is empty. */
+      new->mapsize = 0;
+      new->table = NULL;
+      new->nentry = NULL;
+      new->firstA = NULL;
+      new->iter_itab = 0;
+      new->iter_entry = NULL;
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "KeyMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* SizeGuess. */
+/* ---------- */
+      new->sizeguess = astReadInt( channel, "szgss", INT_MAX );
+      if ( TestSizeGuess( new, status ) ) SetSizeGuess( new, new->sizeguess, status );
+
+/* KeyCase. */
+/* --------- */
+      new->keycase = astReadInt( channel, "kycas", -INT_MAX );
+      if ( TestKeyCase( new, status ) ) SetKeyCase( new, new->keycase, status );
+
+/* KeyError. */
+/* --------- */
+      new->keyerror = astReadInt( channel, "kyerr", -INT_MAX );
+      if ( TestKeyError( new, status ) ) SetKeyError( new, new->keyerror, status );
+
+/* MapLocked. */
+/* --------- */
+      new->maplocked = astReadInt( channel, "mplck", -INT_MAX );
+      if ( TestMapLocked( new, status ) ) SetMapLocked( new, new->maplocked, status );
+
+/* SortBy. */
+/* ------- */
+      sval = astReadString( channel, "sortby", " " );
+      new->sortby = -INT_MAX;
+      if( astOK && strcmp( sval, " " ) ) {
+         new->sortby = SortByInt( sval, "astRead", status );
+      }
+      if( TestSortBy( new, status ) ) SetSortBy( new, new->sortby, status );
+      sval = astFree( sval );
+
+/* MapSize. */
+/* -------- */
+      mapsize = astReadInt( channel, "mapsz", MIN_TABLE_SIZE );
+      NewTable( new, mapsize, status );
+
+/* Entries... */
+/* ---------- */
+
+/* Initialise the index of the next AstMapEntry to be read. */
+      nentry = 0;
+
+/* Read Entries until no more are found */
+      while( astOK ) {
+         nentry++;
+
+/* Get the entry key. */
+         (void) sprintf( buff, "key%d", nentry );
+         key = astReadString( channel, buff, NULL );
+
+/* We have finished reading entries if no key was found. */
+         if( !key ) break;
+
+/* Get the entry comment. */
+         (void) sprintf( buff, "com%d", nentry );
+         com = astReadString( channel, buff, NULL );
+
+/* Get the entry data type. */
+         (void) sprintf( buff, "typ%d", nentry );
+         type = astReadInt( channel, buff, AST__BADTYPE );
+
+         if( type == AST__BADTYPE && astOK ) {
+            astError( AST__BDFTS, "astLoadKeyMap(%s): No data type code found "
+                      "whilst reading a %s.", status, name, name );
+
+         }
+
+/* Get the vector length. */
+         (void) sprintf( buff, "nel%d", nentry );
+         nel = astReadInt( channel, buff, 0 );
+
+/* Get the entry member number. Set the KeyMap member count to this value
+   so that the next entry added to the KeyMap will get this value as its
+   member index. */
+         (void) sprintf( buff, "mem%d", nentry );
+         new->member_count = astReadInt( channel, buff, 0 );
+
+/* First deal with integer entries. */
+         if( type == AST__INTTYPE ) {
+
+/* For scalar entries, use "val<nentry>" to get the value then create a new
+   entry and add it to the KeyMap. */
+            if( nel == 0 ) {
+               (void) sprintf( buff, "val%d", nentry );
+               ival = astReadInt( channel, buff, 0 );
+               MapPut0I( new, key, ival, com, status );
+
+/* If we must have an array of values... */
+            } else {
+
+/* Create an array to hold the values. */
+               ilist = astMalloc( sizeof(int)*nel );
+
+/* Loop round reading values. */
+               for( index = 0; astOK && index < nel; index++ ) {
+                  (void) sprintf( buff, "v%d_%d", nentry, index + 1 );
+                  ilist[ index ] = astReadInt( channel, buff, 0 );
+               }
+
+/* Create the KeyMap entry. */
+               MapPut1I( new, key, nel, ilist, com, status );
+
+/* Free resources. */
+               ilist = astFree( ilist );
+            }
+
+/* Do the same for short int values. */
+         } else if( type == AST__SINTTYPE ) {
+            if( nel == 0 ) {
+               (void) sprintf( buff, "val%d", nentry );
+               wval = (short int) astReadInt( channel, buff, 0 );
+               MapPut0S( new, key, wval, com, status );
+            } else {
+               wlist = astMalloc( sizeof(short int)*nel );
+               for( index = 0; astOK && index < nel; index++ ) {
+                  (void) sprintf( buff, "v%d_%d", nentry, index + 1 );
+                  wlist[ index ] = (short int) astReadInt( channel, buff, 0 );
+               }
+               MapPut1S( new, key, nel, wlist, com, status );
+               wlist = astFree( wlist );
+            }
+
+/* Do the same for byte values. */
+         } else if( type == AST__BYTETYPE ) {
+            if( nel == 0 ) {
+               (void) sprintf( buff, "val%d", nentry );
+               bval = (unsigned char) astReadInt( channel, buff, 0 );
+               MapPut0B( new, key, bval, com, status );
+            } else {
+               blist = astMalloc( sizeof(unsigned char)*nel );
+               for( index = 0; astOK && index < nel; index++ ) {
+                  (void) sprintf( buff, "v%d_%d", nentry, index + 1 );
+                  blist[ index ] = (unsigned char) astReadInt( channel, buff, 0 );
+               }
+               MapPut1B( new, key, nel, blist, com, status );
+               blist = astFree( blist );
+            }
+
+/* Do the same for double values. */
+         } else if( type == AST__DOUBLETYPE ) {
+            if( nel == 0 ) {
+               (void) sprintf( buff, "val%d", nentry );
+               dval = astReadDouble( channel, buff, AST__BAD );
+               MapPut0D( new, key, dval, com, status );
+            } else {
+               dlist = astMalloc( sizeof(double)*nel );
+               for( index = 0; astOK && index < nel; index++ ) {
+                  (void) sprintf( buff, "v%d_%d", nentry, index + 1 );
+                  dlist[ index ] = astReadDouble( channel, buff, AST__BAD );
+               }
+               MapPut1D( new, key, nel, dlist, com, status );
+               dlist = astFree( dlist );
+            }
+
+/* Do the same for float values. */
+         } else if( type == AST__FLOATTYPE ) {
+            if( nel == 0 ) {
+               (void) sprintf( buff, "val%d", nentry );
+               dval = astReadDouble( channel, buff, 0.0 );
+               MapPut0F( new, key, (float) dval, com, status );
+            } else {
+               flist = astMalloc( sizeof(float)*nel );
+               for( index = 0; astOK && index < nel; index++ ) {
+                  (void) sprintf( buff, "v%d_%d", nentry, index + 1 );
+                  flist[ index ] = (float) astReadDouble( channel, buff, 0.0 );
+               }
+               MapPut1F( new, key, nel, flist, com, status );
+               flist = astFree( flist );
+            }
+
+/* Do the same for string values. */
+         } else if( type == AST__STRINGTYPE ) {
+            if( nel == 0 ) {
+               (void) sprintf( buff, "val%d", nentry );
+               sval = astReadString( channel, buff, "" );
+               MapPut0C( new, key, sval, com, status );
+               sval = astFree( sval );
+            } else {
+               slist = astMalloc( sizeof(const char *)*nel );
+               for( index = 0; astOK && index < nel; index++ ) {
+                  (void) sprintf( buff, "v%d_%d", nentry, index + 1 );
+                  slist[ index ] = astReadString( channel, buff, "" );
+               }
+               MapPut1C( new, key, nel, slist, com, status );
+               for( index = 0; astOK && index < nel; index++ ) {
+                  slist[ index ] = astFree( (void *) slist[ index ] );
+               }
+               slist = astFree( slist );
+            }
+
+/* Do the same for object values. */
+         } else if( type == AST__OBJECTTYPE ) {
+            if( nel == 0 ) {
+               (void) sprintf( buff, "val%d", nentry );
+               aval = astReadObject( channel, buff, NULL );
+               MapPut0A( new, key, aval, com, status );
+               if( aval ) aval = astAnnul( aval );
+            } else {
+               alist = astMalloc( sizeof(AstObject *)*nel );
+               for( index = 0; astOK && index < nel; index++ ) {
+                  (void) sprintf( buff, "v%d_%d", nentry, index + 1 );
+                  alist[ index ] = astReadObject( channel, buff, NULL );
+               }
+               MapPut1A( new, key, nel, alist, com, status );
+               for( index = 0; astOK && index < nel; index++ ) {
+                  if( alist[ index ] ) alist[ index ] = astAnnul( alist[ index ] );
+               }
+               alist = astFree( alist );
+            }
+
+/* Undef values have no value. */
+         } else if( type == AST__UNDEFTYPE ) {
+            MapPutU( new, key, com, status );
+
+/* Report an error if the data type is unknown. */
+         } else if( astOK ) {
+            astError( AST__BDFTS, "astLoadKeyMap(%s): Unknown data type code "
+                      "(%d) encountered whilst reading a %s.", status, name, type,
+                      name );
+         }
+/* Free resources. */
+         key = astFree( key );
+         if( com ) com = astFree( com );
+
+      }
+
+/* Set the final member count for the KeyMap. */
+      new->member_count = astReadInt( channel, "memcnt", 0 );
+
+/* If an error occurred, clean up by deleting the new KeyMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new KeyMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+#define MAKE_MAPPUT0_(X,Xtype) \
+void astMapPut0##X##_( AstKeyMap *this, const char *key, Xtype value, \
+                      const char *comment, int *status ){ \
+   if ( !astOK ) return; \
+   (**astMEMBER(this,KeyMap,MapPut0##X))(this,key,value,comment, status ); \
+}
+MAKE_MAPPUT0_(D,double)
+MAKE_MAPPUT0_(F,float)
+MAKE_MAPPUT0_(I,int)
+MAKE_MAPPUT0_(C,const char *)
+MAKE_MAPPUT0_(A,AstObject *)
+MAKE_MAPPUT0_(P,void *)
+MAKE_MAPPUT0_(S,short int)
+MAKE_MAPPUT0_(B,unsigned char)
+#undef MAKE_MAPPUT0_
+
+
+#define MAKE_MAPPUT1_(X,Xtype) \
+void astMapPut1##X##_( AstKeyMap *this, const char *key, int size, \
+                       Xtype value[], const char *comment, \
+                       int *status ){ \
+   if ( !astOK ) return; \
+   (**astMEMBER(this,KeyMap,MapPut1##X))(this,key,size,value,comment, status ); \
+}
+MAKE_MAPPUT1_(S,const short int)
+MAKE_MAPPUT1_(B,const unsigned char)
+MAKE_MAPPUT1_(D,const double)
+MAKE_MAPPUT1_(F,const float)
+MAKE_MAPPUT1_(I,const int)
+MAKE_MAPPUT1_(C,const char *const)
+MAKE_MAPPUT1_(A,AstObject *const)
+MAKE_MAPPUT1_(P,void *const)
+#undef MAKE_MAPPUT1_
+
+#define MAKE_MAPGET0_(X,Xtype) \
+int astMapGet0##X##_( AstKeyMap *this, const char *key, Xtype *value, int *status ){ \
+   if ( !astOK ) return 0; \
+   return (**astMEMBER(this,KeyMap,MapGet0##X))(this,key,value, status ); \
+}
+MAKE_MAPGET0_(D,double)
+MAKE_MAPGET0_(S,short int)
+MAKE_MAPGET0_(B,unsigned char)
+MAKE_MAPGET0_(F,float)
+MAKE_MAPGET0_(I,int)
+MAKE_MAPGET0_(C,const char *)
+MAKE_MAPGET0_(A,AstObject *)
+MAKE_MAPGET0_(P,void *)
+#undef MAKE_MAPGET0_
+
+
+#define MAKE_MAPGET1_(X,Xtype) \
+int astMapGet1##X##_( AstKeyMap *this, const char *key, int mxval, int *nval, \
+                      Xtype *value, int *status ){ \
+   if ( !astOK ) return 0; \
+   return (**astMEMBER(this,KeyMap,MapGet1##X))(this,key,mxval,nval,value,status); \
+}
+MAKE_MAPGET1_(B,unsigned char)
+MAKE_MAPGET1_(S,short int)
+MAKE_MAPGET1_(D,double)
+MAKE_MAPGET1_(F,float)
+MAKE_MAPGET1_(I,int)
+MAKE_MAPGET1_(A,AstObject *)
+MAKE_MAPGET1_(P,void *)
+#undef MAKE_MAPGET1_
+
+#define MAKE_MAPGETELEM_(X,Xtype) \
+int astMapGetElem##X##_( AstKeyMap *this, const char *key, int elem, \
+                         Xtype *value, int *status ){ \
+   if ( !astOK ) return 0; \
+   return (**astMEMBER(this,KeyMap,MapGetElem##X))(this,key,elem,value,status); \
+}
+MAKE_MAPGETELEM_(B,unsigned char)
+MAKE_MAPGETELEM_(S,short int)
+MAKE_MAPGETELEM_(D,double)
+MAKE_MAPGETELEM_(F,float)
+MAKE_MAPGETELEM_(I,int)
+MAKE_MAPGETELEM_(A,AstObject *)
+MAKE_MAPGETELEM_(P,void *)
+#undef MAKE_MAPGETELEM_
+
+int astMapGet1C_( AstKeyMap *this, const char *key, int l, int mxval, int *nval,
+                  char *value, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,KeyMap,MapGet1C))(this,key,l,mxval,nval,value,status);
+}
+
+int astMapGetElemC_( AstKeyMap *this, const char *key, int l, int elem,
+                     char *value, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,KeyMap,MapGetElemC))(this,key,l,elem,value,status);
+}
+
+#define MAKE_MAPPUTELEM_(X,Xtype) \
+void astMapPutElem##X##_( AstKeyMap *this, const char *key, int elem, \
+                         Xtype value, int *status ){ \
+   if ( !astOK ) return; \
+   (**astMEMBER(this,KeyMap,MapPutElem##X))(this,key,elem,value,status); \
+}
+MAKE_MAPPUTELEM_(B,unsigned char)
+MAKE_MAPPUTELEM_(S,short int)
+MAKE_MAPPUTELEM_(D,double)
+MAKE_MAPPUTELEM_(F,float)
+MAKE_MAPPUTELEM_(I,int)
+MAKE_MAPPUTELEM_(A,AstObject *)
+MAKE_MAPPUTELEM_(C,const char *)
+MAKE_MAPPUTELEM_(P,void *)
+#undef MAKE_MAPPUTELEM_
+
+void astMapPutU_( AstKeyMap *this, const char *key, const char *comment, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,KeyMap,MapPutU))(this,key,comment,status);
+}
+
+void astMapRemove_( AstKeyMap *this, const char *key, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,KeyMap,MapRemove))(this,key,status);
+}
+void astMapRename_( AstKeyMap *this, const char *oldkey, const char *newkey,
+                    int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,KeyMap,MapRename))(this,oldkey,newkey,status);
+}
+void astMapCopy_( AstKeyMap *this, AstKeyMap *that, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,KeyMap,MapCopy))(this,that,status);
+}
+int astMapDefined_( AstKeyMap *this, const char *key, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,KeyMap,MapDefined))(this,key,status);
+}
+int astMapSize_( AstKeyMap *this, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,KeyMap,MapSize))(this,status);
+}
+int astMapLenC_( AstKeyMap *this, const char *key, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,KeyMap,MapLenC))(this,key,status);
+}
+int astMapLength_( AstKeyMap *this, const char *key, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,KeyMap,MapLength))(this,key,status);
+}
+int astMapType_( AstKeyMap *this, const char *key, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,KeyMap,MapType))(this,key,status);
+}
+int astMapHasKey_( AstKeyMap *this, const char *key, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,KeyMap,MapHasKey))(this,key,status);
+}
+const char *astMapKey_( AstKeyMap *this, int index, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,KeyMap,MapKey))(this,index,status);
+}
+const char *astMapIterate_( AstKeyMap *this, int reset, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,KeyMap,MapIterate))(this,reset,status);
+}
+int astGetSizeGuess_( AstKeyMap *this, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,KeyMap,GetSizeGuess))(this,status);
+}
+int astTestSizeGuess_( AstKeyMap *this, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,KeyMap,TestSizeGuess))(this,status);
+}
+void astClearSizeGuess_( AstKeyMap *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,KeyMap,ClearSizeGuess))(this,status);
+}
+void astSetSizeGuess_( AstKeyMap *this, int sizeguess, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,KeyMap,SetSizeGuess))(this,sizeguess,status);
+}
+
+void astClearMapLocked_( AstKeyMap *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,KeyMap,ClearMapLocked))(this,status);
+}
+void astSetMapLocked_( AstKeyMap *this, int maplocked, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,KeyMap,SetMapLocked))(this,maplocked,status);
+}
+
+void astClearKeyError_( AstKeyMap *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,KeyMap,ClearKeyError))(this,status);
+}
+void astSetKeyError_( AstKeyMap *this, int keyerror, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,KeyMap,SetKeyError))(this,keyerror,status);
+}
+
+void astClearSortBy_( AstKeyMap *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,KeyMap,ClearSortBy))(this,status);
+}
+void astSetSortBy_( AstKeyMap *this, int sortby, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,KeyMap,SetSortBy))(this,sortby,status);
+}
+
+void astClearKeyCase_( AstKeyMap *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,KeyMap,ClearKeyCase))(this,status);
+}
+void astSetKeyCase_( AstKeyMap *this, int keycase, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,KeyMap,SetKeyCase))(this,keycase,status);
+}
+
diff --git a/ast/keymap.h b/ast/keymap.h
new file mode 100644
index 0000000..78c10f1
--- /dev/null
+++ b/ast/keymap.h
@@ -0,0 +1,566 @@
+#if !defined( KEYMAP_INCLUDED ) /* Include this file only once */
+#define KEYMAP_INCLUDED
+/*
+*+
+*  Name:
+*     keymap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the KeyMap class.
+
+*  Invocation:
+*     #include "keymap.h"
+
+*  Description:
+*     This include file defines the interface to the KeyMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The KeyMap class extends the Object class to represent a set of
+*     key/value pairs. Keys are strings, and values can be integer,
+*     floating point, string or Object - scalar of vector.
+
+*  Inheritance:
+*     The KeyMap class inherits from the Object class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     13-NOV-2004 (DSB):
+*        Original version.
+*     5-JUN-2006 (DSB):
+*        Added support for single precision entries.
+*     7-MAR-2008 (DSB):
+*        Added support for pointer ("P") entries.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Coordinate objects (parent class) */
+
+/* C header files. */
+/* --------------- */
+
+/* Macros */
+/* ====== */
+/* Data type constants: */
+#define AST__BADTYPE 0
+#define AST__INTTYPE 1
+#define AST__DOUBLETYPE 2
+#define AST__STRINGTYPE 3
+#define AST__OBJECTTYPE 4
+#define AST__FLOATTYPE 5
+#define AST__POINTERTYPE 6
+#define AST__SINTTYPE 7
+#define AST__UNDEFTYPE 8
+#define AST__BYTETYPE 9
+
+/* Define constants used to size global arrays in this module. */
+#define AST__KEYMAP_GETATTRIB_BUFF_LEN 50       /* Max length of string returned by GetAttrib */
+#define AST__KEYMAP_CONVERTVALUE_MAX_STRINGS 50 /* Number of string values to buffer in ConvertValue */
+#define AST__KEYMAP_CONVERTVALUE_BUFF_LEN 50    /* Max. characters in result buffer for ConvertValue */
+#define AST__KEYMAP_MAPKEY_MAX_STRINGS 50       /* Number of string values to buffer in MapKey */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Maximum key length when using case insensitive keymaps */
+#define AST__MXKEYLEN 200
+
+/* Type Definitions. */
+/* ================= */
+
+/* This structure contains information describing a single generic entry in
+   a KeyMap. This structure is extended by other structures to hold data of
+   specific data types. */
+
+typedef struct AstMapEntry {
+   struct AstMapEntry *next; /* Pointer to next structure in unsorted list. */
+   const char *key;          /* The name used to identify the entry */
+   unsigned long hash;       /* The full width hash value */
+   int type;                 /* Data type. */
+   int nel;                  /* 0 => scalar, >0 => array with "nel" elements */
+   const char *comment;      /* Pointer to a comment for the entry */
+   int defined;              /* Non-zero if the entry value is defined */
+   struct AstMapEntry *snext;/* Pointer to next structure in sorted list. */
+   struct AstMapEntry *sprev;/* Pointer to previous structure in sorted list. */
+   int member;               /* No. of values added to KeyMap prior to this one  */
+   int keymember;            /* No. of keys added to KeyMap prior to this one  */
+   int sortby;               /* Used for comunnication with qsort function */
+} AstMapEntry;
+
+/* KeyMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstKeyMap {
+
+/* Attributes inherited from the parent class. */
+   AstObject object;               /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int sizeguess;                  /* Guess at KeyMap size */
+   AstMapEntry **table;            /* Hash table containing pointers to
+                                      the KeyMap entries */
+   int *nentry;                    /* No. of Entries in each table element */
+   int mapsize;                    /* Length of table */
+   int keycase;                    /* Are keys case sensitive? */
+   int keyerror;                   /* Report error if no key? */
+   int maplocked;                  /* Prevent addition of new entries? */
+   int sortby;                     /* How the keys should be sorted */
+   AstMapEntry *first;             /* Pointer to first structure in sorted list. */
+   int nsorted;                    /* Length of sorted list */
+   int member_count;               /* Total no. of values ever added to keyMap */
+   AstMapEntry *firstA;            /* Pointer to first "AST object"-type entry */
+   int iter_itab;                  /* Next hash table entry to return */
+   AstMapEntry *iter_entry;        /* Next entry to return */
+} AstKeyMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstKeyMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstObjectVtab object_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   void (* MapPut0I)( AstKeyMap *, const char *, int, const char *, int * );
+   void (* MapPut0D)( AstKeyMap *, const char *, double, const char *, int * );
+   void (* MapPut0B)( AstKeyMap *, const char *, unsigned char, const char *, int * );
+   void (* MapPut0S)( AstKeyMap *, const char *, short int, const char *, int * );
+   void (* MapPut0F)( AstKeyMap *, const char *, float, const char *, int * );
+   void (* MapPut0C)( AstKeyMap *, const char *, const char *, const char *, int * );
+   void (* MapPut0A)( AstKeyMap *, const char *, AstObject *, const char *, int * );
+   void (* MapPut0P)( AstKeyMap *, const char *, void *, const char *, int * );
+   void (* MapPut1I)( AstKeyMap *, const char *, int, const int[], const char *, int * );
+   void (* MapPut1D)( AstKeyMap *, const char *, int, const double[], const char *, int * );
+   void (* MapPut1B)( AstKeyMap *, const char *, int, const unsigned char[], const char *, int * );
+   void (* MapPut1S)( AstKeyMap *, const char *, int, const short int[], const char *, int * );
+   void (* MapPut1F)( AstKeyMap *, const char *, int, const float[], const char *, int * );
+   void (* MapPut1C)( AstKeyMap *, const char *, int, const char *const [], const char *, int * );
+   void (* MapPut1A)( AstKeyMap *, const char *, int, AstObject *const [], const char *, int * );
+   void (* MapPut1P)( AstKeyMap *, const char *, int, void *const [], const char *, int * );
+   void (* MapPutU)( AstKeyMap *, const char *, const char *, int * );
+   int (* MapGet0I)( AstKeyMap *, const char *, int *, int * );
+   int (* MapGet0D)( AstKeyMap *, const char *, double *, int * );
+   int (* MapGet0B)( AstKeyMap *, const char *, unsigned char *, int * );
+   int (* MapGet0S)( AstKeyMap *, const char *, short int *, int * );
+   int (* MapGet0F)( AstKeyMap *, const char *, float *, int * );
+   int (* MapGet0C)( AstKeyMap *, const char *, const char **, int * );
+   int (* MapGet0A)( AstKeyMap *, const char *, AstObject **, int * );
+   int (* MapGet0P)( AstKeyMap *, const char *, void **, int * );
+   int (* MapGet1A)( AstKeyMap *, const char *, int, int *, AstObject **, int * );
+   int (* MapGet1P)( AstKeyMap *, const char *, int, int *, void **, int * );
+   int (* MapGet1C)( AstKeyMap *, const char *, int, int, int *, char *, int * );
+   int (* MapGet1D)( AstKeyMap *, const char *, int, int *, double *, int * );
+   int (* MapGet1B)( AstKeyMap *, const char *, int, int *, unsigned char *, int * );
+   int (* MapGet1S)( AstKeyMap *, const char *, int, int *, short int *, int * );
+   int (* MapGet1F)( AstKeyMap *, const char *, int, int *, float *, int * );
+   int (* MapGet1I)( AstKeyMap *, const char *, int, int *, int *, int * );
+   int (* MapGetElemA)( AstKeyMap *, const char *, int, AstObject **, int * );
+   int (* MapGetElemP)( AstKeyMap *, const char *, int, void **, int * );
+   int (* MapGetElemC)( AstKeyMap *, const char *, int, int, char *, int * );
+   int (* MapGetElemD)( AstKeyMap *, const char *, int, double *, int * );
+   int (* MapGetElemB)( AstKeyMap *, const char *, int, unsigned char *, int * );
+   int (* MapGetElemS)( AstKeyMap *, const char *, int, short int *, int * );
+   int (* MapGetElemF)( AstKeyMap *, const char *, int, float *, int * );
+   int (* MapGetElemI)( AstKeyMap *, const char *, int, int *, int * );
+   void (* MapPutElemA)( AstKeyMap *, const char *, int, AstObject *, int * );
+   void (* MapPutElemP)( AstKeyMap *, const char *, int, void *, int * );
+   void (* MapPutElemC)( AstKeyMap *, const char *, int, const char *, int * );
+   void (* MapPutElemD)( AstKeyMap *, const char *, int, double, int * );
+   void (* MapPutElemB)( AstKeyMap *, const char *, int, unsigned char, int * );
+   void (* MapPutElemS)( AstKeyMap *, const char *, int, short int, int * );
+   void (* MapPutElemF)( AstKeyMap *, const char *, int, float, int * );
+   void (* MapPutElemI)( AstKeyMap *, const char *, int, int, int * );
+   void (* MapRemove)( AstKeyMap *, const char *, int * );
+   void (* MapRename)( AstKeyMap *, const char *, const char *, int * );
+   void (* MapCopy)( AstKeyMap *, AstKeyMap *, int * );
+   int (* MapSize)( AstKeyMap *, int * );
+   int (* MapLength)( AstKeyMap *, const char *, int * );
+   int (* MapLenC)( AstKeyMap *, const char *, int * );
+   int (* MapType)( AstKeyMap *, const char *, int * );
+   int (* MapHasKey)( AstKeyMap *, const char *, int * );
+   int (* MapDefined)( AstKeyMap *, const char *, int * );
+   const char *(* MapIterate)( AstKeyMap *, int, int * );
+   const char *(* MapKey)( AstKeyMap *, int, int * );
+
+   int (* GetSizeGuess)( AstKeyMap *, int * );
+   int (* TestSizeGuess)( AstKeyMap *, int * );
+   void (* SetSizeGuess)( AstKeyMap *, int, int * );
+   void (* ClearSizeGuess)( AstKeyMap *, int * );
+
+   int (* GetMapLocked)( AstKeyMap *, int * );
+   int (* TestMapLocked)( AstKeyMap *, int * );
+   void (* ClearMapLocked)( AstKeyMap *, int * );
+   void (* SetMapLocked)( AstKeyMap *, int, int * );
+
+   int (* GetKeyError)( AstKeyMap *, int * );
+   int (* TestKeyError)( AstKeyMap *, int * );
+   void (* ClearKeyError)( AstKeyMap *, int * );
+   void (* SetKeyError)( AstKeyMap *, int, int * );
+
+   int (* GetKeyCase)( AstKeyMap *, int * );
+   int (* TestKeyCase)( AstKeyMap *, int * );
+   void (* ClearKeyCase)( AstKeyMap *, int * );
+   void (* SetKeyCase)( AstKeyMap *, int, int * );
+
+   int (* GetSortBy)( AstKeyMap *, int * );
+   int (* TestSortBy)( AstKeyMap *, int * );
+   void (* ClearSortBy)( AstKeyMap *, int * );
+   void (* SetSortBy)( AstKeyMap *, int, int * );
+
+} AstKeyMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstKeyMapGlobals {
+   AstKeyMapVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ AST__KEYMAP_GETATTRIB_BUFF_LEN + 1 ];
+   char *ConvertValue_Strings[ AST__KEYMAP_CONVERTVALUE_MAX_STRINGS ];
+   int ConvertValue_Istr;
+   int ConvertValue_Init;
+   char ConvertValue_Buff[ AST__KEYMAP_CONVERTVALUE_BUFF_LEN + 1 ];
+   char *MapKey_Strings[ AST__KEYMAP_MAPKEY_MAX_STRINGS ];
+   int MapKey_Istr;
+   int MapKey_Init;
+} AstKeyMapGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(KeyMap)          /* Check class membership */
+astPROTO_ISA(KeyMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstKeyMap *astKeyMap_( const char *, int *, ...);
+#else
+AstKeyMap *astKeyMapId_( const char *, ... )__attribute__((format(printf,1,2)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstKeyMap *astInitKeyMap_( void *, size_t, int, AstKeyMapVtab *, const char *, int * );
+
+/* Vtab initialiser. */
+void astInitKeyMapVtab_( AstKeyMapVtab *, const char *, int * );
+
+/* Loader. */
+AstKeyMap *astLoadKeyMap_( void *, size_t, AstKeyMapVtab *, const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitKeyMapGlobals_( AstKeyMapGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+#if defined(astCLASS)            /* Protected */
+int astMapGet0A_( AstKeyMap *, const char *, AstObject **, int * );
+int astMapGet1A_( AstKeyMap *, const char *, int, int *, AstObject **, int * );
+void astMapPut1A_( AstKeyMap *, const char *, int, AstObject *const [], const char *, int * );
+int astMapGetElemA_( AstKeyMap *, const char *, int, AstObject **, int * );
+#else
+int astMapGet0AId_( AstKeyMap *, const char *, AstObject **, int * );
+int astMapGet1AId_( AstKeyMap *, const char *, int, int *, AstObject **, int * );
+void astMapPut1AId_( AstKeyMap *, const char *, int, AstObject *const [], const char *, int * );
+int astMapGetElemAId_( AstKeyMap *, const char *, int, AstObject **, int * );
+#endif
+
+const char *astMapKey_( AstKeyMap *, int, int * );
+
+
+int astMapGet0B_( AstKeyMap *, const char *, unsigned char *, int * );
+int astMapGet0C_( AstKeyMap *, const char *, const char **, int * );
+int astMapGet0D_( AstKeyMap *, const char *, double *, int * );
+int astMapGet0F_( AstKeyMap *, const char *, float *, int * );
+int astMapGet0I_( AstKeyMap *, const char *, int *, int * );
+int astMapGet0P_( AstKeyMap *, const char *, void **, int * );
+int astMapGet0S_( AstKeyMap *, const char *, short int *, int * );
+int astMapGet1B_( AstKeyMap *, const char *, int, int *, unsigned char *, int * );
+int astMapGet1C_( AstKeyMap *, const char *, int, int, int *, char *, int * );
+int astMapGet1D_( AstKeyMap *, const char *, int, int *, double *, int * );
+int astMapGet1F_( AstKeyMap *, const char *, int, int *, float *, int * );
+int astMapGet1I_( AstKeyMap *, const char *, int, int *, int *, int * );
+int astMapGet1P_( AstKeyMap *, const char *, int, int *, void **, int * );
+int astMapGet1S_( AstKeyMap *, const char *, int, int *, short int *, int * );
+int astMapGetElemB_( AstKeyMap *, const char *, int, unsigned char *, int * );
+int astMapGetElemC_( AstKeyMap *, const char *, int, int, char *, int * );
+int astMapGetElemD_( AstKeyMap *, const char *, int, double *, int * );
+int astMapGetElemF_( AstKeyMap *, const char *, int, float *, int * );
+int astMapGetElemI_( AstKeyMap *, const char *, int, int *, int * );
+int astMapGetElemP_( AstKeyMap *, const char *, int, void **, int * );
+int astMapGetElemS_( AstKeyMap *, const char *, int, short int *, int * );
+int astMapHasKey_( AstKeyMap *, const char *, int * );
+int astMapDefined_( AstKeyMap *, const char *, int * );
+int astMapLenC_( AstKeyMap *, const char *, int * );
+int astMapLength_( AstKeyMap *, const char *, int * );
+int astMapSize_( AstKeyMap *, int * );
+int astMapType_( AstKeyMap *, const char *, int * );
+void astMapCopy_( AstKeyMap *, AstKeyMap *, int * );
+void astMapPut0A_( AstKeyMap *, const char *, AstObject *, const char *, int * );
+void astMapPut0B_( AstKeyMap *, const char *, unsigned char, const char *, int * );
+void astMapPut0C_( AstKeyMap *, const char *, const char *, const char *, int * );
+void astMapPut0D_( AstKeyMap *, const char *, double, const char *, int * );
+void astMapPut0F_( AstKeyMap *, const char *, float, const char *, int * );
+void astMapPut0I_( AstKeyMap *, const char *, int, const char *, int * );
+void astMapPut0P_( AstKeyMap *, const char *, void *, const char *, int * );
+void astMapPut0S_( AstKeyMap *, const char *, short int, const char *, int * );
+void astMapPut1B_( AstKeyMap *, const char *, int, const unsigned char[], const char *, int * );
+void astMapPut1C_( AstKeyMap *, const char *, int, const char *const [], const char *, int * );
+void astMapPut1D_( AstKeyMap *, const char *, int, const double *, const char *, int * );
+void astMapPut1F_( AstKeyMap *, const char *, int, const float *, const char *, int * );
+void astMapPut1I_( AstKeyMap *, const char *, int, const int *, const char *, int * );
+void astMapPut1P_( AstKeyMap *, const char *, int, void *const [], const char *, int * );
+void astMapPut1S_( AstKeyMap *, const char *, int, const short int *, const char *, int * );
+void astMapPutElemA_( AstKeyMap *, const char *, int, AstObject *, int * );
+void astMapPutElemB_( AstKeyMap *, const char *, int, unsigned char, int * );
+void astMapPutElemC_( AstKeyMap *, const char *, int, const char *, int * );
+void astMapPutElemD_( AstKeyMap *, const char *, int, double, int * );
+void astMapPutElemF_( AstKeyMap *, const char *, int, float, int * );
+void astMapPutElemI_( AstKeyMap *, const char *, int, int, int * );
+void astMapPutElemP_( AstKeyMap *, const char *, int, void *, int * );
+void astMapPutElemS_( AstKeyMap *, const char *, int, short int, int * );
+void astMapPutU_( AstKeyMap *, const char *, const char *, int * );
+void astMapRemove_( AstKeyMap *, const char *, int * );
+void astMapRename_( AstKeyMap *, const char *, const char *, int * );
+
+#if defined(astCLASS)            /* Protected */
+const char *astMapIterate_( AstKeyMap *, int, int * );
+
+int astGetSizeGuess_( AstKeyMap *, int * );
+int astTestSizeGuess_( AstKeyMap *, int * );
+void astSetSizeGuess_( AstKeyMap *, int, int * );
+void astClearSizeGuess_( AstKeyMap *, int * );
+
+int astGetKeyError_( AstKeyMap *, int * );
+int astTestKeyError_( AstKeyMap *, int * );
+void astSetKeyError_( AstKeyMap *, int, int * );
+void astClearKeyError_( AstKeyMap *, int * );
+
+int astGetKeyCase_( AstKeyMap *, int * );
+int astTestKeyCase_( AstKeyMap *, int * );
+void astSetKeyCase_( AstKeyMap *, int, int * );
+void astClearKeyCase_( AstKeyMap *, int * );
+
+int astGetSortBy_( AstKeyMap *, int * );
+int astTestSortBy_( AstKeyMap *, int * );
+void astSetSortBy_( AstKeyMap *, int, int * );
+void astClearSortBy_( AstKeyMap *, int * );
+
+int astGetMapLocked_( AstKeyMap *, int * );
+int astTestMapLocked_( AstKeyMap *, int * );
+void astSetMapLocked_( AstKeyMap *, int, int * );
+void astClearMapLocked_( AstKeyMap *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckKeyMap(this) astINVOKE_CHECK(KeyMap,this,0)
+#define astVerifyKeyMap(this) astINVOKE_CHECK(KeyMap,this,1)
+
+/* Test class membership. */
+#define astIsAKeyMap(this) astINVOKE_ISA(KeyMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astKeyMap astINVOKE(F,astKeyMap_)
+#else
+#define astKeyMap astINVOKE(F,astKeyMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitKeyMap(mem,size,init,vtab,name) astINVOKE(O,astInitKeyMap_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitKeyMapVtab(vtab,name) astINVOKE(V,astInitKeyMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadKeyMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadKeyMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckKeyMap to validate KeyMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+#define astMapPutU(this,key,comment) astINVOKE(V,astMapPutU_(astCheckKeyMap(this),key,comment,STATUS_PTR))
+#define astMapPut0I(this,key,value,comment) astINVOKE(V,astMapPut0I_(astCheckKeyMap(this),key,value,comment,STATUS_PTR))
+#define astMapPut0B(this,key,value,comment) astINVOKE(V,astMapPut0B_(astCheckKeyMap(this),key,value,comment,STATUS_PTR))
+#define astMapPut0S(this,key,value,comment) astINVOKE(V,astMapPut0S_(astCheckKeyMap(this),key,value,comment,STATUS_PTR))
+#define astMapPut0D(this,key,value,comment) astINVOKE(V,astMapPut0D_(astCheckKeyMap(this),key,value,comment,STATUS_PTR))
+#define astMapPut0F(this,key,value,comment) astINVOKE(V,astMapPut0F_(astCheckKeyMap(this),key,value,comment,STATUS_PTR))
+#define astMapPut0C(this,key,value,comment) astINVOKE(V,astMapPut0C_(astCheckKeyMap(this),key,value,comment,STATUS_PTR))
+#define astMapPut0A(this,key,value,comment) astINVOKE(V,astMapPut0A_(astCheckKeyMap(this),key,astCheckObject(value),comment,STATUS_PTR))
+#define astMapPut0P(this,key,value,comment) astINVOKE(V,astMapPut0P_(astCheckKeyMap(this),key,value,comment,STATUS_PTR))
+#define astMapPut1I(this,key,size,value,comment) astINVOKE(V,astMapPut1I_(astCheckKeyMap(this),key,size,value,comment,STATUS_PTR))
+#define astMapPut1B(this,key,size,value,comment) astINVOKE(V,astMapPut1B_(astCheckKeyMap(this),key,size,value,comment,STATUS_PTR))
+#define astMapPut1S(this,key,size,value,comment) astINVOKE(V,astMapPut1S_(astCheckKeyMap(this),key,size,value,comment,STATUS_PTR))
+#define astMapPut1D(this,key,size,value,comment) astINVOKE(V,astMapPut1D_(astCheckKeyMap(this),key,size,value,comment,STATUS_PTR))
+#define astMapPut1F(this,key,size,value,comment) astINVOKE(V,astMapPut1F_(astCheckKeyMap(this),key,size,value,comment,STATUS_PTR))
+#define astMapPut1C(this,key,size,value,comment) astINVOKE(V,astMapPut1C_(astCheckKeyMap(this),key,size,value,comment,STATUS_PTR))
+#define astMapGet0I(this,key,value) astINVOKE(V,astMapGet0I_(astCheckKeyMap(this),key,value,STATUS_PTR))
+#define astMapGet0B(this,key,value) astINVOKE(V,astMapGet0B_(astCheckKeyMap(this),key,value,STATUS_PTR))
+#define astMapGet0S(this,key,value) astINVOKE(V,astMapGet0S_(astCheckKeyMap(this),key,value,STATUS_PTR))
+#define astMapGet0D(this,key,value) astINVOKE(V,astMapGet0D_(astCheckKeyMap(this),key,value,STATUS_PTR))
+#define astMapGet0F(this,key,value) astINVOKE(V,astMapGet0F_(astCheckKeyMap(this),key,value,STATUS_PTR))
+#define astMapGet0C(this,key,value) astINVOKE(V,astMapGet0C_(astCheckKeyMap(this),key,value,STATUS_PTR))
+#define astMapGet1I(this,key,mxval,nval,value) astINVOKE(V,astMapGet1I_(astCheckKeyMap(this),key,mxval,nval,value,STATUS_PTR))
+#define astMapGet1B(this,key,mxval,nval,value) astINVOKE(V,astMapGet1B_(astCheckKeyMap(this),key,mxval,nval,value,STATUS_PTR))
+#define astMapGet1S(this,key,mxval,nval,value) astINVOKE(V,astMapGet1S_(astCheckKeyMap(this),key,mxval,nval,value,STATUS_PTR))
+#define astMapGet1D(this,key,mxval,nval,value) astINVOKE(V,astMapGet1D_(astCheckKeyMap(this),key,mxval,nval,value,STATUS_PTR))
+#define astMapGet1F(this,key,mxval,nval,value) astINVOKE(V,astMapGet1F_(astCheckKeyMap(this),key,mxval,nval,value,STATUS_PTR))
+#define astMapGet1C(this,key,l,mxval,nval,value) astINVOKE(V,astMapGet1C_(astCheckKeyMap(this),key,l,mxval,nval,value,STATUS_PTR))
+#define astMapGetElemI(this,key,elem,value) astINVOKE(V,astMapGetElemI_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapGetElemB(this,key,elem,value) astINVOKE(V,astMapGetElemB_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapGetElemS(this,key,elem,value) astINVOKE(V,astMapGetElemS_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapGetElemD(this,key,elem,value) astINVOKE(V,astMapGetElemD_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapGetElemF(this,key,elem,value) astINVOKE(V,astMapGetElemF_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapGetElemC(this,key,l,elem,value) astINVOKE(V,astMapGetElemC_(astCheckKeyMap(this),key,l,elem,value,STATUS_PTR))
+#define astMapGetElemP(this,key,elem,value) astINVOKE(V,astMapGetElemP_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapPutElemA(this,key,elem,value) astINVOKE(V,astMapPutElemA_(astCheckKeyMap(this),key,elem,astCheckObject(value),STATUS_PTR))
+#define astMapPutElemI(this,key,elem,value) astINVOKE(V,astMapPutElemI_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapPutElemB(this,key,elem,value) astINVOKE(V,astMapPutElemB_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapPutElemS(this,key,elem,value) astINVOKE(V,astMapPutElemS_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapPutElemD(this,key,elem,value) astINVOKE(V,astMapPutElemD_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapPutElemF(this,key,elem,value) astINVOKE(V,astMapPutElemF_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapPutElemC(this,key,elem,value) astINVOKE(V,astMapPutElemC_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapPutElemP(this,key,elem,value) astINVOKE(V,astMapPutElemP_(astCheckKeyMap(this),key,elem,value,STATUS_PTR))
+#define astMapRemove(this,key) astINVOKE(V,astMapRemove_(astCheckKeyMap(this),key,STATUS_PTR))
+#define astMapRename(this,oldkey,newkey) astINVOKE(V,astMapRename_(astCheckKeyMap(this),oldkey,newkey,STATUS_PTR))
+#define astMapCopy(this,that) astINVOKE(V,astMapCopy_(astCheckKeyMap(this),astCheckKeyMap(that),STATUS_PTR))
+#define astMapSize(this) astINVOKE(V,astMapSize_(astCheckKeyMap(this),STATUS_PTR))
+#define astMapLength(this,key) astINVOKE(V,astMapLength_(astCheckKeyMap(this),key,STATUS_PTR))
+#define astMapLenC(this,key) astINVOKE(V,astMapLenC_(astCheckKeyMap(this),key,STATUS_PTR))
+#define astMapHasKey(this,key) astINVOKE(V,astMapHasKey_(astCheckKeyMap(this),key,STATUS_PTR))
+#define astMapDefined(this,key) astINVOKE(V,astMapDefined_(astCheckKeyMap(this),key,STATUS_PTR))
+#define astMapKey(this,index) astINVOKE(V,astMapKey_(astCheckKeyMap(this),index,STATUS_PTR))
+#define astMapType(this,key) astINVOKE(V,astMapType_(astCheckKeyMap(this),key,STATUS_PTR))
+#define astMapGet0P(this,key,value) astINVOKE(V,astMapGet0P_(astCheckKeyMap(this),key,value,STATUS_PTR))
+#define astMapGet1P(this,key,mxval,nval,value) astINVOKE(V,astMapGet1P_(astCheckKeyMap(this),key,mxval,nval,value,STATUS_PTR))
+#define astMapPut1P(this,key,size,value,comment) astINVOKE(V,astMapPut1P_(astCheckKeyMap(this),key,size,value,comment,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#define astMapGet0A(this,key,value) astINVOKE(V,astMapGet0A_(astCheckKeyMap(this),key,(AstObject **)(value),STATUS_PTR))
+#define astMapGet1A(this,key,mxval,nval,value) astINVOKE(V,astMapGet1A_(astCheckKeyMap(this),key,mxval,nval,(AstObject **)(value),STATUS_PTR))
+#define astMapPut1A(this,key,size,value,comment) astINVOKE(V,astMapPut1A_(astCheckKeyMap(this),key,size,value,comment,STATUS_PTR))
+#define astMapGetElemA(this,key,elem,value) astINVOKE(V,astMapGetElemA_(astCheckKeyMap(this),key,elem,(AstObject **)(value),STATUS_PTR))
+#define astMapIterate(this,reset) astINVOKE(V,astMapIterate_(astCheckKeyMap(this),reset,STATUS_PTR))
+
+#define astClearSizeGuess(this) \
+astINVOKE(V,astClearSizeGuess_(astCheckKeyMap(this),STATUS_PTR))
+#define astGetSizeGuess(this) \
+astINVOKE(V,astGetSizeGuess_(astCheckKeyMap(this),STATUS_PTR))
+#define astSetSizeGuess(this,sizeguess) \
+astINVOKE(V,astSetSizeGuess_(astCheckKeyMap(this),sizeguess,STATUS_PTR))
+#define astTestSizeGuess(this) \
+astINVOKE(V,astTestSizeGuess_(astCheckKeyMap(this),STATUS_PTR))
+
+#define astClearKeyError(this) \
+astINVOKE(V,astClearKeyError_(astCheckKeyMap(this),STATUS_PTR))
+#define astGetKeyError(this) \
+astINVOKE(V,astGetKeyError_(astCheckKeyMap(this),STATUS_PTR))
+#define astSetKeyError(this,keyerror) \
+astINVOKE(V,astSetKeyError_(astCheckKeyMap(this),keyerror,STATUS_PTR))
+#define astTestKeyError(this) \
+astINVOKE(V,astTestKeyError_(astCheckKeyMap(this),STATUS_PTR))
+
+#define astClearKeyCase(this) \
+astINVOKE(V,astClearKeyCase_(astCheckKeyMap(this),STATUS_PTR))
+#define astGetKeyCase(this) \
+astINVOKE(V,astGetKeyCase_(astCheckKeyMap(this),STATUS_PTR))
+#define astSetKeyCase(this,keycase) \
+astINVOKE(V,astSetKeyCase_(astCheckKeyMap(this),keycase,STATUS_PTR))
+#define astTestKeyCase(this) \
+astINVOKE(V,astTestKeyCase_(astCheckKeyMap(this),STATUS_PTR))
+
+#define astClearSortBy(this) \
+astINVOKE(V,astClearSortBy_(astCheckKeyMap(this),STATUS_PTR))
+#define astGetSortBy(this) \
+astINVOKE(V,astGetSortBy_(astCheckKeyMap(this),STATUS_PTR))
+#define astSetSortBy(this,sortby) \
+astINVOKE(V,astSetSortBy_(astCheckKeyMap(this),sortby,STATUS_PTR))
+#define astTestSortBy(this) \
+astINVOKE(V,astTestSortBy_(astCheckKeyMap(this),STATUS_PTR))
+
+#define astClearMapLocked(this) \
+astINVOKE(V,astClearMapLocked_(astCheckKeyMap(this),STATUS_PTR))
+#define astGetMapLocked(this) \
+astINVOKE(V,astGetMapLocked_(astCheckKeyMap(this),STATUS_PTR))
+#define astSetMapLocked(this,maplocked) \
+astINVOKE(V,astSetMapLocked_(astCheckKeyMap(this),maplocked,STATUS_PTR))
+#define astTestMapLocked(this) \
+astINVOKE(V,astTestMapLocked_(astCheckKeyMap(this),STATUS_PTR))
+
+
+#else
+#define astMapGet0A(this,key,value) astINVOKE(V,astMapGet0AId_(astCheckKeyMap(this),key,(AstObject **)(value),STATUS_PTR))
+#define astMapGet1A(this,key,mxval,nval,value) astINVOKE(V,astMapGet1AId_(astCheckKeyMap(this),key,mxval,nval,(AstObject **)(value),STATUS_PTR))
+#define astMapPut1A(this,key,size,value,comment) astINVOKE(V,astMapPut1AId_(astCheckKeyMap(this),key,size,value,comment,STATUS_PTR))
+#define astMapGetElemA(this,key,elem,value) astINVOKE(V,astMapGetElemAId_(astCheckKeyMap(this),key,elem,(AstObject **)(value),STATUS_PTR))
+#endif
+
+#endif
+
diff --git a/ast/loader.c b/ast/loader.c
new file mode 100644
index 0000000..fa66440
--- /dev/null
+++ b/ast/loader.c
@@ -0,0 +1,197 @@
+#define astCLASS
+#include "axis.h"
+#include "box.h"
+#include "channel.h"
+#include "circle.h"
+#include "cmpframe.h"
+#include "cmpmap.h"
+#include "cmpregion.h"
+#include "dsbspecframe.h"
+#include "dssmap.h"
+#include "ellipse.h"
+#include "fitschan.h"
+#include "fluxframe.h"
+#include "timeframe.h"
+#include "timemap.h"
+#include "frame.h"
+#include "frameset.h"
+#include "grismmap.h"
+#include "interval.h"
+#include "intramap.h"
+#include "keymap.h"
+#include "loader.h"
+#include "lutmap.h"
+#include "mapping.h"
+#include "mathmap.h"
+#include "matrixmap.h"
+#include "nullregion.h"
+#include "object.h"
+#include "pcdmap.h"
+#include "permmap.h"
+#include "plot.h"
+#include "plot3d.h"
+#include "pointlist.h"
+#include "pointset.h"
+#include "polygon.h"
+#include "polymap.h"
+#include "prism.h"
+#include "normmap.h"
+#include "ratemap.h"
+#include "region.h"
+#include "shiftmap.h"
+#include "skyaxis.h"
+#include "skyframe.h"
+#include "slamap.h"
+#include "specfluxframe.h"
+#include "specframe.h"
+#include "specmap.h"
+#include "sphmap.h"
+#include "tranmap.h"
+#include "selectormap.h"
+#include "switchmap.h"
+#include "unitmap.h"
+#include "unitnormmap.h"
+#include "wcsmap.h"
+#include "winmap.h"
+#include "xmlchan.h"
+#include "zoommap.h"
+#include "stc.h"
+#include "stcresourceprofile.h"
+#include "stcsearchlocation.h"
+#include "stccatalogentrylocation.h"
+#include "stcobsdatalocation.h"
+#include "stcschan.h"
+#include "table.h"
+#include "fitstable.h"
+
+#include "error.h"
+#include "ast_err.h"
+#include <stddef.h>
+#include <string.h>
+
+/*
+*+
+*  Copyright:
+*     Copyright (C) 1997-2016 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+*     RO: Russell Owen (LSST)
+
+*  History:
+*     18-NOV-1997 (RFWS):
+*        Original version.
+*     18-MAR-1998 (RFWS):
+*        Added the IntraMap class.
+*     3-JUN-1999 (RFWS):
+*        Added the PcdMap class.
+*     17-AUG-1999 (RFWS):
+*        Added the MathMap class.
+*     8-JAN-2003 (DSB):
+*        Added the SpecMap and SpecFrame classes.
+*     15-JUL-2003 (DSB):
+*        Added the GrsimMap class.
+*     6-FEB-2009 (DSB):
+*        Added the StcsChan class.
+*     20-APR-2016 (RO):
+*        Added the UnitNormMap class.
+*-
+*/
+
+AstLoaderType *astGetLoader( const char *class, int *status ) {
+   if ( !astOK ) return NULL;
+
+#define LOAD(name) \
+if ( !strcmp( class, #name ) ) return (AstLoaderType *) astLoad##name##_
+
+   LOAD(Axis);
+   LOAD(Box);
+   LOAD(Channel);
+   LOAD(Circle);
+   LOAD(CmpFrame);
+   LOAD(CmpMap);
+   LOAD(CmpRegion);
+   LOAD(DSBSpecFrame);
+   LOAD(DssMap);
+   LOAD(Ellipse);
+   LOAD(FitsChan);
+   LOAD(FitsTable);
+   LOAD(FluxFrame);
+   LOAD(Frame);
+   LOAD(FrameSet);
+   LOAD(GrismMap);
+   LOAD(Interval);
+   LOAD(IntraMap);
+   LOAD(KeyMap);
+   LOAD(LutMap);
+   LOAD(Mapping);
+   LOAD(MathMap);
+   LOAD(MatrixMap);
+   LOAD(NullRegion);
+   LOAD(Object);
+   LOAD(PcdMap);
+   LOAD(PermMap);
+   LOAD(Plot);
+   LOAD(Plot3D);
+   LOAD(PointList);
+   LOAD(PointSet);
+   LOAD(PolyMap);
+   LOAD(Polygon);
+   LOAD(Prism);
+   LOAD(NormMap);
+   LOAD(RateMap);
+   LOAD(Region);
+   LOAD(ShiftMap);
+   LOAD(SkyAxis);
+   LOAD(SkyFrame);
+   LOAD(SlaMap);
+   LOAD(SpecFluxFrame);
+   LOAD(SpecFrame);
+   LOAD(SpecMap);
+   LOAD(SphMap);
+   LOAD(SelectorMap);
+   LOAD(SwitchMap);
+   LOAD(Table);
+   LOAD(TimeFrame);
+   LOAD(TimeMap);
+   LOAD(TranMap);
+   LOAD(UnitMap);
+   LOAD(UnitNormMap);
+   LOAD(WcsMap);
+   LOAD(WinMap);
+   LOAD(XmlChan);
+   LOAD(ZoomMap);
+
+   LOAD(StcsChan);
+   LOAD(Stc);
+   LOAD(StcResourceProfile);
+   LOAD(StcSearchLocation);
+   LOAD(StcCatalogEntryLocation);
+   LOAD(StcObsDataLocation);
+
+   astError( AST__OCLUK, "astGetLoader: Object of unknown class \"%s\" cannot "
+                         "be loaded.", status, class );
+   return NULL;
+#undef LOAD
+}
+
+
+
diff --git a/ast/loader.h b/ast/loader.h
new file mode 100644
index 0000000..6227a81
--- /dev/null
+++ b/ast/loader.h
@@ -0,0 +1,49 @@
+#if !defined( LOADER_INCLUDED )  /* Include this file only once */
+#define LOADER_INCLUDED
+/*
+*+
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     18-NOV-1997 (RFWS):
+*        Original version.
+*-
+*/
+
+#include "object.h"
+#include "channel.h"
+
+#if defined(astCLASS)            /* Protected */
+
+typedef AstObject *(AstLoaderType)( void *, size_t, AstObjectVtab *,
+                                    const char *, AstChannel *, int * );
+
+AstLoaderType *astGetLoader( const char *, int * );
+
+#endif
+#endif
+
+
+
diff --git a/ast/lutmap.c b/ast/lutmap.c
new file mode 100644
index 0000000..76faced
--- /dev/null
+++ b/ast/lutmap.c
@@ -0,0 +1,2629 @@
+/*
+*class++
+*  Name:
+*     LutMap
+
+*  Purpose:
+*     Transform 1-dimensional coordinates using a lookup table.
+
+*  Constructor Function:
+c     astLutMap
+f     AST_LUTMAP
+
+*  Description:
+*     A LutMap is a specialised form of Mapping which transforms
+*     1-dimensional coordinates by using linear interpolation in a
+*     lookup table.
+*
+*     Each input coordinate value is first scaled to give the index of
+*     an entry in the table by subtracting a starting value (the input
+*     coordinate corresponding to the first table entry) and dividing
+*     by an increment (the difference in input coordinate value
+*     between adjacent table entries).
+*
+*     The resulting index will usually contain a fractional part, so
+*     the output coordinate value is then generated by interpolating
+*     linearly between the appropriate entries in the table. If the
+*     index lies outside the range of the table, linear extrapolation
+*     is used based on the two nearest entries (i.e. the two entries
+*     at the start or end of the table, as appropriate). If either of the
+*     entries used for the interplation has a value of AST__BAD, then the
+*     interpolated value is returned as AST__BAD.
+*
+*     If the lookup table entries increase or decrease monotonically
+*     (ignoring any flat sections), then the inverse transformation may
+*     also be performed.
+
+*  Inheritance:
+*     The LutMap class inherits from the Mapping class.
+
+*  Attributes:
+*     In addition to those attributes common to all Mappings, every
+*     LutMap also has the following attributes:
+*
+*     - LutEpsilon: The relative error of the values in the table.
+*     - LutInterp: The interpolation method to use between table entries.
+
+*  Functions:
+c     The LutMap class does not define any new functions beyond those
+f     The LutMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2007-2011 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (JAC, UCLan)
+
+*  History:
+*     8-JUL-1997 (RFWS):
+*        Original version.
+*     10-JUL-1997 (RFWS):
+*        Added the MapMerge function.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitLutMapVtab
+*        method.
+*     12-JAN-2004 (DSB):
+*        Check for AST__BAD values in the supplied lut array.
+*     17-MAR-2006 (DSB):
+*        - MapMerge changed so that a LutMap will cancel with its own
+*        inverse.
+*        - Added attribute LutInterp
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*     4-OCT-2006 (DSB):
+*        - Correct "mintick" to "lutinterp" in SetAttrib.
+*        - Do not include bad values in the dumped LUT array.
+*     8-NOV-2007 (DSB):
+*        - Take account of the requested invert flag when comparing two
+*        neighbouring LutMaps for equality in MapMerge.
+*     19-NOV-2010 (DSB):
+*        Added (protected) astGetLutMapInfo function.
+*     24-JAN-2011 (DSB):
+*        Implement an inverse transformation even if the coordinate
+*        array contains sections of equal or bad values. The inverse
+*        transformation will generate bad values if used within such
+*        regions of the coordinate array.
+*     6-JUL-2011 (DSB):
+*        Avoid indexing the lut array beyond the end when doing an
+*        inverse transform.
+*     2-OCT-2012 (DSB):
+*        Check for Infs as well as NaNs.
+*     21-MAY-2015 (DSB):
+*        Aded LutEpsilon
+*     23-SEP-2015 (DSB):
+*        The GetMonotonic function had a bug that caused all LutMaps
+*        to be considered monotonic, and thus have an inverse
+*        transformation.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS LutMap
+
+#define LINEAR 0
+#define NEAR 1
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory management facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "winmap.h"              /* Linear mappings between windows */
+#include "channel.h"             /* I/O channels */
+#include "unitmap.h"             /* Unit mappings */
+#include "lutmap.h"              /* Interface definition for this class */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(LutMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(LutMap,Class_Init)
+#define class_vtab astGLOBAL(LutMap,Class_Vtab)
+#define getattrib_buff astGLOBAL(LutMap,GetAttrib_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstLutMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstLutMap *astLutMapId_( int, const double [], double, double, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int GetLinear( AstMapping *, int * );
+static int GetMonotonic( int, const double *, int *, double **, int **, int **, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static double *GetLutMapInfo( AstLutMap *, double *, double *, int *, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+static int GetLutInterp( AstLutMap *, int * );
+static int TestLutInterp( AstLutMap *, int * );
+static void ClearLutInterp( AstLutMap *, int * );
+static void SetLutInterp( AstLutMap *, int, int * );
+
+static double GetLutEpsilon( AstLutMap *, int * );
+static int TestLutEpsilon( AstLutMap *, int * );
+static void ClearLutEpsilon( AstLutMap *, int * );
+static void SetLutEpsilon( AstLutMap *, double, int * );
+
+/* Member functions. */
+/* ================= */
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a LutMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     LutMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     LutMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the LutMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstLutMap *this;             /* Pointer to the LutMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the LutMap structure. */
+   this = (AstLutMap *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* LutInterp. */
+/* ---------- */
+   if ( !strcmp( attrib, "lutinterp" ) ) {
+      astClearLutInterp( this );
+
+/* LutEpsilon. */
+/* ------------- */
+   } else if ( !strcmp( attrib, "lutepsilon" ) ) {
+      astClearLutEpsilon( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two LutMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     LutMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two LutMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a LutMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the LutMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstLutMap *that;
+   AstLutMap *this;
+   int i;
+   int nin;
+   int nout;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two LutMap structures. */
+   this = (AstLutMap *) this_object;
+   that = (AstLutMap *) that_object;
+
+/* Check the second object is a LutMap. We know the first is a
+   LutMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsALutMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two LutMaps differ, it may still be possible
+   for them to be equivalent. First compare the LutMaps if their Invert
+   flags are the same. In this case all the attributes of the two LutMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+
+            if( astEQUAL( this->start, that->start ) &&
+                astEQUAL( this->inc, that->inc ) &&
+                this->nlut == that->nlut &&
+                this->lutinterp == that->lutinterp ){
+
+               result = 1;
+               for( i = 0; i < this->nlut; i++ ) {
+                  if( !astEQUAL( (this->lut)[ i ], (that->lut)[ i ] ) ) {
+                     result = 0;
+                     break;
+                  }
+               }
+            }
+
+/* If the Invert flags for the two LutMaps differ, the attributes of the two
+   LutMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a LutMap, Invert flags must be equal. */
+            result = 0;
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a LutMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     LutMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a LutMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the LutMap.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the LutMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the LutMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstLutMap *this;             /* Pointer to the LutMap structure */
+   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
+   const char *result;          /* Pointer value to return */
+   double luteps;               /* LutEpsilon attribute value */
+   int lutinterp;               /* LutInterp attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the LutMap structure. */
+   this = (AstLutMap *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* LutInterp. */
+/* ---------- */
+   if ( !strcmp( attrib, "lutinterp" ) ) {
+      lutinterp = astGetLutInterp( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", lutinterp );
+         result = getattrib_buff;
+      }
+
+/* LutEpsilon. */
+/* ------------- */
+   } else if ( !strcmp( attrib, "lutepsilon" ) ) {
+      luteps = astGetLutEpsilon( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, luteps );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static int GetLinear( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     GetLinear
+
+*  Purpose:
+*     Determine if a LutMap implements a linear coordinate transformation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     int GetLinear( AstMapping *this, int *status )
+
+*  Class Membership:
+*     LutMap member function.
+
+*  Description:
+*     This function returns a boolean value to indicate if the LutMap
+*     supplied is equivalent to a linear coordinate
+*     transformation. This will be the case if the lookup table
+*     elements increase or decrease linearly.
+
+*  Parameters:
+*     this
+*        Pointer to the LutMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*    - A value of zero will be returned if this function is invoked
+*    with the global error status set, or if it should fail for any
+*    reason.
+*/
+
+/* Local Variables: */
+   AstLutMap *this;              /* Pointer to the LutMap structure */
+   double *lut;                  /* Pointer to the lookup table */
+   double eps;                   /* Relative error on the table values */
+   double fract;                 /* Fractional position within table */
+   double hi;                    /* Largest value */
+   double interp;                /* Interpolated value */
+   double lo;                    /* Smallest value */
+   double tol1;                  /* First tolerance estimate */
+   double tol2;                  /* Second tolerance estimate */
+   double tol;                   /* Tolerance value used */
+   int ilut;                     /* Loop counter for table elements */
+   int linear;                   /* Result to be returned */
+   int nlut;                     /* Number of lookup table elements */
+
+/* Initialise. */
+   linear = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return linear;
+
+/* Obtain a pointer to the LutMap structure. */
+   this = (AstLutMap *) this_mapping;
+
+/* Nearest neighbour LutMaps are not considered to be linear because of
+   the discontinuities at the start and end of the table. */
+   if( astGetLutInterp( this ) != NEAR ) {
+
+/* Obtain the lookup table details. */
+      lut = this->lut;
+      nlut = this->nlut;
+
+/* Loop to identify the largest and smallest values in the lookup
+      table. */
+      lo = DBL_MAX;
+      hi = -DBL_MAX;
+      for ( ilut = 0; ilut < nlut; ilut++ ) {
+         if ( lut[ ilut ] > hi ) hi = lut[ ilut ];
+         if ( lut[ ilut ] < lo ) lo = lut[ ilut ];
+      }
+
+/* Check if the values are all the same (this makes the LutMap
+      linear, although it will have no inverse). */
+      linear = ( hi == lo );
+      if ( !linear ) {
+
+/* Get the relative error associated with the table values. */
+         eps = astGetLutEpsilon( this );
+
+/* Form a tolerance estimate based on the overall range of values in
+   the lookup table. */
+         tol1 = fabs( hi - lo ) * eps;
+
+/* Now loop to inspect all the lookup table elements except the first
+      and last. */
+         linear = 1;
+         for ( ilut = 1; ilut < ( nlut - 1 ); ilut++ ) {
+
+/* Calculate the fractional position of the current element within the
+      table. */
+            fract = ( (double) ilut ) / ( (double) ( nlut - 1 ) );
+
+/* Calculate the value it should have if the table is linear by
+      interpolating between the first and last values. */
+            interp = lut[ 0 ] * ( 1.0 - fract ) + lut[ nlut - 1 ] * fract;
+
+/* Form a second tolerance estimate from this interpolated
+   value. Select whichever tolerance estimate is larger (this avoids
+   problems when values are near zero). */
+            tol2 = fabs( interp ) * eps;
+            tol = ( tol1 > tol2 ) ? tol1 : tol2;
+
+/* Test for linearity within a small multiple of the tolerance. */
+            linear = ( fabs( lut[ ilut ] - interp ) <= ( 2.0 * tol ) );
+            if ( !linear ) break;
+         }
+      }
+   }
+
+/* Return the result. */
+   return linear;
+}
+
+static double *GetLutMapInfo( AstLutMap *this, double *start, double *inc,
+                              int *nlut, int *status ){
+/*
+*  Name:
+*     GetLutMapInfo
+
+*  Purpose:
+*     Return information about a LutMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     double *astGetLutMapInfo( AstLutMap *this, double *start, double *inc,
+*                               int *nlut, int *status )
+
+*  Class Membership:
+*     LutMap method
+
+*  Description:
+*     This function returns information about the supplied LutMap.
+
+*  Parameters:
+*     this
+*        Pointer to the LutMap.
+*     start
+*        Pointer to a double in which to return the "start" value
+*        supplied when the LutMap was created.
+*     inc
+*        Pointer to a double in which to return the "inc" value
+*        supplied when the LutMap was created.
+*     nlut
+*        Pointer to a double in which to return the number of values in
+*        the LUT.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array holding a copy of the
+*     look-up table. This is an array of "nlut" elements, giving the
+*     output values for input values "start", "start+inc", "start+2*inc",
+*     etc. The pointer should be freed using astFree when no longer
+*     needed.
+
+*  Notes:
+*     - A value of NULL will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Store the scalar values. */
+   *start = this->start;
+   *inc = this->inc;
+   *nlut = this->nlut;
+
+/* Return a copy of the look up table. */
+   return astStore( NULL, this->lut, sizeof( double )*this->nlut );
+}
+
+static int GetMonotonic( int nlut, const double *lut, int *nluti, double **luti,
+                         int **flagsi, int **indexi, int *status ) {
+/*
+*  Name:
+*     GetMonotonic
+
+*  Purpose:
+*     Determine if a array is monotonic increasing or decreasing.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     int GetMonotonic( int nlut, const double *lut, int *nluti, double **luti,
+*                       int **flagsi, int **indexi, int *status )
+
+*  Class Membership:
+*     LutMap member function.
+
+*  Description:
+*     This function returns a flag indiciating the supplied array is
+*     monotonic increasing, monotonic decreasing, or non-monotonic.
+*     Sections of equal or bad values do not invalidate an otherwise
+*     monotonic array.
+*
+*     It also returns information needed to implement the inverse
+*     transformation of a LutMap.
+
+*  Parameters:
+*     nlut
+*        The length of the array.
+*     lut
+*        The array to check.
+*     nluti
+*        Address of an int in which to store the length of the returned
+*        "luti" and "flags" arrays.
+*     luti
+*        Address at which to return a pointer to a newly allocated array
+*        containing "*nluti" elements. This is a copy of the supplied
+*        "lut" array but with any bad or NaN values omitted. Subsequent
+*        elements are shuffled down to fill the holes left by removing
+*        these bad values. A NULL pointer is returned if there are no bad
+*        values in "lut".
+*     flagsi
+*        Address at which to return a pointer to a newly allocated array
+*        containing "*nluti" elements. Each element is non-zero if the
+*        corresponding value stored in "luti" was adjacent to a bad value
+*        in the supplied "lut" array. A NULL pointer is returned if there
+*        are no bad values in "lut".
+*     indexi
+*        Address at which to return a pointer to a newly allocated array
+*        containing "*nluti" elements. Each element is the index within
+*        "lut" of the corresponding value stored in "luti". A NULL pointer
+*        is returned if there are no bad values in "lut".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*    - A value of zero will be returned if this function is invoked
+*    with the global error status set, or if it should fail for any
+*    reason.
+*/
+
+/* Local Variables: */
+   const double *p3;
+   double *p1;
+   double lval;
+   int *p2;
+   int *p4;
+   int ilut;
+   int nbad;
+   int result;
+
+/* Initialise. */
+   result = 0;
+   *nluti = 0;
+   *luti = NULL;
+   *flagsi = NULL;
+   *indexi = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* As yet we have not found a good value. */
+   lval = AST__BAD;
+
+/* As yet we have not found a bad value. */
+   nbad = 0;
+
+/* Loop  round the supplied array, ignoring bad (AST__BAD or NaN) values. */
+   for ( ilut = 0; ilut < nlut; ilut++ ) {
+      if( !astISBAD( lut[ ilut ] ) ) {
+
+/* If this is the first good value, record it. */
+         if( lval == AST__BAD ) {
+            lval = lut[ ilut ];
+
+/* If this is not the first good value, ignore it if it is equal to the
+   previous god value. */
+         } else if( lut[ ilut ] != lval ) {
+
+/* Set the returned flag on the basis of the first pair of good values. */
+            if( result == 0 ) {
+               result = ( lut[ ilut ] > lval ) ? 1 : -1;
+
+/* For subsequent pairs of good values, check the pair increases or
+   decreases in the same way as the first pair. Reset the returned value
+   to zero and break if not. */
+            } else if( result == 1 && lut[ ilut ] < lval ) {
+               result = 0;
+               break;
+
+            } else if( result == -1 && lut[ ilut ] >lval ) {
+               result = 0;
+               break;
+            }
+
+/* Record the current good value. */
+            lval = lut[ ilut ];
+         }
+      } else {
+         nbad++;
+      }
+   }
+
+/* If any bad values were found, we now allocate the required returned
+   arrays. */
+   if( nbad ) {
+      *nluti = nlut - nbad;
+      *luti = astMalloc( sizeof( double )*( *nluti ) );
+      *flagsi = astMalloc( sizeof( double )*( *nluti ) );
+      *indexi = astMalloc( sizeof( double )*( *nluti ) );
+
+      if( astOK ) {
+
+/* Into "luti" copy all good values from "lut", shuffling down values to
+   fill holes left by bad values. Into "flagsi", store a flag indicating
+   if the corresponding "luti" value was adjacent to a bad value in the
+   full "lut" array. */
+         p1 = *luti;
+         p2 = *flagsi;
+         p3 = lut;
+         p4 = *indexi;
+
+/* Do the first input point (it has no lower neighbour). */
+         if( !astISBAD( *p3 ) ) {
+            *(p1++) = *p3;
+            *(p2++) = astISBAD( p3[ +1 ] );
+            *(p4++) = 0;
+         }
+
+/* Do all remaining input points except for the last one. */
+         for ( ilut = 1,p3++; ilut < nlut-1; ilut++,p3++ ) {
+            if( !astISBAD( *p3 ) ) {
+               *(p1++) = *p3;
+               *(p2++) = astISBAD( p3[ -1 ] ) || astISBAD( p3[ +1 ] );
+               *(p4++) = ilut;
+            }
+         }
+
+/* Do the last input point (it has no upper neighbour). */
+         if( !astISBAD( *p3 ) ) {
+            *p1 = *p3;
+            *p2 = astISBAD( p3[ -1 ] );
+            *p4 = ilut;
+         }
+      }
+   }
+
+
+/* Return the result. */
+   return result;
+}
+
+void astInitLutMapVtab_(  AstLutMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitLutMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a LutMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     void astInitLutMapVtab( AstLutMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     LutMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the LutMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsALutMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->ClearLutInterp = ClearLutInterp;
+   vtab->GetLutInterp = GetLutInterp;
+   vtab->SetLutInterp = SetLutInterp;
+   vtab->TestLutInterp = TestLutInterp;
+   vtab->ClearLutEpsilon = ClearLutEpsilon;
+   vtab->GetLutEpsilon = GetLutEpsilon;
+   vtab->SetLutEpsilon = SetLutEpsilon;
+   vtab->TestLutEpsilon = TestLutEpsilon;
+   vtab->GetLutMapInfo = GetLutMapInfo;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the class dump, copy and delete functions.*/
+   astSetDump( vtab, Dump, "LutMap",
+               "Map 1-d coordinates using a lookup table" );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a LutMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     LutMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated LutMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated LutMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated LutMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the LutMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        LutMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated LutMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstLutMap *map;               /* Pointer to LutMap */
+   AstLutMap *neb;               /* Pointer to neighbouring LutMap */
+   AstMapping *new;              /* Pointer to replacement Mapping */
+   double a1;                    /* First input coordinate value */
+   double a2;                    /* Second input coordinate value */
+   double b1;                    /* First output coordinate value */
+   double b2;                    /* Second output coordinate value */
+   int equal;                    /* Are LutMaps equal? */
+   int i;                        /* Mapping index */
+   int ilo;                      /* Index of lower LutMap */
+   int invneb;                   /* Should the neigbour be used inverted? */
+   int old_inv;                  /* Original Invert value for neigbour */
+   int result;                   /* Result value to return */
+   int simpler;                  /* Mapping simplified? */
+
+/* Initialise the returned result. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the nominated LutMap. */
+   map = (AstLutMap *) ( *map_list )[ where ];
+
+/* See if the LutMap is linear. If so, it can probably be
+   simplified. */
+   simpler = GetLinear( (AstMapping *) map, status );
+   if ( simpler ) {
+
+/* Obtain the range of input values corresponding to the first and
+   last lookup table elements. */
+      a1 = map->start;
+      a2 = map->start + map->inc * ( map->nlut - 1 );
+
+/* Obtain the corresponding range of output values and check these
+   values are not the same. */
+      b1 = map->lut[ 0 ];
+      b2 = map->lut[ map->nlut - 1 ];
+      if ( b1 != b2 ) {
+
+/* Create a new WinMap that implements an equivalent linear Mapping,
+   allowing for the invert flag associated with the LutMap. */
+         if ( !( *invert_list )[ where ] ) {
+            new = (AstMapping *) astWinMap( 1, &a1, &a2, &b1, &b2, "", status );
+         } else {
+            new = (AstMapping *) astWinMap( 1, &b1, &b2, &a1, &a2, "", status );
+         }
+
+/* If OK, annul the original LutMap pointer and substitute the new
+   one. Also clear the associated invert flag. */
+         if ( astOK ) {
+            (void) astAnnul( ( *map_list )[ where ] );
+            ( *map_list )[ where ] = new;
+            ( *invert_list )[ where ] = 0;
+
+/* Assign the result value. */
+            result = where;
+         }
+      }
+
+/* Otherwise, see if the LutMap is in series with its own inverse. If so
+   the pair of LutMaps can be replaced by a UnitMap. */
+   } else if( series ) {
+
+/* Is the higher neighbour a LutMap? If so get a pointer to it, and
+   note the index of the lower of the two adjacent LutMaps. */
+      if( where < ( *nmap - 1 ) &&
+          astIsALutMap( ( *map_list )[ where + 1 ] ) ){
+         neb = (AstLutMap *) ( *map_list )[ where + 1 ];
+         invneb = ( *invert_list )[ where + 1 ];
+         ilo = where;
+
+/* If not, is the lower neighbour a LutMap? If so get a pointer to it,
+   and note the index of the lower of the two adjacent LutMaps. */
+      } else if( where > 0 &&
+                 astIsALutMap( ( *map_list )[ where - 1 ] ) ){
+         neb = (AstLutMap *) ( *map_list )[ where - 1 ];
+         invneb = ( *invert_list )[ where - 1 ];
+         ilo =  where - 1;
+
+      } else {
+         neb = NULL;
+      }
+
+/* If a neighbouring LutMap was found, we can replace the pair by a
+   UnitMap if the two LutMaps are equal but have opposite values for
+   their Invert flags. Temporarily invert the neighbour, then compare
+   the two LutMaps for equality, then re-invert the neighbour. */
+      if( neb ) {
+         old_inv = astGetInvert( neb );
+         astSetInvert( neb, invneb );
+         astInvert( neb );
+         equal = astEqual( map, neb );
+         astSetInvert( neb, old_inv );
+
+/* If the two LutMaps are equal but opposite, annul the first of the two
+   Mappings, and replace it with a UnitMap. Also set the invert flag. */
+         if( equal ) {
+            new = (AstMapping *) astUnitMap( 1, "", status );
+            (void) astAnnul( ( *map_list )[ ilo ] );
+            ( *map_list )[ ilo ] = new;
+            ( *invert_list )[ ilo ] = 0;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+            (void) astAnnul( ( *map_list )[ ilo + 1 ] );
+            for ( i = ilo + 2; i < *nmap; i++ ) {
+               ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+               ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+            }
+
+/* Clear the vacated element at the end. */
+            ( *map_list )[ *nmap - 1 ] = NULL;
+            ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+            ( *nmap )--;
+            result = where;
+         }
+      }
+   }
+
+/* If an error occurred, clear the returned result. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a LutMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     LutMap member function (over-rides the astSetAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a LutMap, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the LutMap.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstLutMap *this;              /* Pointer to the LutMap structure */
+   double luteps;                /* LutEpsilon attribute value */
+   int lutinterp;                /* LutInterp attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the LutMap structure. */
+   this = (AstLutMap *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* LutInterp. */
+/* ---------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "lutinterp= %d %n", &lutinterp, &nc ) )
+        && ( nc >= len ) ) {
+      astSetLutInterp( this, lutinterp );
+
+/* LutEpsilon. */
+/* ------------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "lutepsilon= %lf %n", &luteps, &nc ) )
+        && ( nc >= len ) ) {
+      astSetLutEpsilon( this, luteps );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a LutMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     LutMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a LutMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the LutMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstLutMap *this;             /* Pointer to the LutMap structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the LutMap structure. */
+   this = (AstLutMap *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* LutInterp. */
+/* ---------- */
+   if ( !strcmp( attrib, "lutinterp" ) ) {
+      result = astTestLutInterp( this );
+
+/* LutEpsilon. */
+/* ------------- */
+   } else if ( !strcmp( attrib, "lutepsilon" ) ) {
+      result = astTestLutEpsilon( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a LutMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     LutMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a LutMap and a set of points encapsulated
+*     in a PointSet and transforms the points so as to apply the
+*     lookup table transformation.
+
+*  Parameters:
+*     this
+*        Pointer to the LutMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate
+*        transformation should be applied, while a zero value requests
+*        the inverse transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed
+*        (output) coordinate values. A NULL value may also be given,
+*        in which case a new PointSet will be created by this
+*        function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*     - The number of coordinate values per point in the input
+*     PointSet must equal 1.
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points (with 1 coordinate value per point)
+*     to accommodate the result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstLutMap *map;               /* Pointer to LutMap to be applied */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *lut;                  /* Pointer to LUT */
+   double d1;                    /* Offset to I1 value */
+   double d2;                    /* Offset to I2 value */
+   double fract;                 /* Fractional interpolation distance */
+   double scale;                 /* Normalising scale factor */
+   double value_in;              /* Input coordinate value */
+   double value_out;             /* Output coordinate value */
+   double x;                     /* Value normalised to LUT increment */
+   double xi;                    /* Integer value of "x" */
+   int *flags;                   /* Flags indicating an adjacent bad value */
+   int *index;                   /* Translates reduced to original indices */
+   int i1;                       /* Lower adjacent LUT index */
+   int i2;                       /* Upper adjacent LUT index */
+   int i;                        /* New LUT index */
+   int istart;                   /* Original LUT index at start of interval */
+   int ix;                       /* "x" converted to an int */
+   int near;                     /* Perform nearest neighbour interpolation? */
+   int nlut;                     /* Number of LUT entries */
+   int nlutm1;                   /* Number of LUT entries minus one */
+   int npoint;                   /* Number of points */
+   int ok;                       /* Lookup table is not flat */
+   int point;                    /* Loop counter for points */
+   int up;                       /* LUT values are increasing? */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the LutMap. */
+   map = (AstLutMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform
+   member function inherited from the parent Mapping class. This
+   function validates all arguments and generates an output PointSet
+   if necessary, but does not actually transform any coordinate
+   values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points from the input PointSet and obtain
+   pointers for accessing the input and output coordinate values. */
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse mapping,
+   according to the direction specified and whether the mapping has
+   been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* Forward transformation. */
+/* ----------------------- */
+   if( astOK ){
+      if ( forward ) {
+
+/* Obtain lookup table details. */
+         lut = map->lut;
+         nlut = map->nlut;
+         near = ( astGetLutInterp( map ) == NEAR );
+         nlutm1 = nlut - 1;
+
+/* Calculate the scale factor required. */
+         scale = 1.0 / map->inc;
+
+/* Loop to transform each input point. */
+         for ( point = 0; point < npoint; point++ ) {
+
+/* Extract the input coordinate value. */
+            value_in = ptr_in[ 0 ][ point ];
+
+/* First check if this is the same value as we transformed last. If
+   so, re-use the last result. */
+            if ( value_in == map->last_fwd_in ) {
+               value_out = map->last_fwd_out;
+
+/* Check for bad input coordinates and generate a bad result if
+   necessary. */
+            } else if ( value_in == AST__BAD ) {
+               value_out = AST__BAD;
+
+/* For nearest-neighbour interpolation, return the value of the lookup table
+   entry corresponding to the input coordinate. */
+            } else if( near ){
+               x = ( value_in - map->start ) * scale;
+               xi = floor( x + 0.5 );
+               ix = (int) xi;
+               if ( ix < 0 || ix >= nlut ) {
+                  value_out = AST__BAD;
+               } else {
+                  value_out = lut[ ix ];
+               }
+
+/* Otherwise, (for linear interpolation) identify the lookup table entry
+   corresponding to the input coordinate. */
+            } else {
+               x = ( value_in - map->start ) * scale;
+               xi = floor( x );
+               ix = (int) xi;
+
+/* If the input value lies below the first lookup table entry,
+   extrapolate using the first two table values. */
+               if ( ix < 0 ) {
+                  if( lut[ 0 ] != AST__BAD && lut[ 1 ] != AST__BAD ) {
+                     value_out = lut[ 0 ] + x * ( lut[ 1 ] - lut[ 0 ] );
+                  } else {
+                     value_out = AST__BAD;
+                  }
+
+/* If the input value lies above the last lookup table entry (or equals
+   it), extrapolate using the last two table values. */
+               } else if ( ix >= nlutm1 ) {
+                  if( lut[ nlutm1 ] != AST__BAD &&
+                      lut[ nlut - 2 ] != AST__BAD ) {
+                     value_out = lut[ nlutm1 ] +
+                                 ( x - (double) ( nlutm1 ) ) *
+                                 ( lut[ nlutm1 ] - lut[ nlut - 2 ] );
+                  } else {
+                     value_out = AST__BAD;
+                  }
+
+/* Otherwise, interpolate between the adjacent entries. */
+               } else {
+                  if( lut[ ix ] != AST__BAD &&
+                      lut[ ix + 1 ] != AST__BAD ) {
+                     fract = x - xi;
+                     value_out = lut[ ix ] * ( 1.0 - fract ) +
+                                 lut[ ix + 1 ] * fract;
+                  } else {
+                     value_out = AST__BAD;
+                  }
+               }
+            }
+
+/* Assign the output coordinate value. */
+            ptr_out[ 0 ][ point ] = value_out;
+
+/* Retain the input and output coordinate values for possible re-use
+   in future. */
+            map->last_fwd_in = value_in;
+            map->last_fwd_out = value_out;
+         }
+
+/* Inverse transformation. */
+/* ----------------------- */
+      } else {
+
+/* Obtain details of the lookup table to be used by the inverse
+   transformation. This is the same as the forward transformation lookup
+   table, except that any bad values are omitted. Also, get a pointer to a
+   array of flags that indicate if the corresponding lookup table entries
+   were adjacent to a bad value or not in the full lookup table. */
+         if( map->luti ) {
+            lut = map->luti;
+            flags = map->flagsi;
+            nlut = map->nluti;
+            index = map->indexi;
+         } else {
+            lut = map->lut;
+            flags = NULL;
+            nlut = map->nlut;
+            index = NULL;
+         }
+         near = ( astGetLutInterp( map ) == NEAR );
+         nlutm1 = nlut - 1;
+
+/* Loop to transform each input point. */
+         for ( point = 0; point < npoint; point++ ) {
+
+/* Extract the input coordinate value. */
+            value_in = ptr_in[ 0 ][ point ];
+
+/* First check if this is the same value as we transformed last. If
+   so, re-use the last result. */
+            if ( value_in == map->last_inv_in ) {
+               value_out = map->last_inv_out;
+
+/* Check for bad input coordinates and generate a bad result if
+   necessary. */
+            } else if ( value_in == AST__BAD ) {
+               value_out = AST__BAD;
+
+/* Otherwise, we can determine an inverse. Note the inverse transformation
+   will not be defined, so will not be attempted, unless all the table
+   entries are monotonically increasing or decreasing, possibly with sections
+   of equal or bad values. */
+            } else {
+               up = ( lut[ nlutm1 ] > lut[ 0 ] );
+
+/* Perform a binary search to identify two adjacent lookup table
+   elements whose values bracket the input coordinate value. */
+               i1 = -1;
+               i2 = nlutm1;
+               while ( i2 > ( i1 + 1 ) ) {
+                  i = ( i1 + i2 ) / 2;
+                  *( ( ( value_in >= lut[ i ] ) == up ) ? &i1 : &i2 ) = i;
+               }
+
+/* If the lower table value is equal to the required value, and either of
+   its neighbours is also equal to the required value, then we have been
+   asked to find the inverse in a flat region of the table, so return
+   a bad value. Likewise, if the upper table value is equal to the required
+   value, and either of its neighbours is also equal to the required value,
+   then we have been asked to find the inverse in a flat region of the table,
+   so return a bad value. */
+               ok = 1;
+               if( lut[ i1 ] == value_in ) {
+                  if( i1 > 0 && lut[ i1 - 1 ] == value_in ) ok = 0;
+                  if( lut[ i2 ] == value_in ) ok = 0;
+               } else if( lut[ i2 ] == value_in ) {
+                  if( i2 < nlutm1 && lut[ i2 + 1 ] == value_in ) ok = 0;
+                  if( lut[ i1 ] == value_in ) ok = 0;
+               }
+
+               if( !ok ) {
+                  value_out = AST__BAD;
+
+/* If both of the two table elements were adjacent to a bad value in the
+   full lookup table, return a bad output value. */
+               } else if( flags && ( flags[ i1 ] && flags[ i2 ] ) ) {
+                  value_out = AST__BAD;
+
+/* Nearest neighbour interpolation: return the closest of i1 or i2. Return
+   AST__BAD if the supplied value is less than either or greater than
+   either.  */
+               } else if( near ) {
+                  d1 = lut[ i1 ] - value_in;
+                  d2 = lut[ i2 ] - value_in;
+                  if( ( d1 > 0.0 && d2 > 0.0 ) ||
+                      ( d1 < 0.0 && d2 < 0.0 ) ) {
+                     value_out = AST__BAD;
+
+                  } else {
+
+                     if( fabs( d1 ) < fabs( d2 ) ){
+                        istart = index ? index[ i1 ] : i1;
+                     } else {
+                        istart = index ? index[ i2 ] : i2;
+                     }
+                     value_out = map->start + map->inc * istart;
+
+                  }
+
+/* Linear interpolation... */
+               } else {
+
+/* We are interested in the lower bracketing table element. If
+   necessary, restrict this element's index to lie within the
+   table. This causes extrapolation to occur (instead of
+   interpolation) if the input value actually lies outside the range
+   of the lookup table. */
+                  if ( i1 < 0 ) i1 = 0;
+                  if ( i1 > ( nlut - 2 ) ) i1 = nlut - 2;
+
+/* Interpolate (or extrapolate) to derive the output coordinate
+   value. */
+                  istart = index ? index[ i1 ] : i1;
+                  value_out = map->start + map->inc * ( (double) istart +
+                                             ( ( value_in - lut[ i1 ] ) /
+                                             ( lut[ i1 + 1 ] - lut[ i1 ] ) ) );
+               }
+            }
+
+/* Assign the output coordinate value. */
+            ptr_out[ 0 ][ point ] = value_out;
+
+/* Retain the input and output coordinate values for possible re-use
+   in future. */
+            map->last_inv_in = value_in;
+            map->last_inv_out = value_out;
+         }
+      }
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     LutInterp
+
+*  Purpose:
+*     Look-up table interpolation method.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute indicates the method to be used when finding the
+*     output value of a LutMap for an input value part way between two
+*     table entries. If it is set to 0 (the default) then linear
+*     interpolation is used. Otherwise, nearest neighbour interpolation
+*     is used.
+*
+*     Using nearest neighbour interpolation causes AST__BAD to be returned
+*     for any point which falls outside the bounds of the table. Linear
+*     interpolation results in an extrapolated value being returned based
+*     on the two end entries in the table.
+*
+*     Note, the value of this attribute may changed only if the LutMap
+*     has no more than one reference. That is, an error is reported if the
+*     LutMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     LutMap
+*        All LutMaps have this attribute.
+
+*att--
+*/
+astMAKE_CLEAR1(LutMap,LutInterp,lutinterp,-INT_MAX)
+astMAKE_GET(LutMap,LutInterp,int,LINEAR,( ( this->lutinterp == -INT_MAX ) ?
+                                          LINEAR : this->lutinterp ))
+astMAKE_SET1(LutMap,LutInterp,int,lutinterp,(( value == LINEAR ) ? LINEAR : NEAR ))
+astMAKE_TEST(LutMap,LutInterp,( this->lutinterp != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     LutEpsilon
+
+*  Purpose:
+*     The relative error of the values held in the took-up table.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute holds the relative error of the values held in the
+*     took-up table. It is used when simplifying a LutMap, to determine
+*     if the LutMap should be considered linear. Setting a larger value
+*     makes it more likely that a LutMap will be replaced by a WinMap
+*     (i.e. a linear Mapping) when simplified.
+*
+*     The default value is the value of the system constant DBL_EPSILON
+*     (typically around 1e-16 or 2E-16). If the values in the look-up
+*     table were derived from single precision data, it may be appropriate
+*     to set this attribute to a value around 1E-7.
+*
+*     Note, the value of this attribute may changed only if the LutMap
+*     has no more than one reference. That is, an error is reported if the
+*     LutMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     LutMap
+*        All LutMaps have this attribute.
+
+*att--
+*/
+astMAKE_CLEAR1(LutMap,LutEpsilon,lutepsilon,AST__BAD)
+astMAKE_GET(LutMap,LutEpsilon,double,DBL_EPSILON,( ( this->lutepsilon == AST__BAD ) ?
+                                          DBL_EPSILON : this->lutepsilon ))
+astMAKE_SET1(LutMap,LutEpsilon,double,lutepsilon,(value))
+astMAKE_TEST(LutMap,LutEpsilon,( this->lutepsilon != AST__BAD ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for LutMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for LutMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the LutMap to be copied.
+*     objout
+*        Pointer to the LutMap being constructed.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstLutMap *out;               /* Pointer to output LutMap */
+   AstLutMap *in;                /* Pointer to input LutMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the input and output LutMaps. */
+   in= (AstLutMap *) objin;
+   out = (AstLutMap *) objout;
+
+/* Nullify all output pointers. */
+   out->lut = NULL;
+   out->luti = NULL;
+   out->flagsi = NULL;
+   out->indexi = NULL;
+
+/* Allocate memory and store a copy of the lookup table data. */
+   out->lut = astStore( NULL, in->lut,
+                        sizeof( double ) * (size_t) in->nlut );
+
+/* Do the arrays used for the inverse transformation, if they exist. */
+   if( in->luti ) out->luti = astStore( NULL, in->luti,
+                                        sizeof( double ) * (size_t) in->nluti );
+   if( in->flagsi ) out->flagsi = astStore( NULL, in->flagsi,
+                                        sizeof( double ) * (size_t) in->nluti );
+   if( in->indexi ) out->indexi = astStore( NULL, in->indexi,
+                                        sizeof( double ) * (size_t) in->nluti );
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for LutMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for LutMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the LutMap to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstLutMap *this;              /* Pointer to LutMap */
+
+/* Obtain a pointer to the LutMap structure. */
+   this = (AstLutMap *) obj;
+
+/* Free the memory holding the lookup tables, etc. */
+   this->lut = astFree( this->lut );
+   this->luti = astFree( this->luti );
+   this->flagsi = astFree( this->flagsi );
+   this->indexi = astFree( this->indexi );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for LutMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the LutMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the LutMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstLutMap *this;              /* Pointer to the LutMap structure */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   double dval;                  /* Double value */
+   int ilut;                     /* Loop counter for table elements */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the LutMap structure. */
+   this = (AstLutMap *) this_object;
+
+/* Write out values representing the instance variables for the LutMap
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written. */
+
+/* Number of lookup table elements. */
+   astWriteInt( channel, "Nlut", 1, 1, this->nlut,
+                "Number of lookup table elements" );
+
+/* Input coordinate at first element centre. */
+   astWriteDouble( channel, "Start", ( this->start != 0.0 ), 1, this->start,
+                   "Input value at first element" );
+
+/* Element spacing. */
+   astWriteDouble( channel, "Incr", ( this->inc != 1.0 ), 1, this->inc,
+                   "Input value increment between elements" );
+
+/* Interpolation method */
+   set = TestLutInterp( this, status );
+   ival = set ? GetLutInterp( this, status ) : astGetLutInterp( this );
+   astWriteInt( channel, "LutInt", set, 1, ival, "Interpolation method" );
+
+/* Precision */
+   if( TestLutEpsilon( this, status ) ) {
+      dval = GetLutEpsilon( this, status );
+      astWriteDouble( channel, "LutEps", 1, 1, dval, "Table relative error" );
+   }
+
+/* Lookup table contents. */
+   for ( ilut = 0; ilut < this->nlut; ilut++ ) {
+      if( this->lut[ ilut ] != AST__BAD ) {
+         (void) sprintf( buff, "L%d", ilut + 1 );
+         astWriteDouble( channel, buff, 1, 1, this->lut[ ilut ],
+                         ilut ? "" : "Lookup table elements..." );
+      }
+   }
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsALutMap and astCheckLutMap functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(LutMap,Mapping)
+astMAKE_CHECK(LutMap)
+
+AstLutMap *astLutMap_( int nlut, const double lut[],
+                       double start, double inc,
+                       const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astLutMap
+f     AST_LUTMAP
+
+*  Purpose:
+*     Create a LutMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "lutmap.h"
+c     AstLutMap *astLutMap( int nlut, const double lut[],
+c                           double start, double inc,
+c                           const char *options, ... )
+f     RESULT = AST_LUTMAP( NLUT, LUT, START, INC, OPTIONS, STATUS )
+
+*  Class Membership:
+*     LutMap constructor.
+
+*  Description:
+*     This function creates a new LutMap and optionally initialises
+*     its attributes.
+*
+*     A LutMap is a specialised form of Mapping which transforms
+*     1-dimensional coordinates by using linear interpolation in a
+*     lookup table.  Each input coordinate value is first scaled to
+*     give the index of an entry in the table by subtracting a
+*     starting value (the input coordinate corresponding to the first
+*     table entry) and dividing by an increment (the difference in
+*     input coordinate value between adjacent table entries).
+*
+*     The resulting index will usually contain a fractional part, so
+*     the output coordinate value is then generated by interpolating
+*     linearly between the appropriate entries in the table. If the
+*     index lies outside the range of the table, linear extrapolation
+*     is used based on the two nearest entries (i.e. the two entries
+*     at the start or end of the table, as appropriate).
+*
+*     If the lookup table entries increase or decrease monotonically,
+*     then the inverse transformation may also be performed.
+
+*  Parameters:
+c     nlut
+f     NLUT = INTEGER (Given)
+*        The number of entries in the lookup table. This value must be
+*        at least 2.
+c     lut
+f     LUT( NLUT ) = DOUBLE PRECISION (Given)
+c        An array containing the "nlut"
+f        An array containing the
+*        lookup table entries.
+c     start
+f     START = DOUBLE PRECISION (Given)
+*        The input coordinate value which corresponds to the first lookup
+*        table entry.
+c     inc
+f     INC = DOUBLE PRECISION (Given)
+*        The lookup table spacing (the increment in input coordinate
+*        value between successive lookup table entries). This value
+*        may be positive or negative, but must not be zero.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new LutMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new LutMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astLutMap()
+f     AST_LUTMAP = INTEGER
+*        A pointer to the new LutMap.
+
+*  Notes:
+*     - If the entries in the lookup table either increase or decrease
+*     monotonically, then the new LutMap's TranInverse attribute will
+*     have a value of one, indicating that the inverse transformation
+*     can be performed. Otherwise, it will have a value of zero, so
+*     that any attempt to use the inverse transformation will result
+*     in an error.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstLutMap *new;               /* Pointer to new LutMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the LutMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitLutMap( NULL, sizeof( AstLutMap ), !class_init, &class_vtab,
+                        "LutMap", nlut, lut, start, inc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   LutMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new LutMap. */
+   return new;
+}
+
+AstLutMap *astLutMapId_( int nlut, const double lut[],
+                         double start, double inc,
+                         const char *options, ... ) {
+/*
+*  Name:
+*     astLutMapId_
+
+*  Purpose:
+*     Create a LutMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     AstLutMap *astLutMapId( int nlut, const double lut[],
+*                             double start, double inc,
+*                             const char *options, ... )
+
+*  Class Membership:
+*     LutMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astLutMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astLutMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astLutMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astLutMap_.
+
+*  Returned Value:
+*     The ID value associated with the new LutMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstLutMap *new;               /* Pointer to new LutMap */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the LutMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitLutMap( NULL, sizeof( AstLutMap ), !class_init, &class_vtab,
+                        "LutMap", nlut, lut, start, inc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new LutMap's
+   attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new LutMap. */
+   return astMakeId( new );
+}
+
+AstLutMap *astInitLutMap_( void *mem, size_t size, int init,
+                           AstLutMapVtab *vtab, const char *name,
+                           int nlut, const double lut[],
+                           double start, double inc, int *status ) {
+/*
+*+
+*  Name:
+*     astInitLutMap
+
+*  Purpose:
+*     Initialise a LutMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     AstLutMap *astInitLutMap( void *mem, size_t size, int init,
+*                               AstLutMapVtab *vtab, const char *name,
+*                               int nlut, const double lut[],
+*                               double start, double inc )
+
+*  Class Membership:
+*     LutMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new LutMap object. It allocates memory (if
+*     necessary) to accommodate the LutMap plus any additional data
+*     associated with the derived class.  It then initialises a LutMap
+*     structure at the start of this memory. If the "init" flag is
+*     set, it also initialises the contents of a virtual function
+*     table for a LutMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the LutMap is to be
+*        initialised.  This must be of sufficient size to accommodate
+*        the LutMap data (sizeof(LutMap)) plus any data used by the
+*        derived class. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the LutMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the LutMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*     init
+*        A logical flag indicating if the LutMap's virtual function
+*        table is to be initialised. If this value is non-zero, the
+*        virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new LutMap.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*     nlut
+*        The number of elements in the lookup table. This value must
+*        be at least 2.
+*     lut
+*        An array containing the "nlut" lookup table elements.
+*     start
+*        The input coordinate value which corresponds with the first
+*        lookup table element.
+*     inc
+*        The lookup table element spacing (i.e. the increment in input
+*        coordinate value between successive lookup table elements).
+
+*  Returned Value:
+*     A pointer to the new LutMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstLutMap *new;       /* Pointer to new LutMap */
+   double *luti;         /* Pointer to table for inverse transformation */
+   double *p;            /* Pointer to next lut element */
+   int *flagsi;          /* Pointer to flags for inverse transformation */
+   int *indexi;          /* Pointer to translation from original to reduced */
+   int dirn;             /* +1 => values increasing, -1 => values decreasing */
+   int ilut;             /* Loop counter for LUT elements */
+   int nluti;            /* Length of "luti" array */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitLutMapVtab( vtab, name );
+
+/* Check that the number of lookup table elements is valid. */
+   if ( nlut < 2 ) {
+      astError( AST__LUTIN, "astInitLutMap(%s): Invalid number of lookup "
+                "table elements (%d).", status, name, nlut );
+      astError( AST__LUTIN, "This value should be at least 2." , status);
+
+/* Also check that the input value increment is not zero. */
+   } else if ( inc == 0.0 ) {
+      astError( AST__LUTII, "astInitLutMap(%s): An input value increment of "
+                "zero between lookup table elements is not allowed.", status, name );
+
+/* Determine if the element values increase or decrease monotonically (except
+   that adjacent entries can be equal). We can only implement the inverse
+   transformation if this is so. The inverse transformation will generate
+   AST__BAD output values for sections of the table that contain equal
+   adjacent values, or hold AST__BAD values. */
+   } else {
+      dirn = GetMonotonic( nlut, lut, &nluti, &luti, &flagsi, &indexi,
+                           status );
+
+/* Initialise a Mapping structure (the parent class) as the first
+   component within the LutMap structure, allocating memory if
+   necessary. Specify that the Mapping should be defined in the
+   forward direction, and conditionally in the inverse direction. */
+      new = (AstLutMap *) astInitMapping( mem, size, 0,
+                                         (AstMappingVtab *) vtab, name,
+                                          1, 1, 1, ( dirn != 0 ) );
+
+      if ( astOK ) {
+
+/* Initialise the LutMap data. */
+/* ---------------------------- */
+         new->nlut = nlut;
+         new->start = start;
+         new->inc = inc;
+         new->lutinterp = LINEAR;
+         new->lutepsilon = AST__BAD;
+         new->nluti = nluti;
+         new->luti = luti;
+         new->flagsi = flagsi;
+         new->indexi = indexi;
+
+/* Allocate memory and store the lookup table. */
+         new->lut = astStore( NULL, lut, sizeof( double ) * (size_t) nlut );
+
+/* Replace an NaN values by AST__BAD */
+         p = new->lut;
+         for ( ilut = 0; ilut < nlut; ilut++, p++ ) {
+            if( !astISFINITE(*p) ) *p = AST__BAD;
+         }
+
+/* Initialise the retained input and output coordinate values. */
+         new->last_fwd_in = AST__BAD;
+         new->last_fwd_out = AST__BAD;
+         new->last_inv_in = AST__BAD;
+         new->last_inv_out = AST__BAD;
+      }
+
+/* If an error occurred, clean up by deleting the new LutMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new LutMap. */
+   return new;
+}
+
+AstLutMap *astLoadLutMap_( void *mem, size_t size,
+                           AstLutMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadLutMap
+
+*  Purpose:
+*     Load a LutMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "lutmap.h"
+*     AstLutMap *astLoadLutMap( void *mem, size_t size,
+*                               AstLutMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     LutMap loader.
+
+*  Description:
+*     This function is provided to load a new LutMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     LutMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a LutMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the LutMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        LutMap data (sizeof(LutMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the LutMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the LutMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstLutMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new LutMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the LutMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "LutMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new LutMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstLutMap *new;               /* Pointer to the new LutMap */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   int ilut;                     /* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Loop counter for table elements */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this LutMap. In this case the
+   LutMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstLutMap );
+      vtab = &class_vtab;
+      name = "LutMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitLutMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built LutMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "LutMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* Number of lookup table elements. */
+      new->nlut = astReadInt( channel, "nlut", 2 );
+
+/* Starting input coordinate value. */
+      new->start = astReadDouble( channel, "start", 0.0 );
+
+/* Input coordinate value increment. */
+      new->inc = astReadDouble( channel, "incr", 1.0 );
+
+/* Interpolation method */
+      new->lutinterp = astReadInt( channel, "lutint", LINEAR );
+      if ( TestLutInterp( new, status ) ) SetLutInterp( new, new->lutinterp, status );
+
+/* Precision */
+      new->lutepsilon = astReadDouble( channel, "luteps", AST__BAD );
+      if ( TestLutEpsilon( new, status ) ) SetLutEpsilon( new, new->lutepsilon, status );
+
+/* Allocate memory to hold the lookup table elements. */
+      new->lut = astMalloc( sizeof( double ) * (size_t) new->nlut );
+
+/* If OK, loop to read each element. */
+      if ( astOK ) {
+         for ( ilut = 0; ilut < new->nlut; ilut++ ) {
+            (void) sprintf( buff, "l%d", ilut + 1 );
+            new->lut[ ilut ] = astReadDouble( channel, buff, AST__BAD );
+         }
+
+/* Initialise the retained input and output coordinate values. */
+         new->last_fwd_in = AST__BAD;
+         new->last_fwd_out = AST__BAD;
+         new->last_inv_in = AST__BAD;
+         new->last_inv_out = AST__BAD;
+
+/* See if the array is monotonic increasing or decreasing. */
+         (void) GetMonotonic( new->nlut, new->lut, &(new->nluti),
+                              &(new->luti), &(new->flagsi), &(new->indexi),
+                              status );
+      }
+   }
+
+/* If an error occurred, clean up by deleting the new LutMap. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the new LutMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions
+   defined by this class. Each simply checks the global error status
+   and then locates and executes the appropriate member function,
+   using the function pointer stored in the object's virtual function
+   table (this pointer is located using the astMEMBER macro defined in
+   "object.h").
+
+   Note that the member function may not be the one defined here, as
+   it may have been over-ridden by a derived class. However, it should
+   still have the same interface. */
+
+double *astGetLutMapInfo_( AstLutMap *this, double *start, double *inc,
+                           int *nlut, int *status ){
+   if( !astOK ) return NULL;
+   return (**astMEMBER(this,LutMap,GetLutMapInfo))( this, start, inc, nlut,
+                                                    status );
+}
+
+
+
diff --git a/ast/lutmap.h b/ast/lutmap.h
new file mode 100644
index 0000000..68fa194
--- /dev/null
+++ b/ast/lutmap.h
@@ -0,0 +1,335 @@
+#if !defined( LUTMAP_INCLUDED )  /* Include this file only once */
+#define LUTMAP_INCLUDED
+/*
+*+
+*  Name:
+*     lutmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the LutMap class.
+
+*  Invocation:
+*     #include "lutmap.h"
+
+*  Description:
+*     This include file defines the interface to the LutMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The LutMap class implements Mappings which transform
+*     1-dimensional coordinates using linear interpolation in a lookup
+*     table.
+
+*  Inheritance:
+*     The LutMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astMapMerge
+*           Simplify a sequence of Mappings.
+*        astTransform
+*           Apply a LutMap to transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsALutMap
+*           Test class membership.
+*        astLutMap
+*           Create a LutMap.
+*
+*     Protected:
+*        astCheckLutMap
+*           Validate class membership.
+*        astInitLutMap
+*           Initialise a LutMap.
+*        astInitLutMapVtab
+*           Initialise the virtual function table for the LutMap class.
+*        astLoadLutMap
+*           Load a LutMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstLutMap
+*           LutMap object type.
+*
+*     Protected:
+*        AstLutMapVtab
+*           LutMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     8-JUL-1997 (RFWS):
+*        Original version.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitLutMapVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* LutMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstLutMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *lut;                 /* Pointer to lookup table */
+   double *luti;                /* Reduced lookup table for inverse trans. */
+   double inc;                  /* Input increment between table entries */
+   double last_fwd_in;          /* Last input value (forward transfm.) */
+   double last_fwd_out;         /* Last output value (forward transfm.) */
+   double last_inv_in;          /* Last input value (inverse transfm.) */
+   double last_inv_out;         /* Last output value (inverse transfm.) */
+   double start;                /* Input value for first table entry */
+   int *flagsi;                 /* Flags indicating adjacent bad values */
+   int *indexi;                 /* Translates reduced to original indices */
+   double lutepsilon;           /* Relative error of table values */
+   int lutinterp;               /* Interpolation method */
+   int nlut;                    /* Number of table entries */
+   int nluti;                   /* Reduced number of table entries */
+} AstLutMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstLutMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   int (*GetLutInterp)( AstLutMap *, int * );
+   int (* TestLutInterp)( AstLutMap *, int * );
+   void (* ClearLutInterp)( AstLutMap *, int * );
+   void (* SetLutInterp)( AstLutMap *, int, int * );
+   double (*GetLutEpsilon)( AstLutMap *, int * );
+   int (* TestLutEpsilon)( AstLutMap *, int * );
+   void (* ClearLutEpsilon)( AstLutMap *, int * );
+   void (* SetLutEpsilon)( AstLutMap *, double, int * );
+   double *(* GetLutMapInfo)( AstLutMap *, double *, double *, int *, int * );
+
+} AstLutMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstLutMapGlobals {
+   AstLutMapVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 101 ];
+} AstLutMapGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(LutMap)          /* Check class membership */
+astPROTO_ISA(LutMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstLutMap *astLutMap_( int, const double [], double, double, const char *, int *, ...);
+#else
+AstLutMap *astLutMapId_( int, const double [], double, double, const char *, ... )__attribute__((format(printf,5,6)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstLutMap *astInitLutMap_( void *, size_t, int, AstLutMapVtab *, const char *, int, const double *, double, double, int * );
+
+/* Vtab initialiser. */
+void astInitLutMapVtab_( AstLutMapVtab *, const char *, int * );
+
+/* Loader. */
+AstLutMap *astLoadLutMap_( void *, size_t, AstLutMapVtab *, const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitLutMapGlobals_( AstLutMapGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+   int astGetLutInterp_( AstLutMap *, int * );
+   int astTestLutInterp_( AstLutMap *, int * );
+   void astClearLutInterp_( AstLutMap *, int * );
+   void astSetLutInterp_( AstLutMap *, int, int * );
+
+   double astGetLutEpsilon_( AstLutMap *, int * );
+   int astTestLutEpsilon_( AstLutMap *, int * );
+   void astClearLutEpsilon_( AstLutMap *, int * );
+   void astSetLutEpsilon_( AstLutMap *, double, int * );
+
+   double *astGetLutMapInfo_( AstLutMap *, double *, double *, int *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckLutMap(this) astINVOKE_CHECK(LutMap,this,0)
+#define astVerifyLutMap(this) astINVOKE_CHECK(LutMap,this,1)
+
+/* Test class membership. */
+#define astIsALutMap(this) astINVOKE_ISA(LutMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astLutMap astINVOKE(F,astLutMap_)
+#else
+#define astLutMap astINVOKE(F,astLutMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define \
+astInitLutMap(mem,size,init,vtab,name,nlut,lut,start,inc) \
+astINVOKE(O,astInitLutMap_(mem,size,init,vtab,name,nlut,lut,start,inc,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitLutMapVtab(vtab,name) astINVOKE(V,astInitLutMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadLutMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadLutMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckLutMap to validate LutMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+#if defined(astCLASS)            /* Protected */
+
+#define astClearLutInterp(this) \
+        astINVOKE(V,astClearLutInterp_(astCheckLutMap(this),STATUS_PTR))
+#define astGetLutInterp(this) \
+        astINVOKE(V,astGetLutInterp_(astCheckLutMap(this),STATUS_PTR))
+#define astSetLutInterp(this,value) \
+        astINVOKE(V,astSetLutInterp_(astCheckLutMap(this),value,STATUS_PTR))
+#define astTestLutInterp(this) \
+        astINVOKE(V,astTestLutInterp_(astCheckLutMap(this),STATUS_PTR))
+#define astGetLutMapInfo(this,start,inc,nlut) \
+        astINVOKE(V,astGetLutMapInfo_(astCheckLutMap(this),start,inc,nlut,STATUS_PTR))
+#define astClearLutEpsilon(this) \
+        astINVOKE(V,astClearLutEpsilon_(astCheckLutMap(this),STATUS_PTR))
+#define astGetLutEpsilon(this) \
+        astINVOKE(V,astGetLutEpsilon_(astCheckLutMap(this),STATUS_PTR))
+#define astSetLutEpsilon(this,value) \
+        astINVOKE(V,astSetLutEpsilon_(astCheckLutMap(this),value,STATUS_PTR))
+#define astTestLutEpsilon(this) \
+        astINVOKE(V,astTestLutEpsilon_(astCheckLutMap(this),STATUS_PTR))
+
+#endif
+
+#endif
+
+
+
+
+
diff --git a/ast/makeh b/ast/makeh
new file mode 100644
index 0000000..c07d343
--- /dev/null
+++ b/ast/makeh
@@ -0,0 +1,313 @@
+#!/usr/bin/perl
+#+
+#  Name:
+#     makeh
+
+#  Purpose:
+#     Generate the contents of the "ast.h" header file.
+
+#  Type:
+#     Perl script.
+
+#  Invocation:
+#     makeh file_list
+
+#  Description:
+#     This script processes private header files used within the AST library
+#     in order to extract the public information they contain. This information
+#     is produced in a form suitable for use in the public "ast.h" header file,
+#     which defines the public interface to the library.
+
+#  Parameters:
+#     file_list
+#        A space-separated list of the private AST header files whose public
+#        information is to be extracted.
+
+#  Result:
+#     The contents of the "ast.h" header file are written to standard output.
+
+#  Notes:
+#     - This script is specific to the AST library and contains some knowledge
+#     of the input file contents.
+
+#  History:
+#     10-JAN-2005 (DSB):
+#        Added second argument (mode) to the mkdir invocation which creates
+#        the temporary directory.
+#     2-MAR-2006 (DSB):
+#        Check for "config.h" as well as <config.h>
+#     6-JAN-2010 (DSB):
+#        Add work-around for GCC 4.4.2 problem - the pre-processor seesm to simply
+#        throw away backslshes that escape newlines in the input header file. Reported
+#        by Bryan Irby at GSFC.
+#     27-JUL-2011 (DSB):
+#        Include the process ID in the name of the temporary directory and
+#        file, so that simultaneous invocations of this script do not trample
+#        all over each other.
+#-
+
+( $#ARGV >= 0 ) || Usage();
+
+#  Test whether we need to include config.h in the result.
+$need_config_h = 0;
+
+# Location of source files (makefile variable $(srcdir)).
+# This is most typically '.', but can be different.
+$srcdir = '.';
+
+while ( $ARGV[0] =~ /^-/ ) {
+   if ( $ARGV[0] eq '-s' ) {
+      shift @ARGV;
+      ( $#ARGV >= 0 ) || Usage();
+      $srcdir = $ARGV[0];
+      shift @ARGV;
+   } else {
+      Usage();
+   }
+}
+
+#  Create a scratch directory.
+$tmpdir="/tmp/makeh-$$.tmp";
+unless ( -d $tmpdir && -w $tmpdir ) {
+    if ( -e $tmpdir ) {
+        die "Temp $tmpdir exists, but is unwritable or is not a directory\n";
+    }
+    mkdir $tmpdir, 0777 ||
+        die "Failed to create temporary directory $tmpdir\n";
+}
+
+#  Open each input file.
+foreach $file ( @ARGV ) {
+   protect_copy_file( $file );
+}
+
+
+#  Open a pipe to a script (in the current directory) which runs the C
+# preprocessor and direct its output to a scratch file.
+open( CC, "| ./ast_cpp >/tmp/ast-$$.h" ) ||
+                                die "Can't open pipe to C preprocessor (cpp)";
+
+if ( $need_config_h ) {
+# Include this directory's config.h, unescaped, in the output.  We
+# need to pay attention to the values in this file, but don't want
+# them unexpanded in the final ast.h.
+    chomp($cwd = `pwd`);
+    print(CC "#include \"$cwd/config.h\"\n");
+}
+
+#  Before including each file, write an underlined heading in the form of
+#  C comments (with comment characters suitably protected so that they will
+#  be passed unchanged by cpp).
+foreach $file ( @ARGV ) {
+   $comment = $file;
+   $comment =~ s|^.*/||;
+   $comment =~ s|.h$||;
+   print( CC "/_* " . $comment . ". *_/\n" );
+   $comment =~ s/./=/g;
+   print( CC "/_* " . $comment . "= *_/\n" );
+
+#  Write #include "xxx.h" lines to cpp so that it includes (and
+#  preprocesses) each of the temporary files created above in turn.
+   $tmp_file = $file;
+   $tmp_file =~ s|^.*/||;
+   printf(CC "#include \"%s/%s\"\n", $tmpdir, $tmp_file);
+};
+
+#  Close the pipe to cpp.
+close( CC ) || die "C preprocessor (cpp) error";
+
+#  Remove the temporary directory and the files it contains.
+print( stderr `rm -r $tmpdir` );
+
+#  Write the output preamble.
+print(
+'#if !defined(AST_INCLUDED)
+#define AST_INCLUDED
+/*
+*+
+*  Name:
+*     ast.h
+
+*  Purpose:
+*     Define the public C interface to the AST library.
+
+*  Language:
+*     ANSI C
+
+*  Type of Module:
+*     C header file.
+
+*  Description:
+*     This file defines the public C interface to the AST library. It contains
+*     all the type definitions, function prototypes, macro definitions, etc.
+*     needed to use the library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (STARLINK)
+*     RFWS: R.F. Warren-Smith (STARLINK)
+*     {enter_new_authors_here}
+
+*  History:
+*     ' );
+
+#  Add the current date at this point.
+( $sec, $min, $hour, $mday, $mon, $year ) = localtime;
+print( $mday, '-',
+       ( 'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
+         'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC' )[ $mon ], '-',
+       ( $year > 95 ? 1900 : 2000 ) + $year );
+
+print( ' (makeh):
+*        Original version, generated automatically from the internal header
+*        files by the "makeh" script.
+*     {enter_changes_here}
+*-
+*/
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+' );
+
+#  Open the scratch file created above and read it.
+$space = 0;
+open( TEMP, "</tmp/ast-$$.h" );
+while( <TEMP> ) {
+
+#  Remove the underscores from the protected lines and macros.
+   s/^_#include(\s)/#include$1/;
+   s/^_#define(\s)/#define$1/;
+   s/___LINE__/__LINE__/g;
+   s/___FILE__/__FILE__/g;
+
+#  Also un-protect protected comments.
+   s|/_\*|/*|g;
+   s|\*_/|*/|g;
+
+#  Convert multiple blank lines (cpp creates lots of these) into single blank
+#  lines.
+   if ( /^\s*$/ ) {
+      $space = 1;
+   } else {
+
+#  Remove additional unwanted lines that cpp creates.
+      if ( ! /^# \d+/ ) {
+         if ( $space ) { print( "\n" ) };
+         $space = 0;
+
+#  Output the lines we want to keep.
+         print;
+      }
+   }
+}
+
+#  Write closing output lines.
+print(
+'
+#endif
+' );
+
+#  Close and remove the scratch file.
+close( TEMP );
+unlink "/tmp/ast-$$.h";
+
+
+sub protect_copy_file {
+   my $file = shift;
+
+   open( INCLUDE, "$srcdir/$file" )
+       || die "Can't open input file " . $srcdir/$file;
+
+#  Inicate we have no deferred text to write out.
+   $total = "";
+
+#  Open an output file with the same name in the temporary directory.
+   $tmp_file = $file;
+   $tmp_file =~ s|^.*/||;
+   open( TEMP, ">$tmpdir/$tmp_file" );
+
+#  Read the input file and detect "#include <...>" lines, extracting the name
+#  of the header file being included.
+line: while ( <INCLUDE> ) {
+#  Omit any config.h includes, and note that we saw this
+      if (/^\s*\#\s*include\s+<config.h>/ ||
+          /^\s*\#\s*include\s+"config.h"/) {
+          $need_config_h = 1;
+          next line;
+      }
+
+      if ( ( $header ) = /^\#include\s+<(.+)>/ ) {
+
+#  If this system header file has already been included, ignore it and go on to
+#  the next input line.
+         next line if $done{ $header }++;
+
+#  Otherwise, protect the #include with an underscore to prevent the file
+#  actually being included.
+         s/^/_/;
+      }
+
+#  Also protect "#define ..." lines, to prevent macro substitution being
+#  performed by the C preprocessor. Do not do this to lines of the form
+#  "#define XXX_INCLUDED" because these are still needed to determine which
+#  AST header files get included.
+      if ( /^\#define\s/ ) {
+         if ( ! /^\#define\s+\w*_INCLUDED/ ) { s/^/_/ };
+      }
+
+#  Similarly add underscores to protect standard C macros.
+      s/__LINE__/___LINE__/g;
+      s/__FILE__/___FILE__/g;
+
+#  Some pre-processors (e.g. GCC 4.4.2) seem to simply throw away trailing
+#  backslashes used to concatenate consecutive lines, producing two
+#  independent lines in the output rather than one. This completely fouls
+#  up the resulting header file. To avoid this, we concatenate such lines
+#  explicitly, before writing them out to the temporary output file.
+#  If the current line ends with an escaped newline, remove the escape
+#  character and newline, and concatenate it with any previously deferred
+#  lines.
+      if( /^(.*)\\\s*$/ ) {
+         $total .= $1;
+
+#  If the current line does not end with an escaped newline, concatenate it
+#  with any previously deferred lines, and write the total string out. Then
+#  reset the total string to indicate we have no deferred text.
+      } else {
+         $total .= $_;
+         print TEMP $total;
+         $total = "";
+      }
+   }
+
+#  Close each file when done.
+   close( INCLUDE );
+   close( TEMP );
+}
+
+sub Usage {
+   print STDERR "$0 [-s srcdir] file...\n";
+   exit (1);
+}
diff --git a/ast/mapping.c b/ast/mapping.c
new file mode 100644
index 0000000..ce322a9
--- /dev/null
+++ b/ast/mapping.c
@@ -0,0 +1,24616 @@
+/*
+*class++
+*  Name:
+*     Mapping
+
+*  Purpose:
+*     Inter-relate two coordinate systems.
+
+*  Constructor Function:
+*     None.
+
+*  Description:
+*     This class provides the basic facilities for transforming a set
+*     of coordinates (representing "input" points) to give a new set
+*     of coordinates (representing "output" points).  It is used to
+*     describe the relationship which exists between two different
+*     coordinate systems and to implement operations which make use of
+*     this (such as transforming coordinates and resampling grids of
+*     data).  However, the Mapping class does not have a constructor
+*     function of its own, as it is simply a container class for a
+*     family of specialised Mappings which implement particular types
+*     of coordinate transformation.
+
+*  Inheritance:
+*     The Mapping class inherits from the Object class.
+
+*  Attributes:
+*     In addition to those attributes common to all Objects, every
+*     Mapping also has the following attributes:
+*
+*     - Invert: Mapping inversion flag
+*     - IsLinear: Is the Mapping linear?
+*     - IsSimple: Has the Mapping been simplified?
+*     - Nin: Number of input coordinates for a Mapping
+*     - Nout: Number of output coordinates for a Mapping
+*     - Report: Report transformed coordinates?
+*     - TranForward: Forward transformation defined?
+*     - TranInverse: Inverse transformation defined?
+
+*  Functions:
+c     In addition to those functions applicable to all Objects, the
+c     following functions may also be applied to all Mappings:
+f     In addition to those routines applicable to all Objects, the
+f     following routines may also be applied to all Mappings:
+*
+c     - astDecompose: Decompose a Mapping into two component Mappings
+c     - astTranGrid: Transform a grid of positions
+c     - astInvert: Invert a Mapping
+c     - astLinearApprox: Calculate a linear approximation to a Mapping
+c     - astMapBox: Find a bounding box for a Mapping
+c     - astMapSplit: Split a Mapping up into parallel component Mappings
+c     - astQuadApprox: Calculate a quadratic approximation to a 2D Mapping
+c     - astRate: Calculate the rate of change of a Mapping output
+c     - astRebin<X>: Rebin a region of a data grid
+c     - astRebinSeq<X>: Rebin a region of a sequence of data grids
+c     - astResample<X>: Resample a region of a data grid
+c     - astRemoveRegions: Remove any Regions from a Mapping
+c     - astSimplify: Simplify a Mapping
+c     - astTran1: Transform 1-dimensional coordinates
+c     - astTran2: Transform 2-dimensional coordinates
+c     - astTranN: Transform N-dimensional coordinates
+c     - astTranP: Transform N-dimensional coordinates held in separate arrays
+f     - AST_DECOMPOSE: Decompose a Mapping into two component Mappings
+f     - AST_TRANGRID: Transform a grid of positions
+f     - AST_INVERT: Invert a Mapping
+f     - AST_LINEARAPPROX: Calculate a linear approximation to a Mapping
+f     - AST_QUADAPPROX: Calculate a quadratic approximation to a 2D Mapping
+f     - AST_MAPBOX: Find a bounding box for a Mapping
+f     - AST_MAPSPLIT: Split a Mapping up into parallel component Mappings
+f     - AST_RATE: Calculate the rate of change of a Mapping output
+f     - AST_REBIN<X>: Rebin a region of a data grid
+f     - AST_REBINSEQ<X>: Rebin a region of a sequence of data grids
+f     - AST_REMOVEREGIONS: Remove any Regions from a Mapping
+f     - AST_RESAMPLE<X>: Resample a region of a data grid
+f     - AST_SIMPLIFY: Simplify a Mapping
+f     - AST_TRAN1: Transform 1-dimensional coordinates
+f     - AST_TRAN2: Transform 2-dimensional coordinates
+f     - AST_TRANN: Transform N-dimensional coordinates
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     MBT: Mark Taylor (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     1-FEB-1996 (RFWS):
+*        Original version.
+*     29-FEB-1996 (RFWS):
+*        Minor improvements to error messages.
+*     15-JUL-1996 (RFWS):
+*        Support external interface.
+*     13-DEC-1996 (RFWS):
+*        Added the astMapMerge method.
+*     13-DEC-1996 (RFWS):
+*        Added the astSimplify method.
+*     27-MAY-1997 (RFWS):
+*        Improved the astSimplify method to use astMapMerge to
+*        simplify a single Mapping where possible.
+*     29-MAY-1998 (RFWS):
+*        Added the MapBox method.
+*     13-NOV-1998 (RFWS):
+*        Made default MapBox convergence accuracy larger (i.e. less
+*        accurate).
+*     10-DEC-1998 (RFWS):
+*        First useful implementation of astResample<X>.
+*     16-AUG-1999 (RFWS):
+*        Fixed bug in SpecialBounds - wrong number of coordinates being used
+*        when checking for bad output coordinate values.
+*     17-AUG-1999 (RFWS):
+*        Improved the convergence security of MapBox (return to older but
+*        less efficient setting).
+*     24-NOV-2000 (MBT):
+*        Fixed bug (function being invoked as wrong type) in AST__UINTERP
+*        scheme, and added new AST__BLOCKAVE scheme, in astResample<X>.
+*     9-JAN-2001 (DSB):
+*        Changed in and out arguments for TranN from type "double (*)[]"
+*        to "double *".
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitMappingVtab
+*        method.
+*     10-JUL-2003 (DSB):
+*        Added method astRate.
+*     2-SEP-2004 (DSB):
+*        Free resources before leaving astRate.
+*     31-AUG-2004 (DSB):
+*        Make the LinearApprox function protected rather than private,
+*        rename it to astLinearApprox, and make the bounds parameters of
+*        type double rather than int. Also, correct the size of the fit
+*        coefficient array (was "(nin+1)*nout", now is "(nout+1)*nin").
+*        Also correct the index of the first gradient coefficient from
+*        "fit+nout" to "fit+nin". These errors have probably never been
+*        noticed because they make no difference if nin=nout, which is
+*        usually the case.
+*     6-SEP-2004 (DSB):
+*        Make astRate more robust by adding checks for unusal conditions.
+*     20-SEP-2004 (DSB):
+*        Make the LinearApprox function public and change its interface
+*        to be more appropriate for public use. This involved swapping the
+*        direction of the fit (the original astLinearApprox fitted the
+*        inverse transformation, but the public version now fits the forwrd
+*        transformation).
+*     4-OCT-2004 (DSB):
+*        Modify astMapList to return flag indicating presence of inverted
+*        CmpMaps in supplied Mapping.
+*     9-NOV-2004 (DSB):
+*        Override astEqual method.
+*     6-DEC-2004 (DSB):
+*        Remove the second derivative estimate from the astRate function
+*        since CmpMap has trouble calculating it.
+*     17-DEC-2004 (DSB):
+*        Added astMapSplit
+*     22-APR-2005 (DSB):
+*        Modified SpecialBounds to handle cases where some irrelevant
+*        output always produces bad values (e.g. a PermMap may do this).
+*     30-JUN-2005 (DSB):
+*        Added astRebin.
+*     7-JUL-2005 (DSB):
+*        Make MapSplit public rather than protected.
+*     11-AUG-2005 (DSB):
+*        Added the AST__CONSERVEFLUX flag (used by astResampleX).
+*     17-AUG-2005 (DSB):
+*        Added the AST__SOMBCOS kernel.
+*     31-AUG-2005 (DSB):
+*        Added astRebinSeq.
+*     9-SEP-2005 (DSB):
+*        Corrected axis indices returned by public interface for astMapSplit.
+*     31-JAN-2006 (DSB):
+*        Added IsSimple attribute.
+*     2-FEB-2006 (DSB):
+*        Corrections to prologue of astLinearApprox.
+*     16-FEB-2006 (DSB):
+*        Some speed optimisations to rebinning code.
+*     2-MAR-2006 (DSB):
+*        Use HAVE_LONG_DOUBLE in place of AST_LONG_DOUBLE
+*     7-MAR-2006 (DSB):
+*        Added astTranGrid.
+*     14-MAR-2006 (DSB):
+*        - The constructor no longer reports an error if the resulting
+*	 Mapping cannot transform points in either direction. This is
+*	 because it may be possible to simplify such a Mapping and the
+*	 simplified Mapping may have defined transformations. E.g. if a
+*	 Mapping which has only a forward transformation is combined in
+*	 series with its own inverse, the combination CmpMap will simplify
+*        to a UnitMap (usually).
+*        - Reset the "issimple" flag when the Invert flag is changed.
+*     9-MAY-2006 (DSB):
+*        Correct upper bounds for idim in RebinWithblocking. Also, remove
+*        the single precision "F" instantiation of the MAKE_REBINSEQ macro.
+*        Also correct the "nout = astGetNin" line in the MAKE_REBINSEQ
+*        macro to "nout = astGetNout".
+*     12-MAY-2006 (DSB):
+*        Modify SpecialBounds to include points slightly inside the
+*        corners. This is because some Mappings may have singularies at
+*        the the edges.
+*     17-MAY-2006 (DSB):
+*        Correct the "nout = astGetNin" line in the MAKE_RESAMPLE
+*        and MAKE_REBIN macros to "nout = astGetNout".
+*     7-JUL-2006 (DSB):
+*        Change -CHAR_MAX value (used as a "not set" value for boolean
+*        attributes) to +CHAR_MAX, since some compilers do not allow
+*        chars to have negative values.
+*     23-AUG-2006 (DSB):
+*        Change the Equal function so that it reports an error when
+*        called, rather than using astSimplify to determine if two Mappings
+*        are equal. All concrete Mapping classes should now provide
+*        their own implementation of astEqual, avoiding the use of
+*        astSimplify. This is so that astSimplify can use astEqual safely
+*        (i.e. without danger of entering an infinite loop).
+*     24-NOV-2006 (DSB):
+*        Allow astRebinSeq to be called with a NULL pointer for the input
+*        data array.
+*     14-MAR-2007 (DSB):
+*        Modify astRebinSeq to allow input variances to be used as weights.
+*     19-MAR-2007 (DSB):
+*        Fix bug in LINEAR_2D macro that caused bad input pixel values to be
+*        treated as good.
+*     16-APR-2007 (DSB):
+*        Account for reduction in number of degrees of freedom when
+*        calculating output variances on the basis of spread of input values in
+*        astReinSeq.
+*     28-APR-2007 (DSB):
+*        Correct code within Rebin... and Resample... functions that provides
+*        optimal handling for 1- and 2- dimensional mappings. Previously, the
+*        check for whether or not to use these optimisations was based only on
+*        the dimensionality of either input (Rebin) or output (Resample). This
+*        could cause the optimised code to be used at inappropriate times,
+*        leading to an incorrect effective Mapping between input and output. The
+*        checks now check both input and output dimensionality in all cases.
+*     3-MAY-2007 (DSB):
+*        An extra parameter ("nused") has been added to astRebinSeq, and
+*        all the rebinning stuff has been modified to keep "nused" up to date.
+*        This is needed to correct a fault in the generation of GENVAR
+*        variances.
+*     12-DEC-2007 (DSB):
+*        Some rebinning kernels (e.g. SINCSINC) have negative values and
+*        can result in overall negative output weights. Therefore do not
+*        set output pixels with negative weights bad.
+*     6-MAR-2008 (DSB):
+*        Add an option for astResample to leave unchanged any output pixels
+*        for which an interpolated value cannot be obtained. This is
+*        controlled by the new AST__NOBAD flag.
+*     7-MAY-2008 (DSB):
+*        Clarified meaning of AST__GENVAR, AST__USEVAR and AST__VARWGT flags
+*        in astRebinSeq.
+*     9-MAY-2008 (DSB):
+*        Prevent memory over-run in RebinSeq<X>.
+*     5-MAY-2009 (DSB):
+*        Added astRemoveRegions.
+*     11-NOV-2009 (DSB):
+*        In astRebinSeq initialise "*nused" to zero (as documented) if the
+*        AST__REBININIT flag is supplied.
+*     17-NOV-2009 (DSB):
+*        Added AST_DISVAR flag for use with astRebinSeq.
+*     15-DEC-2009 (DSB):
+*        Ensure that all axes span at least one pixel when calling
+*        astLinearApprox.
+*     18-DEC-2009 (DSB):
+*        When using a 1D spreading kernel (in astRebin(Seq)), if the kernel
+*        is not contained completely within the output array, reflect the
+*        section of the kernel that falls outside the output array back into
+*        the output array so that no flux is lost. Also discovered that the
+*        n-D code (i.e. the KERNEL_ND macro) incorrectly uses the first
+*        user-supplied parameter as the full kernel width rather than the
+*        half-width. This has been fixed.
+*     26-FEB-2010 (DSB):
+*        Add astQuadApprox.
+*     27-FEB-2010 (DSB):
+*        - Make astQuadApprox faster, and fix a bug in the calculation of
+*        the matrix.
+*     7-JUN-2010 (DSB):
+*        In the KERNEL_<x>D rebinning macros, correct the test for the
+*        central point being outside the bounds of the output image.
+*     13-AUG-2010 (DSB):
+*        In astRebinSeq<X>, scale WLIM to take account of weighting by
+*        input variances.
+*     13-DEC-2010 (DSB):
+*        Ensure that astMapSplit returns a Mapping that is independent of
+*        the supplied Mapping (i.e. return a deep copy). This means that
+*        subsequent changes to the supplied Mapping cannot affect the returned
+*        Mapping.
+*     10-FEB-2011 (DSB):
+*        When rebinning (in macros NEAR_1/2/ND, KERNEL_1/2/ND, LINEAR_1/2/ND),
+*        do not treat a zero variance as bad unless the reciprocals of the
+*        variances are being used as weights.
+*     16-JUN-2011 (DSB):
+*        Allow a check for NaNs to be performed as a debugging tool after
+*        every invocation of astTransform. This is controlled by the
+*        AST_REPLACE_NAN environment variable: if unset, no check is
+*        performed, if set to "1" NaNs are changed to AST__BAD but no
+*        error is reported, if set to anything else NaNs are changed to
+*        AST__BAD and an error is reported.
+*     6-JUL-2012 (DSB):
+*        The astRebinSeq<X> family was normalising the returned data and
+*        variances values incorrectly, when the AST__REBINEND flag was
+*        supplied. The exact size of the error depended on the nature of
+*        the Mapping and the spreading method, and so is hard to predict.
+*     20-JUL-2012 (DSB):
+*        Major re-structuring of astRebinSeq<X> to add further
+*        corrections to the normalisation. The model is now that each
+*        input array is first rebinned and then scaled to preserve the
+*        total data sum, and then each final output pixel is the weighed
+*        mean of all the aligned rebinned  pixels.
+*     13-AUG-2012 (DSB):
+*        Added AST__NONORM flag for asstRebuinSeq<X>.
+*     30-AUG_2012 (DSB):
+*        Added AST__CONSERVEFLUX flag for astRebinSeq<X>.
+*     10-SEP-2012 (DSB):
+*        Cater for Mappings that have different numbers of inputs and
+*        outputs when finding the flux conservation factor within
+*        astRebinSeq and astResample.
+*     1-OCT-2012 (DSB):
+*        Ensure astRebinSeq<X> does not create any negative output
+*        variances.
+*     2-OCT-2012 (DSB):
+*        - Check for Infs as well as NaNs.
+*        - In Rate, break out of the loop if the RMS is very small, not
+*          just if it is exactly zero.
+*     5-OCT-2012 (DSB):
+*        Complete re-write of Rate. It's now much simpler, faster and
+*        more reliable.
+*     16-OCT-2012 (DSB):
+*        In MatrixDet, ignore rows/columns filled with AST_BAD as well as
+*        rows/columns filled with zeros.
+*     26-APR-2013 (DSB):
+*        Change the "nused" parameter of astRebinSeq<X> from "int *" to
+*        "size_t *" to allow greater amounts of data to be pasted into
+*        the output array.
+*     29-APR-2013 (DSB):
+*        No sot simplify Mappings that have a set value for their Ident
+*        attribute. If an Ident value has been set then it means that we
+*        should be trying to preserve the identify of the Mapping. This
+*        is implemented via a new protected method (astDoNotSimplify) which
+*        is overridden by the Frame class so that this restriction applies
+*        only to genuine Mappings, not Frames.
+*     9-MAY-2013 (DSB):
+*        Change the "nused" parameter of astRebinSeq<X> from "size_t *" to
+*        "int64_t *" to cater for systems where "size_t" is only 32 bits long.
+*     20-MAY-2013 (DSB):
+*        Always perform a linear fit in RebinAdaptively if flux
+*        conservation is requested.
+*     18-JUL-2013 (DSB):
+*        Correct logic for determining whether to divide or not in
+*        RebinAdaptively. The old logic could lead to infinite recursion.
+*     1-SEP-2014 (DSB):
+*        Modify astLinearAPprox to avoid using regularly placed
+*        test points, as such regular placement may result in
+*        non-representative behaviour.
+*     25-SEP-2014 (DSB):
+*        Add support for B and UB data types to astRebin and astRebinSeq.
+*     23-OCT-2014 (DSB):
+*        Report an error if arrays have too many pixels to count in a 32
+*        bit int (astTranGrid, astResample, astRebin and astRebinSeq).
+*     23-APR-2015 (DSB):
+*        Use one bit of this->flags to store the "IsSimple" attribute
+*        rather using a whole char (this->issimple).
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to the header
+   files that define class interfaces that they should make "protected"
+   symbols available. */
+#define astCLASS Mapping
+
+/* Define numerical constants for use in thie module. */
+#define GETATTRIB_BUFF_LEN 50
+#define RATEFUN_MAX_CACHE  5
+#define RATE_ORDER 8
+
+/* Include files. */
+/* ============== */
+
+/* Configuration results  */
+/* ---------------------- */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#include "mapping.h"             /* Interface definition for this class */
+#include "cmpmap.h"              /* Compund Mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "permmap.h"             /* Axis permutations */
+#include "winmap.h"              /* Window scalings */
+#include "pal.h"                 /* SLALIB interface */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Module type definitions. */
+/* ======================== */
+/* Enum to represent the data type when resampling a grid of data. */
+typedef enum DataType {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+   TYPE_LD,
+#endif
+   TYPE_D,
+   TYPE_F,
+   TYPE_L,
+   TYPE_UL,
+   TYPE_K,
+   TYPE_UK,
+   TYPE_I,
+   TYPE_UI,
+   TYPE_S,
+   TYPE_US,
+   TYPE_B,
+   TYPE_UB
+} DataType;
+
+/* Data structure to hold information about a Mapping for use by
+   optimisation algorithms. */
+typedef struct MapData {
+   AstMapping *mapping;          /* Pointer to the Mapping */
+   AstPointSet *pset_in;         /* Pointer to input PointSet */
+   AstPointSet *pset_out;        /* Pointer to output PointSet */
+   double *lbnd;                 /* Pointer to lower constraints on input */
+   double *ubnd;                 /* Pointer to upper constraints on input */
+   double **ptr_in;              /* Pointer to input PointSet coordinates */
+   double **ptr_out;             /* Pointer to output PointSet coordinates */
+   int coord;                    /* Index of output coordinate to optimise */
+   int forward;                  /* Use forward transformation? */
+   int negate;                   /* Negate the output value? */
+   int nin;                      /* Number of input coordinates per point */
+   int nout;                     /* Number of output coordinates per point */
+} MapData;
+
+/* Convert from floating point to floating point or integer */
+#define CONV(IntType,val) ( ( IntType ) ? (int) ( (val) + (((val)>0)?0.5:-0.5) ) : (val) )
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static int (* parent_equal)( AstObject *, AstObject *, int * );
+
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->Unsimplified_Mapping = NULL; \
+   globals->Rate_Disabled = 0;
+
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Mapping)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Mapping,Class_Init)
+#define class_vtab astGLOBAL(Mapping,Class_Vtab)
+#define getattrib_buff astGLOBAL(Mapping,GetAttrib_Buff)
+#define unsimplified_mapping astGLOBAL(Mapping,Unsimplified_Mapping)
+#define rate_disabled astGLOBAL(Mapping,Rate_Disabled)
+#define ratefun_pset1_cache astGLOBAL(Mapping,RateFun_Pset1_Cache)
+#define ratefun_pset2_cache astGLOBAL(Mapping,RateFun_Pset2_Cache)
+#define ratefun_next_slot astGLOBAL(Mapping,RateFun_Next_Slot)
+#define ratefun_pset_size astGLOBAL(Mapping,RateFun_Pset_Size)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Buffer returned by GetAttrib. */
+static char getattrib_buff[ GETATTRIB_BUFF_LEN + 1 ];
+
+/* Pointer to origin (unsimplified) Mapping, only used for reporting
+   error messages. */
+static AstMapping *unsimplified_mapping = NULL;
+
+/* A flag which indicates if the astRate method should be disabled in
+   order to improve algorithm speed in cases where the rate value is not
+   significant. If astRate is disabled then it always returns a constant
+   value of 1.0. */
+static int rate_disabled = 0;
+
+/* static values used in function "RateFun". */
+static AstPointSet *ratefun_pset1_cache[ RATEFUN_MAX_CACHE ];
+static AstPointSet *ratefun_pset2_cache[ RATEFUN_MAX_CACHE ];
+static int ratefun_next_slot;
+static int ratefun_pset_size[ RATEFUN_MAX_CACHE ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstMappingVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* Prototypes for private member functions. */
+/* ======================================== */
+
+#define DECLARE_GENERIC(X,Xtype) \
+static int InterpolateKernel1##X( AstMapping *, int, const int *, const int *, \
+                                  const Xtype *, const Xtype *, int, \
+                                  const int *, const double *const *, \
+                                  void (*)( double, const double *, int, \
+                                            double *, int * ), \
+                                  void (*)( double, const double *, int, \
+                                            double * ), \
+                                  int, const double *, int, Xtype, \
+                                  Xtype *, Xtype *, int * );\
+\
+static int InterpolateLinear##X( int, const int *, const int *, const Xtype *, \
+                                 const Xtype *, int, const int *, \
+                                 const double *const *, int, Xtype, Xtype *, \
+                                 Xtype *, int * ); \
+\
+static int InterpolateNearest##X( int, const int *, const int *, const Xtype *, \
+                                  const Xtype *, int, const int *, \
+                                  const double *const *, int, Xtype, Xtype *, \
+                                  Xtype *, int * ); \
+\
+static int Resample##X( AstMapping *, int, const int [], const int [], \
+                        const Xtype [], const Xtype [], int, \
+                        void (*)( void ), const double [], int, double, int, \
+                        Xtype, int, const int [], const int [], \
+                        const int [], const int [], Xtype [], Xtype [], int * ); \
+\
+static void ConserveFlux##X( double, int, const int *, Xtype, Xtype *, Xtype *, \
+                             int * ); \
+\
+static void InterpolateBlockAverage##X( int, const int[], const int[], \
+                             const Xtype [], const Xtype [], int, const int[], \
+                             const double *const[], const double[], int, \
+                             Xtype, Xtype *, Xtype *, int * );
+
+DECLARE_GENERIC(B,signed char)
+DECLARE_GENERIC(D,double)
+DECLARE_GENERIC(F,float)
+DECLARE_GENERIC(I,int)
+DECLARE_GENERIC(K,INT_BIG)
+DECLARE_GENERIC(L,long int)
+DECLARE_GENERIC(S,short int)
+DECLARE_GENERIC(UB,unsigned char)
+DECLARE_GENERIC(UI,unsigned int)
+DECLARE_GENERIC(UK,UINT_BIG)
+DECLARE_GENERIC(UL,unsigned long int)
+DECLARE_GENERIC(US,unsigned short int)
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+DECLARE_GENERIC(LD,long double)
+#endif
+
+#undef DECLARE_GENERIC
+
+#define DECLARE_GENERIC(X,Xtype) \
+static void Rebin##X( AstMapping *, double, int, const int [], const int [], \
+                      const Xtype [], const Xtype [], int, const double [], int, \
+                      double, int, Xtype, int, const int [], const int [], \
+                      const int [], const int [], Xtype [], Xtype [], int * ); \
+\
+static void RebinSeq##X( AstMapping *, double, int, const int [], const int [], \
+                         const Xtype [], const Xtype [], int, const double [], \
+                         int, double, int, Xtype, int, const int [], \
+                         const int [], const int [], const int [], Xtype [], \
+                         Xtype [], double [], int64_t *, int * ); \
+\
+static void SpreadKernel1##X( AstMapping *, int, const int *, const int *, \
+                         const Xtype *, const Xtype *, double, int, const int *, \
+                         const double *const *, \
+                         void (*)( double, const double *, int, double *, int * ), \
+                         int, const double *, int, Xtype, int, Xtype *, \
+                         Xtype *, double *, int64_t *, int * ); \
+\
+static void SpreadLinear##X( int, const int *, const int *, const Xtype *, \
+                             const Xtype *, double, int, const int *, const double *const *, \
+                             int, Xtype, int, Xtype *, Xtype *, double *, int64_t *, \
+                             int * ); \
+\
+static void SpreadNearest##X( int, const int *, const int *, const Xtype *, \
+                              const Xtype *, double, int, const int *, const double *const *, \
+                              int, Xtype, int, Xtype *, Xtype *, double *, \
+                              int64_t *, int * );
+
+DECLARE_GENERIC(D,double)
+DECLARE_GENERIC(F,float)
+DECLARE_GENERIC(I,int)
+DECLARE_GENERIC(UB,unsigned char)
+DECLARE_GENERIC(B,signed char)
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+DECLARE_GENERIC(LD,long double)
+#endif
+
+#undef DECLARE_GENERIC
+
+
+
+
+
+
+static AstMapping *RemoveRegions( AstMapping *, int * );
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static double FindGradient( AstMapping *, double *, int, int, double, double, double *, int * );
+static double J1Bessel( double, int * );
+static double LocalMaximum( const MapData *, double, double, double [], int * );
+static double MapFunction( const MapData *, const double [], int *, int * );
+static double MatrixDet( int, int, const double *, int * );
+static double MaxD( double, double, int * );
+static double NewVertex( const MapData *, int, double, double [], double [], int *, double [], int * );
+static double Random( long int *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static double UphillSimplex( const MapData *, double, int, const double [], double [], double *, int *, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetInvert( AstMapping *, int * );
+static int GetIsLinear( AstMapping *, int * );
+static int GetIsSimple( AstMapping *, int * );
+static int GetNin( AstMapping *, int * );
+static int GetNout( AstMapping *, int * );
+static int GetReport( AstMapping *, int * );
+static int GetTranForward( AstMapping *, int * );
+static int GetTranInverse( AstMapping *, int * );
+static int LinearApprox( AstMapping *, const double *, const double *, double, double *, int * );
+static int MapList( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int MaxI( int, int, int * );
+static int MinI( int, int, int * );
+static int DoNotSimplify( AstMapping *, int * );
+static int QuadApprox( AstMapping *, const double[2], const double[2], int, int, double *, double *, int * );
+static int RebinAdaptively( AstMapping *, int, const int *, const int *, const void *, const void *, DataType, int, const double *, int, double, int, const void *, int, const int *, const int *, const int *, const int *, int, void *, void *, double *, int64_t *, int * );
+static int RebinWithBlocking( AstMapping *, const double *, int, const int *, const int *, const void *, const void *, DataType, int, const double *, int, const void *, int, const int *, const int *, const int *, const int *, int, void *, void *, double *, int64_t *, int * );
+static int ResampleAdaptively( AstMapping *, int, const int *, const int *, const void *, const void *, DataType, int, void (*)( void ), const double *, int, double, int, const void *, int, const int *, const int *, const int *, const int *, void *, void *, int * );
+static int ResampleSection( AstMapping *, const double *, int, const int *, const int *, const void *, const void *, DataType, int, void (*)( void ), const double *, double, int, const void *, int, const int *, const int *, const int *, const int *, void *, void *, int * );
+static int ResampleWithBlocking( AstMapping *, const double *, int, const int *, const int *, const void *, const void *, DataType, int, void (*)( void ), const double *, int, const void *, int, const int *, const int *, const int *, const int *, void *, void *, int * );
+static int SpecialBounds( const MapData *, double *, double *, double [], double [], int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestInvert( AstMapping *, int * );
+static int TestReport( AstMapping *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearInvert( AstMapping *, int * );
+static void ClearReport( AstMapping *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Decompose( AstMapping *, AstMapping **, AstMapping **, int *, int *, int *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void Gauss( double, const double [], int, double *, int * );
+static void GlobalBounds( MapData *, double *, double *, double [], double [], int * );
+static void Invert( AstMapping *, int * );
+static void MapBox( AstMapping *, const double [], const double [], int, int, double *, double *, double [], double [], int * );
+static void RateFun( AstMapping *, double *, int, int, int, double *, double *, int * );
+static void RebinSection( AstMapping *, const double *, int, const int *, const int *, const void *, const void *, double, DataType, int, const double *, int, const void *, int, const int *, const int *, const int *, const int *, int, void *, void *, double *, int64_t *, int * );
+static void ReportPoints( AstMapping *, int, AstPointSet *, AstPointSet *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetInvert( AstMapping *, int, int * );
+static void SetReport( AstMapping *, int, int * );
+static void Sinc( double, const double [], int, double *, int * );
+static void SincCos( double, const double [], int, double *, int * );
+static void SincGauss( double, const double [], int, double *, int * );
+static void SincSinc( double, const double [], int, double *, int * );
+static void Somb( double, const double [], int, double *, int * );
+static void SombCos( double, const double [], int, double *, int * );
+static void Tran1( AstMapping *, int, const double [], int, double [], int * );
+static void Tran2( AstMapping *, int, const double [], const double [], int, double [], double [], int * );
+static void TranGrid( AstMapping *, int, const int[], const int[], double, int, int, int, int, double *, int * );
+static void TranGridAdaptively( AstMapping *, int, const int[], const int[], const int[], const int[], double, int, int, double *[], int * );
+static void TranGridSection( AstMapping *, const double *, int, const int *, const int *, const int *, const int *, int, double *[], int * );
+static void TranGridWithBlocking( AstMapping *, const double *, int, const int *, const int *, const int *, const int *, int, double *[], int * );
+static void TranN( AstMapping *, int, int, int, const double *, int, int, int, double *, int * );
+static void TranP( AstMapping *, int, int, const double *[], int, int, double *[], int * );
+static void ValidateMapping( AstMapping *, int, int, int, int, const char *, int * );
+
+
+
+/* Member functions. */
+/* ================= */
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Mapping member function (over-rides the astClearAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     Mapping, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstMapping *this;             /* Pointer to the Mapping structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Mapping structure. */
+   this = (AstMapping *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Invert. */
+/* ------- */
+   if ( !strcmp( attrib, "invert" ) ) {
+      astClearInvert( this );
+
+/* Report. */
+/* ------- */
+   } else if ( !strcmp( attrib, "report" ) ) {
+      astClearReport( this );
+
+/* If the name was not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then report an
+   error. */
+   } else if ( !strcmp( attrib, "nin" ) ||
+               !strcmp( attrib, "nout" ) ||
+               !strcmp( attrib, "issimple" ) ||
+               !strcmp( attrib, "islinear" ) ||
+               !strcmp( attrib, "tranforward" ) ||
+               !strcmp( attrib, "traninverse" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+/*
+*  Name:
+*     ConserveFlux<X>
+
+*  Purpose:
+*     Scale the output data and variance values produced by ResampleSection
+*     by the given flux conservation factor.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void ConserveFlux<X>( double factor, int npoint, const int *offset,
+*                           <Xtype> badval, <Xtype> *out,
+*                           <Xtype> *out_var )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This is a set of functions which scale the supplied resampled data
+*     values by the given flux conservation factor. It also scales any
+*     variances by the square of the factor.
+
+*  Parameters:
+*     factor
+*        The flux conservation factor. This should be the ratio of the
+*        output pixel size to the input pixel size, in the locality of
+*        the supplied data values.
+*     npoint
+*        The number of points at which the input grid was resampled.
+*     offset
+*        Pointer to an array of integers with "npoint" elements. For
+*        each output point, this array should contain the zero-based
+*        offset in the output array(s) (i.e. the "out" and,
+*        optionally, the "out_var" arrays) at which the resampled
+*        output value(s) is stored.
+*     badval
+*        This parameter specifies the value which is used to identify
+*        bad data and/or variance values in the output array(s).
+*     out
+*        Pointer to an array in which the resampled data is supplied. Note
+*        that details of how the output grid maps on to this array
+*        (e.g. the storage order, number of dimensions, etc.) is
+*        arbitrary and is specified entirely by means of the "offset"
+*        array. The "out" array should therefore contain sufficient
+*        elements to accommodate the "offset" values supplied.  There
+*        is no requirement that all elements of the "out" array should
+*        be assigned values, and any which are not addressed by the
+*        contents of the "offset" array will be left unchanged.
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, in which variance estimates for
+*        the resampled values are supplied. If no output variance estimates
+*        are available, a NULL pointer should be given.
+
+*  Notes:
+*     - There is a separate function for each numerical type of
+*     gridded data, distinguished by replacing the <X> in the function
+*     name by the appropriate 1- or 2-character suffix.
+*/
+/* Define a macro to implement the function for a specific data
+   type. */
+#define MAKE_CONSERVEFLUX(X,Xtype) \
+static void ConserveFlux##X( double factor, int npoint, const int *offset, \
+                             Xtype badval, Xtype *out, Xtype *out_var, int *status ) { \
+\
+/* Local Variables: */ \
+   int off_out;                  /* Pixel offset into output array */ \
+   int point;                    /* Loop counter for output points */ \
+\
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+   for ( point = 0; point < npoint; point++ ) { \
+      off_out = offset[ point ]; \
+      if( out[ off_out ] != badval ) out[ off_out ] *= factor; \
+   } \
+\
+   if( out_var ) { \
+      factor *= factor; \
+      for ( point = 0; point < npoint; point++ ) { \
+         off_out = offset[ point ]; \
+         if( out_var[ off_out ] != badval ) out_var[ off_out ] *= factor; \
+      } \
+   } \
+}
+
+
+/* Expand the macro above to generate a function for each required
+   data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_CONSERVEFLUX(LD,long double)
+#endif
+MAKE_CONSERVEFLUX(D,double)
+MAKE_CONSERVEFLUX(F,float)
+MAKE_CONSERVEFLUX(K,INT_BIG)
+MAKE_CONSERVEFLUX(L,long int)
+MAKE_CONSERVEFLUX(I,int)
+MAKE_CONSERVEFLUX(S,short int)
+MAKE_CONSERVEFLUX(B,signed char)
+MAKE_CONSERVEFLUX(UL,unsigned long int)
+MAKE_CONSERVEFLUX(UI,unsigned int)
+MAKE_CONSERVEFLUX(UK,UINT_BIG)
+MAKE_CONSERVEFLUX(US,unsigned short int)
+MAKE_CONSERVEFLUX(UB,unsigned char)
+
+/* Undefine the macros used above. */
+#undef MAKE_CONSERVEFLUX
+
+static void Decompose( AstMapping *this, AstMapping **map1, AstMapping **map2,
+                       int *series, int *invert1, int *invert2, int *status ) {
+/*
+*+
+*  Name:
+*     astDecompose
+
+*  Purpose:
+*     Decompose a Mapping into two component Mappings.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void astDecompose( AstMapping *this, AstMapping **map1,
+*                        AstMapping **map2, int *series, int *invert1,
+*                        int *invert2  )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function returns pointers to two Mappings which, when applied
+*     either in series or parallel, are equivalent to the supplied Mapping.
+*
+*     Since the Frame class inherits from the Mapping class, Frames can
+*     be considered as special types of Mappings and so this method can
+*     be used to decompose CmpMaps, CmpFrames, CmpRegions or Prisms.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     map1
+*        Address of a location to receive a pointer to first component
+*        Mapping.
+*     map2
+*        Address of a location to receive a pointer to second component
+*        Mapping.
+*     series
+*        Address of a location to receive a value indicating if the
+*        component Mappings are applied in series or parallel. A non-zero
+*        value means that the supplied Mapping is equivalent to applying map1
+*        followed by map2 in series. A zero value means that the supplied
+*        Mapping is equivalent to applying map1 to the lower numbered axes
+*        and map2 to the higher numbered axes, in parallel.
+*     invert1
+*        The value of the Invert attribute to be used with map1.
+*     invert2
+*        The value of the Invert attribute to be used with map2.
+
+*  Applicability:
+*     CmpMap
+*        If the supplied Mapping is a CmpMap, then map1 and map2 will be
+*        returned holding pointers to the component Mappings used to
+*        create the CmpMap, either in series or parallel.
+*     Mapping
+*        For any class of Mapping other than a CmpMap, map1 will be
+*        returned holding a clone of the supplied Mapping pointer, and map2
+*        will be returned holding a NULL pointer.
+*     CmpFrame
+*        If the supplied Mapping is a CmpFrame, then map1 and map2 will be
+*        returned holding pointers to the component Frames used to
+*        create the CmpFrame. The component Frames are considered to be in
+*        applied in parallel.
+*     Frame
+*        For any class of Frame other than a CmpFrame, map1 will be
+*        returned holding a clone of the supplied Frame pointer, and map2
+*        will be returned holding a NULL pointer.
+
+*  Notes:
+*     - Any changes made to the component Mappings using the returned
+*     pointers will be reflected in the supplied Mapping.
+*     - The returned Invert values should be used in preference to the
+*     current values of the Invert attribute in map1 and map2. This is
+*     because the attributes may have changed value since the Mappings
+*     were combined.
+
+*  Implementation Notes:
+*     - This function implements the basic astDecompose method
+*     available via the protected interface to the Frame class. The
+*     public interface to this method is provided by the
+*     astDecomposeId_ function.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* The basic Mapping class returns a clone of the supplied Mapping as
+   map1 and a NULL pointer as map2. */
+   if( map1 ) *map1 = astClone( this );
+   if( map2 ) *map2 = NULL;
+   if( series ) *series = 1;
+   if( invert1 ) *invert1 = astGetInvert( this );
+   if( invert2 ) *invert2 = 0;
+}
+
+static int DoNotSimplify( AstMapping *this, int *status ) {
+/*
+*+
+*  Name:
+*     astMapMerge
+
+*  Purpose:
+*     Check if a Mapping is appropriate for simplification.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int astDoNotSImplify( AstMapping *this );
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function returns a flag indivating if the supplied Mapping is
+*     appropriate for simplification.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+
+*  Returned Value:
+*     Non-zero if the supplied Mapping is not appropriate for
+*     simplification, and zero otherwise.
+
+*  Notes:
+*     - A value of 0 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Check inherited status. */
+   if( !astOK ) return 0;
+
+/* Mappings that have a set value for the Ident attribute should not be
+   simplified since we want to preserve their individual identify (otherwise
+   why would the user have given them an Ident value?). */
+   return astTestIdent( this );
+}
+
+int astRateState_( int disabled, int *status ) {
+/*
+*+
+*  Name:
+*     astRateState
+
+*  Purpose:
+*     Control whether the astRate method is disabled or not.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int astRateState( int disabled )
+
+*  Class Membership:
+*     Mapping member function
+
+*  Description:
+*     Some algorithms which use use the astRate method do not actually need
+*     to know what the Rate value is. For instance, when the Plot class draws
+*     a border it evaluates the GRAPHICS->Current Mapping hundreds of time.
+*     If the Mapping includes a RateMap then this can be very very slow
+*     (depending on how the astRate method is implemented). In fact the
+*     border drawing algorithm onlyneeds to know if the result is bad or
+*     not - the actual value produced by the Mappign does not matter.
+*
+*     Such algorithms can be speeded up by forcing the astRate method to
+*     return a constant value rather than actually doing the numerical
+*     differentiation. This can be accomplised by calling this method prior
+*     to implementing the algorithm. It should be called at the end in
+*     order to re-instate the original disabled flag.
+
+*  Parameters:
+*     disabled
+*        The new value for the astRate disabled flag.
+
+*  Returned Value:
+*     The original value of the astRate disabled flag.
+
+*-
+*/
+   astDECLARE_GLOBALS
+   int result;
+   astGET_GLOBALS(NULL);
+
+   result = rate_disabled;
+   rate_disabled = disabled;
+   return result;
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two Mappings are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     Mapping member function (over-rides the astEqual protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two Mappings are equivalent.
+*
+*     The implementation provided by this class (the base Mapping class)
+*     simply reports an error when called, since all concrete Mapping
+*     subclasses should provide their own implementation.
+*
+*     Note, sub-class implementations should not use astSimplify (e.g.
+*     combining the two Mapping and then simplifying it), since the
+*     astSimplify method for certain classes (e.g. CmpMap) may use
+*     astEqual. Consequently, if astEqual called astSimplify, there would
+*     be possibilities for infinite loops.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a Mapping).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the Frames are equivalent, zero otherwise.
+
+*  Notes:
+*     - The two Mappings are considered equivalent if the combination of
+*     the first in series with the inverse of the second simplifies to a
+*     UnitMap.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the Equal method inherited from the parent Object class. This checks
+   that the Objects are both of the same class (amongst other things). */
+   if( (*parent_equal)( this_object, that_object, status ) ) {
+
+/* Report an error since the concrete sub-class should have over-riden
+   this method. */
+      astError( AST__INTER, "astEqual(Mapping): The %s class does "
+                "not override the abstract astEqual method inherited "
+                "from the base Mapping class (internal AST programming "
+                "error).", status, astGetClass( this_object ) );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static double FindGradient( AstMapping *map, double *at, int ax1, int ax2,
+                            double x0, double h, double *range, int *status ){
+/*
+*  Name:
+*     FindGradient
+
+*  Purpose:
+*     Find the mean gradient in an interval, and the range of gradients
+*     within the interval.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     double FindGradient( AstMapping *map, double *at, int ax1, int ax2,
+*                          double x0, double h, double *range, int *status )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function finds the mean gradient in an interval, and the range
+*     of gradients within the interval.
+
+*  Parameters:
+*     map
+*        Pointer to a Mapping which yields the value of the function at x.
+*        The Mapping may have any number of inputs and outputs; the specific
+*        output representing the function value, f, is specified by ax1 and
+*        the specific input representing the argument, x, is specified by ax2.
+*     at
+*        A pointer to an array holding axis values at the position at which
+*        the function is to be evaluated. The number of values supplied
+*        must equal the number of inputs to the Mapping. The value supplied
+*        for axis "ax2" is ignored (the value of "x" is used for axis "ax2").
+*     ax1
+*        The zero-based index of the Mapping output which is to be
+*        differentiated. Set this to -1 to allocate, or -2 to release,
+*        the static resources used by this function.
+*     ax2
+*        The zero-based index of the Mapping input which is to be varied.
+*     x0
+*        The central axis value at which the function is to be evaluated.
+*     h
+*        The interval over which the fitting is to be performed.
+*     range
+*        A pointer to a location at which to return the range of
+*        gradients found within the interval.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     The mean gradient, or AST__BAD if the mean gradient cannot be
+*     calculated.
+*/
+
+/* Local Variables: */
+   double dh;
+   double g;
+   double gmax;
+   double gmin;
+   double ret;
+   double x1;
+   double x2;
+   double x[ RATE_ORDER + 2 ];
+   double y1;
+   double y2;
+   double y[ RATE_ORDER + 2 ];
+   int i0;
+   int i;
+   int ngood;
+
+/* Initialise */
+   ret = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Store the x values at (RATE_ORDER+1) evenly spaced points over the interval
+   "h" centred on "x0". */
+   i0 = RATE_ORDER/2;
+   dh = h/RATE_ORDER;
+
+   for( i = 0; i <= RATE_ORDER; i++ ) {
+      x[ i ] = x0 + ( i - i0 )*dh;
+   }
+
+/* Get the function values at these positions. */
+   RateFun( map, at, ax1, ax2, RATE_ORDER + 1, x, y, status );
+
+/* Find the maximum and minimum mean gradient within any sub-interval, and
+   note the (x,y) values at the first and last good point within the
+   interval. */
+   y1 = AST__BAD;
+   y2 = AST__BAD;
+   gmax = AST__BAD;
+   gmin = AST__BAD;
+   ngood = 0;
+
+   for( i = 0; i < RATE_ORDER; i++ ) {
+      if( y[ i + 1 ] !=AST__BAD && y[ i ] != AST__BAD &&
+          x[ i + 1 ] != x[ i ] ) {
+         ngood++;
+
+         g = ( y[ i + 1 ] - y[ i ] )/( x[ i + 1 ] - x[ i ] );
+
+         if( ngood == 1 ) {
+            gmax = gmin = g;
+         } else if( g < gmin ) {
+            gmin = g;
+         } else if( g > gmax) {
+            gmax = g;
+         }
+         if( y1 == AST__BAD ) {
+            y1 = y[ i ];
+            x1 = x[ i ];
+         }
+         y2 = y[ i + 1 ];
+         x2 = x[ i + 1 ];
+      }
+   }
+
+/* If two or more sub-intervals were usable, return the range of
+   gradients found, and the mean gradient. */
+   if( ngood > 1 ) {
+      ret = ( y2 - y1 )/( x2 - x1 );
+      if( range ) *range = ( gmax - gmin );
+   }
+
+   return ret;
+}
+
+static void Gauss( double offset, const double params[], int flags,
+                   double *value, int *status ) {
+/*
+*  Name:
+*     Gauss
+
+*  Purpose:
+*     1-dimensional Gaussian spreading kernel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void Gauss( double offset, const double params[], int flags,
+*                 double *value, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function calculates the value of a 1-dimensional sub-pixel
+*     spreading kernel. The function used is exp(-k*x*x).
+
+*  Parameters:
+*     offset
+*        The offset of a pixel from the central output point, measured
+*        in pixels.
+*     params
+*        The first element of this array should give a value for "k"
+*        in the exp(-k*x*x) term.
+*     flags
+*        Not used.
+*     value
+*        Pointer to a double to receive the calculated kernel value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function does not perform error checking and does not
+*     generate errors.
+*/
+
+/* Calculate the result. */
+   *value = exp( -params[ 0 ] * offset * offset );
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Mapping member function (over-rides the protected astGetAttrib
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a Mapping, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     attrib
+*        Pointer to a null terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a null terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Mapping, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Mapping. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMapping *this;             /* Pointer to the Mapping structure */
+   const char *result;           /* Pointer value to return */
+   int invert;                   /* Invert attribute value */
+   int islinear;                 /* IsLinear attribute value */
+   int issimple;                 /* IsSimple attribute value */
+   int nin;                      /* Nin attribute value */
+   int nout;                     /* Nout attribute value */
+   int report;                   /* Report attribute value */
+   int tran_forward;             /* TranForward attribute value */
+   int tran_inverse;             /* TranInverse attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the Mapping structure. */
+   this = (AstMapping *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* Invert. */
+/* ------- */
+   if ( !strcmp( attrib, "invert" ) ) {
+      invert = astGetInvert( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", invert );
+         result = getattrib_buff;
+      }
+
+/* IsLinear. */
+/* --------- */
+   } else if ( !strcmp( attrib, "islinear" ) ) {
+      islinear = astGetIsLinear( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", islinear );
+         result = getattrib_buff;
+      }
+
+/* IsSimple. */
+/* --------- */
+   } else if ( !strcmp( attrib, "issimple" ) ) {
+      issimple = astGetIsSimple( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", issimple );
+         result = getattrib_buff;
+      }
+
+/* Nin. */
+/* ---- */
+   } else if ( !strcmp( attrib, "nin" ) ) {
+      nin = astGetNin( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", nin );
+         result = getattrib_buff;
+      }
+
+/* Nout. */
+/* ----- */
+   } else if ( !strcmp( attrib, "nout" ) ) {
+      nout = astGetNout( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", nout );
+         result = getattrib_buff;
+      }
+
+/* Report. */
+/* ------- */
+   } else if ( !strcmp( attrib, "report" ) ) {
+      report = astGetReport( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", report );
+         result = getattrib_buff;
+      }
+
+/* TranForward. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "tranforward" ) ) {
+      tran_forward = astGetTranForward( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", tran_forward );
+         result = getattrib_buff;
+      }
+
+/* TranInverse. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "traninverse" ) ) {
+      tran_inverse = astGetTranInverse( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", tran_inverse );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetIsLinear( AstMapping *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetIsLinear
+
+*  Purpose:
+*     Determine if a Mapping is an instance of a linear Mapping class.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int astGetIsLinear( AstMapping *this )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function returns a value indicating whether a Mapping is
+*     a member of a class of linear Mappings. The base Mapping class
+*     returns a value of zero. Linear Mapping classes should over-ride
+*     this function to return a non-zero value.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+
+*  Returned Value:
+*     One if the Mapping is a member of a linear Mapping class. Zero
+*     otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+   return 0;
+}
+
+static int GetNin( AstMapping *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNin
+
+*  Purpose:
+*     Get the number of input coordinates for a Mapping.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int astGetNin( AstMapping *this )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function returns the number of input coordinate values
+*     required per point by a Mapping (i.e. the number of dimensions
+*     of the space in which input points reside).
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+
+*  Returned Value:
+*     Number of coordinate values required.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                   /* Invert attribute value */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Determine if the Mapping has been inverted. */
+   invert = astGetInvert( this );
+
+/* Obtain the Nin value. */
+   if ( astOK ) result = invert ? this->nout : this->nin;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetNout( AstMapping *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNout
+
+*  Purpose:
+*     Get the number of output coordinates for a Mapping.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int astGetNout( AstMapping *this )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function returns the number of output coordinate values
+*     generated per point by a Mapping (i.e. the number of dimensions
+*     of the space in which output points reside).
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+
+*  Returned Value:
+*     Number of coordinate values generated.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                   /* Invert attribute value */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Determine if the Mapping has been inverted. */
+   invert = astGetInvert( this );
+
+/* Obtain the Nout value. */
+   if ( astOK ) result = invert ? this->nin : this->nout;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetTranForward( AstMapping *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetTranForward
+
+*  Purpose:
+*     Determine if a Mapping defines a forward coordinate transformation.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int astGetTranForward( AstMapping *this )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function returns a value indicating whether a Mapping is
+*     able to perform a coordinate transformation in the "forward"
+*     direction.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+
+*  Returned Value:
+*     Zero if the forward coordinate transformation is not defined, or
+*     1 if it is.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                   /* Mapping inverted? */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Determine if the Mapping has been inverted. */
+   invert = astGetInvert( this );
+
+/* If OK, obtain the result. */
+   if ( astOK ) result = invert ? this->tran_inverse : this->tran_forward;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetTranInverse( AstMapping *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetTranInverse
+
+*  Purpose:
+*     Determine if a Mapping defines an inverse coordinate transformation.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int astGetTranInverse( AstMapping *this )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function returns a value indicating whether a Mapping is
+*     able to perform a coordinate transformation in the "inverse"
+*     direction.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+
+*  Returned Value:
+*     Zero if the inverse coordinate transformation is not defined, or
+*     1 if it is.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   int invert;                   /* Mapping inverted? */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Determine if the Mapping has been inverted. */
+   invert = astGetInvert( this );
+
+/* If OK, obtain the result. */
+   if ( astOK ) result = invert ? this->tran_forward : this->tran_inverse;
+
+/* Return the result. */
+   return result;
+}
+
+static void GlobalBounds( MapData *mapdata, double *lbnd, double *ubnd,
+                          double xl[], double xu[], int *status ) {
+/*
+*  Name:
+*     GlobalBounds
+
+*  Purpose:
+*     Estimate global coordinate bounds for a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GlobalBounds( MapData *mapdata, double *lbnd, double *ubnd,
+*                        double xl[], double xu[], int *status );
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function estimates the global lower and upper bounds of a
+*     Mapping function within a constrained region of its input
+*     coordinate space. It uses a robust global optimisation algorithm
+*     based on the selection of pseudo-random starting positions,
+*     followed by the location of local minima and maxima using the
+*     downhill (or uphill) simplex method. The algorithm will cope
+*     with the case where there are several competing minima (or
+*     maxima) with nearly equal values. It attempts to locate the
+*     global bounds to full machine precision when possible.
+
+*  Parameters:
+*     mapdata
+*        Pointer to a MapData structure describing the Mapping
+*        function, its coordinate constraints, etc.
+*     lbnd
+*        Pointer to a double.  On entry, this should contain a
+*        previously-obtained upper limit on the global lower bound, or
+*        AST__BAD if no such limit is available. On exit, it will be
+*        updated with a new estimate of the global lower bound, if a
+*        better one has been found.
+*     ubnd
+*        Pointer to a double.  On entry, this should contain a
+*        previously-obtained lower limit on the global upper bound, or
+*        AST__BAD if no such limit is available. On exit, it will be
+*        updated with a new estimate of the global upper bound, if a
+*        better one has been found.
+*     xl
+*        Pointer to an array of double, with one element for each
+*        input coordinate. On entry, if *lbnd is not equal to AST__OK,
+*        this should contain the input coordinates of a point at which
+*        the Mapping function takes the value *lbnd. On exit, this
+*        function returns the position of a (not necessarily unique)
+*        input point at which the Mapping function takes the value of
+*        the new global lower bound.  This array is not altered if an
+*        improved estimate of the global lower bound cannot be found.
+*     xu
+*        Pointer to an array of double, with one element for each
+*        input coordinate. On entry, if *ubnd is not equal to AST__OK,
+*        this should contain the input coordinates of a point at which
+*        the Mapping function takes the value *ubnd. On exit, this
+*        function returns the position of a (not necessarily unique)
+*        input point at which the Mapping function takes the value of
+*        the new global upper bound.  This array is not altered if an
+*        improved estimate of the global upper bound cannot be found.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The efficiency of this function will usually be improved if
+*     previously-obtained estimates of the extrema and their locations
+*     are provided.
+*     - The values returned via "lbnd", "ubnd", "xl" and "xu" will be
+*     set to the value AST__BAD if this function should fail for any
+*     reason. Their initial values on entry will not be altered if the
+*     function is invoked with the global error status set.
+*/
+
+/* Local Constants: */
+   const double default_acc = 3.0e-5; /* Default convergence accuracy */
+   const int maxiter = 10000;    /* Maximum number of iterations */
+   const int minsame = 5;        /* Minimum no. consistent extrema required */
+   const int nbatch = 32;        /* No. function samples obtained per batch */
+
+/* Local Variables: */
+   AstPointSet *pset_in;         /* Input PointSet for batch transformation */
+   AstPointSet *pset_out;        /* Output PointSet for batch transformation */
+   double **ptr_in;              /* Pointer to batch input coordinates */
+   double **ptr_out;             /* Pointer to batch output coordinates */
+   double *active_hi;            /* Estimated upper limits of active region */
+   double *active_lo;            /* Estimated lower limits of active region */
+   double *sample_hi;            /* Upper limits of sampled region */
+   double *sample_lo;            /* Lower limits of sampled region */
+   double *sample_width;         /* Nominal widths of sampled region */
+   double *x;                    /* Pointer to array of coordinates */
+   double acc;                   /* Convergence accuracy for finding maximum */
+   double active_width;          /* Estimated width of active region */
+   double new_max;               /* Value of new local maximum */
+   double new_min;               /* Value of new local minimum */
+   double oversize;              /* Over-size factor for sampled region */
+   double random;                /* Pseudo-random number */
+   int bad;                      /* Transformed position is bad? */
+   int batch;                    /* Next element to use in position batch */
+   int coord;                    /* Loop counter for coordinates */
+   int done_max;                 /* Satisfactory global maximum found? */
+   int done_min;                 /* Satisfactory global minimum found? */
+   int iter;                     /* Loop counter for iterations */
+   int ncoord;                   /* Number of coordinates in search space */
+   int nmax;                     /* Number of local maxima found */
+   int nmin;                     /* Number of local minima found */
+   int nsame_max;                /* Number of equivalent local maxima found */
+   int nsame_min;                /* Number of equivalent local minima found */
+   long int seed = 1776655449;   /* Arbitrary pseudo-random number seed */
+
+/* Check the global error status */
+   if ( !astOK ) return;
+
+/* Initialise. */
+   done_max = 0;
+   done_min = 0;
+   nmax = 0;
+   nmin = 0;
+   nsame_max = 0;
+   nsame_min = 0;
+   pset_in = NULL;
+   pset_out = NULL;
+   ptr_in = NULL;
+   ptr_out = NULL;
+   oversize = 0;
+   bad = 0;
+
+/* Extract the number of input coordinates for the Mapping function
+   and allocate workspace. */
+   ncoord = mapdata->nin;
+   active_hi = astMalloc( sizeof( double ) * (size_t) ncoord );
+   active_lo = astMalloc( sizeof( double ) * (size_t) ncoord );
+   sample_hi = astMalloc( sizeof( double ) * (size_t) ncoord );
+   sample_lo = astMalloc( sizeof( double ) * (size_t) ncoord );
+   sample_width = astMalloc( sizeof( double ) * (size_t) ncoord );
+   x = astMalloc( sizeof( double ) * (size_t) ncoord );
+   if ( astOK ) {
+
+/* Calculate the factor by which the size of the region we sample will
+   exceed the size of the Mapping function's active region (the region
+   where the transformed coordinates are non-bad) in each
+   dimension. This is chosen so that the volume ratio will be 2. */
+      oversize = pow( 2.0, 1.0 / (double) ncoord );
+
+/* Initialise the limits of the active region to unknown. */
+      for ( coord = 0; coord < ncoord; coord++ ) {
+         active_lo[ coord ] = DBL_MAX;;
+         active_hi[ coord ] = -DBL_MAX;
+
+/* Initialise the nominal widths of the sampled region to be the
+   actual widths of the search region times the over-size factor. */
+         sample_width[ coord ] = ( mapdata->ubnd[ coord ] -
+                                   mapdata->lbnd[ coord ] ) * oversize;
+
+/* Initialise the sampled region to match the search region. */
+         sample_lo[ coord ] = mapdata->lbnd[ coord ];
+         sample_hi[ coord ] = mapdata->ubnd[ coord ];
+      }
+
+/* Set up position buffer. */
+/* ======================= */
+/* Create two PointSets to act as buffers to hold a complete batch of
+   input and output coordinates. Obtain pointers to their coordinate
+   arrays. */
+      pset_in = astPointSet( nbatch, ncoord, "", status );
+      pset_out = astPointSet( nbatch, mapdata->nout, "", status );
+      ptr_in = astGetPoints( pset_in );
+      ptr_out = astGetPoints( pset_out );
+
+/* Initialise the next element to be used in the position buffer to
+   indicate that the buffer is initially empty. */
+      batch = nbatch;
+   }
+
+/* Define a macro to fill the position buffer with a set of
+   pseudo-random positions and to transform them. */
+#define FILL_POSITION_BUFFER {\
+\
+/* We first generate a suitable volume over which to distribute the\
+   batch of pseudo-random positions. Initially, this will be the\
+   entire search volume, but if we find that the only non-bad\
+   transformed coordinates we obtain are restricted to a small\
+   sub-region of this input volume, then we reduce the sampled volume\
+   so as to concentrate more on the active region. */\
+\
+/* Loop through each input coordinate, checking that at least one\
+   non-bad transformed point has been obtained. If not, we do not\
+   adjust the sampled volume, as we do not yet know where the active\
+   region lies. */\
+   for ( coord = 0; coord < ncoord; coord++ ) {\
+      if ( active_hi[ coord ] >= active_lo[ coord ] ) {\
+\
+/* Estimate the width of the active region from the range of input\
+   coordinates that have so far produced non-bad transformed\
+   coordinates. */\
+         active_width = active_hi[ coord ] - active_lo[ coord ];\
+\
+/* If the current width of the sampled volume exceeds this estimate by\
+   more than the required factor, then reduce the width of the sampled\
+   volume. The rate of reduction is set so that the volume of the\
+   sampled region can halve with every fourth batch of positions. */\
+         if ( ( active_width * oversize ) < sample_width[ coord ] ) {\
+            sample_width[ coord ] /= pow( oversize, 0.25 );\
+\
+/* If the width of the sampled volume does not exceed that of the\
+   known active region by the required factor, then adjust it so that\
+   it does. Note that we must continue to sample some points outside\
+   the known active region in case we have missed any (in which case\
+   the sampled region will expand again to include them). */\
+         } else if ( ( active_width * oversize ) > sample_width[ coord ] ) {\
+            sample_width[ coord ] = active_width * oversize;\
+         }\
+\
+/* Calculate the lower and upper bounds on the sampled volume, using\
+   the new width calculated above and centring it on the active\
+   region, as currently known. */\
+         sample_lo[ coord ] = ( active_lo[ coord ] + active_hi[ coord ] -\
+                                sample_width[ coord ] ) * 0.5;\
+         sample_hi[ coord ] = ( active_lo[ coord ] + active_hi[ coord ] +\
+                                sample_width[ coord ] ) * 0.5;\
+\
+/* Ensure that the sampled region does not extend beyond the original\
+   search region. */\
+         if ( sample_lo[ coord ] < mapdata->lbnd[ coord ] ) {\
+            sample_lo[ coord ] = mapdata->lbnd[ coord ];\
+         }\
+         if ( sample_hi[ coord ] > mapdata->ubnd[ coord ] ) {\
+            sample_hi[ coord ] = mapdata->ubnd[ coord ];\
+         }\
+      }\
+   }\
+\
+/* Having determined the size of the sampled volume, create a batch of\
+   pseudo-random positions uniformly distributed within it. */\
+   for ( batch = 0; batch < nbatch; batch++ ) {\
+      for ( coord = 0; coord < ncoord; coord++ ) {\
+         random = Random( &seed, status );\
+         ptr_in[ coord ][ batch ] = sample_lo[ coord ] * random +\
+                                    sample_hi[ coord ] * ( 1.0 - random );\
+      }\
+   }\
+\
+/* Transform these positions. We process them in a single batch in\
+   order to minimise the overheads in doing this. */\
+   (void) astTransform( mapdata->mapping, pset_in, mapdata->forward,\
+                        pset_out );\
+\
+/* Indicate that the position buffer is now full. */\
+   batch = 0;\
+}
+
+/* Fill the position buffer using the above macro. (Note that because
+   we do not yet have an estimate of the size of the active region,
+   this does not change the sampled region size from our earlier
+   initialised values. */
+   FILL_POSITION_BUFFER;
+
+/* Iterate. */
+/* ======== */
+/* Loop to perform up to "maxiter" iterations to estimate the global
+   minimum and maximum. */
+   for ( iter = 0; astOK && ( iter < maxiter ); iter++ ) {
+
+/* Determine the search accuracy. */
+/* ============================== */
+/* Decide the accuracy to which local extrema should be found. The
+   intention here is to optimise performance, especially where one
+   extremum lies near zero and so could potentially be found to
+   unnecessarily high precision. If we make a mis-assumption (the code
+   below is not fool-proof), we will slow things down for this
+   iteration, but the error will be corrected in future iterations
+   once better estimates are available. */
+
+/* If we have no current estimate of either global extremum, we assume
+   the values we eventually obtain will be of order unity and required
+   to the default accuracy. */
+      acc = default_acc;
+
+/* If we already have an estimate of both global extrema, we set the
+   accuracy level so that the difference between them will be known to
+   the default accuracy. */
+      if ( ( *lbnd != AST__BAD ) && ( *ubnd != AST__BAD ) ) {
+         acc = fabs( *ubnd - *lbnd ) * default_acc;
+
+/* If we have an estimate of only one global extremum, we assume that
+   the difference between the two global extrema will eventually be of
+   the same order as the estimate we currently have, so long as this
+   is not less than unity. */
+      } else if ( *lbnd != AST__BAD ) {
+         if ( fabs( *lbnd ) > 1.0 ) acc = fabs( *lbnd) * default_acc;
+      } else if ( *ubnd != AST__BAD ) {
+         if ( fabs( *ubnd ) > 1.0 ) acc = fabs( *ubnd) * default_acc;
+      }
+
+/* Search for a new local minimum. */
+/* =============================== */
+/* If we are still searching for the global minimum, then obtain a set
+   of starting coordinates from which to find a new local minimum. */
+      if ( !done_min ) {
+
+/* On the first iteration, start searching at the position where the
+   best estimate of the global minimum (if any) has previously been
+   found. We know that this produces non-bad transformed
+   coordinates. */
+         bad = 0;
+         if ( !iter && ( *lbnd != AST__BAD ) ) {
+            for ( coord = 0; coord < ncoord; coord++ ) {
+               x[ coord ] = xl[ coord ];
+            }
+
+/* Otherwise, if no estimate of the global minimum is available, then
+   start searching at the position where the best estimate of the
+   global maximum (if any) has been found. This may be a long way from
+   a local minimum, but at least it will yield a non-bad value for the
+   Mapping function, so some sort of estimate of the global minimum
+   will be obtained. This is important in cases where finding the
+   active region of the function is the main problem. Note that this
+   condition can only occur once, since the global minimum will have
+   an estimate on the next iteration. */
+         } else if ( ( *lbnd == AST__BAD ) && ( *ubnd != AST__BAD ) ) {
+            for ( coord = 0; coord < ncoord; coord++ ) {
+               x[ coord ] = xu[ coord ];
+            }
+
+/* Having exhausted the above possibilities, we use pseudo-random
+   starting positions which are uniformly distributed throughout the
+   search volume. First check to see if the buffer containing such
+   positions is empty and refill it if necessary. */
+         } else {
+            if ( batch >= nbatch ) FILL_POSITION_BUFFER;
+
+/* Test the next available set of output (transformed) coordinates in
+   the position buffer to see if they are bad. */
+            if ( astOK ) {
+               for ( coord = 0; coord < mapdata->nout; coord++ ) {
+                  bad = ( ptr_out[ coord ][ batch ] == AST__BAD );
+                  if ( bad ) break;
+               }
+
+/* If not, we have a good starting position for finding a local
+   minimum, so extract the corresponding input coordinates. */
+               if ( !bad ) {
+                  for ( coord = 0; coord < ncoord; coord++ ) {
+                     x[ coord ] = ptr_in[ coord ][ batch ];
+                  }
+               }
+
+/* Increment the position buffer location. */
+               batch++;
+            }
+         }
+
+/* If we do not have a good starting position, we can't do anything
+   more on this iteration. A new position will be obtained and tested
+   on the next iteration and this (we hope) will eventually identify a
+   suitable starting point. */
+         if ( astOK && !bad ) {
+
+/* Form estimates of the lower and upper limits of the active region
+   from the starting positions used. */
+            for ( coord = 0; coord < ncoord; coord++ ) {
+               if ( x[ coord ] < active_lo[ coord ] ) {
+                  active_lo[ coord ] = x[ coord ];
+               }
+               if ( x[ coord ] > active_hi[ coord ] ) {
+                  active_hi[ coord ] = x[ coord ];
+               }
+            }
+
+/* Indicate that the Mapping function should be negated (because we
+   want a local minimum) and then search for a local maximum in this
+   negated function. If the result is non-bad (as it should always be,
+   barring an error), then negate it to obtain the value of the local
+   minimum found. */
+            mapdata->negate = 1;
+            new_min = LocalMaximum( mapdata, acc, 0.01, x, status );
+            if ( new_min != AST__BAD ) {
+               new_min = -new_min;
+
+/* Update the estimates of the lower and upper bounds of the active
+   region to take account of where the minimum was found. */
+               for ( coord = 0; coord < ncoord; coord++ ) {
+                  if ( x[ coord ] < active_lo[ coord ] ) {
+                     active_lo[ coord ] = x[ coord ];
+                  }
+                  if ( x[ coord ] > active_hi[ coord ] ) {
+                     active_hi[ coord ] = x[ coord ];
+                  }
+               }
+
+/* Count the number of times we successfully locate a local minimum
+   (ignoring the fact they might all be the same one). */
+               nmin++;
+
+/* Update the global minimum. */
+/* ========================== */
+/* If this is the first estimate of the global minimum, then set to
+   one the count of the number of consecutive iterations where this
+   estimate remains unchanged. Store the minimum value and its
+   position. */
+               if ( *lbnd == AST__BAD ) {
+                  nsame_min = 1;
+                  *lbnd = new_min;
+                  for ( coord = 0; coord < ncoord; coord++ ) {
+                     xl[ coord ] = x[ coord ];
+                  }
+
+/* Otherwise, test if this local minimum is lower than the previous
+   estimate of the global minimum. If so, then reset the count of
+   unchanged estimates of the global mimimum to one if the difference
+   exceeds the accuracy with which the minimum was found (i.e. if we
+   have found a significantly different minimum). Otherwise, just
+   increment this count (because we have found the same minimum but by
+   chance with slightly improved accuracy). Store the new minimum and
+   its position. */
+               } else if ( new_min < *lbnd ) {
+                  nsame_min = ( ( *lbnd - new_min ) > acc ) ? 1 :
+                                                              nsame_min + 1;
+                  *lbnd = new_min;
+                  for ( coord = 0; coord < ncoord; coord++ ) {
+                     xl[ coord ] = x[ coord ];
+                  }
+
+/* If the latest local minimum is no improvement on previous estimates
+   of the global minimum, then increment the count of unchanged
+   estimates of the global mimimum, but do not save the new one. */
+               } else {
+                  nsame_min++;
+               }
+
+/* Determine if a satisfactory estimate of the global minimum has been
+   obtained.  It has if the number of consecutive local minima which
+   have not significantly improved the estimate is at least equal to
+   "minsame", and at least 30% of the total number of local minima
+   found. */
+               if ( ( nsame_min >= minsame ) &&
+                    ( nsame_min >= (int) ( 0.3f * (float) nmin + 0.5f ) ) ) {
+                  done_min = 1;
+               }
+            }
+         }
+      }
+
+/* Search for a new local maximum. */
+/* =============================== */
+/* Now repeat all of the above to find a new local maximum which
+   estimates the global maximum. */
+      if ( !done_max ) {
+
+/* Choose a suitable starting position, based on one already available
+   if appropriate. */
+         if ( !iter && ( *ubnd != AST__BAD ) ) {
+            for ( coord = 0; coord < ncoord; coord++ ) {
+               x[ coord ] = xu[ coord ];
+            }
+
+         } else if ( ( *ubnd == AST__BAD ) && ( *lbnd != AST__BAD ) ) {
+            for ( coord = 0; coord < ncoord; coord++ ) {
+               x[ coord ] = xl[ coord ];
+            }
+
+/* Otherwise use a pseudo-random position, refilling the position
+   buffer if necessary. Check if the transformed coordinates are
+   bad. */
+         } else {
+            if ( batch >= nbatch ) FILL_POSITION_BUFFER;
+            if ( astOK ) {
+               for ( coord = 0; coord < mapdata->nout; coord++ ) {
+                  bad = ( ptr_out[ coord ][ batch ] == AST__BAD );
+                  if ( bad ) break;
+               }
+               if ( !bad ) {
+                  for ( coord = 0; coord < ncoord; coord++ ) {
+                     x[ coord ] = ptr_in[ coord ][ batch ];
+                  }
+               }
+               batch++;
+            }
+         }
+
+/* If the coordinates are OK, update the active region limits. */
+         if ( astOK && !bad ) {
+            for ( coord = 0; coord < ncoord; coord++ ) {
+               if ( x[ coord ] < active_lo[ coord ] ) {
+                  active_lo[ coord ] = x[ coord ];
+               }
+               if ( x[ coord ] > active_hi[ coord ] ) {
+                  active_hi[ coord ] = x[ coord ];
+               }
+            }
+
+/* Find a local maximum in the Mapping function. */
+            mapdata->negate = 0;
+            new_max = LocalMaximum( mapdata, acc, 0.01, x, status );
+            if ( new_max != AST__BAD ) {
+
+/* Use the result to further update the active region limits. */
+               for ( coord = 0; coord < ncoord; coord++ ) {
+                  if ( x[ coord ] < active_lo[ coord ] ) {
+                     active_lo[ coord ] = x[ coord ];
+                  }
+                  if ( x[ coord ] > active_hi[ coord ] ) {
+                     active_hi[ coord ] = x[ coord ];
+                  }
+               }
+
+/* Count the number of local maxima found. */
+               nmax++;
+
+/* Update the estimate of the global maximum. */
+               if ( *ubnd == AST__BAD ) {
+                  nsame_max = 1;
+                  *ubnd = new_max;
+                  for ( coord = 0; coord < ncoord; coord++ ) {
+                     xu[ coord ] = x[ coord ];
+                  }
+
+               } else if ( new_max > *ubnd ) {
+                  nsame_max = ( ( new_max - *ubnd ) > acc ) ? 1 :
+                                                              nsame_max + 1;
+                  *ubnd = new_max;
+                  for ( coord = 0; coord < ncoord; coord++ ) {
+                     xu[ coord ] = x[ coord ];
+                  }
+
+               } else {
+                  nsame_max++;
+               }
+
+/* Test for a satisfactory global maximum estimate. */
+               if ( ( nsame_max >= minsame ) &&
+                    ( nsame_max >= (int) ( 0.3f * (float) nmax + 0.5 ) ) ) {
+                  done_max = 1;
+               }
+            }
+         }
+      }
+
+/* Quit iterating once both the global minimum and the global maximum
+   have been found. */
+      if ( done_min && done_max ) break;
+   }
+
+/* Free workspace. */
+   active_hi = astFree( active_hi );
+   active_lo = astFree( active_lo );
+   sample_hi = astFree( sample_hi );
+   sample_lo = astFree( sample_lo );
+   sample_width = astFree( sample_width );
+   x = astFree( x );
+
+/* Annul temporary PointSets. */
+   pset_in = astAnnul( pset_in );
+   pset_out = astAnnul( pset_out );
+
+/* If the global minimum has been found, attempt to polish the result
+   to machine precision by requesting that it be found with an
+   accuracy tolerance of zero (subject to the maximum number of
+   iterations that LocalMaximum will perform,). */
+   if ( astOK ) {
+      if ( *lbnd != AST__BAD ) {
+         mapdata->negate = 1;
+         *lbnd = LocalMaximum( mapdata, 0.0, sqrt( DBL_EPSILON ), xl, status );
+         if ( *lbnd != AST__BAD ) *lbnd = - *lbnd;
+      }
+
+/* Similarly polish the estimate of the global maximum. */
+      if ( *ubnd != AST__BAD ) {
+         mapdata->negate = 0;
+         *ubnd = LocalMaximum( mapdata, 0.0, sqrt( DBL_EPSILON ), xu, status );
+      }
+
+/* If either extremum could not be found, then report an error. */
+      if ( ( *lbnd == AST__BAD ) || ( *ubnd == AST__BAD ) ) {
+         astError( AST__MBBNF, "astMapBox(%s): No valid output coordinates "
+                   "(after %d test points).", status, astGetClass( mapdata->mapping ),
+                   2 * maxiter );
+      }
+
+/* If an error occurred, then return bad extremum values and
+   coordinates. */
+      if ( !astOK ) {
+         *lbnd = AST__BAD;
+         *ubnd = AST__BAD;
+         for ( coord = 0; coord < ncoord; coord++ ) {
+            xl[ coord ] = AST__BAD;
+            xu[ coord ] = AST__BAD;
+         }
+      }
+   }
+
+/* Undefine macros local to this function. */
+#undef FILL_POSITION_BUFFER
+}
+
+void astInitMappingVtab_(  AstMappingVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitMappingVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Mapping.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void astInitMappingVtab( AstMappingVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Mapping vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Mapping class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitObjectVtab( (AstObjectVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAMapping) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstObjectVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+#define VTAB_GENERIC(X) \
+   vtab->Resample##X = Resample##X;
+
+VTAB_GENERIC(B)
+VTAB_GENERIC(D)
+VTAB_GENERIC(F)
+VTAB_GENERIC(I)
+VTAB_GENERIC(K)
+VTAB_GENERIC(L)
+VTAB_GENERIC(S)
+VTAB_GENERIC(UB)
+VTAB_GENERIC(UI)
+VTAB_GENERIC(UK)
+VTAB_GENERIC(UL)
+VTAB_GENERIC(US)
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+VTAB_GENERIC(LD)
+#endif
+
+#undef VTAB_GENERIC
+
+#define VTAB_GENERIC(X) \
+   vtab->Rebin##X = Rebin##X; \
+   vtab->RebinSeq##X = RebinSeq##X;
+
+VTAB_GENERIC(D)
+VTAB_GENERIC(F)
+VTAB_GENERIC(I)
+VTAB_GENERIC(B)
+VTAB_GENERIC(UB)
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+VTAB_GENERIC(LD)
+#endif
+
+#undef VTAB_GENERIC
+
+
+   vtab->ClearInvert = ClearInvert;
+   vtab->ClearReport = ClearReport;
+   vtab->Decompose = Decompose;
+   vtab->DoNotSimplify = DoNotSimplify;
+   vtab->GetInvert = GetInvert;
+   vtab->GetIsLinear = GetIsLinear;
+   vtab->GetIsSimple = GetIsSimple;
+   vtab->GetNin = GetNin;
+   vtab->GetNout = GetNout;
+   vtab->GetReport = GetReport;
+   vtab->GetTranForward = GetTranForward;
+   vtab->GetTranInverse = GetTranInverse;
+   vtab->Invert = Invert;
+   vtab->LinearApprox = LinearApprox;
+   vtab->MapBox = MapBox;
+   vtab->MapList = MapList;
+   vtab->MapMerge = MapMerge;
+   vtab->MapSplit = MapSplit;
+   vtab->QuadApprox = QuadApprox;
+   vtab->Rate = Rate;
+   vtab->ReportPoints = ReportPoints;
+   vtab->RemoveRegions = RemoveRegions;
+   vtab->SetInvert = SetInvert;
+   vtab->SetReport = SetReport;
+   vtab->Simplify = Simplify;
+   vtab->TestInvert = TestInvert;
+   vtab->TestReport = TestReport;
+   vtab->Tran1 = Tran1;
+   vtab->Tran2 = Tran2;
+   vtab->TranGrid = TranGrid;
+   vtab->TranN = TranN;
+   vtab->TranP = TranP;
+   vtab->Transform = Transform;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+   parent_equal = object->Equal;
+   object->Equal = Equal;
+
+/* Declare the destructor, copy constructor and dump function. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "Mapping", "Mapping between coordinate systems" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+/*
+*  Name:
+*     InterpolateKernel1<X>
+
+*  Purpose:
+*     Resample a data grid, using a 1-d interpolation kernel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int InterpolateKernel1<X>( AstMapping *this, int ndim_in,
+*                                const int *lbnd_in, const int *ubnd_in,
+*                                const <Xtype> *in, const <Xtype> *in_var,
+*                                int npoint, const int *offset,
+*                                const double *const *coords,
+*                                void (* kernel)( double, const double [], int,
+*                                                 double *, int * ),
+*                                void (* fkernel)( double, const double [], int,
+*                                                  double * ),
+*                                int neighb, const double *params, int flags,
+*                                <Xtype> badval,
+*                                <Xtype> *out, <Xtype> *out_var )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This is a set of functions which resample a rectangular input
+*     grid of data (and, optionally, associated statistical variance
+*     values) so as to place them into a new output grid. Each output
+*     grid point may be mapped on to a position in the input grid in
+*     an arbitrary way. The input and output grids may have any number
+*     of dimensions, not necessarily equal.
+*
+*     Where the positions given do not correspond with a pixel centre
+*     in the input grid, interpolation is performed using a weighted
+*     sum of the surrounding pixel values. The weights are determined
+*     by a separable kernel which is the product of a 1-dimensional
+*     kernel function evaluated along each input dimension. A pointer
+*     should be supplied to the 1-dimensional kernel function to be
+*     used.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping being used in the resampling operation
+*        (this is only used for constructing error messages).
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input grid, its extent along a particular
+*        (i'th) dimension being ubnd_in[i]-lbnd_in[i]+1 (assuming "i"
+*        is zero-based). They also define the input grid's coordinate
+*        system, with each pixel being of unit extent along each
+*        dimension with integral coordinate values at its centre.
+*     in
+*        Pointer to the array of data to be resampled (with an element
+*        for each pixel in the input grid). The numerical type of
+*        these data should match the function used, as given by the
+*        suffix on the function name. The storage order should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+*        (i.e. Fortran array storage order).
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and type as the "in" array), which
+*        represent estimates of the statistical variance associated
+*        with each element of the "in" array. If this second array is
+*        given (along with the corresponding "out_var" array), then
+*        estimates of the variance of the resampled data will also be
+*        returned.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     npoint
+*        The number of points at which the input grid is to be
+*        resampled.
+*     offset
+*        Pointer to an array of integers with "npoint" elements. For
+*        each output point, this array should contain the zero-based
+*        offset in the output array(s) (i.e. the "out" and,
+*        optionally, the "out_var" arrays) at which the resampled
+*        output value(s) should be stored.
+*     coords
+*        An array of pointers to double, with "ndim_in"
+*        elements. Element "coords[coord]" should point at the first
+*        element of an array of double (with "npoint" elements) which
+*        contains the values of coordinate number "coord" for each
+*        interpolation point. The value of coordinate number "coord"
+*        for interpolation point number "point" is therefore given by
+*        "coords[coord][point]" (assuming both indices to be
+*        zero-based).  If any point has a coordinate value of AST__BAD
+*        associated with it, then the corresponding output data (and
+*        variance) will be set to the value given by "badval" (unles the
+*        AST__NOBAD flag is specified).
+*     kernel
+*        Pointer to the 1-dimensional kernel function to be used.
+*     fkernel
+*        Pointer to the 1-dimensional kernel function to be used with no
+*        trailing status argument. This is only used if "kernel" is NULL.
+*     neighb
+*        The number of neighbouring pixels in each dimension (on each
+*        side of the interpolation position) which are to contribute
+*        to the interpolated value. This value should be at least 1.
+*     params
+*        Pointer to an optional array of parameter values to be passed
+*        to the interpolation kernel function. If no parameters are
+*        required by this function, then a NULL pointer may be
+*        supplied.
+*     flags
+*        The bitwise OR of a set of flag values which provide
+*        additional control over the resampling operation.
+*     badval
+*        If the AST__USEBAD flag is set in the "flags" value (above),
+*        this parameter specifies the value which is used to identify
+*        bad data and/or variance values in the input array(s). Its
+*        numerical type must match that of the "in" (and "in_var")
+*        arrays. Unles the AST__NOBAD flag is specified in "flags", the
+*        same value will also be used to flag any output array elements
+*        for which resampled values could not be obtained.  The output
+*        arrays(s) may be flagged with this value whether or not the
+*        AST__USEBAD flag is set (the function return value indicates
+*        whether any such values have been produced).
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the resampled data will be returned. Note
+*        that details of how the output grid maps on to this array
+*        (e.g. the storage order, number of dimensions, etc.) is
+*        arbitrary and is specified entirely by means of the "offset"
+*        array. The "out" array should therefore contain sufficient
+*        elements to accommodate the "offset" values supplied.  There
+*        is no requirement that all elements of the "out" array should
+*        be assigned values, and any which are not addressed by the
+*        contents of the "offset" array will be left unchanged.
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the resampled values may be returned. This array will only be
+*        used if the "in_var" array has been given. It is addressed in
+*        exactly the same way (via the "offset" array) as the "out"
+*        array. The values returned are estimates of the statistical
+*        variance of the corresponding values in the "out" array, on
+*        the assumption that all errors in input grid values (in the
+*        "in" array) are statistically independent and that their
+*        variance estimates (in the "in_var" array) may simply be
+*        summed (with appropriate weighting factors).
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+
+*  Returned Value:
+*     The number of output grid points for which no valid output value
+*     could be obtained.
+
+*  Notes:
+*     - There is a separate function for each numerical type of
+*     gridded data, distinguished by replacing the <X> in the function
+*     name by the appropriate 1- or 2-character suffix.
+*     - A value of zero will be returned if any of these functions is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+/* Define macros to implement the function for a specific data
+   type. */
+#define MAKE_INTERPOLATE_KERNEL1(X,Xtype,Xfloating,Xfloattype,Xsigned) \
+static int InterpolateKernel1##X( AstMapping *this, int ndim_in, \
+                                  const int *lbnd_in, const int *ubnd_in, \
+                                  const Xtype *in, const Xtype *in_var, \
+                                  int npoint, const int *offset, \
+                                  const double *const *coords, \
+                                  void (* kernel)( double, const double [], \
+                                                   int, double *, int * ), \
+                                  void (* fkernel)( double, const double [], \
+                                                    int, double * ), \
+                                  int neighb, const double *params, \
+                                  int flags, Xtype badval, \
+                                  Xtype *out, Xtype *out_var, int *status ) { \
+\
+/* Local Variables: */ \
+   astDECLARE_GLOBALS            /* Thread-specific data */ \
+   Xfloattype hi_lim;            /* Upper limit on output values */ \
+   Xfloattype lo_lim;            /* Lower limit on output values */ \
+   Xfloattype sum;               /* Weighted sum of pixel data values */ \
+   Xfloattype sum_var;           /* Weighted sum of pixel variance values */ \
+   Xfloattype val;               /* Data value to be assigned to output */ \
+   Xfloattype val_var;           /* Variance to be assigned to output */ \
+   Xfloattype wtsum;             /* Sum of weight values */ \
+   Xfloattype wtsum_sq;          /* Square of sum of weights */ \
+   Xtype var;                    /* Variance value */ \
+   double **wtptr;               /* Pointer to array of weight pointers */ \
+   double **wtptr_last;          /* Array of highest weight pointer values */ \
+   double *kval;                 /* Pointer to array of kernel values */ \
+   double *wtprod;               /* Accumulated weight value array pointer */ \
+   double *xn_max;               /* Pointer to upper limits array (n-d) */ \
+   double *xn_min;               /* Pointer to lower limits array (n-d) */ \
+   double pixwt;                 /* Weight to apply to individual pixel */ \
+   double wt_y;                  /* Value of y-dependent pixel weight */ \
+   double x;                     /* x coordinate value */ \
+   double xmax;                  /* x upper limit */ \
+   double xmin;                  /* x lower limit */ \
+   double xn;                    /* Coordinate value (n-d) */ \
+   double y;                     /* y coordinate value */ \
+   double ymax;                  /* y upper limit */ \
+   double ymin;                  /* y lower limit */ \
+   int *hi;                      /* Pointer to array of upper indices */ \
+   int *lo;                      /* Pointer to array of lower indices */ \
+   int *stride;                  /* Pointer to array of dimension strides */ \
+   int bad;                      /* Output pixel bad? */ \
+   int bad_var;                  /* Output variance bad? */ \
+   int done;                     /* All pixel indices done? */ \
+   int hi_x;                     /* Upper pixel index (x dimension) */ \
+   int hi_y;                     /* Upper pixel index (y dimension) */ \
+   int idim;                     /* Loop counter for dimensions */ \
+   int ii;                       /* Loop counter for dimensions */ \
+   int ix;                       /* Pixel index in input grid x dimension */ \
+   int ixn;                      /* Pixel index in input grid (n-d) */ \
+   int iy;                       /* Pixel index in input grid y dimension */ \
+   int kerror;                   /* Error signalled by kernel function? */ \
+   int lo_x;                     /* Lower pixel index (x dimension) */ \
+   int lo_y;                     /* Lower pixel index (y dimension) */ \
+   int nobad;                    /* Was the AST__NOBAD flag set? */ \
+   int off1;                     /* Input pixel offset due to y index */ \
+   int off_in;                   /* Offset to input pixel */ \
+   int off_out;                  /* Offset to output pixel */ \
+   int pixel;                    /* Offset to input pixel containing point */ \
+   int point;                    /* Loop counter for output points */ \
+   int result;                   /* Result value to return */ \
+   int s;                        /* Temporary variable for strides */ \
+   int usebad;                   /* Use "bad" input pixel values? */ \
+   int usevar;                   /* Process variance array? */ \
+   int ystride;                  /* Stride along input grid y dimension */ \
+\
+/* Initialise. */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Get a pointer to a structure holding thread-specific global data values */ \
+   astGET_GLOBALS(this); \
+\
+/* Further initialisation. */ \
+   kerror = 0; \
+   sum_var = 0; \
+   val = 0; \
+   val_var = 0; \
+   wtsum = 0; \
+   bad = 0; \
+   bad_var = 0; \
+   sum = 0.0; \
+\
+/* Determine if we are processing bad pixels or variances. */ \
+   nobad = flags & AST__NOBAD; \
+   usebad = flags & AST__USEBAD; \
+   usevar = in_var && out_var; \
+\
+/* Set up limits for checking output values to ensure that they do not \
+   overflow the range of the data type being used. */ \
+   lo_lim = LO_##X; \
+   hi_lim = HI_##X; \
+\
+/* Handle the 1-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   if ( ndim_in == 1 ) { \
+\
+/* Calculate the coordinate limits of the input grid. */ \
+      xmin = (double) lbnd_in[ 0 ] - 0.5; \
+      xmax = (double) ubnd_in[ 0 ] + 0.5; \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+      if ( nobad ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,1) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,1) \
+               } \
+            } \
+         } \
+\
+/* Four more cases as above, but this time with the AST__NOBAD flag \
+   un-set. */ \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,0) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,0) \
+               } \
+            } \
+         } \
+      } \
+\
+/* Exit point on error in kernel function */ \
+      Kernel_Error_1d: ; \
+\
+/* Handle the 2-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   } else if ( ndim_in == 2 ) { \
+\
+/* Allocate workspace. */ \
+      kval = astMalloc( sizeof( double ) * (size_t) ( 2 * neighb ) ); \
+      if ( astOK ) { \
+\
+/* Calculate the stride along the y dimension of the input grid. */ \
+         ystride = ubnd_in[ 0 ] - lbnd_in[ 0 ] + 1; \
+\
+/* Calculate the coordinate limits of the input grid in each \
+   dimension. */ \
+         xmin = (double) lbnd_in[ 0 ] - 0.5; \
+         xmax = (double) ubnd_in[ 0 ] + 0.5; \
+         ymin = (double) lbnd_in[ 1 ] - 0.5; \
+         ymax = (double) ubnd_in[ 1 ] + 0.5; \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+         if ( nobad ) { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,1) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,1) \
+                  } \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,1) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,1) \
+                  } \
+               } \
+            } \
+\
+/* Another four cases, as above, but this time without the AST__NOBAD \
+   flag. */ \
+         } else { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,0) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,0) \
+                  } \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,0) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,0) \
+                  } \
+               } \
+            } \
+         } \
+\
+/* Exit point on error in kernel function */ \
+         Kernel_Error_2d: ; \
+      } \
+\
+/* Free the workspace. */ \
+      kval = astFree( kval ); \
+\
+/* Handle other numbers of dimensions. */ \
+/* ----------------------------------- */ \
+   } else { \
+\
+/* Allocate workspace. */ \
+      hi = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      lo = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      stride = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      xn_max = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      xn_min = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      kval = astMalloc( sizeof( double ) * (size_t) \
+                                           ( 2 * neighb * ndim_in ) ); \
+      wtprod = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      wtptr = astMalloc( sizeof( double * ) * (size_t) ndim_in ); \
+      wtptr_last = astMalloc( sizeof( double * ) * (size_t) ndim_in ); \
+      if ( astOK ) { \
+\
+/* Calculate the stride along each dimension of the input grid. */ \
+         for ( s = 1, idim = 0; idim < ndim_in; idim++ ) { \
+            stride[ idim ] = s; \
+            s *= ubnd_in[ idim ] - lbnd_in[ idim ] + 1; \
+\
+/* Calculate the coordinate limits of the input grid in each \
+   dimension. */ \
+            xn_min[ idim ] = (double) lbnd_in[ idim ] - 0.5; \
+            xn_max[ idim ] = (double) ubnd_in[ idim ] + 0.5; \
+         } \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+         if( nobad ) { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,1) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,1) \
+                  } \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,1) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,1) \
+                  } \
+               } \
+            } \
+\
+/* Another 4 cases as above, but this time with the AST__NOBAD flag \
+   un-set. */ \
+         } else { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,0) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,0) \
+                  } \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,0) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,0) \
+                  } \
+               } \
+            } \
+         } \
+\
+/* Exit point on error in kernel function */ \
+         Kernel_Error_Nd: ;\
+      } \
+\
+/* Free the workspace. */ \
+      hi = astFree( hi ); \
+      lo = astFree( lo ); \
+      stride = astFree( stride ); \
+      xn_max = astFree( xn_max ); \
+      xn_min = astFree( xn_min ); \
+      kval = astFree( kval ); \
+      wtprod = astFree( wtprod ); \
+      wtptr = astFree( wtptr ); \
+      wtptr_last = astFree( wtptr_last ); \
+   } \
+\
+/* If an error occurred in the kernel function, then report a \
+   contextual error message. */ \
+   if ( kerror ) { \
+      astError( astStatus, "astResample"#X"(%s): Error signalled by " \
+                "user-supplied 1-d interpolation kernel.", status, \
+                astGetClass( unsimplified_mapping ) ); \
+   } \
+\
+/* If an error has occurred, clear the returned result. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the 1-dimensional
+   case. */
+#define ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,Usebad,Usevar) \
+\
+/* Obtain the x coordinate of the current point and test if it lies \
+   outside the input grid, or is bad. */ \
+   x = coords[ 0 ][ point ]; \
+   bad = ( x < xmin ) || ( x >= xmax ) || ( x == AST__BAD ); \
+   if ( !bad ) { \
+\
+/* If input bad pixels must be detected, then obtain the offset along \
+   the input grid x dimension of the input pixel which contains the \
+   current coordinate, and calculate this pixel's offset from the \
+   start of the input array. */ \
+      if ( Usebad ) { \
+         pixel = (int) floor( x + 0.5 ) - lbnd_in[ 0 ]; \
+\
+/* Test if the pixel is bad. */ \
+         bad = ( in[ pixel ] == badval ); \
+      } \
+\
+/* If OK, calculate the lowest and highest indices (in the x \
+   dimension) of the region of neighbouring pixels that will \
+   contribute to the interpolated result. Constrain these values to \
+   lie within the input grid. */ \
+      if ( !bad ) { \
+         ix = (int) floor( x ); \
+         lo_x = MaxI( ix - neighb + 1, lbnd_in[ 0 ], status ); \
+         hi_x = MinI( ix + neighb,     ubnd_in[ 0 ], status ); \
+\
+/* Initialise sums for forming the interpolated result. */ \
+         sum = (Xfloattype) 0.0; \
+         wtsum = (Xfloattype) 0.0; \
+         if ( Usevar ) { \
+            sum_var = (Xfloattype) 0.0; \
+            bad_var = 0; \
+         } \
+\
+/* Loop to inspect all the contributing pixels, calculating the offset \
+   of each pixel from the start of the input array. */ \
+         off_in = lo_x - lbnd_in[ 0 ]; \
+         for ( ix = lo_x; ix <= hi_x; ix++, off_in++ ) { \
+\
+/* If necessary, test if the input pixel is bad. If not, calculate its \
+   weight by evaluating the kernel function. */ \
+            if ( !( Usebad ) || ( in[ off_in ] != badval ) ) { \
+               if( kernel ) { \
+                  ( *kernel )( (double) ix - x, params, flags, &pixwt, status ); \
+               } else { \
+                  ( *fkernel )( (double) ix - x, params, flags, &pixwt ); \
+               } \
+\
+/* Check for errors arising in the kernel function. */ \
+               if ( !astOK ) { \
+                  kerror = 1; \
+                  goto Kernel_Error_1d; \
+               } \
+\
+/* Form the weighted sums required for finding the interpolated \
+   value. */ \
+               sum += ( (Xfloattype) pixwt ) * ( (Xfloattype) in[ off_in ] ); \
+               wtsum += (Xfloattype) pixwt; \
+\
+/* If a variance estimate is required and it still seems possible to \
+   obtain one, then obtain the variance value associated with the \
+   current input pixel. */ \
+               if ( Usevar ) { \
+                  if ( !( ( Xsigned ) || ( Usebad ) ) || !bad_var ) { \
+                     var = in_var[ off_in ]; \
+\
+/* If necessary, test if this value is bad (if the data type is \
+   signed, also check that it is not negative). */ \
+                     if ( Usebad ) bad_var = ( var == badval ); \
+                     CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+\
+/* If any bad input variance value is obtained, we cannot generate a \
+   valid output variance estimate. Otherwise, form the sum needed to \
+   calculate this estimate. */ \
+                     if ( !( ( Xsigned ) || ( Usebad ) ) || !bad_var ) { \
+                        sum_var += ( (Xfloattype) ( pixwt * pixwt ) ) * \
+                                   ( (Xfloattype) var ); \
+                     } \
+                  } \
+               } \
+            } \
+         } \
+      } \
+   }
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the 2-dimensional
+   case. */
+#define ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,Usebad,Usevar) \
+\
+/* Obtain the x coordinate of the current point and test if it lies \
+   outside the input grid, or is bad. */ \
+   x = coords[ 0 ][ point ]; \
+   bad = ( x < xmin ) || ( x >= xmax ) || ( x == AST__BAD ); \
+   if ( !bad ) { \
+\
+/* If not, then similarly obtain and test the y coordinate. */ \
+      y = coords[ 1 ][ point ]; \
+      bad = ( y < ymin ) || ( y >= ymax ) || ( y == AST__BAD ); \
+      if ( !bad ) { \
+\
+/* If input bad pixels must be detected, then obtain the offsets along \
+   each input grid dimension of the input pixel which contains the \
+   current coordinates, and calculate this pixel's offset from the \
+   start of the input array. */ \
+         if ( Usebad ) { \
+            ix = (int) floor( x + 0.5 ); \
+            iy = (int) floor( y + 0.5 ); \
+            pixel = ix - lbnd_in[ 0 ] + ystride * ( iy - lbnd_in[ 1 ] ); \
+\
+/* Test if the pixel is bad. */ \
+            bad = ( in[ pixel ] == badval ); \
+         } \
+\
+/* If OK, calculate the lowest and highest indices (in each dimension) \
+   of the region of neighbouring pixels that will contribute to the \
+   interpolated result. Constrain these values to lie within the input \
+   grid. */ \
+         if ( !bad ) { \
+            ix = (int) floor( x ); \
+            lo_x = MaxI( ix - neighb + 1, lbnd_in[ 0 ], status ); \
+            hi_x = MinI( ix + neighb,     ubnd_in[ 0 ], status ); \
+            iy = (int) floor( y ); \
+            lo_y = MaxI( iy - neighb + 1, lbnd_in[ 1 ], status ); \
+            hi_y = MinI( iy + neighb,     ubnd_in[ 1 ], status ); \
+\
+/* Loop to evaluate the kernel function along the x dimension, storing \
+   the resulting values. The function's argument is the offset of the \
+   contributing pixel (along this dimension) from the input \
+   position. */ \
+            for ( ix = lo_x; ix <= hi_x; ix++ ) { \
+               if( kernel ) { \
+                  ( *kernel )( (double) ix - x, params, flags, \
+                               kval + ix - lo_x, status ); \
+               } else { \
+                  ( *fkernel )( (double) ix - x, params, flags, \
+                               kval + ix - lo_x ); \
+               } \
+\
+/* Check for errors arising in the kernel function. */ \
+               if ( !astOK ) { \
+                  kerror = 1; \
+                  goto Kernel_Error_2d; \
+               } \
+            } \
+\
+/* Initialise sums for forming the interpolated result. */ \
+            sum = (Xfloattype) 0.0; \
+            wtsum = (Xfloattype) 0.0; \
+            if ( Usevar ) { \
+               sum_var = (Xfloattype) 0.0; \
+               bad_var = 0; \
+            } \
+\
+/* Loop over the y index to inspect all the contributing pixels, while \
+   keeping track of their offset within the input array. Evaluate the \
+   kernel function for each y index value. */ \
+            off1 = lo_x - lbnd_in[ 0 ] + ystride * ( lo_y - lbnd_in[ 1 ] ); \
+            for ( iy = lo_y; iy <= hi_y; iy++, off1 += ystride ) { \
+               if( kernel ) { \
+                  ( *kernel )( (double) iy - y, params, flags, &wt_y, status ); \
+               } else { \
+                  ( *fkernel )( (double) iy - y, params, flags, &wt_y ); \
+               } \
+\
+/* Check for errors arising in the kernel function. */ \
+               if ( !astOK ) { \
+                  kerror = 1; \
+                  goto Kernel_Error_2d; \
+               } \
+\
+/* Loop over the x index, calculating the pixel offset in the input \
+   array. */ \
+               off_in = off1; \
+               for ( ix = lo_x; ix <= hi_x; ix++, off_in++ ) { \
+\
+/* If necessary, test if the input pixel is bad. If not, calculate its \
+   weight as the product of the kernel function's value for the x and \
+   y dimensions. */ \
+                  if ( !( Usebad ) || ( in[ off_in ] != badval ) ) { \
+                     pixwt = kval[ ix - lo_x ] * wt_y; \
+\
+/* Form the weighted sums required for finding the interpolated \
+   value. */ \
+                     sum += ( (Xfloattype) pixwt ) * \
+                            ( (Xfloattype) in[ off_in ] ); \
+                     wtsum += (Xfloattype) pixwt; \
+\
+/* If a variance estimate is required and it still seems possible to \
+   obtain one, then obtain the variance value associated with the \
+   current input pixel. */ \
+                     if ( Usevar ) { \
+                        if ( !( ( Xsigned ) || ( Usebad ) ) || !bad_var ) { \
+                           var = in_var[ off_in ]; \
+\
+/* If necessary, test if this value is bad (if the data type is \
+   signed, also check that it is not negative). */ \
+                           if ( Usebad ) bad_var = ( var == badval ); \
+                           CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+\
+/* If any bad input variance value is obtained, we cannot generate a \
+   valid output variance estimate. Otherwise, form the sum needed to \
+   calculate this estimate. */ \
+                           if ( !( ( Xsigned ) || ( Usebad ) ) || \
+                                !bad_var ) { \
+                              sum_var += ( (Xfloattype) ( pixwt * pixwt ) ) * \
+                                         ( (Xfloattype) var ); \
+                           } \
+                        } \
+                     } \
+                  } \
+               } \
+            } \
+         } \
+      } \
+   }
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the n-dimensional
+   case. */
+#define ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,Usebad,Usevar) \
+\
+/* Initialise offsets into the input array. Then loop to obtain each \
+   coordinate associated with the current output point. */ \
+   pixel = 0; \
+   off_in = 0; \
+   for ( idim = 0; idim < ndim_in; idim++ ) { \
+      xn = coords[ idim ][ point ]; \
+\
+/* Test if the coordinate lies outside the input grid, or is bad. If \
+   either is true, the corresponding output pixel value will be bad, \
+   so give up on this point. */ \
+      bad = ( xn < xn_min[ idim ] ) || ( xn >= xn_max[ idim ] ) || \
+            ( xn == AST__BAD ); \
+      if ( bad ) break; \
+\
+/* If input bad pixels must be detected, then obtain the index along \
+   the current input grid dimension of the pixel which contains this \
+   coordinate and accumulate the pixel's offset from the start of the \
+   input array. */ \
+      if ( Usebad ) { \
+         pixel += stride[ idim ] * \
+                  ( (int) floor( xn + 0.5 ) - lbnd_in[ idim ] ); \
+      } \
+\
+/* Calculate the lowest and highest indices (in the current dimension) \
+   of the region of neighbouring pixels that will contribute to the \
+   interpolated result. Constrain these values to lie within the input \
+   grid. */ \
+      ixn = (int) floor( xn ); \
+      lo[ idim ] = MaxI( ixn - neighb + 1, lbnd_in[ idim ], status ); \
+      hi[ idim ] = MinI( ixn + neighb,     ubnd_in[ idim ], status ); \
+\
+/* Accumulate the offset (from the start of the input array) of the \
+   contributing pixel which has the lowest index in each dimension. */ \
+      off_in += stride[ idim ] * ( lo[ idim ] - lbnd_in[ idim ] ); \
+   } \
+\
+/* Once the input pixel which contains the required coordinates has \
+   been identified, test if it is bad, if necessary. */ \
+   if ( Usebad ) bad = bad || ( in[ pixel ] == badval ); \
+\
+/* If OK, loop to evaluate the kernel function which will be used to \
+   weight the contributions from surrounding pixels. */ \
+   if ( !bad ) { \
+      for ( idim = 0; idim < ndim_in; idim++ ) { \
+\
+/* Set up an array of pointers to locate kernel values (stored in the \
+   "kval" array) for each dimension. Initially, each of these pointers \
+   locates the first weight value which applies to the contributing \
+   pixel with the lowest index in each dimension. */ \
+         wtptr[ idim ] = kval + ( 2 * neighb * idim ); \
+\
+/* Also set up pointers to the last weight value in each dimension. */ \
+         wtptr_last[ idim ] = wtptr[ idim ] + ( hi[ idim ] - lo[ idim ] ); \
+\
+/* Loop to evaluate the kernel function along each dimension, storing \
+   the resulting values. The function's argument is the offset of the \
+   contributing pixel (along the relevant dimension) from the input \
+   point. */ \
+         xn = coords[ idim ][ point ]; \
+         for ( ixn = lo[ idim ]; ixn <= hi[ idim ]; ixn++ ) { \
+            if( kernel ) { \
+               ( *kernel )( (double) ixn - xn, params, flags, \
+                            wtptr[ idim ] + ixn - lo[ idim ], status ); \
+            } else { \
+               ( *fkernel )( (double) ixn - xn, params, flags, \
+                             wtptr[ idim ] + ixn - lo[ idim ] ); \
+            } \
+\
+/* Check for errors arising in the kernel function. */ \
+            if ( !astOK ) { \
+               kerror = 1; \
+               goto Kernel_Error_Nd; \
+            } \
+         } \
+      } \
+\
+/* Initialise, and loop over the neighbouring input pixels to obtain \
+   an interpolated value. */ \
+      sum = (Xfloattype) 0.0; \
+      wtsum = (Xfloattype) 0.0; \
+      if ( Usevar ) { \
+         sum_var = (Xfloattype) 0.0; \
+         bad_var = 0; \
+      } \
+      idim = ndim_in - 1; \
+      wtprod[ idim ] = 1.0; \
+      done = 0; \
+      do { \
+\
+/* Each contributing pixel is weighted by the product of the kernel \
+   weight factors evaluated along each input dimension. However, since \
+   we typically only change the index of one dimension at a time, we \
+   can avoid forming this product repeatedly by retaining an array of \
+   accumulated products for all higher dimensions. We need then only \
+   update the lower elements in this array, corresponding to those \
+   dimensions whose index has changed. We do this here, "idim" being \
+   the index of the most significant dimension to have changed. Note \
+   that on the first pass, all dimensions are considered changed, \
+   causing this array to be initialised. */ \
+         for ( ii = idim; ii >= 1; ii-- ) { \
+            wtprod[ ii - 1 ] = wtprod[ ii ] * *( wtptr[ ii ] ); \
+         } \
+\
+/* If necessary, test each pixel which may contribute to the result to \
+   see if it is bad. If not, obtain its weight from the accumulated \
+   product of weights. Also multiply by the weight for dimension zero, \
+   which is not included in the "wtprod" array). */ \
+         if ( !( Usebad ) || ( in[ off_in ] != badval ) ) { \
+            pixwt = wtprod[ 0 ] * *( wtptr[ 0 ] ); \
+\
+/* Form the weighted sums required for finding the interpolated \
+   value. */ \
+            sum += ( (Xfloattype) pixwt ) * ( (Xfloattype) in[ off_in ] ); \
+            wtsum += (Xfloattype) pixwt; \
+\
+/* If a variance estimate is required and it still seems possible to \
+   obtain one, then obtain the variance value associated with the \
+   current input pixel. */ \
+            if ( Usevar ) { \
+               if ( !( ( Xsigned ) || ( Usebad ) ) || !bad_var ) { \
+                  var = in_var[ off_in ]; \
+\
+/* If necessary, test if this value is bad (if the data type is \
+   signed, also check that it is not negative). */ \
+                  if ( Usebad ) bad_var = ( var == badval ); \
+                  CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+\
+/* If any bad input variance value is obtained, we cannot generate a \
+   valid output variance estimate. Otherwise, form the sum needed to \
+   calculate this estimate. */ \
+                  if ( !( ( Xsigned ) || ( Usebad ) ) || !bad_var ) { \
+                     sum_var += ( (Xfloattype) ( pixwt * pixwt ) ) * \
+                                ( (Xfloattype) var ); \
+                  } \
+               } \
+            } \
+         } \
+\
+/* Now update the weight value pointers and pixel offset to refer to \
+   the next input pixel to be considered. */ \
+         idim = 0; \
+         do { \
+\
+/* The first input dimension whose weight value pointer has not yet \
+   reached its final value has this pointer incremented, and the pixel \
+   offset into the input array is updated accordingly. */ \
+            if ( wtptr[ idim ] != wtptr_last[ idim ] ) { \
+               wtptr[ idim ]++; \
+               off_in += stride[ idim ]; \
+               break; \
+\
+/* Any earlier dimensions (which have reached the final pointer value) \
+   have this pointer returned to its lowest value. Again, the pixel \
+   offset into the input image is updated accordingly. */ \
+            } else { \
+               wtptr[ idim ] -= ( hi[ idim ] - lo[ idim ] ); \
+               off_in -= stride[ idim ] * \
+                               ( hi[ idim ] - lo[ idim ] ); \
+               done = ( ++idim == ndim_in ); \
+            } \
+         } while ( !done ); \
+      } while ( !done ); \
+   }
+
+/* This subsidiary macro calculates the interpolated output value (and
+   variance) from the sums over contributing pixels, checks the
+   results for validity, and assigns them to the output array(s). */
+#define CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Usebad,Usevar,Nobad) \
+\
+/* If the output data value has not yet been flagged as bad, then \
+   check that an interpolated value can actually be produced.  First \
+   check that the sum of weights is not zero. */ \
+   if ( !bad ) { \
+      bad = ( wtsum == (Xfloattype) 0.0 ); \
+\
+/* If OK, calculate the interpolated value. Then, if the output data \
+   type is not floating point, check that this value will not overflow \
+   the available output range. */ \
+      if ( !bad ) { \
+         val = sum / wtsum; \
+         if ( !( Xfloating ) ) { \
+            bad = ( val <= lo_lim ) || ( val >= hi_lim ); \
+         } \
+      } \
+\
+/* If no interpolated data value can be produced, then no associated \
+   variance will be required either. */ \
+      if ( ( Usevar ) && bad ) bad_var = 1; \
+   } \
+\
+/* Now perform similar checks on the output variance value (if \
+   required). This time we check that the square of the sum of \
+   weights is not zero (since this might underflow before the sum of \
+   weights). Again we also check to prevent the result overflowing the \
+   output data type. */ \
+   if ( ( Usevar ) && !bad_var ) { \
+      wtsum_sq = wtsum * wtsum; \
+      bad_var = ( wtsum_sq == (Xfloattype) 0.0 ); \
+      if ( !bad_var ) { \
+         val_var = sum_var / wtsum_sq; \
+         if ( !( Xfloating ) ) { \
+            bad_var = ( val_var <= lo_lim ) || ( val_var >= hi_lim ); \
+         } \
+      } \
+   } \
+\
+/* Obtain the pixel offset into the output array. */ \
+   off_out = offset[ point ]; \
+\
+/* Assign a bad output value (and variance) if required and count it. */ \
+   if ( bad ) { \
+      if( !Nobad ) { \
+         out[ off_out ] = badval; \
+         if ( Usevar ) out_var[ off_out ] = badval; \
+      } \
+      result++; \
+\
+/* Otherwise, assign the interpolated value. If the output data type \
+   is floating point, the result can be stored directly, otherwise we \
+   must round to the nearest integer. */ \
+   } else { \
+      if ( Xfloating ) { \
+         out[ off_out ] = (Xtype) val; \
+      } else { \
+         out[ off_out ] = (Xtype) ( val + ( ( val >= (Xfloattype) 0.0 ) ? \
+                                            ( (Xfloattype) 0.5 ) : \
+                                            ( (Xfloattype) -0.5 ) ) ); \
+      } \
+\
+/* If a variance estimate is required but none can be obtained, then \
+   store a bad output variance value and count it. */ \
+      if ( Usevar ) { \
+         if ( bad_var ) { \
+            if( !Nobad ) { \
+               out_var[ off_out ] = badval; \
+            } \
+            result++; \
+\
+/* Otherwise, store the variance estimate, rounding to the nearest \
+   integer if necessary. */ \
+         } else { \
+            if ( Xfloating ) { \
+               out_var[ off_out ] = (Xtype) val_var; \
+            } else { \
+               out_var[ off_out ] = (Xtype) ( val_var + \
+                                          ( ( val_var >= (Xfloattype) 0.0 ) ? \
+                                            ( (Xfloattype) 0.5 ) : \
+                                            ( (Xfloattype) -0.5 ) ) ); \
+            } \
+         } \
+      } \
+   }
+
+/* These subsidiary macros define limits for range checking of results
+   before conversion to the final data type. For each data type code
+   <X>, HI_<X> gives the least positive floating point value which
+   just overflows that data type towards plus infinity, while LO_<X>
+   gives the least negative floating point value which just overflows
+   that data type towards minus infinity. Thus, a floating point value
+   must satisfy LO<flt_value<HI if overflow is not to occur when it is
+   converted to that data type.
+
+   The data type of each limit should be that of the smallest
+   precision floating point type which will accommodate the full range
+   of values that the target type may take. */
+
+/* If <X> is a floating point type, the limits are not actually used,
+   but must be present to permit error-free compilation. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+#define HI_LD ( 0.0L )
+#define LO_LD ( 0.0L )
+#endif
+#define HI_D ( 0.0 )
+#define LO_D ( 0.0 )
+#define HI_F ( 0.0f )
+#define LO_F ( 0.0f )
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+#define HI_K   ( 0.5L + (long double) LONG_MAX )
+#define LO_K  ( -0.5L + (long double) LONG_MIN )
+#define HI_UK  ( 0.5L + (long double) ULONG_MAX )
+#define LO_UK ( -0.5L )
+#define HI_L   ( 0.5L + (long double) LONG_MAX )
+#define LO_L  ( -0.5L + (long double) LONG_MIN )
+#define HI_UL  ( 0.5L + (long double) ULONG_MAX )
+#define LO_UL ( -0.5L )
+#else
+#define HI_K   ( 0.5 + (double) LONG_MAX )
+#define LO_K  ( -0.5 + (double) LONG_MIN )
+#define HI_UK  ( 0.5 + (double) ULONG_MAX )
+#define LO_UK ( -0.5 )
+#define HI_L   ( 0.5 + (double) LONG_MAX )
+#define LO_L  ( -0.5 + (double) LONG_MIN )
+#define HI_UL  ( 0.5 + (double) ULONG_MAX )
+#define LO_UL ( -0.5 )
+#endif
+
+#define HI_I   ( 0.5 + (double) INT_MAX )
+#define LO_I  ( -0.5 + (double) INT_MIN )
+#define HI_UI  ( 0.5 + (double) UINT_MAX )
+#define LO_UI ( -0.5 )
+#define HI_S   ( 0.5f + (float) SHRT_MAX )
+#define LO_S  ( -0.5f + (float) SHRT_MIN )
+#define HI_US  ( 0.5f + (float) USHRT_MAX )
+#define LO_US ( -0.5f )
+#define HI_B   ( 0.5f + (float) SCHAR_MAX )
+#define LO_B  ( -0.5f + (float) SCHAR_MIN )
+#define HI_UB  ( 0.5f + (float) UCHAR_MAX )
+#define LO_UB ( -0.5f )
+
+/* This subsidiary macro tests for negative variance values. This
+   check is required only for signed data types. */
+#define CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+   bad_var = bad_var || ( var < ( (Xtype) 0 ) );
+
+/* Expand the main macro above to generate a function for each
+   required signed data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_INTERPOLATE_KERNEL1(LD,long double,1,long double,1)
+MAKE_INTERPOLATE_KERNEL1(L,long int,0,long double,1)
+MAKE_INTERPOLATE_KERNEL1(K,INT_BIG,0,long double,1)
+#else
+MAKE_INTERPOLATE_KERNEL1(L,long int,0,double,1)
+MAKE_INTERPOLATE_KERNEL1(K,INT_BIG,0,double,1)
+#endif
+MAKE_INTERPOLATE_KERNEL1(D,double,1,double,1)
+MAKE_INTERPOLATE_KERNEL1(F,float,1,float,1)
+MAKE_INTERPOLATE_KERNEL1(I,int,0,double,1)
+MAKE_INTERPOLATE_KERNEL1(S,short int,0,float,1)
+MAKE_INTERPOLATE_KERNEL1(B,signed char,0,float,1)
+
+/* Re-define the macro for testing for negative variances to do
+   nothing. */
+#undef CHECK_FOR_NEGATIVE_VARIANCE
+#define CHECK_FOR_NEGATIVE_VARIANCE(Xtype)
+
+/* Expand the main macro above to generate a function for each
+   required unsigned data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_INTERPOLATE_KERNEL1(UL,unsigned long int,0,long double,0)
+MAKE_INTERPOLATE_KERNEL1(UK,UINT_BIG,0,long double,0)
+#else
+MAKE_INTERPOLATE_KERNEL1(UL,unsigned long int,0,double,0)
+MAKE_INTERPOLATE_KERNEL1(UK,UINT_BIG,0,double,0)
+#endif
+MAKE_INTERPOLATE_KERNEL1(UI,unsigned int,0,double,0)
+MAKE_INTERPOLATE_KERNEL1(US,unsigned short int,0,float,0)
+MAKE_INTERPOLATE_KERNEL1(UB,unsigned char,0,float,0)
+
+/* Undefine the macros used above. */
+#undef CHECK_FOR_NEGATIVE_VARIANCE
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+#undef HI_LD
+#undef LO_LD
+#endif
+#undef HI_D
+#undef LO_D
+#undef HI_F
+#undef LO_F
+#undef HI_L
+#undef LO_L
+#undef HI_UL
+#undef LO_UL
+#undef HI_K
+#undef LO_K
+#undef HI_UK
+#undef LO_UK
+#undef HI_I
+#undef LO_I
+#undef HI_UI
+#undef LO_UI
+#undef HI_S
+#undef LO_S
+#undef HI_US
+#undef LO_US
+#undef HI_B
+#undef LO_B
+#undef HI_UB
+#undef LO_UB
+#undef CALC_AND_ASSIGN_OUTPUT
+#undef ASSEMBLE_INPUT_ND
+#undef ASSEMBLE_INPUT_2D
+#undef ASSEMBLE_INPUT_1D
+#undef MAKE_INTERPOLATE_KERNEL1
+
+/*
+*  Name:
+*     InterpolateLinear<X>
+
+*  Purpose:
+*     Resample a data grid, using the linear interpolation scheme.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int InterpolateLinear<X>( int ndim_in,
+*                               const int *lbnd_in, const int *ubnd_in,
+*                               const <Xtype> *in, const <Xtype> *in_var,
+*                               int npoint, const int *offset,
+*                               const double *const *coords,
+*                               int flags, <Xtype> badval,
+*                               <Xtype> *out, <Xtype> *out_var )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This is a set of functions which resample a rectangular input
+*     grid of data (and, optionally, associated statistical variance
+*     values) so as to place them into a new output grid. Each output
+*     grid point may be mapped on to a position in the input grid in
+*     an arbitrary way. Where the positions given do not correspond
+*     with a pixel centre in the input grid, the interpolation scheme
+*     used is linear interpolation between the centres of the nearest
+*     neighbouring pixels in each dimension (there are 2 nearest
+*     neighbours in 1 dimension, 4 in 2 dimensions, 8 in 3 dimensions,
+*     etc.).
+
+*  Parameters:
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input grid, its extent along a particular
+*        (i'th) dimension being ubnd_in[i]-lbnd_in[i]+1 (assuming "i"
+*        is zero-based). They also define the input grid's coordinate
+*        system, with each pixel being of unit extent along each
+*        dimension with integral coordinate values at its centre.
+*     in
+*        Pointer to the array of data to be resampled (with an element
+*        for each pixel in the input grid). The numerical type of
+*        these data should match the function used, as given by the
+*        suffix on the function name. The storage order should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+*        (i.e. Fortran array storage order).
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and type as the "in" array), which
+*        represent estimates of the statistical variance associated
+*        with each element of the "in" array. If this second array is
+*        given (along with the corresponding "out_var" array), then
+*        estimates of the variance of the resampled data will also be
+*        returned.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     npoint
+*        The number of points at which the input grid is to be
+*        resampled.
+*     offset
+*        Pointer to an array of integers with "npoint" elements. For
+*        each output point, this array should contain the zero-based
+*        offset in the output array(s) (i.e. the "out" and,
+*        optionally, the "out_var" arrays) at which the resampled
+*        output value(s) should be stored.
+*     coords
+*        An array of pointers to double, with "ndim_in"
+*        elements. Element "coords[coord]" should point at the first
+*        element of an array of double (with "npoint" elements) which
+*        contains the values of coordinate number "coord" for each
+*        interpolation point. The value of coordinate number "coord"
+*        for interpolation point number "point" is therefore given by
+*        "coords[coord][point]" (assuming both indices to be
+*        zero-based).  If any point has a coordinate value of AST__BAD
+*        associated with it, then the corresponding output data (and
+*        variance) will be set to the value given by "badval" (unles the
+*        AST__NOBAD flag is specified).
+*     flags
+*        The bitwise OR of a set of flag values which control the
+*        operation of the function. Currently, only the flag
+*        AST__USEBAD is significant and indicates whether there are
+*        "bad" (i.e. missing) data in the input array(s) which must be
+*        recognised and propagated to the output array(s).  If this
+*        flag is not set, all input values are treated literally.
+*     badval
+*        If the AST__USEBAD flag is set in the "flags" value (above),
+*        this parameter specifies the value which is used to identify
+*        bad data and/or variance values in the input array(s). Its
+*        numerical type must match that of the "in" (and "in_var")
+*        arrays. Unles the AST__NOBAD flag is specified in "flags", the
+*        same value will also be used to flag any output array elements
+*        for which resampled values could not be obtained.  The output
+*        arrays(s) may be flagged with this value whether or not the
+*        AST__USEBAD flag is set (the function return value indicates
+*        whether any such values have been produced).
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the resampled data will be returned. Note
+*        that details of how the output grid maps on to this array
+*        (e.g. the storage order, number of dimensions, etc.) is
+*        arbitrary and is specified entirely by means of the "offset"
+*        array. The "out" array should therefore contain sufficient
+*        elements to accommodate the "offset" values supplied.  There
+*        is no requirement that all elements of the "out" array should
+*        be assigned values, and any which are not addressed by the
+*        contents of the "offset" array will be left unchanged.
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the resampled values may be returned. This array will only be
+*        used if the "in_var" array has been given. It is addressed in
+*        exactly the same way (via the "offset" array) as the "out"
+*        array. The values returned are estimates of the statistical
+*        variance of the corresponding values in the "out" array, on
+*        the assumption that all errors in input grid values (in the
+*        "in" array) are statistically independent and that their
+*        variance estimates (in the "in_var" array) may simply be
+*        summed (with appropriate weighting factors).
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+
+*  Returned Value:
+*     The number of output grid points to which a data value (or a
+*     variance value if relevant) equal to "badval" has been assigned
+*     because no valid output value could be obtained.
+
+*  Notes:
+*     - There is a separate function for each numerical type of
+*     gridded data, distinguished by replacing the <X> in the function
+*     name by the appropriate 1- or 2-character suffix.
+*     - A value of zero will be returned if any of these functions is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+/* Define macros to implement the function for a specific data
+   type. */
+#define MAKE_INTERPOLATE_LINEAR(X,Xtype,Xfloating,Xfloattype,Xsigned) \
+static int InterpolateLinear##X( int ndim_in, \
+                                 const int *lbnd_in, const int *ubnd_in, \
+                                 const Xtype *in, const Xtype *in_var, \
+                                 int npoint, const int *offset, \
+                                 const double *const *coords, \
+                                 int flags, Xtype badval, \
+                                 Xtype *out, Xtype *out_var, int *status ) { \
+\
+/* Local Variables: */ \
+   Xfloattype sum;               /* Weighted sum of pixel data values */ \
+   Xfloattype sum_var;           /* Weighted sum of pixel variance values */ \
+   Xfloattype val;               /* Value to be asigned to output pixel */ \
+   Xfloattype wtsum;             /* Sum of weight values */ \
+   Xtype var;                    /* Variance value */ \
+   double *frac_hi;              /* Pointer to array of weights */ \
+   double *frac_lo;              /* Pointer to array of weights */ \
+   double *wt;                   /* Pointer to array of weights */ \
+   double *wtprod;               /* Array of accumulated weights pointer */ \
+   double *xn_max;               /* Pointer to upper limits array (n-d) */ \
+   double *xn_min;               /* Pointer to lower limits array (n-d) */ \
+   double frac_hi_x;             /* Pixel weight (x dimension) */ \
+   double frac_hi_y;             /* Pixel weight (y dimension) */ \
+   double frac_lo_x;             /* Pixel weight (x dimension) */ \
+   double frac_lo_y;             /* Pixel weight (y dimension) */ \
+   double pixwt;                 /* Weight to apply to individual pixel */ \
+   double x;                     /* x coordinate value */ \
+   double xmax;                  /* x upper limit */ \
+   double xmin;                  /* x lower limit */ \
+   double xn;                    /* Coordinate value (n-d) */ \
+   double y;                     /* y coordinate value */ \
+   double ymax;                  /* y upper limit */ \
+   double ymin;                  /* y lower limit */ \
+   int *dim;                     /* Pointer to array of pixel indices */ \
+   int *hi;                      /* Pointer to array of upper indices */ \
+   int *lo;                      /* Pointer to array of lower indices */ \
+   int *stride;                  /* Pointer to array of dimension strides */ \
+   int bad;                      /* Output pixel bad? */ \
+   int bad_var;                  /* Output variance bad? */ \
+   int done;                     /* All pixel indices done? */ \
+   int hi_x;                     /* Upper pixel index (x dimension) */ \
+   int hi_y;                     /* Upper pixel index (y dimension) */ \
+   int idim;                     /* Loop counter for dimensions */ \
+   int ii;                       /* Loop counter for weights */ \
+   int ix;                       /* Pixel index in input grid x dimension */ \
+   int ixn;                      /* Pixel index (n-d) */ \
+   int iy;                       /* Pixel index in input grid y dimension */ \
+   int lo_x;                     /* Lower pixel index (x dimension) */ \
+   int lo_y;                     /* Lower pixel index (y dimension) */ \
+   int nobad;                    /* Was the AST__NOBAD flag set? */ \
+   int off_in;                   /* Offset to input pixel */ \
+   int off_lo;                   /* Offset to "first" input pixel */ \
+   int off_out;                  /* Offset to output pixel */ \
+   int pixel;                    /* Offset to input pixel containing point */ \
+   int point;                    /* Loop counter for output points */ \
+   int result;                   /* Result value to return */ \
+   int s;                        /* Temporary variable for strides */ \
+   int usebad;                   /* Use "bad" input pixel values? */ \
+   int usevar;                   /* Process variance array? */ \
+   int ystride;                  /* Stride along input grid y dimension */ \
+\
+/* Initialise. */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Initialise variables to avoid "used of uninitialised variable" \
+   messages from dumb compilers. */ \
+   sum = 0; \
+   sum_var = 0; \
+   wtsum = 0; \
+   bad = 0; \
+   bad_var = 0; \
+\
+/* Determine if we are processing bad pixels or variances. */ \
+   nobad = flags & AST__NOBAD; \
+   usebad = flags & AST__USEBAD; \
+   usevar = in_var && out_var; \
+\
+/* Handle the 1-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   if ( ndim_in == 1 ) { \
+\
+/* Calculate the coordinate limits of the input grid. */ \
+      xmin = (double) lbnd_in[ 0 ] - 0.5; \
+      xmax = (double) ubnd_in[ 0 ] + 0.5; \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+      if ( nobad ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         1,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         1,0,1) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         0,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         0,0,1) \
+               } \
+            } \
+         } \
+\
+/* Four more cases as above, but this time with the AST__NOBAD flag \
+   un-set. */ \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         1,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         1,0,0) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         0,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         0,0,0) \
+               } \
+            } \
+         } \
+      } \
+\
+/* Handle the 2-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   } else if ( ndim_in == 2 ) { \
+\
+/* Calculate the stride along the y dimension of the input grid. */ \
+      ystride = ubnd_in[ 0 ] - lbnd_in[ 0 ] + 1; \
+\
+/* Calculate the coordinate limits of the input grid in each \
+   dimension. */ \
+      xmin = (double) lbnd_in[ 0 ] - 0.5; \
+      xmax = (double) ubnd_in[ 0 ] + 0.5; \
+      ymin = (double) lbnd_in[ 1 ] - 0.5; \
+      ymax = (double) ubnd_in[ 1 ] + 0.5; \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+      if ( nobad ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         1,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         1,0,1) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         0,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         0,0,1) \
+               } \
+            } \
+         } \
+\
+/* Four more case as above, but without the AST__NOBAD flag. */ \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         1,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         1,0,0) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         0,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                                         0,0,0) \
+               } \
+            } \
+         } \
+      } \
+\
+/* Handle other numbers of dimensions. */ \
+/* ----------------------------------- */ \
+   } else { \
+\
+/* Allocate workspace. */ \
+      dim = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      frac_hi = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      frac_lo = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      hi = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      lo = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      stride = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      wt = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      wtprod = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      xn_max = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      xn_min = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      if ( astOK ) { \
+\
+/* Calculate the stride along each dimension of the input grid. */ \
+         for ( s = 1, idim = 0; idim < ndim_in; idim++ ) { \
+            stride[ idim ] = s; \
+            s *= ubnd_in[ idim ] - lbnd_in[ idim ] + 1; \
+\
+/* Calculate the coordinate limits of the input grid in each \
+   dimension. */ \
+            xn_min[ idim ] = (double) lbnd_in[ idim ] - 0.5; \
+            xn_max[ idim ] = (double) ubnd_in[ idim ] + 0.5; \
+         } \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+         if ( nobad ) { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype, \
+                                            Xsigned,1,1,1) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype, \
+                                            Xsigned,1,0,1) \
+                  } \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype, \
+                                            Xsigned,0,1,1) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype, \
+                                            Xsigned,0,0,1) \
+                  } \
+               } \
+            } \
+\
+/* Four more case as above, but without the AST__NOBAD flag. */ \
+         } else { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype, \
+                                            Xsigned,1,1,0) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype, \
+                                            Xsigned,1,0,0) \
+                  } \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype, \
+                                            Xsigned,0,1,0) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype, \
+                                            Xsigned,0,0,0) \
+                  } \
+               } \
+            } \
+         } \
+      } \
+\
+/* Free the workspace. */ \
+      dim = astFree( dim ); \
+      frac_hi = astFree( frac_hi ); \
+      frac_lo = astFree( frac_lo ); \
+      hi = astFree( hi ); \
+      lo = astFree( lo ); \
+      stride = astFree( stride ); \
+      wt = astFree( wt ); \
+      wtprod = astFree( wtprod ); \
+      xn_max = astFree( xn_max ); \
+      xn_min = astFree( xn_min ); \
+   } \
+\
+/* If an error has occurred, clear the returned result. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the 1-dimensional
+   case. */
+#define ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,Usebad,Usevar) \
+\
+/* Obtain the x coordinate of the current point and test if it lies \
+   outside the input grid. Also test if it is bad. */ \
+   x = coords[ 0 ][ point ]; \
+   bad = ( x < xmin ) || ( x >= xmax ) || ( x == AST__BAD ); \
+   if ( !bad ) { \
+\
+/* If input bad pixels must be detected, then obtain the offset along \
+   the input grid x dimension of the input pixel which contains the \
+   current coordinate and calculate this pixel's offset from the start \
+   of the input array. */ \
+      if ( Usebad ) { \
+         pixel = (int) floor( x + 0.5 ) - lbnd_in[ 0 ]; \
+\
+/* Test if the pixel is bad. */ \
+         bad = ( in[ pixel ] == badval ); \
+      } \
+\
+/* If OK, obtain the indices along the input grid x dimension of the \
+   two adjacent pixels which will contribute to the interpolated \
+   result. Also obtain the fractional weight to be applied to each of \
+   these pixels. */ \
+      if ( !bad ) { \
+         lo_x = (int) floor( x ); \
+         hi_x = lo_x + 1; \
+         frac_lo_x = (double) hi_x - x; \
+         frac_hi_x = 1.0 - frac_lo_x; \
+\
+/* Obtain the offset within the input array of the first pixel to be \
+   used for interpolation (the one with the smaller index). */ \
+         off_lo = lo_x - lbnd_in[ 0 ]; \
+\
+/* Initialise sums for forming the interpolated result. */ \
+         sum = (Xfloattype) 0.0; \
+         wtsum = (Xfloattype) 0.0; \
+         if ( Usevar ) { \
+            sum_var = (Xfloattype) 0.0; \
+            if ( ( Xsigned ) || ( Usebad ) ) bad_var = 0; \
+         } \
+\
+/* For each of the two pixels which may contribute to the result, \
+   test if the pixel index lies within the input grid. Where it does, \
+   accumulate the sums required for forming the interpolated \
+   result. In each case, we supply the pixel's offset within the input \
+   array and the weight to be applied to it. */ \
+         if ( lo_x >= lbnd_in[ 0 ] ) { \
+            FORM_LINEAR_INTERPOLATION_SUM(off_lo,frac_lo_x,Xtype, \
+                                          Xfloattype,Xsigned,Usebad,Usevar) \
+         } \
+         if ( hi_x <= ubnd_in[ 0 ] ) { \
+            FORM_LINEAR_INTERPOLATION_SUM(off_lo + 1,frac_hi_x,Xtype, \
+                                          Xfloattype,Xsigned,Usebad,Usevar) \
+         } \
+      } \
+   }
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the 2-dimensional
+   case. */
+#define ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,Usebad,Usevar) \
+\
+/* Obtain the x coordinate of the current point and test if it lies \
+   outside the input grid. Also test if it is bad. */ \
+   x = coords[ 0 ][ point ]; \
+   bad = ( x < xmin ) || ( x >= xmax ) || ( x == AST__BAD ); \
+   if ( !bad ) { \
+\
+/* If OK, then similarly obtain and test the y coordinate. */ \
+      y = coords[ 1 ][ point ]; \
+      bad = ( y < ymin ) || ( y >= ymax ) || ( y == AST__BAD ); \
+      if ( !bad ) { \
+\
+/* If input bad pixels must be detected, then obtain the offsets along \
+   each input grid dimension of the input pixel which contains the \
+   current coordinates. */ \
+         if ( Usebad ) { \
+            ix = (int) floor( x + 0.5 ); \
+            iy = (int) floor( y + 0.5 ); \
+\
+/* Calculate this pixel's offset from the start of the input array. */ \
+            pixel = ix - lbnd_in[ 0 ] + ystride * ( iy - lbnd_in[ 1 ] ); \
+\
+/* Test if the pixel is bad. */ \
+            bad = ( in[ pixel ] == badval ); \
+         } \
+\
+/* If OK, obtain the indices along the input grid x dimension of the \
+   two adjacent pixels which will contribute to the interpolated \
+   result. Also obtain the fractional weight to be applied to each of \
+   these pixels. */ \
+         if ( !bad ) { \
+            lo_x = (int) floor( x ); \
+            hi_x = lo_x + 1; \
+            frac_lo_x = (double) hi_x - x; \
+            frac_hi_x = 1.0 - frac_lo_x; \
+\
+/* Repeat this process for the y dimension. */ \
+            lo_y = (int) floor( y ); \
+            hi_y = lo_y + 1; \
+            frac_lo_y = (double) hi_y - y; \
+            frac_hi_y = 1.0 - frac_lo_y; \
+\
+/* Obtain the offset within the input array of the first pixel to be \
+   used for interpolation (the one with the smaller index along both \
+   dimensions). */ \
+            off_lo = lo_x - lbnd_in[ 0 ] + ystride * ( lo_y - lbnd_in[ 1 ] ); \
+\
+/* Initialise sums for forming the interpolated result. */ \
+            sum = (Xfloattype) 0.0; \
+            wtsum = (Xfloattype) 0.0; \
+            if ( Usevar ) { \
+               sum_var = (Xfloattype) 0.0; \
+               if ( ( Xsigned ) || ( Usebad ) ) bad_var = 0; \
+            } \
+\
+/* For each of the four pixels which may contribute to the result, \
+   test if the pixel indices lie within the input grid. Where they do, \
+   accumulate the sums required for forming the interpolated \
+   result. In each case, we supply the pixel's offset within the input \
+   array and the weight to be applied to it. */ \
+            if ( lo_y >= lbnd_in[ 1 ] ) { \
+               if ( lo_x >= lbnd_in[ 0 ] ) { \
+                  FORM_LINEAR_INTERPOLATION_SUM(off_lo, \
+                                                frac_lo_x * frac_lo_y,Xtype, \
+                                                Xfloattype, Xsigned, \
+                                                Usebad,Usevar) \
+               } \
+               if ( hi_x <= ubnd_in[ 0 ] ) { \
+                  FORM_LINEAR_INTERPOLATION_SUM(off_lo + 1, \
+                                                frac_hi_x * frac_lo_y,Xtype, \
+                                                Xfloattype,Xsigned, \
+                                                Usebad,Usevar) \
+               } \
+            } \
+            if ( hi_y <= ubnd_in[ 1 ] ) { \
+               if ( lo_x >= lbnd_in[ 0 ] ) { \
+                  FORM_LINEAR_INTERPOLATION_SUM(off_lo + ystride, \
+                                                frac_lo_x * frac_hi_y,Xtype, \
+                                                Xfloattype,Xsigned, \
+                                                Usebad,Usevar) \
+               } \
+               if ( hi_x <= ubnd_in[ 0 ] ) { \
+                  FORM_LINEAR_INTERPOLATION_SUM(off_lo + ystride + 1, \
+                                                frac_hi_x * frac_hi_y,Xtype, \
+                                                Xfloattype,Xsigned, \
+                                                Usebad,Usevar) \
+               } \
+            } \
+         } \
+      } \
+   }
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the n-dimensional
+   case. */
+#define ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,Usebad,Usevar) \
+\
+/* Initialise offsets into the input array. Then loop to obtain each
+   coordinate associated with the current output point. */ \
+   off_in = 0; \
+   if ( Usebad ) pixel = 0; \
+   for ( idim = 0; idim < ndim_in; idim++ ) { \
+      xn = coords[ idim ][ point ]; \
+\
+/* Test if the coordinate lies outside the input grid.  Also test if \
+   it is bad. If either is true, the corresponding output pixel value \
+   will be bad, so give up on this point. */ \
+      bad = ( xn < xn_min[ idim ] ) || ( xn >= xn_max[ idim ] ) || \
+            ( xn == AST__BAD ); \
+      if ( bad ) break; \
+\
+/* If input bad pixels must be detected, obtain the index along the \
+   current input grid dimension of the pixel which contains this \
+   coordinate and accumulate the pixel's offset from the start of the \
+   input array. */ \
+      if ( Usebad ) { \
+         pixel += stride[ idim ] * \
+                  ( (int) floor( xn + 0.5 ) - lbnd_in[ idim ] ); \
+      } \
+\
+/* Obtain the indices along the current dimension of the input grid of \
+   the two (usually adjacent) pixels which will contribute to the \
+   output value. If necessary, however, restrict each index to ensure \
+   it does not lie outside the input grid. Also calculate the \
+   fractional weight to be given to each pixel in order to interpolate \
+   linearly between them. */ \
+      ixn = (int) floor( xn ); \
+      lo[ idim ] = MaxI( ixn, lbnd_in[ idim ], status ); \
+      hi[ idim ] = MinI( ixn + 1, ubnd_in[ idim ], status ); \
+      frac_lo[ idim ] = 1.0 - fabs( xn - (double) lo[ idim ] ); \
+      frac_hi[ idim ] = 1.0 - fabs( xn - (double) hi[ idim ] ); \
+\
+/* Store the lower index involved in interpolation along each \
+   dimension and accumulate the offset from the start of the input \
+   array of the pixel which has these indices. */ \
+      dim[ idim ] = lo[ idim ]; \
+      off_in += stride[ idim ] * ( lo[ idim ] - lbnd_in[ idim ] ); \
+\
+/* Also store the fractional weight associated with the lower pixel \
+   along each dimension. */ \
+      wt[ idim ] = frac_lo[ idim ]; \
+   } \
+\
+/* If the input pixel which contains the required coordinates has \
+   been identified, test if it is bad. */ \
+   if ( Usebad ) bad = bad || ( in[ pixel ] == badval ); \
+\
+/* If OK, initialise and loop over adjacent input pixels to obtain an \
+   interpolated value. */ \
+   if ( !bad ) { \
+      sum = (Xfloattype) 0.0; \
+      wtsum = (Xfloattype) 0.0; \
+      if ( Usevar ) { \
+         sum_var = (Xfloattype) 0.0; \
+         if ( ( Xsigned ) || ( Usebad ) ) bad_var = 0; \
+      } \
+      idim = ndim_in - 1; \
+      wtprod[ idim ] = 1.0; \
+      done = 0; \
+      do { \
+\
+/* Each contributing pixel is weighted by the product of the weights \
+   which account for the displacement of its centre from the required \
+   position along each dimension. However, since we typically only \
+   change the index of one dimension at a time, we can avoid forming \
+   this product repeatedly by retaining an array of accumulated weight \
+   products for all higher dimensions. We need then only update the \
+   lower elements in this array, corresponding to those dimensions \
+   whose index has changed. We do this here, "idim" being the index of \
+   the most significant dimension to have changed. Note that on the \
+   first pass, all dimensions are considered changed, causing this \
+   array to be initialised. */ \
+         for ( ii = idim; ii >= 1; ii-- ) { \
+            wtprod[ ii - 1 ] = wtprod[ ii ] * wt[ ii ]; \
+         } \
+\
+/* Accumulate the sums required for forming the interpolated \
+   result. We supply the pixel's offset within the input array and the \
+   weight to be applied to it. The pixel weight is formed by including \
+   the weight factor for dimension zero, since this is not included in \
+   the "wtprod" array. */ \
+         FORM_LINEAR_INTERPOLATION_SUM(off_in,wtprod[ 0 ] * wt[ 0 ], \
+                                       Xtype,Xfloattype,Xsigned, \
+                                       Usebad,Usevar) \
+\
+/* Now update the indices, offset and weight factors to refer to the \
+   next input pixel to be considered. */ \
+         idim = 0; \
+         do { \
+\
+/* The first input dimension which still refers to the pixel with the \
+   lower of the two possible indices is switched to refer to the other \
+   pixel (with the higher index). The offset into the input array and \
+   the fractional weight factor for this dimension are also updated \
+   accordingly. */ \
+            if ( dim[ idim ] != hi[ idim ] ) { \
+               dim[ idim ] = hi[ idim ]; \
+               off_in += stride[ idim ]; \
+               wt[ idim ] = frac_hi[ idim ]; \
+               break; \
+\
+/* Any earlier dimensions (referring to the higher index) are switched \
+   back to the lower index, if not already there, before going on to \
+   consider the next dimension. (This process is the same as \
+   incrementing a binary number and propagating overflows up through \
+   successive digits, except that dimensions where the "lo" and "hi" \
+   values are the same can only take one value.) The process stops at \
+   the first attempt to return the final dimension to the lower \
+   index. */ \
+            } else { \
+               if ( dim[ idim ] != lo[ idim ] ) { \
+                  dim[ idim ] = lo[ idim ]; \
+                  off_in -= stride[ idim ]; \
+                  wt[ idim ] = frac_lo[ idim ]; \
+               } \
+               done = ( ++idim == ndim_in ); \
+            } \
+         } while ( !done ); \
+      } while ( !done ); \
+   }
+
+/* This subsidiary macro adds the contribution from a specified input
+   pixel to the accumulated sums for forming the linearly interpolated
+   value. */
+#define FORM_LINEAR_INTERPOLATION_SUM(off,wt,Xtype,Xfloattype,Xsigned, \
+                                      Usebad,Usevar) \
+\
+/* Obtain the offset of the input pixel to use. */ \
+   off_in = ( off ); \
+\
+/* If necessary, test if this pixel is bad. If not, then obtain the \
+   weight to apply to it. */ \
+   if ( !( Usebad ) || ( in[ off_in ] != badval ) ) { \
+      pixwt = ( wt ); \
+\
+/* Increment the weighted sum of pixel values and the sum of weights. */ \
+      sum += ( (Xfloattype) in[ off_in ] ) * ( (Xfloattype) pixwt ); \
+      wtsum += (Xfloattype) pixwt; \
+\
+/* If an output variance estimate is to be generated, and it still \
+   seems possible to produce one, then obtain the input variance \
+   value. */ \
+      if ( Usevar ) { \
+         if ( !( ( Xsigned ) || ( Usebad ) ) || !bad_var ) { \
+            var = in_var[ off_in ]; \
+\
+/* Test if the variance value is bad (if the data type is signed, also \
+   check that it is not negative). */ \
+            if ( Usebad ) bad_var = ( var == badval ); \
+            CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+\
+/* If OK, increment the weighted sum of variance values. */ \
+            if ( !( ( Xsigned ) || ( Usebad ) ) || !bad_var ) { \
+               sum_var += ( (Xfloattype) ( pixwt * pixwt ) ) * \
+                          ( (Xfloattype) var ); \
+            } \
+         } \
+      } \
+   }
+
+/* This subsidiary macro calculates the interpolated output value (and
+   variance) from the sums over contributing pixels and assigns them
+   to the output array(s). */
+#define CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Xsigned, \
+                               Usebad,Usevar,Nobad) \
+\
+/* Obtain the pixel offset into the output array. */ \
+   off_out = offset[ point ]; \
+\
+/* Assign a bad output value (and variance) if required and count it. */ \
+   if ( bad ) { \
+      if( !Nobad ) { \
+         out[ off_out ] = badval; \
+         if ( Usevar ) out_var[ off_out ] = badval; \
+      } \
+      result++; \
+\
+/* Otherwise, calculate the interpolated value. If the output data \
+   type is floating point, this result can be stored directly, \
+   otherwise we must round to the nearest integer. */ \
+   } else { \
+      val = sum / wtsum; \
+      if ( Xfloating ) { \
+         out[ off_out ] = (Xtype) val; \
+      } else { \
+         out[ off_out ] = (Xtype) ( val + ( ( val >= (Xfloattype) 0.0 ) ? \
+                                            ( (Xfloattype) 0.5 ) : \
+                                            ( (Xfloattype) -0.5 ) ) ); \
+      } \
+\
+/* If a variance estimate is required but none can be obtained, then \
+   store a bad output variance value and count it. */ \
+      if ( Usevar ) { \
+         if ( ( ( Xsigned ) || ( Usebad ) ) && bad_var ) { \
+            if( !Nobad ) out_var[ off_out ] = badval; \
+            result++; \
+\
+/* Otherwise, calculate the variance estimate and store it, rounding \
+   to the nearest integer if necessary. */ \
+         } else { \
+            val = sum_var / ( wtsum * wtsum ); \
+            if ( Xfloating ) { \
+               out_var[ off_out ] = (Xtype) val; \
+            } else { \
+               out_var[ off_out ] = (Xtype) ( val + \
+                                            ( ( val >= (Xfloattype) 0.0 ) ? \
+                                              ( (Xfloattype) 0.5 ) : \
+                                              ( (Xfloattype) -0.5 ) ) ); \
+            } \
+         } \
+      } \
+   }
+
+/* This subsidiary macro tests for negative variance values in the
+   macros above. This check is required only for signed data types. */
+#define CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+   bad_var = bad_var || ( var < ( (Xtype) 0 ) );
+
+/* Expand the main macro above to generate a function for each
+   required signed data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_INTERPOLATE_LINEAR(LD,long double,1,long double,1)
+MAKE_INTERPOLATE_LINEAR(L,long int,0,long double,1)
+MAKE_INTERPOLATE_LINEAR(K,INT_BIG,0,long double,1)
+#else
+MAKE_INTERPOLATE_LINEAR(L,long int,0,double,1)
+MAKE_INTERPOLATE_LINEAR(K,INT_BIG,0,double,1)
+#endif
+MAKE_INTERPOLATE_LINEAR(D,double,1,double,1)
+MAKE_INTERPOLATE_LINEAR(F,float,1,float,1)
+MAKE_INTERPOLATE_LINEAR(I,int,0,double,1)
+MAKE_INTERPOLATE_LINEAR(S,short int,0,float,1)
+MAKE_INTERPOLATE_LINEAR(B,signed char,0,float,1)
+
+/* Re-define the macro for testing for negative variances to do
+   nothing. */
+#undef CHECK_FOR_NEGATIVE_VARIANCE
+#define CHECK_FOR_NEGATIVE_VARIANCE(Xtype)
+
+/* Expand the main macro above to generate a function for each
+   required unsigned data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_INTERPOLATE_LINEAR(UL,unsigned long int,0,long double,0)
+MAKE_INTERPOLATE_LINEAR(UK,UINT_BIG,0,long double,0)
+#else
+MAKE_INTERPOLATE_LINEAR(UL,unsigned long int,0,double,0)
+MAKE_INTERPOLATE_LINEAR(UK,UINT_BIG,0,double,0)
+#endif
+MAKE_INTERPOLATE_LINEAR(UI,unsigned int,0,double,0)
+MAKE_INTERPOLATE_LINEAR(US,unsigned short int,0,float,0)
+MAKE_INTERPOLATE_LINEAR(UB,unsigned char,0,float,0)
+
+/* Undefine the macros uxsed above. */
+#undef CHECK_FOR_NEGATIVE_VARIANCE
+#undef CALC_AND_ASSIGN_OUTPUT
+#undef FORM_LINEAR_INTERPOLATION_SUM
+#undef ASSEMBLE_INPUT_ND
+#undef ASSEMBLE_INPUT_2D
+#undef ASSEMBLE_INPUT_1D
+#undef MAKE_INTERPOLATE_LINEAR
+
+/*
+*  Name:
+*     InterpolateNearest<X>
+
+*  Purpose:
+*     Resample a data grid, using the nearest-pixel interpolation scheme.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int InterpolateNearest<X>( int ndim_in,
+*                                const int *lbnd_in, const int *ubnd_in,
+*                                const <Xtype> *in, const <Xtype> *in_var,
+*                                int npoint, const int *offset,
+*                                const double *const *coords,
+*                                int flags, <Xtype> badval,
+*                                <Xtype> *out, <Xtype> *out_var )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This is a set of functions which resample a rectangular input
+*     grid of data (and, optionally, associated statistical variance
+*     values) so as to place them into a new output grid. Each output
+*     grid point may be mapped on to a position in the input grid in
+*     an arbitrary way. Where the positions given do not correspond
+*     with a pixel centre in the input grid, the interpolation scheme
+*     used is simply to select the nearest pixel (i.e. the one whose
+*     bounds contain the supplied position).
+
+*  Parameters:
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input grid, its extent along a particular
+*        (i'th) dimension being ubnd_in[i]-lbnd_in[i]+1 (assuming "i"
+*        is zero-based). They also define the input grid's coordinate
+*        system, with each pixel being of unit extent along each
+*        dimension with integral coordinate values at its centre.
+*     in
+*        Pointer to the array of data to be resampled (with an element
+*        for each pixel in the input grid). The numerical type of
+*        these data should match the function used, as given by the
+*        suffix on the function name. The storage order should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+*        (i.e. Fortran array storage order).
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and type as the "in" array), which
+*        represent estimates of the statistical variance associated
+*        with each element of the "in" array. If this second array is
+*        given (along with the corresponding "out_var" array), then
+*        estimates of the variance of the resampled data will also be
+*        returned.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     npoint
+*        The number of points at which the input grid is to be
+*        resampled.
+*     offset
+*        Pointer to an array of integers with "npoint" elements. For
+*        each output point, this array should contain the zero-based
+*        offset in the output array(s) (i.e. the "out" and,
+*        optionally, the "out_var" arrays) at which the resampled
+*        output value(s) should be stored.
+*     coords
+*        An array of pointers to double, with "ndim_in"
+*        elements. Element "coords[coord]" should point at the first
+*        element of an array of double (with "npoint" elements) which
+*        contains the values of coordinate number "coord" for each
+*        interpolation point. The value of coordinate number "coord"
+*        for interpolation point number "point" is therefore given by
+*        "coords[coord][point]" (assuming both indices to be
+*        zero-based).  If any point has a coordinate value of AST__BAD
+*        associated with it, then the corresponding output data (and
+*        variance) will be set to the value given by "badval" (unles the
+*        AST__NOBAD flag is specified).
+*     flags
+*        The bitwise OR of a set of flag values which control the
+*        operation of the function. Currently, only the flag
+*        AST__USEBAD is significant and indicates whether there are
+*        "bad" (i.e. missing) data in the input array(s) which must be
+*        recognised and propagated to the output array(s).  If this
+*        flag is not set, all input values are treated literally.
+*     badval
+*        If the AST__USEBAD flag is set in the "flags" value (above),
+*        this parameter specifies the value which is used to identify
+*        bad data and/or variance values in the input array(s). Its
+*        numerical type must match that of the "in" (and "in_var")
+*        arrays. Unles the AST__NOBAD flag is specified in "flags", the
+*        same value will also be used to flag any output array elements
+*        for which resampled values could not be obtained.  The output
+*        arrays(s) may be flagged with this value whether or not the
+*        AST__USEBAD flag is set (the function return value indicates
+*        whether any such values have been produced).
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the resampled data will be returned. Note
+*        that details of how the output grid maps on to this array
+*        (e.g. the storage order, number of dimensions, etc.) is
+*        arbitrary and is specified entirely by means of the "offset"
+*        array. The "out" array should therefore contain sufficient
+*        elements to accommodate the "offset" values supplied.  There
+*        is no requirement that all elements of the "out" array should
+*        be assigned values, and any which are not addressed by the
+*        contents of the "offset" array will be left unchanged.
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the resampled values may be returned. This array will only be
+*        used if the "in_var" array has been given. It is addressed in
+*        exactly the same way (via the "offset" array) as the "out"
+*        array. The values returned are estimates of the statistical
+*        variance of the corresponding values in the "out" array, on
+*        the assumption that all errors in input grid values (in the
+*        "in" array) are statistically independent and that their
+*        variance estimates (in the "in_var" array) may simply be
+*        summed (with appropriate weighting factors).
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+
+*  Returned Value:
+*     The number of output grid points to which a data value (or a
+*     variance value if relevant) equal to "badval" has been assigned
+*     because no valid output value could be obtained.
+
+*  Notes:
+*     - There is a separate function for each numerical type of
+*     gridded data, distinguished by replacing the <X> in the function
+*     name by the appropriate 1- or 2-character suffix.
+*     - A value of zero will be returned if any of these functions is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+/* Define a macro to implement the function for a specific data
+   type. */
+#define MAKE_INTERPOLATE_NEAREST(X,Xtype,Xsigned) \
+static int InterpolateNearest##X( int ndim_in, \
+                                  const int *lbnd_in, const int *ubnd_in, \
+                                  const Xtype *in, const Xtype *in_var, \
+                                  int npoint, const int *offset, \
+                                  const double *const *coords, \
+                                  int flags, Xtype badval, \
+                                  Xtype *out, Xtype *out_var, int *status ) { \
+\
+/* Local Variables: */ \
+   Xtype var;                    /* Variance value */ \
+   double *xn_max;               /* Pointer to upper limits array (n-d) */ \
+   double *xn_min;               /* Pointer to lower limits array (n-d) */ \
+   double x;                     /* x coordinate value */ \
+   double xmax;                  /* x upper limit */ \
+   double xmin;                  /* x lower limit */ \
+   double xn;                    /* Coordinate value (n-d) */ \
+   double y;                     /* y coordinate value */ \
+   double ymax;                  /* y upper limit */ \
+   double ymin;                  /* y lower limit */ \
+   int *stride;                  /* Pointer to array of dimension strides */ \
+   int bad;                      /* Output pixel bad? */ \
+   int idim;                     /* Loop counter for dimensions */ \
+   int ix;                       /* Number of pixels offset in x direction */ \
+   int ixn;                      /* Number of pixels offset (n-d) */ \
+   int iy;                       /* Number of pixels offset in y direction */ \
+   int nobad;                    /* Was the AST__NOBAD flag set? */ \
+   int off_in;                   /* Pixel offset into input array */ \
+   int off_out;                  /* Pixel offset into output array */ \
+   int point;                    /* Loop counter for output points */ \
+   int result;                   /* Returned result value */ \
+   int s;                        /* Temporary variable for strides */ \
+   int usebad;                   /* Use "bad" input pixel values? */ \
+   int usevar;                   /* Process variance array? */ \
+   int ystride;                  /* Stride along input grid y direction */ \
+\
+/* Initialise. */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Initialise variables to avoid "used of uninitialised variable" \
+   messages from dumb compilers. */ \
+   bad = 0; \
+   off_in = 0; \
+\
+/* Determine if we are processing bad pixels or variances. */ \
+   nobad = flags & AST__NOBAD; \
+   usebad = flags & AST__USEBAD; \
+   usevar = in_var && out_var; \
+\
+/* Handle the 1-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   if ( ndim_in == 1 ) { \
+\
+/* Calculate the coordinate limits of the input array. */ \
+      xmin = (double) lbnd_in[ 0 ] - 0.5; \
+      xmax = (double) ubnd_in[ 0 ] + 0.5; \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+      if ( nobad ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,0,1) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,0,1) \
+               } \
+            } \
+         } \
+\
+/* Four more cases as above, but without the AST__NOBAD flag. */ \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,0,0) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,0,0) \
+               } \
+            } \
+         } \
+      } \
+\
+/* Handle the 2-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   } else if ( ndim_in == 2 ) { \
+\
+/* Calculate the stride along the y dimension of the input grid. */ \
+      ystride = ubnd_in[ 0 ] - lbnd_in[ 0 ] + 1; \
+\
+/* Calculate the coordinate limits of the input array in each \
+   dimension. */ \
+      xmin = (double) lbnd_in[ 0 ] - 0.5; \
+      xmax = (double) ubnd_in[ 0 ] + 0.5; \
+      ymin = (double) lbnd_in[ 1 ] - 0.5; \
+      ymax = (double) ubnd_in[ 1 ] + 0.5; \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+      if ( nobad ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,0,1) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,0,1) \
+               } \
+            } \
+         } \
+\
+/* Four more cases as above, but without the AST__NOBAD flag. */ \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,0,0) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,0,0) \
+               } \
+            } \
+         } \
+      } \
+\
+/* Handle other numbers of dimensions. */ \
+/* ----------------------------------- */ \
+   } else { \
+\
+/* Allocate workspace. */ \
+      stride = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      xn_max = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      xn_min = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      if ( astOK ) { \
+\
+/* Calculate the stride along each dimension of the input grid. */ \
+         for ( s = 1, idim = 0; idim < ndim_in; idim++ ) { \
+            stride[ idim ] = s; \
+            s *= ubnd_in[ idim ] - lbnd_in[ idim ] + 1; \
+\
+/* Calculate the coordinate limits of the input grid in each \
+   dimension. */ \
+            xn_min[ idim ] = (double) lbnd_in[ idim ] - 0.5; \
+            xn_max[ idim ] = (double) ubnd_in[ idim ] + 0.5; \
+         } \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+         if ( nobad ) { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,1,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,1,1) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,1,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,0,1) \
+                  } \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,0,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,1,1) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,0,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,0,1) \
+                  } \
+               } \
+            } \
+\
+/* Another 4 cases as above, but without the AST__NOBAD flag. */ \
+         } else { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,1,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,1,0) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,1,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,1,0,0) \
+                  } \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,0,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,1,0) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,0,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,0,0,0) \
+                  } \
+               } \
+            } \
+         } \
+      } \
+\
+/* Free the workspace. */ \
+      stride = astFree( stride ); \
+      xn_max = astFree( xn_max ); \
+      xn_min = astFree( xn_min ); \
+   } \
+\
+/* If an error has occurred, clear the returned result. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the 1-dimensional
+   case. */
+#define ASSEMBLE_INPUT_1D(X,Xtype,Usebad,Usevar) \
+\
+/* Obtain the x coordinate of the current point and test if it lies \
+   outside the input grid, or is bad. */ \
+   x = coords[ 0 ][ point ]; \
+   bad = ( x < xmin ) || ( x >= xmax ) || ( x == AST__BAD ); \
+   if ( !bad ) { \
+\
+/* If not, then obtain the offset within the input grid of the pixel \
+   which contains the current point. */ \
+      off_in = (int) floor( x + 0.5 ) - lbnd_in[ 0 ]; \
+\
+/* If necessary, test if the input pixel is bad. */ \
+      if ( Usebad ) bad = ( in[ off_in ] == badval ); \
+   }
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the 2-dimensional
+   case. */
+#define ASSEMBLE_INPUT_2D(X,Xtype,Usebad,Usevar) \
+\
+/* Obtain the x coordinate of the current point and test if it lies \
+   outside the input grid, or is bad. */ \
+   x = coords[ 0 ][ point ]; \
+   bad = ( x < xmin ) || ( x >= xmax ) || ( x == AST__BAD ); \
+   if ( !bad ) { \
+\
+/* If not, then similarly obtain and test the y coordinate. */ \
+      y = coords[ 1 ][ point ]; \
+      bad = ( y < ymin ) || ( y >= ymax ) || ( y == AST__BAD ); \
+      if ( !bad ) { \
+\
+/* Obtain the offsets along each input grid dimension of the input \
+   pixel which contains the current point. */ \
+         ix = (int) floor( x + 0.5 ) - lbnd_in[ 0 ]; \
+         iy = (int) floor( y + 0.5 ) - lbnd_in[ 1 ]; \
+\
+/* Calculate this pixel's offset from the start of the input array. */ \
+         off_in = ix + ystride * iy; \
+\
+/* If necessary, test if the input pixel is bad. */ \
+         if ( Usebad ) bad = ( in[ off_in ] == badval ); \
+      } \
+   }
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the n-dimensional
+   case. */
+#define ASSEMBLE_INPUT_ND(X,Xtype,Usebad,Usevar) \
+\
+/* Initialise the offset into the input array. Then loop to obtain \
+   each coordinate associated with the current output point. */ \
+   off_in = 0; \
+   for ( idim = 0; idim < ndim_in; idim++ ) { \
+      xn = coords[ idim ][ point ]; \
+\
+/* Test if the coordinate lies outside the input grid, or is bad. If \
+   either is true, the corresponding output pixel value will be bad, \
+   so give up on this point. */ \
+      bad = ( xn < xn_min[ idim ] ) || ( xn >= xn_max[ idim ] ) || \
+            ( xn == AST__BAD ); \
+      if ( bad ) break; \
+\
+/* Obtain the offset along the current input grid dimension of the \
+   input pixel which contains the current point. */ \
+      ixn = (int) floor( xn + 0.5 ) - lbnd_in[ idim ]; \
+\
+/* Accumulate this pixel's offset from the start of the input \
+   array. */ \
+      off_in += ixn * stride[ idim ]; \
+   } \
+\
+/* Once the required input pixel has been located, test if it is \
+   bad, if necessary. */ \
+   if ( Usebad ) bad = bad || ( in[ off_in ] == badval );
+
+/* This subsidiary macro assigns the output value (and variance) to
+   the output array(s). */
+#define CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xsigned,Usebad,Usevar,Nobad) \
+\
+/* Obtain the pixel offset into the output array. */ \
+   off_out = offset[ point ]; \
+\
+/* If the input data value is bad, assign a bad output value (and \
+   variance, if required) and count it. */ \
+   if ( bad ) { \
+      if( !Nobad ) { \
+         out[ off_out ] = badval; \
+         if ( Usevar ) out_var[ off_out ] = badval; \
+      } \
+      result++; \
+\
+/* Otherwise, assign the value obtained from the input grid. */ \
+   } else { \
+      out[ off_out ] = in[ off_in ]; \
+\
+/* If required, obtain the associated variance value. If necessary, \
+   test if it is bad (if the data type is signed, also check that it \
+   is not negative). */ \
+      if ( Usevar ) { \
+         var = in_var[ off_in ]; \
+         if ( Usebad ) bad = ( var == badval ); \
+         CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+\
+/* If the variance value can be bad, and is, then store a bad value in \
+   the output array and count it. Otherwise, store the variance \
+   value. */ \
+         if ( ( ( Xsigned ) || ( Usebad ) ) && bad ) { \
+            if( !Nobad ) out_var[ off_out ] = badval; \
+            result++; \
+         } else { \
+            out_var[ off_out ] = var; \
+         } \
+      } \
+   }
+
+/* This subsidiary macro tests for negative variance values in the
+   macros above.  This check is required only for signed data
+   types. */
+#define CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+   bad = bad || ( var < ( (Xtype) 0 ) );
+
+/* Expand the main macro above to generate a function for each
+   required signed data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_INTERPOLATE_NEAREST(LD,long double,1)
+#endif
+MAKE_INTERPOLATE_NEAREST(D,double,1)
+MAKE_INTERPOLATE_NEAREST(F,float,1)
+MAKE_INTERPOLATE_NEAREST(L,long int,1)
+MAKE_INTERPOLATE_NEAREST(K,INT_BIG,1)
+MAKE_INTERPOLATE_NEAREST(I,int,1)
+MAKE_INTERPOLATE_NEAREST(S,short int,1)
+MAKE_INTERPOLATE_NEAREST(B,signed char,1)
+
+/* Re-define the macro for testing for negative variances to do
+   nothing. */
+#undef CHECK_FOR_NEGATIVE_VARIANCE
+#define CHECK_FOR_NEGATIVE_VARIANCE(Xtype)
+
+/* Expand the main macro above to generate a function for each
+   required unsigned data type. */
+MAKE_INTERPOLATE_NEAREST(UK,UINT_BIG,0)
+MAKE_INTERPOLATE_NEAREST(UL,unsigned long int,0)
+MAKE_INTERPOLATE_NEAREST(UI,unsigned int,0)
+MAKE_INTERPOLATE_NEAREST(US,unsigned short int,0)
+MAKE_INTERPOLATE_NEAREST(UB,unsigned char,0)
+
+/* Undefine the macros used above. */
+#undef CHECK_FOR_NEGATIVE_VARIANCE
+#undef CALC_AND_ASSIGN_OUTPUT
+#undef ASSEMBLE_INPUT_ND
+#undef ASSEMBLE_INPUT_2D
+#undef ASSEMBLE_INPUT_1D
+#undef MAKE_INTERPOLATE_NEAREST
+
+/*
+*  Name:
+*     InterpolateBlockAverage<X>
+
+*  Purpose:
+*     Resample a data grid, using multidimensional block averaging.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void InterpolateBlockAverage<X>( int ndim_in,
+*                                      const int lbnd_in[],
+*                                      const int ubnd_in[],
+*                                      const <Xtype> in[],
+*                                      const <Xtype> in_var[],
+*                                      int npoint, const int offset[],
+*                                      const double *const coords[],
+*                                      const double params[], int flags,
+*                                      <Xtype> badval, <Xtype> *out,
+*                                      <Xtype> *out_var, int *nbad )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This is a set of functions which resample a rectangular input
+*     grid of data (and, optionally, associated statistical variance
+*     values) so as to place them into a new output grid. To generate
+*     an output grid pixel, a block average is taken over an ndim-
+*     dimensional hypercube of pixels in the input grid.  If variances
+*     are being used then the input pixels will be weighted according
+*     to the reciprocals of the corresponding variance values, and
+*     input pixels without a valid variance will be ignored;
+*     otherwise an unweighted average will be taken over
+*     all non-bad pixels in the cube.  The size of the cube over which
+*     the average is taken is determined by the first element of the
+*     params array.
+*
+*     This "interpolation" scheme is appropriate where an input grid
+*     is to be resampled onto a much coarser output grid.
+
+*  Parameters:
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input grid, its extent along a particular
+*        (i'th) dimension being ubnd_in[i]-lbnd_in[i]+1 (assuming "i"
+*        is zero-based). They also define the input grid's coordinate
+*        system, with each pixel being of unit extent along each
+*        dimension with integral coordinate values at its centre.
+*     in
+*        Pointer to the array of data to be resampled (with an element
+*        for each pixel in the input grid). The numerical type of
+*        these data should match the function used, as given by the
+*        suffix on the function name. The storage order should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+*        (i.e. Fortran array storage order).
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and type as the "in" array), which
+*        represent estimates of the statistical variance associated
+*        with each element of the "in" array. If this second array is
+*        given (along with the corresponding "out_var" array), then
+*        estimates of the variance of the resampled data will also be
+*        returned.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     npoint
+*        The number of points at which the input grid is to be
+*        resampled.
+*     offset
+*        Pointer to an array of integers with "npoint" elements. For
+*        each output point, this array should contain the zero-based
+*        offset in the output array(s) (i.e. the "out" and,
+*        optionally, the "out_var" arrays) at which the resampled
+*        output value(s) should be stored.
+*     coords
+*        An array of pointers to double, with "ndim_in"
+*        elements. Element "coords[coord]" should point at the first
+*        element of an array of double (with "npoint" elements) which
+*        contains the values of coordinate number "coord" for each
+*        interpolation point. The value of coordinate number "coord"
+*        for interpolation point number "point" is therefore given by
+*        "coords[coord][point]" (assuming both indices to be
+*        zero-based).  If any point has a coordinate value of AST__BAD
+*        associated with it, then the corresponding output data (and
+*        variance) will be set to the value given by "badval" (unles the
+*        AST__NOBAD flag is specified).
+*     params
+*        A pointer to an array of doubles giving further information
+*        about how the resampling is to proceed.  Only the first
+*        element is significant; the nearest integer to this gives
+*        the number of pixels on either side of the central input
+*        grid pixel to use in each dimension.  Therefore
+*        (1 + 2*params[0])**ndim_in pixels will be averaged over to
+*        generate each output pixel.
+*     flags
+*        The bitwise OR of a set of flag values which control the
+*        operation of the function. Currently, only the flag
+*        AST__USEBAD is significant and indicates whether there are
+*        "bad" (i.e. missing) data in the input array(s) which must be
+*        recognised and propagated to the output array(s).  If this
+*        flag is not set, all input values are treated literally.
+*     badval
+*        If the AST__USEBAD flag is set in the "flags" value (above),
+*        this parameter specifies the value which is used to identify
+*        bad data and/or variance values in the input array(s). Its
+*        numerical type must match that of the "in" (and "in_var")
+*        arrays. Unles the AST__NOBAD flag is specified in "flags", the
+*        same value will also be used to flag any output array elements
+*        for which resampled values could not be obtained.  The output
+*        arrays(s) may be flagged with this value whether or not the
+*        AST__USEBAD flag is set (the function return value indicates
+*        whether any such values have been produced).
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the resampled data will be returned. Note
+*        that details of how the output grid maps on to this array
+*        (e.g. the storage order, number of dimensions, etc.) is
+*        arbitrary and is specified entirely by means of the "offset"
+*        array. The "out" array should therefore contain sufficient
+*        elements to accommodate the "offset" values supplied.  There
+*        is no requirement that all elements of the "out" array should
+*        be assigned values, and any which are not addressed by the
+*        contents of the "offset" array will be left unchanged.
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the resampled values may be returned. This array will only be
+*        used if the "in_var" array has been given. It is addressed in
+*        exactly the same way (via the "offset" array) as the "out"
+*        array. The values returned are estimates of the statistical
+*        variance of the corresponding values in the "out" array, on
+*        the assumption that all errors in input grid values (in the
+*        "in" array) are statistically independent and that their
+*        variance estimates (in the "in_var" array) may simply be
+*        summed (with appropriate weighting factors).
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+*     nbad
+*        Pointer to an int in which to return the number of
+*        interpolation points at which an output data value (and/or a
+*        variance value if relevant) equal to "badval" has been
+*        assigned because no valid interpolated value could be
+*        obtained.  The maximum value that will be returned is
+*        "npoint" and the minimum is zero (indicating that all output
+*        values were successfully obtained).
+
+*  Notes:
+*     - There is a separate function for each numerical type of
+*     gridded data, distinguished by replacing the <X> in the function
+*     name by the appropriate 1- or 2-character suffix.
+*/
+/* Define a macro to implement the function for a specific data
+   type. */
+#define MAKE_INTERPOLATE_BLOCKAVE(X,Xtype,Xfloating,Xfloattype,Xsigned) \
+static void InterpolateBlockAverage##X( int ndim_in, \
+                                        const int lbnd_in[], \
+                                        const int ubnd_in[], \
+                                        const Xtype in[], \
+                                        const Xtype in_var[], \
+                                        int npoint, const int offset[], \
+                                        const double *const coords[], \
+                                        const double params[], int flags, \
+                                        Xtype badval, Xtype *out, \
+                                        Xtype *out_var, int *nbad ) { \
+\
+/* Local Variables: */ \
+   Xfloattype hi_lim;            /* Upper limit on output values */ \
+   Xfloattype lo_lim;            /* Lower limit on output values */ \
+   Xfloattype pixwt;             /* Weight to apply to individual pixel */ \
+   Xfloattype sum;               /* Weighted sum of pixel data values */ \
+   Xfloattype sum_var;           /* Weighted sum of pixel variance values */ \
+   Xfloattype val;               /* Data value to be assigned to output */ \
+   Xfloattype val_var;           /* Variance to be assigned to output */ \
+   Xfloattype wtsum;             /* Sum of weight values */ \
+   Xfloattype wtsum_sq;          /* Square of sum of weights */ \
+   Xtype var;                    /* Variance value */ \
+   double *xn_max;               /* Pointer to upper limits array (n-d) */ \
+   double *xn_min;               /* Pointer to lower limits array (n-d) */ \
+   double x;                     /* x coordinate value */ \
+   double xn;                    /* Coordinate value (n-d) */ \
+   double y;                     /* y coordinate value */ \
+   int *hi;                      /* Pointer to array of upper indices */ \
+   int *ixm;                     /* Pointer to array of current indices */ \
+   int *lo;                      /* Pointer to array of lower indices */ \
+   int *status;                  /* Pointer to inherited status value */ \
+   int *stride;                  /* Pointer to array of dimension strides */ \
+   int bad;                      /* Output pixel bad? */ \
+   int bad_var;                  /* Output variance bad? */ \
+   int done;                     /* All pixel indices done? */ \
+   int hi_x;                     /* Upper pixel index (x dimension) */ \
+   int hi_y;                     /* Upper pixel index (y dimension) */ \
+   int idim;                     /* Loop counter for dimensions */ \
+   int ix;                       /* Pixel index in input grid x dimension */ \
+   int ixn;                      /* Pixel index in input grid (n-d) */ \
+   int iy;                       /* Pixel index in input grid y dimension */ \
+   int lo_x;                     /* Lower pixel index (x dimension) */ \
+   int lo_y;                     /* Lower pixel index (y dimension) */ \
+   int neighb;                   /* Number of adjacent pixels on each side */ \
+   int nobad;                    /* Was the AST__NOBAD flag set? */ \
+   int off1;                     /* Input pixel offset due to y index */ \
+   int off_in;                   /* Offset to input pixel */ \
+   int off_out;                  /* Offset to output pixel */ \
+   int point;                    /* Loop counter for output points */ \
+   int s;                        /* Temporary variable for strides */ \
+   int usebad;                   /* Use "bad" input pixel values? */ \
+   int usevar;                   /* Process variance array? */ \
+   int ystride;                  /* Stride along input grid y dimension */ \
+\
+/* Initialise. */ \
+   *nbad = 0; \
+\
+/* Get a pointer to the inherited status argument. */ \
+   status = astGetStatusPtr; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Initialise variables to avoid "used of uninitialised variable" \
+   messages from dumb compilers. */ \
+   val = 0; \
+   val_var = 0; \
+   sum_var = 0; \
+   wtsum = 0; \
+   bad = 0; \
+   bad_var = 0; \
+\
+/* Determine if we are processing bad pixels or variances. */ \
+   nobad = flags & AST__NOBAD; \
+   usebad = flags & AST__USEBAD; \
+   usevar = in_var && out_var; \
+\
+/* Set the number of pixels each side of central pixel to use. */ \
+   neighb = (int) floor( params[ 0 ] + 0.5 ); \
+\
+/* Set up limits for checking output values to ensure that they do not \
+   overflow the range of the data type being used. */ \
+   lo_lim = LO_##X; \
+   hi_lim = HI_##X; \
+\
+/* Handle the 1-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   if ( ndim_in == 1 ) { \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+      if ( nobad ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,1) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,1) \
+               } \
+            } \
+         } \
+\
+/* Another 4 cases as above, but without the AST__NOBAD flag. */ \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,0) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,0) \
+               } \
+            } \
+         } \
+      } \
+\
+/* Handle the 2-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   } else if ( ndim_in == 2 ) { \
+\
+/* Calculate the stride along the y dimension of the input grid. */ \
+      ystride = ubnd_in[ 0 ] - lbnd_in[ 0 ] + 1; \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+      if ( nobad ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,1) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,1) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,1) \
+               } \
+            } \
+         } \
+\
+/* Another 4 cases as above, but without the AST__NOBAD flag. */ \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,0) \
+               } \
+            } \
+         } else { \
+            if ( usevar ) { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,0) \
+               } \
+            } else { \
+               for ( point = 0; point < npoint; point++ ) { \
+                  ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                  CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,0) \
+               } \
+            } \
+         } \
+      } \
+\
+/* Handle other numbers of dimensions. */ \
+/* ----------------------------------- */ \
+   } else { \
+\
+/* Allocate workspace. */ \
+      hi = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      lo = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      stride = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      ixm = astMalloc( sizeof( int ) * (size_t) ndim_in ); \
+      xn_max = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      xn_min = astMalloc( sizeof( double ) * (size_t) ndim_in ); \
+      if ( astOK ) { \
+\
+/* Calculate the stride along each dimension of the input grid. */ \
+         for ( s = 1, idim = 0; idim < ndim_in; idim++ ) { \
+            stride[ idim ] = s; \
+            s *= ubnd_in[ idim ] - lbnd_in[ idim ] + 1; \
+\
+/* Calculate the coordinate limits of the input grid in each \
+   dimension. */ \
+            xn_min[ idim ] = (double) lbnd_in[ idim ] - 0.5; \
+            xn_max[ idim ] = (double) ubnd_in[ idim ] + 0.5; \
+         } \
+\
+/* Identify four cases, according to whether bad pixels and/or \
+   variances are being processed. In each case, loop through all the \
+   output points to (a) assemble the input data needed to form the \
+   interpolated value, and (b) calculate the result and assign it to \
+   the output arrays(s). In each case we assign constant values (0 or \
+   1) to the "Usebad" and "Usevar" flags so that code for handling bad \
+   pixels and variances can be eliminated when not required. */ \
+         if ( nobad ) { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,1) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,1) \
+                  } \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,1) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,1) \
+                  } \
+               } \
+            } \
+\
+/* Another 4 cases as above, but this time without the AST__NOBAD flag. */ \
+         } else { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,1,0) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,1,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,1,0,0) \
+                  } \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,1) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,1,0) \
+                  } \
+               } else { \
+                  for ( point = 0; point < npoint; point++ ) { \
+                     ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,0,0) \
+                     CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,0,0,0) \
+                  } \
+               } \
+            } \
+         } \
+      } \
+\
+/* Free the workspace. */ \
+      hi = astFree( hi ); \
+      lo = astFree( lo ); \
+      stride = astFree( stride ); \
+      ixm = astFree( ixm ); \
+      xn_max = astFree( xn_max ); \
+      xn_min = astFree( xn_min ); \
+   } \
+\
+/* If an error has occurred, clear the returned result. */ \
+   if ( !astOK ) *nbad = 0; \
+\
+/* Return. */ \
+}
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the 1-dimensional
+   case. */
+#define ASSEMBLE_INPUT_1D(X,Xtype,Xfloating,Xfloattype,Xsigned,Usebad,Usevar) \
+\
+/* Obtain the x coordinate of the current point and test if it is bad. */ \
+   x = coords[ 0 ][ point ]; \
+   bad = ( x == AST__BAD ); \
+\
+/* Note we do not need to check here whether the pixel in this position is \
+   bad; if any pixels in the cube are good we can form an average. */ \
+\
+/* If OK, calculate the lowest and highest indices (in the x \
+   dimension) of the region of neighbouring pixels that will \
+   contribute to the interpolated result.  Constrain these values to \
+   lie within the input grid. */ \
+   if ( !bad ) { \
+      ix = (int) floor( x ); \
+      lo_x = MaxI( ix - neighb + 1, lbnd_in[ 0 ], status ); \
+      hi_x = MinI( ix + neighb,     ubnd_in[ 0 ], status ); \
+\
+/* Initialise sums for forming the interpolated result. */ \
+      sum = (Xfloattype) 0.0; \
+      wtsum = (Xfloattype) 0.0; \
+      if ( Usevar ) { \
+         sum_var = (Xfloattype) 0.0; \
+         bad_var = 0; \
+      } \
+\
+/* Loop to inspect all the contributing pixels, calculating the offset \
+   of each pixel from the start of the input array. */ \
+      off_in = lo_x - lbnd_in[ 0 ]; \
+      for ( ix = lo_x; ix <= hi_x; ix++, off_in++ ) { \
+\
+/* If necessary, test if the input pixel is bad. */ \
+         if ( !( Usebad ) || ( in[ off_in ] != badval ) ) { \
+\
+/* If we are using variances, then check that the variance is valid; \
+   if it is invalid then ignore this pixel altogether. */ \
+            if ( Usevar ) { \
+               var = in_var[ off_in ]; \
+               if ( Usebad ) bad_var = ( var == badval ); \
+               CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+\
+/* If variance is valid then accumulate suitably weighted values into \
+   the totals. */ \
+               if ( !( ( Xsigned ) || ( Usebad ) ) || !bad_var ) { \
+                  pixwt = (Xfloattype) 1.0 / var; \
+                  sum += pixwt * ( (Xfloattype) in[ off_in ] ); \
+                  wtsum += pixwt; \
+                  sum_var += pixwt; \
+               } \
+\
+/* If we are not using variances, then accumulate values into the \
+   totals with a weighting of unity. */ \
+            } else { \
+               sum += (Xfloattype) in[ off_in ]; \
+               wtsum++; \
+            } \
+         } \
+      } \
+   }
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the 2-dimensional
+   case. */
+#define ASSEMBLE_INPUT_2D(X,Xtype,Xfloating,Xfloattype,Xsigned,Usebad,Usevar) \
+\
+/* Obtain the x coordinate of the current point and test if it is bad. */ \
+   x = coords[ 0 ][ point ]; \
+   bad = ( x == AST__BAD ); \
+   if ( !bad ) { \
+\
+/* If not, then similarly obtain and test the y coordinate. */ \
+      y = coords[ 1 ][ point ]; \
+      bad = ( y == AST__BAD ); \
+\
+/* Note we do not need to check here whether the pixel in this position is \
+   bad; if any pixels in the cube are good we can form an average. */ \
+\
+/* If OK, calculate the lowest and highest indices (in each dimension) \
+   of the region of neighbouring pixels that will contribute to the \
+   interpolated result.  Constrain these values to lie within the input \
+   grid. */ \
+      if ( !bad ) { \
+         ix = (int) floor( x ); \
+         lo_x = MaxI( ix - neighb + 1, lbnd_in[ 0 ], status ); \
+         hi_x = MinI( ix + neighb,     ubnd_in[ 0 ], status ); \
+         iy = (int) floor( y ); \
+         lo_y = MaxI( iy - neighb + 1, lbnd_in[ 1 ], status ); \
+         hi_y = MinI( iy + neighb,     ubnd_in[ 1 ], status ); \
+\
+/* Initialise sums for forming the interpolated result. */ \
+         sum = (Xfloattype) 0.0; \
+         wtsum = (Xfloattype) 0.0; \
+         if ( Usevar ) { \
+            sum_var = (Xfloattype) 0.0; \
+            bad_var = 0; \
+         } \
+\
+/* Loop to inspect all the contributing pixels, calculating the offset \
+   of each pixel from the start of the input array. */ \
+         off1 = lo_x - lbnd_in[ 0 ] + ystride * ( lo_y - lbnd_in[ 1 ] ); \
+         for ( iy = lo_y; iy <= hi_y; iy++, off1 += ystride ) { \
+            off_in = off1; \
+            for ( ix = lo_x; ix <= hi_x; ix++, off_in++ ) { \
+\
+/* If necessary, test if the input pixel is bad. */ \
+               if ( !( Usebad ) || ( in[ off_in ] != badval ) ) { \
+\
+/* If we are using variances, then check that the variance is valid; \
+   if it is invalid then ignore this pixel altogether. */ \
+                  if ( Usevar ) { \
+                     var = in_var[ off_in ]; \
+                     if ( Usebad ) bad_var = ( var == badval ); \
+                     CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+\
+/* If variance is valid then accumulate suitably weighted values into \
+   the totals. */ \
+                     if ( !( ( Xsigned ) || ( Usebad ) ) || !bad_var ) { \
+                        pixwt = (Xfloattype) 1.0 / var; \
+                        sum += pixwt * ( (Xfloattype) in[ off_in ] ); \
+                        wtsum += pixwt; \
+                        sum_var += pixwt; \
+                     } \
+\
+/* If we are not using variances, then accumulate values into the \
+   totals with a weighting of unity. */ \
+                  } else { \
+                     sum += (Xfloattype) in[ off_in ]; \
+                     wtsum++; \
+                  } \
+               } \
+            } \
+         } \
+      } \
+   }
+
+/* This subsidiary macro assembles the input data needed in
+   preparation for forming the interpolated value in the n-dimensional
+   case. */
+#define ASSEMBLE_INPUT_ND(X,Xtype,Xfloating,Xfloattype,Xsigned,Usebad,Usevar) \
+\
+/* Initialise offsets into the input array.  then loop to obtain each \
+   coordinate associated with the current output poitn. */ \
+   off_in = 0; \
+   for ( idim = 0; idim < ndim_in; idim++ ) { \
+      xn = coords[ idim ][ point ]; \
+\
+/* Test if the coordinate is bad. If so give up on this point. */ \
+      bad = ( xn == AST__BAD ); \
+      if ( bad ) break; \
+\
+/* Calculate the lowest and highest indices (in the current dimension) \
+   of the region of neighbouring pixels that will contribute to the \
+   interpolated result. Constrain these values to lie within the input \
+   grid. */ \
+      ixn = (int) floor( xn ); \
+      lo[ idim ] = MaxI( ixn - neighb + 1, lbnd_in[ idim ], status ); \
+      hi[ idim ] = MinI( ixn + neighb,     ubnd_in[ idim ], status ); \
+\
+/* If the cube has a zero dimension then no data can come from it. */ \
+      bad = ( lo[ idim ] > hi[ idim ] ); \
+      if ( bad ) break; \
+\
+/* Accumulate the offset (from the start of the input array) of the \
+   contributing pixel which has the lowest index in each dimension. */ \
+      off_in += stride[ idim ] * ( lo[ idim ] - lbnd_in[ idim ] ); \
+\
+/* Initialise an array to keep track of the current position in the \
+   input cube. */ \
+      ixm[ idim ] = lo[ idim ]; \
+   } \
+\
+/* Note we do not need to check here whether the pixel in this position is \
+   bad; if any pixels in the cube are good we can form an average. */ \
+\
+/* If OK, initialise sums for forming the interpolated result. */ \
+   if ( !bad ) { \
+      sum = (Xfloattype) 0.0; \
+      wtsum= (Xfloattype) 0.0; \
+      if ( Usevar ) { \
+         sum_var = (Xfloattype) 0.0; \
+         bad_var = 0; \
+      } \
+\
+/* Loop to inspect all the contributing pixels, calculating the offset \
+   of each pixel from the start of the input array. */ \
+      do { \
+\
+/* If necessary, test if the input pixel is bad. */ \
+         if ( !( Usebad ) || ( in[ off_in ] != badval ) ) { \
+\
+/* If we are using variances, then check that the variance is valid; \
+   if it is invalid then ignore this pixel altogether. */ \
+            if ( Usevar ) { \
+               var = in_var[ off_in ]; \
+               if ( Usebad ) bad_var = ( var == badval ); \
+               CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+\
+/* If variance is valid then accumulate suitably weighted values into \
+   the totals. */ \
+               if ( !( ( Xsigned ) || ( Usebad ) ) || !bad_var ) { \
+                  pixwt = (Xfloattype) 1.0 / var; \
+                  sum += pixwt * ( (Xfloattype) in[ off_in ] ); \
+                  wtsum += pixwt; \
+                  sum_var += pixwt; \
+               } \
+\
+/* If we are not using variances, then accumulate values into the \
+   totals with a weighting of unity. */ \
+            } else { \
+               sum += (Xfloattype) in[ off_in ]; \
+               wtsum++; \
+            } \
+         } \
+\
+/* Locate the next pixel in the input cube; try incrementing the lowest \
+   dimension index first, if that rolls over increment the next  \
+   dimension index, and so on. */ \
+         for ( idim = 0; idim < ndim_in; idim++ ) { \
+            if ( ixm[ idim ] < hi[ idim ] ) { \
+               off_in += stride[ idim ]; \
+               ixm[ idim ]++; \
+               break; \
+            } else { \
+               off_in -= stride[ idim ] * ( hi[ idim ] - lo[ idim ] ); \
+               ixm[ idim ] = lo[ idim ]; \
+            } \
+         } \
+\
+/* If the highest dimension index has rolled over, we have done all \
+   the pixels in the cube. */ \
+         done = ( idim == ndim_in ); \
+      } while ( !done ); \
+   }
+
+/* This subsidiary macro calculates the interpolated output value (and
+   variance) from the sums over contributing pixels, checks the
+   results for validity, and assigns them to the output array(s). */
+#define CALC_AND_ASSIGN_OUTPUT(X,Xtype,Xfloating,Xfloattype,Usebad,Usevar,Nobad) \
+\
+/* If the output data value has not yet been flagged as bad, then \
+   check that an interpolated value can actually be produced.  First \
+   check that the sum of weights is not zero. */ \
+   if ( !bad ) { \
+      bad = ( wtsum == (Xfloattype) 0.0 ); \
+\
+/* If OK, calculate the interpolated value. Then, if the output data \
+   type is not floating point, check that this value will not overflow \
+   the available output range. */ \
+      if ( !bad ) { \
+         val = sum / wtsum; \
+         if ( !( Xfloating ) ) { \
+            bad = ( val <= lo_lim ) || ( val >= hi_lim ); \
+         } \
+      } \
+\
+/* If no interpolated data value can be produced, then no associated \
+   variance will be required either. */ \
+      if ( ( Usevar ) && bad ) bad_var = 1; \
+   } \
+\
+/* Now perform similar checks on the output variance value (if \
+   required). This time we check that the square of the sum of \
+   weights is not zero (since this might underflow before the sum of \
+   weights). Again we also check to prevent the result overflowing the \
+   output data type. */ \
+   if ( ( Usevar ) && !bad_var ) { \
+      wtsum_sq = wtsum * wtsum; \
+      bad_var = ( wtsum_sq == (Xfloattype) 0.0 ); \
+      if ( !bad_var ) { \
+         val_var = sum_var / wtsum_sq; \
+         if ( !( Xfloating ) ) { \
+            bad_var = ( val_var <= lo_lim ) || ( val_var >= hi_lim ); \
+         } \
+      } \
+   } \
+\
+/* Obtain the pixel offset into the output array. */ \
+   off_out = offset[ point ]; \
+\
+/* Assign a bad output value (and variance) if required and count it. */ \
+   if ( bad ) { \
+      if( !Nobad ) { \
+         out[ off_out ] = badval; \
+         if ( Usevar ) out_var[ off_out ] = badval; \
+      } \
+      (*nbad)++; \
+\
+/* Otherwise, assign the interpolated value. If the output data type \
+   is floating point, the result can be stored directly, otherwise we \
+   must round to the nearest integer. */ \
+   } else { \
+      if ( Xfloating ) { \
+         out[ off_out ] = (Xtype) val; \
+      } else { \
+         out[ off_out ] = (Xtype) ( val + ( ( val >= (Xfloattype) 0.0 ) ? \
+                                            ( (Xfloattype) 0.5 ) : \
+                                            ( (Xfloattype) -0.5 ) ) ); \
+      } \
+\
+/* If a variance estimate is required but none can be obtained, then \
+   store a bad output variance value and count it. */ \
+      if ( Usevar ) { \
+         if ( bad_var ) { \
+            if( !Nobad ) out_var[ off_out ] = badval; \
+            (*nbad)++; \
+\
+/* Otherwise, store the variance estimate, rounding to the nearest \
+   integer if necessary. */ \
+         } else { \
+            if ( Xfloating ) { \
+               out_var[ off_out ] = (Xtype) val_var; \
+            } else { \
+               out_var[ off_out ] = (Xtype) ( val_var + \
+                                          ( ( val_var >= (Xfloattype) 0.0 ) ? \
+                                            ( (Xfloattype) 0.5 ) : \
+                                            ( (Xfloattype) -0.5 ) ) ); \
+            } \
+         } \
+      } \
+   }
+
+/* These subsidiary macros define limits for range checking of results
+   before conversion to the final data type. For each data type code
+   <X>, HI_<X> gives the least positive floating point value which
+   just overflows that data type towards plus infinity, while LO_<X>
+   gives the least negative floating point value which just overflows
+   that data type towards minus infinity. Thus, a floating point value
+   must satisfy LO<flt_value<HI if overflow is not to occur when it is
+   converted to that data type.
+
+   The data type of each limit should be that of the smallest
+   precision floating point type which will accommodate the full range
+   of values that the target type may take. */
+
+/* If <X> is a floating point type, the limits are not actually used,
+   but must be present to permit error-free compilation. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+#define HI_LD ( 0.0L )
+#define LO_LD ( 0.0L )
+#endif
+#define HI_D ( 0.0 )
+#define LO_D ( 0.0 )
+#define HI_F ( 0.0f )
+#define LO_F ( 0.0f )
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+#define HI_L   ( 0.5L + (long double) LONG_MAX )
+#define LO_L  ( -0.5L + (long double) LONG_MIN )
+#define HI_UL  ( 0.5L + (long double) ULONG_MAX )
+#define LO_UL ( -0.5L )
+#define HI_K   ( 0.5L + (long double) LONG_MAX )
+#define LO_K  ( -0.5L + (long double) LONG_MIN )
+#define HI_UK  ( 0.5L + (long double) ULONG_MAX )
+#define LO_UK ( -0.5L )
+#else
+#define HI_L   ( 0.5 + (double) LONG_MAX )
+#define LO_L  ( -0.5 + (double) LONG_MIN )
+#define HI_UL  ( 0.5 + (double) ULONG_MAX )
+#define LO_UL ( -0.5 )
+#define HI_K   ( 0.5 + (double) LONG_MAX )
+#define LO_K  ( -0.5 + (double) LONG_MIN )
+#define HI_UK  ( 0.5 + (double) ULONG_MAX )
+#define LO_UK ( -0.5 )
+#endif
+#define HI_I   ( 0.5 + (double) INT_MAX )
+#define LO_I  ( -0.5 + (double) INT_MIN )
+#define HI_UI  ( 0.5 + (double) UINT_MAX )
+#define LO_UI ( -0.5 )
+#define HI_S   ( 0.5f + (float) SHRT_MAX )
+#define LO_S  ( -0.5f + (float) SHRT_MIN )
+#define HI_US  ( 0.5f + (float) USHRT_MAX )
+#define LO_US ( -0.5f )
+#define HI_B   ( 0.5f + (float) SCHAR_MAX )
+#define LO_B  ( -0.5f + (float) SCHAR_MIN )
+#define HI_UB  ( 0.5f + (float) UCHAR_MAX )
+#define LO_UB ( -0.5f )
+
+/* This subsidiary macro tests for negative variance values. This
+   check is required only for signed data types. */
+#define CHECK_FOR_NEGATIVE_VARIANCE(Xtype) \
+   bad_var = bad_var || ( var < ( (Xtype) 0 ) );
+
+/* Expand the main macro above to generate a function for each
+   required signed data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_INTERPOLATE_BLOCKAVE(LD,long double,1,long double,1)
+MAKE_INTERPOLATE_BLOCKAVE(L,long int,0,long double,1)
+MAKE_INTERPOLATE_BLOCKAVE(K,INT_BIG,0,long double,1)
+#else
+MAKE_INTERPOLATE_BLOCKAVE(L,long int,0,double,1)
+MAKE_INTERPOLATE_BLOCKAVE(K,INT_BIG,0,double,1)
+#endif
+MAKE_INTERPOLATE_BLOCKAVE(D,double,1,double,1)
+MAKE_INTERPOLATE_BLOCKAVE(F,float,1,float,1)
+MAKE_INTERPOLATE_BLOCKAVE(I,int,0,double,1)
+MAKE_INTERPOLATE_BLOCKAVE(S,short int,0,float,1)
+MAKE_INTERPOLATE_BLOCKAVE(B,signed char,0,float,1)
+
+/* Re-define the macro for testing for negative variances to do
+   nothing. */
+#undef CHECK_FOR_NEGATIVE_VARIANCE
+#define CHECK_FOR_NEGATIVE_VARIANCE(Xtype)
+
+/* Expand the main macro above to generate a function for each
+   required unsigned data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_INTERPOLATE_BLOCKAVE(UL,unsigned long int,0,long double,0)
+MAKE_INTERPOLATE_BLOCKAVE(UK,UINT_BIG,0,long double,0)
+#else
+MAKE_INTERPOLATE_BLOCKAVE(UL,unsigned long int,0,double,0)
+MAKE_INTERPOLATE_BLOCKAVE(UK,UINT_BIG,0,double,0)
+#endif
+MAKE_INTERPOLATE_BLOCKAVE(UI,unsigned int,0,double,0)
+MAKE_INTERPOLATE_BLOCKAVE(US,unsigned short int,0,float,0)
+MAKE_INTERPOLATE_BLOCKAVE(UB,unsigned char,0,float,0)
+
+/* Undefine the macros used above. */
+#undef CHECK_FOR_NEGATIVE_VARIANCE
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+#undef HI_LD
+#undef LO_LD
+#endif
+#undef HI_D
+#undef LO_D
+#undef HI_F
+#undef LO_F
+#undef HI_L
+#undef LO_L
+#undef HI_UL
+#undef LO_UL
+#undef HI_K
+#undef LO_K
+#undef HI_UK
+#undef LO_UK
+#undef HI_I
+#undef LO_I
+#undef HI_UI
+#undef LO_UI
+#undef HI_S
+#undef LO_S
+#undef HI_US
+#undef LO_US
+#undef HI_B
+#undef LO_B
+#undef HI_UB
+#undef LO_UB
+#undef CALC_AND_ASSIGN_OUTPUT
+#undef ASSEMBLE_INPUT_ND
+#undef ASSEMBLE_INPUT_2D
+#undef ASSEMBLE_INPUT_1D
+#undef MAKE_INTERPOLATE_BLOCKAVE
+
+
+static void Invert( AstMapping *this, int *status ) {
+/*
+*++
+*  Name:
+c     astInvert
+f     AST_INVERT
+
+*  Purpose:
+*     Invert a Mapping.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astInvert( AstMapping *this )
+f     CALL AST_INVERT( THIS, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+c     This function inverts a Mapping by reversing the boolean sense
+f     This routine inverts a Mapping by reversing the boolean sense
+*     of its Invert attribute. If this attribute is zero (the
+*     default), the Mapping will transform coordinates in the way
+*     specified when it was created. If it is non-zero, the input and
+*     output coordinates will be inter-changed so that the direction
+*     of the Mapping is reversed. This will cause it to display the
+*     inverse of its original behaviour.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+*--
+*/
+
+/* Local Variables: */
+   int invert;                   /* New Invert attribute value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Determine the new Invert attribute value. */
+   invert = !astGetInvert( this );
+
+/* Clear the old value. */
+   astClearInvert( this );
+
+/* If the resulting default value is not the one required, then set a
+   new value explicitly. */
+   if ( astGetInvert( this ) != invert ) astSetInvert( this, invert );
+}
+
+static double J1Bessel( double x, int *status ) {
+/*
+*  Name:
+*     J1Bessel
+
+*  Purpose:
+*     Calculates the first-order Bessel function of the first kind.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     double J1Bessel( double x, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function calculates the value of the first-order Bessel function
+*     of the first kind.
+
+*  Parameters:
+*     x
+*        The argument for J1.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The calculated J1(x) value.
+
+*  Notes:
+*     - The algorithm is taken from the SCUBA routine SCULIB_BESSJ1, by
+*     J.Lightfoot.
+*     - This function does not perform error checking and does not
+*     generate errors.
+*/
+
+/* Local Variables: */
+   static double p1 = 1.0;
+   static double p2 = 0.183105E-2;
+   static double p3 = -0.3516396496E-4;
+   static double p4 = 0.2457520174E-5;
+   static double p5 = -0.240337019E-6;
+
+   static double q1 = 0.04687499995;
+   static double q2 = -0.2002690873E-3;
+   static double q3 = 0.8449199096E-5;
+   static double q4 = -0.88228987E-6;
+   static double q5 = 0.105787412E-6;
+
+   static double r1 = 72362614232.0;
+   static double r2 = -7895059235.0;
+   static double r3 = 242396853.1;
+   static double r4 = -2972611.439;
+   static double r5 = 15704.48260;
+   static double r6 = -30.16036606;
+
+   static double s1 = 144725228442.0;
+   static double s2 = 2300535178.0;
+   static double s3 = 18583304.74;
+   static double s4 = 99447.43394;
+   static double s5 = 376.9991397;
+   static double s6 = 1.0;
+
+   double ax;
+   double xx;
+   double z;
+   double y;
+   double value;
+   int s;
+
+/* Calculate the value */
+   ax = fabs( x );
+   if( ax < 8.0 ) {
+      y = x*x;
+      value = x*( r1 + y*( r2 + y*( r3 + y*( r4 + y*( r5 + y*r6 ) ) ) ) ) /
+                       ( s1 + y*( s2 + y*( s3 + y*( s4 + y*( s5 + y*s6 ) ) ) ) );
+   } else {
+      s = ( x >= 0.0 ) ? 1 : -1;
+      z = 8.0 / ax;
+      y = z*z;
+      xx = ax - 2.356194491;
+      value = sqrt ( 0.636619772/ax )*( cos( xx )*( p1 + y*( p2 + y*
+        ( p3 + y*( p4 + y*p5 ) ) ) )-z*sin( xx )*( q1 + y*( q2 + y*( q3 + y*
+        ( q4 + y*q5 ) ) ) ) )*s;
+   }
+
+   return value;
+
+}
+
+static int LinearApprox( AstMapping *this, const double *lbnd,
+                         const double *ubnd, double tol, double *fit, int *status ) {
+/*
+*++
+*  Name:
+c     astLinearApprox
+f     AST_LINEARAPPROX
+
+*  Purpose:
+*     Obtain a linear approximation to a Mapping, if appropriate.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     int astLinearApprox( AstMapping *this, const double *lbnd,
+c                          const double *ubnd, double tol, double *fit )
+f     RESULT = AST_LINEARAPPROX( THIS, LBND, UBND, TOL, FIT, STATUS )
+
+*  Class Membership:
+*     Mapping function.
+
+*  Description:
+*     This function tests the forward coordinate transformation
+*     implemented by a Mapping over a given range of input coordinates. If
+*     the transformation is found to be linear to a specified level of
+*     accuracy, then an array of fit coefficients is returned. These
+*     may be used to implement a linear approximation to the Mapping's
+*     forward transformation within the specified range of output coordinates.
+*     If the transformation is not sufficiently linear, no coefficients
+*     are returned.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping.
+c     lbnd
+f     LBND( * ) = DOUBLE PRECISION (Given)
+c        Pointer to an array of doubles
+f        An array
+*        containing the lower bounds of a box defined within the input
+*        coordinate system of the Mapping. The number of elements in this
+*        array should equal the value of the Mapping's Nin attribute. This
+*        box should specify the region over which linearity is required.
+c     ubnd
+f     UBND( * ) = DOUBLE PRECISION (Given)
+c        Pointer to an array of doubles
+f        An array
+*        containing the upper bounds of the box specifying the region over
+*        which linearity is required.
+c     tol
+f     TOL = DOUBLE PRECISION (Given)
+*        The maximum permitted deviation from linearity, expressed as
+*        a positive Cartesian displacement in the output coordinate
+*        space of the Mapping. If a linear fit to the forward
+*        transformation of the Mapping deviates from the true transformation
+*        by more than this amount at any point which is tested, then no fit
+*        coefficients will be returned.
+c     fit
+f     FIT( * ) = DOUBLE PRECISION (Returned)
+c        Pointer to an array of doubles
+f        An array
+*        in which to return the co-efficients of the linear
+*        approximation to the specified transformation. This array should
+*        have at least "( Nin + 1 ) * Nout", elements. The first Nout elements
+*        hold the constant offsets for the transformation outputs. The
+*        remaining elements hold the gradients. So if the Mapping has 2 inputs
+*        and 3 outputs the linear approximation to the forward transformation
+*        is:
+*
+c           X_out = fit[0] + fit[3]*X_in + fit[4]*Y_in
+f           X_out = fit(1) + fit(4)*X_in + fit(5)*Y_in
+*
+c           Y_out = fit[1] + fit[5]*X_in + fit[6]*Y_in
+f           Y_out = fit(2) + fit(6)*X_in + fit(7)*Y_in
+*
+c           Z_out = fit[2] + fit[7]*X_in + fit[8]*Y_in
+f           Z_out = fit(3) + fit(8)*X_in + fit(9)*Y_in
+*
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astLinearApprox()
+f     AST_LINEARAPPROX = LOGICAL
+*        If the forward transformation is sufficiently linear,
+c        a non-zero value is returned. Otherwise zero is returned
+f        .TRUE is returned. Otherwise .FALSE. is returned
+*        and the fit co-efficients are set to AST__BAD.
+
+*  Notes:
+*     - This function fits the Mapping's forward transformation. To fit
+*     the inverse transformation, the Mapping should be inverted using
+c     astInvert
+f     AST_INVERT
+*     before invoking this function.
+c     - A value of zero
+f     - A value of .FALSE.
+*     will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*--
+
+*  Implementation Deficiencies:
+*     Sub-classes which implement linear mappings should probably
+*     over-ride this function to get better accuracy and faster execution,
+*     but currently they do not.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *pset_in_f;       /* PointSet for input fitting points */
+   AstPointSet *pset_in_t;       /* PointSet for input test points */
+   AstPointSet *pset_out_f;      /* PointSet for output fitting points */
+   AstPointSet *pset_out_t;      /* PointSet for output test points */
+   double **ptr_in_f;            /* Input coordinate array pointers */
+   double **ptr_in_t;            /* Input coordinate array pointers */
+   double **ptr_out_f;           /* Output coordinate array pointers */
+   double **ptr_out_t;           /* Output coordinate array pointers */
+   double *grad;                 /* Pointer to matrix of gradients */
+   double *zero;                 /* Pointer to array of zero point values */
+   double diff;                  /* Difference in coordinate values */
+   double err;                   /* Sum of squared error */
+   double frac;                  /* Fraction of input coordinate range */
+   double in1;                   /* Input coordinate value */
+   double in2;                   /* Input coordinate value */
+   double indiff;                /* Difference in input coordinate values */
+   double out1;                  /* Output coordinate value */
+   double out2;                  /* Output coordinate value */
+   double x0;                    /* Coordinate of grid centre */
+   double y;                     /* Output coordinate (transformed) */
+   double yfit;                  /* Coordinate resulting from fit */
+   double z;                     /* Sum for calculating zero points */
+   int *vertex;                  /* Pointer to flag array for vertices */
+   int coord_in;                 /* Loop counter for input coordinates */
+   int coord_out;                /* Loop counter for output coordinates. */
+   int done;                     /* All vertices visited? */
+   int face1;                    /* Index of first face coordinates */
+   int face2;                    /* Index of second face coordinates */
+   int face;                     /* Loop counter for faces */
+   int ii;                       /* Index into gradient matrix */
+   int linear;                   /* Mapping is linear? */
+   int nc;                       /* Number of coeffs in fit */
+   int ndim_in;                  /* Number of Mapping inputs */
+   int ndim_out;                 /* Number of Mapping outputs */
+   int npoint;                   /* Number of test points required */
+   int point;                    /* Counter for points */
+   int result;                   /* Returned flag */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialisation. */
+   linear = 1;
+   grad = NULL;
+   zero = NULL;
+
+/* Get the number of Mapping output and inputs. */
+   ndim_in = astGetNin( this );
+   ndim_out = astGetNout( this );
+
+/* Store the number of coefficients in the fit.*/
+   nc = ( ndim_in + 1 ) * ndim_out;
+
+/* Create a PointSet to hold input coordinates and obtain a pointer
+   to its coordinate arrays. */
+   pset_in_f = astPointSet( 2 * ndim_in, ndim_in, "", status );
+   ptr_in_f = astGetPoints( pset_in_f );
+   if ( astOK ) {
+
+/* Set up and transform an initial set of points. */
+/* ---------------------------------------------- */
+/* Loop to set up input coordinates at the centre of each face of the
+   input grid, storing them in the PointSet created above. */
+      point = 0;
+      for ( face = 0; face < ( 2 * ndim_in ); face++ ) {
+         for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+            ptr_in_f[ coord_in ][ point ] =
+               0.5 * ( lbnd[ coord_in ] + ubnd[ coord_in ] );
+         }
+         ptr_in_f[ face / 2 ][ point ] = ( face % 2 ) ?
+                                          ubnd[ face / 2 ] : lbnd[ face / 2 ];
+         point++;
+      }
+   }
+
+/* Transform these coordinates into the output grid's coordinate system
+   and obtain an array of pointers to the resulting coordinate
+   data. */
+   pset_out_f = astTransform( this, pset_in_f, 1, NULL );
+   ptr_out_f = astGetPoints( pset_out_f );
+   if ( astOK ) {
+
+/* Fit a linear approximation to the points. */
+/* ----------------------------------------- */
+/* Obtain pointers to the locations in the fit coefficients array
+   where the gradients and zero points should be stored. */
+      grad = fit + ndim_out;
+      zero = fit;
+
+/* On the assumption that the transformation applied above is
+   approximately linear, loop to determine the matrix of gradients and
+   the zero points which describe it. */
+      ii = 0;
+      for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+         z = 0.0;
+         for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+
+/* Find the indices of opposite faces in each input dimension. */
+            face1 = 2 * coord_in;
+            face2 = face1 + 1;
+
+/* Obtain the input and output coordinates at these face centres. */
+            in1 = ptr_in_f[ coord_in ][ face1 ];
+            in2 = ptr_in_f[ coord_in ][ face2 ];
+            out1 = ptr_out_f[ coord_out ][ face1 ];
+            out2 = ptr_out_f[ coord_out ][ face2 ];
+
+/* Check whether any transformed coordinates are bad. If so, the
+   transformation cannot be linear, so give up trying to fit it. */
+            if ( ( out1 == AST__BAD ) || ( out2 == AST__BAD ) ) {
+               linear = 0;
+               break;
+            }
+
+/* If possible, determine the gradient along this dimension, storing
+   it in the appropriate element of the gradient matrix. */
+            indiff = in2 - in1;
+            if ( indiff != 0.0 ) {
+               grad[ ii++ ] = ( out2 - out1 ) / indiff;
+            } else {
+               grad[ ii++ ] = 0.0;
+            }
+
+/* Accumulate the sum used to determine the zero point. */
+            z += ( out1 + out2 );
+         }
+
+/* Also quit the outer loop if a linear fit cannot be obtained. */
+         if ( !linear ) break;
+
+/* Determine the average zero point from all dimensions. */
+         zero[ coord_out ] = z / (double) ( 2 * ndim_in );
+      }
+
+/* If a linear fit was obtained, its zero points will be appropriate
+   to an input coordinate system with an origin at the centre of the
+   input grid (we assume this to simplify the calculations above). To
+   correct for this, we transform the actual input coordinates of the
+   grid's centre through the matrix of gradients and subtract the
+   resulting coordinates from the zero point values. The zero points
+   are then correct for the actual output and input coordinate systems
+   we are using. */
+      if ( linear ) {
+         ii = 0;
+         for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+            for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+               x0 = 0.5 * ( lbnd[ coord_in ] + ubnd[ coord_in ] );
+               zero[ coord_out ] -= grad[ ii++ ] * x0;
+            }
+         }
+      }
+   }
+
+/* Annul the pointers to the PointSets used above. */
+   pset_out_f = astAnnul( pset_out_f );
+   pset_in_f = astAnnul( pset_in_f );
+
+/* Calculate the number of test points required. */
+/* --------------------------------------------- */
+/* If we have obtained a linear fit above, it will (by construction)
+   be exact at the centre of each face of the input grid. However, it
+   may not fit anywhere else. We therefore set up some test points to
+   determine if it is an adequate approximation elsewhere. */
+   if ( astOK && linear ) {
+
+/* Calculate the number of test points required to place one at each
+   vertex of the grid. */
+      npoint = 1;
+      for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+         npoint *= 2;
+      }
+
+/* Now calculate the total number of test points required, also
+   allowing one at the centre, one at half the distance to each face,
+   and one at half the distance to each vertex. */
+      npoint = 1 + 2 * ( ndim_in + npoint );
+
+/* Set up test points in the input coordinate system. */
+/* --------------------------------------------------- */
+/* Create a PointSet to hold the test coordinates and obtain an array
+   of pointers to its coordinate data. */
+      pset_in_t = astPointSet( npoint, ndim_in, "", status );
+      ptr_in_t = astGetPoints( pset_in_t );
+      if ( astOK ) {
+
+/* If the input array is 1-dimensional, the face and vertex positions
+   calculated below will co-incide. Therefore, we simply distribute
+   the required number of test points uniformly throughout the input
+   coordinate range (avoiding the end-points, where the fit has been
+   obtained). The coordinates are stored in the PointSet created
+   above. */
+         if ( ndim_in == 1 ) {
+            for ( point = 0; point < npoint; point++ ) {
+               frac = ( (double) ( point + 1 ) ) / (double) ( npoint + 1 );
+               ptr_in_t[ 0 ][ point ] = ( 1.0 - frac ) * lbnd[ 0 ] +
+                                         frac * ubnd[ 0 ];
+            }
+
+/* Otherwise, generate one point at the grid centre (offset slightly
+   since the exact centre may not be very representative). */
+         } else {
+            point = 0;
+            for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+               ptr_in_t[ coord_in ][ point ] =
+                  0.49 * lbnd[ coord_in ] + 0.51 * ubnd[ coord_in ];
+            }
+            point++;
+
+/* Similarly generate a point half way between the grid centre and the
+   centre of each face. Again introduce some small random offsets to break
+   any regularity in the grid. */
+            for ( face = 0; face < ( 2 * ndim_in ); face++ ) {
+               for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                  ptr_in_t[ coord_in ][ point ] =
+                     0.48 * lbnd[ coord_in ] + 0.52 * ubnd[ coord_in ];
+               }
+               ptr_in_t[ face / 2 ][ point ] =
+                        ( 0.51 * ( ( ( face % 2 ) ? ubnd[ face / 2 ] :
+                                             lbnd[ face / 2 ] ) ) +
+                          0.49 * ptr_in_t[ face / 2 ][ 0 ] );
+               point++;
+            }
+
+/* Allocate workspace and initialise flags for identifying the
+   vertices. */
+            vertex = astMalloc( sizeof( int ) * (size_t) ndim_in );
+            if ( astOK ) {
+               for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                  vertex[ coord_in ] = 0;
+               }
+
+/* Now loop to visit each input grid vertex. */
+               done = 0;
+               do {
+
+/* Generate a test point at each vertex. */
+                  for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                     ptr_in_t[ coord_in ][ point ] = vertex[ coord_in ] ?
+                                                       ubnd[ coord_in ] :
+                                                       lbnd[ coord_in ];
+
+/* Also place one half way between the grid centre and each vertex. */
+                     ptr_in_t[ coord_in ][ point + 1 ] =
+                             ( 0.52 * ptr_in_t[ coord_in ][ point ] +
+                               0.48 * ptr_in_t[ coord_in ][ 0 ] );
+                  }
+                  point += 2;
+
+/* Now update the array of vertex flags to identify the next vertex. */
+                  coord_in = 0;
+                  do {
+
+/* The least significant dimension which does not have its upper bound
+   as one of the vertex coordinates is changed to use its upper bound
+   in the next vertex. */
+                     if ( !vertex[ coord_in ] ) {
+                        vertex[ coord_in ] = 1;
+                        break;
+
+/* Any less significant dimensions whose upper bounds are already
+   being used are changed to use their lower bounds in the next
+   vertex. */
+                     } else {
+                        vertex[ coord_in ] = 0;
+
+/* All vertices have been visited when the most significant dimension
+   is changed back to using its lower bound. */
+                        done = ( ++coord_in == ndim_in );
+                     }
+                  } while ( !done );
+               } while ( !done );
+            }
+
+/* Free the workspace used for vertex flags. */
+            vertex = astFree( vertex );
+         }
+
+/* Transform the test points. */
+/* -------------------------- */
+/* Use the Mapping to transform the test points into the output grid's
+   coordinate system, obtaining a pointer to the resulting arrays of
+   output coordinates. */
+         pset_out_t = astTransform( this, pset_in_t, 1, NULL );
+         ptr_out_t = astGetPoints( pset_out_t );
+
+/* Test the linear fit for accuracy. */
+/* --------------------------------- */
+/* If OK so far, then loop to use this fit to transform each test
+   point and compare the result with the result of applying the
+   Mapping. */
+         if ( astOK ) {
+            for ( point = 0; point < npoint; point++ ) {
+
+/* Initialise the fitting error for the current point. */
+               err = 0.0;
+
+/* Obtain each output coordinate (produced by using the Mapping) in
+   turn and check that it is not bad. If it is, then the
+   transformation is not linear, so give up testing the fit. */
+               ii = 0;
+               for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+                  y = ptr_out_t[ coord_out ][ point ];
+                  if ( y == AST__BAD ) {
+                     linear = 0;
+                     break;
+                  }
+
+/* Apply the fitted transformation to the input coordinates to obtain
+   the approximate output coordinate value. */
+                  yfit = zero[ coord_out ];
+                  for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                     yfit += grad[ ii++ ] * ptr_in_t[ coord_in ][ point ];
+                  }
+
+/* Form the sum of squared differences between the Mapping's
+   transformation and the fit. */
+                  diff = ( y - yfit );
+                  err += diff * diff;
+               }
+
+/* Quit the outer loop if the Mapping is found to be non-linear. */
+               if ( !linear ) break;
+
+/* Test if the Cartesian distance between the true output coordinate
+   and the approximate one exceeds the accuracy tolerance. If this
+   happens for any test point, we declare the Mapping non-linear and
+   give up. */
+               if ( sqrt( err ) > tol ) {
+                  linear = 0;
+                  break;
+               }
+            }
+         }
+
+/* Annul the pointers to the PointSets used above. */
+         pset_out_t = astAnnul( pset_out_t );
+      }
+      pset_in_t = astAnnul( pset_in_t );
+   }
+
+/* If an error occurred, or the Mapping was found to be non-linear,
+   then set the coefficients to AST_BAD. Otherwise, set the returned flag
+   to indicate that the fit was succesful. */
+   if ( !astOK || !linear ) {
+      for( ii = 0; ii < nc; ii++ ) fit[ ii ] = AST__BAD;
+   } else {
+      result = 1;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double LocalMaximum( const MapData *mapdata, double acc, double fract,
+                            double x[], int *status ) {
+/*
+*  Name:
+*     LocalMaximum
+
+*  Purpose:
+*     Find a local maximum in a Mapping function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     double LocalMaximum( const MapData *mapdata, double acc, double fract,
+*                          double x[], int *status );
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function finds a local maximum in the Mapping function
+*     supplied.  It employs the modified simplex method (as
+*     implemented by UphillSimplex), but repeatedly re-starts the
+*     simplex algorithm and tests for convergence of successive
+*     maxima, so as to further improve robustness on difficult
+*     problems.
+
+*  Parameters:
+*     mapdata
+*        Pointer to a MapData structure describing the Mapping
+*        function, its coordinate constraints, etc.
+*     acc
+*        The required accuracy with which the maximum is to be found.
+*     fract
+*        A value between 0.0 and 1.0 which determines the initial step
+*        length along each coordinate axis. It should be given as a
+*        fraction of the difference between the upper and lower
+*        constraint values for each axis (as specified in the
+*        "mapdata" structure).
+*     x
+*        Pointer to an array of double containing the coordinates of
+*        an initial estimate of the position of the maximum. On exit,
+*        this will be updated to contain the best estimate of the
+*        maximum's position, as found by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The best estimate of the Mapping function's maximum value.
+
+*  Notes:
+*     - A value of AST__BAD will be returned, and no useful
+*     information about a solution will be produced, if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Constants: */
+   const int maxcall = 1500;     /* Maximum number of function evaluations */
+   const int maxiter = 5;        /* Maximum number of iterations */
+
+/* Local Variables: */
+   double *dx;                   /* Pointer to array of step lengths */
+   double err;                   /* Simplex error estimate */
+   double maximum;               /* Simplex maximum value */
+   double middle;                /* Middle coordinate between bounds */
+   double result;                /* Result value to return */
+   int coord;                    /* Loop counter for coordinates */
+   int done;                     /* Iterations complete? */
+   int iter;                     /* Loop counter for iterations */
+   int ncall;                    /* Number of function calls (junk) */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialise to avoid compiler warnings. */
+   err = 0.0;
+
+/* Allocate workspace. */
+   dx = astMalloc( sizeof( double ) * (size_t) mapdata->nin );
+
+/* Perform iterations to repeatedly identify a local maximum. */
+   for ( iter = 0; astOK && ( iter < maxiter ); iter++ ) {
+
+/* Set up initial step lengths along each coordinate axis, adjusting
+   their signs to avoid placing points outside the coordinate
+   constraints (i.e. step away from the closer boundary on each
+   axis). */
+      for ( coord = 0; coord < mapdata->nin; coord++ ) {
+         middle = 0.5 * ( mapdata->lbnd[ coord ] + mapdata->ubnd[ coord ] );
+         dx[ coord ] = fract * ( mapdata->ubnd[ coord ] -
+                                 mapdata->lbnd[ coord ] );
+         if ( x[ coord ] > middle ) dx[ coord ] = -dx[ coord ];
+      }
+
+/* Find an approximation to a local maximum using the simplex method
+   and check for errors. */
+      maximum = UphillSimplex( mapdata, acc, maxcall, dx, x, &err, &ncall, status );
+      if ( astOK ) {
+
+/* Use this maximum value if no previous maximum has been found. */
+         if ( result == AST__BAD ) {
+            result = maximum;
+
+/* Otherwise use it only if it improves on the previous maximum. */
+         } else if ( maximum >= result ) {
+
+/* We iterate, re-starting the simplex algorithm from its previous
+   best position so as to guard against premature false
+   convergence. Iterations continue until the improvement in the
+   maximum is no greater than the required accuracy (and the simplex
+   algorithm itself has converged to the required accuracy). Note when
+   iterations should cease. */
+            done = ( ( ( maximum - result ) <= acc ) && ( err <= acc ) );
+
+/* Store the best maximum and quit iterating if appropriate. */
+            result = maximum;
+            if ( done ) break;
+         }
+
+/* Otherwise, decrement the initial step size for the next iteration. */
+         fract /= 1000.0;
+      }
+   }
+
+/* Free the workspace. */
+   dx = astFree( dx );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* return the result. */
+   return result;
+}
+
+static void MapBox( AstMapping *this,
+                    const double lbnd_in[], const double ubnd_in[],
+                    int forward, int coord_out,
+                    double *lbnd_out, double *ubnd_out,
+                    double xl[], double xu[], int *status ) {
+/*
+*+
+*  Name:
+*     astMapBox
+
+*  Purpose:
+*     Find a bounding box for a Mapping.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void astMapBox( AstMapping *this,
+*                     const double lbnd_in[], const double ubnd_in[],
+*                     int forward, int coord_out,
+*                     double *lbnd_out, double *ubnd_out,
+*                     double xl[], double xu[] );
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function allows you to find the "bounding box" which just
+*     encloses another box after it has been transformed by a Mapping
+*     (using either its forward or inverse transformation). A typical
+*     use might be to calculate the size which an image would have
+*     after being transformed by the Mapping.
+*
+*     The function works on one dimension at a time. When supplied
+*     with the lower and upper bounds of a rectangular region (box) of
+*     input coordinate space, it finds the lowest and highest values
+*     taken by a nominated output coordinate within that
+*     region. Optionally, it also returns the input coordinates where
+*     these bounding values are attained. It should be used repeatedly
+*     if the extent of the bounding box is required in more than one
+*     dimension.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     lbnd_in
+*        Pointer to an array of double, with one element for each
+*        Mapping input coordinate. This should contain the lower bound
+*        of the input box in each dimension.
+*     ubnd_in
+*        Pointer to an array of double, with one element for each
+*        Mapping input coordinate. This should contain the upper bound
+*        of the input box in each dimension.
+*
+*        Note that it is permissible for the lower bound to exceed the
+*        corresponding upper bound, as the values will simply be
+*        swapped before use.
+*     forward
+*        If this value is non-zero, then the Mapping's forward
+*        transformation will be used to transform the input
+*        box. Otherwise, its inverse transformation will be used.
+*
+*        (If the inverse transformation is selected, then references
+*        to "input" and "output" coordinates in this description
+*        should be transposed. For example, the size of the "lbnd_in"
+*        and "ubnd_in" arrays should match the number of output
+*        coordinates, as given by the Mapping's Nout attribute.)
+*     coord_out
+*        The (zero-based) index of the output coordinate for which the
+*        lower and upper bounds are required.
+*     lbnd_out
+*        Pointer to a double in which to return the lowest value taken
+*        by the nominated output coordinate within the specified
+*        region of input coordinate space.
+*     ubnd_out
+*        Pointer to a double in which to return the highest value
+*        taken by the nominated output coordinate within the specified
+*        region of input coordinate space.
+*     xl
+*        An optional pointer to an array of double, with one element
+*        for each Mapping input coordinate. If given, this array will
+*        be filled with the coordinates of an input point (although
+*        not necessarily a unique one) for which the nominated output
+*        coordinate takes the lower bound value returned in
+*        "*lbnd_out".
+*
+*        If these coordinates are not required, a NULL pointer may be
+*        supplied.
+*     xu
+*        An optional pointer to an array of double, with one element
+*        for each Mapping input coordinate. If given, this array will
+*        be filled with the coordinates of an input point (although
+*        not necessarily a unique one) for which the nominated output
+*        coordinate takes the upper bound value returned in
+*        "*ubnd_out".
+*
+*        If these coordinates are not required, a NULL pointer may be
+*        supplied.
+
+*  Notes:
+*     - Any input points which are transformed by the Mapping to give
+*     output coordinates containing the value AST__BAD are regarded as
+*     invalid and are ignored, They will make no contribution to
+*     determining the output bounds, even although the nominated
+*     output coordinate might still have a valid value at such points.
+*     - An error will occur if the required output bounds cannot be
+*     found. Typically, this might occur if all the input points which
+*     the function considers turn out to be invalid (see above). The
+*     number of points considered before generating such an error is
+*     quite large, however, so this is unlikely to occur by accident
+*     unless valid points are restricted to a very small subset of the
+*     input coordinate space.
+*     - The values returned via "lbnd_out", "ubnd_out", "xl" and "xu"
+*     will be set to the value AST__BAD if this function should fail
+*     for any reason. Their initial values on entry will not be
+*     altered if the function is invoked with the global error status
+*     set.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic astMapBox method available
+*     via the protected interface to the Mapping class. The public
+*     interface to this method is provided by the astMapBoxId_
+*     function.
+*/
+
+/* Local Variables: */
+   MapData mapdata;              /* Structure to describe Mapping function */
+   double *x_l;                  /* Pointer to coordinate workspace */
+   double *x_u;                  /* Pointer to coordinate workspace */
+   double lbnd;                  /* Required lower bound */
+   double ubnd;                  /* Required upper bound */
+   int coord;                    /* Loop counter for coordinates. */
+   int nin;                      /* Effective number of input coordinates */
+   int nout;                     /* Effective number of output coordinates */
+   int refine;                   /* Can bounds be refined? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialisation to avoid compiler warnings. */
+   lbnd = AST__BAD;
+   ubnd = AST__BAD;
+
+/* Obtain the effective numbers of input and output coordinates for
+   the Mapping, taking account of which transformation is to be
+   used. */
+   nin = forward ? astGetNin( this ) : astGetNout( this );
+   nout = forward ? astGetNout( this ) : astGetNin( this );
+
+/* Check that the output coordinate index supplied is valid and report
+   an error if it is not. Use public (one-based) coordinate numbering
+   in the error message. */
+   if ( astOK ) {
+      if ( ( coord_out < 0 ) || ( coord_out >= nout ) ) {
+         astError( AST__BADCI, "astMapBox(%s): Output coordinate index (%d) "
+                   "invalid - it should be in the range 1 to %d.", status,
+                   astGetClass( this ), coord_out + 1, nout );
+      }
+   }
+
+/* Initialise a MapData structure to describe the Mapping function
+   whose limits are to be found.  Since it may be evaluated many
+   times, we attempt to simplify the Mapping supplied. */
+   if ( astOK ) {
+      mapdata.mapping = astSimplify( this );
+
+/* Store the number of input/output coordinates and the index of the
+   output coordinate in which we are interested. */
+      mapdata.nin = nin;
+      mapdata.nout = nout;
+      mapdata.coord = coord_out;
+
+/* Note which Mapping transformation is being used. */
+      mapdata.forward = forward;
+
+/* Store pointers to arrays which will contain the input coordinate
+   bounds. */
+      mapdata.lbnd = astMalloc( sizeof( double ) * (size_t) nin );
+      mapdata.ubnd = astMalloc( sizeof( double ) * (size_t) nin );
+
+/* Create PointSets for passing coordinate data to and from the
+   Mapping. */
+      mapdata.pset_in = astPointSet( 1, nin, "", status );
+      mapdata.pset_out = astPointSet( 1, nout, "", status );
+
+/* Obtain pointers to these PointSets' coordinate arrays. */
+      mapdata.ptr_in = astGetPoints( mapdata.pset_in );
+      mapdata.ptr_out = astGetPoints( mapdata.pset_out );
+
+/* Allocate workspace for the returned input coordinates. */
+      x_l = astMalloc( sizeof( double ) * (size_t) nin );
+      x_u = astMalloc( sizeof( double ) * (size_t) nin );
+      if ( astOK ) {
+
+/* Initialise the output bounds and corresponding input coordinates to
+   "unknown". */
+         for ( coord = 0; coord < nin; coord++ ) {
+            x_l[ coord ] = AST__BAD;
+            x_u[ coord ] = AST__BAD;
+
+/* Initialise the input bounds, ensuring they are the correct way
+   around (if not already supplied this way). */
+            mapdata.lbnd[ coord ] = ( lbnd_in[ coord ] < ubnd_in[ coord ] ) ?
+                                      lbnd_in[ coord ] : ubnd_in[ coord ];
+            mapdata.ubnd[ coord ] = ( ubnd_in[ coord ] > lbnd_in[ coord ] ) ?
+                                      ubnd_in[ coord ] : lbnd_in[ coord ];
+         }
+
+/* First examine a set of special input points to obtain an initial
+   estimate of the required output bounds. Do this only so long as the
+   number of points involved is not excessive. */
+         if ( nin <= 12 ) {
+            refine = SpecialBounds( &mapdata, &lbnd, &ubnd, x_l, x_u, status );
+         } else {
+            refine = 1;
+         }
+
+/* Then attempt to refine this estimate using a global search
+   algorithm. */
+         if( refine ) GlobalBounds( &mapdata, &lbnd, &ubnd, x_l, x_u, status );
+
+/* If an error occurred, generate a contextual error message. */
+         if ( !astOK ) {
+            astError( astStatus, "Unable to find a bounding box for a %s.", status,
+                      astGetClass( this ) );
+         }
+      }
+
+/* Return the output bounds and, if required, the input coordinate
+   values which correspond with them. */
+      if ( astOK ) {
+         *lbnd_out = lbnd;
+         *ubnd_out = ubnd;
+         for ( coord = 0; coord < nin; coord++ ) {
+            if ( xl ) xl[ coord ] = x_l[ coord ];
+            if ( xu ) xu[ coord ] = x_u[ coord ];
+         }
+      }
+
+/* Annul the simplified Mapping pointer and the temporary
+   PointSets. Also free the workspace. */
+      mapdata.mapping = astAnnul( mapdata.mapping );
+      mapdata.lbnd = astFree( mapdata.lbnd );
+      mapdata.ubnd = astFree( mapdata.ubnd );
+      mapdata.pset_in = astAnnul( mapdata.pset_in );
+      mapdata.pset_out = astAnnul( mapdata.pset_out );
+      x_l = astFree( x_l );
+      x_u = astFree( x_u );
+   }
+
+/* If an error occurred, then return bad bounds values and
+   coordinates. */
+   if ( !astOK ) {
+      *lbnd_out = AST__BAD;
+      *ubnd_out = AST__BAD;
+      for ( coord = 0; coord < nin; coord++ ) {
+         if ( xl ) xl[ coord ] = AST__BAD;
+         if ( xu ) xu[ coord ] = AST__BAD;
+      }
+   }
+}
+
+static double MapFunction( const MapData *mapdata, const double in[],
+                           int *ncall, int *status ) {
+/*
+*  Name:
+*     MapFunction
+
+*  Purpose:
+*     Return the value of a selected transformed coordinate.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     double MapFunction( const MapData *mapdata, const double in[],
+*                         int *ncall, int *status );
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function takes a set of input coordinates and applies a
+*     Mapping's coordinate transformation to them. It then returns the
+*     value of one of the transformed coordinates.
+*
+*     It is provided for use by optimisation functions (e.g. those
+*     used for finding bounding boxes). The Mapping to be used and
+*     associated parameters (such as constraints on the range of input
+*     coordinates and the index of the output coordinate to be
+*     returned) are supplied in a MapData structure. The value
+*     returned will be negated if the "negate" component of this
+*     structure is non-zero.
+*
+*     The value AST__BAD will be returned by this function if the
+*     input coordinates lie outside the constrained range given in
+*     the MapData structure, or if any of the transformed output
+*     coordinates is bad.
+
+*  Parameters:
+*     mapdata
+*        Pointer to a MapData structure which describes the Mapping to
+*        be used.
+*     in
+*        A double array containing the input coordinates of a single point.
+*     ncall
+*        Pointer to an int containing a count of the number of times
+*        the Mapping's coordinate transformation has been used. This
+*        value will be updated to reflect any use made by this
+*        function. Normally, this means incrementing the value by 1,
+*        but this will be omitted if the input coordinates supplied
+*        are outside the constrained range so that no transformation
+*        is performed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The selected output coordinate value, or AST__BAD, as appropriate.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   double result;                /* Result to be returned */
+   int bad;                      /* Output coordinates invalid? */
+   int coord_in;                 /* Loop counter for input coordinates */
+   int coord_out;                /* Loop counter for output coordinates */
+   int outside;                  /* Input point outside bounds? */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* See if the input point lies outside the required bounds. */
+   outside = 0;
+   for ( coord_in = 0; coord_in < mapdata->nin; coord_in++ ) {
+      if ( ( in[ coord_in ] < mapdata->lbnd[ coord_in ] ) ||
+           ( in[ coord_in ] > mapdata->ubnd[ coord_in ] ) ) {
+         outside = 1;
+         break;
+      }
+
+/* Also store the input coordinates in the memory associated with the
+   Mapping's input PointSet. */
+      mapdata->ptr_in[ coord_in ][ 0 ] = in[ coord_in ];
+   }
+
+/* If the input coordinates are within bounds, transform them, using the
+   PointSets identified in the "mapdata" structure. */
+   if ( !outside ) {
+      (void) astTransform( mapdata->mapping, mapdata->pset_in,
+                           mapdata->forward, mapdata->pset_out );
+
+/* Increment the number of calls to astTransform and check the error
+   status. */
+      ( *ncall )++;
+      if ( astOK ) {
+
+/* If OK, test if any of the output coordinates is bad. */
+         bad = 0;
+         for ( coord_out = 0; coord_out < mapdata->nout; coord_out++ ) {
+            if ( mapdata->ptr_out[ coord_out ][ 0 ] == AST__BAD ) {
+               bad = 1;
+               break;
+            }
+         }
+
+/* If not, then extract the required output coordinate, negating it if
+   necessary. */
+         if ( !bad ) {
+            result = mapdata->ptr_out[ mapdata->coord ][ 0 ];
+            if ( mapdata->negate ) result = -result;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int MapList( AstMapping *this, int series, int invert, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*+
+*  Name:
+*     astMapList
+
+*  Purpose:
+*     Decompose a Mapping into a sequence of simpler Mappings.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int astMapList( AstMapping *this, int series, int invert, int *nmap,
+*                      AstMapping ***map_list, int **invert_list )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function decomposes a Mapping (which, in derived classes,
+*     may be a compound Mapping) into a sequence of simpler Mappings
+*     which may be applied in sequence to achieve the same effect. The
+*     Mapping is decomposed as far as possible, but it is not
+*     guaranteed that this will necessarily yield any more than one
+*     Mapping, which may actually be the original one supplied.
+*
+*     This function is provided to support both the simplification of
+*     compound Mappings, and the analysis of Mapping structure so that
+*     particular forms can be recognised.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be decomposed (the Mapping is not
+*        actually modified by this function).
+*     series
+*        If this value is non-zero, an attempt will be made to
+*        decompose the Mapping into a sequence of equivalent Mappings
+*        which can be applied in series (i.e. one after the other). If
+*        it is zero, the decomposition will instead yield Mappings
+*        which can be applied in parallel (i.e. on successive sub-sets
+*        of the input/output coordinates).
+*     invert
+*        The value to which the Mapping's Invert attribute is to be
+*        (notionally) set before performing the
+*        decomposition. Normally, the value supplied here will be the
+*        actual Invert value obtained from the Mapping (e.g. using
+*        astGetInvert).  Sometimes, however, when a Mapping is
+*        encapsulated within another structure, that structure may
+*        retain an Invert value (in order to prevent external
+*        interference) which should be used instead.
+*
+*        Note that the actual Invert value of the Mapping supplied is
+*        not used (or modified) by this function.
+*     nmap
+*        The address of an int which holds a count of the number of
+*        individual Mappings in the decomposition. On entry, this
+*        should count the number of Mappings already in the
+*        "*map_list" array (below). On exit, it is updated to include
+*        any new Mappings appended by this function.
+*     map_list
+*        Address of a pointer to an array of Mapping pointers. On
+*        entry, this array pointer should either be NULL (if no
+*        Mappings have yet been obtained) or should point at a
+*        dynamically allocated array containing Mapping pointers
+*        ("*nmap" in number) which have been obtained from a previous
+*        invocation of this function.
+*
+*        On exit, the dynamic array will be enlarged to contain any
+*        new Mapping pointers that result from the decomposition
+*        requested. These pointers will be appended to any previously
+*        present, and the array pointer will be updated as necessary
+*        to refer to the enlarged array (any space released by the
+*        original array will be freed automatically).
+*
+*        The new Mapping pointers returned will identify a sequence of
+*        Mappings which, when applied in order, will perform a forward
+*        transformation equivalent to that of the original Mapping
+*        (after its Invert flag has first been set to the value
+*        requested above). The Mappings should be applied in series or
+*        in parallel according to the type of decomposition requested.
+*
+*        All the Mapping pointers returned by this function should be
+*        annulled by the caller, using astAnnul, when no longer
+*        required. The dynamic array holding these pointers should
+*        also be freed, using astFree.
+*     invert_list
+*        Address of a pointer to an array of int. On entry, this array
+*        pointer should either be NULL (if no Mappings have yet been
+*        obtained) or should point at a dynamically allocated array
+*        containing Invert attribute values ("*nmap" in number) which
+*        have been obtained from a previous invocation of this
+*        function.
+*
+*        On exit, the dynamic array will be enlarged to contain any
+*        new Invert attribute values that result from the
+*        decomposition requested. These values will be appended to any
+*        previously present, and the array pointer will be updated as
+*        necessary to refer to the enlarged array (any space released
+*        by the original array will be freed automatically).
+*
+*        The new Invert values returned identify the values which must
+*        be assigned to the Invert attributes of the corresponding
+*        Mappings (whose pointers are in the "*map_list" array) before
+*        they are applied. Note that these values may differ from the
+*        actual Invert attribute values of these Mappings, which are
+*        not relevant.
+*
+*        The dynamic array holding these values should be freed by the
+*        caller, using astFree, when no longer required.
+
+*  Returned Value:
+*     A non-zero value is returned if the supplied Mapping contained any
+*     inverted CmpMaps.
+
+*  Notes:
+*     - It is unspecified to what extent the original Mapping and the
+*     individual (decomposed) Mappings are
+*     inter-dependent. Consequently, the individual Mappings cannot be
+*     modified without risking modification of the original.
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then the *nmap value, the
+*     list of Mapping pointers and the list of Invert values will all
+*     be returned unchanged.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Since we are dealing with a basic Mapping, only one new Mapping
+   pointer will be returned. Extend the dynamic arrays to accommodate
+   this Mapping. */
+   *map_list = astGrow( *map_list, *nmap + 1, sizeof( AstMapping * ) );
+   *invert_list = astGrow( *invert_list, *nmap + 1, sizeof( int ) );
+   if ( astOK ) {
+
+/* Return the invert flag value for the Mapping and a clone of the
+   Mapping pointer. */
+      ( *invert_list )[ *nmap ] = ( invert != 0 );
+      ( *map_list )[ *nmap ] = astClone( this );
+
+/* If OK, return the new Mapping count. */
+      if ( astOK ) ( *nmap )++;
+   }
+
+   return 0;
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*+
+*  Name:
+*     astMapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int astMapMerge( AstMapping *this, int where, int series, int *nmap,
+*                      AstMapping ***map_list, int **invert_list )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated Mapping in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated Mapping with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated Mapping which is to be merged with
+*        its neighbours. This should be a cloned copy of the Mapping
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        Mapping it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated Mapping resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* This is the default method which is inherited by all Mappings which
+   do not explicitly provide their own simplification method. Return
+   -1 to indicate that no simplification is provided. */
+   return -1;
+}
+
+static int *MapSplit( AstMapping *this, int nin, const int *in,
+                      AstMapping **map, int *status ){
+/*
+*+
+*  Name:
+*     astMapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     Mapping.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int *astMapSplit( AstMapping *this, int nin, const int *in,
+*                       AstMapping **map )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing Mapping. This is only possible if the specified inputs
+*     correspond to some subset of the Mapping outputs. That is, there
+*     must exist a subset of the Mapping outputs for which each output
+*     depends only on the selected Mapping inputs, and not on any of the
+*     inputs which have not been selected. Also, any output which is not in
+*     this subset must not depend on any of the selected inputs. If these
+*     conditions are not met by the supplied Mapping, then a NULL Mapping
+*     is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be split (the Mapping is not
+*        actually modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied Mapping, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be differetn to "nin"). A NULL pointer will be
+*        returned if the supplied Mapping has no subset of outputs which
+*        depend only on the selected inputs. The returned Mapping is a
+*        deep copy of the required parts of the supplied Mapping.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied Mapping. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic astMapSplit method available
+*     via the protected interface to the Mapping class. The public
+*     interface to this method is provided by the astMapSplitId_
+*     function.
+*/
+
+/* Local Variables: */
+   AstCmpMap *rmap;           /* Unsimplified result mapping */
+   AstPermMap *pm;            /* PermMap which rearranges the inputs */
+   int *outperm;              /* PermMap output axis permutation array */
+   int *result;               /* Pointer to returned array */
+   int iin;                   /* Input index */
+   int iout;                  /* Output index */
+   int mapnin;                /* Number of Mapping inputs */
+   int nout;                  /* No of outputs */
+   int ok;                    /* Can the supplied "in" array be used? */
+   int perm;                  /* Are the inputs permuted? */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Verify the input axis indices.*/
+   mapnin = astGetNin( this );
+   for( iin = 0; iin < nin; iin++ ){
+      if( in[ iin ] < 0 || in[ iin ] >= mapnin ) {
+         astError( AST__AXIIN, "astMapSplit(%s): One of the supplied Mapping "
+                   "input indices has value %d which is invalid; it should "
+                   "be in the range 1 to %d.", status, astGetClass( this ),
+                   in[ iin ] + 1, mapnin );
+         break;
+      }
+   }
+
+/* Since we are dealing with a basic Mapping, we can only create the
+   required output Mapping if all inputs are being selected. */
+   if( nin == mapnin ) {
+
+/* The inputs may have been selected in a different order to that in
+   which they occur in the supplied Mapping. We therefore create a
+   PermMap which rearranges the inputs into the order they have in the
+   supplied Mapping. The supplied "in" array can act as the PermMap's
+   "inperm" array. Allocate memory for the "outperm" array. */
+      outperm = astMalloc( sizeof(int)*(size_t) nin );
+      if( astOK ) {
+
+/* Store the input index for each output in the outperm array and check that
+   each input has been selected once and only once. Also set a flag
+   indicating if a PermMap is needed. */
+         perm = 0;
+         ok = 1;
+         for( iout = 0; iout < nin; iout++ ) outperm[ iout ] = -1;
+         for( iin = 0; iin < nin; iin++ ) {
+            iout = in[ iin ];
+            if( outperm[ iout ] != -1 ) {
+               ok = 0;
+               break;
+            } else {
+               outperm[ iout ] = iin;
+            }
+         }
+         for( iout = 0; iout < nin; iout++ ) {
+            if( outperm[ iout ] == -1 ) {
+               ok = 0;
+               break;
+            } else if( outperm[ iout ] != iout ) {
+               perm = 1;
+            }
+         }
+         if( ok ) {
+
+/* Allocate the array to hold the returned output indices. */
+            nout = astGetNout( this );
+            result = astMalloc( sizeof(int)*(size_t) nout );
+            if( astOK ) {
+
+/* The outputs are copied from the supplied Mapping. */
+               for( iout = 0; iout < nout; iout++ ) result[ iout ] = iout;
+
+/* If the inputs are to be permuted, create the PermMap. */
+               if( perm ) {
+                  pm = astPermMap( nin, in, nin, outperm, NULL, "", status );
+
+/* The returned Mapping is a series CmpMap containing this PermMap
+   followed by the supplied Mapping. */
+                  rmap = astCmpMap( pm, this, 1, "", status );
+                  *map = astSimplify( rmap );
+                  rmap = astAnnul( rmap );
+
+/* Annul the PermMap pointer. */
+                  pm = astAnnul( pm );
+
+/* If no input permutation is needed, the resturned Mapping is just the
+   supplied Mapping. */
+               } else {
+                  *map = astClone( this );
+               }
+            }
+         }
+
+/* Free resources. */
+         outperm = astFree( outperm );
+      }
+   }
+
+/* Free resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static double MatrixDet( int nrow, int ncol, const double *matrix, int *status ){
+/*
+*  Name:
+*     MatrixDet
+
+*  Purpose:
+*     Return the determinant of a matrix.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     double MatrixDet( int nrow, int ncol, const double *matrix, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function returns the determinant of the supplied matrix. Any
+*     rows or columns that hold only zeros or AST_BAD values are first
+*     removed from the matrix. If the resulting matrix is not square, a
+*     value of AST__BAD is returned for the determinant.
+
+*  Parameters:
+*     nrow
+*        The number of rows in the matrix.
+*     ncol
+*        The number of columns in the matrix.
+*     matrix
+*        The matrix element values. The first row of "ncol" elements
+*        should be supplied first, followed by the second row, etc.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The determinant, or AST__BAD if the determinant could not be
+*     caclculated.
+*/
+
+/* Local Variables: */
+   const double *sqmat;
+   const double *m;
+   double *a;
+   double *y;
+   double result;
+   int *iw;
+   int *usecol;
+   int *userow;
+   int i;
+   int icol;
+   int irow;
+   int jf;
+   int ncoluse;
+   int ndim;
+   int nrowuse;
+
+/* Initialise */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise... */
+   sqmat = NULL;
+   nrowuse = 0;
+   ncoluse = 0;
+
+/* Flag any rows and columns that should be ignored because they contain
+   only bad values or zeros. */
+   userow = astCalloc( nrow, sizeof( *userow ) );
+   usecol = astCalloc( ncol, sizeof( *userow ) );
+   if( astOK ) {
+      m = matrix;
+      for( irow = 0; irow < nrow; irow++ ) {
+         for( icol = 0; icol < ncol; icol++,m++ ) {
+            if( *m != AST__BAD && *m != 0.0 ) {
+               usecol[ icol ] = 1;
+               userow[ irow ] = 1;
+            }
+         }
+      }
+
+/* Find the number of usable rows and columns. */
+      for( irow = 0; irow < nrow; irow++ ) {
+         if( userow[ irow ] ) nrowuse++;
+      }
+
+      for( icol = 0; icol < ncol; icol++ ) {
+         if( usecol[ icol ] ) ncoluse++;
+      }
+   }
+
+/* Return AST__BAD if the resulting matrix is not square. */
+   if( ncoluse == nrowuse ) {
+      ndim = ncoluse;
+
+/* If any rows or columns contained just bad or zero values, create a new
+   matrix that excludes them. */
+      if( ncol > ndim || nrow > ndim ) {
+         sqmat = astMalloc( ndim*ndim*sizeof(*sqmat) );
+         if( astOK ) {
+            m = matrix;
+            a = (double *) sqmat;
+            for( irow = 0; irow < nrow; irow++ ) {
+               if( userow[ irow ] ) {
+                  for( icol = 0; icol < ncol; icol++,m++ ) {
+                     if( usecol[ icol ] ) *(a++) = *m;
+                  }
+               } else {
+                  m += ncol;
+               }
+            }
+         }
+
+/* If no rows or columns contained just bad values, use the supplied
+   matrix. */
+      } else {
+         sqmat = matrix;
+      }
+
+/* Calculate the determinant of the modified matrix */
+      if( ndim == 1 ) {
+         result = sqmat[ 0 ];
+
+      } else if( ndim == 2 ) {
+         result = sqmat[ 0 ]*sqmat[ 3 ] - sqmat[ 1 ]*sqmat[ 2 ];
+
+      } else {
+         a = astStore( NULL, sqmat, sizeof( double )*(size_t) (ndim*ndim) );
+         iw = astMalloc( sizeof( int )*(size_t) ndim );
+         y = astMalloc( sizeof( double )*(size_t) ndim );
+         if( y ) {
+            for( i = 0; i < ndim; i++ ) y[ i ] = 1.0;
+            palDmat( ndim, a, y, &result, &jf, iw );
+         }
+         y = astFree( y );
+         iw = astFree( iw );
+         a = astFree( a );
+      }
+
+   }
+
+/* Free the square matrix if it was allocated here. */
+   if( sqmat != matrix ) sqmat = astFree( (void *) sqmat );
+
+/* Free the usable row/column flags. */
+   userow = astFree( userow );
+   usecol = astFree( usecol );
+
+   return result;
+}
+
+static double MaxD( double a, double b, int *status ) {
+/*
+*  Name:
+*     MaxD
+
+*  Purpose:
+*     Return the maximum of two double values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     double MaxD( double a, double b, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function returns the maximum of two double values.
+
+*  Parameters:
+*     a
+*        The first value.
+*     b
+*        The second value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The maximum.
+*/
+
+/* Return the larger value. */
+   return ( a > b ) ? a : b;
+}
+
+static int MaxI( int a, int b, int *status ) {
+/*
+*  Name:
+*     MaxI
+
+*  Purpose:
+*     Return the maximum of two integer values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MaxI( int a, int b, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function returns the maximum of two integer values.
+
+*  Parameters:
+*     a
+*        The first value.
+*     b
+*        The second value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The maximum.
+*/
+
+/* Return the larger value. */
+   return ( a > b ) ? a : b;
+}
+
+static int MinI( int a, int b, int *status ) {
+/*
+*  Name:
+*     MinI
+
+*  Purpose:
+*     Return the minimum of two integer values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MinI( int a, int b, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function returns the minimum of two integer values.
+
+*  Parameters:
+*     a
+*        The first value.
+*     b
+*        The second value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The minimum.
+*/
+
+/* Return the smaller value. */
+   return ( a < b ) ? a : b;
+}
+
+static double NewVertex( const MapData *mapdata, int lo, double scale,
+                         double x[], double f[], int *ncall, double xnew[], int *status ) {
+/*
+*  Name:
+*     NewVertex
+
+*  Purpose:
+*     Locate a new vertex for a simplex.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     double NewVertex( const MapData *mapdata, int lo, double scale,
+*                       double x[], double f[], int *ncall, double xnew[], int *status );
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function is provided for use during optimisation of a
+*     Mapping function using the simplex method. It generates the
+*     coordinates of a new simplex vertex and evaluates the Mapping
+*     function at that point.  If the function's value is better then
+*     (i.e. larger than) the value at the previously worst vertex,
+*     then it is used to replace that vertex.
+
+*  Parameters:
+*     mapdata
+*        Pointer to a MapData structure which describes the Mapping
+*        function to be used.
+*     lo
+*        The (zero-based) index of the simplex vertex which initially
+*        has the worst (lowest) value.
+*     scale
+*        The scale factor to be used to generate the new vertex. The
+*        distance of the worst vertex from the centre of the face
+*        opposite it is scaled by this factor to give the new vertex
+*        position. Negative factors result in reflection through this
+*        opposite face.
+*     x
+*        An array of double containing the coordinates of the vertices
+*        of the simplex. The coordinates of the first vertex are
+*        stored first, then those of the second vertex, etc. This
+*        array will be updated by this function if the new vertex is
+*        used to replace an existing one.
+*     f
+*        An array of double containing the Mapping function values at
+*        each vertex of the simplex. This array will be updated by
+*        this function if the new vertex is used to replace an
+*        existing one.
+*     ncall
+*        Pointer to an int containing a count of the number of times
+*        the Mapping function has been invoked. This value will be
+*        updated to reflect the actions of this function.
+*     xnew
+*        An array of double with one element for each input coordinate
+*        of the Mapping function. This is used as workspace.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Mapping function value at the new vertex. This value is
+*     returned whether or not the new vertex replaces an existing one.
+
+*  Notes:
+*     - A value of AST__BAD will be returned by this function if it is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*     - A value of AST__BAD will also be returned if the new vertex
+*     lies outside the constrained range of input coordinates
+*     associated with the Mapping function (as specified in the
+*     MapData structure supplied) or if any of the transformed output
+*     coordinates produced by the underlying Mapping is bad. In either
+*     case the new vertex will not be used to replace an existing one.
+*/
+
+/* Local Variables: */
+   double fnew;                  /* Function value at new vertex */
+   double xface;                 /* Coordinate of centre of magnification */
+   int coord;                    /* Loop counter for coordinates */
+   int ncoord;                   /* Number of coordinates */
+   int nvertex;                  /* Number of simplex vertices */
+   int vertex;                   /* Loop counter for vertices */
+
+/* Initialise. */
+   fnew = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return fnew;
+
+/* Obtain the number of Mapping input coordinates from the MapData
+   structure and calculate the number of simplex vertices. */
+   ncoord = mapdata->nin;
+   nvertex = ncoord + 1;
+
+/* Loop to obtain each coordinate of the new vertex. */
+   for ( coord = 0; coord < ncoord; coord++ ) {
+
+/* Loop over all vertices except the lowest one and average their
+   coordinates. This gives the coordinate of the centre of the face
+   opposite the lowest vertex, which will act as the centre of
+   magnification. */
+      xface = 0.0;
+      for ( vertex = 0; vertex < nvertex; vertex++ ) {
+         if ( vertex != lo ) {
+
+/* Divide each coordinate by the number of vertices as the sum is
+   accumulated in order to minimise the risk of overflow. */
+            xface += x[ vertex * ncoord + coord ] /
+                     ( (double ) ( nvertex - 1 ) );
+         }
+      }
+
+/* Magnify the lowest vertex's distance from this point by the
+   required factor to give the coordinates of the new vertex. */
+      xnew[ coord ] = xface + ( x[ lo * ncoord + coord ] - xface ) * scale;
+   }
+
+/* Evaluate the Mapping function at the new vertex. */
+   fnew = MapFunction( mapdata, xnew, ncall, status );
+
+/* If the result is not bad and exceeds the previous value at the
+   lowest vertex, then replace the lowest vertex with this new one. */
+   if ( astOK && ( fnew != AST__BAD ) && ( fnew > f[ lo ] ) ) {
+      for ( coord = 0; coord < ncoord; coord++ ) {
+         x[ lo * ncoord + coord ] = xnew[ coord ];
+      }
+      f[ lo ] = fnew;
+   }
+
+/* Return the value at the new vertex. */
+   return fnew;
+}
+
+static int QuadApprox( AstMapping *this,  const double lbnd[2],
+                       const double ubnd[2], int nx, int ny, double *fit,
+                       double *rms, int *status ){
+/*
+*++
+*  Name:
+c     astQuadApprox
+f     AST_QUADAPPROX
+
+*  Purpose:
+*     Obtain a quadratic approximation to a 2D Mapping.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     int QuadApprox( AstMapping *this,  const double lbnd[2],
+c                     const double ubnd[2], int nx, int ny, double *fit,
+c                     double *rms )
+f     RESULT = AST_QUADAPPROX( THIS, LBND, UBND, NX, NY, FIT, RMS, STATUS )
+
+*  Class Membership:
+*     Mapping function.
+
+*  Description:
+*     This function returns the co-efficients of a quadratic fit to the
+*     supplied Mapping over the input area specified by
+c     "lbnd" and "ubnd".
+f     LBND and UBND.
+*     The Mapping must have 2 inputs, but may have any number of outputs.
+*     The i'th Mapping output is modelled as a quadratic function of the
+*     2 inputs (x,y):
+*
+*     output_i = a_i_0 + a_i_1*x + a_i_2*y + a_i_3*x*y + a_i_4*x*x +
+*                a_i_5*y*y
+*
+c     The "fit"
+f     The FIT
+*     array is returned holding the values of the co-efficients a_0_0,
+*     a_0_1, etc.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping.
+c     lbnd
+f     LBND( * ) = DOUBLE PRECISION (Given)
+c        Pointer to an array of doubles
+f        An array
+*        containing the lower bounds of a box defined within the input
+*        coordinate system of the Mapping. The number of elements in this
+*        array should equal the value of the Mapping's Nin attribute. This
+*        box should specify the region over which the fit is to be
+*        performed.
+c     ubnd
+f     UBND( * ) = DOUBLE PRECISION (Given)
+c        Pointer to an array of doubles
+f        An array
+*        containing the upper bounds of the box specifying the region over
+*        which the fit is to be performed.
+c     nx
+f     NX = INTEGER (Given)
+*        The number of points to place along the first Mapping input. The
+*        first point is at
+c        "lbnd[0]" and the last is at "ubnd[0]".
+f        LBND( 1 ) and the last is at UBND( 1 ).
+*        If a value less than three is supplied a value of three will be used.
+c     ny
+f     NY = INTEGER (Given)
+*        The number of points to place along the second Mapping input. The
+*        first point is at
+c        "lbnd[1]" and the last is at "ubnd[1]".
+f        LBND( 2 ) and the last is at UBND( 2 ).
+*        If a value less than three is supplied a value of three will be used.
+c     fit
+f     FIT( * ) = DOUBLE PRECISION (Returned)
+c        Pointer to an array of doubles
+f        An array
+*        in which to return the co-efficients of the quadratic
+*        approximation to the specified transformation. This array should
+*        have at least "6*Nout", elements. The first 6 elements hold the
+*        fit to the first Mapping output. The next 6 elements hold the
+*        fit to the second Mapping output, etc. So if the Mapping has 2
+*        inputs and 2 outputs the quadratic approximation to the forward
+*        transformation is:
+*
+c           X_out = fit[0] + fit[1]*X_in + fit[2]*Y_in + fit[3]*X_in*Y_in +
+c                   fit[4]*X_in*X_in + fit[5]*Y_in*Y_in
+c           Y_out = fit[6] + fit[7]*X_in + fit[8]*Y_in + fit[9]*X_in*Y_in +
+c                   fit[10]*X_in*X_in + fit[11]*Y_in*Y_in
+f           X_out = fit(1) + fit(2)*X_in + fit(3)*Y_in + fit(4)*X_in*Y_in +
+f                   fit(5)*X_in*X_in + fit(6)*Y_in*Y_in
+f           Y_out = fit(7) + fit(8)*X_in + fit(9)*Y_in + fit(10)*X_in*Y_in +
+f                   fit(11)*X_in*X_in + fit(12)*Y_in*Y_in
+*
+c     rms
+f     RMS = DOUBLE PRECISION (Returned)
+c        Pointer to a double in which to return the
+f        The
+*        RMS residual between the fit and the Mapping, summed over all
+*        Mapping outputs.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astQuadApprox()
+f     AST_QUADAPPROX = LOGICAL
+*        If a quadratic approximation was created,
+c        a non-zero value is returned. Otherwise zero is returned
+f        .TRUE is returned. Otherwise .FALSE. is returned
+*        and the fit co-efficients are set to AST__BAD.
+
+*  Notes:
+*     - This function fits the Mapping's forward transformation. To fit
+*     the inverse transformation, the Mapping should be inverted using
+c     astInvert
+f     AST_INVERT
+*     before invoking this function.
+c     - A value of zero
+f     - A value of .FALSE.
+*     will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*--
+
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;
+   AstPointSet *pset2;
+   double **pdat1;
+   double **pdat2;
+   double *ofit;
+   double *px;
+   double *py;
+   double *pz;
+   double det;
+   double dx;
+   double dy;
+   double mat[ 6*6 ];
+   double sx2;
+   double sx2y2;
+   double sx2y;
+   double sx3;
+   double sx3y;
+   double sx4;
+   double sx;
+   double sxy2;
+   double sxy3;
+   double sxy;
+   double sy2;
+   double sy3;
+   double sy4;
+   double sy;
+   double sz;
+   double sz2;
+   double szx2;
+   double szx;
+   double szxy;
+   double szy2;
+   double szy;
+   double x;
+   double xx;
+   double xy;
+   double y;
+   double yy;
+   double z;
+   int i;
+   int iout;
+   int iw[ 6 ];
+   int ix;
+   int iy;
+   int n;
+   int nin;
+   int nout;
+   int np;
+   int ntot;
+   int result;
+   int sing;
+
+/* Initialise the returned values. */
+   result = 0;
+   fit[ 0 ] = AST__BAD;
+   *rms = AST__BAD;
+   ntot = 0;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* Get the number of Mapping inputs and outputs. Report an error if not
+   correct. */
+   nin = astGetI( this, "Nin" );
+   nout = astGetI( this, "Nout" );
+   if( nin != 2 && astOK ) {
+      astError( AST__BADNI, "astQuadApprox(%s): Input Mapping has %d %s - "
+                "it must have 2 inputs.", status, astGetClass( this ), nin,
+                (nin==1)?"input":"inputs" );
+   }
+
+/* Ensure we are using at least 3 points on each of the two input axes. */
+   if( nx < 3 ) nx = 3;
+   if( ny < 3 ) ny = 3;
+
+/* Get the total number of grid points. */
+   np = nx*ny;
+
+/* Create a PointSet to hold the 2D grid of input positions. */
+   pset1 = astPointSet( np, 2, " ", status );
+   pdat1 = astGetPoints( pset1 );
+
+/* Create a PointSet to hold the N-D grid of output positions. */
+   pset2 = astPointSet( np, nout, " ", status );
+   pdat2 = astGetPoints( pset2 );
+
+/* Check the memory allocation (and everything else) was succesful. */
+   if( astOK ) {
+
+/* Find the cell dimensions on X and Y input axes. */
+      dx = ( ubnd[ 0 ] - lbnd[ 0 ] )/( nx - 1 );
+      dy = ( ubnd[ 1 ] - lbnd[ 1 ] )/( ny - 1 );
+
+/* Create a regular grid of input positions. */
+      px = pdat1[ 0 ];
+      py = pdat1[ 1 ];
+      for( iy = 0; iy < ny; iy++ ) {
+         x = lbnd[ 0 ];
+         y = lbnd[ 1 ] + iy*dy;
+         for( ix = 0; ix < nx; ix++ ) {
+            *(px++) = x;
+            *(py++) = y;
+            x += dx;
+         }
+      }
+
+/* Use the supplied Mapping to transform this grid into the output space. */
+      (void) astTransform( this, pset1, 1, pset2 );
+
+/* Assume the approximation can be created. */
+      result = 1;
+      *rms = 0.0;
+
+/* Loop round each Mapping output. */
+      for( iout = 0; iout < nout && astOK; iout++ ) {
+
+/* Get a pointer to the first element of the fit array for this output. */
+         ofit = fit + 6*iout;
+
+/* Form the required sums. */
+         n = 0;
+         sx = 0.0;
+         sy = 0.0;
+         sxy = 0.0;
+         sx2 = 0.0;
+         sy2 = 0.0;
+         sx2y = 0.0;
+         sx3 = 0.0;
+         sxy2 = 0.0;
+         sy3 = 0.0;
+         sx2y2 = 0.0;
+         sx3y = 0.0;
+         sxy3 = 0.0;
+         sx4 = 0.0;
+         sy4 = 0.0;
+         sz = 0.0;
+         sz2 = 0.0;
+         szx = 0.0;
+         szy = 0.0;
+         szxy = 0.0;
+         szx2 = 0.0;
+         szy2 = 0.0;
+
+         px = pdat1[ 0 ];
+         py = pdat1[ 1 ];
+         pz = pdat2[ iout ];
+
+         for( i = 0; i < np; i++ ) {
+            x = *(px++);
+            y = *(py++);
+            z = *(pz++);
+
+            if( z != AST__BAD ) {
+               xx = x*x;
+               yy = y*y;
+               xy = x*y;
+
+               n++;
+               sx += x;
+               sy += y;
+               sxy += xy;
+               sx2 += xx;
+               sy2 += yy;
+               sx2y += xx*y;
+               sx3 += xx*x;
+               sxy2 += x*yy;
+               sy3 += yy*y;
+               sx2y2 += xx*yy;
+               sx3y += xx*xy;
+               sxy3 += xy*yy;
+               sx4 += xx*xx;
+               sy4 += yy*yy;
+               sz += z;
+               sz2 += z*z;
+               szx += z*x;
+               szy += z*y;
+               szxy += z*xy;
+               szx2 += z*xx;
+               szy2 += z*yy;
+            }
+         }
+
+/* Form a matrix (M) and vector (V) such that M.X = V, where X is the
+   solution vector holding the required best fit parameter values (V is
+   stored in ofit). */
+         mat[ 0 ] = n;
+         mat[ 1 ] = sx;
+         mat[ 2 ] = sy;
+         mat[ 3 ] = sxy;
+         mat[ 4 ] = sx2;
+         mat[ 5 ] = sy2;
+
+         mat[ 6 ] = sx;
+         mat[ 7 ] = sx2;
+         mat[ 8 ] = sxy;
+         mat[ 9 ] = sx2y;
+         mat[ 10 ] = sx3;
+         mat[ 11 ] = sxy2;
+
+         mat[ 12 ] = sy;
+         mat[ 13 ] = sxy;
+         mat[ 14 ] = sy2;
+         mat[ 15 ] = sxy2;
+         mat[ 16 ] = sx2y;
+         mat[ 17 ] = sy3;
+
+         mat[ 18 ] = sxy;
+         mat[ 19 ] = sx2y;
+         mat[ 20 ] = sxy2;
+         mat[ 21 ] = sx2y2;
+         mat[ 22 ] = sx3y;
+         mat[ 23 ] = sxy3;
+
+         mat[ 24 ] = sx2;
+         mat[ 25 ] = sx3;
+         mat[ 26 ] = sx2y;
+         mat[ 27 ] = sx3y;
+         mat[ 28 ] = sx4;
+         mat[ 29 ] = sx2y2;
+
+         mat[ 30 ] = sy2;
+         mat[ 31 ] = sxy2;
+         mat[ 32 ] = sy3;
+         mat[ 33 ] = sxy3;
+         mat[ 34 ] = sx2y2;
+         mat[ 35 ] = sy4;
+
+         ofit[ 0 ] = sz;
+         ofit[ 1 ] = szx;
+         ofit[ 2 ] = szy;
+         ofit[ 3 ] = szxy;
+         ofit[ 4 ] = szx2;
+         ofit[ 5 ] = szy2;
+
+/* Now find the solution vector (the solution over-writes teh current
+   contents of "ofit"). */
+         palDmat( 6, mat, ofit, &det, &sing, iw );
+
+/* If the fit failed, fill the coefficient array with bad values. */
+         if( sing != 0 ) {
+            for( i = 0; i < 6; i++ ) ofit[ i ] = AST__BAD;
+            result = 0;
+            break;
+
+/* If the fit succeeded, update the summ of the squared residuals. */
+         } else {
+            ntot += n;
+            *rms += ofit[ 0 ]*ofit[ 0 ]*n +
+                    2*ofit[ 0 ]*ofit[ 1 ]*sx +
+                    2*ofit[ 0 ]*ofit[ 2 ]*sy +
+                    2*( ofit[ 0 ]*ofit[ 3 ] + ofit[ 1 ]*ofit[ 2 ] )*sxy +
+                    ( 2*ofit[ 0 ]*ofit[ 4 ] + ofit[ 1 ]*ofit[ 1 ] )*sx2 +
+                    ( 2*ofit[ 0 ]*ofit[ 5 ] + ofit[ 2 ]*ofit[ 2 ] )*sy2 +
+                    2*ofit[ 1 ]*ofit[ 4 ]*sx3 +
+                    2*( ofit[ 1 ]*ofit[ 3 ] + ofit[ 2 ]*ofit[ 4 ] )*sx2y +
+                    2*( ofit[ 1 ]*ofit[ 5 ] + ofit[ 2 ]*ofit[ 3 ] )*sxy2 +
+                    2*ofit[ 2 ]*ofit[ 5 ]*sy3 +
+                    ofit[ 4 ]*ofit[ 4 ]*sx4 +
+                    2*ofit[ 3 ]*ofit[ 4 ]*sx3y +
+                    ( 2*ofit[ 4 ]*ofit[ 5 ] + ofit[ 3 ]*ofit[ 3 ] )*sx2y2 +
+                    2*ofit[ 3 ]*ofit[ 5 ]*sxy3 +
+                    ofit[ 5 ]*ofit[ 5 ]*sy4 +
+                    sz2 - 2*(
+                       ofit[ 0 ]*sz +
+                       ofit[ 1 ]*szx +
+                       ofit[ 2 ]*szy +
+                       ofit[ 3 ]*szxy +
+                       ofit[ 4 ]*szx2 +
+                       ofit[ 5 ]*szy2
+                    );
+         }
+      }
+   }
+
+/* Free resources. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* Return AST__BAD if anything went wrong. */
+   if( !astOK || ntot == 0 ) {
+      result = 0;
+      fit[ 0 ] = AST__BAD;
+      *rms = AST__BAD;
+
+/* Otherwise normalise the returned RMS. */
+   } else {
+      if( *rms > 0.0 ) {
+         *rms = sqrt( *rms/ntot );
+      } else {
+         *rms = 0.0;
+      }
+   }
+
+/* Return result */
+   return result;
+}
+
+static double Random( long int *seed, int *status ) {
+/*
+*  Name:
+*     Random
+
+*  Purpose:
+*     Return a pseudo-random value in the range 0 to 1.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     double Random( long int *seed, int *status );
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function returns a pseudo-random double value from a PDF
+*     uniformly distributed in the range 0 to 1. It also updates a
+*     seed value so that a sequence of pseudo-random values may be
+*     obtained with successive invocations.
+
+*  Parameters:
+*     seed
+*        Pointer to a long int which should initially contain a
+*        non-zero seed value. This will be updated with a new seed
+*        which may be supplied on the next invocation in order to
+*        obtain a different pseudo-random value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The pseudo-random value.
+*/
+
+/* Local Variables: */
+   long int i;                   /* Temporary storage */
+
+/* This a basic random number generator using constants given in
+   Numerical Recipes (Press et al.). */
+   i = *seed / 127773;
+   *seed = ( *seed - i * 127773 ) * 16807 - i * 2836;
+   if ( *seed < 0 ) *seed += 2147483647;
+
+/* Return the result as a double value in the range 0 to 1. */
+   return ( (double) ( *seed - 1 ) ) / (double) 2147483646;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2,
+                    int *status ){
+/*
+*+
+*  Name:
+*     astRate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     result = astRate( AstMapping *this, double *at, int ax1, int ax2 )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function evaluates the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+*
+*     The result is the mean gradient within a small interval centred on
+*     the supplied position. The interval size is selected automatically
+*     to minimise the error on the returned value. For large intervals,
+*     the error is dominated by changes in the gradient of the
+*     transformation. For small intervals, the error is dominated by
+*     rounding errors. The best interval is the one that gives the most
+*     consistent measure of the gradient within the interval. To find this
+*     consistency, each candidate interval is subdivided into eight
+*     sub-intervals, the mean gradient within each sub-interval is found,
+*     and the associated consistency measure is then the difference between
+*     the maximum and minimum sub-interval gradient found within the interval.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+
+*  Returned Value:
+*     astRate()
+*        The rate of change of Mapping output "ax1" with respect to input
+*        "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*        calculated.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic astRate method available
+*     via the protected interface to the Mapping class. The public
+*     interface to this method is provided by the astRateId_
+*     function.
+*/
+
+#define NN 50
+
+/* Local Variables: */
+   double h0;
+   double h;
+   double mean;
+   double minrange;
+   double range0;
+   double range;
+   double ret;
+   double x0;
+   double y[2*NN+1];
+   double z[2*NN+1];
+   int ibot;
+   int iin;
+   int iret;
+   int itop;
+   int nin;
+   int nout;
+
+/* Initialise */
+   ret = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Allocate resources */
+   RateFun( NULL, NULL, -1, 0, 0, NULL, NULL, status );
+
+/* Obtain the numbers of input and output coordinates for the Mapping. */
+   nin = astGetNin( this );
+   nout = astGetNout( this );
+
+/* Validate the output index. */
+   if ( astOK && ( ax1 < 0 || ax1 >= nout ) ) {
+      astError( AST__AXIIN, "astRate(%s): The supplied Mapping output "
+                "index (%d) is invalid; it should be in the range 1 to %d.", status,
+                astGetClass( this ), ax1 + 1, nout );
+   }
+
+/* Validate the input index. */
+   if ( astOK && ( ax2 < 0 || ax2 >= nin ) ) {
+      astError( AST__AXIIN, "astRate(%s): The supplied Mapping input "
+                "index (%d) is invalid; it should be in the range 1 to %d.", status,
+                astGetClass( this ), ax2 + 1, nin );
+   }
+
+/* Check the Mapping has a forward transformation. */
+   if ( astOK && !astGetTranForward( this ) ) {
+      astError( AST__NODEF, "astRate(%s): The supplied Mapping does not "
+                "have a defined forward transformation.", status,
+                astGetClass( this ) );
+   }
+
+/* Save the central value on the Mapping input which is to be varied. */
+   x0 = at[ ax2 ];
+
+/* If it is bad, return bad values. */
+   if( astOK && x0 != AST__BAD ) {
+
+/* The required derivative is formed by evaluating the transformation at
+   two positions close to "x0", and dividing the change in y by the
+   change in x. The complexity comes in deciding how close to "x0" the
+   two points should be. If the points are too far apart, the gradient of
+   the function may vary significantly between the two points and so we
+   have little confidence that he mean gradient in the interval is a good
+   estimate of the gradient at "x0". On the other hand if the points are
+   too close together, rounding errors will make the gradient value
+   unreliable. The optimal interval is found by testing a number of
+   different intervals as follows. Each interval is split into NDIV equal
+   sub-intervals, and the gradient in each sub-interval is found. The max
+   and min gradient for any of these sub-intervals is found, and the
+   difference between them is used as an estimate of the reliability of the
+   mean gradient within the whole interval. The interval with the
+   greatest reliability is used to define the returned gradient.
+
+   The initial estimate of the interval size is a fixed small fraction of
+   the supplied "x0" value, or 1.0 if "x0" is zero. */
+      h0 = ( x0 != 0.0 ) ? DBL_EPSILON*1.0E9*fabs( x0 ) : 1.0;
+
+/* Attempt to find the mean gradient, and the range of gradients, within
+   an interval of size "h0" centred on "x0". If this cannot be done,
+   increase "h0" by a factor fo ten repeatedly until it can be done, or a
+   silly large interval size is reached. */
+      mean = AST__BAD;
+      while( mean == AST__BAD && h0 < 1.0E-10*DBL_MAX ) {
+         h0 *= 10;
+         mean = FindGradient( this, at, ax1, ax2, x0, h0, &range0, status );
+      }
+
+/* If this was not successful, return AST__BAD as the function value. */
+      if( mean != AST__BAD ) {
+
+/* We now search through a range of larger interval sizes, to see if any
+   produce a more reliable mean gradient estimate (i.e. have a smaller range
+   of gradients within the interval ). After that we search through a range
+   of smaller interval sizes. The gradient range and mean gradient for
+   each interval size are stored in arrays "y" and "z" respectively. "iret"
+   is the index of the most reliable interval found so far (i.e. the one
+   with the smallest range of sub-interval gradients). The original interval
+   "h0" is stored in the middle element of these arrays (index "NN").
+   Intervals are stored in monotonic order of interval size in the arrays. */
+         iret = NN;
+         y[ NN ] = range0;
+         z[ NN ] = mean;
+         minrange = range0;
+
+/* itop is the index of the last array elements to store calculated values. */
+         itop = NN;
+
+/* Loop round increasing the interval size by a factor of four each time
+   round. */
+         h = h0;
+         for( iin = NN + 1; iin <= 2*NN && astOK; iin++ ){
+            h *= 4.0;
+
+/* Calculate the mean gradient, and the range of gradients, using the
+   current interval size. */
+            mean = FindGradient( this, at, ax1, ax2, x0, h, &range, status );
+
+/* If it could be done, store the values in the arrays. */
+            if( mean != AST__BAD ) {
+               itop++;
+               z[ itop ] = mean;
+               y[ itop ] = range;
+
+/* Look for the smallest range, and note its index in the arrays. */
+               if( range < minrange ) {
+                  minrange = range;
+                  iret = itop;
+
+/* If a range of zero is encountered, we only believe it if the previous
+   interval also had zero range. Otherwise, it's probably just a numerical
+   fluke. If the previous interval also had a range of zero, we can forget
+   the rest of the algorithm since the supplied transformation is linear
+   and we now have its gradient. So leave the loop. */
+               } else if( range == 0.0 && y[ iin - 1 ] == 0 ) {
+                  iret = itop;
+                  break;
+               }
+
+/* Stop looping when the interval range is 100 times the original
+   interval range. */
+               if( range > 100*range0 ) break;
+            }
+         }
+
+/* Record the minimum range found so far. */
+         range0 = minrange;
+
+/* ibot is the index of the first array elements to store calculated values. */
+         ibot = NN;
+
+/* Loop round decreasing the interval size by a factor of four each time
+   round. This is just like the last loop, but goes the other way, to
+   lower indices. */
+         h = h0;
+         for( iin = NN - 1; iin >= 0 && astOK; iin-- ){
+            h /= 4.0;
+
+            mean = FindGradient( this, at, ax1, ax2, x0, h, &range, status );
+            if( mean != AST__BAD ) {
+               ibot--;
+               z[ ibot ] = mean;
+               y[ ibot ] = range;
+
+               if( range < minrange ) {
+                  minrange = range;
+                  iret = ibot;
+               } else if( range == 0.0 && y[ iin + 1 ] == 0 ) {
+                  iret = ibot;
+                  break;
+               }
+
+               if( range > 100*range0 ) break;
+            }
+         }
+
+/* If the smallest gradient range in any interval was zero, we only
+   believe it if the adjacent interval size also had zero range. */
+         if( minrange == 0.0 ) {
+            if( ( iret > ibot && y[ iret - 1 ] == 0 ) ||
+                ( iret < itop && y[ iret + 1 ] == 0 ) ) {
+               ret = z[ iret ];
+
+/* Otherwise, search for the smallest gradient range, ignoring values
+   exactly equal to zero, and return the corresponding mean interval
+   gradient. */
+            } else {
+               for( iin = ibot; iin <= itop; iin++ ){
+                  if( y[ iin ] > 0.0 ){
+                     if( minrange == 0 || y[ iin ] < minrange ) {
+                        minrange = y[ iin ];
+                        ret = z[ iin ];
+                     }
+                  }
+               }
+            }
+
+/* If the minimum range was non-zero, we can just return the
+   corresponding mean gradient. */
+         } else {
+            ret = z[ iret ];
+         }
+      }
+   }
+
+/* Free resources */
+   RateFun( NULL, NULL, -2, 0, 0, NULL, NULL, status );
+
+/* Return the result. */
+   return ret;
+
+#undef NN
+}
+
+static void RateFun( AstMapping *map, double *at, int ax1, int ax2,
+                     int n, double *x, double *y, int *status ) {
+/*
+*  Name:
+*     RateFun
+
+*  Purpose:
+*     Find the value of the function currently being differentiated by the
+*     astRate method.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void RateFun( AstMapping *map, double *at, int ax1, int ax2,
+*                   int n, double *x, double *y, int *status )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This is a service function for the astRate method. It evaluates the
+*     function being differentiated at specified axis values.
+*
+*     This function uses static resources in order to avoid the overhead
+*     of creating new PointSets each time this function is called. These
+*     static resources which must be initialised before the first invocation
+*     with a given Mapping, and must be released after the final invocation.
+*     See "ax1".
+
+*  Parameters:
+*     map
+*        Pointer to a Mapping which yields the value of the function at x.
+*        The Mapping may have any number of inputs and outputs; the specific
+*        output representing the function value, f, is specified by ax1 and
+*        the specific input representing the argument, x, is specified by ax2.
+*     at
+*        A pointer to an array holding axis values at the position at which
+*        the function is to be evaluated. The number of values supplied
+*        must equal the number of inputs to the Mapping. The value supplied
+*        for axis "ax2" is ignored (the value of "x" is used for axis "ax2").
+*     ax1
+*        The zero-based index of the Mapping output which is to be
+*        differentiated. Set this to -1 to allocate, or -2 to release,
+*        the static resources used by this function.
+*     ax2
+*        The zero-based index of the Mapping input which is to be varied.
+*     n
+*        The number of elements in the "x" and "y" arrays. This should not
+*        be greater than 2*RATE_ORDER.
+*     x
+*        The value of the Mapping input specified by ax2 at which the
+*        function is to be evaluated. If "ax2" is set to -1, then the
+*        supplied value is used as flag indicating if the static resources
+*        used by this function should be initialised (if x >= 0 ) or
+*        freed (if x < 0).
+*     y
+*        An array in which to return the function values at the positions
+*        given in "x".
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   AstPointSet *pset1;
+   AstPointSet *pset2;
+   double **ptr1;
+   double **ptr2;
+   double *oldx;
+   double *oldy;
+   double *p;
+   double xx;
+   int i;
+   int k;
+   int nin;
+   int nout;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(map);
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   pset2 = NULL;
+
+/* If required, initialise things. */
+   if( ax1 == -1 ) {
+      for( i = 0; i < RATEFUN_MAX_CACHE; i++ ) {
+         ratefun_pset_size[ i ] = 0;
+         ratefun_pset1_cache[ i ] = NULL;
+         ratefun_pset2_cache[ i ] = NULL;
+      }
+      ratefun_next_slot = 0;
+
+/* If required, clean up. */
+   } else if( ax1 == -2 ) {
+      for( i = 0; i < RATEFUN_MAX_CACHE; i++ ) {
+         ratefun_pset_size[ i ] = 0;
+         if( ratefun_pset1_cache[ i ] ) ratefun_pset1_cache[ i ] = astAnnul( ratefun_pset1_cache[ i ] );
+         if( ratefun_pset2_cache[ i ] ) ratefun_pset2_cache[ i ] = astAnnul( ratefun_pset2_cache[ i ] );
+      }
+      ratefun_next_slot = 0;
+
+/* Otherwise do the transformations. */
+   } else {
+
+/* See if we have already created PointSets of the correct size. */
+      pset1 = NULL;
+      for( i = 0; i < RATEFUN_MAX_CACHE; i++ ) {
+         if( ratefun_pset_size[ i ] == n ) {
+            pset1 = ratefun_pset1_cache[ i ];
+            pset2 = ratefun_pset2_cache[ i ];
+            break;
+         }
+      }
+
+/* If we have not, create new PointSets now. */
+      if( pset1 == NULL ) {
+         nin = astGetNin( map );
+         pset1 = astPointSet( n, nin, "", status );
+         ptr1 = astGetPoints( pset1 );
+
+         nout = astGetNout( map );
+         pset2 = astPointSet( n, nout, "", status );
+         ptr2 = astGetPoints( pset2 );
+
+/* Store the input position in the input PointSet. */
+         for( i = 0; i < nin; i++ ) {
+            xx = at[ i ];
+            p = ptr1[ i ];
+            for( k = 0; k < n; k++, p++ ) *p = xx;
+         }
+
+/* Add these new PointSets to the cache, removing any existing
+   PointSets. */
+         if( ratefun_pset_size[ ratefun_next_slot ] > 0 ) {
+            (void) astAnnul( ratefun_pset1_cache[ ratefun_next_slot ] );
+            (void) astAnnul( ratefun_pset2_cache[ ratefun_next_slot ] );
+         }
+         ratefun_pset1_cache[ ratefun_next_slot ] = pset1;
+         ratefun_pset2_cache[ ratefun_next_slot ] = pset2;
+         ratefun_pset_size[ ratefun_next_slot ] = n;
+         if( ++ratefun_next_slot == RATEFUN_MAX_CACHE ) ratefun_next_slot = 0;
+
+/* If existing PointSets were found, get there data arrays. */
+      } else {
+         ptr1 = astGetPoints( pset1 );
+         ptr2 = astGetPoints( pset2 );
+      }
+
+/* Store the input X values in the input PointSet data array. */
+      oldx = ptr1[ ax2 ];
+      ptr1[ ax2 ] = x;
+
+/* Store the output Y values in the output PointSet data array. */
+      oldy = ptr2[ ax1 ];
+      ptr2[ ax1 ] = y;
+
+/* Transform the positions. */
+      (void) astTransform( map, pset1, 1, pset2 );
+
+/* Re-instate the original arrays in the PointSets. */
+      ptr1[ ax2 ] = oldx;
+      ptr2[ ax1 ] = oldy;
+
+   }
+}
+
+/*
+*++
+*  Name:
+c     astRebin<X>
+f     AST_REBIN<X>
+
+*  Purpose:
+*     Rebin a region of a data grid.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astRebin<X>( AstMapping *this, double wlim, int ndim_in,
+c                      const int lbnd_in[], const int ubnd_in[],
+c                      const <Xtype> in[], const <Xtype> in_var[],
+c                      int spread, const double params[], int flags,
+c                      double tol, int maxpix,
+c                      <Xtype> badval, int ndim_out,
+c                      const int lbnd_out[], const int ubnd_out[],
+c                      const int lbnd[], const int ubnd[],
+c                      <Xtype> out[], <Xtype> out_var[] );
+f     CALL AST_REBIN<X>( THIS, WLIM, NDIM_IN, LBND_IN, UBND_IN, IN, IN_VAR,
+f                        SPREAD, PARAMS, FLAGS,
+f                        TOL, MAXPIX, BADVAL,
+f                        NDIM_OUT, LBND_OUT, UBND_OUT,
+f                        LBND, UBND, OUT, OUT_VAR, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This is a set of functions for rebinning gridded data (e.g. an
+*     image) under the control of a geometrical transformation, which
+*     is specified by a Mapping.  The functions operate on a pair of
+*     data grids (input and output), each of which may have any number
+*     of dimensions. Rebinning may be restricted to a specified
+*     region of the input grid. An associated grid of error estimates
+*     associated with the input data may also be supplied (in the form
+*     of variance values), so as to produce error estimates for the
+*     rebined output data. Propagation of missing data (bad pixels)
+*     is supported.
+*
+*     Note, if you will be rebining a sequence of input arrays and then
+*     co-adding them into a single array, the alternative
+c     astRebinSeq<X> functions
+f     AST_REBINSEQ<X> routines
+*     will in general be more efficient.
+*
+*     You should use a rebinning function which matches the numerical
+*     type of the data you are processing by replacing <X> in
+c     the generic function name astRebin<X> by an appropriate 1- or
+f     the generic function name AST_REBIN<X> by an appropriate 1- or
+*     2-character type code. For example, if you are rebinning data
+c     with type "float", you should use the function astRebinF (see
+f     with type REAL, you should use the function AST_REBINR (see
+*     the "Data Type Codes" section below for the codes appropriate to
+*     other numerical types).
+*
+*     Rebinning of the grid of input data is performed by transforming
+*     the coordinates of the centre of each input grid element (or pixel)
+*     into the coordinate system of the output grid. The input pixel
+*     value is then divided up and assigned to the output pixels in the
+*     neighbourhood of the central output coordinates. A choice of
+*     schemes are provided for determining how each input pixel value is
+*     divided up between the output pixels. In general, each output pixel
+*     may be assigned values from more than one input pixel. All
+*     contributions to a given output pixel are summed to produce the
+*     final output pixel value. Output pixels can be set to the supplied
+*     bad value if they receive contributions from an insufficient number
+*     of input pixels. This is controlled by the
+c     "wlim" parameter.
+f     WLIM argument.
+*
+*     Input pixel coordinates are transformed into the coordinate
+*     system of the output grid using the forward transformation of the
+*     Mapping which is supplied. This means that geometrical features
+*     in the input data are subjected to the Mapping's forward
+*     transformation as they are transferred from the input to the
+*     output grid.
+*
+*     In practice, transforming the coordinates of every pixel of a
+*     large data grid can be time-consuming, especially if the Mapping
+*     involves complicated functions, such as sky projections. To
+*     improve performance, it is therefore possible to approximate
+*     non-linear Mappings by a set of linear transformations which are
+*     applied piece-wise to separate sub-regions of the data. This
+*     approximation process is applied automatically by an adaptive
+*     algorithm, under control of an accuracy criterion which
+*     expresses the maximum tolerable geometrical distortion which may
+*     be introduced, as a fraction of a pixel.
+*
+*     This algorithm first attempts to approximate the Mapping with a
+*     linear transformation applied over the whole region of the
+*     input grid which is being used. If this proves to be
+*     insufficiently accurate, the input region is sub-divided into
+*     two along its largest dimension and the process is repeated
+*     within each of the resulting sub-regions. This process of
+*     sub-division continues until a sufficiently good linear
+*     approximation is found, or the region to which it is being
+*     applied becomes too small (in which case the original Mapping is
+*     used directly).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to a Mapping, whose forward transformation will be
+*        used to transform the coordinates of pixels in the input
+*        grid into the coordinate system of the output grid.
+*
+*        The number of input coordinates used by this Mapping (as
+*        given by its Nin attribute) should match the number of input
+c        grid dimensions given by the value of "ndim_in"
+f        grid dimensions given by the value of NDIM_IN
+*        below. Similarly, the number of output coordinates (Nout
+*        attribute) should match the number of output grid dimensions
+c        given by "ndim_out".
+f        given by NDIM_OUT.
+c     wlim
+f     WLIM = DOUBLE PRECISION (Given)
+*        Gives the required number of input pixel values which must contribute
+*        to an output pixel in order for the output pixel value to be
+*        considered valid. If the sum of the input pixel weights contributing
+*        to an output pixel is less than the supplied
+c        "wlim"
+f        WLIM
+*        value, then the output pixel value is returned set to the
+*        supplied bad value.
+c     ndim_in
+f     NDIM_IN = INTEGER (Given)
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+c     lbnd_in
+f     LBND_IN( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the centre of the first pixel
+*        in the input grid along each dimension.
+c     ubnd_in
+f     UBND_IN( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the centre of the last pixel in
+*        the input grid along each dimension.
+*
+c        Note that "lbnd_in" and "ubnd_in" together define the shape
+f        Note that LBND_IN and UBND_IN together define the shape
+*        and size of the input grid, its extent along a particular
+c        (j'th) dimension being ubnd_in[j]-lbnd_in[j]+1 (assuming the
+c        index "j" to be zero-based). They also define
+f        (J'th) dimension being UBND_IN(J)-LBND_IN(J)+1. They also define
+*        the input grid's coordinate system, each pixel having unit
+*        extent along each dimension with integral coordinate values
+*        at its centre.
+c     in
+f     IN( * ) = <Xtype> (Given)
+c        Pointer to an array, with one element for each pixel in the
+f        An array, with one element for each pixel in the
+*        input grid, containing the input data to be rebined.  The
+*        numerical type of this array should match the 1- or
+*        2-character type code appended to the function name (e.g. if
+c        you are using astRebinF, the type of each array element
+c        should be "float").
+f        you are using AST_REBINR, the type of each array element
+f        should be REAL).
+*
+*        The storage order of data within this array should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+c        (i.e. Fortran array indexing is used).
+f        (i.e. normal Fortran array storage order).
+c     in_var
+f     IN_VAR( * ) = <Xtype> (Given)
+c        An optional pointer to a second array with the same size and
+c        type as the "in" array. If given, this should contain a set
+c        of non-negative values which represent estimates of the
+c        statistical variance associated with each element of the "in"
+c        array. If this array is supplied (together with the
+c        corresponding "out_var" array), then estimates of the
+c        variance of the rebined output data will be calculated.
+c
+c        If no input variance estimates are being provided, a NULL
+c        pointer should be given.
+f        An optional second array with the same size and type as the
+f        IN array. If the AST__USEVAR flag is set via the FLAGS
+f        argument (below), this array should contain a set of
+f        non-negative values which represent estimates of the
+f        statistical variance associated with each element of the IN
+f        array. Estimates of the variance of the rebined output data
+f        will then be calculated.
+f
+f        If the AST__USEVAR flag is not set, no input variance
+f        estimates are required and this array will not be used. A
+f        dummy (e.g. one-element) array may then be supplied.
+c     spread
+f     SPREAD = INTEGER (Given)
+c        This parameter specifies the scheme to be used for dividing
+f        This argument specifies the scheme to be used for dividing
+*        each input data value up amongst the corresponding output pixels.
+*        It may be used to select
+*        from a set of pre-defined schemes by supplying one of the
+*        values described in the "Pixel Spreading Schemes"
+*        section below.  If a value of zero is supplied, then the
+*        default linear spreading scheme is used (equivalent to
+*        supplying the value AST__LINEAR).
+c     params
+f     PARAMS( * ) = DOUBLE PRECISION (Given)
+c        An optional pointer to an array of double which should contain
+f        An optional array which should contain
+*        any additional parameter values required by the pixel
+*        spreading scheme. If such parameters are required, this
+*        will be noted in the "Pixel Spreading Schemes"
+*        section below.
+*
+c        If no additional parameters are required, this array is not
+c        used and a NULL pointer may be given.
+f        If no additional parameters are required, this array is not
+f        used. A dummy (e.g. one-element) array may then be supplied.
+c     flags
+f     FLAGS = INTEGER (Given)
+c        The bitwise OR of a set of flag values which may be used to
+f        The sum of a set of flag values which may be used to
+*        provide additional control over the rebinning operation. See
+*        the "Control Flags" section below for a description of the
+*        options available.  If no flag values are to be set, a value
+*        of zero should be given.
+c     tol
+f     TOL = DOUBLE PRECISION (Given)
+*        The maximum tolerable geometrical distortion which may be
+*        introduced as a result of approximating non-linear Mappings
+*        by a set of piece-wise linear transformations. This should be
+*        expressed as a displacement in pixels in the output grid's
+*        coordinate system.
+*
+*        If piece-wise linear approximation is not required, a value
+*        of zero may be given. This will ensure that the Mapping is
+*        used without any approximation, but may increase execution
+*        time.
+*
+*        If the value is too high, discontinuities between the linear
+*        approximations used in adjacent panel will be higher, and may
+*        cause the edges of the panel to be visible when viewing the output
+*        image at high contrast. If this is a problem, reduce the
+*        tolerance value used.
+c     maxpix
+f     MAXPIX = INTEGER (Given)
+*        A value which specifies an initial scale size (in pixels) for
+*        the adaptive algorithm which approximates non-linear Mappings
+*        with piece-wise linear transformations. Normally, this should
+*        be a large value (larger than any dimension of the region of
+*        the input grid being used). In this case, a first attempt to
+*        approximate the Mapping by a linear transformation will be
+*        made over the entire input region.
+*
+*        If a smaller value is used, the input region will first be
+c        divided into sub-regions whose size does not exceed "maxpix"
+f        divided into sub-regions whose size does not exceed MAXPIX
+*        pixels in any dimension. Only at this point will attempts at
+*        approximation commence.
+*
+*        This value may occasionally be useful in preventing false
+*        convergence of the adaptive algorithm in cases where the
+*        Mapping appears approximately linear on large scales, but has
+*        irregularities (e.g. holes) on smaller scales. A value of,
+*        say, 50 to 100 pixels can also be employed as a safeguard in
+*        general-purpose software, since the effect on performance is
+*        minimal.
+*
+*        If too small a value is given, it will have the effect of
+*        inhibiting linear approximation altogether (equivalent to
+c        setting "tol" to zero). Although this may degrade
+f        setting TOL to zero). Although this may degrade
+*        performance, accurate results will still be obtained.
+c     badval
+f     BADVAL = <Xtype> (Given)
+*        This argument should have the same type as the elements of
+c        the "in" array. It specifies the value used to flag missing
+f        the IN array. It specifies the value used to flag missing
+*        data (bad pixels) in the input and output arrays.
+*
+c        If the AST__USEBAD flag is set via the "flags" parameter,
+f        If the AST__USEBAD flag is set via the FLAGS argument,
+c        then this value is used to test for bad pixels in the "in"
+c        (and "in_var") array(s).
+f        then this value is used to test for bad pixels in the IN
+f        (and IN_VAR) array(s).
+*
+*        In all cases, this value is also used to flag any output
+c        elements in the "out" (and "out_var") array(s) for which
+f        elements in the OUT (and OUT_VAR) array(s) for which
+*        rebined values could not be obtained (see the "Propagation
+*        of Missing Data" section below for details of the
+*        circumstances under which this may occur).
+c     ndim_out
+f     NDIM_OUT = INTEGER (Given)
+*        The number of dimensions in the output grid. This should be
+*        at least one. It need not necessarily be equal to the number
+*        of dimensions in the input grid.
+c     lbnd_out
+f     LBND_OUT( NDIM_OUT ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_out" elements,
+f        An array
+*        containing the coordinates of the centre of the first pixel
+*        in the output grid along each dimension.
+c     ubnd_out
+f     UBND_OUT( NDIM_OUT ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_out" elements,
+f        An array
+*        containing the coordinates of the centre of the last pixel in
+*        the output grid along each dimension.
+*
+c        Note that "lbnd_out" and "ubnd_out" together define the
+f        Note that LBND_OUT and UBND_OUT together define the
+*        shape, size and coordinate system of the output grid in the
+c        same way as "lbnd_in" and "ubnd_in" define the shape, size
+f        same way as LBND_IN and UBND_IN define the shape, size
+*        and coordinate system of the input grid.
+c     lbnd
+f     LBND( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the first pixel in the region
+*        of the input grid which is to be included in the rebined output
+*        array.
+c     ubnd
+f     UBND( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the last pixel in the region of
+*        the input grid which is to be included in the rebined output
+*        array.
+*
+c        Note that "lbnd" and "ubnd" together define the shape and
+f        Note that LBND and UBND together define the shape and
+*        position of a (hyper-)rectangular region of the input grid
+*        which is to be included in the rebined output array. This region
+*        should lie wholly within the extent of the input grid (as
+c        defined by the "lbnd_in" and "ubnd_in" arrays). Regions of
+f        defined by the LBND_IN and UBND_IN arrays). Regions of
+*        the input grid lying outside this region will not be used.
+c     out
+f     OUT( * ) = <Xtype> (Returned)
+c        Pointer to an array, with one element for each pixel in the
+f        An array, with one element for each pixel in the
+*        output grid, in which the rebined data values will be
+*        returned. The numerical type of this array should match that
+c        of the "in" array, and the data storage order should be such
+f        of the IN array, and the data storage order should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+c        (i.e. Fortran array indexing is used).
+f        (i.e. normal Fortran array storage order).
+c     out_var
+f     OUT_VAR( * ) = <Xtype> (Returned)
+c        An optional pointer to an array with the same type and size
+c        as the "out" array. If given, this array will be used to
+c        return variance estimates for the rebined data values. This
+c        array will only be used if the "in_var" array has also been
+c        supplied.
+f        An optional array with the same type and size as the OUT
+f        array. If the AST__USEVAR flag is set via the FLAGS argument,
+f        this array will be used to return variance estimates for the
+f        rebined data values.
+*
+*        The output variance values will be calculated on the
+*        assumption that errors on the input data values are
+*        statistically independent and that their variance estimates
+*        may simply be summed (with appropriate weighting factors)
+*        when several input pixels contribute to an output data
+*        value. If this assumption is not valid, then the output error
+*        estimates may be biased. In addition, note that the
+*        statistical errors on neighbouring output data values (as
+*        well as the estimates of those errors) may often be
+*        correlated, even if the above assumption about the input data
+*        is correct, because of the pixel spreading schemes
+*        employed.
+*
+c        If no output variance estimates are required, a NULL pointer
+c        should be given.
+f        If the AST__USEVAR flag is not set, no output variance
+f        estimates will be calculated and this array will not be
+f        used. A dummy (e.g. one-element) array may then be supplied.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Data Type Codes:
+*     To select the appropriate rebinning function, you should
+c     replace <X> in the generic function name astRebin<X> with a
+f     replace <X> in the generic function name AST_REBIN<X> with a
+*     1- or 2-character data type code, so as to match the numerical
+*     type <Xtype> of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - I: int
+c     - B: byte (signed char)
+c     - UB: unsigned byte (unsigned char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - B: BYTE (treated as signed)
+f     - UB: BYTE (treated as unsigned)
+*
+c     For example, astRebinD would be used to process "double"
+c     data, while astRebinI would be used to process "int"
+c     data, etc.
+f     For example, AST_REBIND would be used to process DOUBLE
+f     PRECISION data, while AST_REBINI would be used to process
+f     integer data (stored in an INTEGER array), etc.
+*
+*     Note that, unlike
+c     astResample<X>, the astRebin<X>
+f     AST_RESAMPLE<X>, the AST_REBIN<X>
+*     set of functions does not yet support unsigned integer data types
+*     or integers of different sizes.
+
+*  Pixel Spreading Schemes:
+*     The pixel spreading scheme specifies the Point Spread Function (PSF)
+*     applied to each input pixel value as it is copied into the output
+*     array. It can be thought of as the inverse of the sub-pixel
+*     interpolation schemes used by the
+c     astResample<X>
+f     AST_RESAMPLE<X>
+*     group of functions. That is, in a sub-pixel interpolation scheme the
+*     kernel specifies the weight to assign to each input pixel when
+*     forming the weighted mean of the input pixels, whereas the kernel in a
+*     pixel spreading scheme specifies the fraction of the input data value
+*     which is to be assigned to each output pixel. As for interpolation, the
+*     choice of suitable pixel spreading scheme involves stricking a balance
+*     between schemes which tend to degrade sharp features in the data by
+*     smoothing them, and those which attempt to preserve sharp features but
+*     which often tend to introduce unwanted artifacts. See the
+c     astResample<X>
+f     AST_RESAMPLE<X>
+*     documentation for further discussion.
+*
+*     The binning algorithm used has the ability to introduce artifacts
+*     not seen when using a resampling algorithm. Particularly, when
+*     viewing the output image at high contrast, systems of curves lines
+*     covering the entire image may be visible. These are caused by a
+*     beating effect between the input pixel positions and the output pixels
+*     position, and their nature and strength depend critically upon the
+*     nature of the Mapping and the spreading function being used. In
+*     general, the nearest neighbour spreading function demonstrates this
+*     effect more clearly than the other functions, and for this reason
+*     should be used with caution.
+*
+*     The following values (defined in the
+c     "ast.h" header file)
+f     AST_PAR include file)
+*     may be assigned to the
+c     "spread"
+f     SPREAD
+*     parameter. See the
+c     astResample<X>
+f     AST_RESAMPLE<X>
+*     documentation for details of these schemes including the use of the
+c     "fspread" and "params" parameters:
+f     FSPREAD and PARAMS arguments:
+*
+*     - AST__NEAREST
+*     - AST__LINEAR
+*     - AST__SINC
+*     - AST__SINCSINC
+*     - AST__SINCCOS
+*     - AST__SINCGAUSS
+*     - AST__SOMBCOS
+*
+*     In addition, the following schemes can be used with
+f     AST_REBIN<X> but not with AST_RESAMPLE<X>:
+c     astRebin<X> but not with astResample<X>:
+*
+*     - AST__GAUSS: This scheme uses a kernel of the form exp(-k*x*x), with k
+*     a positive constant determined by the full-width at half-maximum (FWHM).
+*     The FWHM should be supplied in units of output pixels by means of the
+c     "params[1]"
+f     PARAMS(2)
+*     value and should be at least 0.1. The
+c     "params[0]"
+f     PARAMS(1)
+*     value should be used to specify at what point the Gaussian is truncated
+*     to zero. This should be given as a number of output pixels on either
+*     side of the central output point in each dimension (the nearest integer
+*     value is used).
+
+*  Control Flags:
+c     The following flags are defined in the "ast.h" header file and
+f     The following flags are defined in the AST_PAR include file and
+*     may be used to provide additional control over the rebinning
+*     process. Having selected a set of flags, you should supply the
+c     bitwise OR of their values via the "flags" parameter:
+f     sum of their values via the FLAGS argument:
+*
+*     - AST__USEBAD: Indicates that there may be bad pixels in the
+*     input array(s) which must be recognised by comparing with the
+c     value given for "badval" and propagated to the output array(s).
+f     value given for BADVAL and propagated to the output array(s).
+*     If this flag is not set, all input values are treated literally
+c     and the "badval" value is only used for flagging output array
+f     and the BADVAL value is only used for flagging output array
+*     values.
+f     - AST__USEVAR: Indicates that variance information should be
+f     processed in order to provide estimates of the statistical error
+f     associated with the rebined values. If this flag is not set,
+f     no variance processing will occur and the IN_VAR and OUT_VAR
+f     arrays will not be used. (Note that this flag is only available
+f     in the Fortran interface to AST.)
+
+*  Propagation of Missing Data:
+*     Instances of missing data (bad pixels) in the output grid are
+c     identified by occurrences of the "badval" value in the "out"
+f     identified by occurrences of the BADVAL value in the OUT
+*     array. These are produced if the sum of the weights of the
+*     contributing input pixels is less than
+c     "wlim".
+f     WLIM.
+*
+*     An input pixel is considered bad (and is consequently ignored) if
+*     its
+c     data value is equal to "badval" and the AST__USEBAD flag is
+c     set via the "flags" parameter.
+f     data value is equal to BADVAL and the AST__USEBAD flag is
+f     set via the FLAGS argument.
+*
+*     In addition, associated output variance estimates (if
+c     calculated) may be declared bad and flagged with the "badval"
+c     value in the "out_var" array for similar reasons.
+f     calculated) may be declared bad and flagged with the BADVAL
+f     value in the OUT_VAR array for similar reasons.
+*--
+*/
+/* Define a macro to implement the function for a specific data
+   type. */
+#define MAKE_REBIN(X,Xtype,IntType) \
+static void Rebin##X( AstMapping *this, double wlim, int ndim_in, \
+                     const int lbnd_in[], const int ubnd_in[], \
+                     const Xtype in[], const Xtype in_var[], \
+                     int spread, const double params[], int flags, \
+                     double tol, int maxpix, Xtype badval, \
+                     int ndim_out, const int lbnd_out[], \
+                     const int ubnd_out[], const int lbnd[], \
+                     const int ubnd[], Xtype out[], Xtype out_var[], int *status ) { \
+\
+/* Local Variables: */ \
+   astDECLARE_GLOBALS            /* Thread-specific data */ \
+   const char *badflag;          /* Name of illegal flag */ \
+   AstMapping *simple;           /* Pointer to simplified Mapping */ \
+   Xtype *d;                     /* Pointer to next output data value */ \
+   Xtype *v;                     /* Pointer to next output variance value */ \
+   double *w;                    /* Pointer to next weight value */ \
+   double *work;                 /* Pointer to weight array */ \
+   int idim;                     /* Loop counter for coordinate dimensions */ \
+   int ipix_out;                 /* Index into output array */ \
+   int nin;                      /* Number of Mapping input coordinates */ \
+   int nout;                     /* Number of Mapping output coordinates */ \
+   int npix;                     /* Number of pixels in input region */ \
+   int npix_out;                 /* Number of pixels in output array */ \
+   int64_t mpix;                 /* Number of pixels for testing */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Get a pointer to a structure holding thread-specific global data values */ \
+   astGET_GLOBALS(this); \
+\
+/* Obtain values for the Nin and Nout attributes of the Mapping. */ \
+   nin = astGetNin( this ); \
+   nout = astGetNout( this ); \
+\
+/* If OK, check that the number of input grid dimensions matches the \
+   number required by the Mapping and is at least 1. Report an error \
+   if necessary. */ \
+   if ( astOK && ( ( ndim_in != nin ) || ( ndim_in < 1 ) ) ) { \
+      astError( AST__NGDIN, "astRebin"#X"(%s): Bad number of input grid " \
+                "dimensions (%d).", status, astGetClass( this ), ndim_in ); \
+      if ( ndim_in != nin ) { \
+         astError( AST__NGDIN, "The %s given requires %d coordinate value%s " \
+                   "to specify an input position.", status, \
+                   astGetClass( this ), nin, ( nin == 1 ) ? "" : "s" ); \
+      } \
+   } \
+\
+/* If OK, also check that the number of output grid dimensions matches \
+   the number required by the Mapping and is at least 1. Report an \
+   error if necessary. */ \
+   if ( astOK && ( ( ndim_out != nout ) || ( ndim_out < 1 ) ) ) { \
+      astError( AST__NGDIN, "astRebin"#X"(%s): Bad number of output grid " \
+                "dimensions (%d).", status, astGetClass( this ), ndim_out ); \
+      if ( ndim_out != nout ) { \
+         astError( AST__NGDIN, "The %s given generates %s%d coordinate " \
+                   "value%s for each output position.", status, astGetClass( this ), \
+                   ( nout < ndim_out ) ? "only " : "", nout, \
+                   ( nout == 1 ) ? "" : "s" ); \
+      } \
+   } \
+\
+/* Check that the lower and upper bounds of the input grid are \
+   consistent. Report an error if any pair is not. */ \
+   mpix = 1; \
+   if ( astOK ) { \
+      for ( idim = 0; idim < ndim_in; idim++ ) { \
+         if ( lbnd_in[ idim ] > ubnd_in[ idim ] ) { \
+            astError( AST__GBDIN, "astRebin"#X"(%s): Lower bound of " \
+                      "input grid (%d) exceeds corresponding upper bound " \
+                      "(%d).", status, astGetClass( this ), \
+                      lbnd_in[ idim ], ubnd_in[ idim ] ); \
+            astError( AST__GBDIN, "Error in input dimension %d.", status, \
+                      idim + 1 ); \
+            break; \
+         } else { \
+            mpix *= ubnd_in[ idim ] - lbnd_in[ idim ] + 1; \
+         } \
+      } \
+   } \
+\
+/* Report an error if there are too many pixels in the input. */ \
+   if ( astOK && (int) mpix != mpix ) { \
+      astError( AST__EXSPIX, "astRebin"#X"(%s): Supplied input array " \
+                "contains too many pixels (%g): must be fewer than %d.", \
+                status, astGetClass( this ), (double) mpix, INT_MAX ); \
+   } \
+\
+/* Check that the positional accuracy tolerance supplied is valid and \
+   report an error if necessary. */ \
+   if ( astOK && ( tol < 0.0 ) ) { \
+      astError( AST__PATIN, "astRebin"#X"(%s): Invalid positional " \
+                "accuracy tolerance (%.*g pixel).", status, \
+                astGetClass( this ), DBL_DIG, tol ); \
+      astError( AST__PATIN, "This value should not be less than zero." , status); \
+   } \
+\
+/* Check that the initial scale size in pixels supplied is valid and \
+   report an error if necessary. */ \
+   if ( astOK && ( maxpix < 0 ) ) { \
+      astError( AST__SSPIN, "astRebin"#X"(%s): Invalid initial scale " \
+                "size in pixels (%d).", status, astGetClass( this ), maxpix ); \
+      astError( AST__SSPIN, "This value should not be less than zero." , status); \
+   } \
+\
+/* Check that the lower and upper bounds of the output grid are \
+   consistent. Report an error if any pair is not. */ \
+   mpix = 1; \
+   if ( astOK ) { \
+      for ( idim = 0; idim < ndim_out; idim++ ) { \
+         if ( lbnd_out[ idim ] > ubnd_out[ idim ] ) { \
+            astError( AST__GBDIN, "astRebin"#X"(%s): Lower bound of " \
+                      "output grid (%d) exceeds corresponding upper bound " \
+                      "(%d).", status, astGetClass( this ), \
+                      lbnd_out[ idim ], ubnd_out[ idim ] ); \
+            astError( AST__GBDIN, "Error in output dimension %d.", status, \
+                      idim + 1 ); \
+            break; \
+         } else { \
+            mpix *= ubnd_out[ idim ] - lbnd_out[ idim ] + 1; \
+         } \
+      } \
+   } \
+\
+/* Report an error if there are too many pixels in the output. */ \
+   if ( astOK && (int) mpix != mpix ) { \
+      astError( AST__EXSPIX, "astRebin"#X"(%s): Supplied output array " \
+                "contains too many pixels (%g): must be fewer than %d.", \
+                status, astGetClass( this ), (double) mpix, INT_MAX ); \
+   } \
+\
+/* Similarly check the bounds of the input region. */ \
+   mpix = 1; \
+   if ( astOK ) { \
+      for ( idim = 0; idim < ndim_out; idim++ ) { \
+         if ( lbnd[ idim ] > ubnd[ idim ] ) { \
+            astError( AST__GBDIN, "astRebin"#X"(%s): Lower bound of " \
+                      "input region (%d) exceeds corresponding upper " \
+                      "bound (%d).", status, astGetClass( this ), \
+                      lbnd[ idim ], ubnd[ idim ] ); \
+\
+/* Also check that the input region lies wholly within the input \
+   grid. */ \
+         } else if ( lbnd[ idim ] < lbnd_in[ idim ] ) { \
+            astError( AST__GBDIN, "astRebin"#X"(%s): Lower bound of " \
+                      "input region (%d) is less than corresponding " \
+                      "bound of input grid (%d).", status, astGetClass( this ), \
+                      lbnd[ idim ], lbnd_in[ idim ] ); \
+         } else if ( ubnd[ idim ] > ubnd_in[ idim ] ) { \
+            astError( AST__GBDIN, "astRebin"#X"(%s): Upper bound of " \
+                      "input region (%d) exceeds corresponding " \
+                      "bound of input grid (%d).", status, astGetClass( this ), \
+                      ubnd[ idim ], ubnd_in[ idim ] ); \
+         } else { \
+            mpix *= ubnd[ idim ] - lbnd[ idim ] + 1; \
+         } \
+\
+/* Say which dimension produced the error. */ \
+         if ( !astOK ) { \
+            astError( AST__GBDIN, "Error in output dimension %d.", status, \
+                      idim + 1 ); \
+            break; \
+         } \
+      } \
+   } \
+\
+/* Report an error if there are too many pixels in the input region. */ \
+   if ( astOK && (int) mpix != mpix ) { \
+      astError( AST__EXSPIX, "astRebin"#X"(%s): Supplied input region " \
+                "contains too many pixels (%g): must be fewer than %d.", \
+                status, astGetClass( this ), (double) mpix, INT_MAX ); \
+   } \
+\
+/* If OK, loop to determine how many input pixels are to be binned. */ \
+   simple = NULL; \
+   npix = 1; \
+   npix_out = 1; \
+   unsimplified_mapping = this; \
+   if ( astOK ) { \
+      for ( idim = 0; idim < ndim_in; idim++ ) { \
+         npix *= ubnd[ idim ] - lbnd[ idim ] + 1; \
+      } \
+\
+/* Loop to determine how many pixels the output array contains. */ \
+      for ( idim = 0; idim < ndim_out; idim++ ) { \
+         npix_out *= ubnd_out[ idim ] - lbnd_out[ idim ] + 1; \
+      } \
+\
+/* If there are sufficient pixels to make it worthwhile, simplify the \
+   Mapping supplied to improve performance. Otherwise, just clone the \
+   Mapping pointer. Note we have already saved a pointer to the original \
+   Mapping so that lower-level functions can use it if they need to report \
+   an error. */ \
+      if ( npix > 1024 ) { \
+         simple = astSimplify( this ); \
+      } else { \
+         simple = astClone( this ); \
+      } \
+   } \
+\
+/* Report an error if the forward transformation of this simplified \
+   Mapping is not defined. */ \
+   if ( !astGetTranForward( simple ) && astOK ) { \
+      astError( AST__TRNND, "astRebin"#X"(%s): An forward coordinate " \
+                "transformation is not defined by the %s supplied.", status, \
+                astGetClass( unsimplified_mapping ), \
+                astGetClass( unsimplified_mapping ) ); \
+   } \
+\
+/* Report an error if any illegal flags were supplied. */ \
+   if( flags & AST__REBININIT ) { \
+      badflag = "AST__REBININIT"; \
+   } else if( flags & AST__REBINEND ) { \
+      badflag = "AST__REBINEND"; \
+   } else if( flags & AST__GENVAR ) { \
+      badflag = "AST__GENVAR"; \
+   } else if( flags & AST__DISVAR ) { \
+      badflag = "AST__DISVAR"; \
+   } else if( flags & AST__VARWGT ) { \
+      badflag = "AST__VARWGT"; \
+   } else if( flags & AST__NONORM ) { \
+      badflag = "AST__NONORM"; \
+   } else if( flags & AST__CONSERVEFLUX ) { \
+      badflag = "AST__CONSERVEFLUX"; \
+   } else if( flags & ~( AST__USEBAD + AST__USEVAR ) ) { \
+      badflag = "unknown"; \
+   } else { \
+      badflag = NULL; \
+   } \
+   if ( badflag && astOK ) { \
+      astError( AST__BADFLG, "astRebin"#X"(%s): An illegal flag (%s) "  \
+                "was included in the 'flags' argument.", status, \
+                astGetClass( unsimplified_mapping ), badflag ); \
+   }  \
+\
+/* If required, allocate work array to hold the sum of the weights \
+   contributing to each output pixel, and initialise it to zero. */ \
+   if( wlim > 0.0 ) { \
+      work = astMalloc( sizeof( double )*(size_t) npix_out ); \
+      if( work ) { \
+         w = work; \
+         for( ipix_out = 0; ipix_out < npix_out; ipix_out++ ) *(w++) = 0.0; \
+      } \
+   } else { \
+      work = NULL; \
+   } \
+\
+/* Initialise the output arrays to hold zeros. */ \
+   d = out; \
+   if( out_var ) { \
+      v = out_var; \
+      for( ipix_out = 0; ipix_out < npix_out; ipix_out++, d++, v++ ) { \
+         *d = 0; \
+         *v = 0; \
+      } \
+   } else { \
+      for( ipix_out = 0; ipix_out < npix_out; ipix_out++, d++ ) { \
+         *d = 0; \
+      } \
+   } \
+\
+/* Perform the rebinning. Note that we pass all gridded data, the \
+   spread function and the bad pixel value by means of pointer \
+   types that obscure the underlying data type. This is to avoid \
+   having to replicate functions unnecessarily for each data \
+   type. However, we also pass an argument that identifies the data \
+   type we have obscured. */ \
+   if( RebinAdaptively( simple, ndim_in, lbnd_in, ubnd_in, \
+                        (const void *) in, (const void *) in_var, \
+                        TYPE_##X, spread, \
+                        params, flags, tol, maxpix, \
+                        (const void *) &badval, \
+                        ndim_out, lbnd_out, ubnd_out, \
+                        lbnd, ubnd, npix_out, \
+                        (void *) out, (void *) out_var, work, \
+                        NULL, status ) && astOK ) { \
+      astError( AST__CNFLX, "astRebin"#X"(%s): Flux conservation was " \
+                "requested but could not be performed because the " \
+                "forward transformation of the supplied Mapping " \
+                "is too non-linear.", status, astGetClass( this ) ); \
+   } \
+\
+/* If required set output pixels bad if they have a total weight less \
+   than "wlim". */ \
+   if( work ) { \
+      w = work; \
+      d = out; \
+      if( out_var ) { \
+         v = out_var; \
+         for( ipix_out = 0; ipix_out < npix_out; ipix_out++, d++, w++, v++ ) { \
+            if( fabs( *w ) < wlim ) { \
+               *d = badval; \
+               *v = badval; \
+            } \
+         } \
+      } else { \
+         for( ipix_out = 0; ipix_out < npix_out; ipix_out++, d++, w++ ) { \
+            if( fabs( *w ) < wlim ) *d = badval; \
+         } \
+      } \
+\
+/* Free the work array. */ \
+      work = astFree( work ); \
+   } \
+\
+/* Annul the pointer to the simplified/cloned Mapping. */ \
+   simple = astAnnul( simple ); \
+\
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_REBIN(LD,long double,0)
+#endif
+MAKE_REBIN(D,double,0)
+MAKE_REBIN(F,float,0)
+MAKE_REBIN(I,int,1)
+MAKE_REBIN(B,signed char,1)
+MAKE_REBIN(UB,unsigned char,1)
+
+/* Undefine the macro. */
+#undef MAKE_REBIN
+
+static int RebinAdaptively( AstMapping *this, int ndim_in,
+                            const int *lbnd_in, const int *ubnd_in,
+                            const void *in, const void *in_var,
+                            DataType type, int spread,
+                            const double *params, int flags, double tol,
+                            int maxpix, const void *badval_ptr,
+                            int ndim_out, const int *lbnd_out,
+                            const int *ubnd_out, const int *lbnd,
+                            const int *ubnd, int npix_out,
+                            void *out, void *out_var, double *work,
+                            int64_t *nused, int *status ){
+/*
+*  Name:
+*     RebinAdaptively
+
+*  Purpose:
+*     Rebin a section of a data grid adaptively.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int RebinAdaptively( AstMapping *this, int ndim_in,
+*                          const int *lbnd_in, const int *ubnd_in,
+*                          const void *in, const void *in_var,
+*                          DataType type, int spread,
+*                          const double *params, int flags, double tol,
+*                          int maxpix, const void *badval_ptr,
+*                          int ndim_out, const int *lbnd_out,
+*                          const int *ubnd_out, const int *lbnd,
+*                          const int *ubnd, int npix_out, void *out,
+*                          void *out_var, double *work, int64_t *nused,
+*                          int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function rebins a specified section of a rectangular grid of
+*     data (with any number of dimensions) into another rectangular grid
+*     (with a possibly different number of dimensions). The coordinate
+*     transformation used to convert input pixel coordinates into positions
+*     in the output grid is given by the forward transformation of the
+*     Mapping which is supplied.  Any pixel spreading scheme may be specified
+*     for distributing the flux of an input pixel amongst the output
+*     pixels.
+*
+*     This function is very similar to RebinWithBlocking and RebinSection
+*     which lie below it in the calling hierarchy. However, this function
+*     also attempts to adapt to the Mapping supplied and to sub-divide the
+*     section being rebinned into smaller sections within which a linear
+*     approximation to the Mapping may be used.  This reduces the number of
+*     Mapping evaluations, thereby improving efficiency particularly when
+*     complicated Mappings are involved.
+
+*  Parameters:
+*     this
+*        Pointer to a Mapping, whose forward transformation may be
+*        used to transform the coordinates of pixels in the input
+*        grid into associated positions in the output grid.
+*
+*        The number of input coordintes for the Mapping (Nin
+*        attribute) should match the value of "ndim_in" (below), and
+*        the number of output coordinates (Nout attribute) should
+*        match the value of "ndim_out".
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input data grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input data grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input data grid, its extent along a
+*        particular (i'th) dimension being (ubnd_in[i] - lbnd_in[i] +
+*        1). They also define the input grid's coordinate system, with
+*        each pixel being of unit extent along each dimension with
+*        integral coordinate values at its centre.
+*     in
+*        Pointer to the input array of data to be rebinned (with one
+*        element for each pixel in the input grid). The numerical type
+*        of these data should match the "type" value (below). The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and data type as the "in" array),
+*        which represent estimates of the statistical variance
+*        associated with each element of the "in" array. If this
+*        second array is given (along with the corresponding "out_var"
+*        array), then estimates of the variance of the rebinned data
+*        will also be returned.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     type
+*        A value taken from the "DataType" enum, which specifies the
+*        data type of the input and output arrays containing the
+*        gridded data (and variance) values.
+*     spread
+*        A value selected from a set of pre-defined macros to identify
+*        which pixel spread function should be used.
+*     params
+*        Pointer to an optional array of parameters that may be passed
+*        to the pixel spread algorithm, if required. If no parameters
+*        are required, a NULL pointer should be supplied.
+*     flags
+*        The bitwise OR of a set of flag values which provide additional
+*        control over the resampling operation.
+*     tol
+*        The maximum permitted positional error in transforming input
+*        pixel positions into the output grid in order to rebin
+*        it. This should be expressed as a displacement in pixels in
+*        the output grid's coordinate system. If the Mapping's forward
+*        transformation can be approximated by piecewise linear functions
+*        to this accuracy, then such functions may be used instead of the
+*        Mapping in order to improve performance. Otherwise, every input
+*        pixel position will be transformed individually using the Mapping.
+*
+*        If linear approximation is not required, a "tol" value of
+*        zero may be given. This will ensure that the Mapping is used
+*        without any approximation.
+*     maxpix
+*        A value which specifies the largest scale size on which to
+*        search for non-linearities in the Mapping supplied. This
+*        value should be expressed as a number of pixels in the input
+*        grid. The function will break the input section specified
+*        into smaller sub-sections (if necessary), each no larger than
+*        "maxpix" pixels in any dimension, before it attempts to
+*        approximate the Mapping by a linear function over each
+*        sub-section.
+*
+*        If the value given is larger than the largest dimension of
+*        the input section (the normal recommendation), the function
+*        will initially search for non-linearity on a scale determined
+*        by the size of the input section.  This is almost always
+*        satisfactory. Very occasionally, however, a Mapping may
+*        appear linear on this scale but nevertheless have smaller
+*        irregularities (e.g. "holes") in it.  In such cases, "maxpix"
+*        may be set to a suitably smaller value so as to ensure this
+*        non-linearity is not overlooked. Typically, a value of 50 to
+*        100 pixels might be suitable and should have little effect on
+*        performance.
+*
+*        If too small a value is given, however, it will have the
+*        effect of preventing linear approximation occurring at all
+*        (equivalent to setting "tol" to zero).  Although this may
+*        degrade performance, accurate results will still be obtained.
+*     badval_ptr
+*        If the AST__USEBAD flag is set (above), this parameter is a
+*        pointer to a value which is used to identify bad data and/or
+*        variance values in the input array(s). The referenced value's
+*        data type must match that of the "in" (and "in_var")
+*        arrays. The same value will also be used to flag any output
+*        array elements for which rebinned values could not be
+*        obtained.  The output arrays(s) may be flagged with this
+*        value whether or not the AST__USEBAD flag is set (the
+*        function return value indicates whether any such values have
+*        been produced).
+*     ndim_out
+*        The number of dimensions in the output grid. This should be
+*        at least one.
+*     lbnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the output data grid along each dimension.
+*     ubnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the output data grid along each dimension.
+*
+*        Note that "lbnd_out" and "ubnd_out" together define the shape
+*        and size of the output data grid in the same way as "lbnd_in"
+*        and "ubnd_in" define the shape and size of the input grid
+*        (see above).
+*     lbnd
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the first pixel in the
+*        section of the input data grid which is to be rebinned.
+*     ubnd
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the last pixel in the
+*        section of the input data grid which is to be rebinned.
+*
+*        Note that "lbnd" and "ubnd" define the shape and position of
+*        the section of the input grid which is to be rebinned. This section
+*        should lie wholly within the extent of the input grid (as defined
+*        by the "lbnd_out" and "ubnd_out" arrays). Regions of the input
+*        grid lying outside this section will be ignored.
+*     npix_out
+*        The number of pixels in the output array.
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the rebinned data will be returned.  The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the rebinned values may be returned. This array will only be
+*        used if the "in_var" array has been given.
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+*     work
+*        An optional pointer to a double array with the same size as
+*        the "out" array. The contents of this array (if supplied) are
+*        incremented by the accumulated weights assigned to each output pixel.
+*        If no accumulated weights are required, a NULL pointer should be
+*        given.
+*     nused
+*        An optional pointer to a int64_t which will be incremented by the
+*        number of input values pasted into the output array. Ignored if NULL.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if "flags" included AST__CONSERVEFLUX (i.e.
+*     flux conservation was requested), but the forward transformation of the
+*     supplied Mapping had zero determinant everywhere within the region
+*     being binned (no error is reported if this happens). Zero is returned
+*     otherwise.
+
+*/
+
+/* Local Variables: */
+   double *flbnd;                /* Array holding floating point lower bounds */
+   double *fubnd;                /* Array holding floating point upper bounds */
+   double *linear_fit;           /* Pointer to array of fit coefficients */
+   int *hi;                      /* Pointer to array of section upper bounds */
+   int *lo;                      /* Pointer to array of section lower bounds */
+   int coord_in;                 /* Loop counter for input coordinates */
+   int dim;                      /* Output section dimension size */
+   int dimx;                     /* Dimension with maximum section extent */
+   int divide;                   /* Sub-divide the output section? */
+   int i;                        /* Loop count */
+   int isLinear;                 /* Is the transformation linear? */
+   int mxdim;                    /* Largest output section dimension size */
+   int need_fit;                 /* Do we need to perform a linear fit? */
+   int npix;                     /* Number of pixels in output section */
+   int npoint;                   /* Number of points for obtaining a fit */
+   int nvertex;                  /* Number of vertices of output section */
+   int result;                   /* Returned value */
+   int res1;                     /* Flux conservation error in 1st section? */
+   int res2;                     /* Flux conservation error in 2nd section? */
+   int toobig;                   /* Section too big (must sub-divide)? */
+   int toosmall;                 /* Section too small to sub-divide? */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialisation. */
+   npix = 1;
+   mxdim = 0;
+   dimx = 1;
+   nvertex = 1;
+
+/* Loop through the input grid dimensions. */
+   for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+
+/* Obtain the extent in each dimension of the input section which is
+   to be rebinned, and calculate the total number of pixels it contains. */
+      dim = ubnd[ coord_in ] - lbnd[ coord_in ] + 1;
+      npix *= dim;
+
+/* Find the maximum dimension size of this input section and note which
+   dimension has this size. */
+      if ( dim > mxdim ) {
+         mxdim = dim;
+         dimx = coord_in;
+      }
+
+/* Calculate how many vertices the output section has. */
+      nvertex *= 2;
+   }
+
+/* Calculate how many sample points will be needed (by the astLinearApprox
+   function) to obtain a linear fit to the Mapping's forward transformation. */
+   npoint = 1 + 4 * ndim_in + 2 * nvertex;
+
+/* If the number of pixels in the input section is not at least 4
+   times this number, we will probably not save significant time by
+   attempting to obtain a linear fit, so note that the input section
+   is too small. */
+   toosmall = ( npix < ( 4 * npoint ) );
+
+/* Note if the maximum dimension of the input section exceeds the
+   user-supplied scale factor. */
+   toobig = ( maxpix < mxdim );
+
+/* Indicate we do not yet have a linear fit. */
+   linear_fit = NULL;
+
+/* Initialise a flag indicating if we need to perform a linear fit. This
+   is always the case if flux conservation was requested. */
+   need_fit = ( flags & AST__CONSERVEFLUX );
+
+/* If the output section is too small to be worth obtaining a linear
+   fit, or if the accuracy tolerance is zero, we will not
+   sub-divide. This means that the Mapping will be used to transform
+   each pixel's coordinates and no linear approximation will be
+   used. */
+   if ( toosmall || ( tol == 0.0 ) ) {
+      divide = 0;
+
+/* Otherwise, if the largest input section dimension exceeds the
+   scale length given, we will sub-divide. This offers the possibility
+   of obtaining a linear approximation to the Mapping over a reduced
+   range of input coordinates (which will be handled by a recursive
+   invocation of this function). */
+   } else if ( toobig ) {
+      divide = 1;
+
+/* If neither of the above apply, we need to do a fit regardless of
+   whether flux conservation was requested or not. Whether we divide or
+   not will depend on whether the Mapping is linear or not. Assume for
+   the moment that the Mapping is not linear and so we will divide. */
+   } else {
+      need_fit = 1;
+      divide = 1;
+   }
+
+/* If required, attempt to fit a linear approximation to the Mapping's
+   forward transformation over the range of coordinates covered by the
+   input section. We need to temporarily copy the integer bounds into
+   floating point arrays to use astLinearApprox. */
+   if( need_fit ) {
+
+/* Allocate memory for floating point bounds and for the coefficient array */
+      flbnd = astMalloc( sizeof( double )*(size_t) ndim_in );
+      fubnd = astMalloc( sizeof( double )*(size_t) ndim_in );
+      linear_fit = astMalloc( sizeof( double )*
+                              (size_t) ( ndim_out*( ndim_in + 1 ) ) );
+      if( astOK ) {
+
+/* Copy the bounds into these arrays, and change them so that they refer
+   to the lower and upper edges of the cell rather than the centre. This
+   is essential if one of the axes is spanned by a single cell, since
+   otherwise the upper and lower bounds would be identical. */
+         for( i = 0; i < ndim_in; i++ ) {
+            flbnd[ i ] = (double) lbnd[ i ] - 0.5;
+            fubnd[ i ] = (double) ubnd[ i ] + 0.5;
+         }
+
+/* Get the linear approximation to the forward transformation. */
+         isLinear = astLinearApprox( this, flbnd, fubnd, tol, linear_fit );
+
+/* Free the coeff array if the inverse transformation is not linear. */
+         if( !isLinear ) linear_fit = astFree( linear_fit );
+
+      } else {
+         linear_fit = astFree( linear_fit );
+      }
+
+/* Free resources */
+      flbnd = astFree( flbnd );
+      fubnd = astFree( fubnd );
+
+/* If a linear fit was obtained, we will use it and therefore do not
+   wish to sub-divide further. Otherwise, we sub-divide (unless the
+   section is too small or too big as determined earlier) in the hope
+   that this may result in a linear fit next time. */
+      if( linear_fit ) divide = 0;
+   }
+
+/* If no sub-division is required, perform rebinning (in a
+   memory-efficient manner, since the section we are rebinning might
+   still be very large). This will use the linear fit, if obtained
+   above. */
+   if ( astOK ) {
+      if ( !divide ) {
+         result = RebinWithBlocking( this, linear_fit, ndim_in, lbnd_in,
+                                     ubnd_in, in, in_var, type, spread,
+                                     params, flags, badval_ptr, ndim_out,
+                                     lbnd_out, ubnd_out, lbnd, ubnd, npix_out,
+                                     out, out_var, work, nused, status );
+
+/* Otherwise, allocate workspace to perform the sub-division. */
+      } else {
+         lo = astMalloc( sizeof( int ) * (size_t) ndim_in );
+         hi = astMalloc( sizeof( int ) * (size_t) ndim_in );
+         if ( astOK ) {
+
+/* Initialise the bounds of a new input section to match the original
+   input section. */
+            for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+               lo[ coord_in ] = lbnd[ coord_in ];
+               hi[ coord_in ] = ubnd[ coord_in ];
+            }
+
+/* Replace the upper bound of the section's largest dimension with the
+   mid-point of the section along this dimension, rounded downwards. */
+            hi[ dimx ] =
+               (int) floor( 0.5 * (double) ( lbnd[ dimx ] + ubnd[ dimx ] ) );
+
+/* Rebin the resulting smaller section using a recursive invocation
+   of this function. */
+            res1 = RebinAdaptively( this, ndim_in, lbnd_in, ubnd_in, in,
+                                    in_var, type, spread, params,
+                                    flags, tol, maxpix, badval_ptr, ndim_out,
+                                    lbnd_out, ubnd_out, lo, hi, npix_out, out,
+                                    out_var, work, nused, status );
+
+/* Now set up a second section which covers the remaining half of the
+   original input section. */
+            lo[ dimx ] = hi[ dimx ] + 1;
+            hi[ dimx ] = ubnd[ dimx ];
+
+/* If this section contains pixels, resample it in the same way,
+   summing the returned values. */
+            if ( lo[ dimx ] <= hi[ dimx ] ) {
+               res2 = RebinAdaptively( this, ndim_in, lbnd_in, ubnd_in, in,
+                                       in_var, type, spread, params,
+                                       flags, tol, maxpix, badval_ptr,
+                                       ndim_out, lbnd_out, ubnd_out,
+                                       lo, hi, npix_out, out, out_var, work,
+                                       nused, status );
+            } else {
+               res2 = 0;
+            }
+
+/* If neither section could be rebinned because of an indeterminant
+   mapping, return a result indicating this. */
+            result = ( res1 && res2 );
+         }
+
+/* Free the workspace. */
+         lo = astFree( lo );
+         hi = astFree( hi );
+      }
+   }
+
+/* If coefficients for a linear fit were obtained, then free the space
+   they occupy. */
+   if ( linear_fit ) linear_fit = astFree( linear_fit );
+
+/* Retyurn a flag indicating if no part of the array could be binned
+   because of an indeterminate Mapping. */
+   return result;
+}
+
+static void RebinSection( AstMapping *this, const double *linear_fit,
+                          int ndim_in, const int *lbnd_in, const int *ubnd_in,
+                          const void *in, const void *in_var, double infac,
+                          DataType type, int spread, const double *params,
+                          int flags, const void *badval_ptr, int ndim_out,
+                          const int *lbnd_out, const int *ubnd_out,
+                          const int *lbnd, const int *ubnd, int npix_out,
+                          void *out, void *out_var, double *work,
+                          int64_t *nused, int *status ) {
+/*
+*  Name:
+*     RebinSection
+
+*  Purpose:
+*     Rebin a section of a data grid.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void RebinSection( AstMapping *this, const double *linear_fit,
+*                        int ndim_in, const int *lbnd_in, const int *ubnd_in,
+*                        const void *in, const void *in_var, double infac,
+*                        DataType type, int spread, const double *params,
+*                        int flags, const void *badval_ptr, int ndim_out,
+*                        const int *lbnd_out, const int *ubnd_out,
+*                        const int *lbnd, const int *ubnd, int npix_out,
+*                        void *out, void *out_var, double *work,
+*                        int64_t *nused, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function rebins a specified section of a rectangular grid of
+*     data (with any number of dimensions) into another rectangular grid
+*     (with a possibly different number of dimensions). The coordinate
+*     transformation used to convert input pixel coordinates into positions
+*     in the output grid is given by the forward transformation of the
+*     Mapping which is supplied or, alternatively, by a linear approximation
+*     fitted to a Mapping's forward transformation. Any pixel spreading scheme
+*     may be specified for distributing the flux of an input pixel amongst
+*     the output pixels.
+
+*  Parameters:
+*     this
+*        Pointer to a Mapping, whose forward transformation may be
+*        used to transform the coordinates of pixels in the input
+*        grid into associated positions in the output grid.
+*
+*        The number of input coordintes for the Mapping (Nin
+*        attribute) should match the value of "ndim_in" (below), and
+*        the number of output coordinates (Nout attribute) should
+*        match the value of "ndim_out".
+*     linear_fit
+*        Pointer to an optional array of double which contains the
+*        coefficients of a linear fit which approximates the above
+*        Mapping's forward coordinate transformation. If this is
+*        supplied, it will be used in preference to the above Mapping
+*        when transforming coordinates. This may be used to enhance
+*        performance in cases where evaluation of the Mapping's
+*        forward transformation is expensive. If no linear fit is
+*        available, a NULL pointer should be supplied.
+*
+*        The way in which the fit coefficients are stored in this
+*        array and the number of array elements are as defined by the
+*        astLinearApprox function.
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input data grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input data grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input data grid, its extent along a
+*        particular (i'th) dimension being (ubnd_in[i] - lbnd_in[i] +
+*        1). They also define the input grid's coordinate system, with
+*        each pixel being of unit extent along each dimension with
+*        integral coordinate values at its centre.
+*     in
+*        Pointer to the input array of data to be rebinned (with one
+*        element for each pixel in the input grid). The numerical type
+*        of these data should match the "type" value (below). The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and data type as the "in" array),
+*        which represent estimates of the statistical variance
+*        associated with each element of the "in" array. If this
+*        second array is given (along with the corresponding "out_var"
+*        array), then estimates of the variance of the rebinned data
+*        will also be returned.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     infac
+*        A factor by which to multiply the input data values before use.
+*     type
+*        A value taken from the "DataType" enum, which specifies the
+*        data type of the input and output arrays containing the
+*        gridded data (and variance) values.
+*     spread
+*        A value selected from a set of pre-defined macros to identify
+*        which pixel spread function should be used.
+*     params
+*        Pointer to an optional array of parameters that may be passed
+*        to the pixel spread algorithm, if required. If no parameters
+*        are required, a NULL pointer should be supplied.
+*     flags
+*        The bitwise OR of a set of flag values which provide additional
+*        control over the resampling operation.
+*     badval_ptr
+*        If the AST__USEBAD flag is set (above), this parameter is a
+*        pointer to a value which is used to identify bad data and/or
+*        variance values in the input array(s). The referenced value's
+*        data type must match that of the "in" (and "in_var")
+*        arrays. The same value will also be used to flag any output
+*        array elements for which rebinned values could not be
+*        obtained.  The output arrays(s) may be flagged with this
+*        value whether or not the AST__USEBAD flag is set (the
+*        function return value indicates whether any such values have
+*        been produced).
+*     ndim_out
+*        The number of dimensions in the output grid. This should be
+*        at least one.
+*     lbnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the output data grid along each dimension.
+*     ubnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the output data grid along each dimension.
+*
+*        Note that "lbnd_out" and "ubnd_out" together define the shape
+*        and size of the output data grid in the same way as "lbnd_in"
+*        and "ubnd_in" define the shape and size of the input grid
+*        (see above).
+*     lbnd
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the first pixel in the
+*        section of the input data grid which is to be rebinned.
+*     ubnd
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the last pixel in the
+*        section of the input data grid which is to be rebinned.
+*
+*        Note that "lbnd" and "ubnd" define the shape and position of
+*        the section of the input grid which is to be rebinned. This section
+*        should lie wholly within the extent of the input grid (as defined
+*        by the "lbnd_out" and "ubnd_out" arrays). Regions of the input
+*        grid lying outside this section will be ignored.
+*     npix_out
+*        The number of pixels in the output array.
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the rebinned data will be returned.  The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the rebinned values may be returned. This array will only be
+*        used if the "in_var" array has been given.
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+*     work
+*        An optional pointer to a double array with the same size as
+*        the "out" array. The contents of this array (if supplied) are
+*        incremented by the accumulated weights assigned to each output pixel.
+*        If no accumulated weights are required, a NULL pointer should be
+*        given.
+*     nused
+*        An optional pointer to a int64_t which will be incremented by the
+*        number of input values pasted into the output array. Ignored if NULL.
+
+*  Notes:
+*     - This function does not take steps to limit memory usage if the
+*     grids supplied are large. To resample large grids in a more
+*     memory-efficient way, the ResampleWithBlocking function should
+*     be used.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific data */
+   AstPointSet *pset_in;         /* Input PointSet for transformation */
+   AstPointSet *pset_out;        /* Output PointSet for transformation */
+   const double *grad;           /* Pointer to gradient matrix of linear fit */
+   const double *zero;           /* Pointer to zero point array of fit */
+   double **ptr_in;              /* Pointer to input PointSet coordinates */
+   double **ptr_out;             /* Pointer to output PointSet coordinates */
+   double *accum;                /* Pointer to array of accumulated sums */
+   double x1;                    /* Interim x coordinate value */
+   double xx1;                   /* Initial x coordinate value */
+   double y1;                    /* Interim y coordinate value */
+   double yy1;                   /* Initial y coordinate value */
+   int *dim;                     /* Pointer to array of output pixel indices */
+   int *offset;                  /* Pointer to array of output pixel offsets */
+   int *stride;                  /* Pointer to array of output grid strides */
+   int coord_in;                 /* Loop counter for input dimensions */
+   int coord_out;                /* Loop counter for output dimensions */
+   int done;                     /* All pixel indices done? */
+   int i1;                       /* Interim offset into "accum" array */
+   int i2;                       /* Final offset into "accum" array */
+   int idim;                     /* Loop counter for dimensions */
+   int ix;                       /* Loop counter for output x coordinate */
+   int iy;                       /* Loop counter for output y coordinate */
+   int neighb;                   /* Number of neighbouring pixels */
+   int npoint;                   /* Number of output points (pixels) */
+   int off1;                     /* Interim pixel offset into output array */
+   int off2;                     /* Interim pixel offset into output array */
+   int off;                      /* Final pixel offset into output array */
+   int point;                    /* Counter for output points (pixels ) */
+   int s;                        /* Temporary variable for strides */
+   const double *par;            /* Pointer to parameter array */
+   double fwhm;                  /* Full width half max. of gaussian */
+   double lpar[ 1 ];             /* Local parameter array */
+   void (* kernel)( double, const double [], int, double *, int * ); /* Kernel fn. */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to a structure holding thread-specific global data values */
+   astGET_GLOBALS(this);
+
+/* Further initialisation. */
+   pset_in = NULL;
+   ptr_in = NULL;
+   ptr_out = NULL;
+   pset_out = NULL;
+   neighb = 0;
+   kernel = NULL;
+
+/* Calculate the number of input points, as given by the product of
+   the input grid dimensions. */
+   for ( npoint = 1, coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+      npoint *= ubnd[ coord_in ] - lbnd[ coord_in ] + 1;
+   }
+
+/* Allocate workspace. */
+   offset = astMalloc( sizeof( int ) * (size_t) npoint );
+   stride = astMalloc( sizeof( int ) * (size_t) ndim_in );
+   if ( astOK ) {
+
+/* Calculate the stride for each input grid dimension. */
+      off = 0;
+      s = 1;
+      for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+         stride[ coord_in ] = s;
+         s *= ubnd_in[ coord_in ] - lbnd_in[ coord_in ] + 1;
+      }
+
+/* A linear fit to the Mapping is available. */
+/* ========================================= */
+      if ( linear_fit ) {
+
+/* If a linear fit to the Mapping has been provided, then obtain
+   pointers to the array of gradients and zero-points comprising the
+   fit. */
+         grad = linear_fit + ndim_out;
+         zero = linear_fit;
+
+/* Create a PointSet to hold the output grid coordinates and obtain an
+   array of pointers to its coordinate data. */
+         pset_out = astPointSet( npoint, ndim_out, "", status );
+         ptr_out = astGetPoints( pset_out );
+         if ( astOK ) {
+
+/* Initialise the count of input points. */
+            point = 0;
+
+/* Handle the 1-dimensional case optimally. */
+/* ---------------------------------------- */
+            if ( ( ndim_in == 1 ) && ( ndim_out == 1 ) ) {
+
+/* Loop through the pixels of the input grid and transform their x
+   coordinates into the output grid's coordinate system using the
+   linear fit supplied. Store the results in the PointSet created
+   above. */
+               off = lbnd[ 0 ] - lbnd_in[ 0 ];
+               xx1 = zero[ 0 ] + grad[ 0 ] * (double) lbnd[ 0 ];
+
+               for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                  ptr_out[ 0 ][ point ] = xx1;
+                  xx1 += grad[ 0 ];
+                  offset[ point++ ] = off++;
+               }
+
+/* Handle the 2-dimensional case optimally. */
+/* ---------------------------------------- */
+            } else if ( ( ndim_in == 2 ) && ( ndim_out == 2 ) ) {
+
+/* Loop through the range of y coordinates in the input grid and
+   calculate interim values of the output coordinates using the linear
+   fit supplied. */
+               x1 = zero[ 0 ] + grad[ 1 ] * (double) ( lbnd[ 1 ] - 1 );
+               y1 = zero[ 1 ] + grad[ 3 ] * (double) ( lbnd[ 1 ] - 1 );
+               off1 = stride[ 1 ] * ( lbnd[ 1 ] - lbnd_in[ 1 ] - 1 ) - lbnd_in[ 0 ];
+               for ( iy = lbnd[ 1 ]; iy <= ubnd[ 1 ]; iy++ ) {
+                  x1 += grad[ 1 ];
+                  y1 += grad[ 3 ];
+
+/* Also calculate an interim pixel offset into the input array. */
+                  off1 += stride[ 1 ];
+
+/* Now loop through the range of input x coordinates and calculate
+   the final values of the input coordinates, storing the results in
+   the PointSet created above. */
+                  xx1 = x1 + grad[ 0 ] * (double) lbnd[ 0 ];
+                  yy1 = y1 + grad[ 2 ] * (double) lbnd[ 0 ];
+                  off = off1 + lbnd[ 0 ];
+                  for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                     ptr_out[ 0 ][ point ] = xx1;
+                     xx1 += grad[ 0 ];
+                     ptr_out[ 1 ][ point ] = yy1;
+                     yy1 += grad[ 2 ];
+
+/* Also calculate final pixel offsets into the input array. */
+                     offset[ point++ ] = off++;
+                  }
+               }
+
+/* Handle other numbers of dimensions. */
+/* ----------------------------------- */
+            } else {
+
+/* Allocate workspace. */
+               accum = astMalloc( sizeof( double ) *
+                                 (size_t) ( ndim_in * ndim_out ) );
+               dim = astMalloc( sizeof( int ) * (size_t) ndim_in );
+               if ( astOK ) {
+
+/* Initialise an array of pixel indices for the input grid which refer to the
+   first pixel which we will rebin. Also calculate the offset of this pixel
+   within the input array. */
+                  off = 0;
+                  for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                     dim[ coord_in ] = lbnd[ coord_in ];
+                     off += stride[ coord_in ] *
+                            ( dim[ coord_in ] - lbnd_in[ coord_in ] );
+                  }
+
+/* To calculate each output grid coordinate we must perform a matrix
+   multiply on the input grid coordinates (using the gradient matrix)
+   and then add the zero points. However, since we will usually only
+   be altering one input coordinate at a time (the least
+   significant), we can avoid the full matrix multiply by accumulating
+   partial sums for the most significant input coordinates and only
+   altering those sums which need to change each time. The zero points
+   never change, so we first fill the "most significant" end of the
+   "accum" array with these. */
+                  for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+                     accum[ ( coord_out + 1 ) * ndim_in - 1 ] =
+                                                              zero[ coord_out ];
+                  }
+                  coord_in = ndim_in - 1;
+
+/* Now loop to process each input pixel. */
+                  for ( done = 0; !done; point++ ) {
+
+/* To generate the output coordinate that corresponds to the current
+   input pixel, we work down from the most significant dimension
+   whose index has changed since the previous pixel we considered
+   (given by "coord_in"). For each affected dimension, we accumulate
+   in "accum" the matrix sum (including the zero point) for that
+   dimension and all higher input dimensions. We must accumulate a
+   separate set of sums for each output coordinate we wish to
+   produce. (Note that for the first pixel we process, all dimensions
+   are considered "changed", so we start by initialising the whole
+   "accum" array.) */
+                     for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+/*
+                        ptr_out[ coord_out ][ point ] = zero[ coord_out ];
+                        for ( idim = 0; idim < ndim_in; idim++ ) {
+                           ptr_out[ coord_out ][ point ] +=
+                                 grad[ idim + coord_out*ndim_in ] *
+                                 dim[ idim ];
+                        }
+*/
+
+                        i1 = coord_out * ndim_in;
+                        for ( idim = coord_in; idim >= 1; idim-- ) {
+                           i2 = i1 + idim;
+                           accum[ i2 - 1 ] = accum[ i2 ] +
+                                             dim[ idim ] * grad[ i2 ];
+                        }
+
+/* The output coordinate for each dimension is given by the accumulated
+   sum for input dimension zero (giving the sum over all input
+   dimensions). We do not store this in the "accum" array, but assign
+   the result directly to the coordinate array of the PointSet created
+   earlier. */
+                        ptr_out[ coord_out ][ point ] = accum[ i1 ] +
+                                                      dim[ 0 ] * grad[ i1 ];
+                     }
+
+/* Store the offset of the current pixel in the input array. */
+                     offset[ point ] = off;
+
+/* Now update the array of pixel indices to refer to the next input pixel. */
+                     coord_in = 0;
+                     do {
+
+/* The least significant index which currently has less than its maximum
+   value is incremented by one. The offset into the input array is updated
+   accordingly. */
+                        if ( dim[ coord_in ] < ubnd[ coord_in ] ) {
+                           dim[ coord_in ]++;
+                           off += stride[ coord_in ];
+                           break;
+
+/* Any less significant indices which have reached their maximum value
+   are returned to their minimum value and the input pixel offset is
+   decremented appropriately. */
+                        } else {
+                           dim[ coord_in ] = lbnd[ coord_in ];
+                           off -= stride[ coord_in ] *
+                                  ( ubnd[ coord_in ] - lbnd[ coord_in ] );
+
+/* All the output pixels have been processed once the most significant
+   pixel index has been returned to its minimum value. */
+                           done = ( ++coord_in == ndim_in );
+                        }
+                     } while ( !done );
+                  }
+               }
+
+/* Free the workspace. */
+               accum = astFree( accum );
+               dim = astFree( dim );
+            }
+         }
+
+/* No linear fit to the Mapping is available. */
+/* ========================================== */
+      } else {
+
+/* Create a PointSet to hold the coordinates of the input pixels and
+   obtain a pointer to its coordinate data. */
+         pset_in = astPointSet( npoint, ndim_in, "", status );
+         ptr_in = astGetPoints( pset_in );
+         if ( astOK ) {
+
+/* Initialise the count of input points. */
+            point = 0;
+
+/* Handle the 1-dimensional case optimally. */
+/* ---------------------------------------- */
+            if ( ndim_in == 1 && ndim_out == 1 ) {
+
+/* Loop through the required range of input x coordinates, assigning
+   the coordinate values to the PointSet created above. Also store a
+   pixel offset into the input array. */
+               for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                  ptr_in[ 0 ][ point ] = (double) ix;
+                  offset[ point++ ] = ix - lbnd_in[ 0 ];
+               }
+
+/* Handle the 2-dimensional case optimally. */
+/* ---------------------------------------- */
+            } else if ( ndim_in == 2 && ndim_out == 2) {
+
+/* Loop through the required range of input y coordinates,
+   calculating an interim pixel offset into the input array. */
+               off1 = stride[ 1 ] * ( lbnd[ 1 ] - lbnd_in[ 1 ] - 1 )
+                      - lbnd_in[ 0 ];
+               for ( iy = lbnd[ 1 ]; iy <= ubnd[ 1 ]; iy++ ) {
+                  off1 += stride[ 1 ];
+
+/* Loop through the required range of input x coordinates, assigning
+   the coordinate values to the PointSet created above. Also store a
+   final pixel offset into the input array. */
+                  off2 = off1 + lbnd[ 0 ];
+                  for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                     ptr_in[ 0 ][ point ] = (double) ix;
+                     ptr_in[ 1 ][ point ] = (double) iy;
+                     offset[ point++ ] = off2++;
+                  }
+               }
+
+/* Handle other numbers of dimensions. */
+/* ----------------------------------- */
+            } else {
+
+/* Allocate workspace. */
+               dim = astMalloc( sizeof( int ) * (size_t) ndim_in );
+               if ( astOK ) {
+
+/* Initialise an array of pixel indices for the input grid which
+   refer to the first pixel to be rebinned. Also calculate the offset
+   of this pixel within the input array. */
+                  off = 0;
+                  for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                     dim[ coord_in ] = lbnd[ coord_in ];
+                     off += stride[ coord_in ] *
+                            ( dim[ coord_in ] - lbnd_in[ coord_in ] );
+                  }
+
+/* Loop to generate the coordinates of each input pixel. */
+                  for ( done = 0; !done; point++ ) {
+
+/* Copy each pixel's coordinates into the PointSet created above. */
+                     for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                        ptr_in[ coord_in ][ point ] =
+                                                     (double) dim[ coord_in ];
+                    }
+
+/* Store the offset of the pixel in the input array. */
+                     offset[ point ] = off;
+
+/* Now update the array of pixel indices to refer to the next input
+   pixel. */
+                     coord_in = 0;
+                     do {
+
+/* The least significant index which currently has less than its
+   maximum value is incremented by one. The offset into the input
+   array is updated accordingly. */
+                        if ( dim[ coord_in ] < ubnd[ coord_in ] ) {
+                           dim[ coord_in ]++;
+                           off += stride[ coord_in ];
+                           break;
+
+/* Any less significant indices which have reached their maximum value
+   are returned to their minimum value and the input pixel offset is
+   decremented appropriately. */
+                        } else {
+                           dim[ coord_in ] = lbnd[ coord_in ];
+                           off -= stride[ coord_in ] *
+                                  ( ubnd[ coord_in ] - lbnd[ coord_in ] );
+
+/* All the input pixels have been processed once the most significant
+   pixel index has been returned to its minimum value. */
+                           done = ( ++coord_in == ndim_in );
+                        }
+                     } while ( !done );
+                  }
+               }
+
+/* Free the workspace. */
+               dim = astFree( dim );
+            }
+
+/* When all the input pixel coordinates have been generated, use the
+   Mapping's forward transformation to generate the output coordinates
+   from them. Obtain an array of pointers to the resulting coordinate
+   data. */
+            pset_out = astTransform( this, pset_in, 1, NULL );
+            ptr_out = astGetPoints( pset_out );
+         }
+
+/* Annul the PointSet containing the input coordinates. */
+         pset_in = astAnnul( pset_in );
+      }
+   }
+
+
+/* Rebin the input grid. */
+/* ------------------------ */
+   if( astOK ) {
+
+/* Identify the pixel spreading scheme to be used. */
+/* Nearest pixel. */
+/* -------------- */
+      switch ( spread ) {
+         case AST__NEAREST:
+
+/* Define a macro to use a "case" statement to invoke the
+   nearest-pixel spreading function appropriate to a given data
+   type. */
+#define CASE_NEAREST(X,Xtype) \
+               case ( TYPE_##X ): \
+                  SpreadNearest##X( ndim_out, lbnd_out, ubnd_out, \
+                                    (Xtype *) in, (Xtype *) in_var, \
+                                    infac, npoint, offset, \
+                                    (const double *const *) ptr_out, \
+                                    flags, *( (Xtype *) badval_ptr ), \
+                                    npix_out, (Xtype *) out, \
+                                    (Xtype *) out_var, work, nused, status ); \
+                  break;
+
+/* Use the above macro to invoke the appropriate function. */
+            switch ( type ) {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+               CASE_NEAREST(LD,long double)
+#endif
+               CASE_NEAREST(D,double)
+               CASE_NEAREST(F,float)
+               CASE_NEAREST(I,int)
+               CASE_NEAREST(B,signed char)
+               CASE_NEAREST(UB,unsigned char)
+
+               case ( TYPE_L ): break;
+               case ( TYPE_K ): break;
+               case ( TYPE_S ): break;
+               case ( TYPE_UL ): break;
+               case ( TYPE_UI ): break;
+               case ( TYPE_UK ): break;
+               case ( TYPE_US ): break;
+            }
+            break;
+
+/* Undefine the macro. */
+#undef CASE_NEAREST
+
+/* Linear spreading. */
+/* ----------------- */
+/* Note this is also the default if zero is given. */
+         case AST__LINEAR:
+         case 0:
+
+/* Define a macro to use a "case" statement to invoke the linear
+   spreading function appropriate to a given data type. */
+#define CASE_LINEAR(X,Xtype) \
+               case ( TYPE_##X ): \
+                  SpreadLinear##X( ndim_out, lbnd_out, ubnd_out,\
+                                   (Xtype *) in, (Xtype *) in_var, \
+                                   infac, npoint, offset, \
+                                   (const double *const *) ptr_out, \
+                                   flags, *( (Xtype *) badval_ptr ), \
+                                   npix_out, (Xtype *) out, \
+                                   (Xtype *) out_var, work, nused, status ); \
+                  break;
+
+/* Use the above macro to invoke the appropriate function. */
+            switch ( type ) {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+               CASE_LINEAR(LD,long double)
+#endif
+               CASE_LINEAR(D,double)
+               CASE_LINEAR(F,float)
+               CASE_LINEAR(I,int)
+               CASE_LINEAR(B,signed char)
+               CASE_LINEAR(UB,unsigned char)
+
+               case ( TYPE_L ): break;
+               case ( TYPE_K ): break;
+               case ( TYPE_S ): break;
+               case ( TYPE_UL ): break;
+               case ( TYPE_UI ): break;
+               case ( TYPE_UK ): break;
+               case ( TYPE_US ): break;
+            }
+            break;
+
+/* Undefine the macro. */
+#undef CASE_LINEAR
+
+/* Spreading using a 1-d kernel. */
+/* ----------------------------- */
+         case AST__SINC:
+         case AST__SINCCOS:
+         case AST__SINCGAUSS:
+         case AST__GAUSS:
+         case AST__SINCSINC:
+         case AST__SOMB:
+         case AST__SOMBCOS:
+
+/* Obtain a pointer to the appropriate 1-d kernel function (either
+   internal or user-defined) and set up any parameters it may
+   require. */
+            par = NULL;
+            switch ( spread ) {
+
+/* sinc(pi*x) */
+/* ---------- */
+/* Assign the kernel function. */
+               case AST__SINC:
+                  kernel = Sinc;
+
+/* Calculate the number of neighbouring pixels to use. */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) {
+                     neighb = 2;
+                  } else {
+                     neighb = MaxI( 1, neighb, status );
+                  }
+                  break;
+
+/* somb(pi*x) */
+/* ---------- */
+/* Assign the kernel function. */
+               case AST__SOMB:
+                  kernel = Somb;
+
+/* Calculate the number of neighbouring pixels to use. */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) {
+                     neighb = 2;
+                  } else {
+                     neighb = MaxI( 1, neighb, status );
+                  }
+                  break;
+
+/* sinc(pi*x)*cos(k*pi*x) */
+/* ---------------------- */
+/* Assign the kernel function. */
+               case AST__SINCCOS:
+                  kernel = SincCos;
+
+/* Store the required value of "k" in a local parameter array and pass
+   this array to the kernel function. */
+                  lpar[ 0 ] = 0.5 / MaxD( 1.0, params[ 1 ], status );
+                  par = lpar;
+
+/* Obtain the number of neighbouring pixels to use. If this is zero or
+   less, the number will be calculated automatically below. */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) neighb = INT_MAX;
+
+/* Calculate the maximum number of neighbouring pixels required by the
+   width of the kernel, and use this value if preferable. */
+                  neighb = MinI( neighb,
+                                 (int) ceil( MaxD( 1.0, params[ 1 ], status ) ), status );
+                  break;
+
+/* sinc(pi*x)*exp(-k*x*x) */
+/* ---------------------- */
+/* Assign the kernel function. */
+               case AST__SINCGAUSS:
+                  kernel = SincGauss;
+
+/* Constrain the full width half maximum of the gaussian factor. */
+                  fwhm = MaxD( 0.1, params[ 1 ], status );
+
+/* Store the required value of "k" in a local parameter array and pass
+   this array to the kernel function. */
+                  lpar[ 0 ] = 4.0 * log( 2.0 ) / ( fwhm * fwhm );
+                  par = lpar;
+
+/* Obtain the number of neighbouring pixels to use. If this is zero or
+   less, use the number of neighbouring pixels required by the width
+   of the kernel (out to where the gaussian term falls to 1% of its
+   peak value). */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) neighb = (int) ceil( sqrt( -log( 0.01 ) /
+                                                                lpar[ 0 ] ) );
+                  break;
+
+/* exp(-k*x*x) */
+/* ----------- */
+/* Assign the kernel function. */
+               case AST__GAUSS:
+                  kernel = Gauss;
+
+/* Constrain the full width half maximum of the gaussian. */
+                  fwhm = MaxD( 0.1, params[ 1 ], status );
+
+/* Store the required value of "k" in a local parameter array and pass
+   this array to the kernel function. */
+                  lpar[ 0 ] = 4.0 * log( 2.0 ) / ( fwhm * fwhm );
+                  par = lpar;
+
+/* Obtain the number of neighbouring pixels to use. If this is zero or
+   less, use the number of neighbouring pixels required by the width
+   of the kernel (out to where the gaussian term falls to 1% of its
+   peak value). */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) neighb = (int) ceil( sqrt( -log( 0.01 ) /
+                                                                lpar[ 0 ] ) );
+                  break;
+
+/* somb(pi*x)*cos(k*pi*x) */
+/* ---------------------- */
+/* Assign the kernel function. */
+               case AST__SOMBCOS:
+                  kernel = SombCos;
+
+/* Store the required value of "k" in a local parameter array and pass
+   this array to the kernel function. */
+                  lpar[ 0 ] = 0.5 / MaxD( 1.0, params[ 1 ], status );
+                  par = lpar;
+
+/* Obtain the number of neighbouring pixels to use. If this is zero or
+   less, the number will be calculated automatically below. */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) neighb = INT_MAX;
+
+/* Calculate the maximum number of neighbouring pixels required by the
+   width of the kernel, and use this value if preferable. */
+                  neighb = MinI( neighb,
+                                 (int) ceil( MaxD( 1.0, params[ 1 ], status ) ), status );
+                  break;
+
+/* sinc(pi*x)*sinc(k*pi*x) */
+/* ----------------------- */
+/* Assign the kernel function. */
+               case AST__SINCSINC:
+                  kernel = SincSinc;
+
+/* Store the required value of "k" in a local parameter array and pass
+   this array to the kernel function. */
+                  lpar[ 0 ] = 0.5 / MaxD( 1.0, params[ 1 ], status );
+                  par = lpar;
+
+/* Obtain the number of neighbouring pixels to use. If this is zero or
+   less, the number will be calculated automatically below. */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) neighb = INT_MAX;
+
+/* Calculate the maximum number of neighbouring pixels required by the
+   width of the kernel, and use this value if preferable. */
+                  neighb = MinI( neighb,
+                                 (int) ceil( MaxD( 1.0, params[ 1 ], status ) ), status );
+                  break;
+            }
+
+/* Define a macro to use a "case" statement to invoke the 1-d kernel
+   interpolation function appropriate to a given data type, passing it
+   the pointer to the kernel function obtained above. */
+#define CASE_KERNEL1(X,Xtype) \
+               case ( TYPE_##X ): \
+                  SpreadKernel1##X( this, ndim_out, lbnd_out, ubnd_out, \
+                                         (Xtype *) in, (Xtype *) in_var, \
+                                         infac, npoint, offset, \
+                                         (const double *const *) ptr_out, \
+                                         kernel, neighb, par, flags, \
+                                         *( (Xtype *) badval_ptr ), \
+                                         npix_out, (Xtype *) out, \
+                                         (Xtype *) out_var, work, nused, \
+                                         status ); \
+                  break;
+
+/* Use the above macro to invoke the appropriate function. */
+            switch ( type ) {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+               CASE_KERNEL1(LD,long double)
+#endif
+               CASE_KERNEL1(D,double)
+               CASE_KERNEL1(F,float)
+               CASE_KERNEL1(I,int)
+               CASE_KERNEL1(B,signed char)
+               CASE_KERNEL1(UB,unsigned char)
+
+               case ( TYPE_L ): break;
+               case ( TYPE_K ): break;
+               case ( TYPE_S ): break;
+               case ( TYPE_UL ): break;
+               case ( TYPE_UI ): break;
+               case ( TYPE_UK ): break;
+               case ( TYPE_US ): break;
+            }
+            break;
+
+/* Undefine the macro. */
+#undef CASE_KERNEL1
+
+/* Error: invalid pixel spreading scheme specified. */
+/* ------------------------------------------------ */
+         default:
+
+/* Define a macro to report an error message appropriate to a given
+   data type. */
+#define CASE_ERROR(X) \
+            case TYPE_##X: \
+               astError( AST__SISIN, "astRebin"#X"(%s): Invalid " \
+                         "pixel spreading scheme (%d) specified.", status, \
+                         astGetClass( unsimplified_mapping ), spread ); \
+               break;
+
+/* Use the above macro to report an appropriate error message. */
+            switch ( type ) {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+               CASE_ERROR(LD)
+#endif
+               CASE_ERROR(D)
+               CASE_ERROR(F)
+               CASE_ERROR(I)
+               CASE_ERROR(B)
+               CASE_ERROR(UB)
+
+               case ( TYPE_L ): break;
+               case ( TYPE_K ): break;
+               case ( TYPE_S ): break;
+               case ( TYPE_UL ): break;
+               case ( TYPE_UI ): break;
+               case ( TYPE_UK ): break;
+               case ( TYPE_US ): break;
+            }
+            break;
+
+/* Undefine the macro. */
+#undef CASE_ERROR
+      }
+   }
+
+/* Annul the PointSet used to hold output coordinates. */
+   pset_out = astAnnul( pset_out );
+
+/* Free the workspace. */
+   offset = astFree( offset );
+   stride = astFree( stride );
+}
+
+/*
+*++
+*  Name:
+c     astRebinSeq<X>
+f     AST_REBINSEQ<X>
+
+*  Purpose:
+*     Rebin a region of a sequence of data grids.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astRebinSeq<X>( AstMapping *this, double wlim, int ndim_in,
+c                          const int lbnd_in[], const int ubnd_in[],
+c                          const <Xtype> in[], const <Xtype> in_var[],
+c                          int spread, const double params[], int flags,
+c                          double tol, int maxpix, <Xtype> badval,
+c                          int ndim_out, const int lbnd_out[],
+c                          const int ubnd_out[], const int lbnd[],
+c                          const int ubnd[], <Xtype> out[], <Xtype> out_var[],
+c                          double weights[], int64_t *nused );
+f     CALL AST_REBINSEQ<X>( THIS, WLIM, NDIM_IN, LBND_IN, UBND_IN, IN, IN_VAR,
+f                           SPREAD, PARAMS, FLAGS, TOL, MAXPIX, BADVAL,
+f                           NDIM_OUT, LBND_OUT, UBND_OUT, LBND, UBND, OUT,
+f                           OUT_VAR, WEIGHTS, NUSED, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This set of
+c     functions is identical to astRebin<X>
+f     routines is identical to AST_REBIN<X>
+*     except that the rebinned input data is added into the supplied
+*     output arrays, rather than simply over-writing the contents of the
+*     output arrays. Thus, by calling this
+c     function
+f     routine
+*     repeatedly, a sequence of input arrays can be rebinned and accumulated
+*     into a single output array, effectively forming a mosaic of the
+*     input data arrays.
+*
+*     In addition, the weights associated with each output pixel are
+*     returned. The weight of an output pixel indicates the number of input
+*     pixels which have been accumulated in that output pixel. If the entire
+*     value of an input pixel is assigned to a single output pixel, then the
+*     weight of that output pixel is incremented by one. If some fraction of
+*     the value of an input pixel is assigned to an output pixel, then the
+*     weight of that output pixel is incremented by the fraction used.
+*
+*     The start of a new sequence is indicated by specifying the
+*     AST__REBININIT flag via the
+c     "flags" parameter.
+f     FLAGS argument.
+*     This causes the supplied arrays to be filled with zeros before the
+*     rebinned input data is added into them. Subsequenct invocations
+*     within the same sequence should omit the AST__REBININIT flag.
+*
+*     The last call in a sequence is indicated by specifying the
+*     AST__REBINEND flag. Depending on which flags are supplied, this may
+*     cause the output data and variance arrays to be normalised before
+*     being returned. This normalisation consists of dividing the data
+*     array by the weights array, and can eliminate artifacts which may be
+*     introduced into the rebinned data as a consequence of aliasing
+*     between the input and output grids. This results in each output
+*     pixel value being the weighted mean of the input pixel values that
+*     fall in the neighbourhood of the output pixel (rather like
+c     astResample<X>).
+f     AST_RESAMPLE<X>).
+*     Optionally, these normalised
+*     values can then be multiplied by a scaling factor to ensure that the
+*     total data sum in any small area is unchanged. This scaling factor
+*     is equivalent to the number of input pixel values that fall into each
+*     output pixel. In addition to
+*     normalisation of the output data values, any output variances are
+*     also appropriately normalised, and any output data values with
+*     weight less than
+c     "wlim" are set to "badval".
+f     WLIM are set to BADVAL.
+*
+*     Output variances can be generated in two ways; by rebinning the supplied
+*     input variances with appropriate weights, or by finding the spread of
+*     input data values contributing to each output pixel (see the AST__GENVAR
+*     and AST__USEVAR flags).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to a Mapping, whose forward transformation will be
+*        used to transform the coordinates of pixels in the input
+*        grid into the coordinate system of the output grid.
+*
+*        The number of input coordinates used by this Mapping (as
+*        given by its Nin attribute) should match the number of input
+c        grid dimensions given by the value of "ndim_in"
+f        grid dimensions given by the value of NDIM_IN
+*        below. Similarly, the number of output coordinates (Nout
+*        attribute) should match the number of output grid dimensions
+c        given by "ndim_out".
+f        given by NDIM_OUT.
+c        If "in" is NULL, the Mapping will not be used, but a valid
+c        Mapping must still be supplied.
+c     wlim
+f     WLIM = DOUBLE PRECISION (Given)
+*        This value is only used if the AST__REBINEND flag is specified
+*        via the
+c        "flags" parameter.
+f        FLAGS argument.
+*        It gives the required number of input pixel values which must
+*        contribute to an output pixel (i.e. the output pixel weight) in
+*        order for the output pixel value to be considered valid. If the sum
+*        of the input pixel weights contributing to an output pixel is less
+*        than the supplied
+c        "wlim"
+f        WLIM
+*        value, then the output pixel value is returned set to the
+*        supplied bad value. If the supplied value is less than 1.0E-10
+*        then 1.0E-10 is used instead.
+c     ndim_in
+f     NDIM_IN = INTEGER (Given)
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+c        Not used if "in" is NULL.
+c     lbnd_in
+f     LBND_IN( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the centre of the first pixel
+*        in the input grid along each dimension.
+c        Not used if "in" is NULL.
+c     ubnd_in
+f     UBND_IN( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the centre of the last pixel in
+*        the input grid along each dimension.
+*
+c        Note that "lbnd_in" and "ubnd_in" together define the shape
+f        Note that LBND_IN and UBND_IN together define the shape
+*        and size of the input grid, its extent along a particular
+c        (j'th) dimension being ubnd_in[j]-lbnd_in[j]+1 (assuming the
+c        index "j" to be zero-based). They also define
+f        (J'th) dimension being UBND_IN(J)-LBND_IN(J)+1. They also define
+*        the input grid's coordinate system, each pixel having unit
+*        extent along each dimension with integral coordinate values
+*        at its centre.
+c        Not used if "in" is NULL.
+c     in
+f     IN( * ) = <Xtype> (Given)
+c        Pointer to an array, with one element for each pixel in the
+f        An array, with one element for each pixel in the
+*        input grid, containing the input data to be rebined.  The
+*        numerical type of this array should match the 1- or
+*        2-character type code appended to the function name (e.g. if
+c        you are using astRebinSeqF, the type of each array element
+c        should be "float").
+f        you are using AST_REBINSEQR, the type of each array element
+f        should be REAL).
+*
+*        The storage order of data within this array should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+c        (i.e. Fortran array indexing is used).
+f        (i.e. normal Fortran array storage order).
+c        If a NULL pointer is supplied for "in", then no data is added to
+c        the output arrays, but any initialisation or normalisation
+c        requested by "flags" is still performed.
+c     in_var
+f     IN_VAR( * ) = <Xtype> (Given)
+*        An optional
+c        pointer to a
+*        second array with the same size and type as the
+c        "in"
+f        IN
+*        array. If given, this should contain a set of non-negative values
+*        which represent estimates of the statistical variance associated
+*        with each element of the
+c        "in"
+f        IN
+*        array.
+*        If neither the AST__USEVAR nor the AST__VARWGT flag is set, no
+*        input variance estimates are required and this
+f        array
+c        pointer
+*        will not be used.
+f        A dummy (e.g. one-element) array
+c        A NULL pointer
+*        may then be supplied.
+c     spread
+f     SPREAD = INTEGER (Given)
+c        This parameter specifies the scheme to be used for dividing
+f        This argument specifies the scheme to be used for dividing
+*        each input data value up amongst the corresponding output pixels.
+*        It may be used to select
+*        from a set of pre-defined schemes by supplying one of the
+*        values described in the "Pixel Spreading Schemes"
+*        section in the description of the
+c        astRebin<X> functions.
+f        AST_REBIN<X> routines.
+*        If a value of zero is supplied, then the default linear spreading
+*        scheme is used (equivalent to supplying the value AST__LINEAR).
+c        Not used if "in" is NULL.
+c     params
+f     PARAMS( * ) = DOUBLE PRECISION (Given)
+c        An optional pointer to an array of double which should contain
+f        An optional array which should contain
+*        any additional parameter values required by the pixel
+*        spreading scheme. If such parameters are required, this
+*        will be noted in the "Pixel Spreading Schemes" section in the
+*        description of the
+c        astRebin<X> functions.
+f        AST_REBIN<X> routines.
+*
+c        If no additional parameters are required, this array is not
+c        used and a NULL pointer may be given.
+f        If no additional parameters are required, this array is not
+f        used. A dummy (e.g. one-element) array may then be supplied.
+c        Not used if "in" is NULL.
+c     flags
+f     FLAGS = INTEGER (Given)
+c        The bitwise OR of a set of flag values which may be used to
+f        The sum of a set of flag values which may be used to
+*        provide additional control over the rebinning operation. See
+*        the "Control Flags" section below for a description of the
+*        options available.  If no flag values are to be set, a value
+*        of zero should be given.
+c     tol
+f     TOL = DOUBLE PRECISION (Given)
+*        The maximum tolerable geometrical distortion which may be
+*        introduced as a result of approximating non-linear Mappings
+*        by a set of piece-wise linear transformations. This should be
+*        expressed as a displacement in pixels in the output grid's
+*        coordinate system.
+*
+*        If piece-wise linear approximation is not required, a value
+*        of zero may be given. This will ensure that the Mapping is
+*        used without any approximation, but may increase execution
+*        time.
+*
+*        If the value is too high, discontinuities between the linear
+*        approximations used in adjacent panel will be higher, and may
+*        cause the edges of the panel to be visible when viewing the output
+*        image at high contrast. If this is a problem, reduce the
+*        tolerance value used.
+c        Not used if "in" is NULL.
+c     maxpix
+f     MAXPIX = INTEGER (Given)
+*        A value which specifies an initial scale size (in pixels) for
+*        the adaptive algorithm which approximates non-linear Mappings
+*        with piece-wise linear transformations. Normally, this should
+*        be a large value (larger than any dimension of the region of
+*        the input grid being used). In this case, a first attempt to
+*        approximate the Mapping by a linear transformation will be
+*        made over the entire input region.
+*
+*        If a smaller value is used, the input region will first be
+c        divided into sub-regions whose size does not exceed "maxpix"
+f        divided into sub-regions whose size does not exceed MAXPIX
+*        pixels in any dimension. Only at this point will attempts at
+*        approximation commence.
+*
+*        This value may occasionally be useful in preventing false
+*        convergence of the adaptive algorithm in cases where the
+*        Mapping appears approximately linear on large scales, but has
+*        irregularities (e.g. holes) on smaller scales. A value of,
+*        say, 50 to 100 pixels can also be employed as a safeguard in
+*        general-purpose software, since the effect on performance is
+*        minimal.
+*
+*        If too small a value is given, it will have the effect of
+*        inhibiting linear approximation altogether (equivalent to
+c        setting "tol" to zero). Although this may degrade
+f        setting TOL to zero). Although this may degrade
+*        performance, accurate results will still be obtained.
+c        Not used if "in" is NULL.
+c     badval
+f     BADVAL = <Xtype> (Given)
+*        This argument should have the same type as the elements of
+c        the "in" array. It specifies the value used to flag missing
+f        the IN array. It specifies the value used to flag missing
+*        data (bad pixels) in the input and output arrays.
+*
+c        If the AST__USEBAD flag is set via the "flags" parameter,
+f        If the AST__USEBAD flag is set via the FLAGS argument,
+c        then this value is used to test for bad pixels in the "in"
+c        (and "in_var") array(s).
+f        then this value is used to test for bad pixels in the IN
+f        (and IN_VAR) array(s).
+*
+*        In all cases, this value is also used to flag any output
+c        elements in the "out" (and "out_var") array(s) for which
+f        elements in the OUT (and OUT_VAR) array(s) for which
+*        rebined values could not be obtained (see the "Propagation
+*        of Missing Data" section below for details of the
+*        circumstances under which this may occur).
+c     ndim_out
+f     NDIM_OUT = INTEGER (Given)
+*        The number of dimensions in the output grid. This should be
+*        at least one. It need not necessarily be equal to the number
+*        of dimensions in the input grid.
+c     lbnd_out
+f     LBND_OUT( NDIM_OUT ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_out" elements,
+f        An array
+*        containing the coordinates of the centre of the first pixel
+*        in the output grid along each dimension.
+c     ubnd_out
+f     UBND_OUT( NDIM_OUT ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_out" elements,
+f        An array
+*        containing the coordinates of the centre of the last pixel in
+*        the output grid along each dimension.
+*
+c        Note that "lbnd_out" and "ubnd_out" together define the
+f        Note that LBND_OUT and UBND_OUT together define the
+*        shape, size and coordinate system of the output grid in the
+c        same way as "lbnd_in" and "ubnd_in" define the shape, size
+f        same way as LBND_IN and UBND_IN define the shape, size
+*        and coordinate system of the input grid.
+c     lbnd
+f     LBND( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the first pixel in the region
+*        of the input grid which is to be included in the rebined output
+*        array.
+c        Not used if "in" is NULL.
+c     ubnd
+f     UBND( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the last pixel in the region of
+*        the input grid which is to be included in the rebined output
+*        array.
+*
+c        Note that "lbnd" and "ubnd" together define the shape and
+f        Note that LBND and UBND together define the shape and
+*        position of a (hyper-)rectangular region of the input grid
+*        which is to be included in the rebined output array. This region
+*        should lie wholly within the extent of the input grid (as
+c        defined by the "lbnd_in" and "ubnd_in" arrays). Regions of
+f        defined by the LBND_IN and UBND_IN arrays). Regions of
+*        the input grid lying outside this region will not be used.
+c        Not used if "in" is NULL.
+c     out
+f     OUT( * ) = <Xtype> (Given and Returned)
+c        Pointer to an array, with one element for each pixel in the
+f        An array, with one element for each pixel in the
+*        output grid. The rebined data values will be added into the
+*        original contents of this array. The numerical type of this array
+*        should match that of the
+c        "in" array, and the data storage order should be such
+f        IN array, and the data storage order should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+c        (i.e. Fortran array indexing is used).
+f        (i.e. normal Fortran array storage order).
+c     out_var
+f     OUT_VAR( * ) = <Xtype> (Given and Returned)
+*        A
+c        pointer to an
+*        array with the same type and size as the
+c        "out"
+f        OUT
+*        array. This
+c        pointer
+f        array
+*        will only be used if the AST__USEVAR or AST__GENVAR flag is set
+f        via the FLAGS argument,
+f        via the "flags" parameter,
+*        in which case variance estimates for the rebined data values will
+*        be added into the array. If neither the AST__USEVAR flag nor the
+*        AST__GENVAR flag is set, no output variance estimates will be
+*        calculated and this
+c        pointer
+f        array
+*        will not be used. A
+c        NULL pointer
+f        dummy (e.g. one-element) array
+*        may then be supplied.
+c     weights
+f     WEIGHTS( * ) = DOUBLE PRECISION (Given and Returned)
+c        Pointer to an array of double,
+f        An array
+*        with one or two elements for each pixel in the output grid,
+*        depending on whether or not the AST__GENVAR flag has been supplied
+*        via the
+c        "flags" parameter.
+f        FLAGS parameter.
+*        If AST__GENVAR has not been specified then the array should have
+*        one element for each output pixel, and it will be used to
+*        accumulate the weight associated with each output pixel.
+*        If AST__GENVAR has been specified then the array should have
+*        two elements for each output pixel. The first half of the array
+*        is again used to accumulate the weight associated with each output
+*        pixel, and the second half is used to accumulate the square of
+*        the weights. In each half, the data storage order should be such that
+*        the index of the first grid dimension varies most rapidly and that of
+*        the final dimension least rapidly
+c        (i.e. Fortran array indexing is used).
+f        (i.e. normal Fortran array storage order).
+c     nused
+f     NUSED = INTEGER*8 (Given and Returned)
+c        A pointer to an int64_t containing the
+f        The
+*        number of input data values that have been added into the output
+*        array so far. The supplied value is incremented on exit by the
+*        number of input values used. The value is initially set to zero
+*        if the AST__REBININIT flag is set in
+c        "flags".
+f        FLAGS.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Data Type Codes:
+*     To select the appropriate rebinning function, you should
+c     replace <X> in the generic function name astRebinSeq<X> with a
+f     replace <X> in the generic function name AST_REBINSEQ<X> with a
+*     1- or 2-character data type code, so as to match the numerical
+*     type <Xtype> of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - I: int
+c     - B: byte (signed char)
+c     - UB: unsigned byte (unsigned char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - B: BYTE (treated as signed)
+f     - UB: BYTE (treated as unsigned)
+*
+c     For example, astRebinSeqD would be used to process "double"
+c     data, while astRebinSeqI would be used to process "int"
+c     data, etc.
+f     For example, AST_REBIND would be used to process DOUBLE
+f     PRECISION data, while AST_REBINI would be used to process
+f     integer data (stored in an INTEGER array), etc.
+*
+*     Note that, unlike
+c     astResample<X>, the astRebinSeq<X>
+f     AST_RESAMPLE<X>, the AST_REBINSEQ<X>
+*     set of functions does not yet support unsigned integer data types
+*     or integers of different sizes.
+
+*  Control Flags:
+c     The following flags are defined in the "ast.h" header file and
+f     The following flags are defined in the AST_PAR include file and
+*     may be used to provide additional control over the rebinning
+*     process. Having selected a set of flags, you should supply the
+c     bitwise OR of their values via the "flags" parameter:
+f     sum of their values via the FLAGS argument:
+*
+*     - AST__REBININIT: Used to mark the first call in a sequence. It indicates
+*     that the supplied
+c     "out", "out_var" and "weights"
+f     OUT, OUT_VAR and WEIGHTS
+*     arrays should be filled with zeros (thus over-writing any supplied
+*     values) before adding the rebinned input data into them. This flag
+*     should be used when rebinning the first input array in a sequence.
+*     - AST__REBINEND: Used to mark the last call in a sequence. It causes
+*     each value in the
+c     "out" and "out_var"
+f     OUT and OUT_VAR
+*     arrays to be divided by a normalisation factor before being
+*     returned. The normalisation factor for each output data value is just
+*     the corresponding value from the weights array. The normalisation
+*     factor for each output variance value is the square of the data value
+*     normalisation factor (see also AST__CONSERVEFLUX). It also causes
+*     output data values to be set bad if the corresponding weight is less
+*     than the value supplied for
+c     parameter "wlim".
+f     argument WLIM.
+*     It also causes any temporary values stored in the output variance array
+*     (see flag AST__GENVAR below) to be converted into usable variance values.
+*     Note, this flag is ignored if the AST__NONORM flag is set.
+*     - AST__USEBAD: Indicates that there may be bad pixels in the
+*     input array(s) which must be recognised by comparing with the
+c     value given for "badval" and propagated to the output array(s).
+f     value given for BADVAL and propagated to the output array(s).
+*     If this flag is not set, all input values are treated literally
+c     and the "badval" value is only used for flagging output array
+f     and the BADVAL value is only used for flagging output array
+*     values.
+*     - AST__USEVAR: Indicates that output variance estimates should be
+*     created by rebinning the supplied input variance estimates. An
+*     error will be reported if both this flag and the AST__GENVAR flag
+*     are supplied.
+*     - AST__GENVAR: Indicates that output variance estimates should be
+*     created based on the spread of input data values contributing to each
+*     output pixel. An error will be reported if both this flag and the
+*     AST__USEVAR flag are supplied. If the AST__GENVAR flag is specified,
+*     the supplied output variance array is first used as a work array to
+*     accumulate the temporary values needed to generate the output
+*     variances. When the sequence ends (as indicated by the
+*     AST__REBINEND flag), the contents of the output variance array are
+*     converted into the required variance estimates. If the generation of
+*     such output variances is required, this flag should be used on every
+*     invocation of this
+c     function
+f     routine
+*     within a sequence, and any supplied input variances will have no effect
+*     on the output variances (although input variances will still be used
+*     to weight the input data if the AST__VARWGT flag is also supplied).
+*     The statistical meaning of these output varianes is determined by
+*     the presence or absence of the AST__DISVAR flag (see below).
+*     - AST__DISVAR: This flag is ignored unless the AST__GENVAR flag
+*     has also been specified. It determines the statistical meaning of
+*     the generated output variances. If AST__DISVAR is not specified,
+*     generated variances represent variances on the output mean  values. If
+*     AST__DISVAR is specified, the generated variances represent the variance
+*     of the distribution from which the input values were taken. Each output
+*     variance created with AST__DISVAR will be larger than that created
+*     without AST__DISVAR by a factor equal to the number of input samples
+*     that contribute to the output sample.
+*     - AST__VARWGT: Indicates that the input data should be weighted by
+*     the reciprocal of the input variances. Otherwise, all input data are
+*     given equal weight. If this flag is specified, the calculation of the
+*     output variances (if any) is modified to take account of the
+*     varying weights assigned to the input data values.
+*     - AST__NONORM: If the simple unnormalised sum of all input data falling
+*     in each output pixel is required, then this flag should be set on
+*     each call in the sequence and the AST__REBINEND should not be used
+*     on the last call. In this case
+c     NULL pointers can be supplied for "weights" and "nused".
+f     WEIGHTS and NUSED are ignored.
+*     This flag cannot be used with the AST__CONSERVEFLUX, AST__GENVAR
+*     or AST__VARWGT flag.
+*     - AST__CONSERVEFLUX: Indicates that the normalized output pixel values
+*     generated by the AST__REBINEND flag should be scaled in such a way as
+*     to preserve the total data value in a feature on the sky. Without this
+*     flag, each normalised output pixel value represents a weighted mean
+*     of the input data values around the corresponding input position.
+f     (i.e. AST_REBINSEQ<F> behaves similarly to AST_RESAMPLE<X>). This
+f     (i.e. AST_REBINSEQ<F> behaves similarly to AST_RESAMPLE<X>). This
+*     is appropriate if the input data represents the spatial density of
+*     some quantity (e.g. surface brightness in Janskys per square
+*     arc-second) because the output pixel values will have the same
+*     normalisation and units as the input pixel values. However, if the
+*     input data values represent flux (or some other physical quantity)
+*     per pixel, then the AST__CONSERVEFLUX flag could be of use. It causes
+*     each output pixel value to be scaled by the ratio of the output pixel
+*     size to the input pixel size.
+*
+*     This flag can only be used if the Mapping is successfully approximated
+*     by one or more linear transformations. Thus an error will be reported
+*     if it used when the
+c     "tol" parameter
+f     TOL argument
+*     is set to zero (which stops the use of linear approximations), or
+*     if the Mapping is too non-linear to be approximated by a piece-wise
+*     linear transformation. The ratio of output to input pixel size is
+*     evaluated once for each panel of the piece-wise linear approximation to
+*     the Mapping, and is assumed to be constant for all output pixels in the
+*     panel. The scaling factors for adjacent panels will in general
+*     differ slightly, and so the joints between panels may be visible when
+*     viewing the output image at high contrast. If this is a problem,
+*     reduce the value of the
+c     "tol" parameter
+f     TOL argument
+*     until the difference between adjacent panels is sufficiently small
+*     to be insignificant.
+*
+*     This flag should normally be supplied on each invocation of
+c     astRebinSeq<X>
+f     AST_REBINSEQ<X>
+*     within a given sequence.
+*
+*     Note, this flag cannot be used in conjunction with the AST__NOSCALE
+*     flag (an error will be reported if both flags are specified).
+
+*  Propagation of Missing Data:
+*     Instances of missing data (bad pixels) in the output grid are
+c     identified by occurrences of the "badval" value in the "out"
+f     identified by occurrences of the BADVAL value in the OUT
+*     array. These are only produced if the AST__REBINEND flag is
+*     specified and a pixel has zero weight.
+*
+*     An input pixel is considered bad (and is consequently ignored) if
+*     its
+c     data value is equal to "badval" and the AST__USEBAD flag is
+c     set via the "flags" parameter.
+f     data value is equal to BADVAL and the AST__USEBAD flag is
+f     set via the FLAGS argument.
+*
+*     In addition, associated output variance estimates (if
+c     calculated) may be declared bad and flagged with the "badval"
+c     value in the "out_var" array for similar reasons.
+f     calculated) may be declared bad and flagged with the BADVAL
+f     value in the OUT_VAR array for similar reasons.
+
+*--
+*/
+/* Define a macro to implement the function for a specific data
+   type. */
+#define MAKE_REBINSEQ(X,Xtype,IntType) \
+static void RebinSeq##X( AstMapping *this, double wlim, int ndim_in, \
+                     const int lbnd_in[], const int ubnd_in[], \
+                     const Xtype in[], const Xtype in_var[], \
+                     int spread, const double params[], int flags, \
+                     double tol, int maxpix, Xtype badval, \
+                     int ndim_out, const int lbnd_out[], \
+                     const int ubnd_out[], const int lbnd[], \
+                     const int ubnd[], Xtype out[], Xtype out_var[], \
+                     double weights[], int64_t *nused, int *status ) { \
+\
+/* Local Variables: */ \
+   AstMapping *simple;           /* Pointer to simplified Mapping */ \
+   Xtype *d;                     /* Pointer to next output data value */ \
+   Xtype *v;                     /* Pointer to next output variance value */ \
+   astDECLARE_GLOBALS            /* Thread-specific data */ \
+   double *w;                    /* Pointer to next weight value */ \
+   double mwpip;                 /* Mean weight per input pixel */ \
+   double neff;                  /* Effective number of contributing input pixels */ \
+   double sw;                    /* Sum of weights at output pixel */ \
+   double wgt;                   /* Output pixel weight */ \
+   int i;                        /* Loop counter for output pixels */ \
+   int idim;                     /* Loop counter for coordinate dimensions */ \
+   int ipix_out;                 /* Index into output array */ \
+   int nin;                      /* Number of Mapping input coordinates */ \
+   int nout;                     /* Number of Mapping output coordinates */ \
+   int npix;                     /* Number of pixels in input region */ \
+   int npix_out;                 /* Number of pixels in output array */ \
+   int64_t mpix;                 /* Number of pixels for testing */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Get a pointer to a structure holding thread-specific global data values */ \
+   astGET_GLOBALS(this); \
+\
+/* Loop to determine how many pixels the output array contains. */ \
+   npix_out = 1; \
+   for ( idim = 0; idim < ndim_out; idim++ ) { \
+      npix_out *= ubnd_out[ idim ] - lbnd_out[ idim ] + 1; \
+   } \
+\
+/* Obtain values for the Nin and Nout attributes of the Mapping. */ \
+   nin = astGetNin( this ); \
+   nout = astGetNout( this ); \
+\
+/* If OK, also check that the number of output grid dimensions matches \
+   the number required by the Mapping and is at least 1. Report an \
+   error if necessary. */ \
+   if ( astOK && ( ( ndim_out != nout ) || ( ndim_out < 1 ) ) ) { \
+      astError( AST__NGDIN, "astRebinSeq"#X"(%s): Bad number of output grid " \
+                "dimensions (%d).", status, astGetClass( this ), ndim_out ); \
+      if ( ndim_out != nout ) { \
+         astError( AST__NGDIN, "The %s given generates %s%d coordinate " \
+                   "value%s for each output position.", status, astGetClass( this ), \
+                   ( nout < ndim_out ) ? "only " : "", nout, \
+                   ( nout == 1 ) ? "" : "s" ); \
+      } \
+   } \
+\
+/* If no input data was supplied, jump to the normalisation section. */ \
+   simple = NULL; \
+   if( in ) { \
+\
+/* If OK, check that the number of input grid dimensions matches the \
+   number required by the Mapping and is at least 1. Report an error \
+   if necessary. */ \
+      if ( astOK && ( ( ndim_in != nin ) || ( ndim_in < 1 ) ) ) { \
+         astError( AST__NGDIN, "astRebinSeq"#X"(%s): Bad number of input grid " \
+                   "dimensions (%d).", status, astGetClass( this ), ndim_in ); \
+         if ( ndim_in != nin ) { \
+            astError( AST__NGDIN, "The %s given requires %d coordinate value%s " \
+                      "to specify an input position.", status, \
+                      astGetClass( this ), nin, ( nin == 1 ) ? "" : "s" ); \
+         } \
+      } \
+\
+/* Check that the lower and upper bounds of the input grid are \
+   consistent. Report an error if any pair is not. */ \
+      mpix = 1; \
+      if ( astOK ) { \
+         for ( idim = 0; idim < ndim_in; idim++ ) { \
+            if ( lbnd_in[ idim ] > ubnd_in[ idim ] ) { \
+              astError( AST__GBDIN, "astRebinSeq"#X"(%s): Lower bound of " \
+                        "input grid (%d) exceeds corresponding upper bound " \
+                        "(%d).", status, astGetClass( this ), \
+                        lbnd_in[ idim ], ubnd_in[ idim ] ); \
+              astError( AST__GBDIN, "Error in input dimension %d.", status, \
+                        idim + 1 ); \
+              break; \
+            } else { \
+               mpix *= ubnd_in[ idim ] - lbnd_in[ idim ] + 1; \
+            } \
+         } \
+      } \
+\
+/* Report an error if there are too many pixels in the input. */ \
+      if ( astOK && (int) mpix != mpix ) { \
+         astError( AST__EXSPIX, "astRebinSeq"#X"(%s): Supplied input array " \
+                   "contains too many pixels (%g): must be fewer than %d.", \
+                   status, astGetClass( this ), (double) mpix, INT_MAX ); \
+      } \
+\
+/* Ensure any supplied "in_var" pointer is ignored if no input variances are \
+   needed. */ \
+      if( !( flags & AST__USEVAR ) && !( flags & AST__VARWGT ) ) { \
+         in_var = NULL; \
+      } \
+\
+/* Ensure any supplied "out_var" pointer is ignored if no output variances \
+   being created. */ \
+      if( !( flags & AST__USEVAR ) && !( flags & AST__GENVAR ) ) { \
+         out_var = NULL; \
+      } \
+\
+/* Check that the positional accuracy tolerance supplied is valid and \
+   report an error if necessary. */ \
+      if ( astOK && ( tol < 0.0 ) ) { \
+         astError( AST__PATIN, "astRebinSeq"#X"(%s): Invalid positional " \
+                   "accuracy tolerance (%.*g pixel).", status, \
+                   astGetClass( this ), DBL_DIG, tol ); \
+         astError( AST__PATIN, "This value should not be less than zero." , status); \
+      } \
+\
+/* Check that the initial scale size in pixels supplied is valid and \
+   report an error if necessary. */ \
+      if ( astOK && ( maxpix < 0 ) ) { \
+         astError( AST__SSPIN, "astRebinSeq"#X"(%s): Invalid initial scale " \
+                   "size in pixels (%d).", status, astGetClass( this ), maxpix ); \
+         astError( AST__SSPIN, "This value should not be less than zero." , status); \
+      } \
+\
+/* Check that the lower and upper bounds of the output grid are \
+   consistent. Report an error if any pair is not. */ \
+      mpix = 1; \
+      if ( astOK ) { \
+         for ( idim = 0; idim < ndim_out; idim++ ) { \
+            if ( lbnd_out[ idim ] > ubnd_out[ idim ] ) { \
+               astError( AST__GBDIN, "astRebinSeq"#X"(%s): Lower bound of " \
+                         "output grid (%d) exceeds corresponding upper bound " \
+                         "(%d).", status, astGetClass( this ), \
+                         lbnd_out[ idim ], ubnd_out[ idim ] ); \
+               astError( AST__GBDIN, "Error in output dimension %d.", status, \
+                         idim + 1 ); \
+               break; \
+            } else { \
+               mpix *= ubnd_out[ idim ] - lbnd_out[ idim ] + 1; \
+            } \
+         } \
+      } \
+\
+/* Report an error if there are too many pixels in the output. */ \
+   if ( astOK && (int) mpix != mpix ) { \
+      astError( AST__EXSPIX, "astRebinSeq"#X"(%s): Supplied output array " \
+                "contains too many pixels (%g): must be fewer than %d.", \
+                status, astGetClass( this ), (double) mpix, INT_MAX ); \
+   } \
+\
+/* Similarly check the bounds of the input region. */ \
+      mpix = 1; \
+      if ( astOK ) { \
+         for ( idim = 0; idim < ndim_in; idim++ ) { \
+            if ( lbnd[ idim ] > ubnd[ idim ] ) { \
+               astError( AST__GBDIN, "astRebinSeq"#X"(%s): Lower bound of " \
+                         "input region (%d) exceeds corresponding upper " \
+                         "bound (%d).", status, astGetClass( this ), \
+                         lbnd[ idim ], ubnd[ idim ] ); \
+\
+/* Also check that the input region lies wholly within the input \
+   grid. */ \
+            } else if ( lbnd[ idim ] < lbnd_in[ idim ] ) { \
+               astError( AST__GBDIN, "astRebinSeq"#X"(%s): Lower bound of " \
+                         "input region (%d) is less than corresponding " \
+                         "bound of input grid (%d).", status, astGetClass( this ), \
+                         lbnd[ idim ], lbnd_in[ idim ] ); \
+            } else if ( ubnd[ idim ] > ubnd_in[ idim ] ) { \
+               astError( AST__GBDIN, "astRebinSeq"#X"(%s): Upper bound of " \
+                         "input region (%d) exceeds corresponding " \
+                         "bound of input grid (%d).", status, astGetClass( this ), \
+                         ubnd[ idim ], ubnd_in[ idim ] ); \
+            } else { \
+               mpix *= ubnd[ idim ] - lbnd[ idim ] + 1; \
+            } \
+\
+/* Say which dimension produced the error. */ \
+            if ( !astOK ) { \
+               astError( AST__GBDIN, "Error in output dimension %d.", status, \
+                         idim + 1 ); \
+               break; \
+            } \
+         } \
+      } \
+\
+/* Report an error if there are too many pixels in the input region. */ \
+      if ( astOK && (int) mpix != mpix ) { \
+         astError( AST__EXSPIX, "astRebinSeq"#X"(%s): Supplied input region " \
+                   "contains too many pixels (%g): must be fewer than %d.", \
+                   status, astGetClass( this ), (double) mpix, INT_MAX ); \
+      } \
+\
+/* Check that only one of AST__USEVAR and ASR__GENVAR has been supplied. */ \
+      if( ( flags & AST__USEVAR ) && ( flags & AST__GENVAR ) ) { \
+         if( astOK ) { \
+            astError( AST__BDPAR, "astRebinSeq"#X"(%s): Incompatible flags " \
+                      "AST__GENVAR and AST__USEVAR have been specified " \
+                      "together (programming error).", status, astGetClass( this ) ); \
+         } \
+      } \
+\
+/* If AST__USEVAR or AST_VARWGT has been specified, check we have an \
+   input variance array. */ \
+      if( !in_var && astOK ) { \
+         if( ( flags & AST__USEVAR ) ) { \
+            astError( AST__BDPAR, "astRebinSeq"#X"(%s): The AST__USEVAR flag " \
+                      "was specified but no input variance array was supplied " \
+                      "(programming error).", status, astGetClass( this )  ); \
+         } else if( ( flags & AST__VARWGT ) ) { \
+            astError( AST__BDPAR, "astRebinSeq"#X"(%s): The AST__VARWGT flag " \
+                      "was specified but no input variance array was supplied " \
+                      "(programming error).", status, astGetClass( this )  ); \
+         } \
+      } \
+\
+/* If AST__USEVAR or AST_GENVAR has been specified, check we have an \
+   output variance array. */ \
+      if( !out_var && astOK ) { \
+         if( ( flags & AST__USEVAR ) ) { \
+            astError( AST__BDPAR, "astRebinSeq"#X"(%s): The AST__USEVAR flag " \
+                      "was specified but no output variance array was supplied " \
+                      "(programming error).", status, astGetClass( this )  ); \
+         } else if( ( flags & AST__GENVAR ) ) { \
+            astError( AST__BDPAR, "astRebinSeq"#X"(%s): The AST__GENVAR flag " \
+                      "was specified but no output variance array was supplied " \
+                      "(programming error).", status, astGetClass( this )  ); \
+         } \
+      } \
+\
+/* If the AST__NONORM flag has been supplied, check no incompatible flags have \
+   been specified. */ \
+      if( flags & AST__NONORM ) { \
+         if( ( flags & AST__GENVAR ) && astOK ) { \
+            astError( AST__BDPAR, "astRebinSeq"#X"(%s): Incompatible flags " \
+                      "AST__GENVAR and AST__NONORM have been specified " \
+                      "together (programming error).", status, astGetClass( this ) ); \
+         } else if( ( flags & AST__VARWGT ) && astOK ) { \
+            astError( AST__BDPAR, "astRebinSeq"#X"(%s): Incompatible flags " \
+                      "AST__VARWGT and AST__NONORM have been specified " \
+                      "together (programming error).", status, astGetClass( this ) ); \
+         } else if( ( flags & AST__CONSERVEFLUX ) && astOK ) { \
+            astError( AST__BDPAR, "astRebinSeq"#X"(%s): Incompatible flags " \
+                      "AST__CONSERVEFLUX and AST__NONORM have been specified " \
+                      "together (programming error).", status, astGetClass( this ) ); \
+         } \
+\
+/* If the AST__NONORM flag has not been supplied, check that a weights array \
+   and nused pointer have been supplied. */ \
+      } else if( !weights ){ \
+         astError( AST__BDPAR, "astRebinSeq"#X"(%s): No weights array " \
+                   "supplied (programming error).", status, \
+                   astGetClass( this )  ); \
+      } else if( !nused ){ \
+         astError( AST__BDPAR, "astRebinSeq"#X"(%s): No 'nused' pointer " \
+                   "supplied (programming error).", status, \
+                   astGetClass( this )  ); \
+      } \
+\
+/* If OK, loop to determine how many input pixels are to be binned. */ \
+      npix = 1; \
+      unsimplified_mapping = this; \
+      if ( astOK ) { \
+         for ( idim = 0; idim < ndim_in; idim++ ) { \
+            npix *= ubnd[ idim ] - lbnd[ idim ] + 1; \
+         } \
+\
+/* If there are sufficient pixels to make it worthwhile, simplify the \
+   Mapping supplied to improve performance. Otherwise, just clone the \
+   Mapping pointer. Note we have already saved a pointer to the original \
+   Mapping so that lower-level functions can use it if they need to report \
+   an error. */ \
+         if ( npix > 1024 ) { \
+            simple = astSimplify( this ); \
+         } else { \
+            simple = astClone( this ); \
+         } \
+      } \
+\
+/* Report an error if the forward transformation of this simplified \
+   Mapping is not defined. */ \
+      if ( !astGetTranForward( simple ) && astOK ) { \
+         astError( AST__TRNND, "astRebinSeq"#X"(%s): An forward coordinate " \
+                   "transformation is not defined by the %s supplied.", status, \
+                   astGetClass( unsimplified_mapping ), \
+                   astGetClass( unsimplified_mapping ) ); \
+      } \
+\
+/* If required, initialise the output arrays to hold zeros. */ \
+      if( flags & AST__REBININIT ) { \
+         d = out; \
+         if( out_var ) { \
+            v = out_var; \
+            for( ipix_out = 0; ipix_out < npix_out; ipix_out++, d++, v++ ) { \
+               *d = 0; \
+               *v = 0; \
+            } \
+         } else { \
+            for( ipix_out = 0; ipix_out < npix_out; ipix_out++, d++ ) { \
+               *d = 0; \
+            } \
+         } \
+         if( weights ) { \
+            w = weights; \
+            for( ipix_out = 0; ipix_out < npix_out; ipix_out++, w++ ) { \
+               *w = 0; \
+            } \
+            if( flags & AST__GENVAR ) { \
+               for( ipix_out = 0; ipix_out < npix_out; ipix_out++, w++ ) *w = 0; \
+            } \
+         } \
+         if( nused ) *nused = 0; \
+      } \
+\
+/* Paste the input values into the supplied output arrays. */ \
+      if( RebinAdaptively( simple, ndim_in, lbnd_in, ubnd_in, \
+                          (const void *) in, (const void *) in_var, \
+                          TYPE_##X, spread, params, flags, \
+                          tol, maxpix, (const void *) &badval, \
+                          ndim_out, lbnd_out, ubnd_out, lbnd, \
+                          ubnd, npix_out, (void *) out, \
+                          (void *) out_var, weights, nused, status ) ) { \
+         astError( AST__CNFLX, "astRebinSeq"#X"(%s): Flux conservation was " \
+                   "requested but could not be performed because the " \
+                   "forward transformation of the supplied Mapping " \
+                   "is too non-linear.", status, astGetClass( this ) ); \
+      } \
+\
+/* Annul the pointer to the simplified/cloned Mapping. */ \
+      simple = astAnnul( simple ); \
+\
+   } \
+\
+/* If required, finalise the sequence. */ \
+   if( ( flags & AST__REBINEND ) && !( flags & AST__NONORM ) && \
+       weights && nused ) { \
+\
+/* Ensure "wlim" is not zero. */ \
+      if( wlim < 1.0E-10 ) wlim = 1.0E-10; \
+\
+/* If it will be needed, find the average weight per input pixel. */ \
+      if( !( flags & AST__GENVAR ) && *nused > 0 ) { \
+         sw = 0.0; \
+         for( i = 0; i < npix_out; i++ ) { \
+            sw += weights[ i ]; \
+         } \
+         mwpip = sw/( *nused ); \
+       } else { \
+         mwpip = AST__BAD; \
+       } \
+\
+/* Normalise each output pixel. */ \
+      for( i = 0; i < npix_out; i++ ) { \
+\
+/* Find the effective number of input samples that contribute to the \
+   output sample. To do this properly requires the sum of the squared \
+   weights in each output pixel, but this is only available if AST__GENVAR \
+   flag is in use. In order to avoid changing the API for astRebinSeq, we \
+   honour this long-standing restriction, and use an approximation if \
+   AST__GENVAR is not in use. */ \
+         wgt = weights[ i ]; \
+         if( flags & AST__GENVAR ) { \
+            if( wgt > 0.0 && weights[ i + npix_out ] > 0 ) { \
+               neff = (wgt*wgt)/weights[ i + npix_out ]; \
+            } else { \
+               neff = 0.0; \
+            } \
+\
+/* If the sum of the squared weights is not available, compare the weight \
+   for this output pixel with the mean weight per input pixel. */ \
+         } else if( mwpip != AST__BAD ){ \
+            neff = wgt/mwpip; \
+\
+         } else if( astOK ) { \
+            astError( AST__BADIN, "astRebinSeq"#X"(%s): The overlap " \
+                      "between the %d-d input array and the %d-d output " \
+                      "array contains no pixels with good data %svalues.", \
+                      status, astGetClass( this ), nin, nout,  \
+                      in_var ? "and variance " : "" ); \
+         } \
+\
+/* Assign bad values to unused output pixels. */ \
+         if( neff < wlim || neff == 0.0 ) { \
+            out[ i ] = badval;  \
+            if( out_var ) out_var[ i ] = badval; \
+\
+/* Otherwise, normalise the returned data value. No need to check "wgt" \
+   since it must be larger than zero since neff is larger than wlim. */ \
+         } else { \
+            out[ i ] /= wgt; \
+\
+/* Normalise the returned variance: propagated from input variances... */ \
+            if( out_var ) { \
+               if( flags & AST__USEVAR ) { \
+                  out_var[ i ] /= wgt*wgt; \
+\
+/* Normalise the returned variance: from spread of input values... */ \
+               } else if( flags & AST__GENVAR && neff > 1.0 ) {  \
+                  out_var[ i ] /= wgt; \
+                  out_var[ i ] -= out[ i ]*out[ i ]; \
+                  if( out_var[ i ] < 0.0 ) out_var[ i ] = 0.0; \
+\
+/* If output variances are estimates of the variance of the distribution \
+   from which the input values were sampled... */ \
+                  if( flags & AST__DISVAR ) { \
+                     out_var[ i ] *= neff/( neff - 1.0 ); \
+\
+/* If output variances are estimates of the error on the mean data value... */ \
+                  } else { \
+                     out_var[ i ] *= 1.0/( neff - 1.0 ); \
+                  } \
+\
+               } else { \
+                  out_var[ i ] = badval; \
+               } \
+            } \
+         } \
+      } \
+   } \
+\
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+#if HAVE_LONG_DOUBLE  /* Not normally implemented */
+MAKE_REBINSEQ(LD,long double,0)
+#endif
+MAKE_REBINSEQ(D,double,0)
+MAKE_REBINSEQ(F,float,0)
+MAKE_REBINSEQ(I,int,1)
+MAKE_REBINSEQ(B,signed char,1)
+MAKE_REBINSEQ(UB,unsigned char,1)
+
+/* Undefine the macro. */
+#undef MAKE_REBINSEQ
+
+static int RebinWithBlocking( AstMapping *this, const double *linear_fit,
+                               int ndim_in, const int *lbnd_in,
+                               const int *ubnd_in, const void *in,
+                               const void *in_var, DataType type,
+                               int spread, const double *params, int flags,
+                               const void *badval_ptr, int ndim_out,
+                               const int *lbnd_out, const int *ubnd_out,
+                               const int *lbnd, const int *ubnd, int npix_out,
+                               void *out, void *out_var, double *work,
+                               int64_t *nused, int *status ) {
+/*
+*  Name:
+*     RebinWithBlocking
+
+*  Purpose:
+*     Rebin a section of a data grid in a memory-efficient way.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int RebinWithBlocking( AstMapping *this, const double *linear_fit,
+*                             int ndim_in, const int *lbnd_in,
+*                             const int *ubnd_in, const void *in,
+*                             const void *in_var, DataType type,
+*                             int spread, const double *params, int flags,
+*                             const void *badval_ptr, int ndim_out,
+*                             const int *lbnd_out, const int *ubnd_out,
+*                             const int *lbnd, const int *ubnd, int npix_out,
+*                             void *out, void *out_var, double *work,
+*                             int64_t *nused, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function rebins a specified section of a rectangular grid of
+*     data (with any number of dimensions) into another rectangular grid
+*     (with a possibly different number of dimensions). The coordinate
+*     transformation used to convert input pixel coordinates into positions
+*     in the output grid is given by the forward transformation of the
+*     Mapping which is supplied.  Any pixel spreading scheme may be specified
+*     for distributing the flux of an input pixel amongst the output
+*     pixels.
+*
+*     This function is very similar to RebinSection, except that in
+*     order to limit memory usage and to ensure locality of reference,
+*     it divides the input grid up into "blocks" which have a limited
+*     extent along each input dimension. Each block, which will not
+*     contain more than a pre-determined maximum number of pixels, is
+*     then passed to RebinSection for resampling.
+
+*  Parameters:
+*     this
+*        Pointer to a Mapping, whose forward transformation may be
+*        used to transform the coordinates of pixels in the input
+*        grid into associated positions in the output grid.
+*
+*        The number of input coordintes for the Mapping (Nin
+*        attribute) should match the value of "ndim_in" (below), and
+*        the number of output coordinates (Nout attribute) should
+*        match the value of "ndim_out".
+*     linear_fit
+*        Pointer to an optional array of double which contains the
+*        coefficients of a linear fit which approximates the above
+*        Mapping's forward coordinate transformation. If this is
+*        supplied, it will be used in preference to the above Mapping
+*        when transforming coordinates. This may be used to enhance
+*        performance in cases where evaluation of the Mapping's
+*        forward transformation is expensive. If no linear fit is
+*        available, a NULL pointer should be supplied.
+*
+*        The way in which the fit coefficients are stored in this
+*        array and the number of array elements are as defined by the
+*        astLinearApprox function.
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input data grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input data grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input data grid, its extent along a
+*        particular (i'th) dimension being (ubnd_in[i] - lbnd_in[i] +
+*        1). They also define the input grid's coordinate system, with
+*        each pixel being of unit extent along each dimension with
+*        integral coordinate values at its centre.
+*     in
+*        Pointer to the input array of data to be rebinned (with one
+*        element for each pixel in the input grid). The numerical type
+*        of these data should match the "type" value (below). The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and data type as the "in" array),
+*        which represent estimates of the statistical variance
+*        associated with each element of the "in" array. If this
+*        second array is given (along with the corresponding "out_var"
+*        array), then estimates of the variance of the rebinned data
+*        will also be returned.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     type
+*        A value taken from the "DataType" enum, which specifies the
+*        data type of the input and output arrays containing the
+*        gridded data (and variance) values.
+*     spread
+*        A value selected from a set of pre-defined macros to identify
+*        which pixel spread function should be used.
+*     params
+*        Pointer to an optional array of parameters that may be passed
+*        to the pixel spread algorithm, if required. If no parameters
+*        are required, a NULL pointer should be supplied.
+*     flags
+*        The bitwise OR of a set of flag values which provide additional
+*        control over the resampling operation.
+*     badval_ptr
+*        If the AST__USEBAD flag is set (above), this parameter is a
+*        pointer to a value which is used to identify bad data and/or
+*        variance values in the input array(s). The referenced value's
+*        data type must match that of the "in" (and "in_var")
+*        arrays. The same value will also be used to flag any output
+*        array elements for which rebinned values could not be
+*        obtained.  The output arrays(s) may be flagged with this
+*        value whether or not the AST__USEBAD flag is set (the
+*        function return value indicates whether any such values have
+*        been produced).
+*     ndim_out
+*        The number of dimensions in the output grid. This should be
+*        at least one.
+*     lbnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the output data grid along each dimension.
+*     ubnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the output data grid along each dimension.
+*
+*        Note that "lbnd_out" and "ubnd_out" together define the shape
+*        and size of the output data grid in the same way as "lbnd_in"
+*        and "ubnd_in" define the shape and size of the input grid
+*        (see above).
+*     lbnd
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the first pixel in the
+*        section of the input data grid which is to be rebinned.
+*     ubnd
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the last pixel in the
+*        section of the input data grid which is to be rebinned.
+*
+*        Note that "lbnd" and "ubnd" define the shape and position of
+*        the section of the input grid which is to be rebinned. This section
+*        should lie wholly within the extent of the input grid (as defined
+*        by the "lbnd_out" and "ubnd_out" arrays). Regions of the input
+*        grid lying outside this section will be ignored.
+*     npix_out
+*        The number of pixels in the output array.
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the rebinned data will be returned.  The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the rebinned values may be returned. This array will only be
+*        used if the "in_var" array has been given.
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+*     work
+*        An optional pointer to a double array with the same size as
+*        the "out" array. The contents of this array (if supplied) are
+*        incremented by the accumulated weights assigned to each output pixel.
+*        If no accumulated weights are required, a NULL pointer should be
+*        given.
+*     nused
+*        An optional pointer to a int64_t which will be incremented by the
+*        number of input values pasted into the output array. Ignored if NULL.
+
+*  Returned Value:
+*     A non-zero value is returned if "flags" included AST__CONSERVEFLUX (i.e.
+*     flux conservation was requested), but the supplied linear fit to the
+*     forward transformation of the Mapping had zero determinant (no error
+*     is reported if this happens). Zero is returned otherwise.
+
+*/
+
+/* Local Constants: */
+   const int mxpix = 2 * 1024;   /* Maximum number of pixels in a block (this
+                                    relatively small number seems to give best
+                                    performance) */
+
+/* Local Variables: */
+   double factor;                /* Flux conservation factor */
+   int *dim_block;               /* Pointer to array of block dimensions */
+   int *lbnd_block;              /* Pointer to block lower bound array */
+   int *ubnd_block;              /* Pointer to block upper bound array */
+   int dim;                      /* Dimension size */
+   int done;                     /* All blocks rebinned? */
+   int hilim;                    /* Upper limit on maximum block dimension */
+   int idim;                     /* Loop counter for dimensions */
+   int lolim;                    /* Lower limit on maximum block dimension */
+   int mxdim_block;              /* Maximum block dimension */
+   int npix;                     /* Number of pixels in block */
+   int result;                   /* Returned value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Allocate workspace. */
+   lbnd_block = astMalloc( sizeof( int ) * (size_t) ndim_in );
+   ubnd_block = astMalloc( sizeof( int ) * (size_t) ndim_in );
+   dim_block = astMalloc( sizeof( int ) * (size_t) ndim_in );
+   if ( astOK ) {
+
+/* Find the optimum block size. */
+/* ---------------------------- */
+/* We first need to find the maximum extent which a block of input
+   pixels may have in each dimension. We determine this by taking the
+   input grid extent in each dimension and then limiting the maximum
+   dimension size until the resulting number of pixels is sufficiently
+   small. This approach allows the block shape to approximate (or
+   match) the input grid shape when appropriate. */
+
+/* First loop to calculate the total number of input pixels and the
+   maximum input dimension size. */
+      npix = 1;
+      mxdim_block = 0;
+      for ( idim = 0; idim < ndim_in; idim++ ) {
+         dim = ubnd[ idim ] - lbnd[ idim ] + 1;
+         npix *= dim;
+         if ( mxdim_block < dim ) mxdim_block = dim;
+      }
+
+/* If the number of input pixels is too large for a single block, we
+   perform iterations to determine the optimum upper limit on a
+   block's dimension size. Initialise the limits on this result. */
+      if ( npix > mxpix ) {
+         lolim = 1;
+         hilim = mxdim_block;
+
+/* Loop to perform a binary chop, searching for the best result until
+   the lower and upper limits on the result converge to adjacent
+   values. */
+         while ( ( hilim - lolim ) > 1 ) {
+
+/* Form a new estimate from the mid-point of the previous limits. */
+            mxdim_block = ( hilim + lolim ) / 2;
+
+/* See how many pixels a block contains if its maximum dimension is
+   limited to this new value. */
+            for ( npix = 1, idim = 0; idim < ndim_in; idim++ ) {
+               dim = ubnd[ idim ] - lbnd[ idim ] + 1;
+               npix *= ( dim < mxdim_block ) ? dim : mxdim_block;
+            }
+
+/* Update the appropriate limit, according to whether the number of
+   pixels is too large or too small. */
+            *( ( npix <= mxpix ) ? &lolim : &hilim ) = mxdim_block;
+         }
+
+/* When iterations have converged, obtain the maximum limit on the
+   dimension size of a block which results in no more than the maximum
+   allowed number of pixels per block. However, ensure that all block
+   dimensions are at least 2. */
+            mxdim_block = lolim;
+      }
+      if ( mxdim_block < 2 ) mxdim_block = 2;
+
+/* Calculate the block dimensions by applying this limit to the output
+   grid dimensions. */
+      for ( idim = 0; idim < ndim_in; idim++ ) {
+         dim = ubnd[ idim ] - lbnd[ idim ] + 1;
+         dim_block[ idim ] = ( dim < mxdim_block ) ? dim : mxdim_block;
+
+/* Also initialise the lower and upper bounds of the first block of
+   output grid pixels to be rebinned, ensuring that this does not
+   extend outside the grid itself. */
+         lbnd_block[ idim ] = lbnd[ idim ];
+         ubnd_block[ idim ] = MinI( lbnd[ idim ] + dim_block[ idim ] - 1,
+                                    ubnd[ idim ], status );
+      }
+
+/* Determine the flux conservation constant if needed. */
+/* --------------------------------------------------- */
+      factor = 1.0;
+      if( flags & AST__CONSERVEFLUX ) {
+         if( linear_fit ) {
+            factor = MatrixDet( ndim_out, ndim_in, linear_fit + ndim_out,
+                                status );
+            if( factor != 0.0 ) {
+               factor = 1.0/factor;
+            } else {
+               result = 1;
+            }
+         } else {
+            result = 1;
+         }
+      }
+
+/* Rebin each block of input pixels. */
+/* --------------------------------- */
+/* Loop to generate the extent of each block of input pixels and to
+   rebin them. */
+      done = result;
+      while ( !done && astOK ) {
+
+/* Rebin the current block, accumulating the sum of bad pixels produced. */
+         RebinSection( this, linear_fit, ndim_in, lbnd_in, ubnd_in, in,
+                       in_var, factor, type, spread, params, flags, badval_ptr,
+                       ndim_out, lbnd_out, ubnd_out, lbnd_block, ubnd_block,
+                       npix_out, out, out_var, work, nused, status );
+
+/* Update the block extent to identify the next block of input pixels. */
+         idim = 0;
+         do {
+
+/* We find the least significant dimension where the upper bound of
+   the block has not yet reached the upper bound of the region of the
+   input grid which we are rebinning. The block's position is then
+   incremented by one block extent along this dimension, checking that
+   the resulting extent does not go outside the region being rebinned. */
+            if ( ubnd_block[ idim ] < ubnd[ idim ] ) {
+               lbnd_block[ idim ] = MinI( lbnd_block[ idim ] +
+                                          dim_block[ idim ], ubnd[ idim ], status );
+               ubnd_block[ idim ] = MinI( lbnd_block[ idim ] +
+                                          dim_block[ idim ] - 1,
+                                          ubnd[ idim ], status );
+               break;
+
+/* If any less significant dimensions are found where the upper bound
+   of the block has reached its maximum value, we reset the block to
+   its lowest position. */
+            } else {
+               lbnd_block[ idim ] = lbnd[ idim ];
+               ubnd_block[ idim ] = MinI( lbnd[ idim ] + dim_block[ idim ] - 1,
+                                          ubnd[ idim ], status );
+
+/* All the blocks have been processed once the position along the most
+   significant dimension has been reset. */
+               done = ( ++idim == ndim_in );
+            }
+         } while ( !done );
+      }
+   }
+
+/* Free the workspace. */
+   lbnd_block = astFree( lbnd_block );
+   ubnd_block = astFree( ubnd_block );
+   dim_block = astFree( dim_block );
+
+/* Return a flag indicating if there was an error conserving flux. */
+   return result;
+}
+
+static AstMapping *RemoveRegions( AstMapping *this, int *status ) {
+/*
+*++
+*  Name:
+c     astRemoveRegions
+f     AST_REMOVEREGIONS
+
+*  Purpose:
+*     Remove any Regions from a Mapping.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     AstMapping *astRemoveRegions( AstMapping *this )
+f     RESULT = AST_REMOVEREGIONS( THIS, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function searches the suppliedMapping (which may be a
+*     compound Mapping such as a CmpMap) for any component Mappings
+*     that are instances of the AST Region class. It then creates a new
+*     Mapping from which all Regions have been removed. If a Region
+*     cannot simply be removed (for instance, if it is a component of a
+*     parallel CmpMap), then it is replaced with an equivalent UnitMap
+*     in the returned Mapping.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the original Mapping.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astRemoveRegions()
+f     AST_REMOVEREGIONS = INTEGER
+*        A new pointer to the (possibly modified) Mapping.
+
+*  Applicability:
+*     CmpFrame
+*        If the supplied Mapping is a CmpFrame, any component Frames that
+*        are instances of the Region class are replaced by the equivalent
+*        Frame.
+*     FrameSet
+*        If the supplied Mapping is a FrameSet, the returned Mapping
+*        will be a copy of the supplied FrameSet in which Regions have
+*        been removed from all the inter-Frame Mappings, and any Frames
+*        which are instances of the Region class are repalced by the
+*        equivalent Frame.
+*     Mapping
+*        This function applies to all Mappings.
+*     Region
+*        If the supplied Mapping is a Region, the returned Mapping will
+*        be the equivalent Frame.
+
+*  Notes:
+*     - This function can safely be applied even to Mappings which
+*     contain no Regions. If no Regions are found, it
+c     behaves exactly like astClone and returns a pointer to the
+f     behaves exactly like AST_CLONE and returns a pointer to the
+*     original Mapping.
+*     - The Mapping returned by this function may not be independent
+*     of the original (even if some Regions were removed), and
+*     modifying it may therefore result in indirect modification of
+*     the original. If a completely independent result is required, a
+c     copy should be made using astCopy.
+f     copy should be made using AST_COPY.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* This base iplementation just returns a clone of the supplied Mapping
+   pointer. Sub-classes should override it as necessary. */
+   return astClone( this );
+}
+
+static void ReportPoints( AstMapping *this, int forward,
+                          AstPointSet *in_points, AstPointSet *out_points, int *status ) {
+/*
+*+
+*  Name:
+*     astReportPoints
+
+*  Purpose:
+*     Report the effect of transforming a set of points using a Mapping.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void astReportPoints( AstMapping *this, int forward,
+*                           AstPointSet *in_points, AstPointSet *out_points )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function reports the coordinates of a set of points before
+*     and after being transformed by a Mapping, by writing them to
+*     standard output.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     forward
+*        A non-zero value indicates that the Mapping's forward
+*        coordinate transformation has been applied, while a zero
+*        value indicates the inverse transformation.
+*     in_points
+*        Pointer to a PointSet which is associated with the
+*        coordinates of a set of points before the Mapping was
+*        applied.
+*     out_points
+*        Pointer to a PointSet which is associated with the
+*        coordinates of the same set of points after the Mapping has
+*        been applied.
+
+*  Notes:
+*     - This method is provided as a development and debugging aid to
+*     be invoked when coordinates are transformed by public Mapping
+*     methods and under control of the "Report" Mapping attribute.
+*     - Derived clases may over-ride this method in order to change
+*     the way in which coordinates are formatted, etc.
+*-
+*/
+
+/* Local Variables: */
+   double **ptr_in;              /* Pointer to array of input data pointers */
+   double **ptr_out;             /* Pointer to array of output data pointers */
+   int coord;                    /* Loop counter for coordinates */
+   int ncoord_in;                /* Number of input coordinates per point */
+   int ncoord_out;               /* Number of output coordinates per point */
+   int npoint;                   /* Number of points to report */
+   int npoint_in;                /* Number of input points */
+   int npoint_out;               /* Number of output points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain the numbers of points and coordinates associated with each
+   PointSet. */
+   npoint_in = astGetNpoint( in_points );
+   npoint_out = astGetNpoint( out_points );
+   ncoord_in = astGetNcoord( in_points );
+   ncoord_out = astGetNcoord( out_points );
+
+/* Obtain the pointers that give access to the coordinate data
+   associated with each PointSet. */
+   ptr_in = astGetPoints( in_points );
+   ptr_out = astGetPoints( out_points );
+
+/* In the event that both PointSets don't contain equal numbers of
+   points (this shouldn't actually happen), simply use the minimum
+   number. */
+   npoint = ( npoint_in < npoint_out ) ? npoint_in : npoint_out;
+
+/* Loop to report the effect of the Mapping on each point in turn. */
+   for ( point = 0; point < npoint; point++ ) {
+
+/* Report the input coordinates (in parentheses and separated by
+   commas). Replace coordinate values of AST__BAD with the string
+   "<bad>" to indicate missing values. */
+      printf( "(" );
+      for ( coord = 0; coord < ncoord_in; coord++ ) {
+         if ( ptr_in[ coord ][ point ] == AST__BAD ) {
+            printf( "%s<bad>", coord ? ", " : "" );
+         } else {
+            printf( "%s%.*g", coord ? ", " : "",
+                              DBL_DIG, ptr_in[ coord ][ point ] );
+         }
+      }
+
+/* Similarly report the output coordinates. */
+      printf( ") --> (" );
+      for ( coord = 0; coord < ncoord_out; coord++ ) {
+         if ( ptr_out[ coord ][ point ] == AST__BAD ) {
+            printf( "%s<bad>", coord ? ", " : "" );
+         } else {
+            printf( "%s%.*g", coord ? ", " : "",
+                              DBL_DIG, ptr_out[ coord ][ point ] );
+         }
+      }
+      printf( ")\n" );
+   }
+}
+
+/*
+*++
+*  Name:
+c     astResample<X>
+f     AST_RESAMPLE<X>
+
+*  Purpose:
+*     Resample a region of a data grid.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     int astResample<X>( AstMapping *this, int ndim_in,
+c                         const int lbnd_in[], const int ubnd_in[],
+c                         const <Xtype> in[], const <Xtype> in_var[],
+c                         int interp, void (* finterp)( void ),
+c                         const double params[], int flags,
+c                         double tol, int maxpix,
+c                         <Xtype> badval, int ndim_out,
+c                         const int lbnd_out[], const int ubnd_out[],
+c                         const int lbnd[], const int ubnd[],
+c                         <Xtype> out[], <Xtype> out_var[] );
+f     RESULT = AST_RESAMPLE<X>( THIS, NDIM_IN, LBND_IN, UBND_IN, IN, IN_VAR,
+f                               INTERP, FINTERP, PARAMS, FLAGS,
+f                               TOL, MAXPIX, BADVAL,
+f                               NDIM_OUT, LBND_OUT, UBND_OUT,
+f                               LBND, UBND, OUT, OUT_VAR, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This is a set of functions for resampling gridded data (e.g. an
+*     image) under the control of a geometrical transformation, which
+*     is specified by a Mapping.  The functions operate on a pair of
+*     data grids (input and output), each of which may have any number
+*     of dimensions. Resampling may be restricted to a specified
+*     region of the output grid. An associated grid of error estimates
+*     associated with the input data may also be supplied (in the form
+*     of variance values), so as to produce error estimates for the
+*     resampled output data. Propagation of missing data (bad pixels)
+*     is supported.
+*
+*     You should use a resampling function which matches the numerical
+*     type of the data you are processing by replacing <X> in
+c     the generic function name astResample<X> by an appropriate 1- or
+f     the generic function name AST_RESAMPLE<X> by an appropriate 1- or
+*     2-character type code. For example, if you are resampling data
+c     with type "float", you should use the function astResampleF (see
+f     with type REAL, you should use the function AST_RESAMPLER (see
+*     the "Data Type Codes" section below for the codes appropriate to
+*     other numerical types).
+*
+*     Resampling of the grid of input data is performed by
+*     transforming the coordinates of the centre of each output grid
+*     element (or pixel) into the coordinate system of the input grid.
+*     Since the resulting coordinates will not, in general, coincide
+*     with the centre of an input pixel, sub-pixel interpolation is
+*     performed between the neighbouring input pixels. This produces a
+*     resampled value which is then assigned to the output pixel. A
+*     choice of sub-pixel interpolation schemes is provided, but you
+*     may also implement your own.
+*
+*     This algorithm samples the input data value, it does not integrate
+*     it. Thus total data value in the input image will not, in general,
+*     be conserved. However, an option is provided (see the "Control Flags"
+*     section below) which can produce approximate flux conservation by
+*     scaling the output values using the ratio of the output pixel size
+*     to the input pixel size. However, if accurate flux conservation is
+*     important to you, consder using the
+c     astRebin<X> or astRebinSeq<X> family of functions
+f     AST_REBIN<X> or AST_REBINSEQ<X> family of routines
+*     instead.
+*
+*     Output pixel coordinates are transformed into the coordinate
+*     system of the input grid using the inverse transformation of the
+*     Mapping which is supplied. This means that geometrical features
+*     in the input data are subjected to the Mapping's forward
+*     transformation as they are transferred from the input to the
+*     output grid (although the Mapping's forward transformation is
+*     not explicitly used).
+*
+*     In practice, transforming the coordinates of every pixel of a
+*     large data grid can be time-consuming, especially if the Mapping
+*     involves complicated functions, such as sky projections. To
+*     improve performance, it is therefore possible to approximate
+*     non-linear Mappings by a set of linear transformations which are
+*     applied piece-wise to separate sub-regions of the data. This
+*     approximation process is applied automatically by an adaptive
+*     algorithm, under control of an accuracy criterion which
+*     expresses the maximum tolerable geometrical distortion which may
+*     be introduced, as a fraction of a pixel.
+*
+*     This algorithm first attempts to approximate the Mapping with a
+*     linear transformation applied over the whole region of the
+*     output grid which is being used. If this proves to be
+*     insufficiently accurate, the output region is sub-divided into
+*     two along its largest dimension and the process is repeated
+*     within each of the resulting sub-regions. This process of
+*     sub-division continues until a sufficiently good linear
+*     approximation is found, or the region to which it is being
+*     applied becomes too small (in which case the original Mapping is
+*     used directly).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to a Mapping, whose inverse transformation will be
+*        used to transform the coordinates of pixels in the output
+*        grid into the coordinate system of the input grid. This
+*        yields the positions which are used to obtain resampled
+*        values by sub-pixel interpolation within the input grid.
+*
+*        The number of input coordinates used by this Mapping (as
+*        given by its Nin attribute) should match the number of input
+c        grid dimensions given by the value of "ndim_in"
+f        grid dimensions given by the value of NDIM_IN
+*        below. Similarly, the number of output coordinates (Nout
+*        attribute) should match the number of output grid dimensions
+c        given by "ndim_out".
+f        given by NDIM_OUT.
+c     ndim_in
+f     NDIM_IN = INTEGER (Given)
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+c     lbnd_in
+f     LBND_IN( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the centre of the first pixel
+*        in the input grid along each dimension.
+c     ubnd_in
+f     UBND_IN( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the centre of the last pixel in
+*        the input grid along each dimension.
+*
+c        Note that "lbnd_in" and "ubnd_in" together define the shape
+f        Note that LBND_IN and UBND_IN together define the shape
+*        and size of the input grid, its extent along a particular
+c        (j'th) dimension being ubnd_in[j]-lbnd_in[j]+1 (assuming the
+c        index "j" to be zero-based). They also define
+f        (J'th) dimension being UBND_IN(J)-LBND_IN(J)+1. They also define
+*        the input grid's coordinate system, each pixel having unit
+*        extent along each dimension with integral coordinate values
+*        at its centre.
+c     in
+f     IN( * ) = <Xtype> (Given)
+c        Pointer to an array, with one element for each pixel in the
+f        An array, with one element for each pixel in the
+*        input grid, containing the input data to be resampled.  The
+*        numerical type of this array should match the 1- or
+*        2-character type code appended to the function name (e.g. if
+c        you are using astResampleF, the type of each array element
+c        should be "float").
+f        you are using AST_RESAMPLER, the type of each array element
+f        should be REAL).
+*
+*        The storage order of data within this array should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+c        (i.e. Fortran array indexing is used).
+f        (i.e. normal Fortran array storage order).
+c     in_var
+f     IN_VAR( * ) = <Xtype> (Given)
+c        An optional pointer to a second array with the same size and
+c        type as the "in" array. If given, this should contain a set
+c        of non-negative values which represent estimates of the
+c        statistical variance associated with each element of the "in"
+c        array. If this array is supplied (together with the
+c        corresponding "out_var" array), then estimates of the
+c        variance of the resampled output data will be calculated.
+c
+c        If no input variance estimates are being provided, a NULL
+c        pointer should be given.
+f        An optional second array with the same size and type as the
+f        IN array. If the AST__USEVAR flag is set via the FLAGS
+f        argument (below), this array should contain a set of
+f        non-negative values which represent estimates of the
+f        statistical variance associated with each element of the IN
+f        array. Estimates of the variance of the resampled output data
+f        will then be calculated.
+f
+f        If the AST__USEVAR flag is not set, no input variance
+f        estimates are required and this array will not be used. A
+f        dummy (e.g. one-element) array may then be supplied.
+c     interp
+f     INTERP = INTEGER (Given)
+c        This parameter specifies the scheme to be used for sub-pixel
+f        This argument specifies the scheme to be used for sub-pixel
+*        interpolation within the input grid. It may be used to select
+*        from a set of pre-defined schemes by supplying one of the
+*        values described in the "Sub-Pixel Interpolation Schemes"
+*        section below.  If a value of zero is supplied, then the
+*        default linear interpolation scheme is used (equivalent to
+*        supplying the value AST__LINEAR).
+*
+*        Alternatively, you may supply a value which indicates that
+c        you will provide your own function to perform sub-pixel
+c        interpolation by means of the "finterp " parameter. Again, see
+f        you will provide your own routine to perform sub-pixel
+f        interpolation by means of the FINTERP argument. Again, see
+*        the "Sub-Pixel Interpolation Schemes" section below for
+*        details.
+c     finterp
+f     FINTERP = SUBROUTINE (Given)
+c        If the value given for the "interp" parameter indicates that
+c        you will provide your own function for sub-pixel
+c        interpolation, then a pointer to that function should be
+c        given here. For details of the interface which the function
+c        should have (several are possible, depending on the value of
+c        "interp"), see the "Sub-Pixel Interpolation Schemes" section
+c        below.
+f        If the value given for the INTERP argument indicates that you
+f        will provide your own routine for sub-pixel interpolation,
+f        then the name of that routine should be given here (the name
+f        should also appear in a Fortran EXTERNAL statement in the
+f        routine which invokes AST_RESAMPLE<X>). For details of the
+f        interface which the routine should have (several are
+f        possible, depending on the value of INTERP), see the
+f        "Sub-Pixel Interpolation Schemes" section below.
+*
+c        If the "interp" parameter has any other value, corresponding
+c        to one of the pre-defined interpolation schemes, then this
+c        function will not be used and you may supply a NULL pointer.
+f        If the INTERP argument has any other value, corresponding to
+f        one of the pre-defined interpolation schemes, then this
+f        routine will not be used and you may supply the null routine
+f        AST_NULL here (note only one underscore).  No EXTERNAL
+f        statement is required for this routine, so long as the AST_PAR
+f        include file has been used.
+c     params
+f     PARAMS( * ) = DOUBLE PRECISION (Given)
+c        An optional pointer to an array of double which should contain
+f        An optional array which should contain
+*        any additional parameter values required by the sub-pixel
+*        interpolation scheme. If such parameters are required, this
+*        will be noted in the "Sub-Pixel Interpolation Schemes"
+c        section below (you may also use this array to pass values
+c        to your own interpolation function).
+f        section below (you may also use this array to pass values
+f        to your own interpolation routine).
+*
+c        If no additional parameters are required, this array is not
+c        used and a NULL pointer may be given.
+f        If no additional parameters are required, this array is not
+f        used. A dummy (e.g. one-element) array may then be supplied.
+c     flags
+f     FLAGS = INTEGER (Given)
+c        The bitwise OR of a set of flag values which may be used to
+f        The sum of a set of flag values which may be used to
+*        provide additional control over the resampling operation. See
+*        the "Control Flags" section below for a description of the
+*        options available.  If no flag values are to be set, a value
+*        of zero should be given.
+c     tol
+f     TOL = DOUBLE PRECISION (Given)
+*        The maximum tolerable geometrical distortion which may be
+*        introduced as a result of approximating non-linear Mappings
+*        by a set of piece-wise linear transformations. This should be
+*        expressed as a displacement in pixels in the input grid's
+*        coordinate system.
+*
+*        If piece-wise linear approximation is not required, a value
+*        of zero may be given. This will ensure that the Mapping is
+*        used without any approximation, but may increase execution
+*        time.
+c     maxpix
+f     MAXPIX = INTEGER (Given)
+*        A value which specifies an initial scale size (in pixels) for
+*        the adaptive algorithm which approximates non-linear Mappings
+*        with piece-wise linear transformations. Normally, this should
+*        be a large value (larger than any dimension of the region of
+*        the output grid being used). In this case, a first attempt to
+*        approximate the Mapping by a linear transformation will be
+*        made over the entire output region.
+*
+*        If a smaller value is used, the output region will first be
+c        divided into sub-regions whose size does not exceed "maxpix"
+f        divided into sub-regions whose size does not exceed MAXPIX
+*        pixels in any dimension. Only at this point will attempts at
+*        approximation commence.
+*
+*        This value may occasionally be useful in preventing false
+*        convergence of the adaptive algorithm in cases where the
+*        Mapping appears approximately linear on large scales, but has
+*        irregularities (e.g. holes) on smaller scales. A value of,
+*        say, 50 to 100 pixels can also be employed as a safeguard in
+*        general-purpose software, since the effect on performance is
+*        minimal.
+*
+*        If too small a value is given, it will have the effect of
+*        inhibiting linear approximation altogether (equivalent to
+c        setting "tol" to zero). Although this may degrade
+f        setting TOL to zero). Although this may degrade
+*        performance, accurate results will still be obtained.
+c     badval
+f     BADVAL = <Xtype> (Given)
+*        This argument should have the same type as the elements of
+c        the "in" array. It specifies the value used to flag missing
+f        the IN array. It specifies the value used to flag missing
+*        data (bad pixels) in the input and output arrays.
+*
+c        If the AST__USEBAD flag is set via the "flags" parameter,
+f        If the AST__USEBAD flag is set via the FLAGS argument,
+c        then this value is used to test for bad pixels in the "in"
+c        (and "in_var") array(s).
+f        then this value is used to test for bad pixels in the IN
+f        (and IN_VAR) array(s).
+*
+c        Unless the AST__NOBAD flag is set via the "flags" parameter,
+f        Unless the AST__NOBAD flag is set via the FLAGS argument,
+*        this value is also used to flag any output
+c        elements in the "out" (and "out_var") array(s) for which
+f        elements in the OUT (and OUT_VAR) array(s) for which
+*        resampled values could not be obtained (see the "Propagation
+*        of Missing Data" section below for details of the
+c        circumstances under which this may occur). The astResample<X>
+f        circumstances under which this may occur). The AST_RESAMPLE<X>
+*        function return value indicates whether any such values have
+*        been produced. If the AST__NOBAD flag is set. then output array
+*        elements for which no resampled value could be obtained are
+*        left set to the value they had on entry to this function.
+c     ndim_out
+f     NDIM_OUT = INTEGER (Given)
+*        The number of dimensions in the output grid. This should be
+*        at least one. It need not necessarily be equal to the number
+*        of dimensions in the input grid.
+c     lbnd_out
+f     LBND_OUT( NDIM_OUT ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_out" elements,
+f        An array
+*        containing the coordinates of the centre of the first pixel
+*        in the output grid along each dimension.
+c     ubnd_out
+f     UBND_OUT( NDIM_OUT ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_out" elements,
+f        An array
+*        containing the coordinates of the centre of the last pixel in
+*        the output grid along each dimension.
+*
+c        Note that "lbnd_out" and "ubnd_out" together define the
+f        Note that LBND_OUT and UBND_OUT together define the
+*        shape, size and coordinate system of the output grid in the
+c        same way as "lbnd_in" and "ubnd_in" define the shape, size
+f        same way as LBND_IN and UBND_IN define the shape, size
+*        and coordinate system of the input grid.
+c     lbnd
+f     LBND( NDIM_OUT ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_out" elements,
+f        An array
+*        containing the coordinates of the first pixel in the region
+*        of the output grid for which a resampled value is to be
+*        calculated.
+c     ubnd
+f     UBND( NDIM_OUT ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_out" elements,
+f        An array
+*        containing the coordinates of the last pixel in the region of
+*        the output grid for which a resampled value is to be
+*        calculated.
+*
+c        Note that "lbnd" and "ubnd" together define the shape and
+f        Note that LBND and UBND together define the shape and
+*        position of a (hyper-)rectangular region of the output grid
+*        for which resampled values should be produced. This region
+*        should lie wholly within the extent of the output grid (as
+c        defined by the "lbnd_out" and "ubnd_out" arrays). Regions of
+f        defined by the LBND_OUT and UBND_OUT arrays). Regions of
+*        the output grid lying outside this region will not be
+*        modified.
+c     out
+f     OUT( * ) = <Xtype> (Returned)
+c        Pointer to an array, with one element for each pixel in the
+f        An array, with one element for each pixel in the
+*        output grid, into which the resampled data values will be
+*        returned. The numerical type of this array should match that
+c        of the "in" array, and the data storage order should be such
+f        of the IN array, and the data storage order should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+c        (i.e. Fortran array indexing is used).
+f        (i.e. normal Fortran array storage order).
+c     out_var
+f     OUT_VAR( * ) = <Xtype> (Returned)
+c        An optional pointer to an array with the same type and size
+c        as the "out" array. If given, this array will be used to
+c        return variance estimates for the resampled data values. This
+c        array will only be used if the "in_var" array has also been
+c        supplied.
+f        An optional array with the same type and size as the OUT
+f        array. If the AST__USEVAR flag is set via the FLAGS argument,
+f        this array will be used to return variance estimates for the
+f        resampled data values.
+*
+*        The output variance values will be calculated on the
+*        assumption that errors on the input data values are
+*        statistically independent and that their variance estimates
+*        may simply be summed (with appropriate weighting factors)
+*        when several input pixels contribute to an output data
+*        value. If this assumption is not valid, then the output error
+*        estimates may be biased. In addition, note that the
+*        statistical errors on neighbouring output data values (as
+*        well as the estimates of those errors) may often be
+*        correlated, even if the above assumption about the input data
+*        is correct, because of the sub-pixel interpolation schemes
+*        employed.
+*
+c        If no output variance estimates are required, a NULL pointer
+c        should be given.
+f        If the AST__USEVAR flag is not set, no output variance
+f        estimates will be calculated and this array will not be
+f        used. A dummy (e.g. one-element) array may then be supplied.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astResample<X>()
+f     AST_RESAMPLE<X> = INTEGER
+*        The number of output pixels for which no valid resampled value
+*        could be obtained. Thus, in the absence of any error, a returned
+*        value of zero indicates that all the required output pixels
+*        received valid resampled data values (and variances). See the
+c        "badval" and "flags" parameters.
+f        BADVAL and FLAGS arguments.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+
+*  Data Type Codes:
+*     To select the appropriate resampling function, you should
+c     replace <X> in the generic function name astResample<X> with a
+f     replace <X> in the generic function name AST_RESAMPLE<X> with a
+*     1- or 2-character data type code, so as to match the numerical
+*     type <Xtype> of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - L: long int (may be 32 or 64 bit)
+c     - K: 64 bit int
+c     - UL: unsigned long int (may be 32 or 64 bit)
+c     - UK: unsigned 64 bit int
+c     - I: int
+c     - UI: unsigned int
+c     - S: short int
+c     - US: unsigned short int
+c     - B: byte (signed char)
+c     - UB: unsigned byte (unsigned char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - UI: INTEGER (treated as unsigned)
+f     - S: INTEGER*2 (short integer)
+f     - US: INTEGER*2 (short integer, treated as unsigned)
+f     - B: BYTE (treated as signed)
+f     - UB: BYTE (treated as unsigned)
+*
+c     For example, astResampleD would be used to process "double"
+c     data, while astResampleS would be used to process "short int"
+c     data, etc.
+f     For example, AST_RESAMPLED would be used to process DOUBLE
+f     PRECISION data, while AST_RESAMPLES would be used to process
+f     short integer data (stored in an INTEGER*2 array), etc.
+f
+f     For compatibility with other Starlink facilities, the codes W
+f     and UW are provided as synonyms for S and US respectively (but
+f     only in the Fortran interface to AST).
+
+*  Sub-Pixel Interpolation Schemes:
+*     There is no such thing as a perfect sub-pixel interpolation
+*     scheme and, in practice, all resampling will result in some
+*     degradation of gridded data.  A range of schemes is therefore
+*     provided, from which you can choose the one which best suits
+*     your needs.
+*
+*     In general, a balance must be struck between schemes which tend
+*     to degrade sharp features in the data by smoothing them, and
+*     those which attempt to preserve sharp features. The latter will
+*     often tend to introduce unwanted oscillations, typically visible
+*     as "ringing" around sharp features and edges, especially if the
+*     data are under-sampled (i.e. if the sharpest features are less
+*     than about two pixels across). In practice, a good interpolation
+*     scheme is likely to be a compromise and may exhibit some aspects
+*     of both these features.
+*
+*     For under-sampled data, some interpolation schemes may appear to
+*     preserve data resolution because they transform single input
+*     pixels into single output pixels, rather than spreading their
+*     data between several output pixels. While this may look
+*     better cosmetically, it can result in a geometrical shift of
+*     sharp features in the data. You should beware of this if you
+*     plan to use such features (e.g.) for image alignment.
+*
+*     The following are two easy-to-use sub-pixel interpolation
+*     schemes which are generally applicable. They are selected by
+c     supplying the appropriate value (defined in the "ast.h" header
+c     file) via the "interp" parameter. In these cases, the "finterp"
+c     and "params" parameters are not used:
+f     supplying the appropriate value (defined in the AST_PAR include
+f     file) via the INTERP argument. In these cases, the FINTERP
+f     and PARAMS arguments are not used:
+*
+*     - AST__NEAREST: This is the simplest possible scheme, in which
+*     the value of the input pixel with the nearest centre to the
+*     interpolation point is used. This is very quick to execute and
+*     will preserve single-pixel features in the data, but may
+*     displace them by up to half their width along each dimension. It
+*     often gives a good cosmetic result, so is useful for quick-look
+*     processing, but is unsuitable if accurate geometrical
+*     transformation is required.
+*     - AST__LINEAR: This is the default scheme, which uses linear
+*     interpolation between the nearest neighbouring pixels in the
+*     input grid (there are two neighbours in one dimension, four
+*     neighbours in two dimensions, eight in three dimensions,
+*     etc.). It is superior to the nearest-pixel scheme (above) in not
+*     displacing features in the data, yet it still executes fairly
+*     rapidly. It is generally a safe choice if you do not have any
+*     particular reason to favour another scheme, since it cannot
+*     introduce oscillations. However, it does introduce some spatial
+*     smoothing which varies according to the distance of the
+*     interpolation point from the neighbouring pixels. This can
+*     degrade the shape of sharp features in the data in a
+*     position-dependent way. It may also show in the output variance
+*     grid (if used) as a pattern of stripes or fringes.
+*
+*     An alternative set of interpolation schemes is based on forming
+*     the interpolated value from the weighted sum of a set of
+*     surrounding pixel values (not necessarily just the nearest
+*     neighbours). This approach has its origins in the theory of
+*     digital filtering, in which interpolated values are obtained by
+*     conceptually passing the sampled data (represented by a grid of
+*     delta functions) through a linear filter which implements a
+*     convolution. Because the convolution kernel is continuous, the
+*     convolution yields a continuous function which may then be
+*     evaluated at fractional pixel positions. The (possibly
+*     multi-dimensional) kernel is usually regarded as "separable" and
+*     formed from the product of a set of identical 1-dimensional
+*     kernel functions, evaluated along each dimension. Different
+*     interpolation schemes are then distinguished by the choice of
+*     this 1-dimensional interpolation kernel. The number of
+*     surrounding pixels which contribute to the result may also be
+*     varied.
+*
+*     From a practical standpoint, it is useful to divide the weighted
+*     sum of pixel values by the sum of the weights when determining
+*     the interpolated value.  Strictly, this means that a true
+*     convolution is no longer being performed. However, the
+*     distinction is rarely important in practice because (for
+*     slightly subtle reasons) the sum of weights is always
+*     approximately constant for good interpolation kernels. The
+*     advantage of this technique, which is used here, is that it can
+*     easily accommodate missing data and tends to minimise unwanted
+*     oscillations at the edges of the data grid.
+*
+*     In the following schemes, which are based on a 1-dimensional
+c     interpolation kernel, the first element of the "params" array
+f     interpolation kernel, the first element of the PARAMS array
+*     should be used to specify how many pixels are to contribute to the
+*     interpolated result on either side of the interpolation point in
+*     each dimension (the nearest integer value is used). Execution time
+*     increases rapidly with this number. Typically, a value of 2 is
+*     appropriate and the minimum value used will be 1 (i.e. two pixels
+*     altogether, one on either side of the interpolation point).
+c     A value of zero or less may be given for "params[0]"
+f     A value of zero or less may be given for PARAMS(1)
+*     to indicate that a suitable number of pixels should be calculated
+*     automatically.
+*
+c     In each of these cases, the "finterp" parameter is not used:
+f     In each of these cases, the FINTERP argument is not used:
+*
+*     - AST__GAUSS: This scheme uses a kernel of the form exp(-k*x*x), with
+*     k a positive constant. The full-width at half-maximum (FWHM) is
+*     given by
+c     "params[1]"
+f     PARAMS(2)
+f     value, which should be at least 0.1 (in addition, setting PARAMS(1)
+*     to zero will select the number of contributing pixels so as to utilise
+*     the width of the kernel out to where the envelope declines to 1% of its
+*     maximum value). This kernel suppresses noise at the expense of
+*     smoothing the output array.
+*     - AST__SINC: This scheme uses a sinc(pi*x) kernel, where x is the
+*     pixel offset from the interpolation point and sinc(z)=sin(z)/z. This
+*     sometimes features as an "optimal" interpolation kernel in books on
+*     image processing. Its supposed optimality depends on the assumption
+*     that the data are band-limited (i.e. have no spatial frequencies above
+*     a certain value) and are adequately sampled. In practice, astronomical
+*     data rarely meet these requirements. In addition, high spatial
+*     frequencies are often present due (e.g.) to image defects and cosmic
+*     ray events. Consequently, substantial ringing can be experienced with
+*     this kernel. The kernel also decays slowly with distance, so that
+*     many surrounding pixels are required, leading to poor performance.
+*     Abruptly truncating it, by using only a few neighbouring pixels,
+c     improves performance and may reduce ringing (if "params[0]" is set to
+f     improves performance and may reduce ringing (if PARAMS(1) is set to
+*     zero, then only two pixels will be used on either side). However, a
+*     more gradual truncation, as implemented by other kernels, is generally
+*     to be preferred. This kernel is provided mainly so that you can
+*     convince yourself not to use it!
+*     - AST__SINCSINC: This scheme uses an improved kernel, of the form
+*     sinc(pi*x).sinc(k*pi*x), with k a constant, out to the point where
+*     sinc(k*pi*x) goes to zero, and zero beyond. The second sinc() factor
+*     provides an "envelope" which gradually rolls off the normal sinc(pi*x)
+*     kernel at large offsets. The width of this envelope is specified by
+*     giving the number of pixels offset at which it goes to zero by means
+c     of the "params[1]" value, which should be at least 1.0 (in addition,
+c     setting "params[0]" to zero will select the number of contributing
+f     of the PARAMS(2) value, which should be at least 1.0 (in addition,
+f     setting PARAMS(1) to zero will select the number of contributing
+*     pixels so as to utilise the full width of the kernel, out to where it
+c     reaches zero). The case given by "params[0]=2, params[1]=2" is typically
+f     reaches zero). The case given by PARAMS(1)=2, PARAMS(2)=2 is typically
+*     a good choice and is sometimes known as the Lanczos kernel. This is a
+*     valuable general-purpose interpolation scheme, intermediate in its
+*     visual effect on images between the AST__NEAREST and AST__LINEAR
+*     schemes. Although the kernel is slightly oscillatory, ringing is
+*     adequately suppressed if the data are well sampled.
+*     - AST__SINCCOS: This scheme uses a kernel of the form
+*     sinc(pi*x).cos(k*pi*x), with k a constant, out to the point where
+*     cos(k*pi*x) goes to zero, and zero beyond. As above, the cos() factor
+*     provides an envelope which gradually rolls off the sinc() kernel
+*     at large offsets. The width of this envelope is specified by giving
+*     the number of pixels offset at which it goes to zero by means
+c     of the "params[1]" value, which should be at least 1.0 (in addition,
+c     setting "params[0]" to zero will select the number of contributing
+f     of the PARAMS(2) value, which should be at least 1.0 (in addition,
+f     setting PARAMS(1) to zero will select the number of contributing
+*     pixels so as to utilise the full width of the kernel, out to where it
+*     reaches zero). This scheme gives similar results to the
+*     AST__SINCSINC scheme, which it resembles.
+*     - AST__SINCGAUSS: This scheme uses a kernel of the form
+*     sinc(pi*x).exp(-k*x*x), with k a positive constant. Here, the sinc()
+*     kernel is rolled off using a Gaussian envelope which is specified by
+c     giving its full-width at half-maximum (FWHM) by means of the "params[1]"
+c     value, which should be at least 0.1 (in addition, setting "params[0]"
+f     giving its full-width at half-maximum (FWHM) by means of the PARAMS(2)
+f     value, which should be at least 0.1 (in addition, setting PARAMS(1)
+*     to zero will select the number of contributing pixels so as to utilise
+*     the width of the kernel out to where the envelope declines to 1% of its
+*     maximum value). On astronomical images and spectra, good results are
+*     often obtained by approximately matching the FWHM of the
+c     envelope function, given by "params[1]", to the point spread function
+f     envelope function, given by PARAMS(2), to the point spread function
+*     of the input data. However, there does not seem to be any theoretical
+*     reason for this.
+*     - AST__SOMB: This scheme uses a somb(pi*x) kernel (a "sombrero"
+*     function), where x is the pixel offset from the interpolation point
+*     and somb(z)=2*J1(z)/z  (J1 is a Bessel function of the first kind of
+*     order 1). It is similar to the AST__SINC kernel, and has the same
+*     parameter usage.
+*     - AST__SOMBCOS: This scheme uses a kernel of the form
+*     somb(pi*x).cos(k*pi*x), with k a constant, out to the point where
+*     cos(k*pi*x) goes to zero, and zero beyond. It is similar to the
+*     AST__SINCCOS kernel, and has the same parameter usage.
+*
+*     In addition, the following schemes are provided which are not based
+*     on a 1-dimensional kernel:
+*
+*     - AST__BLOCKAVE: This scheme simply takes an average of all the
+*     pixels on the input grid in a cube centred on the interpolation
+*     point.  The number of pixels in the cube is determined by the
+c     value of the first element of the "params" array, which gives
+f     value of the first element of the PARAMS array, which gives
+*     the number of pixels in each dimension on either side of the
+c     central point.  Hence a block of (2 * params[0])^ndim_in
+f     central point.  Hence a block of (2 * PARAMS(1))**NDIM_IN
+*     pixels in the input grid will be examined to determine the
+*     value of the output pixel.  If the variance is not being used
+c     (var_in or var_out = NULL) then all valid pixels in this cube
+f     (USEVAR = .FALSE.) then all valid pixels in this cube
+*     will be averaged in to the result with equal weight.
+*     If variances are being used, then each input pixel will be
+*     weighted proportionally to the reciprocal of its variance; any
+*     pixel without a valid variance will be discarded.  This scheme
+*     is suitable where the output grid is much coarser than the
+*     input grid; if the ratio of pixel sizes is R then a suitable
+c     value of params[0] may be R/2.
+f     value of PARAMS(1) may be R/2.
+*
+c     Finally, supplying the following values for "interp" allows you
+c     to implement your own sub-pixel interpolation scheme by means of
+c     your own function. You should supply a pointer to this function
+c     via the "finterp" parameter:
+f     Finally, supplying the following values for INTERP allows you to
+f     implement your own sub-pixel interpolation scheme by means of
+f     your own routine. You should supply the name of this routine via
+f     the FINTERP argument:
+*
+c     - AST__UKERN1: In this scheme, you supply a function to evaluate
+c     your own 1-dimensional interpolation kernel, which is then used
+c     to perform sub-pixel interpolation (as described above). The
+c     function you supply should have the same interface as the
+c     fictitious astUkern1 function (q.v.).  In addition, a value
+c     should be given via "params[0]" to specify the number of
+c     neighbouring pixels which are to contribute to each interpolated
+c     value (in the same way as for the pre-defined interpolation
+c     schemes described above). Other elements of the "params" array
+c     are available to pass values to your interpolation function.
+f     - AST__UKERN1: In this scheme, you supply a routine to evaluate
+f     your own 1-dimensional interpolation kernel, which is then used
+f     to perform sub-pixel interpolation (as described above). The
+f     routine you supply should have the same interface as the
+f     fictitious AST_UKERN1 routine (q.v.).  In addition, a value
+f     should be given via PARAMS(1) to specify the number of
+f     neighbouring pixels which are to contribute to each interpolated
+f     value (in the same way as for the pre-defined interpolation
+f     schemes described above). Other elements of the PARAMS array
+f     are available to pass values to your interpolation routine.
+*
+c     - AST__UINTERP: This is a completely general scheme, in which
+c     your interpolation function has access to all of the input
+c     data. This allows you to implement any interpolation algorithm
+c     you choose, which could (for example) be non-linear, or
+c     adaptive. In this case, the astResample<X> functions play no
+c     role in the sub-pixel interpolation process and simply handle
+c     the geometrical transformation of coordinates and other
+c     housekeeping. The function you supply should have the same
+c     interface as the fictitious astUinterp function (q.v.). In this
+c     case, the "params" parameter is not used by astResample<X>, but
+c     is available to pass values to your interpolation function.
+f     - AST__UINTERP: This is a completely general scheme, in which
+f     your interpolation routine has access to all of the input
+f     data. This allows you to implement any interpolation algorithm
+f     you choose, which could (for example) be non-linear, or
+f     adaptive. In this case, the AST_RESAMPLE<X> functions play no
+f     role in the sub-pixel interpolation process and simply handle
+f     the geometrical transformation of coordinates and other
+f     housekeeping. The routine you supply should have the same
+f     interface as the fictitious AST_UINTERP routine (q.v.). In this
+f     case, the PARAMS argument is not used by AST_RESAMPLE<X>, but
+f     is available to pass values to your interpolation routine.
+
+*  Control Flags:
+c     The following flags are defined in the "ast.h" header file and
+f     The following flags are defined in the AST_PAR include file and
+*     may be used to provide additional control over the resampling
+*     process. Having selected a set of flags, you should supply the
+c     bitwise OR of their values via the "flags" parameter:
+f     sum of their values via the FLAGS argument:
+*
+*     - AST__NOBAD: Indicates that any output array elements for which no
+*     resampled value could be obtained should be left set to the value
+*     they had on entry to this function. If this flag is not supplied,
+*     such output array elements are set to the value supplied for
+c     parameter "badval". Note, this flag cannot be used in conjunction
+f     argument BADVAL. Note, this flag cannot be used in conjunction
+*     with the AST__CONSERVEFLUX flag (an error will be reported if both
+*     flags are specified).
+*     - AST__URESAMP1, 2, 3 & 4: A set of four flags which are
+*     reserved for your own use. They may be used to pass private
+c     information to any sub-pixel interpolation function which you
+f     information to any sub-pixel interpolation routine which you
+*     implement yourself. They are ignored by all the pre-defined
+*     interpolation schemes.
+*     - AST__USEBAD: Indicates that there may be bad pixels in the
+*     input array(s) which must be recognised by comparing with the
+c     value given for "badval" and propagated to the output array(s).
+f     value given for BADVAL and propagated to the output array(s).
+*     If this flag is not set, all input values are treated literally
+c     and the "badval" value is only used for flagging output array
+f     and the BADVAL value is only used for flagging output array
+*     values.
+f     - AST__USEVAR: Indicates that variance information should be
+f     processed in order to provide estimates of the statistical error
+f     associated with the resampled values. If this flag is not set,
+f     no variance processing will occur and the IN_VAR and OUT_VAR
+f     arrays will not be used. (Note that this flag is only available
+f     in the Fortran interface to AST.)
+*     - AST__CONSERVEFLUX: Indicates that the output pixel values should
+*     be scaled in such a way as to preserve (approximately) the total data
+*     value in a feature on the sky. Without this flag, each output pixel
+*     value represents an instantaneous sample of the input data values at
+*     the corresponding input position. This is appropriate if the input
+*     data represents the spatial density of some quantity (e.g. surface
+*     brightness in Janskys per square arc-second) because the output
+*     pixel values will have the same normalisation and units as the
+*     input pixel values. However, if the input data values represent
+*     flux (or some other physical quantity) per pixel, then the
+*     AST__CONSERVEFLUX flag could be used. This causes each output
+*     pixel value to be scaled by the ratio of the output pixel size to
+*     the input pixel size.
+*
+*     This flag can only be used if the Mapping is successfully approximated
+*     by one or more linear transformations. Thus an error will be reported
+*     if it used when the
+c     "tol" parameter
+f     TOL argument
+*     is set to zero (which stops the use of linear approximations), or
+*     if the Mapping is too non-linear to be approximated by a piece-wise
+*     linear transformation. The ratio of output to input pixel size is
+*     evaluated once for each panel of the piece-wise linear approximation to
+*     the Mapping, and is assumed to be constant for all output pixels in the
+*     panel. The scaling factors for adjacent panels will in general
+*     differ slightly, and so the joints between panels may be visible when
+*     viewing the output image at high contrast. If this is a problem,
+*     reduce the value of the
+c     "tol" parameter
+f     TOL argument
+*     until the difference between adjacent panels is sufficiently small
+*     to be insignificant.
+*
+*     Note, this flag cannot be used in conjunction with the AST__NOBAD
+*     flag (an error will be reported if both flags are specified).
+
+*  Propagation of Missing Data:
+*     Unless the AST__NOBAD flag is specified, instances of missing data
+*     (bad pixels) in the output grid are
+c     identified by occurrences of the "badval" value in the "out"
+f     identified by occurrences of the BADVAL value in the OUT
+*     array. These may be produced if any of the following happen:
+*
+*     - The input position (the transformed position of the output
+*     pixel's centre) lies outside the boundary of the grid of input
+*     pixels.
+*     - The input position lies inside the boundary of a bad input
+*     pixel. In this context, an input pixel is considered bad if its
+c     data value is equal to "badval" and the AST__USEBAD flag is
+c     set via the "flags" parameter.
+f     data value is equal to BADVAL and the AST__USEBAD flag is
+f     set via the FLAGS argument.
+*     (Positions which have half-integral coordinate values, and
+*     therefore lie on a pixel boundary, are regarded as lying within
+*     the pixel with the larger, i.e. more positive, index.)
+*     - The set of neighbouring input pixels (excluding those which
+*     are bad) is unsuitable for calculating an interpolated
+*     value. Whether this is true may depend on the sub-pixel
+*     interpolation scheme in use.
+*     - The interpolated value lies outside the range which can be
+c     represented using the data type of the "out" array.
+f     represented using the data type of the OUT array.
+*
+*     In addition, associated output variance estimates (if
+c     calculated) may be declared bad and flagged with the "badval"
+c     value in the "out_var" array under any of the following
+f     calculated) may be declared bad and flagged with the BADVAL
+f     value in the OUT_VAR array under any of the following
+*     circumstances:
+*
+c     - The associated resampled data value (in the "out" array) is bad.
+f     - The associated resampled data value (in the OUT array) is bad.
+*     - The set of neighbouring input pixels which contributed to the
+*     output data value do not all have valid variance estimates
+*     associated with them. In this context, an input variance
+*     estimate may be regarded as bad either because it has the value
+c     "badval" (and the AST__USEBAD flag is set), or because it is
+f     BADVAL (and the AST__USEBAD flag is set), or because it is
+*     negative.
+*     - The set of neighbouring input pixels for which valid variance
+*     values are available is unsuitable for calculating an overall
+*     variance value. Whether this is true may depend on the sub-pixel
+*     interpolation scheme in use.
+*     - The variance value lies outside the range which can be
+c     represented using the data type of the "out_var" array.
+f     represented using the data type of the OUT_VAR array.
+*
+*     If the AST__NOBAD flag is specified via
+c     parameter "flags",
+f     argument FLAGS,
+*     then output array elements that would otherwise be set to
+c     "badval"
+f     BADVAL
+*     are instead left holding the value they had on entry to this
+*     function. The number of such array elements is returned as
+*     the function value.
+*--
+*/
+/* Define a macro to implement the function for a specific data
+   type. */
+#define MAKE_RESAMPLE(X,Xtype) \
+static int Resample##X( AstMapping *this, int ndim_in, \
+                        const int lbnd_in[], const int ubnd_in[], \
+                        const Xtype in[], const Xtype in_var[], \
+                        int interp, void (* finterp)( void ), \
+                        const double params[], int flags, double tol, \
+                        int maxpix, Xtype badval, \
+                        int ndim_out, const int lbnd_out[], \
+                        const int ubnd_out[], const int lbnd[], \
+                        const int ubnd[], Xtype out[], Xtype out_var[], int *status ) { \
+\
+/* Local Variables: */ \
+   astDECLARE_GLOBALS            /* Thread-specific data */ \
+   AstMapping *simple;           /* Pointer to simplified Mapping */ \
+   int idim;                     /* Loop counter for coordinate dimensions */ \
+   int nin;                      /* Number of Mapping input coordinates */ \
+   int nout;                     /* Number of Mapping output coordinates */ \
+   int npix;                     /* Number of pixels in output region */ \
+   int result;                   /* Result value to return */ \
+   int64_t mpix;                 /* Number of pixels for testing */ \
+\
+/* Initialise. */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Get a pointer to a structure holding thread-specific global data values */ \
+   astGET_GLOBALS(this); \
+\
+/* Obtain values for the Nin and Nout attributes of the Mapping. */ \
+   nin = astGetNin( this ); \
+   nout = astGetNout( this ); \
+\
+/* If OK, check that the number of input grid dimensions matches the \
+   number required by the Mapping and is at least 1. Report an error \
+   if necessary. */ \
+   if ( astOK && ( ( ndim_in != nin ) || ( ndim_in < 1 ) ) ) { \
+      astError( AST__NGDIN, "astResample"#X"(%s): Bad number of input grid " \
+                "dimensions (%d).", status, astGetClass( this ), ndim_in ); \
+      if ( ndim_in != nin ) { \
+         astError( AST__NGDIN, "The %s given requires %d coordinate value%s " \
+                   "to specify an input position.", status, \
+                   astGetClass( this ), nin, ( nin == 1 ) ? "" : "s" ); \
+      } \
+   } \
+\
+/* If OK, also check that the number of output grid dimensions matches \
+   the number required by the Mapping and is at least 1. Report an \
+   error if necessary. */ \
+   if ( astOK && ( ( ndim_out != nout ) || ( ndim_out < 1 ) ) ) { \
+      astError( AST__NGDIN, "astResample"#X"(%s): Bad number of output grid " \
+                "dimensions (%d).", status, astGetClass( this ), ndim_out ); \
+      if ( ndim_out != nout ) { \
+         astError( AST__NGDIN, "The %s given generates %s%d coordinate " \
+                   "value%s for each output position.", status, astGetClass( this ), \
+                   ( nout < ndim_out ) ? "only " : "", nout, \
+                   ( nout == 1 ) ? "" : "s" ); \
+      } \
+   } \
+\
+/* Check that the lower and upper bounds of the input grid are \
+   consistent. Report an error if any pair is not. Also get the number \
+   of pixels in the input grid. */ \
+   mpix = 1; \
+   if ( astOK ) { \
+      for ( idim = 0; idim < ndim_in; idim++ ) { \
+         if ( lbnd_in[ idim ] > ubnd_in[ idim ] ) { \
+            astError( AST__GBDIN, "astResample"#X"(%s): Lower bound of " \
+                      "input grid (%d) exceeds corresponding upper bound " \
+                      "(%d).", status, astGetClass( this ), \
+                      lbnd_in[ idim ], ubnd_in[ idim ] ); \
+            astError( AST__GBDIN, "Error in input dimension %d.", status, \
+                      idim + 1 ); \
+            break; \
+         } else { \
+            mpix *= ubnd_in[ idim ] - lbnd_in[ idim ] + 1; \
+         } \
+      } \
+   } \
+\
+/* Report an error if there are too many pixels in the input. */ \
+   if ( astOK && (int) mpix != mpix ) { \
+      astError( AST__EXSPIX, "astResample"#X"(%s): Supplied input array " \
+                "contains too many pixels (%g): must be fewer than %d.", \
+                status, astGetClass( this ), (double) mpix, INT_MAX ); \
+   } \
+\
+/* Check that the positional accuracy tolerance supplied is valid and \
+   report an error if necessary. */ \
+   if ( astOK && ( tol < 0.0 ) ) { \
+      astError( AST__PATIN, "astResample"#X"(%s): Invalid positional " \
+                "accuracy tolerance (%.*g pixel).", status, \
+                astGetClass( this ), DBL_DIG, tol ); \
+      astError( AST__PATIN, "This value should not be less than zero." , status); \
+   } \
+\
+/* Check that the initial scale size in pixels supplied is valid and \
+   report an error if necessary. */ \
+   if ( astOK && ( maxpix < 0 ) ) { \
+      astError( AST__SSPIN, "astResample"#X"(%s): Invalid initial scale " \
+                "size in pixels (%d).", status, astGetClass( this ), maxpix ); \
+      astError( AST__SSPIN, "This value should not be less than zero." , status); \
+   } \
+\
+/* Check that the lower and upper bounds of the output grid are \
+   consistent. Report an error if any pair is not. Also get the \
+   number of pixels in the output array. */ \
+   mpix = 1; \
+   if ( astOK ) { \
+      for ( idim = 0; idim < ndim_out; idim++ ) { \
+         if ( lbnd_out[ idim ] > ubnd_out[ idim ] ) { \
+            astError( AST__GBDIN, "astResample"#X"(%s): Lower bound of " \
+                      "output grid (%d) exceeds corresponding upper bound " \
+                      "(%d).", status, astGetClass( this ), \
+                      lbnd_out[ idim ], ubnd_out[ idim ] ); \
+            astError( AST__GBDIN, "Error in output dimension %d.", status, \
+                      idim + 1 ); \
+            break; \
+         } else { \
+            mpix *= ubnd_out[ idim ] - lbnd_out[ idim ] + 1; \
+         } \
+      } \
+   } \
+\
+/* Report an error if there are too many pixels in the output. */ \
+   if ( astOK && (int) mpix != mpix ) { \
+      astError( AST__EXSPIX, "astResample"#X"(%s): Supplied output array " \
+                "contains too many pixels (%g): must be fewer than %d.", \
+                status, astGetClass( this ), (double) mpix, INT_MAX ); \
+   } \
+\
+/* Similarly check the bounds of the output region. */ \
+   mpix = 1; \
+   if ( astOK ) { \
+      for ( idim = 0; idim < ndim_out; idim++ ) { \
+         if ( lbnd[ idim ] > ubnd[ idim ] ) { \
+            astError( AST__GBDIN, "astResample"#X"(%s): Lower bound of " \
+                      "output region (%d) exceeds corresponding upper " \
+                      "bound (%d).", status, astGetClass( this ), \
+                      lbnd[ idim ], ubnd[ idim ] ); \
+\
+/* Also check that the output region lies wholly within the output \
+   grid. */ \
+         } else if ( lbnd[ idim ] < lbnd_out[ idim ] ) { \
+            astError( AST__GBDIN, "astResample"#X"(%s): Lower bound of " \
+                      "output region (%d) is less than corresponding " \
+                      "bound of output grid (%d).", status, astGetClass( this ), \
+                      lbnd[ idim ], lbnd_out[ idim ] ); \
+         } else if ( ubnd[ idim ] > ubnd_out[ idim ] ) { \
+            astError( AST__GBDIN, "astResample"#X"(%s): Upper bound of " \
+                      "output region (%d) exceeds corresponding " \
+                      "bound of output grid (%d).", status, astGetClass( this ), \
+                      ubnd[ idim ], ubnd_out[ idim ] ); \
+         } else { \
+            mpix *= ubnd[ idim ] - lbnd[ idim ] + 1; \
+         } \
+\
+/* Say which dimension produced the error. */ \
+         if ( !astOK ) { \
+            astError( AST__GBDIN, "Error in output dimension %d.", status, \
+                      idim + 1 ); \
+            break; \
+         } \
+      } \
+   } \
+\
+/* Report an error if there are too many pixels in the output region. */ \
+   if ( astOK && (int) mpix != mpix ) { \
+      astError( AST__EXSPIX, "astResample"#X"(%s): Supplied output region " \
+                "contains too many pixels (%g): must be fewer than %d.", \
+                status, astGetClass( this ), (double) mpix, INT_MAX ); \
+   } \
+\
+/* If we are conserving flux, check "tol" is not zero. */ \
+   if( ( flags & AST__CONSERVEFLUX ) && astOK ) { \
+      if( tol == 0.0 ) { \
+         astError( AST__CNFLX, "astResample"#X"(%s): Flux conservation was " \
+                   "requested but cannot be performed because zero tolerance " \
+                   "was also specified.", status, astGetClass( this ) ); \
+\
+/* Also check "nin" and "nout" are equal. */ \
+      } else if( nin != nout ) { \
+         astError( AST__CNFLX, "astResample"#X"(%s): Flux conservation was " \
+                "requested but cannot be performed because the Mapping " \
+                "has different numbers of inputs and outputs.", status, \
+                astGetClass( this ) ); \
+      } \
+   } \
+\
+/* If OK, loop to determine how many pixels require resampled values. */ \
+   simple = NULL; \
+   if ( astOK ) { \
+      npix = 1; \
+      for ( idim = 0; idim < ndim_out; idim++ ) { \
+         npix *= ubnd[ idim ] - lbnd[ idim ] + 1; \
+      } \
+\
+/* If there are sufficient pixels to make it worthwhile, simplify the \
+   Mapping supplied to improve performance. Otherwise, just clone the \
+   Mapping pointer. Note we save a pointer to the original Mapping so \
+   that lower-level functions can use it if they need to report an \
+   error. */ \
+      unsimplified_mapping = this; \
+      if ( npix > 1024 ) { \
+         simple = astSimplify( this ); \
+      } else { \
+         simple = astClone( this ); \
+      } \
+   } \
+\
+/* Report an error if the inverse transformation of this simplified \
+   Mapping is not defined. */ \
+   if ( !astGetTranInverse( simple ) && astOK ) { \
+      astError( AST__TRNND, "astResample"#X"(%s): An inverse coordinate " \
+                "transformation is not defined by the %s supplied.", status, \
+                astGetClass( unsimplified_mapping ), \
+                astGetClass( unsimplified_mapping ) ); \
+   } \
+\
+/* Perform the resampling. Note that we pass all gridded data, the \
+   interpolation function and the bad pixel value by means of pointer \
+   types that obscure the underlying data type. This is to avoid \
+   having to replicate functions unnecessarily for each data \
+   type. However, we also pass an argument that identifies the data \
+   type we have obscured. */ \
+   result = ResampleAdaptively( simple, ndim_in, lbnd_in, ubnd_in, \
+                                (const void *) in, (const void *) in_var, \
+                                TYPE_##X, interp, finterp, \
+                                params, flags, tol, maxpix, \
+                                (const void *) &badval, \
+                                ndim_out, lbnd_out, ubnd_out, \
+                                lbnd, ubnd, \
+                                (void *) out, (void *) out_var, status ); \
+\
+/* Annul the pointer to the simplified/cloned Mapping. */ \
+   simple = astAnnul( simple ); \
+\
+/* If an error occurred, clear the returned result. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_RESAMPLE(LD,long double)
+#endif
+MAKE_RESAMPLE(D,double)
+MAKE_RESAMPLE(F,float)
+MAKE_RESAMPLE(L,long int)
+MAKE_RESAMPLE(UL,unsigned long int)
+MAKE_RESAMPLE(K,INT_BIG)
+MAKE_RESAMPLE(UK,UINT_BIG)
+MAKE_RESAMPLE(I,int)
+MAKE_RESAMPLE(UI,unsigned int)
+MAKE_RESAMPLE(S,short int)
+MAKE_RESAMPLE(US,unsigned short int)
+MAKE_RESAMPLE(B,signed char)
+MAKE_RESAMPLE(UB,unsigned char)
+
+/* Undefine the macro. */
+#undef MAKE_RESAMPLE
+
+static int ResampleAdaptively( AstMapping *this, int ndim_in,
+                               const int *lbnd_in, const int *ubnd_in,
+                               const void *in, const void *in_var,
+                               DataType type, int interp, void (* finterp)( void ),
+                               const double *params, int flags, double tol,
+                               int maxpix, const void *badval_ptr,
+                               int ndim_out, const int *lbnd_out,
+                               const int *ubnd_out, const int *lbnd,
+                               const int *ubnd, void *out, void *out_var, int *status ) {
+/*
+*  Name:
+*     ResampleAdaptively
+
+*  Purpose:
+*     Resample a section of a data grid adaptively.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int ResampleAdaptively( AstMapping *this, int ndim_in,
+*                             const int *lbnd_in, const int *ubnd_in,
+*                             const void *in, const void *in_var,
+*                             DataType type, int interp, void (* finterp)( void ),
+*                             const double *params, int flags, double tol,
+*                             int maxpix, const void *badval_ptr,
+*                             int ndim_out, const int *lbnd_out,
+*                             const int *ubnd_out, const int *lbnd,
+*                             const int *ubnd, void *out, void *out_var )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function resamples a rectangular grid of data (with any
+*     number of dimensions) into a specified section of another
+*     rectangular grid (with a possibly different number of
+*     dimensions). The coordinate transformation used to convert
+*     output pixel coordinates into positions in the input grid is
+*     given by the inverse transformation of the Mapping which is
+*     supplied.  Any pixel interpolation scheme may be specified for
+*     interpolating between the pixels of the input grid.
+*
+*     This function is very similar to ResampleWithBlocking and
+*     ResampleSection which lie below it in the calling
+*     hierarchy. However, this function also attempts to adapt to the
+*     Mapping supplied and to sub-divide the section being resampled
+*     into smaller sections within which a linear approximation to the
+*     Mapping may be used.  This reduces the number of Mapping
+*     evaluations, thereby improving efficiency particularly when
+*     complicated Mappings are involved.
+
+*  Parameters:
+*     this
+*        Pointer to a Mapping, whose inverse transformation may be
+*        used to transform the coordinates of pixels in the output
+*        grid into associated positions in the input grid, from which
+*        the output pixel values should be derived (by interpolation
+*        if necessary).
+*
+*        The number of input coordintes for the Mapping (Nin
+*        attribute) should match the value of "ndim_in" (below), and
+*        the number of output coordinates (Nout attribute) should
+*        match the value of "ndim_out".
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input data grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input data grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input data grid, its extent along a
+*        particular (i'th) dimension being (ubnd_in[i] - lbnd_in[i] +
+*        1). They also define the input grid's coordinate system, with
+*        each pixel being of unit extent along each dimension with
+*        integral coordinate values at its centre.
+*     in
+*        Pointer to the input array of data to be resampled (with one
+*        element for each pixel in the input grid). The numerical type
+*        of these data should match the "type" value (below). The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and data type as the "in" array),
+*        which represent estimates of the statistical variance
+*        associated with each element of the "in" array. If this
+*        second array is given (along with the corresponding "out_var"
+*        array), then estimates of the variance of the resampled data
+*        will also be returned.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     type
+*        A value taken from the "DataType" enum, which specifies the
+*        data type of the input and output arrays containing the
+*        gridded data (and variance) values.
+*     interp
+*        A value selected from a set of pre-defined macros to identify
+*        which sub-pixel interpolation algorithm should be used.
+*     finterp
+*        If "interp" is set to a value which requires a user-supplied
+*        function, then a pointer to that function shoild be given
+*        here. Otherwise, this value is not used and may be a NULL
+*        pointer.
+*     params
+*        Pointer to an optional array of parameters that may be passed
+*        to the interpolation algorithm, if required. If no parameters
+*        are required, a NULL pointer should be supplied.
+*     flags
+*        The bitwise OR of a set of flag values which provide
+*        additional control over the resampling operation.
+*     tol
+*        The maximum permitted positional error in transforming output
+*        pixel positions into the input grid in order to resample
+*        it. This should be expressed as a displacement in pixels in
+*        the input grid's coordinate system. If the Mapping's inverse
+*        transformation can be approximated by piecewise linear
+*        functions to this accuracy, then such functions may be used
+*        instead of the Mapping in order to improve
+*        performance. Otherwise, every output pixel position will be
+*        transformed individually using the Mapping.
+*
+*        If linear approximation is not required, a "tol" value of
+*        zero may be given. This will ensure that the Mapping is used
+*        without any approximation.
+*     maxpix
+*        A value which specifies the largest scale size on which to
+*        search for non-linearities in the Mapping supplied. This
+*        value should be expressed as a number of pixels in the output
+*        grid. The function will break the output section specified
+*        into smaller sub-sections (if necessary), each no larger than
+*        "maxpix" pixels in any dimension, before it attempts to
+*        approximate the Mapping by a linear function over each
+*        sub-section.
+*
+*        If the value given is larger than the largest dimension of
+*        the output section (the normal recommendation), the function
+*        will initially search for non-linearity on a scale determined
+*        by the size of the output section.  This is almost always
+*        satisfactory. Very occasionally, however, a Mapping may
+*        appear linear on this scale but nevertheless have smaller
+*        irregularities (e.g. "holes") in it.  In such cases, "maxpix"
+*        may be set to a suitably smaller value so as to ensure this
+*        non-linearity is not overlooked. Typically, a value of 50 to
+*        100 pixels might be suitable and should have little effect on
+*        performance.
+*
+*        If too small a value is given, however, it will have the
+*        effect of preventing linear approximation occurring at all
+*        (equivalent to setting "tol" to zero).  Although this may
+*        degrade performance, accurate results will still be obtained.
+*     badval_ptr
+*        If the AST__USEBAD flag is set (above), this parameter is a
+*        pointer to a value which is used to identify bad data and/or
+*        variance values in the input array(s). The referenced value's
+*        data type must match that of the "in" (and "in_var")
+*        arrays. Unless the AST__NOBAD flag is set, the same value will
+*        also be used to flag any output array elements for which
+*        resampled values could not be obtained.  The output arrays(s)
+*        may be flagged with this value whether or not the AST__USEBAD
+*        flag is set (the function return value indicates whether any
+*        such values have been produced).
+*     ndim_out
+*        The number of dimensions in the output grid. This should be
+*        at least one.
+*     lbnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the output data grid along each dimension.
+*     ubnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the output data grid along each dimension.
+*
+*        Note that "lbnd_out" and "ubnd_out" together define the shape
+*        and size of the output data grid in the same way as "lbnd_in"
+*        and "ubnd_in" define the shape and size of the input grid
+*        (see above).
+*     lbnd
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the first pixel in the
+*        section of the output data grid for which a value is
+*        required.
+*     ubnd
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the last pixel in the
+*        section of the output data grid for which a value is
+*        required.
+*
+*        Note that "lbnd" and "ubnd" define the shape and position of
+*        the section of the output grid for which resampled values are
+*        required. This section should lie wholly within the extent of
+*        the output grid (as defined by the "lbnd_out" and "ubnd_out"
+*        arrays). Regions of the output grid lying outside this section
+*        will not be modified.
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the resampled data will be returned.  The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the resampled values may be returned. This array will only be
+*        used if the "in_var" array has been given.
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+
+*  Returned Value:
+*     The number of output grid points for which no valid output value
+*     could be obtained.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   double *flbnd;                /* Array holding floating point lower bounds */
+   double *fubnd;                /* Array holding floating point upper bounds */
+   double *linear_fit;           /* Pointer to array of fit coefficients */
+   int *hi;                      /* Pointer to array of section upper bounds */
+   int *lo;                      /* Pointer to array of section lower bounds */
+   int coord_out;                /* Loop counter for output coordinates */
+   int dim;                      /* Output section dimension size */
+   int dimx;                     /* Dimension with maximum section extent */
+   int divide;                   /* Sub-divide the output section? */
+   int i;                        /* Loop count */
+   int isLinear;                 /* Is the transformation linear? */
+   int mxdim;                    /* Largest output section dimension size */
+   int npix;                     /* Number of pixels in output section */
+   int npoint;                   /* Number of points for obtaining a fit */
+   int nvertex;                  /* Number of vertices of output section */
+   int result;                   /* Result value to return */
+   int toobig;                   /* Section too big (must sub-divide)? */
+   int toosmall;                 /* Section too small to sub-divide? */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialisation. */
+   npix = 1;
+   mxdim = 0;
+   dimx = 1;
+   nvertex = 1;
+
+/* Loop through the output grid dimensions. */
+   for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+
+/* Obtain the extent in each dimension of the output section which is
+   to receive resampled values, and calculate the total number of
+   pixels it contains. */
+      dim = ubnd[ coord_out ] - lbnd[ coord_out ] + 1;
+      npix *= dim;
+
+/* Find the maximum dimension size of this output section and note
+   which dimension has this size. */
+      if ( dim > mxdim ) {
+         mxdim = dim;
+         dimx = coord_out;
+      }
+
+/* Calculate how many vertices the output section has. */
+      nvertex *= 2;
+   }
+
+/* Calculate how many sample points will be needed (by the
+   astLinearApprox function) to obtain a linear fit to the Mapping's
+   inverse transformation. */
+   npoint = 1 + 4 * ndim_out + 2 * nvertex;
+
+/* If the number of pixels in the output section is not at least 4
+   times this number, we will probably not save significant time by
+   attempting to obtain a linear fit, so note that the output section
+   is too small. */
+   toosmall = ( npix < ( 4 * npoint ) );
+
+/* Note if the maximum dimension of the output section exceeds the
+   user-supplied scale factor. */
+   toobig = ( maxpix < mxdim );
+
+/* Assume the Mapping is significantly non-linear before deciding
+   whether to sub-divide the output section. */
+   linear_fit = NULL;
+
+/* If the output section is too small to be worth obtaining a linear
+   fit, or if the accuracy tolerance is zero, we will not
+   sub-divide. This means that the Mapping will be used to transform
+   each pixel's coordinates and no linear approximation will be
+   used. */
+   if ( toosmall || ( tol == 0.0 ) ) {
+      divide = 0;
+
+/* Otherwise, if the largest output section dimension exceeds the
+   scale length given, we will sub-divide. This offers the possibility
+   of obtaining a linear approximation to the Mapping over a reduced
+   range of output coordinates (which will be handled by a recursive
+   invocation of this function). */
+   } else if ( toobig ) {
+      divide = 1;
+
+/* If neither of the above apply, then attempt to fit a linear
+   approximation to the Mapping's inverse transformation over the
+   range of coordinates covered by the output section. We need to
+   temporarily copy the integer bounds into floating point arrays to
+   use astLinearApprox. */
+   } else {
+
+/* Allocate memory for floating point bounds and for the coefficient array */
+      flbnd = astMalloc( sizeof( double )*(size_t) ndim_out );
+      fubnd = astMalloc( sizeof( double )*(size_t) ndim_out );
+      linear_fit = astMalloc( sizeof( double )*
+                              (size_t) ( ndim_in*( ndim_out + 1 ) ) );
+      if( astOK ) {
+
+/* Copy the bounds into these arrays, and change them so that they refer
+   to the lower and upper edges of the cell rather than the centre. This
+   is essential if one of the axes is spanned by a single cell, since
+   otherwise the upper and lower bounds would be identical. */
+         for( i = 0; i < ndim_out; i++ ) {
+            flbnd[ i ] = (double) lbnd[ i ] - 0.5;
+            fubnd[ i ] = (double) ubnd[ i ] + 0.5;
+         }
+
+/* Get the linear approximation to the inverse transformation. The
+   astLinearApprox function fits the forward transformation so temporarily
+   invert the Mapping in order to get a fit to the inverse transformation. */
+         astInvert( this );
+         isLinear = astLinearApprox( this, flbnd, fubnd, tol, linear_fit );
+         astInvert( this );
+
+/* Free the coeff array if the inverse transformation is not linear. */
+         if( !isLinear ) linear_fit = astFree( linear_fit );
+
+      } else {
+         linear_fit = astFree( linear_fit );
+      }
+
+/* Free resources */
+      flbnd = astFree( flbnd );
+      fubnd = astFree( fubnd );
+
+/* If a linear fit was obtained, we will use it and therefore do not
+   wish to sub-divide further. Otherwise, we sub-divide in the hope
+   that this may result in a linear fit next time. */
+      divide = !linear_fit;
+   }
+
+/* If no sub-division is required, perform resampling (in a
+   memory-efficient manner, since the section we are resampling might
+   still be very large). This will use the linear fit, if obtained
+   above. */
+   if ( astOK ) {
+      if ( !divide ) {
+         result = ResampleWithBlocking( this, linear_fit,
+                                        ndim_in, lbnd_in, ubnd_in,
+                                        in, in_var, type, interp, finterp,
+                                        params, flags, badval_ptr,
+                                        ndim_out, lbnd_out, ubnd_out,
+                                        lbnd, ubnd, out, out_var, status );
+
+/* Otherwise, allocate workspace to perform the sub-division. */
+      } else {
+         lo = astMalloc( sizeof( int ) * (size_t) ndim_out );
+         hi = astMalloc( sizeof( int ) * (size_t) ndim_out );
+         if ( astOK ) {
+
+/* Initialise the bounds of a new output section to match the original
+   output section. */
+            for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+               lo[ coord_out ] = lbnd[ coord_out ];
+               hi[ coord_out ] = ubnd[ coord_out ];
+            }
+
+/* Replace the upper bound of the section's largest dimension with the
+   mid-point of the section along this dimension, rounded
+   downwards. */
+            hi[ dimx ] =
+               (int) floor( 0.5 * (double) ( lbnd[ dimx ] + ubnd[ dimx ] ) );
+
+/* Resample the resulting smaller section using a recursive invocation
+   of this function. */
+            result = ResampleAdaptively( this, ndim_in, lbnd_in, ubnd_in,
+                                         in, in_var, type, interp, finterp,
+                                         params, flags, tol, maxpix,
+                                         badval_ptr, ndim_out,
+                                         lbnd_out, ubnd_out,
+                                         lo, hi, out, out_var, status );
+
+/* Now set up a second section which covers the remaining half of the
+   original output section. */
+            lo[ dimx ] = hi[ dimx ] + 1;
+            hi[ dimx ] = ubnd[ dimx ];
+
+/* If this section contains pixels, resample it in the same way,
+   summing the returned values. */
+            if ( lo[ dimx ] <= hi[ dimx ] ) {
+               result += ResampleAdaptively( this, ndim_in, lbnd_in, ubnd_in,
+                                             in, in_var, type, interp, finterp,
+                                             params, flags, tol, maxpix,
+                                             badval_ptr,  ndim_out,
+                                             lbnd_out, ubnd_out,
+                                             lo, hi, out, out_var, status );
+            }
+         }
+
+/* Free the workspace. */
+         lo = astFree( lo );
+         hi = astFree( hi );
+      }
+   }
+
+/* If coefficients for a linear fit were obtained, then free the space
+   they occupy. */
+   if ( linear_fit ) linear_fit = astFree( linear_fit );
+
+/* If an error occurred, clear the returned result. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int ResampleSection( AstMapping *this, const double *linear_fit,
+                            int ndim_in,
+                            const int *lbnd_in, const int *ubnd_in,
+                            const void *in, const void *in_var,
+                            DataType type, int interp, void (* finterp)( void ),
+                            const double *params, double factor, int flags,
+                            const void *badval_ptr, int ndim_out,
+                            const int *lbnd_out, const int *ubnd_out,
+                            const int *lbnd, const int *ubnd,
+                            void *out, void *out_var, int *status ) {
+/*
+*  Name:
+*     ResampleSection
+
+*  Purpose:
+*     Resample a section of a data grid.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int ResampleSection( AstMapping *this, const double *linear_fit,
+*                          int ndim_in, const int *lbnd_in, const int *ubnd_in,
+*                          const void *in, const void *in_var,
+*                          DataType type, int interp, void (* finterp)( void ),
+*                          const double *params, double factor, int flags,
+*                          const void *badval_ptr, int ndim_out,
+*                          const int *lbnd_out, const int *ubnd_out,
+*                          const int *lbnd, const int *ubnd,
+*                          void *out, void *out_var )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function resamples a rectangular grid of data (with any
+*     number of dimensions) into a specified section of another
+*     rectangular grid (with a possibly different number of
+*     dimensions). The coordinate transformation used is given by the
+*     inverse transformation of the Mapping which is supplied or,
+*     alternatively, by a linear approximation fitted to a Mapping's
+*     inverse transformation. Any pixel interpolation scheme may be
+*     specified for interpolating between the pixels of the input
+*     grid.
+
+*  Parameters:
+*     this
+*        Pointer to a Mapping, whose inverse transformation may be
+*        used to transform the coordinates of pixels in the output
+*        grid into associated positions in the input grid, from which
+*        the output pixel values should be derived (by interpolation
+*        if necessary).
+*
+*        The number of input coordintes for the Mapping (Nin
+*        attribute) should match the value of "ndim_in" (below), and
+*        the number of output coordinates (Nout attribute) should
+*        match the value of "ndim_out".
+*     linear_fit
+*        Pointer to an optional array of double which contains the
+*        coefficients of a linear fit which approximates the above
+*        Mapping's inverse coordinate transformation. If this is
+*        supplied, it will be used in preference to the above Mapping
+*        when transforming coordinates. This may be used to enhance
+*        performance in cases where evaluation of the Mapping's
+*        inverse transformation is expensive. If no linear fit is
+*        available, a NULL pointer should be supplied.
+*
+*        The way in which the fit coefficients are stored in this
+*        array and the number of array elements are as defined by the
+*        astLinearApprox function.
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input data grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input data grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input data grid, its extent along a
+*        particular (i'th) dimension being (ubnd_in[i] - lbnd_in[i] +
+*        1). They also define the input grid's coordinate system, with
+*        each pixel being of unit extent along each dimension with
+*        integral coordinate values at its centre.
+*     in
+*        Pointer to the input array of data to be resampled (with one
+*        element for each pixel in the input grid). The numerical type
+*        of these data should match the "type" value (below). The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and data type as the "in" array),
+*        which represent estimates of the statistical variance
+*        associated with each element of the "in" array. If this
+*        second array is given (along with the corresponding "out_var"
+*        array), then estimates of the variance of the resampled data
+*        will also be returned.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     type
+*        A value taken from the "DataType" enum, which specifies the
+*        data type of the input and output arrays containing the
+*        gridded data (and variance) values.
+*     interp
+*        A value selected from a set of pre-defined macros to identify
+*        which sub-pixel interpolation algorithm should be used.
+*     finterp
+*        If "interp" is set to a value which requires a user-supplied
+*        function, then a pointer to that function shoild be given
+*        here. Otherwise, this value is not used and may be a NULL
+*        pointer.
+*     params
+*        Pointer to an optional array of parameters that may be passed
+*        to the interpolation algorithm, if required. If no parameters
+*        are required, a NULL pointer should be supplied.
+*     factor
+*        A factor by which to scale the resampled output data values before
+*        returning them. If flux is being conserved this should be set to
+*        the ratio of the output pixel size to the input pixel size in the
+*        section. Otherwise it should be set to 1.0.
+*     flags
+*        The bitwise OR of a set of flag values which provide
+*        additional control over the resampling operation.
+*     badval_ptr
+*        If the AST__USEBAD flag is set (above), this parameter is a
+*        pointer to a value which is used to identify bad data and/or
+*        variance values in the input array(s). The referenced value's
+*        data type must match that of the "in" (and "in_var")
+*        arrays. Unless the AST__NOBAD flag is set, the same value will
+*        also be used to flag any output array elements for which
+*        resampled values could not be obtained.  The output arrays(s)
+*        may be flagged with this value whether or not the AST__USEBAD
+*        flag is set (the function return value indicates whether any
+*        such values have been produced).
+*     ndim_out
+*        The number of dimensions in the output grid. This should be
+*        at least one.
+*     lbnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the output data grid along each dimension.
+*     ubnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the output data grid along each dimension.
+*
+*        Note that "lbnd_out" and "ubnd_out" together define the shape
+*        and size of the output data grid in the same way as "lbnd_in"
+*        and "ubnd_in" define the shape and size of the input grid
+*        (see above).
+*     lbnd
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the first pixel in the
+*        section of the output data grid for which a value is
+*        required.
+*     ubnd
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the last pixel in the
+*        section of the output data grid for which a value is
+*        required.
+*
+*        Note that "lbnd" and "ubnd" define the shape and position of
+*        the section of the output grid for which resampled values are
+*        required. This section should lie wholly within the extent of
+*        the output grid (as defined by the "lbnd_out" and "ubnd_out"
+*        arrays). Regions of the output grid lying outside this section
+*        will not be modified.
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the resampled data will be returned.  The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the resampled values may be returned. This array will only be
+*        used if the "in_var" array has been given.
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+
+*  Returned Value:
+*       The number of output grid points for which no valid output value
+*       could be obtained.
+
+*  Notes:
+*     - This function does not take steps to limit memory usage if the
+*     grids supplied are large. To resample large grids in a more
+*     memory-efficient way, the ResampleWithBlocking function should
+*     be used.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific data */
+   AstPointSet *pset_in;         /* Input PointSet for transformation */
+   AstPointSet *pset_out;        /* Output PointSet for transformation */
+   const double *grad;           /* Pointer to gradient matrix of linear fit */
+   const double *par;            /* Pointer to parameter array */
+   const double *zero;           /* Pointer to zero point array of fit */
+   double **ptr_in;              /* Pointer to input PointSet coordinates */
+   double **ptr_out;             /* Pointer to output PointSet coordinates */
+   double *accum;                /* Pointer to array of accumulated sums */
+   double fwhm;                  /* Full width half max. of gaussian */
+   double lpar[ 1 ];             /* Local parameter array */
+   double x1;                    /* Interim x coordinate value */
+   double y1;                    /* Interim y coordinate value */
+   int *dim;                     /* Pointer to array of output pixel indices */
+   int *offset;                  /* Pointer to array of output pixel offsets */
+   int *stride;                  /* Pointer to array of output grid strides */
+   int conserve;                 /* Conserve flux? */
+   int coord_in;                 /* Loop counter for input dimensions */
+   int coord_out;                /* Loop counter for output dimensions */
+   int done;                     /* All pixel indices done? */
+   int i1;                       /* Interim offset into "accum" array */
+   int i2;                       /* Final offset into "accum" array */
+   int idim;                     /* Loop counter for dimensions */
+   int ix;                       /* Loop counter for output x coordinate */
+   int iy;                       /* Loop counter for output y coordinate */
+   int nbad;                     /* Number of pixels assigned a bad value */
+   int neighb;                   /* Number of neighbouring pixels */
+   int npoint;                   /* Number of output points (pixels) */
+   int off1;                     /* Interim pixel offset into output array */
+   int off;                      /* Final pixel offset into output array */
+   int point;                    /* Counter for output points (pixels ) */
+   int result;                   /* Result value to be returned */
+   int s;                        /* Temporary variable for strides */
+   int usevar;                   /* Process variance array? */
+   void (* gifunc)( void );      /* General interpolation function */
+   void (* kernel)( double, const double [], int, double *, int * ); /* Kernel fn. */
+   void (* fkernel)( double, const double [], int, double * ); /* User kernel fn. */
+
+/* Initialise. */
+   result = 0;
+
+/* Get a pointer to a structure holding thread-specific global data values */
+   astGET_GLOBALS(this);
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialisation. */
+   pset_in = NULL;
+   ptr_in = NULL;
+   neighb = 0;
+   gifunc = NULL;
+   kernel = NULL;
+   fkernel = NULL;
+
+/* See if we are conserving flux */
+   conserve = flags & AST__CONSERVEFLUX;
+
+/* If we are conserving flux, then we need some way to tell which output
+   array elements have been assigned a value and which have not. If the
+   AST__NOBAD flag has been specified then this is not possible to report
+   an error. */
+   if( ( flags & AST__NOBAD ) && conserve ) {
+      astError( AST__BADFLG, "astResample: Cannot use the AST__NOBAD and "
+                "AST__CONSERVEFLUX flags together (programming error)." , status);
+   }
+
+/* Calculate the number of output points, as given by the product of
+   the output grid dimensions. */
+   for ( npoint = 1, coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+      npoint *= ubnd[ coord_out ] - lbnd[ coord_out ] + 1;
+   }
+
+/* Allocate workspace. */
+   offset = astMalloc( sizeof( int ) * (size_t) npoint );
+   stride = astMalloc( sizeof( int ) * (size_t) ndim_out );
+   if ( astOK ) {
+
+/* Calculate the stride for each output grid dimension. */
+      off = 0;
+      s = 1;
+      for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+         stride[ coord_out ] = s;
+         s *= ubnd_out[ coord_out ] - lbnd_out[ coord_out ] + 1;
+      }
+
+/* A linear fit to the Mapping is available. */
+/* ========================================= */
+      if ( linear_fit ) {
+
+/* If a linear fit to the Mapping has been provided, then obtain
+   pointers to the array of gradients and zero-points comprising the
+   fit. */
+         grad = linear_fit + ndim_in;
+         zero = linear_fit;
+
+/* Create a PointSet to hold the input grid coordinates and obtain an
+   array of pointers to its coordinate data. */
+         pset_in = astPointSet( npoint, ndim_in, "", status );
+         ptr_in = astGetPoints( pset_in );
+         if ( astOK ) {
+
+/* Initialise the count of output points. */
+            point = 0;
+
+/* Handle the 1-dimensional case optimally. */
+/* ---------------------------------------- */
+            if ( ( ndim_in == 1 ) && ( ndim_out == 1 ) ) {
+
+/* Loop through the pixels of the output grid and transform their x
+   coordinates into the input grid's coordinate system using the
+   linear fit supplied. Store the results in the PointSet created
+   above. */
+               for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                  ptr_in[ 0 ][ point ] = zero[ 0 ] + grad[ 0 ] * (double) ix;
+
+/* Calculate the offset of each pixel within the output array. */
+                  offset[ point ] = ix - lbnd_out[ 0 ];
+                  point++;
+               }
+
+/* Handle the 2-dimensional case optimally. */
+/* ---------------------------------------- */
+            } else if ( ( ndim_in == 2 ) && ( ndim_out == 2 ) ) {
+
+/* Loop through the range of y coordinates in the output grid and
+   calculate interim values of the input coordinates using the linear
+   fit supplied. */
+               for ( iy = lbnd[ 1 ]; iy <= ubnd[ 1 ]; iy++ ) {
+                  x1 = zero[ 0 ] + grad[ 1 ] * (double) iy;
+                  y1 = zero[ 1 ] + grad[ 3 ] * (double) iy;
+
+/* Also calculate an interim pixel offset into the output array. */
+                  off1 = stride[ 1 ] * ( iy - lbnd_out[ 1 ] ) - lbnd_out[ 0 ];
+
+/* Now loop through the range of output x coordinates and calculate
+   the final values of the input coordinates, storing the results in
+   the PointSet created above. */
+                  for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                     ptr_in[ 0 ][ point ] = x1 + grad[ 0 ] * (double) ix;
+                     ptr_in[ 1 ][ point ] = y1 + grad[ 2 ] * (double) ix;
+
+/* Also calculate final pixel offsets into the output array. */
+                     offset[ point ] = off1 + ix;
+                     point++;
+                  }
+               }
+
+/* Handle other numbers of dimensions. */
+/* ----------------------------------- */
+            } else {
+
+/* Allocate workspace. */
+               accum = astMalloc( sizeof( double ) *
+                                 (size_t) ( ndim_in * ndim_out ) );
+               dim = astMalloc( sizeof( int ) * (size_t) ndim_out );
+               if ( astOK ) {
+
+/* Initialise an array of pixel indices for the output grid which
+   refer to the first pixel for which we require a value. Also
+   calculate the offset of this pixel within the output array. */
+                  off = 0;
+                  for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+                     dim[ coord_out ] = lbnd[ coord_out ];
+                     off += stride[ coord_out ] *
+                            ( dim[ coord_out ] - lbnd_out[ coord_out ] );
+                  }
+
+/* To calculate each input grid coordinate we must perform a matrix
+   multiply on the output grid coordinates (using the gradient matrix)
+   and then add the zero points. However, since we will usually only
+   be altering one output coordinate at a time (the least
+   significant), we can avoid the full matrix multiply by accumulating
+   partial sums for the most significant output coordinates and only
+   altering those sums which need to change each time. The zero points
+   never change, so we first fill the "most significant" end of the
+   "accum" array with these. */
+                  for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                     accum[ ( coord_in + 1 ) * ndim_out - 1 ] =
+                                                              zero[ coord_in ];
+                  }
+                  coord_out = ndim_out - 1;
+
+/* Now loop to process each output pixel. */
+                  for ( done = 0; !done; point++ ) {
+
+/* To generate the input coordinate that corresponds to the current
+   output pixel, we work down from the most significant dimension
+   whose index has changed since the previous pixel we considered
+   (given by "coord_out"). For each affected dimension, we accumulate
+   in "accum" the matrix sum (including the zero point) for that
+   dimension and all higher output dimensions. We must accumulate a
+   separate set of sums for each input coordinate we wish to
+   produce. (Note that for the first pixel we process, all dimensions
+   are considered "changed", so we start by initialising the whole
+   "accum" array.) */
+                     for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                        i1 = coord_in * ndim_out;
+                        for ( idim = coord_out; idim >= 1; idim-- ) {
+                           i2 = i1 + idim;
+                           accum[ i2 - 1 ] = accum[ i2 ] +
+                                             dim[ idim ] * grad[ i2 ];
+                        }
+
+/* The input coordinate for each dimension is given by the accumulated
+   sum for output dimension zero (giving the sum over all output
+   dimensions). We do not store this in the "accum" array, but assign
+   the result directly to the coordinate array of the PointSet created
+   earlier. */
+                        ptr_in[ coord_in ][ point ] = accum[ i1 ] +
+                                                      dim[ 0 ] * grad[ i1 ];
+                     }
+
+/* Store the offset of the current pixel in the output array. */
+                     offset[ point ] = off;
+
+/* Now update the array of pixel indices to refer to the next output
+   pixel. */
+                     coord_out = 0;
+                     do {
+
+/* The least significant index which currently has less than its
+   maximum value is incremented by one. The offset into the output
+   array is updated accordingly. */
+                        if ( dim[ coord_out ] < ubnd[ coord_out ] ) {
+                           dim[ coord_out ]++;
+                           off += stride[ coord_out ];
+                           break;
+
+/* Any less significant indices which have reached their maximum value
+   are returned to their minimum value and the output pixel offset is
+   decremented appropriately. */
+                        } else {
+                           dim[ coord_out ] = lbnd[ coord_out ];
+                           off -= stride[ coord_out ] *
+                                  ( ubnd[ coord_out ] - lbnd[ coord_out ] );
+
+/* All the output pixels have been processed once the most significant
+   pixel index has been returned to its minimum value. */
+                           done = ( ++coord_out == ndim_out );
+                        }
+                     } while ( !done );
+                  }
+               }
+
+/* Free the workspace. */
+               accum = astFree( accum );
+               dim = astFree( dim );
+            }
+         }
+
+/* No linear fit to the Mapping is available. */
+/* ========================================== */
+      } else {
+
+/* If flux conseravtion was requested, report an error, since we can only
+   conserve flux if a linear approximation is available. */
+         if( conserve && astOK ) {
+            astError( AST__CNFLX, "astResampleSection(%s): Flux conservation "
+                "was requested but cannot be performed because either the Mapping "
+                "is too non-linear, or the requested tolerance is too small.", status,
+                 astGetClass( this ) );
+         }
+
+/* Create a PointSet to hold the coordinates of the output pixels and
+   obtain a pointer to its coordinate data. */
+         pset_out = astPointSet( npoint, ndim_out, "", status );
+         ptr_out = astGetPoints( pset_out );
+         if ( astOK ) {
+
+/* Initialise the count of output points. */
+            point = 0;
+
+/* Handle the 1-dimensional case optimally. */
+/* ---------------------------------------- */
+            if ( ndim_out == 1 && ndim_in == 1 ) {
+
+/* Loop through the required range of output x coordinates, assigning
+   the coordinate values to the PointSet created above. Also store a
+   pixel offset into the output array. */
+               for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                  ptr_out[ 0 ][ point ] = (double) ix;
+                  offset[ point ] = ix - lbnd_out[ 0 ];
+
+/* Increment the count of output pixels. */
+                  point++;
+               }
+
+/* Handle the 2-dimensional case optimally. */
+/* ---------------------------------------- */
+            } else if ( ndim_out == 2 && ndim_in == 2 ) {
+
+/* Loop through the required range of output y coordinates,
+   calculating an interim pixel offset into the output array. */
+               for ( iy = lbnd[ 1 ]; iy <= ubnd[ 1 ]; iy++ ) {
+                  off1 = stride[ 1 ] * ( iy - lbnd_out[ 1 ] ) - lbnd_out[ 0 ];
+
+/* Loop through the required range of output x coordinates, assigning
+   the coordinate values to the PointSet created above. Also store a
+   final pixel offset into the output array. */
+                  for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                     ptr_out[ 0 ][ point ] = (double) ix;
+                     ptr_out[ 1 ][ point ] = (double) iy;
+                     offset[ point ] = off1 + ix;
+
+/* Increment the count of output pixels. */
+                     point++;
+                  }
+               }
+
+/* Handle other numbers of dimensions. */
+/* ----------------------------------- */
+            } else {
+
+/* Allocate workspace. */
+               dim = astMalloc( sizeof( int ) * (size_t) ndim_out );
+               if ( astOK ) {
+
+/* Initialise an array of pixel indices for the output grid which
+   refer to the first pixel for which we require a value. Also
+   calculate the offset of this pixel within the output array. */
+                  off = 0;
+                  for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+                     dim[ coord_out ] = lbnd[ coord_out ];
+                     off += stride[ coord_out ] *
+                            ( dim[ coord_out ] - lbnd_out[ coord_out ] );
+                  }
+
+/* Loop to generate the coordinates of each output pixel. */
+                  for ( done = 0; !done; point++ ) {
+
+/* Copy each pixel's coordinates into the PointSet created above. */
+                     for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+                        ptr_out[ coord_out ][ point ] =
+                                                     (double) dim[ coord_out ];
+                     }
+
+/* Store the offset of the pixel in the output array. */
+                     offset[ point ] = off;
+
+/* Now update the array of pixel indices to refer to the next output
+   pixel. */
+                     coord_out = 0;
+                     do {
+
+/* The least significant index which currently has less than its
+   maximum value is incremented by one. The offset into the output
+   array is updated accordingly. */
+                        if ( dim[ coord_out ] < ubnd[ coord_out ] ) {
+                           dim[ coord_out ]++;
+                           off += stride[ coord_out ];
+                           break;
+
+/* Any less significant indices which have reached their maximum value
+   are returned to their minimum value and the output pixel offset is
+   decremented appropriately. */
+                        } else {
+                           dim[ coord_out ] = lbnd[ coord_out ];
+                           off -= stride[ coord_out ] *
+                                  ( ubnd[ coord_out ] - lbnd[ coord_out ] );
+
+/* All the output pixels have been processed once the most significant
+   pixel index has been returned to its minimum value. */
+                           done = ( ++coord_out == ndim_out );
+                        }
+                     } while ( !done );
+                  }
+               }
+
+/* Free the workspace. */
+               dim = astFree( dim );
+            }
+
+/* When all the output pixel coordinates have been generated, use the
+   Mapping's inverse transformation to generate the input coordinates
+   from them. Obtain an array of pointers to the resulting coordinate
+   data. */
+            pset_in = astTransform( this, pset_out, 0, NULL );
+            ptr_in = astGetPoints( pset_in );
+         }
+
+/* Annul the PointSet containing the output coordinates. */
+         pset_out = astAnnul( pset_out );
+      }
+   }
+
+/* Resample the input grid. */
+/* ------------------------ */
+/* Determine if a variance array is to be processed. */
+   usevar = ( in_var && out_var );
+
+/* If the input coordinates have been produced successfully, identify
+   the input grid resampling method to be used. */
+   if ( astOK ) {
+
+/* Nearest pixel. */
+/* -------------- */
+      switch ( interp ) {
+         case AST__NEAREST:
+
+/* Define a macro to use a "case" statement to invoke the
+   nearest-pixel interpolation function appropriate to a given data
+   type. */
+#define CASE_NEAREST(X,Xtype) \
+               case ( TYPE_##X ): \
+                  result = \
+                  InterpolateNearest##X( ndim_in, lbnd_in, ubnd_in, \
+                                         (Xtype *) in, (Xtype *) in_var, \
+                                         npoint, offset, \
+                                         (const double *const *) ptr_in, \
+                                         flags, *( (Xtype *) badval_ptr ), \
+                                         (Xtype *) out, (Xtype *) out_var, status ); \
+                  break;
+
+/* Use the above macro to invoke the appropriate function. */
+            switch ( type ) {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+               CASE_NEAREST(LD,long double)
+#endif
+               CASE_NEAREST(D,double)
+               CASE_NEAREST(F,float)
+               CASE_NEAREST(L,long int)
+               CASE_NEAREST(UL,unsigned long int)
+               CASE_NEAREST(K,INT_BIG)
+               CASE_NEAREST(UK,UINT_BIG)
+               CASE_NEAREST(I,int)
+               CASE_NEAREST(UI,unsigned int)
+               CASE_NEAREST(S,short int)
+               CASE_NEAREST(US,unsigned short int)
+               CASE_NEAREST(B,signed char)
+               CASE_NEAREST(UB,unsigned char)
+            }
+            break;
+
+/* Undefine the macro. */
+#undef CASE_NEAREST
+
+/* Linear interpolation. */
+/* --------------------- */
+/* Note this is also the default if zero is given. */
+         case AST__LINEAR:
+         case 0:
+
+/* Define a macro to use a "case" statement to invoke the linear
+   interpolation function appropriate to a given data type. */
+#define CASE_LINEAR(X,Xtype) \
+               case ( TYPE_##X ): \
+                  result = \
+                  InterpolateLinear##X( ndim_in, lbnd_in, ubnd_in,\
+                                        (Xtype *) in, (Xtype *) in_var, \
+                                        npoint, offset, \
+                                        (const double *const *) ptr_in, \
+                                        flags, *( (Xtype *) badval_ptr ), \
+                                        (Xtype *) out, (Xtype *) out_var, status ); \
+                  break;
+
+/* Use the above macro to invoke the appropriate function. */
+            switch ( type ) {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+               CASE_LINEAR(LD,long double)
+#endif
+               CASE_LINEAR(D,double)
+               CASE_LINEAR(F,float)
+               CASE_LINEAR(L,long int)
+               CASE_LINEAR(UL,unsigned long int)
+               CASE_LINEAR(K,INT_BIG)
+               CASE_LINEAR(UK,UINT_BIG)
+               CASE_LINEAR(I,int)
+               CASE_LINEAR(UI,unsigned int)
+               CASE_LINEAR(S,short int)
+               CASE_LINEAR(US,unsigned short int)
+               CASE_LINEAR(B,signed char)
+               CASE_LINEAR(UB,unsigned char)
+            }
+            break;
+
+/* Undefine the macro. */
+#undef CASE_LINEAR
+
+/* Interpolation using a 1-d kernel. */
+/* --------------------------------- */
+         case AST__GAUSS:
+         case AST__SINC:
+         case AST__SINCCOS:
+         case AST__SINCGAUSS:
+         case AST__SINCSINC:
+         case AST__SOMB:
+         case AST__SOMBCOS:
+         case AST__UKERN1:       /* User-supplied 1-d kernel function */
+
+/* Obtain a pointer to the appropriate 1-d kernel function (either
+   internal or user-defined) and set up any parameters it may
+   require. */
+            par = NULL;
+            switch ( interp ) {
+
+/* sinc(pi*x) interpolation. */
+/* ------------------------- */
+/* Assign the kernel function. */
+               case AST__SINC:
+                  kernel = Sinc;
+
+/* Calculate the number of neighbouring pixels to use. */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) {
+                     neighb = 2;
+                  } else {
+                     neighb = MaxI( 1, neighb, status );
+                  }
+                  break;
+
+/* sinc(pi*x)*cos(k*pi*x) interpolation. */
+/* ------------------------------------- */
+/* Assign the kernel function. */
+               case AST__SINCCOS:
+                  kernel = SincCos;
+
+/* Store the required value of "k" in a local parameter array and pass
+   this array to the kernel function. */
+                  lpar[ 0 ] = 0.5 / MaxD( 1.0, params[ 1 ], status );
+                  par = lpar;
+
+/* Obtain the number of neighbouring pixels to use. If this is zero or
+   less, the number will be calculated automatically below. */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) neighb = INT_MAX;
+
+/* Calculate the maximum number of neighbouring pixels required by the
+   width of the kernel, and use this value if preferable. */
+                  neighb = MinI( neighb,
+                                 (int) ceil( MaxD( 1.0, params[ 1 ], status ) ), status );
+                  break;
+
+/* somb(pi*x) interpolation. */
+/* ------------------------- */
+/* Assign the kernel function. */
+               case AST__SOMB:
+                  kernel = Somb;
+
+/* Calculate the number of neighbouring pixels to use. */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) {
+                     neighb = 2;
+                  } else {
+                     neighb = MaxI( 1, neighb, status );
+                  }
+                  break;
+
+/* somb(pi*x)*cos(k*pi*x) interpolation. */
+/* ------------------------------------- */
+/* Assign the kernel function. */
+               case AST__SOMBCOS:
+                  kernel = SombCos;
+
+/* Store the required value of "k" in a local parameter array and pass
+   this array to the kernel function. */
+                  lpar[ 0 ] = 0.5 / MaxD( 1.0, params[ 1 ], status );
+                  par = lpar;
+
+/* Obtain the number of neighbouring pixels to use. If this is zero or
+   less, the number will be calculated automatically below. */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) neighb = INT_MAX;
+
+/* Calculate the maximum number of neighbouring pixels required by the
+   width of the kernel, and use this value if preferable. */
+                  neighb = MinI( neighb,
+                                 (int) ceil( MaxD( 1.0, params[ 1 ], status ) ), status );
+                  break;
+
+/* sinc(pi*x)*exp(-k*x*x) interpolation. */
+/* ------------------------------------- */
+/* Assign the kernel function. */
+               case AST__SINCGAUSS:
+                  kernel = SincGauss;
+
+/* Constrain the full width half maximum of the gaussian factor. */
+                  fwhm = MaxD( 0.1, params[ 1 ], status );
+
+/* Store the required value of "k" in a local parameter array and pass
+   this array to the kernel function. */
+                  lpar[ 0 ] = 4.0 * log( 2.0 ) / ( fwhm * fwhm );
+                  par = lpar;
+
+/* Obtain the number of neighbouring pixels to use. If this is zero or
+   less, use the number of neighbouring pixels required by the width
+   of the kernel (out to where the gaussian term falls to 1% of its
+   peak value). */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) neighb = (int) ceil( sqrt( -log( 0.01 ) /
+                                                                lpar[ 0 ] ) );
+                  break;
+
+/* exp(-k*x*x) interpolation. */
+/* -------------------------- */
+/* Assign the kernel function. */
+               case AST__GAUSS:
+                  kernel = Gauss;
+
+/* Constrain the full width half maximum of the gaussian. */
+                  fwhm = MaxD( 0.1, params[ 1 ], status );
+
+/* Store the required value of "k" in a local parameter array and pass
+   this array to the kernel function. */
+                  lpar[ 0 ] = 4.0 * log( 2.0 ) / ( fwhm * fwhm );
+                  par = lpar;
+
+/* Obtain the number of neighbouring pixels to use. If this is zero or
+   less, use the number of neighbouring pixels required by the width
+   of the kernel (out to where the gaussian term falls to 1% of its
+   peak value). */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) neighb = (int) ceil( sqrt( -log( 0.01 ) /
+                                                                lpar[ 0 ] ) );
+                  break;
+
+/* sinc(pi*x)*sinc(k*pi*x) interpolation. */
+/* -------------------------------------- */
+/* Assign the kernel function. */
+               case AST__SINCSINC:
+                  kernel = SincSinc;
+
+/* Store the required value of "k" in a local parameter array and pass
+   this array to the kernel function. */
+                  lpar[ 0 ] = 0.5 / MaxD( 1.0, params[ 1 ], status );
+                  par = lpar;
+
+/* Obtain the number of neighbouring pixels to use. If this is zero or
+   less, the number will be calculated automatically below. */
+                  neighb = (int) floor( params[ 0 ] + 0.5 );
+                  if ( neighb <= 0 ) neighb = INT_MAX;
+
+/* Calculate the maximum number of neighbouring pixels required by the
+   width of the kernel, and use this value if preferable. */
+                  neighb = MinI( neighb,
+                                 (int) ceil( MaxD( 1.0, params[ 1 ], status ) ), status );
+                  break;
+
+/* User-supplied kernel. */
+/* --------------------- */
+/* Assign the kernel function. */
+               case AST__UKERN1:
+                  fkernel = (void (*)( double, const double [],
+                                       int, double * )) finterp;
+
+/* Calculate the number of neighbouring pixels to use. */
+                  neighb = MaxI( 1, (int) floor( params[ 0 ] + 0.5 ), status );
+
+/* Pass a pointer to the "params" array. */
+                  par = params;
+                  break;
+            }
+
+/* Define a macro to use a "case" statement to invoke the 1-d kernel
+   interpolation function appropriate to a given data type, passing it
+   the pointer to the kernel function obtained above. */
+#define CASE_KERNEL1(X,Xtype) \
+               case ( TYPE_##X ): \
+                  result = \
+                  InterpolateKernel1##X( this, ndim_in, lbnd_in, ubnd_in, \
+                                         (Xtype *) in, (Xtype *) in_var, \
+                                         npoint, offset, \
+                                         (const double *const *) ptr_in, \
+                                         kernel, fkernel, neighb, par, flags, \
+                                         *( (Xtype *) badval_ptr ), \
+                                         (Xtype *) out, (Xtype *) out_var, status ); \
+                  break;
+
+/* Use the above macro to invoke the appropriate function. */
+            switch ( type ) {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+               CASE_KERNEL1(LD,long double)
+#endif
+               CASE_KERNEL1(D,double)
+               CASE_KERNEL1(F,float)
+               CASE_KERNEL1(L,long int)
+               CASE_KERNEL1(UL,unsigned long int)
+               CASE_KERNEL1(K,INT_BIG)
+               CASE_KERNEL1(UK,UINT_BIG)
+               CASE_KERNEL1(I,int)
+               CASE_KERNEL1(UI,unsigned int)
+               CASE_KERNEL1(S,short int)
+               CASE_KERNEL1(US,unsigned short int)
+               CASE_KERNEL1(B,signed char)
+               CASE_KERNEL1(UB,unsigned char)
+            }
+            break;
+
+/* Undefine the macro. */
+#undef CASE_KERNEL1
+
+/* General sub-pixel interpolation function. */
+/* ----------------------------------------- */
+         case AST__BLOCKAVE:
+         case AST__UINTERP:
+
+/* Define a macro to use a "case" statement to invoke the general
+   sub-pixel interpolation function appropriate to a given type and
+   the selected value of the interp variable. */
+#define CASE_GINTERP(X,Xtype) \
+               case ( TYPE_##X ): \
+\
+/* Obtain a pointer to the appropriate general interpolation function \
+   (either internal or user-defined) and set up any parameters it may \
+   require. */ \
+                  switch ( interp ) { \
+\
+/* Block averaging interpolation. */ \
+/* ------------------------------ */ \
+                     case AST__BLOCKAVE: \
+                        gifunc = (void (*)( void )) InterpolateBlockAverage##X; \
+                        break; \
+\
+/* User-supplied sub-pixel interpolation function. */ \
+/* ----------------------------------------------- */ \
+                     case AST__UINTERP: \
+                        gifunc = (void (*)( void )) finterp; \
+                        break; \
+                  } \
+\
+/* Invoke the general interpolation function.  It has to be cast to the \
+   right type (i.e. a function with the correctly typed arguments) \
+   to prevent default promotion (to int or double) of its arguments. \
+   The cast here corresponds to the declaration of
+   ast_resample_uinterp##Xtype. */ \
+                  ( *( (void (*)( int, const int[], const int[], \
+                                  const Xtype[], \
+                                  const Xtype[], \
+                                  int, const int[], \
+                                  const double *const[], \
+                                  const double[], int, \
+                                  Xtype, \
+                                  Xtype *, \
+                                  Xtype *, \
+                                  int * )) \
+                       gifunc ) )( ndim_in, lbnd_in, ubnd_in, \
+                                   (Xtype *) in, \
+                                   (Xtype *) ( usevar ? in_var : NULL ), \
+                                   npoint, offset, \
+                                   (const double *const *) ptr_in, \
+                                   params, flags, \
+                                   *( (Xtype *) badval_ptr ), \
+                                   (Xtype *) out, \
+                                   (Xtype *) ( usevar ? out_var : NULL ), \
+                                   &nbad ); \
+                  if ( astOK ) { \
+                     result += nbad; \
+                  } else { \
+                     astError( astStatus, "astResample"#X"(%s): Error " \
+                               "signalled by user-supplied sub-pixel " \
+                               "interpolation function.", status, \
+                               astGetClass( unsimplified_mapping ) ); \
+                  } \
+                  break;
+
+/* Use the above macro to invoke the function. */
+            switch ( type ) {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+               CASE_GINTERP(LD,long double)
+#endif
+               CASE_GINTERP(D,double)
+               CASE_GINTERP(F,float)
+               CASE_GINTERP(L,long int)
+               CASE_GINTERP(UL,unsigned long int)
+               CASE_GINTERP(K,INT_BIG)
+               CASE_GINTERP(UK,UINT_BIG)
+               CASE_GINTERP(I,int)
+               CASE_GINTERP(UI,unsigned int)
+               CASE_GINTERP(S,short int)
+               CASE_GINTERP(US,unsigned short int)
+               CASE_GINTERP(B,signed char)
+               CASE_GINTERP(UB,unsigned char)
+            }
+            break;
+
+/* Undefine the macro. */
+#undef CASE_GINTERP
+
+/* Error: invalid interpolation scheme specified. */
+/* ---------------------------------------------- */
+         default:
+
+/* Define a macro to report an error message appropriate to a given
+   data type. */
+#define CASE_ERROR(X) \
+            case TYPE_##X: \
+               astError( AST__SISIN, "astResample"#X"(%s): Invalid " \
+                         "sub-pixel interpolation scheme (%d) specified.", status, \
+                         astGetClass( unsimplified_mapping ), interp ); \
+               break;
+
+/* Use the above macro to report an appropriate error message. */
+            switch ( type ) {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+               CASE_ERROR(LD)
+#endif
+               CASE_ERROR(D)
+               CASE_ERROR(F)
+               CASE_ERROR(L)
+               CASE_ERROR(UL)
+               CASE_ERROR(K)
+               CASE_ERROR(UK)
+               CASE_ERROR(I)
+               CASE_ERROR(UI)
+               CASE_ERROR(S)
+               CASE_ERROR(US)
+               CASE_ERROR(B)
+               CASE_ERROR(UB)
+            }
+            break;
+
+/* Undefine the macro. */
+#undef CASE_ERROR
+      }
+   }
+
+/* Now scale the output values to conserve flux if required. */
+   if( conserve ) {
+
+/* Define a macro to use a "case" statement to invoke the function
+   appropriate to a given data type. These simply multiple the output data
+   value by the factor, and the output variance by the square of the
+   factor. */
+#define CASE_CONSERVE(X,Xtype) \
+      case ( TYPE_##X ): \
+         ConserveFlux##X( factor, npoint, offset, *( (Xtype *) badval_ptr ), \
+                          (Xtype *) out, \
+                          (Xtype *) ( usevar ? out_var : NULL ), status ); \
+         break;
+
+/* Use the above macro to invoke the appropriate function. */
+      switch ( type ) {
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+         CASE_CONSERVE(LD,long double)
+#endif
+         CASE_CONSERVE(D,double)
+         CASE_CONSERVE(F,float)
+         CASE_CONSERVE(L,long int)
+         CASE_CONSERVE(UL,unsigned long int)
+         CASE_CONSERVE(K,INT_BIG)
+         CASE_CONSERVE(UK,UINT_BIG)
+         CASE_CONSERVE(I,int)
+         CASE_CONSERVE(UI,unsigned int)
+         CASE_CONSERVE(S,short int)
+         CASE_CONSERVE(US,unsigned short int)
+         CASE_CONSERVE(B,signed char)
+         CASE_CONSERVE(UB,unsigned char)
+      }
+
+/* Undefine the macro. */
+#undef CASE_CONSERVE
+   }
+
+/* Annul the PointSet used to hold input coordinates. */
+   pset_in = astAnnul( pset_in );
+
+/* Free the workspace. */
+   offset = astFree( offset );
+   stride = astFree( stride );
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int ResampleWithBlocking( AstMapping *this, const double *linear_fit,
+                                 int ndim_in,
+                                 const int *lbnd_in, const int *ubnd_in,
+                                 const void *in, const void *in_var,
+                                 DataType type, int interp, void (* finterp)( void ),
+                                 const double *params, int flags,
+                                 const void *badval_ptr, int ndim_out,
+                                 const int *lbnd_out, const int *ubnd_out,
+                                 const int *lbnd, const int *ubnd,
+                                 void *out, void *out_var, int *status ) {
+/*
+*  Name:
+*     ResampleWithBlocking
+
+*  Purpose:
+*     Resample a section of a data grid in a memory-efficient way.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int ResampleWithBlocking( AstMapping *this, const double *linear_fit,
+*                               int ndim_in,
+*                               const int *lbnd_in, const int *ubnd_in,
+*                               const void *in, const void *in_var,
+*                               DataType type, int interp, void (* finterp)( void ),
+*                               const double *params, int flags,
+*                               const void *badval_ptr, int ndim_out,
+*                               const int *lbnd_out, const int *ubnd_out,
+*                               const int *lbnd, const int *ubnd,
+*                               void *out, void *out_var, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function resamples a rectangular grid of data (with any
+*     number of dimensions) into a specified section of another
+*     rectangular grid (with a possibly different number of
+*     dimensions). The coordinate transformation used is given by the
+*     inverse transformation of the Mapping which is supplied or,
+*     alternatively, by a linear approximation fitted to a Mapping's
+*     inverse transformation. Any pixel interpolation scheme may be
+*     specified for interpolating between the pixels of the input
+*     grid.
+*
+*     This function is very similar to ResampleSection, except that in
+*     order to limit memory usage and to ensure locality of reference,
+*     it divides the output grid up into "blocks" which have a limited
+*     extent along each output dimension. Each block, which will not
+*     contain more than a pre-determined maximum number of pixels, is
+*     then passed to ResampleSection for resampling.
+
+*  Parameters:
+*     this
+*        Pointer to a Mapping, whose inverse transformation may be
+*        used to transform the coordinates of pixels in the output
+*        grid into associated positions in the input grid, from which
+*        the output pixel values should be derived (by interpolation
+*        if necessary).
+*
+*        The number of input coordintes for the Mapping (Nin
+*        attribute) should match the value of "ndim_in" (below), and
+*        the number of output coordinates (Nout attribute) should
+*        match the value of "ndim_out".
+*     linear_fit
+*        Pointer to an optional array of double which contains the
+*        coefficients of a linear fit which approximates the above
+*        Mapping's inverse coordinate transformation. If this is
+*        supplied, it will be used in preference to the above Mapping
+*        when transforming coordinates. This may be used to enhance
+*        performance in cases where evaluation of the Mapping's
+*        inverse transformation is expensive. If no linear fit is
+*        available, a NULL pointer should be supplied.
+*
+*        The way in which the fit coefficients are stored in this
+*        array and the number of array elements are as defined by the
+*        astLinearApprox function.
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input data grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input data grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input data grid, its extent along a
+*        particular (i'th) dimension being (ubnd_in[i] - lbnd_in[i] +
+*        1). They also define the input grid's coordinate system, with
+*        each pixel being of unit extent along each dimension with
+*        integral coordinate values at its centre.
+*     in
+*        Pointer to the input array of data to be resampled (with one
+*        element for each pixel in the input grid). The numerical type
+*        of these data should match the "type" value (below). The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and data type as the "in" array),
+*        which represent estimates of the statistical variance
+*        associated with each element of the "in" array. If this
+*        second array is given (along with the corresponding "out_var"
+*        array), then estimates of the variance of the resampled data
+*        will also be returned.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     type
+*        A value taken from the "DataType" enum, which specifies the
+*        data type of the input and output arrays containing the
+*        gridded data (and variance) values.
+*     interp
+*        A value selected from a set of pre-defined macros to identify
+*        which sub-pixel interpolation algorithm should be used.
+*     finterp
+*        If "interp" is set to a value which requires a user-supplied
+*        function, then a pointer to that function shoild be given
+*        here. Otherwise, this value is not used and may be a NULL
+*        pointer.
+*     params
+*        Pointer to an optional array of parameters that may be passed
+*        to the interpolation algorithm, if required. If no parameters
+*        are required, a NULL pointer should be supplied.
+*     flags
+*        The bitwise OR of a set of flag values which provide
+*        additional control over the resampling operation.
+*     badval_ptr
+*        If the AST__USEBAD flag is set (above), this parameter is a
+*        pointer to a value which is used to identify bad data and/or
+*        variance values in the input array(s). The referenced value's
+*        data type must match that of the "in" (and "in_var")
+*        arrays. Unless the AST__NOBAD flag is set, the same value will
+*        also be used to flag any output array elements for which
+*        resampled values could not be obtained.  The output arrays(s)
+*        may be flagged with this value whether or not the AST__USEBAD
+*        flag is set (the function return value indicates whether any
+*        such values have been produced).
+*     ndim_out
+*        The number of dimensions in the output grid. This should be
+*        at least one.
+*     lbnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the output data grid along each dimension.
+*     ubnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the output data grid along each dimension.
+*
+*        Note that "lbnd_out" and "ubnd_out" together define the shape
+*        and size of the output data grid in the same way as "lbnd_in"
+*        and "ubnd_in" define the shape and size of the input grid
+*        (see above).
+*     lbnd
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the first pixel in the
+*        section of the output data grid for which a value is
+*        required.
+*     ubnd
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the last pixel in the
+*        section of the output data grid for which a value is
+*        required.
+*
+*        Note that "lbnd" and "ubnd" define the shape and position of
+*        the section of the output grid for which resampled values are
+*        required. This section should lie wholly within the extent of
+*        the output grid (as defined by the "lbnd_out" and "ubnd_out"
+*        arrays). Regions of the output grid lying outside this section
+*        will not be modified.
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the resampled data will be returned.  The
+*        storage order should be such that the coordinate of the first
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order is used).
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the resampled values may be returned. This array will only be
+*        used if the "in_var" array has been given.
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of output grid points for which no valid output value
+*     could be obtained.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Constants: */
+   const int mxpix = 2 * 1024;   /* Maximum number of pixels in a block (this
+                                    relatively small number seems to give best
+                                    performance) */
+
+/* Local Variables: */
+   double factor;                /* Flux conservation factor */
+   int *dim_block;               /* Pointer to array of block dimensions */
+   int *lbnd_block;              /* Pointer to block lower bound array */
+   int *ubnd_block;              /* Pointer to block upper bound array */
+   int dim;                      /* Dimension size */
+   int done;                     /* All blocks resampled? */
+   int hilim;                    /* Upper limit on maximum block dimension */
+   int idim;                     /* Loop counter for dimensions */
+   int lolim;                    /* Lower limit on maximum block dimension */
+   int mxdim_block;              /* Maximum block dimension */
+   int npix;                     /* Number of pixels in block */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Allocate workspace. */
+   lbnd_block = astMalloc( sizeof( int ) * (size_t) ndim_out );
+   ubnd_block = astMalloc( sizeof( int ) * (size_t) ndim_out );
+   dim_block = astMalloc( sizeof( int ) * (size_t) ndim_out );
+   if ( astOK ) {
+
+/* Find the optimum block size. */
+/* ---------------------------- */
+/* We first need to find the maximum extent which a block of output
+   pixels may have in each dimension. We determine this by taking the
+   output grid extent in each dimension and then limiting the maximum
+   dimension size until the resulting number of pixels is sufficiently
+   small. This approach allows the block shape to approximate (or
+   match) the output grid shape when appropriate. */
+
+/* First loop to calculate the total number of output pixels and the
+   maximum output dimension size. */
+      npix = 1;
+      mxdim_block = 0;
+      for ( idim = 0; idim < ndim_out; idim++ ) {
+         dim = ubnd[ idim ] - lbnd[ idim ] + 1;
+         npix *= dim;
+         if ( mxdim_block < dim ) mxdim_block = dim;
+      }
+
+/* If the number of output pixels is too large for a single block, we
+   perform iterations to determine the optimum upper limit on a
+   block's dimension size. Initialise the limits on this result. */
+      if ( npix > mxpix ) {
+         lolim = 1;
+         hilim = mxdim_block;
+
+/* Loop to perform a binary chop, searching for the best result until
+   the lower and upper limits on the result converge to adjacent
+   values. */
+         while ( ( hilim - lolim ) > 1 ) {
+
+/* Form a new estimate from the mid-point of the previous limits. */
+            mxdim_block = ( hilim + lolim ) / 2;
+
+/* See how many pixels a block contains if its maximum dimension is
+   limited to this new value. */
+            for ( npix = 1, idim = 0; idim < ndim_out ; idim++ ) {
+               dim = ubnd[ idim ] - lbnd[ idim ] + 1;
+               npix *= ( dim < mxdim_block ) ? dim : mxdim_block;
+            }
+
+/* Update the appropriate limit, according to whether the number of
+   pixels is too large or too small. */
+            *( ( npix <= mxpix ) ? &lolim : &hilim ) = mxdim_block;
+         }
+
+/* When iterations have converged, obtain the maximum limit on the
+   dimension size of a block which results in no more than the maximum
+   allowed number of pixels per block. However, ensure that all block
+   dimensions are at least 2. */
+            mxdim_block = lolim;
+      }
+      if ( mxdim_block < 2 ) mxdim_block = 2;
+
+/* Calculate the block dimensions by applying this limit to the output
+   grid dimensions. */
+      for ( idim = 0; idim < ndim_out ; idim++ ) {
+         dim = ubnd[ idim ] - lbnd[ idim ] + 1;
+         dim_block[ idim ] = ( dim < mxdim_block ) ? dim : mxdim_block;
+
+/* Also initialise the lower and upper bounds of the first block of
+   output grid pixels to be resampled, ensuring that this does not
+   extend outside the grid itself. */
+         lbnd_block[ idim ] = lbnd[ idim ];
+         ubnd_block[ idim ] = MinI( lbnd[ idim ] + dim_block[ idim ] - 1,
+                                    ubnd[ idim ], status );
+      }
+
+/* Determine the flux conservation constant if needed. */
+/* --------------------------------------------------- */
+   if( ( flags & AST__CONSERVEFLUX ) && linear_fit ) {
+      factor = MatrixDet( ndim_in, ndim_out, linear_fit + ndim_in, status );
+   } else {
+      factor = 1.0;
+   }
+
+/* Resample each block of output pixels. */
+/* ------------------------------------- */
+/* Loop to generate the extent of each block of output pixels and to
+   resample them. */
+      done = 0;
+      while ( !done && astOK ) {
+
+/* Resample the current block, accumulating the sum of bad pixels
+   produced. */
+         result += ResampleSection( this, linear_fit,
+                                    ndim_in, lbnd_in, ubnd_in,
+                                    in, in_var, type, interp, finterp, params,
+                                    factor, flags, badval_ptr,
+                                    ndim_out, lbnd_out, ubnd_out,
+                                    lbnd_block, ubnd_block, out, out_var, status );
+
+/* Update the block extent to identify the next block of output
+   pixels. */
+         idim = 0;
+         do {
+
+/* We find the least significant dimension where the upper bound of
+   the block has not yet reached the upper bound of the region of the
+   output grid which we are resampling. The block's position is then
+   incremented by one block extent along this dimension, checking that
+   the resulting extent does not go outside the region being
+   resampled. */
+            if ( ubnd_block[ idim ] < ubnd[ idim ] ) {
+               lbnd_block[ idim ] = MinI( lbnd_block[ idim ] +
+                                          dim_block[ idim ], ubnd[ idim ], status );
+               ubnd_block[ idim ] = MinI( lbnd_block[ idim ] +
+                                          dim_block[ idim ] - 1,
+                                          ubnd[ idim ], status );
+               break;
+
+/* If any less significant dimensions are found where the upper bound
+   of the block has reached its maximum value, we reset the block to
+   its lowest position. */
+            } else {
+               lbnd_block[ idim ] = lbnd[ idim ];
+               ubnd_block[ idim ] = MinI( lbnd[ idim ] + dim_block[ idim ] - 1,
+                                          ubnd[ idim ], status );
+
+/* All the blocks have been processed once the position along the most
+   significant dimension has been reset. */
+               done = ( ++idim == ndim_out );
+            }
+         } while ( !done );
+      }
+   }
+
+/* Free the workspace. */
+   lbnd_block = astFree( lbnd_block );
+   ubnd_block = astFree( ubnd_block );
+   dim_block = astFree( dim_block );
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     Mapping member function (over-rides the astSetAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function assigns an attribute value for a Mapping, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstMapping *this;             /* Pointer to the Mapping structure */
+   int invert;                   /* Invert attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+   int report;                   /* Report attribute value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Mapping structure. */
+   this = (AstMapping *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* Invert. */
+/* ------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "invert= %d %n", &invert, &nc ) )
+        && ( nc >= len ) ) {
+      astSetInvert( this, invert );
+
+/* Report. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "report= %d %n", &report, &nc ) )
+        && ( nc >= len ) ) {
+      astSetReport( this, report );
+
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* If the attribute was not recognised, use this macro to report an error
+   if a read-only attribute has been specified. */
+   } else if ( MATCH( "nin" ) ||
+        MATCH( "nout" ) ||
+        MATCH( "islinear" ) ||
+        MATCH( "issimple" ) ||
+        MATCH( "tranforward" ) ||
+        MATCH( "traninverse" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+static void Sinc( double offset, const double params[], int flags,
+                  double *value, int *status ) {
+/*
+*  Name:
+*     Sinc
+
+*  Purpose:
+*     1-dimensional sinc(pi*x) interpolation kernel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void Sinc( double offset, const double params[], int flags,
+*                double *value, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function calculates the value of a 1-dimensional sub-pixel
+*     interpolation kernel. The function used is sinc(pi*x), where
+*     sinc(z)=sin(z)/z.
+
+*  Parameters:
+*     offset
+*        The offset of a pixel from the interpolation point, measured
+*        in pixels.
+*     params
+*        Not used.
+*     flags
+*        Not used.
+*     value
+*        Pointer to a double to receive the calculated kernel value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function does not perform error checking and does not
+*     generate errors.
+*/
+
+/* Local Variables: */
+   static double pi;             /* Value of pi */
+   static int init = 0;          /* Initialisation flag */
+
+/* On the first invocation, initialise a local value for pi. Do this
+   only once. */
+   if ( !init ) {
+      pi = acos( -1.0 );
+      init = 1;
+   }
+
+/* Scale the offset. */
+   offset *= pi;
+
+/* Evaluate the function. */
+   *value = ( offset != 0.0 ) ? ( sin( offset ) / offset ) : 1.0;
+}
+
+static void SincCos( double offset, const double params[], int flags,
+                     double *value, int *status ) {
+/*
+*  Name:
+*     SincCos
+
+*  Purpose:
+*     1-dimensional sinc(pi*x)*cos(k*pi*x) interpolation kernel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void SincCos( double offset, const double params[], int flags,
+*                   double *value, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function calculates the value of a 1-dimensional sub-pixel
+*     interpolation kernel. The function used is sinc(pi*x)*cos(k*pi*x)
+*     out to the point where cos(k*pi*x) = 0, and zero beyond. Here,
+*     sinc(z)=sin(z)/z.
+
+*  Parameters:
+*     offset
+*        The offset of a pixel from the interpolation point, measured
+*        in pixels.
+*     params
+*        The first element of this array should give a value for "k"
+*        in the cos(k*pi*x) term.
+*     flags
+*        Not used.
+*     value
+*        Pointer to a double to receive the calculated kernel value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function does not perform error checking and does not
+*     generate errors.
+*/
+
+/* Local Variables: */
+   double offset_k;              /* Scaled offset */
+   static double halfpi;         /* Value of pi/2 */
+   static double pi;             /* Value of pi */
+   static int init = 0;          /* Initialisation flag */
+
+/* On the first invocation, initialise local values for pi and
+   pi/2. Do this only once. */
+   if ( !init ) {
+      pi = acos( -1.0 );
+      halfpi = 0.5 * pi;
+      init = 1;
+   }
+
+/* Multiply the offset by pi and remove its sign. */
+   offset = pi * fabs( offset );
+
+/* Find the offset scaled by the "k" factor. */
+   offset_k = offset * params[ 0 ];
+
+/* If the cos(k*pi*x) term has not reached zero, calculate the
+   result. */
+   if ( offset_k < halfpi ) {
+      *value = ( ( offset != 0.0 ) ? ( sin( offset ) / offset ) : 1.0 ) *
+               cos( offset_k );
+
+/* Otherwise, the result is zero. */
+   } else {
+      *value = 0.0;
+   }
+}
+
+static void SincGauss( double offset, const double params[], int flags,
+                       double *value, int *status ) {
+/*
+*  Name:
+*     SincGauss
+
+*  Purpose:
+*     1-dimensional sinc(pi*x)*exp(-k*x*x) interpolation kernel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void SincGauss( double offset, const double params[], int flags,
+*                     double *value, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function calculates the value of a 1-dimensional sub-pixel
+*     interpolation kernel. The function used is sinc(pi*x)*exp(-k*x*x),
+*     where sinc(z)=sin(z)/z.
+
+*  Parameters:
+*     offset
+*        The offset of a pixel from the interpolation point, measured
+*        in pixels.
+*     params
+*        The first element of this array should give a value for "k"
+*        in the exp(-k*x*x) term.
+*     flags
+*        Not used.
+*     value
+*        Pointer to a double to receive the calculated kernel value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function does not perform error checking and does not
+*     generate errors.
+*/
+
+/* Local Variables: */
+   double offset_pi;             /* Offset multiplied by pi */
+   static double pi;             /* Value of pi */
+   static int init = 0;          /* Initialisation flag */
+
+/* On the first invocation, initialise a local value for pi. Do this
+   only once. */
+   if ( !init ) {
+      pi = acos( -1.0 );
+      init = 1;
+   }
+
+/* Find the offset scaled by pi. */
+   offset_pi = pi * offset;
+
+/* Calculate the result. */
+   *value = ( ( offset_pi != 0.0 ) ? ( sin( offset_pi ) / offset_pi ) : 1.0 ) *
+            exp( -params[ 0 ] * offset * offset );
+}
+
+static void SincSinc( double offset, const double params[], int flags,
+                      double *value, int *status ) {
+/*
+*  Name:
+*     SincSinc
+
+*  Purpose:
+*     1-dimensional sinc(pi*x)*sinc(k*pi*x) interpolation kernel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void SincSinc( double offset, const double params[], int flags,
+*                    double *value, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function calculates the value of a 1-dimensional sub-pixel
+*     interpolation kernel. The function used is sinc(pi*x)*sinc(k*pi*x),
+*     out to the point where sinc(k*pi*x)=0, and zero beyond. Here,
+*     sinc(z)=sin(z)/z.
+
+*  Parameters:
+*     offset
+*        The offset of a pixel from the interpolation point, measured
+*        in pixels.
+*     params
+*        The first element of this array should give a value for "k"
+*        in the sinc(k*pi*x) term.
+*     flags
+*        Not used.
+*     value
+*        Pointer to a double to receive the calculated kernel value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function does not perform error checking and does not
+*     generate errors.
+*/
+
+/* Local Variables: */
+   double offset_k;              /* Scaled offset */
+   static double halfpi;         /* Value of pi/2 */
+   static double pi;             /* Value of pi */
+   static int init = 0;          /* Initialisation flag */
+
+/* On the first invocation, initialise local values for pi and
+   pi/2. Do this only once. */
+   if ( !init ) {
+      pi = acos( -1.0 );
+      halfpi = 0.5 * pi;
+      init = 1;
+   }
+
+/* Multiply the offset by pi and remove its sign. */
+   offset = pi * fabs( offset );
+
+/* Find the offset scaled by the "k" factor. */
+   offset_k = offset * params[ 0 ];
+
+/* If the sinc(k*pi*x) term has not reached zero, calculate the
+   result. */
+   if ( offset_k < halfpi ) {
+      *value = ( ( offset != 0.0 ) ? ( sin( offset ) / offset ) : 1.0 ) *
+               ( ( offset_k != 0.0 ) ? ( sin( offset_k ) / offset_k ) : 1.0 );
+
+/* Otherwise, the result is zero. */
+   } else {
+      *value = 0.0;
+   }
+}
+
+static AstMapping *Simplify( AstMapping *this, int *status ) {
+/*
+*++
+*  Name:
+c     astSimplify
+f     AST_SIMPLIFY
+
+*  Purpose:
+*     Simplify a Mapping.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     AstMapping *astSimplify( AstMapping *this )
+f     RESULT = AST_SIMPLIFY( THIS, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function simplifies a Mapping (which may be a compound
+*     Mapping such as a CmpMap) to eliminate redundant computational
+*     steps, or to merge separate steps which can be performed more
+*     efficiently in a single operation.
+*
+*     As a simple example, a Mapping which multiplied coordinates by
+*     5, and then multiplied the result by 10, could be simplified to
+*     a single step which multiplied by 50. Similarly, a Mapping which
+*     multiplied by 5, and then divided by 5, could be reduced to a
+*     simple copying operation.
+*
+*     This function should typically be applied to Mappings which have
+*     undergone substantial processing or have been formed by merging
+*     other Mappings. It is of potential benefit, for example, in
+*     reducing execution time if applied before using a Mapping to
+*     transform a large number of coordinates.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the original Mapping.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSimplify()
+f     AST_SIMPLIFY = INTEGER
+*        A new pointer to the (possibly simplified) Mapping.
+
+*  Applicability:
+*     Mapping
+*        This function applies to all Mappings.
+*     FrameSet
+*        If the supplied Mapping is a FrameSet, the returned Mapping
+*        will be a copy of the supplied FrameSet in which all the
+*        inter-Frame Mappings have been simplified.
+
+*  Notes:
+*     - Mappings that have a set value for their Ident attribute are
+*     left unchanged after simplification. This is so that their
+*     individual identity is preserved. This restriction does not
+*     apply to the simplification of Frames.
+*     - This function can safely be applied even to Mappings which
+*     cannot be simplified. If no simplification is possible, it
+c     behaves exactly like astClone and returns a pointer to the
+f     behaves exactly like AST_CLONE and returns a pointer to the
+*     original Mapping.
+*     - The Mapping returned by this function may not be independent
+*     of the original (even if simplification was possible), and
+*     modifying it may therefore result in indirect modification of
+*     the original. If a completely independent result is required, a
+c     copy should be made using astCopy.
+f     copy should be made using AST_COPY.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstMapping **map_list;        /* Pointer to array of Mapping pointers */
+   AstMapping *map;              /* Cloned pointer to nominated Mapping */
+   AstMapping *result;           /* Pointer to result Mapping */
+   int *invert_list;             /* Pointer to array of invert flags */
+   int imap;                     /* Loop counter for Mappings */
+   int modified;                 /* Index of first modified element */
+   int nmap;                     /* Number of Mappings */
+   int simpler;                  /* Simplification achieved? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the inherited status. */
+   if ( !astOK ) return result;
+
+/* Initialise dynamic arrays of Mapping pointers and associated invert
+   flags. */
+   nmap = 0;
+   map_list = NULL;
+   invert_list = NULL;
+
+/* Build a Mapping list to contain this Mapping (the list should only
+   have 1 element). */
+   astMapList( this, 1, astGetInvert( this ), &nmap, &map_list, &invert_list );
+
+/* Pass the list repeatedly to the "astMapMerge" method for
+   simplification. */
+   simpler = 0;
+   while ( astOK ) {
+      map = astClone( map_list[ 0 ] );
+      modified = astMapMerge( map, 0, 1, &nmap, &map_list, &invert_list );
+      map = astAnnul( map );
+
+/* Quit looping if the number of Mappings increases above 1, or if no
+   further change occurs. Note if any simplification was achieved. */
+      if ( ( nmap > 1 ) || ( modified < 0 ) ) break;
+      simpler = 1;
+   }
+
+/* Check whether simplification has occurred. If not, simply clone the
+   original Mapping pointer. This is what will normally happen for
+   Mapping classes which inherit the default (null) "astMapMerge"
+   method from this class and do not define one of their own. */
+   if ( astOK ) {
+      if ( !simpler || ( nmap > 1 ) ) {
+         result = astClone( this );
+
+/* If simplification occurred, test if the resulting Mapping has the
+   Invert attribute value we want. If so, we can simply clone a
+   pointer to it. */
+      } else {
+         if ( invert_list[ 0 ] == astGetInvert( map_list[ 0 ] ) ) {
+            result = astClone( map_list[ 0 ] );
+
+/* If not, we must make a copy. */
+         } else {
+            result = astCopy( map_list[ 0 ] );
+
+/* Either clear the copy's Invert attribute, or set it to 1, as
+   required. */
+            if ( invert_list[ 0 ] ) {
+               astSetInvert( result, 1 );
+            } else {
+               astClearInvert( result );
+            }
+         }
+      }
+   }
+
+/* Loop to annul all the pointers in the Mapping list. */
+   for ( imap = 0; imap < nmap; imap++ ) {
+      map_list[ imap ] = astAnnul( map_list[ imap ] );
+   }
+
+/* Free the dynamic arrays. */
+   map_list = astFree( map_list );
+   invert_list = astFree( invert_list );
+
+/* If an error occurred, annul the returned Mapping. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void Somb( double offset, const double params[], int flags,
+                  double *value, int *status ) {
+/*
+*  Name:
+*     Somb
+
+*  Purpose:
+*     1-dimensional somb(pi*x) interpolation kernel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void Somb( double offset, const double params[], int flags,
+*                double *value, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function calculates the value of a 1-dimensional sub-pixel
+*     interpolation kernel. The function used is somb(pi*x), where
+*     somb(z)=2*J1(z)/z  (J1 is a Bessel function of the first kind of
+*     order 1).
+
+*  Parameters:
+*     offset
+*        The offset of a pixel from the interpolation point, measured
+*        in pixels.
+*     params
+*        Not used.
+*     flags
+*        Not used.
+*     value
+*        Pointer to a double to receive the calculated kernel value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function does not perform error checking and does not
+*     generate errors.
+*/
+
+/* Local Variables: */
+   static double pi;             /* Value of pi */
+   static int init = 0;          /* Initialisation flag */
+
+/* On the first invocation, initialise a local value for pi. Do this
+   only once. */
+   if ( !init ) {
+      pi = acos( -1.0 );
+      init = 1;
+   }
+
+/* Scale the offset. */
+   offset *= pi;
+
+/* Evaluate the function. */
+   *value = ( offset != 0.0 ) ? ( 2.0*J1Bessel( offset, status ) / offset ) : 1.0;
+}
+
+static void SombCos( double offset, const double params[], int flags,
+                     double *value, int *status ) {
+/*
+*  Name:
+*     SombCos
+
+*  Purpose:
+*     1-dimensional somb(pi*x)*cos(k*pi*x) interpolation kernel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void SombCos( double offset, const double params[], int flags,
+*                   double *value, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function calculates the value of a 1-dimensional sub-pixel
+*     interpolation kernel. The function used is somb(pi*x)*cos(k*pi*x)
+*     out to the point where cos(k*pi*x) = 0, and zero beyond. Here,
+*     somb(z)=2*J1(z)/z  (J1 is a Bessel function of the first kind of
+*     order 1).
+
+*  Parameters:
+*     offset
+*        The offset of a pixel from the interpolation point, measured
+*        in pixels.
+*     params
+*        The first element of this array should give a value for "k"
+*        in the cos(k*pi*x) term.
+*     flags
+*        Not used.
+*     value
+*        Pointer to a double to receive the calculated kernel value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function does not perform error checking and does not
+*     generate errors.
+*/
+
+/* Local Variables: */
+   double offset_k;              /* Scaled offset */
+   static double halfpi;         /* Value of pi/2 */
+   static double pi;             /* Value of pi */
+   static int init = 0;          /* Initialisation flag */
+
+/* On the first invocation, initialise local values for pi and
+   pi/2. Do this only once. */
+   if ( !init ) {
+      pi = acos( -1.0 );
+      halfpi = 0.5 * pi;
+      init = 1;
+   }
+
+/* Multiply the offset by pi and remove its sign. */
+   offset = pi * fabs( offset );
+
+/* Find the offset scaled by the "k" factor. */
+   offset_k = offset * params[ 0 ];
+
+/* If the cos(k*pi*x) term has not reached zero, calculate the
+   result. */
+   if ( offset_k < halfpi ) {
+      *value = ( ( offset != 0.0 ) ? ( J1Bessel( offset, status ) / offset ) : 1.0 ) *
+               cos( offset_k );
+
+/* Otherwise, the result is zero. */
+   } else {
+      *value = 0.0;
+   }
+}
+
+static int SpecialBounds( const MapData *mapdata, double *lbnd, double *ubnd,
+                          double xl[], double xu[], int *status ) {
+/*
+*  Name:
+*     SpecialBounds
+
+*  Purpose:
+*     Estimate coordinate bounds using special points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int SpecialBounds( const MapData *mapdata, double *lbnd, double *ubnd,
+*                        double xl[], double xu[], int *status );
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function makes a rough estimate of the lower and upper
+*     bounds of a Mapping function over a constrained region of its
+*     input coordinate space by transforming a set of special test
+*     points. The points used lie at the corners of the constrained
+*     region, at the centre of each of its faces, at its centroid, and
+*     (if within the coordinate constraints) the origin.
+*
+*     In many practical cases, the true extrema may actually lie at
+*     one or other of these points, in which case the true bounds will
+*     be found. In other cases, this function only provides an
+*     approximate limit on each bound (there is no way of telling if
+*     this is the case, however). In either case, having these initial
+*     estimates can speed subsequent searches to find the global
+*     extrema as well as making that search more secure
+
+*  Parameters:
+*     mapdata
+*        Pointer to a MapData structure describing the Mapping
+*        function, its coordinate constraints, etc.
+*     lbnd
+*        Pointer to a double.  On entry, this should contain a
+*        previously-obtained upper limit on the lower bound, or
+*        AST__BAD if no such limit is available. On exit, it will be
+*        updated with a new estimate of the lower bound, if a better
+*        one has been found.
+*     ubnd
+*        Pointer to a double.  On entry, this should contain a
+*        previously-obtained lower limit on the upper bound, or
+*        AST__BAD if no such limit is available. On exit, it will be
+*        updated with a new estimate of the upper bound, if a better
+*        one has been found.
+*     xl
+*        Pointer to an array of double, with one element for each
+*        input coordinate, in which to return the position of a (not
+*        necessarily unique) input point at which the lower output
+*        bound is reached. This array is not altered if an improved
+*        estimate of the lower bound cannot be found.
+*     xu
+*        Pointer to an array of double, with one element for each
+*        input coordinate, in which to return the position of a (not
+*        necessarily unique) input point at which the upper output
+*        bound is reached. This array is not altered if an improved
+*        estimate of the upper bound cannot be found.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned:
+*     A flag indicating if the returned values can be refined.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *pset_in;         /* PointSet for input coordinates */
+   AstPointSet *pset_out;        /* PointSet for output coordinates */
+   double **ptr_in;              /* Pointer to input coordinates */
+   double **ptr_out;             /* Pointer to output coordinates */
+   double *sxl;                  /* Secondary xl values */
+   double *sxu;                  /* Secondary xu values */
+   double f;                     /* Output coordinate value */
+   double slbnd;                 /* Secondary lbnd value */
+   double subnd;                 /* Secondary lbnd value */
+   int *limit;                   /* Workspace for lower/upper limit flags */
+   int bad;                      /* Output coordinate bad? */
+   int coord;                    /* Loop counter for coordinates */
+   int done;                     /* All corners done? */
+   int face;                     /* Loop counter for faces */
+   int ic;                       /* Index of corner */
+   int icen;                     /* Index of centroid point */
+   int ncorner;                  /* Number of corners */
+   int ncoord;                   /* Number of input coordinates */
+   int npoint;                   /* Number of points */
+   int origin;                   /* Origin lies within bounds? */
+   int point;                    /* Loop counter for points */
+   int result;                   /* Returned flag */
+
+/* Initialise */
+   result = 1;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   pset_out = NULL;
+
+/* Obtain the number of coordinate axes and calculate the number of
+   points required in order to place one at every corner of the
+   constrained region of the coordinate space. */
+   ncoord = mapdata->nin;
+   for ( npoint = 1, coord = 0; coord < ncoord; coord++ ) npoint *= 2;
+
+/* Also include a second point at each corner,offset slightly from the
+   corner towards the centroid */
+   ncorner = npoint;
+   npoint *= 2;
+
+/* Also include placing one at the centre of every face and one at the
+   centroid of the constrained coordinate space. */
+   npoint += 2 * ncoord + 1;
+
+/* Determine if the origin lies within the bounds. If so, include it
+   as a further point. */
+   origin = 1;
+   for ( coord = 0; coord < ncoord; coord++ ) {
+      if ( ( mapdata->lbnd[ coord ] > 0.0 ) ||
+           ( mapdata->ubnd[ coord ] < 0.0 ) ) {
+         origin = 0;
+         break;
+      }
+   }
+   if ( origin ) npoint++;
+
+/* Initialise secondary bounds to be the supplied primary bounds */
+   slbnd = *lbnd;
+   subnd = *ubnd;
+
+/* Create workspace for ssecondary xl xu values */
+   sxl = astMalloc( sizeof(double)*(size_t) ncoord );
+   sxu = astMalloc( sizeof(double)*(size_t) ncoord );
+
+/* Create a PointSet to hold the coordinates and obtain a pointer to
+   its coordinate values. Also allocate workspace for calculating the
+   corner coordinates. */
+   pset_in = astPointSet( npoint, ncoord, "", status );
+   ptr_in = astGetPoints( pset_in );
+   limit = astMalloc( sizeof( int ) * (size_t) ncoord );
+   if ( astOK ) {
+
+/* Initialise the workspace. */
+      for ( coord = 0; coord < ncoord; coord++ ) limit[ coord ] = 0;
+
+/* Loop to visit every corner. */
+      point = 0;
+      done = 0;
+      do {
+
+/* At each corner, translate the contents of the "limit" array
+   (containing zeros and ones) into the lower or upper bound on the
+   corresponding axis. This gives the coordinates of the corner, which
+   we store in the input PointSet. */
+         for ( coord = 0; coord < ncoord; coord++ ) {
+            ptr_in[ coord ][ point ] = limit[ coord ] ?
+                                       mapdata->ubnd[ coord ] :
+                                       mapdata->lbnd[ coord ];
+         }
+
+/* Increment the count of points (i.e. corners). */
+         point++;
+
+/* Now update the limit array to identify the next corner. */
+         coord = 0;
+         do {
+
+/* Flip the first zero found to become a one. This gives a new
+   corner. */
+            if ( !limit[ coord ] ) {
+               limit[ coord ] = 1;
+               break;
+
+/* However, first flip any previous ones to become zeros and then
+   examine the next element. We have processed all corners once the
+   array is entirely filled with ones. */
+            } else {
+               limit[ coord ] = 0;
+               done = ( ++coord == ncoord );
+            }
+         } while ( !done );
+      } while ( !done );
+
+/* Once the corners have been processed, loop to consider the centre
+   of each face. */
+      for ( face = 0; face < ( 2 * ncoord ); face++ ) {
+
+/* First calculate the centroid value for each coordinate.  Then set
+   one of these coordinates to the bound where the face lies. */
+         for ( coord = 0; coord < ncoord; coord++ ) {
+            ptr_in[ coord ][ point ] = 0.5 * ( mapdata->lbnd[ coord ] +
+                                               mapdata->ubnd[ coord ] );
+         }
+         ptr_in[ face / 2 ][ point ] = ( face % 2 ) ?
+                                       mapdata->lbnd[ face / 2 ] :
+                                       mapdata->ubnd[ face / 2 ];
+
+/* Increment the count of points. */
+         point++;
+      }
+
+/* Place a point at the centroid of the constrained coordinate
+   space. */
+      for ( coord = 0; coord < ncoord; coord++ ) {
+         ptr_in[ coord ][ point ] = 0.5 * ( mapdata->lbnd[ coord ] +
+                                            mapdata->ubnd[ coord ] );
+      }
+      icen = point++;
+
+/* Add a set of positions which are offset slightly from each corner
+   towards the centroid. */
+      for ( ic = 0; ic < ncorner; ic++ ) {
+         for ( coord = 0; coord < ncoord; coord++ ) {
+            ptr_in[ coord ][ point ] = 0.999*ptr_in[ coord ][ ic ] +
+                                       0.001*ptr_in[ coord ][ icen ];
+         }
+         point++;
+      }
+
+/* Finally, add the origin, if it lies within the constraints. */
+      if ( origin ) {
+         for ( coord = 0; coord < ncoord; coord++ ) {
+            ptr_in[ coord ][ point ] = 0.0;
+         }
+      }
+
+/* Once all the input coordinates have been calculated, transform them
+   and obtain a pointer to the resulting coordinate values. */
+      pset_out = astTransform( mapdata->mapping, pset_in, mapdata->forward,
+                               NULL );
+      ptr_out = astGetPoints( pset_out );
+      if ( astOK ) {
+
+/* Loop through each point and test if any of its transformed
+   coordinates is bad. */
+         for ( point = 0; point < npoint; point++ ) {
+            bad = 0;
+            for ( coord = 0; coord < mapdata->nout; coord++ ) {
+               if ( ptr_out[ coord ][ point ] == AST__BAD ) {
+                  bad = 1;
+                  break;
+               }
+            }
+
+/* If so, we ignore the point. Otherwise, extract the required
+   coordinate. */
+            f = ptr_out[ mapdata->coord ][ point ];
+            if ( !bad ) {
+
+/* Use this to update the lower and upper bounds we are seeking. If
+   either bound is updated, also store the coordinates of the
+   corresponding input point. */
+               if ( ( *lbnd == AST__BAD ) || ( f < *lbnd ) ) {
+                  *lbnd = f;
+                  for ( coord = 0; coord < ncoord; coord++ ) {
+                     xl[ coord ] = ptr_in[ coord ][ point ];
+                  }
+               }
+               if ( ( *ubnd == AST__BAD ) || ( f > *ubnd ) ) {
+                  *ubnd = f;
+                  for ( coord = 0; coord < ncoord; coord++ ) {
+                     xu[ coord ] = ptr_in[ coord ][ point ];
+                  }
+               }
+
+/* If this point has a bad coord value, it may still be useful if the
+   required coord value is not bad. In this case, extract the required
+   coordinate. */
+            } else if ( f != AST__BAD ) {
+
+/* Use this to update secondary lower and upper bounds we are seeking.
+   These will be returned if no primary values are found via the previous
+   code block. */
+               if ( ( slbnd == AST__BAD ) || ( f < slbnd ) ) {
+                  slbnd = f;
+                  for ( coord = 0; coord < ncoord; coord++ ) {
+                     sxl[ coord ] = ptr_in[ coord ][ point ];
+                  }
+               }
+               if ( ( subnd == AST__BAD ) || ( f > subnd ) ) {
+                  subnd = f;
+                  for ( coord = 0; coord < ncoord; coord++ ) {
+                     sxu[ coord ] = ptr_in[ coord ][ point ];
+                  }
+               }
+            }
+         }
+
+/* If no primary values could be found, use secondary values. */
+         if( *lbnd == AST__BAD && *ubnd == AST__BAD ) {
+            *lbnd = slbnd;
+            *ubnd = subnd;
+            for ( coord = 0; coord < ncoord; coord++ ) {
+               xu[ coord ] = sxu[ coord ];
+               xl[ coord ] = sxl[ coord ];
+            }
+            result = ( slbnd == AST__BAD || subnd == AST__BAD );
+         }
+      }
+   }
+
+/* Free workspace */
+   sxl = astFree( sxl );
+   sxu = astFree( sxu );
+
+/* Annul the temporary PointSets and free the workspace. */
+   pset_in = astAnnul( pset_in );
+   pset_out = astAnnul( pset_out );
+   limit = astFree( limit );
+
+   return result;
+}
+
+/*
+*  Name:
+*     SpreadKernel1<X>
+
+*  Purpose:
+*     Rebin a data grid, using a 1-d interpolation kernel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void SpreadKernel1<X>( AstMapping *this, int ndim_out,
+*                           const int *lbnd_out, const int *ubnd_out,
+*                           const <Xtype> *in, const <Xtype> *in_var,
+*                           double infac, int npoint, const int *offset,
+*                           const double *const *coords,
+*                           void (* kernel)( double, const double [], int,
+*                                            double *, int * ),
+*                           int neighb, const double *params, int flags,
+*                           <Xtype> badval, int npix_out, <Xtype> *out,
+*                           <Xtype> *out_var, double *work, int64_t *nused,
+*                           int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This is a set of functions which rebins a rectangular region of an
+*     input grid of data (and, optionally, associated statistical variance
+*     values) so as to place them into a new output grid. Each input
+*     grid point may be mapped on to a position in the output grid in
+*     an arbitrary way. The input and output grids may have any number
+*     of dimensions, not necessarily equal.
+*
+*     Where the input positions given do not correspond with a pixel centre
+*     in the output grid, the each input pixel value is spread out between the
+*     surrounding output pixels using weights determined by a separable kernel
+*     which is the product of a 1-dimensional kernel function evaluated along
+*     each output dimension. A pointer should be supplied to the 1-dimensional
+*     kernel function to be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping being used in the rebinning operation
+*        (this is only used for constructing error messages).
+*     ndim_out
+*        The number of dimensions in the output grid. This should be at
+*        least one.
+*     lbnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the output grid along each dimension.
+*     ubnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the output grid along each dimension.
+*
+*        Note that "lbnd_out" and "ubnd_out" together define the shape
+*        and size of the output grid, its extent along a particular
+*        (i'th) dimension being ubnd_out[i]-lbnd_out[i]+1 (assuming "i"
+*        is zero-based). They also define the output grid's coordinate
+*        system, with each pixel being of unit extent along each
+*        dimension with integral coordinate values at its centre.
+*     in
+*        Pointer to the array of data to be rebinned. The numerical type
+*        of these data should match the function used, as given by the
+*        suffix on the function name. Note that details of how the input
+*        grid maps on to this array (e.g. the storage order, number of
+*        dimensions, etc.) is arbitrary and is specified entirely by means
+*        of the "offset" array. The "in" array should therefore contain
+*        sufficient elements to accommodate the "offset" values supplied.
+*        There is no requirement that all elements of the "in" array
+*        should be rebinned, and any which are not addressed by the
+*        contents of the "offset" array will be ignored.
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and type as the "in" array), which
+*        represent estimates of the statistical variance associated
+*        with each element of the "in" array. If this second array is
+*        given (along with the corresponding "out_var" array), then
+*        estimates of the variance of the resampled data will also be
+*        returned. It is addressed in exactly the same way (via the
+*        "offset" array) as the "in" array.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     infac
+*        A factor by which to multiply the input data values before use.
+*     npoint
+*        The number of input points which are to be rebinned.
+*     offset
+*        Pointer to an array of integers with "npoint" elements. For
+*        each input point, this array should contain the zero-based
+*        offset in the input array(s) (i.e. the "in" and, optionally,
+*        the "in_var" arrays) from which the value to be rebinned should
+*        be obtained.
+*     coords
+*        An array of pointers to double, with "ndim_out" elements.
+*        Element "coords[coord]" should point at the first element of
+*        an array of double (with "npoint" elements) which contains the
+*        values of coordinate number "coord" for each point being
+*        rebinned. The value of coordinate number "coord" for
+*        rebinning point number "point" is therefore given by
+*        "coords[coord][point]" (assuming both indices are
+*        zero-based).  If any point has a coordinate value of AST__BAD
+*        associated with it, then the corresponding input data (and
+*        variance) value will be ignored.
+*     kernel
+*        Pointer to the 1-dimensional kernel function to be used.
+*     neighb
+*        The number of neighbouring pixels in each dimension (on each
+*        side of the interpolation position) which are to receive
+*        contributions from the input pixel value. This value should be at
+*        least 1.
+*     params
+*        Pointer to an optional array of parameter values to be passed
+*        to the kernel function. If no parameters are required by this
+*        function, then a NULL pointer may be supplied.
+*     flags
+*        The bitwise OR of a set of flag values which control the
+*        operation of the function. These are chosend from:
+*
+*        - AST__USEBAD: indicates whether there are "bad" (i.e. missing) data
+*        in the input array(s) which must be recognised.  If this flag is not
+*        set, all input values are treated literally.
+*        - AST__GENVAR: Indicates that output variances should be generated
+*        from the spread of values contributing to each output pixel.
+*        - AST__USEVAR: Indicates that output variances should be generated
+*        by rebinning the input variances.
+*        - AST__VARWGT: Indicates that input variances should be used to
+*        create weights for the input data values.
+*
+*        Only one of AST__GENVAR and AST__USEVAR should be supplied.
+*     badval
+*        If the AST__USEBAD flag is set in the "flags" value (above),
+*        this parameter specifies the value which is used to identify
+*        bad data and/or variance values in the input array(s). Its
+*        numerical type must match that of the "in" (and "in_var")
+*        arrays. The same value will also be used to flag any output
+*        array elements for which resampled values could not be
+*        obtained.  The output arrays(s) may be flagged with this
+*        value whether or not the AST__USEBAD flag is set (the
+*        function return value indicates whether any such values have
+*        been produced).
+*     npix_out
+*        Number of pixels in output array.
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the rebinned data will be returned. The
+*        storage order should be such that the index of the first grid
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order).
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the rebinned values may be returned. This array will only be
+*        used if the "in_var" array has been given. The values returned
+*        are estimates of the statistical variance of the corresponding
+*        values in the "out" array, on the assumption that all errors in
+*        input grid values (in the "in" array) are statistically independent
+*        and that their variance estimates (in the "in_var" array) may
+*        simply be summed (with appropriate weighting factors).
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+*     work
+*        A pointer to an array with the same data type and size as the "out"
+*        array which is used as work space. The values in the supplied
+*        array are incremented on exit by the sum of the weights used
+*        with each output pixel.
+*     nused
+*        An optional pointer to a int64_t which will be incremented by the
+*        number of input values pasted into the output array. Ignored if NULL.
+
+*  Notes:
+*     - There is a separate function for each numerical type of
+*     gridded data, distinguished by replacing the <X> in the function
+*     name by the appropriate 1- or 2-character suffix.
+*/
+/* Define macros to implement the function for a specific data
+   type. */
+#define MAKE_SPREAD_KERNEL1(X,Xtype,IntType) \
+static void SpreadKernel1##X( AstMapping *this, int ndim_out, \
+                              const int *lbnd_out, const int *ubnd_out, \
+                              const Xtype *in, const Xtype *in_var, \
+                              double infac, int npoint, const int *offset, \
+                              const double *const *coords, \
+                              void (* kernel)( double, const double [], \
+                                               int, double *, int * ), \
+                              int neighb, const double *params, \
+                              int flags, Xtype badval, int npix_out, \
+                              Xtype *out, Xtype *out_var, double *work, \
+                              int64_t *nused, int *status ) { \
+\
+/* Local Variables: */ \
+   astDECLARE_GLOBALS            /* Thread-specific data */ \
+   Xtype c; \
+   Xtype in_val;                 /* Input pixel value */ \
+   double **wtptr;               /* Pointer to array of weight pointers */ \
+   double **wtptr_last;          /* Array of highest weight pointer values */ \
+   double *filter;               /* Pointer to Nd array of filter values */ \
+   double *kp;                   /* Pointer to next weight values */ \
+   double *kstart;               /* Pointer to next kernel value */ \
+   double *kval;                 /* Pointer to 1d array of kernel values */ \
+   double *wtprod;               /* Accumulated weight value array pointer */ \
+   double *xfilter;              /* Pointer to 1d array of x axis filter values */ \
+   double *xnl;                  /* Pointer to previous ofset array (n-d) */ \
+   double pfac;                  /* Input weight with extra supplied factor */ \
+   double pixwt;                 /* Weight to apply to individual pixel */ \
+   double sum;                   /* Sum of all filter values */ \
+   double wgt;                   /* Weight for input value */ \
+   double x;                     /* x coordinate value */ \
+   double xn;                    /* Coordinate value (n-d) */ \
+   double xx;                    /* X offset */ \
+   double xxl;                   /* Previous X offset */ \
+   double xxn; \
+   double y;                     /* y coordinate value */ \
+   double yy;                    /* Y offset */ \
+   double yyl;                   /* Previous Y offset */ \
+   int *hi;                      /* Pointer to array of upper indices */ \
+   int *jhi;                     /* Pointer to array of filter upper indices */ \
+   int *jlo;                     /* Pointer to array of filter lower indices */ \
+   int *lo;                      /* Pointer to array of lower indices */ \
+   int *stride;                  /* Pointer to array of dimension strides */ \
+   int bad;                      /* Output pixel bad? */ \
+   int done;                     /* All pixel indices done? */ \
+   int genvar;                   /* Generate output variances? */ \
+   int hi_ix;                    /* Upper output pixel index (x dimension) */ \
+   int hi_iy;                    /* Upper output pixel index (y dimension) */ \
+   int hi_jx;                    /* Upper filter pixel index (x dimension) */ \
+   int hi_jy;                    /* Upper filter pixel index (y dimension) */ \
+   int idim;                     /* Loop counter for dimensions */ \
+   int ii;                       /* Loop counter for dimensions */ \
+   int ix;                       /* Pixel index in output grid x dimension */ \
+   int iy;                       /* Pixel index in output grid y dimension */ \
+   int jjx;                      /* Reflected pixel index in filter grid x dimension */ \
+   int jjy;                      /* Reflected pixel index in filter grid y dimension */ \
+   int jx;                       /* Pixel index in filter grid x dimension */ \
+   int jxn; \
+   int jy;                       /* Pixel index in filter grid y dimension */ \
+   int kerror;                   /* Error signalled by kernel function? */ \
+   int lo_ix;                    /* Lower output pixel index (x dimension) */ \
+   int lo_iy;                    /* Lower output pixel index (y dimension) */ \
+   int lo_jx;                    /* Lower filter pixel index (x dimension) */ \
+   int lo_jy;                    /* Lower filter pixel index (y dimension) */ \
+   int nb2;                      /* The total number of neighbouring pixels */ \
+   int nf;                       /* Number of pixels in filter array */ \
+   int nwx;                      /* Used X width of kernel function (*2) */ \
+   int nwy;                      /* Used Y width of kernel function (*2) */ \
+   int off1;                     /* Input pixel offset due to y index */ \
+   int off_in;                   /* Offset to input pixel */ \
+   int off_out;                  /* Offset to output pixel */ \
+   int off_xedge;                /* Does filter box overlap array edge on the X axis? */ \
+   int off_yedge;                /* Does filter box overlap array edge on the Y axis? */ \
+   int point;                    /* Loop counter for output points */ \
+   int s;                        /* Temporary variable for strides */ \
+   int usebad;                   /* Use "bad" input pixel values? */ \
+   int usevar;                   /* Process variance array? */ \
+   int varwgt;                   /* Use input variances as weights? */ \
+   int ystride;                  /* Stride along input grid y dimension */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Get a pointer to a structure holding thread-specific global data values */ \
+   astGET_GLOBALS(this); \
+\
+/* Further initialisation. */ \
+   kerror = 0; \
+   sum = 0.0; \
+   bad = 0; \
+\
+/* Find the total number of pixels in the filter used to spread a single \
+   input pixel into the output image. */ \
+   nb2 = 2*neighb; \
+   nf = 1; \
+   for ( idim = 0; idim < ndim_out; idim++ ) nf *= nb2; \
+\
+/* Allocate workspace to hold the filter values. */ \
+   filter = astMalloc( sizeof( double ) * (size_t) nf ); \
+   if ( astOK ) { \
+\
+/* Determine if we are processing bad pixels or variances. */ \
+      usebad = flags & AST__USEBAD; \
+      usevar = 0; \
+      genvar = 0; \
+      if( flags & AST__GENVAR ) { \
+         genvar = out_var && work; \
+      } else if( flags & AST__USEVAR ) { \
+         usevar = in_var && out_var; \
+      } \
+      varwgt = ( flags & AST__VARWGT ) && in_var && work; \
+\
+/* Handle the 1-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+      if ( ndim_out == 1 ) { \
+\
+/* Identify eight cases, according to whether bad pixels and/or variances \
+   are being processed and/or used. In each case we assign constant values \
+   (0 or 1) to the "Usebad", "Usevar" and "Varwgt" flags so that code for \
+   handling bad pixels and variances can be eliminated by the compiler's \
+   optimisation system when not required. */ \
+         if( varwgt ) { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  KERNEL_1D(X,Xtype,1,1,0,IntType,1) \
+               } else if ( genvar ) { \
+                  KERNEL_1D(X,Xtype,1,0,1,IntType,1) \
+               } else { \
+                  KERNEL_1D(X,Xtype,1,0,0,IntType,1) \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  KERNEL_1D(X,Xtype,0,1,0,IntType,1) \
+               } else if ( genvar ) { \
+                  KERNEL_1D(X,Xtype,0,0,1,IntType,1) \
+               } else { \
+                  KERNEL_1D(X,Xtype,0,0,0,IntType,1) \
+               } \
+            } \
+         } else { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  KERNEL_1D(X,Xtype,1,1,0,IntType,0) \
+               } else if ( genvar ) { \
+                  KERNEL_1D(X,Xtype,1,0,1,IntType,0) \
+               } else { \
+                  KERNEL_1D(X,Xtype,1,0,0,IntType,0) \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  KERNEL_1D(X,Xtype,0,1,0,IntType,0) \
+               } else if ( genvar ) { \
+                  KERNEL_1D(X,Xtype,0,0,1,IntType,0) \
+               } else { \
+                  KERNEL_1D(X,Xtype,0,0,0,IntType,0) \
+               } \
+            } \
+         } \
+\
+/* Exit point on error in kernel function */ \
+         Kernel_SError_1d: ; \
+\
+/* Handle the 2-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+      } else if ( ndim_out == 2 ) { \
+\
+/* Allocate workspace to hold the X axis filter values. */ \
+         xfilter = astMalloc( sizeof( double ) * (size_t) nb2 ); \
+\
+/* Calculate the stride along the y dimension of the output grid. */ \
+         ystride = ubnd_out[ 0 ] - lbnd_out[ 0 ] + 1; \
+\
+/* Identify eight cases, according to whether bad pixels and/or variances \
+   are being processed and/or used. In each case we assign constant values \
+   (0 or 1) to the "Usebad", "Usevar" and "Varwgt" flags so that code for \
+   handling bad pixels and variances can be eliminated by the compiler's \
+   optimisation system when not required. */ \
+         if( varwgt ) { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  KERNEL_2D(X,Xtype,1,1,0,IntType,1) \
+               } else if ( genvar ) { \
+                  KERNEL_2D(X,Xtype,1,0,1,IntType,1) \
+               } else { \
+                  KERNEL_2D(X,Xtype,1,0,0,IntType,1) \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  KERNEL_2D(X,Xtype,0,1,0,IntType,1) \
+               } else if ( genvar ) { \
+                  KERNEL_2D(X,Xtype,0,0,1,IntType,1) \
+               } else { \
+                  KERNEL_2D(X,Xtype,0,0,0,IntType,1) \
+               } \
+            } \
+         } else { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  KERNEL_2D(X,Xtype,1,1,0,IntType,0) \
+               } else if ( genvar ) { \
+                  KERNEL_2D(X,Xtype,1,0,1,IntType,0) \
+               } else { \
+                  KERNEL_2D(X,Xtype,1,0,0,IntType,0) \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  KERNEL_2D(X,Xtype,0,1,0,IntType,0) \
+               } else if ( genvar ) { \
+                  KERNEL_2D(X,Xtype,0,0,1,IntType,0) \
+               } else { \
+                  KERNEL_2D(X,Xtype,0,0,0,IntType,0) \
+               } \
+            } \
+         } \
+\
+/* Free work space */ \
+         xfilter = astFree( xfilter ); \
+\
+/* Exit point on error in kernel function */ \
+         Kernel_SError_2d: ; \
+\
+/* Handle other numbers of dimensions. */ \
+/* ----------------------------------- */ \
+      } else { \
+\
+/* Allocate workspace. */ \
+         hi = astMalloc( sizeof( int ) * (size_t) ndim_out ); \
+         lo = astMalloc( sizeof( int ) * (size_t) ndim_out ); \
+         jhi = astMalloc( sizeof( int ) * (size_t) ndim_out ); \
+         jlo = astMalloc( sizeof( int ) * (size_t) ndim_out ); \
+         stride = astMalloc( sizeof( int ) * (size_t) ndim_out ); \
+         xnl = astMalloc( sizeof( double ) * (size_t) ndim_out ); \
+         kval = astMalloc( sizeof( double ) * (size_t) \
+                                           ( nb2 * ndim_out ) ); \
+         wtprod = astMalloc( sizeof( double ) * (size_t) ndim_out ); \
+         wtptr = astMalloc( sizeof( double * ) * (size_t) ndim_out ); \
+         wtptr_last = astMalloc( sizeof( double * ) * (size_t) ndim_out ); \
+         if ( astOK ) { \
+\
+/* Calculate the stride along each dimension of the output grid. */ \
+            for ( s = 1, idim = 0; idim < ndim_out; idim++ ) { \
+               stride[ idim ] = s; \
+               s *= ubnd_out[ idim ] - lbnd_out[ idim ] + 1; \
+               xnl[ idim ] = AST__BAD; \
+            } \
+\
+/* Identify eight cases, according to whether bad pixels and/or variances \
+   are being processed and/or used. In each case we assign constant values \
+   (0 or 1) to the "Usebad", "Usevar" and "Varwgt" flags so that code for \
+   handling bad pixels and variances can be eliminated by the compiler's \
+   optimisation system when not required. */ \
+            if( varwgt ) { \
+               if ( usebad ) { \
+                  if ( usevar ) { \
+                     KERNEL_ND(X,Xtype,1,1,0,IntType,1) \
+                  } else if ( genvar ) { \
+                     KERNEL_ND(X,Xtype,1,0,1,IntType,1) \
+                  } else { \
+                     KERNEL_ND(X,Xtype,1,0,0,IntType,1) \
+                  } \
+               } else { \
+                  if ( usevar ) { \
+                     KERNEL_ND(X,Xtype,0,1,0,IntType,1) \
+                  } else if ( genvar ) { \
+                     KERNEL_ND(X,Xtype,0,0,1,IntType,1) \
+                  } else { \
+                     KERNEL_ND(X,Xtype,0,0,0,IntType,1) \
+                  } \
+               } \
+            } else { \
+               if ( usebad ) { \
+                  if ( usevar ) { \
+                     KERNEL_ND(X,Xtype,1,1,0,IntType,0) \
+                  } else if ( genvar ) { \
+                     KERNEL_ND(X,Xtype,1,0,1,IntType,0) \
+                  } else { \
+                     KERNEL_ND(X,Xtype,1,0,0,IntType,0) \
+                  } \
+               } else { \
+                  if ( usevar ) { \
+                     KERNEL_ND(X,Xtype,0,1,0,IntType,0) \
+                  } else if ( genvar ) { \
+                     KERNEL_ND(X,Xtype,0,0,1,IntType,0) \
+                  } else { \
+                     KERNEL_ND(X,Xtype,0,0,0,IntType,0) \
+                  } \
+               } \
+            } \
+\
+/* Exit point on error in kernel function */ \
+            Kernel_SError_Nd: ;\
+         } \
+\
+/* Free the workspace. */ \
+         hi = astFree( hi ); \
+         lo = astFree( lo ); \
+         jhi = astFree( jhi ); \
+         jlo = astFree( jlo ); \
+         stride = astFree( stride ); \
+         xnl = astFree( xnl ); \
+         kval = astFree( kval ); \
+         wtprod = astFree( wtprod ); \
+         wtptr = astFree( wtptr ); \
+         wtptr_last = astFree( wtptr_last ); \
+      } \
+      filter = astFree( filter ); \
+   }\
+\
+/* If an error occurred in the kernel function, then report a \
+   contextual error message. */ \
+   if ( kerror ) { \
+      astError( astStatus, "astRebin"#X"(%s): Error signalled by " \
+                "user-supplied 1-d interpolation kernel.", status, \
+                astGetClass( unsimplified_mapping ) ); \
+   } \
+\
+}
+
+
+
+
+#define KERNEL_1D(X,Xtype,Usebad,Usevar,Genvar,IntType,Varwgt) \
+\
+/* We do not yet have a previous filter position. */ \
+   xxl = AST__BAD; \
+\
+/* Loop round all input points which are to be rebinned. */ \
+   for( point = 0; point < npoint; point++ ) { \
+\
+/* Obtain the input data value which is to be added into the output array. */ \
+      off_in = offset[ point ]; \
+      in_val = in[ off_in ]; \
+\
+/* If necessary, test if the input data value or variance is bad. If we \
+   are using the reciprocal of the input variances as weights, then \
+   variance values of zero are also effectively bad (but we can use input \
+   variances of zero otherwise). */ \
+      if ( Usebad ) { \
+         bad = ( in_val == badval ); \
+         if ( Varwgt ) { \
+            bad = bad || ( in_var[ off_in ] == badval ) \
+                      || ( in_var[ off_in ] <= 0.0 ); \
+         } else if ( Usevar ) { \
+            bad = bad || ( in_var[ off_in ] == badval ); \
+         } \
+      } else { \
+         if ( Varwgt ) { \
+            bad = ( in_var[ off_in ] <= 0.0 ); \
+         } else { \
+            bad = 0; \
+         } \
+      } \
+\
+/* Obtain the x coordinate of the current point and test if it is bad. \
+   Also test that the central point falls within the output array. */ \
+      x = coords[ 0 ][ point ]; \
+      ix = (int) floor( x + 0.5 ); \
+      if( ix < lbnd_out[ 0 ] || ix > ubnd_out[ 0 ] ) bad = 1; \
+      bad = bad || ( x == AST__BAD ); \
+\
+/* If OK, calculate the lowest and highest indices (in the x \
+   dimension) of the region of neighbouring output pixels that will \
+   receive contributions from the current input pixel. Constrain these \
+   values to lie within the output grid. */ \
+      if ( !bad ) { \
+         ix = (int) floor( x ) - neighb + 1; \
+         lo_ix = MaxI( ix, lbnd_out[ 0 ], status ); \
+         hi_ix = MinI( ix + nb2 - 1, ubnd_out[ 0 ], status ); \
+\
+/* Skip to the next input point if the current input point makes no \
+   contribution to any output pixel. */ \
+         if( lo_ix <= hi_ix ) { \
+\
+/* Increment the number of input pixels pasted into the output array. */ \
+            if( nused ) (*nused)++; \
+\
+/* Convert these output indices to the corresponding indices \
+   within a box [ 0, 2*neighb ] holding the kernel values. */ \
+            lo_jx = lo_ix - ix; \
+            hi_jx = hi_ix - ix; \
+\
+/* See if the kernel extends off the edge of the output array. */ \
+            nwx =  hi_jx - lo_jx + 1; \
+            off_xedge = ( nwx < nb2 ); \
+\
+/* Use the kernel function to fill the work array with weights for all output \
+   pixels whether or not they fall within the output array. At the same \
+   time find the sum of all the factors. */ \
+            xx = (double) ix - x; \
+            if( xx != xxl || off_xedge ) { \
+               sum = 0.0; \
+\
+/* First handle cases where the kernel box overlaps an edge of the output \
+   array. In these cases, in order to conserve flux, the bit of the \
+   kernel function that is off the edge is reflected back onto the array. \
+   Care must be taken since the reflected part of the kernel may itself \
+   overlap the opposite edge of the array, in which case the overlapping \
+   part must again be reflected back onto the array. This iterative \
+   reflection is implemented using a fractional division (%) operator. */ \
+               if( off_xedge ) { \
+                  nwx *= 2; \
+                  xxl = AST__BAD; \
+                  for( jx = 0; jx < nb2; jx++ ) filter[ jx ] = 0.0; \
+\
+                  for ( jx = 0; jx < nb2; jx++ ) { \
+                     ( *kernel )( xx, params, flags, &pixwt, status ); \
+                     if ( !astOK ) { \
+                        kerror = 1; \
+                        goto Kernel_SError_1d; \
+                     } \
+\
+                     jjx =  ( jx - lo_jx ) % nwx + lo_jx; \
+                     if( jjx < lo_jx ) jjx += nwx; \
+                     if( jjx > hi_jx ) jjx = 2*hi_jx - jjx + 1; \
+\
+                     filter[ jjx ] += pixwt; \
+                     sum += pixwt; \
+                     xx += 1.0; \
+                  } \
+\
+/* Now handle cases where the kernel box is completely within the output \
+   array. */ \
+               } else { \
+                  xxl = xx; \
+\
+                  for ( jx = 0; jx < nb2; jx++ ) { \
+                     ( *kernel )( xx, params, flags, &pixwt, status ); \
+\
+/* Check for errors arising in the kernel function. */ \
+                     if ( !astOK ) { \
+                        kerror = 1; \
+                        goto Kernel_SError_1d; \
+                     } \
+\
+/* Store the kernel factor and increment the sum of all factors. */ \
+                     filter[ jx ] = pixwt; \
+                     sum += pixwt; \
+                     xx += 1.0; \
+                  } \
+\
+               } \
+\
+/* Ensure we do not divide by zero. */ \
+               if( sum == 0.0 ) sum = 1.0; \
+            } \
+\
+/* If we are using the input data variances as weights, calculate the \
+   total weight, incorporating the normalisation factor for the kernel. */ \
+            if( Varwgt ) { \
+               wgt = 1.0/(sum*in_var[ off_in ]); \
+\
+/* If we are not using input variances as weights, the weight is just the \
+   kernel normalisation factor. */ \
+            } else { \
+               wgt = 1.0/sum; \
+            } \
+\
+/* Loop round all the output pixels which receive contributions from this \
+   input pixel, calculating the offset of each pixel from the start of the \
+   input array. */ \
+            off_out = lo_ix - lbnd_out[ 0 ]; \
+            for ( jx = lo_jx; jx <= hi_jx; jx++, off_out++ ) { \
+\
+/* Retrieve the weight for the current output pixel and normalise it. */ \
+               pixwt = wgt*filter[ jx ]; \
+               pfac = pixwt*infac; \
+\
+/* Update the output pixel with the required fraction of the input pixel \
+   value. */ \
+               c = CONV(IntType,pfac*in_val); \
+\
+               if( work ) { \
+                  out[ off_out ] += c; \
+                  work[ off_out ] += pixwt; \
+               } else {\
+                  out[ off_out ] += c; \
+               } \
+\
+               if ( Usevar ) { \
+                  out_var[ off_out ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+               } else if ( Genvar && pixwt != 0.0 ) { \
+                  out_var[ off_out ] += c*c/pixwt; \
+                  work[ off_out + npix_out ] += pixwt*pixwt; \
+               } \
+\
+            } \
+         } \
+      } \
+   }
+
+
+
+
+#define KERNEL_2D(X,Xtype,Usebad,Usevar,Genvar,IntType,Varwgt) \
+\
+/* We do not yet have a previous filter position. */ \
+   xxl = AST__BAD; \
+   yyl = AST__BAD; \
+\
+/* Loop round all input points which are to be rebinned. */ \
+   for( point = 0; point < npoint; point++ ) { \
+\
+/* Obtain the input data value which is to be added into the output array. */ \
+      off_in = offset[ point ]; \
+      in_val = in[ off_in ]; \
+\
+/* If necessary, test if the input data value or variance is bad. If we \
+   are using the reciprocal of the input variances as weights, then \
+   variance values of zero are also effectively bad (but we can use input \
+   variances of zero otherwise). */ \
+      if ( Usebad ) { \
+         bad = ( in_val == badval ); \
+         if ( Varwgt ) { \
+            bad = bad || ( in_var[ off_in ] == badval ) \
+                      || ( in_var[ off_in ] <= 0.0 ); \
+         } else if ( Usevar ) { \
+            bad = bad || ( in_var[ off_in ] == badval ); \
+         } \
+      } else { \
+         if ( Varwgt ) { \
+            bad = ( in_var[ off_in ] <= 0.0 ); \
+         } else { \
+            bad = 0; \
+         } \
+      } \
+\
+/* Obtain the x coordinate of the current point and test if it is bad. \
+   Also test that the central point falls within the output array. */ \
+      x = coords[ 0 ][ point ]; \
+      ix = (int) floor( x + 0.5 ); \
+      if( ix < lbnd_out[ 0 ] || ix > ubnd_out[ 0 ] ) bad = 1; \
+      bad = bad || ( x == AST__BAD ); \
+      if ( !bad ) { \
+\
+/* Similarly obtain and test the y coordinate. */ \
+         y = coords[ 1 ][ point ]; \
+         iy = (int) floor( y + 0.5 ); \
+         if( iy < lbnd_out[ 1 ] || iy > ubnd_out[ 1 ] ) bad = 1; \
+         bad = bad || ( y == AST__BAD ); \
+         if ( !bad ) { \
+\
+/* If OK, calculate the lowest and highest indices (in each dimension) \
+   of the region of neighbouring output pixels which will receive \
+   contributions from the current input pixel. Constrain these values \
+   to lie within the input grid. */ \
+            ix = (int) floor( x ) - neighb + 1; \
+            lo_ix = MaxI( ix, lbnd_out[ 0 ], status ); \
+            hi_ix = MinI( ix + nb2 - 1, ubnd_out[ 0 ], status ); \
+            iy = (int) floor( y ) - neighb + 1; \
+            lo_iy = MaxI( iy, lbnd_out[ 1 ], status ); \
+            hi_iy = MinI( iy + nb2 - 1, ubnd_out[ 1 ], status ); \
+\
+/* Skip to the next input point if the current input point makes no \
+   contribution to any output pixel. */ \
+            if( lo_ix <= hi_ix && lo_iy <= hi_iy ) { \
+\
+/* Increment the number of input pixels pasted into the output array. */ \
+               if( nused ) (*nused)++; \
+\
+/* Convert these output indices to the corresponding indices \
+   within a box [ 0:2*neighb, 0:2*neighb ] holding the kernel values. */ \
+               lo_jx = lo_ix - ix; \
+               hi_jx = hi_ix - ix; \
+               lo_jy = lo_iy - iy; \
+               hi_jy = hi_iy - iy; \
+\
+/* See if the kernel extends off the edge of the output array on either \
+   axis. */ \
+               nwx =  hi_jx - lo_jx + 1; \
+               nwy =  hi_jy - lo_jy + 1; \
+               off_xedge = ( nwx < nb2 ); \
+               off_yedge = ( nwy < nb2 ); \
+\
+/* Loop to evaluate the kernel function along the y dimension, storing \
+   the resulting weight values in all elements of each associated row \
+   in the kvar array. The function's argument is the offset of the \
+   output pixel (along this dimension) from the central output \
+   position. */ \
+               yy = (double) iy - y; \
+               xx = (double) ix - x; \
+               if( xx != xxl || yy != yyl || off_xedge || off_yedge ) { \
+\
+/* First handle cases where the kernel box extends beyond the top or \
+   bottom edge of the output array. In these cases, in order to conserve \
+   flux, the bit of the kernel function that is off the edge is reflected \
+   back onto the array. Care must be taken since the reflected part of the \
+   kernel may itself overlap the opposite edge of the array, in which \
+   case the overlapping part must again be reflected back onto the \
+   array. This iterative reflection is implemented using a fractional \
+   division (%) operator. */ \
+                  if( off_yedge ) { \
+                     nwy *= 2; \
+                     xxl = AST__BAD; \
+                     yyl = AST__BAD; \
+                     for( jy = 0; jy < nb2*nb2; jy++ ) filter[ jy ] = 0.0; \
+\
+                     for ( jy = 0; jy < nb2; jy++ ) { \
+                        ( *kernel )( yy, params, flags, &pixwt, status ); \
+                        if ( !astOK ) { \
+                           kerror = 1; \
+                           goto Kernel_SError_2d; \
+                        } \
+\
+                        jjy =  ( jy - lo_jy ) % nwy + lo_jy; \
+                        if( jjy < lo_jy ) jjy += nwy; \
+                        if( jjy > hi_jy ) jjy = 2*hi_jy - jjy + 1; \
+\
+                        kp = filter + jjy*nb2; \
+                        for( jx = 0; jx < nb2; jx++ ) *(kp++) += pixwt; \
+                        yy += 1.0; \
+                     } \
+\
+/* Now handles cases where the kernel does not overlap the top or bottom edge \
+   of the output array. */ \
+                  } else { \
+                     xxl = xx; \
+                     yyl = yy; \
+                     kp = filter; \
+                     for ( jy = 0; jy < nb2; jy++ ) { \
+                        ( *kernel )( yy, params, flags, &pixwt, status ); \
+\
+/* Check for errors arising in the kernel function. */ \
+                        if ( !astOK ) { \
+                           kerror = 1; \
+                           goto Kernel_SError_2d; \
+                        } \
+\
+/* Store the kernel factor in all elements of the current row. */ \
+                        for( jx = 0; jx < nb2; jx++ ) *(kp++) = pixwt; \
+\
+/* Move on to the next row. */ \
+                        yy += 1.0; \
+                     } \
+                  } \
+\
+/* Loop to evaluate the kernel function along the x dimension, multiplying \
+   the resulting weight values by the values already stored in the the \
+   associated column in the kvar array. The function's argument is the \
+   offset of the output pixel (along this dimension) from the central output \
+   position. Also form the total data sum in the filter array. First \
+   handle cases where the kernel overlaps the left or right edge of the \
+   output array. */ \
+                  sum = 0.0; \
+\
+/* First deal with cases where the kernel extends beyond the left or \
+   right edge of the output array. */ \
+                  if( off_xedge ) { \
+                     nwx *= 2; \
+                     xxl = AST__BAD; \
+                     for( jx = 0; jx < nb2; jx++ ) xfilter[ jx ] = 0.0; \
+\
+                     for ( jx = 0; jx < nb2; jx++ ) { \
+                        ( *kernel )( xx, params, flags, &pixwt, status ); \
+                        if ( !astOK ) { \
+                           kerror = 1; \
+                           goto Kernel_SError_2d; \
+                        } \
+\
+                        jjx =  ( jx - lo_jx ) % nwx + lo_jx; \
+                        if( jjx < lo_jx ) jjx += nwx; \
+                        if( jjx > hi_jx ) jjx = 2*hi_jx - jjx + 1; \
+\
+                        xfilter[ jjx ] += pixwt; \
+                        xx += 1.0; \
+                     } \
+\
+                     for ( jx = 0; jx < nb2; jx++ ) { \
+                        kp = filter + jx; \
+                        for( jy = 0; jy < nb2; jy++, kp += nb2 ) { \
+                           *kp *= xfilter[ jx ]; \
+                           sum += *kp; \
+                        } \
+                     } \
+\
+/* Now deal with cases where the kernel does not extends beyond the left or \
+   right edge of the output array. */ \
+                  } else { \
+\
+                     for ( jx = 0; jx < nb2; jx++ ) { \
+                        ( *kernel )( xx, params, flags, &pixwt, status ); \
+\
+/* Check for errors arising in the kernel function. */ \
+                        if ( !astOK ) { \
+                           kerror = 1; \
+                           goto Kernel_SError_2d; \
+                        } \
+\
+/* Multiply the kernel factor by all elements of the current column. */ \
+                        kp = filter + jx; \
+                        for( jy = 0; jy < nb2; jy++, kp += nb2 ) { \
+                           *kp *= pixwt; \
+                           sum += *kp; \
+                        } \
+\
+/* Move on to the next column. */ \
+                        xx += 1.0; \
+                     } \
+                  } \
+\
+/* Ensure we do not divide by zero. */ \
+                  if( sum == 0.0 ) sum = 1.0; \
+               } \
+\
+/* If we are using the input data variances as weights, calculate the \
+   total weight, incorporating the normalisation factor for the kernel. */ \
+               if( Varwgt ) { \
+                  wgt = 1.0/(sum*in_var[ off_in ]); \
+\
+/* If we are not using input variances as weights, the weight is just the \
+   kernel normalisation factor. */ \
+               } else { \
+                  wgt = 1.0/sum; \
+               } \
+\
+/* Find the offset into the output array at the first modified output pixel \
+   in the first modified row. */ \
+               off1 = lo_ix - lbnd_out[ 0 ] + ystride * ( lo_iy - lbnd_out[ 1 ] ); \
+\
+/* Loop over the affected output rows again. */ \
+               for ( jy = lo_jy; jy <= hi_jy; jy++, off1 += ystride ) { \
+\
+/* Save the offset of the first output pixel to be modified in the \
+   current row. */ \
+                  off_out = off1; \
+\
+/* Get a pointer to the first weight value which will be used. */ \
+                  kp = filter + lo_jx + jy*nb2; \
+\
+/* Loop over the affected output columns again. */ \
+                  for ( jx = lo_jx; jx <= hi_jx; jx++, off_out++, kp++ ) { \
+\
+/* Calculate the weight for this output pixel and normalise it. */ \
+                     pixwt = wgt*( *kp ); \
+\
+/* Update the output pixel with the required fraction of the input pixel \
+   value. */ \
+                     pfac = pixwt*infac; \
+                     c = CONV(IntType,pfac*in_val); \
+\
+                     out[ off_out ] += c; \
+                     if( work ) work[ off_out ] += pixwt; \
+\
+                     if ( Usevar ) { \
+                        out_var[ off_out ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+                     } else if ( Genvar && pixwt != 0.0 ) { \
+                        out_var[ off_out ] += c*c/pixwt; \
+                        work[ off_out + npix_out ] += pixwt*pixwt; \
+                     } \
+                  } \
+               } \
+            } \
+         } \
+      } \
+   }
+
+
+
+
+#define KERNEL_ND(X,Xtype,Usebad,Usevar,Genvar,IntType,Varwgt) \
+\
+/* We do not yet have a normalising factor */ \
+   sum = AST__BAD; \
+\
+/* Loop round all input points which are to be rebinned. */ \
+   for( point = 0; point < npoint; point++ ) { \
+\
+/* Obtain the input data value which is to be added into the output array. */ \
+      off_in = offset[ point ]; \
+      in_val = in[ off_in ]; \
+\
+/* If necessary, test if the input data value or variance is bad. If we \
+   are using the reciprocal of the input variances as weights, then \
+   variance values of zero are also effectively bad (but we can use input \
+   variances of zero otherwise). */ \
+      if ( Usebad ) { \
+         bad = ( in_val == badval ); \
+         if ( Varwgt ) { \
+            bad = bad || ( in_var[ off_in ] == badval ) \
+                      || ( in_var[ off_in ] <= 0.0 ); \
+         } else if ( Usevar ) { \
+            bad = bad || ( in_var[ off_in ] == badval ); \
+         } \
+      } else { \
+         if ( Varwgt ) { \
+            bad = ( in_var[ off_in ] <= 0.0 ); \
+         } else { \
+            bad = 0; \
+         } \
+      } \
+\
+/* Initialise offsets into the output array. Then loop to obtain each \
+   coordinate associated with the current output point. Set a flag \
+   indicating if any output pixel will be modified. */ \
+      if( !bad ) { \
+         off_out = 0; \
+         for ( idim = 0; idim < ndim_out; idim++ ) { \
+            xn = coords[ idim ][ point ]; \
+\
+/* Test if the coordinate is bad. If true, the corresponding output pixel \
+   value will be bad, so give up on this point. */ \
+            ix = (int) floor( xn + 0.5 ); \
+            if( ix < lbnd_out[ idim ] || ix > ubnd_out[ idim ] ) bad = 1; \
+            bad = bad || ( xn == AST__BAD ); \
+            if ( bad ) break; \
+\
+/* Calculate the lowest and highest indices (in the current dimension) \
+   of the region of neighbouring output pixels that will be modified. \
+   Constrain these values to lie within the output grid. */ \
+            ix = (int) floor( xn ) - neighb + 1; \
+            lo[ idim ] = MaxI( ix, lbnd_out[ idim ], status ); \
+            hi[ idim ] = MinI( ix + nb2 - 1, ubnd_out[ idim ], status ); \
+            jlo[ idim ] = lo[ idim ] - ix; \
+            jhi[ idim ] = hi[ idim ] - ix; \
+\
+/* Check there is some overlap with the output array on this axis. */ \
+            if( lo[ idim ] > hi[ idim ] ) { \
+               bad = 1; \
+               break; \
+            } \
+\
+/* Accumulate the offset (from the start of the output array) of the \
+   modified output pixel which has the lowest index in each dimension. */ \
+            off_out += stride[ idim ] * ( lo[ idim ] - lbnd_out[ idim ] ); \
+\
+/* Set up an array of pointers to locate the first filter pixel (stored in the \
+   "kval" array) for each dimension. */ \
+            wtptr[ idim ] = kval + nb2*idim; \
+            wtptr_last[ idim ] = wtptr[ idim ] + nb2 - 1; \
+\
+/* See if the kernel extends off the edge of the output array on the current \
+   axis. */ \
+            lo_jx = jlo[ idim ]; \
+            hi_jx = jhi[ idim ]; \
+            nwx =  hi_jx - lo_jx + 1; \
+            off_xedge = ( nwx < nb2 ); \
+\
+/* Loop to evaluate the kernel function along each dimension, storing \
+   the resulting values. The function's argument is the offset of the \
+   output pixel (along the relevant dimension) from the central output \
+   point. */ \
+            xxn = (double) ix - xn; \
+            if( xxn != xnl[ idim ] || off_xedge ) { \
+               sum = AST__BAD; \
+\
+/* First handle cases where the kernel box overlaps an edge of the output \
+   array. In these cases, in order to conserve flux, the bit of the \
+   kernel function that is off the edge is reflected back onto the array. \
+   Care must be taken since the reflected part of the kernel may itself \
+   overlap the opposite edge of the array, in which case the overlapping \
+   part must again be reflected back onto the array. This iterative \
+   reflection is implemented using a fractional division (%) operator. */ \
+               if( off_xedge ) { \
+                  nwx *= 2; \
+                  xnl[ idim ] = AST__BAD; \
+                  kp = wtptr[ idim ]; \
+                  for( jx = 0; jx < nb2; jx++ ) *(kp++) = 0.0; \
+\
+                  kp = wtptr[ idim ]; \
+                  for ( jx = 0; jx < nb2; jx++ ) { \
+                     ( *kernel )( xxn, params, flags, &pixwt, status ); \
+                     if ( !astOK ) { \
+                        kerror = 1; \
+                        goto Kernel_SError_1d; \
+                     } \
+\
+                     jjx =  ( jx - lo_jx ) % nwx + lo_jx; \
+                     if( jjx < lo_jx ) jjx += nwx; \
+                     if( jjx > hi_jx ) jjx = 2*hi_jx - jjx + 1; \
+\
+                     kp[ jjx ] += pixwt; \
+                     xxn += 1.0; \
+                  } \
+\
+/* Now handle cases where the kernel box is completely within the output \
+   array. */ \
+               } else { \
+                  xnl[ idim ] = xxn; \
+                  for ( jxn = 0; jxn < nb2; jxn++ ) { \
+                     ( *kernel )( xxn, params, flags, wtptr[ idim ] + jxn, status ); \
+\
+/* Check for errors arising in the kernel function. */ \
+                     if ( !astOK ) { \
+                        kerror = 1; \
+                        goto Kernel_SError_Nd; \
+                     } \
+\
+/* Increment the kernel position. */ \
+                     xxn += 1.0; \
+                  } \
+               } \
+            } \
+         } \
+\
+/* If OK... */ \
+         if ( !bad ) { \
+\
+/* We only need to modify the normalising factor if the weight values \
+   have changed. */ \
+            if( sum == AST__BAD ) { \
+\
+/* The kernel value to use for each output pixel is the product of the \
+   kernel values for each individual axis at that point. To conserve \
+   flux we need to make sure that the sum of these kernel products is unity. \
+   So loop over the values now to find the total sum of all kernel values. */ \
+               idim = ndim_out - 1; \
+               wtprod[ idim ] = 1.0; \
+               done = 0; \
+               sum = 0; \
+               do { \
+\
+/* Each modified output pixel has a weight equal to the product of the kernel \
+   weight factors evaluated along each input dimension. However, since \
+   we typically only change the index of one dimension at a time, we \
+   can avoid forming this product repeatedly by retaining an array of \
+   accumulated products for all higher dimensions. We need then only \
+   update the lower elements in this array, corresponding to those \
+   dimensions whose index has changed. We do this here, "idim" being \
+   the index of the most significant dimension to have changed. Note \
+   that on the first pass, all dimensions are considered changed, \
+   causing this array to be initialised. */ \
+                  for ( ii = idim; ii >= 1; ii-- ) { \
+                     wtprod[ ii - 1 ] = wtprod[ ii ] * *( wtptr[ ii ] ); \
+                  } \
+\
+/* Obtain the weight of each pixel from the accumulated product of \
+   weights. Also multiply by the weight for dimension zero, which is not \
+   included in the "wtprod" array). Increment the sum of all weights. */ \
+                  sum += wtprod[ 0 ] * *( wtptr[ 0 ] ); \
+\
+/* Now update the weight value pointers and pixel offset to refer to \
+   the next output pixel to be considered. */ \
+                  idim = 0; \
+                  do { \
+\
+/* The first input dimension whose weight value pointer has not yet \
+   reached its final value has this pointer incremented. */ \
+                     if ( wtptr[ idim ] != wtptr_last[ idim ] ) { \
+                        wtptr[ idim ]++; \
+                        break; \
+\
+/* Any earlier dimensions (which have reached the final pointer value) \
+   have this pointer returned to its lowest value. */ \
+                     } else { \
+                        wtptr[ idim ] -= nb2 - 1; \
+                        done = ( ++idim == ndim_out ); \
+                     } \
+                  } while ( !done ); \
+               } while ( !done ); \
+\
+/* Ensure we do not divide by zero. */ \
+               if( sum == 0.0 ) sum = 1.0; \
+            } \
+\
+/* Re-initialise the weights pointers to refer to the first and last \
+   filter pixels which overlaps the output array. */ \
+            kstart = kval; \
+            for ( idim = 0; idim < ndim_out; idim++ ) { \
+               wtptr[ idim ] = kstart + jlo[ idim ]; \
+               wtptr_last[ idim ] = kstart + jhi[ idim ]; \
+               kstart += nb2; \
+            } \
+\
+/* If we are using the input data variances as weights, calculate the \
+   total weight, incorporating the normalisation factor for the kernel. */ \
+            if( Varwgt ) { \
+               wgt = 1.0/(sum*in_var[ off_in ]); \
+\
+/* If we are not using input variances as weights, the weight is just the \
+   kernel normalisation factor. */ \
+            } else { \
+               wgt = 1.0/sum; \
+            } \
+\
+/* Increment the number of input pixels pasted into the output array. */ \
+            if( nused ) (*nused)++; \
+\
+/* Initialise, and loop over the neighbouring output pixels to divide up \
+   the input pixel value between them. */ \
+            idim = ndim_out - 1; \
+            wtprod[ idim ] = 1.0; \
+            done = 0; \
+            do { \
+\
+/* Each modified output pixel has a weight equal to the product of the kernel \
+   weight factors evaluated along each input dimension. However, since \
+   we typically only change the index of one dimension at a time, we \
+   can avoid forming this product repeatedly by retaining an array of \
+   accumulated products for all higher dimensions. We need then only \
+   update the lower elements in this array, corresponding to those \
+   dimensions whose index has changed. We do this here, "idim" being \
+   the index of the most significant dimension to have changed. Note \
+   that on the first pass, all dimensions are considered changed, \
+   causing this array to be initialised. */ \
+               for ( ii = idim; ii >= 1; ii-- ) { \
+                  wtprod[ ii - 1 ] = wtprod[ ii ] * *( wtptr[ ii ] ); \
+               } \
+\
+/* Obtain the weight of each pixel from the accumulated \
+   product of weights. Also multiply by the weight for dimension zero, \
+   which is not included in the "wtprod" array). */ \
+               pixwt = ( wtprod[ 0 ] * *( wtptr[ 0 ] ) )*wgt; \
+\
+/* Update the output pixel with the required fraction of the input pixel \
+   value. */ \
+               pfac = pixwt*infac; \
+               c = CONV(IntType,pfac*in_val); \
+\
+               if( work ) { \
+                  out[ off_out ] += c; \
+                  work[ off_out ] += pixwt; \
+               } else {\
+                  out[ off_out ] += c; \
+               } \
+\
+               if ( Usevar ) { \
+                  out_var[ off_out ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+               } else if ( Genvar && pixwt != 0.0 ) { \
+                  out_var[ off_out ] += c*c/pixwt; \
+                  work[ off_out + npix_out ] += pixwt*pixwt; \
+               } \
+\
+/* Now update the weight value pointers and pixel offset to refer to \
+   the next output pixel to be considered. */ \
+               idim = 0; \
+               do { \
+\
+/* The first input dimension whose weight value pointer has not yet \
+   reached its final value has this pointer incremented, and the pixel \
+   offset into the input array is updated accordingly. */ \
+                  if ( wtptr[ idim ] != wtptr_last[ idim ] ) { \
+                     wtptr[ idim ]++; \
+                     off_out += stride[ idim ]; \
+                     break; \
+\
+/* Any earlier dimensions (which have reached the final pointer value) \
+   have this pointer returned to its lowest value. Again, the pixel \
+   offset into the input image is updated accordingly. */ \
+                  } else { \
+                     wtptr[ idim ] -= ( hi[ idim ] - lo[ idim ] ); \
+                     off_out -= stride[ idim ] * \
+                                     ( hi[ idim ] - lo[ idim ] ); \
+                     done = ( ++idim == ndim_out ); \
+                  } \
+               } while ( !done ); \
+            } while ( !done ); \
+         } \
+      } \
+   }
+
+
+/* Expand the main macro above to generate a function for each
+   required signed data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_SPREAD_KERNEL1(LD,long double,0)
+#endif
+MAKE_SPREAD_KERNEL1(D,double,0)
+MAKE_SPREAD_KERNEL1(F,float,0)
+MAKE_SPREAD_KERNEL1(I,int,1)
+MAKE_SPREAD_KERNEL1(B,signed char,1)
+MAKE_SPREAD_KERNEL1(UB,unsigned char,1)
+
+/* Undefine the macros used above. */
+#undef KERNEL_ND
+#undef KERNEL_2D
+#undef KERNEL_1D
+#undef MAKE_SPREAD_KERNEL1
+
+/*
+*  Name:
+*     SpreadLinear<X>
+
+*  Purpose:
+*     Rebin a data grid, using the linear spreading scheme.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void SpreadLinear<X>( int ndim_out,
+*                           const int *lbnd_out, const int *ubnd_out,
+*                           const <Xtype> *in, const <Xtype> *in_var,
+*                           double infac, int npoint, const int *offset,
+*                           const double *const *coords, int flags,
+*                           <Xtype> badval, int npix_out, <Xtype> *out,
+*                           <Xtype> *out_var, double *work, int64_t *nused  )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This is a set of functions which rebins a rectangular region of an
+*     input grid of data (and, optionally, associated statistical variance
+*     values) so as to place them into a new output grid. Each input
+*     grid point may be mapped on to a position in the output grid in
+*     an arbitrary way. Where the positions given do not correspond
+*     with a pixel centre in the input grid, the spreading scheme
+*     used divides the input pixel value up linearly between the
+*     nearest neighbouring output pixels in each dimension (there are 2
+*     nearest neighbours in 1 dimension, 4 in 2 dimensions, 8 in 3
+*     dimensions, etc.).
+
+*  Parameters:
+*     ndim_out
+*        The number of dimensions in the output grid. This should be at
+*        least one.
+*     lbnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the output grid along each dimension.
+*     ubnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the output grid along each dimension.
+*
+*        Note that "lbnd_out" and "ubnd_out" together define the shape
+*        and size of the output grid, its extent along a particular
+*        (i'th) dimension being ubnd_out[i]-lbnd_out[i]+1 (assuming "i"
+*        is zero-based). They also define the output grid's coordinate
+*        system, with each pixel being of unit extent along each
+*        dimension with integral coordinate values at its centre.
+*     in
+*        Pointer to the array of data to be rebinned. The numerical type
+*        of these data should match the function used, as given by the
+*        suffix on the function name. Note that details of how the input
+*        grid maps on to this array (e.g. the storage order, number of
+*        dimensions, etc.) is arbitrary and is specified entirely by means
+*        of the "offset" array. The "in" array should therefore contain
+*        sufficient elements to accommodate the "offset" values supplied.
+*        There is no requirement that all elements of the "in" array
+*        should be rebinned, and any which are not addressed by the
+*        contents of the "offset" array will be ignored.
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and type as the "in" array), which
+*        represent estimates of the statistical variance associated
+*        with each element of the "in" array. If this second array is
+*        given (along with the corresponding "out_var" array), then
+*        estimates of the variance of the resampled data will also be
+*        returned. It is addressed in exactly the same way (via the
+*        "offset" array) as the "in" array.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     infac
+*        A factor by which to multiply the input data values before use.
+*     npoint
+*        The number of input points which are to be rebinned.
+*     offset
+*        Pointer to an array of integers with "npoint" elements. For
+*        each input point, this array should contain the zero-based
+*        offset in the input array(s) (i.e. the "in" and, optionally,
+*        the "in_var" arrays) from which the value to be rebinned should
+*        be obtained.
+*     coords
+*        An array of pointers to double, with "ndim_out" elements.
+*        Element "coords[coord]" should point at the first element of
+*        an array of double (with "npoint" elements) which contains the
+*        values of coordinate number "coord" for each point being
+*        rebinned. The value of coordinate number "coord" for
+*        rebinning point number "point" is therefore given by
+*        "coords[coord][point]" (assuming both indices are
+*        zero-based).  If any point has a coordinate value of AST__BAD
+*        associated with it, then the corresponding input data (and
+*        variance) value will be ignored.
+*        The bitwise OR of a set of flag values which control the
+*        operation of the function. These are chosend from:
+*
+*        - AST__USEBAD: indicates whether there are "bad" (i.e. missing) data
+*        in the input array(s) which must be recognised.  If this flag is not
+*        set, all input values are treated literally.
+*        - AST__GENVAR: Indicates that any input variances are to be
+*        ignored, and that the output variances should be generated from
+*        the spread of values contributing to each output pixel.
+*     badval
+*        If the AST__USEBAD flag is set in the "flags" value (above),
+*        this parameter specifies the value which is used to identify
+*        bad data and/or variance values in the input array(s). Its
+*        numerical type must match that of the "in" (and "in_var")
+*        arrays. The same value will also be used to flag any output
+*        array elements for which resampled values could not be
+*        obtained.  The output arrays(s) may be flagged with this
+*        value whether or not the AST__USEBAD flag is set (the
+*        function return value indicates whether any such values have
+*        been produced).
+*     npix_out
+*        Number of pixels in output array.
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the rebinned data will be returned. The
+*        storage order should be such that the index of the first grid
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order).
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the rebinned values may be returned. This array will only be
+*        used if the "in_var" array has been given. The values returned
+*        are estimates of the statistical variance of the corresponding
+*        values in the "out" array, on the assumption that all errors in
+*        input grid values (in the "in" array) are statistically independent
+*        and that their variance estimates (in the "in_var" array) may
+*        simply be summed (with appropriate weighting factors).
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+*     work
+*        An optional pointer to a double array with the same size as
+*        the "out" array. The contents of this array (if supplied) are
+*        incremented by the accumulated weights assigned to each output pixel.
+*        If no accumulated weights are required, a NULL pointer should be
+*        given.
+*     nused
+*        An optional pointer to a int64_t which will be incremented by the
+*        number of input values pasted into the output array. Ignored if NULL.
+
+*  Notes:
+*     - There is a separate function for each numerical type of
+*     gridded data, distinguished by replacing the <X> in the function
+*     name by the appropriate 1- or 2-character suffix.
+*/
+/* Define macros to implement the function for a specific data
+   type. */
+#define MAKE_SPREAD_LINEAR(X,Xtype,IntType) \
+static void SpreadLinear##X( int ndim_out, \
+                            const int *lbnd_out, const int *ubnd_out, \
+                            const Xtype *in, const Xtype *in_var, \
+                            double infac, int npoint, const int *offset, \
+                            const double *const *coords, int flags, \
+                            Xtype badval, int npix_out, Xtype *out, \
+                            Xtype *out_var, double *work, int64_t *nused, \
+                            int *status ) { \
+\
+/* Local Variables: */ \
+   Xtype c;                      /* Contribution to output value */ \
+   Xtype in_val;                 /* Input value */ \
+   double *frac_hi;              /* Pointer to array of weights */ \
+   double *frac_lo;              /* Pointer to array of weights */ \
+   double *wt;                   /* Pointer to array of weights */ \
+   double *wtprod;               /* Array of accumulated weights pointer */ \
+   double *xn_max;               /* Pointer to upper limits array (n-d) */ \
+   double *xn_min;               /* Pointer to lower limits array (n-d) */ \
+   double frac_hi_x;             /* Pixel weight (x dimension) */ \
+   double frac_hi_y;             /* Pixel weight (y dimension) */ \
+   double frac_lo_x;             /* Pixel weight (x dimension) */ \
+   double frac_lo_y;             /* Pixel weight (y dimension) */ \
+   double pfac;                  /* Scaled pixel weight */ \
+   double pixwt;                 /* Total pixel weight */ \
+   double wgt;                   /* Weight for input value */ \
+   double x;                     /* x coordinate value */ \
+   double xmax;                  /* x upper limit */ \
+   double xmin;                  /* x lower limit */ \
+   double xn;                    /* Coordinate value (n-d) */ \
+   double y;                     /* y coordinate value */ \
+   double ymax;                  /* y upper limit */ \
+   double ymin;                  /* y lower limit */ \
+   int *dim;                     /* Pointer to array of pixel indices */ \
+   int *hi;                      /* Pointer to array of upper indices */ \
+   int *lo;                      /* Pointer to array of lower indices */ \
+   int *stride;                  /* Pointer to array of dimension strides */ \
+   int bad;                      /* Output pixel bad? */ \
+   int done;                     /* All pixel indices done? */ \
+   int genvar;                   /* Generate output variances? */ \
+   int hi_x;                     /* Upper pixel index (x dimension) */ \
+   int hi_y;                     /* Upper pixel index (y dimension) */ \
+   int idim;                     /* Loop counter for dimensions */ \
+   int ii;                       /* Loop counter for weights */ \
+   int ixn;                      /* Pixel index (n-d) */ \
+   int lo_x;                     /* Lower pixel index (x dimension) */ \
+   int lo_y;                     /* Lower pixel index (y dimension) */ \
+   int off;                      /* Total offset to input pixel */ \
+   int off_in;                   /* Offset to input pixel */ \
+   int off_lo;                   /* Offset to "first" input pixel */ \
+   int off_out;                  /* Offset to output pixel */ \
+   int point;                    /* Loop counter for output points */ \
+   int s;                        /* Temporary variable for strides */ \
+   int usebad;                   /* Use "bad" input pixel values? */ \
+   int usevar;                   /* Process variance array? */ \
+   int varwgt;                   /* Use input variances as weights? */ \
+   int ystride;                  /* Stride along input grid y dimension */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Initialise variables to avoid "used of uninitialised variable" \
+   messages from dumb compilers. */ \
+   bad = 0; \
+\
+/* Determine if we are processing bad pixels or variances. */ \
+   usebad = flags & AST__USEBAD; \
+   usevar = 0; \
+   genvar = 0; \
+   if( flags & AST__GENVAR ) { \
+      genvar = out_var && work; \
+   } else if( flags & AST__USEVAR ) { \
+      usevar = in_var && out_var; \
+   } \
+   varwgt = ( flags & AST__VARWGT ) && in_var && work; \
+\
+/* Handle the 1-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   if ( ndim_out == 1 ) { \
+\
+/* Calculate the coordinate limits of the input grid. */ \
+      xmin = (double) lbnd_out[ 0 ] - 0.5; \
+      xmax = (double) ubnd_out[ 0 ] + 0.5; \
+\
+/* Identify eight cases, according to whether bad pixels and/or variances \
+   are being processed and/or used. In each case we assign constant values \
+   (0 or 1) to the "Usebad", "Usevar" and "Varwgt" flags so that code for \
+   handling bad pixels and variances can be eliminated by the compiler's \
+   optimisation system when not required. */ \
+      if( varwgt ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               LINEAR_1D(X,Xtype,1,1,0,IntType,1) \
+            } else if ( genvar ) { \
+               LINEAR_1D(X,Xtype,1,0,1,IntType,1) \
+            } else { \
+               LINEAR_1D(X,Xtype,1,0,0,IntType,1) \
+            } \
+         } else { \
+            if ( usevar ) { \
+               LINEAR_1D(X,Xtype,0,1,0,IntType,1) \
+            } else if ( genvar ) { \
+               LINEAR_1D(X,Xtype,0,0,1,IntType,1) \
+            } else { \
+               LINEAR_1D(X,Xtype,0,0,0,IntType,1) \
+            } \
+         } \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               LINEAR_1D(X,Xtype,1,1,0,IntType,0) \
+            } else if ( genvar ) { \
+               LINEAR_1D(X,Xtype,1,0,1,IntType,0) \
+            } else { \
+               LINEAR_1D(X,Xtype,1,0,0,IntType,0) \
+            } \
+         } else { \
+            if ( usevar ) { \
+               LINEAR_1D(X,Xtype,0,1,0,IntType,0) \
+            } else if ( genvar ) { \
+               LINEAR_1D(X,Xtype,0,0,1,IntType,0) \
+            } else { \
+               LINEAR_1D(X,Xtype,0,0,0,IntType,0) \
+            } \
+         } \
+      } \
+\
+/* Handle the 2-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   } else if ( ndim_out == 2 ) { \
+\
+/* Calculate the stride along the y dimension of the output grid. */ \
+      ystride = ubnd_out[ 0 ] - lbnd_out[ 0 ] + 1; \
+\
+/* Calculate the coordinate limits of the output grid in each \
+   dimension. */ \
+      xmin = (double) lbnd_out[ 0 ] - 0.5; \
+      xmax = (double) ubnd_out[ 0 ] + 0.5; \
+      ymin = (double) lbnd_out[ 1 ] - 0.5; \
+      ymax = (double) ubnd_out[ 1 ] + 0.5; \
+\
+/* Identify eight cases, according to whether bad pixels and/or variances \
+   are being processed and/or used. In each case we assign constant values \
+   (0 or 1) to the "Usebad", "Usevar" and "Varwgt" flags so that code for \
+   handling bad pixels and variances can be eliminated by the compiler's \
+   optimisation system when not required. */ \
+      if( varwgt ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               LINEAR_2D(X,Xtype,1,1,0,IntType,1) \
+            } else if ( genvar ) { \
+               LINEAR_2D(X,Xtype,1,0,1,IntType,1) \
+            } else { \
+               LINEAR_2D(X,Xtype,1,0,0,IntType,1) \
+            } \
+         } else { \
+            if ( usevar ) { \
+               LINEAR_2D(X,Xtype,0,1,0,IntType,1) \
+            }else if ( genvar ) { \
+               LINEAR_2D(X,Xtype,0,0,1,IntType,1) \
+            } else { \
+               LINEAR_2D(X,Xtype,0,0,0,IntType,1) \
+            } \
+         } \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               LINEAR_2D(X,Xtype,1,1,0,IntType,0) \
+            } else if ( genvar ) { \
+               LINEAR_2D(X,Xtype,1,0,1,IntType,0) \
+            } else { \
+               LINEAR_2D(X,Xtype,1,0,0,IntType,0) \
+            } \
+         } else { \
+            if ( usevar ) { \
+               LINEAR_2D(X,Xtype,0,1,0,IntType,0) \
+            }else if ( genvar ) { \
+               LINEAR_2D(X,Xtype,0,0,1,IntType,0) \
+            } else { \
+               LINEAR_2D(X,Xtype,0,0,0,IntType,0) \
+            } \
+         } \
+      } \
+\
+/* Handle other numbers of dimensions. */ \
+/* ----------------------------------- */ \
+   } else { \
+\
+/* Allocate workspace. */ \
+      dim = astMalloc( sizeof( int ) * (size_t) ndim_out ); \
+      frac_hi = astMalloc( sizeof( double ) * (size_t) ndim_out ); \
+      frac_lo = astMalloc( sizeof( double ) * (size_t) ndim_out ); \
+      hi = astMalloc( sizeof( int ) * (size_t) ndim_out ); \
+      lo = astMalloc( sizeof( int ) * (size_t) ndim_out ); \
+      stride = astMalloc( sizeof( int ) * (size_t) ndim_out ); \
+      wt = astMalloc( sizeof( double ) * (size_t) ndim_out ); \
+      wtprod = astMalloc( sizeof( double ) * (size_t) ndim_out ); \
+      xn_max = astMalloc( sizeof( double ) * (size_t) ndim_out ); \
+      xn_min = astMalloc( sizeof( double ) * (size_t) ndim_out ); \
+      if ( astOK ) { \
+\
+/* Calculate the stride along each dimension of the output grid. */ \
+         for ( s = 1, idim = 0; idim < ndim_out; idim++ ) { \
+            stride[ idim ] = s; \
+            s *= ubnd_out[ idim ] - lbnd_out[ idim ] + 1; \
+\
+/* Calculate the coordinate limits of the output grid in each \
+   dimension. */ \
+            xn_min[ idim ] = (double) lbnd_out[ idim ] - 0.5; \
+            xn_max[ idim ] = (double) ubnd_out[ idim ] + 0.5; \
+         } \
+\
+/* Identify eight cases, according to whether bad pixels and/or variances \
+   are being processed and/or used. In each case we assign constant values \
+   (0 or 1) to the "Usebad", "Usevar" and "Varwgt" flags so that code for \
+   handling bad pixels and variances can be eliminated by the compiler's \
+   optimisation system when not required. */ \
+         if( varwgt ) { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  LINEAR_ND(X,Xtype,1,1,0,IntType,1) \
+               } else if ( genvar ) { \
+                  LINEAR_ND(X,Xtype,1,0,1,IntType,1) \
+               } else { \
+                  LINEAR_ND(X,Xtype,1,0,0,IntType,1) \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  LINEAR_ND(X,Xtype,0,1,0,IntType,1) \
+               } else if ( genvar ) { \
+                  LINEAR_ND(X,Xtype,0,0,1,IntType,1) \
+               } else { \
+                  LINEAR_ND(X,Xtype,0,0,0,IntType,1) \
+               } \
+            } \
+         } else { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  LINEAR_ND(X,Xtype,1,1,0,IntType,0) \
+               } else if ( genvar ) { \
+                  LINEAR_ND(X,Xtype,1,0,1,IntType,0) \
+               } else { \
+                  LINEAR_ND(X,Xtype,1,0,0,IntType,0) \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  LINEAR_ND(X,Xtype,0,1,0,IntType,0) \
+               } else if ( genvar ) { \
+                  LINEAR_ND(X,Xtype,0,0,1,IntType,0) \
+               } else { \
+                  LINEAR_ND(X,Xtype,0,0,0,IntType,0) \
+               } \
+            } \
+         } \
+      } \
+\
+/* Free the workspace. */ \
+      dim = astFree( dim ); \
+      frac_hi = astFree( frac_hi ); \
+      frac_lo = astFree( frac_lo ); \
+      hi = astFree( hi ); \
+      lo = astFree( lo ); \
+      stride = astFree( stride ); \
+      wt = astFree( wt ); \
+      wtprod = astFree( wtprod ); \
+      xn_max = astFree( xn_max ); \
+      xn_min = astFree( xn_min ); \
+   } \
+\
+}
+
+
+
+
+
+
+#define LINEAR_1D(X,Xtype,Usebad,Usevar,Genvar,IntType,Varwgt) \
+\
+/* Loop round all input points which are to be rebinned. */ \
+   for( point = 0; point < npoint; point++ ) { \
+\
+/* Obtain the input data value which is to be added into the output array. */ \
+      off_in = offset[ point ]; \
+      in_val = in[ off_in ]; \
+\
+/* If necessary, test if the input data value or variance is bad. If we \
+   are using the reciprocal of the input variances as weights, then \
+   variance values of zero are also effectively bad (but we can use input \
+   variances of zero otherwise). */ \
+      if ( Usebad ) { \
+         bad = ( in_val == badval ); \
+         if ( Varwgt ) { \
+            bad = bad || ( in_var[ off_in ] == badval ) \
+                      || ( in_var[ off_in ] <= 0.0 ); \
+         } else if ( Usevar ) { \
+            bad = bad || ( in_var[ off_in ] == badval ); \
+         } \
+      } else { \
+         if ( Varwgt ) { \
+            bad = ( in_var[ off_in ] <= 0.0 ); \
+         } else { \
+            bad = 0; \
+         } \
+      } \
+\
+/* Obtain the x coordinate of the current point and test if it lies \
+   outside the output grid. Also test if it is bad. */ \
+      x = coords[ 0 ][ point ]; \
+      bad = bad || ( x < xmin ) || ( x >= xmax ) || ( x == AST__BAD ); \
+\
+/* If OK, obtain the indices along the output grid x dimension of the \
+   two adjacent output pixels which will receive contributions from the \
+   input pixel. Also obtain the fractional weight to be applied to each of \
+   these pixels. */ \
+      if ( !bad ) { \
+         lo_x = (int) floor( x ); \
+         hi_x = lo_x + 1; \
+         frac_lo_x = (double) hi_x - x; \
+         frac_hi_x = 1.0 - frac_lo_x; \
+\
+/* Increment the number of input pixels pasted into the output array. */ \
+         if( nused ) (*nused)++; \
+\
+/* Obtain the offset within the output array of the first pixel to be \
+   updated (the one with the smaller index). */ \
+         off_lo = lo_x - lbnd_out[ 0 ]; \
+\
+/* If we are using the input data variances as weights, calculate the \
+   weight, and scale the fractions of each input pixel by the weight. */ \
+         if( Varwgt ) { \
+            wgt = 1.0/in_var[ off_in ]; \
+            frac_lo_x *= wgt; \
+            frac_hi_x *= wgt; \
+         } \
+\
+/* For each of the two pixels which may be updated, test if the pixel index \
+   lies within the output grid. Where it does, update the output pixel \
+   with the required fraction of the input pixel value. */ \
+         if ( lo_x >= lbnd_out[ 0 ] ) { \
+            pfac = frac_lo_x*infac; \
+            c = CONV(IntType,pfac*in_val); \
+            out[ off_lo ] += CONV(IntType, c ); \
+            if( work ) work[ off_lo ] += frac_lo_x; \
+            if ( Usevar ) { \
+               out_var[ off_lo ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+            } else if ( Genvar && frac_lo_x != 0.0 ) { \
+               out_var[ off_lo ] += c*c/frac_lo_x; \
+               work[ off_lo + npix_out ] += frac_lo_x*frac_lo_x; \
+            } \
+         } \
+         if ( hi_x <= ubnd_out[ 0 ] ) { \
+            pfac = frac_hi_x*infac; \
+            c = CONV(IntType,pfac*in_val); \
+            out[ off_lo + 1 ] += CONV(IntType, c ); \
+            if( work ) work[ off_lo + 1 ] += frac_hi_x; \
+            if ( Usevar ) { \
+               out_var[ off_lo + 1 ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+            } else if ( Genvar && frac_hi_x != 0.0 ) { \
+               out_var[ off_lo + 1 ] += c*c/frac_hi_x; \
+               work[ off_lo + 1 + npix_out ] += frac_hi_x*frac_hi_x; \
+            } \
+         } \
+      } \
+   }
+
+
+
+
+#define LINEAR_2D(X,Xtype,Usebad,Usevar,Genvar,IntType,Varwgt) \
+\
+/* Loop round all input points which are to be rebinned. */ \
+   for( point = 0; point < npoint; point++ ) { \
+\
+/* Obtain the input data value which is to be added into the output array. */ \
+      off_in = offset[ point ]; \
+      in_val = in[ off_in ]; \
+\
+/* If necessary, test if the input data value or variance is bad. If we \
+   are using the reciprocal of the input variances as weights, then \
+   variance values of zero are also effectively bad (but we can use input \
+   variances of zero otherwise). */ \
+      if ( Usebad ) { \
+         bad = ( in_val == badval ); \
+         if ( Varwgt ) { \
+            bad = bad || ( in_var[ off_in ] == badval ) \
+                      || ( in_var[ off_in ] <= 0.0 ); \
+         } else if ( Usevar ) { \
+            bad = bad || ( in_var[ off_in ] == badval ); \
+         } \
+      } else { \
+         if ( Varwgt ) { \
+            bad = ( in_var[ off_in ] <= 0.0 ); \
+         } else { \
+            bad = 0; \
+         } \
+      } \
+\
+/* Obtain the x coordinate of the current point and test if it lies \
+   outside the output grid. Also test if it is bad. */ \
+      y = coords[ 1 ][ point ]; \
+      bad = bad || ( y < ymin ) || ( y >= ymax ) || ( y == AST__BAD ); \
+      if ( !bad ) { \
+\
+/* Similarly obtain and test the y coordinate. */ \
+         x = coords[ 0 ][ point ]; \
+         bad = bad || ( x < xmin ) || ( x >= xmax ) || ( x == AST__BAD ); \
+         if ( !bad ) { \
+\
+/* Increment the number of input pixels pasted into the output array. */ \
+            if( nused ) (*nused)++; \
+\
+/* If OK, obtain the indices along the output grid x dimension of the \
+   two adjacent pixels which recieve contributions from the input pixel. \
+   Also obtain the fractional weight to be applied to each of \
+   these pixels. */ \
+            lo_x = (int) floor( x ); \
+            hi_x = lo_x + 1; \
+            frac_lo_x = (double) hi_x - x; \
+            frac_hi_x = 1.0 - frac_lo_x; \
+\
+/* Repeat this process for the y dimension. */ \
+            lo_y = (int) floor( y ); \
+            hi_y = lo_y + 1; \
+            frac_lo_y = (double) hi_y - y; \
+            frac_hi_y = 1.0 - frac_lo_y; \
+\
+/* If we are using the input data variances as weights, calculate the \
+   weight, and scale the fractions of each input pixel by the weight. \
+   Since the product of two fractions is always used to scale the input \
+   data values, we use the square root of the reciprocal of the variance \
+   as the weight (so that when the product of two fractions is taken, \
+   the square roots multiply together to give the required 1/variance \
+   weight).  */ \
+            if( Varwgt ) { \
+               wgt = 1.0/sqrt( in_var[ off_in ] ); \
+               frac_lo_x *= wgt; \
+               frac_hi_x *= wgt; \
+               frac_lo_y *= wgt; \
+               frac_hi_y *= wgt; \
+            } \
+\
+/* Obtain the offset within the output array of the first pixel to be \
+   updated (the one with the smaller index along both dimensions). */ \
+            off_lo = lo_x - lbnd_out[ 0 ] + ystride * ( lo_y - lbnd_out[ 1 ] ); \
+\
+/* For each of the four pixels which may be updated, test if the pixel indices \
+   lie within the output grid. Where they do, update the output pixel \
+   with the required fraction of the input pixel value. */ \
+            if ( lo_y >= lbnd_out[ 1 ] ) { \
+               if ( lo_x >= lbnd_out[ 0 ] ) { \
+                  pixwt = frac_lo_x * frac_lo_y; \
+                  pfac = pixwt*infac; \
+                  c = CONV(IntType,pfac*in_val); \
+                  out[ off_lo ] += CONV(IntType, c ); \
+                  if( work ) work[ off_lo ] += pixwt; \
+                  if ( Usevar ) { \
+                     out_var[ off_lo ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+                  } else if ( Genvar && pixwt != 0.0 ) { \
+                     out_var[ off_lo ] += c*c/pixwt; \
+                     work[ off_lo + npix_out ] += pixwt*pixwt; \
+                  } \
+               } \
+               if ( hi_x <= ubnd_out[ 0 ] ) { \
+                  off = off_lo + 1; \
+                  pixwt = frac_hi_x * frac_lo_y; \
+                  pfac = pixwt*infac; \
+                  c = CONV(IntType,pfac*in_val); \
+                  out[ off ] += CONV(IntType, c ); \
+                  if( work ) work[ off ] += pixwt; \
+                  if ( Usevar ) { \
+                     out_var[ off ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+                  } else if ( Genvar && pixwt != 0.0 ) { \
+                     out_var[ off ] += c*c/pixwt; \
+                     work[ off + npix_out ] += pixwt*pixwt; \
+                  } \
+               } \
+            } \
+            if ( hi_y <= ubnd_out[ 1 ] ) { \
+               if ( lo_x >= lbnd_out[ 0 ] ) { \
+                  off = off_lo + ystride; \
+                  pixwt = frac_lo_x * frac_hi_y; \
+                  pfac = pixwt*infac; \
+                  c = CONV(IntType,pfac*in_val); \
+                  out[ off ] += CONV(IntType, c ); \
+                  if( work ) work[ off ] += pixwt; \
+                  if ( Usevar ) { \
+                     out_var[ off ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+                  } else if ( Genvar && pixwt != 0.0 ) { \
+                     out_var[ off ] += c*c/pixwt; \
+                     work[ off + npix_out ] += pixwt*pixwt; \
+                  } \
+               } \
+               if ( hi_x <= ubnd_out[ 0 ] ) { \
+                  off = off_lo + ystride + 1; \
+                  pixwt = frac_hi_x * frac_hi_y; \
+                  pfac = pixwt*infac; \
+                  c = CONV(IntType,pfac*in_val); \
+                  out[ off ] += CONV(IntType, c ); \
+                  if( work ) work[ off ] += pixwt; \
+                  if ( Usevar ) { \
+                     out_var[ off ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+                  } else if ( Genvar && pixwt != 0.0 ) { \
+                     out_var[ off ] += c*c/pixwt; \
+                     work[ off + npix_out ] += pixwt*pixwt; \
+                  } \
+               } \
+            } \
+         } \
+      } \
+   }
+
+
+#define LINEAR_ND(X,Xtype,Usebad,Usevar,Genvar,IntType,Varwgt) \
+\
+/* Loop round all input points which are to be rebinned. */ \
+   for( point = 0; point < npoint; point++ ) { \
+\
+/* Obtain the input data value which is to be added into the output array. */ \
+      off_in = offset[ point ]; \
+      in_val = in[ off_in ]; \
+\
+/* If necessary, test if the input data value or variance is bad. If we \
+   are using the reciprocal of the input variances as weights, then \
+   variance values of zero are also effectively bad (but we can use input \
+   variances of zero otherwise). */ \
+      if ( Usebad ) { \
+         bad = ( in_val == badval ); \
+         if ( Varwgt ) { \
+            bad = bad || ( in_var[ off_in ] == badval ) \
+                      || ( in_var[ off_in ] <= 0.0 ); \
+         } else if ( Usevar ) { \
+            bad = bad || ( in_var[ off_in ] == badval ); \
+         } \
+      } else { \
+         if ( Varwgt ) { \
+            bad = ( in_var[ off_in ] <= 0.0 ); \
+         } else { \
+            bad = 0; \
+         } \
+      } \
+\
+/* Initialise offsets into the output array. Then loop to obtain each \
+   coordinate associated with the current output point. */ \
+      if( !bad ) { \
+         off_out = 0; \
+         for ( idim = 0; idim < ndim_out; idim++ ) { \
+            xn = coords[ idim ][ point ]; \
+\
+/* Test if the coordinate lies outside the output grid.  Also test if \
+   it is bad. If either is true, the corresponding output pixel value \
+   will be bad, so give up on this point. */ \
+            bad = ( xn < xn_min[ idim ] ) || ( xn >= xn_max[ idim ] ) || \
+                  ( xn == AST__BAD ); \
+            if ( bad ) break; \
+\
+/* Obtain the indices along the current dimension of the output grid of \
+   the two (usually adjacent) pixels which will be updated. If necessary, \
+   however, restrict each index to ensure it does not lie outside the \
+   input grid. Also calculate the fractional weight to be given to each \
+   pixel in order to divide the input value linearly between them. */ \
+            ixn = (int) floor( xn ); \
+            lo[ idim ] = MaxI( ixn, lbnd_out[ idim ], status ); \
+            hi[ idim ] = MinI( ixn + 1, ubnd_out[ idim ], status ); \
+            frac_lo[ idim ] = 1.0 - fabs( xn - (double) lo[ idim ] ); \
+            frac_hi[ idim ] = 1.0 - fabs( xn - (double) hi[ idim ] ); \
+\
+/* Store the lower index involved in spreading along each \
+   dimension and accumulate the offset from the start of the output \
+   array of the pixel which has these indices. */ \
+            dim[ idim ] = lo[ idim ]; \
+            off_out += stride[ idim ] * ( lo[ idim ] - lbnd_out[ idim ] ); \
+\
+/* Also store the fractional weight associated with the lower pixel \
+   along each dimension. */ \
+            wt[ idim ] = frac_lo[ idim ]; \
+         } \
+\
+/* If we are using the input data variances as weights, calculate the \
+   weight, and scale the fractions of each input pixel by the weight. */ \
+         if( Varwgt ) { \
+            wgt = pow( in_var[ off_in ], -1.0/(double)ndim_out ); \
+            for ( idim = 0; idim < ndim_out; idim++ ) { \
+               frac_lo[ idim ] *= wgt; \
+               frac_hi[ idim ] *= wgt; \
+               wt[ idim ] = frac_lo[ idim ]; \
+            } \
+         } \
+\
+/* If OK, increment the number of input pixels pasted into the output array. */ \
+         if ( !bad ) { \
+            if( nused ) (*nused)++; \
+\
+/* Loop over adjacent output pixels to divide up the input value. */ \
+            idim = ndim_out - 1; \
+            wtprod[ idim ] = 1.0; \
+            done = 0; \
+            do { \
+\
+/* Each pixel pixel to be updated has a total weight equal to the product \
+   of the weights which account for the displacement of its centre from \
+   the required position along each dimension. However, since we typically \
+   only change the index of one dimension at a time, we can avoid forming \
+   this product repeatedly by retaining an array of accumulated weight \
+   products for all higher dimensions. We need then only update the \
+   lower elements in this array, corresponding to those dimensions \
+   whose index has changed. We do this here, "idim" being the index of \
+   the most significant dimension to have changed. Note that on the \
+   first pass, all dimensions are considered changed, causing this \
+   array to be initialised. */ \
+               for ( ii = idim; ii >= 1; ii-- ) { \
+                  wtprod[ ii - 1 ] = wtprod[ ii ] * wt[ ii ]; \
+               } \
+\
+/* Update the relevent output pixel. The pixel weight is formed by including \
+   the weight factor for dimension zero, since this is not included in \
+   the "wtprod" array. */ \
+               pixwt = wtprod[ 0 ] * wt[ 0 ]; \
+               pfac = pixwt*infac; \
+               c = CONV(IntType,pfac*in_val); \
+               out[ off_out ] += CONV(IntType, c ); \
+               if( work ) work[ off_out ] += pixwt; \
+               if ( Usevar ) { \
+                  out_var[ off_out ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+               } else if ( Genvar && pixwt != 0.0 ) { \
+                  out_var[ off_out ] += c*c/pixwt; \
+                  work[ off_out + npix_out ] += pixwt*pixwt; \
+               } \
+\
+/* Now update the indices, offset and weight factors to refer to the \
+   next output pixel to be updated. */ \
+               idim = 0; \
+               do { \
+\
+/* The first input dimension which still refers to the pixel with the \
+   lower of the two possible indices is switched to refer to the other \
+   pixel (with the higher index). The offset into the output array and \
+   the fractional weight factor for this dimension are also updated \
+   accordingly. */ \
+                  if ( dim[ idim ] != hi[ idim ] ) { \
+                     dim[ idim ] = hi[ idim ]; \
+                     off_out += stride[ idim ]; \
+                     wt[ idim ] = frac_hi[ idim ]; \
+                     break; \
+\
+/* Any earlier dimensions (referring to the higher index) are switched \
+   back to the lower index, if not already there, before going on to \
+   consider the next dimension. (This process is the same as \
+   incrementing a binary number and propagating overflows up through \
+   successive digits, except that dimensions where the "lo" and "hi" \
+   values are the same can only take one value.) The process stops at \
+   the first attempt to return the final dimension to the lower \
+   index. */ \
+                  } else { \
+                     if ( dim[ idim ] != lo[ idim ] ) { \
+                        dim[ idim ] = lo[ idim ]; \
+                        off_out -= stride[ idim ]; \
+                        wt[ idim ] = frac_lo[ idim ]; \
+                     } \
+                     done = ( ++idim == ndim_out ); \
+                  } \
+               } while ( !done ); \
+            } while ( !done ); \
+         } \
+      } \
+   }
+
+/* Expand the main macro above to generate a function for each
+   required signed data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_SPREAD_LINEAR(LD,long double,0)
+#endif
+MAKE_SPREAD_LINEAR(D,double,0)
+MAKE_SPREAD_LINEAR(F,float,0)
+MAKE_SPREAD_LINEAR(I,int,1)
+MAKE_SPREAD_LINEAR(B,signed char,1)
+MAKE_SPREAD_LINEAR(UB,unsigned char,1)
+
+/* Undefine the macros used above. */
+#undef LINEAR_1D
+#undef LINEAR_2D
+#undef LINEAR_ND
+#undef MAKE_SPREAD_LINEAR
+
+/*
+*  Name:
+*     SpreadNearest<X>
+
+*  Purpose:
+*     Rebin a data grid, using the nearest-pixel spreading scheme.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void SpreadNearest<X>( int ndim_out, const int *lbnd_out,
+*                            const int *ubnd_out, const <Xtype> *in,
+*                            const <Xtype> *in_var, double infac, int npoint,
+*                            const int *offset, const double *const *coords,
+*                            int flags, <Xtype> badval, int npix_out, <Xtype> *out,
+*                            <Xtype> *out_var, double *work, int64_t *nused,
+*                            int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This is a set of functions which rebins a rectangular region of an
+*     input grid of data (and, optionally, associated statistical variance
+*     values) so as to place them into a new output grid. Each input
+*     grid point may be mapped on to a position in the output grid in
+*     an arbitrary way. Where the positions given do not correspond
+*     with a pixel centre in the output grid, the spreading scheme
+*     used is simply to select the nearest pixel (i.e. the one whose
+*     bounds contain the supplied position).
+
+*  Parameters:
+*     ndim_out
+*        The number of dimensions in the output grid. This should be at
+*        least one.
+*     lbnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the output grid along each dimension.
+*     ubnd_out
+*        Pointer to an array of integers, with "ndim_out" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the output grid along each dimension.
+*
+*        Note that "lbnd_out" and "ubnd_out" together define the shape
+*        and size of the output grid, its extent along a particular
+*        (i'th) dimension being ubnd_out[i]-lbnd_out[i]+1 (assuming "i"
+*        is zero-based). They also define the output grid's coordinate
+*        system, with each pixel being of unit extent along each
+*        dimension with integral coordinate values at its centre.
+*     in
+*        Pointer to the array of data to be rebinned. The numerical type
+*        of these data should match the function used, as given by the
+*        suffix on the function name. Note that details of how the input
+*        grid maps on to this array (e.g. the storage order, number of
+*        dimensions, etc.) is arbitrary and is specified entirely by means
+*        of the "offset" array. The "in" array should therefore contain
+*        sufficient elements to accommodate the "offset" values supplied.
+*        There is no requirement that all elements of the "in" array
+*        should be rebinned, and any which are not addressed by the
+*        contents of the "offset" array will be ignored.
+*     in_var
+*        An optional pointer to a second array of positive numerical
+*        values (with the same size and type as the "in" array), which
+*        represent estimates of the statistical variance associated
+*        with each element of the "in" array. If this second array is
+*        given (along with the corresponding "out_var" array), then
+*        estimates of the variance of the resampled data will also be
+*        returned. It is addressed in exactly the same way (via the
+*        "offset" array) as the "in" array.
+*
+*        If no variance estimates are required, a NULL pointer should
+*        be given.
+*     infac
+*        A factor by which to multiply the input data values before use.
+*     npoint
+*        The number of input points which are to be rebinned.
+*     offset
+*        Pointer to an array of integers with "npoint" elements. For
+*        each input point, this array should contain the zero-based
+*        offset in the input array(s) (i.e. the "in" and, optionally,
+*        the "in_var" arrays) from which the value to be rebinned should
+*        be obtained.
+*     coords
+*        An array of pointers to double, with "ndim_out" elements.
+*        Element "coords[coord]" should point at the first element of
+*        an array of double (with "npoint" elements) which contains the
+*        values of coordinate number "coord" for each point being
+*        rebinned. The value of coordinate number "coord" for
+*        rebinning point number "point" is therefore given by
+*        "coords[coord][point]" (assuming both indices are
+*        zero-based).  If any point has a coordinate value of AST__BAD
+*        associated with it, then the corresponding input data (and
+*        variance) value will be ignored.
+*     flags
+*        The bitwise OR of a set of flag values which control the
+*        operation of the function. These are chosend from:
+*
+*        - AST__USEBAD: indicates whether there are "bad" (i.e. missing) data
+*        in the input array(s) which must be recognised.  If this flag is not
+*        set, all input values are treated literally.
+*        - AST__GENVAR: Indicates that output variances should be generated
+*        from the spread of values contributing to each output pixel.
+*        - AST__USEVAR: Indicates that output variances should be generated
+*        by rebinning the input variances.
+*        - AST__VARWGT: Indicates that input variances should be used to
+*        create weights for the input data values.
+*
+*        Only one of AST__GENVAR and AST__USEVAR should be supplied.
+*     badval
+*        If the AST__USEBAD flag is set in the "flags" value (above),
+*        this parameter specifies the value which is used to identify
+*        bad data and/or variance values in the input array(s). Its
+*        numerical type must match that of the "in" (and "in_var")
+*        arrays. The same value will also be used to flag any output
+*        array elements for which resampled values could not be
+*        obtained.  The output arrays(s) may be flagged with this
+*        value whether or not the AST__USEBAD flag is set (the
+*        function return value indicates whether any such values have
+*        been produced).
+*     npix_out
+*        Number of pixels in output array.
+*     out
+*        Pointer to an array with the same data type as the "in"
+*        array, into which the rebinned data will be returned. The
+*        storage order should be such that the index of the first grid
+*        dimension varies most rapidly and that of the final dimension
+*        least rapidly (i.e. Fortran array storage order).
+*     out_var
+*        An optional pointer to an array with the same data type and
+*        size as the "out" array, into which variance estimates for
+*        the rebinned values may be returned. This array will only be
+*        used if the "in_var" array has been given. The values returned
+*        are estimates of the statistical variance of the corresponding
+*        values in the "out" array, on the assumption that all errors in
+*        input grid values (in the "in" array) are statistically independent
+*        and that their variance estimates (in the "in_var" array) may
+*        simply be summed (with appropriate weighting factors).
+*
+*        If no output variance estimates are required, a NULL pointer
+*        should be given.
+*     work
+*        A pointer to an array with the same data type and size as the "out"
+*        array which is used as work space. The values in the supplied
+*        array are incremented on exit by the sum of the weights used
+*        with each output pixel.
+*     nused
+*        An optional pointer to a size_t which will be incremented by the
+*        number of input values pasted into the output array. Ignored if NULL.
+
+*  Notes:
+*     - There is a separate function for each numerical type of
+*     gridded data, distinguished by replacing the <X> in the function
+*     name by the appropriate 1- or 2-character suffix.
+*/
+/* Define a macro to implement the function for a specific data type. */
+#define MAKE_SPREAD_NEAREST(X,Xtype,IntType) \
+static void SpreadNearest##X( int ndim_out, \
+                             const int *lbnd_out, const int *ubnd_out, \
+                             const Xtype *in, const Xtype *in_var, \
+                             double infac, int npoint, const int *offset, \
+                             const double *const *coords, int flags, \
+                             Xtype badval, int npix_out, Xtype *out, \
+                             Xtype *out_var, double *work, int64_t *nused, \
+                             int *status ) { \
+\
+/* Local Variables: */ \
+   Xtype c;                      /* Contribution to output value */ \
+   Xtype in_val;                 /* Input data value */ \
+   double *xn_max;               /* Pointer to upper limits array (n-d) */ \
+   double *xn_min;               /* Pointer to lower limits array (n-d) */ \
+   double pfac;                  /* Input weight with extra supplied factor */ \
+   double pixwt;                 /* Weight for input value */ \
+   double x;                     /* x coordinate value */ \
+   double xmax;                  /* x upper limit */ \
+   double xmin;                  /* x lower limit */ \
+   double xn;                    /* Coordinate value (n-d) */ \
+   double y;                     /* y coordinate value */ \
+   double ymax;                  /* y upper limit */ \
+   double ymin;                  /* y lower limit */ \
+   int *stride;                  /* Pointer to array of dimension strides */ \
+   int bad;                      /* Output pixel bad? */ \
+   int genvar;                   /* Generate output variances? */ \
+   int idim;                     /* Loop counter for dimensions */ \
+   int ix;                       /* Number of pixels offset in x direction */ \
+   int ixn;                      /* Number of pixels offset (n-d) */ \
+   int iy;                       /* Number of pixels offset in y direction */ \
+   int off_in;                   /* Pixel offset into input array */ \
+   int off_out;                  /* Pixel offset into output array */ \
+   int point;                    /* Loop counter for output points */ \
+   int s;                        /* Temporary variable for strides */ \
+   int usebad;                   /* Use "bad" input pixel values? */ \
+   int usevar;                   /* Process variance array? */ \
+   int varwgt;                   /* Use input variances as weights? */ \
+   int ystride;                  /* Stride along input grid y direction */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Determine if we are processing bad pixels or variances. */ \
+   usebad = flags & AST__USEBAD; \
+   usevar = 0; \
+   genvar = 0; \
+   if( flags & AST__GENVAR ) { \
+      genvar = out_var && work; \
+   } else if( flags & AST__USEVAR ) { \
+      usevar = in_var && out_var; \
+   } \
+   varwgt = ( flags & AST__VARWGT ) && in_var && work; \
+\
+/* Handle the 1-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   if ( ndim_out == 1 ) { \
+\
+/* Calculate the coordinate limits of the output array. */ \
+      xmin = (double) lbnd_out[ 0 ] - 0.5; \
+      xmax = (double) ubnd_out[ 0 ] + 0.5; \
+\
+/* Identify eight cases, according to whether bad pixels and/or variances \
+   are being processed and/or used. In each case we assign constant values \
+   (0 or 1) to the "Usebad", "Usevar" and "Varwgt" flags so that code for \
+   handling bad pixels and variances can be eliminated by the compiler's \
+   optimisation system when not required. */ \
+      if( varwgt ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               NEAR_1D(X,Xtype,1,1,0,IntType,1) \
+            } else if ( genvar ) { \
+               NEAR_1D(X,Xtype,1,0,1,IntType,1) \
+            } else { \
+               NEAR_1D(X,Xtype,1,0,0,IntType,1) \
+            } \
+         } else { \
+            if ( usevar ) { \
+               NEAR_1D(X,Xtype,0,1,0,IntType,1) \
+            } else if ( genvar ) { \
+               NEAR_1D(X,Xtype,0,0,1,IntType,1) \
+            } else { \
+               NEAR_1D(X,Xtype,0,0,0,IntType,1) \
+            } \
+         } \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               NEAR_1D(X,Xtype,1,1,0,IntType,0) \
+            } else if ( genvar ) { \
+               NEAR_1D(X,Xtype,1,0,1,IntType,0) \
+            } else { \
+               NEAR_1D(X,Xtype,1,0,0,IntType,0) \
+            } \
+         } else { \
+            if ( usevar ) { \
+               NEAR_1D(X,Xtype,0,1,0,IntType,0) \
+            } else if ( genvar ) { \
+               NEAR_1D(X,Xtype,0,0,1,IntType,0) \
+            } else { \
+               NEAR_1D(X,Xtype,0,0,0,IntType,0) \
+            } \
+         } \
+      } \
+\
+/* Handle the 2-dimensional case optimally. */ \
+/* ---------------------------------------- */ \
+   } else if ( ndim_out == 2 ) { \
+\
+/* Calculate the stride along the y dimension of the output grid. */ \
+      ystride = ubnd_out[ 0 ] - lbnd_out[ 0 ] + 1; \
+\
+/* Calculate the coordinate limits of the output array in each \
+   dimension. */ \
+      xmin = (double) lbnd_out[ 0 ] - 0.5; \
+      xmax = (double) ubnd_out[ 0 ] + 0.5; \
+      ymin = (double) lbnd_out[ 1 ] - 0.5; \
+      ymax = (double) ubnd_out[ 1 ] + 0.5; \
+\
+/* Identify eight cases, according to whether bad pixels and/or variances \
+   are being processed and/or used. In each case we assign constant values \
+   (0 or 1) to the "Usebad", "Usevar" and "Varwgt" flags so that code for \
+   handling bad pixels and variances can be eliminated by the compiler's \
+   optimisation system when not required. */ \
+      if( varwgt ) { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               NEAR_2D(X,Xtype,1,1,0,IntType,1) \
+            } else if ( genvar ) { \
+               NEAR_2D(X,Xtype,1,0,1,IntType,1) \
+            } else { \
+               NEAR_2D(X,Xtype,1,0,0,IntType,1) \
+            } \
+         } else { \
+            if ( usevar ) { \
+               NEAR_2D(X,Xtype,0,1,0,IntType,1) \
+            } else if ( genvar ) { \
+               NEAR_2D(X,Xtype,0,0,1,IntType,1) \
+            } else { \
+               NEAR_2D(X,Xtype,0,0,0,IntType,1) \
+            } \
+         } \
+      } else { \
+         if ( usebad ) { \
+            if ( usevar ) { \
+               NEAR_2D(X,Xtype,1,1,0,IntType,0) \
+            } else if ( genvar ) { \
+               NEAR_2D(X,Xtype,1,0,1,IntType,0) \
+            } else { \
+               NEAR_2D(X,Xtype,1,0,0,IntType,0) \
+            } \
+         } else { \
+            if ( usevar ) { \
+               NEAR_2D(X,Xtype,0,1,0,IntType,0) \
+            } else if ( genvar ) { \
+               NEAR_2D(X,Xtype,0,0,1,IntType,0) \
+            } else { \
+               NEAR_2D(X,Xtype,0,0,0,IntType,0) \
+            } \
+         } \
+      } \
+\
+/* Handle other numbers of dimensions. */ \
+/* ----------------------------------- */ \
+   } else { \
+\
+/* Allocate workspace. */ \
+      stride = astMalloc( sizeof( int ) * (size_t) ndim_out ); \
+      xn_max = astMalloc( sizeof( double ) * (size_t) ndim_out ); \
+      xn_min = astMalloc( sizeof( double ) * (size_t) ndim_out ); \
+      if ( astOK ) { \
+\
+/* Calculate the stride along each dimension of the output grid. */ \
+         for ( s = 1, idim = 0; idim < ndim_out; idim++ ) { \
+            stride[ idim ] = s; \
+            s *= ubnd_out[ idim ] - lbnd_out[ idim ] + 1; \
+\
+/* Calculate the coordinate limits of the output grid in each \
+   dimension. */ \
+            xn_min[ idim ] = (double) lbnd_out[ idim ] - 0.5; \
+            xn_max[ idim ] = (double) ubnd_out[ idim ] + 0.5; \
+         } \
+\
+/* Identify eight cases, according to whether bad pixels and/or variances \
+   are being processed and/or used. In each case we assign constant values \
+   (0 or 1) to the "Usebad", "Usevar" and "Varwgt" flags so that code for \
+   handling bad pixels and variances can be eliminated by the compiler's \
+   optimisation system when not required. */ \
+         if( varwgt ) { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  NEAR_ND(X,Xtype,1,1,0,IntType,1) \
+               } else if ( genvar ) { \
+                  NEAR_ND(X,Xtype,1,0,1,IntType,1) \
+               } else { \
+                  NEAR_ND(X,Xtype,1,0,0,IntType,1) \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  NEAR_ND(X,Xtype,0,1,0,IntType,1) \
+               } else if ( genvar ) { \
+                  NEAR_ND(X,Xtype,0,0,1,IntType,1) \
+               } else { \
+                  NEAR_ND(X,Xtype,0,0,0,IntType,1) \
+               } \
+            } \
+         } else { \
+            if ( usebad ) { \
+               if ( usevar ) { \
+                  NEAR_ND(X,Xtype,1,1,0,IntType,0) \
+               } else if ( genvar ) { \
+                  NEAR_ND(X,Xtype,1,0,1,IntType,0) \
+               } else { \
+                  NEAR_ND(X,Xtype,1,0,0,IntType,0) \
+               } \
+            } else { \
+               if ( usevar ) { \
+                  NEAR_ND(X,Xtype,0,1,0,IntType,0) \
+               } else if ( genvar ) { \
+                  NEAR_ND(X,Xtype,0,0,1,IntType,0) \
+               } else { \
+                  NEAR_ND(X,Xtype,0,0,0,IntType,0) \
+               } \
+            } \
+         } \
+      } \
+\
+/* Free the workspace. */ \
+      stride = astFree( stride ); \
+      xn_max = astFree( xn_max ); \
+      xn_min = astFree( xn_min ); \
+   } \
+\
+}
+
+
+
+
+
+#define NEAR_1D(X,Xtype,Usebad,Usevar,Genvar,IntType,Varwgt) \
+\
+/* Loop round all input points which are to be rebinned. */ \
+            for( point = 0; point < npoint; point++ ) { \
+\
+/* Obtain the input data value which is to be added into the output array. */ \
+               off_in = offset[ point ]; \
+               in_val = in[ off_in ]; \
+\
+/* If necessary, test if the input data value or variance is bad. If we \
+   are using the reciprocal of the input variances as weights, then \
+   variance values of zero are also effectively bad (but we can use input \
+   variances of zero otherwise). */ \
+               if ( Usebad ) { \
+                  bad = ( in_val == badval ); \
+                  if ( Varwgt ) { \
+                     bad = bad || ( in_var[ off_in ] == badval ) \
+                               || ( in_var[ off_in ] <= 0.0 ); \
+                  } else if ( Usevar ) { \
+                     bad = bad || ( in_var[ off_in ] == badval ); \
+                  } \
+               } else { \
+                  if ( Varwgt ) { \
+                     bad = ( in_var[ off_in ] <= 0.0 ); \
+                  } else { \
+                     bad = 0; \
+                  } \
+               } \
+\
+/* Obtain the output x coordinate corresponding to the centre of the \
+   current input pixel and test if it lies outside the output grid, or \
+   is bad. */ \
+               x = coords[ 0 ][ point ]; \
+               bad = bad || ( x < xmin ) || ( x >= xmax ) || ( x == AST__BAD ); \
+               if ( !bad ) { \
+\
+/* Increment the number of input pixels pasted into the output array. */ \
+                  if( nused ) (*nused)++; \
+\
+/* If not, then obtain the offset within the output grid of the pixel \
+   which contains the current input point. */ \
+                  off_out = (int) floor( x + 0.5 ) - lbnd_out[ 0 ]; \
+\
+/* If we are using the input data variances as weights, calculate the \
+   weight. */ \
+                  if( Varwgt ) { \
+                     pixwt = 1.0/in_var[ off_in ]; \
+                  } else { \
+                     pixwt = 1.0; \
+                  } \
+\
+/* Get the weighted input data value, including any extra scaling. */ \
+                  pfac = pixwt*infac; \
+                  c = CONV(IntType,pfac*in_val); \
+\
+/* Increment the value of this output pixel by the weighted input pixel \
+   value, and increment the sum of the weights. */ \
+                  out[ off_out ] += CONV(IntType, c ); \
+                  if( work ) work[ off_out ] += pixwt; \
+\
+/* If output variances are being calculated on the basis of the input \
+   variances, then we also store the required sum in "out_var". */ \
+                  if( Usevar ) { \
+                     out_var[ off_out ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+\
+/* If output variances are being calculated on the basis of the spread of \
+   input values, we need the sum of the squared weighted data values, the \
+   sum of the weights (already in the first half of the "work" array), and \
+   the sum of the squared weights. */ \
+                  } else if( Genvar && pixwt != 0.0 ) { \
+                     out_var[ off_out ] += c*c/pixwt; \
+                     work[ off_out + npix_out ] += pixwt*pixwt; \
+                  } \
+               } \
+            }
+
+
+
+
+
+
+#define NEAR_2D(X,Xtype,Usebad,Usevar,Genvar,IntType,Varwgt) \
+\
+/* Loop round all input points which are to be rebinned. */ \
+            for( point = 0; point < npoint; point++ ) { \
+\
+/* Obtain the input data value which is to be added into the output array. */ \
+               off_in = offset[ point ]; \
+               in_val = in[ off_in ]; \
+\
+/* If necessary, test if the input data value or variance is bad. If we \
+   are using the reciprocal of the input variances as weights, then \
+   variance values of zero are also effectively bad (but we can use input \
+   variances of zero otherwise). */ \
+               if ( Usebad ) { \
+                  bad = ( in_val == badval ); \
+                  if ( Varwgt ) { \
+                     bad = bad || ( in_var[ off_in ] == badval ) \
+                               || ( in_var[ off_in ] <= 0.0 ); \
+                  } else if ( Usevar ) { \
+                     bad = bad || ( in_var[ off_in ] == badval ); \
+                  } \
+               } else { \
+                  if ( Varwgt ) { \
+                     bad = ( in_var[ off_in ] <= 0.0 ); \
+                  } else { \
+                     bad = 0; \
+                  } \
+               } \
+\
+/* Obtain the output y coordinate corresponding to the centre of the \
+   current input pixel and test if it lies outside the output grid, or \
+   is bad. */ \
+               y = coords[ 1 ][ point ]; \
+               bad = bad || ( y < ymin ) || ( y >= ymax ) || ( y == AST__BAD ); \
+               if ( !bad ) { \
+\
+/* Obtain the output x coordinate corresponding to the centre of the \
+   current input pixel and test if it lies outside the output grid, or \
+   is bad. */ \
+                  x = coords[ 0 ][ point ]; \
+                  bad = bad || ( x < xmin ) || ( x >= xmax ) || ( x == AST__BAD ); \
+                  if ( !bad ) { \
+\
+/* Increment the number of input pixels pasted into the output array. */ \
+                     if( nused ) (*nused)++; \
+\
+/* Obtain the offsets along each output grid dimension of the output \
+   pixel which is to receive the input pixel value. */ \
+                     ix = (int) floor( x + 0.5 ) - lbnd_out[ 0 ]; \
+                     iy = (int) floor( y + 0.5 ) - lbnd_out[ 1 ]; \
+\
+/* Calculate this pixel's offset from the start of the output array. */ \
+                     off_out = ix + ystride * iy; \
+\
+/* If we are using the input data variances as weights, calculate the \
+   weight. */ \
+                     if( Varwgt ) { \
+                        pixwt = 1.0/in_var[ off_in ]; \
+                     } else { \
+                        pixwt = 1.0; \
+                     } \
+\
+/* Get the weighted input data value, including any extra scaling. */ \
+                     pfac = pixwt*infac; \
+                     c = CONV(IntType,pfac*in_val); \
+\
+/* Increment the value of this output pixel by the weighted input pixel \
+   value, and increment the sum of the weights. */ \
+                     out[ off_out ] += CONV(IntType, c ); \
+                     if( work ) work[ off_out ] += pixwt; \
+\
+/* If output variances are being calculated on the basis of the input \
+   variances, then we also store the required sum in "out_var". */ \
+                     if( Usevar ) { \
+                        out_var[ off_out ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+\
+/* If output variances are being calculated on the basis of the spread of \
+   input values, we need the sum of the squared weighted data values, the \
+   sum of the weights (already in the first half of the "work" array), and \
+   the sum of the squared weights. */ \
+                     } else if( Genvar && pixwt != 0.0 ) { \
+                        out_var[ off_out ] += c*c/pixwt; \
+                        work[ off_out + npix_out ] += pixwt*pixwt; \
+                     } \
+                  } \
+               } \
+            }
+
+
+
+#define NEAR_ND(X,Xtype,Usebad,Usevar,Genvar,IntType,Varwgt) \
+\
+/* Loop round all input points which are to be rebinned. */ \
+            for( point = 0; point < npoint; point++ ) { \
+\
+/* Obtain the input data value which is to be added into the output array. */ \
+               off_in = offset[ point ]; \
+               in_val = in[ off_in ]; \
+\
+/* If necessary, test if the input data value or variance is bad. If we \
+   are using the reciprocal of the input variances as weights, then \
+   variance values of zero are also effectively bad (but we can use input \
+   variances of zero otherwise). */ \
+               if ( Usebad ) { \
+                  bad = ( in_val == badval ); \
+                  if ( Varwgt ) { \
+                     bad = bad || ( in_var[ off_in ] == badval ) \
+                               || ( in_var[ off_in ] <= 0.0 ); \
+                  } else if ( Usevar ) { \
+                     bad = bad || ( in_var[ off_in ] == badval ); \
+                  } \
+               } else { \
+                  if ( Varwgt ) { \
+                     bad = ( in_var[ off_in ] <= 0.0 ); \
+                  } else { \
+                     bad = 0; \
+                  } \
+               } \
+\
+               if( !bad ) { \
+\
+/* Initialise the offset into the output array. Then loop to obtain \
+   each coordinate associated with the current output point. */ \
+                  off_out = 0; \
+                  for ( idim = 0; idim < ndim_out; idim++ ) { \
+                     xn = coords[ idim ][ point ]; \
+\
+/* Test if the coordinate lies outside the output grid, or is bad. If \
+   either is true, the corresponding input pixel value will be ignored, \
+   so give up on this point. */ \
+                     bad = ( xn < xn_min[ idim ] ) || ( xn >= xn_max[ idim ] ) || \
+                           ( xn == AST__BAD ); \
+                     if ( bad ) { \
+                         break; \
+                     } \
+\
+/* Obtain the offset along the current output grid dimension of the \
+   output pixel which is to receive the input pixel value. */ \
+                     ixn = (int) floor( xn + 0.5 ) - lbnd_out[ idim ]; \
+\
+/* Accumulate this pixel's offset from the start of the output array. */ \
+                     off_out += ixn * stride[ idim ]; \
+                  } \
+\
+                  if( !bad ) { \
+\
+/* Increment the number of input pixels pasted into the output array. */ \
+                     if( nused ) (*nused)++; \
+\
+/* If we are using the input data variances as weights, calculate the \
+   weight. */ \
+                     if( Varwgt ) { \
+                        pixwt = 1.0/in_var[ off_in ]; \
+                     } else { \
+                        pixwt = 1.0; \
+                     } \
+\
+/* Get the weighted input data value, including any extra scaling. */ \
+                     pfac = pixwt*infac; \
+                     c = CONV(IntType,pfac*in_val); \
+\
+/* Increment the value of this output pixel by the weighted input pixel \
+   value, and increment the sum of the weights. */ \
+                     out[ off_out ] += CONV(IntType, c ); \
+                     if( work ) work[ off_out ] += pixwt; \
+\
+/* If output variances are being calculated on the basis of the input \
+   variances, then we also store the required sum in "out_var". */ \
+                     if( Usevar ) { \
+                        out_var[ off_out ] += CONV(IntType,in_var[ off_in ]*pfac*pfac); \
+\
+/* If output variances are being calculated on the basis of the spread of \
+   input values, we need the sum of the squared weighted data values, the \
+   sum of the weights (already in the first half of the "work" array), and \
+   the sum of the squared weights. */ \
+                     } else if( Genvar && pixwt != 0.0 ) { \
+                        out_var[ off_out ] += c*c/pixwt; \
+                        work[ off_out + npix_out ] += pixwt*pixwt; \
+                     } \
+                  } \
+               } \
+            }
+
+
+
+
+
+
+/* Expand the main macro above to generate a function for each
+   required signed data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_SPREAD_NEAREST(LD,long double,0)
+#endif
+
+MAKE_SPREAD_NEAREST(D,double,0)
+MAKE_SPREAD_NEAREST(F,float,0)
+MAKE_SPREAD_NEAREST(I,int,1)
+MAKE_SPREAD_NEAREST(B,signed char,1)
+MAKE_SPREAD_NEAREST(UB,unsigned char,1)
+
+/* Undefine the macros used above. */
+#undef NEAR_ND
+#undef NEAR_2D
+#undef NEAR_1D
+#undef MAKE_SPREAD_NEAREST
+
+
+
+
+
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Mapping member function (over-rides the astTestAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a Mapping's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *this;             /* Pointer to the Mapping structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Mapping structure. */
+   this = (AstMapping *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Invert. */
+/* ------- */
+   if ( !strcmp( attrib, "invert" ) ) {
+      result = astTestInvert( this );
+
+/* Report. */
+/* ------- */
+   } else if ( !strcmp( attrib, "report" ) ) {
+      result = astTestReport( this );
+
+/* If the name is not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then return
+   zero. */
+   } else if ( !strcmp( attrib, "nin" ) ||
+        !strcmp( attrib, "islinear" ) ||
+        !strcmp( attrib, "issimple" ) ||
+        !strcmp( attrib, "nout" ) ||
+        !strcmp( attrib, "tranforward" ) ||
+        !strcmp( attrib, "traninverse" ) ) {
+      result = 0;
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static void Tran1( AstMapping *this, int npoint, const double xin[],
+                   int forward, double xout[], int *status ) {
+/*
+*++
+*  Name:
+c     astTran1
+f     AST_TRAN1
+
+*  Purpose:
+*     Transform 1-dimensional coordinates.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astTran1( AstMapping *this, int npoint, const double xin[],
+c                    int forward, double xout[] )
+f     CALL AST_TRAN1( THIS, NPOINT, XIN, FORWARD, XOUT, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+c     This function applies a Mapping to transform the coordinates of
+f     This routine applies a Mapping to transform the coordinates of
+*     a set of points in one dimension.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping to be applied.
+c     npoint
+f     NPOINT = INTEGER (Given)
+*        The number of points to be transformed.
+c     xin
+f     XIN( NPOINT ) = DOUBLE PRECISION (Given)
+c        An array of "npoint" coordinate values for the input
+f        An array of coordinate values for the input
+*        (untransformed) points.
+c     forward
+f     FORWARD = LOGICAL (Given)
+c        A non-zero value indicates that the Mapping's forward
+c        coordinate transformation is to be applied, while a zero
+c        value indicates that the inverse transformation should be
+c        used.
+f        A .TRUE. value indicates that the Mapping's forward
+f        coordinate transformation is to be applied, while a .FALSE.
+f        value indicates that the inverse transformation should be
+f        used.
+c     xout
+f     XOUT( NPOINT ) = DOUBLE PRECISION (Returned)
+c        An array (with "npoint" elements) into which the
+f        An array into which the
+*        coordinates of the output (transformed) points will be written.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - The Mapping supplied must have the value 1 for both its Nin
+*     and Nout attributes.
+*--
+*/
+
+/* Local Variables: */
+   AstPointSet *in_points;       /* Pointer to input PointSet */
+   AstPointSet *out_points;      /* Pointer to output PointSet */
+   const double *in_ptr[ 1 ];    /* Array of input data pointers */
+   double *out_ptr[ 1 ];         /* Array of output data pointers */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the Mapping and numbers of points/coordinates. */
+   ValidateMapping( this, forward, npoint, 1, 1, "astTran1", status );
+
+/* Set up pointers to the input and output coordinate arrays. */
+   if ( astOK ) {
+      in_ptr[ 0 ] = xin;
+      out_ptr[ 0 ] = xout;
+
+/* Create PointSets to describe the input and output points. */
+      in_points = astPointSet( npoint, 1, "", status );
+      out_points = astPointSet( npoint, 1, "", status );
+
+/* Associate the data pointers with the PointSets (note we must
+   explicitly remove the "const" qualifier from the input data here,
+   although they will not be modified).  */
+      astSetPoints( in_points, (double **) in_ptr );
+      astSetPoints( out_points, out_ptr );
+
+/* Apply the required transformation to the coordinates. */
+      (void) astTransform( this, in_points, forward, out_points );
+
+/* If the Mapping's Report attribute is set, report the effect the
+   Mapping has had on the coordinates. */
+      if ( astGetReport( this ) ) astReportPoints( this, forward,
+                                                   in_points, out_points );
+
+/* Delete the two PointSets. */
+      in_points = astDelete( in_points );
+      out_points = astDelete( out_points );
+   }
+}
+
+static void Tran2( AstMapping *this,
+                   int npoint, const double xin[], const double yin[],
+                   int forward, double xout[], double yout[], int *status ) {
+/*
+*++
+*  Name:
+c     astTran2
+f     AST_TRAN2
+
+*  Purpose:
+*     Transform 2-dimensional coordinates.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astTran2( AstMapping *this,
+c                    int npoint, const double xin[], const double yin[],
+c                    int forward, double xout[], double yout[] )
+f     CALL AST_TRAN2( THIS, NPOINT, XIN, YIN, FORWARD, XOUT, YOUT, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+c     This function applies a Mapping to transform the coordinates of
+f     This routine applies a Mapping to transform the coordinates of
+*     a set of points in two dimensions.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping to be applied.
+c     npoint
+f     NPOINT = INTEGER (Given)
+*        The number of points to be transformed.
+c     xin
+f     XIN( NPOINT ) = DOUBLE PRECISION (Given)
+c        An array of "npoint" X-coordinate values for the input
+f        An array of X-coordinate values for the input
+*        (untransformed) points.
+c     yin
+f     YIN( NPOINT ) = DOUBLE PRECISION (Given)
+c        An array of "npoint" Y-coordinate values for the input
+f        An array of Y-coordinate values for the input
+*        (untransformed) points.
+c     forward
+f     FORWARD = LOGICAL (Given)
+c        A non-zero value indicates that the Mapping's forward
+c        coordinate transformation is to be applied, while a zero
+c        value indicates that the inverse transformation should be
+c        used.
+f        A .TRUE. value indicates that the Mapping's forward
+f        coordinate transformation is to be applied, while a .FALSE.
+f        value indicates that the inverse transformation should be
+f        used.
+c     xout
+f     XOUT( NPOINT ) = DOUBLE PRECISION (Returned)
+c        An array (with "npoint" elements) into which the
+f        An array into which the
+*        X-coordinates of the output (transformed) points will be written.
+c     yout
+f     YOUT( NPOINT ) = DOUBLE PRECISION (Returned)
+c        An array (with "npoint" elements) into which the
+f        An array into which the
+*        Y-coordinates of the output (transformed) points will be written.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - The Mapping supplied must have the value 2 for both its Nin
+*     and Nout attributes.
+*--
+*/
+
+/* Local Variables: */
+   AstPointSet *in_points;       /* Pointer to input PointSet */
+   AstPointSet *out_points;      /* Pointer to output PointSet */
+   const double *in_ptr[ 2 ];    /* Array of input data pointers */
+   double *out_ptr[ 2 ];         /* Array of output data pointers */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the Mapping and the numbers of points/coordinates. */
+   ValidateMapping( this, forward, npoint, 2, 2, "astTran2", status );
+
+/* Set up pointers to the input and output coordinate arrays. */
+   if ( astOK ) {
+      in_ptr[ 0 ] = xin;
+      in_ptr[ 1 ] = yin;
+      out_ptr[ 0 ] = xout;
+      out_ptr[ 1 ] = yout;
+
+/* Create PointSets to describe the input and output points. */
+      in_points = astPointSet( npoint, 2, "", status );
+      out_points = astPointSet( npoint, 2, "", status );
+
+/* Associate the data pointers with the PointSets (note we must
+   explicitly remove the "const" qualifier from the input data here,
+   although they will not be modified).  */
+      astSetPoints( in_points, (double **) in_ptr );
+      astSetPoints( out_points, out_ptr );
+
+/* Apply the required transformation to the coordinates. */
+      (void) astTransform( this, in_points, forward, out_points );
+
+/* If the Mapping's Report attribute is set, report the effect the
+   Mapping has had on the coordinates. */
+      if ( astGetReport( this ) ) astReportPoints( this, forward,
+                                                   in_points, out_points );
+
+/* Delete the two PointSets. */
+      in_points = astDelete( in_points );
+      out_points = astDelete( out_points );
+   }
+}
+
+static void TranGrid( AstMapping *this, int ncoord_in, const int lbnd[],
+                      const int ubnd[], double tol, int maxpix, int forward,
+                      int ncoord_out, int outdim, double *out, int *status ) {
+/*
+*++
+*  Name:
+c     astTranGrid
+f     AST_TRANGRID
+
+*  Purpose:
+*     Transform a grid of positions
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astTranGrid( AstMapping *this, int ncoord_in,
+c                       const int lbnd[], const int ubnd[],
+c                       double tol, int maxpix, int forward,
+c                       int ncoord_out, int outdim, double *out );
+f     CALL AST_TRANGRID( THIS, NCOORD_IN, LBND, UBND, TOL, MAXPIX,
+f                        FORWARD, NCOORD_OUT, OUTDIM, OUT, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function uses the supplied Mapping to transforms a regular square
+*     grid of points covering a specified box. It attempts to do this
+*     quickly by first approximating the Mapping with a linear transformation
+*     applied over the whole region of the input grid which is being used.
+*     If this proves to be insufficiently accurate, the input region is
+*     sub-divided into two along its largest dimension and the process is
+*     repeated within each of the resulting sub-regions. This process of
+*     sub-division continues until a sufficiently good linear approximation
+*     is found, or the region to which it is being applied becomes too small
+*     (in which case the original Mapping is used directly).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping to be applied.
+c     ncoord_in
+f     NCOORD_IN = INTEGER (Given)
+*        The number of coordinates being supplied for each box corner
+*        (i.e. the number of dimensions of the space in which the
+*        input points reside).
+c     lbnd
+f     LBND( NCOORD_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ncoord_in" elements,
+f        An array
+*        containing the coordinates of the centre of the first pixel
+*        in the input grid along each dimension.
+c     ubnd
+f     UBND( NCOORD_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ncoord_in" elements,
+f        An array
+*        containing the coordinates of the centre of the last pixel in
+*        the input grid along each dimension.
+*
+c        Note that "lbnd" and "ubnd" together define the shape
+f        Note that LBND and UBND together define the shape
+*        and size of the input grid, its extent along a particular
+c        (j'th) dimension being ubnd[j]-lbnd[j]+1 (assuming the
+c        index "j" to be zero-based). They also define
+f        (J'th) dimension being UBND(J)-LBND(J)+1. They also define
+*        the input grid's coordinate system, each pixel having unit
+*        extent along each dimension with integral coordinate values
+*        at its centre.
+c     tol
+f     TOL = DOUBLE PRECISION (Given)
+*        The maximum tolerable geometrical distortion which may be
+*        introduced as a result of approximating non-linear Mappings
+*        by a set of piece-wise linear transformations. This should be
+*        expressed as a displacement within the output coordinate system
+*        of the Mapping.
+*
+*        If piece-wise linear approximation is not required, a value
+*        of zero may be given. This will ensure that the Mapping is
+*        used without any approximation, but may increase execution
+*        time.
+*
+*        If the value is too high, discontinuities between the linear
+*        approximations used in adjacent panel will be higher. If this
+*        is a problem, reduce the tolerance value used.
+c     maxpix
+f     MAXPIX = INTEGER (Given)
+*        A value which specifies an initial scale size (in input grid points)
+*        for the adaptive algorithm which approximates non-linear Mappings
+*        with piece-wise linear transformations. Normally, this should
+*        be a large value (larger than any dimension of the region of
+*        the input grid being used). In this case, a first attempt to
+*        approximate the Mapping by a linear transformation will be
+*        made over the entire input region.
+*
+*        If a smaller value is used, the input region will first be
+c        divided into sub-regions whose size does not exceed "maxpix"
+f        divided into sub-regions whose size does not exceed MAXPIX
+*        grid points in any dimension. Only at this point will attempts
+*        at approximation commence.
+*
+*        This value may occasionally be useful in preventing false
+*        convergence of the adaptive algorithm in cases where the
+*        Mapping appears approximately linear on large scales, but has
+*        irregularities (e.g. holes) on smaller scales. A value of,
+*        say, 50 to 100 grid points can also be employed as a safeguard
+*        in general-purpose software, since the effect on performance is
+*        minimal.
+*
+*        If too small a value is given, it will have the effect of
+*        inhibiting linear approximation altogether (equivalent to
+c        setting "tol" to zero). Although this may degrade
+f        setting TOL to zero). Although this may degrade
+*        performance, accurate results will still be obtained.
+c     forward
+f     FORWARD = LOGICAL (Given)
+c        A non-zero value indicates that the Mapping's forward
+c        coordinate transformation is to be applied, while a zero
+c        value indicates that the inverse transformation should be
+c        used.
+f        A .TRUE. value indicates that the Mapping's forward
+f        coordinate transformation is to be applied, while a .FALSE.
+f        value indicates that the inverse transformation should be
+f        used.
+c     ncoord_out
+f     NCOORD_OUT = INTEGER (Given)
+*        The number of coordinates being generated by the Mapping for
+*        each output point (i.e. the number of dimensions of the
+*        space in which the output points reside). This need not be
+c        the same as "ncoord_in".
+f        the same as NCOORD_IN.
+c     outdim
+f     OUTDIM = INTEGER (Given)
+c        The number of elements along the second dimension of the "out"
+f        The number of elements along the first dimension of the OUT
+*        array (which will contain the output coordinates). The value
+*        given should not be less than the number of points in the grid.
+c     out
+f     OUT( OUTDIM, NCOORD_OUT ) = DOUBLE PRECISION (Returned)
+c        The address of the first element in a 2-dimensional array of
+c        shape "[ncoord_out][outdim]", into
+c        which the coordinates of the output (transformed) points will
+c        be written. These will be stored such that the value of
+c        coordinate number "coord" for output point number "point"
+c        will be found in element "out[coord][point]".
+f        An array into which the coordinates of the output
+f        (transformed) points will be written. These will be stored
+f        such that the value of coordinate number COORD for output
+f        point number POINT will be found in element OUT(POINT,COORD).
+*        The points are ordered such that the first axis of the input
+*        grid changes most rapidly. For example, if the input grid is
+*        2-dimensional and extends from (2,-1) to (3,1), the output
+*        points will be stored in the order (2,-1), (3, -1), (2,0), (3,0),
+*        (2,1), (3,1).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+c     - If the forward coordinate transformation is being applied, the
+c     Mapping supplied must have the value of "ncoord_in" for its Nin
+c     attribute and the value of "ncoord_out" for its Nout attribute. If
+c     the inverse transformation is being applied, these values should
+c     be reversed.
+f     - If the forward coordinate transformation is being applied, the
+f     Mapping supplied must have the value of NCOORD_IN for its Nin
+f     attribute and the value of NCOORD_OUT for its Nout attribute. If
+f     the inverse transformation is being applied, these values should
+f     be reversed.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific data */
+   AstMapping *simple;           /* Pointer to simplified Mapping */
+   double **out_ptr;             /* Pointer to array of output data pointers */
+   int coord;                    /* Loop counter for coordinates */
+   int idim;                     /* Loop counter for coordinate dimensions */
+   int npoint;                   /* Number of points in the grid */
+   int64_t mpix;                 /* Number of points for testing */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to a structure holding thread-specific global data values */
+   astGET_GLOBALS(this);
+
+/* Calculate the number of points in the grid, and check that the lower and
+   upper bounds of the input grid are consistent. Report an error if any
+   pair is not. */
+   mpix = 1;
+   for ( idim = 0; idim < ncoord_in; idim++ ) {
+      if ( lbnd[ idim ] > ubnd[ idim ] ) {
+         astError( AST__GBDIN, "astTranGrid(%s): Lower bound of "
+                   "input grid (%d) exceeds corresponding upper bound "
+                   "(%d).", status, astGetClass( this ),
+                   lbnd[ idim ], ubnd[ idim ] );
+         astError( AST__GBDIN, "Error in input dimension %d.", status,
+                   idim + 1 );
+         break;
+      } else {
+         mpix *= ubnd[ idim ] - lbnd[ idim ] + 1;
+      }
+   }
+
+/* Report an error if there are too many pixels in the input. */
+   npoint = mpix;
+   if ( astOK && npoint != mpix ) {
+      astError( AST__EXSPIX, "astTranGrid(%s): Supplied grid "
+                "contains too many points (%g): must be fewer than %d.",
+                status, astGetClass( this ), (double) mpix, INT_MAX/ncoord_out );
+   }
+
+   mpix = outdim*ncoord_out;
+   if ( astOK && (int) mpix != mpix ) {
+      astError( AST__EXSPIX, "astTranGrid(%s): Supplied output array "
+                "contains too many pixels (%g): must be fewer than %d.",
+                status, astGetClass( this ), (double) mpix, INT_MAX );
+   }
+
+
+/* Validate the mapping and numbers of points/coordinates. */
+   ValidateMapping( this, forward, npoint, ncoord_in, ncoord_out,
+                    "astTranGrid", status );
+
+/* Check that the positional accuracy tolerance supplied is valid and
+   report an error if necessary. */
+   if ( astOK && ( tol < 0.0 ) ) {
+      astError( AST__PATIN, "astTranGrid(%s): Invalid positional "
+                "accuracy tolerance (%.*g pixel).", status,
+                astGetClass( this ), DBL_DIG, tol );
+      astError( AST__PATIN, "This value should not be less than zero." , status);
+   }
+
+/* Check that the initial scale size in grid points supplied is valid and
+   report an error if necessary. */
+   if ( astOK && ( maxpix < 0 ) ) {
+      astError( AST__SSPIN, "astTranGrid(%s): Invalid initial scale "
+                "size in grid points (%d).", status, astGetClass( this ), maxpix );
+      astError( AST__SSPIN, "This value should not be less than zero." , status);
+   }
+
+/* Validate the output array dimension argument. */
+   if ( astOK && ( outdim < npoint ) ) {
+      astError( AST__DIMIN, "astTranGrid(%s): The output array dimension value "
+                "(%d) is invalid.", status, astGetClass( this ), outdim );
+      astError( AST__DIMIN, "This should not be less than the number of "
+                "grid points being transformed (%d).", status, npoint );
+   }
+
+/* If there are sufficient pixels to make it worthwhile, simplify the
+   Mapping supplied to improve performance. Otherwise, just clone the
+   Mapping pointer. Note we save a pointer to the original Mapping so
+   that lower-level functions can use it if they need to report an error. */
+   simple = NULL;
+   unsimplified_mapping = this;
+   if ( astOK ) {
+      if ( npoint > 1024 ) {
+         simple = astSimplify( this );
+
+/* Report an error if the required transformation of this simplified
+   Mapping is not defined. */
+         if( astOK ) {
+            if ( forward && !astGetTranForward( simple ) ) {
+               astError( AST__TRNND, "astTranGrid(%s): A forward coordinate "
+                         "transformation is not defined by the %s supplied.", status,
+                         astGetClass( unsimplified_mapping ),
+                         astGetClass( unsimplified_mapping ) );
+            } else if ( !forward && !astGetTranInverse( simple ) ) {
+               astError( AST__TRNND, "astTranGrid(%s): An inverse coordinate "
+                         "transformation is not defined by the %s supplied.", status,
+                         astGetClass( unsimplified_mapping ),
+                         astGetClass( unsimplified_mapping ) );
+            }
+         }
+
+      } else {
+         simple = astClone( this );
+      }
+
+/* Allocate memory to hold the array of output data pointers. */
+      out_ptr = astMalloc( sizeof( double * ) * (size_t) ncoord_out );
+
+/* Initialise the output data pointers to point into the "out" array. */
+      if ( astOK ) {
+         for ( coord = 0; coord < ncoord_out; coord++ ) {
+            out_ptr[ coord ] = out + coord * outdim;
+         }
+
+/* If required, temporarily invert the Mapping. */
+         if( !forward ) astInvert( simple );
+
+/* Perform the transformation. */
+         TranGridAdaptively( simple, ncoord_in, lbnd, ubnd, lbnd, ubnd, tol,
+                             maxpix, ncoord_out, out_ptr, status );
+
+/* If required, uninvert the Mapping. */
+         if( !forward ) astInvert( simple );
+
+      }
+
+/* Free the memory used for the data pointers. */
+      out_ptr = astFree( out_ptr );
+
+/* Annul the pointer to the simplified/cloned Mapping. */
+      simple = astAnnul( simple );
+   }
+}
+
+static void TranGridAdaptively( AstMapping *this, int ncoord_in,
+                                const int *lbnd_in, const int *ubnd_in,
+                                const int lbnd[], const int ubnd[],
+                                double tol, int maxpix, int ncoord_out,
+                                double *out[], int *status ){
+/*
+*  Name:
+*     TranGridAdaptively
+
+*  Purpose:
+*     Transform grid positions adaptively.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void TranGridAdaptively( AstMapping *this, int ncoord_in,
+*                              const int *lbnd_in, const int *ubnd_in,
+*                              const int lbnd[], const int ubnd[],
+*                              double tol, int maxpix, int ncoord_out,
+*                              double *out[] )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function transforms grid points within a specified section of a
+*     rectangular grid (with any number of dimensions) using the forward
+*     transformation of the specified Mapping.
+*
+*     This function is very similar to TranGridWithBlocking and TranGridSection
+*     which lie below it in the calling hierarchy. However, this function
+*     also attempts to adapt to the Mapping supplied and to sub-divide the
+*     section being transformed into smaller sections within which a linear
+*     approximation to the Mapping may be used.  This reduces the number of
+*     Mapping evaluations, thereby improving efficiency particularly when
+*     complicated Mappings are involved.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied. The forward transformation
+*        is used.
+*     ncoord_in
+*        The number of coordinates being supplied for each box corner
+*        (i.e. the number of dimensions of the space in which the
+*        input points reside).
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the whole input grid, its extent along a
+*        particular (i'th) dimension being (ubnd_in[i] - lbnd_in[i] +
+*        1). They also define the input grid's coordinate system, with
+*        each pixel being of unit extent along each dimension with
+*        integral coordinate values at its centre.
+*     lbnd
+*        Pointer to an array of integers, with "ncoord_in" elements,
+*        containing the coordinates of the centre of the first pixel
+*        in the input grid along each dimension.
+*     ubnd
+*        Pointer to an array of integers, with "ncoord_in" elements,
+*        containing the coordinates of the centre of the last pixel in
+*        the input grid along each dimension.
+*
+*        Note that "lbnd" and "ubnd" together define the shape
+*        and size of the input grid, its extent along a particular
+*        (j'th) dimension being ubnd[j]-lbnd[j]+1 (assuming the
+*        index "j" to be zero-based). They also define
+*        the input grid's coordinate system, each pixel having unit
+*        extent along each dimension with integral coordinate values
+*        at its centre.
+*     tol
+*        The maximum tolerable geometrical distortion which may be
+*        introduced as a result of approximating non-linear Mappings
+*        by a set of piece-wise linear transformations. This should be
+*        expressed as a displacement in pixels in the output grid's
+*        coordinate system.
+*
+*        If piece-wise linear approximation is not required, a value
+*        of zero may be given. This will ensure that the Mapping is
+*        used without any approximation, but may increase execution
+*        time.
+*
+*        If the value is too high, discontinuities between the linear
+*        approximations used in adjacent panel will be higher. If this
+*        is a problem, reduce the tolerance value used.
+*     maxpix
+*        A value which specifies an initial scale size (in grid points)
+*        for the adaptive algorithm which approximates non-linear Mappings
+*        with piece-wise linear transformations. Normally, this should
+*        be a large value (larger than any dimension of the region of
+*        the input grid being used). In this case, a first attempt to
+*        approximate the Mapping by a linear transformation will be
+*        made over the entire input region.
+*
+*        If a smaller value is used, the input region will first be
+*        divided into sub-regions whose size does not exceed "maxpix"
+*        grid points in any dimension. Only at this point will attempts
+*        at approximation commence.
+*
+*        This value may occasionally be useful in preventing false
+*        convergence of the adaptive algorithm in cases where the
+*        Mapping appears approximately linear on large scales, but has
+*        irregularities (e.g. holes) on smaller scales. A value of,
+*        say, 50 to 100 grid points can also be employed as a safeguard
+*        in general-purpose software, since the effect on performance is
+*        minimal.
+*
+*        If too small a value is given, it will have the effect of
+*        inhibiting linear approximation altogether (equivalent to
+*        setting "tol" to zero). Although this may degrade
+*        performance, accurate results will still be obtained.
+*     ncoord_out
+*        The number of dimensions of the space in which the output points
+*        reside.
+*     out
+*        Pointer to an array with "ndim_out" elements. Element [i] of
+*        this array is a pointer to an array in which to store the
+*        transformed values for output axis "i". The points are ordered
+*        such that the first axis of the input grid changes most rapidly.
+*        For example, if the input grid is 2-dimensional and extends from
+*        (2,-1) to (3,1), the output points will be stored in the order
+*        (2,-1), (3, -1), (2,0), (3,0), (2,1), (3,1).
+
+*/
+
+/* Local Variables: */
+   double *flbnd;                /* Array holding floating point lower bounds */
+   double *fubnd;                /* Array holding floating point upper bounds */
+   double *linear_fit;           /* Pointer to array of fit coefficients */
+   int *hi;                      /* Pointer to array of section upper bounds */
+   int *lo;                      /* Pointer to array of section lower bounds */
+   int coord_in;                 /* Loop counter for input coordinates */
+   int dim;                      /* Output section dimension size */
+   int dimx;                     /* Dimension with maximum section extent */
+   int divide;                   /* Sub-divide the output section? */
+   int i;                        /* Loop count */
+   int isLinear;                 /* Is the transformation linear? */
+   int mxdim;                    /* Largest output section dimension size */
+   int npix;                     /* Number of pixels in output section */
+   int npoint;                   /* Number of points for obtaining a fit */
+   int nvertex;                  /* Number of vertices of output section */
+   int toobig;                   /* Section too big (must sub-divide)? */
+   int toosmall;                 /* Section too small to sub-divide? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Further initialisation. */
+   npix = 1;
+   mxdim = 0;
+   dimx = 1;
+   nvertex = 1;
+
+/* Loop through the input grid dimensions. */
+   for ( coord_in = 0; coord_in < ncoord_in; coord_in++ ) {
+
+/* Obtain the extent in each dimension of the input section which is
+   to be rebinned, and calculate the total number of pixels it contains. */
+      dim = ubnd[ coord_in ] - lbnd[ coord_in ] + 1;
+      npix *= dim;
+
+/* Find the maximum dimension size of this input section and note which
+   dimension has this size. */
+      if ( dim > mxdim ) {
+         mxdim = dim;
+         dimx = coord_in;
+      }
+
+/* Calculate how many vertices the output section has. */
+      nvertex *= 2;
+   }
+
+/* Calculate how many sample points will be needed (by the astLinearApprox
+   function) to obtain a linear fit to the Mapping's forward transformation. */
+   npoint = 1 + 4 * ncoord_in + 2 * nvertex;
+
+/* If the number of pixels in the input section is not at least 4
+   times this number, we will probably not save significant time by
+   attempting to obtain a linear fit, so note that the input section
+   is too small. */
+   toosmall = ( npix < ( 4 * npoint ) );
+
+/* Note if the maximum dimension of the input section exceeds the
+   user-supplied scale factor. */
+   toobig = ( maxpix < mxdim );
+
+/* Assume the Mapping is significantly non-linear before deciding
+   whether to sub-divide the output section. */
+   linear_fit = NULL;
+
+/* If the output section is too small to be worth obtaining a linear
+   fit, or if the accuracy tolerance is zero, we will not
+   sub-divide. This means that the Mapping will be used to transform
+   each pixel's coordinates and no linear approximation will be
+   used. */
+   if ( toosmall || ( tol == 0.0 ) ) {
+      divide = 0;
+
+/* Otherwise, if the largest input section dimension exceeds the
+   scale length given, we will sub-divide. This offers the possibility
+   of obtaining a linear approximation to the Mapping over a reduced
+   range of input coordinates (which will be handled by a recursive
+   invocation of this function). */
+   } else if ( toobig ) {
+      divide = 1;
+
+/* If neither of the above apply, then attempt to fit a linear
+   approximation to the forward transformation of the Mapping over
+   the range of coordinates covered by the input section. We need to
+   temporarily copy the integer bounds into floating point arrays to
+   use astLinearApprox. */
+   } else {
+
+/* Allocate memory for floating point bounds and for the coefficient array */
+      flbnd = astMalloc( sizeof( double )*(size_t) ncoord_in );
+      fubnd = astMalloc( sizeof( double )*(size_t) ncoord_in );
+      linear_fit = astMalloc( sizeof( double )*
+                              (size_t) ( ncoord_out*( ncoord_in + 1 ) ) );
+      if( astOK ) {
+
+/* Copy the bounds into these arrays, and change them so that they refer
+   to the lower and upper edges of the cell rather than the centre. This
+   is essential if one of the axes is spanned by a single cell, since
+   otherwise the upper and lower bounds would be identical. */
+         for( i = 0; i < ncoord_in; i++ ) {
+            flbnd[ i ] = (double) lbnd[ i ] - 0.5;
+            fubnd[ i ] = (double) ubnd[ i ] + 0.5;
+         }
+
+/* Get the linear approximation to the forward transformation. */
+         isLinear = astLinearApprox( this, flbnd, fubnd, tol, linear_fit );
+
+/* Free the coeff array if the inverse transformation is not linear. */
+         if( !isLinear ) linear_fit = astFree( linear_fit );
+
+      } else {
+         linear_fit = astFree( linear_fit );
+      }
+
+/* Free resources */
+      flbnd = astFree( flbnd );
+      fubnd = astFree( fubnd );
+
+/* If a linear fit was obtained, we will use it and therefore do not
+   wish to sub-divide further. Otherwise, we sub-divide in the hope
+   that this may result in a linear fit next time. */
+      divide = !linear_fit;
+   }
+
+/* If no sub-division is required, perform the transformation (in a
+   memory-efficient manner, since the section we are rebinning might
+   still be very large). This will use the linear fit, if obtained
+   above. */
+   if ( astOK ) {
+      if ( !divide ) {
+         TranGridWithBlocking( this, linear_fit, ncoord_in, lbnd_in,
+                               ubnd_in, lbnd, ubnd, ncoord_out, out, status );
+
+/* Otherwise, allocate workspace to perform the sub-division. */
+      } else {
+         lo = astMalloc( sizeof( int ) * (size_t) ncoord_in );
+         hi = astMalloc( sizeof( int ) * (size_t) ncoord_in );
+         if ( astOK ) {
+
+/* Initialise the bounds of a new input section to match the original
+   input section. */
+            for ( coord_in = 0; coord_in < ncoord_in; coord_in++ ) {
+               lo[ coord_in ] = lbnd[ coord_in ];
+               hi[ coord_in ] = ubnd[ coord_in ];
+            }
+
+/* Replace the upper bound of the section's largest dimension with the
+   mid-point of the section along this dimension, rounded downwards. */
+            hi[ dimx ] =
+               (int) floor( 0.5 * (double) ( lbnd[ dimx ] + ubnd[ dimx ] ) );
+
+/* Rebin the resulting smaller section using a recursive invocation
+   of this function. */
+            TranGridAdaptively( this, ncoord_in, lbnd_in, ubnd_in, lo, hi,
+                                tol, maxpix, ncoord_out, out, status );
+
+/* Now set up a second section which covers the remaining half of the
+   original input section. */
+            lo[ dimx ] = hi[ dimx ] + 1;
+            hi[ dimx ] = ubnd[ dimx ];
+
+/* If this section contains pixels, transform it in the same way. */
+            if ( lo[ dimx ] <= hi[ dimx ] ) {
+               TranGridAdaptively( this, ncoord_in, lbnd_in, ubnd_in, lo, hi,
+                                   tol, maxpix, ncoord_out, out, status );
+            }
+         }
+
+/* Free the workspace. */
+         lo = astFree( lo );
+         hi = astFree( hi );
+      }
+   }
+
+/* If coefficients for a linear fit were obtained, then free the space
+   they occupy. */
+   if ( linear_fit ) linear_fit = astFree( linear_fit );
+}
+
+static void TranGridSection( AstMapping *this, const double *linear_fit,
+                             int ndim_in, const int *lbnd_in,
+                             const int *ubnd_in, const int *lbnd,
+                             const int *ubnd, int ndim_out, double *out[], int *status ){
+/*
+*  Name:
+*     TranGridSection
+
+*  Purpose:
+*     Transform grid points within a section of a rectangular grid.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void TranGridSection( AstMapping *this, const double *linear_fit,
+*                           int ndim_in, const int *lbnd_in,
+*                           const int *ubnd_in, const int *lbnd,
+*                           const int *ubnd, int ndim_out, double *out[] )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function transforms grid points within a specified section of a
+*     rectangular grid (with any number of dimensions) using a specified
+*     Mapping or, alternatively, a linear approximation fitted to the
+*     Mapping's forward transformation.
+
+*  Parameters:
+*     this
+*        Pointer to a Mapping, whose forward transformation may be
+*        used to transform the coordinates of points in the input
+*        grid.
+*
+*        The number of input coordintes for the Mapping (Nin
+*        attribute) should match the value of "ndim_in" (below), and
+*        the number of output coordinates (Nout attribute) should
+*        match the value of "ndim_out".
+*     linear_fit
+*        Pointer to an optional array of double which contains the
+*        coefficients of a linear fit which approximates the above
+*        Mapping's forward coordinate transformation. If this is
+*        supplied, it will be used in preference to the above Mapping
+*        when transforming coordinates. This may be used to enhance
+*        performance in cases where evaluation of the Mapping's
+*        forward transformation is expensive. If no linear fit is
+*        available, a NULL pointer should be supplied.
+*
+*        The way in which the fit coefficients are stored in this
+*        array and the number of array elements are as defined by the
+*        astLinearApprox function.
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input data grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input data grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the input data grid, its extent along a
+*        particular (i'th) dimension being (ubnd_in[i] - lbnd_in[i] +
+*        1). They also define the input grid's coordinate system, with
+*        each pixel being of unit extent along each dimension with
+*        integral coordinate values at its centre.
+*     lbnd
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the first pixel in the
+*        section of the input data grid which is to be rebinned.
+*     ubnd
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the last pixel in the
+*        section of the input data grid which is to be rebinned.
+*
+*        Note that "lbnd" and "ubnd" define the shape and position of
+*        the section of the input grid which is to be rebinned. This section
+*        should lie wholly within the extent of the input grid (as defined
+*        by the "lbnd_out" and "ubnd_out" arrays). Regions of the input
+*        grid lying outside this section will be ignored.
+*     ndim_out
+*        The number of dimensions in the output grid. This should be
+*        at least one.
+*     out
+*        Pointer to an array with "ndim_out" elements. Element [i] of
+*        this array is a pointer to an array in which to store the
+*        transformed values for output axis "i". The points are ordered
+*        such that the first axis of the input grid changes most rapidly.
+*        For example, if the input grid is 2-dimensional and extends from
+*        (2,-1) to (3,1), the output points will be stored in the order
+*        (2,-1), (3, -1), (2,0), (3,0), (2,1), (3,1).
+
+*  Notes:
+*     - This function does not take steps to limit memory usage if the
+*     grids supplied are large. To resample large grids in a more
+*     memory-efficient way, the ResampleWithBlocking function should
+*     be used.
+*/
+
+/* Local Variables: */
+   AstPointSet *pset_in;         /* Input PointSet for transformation */
+   AstPointSet *pset_out;        /* Output PointSet for transformation */
+   const double *grad;           /* Pointer to gradient matrix of linear fit */
+   const double *zero;           /* Pointer to zero point array of fit */
+   double **ptr_in;              /* Pointer to input PointSet coordinates */
+   double **ptr_out;             /* Pointer to output PointSet coordinates */
+   double *accum;                /* Pointer to array of accumulated sums */
+   double x1;                    /* Interim x coordinate value */
+   double xx1;                   /* Initial x coordinate value */
+   double y1;                    /* Interim y coordinate value */
+   double yy1;                   /* Initial y coordinate value */
+   int *dim;                     /* Pointer to array of output pixel indices */
+   int *offset;                  /* Pointer to array of output pixel offsets */
+   int *stride;                  /* Pointer to array of output grid strides */
+   int coord_in;                 /* Loop counter for input dimensions */
+   int coord_out;                /* Loop counter for output dimensions */
+   int done;                     /* All pixel indices done? */
+   int i1;                       /* Interim offset into "accum" array */
+   int i2;                       /* Final offset into "accum" array */
+   int idim;                     /* Loop counter for dimensions */
+   int ix;                       /* Loop counter for output x coordinate */
+   int iy;                       /* Loop counter for output y coordinate */
+   int npoint;                   /* Number of output points (pixels) */
+   int off1;                     /* Interim pixel offset into output array */
+   int off2;                     /* Interim pixel offset into output array */
+   int off;                      /* Final pixel offset into output array */
+   int point;                    /* Counter for output points (pixels ) */
+   int s;                        /* Temporary variable for strides */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Further initialisation. */
+   pset_in = NULL;
+   ptr_in = NULL;
+   ptr_out = NULL;
+   pset_out = NULL;
+
+/* Calculate the number of input points, as given by the product of
+   the input grid dimensions. */
+   for ( npoint = 1, coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+      npoint *= ubnd[ coord_in ] - lbnd[ coord_in ] + 1;
+   }
+
+/* Allocate workspace. */
+   offset = astMalloc( sizeof( int ) * (size_t) npoint );
+   stride = astMalloc( sizeof( int ) * (size_t) ndim_in );
+   if ( astOK ) {
+
+/* Calculate the stride for each input grid dimension. */
+      off = 0;
+      s = 1;
+      for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+         stride[ coord_in ] = s;
+         s *= ubnd_in[ coord_in ] - lbnd_in[ coord_in ] + 1;
+      }
+
+/* A linear fit to the Mapping is available. */
+/* ========================================= */
+      if ( linear_fit ) {
+
+/* If a linear fit to the Mapping has been provided, then obtain
+   pointers to the array of gradients and zero-points comprising the
+   fit. */
+         grad = linear_fit + ndim_out;
+         zero = linear_fit;
+
+/* Create a PointSet to hold the output grid coordinates and obtain an
+   array of pointers to its coordinate data. */
+         pset_out = astPointSet( npoint, ndim_out, "", status );
+         ptr_out = astGetPoints( pset_out );
+         if ( astOK ) {
+
+/* Initialise the count of input points. */
+            point = 0;
+
+/* Handle the 1-dimensional case optimally. */
+/* ---------------------------------------- */
+            if ( ( ndim_in == 1 ) && ( ndim_out == 1 ) ) {
+
+/* Loop through the pixels of the input grid and transform their x
+   coordinates into the output grid's coordinate system using the
+   linear fit supplied. Store the results in the PointSet created
+   above. */
+               off = lbnd[ 0 ] - lbnd_in[ 0 ];
+               xx1 = zero[ 0 ] + grad[ 0 ] * (double) lbnd[ 0 ];
+
+               for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                  ptr_out[ 0 ][ point ] = xx1;
+                  xx1 += grad[ 0 ];
+                  offset[ point++ ] = off++;
+               }
+
+/* Handle the 2-dimensional case optimally. */
+/* ---------------------------------------- */
+            } else if ( ( ndim_in == 2 ) && ( ndim_out == 2 ) ) {
+
+/* Loop through the range of y coordinates in the input grid and
+   calculate interim values of the output coordinates using the linear
+   fit supplied. */
+               x1 = zero[ 0 ] + grad[ 1 ] * (double) ( lbnd[ 1 ] - 1 );
+               y1 = zero[ 1 ] + grad[ 3 ] * (double) ( lbnd[ 1 ] - 1 );
+               off1 = stride[ 1 ] * ( lbnd[ 1 ] - lbnd_in[ 1 ] - 1 ) - lbnd_in[ 0 ];
+               for ( iy = lbnd[ 1 ]; iy <= ubnd[ 1 ]; iy++ ) {
+                  x1 += grad[ 1 ];
+                  y1 += grad[ 3 ];
+
+/* Also calculate an interim pixel offset into the input array. */
+                  off1 += stride[ 1 ];
+
+/* Now loop through the range of input x coordinates and calculate
+   the final values of the input coordinates, storing the results in
+   the PointSet created above. */
+                  xx1 = x1 + grad[ 0 ] * (double) lbnd[ 0 ];
+                  yy1 = y1 + grad[ 2 ] * (double) lbnd[ 0 ];
+                  off = off1 + lbnd[ 0 ];
+                  for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                     ptr_out[ 0 ][ point ] = xx1;
+                     xx1 += grad[ 0 ];
+                     ptr_out[ 1 ][ point ] = yy1;
+                     yy1 += grad[ 2 ];
+
+/* Also calculate final pixel offsets into the input array. */
+                     offset[ point++ ] = off++;
+                  }
+               }
+
+/* Handle other numbers of dimensions. */
+/* ----------------------------------- */
+            } else {
+
+/* Allocate workspace. */
+               accum = astMalloc( sizeof( double ) *
+                                 (size_t) ( ndim_in * ndim_out ) );
+               dim = astMalloc( sizeof( int ) * (size_t) ndim_in );
+               if ( astOK ) {
+
+/* Initialise an array of pixel indices for the input grid which refer to the
+   first pixel which we will rebin. Also calculate the offset of this pixel
+   within the input array. */
+                  off = 0;
+                  for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                     dim[ coord_in ] = lbnd[ coord_in ];
+                     off += stride[ coord_in ] *
+                            ( dim[ coord_in ] - lbnd_in[ coord_in ] );
+                  }
+
+/* To calculate each output grid coordinate we must perform a matrix
+   multiply on the input grid coordinates (using the gradient matrix)
+   and then add the zero points. However, since we will usually only
+   be altering one input coordinate at a time (the least
+   significant), we can avoid the full matrix multiply by accumulating
+   partial sums for the most significant input coordinates and only
+   altering those sums which need to change each time. The zero points
+   never change, so we first fill the "most significant" end of the
+   "accum" array with these. */
+                  for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+                     accum[ ( coord_out + 1 ) * ndim_in - 1 ] =
+                                                              zero[ coord_out ];
+                  }
+                  coord_in = ndim_in - 1;
+
+/* Now loop to process each input pixel. */
+                  for ( done = 0; !done; point++ ) {
+
+/* To generate the output coordinate that corresponds to the current
+   input pixel, we work down from the most significant dimension
+   whose index has changed since the previous pixel we considered
+   (given by "coord_in"). For each affected dimension, we accumulate
+   in "accum" the matrix sum (including the zero point) for that
+   dimension and all higher input dimensions. We must accumulate a
+   separate set of sums for each output coordinate we wish to
+   produce. (Note that for the first pixel we process, all dimensions
+   are considered "changed", so we start by initialising the whole
+   "accum" array.) */
+                     for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+                        i1 = coord_out * ndim_in;
+                        for ( idim = coord_in; idim >= 1; idim-- ) {
+                           i2 = i1 + idim;
+                           accum[ i2 - 1 ] = accum[ i2 ] +
+                                             dim[ idim ] * grad[ i2 ];
+                        }
+
+/* The output coordinate for each dimension is given by the accumulated
+   sum for input dimension zero (giving the sum over all input
+   dimensions). We do not store this in the "accum" array, but assign
+   the result directly to the coordinate array of the PointSet created
+   earlier. */
+                        ptr_out[ coord_out ][ point ] = accum[ i1 ] +
+                                                      dim[ 0 ] * grad[ i1 ];
+                     }
+
+/* Store the offset of the current pixel in the input array. */
+                     offset[ point ] = off;
+
+/* Now update the array of pixel indices to refer to the next input pixel. */
+                     coord_in = 0;
+                     do {
+
+/* The least significant index which currently has less than its maximum
+   value is incremented by one. The offset into the input array is updated
+   accordingly. */
+                        if ( dim[ coord_in ] < ubnd[ coord_in ] ) {
+                           dim[ coord_in ]++;
+                           off += stride[ coord_in ];
+                           break;
+
+/* Any less significant indices which have reached their maximum value
+   are returned to their minimum value and the input pixel offset is
+   decremented appropriately. */
+                        } else {
+                           dim[ coord_in ] = lbnd[ coord_in ];
+                           off -= stride[ coord_in ] *
+                                  ( ubnd[ coord_in ] - lbnd[ coord_in ] );
+
+/* All the output pixels have been processed once the most significant
+   pixel index has been returned to its minimum value. */
+                           done = ( ++coord_in == ndim_in );
+                        }
+                     } while ( !done );
+                  }
+               }
+
+/* Free the workspace. */
+               accum = astFree( accum );
+               dim = astFree( dim );
+            }
+         }
+
+/* No linear fit to the Mapping is available. */
+/* ========================================== */
+      } else {
+
+/* Create a PointSet to hold the coordinates of the input pixels and
+   obtain a pointer to its coordinate data. */
+         pset_in = astPointSet( npoint, ndim_in, "", status );
+         ptr_in = astGetPoints( pset_in );
+         if ( astOK ) {
+
+/* Initialise the count of input points. */
+            point = 0;
+
+/* Handle the 1-dimensional case optimally. */
+/* ---------------------------------------- */
+            if ( ndim_in == 1 && ndim_out == 1 ) {
+
+/* Loop through the required range of input x coordinates, assigning
+   the coordinate values to the PointSet created above. Also store a
+   pixel offset into the input array. */
+               for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                  ptr_in[ 0 ][ point ] = (double) ix;
+                  offset[ point++ ] = ix - lbnd_in[ 0 ];
+               }
+
+/* Handle the 2-dimensional case optimally. */
+/* ---------------------------------------- */
+            } else if ( ndim_in == 2 && ndim_out == 2 ) {
+
+/* Loop through the required range of input y coordinates,
+   calculating an interim pixel offset into the input array. */
+               off1 = stride[ 1 ] * ( lbnd[ 1 ] - lbnd_in[ 1 ] - 1 )
+                      - lbnd_in[ 0 ];
+               for ( iy = lbnd[ 1 ]; iy <= ubnd[ 1 ]; iy++ ) {
+                  off1 += stride[ 1 ];
+
+/* Loop through the required range of input x coordinates, assigning
+   the coordinate values to the PointSet created above. Also store a
+   final pixel offset into the input array. */
+                  off2 = off1 + lbnd[ 0 ];
+                  for ( ix = lbnd[ 0 ]; ix <= ubnd[ 0 ]; ix++ ) {
+                     ptr_in[ 0 ][ point ] = (double) ix;
+                     ptr_in[ 1 ][ point ] = (double) iy;
+                     offset[ point++ ] = off2++;
+                  }
+               }
+
+/* Handle other numbers of dimensions. */
+/* ----------------------------------- */
+            } else {
+
+/* Allocate workspace. */
+               dim = astMalloc( sizeof( int ) * (size_t) ndim_in );
+               if ( astOK ) {
+
+/* Initialise an array of pixel indices for the input grid which
+   refer to the first pixel to be rebinned. Also calculate the offset
+   of this pixel within the input array. */
+                  off = 0;
+                  for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                     dim[ coord_in ] = lbnd[ coord_in ];
+                     off += stride[ coord_in ] *
+                            ( dim[ coord_in ] - lbnd_in[ coord_in ] );
+                  }
+
+/* Loop to generate the coordinates of each input pixel. */
+                  for ( done = 0; !done; point++ ) {
+
+/* Copy each pixel's coordinates into the PointSet created above. */
+                     for ( coord_in = 0; coord_in < ndim_in; coord_in++ ) {
+                        ptr_in[ coord_in ][ point ] =
+                                                     (double) dim[ coord_in ];
+                     }
+
+/* Store the offset of the pixel in the input array. */
+                     offset[ point ] = off;
+
+/* Now update the array of pixel indices to refer to the next input
+   pixel. */
+                     coord_in = 0;
+                     do {
+
+/* The least significant index which currently has less than its
+   maximum value is incremented by one. The offset into the input
+   array is updated accordingly. */
+                        if ( dim[ coord_in ] < ubnd[ coord_in ] ) {
+                           dim[ coord_in ]++;
+                           off += stride[ coord_in ];
+                           break;
+
+/* Any less significant indices which have reached their maximum value
+   are returned to their minimum value and the input pixel offset is
+   decremented appropriately. */
+                        } else {
+                           dim[ coord_in ] = lbnd[ coord_in ];
+                           off -= stride[ coord_in ] *
+                                  ( ubnd[ coord_in ] - lbnd[ coord_in ] );
+
+/* All the input pixels have been processed once the most significant
+   pixel index has been returned to its minimum value. */
+                           done = ( ++coord_in == ndim_in );
+                        }
+                     } while ( !done );
+                  }
+               }
+
+/* Free the workspace. */
+               dim = astFree( dim );
+            }
+
+/* When all the input pixel coordinates have been generated, use the
+   Mapping's forward transformation to generate the output coordinates
+   from them. Obtain an array of pointers to the resulting coordinate
+   data. */
+            pset_out = astTransform( this, pset_in, 1, NULL );
+            ptr_out = astGetPoints( pset_out );
+         }
+
+/* Annul the PointSet containing the input coordinates. */
+         pset_in = astAnnul( pset_in );
+      }
+   }
+
+/* Copy the output coordinates into the correct positions within the
+   supplied "out" array. */
+/* ================================================================= */
+   if( astOK ) {
+      for ( coord_out = 0; coord_out < ndim_out; coord_out++ ) {
+         for ( point = 0; point < npoint; point++ ) {
+            out[ coord_out ][ offset[ point ] ] = ptr_out[ coord_out ][ point ];
+         }
+      }
+   }
+
+/* Annul the PointSet used to hold output coordinates. */
+   pset_out = astAnnul( pset_out );
+
+/* Free the workspace. */
+   offset = astFree( offset );
+   stride = astFree( stride );
+}
+
+static void TranGridWithBlocking( AstMapping *this, const double *linear_fit,
+                                  int ndim_in, const int *lbnd_in,
+                                  const int *ubnd_in, const int *lbnd,
+                                  const int *ubnd, int ndim_out,
+                                  double *out[], int *status ){
+/*
+*  Name:
+*     TranGridWithBlocking
+
+*  Purpose:
+*     Transforms positions in a section of a grid in a memory-efficient way.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void TranGridWithBlocking( AstMapping *this, const double *linear_fit,
+*                                int ndim_in, const int *lbnd_in,
+*                                const int *ubnd_in, const int *lbnd,
+*                                const int *ubnd, int ndim_out,
+*                                double *out[], int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function transforms positions within a specified section of a
+*     rectangular grid (with any number of dimensions) using the forward
+*     transformation of the supplied Mapping.
+*
+*     This function is very similar to TranGridSection, except that in
+*     order to limit memory usage and to ensure locality of reference,
+*     it divides the input grid up into "blocks" which have a limited
+*     extent along each input dimension. Each block, which will not
+*     contain more than a pre-determined maximum number of pixels, is
+*     then passed to TranGridSection for transformation.
+
+*  Parameters:
+*     this
+*        Pointer to a Mapping, whose forward transformation may be
+*        used to transform the coordinates of pixels in the input
+*        grid into associated positions in the output grid.
+*
+*        The number of input coordintes for the Mapping (Nin
+*        attribute) should match the value of "ndim_in" (below), and
+*        the number of output coordinates (Nout attribute) should
+*        match the value of "ndim_out".
+*     linear_fit
+*        Pointer to an optional array of double which contains the
+*        coefficients of a linear fit which approximates the above
+*        Mapping's forward coordinate transformation. If this is
+*        supplied, it will be used in preference to the above Mapping
+*        when transforming coordinates. This may be used to enhance
+*        performance in cases where evaluation of the Mapping's
+*        forward transformation is expensive. If no linear fit is
+*        available, a NULL pointer should be supplied.
+*
+*        The way in which the fit coefficients are stored in this
+*        array and the number of array elements are as defined by the
+*        astLinearApprox function.
+*     ndim_in
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the first
+*        pixel in the input grid along each dimension.
+*     ubnd_in
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the centre of the last
+*        pixel in the input grid along each dimension.
+*
+*        Note that "lbnd_in" and "ubnd_in" together define the shape
+*        and size of the whole input grid, its extent along a
+*        particular (i'th) dimension being (ubnd_in[i] - lbnd_in[i] +
+*        1). They also define the input grid's coordinate system, with
+*        each pixel being of unit extent along each dimension with
+*        integral coordinate values at its centre.
+*     lbnd
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the first pixel in the
+*        section of the input data grid which is to be transformed.
+*     ubnd
+*        Pointer to an array of integers, with "ndim_in" elements.
+*        This should give the coordinates of the last pixel in the
+*        section of the input data grid which is to be transformed.
+*
+*        Note that "lbnd" and "ubnd" define the shape and position of the
+*        section of the input grid which is to be transformed.
+*     ndim_out
+*        The number of dimensions in the output grid. This should be
+*        at least one.
+*     out
+*        Pointer to an array with "ndim_out" elements. Element [i] of
+*        this array is a pointer to an array in which to store the
+*        transformed values for output axis "i". The points are ordered
+*        such that the first axis of the input grid changes most rapidly.
+*        For example, if the input grid is 2-dimensional and extends from
+*        (2,-1) to (3,1), the output points will be stored in the order
+*        (2,-1), (3, -1), (2,0), (3,0), (2,1), (3,1).
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Constants: */
+   const int mxpix = 2 * 1024;   /* Maximum number of pixels in a block (this
+                                    relatively small number seems to give best
+                                    performance) */
+
+/* Local Variables: */
+   int *dim_block;               /* Pointer to array of block dimensions */
+   int *lbnd_block;              /* Pointer to block lower bound array */
+   int *ubnd_block;              /* Pointer to block upper bound array */
+   int dim;                      /* Dimension size */
+   int done;                     /* All blocks rebinned? */
+   int hilim;                    /* Upper limit on maximum block dimension */
+   int idim;                     /* Loop counter for dimensions */
+   int lolim;                    /* Lower limit on maximum block dimension */
+   int mxdim_block;              /* Maximum block dimension */
+   int npix;                     /* Number of pixels in block */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Allocate workspace. */
+   lbnd_block = astMalloc( sizeof( int ) * (size_t) ndim_in );
+   ubnd_block = astMalloc( sizeof( int ) * (size_t) ndim_in );
+   dim_block = astMalloc( sizeof( int ) * (size_t) ndim_in );
+   if ( astOK ) {
+
+/* Find the optimum block size. */
+/* ---------------------------- */
+/* We first need to find the maximum extent which a block of input
+   pixels may have in each dimension. We determine this by taking the
+   input grid extent in each dimension and then limiting the maximum
+   dimension size until the resulting number of pixels is sufficiently
+   small. This approach allows the block shape to approximate (or
+   match) the input grid shape when appropriate. */
+
+/* First loop to calculate the total number of input pixels and the
+   maximum input dimension size. */
+      npix = 1;
+      mxdim_block = 0;
+      for ( idim = 0; idim < ndim_in; idim++ ) {
+         dim = ubnd[ idim ] - lbnd[ idim ] + 1;
+         npix *= dim;
+         if ( mxdim_block < dim ) mxdim_block = dim;
+      }
+
+/* If the number of input pixels is too large for a single block, we
+   perform iterations to determine the optimum upper limit on a
+   block's dimension size. Initialise the limits on this result. */
+      if ( npix > mxpix ) {
+         lolim = 1;
+         hilim = mxdim_block;
+
+/* Loop to perform a binary chop, searching for the best result until
+   the lower and upper limits on the result converge to adjacent
+   values. */
+         while ( ( hilim - lolim ) > 1 ) {
+
+/* Form a new estimate from the mid-point of the previous limits. */
+            mxdim_block = ( hilim + lolim ) / 2;
+
+/* See how many pixels a block contains if its maximum dimension is
+   limited to this new value. */
+            for ( npix = 1, idim = 0; idim < ndim_in; idim++ ) {
+               dim = ubnd[ idim ] - lbnd[ idim ] + 1;
+               npix *= ( dim < mxdim_block ) ? dim : mxdim_block;
+            }
+
+/* Update the appropriate limit, according to whether the number of
+   pixels is too large or too small. */
+            *( ( npix <= mxpix ) ? &lolim : &hilim ) = mxdim_block;
+         }
+
+/* When iterations have converged, obtain the maximum limit on the
+   dimension size of a block which results in no more than the maximum
+   allowed number of pixels per block. However, ensure that all block
+   dimensions are at least 2. */
+            mxdim_block = lolim;
+      }
+      if ( mxdim_block < 2 ) mxdim_block = 2;
+
+/* Calculate the block dimensions by applying this limit to the output
+   grid dimensions. */
+      for ( idim = 0; idim < ndim_in; idim++ ) {
+         dim = ubnd[ idim ] - lbnd[ idim ] + 1;
+         dim_block[ idim ] = ( dim < mxdim_block ) ? dim : mxdim_block;
+
+/* Also initialise the lower and upper bounds of the first block of
+   output grid pixels to be rebinned, ensuring that this does not
+   extend outside the grid itself. */
+         lbnd_block[ idim ] = lbnd[ idim ];
+         ubnd_block[ idim ] = MinI( lbnd[ idim ] + dim_block[ idim ] - 1,
+                                    ubnd[ idim ], status );
+      }
+
+/* Transform each block of input grid positions. */
+/* --------------------------------------------- */
+/* Loop to generate the extent of each block of input grid positions and to
+   transform them. */
+      done = 0;
+      while ( !done && astOK ) {
+
+/* Rebin the current block, accumulating the sum of bad pixels produced. */
+         TranGridSection( this, linear_fit, ndim_in, lbnd_in, ubnd_in,
+                          lbnd_block, ubnd_block, ndim_out, out, status );
+
+/* Update the block extent to identify the next block of input pixels. */
+         idim = 0;
+         do {
+
+/* We find the least significant dimension where the upper bound of
+   the block has not yet reached the upper bound of the region of the
+   input grid which we are rebinning. The block's position is then
+   incremented by one block extent along this dimension, checking that
+   the resulting extent does not go outside the region being rebinned. */
+            if ( ubnd_block[ idim ] < ubnd[ idim ] ) {
+               lbnd_block[ idim ] = MinI( lbnd_block[ idim ] +
+                                          dim_block[ idim ], ubnd[ idim ], status );
+               ubnd_block[ idim ] = MinI( lbnd_block[ idim ] +
+                                          dim_block[ idim ] - 1,
+                                          ubnd[ idim ], status );
+               break;
+
+/* If any less significant dimensions are found where the upper bound
+   of the block has reached its maximum value, we reset the block to
+   its lowest position. */
+            } else {
+               lbnd_block[ idim ] = lbnd[ idim ];
+               ubnd_block[ idim ] = MinI( lbnd[ idim ] + dim_block[ idim ] - 1,
+                                          ubnd[ idim ], status );
+
+/* All the blocks have been processed once the position along the most
+   significant dimension has been reset. */
+               done = ( ++idim == ndim_in );
+            }
+         } while ( !done );
+      }
+   }
+
+/* Free the workspace. */
+   lbnd_block = astFree( lbnd_block );
+   ubnd_block = astFree( ubnd_block );
+   dim_block = astFree( dim_block );
+}
+
+static void TranN( AstMapping *this, int npoint,
+                   int ncoord_in, int indim, const double *in,
+                   int forward,
+                   int ncoord_out, int outdim, double *out, int *status ) {
+/*
+*++
+*  Name:
+c     astTranN
+f     AST_TRANN
+
+*  Purpose:
+*     Transform N-dimensional coordinates.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astTranN( AstMapping *this, int npoint,
+c                    int ncoord_in, int indim, const double *in,
+c                    int forward,
+c                    int ncoord_out, int outdim, double *out )
+f     CALL AST_TRANN( THIS, NPOINT,
+f                     NCOORD_IN, INDIM, IN,
+f                     FORWARD, NCOORD_OUT, OUTDIM, OUT, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+c     This function applies a Mapping to transform the coordinates of
+f     This routine applies a Mapping to transform the coordinates of
+*     a set of points in an arbitrary number of dimensions. It is the
+*     appropriate routine to use if the coordinates are not purely 1-
+*     or 2-dimensional and are stored in a single array (which they
+*     need not fill completely).
+c
+c     If the coordinates are not stored in a single array, then the
+c     astTranP function might be more suitable.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping to be applied.
+c     npoint
+f     NPOINT = INTEGER (Given)
+*        The number of points to be transformed.
+c     ncoord_in
+f     NCOORD_IN = INTEGER (Given)
+*        The number of coordinates being supplied for each input point
+*        (i.e. the number of dimensions of the space in which the
+*        input points reside).
+c     indim
+f     INDIM = INTEGER (Given)
+c        The number of elements along the second dimension of the "in"
+f        The number of elements along the first dimension of the IN
+*        array (which contains the input coordinates). This value is
+*        required so that the coordinate values can be correctly
+*        located if they do not entirely fill this array. The value
+c        given should not be less than "npoint".
+f        given should not be less than NPOINT.
+c     in
+f     IN( INDIM, NCOORD_IN ) = DOUBLE PRECISION (Given)
+c        The address of the first element in a 2-dimensional array of
+c        shape "[ncoord_in][indim]",
+c        containing the coordinates of the input (untransformed)
+c        points. These should be stored such that the value of
+c        coordinate number "coord" for input point number "point" is
+c        found in element "in[coord][point]".
+f        An array containing the coordinates of the input
+f        (untransformed) points. These should be stored such that the
+f        value of coordinate number COORD for input point number POINT
+f        is found in element IN(POINT,COORD).
+c     forward
+f     FORWARD = LOGICAL (Given)
+c        A non-zero value indicates that the Mapping's forward
+c        coordinate transformation is to be applied, while a zero
+c        value indicates that the inverse transformation should be
+c        used.
+f        A .TRUE. value indicates that the Mapping's forward
+f        coordinate transformation is to be applied, while a .FALSE.
+f        value indicates that the inverse transformation should be
+f        used.
+c     ncoord_out
+f     NCOORD_OUT = INTEGER (Given)
+*        The number of coordinates being generated by the Mapping for
+*        each output point (i.e. the number of dimensions of the
+*        space in which the output points reside). This need not be
+c        the same as "ncoord_in".
+f        the same as NCOORD_IN.
+c     outdim
+f     OUTDIM = INTEGER (Given)
+c        The number of elements along the second dimension of the "out"
+f        The number of elements along the first dimension of the OUT
+*        array (which will contain the output coordinates). This value
+*        is required so that the coordinate values can be correctly
+*        located if they will not entirely fill this array. The value
+c        given should not be less than "npoint".
+f        given should not be less than NPOINT.
+c     out
+f     OUT( OUTDIM, NCOORD_OUT ) = DOUBLE PRECISION (Returned)
+c        The address of the first element in a 2-dimensional array of
+c        shape "[ncoord_out][outdim]", into
+c        which the coordinates of the output (transformed) points will
+c        be written. These will be stored such that the value of
+c        coordinate number "coord" for output point number "point"
+c        will be found in element "out[coord][point]".
+f        An array into which the coordinates of the output
+f        (transformed) points will be written. These will be stored
+f        such that the value of coordinate number COORD for output
+f        point number POINT will be found in element OUT(POINT,COORD).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+c     - If the forward coordinate transformation is being applied, the
+c     Mapping supplied must have the value of "ncoord_in" for its Nin
+c     attribute and the value of "ncoord_out" for its Nout attribute. If
+c     the inverse transformation is being applied, these values should
+c     be reversed.
+f     - If the forward coordinate transformation is being applied, the
+f     Mapping supplied must have the value of NCOORD_IN for its Nin
+f     attribute and the value of NCOORD_OUT for its Nout attribute. If
+f     the inverse transformation is being applied, these values should
+f     be reversed.
+*--
+*/
+
+/* Local Variables: */
+   AstPointSet *in_points;       /* Pointer to input PointSet */
+   AstPointSet *out_points;      /* Pointer to output PointSet */
+   const double **in_ptr;        /* Pointer to array of input data pointers */
+   double **out_ptr;             /* Pointer to array of output data pointers */
+   int coord;                    /* Loop counter for coordinates */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the mapping and numbers of points/coordinates. */
+   ValidateMapping( this, forward, npoint, ncoord_in, ncoord_out, "astTranN", status );
+
+/* Also validate the input array dimension argument. */
+   if ( astOK && ( indim < npoint ) ) {
+      astError( AST__DIMIN, "astTranN(%s): The input array dimension value "
+                "(%d) is invalid.", status, astGetClass( this ), indim );
+      astError( AST__DIMIN, "This should not be less than the number of "
+                "points being transformed (%d).", status, npoint );
+   }
+
+/* Similarly, validate the output array dimension argument. */
+   if ( astOK && ( outdim < npoint ) ) {
+      astError( AST__DIMIN, "astTranN(%s): The output array dimension value "
+                "(%d) is invalid.", status, astGetClass( this ), outdim );
+      astError( AST__DIMIN, "This should not be less than the number of "
+                "points being transformed (%d).", status, npoint );
+   }
+
+/* Allocate memory to hold the arrays of input and output data
+   pointers. */
+   if ( astOK ) {
+      in_ptr = (const double **) astMalloc( sizeof( const double * ) *
+                                            (size_t) ncoord_in );
+      out_ptr = astMalloc( sizeof( double * ) * (size_t) ncoord_out );
+
+
+#ifdef DEBUG
+      { int i, ns;
+        ns = ncoord_out*outdim;
+        for( i = 0; i < ns; i++ ) out[ i ] = 0.0;
+      }
+#endif
+
+
+/* Initialise the input data pointers to locate the coordinate data in
+   the "in" array. */
+      if ( astOK ) {
+         for ( coord = 0; coord < ncoord_in; coord++ ) {
+            in_ptr[ coord ] = in + coord * indim;
+         }
+
+/* Similarly initialise the output data pointers to point into the
+   "out" array. */
+         for ( coord = 0; coord < ncoord_out; coord++ ) {
+            out_ptr[ coord ] = out + coord * outdim;
+         }
+
+/* Create PointSets to describe the input and output points. */
+         in_points = astPointSet( npoint, ncoord_in, "", status );
+         out_points = astPointSet( npoint, ncoord_out, "", status );
+
+/* Associate the data pointers with the PointSets (note we must
+   explicitly remove the "const" qualifier from the input data here,
+   although they will not be modified).  */
+         astSetPoints( in_points, (double **) in_ptr );
+         astSetPoints( out_points, out_ptr );
+
+/* Apply the required transformation to the coordinates. */
+         (void) astTransform( this, in_points, forward, out_points );
+
+/* If the Mapping's Report attribute is set, report the effect the
+   Mapping has had on the coordinates. */
+         if ( astGetReport( this ) ) astReportPoints( this, forward,
+                                                      in_points, out_points );
+
+/* Delete the two PointSets. */
+         in_points = astDelete( in_points );
+         out_points = astDelete( out_points );
+      }
+
+/* Free the memory used for the data pointers. */
+      in_ptr = (const double **) astFree( (void *) in_ptr );
+      out_ptr = astFree( out_ptr );
+   }
+}
+
+static void TranP( AstMapping *this, int npoint,
+                   int ncoord_in, const double *ptr_in[],
+                   int forward, int ncoord_out, double *ptr_out[], int *status ) {
+/*
+c++
+*  Name:
+*     astTranP
+
+*  Purpose:
+*     Transform N-dimensional coordinates held in separate arrays.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void astTranP( AstMapping *this, int npoint,
+*                    int ncoord_in, const double *ptr_in[],
+*                    int forward, int ncoord_out, double *ptr_out[] )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function applies a Mapping to transform the coordinates of
+*     a set of points in an arbitrary number of dimensions. It is the
+*     appropriate routine to use if the coordinates are not purely 1-
+*     or 2-dimensional and are stored in separate arrays, since each
+*     coordinate array is located by supplying a separate pointer to
+*     it.
+*
+*     If the coordinates are stored in a single (2-dimensional) array,
+*     then the astTranN function might be more suitable.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     npoint
+*        The number of points to be transformed.
+*     ncoord_in
+*        The number of coordinates being supplied for each input point
+*        (i.e. the number of dimensions of the space in which the
+*        input points reside).
+*     ptr_in
+*        An array of pointers to double, with "ncoord_in"
+*        elements. Element "ptr_in[coord]" should point at the first
+*        element of an array of double (with "npoint" elements) which
+*        contain the values of coordinate number "coord" for each
+*        input (untransformed) point. The value of coordinate number
+*        "coord" for input point number "point" is therefore given by
+*        "ptr_in[coord][point]" (assuming both indices are
+*        zero-based).
+*     forward
+*        A non-zero value indicates that the Mapping's forward
+*        coordinate transformation is to be applied, while a zero
+*        value indicates that the inverse transformation should be
+*        used.
+*     ncoord_out
+*        The number of coordinates being generated by the Mapping for
+*        each output point (i.e. the number of dimensions of the space
+*        in which the output points reside). This need not be the same
+*        as "ncoord_in".
+*     ptr_out
+*        An array of pointers to double, with "ncoord_out"
+*        elements. Element "ptr_out[coord]" should point at the first
+*        element of an array of double (with "npoint" elements) into
+*        which the values of coordinate number "coord" for each output
+*        (transformed) point will be written.  The value of coordinate
+*        number "coord" for output point number "point" will therefore
+*        be found in "ptr_out[coord][point]".
+
+*  Notes:
+*     - If the forward coordinate transformation is being applied, the
+*     Mapping supplied must have the value of "ncoord_in" for its Nin
+*     attribute and the value of "ncoord_out" for its Nout
+*     attribute. If the inverse transformation is being applied, these
+*     values should be reversed.
+*     - This routine is not available in the Fortran 77 interface to
+*     the AST library.
+c--
+*/
+
+/* Local Variables: */
+   AstPointSet *in_points;       /* Pointer to input PointSet */
+   AstPointSet *out_points;      /* Pointer to output PointSet */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the Mapping and number of points/coordinates. */
+   ValidateMapping( this, forward, npoint, ncoord_in, ncoord_out, "astTranP", status );
+
+/* Create PointSets to describe the input and output points. */
+   if ( astOK ) {
+      in_points = astPointSet( npoint, ncoord_in, "", status );
+      out_points = astPointSet( npoint, ncoord_out, "", status );
+
+/* Associate the data pointers with the PointSets (note we must
+   explicitly remove the "const" qualifier from the input data here,
+   although they will not be modified).  */
+      astSetPoints( in_points, (double **) ptr_in );
+      astSetPoints( out_points, ptr_out );
+
+/* Apply the required transformation to the coordinates. */
+      (void) astTransform( this, in_points, forward, out_points );
+
+/* If the Mapping's Report attribute is set, report the effect the
+   Mapping has had on the coordinates. */
+      if ( astGetReport( this ) ) astReportPoints( this, forward,
+                                                   in_points, out_points );
+
+/* Delete the two PointSets. */
+      in_points = astDelete( in_points );
+      out_points = astDelete( out_points );
+   }
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*+
+*  Name:
+*     astTransform
+
+*  Purpose:
+*     Transform a set of points.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     AstPointSet *astTransform( AstMapping *this, AstPointSet *in,
+*                                int forward, AstPointSet *out )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This function takes a Mapping and a set of points encapsulated
+*     in a PointSet, and applies either the forward or inverse
+*     coordinate transformation (if defined by the Mapping) to the
+*     points.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping. The nature of the coordinate
+*        transformation will depend on the class of Mapping
+*        supplied. Note that there is no constructor for the Mapping
+*        class itself, so this object should be from a derived class.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate
+*        transformation should be applied, while a zero value requests
+*        the inverse transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed
+*        (output) coordinate values. A NULL value may also be given,
+*        in which case a new PointSet will be created by this
+*        function.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     - An error will result if the Mapping supplied does not define
+*     the requested coordinate transformation (either forward or
+*     inverse).
+*     - The number of coordinate values per point in the input
+*     PointSet must match the number of input coordinates for the
+*     Mapping being applied (or number of output coordinates if the
+*     inverse transformation is requested).
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points and coordinate values per point to
+*     accommodate the result (e.g. the number of Mapping output
+*     coordinates, or number of input coordinates if the inverse
+*     transformation is requested). Any excess space will be ignored.
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   int def;                      /* Coordinate transformation defined? */
+   int ncoord_in;                /* Number of input PointSet coordinates */
+   int ncoord_out;               /* Number of coordinates in output PointSet */
+   int nin;                      /* Number of input Mapping coordinates */
+   int nout;                     /* Number of output Mapping coordinates */
+   int npoint;                   /* Number of points to transform */
+   int npoint_out;               /* Number of points in output PointSet */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise. */
+   result = NULL;
+
+/* Determine if a coordinate transformation is defined for the requested
+   direction. */
+   def = forward ? astGetTranForward( this ) : astGetTranInverse( this );
+
+/* Report an error if the transformation is not defined. */
+   if ( astOK && !def ) {
+      astError( AST__TRNND, "astTransform(%s): %s coordinate transformation "
+                "is not defined by the %s supplied.", status, astGetClass( this ),
+                forward ? "A forward" : "An inverse", astGetClass( this ) );
+   }
+
+/* Obtain the effective number of input and output coordinate values for the
+   transformation to be performed, taking account of the transformation
+   direction required. Note we use Mapping methods to obtain these values, as
+   this will take account of whether the Mapping has been inverted. */
+   nin = forward ? astGetNin( this ) : astGetNout( this );
+   nout = forward ? astGetNout( this ) : astGetNin( this );
+
+/* Obtain the number of input points to transform and the number of coordinate
+   values per input point. */
+   npoint = astGetNpoint( in );
+   ncoord_in = astGetNcoord( in );
+
+/* If OK, check that the number of input coordinates matches the number
+   required by the mapping. Report an error if these numbers do not match. */
+   if ( astOK && ( ncoord_in != nin ) ) {
+      astError( AST__NCPIN, "astTransform(%s): Bad number of coordinate "
+                "values (%d) in input %s.", status, astGetClass( this ), ncoord_in,
+                astGetClass( in ) );
+      astError( AST__NCPIN, "The %s given requires %d coordinate value(s) for "
+                "each input point.", status, astGetClass( this ), nin );
+   }
+
+/* If still OK, and a non-NULL pointer has been given for the output PointSet,
+   then obtain the number of points and number of coordinates per point for
+   this PointSet. */
+   if ( astOK && out ) {
+      npoint_out = astGetNpoint( out );
+      ncoord_out = astGetNcoord( out );
+
+/* Check that the dimensions of this PointSet are adequate to accommodate the
+   output coordinate values and report an error if they are not. */
+      if ( astOK ) {
+         if ( npoint_out < npoint ) {
+            astError( AST__NOPTS, "astTransform(%s): Too few points (%d) in "
+                      "output %s.", status, astGetClass( this ), npoint_out,
+                      astGetClass( out ) );
+            astError( AST__NOPTS, "The %s needs space to hold %d transformed "
+                      "point(s).", status, astGetClass( this ), npoint );
+         } else if ( ncoord_out < nout ) {
+            astError( AST__NOCTS, "astTransform(%s): Too few coordinate "
+                      "values per point (%d) in output %s.", status,
+                      astGetClass( this ), ncoord_out, astGetClass( out ) );
+            astError( AST__NOCTS, "The %s supplied needs space to store %d "
+                      "coordinate value(s) per transformed point.", status,
+                      astGetClass( this ), nout );
+         }
+      }
+   }
+
+/* If all the validation stages are passed successfully, and a NULL output
+   pointer was given, then create a new PointSet to encapsulate the output
+   coordinate data. */
+   if ( astOK ) {
+      if ( !out ) {
+         result = astPointSet( npoint, nout, "", status );
+
+/* Otherwise, use the PointSet supplied. */
+      } else {
+         result = out;
+      }
+   }
+
+/* Return a pointer to the output PointSet. Note that we do not actually
+   transform (or even copy) the coordinates. This is left for derived classes
+   to implement. */
+   return result;
+}
+
+/*
+*++
+*  Name:
+c     astUinterp
+f     AST_UINTERP
+
+*  Purpose:
+*     Perform sub-pixel interpolation on a grid of data.
+
+*  Type:
+*     Fictitious function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astUinterp( int ndim_in, const int lbnd_in[], const int ubnd_in[],
+c                      const <Xtype> in[], const <Xtype> in_var[],
+c                      int npoint, const int offset[],
+c                      const double *const coords[], const double params[],
+c                      int flags, <Xtype> badval,
+c                      <Xtype> out[], <Xtype> out_var[], int *nbad )
+f     CALL AST_UINTERP( NDIM_IN, LBND_IN, UBND_IN, IN, IN_VAR,
+f                       NPOINT, OFFSET, COORDS, PARAMS, FLAGS, BADVAL,
+f                       OUT, OUT_VAR, NBAD, STATUS )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+c     This is a fictitious function which does not actually
+c     exist. Instead, this description constitutes a template so that
+c     you may implement a function with this interface for yourself
+c     (and give it any name you wish). A pointer to such a function
+c     may be passed via the "finterp" parameter of the astResample<X>
+c     functions (q.v.) in order to perform sub-pixel interpolation
+c     during resampling of gridded data (you must also set the
+c     "interp" parameter of astResample<X> to the value
+c     AST__UINTERP). This allows you to use your own interpolation
+c     algorithm in addition to those which are pre-defined.
+f     This is a fictitious routine which does not actually
+f     exist. Instead, this description constitutes a template so that
+f     you may implement a routine with this interface for yourself
+f     (and give it any name you wish). Such a routine
+f     may be passed via the FINTERP argument of the AST_RESAMPLE<X>
+f     functions (q.v.) in order to perform sub-pixel interpolation
+f     during resampling of gridded data (you must also set the
+f     INTERP argument of AST_RESAMPLE<X> to the value
+f     AST__UINTERP). This allows you to use your own interpolation
+f     algorithm in addition to those which are pre-defined.
+*
+c     The function interpolates an input grid of data (and,
+f     The routine interpolates an input grid of data (and,
+*     optionally, processes associated statistical variance estimates)
+*     at a specified set of points.
+
+*  Parameters:
+c     ndim_in
+f     NDIM_IN = INTEGER (Given)
+*        The number of dimensions in the input grid. This will be at
+*        least one.
+c     lbnd_in
+f     LBND_IN( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the centre of the first pixel
+*        in the input grid along each dimension.
+c     ubnd_in
+f     UBND_IN( NDIM_IN ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim_in" elements,
+f        An array
+*        containing the coordinates of the centre of the last pixel in
+*        the input grid along each dimension.
+*
+c        Note that "lbnd_in" and "ubnd_in" together define the shape,
+f        Note that LBND_IN and UBND_IN together define the shape,
+*        size and coordinate system of the input grid in the same
+c        way as they do in astResample<X>.
+f        way as they do in AST_RESAMPLE<X>.
+c     in
+f     IN( * ) = <Xtype> (Given)
+c        Pointer to an array, with one element for each pixel in the
+f        An array, with one element for each pixel in the
+*        input grid, containing the input data. This will be the same
+c        array as was passed to astResample<X> via the "in" parameter.
+f        array as was passed to AST_RESAMPLE<X> via the IN argument.
+*        The numerical type of this array should match that of the
+*        data being processed.
+c     in_var
+f     IN_VAR( * ) = <Xtype> (Given)
+c        Pointer to an optional second array with the same size and
+c        type as the "in" array. If given, this will contain the set
+c        of variance values associated with the input data and will be
+c        the same array as was passed to astResample<X> via the
+c        "in_var" parameter.
+f        An optional second array with the same size and type as the
+f        IN array. This will only be given if the AST__USEVAR flag is
+f        set via the FLAGS argument (below). If given, it will contain
+f        the set of variance values associated with the input data and
+f        will be the same array as was passed to AST_RESAMPLE<X> via
+f        the IN_VAR argument.
+*
+c        If no variance values are being processed, this will be a
+c        NULL pointer.
+f        If the AST__USEVAR flag is not set, then no variance values
+f        are being processed. In this case, this array of variance
+f        values may be a dummy (e.g. one-element) array and should not
+f        be used.
+c     npoint
+f     NPOINT = INTEGER (Given)
+*        The number of points at which the input grid is to be
+*        interpolated. This will be at least one.
+c     offset
+f     OFFSET( NPOINT ) = INTEGER (Given)
+c        Pointer to an array of integers with "npoint" elements. For
+c        each interpolation point, this will contain the zero-based
+c        index in the "out" (and "out_var") array(s) at which the
+c        interpolated value (and its variance, if required) should be
+c        stored. For example, the interpolated value for point number
+c        "point" should be stored in "out[offset[point]]" (assuming
+c        the index "point" is zero-based).
+f        For each interpolation point, this array will contain the
+f        offset from the start of the OUT (and OUT_VAR) array(s) at
+f        which the interpolated value (and its variance, if required)
+f        should be stored. For example, the interpolated value for
+f        point number POINT should be stored in OUT(1+OFFSET(POINT)).
+c     coords
+f     COORDS( NPOINT, NDIM_IN ) = DOUBLE PRECISION (Given)
+c        An array of pointers to double, with "ndim_in"
+c        elements. Element "coords[coord]" will point at the first
+c        element of an array of double (with "npoint" elements) which
+c        contains the values of coordinate number "coord" for each
+c        interpolation point. The value of coordinate number "coord"
+c        for interpolation point number "point" is therefore given by
+c        "coords[coord][point]" (assuming both indices are
+c        zero-based).
+f        A 2-dimensional array containing the coordinates of the
+f        points at which interpolation should be performed. These will
+f        be stored so that coordinate number COORD for interpolation
+f        point number POINT is found in element COORDS(POINT,COORD).
+*
+*        If any interpolation point has any of its coordinates equal
+c        to the value AST__BAD (as defined in the "ast.h" header
+f        to the value AST__BAD (as defined in the AST_PAR include
+*        file), then the corresponding output data (and variance)
+c        should either be set to the value given by "badval",
+f        should either be set to the value given by BADVAL,
+*        or left unchanged, depending on whether the AST__NOBAD flag is
+c        specified by "flags".
+f        specified by FLAGS.
+c     params
+f     PARAMS( * ) = DOUBLE PRECISION (Given)
+c        This will be a pointer to the same array as was given via the
+c        "params" parameter of astResample<X>. You may use this to
+f        This will be the same array as was given via the
+f        PARAMS argument of AST_RESAMPLE<X>. You may use this to
+*        pass any additional parameter values required by your
+*        interpolation algorithm.
+c     flags
+f     FLAGS = INTEGER (Given)
+c        This will be the same value as was given via the "flags"
+c        parameter of astResample<X>. You may test this value to
+f        This will be the same value as was given via the FLAGS
+f        argument of AST_RESAMPLE<X>. You may test this value to
+*        provide additional control over the operation of your
+*        resampling algorithm. Note that the special flag values
+*        AST__URESAMP1, 2, 3 & 4 are reserved for you to use for your
+*        own purposes and will not clash with other pre-defined flag
+c        values (see astResample<X>).
+f        values (see AST_RESAMPLE<X>).
+c     badval
+f     BADVAL = <Xtype> (Given)
+c        This will be the same value as was given via the "badval"
+c        parameter of astResample<X>, and will have the same numerical
+c        type as the data being processed (i.e. as elements of the
+c        "in" array).  It should be used to test for bad pixels in the
+c        input grid (but only if the AST__USEBAD flag is set via the
+c        "flags" parameter) and (unless the AST__NOBAD flag is set in
+c        "flags") for identifying bad output values in
+c        the "out" (and "out_var") array(s).
+f        This will be the same value as was given for the BADVAL
+f        argument of AST_RESAMPLE<X>, and will have the same numerical
+f        type as the data being processed (i.e. as elements of the IN
+f        array).  It should be used to test for bad pixels in the
+f        input grid (but only if the AST__USEBAD flag is set via the
+f        FLAGS argument) and (unless the AST__NOBAD flag is set in
+f        FLAGS) for identifying bad output values in the OUT (and
+f        OUT_VAR) array(s).
+c     out
+f     OUT( * ) = <Xtype> (Returned)
+c        Pointer to an array with the same numerical type as the "in"
+f        An array with the same numerical type as the IN
+*        array, into which the interpolated data values should be
+*        returned.  Note that details of the storage order and number
+*        of dimensions of this array are not required, since the
+c        "offset" array contains all necessary information about where
+f        OFFSET array contains all necessary information about where
+*        each returned value should be stored.
+*
+c        In general, not all elements of this array (or the "out_var"
+f        In general, not all elements of this array (or the OUT_VAR
+*        array below) may be used in any particular invocation of the
+c        function. Those which are not used should be returned
+f        routine. Those which are not used should be returned
+*        unchanged.
+c     out_var
+f     OUT_VAR( * ) = <Xtype> (Returned)
+c        Pointer to an optional array with the same type and size as
+c        the "out" array, into which variance estimates for the
+c        resampled values should be returned.  This array will only be
+c        given if the "in_var" array has also been given.
+f        An optional array with the same type and size as the OUT
+f        array, into which variance estimates for the resampled values
+f        should be returned. This array will only be given if the
+f        AST__USEVAR flag is set via the FLAGS argument.
+*
+c        If given, it is addressed in exactly the same way (via the
+c        "offset" array) as the "out" array. The values returned
+c        should be estimates of the statistical variance of the
+c        corresponding values in the "out" array, on the assumption
+c        that all errors in input data values are statistically
+c        independent and that their variance estimates may simply be
+c        summed (with appropriate weighting factors).
+f        If given, it is addressed in exactly the same way (via the
+f        OFFSET array) as the OUT array. The values returned should be
+f        estimates of the statistical variance of the corresponding
+f        values in the OUT array, on the assumption that all errors in
+f        input data values are statistically independent and that
+f        their variance estimates may simply be summed (with
+f        appropriate weighting factors).
+*
+c        If no output variance estimates are required, a NULL pointer
+c        will be given.
+f        If the AST__USEVAR flag is not set, then variance values are
+f        not being processed.  In this case, this array may be a dummy
+f        (e.g. one-element) array and should not be used.
+c     nbad
+f     NBAD = INTEGER (Returned)
+c        Pointer to an int in which to return the number of interpolation
+c        points at
+f        This should return the number of interpolation points at
+*        which no valid interpolated value could be obtained.  The maximum
+c        value that should be returned is "npoint", and the minimum is
+f        value that should be returned is NPOINT, and the minimum is
+*        zero (indicating that all output values were successfully
+*        obtained).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - The data type <Xtype> indicates the numerical type of the data
+c     being processed, as for astResample<X>.
+f     being processed, as for AST_RESAMPLE<X>.
+c     - This function will typically be invoked more than once for each
+c     invocation of astResample<X>.
+f     - This routine will typically be invoked more than once for each
+f     invocation of AST_RESAMPLE<X>.
+c     - If an error occurs within this function, it should use
+c     astSetStatus to set the AST error status to an error value.
+c     This will cause an immediate return from astResample<X>. The error
+c     value AST__UINER is available for this purpose, but other values may
+c     also be used (e.g. if you wish to distinguish different types of
+c     error).
+f     - If an error occurs within this routine, it should set the
+f     STATUS argument to an error value before returning. This will
+f     cause an immediate return from AST_RESAMPLE<X>. The error value
+f     AST__UINER is available for this purpose, but other values may also
+f     be used (e.g. if you wish to distinguish different types of error).
+f     The AST__UINER error value is defined in the AST_ERR include file.
+*--
+*/
+/* Note the above is just a description to act as a template. The
+   function does not actually exist. */
+
+/*
+*++
+*  Name:
+c     astUkern1
+f     AST_UKERN1
+
+*  Purpose:
+*     1-dimensional sub-pixel interpolation kernel.
+
+*  Type:
+*     Fictitious function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astUkern1( double offset, const double params[], int flags,
+c                     double *value )
+f     CALL AST_UKERN1( OFFSET, PARAMS, FLAGS, VALUE, STATUS )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+c     This is a fictitious function which does not actually
+c     exist. Instead, this description constitutes a template so that
+c     you may implement a function with this interface for yourself
+c     (and give it any name you wish). A pointer to such a function
+c     may be passed via the "finterp" parameter of the astResample<X>
+c     functions (q.v.) in order to supply a 1-dimensional
+c     interpolation kernel to the algorithm which performs sub-pixel
+c     interpolation during resampling of gridded data (you must also
+c     set the "interp" parameter of astResample<X> to the value
+c     AST__UKERN1). This allows you to use your own interpolation
+c     kernel in addition to those which are pre-defined.
+f     This is a fictitious routine which does not actually
+f     exist. Instead, this description constitutes a template so that
+f     you may implement a routine with this interface for yourself
+f     (and give it any name you wish). Such a routine
+f     may be passed via the FINTERP argument of the AST_RESAMPLE<X>
+f     functions (q.v.) in order to supply a 1-dimensional
+f     interpolation kernel to the algorithm which performs sub-pixel
+f     interpolation during resampling of gridded data (you must also
+f     set the INTERP argument of AST_RESAMPLE<X> to the value
+f     AST__UKERN1). This allows you to use your own interpolation
+f     kernel in addition to those which are pre-defined.
+*
+c     The function calculates the value of a 1-dimensional sub-pixel
+f     The routine calculates the value of a 1-dimensional sub-pixel
+*     interpolation kernel. This determines how the weight given to
+*     neighbouring pixels in calculating an interpolated value depends
+*     on the pixel's offset from the interpolation point. In more than
+*     one dimension, the weight assigned to a pixel is formed by
+*     evaluating this 1-dimensional kernel using the offset along each
+*     dimension in turn. The product of the returned values is then
+*     used as the pixel weight.
+
+*  Parameters:
+c     offset
+f     OFFSET = DOUBLE PRECISION (Given)
+*        This will be the offset of the pixel from the interpolation
+*        point, measured in pixels. This value may be positive or
+*        negative, but for most practical interpolation schemes its
+*        sign should be ignored.
+c     params
+f     PARAMS( * ) = DOUBLE PRECISION (Given)
+c        This will be a pointer to the same array as was given via the
+c        "params" parameter of astResample<X>. You may use this to
+f        This will be the same array as was given via the
+f        PARAMS argument of AST_RESAMPLE<X>. You may use this to
+*        pass any additional parameter values required by your kernel,
+c        but note that "params[0]" will already have been used to specify
+f        but note that PARAMS(1) will already have been used to specify
+*        the number of neighbouring pixels which contribute to the
+*        interpolated value.
+c     flags
+f     FLAGS = INTEGER (Given)
+c        This will be the same value as was given via the "flags"
+c        parameter of astResample<X>. You may test this value to
+f        This will be the same value as was given via the FLAGS
+f        argument of AST_RESAMPLE<X>. You may test this value to
+*        provide additional control over the operation of your
+c        function. Note that the special flag values AST__URESAMP1, 2,
+f        routine. Note that the special flag values AST__URESAMP1, 2,
+*        3 & 4 are reserved for you to use for your own purposes and
+*        will not clash with other pre-defined flag
+c        values (see astResample<X>).
+f        values (see AST_RESAMPLE<X>).
+c     value
+f     VALUE = DOUBLE PRECISION (Returned)
+c        Pointer to a double to receive the calculated kernel value,
+f        The calculated kernel value,
+*        which may be positive or negative.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Not all functions make good interpolation kernels. In general,
+*     acceptable kernels tend to be symmetrical about zero, to have a
+*     positive peak (usually unity) at zero, and to evaluate to zero
+*     whenever the pixel offset has any other integral value (this
+*     ensures that the interpolated values pass through the original
+*     data). An interpolation kernel may or may not have regions with
+*     negative values. You should consult a good book on image
+*     processing for more details.
+c     - If an error occurs within this function, it should use
+c     astSetStatus to set the AST error status to an error value.
+c     This will cause an immediate return from astResample<X>. The error
+c     value AST__UK1ER is available for this purpose, but other values may
+c     also be used (e.g. if you wish to distinguish different types of
+c     error).
+f     - If an error occurs within this routine, it should set the
+f     STATUS argument to an error value before returning. This will
+f     cause an immediate return from AST_RESAMPLE<X>. The error value
+f     AST__UK1ER is available for this purpose, but other values may also
+f     be used (e.g. if you wish to distinguish different types of error).
+f     The AST__UK1ER error value is defined in the AST_ERR include file.
+*--
+*/
+/* Note the above is just a description to act as a template. The
+   function does not actually exist. */
+
+static double UphillSimplex( const MapData *mapdata, double acc, int maxcall,
+                             const double dx[], double xmax[], double *err,
+                             int *ncall, int *status ) {
+/*
+*  Name:
+*     UphillSimplex
+
+*  Purpose:
+*     Find a function maximum using a modification of the simplex method.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     double UphillSimplex( const MapData *mapdata, double acc, int maxcall,
+*                           const double dx[], double xmax[], double *err,
+*                           int *ncall, int *status );
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function applies a modification of the simplex method to
+*     find a local maximum in the value returned by a Mapping
+*     function. The modification used allows the method to cope with
+*     coordinate constraints and (equivalently) regions where the
+*     function returns "bad" values. The method is robust and not
+*     susceptible to overflow, so is suitable for applying to Mapping
+*     functions of unknown form.
+
+*  Parameters:
+*     mapdata
+*        Pointer to a MapData structure which describes the Mapping
+*        function, its coordinate constraints, etc.
+*     acc
+*        The accuracy required in the value of the maximum.
+*     maxcall
+*        The maximum number of Mapping function evaluations to use.
+*     dx
+*        Pointer to an array of double containing an offset along each
+*        input coordinate for the Mapping function supplied. These
+*        offsets will be used to construct the initial simplex
+*        (i.e. they are the initial "step lengths" for each
+*        coordinate) and may be positive or negative.
+*     xmax
+*        Pointer to an array of double which contains the coordinates
+*        of an initial estimate of the location of the maximum. On
+*        exit, this will be updated to contain the best estimate of
+*        the location of the maximum as generated by this function.
+*     err
+*        Pointer to a double in which to return an estimate of the
+*        error in the value of the maximum found. For normal
+*        convergence, this should be no larger than "acc". However, if
+*        the maximum number of Mapping function evaluations is
+*        reached, the returned value may be larger than this, although
+*        it should still be valid. In such cases, re-starting the
+*        algorithm at the new location returned in "xmax" may be
+*        advisable.
+*     ncall
+*        Pointer to an int in which the number of Mapping function
+*        evaluations will be returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     An estimate of the Mapping function value at the local maximum.
+
+*  Notes:
+*     - The function may return before the requested accuracy has been
+*     met and before all Mapping function evaluations have been
+*     made. This signifies that an excessive number of function values
+*     have been needed outside the coordinate constraints. This is
+*     only likely if the function is unable to make progress near such
+*     a constraint, in which case the algorithm should probably be
+*     re-started.
+*     - A value of AST__BAD will be returned if no maximum could be
+*     found.  This means that all the Mapping function evaluations
+*     performed returned a value of AST__BAD.
+*     - A value of AST__BAD will also be returned and no useful
+*     information about a solution will be produced if this routine is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Constants: */
+   const double factor = 3.0;    /* Simplex contraction/expansion factor */
+
+/* Local Variables: */
+   double *f;                    /* Pointer to array of function values */
+   double *x;                    /* Pointer to array of vertex coordinates */
+   double *xnew;                 /* Pointer to workspace array */
+   double fnew;                  /* New function value */
+   double fsave;                 /* Saved function value */
+   double offset;                /* Coordinate difference between vertices */
+   double range;                 /* Range of simplex values */
+   double result;                /* Value to return */
+   double tmp;                   /* Temporary store for coordinate */
+   int coord;                    /* Loop counter for coordinates */
+   int hi;                       /* Index of best vertex */
+   int lo;                       /* Index of worst vertex */
+   int ncalla;                   /* Number of function calls attempted */
+   int ncoord;                   /* Number of function dimensions */
+   int nextlo;                   /* Index of second worst vertex */
+   int nvertex;                  /* Number of simplex vertices */
+   int vertex;                   /* Loop counter for vertices */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialisation. */
+   *err = DBL_MAX;
+   *ncall = 0;
+
+/* Obtain the number of input coordinates for the Mapping function and
+   calculate the number of simplex vertices. */
+   ncoord = mapdata->nin;
+   nvertex = ncoord + 1;
+
+/* Allocate workspace. */
+   f = astMalloc( sizeof( double ) * (size_t) nvertex );
+   x = astMalloc( sizeof( double ) * (size_t) ( ncoord * nvertex ) );
+   xnew = astMalloc( sizeof( double ) * (size_t) ncoord );
+   if ( astOK ) {
+
+/* Loop to set up an initial simplex. */
+      for ( vertex = 0; vertex < nvertex; vertex++ ) {
+         for ( coord = 0; coord < ncoord; coord++ ) {
+            tmp = xmax[ coord ];
+
+/* Displace each point (except the first) the required amount along
+   one of the axes to generate the coordinates of the simplex
+   vertices. */
+            if ( coord == ( vertex - 1 ) ) tmp += dx[ coord ];
+            x[ vertex * ncoord + coord ] = tmp;
+         }
+
+/* Evaluate the Mapping function at each vertex. */
+         f[ vertex ] = MapFunction( mapdata, &x[ vertex * ncoord ], ncall, status );
+         if ( f[ vertex ] == AST__BAD ) f[ vertex ] = -DBL_MAX;
+      }
+
+/* Initialise the number of times we attempt to call the Mapping
+   function (not necessarily the same as the number of times it was
+   actually called, which is stored in *ncall). */
+      ncalla = nvertex;
+
+/* Loop until convergence is reached or an error occurs. */
+      while( astOK ) {
+
+/* Initialise the index of the lowest vertex of the simplex, the next
+   lowest vertex and the highest vertex. */
+         lo = ( f[ 0 ] < f[ 1 ] ) ? 0 : 1;
+         nextlo = 1 - lo;
+         hi = 0;
+
+/* Loop to inspect each vertex and update these values. Ensure that in
+   the case of equal vertices, the first one is taken to be the
+   highest. This makes the maximisation stable (so that if no better
+   maximum can be found, the original position is returned rather than
+   a nearby position that yields the same function value). */
+         for ( vertex = 0; vertex < nvertex; vertex++ ) {
+            if ( f[ vertex ] <= f[ lo ] ) {
+               nextlo = lo;
+               lo = vertex;
+            } else if ( ( f[ vertex ] <= f[ nextlo ] ) && ( vertex != lo ) ) {
+               nextlo = vertex;
+            }
+            if ( f[ vertex ] > f[ hi ] ) hi = vertex;
+         }
+
+/* Estimate the error on the result as the difference between the
+   highest and lowest simplex vertices. */
+         if ( ( f[ hi ] == -DBL_MAX ) || ( f[ lo ] == -DBL_MAX ) ) {
+            range = DBL_MAX;
+         } else {
+            range = f[ hi ] - f[ lo ];
+         }
+
+/* Test for convergence. Ideally, the accuracy criterion should have
+   been met. However, also quit if the maximum number of Mapping
+   function evaluations has been reached, or the number of points at
+   which function values have been requested reaches three times this
+   limit (this latter number will typically be larger because points
+   lying outside the coordinate constraints do not result in the
+   Mapping function being evaluated). */
+         if ( range <= fabs( acc ) ||
+              ( *ncall >= maxcall ) || ( ncalla >= ( 3 * maxcall ) ) ) {
+
+/* If quitting, return the coordinates and function value at the best
+   simplex vertex, and the error estimate. */
+            for ( coord = 0; coord < ncoord; coord++ ) {
+               xmax[ coord ] = x[ hi * ncoord + coord ];
+            }
+            result = ( f[ hi ] == -DBL_MAX ) ? AST__BAD : f[ hi ];
+            *err = range;
+            break;
+         }
+
+/* If performing another iteration, first try reflecting the worst
+   vertex through the opposite face of the simplex. Check for
+   errors. */
+         fnew = NewVertex( mapdata, lo, -1.0, x, f, ncall, xnew, status );
+         ncalla++;
+         if ( astOK ) {
+
+/* If this results in a point lying in a forbiddden region (either
+   outside the coordinate constraints or where the Mapping function
+   yields bad coordinate values), then we must make a departure from
+   the standard simplex algorithm. This is because the inability to
+   make forward progress in this case can cause the simplex to
+   repeatedly contract about each face (except one) in turn. This
+   mechanism normally results in lateral contraction as the simplex
+   attempts to squeeze through a narrow gap which is impeding
+   progress. However, in this case there is no gap to get through, so
+   the lateral contraction can eventually make the simplex become
+   degenerate (due to rounding). This prevents it from expanding
+   laterally again and exploring the region adjacent to the constraint
+   boundary once it has become small enough. */
+            if ( fnew == AST__BAD ) {
+
+/* To overcome this, we instead contract the worst simplex vertex
+   towards the best vertex (this has the cumulative effect of
+   contracting the simplex without changing its shape). First find the
+   offset in each coordinate between these two vertices. */
+               for ( coord = 0; coord < ncoord; coord++ ) {
+                  offset = x[ lo * ncoord + coord ] - x[ hi * ncoord + coord ];
+
+/* Scale the offset to obtain the new coordinate. */
+                  x[ lo * ncoord + coord ] = x[ hi * ncoord + coord ] +
+                                             offset / factor;
+
+/* If the distance between the two vertices has not decreased, we are
+   in a region where rounding errors prevent them approaching each
+   other any more closely, so simply set them equal. */
+                  if ( fabs( x[ lo * ncoord + coord ] -
+                             x[ hi * ncoord + coord ] ) >= fabs( offset ) ) {
+                     x[ lo * ncoord + coord ] = x[ hi * ncoord + coord ];
+                  }
+               }
+
+/* Evaluate the Mapping function at the new vertex. */
+               f[ lo ] = MapFunction( mapdata, &x[ lo * ncoord ], ncall, status );
+               if ( f[ lo ] == AST__BAD ) f[ lo ] = -DBL_MAX;
+               ncalla++;
+
+/* We now return to the standard simplex algorithm. If the new vertex
+   is a new maximum, then see if more of the same is even better by
+   trying to expand the best vertex away from the opposite face. */
+            } else if ( fnew >= f[ hi ] ) {
+               fnew = NewVertex( mapdata, lo, factor, x, f, ncall, xnew, status );
+               ncalla++;
+
+/* Otherwise, if the new vertex was no improvement on the second
+   worst, then try contracting the worst vertex towards the opposite
+   face. */
+            } else if ( fnew <= f[ nextlo ] ) {
+               fsave = f[ lo ];
+               fnew = NewVertex( mapdata, lo, 1.0 / factor, x, f, ncall, xnew, status );
+               ncalla++;
+
+/* If this didn't result in any improvement, then contract the entire
+   simplex towards the best vertex. Use the same approach as earlier
+   to protect against rounding so that all the simplex vertices will
+   eventually coalesce if this process is repeated enough times. */
+               if ( astOK && ( fnew <= fsave ) ) {
+                  for ( vertex = 0; vertex < nvertex; vertex++ ) {
+                     if ( vertex != hi ) {
+                        for ( coord = 0; coord < ncoord; coord++ ) {
+                           offset = x[ vertex * ncoord + coord ] -
+                                    x[ hi * ncoord + coord ];
+                           x[ vertex * ncoord + coord ] =
+                               x[ hi * ncoord + coord ] + offset / factor;
+                           if ( fabs( x[ vertex * ncoord + coord ] -
+                                      x[ hi * ncoord + coord ] ) >=
+                                fabs( offset ) ) {
+                              x[ vertex * ncoord + coord ] =
+                                 x[ hi * ncoord + coord ];
+                           }
+                        }
+
+/* Evaluate the Mapping function at each new vertex. */
+                        f[ vertex ] = MapFunction( mapdata,
+                                                   &x[ vertex * ncoord ],
+                                                   ncall, status );
+                        if ( f[ vertex ] == AST__BAD ) f[ vertex ] = -DBL_MAX;
+                        ncalla++;
+                     }
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Free workspace. */
+   f = astFree( f );
+   x = astFree( x );
+   xnew = astFree( xnew );
+
+/* If an error occurred, clear the returned result. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static void ValidateMapping( AstMapping *this, int forward,
+                             int npoint, int ncoord_in, int ncoord_out,
+                             const char *method, int *status ) {
+/*
+*  Name:
+*     ValidateMapping
+
+*  Purpose:
+*     Validate a Mapping for use to transform coordinates.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void ValidateMapping( AstMapping *this, int forward,
+*                           int npoint, int ncoord_in, int ncoord_out,
+*                           const char *method, int *status )
+
+*  Class Membership:
+*     Mapping member function.
+
+*  Description:
+*     This function checks that a Mapping is suitable for transforming
+*     a set of points. It also checks that the number of points and
+*     the number of coordinate values per point is valid. If an error
+*     is detected, the global error status is set and an error report
+*     made. Otherwise, the function returns without further action.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     forward
+*        A non-zero value indicates that the forward coordinate
+*        transformation is to be checked, while a zero value requests
+*        the inverse transformation.
+*     npoint
+*        The number of points being transformed.
+*     ncoord_in
+*        The number of coordinates associated with each input point.
+*     ncoord_out
+*        The number of coordinates associated with each output point.
+*     method
+*        Pointer to a null terminated character string containing the
+*        name of the method which invoked this function to validate a
+*        Mapping. This is used solely for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int nin;                    /* Mapping Nin attribute value */
+   int nout;                   /* Mapping Nout attribute value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Report an error if the requested transformation is not defined. */
+   if ( !( forward ? astGetTranForward( this ) : astGetTranInverse( this ) )
+        && astOK ) {
+      astError( AST__TRNND, "%s(%s): %s coordinate transformation "
+                "is not defined by the %s supplied.", status, method,
+                astGetClass( this ),
+                ( forward ? "A forward" : "An inverse" ),
+                astGetClass( this ) );
+   }
+
+/* Obtain the effective values of the Nin and Nout attributes for the
+   Mapping. */
+   nin = forward ? astGetNin( this ) : astGetNout( this );
+   nout = forward ? astGetNout( this ) : astGetNin( this );
+
+/* If OK, check that the number of input coordinates matches the
+   number required by the Mapping. Report an error if these numbers do
+   not match. */
+   if ( astOK && ( ncoord_in != nin ) ) {
+      astError( AST__NCPIN, "%s(%s): Bad number of input coordinate values "
+                "(%d).", status, method, astGetClass( this ), ncoord_in );
+      astError( AST__NCPIN, "The %s given requires %d coordinate value%s for "
+                "each input point.", status, astGetClass( this ), nin,
+                ( nin == 1 ) ? "" : "s" );
+   }
+
+/* If OK, also check that the number of output coordinates matches the
+   number required by the Mapping. Report an error if these numbers do
+   not match. */
+   if ( astOK && ( ncoord_out != nout ) ) {
+      astError( AST__NCPIN, "%s(%s): Bad number of output coordinate values "
+                "(%d).", status, method, astGetClass( this ), ncoord_out );
+      astError( AST__NCPIN, "The %s given generates %s%d coordinate value%s "
+                "for each output point.", status, astGetClass( this ),
+                ( nout < ncoord_out ) ? "only " : "", nout,
+                ( nout == 1 ) ? "" : "s" );
+   }
+
+/* Check that the number of points being transformed is not negative
+   and report an error if necessary. */
+   if ( astOK && ( npoint < 0 ) ) {
+      astError( AST__NPTIN, "%s(%s): Number of points to be transformed (%d) "
+                "is invalid.", status, method, astGetClass( this ), npoint );
+   }
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. */
+/*
+*att++
+*  Name:
+*     Invert
+
+*  Purpose:
+*     Mapping inversion flag.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls which one of a Mapping's two possible
+*     coordinate transformations is considered the "forward"
+*     transformation (the other being the "inverse"
+*     transformation). If the attribute value is zero (the default),
+*     the Mapping's behaviour will be the same as when it was first
+*     created. However, if it is non-zero, its two transformations
+*     will be inter-changed, so that the Mapping displays the inverse
+*     of its original behaviour.
+*
+*     Inverting the boolean sense of the Invert attribute will cause
+*     the values of a Mapping's Nin and Nout attributes to be
+*     interchanged. The values of its TranForward and TranInverse
+*     attributes will also be interchanged. This operation may be
+c     performed with the astInvert function.
+f     performed with the AST_INVERT routine.
+
+*  Applicability:
+*     Mapping
+*        All Mappings have this attribute.
+*     UnitMap
+*        The value of the Invert attribute has no effect on the
+*        behaviour of a UnitMap.
+*     FrameSet
+*        Inverting the boolean sense of the Invert attribute for a
+*        FrameSet will cause its base and current Frames (and its Base
+*        and Current attributes) to be interchanged. This, in turn,
+*        may affect other properties and attributes of the FrameSet
+*        (such as Nin, Nout, Naxes, TranForward, TranInverse,
+*        etc.). The Invert attribute of a FrameSet is not itself
+*        affected by selecting a new base or current Frame.
+*att--
+*/
+/* This ia a boolean value (0 or 1) with a value of CHAR_MAX when
+   undefined but yielding a default of zero. */
+astMAKE_CLEAR(Mapping,Invert,invert,CHAR_MAX)
+astMAKE_GET(Mapping,Invert,int,0,( ( this->invert == CHAR_MAX ) ?
+                                   0 : this->invert ))
+astMAKE_SET(Mapping,Invert,int,invert,( (this->flags&=~AST__ISSIMPLE_FLAG),(value!=0) ))
+astMAKE_TEST(Mapping,Invert,( this->invert != CHAR_MAX ))
+
+/*
+*att++
+*  Name:
+*     IsLinear
+
+*  Purpose:
+*     Is the Mapping linear?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean), read-only.
+
+*  Description:
+*     This attribute indicates whether a Mapping is an instance of a
+*     class that always represents a linear transformation. Note, some
+*     Mapping classes can represent linear or non-linear transformations
+*     (the MathMap class for instance). Such classes have a zero value for
+*     the IsLinear attribute. Specific instances of such classes can be
+*     tested for linearity using the
+*     astLinearApprox function.
+*     AST_LINEARAPPROX routine.
+
+*  Applicability:
+*     Mapping
+*        All Mappings have this attribute.
+*     CmpMap
+*        The IsLinear value for a CmpMap is determined by the classes
+*        of the encapsulated Mappings. For instance, a CmpMap that combines
+*        a ZoomMap and a ShiftMap will have a non-zero value for its IsLinear
+*        attribute, but a CmpMap that contains a MathMap will have a
+*        value of zero for its IsLinear attribute.
+*     Frame
+*        The IsLinear value for a Frame is 1 (since a Frame is equivalent
+*        to a UnitMap).
+*     FrameSet
+*        The IsLinear value for a FrameSet is obtained from the Mapping
+*        from the base Frame to the current Frame.
+
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     IsSimple
+
+*  Purpose:
+*     Has the Mapping been simplified?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean), read-only.
+
+*  Description:
+*     This attribute indicates whether a Mapping has been simplified
+*     by the
+c     astSimplify
+f     AST_SIMPLIFY
+*     method. If the IsSimple value is non-zero, then the Mapping has
+*     been simplified and so there is nothing to be gained by simplifying
+*     it again. Indeed, the
+c     astSimplify
+f     AST_SIMPLIFY
+*     method will immediately return the Mapping unchanged if the IsSimple
+*     attribute indicates that the Mapping has already been simplified.
+
+*  Applicability:
+*     Mapping
+*        All Mappings have this attribute.
+*     Frame
+*        All classes of Frame return zero for the IsSimple attribute.
+*        This is because changes can be made to a Frame which affect the
+*        Mapping represented by the Frame, and so there can be no
+*        guarantee that the Mapping may not need re-simplifying. Most
+*        non-Frame Mappings, on the other hand, are immutable and so when
+*        they are simplified it is certain that they weill remain in a
+*        simple state.
+
+*att--
+*/
+astMAKE_GET(Mapping,IsSimple,int,0,((this->flags)&AST__ISSIMPLE_FLAG))
+
+/*
+*att++
+*  Name:
+*     Nin
+
+*  Purpose:
+*     Number of input coordinates for a Mapping.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute gives the number of coordinate values required to
+*     specify an input point for a Mapping (i.e. the number of
+*     dimensions of the space in which the Mapping's input points
+*     reside).
+
+*  Applicability:
+*     Mapping
+*        All Mappings have this attribute.
+*     CmpMap
+*        If a CmpMap's component Mappings are joined in series, then
+*        its Nin attribute is equal to the Nin attribute of the first
+*        component (or to the Nout attribute of the second component
+*        if the the CmpMap's Invert attribute is non-zero).
+*
+*        If a CmpMap's component Mappings are joined in parallel, then
+*        its Nin attribute is given by the sum of the Nin attributes
+*        of each component (or to the sum of their Nout attributes if
+*        the CmpMap's Invert attribute is non-zero).
+*     Frame
+*        The Nin attribute for a Frame is always equal to the number
+*        of Frame axes (Naxes attribute).
+*     FrameSet
+*        The Nin attribute of a FrameSet is equal to the number of
+*        axes (Naxes attribute) of its base Frame (as specified by the
+*        FrameSet's Base attribute). The Nin attribute value may
+*        therefore change if a new base Frame is selected.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     Nout
+
+*  Purpose:
+*     Number of output coordinates for a Mapping.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute gives the number of coordinate values generated
+*     by a Mapping to specify each output point (i.e. the number of
+*     dimensions of the space in which the Mapping's output points
+*     reside).
+
+*  Applicability:
+*     Mapping
+*        All Mappings have this attribute.
+*     CmpMap
+*        If a CmpMap's component Mappings are joined in series, then
+*        its Nout attribute is equal to the Nout attribute of the
+*        second component (or to the Nin attribute of the first
+*        component if the the CmpMap's Invert attribute is non-zero).
+*
+*        If a CmpMap's component Mappings are joined in parallel, then
+*        its Nout attribute is given by the sum of the Nout attributes
+*        of each component (or to the sum of their Nin attributes if
+*        the CmpMap's Invert attribute is non-zero).
+*     Frame
+*        The Nout attribute for a Frame is always equal to the number
+*        of Frame axes (Naxes attribute).
+*     FrameSet
+*        The Nout attribute of a FrameSet is equal to the number of
+*        FrameSet axes (Naxes attribute) which, in turn, is equal to
+*        the Naxes attribute of the FrameSet's current Frame (as
+*        specified by the Current attribute). The Nout attribute value
+*        may therefore change if a new current Frame is selected.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     Report
+
+*  Purpose:
+*     Report transformed coordinates?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls whether coordinate values are reported
+*     whenever a Mapping is used to transform a set of points. If its
+*     value is zero (the default), no report is made. However, if it
+*     is non-zero, the coordinates of each point are reported (both
+*     before and after transformation) by writing them to standard
+*     output.
+*
+*     This attribute is provided as an aid to debugging, and to avoid
+*     having to report values explicitly in simple programs.
+
+*  Applicability:
+*     Mapping
+*        All Mappings have this attribute.
+*     CmpMap
+*        When applied to a compound Mapping (CmpMap), only the Report
+*        attribute of the CmpMap, and not those of its component
+*        Mappings, is used.  Coordinate information is never reported
+*        for the component Mappings individually, only for the
+*        complete CmpMap.
+*     Frame
+*        When applied to any Frame, the formatting capabilities of the
+c        Frame (as provided by the astFormat function) will be used to
+f        Frame (as provided by the AST_FORMAT function) will be used to
+*        format the reported coordinates.
+*     FrameSet
+*        When applied to any FrameSet, the formatting capabilities of
+*        the base and current Frames will be used (as above) to
+*        individually format the input and output coordinates, as
+*        appropriate. The Report attribute of a FrameSet is not itself
+*        affected by selecting a new base or current Frame, but the
+*        resulting formatting capabilities may be.
+
+*  Notes:
+*     - Unlike most other attributes, the value of the Report
+*     attribute is not transferred when a Mapping is copied. Instead,
+*     its value is undefined (and therefore defaults to zero) in any
+*     copy. Similarly, it becomes undefined in any external
+c     representation of a Mapping produced by the astWrite function.
+f     representation of a Mapping produced by the AST_WRITE routine.
+*att--
+*/
+/* This ia a boolean value (0 or 1) with a value of CHAR_MAX when
+   undefined but yielding a default of zero. */
+astMAKE_CLEAR(Mapping,Report,report,CHAR_MAX)
+astMAKE_GET(Mapping,Report,int,0,( ( this->report == CHAR_MAX ) ?
+                                   0 : this->report ))
+astMAKE_SET(Mapping,Report,int,report,( value != 0 ))
+astMAKE_TEST(Mapping,Report,( this->report != CHAR_MAX ))
+
+/*
+*att++
+*  Name:
+*     TranForward
+
+*  Purpose:
+*     Forward transformation defined?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean), read-only.
+
+*  Description:
+*     This attribute indicates whether a Mapping is able to transform
+*     coordinates in the "forward" direction (i.e. converting input
+*     coordinates into output coordinates). If this attribute is
+*     non-zero, the forward transformation is available. Otherwise, it
+*     is not.
+
+*  Applicability:
+*     Mapping
+*        All Mappings have this attribute.
+*     CmpMap
+*        The TranForward attribute value for a CmpMap is given by the
+*        boolean AND of the value for each component Mapping.
+*     FrameSet
+*        The TranForward attribute of a FrameSet applies to the
+*        transformation which converts between the FrameSet's base
+*        Frame and its current Frame (as specified by the Base and
+*        Current attributes). This value is given by the boolean AND
+*        of the TranForward values which apply to each of the
+*        individual sub-Mappings required to perform this conversion.
+*        The TranForward attribute value for a FrameSet may therefore
+*        change if a new Base or Current Frame is selected.
+
+*  Notes:
+*     - An error will result if a Mapping with a TranForward value of
+*     zero is used to transform coordinates in the forward direction.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     TranInverse
+
+*  Purpose:
+*     Inverse transformation defined?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean), readonly.
+
+*  Description:
+*     This attribute indicates whether a Mapping is able to transform
+*     coordinates in the "inverse" direction (i.e. converting output
+*     coordinates back into input coordinates). If this attribute is
+*     non-zero, the inverse transformation is available. Otherwise, it
+*     is not.
+
+*  Applicability:
+*     Mapping
+*        All Mappings have this attribute.
+*     CmpMap
+*        The TranInverse attribute value for a CmpMap is given by the
+*        boolean AND of the value for each component Mapping.
+*     FrameSet
+*        The TranInverse attribute of a FrameSet applies to the
+*        transformation which converts between the FrameSet's current
+*        Frame and its base Frame (as specified by the Current and
+*        Base attributes). This value is given by the boolean AND of
+*        the TranInverse values which apply to each of the individual
+*        sub-Mappings required to perform this conversion.
+*        The TranInverse attribute value for a FrameSet may therefore
+*        change if a new Base or Current Frame is selected.
+
+*  Notes:
+*     - An error will result if a Mapping with a TranInverse value of
+*     zero is used to transform coordinates in the inverse direction.
+*att--
+*/
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Mapping objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Mapping objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the Mapping to be copied.
+*     objout
+*        Pointer to the Mapping being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This constructor exists simply to ensure that the "Report"
+*     attribute is cleared in any copy made of a Mapping.
+*/
+
+/* Local Variables: */
+   AstMapping *out;              /* Pointer to output Mapping */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the output Mapping. */
+   out = (AstMapping *) objout;
+
+/* Clear the output Report attribute. */
+   out->report = CHAR_MAX;
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Mapping objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Mapping objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the Mapping to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This destructor does nothing and exists only to maintain a
+*     one-to-one correspondence between destructors and copy
+*     constructors.
+*/
+
+/* Return without action. */
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Mapping objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Mapping class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstMapping *this;             /* Pointer to the Mapping structure */
+   int invert;                   /* Mapping inverted? */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Mapping structure. */
+   this = (AstMapping *) this_object;
+
+/* Write out values representing the instance variables for the
+   Mapping class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Determine if the Mapping is inverted. The output values
+   (e.g. number of input and output coordinates) will refer to the
+   Mapping ***before*** this inversion flag is applied, but we need it
+   when using (e.g.) the astGetNin/astGetNout methods to determine
+   which one will return the required value. */
+   invert = astGetInvert( this );
+
+/* (NB. there is a subtle point here that dictates the extent to which
+   this inversion flag can be used... All use of methods (such as
+   astGetInvert, which might be over-ridden by derived classes) must
+   be restricted to determining the values of "unset" output
+   quantities only (below). This is because when re-loading the
+   Mapping, the derived classes will not have been loaded at the point
+   when these values are re-read - hence any value whose
+   interpretation depends on these methods cannot be reliably
+   recovered.) */
+
+/* Nin. */
+/* ---- */
+/* Use the instance variable directly to avoid the effect of the
+   Invert attribute on the private member function. Treat zero as the
+   default. */
+   set = ( this->nin != 0 );
+   ival = set ? this->nin : ( !invert ? astGetNin( this ) :
+                                        astGetNout( this ) );
+   astWriteInt( channel, "Nin", set, 0, ival,
+                "Number of input coordinates" );
+
+/* Nout. */
+/* ----- */
+/* Use the instance variable directly. Treat zero as the default. */
+   set = ( this->nout != this->nin );
+   ival = set ? this->nout : ( !invert ? astGetNout( this ) :
+                                         astGetNin( this ) );
+   astWriteInt( channel, "Nout", set, 0, ival,
+                "Number of output coordinates" );
+
+/* IsSimple. */
+/* --------- */
+   ival = astGetIsSimple( this );
+   astWriteInt( channel, "IsSimp", ival, 0, ival,
+                ival ? "Mapping has been simplified" :
+                       "Mapping has not been simplified" );
+
+/* Invert. */
+/* ------- */
+   set = TestInvert( this, status );
+   ival = set ? GetInvert( this, status ) : astGetInvert( this );
+   astWriteInt( channel, "Invert", set, 0, ival,
+                ival ? "Mapping inverted" :
+                       "Mapping not inverted" );
+
+/* TranForward. */
+/* ------------ */
+/* Use the instance variable directly. Treat 1 as the default. */
+   set = ( this->tran_forward == 0 );
+   ival = set ? this->tran_forward : ( !invert ? astGetTranForward( this ) :
+                                                 astGetTranInverse( this ) );
+   astWriteInt( channel, "Fwd", set, 0, ival,
+                ival ? "Forward transformation defined" :
+                       "Forward transformation not defined" );
+
+/* TranInverse. */
+/* ------------ */
+/* Use the instance variable directly. Treat 1 as the default. */
+   set = ( this->tran_inverse == 0 );
+   ival = set ? this->tran_inverse : ( !invert ? astGetTranInverse( this ) :
+                                                 astGetTranForward( this ) );
+   astWriteInt( channel, "Inv", set, 0, ival,
+                ival ? "Inverse transformation defined" :
+                       "Inverse transformation not defined" );
+
+/* Report. */
+/* ------- */
+   set = TestReport( this, status );
+   ival = set ? GetReport( this, status ) : astGetReport( this );
+   astWriteInt( channel, "Report", set, 0, ival,
+                ival ? "Report coordinate transformations" :
+                       "Don't report coordinate transformations" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAMapping and astCheckMapping functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Mapping,Object)
+astMAKE_CHECK(Mapping)
+
+AstMapping *astInitMapping_( void *mem, size_t size, int init,
+                             AstMappingVtab *vtab, const char *name,
+                             int nin, int nout,
+                             int tran_forward, int tran_inverse, int *status ) {
+/*
+*+
+*  Name:
+*     astInitMapping
+
+*  Purpose:
+*     Initialise a Mapping.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     AstMapping *astInitMapping( void *mem, size_t size, int init,
+*                                 AstMappingVtab *vtab, const char *name,
+*                                 int nin, int nout,
+*                                 int tran_forward, int tran_inverse )
+
+*  Class Membership:
+*     Mapping initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Mapping object. It allocates memory (if necessary) to accommodate
+*     the Mapping plus any additional data associated with the derived class.
+*     It then initialises a Mapping structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a Mapping at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Mapping is to be initialised.
+*        This must be of sufficient size to accommodate the Mapping data
+*        (sizeof(Mapping)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Mapping (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Mapping
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Mapping's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Mapping.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     nin
+*        The number of coordinate values per input point.
+*     nout
+*        The number of coordinate vales per output point.
+*     tran_forward
+*        A non-zero value indicates that the Mapping will be able to
+*        transform coordinates in the forward direction. A zero value
+*        indicates that it will not.
+*     tran_inverse
+*        A non-zero value indicates that the Mapping will be able to
+*        transform coordinates in the inverse direction. A zero value
+*        indicates that it will not.
+
+*  Returned Value:
+*     A pointer to the new Mapping.
+
+*  Notes:
+*     -  The Mappings produced by this function implement all the basic methods
+*     defined by the Mapping class. However, their astTransform method does not
+*     actually perform any coordinate transformation (although it performs all
+*     necessary argument validation and creates an output PointSet if
+*     necessary, leaving its coordinate values undefined).
+*     -  This means that Mappings produced by this function are of limited use
+*     on their own, but may easily be extended by a derived class simply by
+*     over-riding the astTransform method to add the necessary coordinate
+*     arithmetic.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstMapping *new;              /* Pointer to new Mapping */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitMappingVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the initialisation values for validity, reporting an error if
+   necessary. */
+   if ( nin < 0 ) {
+      astError( AST__BADNI, "astInitMapping(%s): Bad number of input "
+                "coordinates (%d).", status, name, nin );
+      astError( AST__BADNI, "This number should be zero or more." , status);
+   } else if ( nout < 0 ) {
+      astError( AST__BADNO, "astInitMapping(%s): Bad number of output "
+                "coordinates (%d).", status, name, nout );
+      astError( AST__BADNI, "This number should be zero or more." , status);
+   }
+
+/* Initialise an Object structure (the parent class) as the first component
+   within the Mapping structure, allocating memory if necessary. */
+   new = (AstMapping *) astInitObject( mem, size, 0,
+                                       (AstObjectVtab *) vtab, name );
+
+   if ( astOK ) {
+
+/* Initialise the Mapping data. */
+/* ---------------------------- */
+/* Store the numbers of input and output coordinates. */
+      new->nin = nin;
+      new->nout = nout;
+
+/* Store the flags indicating which coordinate transformations are
+   defined (constrain these values to 0 or 1). */
+      new->tran_forward = ( tran_forward != 0 );
+      new->tran_inverse = ( tran_inverse != 0 );
+
+/* Initialise other attributes to their undefined values. */
+      new->invert = CHAR_MAX;
+      new->report = CHAR_MAX;
+      new->flags = 0;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstMapping *astLoadMapping_( void *mem, size_t size,
+                             AstMappingVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadMapping
+
+*  Purpose:
+*     Load a Mapping.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     AstMapping *astLoadMapping( void *mem, size_t size,
+*                                 AstMappingVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     Mapping loader.
+
+*  Description:
+*     This function is provided to load a new Mapping using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Mapping structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Mapping at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Mapping is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Mapping data (sizeof(Mapping)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Mapping (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Mapping structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstMapping) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Mapping. If this is NULL, a pointer
+*        to the (static) virtual function table for the Mapping class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Mapping" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Mapping.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMapping *new;              /* Pointer to the new Mapping */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Mapping. In this case the
+   Mapping belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstMapping );
+      vtab = &class_vtab;
+      name = "Mapping";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitMappingVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Mapping. */
+   new = astLoadObject( mem, size, (AstObjectVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Mapping" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Initialise bitwise flags to zero. */
+      new->flags = 0;
+
+/* Nin. */
+/* ---- */
+      new->nin = astReadInt( channel, "nin", 0 );
+      if ( new->nin < 0 ) new->nin = 0;
+
+/* Nout. */
+/* ----- */
+      new->nout = astReadInt( channel, "nout", new->nin );
+      if ( new->nout < 0 ) new->nout = 0;
+
+/* Invert. */
+/* ------- */
+      new->invert = astReadInt( channel, "invert", CHAR_MAX );
+      if ( TestInvert( new, status ) ) SetInvert( new, new->invert, status );
+
+/* IsSimple. */
+/* --------- */
+      if( astReadInt( channel, "issimp", 0 ) ) new->flags |= AST__ISSIMPLE_FLAG;
+
+/* TranForward. */
+/* ------------ */
+      new->tran_forward = ( astReadInt( channel, "fwd", 1 ) != 0 );
+
+/* TranInverse. */
+/* ------------ */
+      new->tran_inverse = ( astReadInt( channel, "inv", 1 ) != 0 );
+
+/* Report. */
+/* ------- */
+      new->report = astReadInt( channel, "report", CHAR_MAX );
+      if ( TestReport( new, status ) ) SetReport( new, new->report, status );
+
+/* If an error occurred, clean up by deleting the new Mapping. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Mapping pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions
+   defined by this class. Each simply checks the global error status
+   and then locates and executes the appropriate member function,
+   using the function pointer stored in the object's virtual function
+   table (this pointer is located using the astMEMBER macro defined in
+   "object.h").
+
+   Note that the member function may not be the one defined here, as
+   it may have been over-ridden by a derived class. However, it should
+   still have the same interface. */
+
+void astDecompose_( AstMapping *this, AstMapping **map1, AstMapping **map2,
+                    int *series, int *invert1, int *invert2, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Mapping,Decompose))( this, map1, map2, series, invert1, invert2, status );
+}
+int astGetNin_( AstMapping *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Mapping,GetNin))( this, status );
+}
+int astGetNout_( AstMapping *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Mapping,GetNout))( this, status );
+}
+int astGetIsLinear_( AstMapping *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Mapping,GetIsLinear))( this, status );
+}
+int astGetTranForward_( AstMapping *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Mapping,GetTranForward))( this, status );
+}
+int astGetTranInverse_( AstMapping *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Mapping,GetTranInverse))( this, status );
+}
+void astInvert_( AstMapping *this, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Mapping,Invert))( this, status );
+}
+void astMapBox_( AstMapping *this,
+                 const double lbnd_in[], const double ubnd_in[], int forward,
+                 int coord_out, double *lbnd_out, double *ubnd_out,
+                 double xl[], double xu[], int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Mapping,MapBox))( this, lbnd_in, ubnd_in, forward,
+                                       coord_out, lbnd_out, ubnd_out, xl, xu, status );
+}
+int astMapList_( AstMapping *this, int series, int invert, int *nmap,
+                  AstMapping ***map_list, int **invert_list, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Mapping,MapList))( this, series, invert,
+                                        nmap, map_list, invert_list, status );
+}
+int *astMapSplit_( AstMapping *this, int nin, const int *in, AstMapping **map,
+                   int *status ){
+   int *result = NULL;
+   AstMapping *tmap;
+
+   if( map ) *map = NULL;
+   if ( !astOK ) return NULL;
+
+   result = (**astMEMBER(this,Mapping,MapSplit))( this, nin, in, &tmap, status );
+   if( tmap ) {
+      *map = astCopy( tmap );
+      tmap = astAnnul( tmap );
+   }
+
+   return result;
+}
+int astMapMerge_( AstMapping *this, int where, int series, int *nmap,
+                  AstMapping ***map_list, int **invert_list, int *status ) {
+
+   if ( !astOK || astDoNotSimplify( this ) ) return -1;
+   return (**astMEMBER(this,Mapping,MapMerge))( this, where, series, nmap,
+                                                map_list, invert_list, status );
+}
+int astDoNotSimplify_( AstMapping *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Mapping,DoNotSimplify))( this, status );
+}
+void astReportPoints_( AstMapping *this, int forward,
+                       AstPointSet *in_points, AstPointSet *out_points, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Mapping,ReportPoints))( this, forward,
+                                             in_points, out_points, status );
+}
+#define MAKE_RESAMPLE_(X,Xtype) \
+int astResample##X##_( AstMapping *this, int ndim_in, const int *lbnd_in, \
+                       const int *ubnd_in, const Xtype *in, \
+                       const Xtype *in_var, int interp, \
+                       void (* finterp)( void ), const double *params, \
+                       int flags, double tol, int maxpix, Xtype badval, \
+                       int ndim_out, \
+                       const int *lbnd_out, const int *ubnd_out, \
+                       const int *lbnd, const int *ubnd, Xtype *out, \
+                       Xtype *out_var, int *status ) { \
+   if ( !astOK ) return 0; \
+   return (**astMEMBER(this,Mapping,Resample##X))( this, ndim_in, lbnd_in, \
+                                                   ubnd_in, in, in_var, \
+                                                   interp, finterp, params, \
+                                                   flags, tol, maxpix, \
+                                                   badval, ndim_out, \
+                                                   lbnd_out, ubnd_out, \
+                                                   lbnd, ubnd, \
+                                                   out, out_var, status ); \
+}
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_RESAMPLE_(LD,long double)
+#endif
+MAKE_RESAMPLE_(D,double)
+MAKE_RESAMPLE_(F,float)
+MAKE_RESAMPLE_(L,long int)
+MAKE_RESAMPLE_(UL,unsigned long int)
+MAKE_RESAMPLE_(I,int)
+MAKE_RESAMPLE_(UI,unsigned int)
+MAKE_RESAMPLE_(K,INT_BIG)
+MAKE_RESAMPLE_(UK,UINT_BIG)
+MAKE_RESAMPLE_(S,short int)
+MAKE_RESAMPLE_(US,unsigned short int)
+MAKE_RESAMPLE_(B,signed char)
+MAKE_RESAMPLE_(UB,unsigned char)
+#undef MAKE_RESAMPLE_
+
+#define MAKE_REBIN_(X,Xtype) \
+void astRebin##X##_( AstMapping *this, double wlim, int ndim_in, const int *lbnd_in, \
+                    const int *ubnd_in, const Xtype *in, \
+                    const Xtype *in_var, int interp, \
+                    const double *params, \
+                    int flags, double tol, int maxpix, Xtype badval, \
+                    int ndim_out, \
+                    const int *lbnd_out, const int *ubnd_out, \
+                    const int *lbnd, const int *ubnd, Xtype *out, \
+                    Xtype *out_var, int *status ) { \
+   if ( !astOK ) return; \
+   (**astMEMBER(this,Mapping,Rebin##X))( this, wlim, ndim_in, lbnd_in, \
+                                         ubnd_in, in, in_var, \
+                                         interp, params, \
+                                         flags, tol, maxpix, \
+                                         badval, ndim_out, \
+                                         lbnd_out, ubnd_out, \
+                                         lbnd, ubnd, \
+                                         out, out_var, status ); \
+}
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_REBIN_(LD,long double)
+#endif
+MAKE_REBIN_(D,double)
+MAKE_REBIN_(F,float)
+MAKE_REBIN_(I,int)
+MAKE_REBIN_(B,signed char)
+MAKE_REBIN_(UB,unsigned char)
+#undef MAKE_REBIN_
+
+
+#define MAKE_REBINSEQ_(X,Xtype) \
+void astRebinSeq##X##_( AstMapping *this, double wlim, int ndim_in, const int *lbnd_in, \
+                        const int *ubnd_in, const Xtype *in, \
+                        const Xtype *in_var, int interp, \
+                        const double *params, \
+                        int flags, double tol, int maxpix, Xtype badval, \
+                        int ndim_out, \
+                        const int *lbnd_out, const int *ubnd_out, \
+                        const int *lbnd, const int *ubnd, Xtype *out, \
+                        Xtype *out_var, double *weights, int64_t *nused, \
+                        int *status ) { \
+   if ( !astOK ) return; \
+   (**astMEMBER(this,Mapping,RebinSeq##X))( this, wlim, ndim_in, lbnd_in, \
+                                         ubnd_in, in, in_var, \
+                                         interp, params, \
+                                         flags, tol, maxpix, \
+                                         badval, ndim_out, \
+                                         lbnd_out, ubnd_out, \
+                                         lbnd, ubnd, out, out_var, \
+                                         weights, nused, status ); \
+}
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_REBINSEQ_(LD,long double)
+#endif
+MAKE_REBINSEQ_(D,double)
+MAKE_REBINSEQ_(F,float)
+MAKE_REBINSEQ_(I,int)
+MAKE_REBINSEQ_(B,signed char)
+MAKE_REBINSEQ_(UB,unsigned char)
+
+#undef MAKE_REBINSEQ_
+
+double astRate_( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+   astDECLARE_GLOBALS
+
+   if ( !astOK ) return AST__BAD;
+
+   astGET_GLOBALS(this);
+
+   if( ax1 < 0 || ax1 >= astGetNout( this ) ) {
+      astError( AST__AXIIN, "astRate(%s): Invalid output index (%d) "
+                "specified - should be in the range 1 to %d.", status,
+                astGetClass( this ), ax1 + 1, astGetNout( this ) );
+
+   } else if( ax2 < 0 || ax2 >= astGetNin( this ) ) {
+      astError( AST__AXIIN, "astRate(%s): Invalid input index (%d) "
+                "specified - should be in the range 1 to %d.", status,
+                astGetClass( this ), ax2 + 1, astGetNin( this ) );
+   }
+
+   if( rate_disabled ) {
+      return ( at[ ax2 ] != AST__BAD ) ? 1.0 : AST__BAD;
+   } else {
+      return (**astMEMBER(this,Mapping,Rate))( this, at, ax1, ax2, status );
+   }
+}
+AstMapping *astRemoveRegions_( AstMapping *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Mapping,RemoveRegions))( this, status );
+}
+AstMapping *astSimplify_( AstMapping *this, int *status ) {
+   AstMapping *result;
+   if ( !astOK ) return NULL;
+   if( !astGetIsSimple( this ) && !astDoNotSimplify( this ) ) {
+      result = (**astMEMBER(this,Mapping,Simplify))( this, status );
+      if( result ) result->flags |= AST__ISSIMPLE_FLAG; /* Indicate simplification has been done */
+   } else {
+      result = astClone( this );
+   }
+   return result;
+}
+AstPointSet *astTransform_( AstMapping *this, AstPointSet *in,
+                            int forward, AstPointSet *out, int *status ) {
+   AstPointSet *result;
+   if ( !astOK ) return NULL;
+   result = (**astMEMBER(this,Mapping,Transform))( this, in, forward, out, status );
+   (void) astReplaceNaN( result );
+   return result;
+}
+void astTran1_( AstMapping *this, int npoint, const double xin[],
+                int forward, double xout[], int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Mapping,Tran1))( this, npoint, xin, forward, xout, status );
+}
+void astTran2_( AstMapping *this,
+                int npoint, const double xin[], const double yin[],
+                int forward, double xout[], double yout[], int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Mapping,Tran2))( this, npoint, xin, yin,
+                                      forward, xout, yout, status );
+}
+void astTranGrid_( AstMapping *this, int ncoord_in, const int lbnd[],
+                   const int ubnd[], double tol, int maxpix, int forward,
+                   int ncoord_out, int outdim, double *out, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Mapping,TranGrid))( this, ncoord_in, lbnd, ubnd, tol,
+                                         maxpix, forward, ncoord_out, outdim,
+                                         out, status );
+}
+void astTranN_( AstMapping *this, int npoint,
+                int ncoord_in, int indim, const double *in,
+                int forward, int ncoord_out, int outdim, double *out, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Mapping,TranN))( this, npoint,
+                                      ncoord_in, indim, in,
+                                      forward, ncoord_out, outdim, out, status );
+}
+void astTranP_( AstMapping *this, int npoint,
+                int ncoord_in, const double *ptr_in[],
+                int forward, int ncoord_out, double *ptr_out[], int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Mapping,TranP))( this, npoint,
+                                      ncoord_in, ptr_in,
+                                      forward, ncoord_out, ptr_out, status );
+}
+int astLinearApprox_( AstMapping *this, const double *lbnd,
+                       const double *ubnd, double tol, double *fit, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Mapping,LinearApprox))( this, lbnd, ubnd, tol, fit, status );
+}
+
+
+int astQuadApprox_( AstMapping *this,  const double lbnd[2],
+                    const double ubnd[2], int nx, int ny, double *fit,
+                    double *rms, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Mapping,QuadApprox))( this, lbnd, ubnd, nx,
+                                                  ny, fit, rms, status );
+}
+
+
+
+/* Public Interface Function Prototypes. */
+/* ------------------------------------- */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+void DecomposeId_( AstMapping *, AstMapping **, AstMapping **, int *, int *, int *, int * );
+void MapBoxId_( AstMapping *, const double [], const double [], int, int, double *, double *, double [], double [], int * );
+double astRateId_( AstMapping *, double *, int, int, int * );
+void astMapSplitId_( AstMapping *, int, const int *, int *, AstMapping **,
+                     int * );
+
+/* Special interface function implementations. */
+/* ------------------------------------------- */
+void astDecomposeId_( AstMapping *this, AstMapping **map1,
+                      AstMapping **map2, int *series, int *invert1,
+                      int *invert2, int *status ) {
+/*
+*++
+*  Name:
+c     astDecompose
+f     AST_DECOMPOSE
+
+*  Purpose:
+*     Decompose a Mapping into two component Mappings.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astDecompose( AstMapping *this, AstMapping **map1,
+c                        AstMapping **map2, int *series, int *invert1,
+c                        int *invert2  )
+f     CALL AST_DECOMPOSE( THIS, MAP1, MAP2, SERIES, INVERT1, INVERT2, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+c     This function returns pointers to two Mappings which, when applied
+f     This routine returns pointers to two Mappings which, when applied
+*     either in series or parallel, are equivalent to the supplied Mapping.
+*
+*     Since the Frame class inherits from the Mapping class, Frames can
+*     be considered as special types of Mappings and so this method can
+*     be used to decompose either CmpMaps or CmpFrames.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping.
+c     map1
+f     MAP1 = INTEGER (Returned)
+c        Address of a location to receive a pointer to first component
+f        A pointer to first component
+*        Mapping.
+c     map2
+f     MAP2 = INTEGER (Returned)
+c        Address of a location to receive a pointer to second component
+f        A pointer to second component
+*        Mapping.
+c     series
+f     SERIES = LOGICAL (Returned)
+c        Address of a location to receive a value indicating if the
+c        component Mappings are applied in series or parallel. A non-zero
+c        value means that the supplied Mapping is equivalent to applying map1
+c        followed by map2 in series. A zero value means that the supplied
+c        Mapping is equivalent to applying map1 to the lower numbered axes
+c        and map2 to the higher numbered axes, in parallel.
+f        Indicates if the
+f        component Mappings are applied in series or parallel. A .TRUE.
+f        value means that the supplied Mapping is equivalent to applying MAP1
+f        followed by MAP2 in series. A zero value means that the supplied
+f        Mapping is equivalent to applying MAP1 to the lower numbered axes
+f        and MAP2 to the higher numbered axes, in parallel.
+c     invert1
+f     INVERT1 = INTEGER (Returned)
+c        The value of the Invert attribute to be used with map1.
+f        The value of the Invert attribute to be used with MAP1.
+c     invert2
+f     INVERT2 = INTEGER (Returned)
+c        The value of the Invert attribute to be used with map2.
+f        The value of the Invert attribute to be used with MAP2.
+
+*  Applicability:
+*     CmpMap
+c        If the supplied Mapping is a CmpMap, then map1 and map2 will be
+f        If the supplied Mapping is a CmpMap, then MAP1 and MAP2 will be
+*        returned holding pointers to the component Mappings used to
+*        create the CmpMap, either in series or parallel. Note, changing
+*        the Invert attribute of either of the component Mappings using
+*        the returned pointers will have no effect on the supplied CmpMap.
+*        This is because the CmpMap remembers and uses the original settings
+*        of the Invert attributes (that is, the values of the Invert
+*        attributes when the CmpMap was first created). These are the
+c        Invert values which are returned in invert1 and invert2.
+f        Invert values which are returned in INVERT1 and INVERT2.
+*     TranMap
+c        If the supplied Mapping is a TranMap, then map1 and map2 will be
+f        If the supplied Mapping is a TranMap, then MAP1 and MAP2 will be
+*        returned holding pointers to the forward and inverse Mappings
+*        represented by the TranMap (zero will be returned for
+c        series).
+f        SERIES).
+*        Note, changing the Invert attribute of
+*        either of the component Mappings using the returned pointers will
+*        have no effect on the supplied TranMap. This is because the TranMap
+*        remembers and uses the original settings of the Invert attributes
+*        (that is, the values of the Invert attributes when the TranMap was
+*        first created). These are the
+c        Invert values which are returned in invert1 and invert2.
+f        Invert values which are returned in INVERT1 and INVERT2.
+*     Mapping
+c        For any class of Mapping other than a CmpMap, map1 will be
+c        returned holding a clone of the supplied Mapping pointer, and map2
+c        will be returned holding a NULL pointer. Invert1 will be returned
+c        holding the current value of the Invert attribute for the supplied
+c        Mapping, and invert2 will be returned holding zero.
+f        For any class of Mapping other than a CmpMap, MAP1 will be
+f        returned holding a clone of the supplied Mapping pointer, and MAP2
+f        will be returned holding AST__NULL. INVERT1 will be returned
+f        holding the current value of the Invert attribute for the supplied
+f        Mapping, and INVERT2 will be returned holding zero.
+*     CmpFrame
+c        If the supplied Mapping is a CmpFrame, then map1 and map2 will be
+f        If the supplied Mapping is a CmpFrame, then MAP1 and MAP2 will be
+*        returned holding pointers to the component Frames used to
+*        create the CmpFrame. The component Frames are considered to be in
+*        applied in parallel.
+*     Frame
+c        For any class of Frame other than a CmpFrame, map1 will be
+c        returned holding a clone of the supplied Frame pointer, and map2
+c        will be returned holding a NULL pointer.
+f        For any class of Frame other than a CmpFrame, MAP1 will be
+f        returned holding a clone of the supplied Frame pointer, and MAP2
+f        will be returned holding AST__NULL.
+
+*  Notes:
+*     - The returned Invert values should be used in preference to the
+*     current values of the Invert attribute in map1 and map2. This is
+*     because the attributes may have changed value since the Mappings
+*     were combined.
+*     - Any changes made to the component Mappings using the returned
+*     pointers will be reflected in the supplied Mapping.
+
+*--
+
+*  Implementation Notes:
+*     This function implements the public interface for the
+*     astDecompose method. It is identical to astDecompose_ except for
+*     the following:
+*
+*     - ID values are returned via the "map1" and "map2" parameters
+*     instead of true C pointers. This is required because this
+*     conversion cannot be performed by the macro that invokes the
+*     function.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the normal astDecompose_ function to decompose the Mapping. */
+      astDecompose( this, map1, map2, series, invert1, invert2 );
+
+/* If required, return ID values for the component Mappings. */
+   if ( map1 ) *map1 = astMakeId( *map1 );
+   if ( map2 ) *map2 = astMakeId( *map2 );
+
+}
+
+void astMapBoxId_( AstMapping *this,
+                   const double lbnd_in[], const double ubnd_in[],
+                   int forward, int coord_out,
+                   double *lbnd_out, double *ubnd_out,
+                   double xl[], double xu[], int *status ) {
+/*
+*++
+*  Name:
+c     astMapBox
+f     AST_MAPBOX
+
+*  Purpose:
+*     Find a bounding box for a Mapping.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astMapBox( AstMapping *this,
+c                     const double lbnd_in[], const double ubnd_in[],
+c                     int forward, int coord_out,
+c                     double *lbnd_out, double *ubnd_out,
+c                     double xl[], double xu[] );
+f     CALL AST_MAPBOX( THIS, LBND_IN, UBND_IN, FORWARD, COORD_OUT,
+f                      LBND_OUT, UBND_OUT, XL, XU, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+c     This function allows you to find the "bounding box" which just
+c     encloses another box after it has been transformed by a Mapping
+c     (using either its forward or inverse transformation). A typical
+c     use might be to calculate the size of an image after being
+c     transformed by a Mapping.
+f     This routine allows you to find the "bounding box" which just
+f     encloses another box after it has been transformed by a Mapping
+f     (using either its forward or inverse transformation). A typical
+f     use might be to calculate the size of an image after being
+f     transformed by a Mapping.
+*
+c     The function works on one dimension at a time. When supplied
+c     with the lower and upper bounds of a rectangular region (box) of
+c     input coordinate space, it finds the lowest and highest values
+c     taken by a nominated output coordinate within that
+c     region. Optionally, it also returns the input coordinates where
+c     these bounding values are attained. It should be used repeatedly
+c     to obtain the extent of the bounding box in more than one
+c     dimension.
+f     The routine works on one dimension at a time. When supplied with
+f     the lower and upper bounds of a rectangular region (box) of
+f     input coordinate space, it finds the lowest and highest values
+f     taken by a nominated output coordinate within that region. It
+f     also returns the input coordinates where these bounding values
+f     are attained. It should be used repeatedly to obtain the extent
+f     of the bounding box in more than one dimension.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping.
+c     lbnd_in
+f     LBND_IN( * ) = DOUBLE PRECISION (Given)
+c        Pointer to an array of double, with one element for each
+c        Mapping input coordinate. This should contain the lower bound
+c        of the input box in each input dimension.
+f        An array with one element for each Mapping input
+f        coordinate. This should contain the lower bound of the input
+f        box in each input dimension.
+c     ubnd_in
+f     UBND_IN( * ) = DOUBLE PRECISION (Given)
+c        Pointer to an array of double, with one element for each
+c        Mapping input coordinate. This should contain the upper bound
+c        of the input box in each input dimension.
+f        An array with one element for each Mapping input
+f        coordinate. This should contain the upper bound of the input
+f        box in each input dimension.
+*
+*        Note that it is permissible for the upper bound to be less
+*        than the corresponding lower bound, as the values will simply
+*        be swapped before use.
+c     forward
+f     FORWARD = LOGICAL (Given)
+c        If this value is non-zero, then the Mapping's forward
+c        transformation will be used to transform the input
+c        box. Otherwise, its inverse transformation will be used.
+f        If this value is .TRUE., then the Mapping's forward
+f        transformation will be used to transform the input
+f        box. Otherwise, its inverse transformation will be used.
+*
+c        (If the inverse transformation is selected, then references
+c        to "input" and "output" coordinates in this description
+c        should be transposed. For example, the size of the "lbnd_in"
+c        and "ubnd_in" arrays should match the number of output
+c        coordinates, as given by the Mapping's Nout
+c        attribute. Similarly, the "coord_out" parameter, below,
+c        should nominate one of the Mapping's input coordinates.)
+f        (If the inverse transformation is selected, then references
+f        to "input" and "output" coordinates in this description
+f        should be transposed. For example, the size of the LBND_IN
+f        and UBND_IN arrays should match the number of output
+f        coordinates, as given by the Mapping's Nout attribute.
+f        Similarly, the COORD_OUT argument, below, should nominate one
+f        of the Mapping's input coordinates.)
+c     coord_out
+f     COORD_OUT = INTEGER (Given)
+*        The index of the output coordinate for which the lower and
+*        upper bounds are required. This value should be at least one,
+*        and no larger than the number of Mapping output coordinates.
+c     lbnd_out
+f     LBND_OUT = DOUBLE PRECISION (Returned)
+c        Pointer to a double in which to return the lowest value taken
+c        by the nominated output coordinate within the specified
+c        region of input coordinate space.
+f        The lowest value taken by the nominated output coordinate
+f        within the specified region of input coordinate space.
+c     ubnd_out
+f     UBND_OUT = DOUBLE PRECISION (Returned)
+c        Pointer to a double in which to return the highest value
+c        taken by the nominated output coordinate within the specified
+c        region of input coordinate space.
+f        The highest value taken by the nominated output coordinate
+f        within the specified region of input coordinate space.
+c     xl
+f     XL( * ) = DOUBLE PRECISION (Returned)
+c        An optional pointer to an array of double, with one element
+c        for each Mapping input coordinate. If given, this array will
+c        be filled with the coordinates of an input point (although
+c        not necessarily a unique one) for which the nominated output
+c        coordinate attains the lower bound value returned in
+c        "*lbnd_out".
+c
+c        If these coordinates are not required, a NULL pointer may be
+c        supplied.
+f        An array with one element for each Mapping input
+f        coordinate. This will return the coordinates of an input
+f        point (although not necessarily a unique one) for which the
+f        nominated output coordinate attains the lower bound value
+f        returned in LBND_OUT.
+c     xu
+f     XU( * ) = DOUBLE PRECISION (Returned)
+c        An optional pointer to an array of double, with one element
+c        for each Mapping input coordinate. If given, this array will
+c        be filled with the coordinates of an input point (although
+c        not necessarily a unique one) for which the nominated output
+c        coordinate attains the upper bound value returned in
+c        "*ubnd_out".
+c
+c        If these coordinates are not required, a NULL pointer may be
+c        supplied.
+f        An array with one element for each Mapping input
+f        coordinate. This will return the coordinates of an input
+f        point (although not necessarily a unique one) for which the
+f        nominated output coordinate attains the upper bound value
+f        returned in UBND_OUT.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Any input points which are transformed by the Mapping to give
+*     output coordinates containing the value AST__BAD are regarded as
+*     invalid and are ignored. They will make no contribution to
+*     determining the output bounds, even although the nominated
+*     output coordinate might still have a valid value at such points.
+c     - An error will occur if the required output bounds cannot be
+c     found. Typically, this might happen if all the input points
+c     which the function considers turn out to be invalid (see
+c     above). The number of points considered before generating such
+c     an error is quite large, so this is unlikely to occur by
+c     accident unless valid points are restricted to a very small
+c     subset of the input coordinate space.
+f     - An error will occur if the required output bounds cannot be
+f     found. Typically, this might happen if all the input points
+f     which the routine considers turn out to be invalid (see
+f     above). The number of points considered before generating such
+f     an error is quite large, so this is unlikely to occur by
+f     accident unless valid points are restricted to a very small
+f     subset of the input coordinate space.
+c     - The values returned via "lbnd_out", "ubnd_out", "xl" and "xu"
+c     will be set to the value AST__BAD if this function should fail
+c     for any reason. Their initial values on entry will not be
+c     altered if the function is invoked with the AST error status
+c     set.
+f     - The values returned via LBND_OUT, UBND_OUT, XL and XU will be
+f     set to the value AST__BAD if this routine should fail for any
+f     reason. Their initial values on entry will not be altered if the
+f     routine is invoked with STATUS set to an error value.
+*--
+
+*  Implementation Notes:
+*     This function implements the public interface for the astMapBox
+*     method. It is identical to astMapBox_ except that the nominated
+*     output coordinate given in "coord_out" is decremented by one
+*     before use.  This is to allow the public interface to use
+*     one-based coordinate numbering (internally, zero-based
+*     coordinate numbering is used).
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the protected version of this function with the "coord_out"
+   value decremented. */
+   astMapBox( this, lbnd_in, ubnd_in, forward, coord_out - 1,
+              lbnd_out, ubnd_out, xl, xu );
+}
+
+double astRateId_( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*++
+*  Name:
+c     astRate
+f     AST_RATE
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     double astRate( AstMapping *this, double *at, int ax1, int ax2 )
+f     RESULT = AST_RATE( THIS, AT, AX1, AX2, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+c     This function
+f     This routine
+*     evaluates the rate of change of a specified output of the supplied
+*     Mapping with respect to a specified input, at a specified input
+*     position.
+*
+*     The result is estimated by interpolating the function using a
+*     fourth order polynomial in the neighbourhood of the specified
+*     position. The size of the neighbourhood used is chosen to minimise
+*     the RMS residual per unit length between the interpolating
+*     polynomial and the supplied Mapping function. This method produces
+*     good accuracy but can involve evaluating the Mapping 100 or more
+*     times.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping to be applied.
+c     at
+f     AT( * ) = DOUBLE PRECISION (Given)
+c        The address of an
+f        An
+*        array holding the axis values at the position at which the rate
+*        of change is to be evaluated. The number of elements in this
+*        array should equal the number of inputs to the Mapping.
+c     ax1
+f     AX1 = INTEGER (Given)
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 1 for the first output).
+c     ax2
+f     AX2 = INTEGER (Given)
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 1 for the first
+*        input).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astRate()
+f     AST_RATE = DOUBLE PRECISION
+c        The rate of change of Mapping output "ax1" with respect to input
+c        "ax2", evaluated at "at", or AST__BAD if the value cannot be
+c        calculated.
+f        The rate of change of Mapping output AX1 with respect to input
+f        AX2, evaluated at AT, or AST__BAD if the value cannot be
+f        calculated.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*--
+
+*  Implementation Notes:
+*     This function implements the public interface for the astRate
+*     method. It is identical to astRate_ except that the nominated
+*     coordinates given in "ax1" and "ax2" are decremented by one
+*     before use.  This is to allow the public interface to use
+*     one-based coordinate numbering (internally, zero-based
+*     coordinate numbering is used).
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Invoke the protected version of this function with the axis indices
+   decremented. */
+   return astRate( this, at, ax1 - 1, ax2 - 1 );
+}
+
+void astMapSplitId_( AstMapping *this, int nin, const int *in, int *out,
+                     AstMapping **map, int *status ){
+/*
+*++
+*  Name:
+c     astMapSplit
+f     AST_MAPSPLIT
+
+*  Purpose:
+*     Split a Mapping up into parallel component Mappings.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "mapping.h"
+c     void astMapSplit( AstMapping *this, int nin, const int *in, int *out,
+c                       AstMapping **map )
+f     CALL AST_MAPSPLIT( THIS, NIN, IN, OUT, MAP, STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+c     This function
+f     This routine
+*     creates a new Mapping which connects specified inputs within a
+*     supplied Mapping to the corresponding outputs of the supplied Mapping.
+*     This is only possible if the specified inputs correspond to some
+*     subset of the Mapping outputs. That is, there must exist a subset of
+*     the Mapping outputs for which each output depends only on the selected
+*     Mapping inputs, and not on any of the inputs which have not been
+*     selected. Also, any output which is not in this subset must not depend
+*     on any of the selected inputs. If these conditions are not met by the
+*     supplied Mapping, then
+c     a NULL
+f     an AST__NULL
+*     Mapping pointer is returned.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Mapping to be split.
+c     nin
+f     NIN = INTEGER (Given)
+c        The number of inputs to pick from "this".
+f        The number of inputs to pick from THIS.
+c     in
+f     IN( NIN ) = INTEGER (Given)
+c        Pointer to an
+f        An
+*        array holding the indices within the supplied Mapping of the inputs
+*        which are to be picked from the Mapping.
+c        This array should have "nin" elements.
+*        If "Nin" is the number of inputs of the supplied Mapping, then each
+*        element should have a value in the range 1 to Nin.
+c     out
+f     OUT( * ) = INTEGER (Returned)
+c        Pointer to an
+f        An
+*        array in which to return the indices of the outputs of the supplied
+*        Mapping which are fed by the picked inputs. A value of one is
+*        used to refer to the first Mapping output. The supplied array should
+*        have a length at least equal to the number of outputs in the
+*        supplied Mapping. The number of values stored in the array on
+*        exit will equal the number of outputs in the returned Mapping.
+*        The i'th element in the returned array holds the index within
+*        the supplied Mapping which corresponds to the i'th output of
+*        the returned Mapping.
+c     map
+f     MAP = INTEGER (Returned)
+c        Address of a location at which to return a pointer to the
+f        The
+*        returned Mapping. This Mapping will have
+c        "nin" inputs (the number of outputs may be different to "nin"). NULL
+f        NIN inputs (the number of outputs may be different to NIN). AST__NULL
+*        is returned if the supplied Mapping has no subset of outputs which
+*        depend only on the selected inputs. The returned Mapping is a
+*        deep copy of the required parts of the supplied Mapping.
+
+*  Notes:
+*     - If this
+c     function
+f     routine
+*     is invoked with the global error status set, or if it should fail for
+*     any reason, then
+c     a NULL value
+f     AST__NULL
+*     will be returned for
+c     the "map" pointer.
+f     MAP.
+*--
+
+*  Implementation Notes:
+*     - This function implements the astMapSplit method available via the
+*     public interface to the Mapping class and uses 1-based axis indices.
+*     The protected interface method is provided by the astMapSplit function
+*     and uses zero-based axis indices. Also, an ID value is returned for
+*     "map" rather than a pointer.
+*/
+
+/* Local Variables: */
+   int *in_zero;           /* Pointer to array of zero-based input indices */
+   int *result;            /* Pointer to array of zero-based output indices*/
+   int i;                  /* Axis index */
+   int nout;               /* No of outputs */
+
+/* Initialise */
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Decrement the axis indices by 1. */
+   in_zero = astMalloc( sizeof( int )*(size_t) nin );
+   if( in_zero ) {
+      for( i = 0; i < nin; i++ ) in_zero[ i ] = in[ i ] - 1;
+
+/* Invoked the protected astMapSplit functon. */
+      result = astMapSplit( this, nin, in_zero, map );
+
+/* If succesful, copy the output axes to the supplied array. */
+      if( result ) {
+         nout = astGetNout( *map );
+         for( i = 0; i < nout; i++ ) out[ i ] = result[ i ] + 1;
+
+/* Free resurces. */
+         result = astFree( result );
+      }
+      in_zero = astFree( in_zero );
+   }
+
+/* Free the returned Mapping if an error has occurred. */
+   if( !astOK ) *map = astAnnul( *map );
+
+/* Return an ID value for the Mapping. */
+   *map = astMakeId( *map );
+}
+
+
+
+
+
+
+
+
diff --git a/ast/mapping.h b/ast/mapping.h
new file mode 100644
index 0000000..7b63fa2
--- /dev/null
+++ b/ast/mapping.h
@@ -0,0 +1,856 @@
+#if !defined( MAPPING_INCLUDED ) /* Include this file only once */
+#define MAPPING_INCLUDED
+/*
+*++
+*  Name:
+*     mapping.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Mapping class.
+
+*  Invocation:
+*     #include "mapping.h"
+
+*  Description:
+*     This include file defines the interface to the Mapping class and
+*     provides the type definitions, function prototypes and macros, etc.
+*     needed to use this class.
+*
+*     The Mapping class provides basic facilities for transforming a
+*     set of points to give a new set of points and for resampling
+*     grids of data. However, it does not have a constructor
+*     function. This is because the class only forms a template for
+*     deriving new classes which themselves implement specific forms
+*     of coordinate transformation. They do this by extending the
+*     protected astTransform method provided by this class.
+
+*  Inheritance:
+*     The Mapping class inherits from the Object class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     Nin (integer)
+*        A read-only attribute giving the number of input coordinate
+*        values required per point by a Mapping (i.e. the number of
+*        dimensions of the space in which input points reside).
+*     Nout (integer)
+*        A read-only attribute giving the number of output coordinate
+*        values generated per point by a Mapping (i.e. the number of
+*        dimensions of the space in which output points reside).
+*     Invert (integer)
+*        A boolean value (0 or 1) which controls which of a Mapping's
+*        two possible coordinate transformations is considered the
+*        "forward" transformation and which is the "inverse"
+*        transformation. If this value is zero (the default), the
+*        behaviour will be as defined when the Mapping was first
+*        created.  If it is non-zero, the transformations will be
+*        inter-changed, so that the Mapping displays the inverse of
+*        its original behaviour.
+*
+*        Note that inverting the boolean sense of the Invert attribute
+*        will cause the values of the Nin/Nout and
+*        TranForward/TranInverse attributes to be interchanged.
+*     Report (integer)
+*        A boolean value (0 or 1) which controls whether to report
+*        coordinate values when a Mapping is used to transform a set
+*        of points. If this value is zero (the default), no report is
+*        made. If it is non-zero, the coordinates of each point
+*        (before and after transformation) are reported by writing
+*        them to standard output.
+*
+*        This attribute is intended as an aid to debugging and to save
+*        having to report values explicitly in simple programs.
+*        Unlike other attributes, the value of the Report attribute is
+*        not inherited when a Mapping is copied (its value is
+*        initially undefined, and therefore defaults to zero, in any
+*        copy).
+*     IsSimple (boolean)
+*        A read-only attribute indicating if the Mapping has been
+*        simpified.
+*     TranForward (integer)
+*        A read-only boolean value (0 or 1) which indicates whether a
+*        Mapping is able to transform coordinates in the "forward"
+*        direction (i.e. converting input coordinates into output
+*        coordinates).
+*     TranInverse (integer)
+*        A read-only boolean value (0 or 1) which indicates whether a
+*        Mapping is able to transform coordinates in the "inverse"
+*        direction (i.e. converting output coordinates back into input
+*        coordinates).
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearAttrib
+*           Clear an attribute value for a Mapping.
+*        astGetAttrib
+*           Get an attribute value for a Mapping.
+*        astSetAttrib
+*           Set an attribute value for a Mapping.
+*        astTestAttrib
+*           Test if an attribute value has been set for a Mapping.
+
+*  New Methods Defined:
+*     Public:
+*        astDecompose
+*           Decompose a Mapping into two component Mappings.
+*        astInvert
+*           Invert a Mapping.
+*        astLinearApprox
+*           Form a linear approximation to a Mapping
+*        astMapBox
+*           Find a bounding box for a Mapping.
+*        astQuadApprox
+*           Form a quadratic approximation to a Mapping
+*        astRate
+*           Find rate of change of a Mapping output
+*        astRebin<X>
+*           Rebin a region of a data grid.
+*        astRebinSeq<X>
+*           Rebin a region of a sequence of data grids.
+*        astResample<X>
+*           Resample a region of a data grid.
+*        astSimplify
+*           Simplify a Mapping.
+*        astTran1
+*           Transform 1-dimensional coordinates.
+*        astTran2
+*           Transform 2-dimensional coordinates.
+*        astTranGrid
+*           Transform an N-dimensional regular grid of positions.
+*        astTranN
+*           Transform N-dimensional coordinates.
+*        astTranP (C only)
+*           Transform N-dimensional coordinates held in separate arrays.
+*
+*     Protected:
+*        astClearInvert
+*           Clear the Invert attribute value for a Mapping.
+*        astClearReport
+*           Clear the Report attribute value for a Mapping.
+*        astGetInvert
+*           Get the Invert attribute value for a Mapping.
+*        astGetIsSimple
+*           Get the IsSimple attribute.
+*        astGetNin
+*           Get the number of input coordinates for a Mapping.
+*        astGetNout
+*           Get the number of output coordinates for a Mapping.
+*        astGetReport
+*           Get the Report attribute value for a Mapping.
+*        astGetTranForward
+*           Determine if a Mapping can perform a "forward" coordinate
+*           transformation.
+*        astGetTranInverse
+*           Determine if a Mapping can perform an "inverse" coordinate
+*           transformation.
+*        astMapList
+*           Decompose a Mapping into a sequence of simpler Mappings.
+*        astMapSplit
+*           Select a subset of Mapping inputs.
+*        astMapMerge
+*           Simplify a sequence of Mappings.
+*        astReportPoints
+*           Report the effect of transforming a set of points using a Mapping.
+*        astSetInvert
+*           Set the Invert attribute value for a Mapping.
+*        astSetReport
+*           Set the Report attribute value for a Mapping.
+*        astTestInvert
+*           Test if an Invert attribute value has been set for a Mapping.
+*        astTestReport
+*           Test if an Report attribute value has been set for a Mapping.
+*        astTransform
+*           Transform a set of points.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAMapping
+*           Test class membership.
+*
+*     Protected:
+*        astCheckMapping
+*           Validate class membership.
+*        astInitMapping
+*           Initialise a Mapping.
+*        astInitMappingVtab
+*           Initialise the virtual function table for the Mapping class.
+*        astLoadMapping
+*           Load a Mapping.
+
+*  Macros:
+*     Public:
+*        AST__BLOCKAVE
+*           Block averaging interpolation.
+*        AST__GAUSS
+*           Use exp(-k*x*x) spreading.
+*        AST__LINEAR
+*           Simple linear interpolation.
+*        AST__NEAREST
+*           Use nearest pixel centre.
+*        AST__SINC
+*           Use sinc(pi*x) interpolation.
+*        AST__SINCCOS
+*           Use sinc(pi*x)*cos(k*pi*x) interpolation.
+*        AST__SINCGAUSS
+*           Use sinc(pi*x)*exp(-k*x*x) interpolation.
+*        AST__SINCSINC
+*           Use sinc(pi*x)*sinc(k*pi*x) interpolation.
+*        AST__SOMB
+*           Use somb(pi*x) interpolation.
+*        AST__SOMBCOS
+*           Use somb(pi*x)*cos(k*pi*x) interpolation.
+*        AST__UINTERP
+*           Use general user-defined sub-pixel interpolation algorithm.
+*        AST__UKERN1
+*           Use user-defined 1-d interpolation kernel.
+*        AST__URESAMP1, 2, 3 & 4
+*           Flags reserved for user-defined purposes.
+*        AST__USEBAD
+*           Recognise bad pixels?
+*        AST__CONSERVEFLUX
+*           Conserve flux in astResample?
+*        AST__REBININIT
+*           Initialise a new sequnece of calls to astRebinSeq<X>
+*        AST__REBINEND
+*           End a sequnece of calls to astRebinSeq<X>
+*        AST__NOBAD
+*           Leave bad output pixels unchanged in calls to astResample<X>
+*        AST__USEVAR
+*           Use variance arrays?
+
+*  Type Definitions:
+*     Public:
+*        AstMapping
+*           Mapping object type.
+*
+*     Protected:
+*        AstMappingVtab
+*           Mapping virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     MBT: Mark Taylor (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     30-JAN-1996 (RFWS):
+*        Original version.
+*     12-JUL-1996 (RFWS):
+*        Updated to support the external interface plus various other
+*        additions.
+*     12-DEC-1996 (RFWS):
+*        Added the astMapList method.
+*     13-DEC-1996 (RFWS):
+*        Added the astMapMerge method.
+*     13-DEC-1996 (RFWS):
+*        Added the astSimplify method.
+*     28-MAY-1998 (RFWS):
+*        Added the astMapBox method.
+*     12-NOV-1998 (RFWS):
+*        Added astResample<X> and associated code.
+*     24-NOV-2000 (MBT):
+*        Added AST__BLOCKAVE interpolation scheme.
+*     9-JAN-2001 (DSB):
+*        Changed in and out arguments for TranN from type "double (*)[]"
+*        to "double *".
+*     8-JAN-2003 (DSB):
+*        Added protected astInitMappingVtab method.
+*     10-JUL-2003 (DSB):
+*        Added method astRate.
+*     20-SEP-2004 (DSB):
+*        Added method astLinearApprox.
+*     30-JUN-2005 (DSB):
+*        Added method astRebin
+*     1-SEP-2005 (DSB):
+*        Added method astRebinSeq
+*     31-JAN-2006 (DSB):
+*        Added IsSimple attribute.
+*     2-MAR-2006 (DSB):
+*        Use HAVE_LONG_DOUBLE in place of AST_LONG_DOUBLE
+*     8-MAR-2006 (DSB):
+*        Add astTranGrid.
+*     5-MAY-2009 (DSB):
+*        Add astRemoveRegions.
+*     26-FEB-2010 (DSB):
+*        Added method astQuadApprox.
+*--
+*/
+
+/* Include files. */
+/* ============== */
+
+/* Configuration results */
+/* --------------------- */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+
+/* C header files. */
+/* --------------- */
+#include <stddef.h>
+#include <stdint.h>
+
+/* Macros. */
+/* ======= */
+
+/* Sizes of global arrays */
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+#define AST__MAPPING_GETATTRIB_BUFF_LEN 50
+#define AST__MAPPING_RATEFUN_MAX_CACHE  5
+
+/* Resampling flags. */
+/* ----------------- */
+/* These macros define flag values which may be passed to
+   astResample<X> (via the "flags" argument) to provide control over
+   resampling operations. */
+#define AST__URESAMP1 (1)        /* Flags reserved for user-defined purposes */
+#define AST__URESAMP2 (2)
+#define AST__URESAMP3 (4)
+#define AST__URESAMP4 (8)
+#define AST__USEVAR (16)         /* Use variance arrays? */
+#define AST__USEBAD (32)         /* Recognise bad pixels? */
+#define AST__CONSERVEFLUX (64)   /* Conserve flux? */
+#define AST__REBININIT (128)     /* Initialise a new sequence of calls to astRebinSeq? */
+#define AST__REBINEND (256)      /* End a sequence of calls to astRebinSeq? */
+#define AST__GENVAR (512)        /* Generate output variances when rebinning? */
+#define AST__VARWGT (1024)       /* Use input variances as weights? */
+#define AST__NOBAD (2048)        /* Leave bad output values unchanged? */
+#define AST__DISVAR (4096)       /* Generate distribution (not mean) variance? */
+#define AST__NONORM (8192)       /* No normalisation required at end? */
+
+/* These macros identify standard sub-pixel interpolation algorithms
+   for use by astResample<X>. They are used by giving the macro's
+   value for the "interp" argument. */
+#define AST__UKERN1 (1)          /* User-supplied 1-d interpolation kernel */
+#if 0                            /* Not yet implemented */
+#define AST__UKERNN (2)          /* User-supplied n-d interpolation kernel */
+#endif
+#define AST__UINTERP (3)         /* User-supplied interpolation function */
+#define AST__NEAREST (4)         /* Use pixel with nearest centre */
+#define AST__LINEAR (5)          /* Simple linear interpolation */
+#define AST__SINC (6)            /* sinc(pi*x) interpolation */
+#define AST__SINCSINC (7)        /* sinc(pi*x)*sinc(k*pi*x) interpolation */
+#define AST__SINCCOS (8)         /* sinc(pi*x)*cos(k*pi*x) interpolation */
+#define AST__SINCGAUSS (9)       /* sinc(pi*x)*exp(-k*x*x) interpolation */
+#define AST__BLOCKAVE (10)       /* Block averaging interpolation */
+#define AST__GAUSS (11)          /* exp(-k*x*x) spreading */
+#define AST__SOMB (12)           /* somp(pi*x) interpolation */
+#define AST__SOMBCOS (13)        /* somp(pi*x)*cos(k*pi*x) interpolation */
+
+/* 64 bit types */
+#if HAVE_INT64_T && HAVE_UINT64_T
+#include <stdint.h>
+typedef int64_t INT_BIG;
+typedef uint64_t UINT_BIG;
+
+#elif SIZEOF_LONG == 8
+typedef long int INT_BIG;
+typedef unsigned long int UINT_BIG;
+
+#elif SIZEOF_LONG_LONG == 8
+typedef long long int INT_BIG;
+typedef unsigned long long int UINT_BIG;
+
+#else
+#define INT_BIG  "no int64_t type available"
+#define UINT_BIG "no uint64_t type available"
+#endif
+
+/* Flags defining the meaning of each bit in the "flags" field of the
+   Mapping structure. */
+#if defined(astCLASS)         /* Protected */
+#define AST__ISSIMPLE_FLAG 1  /* Mapping has been simplified */
+#define AST__FROZEN_FLAG 2    /* Mapping cannot be nominated for simplification */
+#endif
+
+
+/* Type Definitions. */
+/* ================= */
+/* Mapping structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstMapping {
+
+/* Attributes inherited from the parent class. */
+   AstObject object;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   char invert;                   /* Mapping inverted? */
+   char flags;                    /* Bit-wise flags describing the Mapping */
+   int nin;                       /* Number of input coordinates */
+   int nout;                      /* Number of output coordinates */
+   char report;                   /* Report when converting coordinates? */
+   char tran_forward;             /* Forward transformation defined? */
+   char tran_inverse;             /* Inverse transformation defined? */
+} AstMapping;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstMappingVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstObjectVtab object_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstMapping *(* RemoveRegions)( AstMapping *, int * );
+   AstMapping *(* Simplify)( AstMapping *, int * );
+   AstPointSet *(* Transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+   double (* Rate)( AstMapping *, double *, int, int, int * );
+   int (* DoNotSimplify)( AstMapping *, int * );
+   int (* GetInvert)( AstMapping *, int * );
+   int (* GetIsSimple)( AstMapping *, int * );
+   int (* GetNin)( AstMapping *, int * );
+   int (* GetNout)( AstMapping *, int * );
+   int (* GetReport)( AstMapping *, int * );
+   int (* GetTranForward)( AstMapping *, int * );
+   int (* GetTranInverse)( AstMapping *, int * );
+   int (* GetIsLinear)( AstMapping *, int * );
+   int (* LinearApprox)( AstMapping *, const double *, const double *, double, double *, int * );
+   int (* MapMerge)( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+   int (* QuadApprox)( AstMapping *, const double[2], const double[2], int, int, double *, double *, int * );
+   int (* TestInvert)( AstMapping *, int * );
+   int (* TestReport)( AstMapping *, int * );
+   void (* ClearInvert)( AstMapping *, int * );
+   void (* ClearReport)( AstMapping *, int * );
+   void (* Decompose)( AstMapping *, AstMapping **, AstMapping **, int *, int *, int *, int * );
+   void (* Invert)( struct AstMapping *, int * );
+   void (* MapBox)( AstMapping *, const double [], const double [], int, int, double *, double *, double [], double [], int * );
+   int (* MapList)( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+   int *(* MapSplit)( AstMapping *, int, const int *, AstMapping **, int * );
+   void (* ReportPoints)( AstMapping *, int, AstPointSet *, AstPointSet *, int * );
+   void (* SetInvert)( AstMapping *, int, int * );
+   void (* SetReport)( AstMapping *, int, int * );
+   void (* Tran1)( AstMapping *, int, const double [], int, double [], int * );
+   void (* Tran2)( AstMapping *, int, const double [], const double [], int, double [], double [], int * );
+   void (* TranGrid)( AstMapping *, int, const int[], const int[], double, int, int, int, int, double *, int * );
+   void (* TranN)( AstMapping *, int, int, int, const double *, int, int, int, double *, int * );
+   void (* TranP)( AstMapping *, int, int, const double *[], int, int, double *[], int * );
+
+#define DECLARE_GENERIC_ALL(X,Xtype) \
+   int (* Resample##X)( AstMapping *, int, const int [], const int [], \
+                        const Xtype [], const Xtype [], int, \
+                        void (*)( void ), const double [], int, double, int, \
+                        Xtype, int, const int [], const int [], \
+                        const int [], const int [], Xtype [], Xtype [], int * ); \
+
+DECLARE_GENERIC_ALL(B,signed char)
+DECLARE_GENERIC_ALL(D,double)
+DECLARE_GENERIC_ALL(F,float)
+DECLARE_GENERIC_ALL(I,int)
+DECLARE_GENERIC_ALL(K,INT_BIG)
+DECLARE_GENERIC_ALL(L,long int)
+DECLARE_GENERIC_ALL(S,short int)
+DECLARE_GENERIC_ALL(UB,unsigned char)
+DECLARE_GENERIC_ALL(UI,unsigned int)
+DECLARE_GENERIC_ALL(UK,UINT_BIG)
+DECLARE_GENERIC_ALL(UL,unsigned long int)
+DECLARE_GENERIC_ALL(US,unsigned short int)
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+DECLARE_GENERIC_ALL(LD,long double)
+#endif
+
+#undef DECLARE_GENERIC_ALL
+
+#define DECLARE_GENERIC_DFI(X,Xtype) \
+   void (* Rebin##X)( AstMapping *, double, int, const int [], const int [], \
+                      const Xtype [], const Xtype [], int, const double [], int, \
+                      double, int, Xtype, int, const int [], const int [], \
+                      const int [], const int [], Xtype [], Xtype [], int * ); \
+   void (* RebinSeq##X)( AstMapping *, double, int, const int [], const int [], \
+                         const Xtype [], const Xtype [], int, const double [], \
+                         int, double, int, Xtype, int, const int [], \
+                         const int [], const int [], const int [], Xtype [], \
+                         Xtype [], double [], int64_t *, int * );
+
+DECLARE_GENERIC_DFI(D,double)
+DECLARE_GENERIC_DFI(F,float)
+DECLARE_GENERIC_DFI(I,int)
+DECLARE_GENERIC_DFI(B,signed char)
+DECLARE_GENERIC_DFI(UB,unsigned char)
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+DECLARE_GENERIC_DFI(LD,long double)
+#endif
+
+#undef DECLARE_GENERIC_DFI
+
+} AstMappingVtab;
+
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstMappingGlobals {
+   AstMappingVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ AST__MAPPING_GETATTRIB_BUFF_LEN + 1 ];
+   AstMapping *Unsimplified_Mapping;
+   int Rate_Disabled;
+   AstPointSet *RateFun_Pset1_Cache[ AST__MAPPING_RATEFUN_MAX_CACHE ];
+   AstPointSet *RateFun_Pset2_Cache[ AST__MAPPING_RATEFUN_MAX_CACHE ];
+   int RateFun_Next_Slot;
+   int RateFun_Pset_Size[ AST__MAPPING_RATEFUN_MAX_CACHE ];
+} AstMappingGlobals;
+
+#endif
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Mapping)          /* Check class membership */
+astPROTO_ISA(Mapping)            /* Test class membership */
+
+/* NB. There is no constructor function for this class. */
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstMapping *astInitMapping_( void *, size_t, int, AstMappingVtab *,
+                             const char *, int, int, int, int, int * );
+
+/* Vtab initialiser. */
+void astInitMappingVtab_( AstMappingVtab *, const char *, int * );
+
+/* Loader. */
+AstMapping *astLoadMapping_( void *, size_t, AstMappingVtab *,
+                             const char *, AstChannel *channel, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitMappingGlobals_( AstMappingGlobals * );
+#endif
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+#define PROTO_GENERIC_ALL(X,Xtype) \
+   int astResample##X##_( AstMapping *, int, const int [], const int [], \
+                        const Xtype [], const Xtype [], int, \
+                        void (*)( void ), const double [], int, double, int, \
+                        Xtype, int, const int [], const int [], \
+                        const int [], const int [], Xtype [], Xtype [], int * ); \
+
+PROTO_GENERIC_ALL(B,signed char)
+PROTO_GENERIC_ALL(D,double)
+PROTO_GENERIC_ALL(F,float)
+PROTO_GENERIC_ALL(I,int)
+PROTO_GENERIC_ALL(K,INT_BIG)
+PROTO_GENERIC_ALL(L,long int)
+PROTO_GENERIC_ALL(S,short int)
+PROTO_GENERIC_ALL(UB,unsigned char)
+PROTO_GENERIC_ALL(UI,unsigned int)
+PROTO_GENERIC_ALL(UK,UINT_BIG)
+PROTO_GENERIC_ALL(UL,unsigned long int)
+PROTO_GENERIC_ALL(US,unsigned short int)
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+PROTO_GENERIC_ALL(LD,long double)
+#endif
+
+#undef PROTO_GENERIC_ALL
+
+#define PROTO_GENERIC_DFI(X,Xtype) \
+   void astRebin##X##_( AstMapping *, double, int, const int [], const int [], \
+                      const Xtype [], const Xtype [], int, const double [], int, \
+                      double, int, Xtype, int, const int [], const int [], \
+                      const int [], const int [], Xtype [], Xtype [], int * ); \
+   void astRebinSeq##X##_( AstMapping *, double, int, const int [], const int [], \
+                         const Xtype [], const Xtype [], int, const double [], \
+                         int, double, int, Xtype, int, const int [], \
+                         const int [], const int [], const int [], Xtype [], \
+                         Xtype [], double [], int64_t *, int * );
+
+PROTO_GENERIC_DFI(D,double)
+PROTO_GENERIC_DFI(F,float)
+PROTO_GENERIC_DFI(I,int)
+PROTO_GENERIC_DFI(B,signed char)
+PROTO_GENERIC_DFI(UB,unsigned char)
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+PROTO_GENERIC_DFI(LD,long double)
+#endif
+
+#undef PROTO_GENERIC_DFI
+
+AstMapping *astRemoveRegions_( AstMapping *, int * );
+AstMapping *astSimplify_( AstMapping *, int * );
+void astInvert_( AstMapping *, int * );
+int astLinearApprox_( AstMapping *, const double *, const double *, double, double *, int * );
+int astQuadApprox_( AstMapping *, const double[2], const double[2], int, int, double *, double *, int * );
+void astTran1_( AstMapping *, int, const double [], int, double [], int * );
+void astTran2_( AstMapping *, int, const double [], const double [], int, double [], double [], int * );
+void astTranGrid_( AstMapping *, int, const int[], const int[], double, int, int, int, int, double *, int * );
+void astTranN_( AstMapping *, int, int, int, const double *, int, int, int, double *, int * );
+void astTranP_( AstMapping *, int, int, const double *[], int, int, double *[], int * );
+
+#if defined(astCLASS)            /* Protected */
+void astDecompose_( AstMapping *, AstMapping **, AstMapping **, int *, int *, int *, int * );
+void astMapBox_( AstMapping *, const double [], const double [], int, int, double *, double *, double [], double [], int * );
+double astRate_( AstMapping *, double *, int, int, int * );
+int *astMapSplit_( AstMapping *, int, const int *, AstMapping **, int * );
+#else
+void astDecomposeId_( AstMapping *, AstMapping **, AstMapping **, int *, int *, int *, int * );
+void astMapBoxId_( AstMapping *, const double [], const double [], int, int, double *, double *, double [], double [], int * );
+double astRateId_( AstMapping *, double *, int, int, int * );
+void astMapSplitId_( AstMapping *, int, const int *, int *, AstMapping **, int * );
+#endif
+
+#if defined(astCLASS)            /* Protected */
+int astRateState_( int, int * );
+AstPointSet *astTransform_( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+int astGetInvert_( AstMapping *, int * );
+int astGetIsSimple_( AstMapping *, int * );
+int astGetNin_( AstMapping *, int * );
+int astGetNout_( AstMapping *, int * );
+int astGetReport_( AstMapping *, int * );
+int astGetTranForward_( AstMapping *, int * );
+int astGetTranInverse_( AstMapping *, int * );
+int astGetIsLinear_( AstMapping *, int * );
+int astDoNotSimplify_( AstMapping *, int * );
+int astMapMerge_( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+int astTestInvert_( AstMapping *, int * );
+int astTestReport_( AstMapping *, int * );
+void astClearInvert_( AstMapping *, int * );
+void astClearReport_( AstMapping *, int * );
+int astMapList_( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+void astReportPoints_( AstMapping *, int, AstPointSet *, AstPointSet *, int * );
+void astSetInvert_( AstMapping *, int, int * );
+void astSetReport_( AstMapping *, int, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class to make
+   them easier to invoke (e.g. to avoid type mis-matches when passing pointers
+   to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them to
+   validate their own arguments. We must use a cast when passing object
+   pointers (so that they can accept objects from derived classes). */
+
+/* Check class membership. */
+#define astCheckMapping(this) astINVOKE_CHECK(Mapping,this,0)
+#define astVerifyMapping(this) astINVOKE_CHECK(Mapping,this,1)
+
+/* Test class membership. */
+#define astIsAMapping(this) astINVOKE_ISA(Mapping,this)
+
+/* NB. There is no constructor function for this class. */
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitMapping(mem,size,init,vtab,name,nin,nout,tran_forward,tran_inverse) \
+astINVOKE(O,astInitMapping_(mem,size,init,vtab,name,nin,nout,tran_forward,tran_inverse,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitMappingVtab(vtab,name) astINVOKE(V,astInitMappingVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadMapping(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadMapping_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to member functions. */
+/* ------------------------------- */
+/* Here we make use of astCheckMapping (et al.) to validate Mapping
+   pointers before use. This provides a contextual error report if a
+   pointer to the wrong sort of object is supplied. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+#define astResampleLD(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleLD_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#endif
+
+#define astInvert(this) \
+astINVOKE(V,astInvert_(astCheckMapping(this),STATUS_PTR))
+#define astLinearApprox(this,lbnd,ubnd,tol,fit) \
+astINVOKE(V,astLinearApprox_(astCheckMapping(this),lbnd,ubnd,tol,fit,STATUS_PTR))
+#define astQuadApprox(this,lbnd,ubnd,nx,ny,fit,rms) \
+astINVOKE(V,astQuadApprox_(astCheckMapping(this),lbnd,ubnd,nx,ny,fit,rms,STATUS_PTR))
+#define astRebinD(this,wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astRebinD_(astCheckMapping(this),wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astRebinF(this,wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astRebinF_(astCheckMapping(this),wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astRebinI(this,wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astRebinI_(astCheckMapping(this),wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astRebinB(this,wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astRebinB_(astCheckMapping(this),wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astRebinUB(this,wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astRebinUB_(astCheckMapping(this),wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astRebinSeqD(this,wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,weights,nused) \
+astINVOKE(V,astRebinSeqD_(astCheckMapping(this),wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,weights,nused,STATUS_PTR))
+#define astRebinSeqF(this,wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,weights,nused) \
+astINVOKE(V,astRebinSeqF_(astCheckMapping(this),wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,weights,nused,STATUS_PTR))
+#define astRebinSeqI(this,wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,weights,nused) \
+astINVOKE(V,astRebinSeqI_(astCheckMapping(this),wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,weights,nused,STATUS_PTR))
+#define astRebinSeqB(this,wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,weights,nused) \
+astINVOKE(V,astRebinSeqB_(astCheckMapping(this),wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,weights,nused,STATUS_PTR))
+#define astRebinSeqUB(this,wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,weights,nused) \
+astINVOKE(V,astRebinSeqUB_(astCheckMapping(this),wlim,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,weights,nused,STATUS_PTR))
+#define astResampleD(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleD_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleF(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleF_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleL(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleL_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleUL(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleUL_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleI(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleI_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleUI(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleUI_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleK(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleK_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleUK(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleUK_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleS(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleS_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleUS(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleUS_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleB(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleB_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astResampleUB(this,ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var) \
+astINVOKE(V,astResampleUB_(astCheckMapping(this),ndim_in,lbnd_in,ubnd_in,in,in_var,interp,finterp,params,flags,tol,maxpix,badval,ndim_out,lbnd_out,ubnd_out,lbnd,ubnd,out,out_var,STATUS_PTR))
+#define astRemoveRegions(this) astINVOKE(O,astRemoveRegions_(astCheckMapping(this),STATUS_PTR))
+#define astSimplify(this) astINVOKE(O,astSimplify_(astCheckMapping(this),STATUS_PTR))
+#define astTran1(this,npoint,xin,forward,xout) \
+astINVOKE(V,astTran1_(astCheckMapping(this),npoint,xin,forward,xout,STATUS_PTR))
+#define astTran2(this,npoint,xin,yin,forward,xout,yout) \
+astINVOKE(V,astTran2_(astCheckMapping(this),npoint,xin,yin,forward,xout,yout,STATUS_PTR))
+#define astTranGrid(this,ncoord_in,lbnd,ubnd,tol,maxpix,forward,ncoord_out,outdim,out) \
+astINVOKE(V,astTranGrid_(astCheckMapping(this),ncoord_in,lbnd,ubnd,tol,maxpix,forward,ncoord_out,outdim,out,STATUS_PTR))
+#define astTranN(this,npoint,ncoord_in,indim,in,forward,ncoord_out,outdim,out) \
+astINVOKE(V,astTranN_(astCheckMapping(this),npoint,ncoord_in,indim,in,forward,ncoord_out,outdim,out,STATUS_PTR))
+#define astTranP(this,npoint,ncoord_in,ptr_in,forward,ncoord_out,ptr_out) \
+astINVOKE(V,astTranP_(astCheckMapping(this),npoint,ncoord_in,ptr_in,forward,ncoord_out,ptr_out,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#define astDecompose(this,map1,map2,series,inv1,inv2) \
+astINVOKE(V,astDecompose_(astCheckMapping(this),(AstMapping **)(map1),(AstMapping **)(map2),series,inv1,inv2,STATUS_PTR))
+#define astMapBox(this,lbnd_in,ubnd_in,forward,coord_out,lbnd_out,ubnd_out,xl,xu) \
+astINVOKE(V,astMapBox_(astCheckMapping(this),lbnd_in,ubnd_in,forward,coord_out,lbnd_out,ubnd_out,xl,xu,STATUS_PTR))
+#define astRate(this,at,ax1,ax2) \
+astINVOKE(V,astRate_(astCheckMapping(this),at,ax1,ax2,STATUS_PTR))
+#define astMapSplit(this,nin,in,map) \
+astINVOKE(V,astMapSplit_(this,nin,in,map,STATUS_PTR))
+#else
+#define astDecompose(this,map1,map2,series,inv1,inv2) \
+astINVOKE(V,astDecomposeId_(astCheckMapping(this),(AstMapping **)(map1),(AstMapping **)(map2),series,inv1,inv2,STATUS_PTR))
+#define astMapBox(this,lbnd_in,ubnd_in,forward,coord_out,lbnd_out,ubnd_out,xl,xu) \
+astINVOKE(V,astMapBoxId_(astCheckMapping(this),lbnd_in,ubnd_in,forward,coord_out,lbnd_out,ubnd_out,xl,xu,STATUS_PTR))
+#define astRate(this,at,ax1,ax2) \
+astINVOKE(V,astRateId_(astCheckMapping(this),at,ax1,ax2,STATUS_PTR))
+#define astMapSplit(this,nin,in,out,map) \
+astINVOKE(V,astMapSplitId_(astCheckMapping(this),nin,in,out,map,STATUS_PTR))
+#endif
+
+#if defined(astCLASS)            /* Protected */
+#define astRateState(disabled) astRateState_(disabled,STATUS_PTR)
+#define astClearInvert(this) \
+astINVOKE(V,astClearInvert_(astCheckMapping(this),STATUS_PTR))
+#define astClearReport(this) \
+astINVOKE(V,astClearReport_(astCheckMapping(this),STATUS_PTR))
+#define astGetInvert(this) \
+astINVOKE(V,astGetInvert_(astCheckMapping(this),STATUS_PTR))
+#define astGetIsSimple(this) \
+astINVOKE(V,astGetIsSimple_(astCheckMapping(this),STATUS_PTR))
+#define astGetNin(this) \
+astINVOKE(V,astGetNin_(astCheckMapping(this),STATUS_PTR))
+#define astGetNout(this) \
+astINVOKE(V,astGetNout_(astCheckMapping(this),STATUS_PTR))
+#define astGetReport(this) \
+astINVOKE(V,astGetReport_(astCheckMapping(this),STATUS_PTR))
+#define astGetTranForward(this) \
+astINVOKE(V,astGetTranForward_(astCheckMapping(this),STATUS_PTR))
+#define astGetTranInverse(this) \
+astINVOKE(V,astGetTranInverse_(astCheckMapping(this),STATUS_PTR))
+#define astGetIsLinear(this) \
+astINVOKE(V,astGetIsLinear_(astCheckMapping(this),STATUS_PTR))
+#define astMapList(this,series,invert,nmap,map_list,invert_list) \
+astINVOKE(V,astMapList_(this,series,invert,nmap,map_list,invert_list,STATUS_PTR))
+#define astMapMerge(this,where,series,nmap,map_list,invert_list) \
+astINVOKE(V,astMapMerge_(astCheckMapping(this),where,series,nmap,map_list,invert_list,STATUS_PTR))
+#define astReportPoints(this,forward,in_points,out_points) \
+astINVOKE(V,astReportPoints_(astCheckMapping(this),forward,astCheckPointSet(in_points),astCheckPointSet(out_points),STATUS_PTR))
+#define astSetInvert(this,value) \
+astINVOKE(V,astSetInvert_(astCheckMapping(this),value,STATUS_PTR))
+#define astSetReport(this,value) \
+astINVOKE(V,astSetReport_(astCheckMapping(this),value,STATUS_PTR))
+#define astTestInvert(this) \
+astINVOKE(V,astTestInvert_(astCheckMapping(this),STATUS_PTR))
+#define astTestReport(this) \
+astINVOKE(V,astTestReport_(astCheckMapping(this),STATUS_PTR))
+#define astDoNotSimplify(this) \
+astINVOKE(V,astDoNotSimplify_(astCheckMapping(this),STATUS_PTR))
+
+/* Since a NULL PointSet pointer is acceptable here, we must omit the argument
+   checking in that case. (But unfortunately, "out" then gets evaluated
+   twice - this is unlikely to matter, but is there a better way?) */
+#define astTransform(this,in,forward,out) \
+astINVOKE(O,astTransform_(astCheckMapping(this),astCheckPointSet(in),forward,(out)?astCheckPointSet(out):NULL,STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/mathmap.c b/ast/mathmap.c
new file mode 100644
index 0000000..cfcd033
--- /dev/null
+++ b/ast/mathmap.c
@@ -0,0 +1,7421 @@
+/*
+*class++
+*  Name:
+*     MathMap
+
+*  Purpose:
+*     Transform coordinates using mathematical expressions.
+
+*  Constructor Function:
+c     astMathMap
+f     AST_MATHMAP
+
+*  Description:
+c     A MathMap is a Mapping which allows you to specify a set of forward
+c     and/or inverse transformation functions using arithmetic operations
+c     and mathematical functions similar to those available in C. The
+c     MathMap interprets these functions at run-time, whenever its forward
+c     or inverse transformation is required. Because the functions are not
+c     compiled in the normal sense (unlike an IntraMap), they may be used to
+c     describe coordinate transformations in a transportable manner. A
+c     MathMap therefore provides a flexible way of defining new types of
+c     Mapping whose descriptions may be stored as part of a dataset and
+c     interpreted by other programs.
+f     A MathMap is a Mapping which allows you to specify a set of forward
+f     and/or inverse transformation functions using arithmetic operations
+f     and mathematical functions similar to those available in Fortran. The
+f     MathMap interprets these functions at run-time, whenever its forward
+f     or inverse transformation is required. Because the functions are not
+f     compiled in the normal sense (unlike an IntraMap), they may be used to
+f     describe coordinate transformations in a transportable manner. A
+f     MathMap therefore provides a flexible way of defining new types of
+f     Mapping whose descriptions may be stored as part of a dataset and
+f     interpreted by other programs.
+
+*  Inheritance:
+*     The MathMap class inherits from the Mapping class.
+
+*  Attributes:
+*     In addition to those attributes common to all Mappings, every
+*     MathMap also has the following attributes:
+*     - Seed: Random number seed
+*     - SimpFI: Forward-inverse MathMap pairs simplify?
+*     - SimpIF: Inverse-forward MathMap pairs simplify?
+
+*  Functions:
+c     The MathMap class does not define any new functions beyond those
+f     The MathMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     3-SEP-1999 (RFWS):
+*        Original version.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitMathMapVtab
+*        method.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     14-MAR-2006 (DSB):
+*        - Add QIF function.
+*        - Override astEqual method.
+*     20-NOV-2006 (DSB):
+*        Re-implement the Equal method to avoid use of astSimplify.
+*     30-AUG-2012 (DSB):
+*        Fix bug in undocumented Gaussian noise function.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS MathMap
+
+/* Allocate pointer array. */
+/* ----------------------- */
+/* This macro allocates an array of pointers. If successful, each element
+   of the array is initialised to NULL. */
+#define MALLOC_POINTER_ARRAY(array_name,array_type,array_size) \
+\
+/* Allocate the array. */ \
+   (array_name) = astMalloc( sizeof(array_type) * (size_t) (array_size) ); \
+   if ( astOK ) { \
+\
+/* If successful, loop to initialise each element. */ \
+      int array_index_; \
+      for ( array_index_ = 0; array_index_ < (array_size); array_index_++ ) { \
+         (array_name)[ array_index_ ] = NULL; \
+      } \
+   }
+
+/* Free pointer array. */
+/* ------------------- */
+/* This macro frees a dynamically allocated array of pointers, each of
+   whose elements may point at a further dynamically allocated array
+   (which is also to be freed). It also allows for the possibility of any
+   of the pointers being NULL. */
+#define FREE_POINTER_ARRAY(array_name,array_size) \
+\
+/* Check that the main array pointer is not NULL. */ \
+   if ( (array_name) ) { \
+\
+/* If OK, loop to free each of the sub-arrays. */ \
+      int array_index_; \
+      for ( array_index_ = 0; array_index_ < (array_size); array_index_++ ) { \
+\
+/* Check that each sub-array pointer is not NULL before freeing it. */ \
+         if ( (array_name)[ array_index_ ] ) { \
+            (array_name)[ array_index_ ] = \
+               astFree( (array_name)[ array_index_ ] ); \
+         } \
+      } \
+\
+/* Free the main pointer array. */ \
+      (array_name) = astFree( (array_name) ); \
+   }
+
+/* SizeOf pointer array. */
+/* --------------------- */
+/* This macro increments "result" by the number of bytes allocated for an
+   array of pointers, each of whose elements may point at a further
+   dynamically allocated array (which is also to be included). It also
+   allows for the possibility of any of the pointers being NULL. */
+#define SIZEOF_POINTER_ARRAY(array_name,array_size) \
+\
+/* Check that the main array pointer is not NULL. */ \
+   if ( (array_name) ) { \
+\
+/* If OK, loop to measure each of the sub-arrays. */ \
+      int array_index_; \
+      for ( array_index_ = 0; array_index_ < (array_size); array_index_++ ) { \
+\
+/* Check that each sub-array pointer is not NULL before measuring it. */ \
+         if ( (array_name)[ array_index_ ] ) { \
+            result += astTSizeOf( (array_name)[ array_index_ ] ); \
+         } \
+      } \
+\
+/* Include the main pointer array. */ \
+      result += astTSizeOf( (array_name) ); \
+   }
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+#include "channel.h"             /* I/O channels */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "mathmap.h"             /* Interface definition for this class */
+#include "memory.h"              /* Memory allocation facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points */
+#include "unitmap.h"             /* Unit Mapping */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* Module Variables. */
+/* ================= */
+/* This type is made obscure since it is publicly accessible (but not
+   useful). Provide shorthand for use within this module. */
+typedef AstMathMapRandContext_ Rcontext;
+
+
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+/* This declaration enumerates the operation codes recognised by the
+   EvaluateFunction function which evaluates arithmetic expressions. */
+typedef enum {
+
+/* User-supplied constants and variables. */
+   OP_LDCON,                     /* Load constant */
+   OP_LDVAR,                     /* Load variable */
+
+/* System constants. */
+   OP_LDBAD,                     /* Load bad value (AST__BAD) */
+   OP_LDDIG,                     /* Load # decimal digits (DBL_DIG) */
+   OP_LDEPS,                     /* Load relative precision (DBL_EPSILON) */
+   OP_LDMAX,                     /* Load largest value (DBL_MAX) */
+   OP_LDMAX10E,                  /* Max. decimal exponent (DBL_MAX_10_EXP) */
+   OP_LDMAXE,                    /* Load maximum exponent (DBL_MAX_EXP) */
+   OP_LDMDIG,                    /* Load # mantissa digits (DBL_MANT_DIG) */
+   OP_LDMIN,                     /* Load smallest value (DBL_MIN) */
+   OP_LDMIN10E,                  /* Min. decimal exponent (DBL_MIN_10_EXP) */
+   OP_LDMINE,                    /* Load minimum exponent (DBL_MIN_EXP) */
+   OP_LDRAD,                     /* Load floating radix (FLT_RADIX) */
+   OP_LDRND,                     /* Load rounding mode (FLT_ROUNDS) */
+
+/* Mathematical constants. */
+   OP_LDE,                       /* Load e (base of natural logarithms) */
+   OP_LDPI,                      /* Load pi */
+
+/* Functions with one argument. */
+   OP_ABS,                       /* Absolute value (sign removal) */
+   OP_ACOS,                      /* Inverse cosine (radians) */
+   OP_ACOSD,                     /* Inverse cosine (degrees) */
+   OP_ACOSH,                     /* Inverse hyperbolic cosine */
+   OP_ACOTH,                     /* Inverse hyperbolic cotangent */
+   OP_ACSCH,                     /* Inverse hyperbolic cosecant */
+   OP_ASECH,                     /* Inverse hyperbolic secant */
+   OP_ASIN,                      /* Inverse sine (radians) */
+   OP_ASIND,                     /* Inverse sine (degrees) */
+   OP_ASINH,                     /* Inverse hyperbolic sine */
+   OP_ATAN,                      /* Inverse tangent (radians) */
+   OP_ATAND,                     /* Inverse tangent (degrees) */
+   OP_ATANH,                     /* Inverse hyperbolic tangent */
+   OP_CEIL,                      /* C ceil function (round up) */
+   OP_COS,                       /* Cosine (radians) */
+   OP_COSD,                      /* Cosine (degrees) */
+   OP_COSH,                      /* Hyperbolic cosine */
+   OP_COTH,                      /* Hyperbolic cotangent */
+   OP_CSCH,                      /* Hyperbolic cosecant */
+   OP_EXP,                       /* Exponential function */
+   OP_FLOOR,                     /* C floor function (round down) */
+   OP_INT,                       /* Integer value (round towards zero) */
+   OP_ISBAD,                     /* Test for bad value */
+   OP_LOG,                       /* Natural logarithm */
+   OP_LOG10,                     /* Base 10 logarithm */
+   OP_NINT,                      /* Fortran NINT function (round to nearest) */
+   OP_POISS,                     /* Poisson random number */
+   OP_SECH,                      /* Hyperbolic secant */
+   OP_SIN,                       /* Sine (radians) */
+   OP_SINC,                      /* Sinc function [= sin(x)/x] */
+   OP_SIND,                      /* Sine (degrees) */
+   OP_SINH,                      /* Hyperbolic sine */
+   OP_SQR,                       /* Square */
+   OP_SQRT,                      /* Square root */
+   OP_TAN,                       /* Tangent (radians) */
+   OP_TAND,                      /* Tangent (degrees) */
+   OP_TANH,                      /* Hyperbolic tangent */
+
+/* Functions with two arguments. */
+   OP_ATAN2,                     /* Inverse tangent (2 arguments, radians) */
+   OP_ATAN2D,                    /* Inverse tangent (2 arguments, degrees) */
+   OP_DIM,                       /* Fortran DIM (positive difference) fn. */
+   OP_GAUSS,                     /* Gaussian random number */
+   OP_MOD,                       /* Modulus function */
+   OP_POW,                       /* Raise to power */
+   OP_RAND,                      /* Uniformly distributed random number */
+   OP_SIGN,                      /* Transfer of sign function */
+
+/* Functions with three arguments. */
+   OP_QIF,                       /* C "question mark" operator "a?b:c" */
+
+/* Functions with variable numbers of arguments. */
+   OP_MAX,                       /* Maximum of 2 or more values */
+   OP_MIN,                       /* Minimum of 2 or more values */
+
+/* Unary arithmetic operators. */
+   OP_NEG,                       /* Negate (change sign) */
+
+/* Unary boolean operators. */
+   OP_NOT,                       /* Boolean NOT */
+
+/* Binary arithmetic operators. */
+   OP_ADD,                       /* Add */
+   OP_DIV,                       /* Divide */
+   OP_MUL,                       /* Multiply */
+   OP_SUB,                       /* Subtract */
+
+/* Bit-shift operators. */
+   OP_SHFTL,                     /* Shift bits left */
+   OP_SHFTR,                     /* Shift bits right */
+
+/* Relational operators. */
+   OP_EQ,                        /* Relational equal */
+   OP_GE,                        /* Greater than or equal */
+   OP_GT,                        /* Greater than */
+   OP_LE,                        /* Less than or equal */
+   OP_LT,                        /* Less than */
+   OP_NE,                        /* Not equal */
+
+/* Bit-wise operators. */
+   OP_BITAND,                    /* Bit-wise AND */
+   OP_BITOR,                     /* Bit-wise OR */
+   OP_BITXOR,                    /* Bit-wise exclusive OR */
+
+/* Binary boolean operators. */
+   OP_AND,                       /* Boolean AND */
+   OP_EQV,                       /* Fortran logical .EQV. operation */
+   OP_OR,                        /* Boolean OR */
+   OP_XOR,                       /* Boolean exclusive OR */
+
+/* Null operation. */
+   OP_NULL                       /* Null operation */
+} Oper;
+
+/* This structure holds a description of each symbol which may appear
+   in an expression. */
+typedef struct {
+   const char *text;             /* Symbol text as it appears in expressions */
+   const int size;               /* Size of symbol text */
+   const int operleft;           /* An operator when seen from the left? */
+   const int operright;          /* An operator when seen from the right? */
+   const int unarynext;          /* May be followed by a unary +/- ? */
+   const int unaryoper;          /* Is a unary +/- ? */
+   const int leftpriority;       /* Priority when seen from the left */
+   const int rightpriority;      /* Priority when seen from the right */
+   const int parincrement;       /* Change in parenthesis level */
+   const int stackincrement;     /* Change in evaluation stack size */
+   const int nargs;              /* Number of function arguments */
+   const Oper opcode;            /* Resulting operation code */
+} Symbol;
+
+/* This initialises an array of Symbol structures to hold data on all
+   the supported symbols. The order is not important, but symbols are
+   arranged here in approximate order of descending evaluation
+   priority. The end of the array is indicated by an element with a NULL
+   "text" component. */
+static const Symbol symbol[] = {
+
+/* User-supplied constants and variables. */
+   { ""            ,  0,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDCON    },
+   { ""            ,  0,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDVAR    },
+
+/* System constants. */
+   { "<bad>"       ,  5,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDBAD    },
+   { "<dig>"       ,  5,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDDIG    },
+   { "<epsilon>"   ,  9,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDEPS    },
+   { "<mant_dig>"  , 10,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDMDIG   },
+   { "<max>"       ,  5,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDMAX    },
+   { "<max_10_exp>", 12,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDMAX10E },
+   { "<max_exp>"   ,  9,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDMAXE   },
+   { "<min>"       ,  5,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDMIN    },
+   { "<min_10_exp>", 12,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDMIN10E },
+   { "<min_exp>"   ,  9,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDMINE   },
+   { "<radix>"     ,  7,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDRAD    },
+   { "<rounds>"    ,  8,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDRND    },
+
+/* Mathematical constants. */
+   { "<e>"         ,  3,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDE      },
+   { "<pi>"        ,  4,  0,  0,  0,  0, 19, 19,  0,  1,  0,  OP_LDPI     },
+
+/* Functions with one argument. */
+   { "abs("        ,  4,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ABS      },
+   { "acos("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ACOS     },
+   { "acosd("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ACOSD    },
+   { "acosh("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ACOSH    },
+   { "acoth("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ACOTH    },
+   { "acsch("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ACSCH    },
+   { "aint("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_INT      },
+   { "asech("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ASECH    },
+   { "asin("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ASIN     },
+   { "asind("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ASIND    },
+   { "asinh("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ASINH    },
+   { "atan("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ATAN     },
+   { "atand("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ATAND    },
+   { "atanh("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ATANH    },
+   { "ceil("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_CEIL     },
+   { "cos("        ,  4,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_COS      },
+   { "cosd("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_COSD     },
+   { "cosh("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_COSH     },
+   { "coth("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_COTH     },
+   { "csch("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_CSCH     },
+   { "exp("        ,  4,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_EXP      },
+   { "fabs("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ABS      },
+   { "floor("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_FLOOR    },
+   { "int("        ,  4,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_INT      },
+   { "isbad("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_ISBAD    },
+   { "log("        ,  4,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_LOG      },
+   { "log10("      ,  6,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_LOG10    },
+   { "nint("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_NINT     },
+   { "poisson("    ,  8,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_POISS    },
+   { "sech("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_SECH     },
+   { "sin("        ,  4,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_SIN      },
+   { "sinc("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_SINC     },
+   { "sind("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_SIND     },
+   { "sinh("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_SINH     },
+   { "sqr("        ,  4,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_SQR      },
+   { "sqrt("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_SQRT     },
+   { "tan("        ,  4,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_TAN      },
+   { "tand("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_TAND     },
+   { "tanh("       ,  5,  0,  1,  1,  0, 19,  1,  1,  0,  1,  OP_TANH     },
+
+/* Functions with two arguments. */
+   { "atan2("      ,  6,  0,  1,  1,  0, 19,  1,  1, -1,  2,  OP_ATAN2    },
+   { "atan2d("     ,  7,  0,  1,  1,  0, 19,  1,  1, -1,  2,  OP_ATAN2D   },
+   { "dim("        ,  4,  0,  1,  1,  0, 19,  1,  1, -1,  2,  OP_DIM      },
+   { "fmod("       ,  5,  0,  1,  1,  0, 19,  1,  1, -1,  2,  OP_MOD      },
+   { "gauss("      ,  6,  0,  1,  1,  0, 19,  1,  1, -1,  2,  OP_GAUSS    },
+   { "mod("        ,  4,  0,  1,  1,  0, 19,  1,  1, -1,  2,  OP_MOD      },
+   { "pow("        ,  4,  0,  1,  1,  0, 19,  1,  1, -1,  2,  OP_POW      },
+   { "rand("       ,  5,  0,  1,  1,  0, 19,  1,  1, -1,  2,  OP_RAND     },
+   { "sign("       ,  5,  0,  1,  1,  0, 19,  1,  1, -1,  2,  OP_SIGN     },
+
+/* Functions with two arguments. */
+   { "qif("        ,  4,  0,  1,  1,  0, 19,  1,  1, -2,  3,  OP_QIF      },
+
+/* Functions with variable numbers of arguments. */
+   { "max("        ,  4,  0,  1,  1,  0, 19,  1,  1, -1, -2,  OP_MAX      },
+   { "min("        ,  4,  0,  1,  1,  0, 19,  1,  1, -1, -2,  OP_MIN      },
+
+/* Parenthesised expressions. */
+   { ")"           ,  1,  1,  0,  0,  0,  2, 19, -1,  0,  0,  OP_NULL     },
+   { "("           ,  1,  0,  1,  1,  0, 19,  1,  1,  0,  0,  OP_NULL     },
+
+/* Unary arithmetic operators. */
+   { "+"           ,  1,  0,  1,  1,  1, 17, 16,  0,  0,  0,  OP_NULL     },
+   { "-"           ,  1,  0,  1,  1,  1, 17, 16,  0,  0,  0,  OP_NEG      },
+
+/* Unary boolean operators. */
+   { "!"           ,  1,  0,  1,  1,  0, 17, 16,  0,  0,  0,  OP_NOT      },
+   { ".not."       ,  5,  0,  1,  1,  0, 17, 16,  0,  0,  0,  OP_NOT      },
+
+/* Binary arithmetic operators. */
+   { "**"          ,  2,  1,  1,  1,  0, 18, 15,  0, -1,  0,  OP_POW      },
+   { "*"           ,  1,  1,  1,  1,  0, 14, 14,  0, -1,  0,  OP_MUL      },
+   { "/"           ,  1,  1,  1,  1,  0, 14, 14,  0, -1,  0,  OP_DIV      },
+   { "+"           ,  1,  1,  1,  1,  0, 13, 13,  0, -1,  0,  OP_ADD      },
+   { "-"           ,  1,  1,  1,  1,  0, 13, 13,  0, -1,  0,  OP_SUB      },
+
+/* Bit-shift operators. */
+   { "<<"          ,  2,  1,  1,  1,  0, 12, 12,  0, -1,  0,  OP_SHFTL    },
+   { ">>"          ,  2,  1,  1,  1,  0, 12, 12,  0, -1,  0,  OP_SHFTR    },
+
+/* Relational operators. */
+   { "<"           ,  1,  1,  1,  1,  0, 11, 11,  0, -1,  0,  OP_LT       },
+   { ".lt."        ,  4,  1,  1,  1,  0, 11, 11,  0, -1,  0,  OP_LT       },
+   { "<="          ,  2,  1,  1,  1,  0, 11, 11,  0, -1,  0,  OP_LE       },
+   { ".le."        ,  4,  1,  1,  1,  0, 11, 11,  0, -1,  0,  OP_LE       },
+   { ">"           ,  1,  1,  1,  1,  0, 11, 11,  0, -1,  0,  OP_GT       },
+   { ".gt."        ,  4,  1,  1,  1,  0, 11, 11,  0, -1,  0,  OP_GT       },
+   { ">="          ,  2,  1,  1,  1,  0, 11, 11,  0, -1,  0,  OP_GE       },
+   { ".ge."        ,  4,  1,  1,  1,  0, 11, 11,  0, -1,  0,  OP_GE       },
+   { "=="          ,  2,  1,  1,  1,  0, 10, 10,  0, -1,  0,  OP_EQ       },
+   { ".eq."        ,  4,  1,  1,  1,  0, 10, 10,  0, -1,  0,  OP_EQ       },
+   { "!="          ,  2,  1,  1,  1,  0, 10, 10,  0, -1,  0,  OP_NE       },
+   { ".ne."        ,  4,  1,  1,  1,  0, 10, 10,  0, -1,  0,  OP_NE       },
+
+/* Bit-wise operators. */
+   { "&"           ,  1,  1,  1,  1,  0,  9,  9,  0, -1,  0,  OP_BITAND   },
+   { "^"           ,  1,  1,  1,  1,  0,  8,  8,  0, -1,  0,  OP_BITXOR   },
+   { "|"           ,  1,  1,  1,  1,  0,  7,  7,  0, -1,  0,  OP_BITOR    },
+
+/* Binary boolean operators. */
+   { "&&"          ,  2,  1,  1,  1,  0,  6,  6,  0, -1,  0,  OP_AND      },
+   { ".and."       ,  5,  1,  1,  1,  0,  6,  6,  0, -1,  0,  OP_AND      },
+   { "^^"          ,  2,  1,  1,  1,  0,  5,  5,  0, -1,  0,  OP_XOR      },
+   { "||"          ,  2,  1,  1,  1,  0,  4,  4,  0, -1,  0,  OP_OR       },
+   { ".or."        ,  4,  1,  1,  1,  0,  4,  4,  0, -1,  0,  OP_OR       },
+   { ".eqv."       ,  5,  1,  1,  1,  0,  3,  3,  0, -1,  0,  OP_EQV      },
+   { ".neqv."      ,  6,  1,  1,  1,  0,  3,  3,  0, -1,  0,  OP_XOR      },
+   { ".xor."       ,  5,  1,  1,  1,  0,  3,  3,  0, -1,  0,  OP_XOR      },
+
+/* Separators. */
+   { ","           ,  1,  1,  1,  1,  0,  2,  2,  0,  0,  0,  OP_NULL     },
+
+/* End of symbol data. */
+   { NULL          ,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  OP_NULL     }
+};
+
+/* These variables identify indices in the above array which hold
+   special symbols used explicitly in the code. */
+static const int symbol_ldcon = 0; /* Load a constant */
+static const int symbol_ldvar = 1; /* Load a variable */
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(MathMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(MathMap,Class_Init)
+#define class_vtab astGLOBAL(MathMap,Class_Vtab)
+#define getattrib_buff astGLOBAL(MathMap,GetAttrib_Buff)
+
+
+
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+static pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX3 pthread_mutex_lock( &mutex3 );
+#define UNLOCK_MUTEX3 pthread_mutex_unlock( &mutex3 );
+
+static pthread_mutex_t mutex4 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX4 pthread_mutex_lock( &mutex4 );
+#define UNLOCK_MUTEX4 pthread_mutex_unlock( &mutex4 );
+
+static pthread_mutex_t mutex5 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX5 pthread_mutex_lock( &mutex5 );
+#define UNLOCK_MUTEX5 pthread_mutex_unlock( &mutex5 );
+
+static pthread_mutex_t mutex6 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX6 pthread_mutex_lock( &mutex6 );
+#define UNLOCK_MUTEX6 pthread_mutex_unlock( &mutex6 );
+
+static pthread_mutex_t mutex7 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX7 pthread_mutex_lock( &mutex7 );
+#define UNLOCK_MUTEX7 pthread_mutex_unlock( &mutex7 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 51 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstMathMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+
+#define LOCK_MUTEX3
+#define UNLOCK_MUTEX3
+
+#define LOCK_MUTEX4
+#define UNLOCK_MUTEX4
+
+#define LOCK_MUTEX5
+#define UNLOCK_MUTEX5
+
+#define LOCK_MUTEX6
+#define UNLOCK_MUTEX6
+
+#define LOCK_MUTEX7
+#define UNLOCK_MUTEX7
+
+#endif
+
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstMathMap *astMathMapId_( int, int, int, const char *[], int, const char *[], const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int GetObjSize( AstObject *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static double Gauss( Rcontext *, int * );
+static double LogGamma( double, int * );
+static double Poisson( Rcontext *, double, int * );
+static double Rand( Rcontext *, int * );
+static int DefaultSeed( const Rcontext *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetSeed( AstMathMap *, int * );
+static int GetSimpFI( AstMathMap *, int * );
+static int GetSimpIF( AstMathMap *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestSeed( AstMathMap *, int * );
+static int TestSimpFI( AstMathMap *, int * );
+static int TestSimpIF( AstMathMap *, int * );
+static void CleanFunctions( int, const char *[], char ***, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearSeed( AstMathMap *, int * );
+static void ClearSimpFI( AstMathMap *, int * );
+static void ClearSimpIF( AstMathMap *, int * );
+static void CompileExpression( const char *, const char *, const char *, int, const char *[], int **, double **, int *, int * );
+static void CompileMapping( const char *, const char *, int, int, int, const char *[], int, const char *[], int ***, int ***, double ***, double ***, int *, int *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void EvaluateFunction( Rcontext *, int, const double **, const int *, const double *, int, double *, int * );
+static void EvaluationSort( const double [], int, int [], int **, int *, int * );
+static void ExtractExpressions( const char *, const char *, int, const char *[], int, char ***, int * );
+static void ExtractVariables( const char *, const char *, int, const char *[], int, int, int, int, int, char ***, int * );
+static void ParseConstant( const char *, const char *, const char *, int, int *, double *, int * );
+static void ParseName( const char *, int, int *, int * );
+static void ParseVariable( const char *, const char *, const char *, int, int, const char *[], int *, int *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetSeed( AstMathMap *, int, int * );
+static void SetSimpFI( AstMathMap *, int, int * );
+static void SetSimpIF( AstMathMap *, int, int * );
+static void ValidateSymbol( const char *, const char *, const char *, int, int, int *, int **, int **, int *, double **, int * );
+
+/* Member functions. */
+/* ================= */
+static void CleanFunctions( int nfun, const char *fun[], char ***clean, int *status ) {
+/*
+*  Name:
+*     CleanFunctions
+
+*  Purpose:
+*     Make a clean copy of a set of functions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void CleanFunctions( int nfun, const char *fun[], char ***clean, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This function copies an array of strings, eliminating any white space
+*     characters and converting to lower case. It is intended for cleaning
+*     up arrays of function definitions prior to compilation. The returned
+*     copy is stored in dynamically allocated memory.
+
+*  Parameters:
+*     nfun
+*        The number of functions to be cleaned.
+*     fun
+*        Pointer to an array, with "nfun" elements, of pointers to null
+*        terminated strings which contain each of the functions.
+*     clean
+*        Address in which to return a pointer to an array (with "nfun"
+*        elements) of pointers to null terminated strings containing the
+*        cleaned functions (i.e. this returns an array of strings).
+*
+*        Both the returned array of pointers, and the strings to which they
+*        point, will be dynamically allocated and should be freed by the
+*        caller (using astFree) when no longer required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*        - A NULL value will be returned for "*clean" if this function is
+*        invoked with the global error status set, or if it should fail for
+*        any reason.
+*/
+
+/* Local Variables: */
+   char c;                       /* Character from function string */
+   int i;                        /* Loop counter for characters */
+   int ifun;                     /* Loop counter for functions */
+   int nc;                       /* Count of non-blank characters */
+
+/* Initialise. */
+   *clean = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Allocate and initialise an array to hold the returned pointers. */
+   MALLOC_POINTER_ARRAY( *clean, char *, nfun )
+
+/* Loop through all the input functions. */
+   if ( astOK ) {
+      for ( ifun = 0; ifun < nfun; ifun++ ) {
+
+/* Count the number of non-blank characters in each function string. */
+         nc = 0;
+         for ( i = 0; ( c = fun[ ifun ][ i ] ); i++ ) nc += !isspace( c );
+
+/* Allocate a string long enough to hold the function with all the
+   white space removed, storing its pointer in the array allocated
+   earlier. Check for errors. */
+         ( *clean )[ ifun ] = astMalloc( sizeof( char ) *
+                                         (size_t) ( nc + 1 ) );
+         if ( !astOK ) break;
+
+/* Loop to copy the non-blank function characters into the new
+   string. */
+         nc = 0;
+         for ( i = 0; ( c = fun[ ifun ][ i ] ); i++ ) {
+            if ( !isspace( c ) ) ( *clean )[ ifun ][ nc++ ] = tolower( c );
+         }
+
+/* Null-terminate the result. */
+         ( *clean )[ ifun ][ nc ] = '\0';
+      }
+
+/* If an error occurred, then free the main pointer array together
+   with any strings that have been allocated, resetting the output
+   value. */
+      if ( !astOK ) {
+         FREE_POINTER_ARRAY( *clean, nfun )
+      }
+   }
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a MathMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     MathMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     MathMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the MathMap.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstMathMap *this;             /* Pointer to the MathMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the MathMap structure. */
+   this = (AstMathMap *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Seed. */
+/* ----- */
+   if ( !strcmp( attrib, "seed" ) ) {
+      astClearSeed( this );
+
+/* SimpFI. */
+/* ------- */
+   } else if ( !strcmp( attrib, "simpfi" ) ) {
+      astClearSimpFI( this );
+
+/* SimpIF. */
+/* ------- */
+   } else if ( !strcmp( attrib, "simpif" ) ) {
+      astClearSimpIF( this );
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void CompileExpression( const char *method, const char *class,
+                               const char *exprs, int nvar, const char *var[],
+                               int **code, double **con, int *stacksize, int *status ) {
+/*
+*  Name:
+*     CompileExpression
+
+*  Purpose:
+*     Compile a mathematical expression.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void CompileExpression( const char *method, const char *class,
+*                             const char *exprs, int nvar, const char *var[],
+*                             int **code, double **con, int *stacksize )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This function checks and compiles a mathematical expression. It
+*     produces a sequence of operation codes (opcodes) and a set of
+*     numerical constants which may subsequently be used to evaluate the
+*     expression on a push-down stack.
+
+*  Parameters:
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function.
+*        This method name is used solely for constructing error messages.
+*     class
+*        Pointer to a constant null-terminated character string containing the
+*        class name of the Object being processed. This name is used solely
+*        for constructing error messages.
+*     exprs
+*        Pointer to a null-terminated string containing the expression
+*        to be compiled. This is case sensitive and should contain no white
+*        space.
+*     nvar
+*        The number of variable names defined for use in the expression.
+*     var
+*        An array of pointers (with "nvar" elements) to null-terminated
+*        strings. Each of these should contain a variable name which may
+*        appear in the expression. These strings are case sensitive and
+*        should contain no white space.
+*     code
+*        Address of a pointer which will be set to point at a dynamically
+*        allocated array of int containing the set of opcodes (cast to int)
+*        produced by this function. The first element of this array will
+*        contain a count of the number of opcodes which follow.
+*
+*        The allocated space must be freed by the caller (using astFree) when
+*        no longer required.
+*     con
+*        Address of a pointer which will be set to point at a dynamically
+*        allocated array of double containing the set of constants
+*        produced by this function (this may be NULL if no constants are
+*        produced).
+*
+*        The allocated space must be freed by the caller (using astFree) when
+*        no longer required.
+*     stacksize
+*        Pointer to an int in which to return the size of the push-down stack
+*        required to evaluate the expression using the returned opcodes and
+*        constants.
+
+*  Algorithm:
+*     The function passes through the input expression searching for
+*     symbols. It looks for standard symbols (arithmetic operators,
+*     parentheses, function calls and delimiters) in the next part of the
+*     expression to be parsed, using identification information stored in
+*     the static "symbol" array. It ignores certain symbols, according to
+*     whether they appear to be operators or operands. The choice depends on
+*     what the previous symbol was; for instance, two operators may not
+*     occur in succession. Unary +/- operators are also ignored in
+*     situations where they are not permitted.
+*
+*     If a standard symbol is found, it is passed to the ValidateSymbol
+*     function, which keeps track of the current level of parenthesis in the
+*     expression and of the number of arguments supplied to any (possibly
+*     nested) function calls. This function then accepts or rejects the
+*     symbol according to whether it is valid within the current context. An
+*     error is reported if it is rejected.
+*
+*     If the part of the expression currently being parsed did not contain a
+*     standard symbol, an attempt is made to parse it first as a constant,
+*     then as a variable name. If either of these succeeds, an appropriate
+*     symbol number is added to the list of symbols identified so far, and a
+*     value is added to the list of constants - this is either the value of
+*     the constant itself, or the identification number of the variable. If
+*     the expression cannot be parsed, an error is reported.
+*
+*     When the entire expression has been analysed as a sequence of symbols
+*     (and associated constants), the EvaluationSort function is
+*     invoked. This sorts the symbols into evaluation order, which is the
+*     order in which the associated operations must be performed on a
+*     push-down arithmetic stack to evaluate the expression. This routine
+*     also substitutes operation codes (defined in the "Oper" enum) for the
+*     symbol numbers and calculates the size of evaluation stack which will
+*     be required.
+
+*  Notes:
+*     - A value of NULL will be returned for the "*code" and "*con" pointers
+*     and a value of zero will be returned for the "*stacksize" value if this
+*     function is invoked with the global error status set, or if it should
+*     fail for any reason.
+*/
+
+/* Local Variables: */
+   double c;                     /* Value of parsed constant */
+   int *argcount;                /* Array of argument count information */
+   int *opensym;                 /* Array of opening parenthesis information */
+   int *symlist;                 /* Array of symbol indices */
+   int found;                    /* Standard symbol identified? */
+   int iend;                     /* Ending index in the expression string */
+   int istart;                   /* Staring index in the expression string */
+   int isym;                     /* Loop counter for symbols */
+   int ivar;                     /* Index of variable name */
+   int lpar;                     /* Parenthesis level */
+   int ncon;                     /* Number of constants generated */
+   int nsym;                     /* Number of symbols identified */
+   int opernext;                 /* Next symbol an operator (from left)? */
+   int size;                     /* Size of symbol matched */
+   int sym;                      /* Index of symbol in static "symbol" array */
+   int unarynext;                /* Next symbol may be unary +/- ? */
+
+/* Initialise. */
+   *code = NULL;
+   *con = NULL;
+   *stacksize = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Further initialisation. */
+   argcount = NULL;
+   lpar = 0;
+   ncon = 0;
+   nsym = 0;
+   opensym = NULL;
+   symlist = NULL;
+   sym = 0;
+   ivar = 0;
+
+/* The first symbol to be encountered must not look like an operator
+   from the left. It may be a unary + or - operator. */
+   opernext = 0;
+   unarynext = 1;
+
+/* Search through the expression to classify each symbol which appears
+   in it. Stop when there are no more input characters or an error is
+   detected. */
+   istart = 0;
+   for ( istart = 0; astOK && exprs[ istart ]; istart = iend + 1 ) {
+
+/* Compare each of the symbols in the symbol data with the next
+   section of the expression, looking for the longest symbol text which
+   will match. Stop if a NULL "text" value is found, which acts as the
+   end flag. */
+      found = 0;
+      size = 0;
+      for ( isym = 0; symbol[ isym ].text; isym++ ) {
+
+/* Only consider symbols which have text associated with them and
+   which look like operators or operands from the left, according to the
+   setting of the "opernext" flag. Thus, if an operator or operand is
+   missing from the input expression, the next symbol will not be
+   identified, because it will be of the wrong type. Also exclude unary
+   +/- operators if they are out of context. */
+         if ( symbol[ isym ].size &&
+              ( symbol[ isym ].operleft == opernext ) &&
+              ( !symbol[ isym ].unaryoper || unarynext ) ) {
+
+/* Test if the text of the symbol matches the expression at the
+   current position. If so, note that a match has been found. */
+            if ( !strncmp( exprs + istart, symbol[ isym ].text,
+                           (size_t) symbol[ isym ].size ) ) {
+               found = 1;
+
+/* If this symbol matches more characters than any previous symbol,
+   then store the symbol's index and note its size. */
+               if ( symbol[ isym ].size > size ) {
+                  sym = isym;
+                  size = symbol[ isym ].size;
+
+/* Calculate the index of the last symbol character in the expression
+   string. */
+                  iend = istart + size - 1;
+               }
+            }
+         }
+      }
+
+/* If the symbol was identified as one of the standard symbols, then
+   validate it, updating the parenthesis level and argument count
+   information at the same time. */
+      if ( found ) {
+         ValidateSymbol( method, class, exprs, iend, sym, &lpar, &argcount,
+                         &opensym, &ncon, con, status );
+
+/* If it was not one of the standard symbols, then check if the next
+   symbol was expected to be an operator. If so, then there is a missing
+   operator, so report an error. */
+      } else {
+         if ( opernext ) {
+            astError( AST__MIOPR,
+                      "%s(%s): Missing or invalid operator in the expression "
+                      "\"%.*s\".", status,
+                      method, class, istart + 1, exprs );
+
+/* If the next symbol was expected to be an operand, then it may be a
+   constant, so try to parse it as one. */
+         } else {
+            ParseConstant( method, class, exprs, istart, &iend, &c, status );
+            if ( astOK ) {
+
+/* If successful, set the symbol number to "symbol_ldcon" (load
+   constant) and extend the "*con" array to accommodate a new
+   constant. Check for errors. */
+               if ( iend >= istart ) {
+                  sym = symbol_ldcon;
+                  *con = astGrow( *con, ncon + 1, sizeof( double ) );
+                  if ( astOK ) {
+
+/* Append the constant to the "*con" array. */
+                     ( *con )[ ncon++ ] = c;
+                  }
+
+/* If the symbol did not parse as a constant, then it may be a
+   variable name, so try to parse it as one. */
+               } else {
+                  ParseVariable( method, class, exprs, istart, nvar, var,
+                                 &ivar, &iend, status );
+                  if ( astOK ) {
+
+/* If successful, set the symbol to "symbol_ldvar" (load variable) and
+   extend the "*con" array to accommodate a new constant. Check for
+   errors. */
+                     if ( ivar != -1 ) {
+                        sym = symbol_ldvar;
+                        *con = astGrow( *con, ncon + 1, sizeof( double ) );
+                        if ( astOK ) {
+
+/* Append the variable identification number as a constant to the
+   "*con" array. */
+                           ( *con )[ ncon++ ] = (double) ivar;
+                        }
+
+/* If the expression did not parse as a variable name, then there is a
+   missing operand in the expression, so report an error. */
+                     } else {
+                        astError( AST__MIOPA,
+                                  "%s(%s): Missing or invalid operand in the "
+                                  "expression \"%.*s\".", status,
+                                  method, class, istart + 1, exprs );
+                     }
+                  }
+               }
+            }
+         }
+      }
+
+/* If there has been no error, then the next symbol in the input
+   expression has been identified and is valid. */
+      if ( astOK ) {
+
+/* Decide whether the next symbol should look like an operator or an
+   operand from the left. This is determined by the nature of the symbol
+   just identified (seen from the right) - two operands or two operators
+   cannot be adjacent. */
+         opernext = !symbol[ sym ].operright;
+
+/* Also decide whether the next symbol may be a unary +/- operator,
+   according to the "unarynext" symbol data entry for the symbol just
+   identified. */
+         unarynext = symbol[ sym ].unarynext;
+
+/* Extend the "symlist" array to accommodate the symbol just
+   identified. Check for errors. */
+         symlist = astGrow( symlist, nsym + 1, sizeof( int ) );
+         if ( astOK ) {
+
+/* Append the symbol's index to the end of this list. */
+            symlist[ nsym++ ] = sym;
+         }
+      }
+   }
+
+/* If there has been no error, check the final context after
+   identifying all the symbols... */
+   if ( astOK ) {
+
+/* If an operand is still expected, then there is an unsatisfied
+   operator on the end of the expression, so report an error. */
+      if ( !opernext ) {
+         astError( AST__MIOPA,
+                   "%s(%s): Missing or invalid operand in the expression "
+                   "\"%s\".", status,
+                   method, class, exprs );
+
+/* If the final parenthesis level is positive, then there is a missing
+   right parenthesis, so report an error. */
+      } else if ( lpar > 0 ) {
+         astError( AST__MRPAR,
+                   "%s(%s): Missing right parenthesis in the expression "
+                   "\"%s\".", status,
+                   method, class, exprs );
+      }
+   }
+
+/* Sort the symbols into evaluation order to produce output opcodes. */
+   EvaluationSort( *con, nsym, symlist, code, stacksize, status );
+
+/* Free any memory used as workspace. */
+   if ( argcount ) argcount = astFree( argcount );
+   if ( opensym ) opensym = astFree( opensym );
+   if ( symlist ) symlist = astFree( symlist );
+
+/* If OK, re-allocate the "*con" array to have the correct size (since
+   astGrow may have over-allocated space). */
+   if ( astOK && *con ) {
+      *con = astRealloc( *con, sizeof( double ) * (size_t) ncon );
+   }
+
+/* If an error occurred, free any allocated memory and reset the
+   output values. */
+   if ( !astOK ) {
+      *code = astFree( *code );
+      *con = astFree( *con );
+      *stacksize = 0;
+   }
+}
+
+static void CompileMapping( const char *method, const char *class,
+                            int nin, int nout,
+                            int nfwd, const char *fwdfun[],
+                            int ninv, const char *invfun[],
+                            int ***fwdcode, int ***invcode,
+                            double ***fwdcon, double ***invcon,
+                            int *fwdstack, int *invstack, int *status ) {
+/*
+*  Name:
+*     CompileMapping
+
+*  Purpose:
+*     Compile the transformation functions for a MathMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void CompileMapping( const char *method, const char *class,
+*                          int nin, int nout,
+*                          int nfwd, const char *fwdfun[],
+*                          int ninv, const char *invfun[],
+*                          int ***fwdcode, int ***invcode,
+*                          double ***fwdcon, double ***invcon,
+*                          int *fwdstack, int *invstack, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This function checks and compiles the transformation functions required
+*     to create a MathMap. It produces sequences of operation codes (opcodes)
+*     and numerical constants which may subsequently be used to evaluate the
+*     functions on a push-down stack.
+
+*  Parameters:
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function.
+*        This method name is used solely for constructing error messages.
+*     class
+*        Pointer to a constant null-terminated character string containing the
+*        class name of the Object being processed. This name is used solely
+*        for constructing error messages.
+*     nin
+*        Number of input variables for the MathMap.
+*     nout
+*        Number of output variables for the MathMap.
+*     nfwd
+*        The number of forward transformation functions being supplied.
+*        This must be at least equal to "nout".
+*     fwdfun
+*        Pointer to an array, with "nfwd" elements, of pointers to null
+*        terminated strings which contain each of the forward transformation
+*        functions. These must be in lower case and should contain no white
+*        space.
+*     ninv
+*        The number of inverse transformation functions being supplied.
+*        This must be at least equal to "nin".
+*     invfun
+*        Pointer to an array, with "ninv" elements, of pointers to null
+*        terminated strings which contain each of the inverse transformation
+*        functions. These must be in lower case and should contain no white
+*        space.
+*     fwdcode
+*        Address in which to return a pointer to an array (with "nfwd"
+*        elements) of pointers to arrays of int containing the set of opcodes
+*        (cast to int) for each forward transformation function. The number
+*        of opcodes produced for each function is given by the first element
+*        of the opcode array.
+*
+*        Both the returned array of pointers, and the arrays of int to which
+*        they point, will be stored in dynamically allocated memory and should
+*        be freed by the caller (using astFree) when no longer required.
+*
+*        If the right hand sides (including the "=" sign) of all the supplied
+*        functions are absent, then this indicates an undefined transformation
+*        and the returned pointer value will be NULL. An error results if
+*        an "=" sign is present but no expression follows it.
+*     invcode
+*        Address in which to return a pointer to an array (with "ninv"
+*        elements) of pointers to arrays of int containing the set of opcodes
+*        (cast to int) for each inverse transformation function. The number
+*        of opcodes produced for each function is given by the first element
+*        of the opcode array.
+*
+*        Both the returned array of pointers, and the arrays of int to which
+*        they point, will be stored in dynamically allocated memory and should
+*        be freed by the caller (using astFree) when no longer required.
+*
+*        If the right hand sides (including the "=" sign) of all the supplied
+*        functions are absent, then this indicates an undefined transformation
+*        and the returned pointer value will be NULL. An error results if
+*        an "=" sign is present but no expression follows it.
+*     fwdcon
+*        Address in which to return a pointer to an array (with "nfwd"
+*        elements) of pointers to arrays of double containing the set of
+*        constants for each forward transformation function.
+*
+*        Both the returned array of pointers, and the arrays of double to which
+*        they point, will be stored in dynamically allocated memory and should
+*        be freed by the caller (using astFree) when no longer required. Note
+*        that any of the pointers to the arrays of double may be NULL if no
+*        constants are associated with a particular function.
+*
+*        If the forward transformation is undefined, then the returned pointer
+*        value will be NULL.
+*     invcon
+*        Address in which to return a pointer to an array (with "ninv"
+*        elements) of pointers to arrays of double containing the set of
+*        constants for each inverse transformation function.
+*
+*        Both the returned array of pointers, and the arrays of double to which
+*        they point, will be stored in dynamically allocated memory and should
+*        be freed by the caller (using astFree) when no longer required. Note
+*        that any of the pointers to the arrays of double may be NULL if no
+*        constants are associated with a particular function.
+*
+*        If the inverse transformation is undefined, then the returned pointer
+*        value will be NULL.
+*     fwdstack
+*        Pointer to an int in which to return the size of the push-down stack
+*        required to evaluate the forward transformation functions.
+*     invstack
+*        Pointer to an int in which to return the size of the push-down stack
+*        required to evaluate the inverse transformation functions.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - A value of NULL will be returned for the "*fwdcode", "*invcode",
+*     "*fwdcon" and "*invcon" pointers and a value of zero will be returned
+*     for the "*fwdstack" and "*invstack" values if this function is invoked
+*     with the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   char **exprs;                 /* Pointer to array of expressions */
+   char **var;                   /* Pointer to array of variable names */
+   const char **strings;         /* Pointer to temporary array of strings */
+   int ifun;                     /* Loop counter for functions */
+   int nvar;                     /* Number of variables to extract */
+   int stacksize;                /* Required stack size */
+
+/* Initialise. */
+   *fwdcode = NULL;
+   *invcode = NULL;
+   *fwdcon = NULL;
+   *invcon = NULL;
+   *fwdstack = 0;
+   *invstack = 0;
+   nvar = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Further initialisation. */
+   exprs = NULL;
+   var = NULL;
+
+/* Compile the forward transformation. */
+/* ----------------------------------- */
+/* Allocate space for an array of pointers to the functions from which
+   we will extract variable names. */
+   strings = astMalloc( sizeof( char * ) * (size_t) ( nin + nfwd ) );
+
+/* Fill the first elements of this array with pointers to the inverse
+   transformation functions ("nin" in number) which yield the final input
+   values. These will have the names of the input variables on their left
+   hand sides. */
+   if ( astOK ) {
+      nvar = 0;
+      for ( ifun = ninv - nin; ifun < ninv; ifun++ ) {
+         strings[ nvar++ ] = invfun[ ifun ];
+      }
+
+/* Fill the remaining elements of the array with pointers to the
+   forward transformation functions. These will have the names of any
+   intermediate variables plus the final output variables on their left
+   hand sides. */
+      for ( ifun = 0; ifun < nfwd; ifun++ ) strings[ nvar++ ] = fwdfun[ ifun ];
+
+/* Extract the variable names from the left hand sides of these
+   functions and check them for validity and absence of duplication. */
+      ExtractVariables( method, class, nvar, strings, nin, nout, nfwd, ninv, 1,
+                        &var, status );
+   }
+
+/* Free the temporary array of string pointers. */
+   strings = astFree( strings );
+
+/* Extract the expressions from the right hand sides of the forward
+   transformation functions. */
+   ExtractExpressions( method, class, nfwd, fwdfun, 1, &exprs, status );
+
+/* If OK, and the forward transformation is defined, then allocate and
+   initialise space for an array of pointers to the opcodes for each
+   expression and, similarly, for the constants for each expression. */
+   if ( astOK && exprs ) {
+      MALLOC_POINTER_ARRAY( *fwdcode, int *, nfwd )
+      MALLOC_POINTER_ARRAY( *fwdcon, double *, nfwd )
+
+/* If OK, loop to compile each of the expressions, storing pointers to
+   the resulting opcodes and constants in the arrays allocated above. On
+   each loop, we make progressively more of the variable names in "var"
+   visible to the compilation function. This ensures that each expression
+   can only use variables which have been defined earlier. */
+      if ( astOK ) {
+         for ( ifun = 0; ifun < nfwd; ifun++ ) {
+            CompileExpression( method, class, exprs[ ifun ],
+                               nin + ifun, (const char **) var,
+                               &( *fwdcode )[ ifun ], &( *fwdcon )[ ifun ],
+                               &stacksize, status );
+
+/* If an error occurs, then report contextual information and quit. */
+            if ( !astOK ) {
+               astError( astStatus,
+                         "Error in forward transformation function %d.", status,
+                         ifun + 1 );
+               break;
+            }
+
+/* If OK, calculate the maximum evaluation stack size required by any
+   of the expressions. */
+            *fwdstack = ( *fwdstack > stacksize ) ? *fwdstack : stacksize;
+         }
+      }
+   }
+
+/* Free the memory containing the extracted expressions and variables. */
+   FREE_POINTER_ARRAY( exprs, nfwd )
+   FREE_POINTER_ARRAY( var, nvar )
+
+/* Compile the inverse transformation. */
+/* ----------------------------------- */
+/* Allocate space for an array of pointers to the functions from which
+   we will extract variable names. */
+   strings = astMalloc( sizeof( char * ) * (size_t) ( nout + ninv ) );
+
+/* Fill the first elements of this array with pointers to the forward
+   transformation functions ("nout" in number) which yield the final
+   output values. These will have the names of the output variables on
+   their left hand sides. */
+   if ( astOK ) {
+      nvar = 0;
+      for ( ifun = nfwd - nout; ifun < nfwd; ifun++ ) {
+         strings[ nvar++ ] = fwdfun[ ifun ];
+      }
+
+/* Fill the remaining elements of the array with pointers to the
+   inverse transformation functions. These will have the names of any
+   intermediate variables plus the final input variables on their left
+   hand sides. */
+      for ( ifun = 0; ifun < ninv; ifun++ ) strings[ nvar++ ] = invfun[ ifun ];
+
+/* Extract the variable names from the left hand sides of these
+   functions and check them for validity and absence of duplication. */
+      ExtractVariables( method, class, nvar, strings, nin, nout, nfwd, ninv, 0,
+                        &var, status );
+   }
+
+/* Free the temporary array of string pointers. */
+   strings = astFree( strings );
+
+/* Extract the expressions from the right hand sides of the inverse
+   transformation functions. */
+   ExtractExpressions( method, class, ninv, invfun, 0, &exprs, status );
+
+/* If OK, and the forward transformation is defined, then allocate and
+   initialise space for an array of pointers to the opcodes for each
+   expression and, similarly, for the constants for each expression. */
+   if ( astOK && exprs ) {
+      MALLOC_POINTER_ARRAY( *invcode, int *, ninv )
+      MALLOC_POINTER_ARRAY( *invcon, double *, ninv )
+
+/* If OK, loop to compile each of the expressions, storing pointers to
+   the resulting opcodes and constants in the arrays allocated above. On
+   each loop, we make progressively more of the variable names in "var"
+   visible to the compilation function. This ensures that each expression
+   can only use variables which have been defined earlier. */
+      if ( astOK ) {
+         for ( ifun = 0; ifun < ninv; ifun++ ) {
+            CompileExpression( method, class, exprs[ ifun ],
+                               nout + ifun, (const char **) var,
+                               &( *invcode )[ ifun ], &( *invcon )[ ifun ],
+                               &stacksize, status );
+
+/* If an error occurs, then report contextual information and quit. */
+            if ( !astOK ) {
+               astError( astStatus,
+                         "Error in inverse transformation function %d.", status,
+                         ifun + 1 );
+               break;
+            }
+
+/* If OK, calculate the maximum evaluation stack size required by any
+   of the expressions. */
+            *invstack = ( *invstack > stacksize ) ? *invstack : stacksize;
+         }
+      }
+   }
+
+/* Free the memory containing the extracted expressions and variables. */
+   FREE_POINTER_ARRAY( exprs, ninv )
+   FREE_POINTER_ARRAY( var, nvar )
+
+/* If an error occurred, then free all remaining allocated memory and
+   reset the output values. */
+   if ( !astOK ) {
+      FREE_POINTER_ARRAY( *fwdcode, nfwd )
+      FREE_POINTER_ARRAY( *invcode, ninv )
+      FREE_POINTER_ARRAY( *fwdcon, nfwd )
+      FREE_POINTER_ARRAY( *invcon, ninv )
+      *fwdstack = 0;
+      *invstack = 0;
+   }
+}
+
+static int DefaultSeed( const Rcontext *context, int *status ) {
+/*
+*  Name:
+*     DefaultSeed
+
+*  Purpose:
+*     Generate an unpredictable seed for a random number generator.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     int DefaultSeed( Rcontext *context, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     On each invocation this function returns an integer value which is
+*     highly unpredictable. This value may be used as a default seed for the
+*     random number generator associated with a MathMap, so that it
+*     generates a different sequence on each occasion.
+
+*  Parameters:
+*     context
+*        Pointer to the random number generator context associated with
+*        the MathMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The unpredictable integer.
+
+*  Notes:
+*     - This function does not perform error checking and will execute even
+*     if the global error status is set.
+*/
+
+/* Local Constants: */
+   const int nwarm = 5;          /* Number of warm-up iterations */
+   const long int a = 8121L;     /* Constants for random number generator... */
+   const long int c = 28411L;
+   const long int m = 134456L;
+
+/* Local Variables; */
+   int iwarm;                    /* Loop counter for warm-up iterations */
+   static long init = 0;         /* Local initialisation performed? */
+   static long int rand;         /* Local random integer */
+   unsigned long int bits;       /* Bit pattern for producing result */
+
+/* On the first invocation, initialise a local random number generator
+   to a value derived by combining bit patterns obtained from the system
+   clock and the processor time used. The result needs to be positive and
+   lie in the range 0 to "m-1". */
+   LOCK_MUTEX5
+   if ( !init ) {
+      rand = (long int) ( ( (unsigned long int) time( NULL ) ^
+                            (unsigned long int) clock() ) %
+                          (unsigned long int) m );
+
+/* These values will typically only change in their least significant
+   bits between programs run successively, but by using the bit pattern
+   as a seed, we ensure that these differences are rapidly propagated to
+   other bits. To hasten this process, we "warm up" the local generator
+   with a few iterations. This is a quick and dirty generator using
+   constants from Press et al. (Numerical recipes). */
+      for ( iwarm = 0; iwarm < nwarm; iwarm++ ) {
+         rand = ( rand * a + c ) % m;
+      }
+
+/* Note that this initialisation has been performed. */
+      init = 1;
+   }
+   UNLOCK_MUTEX5
+
+/* Generate a new bit pattern from the system time. Apart from the
+   first invocation, this will be a different time to that used above. */
+   bits = (unsigned long int) time( NULL );
+
+/* Mask in a pattern derived from the CPU time used. */
+   bits ^= (unsigned long int) clock();
+
+/* The system time may change quite slowly (e.g. every second), so
+   also mask in the address of the random number generator context
+   supplied. This makes the seed depend on which MathMap is in use. */
+   bits ^= (unsigned long int) context;
+
+/* Now mask in the last random integer produced by the random number
+   generator whose context has been supplied. This makes the seed depend
+   on the MathMap's past use of random numbers. */
+   bits ^= (unsigned long int) context->random_int;
+
+/* Finally, in order to produce different seeds when this function is
+   invoked twice in rapid succession on the same object (with no
+   intermediate processing), we also mask in a pseudo-random value
+   generated here. Generate the next local random integer. */
+   rand = ( rand * a + c ) % m;
+
+/* We then scale this value to give an integer in the range 0 to
+   ULONG_MAX and mask the corresponding bit pattern into our seed. */
+   bits ^= (unsigned long int) ( ( (double) rand / (double) ( m - 1UL ) ) *
+                                 ( ( (double) ULONG_MAX + 1.0 ) *
+                                   ( 1.0 - DBL_EPSILON ) ) );
+
+/* Return the integer value of the seed (which may involve discarding
+   some unwanted bits). */
+   return (int) bits;
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two MathMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     MathMap member function (over-rides the astEqual protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two MathMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a MathMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the MathMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - The two MathMaps are considered equivalent if the combination of
+*     the first in series with the inverse of the second simplifies to a
+*     UnitMap.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMathMap *that;          /* Pointer to the second MathMap structure */
+   AstMathMap *this;          /* Pointer to the first MathMap structure */
+   double **that_con;         /* Lists of constants from "that" */
+   double **this_con;         /* Lists of constants from "this" */
+   int **that_code;           /* Lists of opcodes from "that" */
+   int **this_code;           /* Lists of opcodes from "this" */
+   int code;                  /* Opcode value */
+   int icode;                 /* Opcode index */
+   int icon;                  /* Constant index */
+   int ifun;                  /* Function index */
+   int ncode;                 /* No. of opcodes for current "this" function */
+   int ncode_that;            /* No. of opcodes for current "that" function */
+   int nin;                   /* Number of inputs */
+   int nout;                  /* Number of outputs */
+   int pass;                  /* Check fwd or inv */
+   int result;                /* Result value to return */
+   int that_nfun;             /* Number of functions from "that" */
+   int this_nfun;             /* Number of functions from "this" */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two MathMap structures. */
+   this = (AstMathMap *) this_object;
+   that = (AstMathMap *) that_object;
+
+/* Check the second object is a MathMap. We know the first is a
+   MathMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAMathMap( that ) ) {
+
+/* Check they have the same number of inputs and outputs */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNout( that ) == nout && astGetNin( that ) == nin ) {
+
+/* Assume equality. */
+         result = 1;
+
+/* The first pass through this next loop compares forward functions, and
+   the second pass compares inverse functions. */
+         for( pass = 0; pass < 2 && result; pass++ ) {
+
+/* On the first pass, get pointers to the lists of opcodes and constants for
+   the effective forward transformations (taking into account the value
+   of the Invert attribute), together with the number of such functions. */
+            if( pass == 0 ) {
+               if( !astGetInvert( this ) ) {
+                  this_code = this->fwdcode;
+                  this_con = this->fwdcon;
+                  this_nfun = this->nfwd;
+               } else {
+                  this_code = this->invcode;
+                  this_con = this->invcon;
+                  this_nfun = this->ninv;
+               }
+
+               if( !astGetInvert( that ) ) {
+                  that_code = that->fwdcode;
+                  that_con = that->fwdcon;
+                  that_nfun = that->nfwd;
+               } else {
+                  that_code = that->invcode;
+                  that_con = that->invcon;
+                  that_nfun = that->ninv;
+               }
+
+/* On the second pass, get pointers to the lists of opcodes and constants for
+   the effective inverse transformations, together with the number of such
+   functions. */
+            } else {
+
+               if( astGetInvert( this ) ) {
+                  this_code = this->fwdcode;
+                  this_con = this->fwdcon;
+                  this_nfun = this->nfwd;
+               } else {
+                  this_code = this->invcode;
+                  this_con = this->invcon;
+                  this_nfun = this->ninv;
+               }
+
+               if( astGetInvert( that ) ) {
+                  that_code = that->fwdcode;
+                  that_con = that->fwdcon;
+                  that_nfun = that->nfwd;
+               } else {
+                  that_code = that->invcode;
+                  that_con = that->invcon;
+                  that_nfun = that->ninv;
+               }
+            }
+
+/* Check that "this" and "that" have the same number of functions */
+            if( that_nfun != this_nfun ) result = 0;
+
+/* Loop round each function. */
+            for( ifun = 0; ifun < this_nfun && result; ifun++ ) {
+
+/* The first element in the opcode array is the number of subsequent
+   opcodes. Obtain and compare these counts. */
+               ncode = this_code ? this_code[ ifun ][ 0 ] : 0;
+               ncode_that = that_code ? that_code[ ifun ][ 0 ] : 0;
+               if( ncode != ncode_that ) result = 0;
+
+/* Compare the following opcodes. Some opcodes consume constants from the
+   list of constants associated with the MathMap. Compare the constants
+   for such opcodes. */
+               icon = 0;
+               for( icode = 0; icode < ncode && result; icode++ ){
+                  code = this_code[ ifun ][ icode ];
+                  if( that_code[ ifun ][ icode ] != code ) {
+                     result = 0;
+
+                  } else if( code == OP_LDCON ||
+                             code == OP_LDVAR ||
+                             code == OP_MAX ||
+                             code == OP_MIN ) {
+
+                     if( this_con[ ifun ][ icon ] !=
+                         that_con[ ifun ][ icon ] ) {
+                        result = 0;
+                     } else {
+                        icon++;
+                     }
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static void EvaluateFunction( Rcontext *rcontext, int npoint,
+                              const double **ptr_in, const int *code,
+                              const double *con, int stacksize, double *out, int *status ) {
+/*
+*  Name:
+*     EvaluateFunction
+
+*  Purpose:
+*     Evaluate a compiled function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void EvaluateFunction( Rcontext *rcontext, int npoint,
+*                            const double **ptr_in, const int *code,
+*                            const double *con, int stacksize, double *out, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This function implements a "virtual machine" which executes operations
+*     on an arithmetic stack in order to evaluate transformation functions.
+*     Each operation is specified by an input operation code (opcode) and
+*     results in the execution of a vector operation on a stack. The final
+*     result, after executing all the supplied opcodes, is returned as a
+*     vector.
+*
+*     This function detects arithmetic errors (such as overflow and division
+*     by zero) and propagates any "bad" coordinate values, including those
+*     present in the input, to the output.
+
+*  Parameters:
+*     npoint
+*        The number of points to be transformd (i.e. the size of the vector
+*        of values on which operations are to be performed).
+*     ptr_in
+*        Pointer to an array of pointers to arrays of double (with "npoint"
+*        elements). These arrays should contain the input coordinate values,
+*        such that coordinate number "coord" for point number "point" can be
+*        found in "ptr_in[coord][point]".
+*     code
+*        Pointer to an array of int containing the set of opcodes (cast to int)
+*        for the operations to be performed. The first element of this array
+*        should contain a count of the number of opcodes which follow.
+*     con
+*        Pointer to an array of double containing the set of constants required
+*        to evaluate the function (this may be NULL if no constants are
+*        required).
+*     stacksize
+*        The size of the stack required to evaluate the expression using the
+*        opcodes and constants supplied. This value should be calculated during
+*        expression compilation.
+*     out
+*        Pointer to an array of double (with "npoint" elements) in which to
+*        return the vector of result values.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+   const int bits =              /* Number of bits in an unsigned long */
+      sizeof( unsigned long ) * CHAR_BIT;
+   const double eps =            /* Smallest number subtractable from 2.0 */
+      2.0 * DBL_EPSILON;
+   const double scale =          /* 2.0 raised to the power "bits" */
+      ldexp( 1.0, bits );
+   const double scale1 =         /* 2.0 raised to the power "bits-1" */
+      scale * 0.5;
+   const double rscale =         /* Reciprocal scale factor */
+      1.0 / scale;
+   const double rscale1 =        /* Reciprocal initial scale factor */
+      1.0 / scale1;
+   const int nblock =            /* Number of blocks of bits to process */
+      ( sizeof( double ) + sizeof( unsigned long ) - 1 ) /
+      sizeof( unsigned long );
+   const unsigned long signbit = /* Mask for extracting sign bit */
+      1UL << ( bits - 1 );
+
+/* Local Variables: */
+   double **stack;               /* Array of pointers to stack elements */
+   double *work;                 /* Pointer to stack workspace */
+   double *xv1;                  /* Pointer to first argument vector */
+   double *xv2;                  /* Pointer to second argument vector */
+   double *xv3;                  /* Pointer to third argument vector */
+   double *xv;                   /* Pointer to sole argument vector */
+   double *y;                    /* Pointer to result */
+   double *yv;                   /* Pointer to result vector */
+   double abs1;                  /* Absolute value (temporary variable) */
+   double abs2;                  /* Absolute value (temporary variable) */
+   double frac1;                 /* First (maybe normalised) fraction */
+   double frac2;                 /* Second (maybe normalised) fraction */
+   double frac;                  /* Sole normalised fraction */
+   double newexp;                /* New power of 2 exponent value */
+   double ran;                   /* Random number */
+   double result;                /* Function result value */
+   double unscale;               /* Factor for removing scaling */
+   double value;                 /* Value to be assigned to stack vector */
+   double x1;                    /* First argument value */
+   double x2;                    /* Second argument value */
+   double x3;                    /* Third argument value */
+   double x;                     /* Sole argument value */
+   int expon1;                   /* First power of 2 exponent */
+   int expon2;                   /* Second power of 2 exponent */
+   int expon;                    /* Sole power of 2 exponent */
+   int iarg;                     /* Loop counter for arguments */
+   int iblock;                   /* Loop counter for blocks of bits */
+   int icode;                    /* Opcode value */
+   int icon;                     /* Counter for number of constants used */
+   int istk;                     /* Loop counter for stack elements */
+   int ivar;                     /* Input variable number */
+   int narg;                     /* Number of function arguments */
+   int ncode;                    /* Number of opcodes to process */
+   int point;                    /* Loop counter for stack vector elements */
+   int sign;                     /* Argument is non-negative? */
+   int tos;                      /* Top of stack index */
+   static double d2r;            /* Degrees to radians conversion factor */
+   static double log2;           /* Natural logarithm of 2.0 */
+   static double pi;             /* Value of PI */
+   static double r2d;            /* Radians to degrees conversion factor */
+   static double rsafe_sq;       /* Reciprocal of "safe_sq" */
+   static double safe_sq;        /* Huge value that can safely be squared */
+   static int init = 0;          /* Initialisation performed? */
+   unsigned long b1;             /* Block of bits from first argument */
+   unsigned long b2;             /* Block of bits from second argument */
+   unsigned long b;              /* Block of bits for result */
+   unsigned long neg;            /* Result is negative? (sign bit) */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If this is the first invocation of this function, then initialise
+   constant values. */
+   LOCK_MUTEX2
+   if ( !init ) {
+
+/* Trigonometrical conversion factors. */
+      pi = acos( -1.0 );
+      r2d = 180.0 / pi;
+      d2r = pi / 180.0;
+
+/* Natural logarithm of 2.0. */
+      log2 = log( 2.0 );
+
+/* This value must be safe to square without producing overflow, yet
+   large enough that adding or subtracting 1.0 from the square makes no
+   difference. We also need its reciprocal. */
+      safe_sq = 0.9 * sqrt( DBL_MAX );
+      rsafe_sq = 1.0 / safe_sq;
+
+/* Note that initialisation has been performed. */
+      init = 1;
+   }
+   UNLOCK_MUTEX2
+
+/* Allocate space for an array of pointers to elements of the
+   workspace stack (each stack element being an array of double). */
+   stack = astMalloc( sizeof( double * ) * (size_t) stacksize );
+
+/* Allocate space for the stack itself. */
+   work = astMalloc( sizeof( double ) *
+                     (size_t) ( npoint * ( stacksize - 1 ) ) );
+
+/* If OK, then initialise the stack pointer array to identify the
+   start of each vector on the stack. The first element points at the
+   output array (in which the result will be accumulated), while other
+   elements point at successive vectors within the workspace allocated
+   above. */
+   if ( astOK ) {
+      stack[ 0 ] = out;
+      for ( istk = 1; istk < stacksize; istk++ ) {
+         stack[ istk ] = work + ( istk - 1 ) * npoint;
+      }
+
+/* Define stack operations. */
+/* ======================== */
+/* We now define a set of macros for performing vector operations on
+   elements of the stack. Each is in the form of a "case" block for
+   execution in response to the appropriate operation code (opcode). */
+
+/* Zero-argument operation. */
+/* ------------------------ */
+/* This macro performs a zero-argument operation, which results in the
+   insertion of a new vector on to the stack. */
+#define ARG_0(oper,setup,function) \
+\
+/* Test for the required opcode value. */ \
+   case oper: \
+\
+/* Perform any required initialisation. */ \
+      {setup;} \
+\
+/* Increment the top of stack index and obtain a pointer to the new stack \
+   element (vector). */ \
+      yv = stack[ ++tos ]; \
+\
+/* Loop to access each vector element, obtaining a pointer to it. */ \
+      for ( point = 0; point < npoint; point++ ) { \
+         y = yv + point; \
+\
+/* Perform the processing, which results in assignment to this element. */ \
+         {function;} \
+      } \
+\
+/* Break out of the "case" block. */ \
+      break;
+
+/* One-argument operation. */
+/* ----------------------- */
+/* This macro performs a one-argument operation, which processes the
+   top stack element without changing the stack size. */
+#define ARG_1(oper,function) \
+\
+/* Test for the required opcode value. */ \
+   case oper: \
+\
+/* Obtain a pointer to the top stack element (vector). */ \
+      xv = stack[ tos ]; \
+\
+/* Loop to access each vector element, obtaining its value and \
+   checking that it is not bad. */ \
+      for ( point = 0; point < npoint; point++ ) { \
+         if ( ( x = xv[ point ] ) != AST__BAD ) { \
+\
+/* Also obtain a pointer to the element. */ \
+            y = xv + point; \
+\
+/* Perform the processing, which uses the element's value and then \
+   assigns the result to this element. */ \
+            {function;} \
+         } \
+      } \
+\
+/* Break out of the "case" block. */ \
+      break;
+
+/* One-argument boolean operation. */
+/* ------------------------------- */
+/* This macro is similar in function to ARG_1 above, except that no
+   checks are made for bad argument values. It is intended for use with
+   boolean functions where bad values are handled explicitly. */
+#define ARG_1B(oper,function) \
+\
+/* Test for the required opcode value. */ \
+   case oper: \
+\
+/* Obtain a pointer to the top stack element (vector). */ \
+      xv = stack[ tos ]; \
+\
+/* Loop to access each vector element, obtaining the argument value \
+   and a pointer to the element. */ \
+      for ( point = 0; point < npoint; point++ ) { \
+         x = xv[ point ]; \
+         y = xv + point; \
+\
+/* Perform the processing, which uses the element's value and then \
+   assigns the result to this element. */ \
+         {function;} \
+      } \
+\
+/* Break out of the "case" block. */ \
+      break;
+
+/* Two-argument operation. */
+/* ----------------------- */
+/* This macro performs a two-argument operation, which processes the
+   top two stack elements and produces a single result, resulting in the
+   stack size decreasing by one. In this case, we first define a macro
+   without the "case" block statements present. */
+#define DO_ARG_2(function) \
+\
+/* Obtain pointers to the top two stack elements (vectors), decreasing \
+   the top of stack index by one. */ \
+      xv2 = stack[ tos-- ]; \
+      xv1 = stack[ tos ]; \
+\
+/* Loop to access each vector element, obtaining the value of the \
+   first argument and checking that it is not bad. */ \
+      for ( point = 0; point < npoint; point++ ) { \
+         if ( ( x1 = xv1[ point ] ) != AST__BAD ) { \
+\
+/* Also obtain a pointer to the element which is to receive the \
+   result. */ \
+            y = xv1 + point; \
+\
+/* Obtain the value of the second argument, again checking that it is \
+   not bad. */ \
+            if ( ( x2 = xv2[ point ] ) != AST__BAD ) { \
+\
+/* Perform the processing, which uses the two argument values and then \
+   assigns the result to the appropriate top of stack element. */ \
+               {function;} \
+\
+/* If the second argument was bad, so is the result. */ \
+            } else { \
+               *y = AST__BAD; \
+            } \
+         } \
+      }
+
+/* This macro simply wraps the one above up in a "case" block. */
+#define ARG_2(oper,function) \
+   case oper: \
+      DO_ARG_2(function) \
+      break;
+
+/* Two-argument boolean operation. */
+/* ------------------------------- */
+/* This macro is similar in function to ARG_2 above, except that no
+   checks are made for bad argument values. It is intended for use with
+   boolean functions where bad values are handled explicitly. */
+#define ARG_2B(oper,function) \
+\
+/* Test for the required opcode value. */ \
+   case oper: \
+\
+/* Obtain pointers to the top two stack elements (vectors), decreasing \
+   the top of stack index by one. */ \
+      xv2 = stack[ tos-- ]; \
+      xv1 = stack[ tos ]; \
+\
+/* Loop to access each vector element, obtaining the value of both \
+   arguments and a pointer to the element which is to receive the \
+   result. */ \
+      for ( point = 0; point < npoint; point++ ) { \
+         x1 = xv1[ point ]; \
+         x2 = xv2[ point ]; \
+         y = xv1 + point; \
+\
+/* Perform the processing, which uses the two argument values and then \
+   assigns the result to the appropriate top of stack element. */ \
+         {function;} \
+      } \
+\
+/* Break out of the "case" block. */ \
+      break;
+
+/* Three-argument boolean operation. */
+/* --------------------------------- */
+/* This macro is similar in function to ARG_2B above, except that it
+   takes three values of the stack and puts one back. It performs no
+   checks for bad values. */
+#define ARG_3B(oper,function) \
+\
+/* Test for the required opcode value. */ \
+   case oper: \
+\
+/* Obtain pointers to the top three stack elements (vectors), decreasing \
+   the top of stack index by two. */ \
+      xv3 = stack[ tos-- ]; \
+      xv2 = stack[ tos-- ]; \
+      xv1 = stack[ tos ]; \
+\
+/* Loop to access each vector element, obtaining the value of all 3 \
+   arguments and a pointer to the element which is to receive the \
+   result. */ \
+      for ( point = 0; point < npoint; point++ ) { \
+         x1 = xv1[ point ]; \
+         x2 = xv2[ point ]; \
+         x3 = xv3[ point ]; \
+         y = xv1 + point; \
+\
+/* Perform the processing, which uses the three argument values and then \
+   assigns the result to the appropriate top of stack element. */ \
+         {function;} \
+      } \
+\
+/* Break out of the "case" block. */ \
+      break;
+
+/* Define arithmetic operations. */
+/* ============================= */
+/* We now define macros for performing some of the arithmetic
+   operations we will require in a "safe" way - i.e. trapping numerical
+   problems such as overflow and invalid arguments and translating them
+   into the AST__BAD value. */
+
+/* Absolute value. */
+/* --------------- */
+/* This is just shorthand. */
+#define ABS(x) ( ( (x) >= 0.0 ) ? (x) : -(x) )
+
+/* Integer part. */
+/* ------------- */
+/* This implements rounding towards zero without involving conversion
+   to an integer (which could overflow). */
+#define INT(x) ( ( (x) >= 0.0 ) ? floor( (x) ) : ceil( (x) ) )
+
+/* Trap maths overflow. */
+/* -------------------- */
+/* This macro calls a C maths library function and checks for overflow
+   in the result. */
+#define CATCH_MATHS_OVERFLOW(function) \
+   ( \
+\
+/* Clear the "errno" value. */ \
+      errno = 0, \
+\
+/* Evaluate the function. */ \
+      result = (function), \
+\
+/* Check if "errno" and the returned result indicate overflow and \
+   return the appropriate result. */ \
+      ( ( errno == ERANGE ) && ( ABS( result ) == HUGE_VAL ) ) ? AST__BAD : \
+                                                                 result \
+   )
+
+/* Trap maths errors. */
+/* ------------------ */
+/* This macro is similar to the one above, except that it also checks
+   for domain errors (i.e. invalid argument values). */
+#define CATCH_MATHS_ERROR(function) \
+   ( \
+\
+/* Clear the "errno" value. */ \
+      errno = 0, \
+\
+/* Evaluate the function. */ \
+      result = (function), \
+\
+/* Check if "errno" and the returned result indicate a domain error or \
+   overflow and return the appropriate result. */ \
+      ( ( errno == EDOM ) || \
+        ( ( errno == ERANGE ) && ( ABS( result ) == HUGE_VAL ) ) ) ? \
+                                 AST__BAD : result \
+   )
+
+/* Tri-state boolean OR. */
+/* --------------------- */
+/* This evaluates a boolean OR using tri-state logic. For example,
+   "a||b" may evaluate to 1 if "a" is bad but "b" is non-zero, so that
+   the normal rules of bad value propagation do not apply. */
+#define TRISTATE_OR(x1,x2) \
+\
+/* Test if the first argument is bad. */ \
+   ( (x1) == AST__BAD ) ? ( \
+\
+/* If so, test the second argument. */ \
+      ( ( (x2) == 0.0 ) || ( (x2) == AST__BAD ) ) ? AST__BAD : 1.0 \
+   ) : ( \
+\
+/* Test if the second argument is bad. */ \
+      ( (x2) == AST__BAD ) ? ( \
+\
+/* If so, test the first argument. */ \
+         ( (x1) == 0.0 ) ? AST__BAD : 1.0 \
+\
+/* If neither argument is bad, use the normal OR operator. */ \
+      ) : ( \
+         ( (x1) != 0.0 ) || ( (x2) != 0.0 ) \
+      ) \
+   )
+
+/* Tri-state boolean AND. */
+/* ---------------------- */
+/* This evaluates a boolean AND using tri-state logic. */
+#define TRISTATE_AND(x1,x2) \
+\
+/* Test if the first argument is bad. */ \
+   ( (x1) == AST__BAD ) ? ( \
+\
+/* If so, test the second argument. */ \
+      ( (x2) != 0.0 ) ? AST__BAD : 0.0 \
+   ) : ( \
+\
+/* Test if the second argument is bad. */ \
+      ( (x2) == AST__BAD ) ? ( \
+\
+/* If so, test the first argument. */ \
+         ( (x1) != 0.0 ) ? AST__BAD : 0.0 \
+\
+/* If neither argument is bad, use the normal AND operator. */ \
+      ) : ( \
+         ( (x1) != 0.0 ) && ( (x2) != 0.0 ) \
+      ) \
+   )
+
+/* Safe addition. */
+/* -------------- */
+/* This macro performs addition while avoiding possible overflow. */
+#define SAFE_ADD(x1,x2) ( \
+\
+/* Test if the first argument is non-negative. */ \
+   ( (x1) >= 0.0 ) ? ( \
+\
+/* If so, then we can perform addition if the second argument is \
+   non-positive. Otherwise, we must calculate the most positive safe \
+   second argument value that can be added and test for this (the test \
+   itself is safe against overflow). */ \
+      ( ( (x2) <= 0.0 ) || ( ( (DBL_MAX) - (x1) ) >= (x2) ) ) ? ( \
+\
+/* Perform addition if it is safe, otherwise return AST__BAD. */ \
+         (x1) + (x2) \
+      ) : ( \
+         AST__BAD \
+      ) \
+\
+/* If the first argument is negative, then we can perform addition if \
+   the second argument is non-negative. Otherwise, we must calculate the \
+   most negative second argument value that can be added and test for \
+   this (the test itself is safe against overflow). */ \
+   ) : ( \
+      ( ( (x2) >= 0.0 ) || ( ( (DBL_MAX) + (x1) ) >= -(x2) ) ) ? ( \
+\
+/* Perform addition if it is safe, otherwise return AST__BAD. */ \
+         (x1) + (x2) \
+      ) : ( \
+         AST__BAD \
+      ) \
+   ) \
+)
+
+/* Safe subtraction. */
+/* ----------------- */
+/* This macro performs subtraction while avoiding possible overflow. */
+#define SAFE_SUB(x1,x2) ( \
+\
+/* Test if the first argument is non-negative. */ \
+   ( (x1) >= 0.0 ) ? ( \
+\
+/* If so, then we can perform subtraction if the second argument is \
+   also non-negative. Otherwise, we must calculate the most negative safe \
+   second argument value that can be subtracted and test for this (the \
+   test itself is safe against overflow). */ \
+      ( ( (x2) >= 0.0 ) || ( ( (DBL_MAX) - (x1) ) >= -(x2) ) ) ? ( \
+\
+/* Perform subtraction if it is safe, otherwise return AST__BAD. */ \
+         (x1) - (x2) \
+      ) : ( \
+         AST__BAD \
+      ) \
+\
+/* If the first argument is negative, then we can perform subtraction \
+   if the second argument is non-positive. Otherwise, we must calculate \
+   the most positive second argument value that can be subtracted and \
+   test for this (the test itself is safe against overflow). */ \
+   ) : ( \
+      ( ( (x2) <= 0.0 ) || ( ( (DBL_MAX) + (x1) ) >= (x2) ) ) ? ( \
+\
+/* Perform subtraction if it is safe, otherwise return AST__BAD. */ \
+         (x1) - (x2) \
+      ) : ( \
+         AST__BAD \
+      ) \
+   ) \
+)
+
+/* Safe multiplication. */
+/* -------------------- */
+/* This macro performs multiplication while avoiding possible overflow. */
+#define SAFE_MUL(x1,x2) ( \
+\
+/* Multiplication is safe if the absolute value of either argument is \
+   unity or less. Otherwise, we must use the first argument to calculate \
+   the maximum absolute value that the second argument may have and test \
+   for this (the test itself is safe against overflow). */ \
+   ( ( ( abs1 = ABS( (x1) ) ) <= 1.0 ) || \
+     ( ( abs2 = ABS( (x2) ) ) <= 1.0 ) || \
+     ( ( (DBL_MAX) / abs1 ) >= abs2 ) ) ? ( \
+\
+/* Perform multiplication if it is safe, otherwise return AST__BAD. */ \
+      (x1) * (x2) \
+   ) : ( \
+      AST__BAD \
+   ) \
+)
+
+/* Safe division. */
+/* -------------- */
+/* This macro performs division while avoiding possible overflow. */
+#define SAFE_DIV(x1,x2) ( \
+\
+/* Division is unsafe if the second argument is zero. Otherwise, it is \
+   safe if the abolute value of the second argument is unity or \
+   more. Otherwise, we must use the second argument to calculate the \
+   maximum absolute value that the first argument may have and test for \
+   this (the test itself is safe against overflow). */ \
+   ( ( (x2) != 0.0 ) && \
+     ( ( ( abs2 = ABS( (x2) ) ) >= 1.0 ) || \
+       ( ( (DBL_MAX) * abs2 ) >= ABS( (x1) ) ) ) ) ? ( \
+\
+/* Perform division if it is safe, otherwise return AST__BAD. */ \
+      (x1) / (x2) \
+   ) : ( \
+      AST__BAD \
+   ) \
+)
+
+/* Bit-shift operation. */
+/* -------------------- */
+/* This macro shifts the bits in a double value a specified number of
+   places to the left, which simply corresponds to multiplying by the
+   appropriate power of two. */
+#define SHIFT_BITS(x1,x2) ( \
+\
+/* Decompose the value into a normalised fraction and a power of 2. */ \
+   frac = frexp( (x1), &expon ), \
+\
+/* Calculate the new power of 2 which should apply after the shift, \
+   rounding towards zero to give an integer value. */ \
+   newexp = INT( (x2) ) + (double) expon, \
+\
+/* If the new exponent is too negative to convert to an integer, then \
+   the result must underflow to zero. */ \
+   ( newexp < (double) -INT_MAX ) ? ( \
+      0.0 \
+\
+/* Otherwise, if it is too positive to convert to an integer, then the \
+   result must overflow, unless the normalised fraction is zero. */ \
+   ) : ( ( newexp > (double) INT_MAX ) ? ( \
+      ( frac == 0.0 ) ? 0.0 : AST__BAD \
+\
+/* Otherwise, convert the new exponent to an integer and apply \
+   it. Trap any overflow which may still occur. */ \
+   ) : ( \
+      CATCH_MATHS_OVERFLOW( ldexp( frac, (int) newexp ) ) \
+   ) ) \
+)
+
+/* Two-argument bit-wise boolean operation. */
+/* ---------------------------------------- */
+/* This macro expands to code which performs a bit-wise boolean
+   operation on a pair of arguments and assigns the result to the
+   variable "result". It operates on floating point (double) values,
+   which are regarded as if they are fixed-point binary numbers with
+   negative values expressed in twos-complement notation. This means that
+   it delivers the same results for integer values as the normal
+   (integer) C bit-wise operations. However, it will also operate on the
+   fraction bits of floating point numbers. It also offers greater
+   precision (the first 53 or so significant bits of the result being
+   preserved for typical IEEE floating point implementations). */
+#define BIT_OPER(oper,x1,x2) \
+\
+/* Convert each argument to a normalised fraction in the range \
+   [0.5,1.0) and a power of two exponent, removing any sign \
+   information. */ \
+   frac1 = frexp( ABS( (x1) ), &expon1 ); \
+   frac2 = frexp( ABS( (x2) ), &expon2 ); \
+\
+/* Set "expon" to be the larger of the two exponents. If the two \
+   exponents are not equal, divide the fraction with the smaller exponent \
+   by 2 to the power of the exponent difference. This gives both \
+   fractions the same effective exponent (although one of them may no \
+   longer be normalised). Note that overflow is avoided because all \
+   numbers remain less than 1.0, but underflow may occur. */ \
+   expon = expon1; \
+   if ( expon2 > expon1 ) { \
+      expon = expon2; \
+      frac1 = ldexp( frac1, expon1 - expon ); \
+   } else if ( expon1 > expon2 ) { \
+      frac2 = ldexp( frac2, expon2 - expon ); \
+   } \
+\
+/* If either of the original arguments is negative, we now subtract \
+   the corresponding fraction from 2.0. If we think of the fraction as \
+   represented in fixed-point binary notation, this corresponds to \
+   converting negative numbers into the twos-complement form normally used \
+   for integers (the sign bit being the bit with value 1) instead \
+   of having a separate sign bit as for floating point numbers. \
+\
+   Note that one of the fractions may have underflowed during the \
+   scaling above. In that case (if the original argument was negative), \
+   we must subtract the value "eps" (= 2.0 * DBL_EPSILON) from 2.0 \
+   instead, so that we produce the largest number less than 2.0. In \
+   twos-complement notation this represents the smallest possible \
+   negative number and corresponds to extending the sign bit of the \
+   original number up into more significant bits. This causes all bits to \
+   be set as we require (rather than all being clear if the underflow \
+   is simply ignored). */ \
+   if ( (x1) < 0.0 ) frac1 = 2.0 - ( ( frac1 > eps ) ? frac1 : eps ); \
+   if ( (x2) < 0.0 ) frac2 = 2.0 - ( ( frac2 > eps ) ? frac2 : eps ); \
+\
+/* We now extract the bits from the fraction values into integer \
+   variables so that we may perform bit-wise operations on them. However, \
+   since a double may be longer than any available integer, we may \
+   have to handle several successive blocks of bits individually. */ \
+\
+/* Extract the first block of bits by scaling by the required power of \
+   2 to shift the required bits to the left of the binary point. Then \
+   extract the integer part. Note that this initial shift is one bit less \
+   than the number of bits in an unsigned long, because we have \
+   introduced an extra sign bit. */ \
+   frac1 *= scale1; \
+   frac2 *= scale1; \
+   b1 = (unsigned long) frac1; \
+   b2 = (unsigned long) frac2; \
+\
+/* Perform the required bit-wise operation on the extracted blocks of \
+   bits. */ \
+   b = b1 oper b2; \
+\
+/* Extract the sign bit from this initial result. This determines \
+   whether the final result bit pattern should represent a negative \
+   floating point number. */ \
+   neg = b & signbit; \
+\
+/* Initialise the floating point result by setting it to the integer \
+   result multipled by the reciprocal of the scale factor used to shift \
+   the bits above. This returns the result bits to their correct \
+   significance. */ \
+   unscale = rscale1; \
+   result = (double) b * unscale; \
+\
+/* We now loop to extract and process further blocks of bits (if \
+   present). The number of blocks is determined by the relative lengths \
+   of a double and an unsigned long. In practice, some bits of the double \
+   will be used by its exponent, so the last block may be incomplete and \
+   will simply be padded with zeros. */ \
+   for ( iblock = 1; iblock < nblock; iblock++ ) { \
+\
+/* Subtract the integer part (which has already been processed) from \
+   each fraction, to leave the bits which remain to be processed. Then \
+   multiply by a scale factor to shift the next set of bits to the left \
+   of the binary point. This time, we use as many bits as will fit into \
+   an unsigned long. */ \
+      frac1 = ( frac1 - (double) b1 ) * scale; \
+      frac2 = ( frac2 - (double) b2 ) * scale; \
+\
+/* Extract the integer part, which contains the required bits. */ \
+      b1 = (unsigned long) frac1; \
+      b2 = (unsigned long) frac2; \
+\
+/* Perform the required bit-wise operation on the extracted blocks of \
+   bits. */ \
+      b = b1 oper b2; \
+\
+/* Update the result floating point value by adding the new integer \
+   result multiplied by a scale factor to return the bits to their \
+   original significance. */ \
+      unscale *= rscale; \
+      result += (double) b * unscale; \
+   } \
+\
+/* If the (normalised fraction) result represents a negative number, \
+   then subtract 2.0 from it (equivalent to subtracting it from 2 and \
+   negating the result). This converts back to using a separate sign bit \
+   instead of twos-complement notation. */ \
+   if ( neg ) result -= 2.0; \
+\
+/* Scale by the required power of 2 to remove the initial \
+   normalisation applied and assign the result to the "result" \
+   variable. */ \
+   result = ldexp( result, expon )
+
+/* Gaussian random number. */
+/* ----------------------- */
+/* This macro expands to code which assigns a pseudo-random value to
+   the "result" variable. The value is drawn from a Gaussian distribution
+   with mean "x1" and standard deviation "ABS(x2)". */
+#define GAUSS(x1,x2) \
+\
+/* Loop until a satisfactory result is obtained. */ \
+   do { \
+\
+/* Obtain a value drawn from a standard Gaussian distribution. */ \
+      ran = Gauss( rcontext, status ); \
+\
+/* Multiply by "ABS(x2)", trapping possible overflow. */ \
+      result = ABS( (x2) ); \
+      result = SAFE_MUL( ran, result ); \
+\
+/* If OK, add "x1", again trapping possible overflow. */ \
+      if ( result != AST__BAD ) result = SAFE_ADD( result, (x1) ); \
+\
+/* Continue generating values until one is found which does not cause \
+   overflow. */ \
+   } while ( result == AST__BAD );
+
+/* Implement the stack-based arithmetic. */
+/* ===================================== */
+/* Initialise the top of stack index and constant counter. */
+      tos = -1;
+      icon = 0;
+
+/* Determine the number of opcodes to be processed and loop to process
+   them, executing the appropriate "case" block for each one. */
+      ncode = code[ 0 ];
+      for ( icode = 1; icode <= ncode; icode++ ) {
+         switch ( (Oper) code[ icode ] ) {
+
+/* Ignore any null opcodes (which shouldn't occur). */
+            case OP_NULL: break;
+
+/* Otherwise, perform the required vector operation on the stack... */
+
+/* User-supplied constants and variables. */
+/* -------------------------------------- */
+/* Loading a constant involves incrementing the constant count and
+   assigning the next constant's value to the top of stack element. */
+            ARG_0( OP_LDCON,    value = con[ icon++ ], *y = value )
+
+/* Loading a variable involves obtaining the variable's index by
+   consuming a constant (as above), and then copying the variable's
+   values into the top of stack element. */
+            ARG_0( OP_LDVAR,    ivar = (int) ( con[ icon++ ] + 0.5 ),
+                                *y = ptr_in[ ivar ][ point ] )
+
+/* System constants. */
+/* ----------------- */
+/* Loading a "bad" value simply means assigning AST__BAD to the top of
+   stack element. */
+            ARG_0( OP_LDBAD,    ;, *y = AST__BAD )
+
+/* The following load constants associated with the (double) floating
+   point representation into the top of stack element. */
+            ARG_0( OP_LDDIG,    ;, *y = (double) DBL_DIG )
+            ARG_0( OP_LDEPS,    ;, *y = DBL_EPSILON )
+            ARG_0( OP_LDMAX,    ;, *y = DBL_MAX )
+            ARG_0( OP_LDMAX10E, ;, *y = (double) DBL_MAX_10_EXP )
+            ARG_0( OP_LDMAXE,   ;, *y = (double) DBL_MAX_EXP )
+            ARG_0( OP_LDMDIG,   ;, *y = (double) DBL_MANT_DIG )
+            ARG_0( OP_LDMIN,    ;, *y = DBL_MIN )
+            ARG_0( OP_LDMIN10E, ;, *y = (double) DBL_MIN_10_EXP )
+            ARG_0( OP_LDMINE,   ;, *y = (double) DBL_MIN_EXP )
+            ARG_0( OP_LDRAD,    ;, *y = (double) FLT_RADIX )
+            ARG_0( OP_LDRND,    ;, *y = (double) FLT_ROUNDS )
+
+/* Mathematical constants. */
+/* ----------------------- */
+/* The following load mathematical constants into the top of stack
+   element. */
+            ARG_0( OP_LDE,      value = exp( 1.0 ), *y = value )
+            ARG_0( OP_LDPI,     ;, *y = pi )
+
+/* Functions with one argument. */
+/* ---------------------------- */
+/* The following simply evaluate a function of the top of stack
+   element and assign the result to the same element. */
+            ARG_1( OP_ABS,      *y = ABS( x ) )
+            ARG_1( OP_ACOS,     *y = ( ABS( x ) <= 1.0 ) ?
+                                     acos( x ) : AST__BAD )
+            ARG_1( OP_ACOSD,    *y = ( ABS( x ) <= 1.0 ) ?
+                                     acos( x ) * r2d : AST__BAD )
+            ARG_1( OP_ACOSH,    *y = ( x < 1.0 ) ? AST__BAD :
+                                     ( ( x > safe_sq ) ? log( x ) + log2 :
+                                       log( x + sqrt( x * x - 1.0 ) ) ) )
+            ARG_1( OP_ACOTH,    *y = ( ABS( x ) <= 1.0 ) ? AST__BAD :
+                                     0.5 * ( log( ( x + 1.0 ) /
+                                                  ( x - 1.0 ) ) ) )
+            ARG_1( OP_ACSCH,    *y = ( ( x == 0.0 ) ? AST__BAD :
+                                       ( sign = ( x >= 0.0 ), x = ABS( x ),
+                                       ( sign ? 1.0 : -1.0 ) *
+                                       ( ( x < rsafe_sq ) ? log2 - log( x ) :
+                                         ( x = 1.0 / x,
+                                       log( x + sqrt( x * x + 1.0 ) ) ) ) ) ) )
+            ARG_1( OP_ASECH,    *y = ( ( x <= 0 ) || ( x > 1.0 ) ) ? AST__BAD :
+                                       ( ( x < rsafe_sq ) ? log2 - log( x ) :
+                                         ( x = 1.0 / x,
+                                           log( x + sqrt( x * x - 1.0 ) ) ) ) )
+            ARG_1( OP_ASIN,     *y = ( ABS( x ) <= 1.0 ) ?
+                                     asin( x ) : AST__BAD )
+            ARG_1( OP_ASIND,    *y = ( ABS( x ) <= 1.0 ) ?
+                                     asin( x ) * r2d : AST__BAD )
+            ARG_1( OP_ASINH,    *y = ( sign = ( x >= 0.0 ), x = ABS( x ),
+                                       ( sign ? 1.0 : -1.0 ) *
+                                       ( ( x > safe_sq ) ? log( x ) + log2 :
+                                         log( x + sqrt( x * x + 1.0 ) ) ) ) )
+            ARG_1( OP_ATAN,     *y = atan( x ) )
+            ARG_1( OP_ATAND,    *y = atan( x ) * r2d )
+            ARG_1( OP_ATANH,    *y = ( ABS( x ) >= 1.0 ) ? AST__BAD :
+                                     0.5 * ( log( ( 1.0 + x ) /
+                                                  ( 1.0 - x ) ) ) )
+            ARG_1( OP_CEIL,     *y = ceil( x ) )
+            ARG_1( OP_COS,      *y = cos( x ) )
+            ARG_1( OP_COSD,     *y = cos( x * d2r ) )
+            ARG_1( OP_COSH,     *y = CATCH_MATHS_OVERFLOW( cosh( x ) ) )
+            ARG_1( OP_COTH,     *y = ( x = tanh( x ), SAFE_DIV( 1.0, x ) ) )
+            ARG_1( OP_CSCH,     *y = ( x = CATCH_MATHS_OVERFLOW( sinh( x ) ),
+                                       ( x == AST__BAD ) ?
+                                       0.0 : SAFE_DIV( 1.0, x ) ) )
+            ARG_1( OP_EXP,      *y = CATCH_MATHS_OVERFLOW( exp( x ) ) )
+            ARG_1( OP_FLOOR,    *y = floor( x ) )
+            ARG_1( OP_INT,      *y = INT( x ) )
+            ARG_1B( OP_ISBAD,   *y = ( x == AST__BAD ) )
+            ARG_1( OP_LOG,      *y = ( x > 0.0 ) ? log( x ) : AST__BAD )
+            ARG_1( OP_LOG10,    *y = ( x > 0.0 ) ? log10( x ) : AST__BAD )
+            ARG_1( OP_NINT,     *y = ( x >= 0 ) ?
+                                     floor( x + 0.5 ) : ceil( x - 0.5 ) )
+            ARG_1( OP_POISS,    *y = Poisson( rcontext, x, status ) )
+            ARG_1( OP_SECH,     *y = ( x = CATCH_MATHS_OVERFLOW( cosh( x ) ),
+                                       ( x == AST__BAD ) ? 0.0 : 1.0 / x ) )
+            ARG_1( OP_SIN,      *y = sin( x ) )
+            ARG_1( OP_SINC,     *y = ( x == 0.0 ) ? 1.0 : sin( x ) / x )
+            ARG_1( OP_SIND,     *y = sin( x * d2r ) )
+            ARG_1( OP_SINH,     *y = CATCH_MATHS_OVERFLOW( sinh( x ) ) )
+            ARG_1( OP_SQR,      *y = SAFE_MUL( x, x ) )
+            ARG_1( OP_SQRT,     *y = ( x >= 0.0 ) ? sqrt( x ) : AST__BAD )
+            ARG_1( OP_TAN,      *y = CATCH_MATHS_OVERFLOW( tan( x ) ) )
+            ARG_1( OP_TAND,     *y = tan( x * d2r ) )
+            ARG_1( OP_TANH,     *y = tanh( x ) )
+
+/* Functions with two arguments. */
+/* ----------------------------- */
+/* These evaluate a function of the top two entries on the stack. */
+            ARG_2( OP_ATAN2,    *y = atan2( x1, x2 ) )
+            ARG_2( OP_ATAN2D,   *y = atan2( x1, x2 ) * r2d )
+            ARG_2( OP_DIM,      *y = ( x1 > x2 ) ? x1 - x2 : 0.0 )
+            ARG_2( OP_GAUSS,    GAUSS( x1, x2 ); *y = result )
+            ARG_2( OP_MOD,      *y = ( x2 != 0.0 ) ?
+                                     fmod( x1, x2 ) : AST__BAD )
+            ARG_2( OP_POW,      *y = CATCH_MATHS_ERROR( pow( x1, x2 ) ) )
+            ARG_2( OP_RAND,     ran = Rand( rcontext, status );
+                                *y = x1 * ran + x2 * ( 1.0 - ran ); )
+            ARG_2( OP_SIGN,     *y = ( ( x1 >= 0.0 ) == ( x2 >= 0.0 ) ) ?
+                                     x1 : -x1 )
+
+/* Functions with three arguments. */
+/* ------------------------------- */
+/* These evaluate a function of the top three entries on the stack. */
+            ARG_3B( OP_QIF,     *y = ( ( x1 ) ? ( x2 ) : ( x3 ) ) )
+
+
+/* Functions with variable numbers of arguments. */
+/* --------------------------------------------- */
+/* These operations take a variable number of arguments, the actual
+   number being determined by consuming a constant. We then loop to
+   perform a 2-argument operation on the stack (as above) the required
+   number of times. */
+            case OP_MAX:
+               narg = (int) ( con[ icon++ ] + 0.5 );
+               for ( iarg = 0; iarg < ( narg - 1 ); iarg++ ) {
+                  DO_ARG_2( *y = ( x1 >= x2 ) ? x1 : x2 )
+               }
+               break;
+            case OP_MIN:
+               narg = (int) ( con[ icon++ ] + 0.5 );
+               for ( iarg = 0; iarg < ( narg - 1 ); iarg++ ) {
+                  DO_ARG_2( *y = ( x1 <= x2 ) ? x1 : x2 )
+               }
+               break;
+
+/* Unary arithmetic operators. */
+/* --------------------------- */
+            ARG_1( OP_NEG,      *y = -x )
+
+/* Unary boolean operators. */
+/* ------------------------ */
+            ARG_1( OP_NOT,      *y = ( x == 0.0 ) )
+
+/* Binary arithmetic operators. */
+/* ---------------------------- */
+            ARG_2( OP_ADD,      *y = SAFE_ADD( x1, x2 ) )
+            ARG_2( OP_SUB,      *y = SAFE_SUB( x1, x2 ) )
+            ARG_2( OP_MUL,      *y = SAFE_MUL( x1, x2 ) )
+            ARG_2( OP_DIV ,     *y = SAFE_DIV( x1, x2 ) )
+
+/* Bit-shift operators. */
+/* -------------------- */
+            ARG_2( OP_SHFTL,    *y = SHIFT_BITS( x1, x2 ) )
+            ARG_2( OP_SHFTR,    *y = SHIFT_BITS( x1, -x2 ) )
+
+/* Relational operators. */
+/* --------------------- */
+            ARG_2( OP_EQ,       *y = ( x1 == x2 ) )
+            ARG_2( OP_GE,       *y = ( x1 >= x2 ) )
+            ARG_2( OP_GT,       *y = ( x1 > x2 ) )
+            ARG_2( OP_LE,       *y = ( x1 <= x2 ) )
+            ARG_2( OP_LT,       *y = ( x1 < x2 ) )
+            ARG_2( OP_NE,       *y = ( x1 != x2 ) )
+
+/* Bit-wise operators. */
+/* ------------------- */
+            ARG_2( OP_BITOR,    BIT_OPER( |, x1, x2 ); *y = result )
+            ARG_2( OP_BITXOR,   BIT_OPER( ^, x1, x2 ); *y = result )
+            ARG_2( OP_BITAND,   BIT_OPER( &, x1, x2 ); *y = result )
+
+/* Binary boolean operators. */
+/* ------------------------- */
+            ARG_2B( OP_AND,     *y = TRISTATE_AND( x1, x2 ) )
+            ARG_2( OP_EQV,      *y = ( ( x1 != 0.0 ) == ( x2 != 0.0 ) ) )
+            ARG_2B( OP_OR,      *y = TRISTATE_OR( x1, x2 ) )
+            ARG_2( OP_XOR,      *y = ( ( x1 != 0.0 ) != ( x2 != 0.0 ) ) )
+         }
+      }
+   }
+
+/* When all opcodes have been processed, the result of the function
+   evaluation will reside in the lowest stack entry - i.e. the output
+   array. */
+
+/* Free the workspace arrays. */
+   work = astFree( work );
+   stack = astFree( stack );
+
+/* Undefine macros local to this function. */
+#undef ARG_0
+#undef ARG_1
+#undef ARG_1B
+#undef DO_ARG_2
+#undef ARG_2
+#undef ARG_2B
+#undef ABS
+#undef INT
+#undef CATCH_MATHS_OVERFLOW
+#undef CATCH_MATHS_ERROR
+#undef TRISTATE_OR
+#undef TRISTATE_AND
+#undef SAFE_ADD
+#undef SAFE_SUB
+#undef SAFE_MUL
+#undef SAFE_DIV
+#undef SHIFT_BITS
+#undef BIT_OPER
+#undef GAUSS
+}
+
+static void EvaluationSort( const double con[], int nsym, int symlist[],
+                            int **code, int *stacksize, int *status ) {
+/*
+*  Name:
+*     EvaluationSort
+
+*  Purpose:
+*     Perform an evaluation-order sort on parsed expression symbols.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void EvaluationSort( const double con[], int nsym, int symlist[],
+*                          int **code, int *stacksize, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This function sorts a sequence of numbers representing symbols
+*     identified in an expression. The symbols (i.e. the expression syntax)
+*     must have been fully validated beforehand, as no validation is
+*     performed here.
+*
+*     The symbols are sorted into the order in which corresponding
+*     operations must be performed on a push-down arithmetic stack in order
+*     to evaluate the expression. Operation codes (opcodes), as defined in
+*     the "Oper" enum, are then substituted for the symbol numbers.
+
+*  Parameters:
+*     con
+*        Pointer to an array of double containing the set of constants
+*        generated while parsing the expression (these are required in order
+*        to determine the number of arguments associated with functions which
+*        take a variable number of arguments).
+*     nsym
+*        The number of symbols identified while parsing the expression.
+*     symlist
+*        Pointer to an array of int, with "nsym" elements. On entry, this
+*        should contain the indices in the static "symbol" array of the
+*        symbols identified while parsing the expression. On exit, the
+*        contents are undefined.
+*     code
+*        Address of a pointer which will be set to point at a dynamically
+*        allocated array of int containing the set of opcodes (cast to int)
+*        produced by this function. The first element of this array will
+*        contain a count of the number of opcodes which follow.
+*
+*        The allocated space must be freed by the caller (using astFree) when
+*        no longer required.
+*     stacksize
+*        Pointer to an int in which to return the size of the push-down stack
+*        required to evaluate the expression using the returned opcodes.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - A value of NULL will be returned for the "*code" pointer and a value
+*     of zero will be returned for the "*stacksize" value if this function is
+*     invoked with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   int flush;                    /* Flush parenthesised symbol sequence? */
+   int icon;                     /* Input constant counter */
+   int isym;                     /* Input symbol counter */
+   int ncode;                    /* Number of opcodes generated */
+   int nstack;                   /* Evaluation stack size */
+   int push;                     /* Push a new symbol on to stack? */
+   int sym;                      /* Variable for symbol number */
+   int tos;                      /* Top of sort stack index */
+
+/* Initialise */
+   *code = NULL;
+   *stacksize = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Further initialisation. */
+   flush = 0;
+   icon = 0;
+   isym = 0;
+   ncode = 0;
+   nstack = 0;
+   tos = -1;
+
+/* Loop to generate output opcodes until the sort stack is empty and
+   there are no further symbols to process, or an error is detected.  */
+   while ( astOK && ( ( tos > -1 ) || ( isym < nsym ) ) ) {
+
+/* Decide whether to push a symbol on to the sort stack (which
+   "diverts" it so that higher-priority symbols can be output), or to pop
+   the top symbol off the sort stack and send it to the output
+   stream... */
+
+/* We must push a symbol on to the sort stack if the stack is
+   currently empty. */
+      if ( tos == -1 ) {
+         push = 1;
+
+/* We must pop the top symbol off the sort stack if there are no more
+   input symbols to process. */
+      } else if ( isym >= nsym ) {
+         push = 0;
+
+/* If the sort stack is being flushed to complete the evaluation of a
+   parenthesised expression, then the top symbol (which will be the
+   opening parenthesis or function call) must be popped. This is only
+   done once, so reset the "flush" flag before the next loop. */
+      } else if ( flush ) {
+         push = 0;
+         flush = 0;
+
+/* In all other circumstances, we must push a symbol on to the sort
+   stack if its evaluation priority (seen from the left) is higher than
+   that of the current top of stack symbol (seen from the right). This
+   means it will eventually be sent to the output stream ahead of the
+   current top of stack symbol. */
+      } else {
+         push = ( symbol[ symlist[ isym ] ].leftpriority >
+                  symbol[ symlist[ tos ] ].rightpriority );
+      }
+
+/* If a symbol is being pushed on to the sort stack, then get the next
+   input symbol which is to be used. */
+      if ( push ) {
+         sym = symlist[ isym++ ];
+
+/* If the symbol decreases the parenthesis level (a closing
+   parenthesis), then all the sort stack entries down to the symbol which
+   opened the current level of parenthesis (the matching opening
+   parenthesis or function call) will already have been sent to the
+   output stream as a consequence of the evaluation priority defined for
+   a closing parenthesis in the symbol data. The opening parenthesis (or
+   function call) must next be flushed from the sort stack, so set the
+   "flush" flag which is interpreted on the next loop. Ignore the current
+   symbol, which cancels with the opening parenthesis on the stack. */
+         if ( symbol[ sym ].parincrement < 0 ) {
+            flush = 1;
+
+/* All other symbols are pushed on to the sort stack. The stack
+   occupies that region of the "symlist" array from which the input
+   symbol numbers have already been extracted. */
+         } else {
+            symlist[ ++tos ] = sym;
+         }
+
+/* If a symbol is being popped from the top of the sort stack, then
+   the top of stack entry is transferred to the output stream. Obtain the
+   symbol number from the stack. Increment the local constant counter if
+   the associated operation will use a constant. */
+      } else {
+         sym = symlist[ tos-- ];
+         icon += ( ( sym == symbol_ldvar ) || ( sym == symbol_ldcon ) );
+
+/* If the output symbol does not represent a "null" operation,
+   increase the size of the output opcode array to accommodate it,
+   checking for errors. Note that we allocate one extra array element
+   (the first) which will eventually hold a count of all the opcodes
+   generated. */
+         if ( symbol[ sym ].opcode != OP_NULL ) {
+            *code = astGrow( *code, ncode + 2, sizeof( int ) );
+            if ( astOK ) {
+
+/* Append the new opcode to the end of this array. */
+               ( *code )[ ++ncode ] = (int) symbol[ sym ].opcode;
+
+/* Increment/decrement the counter representing the stack size
+   required for evaluation of the expression.  If the symbol is a
+   function with a variable number of arguments (indicated by a negative
+   "nargs" entry in the symbol data table), then the change in stack size
+   must be determined from the argument number stored in the constant
+   table. */
+               if ( symbol[ sym ].nargs >= 0 ) {
+                  nstack += symbol[ sym ].stackincrement;
+               } else {
+                  nstack -= (int) ( con[ icon++ ] + 0.5 ) - 1;
+               }
+
+/* Note the maximum size of the stack. */
+               *stacksize = ( nstack > *stacksize ) ? nstack : *stacksize;
+            }
+         }
+      }
+   }
+
+/* If no "*code" array has been allocated, then allocate one simply to
+   store the number of opcodes generated, i.e. zero (this shouldn't
+   normally happen as this represents an invalid expression). */
+   if ( !*code ) *code = astMalloc( sizeof( int ) );
+
+/* If no error has occurred, store the count of opcodes generated in
+   the first element of the "*code" array and re-allocate the array to
+   its final size (since astGrow may have over-allocated space). */
+   if ( astOK ) {
+      ( *code )[ 0 ] = ncode;
+      *code = astRealloc( *code, sizeof( int ) * (size_t) ( ncode + 1 ) );
+   }
+
+/* If an error occurred, free any memory that was allocated and reset
+   the output values. */
+   if ( !astOK ) {
+      *code = astFree( *code );
+      *stacksize = 0;
+   }
+}
+
+static void ExtractExpressions( const char *method, const char *class,
+                                int nfun, const char *fun[], int forward,
+                                char ***exprs, int *status ) {
+/*
+*  Name:
+*     ExtractExpressions
+
+*  Purpose:
+*     Extract and validate expressions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void ExtractExpressions( const char *method, const char *class,
+*                              int nfun, const char *fun[], int forward,
+*                              char ***exprs, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This function extracts expressions from the right hand sides of a set
+*     of functions. These expressions are then validated to check that they
+*     are either all present, or all absent (absence indicating an undefined
+*     transformation). An error is reported if anything is found to be
+*     wrong.
+*
+*     Note that the syntax of the expressions is not checked by this function
+*     (i.e. they are not compiled).
+
+*  Parameters:
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function.
+*        This method name is used solely for constructing error messages.
+*     class
+*        Pointer to a constant null-terminated character string containing the
+*        class name of the Object being processed. This name is used solely
+*        for constructing error messages.
+*     nfun
+*        The number of functions to be analysed.
+*     fun
+*        Pointer to an array, with "nfun" elements, of pointers to null
+*        terminated strings which contain each of the functions. These
+*        strings should contain no white space.
+*     forward
+*        A non-zero value indicates the the MathMap's forward transformation
+*        functions are being processed, while a zero value indicates processing
+*        of the inverse transformation functions. This value is used solely for
+*        constructing error messages.
+*     exprs
+*        Address in which to return a pointer to an array (with "nfun"
+*        elements) of pointers to null terminated strings containing the
+*        extracted expressions (i.e. this returns an array of strings).
+*
+*        Both the returned array of pointers, and the strings to which they
+*        point, will be stored in dynamically allocated memory and should
+*        be freed by the caller (using astFree) when no longer required.
+*
+*        If the right hand sides (including the "=" sign) of all the supplied
+*        functions are absent, then this indicates an undefined transformation
+*        and the returned pointer value will be NULL. An error results if
+*        an "=" sign is present but no expression follows it.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*        - A NULL value will be returned for "*exprs" if this function is
+*        invoked with the global error status set, or if it should fail for
+*        any reason.
+*/
+
+/* Local Variables: */
+   char *ex;                     /* Pointer to start of expression string */
+   int ifun;                     /* Loop counter for functions */
+   int iud;                      /* Index of first undefined function */
+   int nud;                      /* Number of undefined expressions */
+
+/* Initialise. */
+   *exprs = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Further initialisation. */
+   nud = 0;
+   iud = 0;
+
+/* Allocate and initialise memory for the returned array of pointers. */
+   MALLOC_POINTER_ARRAY( *exprs, char *, nfun )
+
+/* Loop to inspect each function in turn. */
+   if ( astOK ) {
+      for ( ifun = 0; ifun < nfun; ifun++ ) {
+
+/* Search for the first "=" sign. */
+         if ( ( ex = strchr( fun[ ifun ], '=' ) ) ) {
+
+/* If found, and there are more characters after the "=" sign, then
+   find the length of the expression which follows. Allocate a string to
+   hold this expression, storing its pointer in the array allocated
+   above. Check for errors. */
+            if ( *++ex ) {
+               ( *exprs )[ ifun ] = astMalloc( strlen( ex ) + (size_t) 1 );
+               if ( !astOK ) break;
+
+/* If OK, extract the expression string. */
+               (void) strcpy( ( *exprs )[ ifun ], ex );
+
+/* If an "=" sign was found but there are no characters following it,
+   then there is a missing right hand side to a function, so report an
+   error and quit. */
+            } else {
+               astError( AST__NORHS,
+                         "%s(%s): Missing right hand side in expression: "
+                         "\"%s\".", status,
+                         method, class, fun[ ifun ] );
+               astError( astStatus,
+                         "Error in %s transformation function %d.", status,
+                         forward ? "forward" : "inverse", ifun + 1 );
+               break;
+            }
+
+/* If no "=" sign was found, then the transformation may be undefined,
+   in which case each function should only contain a variable name. Count
+   the number of times this happens and record the index of the first
+   instance. */
+         } else {
+            nud++;
+            if ( nud == 1 ) iud = ifun;
+         }
+      }
+   }
+
+/* Either all functions should have an "=" sign (in which case the
+   transformation is defined), or none of them should have (in which case
+   it is undefined). If some do and some don't, then report an error,
+   citing the first instance of a missing "=" sign. */
+   if ( astOK && ( nud != 0 ) && ( nud != nfun ) ) {
+      astError( AST__NORHS,
+                "%s(%s): Missing right hand side in function: \"%s\".", status,
+                method, class, fun[ iud ] );
+      astError( astStatus,
+                "Error in %s transformation function %d.", status,
+                forward ? "forward" : "inverse", iud + 1 );
+   }
+
+/* If an error occurred, or all the expressions were absent, then free any
+   allocated memory and reset the output value. */
+   if ( !astOK || nud ) {
+      FREE_POINTER_ARRAY( *exprs, nfun )
+   }
+}
+
+static void ExtractVariables( const char *method, const char *class,
+                              int nfun, const char *fun[],
+                              int nin, int nout, int nfwd, int ninv,
+                              int forward, char ***var, int *status ) {
+/*
+*  Name:
+*     ExtractVariables
+
+*  Purpose:
+*     Extract and validate variable names.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void ExtractVariables( const char *method, const char *class,
+*                            int nfun, const char *fun[],
+*                            int nin, int nout, int nfwd, int ninv,
+*                            int forward, char ***var, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This function extracts variable names from the left hand sides of a
+*     set of transformation functions belonging to a MathMap. These variable
+*     names are then validated to check for correct syntax and no
+*     duplication. An error is reported if anything is wrong with the
+*     variable names obtained.
+
+*  Parameters:
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function.
+*        This method name is used solely for constructing error messages.
+*     class
+*        Pointer to a constant null-terminated character string containing the
+*        class name of the Object being processed. This name is used solely
+*        for constructing error messages.
+*     nfun
+*        The number of functions to be analysed.
+*     fun
+*        Pointer to an array, with "nfun" elements, of pointers to null
+*        terminated strings which contain each of the functions. These strings
+*        are case sensitive and should contain no white space.
+*
+*        The first elements of this array should point to the functions that
+*        define the primary input/output variables (depending on direction).
+*        These should be followed by any functions which define intermediate
+*        variables (taken from the set of functions which transform in the
+*        opposite direction to the first ones).
+*     nin
+*        Number of input variables for the MathMap.
+*     nout
+*        Number of output variables for the MathMap.
+*     nfwd
+*        Number of forward transformation functions for the MathMap.
+*     ninv
+*        Number of inverse transformation functions for the MathMap.
+*     forward
+*        A non-zero value indicates the the MathMap's forward transformation
+*        functions are being processed, while a zero value indicates processing
+*        of the inverse transformation functions. This value, together with
+*        "nin", "nout", "nfwd" and "ninv" are used solely for constructing
+*        error messages.
+*     var
+*        Address in which to return a pointer to an array (with "nfun"
+*        elements) of pointers to null terminated strings containing the
+*        extracted variable names (i.e. this returns an array of strings).
+*
+*        Both the returned array of pointers, and the strings to which they
+*        point, will be stored in dynamically allocated memory and should
+*        be freed by the caller (using astFree) when no longer required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*        - A NULL value will be returned for "*var" if this function is
+*        invoked with the global error status set, or if it should fail for
+*        any reason.
+*/
+
+/* Local Variables: */
+   char *duser1;                 /* Transformation direction for function */
+   char *duser2;                 /* Transformation direction for function */
+   char c;                       /* Extracted character */
+   int i1;                       /* Loop counter for detecting duplicates */
+   int i2;                       /* Loop counter for detecting duplicates */
+   int i;                        /* Loop counter for characters */
+   int iend;                     /* Last character index in parsed name */
+   int ifun;                     /* Loop counter for functions */
+   int iuser1;                   /* Function number as known to the user */
+   int iuser2;                   /* Function number as known to the user */
+   int nc;                       /* Character count */
+   int nextra;                   /* Number of intermediate functions */
+   int nprimary;                 /* Number of primary input/output variables */
+
+/* Initialise. */
+   *var = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain the number of primary input/output variables, depending on
+   the direction of the coordinate transformation. */
+   nprimary = ( forward ? nin : nout );
+
+/* Deterine the number of extra (intermediate) functions that come
+   before these primary ones. These affect the numbering of
+   transformation functions as known to the user, and must be accounted
+   for when reporting error messages. */
+   nextra = ( forward ? ninv - nin : nfwd - nout );
+
+/* Allocate and initialise memory for the returned array of pointers. */
+   MALLOC_POINTER_ARRAY( *var, char *, nfun )
+
+/* Loop to process each function in turn. */
+   if ( astOK ) {
+      for ( ifun = 0; ifun < nfun; ifun++ ) {
+
+/* Count the number of characters appearing before the "=" sign (or in
+   the entire string if the "=" is absent). */
+         for ( nc = 0; ( c = fun[ ifun ][ nc ] ); nc++ ) if ( c == '=' ) break;
+
+/* If no characters were counted, then report an appropriate error
+   message, depending on whether the function string was entirely
+   blank. */
+         if ( !nc ) {
+            if ( c ) {
+               astError( AST__MISVN,
+                         "%s(%s): No left hand side in expression: \"%s\".", status,
+                         method, class, fun[ ifun ] );
+            } else {
+               astError( AST__MISVN,
+                         "%s: Transformation function contains no variable "
+                         "name.", status,
+                         method );
+            }
+            break;
+         }
+
+/* If OK, allocate memory to hold the output string and check for
+   errors. */
+         ( *var )[ ifun ] = astMalloc( sizeof( char ) * (size_t) ( nc + 1 ) ) ;
+         if ( !astOK ) break;
+
+/* If OK, copy the characters before the "=" sign to the new
+   string. */
+         nc = 0;
+         for ( i = 0; ( c = fun[ ifun ][ i ] ); i++ ) {
+            if ( c == '=' ) break;
+            ( *var )[ ifun ][ nc++] = c;
+         }
+
+/* Null terminate the result. */
+         ( *var )[ ifun ][ nc ] = '\0';
+
+/* Try to parse the contents of the extracted string as a name. */
+         ParseName( ( *var )[ ifun ], 0, &iend, status );
+
+/* If unsuccessful, or if all the characters were not parsed, then we
+   have an invalid variable name, so report an error and quit. */
+         if ( ( iend < 0 ) || ( *var )[ ifun ][ iend + 1 ] ) {
+            astError( AST__VARIN,
+                      "%s(%s): Variable name is invalid: \"%s\".", status,
+                      method, class, ( *var )[ ifun ] );
+            break;
+         }
+      }
+
+/* If an error occurred above, then determine the function number, and
+   the direction of the transformation of which it forms part, as known
+   to the user. */
+      if ( !astOK ) {
+         if ( ifun < nprimary ) {
+            iuser1 = ifun + 1 + nextra;
+            duser1 = ( forward ? "inverse" : "forward" );
+         } else {
+            iuser1 = ifun + 1 - nprimary;
+            duser1 = ( forward ? "forward" : "inverse" );
+         }
+
+/* Report a contextual error message. */
+         astError( astStatus,
+                   "Error in %s transformation function %d.", status,
+                   duser1, iuser1 );
+      }
+   }
+
+/* If there has been no error, loop to compare all the variable names
+   with each other to detect duplication. */
+   if ( astOK ) {
+      for ( i1 = 1; i1 < nfun; i1++ ) {
+         for ( i2 = 0; i2 < i1; i2++ ) {
+
+/* If a duplicate variable name is found, report an error. */
+            if ( !strcmp( ( *var )[ i1 ], ( *var )[ i2 ] ) ) {
+               astError( AST__DUVAR,
+                         "%s(%s): Duplicate definition of variable name: "
+                         "\"%s\".", status,
+                         method, class, ( *var )[ i1 ] );
+
+/* For each transformation function involved, determine the function
+   number and the direction of the transformation of which it forms part,
+   as known to the user. */
+               if ( i1 < nprimary ) {
+                  iuser1 = i1 + 1 + nextra;
+                  duser1 = ( forward ? "inverse" : "forward" );
+               } else {
+                  iuser1 = i1 + 1 - nprimary;
+                  duser1 = ( forward ? "forward" : "inverse" );
+               }
+               if ( i2 < nprimary ) {
+                  iuser2 = i2 + 1 + nextra;
+                  duser2 = ( forward ? "inverse" : "forward" );
+               } else {
+                  iuser2 = i2 + 1 - nprimary;
+                  duser2 = ( forward ? "forward" : "inverse" );
+               }
+
+/* Report a contextual error message. */
+               astError( astStatus,
+                         "Conflict between %s function %d and %s function %d.", status,
+                         duser1, iuser1, duser2, iuser2 );
+               break;
+            }
+         }
+         if ( !astOK ) break;
+      }
+   }
+
+/* If an error occurred, free any allocated memory and reset the
+   output value. */
+   if ( !astOK ) {
+      FREE_POINTER_ARRAY( *var, nfun )
+   }
+}
+
+static double Gauss( Rcontext *context, int *status ) {
+/*
+*  Name:
+*     Gauss
+
+*  Purpose:
+*     Produce a pseudo-random sample from a standard Gaussian distribution.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     double Gauss( Rcontext *context, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     On each invocation, this function returns a pseudo-random sample drawn
+*     from a standard Gaussian distribution with mean zero and standard
+*     deviation unity. The Box-Muller transformation method is used.
+
+*  Parameters:
+*     context
+*        Pointer to an Rcontext structure which holds the random number
+*        generator's context between invocations.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A sample from a standard Gaussian distribution.
+
+*  Notes:
+*     - The sequence of numbers returned is determined by the "seed"
+*     value in the Rcontext structure supplied.
+*     - If the seed value is changed, the "active" flag must also be cleared
+*     so that this function can re-initiallise the Rcontext structure before
+*     generating the next pseudo-random number. The "active" flag should
+*     also be clear to force initialisation the first time an Rcontext
+*     structure is used.
+*     - This function does not perform error checking and does not generate
+*     errors. It will execute even if the global error status is set.
+*/
+
+/* Local Variables: */
+   double rsq;                   /* Squared radius */
+   double s;                     /* Scale factor */
+   double x;                     /* First result value */
+   static double y;              /* Second result value */
+   static int ysaved = 0;        /* Previously-saved value available? */
+
+   LOCK_MUTEX7
+
+/* If the random number generator context is not active, then it will
+   be (re)initialised on the first invocation of Rand (below). Ensure
+   that any previously-saved value within this function is first
+   discarded. */
+   if ( !context->active ) ysaved = 0;
+
+/* If there is a previously-saved value available, then use it and
+   mark it as no longer available. */
+   if ( ysaved ) {
+      x = y;
+      ysaved = 0;
+
+/* Otherwise, loop until a suitable new pair of values has been
+   obtained. */
+   } else {
+      while ( 1 ) {
+
+/* Loop to obtain two random values uniformly distributed inside the
+   unit circle, while avoiding the origin (which maps to an infinite
+   result). */
+         do {
+            x = 2.0 * Rand( context, status ) - 1.0;
+            y = 2.0 * Rand( context, status ) - 1.0;
+            rsq = x * x + y * y;
+         } while ( ( rsq >= 1.0 ) || ( rsq == 0.0 ) );
+
+/* Perform the Box-Muller transformation, checking that this will not
+   produce overflow (which is extremely unlikely). If overflow would
+   occur, we simply repeat the above steps with a new pair of random
+   numbers. */
+         s = -2.0 * log( rsq );
+         if ( ( DBL_MAX * rsq ) >= s ) {
+            s = sqrt( s / rsq );
+
+/* Scale the original random values to give a pair of results. One will be
+   returned and the second kept until next time. */
+            x *= s;
+            y *= s;
+            break;
+         }
+      }
+
+/* Note that a saved value is available. */
+      ysaved = 1;
+   }
+
+   UNLOCK_MUTEX7
+
+/* Return the current result. */
+   return x;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     MathMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied MathMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the MathMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMathMap *this;         /* Pointer to MathMap structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the MathMap structure. */
+   this = (AstMathMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   SIZEOF_POINTER_ARRAY( this->fwdfun, this->nfwd )
+   SIZEOF_POINTER_ARRAY( this->invfun, this->ninv )
+   SIZEOF_POINTER_ARRAY( this->fwdcode, this->nfwd )
+   SIZEOF_POINTER_ARRAY( this->invcode, this->ninv )
+   SIZEOF_POINTER_ARRAY( this->fwdcon, this->nfwd )
+   SIZEOF_POINTER_ARRAY( this->invcon, this->ninv )
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a MathMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     MathMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a MathMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the MathMap.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the MathMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the MathMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMathMap *this;             /* Pointer to the MathMap structure */
+   const char *result;           /* Pointer value to return */
+   int ival;                     /* Integer attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the MathMap structure. */
+   this = (AstMathMap *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* Seed. */
+/* ----- */
+   if ( !strcmp( attrib, "seed" ) ) {
+      ival = astGetSeed( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* SimpFI. */
+/* ------- */
+   } else if ( !strcmp( attrib, "simpfi" ) ) {
+      ival = astGetSimpFI( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* SimpIF. */
+/* ------- */
+   } else if ( !strcmp( attrib, "simpif" ) ) {
+      ival = astGetSimpIF( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+void astInitMathMapVtab_(  AstMathMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitMathMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a MathMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void astInitMathMapVtab( AstMathMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     MathMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the MathMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAMathMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->ClearSeed = ClearSeed;
+   vtab->ClearSimpFI = ClearSimpFI;
+   vtab->ClearSimpIF = ClearSimpIF;
+   vtab->GetSeed = GetSeed;
+   vtab->GetSimpFI = GetSimpFI;
+   vtab->GetSimpIF = GetSimpIF;
+   vtab->SetSeed = SetSeed;
+   vtab->SetSimpFI = SetSimpFI;
+   vtab->SetSimpIF = SetSimpIF;
+   vtab->TestSeed = TestSeed;
+   vtab->TestSimpFI = TestSimpFI;
+   vtab->TestSimpIF = TestSimpIF;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "MathMap",
+               "Transformation using mathematical functions" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static double LogGamma( double x, int *status ) {
+/*
+*  Name:
+*     LogGamma
+
+*  Purpose:
+*     Calculate the logarithm of the gamma function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     double LogGamma( double x, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This function returns the natural logarithm of the gamma function
+*     for real arguments x>0. It uses the approximation of Lanczos, with
+*     constants from Press et al. (Numerical Recipes), giving a maximum
+*     fractional error (on the gamma function) of less than 2e-10.
+
+*  Parameters:
+*     x
+*        The function argument, which must be greater than zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The natural logarithm of the gamma function with "x" as argument,
+*     or AST__BAD if "x" is not greater than zero.
+
+*  Notes:
+*     - This function does not generate errors and does not perform error
+*     reporting. It will execute even if the global error status is set.
+*/
+
+/* Local Constants: */
+   const double c0 = 1.000000000190015; /* Coefficients for series sum... */
+   const double c1 = 76.18009172947146;
+   const double c2 = -86.50532032941677;
+   const double c3 = 24.01409824083091;
+   const double c4 = -1.231739572450155;
+   const double c5 = 0.1208650973866179e-2;
+   const double c6 = -0.5395239384953e-5;
+   const double g = 5.0;
+
+/* Local Variables: */
+   double result;                /* Result value to return */
+   double sum;                   /* Series sum */
+   double xx;                    /* Denominator for summing series */
+   static double root_twopi;     /* sqrt( 2.0 * pi ) */
+   static int init = 0;          /* Initialisation performed? */
+
+/* If initialisation has not yet been performed, calculate the
+   constant required below. */
+   LOCK_MUTEX3
+   if ( !init ) {
+      root_twopi = sqrt( 2.0 * acos( -1.0 ) );
+
+/* Note that initialisation has been performed. */
+      init = 1;
+   }
+   UNLOCK_MUTEX3
+
+/* Return a bad value if "x" is not greater than zero. */
+   if ( x <= 0.0 ) {
+      result = AST__BAD;
+
+/* Otherwise, form the series sum. Since we only use 6 terms, the loop
+   that would normally be used has been completely unrolled here. */
+   } else {
+      xx = x;
+      sum = c0;
+      sum += c1 / ++xx;
+      sum += c2 / ++xx;
+      sum += c3 / ++xx;
+      sum += c4 / ++xx;
+      sum += c5 / ++xx;
+      sum += c6 / ++xx;
+
+/* Calculate the result. */
+      result = x + g + 0.5;
+      result -= ( x + 0.5 ) * log( result );
+      result = log( root_twopi * sum / x ) - result;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a MathMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     MathMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated MathMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated MathMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated MathMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the MathMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        MathMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated MathMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *new;              /* Pointer to replacement Mapping */
+   AstMathMap *mathmap1;         /* Pointer to first MathMap */
+   AstMathMap *mathmap2;         /* Pointer to second MathMap */
+   char **fwd1;                  /* Pointer to first forward function array */
+   char **fwd2;                  /* Pointer to second forward function array */
+   char **inv1;                  /* Pointer to first inverse function array */
+   char **inv2;                  /* Pointer to second inverse function array */
+   int ifun;                     /* Loop counter for functions */
+   int imap1;                    /* Index of first Mapping */
+   int imap2;                    /* Index of second Mapping */
+   int imap;                     /* Loop counter for Mappings */
+   int invert1;                  /* Invert flag for first MathMap */
+   int invert2;                  /* Invert flag for second MathMap */
+   int nfwd1;                    /* No. forward functions for first MathMap */
+   int nfwd2;                    /* No. forward functions for second MathMap */
+   int nin1;                     /* Number input coords for first MathMap */
+   int ninv1;                    /* No. inverse functions for first MathMap */
+   int ninv2;                    /* No. inverse functions for second MathMap */
+   int nout2;                    /* Number output coords for second MathMap */
+   int result;                   /* Result value to return */
+   int simplify;                 /* Mappings may simplify? */
+
+/* Initialise the returned result. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   mathmap1 = NULL;
+   mathmap2 = NULL;
+   imap1 = 0;
+   imap2 = 0;
+   invert1 = 0;
+   invert2 = 0;
+   nfwd1 = 0;
+   nin1 = 0;
+   ninv1 = 0;
+
+/* MathMaps are only worth simplifying if they occur in series. */
+   simplify = series;
+
+/* If simplification appears possible, then obtain the indices of the
+   nominated mapping and of the one which follows it. Check that a
+   mapping exists for the second index. */
+   if ( simplify ) {
+      imap1 = where;
+      imap2 = imap1 + 1;
+      simplify = ( imap2 < *nmap );
+   }
+
+/* If OK, check whether the class of both Mappings is "MathMap" (a
+   MathMap can only combine with another MathMap). */
+   if ( simplify ) {
+      simplify = !strcmp( astGetClass( ( *map_list )[ imap1 ] ), "MathMap" );
+   }
+   if ( astOK && simplify ) {
+      simplify = !strcmp( astGetClass( ( *map_list )[ imap2 ] ), "MathMap" );
+   }
+
+/* If still OK, obtain pointers to the two MathMaps and the associated
+   invert flag values. */
+   if ( astOK && simplify ) {
+      mathmap1 = (AstMathMap *) ( *map_list )[ imap1 ];
+      mathmap2 = (AstMathMap *) ( *map_list )[ imap2 ];
+      invert1 = ( *invert_list )[ imap1 ];
+      invert2 = ( *invert_list )[ imap2 ];
+
+/* Depending on the invert flag values, obtain the SimpFI or SimpIF
+   attribute value from each MathMap and check whether they are set so as
+   to permit simplification. */
+      simplify = ( ( invert1 ? astGetSimpIF( mathmap1 ) :
+                               astGetSimpFI( mathmap1 ) ) &&
+                   ( invert2 ? astGetSimpFI( mathmap2 ) :
+                               astGetSimpIF( mathmap2 ) ) );
+   }
+
+/* If still OK, obtain the effective numbers of input coordinates for
+   the first MathMap and output coordinates for the second. Take account
+   of the associated invert flags and the way the Invert attribute of
+   each MathMap is currently set. */
+   if ( astOK && simplify ) {
+      nin1 = ( invert1 == astGetInvert( mathmap1 ) ) ?
+             astGetNin( mathmap1 ) : astGetNout( mathmap1 );
+      nout2 = ( invert2 == astGetInvert( mathmap2 ) ) ?
+              astGetNout( mathmap2 ) : astGetNin( mathmap2 );
+
+/* Simplification is only possible if these two numbers are equal
+   (otherwise the the two MathMaps cannot be identical). */
+      simplify = ( nin1 == nout2 );
+   }
+
+/* If still OK, obtain the effective number of forward transformation
+   functions for the first MathMap (allowing for the associated invert
+   flag). Similarly, obtain the effective number of inverse
+   transformation functions for the second MathMap. */
+   if ( astOK && simplify ) {
+      nfwd1 = !invert1 ? mathmap1->nfwd : mathmap1->ninv;
+      ninv2 = !invert2 ? mathmap2->ninv : mathmap2->nfwd;
+
+/* Check whether these values are equal. The MathMaps cannot be
+   identical if they are not. */
+      simplify = ( nfwd1 == ninv2 );
+   }
+
+/* As above, obtain pointers to the array of effective forward
+   transformation functions for the first MathMap, and the effective
+   inverse transformation functions for the second MathMap. */
+   if ( astOK && simplify ) {
+      fwd1 = !invert1 ? mathmap1->fwdfun : mathmap1->invfun;
+      inv2 = !invert2 ? mathmap2->invfun : mathmap2->fwdfun;
+
+/* Loop to check whether these two sets of functions are
+   identical. The MathMaps cannot be merged unless they are. */
+      for ( ifun = 0; ifun < nfwd1; ifun++ ) {
+         simplify = !strcmp( fwd1[ ifun ], inv2[ ifun ] );
+         if ( !simplify ) break;
+      }
+   }
+
+/* If OK, repeat the above process to compare the effective inverse
+   transformation functions of the first MathMap with the forward
+   functions of the second one. */
+   if ( astOK && simplify ) {
+      ninv1 = !invert1 ? mathmap1->ninv : mathmap1->nfwd;
+      nfwd2 = !invert2 ? mathmap2->nfwd : mathmap2->ninv;
+      simplify = ( ninv1 == nfwd2 );
+   }
+   if ( astOK && simplify ) {
+      inv1 = !invert1 ? mathmap1->invfun : mathmap1->fwdfun;
+      fwd2 = !invert2 ? mathmap2->fwdfun : mathmap2->invfun;
+      for ( ifun = 0; ifun < ninv1; ifun++ ) {
+         simplify = !strcmp( inv1[ ifun ], fwd2[ ifun ] );
+         if ( !simplify ) break;
+      }
+   }
+
+/* If the two MathMaps can be merged, create a UnitMap as a
+   replacement. */
+   if ( astOK && simplify ) {
+      new = (AstMapping *) astUnitMap( nin1, "", status );
+
+/* If OK, annul the pointers to the original MathMaps. */
+      if ( astOK ) {
+         ( *map_list )[ imap1 ] = astAnnul( ( *map_list )[ imap1 ] );
+         ( *map_list )[ imap2 ] = astAnnul( ( *map_list )[ imap2 ] );
+
+/* Insert the pointer to the replacement UnitMap and store the
+   associated invert flag. */
+         ( *map_list )[ imap1 ] = new;
+         ( *invert_list )[ imap1 ] = 0;
+
+/* Loop to move the following Mapping pointers and invert flags down
+   in their arrays to close the gap. */
+         for ( imap = imap2 + 1; imap < *nmap; imap++ ) {
+            ( *map_list )[ imap - 1 ] = ( *map_list )[ imap ];
+            ( *invert_list )[ imap - 1 ] = ( *invert_list )[ imap ];
+         }
+
+/* Clear the final entry in each array. */
+         ( *map_list )[ *nmap - 1 ] = NULL;
+         ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+         ( *nmap )--;
+         result = imap1;
+      }
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static void ParseConstant( const char *method, const char *class,
+                           const char *exprs, int istart, int *iend,
+                           double *con, int *status ) {
+/*
+*  Name:
+*     ParseConstant
+
+*  Purpose:
+*     Parse a constant.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void ParseConstant( const char *method, const char *class,
+*                         const char *exprs, int istart, int *iend,
+*                         double *con, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This routine parses an expression, looking for a constant starting at
+*     the character with index "istart" in the string "exprs". If it
+*     identifies the constant successfully, "*con" it will return its value
+*     and "*iend" will be set to the index of the final constant character
+*     in "exprs".
+*
+*     If the characters encountered are clearly not part of a constant (it
+*     does not begin with a numeral or decimal point) the function returns
+*     with "*con" set to zero and "*iend" set to -1, but without reporting
+*     an error. However, if the first character appears to be a constant but
+*     its syntax proves to be invalid, then an error is reported.
+*
+*     The expression must be in lower case with no embedded white space.
+*     The constant must not have a sign (+ or -) in front of it.
+
+*  Parameters:
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function.
+*        This method name is used solely for constructing error messages.
+*     class
+*        Pointer to a constant null-terminated character string containing the
+*        class name of the Object being processed. This name is used solely
+*        for constructing error messages.
+*     exprs
+*        Pointer to a null-terminated string containing the expression
+*        to be parsed.
+*     istart
+*        Index of the first character in "exprs" to be considered by this
+*        function.
+*     iend
+*        Pointer to an int in which to return the index in "exprs" of the
+*        final character which forms part of the constant. If no constant is
+*        found, a value of -1 is returned.
+*     con
+*        Pointer to a double, in which the value of the constant, if found,
+*        will be returned.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   char *str;                    /* Pointer to temporary string */
+   char c;                       /* Single character from the expression */
+   int dpoint;                   /* Decimal point encountered? */
+   int expon;                    /* Exponent character encountered? */
+   int i;                        /* Loop counter for characters */
+   int iscon;		         /* Character is part of the constant? */
+   int n;                        /* Number of values read by astSscanf */
+   int nc;                       /* Number of characters read by astSscanf */
+   int numer;                    /* Numeral encountered in current field? */
+   int sign;                     /* Sign encountered? */
+   int valid;		         /* Constant syntax valid? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise. */
+   *con = 0.0;
+   *iend = -1;
+
+/* Check if the expression starts with a numeral or a decimal point. */
+   c = exprs[ istart ];
+   numer = isdigit( c );
+   dpoint = ( c == '.' );
+
+/* If it begins with any of these, the expression is clearly intended
+   to be a constant, so any failure beyond this point will result in an
+   error. Otherwise, failure to find a constant is not an error. */
+   if ( numer || dpoint ) {
+
+/* Initialise remaining variables specifying the parser context. */
+      expon = 0;
+      sign = 0;
+      valid = 1;
+
+/* Loop to increment the last constant character position until the
+   following character in the expression does not look like part of the
+   constant. */
+      *iend = istart;
+      iscon = 1;
+      while ( ( c = exprs[ *iend + 1 ] ) && iscon ) {
+         iscon = 0;
+
+/* It may be part of a numerical constant if it is a numeral, wherever
+   it occurs. */
+         if ( isdigit( c ) ) {
+            numer = 1;
+            iscon = 1;
+
+/* Or a decimal point, so long as it is the first one and is not in
+   the exponent field. Otherwise it is invalid. */
+         } else if ( c == '.' ) {
+            if ( !( dpoint || expon ) ) {
+               dpoint = 1;
+               iscon = 1;
+            } else {
+               valid = 0;
+            }
+
+/* Or if it is a 'd' or 'e' exponent character, so long as it is the
+   first one and at least one numeral has been encountered first.
+   Otherwise it is invalid. */
+          } else if ( ( c == 'd' ) || ( c == 'e' ) ) {
+             if ( !expon && numer ) {
+                expon = 1;
+                numer = 0;
+                iscon = 1;
+             } else {
+                valid = 0;
+             }
+
+/* Or if it is a sign, so long as it is in the exponent field and is
+   the first sign with no previous numerals in the same field. Otherwise
+   it is invalid (unless numerals have been encountered, in which case it
+   marks the end of the constant). */
+          } else if ( ( c == '+' ) || ( c == '-' ) ) {
+             if ( expon && !sign && !numer ) {
+                sign = 1;
+                iscon = 1;
+             } else if ( !numer ) {
+                valid = 0;
+             }
+          }
+
+/* Increment the character count if the next character may be part of
+   the constant, or if it was invalid (it will then form part of the
+   error message). */
+          if ( iscon || !valid ) ( *iend )++;
+      }
+
+/* Finally, check that the last field contained a numeral. */
+      valid = ( valid && numer );
+
+/* If the constant appears valid, allocate a temporary string to hold
+   it. */
+      if ( valid ) {
+         str = astMalloc( (size_t) ( *iend - istart + 2 ) );
+         if ( astOK ) {
+
+/* Copy the constant's characters, changing 'd' to 'e' so that
+   "astSscanf" will recognise it as an exponent character. */
+            for ( i = istart; i <= *iend; i++ ) {
+               str[ i - istart ] = ( exprs[ i ] == 'd' ) ? 'e' : exprs[ i ];
+            }
+            str[ *iend - istart + 1 ] = '\0';
+
+/* Attempt to read the constant as a double, noting how many values
+   are read and how many characters consumed. */
+            n = astSscanf( str, "%lf%n", con, &nc );
+
+/* Check that one value was read and all the characters consumed. If
+   not, then the constant's syntax is invalid. */
+            if ( ( n != 1 ) || ( nc < ( *iend - istart + 1 ) ) ) valid = 0;
+         }
+
+/* Free the temporary string. */
+         str = astFree( str );
+      }
+
+/* If the constant syntax is invalid, and no other error has occurred,
+   then report an error. */
+      if ( astOK && !valid ) {
+         astError( AST__CONIN,
+                   "%s(%s): Invalid constant syntax in the expression "
+                   "\"%.*s\".", status,
+                   method, class, *iend + 1, exprs );
+      }
+
+/* If an error occurred, reset the output values. */
+      if ( !astOK ) {
+         *iend = -1;
+         *con = 0.0;
+      }
+   }
+}
+
+static void ParseName( const char *exprs, int istart, int *iend, int *status ) {
+/*
+*  Name:
+*     ParseName
+
+*  Purpose:
+*     Parse a name.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void ParseName( const char *exprs, int istart, int *iend, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This routine parses an expression, looking for a name starting at the
+*     character with index "istart" in the string "exprs". If it identifies
+*     a name successfully, "*iend" will return the index of the final name
+*     character in "exprs". A name must begin with an alphabetic character
+*     and subsequently contain only alphanumeric characters or underscores.
+*
+*     If the expression does not contain a name at the specified location,
+*     "*iend" is set to -1. No error results.
+*
+*     The expression should not contain embedded white space.
+
+*  Parameters:
+*     exprs
+*        Pointer to a null-terminated string containing the expression
+*        to be parsed.
+*     istart
+*        Index of the first character in "exprs" to be considered by this
+*        function.
+*     iend
+*        Pointer to an int in which to return the index in "exprs" of the
+*        final character which forms part of the name. If no name is
+*        found, a value of -1 is returned.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   char c;                       /* Single character from expression */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise. */
+   *iend = -1;
+
+/* Check the first character is valid for a name (alphabetic). */
+   if ( isalpha( exprs[ istart ] ) ) {
+
+/* If so, loop to inspect each subsequent character until one is found
+   which is not part of a name (not alphanumeric or underscore). */
+      for ( *iend = istart; ( c = exprs[ *iend + 1 ] ); ( *iend )++ ) {
+         if ( !( isalnum( c ) || ( c == '_' ) ) ) break;
+      }
+   }
+}
+
+static void ParseVariable( const char *method, const char *class,
+                           const char *exprs, int istart, int nvar,
+                           const char *var[], int *ivar, int *iend, int *status ) {
+/*
+*  Name:
+*     ParseVariable
+
+*  Purpose:
+*     Parse a variable name.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void ParseVariable( const char *method, const char *class,
+*                         const char *exprs, int istart, int nvar,
+*                         const char *var[], int *ivar, int *iend, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This routine parses an expression, looking for a recognised variable
+*     name starting at the character with index "istart" in the string
+*     "exprs". If it identifies a variable name successfully, "*ivar" will
+*     return a value identifying it and "*iend" will return the index of the
+*     final variable name character in "exprs". To be recognised, a name
+*     must begin with an alphabetic character and subsequently contain only
+*     alphanumeric characters or underscores. It must also appear in the
+*     list of defined variable names supplied to this function.
+*
+*     If the expression does not contain a name at the specified location,
+*     "*ivar" and "*iend" are set to -1 and no error results. However, if
+*     the expression contains a name but it is not in the list of defined
+*     variable names supplied, then an error is reported.
+*
+*     This function is case sensitive. The expression should not contain
+*     embedded white space.
+
+*  Parameters:
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function.
+*        This method name is used solely for constructing error messages.
+*     class
+*        Pointer to a constant null-terminated character string containing the
+*        class name of the Object being processed. This name is used solely
+*        for constructing error messages.
+*     exprs
+*        Pointer to a null-terminated string containing the expression
+*        to be parsed.
+*     istart
+*        Index of the first character in "exprs" to be considered by this
+*        function.
+*     nvar
+*        The number of defined variable names.
+*     var
+*        An array of pointers (with "nvar" elements) to null-terminated
+*        strings. Each of these should contain a variable name to be
+*        recognised. These strings are case sensitive and should contain
+*        no white space.
+*     ivar
+*        Pointer to an int in which to return the index in "vars" of the
+*        variable name found. If no variable name is found, a value of -1
+*        is returned.
+*     iend
+*        Pointer to an int in which to return the index in "exprs" of the
+*        final character which forms part of the variable name. If no variable
+*        name is found, a value of -1 is returned.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int found;                    /* Variable name recognised? */
+   int nc;                       /* Number of characters in variable name */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise. */
+   *ivar = -1;
+   *iend = -1;
+
+/* Determine if the characters in the expression starting at index
+   "istart" constitute a valid name. */
+   ParseName( exprs, istart, iend, status );
+
+/* If so, calculate the length of the name. */
+   if ( *iend >= istart ) {
+      nc = *iend - istart + 1;
+
+/* Loop to compare the name with the list of variable names
+   supplied. */
+      found = 0;
+      for ( *ivar = 0; *ivar < nvar; ( *ivar )++ ) {
+         found = ( nc == (int) strlen( var[ *ivar ] ) ) &&
+                 !strncmp( exprs + istart, var[ *ivar ], (size_t) nc );
+
+/* Break if the name is recognised. */
+         if ( found ) break;
+      }
+
+/* If it was not recognised, then report an error and reset the output
+   values. */
+      if ( !found ) {
+         astError( AST__UDVOF,
+                   "%s(%s): Undefined variable or function in the expression "
+                   "\"%.*s\".", status,
+                   method, class, *iend + 1, exprs );
+         *ivar = -1;
+         *iend = -1;
+      }
+   }
+}
+
+static double Poisson( Rcontext *context, double mean, int *status ) {
+/*
+*  Name:
+*     Poisson
+
+*  Purpose:
+*     Produce a pseudo-random sample from a Poisson distribution.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     double Poisson( Rcontext *context, double mean, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     On each invocation, this function returns a pseudo-random sample drawn
+*     from a Poisson distribution with a specified mean. A combination of
+*     methods is used, depending on the value of the mean. The algorithm is
+*     based on that given by Press et al. (Numerical Recipes), but
+*     re-implemented and extended.
+
+*  Parameters:
+*     context
+*        Pointer to an Rcontext structure which holds the random number
+*        generator's context between invocations.
+*     mean
+*        The mean of the Poisson distribution, which should not be
+*        negative.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A sample (which will only take integer values) from the Poisson
+*     distribution, or AST__BAD if the mean supplied is negative.
+
+*  Notes:
+*     - The sequence of numbers returned is determined by the "seed"
+*     value in the Rcontext structure supplied.
+*     - If the seed value is changed, the "active" flag must also be cleared
+*     so that this function can re-initiallise the Rcontext structure before
+*     generating the next pseudo-random number. The "active" flag should
+*     also be clear to force initialisation the first time an Rcontext
+*     structure is used.
+*     - This function does not perform error checking and does not generate
+*     errors. It will execute even if the global error status is set.
+*/
+
+/* Local Constants: */
+   const double small = 9.3;     /* "Small" distribution mean value */
+
+/* Local Variables: */
+   double pfract;                /* Probability of accepting sample */
+   double product;               /* Product of random samples */
+   double ran;                   /* Sample from Lorentzian distribution */
+   double result;                /* Result value to return */
+   static double beta;           /* Constant for forming acceptance ratio */
+   static double huge;           /* Large mean where std. dev. is negligible */
+   static double last_mean;      /* Value of "mean" on last invocation */
+   static double log_mean;       /* Logarithm of "mean" */
+   static double pi;             /* Value of pi */
+   static double ranmax;         /* Maximum safe value of "ran" */
+   static double root_2mean;     /* sqrt( 2.0 * mean ) */
+   static double sqrt_point9;    /* Square root of 0.9 */
+   static double thresh;         /* Threshold for product of samples */
+   static int init = 0;          /* Local initialisation performed? */
+
+   LOCK_MUTEX6
+
+/* If initialisation has not yet been performed, then perform it
+   now. */
+   if ( !init ) {
+
+/* Initialise the mean value from the previous invocation. */
+      last_mean = -1.0;
+
+/* Calculate simple constants. */
+      pi = acos( -1.0 );
+      sqrt_point9 = sqrt( 0.9 );
+
+/* Calculate the value of the distribution mean for which the smallest
+   representable deviation from the mean permitted by the machine
+   precision is one thousand standard deviations. */
+      huge = pow( 1.0e3 / DBL_EPSILON, 2.0 );
+
+/* Calculate the largest value such that
+   (0.9+(sqrt_point9*ranmax)*(sqrt_point9*ranmax)) doesn't overflow,
+   allowing a small margin for rounding error. */
+      ranmax = ( sqrt( DBL_MAX - 0.9 ) / sqrt( 0.9 ) ) *
+               ( 1.0 - 4.0 * DBL_EPSILON );
+
+/* Note that initialisation has been performed. */
+      init = 1;
+   }
+
+/* If the distribution mean is less than zero, then return a bad
+   result. */
+   if ( mean < 0.0 ) {
+      result = AST__BAD;
+
+/* If the mean is zero, then the result can only be zero. */
+   } else if ( mean == 0.0 ) {
+      result = 0.0;
+
+/* Otherwise, if the mean is sufficiently small, we can use the direct
+   method of summing a series of exponentially distributed random samples
+   and counting the number which occur before the mean is exceeded. This
+   is equivalent to multiplying a series of uniformly distributed
+   samples and counting the number which occur before the product
+   becomes less then an equivalent threshold. */
+   } else if ( mean <= small ) {
+
+/* If the mean has changed since the last invocation, store the new
+   mean and calculate a new threshold. */
+      if ( mean != last_mean ) {
+         last_mean = mean;
+         thresh = exp( -mean );
+      }
+
+/* Initialise the product and the result. */
+      product = 1.0;
+      result = -1.0;
+
+/* Multiply the random samples, counting the number needed to reach
+   the threshold. */
+      do {
+         product *= Rand( context, status );
+         result += 1.0;
+      } while ( product > thresh );
+
+/* Otherwise, if the distribution mean is large (but not huge), we
+   must use an indirect rejection method. */
+   } else if ( mean <= huge ) {
+
+/* If the mean has changed since the last invocation, then
+   re-calculate the constants required below. Note that because of the
+   restrictions we have placed on "mean", these calculations are safe
+   against overflow. */
+      if ( mean != last_mean ) {
+         last_mean = mean;
+         log_mean = log( mean );
+         root_2mean = sqrt( 2.0 * mean );
+         beta = mean * log_mean - LogGamma( mean + 1.0, status );
+      }
+
+/* Loop until a suitable random sample has been generated. */
+      do {
+         do {
+
+/* First transform a sample from a uniform distribution to obtain a
+   sample from a Lorentzian distribution. Check that the result is not so
+   large as to cause overflow later. Also check for overflow in the maths
+   library. If necessary, obtain a new sample. */
+            do {
+               errno = 0;
+               ran = tan( pi * Rand( context, status ) );
+            } while ( ( ran > ranmax ) ||
+                      ( ( errno == ERANGE ) &&
+                        ( ( ( ran >= 0.0 ) ? ran : -ran ) == HUGE_VAL ) ) );
+
+/* If OK, scale the sample and add a constant so that the sample's
+   distribution approximates the Poisson distribution we
+   require. Overflow is prevented by the check on "ran" above, together
+   with the restricted value of "mean". */
+            result = ran * root_2mean + mean;
+
+/* If the result is less than zero (where the Poisson distribution has
+   value zero), then obtain a new sample. */
+         } while ( result < 0.0 );
+
+/* Round down to an integer, so that the sample is valid for a Poisson
+   distribution. */
+         result = floor( result );
+
+/* Calculate the ratio between the required Poisson distribution and
+   the Lorentzian from which we have sampled (the factor of 0.9 prevents
+   this exceeding 1.0, and overflow is again prevented by the checks
+   performed above). */
+         ran *= sqrt_point9;
+         pfract = ( 0.9 + ran * ran ) *
+                  exp( result * log_mean - LogGamma( result + 1.0, status ) - beta );
+
+/* Accept the sample with this fractional probability, otherwise
+   obtain a new sample. */
+      } while ( Rand( context, status ) > pfract );
+
+/* If the mean is huge, the relative standard deviation will be
+   negligible compared to the machine precision. In such cases, the
+   probability of getting a result that differs from the mean is
+   effectively zero, so we can simply return the mean. */
+   } else {
+      result = mean;
+   }
+
+   UNLOCK_MUTEX6
+
+/* Return the result. */
+   return result;
+}
+
+static double Rand( Rcontext *context, int *status ) {
+/*
+*  Name:
+*     Rand
+
+*  Purpose:
+*     Produce a uniformly distributed pseudo-random number.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     double Rand( Rcontext *context, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     On each invocation, this function returns a pseudo-random number
+*     uniformly distributed in the range 0.0 to 1.0 (inclusive). The
+*     underlying algorithm is that used by the "ran2" function of Press et
+*     al. (Numerical Recipes), which has a long period and good statistical
+*     properties. This independent implementation returns double precision
+*     values.
+
+*  Parameters:
+*     context
+*        Pointer to an Rcontext structure which holds the random number
+*        generator's context between invocations.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The sequence of numbers returned is determined by the "seed"
+*     value in the Rcontext structure supplied.
+*     - If the seed value is changed, the "active" flag must also be cleared
+*     so that this function can re-initiallise the Rcontext structure before
+*     generating the next pseudo-random number. The "active" flag should
+*     also be clear to force initialisation the first time an Rcontext
+*     structure is used.
+*     - This function does not perform error checking and does not generate
+*     errors. It will execute even if the global error status is set.
+*/
+
+/* Local Constants: */
+   const long int a1 = 40014L;   /* Random number generator constants... */
+   const long int a2 = 40692L;
+   const long int m1 = 2147483563L;
+   const long int m2 = 2147483399L;
+   const long int q1 = 53668L;
+   const long int q2 = 52774L;
+   const long int r1 = 12211L;
+   const long int r2 = 3791L;
+   const int ntab =              /* Size of shuffle table */
+      AST_MATHMAP_RAND_CONTEXT_NTAB_;
+   const int nwarm = 8;          /* Number of warm-up iterations */
+
+/* Local Variables: */
+   double result;                /* Result value to return */
+   double scale;                 /* Scale factor for random integers */
+   double sum;                   /* Sum for forming normalisation constant */
+   int dbits;                    /* Approximate bits in double mantissa */
+   int irand;                    /* Loop counter for random integers */
+   int itab;                     /* Loop counter for shuffle table */
+   int lbits;                    /* Approximate bits used by generators */
+   long int seed;                /* Random number seed */
+   long int tmp;                 /* Temporary variable */
+   static double norm;           /* Normalisation constant */
+   static double scale0;         /* Scale decrement for successive integers */
+   static int init = 0;          /* Local initialisation performed? */
+   static int nrand;             /* Number of random integers to use */
+
+/* If the random number generator context is not active, then
+   initialise it. */
+   if ( !context->active ) {
+
+/* First, perform local initialisation for this function, if not
+   already done. */
+      LOCK_MUTEX4
+      if ( !init ) {
+
+/* Obtain the approximate number of bits used by the random integer
+   generator from the value "m1". */
+         (void) frexp( (double) m1, &lbits );
+
+/* Obtain the approximate number of bits used by the mantissa of the
+   double value we want to produce, allowing for the (unlikely)
+   possibility that the mantissa's radix isn't 2. */
+         dbits = (int) ceil( (double) DBL_MANT_DIG *
+                             log( (double) FLT_RADIX ) / log( 2.0 ) );
+
+/* Hence determine how many random integers we need to combine to
+   produce each double value, so that all the mantissa's bits will be
+   used. */
+         nrand = ( dbits + lbits - 1 ) / lbits;
+
+/* Calculate the scale factor by which each successive random
+   integer's contribution to the result is reduced so as to generate
+   progressively less significant bits. */
+         scale0 = 1.0 / (double) ( m1 - 1L );
+
+/* Loop to sum the maximum contributions from each random integer
+   (assuming that each takes the largest possible value, of "m1-1",
+   from which we will later subtract 1). This produces the normalisation
+   factor by which the result must be scaled so as to lie between 0.0 and
+   1.0 (inclusive). */
+         sum = 0.0;
+         scale = 1.0;
+         for ( irand = 0; irand < nrand; irand++ ) {
+            scale *= scale0;
+            sum += scale;
+         }
+         norm = 1.0 / ( sum * (double) ( m1 - 2L ) );
+
+/* Note that local initialisation has been done. */
+         init = 1;
+      }
+      UNLOCK_MUTEX4
+
+/* Obtain the seed value, enforcing positivity. */
+      seed = (long int) context->seed;
+      if ( seed < 1 ) seed = seed + LONG_MAX;
+      if ( seed < 1 ) seed = LONG_MAX;
+
+/* Initialise the random number generators with this seed. */
+      context->rand1 = context->rand2 = seed;
+
+/* Now loop to initialise the shuffle table with an initial set of
+   random values. We generate more values than required in order to "warm
+   up" the generator before recording values in the table. */
+      for ( itab = ntab + nwarm - 1; itab >= 0; itab-- ) {
+
+/* Repeatedly update "rand1" from the expression "(rand1*a1)%m1" while
+   avoiding overflow. */
+         tmp = context->rand1 / q1;
+         context->rand1 = a1 * ( context->rand1 - tmp * q1 ) - tmp * r1;
+         if ( context->rand1 < 0L ) context->rand1 += m1;
+
+/* After warming up, start recording values in the table. */
+         if ( itab < ntab ) context->table[ itab ] = context->rand1;
+      }
+
+/* Record the last entry in the table as the "previous" random
+   integer. */
+      context->random_int = context->table[ 0 ];
+
+/* Note the random number generator context is active. */
+      context->active = 1;
+   }
+
+/* Generate a random value. */
+/* ------------------------ */
+/* Initialise. */
+   result = 0.0;
+
+/* Loop to generate sufficient random integers to combine into a
+   double value. */
+   scale = norm;
+   for ( irand = 0; irand < nrand; irand++ ) {
+
+/* Update the first generator "rand1" from the expression
+   "(a1*rand1)%m1" while avoiding overflow. */
+      tmp = context->rand1 / q1;
+      context->rand1 = a1 * ( context->rand1 - tmp * q1 ) - tmp * r1;
+      if ( context->rand1 < 0L ) context->rand1 += m1;
+
+/* Similarly, update the second generator "rand2" from the expression
+   "(a2*rand2)%m2". */
+      tmp = context->rand2 / q2;
+      context->rand2 = a2 * ( context->rand2 - tmp * q2 ) - tmp * r2;
+      if ( context->rand2 < 0L ) context->rand2 += m2;
+
+/* Use the previous random integer to generate an index into the
+   shuffle table. */
+      itab = (int) ( context->random_int /
+                     ( 1L + ( m1 - 1L ) / (long int) ntab ) );
+
+/* The algorithm left by RFWS seems to have a bug that "itab" can
+   sometimes be outside the range of [0.,ntab-1] causing the context->table
+   array to be addressed out of bounds. To avoid this, use the
+   following sticking plaster, since I'm not sure what the correct fix is. */
+      if( itab < 0 ) itab = -itab;
+      itab = itab % ntab;
+
+/* Extract the table entry and replace it with a new random value from
+   the first generator "rand1". This is the Bays-Durham shuffle. */
+      context->random_int = context->table[ itab ];
+      context->table[ itab ] = context->rand1;
+
+/* Combine the extracted value with the latest value from the second
+   generator "rand2". */
+      context->random_int -= context->rand2;
+      if ( context->random_int < 1L ) context->random_int += m1 - 1L;
+
+/* Update the scale factor to apply to the resulting random integer
+   and accumulate its contribution to the result. */
+      scale *= scale0;
+      result += scale * (double) ( context->random_int - 1L );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a MathMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     MathMap member function (extends the astSetAttrib method inherited from
+*     the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a MathMap, the attribute
+*     and its value being specified by means of a string of the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in lower
+*     case with no white space present. The value to the right of the "="
+*     should be a suitable textual representation of the value to be assigned
+*     and this will be interpreted according to the attribute's data type.
+*     White space surrounding the value is only significant for string
+*     attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the MathMap.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+*/
+
+/* Local Vaiables: */
+   AstMathMap *this;             /* Pointer to the MathMap structure */
+   int ival;                     /* Integer attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the MathMap structure. */
+   this = (AstMathMap *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* Seed. */
+/* ----- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "seed= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetSeed( this, ival );
+
+/* SimpFI. */
+/* ------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "simpfi= %d %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetSimpFI( this, ival );
+
+/* SimpIF. */
+/* ------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "simpif= %d %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetSimpIF( this, ival );
+
+/* Pass any unrecognised setting to the parent method for further
+   interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a MathMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     MathMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a MathMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the MathMap.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMathMap *this;             /* Pointer to the MathMap structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the MathMap structure. */
+   this = (AstMathMap *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Seed. */
+/* ----- */
+   if ( !strcmp( attrib, "seed" ) ) {
+      result = astTestSeed( this );
+
+/* SimpFI. */
+/* ------- */
+   } else if ( !strcmp( attrib, "simpfi" ) ) {
+      result = astTestSimpFI( this );
+
+/* SimpIF. */
+/* ------- */
+   } else if ( !strcmp( attrib, "simpif" ) ) {
+      result = astTestSimpIF( this );
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *map, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a MathMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     AstPointSet *Transform( AstMapping *map, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     MathMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a MathMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required coordinate
+*     transformation.
+
+*  Parameters:
+*     map
+*        Pointer to the MathMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the MathMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstMathMap *this;             /* Pointer to MathMap to be applied */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   double **data_ptr;            /* Array of pointers to coordinate data */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *work;                 /* Workspace for intermediate results */
+   int idata;                    /* Loop counter for data pointer elements */
+   int ifun;                     /* Loop counter for functions */
+   int ncoord_in;                /* Number of coordinates per input point */
+   int ncoord_out;               /* Number of coordinates per output point */
+   int ndata;                    /* Number of data pointer elements filled */
+   int nfun;                     /* Number of functions to evaluate */
+   int npoint;                   /* Number of points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   work = NULL;
+
+/* Obtain a pointer to the MathMap. */
+   this = (AstMathMap *) map;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( map, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   transformation needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   and output PointSets and obtain pointers for accessing the input and output
+   coordinate values. */
+   ncoord_in = astGetNcoord( in );
+   ncoord_out = astGetNcoord( result );
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse transformation, according
+   to the direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( this ) ) forward = !forward;
+
+/* Obtain the number of transformation functions that must be
+   evaluated to perform the transformation. This will include any that
+   produce intermediate results from which the final results are
+   calculated. */
+   nfun = forward ? this->nfwd : this->ninv;
+
+/* If intermediate results are to be calculated, then allocate
+   workspace to hold them (each intermediate result being a vector of
+   "npoint" double values). */
+   if ( nfun > ncoord_out ) {
+      work = astMalloc( sizeof( double) *
+                        (size_t) ( npoint * ( nfun - ncoord_out ) ) );
+   }
+
+/* Also allocate space for an array to hold pointers to the input
+   data, intermediate results and output data. */
+   data_ptr = astMalloc( sizeof( double * ) * (size_t) ( ncoord_in + nfun ) );
+
+/* We now set up the "data_ptr" array to locate the data to be
+   processed. */
+   if ( astOK ) {
+
+/* The first elements of this array point at the input data
+   vectors. */
+      ndata = 0;
+      for ( idata = 0; idata < ncoord_in; idata++ ) {
+         data_ptr[ ndata++ ] = ptr_in[ idata ];
+      }
+
+/* The following elements point at successive vectors within the
+   workspace array (if allocated). These vectors will act first as output
+   arrays for intermediate results, and then as input arrays for
+   subsequent calculations which use these results. */
+      for ( idata = 0; idata < ( nfun - ncoord_out ); idata++ ) {
+         data_ptr[ ndata++ ] = work + ( idata * npoint );
+      }
+
+/* The final elements point at the output coordinate data arrays into
+   which the final results will be written. */
+      for ( idata = 0; idata < ncoord_out; idata++ ) {
+         data_ptr[ ndata++ ] = ptr_out[ idata ];
+      }
+
+/* Perform coordinate transformation. */
+/* ---------------------------------- */
+/* Loop to evaluate each transformation function in turn. */
+      for ( ifun = 0; ifun < nfun; ifun++ ) {
+
+/* Invoke the function that evaluates compiled expressions. Pass the
+   appropriate code and constants arrays, depending on the direction of
+   coordinate transformation, together with the required stack size. The
+   output array is the vector located by successive elements of the
+   "data_ptr" array (skipping the input data elements), while the
+   function has access to all previous elements of the "data_ptr" array
+   to locate the required input data. */
+         EvaluateFunction( &this->rcontext, npoint, (const double **) data_ptr,
+                           forward ? this->fwdcode[ ifun ] :
+                                     this->invcode[ ifun ],
+                           forward ? this->fwdcon[ ifun ] :
+                                     this->invcon[ ifun ],
+                           forward ? this->fwdstack : this->invstack,
+                           data_ptr[ ifun + ncoord_in ], status );
+      }
+   }
+
+/* Free the array of data pointers and any workspace allocated for
+   intermediate results. */
+   data_ptr = astFree( data_ptr );
+   if ( nfun > ncoord_out ) work = astFree( work );
+
+/* If an error occurred, then return a NULL pointer. If no output
+   PointSet was supplied, also delete any new one that may have been
+   created. */
+   if ( !astOK ) {
+      result = ( result == out ) ? NULL : astDelete( result );
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static void ValidateSymbol( const char *method, const char *class,
+                            const char *exprs, int iend, int sym,
+                            int *lpar, int **argcount, int **opensym,
+                            int *ncon, double **con, int *status ) {
+/*
+*  Name:
+*     ValidateSymbol
+
+*  Purpose:
+*     Validate a symbol in an expression.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     void ValidateSymbol( const char *method, const char *class,
+*                          const char *exprs, int iend, int sym, int *lpar,
+*                          int **argcount, int **opensym, int *ncon,
+*                          double **con, int *status )
+
+*  Class Membership:
+*     MathMap member function.
+
+*  Description:
+*     This function validates an identified standard symbol during
+*     compilation of an expression. Its main task is to keep track of the
+*     level of parenthesis in the expression and to count the number of
+*     arguments supplied to functions at each level of parenthesis (for
+*     nested function calls). On this basis it is able to interpret and
+*     accept or reject symbols which represent function calls, parentheses
+*     and delimiters. Other symbols are accepted automatically.
+
+*  Parameters:
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function.
+*        This method name is used solely for constructing error messages.
+*     class
+*        Pointer to a constant null-terminated character string containing the
+*        class name of the Object being processed. This name is used solely
+*        for constructing error messages.
+*     exprs
+*        Pointer to a null-terminated string containing the expression
+*        being parsed. This is only used for constructing error messages.
+*     iend
+*        Index in "exprs" of the last character belonging to the most
+*        recently identified symbol. This is only used for constructing error
+*        messages.
+*     sym
+*        Index in the static "symbol" array of the most recently identified
+*        symbol in the expression. This is the symbol to be verified.
+*     lpar
+*        Pointer to an int which holds the current level of parenthesis. On
+*        the first invocation, this should be zero. The returned value should
+*        be passed to subsequent invocations.
+*     argcount
+*        Address of a pointer to a dynamically allocated array of int in
+*        which argument count information is maintained for each level of
+*        parenthesis (e.g. for nested function calls). On the first invocation,
+*        "*argcount" should be NULL. This function will allocate the required
+*        space as needed and update this pointer. The returned pointer value
+*        should be passed to subsequent invocations.
+*
+*        The allocated space must be freed by the caller (using astFree) when
+*        no longer required.
+*     opensym
+*        Address of a pointer to a dynamically allocated array of int, in which
+*        information is maintained about the functions associated with each
+*        level of parenthesis (e.g. for nested function calls). On the first
+*        invocation, "*opensym" should be NULL. This function will allocate the
+*        required space as needed and update this pointer. The returned pointer
+*        value should be passed to subsequent invocations.
+*
+*        The allocated space must be freed by the caller (using astFree) when
+*        no longer required.
+*     ncon
+*        Pointer to an int which holds a count of the constants associated
+*        with the expression (and determines the size of the "*con" array).
+*        This function will update the count to reflect any new constants
+*        appended to the "*con" array and the returned value should be passed
+*        to subsequent invocations.
+*     con
+*        Address of a pointer to a dynamically allocated array of double, in
+*        which the constants associated with the expression being parsed are
+*        accumulated. On entry, "*con" should point at a dynamic array with
+*        at least "*ncon" elements containing existing constants (or may be
+*        NULL if no constants have yet been stored). This function will
+*        allocate the required space as needed and update this pointer (and
+*        "*ncon") appropriately. The returned pointer value should be passed
+*        to subsequent invocations.
+*
+*        The allocated space must be freed by the caller (using astFree) when
+*        no longer required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The dynamically allocated arrays normally returned by this function
+*     will be freed and NULL pointers will be returned if this function is
+*     invoked with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Check the global error status, but do not return at this point
+   because dynamic arrays may require freeing. */
+   if ( astOK ) {
+
+/* Check if the symbol is a comma. */
+      if ( ( symbol[ sym ].text[ 0 ] == ',' ) &&
+           ( symbol[ sym ].text[ 1 ] == '\0' ) ) {
+
+/* A comma is only used to delimit function arguments. If the current
+   level of parenthesis is zero, or the symbol which opened the current
+   level of parenthesis was not a function call (indicated by an argument
+   count of zero at the current level of parenthesis), then report an
+   error. */
+         if ( ( *lpar <= 0 ) || ( ( *argcount )[ *lpar - 1 ] == 0 ) ) {
+            astError( AST__COMIN,
+                      "%s(%s): Spurious comma encountered in the expression "
+                      "\"%.*s\".", status,
+                      method, class, iend + 1, exprs );
+
+/* If a comma is valid, then increment the argument count at the
+   current level of parenthesis. */
+         } else {
+            ( *argcount )[ *lpar - 1 ]++;
+         }
+
+/* If the symbol is not a comma, check if it increases the current
+   level of parenthesis. */
+      } else if ( symbol[ sym ].parincrement > 0 ) {
+
+/* Increase the size of the arrays which hold parenthesis level
+   information and check for errors. */
+         *argcount = astGrow( *argcount, *lpar + 1, sizeof( int ) );
+         *opensym = astGrow( *opensym, *lpar + 1, sizeof( int ) );
+         if ( astOK ) {
+
+/* Increment the level of parenthesis and initialise the argument
+   count at the new level. This count is set to zero if the symbol which
+   opens the parenthesis level is not a function call (indicated by a
+   zero "nargs" entry in the symbol data), and it subsequently remains at
+   zero. If the symbol is a function call, the argument count is
+   initially set to 1 and increments whenever a comma is encountered at
+   this parenthesis level. */
+            ( *argcount )[ ++( *lpar ) - 1 ] = ( symbol[ sym ].nargs != 0 );
+
+/* Remember the symbol which opened this parenthesis level. */
+            ( *opensym )[ *lpar - 1 ] = sym;
+         }
+
+/* Check if the symbol decreases the current parenthesis level. */
+      } else if ( symbol[ sym ].parincrement < 0 ) {
+
+/* Ensure that the parenthesis level is not already at zero. If it is,
+   then there is a missing left parenthesis in the expression being
+   compiled, so report an error. */
+         if ( *lpar == 0 ) {
+            astError( AST__MLPAR,
+                      "%s(%s): Missing left parenthesis in the expression "
+                      "\"%.*s\".", status,
+                      method, class, iend + 1, exprs );
+
+/* If the parenthesis level is valid and the symbol which opened this
+   level of parenthesis was a function call with a fixed number of
+   arguments (indicated by a positive "nargs" entry in the symbol data),
+   then we must check the number of function arguments which have been
+   encountered. */
+         } else if ( symbol[ ( *opensym )[ *lpar - 1 ] ].nargs > 0 ) {
+
+/* Report an error if the number of arguments is wrong. */
+            if ( ( *argcount )[ *lpar - 1 ] !=
+                 symbol[ ( *opensym )[ *lpar - 1 ] ].nargs ) {
+               astError( AST__WRNFA,
+                         "%s(%s): Wrong number of function arguments in the "
+                         "expression \"%.*s\".", status,
+                         method, class, iend + 1, exprs );
+
+/* If the number of arguments is valid, decrement the parenthesis
+   level. */
+            } else {
+               ( *lpar )--;
+            }
+
+/* If the symbol which opened this level of parenthesis was a function
+   call with a variable number of arguments (indicated by a negative
+   "nargs" entry in the symbol data), then we must check and process the
+   number of function arguments. */
+         } else if ( symbol[ ( *opensym )[ *lpar - 1 ] ].nargs < 0 ) {
+
+/* Check that the minimum required number of arguments have been
+   supplied. Report an error if they have not. */
+            if ( ( *argcount )[ *lpar - 1 ] <
+                 ( -symbol[ ( *opensym )[ *lpar - 1 ] ].nargs ) ) {
+               astError( AST__WRNFA,
+                         "%s(%s): Insufficient function arguments in the "
+                         "expression \"%.*s\".", status,
+                         method, class, iend + 1, exprs );
+
+/* If the number of arguments is valid, increase the size of the
+   constants array and check for errors. */
+            } else {
+               *con = astGrow( *con, *ncon + 1, sizeof( double ) );
+               if ( astOK ) {
+
+/* Append the argument count to the end of the array of constants and
+   decrement the parenthesis level. */
+                  ( *con )[ ( *ncon )++ ] =
+                     (double) ( *argcount )[ --( *lpar ) ];
+               }
+            }
+
+/* Finally, if the symbol which opened this level of parenthesis was
+   not a function call ("nargs" entry in the symbol data is zero), then
+   decrement the parenthesis level. In this case there is no need to
+   check the argument count, because it will not have been
+   incremented. */
+         } else {
+            ( *lpar )--;
+         }
+      }
+   }
+
+/* If an error occurred (or the global error status was set on entry),
+   then reset the parenthesis level and free any memory which may have
+   been allocated. */
+   if ( !astOK ) {
+      *lpar = 0;
+      if ( *argcount ) *argcount = astFree( *argcount );
+      if ( *opensym ) *opensym = astFree( *opensym );
+      if ( *con ) *con = astFree( *con );
+   }
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     Seed
+
+*  Purpose:
+*     Random number seed for a MathMap.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute, which may take any integer value, determines the
+*     sequence of random numbers produced by the random number functions in
+*     MathMap expressions. It is set to an unpredictable default value when
+*     a MathMap is created, so that by default each MathMap uses a different
+*     set of random numbers.
+*
+*     If required, you may set this Seed attribute to a value of your
+*     choosing in order to produce repeatable behaviour from the random
+*     number functions. You may also enquire the Seed value (e.g. if an
+*     initially unpredictable value has been used) and then use it to
+*     reproduce the resulting sequence of random numbers, either from the
+*     same MathMap or from another one.
+*
+*     Clearing the Seed attribute gives it a new unpredictable default
+*     value.
+
+*  Applicability:
+*     MathMap
+*        All MathMaps have this attribute.
+*att--
+*/
+/* Clear the Seed value by setting it to a new unpredictable value
+   produced by DefaultSeed and clearing the "seed_set" flag in the
+   MathMap's random number generator context. Also clear the "active"
+   flag, so that the generator will be re-initialised to use this seed
+   when it is next invoked. */
+astMAKE_CLEAR(MathMap,Seed,rcontext.seed,( this->rcontext.seed_set = 0,
+                                           this->rcontext.active = 0,
+                                           DefaultSeed( &this->rcontext, status ) ))
+
+/* Return the "seed" value from the random number generator
+   context. */
+astMAKE_GET(MathMap,Seed,int,0,this->rcontext.seed)
+
+/* Store the new seed value in the MathMap's random number generator
+   context and set the context's "seed_set" flag. Also clear the "active"
+   flag, so that the generator will be re-initialised to use this seed
+   when it is next invoked. */
+astMAKE_SET(MathMap,Seed,int,rcontext.seed,( this->rcontext.seed_set = 1,
+                                             this->rcontext.active = 0,
+                                             value ))
+
+/* Test the "seed_set" flag in the random number generator context. */
+astMAKE_TEST(MathMap,Seed,( this->rcontext.seed_set ))
+
+/*
+*att++
+*  Name:
+*     SimpFI
+
+*  Purpose:
+*     Forward-inverse MathMap pairs simplify?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+c     This attribute should be set to a non-zero value if applying a
+c     MathMap's forward transformation, followed immediately by the matching
+c     inverse transformation will always restore the original set of
+c     coordinates. It indicates that AST may replace such a sequence of
+c     operations by an identity Mapping (a UnitMap) if it is encountered
+c     while simplifying a compound Mapping (e.g. using astSimplify).
+f     This attribute should be set to a non-zero value if applying a
+f     MathMap's forward transformation, followed immediately by the matching
+f     inverse transformation will always restore the original set of
+f     coordinates. It indicates that AST may replace such a sequence of
+f     operations by an identity Mapping (a UnitMap) if it is encountered
+f     while simplifying a compound Mapping (e.g. using AST_SIMPLIFY).
+*
+*     By default, the SimpFI attribute is zero, so that AST will not perform
+*     this simplification unless you have set SimpFI to indicate that it is
+*     safe to do so.
+
+*  Applicability:
+*     MathMap
+*        All MathMaps have this attribute.
+
+*  Notes:
+*     - For simplification to occur, the two MathMaps must be in series and
+*     be identical (with textually identical transformation
+*     functions). Functional equivalence is not sufficient.
+*     - The consent of both MathMaps is required before simplification can
+*     take place. If either has a SimpFI value of zero, then simplification
+*     will not occur.
+*     - The SimpFI attribute controls simplification only in the case where
+*     a MathMap's forward transformation is followed by the matching inverse
+*     transformation. It does not apply if an inverse transformation is
+*     followed by a forward transformation. This latter case is controlled
+*     by the SimpIF attribute.
+c     - The "forward" and "inverse" transformations referred to are those
+c     defined when the MathMap is created (corresponding to the "fwd" and
+c     "inv" parameters of its constructor function). If the MathMap is
+c     inverted (i.e. its Invert attribute is non-zero), then the role of the
+c     SimpFI and SimpIF attributes will be interchanged.
+f     - The "forward" and "inverse" transformations referred to are those
+f     defined when the MathMap is created (corresponding to the FWD and
+f     INV arguments of its constructor function). If the MathMap is
+f     inverted (i.e. its Invert attribute is non-zero), then the role of the
+f     SimpFI and SimpIF attributes will be interchanged.
+*att--
+*/
+/* Clear the SimpFI value by setting it to -INT_MAX. */
+astMAKE_CLEAR(MathMap,SimpFI,simp_fi,-INT_MAX)
+
+/* Supply a default of 0 if no SimpFI value has been set. */
+astMAKE_GET(MathMap,SimpFI,int,0,( ( this->simp_fi != -INT_MAX ) ?
+                                   this->simp_fi : 0 ))
+
+/* Set a SimpFI value of 1 if any non-zero value is supplied. */
+astMAKE_SET(MathMap,SimpFI,int,simp_fi,( value != 0 ))
+
+/* The SimpFI value is set if it is not -INT_MAX. */
+astMAKE_TEST(MathMap,SimpFI,( this->simp_fi != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     SimpIF
+
+*  Purpose:
+*     Inverse-forward MathMap pairs simplify?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+c     This attribute should be set to a non-zero value if applying a
+c     MathMap's inverse transformation, followed immediately by the matching
+c     forward transformation will always restore the original set of
+c     coordinates. It indicates that AST may replace such a sequence of
+c     operations by an identity Mapping (a UnitMap) if it is encountered
+c     while simplifying a compound Mapping (e.g. using astSimplify).
+f     This attribute should be set to a non-zero value if applying a
+f     MathMap's inverse transformation, followed immediately by the matching
+f     forward transformation will always restore the original set of
+f     coordinates. It indicates that AST may replace such a sequence of
+f     operations by an identity Mapping (a UnitMap) if it is encountered
+f     while simplifying a compound Mapping (e.g. using AST_SIMPLIFY).
+*
+*     By default, the SimpIF attribute is zero, so that AST will not perform
+*     this simplification unless you have set SimpIF to indicate that it is
+*     safe to do so.
+
+*  Applicability:
+*     MathMap
+*        All MathMaps have this attribute.
+
+*  Notes:
+*     - For simplification to occur, the two MathMaps must be in series and
+*     be identical (with textually identical transformation
+*     functions). Functional equivalence is not sufficient.
+*     - The consent of both MathMaps is required before simplification can
+*     take place. If either has a SimpIF value of zero, then simplification
+*     will not occur.
+*     - The SimpIF attribute controls simplification only in the case where
+*     a MathMap's inverse transformation is followed by the matching forward
+*     transformation. It does not apply if a forward transformation is
+*     followed by an inverse transformation. This latter case is controlled
+*     by the SimpFI attribute.
+c     - The "forward" and "inverse" transformations referred to are those
+c     defined when the MathMap is created (corresponding to the "fwd" and
+c     "inv" parameters of its constructor function). If the MathMap is
+c     inverted (i.e. its Invert attribute is non-zero), then the role of the
+c     SimpFI and SimpIF attributes will be interchanged.
+f     - The "forward" and "inverse" transformations referred to are those
+f     defined when the MathMap is created (corresponding to the FWD and
+f     INV arguments of its constructor function). If the MathMap is
+f     inverted (i.e. its Invert attribute is non-zero), then the role of the
+f     SimpFI and SimpIF attributes will be interchanged.
+*att--
+*/
+/* Clear the SimpIF value by setting it to -INT_MAX. */
+astMAKE_CLEAR(MathMap,SimpIF,simp_if,-INT_MAX)
+
+/* Supply a default of 0 if no SimpIF value has been set. */
+astMAKE_GET(MathMap,SimpIF,int,0,( ( this->simp_if != -INT_MAX ) ?
+                                   this->simp_if : 0 ))
+
+/* Set a SimpIF value of 1 if any non-zero value is supplied. */
+astMAKE_SET(MathMap,SimpIF,int,simp_if,( value != 0 ))
+
+/* The SimpIF value is set if it is not -INT_MAX. */
+astMAKE_TEST(MathMap,SimpIF,( this->simp_if != -INT_MAX ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for MathMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for MathMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstMathMap *in;               /* Pointer to input MathMap */
+   AstMathMap *out;              /* Pointer to output MathMap */
+   int ifun;                     /* Loop counter for functions */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output MathMaps. */
+   in = (AstMathMap *) objin;
+   out = (AstMathMap *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output MathMap. */
+   out->fwdfun = NULL;
+   out->invfun = NULL;
+   out->fwdcode = NULL;
+   out->invcode = NULL;
+   out->fwdcon = NULL;
+   out->invcon = NULL;
+
+/* Now allocate and initialise each of the output pointer arrays
+   required. */
+   if ( in->fwdfun ) {
+      MALLOC_POINTER_ARRAY( out->fwdfun, char *, out->nfwd )
+   }
+   if ( in->invfun ) {
+      MALLOC_POINTER_ARRAY( out->invfun, char *, out->ninv )
+   }
+   if ( in->fwdcode ) {
+      MALLOC_POINTER_ARRAY( out->fwdcode, int *, out->nfwd )
+   }
+   if ( in->invcode ) {
+      MALLOC_POINTER_ARRAY( out->invcode, int *, out->ninv )
+   }
+   if ( in->fwdcon ) {
+      MALLOC_POINTER_ARRAY( out->fwdcon, double *, out->nfwd )
+   }
+   if ( in->invcon ) {
+      MALLOC_POINTER_ARRAY( out->invcon, double *, out->ninv )
+   }
+
+/* If OK, loop to make copies of the data (where available) associated
+   with each forward transformation function, storing pointers to the
+   copy in the output pointer arrays allocated above. */
+   if ( astOK ) {
+      for ( ifun = 0; ifun < out->nfwd; ifun++ ) {
+         if ( in->fwdfun && in->fwdfun[ ifun ] ) {
+            out->fwdfun[ ifun ] = astStore( NULL, in->fwdfun[ ifun ],
+                                            astSizeOf( in->fwdfun[ ifun ] ) );
+         }
+         if ( in->fwdcode && in->fwdcode[ ifun ] ) {
+            out->fwdcode[ ifun ] = astStore( NULL, in->fwdcode[ ifun ],
+                                            astSizeOf( in->fwdcode[ ifun ] ) );
+         }
+         if ( in->fwdcon && in->fwdcon[ ifun ] ) {
+            out->fwdcon[ ifun ] = astStore( NULL, in->fwdcon[ ifun ],
+                                            astSizeOf( in->fwdcon[ ifun ] ) );
+         }
+         if ( !astOK ) break;
+      }
+   }
+
+/* Repeat this process for the inverse transformation functions. */
+   if ( astOK ) {
+      for ( ifun = 0; ifun < out->ninv; ifun++ ) {
+         if ( in->invfun && in->invfun[ ifun ] ) {
+            out->invfun[ ifun ] = astStore( NULL, in->invfun[ ifun ],
+                                            astSizeOf( in->invfun[ ifun ] ) );
+         }
+         if ( in->invcode && in->invcode[ ifun ] ) {
+            out->invcode[ ifun ] = astStore( NULL, in->invcode[ ifun ],
+                                            astSizeOf( in->invcode[ ifun ] ) );
+         }
+         if ( in->invcon && in->invcon[ ifun ] ) {
+            out->invcon[ ifun ] = astStore( NULL, in->invcon[ ifun ],
+                                            astSizeOf( in->invcon[ ifun ] ) );
+         }
+         if ( !astOK ) break;
+      }
+   }
+
+/* If an error occurred, clean up by freeing all output memory
+   allocated above. */
+   if ( !astOK ) {
+      FREE_POINTER_ARRAY( out->fwdfun, out->nfwd )
+      FREE_POINTER_ARRAY( out->invfun, out->ninv )
+      FREE_POINTER_ARRAY( out->fwdcode, out->nfwd )
+      FREE_POINTER_ARRAY( out->invcode, out->ninv )
+      FREE_POINTER_ARRAY( out->fwdcon, out->nfwd )
+      FREE_POINTER_ARRAY( out->invcon, out->ninv )
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for MathMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for MathMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstMathMap *this;             /* Pointer to MathMap */
+
+/* Obtain a pointer to the MathMap structure. */
+   this = (AstMathMap *) obj;
+
+/* Free all memory allocated by the MathMap. */
+   FREE_POINTER_ARRAY( this->fwdfun, this->nfwd )
+   FREE_POINTER_ARRAY( this->invfun, this->ninv )
+   FREE_POINTER_ARRAY( this->fwdcode, this->nfwd )
+   FREE_POINTER_ARRAY( this->invcode, this->ninv )
+   FREE_POINTER_ARRAY( this->fwdcon, this->nfwd )
+   FREE_POINTER_ARRAY( this->invcon, this->ninv )
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for MathMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the MathMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the MathMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define COMMENT_LEN 150          /* Maximum length of a comment string */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstMathMap *this;             /* Pointer to the MathMap structure */
+   char comment[ COMMENT_LEN + 1 ]; /* Buffer for comment strings */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword strings */
+   int ifun;                     /* Loop counter for functions */
+   int invert;                   /* MathMap inverted? */
+   int ival;                     /* Integer attribute value */
+   int nin;                      /* True number of input coordinates */
+   int nout;                     /* True number of output coordinates */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the MathMap structure. */
+   this = (AstMathMap *) this_object;
+
+/* Determine if the MathMap is inverted and obtain the "true" number
+   of input and output coordinates by un-doing the effects of any
+   inversion. */
+   invert = astGetInvert( this );
+   nin = !invert ? astGetNin( this ) : astGetNout( this );
+   nout = !invert ? astGetNout( this ) : astGetNin( this );
+
+/* Write out values representing the instance variables for the
+   MathMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Number of forward transformation functions. */
+/* ------------------------------------------- */
+/* We regard this value as set if it differs from the number of output
+   coordinates for the MathMap. */
+   set = ( this->nfwd != nout );
+   astWriteInt( channel, "Nfwd", set, 0, this->nfwd,
+                "Number of forward transformation functions" );
+
+/* Forward transformation functions. */
+/* --------------------------------- */
+/* Loop to write out each forward transformation function, generating
+   a suitable keyword and comment for each one. */
+   for ( ifun = 0; ifun < this->nfwd; ifun++ ) {
+      (void) sprintf( key, "Fwd%d", ifun + 1 );
+      (void) sprintf( comment, "Forward function %d", ifun + 1 );
+      astWriteString( channel, key, 1, 1, this->fwdfun[ ifun ], comment );
+   }
+
+/* Number of inverse transformation functions. */
+/* ------------------------------------------- */
+/* We regard this value as set if it differs from the number of input
+   coordinates for the MathMap. */
+   set = ( this->ninv != nin );
+   astWriteInt( channel, "Ninv", set, 0, this->ninv,
+                "Number of inverse transformation functions" );
+
+/* Inverse transformation functions. */
+/* --------------------------------- */
+/* Similarly, loop to write out each inverse transformation
+   function. */
+   for ( ifun = 0; ifun < this->ninv; ifun++ ) {
+      (void) sprintf( key, "Inv%d", ifun + 1 );
+      (void) sprintf( comment, "Inverse function %d", ifun + 1 );
+      astWriteString( channel, key, 1, 1, this->invfun[ ifun ], comment );
+   }
+
+/* SimpFI. */
+/* ------- */
+/* Write out the forward-inverse simplification flag. */
+   set = TestSimpFI( this, status );
+   ival = set ? GetSimpFI( this, status ) : astGetSimpFI( this );
+   astWriteInt( channel, "SimpFI", set, 0, ival,
+                ival ? "Forward-inverse pairs may simplify" :
+                       "Forward-inverse pairs do not simplify" );
+
+/* SimpIF. */
+/* ------- */
+/* Write out the inverse-forward simplification flag. */
+   set = TestSimpIF( this, status );
+   ival = set ? GetSimpIF( this, status ) : astGetSimpIF( this );
+   astWriteInt( channel, "SimpIF", set, 0, ival,
+                ival ? "Inverse-forward pairs may simplify" :
+                       "Inverse-forward pairs do not simplify" );
+
+/* Seed. */
+/* ----- */
+/* Write out any random number seed value which is set. Prefix this with
+   a separate flag which indicates if the seed has been set. */
+   set = TestSeed( this, status );
+   ival = set ? GetSeed( this, status ) : astGetSeed( this );
+   astWriteInt( channel, "Seeded", set, 0, set,
+                set? "Explicit random number seed set" :
+                     "No random number seed set" );
+   astWriteInt( channel, "Seed", set, 0, ival,
+                set ? "Random number seed value" :
+                      "Default random number seed used" );
+
+/* Undefine macros local to this function. */
+#undef COMMENT_LEN
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAMathMap and astCheckMathMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(MathMap,Mapping)
+astMAKE_CHECK(MathMap)
+
+AstMathMap *astMathMap_( int nin, int nout,
+                         int nfwd, const char *fwd[],
+                         int ninv, const char *inv[],
+                         const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astMathMap
+
+*  Purpose:
+*     Create a MathMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     AstMathMap *astMathMap( int nin, int nout,
+*                             int nfwd, const char *fwd[],
+*                             int ninv, const char *inv[],
+*                             const char *options, ..., int *status )
+
+*  Class Membership:
+*     MathMap constructor.
+
+*  Description:
+*     This function creates a new MathMap and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     nin
+*        Number of input variables for the MathMap.
+*     nout
+*        Number of output variables for the MathMap.
+*     nfwd
+*        The number of forward transformation functions being supplied.
+*        This must be at least equal to "nout".
+*     fwd
+*        Pointer to an array, with "nfwd" elements, of pointers to null
+*        terminated strings which contain each of the forward transformation
+*        functions.
+*     ninv
+*        The number of inverse transformation functions being supplied.
+*        This must be at least equal to "nin".
+*     inv
+*        Pointer to an array, with "ninv" elements, of pointers to null
+*        terminated strings which contain each of the inverse transformation
+*        functions.
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new MathMap. The syntax used is the same as
+*        for the astSet method and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of arguments may follow it in order to
+*        supply values to be substituted for these specifiers. The
+*        rules for supplying these are identical to those for the
+*        astSet method (and for the C "printf" function).
+
+*  Returned Value:
+*     A pointer to the new MathMap.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic MathMap constructor which is
+*     available via the protected interface to the MathMap class.  A
+*     public interface is provided by the astMathMapId_ function.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMathMap *new;              /* Pointer to new MathMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the MathMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitMathMap( NULL, sizeof( AstMathMap ), !class_init, &class_vtab,
+                         "MathMap", nin, nout, nfwd, fwd, ninv, inv );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new MathMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new MathMap. */
+   return new;
+}
+
+AstMathMap *astMathMapId_( int nin, int nout,
+                           int nfwd, const char *fwd[],
+                           int ninv, const char *inv[],
+                           const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astMathMap
+f     AST_MATHMAP
+
+*  Purpose:
+*     Create a MathMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "mathmap.h"
+c     AstMathMap *astMathMap( int nin, int nout,
+c                             int nfwd, const char *fwd[],
+c                             int ninv, const char *inv[],
+c                             const char *options, ... )
+f     RESULT = AST_MATHMAP( NIN, NOUT, NFWD, FWD, NINV, INV, OPTIONS, STATUS )
+
+*  Class Membership:
+*     MathMap constructor.
+
+*  Description:
+*     This function creates a new MathMap and optionally initialises its
+*     attributes.
+*
+c     A MathMap is a Mapping which allows you to specify a set of forward
+c     and/or inverse transformation functions using arithmetic operations
+c     and mathematical functions similar to those available in C. The
+c     MathMap interprets these functions at run-time, whenever its forward
+c     or inverse transformation is required. Because the functions are not
+c     compiled in the normal sense (unlike an IntraMap), they may be used to
+c     describe coordinate transformations in a transportable manner. A
+c     MathMap therefore provides a flexible way of defining new types of
+c     Mapping whose descriptions may be stored as part of a dataset and
+c     interpreted by other programs.
+f     A MathMap is a Mapping which allows you to specify a set of forward
+f     and/or inverse transformation functions using arithmetic operations
+f     and mathematical functions similar to those available in Fortran. The
+f     MathMap interprets these functions at run-time, whenever its forward
+f     or inverse transformation is required. Because the functions are not
+f     compiled in the normal sense (unlike an IntraMap), they may be used to
+f     describe coordinate transformations in a transportable manner. A
+f     MathMap therefore provides a flexible way of defining new types of
+f     Mapping whose descriptions may be stored as part of a dataset and
+f     interpreted by other programs.
+
+*  Parameters:
+c     nin
+f     NIN = INTEGER
+*        Number of input variables for the MathMap. This determines the
+*        value of its Nin attribute.
+c     nout
+f     NOUT = INTEGER
+*        Number of output variables for the MathMap. This determines the
+*        value of its Nout attribute.
+c     nfwd
+f     NFWD = INTEGER
+*        The number of forward transformation functions being supplied.
+c        This must be at least equal to "nout", but may be increased to
+f        This must be at least equal to NOUT, but may be increased to
+*        accommodate any additional expressions which define intermediate
+*        variables for the forward transformation (see the "Calculating
+*        Intermediate Values" section below).
+c     fwd
+f     FWD = CHARACTER * ( * )( NFWD )
+c        An array (with "nfwd" elements) of pointers to null terminated strings
+c        which contain the expressions defining the forward transformation.
+f        An array which contains the expressions defining the forward
+f        transformation.
+*        The syntax of these expressions is described below.
+c     ninv
+f     NINV = INTEGER
+*        The number of inverse transformation functions being supplied.
+c        This must be at least equal to "nin", but may be increased to
+f        This must be at least equal to NIN, but may be increased to
+*        accommodate any additional expressions which define intermediate
+*        variables for the inverse transformation (see the "Calculating
+*        Intermediate Values" section below).
+c     inv
+f     INV = CHARACTER * ( * )( NINV )
+c        An array (with "ninv" elements) of pointers to null terminated strings
+c        which contain the expressions defining the inverse transformation.
+f        An array which contains the expressions defining the inverse
+f        transformation.
+*        The syntax of these expressions is described below.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new MathMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new MathMap. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMathMap()
+f     AST_MATHMAP = INTEGER
+*        A pointer to the new MathMap.
+
+*  Defining Transformation Functions:
+c     A MathMap's transformation functions are supplied as a set of
+c     expressions in an array of character strings. Normally you would
+c     supply the same number of expressions for the forward transformation,
+c     via the "fwd" parameter, as there are output variables (given by the
+c     MathMap's Nout attribute). For instance, if Nout is 2 you might use:
+c     - "r = sqrt( x * x + y * y )"
+c     - "theta = atan2( y, x )"
+c
+c     which defines a transformation from Cartesian to polar
+c     coordinates. Here, the variables that appear on the left of each
+c     expression ("r" and "theta") provide names for the output variables
+c     and those that appear on the right ("x" and "y") are references to
+c     input variables.
+f     A MathMap's transformation functions are supplied as a set of
+f     expressions in an array of character strings. Normally you would
+f     supply the same number of expressions for the forward transformation,
+f     via the FWD argument, as there are output variables (given by the
+f     MathMap's Nout attribute). For instance, if Nout is 2 you might use:
+f     - 'R = SQRT( X * X + Y * Y )'
+f     - 'THETA = ATAN2( Y, X )'
+f
+f     which defines a transformation from Cartesian to polar
+f     coordinates. Here, the variables that appear on the left of each
+f     expression (R and THETA) provide names for the output variables and
+f     those that appear on the right (X and Y) are references to input
+f     variables.
+*
+c     To complement this, you must also supply expressions for the inverse
+c     transformation via the "inv" parameter.  In this case, the number of
+c     expressions given would normally match the number of MathMap input
+c     coordinates (given by the Nin attribute).  If Nin is 2, you might use:
+c     - "x = r * cos( theta )"
+c     - "y = r * sin( theta )"
+c
+c     which expresses the transformation from polar to Cartesian
+c     coordinates. Note that here the input variables ("x" and "y") are
+c     named on the left of each expression, and the output variables ("r"
+c     and "theta") are referenced on the right.
+f     To complement this, you must also supply expressions for the inverse
+f     transformation via the INV argument.  In this case, the number of
+f     expressions given would normally match the number of MathMap input
+f     coordinates (given by the Nin attribute).  If Nin is 2, you might use:
+f     - 'X = R * COS( THETA )'
+f     - 'Y = R * SIN( THETA )'
+f
+f     which expresses the transformation from polar to Cartesian
+f     coordinates. Note that here the input variables (X and Y) are named on
+f     the left of each expression, and the output variables (R and THETA)
+f     are referenced on the right.
+*
+*     Normally, you cannot refer to a variable on the right of an expression
+*     unless it is named on the left of an expression in the complementary
+*     set of functions. Therefore both sets of functions (forward and
+*     inverse) must be formulated using the same consistent set of variable
+*     names. This means that if you wish to leave one of the transformations
+*     undefined, you must supply dummy expressions which simply name each of
+*     the output (or input) variables.  For example, you might use:
+c     - "x"
+c     - "y"
+f     - 'X'
+f     - 'Y'
+*
+*     for the inverse transformation above, which serves to name the input
+*     variables but without defining an inverse transformation.
+
+*  Calculating Intermediate Values:
+c     It is sometimes useful to calculate intermediate values and then to
+c     use these in the final expressions for the output (or input)
+c     variables. This may be done by supplying additional expressions for
+c     the forward (or inverse) transformation functions. For instance, the
+c     following array of five expressions describes 2-dimensional pin-cushion
+c     distortion:
+c     - "r = sqrt( xin * xin + yin * yin )"
+c     - "rout = r * ( 1 + 0.1 * r * r )"
+c     - "theta = atan2( yin, xin )"
+c     - "xout = rout * cos( theta )"
+c     - "yout = rout * sin( theta )"
+f     It is sometimes useful to calculate intermediate values and then to
+f     use these in the final expressions for the output (or input)
+f     variables. This may be done by supplying additional expressions for
+f     the forward (or inverse) transformation functions. For instance, the
+f     following array of five expressions describes 2-dimensional pin-cushion
+f     distortion:
+f     - 'R = SQRT( XIN * XIN + YIN * YIN )'
+f     - 'ROUT = R * ( 1 + 0.1 * R * R )'
+f     - 'THETA = ATAN2( YIN, XIN )',
+f     - 'XOUT = ROUT * COS( THETA )'
+f     - 'YOUT = ROUT * SIN( THETA )'
+*
+c     Here, we first calculate three intermediate results ("r", "rout"
+c     and "theta") and then use these to calculate the final results ("xout"
+c     and "yout"). The MathMap knows that only the final two results
+c     constitute values for the output variables because its Nout attribute
+c     is set to 2. You may define as many intermediate variables in this
+c     way as you choose. Having defined a variable, you may then refer to it
+c     on the right of any subsequent expressions.
+f     Here, we first calculate three intermediate results (R, ROUT
+f     and THETA) and then use these to calculate the final results (XOUT
+f     and YOUT). The MathMap knows that only the final two results
+f     constitute values for the output variables because its Nout attribute
+f     is set to 2. You may define as many intermediate variables in this
+f     way as you choose. Having defined a variable, you may then refer to it
+f     on the right of any subsequent expressions.
+*
+c     Note that when defining the inverse transformation you may only refer
+c     to the output variables "xout" and "yout".  The intermediate variables
+c     "r", "rout" and "theta" (above) are private to the forward
+c     transformation and may not be referenced by the inverse
+c     transformation. The inverse transformation may, however, define its
+c     own private intermediate variables.
+f     Note that when defining the inverse transformation you may only refer
+f     to the output variables XOUT and YOUT. The intermediate variables R,
+f     ROUT and THETA (above) are private to the forward transformation and
+f     may not be referenced by the inverse transformation. The inverse
+f     transformation may, however, define its own private intermediate
+f     variables.
+
+*  Expression Syntax:
+c     The expressions given for the forward and inverse transformations
+c     closely follow the syntax of the C programming language (with some
+c     extensions for compatibility with Fortran). They may contain
+c     references to variables and literal constants, together with
+c     arithmetic, boolean, relational and bitwise operators, and function
+c     invocations. A set of symbolic constants is also available. Each of
+c     these is described in detail below. Parentheses may be used to
+c     over-ride the normal order of evaluation. There is no built-in limit
+c     to the length of expressions and they are insensitive to case or the
+c     presence of additional white space.
+f     The expressions given for the forward and inverse transformations
+f     closely follow the syntax of Fortran (with some extensions for
+f     compatibility with the C language). They may contain references to
+f     variables and literal constants, together with arithmetic, logical,
+f     relational and bitwise operators, and function invocations. A set of
+f     symbolic constants is also available. Each of these is described in
+f     detail below. Parentheses may be used to over-ride the normal order of
+f     evaluation. There is no built-in limit to the length of expressions
+f     and they are insensitive to case or the presence of additional white
+f     space.
+
+*  Variables:
+*     Variable names must begin with an alphabetic character and may contain
+*     only alphabetic characters, digits, and the underscore character
+*     "_". There is no built-in limit to the length of variable names.
+
+*  Literal Constants:
+c     Literal constants, such as "0", "1", "0.007" or "2.505e-16" may appear
+c     in expressions, with the decimal point and exponent being optional (a
+c     "D" may also be used as an exponent character for compatibility with
+c     Fortran). A unary minus "-" may be used as a prefix.
+f     Literal constants, such as "0", "1", "0.007" or "2.505E-16" may appear
+f     in expressions, with the decimal point and exponent being optional (a
+f     "D" may also be used as an exponent character). A unary minus "-" may
+f     be used as a prefix.
+
+*  Arithmetic Precision:
+*     All arithmetic is floating point, performed in double precision.
+
+*  Propagation of Missing Data:
+*     Unless indicated otherwise, if any argument of a function or operator
+*     has the value AST__BAD (indicating missing data), then the result of
+*     that function or operation is also AST__BAD, so that such values are
+*     propagated automatically through all operations performed by MathMap
+*     transformations.  The special value AST__BAD can be represented in
+*     expressions by the symbolic constant "<bad>".
+*
+*     A <bad> result (i.e. equal to AST__BAD) is also produced in response
+*     to any numerical error (such as division by zero or numerical
+*     overflow), or if an invalid argument value is provided to a function
+*     or operator.
+
+*  Arithmetic Operators:
+*     The following arithmetic operators are available:
+c     - x1 + x2: Sum of "x1" and "x2".
+f     - X1 + X2: Sum of X1 and X2.
+c     - x1 - x2: Difference of "x1" and "x2".
+f     - X1 - X2: Difference of X1 and X2.
+c     - x1 * x2: Product of "x1" and "x1".
+f     - X1 * X2: Product of X1 and X2.
+c     - x1 / x2: Ratio of "x1" and "x2".
+f     - X1 / X2: Ratio of X1 and X2.
+c     - x1 ** x2: "x1" raised to the power of "x2".
+f     - X1 ** X2: X1 raised to the power of X2.
+c     - + x: Unary plus, has no effect on its argument.
+f     - + X: Unary plus, has no effect on its argument.
+c     - - x: Unary minus, negates its argument.
+f     - - X: Unary minus, negates its argument.
+
+c  Boolean Operators:
+f  Logical Operators:
+c     Boolean values are represented using zero to indicate false and
+c     non-zero to indicate true. In addition, the value AST__BAD is taken to
+c     mean "unknown". The values returned by boolean operators may therefore
+c     be 0, 1 or AST__BAD. Where appropriate, "tri-state" logic is
+c     implemented. For example, "a||b" may evaluate to 1 if "a" is non-zero,
+c     even if "b" has the value AST__BAD. This is because the result of the
+c     operation would not be affected by the value of "b", so long as "a" is
+c     non-zero.
+f     Logical values are represented using zero to indicate .FALSE. and
+f     non-zero to indicate .TRUE.. In addition, the value AST__BAD is taken to
+f     mean "unknown". The values returned by logical operators may therefore
+f     be 0, 1 or AST__BAD. Where appropriate, "tri-state" logic is
+f     implemented. For example, A.OR.B may evaluate to 1 if A is non-zero,
+f     even if B has the value AST__BAD. This is because the result of the
+f     operation would not be affected by the value of B, so long as A is
+f     non-zero.
+*
+c     The following boolean operators are available:
+f     The following logical operators are available:
+c     - x1 && x2: Boolean AND between "x1" and "x2", returning 1 if both "x1"
+c     and "x2" are non-zero, and 0 otherwise. This operator implements
+c     tri-state logic. (The synonym ".and." is also provided for compatibility
+c     with Fortran.)
+f     - X1 .AND. X2: Logical AND between X1 and X2, returning 1 if both X1
+f     and X2 are non-zero, and 0 otherwise. This operator implements
+f     tri-state logic. (The synonym "&&" is also provided for compatibility
+f     with C.)
+c     - x1 || x2: Boolean OR between "x1" and "x2", returning 1 if either "x1"
+c     or "x2" are non-zero, and 0 otherwise. This operator implements
+c     tri-state logic. (The synonym ".or." is also provided for compatibility
+c     with Fortran.)
+f     - X1 .OR. X2: Logical OR between X1 and X2, returning 1 if either X1
+f     or X2 are non-zero, and 0 otherwise. This operator implements
+f     tri-state logic. (The synonym "||" is also provided for compatibility
+f     with C.)
+c     - x1 ^^ x2: Boolean exclusive OR (XOR) between "x1" and "x2", returning
+c     1 if exactly one of "x1" and "x2" is non-zero, and 0 otherwise. Tri-state
+c     logic is not used with this operator. (The synonyms ".neqv." and ".xor."
+c     are also provided for compatibility with Fortran, although the second
+c     of these is not standard.)
+f     - X1 .NEQV. X2: Logical exclusive OR (XOR) between X1 and X2,
+f     returning 1 if exactly one of X1 and X2 is non-zero, and 0
+f     otherwise. Tri-state logic is not used with this operator. (The
+f     synonym ".XOR." is also provided, although this is not standard
+f     Fortran. In addition, the C-like synonym "^^" may be used, although
+f     this is also not standard.)
+c     - x1 .eqv. x2: This is provided only for compatibility with Fortran
+c     and tests whether the boolean states of "x1" and "x2" (i.e. true/false)
+c     are equal. It is the negative of the exclusive OR (XOR) function.
+c     Tri-state logic is not used with this operator.
+f     - X1 .EQV. X2: Tests whether the logical states of X1 and X2
+f     (i.e. .TRUE./.FALSE.) are equal. It is the negative of the exclusive OR
+f     (XOR) function.  Tri-state logic is not used with this operator.
+c     - ! x: Boolean unary NOT operation, returning 1 if "x" is zero, and
+c     0 otherwise. (The synonym ".not." is also provided for compatibility
+c     with Fortran.)
+f     - .NOT. X: Logical unary NOT operation, returning 1 if X is zero, and
+f     0 otherwise. (The synonym "!" is also provided for compatibility with
+f     C.)
+
+*  Relational Operators:
+c     Relational operators return the boolean result (0 or 1) of comparing
+c     the values of two floating point values for equality or inequality. The
+c     value AST__BAD may also be returned if either argument is <bad>.
+f     Relational operators return the logical result (0 or 1) of comparing
+f     the values of two floating point values for equality or inequality. The
+f     value AST__BAD may also be returned if either argument is <bad>.
+*
+*     The following relational operators are available:
+c     - x1 == x2: Tests whether "x1" equals "x1". (The synonym ".eq." is
+c     also provided for compatibility with Fortran.)
+f     - X1 .EQ. X2: Tests whether X1 equals X2. (The synonym "==" is also
+f     provided for compatibility with C.)
+c     - x1 != x2: Tests whether "x1" is unequal to "x2". (The synonym ".ne."
+c     is also provided for compatibility with Fortran.)
+f     - X1 .NE. X2: Tests whether X1 is unequal to X2. (The synonym "!=" is
+f     also provided for compatibility with C.)
+c     - x1 > x2: Tests whether "x1" is greater than "x2". (The synonym
+c     ".gt." is also provided for compatibility with Fortran.)
+f     - X1 .GT. X2: Tests whether X1 is greater than X2. (The synonym ">" is
+f     also provided for compatibility with C.)
+c     - x1 >= x2: Tests whether "x1" is greater than or equal to "x2". (The
+c     synonym ".ge."  is also provided for compatibility with Fortran.)
+f     - X1 .GE. X2: Tests whether X1 is greater than or equal to X2. (The
+f     synonym ">=" is also provided for compatibility with C.)
+c     - x1 < x2: Tests whether "x1" is less than "x2". (The synonym ".lt."
+c     is also provided for compatibility with Fortran.)
+f     - X1 .LT. X2: Tests whether X1 is less than X2. (The synonym "<" is also
+f     provided for compatibility with C.)
+c     - x1 <= x2: Tests whether "x1" is less than or equal to "x2". (The
+c     synonym ".le." is also provided for compatibility with Fortran.)
+f     - X1 .LE. X2: Tests whether X1 is less than or equal to X2. (The synonym
+f     "<=" is also provided for compatibility with C.)
+*
+c     Note that relational operators cannot usefully be used to compare
+c     values with the <bad> value (representing missing data), because the
+c     result is always <bad>. The isbad() function should be used instead.
+f     Note that relational operators cannot usefully be used to compare
+f     values with the <bad> value (representing missing data), because the
+f     result is always <bad>. The ISBAD() function should be used instead.
+f
+f     Note, also, that because logical operators can operate on floating
+f     point values, care must be taken to use parentheses in some cases
+f     where they would not normally be required in Fortran. For example,
+f     the expresssion:
+f     - .NOT. A .EQ. B
+f
+f     must be written:
+f     - .NOT. ( A .EQ. B )
+f
+f     to prevent the .NOT. operator from associating with the variable A.
+
+*  Bitwise Operators:
+c     The bitwise operators provided by C are often useful when operating on
+c     raw data (e.g. from instruments), so they are also provided for use in
+c     MathMap expressions. In this case, however, the values on which they
+c     operate are floating point values rather than pure integers. In order
+c     to produce results which match the pure integer case, the operands are
+c     regarded as fixed point binary numbers (i.e. with the binary
+c     equivalent of a decimal point) with negative numbers represented using
+c     twos-complement notation. For integer values, the resulting bit
+c     pattern corresponds to that of the equivalent signed integer (digits
+c     to the right of the point being zero). Operations on the bits
+c     representing the fractional part are also possible, however.
+f     Bitwise operators are often useful when operating on raw data
+f     (e.g. from instruments), so they are provided for use in MathMap
+f     expressions. In this case, however, the values on which they operate
+f     are floating point values rather than the more usual pure integers. In
+f     order to produce results which match the pure integer case, the
+f     operands are regarded as fixed point binary numbers (i.e. with the
+f     binary equivalent of a decimal point) with negative numbers
+f     represented using twos-complement notation. For integer values, the
+f     resulting bit pattern corresponds to that of the equivalent signed
+f     integer (digits to the right of the point being zero). Operations on
+f     the bits representing the fractional part are also possible, however.
+*
+*     The following bitwise operators are available:
+c     - x1 >> x2: Rightward bit shift. The integer value of "x2" is taken
+c     (rounding towards zero) and the bits representing "x1" are then
+c     shifted this number of places to the right (or to the left if the
+c     number of places is negative). This is equivalent to dividing "x1" by
+c     the corresponding power of 2.
+f     - X1 >> X2: Rightward bit shift. The integer value of X2 is taken
+f     (rounding towards zero) and the bits representing X1 are then
+f     shifted this number of places to the right (or to the left if the
+f     number of places is negative). This is equivalent to dividing X1 by
+f     the corresponding power of 2.
+c     - x1 << x2: Leftward bit shift. The integer value of "x2" is taken
+c     (rounding towards zero), and the bits representing "x1" are then
+c     shifted this number of places to the left (or to the right if the
+c     number of places is negative). This is equivalent to multiplying "x1"
+c     by the corresponding power of 2.
+f     - X1 << X2: Leftward bit shift. The integer value of X2 is taken
+f     (rounding towards zero), and the bits representing X1 are then
+f     shifted this number of places to the left (or to the right if the
+f     number of places is negative). This is equivalent to multiplying X1
+f     by the corresponding power of 2.
+c     - x1 & x2: Bitwise AND between the bits of "x1" and those of "x2"
+c     (equivalent to a boolean AND applied at each bit position in turn).
+f     - X1 & X2: Bitwise AND between the bits of X1 and those of X2
+f     (equivalent to a logical AND applied at each bit position in turn).
+c     - x1 | x2: Bitwise OR between the bits of "x1" and those of "x2"
+c     (equivalent to a boolean OR applied at each bit position in turn).
+f     - X1 | X2: Bitwise OR between the bits of X1 and those of X2
+f     (equivalent to a logical OR applied at each bit position in turn).
+c     - x1 ^ x2: Bitwise exclusive OR (XOR) between the bits of "x1" and
+c     those of "x2" (equivalent to a boolean XOR applied at each bit
+c     position in turn).
+f     - X1 ^ X2: Bitwise exclusive OR (XOR) between the bits of X1 and
+f     those of X2 (equivalent to a logical XOR applied at each bit
+f     position in turn).
+*
+c     Note that no bit inversion operator ("~" in C) is provided. This is
+c     because inverting the bits of a twos-complement fixed point binary
+c     number is equivalent to simply negating it. This differs from the
+c     pure integer case because bits to the right of the binary point are
+c     also inverted. To invert only those bits to the left of the binary
+c     point, use a bitwise exclusive OR with the value -1 (i.e. "x^-1").
+f     Note that no bit inversion operator is provided. This is
+f     because inverting the bits of a twos-complement fixed point binary
+f     number is equivalent to simply negating it. This differs from the
+f     pure integer case because bits to the right of the binary point are
+f     also inverted. To invert only those bits to the left of the binary
+f     point, use a bitwise exclusive OR with the value -1 (i.e. X^-1).
+
+*  Functions:
+*     The following functions are available:
+c     - abs(x): Absolute value of "x" (sign removal), same as fabs(x).
+f     - ABS(X): Absolute value of X (sign removal), same as FABS(X).
+c     - acos(x): Inverse cosine of "x", in radians.
+f     - ACOS(X): Inverse cosine of X, in radians.
+c     - acosd(x): Inverse cosine of "x", in degrees.
+f     - ACOSD(X): Inverse cosine of X, in degrees.
+c     - acosh(x): Inverse hyperbolic cosine of "x".
+f     - ACOSH(X): Inverse hyperbolic cosine of X.
+c     - acoth(x): Inverse hyperbolic cotangent of "x".
+f     - ACOTH(X): Inverse hyperbolic cotangent of X.
+c     - acsch(x): Inverse hyperbolic cosecant of "x".
+f     - ACSCH(X): Inverse hyperbolic cosecant of X.
+c     - aint(x): Integer part of "x" (round towards zero), same as int(x).
+f     - AINT(X): Integer part of X (round towards zero), same as INT(X).
+c     - asech(x): Inverse hyperbolic secant of "x".
+f     - ASECH(X): Inverse hyperbolic secant of X.
+c     - asin(x): Inverse sine of "x", in radians.
+f     - ASIN(X): Inverse sine of X, in radians.
+c     - asind(x): Inverse sine of "x", in degrees.
+f     - ASIND(X): Inverse sine of X, in degrees.
+c     - asinh(x): Inverse hyperbolic sine of "x".
+f     - ASINH(X): Inverse hyperbolic sine of X.
+c     - atan(x): Inverse tangent of "x", in radians.
+f     - ATAN(X): Inverse tangent of X, in radians.
+c     - atand(x): Inverse tangent of "x", in degrees.
+f     - ATAND(X): Inverse tangent of X, in degrees.
+c     - atanh(x): Inverse hyperbolic tangent of "x".
+f     - ATANH(X): Inverse hyperbolic tangent of X.
+c     - atan2(x1, x2): Inverse tangent of "x1/x2", in radians.
+f     - ATAN2(X1, X2): Inverse tangent of X1/X2, in radians.
+c     - atan2d(x1, x2): Inverse tangent of "x1/x2", in degrees.
+f     - ATAN2D(X1, X2): Inverse tangent of X1/X2, in degrees.
+c     - ceil(x): Smallest integer value not less then "x" (round towards
+c       plus infinity).
+f     - CEIL(X): Smallest integer value not less then X (round towards
+f       plus infinity).
+c     - cos(x): Cosine of "x" in radians.
+f     - COS(X): Cosine of X in radians.
+c     - cosd(x): Cosine of "x" in degrees.
+f     - COSD(X): Cosine of X in degrees.
+c     - cosh(x): Hyperbolic cosine of "x".
+f     - COSH(X): Hyperbolic cosine of X.
+c     - coth(x): Hyperbolic cotangent of "x".
+f     - COTH(X): Hyperbolic cotangent of X.
+c     - csch(x): Hyperbolic cosecant of "x".
+f     - CSCH(X): Hyperbolic cosecant of X.
+c     - dim(x1, x2): Returns "x1-x2" if "x1" is greater than "x2", otherwise 0.
+f     - DIM(X1, X2): Returns X1-X2 if X1 is greater than X2, otherwise 0.
+c     - exp(x): Exponential function of "x".
+f     - EXP(X): Exponential function of X.
+c     - fabs(x): Absolute value of "x" (sign removal), same as abs(x).
+f     - FABS(X): Absolute value of X (sign removal), same as ABS(X).
+c     - floor(x): Largest integer not greater than "x" (round towards
+c       minus infinity).
+f     - FLOOR(X): Largest integer not greater than X (round towards
+f       minus infinity).
+c     - fmod(x1, x2): Remainder when "x1" is divided by "x2", same as
+c       mod(x1, x2).
+f     - FMOD(X1, X2): Remainder when X1 is divided by X2, same as
+f       MOD(X1, X2).
+c     - gauss(x1, x2): Random sample from a Gaussian distribution with mean
+c       "x1" and standard deviation "x2".
+f     - GAUSS(X1, X2): Random sample from a Gaussian distribution with mean
+f       X1 and standard deviation X2.
+c     - int(x): Integer part of "x" (round towards zero), same as aint(x).
+f     - INT(X): Integer part of X (round towards zero), same as AINT(X).
+c     - isbad(x): Returns 1 if "x" has the <bad> value (AST__BAD), otherwise 0.
+f     - ISBAD(X): Returns 1 if X has the <bad> value (AST__BAD), otherwise 0.
+c     - log(x): Natural logarithm of "x".
+f     - LOG(X): Natural logarithm of X.
+c     - log10(x): Logarithm of "x" to base 10.
+f     - LOG10(X): Logarithm of X to base 10.
+c     - max(x1, x2, ...): Maximum of two or more values.
+f     - MAX(X1, X2, ...): Maximum of two or more values.
+c     - min(x1, x2, ...): Minimum of two or more values.
+f     - MIN(X1, X2, ...): Minimum of two or more values.
+c     - mod(x1, x2): Remainder when "x1" is divided by "x2", same as
+c       fmod(x1, x2).
+f     - MOD(X1, X2): Remainder when X1 is divided by X2, same as
+f       FMOD(X1, X2).
+c     - nint(x): Nearest integer to "x" (round to nearest).
+f     - NINT(X): Nearest integer to X (round to nearest).
+c     - poisson(x): Random integer-valued sample from a Poisson
+c       distribution with mean "x".
+f     - POISSON(X): Random integer-valued sample from a Poisson
+f       distribution with mean X.
+c     - pow(x1, x2): "x1" raised to the power of "x2".
+f     - POW(X1, X2): X1 raised to the power of X2.
+c     - qif(x1, x2, x3): Returns "x2" if "x1" is true, and "x3" otherwise.
+f     - QIF(x1, x2, x3): Returns X2 if X1 is true, and X3 otherwise.
+c     - rand(x1, x2): Random sample from a uniform distribution in the
+c       range "x1" to "x2" inclusive.
+f     - RAND(X1, X2): Random sample from a uniform distribution in the
+f       range X1 to X2 inclusive.
+c     - sech(x): Hyperbolic secant of "x".
+f     - SECH(X): Hyperbolic secant of X.
+c     - sign(x1, x2): Absolute value of "x1" with the sign of "x2"
+c       (transfer of sign).
+f     - SIGN(X1, X2): Absolute value of X1 with the sign of X2
+f       (transfer of sign).
+c     - sin(x): Sine of "x" in radians.
+f     - SIN(X): Sine of X in radians.
+c     - sinc(x): Sinc function of "x" [= "sin(x)/x"].
+f     - SINC(X): Sinc function of X [= SIN(X)/X].
+c     - sind(x): Sine of "x" in degrees.
+f     - SIND(X): Sine of X in degrees.
+c     - sinh(x): Hyperbolic sine of "x".
+f     - SINH(X): Hyperbolic sine of X.
+c     - sqr(x): Square of "x" (= "x*x").
+f     - SQR(X): Square of X (= X*X).
+c     - sqrt(x): Square root of "x".
+f     - SQRT(X): Square root of X.
+c     - tan(x): Tangent of "x" in radians.
+f     - TAN(X): Tangent of X in radians.
+c     - tand(x): Tangent of "x" in degrees.
+f     - TAND(X): Tangent of X in degrees.
+c     - tanh(x): Hyperbolic tangent of "x".
+f     - TANH(X): Hyperbolic tangent of X.
+
+*  Symbolic Constants:
+*     The following symbolic constants are available (the enclosing "<>"
+*     brackets must be included):
+c     - <bad>: The "bad" value (AST__BAD) used to flag missing data. Note
+c     that you cannot usefully compare values with this constant because the
+c     result is always <bad>. The isbad() function should be used instead.
+f     - <bad>: The "bad" value (AST__BAD) used to flag missing data. Note
+f     that you cannot usefully compare values with this constant because the
+f     result is always <bad>. The ISBAD() function should be used instead.
+c     - <dig>: Number of decimal digits of precision available in a
+c     floating point (double) value.
+f     - <dig>: Number of decimal digits of precision available in a
+f     floating point (double precision) value.
+*     - <e>: Base of natural logarithms.
+*     - <epsilon>: Smallest positive number such that 1.0+<epsilon> is
+*     distinguishable from unity.
+c     - <mant_dig>: The number of base <radix> digits stored in the
+c     mantissa of a floating point (double) value.
+f     - <mant_dig>: The number of base <radix> digits stored in the
+f     mantissa of a floating point (double precision) value.
+c     - <max>: Maximum representable floating point (double) value.
+f     - <max>: Maximum representable floating point (double precision) value.
+c     - <max_10_exp>: Maximum integer such that 10 raised to that power
+c     can be represented as a floating point (double) value.
+f     - <max_10_exp>: Maximum integer such that 10 raised to that power
+f     can be represented as a floating point (double precision) value.
+c     - <max_exp>: Maximum integer such that <radix> raised to that
+c     power minus 1 can be represented as a floating point (double) value.
+f     - <max_exp>: Maximum integer such that <radix> raised to that
+f     power minus 1 can be represented as a floating point (double precision)
+f     value.
+c     - <min>: Smallest positive number which can be represented as a
+c     normalised floating point (double) value.
+f     - <min>: Smallest positive number which can be represented as a
+f     normalised floating point (double precision) value.
+c     - <min_10_exp>: Minimum negative integer such that 10 raised to that
+c     power can be represented as a normalised floating point (double) value.
+f     - <min_10_exp>: Minimum negative integer such that 10 raised to that
+f     power can be represented as a normalised floating point (double
+f     precision) value.
+c     - <min_exp>: Minimum negative integer such that <radix> raised to
+c     that power minus 1 can be represented as a normalised floating point
+c     (double) value.
+f     - <min_exp>: Minimum negative integer such that <radix> raised to
+f     that power minus 1 can be represented as a normalised floating point
+f     (double precision) value.
+*     - <pi>: Ratio of the circumference of a circle to its diameter.
+c     - <radix>: The radix (number base) used to represent the mantissa of
+c     floating point (double) values.
+f     - <radix>: The radix (number base) used to represent the mantissa of
+f     floating point (double precision) values.
+*     - <rounds>: The mode used for rounding floating point results after
+*     addition. Possible values include: -1 (indeterminate), 0 (toward
+*     zero), 1 (to nearest), 2 (toward plus infinity) and 3 (toward minus
+*     infinity). Other values indicate machine-dependent behaviour.
+
+*  Evaluation Precedence and Associativity:
+*     Items appearing in expressions are evaluated in the following order
+*     (highest precedence first):
+*     - Constants and variables
+*     - Function arguments and parenthesised expressions
+*     - Function invocations
+*     - Unary + - ! .not.
+*     - **
+*     - * /
+*     - + -
+*     - << >>
+*     - < .lt. <= .le. > .gt. >= .ge.
+*     - == .eq. != .ne.
+*     - &
+*     - ^
+*     - |
+*     - && .and.
+*     - ^^
+*     - || .or
+*     - .eqv. .neqv. .xor.
+*
+*     All operators associate from left-to-right, except for unary +,
+*     unary -, !, .not. and ** which associate from right-to-left.
+
+*  Notes:
+*     - The sequence of numbers produced by the random number functions
+*     available within a MathMap is normally unpredictable and different for
+*     each MathMap. However, this behaviour may be controlled by means of
+*     the MathMap's Seed attribute.
+c     - Normally, compound Mappings (CmpMaps) which involve MathMaps will
+c     not be subject to simplification (e.g. using astSimplify) because AST
+c     cannot know how different MathMaps will interact. However, in the
+c     special case where a MathMap occurs in series with its own inverse,
+c     then simplification may be possible. Whether simplification does, in
+c     fact, occur under these circumstances is controlled by the MathMap's
+c     SimpFI and SimpIF attributes.
+f     - Normally, compound Mappings (CmpMaps) which involve MathMaps will
+f     not be subject to simplification (e.g. using AST_SIMPLIFY) because AST
+f     cannot know how different MathMaps will interact. However, in the
+f     special case where a MathMap occurs in series with its own inverse,
+f     then simplification may be possible. Whether simplification does, in
+f     fact, occur under these circumstances is controlled by the MathMap's
+f     SimpFI and SimpIF attributes.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astMathMap constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astMathMap_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - The variable argument list also prevents this function from
+*     invoking astMathMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMathMap *new;              /* Pointer to new MathMap */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the MathMap, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitMathMap( NULL, sizeof( AstMathMap ), !class_init, &class_vtab,
+                         "MathMap", nin, nout, nfwd, fwd, ninv, inv );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new MathMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new MathMap. */
+   return astMakeId( new );
+}
+
+AstMathMap *astInitMathMap_( void *mem, size_t size, int init,
+                             AstMathMapVtab *vtab, const char *name,
+                             int nin, int nout,
+                             int nfwd, const char *fwd[],
+                             int ninv, const char *inv[], int *status ) {
+/*
+*+
+*  Name:
+*     astInitMathMap
+
+*  Purpose:
+*     Initialise a MathMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     AstMathMap *astInitMathMap_( void *mem, size_t size, int init,
+*                                  AstMathMapVtab *vtab, const char *name,
+*                                  int nin, int nout,
+*                                  int nfwd, const char *fwd[],
+*                                  int ninv, const char *inv[] )
+
+*  Class Membership:
+*     MathMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new MathMap object. It allocates memory (if necessary) to accommodate
+*     the MathMap plus any additional data associated with the derived class.
+*     It then initialises a MathMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a MathMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the MathMap is to be initialised.
+*        This must be of sufficient size to accommodate the MathMap data
+*        (sizeof(MathMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the MathMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the MathMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the MathMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new MathMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     nin
+*        Number of input variables for the MathMap.
+*     nout
+*        Number of output variables for the MathMap.
+*     nfwd
+*        The number of forward transformation functions being supplied.
+*        This must be at least equal to "nout".
+*     fwd
+*        Pointer to an array, with "nfwd" elements, of pointers to null
+*        terminated strings which contain each of the forward transformation
+*        functions.
+*     ninv
+*        The number of inverse transformation functions being supplied.
+*        This must be at least equal to "nin".
+*     inv
+*        Pointer to an array, with "ninv" elements, of pointers to null
+*        terminated strings which contain each of the inverse transformation
+*        functions.
+
+*  Returned Value:
+*     A pointer to the new MathMap.
+
+*  Notes:
+*     -  This function does not attempt to ensure that the forward and inverse
+*     transformations performed by the resulting MathMap are consistent in any
+*     way.
+*     -  This function makes a copy of the contents of the strings supplied.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstMathMap *new;              /* Pointer to new MathMap */
+   char **fwdfun;                /* Array of cleaned forward functions */
+   char **invfun;                /* Array of cleaned inverse functions */
+   double **fwdcon;              /* Constants for forward functions */
+   double **invcon;              /* Constants for inverse functions */
+   int **fwdcode;                /* Code for forward functions */
+   int **invcode;                /* Code for inverse functions */
+   int fwdstack;                 /* Stack size for forward functions */
+   int invstack;                 /* Stack size for inverse functions */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitMathMapVtab( vtab, name );
+
+/* Check the numbers of input and output variables for validity,
+   reporting an error if necessary. */
+   if ( nin < 1 ) {
+      astError( AST__BADNI,
+                "astInitMathMap(%s): Bad number of input coordinates (%d).", status,
+                name, nin );
+      astError( AST__BADNI,
+                "This number should be one or more." , status);
+   } else if ( nout < 1 ) {
+      astError( AST__BADNO,
+                "astInitMathMap(%s): Bad number of output coordinates (%d).", status,
+                name, nout );
+      astError( AST__BADNI,
+                "This number should be one or more." , status);
+
+/* Check that sufficient number of forward and inverse transformation
+   functions have been supplied and report an error if necessary. */
+   } else if ( nfwd < nout ) {
+      astError( AST__INNTF,
+                "astInitMathMap(%s): Too few forward transformation functions "
+                "given (%d).", status,
+                name, nfwd );
+      astError( astStatus,
+                "At least %d forward transformation functions must be "
+                "supplied. ", status,
+                nout );
+   } else if ( ninv < nin ) {
+      astError( AST__INNTF,
+                "astInitMathMap(%s): Too few inverse transformation functions "
+                "given (%d).", status,
+                name, ninv );
+      astError( astStatus,
+                "At least %d inverse transformation functions must be "
+                "supplied. ", status,
+                nin );
+
+/* Of OK, clean the forward and inverse functions provided. This makes
+   a lower-case copy with white space removed. */
+   } else {
+      CleanFunctions( nfwd, fwd, &fwdfun, status );
+      CleanFunctions( ninv, inv, &invfun, status );
+
+/* Compile the cleaned functions. From the returned pointers (if
+   successful), we can now tell which transformations (forward and/or
+   inverse) are defined. */
+      CompileMapping( "astInitMathMap", name, nin, nout,
+                      nfwd, (const char **) fwdfun,
+                      ninv, (const char **) invfun,
+                      &fwdcode, &invcode, &fwdcon, &invcon,
+                      &fwdstack, &invstack, status );
+
+/* Initialise a Mapping structure (the parent class) as the first
+   component within the MathMap structure, allocating memory if
+   necessary. Specify that the Mapping should be defined in the required
+   directions. */
+      new = (AstMathMap *) astInitMapping( mem, size, 0,
+                                           (AstMappingVtab *) vtab, name,
+                                           nin, nout,
+                                           ( fwdcode != NULL ),
+                                           ( invcode != NULL ) );
+
+
+/* If an error has occurred, free all the memory which may have been
+   allocated by the cleaning and compilation steps above. */
+      if ( !astOK ) {
+         FREE_POINTER_ARRAY( fwdfun, nfwd )
+         FREE_POINTER_ARRAY( invfun, ninv )
+         FREE_POINTER_ARRAY( fwdcode, nfwd )
+         FREE_POINTER_ARRAY( invcode, ninv )
+         FREE_POINTER_ARRAY( fwdcon, nfwd )
+         FREE_POINTER_ARRAY( invcon, ninv )
+      }
+
+/* Initialise the MathMap data. */
+/* ---------------------------- */
+/* Store pointers to the compiled function information, together with
+   other MathMap data. */
+      if ( new ) {
+         new->fwdfun = fwdfun;
+         new->invfun = invfun;
+         new->fwdcode = fwdcode;
+         new->invcode = invcode;
+         new->fwdcon = fwdcon;
+         new->invcon = invcon;
+         new->fwdstack = fwdstack;
+         new->invstack = invstack;
+         new->nfwd = nfwd;
+         new->ninv = ninv;
+         new->simp_fi = -INT_MAX;
+         new->simp_if = -INT_MAX;
+
+/* Initialise the random number generator context associated with the
+   MathMap, using an unpredictable default seed value. */
+         new->rcontext.active = 0;
+         new->rcontext.random_int = 0;
+         new->rcontext.seed_set = 0;
+         new->rcontext.seed = DefaultSeed( &new->rcontext, status );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstMathMap *astLoadMathMap_( void *mem, size_t size,
+                             AstMathMapVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadMathMap
+
+*  Purpose:
+*     Load a MathMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "mathmap.h"
+*     AstMathMap *astLoadMathMap( void *mem, size_t size,
+*                                 AstMathMapVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     MathMap loader.
+
+*  Description:
+*     This function is provided to load a new MathMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     MathMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a MathMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the MathMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        MathMap data (sizeof(MathMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the MathMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the MathMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstMathMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new MathMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the MathMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "MathMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new MathMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstMathMap *new;              /* Pointer to the new MathMap */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword strings */
+   int ifun;                     /* Loop counter for functions */
+   int invert;                   /* Invert attribute value */
+   int nin;                      /* True number of input coordinates */
+   int nout;                     /* True number of output coordinates */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this MathMap. In this case the
+   MathMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstMathMap );
+      vtab = &class_vtab;
+      name = "MathMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitMathMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built MathMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "MathMap" );
+
+/* Determine if the MathMap is inverted and obtain the "true" number
+   of input and output coordinates by un-doing the effects of any
+   inversion. */
+      invert = astGetInvert( new );
+      nin = invert ? astGetNout( new ) : astGetNin( new );
+      nout = invert ? astGetNin( new ) : astGetNout( new );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Numbers of transformation functions. */
+/* ------------------------------------ */
+/* Read the numbers of forward and inverse transformation functions,
+   supplying appropriate defaults. */
+      new->nfwd = astReadInt( channel, "nfwd", nout );
+      new->ninv = astReadInt( channel, "ninv", nin );
+      if ( astOK ) {
+
+/* Allocate memory for the MathMap's transformation function arrays. */
+         MALLOC_POINTER_ARRAY( new->fwdfun, char *, new->nfwd )
+         MALLOC_POINTER_ARRAY( new->invfun, char *, new->ninv )
+         if ( astOK ) {
+
+/* Forward transformation functions. */
+/* --------------------------------- */
+/* Create a keyword for each forward transformation function and read
+   the function's value as a string. */
+            for ( ifun = 0; ifun < new->nfwd; ifun++ ) {
+               (void) sprintf( key, "fwd%d", ifun + 1 );
+               new->fwdfun[ ifun ] = astReadString( channel, key, "" );
+            }
+
+/* Inverse transformation functions. */
+/* --------------------------------- */
+/* Repeat this process for the inverse transformation functions. */
+            for ( ifun = 0; ifun < new->ninv; ifun++ ) {
+               (void) sprintf( key, "inv%d", ifun + 1 );
+               new->invfun[ ifun ] = astReadString( channel, key, "" );
+            }
+
+/* Forward-inverse simplification flag. */
+/* ------------------------------------ */
+            new->simp_fi = astReadInt( channel, "simpfi", -INT_MAX );
+            if ( TestSimpFI( new, status ) ) SetSimpFI( new, new->simp_fi, status );
+
+/* Inverse-forward simplification flag. */
+/* ------------------------------------ */
+            new->simp_if = astReadInt( channel, "simpif", -INT_MAX );
+            if ( TestSimpIF( new, status ) ) SetSimpIF( new, new->simp_if, status );
+
+/* Random number context. */
+/* ---------------------- */
+/* Initialise the random number generator context. */
+            new->rcontext.active = 0;
+            new->rcontext.random_int = 0;
+
+/* Read the flag that determines if the Seed value is set, and the
+   Seed value itself. */
+            new->rcontext.seed_set = astReadInt( channel, "seeded", 0 );
+            if ( TestSeed( new, status ) ) {
+               new->rcontext.seed = astReadInt( channel, "seed", 0 );
+               SetSeed( new, new->rcontext.seed, status );
+
+/* Supply an unpredictable default Seed value if necessary. */
+            } else {
+               new->rcontext.seed = DefaultSeed( &new->rcontext, status );
+            }
+
+/* Compile the MathMap's transformation functions. */
+            CompileMapping( "astLoadMathMap", name, nin, nout,
+                            new->nfwd, (const char **) new->fwdfun,
+                            new->ninv, (const char **) new->invfun,
+                            &new->fwdcode, &new->invcode,
+                            &new->fwdcon, &new->invcon,
+                            &new->fwdstack, &new->invstack, status );
+         }
+
+/* If an error occurred, clean up by deleting the new MathMap. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return the new MathMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+
+
+
+
diff --git a/ast/mathmap.h b/ast/mathmap.h
new file mode 100644
index 0000000..e3b4004
--- /dev/null
+++ b/ast/mathmap.h
@@ -0,0 +1,410 @@
+#if !defined( MATHMAP_INCLUDED ) /* Include this file only once */
+#define MATHMAP_INCLUDED
+/*
+*+
+*  Name:
+*     mathmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the MathMap class.
+
+*  Invocation:
+*     #include "mathmap.h"
+
+*  Description:
+*     This include file defines the interface to the MathMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The MathMap class implements Mappings that are specified by a series
+*     of arithmetic expressions that relate output variables to input
+*     variables (and vice versa).
+
+*  Inheritance:
+*     The MathMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     Seed
+*        Random number seed.
+*     SimpFI
+*        Forward-inverse MathMap pairs simplify?
+*     SimpIF
+*        Inverse-forward MathMap pairs simplify?
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearAttrib
+*           Clear an attribute value for a Frame.
+*        astGetAttrib
+*           Get an attribute value for a Frame.
+*        astMapMerge
+*           Simplify a sequence of Mappings containing a MathMap.
+*        astSetAttrib
+*           Set an attribute value for a Frame.
+*        astTestAttrib
+*           Test if an attribute value has been set for a Frame.
+*        astTransform
+*           Transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearSeed
+*           Clear the Seed attribute for a MathMap.
+*        astClearSimpFI
+*           Clear the SimpFI attribute for a MathMap.
+*        astClearSimpIF
+*           Clear the SimpIF attribute for a MathMap.
+*        astGetSeed
+*           Get the value of the Seed attribute for a MathMap.
+*        astGetSimpFI
+*           Get the value of the SimpFI attribute for a MathMap.
+*        astGetSimpIF
+*           Get the value of the SimpIF attribute for a MathMap.
+*        astSetSeed
+*           Set the value of the Seed attribute for a MathMap.
+*        astSetSimpFI
+*           Set the value of the SimpFI attribute for a MathMap.
+*        astSetSimpIF
+*           Set the value of the SimpIF attribute for a MathMap.
+*        astTestSeed
+*           Test whether a value has been set for the Seed attribute of a
+*           MathMap.
+*        astTestSimpFI
+*           Test whether a value has been set for the SimpFI attribute of a
+*           MathMap.
+*        astTestSimpIF
+*           Test whether a value has been set for the SimpIF attribute of a
+*           MathMap.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAMathMap
+*           Test class membership.
+*        astMathMap
+*           Create a MathMap.
+*
+*     Protected:
+*        astCheckMathMap
+*           Validate class membership.
+*        astInitMathMap
+*           Initialise a MathMap.
+*        astInitMathMapVtab
+*           Initialise the virtual function table for the MathMap class.
+*        astLoadMathMap
+*           Load a MathMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstMathMap
+*           MathMap object type.
+*
+*     Protected:
+*        AstMathMapVtab
+*           MathMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     3-SEP-1999 (RFWS):
+*        Original version.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitMathMapVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#include "pointset.h"            /* Sets of points/coordinates */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros. */
+/* ======= */
+/* This value defines the size of an internal table in the AstMathMap
+   data type. Since it will be publicly accessible (but of no external
+   use), we give it an obscure name. */
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+#define AST_MATHMAP_RAND_CONTEXT_NTAB_ (32)
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* Random number generator context. */
+/* -------------------------------- */
+/* This structure holds the context for the random number generator
+   used by each MathMap. This ensures that the random number sequences
+   used by different MathMaps are independent, and can be independently
+   controlled by setting/clearing their Seed attributes. Random numbers
+   are produced by combining the output of two internal generators. */
+typedef struct AstMathMapRandContext_ {
+   long int rand1;               /* State of first internal generator */
+   long int rand2;               /* State of second internal generator */
+   long int random_int;          /* Last random integer produced */
+   long int table[ AST_MATHMAP_RAND_CONTEXT_NTAB_ ]; /* Shuffle table */
+   int active;                   /* Generator has been initialised? */
+   int seed;                     /* Seed to be used during initialisation */
+   int seed_set;                 /* Seed value set via "Seed" attribute? */
+} AstMathMapRandContext_;
+
+/* MathMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstMathMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstMathMapRandContext_ rcontext; /* Random number generator context */
+   char **fwdfun;                /* Array of forward functions */
+   char **invfun;                /* Array of inverse functions */
+   double **fwdcon;              /* Array of constants for forward functions */
+   double **invcon;              /* Array of constants for inverse functions */
+   int **fwdcode;                /* Array of opcodes for forward functions */
+   int **invcode;                /* Array of opcodes for inverse functions */
+   int fwdstack;                 /* Stack size required by forward functions */
+   int invstack;                 /* Stack size required by inverse functions */
+   int nfwd;                     /* Number of forward functions */
+   int ninv;                     /* Number of inverse functions */
+   int simp_fi;                  /* Forward-inverse MathMap pairs simplify? */
+   int simp_if;                  /* Inverse-forward MathMap pairs simplify? */
+} AstMathMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstMathMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   int (* GetSeed)( AstMathMap *, int * );
+   int (* GetSimpFI)( AstMathMap *, int * );
+   int (* GetSimpIF)( AstMathMap *, int * );
+   int (* TestSeed)( AstMathMap *, int * );
+   int (* TestSimpFI)( AstMathMap *, int * );
+   int (* TestSimpIF)( AstMathMap *, int * );
+   void (* ClearSeed)( AstMathMap *, int * );
+   void (* ClearSimpFI)( AstMathMap *, int * );
+   void (* ClearSimpIF)( AstMathMap *, int * );
+   void (* SetSeed)( AstMathMap *, int, int * );
+   void (* SetSimpFI)( AstMathMap *, int, int * );
+   void (* SetSimpIF)( AstMathMap *, int, int * );
+} AstMathMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstMathMapGlobals {
+   AstMathMapVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 51 ];
+} AstMathMapGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(MathMap)          /* Check class membership */
+astPROTO_ISA(MathMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstMathMap *astMathMap_( int, int, int, const char *[], int, const char *[],
+                         const char *, int *, ...);
+#else
+AstMathMap *astMathMapId_( int, int, int, const char *[], int, const char *[],
+                           const char *, ... )__attribute__((format(printf,7,8)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstMathMap *astInitMathMap_( void *, size_t, int, AstMathMapVtab *,
+                             const char *, int, int,
+                             int, const char *[], int, const char *[], int * );
+
+/* Vtab initialiser. */
+void astInitMathMapVtab_( AstMathMapVtab *, const char *, int * );
+
+/* Loader. */
+AstMathMap *astLoadMathMap_( void *, size_t, AstMathMapVtab *,
+                             const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitMathMapGlobals_( AstMathMapGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+#if defined(astCLASS)            /* Protected */
+int astGetSeed_( AstMathMap *, int * );
+int astGetSimpFI_( AstMathMap *, int * );
+int astGetSimpIF_( AstMathMap *, int * );
+int astTestSeed_( AstMathMap *, int * );
+int astTestSimpFI_( AstMathMap *, int * );
+int astTestSimpIF_( AstMathMap *, int * );
+void astClearSeed_( AstMathMap *, int * );
+void astClearSimpFI_( AstMathMap *, int * );
+void astClearSimpIF_( AstMathMap *, int * );
+void astSetSeed_( AstMathMap *, int, int * );
+void astSetSimpFI_( AstMathMap *, int, int * );
+void astSetSimpIF_( AstMathMap *, int, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckMathMap(this) astINVOKE_CHECK(MathMap,this,0)
+#define astVerifyMathMap(this) astINVOKE_CHECK(MathMap,this,1)
+
+/* Test class membership. */
+#define astIsAMathMap(this) astINVOKE_ISA(MathMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astMathMap astINVOKE(F,astMathMap_)
+#else
+#define astMathMap astINVOKE(F,astMathMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitMathMap(mem,size,init,vtab,name,nin,nout,nfwd,fwd,ninv,inv) \
+astINVOKE(O,astInitMathMap_(mem,size,init,vtab,name,nin,nout,nfwd,fwd,ninv,inv,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitMathMapVtab(vtab,name) astINVOKE(V,astInitMathMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadMathMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadMathMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckMathMap to validate MathMap pointers
+   before use. This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+#if defined(astCLASS)            /* Protected */
+#define astClearSeed(this) \
+astINVOKE(V,astClearSeed_(astCheckMathMap(this),STATUS_PTR))
+#define astClearSimpFI(this) \
+astINVOKE(V,astClearSimpFI_(astCheckMathMap(this),STATUS_PTR))
+#define astClearSimpIF(this) \
+astINVOKE(V,astClearSimpIF_(astCheckMathMap(this),STATUS_PTR))
+#define astGetSeed(this) \
+astINVOKE(V,astGetSeed_(astCheckMathMap(this),STATUS_PTR))
+#define astGetSimpFI(this) \
+astINVOKE(V,astGetSimpFI_(astCheckMathMap(this),STATUS_PTR))
+#define astGetSimpIF(this) \
+astINVOKE(V,astGetSimpIF_(astCheckMathMap(this),STATUS_PTR))
+#define astSetSeed(this,value) \
+astINVOKE(V,astSetSeed_(astCheckMathMap(this),value,STATUS_PTR))
+#define astSetSimpFI(this,value) \
+astINVOKE(V,astSetSimpFI_(astCheckMathMap(this),value,STATUS_PTR))
+#define astSetSimpIF(this,value) \
+astINVOKE(V,astSetSimpIF_(astCheckMathMap(this),value,STATUS_PTR))
+#define astTestSeed(this) \
+astINVOKE(V,astTestSeed_(astCheckMathMap(this),STATUS_PTR))
+#define astTestSimpFI(this) \
+astINVOKE(V,astTestSimpFI_(astCheckMathMap(this),STATUS_PTR))
+#define astTestSimpIF(this) \
+astINVOKE(V,astTestSimpIF_(astCheckMathMap(this),STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/matrixmap.c b/ast/matrixmap.c
new file mode 100644
index 0000000..4ffc3f7
--- /dev/null
+++ b/ast/matrixmap.c
@@ -0,0 +1,5501 @@
+/*
+*class++
+*  Name:
+*     MatrixMap
+
+*  Purpose:
+*     Map coordinates by multiplying by a matrix.
+
+*  Constructor Function:
+c     astMatrixMap
+f     AST_MATRIXMAP
+
+*  Description:
+*     A MatrixMap is form of Mapping which performs a general linear
+*     transformation. Each set of input coordinates, regarded as a
+*     column-vector, are pre-multiplied by a matrix (whose elements
+*     are specified when the MatrixMap is created) to give a new
+*     column-vector containing the output coordinates. If appropriate,
+*     the inverse transformation may also be performed.
+
+*  Inheritance:
+*     The MatrixMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The MatrixMap class does not define any new attributes beyond
+*     those which are applicable to all Mappings.
+
+*  Functions:
+c     The MatrixMap class does not define any new functions beyond those
+f     The MatrixMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     9-FEB-1996 (DSB):
+*        Original version.
+*     13-NOV-1996 (DSB):
+*        Updated to support attributes, I/O and an external interface.
+*     3-JUN-1997 (DSB):
+*        astMtrMult and astMtrRot made protected instead of public.
+*     16-JUN-1997 (RFWS):
+*        Tidied public prologues.
+*     24-JUN-1997 (DSB):
+*        Zero returned for coordinates which are indeterminate as a
+*        result of using an inverted, non-square, diagonal matrix.
+*     10-OCT-1997 (DSB):
+*        o  The inverse matrix is no longer dumped by the Dump function.
+*        Instead, it is re-calculated by the Load function.
+*        o  The description of argument "form" in astMatrixMap corrected
+*        to indicate that a value of 2 produces a unit matrix.
+*        o  String values used to represent choices externally, instead
+*        of integers.
+*     24-NOV-1997 (DSB):
+*        Use of error code AST__OPT replaced by AST__RDERR.
+*     28-JAN-1998 (DSB):
+*        Bug fix in astMtrMult: the matrix (forward or inverse) used for
+*        the "a" MatrixMap was determined by the Invert flag of the other
+*        ("this") MatrixMap.
+*     14-APR-1998 (DSB):
+*        Bug fix in Dump. Previously, matrix elements with value AST__BAD
+*        were explicitly written out. Now they are not written out, since
+*        AST__BAD can have different values on different machines. Missing
+*        elements default to AST__BAD when read back in using astLoadMatrixMap.
+*     20-APR-1998 (DSB):
+*        Bug fix in astLoadMatrixMap: initialise the pointer to the inverse
+*        matrix array to NULL if no inverse matrix is needed.
+*     25-AUG-1998 (DSB):
+*        - Transform changed so that bad input axis values are not
+*        propagated to output axes which are independant of the input axis.
+*        - CompressMatrix changed to allow a tolerance of DBL_EPSILON when
+*        determining if a matrix is a unit matrix, or a diagonal matrix.
+*        - MapMerge changed to allow MatrixMaps to swap with PermMaps
+*        in order to move the MatrixMap closer to a Mapping with which it
+*        could merge.
+*     22-FEB-1999 (DSB):
+*        Changed logic of MapMerge to avoid infinite looping.
+*     5-MAY-1999 (DSB):
+*        More corrections to MapMerge: Cleared up errors in the use of the
+*        supplied invert flags, and corrected logic for deciding which
+*        neighbouring Mapping to swap with.
+*     16-JUL-1999 (DSB):
+*        Fixed memory leaks in MatWin and MapMerge.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitatrixMapVtab
+*        method.
+*     11-SEP-2003 (DSB):
+*        Increased tolerance on checks for unit matrices within
+*        CompressMatrix. Now uses sqrt(DBL_EPSILON)*diag (previously was
+*        DBL_EPSILON*DIAG ).
+*     10-NOV-2003 (DSB):
+*        Modified functions which swap a MatrixMap with another Mapping
+*        (e.g. MatSwapPerm, etc), to simplify the returned Mappings.
+*     13-JAN-2003 (DSB):
+*        Modified the tolerance used by CompressMatrix when checking for
+*        zero matrix elements. Old system compared each element to thre
+*        size of the diagonal, but different scalings on different axes could
+*        cause this to trat as zero values which should nto be treated as
+*        zero.
+*     23-APR-2004 (DSB):
+*        Changes to simplification algorithm.
+*     8-JUL-2004 (DSB):
+*        astMtrMult - Report an error if either MatrixMap does not have a
+*        defined forward transformation.
+*     1-SEP-2004 (DSB):
+*        Ensure do1 and do2 are initialised before use in MapMerge.
+*     7-SEP-2005 (DSB):
+*        Take account of the Invert flag when using the zoom factor from
+*        a ZoomMap.
+*     14-FEB-2006 (DSB):
+*        Correct row/col confusion in CompressMatrix.
+*     15-MAR-2006 (DSB):
+*        Override astEqual.
+*     15-MAR-2009 (DSB):
+*        MapSplit: Only create the returned Mapping if it would have some
+*        outputs. Also, do not create the returned Mapping if any output
+*        depends on a mixture of selected and unselected inputs.
+*     16-JUL-2009 (DSB):
+*        MatPerm: Fix memory leak (mm2 was not being annulled).
+*     2-OCT-2012 (DSB):
+*        - Check for Infs as well as NaNs.
+*        - In MapSplit do not split the MatrixMap if the resulting
+*          matrix would contain only bad elements.
+*        - Report an error if an attempt is made to create a MatrixMap
+*          containing only bad elements.
+*     4-NOV-2013 (DSB):
+*        Allow a full form MatrixMap to be simplified to a diagonal form
+*        MatrixMap if all the off-diagonal values are zero.
+*     23-APR-2015 (DSB):
+*        Improve MapMerge. If a MatrixMap can merge with its next-but-one
+*        neighbour, then swap the MatrixMap with its neighbour, so that
+*        it is then next its next-but-one neighbour, and then merge the
+*        two Mappings into a single Mapping. Previously, only the swap
+*        was performed - not the merger. And the swap was only performed
+*        if the intervening neighbour could not itself merge. This could
+*        result in an infinite simplification loop, which was detected by
+*        CmpMap and and aborted, resulting in no useful simplification.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS MatrixMap
+
+/* Define identifiers for the different forms of matrix storage. */
+#define FULL       0
+#define DIAGONAL   1
+#define UNIT       2
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "matrixmap.h"           /* Interface definition for this class */
+#include "pal.h"              /* SLALIB function definitions */
+#include "permmap.h"
+#include "zoommap.h"
+#include "unitmap.h"
+#include "winmap.h"
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+static const char *Form[3] = { "Full", "Diagonal", "Unit" }; /* Text values
+                                   used to represent storage form externally */
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int *(* parent_mapsplit)( AstMapping *, int, const int *, AstMapping **, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(MatrixMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(MatrixMap,Class_Init)
+#define class_vtab astGLOBAL(MatrixMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstMatrixMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstMatrixMap *astMatrixMapId_( int, int, int, const double [], const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMatrixMap *MatMat( AstMapping *, AstMapping *, int, int, int * );
+static AstMatrixMap *MatPerm( AstMatrixMap *, AstPermMap *, int, int, int, int * );
+static AstMatrixMap *MatZoom( AstMatrixMap *, AstZoomMap *, int, int, int * );
+static AstMatrixMap *MtrMult( AstMatrixMap *, AstMatrixMap *, int * );
+static AstMatrixMap *MtrRot( AstMatrixMap *, double, const double[], int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double *InvertMatrix( int, int, int, double *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int FindString( int, const char *[], const char *, const char *, const char *, const char *, int * );
+static int Ustrcmp( const char *, const char *, int * );
+static int GetTranForward( AstMapping *, int * );
+static int GetIsLinear( AstMapping *, int * );
+static int GetTranInverse( AstMapping *, int * );
+static int CanSwap( AstMapping *, AstMapping *, int, int, int *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int PermOK( AstMapping *, int * );
+static int ScalingRowCol( AstMatrixMap *, int, int * );
+static void CompressMatrix( AstMatrixMap *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *obj, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void ExpandMatrix( AstMatrixMap *, int * );
+static void MatWin( AstMapping **, int *, int, int * );
+static void MatPermSwap( AstMapping **, int *, int, int * );
+static void PermGet( AstPermMap *, int **, int **, double **, int * );
+static void SMtrMult( int, int, int, const double *, double *, double*, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+
+/* Member functions. */
+/* ================= */
+static int CanSwap( AstMapping *map1, AstMapping *map2, int inv1, int inv2,
+                    int *simpler, int *status ){
+/*
+*  Name:
+*     CanSwap
+
+*  Purpose:
+*     Determine if two Mappings could be swapped.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     int CanSwap( AstMapping *map1, AstMapping *map2, int inv1, int inv2,
+*                  int *simpler, int *status )
+
+*  Class Membership:
+*     MatrixMap member function
+
+*  Description:
+*     This function returns a flag indicating if the pair of supplied
+*     Mappings could be replaced by an equivalent pair of Mappings from the
+*     same classes as the supplied pair, but in reversed order. Each pair
+*     of Mappings is considered to be compunded in series. The supplied
+*     Mapings are not changed in any way.
+
+*  Parameters:
+*     map1
+*        The Mapping to be applied first.
+*     map2
+*        The Mapping to be applied second.
+*     inv1
+*        The invert flag to use with map1. A value of zero causes the forward
+*        mapping to be used, and a non-zero value causes the inverse
+*        mapping to be used.
+*     inv2
+*        The invert flag to use with map2.
+*     simpler
+*        Addresss of a location at which to return a flag indicating if
+*        the swapped Mappings would be intrinsically simpler than the
+*        original Mappings.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     1 if the Mappings could be swapped, 0 otherwise.
+
+*  Notes:
+*     -  One of the supplied pair of Mappings must be a MatrixMap.
+*     -  A value of 0 is returned if an error has already occurred, or if
+*     this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *mat;        /* Pointer to MatrixMap Mapping */
+   AstMapping *nomat;        /* Pointer to non-MatrixMap Mapping */
+   const char *class1;       /* Pointer to map1 class string */
+   const char *class2;       /* Pointer to map2 class string */
+   const char *nomat_class;  /* Pointer to non-MatrixMap class string */
+   double *consts;           /* Pointer to constants array */
+   int *inperm;              /* Pointer to input axis permutation array */
+   int *outperm;             /* Pointer to output axis permutation array */
+   int i;                    /* Loop count */
+   int invert[ 2 ];          /* Original invert flags */
+   int nax;                  /* No. of in/out coordinates for the MatrixMap */
+   int nin;                  /* No. of input coordinates for the PermMap */
+   int nout;                 /* No. of output coordinates for the PermMap */
+   int ret;                  /* Returned flag */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise */
+   ret = 0;
+   *simpler = 0;
+
+/* Temporarily set the Invert attributes of both Mappings to the supplied
+   values. */
+   invert[ 0 ] = astGetInvert( map1 );
+   astSetInvert( map1, inv1 );
+
+   invert[ 1 ] = astGetInvert( map2 );
+   astSetInvert( map2, inv2 );
+
+/* Get the classes of the two mappings. */
+   class1 = astGetClass( map1 );
+   class2 = astGetClass( map2 );
+   if( astOK ){
+
+/* Get a pointer to the MatrixMap and non-MatrixMap Mappings. */
+      if( !strcmp( class1, "MatrixMap" ) ){
+         mat = (AstMatrixMap *) map1;
+         nomat = map2;
+         nomat_class = class2;
+      } else {
+         nomat = map1;
+         mat = (AstMatrixMap *) map2;
+         nomat_class = class1;
+      }
+
+/* Get the number of input axes for the MatrixMap. */
+      nax = astGetNin( mat );
+
+/* If it is a WinMap, the Mappings can be swapped. */
+      if( !strcmp( nomat_class, "WinMap" ) ){
+         ret = 1;
+
+/* If it is a PermMap, the Mappings can be swapped so long as:
+   1) all links between input and output axes in the PermMap are
+   bi-directional. This does not preclude the existence of unconnected
+   axes, which do not have links (bi-directional or otherwise).
+   2) The MatrixMap is square, and invertable.
+   3) If the permMap is applied first, then each output of the PermMap
+      which is assigned a constant value must correspond to a "scaling" row
+      and column in the MatrixMap. I.e. if PermMap output axis "i" is
+      assigned a constant value, then row i and column i of the following
+      MatrixMap must contain only zeros, EXCEPT for the diagonal term (row
+      i, column i) which must be non-zero. If the Mappings are in the other
+      order, then the same applies to PermMap input axes assigned a constant
+      value. */
+
+/* Check the other Mapping is a PermMap, and that the MatrixMap is square
+   and has an inverse. */
+      } else if( !strcmp( nomat_class, "PermMap" ) &&
+                 nax == astGetNout( mat ) && ( mat->form == UNIT ||
+                 ( mat->i_matrix != NULL &&
+                   mat->f_matrix != NULL ) ) ) {
+
+/* Get the number of input and output coordinates for the PermMap. */
+         nin = astGetNin( nomat );
+         nout = astGetNout( nomat );
+
+/* We need to know the axis permutation arrays and constants array for
+   the PermMap. */
+         PermGet( (AstPermMap *) nomat, &outperm, &inperm, &consts, status );
+         if( astOK ) {
+
+/* Indicate we can swap with the PermMap. */
+            ret = 1;
+
+/* Check each output axis. If any links between axes are found which are
+   not bi-directional, indicate that we cannot swap with the PermMap. */
+            for( i = 0; i < nout; i++ ){
+               if( outperm[ i ] >= 0 && outperm[ i ] < nin ) {
+                  if( inperm[ outperm[ i ] ] != i ) {
+                     ret = 0;
+                     break;
+                  }
+               }
+            }
+
+/* Check each input axis. If any links between axes are found which are
+   not bi-directional, indicate that we cannot swap with the PermMap. */
+            for( i = 0; i < nin; i++ ){
+               if( inperm[ i ] >= 0 && inperm[ i ] < nout ) {
+                  if( outperm[ inperm[ i ] ] != i ) {
+                     ret = 0;
+                     break;
+                  }
+               }
+            }
+
+/* If the PermMap is suitable, check that any constant values fed from the
+   PermMap into the MatrixMap (in either forward or inverse direction)
+   are not changed by the MatrixMap. This requires the row and column for
+   each constant axis to be zeros, ecept for a value of 1.0 on the
+   diagonal. First deal with the cases where the PermMap is applied
+   first, so the outputs of the PermMap are fed into the MatrixMap in the
+   forward direction. */
+            if( ret && ( nomat == map1 ) ) {
+
+               if( nout != nax ){
+                  astError( AST__RDERR, "PermMap produces %d outputs, but the following"
+                            "MatrixMap has %d inputs\n", status, nout, nax );
+                  ret = 0;
+               }
+
+/* Consider each output axis of the PermMap. */
+               for( i = 0; i < nout && astOK ; i++ ) {
+
+/* If this PermMap output is assigned a constant... */
+                  if( outperm[ i ] < 0 || outperm[ i ] >= nin ) {
+
+/* Check the i'th row of the MatrixMap is all zero except for the i'th
+   column which must be non-zero. If not indicate that the MatrixMap cannot
+   swap with the PermMap and leave the loop. */
+                     if( !ScalingRowCol( mat, i, status ) ) {
+                        ret = 0;
+                        break;
+                     }
+                  }
+               }
+            }
+
+/* Now deal with the cases where the PermMap is applied second, so the inputs
+   of the PermMap are fed into the MatrixMap in the inverse direction. */
+            if( ret && ( nomat == map2 ) ) {
+
+               if( nin != nax ){
+                  astError( AST__RDERR, "Inverse PermMap produces %d inputs, but the "
+                            "preceding MatrixMap has %d outputs\n", status, nin, nax );
+                  ret = 0;
+               }
+
+/* Consider each input axis of the PermMap. */
+               for( i = 0; i < nin && astOK; i++ ){
+
+/* If this PermMap input is assigned a constant (by the inverse Mapping)... */
+                  if( inperm[ i ] < 0 || inperm[ i ] >= nout ) {
+
+/* Check the i'th row of the MatrixMap is all zero except for the i'th
+   column which must be non-zero. If not indicate that the MatrixMap cannot
+   swap with the PermMap and leave the loop. */
+                     if( !ScalingRowCol( mat, i, status ) ) {
+                        ret = 0;
+                        break;
+                     }
+                  }
+               }
+            }
+
+/* If we can swap with the PermMap, the swapped Mappings may be
+   intrinsically simpler than the original mappings. */
+            if( ret ) {
+
+/* If the PermMap precedes the WinMap, this will be the case if the PermMap
+   has more outputs than inputs. If the WinMap precedes the PermMap, this
+   will be the case if the PermMap has more inputs than outputs. */
+               *simpler = ( nomat == map1 ) ? nout > nin : nin > nout;
+            }
+
+/* Free the axis permutation and constants arrays. */
+            outperm = (int *) astFree( (void *) outperm );
+            inperm = (int *) astFree( (void *) inperm );
+            consts = (double *) astFree( (void *) consts );
+         }
+      }
+   }
+
+/* Re-instate the original settings of the Invert attributes for the
+   supplied MatrixMaps. */
+   astSetInvert( map1, invert[ 0 ] );
+   astSetInvert( map2, invert[ 1 ] );
+
+/* Return the answer. */
+   return astOK ? ret : 0;
+}
+
+static void CompressMatrix( AstMatrixMap *this, int *status ){
+/*
+*  Name:
+*     CompressMatrix
+
+*  Purpose:
+*     If possible, reduce the amount of storage needed to store a MatrixMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     void CompressMatrix( AstMatrixMap *this, int *status )
+
+*  Class Membership:
+*     MatrixMap member function.
+
+*  Description:
+*     The supplid MatrixMap is converted to its most compressed form
+*     (i.e no element values if it is a unit matrix, diagonal elements only
+*     if it is a diagonal matrix, or all elements otherwise).
+
+*  Parameters:
+*     this
+*        A pointer to the MatrixMap to be compressed.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double *a;                     /* Pointer to next element */
+   double *colmax;                /* Pointer to array holding column max values */
+   double *fmat;                  /* Pointer to compressed forward matrix */
+   double *rowmax;                /* Pointer to array holding row max values */
+   double mval;                   /* Matrix element value */
+   int i;                         /* Loop count */
+   int j;                         /* Loop count */
+   int k;                         /* Loop count */
+   int ncol;                      /* No. of columns in forward matrix */
+   int ndiag;                     /* No. of diagonal elements in matrix */
+   int new_form;                  /* Compressed storage form */
+   int new_inv;                   /* New inverse requied? */
+   int next_diag;                 /* Index of next diagonal element */
+   int nrow;                      /* No. of rows in forward matrix */
+
+/* Check the global error status. */
+   if ( !astOK || !this ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   new_inv = 0;
+
+/* Get the dimensions of the forward matrix. */
+   if( astGetInvert( this ) ){
+      nrow = astGetNin( this );
+      ncol = astGetNout( this );
+   } else {
+      ncol = astGetNin( this );
+      nrow = astGetNout( this );
+   }
+
+/* Store the number of diagonal elements in the matrix. This is the
+   minimum of the number of rows and columns. */
+   if( ncol < nrow ){
+      ndiag = ncol;
+   } else {
+      ndiag = nrow;
+   }
+
+/* If the MatrixMap is already stored in UNIT form, it cannot be compressed
+   any further. */
+   if( this->form == UNIT){
+      return;
+
+/* Otherwise, if the MatrixMap is stored in DIAGONAL form, it could be
+   compressed into a UNIT MatrixMap if all the supplied element values are
+   one. */
+   } else if( this->form == DIAGONAL ){
+      new_form = UNIT;
+      for( i = 0; i < ndiag; i++ ){
+         if( !astEQUAL( (this->f_matrix)[ i ], 1.0 ) ){
+            new_form = DIAGONAL;
+            break;
+         }
+      }
+
+/* If it can be compressed, change the storage form and free the arrays
+   holding the diagonal element values. */
+      if( new_form == UNIT ) {
+         this->f_matrix = (double *) astFree( (void *)( this->f_matrix ) );
+         this->i_matrix = (double *) astFree( (void *)( this->i_matrix ) );
+         this->form = UNIT;
+      }
+
+/* Otherwise, a full MatrixMap has been supplied, but this could be stored
+   in a unit or diagonal MatrixMap if the element values are appropriate. */
+   } else {
+      new_form = FULL;
+
+/* Find the maximum absolute value in each column. Scale by
+   sqrt(DBL_EPSILON) to be come a lower limit for non-zero values. */
+      colmax = astMalloc( ncol*sizeof( double ) );
+      if( colmax ) {
+         for( j = 0; j < ncol; j++ ) {
+            colmax[ j ] = 0.0;
+            i = j;
+            for( k = 0; k < nrow; k++ ) {
+               mval = (this->f_matrix)[ i ];
+               if( mval != AST__BAD ) {
+                  mval = fabs( mval );
+                  if( mval > colmax[ j ] ) colmax[ j ] = mval;
+               }
+               i += ncol;
+            }
+            colmax[ j ] *= sqrt( DBL_EPSILON );
+         }
+      }
+
+/* Find the maximum absolute value in each row. Scale by
+   sqrt(DBL_EPSILON) to be come a lower limit for non-zero values. */
+      rowmax = astMalloc( nrow*sizeof( double ) );
+      if( rowmax ) {
+         for( k = 0; k < nrow; k++ ) {
+            rowmax[ k ] = 0.0;
+            i = k*ncol;
+            for( j = 0; j < ncol; j++ ) {
+               mval = (this->f_matrix)[ i ];
+               if( mval != AST__BAD ) {
+                  mval = fabs( mval );
+                  if( mval > rowmax[ k ] ) rowmax[ k ] = mval;
+               }
+               i++;
+            }
+            rowmax[ k ] *= sqrt( DBL_EPSILON );
+         }
+      }
+
+/* Check memory can be used */
+      if( astOK ) {
+
+/* Initialise a flag indicating that the inverse matrix does not need to
+   be re-calculated. */
+         new_inv = 0;
+
+/* Initially assume that the forward matrix is a unit matrix. */
+         new_form = UNIT;
+
+/* Store a pointer to the next matrix element. */
+         a = this->f_matrix;
+
+/* Loop through all the rows in the forward matrix array. */
+         for( k = 0; k < nrow; k++ ) {
+
+/* Loop through all the elements in this column. */
+            for( j = 0; j < ncol; j++, a++ ) {
+
+/* If this element is bad, use full form. */
+               if( *a == AST__BAD ) {
+                  new_form = FULL;
+
+/* Otherwise, if this is a diagonal term, check its value. If it is not one,
+   then the matrix cannot be a unit matrix, but it could still be a diagonal
+   matrix. */
+               } else {
+                  if( j == k ) {
+                     if( *a != 1.0 && new_form == UNIT ) new_form = DIAGONAL;
+
+/* If this is not a diagonal element, and the element value is not zero,
+   then the matrix is not a diagonal matrix. Allow a tolerance of
+   SQRT(DBL_EPSILON) times the largest value in the same row or column as
+   the current matrix element. That is, an element must be insignificant
+   to both its row and its column to be considered as effectively zero.
+   Replace values less than this limit with zero. */
+                  } else {
+                     mval = fabs( *a );
+                     if( mval <= rowmax[ k ] &&
+                         mval <= colmax[ j ] ) {
+
+/* If the element will change value, set a flag indicating that the inverse
+   matrix needs to be re-calculated. */
+                        if( *a != 0.0 ) new_inv = 1;
+
+/* Ensure this element value is zero. */
+                        *a = 0.0;
+
+                     } else {
+                        new_form = FULL;
+                     }
+                  }
+               }
+            }
+         }
+      }
+
+/* Free memory. */
+      colmax = astFree( colmax );
+      rowmax = astFree( rowmax );
+
+/* If it can be compressed into a UNIT MatrixMap, change the storage form and
+   free the arrays holding the element values. */
+      if( new_form == UNIT ) {
+         this->f_matrix = (double *) astFree( (void *)( this->f_matrix ) );
+         this->i_matrix = (double *) astFree( (void *)( this->i_matrix ) );
+         this->form = UNIT;
+
+/* Otherwise, if it can be compressed into a DIAGONAL MatrixMap, copy the
+   diagonal elements from the full forward matrix into a newly allocated
+   array, use this array to replace the forward matrix array in the MatrixMap,
+   create a new inverse matrix, and change the storage form. */
+      } else if( new_form == DIAGONAL ) {
+         fmat = astMalloc( sizeof(double)*(size_t)ndiag );
+         if( fmat ){
+
+            next_diag = 0;
+            for( i = 0; i < ndiag; i++ ){
+               fmat[ i ] = (this->f_matrix)[ next_diag ];
+               next_diag += ncol + 1;
+            }
+
+            (void) astFree( (void *) this->f_matrix );
+            (void) astFree( (void *) this->i_matrix );
+
+            this->f_matrix = fmat;
+            this->i_matrix = InvertMatrix( DIAGONAL, nrow, ncol, fmat, status );
+            this->form = DIAGONAL;
+
+         }
+
+/* Calculate a new inverse matrix if necessary. */
+      } else if( new_inv ) {
+         (void) astFree( (void *) this->i_matrix );
+         this->i_matrix = InvertMatrix( FULL, nrow, ncol, this->f_matrix, status );
+      }
+   }
+
+   return;
+
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two MatrixMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     MatrixMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two MatrixMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a MatrixMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the MatrixMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *that;
+   AstMatrixMap *this;
+   double *that_matrix;
+   double *this_matrix;
+   int i;
+   int nin;
+   int nout;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two MatrixMap structures. */
+   this = (AstMatrixMap *) this_object;
+   that = (AstMatrixMap *) that_object;
+
+/* Check the second object is a MatrixMap. We know the first is a
+   MatrixMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAMatrixMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNout( that ) == nout && astGetNin( that ) == nin ) {
+
+/* Assume the MatrixMaps are equivalent. */
+         result = 1;
+
+/* Ensure both MatrixMaps are stored in full form. */
+         ExpandMatrix( this, status );
+         ExpandMatrix( that, status );
+
+/* Get pointers to the arrays holding the elements of the forward matrix
+   for both MatrixMaps. */
+         if( astGetInvert( this ) ) {
+            this_matrix = this->i_matrix;
+         } else {
+            this_matrix = this->f_matrix;
+         }
+
+         if( astGetInvert( that ) ) {
+            that_matrix = that->i_matrix;
+         } else {
+            that_matrix = that->f_matrix;
+         }
+
+/* If either of the above arrays is not available, try to get the inverse
+   matrix arrays. */
+         if( !this_matrix || !that_matrix ) {
+            if( astGetInvert( this ) ) {
+               this_matrix = this->f_matrix;
+            } else {
+               this_matrix = this->i_matrix;
+            }
+
+            if( astGetInvert( that ) ) {
+               that_matrix = that->f_matrix;
+            } else {
+               that_matrix = that->i_matrix;
+            }
+         }
+
+/* If both arrays are now available compare their elements. */
+         if( this_matrix && that_matrix ) {
+            result = 1;
+            for( i = 0; i < nin*nout; i++ ) {
+               if( !astEQUAL( this_matrix[ i ], that_matrix[ i ] ) ){
+                  result = 0;
+                  break;
+               }
+            }
+         }
+
+/* Ensure the supplied MatrixMaps are stored back in compressed form. */
+         CompressMatrix( this, status );
+         CompressMatrix( that, status );
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static void ExpandMatrix( AstMatrixMap *this, int *status ){
+/*
+*  Name:
+*     ExpandMatrix
+
+*  Purpose:
+*     Ensure the MatrixMap is stored in full (non-compressed) form.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     void ExpandMatrix( AstMatrixMap *this, int *status )
+
+*  Class Membership:
+*     MatrixMap member function.
+
+*  Description:
+*     If the supplid MatrixMap is stored in a compressed form (i.e no
+*     element values if it is a unit matrix, diagonal elements only
+*     if it is a diagonal matrix), it is expanded into a full MatrixMap
+*     in which all elements are stored.
+
+*  Parameters:
+*     this
+*        A pointer to the MatrixMap to be expanded.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double *fmat;                  /* Pointer to full forward matrix */
+   double *imat;                  /* Pointer to full inverse matrix */
+   int i;                         /* Loop count */
+   int ncol;                      /* No. of columns in forward matrix */
+   int ndiag;                     /* No. of diagonal elements in matrix */
+   int nrow;                      /* No. of rows in forward matrix */
+
+/* Check the global error status. Also return if the MatrixMap
+   pointer is null. */
+   if ( !astOK || !this ) return;
+
+/* Return without action if the MatrixMap is already in full form. */
+   if( this->form == FULL ) return;
+
+/* Get the dimensions of the forward matrix. */
+   if( astGetInvert( this ) ){
+      nrow = astGetNin( this );
+      ncol = astGetNout( this );
+   } else {
+      ncol = astGetNin( this );
+      nrow = astGetNout( this );
+   }
+
+/* Store the number of diagonal elements. */
+   if( nrow > ncol ){
+      ndiag = ncol;
+   } else {
+      ndiag = nrow;
+   }
+
+/* Allocate arrays to hold the full forward and inverse matrices. */
+   fmat = (double *) astMalloc( sizeof( double )*(size_t)( nrow*ncol ) );
+   imat = (double *) astMalloc( sizeof( double )*(size_t)( nrow*ncol ) );
+   if( imat && fmat ){
+
+/* Fill them both with zeros. */
+      for( i = 0; i < nrow*ncol; i++ ) {
+         fmat[ i ] = 0.0;
+         imat[ i ] = 0.0;
+      }
+
+/* If a unit MatrixMap was supplied, put ones on the diagonals. */
+      if( this->form == UNIT ){
+         for( i = 0; i < ndiag; i++ ) {
+            fmat[ i*( ncol + 1 ) ] = 1.0;
+            imat[ i*( nrow + 1 ) ] = 1.0;
+         }
+
+/* If a diagonal MatrixMap was supplied, copy the diagonal terms from
+   the supplied MatrixMap. */
+      } else if( this->form == DIAGONAL ){
+         for( i = 0; i < ndiag; i++ ) {
+            fmat[ i*( ncol + 1 ) ] = (this->f_matrix)[ i ];
+            imat[ i*( nrow + 1 ) ] = (this->i_matrix)[ i ];
+         }
+      }
+
+/* Free any existing arrays in the MatrixMap and store the new ones. */
+      (void) astFree( (void *) this->f_matrix );
+      (void) astFree( (void *) this->i_matrix );
+
+      this->f_matrix = fmat;
+      this->i_matrix = imat;
+
+/* Update the storage form. */
+      this->form = FULL;
+
+/* If either of the new matrices could not be allocated, ensure that
+   both have been freed. */
+   } else {
+      fmat = (double *) astFree( (void *) fmat );
+      imat = (double *) astFree( (void *) imat );
+   }
+
+   return;
+
+}
+
+static int FindString( int n, const char *list[], const char *test,
+                       const char *text, const char *method,
+                       const char *class, int *status ){
+/*
+*  Name:
+*     FindString
+
+*  Purpose:
+*     Find a given string within an array of character strings.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrix.h"
+*     int FindString( int n, const char *list[], const char *test,
+*                     const char *text, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     MatrixMap method.
+
+*  Description:
+*     This function identifies a supplied string within a supplied
+*     array of valid strings, and returns the index of the string within
+*     the array. The test option may not be abbreviated, but case is
+*     insignificant.
+
+*  Parameters:
+*     n
+*        The number of strings in the array pointed to be "list".
+*     list
+*        A pointer to an array of legal character strings.
+*     test
+*        A candidate string.
+*     text
+*        A string giving a description of the object, parameter,
+*        attribute, etc, to which the test value refers.
+*        This is only for use in constructing error messages. It should
+*        start with a lower case letter.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The index of the identified string within the supplied array, starting
+*     at zero.
+
+*  Notes:
+*     -  A value of -1 is returned if an error has already occurred, or
+*     if this function should fail for any reason (for instance if the
+*     supplied option is not specified in the supplied list).
+
+*/
+
+/* Local Variables: */
+   int ret;                /* The returned index */
+
+/* Check global status. */
+   if( !astOK ) return -1;
+
+/* Compare the test string with each element of the supplied list. Leave
+   the loop when a match is found. */
+   for( ret = 0; ret < n; ret++ ) {
+      if( !Ustrcmp( test, list[ ret ], status ) ) break;
+   }
+
+/* Report an error if the supplied test string does not match any element
+   in the supplied list. */
+   if( ret >= n ) {
+      astError( AST__RDERR, "%s(%s): Illegal value '%s' supplied for %s.", status,
+                method, class, test, text );
+      ret = -1;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static int GetIsLinear( AstMapping *this_mapping, int *status ){
+/*
+*  Name:
+*     GetIsLinear
+
+*  Purpose:
+*     Return the value of the IsLinear attribute for a MatrixMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GetIsLinear( AstMapping *this, int *status )
+
+*  Class Membership:
+*     MatrixMap member function (over-rides the protected astGetIsLinear
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns the value of the IsLinear attribute for a
+*     Frame, which is always one.
+
+*  Parameters:
+*     this
+*        Pointer to the MatrixMap.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   return 1;
+}
+
+static int Ustrcmp( const char *a, const char *b, int *status ){
+/*
+*  Name:
+*     Ustrncmp
+
+*  Purpose:
+*     A case blind version of strcmp.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     int Ustrcmp( const char *a, const char *b )
+
+*  Class Membership:
+*     MatrixMap member function.
+
+*  Description:
+*     Returns 0 if there are no differences between the two strings, and 1
+*     otherwise. Comparisons are case blind.
+
+*  Parameters:
+*     a
+*        Pointer to first string.
+*     b
+*        Pointer to second string.
+
+*  Returned Value:
+*     Zero if the strings match, otherwise one.
+
+*  Notes:
+*     -  This function does not consider the sign of the difference between
+*     the two strings, whereas "strcmp" does.
+*     -  This function attempts to execute even if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   const char *aa;         /* Pointer to next "a" character */
+   const char *bb;         /* Pointer to next "b" character */
+   int ret;                /* Returned value */
+
+/* Initialise the returned value to indicate that the strings match. */
+   ret = 0;
+
+/* Initialise pointers to the start of each string. */
+   aa = a;
+   bb = b;
+
+/* Loop round each character. */
+   while( 1 ){
+
+/* We leave the loop if either of the strings has been exhausted. */
+      if( !(*aa ) || !(*bb) ){
+
+/* If one of the strings has not been exhausted, indicate that the
+   strings are different. */
+         if( *aa || *bb ) ret = 1;
+
+/* Break out of the loop. */
+         break;
+
+/* If neither string has been exhausted, convert the next characters to
+   upper case and compare them, incrementing the pointers to the next
+   characters at the same time. If they are different, break out of the
+   loop. */
+      } else {
+
+         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
+            ret = 1;
+            break;
+         }
+
+      }
+
+   }
+
+/* Return the result. */
+   return ret;
+
+}
+
+void astInitMatrixMapVtab_(  AstMatrixMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitMatrixMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a MatrixMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     void astInitMatrixMapVtab( AstMatrixMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     MatrixMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the MatrixMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAMatrixMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->MtrRot = MtrRot;
+   vtab->MtrMult = MtrMult;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_mapsplit = mapping->MapSplit;
+   mapping->MapSplit = MapSplit;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->GetIsLinear = GetIsLinear;
+   mapping->GetTranForward = GetTranForward;
+   mapping->GetTranInverse = GetTranInverse;
+   mapping->MapMerge = MapMerge;
+   mapping->Rate = Rate;
+
+/* Declare the destructor and copy constructor. */
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+
+/* Declare the class dump function. */
+   astSetDump( vtab, Dump, "MatrixMap", "Matrix transformation" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+
+static double *InvertMatrix( int form, int nrow, int ncol, double *matrix, int *status ){
+/*
+*  Name:
+*     InvertMatrix
+
+*  Purpose:
+*     Invert a suplied matrix.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     double *InvertMatrix( int form, int nrow, int ncol, double *matrix, int *status )
+
+*  Class Membership:
+*     MatrixMap member function.
+
+*  Description:
+*     This function returns a pointer to a matrix holding the inverse of
+*     the supplied matrix, or a NULL pointer if the inverse is not defined.
+*     The memory to store the inverse matrix is allocated internally, and
+*     should be freed using astFree when no longer required.
+*
+*     The correspondence between a full matrix and its inverse is only
+*     unique if the matrix is square, and so a NULL pointer is returned if
+*     the supplied matrix is not square.
+
+*  Parameters:
+*     form
+*        The form of the MatrixMap; UNIT, DIAGONAL or FULL.
+*     nrow
+*        Number of rows in the supplied matrix.
+*     ncol
+*        Number of columns in the supplied matrix.
+*     matrix
+*        A pointer to the input matrix. Elements should be stored in row
+*        order (i.e. (row 1,column 1 ), (row 1,column 2 )... (row 2,column 1),
+*        etc).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output matrix.
+
+*  Notes:
+*     -  A NULL pointer is returned if a unit matrix is supplied.
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  No error is reported if the inverse is not defined.
+*/
+
+/* Local Variables: */
+   double det;                    /* Determinant of supplied matrix */
+   double mval;                   /* Matrix element value */
+   double *out;                   /* Pointer to returned inverse matrix */
+   double *vector;                /* Pointer to vector used by palDmat */
+   int i;                         /* Matrix element number */
+   int *iw;                       /* Pointer to workspace used by palDmat */
+   int nel;                       /* No. of elements in square matrix */
+   int ndiag;                     /* No. of diagonal elements */
+   int ok;                        /* Zero if any bad matrix values found */
+   int sing;                      /* Zero if matrix is not singular */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Return a NULL pointer if the input matrix is NULL. */
+   if( !matrix ) return NULL;
+
+/* If a unit matrix map has been supplied, return NULL. */
+   if( form == UNIT ){
+      return NULL;
+
+/* If a diagonal matrix has been supplied, allocate an array to hold
+   the diagonal terms of the inverse matrix. Store the reciprocal
+   of the input matrix diagonal terms in it. If any of the input diagonal
+   terms are zero or BAD, set the associated elements of the inverse matrix
+   BAD. */
+   } else if( form == DIAGONAL ){
+      if( nrow > ncol ) {
+         ndiag = ncol;
+      } else {
+         ndiag = nrow;
+      }
+
+      out = (double *) astMalloc( sizeof( double )*(size_t)ndiag );
+
+      if( out ) {
+         for( i = 0; i < ndiag; i++ ) {
+            mval = matrix[ i ];
+            if( mval != 0.0 && mval != AST__BAD ){
+               out[ i ] = 1.0/mval;
+            } else {
+               out[ i ] = AST__BAD;
+            }
+         }
+      }
+
+/* If a full matrix has been supplied, initialise the returned pointer. */
+   } else {
+      out = NULL;
+
+/* Check that the matrix is square. */
+      if( nrow == ncol ){
+
+/* Find the number of elements in the matrix. */
+         nel = nrow*ncol;
+
+/* See if there are any bad values in the matrix. */
+         ok = 1;
+         for ( i=0; i<nel; i++ ) {
+            if ( matrix[i] == AST__BAD ) {
+               ok = 0;
+               break;
+            }
+         }
+
+/* Only continue if there are no bad matrix values. */
+         if( ok ) {
+
+/* Take a copy of the supplied matrix */
+            out = (double *) astStore( NULL, (void *) matrix,
+                                       astSizeOf( (void *) matrix ) );
+
+/* The SLALIB function which inverts the matrix also applies the inverse
+   matrix to a vector. We are not interested in the vector in this
+   instance, but we still have to provide one for SLALIB to use. Allocate
+   memory for the vector. */
+            vector = (double *) astMalloc( sizeof(double)*(size_t) nrow );
+
+/* If it was allocated succesfully, fill it with zeros. */
+            if( astOK ){
+               for ( i=0; i<nrow; i++ ) vector[i] = 0.0;
+
+/* Obtain work space and attempt to invert the matrix using SLALIB, then
+   free the work space. */
+               iw = (int *) astMalloc( sizeof(int)*(size_t) nrow );
+               if( astOK ) palDmat( nrow, out, vector, &det, &sing, iw );
+               iw = (int *) astFree( (void *) iw );
+
+            }
+
+/* If the matrix could not be inverted, free the memory used to hold the
+   square matrix, and return the NULL pointer. */
+            if ( !astOK || sing != 0 ){
+               out = (double *) astFree( (void *) out );
+            }
+
+/*  Free the memory used to hold the vector. */
+            vector = (double *) astFree( (void *) vector );
+         }
+      }
+   }
+
+/* Return the pointer. */
+
+   return out;
+
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a MatrixMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     MatrixMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated MatrixMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated MatrixMap with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated MatrixMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the MatrixMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        MatrixMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated MatrixMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping **maplt;   /* New mappings list pointer */
+   AstMapping *map2;     /* Pointer to replacement Mapping */
+   AstMapping *mc[2];    /* Copies of supplied Mappings to swap */
+   AstMapping *smc0;     /* Simplied Mapping */
+   AstMapping *smc1;     /* Simplied Mapping */
+   AstMatrixMap *mm;     /* Pointer to supplied MatrixMap */
+   AstMatrixMap *newmm;  /* Pointer to replacement MatrixMap */
+   const char *class1;   /* Pointer to first Mapping class string */
+   const char *class2;   /* Pointer to second Mapping class string */
+   const char *nclass;   /* Pointer to neighbouring Mapping class */
+   double *b;            /* Pointer to scale terms */
+   double *new_mat;      /* Pointer to elements of new MatrixMap */
+   int *invlt;           /* New invert flags list pointer */
+   int do1;              /* Would a backward swap make a simplification? */
+   int do2;              /* Would a forward swap make a simplification? */
+   int i1;               /* Index of first MatrixMap to merge */
+   int i2;               /* Index of last MatrixMap to merge */
+   int i;                /* Loop counter */
+   int ic[2];            /* Copies of supplied invert flags to swap */
+   int invert;           /* Should the inverted Mapping be used? */
+   int j;                /* Loop counter */
+   int nin;              /* Number of input coordinates for MatrixMap */
+   int nmapt;            /* No. of Mappings in list */
+   int nout;             /* Number of output coordinates for MatrixMap */
+   int nstep1;           /* No. of Mappings backwards to next mergable Mapping */
+   int nstep2;           /* No. of Mappings forward to next mergable Mapping */
+   int result;           /* Result value to return */
+   int swaphi;           /* Can MatrixMap be swapped with higher neighbour? */
+   int swaplo;           /* Can MatrixMap be swapped with lower neighbour? */
+   int zoom;             /* Can MatrixMap be replaced by a ZoomMap? */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   i1 = 0;
+   i2 = 0;
+
+/* Get the Invert attribute for the specified mapping. */
+   invert = astGetInvert( ( *map_list )[ where ] );
+
+/* Get the number of input and output axes for the MatrixMap. Swap these
+   if the supplied invert flag is not the same as the Invert attribute of
+   the Mapping. */
+   if( ( invert && !( *invert_list )[ where ] ) ||
+       ( !invert && ( *invert_list )[ where ] ) ) {
+      nout = astGetNin( ( *map_list )[ where ] );
+      nin = astGetNout( ( *map_list )[ where ] );
+
+   } else {
+      nin = astGetNin( ( *map_list )[ where ] );
+      nout = astGetNout( ( *map_list )[ where ] );
+   }
+
+/* First of all, see if the MatrixMap can be replaced by a simpler Mapping,
+   without reference to the neighbouring Mappings in the list.           */
+/* ======================================================================*/
+   map2 = NULL;
+   mm = (AstMatrixMap *) ( *map_list )[ where ];
+
+/* If the MatrixMap is a square unit matrix, it can be replaced by a
+   UnitMap. */
+   if( mm->form == UNIT && nin == nout ){
+      map2 = (AstMapping *) astUnitMap( nin, "", status );
+
+/* If the MatrixMap is a square diagonal matrix with equal diagonal
+   terms, then it can be replaced by a ZoomMap. */
+   } else if( mm->form == DIAGONAL && nin == nout &&
+              mm->f_matrix && mm->i_matrix &&
+             (mm->f_matrix)[ 0 ] != AST__BAD ){
+      zoom = 1;
+      b = mm->f_matrix + 1;
+      for( i = 1; i < nin; i++ ){
+         if( !astEQUAL( *b, *( b - 1 ) ) ){
+            zoom = 0;
+            break;
+         }
+         b++;
+      }
+
+      if( zoom ){
+         if( ( *invert_list )[ where ] ){
+            map2 = (AstMapping *) astZoomMap( nin, (mm->i_matrix)[ 0 ], "", status );
+         } else {
+            map2 = (AstMapping *) astZoomMap( nin, (mm->f_matrix)[ 0 ], "", status );
+         }
+      }
+
+/* If the MatrixMap is a full matrix but all off-diagnal elements are
+   zero, it can be replaced by a diagonal MatrixMap. */
+   } else if( mm->form == FULL && nin == nout && mm->f_matrix ){
+      new_mat = astMalloc( sizeof( double )*nin );
+      b = mm->f_matrix;
+      for( i = 0; i < nin && new_mat; i++ ){
+         for( j = 0; j < nout; j++,b++ ){
+            if( i == j ) {
+               new_mat[ i ] = *b;
+            } else if( *b != 0.0 ) {
+               new_mat = astFree( new_mat );
+               break;
+            }
+         }
+      }
+
+      if( new_mat ) {
+         map2 = (AstMapping *) astMatrixMap( nin, nout, 1, new_mat, "",
+                                             status );
+         new_mat = astFree( new_mat );
+      }
+   }
+
+/* If the MatrixMap can be replaced, annul the MatrixMap pointer in the
+   list and replace it with the new Mapping pointer, and indicate that the
+   forward transformation of the returned Mapping should be used. */
+   if( map2 ){
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = map2;
+      ( *invert_list )[ where ] = 0;
+
+/* Return the index of the first modified element. */
+      result = where;
+
+/* If the MatrixMap itself could not be simplified, see if it can be merged
+   with the Mappings on either side of it in the list. */
+/*==========================================================================*/
+   } else {
+
+/* Store the classes of the neighbouring Mappings in the list. */
+       class1 = ( where > 0 ) ? astGetClass( ( *map_list )[ where - 1 ] ) : NULL;
+       class2 = ( where < *nmap - 1 ) ? astGetClass( ( *map_list )[ where + 1 ] ) : NULL;
+
+/* In series. */
+/* ========== */
+      if ( series ) {
+
+/* We first look to see if the MatrixMap can be merged with one of its
+   neighbours, resulting in a reduction of one in the number of Mappings
+   in the list. MatrixMaps can merge directly with another MatrixMap, a
+   ZoomMap, an invertable PermMap, or a UnitMap. */
+         if( class1 && ( !strcmp( class1, "MatrixMap" ) ||
+                         !strcmp( class1, "ZoomMap" ) ||
+                         !strcmp( class1, "PermMap" ) ||
+                         !strcmp( class1, "UnitMap" ) ) ){
+            nclass = class1;
+            i1 = where - 1;
+            i2 = where;
+
+         } else if( class2 && ( !strcmp( class2, "MatrixMap" ) ||
+                                !strcmp( class2, "ZoomMap" ) ||
+                                !strcmp( class2, "PermMap" ) ||
+                                !strcmp( class2, "UnitMap" ) ) ){
+            nclass = class2;
+            i1 = where;
+            i2 = where + 1;
+
+         } else {
+            nclass = NULL;
+         }
+
+/* Only some PermMaps can be merged with (those which have consistent
+   forward and inverse mappings). If this is not one of them, set nclass
+   NULL to indicate this. */
+         if( nclass && !strcmp( nclass, "PermMap" ) &&
+             !PermOK( ( *map_list )[ (i1==where)?i2:i1 ], status ) ) nclass = NULL;
+
+/* If the MatrixMap can merge with one of its neighbours, create the merged
+   Mapping. */
+         if( nclass ){
+
+            if( !strcmp( nclass, "MatrixMap" ) ){
+               newmm = MatMat( ( *map_list )[ i1 ], ( *map_list )[ i2 ],
+                               ( *invert_list )[ i1 ], ( *invert_list )[ i2 ], status );
+               invert = 0;
+
+            } else if( !strcmp( nclass, "ZoomMap" ) ){
+               if( i1 == where ){
+                  newmm = MatZoom( (AstMatrixMap *)( *map_list )[ i1 ],
+                                   (AstZoomMap *)( *map_list )[ i2 ],
+                              ( *invert_list )[ i1 ], ( *invert_list )[ i2 ], status );
+               } else {
+                  newmm = MatZoom( (AstMatrixMap *)( *map_list )[ i2 ],
+                                   (AstZoomMap *)( *map_list )[ i1 ],
+                           ( *invert_list )[ i2 ], ( *invert_list )[ i1 ], status );
+               }
+               invert = 0;
+
+            } else if( !strcmp( nclass, "PermMap" ) ){
+               if( i1 == where ){
+                  newmm = MatPerm( (AstMatrixMap *)( *map_list )[ i1 ],
+                                   (AstPermMap *)( *map_list )[ i2 ],
+                           ( *invert_list )[ i1 ], ( *invert_list )[ i2 ], 1, status );
+               } else {
+                  newmm = MatPerm( (AstMatrixMap *)( *map_list )[ i2 ],
+                                   (AstPermMap *)( *map_list )[ i1 ],
+                           ( *invert_list )[ i2 ], ( *invert_list )[ i1 ], 0, status );
+               }
+               invert = 0;
+
+            } else {
+               newmm = astClone( ( *map_list )[ where ] );
+               invert = ( *invert_list )[ where ];
+            }
+
+/* If succesfull... */
+            if( astOK ){
+
+/* Annul the first of the two Mappings, and replace it with the merged
+   MatrixMap. Also set the invert flag. */
+               (void) astAnnul( ( *map_list )[ i1 ] );
+               ( *map_list )[ i1 ] = (AstMapping *) newmm;
+               ( *invert_list )[ i1 ] = invert;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+               (void) astAnnul( ( *map_list )[ i2 ] );
+               for ( i = i2 + 1; i < *nmap; i++ ) {
+                  ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+                  ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+               }
+
+/* Clear the vacated element at the end. */
+               ( *map_list )[ *nmap - 1 ] = NULL;
+               ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+               ( *nmap )--;
+               result = i1;
+
+            }
+
+/* If the MatrixMap could not merge directly with either of its neighbours,
+   we consider whether it would be worthwhile to swap the MatrixMap with
+   either of its neighbours. This can only be done for certain classes
+   of Mapping (WinMaps and some PermMaps), and will usually require both
+   Mappings to be modified (unless they are commutative). The advantage of
+   swapping the order of the Mappings is that it may result in the MatrixMap
+   being adjacent to a Mapping with which it can merge directly on the next
+   invocation of this function, thus reducing the number of Mappings
+   in the list. */
+         } else {
+
+/* Set a flag if we could swap the MatrixMap with its higher neighbour. "do2"
+   is returned if swapping the Mappings would simplify either of the Mappings. */
+            if( where + 1 < *nmap ){
+               swaphi = CanSwap(  ( *map_list )[ where ],
+                                  ( *map_list )[ where + 1 ],
+                                  ( *invert_list )[ where ],
+                                  ( *invert_list )[ where + 1 ], &do2, status );
+            } else {
+               swaphi = 0;
+               do2 = 0;
+            }
+
+/* If so, step through each of the Mappings which follow the MatrixMap,
+   looking for a Mapping with which the MatrixMap could merge directly. Stop
+   when such a Mapping is found, or if a Mapping is found with which the
+   MatrixMap could definitely not swap. Note the number of Mappings which
+   separate the MatrixMap from the Mapping with which it could merge (if
+   any). */
+            nstep2 = -1;
+            if( swaphi ){
+               for( i2 = where + 1; i2 < *nmap; i2++ ){
+
+/* See if we can merge with this Mapping. If so, note the number of steps
+   between the two Mappings and leave the loop. */
+                  nclass = astGetClass( ( *map_list )[ i2 ] );
+                  if( !strcmp( nclass, "MatrixMap" ) ||
+                      !strcmp( nclass, "ZoomMap" ) ||
+                      ( !strcmp( nclass, "PermMap" ) && PermOK( ( *map_list )[ i2 ], status ) ) ||
+                      !strcmp( nclass, "UnitMap" ) ) {
+                     nstep2 = i2 - where - 1;
+                     break;
+                  }
+
+/* If there is no chance that we can swap with this Mapping, leave the loop
+   with -1 for the number of steps to indicate that no merging is possible.
+   MatrixMaps can swap with WinMaps and some permmaps. */
+                  if( strcmp( nclass, "WinMap" ) &&
+                      strcmp( nclass, "PermMap" ) ) {
+                     break;
+                  }
+
+               }
+
+            }
+
+/* Do the same working forward from the MatrixMap towards the start of the map
+   list. */
+            if( where > 0 ){
+               swaplo = CanSwap(  ( *map_list )[ where - 1 ],
+                                  ( *map_list )[ where ],
+                                  ( *invert_list )[ where - 1 ],
+                                  ( *invert_list )[ where ], &do1, status );
+            } else {
+               swaplo = 0;
+               do1 = 0;
+            }
+
+            nstep1 = -1;
+            if( swaplo ){
+               for( i1 = where - 1; i1 >= 0; i1-- ){
+
+                  nclass = astGetClass( ( *map_list )[ i1 ] );
+                  if( !strcmp( nclass, "MatrixMap" ) ||
+                      ( !strcmp( nclass, "PermMap" ) && PermOK( ( *map_list )[ i1 ], status ) ) ||
+                      !strcmp( nclass, "ZoomMap" ) ||
+                      !strcmp( nclass, "UnitMap" ) ) {
+                     nstep1 = where - 1 - i1;
+                     break;
+                  }
+
+                  if( strcmp( nclass, "WinMap" ) &&
+                      strcmp( nclass, "PermMap" ) ) {
+                     break;
+                  }
+
+               }
+
+            }
+
+/* Choose which neighbour to swap with so that the MatrixMap moves towards the
+   nearest Mapping with which it can merge. */
+            if( do1 || (
+                nstep1 != -1 && ( nstep2 == -1 || nstep2 > nstep1 ) ) ){
+               nclass = class1;
+               i1 = where - 1;
+               i2 = where;
+            } else if( do2 || nstep2 != -1 ){
+               nclass = class2;
+               i1 = where;
+               i2 = where + 1;
+            } else {
+               nclass = NULL;
+            }
+
+/* If there is a target Mapping in the list with which the MatrixMap could
+   merge, consider replacing the supplied Mappings with swapped Mappings to
+   bring the MatrixMap closer to the target Mapping. */
+            if( nclass ){
+
+/* Swap the Mappings. */
+               if (!strcmp( nclass, "WinMap" ) ){
+                  MatWin( (*map_list) + i1, (*invert_list) + i1, where - i1, status );
+
+               } else if( !strcmp( nclass, "PermMap" ) ){
+                  MatPermSwap( (*map_list) + i1, (*invert_list) + i1, where - i1, status );
+               }
+
+/* And then merge them. */
+               if( where == i1 && where + 1 < *nmap ) {    /* Merging upwards */
+                  map2 = astClone( (*map_list)[ where + 1 ] );
+                  nmapt = *nmap - where - 1;
+                  maplt = *map_list + where + 1;
+                  invlt = *invert_list + where + 1;
+
+                  (void) astMapMerge( map2, 0, series, &nmapt, &maplt, &invlt );
+                  map2 = astAnnul( map2 );
+                  *nmap = where + 1 + nmapt;
+
+               } else if( where - 2 >= 0 ) {               /* Merging downwards */
+                  map2 = astClone( (*map_list)[ where - 2 ] );
+                  nmapt = *nmap - where + 2;
+                  maplt = *map_list + where - 2 ;
+                  invlt = *invert_list + where - 2;
+
+                  (void) astMapMerge( map2, 0, series, &nmapt, &maplt, &invlt );
+                  map2 = astAnnul( map2 );
+                  *nmap = where - 2 + nmapt;
+               }
+
+               result = i1;
+
+/* If there is no Mapping available for merging, it may still be
+   advantageous to swap with a neighbour because the swapped Mapping may
+   be simpler than the original Mappings. For instance, a PermMap may
+   strip rows of the MatrixMap leaving only a UnitMap. */
+            } else if( swaphi || swaplo ) {
+
+/* Try swapping with each possible neighbour in turn. */
+               for( i = 0; i < 2; i++ ) {
+
+/*  Set up the class and pointers for the mappings to be swapped, first
+    the lower neighbour, then the upper neighbour. */
+                  if( i == 0 && swaplo ){
+                     nclass = class1;
+                     i1 = where - 1;
+                     i2 = where;
+
+                  } else if( i == 1 && swaphi ){
+                     nclass = class2;
+                     i1 = where;
+                     i2 = where + 1;
+
+                  } else {
+                     nclass = NULL;
+                  }
+
+/* If we have a Mapping to swap with... */
+                  if( nclass ) {
+
+/* Take copies of the Mapping and Invert flag arrays so we do not change
+   the supplied values. */
+                     mc[ 0 ] = (AstMapping *) astCopy( ( (*map_list) + i1 )[0] );
+                     mc[ 1 ] = (AstMapping *) astCopy( ( (*map_list) + i1 )[1] );
+                     ic[ 0 ] = ( (*invert_list) + i1 )[0];
+                     ic[ 1 ] = ( (*invert_list) + i1 )[1];
+
+/* Swap these Mappings. */
+                     if( !strcmp( nclass, "WinMap" ) ){
+                        MatWin( mc, ic, where - i1, status );
+                     } else if( !strcmp( nclass, "PermMap" ) ){
+                        MatPermSwap( mc, ic, where - i1, status );
+                     }
+
+/* If neither of the swapped Mappings can be simplified further, then there
+   is no point in swapping the Mappings, so just annul the map copies. */
+                     smc0 = astSimplify( mc[0] );
+                     smc1 = astSimplify( mc[1] );
+
+                     if( astGetClass( smc0 ) == astGetClass( mc[0] ) &&
+                         astGetClass( smc1 ) == astGetClass( mc[1] ) ) {
+
+                        mc[ 0 ] = (AstMapping *) astAnnul( mc[ 0 ] );
+                        mc[ 1 ] = (AstMapping *) astAnnul( mc[ 1 ] );
+
+/* If one or both of the swapped Mappings could be simplified, then annul
+   the supplied Mappings and return the swapped mappings, storing the index
+   of the first modified Mapping. */
+                     } else {
+                        (void ) astAnnul( ( (*map_list) + i1 )[0] );
+                        (void ) astAnnul( ( (*map_list) + i1 )[1] );
+
+                        ( (*map_list) + i1 )[0] = mc[ 0 ];
+                        ( (*map_list) + i1 )[1] = mc[ 1 ];
+
+                        ( (*invert_list) + i1 )[0] = ic[ 0 ];
+                        ( (*invert_list) + i1 )[1] = ic[ 1 ];
+
+                        result = i1;
+                        break;
+                     }
+
+/* Annul the simplied Mappings */
+                     smc0 = astAnnul( smc0 );
+                     smc1 = astAnnul( smc1 );
+
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     MatrixMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     MatrixMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing MatrixMap. This is only possible if the specified inputs
+*     correspond to some subset of the MatrixMap outputs. That is, there
+*     must exist a subset of the MatrixMap outputs for which each output
+*     depends only on the selected MatrixMap inputs, and not on any of the
+*     inputs which have not been selected. In addition, outputs that are
+*     not in this subset must not depend on any selected inputs. If these
+*     conditions are not met by the supplied MatrixMap, then a NULL Mapping
+*     is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the MatrixMap to be split (the MatrixMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied MatrixMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied MatrixMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied MatrixMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *this;        /* Pointer to MatrixMap structure */
+   double *mat;               /* Pointer to matrix for supplied MatrixMap */
+   double *pmat;              /* Pointer to row start in returned matrix */
+   double *prow;              /* Pointer to row start in supplied matrix */
+   double *rmat;              /* Pointer to matrix for returned MatrixMap */
+   double el;                 /* Next element value in supplied matrix */
+   int *result;               /* Pointer to returned array */
+   int good;                  /* Would new matrix contain any good values/ */
+   int i;                     /* Loop count */
+   int icol;                  /* Column index within supplied MatrixMap */
+   int iel;                   /* Index of next element from the input matrix */
+   int irow;                  /* Row index within supplied MatrixMap */
+   int isel;                  /* Does output depend on any selected inputs? */
+   int ncol;                  /* Number of columns (inputs) in supplied MatrixMap */
+   int nout;                  /* Number of outputs in returned MatrixMap */
+   int nrow;                  /* Number of rows (outputs) in supplied MatrixMap */
+   int ok;                    /* Are input indices OK? */
+   int sel;                   /* Does any output depend on selected inputs? */
+   int unsel;                 /* Does any output depend on unselected inputs? */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the parent astMapSplit method to see if it can do the job. */
+   result = (*parent_mapsplit)( this_map, nin, in, map, status );
+
+/* If not, we provide a special implementation here. */
+   if( !result ) {
+
+/* Get a pointer to the MatrixMap structure. */
+      this = (AstMatrixMap *) this_map;
+
+/* Get the number of inputs and outputs. */
+      ncol = astGetNin( this );
+      nrow = astGetNout( this );
+
+/* Check the supplied input indices are usable. */
+      ok = 1;
+      for( i = 0; i < nin; i++ ) {
+         if( in[ i ] < 0 || in[ i ] >= ncol ) {
+            ok = 0;
+            break;
+         }
+      }
+
+      if( ok ) {
+
+/* Ensure the MatrixMap is stored in full form. */
+         ExpandMatrix( this, status );
+
+/* Allocate the largest array that could be necessary to hold the
+   returned array of Mapping outputs. */
+         result = astMalloc( sizeof(int)*(size_t) nrow );
+
+/* Allocate the largest array that could be necessary to hold the
+   matrix representing the returned MatrixMap. */
+         rmat = astMalloc( sizeof(double)*(size_t) (nrow*ncol) );
+
+/* Get the matrix which defines the current forward transformation. This
+   takes into account whether the MatrixMap has been inverted or not. */
+         if( astGetInvert( this ) ) {
+            mat = this->i_matrix;
+         } else {
+            mat = this->f_matrix;
+         }
+
+/* We cannot create the require Mapping if the matrix is undefined. */
+         if( !mat || !astOK ) {
+            ok = 0;
+            nout = 0;
+            good = 0;
+
+/* Otherwise, loop round all the rows in the matrix. */
+         } else {
+            nout = 0;
+            good = 0;
+            pmat = rmat;
+            iel = 0;
+            for( irow = 0; irow < nrow; irow++ ) {
+
+/* Indicate that this output (i.e. row of the matrix) depends on neither
+   selected nor unselected inputs as yet. */
+               sel = 0;
+               unsel = 0;
+
+/* Save a pointer to the first element of this row in the MatrixMap
+   matrix. */
+               prow = mat + iel;
+
+/* Loop round all the elements in the current row of the matrix. */
+               for( icol = 0; icol < ncol; icol++ ) {
+
+/* If this element is non-zero and non-bad, then output "irow" depends on
+   input "icol". */
+                  el = mat[ iel++ ];
+                  if( el != 0.0 && el != AST__BAD ) {
+
+/* Is input "icol" one of the selected inputs? */
+                     isel = 0;
+                     for( i = 0; i < nin; i++ ) {
+                        if( in[ i ] == icol ) {
+                           isel = 1;
+                           break;
+                        }
+                     }
+
+/* If so, note that this output depends on selected inputs. Otherwise note
+   it depends on unselected inputs. */
+                     if( isel ) {
+                        sel = 1;
+                     } else  {
+                        unsel = 1;
+                     }
+                  }
+               }
+
+/* If this output depends only on selected inputs, we can include it in
+   the returned Mapping.*/
+               if( sel && !unsel ) {
+
+/* Store the index of the output within the original MatrixMap. */
+                  result[ nout ] = irow;
+
+/* Increment the number of outputs in the returned Mapping. */
+                  nout++;
+
+/* Copy the elements of the current matrix row which correspond to the
+   selected inputs into the new matrix. */
+                  for( i = 0; i < nin; i++ ) {
+                    if( astISGOOD( prow[ in[ i ] ] ) ) {
+                        *(pmat++) = prow[ in[ i ] ];
+                        good = 1;
+                     }
+                  }
+               }
+
+/* If this output depends on a selected input, but also depends on an
+   unselected input, we cannot split the MatrixMap. */
+               if( sel && unsel ) {
+                  ok = 0;
+                  break;
+               }
+            }
+         }
+
+
+/* If the returned Mapping can be created, create it. */
+         if( ok && nout > 0 && good ) {
+            *map = (AstMapping *) astMatrixMap( nin, nout, 0, rmat, "", status );
+
+/* Otherwise, free the returned array. */
+         } else {
+            result = astFree( result );
+         }
+
+/* Free resources. */
+         rmat = astFree( rmat );
+
+/* Re-compress the supplied MatrixMap. */
+         CompressMatrix( this, status );
+      }
+   }
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static AstMatrixMap *MatMat( AstMapping *map1, AstMapping *map2, int inv1,
+                             int inv2, int *status ){
+/*
+*  Name:
+*     MatMat
+
+*  Purpose:
+*     Create a merged MatrixMap from two supplied MatrixMaps.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     AstMatrixMap *MatMat( AstMapping *map1, AstMapping *map2, int inv1,
+*                           int inv2, int *status )
+
+*  Class Membership:
+*     MatrixMap member function
+
+*  Description:
+*     This function creates a new MatrixMap which performs a mapping
+*     equivalent to applying the two supplied MatrixMaps in series, in the
+*     directions specified by the "invert" flags (the Invert attributes of
+*     the supplied MatrixMaps are ignored).
+
+*  Parameters:
+*     map1
+*        A pointer to the MatrixMap to apply first.
+*     map2
+*        A pointer to the MatrixMap to apply second.
+*     inv1
+*        The invert flag to use with map1. A value of zero causes the forward
+*        mapping to be used, and a non-zero value causes the inverse
+*        mapping to be used.
+*     inv2
+*        The invert flag to use with map2.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the new MatrixMap.
+
+*  Notes:
+*     -  The forward direction of the returned MatrixMap is equivalent to the
+*     combined effect of the two supplied MatrixMap, operating in the
+*     directions specified by "inv1" and "inv2".
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *result;            /* Pointer to output MatrixMap */
+   int invert[ 2 ];                 /* Original invert flags */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the returned pointer. */
+   result = NULL;
+
+/* Temporarily set their Invert attributes to the supplied values. */
+   invert[ 0 ] = astGetInvert( map1 );
+   astSetInvert( map1, inv1 );
+
+   invert[ 1 ] = astGetInvert( map2 );
+   astSetInvert( map2, inv2 );
+
+/* Create a new MatrixMap by multiplying them together. */
+   result = astMtrMult( (AstMatrixMap *) map1, (AstMatrixMap *) map2 );
+
+/* Re-instate the original settings of the Invert attributes for the
+   supplied MatrixMaps. */
+   astSetInvert( map1, invert[ 0 ] );
+   astSetInvert( map2, invert[ 1 ] );
+
+/* If an error has occurred, annull the returned MatrixMap. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output MatrixMap. */
+   return result;
+}
+
+static AstMatrixMap *MatPerm( AstMatrixMap *mm, AstPermMap *pm, int minv,
+                              int pinv, int mat1, int *status ){
+/*
+*  Name:
+*     MatPerm
+
+*  Purpose:
+*     Create a MatrixMap by merging a MatrixMap and a PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     AstMatrixMap *MatPerm( AstMatrixMap *mm, AstPermMap *pm, int minv,
+*                            int pinv, int mat1, int *status )
+
+*  Class Membership:
+*     MatrixMap member function
+
+*  Description:
+*     This function creates a new MatrixMap which performs a mapping
+*     equivalent to applying the two supplied Mappings in series in the
+*     directions specified by the "invert" flags (the Invert attributes of
+*     the supplied MatrixMaps are ignored).
+
+*  Parameters:
+*     mm
+*        A pointer to the MatrixMap.
+*     pm
+*        A pointer to the PermMap.
+*     minv
+*        The invert flag to use with mm. A value of zero causes the forward
+*        mapping to be used, and a non-zero value causes the inverse
+*        mapping to be used.
+*     pinv
+*        The invert flag to use with pm.
+*     mat1
+*        If non-zero, then "mm" is applied first followed by "pm". Otherwise,
+*        "pm" is applied first followed by "mm".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the new MatrixMap.
+
+*  Notes:
+*     -  The forward direction of the returned MatrixMap is equivalent to the
+*     combined effect of the two supplied Mappings, operating in the
+*     directions specified by "pinv" and "minv".
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *mm2;            /* Pointer to an intermediate MatrixMap */
+   AstMatrixMap *result;         /* Pointer to output MatrixMap */
+   AstPointSet *pset1;           /* Pointer to a PointSet holding unpermuted unit vectors */
+   AstPointSet *pset2;           /* Pointer to a PointSet holding permuted unit vectors */
+   double *matrix;               /* Pointer to a matrix representing the PermMap */
+   double *p;                    /* Pointer to next matrix element */
+   double **ptr1;                /* Pointer to the data in pset1 */
+   double **ptr2;                /* Pointer to the data in pset2 */
+   int i;                        /* Axis index */
+   int j;                        /* Point index */
+   int nax;                      /* No. of axes in the PermMap */
+   int old_minv;                 /* Original setting of MatrixMap Invert attribute */
+   int old_pinv;                 /* Original setting of PermMap Invert attribute */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the returned pointer. */
+   result = NULL;
+
+/* Temporarily set the Invert attributes of both Mappings to the supplied
+   values. */
+   old_minv = astGetInvert( mm );
+   astSetInvert( mm, minv );
+
+   old_pinv = astGetInvert( pm );
+   astSetInvert( pm, pinv );
+
+/* Get the number of axes in the PermMap. The PermMap will have the same
+   number of input and output axes because a check has already been made on
+   it to ensure that this is so (in function PermOK). */
+   nax = astGetNin( pm );
+
+/* We first represent the PermMap as a MatrixMap containing elements with
+   values zero or one. Each row of this matrix is obtained by transforming a
+   unit vector along each axis using the inverse PermMap. Allocate memory
+   to hold the matrix array, and create a PointSet holding the unit
+   vectors. */
+   matrix = (double *) astMalloc( sizeof( double )*(size_t)( nax*nax ) );
+
+   pset1 = astPointSet( nax, nax, "", status );
+   ptr1 = astGetPoints( pset1 );
+
+   pset2 = astPointSet( nax, nax, "", status );
+   ptr2 = astGetPoints( pset2 );
+
+   if( astOK ){
+      for( i = 0; i < nax; i++ ){
+         for( j = 0; j < nax; j++ ) ptr1[ i ][ j ] = 0.0;
+         ptr1[ i ][ i ] = 1.0;
+      }
+
+/* Transform these unit vectors using the inverse PermMap. */
+      (void) astTransform( pm, pset1, 0, pset2 );
+
+/* Copy the transformed vectors into the matrix array. */
+      p = matrix;
+      for( j = 0; j < nax; j++ ){
+         for( i = 0; i < nax; i++ ) *(p++) = ptr2[ i ][ j ];
+      }
+
+/* Create a MatrixMap holding this array. */
+      mm2 = astMatrixMap( nax, nax, 0, matrix, "", status );
+
+/* Create a new MatrixMap equal to the product of the supplied MatrixMap
+   and the MatrixMap just created from the PermMap. */
+      if( mat1 ){
+         result = astMtrMult( mm, mm2 );
+      } else {
+         result = astMtrMult( mm2, mm );
+      }
+
+/* Free everything. */
+      mm2 = astAnnul( mm2 ) ;
+   }
+
+   pset2 = astAnnul( pset2 );
+   pset1 = astAnnul( pset1 );
+   matrix = (double *) astFree( (void *) matrix );
+
+/* Re-instate the original settings of the Invert attribute for the
+   supplied Mappings. */
+   astSetInvert( mm, old_minv );
+   astSetInvert( pm, old_pinv );
+
+/* If an error has occurred, annull the returned MatrixMap. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output MatrixMap. */
+   return result;
+}
+
+static void MatPermSwap( AstMapping **maps, int *inverts, int imm, int *status ){
+/*
+*  Name:
+*     MatPermSwap
+
+*  Purpose:
+*     Swap a PermMap and a MatrixMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     void MatPermSwap( AstMapping **maps, int *inverts, int imm )
+
+*  Class Membership:
+*     MatrixMap member function
+
+*  Description:
+*     A list of two Mappings is supplied containing a PermMap and a
+*     MatrixMap. These Mappings are annulled, and replaced with
+*     another pair of Mappings consisting of a PermMap and a MatrixMap
+*     in the opposite order. These Mappings are chosen so that their
+*     combined effect is the same as the original pair of Mappings.
+
+*  Parameters:
+*     maps
+*        A pointer to an array of two Mapping pointers.
+*     inverts
+*        A pointer to an array of two invert flags.
+*     imm
+*        The index within "maps" of the MatrixMap.
+
+*  Notes:
+*     -  There are restictions on the sorts of PermMaps which can be
+*     swapped with a MatrixMap -- see function CanSwap. It is assumed
+*     that the supplied MatrixMap and PermMap satisfy these requirements.
+
+*/
+
+/* Local Variables: */
+   AstMatrixMap *mm;         /* Pointer to the supplied MatrixMap */
+   AstMatrixMap *mmnew;      /* Pointer to new MatrixMap */
+   AstMatrixMap *smmnew;     /* Pointer to new simplified MatrixMap */
+   AstPermMap *pm;           /* Pointer to the supplied PermMap */
+   AstPermMap *pmnew;        /* Pointer to new PermMap */
+   AstPermMap *spmnew;       /* Pointer to new simplified PermMap */
+   double *consts;           /* Pointer to constants array */
+   double *matrix;           /* Supplied array of matrix elements */
+   double *out_el;           /* Pointer to next element of new MatrixMap */
+   double *out_mat;          /* Matrix elements for new MatrixMap */
+   double c;                 /* Constant */
+   double matel;             /* Matrix element */
+   int *inperm;              /* Pointer to input axis permutation array */
+   int *outperm;             /* Pointer to output axis permutation array */
+   int col;                  /* Index of matrix column */
+   int i;                    /* Axis count */
+   int k;                    /* Axis count */
+   int nin;                  /* No. of axes in supplied PermMap */
+   int nout;                 /* No. of axes in returned PermMap */
+   int old_pinv;             /* Invert value for the supplied PermMap */
+   int row;                  /* Index of matrix row */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   mmnew = NULL;
+   pmnew = NULL;
+
+/* Store pointers to the supplied PermMap and the MatrixMap. */
+   pm = (AstPermMap *) maps[ 1 - imm ];
+   mm = (AstMatrixMap *) maps[ imm ];
+
+/* Temporarily set the Invert attribute of the supplied PermMap to the
+   supplied value. */
+   old_pinv = astGetInvert( pm );
+   astSetInvert( pm, inverts[ 1 - imm ] );
+
+/* Ensure the MatrixMap is stored in full form. */
+   ExpandMatrix( mm, status );
+
+/* Store a pointer to the required array of matrix elements. */
+   if( inverts[ imm ] ) {
+      matrix = mm->i_matrix;
+   } else {
+      matrix = mm->f_matrix;
+   }
+
+/* Get the number of input and output axes of the PermMap. */
+   nin = astGetNin( pm );
+   nout = astGetNout( pm );
+
+/* Allocate memory to hold the matrix elements for the swapped MatrixMap.
+   The number of rows and olumns in the new matrix must equal the number of
+   input or output axes for the PermMap, depending on whether the PermMap
+   or MatrixMap is applied first. */
+   if( imm == 0 ) {
+      out_mat = (double *) astMalloc( sizeof( double )*(size_t)( nout*nout ) );
+   } else {
+      out_mat = (double *) astMalloc( sizeof( double )*(size_t)( nin*nin ) );
+   }
+
+/* We need to know the axis permutation arrays and constants array for
+   the PermMap. */
+   PermGet( pm, &outperm, &inperm, &consts, status );
+   if( astOK ) {
+
+/* First deal with cases where the MatrixMap is applied first. */
+      if( imm == 0 ) {
+
+/* Consider each output axis of the PermMap. */
+         for( i = 0; i < nout; i++ ) {
+
+/* If this output is connected to one of the input axes... */
+            row = outperm[ i ];
+            if( row >= 0 && row < nin ) {
+
+/* Permute the row of the supplied matrix which feeds the corresponding
+   PermMap input axis (i.e. axis outperm[k] ) using the forward PermMap.
+   Store zeros for any output axes which are assigned constants. This forms
+   row i of the new MatrixMap. */
+               out_el = out_mat + nout*i;
+               for( k = 0; k < nout; k++ ){
+                  col = outperm[ k ];
+                  if( col >= 0 && col < nin ) {
+                     *(out_el++) = *( matrix + nin*row + col );
+                  } else {
+                     *(out_el++) = 0.0;
+                  }
+               }
+
+/* If this output is asigned a constant value, use a "diagonal" vector for
+   row i of the new MatrixMap (i.e. all zeros except for a 1.0 in column
+   i ). */
+            } else {
+               out_el = out_mat + nout*i;
+               for( k = 0; k < nout; k++ ) {
+                  if( k != i ) {
+                     *(out_el++) = 0.0;
+                  } else {
+                     *(out_el++) = 1.0;
+                  }
+               }
+            }
+         }
+
+/* Create the new MatrixMap. */
+         mmnew = astMatrixMap( nout, nout, 0, out_mat, "", status );
+
+/* Any PermMap inputs which are assigned a constant value need to be
+   changed now, since they will no longer be scaled by the inverse
+   MatrixMap. CanSwap ensures that the inverse MatrixMap produces a
+   simple scaling for constant axes, so we change the PermMap constant
+   to be the constant AFTER scaling by the inverse MatrixMap.
+
+   Consider each input axis of the PermMap. */
+         for( i = 0; i < nin; i++ ) {
+
+/* If this input is assigned a constant value... */
+            if( inperm[ i ] < 0 ) {
+
+/* Divide the supplied constant value by the corresponding diagonal term
+   in the supplied MatrixMap. */
+               c = consts[ -inperm[ i ] - 1 ];
+               if( c != AST__BAD ) {
+                  matel = matrix[ i*( nin + 1 ) ];
+                  if( matel != 0.0 && matel != AST__BAD ) {
+                     consts[ -inperm[ i ] - 1 ] /= matel;
+                  } else {
+                     consts[ -inperm[ i ] - 1 ] = AST__BAD;
+                  }
+               }
+            }
+         }
+
+/* Now deal with cases where the PermMap is applied first. */
+      } else {
+
+/* Consider each input axis of the PermMap. */
+         for( i = 0; i < nin; i++ ) {
+
+/* If this input is connected to one of the output axes... */
+            row = inperm[ i ];
+            if( row >= 0 && row < nout ) {
+
+/* Permute the row of the supplied matrix which feeds the corresponding
+   PermMap output axis (i.e. axis inperm[k] ) using the inverse PermMap.
+   Store zeros for any input axes which are assigned constants. This forms
+   row i of the new MatrixMap. */
+               out_el = out_mat + nin*i;
+               for( k = 0; k < nin; k++ ){
+                  col = inperm[ k ];
+                  if( col >= 0 && col < nout ) {
+                     *(out_el++) = *( matrix + nout*row + col );
+                  } else {
+                     *(out_el++) = 0.0;
+                  }
+               }
+
+/* If this input is asigned a constant value, use a "diagonal" vector for
+   row i of the new MatrixMap (i.e. all zeros except for a 1.0 in column
+   i ). */
+            } else {
+               out_el = out_mat + nin*i;
+               for( k = 0; k < nin; k++ ) {
+                  if( k != i ) {
+                     *(out_el++) = 0.0;
+                  } else {
+                     *(out_el++) = 1.0;
+                  }
+               }
+            }
+         }
+
+/* Create the new MatrixMap. */
+         mmnew = astMatrixMap( nin, nin, 0, out_mat, "", status );
+
+/* Any PermMap outputs which are assigned a constant value need to be
+   changed now, since they will no longer be scaled by the forward
+   MatrixMap. CanSwap ensures that the forward MatrixMap produces a
+   simple scaling for constant axes, so we change the PermMap constant
+   to be the constant AFTER scaling by the forward MatrixMap.
+
+   Consider each output axis of the PermMap. */
+         for( i = 0; i < nout; i++ ) {
+
+/* If this output is assigned a constant value... */
+            if( outperm[ i ] < 0 ) {
+
+/* Multiple the supplied constant value by the corresponding diagonal term in
+   the supplied MatrixMap. */
+               c = consts[ -outperm[ i ] - 1 ];
+               if( c != AST__BAD ) {
+                  matel = matrix[ i*( nout + 1 ) ];
+                  if( matel != AST__BAD ) {
+                     consts[ -outperm[ i ] - 1 ] *= matel;
+                  } else {
+                     consts[ -outperm[ i ] - 1 ] = AST__BAD;
+                  }
+               }
+            }
+         }
+      }
+
+/* Create a new PermMap (since the constants may have changed). */
+      pmnew = astPermMap( nin, inperm, nout, outperm, consts, "", status );
+
+/* Free the axis permutation and constants arrays. */
+      outperm = (int *) astFree( (void *) outperm );
+      inperm = (int *) astFree( (void *) inperm );
+      consts = (double *) astFree( (void *) consts );
+   }
+
+/* Free the memory used to hold the new matrix elements. */
+   out_mat = (double *) astFree( (void *) out_mat );
+
+/* Ensure the supplied MatrixMap is stored back in compressed form. */
+   CompressMatrix( mm, status );
+
+/* Re-instate the original value of the Invert attribute of the supplied
+   PermMap. */
+   astSetInvert( pm, old_pinv );
+
+   if( astOK ) {
+
+/* Annul the supplied PermMap. */
+      (void) astAnnul( pm );
+
+/* Simplify the returned Mappings. */
+      spmnew = astSimplify( pmnew );
+      pmnew = astAnnul( pmnew );
+
+      smmnew = astSimplify( mmnew );
+      mmnew = astAnnul( mmnew );
+
+/* Store a pointer to the new PermMap in place of the supplied MatrixMap. This
+   PermMap should be used in its forward direction. */
+      maps[ imm ] = (AstMapping *) spmnew;
+      inverts[ imm ] = astGetInvert( spmnew );
+
+/* Annul the supplied matrixMap. */
+      (void) astAnnul( mm );
+
+/* Store a pointer to the new MatrixMap. This MatrixMap should be used in
+   its forward direction. */
+      maps[ 1 - imm ] = (AstMapping *) smmnew;
+      inverts[ 1 - imm ] = astGetInvert( smmnew );
+   }
+
+/* Return. */
+   return;
+}
+
+static void MatWin( AstMapping **maps, int *inverts, int imm, int *status ){
+/*
+*  Name:
+*     MatWin
+
+*  Purpose:
+*     Swap a WinMap and a MatrixMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     void MatWin( AstMapping **maps, int *inverts, int imm, int *status )
+
+*  Class Membership:
+*     WinMap member function
+
+*  Description:
+*     A list of two Mappings is supplied containing a WinMap and a
+*     MatrixMap. These Mappings are annulled, and replaced with
+*     another pair of Mappings consisting of a WinMap and a MatrixMap
+*     in the opposite order. These Mappings are chosen so that their
+*     combined effect is the same as the original pair of Mappings.
+*     The scale factors in the returned WinMap are always unity (i.e.
+*     the differences in scaling get absorbed into the returned
+*     MatrixMap).
+
+*  Parameters:
+*     maps
+*        A pointer to an array of two Mapping pointers.
+*     inverts
+*        A pointer to an array of two invert flags.
+*     imm
+*        The index within "maps" of the MatrixMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMatrixMap *m1;             /* Pointer to Diagonal scale factor MatrixMap */
+   AstMatrixMap *m2;             /* Pointer to returned MatrixMap */
+   AstMatrixMap *sm2;            /* Pointer to simplified returned MatrixMap */
+   AstMatrixMap *mm;             /* Pointer to the supplied MatrixMap */
+   AstPointSet *pset1;           /* Shift terms from supplied WinMap */
+   AstPointSet *pset2;           /* Shift terms for returned WinMap */
+   AstWinMap *w1;                /* Pointer to the returned WinMap */
+   AstWinMap *sw1;               /* Pointer to the simplified returned WinMap */
+   AstWinMap *wm;                /* Pointer to the supplied WinMap */
+   double **ptr1;                /* Pointer to pset1 data */
+   double **ptr2;                /* Pointer to pset2 data */
+   double *a;                    /* Array of shift terms from supplied WinMap */
+   double *aa;                   /* Pointer to next shift term */
+   double *b;                    /* Array of scale terms from supplied WinMap */
+   double *bb;                   /* Pointer to next scale term */
+   int i;                        /* Axis count */
+   int nin;                      /* No. of axes in supplied WinMap */
+   int nout;                     /* No. of axes in returned WinMap */
+   int old_minv;                 /* Invert value for the supplied MatrixMap */
+   int old_winv;                 /* Invert value for the supplied WinMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store pointers to the supplied WinMap and the MatrixMap. */
+   wm = (AstWinMap *) maps[ 1 - imm ];
+   mm = (AstMatrixMap *) maps[ imm ];
+
+/* Temporarily set the Invert attribute of the supplied Mappings to the
+   supplied values. */
+   old_winv = astGetInvert( wm );
+   astSetInvert( wm, inverts[ 1 - imm ] );
+
+   old_minv = astGetInvert( mm );
+   astSetInvert( mm, inverts[ imm ] );
+
+/* Get copies of the shift and scale terms used by the WinMap. This
+   also returns the number of axes in the WinMap. */
+   nin = astWinTerms( wm, &a, &b );
+
+/* Create a diagonal MatrixMap holding the scale factors from the
+   supplied WinMap. */
+   m1 = astMatrixMap( nin, nin, 1, b, "", status );
+
+/* Create a PointSet holding a single position given by the shift terms
+   in the supplied WinMap. */
+   pset1 = astPointSet( 1, nin, "", status );
+   ptr1 = astGetPoints( pset1 );
+   if( astOK ){
+      aa = a;
+      for( i = 0; i < nin; i++ ) ptr1[ i ][ 0 ] = *(aa++);
+   }
+
+/* First deal with cases when the WinMap is applied first, followed by
+   the MatrixMap. */
+   if( imm == 1 ){
+
+/* Multiply the diagonal matrix holding the WinMap scale factors by the
+   supplied matrix. The resulting MatrixMap is the one to return in the
+   map list. */
+      m2 = astMtrMult( m1, mm );
+
+/* Transform the position given by the shift terms from the supplied
+   WinMap using the supplied MatrixMap to get the shift terms for
+   the returned WinMap. */
+      pset2 = astTransform( mm, pset1, 1, NULL );
+
+/* Now deal with cases when the MatrixMap is applied first, followed by
+   the WinMap. */
+   } else {
+
+/* Multiply the supplied MatrixMap by the diagonal matrix holding scale
+   factors from the supplied WinMap. The resulting MatrixMap is the one to
+   return in the map list. */
+      m2 = astMtrMult( mm, m1 );
+
+/* Transform the position given by the shift terms from the supplied
+   WinMap using the inverse of the returned MatrixMap to get the shift
+   terms for the returned WinMap. */
+      pset2 = astTransform( m2, pset1, 0, NULL );
+
+   }
+
+/* Re-instate the original value of the Invert attributes of the supplied
+   Mappings. */
+   astSetInvert( wm, old_winv );
+   astSetInvert( mm, old_minv );
+
+/* Get pointers to the shift terms for the returned WinMap. */
+   ptr2 = astGetPoints( pset2 );
+
+/* Create the returned WinMap, initially with undefined corners. The number of
+   axes in the WinMap must equal the number of shift terms. */
+   nout = astGetNcoord( pset2 );
+   w1 = astWinMap( nout, NULL, NULL, NULL, NULL, "", status );
+
+/* If succesful, store the scale and shift terms in the WinMap. The scale
+   terms are always unity. */
+   if( astOK ){
+      bb = w1->b;
+      aa = w1->a;
+      for( i = 0; i < nout; i++ ) {
+         *(bb++) = 1.0;
+         *(aa++) = ptr2[ i ][ 0 ];
+      }
+
+/* Replace the supplied Mappings and invert flags with the ones found
+   above. Remember that the order of the Mappings is now swapped */
+      (void) astAnnul( maps[ 0 ] );
+      (void) astAnnul( maps[ 1 ] );
+
+      sw1 = astSimplify( w1 );
+      w1 = astAnnul( w1 );
+
+      maps[ imm ] = (AstMapping *) sw1;
+      inverts[ imm  ] = astGetInvert( sw1 );
+
+      sm2 = astSimplify( m2 );
+      m2 = astAnnul( m2 );
+
+      maps[ 1 - imm ] = (AstMapping *) sm2;
+      inverts[ 1 - imm  ] = astGetInvert( sm2 );
+
+   }
+
+/* Annul the MatrixMap and PointSet holding the scale and shift terms from the
+   supplied WinMap. */
+   m1 = astAnnul( m1 );
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* Free the copies of the scale and shift terms from the supplied WinMap. */
+   b = (double *) astFree( (void *) b );
+   a = (double *) astFree( (void *) a );
+
+/* Return. */
+   return;
+}
+
+static AstMatrixMap *MatZoom( AstMatrixMap *mm, AstZoomMap *zm, int minv,
+                              int zinv, int *status ){
+/*
+*  Name:
+*     MatZoom
+
+*  Purpose:
+*     Create a MatrixMap by merging a MatrixMap and a ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     AstMatrixMap *MatZoom( AstMatrixMap *mm, AstZoomMap *zm, int minv,
+*                            int zinv, int *status )
+
+*  Class Membership:
+*     MatrixMap member function
+
+*  Description:
+*     This function creates a new MatrixMap which performs a mapping
+*     equivalent to applying the two supplied Mappings in series in the
+*     directions specified by the "invert" flags (the Invert attributes of
+*     the supplied MatrixMaps are ignored).
+
+*  Parameters:
+*     mm
+*        A pointer to the MatrixMap.
+*     zm
+*        A pointer to the ZoomMap.
+*     minv
+*        The invert flag to use with mm. A value of zero causes the forward
+*        mapping to be used, and a non-zero value causes the inverse
+*        mapping to be used.
+*     zinv
+*        The invert flag to use with zm.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the new MatrixMap.
+
+*  Notes:
+*     -  The forward direction of the returned MatrixMap is equivalent to the
+*     combined effect of the two supplied Mappings, operating in the
+*     directions specified by "zinv" and "minv".
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *mm2;            /* Pointer to intermediate MatrixMap */
+   AstMatrixMap *result;         /* Pointer to output MatrixMap */
+   double *matrix;               /* Pointer to diagonal matrix elements array */
+   double zfac;                  /* Zoom factor */
+   int i;                        /* Axis index */
+   int nrow;                     /* No. of rows in the MatrixMap */
+   int old_minv;                 /* Original setting of MatrixMap Invert attribute */
+   int old_zinv;                 /* Original setting of ZoomMap Invert attribute */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the returned pointer. */
+   result = NULL;
+
+/* Temporarily set the Invert attributes of both Mappings to the supplied
+   values. */
+   old_minv = astGetInvert( mm );
+   astSetInvert( mm, minv );
+
+   old_zinv = astGetInvert( zm );
+   astSetInvert( zm, zinv );
+
+/* Get the number of rows in the MatrixMap (i.e. the number of output
+   axes). */
+   nrow = astGetNout( mm );
+
+/* Get the zoom factor implemented by the ZoomMap. Invert it if necessary
+   since astGetZoom does not take account of the Invert setting.  */
+   zfac = astGetZoom( zm );
+   if( zinv ) zfac = 1.0 / zfac;
+
+/* Create a diagonal matrix map in which each diagonal element is equal
+   to the zoom factor. */
+   matrix = (double *) astMalloc( sizeof( double )*(size_t) nrow );
+   if( astOK ) {
+      for( i = 0; i < nrow; i++ ) matrix[ i ] = zfac;
+   }
+   mm2 = astMatrixMap( nrow, nrow, 1, matrix, "", status );
+   matrix = (double *) astFree( (void *) matrix );
+
+/* Create a new MatrixMap holding the product of the supplied MatrixMap
+   and the diagonal MatrixMap just created. */
+   result = astMtrMult( mm, mm2 );
+   mm2 = astAnnul( mm2 );
+
+/* Re-instate the original settings of the Invert attribute for the
+   supplied Mappings. */
+   astSetInvert( mm, old_minv );
+   astSetInvert( zm, old_zinv );
+
+/* If an error has occurred, annull the returned MatrixMap. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output MatrixMap. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+static AstMatrixMap *MtrMult( AstMatrixMap *this, AstMatrixMap *a, int *status ){
+/*
+*+
+*  Name:
+*     astMtrMult
+
+*  Purpose:
+*     Multiply a MatrixMap by another MatrixMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     AstMatrixMap *MtrMult( astMatrixMap *this, astMatrixMap *a )
+
+*  Class Membership:
+*     MatrixMap method
+
+*  Description:
+*     This function multiples the matrices given by "this" and "a", returning
+*     a pointer to a new MatrixMap holding the product "a x this".
+*
+*     The number of columns in the "a" matrix must match the number of
+*     rows in the "this" matrix. The number of rows in the returned
+*     MatrixMap is equal to the number of rows in "a", and the number of
+*     columns is the same as the number of rows in "this".
+
+*  Parameters:
+*     this
+*        Pointer to the first MatrixMap.
+*     a
+*        Pointer to a second MatrixMap.
+
+*  Returned Value:
+*     A pointer to the product MatrixMap.
+
+*  Notes:
+*     -  An error is reported if the two MatrixMaps have incompatible
+*     shapes, or if either MatrixMap does not have a defined forward
+*     transformation.
+*     - A null Object pointer will also be returned if this function
+*     is invoked with the AST error status set, or if it should fail
+*     for any reason.
+*-
+*/
+
+/* Local variables. */
+   astDECLARE_GLOBALS        /* Pointer to thread-specific global data */
+   AstMatrixMap *new;        /* New MatrixMap holding the product matrix */
+   double *a_matrix;         /* Pointer to the forward "a" matrix */
+   double *a_row;            /* Pointer to start of current row in "a" */
+   double a_val;             /* Current element value from "a" */
+   double factor;            /* Diagonal matrix term */
+   double *new_matrix;       /* Pointer to the new forward "this" matrix */
+   double *new_val;          /* Pointer to current output element value */
+   double sum;               /* Dot product value */
+   double *this_col;         /* Pointer to start of current column in "this" */
+   double *this_matrix;      /* Pointer to the forward "this" matrix */
+   double this_val;          /* Current element value from "this" */
+   int col;                  /* Current output column number */
+   int i;                    /* Loop count */
+   int minrow;               /* Min. number of rows in "a" or "this" */
+   int ncol_a;               /* No. of columns in the "a" MatrixMap */
+   int ncol_this;            /* No. of columns in the "this" MatrixMap */
+   int nrow_a;               /* No. of rows in the "a" MatrixMap */
+   int nrow_this;            /* No. of rows in the "this" MatrixMap */
+   int row;                  /* Current output row number */
+
+/* Return a NULL pointer if an error has already occurred. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise */
+   new = NULL;
+
+/* Report an error if eitherof the MatrixMaps doe snot have a defined
+   forward transformation.*/
+   if( !astGetTranForward( this ) ){
+      astError( AST__MTRML, "astMtrMult(%s): Cannot find the product of 2 "
+                "MatrixMaps- the first MatrixMap has no forward transformation.", status,
+                astClass(this) );
+      return NULL;
+   }
+
+   if( !astGetTranInverse( this ) ){
+      astError( AST__MTRML, "astMtrMult(%s): Cannot find the product of 2 "
+                "MatrixMaps- the second MatrixMap has no forward transformation.", status,
+                astClass(this) );
+      return NULL;
+   }
+
+/* Report an error if the shapes of the two matrices are incompatible. */
+   nrow_a = astGetNout( a );
+   ncol_a = astGetNin( a );
+   nrow_this = astGetNout( this );
+   ncol_this = astGetNin( this );
+
+   if( ncol_a != nrow_this && astOK ){
+      astError( AST__MTRML, "astMtrMult(%s): Number of rows in the first "
+                "MatrixMap (%d) does not equal number of columns in the "
+                "second MatrixMap (%d).", status, astClass(this), nrow_this, ncol_a );
+      return NULL;
+   }
+
+/* Store the minimum number of rows in either matrix for later use. */
+   if( nrow_a < nrow_this ){
+      minrow = nrow_a;
+   } else {
+      minrow = nrow_this;
+   }
+
+/* Ensure that "this" is stored in FULL form (i.e. with all elements
+   stored explicitly, even if the matrix is a unit or diagonal matrix). */
+   ExpandMatrix( this, status );
+
+/* Store pointers to the current forward matrices (taking into
+   account the current states of the Mapping inversion flags ). */
+   this_matrix = astGetInvert( this ) ? this->i_matrix : this->f_matrix;
+   a_matrix = astGetInvert( a ) ? a->i_matrix : a->f_matrix;
+
+/* Get memory to hold the product matrix in full form. */
+   new_matrix = (double *) astMalloc( sizeof( double )*
+                                      (size_t)( nrow_a*ncol_this ) );
+   if( astOK ){
+
+/* First deal with cases where the "a" MatrixMap represents a unit
+   matrix. */
+      if( a->form == UNIT ){
+
+/* Copy the required number of rows from "this" to "new". */
+         (void) memcpy( (void *) new_matrix, (const void *) this_matrix,
+                         sizeof(double)*(size_t)( minrow*ncol_this ) );
+
+/* If there are insufficient rows in "this", append some zero-filled rows. */
+         if( minrow < nrow_a ){
+            for( i = minrow*ncol_this; i < nrow_a*ncol_this; i++ ){
+               new_matrix[ i ] = 0.0;
+            }
+         }
+
+/* Now deal with cases where the "a" MatrixMap represents a diagonal
+   matrix. */
+      } else if( a->form == DIAGONAL ){
+
+/* Scale the required number of rows from "this" storing them in "new",
+   and checking for bad values. */
+         i = 0;
+
+         for( row = 0; row < minrow; row++ ){
+            factor = a_matrix[ row ];
+
+            if( factor != AST__BAD ){
+
+               for( col = 0; col < ncol_this; col++ ){
+                  this_val = this_matrix[ i ];
+                  if( this_val != AST__BAD ){
+                     new_matrix[ i ] = this_val*factor;
+                  } else {
+                     new_matrix[ i ] = AST__BAD;
+                  }
+                  i++;
+               }
+
+            } else {
+
+               for( col = 0; col < ncol_this; col++ ){
+                  new_matrix[ i++ ] = AST__BAD;
+               }
+
+            }
+         }
+
+/* If there are insufficient rows in "this", append some zero-filled rows. */
+         if( minrow < nrow_a ){
+            for( i = minrow*ncol_this; i < nrow_a*ncol_this; i++ ){
+               new_matrix[ i ] = 0.0;
+            }
+         }
+
+
+/* Now deal with cases where the "a" MatrixMap represents a full, non-diagonal
+   matrix. */
+      } else {
+
+/* Initialise a pointer to the next element in the product matrix. */
+         new_val = new_matrix;
+
+/* Get a pointer to the start of each row of the "a" matrix. */
+         for( row = 0; row < nrow_a; row++ ){
+            a_row = a_matrix + ncol_a*row;
+
+/* Get a pointer to the start of each column of the "this" matrix. */
+            for( col = 0; col < ncol_this; col++ ){
+               this_col = this_matrix + col;
+
+/* Form the dot product of the current row from "a", and the current
+   column from "this", checking for bad values. */
+               sum = 0.0;
+               for( i = 0; i < ncol_a; i++ ){
+                  a_val = a_row[ i ];
+                  this_val = this_col[ i*ncol_this ];
+                  if( a_val != AST__BAD && this_val != AST__BAD ){
+                     sum += a_val*this_val;
+                  } else {
+                     sum = AST__BAD;
+                     break;
+                  }
+               }
+
+/* Store the output matrix element value. */
+               *(new_val++) = sum;
+
+            }
+         }
+      }
+
+/* Create the new MatrixMap. */
+      new = astInitMatrixMap( NULL, sizeof( AstMatrixMap ), !class_init,
+                              &class_vtab, "MatrixMap", ncol_this, nrow_a,
+                              FULL, new_matrix );
+
+/* If possible, compress the new MatrixMap by removing off-diagonal zero
+   elements. */
+      CompressMatrix( new, status );
+
+/* Re-compress the original "this" MatrixMap. */
+      CompressMatrix( this, status );
+
+   }
+
+/* Free the memory used to hold the product matrix in full form. */
+   new_matrix = (double *) astFree( (void *) new_matrix );
+
+   return new;
+
+}
+
+static AstMatrixMap *MtrRot( AstMatrixMap *this, double theta,
+                             const double axis[], int *status ){
+/*
+*+
+*  Name:
+*     astMtrRot
+
+*  Purpose:
+*     Multiply a MatrixMap by a rotation matrix.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     AstMatrixMap *astMtrRot( astMatrixMap *this, double theta,
+*                              const double axis[] )
+
+*  Class Membership:
+*     MatrixMap method.
+
+*  Description:
+*     This function creates a new MatrixMap which is a copy of "this",
+*     rotated by a specified angle. It can only be used on MatrixMaps which
+*     have either 2 or 3 output coordinates. In the 3-D case, the rotation
+*     is about an arbitrary axis passing through the origin.
+
+*  Parameters:
+*     this
+*        Pointer to the MatrixMap.
+*     theta
+*        The angle by which to rotate the matrix, in radians. If the matrix
+*        is applied to a 2-D vector position, the resulting vector is
+*        rotated clockwise about the origin (i.e. from the positive direction
+*        of the second axis to the positive direction of the first axis). If
+*        the vector positions are three dimensional, the rotation is clockwise
+*        when looking along the vector given by "axis". Note, "theta" measures
+f        when looking along the vector given by AXIS. Note, THETA measures
+*        the movemement of the vectors relative to a fixed reference frame.
+*        Alternatively, the reference frame can be thought of as rotating by
+*        (-theta) relative to the fixed vectors.
+*     axis
+*        A 3-D vector specifying the axis of rotation. This parameter is
+*        ignored if the output from MatrixMap is 2-dimensional.
+
+*  Returned Value:
+*     A pointer to the rotated MatrixMap.
+
+*  Notes:
+*     - A null Object pointer will also be returned if this function
+*     is invoked with the AST error status set, or if it should fail
+*     for any reason.
+*-
+*/
+
+/* Local variables. */
+   AstMatrixMap *new;        /* New MatrixMap holding the rotated matrix */
+   double as,a,b,c,d,e,f,g;  /* Intermediate quantities */
+   double axlen;             /* Length of axis vector */
+   double axlen2;            /* Squared length of axis vector */
+   double costh;             /* Cos(rotation angle) */
+   double sinth;             /* Sin(rotation angle) */
+   double rotmat[9];         /* Rotation matrix */
+   double work[3];           /* Work space for matrix multiplication */
+   int ncol;                 /* No. of columns in the MatrixMap */
+   int nrow;                 /* No. of rows in the MatrixMap */
+
+/* Return a NULL pointer if an error has already occurred. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the returned MarixMap to be a copy of the supplied MatrixMap. */
+   new = astCopy( this );
+
+/* Save the cos and sin of the rotation angle for future use. */
+   costh = cos( theta );
+   sinth = sin( theta );
+
+/* Return without changing the MatrixMap if the rotation angle is a
+   multiple of 360 degrees. */
+   if ( costh == 1.0 ) return new;
+
+/* Get the dimensions of the MatrixMap. */
+   nrow = astGetNout( new );
+   ncol = astGetNin( new );
+
+/* First do rotation of a plane about the origin. */
+   if( nrow == 2 ){
+
+/* Ensure that the MatrixMap is stored in full form rather than
+   compressed form. */
+      ExpandMatrix( new, status );
+
+/* Form the 2x2 forward rotation matrix. Theta is the clockwise angle
+   of rotation. */
+      rotmat[0] = costh;
+      rotmat[1] = sinth;
+      rotmat[2] = -sinth;
+      rotmat[3] = costh;
+
+/*  Post-multiply the current forward matrix (depending on whether or not
+    the MatrixMap has been inverted) by the forward rotation matrix. */
+      if( !astGetInvert( new ) ){
+         SMtrMult( 1, 2, ncol, rotmat, new->f_matrix, work, status );
+      } else {
+         SMtrMult( 1, 2, ncol, rotmat, new->i_matrix, work, status );
+      }
+
+/* Now form the 2x2 inverse rotation matrix (the diagonal elements
+   don't change). */
+      rotmat[1] = -sinth;
+      rotmat[2] = sinth;
+
+/*  Pre-multiply the current inverse matrix (depending on whether or
+    not the MatrixMap has been inverted) by the inverse rotation matrix. */
+      if( !astGetInvert( new ) ){
+         SMtrMult( 0, ncol, 2, rotmat, new->i_matrix, work, status );
+      } else {
+         SMtrMult( 0, ncol, 2, rotmat, new->f_matrix, work, status );
+      }
+
+/*  See if the matrix can be stored as a UNIT or DIAGONAL matrix. */
+      CompressMatrix( new, status );
+
+/* Now do rotation of a volume about an axis passing through the origin. */
+   } else if( nrow == 3 ){
+
+/* Find the length of the axis vector. Report an error if it has zero
+   length or has not been supplied. */
+      if( axis ) {
+         axlen2 = axis[0]*axis[0] + axis[1]*axis[1] + axis[2]*axis[2];
+      } else {
+         axlen2 = 0.0;
+      }
+      if( axlen2 <= 0.0 ) {
+         astError( AST__MTRAX, "astMtrRot(%s): NULL or zero length "
+                   "axis vector supplied.", status, astClass(new) );
+      }
+      axlen = sqrt( axlen2 );
+
+/* Ensure that the MatrixMap is stored in full form rather than
+   compressed form. */
+      ExpandMatrix( new, status );
+
+/* Form commonly used terms in the rotation matrix. */
+      as = sinth/axlen;
+      a = (1.0 - costh)/axlen2;
+      b = a*axis[0]*axis[1];
+      c = as*axis[2];
+      d = a*axis[0]*axis[2];
+      e = as*axis[1];
+      f = a*axis[1]*axis[2];
+      g = as*axis[0];
+
+/* Form the 3x3 forward rotation matrix. Theta is the clockwise angle
+   of rotation looking in the direction of the axis vector. */
+      rotmat[0] = a*axis[0]*axis[0] + costh;
+      rotmat[1] = b - c;
+      rotmat[2] = d + e;
+      rotmat[3] = b + c;
+      rotmat[4] = a*axis[1]*axis[1] + costh;
+      rotmat[5] = f - g;
+      rotmat[6] = d - e;
+      rotmat[7] = f + g;
+      rotmat[8] = a*axis[2]*axis[2] + costh;
+
+/*  Post-multiply the current forward matrix (depending on whether or not
+    the MatrixMap has been inverted) by the forward rotation matrix. */
+      if( !astGetInvert( new ) ){
+         SMtrMult( 1, 3, ncol, rotmat, new->f_matrix, work, status );
+      } else {
+         SMtrMult( 1, 3, ncol, rotmat, new->i_matrix, work, status );
+      }
+
+/* Now form the 3x3 inverse rotation matrix (the diagonal elements
+   don't change). */
+      rotmat[1] = b + c;
+      rotmat[2] = d - e;
+      rotmat[3] = b - c;
+      rotmat[5] = f + g;
+      rotmat[6] = d + e;
+      rotmat[7] = f - g;
+
+/* Pre-multiply the current inverse matrix (depending on whether or
+   not the MatrixMap has been inverted) by the inverse rotation matrix. */
+      if( !astGetInvert( new ) ){
+         SMtrMult( 0, ncol, 3, rotmat, new->i_matrix, work, status );
+      } else {
+         SMtrMult( 0, ncol, 3, rotmat, new->f_matrix, work, status );
+      }
+
+/*  See if the matrix can be stored as a UNIT or DIAGONAL matrix. */
+      CompressMatrix( new, status );
+
+/* Report an error if the matrix is not suitable for rotation. */
+   } else {
+      astError( AST__MTR23, "astMtrRot(%s): Cannot rotate a %dx%d"
+                " MatrixMap.", status, astClass(new), nrow, ncol );
+   }
+
+/* Delete the new MatrixMap if an error has occurred. */
+   if( !astOK ) new = astDelete( new );
+
+   return new;
+
+}
+
+static void PermGet( AstPermMap *map, int **outperm, int **inperm,
+                     double **consts, int *status ){
+/*
+*  Name:
+*     PermGet
+
+*  Purpose:
+*     Get the axis permutation and constants array for a PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     void PermGet( AstPermMap *map, int **outperm, int **inperm,
+*                   double **const, int *status )
+
+*  Class Membership:
+*     MatrixMap member function
+
+*  Description:
+*     This function returns axis permutation and constants arrays which can
+*     be used to create a PermMap which is equivalent to the supplied PermMap.
+
+*  Parameters:
+*     map
+*        The PermMap.
+*     outperm
+*        An address at which to return a popinter to an array of ints
+*        holding the output axis permutation array. The array should be
+*        released using astFree when no longer needed.
+*     inperm
+*        An address at which to return a popinter to an array of ints
+*        holding the input axis permutation array. The array should be
+*        released using astFree when no longer needed.
+*     consts
+*        An address at which to return a popinter to an array of doubles
+*        holding the constants array. The array should be released using
+*        astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  NULL pointers are returned if an error has already occurred, or if
+*     this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;       /* PointSet holding input positions for PermMap */
+   AstPointSet *pset2;       /* PointSet holding output positions for PermMap */
+   double **ptr1;            /* Pointer to pset1 data */
+   double **ptr2;            /* Pointer to pset2 data */
+   double *cnst;             /* Pointer to constants array */
+   double cn;                /* Potential new constant value */
+   double ip;                /* Potential output axis index */
+   double op;                /* Potential input axis index */
+   int *inprm;               /* Pointer to input axis permutation array */
+   int *outprm;              /* Pointer to output axis permutation array */
+   int i;                    /* Axis count */
+   int nc;                   /* Number of constants stored so far */
+   int nin;                  /* No. of input coordinates for the PermMap */
+   int nout;                 /* No. of output coordinates for the PermMap */
+
+/* Initialise. */
+   if( outperm ) *outperm = NULL;
+   if( inperm ) *inperm = NULL;
+   if( consts ) *consts = NULL;
+
+/* Check the global error status and the supplied pointers. */
+   if ( !astOK || !outperm || !inperm || !consts ) return;
+
+/* Get the number of input and output axes for the supplied PermMap. */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+
+/* Allocate the memory for the returned arrays. */
+   outprm = (int *) astMalloc( sizeof( int )* (size_t) nout );
+   inprm = (int *) astMalloc( sizeof( int )* (size_t) nin );
+   cnst = (double *) astMalloc( sizeof( double )* (size_t) ( nout + nin ) );
+
+/* Returned the pointers to these arrays.*/
+   *outperm = outprm;
+   *inperm = inprm;
+   *consts = cnst;
+
+/* Create two PointSets, each holding two points, which can be used for
+   input and output positions with the PermMap. */
+   pset1 = astPointSet( 2, nin, "", status );
+   pset2 = astPointSet( 2, nout, "", status );
+
+/* Set up the two input positions to be [0,1,2...] and [-1,-1,-1,...]. The
+   first position is used to enumerate the axes, and the second is used to
+   check for constant axis values. */
+   ptr1 = astGetPoints( pset1 );
+   if( astOK ){
+      for( i = 0; i < nin; i++ ){
+         ptr1[ i ][ 0 ] = ( double ) i;
+         ptr1[ i ][ 1 ] = -1.0;
+      }
+   }
+
+/* Use the PermMap to transform these positions in the forward direction. */
+   (void) astTransform( map, pset1, 1, pset2 );
+
+/* No constant axis valeus found yet. */
+   nc = 0;
+
+/* Look at the mapped positions to determine the output axis permutation
+   array. */
+   ptr2 = astGetPoints( pset2 );
+   if( astOK ){
+
+/* Do each output axis. */
+      for( i = 0; i < nout; i++ ){
+
+/* If the output axis value is copied from an input axis value, the index
+   of the appropriate input axis will be in the mapped first position. */
+         op = ptr2[ i ][ 0 ];
+
+/* If the output axis value is assigned a constant value, the result of
+   mapping the two different input axis values will be the same. */
+         cn = ptr2[ i ][ 1 ];
+         if( op == cn ) {
+
+/* We have found another constant. Store it in the constants array, and
+   store the index of the constant in the output axis permutation array. */
+            cnst[ nc ] = cn;
+            outprm[ i ] = -( nc + 1 );
+            nc++;
+
+/* If the output axis values are different, then the output axis value
+   must be copied from the input axis value. */
+         } else {
+            outprm[ i ] = (int) ( op + 0.5 );
+         }
+      }
+   }
+
+/* Now do the same thing to determine the input permutation array. */
+   if( astOK ){
+      for( i = 0; i < nout; i++ ){
+         ptr2[ i ][ 0 ] = ( double ) i;
+         ptr2[ i ][ 1 ] = -1.0;
+      }
+   }
+
+   (void) astTransform( map, pset2, 0, pset1 );
+
+   if( astOK ){
+
+      for( i = 0; i < nin; i++ ){
+
+         ip = ptr1[ i ][ 0 ];
+         cn = ptr1[ i ][ 1 ];
+         if( ip == cn ) {
+
+            cnst[ nc ] = cn;
+            inprm[ i ] = -( nc + 1 );
+            nc++;
+
+         } else {
+            inprm[ i ] = (int) ( ip + 0.5 );
+         }
+      }
+   }
+
+/* Annul the PointSets. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* If an error has occurred, attempt to free the returned arrays. */
+   if( !astOK ) {
+      *outperm = (int *) astFree( (void *) *outperm );
+      *inperm = (int *) astFree( (void *) *inperm );
+      *consts = (double *) astFree( (void *) *consts );
+   }
+
+/* Return. */
+   return;
+}
+
+static int PermOK( AstMapping *pm, int *status ){
+/*
+*  Name:
+*     PermOK
+
+*  Purpose:
+*     Determine if a PermMap can be merged with a MatrixMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     int PermOK( AstMapping *pm, int *status )
+
+*  Class Membership:
+*     PermMap member function
+
+*  Description:
+*     This function returns a flag indicating if the supplied PermMap
+*     could be merged with a MatrixMap. For thios to be possible, the
+*     PermMap must have the same number of input and output axes, and the
+*     inverse and forward mappings must be consistent.
+
+*  Parameters:
+*     pm
+*        The PermMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     1 if the PermMap can be merged, 0 otherwise.
+
+*  Notes:
+*     -  A value of 0 is returned if an error has already occurred, or if
+*     this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;       /* PointSet holding input positions for PermMap */
+   AstPointSet *pset2;       /* PointSet holding output positions for PermMap */
+   double **ptr1;            /* Pointer to pset1 data */
+   int i;                    /* Loop count */
+   int nin;                  /* No. of input coordinates for the PermMap */
+   int nout;                 /* No. of output coordinates for the PermMap */
+   int ret;                  /* Returned flag */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise */
+   ret = 0;
+
+/* The PermMap must have the same number of input and output coordinates. */
+   nin = astGetNin( pm );
+   nout = astGetNout( pm );
+   if( nin == nout ){
+
+/* Create two PointSets, each holding two points, which can be used for
+   the input and output positions with the PermMap. */
+      pset1 = astPointSet( 2, nin, "", status );
+      pset2 = astPointSet( 2, nout, "", status );
+
+/* Set up the two input positions to be [1,2,3...] and [0,-1,-2,...] */
+      ptr1 = astGetPoints( pset1 );
+      if( astOK ){
+         for( i = 0; i < nin; i++ ){
+            ptr1[ i ][ 0 ] = ( double )( i + 1 );
+            ptr1[ i ][ 1 ] = ( double )( -i );
+         }
+
+      }
+
+/* Use the PermMap to transform these positions in the forward direction. */
+      (void) astTransform( pm, pset1, 1, pset2 );
+
+/* Now transform the results back again using the inverse PermMap. */
+      (void) astTransform( pm, pset2, 0, pset1 );
+
+/* See if the input positions have changed. If they have, then the PermMap
+   does not have a consistent pair of transformations. If they have not,
+   then the transformations must be consistent because we used two
+   different input positions and only one could come out unchanged by
+   chance. */
+      if( astOK ){
+         ret = 1;
+         for( i = 0; i < nin; i++ ){
+            if( ptr1[ i ][ 0 ] != ( double )( i + 1 ) ||
+                ptr1[ i ][ 1 ] != ( double )( -i ) ){
+               ret = 0;
+               break;
+            }
+         }
+      }
+
+/* Annul the PointSets. */
+      pset1 = astAnnul( pset1 );
+      pset2 = astAnnul( pset2 );
+   }
+
+/* Return the answer. */
+   return astOK ? ret : 0;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     MatrixMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*/
+
+/* Local Variables: */
+   AstMatrixMap *map;
+   double *matrix;
+   double result;
+
+/* Check inherited status */
+   if( !astOK ) return AST__BAD;
+
+/* Get a pointer to the MatrixMap structure. */
+   map = (AstMatrixMap *) this;
+
+/* Get a pointer to the array holding the required matrix elements, according
+   to whether the MatrixMap has been inverted. */
+   if( !astGetInvert( this ) ) {
+      matrix = map->f_matrix;
+   } else {
+      matrix = map->i_matrix;
+   }
+
+/* First deal with full MatrixMaps in which all matrix elements are stored. */
+   if( map->form == FULL ){
+      result = matrix[ ax1*astGetNin( this ) + ax2 ];
+
+/* For unit matrices, the rate is unity if the input and output axes are
+   equal, and zero otherwise. */
+   } else if( map->form == UNIT ){
+      result = (ax1 == ax2 ) ? 1.0 : 0.0;
+
+/* For diagonal matrices, the rate is zero for off diagonal elements and
+   the matrix array stored the on-diagonal rates. */
+   } else if( ax1 == ax2 ) {
+      result = matrix[ ax1 ];
+
+   } else {
+      result = 0.0;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void SMtrMult( int post, int m, int n, const double *mat1,
+                        double *mat2, double *work, int *status ){
+/*
+*  Name:
+*     SMtrMult
+
+*  Purpose:
+*     Multiply a square matrix and a non-square matrix.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     void SMtrMult( int post, int m, int n, const double *mat1,
+*                    double *mat2, double *work, int *status )
+
+*  Class Membership:
+*     MatrixMap member function.
+
+*  Description:
+*     The matrix pointed to by "mat2" is modified by multiplying it by
+*     the square matrix pointed to by "mat1". If "post" is 1, then:
+*
+*        mat2 -> mat1*mat2  (mat1 is mxm and mat2 is mxn)
+*
+*     If "post" is zero, then:
+*
+*        mat2 -> mat2*mat1  (mat1 is nxn and mat2 is mxn)
+*
+*     The restriction that "mat1" must be square is imposed so that the
+*     returned matrix will have the same shape as the supplied matrix (mat1).
+
+*  Parameters:
+*     post
+*        Specifies whether to post- or pre- multiply mat2 by mat1.
+*     m
+*        The number of rows in mat2.
+*     n
+*        The number of columns in mat2.
+*     mat1
+*        The multiplier matrix. It must be square of size m or n, depending
+*        on "post".
+*     mat2
+*        The multiplicand matrix.
+*     work
+*        Pointer to work space containing room for m doubles (if "post"
+*        is 1), or n doubles (if "post" is 0).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  No error is reported if "mat2" is supplied NULL. In this case
+*     it will also be returned NULL.
+*/
+
+/* Local Variables */
+   double dot;             /* Output matrix element value */
+   const double *mat1_col; /* Pointer to start of current column of mat1 */
+   const double *mat1_row; /* Pointer to start of current row of mat1 */
+   double *mat2_col;       /* Pointer to start of current column of mat2 */
+   double *mat2_row;       /* Pointer to start of current row of mat2 */
+   double cel;             /* Column element value */
+   double rel;             /* Row element value */
+   int i;                  /* Index of current output row */
+   int j;                  /* Index of current output column */
+   int k;                  /* Dot product index */
+
+/* Do nothing if mat2 is NULL */
+   if ( mat2 ){
+
+/* First deal with cases where the supplied matrix is post-multiplied
+   (i.e. the returned matrix is mat1*mat2). */
+      if( post ){
+
+/* Loop round each column of the output matrix, storing a pointer to
+   the start of the corresponding column of mat2. */
+         for( j=0; j<n; j++ ){
+            mat2_col = mat2 + j;
+
+/* Loop round each row of the output matrix, storing a pointer to
+   the start of the corresponding row of mat1. */
+            for( i=0; i<m; i++ ){
+               mat1_row = mat1 + i*m;
+
+/* Get the dot product of the corresponding row from mat1 and the
+   corresponding column from mat2 and store it in the work array. */
+               dot = 0.0;
+               for( k=0; k<m; k++ ) {
+                  rel = mat1_row[ k ];
+                  cel = mat2_col[ k*n ];
+                  if( rel != AST__BAD && cel != AST__BAD ){
+                     dot += rel*cel;
+                  } else {
+                     dot = AST__BAD;
+                     break;
+                  }
+               }
+               work[ i ] = dot;
+            }
+
+/* Copy the values stored in the work array to the current column of
+   the output matrix. */
+            for( i=0; i<m; i++ ) mat2_col[ i*n ] = work[ i ];
+         }
+
+/* Now deal with cases where the supplied matrix is pre-multiplied
+   (i.e. the returned matrix is mat2*mat1). */
+      } else {
+
+/* Loop round each row of the output matrix, storing a pointer to
+   the start of the corresponding row of mat2. */
+         for( i=0; i<m; i++ ){
+            mat2_row = mat2 + i*n;
+
+/* Loop round each column of the output matrix, storing a pointer to
+   the start of the corresponding column of mat1. */
+            for( j=0; j<n; j++ ){
+               mat1_col = mat1 + j;
+
+/* Get the dot product of the corresponding row from mat2 and the
+   corresponding column from mat1 and store it in the work array. */
+               dot = 0.0;
+               for( k=0; k<n; k++ ) {
+                  rel = mat2_row[ k ];
+                  cel = mat1_col[ k*n ];
+                  if( rel != AST__BAD && cel != AST__BAD ){
+                     dot += rel*cel;
+                  } else {
+                     dot = AST__BAD;
+                     break;
+                  }
+               }
+               work[ j ] = dot;
+            }
+
+/* Copy the values stored in the work array to the current row of
+   the output matrix. */
+            for( j=0; j<n; j++ ) mat2_row[ j ] = work[ j ];
+         }
+      }
+   }
+
+   return;
+
+}
+
+static int GetTranForward( AstMapping *this, int *status ) {
+/*
+*
+*  Name:
+*     GetTranForward
+
+*  Purpose:
+*     Determine if a MatrixMap defines a forward coordinate transformation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     int GetTranForward( AstMapping *this, int *status )
+
+*  Class Membership:
+*     MatrixMap member function (over-rides the astGetTranForward method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function returns a value indicating if the MatrixMap is able
+*     to perform a forward coordinate transformation.
+
+*  Parameters:
+*     this
+*        Pointer to the MatrixMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the forward coordinate transformation is not defined, or 1 if it
+*     is.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *map;            /* Pointer to MatrixMap to be queried */
+   int invert;                   /* Has the mapping been inverted? */
+   int result;                   /* The returned value */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the MatrixMap. */
+   map = (AstMatrixMap *) this;
+
+/* All unit MatrixMaps are defined in both directions. */
+   if( map->form == UNIT ) {
+      result = 1;
+
+/* Otherwise, check that the appropriate array is defined in the
+   MatrixMap. */
+   } else {
+
+/* Determine if the Mapping has been inverted. */
+      invert = astGetInvert( this );
+
+/* If OK, obtain the result. */
+      if ( astOK ) {
+
+         if( invert ){
+            result = ( map->i_matrix != NULL );
+         } else {
+            result = ( map->f_matrix != NULL );
+         }
+
+      }
+
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static int GetTranInverse( AstMapping *this, int *status ) {
+/*
+*
+*  Name:
+*     GetTranInverse
+
+*  Purpose:
+*     Determine if a MatrixMap defines an inverse coordinate transformation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     int GetTranInverse( AstMapping *this, int *status )
+
+*  Class Membership:
+*     MatrixMap member function (over-rides the astGetTranInverse method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function returns a value indicating if the MatrixMap is able
+*     to perform an inverse coordinate transformation.
+
+*  Parameters:
+*     this
+*        Pointer to the MatrixMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the inverse coordinate transformation is not defined, or 1 if it
+*     is.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *map;            /* Pointer to MatrixMap to be queried */
+   int invert;                   /* Has the mapping been inverted? */
+   int result;                   /* The returned value */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the MatrixMap. */
+   map = (AstMatrixMap *) this;
+
+/* All unit MatrixMaps are defined in both directions. */
+   if( map->form == UNIT ) {
+      result = 1;
+
+/* Otherwise, check that the appropriate array is defined in the
+   MatrixMap. */
+   } else {
+
+/* Determine if the Mapping has been inverted. */
+      invert = astGetInvert( this );
+
+/* If OK, obtain the result. */
+      if ( astOK ) {
+
+         if( invert ){
+            result = ( map->f_matrix != NULL );
+         } else {
+            result = ( map->i_matrix != NULL );
+         }
+
+      }
+
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a MatrixMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     MatrixMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a MatrixMap and a set of points encapsulated in a
+*     PointSet and transforms the points by multiplying them by the matrix.
+
+*  Parameters:
+*     this
+*        Pointer to the MatrixMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of columns in the MatrixMap being applied.
+*     -  The number of coordinate values per point in the output PointSet will
+*     equal the number of rows in the MatrixMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstMatrixMap *map;            /* Pointer to MatrixMap to be applied */
+   double diag_term;             /* Current diagonal element value */
+   double *indata;               /* Pointer to next input data value */
+   double *matrix;               /* Pointer to start of matrix element array */
+   double *matrix_element;       /* Pointer to current matrix element value */
+   double *outdata;              /* Pointer to next output data value */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double sum;                   /* Partial output value */
+   double val;                   /* Data value */
+   int in_coord;                 /* Index of output coordinate */
+   int nax;                      /* Output axes for which input axes exist */
+   int ncoord_in;                /* Number of coordinates per input point */
+   int ncoord_out;               /* Number of coordinates per output point */
+   int npoint;                   /* Number of points */
+   int out_coord;                /* Index of output coordinate */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the MatrixMap. */
+   map = (AstMatrixMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   and output PointSets and obtain pointers for accessing the input and
+   output coordinate values. */
+   ncoord_in = astGetNcoord( in );
+   ncoord_out = astGetNcoord( result );
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse mapping, according to the
+   direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* Get a pointer to the array holding the required matrix elements, according
+   to the direction of mapping required. */
+   if ( forward ) {
+      matrix = map->f_matrix;
+   } else {
+      matrix = map->i_matrix;
+   }
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+
+/* First deal with full MatrixMaps in which all matrix elements are stored. */
+      if( map->form == FULL ){
+
+/* Loop to apply the matrix to each point in turn, checking for
+   (and propagating) bad values in the process. The matrix elements are
+   accessed sequentially in row order. The next matrix element to be
+   used is identified by a pointer which is initialised to point to the
+   first element of the matrix prior to processing each point. */
+         for ( point = 0; point < npoint; point++ ) {
+            matrix_element = matrix;
+
+/* Each output co-ordinate value is created by summing the product of the
+   corresponding input co-ordinates and the elements of one row of the
+   matrix. */
+            for ( out_coord = 0; out_coord < ncoord_out; out_coord++ ) {
+               sum = 0.0;
+
+               for ( in_coord = 0; in_coord < ncoord_in; in_coord++ ) {
+
+/*  Check the current input coordinate value and the current matrix element.
+    If the coordinate value is bad, then the output value will also be
+    bad unless the matrix element is zero. That is, a zero matrix element
+    results in the input coordinate value being ignored, even if it is bad.
+    This prevents bad input values being propagated to output axes which
+    are independant of the bad input axis. A bad matrix element always results
+    in the output value being bad. In either of these cases, break out of the
+    loop, remembering to advance the pointer to the next matrix element so
+    that it points to the start of the next row ready for doing the next
+    output coordinate. */
+                  if ( ( ptr_in[ in_coord ][ point ] == AST__BAD &&
+                                         (*matrix_element) != 0.0 ) ||
+                       (*matrix_element) == AST__BAD ) {
+                     sum = AST__BAD;
+                     matrix_element += ncoord_in - in_coord;
+                     break;
+
+/*  If the input coordinate and the current matrix element are both
+    valid, increment the sum by their product, and step to the next matrix
+    element pointer If we arrive here with a bad input value, then the
+    matrix element must be zero, in which case the running sum is left
+    unchanged. */
+                  } else {
+                     if ( ptr_in[ in_coord ][ point ] != AST__BAD ) {
+                        sum += ptr_in[ in_coord ][ point ] * (*matrix_element);
+                     }
+                     matrix_element++;
+                  }
+               }
+
+/*  Store the output coordinate value. */
+               ptr_out[ out_coord ][ point ] = sum;
+
+            }
+
+         }
+
+/* Now deal with unit and diagonal MatrixMaps. */
+      } else {
+
+/* Find the number of output axes for which input data is available. */
+         if( ncoord_in < ncoord_out ){
+            nax = ncoord_in;
+         } else {
+            nax = ncoord_out;
+         }
+
+/* For unit matrices, copy the input axes to the corresponding output axes. */
+         if( map->form == UNIT ){
+            for( out_coord = 0; out_coord < nax; out_coord++ ) {
+               (void) memcpy( ptr_out[ out_coord ],
+                              (const void *) ptr_in[ out_coord ],
+                              sizeof( double )*(size_t)npoint );
+            }
+
+/* For diagonal matrices, scale each input axis using the appropriate
+   diagonal element from the matrix, and store in the output. */
+         } else {
+            for( out_coord = 0; out_coord < nax; out_coord++ ){
+               diag_term = matrix[ out_coord ];
+               outdata = ptr_out[ out_coord ];
+               indata = ptr_in[ out_coord ];
+
+               if( diag_term != AST__BAD ){
+                  for( point = 0; point < npoint; point++ ){
+                     val = *(indata++);
+                     if( val != AST__BAD ){
+                        *(outdata++) = diag_term*val;
+                     } else {
+                        *(outdata++) = AST__BAD;
+                     }
+                  }
+
+               } else {
+                  for( point = 0; point < npoint; point++ ){
+                     *(outdata++) = AST__BAD;
+                  }
+               }
+            }
+         }
+
+/* If there are any remaining output axes, fill the first one with zeros. */
+         if( nax < ncoord_out ){
+            outdata = ptr_out[ nax ];
+            for( point = 0; point < npoint; point++ ) *(outdata++) = 0.0;
+
+/* Copy this axis to any remaining output axes. */
+            outdata = ptr_out[ nax ];
+            for( out_coord = nax + 1; out_coord < ncoord_out; out_coord++ ) {
+               (void) memcpy( ptr_out[ out_coord ], (const void *) outdata,
+                              sizeof( double )*(size_t)npoint );
+            }
+         }
+      }
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static int ScalingRowCol( AstMatrixMap *map, int axis, int *status ){
+/*
+*  Name:
+*     ScalingRowCol
+
+*  Purpose:
+*     Determine if a given row and column of a MatrixMap are zeros
+*     with a non-zero diagonal term.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     int ScalingRowCol( AstMatrixMap *map, int axis, int *status )
+
+*  Class Membership:
+*     MatrixMap member function
+
+*  Description:
+*     This function returns a flag indicating if a MatrixMap presents a
+*     simple scaling for a given axis in both directions. The MatrixMap
+*     must be square. A value of one is returned if every element of the
+*     row and column corresponding to the given axis is zero, except for
+*     the diagonal term which must be non-zero.
+
+*  Parameters:
+*     map
+*        The MatrixMap.
+*     axis
+*        The zero-based index of the axis to check.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     1 if the row/column produces a simple scaling, 0 otherwise.
+
+*/
+
+/* Local Variables: */
+   double *el;               /* Pointer to matrix element */
+   int i;                    /* Element count */
+   int ncol;                 /* No. of input coordinates */
+   int ret;                  /* Returned flag */
+
+/* Initialise */
+   ret = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* If a unit or diagonal MatrixMap has been supplied, return 1. */
+   if( map->form != FULL ){
+      ret = 1;
+
+/* If a full matrix has been supplied... */
+   } else {
+
+/* Assume the row/column gives a unit mapping. */
+      ret = 1;
+
+/* Get the number of input axes for the MatrixMap. */
+      ncol = astGetNin( map );
+
+/* Check that all elements of the "axis"th row are effectively zero, except
+   for the "axis"th element which must be non-zero. */
+      el = map->f_matrix + axis*ncol;
+      for( i = 0; i < ncol; i++ ) {
+         if( i == axis ) {
+            if( fabs( *el ) <= DBL_EPSILON ) {
+               ret = 0;
+               break;
+            }
+         } else if( fabs( *el ) > DBL_EPSILON ) {
+            ret = 0;
+            break;
+         }
+         el++;
+      }
+
+/* Check that all elements of the "axis"th column are effectively zero, except
+   for the "axis"th element which must be non-zero. */
+      if( ret ) {
+         el = map->f_matrix + axis;
+         for( i = 0; i < ncol; i++ ) {
+            if( i == axis ) {
+               if( fabs( *el ) <= DBL_EPSILON ) {
+                  ret = 0;
+                  break;
+               }
+            } else if( fabs( *el ) > DBL_EPSILON ) {
+               ret = 0;
+               break;
+            }
+            el += ncol;
+         }
+      }
+   }
+
+/* Return the answer. */
+   return astOK ? ret : 0;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for MatrixMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for MatrixMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the matrix
+*     element values associated with the input MatrixMap.
+*/
+
+
+/* Local Variables: */
+   AstMatrixMap *in;             /* Pointer to input MatrixMap */
+   AstMatrixMap *out;            /* Pointer to output MatrixMap */
+   int nel;                      /* No. of elements in the matrix */
+   int nin;                      /* No. of input coordinates */
+   int nout;                     /* No. of output coordinates */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output MatrixMaps. */
+   in = (AstMatrixMap *) objin;
+   out = (AstMatrixMap *) objout;
+
+/* Nullify the pointers stored in the output object since these will
+   currently be pointing at the input data (since the output is a simple
+   byte-for-byte copy of the input). Otherwise, the input data could be
+   freed by accidient if the output object is deleted due to an error
+   occuring in this function. */
+   out->f_matrix = NULL;
+   out->i_matrix = NULL;
+
+/* If the input MatrixMap is a unit mapping, then no matrix elements are
+   stored with it, so do nothing in this case. */
+   if( out->form != UNIT ){
+
+/* Obtain the number of stored values in the MatrixMap. This is independant of
+   whether the Mapping has been inverted or not. If the MatrixMap is diagonal,
+   only the diagonal terms are stored. */
+      nin = astGetNin( in );
+      nout = astGetNout( in );
+
+      if( out->form == DIAGONAL ){
+         if( nin < nout ){
+            nel = nin;
+         } else {
+            nel = nout;
+         }
+
+      } else {
+         nel = nin*nout;
+      }
+
+/* Store the forward matrix elements in the output MatrixMap. */
+      out->f_matrix = (double *) astStore( NULL, (void *) in->f_matrix,
+                                           sizeof( double )*(size_t) nel );
+
+/* Store the inverse matrix elements (if defined) in the output
+   MatrixMap. */
+      if( in->i_matrix ){
+         out->i_matrix = (double *) astStore( NULL, (void *) in->i_matrix,
+                                              sizeof( double )*(size_t) nel );
+      }
+
+/* If an error has occurred, free the output MatrixMap arrays. */
+      if( !astOK ) {
+         out->f_matrix = (double *) astFree( (void *) out->f_matrix );
+         out->i_matrix = (double *) astFree( (void *) out->i_matrix );
+      }
+   }
+
+   return;
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for MatrixMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for MatrixMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstMatrixMap *this;            /* Pointer to MatrixMap */
+
+/* Obtain a pointer to the MatrixMap structure. */
+   this = (AstMatrixMap *) obj;
+
+/* Free the arrays used to store element values for forward and inverse
+   matrices. */
+   this->f_matrix = (double *) astFree( (void *) this->f_matrix );
+   this->i_matrix = (double *) astFree( (void *) this->i_matrix );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for MatrixMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the MatrixMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the MatrixMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstMatrixMap *this;           /* Pointer to the MatrixMap structure */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   int el;                       /* Element index */
+   int nel;                      /* No. of elements in the matrix */
+   int nin;                      /* No. of input coords */
+   int nout;                     /* No. of output coords */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the MatrixMap structure. */
+   this = (AstMatrixMap *) this_object;
+
+/* Find the number of elements stored for each matrix. */
+   nin = astGetNin( this );
+   nout = astGetNout( this );
+
+   if( this->form == FULL ){
+      nel = nin*nout;
+
+   } else if( this->form == DIAGONAL ){
+      nel = astMIN( nin, nout );
+
+   } else {
+      nel = 0;
+   }
+
+/* Write out values representing the instance variables for the
+   MatrixMap class.  */
+
+/* The forward matrix. The inverse matrix not written out since it can be
+   re-calculated when the MatrixMap is read back in. Note BAD values are
+   not written out as the AST__BAD value may differ on different machines.
+   If a matrix element is not found when reading the matrix back in
+   again (in astLoadMatrixMap), then it is assigned a default value of
+   AST__BAD. */
+   if( this->f_matrix ){
+      for( el = 0; el < nel; el++ ){
+         if( (this->f_matrix)[ el ] != AST__BAD ) {
+            (void) sprintf( buff, "M%d", el );
+            astWriteDouble( channel, buff, 1, 1, (this->f_matrix)[ el ],
+                            "Forward matrix value" );
+         }
+      }
+   }
+
+/* The matrix storage form. */
+   astWriteString( channel, "Form", 1, 1, Form[ this->form ],
+                   "Matrix storage form" );
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAMatrixMap and astCheckMatrixMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(MatrixMap,Mapping)
+astMAKE_CHECK(MatrixMap)
+
+AstMatrixMap *astMatrixMap_( int nin, int nout, int form,
+                             const double matrix[], const char *options, int *status, ...){
+/*
+*++
+*  Name:
+c     astMatrixMap
+f     AST_MATRIXMAP
+
+*  Purpose:
+*     Create a MatrixMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "matrixmap.h"
+c     AstMatrixMap *astMatrixMap( int nin, int nout, int form,
+c                                 const double matrix[],
+c                                 const char *options, ... )
+f     RESULT = AST_MATRIXMAP( NIN, NOUT, FORM, MATRIX, OPTIONS, STATUS )
+
+*  Class Membership:
+*     MatrixMap constructor.
+
+*  Description:
+*     This function creates a new MatrixMap and optionally initialises
+*     its attributes.
+*
+*     A MatrixMap is a form of Mapping which performs a general linear
+*     transformation.  Each set of input coordinates, regarded as a
+*     column-vector, are pre-multiplied by a matrix (whose elements
+*     are specified when the MatrixMap is created) to give a new
+*     column-vector containing the output coordinates. If appropriate,
+*     the inverse transformation may also be performed.
+
+*  Parameters:
+c     nin
+f     NIN = INTEGER (Given)
+*        The number of input coordinates, which determines the number
+*        of columns in the matrix.
+c     nout
+f     NOUT = INTEGER (Given)
+*        The number of output coordinates, which determines the number
+*        of rows in the matrix.
+c     form
+f     FORM = INTEGER (Given)
+*        An integer which indicates the form in which the matrix
+*        elements will be supplied.
+*
+c        A value of zero indicates that a full "nout" x "nin" matrix
+f        A value of zero indicates that a full NOUT x NIN  matrix
+c        of values will be supplied via the "matrix" parameter
+f        of values will be supplied via the MATRIX argument
+*        (below). In this case, the elements should be given in row
+*        order (the elements of the first row, followed by the
+*        elements of the second row, etc.).
+*
+*        A value of 1 indicates that only the diagonal elements of the
+*        matrix will be supplied, and that all others should be
+c        zero. In this case, the elements of "matrix" should contain
+f        zero. In this case, the elements of MATRIX should contain
+*        only the diagonal elements, stored consecutively.
+*
+*        A value of 2 indicates that a "unit" matrix is required,
+*        whose diagonal elements are set to unity (with all other
+c        elements zero).  In this case, the "matrix" parameter is
+c        ignored and a NULL pointer may be supplied.
+f        elements zero).  In this case, the MATRIX argument is not used.
+c     matrix
+f     MATRIX( * ) = DOUBLE PRECISION (Given)
+*        The array of matrix elements to be used, stored according to
+c        the value of "form".
+f        the value of FORM.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new MatrixMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new MatrixMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMatrixMap()
+f     AST_MATRIXMAP = INTEGER
+*        A pointer to the new MatrixMap.
+
+*  Notes:
+*     - In general, a MatrixMap's forward transformation will always
+*     be available (as indicated by its TranForward attribute), but
+*     its inverse transformation (TranInverse attribute) will only be
+*     available if the associated matrix is square and non-singular.
+*     - As an exception to this, the inverse transformation is always
+*     available if a unit or diagonal matrix is specified. In this
+*     case, if the matrix is not square, one or more of the input
+*     coordinate values may not be recoverable from a set of output
+*     coordinates. Any coordinates affected in this way will simply be
+*     set to the value zero.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMatrixMap *new;            /* Pointer to new MatrixMap */
+   va_list args;                 /* Variable argument list */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise the MatrixMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitMatrixMap( NULL, sizeof( AstMatrixMap ), !class_init,
+                           &class_vtab, "MatrixMap", nin, nout, form, matrix);
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new MatrixMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new MatrixMap. */
+   return new;
+}
+
+AstMatrixMap *astMatrixMapId_( int nin, int nout, int form, const double matrix[],
+                               const char *options, ... ) {
+/*
+*  Name:
+*     astMatrixMapId_
+
+*  Purpose:
+*     Create a MatrixMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     AstMatrixMap *astMatrixMapId_( int nin, int nout, int form,
+*                                    const double matrix[], const char *options,
+*                                    ... )
+
+*  Class Membership:
+*     MatrixMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astMatrixMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astMatrixMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astMatrixMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astMatrixMap_.
+
+*  Returned Value:
+*     The ID value associated with the new MatrixMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMatrixMap *new;            /* Pointer to new MatrixMap */
+   va_list args;                 /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the MatrixMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitMatrixMap( NULL, sizeof( AstMatrixMap ), !class_init, &class_vtab,
+                         "MatrixMap", nin, nout, form, matrix );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new MatrixMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new MatrixMap. */
+   return astMakeId( new );
+}
+
+AstMatrixMap *astInitMatrixMap_( void *mem, size_t size, int init,
+                                 AstMatrixMapVtab *vtab, const char *name,
+                                 int nin, int nout, int form,
+                                 const double *matrix, int *status ) {
+/*
+*+
+*  Name:
+*     astInitMatrixMap
+
+*  Purpose:
+*     Initialise a MatrixMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     AstMatrixMap *astInitMatrixMap( void *mem, size_t size, int init,
+*                                 AstMatrixMapVtab *vtab, const char *name,
+*                                 int nin, int nout, int form,
+*                                 const double *matrix )
+
+*  Class Membership:
+*     MatrixMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new MatrixMap object. It allocates memory (if necessary) to accommodate
+*     the MatrixMap plus any additional data associated with the derived class.
+*     It then initialises a MatrixMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a MatrixMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the MatrixMap is to be initialised.
+*        This must be of sufficient size to accommodate the MatrixMap data
+*        (sizeof(MatrixMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the MatrixMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the MatrixMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the MatrixMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new MatrixMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     nin
+*        The number of input coordinate values per point. This is the
+*        same as the number of columns in the matrix.
+*     nout
+*        The number of output coordinate values per point. This is the
+*        same as the number of rows in the matrix.
+*     form
+*        If "form" is 2 or larger, then a unit MatrixMap is created. In this
+*        case "matrix" is ignored and can be supplied as NULL. If "form" is
+*        1, then a diagonal MatrixMap is created. In this case, the number of
+*        values in "matrix" should be equal to the minimum of nin and nout,
+*        and "matrix" should contain the corresponding diagonal terms, in row
+*        order. If "form" is 0 or less, then a full MatrixMap is created, and
+*        "matrix" should contain all nin*nout element values.
+*     matrix
+*        A pointer to an array of matrix element values. The values should be
+*        supplied in row order. The content of this array is determined by
+*        "form". If a full MatrixMap is to be created then the array starts
+*        with (row 1, column 1), then comes (row 1, column 2), up to (row 1,
+*        column nin), then (row 2, column 1), (row 2, column 2), and so on,
+*        finishing with (row nout, column nin) ). An error is reported if a
+*        NULL value is supplied unless "form" is 2 or more.
+
+*  Returned Value:
+*     A pointer to the new MatrixMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstMatrixMap *new;              /* Pointer to new MatrixMap */
+   double *fmat;                   /* Pointer to the forward matrix */
+   double *imat;                   /* Pointer to the inverse matrix */
+   int i;                          /* Loop count */
+   int nel;                        /* No. of elements in matrix array */
+   int nuse;                       /* Number of usable matrix elements */
+   int used_form;                  /* Form limited to 0, 1 or 2 */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitMatrixMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Report an error if a NULL matrix was supplied, unless a unit MatrixMap
+   has been requested. */
+   if( form < 2 && !matrix ){
+      astError( AST__MTRMT, "astInitMatrixMap(%s): NULL matrix supplied.", status,
+                name );
+
+   } else {
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the MatrixMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+      new = (AstMatrixMap *) astInitMapping( mem, size, 0,
+                                           (AstMappingVtab *) vtab, name,
+                                            nin, nout, 1, 1 );
+
+      if ( astOK ) {
+
+/* Initialise the MatrixMap data. */
+/* ---------------------------- */
+/* If a unit MatrixMap is being created, then no additional storage is
+   required. */
+         if( form > 1 ){
+            nel = 0;
+            used_form = UNIT;
+
+/* If a diagonal MatrixMap is being created, then memory is needed to hold
+   the diagonal terms. */
+         } else if( form == 1 ){
+            if( nin < nout ){
+               nel = nin;
+            } else {
+               nel = nout;
+            }
+            used_form = DIAGONAL;
+
+/* If a full MatrixMap is being created, then memory is needed to hold
+   all the terms. */
+         } else {
+            nel = nin*nout ;
+            used_form = FULL;
+         }
+
+/* Allocate memory for the forward matrix, storing the supplied matrix
+   values in it. */
+         fmat = (double *) astStore( NULL, (void *) matrix,
+                                     sizeof(double)*(size_t)nel );
+
+/* Replace any NaNs by AST__BAD and count the number of usable values. */
+         if( nel > 0 ) {
+            nuse = 0;
+            for( i = 0; i < nel; i++ ) {
+               if( !astISFINITE(fmat[ i ]) ) {
+                  fmat[ i ] = AST__BAD;
+               } else if( fmat[ i ] != AST__BAD ) {
+                  nuse++;
+               }
+            }
+
+/* Report an error if there are no usable values. */
+            if( nuse == 0 && astOK ) {
+               astError( AST__MTRMT, "astInitMatrixMap(%s): Supplied matrix "
+                         "contains only bad values.",  status, name );
+            }
+         }
+
+/* Create an inverse matrix if possible. */
+         imat = InvertMatrix( used_form, nout, nin, fmat, status );
+
+/* Store the matrix arrays. */
+         new->form = used_form;
+         new->f_matrix = fmat;
+         new->i_matrix = imat;
+
+/* Attempt to compress the MatrixMap into DIAGONAL or UNIT form. */
+         CompressMatrix( new, status );
+
+/* If an error occurred, clean up by deleting the new MatrixMap. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new MatrixMap. */
+   return new;
+}
+
+AstMatrixMap *astLoadMatrixMap_( void *mem, size_t size,
+                                 AstMatrixMapVtab *vtab, const char *name,
+                                 AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadMatrixMap
+
+*  Purpose:
+*     Load a MatrixMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     AstMatrixMap *astLoadMatrixMap( void *mem, size_t size,
+*                                     AstMatrixMapVtab *vtab, const char *name,
+*                                     AstChannel *channel )
+
+*  Class Membership:
+*     MatrixMap loader.
+
+*  Description:
+*     This function is provided to load a new MatrixMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     MatrixMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a MatrixMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the MatrixMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        MatrixMap data (sizeof(MatrixMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the MatrixMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the MatrixMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstMatrixMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new MatrixMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the MatrixMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "MatrixMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new MatrixMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+/* Local Variables: */
+   AstMatrixMap *new;            /* Pointer to the new MatrixMap */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   const char *form;             /* String form */
+   int def;                      /* Is the matrix defined? */
+   int el;                       /* Element index */
+   int nel;                      /* No. of elements in the matrix */
+   int nin;                      /* No. of input coords */
+   int nout;                     /* No. of output coords */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this MatrixMap. In this case the
+   MatrixMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstMatrixMap );
+      vtab = &class_vtab;
+      name = "MatrixMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitMatrixMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built MatrixMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "MatrixMap" );
+
+/* Now obtain the Matrix storage form from this list. */
+      form = astReadString( channel, "form", Form[FULL] );
+      new->form = FindString( 3, Form, form, "the MatrixMap component 'Form'",
+                              "astRead", astGetClass( channel ), status );
+      form = astFree( (void *) form );
+
+/* Find the number of elements stored for each matrix. */
+      nin = astGetNin( (AstMapping *) new );
+      nout = astGetNout( (AstMapping *) new );
+
+      if( new->form == FULL ){
+         nel = nin*nout;
+
+      } else if( new->form == DIAGONAL ){
+         nel = astMIN( nin, nout );
+
+      } else {
+         nel = 0;
+      }
+
+/* Allocate memory to hold the forward matrix. */
+      new->f_matrix = (double *) astMalloc( sizeof(double)*(size_t)nel );
+
+/* Now read the other data items from the list and use them to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* The forward matrix. */
+      if( new->f_matrix ){
+         def = 0;
+
+         for( el = 0; el < nel; el++ ){
+            (void) sprintf( buff, "m%d", el );
+            (new->f_matrix)[ el ] = astReadDouble( channel, buff, AST__BAD );
+            if( (new->f_matrix)[ el ] != AST__BAD ) def = 1;
+         }
+
+/* Store a NULL pointer if no elements of the matrix were found. */
+         if( !def ) new->f_matrix = (double *) astFree( (void *) new->f_matrix );
+
+      }
+
+/* Create an inverse matrix if possible, otherwise store a NULL pointer. */
+      if( new->f_matrix ){
+         new->i_matrix = InvertMatrix( new->form, nout, nin, new->f_matrix, status );
+      } else {
+         new->i_matrix = NULL;
+      }
+
+/* If an error occurred, clean up by deleting the new MatrixMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new MatrixMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+AstMatrixMap *astMtrRot_( AstMatrixMap *this, double theta,
+                                const double axis[], int *status ){
+   if( !astOK ) return NULL;
+   return (**astMEMBER(this,MatrixMap,MtrRot))( this, theta, axis, status );
+}
+
+AstMatrixMap *astMtrMult_( AstMatrixMap *this, AstMatrixMap *a, int *status ){
+   if( !astOK ) return NULL;
+   return (**astMEMBER(this,MatrixMap,MtrMult))( this, a, status );
+}
+
+
+
+
diff --git a/ast/matrixmap.h b/ast/matrixmap.h
new file mode 100644
index 0000000..b24565a
--- /dev/null
+++ b/ast/matrixmap.h
@@ -0,0 +1,318 @@
+#if !defined( MATRIXMAP_INCLUDED ) /* Include this file only once */
+#define MATRIXMAP_INCLUDED
+/*
+*+
+*  Name:
+*     matrixmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the MatrixMap class.
+
+*  Invocation:
+*     #include "matrixmap.h"
+
+*  Description:
+*     This include file defines the interface to the MatrixMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The MatrixMap class implements Mappings that transform a set of
+*     coordinates by multiplying them by a matrix. The inverse transformation
+*     can only be applied if the associated matrix is square and non-singular.
+
+*  Inheritance:
+*     The MatrixMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     TranForward (integer)
+*        A read-only boolean value (0 or 1) which indicates whether a
+*        MatrixMap is able to transform coordinates in the "forward"
+*        direction (i.e. converting input coordinates into output
+*        coordinates).
+*     TranInverse (integer)
+*        A read-only boolean value (0 or 1) which indicates whether a
+*        MatrixMap is able to transform coordinates in the "inverse"
+*        direction (i.e. converting output coordinates back into input
+*        coordinates).
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astTransform
+*           Apply a MatrixMap to transform a set of points.
+*        astGetTranForward
+*           Determine if a MatrixMap can perform a "forward" coordinate
+*           transformation.
+*        astGetTranInverse
+*           Determine if a MatrixMap can perform an "inverse" coordinate
+*           transformation.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astMtrMult
+*           Multiply a MatrixMap by another MatrixMap.
+*        astMtrRot
+*           Rotate a MatrixMap.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAMatrixMap
+*           Test class membership.
+*        astMatrixMap
+*           Create a MatrixMap.
+*
+*     Protected:
+*        astCheckMatrixMap
+*           Validate class membership.
+*        astInitMatrixMap
+*           Initialise a MatrixMap.
+*        astInitMatrixMapVtab
+*           Initialise the virtual function table for the MatrixMap class.
+*        astLoadMatrixMap
+*           Load a MatrixMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstMatrixMap
+*           MatrixMap object type.
+*
+*     Protected:
+*        AstMatrixMapVtab
+*           MatrixMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     12-Feb-1996 (DSB):
+*        Original version.
+*     22-Feb-1996 (DSB):
+*        Method "astMatrixRotate" added.
+*     5-Mar-1996 (DSB):
+*        Method "astMatrixMult" added.
+*     14-NOV-1996 (DSB):
+*        External interface and I/O added. Public method names shortened.
+*     3-JUN-1997 (DSB):
+*        astMtrMult and astMtrRot made protected instead of public.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitMatrixMapVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* MatrixMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstMatrixMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *f_matrix;             /* Pointer to forward matrix */
+   double *i_matrix;             /* Pointer to inverse matrix */
+   int form;                     /* Matrix storage form */
+
+} AstMatrixMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstMatrixMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstMatrixMap *(* MtrRot)( AstMatrixMap *, double, const double[], int * );
+   AstMatrixMap *(* MtrMult)( AstMatrixMap *,  AstMatrixMap *, int * );
+
+} AstMatrixMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstMatrixMapGlobals {
+   AstMatrixMapVtab Class_Vtab;
+   int Class_Init;
+} AstMatrixMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitMatrixMapGlobals_( AstMatrixMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(MatrixMap)          /* Check class membership */
+astPROTO_ISA(MatrixMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstMatrixMap *astMatrixMap_( int, int, int, const double[], const char *, int *, ...);
+#else
+AstMatrixMap *astMatrixMapId_( int, int, int, const double[], const char *, ... )__attribute__((format(printf,5,6)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstMatrixMap *astInitMatrixMap_( void *, size_t, int, AstMatrixMapVtab *,
+                                 const char *, int, int, int, const double[], int * );
+
+/* Vtab initialiser. */
+void astInitMatrixMapVtab_( AstMatrixMapVtab *, const char *, int * );
+
+/* Loader. */
+AstMatrixMap *astLoadMatrixMap_( void *, size_t, AstMatrixMapVtab *,
+                                 const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+AstMatrixMap *astMtrRot_( AstMatrixMap *, double, const double[], int * );
+AstMatrixMap *astMtrMult_( AstMatrixMap *, AstMatrixMap *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckMatrixMap(this) astINVOKE_CHECK(MatrixMap,this,0)
+#define astVerifyMatrixMap(this) astINVOKE_CHECK(MatrixMap,this,1)
+
+/* Test class membership. */
+#define astIsAMatrixMap(this) astINVOKE_ISA(MatrixMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astMatrixMap astINVOKE(F,astMatrixMap_)
+#else
+#define astMatrixMap astINVOKE(F,astMatrixMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitMatrixMap(mem,size,init,vtab,name,nin,nout,form,matrix) \
+astINVOKE(O,astInitMatrixMap_(mem,size,init,vtab,name,nin,nout,form,matrix,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitMatrixMapVtab(vtab,name) astINVOKE(V,astInitMatrixMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadMatrixMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadMatrixMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckMatrixMap to validate MatrixMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astMtrRot(this,theta,axis) \
+astINVOKE(O,astMtrRot_(astCheckMatrixMap(this),theta,axis,STATUS_PTR))
+
+#define astMtrMult(this,a) \
+astINVOKE(O,astMtrMult_(astCheckMatrixMap(this),astCheckMatrixMap(a),STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/memory.c b/ast/memory.c
new file mode 100644
index 0000000..0bc3d93
--- /dev/null
+++ b/ast/memory.c
@@ -0,0 +1,5483 @@
+/*
+*  Name:
+*     memory.c
+
+*  Purpose:
+*     Implement memory allocation/deallocation functions.
+
+*  Description:
+*     This file implements the Memory module which is used for
+*     allocating and freeing memory in the AST library.  For a
+*     description of the module and its interface, see the .h file of
+*     the same name.
+
+*     Note, it is assumed that malloc, free and realloc are thread-safe.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009-2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     2-JAN-1996 (RFWS):
+*        Original version.
+*     26-JAN-1996 (RFWS):
+*        Removed trailing underscores from static functions and
+*        changed to use new error function interfaces.
+*     20-JUN-1996 (RFWS):
+*        Added astString.
+*     15-JUL-1996 (RFWS):
+*        Make IsDynamic execute under error conditions to avoid memory
+*        leaks in such situations.
+*     11-SEP-1996 (RFWS):
+*        Added astStringArray (original written by DSB).
+*     18-MAR-1998 (RFWS):
+*        Added notes about these functions being available for writing
+*        foreign language and graphics interfaces, etc.
+*     29-JAN-2002 (DSB):
+*        Added astChrLen and astSscanf.
+*     15-FEB-2002 (DSB):
+*        Removed use of non-ANSI vsscanf from astSscanf.
+*     15-NOV-2002 (DSB):
+*        Moved ChrMatch from SkyFrame (etc) to here. Included stdio.h and
+*        ctype.h.
+*     10-FEB-2003 (DSB):
+*        Added facilities for detecting and tracing memory leaks. These
+*        are only included if AST is compiled with the -DDEBUG flag.
+*     3-MAR-2004 (DSB):
+*        Modified astSscanf to avoid use of uninitialised values
+*        corresponding to "%n" fields in the format string.
+*     26-JAN-2004 (DSB):
+*        Modified astRealloc to clarify the nature of the returned pointer
+*        (which is not a "Memory *"). Also correct issuing and deissuing
+*        of pointers in DEBUG code within astRealloc.
+*     16-FEB-2006 (DSB):
+*        Convert Magic from a function to a macro for extra speed.
+*     21-FEB-2006 (DSB):
+*        Convert IsDynamic from a function to a macro for extra speed.
+*     23-FEB-2006 (DSB):
+*        Added the caching system for allocated but unused memory blocks,
+*        controlled by AST tuning parameter MemoryCaching.
+*     2-MAR-2006 (DSB):
+*        Added astFlushMemory, and renamed the memory debugging functions.
+*        These are now conditionally compiled if the MEM_DEBUG macros is
+*        defined (set by configuring AST with the --with-memdebug option).
+*        Also modified them to take into account MemoryCaching.
+*     24-MAY-2006 (DSB):
+*        Ensure that pointers to memory returned by this module are all
+*        aligned on 8 byte boundaries. This fixes problems with ualigned
+*        memory access that could cause bus errors on Solaris.
+*     26-MAY-2006 (DSB):
+*        Cast (void *) pointers to (char *) before doing arithmetic on
+*        them (changes supplied by Micah Johnson).
+*     4-DEC-2006 (DSB):
+*        Fix bug in astMalloc that caused a non-null pointer to be
+*        returned on error.
+*     4-JAN-2007 (DSB):
+*        Move definition of astCLASS macro so that it comes before the
+*        inclusion of the AST include files (which test for astCLASS).
+*     27-JUN-2007 (DSB):
+*        Added astIsDynamic.
+*     24-OCT-2007 (DSB):
+*        Zero the size of memory blocks stored in the Cache so that an
+*        error will be reported if an attempt is made to free a memory
+*        block that has already been freed.
+*     25-OCT-2007 (DSB):
+*        Added astRemoveLeadingBlanks.
+*     28-FEB-2008 (DSB):
+*        Added astChrSub.
+*     17-MAR-2008 (DSB):
+*        Added "{nnn}" quantifier to astChrSub.
+*     27-MAR-2008 (DSB):
+*        Added astChrSplitRE, and re-structured regexp functions.
+*     18-NOV-2008 (DSB):
+*        In astFlushMemory, do not release permanent memory blocks as
+*        they may still be needed.
+*     9-FEB-2009 (DSB):
+*        Added astChr2Double.
+*     25-JUN-2009 (DSB):
+*        Fix handling of escape characters in astSplitC.
+*     19-MAY-2010 (DSB):
+*        - Added astStringCase.
+*        - Changed access from protected to public for commonly used
+*        functions.
+*     26-MAY-2010 (DSB):
+*        Added astCalloc.
+*     18-AUG-2010 (DSB):
+*        Added astMemoryStats
+*     19-AUG-2010 (DSB):
+*        Added astMemoryWarning
+*     8-OCT-2010 (DSB):
+*        Modify memory allocation to use "calloc" directly, rather than
+*        using "malloc+memset".
+*     12-APR-2011 (DSB):
+*        Fix regular expression problem where a ".*" template field failed to
+*        match a null string if it occurred before a closing parenthesis at
+*        the end of the template.
+*     26-MAY-2011 (DSB):
+*        - Changed API for astCalloc to match RTL (i.e. remove "init").
+*        - Changed astChr2Double to check for strigs like "2.", which
+*        some sscanfs fail to read as a floating point value.
+*     27-MAY-2011 (DSB):
+*        Added astFreeDouble to free a dynamically allocated array of
+*        pointers to other dynamically allocated arrays.
+*     21-JUN-2011 (DSB):
+*        Added astCheckMemory - an alternative to astFlushMemory that does
+*        not free any memory.
+*     21-NOV-2011 (DSB):
+*        Correct matchend value returned by astChrSplitRE.
+*     6-JAN-2014 (DSB):
+*        Optimise access to cache to avoid valgrind warnings.
+*     16-JAN-2014 (DSB):
+*        Dump details of all active memory blocks if the total memory allocation
+*        specified by astMemoryWarning is exceeded.
+*     23-SEP-2014 (DSB):
+*        Modify astAppendString to allow printf conversion specifications to
+*        be included in the appended string.
+*     20-OCT-2014 (DSB):
+*        Revert the change to astAppendString made on 23-SEP-2014 as it is
+*        insecure. Instead add new function astAppendStringf.
+*     26-MAR-2015 (DSB):
+*        Added astChrTrunc.
+*/
+
+/* Configuration results. */
+/* ---------------------- */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Module Macros. */
+/* ============== */
+/* Define the astCLASS macro (even although this is not a class
+   implementation) to obtain access to the protected error handling
+   functions. */
+#define astCLASS memory
+
+/* The maximum number of fields within a format string allowed by astSscanf. */
+#define VMAXFLD 20
+
+/* The maximum number of nested astBeginPM/astEndPM contexts. */
+#define PM_STACK_MAXSIZE 20
+
+/* Select the appropriate memory management functions. These will be the
+   system's malloc, calloc, free and realloc unless AST was configured with
+   the "--with-starmem" option, in which case they will be the starmem
+   malloc, calloc, free and realloc. */
+#ifdef HAVE_STAR_MEM_H
+#  include <star/mem.h>
+#  define MALLOC starMalloc
+#  define CALLOC starCalloc
+#  define FREE starFree
+#  define REALLOC starRealloc
+#else
+#  define MALLOC malloc
+#  define CALLOC calloc
+#  define FREE free
+#  define REALLOC realloc
+#endif
+
+
+#ifdef MEM_DEBUG
+#define ISSUED "issued"
+#define FREED "freed"
+#endif
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "error.h"               /* Error reporting facilities */
+#include "globals.h"             /* Thread-specific global data */
+#include "memory.h"              /* Interface to this module */
+#include "pointset.h"            /* For AST__BAD */
+
+#ifdef MEM_DEBUG
+#include "object.h"              /* For astMakePointer */
+#endif
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <limits.h>
+
+#ifdef THREAD_SAFE
+#include <pthread.h>
+#endif
+
+#ifdef MEM_PROFILE
+#include <sys/times.h>
+#endif
+
+/* Function Macros. */
+/* =============== */
+/* These are defined as macros rather than functions to avoid the
+   overhead of a function call since they are called extremely frequently. */
+
+/*
+*  Name:
+*     IS_DYNAMIC
+
+*  Purpose:
+*     Test whether a memory region has been dynamically allocated.
+
+*  Type:
+*     Private macro
+
+*  Synopsis:
+*     #include "memory.h"
+*     IS_DYNAMIC( ptr, dynamic )
+
+*  Description:
+*     This macro takes a pointer to a region of memory and tests if
+*     the memory has previously been dynamically allocated using other
+*     functions from this module. It does this by checking for the
+*     presence of a "magic" number in the header which precedes the
+*     allocated memory. If the magic number is not present (or the
+*     pointer is invalid for any other reason), an error is reported
+*     and the global error status is set.
+*
+*     The result of the test is written to the variable specified by "res".
+
+*  Parameters:
+*     ptr
+*        Pointer to the start (as known to the external user) of the
+*        dynamically allocated memory.
+*     dynamic
+*        Name of an "int" variable to recieve the result of the test.
+*        If the memory was allocated dynamically, a value of 1 is
+*        stored in this variable.  Otherwise, zero is stored and an error
+*        results.
+
+*  Notes:
+*     - A NULL pointer value produces an error report from this
+*     function, although other functions may wish to regard a NULL
+*     pointer as valid.
+*     - This function attempts to execute even if the global error
+*     status is set, although no further error report will be made if
+*     the memory is not dynamically allocated under these
+*     circumstances.
+*     - The test performed by this function is not 100% secure as the
+*     "magic" value could occur by accident (although this is
+*     unlikely). It is mainly intended to provide security against
+*     programming errors, including accidental corruption of the
+*     memory header and attempts to allocate the same region of memory
+*     more than once.
+*/
+
+#define IS_DYNAMIC(ptr,dynamic) \
+\
+/* Initialise. */ \
+   dynamic = 0; \
+\
+/* Check that a NULL pointer has not been supplied and report an error \
+   if it has (but not if the global status is already set). */ \
+   if ( !ptr ) { \
+      if ( astOK ) { \
+         astError( AST__PTRIN, "Invalid NULL pointer (address %p).", status, ptr ); \
+      } \
+\
+/* If OK, derive a pointer to the memory header that precedes the \
+   allocated region of memory. */ \
+   } else { \
+      Memory *isdynmem;                /* Pointer to memory header */ \
+      isdynmem = (Memory *) ( (char *) ptr - SIZEOF_MEMORY ); \
+\
+/* Check if the "magic number" in the header is valid and report an \
+   error if it is not (but not if the global status is already \
+   set). */ \
+      if ( isdynmem->magic != MAGIC( isdynmem, isdynmem->size ) ) { \
+         if ( astOK ) { \
+            astError( AST__PTRIN, \
+                      "Invalid pointer or corrupted memory at address %p.", status, \
+                      ptr ); \
+         } \
+\
+/* Note if the magic number is OK. */ \
+      } else { \
+         dynamic = 1; \
+      } \
+   }
+
+
+
+/*
+*  Name:
+*     MAGIC
+
+*  Purpose:
+*     Generate a "magic number".
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "memory.h"
+*     unsigned long MAGIC( void *ptr, size_t size )
+
+*  Description:
+*     This macro generates a "magic number" which is a function of
+*     a memory address and an object size. This number may be stored
+*     in a region of dynamically allocated memory to allow it to be
+*     recognised as dynamically allocated by other routines, and also
+*     to provide security against memory leaks, etc.
+
+*  Parameters:
+*     ptr
+*        The memory pointer.
+*     size
+*        The object size.
+
+*  Returned Value:
+*     The function returns the magic number.
+
+*  Notes:
+*     This function does not perform error checking.
+*/
+
+/* Form the bit-wise exclusive OR between the memory address and the
+   object size, then add 1 and invert the bits. Return the result as
+   an unsigned long integer. */
+#define MAGIC(ptr,size) \
+   ( ~( ( ( (unsigned long) ptr ) ^ ( (unsigned long) size ) ) + \
+             ( (unsigned long) 1 ) ) )
+
+/* A macro that returns the size of the a Memory structure padded to a
+   multiple of 8 bytes. */
+#define SIZEOF_MEMORY \
+   ( ( sizeof_memory != 0 ) ? sizeof_memory : SizeOfMemory( status ) )
+
+
+/* Type Definitions. */
+/* ================= */
+
+#ifdef MEM_PROFILE
+
+/* Structure used to record the time spent between matching calls to
+   astStartTimer and astStopTimer. */
+typedef struct AstTimer {
+   int id;                    /* Unique integer identifier for timer */
+   clock_t e0;                /* Absolute elapsed time at timer start */
+   clock_t u0;                /* Absolute user time at timer start */
+   clock_t s0;                /* Absolute system time at timer start */
+   clock_t et;                /* Cumulative elapsed time within timer */
+   clock_t ut;                /* Cumulative user time within timer */
+   clock_t st;                /* Cumulative system time within timer */
+   int nentry;                /* Number of entries into the timer */
+   const char *name;          /* An identifying label for the timer */
+   const char *file;          /* Name of source file where timer was started */
+   int line;                  /* Source file line no. where timer was started */
+   struct AstTimer *parent;   /* The parent enclosing timer */
+   int nchild;                /* Number of child timers */
+   struct AstTimer **children;/* Timers that count time within this timer */
+} AstTimer;
+
+#endif
+
+/* Module Variables. */
+/* ================= */
+
+/* Extra stuff for profiling (can only be used in single threaded
+   environments). */
+#ifdef MEM_PROFILE
+static AstTimer *Current_Timer = NULL;
+static int Enable_Timers = 0;
+static int Timer_Count = 0;
+#endif
+
+/* Extra stuff for debugging of memory management (tracking of leaks
+   etc). */
+#ifdef MEM_DEBUG
+
+/* The identifier for the memory block which is to be tracked. */
+static int Watched_ID = -1;
+
+/* The next integer to use to identify an active memory block pointer. */
+static int Next_ID = -1;
+
+/* Indicates if future memory allocations are permanent (i.e. will not
+   usually be freed explicitly by AST). */
+static int Perm_Mem = 0;
+
+/* A "first in, last out" stack of Perm_Mem values used by nested
+   astBeginPM/astEndPM contexts. */
+static int PM_Stack[ PM_STACK_MAXSIZE ];
+
+/* The number of values currently in the PM_Stack array. */
+static int PM_Stack_Size = 0;
+
+/* A pointer to a double linked list holding pointers to currently active
+   memory blocks (i.e. memory blocks for which a pointer has been issued
+   but not yet freed). This does not include the memory blocks in the
+   Cache array (these are not considered to be active). */
+static Memory *Active_List = NULL;
+
+/* Should a new ID be issued each time a cached memory block is returned
+   by astMalloc? Otherwise, the same ID value is used throughout the
+   life of a memory block. */
+static int Keep_ID = 0;
+
+/* Suppress all memory use reports except for issuing and freeing? */
+static int Quiet_Use = 0;
+
+/* Report the ID of every cached block when the cache is emptied? */
+static int List_Cache = 0;
+
+/* Memory allocation at which to issue a warning. */
+static size_t Warn_Usage = 0;
+
+/* Current memory allocated by AST. */
+static size_t Current_Usage = 0;
+
+/* Peak memory allocated by AST. */
+static size_t Peak_Usage = 0;
+
+#ifdef THREAD_SAFE
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_DEBUG_MUTEX pthread_mutex_lock( &mutex2 );
+#define UNLOCK_DEBUG_MUTEX pthread_mutex_unlock( &mutex2 );
+#else
+#define LOCK_DEBUG_MUTEX
+#define UNLOCK_DEBUG_MUTEX
+#endif
+
+#endif
+
+/* Define macros for accessing all items of thread-safe global data
+   used by this module. */
+#ifdef THREAD_SAFE
+
+#define sizeof_memory astGLOBAL(Memory,Sizeof_Memory)
+#define cache astGLOBAL(Memory,Cache)
+#define cache_init astGLOBAL(Memory,Cache_Init)
+#define use_cache astGLOBAL(Memory,Use_Cache)
+
+/* Define the initial values for the global data for this module. */
+#define GLOBAL_inits \
+   globals->Sizeof_Memory = 0; \
+   globals->Cache_Init = 0; \
+   globals->Use_Cache = 0; \
+
+/* Create the global initialisation function. */
+astMAKE_INITGLOBALS(Memory)
+
+/* If thread safety is not needed, declare globals at static variables. */
+/* -------------------------------------------------------------------- */
+#else
+
+/* The size of a Memory header structure, padded to a multiple of 8
+   bytes. This value is initialised by the SizeOfMemory function, and
+   should be accessed using the SIZEOF_MEMORY macro. */
+static size_t sizeof_memory = 0;
+
+/* A cache of allocated but currently unused memory block. This cache is
+   maintained in order to avoid the overhead of continual calls to malloc to
+   allocate small blocks of memory. The vast majority of memory blocks
+   allocated by AST are under 200 bytes in size. Each element in this array
+   stores a pointer to the header for a free (i.e. allocated but currently
+   unused) memory block. The size of the memory block (not including the
+   Memory header) will equal the index at which the pointer is stored within
+   "cache". Each free memory block contains (in its Memory header) a pointer
+   to the header for another free memory block of the same size (or a NULL
+   pointer if there are no other free memory blocks of the same size). */
+static Memory *cache[ MXCSIZE + 1 ];
+
+/* Has the "cache" array been initialised? */
+static int cache_init = 0;
+
+/* Should the cache be used? */
+static int use_cache = 0;
+
+#endif
+
+/* Prototypes for Private Functions. */
+/* ================================= */
+static size_t SizeOfMemory( int * );
+static char *CheckTempStart( const char *, const char *, const char *, char *, int *, int *, int *, int *, int *, int *, int *, int * );
+static char *ChrMatcher( const char *, const char *, const char *, const char *, const char *[], int, int, int, char ***, int *, const char **, int * );
+static char *ChrSuber( const char *, const char *, const char *[], int, int, char ***, int *, const char **, int * );
+
+#ifdef MEM_DEBUG
+static void Issue( Memory *, int * );
+static void DeIssue( Memory *, int * );
+#endif
+
+#ifdef MEM_PROFILE
+static AstTimer *ReportTimer( AstTimer *, int, AstTimer **, int *, int * );
+static int CompareTimers( const void *, const void * );
+static int CompareTimers2( const void *, const void * );
+#endif
+
+/* Function implementations. */
+/* ========================= */
+char *astAppendString_( char *str1, int *nc, const char *str2, int *status ) {
+/*
+*++
+*  Name:
+*     astAppendString
+
+*  Purpose:
+*     Append a string to another string which grows dynamically.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     char *astAppendString( char *str1, int *nc, const char *str2 )
+
+*  Description:
+*     This function appends one string to another dynamically
+*     allocated string, extending the dynamic string as necessary to
+*     accommodate the new characters (plus the final null).
+
+*  Parameters:
+*     str1
+*        Pointer to the null-terminated dynamic string, whose memory
+*        has been allocated using an AST memory allocation function.
+*        If no space has yet been allocated for this string, a NULL
+*        pointer may be given and fresh space will be allocated by this
+*        function.
+*     nc
+*        Pointer to an integer containing the number of characters in
+*        the dynamic string (excluding the final null). This is used
+*        to save repeated searching of this string to determine its
+*        length and it defines the point where the new string will be
+*        appended. Its value is updated by this function to include
+*        the extra characters appended.
+*
+*        If "str1" is NULL, the initial value supplied for "*nc" will
+*        be ignored and zero will be used.
+*     str2
+*        Pointer to a constant null-terminated string, a copy of which
+*        is to be appended to "str1".
+
+*  Returned Value:
+*     astAppendString()
+*        A possibly new pointer to the dynamic string with the new string
+*        appended (its location in memory may have to change if it has to
+*        be extended, in which case the original memory is automatically
+*        freed by this function). When the string is no longer required,
+*        its memory should be freed using astFree.
+
+*  Notes:
+*     - If this function is invoked with the global error status set
+*     or if it should fail for any reason, then the returned pointer
+*     will be equal to "str1" and the dynamic string contents will be
+*     unchanged.
+*--
+*/
+
+
+/* Local Variables: */
+   char *result;                 /* Pointer value to return */
+   int len;                      /* Length of new string */
+
+/* Initialise. */
+   result = str1;
+
+/* If the first string pointer is NULL, also initialise the character
+   count to zero. */
+   if ( !str1 ) *nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK || !str2 ) return result;
+
+/* Calculate the total string length once the two strings have been
+   concatenated. */
+   len = *nc + (int) strlen( str2 );
+
+/* Extend the first (dynamic) string to the required length, including
+   a final null. Save the resulting pointer, which will be
+   returned. */
+   result = astGrow( str1, len + 1, sizeof( char ) );
+
+/* If OK, append the second string and update the total character
+   count. */
+   if ( astOK ) {
+      (void) strcpy( result + *nc, str2 );
+      *nc = len;
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+char *astAppendStringf_( char *str1, int *nc, const char *str2, ... ) {
+/*
+*++
+*  Name:
+*     astAppendStringf
+
+*  Purpose:
+*     Append a string to another string, allowing printf format specifiers.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     char *astAppendStringf( char *str1, int *nc, const char *str2, ... )
+
+*  Description:
+*     This function appends one string to another dynamically
+*     allocated string, extending the dynamic string as necessary to
+*     accommodate the new characters (plus the final null). It is the
+*     same as astAppendString, except that the "str2" string ay include
+*     printf format specifiers.
+
+*  Parameters:
+*     str1
+*        Pointer to the null-terminated dynamic string, whose memory
+*        has been allocated using an AST memory allocation function.
+*        If no space has yet been allocated for this string, a NULL
+*        pointer may be given and fresh space will be allocated by this
+*        function.
+*     nc
+*        Pointer to an integer containing the number of characters in
+*        the dynamic string (excluding the final null). This is used
+*        to save repeated searching of this string to determine its
+*        length and it defines the point where the new string will be
+*        appended. Its value is updated by this function to include
+*        the extra characters appended.
+*
+*        If "str1" is NULL, the initial value supplied for "*nc" will
+*        be ignored and zero will be used.
+*     str2
+*        Pointer to a constant null-terminated string, a copy of which
+*        is to be appended to "str1". It may contain format
+*        specifications such as used with the C "printf" family of
+*        functions.
+*     ...
+*        Additional optional arguments (as used by e.g. "printf")
+*        which specify values which are to be substituted into the "str2"
+*        string in place of any format specifications.
+
+*  Returned Value:
+*     astAppendString()
+*        A possibly new pointer to the dynamic string with the new string
+*        appended (its location in memory may have to change if it has to
+*        be extended, in which case the original memory is automatically
+*        freed by this function). When the string is no longer required,
+*        its memory should be freed using astFree.
+
+*  Notes:
+*     - If this function is invoked with the global error status set
+*     or if it should fail for any reason, then the returned pointer
+*     will be equal to "str1" and the dynamic string contents will be
+*     unchanged.
+*--
+*/
+
+/* Local Variables: */
+   char *pbuf;                   /* Pointer to buffer for expanded "str2" */
+   char *result;                 /* Pointer value to return */
+   char buf[1000];               /* A large buffer for the expanded "str2" */
+   int *status;                  /* Pointer to inherited status variable */
+   int buf_size;                 /* Size of buffer for expanded "str2" */
+   int len;                      /* Length of new string */
+   int nexp;                     /* Number of characters written to "pbuf". */
+   va_list args;                 /* Variable argument list pointer */
+
+/* Initialise. */
+   result = str1;
+
+/* If the first string pointer is NULL, also initialise the character
+   count to zero. */
+   if( !str1 ) *nc = 0;
+
+/* Get a pointer to the integer holding the inherited status value. This
+   function cannot have a "status" argument like most other functions
+   because of the variable argument list. */
+   status = astGetStatusPtr;
+
+/* Check the global error status. */
+   if ( !astOK || !str2 ) return result;
+
+/* If available use vsnprintf to determine the amount of memory needed to
+   hold the expanded version of "str2". Then allocate the required memory. */
+#if HAVE_VSNPRINTF
+   va_start( args, str2 );
+   buf_size = vsnprintf( buf, sizeof( buf ), str2, args ) + 1;
+   va_end( args );
+   pbuf = astMalloc( buf_size );
+
+/* Otherwise, all we can do is use a big buffer and hope for the best. */
+#else
+   buf_size = sizeof( buf );
+   pbuf = buf;
+#endif
+
+/* Expand any conversion specifications in "str2". */
+   va_start( args, str2 );
+   nexp = vsprintf( pbuf, str2, args );
+   va_end( args );
+
+/* Check that the result buffer did not overflow (should only be
+   possible if vsnprintf is not available). If it did, memory may
+   have been corrupted. Report the error and abort. */
+   if( nexp >= buf_size ) {
+      if( astOK ) astError( AST__ATSER, "astAppendString: Internal buffer "
+                            "overflow while appending a string - the "
+                            "result exceeds %d characters.", status,
+                            buf_size - 1 );
+   }
+
+/* Calculate the total string length once the two strings have been
+   concatenated. */
+   len = *nc + nexp;
+
+/* Extend the first (dynamic) string to the required length, including
+   a final null. Save the resulting pointer, which will be
+   returned. */
+   result = astGrow( str1, len + 1, sizeof( char ) );
+
+/* If OK, append the second string and update the total character
+   count. */
+   if ( astOK ) {
+      (void) strcpy( result + *nc, pbuf );
+      *nc = len;
+   }
+
+/* If required, free the buffer holding the expanded version of "str2". */
+#if HAVE_VSNPRINTF
+   pbuf = astFree( pbuf );
+#endif
+
+/* Return the result pointer. */
+   return result;
+}
+
+void *astCalloc_( size_t nmemb, size_t size, int *status ) {
+/*
+*++
+*  Name:
+*     astCalloc
+
+*  Purpose:
+*     Allocate and initialise memory.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void *astCalloc( size_t nmemb, size_t size )
+
+*  Description:
+*     This function allocates memory in a similar manner to the
+*     standard C "calloc" function, but with improved security
+*     (against memory leaks, etc.) and with error reporting. It also
+*     fills the allocated memory with zeros.
+*
+*     Like astMalloc, it allows zero-sized memory allocation
+*     (without error), resulting in a NULL returned pointer value.
+
+*  Parameters:
+*     nmemb
+*        The number of array elements for which memory is to be allocated.
+*     size
+*        The size of each array element, in bytes.
+
+*  Returned Value:
+*     astCalloc()
+*        If successful, the function returns a pointer to the start of
+*        the allocated memory region. If the size allocated is zero, this
+*        will be a NULL pointer.
+
+*  Notes:
+*     - A pointer value of NULL is returned if this function is
+*     invoked with the global error status set or if it fails for any
+*     reason.
+*--
+*/
+/* Local Variables: */
+   void *result;    /* Returned pointer */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Attempt to allocate and initialise the required amount of memory. */
+   result = astMallocInit( nmemb*size );
+
+/* If the above call failed due to failure of the system malloc function,
+   issue an extra error giving the number of elements and element size. */
+   if( astStatus == AST__NOMEM ) {
+      astError( AST__NOMEM, "(%lu elements, each of %lu bytes).", status,
+                (unsigned long) nmemb, (unsigned long) size );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static char *CheckTempStart( const char *template, const char *temp,
+                             const char *pattern,
+                             char *allowed, int *ntemp, int *allow,
+                             int *min_nc, int *max_nc, int *start_sub,
+                             int *end_sub, int *greedy, int *status ){
+/*
+*  Name:
+*     CheckTempStart
+
+*  Purpose:
+*     Examine the leading field in an astChrSub template.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     char *CheckTempStart( const char *template, const char *temp,
+*                           const char *pattern,
+*                           char *allowed, int *ntemp, int *allow,
+*                           int *min_nc, int *max_nc, int *start_sub,
+*                           int *end_sub, int *greedy, int *status )
+
+*  Description:
+*     This function returns inforation about the leading field in a
+*     template string supplied to astChrSub.
+
+*  Parameters:
+*     template
+*        The full template string (used for error messages).
+*     temp
+*        Pointer to the next character to read from the template string.
+*     pattern
+*        Pointer to the user supplied pattern string (only used in error
+*        messages).
+*     allowed
+*        Pointer to a buffer in which to store a string of characters
+*        that the leading temeplate field will match. A NULL pointer may
+*        be supplied in which case new memory will be allocated. The
+*        supplied memory is expanded as necessary, and a pointer to it is
+*        returned as the function value.
+*     ntemp
+*        Address of an int in which to return the number of characters
+*        consumed from the start of "temp".
+*     allow
+*        Address of an int in which to return a flag which is non-zero if
+*        the returned string contains characters that are allowed in the
+*        test field, or zero if the returned string contains characters that
+*        are disallowed in the test field.
+*     min_nc
+*        Address of an int in which to return the minimum number of
+*        test characters that must belong to the returned set of
+*        allowed characters.
+*     max_nc
+*        Address of an int in which to return the maximum number of
+*        test characters that must belong to the returned set of
+*        allowed characters.
+*     start_sub
+*        Address of an int in which to return a flag which is non-zero if
+*        the leading template field indicates the start of a field to be
+*        substituted. In this case the supplied "allowed" pointer is
+*        returned without change as the function value, "Min_nc" is
+*        returned as zero, and max_nc is returned as zero.
+*     end_sub
+*        Address of an int in which to return a flag which is non-zero if
+*        the leading template field indicates the end of a field to be
+*        substituted. In this case the supplied "allowed" pointer is
+*        returned without change as the function value, "Min_nc" is
+*        returned as zero, and limit is returned as zero.
+*     greedy
+*        Address of an int in which to return a flag which is non-zero if
+*        the template starts with a greedy quantifier.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a (possibly newly allocated) memory area holding a
+*     string of characters that the leading temeplate field will match.
+*     This string should be released using astFree when no longer needed.
+*     If a NULL pointyer is returned, then all characters are allowed
+*     (or disallowed if "*allow" is zero).
+
+*  Notes:
+*     - The returned value is also stored in the module variable
+*     sizeof_memory.
+*/
+
+/* Local Variables: */
+   char *result;
+   const char *start;
+   const char *end;
+
+/* Initialise. */
+   result = allowed;
+   *ntemp = 0;
+   *allow = 1;
+   *min_nc = 0;
+   *max_nc = 0;
+   *start_sub = 0;
+   *end_sub = 0;
+   *greedy = 1;
+
+/* Check global status */
+   if( !astOK ) return result;
+
+/* If the next character is an opening parenthesis, this marks the start
+   of a substitution field. */
+   if( *temp == '(' ) {
+      *start_sub = 1;
+      *ntemp = 1;
+
+/* If the next character is an closing parenthesis, this marks the end
+   of a substitution field. */
+   } else if( *temp == ')' ) {
+      *end_sub = 1;
+      *ntemp = 1;
+
+/* If the next character is an opening bracket, this marks the start of a
+   field of allowed or disallowed characters. */
+   } else {
+      if( *temp == '[' ) {
+
+/* If the first character in the brackets is "^" this is a field of
+   disallowed characters, otherwise they are allowed. */
+         if( temp[ 1 ] == '^' ) {
+            *allow = 0;
+            start = temp + 2;
+         } else {
+            start = temp + 1;
+         }
+
+/* Get a pointer to the closing bracket. */
+         end = strchr( temp, ']' );
+
+/* Copy the intervening string into the returned string. */
+         if( end ) {
+            result = astStore( allowed, start, end - start + 1 );
+            if( result ) result[ end - start  ] = 0;
+
+/* Report an error if no closing bracket was found. */
+         } else {
+            astError( AST__BADSUB, "Invalid pattern matching template \"%s\": "
+                      "missing ']'.", status, pattern );
+         }
+
+/* Indicate how many template characters have been used. */
+         *ntemp = end - temp + 1;
+
+/* A single dot matches any character. */
+      } else if( *temp == '.' ) {
+         result = astFree( result );
+         *ntemp = 1;
+
+/* Now deal with escape sequences. */
+      } else if( *temp == '\\' ) {
+
+/* Digits... */
+         if( temp[ 1 ] == 'd' || temp[ 1 ] == 'D' ) {
+            result = astStore( allowed, "0123456789", 11 );
+            result[ 10 ] = 0;
+            if( temp[ 1 ] == 'D' ) *allow = 0;
+
+/* White space... */
+         } else if( temp[ 1 ] == 's' || temp[ 1 ] == 'S' ) {
+            result = astStore( allowed, " 	\n\r", 5 );
+            result[ 4 ] = 0;
+            if( temp[ 1 ] == 'S' ) *allow = 0;
+
+/* Word characters... */
+         } else if( temp[ 1 ] == 'w' || temp[ 1 ] == 'W' ) {
+            result = astStore( allowed, "abcdefghijklmnopqrstuvwxyz"
+                               "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_",
+                               64 );
+            result[ 63 ] = 0;
+            if( temp[ 1 ] == 'W' ) *allow = 0;
+
+/* Any other character is treated literally. */
+         } else {
+            result = astStore( allowed, temp + 1, 2 );
+            result[ 1 ] = 0;
+         }
+
+/* Set number of template characters consumed. */
+         *ntemp = 2;
+
+/* Everything else must be matched literally. */
+      } else {
+
+         if( *temp == '*' || *temp == '?' || *temp == '+' ){
+            astError( AST__BADSUB, "Invalid pattern matching template \"%s\": "
+                      "field starts with '%c'.", status, pattern, temp[ *ntemp ] );
+         } else {
+            result = astStore( allowed, temp, 2 );
+            result[ 1 ] = 0;
+            *ntemp = 1;
+         }
+
+      }
+
+/* Now see if there is any quantifier. */
+      if( temp[ *ntemp ] == '*' ) {
+         *min_nc = 0;
+         *max_nc = INT_MAX;
+         (*ntemp)++;
+         if( temp[ *ntemp ] == '?' ){
+            *greedy = 0;
+            (*ntemp)++;
+         }
+
+      } else if( temp[ *ntemp ] == '+' ) {
+         *min_nc = 1;
+         *max_nc = INT_MAX;
+         (*ntemp)++;
+         if( temp[ *ntemp ] == '?' ){
+            *greedy = 0;
+            (*ntemp)++;
+         }
+
+      } else if( temp[ *ntemp ] == '?' ) {
+         *min_nc = 0;
+         *max_nc = 1;
+         (*ntemp)++;
+
+      } else {
+
+/* See if the remaining string starts with "{nnn}". If so, extract the
+   "nnn" and use it as the minimum and maximum field length. */
+         if( temp[ *ntemp ] == '{' ) {
+
+            start = temp + *ntemp + 1;
+            while( isdigit( (int) *start ) ) {
+               *min_nc = 10*( *min_nc ) + (int )( ( *start ) - '0' );
+               start++;
+            }
+
+            if( *start == '}' ) {
+               *max_nc = *min_nc;
+               *ntemp = start - temp + 1;
+            } else {
+               start = NULL;
+            }
+
+         } else {
+            start = NULL;
+         }
+
+/* If the remaining string does not start with "{nnn}", use a minimum and
+   maximum field length of 1. */
+         if( !start ) {
+            *min_nc = 1;
+            *max_nc = 1;
+         }
+      }
+   }
+
+/* Return the string of allowed characters. */
+   return result;
+}
+
+double astChr2Double_( const char *str, int *status ) {
+/*
+*++
+*  Name:
+*     astChr2Double
+
+*  Purpose:
+*     read a double value from a string.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     double astChr2Double( const char *str )
+
+*  Description:
+*     This function reads a double from the supplied null-terminated string,
+*     ignoring leading and trailing white space. AST__BAD is ereturned
+*     without error if the string is not a numerical value.
+
+*  Parameters:
+*     str
+*        Pointer to the string.
+
+*  Returned Value:
+*     astChr2Double()
+*       The double value, or AST__BAD.
+
+*  Notes:
+*     -  A value of AST__BAD is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   double result;     /* The returned value */
+   int ival;          /* Integer value read from string */
+   int len;           /* Length of supplied string */
+   int nc;            /* Number of characters read from the string */
+
+/* Check the global error status and supplied pointer. */
+   if ( !astOK || !str ) return AST__BAD;
+
+/* Save the length of the supplied string. */
+   len = strlen( str );
+
+/* Use scanf to read the floating point value. This fails if either 1) the
+   string does not begin with a numerical value (in which case astSscanf
+   returns zero), or 2) there are non-white characters following the
+   numerical value (in which case "nc" - the number of characters read from
+   the string - is less than the length of the string). */
+   if ( nc = 0,
+        ( 1 != astSscanf( str, " %lg %n", &result, &nc ) ) || ( nc < len ) ) {
+      result = AST__BAD;
+   }
+
+/* If the above failed, try again allowing the string to be an integer
+   followed by a dot (e.g. "1."). Some implementations of sscanf do not
+   consider this to be a floating point value. */
+   if( 1 || result == AST__BAD ) {
+      if ( nc = 0,
+           ( 1 == astSscanf( str, " %d. %n", &ival, &nc ) ) && ( nc >= len ) ) {
+         result = ival;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astChrCase_( const char *in, char *out, int upper, int blen, int *status ) {
+/*
+*++
+*  Name:
+*     astChrCase
+
+*  Purpose:
+*     Convert a string to upper or lower case.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void astChrCase( const char *in, char *out, int upper, int blen, int *status )
+
+*  Description:
+*     This function converts a supplied string to upper or lower case,
+*     storing the result in a supplied buffer. The astStringCase function
+*     is similar, but stores the result in a dynamically allocated buffer.
+
+*  Parameters:
+*     in
+*        Pointer to the null terminated string to be converted. If this
+*        is NULL, the supplied contents of the "out" string are used as
+*        the input string.
+*     out
+*        Pointer to the buffer to receive the converted string. The
+*        length of this buffer is given by "blen". If NULL is supplied
+*        for "in", then the supplied contents of "out" are converted and
+*        written back into "out" over-writing the supplied contents.
+*     upper
+*        If non-zero, the string is converted to upper case. Otherwise it
+*        is converted to lower case.
+*     blen
+*        The length of the output buffer. Ignored if "in" is NULL. No
+*        more than "blen - 1" characters will be copied from "in" to
+*        "out", and a terminating null character will then be added.
+
+*--
+*/
+
+/* Local Variables: */
+   const char *pin;
+   char *pout;
+   int i;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* The simple case of over-writing the supplied string. */
+   if( ! in ) {
+      pout = out - 1;
+      while( *(++pout) ) *pout = toupper( (int) *pout );
+
+/* If a separate output buffer has been supplied... */
+   } else {
+
+/* Initialise pointers to the input and output buffers. */
+      pin = in;
+      pout = out;
+
+/* Copy the string character by character, converting the case in the
+   process. Start counting from 1, not 0, in order to ensure that we are
+   left with room for a terminating null. */
+      for( i = 1; i < blen && *pin; i++ ) {
+         *(pout++) = toupper( (int) *(pin++) );
+      }
+
+/* Terminate the returned string. */
+      *pout = 0;
+   }
+}
+
+int astChrMatch_( const char *str1, const char *str2, int *status ) {
+/*
+*++
+*  Name:
+*     astChrMatch
+
+*  Purpose:
+*     Case insensitive string comparison.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     int astChrMatch( const char *str1, const char *str2 )
+
+*  Description:
+*     This function compares two null terminated strings for equality,
+*     discounting differences in case and any trailing white space in either
+*     string.
+
+*  Parameters:
+*     str1
+*        Pointer to the first string.
+*     str2
+*        Pointer to the second string.
+
+*  Returned Value:
+*     astChrMatch()
+*        Non-zero if the two strings match, otherwise zero.
+
+*  Notes:
+*     -  A value of zero is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   int match;                    /* Strings match? */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise. */
+   match = 1;
+
+/* Loop to compare characters in the two strings until a mis-match occurs or
+   we reach the end of the longer string. */
+   while ( match && ( *str1 || *str2 ) ) {
+
+/* Two characters match if (a) we are at the end of one string and the other
+   string contains white space or (b) both strings contain the same character
+   when converted to lower case. */
+      match = ( !*str1 && isspace( *str2 ) ) ||
+              ( !*str2 && isspace( *str1 ) ) ||
+              ( tolower( *str1 ) == tolower( *str2 ) );
+
+/* Step through each string a character at a time until its end is reached. */
+      if ( *str1 ) str1++;
+      if ( *str2 ) str2++;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+int astChrMatchN_( const char *str1, const char *str2, size_t n, int *status ) {
+/*
+*++
+*  Name:
+*     astChrMatchN
+
+*  Purpose:
+*     Case insensitive string comparison of at most N characters
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     int astChrMatchN( const char *str1, const char *str2, size_t n )
+
+*  Description:
+*     This function compares two null terminated strings for equality,
+*     discounting differences in case and any trailing white space in either
+*     string. No more than "n" characters are compared.
+
+*  Parameters:
+*     str1
+*        Pointer to the first string.
+*     str2
+*        Pointer to the second string.
+*     n
+*        Maximum number of characters to compare.
+
+*  Returned Value:
+*     astChrMatchN()
+*        Non-zero if the two strings match, otherwise zero.
+
+*  Notes:
+*     -  A value of zero is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   int match;                    /* Strings match? */
+   int nc;                       /* Number of characters compared so far */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise. */
+   match = 1;
+
+/* So far we have compared zero characters */
+   nc = 0;
+
+/* Loop to compare characters in the two strings until a mis-match occurs or
+   we reach the end of the longer string, or we reach the specified
+   maximum number of characters. */
+   while ( match && ( *str1 || *str2 ) && nc++ < n ) {
+
+/* Two characters match if (a) we are at the end of one string and the other
+   string contains white space or (b) both strings contain the same character
+   when converted to lower case. */
+      match = ( !*str1 && isspace( *str2 ) ) ||
+              ( !*str2 && isspace( *str1 ) ) ||
+              ( tolower( *str1 ) == tolower( *str2 ) );
+
+/* Step through each string a character at a time until its end is reached. */
+      if ( *str1 ) str1++;
+      if ( *str2 ) str2++;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+char **astChrSplit_( const char *str, int *n, int *status ) {
+/*
+*++
+*  Name:
+*     astChrSplit
+
+*  Purpose:
+*     Extract words from a supplied string.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     char **astChrSplit_( const char *str, int *n )
+
+*  Description:
+*     This function extracts all space-separated words form the supplied
+*     string and returns them in an array of dynamically allocated strings.
+
+*  Parameters:
+*     str
+*        Pointer to the string to be split.
+*     n
+*        Address of an int in which to return the number of words returned.
+
+*  Returned Value:
+*     astChrSplit()
+*        A pointer to a dynamically allocated array containing "*n" elements.
+*        Each element is a pointer to a dynamically allocated character
+*        string containing a word extracted from the supplied string. Each
+*        of these words will have no leading or trailing white space.
+
+*  Notes:
+*     -  A NULL pointer is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason, or if
+*     the supplied string contains no words.
+*--
+*/
+
+/* Local Variables: */
+   char **result;
+   char *w;
+   const char *p;
+   const char *ws;
+   int first;
+   int state;
+   int wl;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise. */
+   result = NULL;
+   ws = NULL;
+   *n = 0;
+
+/* State 0 is "looking for the next non-white character which marks the
+   start of the next word". State 1 is "looking for the next white character
+   which marks the end of the current word". */
+   state = 0;
+
+/* Loop through all characters in the supplied string, including the
+   terminating null. */
+   p = str - 1;
+   first = 1;
+   while( *(p++) || first ) {
+      first = 0;
+
+/* If this is the terminating null or a space, and we are currently looking
+   for the end of a word, allocate memory for the new word, copy the text
+   in, terminate it, extend the returned array by one element, and store
+   the new word in it. */
+      if( !*p || isspace( *p ) ) {
+         if( state == 1 ) {
+            wl = p - ws;
+            w = astMalloc( wl + 1 );
+            if( w ) {
+               strncpy( w, ws, wl );
+               w[ wl ] = 0;
+               result = astGrow( result, *n + 1, sizeof( char * ) );
+               if( result ) result[ (*n)++ ] = w;
+            }
+            state = 0;
+         }
+
+/* If this is non-blank character, and we are currently looking for the
+   start of a word, note the address of the start of the word, and
+   indicate that we are now looking for the end of a word. */
+      } else {
+         if( state == 0 ) {
+            state = 1;
+            ws = p;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+char **astChrSplitC_( const char *str, char c, int *n, int *status ) {
+/*
+*++
+*  Name:
+*     astChrSplitC
+
+*  Purpose:
+*     Split a string using a specified character delimiter.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     char **astChrSplitC( const char *str, char c, int *n )
+
+*  Description:
+*     This function extracts all sub-strings separated by a given
+*     character from the supplied string and returns them in an array
+*     of dynamically allocated strings. The delimiter character itself
+*     is not included in the returned strings.
+*
+*     Delimiter characters that are preceded by "\" are not used as
+*     delimiters but are included in the returned word instead (without
+*     the "\").
+
+*  Parameters:
+*     str
+*        Pointer to the string to be split.
+*     c
+*        The delimiter character.
+*     n
+*        Address of an int in which to return the number of words returned.
+
+*  Returned Value:
+*     astChrSplitC()
+*        A pointer to a dynamically allocated array containing "*n" elements.
+*        Each element is a pointer to a dynamically allocated character
+*        string containing a word extracted from the supplied string.
+
+*  Notes:
+*     -  A NULL pointer is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason, or if
+*     the supplied string contains no words.
+*--
+*/
+
+/* Local Variables: */
+   char **result;
+   char *word;
+   const char *p;
+   int escaped;
+   int wordlen;
+
+/* Initialise returned values. */
+   *n = 0;
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* More initialisations. */
+   word = NULL;
+   wordlen = 0;
+   escaped = 0;
+
+/* Loop through all characters in the supplied string, including the
+   terminating null. */
+   p = str;
+   while( *p ) {
+
+/* Is this a delimiter character? */
+      if( *p == c ) {
+
+/* If it is escaped, it does not mark the end of a word. Put it into the
+   current output buffer instead, overwriting the escape character that
+   preceded it. */
+         if( escaped ) {
+            word[ wordlen - 1 ] = c;
+
+/* The next character is not escaped. */
+            escaped = 0;
+
+/* If the delimiter is not escaped, terminate the current word and store
+   a pointer to it in the returned array. */
+         } else {
+            result = astGrow( result, *n + 1, sizeof( char * ) );
+            word = astGrow( word, wordlen + 1, 1 );
+            if( result && word ) {
+               word[ wordlen ] = 0;
+               result[ (*n)++ ] = word;
+               wordlen = 0;
+               word = NULL;
+            }
+         }
+
+/* If this is not a delimitier character, store it in the returned word. */
+      } else {
+         word = astGrow( word, wordlen + 1, 1 );
+         if( word ) word[ wordlen++ ] = *p;
+
+/* If the current character was escaped, indicate that the next character
+   is not escaped. */
+         if( escaped ) {
+            escaped = 0;
+
+/* If this character is a unescaped backslash, set a flag indicating that the
+   next character is escaped. */
+         } else if( *p == '\\' ){
+            escaped = 1;
+         }
+      }
+
+/* Move on to the next character. */
+      p++;
+   }
+
+/* Store the text following the final delimitier. */
+   result = astGrow( result, *n + 1, sizeof( char * ) );
+   word = astGrow( word, wordlen + 1, 1 );
+   if( result && word ) {
+      word[ wordlen ] = 0;
+      result[ (*n)++ ] = word;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+char **astChrSplitRE_( const char *str, const char *regexp, int *n,
+                       const char **matchend, int *status ) {
+/*
+*++
+*  Name:
+*     astChrSplitRE
+
+*  Purpose:
+*     Extract sub-strings matching a specified regular expression.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     char **astChrSplitRE( const char *str, const char *regexp, int *n,
+*                           const char **matchend )
+
+*  Description:
+*     This function compares the supplied string with the supplied
+*     regular expression. If they match, each section of the test string
+*     that corresponds to a parenthesised sub-string in the regular
+*     expression is copied and stored in the returned array.
+
+*  Parameters:
+*     str
+*        Pointer to the string to be split.
+*     regexp
+*        The regular expression. See "Template Syntax:" in the astChrSub
+*        prologue. Note, this function differs from astChrSub in that any
+*        equals signs (=) in the regular expression are treated literally.
+*     n
+*        Address of an int in which to return the number of sub-strings
+*        returned.
+*     matchend
+*        A pointer to a location at which to return a pointer to the
+*        character that follows the last character within the supplied test
+*        string that matched any parenthesises sub-section of "regexp". A
+*        NULL pointer is returned if no matches were found. A NULL pointer
+*        may be supplied if the location of the last matching character is
+*        not needed.
+
+*  Returned Value:
+*     astChrSplitRE()
+*        A pointer to a dynamically allocated array containing "*n" elements.
+*        Each element is a pointer to a dynamically allocated character
+*        string containing a sub-string extracted from the supplied string.
+*        The array itself, and the strings within it, should all be freed
+*        using astFree when no longer needed.
+
+*  Notes:
+*     - If a parenthesised sub-string in the regular expression is matched
+*     by more than one sub-string within the test string, then only the
+*     first is returned. To return multiple matches, the regular
+*     expression should include multiple copies of the parenthesised
+*     sub-string (for instance, separated by ".+?" if the intervening
+*     string is immaterial).
+*     -  A NULL pointer is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason, or if
+*     the supplied string contains no words.
+*--
+*/
+
+/* Local Variables: */
+   char **result;
+   char *temp;
+   int i;
+
+/* Initialise returned values. */
+   *n = 0;
+   result = NULL;
+
+/* Check global status */
+   if( !astOK ) return result;
+
+/* Call ChrSuber to do the work, saving the matching parts of the test
+   string. */
+   temp = ChrSuber( str, regexp, NULL, 0, 1, &result, n, matchend, status );
+   if( temp ) {
+      temp = astFree( temp );
+
+/* Free all results if no match was found. */
+   } else if( result ) {
+      for( i = 0; i < *n; i++ ) result[ i ] = astFree( result[ i ] );
+      result = astFree( result );
+      *n = 0;
+   }
+
+/* Return the result */
+   return result;
+}
+
+char *ChrSuber( const char *test, const char *pattern, const char *subs[],
+                int nsub, int ignore_equals, char ***parts, int *npart,
+                const char **matchend, int *status ){
+/*
+*  Name:
+*     ChrSuber
+
+*  Purpose:
+*     Performs substitutions on a supplied string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     char *ChrSuber( const char *test, const char *pattern,
+*                     const char *subs[], int nsub, int ignore_equals,
+*                     char ***parts, int *npart, const char **matchend,
+*                     int *status )
+
+*  Description:
+*     This function performs the work for astChrSub and astChrSplitRE.
+
+*  Parameters:
+*     test
+*        The string to be tested.
+*     pattern
+*        The template string. See "Template Syntax:" in the astChrSub
+         prologue.
+*     subs
+*        An array of strings that are to replace the sections of the test
+*        string that match each parenthesised sub-string in "pattern". The
+*        first element of "subs" replaces the part of the test string that
+*        matches the first parenthesised sub-string in the template, etc.
+*
+*        If "nsub" is zero, then the "subs" pointer is ignored. In this
+*        case, and if parameter "ignore_equals" is zero, substitution strings
+*        may be specified by appended them to the end of the "pattern" string,
+*        separated by "=" characters
+*     nsub
+*        The number of substitution strings supplied in array "subs".
+*     ignore_equals
+*        If non-zero, any equals signs in the supplied pattern are
+*        treated literally, rather than being used to split the template
+*        from any substitution strigs.
+*     parts
+*        Address of a location at which to return a pointer to an array
+*        of character string pointers. The strings are the sub-sections
+*        of "test" that matched the parenthesised sub-sections of
+*        "template". The array will have "*npart" elements. Ignored if NULL.
+*     npart
+*        Address of a location at which to return the length of the
+*        "parts" array. Ignored if "parts" is NULL.
+*     matchend
+*        A pointer to a location at which to return a pointer to the
+*        character that follows the last character within the supplied test
+*        string that matched any parenthesises sub-section of "regexp". A
+*        NULL pointer is returned if no matches were found. A NULL pointer
+*        may be supplied if the location of the last matching character is
+*        not needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated string holding the result
+*     of the substitutions, or NULL if the test string does not match
+*     the template string. This string should be freed using astFree
+*     when no longer needed. If no substituions are specified then a
+*     copy of the test string is returned if it matches the template.
+
+*  Notes:
+*     -  A NULL pointer is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason, or if
+*     the supplied test string does not match the template.
+
+*/
+
+/* Local Variables: */
+   char **sections;
+   char **temps;
+   char *cptr;
+   char *result;
+   char *temp;
+   char *template;
+   int i;
+   int nsec;
+   int ntemp;
+   size_t tlen;
+
+/* Initialise */
+   result = NULL;
+   if( parts ) *npart = 0;
+
+/* Check global status */
+   if( !astOK ) return result;
+
+/* If required, split the total "pattern" string into sections, using
+   (unescaped) "=" characters as the delimiter. The first section is the
+   actual template, and each subsequent section (if any) holds a
+   substitution string. */
+   if( ! ignore_equals ) {
+      sections = astChrSplitC( pattern, '=', &nsec );
+
+/* If equals signs are being treated literally, just take a copy of the
+   supplied pattern. */
+   } else {
+      cptr = astStore( NULL, pattern, strlen( pattern ) + 1 );
+      sections = &cptr;
+      nsec = 1;
+   }
+
+   if( sections ) {
+
+/* If the caller did not provide any substitution strings, use the ones
+   appended to the end of the pattern string (if any). */
+      if( nsub == 0 ) {
+         subs = (void *) ( sections + 1 );
+         nsub = nsec - 1;
+      }
+
+/* Split the template section into sub-sections, using (unescaped) "|"
+   characters as the delimiter. Each sub-section is an alternate pattern
+   matching template. */
+      temps = astChrSplitC( sections[ 0 ], '|', &ntemp );
+
+   } else {
+      temps = 0;
+      ntemp = 0;
+   }
+
+/* Loop round each template until all templates have been checked or a
+   match occurs.. */
+   for( i = 0; i < ntemp && !result; i++ ) {
+      temp = temps[ i ];
+      tlen = strlen( temp );
+
+/* If the template starts with "^" or "(^", remove the "^" character.
+   Otherwise insert ".*?" at the start. Allocate three extra characters
+   in case we later need to add ".*?" to the end of the string. */
+      if( temp[ 0 ] == '^' ) {
+         template = astMalloc( tlen + 3 );
+         if( template ) {
+            strcpy( template, temp + 1 );
+            tlen--;
+         }
+
+      } else if( temp[ 0 ] == '(' && temp[ 1 ] == '^') {
+         template = astMalloc( tlen + 3 );
+         if( template ) {
+            template[ 0 ] = '(';
+            strcpy( template + 1, temp + 2 );
+            tlen--;
+         }
+
+      } else {
+         template = astMalloc( tlen + 7 );
+         if( template ) {
+            template[ 0 ] = '.';
+            template[ 1 ] = '*';
+            template[ 2 ] = '?';
+            strcpy( template + 3, temp );
+            tlen += 3;
+         }
+      }
+
+/* If the pattern ends with "$" or "$)", remove the "$" character. Otherwise
+   insert ".*?" at the end. */
+      if( template[ tlen - 1 ] == '$' ) {
+         tlen--;
+
+      } else if( template[ tlen - 2 ] == '$' && template[ tlen - 1 ] == ')' ) {
+         template[ tlen - 2 ] = ')';
+         tlen--;
+
+      } else {
+         template[ tlen ] = '.';
+         template[ tlen + 1 ] = '*';
+         template[ tlen + 2 ] = '?';
+         tlen += 3;
+      }
+
+/* Ensure the string is terminated */
+      template[ tlen ] = 0;
+
+/* See if the test string matches the current template. */
+      result = ChrMatcher( test, test + strlen( test ), template, pattern,
+                           subs, nsub, 0, 1, parts, npart, matchend, status );
+
+/* Free resources. */
+      template = astFree( template );
+   }
+
+   if( temps ) {
+      for( i = 0; i < ntemp; i++ ) temps[ i ] = astFree( temps[ i ] );
+      temps = astFree( temps );
+   }
+
+   if( sections ) {
+      for( i = 0; i < nsec; i++ ) sections[ i ] = astFree( sections[ i ] );
+      if( ! ignore_equals ) sections = astFree( sections );
+   }
+
+/* Return a NULL pointer if an error has occurred. */
+   if( !astOK ) result = astFree( result );
+
+/* Return the result */
+   return result;
+}
+
+char *astChrSub_( const char *test, const char *pattern, const char *subs[],
+                  int nsub, int *status ){
+/*
+*++
+*  Name:
+c     astChrSub
+f     AST_CHRSUB
+
+*  Purpose:
+*     Performs substitutions on a supplied string.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "memory.h"
+c     char *astChrSub( const char *test, const char *pattern,
+c                      const char *subs[], int nsub )
+f     MATCH = AST_CHRSUB( TEST, PATTERN, RESULT, STATUS )
+
+*  Description:
+*     This function checks a supplied test string to see if it matches a
+*     supplied template. If it does, specified sub-sections of the test
+*     string may optionally be replaced by supplied substitution strings.
+*     The resulting string is returned.
+
+*  Parameters:
+c     test
+f     TEST = CHARACTER * ( * ) (Given)
+*        The string to be tested.
+*     pattern
+f     PATTERN = CHARACTER * ( * ) (Given)
+*        The template string. See "Template Syntax:" below.
+*     subs
+*        An array of strings that are to replace the sections of the test
+*        string that match each parenthesised sub-string in "pattern". The
+*        first element of "subs" replaces the part of the test string that
+*        matches the first parenthesised sub-string in the template, etc.
+*
+*        If "nsub" is zero, then the "subs" pointer is ignored. In this
+*        case, substitution strings may be specified by appended them to
+*        the end of the "pattern" string, separated by "=" characters.
+*        Note, if you need to include a literal "=" character in the
+*        pattern, precede it by an escape "\" character.
+*     nsub
+*        The number of substitution strings supplied in array "subs".
+f     RESULT = CHARACTER * ( * ) (Returned)
+f        Returned holding the result of the substitutions. If the test
+f        string does not match the template, then a blank string is
+f        returned.
+
+*  Returned Value:
+c     astChrSub()
+c        A pointer to a dynamically allocated string holding the result
+c        of the substitutions, or NULL if the test string does not match
+c        the template string. This string should be freed using astFree
+c        when no longer needed. If no substituions are specified then a
+c        copy of the test string is returned if it matches the template.
+f     AST_CHRSUB = LOGICAL
+f        .TRUE. if the test string matched the supplied template, and
+f        .FALSE. otherwise.
+
+*  Template Syntax:
+*     The template syntax is a minimal form of regular expression, The
+*     quantifiers allowed are "*", "?", "+", "{n}", "*?" and "+?" (the
+*     last two are non-greedy - they match the minimum length possible
+*     that still gives an overall match to the template). The only
+*     constraints allowed are "^" and "$". The following atoms are allowed:
+*
+*     - [chars]: Matches any of the specified characters.
+*
+*     - [^chars]: Matches anything but the specified characters.
+*
+*     - .: Matches any single character.
+*
+*     - x: Matches the character x so long as x has no other significance.
+*
+*     - \x: Always matches the character x (except for [dDsSwW]).
+*
+*     - \d: Matches a single digit.
+*
+*     - \D: Matches anything but a single digit.
+*
+*     - \w: Matches any alphanumeric character, and "_".
+*
+*     - \W: Matches anything but alphanumeric characters, and "_".
+*
+*     - \s: Matches white space.
+*
+*     - \S: Matches anything but white space.
+*
+*     Note, minus signs ("-") within brackets have no special significance,
+*     so ranges of characters must be specified explicitly.
+*
+*     Multiple template strings can be concatenated, using the "|"
+*     character to separate them. The test string is compared against
+*     each one in turn until a match is found.
+*
+c     Parentheses are used within each template to identify sub-strings
+c     that are to be replaced by the strings supplied in "sub".
+c
+c     If "nsub" is supplied as zero, then substitution strings may be
+c     specified by appended them to the end of the "pattern" string,
+c     separated by "=" characters. If "nsub" is not zero, then any
+c     substitution strings appended to the end of "pattern" are ignored.
+f
+f     Parentheses are used within each template to identify sub-strings
+f     that are to be replaced by new strings. The new strings are
+f     specified by appended them to the end of the "pattern" string,
+f     separated by "=" characters.
+*
+c     Each element of "subs"
+f     Each new string
+*     may contain a reference to a token of the
+*     form "$1", "$2", etc. The "$1" token will be replaced by the part
+*     of the test string that matched the first parenthesised sub-string
+*     in "pattern". The "$2" token will be replaced by the part of the
+*     test string that matched the second parenthesised sub-string in
+*     "pattern", etc.
+*
+
+c  Notes:
+c     -  A NULL pointer is returned if this function is invoked with the
+c     global error status set or if it should fail for any reason, or if
+c     the supplied test string does not match the template.
+
+*--
+*/
+
+/* Call ChrSuber to do the work, without saving the matching parts of the
+   test string. */
+   return ChrSuber( test, pattern, subs, nsub, 0, NULL, NULL, NULL, status );
+}
+
+void astChrTrunc_( char *text, int *status ){
+/*
+*++
+*  Name:
+*     astChrTrunc
+
+*  Purpose:
+*     Terminate a string to exclude trailing spaces.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void astChrTrunc( char *text )
+
+*  Description:
+*     This function pokes a null character into the supplied string to
+*     remove any trailing spaces.
+
+*  Parameters:
+*     text
+*        The string to be truncated.
+
+*--
+*/
+   if( !text ) return;
+   text[ astChrLen( text ) ] = 0;
+}
+
+void *astFree_( void *ptr, int *status ) {
+/*
+*++
+*  Name:
+*     astFree
+
+*  Purpose:
+*     Free previously allocated memory.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void *astFree( void *ptr )
+
+*  Description:
+*     This function frees memory that has previouly been dynamically
+*     allocated using one of the AST memory function.
+
+*  Parameters:
+*     ptr
+*        Pointer to previously allocated memory. An error will result
+*        if the memory has not previously been allocated by another
+*        function in this module. However, a NULL pointer value is
+*        accepted (without error) as indicating that no memory has yet
+*        been allocated, so that no action is required.
+
+*  Returned Value:
+*     astFree()
+*        Always returns a NULL pointer.
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   Memory *mem;                  /* Pointer to memory header */
+   int isdynamic;                /* Is the memory dynamically allocated? */
+   size_t size;                  /* The usable size of the memory block */
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* If the incoming pointer is NULL, do nothing. Otherwise, check if it
+   points at dynamically allocated memory (IsDynamic sets the global
+   error status if it does not). */
+   if( ptr ) {
+      IS_DYNAMIC( ptr, isdynamic );
+   } else {
+      isdynamic = 0;
+   }
+   if ( isdynamic ) {
+
+/* If OK, obtain a pointer to the memory header. */
+      mem = (Memory *) ( (char *) ptr - SIZEOF_MEMORY );
+
+#ifdef MEM_DEBUG
+      DeIssue( mem, status );
+#endif
+
+/* If the memory block is small enough, and the cache is being used, put it
+   into the cache rather than freeing it, so that it can be reused. */
+      size = mem->size;
+      if( use_cache && size <= MXCSIZE ) {
+         mem->next = cache[ size ];
+         cache[ size ] = mem;
+
+/* Set the size to zero to indicate that the memory block has been freed.
+   The size of the block is implied by the Cache element it is stored in. */
+         mem->size = (size_t) 0;
+
+/* Simply free other memory blocks, clearing the "magic number" and size
+   values it contains. This helps prevent accidental re-use of the memory. */
+      } else {
+         mem->magic = (unsigned long) 0;
+         mem->size = (size_t) 0;
+
+/* Free the allocated memory. */
+         FREE( mem );
+      }
+   }
+
+/* Always return a NULL pointer. */
+   return NULL;
+
+}
+
+void *astFreeDouble_( void *ptr, int *status ) {
+/*
+*++
+*  Name:
+*     astFreeDouble
+
+*  Purpose:
+*     Free previously double allocated memory.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void *astFreeDouble( void *ptr )
+
+*  Description:
+*     This function frees memory that has previouly been dynamically
+*     allocated using one of the AST memory function. It assumes that
+*     the supplied pointer is a pointer to an array of pointers. Each
+*     of these pointers is first freed, and then the supplied pointer
+*     is freed.
+*
+*     Note, this routine should not be used with arrays allocated
+*     by astGrow since astGrow over-allocates and so there may be
+*     non-initialised pointers at the end of the array.
+
+*  Parameters:
+*     ptr
+*        Pointer to previously allocated memory. An error will result
+*        if the memory has not previously been allocated by another
+*        function in this module. However, a NULL pointer value is
+*        accepted (without error) as indicating that no memory has yet
+*        been allocated, so that no action is required.
+
+*  Returned Value:
+*     astFreeDouble()
+*        Always returns a NULL pointer.
+
+*--
+*/
+
+/* Local Variables: */
+   int iptr;                     /* Index of sub-pointer */
+   int nptr;                     /* Number of sub-pointers */
+   size_t size;                  /* The usable size of the memory block */
+   void **ptrs;                  /* Pointer to array of pointers */
+
+/* Check a pointer was supplied. */
+   if( ! ptr ) return NULL;
+
+/* Get the size of the memory area. */
+   size = astSizeOf( ptr );
+
+/* Get the number of points this amount of memory could hold. */
+   nptr = size/sizeof( void * );
+
+/* Report an error if the size is not an integer multiple of an address
+   size. */
+   if( nptr*sizeof( void * ) != size ) {
+      astError( AST__MEMIN, "Invalid attempt to free double allocated "
+                "memory: the supplied memory size (%lu bytes) is not "
+                "an integer multiple of %lu.", status, size,
+                sizeof( void * ) );
+
+   } else {
+
+/* Free each sub-pointer. */
+      ptrs = (void **) ptr;
+      for( iptr = 0; iptr < nptr; iptr++ ) {
+         ptrs[ iptr ] = astFree(  ptrs[ iptr ] );
+      }
+
+/* Free the supplied pointer. */
+      ptr = astFree( ptr );
+   }
+
+/* Always return a NULL pointer. */
+   return NULL;
+}
+
+void *astGrow_( void *ptr, int n, size_t size, int *status ) {
+/*
+*++
+*  Name:
+*     astGrow
+
+*  Purpose:
+*     Allocate memory for an adjustable array.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void *astGrow( void *ptr, int n, size_t size )
+
+*  Description:
+*     This function allocates memory in which to store an array of
+*     data whose eventual size is unknown. It should be invoked
+*     whenever a new array size is determined and will appropriately
+*     increase the amount of memory allocated when necessary. In
+*     general, it will over-allocate in anticipation of future growth
+*     so that the amount of memory does not need adjusting on every
+*     invocation.
+
+*  Parameters:
+*     ptr
+*        Pointer to previously allocated memory (or NULL if none has
+*        yet been allocated).
+*     n
+*        Number of array elements to be stored (may be zero).
+*     size
+*        The size of each array element.
+
+*  Returned Value:
+*     astGrow()
+*        If the memory was allocated successfully, a pointer to the start
+*        of the possibly new memory region is returned (this may be the
+*        same as the original pointer).
+
+*  Notes:
+*     - When new memory is allocated, the existing contents are preserved.
+*     - This function does not free memory once it is allocated, so
+*     the size allocated grows to accommodate the maximum size of the
+*     array (or "high water mark"). Other memory handling routines may
+*     be used to free the memory (or alter its size) if necessary.
+*     - If this function is invoked with the global error status set,
+*     or if it fails for any reason, the original pointer value is
+*     returned and the memory contents are unchanged.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   int isdynamic;                /* Is the memory dynamically allocated? */
+   Memory *mem;                  /* Pointer to memory header */
+   size_t newsize;               /* New size to allocate */
+   void *new;                    /* Result pointer */
+
+/* Check the global error status. */
+   if ( !astOK ) return ptr;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   new = ptr;
+
+/* Calculate the total size of memory needed. */
+   size *= (size_t) n;
+
+/* If no memory has yet been allocated, allocate exactly the amount
+   required. */
+   if ( !ptr ) {
+      new = astMalloc( size );
+
+/* Otherwise, check that the incoming pointer identifies previously
+   allocated memory. */
+   } else {
+      IS_DYNAMIC( ptr, isdynamic );
+      if ( isdynamic ) {
+
+/* Obtain a pointer to the memory header and check if the new size
+   exceeds that already allocated. */
+         mem = (Memory *) ( (char *) ptr - SIZEOF_MEMORY );
+         if ( mem->size < size ) {
+
+/* If so, calculate a possible new size by doubling the old
+   size. Increase this further if necessary. */
+            newsize = mem->size * ( (size_t) 2 );
+            if ( size > newsize ) newsize = size;
+
+/* Re-allocate the memory. */
+            new = astRealloc( ptr, newsize );
+         }
+      }
+   }
+
+/* Return the result. */
+   return new;
+}
+
+int astIsDynamic_( const void *ptr, int *status ) {
+/*
+*++
+*  Name:
+*     astIsDynamic
+
+*  Purpose:
+*     Returns a flag indicating if memory was allocated dynamically.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     int astIsDynamic_( const void *ptr )
+
+*  Description:
+*     This function takes a pointer to a region of memory and tests if
+*     the memory has previously been dynamically allocated using other
+*     functions from this module. It does this by checking for the
+*     presence of a "magic" number in the header which precedes the
+*     allocated memory. If the magic number is not present (or the
+*     pointer is invalid for any other reason), zero is returned.
+*     Otherwise 1 is returned.
+
+*  Parameters:
+*     ptr
+*        Pointer to test.
+
+*  Returned Value:
+*     astIsDynamic()
+*        Non-zero if the memory was allocated dynamically. Zero is returned
+*        if the supplied pointer is NULL.
+
+*  Notes:
+*     - A value of zero is returned if this function is invoked with
+*     the global error status set, or if it fails for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   Memory *isdynmem;               /* Pointer to memory header */ \
+
+/* Check the global error status and the supplied pointer. */
+   if ( !astOK || ! ptr ) return 0;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Derive a pointer to the memory header that precedes the
+   supplied region of memory. */
+   isdynmem = (Memory *) ( (char *) ptr - SIZEOF_MEMORY );
+
+/* Check if the "magic number" in the header is valid, returning non-zero
+   if it is. */
+   return ( isdynmem->magic == MAGIC( isdynmem, isdynmem->size ) );
+}
+
+void *astMalloc_( size_t size, int init, int *status ) {
+/*
+*++
+*  Name:
+*     astMalloc
+
+*  Purpose:
+*     Allocate memory.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void *astMalloc( size_t size )
+
+*  Description:
+*     This function allocates memory in a similar manner to the
+*     standard C "malloc" function, but with improved security
+*     (against memory leaks, etc.) and with error reporting. It also
+*     allows zero-sized memory allocation (without error), resulting
+*     in a NULL returned pointer value.
+
+*  Parameters:
+*     size
+*        The size of the memory region required (may be zero).
+
+*  Returned Value:
+*     astMalloc()
+*        If successful, the function returns a pointer to the start of
+*        the allocated memory region. If the size allocated is zero, this
+*        will be a NULL pointer.
+
+*  Notes:
+*     - A pointer value of NULL is returned if this function is
+*     invoked with the global error status set or if it fails for any
+*     reason.
+*--
+
+*  astMallocInit:
+*     - This function can be invoked using either the public astMalloc
+*     macro documented above, or the private astMallocInit macro.
+*     astMallocInit has the same interface as astMalloc, but calls calloc
+*     rather than malloc so that the allocated memory is filled with zeros.
+*     Ideally, we should use an extra layer in the calling heirarchy to
+*     remove the hidden "init" argument in the astMalloc_ interface, but
+*     astMalloc is time-critical in many situations and so it is included
+*     as a "hidden" argument.
+
+*/
+
+/* Local Constants: */
+#define ERRBUF_LEN 80
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   char errbuf[ ERRBUF_LEN ];    /* Buffer for system error message */
+   char *errstat;                /* Pointer to system error message */
+   Memory *mem;                  /* Pointer to space allocated by malloc */
+   void *result;                 /* Returned pointer */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check that the size requested is not negative and report an error
+   if it is. */
+   if ( size < (size_t) 0 ) {
+      astError( AST__MEMIN,
+                "Invalid attempt to allocate %lu bytes of memory.", status,
+                (unsigned long) size );
+
+/* Otherwise, if the size is greater than zero, either get a previously
+   allocated memory block from the cache, or attempt to use malloc
+   to allocate the memory, including space for the header structure. */
+   } else if ( size != (size_t ) 0 ) {
+
+/* If the cache is being used and a cached memory block of the required size
+   is available, remove it from the cache array and use it. */
+      mem = ( use_cache && size <= MXCSIZE ) ? cache[ size ] : NULL;
+      if( mem ) {
+         cache[ size ] = mem->next;
+         mem->next = NULL;
+         mem->size = (size_t) size;
+
+/* Initialise the memory (but not the header) if required. */
+         if( init ) (void) memset( (char *) mem + SIZEOF_MEMORY, 0, size );
+
+/* Otherwise, allocate a new memory block using "malloc" or "calloc". */
+      } else {
+         if( init ) {
+            mem = CALLOC( 1, SIZEOF_MEMORY + size );
+         } else {
+            mem = MALLOC( SIZEOF_MEMORY + size );
+         }
+
+/* Report an error if malloc failed. */
+         if ( !mem ) {
+
+#if HAVE_STRERROR_R
+            strerror_r( errno, errbuf, ERRBUF_LEN );
+            errstat = errbuf;
+#else
+            errstat = strerror( errno );
+#endif
+            astError( AST__NOMEM, "malloc: %s", status, errstat );
+            astError( AST__NOMEM, "Failed to allocate %lu bytes of memory.", status,
+                      (unsigned long) size );
+
+/* If successful, set the "magic number" in the header and also store
+   the size. */
+         } else {
+            mem->magic = MAGIC( mem, size );
+            mem->size = size;
+            mem->next = NULL;
+
+#ifdef MEM_DEBUG
+            mem->id = -1;
+            mem->prev = NULL;
+#endif
+
+         }
+      }
+
+/* Do nothing more if no memory is being returned. */
+      if( mem ) {
+
+#ifdef MEM_DEBUG
+      Issue( mem, status );
+#endif
+
+/* Increment the memory pointer to the start of the region of
+   allocated memory to be used by the caller.*/
+         result = mem;
+         result = (char *) result + SIZEOF_MEMORY;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+#undef ERRBUF_LEN
+
+static char *ChrMatcher( const char *test, const char *end, const char *template,
+                         const char *pattern, const char *subs[], int nsub,
+                         int ignore, int expdoll, char ***mres, int *mlen,
+                         const char **matchend, int *status ){
+/*
+*  Name:
+*     ChrMatcher
+
+*  Purpose:
+*     Performs substitutions on a supplied string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     char *ChrMatcher( const char *test, const char *end, const char *template,
+*                       const char *pattern, const char *subs[], int nsub,
+*                       int ignore, int expdoll, char ***mres, int *mlen,
+*                       const char **matchend, int *status )
+
+*  Description:
+*     This function is performs most of the work for astChrSub.
+
+*  Parameters:
+*     test
+*        The string to be tested.
+*     end
+*        Pointer to the terminating null character at the end of "test".
+*     template
+*        The template string. See astChrSub for details.
+*     pattern
+*        The user supplied "pattern" string (only used for error messages).
+*     subs
+*        An array of strings holding the values that are to be substituted
+*        into each parenthesised substring in "test".
+*     nsub
+*        The length of the subs arrays.
+*     ignore
+*        If non-zero, then no substitutions are performed, and any
+*        inbalance in parentheses is ignored.
+*     expdoll
+*        If non-zero, then any "$1", "$2", etc, tokens in the
+*        substitution fields will be repalced by the corresponding fields
+*        in the test string.
+*     mres
+*        Address of a location at which to return a pointer to an array
+*        of character string pointers. The strings are the sub-sections
+*        of "test" that matched the parenthesised sub-sections of
+*        "template". The array will have "*m" elements. Ignored if NULL.
+*     mlen
+*        Address of a location at which to return the length of the
+*        returned "mres" array. Ignored if "mres" is NULL.
+*     matchend
+*        A pointer to a location at which to return a pointer to the
+*        character that follows the last character within the supplied test
+*        string that matched any parenthesises sub-section of "regexp". A
+*        NULL pointer is returned if no matches were found. A NULL pointer
+*        may be supplied if the location of the last matching character is
+*        not needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated string holding the result of the
+*     substitutions, or NULL if the test string does not match the template
+*     string. This string should be freed using astFree when no longer
+*     needed.
+
+*  Notes:
+*     -  A NULL pointer is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason, or if
+*     the supplied test string does not match the template.
+*/
+
+/* Local Variables: */
+   char **matches;
+   char **newsubs;
+   char **parts;
+   char *allowed;
+   char *r;
+   char *result;
+   char *sres;
+   char *stest;
+   char stemp[10];
+   const char *aaa;
+   const char *aa;
+   const char *a;
+   const char *b;
+   int allow;
+   int dollar;
+   int end_sub;
+   int greedy;
+   int i;
+   int in_sub;
+   int ipart;
+   int match;
+   int matchlen;
+   int max_na;
+   int min_na;
+   int na;
+   int nb;
+   int nmatch;
+   int npart;
+   int partlen;
+   int reslen;
+   int start_sub;
+   size_t stl;
+
+/* Initialisation. */
+   if( mres ) *mlen = 0;
+   aaa = NULL;
+   if( matchend ) *matchend = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return NULL;
+
+/* more initialisation. */
+   result = NULL;
+   allowed = NULL;
+
+/* Get memory for a set of pointers to copies of the test sub-strings that
+   fall between the sub-strings being replaced. */
+   parts = astMalloc( sizeof( char *)*(size_t) ( nsub + 1 ) );
+
+/* Get memory for a set of pointers to copies of the test sub-strings that
+   match the parenthesised sub-strings in the template. */
+   matches = astMalloc( sizeof( char *)*(size_t) nsub );
+
+/* Initialise pointers to the next test and template characters to read. */
+   a = test;
+   b = template;
+
+/* Initialise the pointer to the start of the previous test character */
+   aa = test;
+
+/* Assume the test string matches the template. */
+   match = 1;
+
+/* The template pointer is not currently in a substitution field. */
+   in_sub = 0;
+
+/* Initialise the number of substitution fields found so far. */
+   npart = 0;
+   nmatch = 0;
+
+/* Loop until we have reached the end of either the test or template
+   string. We break out of the loop early if we find that the test string
+   does not match the template string. */
+   while( match && *a && *b ) {
+
+/* Examine the string at the start of the template string. This returns a
+   string of allowed (or disallowed) characters that the next test character
+   can match, the number of template characters consumed, the minimum number
+   of test characters that must match the allowed character set, and a flag
+   indicating if the number of matching test characters can exceed the
+   minimum number or must be exactly equal to the minimum number.  */
+      allowed = CheckTempStart( template, b, pattern, allowed, &nb, &allow,
+                                &min_na, &max_na, &start_sub, &end_sub,
+                                &greedy, status );
+      if( !astOK ) break;
+
+/* Increment the the pointer to the next template character. */
+      b += nb;
+
+/* If the leading field in the template indicates the start of a
+   substitution field, record the test string up to the current point. */
+      if( start_sub ){
+
+/* Do nothing if we are ignoring substitutions. */
+         if( ! ignore ){
+
+/* Store a pointer to the first test character that matches the
+   substitution template. */
+            aaa = a;
+
+/* Report an error and abort if we are already inside a substitution
+   field */
+            if( in_sub ) {
+               astError( AST__BADSUB, "Invalid pattern matching template \"%s\": "
+                         "missing ')'.", status, pattern );
+               break;
+            }
+
+/* Indicate that we are now in a substitution field. */
+            in_sub = 1;
+
+/* If possible, store a copy of the test string that started at the end
+   of the previous substitution field and ends at the current point.
+   First ensure the "parts" array is large enough since the string may
+   contain more than "nsub" parenthesised sub-strings. */
+            parts = astGrow( parts, npart + 1, sizeof( char * ) );
+            if( parts ) {
+               partlen = ( a - aa );
+               parts[ npart ] = astStore( NULL, aa, partlen + 1 );
+               if( parts[ npart ] ) {
+                  parts[ npart ][ partlen ] = 0;
+                  npart++;
+               }
+            }
+         }
+
+/* If the leading field in the template indicates the end of a
+   substitution field, initialise the start of the next part of the test
+   string. */
+      } else if( end_sub ){
+
+/* Do nothing if we are ignoring substitutions. */
+         if( ! ignore ){
+
+/* Report an error and abort if we are not currently in a substitution
+   field. */
+            if( ! in_sub ) {
+               astError( AST__BADSUB, "Invalid pattern matching template \"%s\": "
+                         "missing '('.", status, pattern );
+               break;
+            }
+
+/* We are no longer in a substitution field. */
+            in_sub = 0;
+
+/* If possible, store a copy of the test string that matched the
+   substitution template. */
+            matches = astGrow( matches, nmatch + 1, sizeof( char * ) );
+            if( matches ) {
+               matchlen = ( a - aaa );
+               matches[ nmatch ] = astStore( NULL, aaa, matchlen + 1 );
+               if( matches[ nmatch ] ) {
+                  matches[ nmatch ][ matchlen ] = 0;
+                  nmatch++;
+                  if( matchend ) *matchend = a;
+               }
+            }
+
+/* Record the start of the next test string part. */
+            aa = a;
+         }
+
+/* Otherwise, find how many characters at the front of the test string
+   match the leading field in the template. Find the number of leading
+   characters in the test string that are contained in the set of
+   characters allowed by the leading field in the template. */
+      } else {
+         if( !allowed ) {
+            na = strlen( a );
+
+         } else if( allow ) {
+            na = strspn( a, allowed );
+
+         } else {
+            na = strcspn( a, allowed );
+         }
+
+/* Check that the minmum number of matching characters is available. */
+         if( na < min_na ){
+            match = 0;
+            break;
+         }
+
+/* Dont match more characters than are needed. */
+         if( na > max_na ) na = max_na;
+
+/* We cannot match more characters than are available. */
+         if( na < max_na ) max_na = na;
+
+/* If we have exhausted the template, match the maximum number of
+   characters. */
+         if( ! *b ) {
+            na = max_na;
+
+/* If we still have a match, we may choose to use fewer than the max
+   allowed number of test characters in order to allow the next template
+   field to be matched. Don't need to do this if we have reached the end
+   of the template. */
+         } else if( max_na > min_na ) {
+            match = 0;
+
+/* If a greedy quantifier was used, try using a decreasing number of test
+   characters, starting at the maximum allowed and decreasing down to the
+   minimum, until a number is found which allows the rest of the string
+   to be matched. */
+            if( greedy ) {
+               for( na = max_na; na >= min_na; na-- ) {
+                  r = ChrMatcher( a + na, end, b, pattern, NULL, 0, 1, 0,
+                                  NULL, NULL, NULL, status );
+                  if( r ) {
+                     match = 1;
+                     r = astFree( r );
+                     break;
+                  }
+               }
+
+/* If a non-greedy quantifier was used, try using an increasing number of
+   test characters, starting at the minimum allowed and increasing up to
+   the maximum, until a number is found which allows the rest of the string
+   to be matched. */
+            } else {
+               for( na = min_na; na <= max_na; na++ ) {
+                  r = ChrMatcher( a + na, end, b, pattern, NULL, 0, 1, 0,
+                                  NULL, NULL, NULL, status );
+                  if( r ) {
+                     match = 1;
+                     r = astFree( r );
+                     break;
+                  }
+               }
+            }
+         }
+
+/* Increment the the pointer to the next test character. */
+         a += na;
+         if( a > end ) a = end;
+      }
+   }
+
+/* If the test string is finished but the template string is not, see if
+   the next part of the template string will match a null test string.
+   But ignore the ends of substitution fields. */
+   if( !*a && *b && match ) {
+      while( *b && *b != ')' ) {
+         allowed = CheckTempStart( template, b, pattern, allowed, &nb, &allow,
+                                   &min_na, &max_na, &start_sub, &end_sub,
+                                   &greedy, status );
+         b += nb;
+         allowed = astFree( allowed );
+
+         if( min_na > 0 ) {
+            match = 0;
+            break;
+         }
+      }
+   }
+
+/* If the next character in the template is a closing parenthesis, then
+   we are finishing a substitution field. */
+   if( match && *b == ')' ) {
+
+/*Check we are not ignoring substitutions. */
+      if( ! ignore ){
+
+/* Report an error and abort if we are not currently in a substitution
+   field. */
+         if( ! in_sub ) {
+            astError( AST__BADSUB, "Invalid pattern matching template \"%s\": "
+                      "missing '('.", status, pattern );
+         }
+
+/* We are no longer in a substitution field. */
+         in_sub = 0;
+
+/* If possible, store a copy of the test string that matched the
+   substitution field. */
+         matches = astGrow( matches, nmatch + 1, sizeof( char * ) );
+         if( matches ) {
+            matchlen = ( a - aaa );
+            matches[ nmatch ] = astStore( NULL, aaa, matchlen + 1 );
+            if( matches[ nmatch ] ) {
+               matches[ nmatch ][ matchlen ] = 0;
+               nmatch++;
+               if( matchend ) *matchend = a;
+            }
+         }
+
+         aa = a;
+      }
+      b++;
+   }
+
+/* If the test string is finished but the template string is not, see if
+   the rest of the template string will match a null test string. */
+   if( !*a && *b && match ) {
+
+      while( *b ) {
+         allowed = CheckTempStart( template, b, pattern, allowed, &nb, &allow,
+                                   &min_na, &max_na, &start_sub, &end_sub,
+                                   &greedy, status );
+         b += nb;
+         allowed = astFree( allowed );
+
+         if( min_na > 0 ) {
+            match = 0;
+            break;
+         }
+      }
+
+   }
+
+/* No match if either string was not used completely. */
+   if( *a || *b ) match = 0;
+
+/* Report an error if we are still inside a substitution field */
+   if( match && in_sub && !ignore ) {
+      astError( AST__BADSUB, "Invalid pattern matching template \"%s\": "
+                "missing ')'.", status, pattern );
+      match = 0;
+   }
+
+/* If we have a match, construct the returned string. */
+   if( match && parts ) {
+
+/* Store the test string following the final substitution field. */
+      parts = astGrow( parts, npart + 1, sizeof( char * ) );
+      if( parts ) {
+         partlen = ( a - aa );
+         parts[ npart ] = astStore( NULL, aa, partlen + 1 );
+         if( parts[ npart ] ) {
+            parts[ npart ][ partlen ] = 0;
+            npart++;
+         }
+      }
+
+/* If required, expand  $1, $2, etc within the replacement strings. */
+      if( expdoll) {
+         newsubs = astMalloc( sizeof( char * )*nsub );
+         if( newsubs ) {
+            for( i = 0; i < nsub; i++ ) {
+               stl = strlen( subs[ i ] );
+               stest = astStore( NULL, subs[ i ], stl + 1 );
+               for( dollar = 1; dollar <= nsub; dollar++ ) {
+                  sprintf( stemp, ".*($%d).*", dollar );
+                  sres = ChrMatcher( stest, stest + stl, stemp, stemp,
+                                     (void *) ( matches + dollar - 1 ),
+                                     1, 0, 0, NULL, NULL, NULL, status );
+                  if( sres ) {
+                     (void) astFree( stest );
+                     stest = sres;
+                  }
+               }
+               newsubs[ i ] = stest;
+            }
+         }
+
+      } else {
+         newsubs = (char **) subs;
+      }
+
+/* Concatenate the sub-strings to form the final string. */
+      reslen = 0;
+      for( ipart = 0; ipart < npart - 1; ipart++ ) {
+         result = astAppendString( result, &reslen, parts[ ipart ] );
+         if( ipart < nsub ) {
+            result = astAppendString( result, &reslen, newsubs[ ipart ] );
+         } else {
+            result = astAppendString( result, &reslen, matches[ ipart ] );
+         }
+      }
+      result = astAppendString( result, &reslen, parts[ ipart ] );
+
+/* Free resources. */
+      if( newsubs && newsubs != (char **) subs ) {
+         for( i = 0; i < nsub; i++ ) {
+            newsubs[ i ] = astFree( newsubs[ i ] );
+         }
+         newsubs = astFree( newsubs );
+      }
+   }
+
+   allowed = astFree( allowed );
+   for( ipart = 0; ipart < npart; ipart++ ) {
+      parts[ ipart ] = astFree( parts[ ipart ] );
+   }
+   parts = astFree( parts );
+
+/* If required, return the array holding the test sub-strings that
+   matched the parenthesised template sub-strings, together with
+   the length of the array. Otherwise, free the memory holding these
+   strings. */
+   if( mres ) {
+      *mres = matches;
+      *mlen = nmatch;
+
+   } else if( matches ) {
+      for( i = 0; i < nmatch; i++ ) {
+         matches[ i ] = astFree( matches[ i ] );
+      }
+      matches = astFree( matches );
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+int astMemCaching_( int newval, int *status ){
+/*
+*++
+*  Name:
+*     astMemCaching
+
+*  Purpose:
+*     Controls whether allocated but unused memory is cached in this module.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     int astMemCaching( int newval )
+
+*  Description:
+*     This function sets a flag indicating if allocated but unused memory
+*     should be cached or not. It also returns the original value of the
+*     flag.
+*
+*     If caching is switched on or off as a result of this call, then the
+*     current contents of the cache are discarded.
+*
+*     Note, each thread has a separate cache. Calling this function
+*     affects only the currently executing thread.
+
+*  Parameters:
+*     newval
+*        The new value for the MemoryCaching tuning parameter (see
+*        astTune in objectc.c). If AST__TUNULL is supplied, the current
+*        value is left unchanged.
+
+*  Returned Value:
+*     astMemCaching()
+*        The original value of the MemoryCaching tuning parameter.
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   int i;
+   int result;
+   Memory *mem;
+
+#ifdef MEM_DEBUG
+   int id_list_size;
+   int *id_list;
+#endif
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Store the original value of the tuning parameter. */
+   result = use_cache;
+
+/* If a new value is to be set. */
+   if( newval != AST__TUNULL ) {
+
+/* If the cache has been initialised, empty it. */
+      if( cache_init ) {
+
+/* If we are listing the ID of every memory block in the cache, count the
+   number of blocks in the cache and then allocate an array to store the ID
+   values in. This is done so that we can sort them before displaying them. */
+#ifdef MEM_DEBUG
+         if( List_Cache ) {
+            Memory *next;
+
+            id_list_size = 0;
+            for( i = 0; i <= MXCSIZE; i++ ) {
+               next = cache[ i ];
+               while( next ) {
+                  id_list_size++;
+                  next = next->next;
+               }
+            }
+
+            id_list = MALLOC( sizeof(int)*id_list_size );
+            if( !id_list ) {
+               astError( AST__INTER, "astMemCaching: Cannot allocate %lu "
+                         "bytes of memory", status, (unsigned long)(sizeof(int)*id_list_size) );
+            }
+
+            id_list_size = 0;
+
+         } else {
+            id_list = NULL;
+         }
+#endif
+
+         for( i = 0; i <= MXCSIZE; i++ ) {
+            while( cache[ i ] ) {
+               mem = cache[ i ];
+               cache[ i ] = mem->next;
+               mem->size = (size_t) i;
+
+#ifdef MEM_DEBUG
+               if( id_list ) {
+                  id_list[ id_list_size++ ] = mem->id;
+               }
+#endif
+
+               FREE( mem );
+            }
+         }
+
+/* If we are displaying the IDs of memory blocks still in the cache, sort
+   them using a bubblesort algorithm, then display them. */
+#ifdef MEM_DEBUG
+         if( id_list ) {
+
+            if( id_list_size == 0 ) {
+               printf( "Emptying the AST memory cache - (the cache is "
+                       "already empty)\n" );
+
+            } else {
+               int sorted, j, t;
+
+               sorted = 0;
+               for( j = id_list_size - 2; !sorted && j >= 0; j-- ) {
+                  sorted = 1;
+                  for( i = 0; i <= j; i++ ) {
+                     if( id_list[ i ] > id_list[ i + 1 ] ) {
+                        sorted = 0;
+                        t = id_list[ i ];
+                        id_list[ i ] = id_list[ i + 1 ];
+                        id_list[ i + 1 ] = t;
+                     }
+                  }
+               }
+
+               printf( "Emptying the AST memory cache - freeing the "
+                       "following memory blocks: ");
+               for( i = 0; i < id_list_size; i++ ) printf( "%d ", id_list[ i ] );
+               printf( "\n" );
+
+            }
+         }
+#endif
+
+/* Otherwise, initialise the cache array to hold a NULL pointer at every
+   element. */
+      } else {
+         for( i = 0; i <= MXCSIZE; i++ ) cache[ i ] = NULL;
+         cache_init = 1;
+      }
+
+/* Store the new value. */
+      use_cache = newval;
+
+   }
+
+/* Return the original value. */
+   return result;
+}
+
+void *astRealloc_( void *ptr, size_t size, int *status ) {
+/*
+*++
+*  Name:
+*     astRealloc
+
+*  Purpose:
+*     Change the size of a dynamically allocated region of memory.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void *astRealloc( void *ptr, size_t size )
+
+*  Description:
+*     This function changes the size of a dynamically allocated region
+*     of memory, preserving its contents up to the minimum of the old
+*     and new sizes. This may involve copying the contents to a new
+*     location, so a new pointer is returned (and the old memory freed
+*     if necessary).
+*
+*     This function is similar to the standard C "realloc" function
+*     except that it provides better security against programming
+*     errors and also supports the allocation of zero-size memory
+*     regions (indicated by a NULL pointer).
+
+*  Parameters:
+*     ptr
+*        Pointer to previously allocated memory (or NULL if the
+*        previous size of the allocated memory was zero).
+*     size
+*        New size required for the memory region. This may be zero, in
+*        which case a NULL pointer is returned (no error results). It
+*        should not be negative.
+
+*  Returned Value:
+*     astRealloc()
+*        If the memory was reallocated successfully, a pointer to the
+*        start of the new memory region is returned (this may be the same
+*        as the original pointer). If size was given as zero, a NULL
+*        pointer is returned.
+
+*  Notes:
+*     - If this function is invoked with the error status set, or if
+*     it fails for any reason, the original pointer value is returned
+*     and the memory contents are unchanged. Note that this behaviour
+*     differs from that of the standard C "realloc" function which
+*     returns NULL if it fails.
+*--
+*/
+
+/* Local Constants: */
+#define ERRBUF_LEN 80
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   char errbuf[ ERRBUF_LEN ];    /* Buffer for system error message */
+   char *errstat;                /* Pointer to system error message */
+   int isdynamic;                /* Was memory allocated dynamically? */
+   void *result;                 /* Returned pointer */
+   Memory *mem;                  /* Pointer to memory header */
+
+/* Check the global error status. */
+   if ( !astOK ) return ptr;
+
+/* Initialise. */
+   result = ptr;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* If a NULL pointer was supplied, use astMalloc to allocate some new
+   memory. */
+   if ( !ptr ) {
+      result = astMalloc( size );
+
+/* Otherwise, check that the pointer supplied points at memory
+   allocated by a function in this module (IsDynamic sets the global
+   error status if it does not). */
+   } else {
+      IS_DYNAMIC( ptr, isdynamic );
+      if ( isdynamic ) {
+
+/* Check that a negative size has not been given and report an error
+   if necessary. */
+         if ( size < (size_t) 0 ) {
+            astError( AST__MEMIN,
+               "Invalid attempt to reallocate a block of memory to %ld bytes.", status,
+                      (long) size );
+
+/* If OK, obtain a pointer to the memory header. */
+         } else {
+            mem = (Memory *) ( (char *) ptr - SIZEOF_MEMORY );
+
+/* If the new size is zero, free the old memory and set a NULL return
+   pointer value. */
+            if ( size == (size_t) 0 ) {
+               astFree( ptr );
+               result = NULL;
+
+/* Otherwise, reallocate the memory. */
+            } else {
+
+/* If the cache is being used, for small memory blocks, do the equivalent of
+               mem = REALLOC( mem, SIZEOF_MEMORY + size );
+
+   using astMalloc, astFree and memcpy explicitly in order to ensure
+   that the memory blocks are cached. */
+               if( use_cache && ( mem->size <= MXCSIZE || size <= MXCSIZE ) ) {
+                  result = astMalloc( size );
+                  if( result ) {
+                     if( mem->size < size ) {
+                        memcpy( result, ptr, mem->size );
+                     } else {
+                        memcpy( result, ptr, size );
+                     }
+                     astFree( ptr );
+
+                  } else {
+                     result = ptr;
+                  }
+
+/* For other memory blocks simply use realloc. */
+               } else {
+
+#ifdef MEM_DEBUG
+                  DeIssue( mem, status );
+#endif
+
+                  mem = REALLOC( mem, SIZEOF_MEMORY + size );
+
+/* If this failed, report an error and return the original pointer
+   value. */
+                  if ( !mem ) {
+#if HAVE_STRERROR_R
+                     strerror_r( errno, errbuf, ERRBUF_LEN );
+                     errstat = errbuf;
+#else
+                     errstat = strerror( errno );
+#endif
+                     astError( AST__NOMEM, "realloc: %s", status, errstat );
+                     astError( AST__NOMEM, "Failed to reallocate a block of "
+                               "memory to %ld bytes.", status, (long) size );
+
+/* If successful, set the new "magic" value and size in the memory
+   header and obtain a pointer to the start of the region of memory to
+   be used by the caller. */
+                  } else {
+                     mem->magic = MAGIC( mem, size );
+                     mem->size = size;
+                     mem->next = NULL;
+#ifdef MEM_DEBUG
+                     mem->id = -1;
+                     mem->prev = NULL;
+                     Issue( mem, status );
+#endif
+                     result = mem;
+                     result = (char *) result + SIZEOF_MEMORY;
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+#undef ERRBUF_LEN
+
+void astRemoveLeadingBlanks_( char *string, int *status ) {
+/*
+*++
+*  Name:
+*     astRemoveLeadingBlanks
+
+*  Purpose:
+*     Remove any leading white space from a string.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void astRemoveLeadingBlanks( char *string )
+
+*  Description:
+*     This function moves characters in the supplied string to the left
+*     in order to remove any leading white space.
+
+*  Parameters:
+*     string
+*        Pointer to the string.
+
+*--
+*/
+
+/* Local Variables: */
+   char *c, *d;
+
+/* Check a string has been supplied. */
+   if( string ){
+
+/* Get a pointer to the first non-white character in the string. */
+      c = string;
+      while( *c && isspace( *c ) ) c++;
+
+/* Do nothing more if there are no leading spaces. */
+      if( c > string ) {
+
+/* Copy all characters (excluding the trailing null) to the left to
+   over-write the leading spaces. */
+         d = string;
+         while( *c ) *(d++) = *(c++);
+
+/* Terminate the returned string. */
+         *d = 0;
+      }
+   }
+}
+
+size_t astSizeOf_( const void *ptr, int *status ) {
+/*
+*++
+*  Name:
+*     astSizeOf
+
+*  Purpose:
+*     Determine the size of a dynamically allocated region of memory.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     size_t astSizeOf( const void *ptr )
+
+*  Description:
+*     This function returns the size of a region of dynamically
+*     allocated memory.
+
+*  Parameters:
+*     ptr
+*        Pointer to dynamically allocated memory (or NULL if the size
+*        of the allocated memory was zero).
+
+*  Returned Value:
+*     astSizeOf()
+*        The allocated size. This will be zero if a NULL pointer was
+*        supplied (no error will result).
+
+*  Notes:
+*     - A value of zero is returned if this function is invoked with
+*     the global error status set, or if it fails for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   int isdynamic;                /* Was the memory allocated dynamically? */
+   size_t size;                  /* Memory size */
+
+/* Check the global error status. */
+   if ( !astOK ) return (size_t) 0;
+
+/* Initialise. */
+   size = (size_t) 0;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check if a non-NULL valid pointer has been given. If so, extract
+   the memory size from the header which precedes it. */
+   if ( ptr ){
+      IS_DYNAMIC( ptr, isdynamic );
+      if( isdynamic ) size = ( (Memory *) ( (char *) ptr - SIZEOF_MEMORY ) )->size;
+   }
+
+/* Return the result. */
+   return size;
+}
+
+static size_t SizeOfMemory( int *status ){
+/*
+*  Name:
+*     SizeOfMemory
+
+*  Purpose:
+*     Returns the size of a Memory structure, padded to an 8 byte
+*     boundary.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     size_t SizeOfMemory( int *status )
+
+*  Description:
+*     This function returns the size of a Memory structure used to
+*     store header information about any block of memory allocated by this
+*     module. The returned value may be larger than the actual size of
+*     the Memory structure in order to ensure that the pointer returned by
+*     astMalloc etc points to an 8 byte boundary. Failure to do this can
+*     result in some operating systems having problems. E.g Solaris
+*     requires this alignment if the returned pointer is going to be used to
+*     store doubles.
+
+*  Parameters:
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The size to use for a Memory structure.
+
+*  Notes:
+*     - The returned value is also stored in the module variable
+*     sizeof_memory.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get the basic size of a Memory structure. */
+   sizeof_memory = sizeof( Memory );
+
+/* Now increase the returned value to ensure it is a multiple of 8. Mask
+   off all but the last 3 bits, xor with 0x7 to get the remainder, add 1
+   to make it a multiple of 8 bytes. */
+   sizeof_memory += ((sizeof_memory & 0x7) ? ((sizeof_memory & 0x7) ^ 0x7) + 1 : 0);
+
+/* Return the value */
+   return sizeof_memory;
+
+}
+
+size_t astTSizeOf_( const void *ptr, int *status ) {
+/*
+*+
+*  Name:
+*     astTSizeOf
+
+*  Purpose:
+*     Determine the total size of a dynamically allocated region of memory.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     size_t astTSizeOf( const void *ptr )
+
+*  Description:
+*     This function returns the size of a region of dynamically
+*     allocated memory, including the extra memory used to store
+*     the header information for the memory block (size and magic number).
+
+*  Parameters:
+*     ptr
+*        Pointer to dynamically allocated memory (or NULL if the size
+*        of the allocated memory was zero).
+
+*  Returned Value:
+*     The allocated size. This will be zero if a NULL pointer was
+*     supplied (no error will result).
+
+*  Notes:
+*     - A value of zero is returned if this function is invoked with
+*     the global error status set, or if it fails for any reason.
+*     - This function is documented as protected because it should not
+*     be invoked by external code. However, it is available via the
+*     external C interface so that it may be used when writing (e.g.)
+*     foreign language or graphics interfaces.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   int isdynamic;                /* Was the memory allocated dynamically? */
+   size_t size;                  /* Memory size */
+
+/* Check the global error status. */
+   if ( !astOK ) return (size_t) 0;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   size = (size_t) 0;
+
+/* Check if a non-NULL valid pointer has been given. If so, extract
+   the memory size from the header which precedes it. */
+   if ( ptr ){
+      IS_DYNAMIC( ptr, isdynamic );
+      if( isdynamic ) size = SIZEOF_MEMORY +
+                             ( (Memory *) ( (char *) ptr - SIZEOF_MEMORY ) )->size;
+   }
+
+/* Return the result. */
+   return size;
+}
+
+void *astStore_( void *ptr, const void *data, size_t size, int *status ) {
+/*
+*++
+*  Name:
+*     astStore
+
+*  Purpose:
+*     Store data in dynamically allocated memory.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void *astStore( void *ptr, const void *data, size_t size )
+
+*  Description:
+*     This function stores data in dynamically allocated memory,
+*     allocating the memory (or adjusting the size of previously
+*     allocated memory) to match the amount of data to be stored.
+
+*  Parameters:
+*     ptr
+*        Pointer to previously allocated memory (or NULL if none has
+*        yet been allocated).
+*     data
+*        Pointer to the start of the data to be stored. This may be
+*        given as NULL if there are no data, in which case it will be
+*        ignored and this function behaves like astRealloc, preserving
+*        the existing memory contents.
+*     size
+*        The total size of the data to be stored and/or the size of
+*        memory to be allocated. This may be zero, in which case the
+*        data parameter is ignored, any previously-allocated memory is
+*        freed and a NULL pointer is returned.
+
+*  Returned Value:
+*     astStore()
+*        If the data were stored successfully, a pointer to the start of
+*        the possibly new memory region is returned (this may be the same
+*        as the original pointer). If size was given as zero, a NULL
+*        pointer is returned.
+
+*  Notes:
+*     - This is a convenience function for use when storing data of
+*     arbitrary size in memory which is to be allocated
+*     dynamically. It is appropriate when the size of the data will
+*     not change frequently because the size of the memory region will
+*     be adjusted to fit the data on every invocation.
+*     - If this function is invoked with the error status set, or if
+*     it fails for any reason, the original pointer value is returned
+*     and the memory contents are unchanged.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   int valid;                    /* Is the memory pointer usable? */
+   void *new;                    /* Pointer to returned memory */
+
+/* Check the global error status. */
+   if ( !astOK ) return ptr;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   new = ptr;
+
+/* If the new size is zero, use astRealloc to free any previously
+   allocated memory. Also re-allocate the memory if the data pointer
+   is NULL (in which case we want to preserve its contents). */
+   if ( ( size == (size_t) 0 ) || !data ) {
+      new = astRealloc( ptr, size );
+
+/* In other cases, we do not want to preserve any memory
+   contents. Check if the incoming memory pointer is valid (IsDynamic
+   sets the global error status if it is not). */
+   } else {
+      if ( !ptr ){
+         valid = 1;
+      } else {
+         IS_DYNAMIC( ptr, valid );
+      }
+      if( valid ) {
+
+/* Allocate the new memory. If successful, free the old memory (if
+   necessary) and copy the data into it. */
+         new = astMalloc( size );
+         if ( astOK ) {
+            if ( ptr ) ptr = astFree( ptr );
+            (void) memcpy( new, data, size );
+
+/* If memory allocation failed, do not free the old memory but return
+   a pointer to it. */
+         } else {
+            new = ptr;
+         }
+      }
+   }
+
+/* Return the result. */
+   return new;
+}
+
+char *astString_( const char *chars, int nchars, int *status ) {
+/*
+*++
+*  Name:
+*     astString
+
+*  Purpose:
+*     Create a C string from an array of characters.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     char *astString( const char *chars, int nchars )
+
+*  Description:
+*     This function allocates memory to hold a C string and fills the
+*     string with the sequence of characters supplied. It then
+*     terminates the string with a null character and returns a
+*     pointer to its start. The memory used for the string may later
+*     be de-allocated using astFree.
+*
+*     This function is intended for constructing null terminated C
+*     strings from arrays of characters which are not null terminated,
+*     such as when importing a character argument from a Fortran 77
+*     program.
+
+*  Parameters:
+*     chars
+*        Pointer to the array of characters to be used to fill the string.
+*     nchars
+*        The number of characters in the array (zero or more).
+
+*  Returned Value:
+*     astString()
+*        If successful, the function returns a pointer to the start of
+*        the allocated string. If the number of characters is zero, a
+*        zero-length string is still allocated and a pointer to it is
+*        returned.
+
+*  Notes:
+*     - A pointer value of NULL is returned if this function is
+*     invoked with the global error status set or if it fails for any
+*     reason.
+*--
+*/
+
+/* Local Variables: */
+   char *result;                 /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check that the number of characters in the string is valid and
+   report an error if it is not. */
+   if ( nchars < 0 ) {
+      astError( AST__NCHIN, "astString: Invalid attempt to allocate a string "
+                "with %d characters.", status, nchars);
+
+/* Allocate memory to hold the string. */
+   } else {
+      result = (char *) astMalloc( (size_t) ( nchars + 1 ) );
+
+/* If successful, copy the characters into the string. */
+      if ( astOK && result ) {
+         (void) memcpy( result, chars, (size_t) nchars );
+
+/* Terminate the string. */
+         result[ nchars ] = '\0';
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+char **astStringArray_( const char *chars, int nel, int len, int *status ) {
+/*
+*++
+*  Name:
+*     astStringArray
+
+*  Purpose:
+*     Create an array of C strings from an array of characters.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     char **astStringArray( const char *chars, int nel, int len )
+
+*  Description:
+*     This function turns an array of fixed-length character data into
+*     a dynamicllay allocated array of null-terminated C strings with
+*     an index array that may be used to access them.
+*
+*     The array of character data supplied is assumed to hold "nel"
+*     adjacent fixed-length strings (without terminating nulls), each
+*     of length "len" characters. This function allocates memory and
+*     creates a null-terminated copy of each of these strings. It also
+*     creates an array of "nel" pointers which point at the start of
+*     each of these new strings. A pointer to this index array is
+*     returned.
+*
+*     The memory used is allocated in a single block and should later
+*     be de-allocated using astFree.
+s
+*  Parameters:
+*     chars
+*        Pointer to the array of input characters. The number of characters
+*        in this array should be at least equal to (nel * len).
+*     nel
+*        The number of fixed-length strings in the input character
+*        array. This may be zero but should not be negative.
+*     len
+*        The number of characters in each fixed-length input
+*        string. This may be zero but should not be negative.
+
+*  Returned Value:
+*     astStringArray()
+*        A pointer to the start of the index array, which contains "nel"
+*        pointers pointing at the start of each null-terminated output
+*        string.
+*
+*        The returned pointer should be passed to astFree to de-allocate
+*        the memory used when it is no longer required. This will free
+*        both the index array and the memory used by the strings it
+*        points at.
+
+*  Notes:
+*     - A NULL pointer will also be returned if the value of "nel" is
+*     zero, in which case no memory is allocated.
+*     - A pointer value of NULL will also be returned if this function
+*     is invoked with the global error status set or if it fails for
+*     any reason.
+*--
+*/
+
+/* Local Variables: */
+   char **result;                 /* Result pointer to return */
+   char *out_str;                 /* Pointer to start of next output string */
+   const char *in_str;            /* Pointer to start of next input string */
+   int i;                         /* Loop counter for array elements */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check that the array size is valid and report an error if it is
+   not. */
+   if ( nel < 0 ) {
+      astError( AST__NELIN,
+                "astStringArray: Invalid attempt to allocate an array of "
+                "%d strings.", status, nel );
+
+/* If the string length will be used, check that it is valid and
+   report an error if it is not. */
+   } else if ( ( nel > 0 ) && ( len < 0 ) ) {
+      astError( AST__NCHIN,
+                "astStringArray: Invalid attempt to allocate an "
+                "array of strings with %d characters in each.", status, len );
+
+/* Allocate memory to hold the array of string pointers plus the
+   string data (with terminating nulls). */
+   } else {
+      result = astMalloc( sizeof( char * ) * (size_t) nel +
+                          (size_t) ( nel * ( len + 1 ) ) );
+
+/* If successful, initialise pointers to the start of the current
+   input and output strings. */
+      if( astOK ){
+         in_str = chars;
+         out_str = (char *) ( result + nel );
+
+/* Loop to copy each string. */
+         for ( i = 0; i < nel; i++ ) {
+            (void) memcpy( out_str, in_str, (size_t) len );
+
+/* Terminate the output string. */
+            out_str[ len ] = '\0';
+
+/* Store a pointer to the start of the output string in the array of
+   character pointers. */
+            result[ i ] = out_str;
+
+/* Increment the pointers to the start of the next string. */
+            out_str += len + 1;
+            in_str += len;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+char *astStringCase_( const char *string, int upper, int *status ) {
+/*
+*++
+*  Name:
+*     astStringCase
+
+*  Purpose:
+*     Convert a string to upper or lower case.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     char *astStringCase( const char string, int upper )
+
+*  Description:
+*     This function converts a supplied string to upper or lower case,
+*     storing the result in dynamically allocated memory. The astChrCase
+*     function is similar, but stores the result in a supplied buffer.
+
+*  Parameters:
+*     string
+*        Pointer to the null terminated string to be converted.
+*     upper
+*        If non-zero, the string is converted to upper case. Otherwise it
+*        is converted to lower case.
+
+*  Returned Value:
+*     astStringCase()
+*        If successful, the function returns a pointer to the start of
+*        the allocated string. The returned memory should be freed using
+*        astFree when no longer needed.
+
+*  Notes:
+*     - A pointer value of NULL is returned if this function is
+*     invoked with the global error status set or if it fails for any
+*     reason.
+*--
+*/
+
+/* Local Variables: */
+   char *pout;                   /* Pointer to next output character */
+   char *result;                 /* Pointer value to return */
+   const char *pin;              /* Pointer to next input character */
+   int i;                        /* Character index */
+   int len;                      /* String length */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the length of the supplied string, excluding the trailing null. */
+   len = strlen( string );
+
+/* Allocate memory to hold the converted string, plus terminating null. */
+   result = (char *) astMalloc( (size_t) ( len + 1 ) );
+
+/* If successful, copy the characters into the string, converting each
+   one to the requested case. */
+   if ( result ) {
+      pin = string;
+      pout = result;
+
+      if( upper ) {
+         for( i = 0; i < len; i++ ) {
+            *(pout++) = toupper( (int) *(pin++) );
+         }
+
+      } else {
+
+         for( i = 0; i < len; i++ ) {
+            *(pout++) = tolower( (int) *(pin++) );
+         }
+      }
+
+/* Terminate the string. */
+      *pout = '\0';
+   }
+
+/* Return the result. */
+   return result;
+}
+
+size_t astChrLen_( const char *string, int *status ) {
+/*
+*++
+*  Name:
+*     astChrLen
+
+*  Purpose:
+*     Determine the used length of a string.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     size_t astChrLen( const char *string )
+
+*  Description:
+*     This function returns the used length of a string. This excludes any
+*     trailing white space or non-printable characters (such as the
+*     trailing null character).
+
+*  Parameters:
+*     string
+*        Pointer to the string.
+
+*  Returned Value:
+*     astChrLen()
+*        The number of characters in the supplied string, not including the
+*        trailing newline, and any trailing white-spaces or non-printable
+*        characters.
+
+*--
+*/
+
+/* Local Variables: */
+   const char *c;           /* Pointer to the next character to check */
+   size_t ret;              /* The returned string length */
+
+/* Initialise the returned string length. */
+   ret = 0;
+
+/* Check a string has been supplied. */
+   if( string ){
+
+/* Check each character in turn, starting with the last one. */
+      ret = strlen( string );
+      c = string + ret - 1;
+      while( ret ){
+         if( isprint( (int) *c ) && !isspace( (int) *c ) ) break;
+         c--;
+         ret--;
+      }
+   }
+
+/* Return the answer. */
+   return ret;
+
+}
+
+int astSscanf_( const char *str, const char *fmt, ...) {
+/*
+*+
+*  Name:
+*     astSscanf
+
+*  Purpose:
+*     A wrapper for the ANSI sscanf function.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     int astSscanf( const char *str, const char *fmt, ...)
+
+*  Description:
+*     This function is a direct plug-in replacement for sscanf. It ensures ANSI
+*     behaviour is available on all platforms, including those (such as
+*     MacOS) on which have the native sscanf function exhibits non-ANSI
+*     behaviour.
+
+*  Parameters:
+*     str
+*        Pointer to the string to be scanned.
+*     fmt
+*        Pointer to the format string which defines the fields to be
+*        looked for within "str".
+*     ...
+*        Pointers to locations at which to return the value of each
+*        succesfuly converted field, in the order specified in "fmt".
+
+*  Returned Value:
+*     The number of fields which were succesfully read from "str".
+*-
+*/
+
+/* Local Variables: */
+   char *c;                 /* Pointer to the next character to check */
+   char *newfor;            /* Pointer to modified format string */
+   const char *d;           /* Pointer to the next character to check */
+   int *status;             /* Pointer to inherited status value */
+   int iptr;                /* Index into ptr array */
+   int lfor;                /* No. of characters in format string */
+   int lstr;                /* No. of characters in scanned string */
+   int nc;                  /* No. of characters read from str */
+   int nfld;                /* No. of counted field specifiers found so far */
+   int nptr;                /* Np. of pointers stored */
+   int ret;                 /* The returned number of conversions */
+   va_list args;            /* Variable argument list pointer */
+   void *fptr;              /* The next supplied pointer */
+   void *ptr[ VMAXFLD ];    /* Array of supplied pointers */
+
+/* Initialise the variable argument list pointer. */
+   va_start( args, fmt );
+
+/* Get a pointer to the integer holding the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Initialise the returned string length. */
+   ret = 0;
+
+/* Check a string and format have been supplied. */
+   if( str && fmt ){
+
+/* Go through the format string, counting the number of field specifiers which
+   will return a value, and storing the corresponding points in the ptr
+   array. */
+      nptr = 0;
+      c = (char *) fmt;
+      while( *c ) {
+
+/* Field specifiers are marked by a % sign. */
+         if( *c == '%' ) {
+
+/* Look at the character following the % sign. Quit if the end of the string
+   has been reached. */
+            c++;
+            if( *c ) {
+
+/* If the % sign is followed by a "*" or another "%", then there will be no
+   corresponding pointer in the variable argument list "args". Ignore such
+   field specifiers. */
+               if( *c != '*' && *c != '%' ) {
+
+/* If possible store the corresponding pointer from the variable argument
+   list supplied to this function. Report an error if there are too many. */
+                  if ( nptr < VMAXFLD ) {
+                     ptr[ nptr++ ] = va_arg( args, void *);
+
+/* If the current field specifier is "%n" the corresponding pointer
+   should be a pointer to an integer. We initialise the integer to zero.
+   We need to do this because sscanf does not include "%n" values in the
+   returned count of succesful conversions, and so there is no sure way
+   of knowing whether a value has been stored for a "%n" field, and so
+   whether it is safe to use it as an index into the supplied. */
+                     if( *c == 'n' ) *( (int *) ptr[ nptr - 1 ] ) = 0;
+
+                  } else {
+                     astError( AST__INTER, "astSscanf: Format string "
+                               "'%s' contains more than %d fields "
+                               "(AST internal programming error).", status,
+                               fmt, VMAXFLD );
+                     break;
+                  }
+               }
+
+/* Move on the first character following the field specifier. */
+               c++;
+            }
+
+/* If this is not the start of a field specifier, pass on. */
+         } else {
+            c++;
+         }
+      }
+
+/* Fill any unused pointers with NULL. */
+      for( iptr = nptr; iptr < VMAXFLD; iptr++ ) ptr[iptr] = NULL;
+
+/* Get the length of the string to be scanned. */
+      lstr = strlen( str );
+
+/* Get the length of the format string excluding any trailing white space. */
+      lfor = astChrLen( fmt );
+
+/* Bill Joye reports that MacOS sscanf fails to return the correct number of
+   characters read (using a %n conversion) if there is a space before the
+   %n. So check for this. Does the format string contain " %n"? */
+      c = strstr( fmt, " %n" );
+      if( c && astOK ) {
+
+/* Take a copy of the supplied format string (excluding any trailing spaces). */
+         newfor = (char *) astStore( NULL, (void *) fmt, (size_t) lfor + 1 );
+         if( newfor ) {
+
+/* Ensure the string is terminated (in case the supplied format string
+   has any trailing spaces). */
+            newfor[ lfor ] = 0;
+
+/* Remove all spaces from before any %n. */
+            c = strstr( (const char *) newfor, " %n" );
+            while( c ) {
+               while( *(c++) ) *( c - 1 ) = *c;
+               c = strstr( newfor, " %n" );
+            }
+
+/* Use the native sscanf with the modified format string. Note, we cannot
+   use vsscanf because it is not ANSI C. Instead, we list the pointers
+   explicitly. */
+            ret = sscanf( str, newfor, ptr[0], ptr[1], ptr[2], ptr[3],
+                          ptr[4], ptr[5], ptr[6], ptr[7], ptr[8], ptr[9],
+                          ptr[10], ptr[11], ptr[12], ptr[13], ptr[14],
+                          ptr[15], ptr[16], ptr[17], ptr[18], ptr[19] );
+
+/* Now look through the original format string for conversions specifiers.
+   If any %n conversions are found which are preceded by a space, then
+   correct the returned character counts to include any spaces following the
+   corresponding point in the scanned string. */
+            nfld = 0;
+            iptr = 0;
+            c = (char *) fmt;
+            while( *c ) {
+
+/* Field specifiers are marked by a % sign. */
+               if( *c == '%' ) {
+
+/* Look at the character following the % sign. Quit if the end of the string
+   has been reached. */
+                  c++;
+                  if( *c ) {
+
+/* If the % sign is followed by a "*" or another "%", then there will be no
+   corresponding pointer in the variable argument list "args". Ignore such
+   field specifiers. */
+                     if( *c != '*' && *c != '%' ) {
+
+/* Get the supplied pointer corresponding to this field specifier. */
+                        fptr = ptr[ iptr++ ];
+
+/* Increment the number of matched fields required. "%n" specifiers are not
+   included in the value returned by sscanf so skip over them. */
+                        if( *c != 'n' ) {
+                           nfld++;
+
+/* If the % sign is followed by a "n", and was preceded by a space, we
+   may need to correct the returned character count. */
+                        } else if( c > fmt + 1 && *(c-2) == ' ' ) {
+
+/* Do not correct the returned value if sscanf did not get as far as this
+   field specifier before an error occurred. */
+                           if( ret >= nfld ) {
+
+/* Get the original character count produced by sscanf. */
+                              nc = *( (int *) fptr );
+
+/* For each space in "str" which follows, increment the returned count by
+   one (so long as the original count is not zero or more than the length
+   of the string - this is not foolproof, but I can't think of a better
+   check - all uses of %n in AST initialize the supplied count to zero
+   before calling sscanf so a value fo zero is a safe (ish) bet that the
+   supplied string doesn't match the supplied format). */
+                              if( nc > 0 && nc < lstr ) {
+                                 d = str + nc;
+                                 while( *(d++) == ' ' ) nc++;
+                                 *( (int *) fptr ) = nc;
+                              }
+                           }
+                        }
+                     }
+
+/* Move on the first character following the field specifier. */
+                     c++;
+                  }
+
+/* If this is not the start of a field specifier, pass on. */
+               } else {
+                  c++;
+               }
+            }
+
+/* Release the temporary copy of the format string. */
+            newfor = (char *) astFree( (void *) newfor );
+         }
+
+/* If the format string should not trigger any known problems, use sscanf
+   directly. */
+      } else if( astOK ) {
+         ret = sscanf( str, fmt, ptr[0], ptr[1], ptr[2], ptr[3],
+                       ptr[4], ptr[5], ptr[6], ptr[7], ptr[8], ptr[9],
+                       ptr[10], ptr[11], ptr[12], ptr[13], ptr[14],
+                       ptr[15], ptr[16], ptr[17], ptr[18], ptr[19] );
+      }
+   }
+
+/* Tidy up the argument pointer. */
+   va_end( args );
+
+/* Return the answer. */
+   return ret;
+
+}
+
+
+/* The next functions are used only when memory debugging is
+   switched on via the MEM_DEBUG macro. They can be used for locating
+   memory leaks, etc. */
+#ifdef MEM_DEBUG
+
+void astActiveMemory_( const char *label ) {
+/*
+*+
+*  Name:
+*     astActiveMemory
+
+*  Purpose:
+*     Display a list of any currently active AST memory pointers.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     astActiveMemory( const char *label )
+
+*  Description:
+*     This function displays a list of the identifiers for all currently
+*     active AST memory chunks. The list is written to standard output
+*     using "printf", preceded by the supplied text.
+
+*  Parameters:
+*     label
+*        A textual label to display before the memody id values (may be
+*        NULL).
+
+*  Notes:
+*     - This function attempts to execute even if an error has occurred.
+*     - Memory blocks which are not usually freed are not reported. Such
+*     blocks are typically used by AST to hold internal state information.
+*-
+*/
+
+   Memory *next;
+
+   if( label ) printf("%s: ", label );
+   next = Active_List;
+   if( next ) {
+      while( next ) {
+         if( !next->perm ) {
+            printf( "%d(%s:%d) ", next->id, next->file, next->line );
+         }
+         next = next->next;
+      }
+   } else {
+      printf("There are currently no active AST memory blocks.");
+   }
+   printf("\n");
+
+}
+
+void astWatchMemory_( int id ) {
+/*
+*+
+*  Name:
+*     astWatchMemory
+
+*  Purpose:
+*     Indicate uses of the memory block with the specified identifier
+*     should be reported.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     astWatchMemory( int id )
+
+*  Description:
+*     This function forces astMemoryAlarm to be invoked when key
+*     operations are performed on a specified memory block. These key
+*     operations include; allocation, freeing, copying and cloning of
+*     Objects, etc.
+*
+*     astMemoryAlarm reports a message when called identifying the memory
+*     block and the action performed on it. When using a debugger, these
+*     events can be trapped and investigated by setting a debugger
+*     breakpoint in astMemoryAlarm_.
+
+*  Parameters:
+*     id
+*        The identifier of the memory block which is to be watched.
+
+*  Notes:
+*     - This function attempts to execute even if an error has occurred.
+*-
+*/
+   Watched_ID = id;
+}
+
+int astMemoryId_( const void *ptr, int *status ){
+/*
+*+
+*  Name:
+*     astMemoryId
+
+*  Purpose:
+*     Return the integer identifier for a memory block.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     int astMemoryId( const void *ptr )
+
+*  Description:
+*     This function returns the integer identifier associated with a
+*     memory block allocated by function sin this module.
+
+*  Parameters:
+*     ptr
+*        The pointer (a genuine C pointer, not an encoded object
+*        identifier).
+
+*  Returned Value:
+*     The integer identifier. A value of -1 is returned if "ptr" is NULL.
+
+*-
+*/
+   astDECLARE_GLOBALS
+   astGET_GLOBALS(NULL);
+   return ptr ? ((Memory *)(ptr-SIZEOF_MEMORY))->id : -1;
+}
+
+void *astMemoryPtr_( int id ){
+/*
+*+
+*  Name:
+*     astMemoryPtr
+
+*  Purpose:
+*     Return a pointer to the memory block with a given identifier.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void *astMemoryPtr( int id )
+
+*  Description:
+*     This function returns a pointer to the memory block with a given
+*     identifier. NULL is returned if the given identifier is not active.
+
+*  Parameters:
+*     id
+*        The identifier for an active memory block.
+
+*  Returned Value:
+*     The pointer to the memory block. NULL is returned if no active memory
+*     with the given ID can be found. Note, this is always a genuine C
+*     pointer (even for public Object pointers).
+
+*-
+*/
+   Memory *next;
+   void *ret;
+
+   ret = NULL;
+   next = Active_List;
+   while( next ) {
+      if( next->id == id ) {
+         ret = next + 1;
+         break;
+      }
+   }
+
+   return ret;
+}
+
+void astMemoryAlarm_( const char *verb ){
+/*
+*+
+*  Name:
+*     astMemoryAlarm
+
+*  Purpose:
+*     Called when a watched memory ID is used.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void astMemoryAlarm( const char *verb )
+
+*  Description:
+*     This function is called when a watched memory ID is used. See
+*     astWatchMemory.
+
+*  Parameters:
+*     verb
+*        Text to include in message.
+*-
+*/
+
+   printf( "astMemoryAlarm: Memory id %d has been %s.\n", Watched_ID, verb );
+}
+
+void astMemoryStats_( int reset, size_t *peak, size_t *current, int *status ) {
+/*
+*+
+*  Name:
+*     astMemoryStats
+
+*  Purpose:
+*     Return the current and peak AST memory usage.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     astMemoryStats( int reset, size_t *peak, size_t *current )
+
+*  Description:
+*     This function returns the current amount of memory allocated
+*     using AST memory management functions, and the peak amount of
+*     simultaneously allocated memory since the last time the peak value
+*     was reset.
+
+*  Parameters:
+*     reset
+*        If non-zero, the peak value is reset to the current usage
+*        upon return.
+*     peak
+*        Address at which to return the peak memory usage since the last
+*        reset, in bytes.
+*     current
+*        Address at which to return the current memory usage, in bytes.
+
+*-
+*/
+
+   LOCK_DEBUG_MUTEX;
+
+   if( peak ) *peak = Peak_Usage;
+   if( current ) *current = Current_Usage;
+   if( reset ) Peak_Usage = Current_Usage;
+
+   UNLOCK_DEBUG_MUTEX;
+}
+
+void astMemoryWarning_( size_t threshold, int *status ) {
+/*
+*+
+*  Name:
+*     astMemoryWarning
+
+*  Purpose:
+*     Issues a warning memory goes over a specified threshold.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     astMemoryWarning( size_t threshold )
+
+*  Description:
+*     This function prints a warning message to standard output if the
+*     AST memory usage exceeds the specified threshold.
+
+*  Parameters:
+*     threshold
+*        The memory allocation, in bytes, at which a warning should be issued,
+*        Supply zero to suppress warnings.
+
+*  Notes:
+*     - This function is used to reset the threshold to zero when the first
+*     warning is issued in order to prevent a flood of warnings appearing.
+*     Therefore, setting a debugger breakpoint in this function
+*     ("astMemoryWarning_" - do not forget the trailing underscore)
+*     allows you to locate the point at which memory allocation first
+*     exceeds the threshold.
+
+*-
+*/
+
+   LOCK_DEBUG_MUTEX;
+
+   Warn_Usage = threshold;
+
+   UNLOCK_DEBUG_MUTEX;
+}
+
+void astMemoryUse_( const void *ptr, const char *verb, int *status ){
+/*
+*+
+*  Name:
+*     astMemoryUse
+
+*  Purpose:
+*     Called to report the use of a memory block pointer.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void astMemoryUse( void *ptr, const char *verb )
+
+*  Description:
+*     If the supplied memory block is being watched, astMemoryAlarm is
+*     called to report the use of the pointer. The reported text includes
+*     the supplied "verb". A memory block can be watched by calling
+*     astWatchMemory.
+
+*  Parameters:
+*     ptr
+*        A pointer to the memory block being used. The pointer must have
+*        been returned by one of the AST memory management functions (e.g.
+*        astMalloc, astRealloc, etc).
+*     verb
+*        A verb indicating what is being done to the pointer.
+*-
+*/
+
+   astDECLARE_GLOBALS
+   astGET_GLOBALS(NULL);
+
+   if( ptr && astMemoryId( ptr ) == Watched_ID ) {
+      if( !Quiet_Use || !strcmp( verb, ISSUED ) ||
+                        !strcmp( verb, FREED ) ) {
+         astMemoryAlarm( verb );
+      }
+   }
+}
+
+int astMemoryTune_( const char *name, int value, int *status ){
+/*
+*+
+*  Name:
+*     astMemoryTune
+
+*  Purpose:
+*     Set a tuning parameter for the memory debugging functions.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     int astMemoryTune( const char *name, int value )
+
+*  Description:
+*     There are a few tuning parameters which control the behaviour of
+*     the memory debugging functions. This function allows these tuning
+*     parameters to be queried or set.
+
+*  Parameters:
+*     name
+*        The name of the tuning parameter to query or set. Valid names are:
+*
+*        "Keep_ID": A boolean flag indicating if a new ID should be issued
+*        for a cached memory block each time it is returned by astMalloc?
+*        Otherwise, the same ID value is used throughtout the life of a
+*        memory block. Default is zero (false).
+*
+*        "List_Cache": A boolean flag which if non-zero (true) causes the
+*        ID of every memory block in the cache to be reported when the
+*        cache is emptied by astFlushMemory.
+*
+*        "Quiet_Use": A boolean flag controlling the number of reports issued
+*        when a memory block is being watched (see astWatchMemory). If
+*        non-zero (true), then the only events which are reported are the
+*        issuing of a memory block pointer by astMalloc or astRealloc,and
+*        the freeing (or caching) of a memory block by astFree. If Quiet_Use
+*        is zero (the default), then additional reports are made for
+*        memory blocks used to hold AST Objects whenever the Object is
+*        copied, cloned, or checked.
+*     value
+*        The new value for the tuning parameter. If AST__TUNULL is
+*        supplied, the original value is left unchanged.
+
+*  Returned Value:
+*     The original value of the tuning parameter.
+
+*-
+*/
+
+   int result = AST__TUNULL;
+
+   if( name ) {
+
+      if( astChrMatch( name, "Keep_ID" ) ) {
+         result = Keep_ID;
+         if( value != AST__TUNULL ) Keep_ID = value;
+
+      } else if( astChrMatch( name, "Quiet_Use" ) ) {
+         result = Quiet_Use;
+         if( value != AST__TUNULL ) Quiet_Use = value;
+
+      } else if( astChrMatch( name, "List_Cache" ) ) {
+         result = List_Cache;
+         if( value != AST__TUNULL ) List_Cache = value;
+
+      } else if( astOK ) {
+         astError( AST__TUNAM, "astMemoryTune: Unknown AST memory tuning "
+                   "parameter specified \"%s\".", status, name );
+      }
+   }
+
+   return result;
+}
+
+void astBeginPM_( int *status ) {
+/*
+*+
+*  Name:
+*     astBeginPM
+
+*  Purpose:
+*     Start a block of permanent memory allocations.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     astBeginPM
+
+*  Description:
+*     This function indicates that all memory allocations made by calls
+*     to other functions in this module (e.g. astMalloc), up to the
+*     astEndPM call which matches the astBeginPM call,  will not usually
+*     be freed explicitly. Matching astBeginPM/astEndPM calls should be
+*     used to enclose all code which allocates memory which is never
+*     freed explitly by AST. Such memory allocations may be freed if
+*     required, using the astFlushMemory function (but note this should
+*     only be done once all use of AST by an application has finished).
+*
+*     Matching pairs of astBeginPM/astEndPM calls can be nested up to a
+*     maximum depth of 20.
+
+*-
+*/
+
+   LOCK_DEBUG_MUTEX;
+
+/* The global Perm_Mem flag indicates whether or not subsequent memory
+   management functions in this module should store pointers to allocated
+   blocks in the PM_List array. Push the current value of this flag
+   onto a stack, and set the value to 1. */
+   if( PM_Stack_Size >= PM_STACK_MAXSIZE ){
+      if( astOK ) {
+         astError( AST__INTER, "astBeginPM: Maximum stack size has been "
+                   "exceeded (internal AST programming error)." , status);
+      }
+
+   } else {
+      PM_Stack[ PM_Stack_Size++ ] = Perm_Mem;
+      Perm_Mem = 1;
+   }
+   UNLOCK_DEBUG_MUTEX;
+}
+
+void astEndPM_( int *status ) {
+/*
+*+
+*  Name:
+*     astEndPM
+
+*  Purpose:
+*     End a block of permanent memory allocations.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     astEndPM
+
+*  Description:
+*     This function indicates the end of the block of permanent memory
+*     allocations started by the matching call to astBeginPM. See
+*     astBeginPM for further details.
+
+*-
+*/
+
+   LOCK_DEBUG_MUTEX;
+
+/* The global Perm_Mem flag indicates whether or not subsequent memory
+   management functions in this module should store pointers to allocated
+   blocks in the PM_List array. Pop the value from the top of this stack. */
+   if( PM_Stack_Size == 0 ){
+      if( astOK ) {
+         astError( AST__INTER, "astEndPM: astEndPM called without "
+                   "matching astBeginPM (internal AST programming error)." , status);
+      }
+
+   } else {
+      Perm_Mem = PM_Stack[ --PM_Stack_Size ];
+   }
+
+   UNLOCK_DEBUG_MUTEX;
+}
+
+void astFlushMemory_( int leak, int *status ) {
+/*
+*+
+*  Name:
+*     astFlushMemory
+
+*  Purpose:
+*     Free all permanent and cached memory blocks.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     astFlushMemory( int leak );
+
+*  Description:
+*     This function should only be called once all use of AST by an
+*     application has finished. It frees any allocated but currently
+*     unused memory stored in an internal cache of unused memory
+*     pointers. (Note, it does not free any memory used permanently to
+*     store internal AST state information).
+*
+*     It is not normally necessary to call this function since the memory
+*     will be freed anyway by the operating system when the application
+*     terminates. However, it can be called if required in order to
+*     stop memory management tools such as valgrind from reporting that
+*     the memory has not been freed at the end of an application.
+*
+*     In addition, if "leak" is non-zero this function will also report
+*     an error if any active AST memory pointers remain which have not
+*     been freed (other than pointers for the cached and permanent
+*     memory described above). Leakage of active memory blocks can be
+*     investigated using astActiveMemory and astWatchMemory.
+
+*  Parameters:
+*     leak
+*        Should an error be reported if any non-permanent memory blocks
+*        are found to be active?
+
+*-
+*/
+
+/* Local Variables: */
+   Memory *next;
+   int nact;
+   int istat;
+
+/* Empty the cache. */
+   astMemCaching( astMemCaching( AST__TUNULL ) );
+
+/* Free and count all non-permanent memory blocks. */
+   nact = 0;
+   next = Active_List;
+   while( Active_List ) {
+      next = Active_List->next;
+      if( !Active_List->perm ) {
+         nact++;
+         FREE( Active_List );
+      }
+      Active_List = next;
+   }
+
+/* Report an error if any active pointers remained. if an error has
+   already occurred, use the existing status value. */
+   if( nact && leak ){
+
+      if( astOK ) {
+         istat = AST__INTER;
+      } else {
+         istat = astStatus;
+      }
+      astError( istat, "astFlushMemory: %d AST memory blocks have not "
+                "been released (programming error).", status, nact );
+
+   } else {
+      printf("astFlushMemory: All AST memory blocks were released correctly.\n" );
+   }
+}
+
+void astCheckMemory_( int *status ) {
+/*
+*+
+*  Name:
+*     astCheckMemory
+
+*  Purpose:
+*     Check that all AST memory blocks have been released.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     astCheckMemory
+
+*  Description:
+*     This macro reports an error if any active AST memory pointers
+*     remain which have not been freed (other than pointers for cached
+*     and "permanently allocated" memory). Leakage of active memory blocks
+*     can be investigated using astActiveMemory and astWatchMemory.
+*-
+*/
+
+/* Local Variables: */
+   Memory *next;
+   int nact;
+   int istat;
+
+/* Empty the cache. */
+   astMemCaching( astMemCaching( AST__TUNULL ) );
+
+/* Count all non-permanent memory blocks. */
+   nact = 0;
+   next = Active_List;
+   while( Active_List ) {
+      next = Active_List->next;
+      if( !Active_List->perm ) nact++;
+      Active_List = next;
+   }
+
+/* Report an error if any active pointers remained. If an error has
+   already occurred, use the existing status value. */
+   if( nact ){
+
+      if( astOK ) {
+         istat = AST__INTER;
+      } else {
+         istat = astStatus;
+      }
+      astError( istat, "astCheckMemory: %d AST memory blocks have not "
+                "been released (programming error).", status, nact );
+
+   } else {
+      printf("astCheckMemory: All AST memory blocks were released correctly.\n" );
+   }
+}
+
+static void Issue( Memory *mem, int *status ) {
+/*
+*  Name:
+*     Issue
+
+*  Purpose:
+*     Indicate that a pointer to a memory block has been issued.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void Issue( Memory *mem, int *status );
+
+*  Description:
+*     Initialises the extra debug items in the Memory header, and adds the
+*     Memory structure to the list of active memory blocks.
+
+*  Parameters:
+*     mem
+*        Pointer to the Memory structure.
+*     status
+*        Pointer to the inherited status value.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+
+/* Return if no pointer was supplied. */
+   if( !mem ) return;
+
+   LOCK_DEBUG_MUTEX;
+   astGET_GLOBALS(NULL);
+
+/* Store a unique identifier for this pointer. Unless global Keep_ID is
+   non-zero, a new identifier is used each time the pointer becomes active
+   (i.e. each time it is remove from the cache or malloced). */
+   if( !Keep_ID || mem->id < 0 ) mem->id = ++Next_ID;
+
+/* Record the file name and line number where it was issued. */
+   if( AST__GLOBALS && AST__GLOBALS->Error.Current_File ) {
+      strncpy( mem->file, AST__GLOBALS->Error.Current_File, sizeof(mem->file) );
+      mem->file[ sizeof(mem->file) - 1 ] = 0;
+      mem->line = AST__GLOBALS->Error.Current_Line;
+   } else {
+      mem->file[ 0 ] = 0;
+      mem->line = 0;
+   }
+
+/* Indicate if this is a permanent memory block (i.e. it will usually not
+   be freed by AST). */
+   mem->perm = Perm_Mem;
+
+/* Add it to the double linked list of active pointers. */
+   mem->next = Active_List;
+   mem->prev = NULL;
+   if( Active_List ) Active_List->prev = mem;
+   Active_List = mem;
+
+/* Report that the pointer is being issued. */
+   astMemoryUse( (void *) mem + SIZEOF_MEMORY, ISSUED );
+
+/* Update the current and peak memory usage. */
+   Current_Usage += mem->size + SIZEOF_MEMORY;
+   if( Current_Usage > Peak_Usage ) Peak_Usage = Current_Usage;
+
+/* If the current allocation is above the threshold set using
+   astMemoryWarning, issue a warning message, and then reset the threshold
+   to zero to prevent further warnings being issued, and to allow a
+   debugger breakpoint to be set. */
+   if( Current_Usage > Warn_Usage &&
+       Warn_Usage > 0 ) {
+      printf( "Warning - AST memory allocation has exceeded %ld bytes - "
+              "dumping catalogue of active memory blocks to file 'memory.dump'\n",
+              Warn_Usage );
+
+/* Create a file holding the details of all currently active memory blocks. It can be
+   examined using topcat. */
+      FILE *fd = fopen( "memory.dump", "w" );
+      if( fd ) {
+         Memory *next;
+
+         fprintf( fd, "# id size perm file line\n");
+         next = Active_List;
+         if( next ) {
+            while( next ) {
+               if( !next->perm ) {
+                  fprintf( fd, "%d %zu %d %s %d\n", next->id, next->size,
+                           next->perm, next->file, next->line );
+               }
+               next = next->next;
+            }
+         }
+
+         fclose(fd );
+      }
+
+      Warn_Usage  = 0;
+   }
+
+   UNLOCK_DEBUG_MUTEX;
+}
+
+static void DeIssue( Memory *mem, int *status ) {
+/*
+*  Name:
+*     DeIssue
+
+*  Purpose:
+*     Indicate that a pointer to a memory block has been freed.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void DeIssue( Memeory *mem, int *status );
+
+*  Description:
+*     Initialises the extra debug items in the Memory header, and adds the
+*     Memory structure to the list of active memory blocks.
+
+*  Parameters:
+*     mem
+*        Pointer to the Memory structure.
+*     status
+*        Pointer to the inherited status value.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   Memory *next;
+   Memory *prev;
+
+/* Return if no pointer was supplied. */
+   if( !mem ) return;
+
+   LOCK_DEBUG_MUTEX;
+   astGET_GLOBALS(NULL);
+
+/* Report that the pointer is being freed. */
+   astMemoryUse( (void *) mem + SIZEOF_MEMORY, FREED );
+
+/* Remove the block from the double linked list of active pointers. */
+   next = mem->next;
+   prev = mem->prev;
+   if( prev ) prev->next = next;
+   if( next ) next->prev = prev;
+   if( mem == Active_List ) Active_List = next;
+   mem->next = NULL;
+   mem->prev = NULL;
+
+/* Update the current memory usage. */
+   Current_Usage -= mem->size + SIZEOF_MEMORY;
+
+   UNLOCK_DEBUG_MUTEX;
+}
+
+
+#endif
+
+
+
+
+
+
+/* The next functions are used only when profiling AST application. */
+#ifdef MEM_PROFILE
+
+
+void astStartTimer_( const char *file, int line, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astStartTimer
+
+*  Purpose:
+*     Measure the time spent until the corresponding call to astStopTimer.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void astStartTimer( const char *name );
+
+*  Description:
+*     This function looks for a timer with the specified name within the
+*     current parent timer. If no timer with the given name is found, a
+*     new timer is created and initialised to zero. The current absolute
+*     time (elapsed, user and system) is recorded in the timer. The new
+*     timer then becomes the current timer.
+
+*  Parameters:
+*     name
+*        A label for the timer. This should be unique within the
+*        enclosing parent timer.
+
+*  Notes:
+*     - This function should only be used in a single-threaded environment.
+*     - This function returns without action if timers are currently
+*     disabled (see astEnableTimers).
+
+*-
+*/
+
+/* Local Variables: */
+   int n, found, i;
+   AstTimer *t;
+   struct tms buf;
+
+/* Check inherited status. Also return if timers are currently disabled. */
+   if( !Enable_Timers || *status != 0 ) return;
+
+/* See if a timer with the given name exists in the list of child timers
+   within the current timer. */
+   found = 0;
+   if( Current_Timer ) {
+      for( i = 0; i < Current_Timer->nchild; i++ ) {
+         t = Current_Timer->children[ i ];
+         if( !strcmp( t->name, name ) ) {
+            found = 1;
+            break;
+         }
+      }
+   }
+
+/* If not, create and initialise one now, and add it into the list of
+   children within the current timer. */
+   if( !found ) {
+      t = astMalloc( sizeof( AstTimer ) );
+      t->id = Timer_Count++;
+      t->et = 0;
+      t->ut = 0;
+      t->st = 0;
+      t->nentry = 0;
+      t->name = name;
+      t->file = file;
+      t->line = line;
+      t->parent = Current_Timer;
+      t->nchild = 0;
+      t->children = NULL;
+
+      if( Current_Timer ) {
+         n = (Current_Timer->nchild)++;
+         Current_Timer->children = astGrow( Current_Timer->children,
+                                            sizeof( AstTimer *),
+                                            Current_Timer->nchild );
+         Current_Timer->children[ n ] = t;
+      }
+   }
+
+/* Record the current absolute times (elapsed, user and system) within
+   the new timer. */
+   t->e0 = times(&buf);
+   t->u0 = buf.tms_utime;
+   t->s0 = buf.tms_stime;
+
+/* Increment the number of entries into the timer. */
+   (t->nentry)++;
+
+/* Use the new timer as the current timer until the corresponding call to
+   astStopTimer. */
+   Current_Timer = t;
+}
+
+void astEnableTimers_( int enable, int *status ) {
+/*
+*+
+*  Name:
+*     astEnableTimers
+
+*  Purpose:
+*     Set a global flag indicating if the use of AST timers is enabled.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void astStartTimer( int enable );
+
+*  Description:
+*     This function sets a global flag that enables otr disables the user
+*     of AST Timers. If timers are disabled, the astStartTimer and
+*     astStopTimer functions will return without action.
+
+*  Parameters:
+*     enable
+*        If non-zero, timers will be used.
+
+*  Notes:
+*     - This function should only be used in a single-threaded environment.
+
+*-
+*/
+   Enable_Timers = enable;
+}
+
+void astStopTimer_( int *status ) {
+/*
+*+
+*  Name:
+*     astStopTimer
+
+*  Purpose:
+*     Record the time spent since the corresponding call to astStartTimer.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     void astStopTimer;
+
+*  Description:
+*     This function obtains the time increments since the corresponding
+*     call to astStartTimer, and adds these increments onto the total
+*     times stored in the current timer. It then changes the current
+*     timer to be the parent timer associated the current timer on entry.
+*
+*     If the current timer on entry has no parent (i.e. is a top level
+*     timer), the times spent in the top-level timer, and all its
+*     descendent timers, are displayed.
+
+*  Notes:
+*     - This function should only be used in a single-threaded environment.
+*     - This function returns without action if timers are currently
+*     disabled (see astEnableTimers).
+
+*-
+*/
+
+/* Local Variables: */
+   AstTimer *flat;
+   AstTimer *t;
+   int i;
+   int nflat;
+   struct tms buf;
+
+/* Check inherited status. Also return if timers are currently disabled. */
+   if( !Enable_Timers || !Current_Timer || *status != 0 ) return;
+
+/* Get the current absolute times, and thus find the elapsed times since the
+   corresponding call to astStartTimer. Use these elapsed times to increment
+   the total times spent in the timer. */
+   Current_Timer->et += ( times(&buf) - Current_Timer->e0 );
+   Current_Timer->st += ( buf.tms_stime - Current_Timer->s0 );
+   Current_Timer->ut += ( buf.tms_utime - Current_Timer->u0 );
+
+/* If this is a top level timer, display the times spent in the current
+   timer, and in all its descendent timers. This also frees the memory
+   used by the timers. */
+   if( !Current_Timer->parent ) {
+      flat = NULL;
+      nflat = 0;
+      Current_Timer = ReportTimer( Current_Timer, 0, &flat, &nflat, status );
+
+/* Sort and display the flat list of timers, then free the memory used by
+   the flat list. */
+      qsort( flat, nflat, sizeof( AstTimer), CompareTimers2 );
+      printf("\n\n");
+      t = flat;
+      for( i = 0; i < nflat; i++,t++ ) {
+         printf( "%s (%s:%d): ", t->name, t->file, t->line );
+         printf( "elapsed=%ld ", (long int) t->et );
+/*
+         printf( "system=%ld ", (long int) t->st );
+         printf( "user=%ld ", (long int) t->ut );
+*/
+         printf( "calls=%d ", t->nentry );
+         printf("\n");
+      }
+      flat = astFree( flat );
+
+/* If this is not a top level timer, restore the parent timer as the
+   curent timer. */
+   } else {
+      Current_Timer = Current_Timer->parent;
+   }
+}
+
+static AstTimer *ReportTimer( AstTimer *t, int ind, AstTimer **flat,
+                              int *nflat, int *status ) {
+/*
+*  Name:
+*     ReportTimer
+
+*  Purpose:
+*     Free and report the times spent in a given timer, and all descendents.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "memory.h"
+*     AstTimer *ReportTimer( AstTimer *t, int ind, AstTimer **flat,
+*                            int *nflat, int *status )
+
+*  Description:
+*     This routines reports to standard output the times spent in the
+*     supplied timer. It then calls itself recursively to report the times
+*     spent in each of the child timers of the supplied timer.
+*
+*     It also frees the memory used to hold the supplied timer.
+
+*  Parameters:
+*     t
+*        Pointer to the AstTimer structure.
+*     ind
+*        The number of spaces of indentation to display before the timer
+*        details.
+*     flat
+*        Address of a pointer to the start of an array of AstTimers. The
+*        number of elements in this array is given by "*nflat". Each
+*        Timer in this array holds the accumulated total for all entries
+*        into a given timer, from all parent contexts.
+*     nflat
+*        Address of an int holding the current length of the "*flat" array.
+*     status
+*        Pointer to the inherited status value.
+*/
+
+/* Local Variables: */
+   int found;
+   int i;
+   AstTimer *ft;
+   AstTimer *parent;
+
+/* Check inherited status */
+   if( *status != 0 ) return NULL;
+
+/* Display a single line of text containing the times stored in the supplied
+   timer, preceded by the requested number of spaces. */
+   for( i = 0; i < ind; i++ ) printf(" ");
+   printf( "%s (%s:%d): ", t->name, t->file, t->line );
+
+   printf( "id=%d ", t->id );
+   printf( "elapsed=%ld ", (long int) t->et );
+/*
+   printf( "system=%ld ", (long int) t->st );
+   printf( "user=%ld ", (long int) t->ut );
+*/
+   printf( "calls=%d ", t->nentry );
+
+/* If there are any children, end the line with an opening bvrace. */
+   if( t->nchild ) printf("{");
+   printf("\n");
+
+/* If there is more than one child, sort them into descending order of
+   elapsed time usage. */
+   if( t->nchild > 1 ) qsort( t->children, t->nchild, sizeof( AstTimer * ),
+                              CompareTimers );
+
+/* Increment the indentation and call this function recursively to
+   display and free each child timer. */
+   ind += 3;
+   for( i = 0; i < t->nchild; i++ ) {
+      (t->children)[ i ] = ReportTimer( (t->children)[ i ], ind, flat,
+                                        nflat, status );
+   }
+
+/* Delimit the children by displaying a closing brace. */
+   if( t->nchild ) {
+      for( i = 0; i < ind - 3; i++ ) printf(" ");
+      printf("}\n");
+   }
+
+/* See if this timer is contained within itself at a higher level. */
+   parent = t->parent;
+   while( parent && ( parent->line != t->line ||
+                      strcmp( parent->file, t->file ) ) ) {
+      parent = parent->parent;
+   }
+
+/* If not, search for a timer in the "flat" array of timers that has the same
+   source file and line number. */
+   if( !parent ) {
+      found = 0;
+      ft = *flat;
+      for( i = 0; i < *nflat; i++, ft++ ) {
+         if( ft->line == t->line &&
+             !strcmp( ft->file, t->file ) ) {
+            found = 1;
+            break;
+         }
+      }
+
+/* If not found, add a new timer to the end of the "flat" array and
+   initialise it. */
+      if( !found ) {
+         i = (*nflat)++;
+         *flat = astGrow( *flat, *nflat, sizeof( AstTimer ) );
+         ft = (*flat) + i;
+         ft->id = 0;
+         ft->et = t->et;
+         ft->ut = t->ut;
+         ft->st = t->st;
+         ft->nentry = t->nentry;
+         ft->name = t->name;
+         ft->file = t->file;
+         ft->line = t->line;
+         ft->parent = NULL;
+         ft->nchild = 0;
+         ft->children = NULL;
+
+
+/* If found, increment the properites to include the supplied timer. */
+      } else {
+         ft->et += t->et;
+         ft->ut += t->ut;
+         ft->st += t->st;
+         ft->nentry += t->nentry;
+      }
+   }
+
+/* Free the memory used by the supplied timer. */
+   t->children = astFree( t->children );
+   return astFree( t );
+}
+
+
+static int CompareTimers( const void *a, const void *b ){
+   return ((*((AstTimer **) b ))->et) - ((*((AstTimer **) a ))->et);
+}
+
+static int CompareTimers2( const void *a, const void *b ){
+   return (((AstTimer *) b )->et) - (((AstTimer *) a )->et);
+}
+
+#endif
diff --git a/ast/memory.h b/ast/memory.h
new file mode 100644
index 0000000..7a631fb
--- /dev/null
+++ b/ast/memory.h
@@ -0,0 +1,347 @@
+#if !defined( MEMORY_INCLUDED )  /* Include this file only once */
+#define MEMORY_INCLUDED
+/*
+*+
+*  Name:
+*     memory.h
+
+*  Purpose:
+*     Define the interface to the Memory module.
+
+*  Description:
+*     This module defines functions which wrap up and extend the
+*     standard C functions for performing memory allocation. They
+*     provide better security against memory leaks, etc., and should
+*     not be inter-mixed with the standard C functions.
+*
+*     Note that this module is not a class implementation, although it
+*     resembles one.
+
+*  Functions Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astAppendString
+*           Append a string to another string which grows dynamically.
+*        astCalloc
+*           Allocate memory.
+*        astChrMatch
+*           Case-insensitive string comparison.
+*        astChrMatchN
+*           Case-insensitive string comparison of an most N characters.
+*        astFree
+*           Free previously allocated memory.
+*        astGrow
+*           Allocate memory for an adjustable array.
+*        astMalloc
+*           Allocate memory.
+*        astRealloc
+*           Change the size of a dynamically allocated region of memory.
+*        astSizeOf
+*           Determine the size of a dynamically allocated region of memory.
+*        astStore
+*           Store data in dynamically allocated memory.
+*        astString
+*           Create a C string from an array of characters.
+*        astStringArray
+*           Create an array of C strings from an array of characters.
+*        astStringCase
+*           Convert a string to upper or lower case.
+*        astChrLen
+*           Returns length of a string without trailing white space, etc.
+*        astChrTrunc
+*           Terminate a string to exclude trailing spaces.
+*        astSscanf
+*           Like sscanf, but fixes certain platform-specific bugs in the
+*           native sscanf implementation.
+*        astTSizeOf
+*           Determine the total size of a dynamically allocated region of memory.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     8-JAN-1996 (RFWS):
+*        Original version.
+*     26-JAN-1996 (RFWS)
+*        Added function interfaces.
+*     20-JUN-1996 (RFWS):
+*        Added astString.
+*     15-JUL-1996 (RFWS):
+*        Use improved prologue style, etc. and make all functions protected.
+*     11-SEP-1996 (RFWS):
+*        Added astStringArray (original written by DSB).
+*     18-MAR-1998 (RFWS):
+*        Make interface available for writing foreign language and
+*        graphics interfaces, etc.
+*     18-MAR-1998 (RFWS):
+*        Added explicit arguments to function macros.
+*     29-JAN-2002 (DSB):
+*        Added astChrLen and astSscanf.
+*     15-NOV-2002 (DSB):
+*        Added astChrMatch astChrMatchN.
+*     23-FEB-2006 (DSB):
+*        Added astMemCaching and AST__TUNULL.
+*     2-MAR-2006 (DSB):
+*        Only use astSscanf if the system on which AST was configured
+*        showed the non-ANSI behaviour reported by Bill Joye.
+*     27-JUN-2007 (DSB):
+*        Added astIsDynamic.
+*     25-OCT-2007 (DSB):
+*        Added astRemoveLeadingBlanks.
+*     22-FEB-2008 (DSB):
+*        Added astChrSub.
+*     19-MAY-2010 (DSB):
+*        Added astStringCase.
+*     26-MAR-2015 (DSB):
+*        Added astChrTrunc.
+
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Configuration results. */
+/* ---------------------- */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* C header files. */
+/* --------------- */
+#include <stddef.h>
+#include "error.h"
+
+/* Macros. */
+/* ======= */
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+#define AST__TUNULL -99999
+#define AST__TUNULLC "<NOTUNEPAR>"
+
+/* Type definitions */
+/* ================ */
+
+#if defined(astCLASS)
+
+/* Header for allocated memory. */
+/* ---------------------------- */
+/* This stores a "magic" value so that dynamically allocated memory
+   can be recognised, together with the allocated size. It also
+   ensures correct alignment. */
+typedef struct Memory {
+   struct Memory *next;
+   unsigned long magic;
+   size_t size;
+
+#ifdef MEM_DEBUG
+   struct Memory *prev; /* Pointer to the previous linked Memory structure */
+   int id;      /* A unique identifier for every allocated memory chunk */
+   int perm;    /* Is this chunk part of an acceptable once-off "memory leak"? */
+   int line;    /* Line number in "file" (below). */
+   char file[50];/* The source file that made the top-level call to AST */
+#endif
+
+} Memory;
+
+/* Define the largest size of a cached memory block in bytes. This does
+   not include the size of the Memory header. This does not need to be
+   too big because the vast majority of memory blocks allocated by AST are
+   less than a few hundred bytes. */
+#define MXCSIZE 300
+
+#endif
+
+
+#if defined(THREAD_SAFE) && defined(astCLASS)
+
+/* Define a structure holding all data items that are global within the
+   memory.c file. */
+typedef struct AstMemoryGlobals {
+   size_t Sizeof_Memory;
+   int Cache_Init;
+   int Use_Cache;
+   Memory *Cache[ MXCSIZE + 1 ];
+
+} AstMemoryGlobals;
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+
+#if defined(THREAD_SAFE) && defined(astCLASS)
+void astInitMemoryGlobals_( AstMemoryGlobals * );
+#endif
+
+#if defined(astCLASS) || 1       /* Nominally protected, but available for */
+                                 /* use in developing (e.g.) foreign */
+                                 /* language or graphics interfaces. */
+int astMemCaching_( int, int * );
+void astChrCase_( const char *, char *, int, int, int * );
+char **astChrSplit_( const char *, int *, int * );
+char **astChrSplitRE_( const char *, const char *, int *, const char **, int * );
+char **astChrSplitC_( const char *, char, int *, int * );
+int astChrMatch_( const char *, const char *, int * );
+int astChrMatchN_( const char *, const char *, size_t, int * );
+char **astStringArray_( const char *, int, int, int * );
+char *astStringCase_( const char *, int, int * );
+char *astString_( const char *, int, int * );
+int astSscanf_( const char *str, const char *format, ...);
+size_t astSizeOf_( const void *, int * );
+int astIsDynamic_( const void *, int * );
+size_t astTSizeOf_( const void *, int * );
+void *astFree_( void *, int * );
+void *astFreeDouble_( void *, int * );
+void *astGrow_( void *, int, size_t, int * );
+void *astCalloc_( size_t, size_t, int * );
+void *astMalloc_( size_t, int, int * );
+void *astRealloc_( void *, size_t, int * );
+void *astStore_( void *, const void *, size_t, int * );
+size_t astChrLen_( const char *, int * );
+double astChr2Double_( const char *, int * );
+void astRemoveLeadingBlanks_( char *, int * );
+char *astAppendString_( char *, int *, const char *, int * );
+char *astAppendStringf_( char *, int *, const char *, ... )__attribute__((format(printf,3,4)));
+char *astChrSub_( const char *, const char *, const char *[], int, int * );
+void astChrTrunc_( char *, int * );
+
+#ifdef MEM_PROFILE
+void astStartTimer_( const char *, int, const char *, int * );
+void astStopTimer_( int * );
+void astEnableTimers_( int, int * );
+#endif
+
+
+#ifdef MEM_DEBUG
+void astActiveMemory_( const char * );
+void astWatchMemory_( int );
+void astCheckMemory_( int * );
+void astFlushMemory_( int, int * );
+int astMemoryTune_( const char *, int, int * );
+void *astMemoryPtr_( int );
+void astMemoryAlarm_( const char * );
+void astMemoryStats_( int , size_t *, size_t *, int * );
+void astMemoryWarning_( size_t, int * );
+void astMemoryUse_( const void *, const char *, int * );
+int astMemoryId_( const void *, int * );
+void astBeginPM_( int * );
+void astEndPM_( int * );
+#endif
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These wrap up the functions defined by this module. */
+
+#define astCalloc(nmemb,size) astERROR_INVOKE(astCalloc_(nmemb,size,STATUS_PTR))
+#define astChrMatch(str1,str2) astERROR_INVOKE(astChrMatch_(str1,str2,STATUS_PTR))
+#define astChrMatchN(str1,str2,n) astERROR_INVOKE(astChrMatchN_(str1,str2,n,STATUS_PTR))
+#define astFree(ptr) astERROR_INVOKE(astFree_(ptr,STATUS_PTR))
+#define astFreeDouble(ptr) astERROR_INVOKE(astFreeDouble_(ptr,STATUS_PTR))
+#define astGrow(ptr,n,size) astERROR_INVOKE(astGrow_(ptr,n,size,STATUS_PTR))
+#define astMalloc(size) astERROR_INVOKE(astMalloc_(size,0,STATUS_PTR))
+#define astMemCaching(flag) astERROR_INVOKE(astMemCaching_(flag,STATUS_PTR))
+#define astRealloc(ptr,size) astERROR_INVOKE(astRealloc_(ptr,size,STATUS_PTR))
+#define astSizeOf(ptr) astERROR_INVOKE(astSizeOf_(ptr,STATUS_PTR))
+#define astIsDynamic(ptr) astERROR_INVOKE(astIsDynamic_(ptr,STATUS_PTR))
+#define astTSizeOf(ptr) astERROR_INVOKE(astTSizeOf_(ptr,STATUS_PTR))
+#define astStore(ptr,data,size) astERROR_INVOKE(astStore_(ptr,data,size,STATUS_PTR))
+#define astAppendString(str1,nc,str2) astERROR_INVOKE(astAppendString_(str1,nc,str2,STATUS_PTR))
+#define astAppendStringf astAppendStringf_
+#define astString(chars,nchars) astERROR_INVOKE(astString_(chars,nchars,STATUS_PTR))
+#define astStringArray(chars,nel,len) astERROR_INVOKE(astStringArray_(chars,nel,len,STATUS_PTR))
+#define astStringCase(string,toupper) astERROR_INVOKE(astStringCase_(string,toupper,STATUS_PTR))
+#define astChrLen(string) astERROR_INVOKE(astChrLen_(string,STATUS_PTR))
+#define astChrTrunc(string) astERROR_INVOKE(astChrTrunc_(string,STATUS_PTR))
+#define astChr2Double(string) astERROR_INVOKE(astChr2Double_(string,STATUS_PTR))
+#define astRemoveLeadingBlanks(string) astERROR_INVOKE(astRemoveLeadingBlanks_(string,STATUS_PTR))
+#define astChrSub(test,template,subs,nsub) astERROR_INVOKE(astChrSub_(test,template,subs,nsub,STATUS_PTR))
+#define astChrCase(in,out,upper,blen) astERROR_INVOKE(astChrCase_(in,out,upper,blen,STATUS_PTR))
+
+#if defined(astCLASS) /* Protected */
+#define astMallocInit(size) astMalloc_(size,1,STATUS_PTR)
+#endif
+
+#ifdef HAVE_NONANSI_SSCANF
+#define astSscanf astERROR_INVOKE(astSscanf_)
+#else
+#define astSscanf astERROR_INVOKE(sscanf)
+#endif
+#define astChrSplit(str,n) astERROR_INVOKE(astChrSplit_(str,n,STATUS_PTR))
+#define astChrSplitC(str,c,n) astERROR_INVOKE(astChrSplitC_(str,c,n,STATUS_PTR))
+#define astChrSplitRE(str,c,n,m) astERROR_INVOKE(astChrSplitRE_(str,c,n,m,STATUS_PTR))
+
+
+#ifdef MEM_PROFILE
+#define astStartTimer(name) astERROR_INVOKE(astStartTimer_(__FILE__,__LINE__,name,STATUS_PTR))
+#define astStopTimer astERROR_INVOKE(astStopTimer_(STATUS_PTR))
+#define astEnableTimers(enable) astERROR_INVOKE(astEnableTimers_(enable,STATUS_PTR))
+#endif
+
+
+/* Functions used for debugging memory leaks, etc */
+#ifdef MEM_DEBUG
+
+#define astActiveMemory(label) astERROR_INVOKE(astActiveMemory_(label))
+#define astMemoryTune(name,value) astERROR_INVOKE(astMemoryTune_(name,value,STATUS_PTR))
+#define astWatchMemory(id) astERROR_INVOKE(astWatchMemory_(id))
+#define astCheckMemory astERROR_INVOKE(astCheckMemory_(STATUS_PTR))
+#define astFlushMemory(leak) astERROR_INVOKE(astFlushMemory_(leak,STATUS_PTR))
+#define astBeginPM astERROR_INVOKE(astBeginPM_(STATUS_PTR))
+#define astEndPM astERROR_INVOKE(astEndPM_(STATUS_PTR))
+#define astMemoryPtr(id) astERROR_INVOKE(astMemoryPtr_(id))
+#define astMemoryAlarm(text) astERROR_INVOKE(astMemoryAlarm_(text))
+#define astMemoryStats(reset,peak,current) astERROR_INVOKE(astMemoryStats_(reset,peak,current,STATUS_PTR))
+#define astMemoryWarning(threshold) astERROR_INVOKE(astMemoryWarning_(threshold,STATUS_PTR))
+#define astMemoryUse(ptr,text) astERROR_INVOKE(astMemoryUse_(ptr,text,STATUS_PTR))
+#define astMemoryId(ptr) astERROR_INVOKE(astMemoryId_(ptr,STATUS_PTR))
+#else
+
+#define astActiveMemory(label)
+#define astMemoryTune(name,value)
+#define astWatchMemory(id)
+#define astCheckMemory
+#define astFlushMemory(leak)
+#define astBeginPM
+#define astEndPM
+#define astMemoryPtr(id) NULL
+#define astMemoryAlarm(text)
+#define astMemoryStats(reset,peak,current)
+#define astMemoryUse(ptr,text)
+#define astMemoryId(ptr)
+#define astMemoryWarning(threshold)
+
+#endif
+
+#endif
+
+
+
diff --git a/ast/normmap.c b/ast/normmap.c
new file mode 100644
index 0000000..3739739
--- /dev/null
+++ b/ast/normmap.c
@@ -0,0 +1,1720 @@
+/*
+*class++
+*  Name:
+*     NormMap
+
+*  Purpose:
+*     Normalise coordinates using a supplied Frame.
+
+*  Constructor Function:
+c     astNormMap
+f     AST_NORMMAP
+
+*  Description:
+*     The NormMap class implements a Mapping which normalises coordinate
+*     values using the
+c     astNorm function
+f     AST_NORM routine
+*     of a supplied Frame. The number of inputs and outputs of a NormMap
+*     are both equal to the number of axes in the supplied Frame.
+*
+*     The forward and inverse transformation of a NormMap are both
+*     defined but are identical (that is, they do not form a real inverse
+*     pair in that the inverse transformation does not undo the
+*     normalisation, instead it reapplies it). However, the
+c     astSimplify
+f     AST_SIMPLIFY
+*     function will replace neighbouring pairs of forward and inverse
+*     NormMaps by a single UnitMap (so long as the Frames encapsulated by
+*     the two NormMaps are equal - i.e. have the same class and the same
+*     attribute values). This means, for instance, that if a CmpMap contains
+*     a NormMap, the CmpMap will still cancel with its own inverse.
+
+*  Inheritance:
+*     The NormMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The NormMap class does not define any new attributes beyond
+*     those which are applicable to all Mappings.
+
+*  Functions:
+c     The NormMap class does not define any new functions beyond those
+f     The NormMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     11-JUL-2005 (DSB):
+*        Original version.
+*     23-AUG-2006 (DSB):
+*        Override astEqual.
+*     8-NOV-2016 (DSB):
+*        - Allow multiple identical NormMaps in series to be simplified to
+*        a single NormMap.
+*        - Allow a NormMap that contains a basic Frame to be simplified
+*        to a UnitMap.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS NormMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "unitmap.h"             /* Unit Mappings */
+#include "normmap.h"             /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int *(* parent_mapsplit)( AstMapping *, int, const int *, AstMapping **, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(NormMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(NormMap,Class_Init)
+#define class_vtab astGLOBAL(NormMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstNormMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstNormMap *astNormMapId_( void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *RemoveRegions( AstMapping *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+/* Member functions. */
+/* ================= */
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two NormMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "normmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     NormMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two NormMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a NormMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the NormMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstNormMap *that;
+   AstNormMap *this;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two NormMap structures. */
+   this = (AstNormMap *) this_object;
+   that = (AstNormMap *) that_object;
+
+/* Check the second object is a NormMap. We know the first is a
+   NormMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsANormMap( that ) ) {
+
+/* Check the Invert flags for the two NormMaps are equal. */
+      if( astGetInvert( this ) == astGetInvert( that ) ) {
+
+/* Check the two Frames are equal. */
+         if( astEqual( this->frame, that->frame ) ) {
+            result = 1;
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+void astInitNormMapVtab_(  AstNormMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitNormMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a NormMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "normmap.h"
+*     void astInitNormMapVtab( AstNormMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     NormMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the NormMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsANormMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+   mapping->RemoveRegions = RemoveRegions;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_mapsplit = mapping->MapSplit;
+   mapping->MapSplit = MapSplit;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+   mapping->MapSplit = MapSplit;
+   mapping->Rate = Rate;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetDump( vtab, Dump, "NormMap", "Normalise axis values" );
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     NormMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstNormMap *this;       /* Pointer to NormMap structure */
+   int result;             /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the NormMap structure. */
+   this = (AstNormMap *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->frame, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a NormMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     NormMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated NormMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated NormMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated NormMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the NormMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        NormMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated NormMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frm1;
+   AstFrame *frm2;
+   AstMapping *smap;
+   AstNormMap *map;
+   AstNormMap *nmap1;
+   AstNormMap *nmap2;
+   const char *class;
+   int cancel;
+   int map_inv;
+   int nax;
+   int result;
+
+/* Initialise. */
+   result = -1;
+
+/* Check the inherited status. */
+   if ( !astOK ) return result;
+
+/* Initialisation to avoid compiler warnings. */
+   nax = 0;
+
+/* Get a pointer to this NormMap. */
+   map = (AstNormMap *) this;
+
+/* Temporarily set its Invert flag to the requested value. */
+   map_inv = astGetInvert( map );
+   astSetInvert( map, ( *invert_list )[ where ] );
+
+/* First try to simplify the NormMap by simplifying its encapsulated
+   Frame. Get its class. */
+   smap = astSimplify( map->frame );
+   class = astGetClass( smap );
+
+/* If any simplification took place, create a new NormMap with the
+   simplified Frame. */
+   if( smap != (AstMapping *) map->frame ) {
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = (AstMapping *) astNormMap( (AstFrame *) smap,
+                                                          "", status );
+      result = where;
+
+/* If the encapsulated Frame is a basic Frame, we can safely assume that
+   the astNorm method does nothing, and so we can replace the NormMap with
+   a UnitMap. */
+   } else if( class && !strcmp( class, "Frame" ) ) {
+      nax = astGetNin( smap );
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = (AstMapping *) astUnitMap( nax, "", status );
+      result = where;
+
+/* The only other simplications which can be performed are a) to cancel a NormMap
+   with its own inverse in series, or b) to remove duplicated adjacent
+   NormMaps in series. */
+   } else if( series ) {
+
+/* Indicate we have nothing to cancel with as yet. */
+      cancel = -1;
+
+/* First consider the lower neighbour. */
+      if( where > 0 && astIsANormMap( ( *map_list )[ where - 1 ] ) ) {
+
+/* Check the Invert flags are opposite */
+         if( ( *invert_list )[ where ] != ( *invert_list )[ where - 1 ] ) {
+            nmap1 = map;
+            nmap2 = (AstNormMap *) ( *map_list )[ where - 1 ];
+
+/* Check the encapsulated Frames are equal. */
+            frm1 = nmap1->frame;
+            frm2 = nmap2->frame;
+            if( astEqual( frm1, frm2 ) ) cancel = where - 1;
+            nax = astGetNout( nmap1 );
+         }
+      }
+
+/* Likewise consider the upper neighbour. */
+      if( cancel == -1 && where + 1 < *nmap &&
+          astIsANormMap( ( *map_list )[ where + 1 ] ) ) {
+
+         if( ( *invert_list )[ where ] != ( *invert_list )[ where + 1 ] ) {
+            nmap1 = map;
+            nmap2 = (AstNormMap *) ( *map_list )[ where + 1 ];
+            frm1 = nmap1->frame;
+            frm2 = nmap2->frame;
+            if( astEqual( frm1, frm2 ) ) cancel = where + 1;
+            nax = astGetNin( nmap1 );
+         }
+      }
+
+/* If we can cancel with a neightbour, do so. */
+      if( cancel != -1 ) {
+         (void) astAnnul( ( *map_list )[ where ] );
+         (void) astAnnul( ( *map_list )[ cancel ] );
+         ( *map_list )[ where ] = (AstMapping *) astUnitMap( nax, "", status );
+         ( *invert_list )[ where ] = 0;
+         ( *map_list )[ cancel ] = (AstMapping *) astUnitMap( nax, "", status );
+         ( *invert_list )[ cancel ] = 0;
+          result = ( cancel < where ) ? cancel : where;
+
+/* Otherwise, see if the nominated NormMap is followed by one or more
+   identical NormMaps, in which case we can remove all but one of the
+   NormMaps. */
+      } else {
+
+/* Loop round all subsequent Mappings until the end of the list is
+   reached, or a Mapping that is not a NormMap is reached. On each pass
+   we replace the NormMap with a UnitMap. */
+         nax = astGetNin( map );
+         cancel = where;
+         while( ++cancel < *nmap &&
+                astIsANormMap( ( *map_list )[ cancel ] ) ) {
+
+/* Check the Invert flags are equal. */
+            if( ( *invert_list )[ where ] == ( *invert_list )[ cancel ] ) {
+
+/* Check the Frames are equal .*/
+               nmap2 = (AstNormMap *) ( *map_list )[ cancel ];
+               if( astEqual( map->frame, nmap2->frame ) ) {
+
+/* Replace the later NormMap with a UnitMap. */
+                  (void) astAnnul( ( *map_list )[ cancel ] );
+                  ( *map_list )[ cancel ] = (AstMapping *) astUnitMap( nax,
+                                                               "", status );
+
+/* We return the index of the first modified Mapping in the list, so do
+   not update "result" if this is not the first change. */
+                  if( result == -1 ) result = cancel;
+               }
+            }
+         }
+      }
+   }
+
+/* Free resources. */
+   smap = astAnnul( smap );
+
+/* Reset the original Invert attribute for the specified NormMap */
+   astSetInvert( map, map_inv );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     NormMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "normmap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     NormMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing NormMap. This is only possible if the specified inputs
+*     correspond to some subset of the NormMap outputs. That is, there
+*     must exist a subset of the NormMap outputs for which each output
+*     depends only on the selected NormMap inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied NormMap, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the NormMap to be split (the NormMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied NormMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied NormMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied NormMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstFrame *frm2;            /* Pointer to new Frame */
+   AstNormMap *this;          /* Pointer to NormMap structure */
+   int *result;               /* Pointer to returned array */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the parent astMapSplit method to see if it can do the job. */
+   result = (*parent_mapsplit)( this_map, nin, in, map, status );
+
+/* If not, we provide a special implementation here. */
+   if( !result ) {
+
+/* Get a pointer to the NormMap structure. */
+      this = (AstNormMap *) this_map;
+
+/* Pick the requried axes from the encapsulated Frame. */
+      frm2 = astPickAxes( this->frame, nin, in, NULL );
+
+/* Create a new NormMap from this. */
+      *map = (AstMapping *) astNormMap( frm2, "", status );
+
+/* The returned list of output axes is a copy the supplied list of input
+   axes. */
+      result = astStore( NULL, in, sizeof( int )*(size_t) nin );
+
+/* Free resources. */
+      frm2 = astAnnul( frm2 );
+   }
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "normmap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     NormMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*/
+
+   return ( ax1 == ax2 ) ? 1.0 : 0.0;
+}
+
+static AstMapping *RemoveRegions( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     RemoveRegions
+
+*  Purpose:
+*     Remove any Regions from a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "normmap.h"
+*     AstMapping *RemoveRegions( AstMapping *this, int *status )
+
+*  Class Membership:
+*     NormMap method (over-rides the astRemoveRegions method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function searches the supplied Mapping (which may be a
+*     compound Mapping such as a CmpMap) for any component Mappings
+*     that are instances of the AST Region class. It then creates a new
+*     Mapping from which all Regions have been removed. If a Region
+*     cannot simply be removed (for instance, if it is a component of a
+*     parallel CmpMap), then it is replaced with an equivalent UnitMap
+*     in the returned Mapping.
+*
+*     The implementation provided by the NormMap class invokes the
+*     astRemoveRegions method on the encapsulated Frame, and returns a
+*     new NormMap containing the resulting Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the modified mapping.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *newfrm;             /* New component Frame */
+   AstMapping *result;           /* Result pointer to return */
+   AstNormMap *new;              /* Pointer to new MormMap */
+   AstNormMap *this;             /* Pointer to NormMap structure */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the NormMap. */
+   this = (AstNormMap *) this_mapping;
+
+/* Invoke the astRemoveRegions method on the component Frame. */
+   newfrm = astRemoveRegions( this->frame );
+
+/* If the Frame was not modified, just return a clone of the supplied
+   pointer. */
+   if( this->frame == newfrm ) {
+      result = astClone( this );
+
+/* Otherwise, we need to create a new NormMap to return. We take a deep
+   copy of the supplied NormMap and then modify the Frame so that we
+   retain any extra information in the supplied NormMap. */
+   } else {
+      new = astCopy( this );
+      (void) astAnnul( new->frame );
+      new->frame = astClone( newfrm );
+      result = (AstMapping *) new;
+   }
+
+/* Free resources. */
+   newfrm = astAnnul( newfrm );
+
+/* Annul the returned Mapping if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a NormMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "normmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     NormMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a NormMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required zoom
+*     factor.
+
+*  Parameters:
+*     this
+*        Pointer to the NormMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the NormMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstNormMap *map;              /* Pointer to NormMap to be applied */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *work;                 /* Work space for a single point */
+   int coord;                    /* Loop counter for coordinates */
+   int ncoord_in;                /* Number of coordinates per input point */
+   int npoint;                   /* Number of points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the NormMap. */
+   map = (AstNormMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   ncoord_in = astGetNcoord( in );
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+   work = astMalloc( sizeof( double )* ncoord_in );
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+      for ( point = 0; point < npoint; point++ ) {
+         for ( coord = 0; coord < ncoord_in; coord++ ) {
+            work[ coord ] = ptr_in[ coord ][ point ];
+         }
+         astNorm( map->frame, work );
+         for ( coord = 0; coord < ncoord_in; coord++ ) {
+            ptr_out[ coord ][ point ] = work[ coord ];
+         }
+      }
+   }
+
+/* Free resources */
+   work = astFree( work );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for NormMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for NormMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the
+*     Frame within the NormMap.
+*/
+
+/* Local Variables: */
+   AstNormMap *in;                /* Pointer to input NormMap */
+   AstNormMap *out;               /* Pointer to output NormMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+   in = (AstNormMap *) objin;
+   out = (AstNormMap *) objout;
+   out->frame = astCopy( in->frame );
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for NormMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for NormMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstNormMap *this;              /* Pointer to NormMap */
+
+   this = (AstNormMap *) obj;
+   this->frame = astAnnul( this->frame );
+
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for NormMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the NormMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the NormMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstNormMap *this;             /* Pointer to the NormMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the NormMap structure. */
+   this = (AstNormMap *) this_object;
+
+/* Write out values representing the instance variables for the
+   NormMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* Frame. */
+/* ------ */
+   astWriteObject( channel, "Frame", 1, 1, this->frame,
+                   "Frame defining the normalisation" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsANormMap and astCheckNormMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(NormMap,Mapping)
+astMAKE_CHECK(NormMap)
+
+AstNormMap *astNormMap_( void *frame_void, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astNormMap
+f     AST_NORMMAP
+
+*  Purpose:
+*     Create a NormMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "normmap.h"
+c     AstNormMap *astNormMap( AstFrame *frame, const char *options, ... )
+f     RESULT = AST_NORMMAP( FRAME, OPTIONS, STATUS )
+
+*  Class Membership:
+*     NormMap constructor.
+
+*  Description:
+*     This function creates a new NormMap and optionally initialises its
+*     attributes.
+*
+*     A NormMap is a Mapping which normalises coordinate values using the
+c     astNorm function
+f     AST_NORM routine
+*     of the supplied Frame. The number of inputs and outputs of a NormMap
+*     are both equal to the number of axes in the supplied Frame.
+*
+*     The forward and inverse transformation of a NormMap are both
+*     defined but are identical (that is, they do not form a real inverse
+*     pair in that the inverse transformation does not undo the
+*     normalisation, instead it reapplies it). However, the
+c     astSimplify
+f     AST_SIMPLIFY
+*     function will replace neighbouring pairs of forward and inverse
+*     NormMaps by a single UnitMap (so long as the Frames encapsulated by
+*     the two NormMaps are equal - i.e. have the same class and the same
+*     attribute values). This means, for instance, that if a CmpMap contains
+*     a NormMap, the CmpMap will still cancel with its own inverse.
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        A pointer to the Frame which is to be used to normalise the
+*        supplied axis values.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new NormMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new NormMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astNormMap()
+f     AST_NORMMAP = INTEGER
+*        A pointer to the new NormMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* The Frame pointer */
+   AstNormMap *new;              /* Pointer to new NormMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate pointers to the Frame structures provided. */
+   frame = astCheckFrame( frame_void );
+
+/* Initialise the NormMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitNormMap( NULL, sizeof( AstNormMap ), !class_init, &class_vtab,
+                         "NormMap", frame );
+   if( astOK ) {
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new NormMap's attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new NormMap. */
+   return new;
+}
+
+AstNormMap *astNormMapId_( void *frame_void, const char *options, ... ) {
+/*
+*  Name:
+*     astNormMapId_
+
+*  Purpose:
+*     Create a NormMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "normmap.h"
+*     AstNormMap *astNormMapId_( int ncoord, double zoom,
+*                                const char *options, ... )
+
+*  Class Membership:
+*     NormMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astNormMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astNormMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astNormMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astNormMap_.
+
+*  Returned Value:
+*     The ID value associated with the new NormMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* The Frame pointer */
+   AstNormMap *new;              /* Pointer to new NormMap */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate pointers to the Frame structures provided. */
+   frame = astVerifyFrame( astMakePointer( frame_void ) );
+
+/* Initialise the NormMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitNormMap( NULL, sizeof( AstNormMap ), !class_init, &class_vtab,
+                         "NormMap", frame );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new NormMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new NormMap. */
+   return astMakeId( new );
+}
+
+AstNormMap *astInitNormMap_( void *mem, size_t size, int init,
+                             AstNormMapVtab *vtab, const char *name,
+                             AstFrame *frame, int *status ) {
+/*
+*+
+*  Name:
+*     astInitNormMap
+
+*  Purpose:
+*     Initialise a NormMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "normmap.h"
+*     AstNormMap *astInitNormMap( void *mem, size_t size, int init,
+*                                 AstNormMapVtab *vtab, const char *name,
+*                                 AstFrame *frame )
+
+*  Class Membership:
+*     NormMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new NormMap object. It allocates memory (if necessary) to accommodate
+*     the NormMap plus any additional data associated with the derived class.
+*     It then initialises a NormMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a NormMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the NormMap is to be initialised.
+*        This must be of sufficient size to accommodate the NormMap data
+*        (sizeof(NormMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the NormMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the NormMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the NormMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new NormMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     frame
+*        The Frame to use to do the normalisations.
+
+*  Returned Value:
+*     A pointer to the new NormMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstNormMap *new;              /* Pointer to new NormMap */
+   int ncoord;                   /* Number of input and output coords */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitNormMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Get the number of axes in the Frame. */
+   ncoord = astGetNaxes( frame );
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the NormMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstNormMap *) astInitMapping( mem, size, 0,
+                                        (AstMappingVtab *) vtab, name,
+                                        ncoord, ncoord, 1, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the NormMap data. */
+/* ---------------------------- */
+/* Store a pointer to the Frame. */
+      new->frame = astClone( frame );
+
+/* If an error occurred, clean up by deleting the new NormMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new NormMap. */
+   return new;
+}
+
+AstNormMap *astLoadNormMap_( void *mem, size_t size,
+                             AstNormMapVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadNormMap
+
+*  Purpose:
+*     Load a NormMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "normmap.h"
+*     AstNormMap *astLoadNormMap( void *mem, size_t size,
+*                                 AstNormMapVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     NormMap loader.
+
+*  Description:
+*     This function is provided to load a new NormMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     NormMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a NormMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the NormMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        NormMap data (sizeof(NormMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the NormMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the NormMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstNormMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new NormMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the NormMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "NormMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new NormMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstNormMap *new;              /* Pointer to the new NormMap */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this NormMap. In this case the
+   NormMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstNormMap );
+      vtab = &class_vtab;
+      name = "NormMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitNormMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built NormMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "NormMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Frame. */
+/* ------ */
+      new->frame = astReadObject( channel, "frame", NULL );
+
+/* If an error occurred, clean up by deleting the new NormMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new NormMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
diff --git a/ast/normmap.h b/ast/normmap.h
new file mode 100644
index 0000000..5336e56
--- /dev/null
+++ b/ast/normmap.h
@@ -0,0 +1,217 @@
+#if !defined( NORMMAP_INCLUDED ) /* Include this file only once */
+#define NORMMAP_INCLUDED
+/*
+*+
+*  Name:
+*     normmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the NormMap class.
+
+*  Invocation:
+*     #include "normmap.h"
+
+*  Description:
+*     This include file defines the interface to the NormMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The NormMap class implements Mappings which use a Frame to
+*     normalise the input axis values.
+
+*  Inheritance:
+*     The NormMap class inherits from the Mapping class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     11-JUL-2005 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "frame.h"               /* Coordinate Frames */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* NormMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstNormMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstFrame *frame;              /* Encapsulated Frame */
+} AstNormMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstNormMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+} AstNormMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstNormMapGlobals {
+   AstNormMapVtab Class_Vtab;
+   int Class_Init;
+} AstNormMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitNormMapGlobals_( AstNormMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(NormMap)          /* Check class membership */
+astPROTO_ISA(NormMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstNormMap *astNormMap_( void *, const char *, int *, ...);
+#else
+AstNormMap *astNormMapId_( void *, const char *, ... )__attribute__((format(printf,2,3)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstNormMap *astInitNormMap_( void *, size_t, int, AstNormMapVtab *,
+                             const char *, AstFrame *, int * );
+
+/* Vtab initialiser. */
+void astInitNormMapVtab_( AstNormMapVtab *, const char *, int * );
+
+/* Loader. */
+AstNormMap *astLoadNormMap_( void *, size_t, AstNormMapVtab *,
+                             const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckNormMap(this) astINVOKE_CHECK(NormMap,this,0)
+#define astVerifyNormMap(this) astINVOKE_CHECK(NormMap,this,1)
+
+/* Test class membership. */
+#define astIsANormMap(this) astINVOKE_ISA(NormMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astNormMap astINVOKE(F,astNormMap_)
+#else
+#define astNormMap astINVOKE(F,astNormMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitNormMap(mem,size,init,vtab,name,frame) \
+astINVOKE(O,astInitNormMap_(mem,size,init,vtab,name,frame,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitNormMapVtab(vtab,name) astINVOKE(V,astInitNormMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadNormMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadNormMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckNormMap to validate NormMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/nullregion.c b/ast/nullregion.c
new file mode 100644
index 0000000..6ef9cfa
--- /dev/null
+++ b/ast/nullregion.c
@@ -0,0 +1,2093 @@
+/*
+*class++
+*  Name:
+*     NullRegion
+
+*  Purpose:
+*     A boundless region within a Frame.
+
+*  Constructor Function:
+c     astNullRegion
+f     AST_NULLREGION
+
+*  Description:
+*     The NullRegion class implements a Region with no bounds within a Frame.
+*     If the Negated attribute of a NullRegion is false, the NullRegion
+*     represents a Region containing no points. If the Negated attribute of
+*     a NullRegion is true, the NullRegion represents an infinite Region
+*     (that is, all points in the coordinate system are inside the NullRegion).
+
+*  Inheritance:
+*     The NullRegion class inherits from the Region class.
+
+*  Attributes:
+*     The NullRegion class does not define any new attributes beyond
+*     those which are applicable to all Regions.
+
+*  Functions:
+c     The NullRegion class does not define any new functions beyond those
+f     The NullRegion class does not define any new routines beyond those
+*     which are applicable to all Regions.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     11-OCT-2004 (DSB):
+*        Original version.
+*     20-JAN-2009 (DSB):
+*        Over-ride astRegBasePick.
+*     26-JAN-2009 (DSB):
+*        Over-ride astMapMerge.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS NullRegion
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "region.h"              /* Coordinate regions (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "nullregion.h"          /* Interface definition for this class */
+#include "mapping.h"             /* Position mappings */
+#include "circle.h"              /* Circle regions */
+#include "prism.h"               /* Extruded Regions */
+#include "unitmap.h"             /* Unit Mapping */
+#include "cmpframe.h"            /* Compound Frames */
+#include "cmpmap.h"              /* Compound Mappings */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstMapping *(* parent_simplify)( AstMapping *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(NullRegion)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(NullRegion,Class_Init)
+#define class_vtab astGLOBAL(NullRegion,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstNullRegionVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstNullRegion *astNullRegionId_( void *, void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *RegMesh( AstRegion *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstRegion *RegBasePick( AstRegion *this, int, const int *, int * );
+static AstRegion *GetDefUnc( AstRegion *, int * );
+static AstRegion *MergeNullRegion( AstNullRegion *, AstRegion *, int, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int Overlap( AstRegion *, AstRegion *, int * );
+static int OverlapX( AstRegion *, AstRegion *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void RegBaseBox( AstRegion *this, double *, double *, int * );
+static void GetRegionBounds( AstRegion *this, double *, double *, int * );
+
+/* Member functions. */
+/* ================= */
+static AstRegion *GetDefUnc( AstRegion *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetDefUnc
+
+*  Purpose:
+*     Obtain a pointer to the default uncertainty Region for a given Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     AstRegion *astGetDefUnc( AstRegion *this )
+
+*  Class Membership:
+*     NullRegion member function (over-rides the astGetDefUnc protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a pointer to a Region which represents the
+*     default uncertainty associated with a position on the boundary of the
+*     given  Region. The returned Region refers to the base Frame within the
+*     FrameSet encapsulated by the supplied Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     A pointer to the Region. This should be annulled (using astAnnul)
+*     when no longer needed.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstRegion *result;
+   double *cen;
+   double rad;
+   int i;
+   int n;
+
+/* Initialise */
+   result = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Create a Circle centred on the origin with zero radius. */
+   n = astGetNaxes( this );
+   cen = astMalloc( sizeof(double)*(size_t) n );
+   if( cen ) {
+      for( i = 0; i < n; i++ ) cen[ i ] = 0.0;
+      rad = 0.0;
+      result = (AstRegion *) astCircle( this, 1, cen, &rad, NULL, "", status );
+      cen = astFree( cen );
+   }
+
+/* Return the default uncertainty Region. */
+   return result;
+}
+
+void astInitNullRegionVtab_(  AstNullRegionVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitNullRegionVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a NullRegion.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     void astInitNullRegionVtab( AstNullRegionVtab *vtab, const char *name )
+
+*  Class Membership:
+*     NullRegion vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the NullRegion class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstRegionVtab *region;        /* Pointer to Region component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitRegionVtab( (AstRegionVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsANullRegion) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstRegionVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   mapping = (AstMappingVtab *) vtab;
+   region = (AstRegionVtab *) vtab;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_simplify = mapping->Simplify;
+   mapping->Simplify = Simplify;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   mapping->MapMerge = MapMerge;
+
+   region->GetDefUnc = GetDefUnc;
+   region->Overlap = Overlap;
+   region->OverlapX = OverlapX;
+   region->RegBaseBox = RegBaseBox;
+   region->RegBaseMesh = RegBaseMesh;
+   region->GetRegionBounds = GetRegionBounds;
+   region->RegMesh = RegMesh;
+   region->RegBasePick = RegBasePick;
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDump( vtab, Dump, "NullRegion", "Boundless region" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a NullRegion.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     NullRegion method (over-rides the protected astMapMerge method
+*     inherited from the Region class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated NullRegion in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated NullRegion with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated NullRegion which is to be merged with
+*        its neighbours. This should be a cloned copy of the NullRegion
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        NullRegion it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated NullRegion resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstNullRegion *oldint;  /* Pointer to supplied NullRegion */
+   AstMapping *map;      /* Pointer to adjacent Mapping */
+   AstMapping *new;      /* Simplified or merged Region */
+   int i1;               /* Index of first Mapping merged */
+   int i;                /* Loop counter */
+   int result;           /* Result value to return */
+
+/* Initialise. */
+   result = -1;
+   i1 = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the NullRegion. */
+   oldint = (AstNullRegion *) this;
+
+/* First of all, see if the NullRegion can be replaced by a simpler Region,
+   without reference to the neighbouring Regions in the list.           */
+/* =====================================================================*/
+
+/* Try to simplify the NullRegion. If the pointer value has changed, we assume
+   some simplification took place. */
+   new = astSimplify( oldint );
+   if( new != (AstMapping *) oldint ) {
+
+/* Annul the NullRegion pointer in the list and replace it with the new Region
+   pointer, and indicate that the forward transformation of the returned
+   Region should be used (not really needed but keeps things clean). */
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = new;
+      ( *invert_list )[ where ] = 0;
+
+/* Return the index of the first modified element. */
+      result = where;
+
+/* If the NullRegion itself could not be simplified, see if it can be merged
+   with the Regions on either side of it in the list. We can only merge
+   in parallel. */
+/* =====================================================================*/
+   } else if( ! series ){
+      new = astAnnul( new );
+
+/* Attempt to merge the NullRegion with its lower neighbour (if any). */
+      if( where > 0 ) {
+         i1 = where - 1;
+         map = ( *map_list )[ where - 1 ];
+         if( astIsARegion( map ) ) {
+            new = (AstMapping *) MergeNullRegion( oldint, (AstRegion *) map,
+                                                  0, status );
+         }
+      }
+
+/* If this did not produced a merged Region, attempt to merge the NullRegion
+   with its upper neighbour (if any). */
+      if( !new && where < *nmap - 1 ) {
+         i1 = where;
+         map = ( *map_list )[ where + 1 ];
+         if( astIsARegion( map ) ) {
+            new = (AstMapping *) MergeNullRegion( oldint, (AstRegion *) map,
+                                                  1, status );
+         }
+      }
+
+/* If succesfull... */
+      if( new ){
+
+/* Annul the first of the two Mappings, and replace it with the merged
+   Region. Also clear the invert flag. */
+         (void) astAnnul( ( *map_list )[ i1 ] );
+         ( *map_list )[ i1 ] = new;
+         ( *invert_list )[ i1 ] = 0;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+         (void) astAnnul( ( *map_list )[ i1 + 1 ] );
+         for ( i = i1 + 2; i < *nmap; i++ ) {
+            ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+            ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+         }
+
+/* Clear the vacated element at the end. */
+         ( *map_list )[ *nmap - 1 ] = NULL;
+         ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+         ( *nmap )--;
+         result = i1;
+      }
+
+   } else {
+      new = astAnnul( new );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstRegion *MergeNullRegion( AstNullRegion *this, AstRegion *reg,
+                                   int intfirst, int *status ) {
+/*
+*  Name:
+*     MergeNullRegion
+
+*  Purpose:
+*     Attempt to merge a NullRegion with another Region to form a Region of
+*     higher dimensionality.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     AstRegion *MergeNullRegion( AstNullRegion *this, AstRegion *reg,
+*                                 int intfirst, int *status )
+
+*  Class Membership:
+*     NullRegion member function.
+
+*  Description:
+*     This function attempts to combine the supplied Regions together
+*     into a Region of higher dimensionality.
+
+*  Parameters:
+*     this
+*        Pointer to a NullRegion.
+*     reg
+*        Pointer to another Region.
+*     intfirst
+*        If non-zero, then the NullRegion axes are put first in the new Region.
+*        Otherwise, the other Region's axes are put first.
+*     status
+*        Pointer to the inherited status value.
+
+*  Returned Value:
+*     A pointer to a new region, or NULL if the supplied Regions could
+*     not be merged.
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;           /* Pointer to base Frame for "result" */
+   AstFrame *cfrm;           /* Pointer to current Frame for "result" */
+   AstFrame *frm_reg;        /* Pointer to Frame from "reg" */
+   AstFrame *frm_this;       /* Pointer to Frame from "this" */
+   AstMapping *bcmap;        /* Base->current Mapping for "result" */
+   AstMapping *map_reg;      /* Base->current Mapping from "reg" */
+   AstMapping *map_this;     /* Base->current Mapping from "this" */
+   AstMapping *sbunc;        /* Simplified uncertainty */
+   AstRegion *bunc;          /* Base Frame uncertainty Region */
+   AstRegion *new;           /* Pointer to new NullRegion in base Frame */
+   AstRegion *result;        /* Pointer to returned NullRegion in current Frame */
+   AstRegion *unc_reg;       /* Current Frame uncertainty Region from "reg" */
+   AstRegion *unc_this;      /* Current Frame uncertainty Region from "this" */
+   double fac_reg;           /* Ratio of used to default MeshSize for "reg" */
+   double fac_this;          /* Ratio of used to default MeshSize for "this" */
+   int msz_reg;              /* Original MeshSize for "reg" */
+   int msz_reg_set;          /* Was MeshSize originally set for "reg"? */
+   int msz_this;             /* Original MeshSize for "this" */
+   int msz_this_set;         /* Was MeshSize originally set for "this"? */
+   int nax;                  /* Number of axes in "result" */
+   int nax_reg;              /* Number of axes in "reg" */
+   int nax_this;             /* Number of axes in "this" */
+   int neg_reg;              /* Negated attribute value for other supplied Region */
+   int neg_this;             /* Negated attribute value for supplied NullRegion  */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get the Closed attributes of the two Regions. They must be the same in
+   each Region if we are to merge the Regions. In addition, in order to
+   merge, either both Regions must have a defined uncertainty, or neither
+   Region must have a defined Uncertainty. */
+   if( astGetClosed( this ) == astGetClosed( reg ) &&
+       astTestUnc( this ) == astTestUnc( reg ) ) {
+
+/* Get the Nagated attributes of the two Regions. */
+      neg_this = astGetNegated( this );
+      neg_reg = astGetNegated( reg );
+
+/* Get the number of axes in the two supplied Regions. */
+      nax_reg = astGetNaxes( reg );
+      nax_this = astGetNaxes( this );
+
+/* If the Regions can be combined, get the number of axes the
+   combination will have. */
+      nax = nax_reg + nax_this;
+
+/* Get the base Frames from the two Region FrameSets, and combine them
+   into a single CmpFrame that will be used to create any new Region. */
+      frm_this = astGetFrame( ((AstRegion *) this)->frameset, AST__BASE );
+      frm_reg = astGetFrame( reg->frameset, AST__BASE );
+
+      if( intfirst ) {
+         bfrm = (AstFrame *) astCmpFrame( frm_this, frm_reg, "", status );
+      } else {
+         bfrm = (AstFrame *) astCmpFrame( frm_reg, frm_this, "", status );
+      }
+
+      frm_this = astAnnul( frm_this );
+      frm_reg = astAnnul( frm_reg );
+
+/* Indicate we do not yet have a merged Region. */
+      new = NULL;
+
+/* We only check for merging with another NullRegion (other classes such
+   as Box and Interval check for merging of NullRegions with other classes).
+   The result will be an NullRegion. Both Regions must have the same value
+   for the Negated flag. */
+      if( astIsANullRegion( reg ) && neg_this == neg_reg ) {
+         new = (AstRegion *) astNullRegion( bfrm, NULL, "", status );
+
+/* Propagate remaining attributes of the supplied Region to it. */
+         astRegOverlay( new, this, 1 );
+
+/* Ensure the Negated flag is set correctly in the returned NullRegion. */
+         if( neg_this ) {
+            astSetNegated( new, neg_this );
+         } else {
+            astClearNegated( new );
+         }
+
+/* If both the supplied Regions have uncertainty, assign the new Region an
+   uncertainty. */
+         if( astTestUnc( this ) && astTestUnc( reg ) ) {
+
+/* Get the uncertainties from the two supplied Regions. */
+            unc_this = astGetUncFrm( this, AST__BASE );
+            unc_reg = astGetUncFrm( reg, AST__BASE );
+
+/* Combine them into a single Region (a Prism), in the correct order. */
+            if( intfirst ) {
+               bunc = (AstRegion *) astPrism( unc_this, unc_reg, "", status );
+            } else {
+               bunc = (AstRegion *) astPrism( unc_reg, unc_this, "", status );
+            }
+
+/* Attempt to simplify the Prism. */
+            sbunc = astSimplify( bunc );
+
+/* Use the simplified Prism as the uncertainty for the returned Region. */
+            astSetUnc( new, sbunc );
+
+/* Free resources. */
+            sbunc = astAnnul( sbunc );
+            bunc = astAnnul( bunc );
+            unc_reg = astAnnul( unc_reg );
+            unc_this = astAnnul( unc_this );
+         }
+
+/* Get the current Frames from the two Region FrameSets, and combine them
+   into a single CmpFrame. */
+         frm_this = astGetFrame( ((AstRegion *) this)->frameset, AST__CURRENT );
+         frm_reg = astGetFrame( reg->frameset, AST__CURRENT );
+
+         if( intfirst ) {
+            cfrm = (AstFrame *) astCmpFrame( frm_this, frm_reg, "", status );
+         } else {
+            cfrm = (AstFrame *) astCmpFrame( frm_reg, frm_this, "", status );
+         }
+
+/* Get the base -> current Mappings from the two Region FrameSets, and
+   combine them into a single parallel CmpMap that connects bfrm and cfrm. */
+         map_this = astGetMapping( ((AstRegion *) this)->frameset, AST__BASE,
+                                   AST__CURRENT );
+         map_reg = astGetMapping( reg->frameset, AST__BASE, AST__CURRENT );
+
+         if( intfirst ) {
+            bcmap = (AstMapping *) astCmpMap( map_this, map_reg, 0, "",
+                                              status );
+         } else {
+            bcmap = (AstMapping *) astCmpMap( map_reg, map_this, 0, "",
+                                              status );
+         }
+
+/* Map the new Region into the new current Frame. */
+         result = astMapRegion( new, bcmap, cfrm );
+
+/* The filling factor in the returned is the product of the filling
+   factors for the two supplied Regions. */
+         if( astTestFillFactor( reg ) || astTestFillFactor( this ) ) {
+            astSetFillFactor( result, astGetFillFactor( reg )*
+                                      astGetFillFactor( this ) );
+         }
+
+/* If the MeshSize value is set in either supplied Region, set a value
+   for the returned Region which scales the default value by the
+   product of the scaling factors for the two supplied Regions. First see
+   if either MeshSize value is set. */
+         msz_this_set = astTestMeshSize( this );
+         msz_reg_set = astTestMeshSize( reg );
+         if( msz_this_set || msz_reg_set ) {
+
+/* If so, get the two MeshSize values (one of which may be a default
+   value), and then clear them so that the default value will be returned
+   in future. */
+            msz_this = astGetMeshSize( this );
+            msz_reg = astGetMeshSize( reg );
+            astClearMeshSize( this );
+            astClearMeshSize( reg );
+
+/* Get the ratio of the used MeshSize to the default MeshSize for both
+   Regions. */
+            fac_this = (double)msz_this/(double)astGetMeshSize( this );
+            fac_reg = (double)msz_reg/(double)astGetMeshSize( reg );
+
+/* The MeshSize of the returned Returned is the default value scaled by
+   the product of the two ratios found above. */
+            astSetMeshSize( result, fac_this*fac_reg*astGetMeshSize( result ) );
+
+/* Re-instate the original MeshSize values for the supplied Regions (if
+   set) */
+            if( msz_this_set ) astSetMeshSize( this, msz_this );
+            if( msz_reg_set ) astSetMeshSize( reg, msz_reg );
+         }
+
+/* Free remaining resources */
+         frm_this = astAnnul( frm_this );
+         frm_reg = astAnnul( frm_reg );
+         map_this = astAnnul( map_this );
+         map_reg = astAnnul( map_reg );
+         bcmap = astAnnul( bcmap );
+         new = astAnnul( new );
+         cfrm = astAnnul( cfrm );
+      }
+   }
+
+/* If an error has occurred, annul the returned pointer. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int Overlap( AstRegion *this, AstRegion *that, int *status ){
+/*
+*  Name:
+*     Overlap
+
+*  Purpose:
+*     Test if two regions overlap each other.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     int Overlap( AstRegion *this, AstRegion *that )
+
+*  Class Membership:
+*     NullRegion member function (over-rides the astOverlap method inherited
+*     from the Region class).
+
+*  Description:
+*     This function returns an integer value indicating if the two
+*     supplied Regions overlap. The two Regions are converted to a commnon
+*     coordinate system before performing the check. If this conversion is
+*     not possible (for instance because the two Regions represent areas in
+*     different domains), then the check cannot be performed and a zero value
+*     is returned to indicate this.
+
+*  Parameters:
+*     this
+*        Pointer to the first Region.
+*     that
+*        Pointer to the second Region.
+
+*  Returned Value:
+*     astOverlap()
+*        A value indicating if there is any overlap between the two Regions.
+*        Possible values are:
+*
+*        0 - The check could not be performed because the second Region
+*            could not be mapped into the coordinate system of the first
+*            Region.
+*
+*        1 - There is no overlap between the two Regions.
+*
+*        2 - The first Region is completely inside the second Region.
+*
+*        3 - The second Region is completely inside the first Region.
+*
+*        4 - There is partial overlap between the two Regions.
+*
+*        5 - The Regions are identical.
+*
+*        6 - The second Region is the negation of the first Region.
+
+*  Notes:
+*     - The returned values 5 and 6 do not check the value of the Closed
+*     attribute in the two Regions.
+*     - A value of zero will be returned if this function is invoked with the
+*     AST error status set, or if it should fail for any reason.
+
+* Implementation Notes:
+*    - This function is simply a wrap-up for the OverlapX function
+*    which performs the required processing but swaps the order of the
+*    two arguments. This is a trick to allow the astOverlap
+*    method to be over-ridden by derived classes on the basis of the
+*    class of either of the two arguments.
+*/
+
+/* Check the inherited status. */
+   if ( !astOK ) return 0;
+
+/* Invoke the private "OverlapX" member function with the two arguments
+   swapped. */
+   return OverlapX( that, this, status );
+}
+
+static int OverlapX( AstRegion *that, AstRegion *this, int *status ){
+/*
+*  Name:
+*     OverlapX
+
+*  Purpose:
+*     Test if two regions overlap each other.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     int OverlapX( AstRegion *that, AstRegion *this )
+
+*  Class Membership:
+*     NullRegion member function (over-rides the astOverlapX method inherited
+*     from the Region class).
+
+*  Description:
+*     This function performs the processing for the public astOverlap
+*     method (as inherited from the Region class and over-ridden by the
+*     NullRegion class) and has exactly the same interface except that
+*     the order of the two arguments is swapped. This is a trick
+*     to allow the astOverlap method to be over-ridden by derived
+*     classes on the basis of the class of either of its two
+*     arguments.
+*
+*     See the astOverlap method for details of the interface.
+
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;               /* FrameSet connecting Region Frames */
+   int result;                    /* Returned value */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check that the Regions can be compared meaningfully. */
+   fs = astConvert( this, that, "" );
+   if( fs ) {
+      fs = astAnnul( fs );
+
+/* If both the supplied Regions are NullRegion... */
+      if( astIsANullRegion( this ) && astIsANullRegion( that ) ) {
+
+/* If the Negated attributes are equal, the Regions are identical.
+   Otherwise they are mutually exclusive. */
+         if( astGetNegated( this ) == astGetNegated( that ) ) {
+            result = 5;
+         } else {
+            result = 6;
+         }
+
+/* If one of the supplied Region is a NullRegion containing no points,
+   then there is no overlap. */
+      } else if( ( astIsANullRegion( this ) && !astGetNegated( this ) ) ||
+                 ( astIsANullRegion( that ) && !astGetNegated( that ) ) ) {
+         result = 1;
+
+/* If "that" is infinite and "this" is not infinite, then "this" is
+   entirely inside "that". */
+      } else if( astIsANullRegion( that ) && astGetNegated( that ) ) {
+         result = 2;
+
+/* If "this" is infinite and "that" is not infinite, then "that" is
+   entirely inside "this". */
+      } else if( astIsANullRegion( this ) && astGetNegated( this ) ){
+         result = 3;
+
+/* Otherwise there is partial overlap. */
+      } else {
+         result = 4;
+      }
+   }
+
+/* If not OK, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static void RegBaseBox( AstRegion *this_region, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     RegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     NullRegion member function (over-rides the astRegBaseBox protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstNullRegion *this;          /* Pointer to NullRegion structure */
+   int i;                        /* Axis index */
+   int nc;                       /* No. of axes in base Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the NullRegion structure */
+   this = (AstNullRegion *) this_region;
+
+/* Get the number of base Frame axes. */
+   nc = astGetNin( this_region->frameset );
+
+/* Set the upper bound less than the lower bound to indicate that the region
+   contains no points. */
+   for( i = 0; i < nc; i++ ) {
+      lbnd[ i ] = 1.0;
+      ubnd[ i ] = -1.0;
+   }
+
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this, int *status ){
+/*
+*  Name:
+*     RegBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing a mesh of points on the boundary of a
+*     Region in its base Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     AstPointSet *astRegBaseMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     NullRegion member function (over-rides the astRegBaseMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet. Note, a NullRegion has no boundary. This
+*     is indicated by returned a PointSet containing a single point with a
+*     value of AST__BAD on every axis.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    Pointer to a new PointSet containing a single point with value of
+*    AST__BAD on every axis.
+
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *result;
+   double **ptr;
+   int i;
+   int nc;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the inherited status */
+   if( !astOK ) return result;
+
+/* If the Region structure contains a pointer to a PointSet holding
+   a previously created mesh, return it. */
+   if( this->basemesh ) {
+      result = astClone( this->basemesh );
+
+/* Otherwise, create a new mesh. */
+   } else {
+
+/* Get the number of base Frame axes */
+      nc = astGetNin( this->frameset );
+
+/* Create the PointSet. */
+      result = astPointSet( 1, nc, "", status );
+
+/* Get a pointer to the axis values. */
+      ptr = astGetPoints( result );
+
+/* If OK, store AST__BAD on every axis. */
+      if( ptr ) for( i = 0; i < nc; i++ ) ptr[ i ][ 0 ] = AST__BAD;
+
+/* Same the returned pointer in the Region structure so that it does not
+   need to be created again next time this function is called. */
+      if( astOK && result ) this->basemesh = astClone( result );
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstRegion *RegBasePick( AstRegion *this_region, int naxes,
+                               const int *axes, int *status ){
+/*
+*  Name:
+*     RegBasePick
+
+*  Purpose:
+*     Return a Region formed by picking selected base Frame axes from the
+*     supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     AstRegion *RegBasePick( AstRegion *this, int naxes, const int *axes,
+*                             int *status )
+
+*  Class Membership:
+*     NullRegion member function (over-rides the astRegBasePick protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function attempts to return a Region that is spanned by selected
+*     axes from the base Frame of the encapsulated FrameSet of the supplied
+*     Region. This may or may not be possible, depending on the class of
+*     Region. If it is not possible a NULL pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     naxes
+*        The number of base Frame axes to select.
+*     axes
+*        An array holding the zero-based indices of the base Frame axes
+*        that are to be selected.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Region, or NULL if no region can be formed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;         /* The base Frame in the supplied Region */
+   AstFrame *frm;          /* The base Frame in the returned Region */
+   AstRegion *bunc;        /* The uncertainty in the supplied Region */
+   AstRegion *result;      /* Returned Region */
+   AstRegion *unc;         /* The uncertainty in the returned Region */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the base Frame of the encapsulated FrameSet. */
+   bfrm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Create a Frame by picking the selected axes from the base Frame of the
+   encapsulated FrameSet. */
+   frm = astPickAxes( bfrm, naxes, axes, NULL );
+
+/* Get the uncertainty Region (if any) within the base Frame of the supplied
+   Region, and select the required axes from it. If the resulting Object
+   is not a Region, annul it so that the returned Region will have no
+   uncertainty. */
+   if( astTestUnc( this_region ) ) {
+      bunc = astGetUncFrm( this_region, AST__BASE );
+      unc = astPickAxes( bunc, naxes, axes, NULL );
+      bunc = astAnnul( bunc );
+
+      if( ! astIsARegion( unc ) ) unc = astAnnul( unc );
+
+   } else {
+      unc = NULL;
+   }
+
+/* Create the new NullRegion. */
+   result = (AstRegion *) astNullRegion( frm, unc, "", status );
+
+/* Free resources */
+   frm = astAnnul( frm );
+   bfrm = astAnnul( bfrm );
+   if( unc ) unc = astAnnul( unc );
+
+/* Return a NULL pointer if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void GetRegionBounds( AstRegion *this_region, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     GetRegionBounds
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the current Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     void astGetRegionBounds( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     NullRegion member function (over-rides the astGetRegionBounds protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the current Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the current Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the Region.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the current Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstNullRegion *this;          /* Pointer to NullRegion structure */
+   int i;                        /* Axis index */
+   int nc;                       /* No. of axes in base Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the NullRegion structure */
+   this = (AstNullRegion *) this_region;
+
+/* Get the number of base Frame axes. */
+   nc = astGetNin( this_region->frameset );
+
+/* Set the upper bound less than the lower bound to indicate that the region
+   contains no points. */
+   for( i = 0; i < nc; i++ ) {
+      lbnd[ i ] = 1.0;
+      ubnd[ i ] = -1.0;
+   }
+
+}
+
+static AstPointSet *RegMesh( AstRegion *this, int *status ){
+/*
+*  Name:
+*     RegMesh
+
+*  Purpose:
+*     Return a PointSet containing points spread over the boundary of a
+*     Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     AstPointSet *astRegMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     NullRegion member function (over-rides the astRegMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the current Frame of
+*     the encapsulated FrameSet. Note, a NullRegion has no boundary and
+*     so an error is reported if this function is called.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     NULL pointer.
+
+*  Notes:
+*     - This implementation reports and error and returns NULL since a
+*     NullRegion has no boundary.
+*-
+*/
+
+   astError( AST__INTER, "astRegMesh(%s): The %s class does not implement "
+             "the astRegMesh method inherited from the Region class "
+             "(internal AST programming error).", status, astGetClass( this ),
+             astGetClass( this ) );
+   return NULL;
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify the Mapping represented by a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     NullRegion method (over-rides the astSimplify method inherited
+*     from the Region class).
+
+*  Description:
+*     This function invokes the parent Region Simplify method, and then
+*     performs any further region-specific simplification.
+*
+*     If the Mapping from base to current Frame is not a UnitMap, this
+*     will include attempting to fit a new Region to the boundary defined
+*     in the current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the simplified Region. A cloned pointer to the
+*     supplied Region will be returned if no simplication could be
+*     performed.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;             /* Current Frame */
+   AstMapping *map;           /* Base->current Mapping */
+   AstMapping *result;        /* Result pointer to return */
+   AstRegion *new;            /* Simplified Region */
+   AstRegion *this;           /* Pointer to supplied Region structure */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the supplied Region structure. */
+   this = (AstRegion *) this_mapping;
+
+/* Invoke the parent Simplify method inherited from the Region class. This
+   will simplify the encapsulated FrameSet and uncertainty Region. */
+   new = (AstRegion *) (*parent_simplify)( this_mapping, status );
+
+/* Is the Mapping from base Frame to current Frame in the Region a
+   UnitMap? If so, no simplification is possible. */
+   map = astGetMapping( new->frameset, AST__BASE, AST__CURRENT );
+   if( astIsAUnitMap( map ) ){
+      result = astClone( new );
+
+   } else {
+
+/* Create a NullRegion from the current Frame. */
+      frm = astGetFrame( new->frameset, AST__CURRENT );
+      result = (AstMapping *) astNullRegion( frm, astGetUnc( new, 0 ), "", status );
+
+/* Free resources. */
+      frm = astAnnul( frm );
+
+   }
+   map = astAnnul( map );
+   new = astAnnul( new );
+
+/* If any simplification could be performed, copy Region attributes from
+   the supplied Region to the returned Region, and return a pointer to it.
+   If the supplied Region had no uncertainty, ensure the returned Region
+   has no uncertainty. Otherwise, return a clone of the supplied pointer. */
+   if( result != this_mapping ) astRegOverlay( result, this, 1 );
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a NullRegion to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     NullRegion member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a NullRegion and a set of points encapsulated in a
+*     PointSet and transforms the points by setting axis values to
+*     AST__BAD for all points which are outside the region. Points inside
+*     the region are copied unchanged from input to output.
+
+*  Parameters:
+*     this
+*        Pointer to the NullRegion.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  The forward and inverse transformations are identical for a
+*     Region.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of axes in the Frame represented by the NullRegion.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstNullRegion *this;          /* Pointer to NullRegion */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *p;                    /* Pointer to next axis value */
+   int coord;                    /* Zero-based index for coordinates */
+   int ncoord_out;               /* No. of coordinates per output point */
+   int npoint_out;               /* No. of points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the NullRegion structure. */
+   this = (AstNullRegion *) this_mapping;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Region class. This function validates
+   all arguments and generates an output PointSet if necessary,
+   containing a copy of the input PointSet. */
+   result = (*parent_transform)( this_mapping, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* If the NullRegion has been inverted, it represents an infinite region
+   which includes all points, so just retain the copy of the supplied
+   PointSet created by the parent Transform method above. If the NullRegion
+   has not been inverted, it contains no points and so set all output points
+   bad. */
+   if( !astGetNegated( this ) ) {
+      ncoord_out = astGetNcoord( result );
+      npoint_out = astGetNpoint( result );
+      ptr_out = astGetPoints( result );
+      if ( astOK ) {
+         for ( coord = 0; coord < ncoord_out; coord++ ) {
+            p = ptr_out[ coord ];
+            for ( point = 0; point < npoint_out; point++ ) {
+               *(p++) = AST__BAD;
+            }
+         }
+      }
+   }
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+/* None */
+
+/* Destructor. */
+/* ----------- */
+/* None */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for NullRegion objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the NullRegion class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the NullRegion whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstNullRegion *this;                 /* Pointer to the NullRegion structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the NullRegion structure. */
+   this = (AstNullRegion *) this_object;
+
+/* Write out values representing the instance variables for the
+   NullRegion class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* There are no values to write, so return without further action. */
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsANullRegion and astCheckNullRegion functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(NullRegion,Region)
+astMAKE_CHECK(NullRegion)
+
+AstNullRegion *astNullRegion_( void *frame_void, AstRegion *unc, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astNullRegion
+f     AST_NULLREGION
+
+*  Purpose:
+*     Create a NullRegion.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "nullregion.h"
+c     AstNullRegion *astNullRegion( AstFrame *frame, AstRegion *unc, const char *options, ... )
+f     RESULT = AST_NULLREGION( FRAME, UNC, OPTIONS, STATUS )
+
+*  Class Membership:
+*     NullRegion constructor.
+
+*  Description:
+*     This function creates a new NullRegion and optionally initialises its
+*     attributes.
+*
+*     A NullRegion is a Region with no bounds. If the Negated attribute of a
+*     NullRegion is false, the NullRegion represents a Region containing no
+*     points. If the Negated attribute of a NullRegion is true, the NullRegion
+*     represents an infinite Region containing all points within the
+*     coordinate system.
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        A pointer to the Frame in which the region is defined. A deep
+*        copy is taken of the supplied Frame. This means that any
+*        subsequent changes made to the Frame using the supplied pointer
+*        will have no effect the Region.
+c     unc
+f     UNC = INTEGER (Given)
+*        An optional pointer to an existing Region which specifies the
+*        uncertainties associated with positions in the supplied Frame.
+*        The uncertainty in any point in the Frame is found by shifting the
+*        supplied "uncertainty" Region so that it is centred at the point
+*        being considered. The area covered by the shifted uncertainty
+*        Region then represents the uncertainty in the position. The
+*        uncertainty is assumed to be the same for all points.
+*
+*        If supplied, the uncertainty Region must be of a class for which
+*        all instances are centro-symetric (e.g. Box, Circle, Ellipse, etc.)
+*        or be a Prism containing centro-symetric component Regions. A deep
+*        copy of the supplied Region will be taken, so subsequent changes to
+*        the uncertainty Region using the supplied pointer will have no
+*        effect on the created NullRegion. Alternatively,
+f        a null Object pointer (AST__NULL)
+c        a NULL Object pointer
+*        may be supplied, in which case a default uncertainty of zero is
+*        used.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new NullRegion. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new NullRegion. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astNullRegion()
+f     AST_NULLREGION = INTEGER
+*        A pointer to the new NullRegion.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstNullRegion *new;           /* Pointer to new NullRegion */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the supplied Frame structure. */
+   frame = astCheckFrame( frame_void );
+
+/* Initialise the NullRegion, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitNullRegion( NULL, sizeof( AstNullRegion ), !class_init,
+                            &class_vtab, "NullRegion", frame, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new NullRegion's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new NullRegion. */
+   return new;
+}
+
+AstNullRegion *astNullRegionId_( void *frame_void, void *unc_void,
+                                 const char *options, ... ) {
+/*
+*  Name:
+*     astNullRegionId_
+
+*  Purpose:
+*     Create a NullRegion.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     AstNullRegion *astNullRegionId_( AstFrame *frame, AstRegion *unc, const char *options, ... )
+
+*  Class Membership:
+*     NullRegion constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astNullRegion constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astNullRegion_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astNullRegion_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astNullRegion_.
+
+*  Returned Value:
+*     The ID value associated with the new NullRegion.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstNullRegion *new;           /* Pointer to new NullRegion */
+   AstRegion *unc;               /* Pointer to Region structure */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Frame pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Frame. */
+   frame = astVerifyFrame( astMakePointer( frame_void ) );
+
+/* Obtain a Region pointer from the supplied "unc" ID and validate the
+   pointer to ensure it identifies a valid Region . */
+   unc = unc_void ? astCheckRegion( astMakePointer( unc_void ) ) : NULL;
+
+/* Initialise the NullRegion, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitNullRegion( NULL, sizeof( AstNullRegion ), !class_init,
+                            &class_vtab, "NullRegion", frame, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new NullRegion's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new NullRegion. */
+   return astMakeId( new );
+}
+
+AstNullRegion *astInitNullRegion_( void *mem, size_t size, int init,
+                                   AstNullRegionVtab *vtab, const char *name,
+                                   AstFrame *frame, AstRegion *unc, int *status ) {
+/*
+*+
+*  Name:
+*     astInitNullRegion
+
+*  Purpose:
+*     Initialise a NullRegion.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     AstNullRegion *astInitNullRegion_( void *mem, size_t size, int init,
+*                                        AstNullRegionVtab *vtab, const char *name,
+*                                        AstFrame *frame, AstRegion *unc  )
+
+*  Class Membership:
+*     NullRegion initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new NullRegion object. It allocates memory (if necessary) to accommodate
+*     the NullRegion plus any additional data associated with the derived class.
+*     It then initialises a NullRegion structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a NullRegion at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the NullRegion is to be initialised.
+*        This must be of sufficient size to accommodate the NullRegion data
+*        (sizeof(NullRegion)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the NullRegion (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the NullRegion
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the NullRegion's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new NullRegion.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     frame
+*        A pointer to the Frame in which the region is defined.
+*     unc
+*        A pointer to a Region which specifies the uncertainty of positions
+*        within the supplied Frame. A NULL pointer can be supplied, in which
+*        case default uncertainties of zero are used. If an uncertainty
+*        Region is supplied, it must be either a Box, a Circle or an Ellipse,
+*        and its encapsulated Frame must be related to the Frame supplied
+*        for parameter "frame" (i.e. astConvert should be able to find a
+*        Mapping between them). The centre of the supplied uncertainty
+*        Region is immaterial since it will be re-centred on the point
+*        being tested before use. A deep copy is taken of the supplied
+*        Region.
+
+*  Returned Value:
+*     A pointer to the new NullRegion.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstNullRegion *new;       /* Pointer to new NullRegion */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitNullRegionVtab( vtab, name );
+
+/* Initialise a Region structure (the parent class) as the first component
+   within the NullRegion structure, allocating memory if necessary. */
+   new = (AstNullRegion *) astInitRegion( mem, size, 0, (AstRegionVtab *) vtab,
+                                          name, frame, NULL, unc );
+
+/* If an error occurred, clean up by deleting the new NullRegion. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return a pointer to the new NullRegion. */
+   return new;
+}
+
+AstNullRegion *astLoadNullRegion_( void *mem, size_t size, AstNullRegionVtab *vtab,
+                                   const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadNullRegion
+
+*  Purpose:
+*     Load a NullRegion.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "nullregion.h"
+*     AstNullRegion *astLoadNullRegion( void *mem, size_t size, AstNullRegionVtab *vtab,
+*                                       const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     NullRegion loader.
+
+*  Description:
+*     This function is provided to load a new NullRegion using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     NullRegion structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a NullRegion at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the NullRegion is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        NullRegion data (sizeof(NullRegion)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the NullRegion (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the NullRegion structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstNullRegion) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new NullRegion. If this is NULL, a pointer
+*        to the (static) virtual function table for the NullRegion class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "NullRegion" is used instead.
+
+*  Returned Value:
+*     A pointer to the new NullRegion.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstNullRegion *new;              /* Pointer to the new NullRegion */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this NullRegion. In this case the
+   NullRegion belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstNullRegion );
+      vtab = &class_vtab;
+      name = "NullRegion";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitNullRegionVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built NullRegion. */
+   new = astLoadRegion( mem, size, (AstRegionVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "NullRegion" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* There are no values to read. */
+/* ---------------------------- */
+
+/* If an error occurred, clean up by deleting the new NullRegion. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new NullRegion pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
+
+
+
+
+
diff --git a/ast/nullregion.h b/ast/nullregion.h
new file mode 100644
index 0000000..bcfc501
--- /dev/null
+++ b/ast/nullregion.h
@@ -0,0 +1,221 @@
+#if !defined( NULLREGION_INCLUDED ) /* Include this file only once */
+#define NULLREGION_INCLUDED
+/*
+*+
+*  Name:
+*     nullregion.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the NullRegion class.
+
+*  Invocation:
+*     #include "nullregion.h"
+
+*  Description:
+*     This include file defines the interface to the NullRegion class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The NullRegion class implement a Region with no boundaries.
+
+*  Inheritance:
+*     The NullRegion class inherits from the Region class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     11-OCT-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "region.h"              /* Coordinate regions (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* NullRegion structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstNullRegion {
+
+/* Attributes inherited from the parent class. */
+   AstRegion region;             /* Parent class structure */
+
+} AstNullRegion;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstNullRegionVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstRegionVtab region_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+} AstNullRegionVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstNullRegionGlobals {
+   AstNullRegionVtab Class_Vtab;
+   int Class_Init;
+} AstNullRegionGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitNullRegionGlobals_( AstNullRegionGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(NullRegion)          /* Check class membership */
+astPROTO_ISA(NullRegion)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstNullRegion *astNullRegion_( void *, AstRegion *, const char *, int *, ...);
+#else
+AstNullRegion *astNullRegionId_( void *, AstRegion *, const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstNullRegion *astInitNullRegion_( void *, size_t, int, AstNullRegionVtab *,
+                                   const char *, AstFrame *, AstRegion *, int * );
+
+/* Vtab initialiser. */
+void astInitNullRegionVtab_( AstNullRegionVtab *, const char *, int * );
+
+/* Loader. */
+AstNullRegion *astLoadNullRegion_( void *, size_t, AstNullRegionVtab *,
+                                   const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckNullRegion(this) astINVOKE_CHECK(NullRegion,this,0)
+#define astVerifyNullRegion(this) astINVOKE_CHECK(NullRegion,this,1)
+
+/* Test class membership. */
+#define astIsANullRegion(this) astINVOKE_ISA(NullRegion,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astNullRegion astINVOKE(F,astNullRegion_)
+#else
+#define astNullRegion astINVOKE(F,astNullRegionId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitNullRegion(mem,size,init,vtab,name,frame,unc) \
+astINVOKE(O,astInitNullRegion_(mem,size,init,vtab,name,frame,unc,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitNullRegionVtab(vtab,name) astINVOKE(V,astInitNullRegionVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadNullRegion(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadNullRegion_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckNullRegion to validate NullRegion pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/object.c b/ast/object.c
new file mode 100644
index 0000000..f1faaa2
--- /dev/null
+++ b/ast/object.c
@@ -0,0 +1,8657 @@
+/*
+*class++
+
+*  Name:
+*     Object
+
+*  Purpose:
+*     Base class for all AST Objects.
+
+*  Constructor Function:
+*     None.
+
+*  Description:
+*     This class is the base class from which all other classes in the
+*     AST library are derived. It provides all the basic Object
+*     behaviour and Object manipulation facilities required throughout
+*     the library. There is no Object constructor, however, as Objects
+*     on their own are not useful.
+
+*  Inheritance:
+*     The Object base class does not inherit from any other class.
+
+*  Attributes:
+*     All Objects have the following attributes:
+*
+*     - Class: Object class name
+*     - ID: Object identification string
+*     - Ident: Permanent Object identification string
+*     - Nobject: Number of Objects in class
+*     - ObjSize: The in-memory size of the Object in bytes
+*     - RefCount: Count of active Object pointers
+*     - UseDefs: Allow use of default values for Object attributes?
+
+*  Functions:
+c     The following functions may be applied to all Objects:
+f     The following routines may be applied to all Objects:
+*
+c     - astAnnul: Annul a pointer to an Object
+c     - astBegin: Begin a new AST context
+c     - astClear: Clear attribute values for an Object
+c     - astClone: Clone a pointer to an Object
+c     - astCopy: Copy an Object
+c     - astDelete: Delete an Object
+c     - astEnd: End an AST context
+c     - astEscapes: Control whether graphical escape sequences are removed
+c     - astExempt: Exempt an Object pointer from AST context handling
+c     - astExport: Export an Object pointer to an outer context
+c     - astGet<X>: Get an attribute value for an Object
+c     - astHasAttribute: Test if an Object has a named attribute
+c     - astImport: Import an Object pointer to the current context
+c     - astIsA<Class>: Test class membership
+c     - astLock: Lock an Object for use by the calling thread
+c     - astToString: Create an in-memory serialisation of an Object
+c     - astSame: Do two AST pointers refer to the same Object?
+c     - astSet: Set attribute values for an Object
+c     - astSet<X>: Set an attribute value for an Object
+c     - astShow: Display a textual representation of an Object on standard
+c     output
+c     - astTest: Test if an attribute value is set for an Object
+c     - astTune: Set or get an integer AST tuning parameter
+c     - astTuneC: Set or get a character AST tuning parameter
+c     - astUnlock: Unlock an Object for use by other threads
+c     - astFromString: Re-create an Object from an in-memory serialisation
+c     - astVersion: Return the verson of the AST library being used.
+f     - AST_ANNUL: Annul a pointer to an Object
+f     - AST_BEGIN: Begin a new AST context
+f     - AST_CLEAR: Clear attribute values for an Object
+f     - AST_CLONE: Clone a pointer to an Object
+f     - AST_COPY: Copy an Object
+f     - AST_DELETE: Delete an Object
+f     - AST_END: End an AST context
+f     - AST_ESCAPES: Control whether graphical escape sequences are removed
+f     - AST_EXEMPT: Exempt an Object pointer from AST context handling
+f     - AST_EXPORT: Export an Object pointer to an outer context
+f     - AST_GET<X>: Get an attribute value for an Object
+f     - AST_HASATTRIBUTE: Test if an Object has a named attribute
+f     - AST_IMPORT: Import an Object pointer to the current context
+f     - AST_ISA<CLASS>: Test class membership
+f     - AST_SAME: Do two AST pointers refer to the same Object?
+f     - AST_SET: Set attribute values for an Object
+f     - AST_SET<X>: Set an attribute value for an Object
+f     - AST_SHOW: Display a textual representation of an Object on standard
+f     output
+f     - AST_TEST: Test if an attribute value is set for an Object
+f     - AST_TUNE: Set or get an integer AST tuning parameter
+f     - AST_TUNEC: Set or get a character AST tuning parameter
+f     - AST_VERSION: Return the verson of the AST library being used.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     1-FEB-1996 (RFWS):
+*        Original version.
+*     22-APR-1996 (RFWS):
+*        Added attribute setting functions.
+*     2-JUL-1996 (RFWS):
+*        Added functions to support the external interface (using
+*        identfiers).
+*     10-SEP-1996 (RFWS):
+*        Added I/O facilities.
+*     30-MAY-1997 (RFWS):
+*        Add ID attribute.
+*     14-JUL-1997 (RFWS):
+*        Add astExempt function.
+*     14-OCT-1997 (RFWS):
+*        Fixed uninitialised use of "dynamic" in astCopy_.
+*     14-NOV-1997 (RFWS):
+*        Remove the subversive C "strtok" function.
+*     20-JAN-1998 (RFWS):
+*        Make the astClear and astRVSet methods virtual.
+*     29-APR-1998 (RFWS):
+*        Fixed bug in algorithm for encoding Object IDs.
+*     15-SEP-1999 (RFWS)
+*        Made astAnnulId accessible from protected code.
+*     12-APR-2000 (DSB):
+*        Zero all memory allocated for a new Object in InitObject before
+*        storing any new values in the memory.
+*     3-APR-2001 (DSB):
+*        Added the Ident attribute.
+*     28-SEP-2001 (DSB):
+*        Modified VSet to ensure a non-null string always follows the equal
+*        sign in the attribute setting passed to SetAttrib.
+*     27-NOV-2002 (DSB):
+*        Modified astShow to use astWrite instead of astDump, so that
+*        invocations of astShow will be included in the count of the
+*        number of invocations of astWrite returned by astWriteInvocations.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitObjectVtab
+*        method.
+*     8-FEB-2004 (DSB):
+*        Added astEscapes.
+*     10-FEB-2004 (DSB):
+*        Added debug conditional code to keep track of memory leaks.
+*     22-AUG-2004 (DSB):
+*        Added astEqual
+*     27-JAN-2005 (DSB):
+*        Correct use of ->ident pointers, and added further DEBUG blocks.
+*     11-MAR-2005 (DSB):
+*        Added attribute UseDefs.
+*     14-FEB-2006 (DSB):
+*        Added attribute ObjSize.
+*     23-FEB-2006 (DSB):
+*        Added MemoryCaching tuning parameter.
+*     27-FEB-2006 (DSB):
+*        Include Objects returned by astCopy in the ObjectCaching system.
+*     28-FEB-2006 (DSB):
+*        Use astOK to protect against errors within astGrow.
+*     1-MAR-2006 (DSB):
+*        Replace astSetPermMap within DEBUG blocks by astBeginPM/astEndPM.
+*     26-MAY-2006 (DSB):
+*        Correct handling of commas within the attribute value supplied
+*        to astSetC.
+*     30-MAY-2006 (DSB):
+*        Correct the correction made to handle commas within attribute
+*     6-JUN-2007 (DSB):
+*        Fix harmless compiler warnings.
+*     21-JUN-2007 (DSB):
+*        In astSet<X>, ignore trailing spaces in the attribute name.
+*     22-JUN-2007 (DSB):
+*        - Make astVSet return a pointer to dynamic memory holding the
+*        expanded setting string.
+*        - Add astSetVtab, and astCast.
+*     27-JUN-2007 (DSB):
+*        Modify astInitObject so that it ignores the supplied "size" value
+*        if some memory is supplied.
+*     2-JULY-2007 (DSB):
+*        Fix memory access problem in VSet.
+*     20-SEP-2007 (DSB):
+*        In astDelete, ensure the error status is reset before calling
+*        astGrow to extend the vtab free list.
+*     22-APR-2008 (DSB):
+*        Added astSame.
+*     24-OCT-2008 (DSB):
+*        Prevent a mutex deadlock that could occur when annulling an
+*        Object ID.
+*     28-JAN-2008 (DSB):
+*        Allow unlocked objects to be annulled using astAnnul.
+*     14-OCT-2009 (DSB):
+*        Modify astCast to make it a virtual function and add type
+*        checking.
+*     7-APR-2010 (DSB):
+*        Added method astHasAttribute.
+*     24-AUG-2010 (DSB):
+*        Allow commas to be included in attribute values supplied to
+*        astSet or astVSet by putting quotes around the attribute value.
+*     16-JUN-2011 (DSB):
+*        Added component "iref" to the Object structure. This is an
+*        integer identifier for each object that is unique within the
+*        class of the object. Useful for debugging.
+*     22-JUL-2011 (DSB):
+*        Add methods astSetProxy and astGetProxy.
+*     2-SEP-2011 (DSB):
+*        Add functions astToString and astFromString.
+*     13-SEP-2013 (DSB):
+*        Report an error in astAnnul if the supplied object handle is owned by
+*        a different thread. Note, the Object itself does not need to be owned
+*        by the current thread, since it should be possible for a thread to
+*        relinquish a pointer to an object (i.e. a handle) without actually
+*        owning the object itself.
+*     6-JAN-2014 (DSB):
+*        Added method astEnvSet.
+*     9-APR-2015 (DSB):
+*        Modify VSet to handle "%s" setting strings (i.e. where the whole
+*        list of settings is provided as a single variable argument).
+*        This is needed because supplying the while settings string in
+*        place of "%s" is considered a security issue by many compilers.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Object
+
+#define INVALID_CONTEXT -1   /* Context value for handles that have no
+                                associated Object */
+#define UNOWNED_CONTEXT -2   /* Context value for handles for objects
+                                that are not locked by any thread */
+
+
+/* Include files. */
+/* ============== */
+
+/* Configuration information */
+/* ------------------------ */
+#include "version.h"             /* Version numbers */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Interface definitions. */
+/* ---------------------- */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "channel.h"             /* I/O channels */
+#include "object.h"              /* Interface definition for this class */
+#include "plot.h"                /* Plot class (for astStripEscapes) */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+/* Type Definitions */
+/* ================ */
+/* Structure used to pass data between astToString/FromString and the
+   corresponding Channel source and sink functions. */
+typedef struct StringData {
+   char *ptr;      /* Pointer to serialisation text */
+   char *buff;     /* Pointer to a buffer for a single line of text */
+   int len;        /* Current length of serialisation text */
+} StringData;
+
+/* Module Variables. */
+/* ================= */
+
+/* The following globals have the same values in all threads and so do
+   not need to be in thread-specific data. */
+/* ------------------------------------------------------------------ */
+
+/* Character-valued tuning parameters. */
+#define MAXLEN_TUNEC 200
+static char hrdel[ MAXLEN_TUNEC ] = "%-%^50+%s70+h%+";
+static char mndel[ MAXLEN_TUNEC ] = "%-%^50+%s70+m%+";
+static char scdel[ MAXLEN_TUNEC ] = "%-%^50+%s70+s%+";
+static char dgdel[ MAXLEN_TUNEC ] = "%-%^53+%s60+o%+";
+static char amdel[ MAXLEN_TUNEC ] = "%-%^20+%s85+'%+";
+static char asdel[ MAXLEN_TUNEC ] = "%-%^20+%s85+\"%+";
+static char exdel[ MAXLEN_TUNEC ] = "10%-%^50+%s70+";
+
+/* A pointer full of zeros. */
+static AstObject *zero_ptr;
+
+/* A flag which indicates what should happen when an AST Object is
+   deleted. If this flag is non-zero, the memory used by the Object is
+   not freed, but a pointer to it is placed on the end of a list of free
+   memory chunk pointers so that the memory can be re-used if necessary
+   avoiding the need to re-allocate memory with malloc (which is slow).
+   A separate list of free memory chunks is kept for each class because
+   each class object will require chunks of a different size. Pointers
+   to these lists are stored in the virtual function table associated
+   with each class. All memory on these lists is freed when object
+   caching is switched off via the astTune function. */
+static int object_caching = 0;
+
+/* Set up global data access, mutexes, etc, needed for thread safety. */
+#ifdef THREAD_SAFE
+
+/* Define the initial values for the global data for this module. */
+#define GLOBAL_inits \
+   globals->Retain_Esc = 0; \
+   globals->Context_Level = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->AstGetC_Init = 0; \
+   globals->AstGetC_Istr = 0; \
+   globals->Active_Handles = NULL; \
+   globals->Class_Init = 0; \
+   globals->Nvtab = 0; \
+   globals->Known_Vtabs = NULL;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Object)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define retain_esc  astGLOBAL(Object,Retain_Esc)
+#define context_level  astGLOBAL(Object,Context_Level)
+#define active_handles  astGLOBAL(Object,Active_Handles)
+#define getattrib_buff  astGLOBAL(Object,GetAttrib_Buff)
+#define astgetc_strings  astGLOBAL(Object,AstGetC_Strings)
+#define astgetc_istr  astGLOBAL(Object,AstGetC_Istr)
+#define astgetc_init  astGLOBAL(Object,AstGetC_Init)
+#define class_init astGLOBAL(Object,Class_Init)
+#define class_vtab astGLOBAL(Object,Class_Vtab)
+#define nvtab astGLOBAL(Object,Nvtab)
+#define known_vtabs astGLOBAL(Object,Known_Vtabs)
+
+/* mutex1 is used to prevent tuning parameters being accessed by more
+   than one thread at any one time.  */
+static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX1 pthread_mutex_lock( &mutex1 );
+#define UNLOCK_MUTEX1 pthread_mutex_unlock( &mutex1 );
+
+/* mutex2 is used to prevent the global lists of object handles being
+   accessed by more than one thread at any one time.  */
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+/* Each Object contains two mutexes. The primary mutex (mutex1) is used
+   to guard access to all aspects of the Object except for the "locker"
+   and "ref_count" items. The secondary mutex (mutex2) is used to guard
+   access to these two remaining items. We need this secondary mutex
+   since the "locker" and "ref_count" items need to be accessable within
+   a thread even if that thread has not locked the Object using astLock.
+   Define macros for accessing these two mutexes. */
+#define LOCK_PMUTEX(this) (pthread_mutex_lock(&((this)->mutex1)))
+#define UNLOCK_PMUTEX(this) (pthread_mutex_unlock(&((this)->mutex1)))
+#define LOCK_SMUTEX(this) (pthread_mutex_lock(&((this)->mutex2)))
+#define UNLOCK_SMUTEX(this) (pthread_mutex_unlock(&((this)->mutex2)))
+
+
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Define the class virtual function table and its initialisation flag as
+   static variables. */
+static int class_init = 0;       /* Virtual function table initialised? */
+static AstObjectVtab class_vtab; /* Virtual function table */
+
+/* A list of pointers to all the known class virtual function tables. */
+static int nvtab = 0;
+static AstObjectVtab **known_vtabs = NULL;
+
+/* A flag which indicates if AST functions which return text strings
+   should retain any graphical escape sequences (as interpreted by the
+   Plot class). */
+static int retain_esc = 0;
+
+/* Context level (Begin/End/Exempt/Export) */
+static int context_level = 0;
+
+/* Array of list heads for each context (each list is a list of Handle
+   structures). */
+static int *active_handles = NULL;
+
+/* String returned by GetAttrib. */
+static char getattrib_buff[ AST__GETATTRIB_BUFF_LEN + 1 ] = "";
+
+/* Pointers to string buffers returned by astGetC. */
+static char *astgetc_strings[ AST__ASTGETC_MAX_STRINGS ];
+
+/* Offset of next string in "AstGetC_Strings" */
+static int astgetc_istr = 0;
+
+/* "AstGetC_Strings" array initialised? */
+static int astgetc_init = 0;
+
+/* Null macros for mutex locking and unlocking */
+#define LOCK_MUTEX1
+#define UNLOCK_MUTEX1
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+#define LOCK_PMUTEX(this)
+#define LOCK_SMUTEX(this)
+#define UNLOCK_PMUTEX(this)
+#define UNLOCK_SMUTEX(this)
+
+#endif
+
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstObject *Cast( AstObject *, AstObject *, int * );
+static const char *GetID( AstObject *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static const char *GetIdent( AstObject *, int * );
+static const char *Get( AstObject *, const char *, int * );
+static const char *FromStringSource( void );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetObjSize( AstObject *, int * );
+static int HasAttribute( AstObject *, const char *, int * );
+static int Same( AstObject *, AstObject *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestID( AstObject *, int * );
+static int TestIdent( AstObject *, int * );
+static unsigned long Magic( const AstObject *, size_t, int * );
+static void CleanAttribs( AstObject *, int * );
+static void Clear( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearIdent( AstObject *, int * );
+static void ClearID( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void EmptyObjectCache( int * );
+static void ToStringSink( const char * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetID( AstObject *, const char *, int * );
+static void SetIdent( AstObject *, const char *, int * );
+static void Show( AstObject *, int * );
+static void VSet( AstObject *, const char *, char **, va_list, int * );
+static void EnvSet( AstObject *, int * );
+
+static int GetUseDefs( AstObject *, int * );
+static int TestUseDefs( AstObject *, int * );
+static void ClearUseDefs( AstObject *, int * );
+static void SetUseDefs( AstObject *, int, int * );
+
+#if defined(THREAD_SAFE)
+static void ChangeThreadVtab( AstObject *, int * );
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+AstObject *astAnnul_( AstObject *this, int *status ) {
+/*
+*++
+*  Name:
+c     astAnnul
+f     AST_ANNUL
+
+*  Purpose:
+*     Annul a pointer to an Object.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     AstObject *astAnnul( AstObject *this )
+f     CALL AST_ANNUL( THIS, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function annuls a pointer to an Object so that it is no
+f     This routine annuls a pointer to an Object so that it is no
+*     longer recognised as a valid pointer by the AST library. Any
+*     resources associated with the pointer are released and made
+*     available for re-use.
+*
+c     This function also decrements the Object's RefCount attribute by
+f     This routine also decrements the Object's RefCount attribute by
+*     one. If this attribute reaches zero (which happens when the last
+*     pointer to the Object is annulled), then the Object is deleted.
+
+*  Parameters:
+c     this
+c        The Object pointer to be annulled.
+f     THIS = INTEGER (Given and Returned)
+f        The Object pointer to be annulled. A null pointer value (AST__NULL)
+f        is always returned.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+c  Returned Value:
+c     astAnnul()
+c        A null Object pointer (AST__NULL) is always returned.
+c
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Notes:
+c     - This function will attempt to annul the pointer even if the
+c     Object is not currently locked by the calling thread (see astLock).
+c     - This function attempts to execute even if the AST error
+c     status is set
+f     - This routine attempts to execute even if STATUS is set to an
+f     error value
+*     on entry, although no further error report will be
+*     made if it subsequently fails under these circumstances. In
+*     particular, it will fail if the pointer suppled is not valid,
+*     but this will only be reported if the error status is clear on
+*     entry.
+*--
+*/
+
+/* Check the pointer to ensure it identifies a valid Object (this
+   generates an error if it doesn't). */
+   if ( !astIsAObject( this ) ) return NULL;
+
+/* Get a lock on the object's secondary mutex. This mutex guards access
+   to the "ref_count" and "locker" components of the AstObject structure. */
+   LOCK_SMUTEX(this);
+
+#ifdef MEM_DEBUG
+   {   int rc;
+       char buf[100];
+       rc = this->ref_count;
+       sprintf(buf,"annulled (refcnt: %d -> %d)", rc, rc-1 );
+       astMemoryUse( this, buf );
+   }
+#endif
+
+/* Decrement the Object's reference count. */
+   --(this->ref_count);
+
+/* Unlock the object's secondary mutex. */
+   UNLOCK_SMUTEX(this);
+
+/* Decrement the Object's reference count and delete the Object if
+   necessary. */
+   if ( !this->ref_count ) (void) astDelete( this );
+
+/* Always return NULL. */
+   return NULL;
+}
+
+static AstObject *Cast( AstObject *this, AstObject *obj, int *status ) {
+/*
+*+
+*  Name:
+*     astCast
+
+*  Purpose:
+*     Cast an Object into an instance of a sub-class.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astCast( AstObject *this, AstObject *obj )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns a deep copy of an ancestral component of the
+*     supplied object. The required class of the ancestral component is
+*     specified by another object. Specifically, if "this" and "new" are
+*     of the same class, a copy of "this" is returned. If "this" is an
+*     instance of a subclass of "obj", then a copy of the component
+*     of "this" that matches the class of "obj" is returned. Otherwise,
+*     a NULL pointer is returned without error.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be cast.
+*     obj
+*        Pointer to an Object that defines the class of the returned Object.
+*        The returned Object will be of the same class as "obj".
+
+*  Returned Value:
+*     A pointer to the new Object. NULL if "this" is not a sub-class of
+*     "obj", or if an error occurs.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstObject *new;
+   int generation_gap;
+
+/* Initialise */
+   new = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return new;
+
+/* Check pointer have been supplied. */
+   if( this && obj ) {
+
+/* See how many steps up the class inheritance ladder it is from "this" to
+   "obj". A positive value is returned if "this" is a sub-class of "obj".
+   A negative value is returned if "obj" is a sub-class of "this". Zero
+   is returned if they are of the same class. AST__COUSIN is returned if
+   they share a common ancestor but are not on the same line of descent. */
+      generation_gap = astClassCompare( astVTAB( this ), astVTAB( obj ) );
+
+/* If the two objects are of the same class, just return a copy of
+   "this". */
+      if( generation_gap == 0 ) {
+         new = astCopy( this );
+
+/* If "this" is a subclass of "obj", return a deep copy of "this" cast
+   into the class of "obj". */
+      } else if( generation_gap != AST__COUSIN && generation_gap > 0 ) {
+         new = astCastCopy( this, obj );
+
+      }
+   }
+
+/* Return the new pointer. */
+   return new;
+}
+
+AstObject *astCastCopy_( AstObject *this, AstObject *obj, int *status ) {
+/*
+*+
+*  Name:
+*     astCastCopy
+
+*  Purpose:
+*     Cast an Object into an instance of a sub-class, without type-checking.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astCastCopy( AstObject *this, AstObject *obj )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns a deep copy of an ancestral component of the
+*     supplied object. The required class of the ancestral component is
+*     specified by another object. No checks are performed that "this" is
+*     a sub-class of "obj".
+*
+*     It works by temporarily changing the vtab in "this" to be the same
+*     as in "obj", and then doing a deep copy, and then re-instating the
+*     original vtab.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be cast.
+*     obj
+*        Pointer to an Object that defines the class of the returned Object.
+*        The returned Object will be of the same class as "obj".
+
+*  Returned Value:
+*     A pointer to the new Object.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstObject *new;
+   AstObjectVtab *this_vtab;
+   size_t this_size;
+
+/* Initialise */
+   new = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return new;
+
+/* Check pointer have been supplied. */
+   if( this && obj ) {
+
+/* Save a pointer to the original virtual function tables for "this". */
+      this_vtab = astVTAB( this );
+
+/* Temporarily change the vtab of "this" to that of "obJ". */
+      this->vtab = astVTAB( obj );
+
+/* Temporarily change the size of "this" to be the size of "obj". */
+      this_size = this->size;
+      this->size = obj->size;
+
+/* Now take a copy of the object (now considered to be an instance of the
+   class specified by "obj"). */
+      new = astCopy( this );
+
+/* Re-instate the original Object vtab and size. */
+      this->vtab = this_vtab;
+      this->size = this_size;
+
+/* The sub-clas to which "this" originally belonged may have extended the
+   range of values allowed for one or more of the attributes inherited from
+   the "obj" class. This means that the current attribute values stored
+   in the returned object may be inappropriate for the class of "obj". An
+   example is the System attribute defined by the Frame class, and extended
+   by sub-classes of Frame. So we now call astCleanAttribs to ensure that
+   any inappropriate attribute values are cleared in the returned object. */
+      astCleanAttribs( new );
+   }
+
+/* Return the new pointer. */
+   return new;
+}
+
+#if defined(THREAD_SAFE)
+static void ChangeThreadVtab( AstObject *this, int *status ){
+/*
+*  Name:
+*     ChangeThreadVtab
+
+*  Purpose:
+*     Modify an Object structure so that it refers to a vtab created by
+*     the currently executing thread.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void ChangeThreadVtab( AstObject *this, int *status )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     Each Object structure contains a pointer to a virtual function
+*     table (vtab) that identifies information about the class to
+*     which the Object belongs (function pointers, Object caches,
+*     etc). In order to avoid use of mutexes (which can slow down AST
+*     applications enormously), each thread has its own set of vtab
+*     structures (one for each AST class) stored in thread-specific
+*     data. Each time an Object is locked by the currently executing
+*     thread, this function should be called to change the vtab pointer
+*     in the Object to refer to the vtab relevant to the currently
+*     executing thread.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   const char *class;
+   int i;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to Thread-specific data for the currently executing thread. */
+   astGET_GLOBALS(this);
+
+/* Get the class name for the supplied Object. This uses the existing
+   vtab pointer in the Object structure to locate the required GetClass
+   method and the class name. This vtab pointer may be for a vtab created
+   by a different thread to the one currently executing, but this shouldn't
+   matter since we are not modifying the vtab contents. */
+   class = astGetClass( this );
+
+/* Check a class name was obtained */
+   if( class ) {
+
+/* Loop round the vtab structures created by the currently executing thread. */
+      for( i = 0; i < nvtab; i++ ) {
+
+/* If the current vtab is for a class that matches the class of the
+   supplied Object, then store a pointer to the vtab in the Object
+   structure, and exit. */
+         if( !strcmp( class, known_vtabs[ i ]->class ) ) {
+            this->vtab = known_vtabs[ i ];
+            break;
+         }
+      }
+   }
+}
+#endif
+
+AstObject *astCheckLock_( AstObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astCheckLock
+
+*  Purpose:
+*     Check that supplied Object is locked by the calling thread.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astCheckLock( AstObject *this )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function reports an error if the supplied object has not
+*     previously been locked (using astLock) by the calling thread.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+
+*  Returned Value:
+*     A copy of the supplied pointer ("this") is returned. The Object
+*     reference count is not changed.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+
+*-
+*/
+
+/* This function does nothing in the non-threads version of libast. */
+#if defined(THREAD_SAFE)
+
+/* Local Variables; */
+   AstObject *fail;
+
+/* Check the supplied pointer. */
+   if( this ) {
+
+/* First use the private ManageLock function rather than the virtual
+   astManageLock method to check the top level Object is locked for use
+   by the current thread. This saves time and allows a more appropriate
+   error message to be issued. */
+      if( ManageLock( this, AST__CHECKLOCK, 0, NULL, status ) ) {
+         if( astOK ) {
+            astError( AST__LCKERR, "astCheckLock(%s): The supplied %s cannot "
+                      "be used since it is not locked for use by the current "
+                      "thread (programming error).", status, astGetClass( this ),
+                      astGetClass( this ) );
+         }
+
+/* If the top level Object is locked, now use the virtual astManageLock
+   method to check any objects contained within the top level Object. */
+      } else if( astManageLock( this, AST__CHECKLOCK, 0, &fail ) ) {
+         if( astOK ) {
+            astError( AST__LCKERR, "astCheckLock(%s): The supplied %s cannot "
+                      "be used since a %s contained within the %s is not "
+                      "locked for use by the current thread (programming "
+                      "error).", status, astGetClass( this ),
+                       astGetClass( this ), astGetClass( fail ),
+                       astGetClass( this ) );
+         }
+      }
+   }
+#endif
+
+/* Return the supploed pointer. */
+   return this;
+
+}
+
+int astClassCompare_( AstObjectVtab *class1, AstObjectVtab *class2,
+                      int *status ) {
+/*
+*+
+*  Name:
+*     astClassCompare
+
+*  Purpose:
+*     Determine the relationship between two AST classes.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int astClassCompare( AstObjectVtab *class1, AstObjectVtab *class2 )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns the number of steps up the class inheritance
+*     ladder from the class specified by "class1" to the class specified
+*     by "class2".
+
+*  Parameters:
+*     class1
+*        Pointer to a virtual function table describing the first AST class.
+*     class2
+*        Pointer to a virtual function table describing the second AST class.
+
+*  Returned Value:
+*     The generation gap between "class1" and "class2". The result will be
+*     positive if "class1" is a subclass of "class2", negative if "class2"
+*     is a subclass of "class1", zero if they are of the same class (or
+*     an error occurs), or AST__COUSIN if they are not on the same line
+*     of descent.
+
+*-
+*/
+
+/* Local Variables: */
+   AstClassIdentifier *class1_id;
+   AstClassIdentifier *class2_id;
+   AstClassIdentifier *id;
+   int *class1_check;
+   int *class2_check;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Check pointer have been supplied. */
+   if( class1 && class2 ) {
+
+/* Get pointers to the AstClassIdentifier that identifies the top-level
+   class of each vtab. */
+      class1_id = class1->top_id;
+      class2_id = class2->top_id;
+
+/* Class membership is specified by the "check" value in each class
+   identifier. Get the check values for both vtabs. */
+      class1_check = class1_id->check;
+      class2_check = class2_id->check;
+
+/* Try walking up the class heirarchy of "class1" until the class of
+   "class2" is reached. The top-level AstObject class has a NULL "parent"
+   pointer in its class identifier structure. */
+      id = class1_id;
+      while( id && ( id->check != class2_check ) ) {
+         id = id->parent;
+         result++;
+      }
+
+/* If "class1" is not a subclass of "class2", try walking up the class
+   heirarchy of "class2" until the class of "class1" is reached. */
+      if( !id ) {
+         result = 0;
+         id = class2_id;
+         while( id && ( id->check != class1_check ) ) {
+            id = id->parent;
+            result--;
+         }
+
+/* If "class2" is not a subclass of "class1", return AST__COUSIN. */
+         if( !id ) result = AST__COUSIN;
+      }
+   }
+
+/* Return the generation gap. */
+   return result;
+}
+
+static void CleanAttribs( AstObject *this_object, int *status ) {
+/*
+*+
+*  Name:
+*     astCleanAttribs
+
+*  Purpose:
+*     Clear any invalid set attribute values.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astCleanAttribs( AstObject *this, int *status )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function clears any attributes that are currently set to
+*     invalid values in the supplied object. This can happen for instance
+*     when an object is cast into an instance of a parent class using
+*     astCast, since sub-classes can extend the range of valid values
+*     an attribute can take.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be cleaned.
+*-
+*/
+
+/* The base Object class has no attributes that need cleaning. */
+
+}
+
+static void Clear( AstObject *this, const char *attrib, int *status ) {
+/*
+*++
+*  Name:
+c     astClear
+f     AST_CLEAR
+
+*  Purpose:
+*     Clear attribute values for an Object.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "object.h"
+c     void astClear( AstObject *this, const char *attrib )
+f     CALL AST_CLEAR( THIS, ATTRIB, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function clears the values of a specified set of attributes
+f     This routine clears the values of a specified set of attributes
+*     for an Object. Clearing an attribute cancels any value that has
+*     previously been explicitly set for it, so that the standard
+*     default attribute value will subsequently be used instead. This
+c     also causes the astTest function to return the value zero for
+f     also causes the AST_TEST function to return the value .FALSE. for
+*     the attribute, indicating that no value has been set.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Object.
+c     attrib
+f     ATTRIB = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string containing a
+c        comma-separated list of the names of the attributes to be cleared.
+f        A character string containing a comma-separated list of the
+f        names of the attributes to be cleared.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Notes:
+*     - Attribute names are not case sensitive and may be surrounded
+*     by white space.
+*     - It does no harm to clear an attribute whose value has not been
+*     set.
+*     - An error will result if an attempt is made to clear the value
+*     of a read-only attribute.
+*--
+*/
+
+/* Local Variables: */
+   char *buff;                   /* Pointer to character buffer */
+   char *name;                   /* Pointer to individual attribute name */
+   char *name_end;               /* Pointer to null at end of name */
+   int i;                        /* Loop counter for characters */
+   int j;                        /* Non-blank character count */
+   int len;                      /* Length of attrib string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain the length of the attrib string. */
+   len = (int) strlen( attrib );
+   if ( len != 0 ) {
+
+/* Allocate memory and store a copy of the string. */
+      buff = astStore( NULL, attrib, (size_t) ( len + 1 ) );
+      if ( astOK ) {
+
+/* Loop to process each element in the comma-separated list. */
+         name = buff;
+         while ( name ) {
+
+/* Change the comma at the end of each element to a null to terminate
+   the name. */
+            if ( ( name_end = strchr( name, ',' ) ) ) *name_end = '\0';
+
+/* Remove white space and upper case characters from the attribute
+   name. */
+            for ( i = j = 0; name[ i ]; i++ ) {
+               if ( !isspace( name[ i ] ) ) name[ j++ ] = tolower( name[ i ] );
+            }
+
+/* Terminate the attribute name and pass it to astClearAttrib to clear
+   the attribute (unless it is all blank, in which case we ignore
+   it). */
+            name[ j ] = '\0';
+            if ( j ) astClearAttrib( this, name );
+
+/* Check for errors and abort if any clear operation fails. Otherwise,
+   process the next attribute. */
+            if ( !astOK ) break;
+            name = name_end ? name_end + 1 : NULL;
+         }
+      }
+
+/* Free the memory allocated for the string buffer. */
+      buff = astFree( buff );
+   }
+}
+
+static void ClearAttrib( AstObject *this, const char *attrib, int *status ) {
+/*
+*+
+*  Name:
+*     astClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for an Object.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astClearAttrib( AstObject *this, const char *attrib )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function clears the value of a specified attribute for an
+*     Object, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+
+*  Notes:
+*     - The Object class does not have any writable attributes, so
+*     this function merely reports an error. It is intended to be
+*     extended by other class definitions.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* ID. */
+/* --- */
+   if ( !strcmp( attrib, "id" ) ) {
+      astClearID( this );
+
+/* Ident. */
+/* ------ */
+   } else if ( !strcmp( attrib, "ident" ) ) {
+      astClearIdent( this );
+
+/* UseDefs. */
+/* -------- */
+   } else if ( !strcmp( attrib, "usedefs" ) ) {
+      astClearUseDefs( this );
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Test if the attribute string matches any of the read-only
+   attributes of this class. If it does, then report an error. */
+   } else if ( !strcmp( attrib, "class" ) ||
+               !strcmp( attrib, "nobject" ) ||
+               !strcmp( attrib, "objsize" ) ||
+               !strcmp( attrib, "refcount" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Since no writable attributes are defined for the Object class, any
+   attempt to clear a value for anything else is also an error. */
+   } else {
+      astError( AST__BADAT, "astClear: The attribute name \"%s\" is invalid "
+               "for a %s.", status, attrib, astGetClass( this ) );
+   }
+}
+
+AstObject *astClone_( AstObject *this, int *status ) {
+/*
+*++
+*  Name:
+c     astClone
+f     AST_CLONE
+
+*  Purpose:
+*     Clone (duplicate) an Object pointer.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     AstObject *astClone( AstObject *this )
+f     RESULT = AST_CLONE( THIS, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns a duplicate pointer to an existing
+*     Object. It also increments the Object's RefCount attribute to
+*     keep track of how many pointers have been issued.
+*
+*     Note that this function is NOT equivalent to an assignment
+*     statement, as in general the two pointers will not have the same
+*     value.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Original pointer to the Object.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astClone()
+f     AST_CLONE = INTEGER
+*        A duplicate pointer to the same Object.
+
+*  Applicability:
+*     Object
+*        This function applies to all Objects.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a lock on the object's secondary mutex. This mutex guards access
+   to the "ref_count" and "locker" components of the AstObject structure. */
+   LOCK_SMUTEX(this);
+
+#ifdef MEM_DEBUG
+   {   int rc;
+       char buf[100];
+       rc = this->ref_count;
+       sprintf(buf,"cloned (refcnt: %d -> %d)", rc, rc+1 );
+       astMemoryUse( this, buf );
+   }
+#endif
+
+/* Increment the Object's reference count. */
+   this->ref_count++;
+
+/* Unlock the object's secondary mutex. */
+   UNLOCK_SMUTEX(this);
+
+/* Return a new pointer to the Object. */
+   return this;
+}
+
+AstObject *astCopy_( const AstObject *this, int *status ) {
+/*
+*++
+*  Name:
+c     astCopy
+f     AST_COPY
+
+*  Purpose:
+*     Copy an Object.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     AstObject *astCopy( const AstObject *this )
+f     RESULT = AST_COPY( THIS, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function creates a copy of an Object and returns a pointer
+*     to the resulting new Object. It makes a "deep" copy, which
+*     contains no references to any other Object (i.e. if the original
+*     Object contains references to other Objects, then the actual
+*     data are copied, not simply the references). This means that
+*     modifications may safely be made to the copy without indirectly
+*     affecting any other Object.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Object to be copied.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astCopy()
+f     AST_COPY = INTEGER
+*        Pointer to the new Object.
+
+*  Applicability:
+*     Object
+*        This function applies to all Objects.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstObject *new;               /* Pointer to new object */
+   AstObjectVtab *vtab;          /* Pointer to object vtab */
+   int i;                        /* Loop counter for copy constructors */
+
+/* Initiallise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Re-use cached memory, or allocate new memory using the size of the input
+   object, to store the output Object. */
+
+   vtab = this->vtab;
+   if( object_caching ){
+
+      if( vtab->nfree > 0 ) {
+         new = vtab->free_list[ --(vtab->nfree) ];
+         vtab->free_list[ vtab->nfree ] = NULL;
+      } else {
+         new = astMalloc( this->size );
+      }
+
+   } else {
+      new = astMalloc( this->size );
+   }
+
+   if ( astOK ) {
+
+/* Perform an initial byte-by-byte copy of the entire object
+   structure. */
+      (void) memcpy( (void *) new, (const void *) this, this->size );
+
+/* Initialise any components of the new Object structure that need to
+   differ from the input. */
+      new->check = Magic( new, new->size, status );
+      new->dynamic = 1;
+      new->ref_count = 1;
+      new->id = NULL;   /* ID attribute is not copied (but Ident is copied) */
+      new->proxy = NULL;
+
+/* Copy the persistent identifier string. */
+      if( this->ident ) {
+         new->ident = astStore( NULL, this->ident, strlen( this->ident ) + 1 );
+      }
+
+/* Create a new mutex for the new Object, and lock it for use by the
+   current thread. */
+#ifdef THREAD_SAFE
+      if( pthread_mutex_init( &(new->mutex1), NULL ) != 0 && astOK ) {
+         astError( AST__INTER, "astInitObject(%s): Failed to "
+                   "initialise POSIX mutex1 for the new Object.", status,
+                   vtab->class );
+      }
+      if( pthread_mutex_init( &(new->mutex2), NULL ) != 0 && astOK ) {
+         astError( AST__INTER, "astInitObject(%s): Failed to "
+                   "initialise POSIX mutex2 for the new Object.", status,
+                   vtab->class );
+      }
+      new->locker = -1;
+      new->globals = NULL;
+      (void) ManageLock( new, AST__LOCK, 0, NULL, status );
+#endif
+
+/* Loop to execute any copy constructors declared by derived classes. */
+      for ( i = 0; i < vtab->ncopy; i++ ) {
+
+/* Invoke each copy constructor in turn. */
+         (*vtab->copy[ i ])( this, new, status );
+
+/* If any copy constructor fails, work backwards through the
+   corresponding destructor functions, invoking each in turn to undo
+   the copy operations that have been completed so far. */
+         if ( !astOK ) {
+            for ( ; i >= 0; i-- ) {
+               (*vtab->delete[ i ])( new, status );
+            }
+
+/* Zero the entire new Object structure (to prevent accidental re-use
+   of any of its values after deletion). */
+            (void) memset( new, 0, new->size );
+
+/* Free the Object's memory and ensure that a NULL pointer will be
+   returned. */
+            new = astFree( new );
+
+/* Quit trying to copy the Object. */
+            break;
+         }
+      }
+   }
+
+/* If OK, increment the count of active objects. */
+   if ( astOK ) vtab->nobject++;
+
+/* Return a pointer to the new Object. */
+   return new;
+}
+
+AstObject *astDelete_( AstObject *this, int *status ) {
+/*
+*++
+*  Name:
+c     astDelete
+f     AST_DELETE
+
+*  Purpose:
+*     Delete an Object.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     AstObject *astDelete( AstObject *this )
+f     CALL AST_DELETE( THIS, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function deletes an Object, freeing all resources
+f     This routine deletes an Object, freeing all resources
+*     associated with it and rendering any remaining pointers to the
+*     Object invalid.
+*
+*     Note that deletion is unconditional, regardless of whether other
+*     pointers to the Object are still in use (possibly within other
+*     Objects). A safer approach is to defer deletion, until all
+c     references to an Object have expired, by using astBegin/astEnd
+c     (together with astClone and astAnnul if necessary).
+f     references to an Object have expired, by using AST_BEGIN/AST_END
+f     (together with AST_CLONE and AST_ANNUL if necessary).
+
+*  Parameters:
+c     this
+c        Pointer to the Object to be deleted.
+f     THIS = INTEGER (Given and Returned)
+f        Pointer to the Object to be deleted. A null pointer value
+f        (AST__NULL) is always returned.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+c  Returned Value:
+c     astDelete()
+c        A null Object pointer (AST__NULL) is always returned.
+c
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Notes:
+c     - This function attempts to execute even if the AST error status
+c     is set
+f     - This routine attempts to execute even if STATUS is set to an error
+f     value
+*     on entry, although no further error report will be
+*     made if it subsequently fails under these circumstances.
+*--
+*/
+
+/* Local Variables: */
+   AstObjectVtab *vtab;          /* Pointer to virtual function table */
+   int dynamic;                  /* Was memory allocated dynamically? */
+   int i;                        /* Loop counter for destructors */
+   int ifree;                    /* Index of next slot on free list */
+   int status_value;             /* AST error status value */
+   size_t size;                  /* Object size */
+
+/* Check the pointer to ensure it identifies a valid Object (this
+   generates an error if it doesn't). */
+   if ( !astIsAObject( this ) ) return NULL;
+
+/* Loop through all the destructors associated with the Object by derived
+   classes (working up the class hierarchy). */
+   for ( i = this->vtab->ndelete - 1; i >= 0; i-- ) {
+
+/* Invoke each destructor in turn. Attempt to continue even if destructors
+   fail. */
+      ( *this->vtab->delete[ i ] )( this, status );
+   }
+
+/* Free the ID strings. */
+   this->id = astFree( this->id );
+   this->ident = astFree( this->ident );
+
+/* Attempt to unlock the Object and destroy its mutexes. */
+#if defined(THREAD_SAFE)
+   (void) ManageLock( this, AST__UNLOCK, 0, NULL, status );
+   pthread_mutex_destroy( &(this->mutex1) );
+   pthread_mutex_destroy( &(this->mutex2) );
+#endif
+
+/* Save the virtual function table address and note if the Object's
+   memory was allocated dynamically. Also note its size. */
+   vtab = this->vtab;
+   dynamic = this->dynamic;
+   size = this->size;
+
+/* Zero the entire Object structure (to prevent accidental re-use of
+   any of its values after deletion). */
+   (void) memset( this, 0, size );
+
+/* If necessary, free the Object's memory. If object caching is switched
+   on, the memory is not in fact freed; it is merely placed onto the end
+   of the list of free memory blocks included in the virtual function table
+   of the AST class concerned. astGrow returns immediately if an error
+   has already occurred, so we need to reset the error status explicitly
+   before calling astGrow. */
+   if ( dynamic ) {
+      if( object_caching ) {
+         ifree = (vtab->nfree)++;
+
+         status_value = astStatus;
+         astClearStatus;
+         vtab->free_list = astGrow( vtab->free_list, vtab->nfree,
+                                    sizeof(AstObject *) );
+         astSetStatus( status_value );
+
+         if( vtab->free_list ) vtab->free_list[ ifree ] = this;
+      } else {
+         (void) astFree( this );
+      }
+   }
+
+/* Decrement the count of active Objects. */
+   vtab->nobject--;
+
+/* Always return NULL. */
+   return NULL;
+}
+
+static void Dump( AstObject *this, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astDump
+
+*  Purpose:
+*     Write an Object to a Channel.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astDump( AstObject *this, AstChannel *channel )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function writes an Object to a Channel, appending it to any
+*     previous Objects written to that Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be written.
+*     channel
+*        Pointer to the output Channel.
+*-
+*/
+
+/* Local Variables: */
+   AstObjectVtab *vtab;          /* Pointer to virtual function table */
+   const char *sval;             /* Pointer to string value */
+   int helpful;                  /* Helpful to show value even if not set? */
+   int idump;                    /* Loop counter for dump functions */
+   int ival;                     /* Attribute value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Write an initial "Begin" item, giving the class name of the Object
+   being written. Also supply a pointer to the comment associated with
+   the most recently-declared dump function in the Object's virtual
+   function table. This should describe the class to which the Object
+   belongs (assuming it has correctly declared its dump function). */
+   astWriteBegin( channel, astGetClass( this ),
+                  this->vtab->dump_comment[ this->vtab->ndump - 1 ] );
+
+/* Write out instance variable information for the base Object
+   class. Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* ID. */
+/* --- */
+   set = TestID( this, status );
+   sval = set ? GetID( this, status ) : astGetID( this );
+
+/* Don't show an un-set ID value if it is blank. */
+   helpful = ( sval && *sval );
+   astWriteString( channel, "ID", set, helpful, sval,
+                   "Object identification string" );
+
+/* Ident. */
+/* --- */
+   set = TestIdent( this, status );
+   sval = set ? GetIdent( this, status ) : astGetIdent( this );
+
+/* Don't show an un-set Ident value if it is blank. */
+   helpful = ( sval && *sval );
+   astWriteString( channel, "Ident", set, helpful, sval,
+                   "Permanent Object identification string" );
+
+/* UseDefs */
+/* ------- */
+   set = TestUseDefs( this, status );
+   ival = set ? GetUseDefs( this, status ) : astGetUseDefs( this );
+   astWriteInt( channel, "UseDfs", set, 0, ival,
+                ival ? "Default attribute values can be used" :
+                       "Default values cannot be used" );
+
+/* RefCnt. */
+/* ------- */
+   LOCK_SMUTEX(this);
+   ival = this->ref_count;
+   UNLOCK_SMUTEX(this);
+
+   astWriteInt( channel, "RefCnt", 0, 0, ival,
+                "Count of active Object pointers" );
+
+
+/* Nobj. */
+/* ----- */
+   vtab = this->vtab;
+   astWriteInt( channel, "Nobj", 0, 0, vtab->nobject,
+                "Count of active Objects in same class" );
+
+/* Terminate the information above with an "IsA" item for the base
+   Object class. */
+   astWriteIsA( channel, "Object", "AST Object" );
+
+/* Now loop to perform the same operation for each additional class
+   from which the Object inherits (the Object class itself does not
+   declare a dump function). Invoke the dump function for each class
+   in turn, working down the class hierarchy, to write out instance
+   variable information for that class. */
+   for ( idump = 0; idump < this->vtab->ndump; idump++ ) {
+      ( *this->vtab->dump[ idump ] )( this, channel, status );
+
+/* Terminate the output from all except the final dump function with
+   an appropriate "IsA" item describing the class whose data have just
+   been written. */
+      if ( idump != ( this->vtab->ndump - 1 ) ) {
+         astWriteIsA( channel, this->vtab->dump_class[ idump ],
+                      this->vtab->dump_comment[ idump ] );
+      }
+
+/* Quit looping if an error occurs. */
+      if ( !astOK ) break;
+   }
+
+/* Terminate the output from the final dump function with an "End"
+   item to match the initial "Begin" item. */
+   astWriteEnd( channel, astGetClass( this ) );
+}
+
+static void EmptyObjectCache( int *status ){
+/*
+*  Name:
+*     EmptyObjectCache
+
+*  Purpose:
+*     Free all memory blocks currently on the free list of any class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     EmptyObjectCache( int *status )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function empties the cache of Object memory by freeing all
+*     memory blocks on the free_list of all classes.
+
+*  Parameters:
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This function attempts to execute even if an error has occurred.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS    /* Thread-specific global data */
+   int iblock;           /* Index of next entry in free list */
+   int itab;             /* Index of next virtual function table */
+   AstObjectVtab *vtab;  /* Pointer to next virtual function table */
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Loop round all the virtual function tables which are known about. */
+   for( itab = 0; itab < nvtab; itab++ ) {
+      vtab = known_vtabs[ itab ];
+
+/* Free all memory blocks stored on the free list for this class. */
+      for( iblock = 0; iblock < vtab->nfree; iblock++ ) {
+         (vtab->free_list)[ iblock ] = astFree( (vtab->free_list)[ iblock ] );
+      }
+
+/* Free the memory used to hold the free list, and indicate it has zero
+   length. */
+      vtab->free_list = astFree( vtab->free_list );
+      vtab->nfree = 0;
+   }
+}
+
+static void EnvSet( AstObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astEnvSet
+
+*  Purpose:
+*     Set default values for an Object's attributes.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astEnvSet( AstObject *this )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function assigns a set of attribute values for an Object,
+*     the attributes and their values being specified by means of an
+*     environment variable of the form "<CLASSNAME>_OPTIONS" that has
+*     a value of the form:
+*
+*        "attribute1 = value1, attribute2 = value2, ... "
+*
+*     Here, "attribute" specifies an attribute name and the value to
+*     the right of each "=" sign should be a suitable textual
+*     representation of the value to be assigned to that
+*     attribute. This will be interpreted according to the attribute's
+*     data type.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+
+*  Notes:
+*     - See astVSet for details of how the setting strings are
+*     interpreted.
+*-
+*/
+
+/* Local Variables: */
+   char varname[ 100 ];
+   const char *attrs = NULL;
+   const char *class = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the string holding default attribute values for the class of the
+   supplied object. This string is held in the class virtual function
+   table. */
+   attrs = this->vtab->defaults;
+
+/* If this is the first time the defaults have been requested, get the
+   list of defaults from the environment variable "<CLASSNAME>_OPTIONS"
+   and store in the virtual function table. */
+   if( !attrs ) {
+
+/* Get the class name. */
+      class = astGetClass( this );
+
+/* Form the upper-case name of the environment variable. */
+      if( class ) {
+         sprintf( varname, "%s_OPTIONS", class );
+         astChrCase( NULL, varname, 1, sizeof( varname ) );
+
+/* Get the value of the environment variable. */
+         attrs = getenv( varname );
+
+/* If no defaults were specified store the string "None". */
+         if( ! attrs ) attrs = "None";
+
+/* Store a copy in the virtual function table. */
+         astBeginPM;
+         this->vtab->defaults = astStore( NULL, attrs, strlen( attrs ) + 1 );
+         astEndPM;
+      }
+   }
+
+/* If any defaults were specified, set the corresponding attributes. */
+   if( attrs && strcmp( attrs, "None" ) ) astSet( this, attrs, status );
+
+}
+
+static int Equal( AstObject *this, AstObject *that, int *status ){
+/*
+*+
+*  Name:
+*     astEqual
+
+*  Purpose:
+*     Check equality of two AST Objects.
+
+*  Type:
+*     Public (but undocumented) function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int astEqual( AstObject *this, AstObject *this )
+
+*  Class Membership:
+*     Object virtual function.
+
+*  Description:
+*     This function returns non-zero if the two pointers identify
+*     equivalent objects.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object.
+*     that
+*        Pointer to the second Object.
+
+*  Returned Value:
+*     Non-zero if the objects are equivalent.
+
+*  Notes:
+*    - This function is available in the public interface even though it is
+*    documented as protected. This is because it is difficult to document
+*    precisely which aspects of two Objects must be equal in order for this
+*    function to return a non-zero value. Each class of Object supplies
+*    its own Equal method that tests which-ever attributes the class
+*    considers to be significiant.
+*    - The implementation of this function provided by the base Object
+*    class simply compares the class names and the structure size.
+*    Sub-classes should override this method to provide more appropriate tests.
+*    - Zero is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*-
+*/
+
+/* Local Variables: */
+   int result;
+
+/* Check inherited status */
+   if( !astOK ) return 0;
+
+/* Objects are equivalent if they are the same object. */
+   if( this == that ) {
+      result = 1;
+
+/* Otherwise, check the structure size and class names */
+   } else {
+      result = ( this->size == that->size &&
+                 !strcmp( astGetClass( this ), astGetClass( that ) ) );
+   }
+
+   return result;
+}
+
+static const char *Get( AstObject *this, const char *attrib, int *status ) {
+/*
+*  Name:
+*     Get
+
+*  Purpose:
+*     Get the value of a specified attribute for an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     const char *Get( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for an Object, formatted as a character string. It is
+*     mainly a wrap-up used internally for invoking the astGetAttrib
+*     method. It converts the attribute name to lower case and removes
+*     white space before invoking the method. This saves derived
+*     classes that over-ride the astGetAttrib method from having to do
+*     this themselves.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This may contain mixed
+*        case and white space, but should not be composed entirely of
+*        white space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Object, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Object. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   char *buff;                   /* Pointer to local string buffer */
+   const char *result;           /* Pointer value to return */
+   int i;                        /* Loop counter for characters */
+   int j;                        /* Non-blank character count */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Allocate a local buffer long enough to hold the attribute name
+   string. */
+   buff = astMalloc( strlen( attrib ) + (size_t) 1 );
+   if ( astOK ) {
+
+/* Copy the attribute name characters into the buffer, omitting all
+   white space and converting to lower case. */
+      for ( i = j = 0; attrib[ i ]; i++ ) {
+         if ( !isspace( attrib[ i ] ) ) buff[ j++ ] = tolower( attrib[ i ] );
+      }
+
+/* Terminate the copied string. */
+      buff[ j ] = '\0';
+
+/* If no characters were copied, the attribute name was blank, so
+   report an error. */
+      if ( !j ) {
+         if( astOK ) astError( AST__BADAT, "astGet(%s): A blank attribute "
+                               "name was given.", status, astGetClass( this ) );
+
+/* Of OK, invoke astGetAttrib to obtain a pointer to the attribute
+   value formatted as a character string. */
+      } else {
+         result = astGetAttrib( this, buff );
+
+/* If required, strip out graphical escape sequences. */
+         if( !astEscapes( -1 ) ) result = astStripEscapes( result );
+      }
+   }
+
+/* Free the local string buffer. */
+   buff = astFree( buff );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this, const char *attrib, int *status ) {
+/*
+*+
+*  Name:
+*     astGetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for an Object.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "object.h"
+*     const char *astGetAttrib( AstObject *this, const char *attrib )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for an Object, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Object, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Object. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   const char *result;           /* Pointer value to return */
+   int nobject;                  /* Nobject attribute value */
+   int objsize;                  /* ObjSize attribute value */
+   int ref_count;                /* RefCount attribute value */
+   int usedefs;                  /* UseDefs attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an
+   appropriate format.  Set "result" to point at the result string. */
+
+/* Class. */
+/* ------ */
+   if ( !strcmp( attrib, "class" ) ) {
+      result = astGetClass( this );
+
+/* ID. */
+/* --- */
+   } else if ( !strcmp( attrib, "id" ) ) {
+      result = astGetID( this );
+
+/* Ident. */
+/* ------ */
+   } else if ( !strcmp( attrib, "ident" ) ) {
+      result = astGetIdent( this );
+
+/* UseDefs */
+/* ------- */
+   } else if ( !strcmp( attrib, "usedefs" ) ) {
+      usedefs = astGetUseDefs( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", usedefs );
+         result = getattrib_buff;
+      }
+
+/* Nobject. */
+/* -------- */
+   } else if ( !strcmp( attrib, "nobject" ) ) {
+      nobject = astGetNobject( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", nobject );
+         result = getattrib_buff;
+      }
+
+/* ObjSize */
+/* ------- */
+   } else if ( !strcmp( attrib, "objsize" ) ) {
+      objsize = astGetObjSize( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", objsize );
+         result = getattrib_buff;
+      }
+
+/* RefCount. */
+/* --------- */
+   } else if ( !strcmp( attrib, "refcount" ) ) {
+      ref_count = astGetRefCount( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ref_count );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, then report an error. */
+   } else if( astOK ){
+      astError( AST__BADAT, "astGet: The %s given does not have an attribute "
+                "called \"%s\".", status, astGetClass( this ), attrib );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+const char *astGetClass_( const AstObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetClass
+
+*  Purpose:
+*     Obtain the value of the Class attribute for an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     const char *astGetClass( const AstObject *this )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns a pointer to the Class string for an
+*     Object. This contains the name of the class which created the
+*     Object.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+
+*  Returned Value:
+*     Pointer to a string containing the class name.
+
+*  Notes:
+*     - This function does not check the global error status before
+*     executing.  This is to allow it to be used to obtain class names
+*     for inclusion in error messages.
+*     - A pointer to an explanatory string will be returned if this
+*     function is given a pointer which does not identify an Object.
+*-
+*/
+
+/* Local Variables: */
+   const char *name;             /* Pointer to returned string */
+
+/* First check if the Object pointer supplied is NULL, and set the
+   returned pointer accordingly. */
+   if ( !this ) {
+      name = "<NULL>";
+
+/* Also check if the supposed Object has the correct "magic number" in
+   its check field. If not, it is not an Object. */
+   } else if ( this->check != Magic( this, this->size, status ) ) {
+      name = "<unknown>";
+
+/* If OK, obtain a pointer to the class name from the Object's virtual
+   function table. */
+   } else {
+      name = this->vtab->class;
+   }
+
+/* Return the result. */
+   return name;
+}
+
+int astGetNobject_( const AstObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNobject
+
+*  Purpose:
+*     Obtain the value of the Nobject attribute for an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int astGetNobject( const AstObject *this )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns the value of the Nobject attribute for an
+*     Object. This is a count of the number of active Objects in the
+*     same class as the Object supplied. This count does not include
+*     Objects in derived classes.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+
+*  Returned Value:
+*     The number of active Objects.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Return the active object count. */
+   return this->vtab->nobject;
+}
+
+static int GetObjSize( AstObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetObjSize
+
+*  Purpose:
+*     Determine the in-memory size of the Object.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int astGetObjSize( AstObject *this )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns the in-memory size of an Object.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Return the object size. */
+   return this->size;
+}
+
+void *astGetProxy_( AstObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetProxy
+
+*  Purpose:
+*     Get a pointer to the foreign language proxy used to represent a
+*     given AST Object.
+
+*  Type:
+*     Undocumented public function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void *astGetProxy( AstObject *this )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns any pointer stored previously in the AST
+*     Object using astSetProxy. If no such pointer has been stored, a
+*     NULL pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+
+*  Returned Value:
+*     Pointer to the proxy object, or NULL.
+
+*  Notes:
+*     - This function is public, but is currently undocumented since it
+*     is only of interest to people writing AST interfaces for other
+*     languages.
+*     - This function attempts to execute even if the AST error status
+*     is set on entry, although no further error report will be made
+*     if it subsequently fails under these circumstances.
+*-
+*/
+   return this ? this->proxy : NULL;
+}
+
+int astGetRefCount_( AstObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetRefCount
+
+*  Purpose:
+*     Obtain the value of the RefCount attribute for an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int astGetRefCount( const AstObject *this )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns the value of the read-only RefCount
+*     attribute for an Object. This is a "reference count" of the
+*     number of active pointers to it, as accounted for by astClone
+*     and astAnnul (plus the pointer issued when it was created).  If
+*     the reference count for an Object falls to zero when astAnnul is
+*     invoked, the object will be deleted.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+
+*  Returned Value:
+*     The reference count.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables; */
+   int result;          /* Returned value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get a lock on the object's secondary mutex. This mutex guards access
+   to the "ref_count" and "locker" components of the AstObject structure. */
+   LOCK_SMUTEX(this);
+
+/* Get the reference count. */
+   result = this->ref_count;
+
+/* Unlock the object's secondary mutex. */
+   UNLOCK_SMUTEX(this);
+
+/* Return the result. */
+   return result;
+}
+
+/*
+*++
+*  Name:
+c     astGet<X>
+f     AST_GET<X>
+
+*  Purpose:
+*     Get an attribute value for an Object.
+
+*  Type:
+*     Public functions.
+
+*  Synopsis:
+c     #include "object.h"
+c     <X>type astGet<X>( AstObject *this, const char *attrib )
+f     RESULT = AST_GET<X>( THIS, ATTRIB, STATUS )
+
+*  Class Membership:
+*     Object methods.
+
+*  Description:
+*     This is a family of functions which return a specified attribute
+*     value for an Object using one of several different data
+*     types. The type is selected by replacing <X> in the function name
+c     by C, D, F, I or L, to obtain a result in const char* (i.e. string),
+c     double, float, int, or long format, respectively.
+f     by C, D, I, L or R, to obtain a result in Character, Double
+f     precision, Integer, Logical or Real format, respectively.
+*
+*     If possible, the attribute value is converted to the type you
+*     request. If conversion is not possible, an error will result.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Object.
+c     attrib
+f     ATTRIB = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing the name of
+c        the attribute whose value is required.
+f        A character string containing the name of the attribute whose
+f        value is required.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGet<X>()
+f     AST_GET<X> = <X>type
+c        The attribute value, in the data type corresponding to <X> (or,
+c        in the case of astGetC, a pointer to a constant null-terminated
+c        character string containing this value).
+f        The attribute value, in the data type corresponding to <X>.
+
+*  Applicability:
+*     Object
+*        These functions apply to all Objects.
+
+*  Examples:
+c     printf( "RefCount = %d\n", astGetI( z, "RefCount" ) );
+c        Prints the RefCount attribute value for Object "z" as an int.
+c     title = astGetC( axis, "Title" );
+c        Obtains a pointer to a null-terminated character string containing
+c        the Title attribute of Object "axis".
+f     WRITE( *, '('' RefCount = '', A10 )' ) AST_GETC( Z, 'RefCount', STATUS )
+f        Prints the RefCount attribute value for Object Z as a character
+f        string.
+f     NAXES = AST_GETI( FRAME, 'Naxes', STATUS )
+f        Obtains the value of the Naxes attribute for Object FRAME as an
+f        integer.
+
+*  Notes:
+*     - Attribute names are not case sensitive and may be surrounded
+*     by white space.
+*     - An appropriate "null" value will be returned if this function
+c     is invoked with the AST error status set, or if it should
+f     is invoked with STATUS set to an error value, or if it should
+*     fail for any reason. This null value is zero for numeric
+c     values and NULL for pointer values.
+f     values, .FALSE. for logical values, and blank for character values.
+f     - Numerical attribute values of zero translate to logical value
+f     .FALSE. and all other numerical values translate to .TRUE..
+c     - The pointer returned by astGetC is guaranteed to remain valid
+c     and the string to which it points will not be over-written for a
+c     total of 50 successive invocations of this function. After this,
+c     the memory containing the string may be re-used, so a copy of
+c     the string should be made if it is needed for longer than this.
+*--
+*/
+
+/* Define a macro that expands to implement the astGetX_ member
+   functions required. The arguments to this macro are:
+
+      code
+         The character that appears at the end of the function name.
+      type
+         The C type of the function return value.
+      format
+         A quoted string containing a astSscanf format specifier that
+         will read the attribute value into a variable of the required
+         data type. This format should transfer 1 astSscanf value.
+*/
+#define MAKE_GETX(code,type,format) \
+type astGet##code##_( AstObject *this, const char *attrib, int *status ) { \
+\
+/* Local Variables: */ \
+   const char *str;              /* Pointer to string attribute value */ \
+   int nc;                       /* Number of characters read from string */ \
+   int nval;                     /* Number of values read from string */ \
+   type result;                  /* Value to return */ \
+   type value;                   /* Converted value */ \
+\
+/* Initialise. */ \
+   result = (type) 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Obtain the attribute value as a string. */ \
+   str = Get( this, attrib, status ); \
+   if ( astOK ) { \
+\
+/* Read the value from the string, ignoring surrounding white \
+   space. */ \
+      nc = 0; \
+      nval = astSscanf( str, " " format " %n", &value, &nc ); \
+\
+/* Check that the number of values read was 1 and that all the \
+   string's characters were consumed. If so, use the result. */ \
+      if ( ( nval == 1 ) && ( nc >= (int) strlen( str ) ) ) { \
+         result = value; \
+\
+/* If the read was unsuccessful, report an error. */ \
+      } else if( astOK ) { \
+         astError( AST__ATGER, "astGet" #code "(%s): The attribute " \
+                   "value \"%s=%s\" cannot be read using the requested data " \
+                   "type.", status,astGetClass( this ), attrib, str ); \
+      } \
+   } \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* Use this macro to create all the GetX_ private member functions,
+   except SetC (which is handled separately). */
+MAKE_GETX(D,double,"%lf")
+MAKE_GETX(F,float,"%f")
+MAKE_GETX(I,int,"%d")
+MAKE_GETX(L,long,"%ld")
+
+/* Handle GetC separately because memory must be allocated to hold the
+   returned character values. */
+const char *astGetC_( AstObject *this, const char *attrib, int *status ) {
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   const char *result;           /* Pointer value to return */
+   const char *value;            /* Pointer to attribute value */
+   int i;                        /* Loop count */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* If the "strings" array has not been initialised, fill it with
+   NULL pointers. */
+   if ( !astgetc_init ) {
+      astgetc_init = 1;
+      for ( i = 0; i < AST__ASTGETC_MAX_STRINGS; i++ ) astgetc_strings[ i ] = NULL;
+   }
+
+/* Obtain a pointer to the required attribute value, formatted as a
+   character string. */
+   value = Get( this, attrib, status );
+
+/* If OK, store a copy of the resulting string in dynamically
+   allocated memory, putting a pointer to the copy into the next
+   element of the "astgetc_strings" array.  (This process also de-allocates
+   any previously allocated memory pointed at by this "strings"
+   element, so the earlier string is effectively replaced by the new
+   one.) */
+   if ( astOK ) {
+
+      astBeginPM;
+      astgetc_strings[ astgetc_istr ] = astStore( astgetc_strings[ astgetc_istr ],
+                                             value, strlen( value ) + (size_t) 1 );
+      astEndPM;
+
+/* If OK, return a pointer to the copy and increment "astgetc_istr" to use the
+   next element of "astgetc_strings" on the next invocation. Recycle
+   "astgetc_istr" to zero when all elements have been used. */
+      if ( astOK ) {
+         result = astgetc_strings[ astgetc_istr++ ];
+         if ( astgetc_istr == ( AST__ASTGETC_MAX_STRINGS - 1 ) ) astgetc_istr = 0;
+      }
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static int HasAttribute( AstObject *this, const char *attrib, int *status ) {
+/*
+*++
+*  Name:
+c     astHasAttribute
+f     AST_HASATTRIBUTE
+
+*  Purpose:
+*     Test if an Object has a named attribute.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     int astHasAttribute( AstObject *this, const char *attrib )
+f     RESULT = AST_HASATTRIBUTE( THIS, ATTRIB, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function returns a boolean result (0 or 1) to indicate
+f     This function returns a logical result to indicate
+*     whether the supplied Object has an attribute with the supplied name.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the first Object.
+c     attrib
+f     ATTRIB = INTEGER (Given)
+c        Pointer to a string holding the
+f        The
+*        name of the attribute to be tested.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astHasAttribute()
+c        One if the Object has the named attribute, otherwise zero.
+f     AST_SAME = LOGICAL
+f        .TRUE. if the Object has the named attribute, otherwise
+f        .FALSE.
+
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Notes:
+c     - A value of zero will be returned if this function is invoked
+c     with the AST error status set, or if it should fail for any reason.
+f     - A value of .FALSE. will be returned if this function is invoked
+f     with STATUS set to an error value, or if it should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   int oldrep;           /* Original AST error reporting flag */
+   int result;           /* Returned value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Temporarily switch off error reporting. */
+   oldrep = astReporting( 0 );
+
+/* Attempt to get a value for the specified attribute. */
+   (void) Get( this, attrib, status );
+
+/* An error will have been reported if the object does not have the
+   requested attribute. Set the result and clear the error status. */
+   if( !astOK ) {
+      result = 0;
+      astClearStatus;
+   } else {
+      result = 1;
+   }
+
+/* Re-instate the original error reporting flag. */
+   (void) astReporting( oldrep );
+
+/* Return the result. */
+   return result;
+}
+
+static unsigned long Magic( const AstObject *this, size_t size, int *status ) {
+/*
+*  Name:
+*     Magic
+
+*  Purpose:
+*     Generate a "magic number" for an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     unsigned long Magic( const AstObject *this, size_t size, int *status )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function generates a "magic number" which is a function of an Object
+*     pointer (address) and an Object size. This number may be stored in an
+*     Object to allow it to be recognised as a valid Object by other routines
+*     and to provide security against argument passing errors, etc.
+
+*  Parameters:
+*     this
+*        Pointer to an Object.
+*     size
+*        The Object size.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The magic number.
+
+*  Notes:
+*     -  This function does not perform any error checking.
+*/
+
+/* Form the bit-wise exclusive OR between the Object address and the Object
+   size, then add 2 and invert the bits. Return the result as an unsigned
+   long integer. */
+   return ~( ( ( (unsigned long) this ) ^ ( (unsigned long) size ) ) +
+             ( (unsigned long) 2 ) );
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*+
+*  Name:
+*     astManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int astManageLock( AstObject *this, int mode, int extra,
+*                        AstObject **fail )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread.
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+
+*  Returned Value:
+*     A status value:
+*        0 - Success.
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+*        5 - Check failed - object is locked by a different thread
+*        6 - Check failed - object is unlocked
+*
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   int result;                   /* Returned value */
+
+/* Initialise */
+   result = 0;
+   if( fail ) *fail = NULL;
+
+/* Check the supplied point is not NULL. */
+   if( ! this ) return result;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Get a lock on the object's secondary mutex. This gives us exclusive
+   access to the "locker" (and "ref_count") component in the AstObject
+   structure. All other components in the structure are guarded by the
+   primary mutex (this->mutex1). */
+   if( LOCK_SMUTEX(this) ) {
+      result = 2;
+
+/* If the secondary mutex was locked succesfully, first deal with cases
+   where the caller wants to lock the Object for exclusive use by the
+   calling thread. */
+   } else if( mode == AST__LOCK ) {
+
+/* If the Object is not currently locked, lock the Object primary mutex
+   and record the identity of the calling thread in the Object. */
+      if( this->locker == -1 ) {
+         if( LOCK_PMUTEX(this) ) result = 2;
+         this->locker = AST__THREAD_ID;
+         this->globals = AST__GLOBALS;
+         ChangeThreadVtab( this, status );
+
+/* If the Object is already locked by the calling thread, do nothing. */
+      } else if( this->locker == AST__THREAD_ID ) {
+
+/* If the object is locked by a different thread, and the caller is
+   willing to wait, attempt to lock the Object primary mutex. This will
+   cause the calling thread to block until the Object is release by the
+   thread that currently has it locked. Then store the identity of the
+   calling thread (the new lock owner). We first need to release the
+   secondary mutex so that the other thread can modify the "locker"
+   component in the AstObject structure when it releases the Object
+   (using this function). We then re-lock the secondary mutex so this
+   thread can change the "locker" component safely. */
+      } else if( extra ) {
+         if( UNLOCK_SMUTEX(this) ) {
+            result = 3;
+         } else if( LOCK_PMUTEX(this) ) {
+            result = 2;
+         } else if( LOCK_SMUTEX(this) ) {
+            result = 2;
+         }
+         this->locker = AST__THREAD_ID;
+         this->globals = AST__GLOBALS;
+         ChangeThreadVtab( this, status );
+
+/* If the caller does not want to wait until the Object is available,
+   return a status of 1. */
+      } else {
+         result = 1;
+      }
+
+/* Unlock the Object for use by other threads. */
+   } else if( mode == AST__UNLOCK ) {
+
+/* Do nothing if the Object is currently unlocked. */
+      if( this->locker == -1 ) {
+
+/* If the object is currently locked by the calling thread, clear the
+   identity of the thread that owns the lock and unlock the primary
+   mutex. */
+      } else if( this->locker == AST__THREAD_ID ) {
+         this->locker = -1;
+         this->globals = NULL;
+         if( UNLOCK_PMUTEX(this) ) result = 3;
+
+/* Return an error status value if the Object is locked by another
+   thread. */
+      } else {
+         result = 1;
+      }
+
+/* Check the Object is locked by the calling thread. Return a status of 1 if
+   not. */
+   } else if( mode == AST__CHECKLOCK ) {
+      if( this->locker == -1 ) {
+         result = 6;
+      } else if( this->locker != AST__THREAD_ID ) {
+         result = 5;
+      }
+
+/* Return a status of 4 for any other modes. */
+   } else {
+      result = 4;
+   }
+
+/* Unlock the secondary mutex so that other threads can access the "locker"
+   component in the Object to see if it is locked. */
+   if( UNLOCK_SMUTEX(this) ) result = 3;
+
+/* If the operation failed, return a pointer to the failed object. */
+   if( result && fail ) *fail = this;
+
+/* Return the status value */
+   return result;
+}
+#endif
+
+char *astToString_( AstObject *this, int *status ) {
+/*
+c++
+*  Name:
+*     astToString
+
+*  Purpose:
+*     Create an in-memory serialisation of an Object
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "object.h"
+*     char *astToString( AstObject *this )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns a string holding a minimal textual
+*     serialisation of the supplied AST Object. The Object can re
+*     re-created from the serialisation using astFromString.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be serialised.
+
+*  Returned Value:
+*     astToString()
+*        Pointer to dynamically allocated memory holding the
+*        serialisation, or NULL if an error occurs. The pointer
+*        should be freed when no longer needed using astFree.
+
+c--
+*/
+
+/* Local Variables: */
+   StringData data;              /* Data passed to the sink function */
+   AstChannel *channel;          /* Pointer to output Channel */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Create a Channel which will write to an expanding dynamically
+   allocated memory buffer. Set Channel attributes to exclude all
+   non-essential characters. */
+   channel = astChannel( NULL, ToStringSink, "Comment=0,Full=-1,Indent=0",
+                         status );
+
+/* Initialise the data structure used to communicate with the sink
+   function, and store a pointer to it in the Channel. */
+   data.ptr = NULL;
+   data.buff = NULL;
+   data.len = 0;
+   astPutChannelData( channel, &data );
+
+/* Write the Object to the Channel. */
+   astWrite( channel, this );
+
+/* Annul the Channel pointer. */
+   channel = astAnnul( channel );
+
+/* Free the returned string if an error has occurred. */
+   if( !astOK ) data.ptr = astFree( data.ptr );
+
+/* Return the pointer. */
+   return data.ptr;
+}
+
+static void ToStringSink( const char *text ){
+/*
+*  Name:
+*     ToStringSink
+
+*  Purpose:
+*     A Channel sink function for use by the astToString method.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*      ToStringSink( const char *text )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function appends the supplied line of text to the end of a
+*     dynamically growing memory block.
+
+*  Parameters:
+*     text
+*        Pointer to the null-terminated line of text to be stored.
+
+*/
+
+/* Local Variables: */
+   StringData *data;     /* Data passed to the sink function */
+   int *status;          /* Pointer to local status value */
+   int status_value;     /* Local status value */
+
+/* Set up the local status */
+   status_value = 0;
+   status = &status_value;
+
+/* Get a pointer to the structure holding the current memory pointer and
+   the length of the currently allocated memory. */
+   data = astChannelData;
+
+/* Append the supplied text to the end of the string, and update the
+   string length. */
+   data->ptr = astAppendString( data->ptr, &(data->len), text );
+
+/* Append a newline character to the end of the string, and update the
+   string length. */
+   data->ptr = astAppendString( data->ptr, &(data->len), "\n" );
+}
+
+void astSet_( void *this_void, const char *settings, int *status, ... ) {
+/*
+*++
+*  Name:
+c     astSet
+f     AST_SET
+
+*  Purpose:
+*     Set attribute values for an Object.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     void astSet( AstObject *this, const char *settings, ... )
+f     CALL AST_SET( THIS, SETTINGS, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function assigns a set of attribute values to an Object,
+f     This routine assigns a set of attribute values to an Object,
+*     over-riding any previous values. The attributes and their new
+*     values are specified via a character string, which should
+*     contain a comma-separated list of the form:
+*
+*        "attribute_1 = value_1, attribute_2 = value_2, ... "
+*
+*     where "attribute_n" specifies an attribute name, and the value
+*     to the right of each "=" sign should be a suitable textual
+*     representation of the value to be assigned. This value will be
+*     interpreted according to the attribute's data type.
+c
+c     The string supplied may also contain "printf"-style format
+c     specifiers, identified by "%" signs in the usual way. If
+c     present, these will be substituted by values supplied as
+c     additional optional arguments (using the normal "printf" rules)
+c     before the string is used.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Object.
+c     settings
+f     SETTINGS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string containing a
+c        comma-separated list of attribute settings in the form described
+c        above.
+f        A character string containing a comma-separated list of
+f        attribute settings in the form described above.
+c     ...
+c        Optional additional arguments which supply values to be
+c        substituted for any "printf"-style format specifiers that
+c        appear in the "settings" string.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Examples:
+c     astSet( map, "Report = 1, Zoom = 25.0" );
+c        Sets the Report attribute for Object "map" to the value 1 and
+c        the Zoom attribute to 25.0.
+c     astSet( frame, "Label( %d ) =Offset along axis %d", axis, axis );
+c        Sets the Label(axis) attribute for Object "frame" to a
+c        suitable string, where the axis number is obtained from
+c        "axis", a variable of type int.
+c     astSet( frame, "Title =%s", mystring );
+c        Sets the Title attribute for Object "frame" to the contents of
+c        the string "mystring".
+f     CALL AST_SET( MAP, 'Report = 1, Zoom = 25.0', STATUS )
+f        Sets the Report attribute for Object MAP to the value 1 and
+f        the Zoom attribute to 25.0.
+f     CALL AST_SET( FRAME, 'Label( 1 ) =Offset from cluster axis', STATUS )
+f        Sets the Label(1) attribute for Object FRAME to a suitable
+f        string.
+
+*  Notes:
+*     - Attribute names are not case sensitive and may be surrounded
+*     by white space.
+*     - White space may also surround attribute values, where it will
+*     generally be ignored (except for string-valued attributes where
+*     it is significant and forms part of the value to be assigned).
+*     - To include a literal comma in the value assigned to an attribute,
+*     the whole attribute value should be enclosed in quotation markes.
+c     Alternatively, you can use "%s" format and supply the value as a
+c     separate additional argument to astSet (or use the astSetC
+c     function instead).
+c     - The same procedure may be adopted if "%" signs are to be included
+c     and are not to be interpreted as format specifiers (alternatively,
+c     the "printf" convention of writing "%%" may be used).
+*     - An error will result if an attempt is made to set a value for
+*     a read-only attribute.
+*--
+
+*  Implementation Notes:
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     Object identifier is of type (void *) and is converted and
+*     validated within the function itself.
+*     - This implementation of astSet is designed to be used within AST,
+*     and has an explicit status parameter. From outside AST, the astSet
+*     macro will invoke the astSetId_ function which does not have an
+*     status parameter.
+
+*--
+*/
+
+/* Local Variables: */
+   AstObject *this;              /* Pointer to the Object structure */
+   va_list args;                 /* Variable argument list */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain and validate a pointer to the Object structure. */
+   this = astCheckObject( this_void );
+   if ( astOK ) {
+
+/* Obtain the variable argument list and pass all arguments to the
+   astVSet method for interpretation. */
+      va_start( args, status );
+      astVSet( this, settings, NULL, args );
+      va_end( args );
+   }
+}
+
+static void SetAttrib( AstObject *this, const char *setting, int *status ) {
+/*
+*+
+*  Name:
+*     astSetAttrib
+
+*  Purpose:
+*     Set an attribute value for an Object.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astSetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function assigns an attribute value for an Object, the attribute and
+*     its value being specified by means of a string of the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in lower
+*     case with no white space present. The value to the right of the "="
+*     should be a suitable textual representation of the value to be assigned
+*     and this will be interpreted according to the attribute's data type.
+*     White space surrounding the value is only significant for string
+*     attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+
+*  Notes:
+*     - The Object class does not have any writable attributes, so
+*     this function merely reports an error. It is intended to be
+*     extended by other class definitions.
+*-
+*/
+
+/* Local Variables: */
+   int id;                       /* Offset of ID string */
+   int ival;                     /* Integer attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* ID. */
+/* --- */
+   if ( nc = 0, ( 0 == astSscanf( setting, "id=%n%*[^\n]%n", &id, &nc ) )
+                && ( nc >= len ) ) {
+      astSetID( this, setting + id );
+
+/* Ident. */
+/* ------ */
+   } else if ( nc = 0, ( 0 == astSscanf( setting, "ident=%n%*[^\n]%n", &id, &nc ) )
+                && ( nc >= len ) ) {
+      astSetIdent( this, setting + id );
+
+/* UseDefs */
+/* ------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "usedefs= %d %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetUseDefs( this, ival );
+
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class and use this to report an error
+   if it does. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+   } else if ( MATCH( "class" ) ||
+               MATCH( "nobject" ) ||
+               MATCH( "objsize" ) ||
+               MATCH( "refcount" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Since no writable attributes are defined for the Object class, any
+   attempt to set a value for anything else is also an error. */
+   } else {
+      astError( AST__BADAT, "astSet: The attribute setting \"%s\" is invalid "
+               "for a %s.", status, setting, astGetClass( this ) );
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+void astSetCopy_( AstObjectVtab *vtab,
+                  void (* copy)( const AstObject *, AstObject *, int * ), int *status ) {
+/*
+*+
+*  Name:
+*     astSetCopy
+
+*  Purpose:
+*     Declare a copy constructor for an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astSetCopy( AstObjectVtab *vtab,
+*                      void (* copy)( const AstObject *, AstObject * ) )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function is provided so that class definitions can declare a copy
+*     constructor to be associated with an Object that is being constructed.
+*     When a copy is later performed on the Object, the copy constructor of
+*     each class to which the Object belongs will be invoked in turn (working
+*     down the class hierarchy). The copy constructor is passed pointers to the
+*     source and destination Objects. It should implement the copy and return
+*     void.
+
+*  Parameters:
+*     vtab
+*        Pointer to the Object's virtual function table, in which the copy
+*        constructor's pointer is to be stored for future use.
+*     copy
+*        Pointer to the copy constructor function.
+
+*  Notes:
+*     -  When an Object is copied, a byte-by-byte copy of its structure is
+*     automatically made before any copy constructors are invoked. A copy
+*     constructor need only be provided if this does not suffice (e.g. if the
+*     structure contains pointers to other data).
+*     - If a copy constructor is declared for a class, then a
+*     destructor for that class must also be declared (using
+*     astSetDelete) so that there is a one-to-one correspondence
+*     between copy constructors and their associated destructors.
+*     -  Copy constructors should check the global error status in the normal
+*     way and should set it (and report an error) if they fail.
+*-
+*/
+
+
+/* Check the global status. */
+   if ( !astOK ) return;
+
+/* Indicate that subsequent memory allocations may never be freed (other
+   than by any AST exit handler). */
+   astBeginPM;
+
+/* Expand the array of copy constructor pointers in the virtual function table
+   (if necessary) to accommodate the new one. */
+   vtab->copy = astGrow( vtab->copy, vtab->ncopy + 1,
+                        sizeof( void (*)( const AstObject *, AstObject * ) ) );
+
+/* If OK, store the new function pointer and increment the count of copy
+   constructors. */
+   if ( astOK ) {
+      vtab->copy[ vtab->ncopy++ ] = copy;
+   }
+
+/* Mark the end of the section in which memory allocations may never be freed
+   (other than by any AST exit handler). */
+   astEndPM;
+
+}
+
+void astSetDelete_( AstObjectVtab *vtab, void (* delete)( AstObject *, int * ), int *status ) {
+/*
+*+
+*  Name:
+*     astSetDelete
+
+*  Purpose:
+*     Declare a destructor for an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astSetDelete( AstObjectVtab *vtab, void (* delete)( AstObject * ) )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function is provided so that class definitions can declare a
+*     destructor to be associated with an Object. When the Object is later
+*     deleted, the destructor declared by each class to which the Object
+*     belongs will be invoked in turn (working up the class hierarchy). The
+*     destructor is passed a pointer to the Object. It should free any
+*     resources (e.g. memory) associated with it and return void. It should
+*     not free the memory containing the Object itself.
+
+*  Parameters:
+*     vtab
+*        Pointer to the Object's virtual function table, in which the
+*        destructor's pointer is to be stored for future use.
+*     delete
+*        Pointer to the destructor function.
+
+*  Notes:
+*     - A destructor need not be declared for a class if there are no
+*     resources to free.
+*     - If a destructor is declared for a class, then a copy
+*     constructor for that class must also be declared (using
+*     astSetCopy) so that there is a one-to-one correspondence between
+*     copy constructors and their associated destructors.
+*     - A destructor function should generally attempt to execute even
+*     if the global error status is set on entry, but should not
+*     report further errors in that case (errors should be reported
+*     normally if status is not set on entry).
+*-
+*/
+
+
+/* Check the global status. */
+   if ( !astOK ) return;
+
+/* Indicate that subsequent memory allocations may never be freed (other
+   than by any AST exit handler). */
+   astBeginPM;
+
+/* Expand the array of destructor pointers in the virtual function table (if
+   necessary) to accommodate the new one. */
+   vtab->delete = astGrow( vtab->delete, vtab->ndelete + 1,
+                           sizeof( void (*)( AstObject * ) ) );
+
+/* If OK, store the new function pointer and increment the count of
+   destructors. */
+   if ( astOK ) {
+      vtab->delete[ vtab->ndelete++ ] = delete;
+   }
+
+/* Mark the end of the section in which memory allocations may never be freed
+   (other than by any AST exit handler). */
+   astEndPM;
+
+}
+
+void astSetDump_( AstObjectVtab *vtab,
+                  void (* dump)( AstObject *, AstChannel *, int * ),
+                  const char *class, const char *comment, int *status ) {
+/*
+*+
+*  Name:
+*     astSetDump
+
+*  Purpose:
+*     Declare a dump function for an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astSetDump( AstObjectVtab *vtab,
+*                      void (* dump)( AstObject *, AstChannel * ),
+*                      const char *class, const char *comment )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function is provided so that class definitions can declare
+*     a dump function to be associated with an Object that is being
+*     constructed.  When the astWrite (or astShow or astToString) method
+*     is later used to write the Object to a Channel, the dump function
+*     of each class to which the Object belongs will be invoked in turn
+*     (working down the class hierarchy). The dump function is passed
+*     pointers to the Object and the output Channel. It should write
+*     out any internal values (e.g. instance variables) for its class
+*     that are to be kept (using the protected astWrite... methods of
+*     the Channel) and return void.
+
+*  Parameters:
+*     vtab
+*        Pointer to the Object's virtual function table, in which the
+*        dump function's pointer is to be stored for future use.
+*     dump
+*        Pointer to the dump function.
+*     class
+*        Pointer to a constant null-terminated string (residing in
+*        static memory) containing the name of the class that is
+*        declaring the dump function.
+*     comment
+*        Pointer to a constant null-terminated string (residing in
+*        static memory) containing a comment to associate with the
+*        dump function.  This should normally describe the purpose of
+*        the class that is declaring the dump function.
+
+*  Notes:
+*     - Dump functions should check the global error status in the
+*     normal way and should set it (and report an error) if they fail.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Indicate that subsequent memory allocations may never be freed (other
+   than by any AST exit handler). */
+   astBeginPM;
+
+/* Expand the arrays of pointers to dump functions and related data in
+   the virtual function table (if necessary) to accommodate the new
+   one. */
+   vtab->dump = astGrow( vtab->dump, vtab->ndump + 1,
+                         sizeof( void (*)( AstObject *, AstChannel * ) ) );
+   vtab->dump_class = astGrow( vtab->dump_class, vtab->ndump + 1,
+                               sizeof( char * ) );
+   vtab->dump_comment = astGrow( vtab->dump_comment, vtab->ndump + 1,
+                                 sizeof( char * ) );
+
+/* If OK, store the new pointers (to the dump function, class name and
+   comment) and increment the count of dump functions. */
+   if ( astOK ) {
+      vtab->dump[ vtab->ndump ] = dump;
+      vtab->dump_class[ vtab->ndump ] = class;
+      vtab->dump_comment[ vtab->ndump ] = comment;
+      vtab->ndump++;
+   }
+
+/* Mark the end of the section in which memory allocations may never be
+   freed (other than by any AST exit handler). */
+   astEndPM;
+}
+
+void astSetProxy_( AstObject *this, void *proxy, int *status ) {
+/*
+*+
+*  Name:
+*     astSetProxy
+
+*  Purpose:
+*     Store a pointer to the foreign language proxy used to represent a
+*     given AST Object.
+
+*  Type:
+*     Undocumented public function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astSetProxy( AstObject *this, void *proxy )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function stores the supplied pointer in the AST Object so that
+*     it can be retrieved later using astGetProxy.
+*
+*     The supplied pointer should point to a structure that is used
+*     to represent the AST Object within some external system. It is
+*     expected that the external system will check each object reference
+*     returned by AST to see if it has an associated proxy object. If not
+*     (i.e. if astGetProxy returns NULL), a new external object will be
+*     created to represent the AST Object, and a pointer to it will be
+*     stored in the AST Object using astSetProxy. If the AST Object
+*     already has a proxy, the AST reference is annulled and the existing
+*     proxy object is used by the external system.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     proxy
+*        Pointer to the proxy object, or NULL.
+
+*  Notes:
+*     - The suppied pointer is not used within AST itself, other than to
+*     be returned by the astGetProxy method.
+*     - This function is public, but is currently undocumented since it
+*     is only of interest to people writing AST interfaces for other
+*     languages.
+*-
+*/
+   if( !astOK ) return;
+   this->proxy = proxy;
+}
+
+void astSetVtab_( AstObject *this, AstObjectVtab *vtab, int *status ) {
+/*
+*+
+*  Name:
+*     astSetVtab
+
+*  Purpose:
+*     Change the virtual function table associated with an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astSetVtab( AstObject *this, AstObjectVtab *vtab )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function changes the virtual function table associated with an
+*     Object. This may be needed, for instance, if a super-class
+*     initialises a parent class structure with a NULL vtab, causing the
+*     vtab of the parent class to be used instead of the super-class.
+*     Whilst the super-class object is being constructed its inherited methods
+*     will be determined by the parent class. Once the super-class object
+*     has been constructed, it can invoke this fuction in order to
+*     set the vtab to the super-class vtab, thus causing the method
+*     implementations provided by the super-cvlass to be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be modified.
+*     vtab
+*        Pointer to the virtual function table to store in the Object.
+*-
+*/
+   if( this ) this->vtab = vtab;
+}
+
+static int Same( AstObject *this, AstObject *that, int *status ) {
+/*
+*++
+*  Name:
+c     astSame
+f     AST_SAME
+
+*  Purpose:
+*     Test if two AST pointers refer to the same Object.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     int astSame( AstObject *this,  AstObject *that )
+f     RESULT = AST_SAME( THIS, THAT, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function returns a boolean result (0 or 1) to indicate
+f     This function returns a logical result to indicate
+*     whether two pointers refer to the same Object.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the first Object.
+c     that
+f     THAT = INTEGER (Given)
+*        Pointer to the second Object.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSame()
+c        One if the two pointers refer to the same Object, otherwise zero.
+f     AST_SAME = LOGICAL
+f        .TRUE. if the two pointers refer to the same Object, otherwise
+f        .FALSE.
+
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Notes:
+*     - Two independent Objects that happen to be identical are not
+*     considered to be the same Object by this function.
+c     - A value of zero will be returned if this function is invoked
+c     with the AST error status set, or if it should fail for any reason.
+f     - A value of .FALSE. will be returned if this function is invoked
+f     with STATUS set to an error value, or if it should fail for any reason.
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Return the result. */
+   return ( this == that ) ? 1 : 0;
+}
+
+/*
+*++
+*  Name:
+c     astSet<X>
+f     AST_SET<X>
+
+*  Purpose:
+*     Set an attribute value for an Object.
+
+*  Type:
+*     Public functions.
+
+*  Synopsis:
+c     #include "object.h"
+c     void astSet<X>( AstObject *this, const char *attrib, <X>type value )
+f     CALL AST_SET<X>( THIS, ATTRIB, VALUE, STATUS )
+
+*  Class Membership:
+*     Object methods.
+
+*  Description:
+c     This is a family of functions which set a specified attribute
+f     This is a family of routines which set a specified attribute
+*     value for an Object using one of several different data
+c     types. The type is selected by replacing <X> in the function name
+f     types. The type is selected by replacing <X> in the routine name
+c     by C, D, F, I or L, to supply a value in const char* (i.e. string),
+c     double, float, int, or long format, respectively.
+f     by C, D, I, L or R, to supply a value in Character, Double
+f     precision, Integer, Logical or Real format, respectively.
+*
+*     If possible, the value you supply is converted to the type of
+*     the attribute. If conversion is not possible, an error will
+*     result.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Object.
+c     attrib
+f     ATTRIB = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string containing the
+c        name of the attribute whose value is to be set.
+f        A character string containing the name of the attribute whose
+f        value is to be set.
+c     value
+f     VALUE = <X>type (Given)
+c        The value to be set for the attribute, in the data type corresponding
+c        to <X> (or, in the case of astSetC, a pointer to a null-terminated
+c        character string containing this value).
+f        The value to be set for the attribute, in the data type corresponding
+f        to <X>.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Object
+c        These functions apply to all Objects.
+f        These routines apply to all Objects.
+
+*  Examples:
+c     astSetI( frame, "Preserve", 1 );
+c        Sets the Preserve attribute value for Object "frame" to 1.
+c     astSetC( plot, "Format(1)", "%.2g" );
+c        Sets the Format(1) attribute value for Object "plot" to the
+c        character string "%.2g".
+f     CALL AST_SETC( PLOT, 'Title', CVALUE, STATUS )
+f        Sets the Title attribute value for Object PLOT to the contents
+f        of the character variable CVALUE.
+f     CALL AST_SETL( FRAME, 'Preserve', .TRUE., STATUS );
+f        Sets the Preserve attribute value for Object FRAME to 1 (true).
+
+*  Notes:
+*     - Attribute names are not case sensitive and may be surrounded
+*     by white space.
+f     - The logical value .FALSE. will translate to a numerical attribute
+f     value of zero and logical .TRUE. will translate to one.
+*     - An error will result if an attempt is made to set a value for
+*     a read-only attribute.
+*--
+*/
+
+/* Define a macro that expands to implement the astSetX_ member
+   functions required. The arguments to this macro are:
+
+      code
+         The character that appears at the end of the function name.
+      type
+         The C type of the function "value" parameter.
+      format
+         A quoted string containing a sprintf format specifier that will
+         format the supplied value as a character string. This format should
+         consume 2 sprintf arguments: a field width and the value to be
+         formatted.
+      fmtlen
+         The number of characters in the format specifier (above).
+      fieldsz
+         The value of the field width to be used by the format specifier.
+*/
+#define MAKE_SETX(code,type,format,fmtlen,fieldsz) \
+void astSet##code##_( AstObject *this, const char *attrib, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   char *setting;                /* Pointer to attribute setting string */ \
+   int len;                      /* Length of attribute name */ \
+\
+/* Check the global status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain the length of the attribute name and allocate memory to hold \
+   this name plus the format specifier to be appended to it. */ \
+   len = (int) astChrLen( attrib ); \
+   setting = astMalloc( (size_t) ( len + fmtlen + 2 ) ); \
+\
+/* Make a copy of the attribute name in the allocated memory. */ \
+   if ( astOK ) { \
+      (void) memcpy( setting, attrib, (size_t) len ); \
+      setting[ len ] = 0; \
+\
+/* Append "=", followed by the format specifier, to construct a \
+   suitable "setting" string for use by astSet. */ \
+      (void) strcat( setting, "=" format ); \
+\
+/* Invoke astSet to set the attribute value. */ \
+      astSet( this, setting, status, fieldsz, value ); \
+   } \
+\
+/* Free the allocated memory. */ \
+   setting = astFree( setting ); \
+}
+
+/* Use this macro to create all the SetX_ private member functions. */
+MAKE_SETX(D,double,"%.*g",4,DBL_DIG)
+MAKE_SETX(F,float,"%.*g",4,FLT_DIG)
+MAKE_SETX(I,int,"%.*d",4,1)
+MAKE_SETX(L,long,"%.*ld",5,1)
+
+
+/* The astSetC_ function is implemented separately so that commas can be
+   handled. Since astSetC can only be used to set a single attribute
+   value, we know that any commas in the supplied value are included
+   within the attribuite value, rather than being used as delimiters
+   between adjacent attribute settings. To avoid VSet using them as
+   delimiters, they are replaced here by '\r' before calling astSet, and
+   VSet then converts them back to commas. */
+
+void astSetC_( AstObject *this, const char *attrib, const char *value, int *status ) {
+
+/* Local Variables: */
+   char *d;                      /* Pointer to next setting character */
+   char *newv;                   /* Pointer to new attribute value string */
+   char *setting;                /* Pointer to attribute setting string */
+   const char *c;                /* Pointer to next value character */
+   int len;                      /* Length of attribute name */
+
+/* Check the global status. */
+   if ( !astOK ) return;
+
+/* Produce a copy of the supplied attribute value in which any commas
+   are replaced by carriage returns ("\r"). */
+   newv = astMalloc( (size_t)( strlen( value ) + 1 ) );
+   if( newv ) {
+      d = newv;
+      c = value;
+      while( *c ) {
+         if( *c == ',' ) {
+            *d = '\r';
+         } else {
+            *d = *c;
+         }
+         c++;
+         d++;
+      }
+      *d = 0;
+
+/* Obtain the length of the attribute name and allocate memory to hold
+   this name plus the format specifier to be appended to it. */
+      len = (int) astChrLen( attrib );
+      setting = astMalloc( (size_t) ( len + 5 ) );
+
+/* Make a copy of the attribute name in the allocated memory. */
+      if ( astOK ) {
+         (void) memcpy( setting, attrib, (size_t) len );
+         setting[ len ] = 0;
+
+/* Append "=", followed by the format specifier, to construct a
+   suitable "setting" string for use by astSet. */
+         (void) strcat( setting, "=%*s" );
+
+/* Invoke astSet to set the attribute value. */
+         astSet( this, setting, status, 0, newv );
+      }
+
+/* Free the allocated memory. */
+      setting = astFree( setting );
+   }
+   newv = astFree( newv );
+}
+
+static void Show( AstObject *this, int *status ) {
+/*
+*++
+*  Name:
+c     astShow
+f     AST_SHOW
+
+*  Purpose:
+*     Display a textual representation of an Object on standard output.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     void astShow( AstObject *this )
+f     CALL AST_SHOW( THIS, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function displays a textual description of any AST Object
+f     This routine displays a textual description of any AST Object
+*     on standard output. It is provided primarily as an aid to
+*     debugging.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Object to be displayed.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+*--
+*/
+
+/* Local Variables: */
+   AstChannel *channel;          /* Pointer to output Channel */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Create a Channel which will write to standard output. */
+   channel = astChannel( NULL, NULL, "", status );
+
+/* Write the Object to the Channel. */
+   astWrite( channel, this );
+
+/* Annul the Channel pointer. */
+   channel = astAnnul( channel );
+}
+
+int astTest_( AstObject *this, const char *attrib, int *status ) {
+/*
+*++
+*  Name:
+c     astTest
+f     AST_TEST
+
+*  Purpose:
+*     Test if an Object attribute value is set.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     int astTest( AstObject *this, const char *attrib )
+f     RESULT = AST_TEST( THIS, ATTRIB, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function returns a boolean result (0 or 1) to indicate
+f     This function returns a logical result to indicate
+*     whether a value has been explicitly set for one of an Object's
+*     attributes.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Object.
+c     attrib
+f     ATTRIB = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string containing
+c        the name of the attribute to be tested.
+f        A character string containing the name of the attribute to be
+f        tested.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astTest()
+c        One if a value has previously been explicitly set for the attribute
+c        (and hasn't been cleared), otherwise zero.
+f     AST_TEST = LOGICAL
+f        .TRUE. if a value has previously been explicitly set for the
+f        attribute (and hasn't been cleared), otherwise .FALSE..
+
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Notes:
+*     - Attribute names are not case sensitive and may be surrounded
+*     by white space.
+c     - A value of zero will be returned if this function is invoked
+f     - A value of .FALSE. will be returned if this function is invoked
+c     with the AST error status set, or if it should fail for any reason.
+f     with STATUS set to an error value, or if it should fail for any reason.
+c     - A value of zero will also be returned if this function is used
+f     - A value of .FALSE. will also be returned if this function is used
+*     to test a read-only attribute, although no error will result.
+*--
+*/
+
+/* Local Variables: */
+   char *buff;                   /* Pointer to character buffer */
+   int i;                        /* Loop counter for characters */
+   int j;                        /* Non-blank character count */
+   int len;                      /* Length of attrib string */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain the length of the attrib string. */
+   len = (int) strlen( attrib );
+
+/* Allocate memory and store a copy of the string. */
+   buff = astStore( NULL, attrib, (size_t) ( len + 1 ) );
+   if ( astOK ) {
+
+/* Remove white space and upper case characters. */
+      for ( i = j = 0; buff[ i ]; i++ ) {
+         if ( !isspace( buff[ i ] ) ) buff[ j++ ] = tolower( buff[ i ] );
+      }
+
+/* Terminate the attribute name and pass it to astTestAttrib to test
+   the attribute. */
+      buff[ j ] = '\0';
+      result = astTestAttrib( this, buff );
+   }
+
+/* Free the memory allocated for the string buffer. */
+   buff = astFree( buff );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int TestAttrib( AstObject *this, const char *attrib, int *status ) {
+/*
+*+
+*  Name:
+*     astTestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for an Object.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int astTestAttrib( AstObject *this, const char *attrib )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of an Object's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* ID. */
+/* --- */
+   if ( !strcmp( attrib, "id" ) ) {
+      result = astTestID( this );
+
+/* Ident. */
+/* ------ */
+   } else if ( !strcmp( attrib, "ident" ) ) {
+      result = astTestIdent( this );
+
+/* UseDefs */
+/* ------- */
+   } else if ( !strcmp( attrib, "usedefs" ) ) {
+      result = astTestUseDefs( this );
+
+/* Test if the attribute string matches any of the read-only
+   attributes of this class. If it does, then return zero. */
+   } else if ( !strcmp( attrib, "class" ) ||
+               !strcmp( attrib, "nobject" ) ||
+               !strcmp( attrib, "objsize" ) ||
+               !strcmp( attrib, "refcount" ) ) {
+      result = 0;
+
+/* Any attempt to test any other attribute is an error. */
+   } else if( astOK ){
+      astError( AST__BADAT, "astTest: The attribute name \"%s\" is invalid "
+               "for a %s.", status, attrib, astGetClass( this ) );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+int astTune_( const char *name, int value, int *status ) {
+/*
+*++
+*  Name:
+c     astTune
+f     AST_TUNE
+
+*  Purpose:
+*     Set or get an integer-valued AST global tuning parameter.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     int astTune( const char *name, int value )
+f     RESULT = AST_TUNE( NAME, VALUE, STATUS )
+
+*  Class Membership:
+*     Object function.
+
+*  Description:
+*     This function returns the current value of an integer-valued AST
+*     global tuning parameter, optionally storing a new value for the
+*     parameter. For character-valued tuning parameters, see
+c     astTuneC.
+f     AST_TUNEC.
+
+*  Parameters:
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+*        The name of the tuning parameter (case-insensitive).
+c     value
+f     VALUE = INTEGER (Given)
+*        The new value for the tuning parameter. If this is AST__TUNULL,
+*        the existing current value will be retained.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astTune()
+f     AST_TUNE = INTEGER
+c        The original value of the tuning parameter. A default value will
+*        be returned if no value has been set for the parameter.
+
+*  Tuning Parameters:
+*     ObjectCaching
+*        A boolean flag which indicates what should happen
+*        to the memory occupied by an AST Object when the Object is deleted
+*        (i.e. when its reference count falls to zero or it is deleted using
+c        astDelete).
+f        AST_DELETE).
+*        If this is zero, the memory is simply freed using the systems "free"
+*        function. If it is non-zero, the memory is not freed. Instead a
+*        pointer to it is stored in a pool of such pointers, all of which
+*        refer to allocated but currently unused blocks of memory. This allows
+*        AST to speed up subsequent Object creation by re-using previously
+*        allocated memory blocks rather than allocating new memory using the
+*        systems malloc function. The default value for this parameter is
+*        zero. Setting it to a non-zero value will result in Object memory
+*        being cached in future. Setting it back to zero causes any memory
+*        blocks currently in the pool to be freed. Note, this tuning parameter
+*        only controls the caching of memory used to store AST Objects. To
+*        cache other memory blocks allocated by AST, use MemoryCaching.
+*     MemoryCaching
+*        A boolean flag similar to ObjectCaching except
+*        that it controls caching of all memory blocks of less than 300 bytes
+*        allocated by AST (whether for internal or external use), not just
+*        memory used to store AST Objects.
+
+*  Notes:
+c     - This function attempts to execute even if the AST error
+c     status is set
+f     - This routine attempts to execute even if STATUS is set to an
+f     error value
+*     on entry, although no further error report will be
+*     made if it subsequently fails under these circumstances.
+*     - All threads in a process share the same AST tuning parameters
+*     values.
+*--
+*/
+
+   int result = AST__TUNULL;
+
+   if( name ) {
+
+      LOCK_MUTEX1;
+
+      if( astChrMatch( name, "ObjectCaching" ) ) {
+         result = object_caching;
+         if( value != AST__TUNULL ) {
+            object_caching = value;
+            if( !object_caching ) EmptyObjectCache( status );
+         }
+
+      } else if( astChrMatch( name, "MemoryCaching" ) ) {
+         result = astMemCaching( value );
+
+      } else if( astOK ) {
+         astError( AST__TUNAM, "astTune: Unknown AST tuning parameter "
+                   "specified \"%s\".", status, name );
+      }
+
+      UNLOCK_MUTEX1;
+
+   }
+
+   return result;
+}
+
+void astTuneC_( const char *name, const char *value, char *buff,
+                int bufflen, int *status ) {
+/*
+*++
+*  Name:
+c     astTuneC
+f     AST_TUNEC
+
+*  Purpose:
+*     Set or get a character-valued AST global tuning parameter.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     void astTuneC( const char *name, const char *value, char *buff,
+c                    int bufflen )
+f     CALL AST_TUNEC( NAME, VALUE, BUFF, STATUS )
+
+*  Class Membership:
+*     Object function.
+
+*  Description:
+*     This function returns the current value of a character-valued
+*     AST global tuning parameter, optionally storing a new value
+*     for the parameter. For integer-valued tuning parameters, see
+c     astTune.
+f     AST_TUNE.
+
+*  Parameters:
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+*        The name of the tuning parameter (case-insensitive).
+c     value
+f     VALUE = CHARACTER * ( ) (Given)
+*        The new value for the tuning parameter. If this is
+f        AST__TUNULLC,
+c        NULL,
+*        the existing current value will be retained.
+c     buff
+f     BUFF = CHARACTER * ( ) (Given)
+*        A character string in which to return the original value of
+*        the tuning parameter. An error will be reported if the buffer
+*        is too small to hold the value.
+c        NULL may be supplied if the old value is not required.
+c     bufflen
+c        The size of the supplied "buff" array. Ignored if "buff" is NULL.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Tuning Parameters:
+*     HRDel
+*        A string to be drawn following the hours field in a formatted
+*        sky axis value when "g" format is in use (see the Format
+*        attribute). This string may include escape sequences to produce
+*        super-scripts, etc. (see the Escapes attribute for details
+*        of the escape sequences allowed). The default value is
+*        "%-%^50+%s70+h%+" which produces a super-script "h".
+*     MNDel
+*        A string to be drawn following the minutes field in a formatted
+*        sky axis value when "g" format is in use. The default value is
+*        "%-%^50+%s70+m%+" which produces a super-script "m".
+*     SCDel
+*        A string to be drawn following the seconds field in a formatted
+*        sky axis value when "g" format is in use. The default value is
+*        "%-%^50+%s70+s%+" which produces a super-script "s".
+*     DGDel
+*        A string to be drawn following the degrees field in a formatted
+*        sky axis value when "g" format is in use. The default value is
+*        "%-%^53+%s60+o%+" which produces a super-script "o".
+*     AMDel
+*        A string to be drawn following the arc-minutes field in a formatted
+*        sky axis value when "g" format is in use. The default value is
+*        "%-%^20+%s85+'%+" which produces a super-script "'" (single quote).
+*     ASDel
+*        A string to be drawn following the arc-seconds field in a formatted
+*        sky axis value when "g" format is in use. The default value is
+*        "%-%^20+%s85+\"%+" which produces a super-script """ (double quote).
+*     EXDel
+*        A string to be drawn to introduce the exponent in a value when "g"
+*        format is in use. The default value is "10%-%^50+%s70+" which
+*        produces "10" followed by the exponent as a super-script.
+
+*  Notes:
+c     - This function attempts to execute even if the AST error
+c     status is set
+f     - This routine attempts to execute even if STATUS is set to an
+f     error value
+*     on entry, although no further error report will be
+*     made if it subsequently fails under these circumstances.
+*     - All threads in a process share the same AST tuning parameters
+*     values.
+*--
+*/
+
+/* Local Variables: */
+   char *p = NULL;
+   int len;
+
+/* Check the name of a tuning parameter was supplied. */
+   if( name ) {
+
+/* Serialise access to the tuning parameters since they are common to all
+   threads. */
+      LOCK_MUTEX1;
+
+/* Get a pointer to the buffer that holds the value of the requested
+   tuning parameter. */
+      if( astChrMatch( name, "hrdel" ) ) {
+         p = hrdel;
+      } else if( astChrMatch( name, "mndel" ) ) {
+         p = mndel;
+      } else if( astChrMatch( name, "scdel" ) ) {
+         p = scdel;
+      } else if( astChrMatch( name, "dgdel" ) ) {
+         p = dgdel;
+      } else if( astChrMatch( name, "amdel" ) ) {
+         p = amdel;
+      } else if( astChrMatch( name, "asdel" ) ) {
+         p = asdel;
+      } else if( astChrMatch( name, "exdel" ) ) {
+         p = exdel;
+
+/* Report an error if an the tuning parameter name is unknown. */
+      } else if( astOK ) {
+         p = NULL;
+         astError( AST__TUNAM, "astTuneC: Unknown AST tuning parameter "
+                   "specified \"%s\".", status, name );
+      }
+
+/* If the existing value was found. */
+      if( p ) {
+
+/* And is to be returned in the supplied buffer... */
+         if( buff ) {
+
+/* Check that the buffer is long enough. If so, copy the current value
+   into the buffer, otherwise report an error. */
+            len = strlen( p ) ;
+            if( len < bufflen ) {
+               strcpy( buff, p );
+            } else {
+               astError( AST__TUNAM, "astTuneC: Supplied string variable "
+                         "is too small - the current '%s' value (%s) has "
+                         "%d characters.", status, name, p, len );
+            }
+         }
+
+/* If a new value is to be stored.... */
+         if( value ) {
+
+/* Report an error if it is too long to fit in the static buffer. */
+            len = strlen( value ) ;
+            if( len >= MAXLEN_TUNEC ) {
+               astError( AST__TUNAM, "astTuneC: Supplied value for '%s' "
+                         "(%s) is too long - must not be longer than %d "
+                         "characters.", status, name, value, MAXLEN_TUNEC );
+
+/* Otherwise, copy the new value into the static buffer. */
+            } else {
+               strcpy( p, value );
+            }
+         }
+      }
+
+      UNLOCK_MUTEX1;
+   }
+}
+
+AstObject *astFromString_( const char *string, int *status ) {
+/*
+c++
+*  Name:
+*     astFromString
+
+*  Purpose:
+*     Re-create an Object from an in-memory serialisation
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astFromString( const char *string )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function returns a pointer to a new Object created from the
+*     supplied text string, which should have been created by astToString.
+
+*  Parameters:
+*     string
+*        Pointer to a text string holding an Object serialisation created
+*        previously by astToString.
+
+*  Returned Value:
+*     astFromString()
+*        Pointer to a new Object created from the supplied serialisation,
+*        or NULL if the serialisation was invalid, or an error occurred.
+
+c--
+*/
+
+/* Local Variables: */
+   StringData data;              /* Data passed to the source function */
+   AstChannel *channel;          /* Pointer to output Channel */
+   AstObject *result;            /* Pointer to returned Object */
+
+/* Check the global error status and supplied serialisation. */
+   if ( !astOK || !string ) return NULL;
+
+/* Create a Channel which will read from the supplied serialisation. */
+   channel = astChannel( FromStringSource, NULL, "", status );
+
+/* Initialise the data structure used to communicate with the source
+   function, and store a pointer to it in the Channel. */
+   data.ptr = (char *) string;
+   data.buff = NULL;
+   data.len = 0;
+   astPutChannelData( channel, &data );
+
+/* Read an Object from the Channel. */
+   result = astRead( channel );
+
+/* Annul the Channel pointer. */
+   channel = astAnnul( channel );
+
+/* Free the line buffer. */
+   data.buff = astFree( data.buff );
+
+/* Annul the returned Object if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the Object pointer. */
+   return result;
+}
+
+static const char *FromStringSource( void ){
+/*
+*  Name:
+*     FromStringSource
+
+*  Purpose:
+*     A Channel source function for use by the astFromString method.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     result = FromStringSource( void )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function reads the next line of text from a serialisation and
+*     returns a pointer to it, or NULL if no lines remain.
+
+*  Returned Value:
+*     Pointer to the null terminated line of text or NULL if no lines
+*     remain.
+*/
+
+/* Local Variables: */
+   StringData *data;     /* Data passed to the sink function */
+   char *nl;             /* Pointer to next newline character */
+   int *status;          /* Pointer to local status value */
+   int nc;               /* Number of characters to read from serialisation */
+   int status_value;     /* Local status value */
+
+/* Set up the local status */
+   status_value = 0;
+   status = &status_value;
+
+/* Get a pointer to the structure holding a pointer to the next line, and
+   to the buffer to return. */
+   data = astChannelData;
+
+/* Return NULL if no text remains to be read. */
+   if( !data->ptr || (data->ptr)[0] == 0 ) return NULL;
+
+/* Find the next newline (if any) in the serialisation. */
+   nl = strchr( data->ptr, '\n' );
+
+/* Get the number of characters to copy. */
+   nc = nl ? nl - data->ptr : strlen( data->ptr );
+
+/* Copy them into the returned buffer, including an extra character for
+   the terminating null. */
+   data->buff = astStore( data->buff, data->ptr, nc + 1 );
+
+/* Store the terminating null. */
+   (data->buff)[ nc ] = 0;
+
+/* Update the pointer to the next character to read from the
+   serialisation. */
+   data->ptr = nl ? nl + 1 : NULL;
+
+/* Return the buffer. */
+   return data->buff;
+}
+
+static void VSet( AstObject *this, const char *settings, char **text,
+                  va_list args, int *status ) {
+/*
+*+
+*  Name:
+*     astVSet
+
+*  Purpose:
+*     Set values for an Object's attributes.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astVSet( AstObject *this, const char *settings, char **text,
+*                   va_list args )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     This function assigns a set of attribute values for an Object,
+*     the attributes and their values being specified by means of a
+*     string containing a comma-separated list of the form:
+*
+*        "attribute1 = value1, attribute2 = value2, ... "
+*
+*     Here, "attribute" specifies an attribute name and the value to
+*     the right of each "=" sign should be a suitable textual
+*     representation of the value to be assigned to that
+*     attribute. This will be interpreted according to the attribute's
+*     data type.
+*
+*     The string supplied may also contain "printf"-style format
+*     specifiers identified by a "%" sign in the usual way. If
+*     present, these will be substituted by values supplied as
+*     optional arguments (as a va_list variable argument list), using
+*     the normal "printf" rules, before the string is used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     settings
+*        Pointer to a null-terminated string containing a
+*        comma-separated list of attribute settings.
+*     text
+*        Pointer to a location at which to return a pointer to dynamic
+*        memory holding a copy of the expanded setting string. This memory
+*        should be freed using astFree when no longer needed. If a NULL
+*        pointer is supplied, no string is created.
+*     args
+*        The variable argument list which contains values to be
+*        substituted for any "printf"-style format specifiers that
+*        appear in the "settings" string.
+
+*  Notes:
+*     - Attribute names are not case sensitive.
+*     - White space may surround attribute names and will be ignored.
+*     - White space may also surround attribute values where it will
+*     be ignored (except for string-valued attributes where it is
+*     significant and forms part of the value to be assigned).
+*     - After this function has substituted values for "printf"-style
+*     format specifiers it splits the "settings" string into
+*     individual attribute settings which it passes one at a time to
+*     the protected astSetAttrib method (after removal of white space
+*     and conversion of attribute names to lower case). The
+*     astSetAttrib method should therefore be extended by derived
+*     classes which define new attributes, and this will allow the
+*     astVSet (and astSet) methods to have access to those attributes.
+*     - This function provides the same functionality as the astSet
+*     public method but accepts a va_list variable argument list
+*     instead of a variable number of arguments. It is provided for
+*     use by functions in other class implementations which accept a
+*     variable number of arguments and must therefore pass their
+*     argument list to this method in va_list form.
+*-
+*/
+
+#define MIN_BUFF_LEN 1024
+#define ERRBUF_LEN 80
+
+/* Local Variables: */
+   char errbuf[ ERRBUF_LEN ];    /* Buffer for system error message */
+   char setting_buf[ MIN_BUFF_LEN ]; /* Expanded "%s" settting string */
+   char *dyn_buf;                /* Pointer to dynamic buffer for expanded setting */
+   char *errstat;                /* Pointer to error message */
+   char *assign;                 /* Pointer to assigment substring */
+   char *assign_end;             /* Pointer to null at end of assignment */
+   char *buff1;                  /* Pointer to temporary string buffer */
+   char *buff2;                  /* Pointer to temporary string buffer */
+   char *buff3;                  /* Pointer to temporary string buffer */
+   char *eq1;                    /* Pointer to 1st equals sign */
+   int buff_len;                 /* Length of temporary buffer */
+   int expanded;                 /* Has the Settings string been expanded yet? */
+   int i;                        /* Loop counter for characters */
+   int j;                        /* Offset for revised assignment character */
+   int len;                      /* Length of settings string */
+   int lo;                       /* Convert next character to lower case? */
+   int nc;                       /* Number of vsprintf output characters */
+   int quoted;                   /* Are we in a quoted string? */
+   int stat;                     /* Value of errno after an error */
+   int tq;                       /* Test if the next non-space is a quote? */
+
+/* Initialise */
+   if( text ) *text = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain the length of the "settings" string and test it is not
+   zero. If it is, there is nothing more to do. */
+   len = (int) strlen( settings );
+   if ( len != 0 ) {
+
+/* If the setting string is just "%s" (with optional trailing and leading
+   white space) then the variable argument potentially contains more than
+   one attribute setting, in which case we expand the setting string now
+   and use the expanded string in place of the supplied string in the rest
+   of this function. */
+      nc = 0;
+      sscanf( settings, " %%s %n", &nc );
+      if( nc == len ) {
+
+/* Expand the supplied string using a fixed-length buffer. This writes at
+   most MIN_BUFF_LEN characters to "buf", but returns the number of
+   characters that would have been needed to write the whole string. */
+         len =  vsnprintf( setting_buf, sizeof(setting_buf), settings, args );
+
+/* If the fixed-length buffer is too short, use a dynamically allocated
+   buffer instead. */
+         if( len + 1 > MIN_BUFF_LEN ) {
+            dyn_buf = astMalloc( len + 1 );
+            if( astOK ) {
+               len =  vsnprintf( dyn_buf, len + 1, settings, args );
+               settings = dyn_buf;
+            }
+         } else {
+            dyn_buf = NULL;
+            settings = setting_buf;
+         }
+
+/* Indicate that "settings" has been expanded. */
+         expanded = 1;
+
+      } else {
+         expanded = 0;
+         dyn_buf = NULL;
+      }
+
+/* Allocate memory and store a copy of the string. */
+      buff1 = astStore( NULL, settings, (size_t) ( len + 1 ) );
+      if ( astOK ) {
+
+/* Convert each comma in the string into '\n'. This is to distinguish
+   commas initially present from those introduced by the formatting to
+   be performed below. We only do this if there is more than one equals
+   sign in the setting string, since otherwise any commas are probably
+   characters contained within a string attribute value. Ignore commas
+   that occur within quoted strings. */
+         eq1 = strchr( buff1, '=' );
+         if( eq1 && strchr( eq1 + 1, '=' ) ) {
+            quoted = 0;
+            for ( i = 0; i < len; i++ ) {
+               if( !quoted ) {
+                  if ( buff1[ i ] == ',' ) {
+                     buff1[ i ] = '\n';
+                  } else if( buff1[ i ] == '"' ) {
+                     quoted = 1;
+                  }
+               } else if( buff1[ i ] == '"' ){
+                  quoted = 0;
+               }
+            }
+         }
+
+/* Calculate a size for a further buffer twice the size of the first
+   one.  Ensure it is not less than a minimum size and then allocate
+   this buffer. */
+         buff_len = 2 * len;
+         if ( buff_len < MIN_BUFF_LEN ) buff_len = MIN_BUFF_LEN;
+         buff2 = astMalloc( (size_t) ( buff_len + 1 ) );
+         if ( astOK ) {
+
+/* Use "vsprintf" to substitute values for any format specifiers in
+   the "settings" string, writing the resulting string into the second
+   buffer. If the "settings" string has already been expanded, then just
+   copy it. */
+            errno = 0;
+            if( !expanded ) {
+               nc = vsprintf( buff2, buff1, args );
+            } else {
+               strcpy( buff2, buff1 );
+               nc = strlen( buff1 );
+            }
+
+/* Get a copy of the expanded string to return as the function value and
+   convert newlines back to commas. */
+            if( text ) {
+               *text = astStore( NULL, buff2, nc + 1 );
+               if( *text ) {
+                  for ( i = 0; i <= nc; i++ ) {
+                     if ( (*text)[ i ] == '\n' ) (*text)[ i ] = ',';
+                  }
+               }
+            }
+
+/* The possibilities for error detection are limited here, but check
+   if an error value was returned and report an error. Include
+   information from errno if it was set. */
+            if ( nc < 0 ) {
+               if( astOK ) {
+                  stat = errno;
+
+                  if( stat ) {
+#if HAVE_STRERROR_R
+                     strerror_r( stat, errbuf, ERRBUF_LEN );
+                     errstat = errbuf;
+#else
+                     errstat = strerror( stat );
+#endif
+                  } else {
+                     *errbuf = 0;
+                     errstat = errbuf;
+                  }
+
+                  astError( AST__ATSER, "astVSet(%s): Error formatting an "
+                            "attribute setting%s%s.", status, astGetClass( this ),
+                            stat? " - " : "", errstat );
+                  astError( AST__ATSER, "The setting string was \"%s\".", status,
+                            settings );
+               }
+
+/* Also check that the result buffer did not overflow. If it did,
+   memory will probably have been corrupted but this cannot be
+   prevented with "vsprintf" (although we try and make the buffer
+   large enough). Report the error and abort. */
+            } else if ( nc > buff_len ) {
+               if( astOK ) {
+                  astError( AST__ATSER, "astVSet(%s): Internal buffer overflow "
+                            "while formatting an attribute setting - the result "
+                            "exceeds %d characters.", status, astGetClass( this ),
+                            buff_len );
+                  astError( AST__ATSER, "The setting string was \"%s\".", status,
+                            settings );
+               }
+
+/* If all is OK, loop to process each formatted attribute assignment
+   (these are now separated by '\n' characters). */
+	    } else {
+               assign = buff2;
+               while ( assign ) {
+
+/* Change the '\n' at the end of each assignment to a null to
+   terminate it. */
+                  if ( ( assign_end = strchr( assign, '\n' ) ) ) {
+                     *assign_end = '\0';
+                  }
+
+/* Before making the assignment, loop to remove white space and upper
+   case characters from the attribute name. */
+                  lo = 1;
+                  tq = -1;
+                  quoted = 0;
+                  for ( i = j = 0; assign[ i ]; i++ ) {
+
+/* Note when an '=' sign is encountered (this signals the end of the
+   attribute name). */
+                     if ( assign[ i ] == '=' ) lo = 0;
+
+/* Before the '=' sign, convert all characters to lower case and move
+   everything to the left to eliminate white space. Afer the '=' sign,
+   copy all characters to their new location unchanged, except for any
+   delimiting quotes, which are removed. astSetC replaces commas in the
+   attribute value by '\r' characters. Reverse this now. */
+                     if ( !lo || !isspace( assign[ i ] ) ) {
+                        if( assign[ i ] == '\r' ) {
+                           assign[ j++ ] = ',';
+
+                        } else if( lo ) {
+                           assign[ j++ ] = tolower( assign[ i ] );
+
+                        } else {
+                           assign[ j++ ] = assign[ i ];
+
+                           if( tq > 0 && !isspace( assign[ i ] ) ) {
+                              if( assign[ i ] == '"' ) {
+                                 quoted = 1;
+                                 j--;
+                              }
+                              tq = 0;
+                           }
+
+                        }
+                     }
+
+/* If the current character is the initial '=' sign, set "tq" positive,
+   meaning "check if the next non-space character is a quote". */
+                     if ( assign[ i ] == '=' && tq == -1 ) tq = 1;
+                  }
+
+/* if the value was quoted. remove the trailing quote. */
+                  if( quoted ) {
+                     j--;
+                     while( isspace( assign[ j ] ) ) j--;
+                     if( assign[ j ] == '"' ) j--;
+                     j++;
+                  }
+
+/* Terminate the revised assignment string and pass it to astSetAttrib
+   to make the assignment (unless the string was all blank, in which
+   case we ignore it). */
+                  assign[ j ] = '\0';
+                  if ( j ) {
+
+/* If there are no characters to the right of the equals sign append a
+   space after the equals sign. Without this, a string such as "Title="
+   would not be succesfully matched against the attribute name "Title"
+   within SetAttrib. */
+                     if( assign[ j - 1 ] == '=' ) {
+                        buff3 = astStore( NULL, assign,
+                                          (size_t) j + 2 );
+                        if ( astOK ) {
+                           buff3[ j ] = ' ';
+                           buff3[ j + 1 ] = '\0';
+                           astSetAttrib( this, buff3 );
+                        }
+                        buff3 = astFree( buff3 );
+
+                     } else {
+                        astSetAttrib( this, assign );
+                     }
+                  }
+
+/* Check for errors and abort if any assignment fails. Otherwise,
+   process the next assignment substring. */
+                  if ( !astOK ) break;
+                  assign = assign_end ? assign_end + 1 : NULL;
+               }
+	    }
+         }
+
+/* Free the memory allocated for string buffers. */
+         buff2 = astFree( buff2 );
+         dyn_buf = astFree( dyn_buf );
+      }
+      buff1 = astFree( buff1 );
+   }
+}
+#undef ERRBUF_LEN
+#undef MIN_BUFF_LEN
+
+/* Attribute access functions. */
+/* --------------------------- */
+/*
+*att++
+*  Name:
+*     Class
+
+*  Purpose:
+*     Object class name.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Character string, read-only.
+
+*  Description:
+*     This attribute gives the name of the class to which an Object
+*     belongs.
+
+*  Applicability:
+*     Object
+*        All Objects have this attribute.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     ID
+
+*  Purpose:
+*     Object identification string.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute contains a string which may be used to identify
+*     the Object to which it is attached. There is no restriction on
+*     the contents of this string, which is not used internally by the
+*     AST library, and is simply returned without change when
+*     required. The default value is an empty string.
+*
+*     An identification string can be valuable when, for example,
+c     several Objects have been stored in a file (using astWrite) and
+f     several Objects have been stored in a file (using AST_WRITE) and
+c     are later retrieved (using astRead). Consistent use of the ID
+f     are later retrieved (using AST_READ). Consistent use of the ID
+*     attribute allows the retrieved Objects to be identified without
+*     depending simply on the order in which they were stored.
+*
+*     This attribute may also be useful during debugging, to
+c     distinguish similar Objects when using astShow to display them.
+f     distinguish similar Objects when using AST_SHOW to display them.
+
+*  Applicability:
+*     Object
+*        All Objects have this attribute.
+
+*  Notes:
+*     - Unlike most other attributes, the value of the ID attribute is
+*     not transferred when an Object is copied. Instead, its value is
+*     undefined (and therefore defaults to an empty string) in any
+*     copy. However, it is retained in any external representation of
+c     an Object produced by the astWrite function.
+f     an Object produced by the AST_WRITE routine.
+*att--
+*/
+/* Clear the ID value by freeing the allocated memory and assigning a
+   NULL pointer. */
+astMAKE_CLEAR(Object,ID,id,astFree( this->id ))
+
+/* If the ID value is not set, supply a default in the form of a
+   pointer to the constant string "". */
+astMAKE_GET(Object,ID,const char *,NULL,( this->id ? this->id : "" ))
+
+/* Set an ID value by freeing any previously allocated memory,
+   allocating new memory and storing the string. */
+astMAKE_SET(Object,ID,const char *,id,astStore( this->id, value,
+                                                strlen( value ) + (size_t) 1 ))
+
+/* The ID value is set if the pointer to it is not NULL. */
+astMAKE_TEST(Object,ID,( this->id != NULL ))
+
+/*
+*att++
+*  Name:
+*     Ident
+
+*  Purpose:
+*     Permanent Object identification string.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute is like the ID attribute, in that it contains a
+*     string which may be used to identify the Object to which it is
+*     attached. The only difference between ID and Ident is that Ident
+*     is transferred when an Object is copied, but ID is not.
+
+*  Applicability:
+*     Object
+*        All Objects have this attribute.
+
+*att--
+*/
+/* Clear the Ident value by freeing the allocated memory and assigning a
+   NULL pointer. */
+astMAKE_CLEAR(Object,Ident,ident,astFree( this->ident ))
+
+/* If the Ident value is not set, supply a default in the form of a
+   pointer to the constant string "". */
+astMAKE_GET(Object,Ident,const char *,NULL,( this->ident ? this->ident : "" ))
+
+/* Set an Ident value by freeing any previously allocated memory,
+   allocating new memory and storing the string. */
+astMAKE_SET(Object,Ident,const char *,ident,astStore( this->ident, value,
+                                                strlen( value ) + (size_t) 1 ))
+
+/* The Ident value is set if the pointer to it is not NULL. */
+astMAKE_TEST(Object,Ident,( this->ident != NULL ))
+
+/*
+*att++
+*  Name:
+*     UseDefs
+
+*  Purpose:
+*     Use default values for unspecified attributes?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute specifies whether default values should be used
+*     internally for object attributes which have not been assigned a
+*     value explicitly. If a non-zero value (the default) is supplied for
+*     UseDefs, then default values will be used for attributes which have
+*     not explicitly been assigned a value. If zero is supplied for UseDefs,
+*     then an error will be reported if an attribute for which no explicit
+*     value has been supplied is needed internally within AST.
+*
+*     Many attributes (including the UseDefs attribute itself) are unaffected
+*     by the setting of the UseDefs attribute, and default values will always
+*     be used without error for such attributes. The "Applicability:" section
+*     below lists the attributes which are affected by the setting of UseDefs.
+
+*     Note, UseDefs only affects access to attributes internally within
+*     AST. The public accessor functions such as
+c     astGetC
+f     AST_GETC
+*     is unaffected by the UseDefs attribute - default values will always
+*     be returned if no value has been set. Application code should use the
+c     astTest
+f     AST_TEST
+*     function if required to determine if a value has been set for an
+*     attribute.
+
+*  Applicability:
+*     Object
+*        All Objects have this attribute, but ignore its setting except
+*        as described below for individual classes.
+*     FrameSet
+*        The default value of UseDefs for a FrameSet is redefined to be
+*        the UseDefs value of its current Frame.
+*     CmpFrame
+*        The default value of UseDefs for a CmpFrame is redefined to be
+*        the UseDefs value of its first component Frame.
+*     Region
+*        The default value of UseDefs for a Region is redefined to be
+*        the UseDefs value of its encapsulated Frame.
+*     Frame
+*        If UseDefs is zero, an error is reported when aligning Frames if the
+*        Epoch, ObsLat or ObsLon attribute is required but has not been
+*        assigned a value explicitly.
+*     SkyFrame
+*        If UseDefs is zero, an error is reported when aligning SkyFrames
+*        if any of the following attributes are required but have not been
+*        assigned a value explicitly: Epoch, Equinox.
+*     SpecFrame
+*        If UseDefs is zero, an error is reported when aligning SpecFrames
+*        if any of the following attributes are required but have not been
+*        assigned a value explicitly: Epoch, RefRA, RefDec, RestFreq,
+*        SourceVel, StdOfRest.
+*     DSBSpecFrame
+*        If UseDefs is zero, an error is reported when aligning DSBSpecFrames
+*        or when accessing the ImagFreq attribute if any of the following
+*        attributes are required but have not been assigned a value explicitly:
+*        Epoch, DSBCentre, IF.
+*att--
+*/
+astMAKE_CLEAR(Object,UseDefs,usedefs,CHAR_MAX)
+astMAKE_GET(Object,UseDefs,int,1,((this->usedefs!=CHAR_MAX)?this->usedefs:1))
+astMAKE_SET(Object,UseDefs,int,usedefs,((value)?1:0))
+astMAKE_TEST(Object,UseDefs,(this->usedefs!=CHAR_MAX))
+
+/*
+*att++
+*  Name:
+*     Nobject
+
+*  Purpose:
+*     Number of Objects in class.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute gives the total number of Objects currently in
+*     existence in the same class as the Object whose attribute value
+*     is requested. This count does not include Objects which belong
+*     to derived (more specialised) classes.
+*
+*     This attribute is mainly intended for debugging. It can be used
+*     to detect whether Objects which should have been deleted have,
+*     in fact, been deleted.
+
+*  Applicability:
+*     Object
+*        All Objects have this attribute.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     ObjSize
+
+*  Purpose:
+*     The in-memory size of the Object.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute gives the total number of bytes of memory used by
+*     the Object. This includes any Objects which are encapsulated within
+*     the supplied Object.
+
+*  Applicability:
+*     Object
+*        All Objects have this attribute.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     RefCount
+
+*  Purpose:
+*     Count of active Object pointers.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute gives the number of active pointers associated
+*     with an Object. It is modified whenever pointers are created or
+c     annulled (by astClone, astAnnul or astEnd for example). The count
+f     annulled (by AST_CLONE, AST_ANNUL or AST_END for example). The count
+*     includes the initial pointer issued when the Object was created.
+*
+*     If the reference count for an Object falls to zero as the result
+*     of annulling a pointer to it, then the Object will be deleted.
+
+*  Applicability:
+*     Object
+*        All Objects have this attribute.
+*att--
+*/
+
+/* Standard class functions. */
+/* ========================= */
+/*
+*+
+*  Name:
+*     astCheck<Class>
+
+*  Purpose:
+*     Validate class membership.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "class.h"
+*     Ast<Class> *astCheck<Class>( Ast<Class> *this )
+
+*  Class Membership:
+*     <Class> class function.
+
+*  Description:
+*     This function validates membership of the class called <Class>,
+*     or of any class derived from it. If the Object is not a member,
+*     or the pointer supplied does not identify a valid Object, an
+*     error is reported and the global error status is set to
+*     AST__OBJIN.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+
+*  Returned Value:
+*     The function always returns a copy of the "this" pointer
+*     (whether it finds it valid or not).
+
+*  Notes:
+*     - Each class provides a function (astCheck<Class>) of this form,
+*     where <Class> and <class> are replaced by the class name (with
+*     appropriate capitalisation).
+*     - Normal error status checking is performed, so this function
+*     will not execute if the global error status is set on entry (the
+*     usual function value will be returned, however).
+*     - This function is primarily intended for validating Object
+*     pointers passed to member functions as part of a class
+*     interface.
+*-
+*/
+
+/* Implement the astCheckObject function using the macro defined for this
+   purpose in the "object.h" header file. */
+astMAKE_CHECK(Object)
+
+int astIsAObject_( const AstObject *this, int *status ) {
+/*
+*++
+*  Name:
+c     astIsA<Class>
+f     AST_ISA<CLASS>
+
+*  Purpose:
+*     Test membership of a class by an Object.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "class.h"
+c     int astIsA<Class>( const Ast<Class> *this )
+f     RESULT = AST_ISA<CLASS>( THIS, STATUS )
+
+*  Class Membership:
+*     Class function.
+
+*  Description:
+*     This is a family of functions which test whether an Object is a
+c     member of the class called <Class>, or of any class derived from
+f     member of the class called <CLASS>, or of any class derived from
+*     it.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Object.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astIsA<Class>()
+c        One if the Object belongs to the class called <Class> (or to a
+c        class derived from it), otherwise zero.
+f     AST_ISA<CLASS> = LOGICAL
+f        .TRUE. if the Object belongs to the class called <CLASS> (or to
+f        a class derived from it), otherwise .FALSE..
+
+*  Applicability:
+*     Object
+*        These functions apply to all Objects.
+
+*  Examples:
+c     member = astIsAFrame( obj );
+c        Tests whether Object "obj" is a member of the Frame class, or
+c        of any class derived from a Frame.
+f     MEMBER = AST_ISAFRAME( OBJ, STATUS );
+f        Tests whether Object OBJ is a member of the Frame class, or
+f        of any class derived from a Frame.
+
+*  Notes:
+c     - Every AST class provides a function (astIsA<Class>) of this
+c     form, where <Class> should be replaced by the class name.
+f     - Every AST class provides a function (AST_ISA<CLASS>) of this
+f     form, where <CLASS> should be replaced by the class name.
+c     - This function attempts to execute even if the AST error status
+c     is set
+f     - This function attempts to execute even if STATUS is set to an
+f     error value
+*     on entry, although no further error report will be made
+*     if it subsequently fails under these circumstances.
+c     - A value of zero will be returned if this function should fail
+f     - A value of .FALSE. will be returned if this function should fail
+*     for any reason. In particular, it will fail if the pointer
+*     supplied does not identify an Object of any sort.
+*--
+*/
+
+/* Local Variables: */
+   int valid;                    /* Valid object? */
+
+/* Since this is the base class, the implementation of this function
+   differs from that in derived classes (in that it fails and
+   potentially reports an error if the returned result is zero). */
+
+/* Initialise. */
+   valid = 0;
+
+/* Check if a NULL pointer was supplied (this can never be valid). If
+   OK, check if the Object contains the correct "magic number" in its
+   check field. */
+   if ( !this || ( this->check != Magic( this, this->size, status ) ) ) {
+
+/* If it is not a valid Object, then report an error (but only if the
+   global error status has not already been set). */
+      if ( astOK ) {
+         astError( AST__OBJIN, "astIsAObject(%s): Invalid Object pointer "
+                   "given (points at address %p).", status, astGetClass( this ),
+                   (void *) this );
+      }
+
+/* Otherwise, note that the Object is valid. */
+   } else {
+      valid = 1;
+   }
+
+/* Return the result. */
+   return valid;
+}
+
+void astInitObjectVtab_(  AstObjectVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitObjectVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astInitObjectVtab( AstObjectVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Object vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Object class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   int ivtab;                    /* Index of next entry in known_vtabs */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise the contents of the class identifier. Since this is the
+   base class, we assign null values to the fields. */
+   vtab->id.check = NULL;
+   vtab->id.parent = NULL;
+
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->Clear = Clear;
+   vtab->ClearAttrib = ClearAttrib;
+   vtab->ClearID = ClearID;
+   vtab->ClearIdent = ClearIdent;
+   vtab->Dump = Dump;
+   vtab->Equal = Equal;
+   vtab->GetAttrib = GetAttrib;
+   vtab->GetID = GetID;
+   vtab->GetIdent = GetIdent;
+   vtab->HasAttribute = HasAttribute;
+   vtab->Same = Same;
+   vtab->SetAttrib = SetAttrib;
+   vtab->SetID = SetID;
+   vtab->SetIdent = SetIdent;
+   vtab->Show = Show;
+   vtab->TestAttrib = TestAttrib;
+   vtab->TestID = TestID;
+   vtab->TestIdent = TestIdent;
+   vtab->EnvSet = EnvSet;
+   vtab->VSet = VSet;
+   vtab->Cast = Cast;
+   vtab->GetObjSize = GetObjSize;
+   vtab->CleanAttribs = CleanAttribs;
+
+   vtab->TestUseDefs = TestUseDefs;
+   vtab->SetUseDefs = SetUseDefs;
+   vtab->ClearUseDefs = ClearUseDefs;
+   vtab->GetUseDefs = GetUseDefs;
+
+#if defined(THREAD_SAFE)
+   vtab->ManageLock = ManageLock;
+#endif
+
+/* Store the pointer to the class name. */
+   vtab->class = name;
+
+/* Initialise the count of active objects and the number of destructors,
+   copy constructors and dump functions. */
+   vtab->nobject = 0;
+   vtab->ndelete = 0;
+   vtab->ncopy = 0;
+   vtab->ndump = 0;
+
+/* Initialise the arrays of destructor, copy constructor and dump
+   function pointers. */
+   vtab->delete = NULL;
+   vtab->copy = NULL;
+   vtab->dump = NULL;
+   vtab->dump_class = NULL;
+   vtab->dump_comment = NULL;
+
+/* Initialise the default attributes to use when creating objects. */
+   vtab->defaults = NULL;
+
+/* The virtual function table for each class contains a list of pointers
+   to memory blocks which have previously been used to store an Object of
+   the same class, but which have since been deleted using astDelete.
+   These memory blocks are free to be re-used when a new Object of the
+   same class is initialised. This saves on the overheads associated with
+   continuously allocating small blocks of memory using malloc. */
+   vtab->nfree = 0;
+   vtab->free_list = NULL;
+
+/* Add the supplied virtual function table pointer to the end of the list
+   of known vtabs. */
+   ivtab = nvtab++;
+
+   astBeginPM;
+   known_vtabs = astGrow( known_vtabs, nvtab, sizeof( AstObjectVtab *) );
+   astEndPM;
+
+   if( astOK && known_vtabs ) known_vtabs[ ivtab ] = vtab;
+
+/* Fill a pointer value with zeros (not necessarily the same thing as a
+   NULL pointer) for subsequent use. */
+   (void) memset( &zero_ptr, 0, sizeof( AstObject * ) );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised. */
+   if( vtab == &class_vtab ) class_init = 1;
+}
+
+AstObject *astInitObject_( void *mem, size_t size, int init,
+                           AstObjectVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitObject
+
+*  Purpose:
+*     Initialise an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astInitObject( void *mem, size_t size, int init,
+*                               AstObjectVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Object initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Object. It allocates memory (if necessary) to accommodate the
+*     Object plus any additional data associated with the derived class. It
+*     then initialises an Object structure at the start of this memory. If the
+*     "init" flag is set, it also initialises the contents of a virtual
+*     function table for an Object at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Object is to be initialised.
+*        This must be of sufficient size to accommodate the Object data
+*        (sizeof(Object)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Object (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Object
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Object's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Object.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+
+*  Returned Value:
+*     A pointer to the new Object.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstObject *new;               /* Pointer to new Object */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Determine if memory must be allocated dynamically. If so, use the last
+   block of memory in the list of previously allocated but currently
+   unused blocks identified by the vtab "free_list" array, reducing the
+   length of the free list by one, and nullifying the entry in the list
+   for safety. If the list is originally empty, allocate memory for a new
+   object using astMalloc. */
+   if( !mem ) {
+      if( object_caching ) {
+         if( vtab->nfree > 0 ) {
+            mem = vtab->free_list[ --(vtab->nfree) ];
+            vtab->free_list[ vtab->nfree ] = NULL;
+            if( astSizeOf( mem ) != size && astOK ) {
+               astError( AST__INTER, "astInitObject(%s): Free block has size "
+                         "%d but the %s requires %d bytes (internal AST "
+                         "programming error).", status, vtab->class,
+                         (int) astSizeOf( mem ), vtab->class, (int) size );
+            }
+         } else {
+            mem = astMalloc( size );
+         }
+
+      } else {
+         mem = astMalloc( size );
+      }
+
+/* If memory had already been allocated, adjust the "size" value to match
+   the size of the allocated memory. */
+   } else {
+      size = astSizeOf( mem );
+   }
+
+/* Obtain a pointer to the new Object. */
+   if ( astOK ) {
+      new = (AstObject *) mem;
+
+/* Zero the entire new Object structure (to prevent accidental re-use
+   of any of its values after deletion). */
+      (void) memset( new, 0, size );
+
+/* If necessary, initialise the virtual function table. */
+/* ---------------------------------------------------- */
+      if ( init ) astInitObjectVtab( vtab, name );
+      if( astOK ) {
+
+/* Initialise the Object data. */
+/* --------------------------- */
+/* Store a unique "magic" value in the Object structure. This will be
+   used (e.g. by astIsAObject) to determine if a pointer identifies a
+   valid Object. Note that this differs from the practice in derived
+   classes, where this number is stored in the virtual function
+   table. We take a different approach here so that we need not follow
+   a pointer to the virtual function table obtained from a structure
+   that hasn't yet been validated as an Object. This minimises the
+   risk of a memory access violation. */
+         new->check = Magic( new, size, status );
+
+/* Associate the Object with its virtual function table. */
+         new->vtab = vtab;
+
+/* Store the Object size and note if its memory was dynamically allocated. */
+         new->size = size;
+         new->dynamic = astIsDynamic( new );
+
+/* Initialise the reference count (of Object pointers in use). */
+         new->ref_count = 1;
+
+/* Initialise the ID strings. */
+         new->id = NULL;
+         new->ident = NULL;
+
+/* Use default values for unspecified attributes. */
+         new->usedefs = CHAR_MAX;
+
+/* Increment the count of active Objects in the virtual function table.
+   Use the count as a unique identifier (unique within the class) for
+   the Object. */
+         new->iref = vtab->nobject++;
+
+/* Initialise the pointer to an external object that acts as a proxy for
+   the AST Object within foreign language interfaces. */
+         new->proxy = NULL;
+      }
+
+/* If an error occurred, clean up by deleting the new Object. Otherwise
+   lock the object for use by the currently executing thread. */
+      if ( !astOK ) {
+         new = astDelete( new );
+
+#ifdef THREAD_SAFE
+      } else {
+         if( pthread_mutex_init( &(new->mutex1), NULL ) != 0 && astOK ) {
+            astError( AST__INTER, "astInitObject(%s): Failed to "
+                      "initialise POSIX mutex1 for the new Object.", status,
+                      vtab->class );
+         }
+         if( pthread_mutex_init( &(new->mutex2), NULL ) != 0 && astOK ) {
+            astError( AST__INTER, "astInitObject(%s): Failed to "
+                      "initialise POSIX mutex2 for the new Object.", status,
+                      vtab->class );
+         }
+         new->locker = -1;
+         new->globals = NULL;
+         (void) ManageLock( new, AST__LOCK, 0, NULL, status );
+         if( !astOK ) new = astDelete( new );
+#endif
+      }
+   }
+
+/* Return a pointer to the new Object. */
+   return new;
+}
+
+AstObject *astLoadObject_( void *mem, size_t size,
+                           AstObjectVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadObject
+
+*  Purpose:
+*     Load an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astLoadObject( void *mem, size_t size,
+*                               AstObjectVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     Object loader.
+
+*  Description:
+*     This function is provided to load a new Object using data read
+*     from a Channel, and to allocate memory for it if necessary.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for an Object at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Object is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Object data (sizeof(Object)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Object (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Object structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstObject) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Object. If this is NULL, a pointer to
+*        the (static) virtual function table for the Object class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new Object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Object" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Object.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   AstObject *new;               /* Pointer to the new Object */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Object. In this case the
+   Object belongs to this class, so supply appropriate values for
+   initialising it and its virtual function table. */
+   if ( !vtab ) {
+      size = sizeof( AstObject );
+      vtab = &class_vtab;
+      name = "Object";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitObjectVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* There is no parent class to load, so simply initialise a new Object
+   structure as if a new Object were being created from scratch. */
+   new = astInitObject( mem, size, 0, vtab, name );
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Object" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+      new->id = astReadString( channel, "id", NULL );
+      new->ident = astReadString( channel, "ident", NULL );
+      new->usedefs = astReadInt( channel, "usedfs", CHAR_MAX );
+
+/* We simply read the values for the read-only attributes (just in
+   case they've been un-commented in the external representation) and
+   discard them. This prevents any possibility of error due to un-read
+   input. */
+      (void) astReadInt( channel, "refcnt", 0 );
+      (void) astReadInt( channel, "nobj", 0 );
+
+/* Initialise the pointer to an external object that acts as a proxy for
+   the AST Object within foreign language interfaces. */
+      new->proxy = NULL;
+
+/* If an error occurred, clean up by deleting the new Object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Object pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+void astClear_( AstObject *this, const char *attrib, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Object,Clear))( this, attrib, status );
+}
+void astClearAttrib_( AstObject *this, const char *attrib, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Object,ClearAttrib))( this, attrib, status );
+}
+void astDump_( AstObject *this, AstChannel *channel, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Object,Dump))( this, channel, status );
+}
+
+#if defined(THREAD_SAFE)
+int astManageLock_( AstObject *this, int mode, int extra, AstObject **fail,
+                    int *status ) {
+   if( !this ) return 0;
+   return (**astMEMBER(this,Object,ManageLock))( this, mode, extra, fail, status );
+}
+#endif
+
+int astEqual_( AstObject *this, AstObject *that, int *status ) {
+   if ( !astOK ) return 0;
+   if( this == that ) return 1;
+   return (**astMEMBER(this,Object,Equal))( this, that, status );
+}
+const char *astGetAttrib_( AstObject *this, const char *attrib, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Object,GetAttrib))( this, attrib, status );
+}
+void astSetAttrib_( AstObject *this, const char *setting, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Object,SetAttrib))( this, setting, status );
+}
+void astShow_( AstObject *this, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Object,Show))( this, status );
+}
+int astTestAttrib_( AstObject *this, const char *attrib, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Object,TestAttrib))( this, attrib, status );
+}
+void astEnvSet_( AstObject *this, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Object,EnvSet))( this, status );
+}
+void astVSet_( AstObject *this, const char *settings, char **text, va_list args, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Object,VSet))( this, settings, text, args, status );
+}
+int astGetObjSize_( AstObject *this, int *status ) {
+   if ( !astOK || !this ) return 0;
+   return (**astMEMBER(this,Object,GetObjSize))( this, status );
+}
+void astCleanAttribs_( AstObject *this, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Object,CleanAttribs))( this, status );
+}
+AstObject *astCast_( AstObject *this, AstObject *obj, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Object,Cast))( this, obj, status );
+}
+int astSame_( AstObject *this, AstObject *that, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Object,Same))( this, that, status );
+}
+int astHasAttribute_( AstObject *this, const char *attrib, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Object,HasAttribute))( this, attrib, status );
+}
+
+/* External interface. */
+/* =================== */
+/* The following relates to the external interface to Objects and not
+   specifically to the implementation of the Object class itself
+   (although it contains external functions which replace the internal
+   versions defined earlier). */
+
+
+/* Type Definitions. */
+/* ----------------- */
+/* Define the Handle structure. This is attached to Objects when they
+   are accessed via the public (external, user-callable) interface.
+   Handles provide a buffer between the Object identifiers issued to
+   external users and the naked C pointers used to handle Objects
+   internally. They also implement the context levels used by
+   astBegin, astEnd, astExempt and astExport (which are only available
+   to external users). */
+typedef struct Handle {
+   AstObject *ptr;               /* C Pointer to the associated Object */
+   int context;                  /* Context level for this Object */
+   int check;                    /* Check value to ensure validity */
+
+#if defined(THREAD_SAFE)
+   int thread;                   /* Identifier for owning thread */
+#endif
+
+#if defined(MEM_DEBUG)
+   int id;                       /* The id associated with the memory block
+                                    holding the Object last associated with
+                                    this handle. */
+   AstObjectVtab *vtab;          /* Pointer to the firtual function table of
+                                    the Object last associated with this
+                                    handle. */
+#endif
+
+/* Links between Handles are implemented using integer offsets rather
+   than through pointers. */
+   int flink;                    /* Backward link to previous Handle */
+   int blink;                    /* Forward link to next Handle */
+} Handle;
+
+/* Define a union with an overlapping int and AstObject*. This is used
+   to transfer an integer bit pattern into and out of a pointer
+   variable used to store an Object identifier. This avoids any
+   implementation dependent aspects of integer-to-pointer
+   conversions. */
+typedef union IdUnion {
+   AstObject *pointer;
+   int integer;
+} IdUnion;
+
+/* Define a union which allows a bit pattern to be accessed as a
+   signed or unsigned int. */
+typedef union MixedInts {
+   int i;
+   unsigned u;
+} MixedInts;
+
+/* Static Variables. */
+/* ----------------- */
+/* The array of Handle structures is a pool of resources available to all
+   threads. Each thread has its own conext level and its own "active_handles"
+   array to identify the first Handle at each context level. */
+static Handle *handles = NULL; /* Pointer to allocated array of Handles */
+static int free_handles = -1; /* Offset to head of free Handle list */
+static int nhandles = 0; /* Number of Handles in "handles" array */
+static unsigned nids = 0; /* Number of IDs issued to external users */
+
+#if defined(THREAD_SAFE)
+static int unowned_handles = -1; /* Offset to head of unowned Handle
+                                    list. In a single threaded environment,
+                                    all handles must be owned by a thread. */
+#endif
+
+#ifdef MEM_DEBUG
+static int Watched_Handle = -1; /* A handle index to be watched. Activity
+                                   on this handle is reported using
+                                   astHandleUse and astHandleAlarm. */
+#endif
+
+
+/* External Interface Function Prototypes. */
+/* --------------------------------------- */
+/* MYSTATIC should normally be set to "static" to make the following
+   function have local symbols. But it may be set to blank for debugging
+   purposes in order to enable these functions to appear in a backtrace
+   such as produced by the astBacktrace function. */
+#define MYSTATIC
+
+/* Private functions associated with the external interface. */
+MYSTATIC AstObject *AssocId( int, int * );
+MYSTATIC int CheckId( AstObject *, int, int * );
+MYSTATIC void AnnulHandle( int, int * );
+MYSTATIC void InitContext( int * );
+MYSTATIC void InsertHandle( int, int *, int * );
+MYSTATIC void RemoveHandle( int, int *, int * );
+
+#if defined(MEM_DEBUG)
+MYSTATIC void CheckList( int *, int * );
+MYSTATIC void CheckInList( int, int *, int, int * );
+MYSTATIC int CheckThread( int, int *, int * );
+MYSTATIC const char *HandleString( int, char * );
+MYSTATIC const char *HeadString( int *, char * );
+#endif
+
+
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstObject *astDeleteId_( AstObject *, int * );
+void astBegin_( void );
+void astEnd_( int * );
+void astExemptId_( AstObject *, int * );
+void astImportId_( AstObject *, int * );
+void astSetId_( void *, const char *, ... );
+void astLockId_( AstObject *, int, int * );
+void astUnlockId_( AstObject *, int, int * );
+
+
+/* External Interface Functions. */
+/* ----------------------------- */
+MYSTATIC void AnnulHandle( int ihandle, int *status ) {
+/*
+*  Name:
+*     AnnulHandle
+
+*  Purpose:
+*     Annul a Handle and its associated Object pointer.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void AnnulHandle( int ihandle, int *status )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function annuls an active Handle by annulling the Object
+*     pointer it is associated with, deactivating the Handle and
+*     returning it to the free Handle list for re-use.
+*
+*     The reference count for the associated Object is decremented by
+*     this function and the Object will be deleted if this reference
+*     count falls to zero.
+
+*  Parameters:
+*     ihandle
+*        Array offset that identifies the Handle to be annulled in the
+*        "handles" array. This is fully validated by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The Handle supplied should be active, otherwise an error will
+*     result and the Handle will not be touched (but no error report
+*     will be made if the global error status has already been set).
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   AstObject *ptr;               /* Object pointer */
+   int context;                  /* Context level where Handle was issued */
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Check that the handle offset supplied is valid and report an error
+   if it is not (but only if the global error status has not already
+   been set). */
+   if ( ( ihandle < 0 ) || ( ihandle >= nhandles ) ) {
+      if ( astOK ) {
+         astError( AST__INHAN, "astAnnulHandle: Invalid attempt to annul an "
+                   "Object Handle (no. %u).", status, ihandle );
+         astError( AST__INHAN, "This Handle number is not valid (possible "
+                   "internal programming error)." , status);
+      }
+
+/* If OK, obtain the Handle's context level. */
+   } else {
+      context = handles[ ihandle ].context;
+
+/* If this indicates that the Handle isn't active, then report an
+   error (but only if the global error status has not already been
+   set). We allow handles that are currently not owned by any thread to
+   be annulled. */
+      if ( context < 0 && context != UNOWNED_CONTEXT ) {
+         if ( astOK ) {
+            astError( AST__INHAN, "astAnnulHandle: Invalid attempt to annul "
+                      "an Object Handle (no. %u).", status, ihandle );
+            astError( AST__INHAN, "This Handle is not active (possible "
+                      "internal programming error)." , status);
+         }
+
+/* If the Handle is active, annul its Object pointer. The astAnnul
+   function may call Delete functions supplied by any class, and these
+   Delete functions may involve annulling external Object IDs, which in
+   turn requires access to the handles array. For this reason, we release
+   the mutex that protects access to the handles arrays so that it can
+   potentially be re-aquired within astAnnul without causing deadlock. */
+      } else {
+
+#ifdef MEM_DEBUG
+         astHandleUse( ihandle, "annulled using check value %d ",
+                       handles[ ihandle ].check  );
+#endif
+
+         ptr = handles[ ihandle ].ptr;
+         UNLOCK_MUTEX2;
+         ptr = astAnnul( ptr );
+         LOCK_MUTEX2;
+
+/* Remove the Handle from the active list for its context level. */
+         if( context == UNOWNED_CONTEXT ) {
+
+#if defined(THREAD_SAFE)
+            RemoveHandle( ihandle, &unowned_handles, status );
+#else
+            if( astOK ) astError( AST__INTER, "AnnulHandle: reference to "
+                         "'unowned_handles' in a non-thread-safe context "
+                         "(internal AST programming error).", status );
+#endif
+
+         } else if( active_handles ) {
+            RemoveHandle( ihandle, &active_handles[ context ], status );
+
+         } else if( astOK ){
+            astError( AST__INTER, "AnnulHandle: active_handles array has "
+                      "not been initialised (internal AST programming error).",
+                      status );
+         }
+
+/* Reset the Handle's "context" value (making it inactive) and its "check"
+   value (so it is no longer associated with an identifier value). */
+         handles[ ihandle ].ptr = NULL;
+         handles[ ihandle ].context = INVALID_CONTEXT;
+         handles[ ihandle ].check = 0;
+#if defined(THREAD_SAFE)
+         handles[ ihandle ].thread = -1;
+#endif
+
+/* Place the modified Handle on the free Handles list ready for re-use. */
+         InsertHandle( ihandle, &free_handles, status );
+
+      }
+   }
+}
+
+AstObject *astAnnulId_( AstObject *this_id, int *status ) {
+/*
+*+
+*  Name:
+*     AnnulId
+
+*  Purpose:
+*     Annul an external Object identifier.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astAnnulId( AstObject *this )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astAnnul method. It accepts an active Object identifier, which
+*     it annuls, causing the associated Handle to be deactivated and
+*     returned to the free Handles list for re-use. It also causes the
+*     reference count of the associated Object to be decremented and
+*     the Object to be deleted if this reference count falls to zero.
+
+*  Parameters:
+*     this
+*        The Object identifier to be annulled.
+
+*  Returned Value:
+*     A NULL C pointer is always returned (this should be translated
+*     into an identifier value of zero for external use).
+
+*  Notes:
+*     - This function attempts to execute even if the global error
+*     status is set.
+*     - The identifier supplied should be associated with an active
+*     Object, otherwise an error will result (but no error report will
+*     be made if the global error status has already been set).
+*     - This function is invoked via the astAnnul macro for external use.
+*     For internal use (from protected code which needs to handle external
+*     IDs) it should be invoked via the astAnnulId macro (since astAnnul
+*     expects a true C pointer as its argument when used internally).
+*-
+*/
+
+/* Obtain the Object pointer from the ID supplied and validate the
+   pointer to ensure it identifies a valid Object (this generates an
+   error if it doesn't). Note, we use "astMakePointer_NoLockCheck",
+   rather than the usual "astMakePointer" since a thread should be able
+   to renounce interest in an object without needing to own the object.
+   If we used "astMakePointer" then a thread could not annul a pointer
+   unless it owned the object. But having annulled the pointer it could
+   then not unlock the object for use by another thread (since the
+   pointer it would need to do this has just been annulled). */
+   if ( !astIsAObject( astMakePointer_NoLockCheck( this_id ) ) ) return NULL;
+
+/* Obtain the Handle offset for this Object and annul the Handle and
+   its associated Object pointer. Report an error if the handle is
+   currently owned by a different thread. That is, the *Object* need
+   not be locked by the current thread (as indicated by the use of
+   astMakePointer above), but the *handle* must be owned by the current
+   thread. */
+   LOCK_MUTEX2;
+   AnnulHandle( CheckId( this_id, 1, status ), status );
+   UNLOCK_MUTEX2;
+
+/* Always return a NULL pointer value. */
+   return NULL;
+}
+
+MYSTATIC AstObject *AssocId( int ihandle, int *status ) {
+/*
+*  Name:
+*     AssocId
+
+*  Purpose:
+*     Associate an identifier value with a Handle.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *AssocId( int ihandle )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function takes a zero-based offset into the "handles" array
+*     that identifies a Handle associated with an active Object. It
+*     encodes this into an identifier value to be issued to an
+*     external user to identify that Handle and its Object, and then
+*     associates this identifier value with the Handle.
+
+*  Parameters:
+*     ihandle
+*        Offset in the "handles" array that identifies the Handle,
+*        which should be active (i.e. associated with an active
+*        Object). This function will modify the "check" field in this
+*        Handle to associate it with the identifier value it returns.
+
+*  Returned Value:
+*     The resulting identifier value.
+
+*  Notes:
+*     - The returned identifier value is, in fact, an int.  This
+*     allows the value to be passed easily to other languages
+*     (e.g. Fortran) and stored as an integer without loss of
+*     information.
+*     - The value is stored within C code as type AstObject*.  This
+*     means that C code (e.g. function prototypes, etc.) need not be
+*     aware that an identifier (as opposed to an Object pointer) is
+*     being used, even though the actual bit patterns will be
+*     different. The same C code can then be used for both internal
+*     and external purposes so long as care is taken to convert
+*     between the two representations at appropriate points.
+*     - The reverse operation (conversion of an ID back to a handle
+*     offset) is performed by CheckId.
+*     - A zero identifier value will be returned if this function is
+*     invoked with the global status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstObject *result;            /* Pointer value to return */
+   MixedInts test;               /* Union for testing encoding */
+   MixedInts work;               /* Union for encoding ID value */
+
+/* Initialise. */
+   result = astI2P( 0 );
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Copy the Handle offset and clear the lowest 8 bits by shifting
+   left. */
+   work.i = ihandle;
+   work.u = work.u << 8U;
+
+/* Make a copy of the result shifted right again. Test if any bits
+   have been lost in this process. If so, there are too many Handles
+   in use at once to encode them into IDs, so report an error. */
+   test.u = work.u >> 8U;
+   if ( test.i != ihandle ) {
+      astError( AST__XSOBJ, "AssocId(%s): There are too many AST Objects in "
+                "use at once.", status, astGetClass( handles[ ihandle ].ptr ) );
+
+/* If OK, scramble the value by exclusive-ORing with the bit pattern
+   in AST__FAC (a value unique to this library), also shifted left by
+   8 bits. This makes it even less likely that numbers from other
+   sources will be accepted in error as valid IDs. */
+   } else {
+      work.u ^= ( ( (unsigned) AST__FAC ) << 8U );
+
+/* Fill the lowest 8 bits with a count of the total number of IDs
+   issued so far (which we increment here). This makes each ID unique,
+   so that an old one that identifies a Handle that has been annulled
+   and re-used (i.e. associated with a new ID) can be spotted.  We
+   only use the lowest 8 bits of this count because this provides
+   adequate error detection to reveal programming errors and we do not
+   need higher security than this. We also prevent a count of zero
+   being used, as this could result in a zero identifier value (this
+   being reserved as the "null" value). */
+      if ( ++nids > 255U ) nids = 1U;
+      work.u |= nids;
+
+/* Store the value as a check count in the Handle. This will be used
+   to validate the ID in future. */
+      handles[ ihandle ].check = work.i;
+
+/* Pack the value into the pointer to be returned. */
+      result = astI2P( work.i );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astBegin_( void ) {
+/*
+*++
+*  Name:
+c     astBegin
+f     AST_BEGIN
+
+*  Purpose:
+*     Begin a new AST context.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     void astBegin
+f     CALL AST_BEGIN( STATUS )
+
+*  Class Membership:
+*     Object class function.
+
+*  Description:
+c     This macro invokes a function to begin a new AST context.
+c     Any Object pointers
+f     This routine begins a new AST context. Any Object pointers
+*     created within this context will be annulled when it is later
+c     ended using astEnd (just as if astAnnul had been invoked),
+f     ended using AST_END (just as if AST_ANNUL had been invoked),
+c     unless they have first been exported using astExport or rendered
+c     exempt using astExempt. If
+f     unless they have first been exported using AST_EXPORT or rendered
+f     exempt using AST_EXEMPT. If
+*     annulling a pointer causes an Object's RefCount attribute to
+*     fall to zero (which happens when the last pointer to it is
+*     annulled), then the Object will be deleted.
+
+f  Parameters:
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Object
+c        This macro applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Notes:
+c     - astBegin attempts to execute even if the AST error status
+c     is set on entry.
+f     - This routine attempts to execute even if STATUS is set to an
+f     error value.
+c     - Contexts delimited by astBegin and astEnd may be nested to any
+c     depth.
+f     - Contexts delimited by AST_BEGIN and AST_END may be nested to any
+f     depth.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   int stat;                     /* Copy of global status */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Now that the thread-specific data has been initialised, we can get a
+   pointer to the threads inherited status value. */
+   status = astGetStatusPtr;
+
+/* Save and clear the global status so that memory allocation can be
+   performed within this function even under error conditions. */
+   stat = astStatus;
+   astClearStatus;
+
+/* Ensure that the active_handles array has been initialised. */
+   if ( !active_handles ) InitContext( status );
+
+/* Extend the "active handles" array to accommodate a new context
+   level. This array contains integer offsets into the "handles" array
+   to identify the handle which is at the head of the list of active
+   handles for each context level. */
+   astBeginPM;
+   active_handles = astGrow( active_handles, context_level + 2,
+                             sizeof( int ) );
+   astEndPM;
+
+/* Initialise the array element for the new context level to indicate
+   an empty Handle list. */
+   if ( astOK ) active_handles[ ++context_level ] = -1;
+
+/* Restore the original global status value. */
+   astSetStatus( stat );
+}
+
+MYSTATIC int CheckId( AstObject *this_id, int lock_check, int *status ) {
+/*
+*  Name:
+*     CheckId
+
+*  Purpose:
+*     Check an identifier value and convert it into a Handle offset.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int CheckId( AstObject *this, int lock_check, int *status )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function takes an identifier value encoded by AssocId and
+*     validates it to ensure it is associated with an active
+*     Handle. If valid, it converts it back into a zero-based offset
+*     which may be used to access the Handle in the "handles"
+*     array. Otherwise, an error is reported.
+
+*  Parameters:
+*     this
+*        The identifier value to be decoded.
+*     lock_check
+*        Should an error be reported if the handle is in an Object
+*        context for a different thread?
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The resulting Handle offset, or -1 if the identifier is not
+*     valid or any other error occurs.
+
+*  Notes:
+*     - This function attempts to execute even if the global error
+*     status is set, but no further error report will be made if it
+*     fails under these circumstances.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   MixedInts work;               /* Union for decoding ID value */
+   int id;                       /* ID value as an int */
+   int ihandle;                  /* Result to return */
+
+#ifdef MEM_DEBUG
+   int oldok = astOK;
+#endif
+
+/* Initialise. */
+   ihandle = -1;
+
+/* Get a pointer to thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Retrieve the integer Object identifier value from the pointer
+   supplied. */
+   id = astP2I( this_id );
+
+/* Check if a value of zero has been supplied and report an error if
+   it has. */
+   if ( !id ) {
+      if ( astOK ) {
+         astError( AST__OBJIN, "Invalid Object pointer given (value is "
+                   "zero)." , status);
+      }
+
+/* If OK, reverse the encoding process performed by AssocId to
+   retrieve the Handle offset. */
+   } else {
+      work.i = id;
+      work.u = ( work.u ^ ( ( (unsigned) AST__FAC ) << 8U ) ) >> 8U;
+
+/* Check that the offset obtained doesn't extend beyond the limits of
+   the "handles" array. Report an error if it does. */
+      if ( ( work.i < 0 ) || ( work.i >= nhandles ) ) {
+         if ( astOK ) {
+            astError( AST__OBJIN, "Invalid Object pointer given (value is "
+                      "%d).", status, id );
+         }
+
+/* See if the "check" field matches the ID value and the Handle is
+   valid (i.e. is associated with an active Object). If not, the
+   Handle has been annulled and possibly re-used, so report an
+   error. */
+      } else if ( ( handles[ work.i ].check != id ) ||
+                  ( handles[ work.i ].context == INVALID_CONTEXT ) ) {
+         if ( astOK ) {
+            astError( AST__OBJIN, "Invalid Object pointer given (value is "
+                      "%d).", status, id  );
+            astError( AST__OBJIN, "This pointer has been annulled, or the "
+                      "associated Object deleted." , status);
+         }
+#if defined(THREAD_SAFE)
+      } else if(  lock_check && handles[ work.i ].context != UNOWNED_CONTEXT &&
+                                handles[ work.i ].thread != AST__THREAD_ID ) {
+         if ( astOK ) {
+            astError( AST__OBJIN, "Invalid Object pointer given (value is "
+                      "%d).", status, id  );
+            astError( AST__OBJIN, "This pointer is currently owned by "
+                      "another thread (possible programming error)." , status);
+         }
+#endif
+
+/* If OK, set the Handle offset to be returned. */
+      } else {
+         ihandle = work.i;
+      }
+
+#ifdef MEM_DEBUG
+      if ( oldok && !astOK && ( work.i >= 0 ) && ( work.i < nhandles ) ) {
+         char buf[200];
+         astError( astStatus, "Handle properties: %s ", status,
+                   HandleString( work.i, buf ) );
+      }
+#endif
+
+   }
+
+/* Return the result. */
+   return ihandle;
+}
+
+AstObject *astDeleteId_( AstObject *this_id, int *status ) {
+/*
+*+
+*  Name:
+*     astDeleteId
+
+*  Purpose:
+*     Delete an Object via an identifier.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astDeleteId_( AstObject *this )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astDelete method. It accepts an active Object identifier and
+*     deletes the associated Object. Before doing so, it annuls all
+*     active identifiers associated with the object, deactivates their
+*     Handles and returns these Handles to the free Handles list for
+*     re-use.
+
+*  Parameters:
+*     this
+*        An identifier for the Object to be deleted.
+
+*  Returned Value:
+*     A NULL C pointer is always returned (this should be translated
+*     into an identifier value of zero for external use).
+
+*  Notes:
+*     - This function attempts to execute even if the global error
+*     status is set.
+*     - The identifier supplied should be associated with an active
+*     Object, otherwise an error will result (but no error report will
+*     be made if the global error status has already been set).
+*     - Although this function is documented as "private" and should
+*     not be invoked directly from outside this class, it is not a
+*     static function and has a public prototype. This is because it
+*     must be invoked via the astDelete macro (defined in the
+*     "object.h" include file) as part of the public interface.
+*-
+*/
+
+/* Local Variables: */
+   AstObject *this;              /* Pointer to Object */
+   int i;                        /* Loop counter for Handles */
+   int ihandle;                  /* Object Handle offset */
+
+/* Obtain the Object pointer from the ID supplied and validate the
+   pointer to ensure it identifies a valid Object (this generates an
+   error if it doesn't). */
+   if ( !astIsAObject( this = astMakePointer( this_id ) ) ) return NULL;
+
+/* Gain exclusive access to the handles array. */
+   LOCK_MUTEX2;
+
+/* Obtain the Handle offset for this Object. */
+   ihandle = CheckId( this_id, 1, status );
+   if ( ihandle != -1 ) {
+
+/* Since the Object is to be deleted, we must annul all identifiers
+   that refer to it.  Loop to inspect each currently allocated Handle
+   in the "handles" array. */
+      for ( i = 0; i < nhandles; i++ ) {
+
+/* Select active handles and test if their Object pointer refers to
+   the Object to be deleted. */
+         if ( ( handles[ i ].context != INVALID_CONTEXT ) &&
+              ( handles[ i ].ptr == this ) ) {
+
+/* If so, explicitly set the reference count for the Object to 2 so
+   that it will not be deleted (yet) when we annul the pointer
+   associated with the Handle. */
+            handles[ i ].ptr->ref_count = 2;
+
+/* Annul the Handle, which frees its resources, decrements the Object
+   reference count and makes any ID associated with the Handle become
+   invalid. */
+            AnnulHandle( i, status );
+         }
+      }
+
+/* If required, tell the user that the handle's object has been deleted. */
+#ifdef MEM_DEBUG
+      astHandleUse( ihandle, "object-deleted" );
+#endif
+   }
+
+   UNLOCK_MUTEX2;
+
+/* When all Handles associated with the Object have been annulled,
+   delete the object itself. This over-rides the reference count and
+   causes any remaining pointers to the Object (e.g. in internal code
+   or within other Objects) to become invalid. */
+   this = astDelete( this );
+
+/* Always return a NULL pointer value. */
+   return NULL;
+}
+
+void astEnd_( int *status ) {
+/*
+*++
+*  Name:
+c     astEnd
+f     AST_END
+
+*  Purpose:
+*     End an AST context.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     void astEnd
+f     CALL AST_END( STATUS )
+
+*  Class Membership:
+*     Object class function.
+
+*  Description:
+c     This macro invokes a function to end an AST context which was
+f     This routine ends an AST context which was
+c     begun with a matching invocation of astBegin. Any Object
+f     begun with a matching invocation of AST_BEGIN. Any Object
+*     pointers created within this context will be annulled (just as
+c     if astAnnul had been invoked) and will cease to be valid
+f     if AST_ANNUL had been invoked) and will cease to be valid
+*     afterwards, unless they have previously been exported using
+c     astExport or rendered exempt using astExempt.
+f     AST_EXPORT or rendered exempt using AST_EXEMPT.
+*     If annulling a pointer causes an Object's RefCount attribute to
+*     fall to zero (which happens when the last pointer to it is
+*     annulled), then the Object will be deleted.
+
+*  Parameters:
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Object
+c        This macro applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Notes:
+c     - astEnd attempts to execute even if the AST error status is set.
+f     - This routine attempts to execute even if STATUS is set to an
+f     error value.
+c     - Contexts delimited by astBegin and astEnd may be nested to any
+c     depth.
+f     - Contexts delimited by AST_BEGIN and AST_END may be nested to any
+f     depth.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   int ihandle;                  /* Offset of Handle to be annulled */
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Check that the current context level is at least 1, otherwise there
+   has been no matching use of astBegin, so report an error (unless
+   the global status has already been set). */
+   if ( context_level < 1 ) {
+      if ( astOK ) {
+         astError( AST__ENDIN, "astEnd: Invalid use of astEnd without a "
+                   "matching astBegin." , status);
+      }
+
+/* If OK, loop while there are still active Handles associated with
+   the current context level. First gain exclusive access to the handles
+   array. */
+   } else if ( active_handles ) {
+      LOCK_MUTEX2;
+      while ( ( ihandle = active_handles[ context_level ] ) != -1 ) {
+
+/* Annul the Handle at the head of the active Handles list. */
+         AnnulHandle( ihandle, status );
+
+/* It is just posible that under error conditions inactive Handles
+   might get left in the active_handles list and AnnulHandle would
+   then fail. Since this would result in an infinite loop, we check to
+   see if the handle we have just annulled is still in the list. If
+   so, transfer it to the free Handles list for re-use. */
+         if ( ihandle == active_handles[ context_level ] ) {
+            RemoveHandle( ihandle, &active_handles[ context_level ], status );
+            InsertHandle( ihandle, &free_handles, status );
+         }
+      }
+
+/* Ensure the context level is decremented unless it was zero to start
+   with. */
+      context_level--;
+
+/* Relinquish access to the handles array. */
+      UNLOCK_MUTEX2;
+   }
+
+}
+
+void astExemptId_( AstObject *this_id, int *status ) {
+/*
+*++
+*  Name:
+c     astExempt
+f     AST_EXEMPT
+
+*  Purpose:
+*     Exempt an Object pointer from AST context handling.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     void astExempt( AstObject *this )
+f     CALL AST_EXEMPT( THIS, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function exempts an Object pointer from AST context
+f     This routine exempts an Object pointer from AST context
+c     handling, as implemented by astBegin and astEnd. This means that
+f     handling, as implemented by AST_BEGIN and AST_END. This means that
+c     the pointer will not be affected when astEnd is invoked and will
+f     the pointer will not be affected when AST_END is called and will
+*     remain active until the end of the program, or until explicitly
+c     annulled using astAnnul.
+f     annulled using AST_ANNUL.
+*
+c     If possible, you should avoid using this function when writing
+f     If possible, you should avoid using this routine when writing
+*     applications. It is provided mainly for developers of other
+*     libraries, who may wish to retain references to AST Objects in
+*     internal data structures, and who therefore need to avoid the
+c     effects of astBegin and astEnd.
+f     effects of AST_BEGIN and AST_END.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Object pointer to be exempted from context handling.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+*--
+
+*  Implementation Notes:
+*     - This function does not exist in the "protected" interface to
+*     the Object class and is not available to other class
+*     implementations.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   int context;                  /* Handle context level */
+   int ihandle;                  /* Offset of Handle in "handles" array */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Obtain the Object pointer from the ID supplied and validate the
+   pointer to ensure it identifies a valid Object. */
+   (void) astCheckObject( astMakePointer( this_id ) );
+   if ( astOK ) {
+
+/* Gain exclusive access to the handles array. */
+      LOCK_MUTEX2;
+
+/* Obtain the Handle offset for this Object. */
+      ihandle = CheckId( this_id, 1, status );
+      if ( ihandle != -1 ) {
+
+/* Extract the context level at which the Object was created. */
+         context = handles[ ihandle ].context;
+
+/* Set the new context level to zero, where it cannot be affected by
+   ending any context. */
+         handles[ ihandle ].context = 0;
+
+/* Remove the object's Handle from its original active Handles list
+   and insert it into the list appropriate to its new context
+   level. */
+
+#if defined(THREAD_SAFE)
+         if( context == UNOWNED_CONTEXT ) {
+            RemoveHandle( ihandle, &unowned_handles, status );
+         } else {
+            RemoveHandle( ihandle, &active_handles[ context ], status );
+         }
+#else
+         RemoveHandle( ihandle, &active_handles[ context ], status );
+#endif
+
+         InsertHandle( ihandle, &active_handles[ 0 ], status );
+
+/* If required, tell the user that the handle has been exempted. */
+#ifdef MEM_DEBUG
+         astHandleUse( ihandle, "exempted" );
+#endif
+      }
+
+      UNLOCK_MUTEX2;
+   }
+}
+
+void astExportId_( AstObject *this_id, int *status ) {
+/*
+*++
+*  Name:
+c     astExport
+f     AST_EXPORT
+
+*  Purpose:
+*     Export an Object pointer to an outer context.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     void astExport( AstObject *this )
+f     CALL AST_EXPORT( THIS, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function exports an Object pointer from the current AST context
+f     This routine exports an Object pointer from the current AST context
+*     into the context that encloses the current one. This means that
+*     the pointer will no longer be annulled when the current context
+c     is ended (with astEnd), but only when the next outer context (if
+f     is ended (with AST_END), but only when the next outer context (if
+*     any) ends.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Object pointer to be exported.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Notes:
+c     - It is only sensible to apply this function to pointers that
+f     - It is only sensible to apply this routine to pointers that
+*     have been created within (or exported to) the current context
+c     and have not been rendered exempt using astExempt.
+f     and have not been rendered exempt using AST_EXEMPT.
+*     Applying it to an unsuitable Object pointer has no effect.
+*--
+
+*  Implementation Notes:
+*     - This function does not exist in the "protected" interface to
+*     the Object class and is not available to other class
+*     implementations.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   int context;                  /* Handle context level */
+   int ihandle;                  /* Offset of Handle in "handles" array */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Obtain the Object pointer from the ID supplied and validate the
+   pointer to ensure it identifies a valid Object. */
+   (void) astCheckObject( astMakePointer( this_id ) );
+   if ( astOK ) {
+
+/* Gain exclusive access to the handles array. */
+      LOCK_MUTEX2;
+
+/* Obtain the Handle offset for this Object. */
+      ihandle = CheckId( this_id, 1, status );
+      if ( ihandle != -1 ) {
+
+/* Check that the current context level is at least 1 and report an
+   error if it is not. */
+         if ( context_level < 1 ) {
+            if( astOK ) astError( AST__EXPIN, "astExport(%s): Attempt to export an Object "
+                                  "from context level zero.", status,
+                                  astGetClass( handles[ ihandle ].ptr ) );
+
+/* Extract the context level at which the Object was created. */
+         } else {
+            context = handles[ ihandle ].context;
+
+/* Check that the Object's existing context level is high enough to be
+   affected by being exported to the next outer context level. If not,
+   do nothing. */
+            if ( context > ( context_level - 1 ) ) {
+
+/* Set the new context level. */
+               handles[ ihandle ].context = context_level - 1;
+
+/* Remove the object's Handle from its original active Handles list
+   and insert it into the list appropriate to its new context
+   level. */
+               RemoveHandle( ihandle, &active_handles[ context ], status );
+               InsertHandle( ihandle, &active_handles[ context_level - 1 ],
+                             status );
+
+/* If required, tell the user that the handle has been exempted. */
+#ifdef MEM_DEBUG
+               astHandleUse( ihandle, "exported from context level %d",
+                              context_level );
+#endif
+            }
+         }
+      }
+      UNLOCK_MUTEX2;
+   }
+}
+
+void astImportId_( AstObject *this_id, int *status ) {
+/*
+*++
+*  Name:
+c     astImport
+f     AST_IMPORT
+
+*  Purpose:
+*     Import an Object pointer to the current context.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     void astImport( AstObject *this )
+f     CALL AST_IMPORT( THIS, STATUS )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+c     This function
+f     This routine
+*     imports an Object pointer that was created in a higher or lower
+*     level context, into the current AST context.
+*     This means that the pointer will be annulled when the current context
+c     is ended (with astEnd).
+f     is ended (with AST_END).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Object pointer to be imported.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Object
+c        This function applies to all Objects.
+f        This routine applies to all Objects.
+
+*--
+
+*  Implementation Notes:
+*     - This function does not exist in the "protected" interface to
+*     the Object class and is not available to other class
+*     implementations.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   int context;                  /* Handle context level */
+   int ihandle;                  /* Offset of Handle in "handles" array */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Obtain the Object pointer from the ID supplied and validate the
+   pointer to ensure it identifies a valid Object. */
+   (void) astCheckObject( astMakePointer( this_id ) );
+   if ( astOK ) {
+
+/* Gain exclusive access to the handles array. */
+      LOCK_MUTEX2;
+
+/* Obtain the Handle offset for this Object. */
+      ihandle = CheckId( this_id, 1, status );
+      if ( ihandle != -1 ) {
+
+/* Extract the context level at which the Object was created. */
+         context = handles[ ihandle ].context;
+
+/* Do nothing if the Identifier already belongs to the current context. */
+         if( context != context_level ) {
+
+/* Set the new context level. */
+            handles[ ihandle ].context = context_level;
+
+/* Remove the object's Handle from its original active Handles list
+   and insert it into the list appropriate to its new context
+   level. */
+            RemoveHandle( ihandle, &active_handles[ context ], status );
+            InsertHandle( ihandle, &active_handles[ context_level ], status );
+
+/* If required, tell the user that the handle has been imported. */
+#ifdef MEM_DEBUG
+            astHandleUse( ihandle, "imported into context level %d",
+                           context_level );
+#endif
+         }
+      }
+      UNLOCK_MUTEX2;
+   }
+}
+
+void astLockId_( AstObject *this_id, int wait, int *status ) {
+/*
+c++
+*  Name:
+*     astLock
+
+*  Purpose:
+*     Lock an Object for exclusive use by the calling thread.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astLock( AstObject *this, int wait )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     The thread-safe public interface to AST is designed so that an
+*     error is reported if any thread attempts to use an Object that it
+*     has not previously locked for its own exclusive use using this
+*     function. When an Object is created, it is initially locked by the
+*     thread that creates it, so newly created objects do not need to be
+*     explicitly locked. However, if an Object pointer is passed to
+*     another thread, the original thread must first unlock it (using
+*     astUnlock) and the new thread must then lock it (using astLock)
+*     before the new thread can use the Object.
+*
+*     The "wait" parameter determines what happens if the supplied Object
+*     is curently locked by another thread when this function is invoked.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be locked.
+*     wait
+*        If the Object is curently locked by another thread then this
+*        function will either report an error or block. If a non-zero value
+*        is supplied for "wait", the calling thread waits until the object
+*        is available for it to use. Otherwise, an error is reported and
+*        the function returns immediately without locking the Object.
+
+*  Applicability:
+*     Object
+*        This function applies to all Objects.
+
+*  Notes:
+*     - The astAnnul function is exceptional in that it can be used on
+*     pointers for Objects that are not currently locked by the calling
+*     thread. All other AST functions will report an error.
+*     - The Locked object will belong to the current AST context.
+*     - This function returns without action if the Object is already
+*     locked by the calling thread.
+*     - If simultaneous use of the same object is required by two or more
+*     threads, astCopy should be used to to produce a deep copy of
+*     the Object for each thread. Each copy should then be unlocked by
+*     the parent thread (i.e. the thread that created the copy), and then
+*     locked by the child thread (i.e. the thread that wants to use the
+*     copy).
+*     - This function is only available in the C interface.
+*     - This function returns without action if the AST library has
+*     been built without POSIX thread support (i.e. the "-with-pthreads"
+*     option was not specified when running the "configure" script).
+c--
+*/
+
+/* This function odes nothing if thread support is not enabvled. */
+#if defined(THREAD_SAFE)
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   AstObject *fail;              /* Pointer to Object that failed to lock */
+   AstObject *this;              /* Pointer to Object */
+   int ihandle;                  /* Index of supplied objetc handle */
+   int lstat;                    /* Local status value */
+
+/* Obtain the Object pointer from the ID supplied and validate the
+   pointer to ensure it identifies a valid Object (this generates an
+   error if it doesn't). Note, we use the "astMakePointer_NoLockCheck",
+   since the usual "astMakePointer" macro invokes astCheckLock to report
+   an error if the Object is not currently locked by the calling thread. */
+   if ( !astIsAObject( this = astMakePointer_NoLockCheck( this_id ) ) ) return;
+
+/* Ensure the global data for this class is accessable. Do not use the
+   globals pointer stored in "*this" because "*this" may be locked by
+   another thread and so we would pick up the wrong globals. */
+   astGET_GLOBALS(NULL);
+
+/* Ensure the running thread has sole access to the static handles arrays. */
+   LOCK_MUTEX2;
+
+/* Ensure the Handles arrays have been initialised. */
+   if ( !active_handles ) InitContext( status );
+
+/* Get the Handle index for the supplied object identifier. No error is
+   reported if the handle is not curently associated with a thread.
+   However, an error is reported if the Handle is associated with any
+   thread other than the running thread. */
+   ihandle = CheckId( this_id, 0, status );
+
+/* We've finished with the handles arrays, for the moment. */
+   UNLOCK_MUTEX2;
+
+/* Check the object pointer was valid. */
+   if( ihandle != -1 ){
+
+/* The protected astManageLock function implements the public functions,
+   astLock and astUnlock. */
+      lstat = astManageLock( this, AST__LOCK, wait, &fail );
+      if( astOK ) {
+         if( lstat == 1 ) {
+            if( fail == this ) {
+               astError( AST__LCKERR, "astLock(%s): Failed to lock the %s because"
+                         " it is already locked by another thread (programming "
+                         "error).", status, astGetClass( this ),
+                         astGetClass( this ) );
+
+            } else {
+               astError( AST__LCKERR, "astLock(%s): Failed to lock the %s because"
+                         " a %s contained within it is already locked by another "
+                         "thread (programming error).", status,
+                         astGetClass( this ), astGetClass( this ),
+                         astGetClass( fail ) );
+            }
+
+         } else if( lstat == 2 ) {
+            astError( AST__LCKERR, "astLock(%s): Failed to lock a POSIX mutex.", status,
+                      astGetClass( this ) );
+
+/* If the Object is now locked for the running thread... */
+         } else {
+
+/* We need access to the handles arrays again. */
+            LOCK_MUTEX2;
+
+/* If the supplied handle is not currently assigned to any thread, assign
+   it to the running thread. */
+            if( handles[ ihandle ].context == UNOWNED_CONTEXT ) {
+               RemoveHandle( ihandle, &unowned_handles, status );
+
+#if defined(MEM_DEBUG)
+               astHandleUse( ihandle, "locked by thread %d at context level %d",
+                             handles[ ihandle ].thread, context_level );
+#endif
+
+               handles[ ihandle ].thread = AST__THREAD_ID;
+               handles[ ihandle ].context = context_level;
+               InsertHandle( ihandle, &active_handles[ context_level ],
+                                status );
+            }
+
+/* Finished with the handles arrays again. */
+            UNLOCK_MUTEX2;
+         }
+      }
+   }
+#endif
+}
+
+void astUnlockId_( AstObject *this_id, int report, int *status ) {
+/*
+c++
+*  Name:
+*     astUnlock
+
+*  Purpose:
+*     Unlock an Object for use by other threads.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astUnlock( AstObject *this, int report )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     Unlocks an Object previously locked using astLock, so that other
+*     threads can use the Object. See astLock for further details.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be unlocked.
+*     report
+*        If non-zero, an error will be reported if the supplied Object,
+*        or any Object contained within the supplied Object, is not
+*        currently locked by the running thread. If zero, such Objects
+*        will be left unchanged, and no error will be reported.
+
+*  Applicability:
+*     Object
+*        This function applies to all Objects.
+
+*  Notes:
+*     - This function attempts to execute even if the global error
+*     status is set, but no further error report will be made if it
+*     subsequently fails under these circumstances.
+*     - All unlocked Objects are excluded from AST context handling until
+*     they are re-locked using astLock.
+*     - This function is only available in the C interface.
+*     - This function returns without action if the Object is not currently
+*     locked by any thread. If it is locked by the running thread, it is
+*     unlocked. If it is locked by another thread, an error will be reported
+*     if "error" is non-zero.
+*     - This function returns without action if the AST library has
+*     been built without POSIX thread support (i.e. the "-with-pthreads"
+*     option was not specified when running the "configure" script).
+c--
+*/
+
+/* This function odes nothing if thread support is not enabvled. */
+#if defined(THREAD_SAFE)
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   AstErrorContext error_context;/* Info about the current error context */
+   AstObject *fail;              /* Pointer to Object that failed */
+   AstObject *this;              /* Pointer to Object */
+   int ihandle;                  /* Index of supplied objetc handle */
+   int lstat;                    /* Local status value */
+
+/* Obtain the Object pointer from the ID supplied and validate the
+   pointer to ensure it identifies a valid Object (this generates an
+   error if it doesn't). Note, we use the "astMakePointer_NoLockCheck",
+   since the usual "astMakePointer" macro invokes astCheckLock to report
+   an error if the Object is not currently locked by the calling thread. */
+   if ( !astIsAObject( this = astMakePointer_NoLockCheck( this_id ) ) ) return;
+
+/* Ensure the global data for this class is accessable. */
+   astGET_GLOBALS(this);
+
+/* Start a new error reporting context. This saves any existing error status
+   and then clear the status value. It also defer further error reporting. */
+   astErrorBegin( &error_context );
+
+/* Ensure the running thread has sole access to the static handles arrays. */
+   LOCK_MUTEX2;
+
+/* Ensure the Handles arrays have been initialised. */
+   if ( !active_handles ) InitContext( status );
+
+/* Get the Handle index for the supplied object identifier. Report an error
+   if the handle is not curently associated with the running thread. */
+   ihandle = CheckId( this_id, 1, status );
+
+/* Break the associated of the handle with the current thread so that the
+   handle is not assigned to any thread. We do this before unlocking the
+   Object structure (using astManageLock) since as soon as astManageLock
+   returns, another thread that is waiting for the object to be unlocked
+   may start up and modify the handle properties. */
+   if( ihandle >= 0 && handles[ ihandle ].context >= 0 ) {
+      RemoveHandle( ihandle, &active_handles[ handles[ ihandle ].context ],
+                    status );
+#if defined(MEM_DEBUG)
+      astHandleUse( ihandle, "unlocked from thread %d at context "
+                    "level %d", handles[ ihandle ].thread,
+                    handles[ ihandle ].context );
+#endif
+      handles[ ihandle ].thread = -1;
+      handles[ ihandle ].context = UNOWNED_CONTEXT;
+      InsertHandle( ihandle, &unowned_handles, status );
+   }
+
+/* We've finished with the handles arrays, for the moment. */
+   UNLOCK_MUTEX2;
+
+/* Check the supplied object pointer was valid. */
+   if( ihandle != -1 ){
+
+/* The protected astManageLock function implements the public functions,
+   astLock and astUnlock. */
+      lstat = astManageLock( this, AST__UNLOCK, 0, &fail );
+      if( astOK ) {
+         if( lstat == 1 ) {
+            if( report ) {
+               if( fail == this ) {
+                  astError( AST__LCKERR, "astUnlock(%s): Failed to unlock the %s "
+                            "because it is locked by another thread (programming "
+                            "error).", status, astGetClass( this ),
+                            astGetClass( this ) );
+
+               } else {
+                  astError( AST__LCKERR, "astUnlock(%s): Failed to unlock the %s "
+                            "because a %s contained within it is locked by another "
+                            "thread (programming error).", status,
+                            astGetClass( this ), astGetClass( this ),
+                            astGetClass( fail ) );
+               }
+            }
+
+         } else if( lstat == 3 ) {
+            astError( AST__LCKERR, "astUnlock(%s): Failed to unlock a POSIX mutex.", status,
+                      astGetClass( this ) );
+
+         }
+      }
+   }
+
+/* End the error reporting context. If an error has occurred within this
+   function, then this will display the deferred error messages so long
+   as there was no error condition on entry to this function. If there
+   was an error condition on entry, then the original status value will be
+   re-instated. */
+   astErrorEnd( &error_context );
+
+#endif
+}
+
+AstObject *astI2P_( int integer, int *status ) {
+/*
+*+
+*  Name:
+*     astI2P
+
+*  Purpose:
+*     Pack an integer Object ID into a pointer.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astI2P( int integer )
+
+*  Class Membership:
+*     Object class function.
+
+*  Description:
+*     This function accepts an integer (int) value representing an
+*     Object identifier and packs its bit pattern into a pointer value
+*     (from which it may subsequently be retrieved using astP2I).
+*
+*     These functions should be used to avoid any dependency on
+*     integer-to-pointer conversions (given that the values are not
+*     true pointers) which might affect the exchange of Object
+*     identifier values with other languages, such as Fortran 77,
+*     where they are stored as integers.
+
+*  Parameters:
+*     integer
+*        The integer value to be stored.
+
+*  Returned Value:
+*     The resulting pointer value (which is not usually a valid C pointer).
+
+*  Notes:
+*     - This function does not perform error checking and does not
+*     generate errors.
+*     - This is a protected function in the sense that it is not
+*     intended that external users should invoke it directly.  It is
+*     accessible from external code, however, in order that public
+*     macros invoked from that code can make use of it.
+*-
+*/
+
+/* Local Variables: */
+   IdUnion temp;                 /* Overlapping int and pointer */
+
+/* Clear the pointer value in the "temp" IdUnion and then set the
+   integer part of it to the value to be stored. */
+   temp.pointer = zero_ptr;
+   temp.integer = integer;
+
+/* Return the resulting pointer value. */
+   return temp.pointer;
+}
+
+MYSTATIC void InitContext( int *status ) {
+/*
+*  Name:
+*     InitContext
+
+*  Purpose:
+*     Initialise the first AST context level.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void InitContext( int *status )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function initialises the first AST context level (the level
+*     before any call to astBegin has been made). It should be invoked
+*     once, before any use is made of context levels.
+
+*  Parameters:
+*     status
+*        Pointer to the inherited status variable.
+
+*  Parameters:
+*     None.
+
+*  Notes:
+*     - This function does nothing after the first successful invocation.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Check that the active_handles array hasn't already been allocated. */
+   if ( !active_handles ) {
+
+/* Allocate and initialise the "active_handles" array. */
+
+      astBeginPM;
+      active_handles = astMalloc( sizeof( int ) );
+      astEndPM;
+
+      if ( astOK ) active_handles[ 0 ] = -1;
+   }
+}
+
+MYSTATIC void InsertHandle( int ihandle, int *head, int *status ) {
+/*
+*  Name:
+*     InsertHandle
+
+*  Purpose:
+*     Insert a Handle into a list.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void InsertHandle( int ihandle, int *head, int *status )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function inserts a Handle structure into a doubly linked
+*     list of such structures composed of elements drawn from the
+*     "handles" array. The new list member is inserted in front of the
+*     member previously occupying the "head of list" position.
+
+*  Parameters:
+*     ihandle
+*        Offset in the "handles" array that identifies the handle to
+*        be added to the list.
+*     head
+*        Address of an int which holds the offset in the "handles"
+*        array of the element at the head of the list (or -1 if the
+*        list is empty). This value will be updated to identify the
+*        new list member.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function does not perform error chacking and does not
+*     generate errors.
+*     - The lists generated by this function use integer offsets into
+*     the "handles" array for their links, rather than pointers. This
+*     is because the array may be re-located in memory when it needs
+*     to be extended, so pointers to its element would not remain
+*     valid.
+*     - The list elements are drawn from the "handles" array in the
+*     first place so that they can be addressed by small integers (the
+*     offset in the array). This allows references to Handles to be
+*     encoded along with security information into an integer that is
+*     sufficiently short to be exported to other languages
+*     (e.g. Fortran) which might not be able to accommodate
+*     full-length C pointers.
+*/
+
+
+#if defined(MEM_DEBUG)
+   char buf[80];
+   astHandleUse( ihandle, "about to be inserted into %s (%d)",
+                 HeadString( head, buf ), head );
+   CheckList( head, status );
+   CheckInList( ihandle, head, 0, status );
+#endif
+
+/* Check a head pointer was supplied (may not be if an error has
+   occurred). */
+   if( ! head ) return;
+
+/* If the list is empty, the sole new element points at itself. */
+   if ( *head == -1 ) {
+      handles[ ihandle ].flink = ihandle;
+      handles[ ihandle ].blink = ihandle;
+
+/* Otherwise, insert the new element in front of the element at the
+   head of the list. */
+   } else {
+      handles[ ihandle ].flink = *head;
+      handles[ ihandle ].blink = handles[ *head ].blink;
+      handles[ ( handles[ *head ].blink) ].flink = ihandle;
+      handles[ *head ].blink = ihandle;
+   }
+
+/* Update the list head to identify the new element. */
+   *head = ihandle;
+
+#if defined(MEM_DEBUG)
+   CheckList( head, status );
+   astHandleUse( ihandle, "has been inserted into %s", buf );
+#endif
+}
+
+AstObject *astMakeId_( AstObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astMakeId
+
+*  Purpose:
+*     Issue an identifier for an Object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astMakeId( AstObject *this )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function takes a normal C pointer to an Object and
+*     associates an identifier with it.
+
+*  Parameters:
+*     this
+*        Pointer to an Object. Note that this function copies this
+*        value (it is not cloned), so the caller should not annul the
+*        pointer afterwards.
+
+*  Returned Value:
+*     The resulting identifier value.
+
+*  Notes:
+*     - An identifier value of zero will be returned and the supplied
+*     Object pointer will be annulled if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*     - A zero identifier value will also be returned if a NULL object
+*     pointer is supplied, but this will not provoke an error.
+*     - This is a protected function in the sense that it is not
+*     intended that external users should invoke it directly.  It is
+*     accessible from external code, however, in order that public
+*     macros invoked from that code can make use of it.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Thread-specific global data */
+   AstObject *id;                /* ID value to return */
+   int ihandle;                  /* Handle offset */
+
+/* Initialise. */
+   id = astI2P( 0 );
+   ihandle = 0;
+
+/* Check the global error status. */
+   if ( astOK ) {
+
+/* Get a pointer to Thread-specific global data. */
+      astGET_GLOBALS(this);
+
+/* Gain exclusive access to the handles array. */
+      LOCK_MUTEX2;
+
+/* If a non-NULL Object pointer was given, we must obtain a Handle
+   structure to associate with it (otherwise a zero identifier value
+   is returned without error). */
+      if ( this ) {
+
+/* If the free Handles list is not empty, obtain a Handle from it. */
+         if ( free_handles != -1 ) {
+            ihandle = free_handles;
+            RemoveHandle( ihandle, &free_handles, status );
+
+/* Otherwise, allocate a new Handle by extending the "handles" array
+   and using the offset of the new element. */
+         } else {
+
+            astBeginPM;
+            handles = astGrow( handles, nhandles + 1, sizeof( Handle ) );
+            astEndPM;
+
+            if ( astOK ) {
+               ihandle = nhandles++;
+
+               handles[ ihandle ].ptr = NULL;
+               handles[ ihandle ].context = INVALID_CONTEXT;
+               handles[ ihandle ].check = 0;
+               handles[ ihandle ].flink = -1;
+               handles[ ihandle ].blink = -1;
+#if defined(THREAD_SAFE)
+               handles[ ihandle ].thread = 0;
+#endif
+
+#if defined(MEM_DEBUG)
+               handles[ ihandle ].id = 0;
+               handles[ ihandle ].vtab = NULL;
+#endif
+            }
+         }
+
+/* If the first AST context level has not yet been initialised, invoke
+   InitContext to initialise it and allocate memory for the
+   "active_handles" array which stores context information. */
+         if ( astOK ) {
+            if ( !active_handles ) InitContext( status );
+
+/* Store the Object pointer and current context level in the Handle. */
+            if ( astOK ) {
+               handles[ ihandle ].ptr = this;
+               handles[ ihandle ].context = context_level;
+#if defined(THREAD_SAFE)
+               handles[ ihandle ].thread = AST__THREAD_ID;
+#endif
+
+/* Store extra debugging information in the handle if enabled */
+#if defined(MEM_DEBUG)
+               handles[ ihandle ].id = astMemoryId( this );
+               handles[ ihandle ].vtab = this->vtab;
+               astHandleUse( ihandle, "associated with a %s (id %d)",
+                              astGetClass( this ), astMemoryId( this ));
+#endif
+
+/* Insert the Handle into the active Handles list for the current
+   context level. */
+               InsertHandle( ihandle, &active_handles[ context_level ], status );
+
+/* Associate an identifier value with the Handle. */
+               id = AssocId( ihandle, status );
+
+/* If an error occurred, clean up by annulling the Handle. This
+   ensures that the Object pointer is annulled and returns the unused
+   Handle to the Free Handle list. */
+               if ( !astOK ) {
+                  AnnulHandle( ihandle, status );
+                  this = NULL;
+               }
+
+/* If the Handle wasn't used (because of an earlier error), return it
+   to the free Handles list. */
+            } else {
+               InsertHandle( ihandle, &free_handles, status );
+            }
+         }
+      }
+      UNLOCK_MUTEX2;
+   }
+
+/* If a bad status value was either supplied or generated within this
+   function, then annul the supplied pointer (unless it is NULL). */
+   if ( !astOK && this ) (void) astAnnul( this );
+
+/* Return the identifier value. */
+   return id;
+}
+
+AstObject *astMakePointer_( AstObject *this_id, int *status ) {
+/*
+*+
+*  Name:
+*     astMakePointer
+
+*  Purpose:
+*     Obtain a true C pointer from an Object identifier.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astMakePointer( AstObject *this )
+
+*  Class Membership:
+*     Object class function.
+
+*  Description:
+*     This function accepts an external Object identifier and returns
+*     a true C Object pointer that may be de-referenced to access the
+*     Object's data and methods.
+
+*  Parameters:
+*     this
+*        The identifier value.
+
+*  Returned Value:
+*     The true C pointer value.
+
+*  Notes:
+*     - In a thread-safe context, an error is reported if the supplied
+*     Object has not been locked by the calling thread (using astLock)
+*     prior to invoking this function.
+*     - The object reference count is not modified by this function,
+*     so the returned pointer should not be annulled by the caller.
+*     - Typically, this function should be used whenever a public
+*     function must accept identifier values directly (rather than
+*     having them translated automatically into pointers during
+*     argument validation by the astCheck<Class> range of functions).
+*     - Note that this function will NOT accept a true C pointer as an
+*     argument, even when invoked from internal code (with astCLASS
+*     defined). An identifier value MUST be supplied, and an error
+*     will result if it is not associated with an active Object.
+*     - This function attempts to execute even if the global error
+*     status is set, but no further error report will be made if it
+*     subsequently fails under these circumstances.
+*     - This is a protected function in the sense that it is not
+*     intended that external users should invoke it directly.  It is
+*     accessible from external code, however, in order that public
+*     macros invoked from that code can make use of it.
+*-
+*/
+
+/* Local Variables: */
+   AstObject *ptr;               /* Pointer value to return */
+   int ihandle;                  /* Handle offset in "handles" array */
+
+/* Initialise. */
+   ptr = NULL;
+
+/* Gain exclusive access to the handles array. */
+   LOCK_MUTEX2;
+
+/* Validate the identifier supplied and derive the Handle offset. */
+   ihandle = CheckId( this_id, 1, status );
+
+/* If the identifier was valid, extract the Object pointer from the
+   Handle. */
+   if ( ihandle != -1 ) ptr = handles[ ihandle ].ptr;
+
+   UNLOCK_MUTEX2;
+
+/* Return the result. */
+   return ptr;
+}
+
+AstObject *astMakePointer_NoLockCheck_( AstObject *this_id, int *status ) {
+/*
+*+
+*  Name:
+*     astMakePointer_NoLockCheck
+
+*  Purpose:
+*     Obtain a true C pointer from an Object identifier.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *astMakePointer_NoLockCheck( AstObject *this )
+
+*  Class Membership:
+*     Object class function.
+
+*  Description:
+*     This function accepts an external Object identifier and returns
+*     a true C Object pointer that may be de-referenced to access the
+*     Object's data and methods.
+*
+*     It is identicial to astMakePointer except that it does not check
+*     that the supplied Object is locked by the running thread.
+
+*  Parameters:
+*     this
+*        The identifier value.
+
+*  Returned Value:
+*     The true C pointer value.
+
+*  Notes:
+*     - The object reference count is not modified by this function,
+*     so the returned pointer should not be annulled by the caller.
+*     - Typically, this function should be used whenever a public
+*     function must accept identifier values directly (rather than
+*     having them translated automatically into pointers during
+*     argument validation by the astCheck<Class> range of functions).
+*     - Note that this function will NOT accept a true C pointer as an
+*     argument, even when invoked from internal code (with astCLASS
+*     defined). An identifier value MUST be supplied, and an error
+*     will result if it is not associated with an active Object.
+*     - This function attempts to execute even if the global error
+*     status is set, but no further error report will be made if it
+*     subsequently fails under these circumstances.
+*     - This is a protected function in the sense that it is not
+*     intended that external users should invoke it directly.  It is
+*     accessible from external code, however, in order that public
+*     macros invoked from that code can make use of it.
+*-
+*/
+
+/* Local Variables: */
+   AstObject *ptr;               /* Pointer value to return */
+   int ihandle;                  /* Handle offset in "handles" array */
+
+/* Initialise. */
+   ptr = NULL;
+
+/* Gain exclusive access to the handles array. */
+   LOCK_MUTEX2;
+
+/* Validate the identifier supplied and derive the Handle offset. */
+   ihandle = CheckId( this_id, 0, status );
+
+/* If the identifier was valid, extract the Object pointer from the
+   Handle. */
+   if ( ihandle != -1 ) ptr = handles[ ihandle ].ptr;
+
+   UNLOCK_MUTEX2;
+
+/* Return the result. */
+   return ptr;
+}
+
+int astP2I_( AstObject *pointer, int *status ) {
+/*
+*+
+*  Name:
+*     astP2I
+
+*  Purpose:
+*     Extract an integer Object ID from a pointer.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int astP2I( AstObject *pointer )
+
+*  Class Membership:
+*     Object class function.
+
+*  Description:
+*     This function retrieves an integer (int) value representing an
+*     Object identifier from a pointer value (into which it has
+*     previously been packed using astI2P).
+*
+*     These functions should be used to avoid any dependency on
+*     integer-to-pointer conversions (given that the values are not
+*     true pointers) which might affect the exchange of Object
+*     identifier values with other languages, such as Fortran 77,
+*     where they are stored as integers.
+
+*  Parameters:
+*     pointer
+*       The pointer value into which the ID has previously been packed.
+
+*  Returned Value:
+*     The extracted Object identifier value as an integer (int).
+
+*  Notes:
+*     - This function does not perform error checking and does not
+*     generate errors.
+*     - This is a protected function in the sense that it is not
+*     intended that external users should invoke it directly.  It is
+*     accessible from external code, however, in order that public
+*     macros invoked from that code can make use of it.
+*-
+*/
+
+/* Local Variables: */
+   IdUnion temp;                 /* Overlapping int and pointer */
+
+/* Store the pointer value supplied. */
+   temp.pointer = pointer;
+
+/* Return the integer part of it. */
+   return temp.integer;
+}
+
+MYSTATIC void RemoveHandle( int ihandle, int *head, int *status ) {
+/*
+*  Name:
+*     RemoveHandle
+
+*  Purpose:
+*     Remove a Handle from a list.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void RemoveHandle( int ihandle, int *head )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function removes a Handle structure from a doubly linked
+*     list of such structures composed of elements drawn from the
+*     "handles" array. The "head of list" position is updated if
+*     necessary.
+
+*  Parameters:
+*     ihandle
+*        Offset in the "handles" array that identifies the Handle to
+*        be removed from the list (note that the Handle must actually
+*        be in the list, although this function does not check this).
+*     head
+*        Address of an int which holds the offset in the "handles"
+*        array of the element at the head of the list. This value will
+*        be updated if necessary to identify the new list head (or set
+*        to -1 if removing the Handle causes the list to become
+*        empty).
+
+*  Notes:
+*     - This function does not perform error chacking and does not
+*     generate errors.
+*     - See the InsertHandle function for details of how and why lists
+*     of Handles are constructed.
+*/
+
+
+#if defined(MEM_DEBUG)
+   char buf[80];
+   astHandleUse( ihandle, "about to be removed from %s", HeadString( head, buf ) );
+   CheckList( head, status );
+   CheckInList( ihandle, head, 1, status );
+#endif
+
+/* Check a head pointer was supplied (may not be if an error has
+   occurred). */
+   if( ! head ) return;
+
+/* Remove the Handle from the list by re-establishing links between
+   the elements on either side of it. */
+   handles[ ( handles[ ihandle ].blink ) ].flink = handles[ ihandle ].flink;
+   handles[ ( handles[ ihandle ].flink ) ].blink = handles[ ihandle ].blink;
+
+/* If the element removed was at the head of the list, update the head
+   of list offset to identify the following element. */
+   if ( ihandle == *head ) {
+      *head = handles[ ihandle ].flink;
+
+/* If the head of list still identifies the removed element, then note
+   that the list is now empty. */
+      if ( *head == ihandle ) *head = -1;
+   }
+
+/* Make the removed element point at itself. */
+   handles[ ihandle ].flink = ihandle;
+   handles[ ihandle ].blink = ihandle;
+
+#if defined(MEM_DEBUG)
+   astHandleUse( ihandle, "has been removed from %s", buf );
+   CheckList( head, status );
+#endif
+}
+
+void astSetId_( void *this_id_void, const char *settings, ... ) {
+/*
+*  Name:
+*     astSetId_
+
+*  Purpose:
+*     Set values for an Object's attributes via an identifier.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     void astSetId_( AstObject *this, const char *settings, ... )
+
+*  Class Membership:
+*     Object member function.
+
+*  Description:
+*     This function implements the axternal interface to the astSet
+*     method (q.v.). It accepts an Object identifier, but otherwise
+*     behaves identically to astSet.
+
+*  Parameters:
+*     this
+*        Object identifier.
+*     settings
+*        Pointer to a null-terminated string containing a
+*        comma-separated list of attribute settings.
+*     ...
+*        Optional arguments which supply values to be substituted for
+*        any "printf"-style format specifiers that appear in the
+*        "settings" string.
+
+*  Notes:
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     Object identifier is of type (void *) and is converted and
+*     validated within the function itself.
+*/
+
+/* Local Variables: */
+   AstObject *this;              /* Pointer to the Object structure */
+   int *status;                  /* Pointer to inherited status variable */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the integer holding the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain the Object pointer from the ID supplied and validate the
+   pointer to ensure it identifies a valid Object. */
+   this = astCheckObject( astMakePointer( this_id_void ) );
+   if ( astOK ) {
+
+/* Obtain the variable argument list and pass all arguments to the
+   astVSet method for interpretation. */
+      va_start( args, settings );
+      astVSet( this, settings, NULL, args );
+      va_end( args );
+   }
+}
+
+int astThreadId_( AstObject *this_id, int ptr, int *status ) {
+/*
+c++
+*  Name:
+*     astThread
+
+*  Purpose:
+*     Determine the thread that owns an Object.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "object.h"
+*     int astThread( AstObject *this, int ptr )
+
+*  Class Membership:
+*     Object method.
+
+*  Description:
+*     Returns an integer that indicates whether the supplied Object (or
+*     Object pointer) is currently unlocked, or is currently locked by
+*     the running thread, or another thread.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be checked.
+*     ptr
+*        If non-zero, returns information about the supplied Object
+*        pointer, rather than the Object structure itself. See "Object
+*        Pointers and Structures" below.
+
+*  Returned Value:
+*     astThread()
+*        A value of AST__UNLOCKED is returned if the Object (or pointer)
+*        is currently unlocked (i.e. has been unlocked using astUnlock
+*        but has not yet been locked using astLock). A value of
+*        AST__RUNNING is returned if the Object (or pointer) is currently
+*        locked by the running thread. A value of AST__OTHER is returned
+*        if the Object (or pointer) is currently locked by the another
+*        thread.
+
+*  Object Pointers and Structures:
+*     At any one time, an AST Object can have several distinct pointers,
+*     any one of which can be used to access the Object structure. For
+*     instance, the astClone function will produce a new distinct pointer
+*     for a given Object. In fact, an AST "pointer" is not a real pointer
+*     at all - it is an identifier for a "handle" structure, encoded to
+*     make it look like a pointer. Each handle contains (amongst othere
+*     things) a "real" pointer to the Object structure. This allows more
+*     than one handle to refer to the same Object structure. So when you
+*     call astClone (for instance) you get back an identifier for a new
+*     handle that refers to the same Object as the supplied handle.
+*
+*     In order to use an Object for anything useful, it must be locked
+*     for use by the running thread (either implicitly at creation or
+*     explicitly using astLock). The identity of the thread is stored in
+*     both the Object structure, and in the handle that was passed to
+*     astLock (or returned by the constructor function). Thus it is
+*     possible for a thread to have active pointers for Objects that are
+*     currently locked by another thread. In general, if such a pointer is
+*     passed to an AST function an error will be reported indicating that
+*     the Object is currently locked by another thread. The two exceptions
+*     to this is that astAnnul can be used to annull such a pointer, and
+*     this function can be used to return information about the pointer.
+*
+*     The other practical consequence of this is that when astEnd is
+*     called, all active pointers currently owned by the running thread
+*     (at the current context level) are annulled. This includes pointers
+*     for Objects that are currently locked by other threads.
+*
+*     If the "ptr" parameter is zero, then the returned value describes
+*     the Object structure itself. If "ptr" is non-zero, then the returned
+*     value describes the supplied Object pointer (i.e. handle), rather
+*     than the Object structure.
+
+*  Notes:
+*     - This function attempts to execute even if the global error
+*     status is set, but no further error report will be made if it
+*     subsequently fails under these circumstances.
+*     - This function is only available in the C interface.
+*     - This function always returns AST__RUNNING if the AST library has
+*     been built without POSIX thread support (i.e. the "-with-pthreads"
+*     option was not specified when running the "configure" script).
+c--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS       /* Thread-specific global data */
+   int result;              /* The returned value */
+
+#if defined(THREAD_SAFE)
+
+/* More local Variables: */
+   AstObject *this;
+   int ihandle;
+   int check;
+
+/* Ensure global variables are accessable. */
+   astGET_GLOBALS(NULL);
+#endif
+
+/* Initialise the returned value */
+   result = AST__RUNNING;
+
+/* Nothing more to do if AST was not build with thread support. */
+#if defined(THREAD_SAFE)
+
+/* If the ownership of the handle is being queried... */
+   if( ptr ) {
+
+/* Lock the mutex that guards access to the handles array */
+      LOCK_MUTEX2;
+
+/* Check the supplied object identifier is valid and get the
+   corresponding index into the handles array. */
+      ihandle = CheckId( this_id, 1, status );
+      if( ihandle != -1 ) {
+
+/* Set the returned value on the basis of the threa didentifier stored in
+   the handle structure. */
+         if( handles[ ihandle ].thread == -1 ) {
+            result = AST__UNLOCKED;
+         } else if( handles[ ihandle ].thread != AST__THREAD_ID ) {
+            result = AST__OTHER;
+         }
+      }
+
+/* Unlock the mutex that guards access to the handles array */
+      UNLOCK_MUTEX2;
+
+/* Otherwise, the ownership of the Object is being queried. Obtain the
+   Object pointer from the ID supplied and validate the pointer to ensure
+   it identifies a valid Object (this generates an error if it doesn't).
+   Note, we use the "astMakePointer_NoLockCheck", since the usual
+   "astMakePointer" macro invokes astCheckLock to report an error if the
+   Object is not currently locked by the calling thread. */
+   } else if( astIsAObject( this = astMakePointer_NoLockCheck( this_id ) ) ) {
+
+/* Determine which thread (if any) has the object locked, and set an
+   appropriate return value. */
+      check = astManageLock( this, AST__CHECKLOCK, 0, NULL );
+
+      if( check == 5 ) {
+         result = AST__OTHER;
+      } else if( check == 6 ) {
+         result = AST__UNLOCKED;
+      }
+   }
+
+#endif
+
+/* Return the result. */
+   return result;
+}
+
+int astVersion_( int *status ) {
+/*
+*++
+*  Name:
+c     astVersion
+f     AST_VERSION
+
+*  Purpose:
+*     Return the version of the AST library being used.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     int astVersion
+f     RESULT = AST_VERSION()
+
+*  Class Membership:
+*     Object class function.
+
+*  Description:
+c     This macro invokes a function which
+f     This function
+*     returns an integer representing the version of the AST library
+*     being used. The library version is formatted as a string such as
+*     "2.0-7" which contains integers representing the "major version" (2),
+*     the "minor version" (0) and the "release" (7). The integer returned
+*     by this function combines all three integers together into a single
+*     integer using the expresion:
+*
+*     (major version)*1E6 + (minor version)*1E3 + (release)
+
+*  Returned Value:
+c     astVersion
+f     AST_VERSION = INTEGER
+*        The major version, minor version and release numbers for the AST
+*        library, encoded as a single integer.
+
+*  Applicability:
+*     Object
+c        This macro applies to all Objects.
+f        This routine applies to all Objects.
+
+*--
+*/
+
+   return (int) AST__VMAJOR*1E6 + AST__VMINOR*1E3 + AST__RELEASE;
+}
+
+int astEscapes_( int new_value, int *status ) {
+/*
+*++
+*  Name:
+c     astEscapes
+f     AST_ESCAPES
+
+*  Purpose:
+*     Control whether graphical escape sequences are included in strings.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "object.h"
+c     int astEscapes( int new_value )
+f     RESULT = AST_ESCAPES( NEWVAL, STATUS )
+
+*  Class Membership:
+*     Object class function.
+
+*  Description:
+*     The Plot class defines a set of escape sequences which can be
+*     included within a text string in order to control the appearance of
+*     sub-strings within the text. See the Escape attribute for a
+*     description of these escape sequences. It is usually inappropriate
+*     for AST to return strings containing such escape sequences when
+*     called by application code. For instance, an application which
+*     displays the value of the Title attribute of a Frame usually does
+*     not want the displayed string to include potentially long escape
+*     sequences which a human read would have difficuly interpreting.
+*     Therefore the default behaviour is for AST to strip out such escape
+*     sequences when called by application code. This default behaviour
+*     can be changed using this function.
+
+*  Parameters:
+c     new_value
+f     NEWVAL = INTEGER (Given)
+*        A flag which indicates if escapes sequences should be included
+*        in returned strings. If zero is supplied, escape sequences will
+*        be stripped out of all strings returned by any AST function. If
+*        a positive value is supplied, then any escape sequences will be
+*        retained in the value returned to the caller. If a negative
+*        value is supplied, the current value of the flag will be left
+*        unchanged.
+
+*  Returned Value:
+c     astEscapes
+f     AST_ESCAPES = INTEGER
+*        The value of the flag on entry to this function.
+
+*  Applicability:
+*     Object
+c        This macro applies to all Objects.
+f        This routine applies to all Objects.
+
+*  Notes:
+*     - This function also controls whether the
+c     astStripEscapes
+f     AST_STRIPESCAPES
+*     function removes escape sequences from the supplied string, or
+*     returns the supplied string without change.
+*     - This function attempts to execute even if an error has already
+*     occurred.
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   int old_val;
+
+/* Get a pointer to Thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Save the old value. */
+   old_val = retain_esc;
+
+/* Set the new value. */
+   if( new_value > 0 ) {
+      retain_esc = 1;
+
+   } else if( new_value == 0 ) {
+      retain_esc = 0;
+   }
+
+/* Return the old value. */
+   return old_val;
+}
+
+
+#if defined(MEM_DEBUG)
+
+/* Check each handle in a list is uniquely connected to one other handle
+   in both the forward and backward directions. */
+
+void CheckList( int *head, int *status ) {
+   int ok;
+   int ihandle;
+   char buf[200];
+   astDECLARE_GLOBALS
+   if( !astOK ) return;
+
+   astGET_GLOBALS(NULL);
+
+   ok = 1;
+   if ( *head != -1 ) {
+      ihandle = *head;
+      if( handles[ handles[ ihandle ].blink ].flink != ihandle ||
+          handles[ handles[ ihandle ].flink ].blink != ihandle ) {
+         ok = 0;
+
+      } else {
+         if( CheckThread( ihandle, head, status ) ) {
+            ihandle= handles[ *head ].blink;
+            while( ihandle != *head ) {
+               if( handles[ handles[ ihandle ].blink ].flink != ihandle ||
+                   handles[ handles[ ihandle ].flink ].blink != ihandle ||
+                   CheckThread( ihandle, head, status ) == 0 ) {
+                  ok = 0;
+                  break;
+               }
+               ihandle= handles[ ihandle ].blink;
+            }
+         }
+      }
+   }
+
+   if( !ok ) {
+      printf("CheckList error in %s\n", HeadString( head, buf ) );
+      printf("   Central handle: %s\n", HandleString( ihandle, buf ) );
+
+      if( handles[ handles[ ihandle ].blink ].flink != ihandle ) {
+         printf("   Central handle->blink: %s\n",
+                 HandleString( handles[ ihandle ].blink, buf ) );
+         printf("   Central handle->blink->flink: %s\n",
+                 HandleString( handles[ handles[ ihandle ].blink ].flink, buf ) );
+      }
+
+      if( handles[ handles[ ihandle ].flink ].blink != ihandle ) {
+         printf("   Central handle->flink: %s\n",
+                 HandleString( handles[ ihandle ].flink, buf ) );
+         printf("   Central handle->flink->blink: %s\n",
+                 HandleString( handles[ handles[ ihandle ].flink ].blink, buf ) );
+      }
+   }
+
+}
+
+
+/* Check if a specified handle is, or is not, in a given list of handles. */
+
+void CheckInList( int ihandle, int *head, int in, int *status ){
+   char list[80], buf[200];
+   int found = 0;
+   if( !astOK ) return;
+
+   if ( *head != -1 ) {
+      if( ihandle == *head ) {
+         found = 1;
+      } else {
+         if( CheckThread( ihandle, head, status ) ) {
+            int jhandle= handles[ *head ].blink;
+            while( jhandle != *head ) {
+               if( ihandle == jhandle ) {
+                  found = 1;
+                  break;
+               }
+               jhandle= handles[ jhandle ].blink;
+            }
+         }
+      }
+   }
+
+   if( in && !found ) {
+      printf("Error: Handle %s not in %s\n", HandleString( ihandle, buf ),
+              HeadString( head, list ) );
+   } else if( !in && found ) {
+      printf("Error: Handle %s is in %s\n", HandleString( ihandle, buf ),
+              HeadString( head, list ) );
+   }
+
+}
+
+/* Check that a handle is owned by the currently executing thread. */
+
+int CheckThread( int ihandle, int *head, int *status ) {
+   int result = 1;
+
+#if defined(THREAD_SAFE)
+
+   char buf[200];
+   astDECLARE_GLOBALS
+   if( !astOK ) return result;
+
+   astGET_GLOBALS(NULL);
+
+   if( *head == unowned_handles  ) {
+      if(  handles[ ihandle ].thread != -1 ) {
+         printf("Handle %s has wrong thread: is %d, should "
+             "be -1 (i.e. unowned)\n", HandleString( ihandle, buf ),
+              handles[ ihandle ].thread );
+
+         result = 0;
+      }
+
+   } else if( *head == free_handles ) {
+      if(  handles[ ihandle ].thread != -1 ) {
+         printf("Handle %s has wrong thread: is %d, should "
+             "be -1 (i.e. free)\n", HandleString( ihandle, buf ),
+              handles[ ihandle ].thread );
+         result = 0;
+      }
+
+   } else if( handles[ ihandle ].thread != AST__THREAD_ID ) {
+      printf("Handle %s has wrong thread: is %d, should "
+             "be %d\n", HandleString( ihandle, buf ),
+              handles[ ihandle ].thread, AST__THREAD_ID );
+      result = 0;
+   }
+
+#endif
+
+   return result;
+}
+
+void astWatchHandle_( int handle ){
+   Watched_Handle = handle;
+}
+
+void astHandleUse_( int handle, const char *verb, ... ){
+   va_list args;
+   if( handle == Watched_Handle ) {
+      va_start( args, verb );
+      astHandleAlarm( verb, args );
+      va_end( args );
+   }
+}
+
+void astHandleAlarm_( const char *verb, va_list args ){
+   char buff[200], hbuf[200];
+   astDECLARE_GLOBALS
+   astGET_GLOBALS(NULL);
+
+   vsprintf( buff, verb, args );
+
+#if defined(THREAD_SAFE)
+   printf( "astHandleAlarm: Handle %s %s (current thread is %d).\n\n",
+           HandleString( Watched_Handle, hbuf ), buff, AST__THREAD_ID );
+#else
+   printf( "astHandleAlarm: Handle %s %s.\n\n",
+           HandleString( Watched_Handle, hbuf ), buff );
+#endif
+}
+
+MYSTATIC const char *HandleString( int ihandle, char *buf ){
+#if defined(THREAD_SAFE)
+   astDECLARE_GLOBALS
+   astGET_GLOBALS(NULL);
+
+   if( ihandle >= 0 ) {
+      sprintf( buf, "(index:%d v:%d c:%d t:%d i:%d cl:%s) [cur. thread: %d]",
+               ihandle,
+               handles[ ihandle ].check,
+               handles[ ihandle ].context, handles[ ihandle ].thread,
+               handles[ ihandle ].id,
+               handles[ ihandle ].vtab ? handles[ ihandle ].vtab->class : "<none>",
+               AST__THREAD_ID );
+   } else {
+      sprintf( buf, "(index:%d <invalid>) [cur. thread: %d]", ihandle,
+               AST__THREAD_ID );
+   }
+
+#else
+   if( ihandle >= 0 ) {
+      sprintf( buf, "(index:%d v:%d c:%d i:%d cl:%s)", ihandle,
+               handles[ ihandle ].check,
+               handles[ ihandle ].context, handles[ ihandle ].id,
+               handles[ ihandle ].vtab ? handles[ ihandle ].vtab->class : "<none>" );
+   } else {
+      sprintf( buf, "(index:%d <invalid>)", ihandle );
+   }
+#endif
+   return buf;
+}
+
+MYSTATIC const char *HeadString( int *head, char *list ){
+   int i;
+   astDECLARE_GLOBALS
+   astGET_GLOBALS(NULL);
+
+   if( head == &free_handles ) {
+      strcpy( list, "free_handles" );
+
+#if defined(THREAD_SAFE)
+   } else if( head == &unowned_handles ) {
+      strcpy( list, "unowned_handles" );
+#endif
+
+   } else {
+      *list = 0;
+      for( i = 0; i <= context_level; i++ ) {
+         if( *head == active_handles[ i ] ) {
+            sprintf( list, "active_handles[%d]", i );
+            break;
+         }
+      }
+      if( *list == 0 ) sprintf( list, "unknown handles list with head %d",
+                                *head );
+   }
+   return list;
+}
+
+#endif
diff --git a/ast/object.h b/ast/object.h
new file mode 100644
index 0000000..b2c2d1f
--- /dev/null
+++ b/ast/object.h
@@ -0,0 +1,1934 @@
+#if !defined( OBJECT_INCLUDED )  /* Include this file only once */
+#define OBJECT_INCLUDED
+/*
+*++
+*  Name:
+*     object.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Object class.
+
+*  Invocation:
+*     #include "object.h"
+
+*  Description:
+*     This include file defines the interface to the Object class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+
+*     The Object class is the base class from which all other classes
+*     in the AST library are derived. This class provides all the
+*     basic Object behaviour and Object manipulation facilities
+*     required throughout the library. There is no Object constructor,
+*     however, as Objects on their own are not of much use.
+
+*  Inheritance:
+*     The Object base class does not inherit from any other class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     Class (string)
+*        This is a read-only attribute containing the name of the
+*        class to which an Object belongs.
+*     ID (string)
+*        An identification string which may be used to identify the
+*        Object (e.g.) in debugging output, or when stored in an
+*        external medium such as a data file. There is no restriction
+*        on the string's contents. The default is an empty string.
+*     Ident (string)
+*        Like ID, this is an identification string which may be used
+*        to identify the Object. Unlike ID, Ident is transferred when an
+*        Object is copied.
+*     UseDefs (int)
+*        Should default values be used for unset attributes?
+*     Nobject (integer)
+*        This is a read-only attribute which gives the total number of
+*        Objects currently in existence in the same class as the
+*        Object given. It does not include Objects which belong to
+*        derived (more specialised) classes. This value is mainly
+*        intended for debugging, as it can be used to show whether
+*        Objects which should have been deleted have, in fact, been
+*        deleted.
+*     ObjSize (int)
+*        The in-memory size of the Object in bytes.
+*     RefCount (integer)
+*        This is a read-only Attribute which gives the "reference
+*        count" (the number of active pointers) associated with an
+*        Object. It is modified whenever pointers are created or
+*        annulled (by astClone or astAnnul/astEnd for example) and
+*        includes the initial pointer issued when the Object was
+*        created. If the reference count for an Object falls to zero
+*        as the result of annulling a pointer to it, then the Object
+*        will be deleted.
+
+*  Methods Over-Ridden:
+*     None.
+
+*  New Methods Defined:
+*     Public:
+*        astAnnul
+*           Annul a pointer to an Object.
+*        astClear
+*           Clear attribute values for an Object.
+*        astClone
+*           Clone a pointer to an Object.
+*        astCopy
+*           Copy an Object.
+*        astDelete
+*           Delete an Object.
+*        astExempt
+*           Exempt an Object pointer from AST context handling
+*        astExport
+*           Export an Object pointer to an outer context.
+*        astGet<X>, where <X> = C, D, F, I, L
+*           Get an attribute value for an Object.
+*        astImport
+*           Import an Object pointer into the current context.
+*        astSame
+*           Return true if two pointers refer to the same object.
+*        astSet
+*           Set attribute values for an Object.
+*        astSet<X>, where <X> = C, D, F, I, L
+*           Set an attribute value for an Object.
+*        astShow
+*           Display a textual representation of an Object on standard output.
+*        astTest
+*           Test if an attribute value is set for an Object.
+*        astTune
+*           Get or set the value of a global AST tuning parameter.
+*
+*     Protected:
+*        astAnnulId
+*           Annul an external ID for an Object (for use from protected code
+*           which must handle external IDs).
+*        astClearAttrib
+*           Clear the value of a specified attribute for an Object.
+*        astClearID
+*           Clear the value of the ID attribute for an Object.
+*        astClearIdent
+*           Clear the value of the Ident attribute for an Object.
+*        astCast
+*           Return a deep copy of an object, cast into an instance of a
+*           parent class.
+*        astDump
+*           Write an Object to a Channel.
+*        astEqual
+*           Are two Objects equivalent?
+*        astGetAttrib
+*           Get the value of a specified attribute for an Object.
+*        astGetClass (deprecated synonym astClass)
+*           Obtain the value of the Class attribute for an Object.
+*        astGetID
+*           Obtain the value of the ID attribute for an Object.
+*        astGetIdent
+*           Obtain the value of the Ident attribute for an Object.
+*        astGetNobject
+*           Obtain the value of the Nobject attribute for an Object.
+*        astGetRefCount
+*           Obtain the value of the RefCount attribute for an Object.
+*        astSetAttrib
+*           Set the value of a specified attribute for an Object.
+*        astSetCopy
+*           Declare a copy constructor for an Object.
+*        astSetDelete
+*           Declare a destructor for an Object.
+*        astSetDump
+*           Declare a dump function for an Object.
+*        astSetVtab
+*           Chaneg the virtual function table associated with an Object.
+*        astSetID
+*           Set the value of the ID attribute for an Object.
+*        astSetIdent
+*           Set the value of the Ident attribute for an Object.
+*        astTestAttrib
+*           Test if a specified attribute value is set for an Object.
+*        astTestID
+*           Test whether the ID attribute for an Object is set.
+*        astTestIdent
+*           Test whether the Ident attribute for an Object is set.
+*        astVSet
+*           Set values for an Object's attributes.
+
+*  Other Class Functions:
+*     Public:
+*        astBegin
+*           Begin a new AST context.
+*        astEnd
+*           End an AST context.
+*        astIsAObject
+*           Test class membership.
+*        astVersion
+*           Returns the AST library version number.
+*        astEscapes
+*           Remove escape sequences from returned text strings?
+*        astP2I
+*           Retrieve an int from a pointer.
+*        astI2P
+*           Pack an int into a pointer.
+*
+*     Protected:
+*        astCheckObject
+*           Validate class membership.
+*        astInitObject
+*           Initialise an Object.
+*        astInitObjectVtab
+*           Initialise the virtual function table for the Object class.
+*        astLoadObject
+*           Load an Object.
+*        astMakeId
+*           Issue an identifier for an Object.
+*        astMakePointer
+*           Obtain a true C pointer from an Object identifier.
+
+*  Macros:
+*     Public:
+*        AST__NULL
+*           Null Object pointer value.
+*        AST__VMAJOR
+*           The AST library major version number.
+*        AST__VMINOR
+*           The AST library minor version number.
+*        AST__RELEASE
+*           The AST library release number.
+*
+*     Protected:
+*        astEQUAL
+*           Compare two doubles for equality.
+*        astMAX
+*           Return maximum of two values.
+*        astMIN
+*           Return minimum of two values.
+*        astMAKE_CHECK
+*           Implement the astCheck<Class>_ function for a class.
+*        astMAKE_CLEAR
+*           Implement a method to clear an attribute value for a class.
+*        astMAKE_GET
+*           Implement a method to get an attribute value for a class.
+*        astMAKE_ISA
+*           Implement the astIsA<Class>_ function for a class.
+*        astMAKE_SET
+*           Implement a method to set an attribute value for a class.
+*        astMAKE_TEST
+*           Implement a method to test if an attribute has been set for a
+*           class.
+*        astMEMBER
+*           Locate a member function.
+
+*  Type Definitions:
+*     Public:
+*        AstObject
+*           Object type.
+*
+*     Protected:
+*        AstObjectVtab
+*           Object virtual function table type.
+
+*  Feature Test Macros:
+*     AST_CHECK_CLASS
+*        If the AST_CHECK_CLASS macro is defined, then Object class
+*        checking is enabled for all internal function invocations
+*        within the AST library. Otherwise, this checking is
+*        omitted. This macro should normally be defined as a compiler
+*        option during library development and debugging, but left
+*        undefined for software releases, so as to improve
+*        peformance. Class checking by the AST public interface is not
+*        affected by this macro.
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+*     astFORTRAN77
+*        If the astFORTRAN77 macro is defined, reporting of error
+*        context information is suppressed. This is necessary when
+*        implementing foreign language interfaces to the AST library, as
+*        otherwise the file names and line numbers given would refer
+*        to the interface implementation rather than the user's own
+*        code.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     30-JAN-1996 (RFWS):
+*        Original version.
+*     19-APR-1996 (RFWS):
+*        Added macros for implementing attribute access methods.
+*     3-JUL-1996 (RFWS):
+*        Added new definitions to support the external interface.
+*     10-SEP-1996 (RFWS):
+*        Added loader and related facilities.
+*     30-MAY-1997 (RFWS):
+*        Add the ID attribute.
+*     14-JUL-1997 (RFWS):
+*        Add astExempt function.
+*     20-JAN-1998 (RFWS):
+*        Make the astClear and astVSet methods virtual.
+*     15-SEP-1999 (RFWS):
+*        Made the astAnnulId function accessible to protected code.
+*     3-APR-2001 (DSB):
+*        Added Ident attribute.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitObjectVtab method.
+*     30-APR-2003 (DSB):
+*        Added macros AST__VMAJOR, AST__VMINOR and AST__RELEASE.
+*        Added astVersion function.
+*     7-FEB-2004 (DSB):
+*        Added astEscapes function.
+*     11-MAR-2005 (DSB):
+*        Added UseDefs attribute.
+*     7-FEB-2006 (DSB):
+*        Added astTune function.
+*     14-FEB-2006 (DSB):
+*        Added ObjSize attribute.
+*     23-FEB-2006 (DSB):
+*        Moved AST__TUNULL from this file to memory.h.
+*     10-MAY-2006 (DSB):
+*        Added astEQUAL, astMAX and astMIN.
+*     26-MAY-2006 (DSB):
+*        Make all system includes unconditional, so that makeh is not
+*        confused when creating ast.h.
+*     22-JUN-2007 (DSB):
+*        - Make astVSet return a pointer to dynamic memory holding the
+*        expanded setting string.
+*        - Add ast astSetVtab and astCast.
+*     22-APR-2008 (DSB):
+*        Added astSame.
+*     7-APR-2010 (DSB):
+*        Added astHasAttribute.
+*--
+*/
+
+/* Include files. */
+/* ============== */
+/* Configuration results. */
+/* ---------------------- */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Interface definitions. */
+/* ---------------------- */
+#include "error.h"               /* Error reporting facilities */
+#include "version.h"             /* Version number macros */
+
+/* C header files. */
+/* --------------- */
+#include <stddef.h>
+#include <stdarg.h>
+#include <float.h>
+#include <stdio.h>
+
+#if defined(THREAD_SAFE)
+#include <pthread.h>
+#endif
+
+/* Macros. */
+/* ======= */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Set to "1" (yes) or "0" (no) to indicate if AST was build with threads
+   support. */
+#define AST__THREADSAFE 1
+
+#if defined(astCLASS )
+#define AST__GETATTRIB_BUFF_LEN 50 /* Length of string returned by GetAttrib. */
+#define AST__ASTGETC_MAX_STRINGS 50 /* Number of string values to buffer within astGetC */
+
+/* Values supplied to astManageLock */
+#define AST__LOCK 1      /* Lock the object */
+#define AST__UNLOCK 2    /* Unlock the object */
+#define AST__CHECKLOCK 3 /* Check if the object is locked */
+
+/* Values returned by astThread */
+#define AST__UNLOCKED 1  /* Object is unlocked */
+#define AST__RUNNING 2   /* Object is locked by the running thread */
+#define AST__OTHER 3     /* Object is locked by another thread */
+
+#endif
+
+/* Value that indicates that two classes are not in direct line from each
+   other. */
+#if defined(astCLASS )
+#define AST__COUSIN -1000000
+#endif
+
+/*
+*+
+*  Name:
+*     astINVOKE
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Invoke an AST function.
+
+*  Synopsis:
+*     #include "object.h"
+*     astINVOKE(rettype,function)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an invocation of an AST function, together
+*     with any additional actions required to support it.  The actual
+*     expansion depends on whether the macro is expanded in internal
+*     code (astCLASS defined) or external code (astCLASS undefined)
+*     and it therefore hides the differences between these two
+*     interfaces.
+
+*  Parameters:
+*     rettype
+*        A character to indicate the type of result returned by the function:
+*
+*        V
+*           The function returns a value (including void or a pointer
+*           value, but excluding an Object pointer). astINVOKE will
+*           return the value unchanged.
+*        O
+*           The function returns an Object pointer. astINVOKE will
+*           convert it to an Object identifier if necessary.
+*        F
+*           The function returns a function pointer. astINVOKE will
+*           return it unchanged. This is typically used when the
+*           function has a variable argument list. In this case the
+*           function name is passed to astINVOKE without its argument
+*           list and a pointer to the function is returned which can
+*           then be supplied with an argument list. This avoids the
+*           need to define a macro with a variable number of arguments
+*           (which isn't allowed).
+*     function
+*        A normal invocation of the function returning the required
+*        result.  In the case of a variable argument list, the
+*        argument list should be omitted so that the function is not
+*        invoked but a function pointer is returned that may then be
+*        used to invoke it.
+
+*  Examples:
+*     #define astGetNobject(this) \
+*             astINVOKE(V,astGetNobject_(astCheckObject(this)))
+*        Defines a macro to invoke the astGetNobject_ function which
+*        returns an int.
+*     #define astClone(this) \
+*             astINVOKE(O,astClone_(astCheckObject(this)))
+*        Defines a macro to invoke the astClone_ function which
+*        returns an Object pointer.
+*     #define astSet astINVOKE(F,astSet_)
+*        Defines a macro to invoke the astSet_ function which has a
+*        variable argument list and returns void. The macro result is
+*        a pointer to the astSet_ function. This function must perform
+*        its own argument validation, as (e.g) astCheckObject cannot
+*        be invoked on its arguments via a macro.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* Define astINVOKE, which records the current file and line number
+   (in case of error) using astAt, and then invokes the function
+   supplied as an argument of the astRetV_, astRetO_ or astRetF_
+   macro.
+
+   Suppress reporting of the file and line number from internal code
+   and from foreign language interfaces by not using astAt in these
+   cases. */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define astINVOKE(rettype,function) astRet##rettype##_(function)
+#else
+#define astINVOKE(rettype,function) \
+astERROR_INVOKE(astRet##rettype##_(function))
+#endif
+
+/* astRetF_ and astRetV_ currently do nothing. */
+#define astRetF_(x) (x)
+#define astRetV_(x) (x)
+
+/* However, astRetO_ converts a pointer to an ID if necessary. */
+#if defined(astCLASS)
+#define astRetO_(x) ((void *)(x))
+#else
+#define astRetO_(x) ((void *)astMakeId_((AstObject *)(x),STATUS_PTR))
+#endif
+
+/*
+*+
+*  Name:
+*     astINVOKE_CHECK
+*     astINVOKE_ISA
+
+*  Type:
+*     Protected macros.
+
+*  Purpose:
+*     Invoke the astCheck<Class>_ and astIsA<Class>_ functions.
+
+*  Synopsis:
+*     #include "object.h"
+*     astINVOKE_CHECK(class,this,force)
+*     astINVOKE_ISA(class,this)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     These macros expand to invocations of the standard
+*     astCheck<Class>_ and astIsA<Class>_ functions for a class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class for which the function
+*        is to be invoked.
+*     this
+*        The "this" argument (the Object pointer) to be passed to the
+*        function.
+*     force
+*        Type checking takes time, and so can be disabled within the
+*        protected context in order to save time. Setting "force" to
+*        zero causes the astINVOKE_CHECK macro to skip the class check
+*        in a protected context (it assumes that AST "knows what it is
+*        doing"). Setting "force" to a non-zero value forces the class
+*        check even in a protected context.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* For the public interface (and also internally if AST_CHECK_CLASS is
+   defined), define astINVOKE_CHECK to invoke the astCheck<Class>
+   function. */
+#if !defined(astCLASS) || defined(AST_CHECK_CLASS)
+#define astINVOKE_CHECK(class,this,force) \
+astCheck##class##_((Ast##class *)astEnsurePointer_(this),astGetStatusPtr)
+
+/* For the internal interface, astINVOKE_CHECK omits the
+   astCheck<class> function (unless AST_CHECK_CLASS is defined). */
+#else
+
+#define astINVOKE_CHECK(class,this,force) ( (force) ? \
+   ( astCheck##class##_((Ast##class *)astEnsurePointer_(this),astGetStatusPtr) ) : \
+   ( (Ast##class *) astEnsurePointer_(this) ) )
+
+#endif
+
+/* Define astINVOKE_ISA to invoke the astIsA<Class> function. */
+#if defined(astCLASS)            /* Protected */
+#define astINVOKE_ISA(class,this) \
+astIsA##class##_((const Ast##class *)(this),status)
+#else                            /* Public */
+#define astINVOKE_ISA(class,this) \
+astINVOKE(V,astIsA##class##_((const Ast##class *)astEnsurePointer_(this),astGetStatusPtr))
+#endif
+
+/* The astEnsurePointer_ macro ensures a true C pointer, converting
+   from an ID if necessary. */
+#if defined(astCLASS)            /* Protected */
+#define astEnsurePointer_(x) ((void *)(x))
+#else                            /* Public */
+#define astEnsurePointer_(x) ((void *)astCheckLock_(astMakePointer_((AstObject *)(x),STATUS_PTR),STATUS_PTR))
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_CHECK
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Implement the astCheck<Class>_ function for a class.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_CHECK(class)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of the public astCheck<Class>_
+*     function (q.v.) which validates membership of a specified class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class whose membership is to be
+*        validated.
+
+*  Notes:
+*     -  This macro is provided so that class definitions can easiy
+*     implement the astCheck<Class>_ function, which is essentially the same
+*     for each class apart for a change of name.
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+#ifndef MEM_DEBUG
+
+/* Define the macro. */
+#define astMAKE_CHECK(class) \
+\
+/* Declare the function (see the object.c file for a prologue). */ \
+Ast##class *astCheck##class##_( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return this; \
+\
+/* Check if the object is a class member. */ \
+   if ( !astIsA##class( this ) ) { \
+\
+/* If not, but the pointer was valid (which means it identifies an Object \
+   of some sort), then report more information about why this Object is \
+   unsuitable. */ \
+      if ( astOK ) { \
+         astError( AST__OBJIN, "Pointer to " #class " required, but pointer " \
+                   "to %s given.", status, astGetClass( this ) ); \
+      } \
+   } \
+\
+/* Return the pointer value supplied. */ \
+   return this; \
+}
+
+/* Define the macro with memory debugging facilities. */
+#else
+
+#define astMAKE_CHECK(class) \
+\
+/* Declare the function (see the object.c file for a prologue). */ \
+Ast##class *astCheck##class##_( Ast##class *this, int *status ) { \
+\
+   char buf[100]; \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return this; \
+\
+/* Check if the object is a class member. */ \
+   if ( !astIsA##class( this ) ) { \
+\
+/* If not, but the pointer was valid (which means it identifies an Object \
+   of some sort), then report more information about why this Object is \
+   unsuitable. */ \
+      if ( astOK ) { \
+         astError( AST__OBJIN, "Pointer to " #class " required, but pointer " \
+                   "to %s given.", status, astGetClass( this ) ); \
+      }\
+\
+   } else { \
+\
+/* Call the astMemoryUse function to report it if the memory block is \
+   being watched. */ \
+      sprintf( buf, "checked (refcnt: %d)", astGetRefCount_( (AstObject *) this, status ) ); \
+      astMemoryUse( this, buf ); \
+   } \
+\
+/* Return the pointer value supplied. */ \
+   return this; \
+}
+#endif
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_CLEAR
+
+*  Purpose:
+*     Implement a method to clear an attribute value for a class.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_CLEAR(class,attribute,component,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( Ast<Class> *this )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( Ast<Class> *this )
+*
+*     which implement a method for clearing a specified attribute value for
+*     a class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class to which the attribute belongs.
+*     attribute
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. Label in "astClearLabel").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value.
+
+*  Examples:
+*     astMAKE_CLEAR(MyStuff,Flag,flag,-1)
+*        Implements the astClearFlag method for the MyStuff class which
+*        operates by setting the "flag" structure component to -1 to indicate
+*        that it has no value.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_CLEAR(class,attribute,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attribute( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Assign the "clear" value. */ \
+   this->component = (assign); \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attribute##_( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,class,Clear##attribute))( this, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_CLEAR1
+
+*  Purpose:
+*     Implement a method to clear an attribute value for a class, reporting
+*     an error if the object has more than one reference.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_CLEAR1(class,attribute,component,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( Ast<Class> *this )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( Ast<Class> *this )
+*
+*     which implement a method for clearing a specified attribute value for
+*     a class. An error is reported if the object has a reference count that
+*     is greater than one.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class to which the attribute belongs.
+*     attribute
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. Label in "astClearLabel").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_CLEAR1(class,attribute,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attribute( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Report an error if the object has been cloned (i.e. has a reference \
+   count that is greater than one). */ \
+   if( astGetRefCount( this ) > 1 ) { \
+      astError( AST__IMMUT, "astClear(%s): The " #attribute "attribute of " \
+                "the supplied %s cannot be cleared because the %s has " \
+                "been cloned (programming error).", status, \
+                astGetClass(this), astGetClass(this), astGetClass(this) ); \
+\
+/* Otherwise, assign the "clear" value in the structure component. */ \
+   } else { \
+      this->component = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attribute##_( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,class,Clear##attribute))( this, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_GET
+
+*  Purpose:
+*     Implement a method to get an attribute value for a class.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_GET(class,attribute,type,bad_value,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( Ast<Class> *this )
+*
+*     and an external interface function of the form:
+*
+*        <Type> astGet<Attribute>_( Ast<Class> *this )
+*
+*     which implement a method for getting a specified attribute value for a
+*     class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class to which the attribute belongs.
+*     attribute
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. Label in "astGetLabel").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the inherited error status is set, or if
+*        the function fails.
+*     assign
+*        An expression that evaluates to the value to be returned.
+
+*  Examples:
+*     astMAKE_GET(MyStuff,Flag,int,0,( this->flag == 1 ))
+*        Implements the astGetFlag method for the MyStuff class which operates
+*        by examining the integer "flag" structure component and comparing it
+*        with the value 1 to see if it is set. A value of 0 is returned if the
+*        method fails to complete successfully.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_GET(class,attribute,type,bad_value,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type Get##attribute( Ast##class *this, int *status ) { \
+   type result;                  /* Result to be returned */ \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Assign the result value. */ \
+   result = (assign); \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */  \
+type astGet##attribute##_( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,class,Get##attribute))( this, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_ISA
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Implement the astIsA<Class>_ function for a class.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_ISA(class,parent)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of the public
+*     astIsA<Class>_ function (q.v.) which checks membership of a
+*     specified class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class whose membership is to be
+*        tested.
+*     parent
+*        The name of the parent class.
+
+*  Notes:
+*     - This macro is provided so that class definitions can easiy
+*     implement the astIsA<Class>_ function, which is essentially the
+*     same for each class apart for a change of name.
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_ISA(class,parent) \
+\
+/* Declare the function (see the object.c file for a prologue). */ \
+int astIsA##class##_( const Ast##class *this, int *status ) { \
+\
+/* Local Variables: */ \
+   int isa = 0;                  /* Is object a member? */ \
+\
+/* To test if the object is correctly constructed, we first test if it is a \
+   member of the parent class. This improves the security of the test by \
+   checking the object structure from the base Object class downwards \
+   (without this, the "magic numbers" that identify classes might be \
+   encountered by accident or we might address parts of the Object which \
+   don't exist). */ \
+   if ( astIsA##parent( this ) ) { \
+\
+/* Obtain the Object's size and check it is adequate for an object of the \
+   proposed type (this avoids any attempt to access derived class data that \
+   doesn't exist and therefore lies outside the memory allocated for the \
+   object). */ \
+      if ( ( (AstObject *) this )->size >= sizeof( Ast##class ) ) { \
+\
+/* If OK, see whether the check component in the object's virtual function \
+   table matches the expected "magic" value. */ \
+         isa = ( *astMEMBER(this,class,id.check) == &class_check ); \
+      } \
+   } \
+\
+/* Return the result. */ \
+   return isa; \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_SET
+
+*  Purpose:
+*     Implement a method to set an attribute value for a class.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_SET(class,attribute,type,component,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( Ast<Class> *this, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( Ast<Class> *this, <Type> value )
+*
+*     which implement a method for setting a specified attribute value for a
+*     class.
+
+*  Parameters:
+*      class
+*         The name (not the type) of the class to which the attribute belongs.
+*      attribute
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. Label in "astSetLabel").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+
+*  Examples:
+*     astMAKE_SET(MyStuff,Flag,int,flag,( value != 0 ))
+*        Implements the astSetFlag method for the MyStuff class which operates
+*        by setting the "flag" structure component to 0 or 1 depending on
+*        whether the "value" parameter is non-zero or not.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_SET(class,attribute,type,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attribute( Ast##class *this, type value, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Store the new value in the structure component. */ \
+   this->component = (assign); \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attribute##_( Ast##class *this, type value, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,class,Set##attribute))( this, value, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_SET1
+
+*  Purpose:
+*     Implement a method to set an attribute value for a class, reporting
+*     an error if the object has more than one reference.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_SET1(class,attribute,type,component,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( Ast<Class> *this, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( Ast<Class> *this, <Type> value )
+*
+*     which implement a method for setting a specified attribute value for a
+*     class. An error is reported if the object has a reference count that
+*     is greater than one.
+
+*  Parameters:
+*      class
+*         The name (not the type) of the class to which the attribute belongs.
+*      attribute
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. Label in "astSetLabel").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_SET1(class,attribute,type,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attribute( Ast##class *this, type value, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Report an error if the object has been cloned (i.e. has a reference \
+   count that is greater than one). */ \
+   if( astGetRefCount( this ) > 1 ) { \
+      astError( AST__IMMUT, "astSet(%s): The " #attribute "attribute of " \
+                "the supplied %s cannot be changed because the %s has " \
+                "been cloned (programming error).", status, \
+                astGetClass(this), astGetClass(this), astGetClass(this) ); \
+\
+/* Otherwise, store the new value in the structure component. */ \
+   } else { \
+      this->component = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attribute##_( Ast##class *this, type value, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,class,Set##attribute))( this, value, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_TEST
+
+*  Purpose:
+*     Implement a method to test if an attribute has been set for a class.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_TEST(class,attribute,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static int Test<Attribute>( Ast<Class> *this )
+*
+*     and an external interface function of the form:
+*
+*        int astTest<Attribute>_( Ast<Class> *this )
+*
+*     which implement a method for testing if a specified attribute has been
+*     set for a class.
+
+*  Parameters:
+*      class
+*         The name (not the type) of the class to which the attribute belongs.
+*      attribute
+*         The name of the attribute to be tested, as it appears in the function
+*         name (e.g. Label in "astTestLabel").
+*      assign
+*         An expression that evaluates to 0 or 1, to be used as the returned
+*         value.
+
+*  Examples:
+*     astMAKE_TEST(MyStuff,Flag,( this->flag != -1 ))
+*        Implements the astTestFlag method for the MyStuff class which operates
+*        by testing the "flag" structure component to see if it is set to a
+*        value other than -1.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_TEST(class,attribute,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static int Test##attribute( Ast##class *this, int *status ) { \
+   int result;                   /* Value to return */ \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Assign the result value. */ \
+   result = (assign); \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */ \
+int astTest##attribute##_( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,class,Test##attribute))( this, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMEMBER
+
+*  Purpose:
+*     Locate a member function.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMEMBER(this,class,method)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro evaluates to the address where the pointer to a
+*     specified Object member function is stored. Typically, this will
+*     be used to obtain a pointer to the member function so that it
+*     can be invoked. It may also be used to assign a new function
+*     pointer so that a derived class can re-define a virtual function
+*     and hence over-ride an inherited method.
+
+*  Parameters:
+*     this
+*        Pointer to an Object belonging to the class for which the
+*        virtual function is required. This must either be the class
+*        that originally defined the method, or one derived from it.
+*     class
+*        Name of the class that originally defined the method. This
+*        may differ from (i.e. be an ancestor of) the class to which
+*        "this" belongs.
+*     method
+*        Name of the method whose member function is to be located.
+
+*  Returned Value:
+*     The address where the member function pointer is stored (the
+*     type of the result is determined by the type of the member
+*     function itself).
+
+*  Examples:
+*     astMEMBER(this,Gnome,astFish)
+*        Returns the address where the pointer to the function that
+*        implements the astFish method for the "this" object is
+*        stored. The Gnome class should be where the astFish method
+*        was first defined (i.e. from where it was inherited by
+*        "this").
+*     (**astMEMBER(this,Gnome,astFish))( this, arg1, arg2 );
+*        Invokes the virtual function that implements the astFish
+*        method for object "this" and passes it additional arguments
+*        "arg2" and "arg2".  Again, Gnome should be the class that
+*        originally defined the astFish method.
+*     *astMEMBER(this,Gnome,astFish) = myFish;
+*        Stores a pointer to the myFish function so that it replaces
+*        the virtual function that previously implemented the astFish
+*        method for the "this" object. Note that all objects in the
+*        same class as "this" are affected, but objects in class
+*        "class" are not affected (unless it happens to be the class
+*        to which "this" belongs).
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* A subsiduary macro that returns a pointer to the vtab of an object,
+   cast to an AstObjectVtab. */
+#define astVTAB(this) (((AstObject*)(this))->vtab)
+
+/* Define the macro. This functions by (a) casting the Object pointer
+   to type (AstObject *) and locating the Object's virtual function
+   table (b) casting the table pointer to the correct type
+   (AstClassVtab *) for the class in which the method pointer resides,
+   (c) locating the component holding the member function pointer, and
+   (d) taking its address. */
+#define astMEMBER(this,class,method) \
+(&((Ast##class##Vtab*)astVTAB(this))->method)
+
+#endif
+
+/*
+*+
+*  Name:
+*     astPROTO_CHECK
+*     astPROTO_ISA
+
+*  Type:
+*     Protected macros.
+
+*  Purpose:
+*     Prototype the astCheck<Class>_ and astIsA<Class>_ functions.
+
+*  Synopsis:
+*     #include "object.h"
+*     astPROTO_CHECK(class)
+*     astPROTO_ISA(class)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     These macros expands to function prototypes for the
+*     astCheck<Class>_ and astIsA<Class>_ functions (q.v.) which
+*     validate and test for membership of a specified class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class whose membership is to
+*        be validated.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* Define the macros. */
+#define astPROTO_CHECK(class) Ast##class *astCheck##class##_( Ast##class *, int * );
+#define astPROTO_ISA(class) int astIsA##class##_( const Ast##class *, int * );
+
+/* Macros which return the maximum and minimum of two values. */
+#define astMAX(aa,bb) ((aa)>(bb)?(aa):(bb))
+#define astMIN(aa,bb) ((aa)<(bb)?(aa):(bb))
+
+/* Check for equality of floating point values. We cannot compare bad values
+   directly because of the danger of floating point exceptions, so bad
+   values are dealt with explicitly. */
+#define astEQUALS(aa,bb,tol) (((aa)==AST__BAD)?(((bb)==AST__BAD)?1:0):(((bb)==AST__BAD)?0:(fabs((aa)-(bb))<=(tol)*astMAX((fabs(aa)+fabs(bb))*DBL_EPSILON,DBL_MIN))))
+#define astEQUAL(aa,bb) astEQUALS(aa,bb,1.0E5)
+
+
+/* AST__NULL. */
+/* ---------- */
+/* Define the AST__NULL macro, which evaluates to a null Object
+   pointer. */
+#define AST__NULL (astI2P(0))
+
+
+#if defined(astCLASS)            /* Protected */
+
+/* Test the validy of an attribute value  */
+/* -------------------------------------  */
+/* If the set attribute value is invalid, clear it. These macros should
+   be used in a context in which error reporting has been deferred by
+   calling astReporting( 0 ). */
+
+#define astCLEAN_ATTRIB(attr) \
+   if( astTest##attr(this) ) { \
+      astSet##attr( this, astGet##attr( this ) ); \
+      if( !astOK ) { \
+         astClearStatus; \
+         astClear##attr( this ); \
+      } \
+   }
+
+#define astCLEAN_INDEXED_ATTRIB(attr,index) \
+   if( astTest##attr(this,index) ) { \
+      astSet##attr( this, index, astGet##attr( this, index ) ); \
+      if( !astOK ) { \
+         astClearStatus; \
+         astClear##attr( this, index ); \
+      } \
+   }
+
+#endif
+
+
+#if defined(astCLASS)            /* Protected */
+#define astSetVtabClassIdentifier(vtab,id_ptr) \
+   ((AstObjectVtab *)(vtab))->top_id = (id_ptr)
+#endif
+
+/* Type Definitions. */
+/* ================= */
+
+/* Object structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstObject {
+
+/* Attributes specific to objects in this class. */
+   unsigned long check;          /* Check value to identify Objects */
+   size_t size;                  /* Amount of memory used by Object */
+   struct AstObjectVtab *vtab;   /* Pointer to virtual function table */
+   char dynamic;                 /* Memory allocated dynamically? */
+   int ref_count;                /* Number of active pointers to the Object */
+   char *id;                     /* Pointer to ID string */
+   char *ident;                  /* Pointer to Ident string */
+   char usedefs;                 /* Use default attribute values? */
+   int iref;                     /* Object index (unique within class) */
+   void *proxy;                  /* A pointer to an external object that
+                                    acts as a foreign language proxy for the
+                                    AST object */
+#if defined(THREAD_SAFE)
+   int locker;                   /* Thread that has locked this Object */
+   pthread_mutex_t mutex1;       /* Guards access to all elements of the
+                                    Object except for the "locker" and
+                                    "ref_count" components */
+   pthread_mutex_t mutex2;       /* Guards access to the "locker" and
+                                    "ref_count" components */
+   struct AstGlobals *globals;   /* Pointer to thread-specific global data */
+#endif
+
+} AstObject;
+
+/* Class identifier structure */
+typedef struct AstClassIdentifier {
+   int *check;
+   struct AstClassIdentifier *parent;
+} AstClassIdentifier;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* The virtual function table makes a forward reference to the
+   AstChannel structure which is not defined until "channel.h" is
+   included (below). Hence make a preliminary definition available
+   now. */
+struct AstChannel;
+
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstObjectVtab {
+
+/* A unique identifier for this class. */
+   AstClassIdentifier id;
+
+/* Pointer to the structure that identifies the top-level class described
+   by the whole vtab (of which the AstObjectVtab is just the first,
+   lowest-level, component). */
+   AstClassIdentifier *top_id;
+
+/* Pointer to a dynamically allocated string holding the default
+   attribute values to use when creating new objects. These are read from
+   environment variables of the form "<CLASSNAME>_OPTIONS". */
+   const char *defaults;
+
+/* Properties specific to this class. */
+   void ( *CleanAttribs )( AstObject *, int * );
+   AstObject *( *Cast )( AstObject *, AstObject *, int * );
+   const char *( *GetID )( AstObject *, int * );
+   const char *( *GetIdent )( AstObject *, int * );
+   const char *(* GetAttrib)( AstObject *, const char *, int * );
+   int (* TestAttrib)( AstObject *, const char *, int * );
+   int (* TestID)( AstObject *, int * );
+   int (* Same)( AstObject *, AstObject *, int * );
+   int (* HasAttribute)( AstObject *, const char *, int * );
+   int (* TestIdent)( AstObject *, int * );
+   void (* Clear)( AstObject *, const char *, int * );
+   void (* ClearAttrib)( AstObject *, const char *, int * );
+   void (* ClearID)( AstObject *, int * );
+   void (* ClearIdent)( AstObject *, int * );
+   void (* Dump)( AstObject *, struct AstChannel *, int * );
+   int (* Equal)( AstObject *, AstObject *, int * );
+   void (* SetAttrib)( AstObject *, const char *, int * );
+   void (* SetID)( AstObject *, const char *, int * );
+   void (* SetIdent)( AstObject *, const char *, int * );
+   void (* Show)( AstObject *, int * );
+   void (* VSet)( AstObject *, const char *, char **, va_list, int * );
+   void (* EnvSet)( AstObject *, int * );
+
+   void *(* GetProxy)( AstObject *, int * );
+   void (* SetProxy)( AstObject *, void *, int * );
+
+   int (* GetObjSize)( AstObject *, int * );
+
+   int (* TestUseDefs)( AstObject *, int * );
+   int (* GetUseDefs)( AstObject *, int * );
+   void (* SetUseDefs)( AstObject *, int, int * );
+   void (* ClearUseDefs)( AstObject *, int * );
+
+   const char *class;            /* Pointer to class name string */
+   void (** delete)( AstObject *, int * ); /* Pointer to array of destructors */
+   void (** copy)( const AstObject *, AstObject *, int * ); /* Copy constructors */
+   void (** dump)( AstObject *, struct AstChannel *, int * ); /* Dump functions */
+   const char **dump_class;      /* Dump function class string pointers */
+   const char **dump_comment;    /* Dump function comment string pointers */
+   int ndelete;                  /* Number of destructors */
+   int ncopy;                    /* Number of copy constructors */
+   int ndump;                    /* Number of dump functions */
+   int nobject;                  /* Number of active objects in the class */
+   int nfree;                    /* No. of entries in "free_list" */
+   AstObject **free_list;        /* List of pointers for freed Objects */
+
+#if defined(THREAD_SAFE)
+   int (* ManageLock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+} AstObjectVtab;
+#endif
+
+#if defined(THREAD_SAFE) && defined(astCLASS)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstObjectGlobals {
+   AstObjectVtab Class_Vtab;
+   int Class_Init;
+   int Retain_Esc;
+   int Context_Level;
+   int *Active_Handles;
+   char GetAttrib_Buff[ AST__GETATTRIB_BUFF_LEN + 1 ];
+   char *AstGetC_Strings[ AST__ASTGETC_MAX_STRINGS ];
+   int AstGetC_Istr;
+   int AstGetC_Init;
+   int Nvtab;
+   AstObjectVtab **Known_Vtabs;
+} AstObjectGlobals;
+
+#endif
+
+/* More include files. */
+/* =================== */
+/* The interface to the Channel class must be included here (after the
+   type definitions for the Object class) because "channel.h" itself
+   includes this file ("object.h"), although "object.h" refers to the
+   AstChannel structure above. This seems a little strange at first,
+   but is simply analogous to making a forward reference to a
+   structure type when recursively defining a normal C structure
+   (except that here the definitions happen to be in separate include
+   files). */
+#include "channel.h"
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Object)           /* Validate class membership */
+astPROTO_ISA(Object)             /* Test class membership */
+
+/* NB. There is no constructor function for this class. */
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstObject *astInitObject_( void *, size_t, int, AstObjectVtab *,
+                           const char *, int * );
+
+/* Vtab Initialiser. */
+void astInitObjectVtab_( AstObjectVtab *, const char *, int * );
+
+/* Loader. */
+AstObject *astLoadObject_( void *, size_t, AstObjectVtab *,
+                           const char *, AstChannel *channel, int * );
+
+#if defined(THREAD_SAFE)
+void astInitObjectGlobals_( AstObjectGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for other class functions. */
+/* ------------------------------------- */
+#if !defined(astCLASS)           /* Public */
+void astBegin_( void );
+void astEnd_( int * );
+#endif
+
+AstObject *astI2P_( int, int * );
+AstObject *astMakeId_( AstObject *, int * );
+AstObject *astMakePointer_( AstObject *, int * );
+AstObject *astMakePointer_NoLockCheck_( AstObject *, int * );
+int astP2I_( AstObject *, int * );
+int astVersion_( int * );
+int astEscapes_( int, int * );
+int astTune_( const char *, int, int * );
+void astTuneC_( const char *, const char *, char *, int, int * );
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+#if defined(astCLASS)            /* Protected */
+AstObject *astAnnul_( AstObject *, int * );
+AstObject *astDelete_( AstObject *, int * );
+void astSet_( void *, const char *, int *, ... );
+
+#else                            /* Public */
+AstObject *astDeleteId_( AstObject *, int * );
+int astThreadId_( AstObject *, int, int * );
+void astExportId_( AstObject *, int * );
+void astImportId_( AstObject *, int * );
+void astSetId_( void *, const char *, ... )__attribute__((format(printf,2,3)));
+#endif
+
+AstObject *astAnnulId_( AstObject *, int * );
+AstObject *astCheckLock_( AstObject *, int * );
+AstObject *astClone_( AstObject *, int * );
+AstObject *astCopy_( const AstObject *, int * );
+AstObject *astFromString_( const char *, int * );
+char *astToString_( AstObject *, int * );
+const char *astGetC_( AstObject *, const char *, int * );
+double astGetD_( AstObject *, const char *, int * );
+float astGetF_( AstObject *, const char *, int * );
+int astEqual_( AstObject *, AstObject *, int * );
+int astGetI_( AstObject *, const char *, int * );
+int astHasAttribute_( AstObject *, const char *, int * );
+int astSame_( AstObject *, AstObject *, int * );
+int astTest_( AstObject *, const char *, int * );
+long astGetL_( AstObject *, const char *, int * );
+void *astGetProxy_( AstObject *, int * );
+void astClear_( AstObject *, const char *, int * );
+void astExemptId_( AstObject *, int * );
+void astLockId_( AstObject *, int, int * );
+void astSetC_( AstObject *, const char *, const char *, int * );
+void astSetD_( AstObject *, const char *, double, int * );
+void astSetF_( AstObject *, const char *, float, int * );
+void astSetI_( AstObject *, const char *, int, int * );
+void astSetL_( AstObject *, const char *, long, int * );
+void astSetProxy_( AstObject *, void *, int * );
+void astShow_( AstObject *, int * );
+void astUnlockId_( AstObject *, int, int * );
+
+#if defined(astCLASS)            /* Protected */
+
+void astCleanAttribs_( AstObject *, int * );
+AstObject *astCast_( AstObject *, AstObject *, int * );
+AstObject *astCastCopy_( AstObject *, AstObject *, int * );
+
+#if defined(THREAD_SAFE)
+int astManageLock_( AstObject *, int, int, AstObject **, int * );
+#endif
+
+int astGetObjSize_( AstObject *, int * );
+
+int astTestUseDefs_( AstObject *, int * );
+int astGetUseDefs_( AstObject *, int * );
+void astSetUseDefs_( AstObject *, int, int * );
+void astClearUseDefs_( AstObject *, int * );
+
+const char *astGetAttrib_( AstObject *, const char *, int * );
+const char *astGetClass_( const AstObject *, int * );
+const char *astGetID_( AstObject *, int * );
+const char *astGetIdent_( AstObject *, int * );
+int astClassCompare_( AstObjectVtab *, AstObjectVtab *, int * );
+int astGetNobject_( const AstObject *, int * );
+int astGetRefCount_( AstObject *, int * );
+int astTestAttrib_( AstObject *, const char *, int * );
+int astTestID_( AstObject *, int * );
+int astTestIdent_( AstObject *, int * );
+void astClearAttrib_( AstObject *, const char *, int * );
+void astClearID_( AstObject *, int * );
+void astClearIdent_( AstObject *, int * );
+void astDump_( AstObject *, AstChannel *, int * );
+void astSetAttrib_( AstObject *, const char *, int * );
+void astSetCopy_( AstObjectVtab *, void (*)( const AstObject *, AstObject *, int * ), int * );
+void astSetDelete_( AstObjectVtab *, void (*)( AstObject *, int * ), int * );
+void astSetDump_( AstObjectVtab *, void (*)( AstObject *, AstChannel *, int * ), const char *, const char *, int * );
+void astSetVtab_( AstObject *, AstObjectVtab *, int * );
+void astSetID_( AstObject *, const char *, int * );
+void astSetIdent_( AstObject *, const char *, int * );
+void astEnvSet_( AstObject *, int * );
+void astVSet_( AstObject *, const char *, char **, va_list, int * );
+
+#endif
+
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Check class membership. */
+#define astCheckObject(this) astINVOKE_CHECK(Object,this,0)
+#define astVerifyObject(this) astINVOKE_CHECK(Object,this,1)
+
+/* Test class membership. */
+#define astIsAObject(this) astINVOKE_ISA(Object,this)
+
+/* NB. There is no constructor function for this class. */
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitObject(mem,size,init,vtab,name) \
+astINVOKE(O,astInitObject_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitObjectVtab(vtab,name) astINVOKE(V,astInitObjectVtab_(vtab,name,STATUS_PTR))
+
+/* Loader. */
+#define astLoadObject(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadObject_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to other class functions. */
+/* ------------------------------------ */
+#if !defined(astCLASS)           /* Public */
+#define astBegin astBegin_()
+#define astEnd astINVOKE(V,astEnd_(STATUS_PTR))
+#else                            /* Protected */
+#define astMakePointer_NoLockCheck(id) ((void *)astMakePointer_NoLockCheck_((AstObject *)(id),STATUS_PTR))
+#endif
+
+#define astVersion astVersion_(STATUS_PTR)
+#define astEscapes(int) astEscapes_(int,STATUS_PTR)
+#define astTune(name,val) astTune_(name,val,STATUS_PTR)
+#define astTuneC(name,value,buff,bufflen) astTuneC_(name,value,buff,bufflen,STATUS_PTR)
+#define astI2P(integer) ((void *)astI2P_(integer,STATUS_PTR))
+#define astMakeId(pointer) ((void *)astMakeId_((AstObject *)(pointer),STATUS_PTR))
+#define astP2I(pointer) astP2I_((AstObject *)(pointer),STATUS_PTR)
+#define astMakePointer(id) ((void *)astCheckLock_(astMakePointer_((AstObject *)(id),STATUS_PTR),STATUS_PTR))
+#define astToString(this) astINVOKE(V,astToString_(astCheckObject(this),STATUS_PTR))
+#define astFromString(string) astINVOKE(O,astFromString_(string,STATUS_PTR))
+
+/* Interfaces to member functions. */
+/* ------------------------------- */
+/* Here we make use of astCheckObject (et al.) to validate Object
+   pointers before use. This provides a contextual error report if a
+   pointer to the wrong sort of object is supplied. In the case of an
+   external caller, it also performs the required conversion from an
+   Object identifier to a true C pointer. */
+
+/* These functions require special treatment for external use because
+   they handle Object identifiers and their resources explicitly, and
+   must therefore be passed identifier values without conversion to C
+   pointers. */
+
+#if defined(astCLASS) || defined(astFORTRAN77) /* Protected or Fotran interface */
+#define astAnnulId(this) astINVOKE(O,astAnnulId_((AstObject *)(this),STATUS_PTR))
+#endif
+
+#if defined(astCLASS)                      /* Protected only */
+#define astAnnul(this) astINVOKE(O,astAnnul_(astCheckObject(this),STATUS_PTR))
+#define astDelete(this) astINVOKE(O,astDelete_(astCheckObject(this),STATUS_PTR))
+#define astSet astINVOKE(F,astSet_)
+
+#else                                          /* Public only */
+#define astAnnul(this) astINVOKE(O,astAnnulId_((AstObject *)(this),STATUS_PTR))
+#define astDelete(this) astINVOKE(O,astDeleteId_((AstObject *)(this),STATUS_PTR))
+#define astExport(this) astINVOKE(V,astExportId_((AstObject *)(this),STATUS_PTR))
+#define astImport(this) astINVOKE(V,astImportId_((AstObject *)(this),STATUS_PTR))
+#define astSet astINVOKE(F,astSetId_)
+#define astThread(this,ptr) astINVOKE(V,astThreadId_((AstObject *)(this),ptr,STATUS_PTR))
+#endif
+
+/* Both.... */
+#define astLock(this,wait) astINVOKE(V,astLockId_((AstObject *)(this),wait,STATUS_PTR))
+#define astUnlock(this,report) astINVOKE(V,astUnlockId_((AstObject *)(this),report,STATUS_PTR))
+#define astEqual(this,that) astINVOKE(V,(((AstObject*)this==(AstObject*)that)||astEqual_(astCheckObject(this),astCheckObject(that),STATUS_PTR)))
+#define astExempt(this) astINVOKE(V,astExemptId_((AstObject *)(this),STATUS_PTR))
+#define astClear(this,attrib) astINVOKE(V,astClear_(astCheckObject(this),attrib,STATUS_PTR))
+#define astClone(this) astINVOKE(O,astClone_(astCheckObject(this),STATUS_PTR))
+#define astCopy(this) astINVOKE(O,astCopy_(astCheckObject(this),STATUS_PTR))
+#define astGetC(this,attrib) astINVOKE(V,astGetC_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetD(this,attrib) astINVOKE(V,astGetD_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetF(this,attrib) astINVOKE(V,astGetF_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetI(this,attrib) \
+astINVOKE(V,astGetI_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetL(this,attrib) \
+astINVOKE(V,astGetL_(astCheckObject(this),attrib,STATUS_PTR))
+#define astSetC(this,attrib,value) \
+astINVOKE(V,astSetC_(astCheckObject(this),attrib,value,STATUS_PTR))
+#define astSetD(this,attrib,value) \
+astINVOKE(V,astSetD_(astCheckObject(this),attrib,value,STATUS_PTR))
+#define astSetF(this,attrib,value) \
+astINVOKE(V,astSetF_(astCheckObject(this),attrib,value,STATUS_PTR))
+#define astSetI(this,attrib,value) \
+astINVOKE(V,astSetI_(astCheckObject(this),attrib,value,STATUS_PTR))
+#define astSetL(this,attrib,value) \
+astINVOKE(V,astSetL_(astCheckObject(this),attrib,value,STATUS_PTR))
+#define astShow(this) \
+astINVOKE(V,astShow_(astCheckObject(this),STATUS_PTR))
+#define astTest(this,attrib) \
+astINVOKE(V,astTest_(astCheckObject(this),attrib,STATUS_PTR))
+#define astSame(this,that) \
+astINVOKE(V,astSame_(astCheckObject(this),astCheckObject(that),STATUS_PTR))
+#define astHasAttribute(this,attrib) \
+astINVOKE(V,astHasAttribute_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetProxy(this) \
+astINVOKE(V,astGetProxy_(astCheckObject(this),STATUS_PTR))
+#define astSetProxy(this,proxy) \
+astINVOKE(V,astSetProxy_(astCheckObject(this),proxy,STATUS_PTR))
+
+
+#if defined(astCLASS)            /* Protected */
+
+#if defined(THREAD_SAFE)
+#define astManageLock(this,mode,extra,fail) \
+astINVOKE(V,astManageLock_(astCheckObject(this),mode, extra,fail,STATUS_PTR))
+#else
+#define astManageLock(this,mode,extra,fail)
+#endif
+
+#define astCleanAttribs(this) astINVOKE(V,astCleanAttribs_(astCheckObject(this),STATUS_PTR))
+#define astGetObjSize(this) astINVOKE(V,astGetObjSize_(astCheckObject(this),STATUS_PTR))
+#define astCast(this,obj) astINVOKE(O,astCast_(astCheckObject(this),astCheckObject(obj),STATUS_PTR))
+#define astCastCopy(this,obj) astCastCopy_((AstObject*)this,(AstObject*)obj,STATUS_PTR)
+
+#define astClearUseDefs(this) astINVOKE(V,astClearUseDefs_(astCheckObject(this),STATUS_PTR))
+#define astTestUseDefs(this) astINVOKE(V,astTestUseDefs_(astCheckObject(this),STATUS_PTR))
+#define astGetUseDefs(this) astINVOKE(V,astGetUseDefs_(astCheckObject(this),STATUS_PTR))
+#define astSetUseDefs(this,val) astINVOKE(V,astSetUseDefs_(astCheckObject(this),val,STATUS_PTR))
+
+#define astClearAttrib(this,attrib) \
+astINVOKE(V,astClearAttrib_(astCheckObject(this),attrib,STATUS_PTR))
+#define astClearID(this) astINVOKE(V,astClearID_(astCheckObject(this),STATUS_PTR))
+#define astClearIdent(this) astINVOKE(V,astClearIdent_(astCheckObject(this),STATUS_PTR))
+#define astDump(this,channel) \
+astINVOKE(V,astDump_(astCheckObject(this),astCheckChannel(channel),STATUS_PTR))
+
+#define astGetAttrib(this,attrib) \
+astINVOKE(V,astGetAttrib_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetClass(this) astINVOKE(V,astGetClass_((const AstObject *)(this),STATUS_PTR))
+#define astGetID(this) astINVOKE(V,astGetID_(astCheckObject(this),STATUS_PTR))
+#define astGetIdent(this) astINVOKE(V,astGetIdent_(astCheckObject(this),STATUS_PTR))
+#define astGetNobject(this) astINVOKE(V,astGetNobject_(astCheckObject(this),STATUS_PTR))
+#define astClassCompare(class1,class2) astClassCompare_(class1,class2,STATUS_PTR)
+#define astGetRefCount(this) astINVOKE(V,astGetRefCount_(astCheckObject(this),STATUS_PTR))
+#define astSetAttrib(this,setting) \
+astINVOKE(V,astSetAttrib_(astCheckObject(this),setting,STATUS_PTR))
+#define astSetCopy(vtab,copy) \
+astINVOKE(V,astSetCopy_((AstObjectVtab *)(vtab),copy,STATUS_PTR))
+#define astSetDelete(vtab,delete) \
+astINVOKE(V,astSetDelete_((AstObjectVtab *)(vtab),delete,STATUS_PTR))
+#define astSetDump(vtab,dump,class,comment) \
+astINVOKE(V,astSetDump_((AstObjectVtab *)(vtab),dump,class,comment,STATUS_PTR))
+#define astSetVtab(object,vtab) \
+astINVOKE(V,astSetVtab_((AstObject *)object,(AstObjectVtab *)(vtab),STATUS_PTR))
+#define astSetID(this,id) astINVOKE(V,astSetID_(astCheckObject(this),id,STATUS_PTR))
+#define astSetIdent(this,id) astINVOKE(V,astSetIdent_(astCheckObject(this),id,STATUS_PTR))
+#define astVSet(this,settings,text,args) \
+astINVOKE(V,astVSet_(astCheckObject(this),settings,text,args,STATUS_PTR))
+#define astEnvSet(this) \
+astINVOKE(V,astEnvSet_(astCheckObject(this),STATUS_PTR))
+#define astTestAttrib(this,attrib) \
+astINVOKE(V,astTestAttrib_(astCheckObject(this),attrib,STATUS_PTR))
+#define astTestID(this) astINVOKE(V,astTestID_(astCheckObject(this),STATUS_PTR))
+#define astTestIdent(this) astINVOKE(V,astTestIdent_(astCheckObject(this),STATUS_PTR))
+
+/* Deprecated synonym. */
+#define astClass(this) astGetClass(this)
+#endif
+
+/* Extra stuff for debuging probnlems with object handles and memory usage */
+#ifdef MEM_DEBUG
+
+void astWatchHandle_( int );
+void astHandleUse_( int, const char *, ... );
+void astHandleAlarm_( const char *, va_list );
+
+#define astWatchHandle astWatchHandle_
+#define astHandleUse astHandleUse_
+#define astHandleAlarm astHandleAlarm_
+
+#else
+
+#define astWatchHandle
+#define astHandleUse
+#define astHandleAlarm
+
+#endif
+
+#endif
+
diff --git a/ast/object.h.in b/ast/object.h.in
new file mode 100644
index 0000000..41901d5
--- /dev/null
+++ b/ast/object.h.in
@@ -0,0 +1,1934 @@
+#if !defined( OBJECT_INCLUDED )  /* Include this file only once */
+#define OBJECT_INCLUDED
+/*
+*++
+*  Name:
+*     object.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Object class.
+
+*  Invocation:
+*     #include "object.h"
+
+*  Description:
+*     This include file defines the interface to the Object class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+
+*     The Object class is the base class from which all other classes
+*     in the AST library are derived. This class provides all the
+*     basic Object behaviour and Object manipulation facilities
+*     required throughout the library. There is no Object constructor,
+*     however, as Objects on their own are not of much use.
+
+*  Inheritance:
+*     The Object base class does not inherit from any other class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     Class (string)
+*        This is a read-only attribute containing the name of the
+*        class to which an Object belongs.
+*     ID (string)
+*        An identification string which may be used to identify the
+*        Object (e.g.) in debugging output, or when stored in an
+*        external medium such as a data file. There is no restriction
+*        on the string's contents. The default is an empty string.
+*     Ident (string)
+*        Like ID, this is an identification string which may be used
+*        to identify the Object. Unlike ID, Ident is transferred when an
+*        Object is copied.
+*     UseDefs (int)
+*        Should default values be used for unset attributes?
+*     Nobject (integer)
+*        This is a read-only attribute which gives the total number of
+*        Objects currently in existence in the same class as the
+*        Object given. It does not include Objects which belong to
+*        derived (more specialised) classes. This value is mainly
+*        intended for debugging, as it can be used to show whether
+*        Objects which should have been deleted have, in fact, been
+*        deleted.
+*     ObjSize (int)
+*        The in-memory size of the Object in bytes.
+*     RefCount (integer)
+*        This is a read-only Attribute which gives the "reference
+*        count" (the number of active pointers) associated with an
+*        Object. It is modified whenever pointers are created or
+*        annulled (by astClone or astAnnul/astEnd for example) and
+*        includes the initial pointer issued when the Object was
+*        created. If the reference count for an Object falls to zero
+*        as the result of annulling a pointer to it, then the Object
+*        will be deleted.
+
+*  Methods Over-Ridden:
+*     None.
+
+*  New Methods Defined:
+*     Public:
+*        astAnnul
+*           Annul a pointer to an Object.
+*        astClear
+*           Clear attribute values for an Object.
+*        astClone
+*           Clone a pointer to an Object.
+*        astCopy
+*           Copy an Object.
+*        astDelete
+*           Delete an Object.
+*        astExempt
+*           Exempt an Object pointer from AST context handling
+*        astExport
+*           Export an Object pointer to an outer context.
+*        astGet<X>, where <X> = C, D, F, I, L
+*           Get an attribute value for an Object.
+*        astImport
+*           Import an Object pointer into the current context.
+*        astSame
+*           Return true if two pointers refer to the same object.
+*        astSet
+*           Set attribute values for an Object.
+*        astSet<X>, where <X> = C, D, F, I, L
+*           Set an attribute value for an Object.
+*        astShow
+*           Display a textual representation of an Object on standard output.
+*        astTest
+*           Test if an attribute value is set for an Object.
+*        astTune
+*           Get or set the value of a global AST tuning parameter.
+*
+*     Protected:
+*        astAnnulId
+*           Annul an external ID for an Object (for use from protected code
+*           which must handle external IDs).
+*        astClearAttrib
+*           Clear the value of a specified attribute for an Object.
+*        astClearID
+*           Clear the value of the ID attribute for an Object.
+*        astClearIdent
+*           Clear the value of the Ident attribute for an Object.
+*        astCast
+*           Return a deep copy of an object, cast into an instance of a
+*           parent class.
+*        astDump
+*           Write an Object to a Channel.
+*        astEqual
+*           Are two Objects equivalent?
+*        astGetAttrib
+*           Get the value of a specified attribute for an Object.
+*        astGetClass (deprecated synonym astClass)
+*           Obtain the value of the Class attribute for an Object.
+*        astGetID
+*           Obtain the value of the ID attribute for an Object.
+*        astGetIdent
+*           Obtain the value of the Ident attribute for an Object.
+*        astGetNobject
+*           Obtain the value of the Nobject attribute for an Object.
+*        astGetRefCount
+*           Obtain the value of the RefCount attribute for an Object.
+*        astSetAttrib
+*           Set the value of a specified attribute for an Object.
+*        astSetCopy
+*           Declare a copy constructor for an Object.
+*        astSetDelete
+*           Declare a destructor for an Object.
+*        astSetDump
+*           Declare a dump function for an Object.
+*        astSetVtab
+*           Chaneg the virtual function table associated with an Object.
+*        astSetID
+*           Set the value of the ID attribute for an Object.
+*        astSetIdent
+*           Set the value of the Ident attribute for an Object.
+*        astTestAttrib
+*           Test if a specified attribute value is set for an Object.
+*        astTestID
+*           Test whether the ID attribute for an Object is set.
+*        astTestIdent
+*           Test whether the Ident attribute for an Object is set.
+*        astVSet
+*           Set values for an Object's attributes.
+
+*  Other Class Functions:
+*     Public:
+*        astBegin
+*           Begin a new AST context.
+*        astEnd
+*           End an AST context.
+*        astIsAObject
+*           Test class membership.
+*        astVersion
+*           Returns the AST library version number.
+*        astEscapes
+*           Remove escape sequences from returned text strings?
+*        astP2I
+*           Retrieve an int from a pointer.
+*        astI2P
+*           Pack an int into a pointer.
+*
+*     Protected:
+*        astCheckObject
+*           Validate class membership.
+*        astInitObject
+*           Initialise an Object.
+*        astInitObjectVtab
+*           Initialise the virtual function table for the Object class.
+*        astLoadObject
+*           Load an Object.
+*        astMakeId
+*           Issue an identifier for an Object.
+*        astMakePointer
+*           Obtain a true C pointer from an Object identifier.
+
+*  Macros:
+*     Public:
+*        AST__NULL
+*           Null Object pointer value.
+*        AST__VMAJOR
+*           The AST library major version number.
+*        AST__VMINOR
+*           The AST library minor version number.
+*        AST__RELEASE
+*           The AST library release number.
+*
+*     Protected:
+*        astEQUAL
+*           Compare two doubles for equality.
+*        astMAX
+*           Return maximum of two values.
+*        astMIN
+*           Return minimum of two values.
+*        astMAKE_CHECK
+*           Implement the astCheck<Class>_ function for a class.
+*        astMAKE_CLEAR
+*           Implement a method to clear an attribute value for a class.
+*        astMAKE_GET
+*           Implement a method to get an attribute value for a class.
+*        astMAKE_ISA
+*           Implement the astIsA<Class>_ function for a class.
+*        astMAKE_SET
+*           Implement a method to set an attribute value for a class.
+*        astMAKE_TEST
+*           Implement a method to test if an attribute has been set for a
+*           class.
+*        astMEMBER
+*           Locate a member function.
+
+*  Type Definitions:
+*     Public:
+*        AstObject
+*           Object type.
+*
+*     Protected:
+*        AstObjectVtab
+*           Object virtual function table type.
+
+*  Feature Test Macros:
+*     AST_CHECK_CLASS
+*        If the AST_CHECK_CLASS macro is defined, then Object class
+*        checking is enabled for all internal function invocations
+*        within the AST library. Otherwise, this checking is
+*        omitted. This macro should normally be defined as a compiler
+*        option during library development and debugging, but left
+*        undefined for software releases, so as to improve
+*        peformance. Class checking by the AST public interface is not
+*        affected by this macro.
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+*     astFORTRAN77
+*        If the astFORTRAN77 macro is defined, reporting of error
+*        context information is suppressed. This is necessary when
+*        implementing foreign language interfaces to the AST library, as
+*        otherwise the file names and line numbers given would refer
+*        to the interface implementation rather than the user's own
+*        code.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     30-JAN-1996 (RFWS):
+*        Original version.
+*     19-APR-1996 (RFWS):
+*        Added macros for implementing attribute access methods.
+*     3-JUL-1996 (RFWS):
+*        Added new definitions to support the external interface.
+*     10-SEP-1996 (RFWS):
+*        Added loader and related facilities.
+*     30-MAY-1997 (RFWS):
+*        Add the ID attribute.
+*     14-JUL-1997 (RFWS):
+*        Add astExempt function.
+*     20-JAN-1998 (RFWS):
+*        Make the astClear and astVSet methods virtual.
+*     15-SEP-1999 (RFWS):
+*        Made the astAnnulId function accessible to protected code.
+*     3-APR-2001 (DSB):
+*        Added Ident attribute.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitObjectVtab method.
+*     30-APR-2003 (DSB):
+*        Added macros AST__VMAJOR, AST__VMINOR and AST__RELEASE.
+*        Added astVersion function.
+*     7-FEB-2004 (DSB):
+*        Added astEscapes function.
+*     11-MAR-2005 (DSB):
+*        Added UseDefs attribute.
+*     7-FEB-2006 (DSB):
+*        Added astTune function.
+*     14-FEB-2006 (DSB):
+*        Added ObjSize attribute.
+*     23-FEB-2006 (DSB):
+*        Moved AST__TUNULL from this file to memory.h.
+*     10-MAY-2006 (DSB):
+*        Added astEQUAL, astMAX and astMIN.
+*     26-MAY-2006 (DSB):
+*        Make all system includes unconditional, so that makeh is not
+*        confused when creating ast.h.
+*     22-JUN-2007 (DSB):
+*        - Make astVSet return a pointer to dynamic memory holding the
+*        expanded setting string.
+*        - Add ast astSetVtab and astCast.
+*     22-APR-2008 (DSB):
+*        Added astSame.
+*     7-APR-2010 (DSB):
+*        Added astHasAttribute.
+*--
+*/
+
+/* Include files. */
+/* ============== */
+/* Configuration results. */
+/* ---------------------- */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Interface definitions. */
+/* ---------------------- */
+#include "error.h"               /* Error reporting facilities */
+#include "version.h"             /* Version number macros */
+
+/* C header files. */
+/* --------------- */
+#include <stddef.h>
+#include <stdarg.h>
+#include <float.h>
+#include <stdio.h>
+
+#if defined(THREAD_SAFE)
+#include <pthread.h>
+#endif
+
+/* Macros. */
+/* ======= */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Set to "1" (yes) or "0" (no) to indicate if AST was build with threads
+   support. */
+#define AST__THREADSAFE @THREADS@
+
+#if defined(astCLASS )
+#define AST__GETATTRIB_BUFF_LEN 50 /* Length of string returned by GetAttrib. */
+#define AST__ASTGETC_MAX_STRINGS 50 /* Number of string values to buffer within astGetC */
+
+/* Values supplied to astManageLock */
+#define AST__LOCK 1      /* Lock the object */
+#define AST__UNLOCK 2    /* Unlock the object */
+#define AST__CHECKLOCK 3 /* Check if the object is locked */
+
+/* Values returned by astThread */
+#define AST__UNLOCKED 1  /* Object is unlocked */
+#define AST__RUNNING 2   /* Object is locked by the running thread */
+#define AST__OTHER 3     /* Object is locked by another thread */
+
+#endif
+
+/* Value that indicates that two classes are not in direct line from each
+   other. */
+#if defined(astCLASS )
+#define AST__COUSIN -1000000
+#endif
+
+/*
+*+
+*  Name:
+*     astINVOKE
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Invoke an AST function.
+
+*  Synopsis:
+*     #include "object.h"
+*     astINVOKE(rettype,function)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an invocation of an AST function, together
+*     with any additional actions required to support it.  The actual
+*     expansion depends on whether the macro is expanded in internal
+*     code (astCLASS defined) or external code (astCLASS undefined)
+*     and it therefore hides the differences between these two
+*     interfaces.
+
+*  Parameters:
+*     rettype
+*        A character to indicate the type of result returned by the function:
+*
+*        V
+*           The function returns a value (including void or a pointer
+*           value, but excluding an Object pointer). astINVOKE will
+*           return the value unchanged.
+*        O
+*           The function returns an Object pointer. astINVOKE will
+*           convert it to an Object identifier if necessary.
+*        F
+*           The function returns a function pointer. astINVOKE will
+*           return it unchanged. This is typically used when the
+*           function has a variable argument list. In this case the
+*           function name is passed to astINVOKE without its argument
+*           list and a pointer to the function is returned which can
+*           then be supplied with an argument list. This avoids the
+*           need to define a macro with a variable number of arguments
+*           (which isn't allowed).
+*     function
+*        A normal invocation of the function returning the required
+*        result.  In the case of a variable argument list, the
+*        argument list should be omitted so that the function is not
+*        invoked but a function pointer is returned that may then be
+*        used to invoke it.
+
+*  Examples:
+*     #define astGetNobject(this) \
+*             astINVOKE(V,astGetNobject_(astCheckObject(this)))
+*        Defines a macro to invoke the astGetNobject_ function which
+*        returns an int.
+*     #define astClone(this) \
+*             astINVOKE(O,astClone_(astCheckObject(this)))
+*        Defines a macro to invoke the astClone_ function which
+*        returns an Object pointer.
+*     #define astSet astINVOKE(F,astSet_)
+*        Defines a macro to invoke the astSet_ function which has a
+*        variable argument list and returns void. The macro result is
+*        a pointer to the astSet_ function. This function must perform
+*        its own argument validation, as (e.g) astCheckObject cannot
+*        be invoked on its arguments via a macro.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* Define astINVOKE, which records the current file and line number
+   (in case of error) using astAt, and then invokes the function
+   supplied as an argument of the astRetV_, astRetO_ or astRetF_
+   macro.
+
+   Suppress reporting of the file and line number from internal code
+   and from foreign language interfaces by not using astAt in these
+   cases. */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define astINVOKE(rettype,function) astRet##rettype##_(function)
+#else
+#define astINVOKE(rettype,function) \
+astERROR_INVOKE(astRet##rettype##_(function))
+#endif
+
+/* astRetF_ and astRetV_ currently do nothing. */
+#define astRetF_(x) (x)
+#define astRetV_(x) (x)
+
+/* However, astRetO_ converts a pointer to an ID if necessary. */
+#if defined(astCLASS)
+#define astRetO_(x) ((void *)(x))
+#else
+#define astRetO_(x) ((void *)astMakeId_((AstObject *)(x),STATUS_PTR))
+#endif
+
+/*
+*+
+*  Name:
+*     astINVOKE_CHECK
+*     astINVOKE_ISA
+
+*  Type:
+*     Protected macros.
+
+*  Purpose:
+*     Invoke the astCheck<Class>_ and astIsA<Class>_ functions.
+
+*  Synopsis:
+*     #include "object.h"
+*     astINVOKE_CHECK(class,this,force)
+*     astINVOKE_ISA(class,this)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     These macros expand to invocations of the standard
+*     astCheck<Class>_ and astIsA<Class>_ functions for a class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class for which the function
+*        is to be invoked.
+*     this
+*        The "this" argument (the Object pointer) to be passed to the
+*        function.
+*     force
+*        Type checking takes time, and so can be disabled within the
+*        protected context in order to save time. Setting "force" to
+*        zero causes the astINVOKE_CHECK macro to skip the class check
+*        in a protected context (it assumes that AST "knows what it is
+*        doing"). Setting "force" to a non-zero value forces the class
+*        check even in a protected context.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* For the public interface (and also internally if AST_CHECK_CLASS is
+   defined), define astINVOKE_CHECK to invoke the astCheck<Class>
+   function. */
+#if !defined(astCLASS) || defined(AST_CHECK_CLASS)
+#define astINVOKE_CHECK(class,this,force) \
+astCheck##class##_((Ast##class *)astEnsurePointer_(this),astGetStatusPtr)
+
+/* For the internal interface, astINVOKE_CHECK omits the
+   astCheck<class> function (unless AST_CHECK_CLASS is defined). */
+#else
+
+#define astINVOKE_CHECK(class,this,force) ( (force) ? \
+   ( astCheck##class##_((Ast##class *)astEnsurePointer_(this),astGetStatusPtr) ) : \
+   ( (Ast##class *) astEnsurePointer_(this) ) )
+
+#endif
+
+/* Define astINVOKE_ISA to invoke the astIsA<Class> function. */
+#if defined(astCLASS)            /* Protected */
+#define astINVOKE_ISA(class,this) \
+astIsA##class##_((const Ast##class *)(this),status)
+#else                            /* Public */
+#define astINVOKE_ISA(class,this) \
+astINVOKE(V,astIsA##class##_((const Ast##class *)astEnsurePointer_(this),astGetStatusPtr))
+#endif
+
+/* The astEnsurePointer_ macro ensures a true C pointer, converting
+   from an ID if necessary. */
+#if defined(astCLASS)            /* Protected */
+#define astEnsurePointer_(x) ((void *)(x))
+#else                            /* Public */
+#define astEnsurePointer_(x) ((void *)astCheckLock_(astMakePointer_((AstObject *)(x),STATUS_PTR),STATUS_PTR))
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_CHECK
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Implement the astCheck<Class>_ function for a class.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_CHECK(class)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of the public astCheck<Class>_
+*     function (q.v.) which validates membership of a specified class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class whose membership is to be
+*        validated.
+
+*  Notes:
+*     -  This macro is provided so that class definitions can easiy
+*     implement the astCheck<Class>_ function, which is essentially the same
+*     for each class apart for a change of name.
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+#ifndef MEM_DEBUG
+
+/* Define the macro. */
+#define astMAKE_CHECK(class) \
+\
+/* Declare the function (see the object.c file for a prologue). */ \
+Ast##class *astCheck##class##_( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return this; \
+\
+/* Check if the object is a class member. */ \
+   if ( !astIsA##class( this ) ) { \
+\
+/* If not, but the pointer was valid (which means it identifies an Object \
+   of some sort), then report more information about why this Object is \
+   unsuitable. */ \
+      if ( astOK ) { \
+         astError( AST__OBJIN, "Pointer to " #class " required, but pointer " \
+                   "to %s given.", status, astGetClass( this ) ); \
+      } \
+   } \
+\
+/* Return the pointer value supplied. */ \
+   return this; \
+}
+
+/* Define the macro with memory debugging facilities. */
+#else
+
+#define astMAKE_CHECK(class) \
+\
+/* Declare the function (see the object.c file for a prologue). */ \
+Ast##class *astCheck##class##_( Ast##class *this, int *status ) { \
+\
+   char buf[100]; \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return this; \
+\
+/* Check if the object is a class member. */ \
+   if ( !astIsA##class( this ) ) { \
+\
+/* If not, but the pointer was valid (which means it identifies an Object \
+   of some sort), then report more information about why this Object is \
+   unsuitable. */ \
+      if ( astOK ) { \
+         astError( AST__OBJIN, "Pointer to " #class " required, but pointer " \
+                   "to %s given.", status, astGetClass( this ) ); \
+      }\
+\
+   } else { \
+\
+/* Call the astMemoryUse function to report it if the memory block is \
+   being watched. */ \
+      sprintf( buf, "checked (refcnt: %d)", astGetRefCount_( (AstObject *) this, status ) ); \
+      astMemoryUse( this, buf ); \
+   } \
+\
+/* Return the pointer value supplied. */ \
+   return this; \
+}
+#endif
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_CLEAR
+
+*  Purpose:
+*     Implement a method to clear an attribute value for a class.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_CLEAR(class,attribute,component,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( Ast<Class> *this )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( Ast<Class> *this )
+*
+*     which implement a method for clearing a specified attribute value for
+*     a class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class to which the attribute belongs.
+*     attribute
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. Label in "astClearLabel").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value.
+
+*  Examples:
+*     astMAKE_CLEAR(MyStuff,Flag,flag,-1)
+*        Implements the astClearFlag method for the MyStuff class which
+*        operates by setting the "flag" structure component to -1 to indicate
+*        that it has no value.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_CLEAR(class,attribute,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attribute( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Assign the "clear" value. */ \
+   this->component = (assign); \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attribute##_( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,class,Clear##attribute))( this, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_CLEAR1
+
+*  Purpose:
+*     Implement a method to clear an attribute value for a class, reporting
+*     an error if the object has more than one reference.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_CLEAR1(class,attribute,component,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( Ast<Class> *this )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( Ast<Class> *this )
+*
+*     which implement a method for clearing a specified attribute value for
+*     a class. An error is reported if the object has a reference count that
+*     is greater than one.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class to which the attribute belongs.
+*     attribute
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. Label in "astClearLabel").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_CLEAR1(class,attribute,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attribute( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Report an error if the object has been cloned (i.e. has a reference \
+   count that is greater than one). */ \
+   if( astGetRefCount( this ) > 1 ) { \
+      astError( AST__IMMUT, "astClear(%s): The " #attribute "attribute of " \
+                "the supplied %s cannot be cleared because the %s has " \
+                "been cloned (programming error).", status, \
+                astGetClass(this), astGetClass(this), astGetClass(this) ); \
+\
+/* Otherwise, assign the "clear" value in the structure component. */ \
+   } else { \
+      this->component = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attribute##_( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,class,Clear##attribute))( this, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_GET
+
+*  Purpose:
+*     Implement a method to get an attribute value for a class.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_GET(class,attribute,type,bad_value,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( Ast<Class> *this )
+*
+*     and an external interface function of the form:
+*
+*        <Type> astGet<Attribute>_( Ast<Class> *this )
+*
+*     which implement a method for getting a specified attribute value for a
+*     class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class to which the attribute belongs.
+*     attribute
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. Label in "astGetLabel").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the inherited error status is set, or if
+*        the function fails.
+*     assign
+*        An expression that evaluates to the value to be returned.
+
+*  Examples:
+*     astMAKE_GET(MyStuff,Flag,int,0,( this->flag == 1 ))
+*        Implements the astGetFlag method for the MyStuff class which operates
+*        by examining the integer "flag" structure component and comparing it
+*        with the value 1 to see if it is set. A value of 0 is returned if the
+*        method fails to complete successfully.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_GET(class,attribute,type,bad_value,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type Get##attribute( Ast##class *this, int *status ) { \
+   type result;                  /* Result to be returned */ \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Assign the result value. */ \
+   result = (assign); \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */  \
+type astGet##attribute##_( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,class,Get##attribute))( this, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_ISA
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Implement the astIsA<Class>_ function for a class.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_ISA(class,parent)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of the public
+*     astIsA<Class>_ function (q.v.) which checks membership of a
+*     specified class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class whose membership is to be
+*        tested.
+*     parent
+*        The name of the parent class.
+
+*  Notes:
+*     - This macro is provided so that class definitions can easiy
+*     implement the astIsA<Class>_ function, which is essentially the
+*     same for each class apart for a change of name.
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_ISA(class,parent) \
+\
+/* Declare the function (see the object.c file for a prologue). */ \
+int astIsA##class##_( const Ast##class *this, int *status ) { \
+\
+/* Local Variables: */ \
+   int isa = 0;                  /* Is object a member? */ \
+\
+/* To test if the object is correctly constructed, we first test if it is a \
+   member of the parent class. This improves the security of the test by \
+   checking the object structure from the base Object class downwards \
+   (without this, the "magic numbers" that identify classes might be \
+   encountered by accident or we might address parts of the Object which \
+   don't exist). */ \
+   if ( astIsA##parent( this ) ) { \
+\
+/* Obtain the Object's size and check it is adequate for an object of the \
+   proposed type (this avoids any attempt to access derived class data that \
+   doesn't exist and therefore lies outside the memory allocated for the \
+   object). */ \
+      if ( ( (AstObject *) this )->size >= sizeof( Ast##class ) ) { \
+\
+/* If OK, see whether the check component in the object's virtual function \
+   table matches the expected "magic" value. */ \
+         isa = ( *astMEMBER(this,class,id.check) == &class_check ); \
+      } \
+   } \
+\
+/* Return the result. */ \
+   return isa; \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_SET
+
+*  Purpose:
+*     Implement a method to set an attribute value for a class.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_SET(class,attribute,type,component,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( Ast<Class> *this, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( Ast<Class> *this, <Type> value )
+*
+*     which implement a method for setting a specified attribute value for a
+*     class.
+
+*  Parameters:
+*      class
+*         The name (not the type) of the class to which the attribute belongs.
+*      attribute
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. Label in "astSetLabel").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+
+*  Examples:
+*     astMAKE_SET(MyStuff,Flag,int,flag,( value != 0 ))
+*        Implements the astSetFlag method for the MyStuff class which operates
+*        by setting the "flag" structure component to 0 or 1 depending on
+*        whether the "value" parameter is non-zero or not.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_SET(class,attribute,type,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attribute( Ast##class *this, type value, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Store the new value in the structure component. */ \
+   this->component = (assign); \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attribute##_( Ast##class *this, type value, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,class,Set##attribute))( this, value, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_SET1
+
+*  Purpose:
+*     Implement a method to set an attribute value for a class, reporting
+*     an error if the object has more than one reference.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_SET1(class,attribute,type,component,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( Ast<Class> *this, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( Ast<Class> *this, <Type> value )
+*
+*     which implement a method for setting a specified attribute value for a
+*     class. An error is reported if the object has a reference count that
+*     is greater than one.
+
+*  Parameters:
+*      class
+*         The name (not the type) of the class to which the attribute belongs.
+*      attribute
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. Label in "astSetLabel").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_SET1(class,attribute,type,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attribute( Ast##class *this, type value, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Report an error if the object has been cloned (i.e. has a reference \
+   count that is greater than one). */ \
+   if( astGetRefCount( this ) > 1 ) { \
+      astError( AST__IMMUT, "astSet(%s): The " #attribute "attribute of " \
+                "the supplied %s cannot be changed because the %s has " \
+                "been cloned (programming error).", status, \
+                astGetClass(this), astGetClass(this), astGetClass(this) ); \
+\
+/* Otherwise, store the new value in the structure component. */ \
+   } else { \
+      this->component = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attribute##_( Ast##class *this, type value, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,class,Set##attribute))( this, value, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMAKE_TEST
+
+*  Purpose:
+*     Implement a method to test if an attribute has been set for a class.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMAKE_TEST(class,attribute,assign)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static int Test<Attribute>( Ast<Class> *this )
+*
+*     and an external interface function of the form:
+*
+*        int astTest<Attribute>_( Ast<Class> *this )
+*
+*     which implement a method for testing if a specified attribute has been
+*     set for a class.
+
+*  Parameters:
+*      class
+*         The name (not the type) of the class to which the attribute belongs.
+*      attribute
+*         The name of the attribute to be tested, as it appears in the function
+*         name (e.g. Label in "astTestLabel").
+*      assign
+*         An expression that evaluates to 0 or 1, to be used as the returned
+*         value.
+
+*  Examples:
+*     astMAKE_TEST(MyStuff,Flag,( this->flag != -1 ))
+*        Implements the astTestFlag method for the MyStuff class which operates
+*        by testing the "flag" structure component to see if it is set to a
+*        value other than -1.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define astMAKE_TEST(class,attribute,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static int Test##attribute( Ast##class *this, int *status ) { \
+   int result;                   /* Value to return */ \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Assign the result value. */ \
+   result = (assign); \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */ \
+int astTest##attribute##_( Ast##class *this, int *status ) { \
+\
+/* Check the inherited error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,class,Test##attribute))( this, status ); \
+}
+#endif
+
+#if defined(astCLASS)            /* Protected */
+/*
+*+
+*  Name:
+*     astMEMBER
+
+*  Purpose:
+*     Locate a member function.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     #include "object.h"
+*     astMEMBER(this,class,method)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     This macro evaluates to the address where the pointer to a
+*     specified Object member function is stored. Typically, this will
+*     be used to obtain a pointer to the member function so that it
+*     can be invoked. It may also be used to assign a new function
+*     pointer so that a derived class can re-define a virtual function
+*     and hence over-ride an inherited method.
+
+*  Parameters:
+*     this
+*        Pointer to an Object belonging to the class for which the
+*        virtual function is required. This must either be the class
+*        that originally defined the method, or one derived from it.
+*     class
+*        Name of the class that originally defined the method. This
+*        may differ from (i.e. be an ancestor of) the class to which
+*        "this" belongs.
+*     method
+*        Name of the method whose member function is to be located.
+
+*  Returned Value:
+*     The address where the member function pointer is stored (the
+*     type of the result is determined by the type of the member
+*     function itself).
+
+*  Examples:
+*     astMEMBER(this,Gnome,astFish)
+*        Returns the address where the pointer to the function that
+*        implements the astFish method for the "this" object is
+*        stored. The Gnome class should be where the astFish method
+*        was first defined (i.e. from where it was inherited by
+*        "this").
+*     (**astMEMBER(this,Gnome,astFish))( this, arg1, arg2 );
+*        Invokes the virtual function that implements the astFish
+*        method for object "this" and passes it additional arguments
+*        "arg2" and "arg2".  Again, Gnome should be the class that
+*        originally defined the astFish method.
+*     *astMEMBER(this,Gnome,astFish) = myFish;
+*        Stores a pointer to the myFish function so that it replaces
+*        the virtual function that previously implemented the astFish
+*        method for the "this" object. Note that all objects in the
+*        same class as "this" are affected, but objects in class
+*        "class" are not affected (unless it happens to be the class
+*        to which "this" belongs).
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* A subsiduary macro that returns a pointer to the vtab of an object,
+   cast to an AstObjectVtab. */
+#define astVTAB(this) (((AstObject*)(this))->vtab)
+
+/* Define the macro. This functions by (a) casting the Object pointer
+   to type (AstObject *) and locating the Object's virtual function
+   table (b) casting the table pointer to the correct type
+   (AstClassVtab *) for the class in which the method pointer resides,
+   (c) locating the component holding the member function pointer, and
+   (d) taking its address. */
+#define astMEMBER(this,class,method) \
+(&((Ast##class##Vtab*)astVTAB(this))->method)
+
+#endif
+
+/*
+*+
+*  Name:
+*     astPROTO_CHECK
+*     astPROTO_ISA
+
+*  Type:
+*     Protected macros.
+
+*  Purpose:
+*     Prototype the astCheck<Class>_ and astIsA<Class>_ functions.
+
+*  Synopsis:
+*     #include "object.h"
+*     astPROTO_CHECK(class)
+*     astPROTO_ISA(class)
+
+*  Class Membership:
+*     Defined by the Object class.
+
+*  Description:
+*     These macros expands to function prototypes for the
+*     astCheck<Class>_ and astIsA<Class>_ functions (q.v.) which
+*     validate and test for membership of a specified class.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class whose membership is to
+*        be validated.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* Define the macros. */
+#define astPROTO_CHECK(class) Ast##class *astCheck##class##_( Ast##class *, int * );
+#define astPROTO_ISA(class) int astIsA##class##_( const Ast##class *, int * );
+
+/* Macros which return the maximum and minimum of two values. */
+#define astMAX(aa,bb) ((aa)>(bb)?(aa):(bb))
+#define astMIN(aa,bb) ((aa)<(bb)?(aa):(bb))
+
+/* Check for equality of floating point values. We cannot compare bad values
+   directly because of the danger of floating point exceptions, so bad
+   values are dealt with explicitly. */
+#define astEQUALS(aa,bb,tol) (((aa)==AST__BAD)?(((bb)==AST__BAD)?1:0):(((bb)==AST__BAD)?0:(fabs((aa)-(bb))<=(tol)*astMAX((fabs(aa)+fabs(bb))*DBL_EPSILON,DBL_MIN))))
+#define astEQUAL(aa,bb) astEQUALS(aa,bb,1.0E5)
+
+
+/* AST__NULL. */
+/* ---------- */
+/* Define the AST__NULL macro, which evaluates to a null Object
+   pointer. */
+#define AST__NULL (astI2P(0))
+
+
+#if defined(astCLASS)            /* Protected */
+
+/* Test the validy of an attribute value  */
+/* -------------------------------------  */
+/* If the set attribute value is invalid, clear it. These macros should
+   be used in a context in which error reporting has been deferred by
+   calling astReporting( 0 ). */
+
+#define astCLEAN_ATTRIB(attr) \
+   if( astTest##attr(this) ) { \
+      astSet##attr( this, astGet##attr( this ) ); \
+      if( !astOK ) { \
+         astClearStatus; \
+         astClear##attr( this ); \
+      } \
+   }
+
+#define astCLEAN_INDEXED_ATTRIB(attr,index) \
+   if( astTest##attr(this,index) ) { \
+      astSet##attr( this, index, astGet##attr( this, index ) ); \
+      if( !astOK ) { \
+         astClearStatus; \
+         astClear##attr( this, index ); \
+      } \
+   }
+
+#endif
+
+
+#if defined(astCLASS)            /* Protected */
+#define astSetVtabClassIdentifier(vtab,id_ptr) \
+   ((AstObjectVtab *)(vtab))->top_id = (id_ptr)
+#endif
+
+/* Type Definitions. */
+/* ================= */
+
+/* Object structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstObject {
+
+/* Attributes specific to objects in this class. */
+   unsigned long check;          /* Check value to identify Objects */
+   size_t size;                  /* Amount of memory used by Object */
+   struct AstObjectVtab *vtab;   /* Pointer to virtual function table */
+   char dynamic;                 /* Memory allocated dynamically? */
+   int ref_count;                /* Number of active pointers to the Object */
+   char *id;                     /* Pointer to ID string */
+   char *ident;                  /* Pointer to Ident string */
+   char usedefs;                 /* Use default attribute values? */
+   int iref;                     /* Object index (unique within class) */
+   void *proxy;                  /* A pointer to an external object that
+                                    acts as a foreign language proxy for the
+                                    AST object */
+#if defined(THREAD_SAFE)
+   int locker;                   /* Thread that has locked this Object */
+   pthread_mutex_t mutex1;       /* Guards access to all elements of the
+                                    Object except for the "locker" and
+                                    "ref_count" components */
+   pthread_mutex_t mutex2;       /* Guards access to the "locker" and
+                                    "ref_count" components */
+   struct AstGlobals *globals;   /* Pointer to thread-specific global data */
+#endif
+
+} AstObject;
+
+/* Class identifier structure */
+typedef struct AstClassIdentifier {
+   int *check;
+   struct AstClassIdentifier *parent;
+} AstClassIdentifier;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* The virtual function table makes a forward reference to the
+   AstChannel structure which is not defined until "channel.h" is
+   included (below). Hence make a preliminary definition available
+   now. */
+struct AstChannel;
+
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstObjectVtab {
+
+/* A unique identifier for this class. */
+   AstClassIdentifier id;
+
+/* Pointer to the structure that identifies the top-level class described
+   by the whole vtab (of which the AstObjectVtab is just the first,
+   lowest-level, component). */
+   AstClassIdentifier *top_id;
+
+/* Pointer to a dynamically allocated string holding the default
+   attribute values to use when creating new objects. These are read from
+   environment variables of the form "<CLASSNAME>_OPTIONS". */
+   const char *defaults;
+
+/* Properties specific to this class. */
+   void ( *CleanAttribs )( AstObject *, int * );
+   AstObject *( *Cast )( AstObject *, AstObject *, int * );
+   const char *( *GetID )( AstObject *, int * );
+   const char *( *GetIdent )( AstObject *, int * );
+   const char *(* GetAttrib)( AstObject *, const char *, int * );
+   int (* TestAttrib)( AstObject *, const char *, int * );
+   int (* TestID)( AstObject *, int * );
+   int (* Same)( AstObject *, AstObject *, int * );
+   int (* HasAttribute)( AstObject *, const char *, int * );
+   int (* TestIdent)( AstObject *, int * );
+   void (* Clear)( AstObject *, const char *, int * );
+   void (* ClearAttrib)( AstObject *, const char *, int * );
+   void (* ClearID)( AstObject *, int * );
+   void (* ClearIdent)( AstObject *, int * );
+   void (* Dump)( AstObject *, struct AstChannel *, int * );
+   int (* Equal)( AstObject *, AstObject *, int * );
+   void (* SetAttrib)( AstObject *, const char *, int * );
+   void (* SetID)( AstObject *, const char *, int * );
+   void (* SetIdent)( AstObject *, const char *, int * );
+   void (* Show)( AstObject *, int * );
+   void (* VSet)( AstObject *, const char *, char **, va_list, int * );
+   void (* EnvSet)( AstObject *, int * );
+
+   void *(* GetProxy)( AstObject *, int * );
+   void (* SetProxy)( AstObject *, void *, int * );
+
+   int (* GetObjSize)( AstObject *, int * );
+
+   int (* TestUseDefs)( AstObject *, int * );
+   int (* GetUseDefs)( AstObject *, int * );
+   void (* SetUseDefs)( AstObject *, int, int * );
+   void (* ClearUseDefs)( AstObject *, int * );
+
+   const char *class;            /* Pointer to class name string */
+   void (** delete)( AstObject *, int * ); /* Pointer to array of destructors */
+   void (** copy)( const AstObject *, AstObject *, int * ); /* Copy constructors */
+   void (** dump)( AstObject *, struct AstChannel *, int * ); /* Dump functions */
+   const char **dump_class;      /* Dump function class string pointers */
+   const char **dump_comment;    /* Dump function comment string pointers */
+   int ndelete;                  /* Number of destructors */
+   int ncopy;                    /* Number of copy constructors */
+   int ndump;                    /* Number of dump functions */
+   int nobject;                  /* Number of active objects in the class */
+   int nfree;                    /* No. of entries in "free_list" */
+   AstObject **free_list;        /* List of pointers for freed Objects */
+
+#if defined(THREAD_SAFE)
+   int (* ManageLock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+} AstObjectVtab;
+#endif
+
+#if defined(THREAD_SAFE) && defined(astCLASS)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstObjectGlobals {
+   AstObjectVtab Class_Vtab;
+   int Class_Init;
+   int Retain_Esc;
+   int Context_Level;
+   int *Active_Handles;
+   char GetAttrib_Buff[ AST__GETATTRIB_BUFF_LEN + 1 ];
+   char *AstGetC_Strings[ AST__ASTGETC_MAX_STRINGS ];
+   int AstGetC_Istr;
+   int AstGetC_Init;
+   int Nvtab;
+   AstObjectVtab **Known_Vtabs;
+} AstObjectGlobals;
+
+#endif
+
+/* More include files. */
+/* =================== */
+/* The interface to the Channel class must be included here (after the
+   type definitions for the Object class) because "channel.h" itself
+   includes this file ("object.h"), although "object.h" refers to the
+   AstChannel structure above. This seems a little strange at first,
+   but is simply analogous to making a forward reference to a
+   structure type when recursively defining a normal C structure
+   (except that here the definitions happen to be in separate include
+   files). */
+#include "channel.h"
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Object)           /* Validate class membership */
+astPROTO_ISA(Object)             /* Test class membership */
+
+/* NB. There is no constructor function for this class. */
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstObject *astInitObject_( void *, size_t, int, AstObjectVtab *,
+                           const char *, int * );
+
+/* Vtab Initialiser. */
+void astInitObjectVtab_( AstObjectVtab *, const char *, int * );
+
+/* Loader. */
+AstObject *astLoadObject_( void *, size_t, AstObjectVtab *,
+                           const char *, AstChannel *channel, int * );
+
+#if defined(THREAD_SAFE)
+void astInitObjectGlobals_( AstObjectGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for other class functions. */
+/* ------------------------------------- */
+#if !defined(astCLASS)           /* Public */
+void astBegin_( void );
+void astEnd_( int * );
+#endif
+
+AstObject *astI2P_( int, int * );
+AstObject *astMakeId_( AstObject *, int * );
+AstObject *astMakePointer_( AstObject *, int * );
+AstObject *astMakePointer_NoLockCheck_( AstObject *, int * );
+int astP2I_( AstObject *, int * );
+int astVersion_( int * );
+int astEscapes_( int, int * );
+int astTune_( const char *, int, int * );
+void astTuneC_( const char *, const char *, char *, int, int * );
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+#if defined(astCLASS)            /* Protected */
+AstObject *astAnnul_( AstObject *, int * );
+AstObject *astDelete_( AstObject *, int * );
+void astSet_( void *, const char *, int *, ... );
+
+#else                            /* Public */
+AstObject *astDeleteId_( AstObject *, int * );
+int astThreadId_( AstObject *, int, int * );
+void astExportId_( AstObject *, int * );
+void astImportId_( AstObject *, int * );
+void astSetId_( void *, const char *, ... )__attribute__((format(printf,2,3)));
+#endif
+
+AstObject *astAnnulId_( AstObject *, int * );
+AstObject *astCheckLock_( AstObject *, int * );
+AstObject *astClone_( AstObject *, int * );
+AstObject *astCopy_( const AstObject *, int * );
+AstObject *astFromString_( const char *, int * );
+char *astToString_( AstObject *, int * );
+const char *astGetC_( AstObject *, const char *, int * );
+double astGetD_( AstObject *, const char *, int * );
+float astGetF_( AstObject *, const char *, int * );
+int astEqual_( AstObject *, AstObject *, int * );
+int astGetI_( AstObject *, const char *, int * );
+int astHasAttribute_( AstObject *, const char *, int * );
+int astSame_( AstObject *, AstObject *, int * );
+int astTest_( AstObject *, const char *, int * );
+long astGetL_( AstObject *, const char *, int * );
+void *astGetProxy_( AstObject *, int * );
+void astClear_( AstObject *, const char *, int * );
+void astExemptId_( AstObject *, int * );
+void astLockId_( AstObject *, int, int * );
+void astSetC_( AstObject *, const char *, const char *, int * );
+void astSetD_( AstObject *, const char *, double, int * );
+void astSetF_( AstObject *, const char *, float, int * );
+void astSetI_( AstObject *, const char *, int, int * );
+void astSetL_( AstObject *, const char *, long, int * );
+void astSetProxy_( AstObject *, void *, int * );
+void astShow_( AstObject *, int * );
+void astUnlockId_( AstObject *, int, int * );
+
+#if defined(astCLASS)            /* Protected */
+
+void astCleanAttribs_( AstObject *, int * );
+AstObject *astCast_( AstObject *, AstObject *, int * );
+AstObject *astCastCopy_( AstObject *, AstObject *, int * );
+
+#if defined(THREAD_SAFE)
+int astManageLock_( AstObject *, int, int, AstObject **, int * );
+#endif
+
+int astGetObjSize_( AstObject *, int * );
+
+int astTestUseDefs_( AstObject *, int * );
+int astGetUseDefs_( AstObject *, int * );
+void astSetUseDefs_( AstObject *, int, int * );
+void astClearUseDefs_( AstObject *, int * );
+
+const char *astGetAttrib_( AstObject *, const char *, int * );
+const char *astGetClass_( const AstObject *, int * );
+const char *astGetID_( AstObject *, int * );
+const char *astGetIdent_( AstObject *, int * );
+int astClassCompare_( AstObjectVtab *, AstObjectVtab *, int * );
+int astGetNobject_( const AstObject *, int * );
+int astGetRefCount_( AstObject *, int * );
+int astTestAttrib_( AstObject *, const char *, int * );
+int astTestID_( AstObject *, int * );
+int astTestIdent_( AstObject *, int * );
+void astClearAttrib_( AstObject *, const char *, int * );
+void astClearID_( AstObject *, int * );
+void astClearIdent_( AstObject *, int * );
+void astDump_( AstObject *, AstChannel *, int * );
+void astSetAttrib_( AstObject *, const char *, int * );
+void astSetCopy_( AstObjectVtab *, void (*)( const AstObject *, AstObject *, int * ), int * );
+void astSetDelete_( AstObjectVtab *, void (*)( AstObject *, int * ), int * );
+void astSetDump_( AstObjectVtab *, void (*)( AstObject *, AstChannel *, int * ), const char *, const char *, int * );
+void astSetVtab_( AstObject *, AstObjectVtab *, int * );
+void astSetID_( AstObject *, const char *, int * );
+void astSetIdent_( AstObject *, const char *, int * );
+void astEnvSet_( AstObject *, int * );
+void astVSet_( AstObject *, const char *, char **, va_list, int * );
+
+#endif
+
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Check class membership. */
+#define astCheckObject(this) astINVOKE_CHECK(Object,this,0)
+#define astVerifyObject(this) astINVOKE_CHECK(Object,this,1)
+
+/* Test class membership. */
+#define astIsAObject(this) astINVOKE_ISA(Object,this)
+
+/* NB. There is no constructor function for this class. */
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitObject(mem,size,init,vtab,name) \
+astINVOKE(O,astInitObject_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitObjectVtab(vtab,name) astINVOKE(V,astInitObjectVtab_(vtab,name,STATUS_PTR))
+
+/* Loader. */
+#define astLoadObject(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadObject_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to other class functions. */
+/* ------------------------------------ */
+#if !defined(astCLASS)           /* Public */
+#define astBegin astBegin_()
+#define astEnd astINVOKE(V,astEnd_(STATUS_PTR))
+#else                            /* Protected */
+#define astMakePointer_NoLockCheck(id) ((void *)astMakePointer_NoLockCheck_((AstObject *)(id),STATUS_PTR))
+#endif
+
+#define astVersion astVersion_(STATUS_PTR)
+#define astEscapes(int) astEscapes_(int,STATUS_PTR)
+#define astTune(name,val) astTune_(name,val,STATUS_PTR)
+#define astTuneC(name,value,buff,bufflen) astTuneC_(name,value,buff,bufflen,STATUS_PTR)
+#define astI2P(integer) ((void *)astI2P_(integer,STATUS_PTR))
+#define astMakeId(pointer) ((void *)astMakeId_((AstObject *)(pointer),STATUS_PTR))
+#define astP2I(pointer) astP2I_((AstObject *)(pointer),STATUS_PTR)
+#define astMakePointer(id) ((void *)astCheckLock_(astMakePointer_((AstObject *)(id),STATUS_PTR),STATUS_PTR))
+#define astToString(this) astINVOKE(V,astToString_(astCheckObject(this),STATUS_PTR))
+#define astFromString(string) astINVOKE(O,astFromString_(string,STATUS_PTR))
+
+/* Interfaces to member functions. */
+/* ------------------------------- */
+/* Here we make use of astCheckObject (et al.) to validate Object
+   pointers before use. This provides a contextual error report if a
+   pointer to the wrong sort of object is supplied. In the case of an
+   external caller, it also performs the required conversion from an
+   Object identifier to a true C pointer. */
+
+/* These functions require special treatment for external use because
+   they handle Object identifiers and their resources explicitly, and
+   must therefore be passed identifier values without conversion to C
+   pointers. */
+
+#if defined(astCLASS) || defined(astFORTRAN77) /* Protected or Fotran interface */
+#define astAnnulId(this) astINVOKE(O,astAnnulId_((AstObject *)(this),STATUS_PTR))
+#endif
+
+#if defined(astCLASS)                      /* Protected only */
+#define astAnnul(this) astINVOKE(O,astAnnul_(astCheckObject(this),STATUS_PTR))
+#define astDelete(this) astINVOKE(O,astDelete_(astCheckObject(this),STATUS_PTR))
+#define astSet astINVOKE(F,astSet_)
+
+#else                                          /* Public only */
+#define astAnnul(this) astINVOKE(O,astAnnulId_((AstObject *)(this),STATUS_PTR))
+#define astDelete(this) astINVOKE(O,astDeleteId_((AstObject *)(this),STATUS_PTR))
+#define astExport(this) astINVOKE(V,astExportId_((AstObject *)(this),STATUS_PTR))
+#define astImport(this) astINVOKE(V,astImportId_((AstObject *)(this),STATUS_PTR))
+#define astSet astINVOKE(F,astSetId_)
+#define astThread(this,ptr) astINVOKE(V,astThreadId_((AstObject *)(this),ptr,STATUS_PTR))
+#endif
+
+/* Both.... */
+#define astLock(this,wait) astINVOKE(V,astLockId_((AstObject *)(this),wait,STATUS_PTR))
+#define astUnlock(this,report) astINVOKE(V,astUnlockId_((AstObject *)(this),report,STATUS_PTR))
+#define astEqual(this,that) astINVOKE(V,(((AstObject*)this==(AstObject*)that)||astEqual_(astCheckObject(this),astCheckObject(that),STATUS_PTR)))
+#define astExempt(this) astINVOKE(V,astExemptId_((AstObject *)(this),STATUS_PTR))
+#define astClear(this,attrib) astINVOKE(V,astClear_(astCheckObject(this),attrib,STATUS_PTR))
+#define astClone(this) astINVOKE(O,astClone_(astCheckObject(this),STATUS_PTR))
+#define astCopy(this) astINVOKE(O,astCopy_(astCheckObject(this),STATUS_PTR))
+#define astGetC(this,attrib) astINVOKE(V,astGetC_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetD(this,attrib) astINVOKE(V,astGetD_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetF(this,attrib) astINVOKE(V,astGetF_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetI(this,attrib) \
+astINVOKE(V,astGetI_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetL(this,attrib) \
+astINVOKE(V,astGetL_(astCheckObject(this),attrib,STATUS_PTR))
+#define astSetC(this,attrib,value) \
+astINVOKE(V,astSetC_(astCheckObject(this),attrib,value,STATUS_PTR))
+#define astSetD(this,attrib,value) \
+astINVOKE(V,astSetD_(astCheckObject(this),attrib,value,STATUS_PTR))
+#define astSetF(this,attrib,value) \
+astINVOKE(V,astSetF_(astCheckObject(this),attrib,value,STATUS_PTR))
+#define astSetI(this,attrib,value) \
+astINVOKE(V,astSetI_(astCheckObject(this),attrib,value,STATUS_PTR))
+#define astSetL(this,attrib,value) \
+astINVOKE(V,astSetL_(astCheckObject(this),attrib,value,STATUS_PTR))
+#define astShow(this) \
+astINVOKE(V,astShow_(astCheckObject(this),STATUS_PTR))
+#define astTest(this,attrib) \
+astINVOKE(V,astTest_(astCheckObject(this),attrib,STATUS_PTR))
+#define astSame(this,that) \
+astINVOKE(V,astSame_(astCheckObject(this),astCheckObject(that),STATUS_PTR))
+#define astHasAttribute(this,attrib) \
+astINVOKE(V,astHasAttribute_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetProxy(this) \
+astINVOKE(V,astGetProxy_(astCheckObject(this),STATUS_PTR))
+#define astSetProxy(this,proxy) \
+astINVOKE(V,astSetProxy_(astCheckObject(this),proxy,STATUS_PTR))
+
+
+#if defined(astCLASS)            /* Protected */
+
+#if defined(THREAD_SAFE)
+#define astManageLock(this,mode,extra,fail) \
+astINVOKE(V,astManageLock_(astCheckObject(this),mode, extra,fail,STATUS_PTR))
+#else
+#define astManageLock(this,mode,extra,fail)
+#endif
+
+#define astCleanAttribs(this) astINVOKE(V,astCleanAttribs_(astCheckObject(this),STATUS_PTR))
+#define astGetObjSize(this) astINVOKE(V,astGetObjSize_(astCheckObject(this),STATUS_PTR))
+#define astCast(this,obj) astINVOKE(O,astCast_(astCheckObject(this),astCheckObject(obj),STATUS_PTR))
+#define astCastCopy(this,obj) astCastCopy_((AstObject*)this,(AstObject*)obj,STATUS_PTR)
+
+#define astClearUseDefs(this) astINVOKE(V,astClearUseDefs_(astCheckObject(this),STATUS_PTR))
+#define astTestUseDefs(this) astINVOKE(V,astTestUseDefs_(astCheckObject(this),STATUS_PTR))
+#define astGetUseDefs(this) astINVOKE(V,astGetUseDefs_(astCheckObject(this),STATUS_PTR))
+#define astSetUseDefs(this,val) astINVOKE(V,astSetUseDefs_(astCheckObject(this),val,STATUS_PTR))
+
+#define astClearAttrib(this,attrib) \
+astINVOKE(V,astClearAttrib_(astCheckObject(this),attrib,STATUS_PTR))
+#define astClearID(this) astINVOKE(V,astClearID_(astCheckObject(this),STATUS_PTR))
+#define astClearIdent(this) astINVOKE(V,astClearIdent_(astCheckObject(this),STATUS_PTR))
+#define astDump(this,channel) \
+astINVOKE(V,astDump_(astCheckObject(this),astCheckChannel(channel),STATUS_PTR))
+
+#define astGetAttrib(this,attrib) \
+astINVOKE(V,astGetAttrib_(astCheckObject(this),attrib,STATUS_PTR))
+#define astGetClass(this) astINVOKE(V,astGetClass_((const AstObject *)(this),STATUS_PTR))
+#define astGetID(this) astINVOKE(V,astGetID_(astCheckObject(this),STATUS_PTR))
+#define astGetIdent(this) astINVOKE(V,astGetIdent_(astCheckObject(this),STATUS_PTR))
+#define astGetNobject(this) astINVOKE(V,astGetNobject_(astCheckObject(this),STATUS_PTR))
+#define astClassCompare(class1,class2) astClassCompare_(class1,class2,STATUS_PTR)
+#define astGetRefCount(this) astINVOKE(V,astGetRefCount_(astCheckObject(this),STATUS_PTR))
+#define astSetAttrib(this,setting) \
+astINVOKE(V,astSetAttrib_(astCheckObject(this),setting,STATUS_PTR))
+#define astSetCopy(vtab,copy) \
+astINVOKE(V,astSetCopy_((AstObjectVtab *)(vtab),copy,STATUS_PTR))
+#define astSetDelete(vtab,delete) \
+astINVOKE(V,astSetDelete_((AstObjectVtab *)(vtab),delete,STATUS_PTR))
+#define astSetDump(vtab,dump,class,comment) \
+astINVOKE(V,astSetDump_((AstObjectVtab *)(vtab),dump,class,comment,STATUS_PTR))
+#define astSetVtab(object,vtab) \
+astINVOKE(V,astSetVtab_((AstObject *)object,(AstObjectVtab *)(vtab),STATUS_PTR))
+#define astSetID(this,id) astINVOKE(V,astSetID_(astCheckObject(this),id,STATUS_PTR))
+#define astSetIdent(this,id) astINVOKE(V,astSetIdent_(astCheckObject(this),id,STATUS_PTR))
+#define astVSet(this,settings,text,args) \
+astINVOKE(V,astVSet_(astCheckObject(this),settings,text,args,STATUS_PTR))
+#define astEnvSet(this) \
+astINVOKE(V,astEnvSet_(astCheckObject(this),STATUS_PTR))
+#define astTestAttrib(this,attrib) \
+astINVOKE(V,astTestAttrib_(astCheckObject(this),attrib,STATUS_PTR))
+#define astTestID(this) astINVOKE(V,astTestID_(astCheckObject(this),STATUS_PTR))
+#define astTestIdent(this) astINVOKE(V,astTestIdent_(astCheckObject(this),STATUS_PTR))
+
+/* Deprecated synonym. */
+#define astClass(this) astGetClass(this)
+#endif
+
+/* Extra stuff for debuging probnlems with object handles and memory usage */
+#ifdef MEM_DEBUG
+
+void astWatchHandle_( int );
+void astHandleUse_( int, const char *, ... );
+void astHandleAlarm_( const char *, va_list );
+
+#define astWatchHandle astWatchHandle_
+#define astHandleUse astHandleUse_
+#define astHandleAlarm astHandleAlarm_
+
+#else
+
+#define astWatchHandle
+#define astHandleUse
+#define astHandleAlarm
+
+#endif
+
+#endif
+
diff --git a/ast/pal.h b/ast/pal.h
new file mode 100644
index 0000000..31ff8c7
--- /dev/null
+++ b/ast/pal.h
@@ -0,0 +1,582 @@
+#ifndef PALHDEF
+#define PALHDEF
+
+/*
+*+
+*  Name:
+*     pal.h
+
+*  Purpose:
+*     Function prototypes for PAL routines.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Include file
+
+*  Description:
+*     Function prototypes for PAL routines.
+
+*  Authors:
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     DSBJ: David S Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version. Define all SLA prototypes in PAL form even
+*        though none are implemented.
+*     16-FEB-2012 (DSB):
+*        If the PAL and ERFA libraries distributed within AST are being
+*        used, rename the public symbols defined by both to avoid clashes
+*        with any external PAL or ERFA library.
+*     11-JAN-2017 (DSB):
+*        Update to PAL V0.9.7.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software; you can redistribute it and/or
+*     modify it under the terms of the GNU General Public License as
+*     published by the Free Software Foundation; either version 3 of
+*     the License, or (at your option) any later version.
+*
+*     This program is distributed in the hope that it will be
+*     useful, but WITHOUT ANY WARRANTY; without even the implied
+*     warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+*     PURPOSE. See the GNU General Public License for more details.
+*
+*     You should have received a copy of the GNU General Public License
+*     along with this program; if not, write to the Free Software
+*     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+*     USA.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+/* Include configuration results in order to get any definition for the
+   EXTERNAL_PAL macro. This macro is set if the --with-external_pal
+   option was set when AST was configured. */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* If we not using external PAL and ERFA libraries, rename all PAL
+   functions so that references to "palXxx" below get translated to
+   "astPalXxx". */
+#ifndef EXTERNAL_PAL
+#include "pal2ast.h"
+#endif
+
+/* Prototypes for all PAL functions. Note, these should be copied
+   verbatim from the pal.h file included in the pal library. We cannot
+   simply "include pal./pal.h" here as the macros established in
+   pal2ast.h above would then not be used to replace the names of the pal
+   function. */
+
+/* ------------- start of pal/pal.h ------------------------------ */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <math.h>
+#include <stdlib.h>
+
+void palAddet ( double rm, double dm, double eq, double *rc, double *dc );
+
+void palAfin ( const char *string, int *iptr, float *a, int *j );
+
+double palAirmas ( double zd );
+
+void palAltaz ( double ha, double dec, double phi,
+                double *az, double *azd, double *azdd,
+                double *el, double *eld, double *eldd,
+                double *pa, double *pad, double *padd );
+
+void palAmp ( double ra, double da, double date, double eq,
+              double *rm, double *dm );
+
+void palAmpqk ( double ra, double da, double amprms[21],
+                double *rm, double *dm );
+
+void palAop ( double rap, double dap, double date, double dut,
+              double elongm, double phim, double hm, double xp,
+              double yp, double tdk, double pmb, double rh,
+              double wl, double tlr,
+              double *aob, double *zob, double *hob,
+              double *dob, double *rob );
+
+void palAoppa ( double date, double dut, double elongm, double phim,
+                double hm, double xp, double yp, double tdk, double pmb,
+                double rh, double wl, double tlr, double aoprms[14] );
+
+void palAoppat ( double date, double aoprms[14] );
+
+void palAopqk ( double rap, double dap, const double aoprms[14],
+                double *aob, double *zob, double *hob,
+                double *dob, double *rob );
+
+void palAtmdsp ( double tdk, double pmb, double rh, double wl1,
+                 double a1, double b1, double wl2, double *a2, double *b2 );
+
+void palAv2m ( float axvec[3], float rmat[3][3] );
+
+float palBear ( float a1, float b1, float a2, float b2 );
+
+void palCaf2r ( int ideg, int iamin, float asec, float *rad, int *j );
+
+void palCaldj ( int iy, int im, int id, double *djm, int *j );
+
+void palCalyd ( int iy, int im, int id, int *ny, int *nd, int *j );
+
+void palCc2s ( float v[3], float *a, float *b );
+
+void palCc62s ( float v[6], float *a, float *b, float *r,
+                float *ad, float *bd, float *rd );
+
+void palCd2tf ( int ndp, float days, char *sign, int ihmsf[4] );
+
+void palCldj ( int iy, int im, int id, double *djm, int *j );
+
+void palClyd ( int iy, int im, int id, int *ny, int *nd, int *jstat );
+
+void palCombn ( int nsel, int ncand, int list[], int *j );
+
+void palCr2af ( int ndp, float angle, char *sign, int idmsf[4] );
+
+void palCr2tf ( int ndp, float angle, char *sign, int ihmsf[4] );
+
+void palCs2c ( float a, float b, float v[3] );
+
+void palCs2c6 ( float a, float b, float r, float ad,
+                float bd, float rd, float v[6] );
+
+void palCtf2d ( int ihour, int imin, float sec, float *days, int *j );
+
+void palCtf2r ( int ihour, int imin, float sec, float *rad, int *j );
+
+void palDaf2r ( int ideg, int iamin, double asec, double *rad, int *j );
+
+void palDafin ( const char *string, int *iptr, double *a, int *j );
+
+double palDat ( double dju );
+
+void palDav2m ( double axvec[3], double rmat[3][3] );
+
+double palDbear ( double a1, double b1, double a2, double b2 );
+
+void palDbjin ( const char *string, int *nstrt,
+                double *dreslt, int *jf1, int *jf2 );
+
+void palDc62s ( double v[6], double *a, double *b, double *r,
+                double *ad, double *bd, double *rd );
+
+void palDcc2s ( double v[3], double *a, double *b );
+
+void palDcmpf ( double coeffs[6], double *xz, double *yz, double *xs,
+                double *ys, double *perp, double *orient );
+
+void palDcs2c ( double a, double b, double v[3] );
+
+void palDd2tf ( int ndp, double days, char *sign, int ihmsf[4] );
+
+void palDe2h ( double ha, double dec, double phi,
+               double *az, double *el );
+
+void palDeuler ( const char *order, double phi, double theta, double psi,
+                 double rmat[3][3] );
+
+void palDfltin ( const char *string, int *nstrt, double *dreslt, int *jflag );
+
+void palDh2e ( double az, double el, double phi, double *ha, double *dec);
+
+void palDimxv ( double dm[3][3], double va[3], double vb[3] );
+
+void palDjcal ( int ndp, double djm, int iymdf[4], int *j );
+
+void palDjcl ( double djm, int *iy, int *im, int *id, double *fd, int *j );
+
+void palDm2av ( double rmat[3][3], double axvec[3] );
+
+void palDmat ( int n, double *a, double *y, double *d, int *jf, int *iw );
+
+void palDmoon ( double date, double pv[6] );
+
+void palDmxm ( double a[3][3], double b[3][3], double c[3][3] );
+
+void palDmxv ( double dm[3][3], double va[3], double vb[3] );
+
+double palDpav ( double v1[3], double v2[3] );
+
+void palDr2af ( int ndp, double angle, char *sign, int idmsf[4] );
+
+void palDr2tf ( int ndp, double angle, char *sign, int ihmsf[4] );
+
+double palDrange ( double angle );
+
+double palDranrm ( double angle );
+
+void palDs2c6 ( double a, double b, double r, double ad, double bd,
+                double rd, double v[6] );
+
+void palDs2tp ( double ra, double dec, double raz, double decz,
+                double *xi, double *eta, int *j );
+
+double palDsep ( double a1, double b1, double a2, double b2 );
+
+double palDsepv ( double v1[3], double v2[3] );
+
+double palDt ( double epoch );
+
+void palDtf2d ( int ihour, int imin, double sec, double *days, int *j );
+
+void palDtf2r ( int ihour, int imin, double sec, double *rad, int *j );
+
+void palDtp2s ( double xi, double eta, double raz, double decz,
+                double *ra, double *dec );
+
+void palDtp2v ( double xi, double eta, double v0[3], double v[3] );
+
+void palDtps2c ( double xi, double eta, double ra, double dec,
+                 double *raz1, double *decz1,
+                 double *raz2, double *decz2, int *n );
+
+void palDtpv2c ( double xi, double eta, double v[3],
+                 double v01[3], double v02[3], int *n );
+
+double palDtt ( double dju );
+
+void palDv2tp ( double v[3], double v0[3], double *xi, double *eta, int *j );
+
+double palDvdv ( double va[3], double vb[3] );
+
+void palDvn ( double v[3], double uv[3], double *vm );
+
+void palDvxv ( double va[3], double vb[3], double vc[3] );
+
+void palE2h ( float ha, float dec, float phi, float *az, float *el );
+
+void palEarth ( int iy, int id, float fd, float posvel[6] );
+
+void palEcleq ( double dl, double db, double date, double *dr, double *dd );
+
+void palEcmat ( double date, double rmat[3][3] );
+
+void palEcor ( float rm, float dm, int iy, int id, float fd,
+               float *rv, float *tl );
+
+void palEg50 ( double dr, double dd, double *dl, double *db );
+
+void palEl2ue ( double date, int jform, double epoch, double orbinc,
+                double anode, double perih, double aorq, double e,
+                double aorl, double dm, double u[13], int *jstat );
+
+double palEpb ( double date );
+
+double palEpb2d ( double epb );
+
+double palEpco ( char k0, char k, double e );
+
+double palEpj ( double date );
+
+double palEpj2d ( double epj );
+
+void palEpv( double date, double ph[3], double vh[3],
+             double pb[3], double vb[3] );
+
+void palEqecl ( double dr, double dd, double date, double *dl, double *db );
+
+double palEqeqx ( double date );
+
+void palEqgal ( double dr, double dd, double *dl, double *db );
+
+void palEtrms ( double ep, double ev[3] );
+
+void palEuler ( const char *order, float phi, float theta, float psi,
+                float rmat[3][3] );
+
+void palEvp ( double date, double deqx,
+              double dvb[3], double dpb[3],
+              double dvh[3], double dph[3] );
+
+void palFitxy ( int itype, int np, double xye[][2], double xym[][2],
+                double coeffs[6], int *j );
+
+void palFk425 ( double r1950, double d1950, double dr1950,
+                double dd1950, double p1950, double v1950,
+                double *r2000, double *d2000, double *dr2000,
+                double *dd2000, double *p2000, double *v2000 );
+
+void palFk45z ( double r1950, double d1950, double bepoch,
+                double *r2000, double *d2000 );
+
+void palFk524 ( double r2000, double d2000, double dr2000,
+                double dd2000, double p2000, double v2000,
+                double *r1950, double *d1950, double *dr1950,
+                double *dd1950, double *p1950, double *v1950 );
+
+void palFk52h ( double r5, double d5, double dr5, double dd5,
+                double *dr, double *dh, double *drh, double *ddh );
+
+void palFk54z ( double r2000, double d2000, double bepoch,
+                double *r1950, double *d1950,
+                double *dr1950, double *dd1950 );
+
+void palFk5hz ( double r5, double d5, double epoch,
+                double *rh, double *dh );
+
+void palFlotin ( const char *string, int *nstrt, float *reslt, int *jflag );
+
+void palGaleq ( double dl, double db, double *dr, double *dd );
+
+void palGalsup ( double dl, double db, double *dsl, double *dsb );
+
+void palGe50 ( double dl, double db, double *dr, double *dd );
+
+void palGeoc ( double p, double h, double *r, double *z );
+
+double palGmst ( double ut1 );
+
+double palGmsta ( double date, double ut1 );
+
+void palH2e ( float az, float el, float phi, float *ha, float *dec );
+
+void palH2fk5 ( double dr, double dh, double drh, double ddh,
+                double *r5, double *d5, double *dr5, double *dd5 );
+
+void palHfk5z ( double rh, double dh, double epoch,
+                double *r5, double *d5, double *dr5, double *dd5 );
+
+void palImxv ( float rm[3][3], float va[3], float vb[3] );
+
+void palInt2in ( const char *string, int *nstrt, int *ireslt, int *jflag );
+
+void palIntin ( const char *string, int *nstrt, long *ireslt, int *jflag );
+
+void palInvf ( double fwds[6], double bkwds[6], int *j );
+
+void palKbj ( int jb, double e, char *k, int *j );
+
+void palM2av ( float rmat[3][3], float axvec[3] );
+
+void palMap ( double rm, double dm, double pr, double pd,
+              double px, double rv, double eq, double date,
+              double *ra, double *da );
+
+void palMappa ( double eq, double date, double amprms[21] );
+
+void palMapqk ( double rm, double dm, double pr, double pd,
+                double px, double rv, double amprms[21],
+                double *ra, double *da );
+
+void palMapqkz ( double rm, double dm, double amprms[21],
+                 double *ra, double *da );
+
+void palMoon ( int iy, int id, float fd, float posvel[6] );
+
+void palMxm ( float a[3][3], float b[3][3], float c[3][3] );
+
+void palMxv ( float rm[3][3], float va[3], float vb[3] );
+
+void palNut ( double date, double rmatn[3][3] );
+
+void palNutc ( double date, double *dpsi, double *deps, double *eps0 );
+
+void palNutc80 ( double date, double *dpsi, double *deps, double *eps0 );
+
+void palOap ( const char *type, double ob1, double ob2, double date,
+              double dut, double elongm, double phim, double hm,
+              double xp, double yp, double tdk, double pmb,
+              double rh, double wl, double tlr,
+              double *rap, double *dap );
+
+void palOapqk ( const char *type, double ob1, double ob2, const double aoprms[14],
+                double *rap, double *dap );
+
+int palObs( size_t n, const char * c,
+            char * ident, size_t identlen,
+            char * name, size_t namelen,
+            double * w, double * p, double * h );
+
+double palPa ( double ha, double dec, double phi );
+
+double palPav ( float v1[3], float v2[3] );
+
+void palPcd ( double disco, double *x, double *y );
+
+void palPda2h ( double p, double d, double a,
+                double *h1, int *j1, double *h2, int *j2 );
+
+void palPdq2h ( double p, double d, double q,
+                double *h1, int *j1, double *h2, int *j2 );
+
+void palPermut ( int n, int istate[], int iorder[], int *j );
+
+void palPertel (int jform, double date0, double date1,
+                double epoch0, double orbi0, double anode0,
+                double perih0, double aorq0, double e0, double am0,
+                double *epoch1, double *orbi1, double *anode1,
+                double *perih1, double *aorq1, double *e1, double *am1,
+                int *jstat );
+
+void palPertue ( double date, double u[13], int *jstat );
+
+void palPlanel ( double date, int jform, double epoch, double orbinc,
+                 double anode, double perih, double aorq,  double e,
+                 double aorl, double dm, double pv[6], int *jstat );
+
+void palPlanet ( double date, int np, double pv[6], int *j );
+
+void palPlante ( double date, double elong, double phi, int jform,
+                 double epoch, double orbinc, double anode, double perih,
+                 double aorq, double e, double aorl, double dm,
+                 double *ra, double *dec, double *r, int *jstat );
+
+void palPlantu ( double date, double elong, double phi, const double u[13],
+                 double *ra, double *dec, double *r, int *jstat );
+
+void palPm ( double r0, double d0, double pr, double pd,
+             double px, double rv, double ep0, double ep1,
+             double *r1, double *d1 );
+
+void palPolmo ( double elongm, double phim, double xp, double yp,
+                double *elong, double *phi, double *daz );
+
+void palPrebn ( double bep0, double bep1, double rmatp[3][3] );
+
+void palPrec ( double ep0, double ep1, double rmatp[3][3] );
+
+void palPrecl ( double ep0, double ep1, double rmatp[3][3] );
+
+void palPreces ( const char sys[3], double ep0, double ep1,
+                 double *ra, double *dc );
+
+void palPrenut ( double epoch, double date, double rmatpn[3][3] );
+
+void palPv2el ( const double pv[6], double date, double pmass, int jformr,
+                int *jform, double *epoch, double *orbinc,
+                double *anode, double *perih, double *aorq, double *e,
+                double *aorl, double *dm, int *jstat );
+
+void palPv2ue ( const double pv[6], double date, double pmass,
+                double u[13], int *jstat );
+
+void palPvobs ( double p, double h, double stl, double pv[6] );
+
+void palPxy ( int np, double xye[][2], double xym[][2],
+              double coeffs[6],
+              double xyp[][2], double *xrms, double *yrms, double *rrms );
+
+float palRange ( float angle );
+
+float palRanorm ( float angle );
+
+double palRcc ( double tdb, double ut1, double wl, double u, double v );
+
+void palRdplan ( double date, int np, double elong, double phi,
+                 double *ra, double *dec, double *diam );
+
+void palRefco ( double hm, double tdk, double pmb, double rh,
+                double wl, double phi, double tlr, double eps,
+                double *refa, double *refb );
+
+void palRefcoq ( double tdk, double pmb, double rh, double wl,
+                double *refa, double *refb );
+
+void palRefro ( double zobs, double hm, double tdk, double pmb,
+                double rh, double wl, double phi, double tlr, double eps,
+                double *ref );
+
+void palRefv ( double vu[3], double refa, double refb, double vr[3] );
+
+void palRefz ( double zu, double refa, double refb, double *zr );
+
+double palRverot ( double phi, double ra, double da, double st );
+
+double palRvgalc ( double r2000, double d2000 );
+
+double palRvlg ( double r2000, double d2000 );
+
+double palRvlsrd ( double r2000, double d2000 );
+
+double palRvlsrk ( double r2000, double d2000 );
+
+void palS2tp ( float ra, float dec, float raz, float decz,
+               float *xi, float *eta, int *j );
+
+float palSep ( float a1, float b1, float a2, float b2 );
+
+float palSepv ( float v1[3], float v2[3] );
+
+void palSmat ( int n, float *a, float *y, float *d, int *jf, int *iw );
+
+void palSubet ( double rc, double dc, double eq,
+                double *rm, double *dm );
+
+void palSupgal ( double dsl, double dsb, double *dl, double *db );
+
+void palSvd ( int m, int n, int mp, int np,
+              double *a, double *w, double *v, double *work,
+              int *jstat );
+
+void palSvdcov ( int n, int np, int nc,
+                 double *w, double *v, double *work, double *cvm );
+
+void palSvdsol ( int m, int n, int mp, int np,
+                 double *b, double *u, double *w, double *v,
+                 double *work, double *x );
+
+void palTp2s ( float xi, float eta, float raz, float decz,
+               float *ra, float *dec );
+
+void palTp2v ( float xi, float eta, float v0[3], float v[3] );
+
+void palTps2c ( float xi, float eta, float ra, float dec,
+                float *raz1, float *decz1,
+                float *raz2, float *decz2, int *n );
+
+void palTpv2c ( float xi, float eta, float v[3],
+                float v01[3], float v02[3], int *n );
+
+void palUe2el ( const double u[13], int jformr,
+                int *jform, double *epoch, double *orbinc,
+                double *anode, double *perih, double *aorq, double *e,
+                double *aorl, double *dm, int *jstat );
+
+void palUe2pv ( double date, double u[13], double pv[], int *jstat );
+
+void palUnpcd ( double disco, double *x, double *y );
+
+void palV2tp ( float v[3], float v0[3], float *xi, float *eta, int *j );
+
+float palVdv ( float va[3], float vb[3] );
+
+int palVers ( char * verstring, size_t verlen );
+
+void palVn ( float v[3], float uv[3], float *vm );
+
+void palVxv ( float va[3], float vb[3], float vc[3] );
+
+void palXy2xy ( double x1, double y1, double coeffs[6],
+                double *x2, double *y2 );
+
+double palZd ( double ha, double dec, double phi );
+
+#ifdef __cplusplus
+}
+#endif
+
+/* ------------- end of pal/pal.h ------------------------------ */
+
+
+
+
+#endif
diff --git a/ast/pal/pal.h b/ast/pal/pal.h
new file mode 100644
index 0000000..a588168
--- /dev/null
+++ b/ast/pal/pal.h
@@ -0,0 +1,551 @@
+#ifndef PALHDEF
+#define PALHDEF
+
+/*
+*+
+*  Name:
+*     pal.h
+
+*  Purpose:
+*     Function prototypes for PAL routines.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Include file
+
+*  Description:
+*     Function prototypes for PAL routines.
+
+*  Authors:
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version. Define all SLA prototypes in PAL form even
+*        though none are implemented.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <math.h>
+#include <stdlib.h>
+
+void palAddet ( double rm, double dm, double eq, double *rc, double *dc );
+
+void palAfin ( const char *string, int *iptr, float *a, int *j );
+
+double palAirmas ( double zd );
+
+void palAltaz ( double ha, double dec, double phi,
+                double *az, double *azd, double *azdd,
+                double *el, double *eld, double *eldd,
+                double *pa, double *pad, double *padd );
+
+void palAmp ( double ra, double da, double date, double eq,
+              double *rm, double *dm );
+
+void palAmpqk ( double ra, double da, double amprms[21],
+                double *rm, double *dm );
+
+void palAop ( double rap, double dap, double date, double dut,
+              double elongm, double phim, double hm, double xp,
+              double yp, double tdk, double pmb, double rh,
+              double wl, double tlr,
+              double *aob, double *zob, double *hob,
+              double *dob, double *rob );
+
+void palAoppa ( double date, double dut, double elongm, double phim,
+                double hm, double xp, double yp, double tdk, double pmb,
+                double rh, double wl, double tlr, double aoprms[14] );
+
+void palAoppat ( double date, double aoprms[14] );
+
+void palAopqk ( double rap, double dap, const double aoprms[14],
+                double *aob, double *zob, double *hob,
+                double *dob, double *rob );
+
+void palAtmdsp ( double tdk, double pmb, double rh, double wl1,
+                 double a1, double b1, double wl2, double *a2, double *b2 );
+
+void palAv2m ( float axvec[3], float rmat[3][3] );
+
+float palBear ( float a1, float b1, float a2, float b2 );
+
+void palCaf2r ( int ideg, int iamin, float asec, float *rad, int *j );
+
+void palCaldj ( int iy, int im, int id, double *djm, int *j );
+
+void palCalyd ( int iy, int im, int id, int *ny, int *nd, int *j );
+
+void palCc2s ( float v[3], float *a, float *b );
+
+void palCc62s ( float v[6], float *a, float *b, float *r,
+                float *ad, float *bd, float *rd );
+
+void palCd2tf ( int ndp, float days, char *sign, int ihmsf[4] );
+
+void palCldj ( int iy, int im, int id, double *djm, int *j );
+
+void palClyd ( int iy, int im, int id, int *ny, int *nd, int *jstat );
+
+void palCombn ( int nsel, int ncand, int list[], int *j );
+
+void palCr2af ( int ndp, float angle, char *sign, int idmsf[4] );
+
+void palCr2tf ( int ndp, float angle, char *sign, int ihmsf[4] );
+
+void palCs2c ( float a, float b, float v[3] );
+
+void palCs2c6 ( float a, float b, float r, float ad,
+                float bd, float rd, float v[6] );
+
+void palCtf2d ( int ihour, int imin, float sec, float *days, int *j );
+
+void palCtf2r ( int ihour, int imin, float sec, float *rad, int *j );
+
+void palDaf2r ( int ideg, int iamin, double asec, double *rad, int *j );
+
+void palDafin ( const char *string, int *iptr, double *a, int *j );
+
+double palDat ( double dju );
+
+void palDav2m ( double axvec[3], double rmat[3][3] );
+
+double palDbear ( double a1, double b1, double a2, double b2 );
+
+void palDbjin ( const char *string, int *nstrt,
+                double *dreslt, int *jf1, int *jf2 );
+
+void palDc62s ( double v[6], double *a, double *b, double *r,
+                double *ad, double *bd, double *rd );
+
+void palDcc2s ( double v[3], double *a, double *b );
+
+void palDcmpf ( double coeffs[6], double *xz, double *yz, double *xs,
+                double *ys, double *perp, double *orient );
+
+void palDcs2c ( double a, double b, double v[3] );
+
+void palDd2tf ( int ndp, double days, char *sign, int ihmsf[4] );
+
+void palDe2h ( double ha, double dec, double phi,
+               double *az, double *el );
+
+void palDeuler ( const char *order, double phi, double theta, double psi,
+                 double rmat[3][3] );
+
+void palDfltin ( const char *string, int *nstrt, double *dreslt, int *jflag );
+
+void palDh2e ( double az, double el, double phi, double *ha, double *dec);
+
+void palDimxv ( double dm[3][3], double va[3], double vb[3] );
+
+void palDjcal ( int ndp, double djm, int iymdf[4], int *j );
+
+void palDjcl ( double djm, int *iy, int *im, int *id, double *fd, int *j );
+
+void palDm2av ( double rmat[3][3], double axvec[3] );
+
+void palDmat ( int n, double *a, double *y, double *d, int *jf, int *iw );
+
+void palDmoon ( double date, double pv[6] );
+
+void palDmxm ( double a[3][3], double b[3][3], double c[3][3] );
+
+void palDmxv ( double dm[3][3], double va[3], double vb[3] );
+
+double palDpav ( double v1[3], double v2[3] );
+
+void palDr2af ( int ndp, double angle, char *sign, int idmsf[4] );
+
+void palDr2tf ( int ndp, double angle, char *sign, int ihmsf[4] );
+
+double palDrange ( double angle );
+
+double palDranrm ( double angle );
+
+void palDs2c6 ( double a, double b, double r, double ad, double bd,
+                double rd, double v[6] );
+
+void palDs2tp ( double ra, double dec, double raz, double decz,
+                double *xi, double *eta, int *j );
+
+double palDsep ( double a1, double b1, double a2, double b2 );
+
+double palDsepv ( double v1[3], double v2[3] );
+
+double palDt ( double epoch );
+
+void palDtf2d ( int ihour, int imin, double sec, double *days, int *j );
+
+void palDtf2r ( int ihour, int imin, double sec, double *rad, int *j );
+
+void palDtp2s ( double xi, double eta, double raz, double decz,
+                double *ra, double *dec );
+
+void palDtp2v ( double xi, double eta, double v0[3], double v[3] );
+
+void palDtps2c ( double xi, double eta, double ra, double dec,
+                 double *raz1, double *decz1,
+                 double *raz2, double *decz2, int *n );
+
+void palDtpv2c ( double xi, double eta, double v[3],
+                 double v01[3], double v02[3], int *n );
+
+double palDtt ( double dju );
+
+void palDv2tp ( double v[3], double v0[3], double *xi, double *eta, int *j );
+
+double palDvdv ( double va[3], double vb[3] );
+
+void palDvn ( double v[3], double uv[3], double *vm );
+
+void palDvxv ( double va[3], double vb[3], double vc[3] );
+
+void palE2h ( float ha, float dec, float phi, float *az, float *el );
+
+void palEarth ( int iy, int id, float fd, float posvel[6] );
+
+void palEcleq ( double dl, double db, double date, double *dr, double *dd );
+
+void palEcmat ( double date, double rmat[3][3] );
+
+void palEcor ( float rm, float dm, int iy, int id, float fd,
+               float *rv, float *tl );
+
+void palEg50 ( double dr, double dd, double *dl, double *db );
+
+void palEl2ue ( double date, int jform, double epoch, double orbinc,
+                double anode, double perih, double aorq, double e,
+                double aorl, double dm, double u[13], int *jstat );
+
+double palEpb ( double date );
+
+double palEpb2d ( double epb );
+
+double palEpco ( char k0, char k, double e );
+
+double palEpj ( double date );
+
+double palEpj2d ( double epj );
+
+void palEpv( double date, double ph[3], double vh[3],
+             double pb[3], double vb[3] );
+
+void palEqecl ( double dr, double dd, double date, double *dl, double *db );
+
+double palEqeqx ( double date );
+
+void palEqgal ( double dr, double dd, double *dl, double *db );
+
+void palEtrms ( double ep, double ev[3] );
+
+void palEuler ( const char *order, float phi, float theta, float psi,
+                float rmat[3][3] );
+
+void palEvp ( double date, double deqx,
+              double dvb[3], double dpb[3],
+              double dvh[3], double dph[3] );
+
+void palFitxy ( int itype, int np, double xye[][2], double xym[][2],
+                double coeffs[6], int *j );
+
+void palFk425 ( double r1950, double d1950, double dr1950,
+                double dd1950, double p1950, double v1950,
+                double *r2000, double *d2000, double *dr2000,
+                double *dd2000, double *p2000, double *v2000 );
+
+void palFk45z ( double r1950, double d1950, double bepoch,
+                double *r2000, double *d2000 );
+
+void palFk524 ( double r2000, double d2000, double dr2000,
+                double dd2000, double p2000, double v2000,
+                double *r1950, double *d1950, double *dr1950,
+                double *dd1950, double *p1950, double *v1950 );
+
+void palFk52h ( double r5, double d5, double dr5, double dd5,
+                double *dr, double *dh, double *drh, double *ddh );
+
+void palFk54z ( double r2000, double d2000, double bepoch,
+                double *r1950, double *d1950,
+                double *dr1950, double *dd1950 );
+
+void palFk5hz ( double r5, double d5, double epoch,
+                double *rh, double *dh );
+
+void palFlotin ( const char *string, int *nstrt, float *reslt, int *jflag );
+
+void palGaleq ( double dl, double db, double *dr, double *dd );
+
+void palGalsup ( double dl, double db, double *dsl, double *dsb );
+
+void palGe50 ( double dl, double db, double *dr, double *dd );
+
+void palGeoc ( double p, double h, double *r, double *z );
+
+double palGmst ( double ut1 );
+
+double palGmsta ( double date, double ut1 );
+
+void palH2e ( float az, float el, float phi, float *ha, float *dec );
+
+void palH2fk5 ( double dr, double dh, double drh, double ddh,
+                double *r5, double *d5, double *dr5, double *dd5 );
+
+void palHfk5z ( double rh, double dh, double epoch,
+                double *r5, double *d5, double *dr5, double *dd5 );
+
+void palImxv ( float rm[3][3], float va[3], float vb[3] );
+
+void palInt2in ( const char *string, int *nstrt, int *ireslt, int *jflag );
+
+void palIntin ( const char *string, int *nstrt, long *ireslt, int *jflag );
+
+void palInvf ( double fwds[6], double bkwds[6], int *j );
+
+void palKbj ( int jb, double e, char *k, int *j );
+
+void palM2av ( float rmat[3][3], float axvec[3] );
+
+void palMap ( double rm, double dm, double pr, double pd,
+              double px, double rv, double eq, double date,
+              double *ra, double *da );
+
+void palMappa ( double eq, double date, double amprms[21] );
+
+void palMapqk ( double rm, double dm, double pr, double pd,
+                double px, double rv, double amprms[21],
+                double *ra, double *da );
+
+void palMapqkz ( double rm, double dm, double amprms[21],
+                 double *ra, double *da );
+
+void palMoon ( int iy, int id, float fd, float posvel[6] );
+
+void palMxm ( float a[3][3], float b[3][3], float c[3][3] );
+
+void palMxv ( float rm[3][3], float va[3], float vb[3] );
+
+void palNut ( double date, double rmatn[3][3] );
+
+void palNutc ( double date, double *dpsi, double *deps, double *eps0 );
+
+void palNutc80 ( double date, double *dpsi, double *deps, double *eps0 );
+
+void palOap ( const char *type, double ob1, double ob2, double date,
+              double dut, double elongm, double phim, double hm,
+              double xp, double yp, double tdk, double pmb,
+              double rh, double wl, double tlr,
+              double *rap, double *dap );
+
+void palOapqk ( const char *type, double ob1, double ob2, const double aoprms[14],
+                double *rap, double *dap );
+
+int palObs( size_t n, const char * c,
+            char * ident, size_t identlen,
+            char * name, size_t namelen,
+            double * w, double * p, double * h );
+
+double palPa ( double ha, double dec, double phi );
+
+double palPav ( float v1[3], float v2[3] );
+
+void palPcd ( double disco, double *x, double *y );
+
+void palPda2h ( double p, double d, double a,
+                double *h1, int *j1, double *h2, int *j2 );
+
+void palPdq2h ( double p, double d, double q,
+                double *h1, int *j1, double *h2, int *j2 );
+
+void palPermut ( int n, int istate[], int iorder[], int *j );
+
+void palPertel (int jform, double date0, double date1,
+                double epoch0, double orbi0, double anode0,
+                double perih0, double aorq0, double e0, double am0,
+                double *epoch1, double *orbi1, double *anode1,
+                double *perih1, double *aorq1, double *e1, double *am1,
+                int *jstat );
+
+void palPertue ( double date, double u[13], int *jstat );
+
+void palPlanel ( double date, int jform, double epoch, double orbinc,
+                 double anode, double perih, double aorq,  double e,
+                 double aorl, double dm, double pv[6], int *jstat );
+
+void palPlanet ( double date, int np, double pv[6], int *j );
+
+void palPlante ( double date, double elong, double phi, int jform,
+                 double epoch, double orbinc, double anode, double perih,
+                 double aorq, double e, double aorl, double dm,
+                 double *ra, double *dec, double *r, int *jstat );
+
+void palPlantu ( double date, double elong, double phi, const double u[13],
+                 double *ra, double *dec, double *r, int *jstat );
+
+void palPm ( double r0, double d0, double pr, double pd,
+             double px, double rv, double ep0, double ep1,
+             double *r1, double *d1 );
+
+void palPolmo ( double elongm, double phim, double xp, double yp,
+                double *elong, double *phi, double *daz );
+
+void palPrebn ( double bep0, double bep1, double rmatp[3][3] );
+
+void palPrec ( double ep0, double ep1, double rmatp[3][3] );
+
+void palPrecl ( double ep0, double ep1, double rmatp[3][3] );
+
+void palPreces ( const char sys[3], double ep0, double ep1,
+                 double *ra, double *dc );
+
+void palPrenut ( double epoch, double date, double rmatpn[3][3] );
+
+void palPv2el ( const double pv[6], double date, double pmass, int jformr,
+                int *jform, double *epoch, double *orbinc,
+                double *anode, double *perih, double *aorq, double *e,
+                double *aorl, double *dm, int *jstat );
+
+void palPv2ue ( const double pv[6], double date, double pmass,
+                double u[13], int *jstat );
+
+void palPvobs ( double p, double h, double stl, double pv[6] );
+
+void palPxy ( int np, double xye[][2], double xym[][2],
+              double coeffs[6],
+              double xyp[][2], double *xrms, double *yrms, double *rrms );
+
+float palRange ( float angle );
+
+float palRanorm ( float angle );
+
+double palRcc ( double tdb, double ut1, double wl, double u, double v );
+
+void palRdplan ( double date, int np, double elong, double phi,
+                 double *ra, double *dec, double *diam );
+
+void palRefco ( double hm, double tdk, double pmb, double rh,
+                double wl, double phi, double tlr, double eps,
+                double *refa, double *refb );
+
+void palRefcoq ( double tdk, double pmb, double rh, double wl,
+                double *refa, double *refb );
+
+void palRefro ( double zobs, double hm, double tdk, double pmb,
+                double rh, double wl, double phi, double tlr, double eps,
+                double *ref );
+
+void palRefv ( double vu[3], double refa, double refb, double vr[3] );
+
+void palRefz ( double zu, double refa, double refb, double *zr );
+
+double palRverot ( double phi, double ra, double da, double st );
+
+double palRvgalc ( double r2000, double d2000 );
+
+double palRvlg ( double r2000, double d2000 );
+
+double palRvlsrd ( double r2000, double d2000 );
+
+double palRvlsrk ( double r2000, double d2000 );
+
+void palS2tp ( float ra, float dec, float raz, float decz,
+               float *xi, float *eta, int *j );
+
+float palSep ( float a1, float b1, float a2, float b2 );
+
+float palSepv ( float v1[3], float v2[3] );
+
+void palSmat ( int n, float *a, float *y, float *d, int *jf, int *iw );
+
+void palSubet ( double rc, double dc, double eq,
+                double *rm, double *dm );
+
+void palSupgal ( double dsl, double dsb, double *dl, double *db );
+
+void palSvd ( int m, int n, int mp, int np,
+              double *a, double *w, double *v, double *work,
+              int *jstat );
+
+void palSvdcov ( int n, int np, int nc,
+                 double *w, double *v, double *work, double *cvm );
+
+void palSvdsol ( int m, int n, int mp, int np,
+                 double *b, double *u, double *w, double *v,
+                 double *work, double *x );
+
+void palTp2s ( float xi, float eta, float raz, float decz,
+               float *ra, float *dec );
+
+void palTp2v ( float xi, float eta, float v0[3], float v[3] );
+
+void palTps2c ( float xi, float eta, float ra, float dec,
+                float *raz1, float *decz1,
+                float *raz2, float *decz2, int *n );
+
+void palTpv2c ( float xi, float eta, float v[3],
+                float v01[3], float v02[3], int *n );
+
+void palUe2el ( const double u[13], int jformr,
+                int *jform, double *epoch, double *orbinc,
+                double *anode, double *perih, double *aorq, double *e,
+                double *aorl, double *dm, int *jstat );
+
+void palUe2pv ( double date, double u[13], double pv[], int *jstat );
+
+void palUnpcd ( double disco, double *x, double *y );
+
+void palV2tp ( float v[3], float v0[3], float *xi, float *eta, int *j );
+
+float palVdv ( float va[3], float vb[3] );
+
+int palVers ( char * verstring, size_t verlen );
+
+void palVn ( float v[3], float uv[3], float *vm );
+
+void palVxv ( float va[3], float vb[3], float vc[3] );
+
+void palXy2xy ( double x1, double y1, double coeffs[6],
+                double *x2, double *y2 );
+
+double palZd ( double ha, double dec, double phi );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/ast/pal/pal1sofa.h b/ast/pal/pal1sofa.h
new file mode 100644
index 0000000..11f7446
--- /dev/null
+++ b/ast/pal/pal1sofa.h
@@ -0,0 +1,142 @@
+/*
+*+
+*  Name:
+*     pal1sofa.h
+
+*  Purpose:
+*     Mappings of ERFA names to SOFA names
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Include file
+
+*  Invocation:
+*     #include "pal1sofa.h"
+
+*  Description:
+*     PAL will work with both SOFA and ERFA libraries and the
+*     difference is generally a change in prefix. This include
+*     file maps the ERFA form of functions to the SOFA form
+*     and includes the relevant sofa.h vs erfa.h file.
+
+*  Authors:
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     - PAL uses the ERFA form by default.
+
+*  History:
+*     2014-07-29 (TIMJ):
+*        Initial version
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2014 Tim Jenness
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#ifndef PAL1SOFAHDEF
+#define PAL1SOFAHDEF
+
+#if HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+# if HAVE_SOFA_H
+
+#  include "sofa.h"
+#  include "sofam.h"
+
+   /* Must replace ERFA with SOFA */
+
+#  define eraA2af iauA2af
+#  define eraA2tf iauA2tf
+#  define eraAf2a iauAf2a
+#  define eraAnp iauAnp
+#  define eraAnpm iauAnpm
+#  define eraC2s iauC2s
+#  define eraCal2jd iauCal2jd
+#  define eraD2tf iauD2tf
+#  define eraDat iauDat
+#  define eraEe06a iauEe06a
+#  define eraEpb iauEpb
+#  define eraEpb2jd iauEpb2jd
+#  define eraEpj iauEpj
+#  define eraEpj2jd iauEpj2jd
+#  define eraEpv00 iauEpv00
+#  define eraFk5hz iauFk5hz
+#  define eraGd2gc iauGd2gc
+#  define eraGmst06 iauGmst06
+#  define eraHfk5z iauHfk5z
+#  define eraIr iauIr
+#  define eraJd2cal iauJd2cal
+#  define eraNut06a iauNut06a
+#  define eraObl06 iauObl06
+#  define eraP06e iauP06e
+#  define eraPap iauPap
+#  define eraPas iauPas
+#  define eraPdp iauPdp
+#  define eraPlan94 iauPlan94
+#  define eraPmat06 iauPmat06
+#  define eraPn iauPn
+#  define eraPnm06a iauPnm06a
+#  define eraPxp iauPxp
+#  define eraRefco iauRefco
+#  define eraRm2v iauRm2v
+#  define eraRv2m iauRv2m
+#  define eraRx iauRx
+#  define eraRxp iauRxp
+#  define eraRxpv iauRxpv
+#  define eraRxr iauRxr
+#  define eraRy iauRy
+#  define eraRz iauRz
+#  define eraS2c iauS2c
+#  define eraSepp iauSepp
+#  define eraSeps iauSeps
+#  define eraStarpm iauStarpm
+#  define eraTf2a iauTf2a
+#  define eraTf2d iauTf2d
+#  define eraTr iauTr
+#  define eraTrxp iauTrxp
+
+/* These are from sofam.h */
+
+#  define ERFA_WGS84 WGS84
+
+#  define ERFA_DJ00 DJ00
+#  define ERFA_DJY DJY
+#  define ERFA_DAU DAU
+
+# else
+
+#  include "erfa.h"
+#  include "erfam.h"
+
+/* No further action required */
+
+# endif
+
+#endif
diff --git a/ast/pal/palAddet.c b/ast/pal/palAddet.c
new file mode 100644
index 0000000..ce3ddab
--- /dev/null
+++ b/ast/pal/palAddet.c
@@ -0,0 +1,112 @@
+/*
+*+
+*  Name:
+*     palAddet
+
+*  Purpose:
+*     Add the E-terms to a pre IAU 1976 mean place
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palAddet ( double rm, double dm, double eq,
+*                     double *rc, double *dc );
+
+*  Arguments:
+*     rm = double (Given)
+*        RA without E-terms (radians)
+*     dm = double (Given)
+*        Dec without E-terms (radians)
+*     eq = double (Given)
+*        Besselian epoch of mean equator and equinox
+*     rc = double * (Returned)
+*        RA with E-terms included (radians)
+*     dc = double * (Returned)
+*        Dec with E-terms included (radians)
+
+*  Description:
+*     Add the E-terms (elliptic component of annual aberration)
+*     to a pre IAU 1976 mean place to conform to the old
+*     catalogue convention.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     Most star positions from pre-1984 optical catalogues (or
+*     derived from astrometry using such stars) embody the
+*     E-terms.  If it is necessary to convert a formal mean
+*     place (for example a pulsar timing position) to one
+*     consistent with such a star catalogue, then the RA,Dec
+*     should be adjusted using this routine.
+
+*  See Also:
+*     Explanatory Supplement to the Astronomical Ephemeris,
+*     section 2D, page 48.
+
+*  History:
+*     2012-02-12(TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1999 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palAddet ( double rm, double dm, double eq, double *rc, double *dc ) {
+  double a[3];   /* The E-terms */
+  double v[3];
+  int i;
+
+  /* Note the preference for IAU routines */
+
+  /* Retrieve the E-terms */
+  palEtrms( eq, a );
+
+  /* Spherical to Cartesian */
+  eraS2c( rm, dm, v );
+
+  /* Include the E-terms */
+  for (i=0; i<3; i++) {
+    v[i] += a[i];
+  }
+
+  /* Cartesian to spherical */
+  eraC2s( v, rc, dc );
+
+  /* Bring RA into conventional range */
+  *rc = eraAnp( *rc );
+
+}
diff --git a/ast/pal/palAmpqk.c b/ast/pal/palAmpqk.c
new file mode 100644
index 0000000..ce698b8
--- /dev/null
+++ b/ast/pal/palAmpqk.c
@@ -0,0 +1,159 @@
+/*
+*+
+*  Name:
+*     palAmpqk
+
+*  Purpose:
+*     Convert star RA,Dec from geocentric apparent to mean place.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palAmpqk ( double ra, double da, double amprms[21],
+*                     double *rm, double *dm )
+
+*  Arguments:
+*     ra = double (Given)
+*        Apparent RA (radians).
+*     da = double (Given)
+*        Apparent Dec (radians).
+*     amprms = double[21] (Given)
+*        Star-independent mean-to-apparent parameters (see palMappa):
+*        (0)      time interval for proper motion (Julian years)
+*        (1-3)    barycentric position of the Earth (AU)
+*        (4-6)    heliocentric direction of the Earth (unit vector)
+*        (7)      (grav rad Sun)*2/(Sun-Earth distance)
+*        (8-10)   abv: barycentric Earth velocity in units of c
+*        (11)     sqrt(1-v*v) where v=modulus(abv)
+*        (12-20)  precession/nutation (3,3) matrix
+*     rm = double (Returned)
+*        Mean RA (radians).
+*     dm = double (Returned)
+*        Mean Dec (radians).
+
+*  Description:
+*     Convert star RA,Dec from geocentric apparent to mean place. The "mean"
+*     coordinate system is in fact close to ICRS. Use of this function
+*     is appropriate when efficiency is important and where many star
+*     positions are all to be transformed for one epoch and equinox.  The
+*     star-independent parameters can be obtained by calling the palMappa
+*     function.
+
+*  Note:
+*     Iterative techniques are used for the aberration and
+*     light deflection corrections so that the routines
+*     palAmp (or palAmpqk) and palMap (or palMapqk) are
+*     accurate inverses;  even at the edge of the Sun's disc
+*     the discrepancy is only about 1 nanoarcsecond.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-13 (PTW):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     2016-12-19 (TIMJ):
+*        Add in light deflection (was missed in the initial port).
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2000 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     Copyright (C) 2016 Tim Jenness
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palAmpqk ( double ra, double da, double amprms[21], double *rm,
+                double *dm ){
+
+/* Local Variables: */
+   double ab1;                 /* sqrt(1-v*v) where v=modulus of Earth vel */
+   double abv[3];              /* Earth velocity wrt SSB (c, FK5) */
+   double p1[3], p2[3], p3[3]; /* work vectors */
+   double ab1p1, p1dv, p1dvp1, w;
+   double gr2e, pde, pdep1, ehn[3], p[3];
+   int i, j;
+
+/* Unpack some of the parameters */
+   gr2e = amprms[7];
+   ab1  = amprms[11];
+   for( i = 0; i < 3; i++ ) {
+      ehn[i] = amprms[i + 4];
+      abv[i] = amprms[i + 8];
+   }
+
+/* Apparent RA,Dec to Cartesian */
+   eraS2c( ra, da, p3 );
+
+/* Precession and nutation */
+   eraTrxp( (double(*)[3]) &amprms[12], p3, p2 );
+
+/* Aberration */
+   ab1p1 = ab1 + 1.0;
+   for( i = 0; i < 3; i++ ) {
+      p1[i] = p2[i];
+   }
+   for( j = 0; j < 2; j++ ) {
+      p1dv = eraPdp( p1, abv );
+      p1dvp1 = 1.0 + p1dv;
+      w = 1.0 + p1dv / ab1p1;
+      for( i = 0; i < 3; i++ ) {
+         p1[i] = ( p1dvp1 * p2[i] - w * abv[i] ) / ab1;
+      }
+      eraPn( p1, &w, p3 );
+      for( i = 0; i < 3; i++ ) {
+         p1[i] = p3[i];
+      }
+   }
+
+/* Light deflection */
+   for( i = 0; i < 3; i++ ) {
+       p[i] = p1[i];
+   }
+   for( j = 0; j < 5; j++ ) {
+      pde = eraPdp( p, ehn );
+      pdep1 = 1.0 + pde;
+      w = pdep1 - gr2e*pde;
+      for( i = 0; i < 3; i++ ) {
+         p[i] = (pdep1*p1[i] - gr2e*ehn[i])/w;
+      }
+      eraPn( p, &w, p2 );
+      for( i = 0; i < 3; i++ ) {
+         p[i] = p2[i];
+      }
+   }
+
+/* Mean RA,Dec */
+   eraC2s( p, rm, dm );
+   *rm = eraAnp( *rm );
+}
diff --git a/ast/pal/palCaldj.c b/ast/pal/palCaldj.c
new file mode 100644
index 0000000..03c6b97
--- /dev/null
+++ b/ast/pal/palCaldj.c
@@ -0,0 +1,99 @@
+/*
+*+
+*  Name:
+*     palCaldj
+
+*  Purpose:
+*     Gregorian Calendar to Modified Julian Date
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palCaldj ( int iy, int im, int id, double *djm, int *j );
+
+*  Arguments:
+*     iy = int (Given)
+*        Year in the Gregorian calendar
+*     im = int (Given)
+*        Month in the Gergorian calendar
+*     id = int (Given)
+*        Day in the Gregorian calendar
+*     djm = double * (Returned)
+*        Modified Julian Date (JD-2400000.5) for 0 hrs
+*     j = status (Returned)
+*       0 = OK. See eraCal2jd for other values.
+
+*  Description:
+*     Modified Julian Date to Gregorian Calendar with special
+*     behaviour for 2-digit years relating to 1950 to 2049.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-11 (TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Notes:
+*     - Uses eraCal2jd
+*     - Unlike eraCal2jd this routine treats the years 0-100 as
+*       referring to the end of the 20th Century and beginning of
+*       the 21st Century. If this behaviour is not acceptable
+*       use the SOFA/ERFA routine directly or palCldj.
+*       Acceptable years are 00-49, interpreted as 2000-2049,
+*                            50-99,     "       "  1950-1999,
+*                            all others, interpreted literally.
+*     - Unlike SLA this routine will work with negative years.
+
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include "pal1sofa.h"
+
+void palCaldj ( int iy, int im, int id, double *djm, int *j ) {
+  int adj = 0;   /* Year adjustment */
+  double djm0;
+
+  if (iy >= 0 && iy <= 49) {
+    adj = 2000;
+  } else if (iy >= 50 && iy <= 99) {
+    adj = 1900;
+  }
+  iy += adj;
+
+  *j = eraCal2jd( iy, im, id, &djm0, djm );
+}
diff --git a/ast/pal/palDat.c b/ast/pal/palDat.c
new file mode 100644
index 0000000..f869681
--- /dev/null
+++ b/ast/pal/palDat.c
@@ -0,0 +1,95 @@
+/*
+*+
+*  Name:
+*     palDat
+
+*  Purpose:
+*     Return offset between UTC and TAI
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     dat = palDat( double utc );
+
+*  Arguments:
+*     utc = double (Given)
+*        UTC date as a modified JD (JD-2400000.5)
+
+*  Returned Value:
+*     dat = double
+*        TAI-UTC in seconds
+
+*  Description:
+*     Increment to be applied to Coordinated Universal Time UTC to give
+*     International Atomic Time (TAI).
+
+*  Authors:
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     - This routine converts the MJD argument to calendar date before calling
+*       the SOFA/ERFA eraDat function.
+*     - This routine matches the slaDat interface which differs from the eraDat
+*       interface. Consider coding directly to the SOFA/ERFA interface.
+*     - See eraDat for a description of error conditions when calling this function
+*       with a time outside of the UTC range.
+*     - The status argument from eraDat is ignored. This is reasonable since the
+*       error codes are mainly related to incorrect calendar dates when calculating
+*       the JD internally.
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version
+*        Adapted with permission from the Fortran SLALIB library
+*        although the core algorithm is now from SOFA.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+
+#include "pal1sofa.h"
+
+double palDat ( double dju ) {
+  int iy;
+  int im;
+  int id;
+  int status;
+  double fd;
+  double deltat;
+
+  eraJd2cal( PAL__MJD0, dju,
+             &iy, &im, &id, &fd );
+
+  status = eraDat( iy, im, id, fd, &deltat );
+  return deltat;
+}
diff --git a/ast/pal/palDe2h.c b/ast/pal/palDe2h.c
new file mode 100644
index 0000000..a250e9e
--- /dev/null
+++ b/ast/pal/palDe2h.c
@@ -0,0 +1,142 @@
+/*
+*+
+*  Name:
+*     palDe2h
+
+*  Purpose:
+*     Equatorial to horizon coordinates: HA,Dec to Az,E
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDe2h( double ha, double dec, double phi, double * az, double * el );
+
+*  Arguments:
+*     ha = double * (Given)
+*        Hour angle (radians)
+*     dec = double * (Given)
+*        Declination (radians)
+*     phi = double (Given)
+*        Observatory latitude (radians)
+*     az = double * (Returned)
+*        Azimuth (radians)
+*     el = double * (Returned)
+*        Elevation (radians)
+
+*  Description:
+*     Convert equatorial to horizon coordinates.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     - All the arguments are angles in radians.
+*     - Azimuth is returned in the range 0-2pi;  north is zero,
+*       and east is +pi/2.  Elevation is returned in the range
+*       +/-pi/2.
+*     - The latitude must be geodetic.  In critical applications,
+*       corrections for polar motion should be applied.
+*     - In some applications it will be important to specify the
+*       correct type of hour angle and declination in order to
+*       produce the required type of azimuth and elevation.  In
+*       particular, it may be important to distinguish between
+*       elevation as affected by refraction, which would
+*       require the "observed" HA,Dec, and the elevation
+*       in vacuo, which would require the "topocentric" HA,Dec.
+*       If the effects of diurnal aberration can be neglected, the
+*       "apparent" HA,Dec may be used instead of the topocentric
+*       HA,Dec.
+*     - No range checking of arguments is carried out.
+*     - In applications which involve many such calculations, rather
+*       than calling the present routine it will be more efficient to
+*       use inline code, having previously computed fixed terms such
+*       as sine and cosine of latitude, and (for tracking a star)
+*       sine and cosine of declination.
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include <math.h>
+
+void
+palDe2h ( double ha, double dec, double phi, double *az, double *el) {
+
+  double sh;
+  double ch;
+  double sd;
+  double cd;
+  double sp;
+  double cp;
+
+  double a;
+
+  double x;
+  double y;
+  double z;
+  double r;
+
+  /*  Useful trig functions */
+  sh = sin(ha);
+  ch = cos(ha);
+  sd = sin(dec);
+  cd = cos(dec);
+  sp = sin(phi);
+  cp = cos(phi);
+
+  /*  Az,El as x,y,z */
+  x = -ch * cd * sp + sd * cp;
+  y = -sh * cd;
+  z = ch * cd * cp + sd * sp;
+
+  /*  To spherical */
+  r = sqrt(x * x + y * y);
+  if (r == 0.) {
+    a = 0.;
+  } else {
+    a = atan2(y, x);
+  }
+  if (a < 0.) {
+    a += PAL__D2PI;
+  }
+  *az = a;
+  *el = atan2(z, r);
+
+  return;
+}
diff --git a/ast/pal/palDeuler.c b/ast/pal/palDeuler.c
new file mode 100644
index 0000000..17e536b
--- /dev/null
+++ b/ast/pal/palDeuler.c
@@ -0,0 +1,141 @@
+/*
+*+
+*  Name:
+*     palDeuler
+
+*  Purpose:
+*     Form a rotation matrix from the Euler angles
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palDeuler ( const char *order, double phi, double theta, double psi,
+*                      double rmat[3][3] );
+
+*  Arguments:
+*     order = const char[] (Given)
+*        Specifies about which axes the rotation occurs
+*     phi = double (Given)
+*        1st rotation (radians)
+*     theta = double (Given)
+*        2nd rotation (radians)
+*     psi = double (Given)
+*        3rd rotation (radians)
+*     rmat = double[3][3] (Given & Returned)
+*        Rotation matrix
+
+*  Description:
+*     A rotation is positive when the reference frame rotates
+*     anticlockwise as seen looking towards the origin from the
+*     positive region of the specified axis.
+*
+*     The characters of ORDER define which axes the three successive
+*     rotations are about.  A typical value is 'ZXZ', indicating that
+*     RMAT is to become the direction cosine matrix corresponding to
+*     rotations of the reference frame through PHI radians about the
+*     old Z-axis, followed by THETA radians about the resulting X-axis,
+*     then PSI radians about the resulting Z-axis.
+*
+*     The axis names can be any of the following, in any order or
+*     combination:  X, Y, Z, uppercase or lowercase, 1, 2, 3.  Normal
+*     axis labelling/numbering conventions apply;  the xyz (=123)
+*     triad is right-handed.  Thus, the 'ZXZ' example given above
+*     could be written 'zxz' or '313' (or even 'ZxZ' or '3xZ').  ORDER
+*     is terminated by length or by the first unrecognized character.
+*
+*     Fewer than three rotations are acceptable, in which case the later
+*     angle arguments are ignored.  If all rotations are zero, the
+*     identity matrix is produced.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1997 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void
+palDeuler( const char *order, double phi, double theta, double psi,
+                 double rmat[3][3] ) {
+  int i = 0;
+  double rotations[3];
+
+  /* Initialise rmat */
+  eraIr( rmat );
+
+  /* copy the rotations into an array */
+  rotations[0] = phi;
+  rotations[1] = theta;
+  rotations[2] = psi;
+
+  /* maximum three rotations */
+  while (i < 3 && order[i] != '\0') {
+
+    switch (order[i]) {
+    case 'X':
+    case 'x':
+    case '1':
+      eraRx( rotations[i], rmat );
+      break;
+
+    case 'Y':
+    case 'y':
+    case '2':
+      eraRy( rotations[i], rmat );
+      break;
+
+    case 'Z':
+    case 'z':
+    case '3':
+      eraRz( rotations[i], rmat );
+      break;
+
+    default:
+      /* break out the loop if we do not recognize something */
+      i = 3;
+
+    }
+
+    /* Go to the next position */
+    i++;
+  }
+
+  return;
+}
diff --git a/ast/pal/palDh2e.c b/ast/pal/palDh2e.c
new file mode 100644
index 0000000..65434b7
--- /dev/null
+++ b/ast/pal/palDh2e.c
@@ -0,0 +1,133 @@
+/*
+*+
+*  Name:
+*     palDh2e
+
+*  Purpose:
+*     Horizon to equatorial coordinates: Az,El to HA,Dec
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDh2e( double az, double el, double phi, double * ha, double * dec );
+
+*  Arguments:
+*     az = double (Given)
+*        Azimuth (radians)
+*     el = double (Given)
+*        Elevation (radians)
+*     phi = double (Given)
+*        Observatory latitude (radians)
+*     ha = double * (Returned)
+*        Hour angle (radians)
+*     dec = double * (Returned)
+*        Declination (radians)
+
+*  Description:
+*     Convert horizon to equatorial coordinates.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     - All the arguments are angles in radians.
+*     - The sign convention for azimuth is north zero, east +pi/2.
+*     - HA is returned in the range +/-pi.  Declination is returned
+*       in the range +/-pi/2.
+*     - The latitude is (in principle) geodetic.  In critical
+*       applications, corrections for polar motion should be applied.
+*     - In some applications it will be important to specify the
+*       correct type of elevation in order to produce the required
+*       type of HA,Dec.  In particular, it may be important to
+*       distinguish between the elevation as affected by refraction,
+*       which will yield the "observed" HA,Dec, and the elevation
+*       in vacuo, which will yield the "topocentric" HA,Dec.  If the
+*       effects of diurnal aberration can be neglected, the
+*       topocentric HA,Dec may be used as an approximation to the
+*       "apparent" HA,Dec.
+*     - No range checking of arguments is done.
+*     - In applications which involve many such calculations, rather
+*       than calling the present routine it will be more efficient to
+*       use inline code, having previously computed fixed terms such
+*       as sine and cosine of latitude.
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1996 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include <math.h>
+
+void
+palDh2e ( double az, double el, double phi, double *ha, double *dec) {
+
+  double sa;
+  double ca;
+  double se;
+  double ce;
+  double sp;
+  double cp;
+
+  double x;
+  double y;
+  double z;
+  double r;
+
+  /*  Useful trig functions */
+  sa = sin(az);
+  ca = cos(az);
+  se = sin(el);
+  ce = cos(el);
+  sp = sin(phi);
+  cp = cos(phi);
+
+  /*  HA,Dec as x,y,z */
+  x = -ca * ce * sp + se * cp;
+  y = -sa * ce;
+  z = ca * ce * cp + se * sp;
+
+  /*  To HA,Dec */
+  r = sqrt(x * x + y * y);
+  if (r == 0.) {
+    *ha = 0.;
+  } else {
+    *ha = atan2(y, x);
+  }
+  *dec = atan2(z, r);
+
+  return;
+}
diff --git a/ast/pal/palDjcal.c b/ast/pal/palDjcal.c
new file mode 100644
index 0000000..b6aac9e
--- /dev/null
+++ b/ast/pal/palDjcal.c
@@ -0,0 +1,97 @@
+/*
+*+
+*  Name:
+*     palDjcal
+
+*  Purpose:
+*     Modified Julian Date to Gregorian Calendar
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palDjcal ( int ndp, double djm, int iymdf[4], int *j );
+
+*  Arguments:
+*     ndp = int (Given)
+*        Number of decimal places of days in fraction.
+*     djm = double (Given)
+*        Modified Julian Date (JD-2400000.5)
+*     iymdf[4] = int[] (Returned)
+*       Year, month, day, fraction in Gregorian calendar.
+*     j = status (Returned)
+*       0 = OK. See eraJd2cal for other values.
+
+*  Description:
+*     Modified Julian Date to Gregorian Calendar, expressed
+*     in a form convenient for formatting messages (namely
+*     rounded to a specified precision, and with the fields
+*     stored in a single array)
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-10 (TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Notes:
+*     - Uses eraJd2cal
+
+*  Copyright:
+*     Copyright (C) 2004 Patrick T. Wallace
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include <math.h>
+
+#include "pal.h"
+#include "palmac.h"
+#include "pal1sofa.h"
+
+void palDjcal ( int ndp, double djm, int iymdf[4], int *j ) {
+  double frac = 0.0;
+  double nfd;
+
+  *j = eraJd2cal( PAL__MJD0, djm, &(iymdf[0]),
+		  &(iymdf[1]), &(iymdf[2]),
+		  &frac);
+
+  /* Convert ndp to a power of 10 */
+  nfd = pow( 10., (double)ndp );
+
+  /* Multiply the fraction */
+  frac *= nfd;
+
+  /* and now we want to round to the nearest integer */
+  iymdf[3] = (int)DNINT(frac);
+
+}
diff --git a/ast/pal/palDmat.c b/ast/pal/palDmat.c
new file mode 100644
index 0000000..2948f92
--- /dev/null
+++ b/ast/pal/palDmat.c
@@ -0,0 +1,182 @@
+/*
+*+
+*  Name:
+*     palDmat
+
+*  Purpose:
+*     Matrix inversion & solution of simultaneous equations
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palDmat( int n, double *a, double *y, double *d, int *jf,
+*                    int *iw );
+
+*  Arguments:
+*     n = int (Given)
+*        Number of simultaneous equations and number of unknowns.
+*     a = double[] (Given & Returned)
+*        A non-singular NxN matrix (implemented as a contiguous block
+*        of memory). After calling this routine "a" contains the
+*        inverse of the matrix.
+*     y = double[] (Given & Returned)
+*        On input the vector of N knowns. On exit this vector contains the
+*        N solutions.
+*     d = double * (Returned)
+*        The determinant.
+*     jf = int * (Returned)
+*        The singularity flag.  If the matrix is non-singular, jf=0
+*        is returned.  If the matrix is singular, jf=-1 & d=0.0 are
+*        returned.  In the latter case, the contents of array "a" on
+*        return are undefined.
+*     iw = int[] (Given)
+*        Integer workspace of size N.
+
+*  Description:
+*     Matrix inversion & solution of simultaneous equations
+*     For the set of n simultaneous equations in n unknowns:
+*          A.Y = X
+*     this routine calculates the inverse of A, the determinant
+*     of matrix A and the vector of N unknowns.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-11 (TIMJ):
+*        Combination of a port of the Fortran and a comparison
+*        with the obfuscated GPL C routine.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Notes:
+*     - Implemented using Gaussian elimination with partial pivoting.
+*     - Optimized for speed rather than accuracy with errors 1 to 4
+*       times those of routines optimized for accuracy.
+
+*  Copyright:
+*     Copyright (C) 2001 Rutherford Appleton Laboratory.
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+
+void palDmat ( int n, double *a, double *y, double *d, int *jf, int *iw ) {
+
+  const double SFA = 1e-20;
+
+  int k;
+  double*aoff;
+
+  *jf=0;
+  *d=1.0;
+  for(k=0,aoff=a; k<n; k++, aoff+=n){
+    int imx;
+    double * aoff2 = aoff;
+    double amx=fabs(aoff[k]);
+    imx=k;
+    if(k!=n){
+      int i;
+      double *apos2;
+      for(i=k+1,apos2=aoff+n;i<n;i++,apos2+=n){
+	double t=fabs(apos2[k]);
+	if(t>amx){
+	  amx=t;
+	  imx=i;
+	  aoff2=apos2;
+	}
+      }
+    }
+    if(amx<SFA){
+      *jf=-1;
+    } else {
+      if(imx!=k){
+	double t;
+	int j;
+	for(j=0;j<n;j++){
+	  t=aoff[j];
+	  aoff[j]=aoff2[j];
+	  aoff2[j]=t;
+	}
+	t=y[k];
+	y[k]=y[imx];
+	y[imx]=t;*d=-*d;
+      }
+      iw[k]=imx;
+      *d*=aoff[k];
+      if(fabs(*d)<SFA){
+	*jf=-1;
+      } else {
+	double yk;
+	double * apos2;
+	int i, j;
+	aoff[k]=1.0/aoff[k];
+	for(j=0;j<n;j++){
+	  if(j!=k){
+	    aoff[j]*=aoff[k];
+	  }
+	}
+	yk=y[k]*aoff[k];
+	y[k]=yk;
+	for(i=0,apos2=a;i<n;i++,apos2+=n){
+	  if(i!=k){
+	    for(j=0;j<n;j++){
+	      if(j!=k){
+		apos2[j]-=apos2[k]*aoff[j];
+	      }
+	    }
+	    y[i]-=apos2[k]*yk;
+	  }
+	}
+	for(i=0,apos2=a;i<n;i++,apos2+=n){
+	  if(i!=k){
+	    apos2[k]*=-aoff[k];
+	  }
+	}
+      }
+    }
+  }
+  if(*jf!=0){
+    *d=0.0;
+  } else {
+    for(k=n;k-->0;){
+      int ki=iw[k];
+      if(k!=ki){
+	int i;
+	double *apos = a;
+	for(i=0;i<n;i++,apos+=n){
+	  double t=apos[k];
+	  apos[k]=apos[ki];
+	  apos[ki]=t;
+	}
+      }
+    }
+  }
+}
diff --git a/ast/pal/palDrange.c b/ast/pal/palDrange.c
new file mode 100644
index 0000000..804ed55
--- /dev/null
+++ b/ast/pal/palDrange.c
@@ -0,0 +1,77 @@
+/*
+*+
+*  Name:
+*     palDrange
+
+*  Purpose:
+*     Normalize angle into range +/- pi
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDrange( double angle )
+
+*  Arguments:
+*     angle = double (Given)
+*        The angle in radians.
+
+*  Description:
+*     The result is "angle" expressed in the range +/- pi. If the
+*     supplied value for "angle" is equal to +/- pi, it is returned
+*     unchanged.
+
+*  Authors:
+*     DSB: David S Berry (JAC, Hawaii)
+*     PTW: Patrick T. Wallace
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-05-09 (DSB):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include <math.h>
+
+double palDrange( double angle ){
+   double result = fmod( angle, PAL__D2PI );
+   if( result > PAL__DPI ) {
+      result -= PAL__D2PI;
+   } else if( result < -PAL__DPI ) {
+      result += PAL__D2PI;
+   }
+   return result;
+}
+
diff --git a/ast/pal/palDs2tp.c b/ast/pal/palDs2tp.c
new file mode 100644
index 0000000..cbf582a
--- /dev/null
+++ b/ast/pal/palDs2tp.c
@@ -0,0 +1,127 @@
+/*
+*+
+*  Name:
+*     palDs2tp
+
+*  Purpose:
+*     Spherical to tangent plane projection
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDs2tp( double ra, double dec, double raz, double decz,
+*               double *xi, double *eta, int *j );
+
+*  Arguments:
+*     ra = double (Given)
+*        RA spherical coordinate of point to be projected (radians)
+*     dec = double (Given)
+*        Dec spherical coordinate of point to be projected (radians)
+*     raz = double (Given)
+*        RA spherical coordinate of tangent point (radians)
+*     decz = double (Given)
+*        Dec spherical coordinate of tangent point (radians)
+*     xi = double * (Returned)
+*        First rectangular coordinate on tangent plane (radians)
+*     eta = double * (Returned)
+*        Second rectangular coordinate on tangent plane (radians)
+*     j = int * (Returned)
+*        status: 0 = OK, star on tangent plane
+*                1 = error, star too far from axis
+*                2 = error, antistar on tangent plane
+*                3 = error, antistar too far from axis
+
+*  Description:
+*     Projection of spherical coordinates onto tangent plane:
+*     "gnomonic" projection - "standard coordinates"
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1996 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include <math.h>
+
+void
+palDs2tp ( double ra, double dec, double raz, double decz,
+           double *xi, double *eta, int *j ) {
+
+  const double TINY = 1.0e-6;
+
+  double cdec;
+  double sdec;
+  double radif;
+  double cdecz;
+  double denom;
+  double sdecz;
+  double cradif;
+  double sradif;
+
+  /*  Trig functions */
+  sdecz = sin(decz);
+  sdec = sin(dec);
+  cdecz = cos(decz);
+  cdec = cos(dec);
+  radif = ra - raz;
+  sradif = sin(radif);
+  cradif = cos(radif);
+
+  /*  Reciprocal of star vector length to tangent plane */
+  denom = sdec * sdecz + cdec * cdecz * cradif;
+
+  /*  Handle vectors too far from axis */
+  if (denom > TINY) {
+    *j = 0;
+  } else if (denom >= 0.) {
+    *j = 1;
+    denom = TINY;
+  } else if (denom > -TINY) {
+    *j = 2;
+    denom = -TINY;
+  } else {
+    *j = 3;
+  }
+
+  /*  Compute tangent plane coordinates (even in dubious cases) */
+  *xi = cdec * sradif / denom;
+  *eta = (sdec * cdecz - cdec * sdecz * cradif) / denom;
+
+  return;
+}
diff --git a/ast/pal/palDtp2s.c b/ast/pal/palDtp2s.c
new file mode 100644
index 0000000..583a56d
--- /dev/null
+++ b/ast/pal/palDtp2s.c
@@ -0,0 +1,95 @@
+/*
+*+
+*  Name:
+*     palDtp2s
+
+*  Purpose:
+*     Tangent plane to spherical coordinates
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDtp2s( double xi, double eta, double raz, double decz,
+*               double *ra, double *dec);
+
+*  Arguments:
+*     xi = double (Given)
+*        First rectangular coordinate on tangent plane (radians)
+*     eta = double (Given)
+*        Second rectangular coordinate on tangent plane (radians)
+*     raz = double (Given)
+*        RA spherical coordinate of tangent point (radians)
+*     decz = double (Given)
+*        Dec spherical coordinate of tangent point (radians)
+*     ra = double * (Returned)
+*        RA spherical coordinate of point to be projected (radians)
+*     dec = double * (Returned)
+*        Dec spherical coordinate of point to be projected (radians)
+
+*  Description:
+*     Transform tangent plane coordinates into spherical.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+#include <math.h>
+
+void
+palDtp2s ( double xi, double eta, double raz, double decz,
+           double *ra, double *dec ) {
+
+  double cdecz;
+  double denom;
+  double sdecz;
+  double d;
+
+  sdecz = sin(decz);
+  cdecz = cos(decz);
+  denom = cdecz - eta * sdecz;
+  d = atan2(xi, denom) + raz;
+  *ra = eraAnp(d);
+  *dec = atan2(sdecz + eta * cdecz, sqrt(xi * xi + denom * denom));
+
+  return;
+}
diff --git a/ast/pal/palDtps2c.c b/ast/pal/palDtps2c.c
new file mode 100644
index 0000000..ecb3090
--- /dev/null
+++ b/ast/pal/palDtps2c.c
@@ -0,0 +1,151 @@
+/*
+*+
+*  Name:
+*     palDtps2c
+
+*  Purpose:
+*     Determine RA,Dec of tangent point from coordinates
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDtps2c( double xi, double eta, double ra, double dec,
+*                double * raz1, double decz1,
+*                double * raz2, double decz2, int *n);
+
+*  Arguments:
+*     xi = double (Given)
+*        First rectangular coordinate on tangent plane (radians)
+*     eta = double (Given)
+*        Second rectangular coordinate on tangent plane (radians)
+*     ra = double (Given)
+*        RA spherical coordinate of star (radians)
+*     dec = double (Given)
+*        Dec spherical coordinate of star (radians)
+*     raz1 = double * (Returned)
+*        RA spherical coordinate of tangent point, solution 1 (radians)
+*     decz1 = double * (Returned)
+*        Dec spherical coordinate of tangent point, solution 1 (radians)
+*     raz2 = double * (Returned)
+*        RA spherical coordinate of tangent point, solution 2 (radians)
+*     decz2 = double * (Returned)
+*        Dec spherical coordinate of tangent point, solution 2 (radians)
+*     n = int * (Returned)
+*        number of solutions: 0 = no solutions returned (note 2)
+*                             1 = only the first solution is useful (note 3)
+*                             2 = both solutions are useful (note 3)
+
+
+*  Description:
+*     From the tangent plane coordinates of a star of known RA,Dec,
+*     determine the RA,Dec of the tangent point.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     - The RAZ1 and RAZ2 values are returned in the range 0-2pi.
+*     - Cases where there is no solution can only arise near the poles.
+*       For example, it is clearly impossible for a star at the pole
+*       itself to have a non-zero XI value, and hence it is
+*       meaningless to ask where the tangent point would have to be
+*       to bring about this combination of XI and DEC.
+*     - Also near the poles, cases can arise where there are two useful
+*       solutions.  The argument N indicates whether the second of the
+*       two solutions returned is useful.  N=1 indicates only one useful
+*       solution, the usual case;  under these circumstances, the second
+*       solution corresponds to the "over-the-pole" case, and this is
+*       reflected in the values of RAZ2 and DECZ2 which are returned.
+*     - The DECZ1 and DECZ2 values are returned in the range +/-pi, but
+*       in the usual, non-pole-crossing, case, the range is +/-pi/2.
+*     - This routine is the spherical equivalent of the routine sla_DTPV2C.
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+#include <math.h>
+
+void
+palDtps2c( double xi, double eta, double ra, double dec,
+           double * raz1, double * decz1,
+           double * raz2, double * decz2, int *n) {
+
+  double x2;
+  double y2;
+  double sd;
+  double cd;
+  double sdf;
+  double r2;
+
+  x2 = xi * xi;
+  y2 = eta * eta;
+  sd = sin(dec);
+  cd = cos(dec);
+  sdf = sd * sqrt(x2 + 1. + y2);
+  r2 = cd * cd * (y2 + 1.) - sd * sd * x2;
+  if (r2 >= 0.) {
+    double r;
+    double s;
+    double c;
+
+    r = sqrt(r2);
+    s = sdf - eta * r;
+    c = sdf * eta + r;
+    if (xi == 0. && r == 0.) {
+      r = 1.;
+    }
+    *raz1 = eraAnp(ra - atan2(xi, r));
+    *decz1 = atan2(s, c);
+    r = -r;
+    s = sdf - eta * r;
+    c = sdf * eta + r;
+    *raz2 = eraAnp(ra - atan2(xi, r));
+    *decz2 = atan2(s, c);
+    if (fabs(sdf) < 1.) {
+      *n = 1;
+    } else {
+      *n = 2;
+    }
+  } else {
+    *n = 0;
+  }
+  return;
+}
diff --git a/ast/pal/palDtt.c b/ast/pal/palDtt.c
new file mode 100644
index 0000000..f6a3714
--- /dev/null
+++ b/ast/pal/palDtt.c
@@ -0,0 +1,77 @@
+/*
+*+
+*  Name:
+*     palDtt
+
+*  Purpose:
+*     Return offset between UTC and TT
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     dtt = palDtt( double utc );
+
+*  Arguments:
+*     utc = double (Given)
+*        UTC date as a modified JD (JD-2400000.5)
+
+*  Returned Value:
+*     dtt = double
+*        TT-UTC in seconds
+
+*  Description:
+*     Increment to be applied to Coordinated Universal Time UTC to give
+*     Terrestrial Time TT (formerly Ephemeris Time ET)
+
+*  Authors:
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     PTW: Patrick T. Wallace
+*     {enter_new_authors_here}
+
+*  Notes:
+*     - Consider a comprehensive upgrade to use the time transformations in SOFA's time
+*       cookbook:  http://www.iausofa.org/sofa_ts_c.pdf.
+*     - See eraDat for a description of error conditions when calling this function
+*       with a time outside of the UTC range. This behaviour differs from slaDtt.
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+
+double palDtt( double utc ) {
+  return 32.184 + palDat( utc );
+}
diff --git a/ast/pal/palEcmat.c b/ast/pal/palEcmat.c
new file mode 100644
index 0000000..e9d9aeb
--- /dev/null
+++ b/ast/pal/palEcmat.c
@@ -0,0 +1,82 @@
+/*
+*+
+*  Name:
+*     palEcmat
+
+*  Purpose:
+*     Form the equatorial to ecliptic rotation matrix - IAU 2006
+*     precession model.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palEcmat( double date, double rmat[3][3] )
+
+*  Arguments:
+*     date = double (Given)
+*        TT as Modified Julian Date (JD-2400000.5). The difference
+*        between TT and TDB is of the order of a millisecond or two
+*        (i.e. about 0.02 arc-seconds).
+*     rmat = double[3][3] (Returned)
+*        Rotation matrix
+
+*  Description:
+*     The equatorial to ecliptic rotation matrix is found and returned.
+*     The matrix is in the sense   V(ecl)  =  RMAT * V(equ);  the
+*     equator, equinox and ecliptic are mean of date.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-10 (DSB):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1996 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include "pal1sofa.h"
+
+void palEcmat( double date, double rmat[3][3] ) {
+
+/* Mean obliquity (the angle between the ecliptic and mean equator of
+   date). */
+   double eps0 = eraObl06( PAL__MJD0, date );
+
+/* Matrix */
+   palDeuler( "X", eps0, 0.0, 0.0, rmat );
+
+}
diff --git a/ast/pal/palEqgal.c b/ast/pal/palEqgal.c
new file mode 100644
index 0000000..9df3d09
--- /dev/null
+++ b/ast/pal/palEqgal.c
@@ -0,0 +1,118 @@
+/*
+*+
+*  Name:
+*     palEqgal
+
+*  Purpose:
+*     Convert from J2000.0 equatorial coordinates to Galactic
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palEqgal ( double dr, double dd, double *dl, double *db );
+
+*  Arguments:
+*     dr = double (Given)
+*       J2000.0 RA (radians)
+*     dd = double (Given)
+*       J2000.0 Dec (radians
+*     dl = double * (Returned)
+*       Galactic longitude (radians).
+*     db = double * (Returned)
+*       Galactic latitude (radians).
+
+*  Description:
+*     Transformation from J2000.0 equatorial coordinates
+*     to IAU 1958 galactic coordinates.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     The equatorial coordinates are J2000.0.  Use the routine
+*     palGe50 if conversion to B1950.0 'FK4' coordinates is
+*     required.
+
+*  See Also:
+*     Blaauw et al, Mon.Not.R.Astron.Soc.,121,123 (1960)
+
+*  History:
+*     2012-02-12(TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1998 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palEqgal ( double dr, double dd, double *dl, double *db ) {
+
+  double v1[3];
+  double v2[3];
+
+/*
+*  L2,B2 system of galactic coordinates
+*
+*  P = 192.25       RA of galactic north pole (mean B1950.0)
+*  Q =  62.6        inclination of galactic to mean B1950.0 equator
+*  R =  33          longitude of ascending node
+*
+*  P,Q,R are degrees
+*
+*  Equatorial to galactic rotation matrix (J2000.0), obtained by
+*  applying the standard FK4 to FK5 transformation, for zero proper
+*  motion in FK5, to the columns of the B1950 equatorial to
+*  galactic rotation matrix:
+*/
+  double rmat[3][3] = {
+    { -0.054875539726,-0.873437108010,-0.483834985808 },
+    { +0.494109453312,-0.444829589425,+0.746982251810 },
+    { -0.867666135858,-0.198076386122,+0.455983795705 }
+  };
+
+  /* Spherical to Cartesian */
+  eraS2c( dr, dd, v1 );
+
+  /* Equatorial to Galactic */
+  eraRxp( rmat, v1, v2 );
+
+  /* Cartesian to spherical */
+  eraC2s( v2, dl, db );
+
+  /* Express in conventional ranges */
+  *dl = eraAnp( *dl );
+  *db = eraAnpm( *db );
+
+}
diff --git a/ast/pal/palEtrms.c b/ast/pal/palEtrms.c
new file mode 100644
index 0000000..4484682
--- /dev/null
+++ b/ast/pal/palEtrms.c
@@ -0,0 +1,106 @@
+/*
+*+
+*  Name:
+*     palEtrms
+
+*  Purpose:
+*     Compute the E-terms vector
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palEtrms ( double ep, double ev[3] );
+
+*  Arguments:
+*     ep = double (Given)
+*        Besselian epoch
+*     ev = double [3] (Returned)
+*        E-terms as (dx,dy,dz)
+
+*  Description:
+*     Computes the E-terms (elliptic component of annual aberration)
+*     vector.
+*
+*     Note the use of the J2000 aberration constant (20.49552 arcsec).
+*     This is a reflection of the fact that the E-terms embodied in
+*     existing star catalogues were computed from a variety of
+*     aberration constants.  Rather than adopting one of the old
+*     constants the latest value is used here.
+*
+*  See also:
+*     - Smith, C.A. et al., 1989.  Astr.J. 97, 265.
+*     - Yallop, B.D. et al., 1989.  Astr.J. 97, 274.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-12 (TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1996 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+
+void palEtrms ( double ep, double ev[3] ) {
+
+  /* Use the J2000 aberration constant */
+  const double ABCONST = 20.49552;
+
+  double t, e, e0, p, ek, cp;
+
+  /*  Julian centuries since B1950 */
+  t = (ep - 1950.) * .0100002135903;
+
+  /*  Eccentricity */
+  e = .01673011 - (t * 1.26e-7 + 4.193e-5) * t;
+
+  /*  Mean obliquity */
+  e0 = (84404.836 - ((t * .00181 + .00319) * t + 46.8495) * t) *
+    PAL__DAS2R;
+
+  /*  Mean longitude of perihelion */
+  p = (((t * .012 + 1.65) * t + 6190.67) * t + 1015489.951) *
+    PAL__DAS2R;
+
+  /*  E-terms */
+  ek = e * ABCONST * PAL__DAS2R;
+  cp = cos(p);
+  ev[0] = ek * sin(p);
+  ev[1] = -ek * cp * cos(e0);
+  ev[2] = -ek * cp * sin(e0);
+
+}
diff --git a/ast/pal/palEvp.c b/ast/pal/palEvp.c
new file mode 100644
index 0000000..b30a3a9
--- /dev/null
+++ b/ast/pal/palEvp.c
@@ -0,0 +1,110 @@
+/*
+*+
+*  Name:
+*     palEvp
+
+*  Purpose:
+*     Returns the barycentric and heliocentric velocity and position of the
+*     Earth.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palEvp( double date, double deqx, double dvb[3], double dpb[3],
+*                  double dvh[3], double dph[3] )
+
+*  Arguments:
+*     date = double (Given)
+*        TDB (loosely ET) as a Modified Julian Date (JD-2400000.5)
+*     deqx = double (Given)
+*        Julian epoch (e.g. 2000.0) of mean equator and equinox of the
+*        vectors returned.  If deqx <= 0.0, all vectors are referred to the
+*        mean equator and equinox (FK5) of epoch date.
+*     dvb = double[3] (Returned)
+*        Barycentric velocity (AU/s, AU)
+*     dpb = double[3] (Returned)
+*        Barycentric position (AU/s, AU)
+*     dvh = double[3] (Returned)
+*        heliocentric velocity (AU/s, AU)
+*     dph = double[3] (Returned)
+*        Heliocentric position (AU/s, AU)
+
+*  Description:
+*     Returns the barycentric and heliocentric velocity and position of the
+*     Earth at a given epoch, given with respect to a specified equinox.
+*     For information about accuracy, see the function eraEpv00.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-13 (PTW):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2005 Patrick T. Wallace
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include "pal1sofa.h"
+
+void palEvp( double date, double deqx, double dvb[3], double dpb[3],
+             double dvh[3], double dph[3] ){
+
+/* Local Variables; */
+   int i;
+   double pvh[2][3], pvb[2][3], d1, d2, r[3][3];
+
+/* BCRS PV-vectors. */
+   eraEpv00 ( 2400000.5, date, pvh, pvb );
+
+/* Was precession to another equinox requested? */
+   if ( deqx > 0.0 ) {
+
+/* Yes: compute precession matrix from J2000.0 to deqx. */
+      eraEpj2jd ( deqx, &d1, &d2 );
+      eraPmat06 ( d1, d2, r );
+
+/* Rotate the PV-vectors. */
+      eraRxpv ( r, pvh, pvh );
+      eraRxpv ( r, pvb, pvb );
+   }
+
+/* Return the required vectors. */
+   for ( i = 0; i < 3; i++ ) {
+      dvh[i] = pvh[1][i] / PAL__SPD;
+      dvb[i] = pvb[1][i] / PAL__SPD;
+      dph[i] = pvh[0][i];
+      dpb[i] = pvb[0][i];
+   }
+}
diff --git a/ast/pal/palFk45z.c b/ast/pal/palFk45z.c
new file mode 100644
index 0000000..643fd74
--- /dev/null
+++ b/ast/pal/palFk45z.c
@@ -0,0 +1,186 @@
+/*
+*+
+*  Name:
+*     palFk45z
+
+*  Purpose:
+*     Convert B1950.0 FK4 star data to J2000.0 FK5 assuming zero
+*     proper motion in the FK5 frame
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palFk45z( double r1950, double d1950, double bepoch, double *r2000,
+*               double *d2000 )
+
+*  Arguments:
+*     r1950 = double (Given)
+*        B1950.0 FK4 RA at epoch (radians).
+*     d1950 = double (Given)
+*        B1950.0 FK4 Dec at epoch (radians).
+*     bepoch = double (Given)
+*        Besselian epoch (e.g. 1979.3)
+*     r2000 = double (Returned)
+*        J2000.0 FK5 RA (Radians).
+*     d2000 = double (Returned)
+*        J2000.0 FK5 Dec(Radians).
+
+*  Description:
+*     Convert B1950.0 FK4 star data to J2000.0 FK5 assuming zero
+*     proper motion in the FK5 frame (double precision)
+*
+*     This function converts stars from the Bessel-Newcomb, FK4
+*     system to the IAU 1976, FK5, Fricke system, in such a
+*     way that the FK5 proper motion is zero.  Because such a star
+*     has, in general, a non-zero proper motion in the FK4 system,
+*     the routine requires the epoch at which the position in the
+*     FK4 system was determined.
+*
+*     The method is from Appendix 2 of Ref 1, but using the constants
+*     of Ref 4.
+
+*  Notes:
+*     - The epoch BEPOCH is strictly speaking Besselian, but if a
+*     Julian epoch is supplied the result will be affected only to
+*     a negligible extent.
+*
+*     - Conversion from Besselian epoch 1950.0 to Julian epoch 2000.0
+*     only is provided for.  Conversions involving other epochs will
+*     require use of the appropriate precession, proper motion, and
+*     E-terms routines before and/or after palFk45z is called.
+*
+*     - In the FK4 catalogue the proper motions of stars within 10
+*     degrees of the poles do not embody the differential E-term effect
+*     and should, strictly speaking, be handled in a different manner
+*     from stars outside these regions. However, given the general lack
+*     of homogeneity of the star data available for routine astrometry,
+*     the difficulties of handling positions that may have been
+*     determined from astrometric fields spanning the polar and non-polar
+*     regions, the likelihood that the differential E-terms effect was not
+*     taken into account when allowing for proper motion in past
+*     astrometry, and the undesirability of a discontinuity in the
+*     algorithm, the decision has been made in this routine to include the
+*     effect of differential E-terms on the proper motions for all stars,
+*     whether polar or not.  At epoch 2000, and measuring on the sky rather
+*     than in terms of dRA, the errors resulting from this simplification
+*     are less than 1 milliarcsecond in position and 1 milliarcsecond per
+*     century in proper motion.
+*
+*  References:
+*     - Aoki,S., et al, 1983.  Astron.Astrophys., 128, 263.
+*     - Smith, C.A. et al, 1989.  "The transformation of astrometric
+*       catalog systems to the equinox J2000.0".  Astron.J. 97, 265.
+*     - Yallop, B.D. et al, 1989.  "Transformation of mean star places
+*       from FK4 B1950.0 to FK5 J2000.0 using matrices in 6-space".
+*       Astron.J. 97, 274.
+*     - Seidelmann, P.K. (ed), 1992.  "Explanatory Supplement to
+*       the Astronomical Almanac", ISBN 0-935702-68-7.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-10 (DSB):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1998 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include "pal1sofa.h"
+
+void palFk45z( double r1950, double d1950, double bepoch, double *r2000,
+               double *d2000 ){
+
+/* Local Variables: */
+   double w;
+   int i;
+   int j;
+   double r0[3], a1[3], v1[3], v2[6]; /* Position and position+velocity vectors */
+
+
+/* CANONICAL CONSTANTS  (see references) */
+
+/* Vector A. */
+   double a[3] = { -1.62557E-6, -0.31919E-6, -0.13843E-6 };
+
+/* Vectors Adot. */
+   double ad[3] = { 1.245E-3, -1.580E-3, -0.659E-3 };
+
+/* Matrix M (only half of which is needed here). */
+   double em[6][3] = { {0.9999256782, -0.0111820611, -0.0048579477},
+                       {0.0111820610, 0.9999374784, -0.0000271765},
+                       {0.0048579479, -0.0000271474, 0.9999881997},
+                       {-0.000551, -0.238565, 0.435739},
+                       {0.238514, -0.002667, -0.008541},
+                       {-0.435623, 0.012254, 0.002117} };
+
+
+/* Spherical to Cartesian. */
+   eraS2c( r1950, d1950, r0 );
+
+/* Adjust vector A to give zero proper motion in FK5. */
+   w = ( bepoch - 1950.0 )/PAL__PMF;
+   for( i = 0; i < 3; i++ ) {
+      a1[ i ] = a[ i ] + w*ad[ i ];
+   }
+
+/* Remove e-terms. */
+   w = r0[ 0 ]*a1[ 0 ] + r0[ 1 ]*a1[ 1 ] + r0[ 2 ]*a1[ 2 ];
+   for( i = 0; i < 3; i++ ) {
+      v1[ i ] = r0[ i ] - a1[ i ] + w*r0[ i ];
+   }
+
+/* Convert position vector to Fricke system. */
+   for( i = 0; i < 6; i++ ) {
+      w = 0.0;
+      for( j = 0; j < 3; j++ ) {
+         w += em[ i ][ j ]*v1[ j ];
+      }
+      v2[ i ] = w;
+   }
+
+/* Allow for fictitious proper motion in FK4. */
+   w = ( palEpj( palEpb2d( bepoch ) ) - 2000.0 )/PAL__PMF;
+   for( i = 0; i < 3; i++ ) {
+      v2[ i ] += w*v2[ i + 3 ];
+   }
+
+/* Revert to spherical coordinates. */
+   eraC2s( v2, &w, d2000 );
+   *r2000 = eraAnp( w );
+}
+
+
diff --git a/ast/pal/palFk524.c b/ast/pal/palFk524.c
new file mode 100644
index 0000000..47c3abb
--- /dev/null
+++ b/ast/pal/palFk524.c
@@ -0,0 +1,259 @@
+/*
+*+
+*  Name:
+*     palFk524
+
+*  Purpose:
+*     Convert J2000.0 FK5 star data to B1950.0 FK4.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palFk524( double r2000, double d2000, double dr2000, double dd2000,
+*               double p2000, double v2000, double *r1950, double *d1950,
+*               double *dr1950, double *dd1950, double *p1950, double *v1950 )
+
+*  Arguments:
+*     r2000 = double (Given)
+*        J2000.0 FK5 RA (radians).
+*     d2000 = double (Given)
+*        J2000.0 FK5 Dec (radians).
+*     dr2000 = double (Given)
+*        J2000.0 FK5 RA proper motion (rad/Jul.yr)
+*     dd2000 = double (Given)
+*        J2000.0 FK5 Dec proper motion (rad/Jul.yr)
+*     p2000 = double (Given)
+*        J2000.0 FK5 parallax (arcsec)
+*     v2000 = double (Given)
+*         J2000.0 FK5 radial velocity (km/s, +ve = moving away)
+*     r1950 = double * (Returned)
+*        B1950.0 FK4 RA (radians).
+*     d1950 = double * (Returned)
+*        B1950.0 FK4 Dec (radians).
+*     dr1950 = double * (Returned)
+*        B1950.0 FK4 RA proper motion (rad/Jul.yr)
+*     dd1950 = double * (Returned)
+*        B1950.0 FK4 Dec proper motion (rad/Jul.yr)
+*     p1950 = double * (Returned)
+*        B1950.0 FK4 parallax (arcsec)
+*     v1950 = double * (Returned)
+*         B1950.0 FK4 radial velocity (km/s, +ve = moving away)
+
+*  Description:
+*     This function converts stars from the IAU 1976, FK5, Fricke
+*     system, to the Bessel-Newcomb, FK4 system.  The precepts
+*     of Smith et al (Ref 1) are followed, using the implementation
+*     by Yallop et al (Ref 2) of a matrix method due to Standish.
+*     Kinoshita's development of Andoyer's post-Newcomb precession is
+*     used.  The numerical constants from Seidelmann et al (Ref 3) are
+*     used canonically.
+
+*  Notes:
+*     - The proper motions in RA are dRA/dt rather than
+*     cos(Dec)*dRA/dt, and are per year rather than per century.
+*     - Note that conversion from Julian epoch 2000.0 to Besselian
+*     epoch 1950.0 only is provided for.  Conversions involving
+*     other epochs will require use of the appropriate precession,
+*     proper motion, and E-terms routines before and/or after
+*     FK524 is called.
+*     - In the FK4 catalogue the proper motions of stars within
+*     10 degrees of the poles do not embody the differential
+*     E-term effect and should, strictly speaking, be handled
+*     in a different manner from stars outside these regions.
+*     However, given the general lack of homogeneity of the star
+*     data available for routine astrometry, the difficulties of
+*     handling positions that may have been determined from
+*     astrometric fields spanning the polar and non-polar regions,
+*     the likelihood that the differential E-terms effect was not
+*     taken into account when allowing for proper motion in past
+*     astrometry, and the undesirability of a discontinuity in
+*     the algorithm, the decision has been made in this routine to
+*     include the effect of differential E-terms on the proper
+*     motions for all stars, whether polar or not.  At epoch 2000,
+*     and measuring on the sky rather than in terms of dRA, the
+*     errors resulting from this simplification are less than
+*     1 milliarcsecond in position and 1 milliarcsecond per
+*     century in proper motion.
+*
+*  References:
+*     - Smith, C.A. et al, 1989.  "The transformation of astrometric
+*       catalog systems to the equinox J2000.0".  Astron.J. 97, 265.
+*     - Yallop, B.D. et al, 1989.  "Transformation of mean star places
+*       from FK4 B1950.0 to FK5 J2000.0 using matrices in 6-space".
+*       Astron.J. 97, 274.
+*     - Seidelmann, P.K. (ed), 1992.  "Explanatory Supplement to
+*       the Astronomical Almanac", ISBN 0-935702-68-7.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-13 (DSB):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include "math.h"
+
+void palFk524( double r2000, double d2000, double dr2000, double dd2000,
+               double p2000, double v2000, double *r1950, double *d1950,
+               double *dr1950, double *dd1950, double *p1950, double *v1950 ){
+
+/* Local Variables; */
+   double r, d, ur, ud, px, rv;
+   double sr, cr, sd, cd, x, y, z, w;
+   double v1[ 6 ], v2[ 6 ];
+   double xd, yd, zd;
+   double rxyz, wd, rxysq, rxy;
+   int i, j;
+
+/* Small number to avoid arithmetic problems. */
+   static const double tiny = 1.0E-30;
+
+/* Canonical constants (see references). Constant vector and matrix. */
+   double a[ 6 ] = { -1.62557E-6,  -0.31919E-6, -0.13843E-6,
+                     +1.245E-3,    -1.580E-3,   -0.659E-3 };
+   double emi[ 6 ][ 6 ] = {
+                 { 0.9999256795,      0.0111814828,      0.0048590039,
+                  -0.00000242389840, -0.00000002710544, -0.00000001177742},
+                 {-0.0111814828,      0.9999374849,     -0.0000271771,
+                   0.00000002710544, -0.00000242392702,  0.00000000006585 },
+                 {-0.0048590040,     -0.0000271557,      0.9999881946,
+                   0.00000001177742,  0.00000000006585, -0.00000242404995 },
+                 {-0.000551,          0.238509,         -0.435614,
+                   0.99990432,        0.01118145,        0.00485852 },
+                 {-0.238560,         -0.002667,          0.012254,
+                  -0.01118145,        0.99991613,       -0.00002717},
+                 { 0.435730,         -0.008541,          0.002117,
+                  -0.00485852,       -0.00002716,        0.99996684 } };
+
+/* Pick up J2000 data (units radians and arcsec/JC). */
+   r = r2000;
+   d = d2000;
+   ur = dr2000*PAL__PMF;
+   ud = dd2000*PAL__PMF;
+   px = p2000;
+   rv = v2000;
+
+/* Spherical to Cartesian. */
+   sr = sin( r );
+   cr = cos( r );
+   sd = sin( d );
+   cd = cos( d );
+
+   x = cr*cd;
+   y = sr*cd;
+   z =    sd;
+
+   w = PAL__VF*rv*px;
+
+   v1[ 0 ] = x;
+   v1[ 1 ] = y;
+   v1[ 2 ] = z;
+
+   v1[ 3 ] = -ur*y - cr*sd*ud + w*x;
+   v1[ 4 ] =  ur*x - sr*sd*ud + w*y;
+   v1[ 5 ] =            cd*ud + w*z;
+
+/* Convert position+velocity vector to BN system. */
+   for( i = 0; i < 6; i++ ) {
+      w = 0.0;
+      for( j = 0; j < 6; j++ ) {
+         w += emi[ i ][ j ]*v1[ j ];
+      }
+      v2[ i ] = w;
+   }
+
+/* Position vector components and magnitude. */
+   x = v2[ 0 ];
+   y = v2[ 1 ];
+   z = v2[ 2 ];
+   rxyz = sqrt( x*x + y*y + z*z );
+
+/* Apply E-terms to position. */
+   w = x*a[ 0 ] + y*a[ 1 ] + z*a[ 2 ];
+   x += a[ 0 ]*rxyz - w*x;
+   y += a[ 1 ]*rxyz - w*y;
+   z += a[ 2 ]*rxyz - w*z;
+
+/* Recompute magnitude. */
+   rxyz = sqrt( x*x + y*y + z*z );
+
+/* Apply E-terms to both position and velocity. */
+   x = v2[ 0 ];
+   y = v2[ 1 ];
+   z = v2[ 2 ];
+   w = x*a[ 0 ] + y*a[ 1 ] + z*a[ 2 ];
+   wd = x*a[ 3 ] + y*a[ 4 ] + z*a[ 5 ];
+   x += a[ 0 ]*rxyz - w*x;
+   y += a[ 1 ]*rxyz - w*y;
+   z += a[ 2 ]*rxyz - w*z;
+   xd = v2[ 3 ] + a[ 3 ]*rxyz - wd*x;
+   yd = v2[ 4 ] + a[ 4 ]*rxyz - wd*y;
+   zd = v2[ 5 ] + a[ 5 ]*rxyz - wd*z;
+
+/* Convert to spherical. */
+   rxysq = x*x + y*y;
+   rxy = sqrt( rxysq );
+
+   if( x == 0.0 && y == 0.0 ) {
+      r = 0.0;
+   } else {
+      r = atan2( y, x );
+      if( r <  0.0 ) r += PAL__D2PI;
+   }
+   d = atan2( z, rxy );
+
+   if( rxy > tiny ) {
+      ur = ( x*yd - y*xd )/rxysq;
+      ud = ( zd*rxysq - z*( x*xd + y*yd ) )/( ( rxysq + z*z )*rxy );
+   }
+
+/* Radial velocity and parallax. */
+   if( px > tiny ) {
+      rv = ( x*xd + y*yd + z*zd )/( px*PAL__VF*rxyz );
+      px /= rxyz;
+   }
+
+/* Return results. */
+   *r1950 = r;
+   *d1950 = d;
+   *dr1950 = ur/PAL__PMF;
+   *dd1950 = ud/PAL__PMF;
+   *p1950 = px;
+   *v1950 = rv;
+}
diff --git a/ast/pal/palFk54z.c b/ast/pal/palFk54z.c
new file mode 100644
index 0000000..b902b0d
--- /dev/null
+++ b/ast/pal/palFk54z.c
@@ -0,0 +1,113 @@
+/*
+*+
+*  Name:
+*     palFk54z
+
+*  Purpose:
+*     Convert a J2000.0 FK5 star position to B1950.0 FK4 assuming
+*     zero proper motion and parallax.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palFk54z( double r2000, double d2000, double bepoch, double *r1950,
+*               double *d1950, double *dr1950, double *dd1950 )
+
+*  Arguments:
+*     r2000 = double (Given)
+*        J2000.0 FK5 RA (radians).
+*     d2000 = double (Given)
+*        J2000.0 FK5 Dec (radians).
+*     bepoch = double (Given)
+*         Besselian epoch (e.g. 1950.0).
+*     r1950 = double * (Returned)
+*        B1950 FK4 RA (radians) at epoch "bepoch".
+*     d1950 = double * (Returned)
+*        B1950 FK4 Dec (radians) at epoch "bepoch".
+*     dr1950 = double * (Returned)
+*        B1950 FK4 proper motion (RA) (radians/trop.yr)).
+*     dr1950 = double * (Returned)
+*        B1950 FK4 proper motion (Dec) (radians/trop.yr)).
+
+*  Description:
+*     This function converts star positions from the IAU 1976,
+*     FK5, Fricke system to the Bessel-Newcomb, FK4 system.
+
+*  Notes:
+*     - The proper motion in RA is dRA/dt rather than cos(Dec)*dRA/dt.
+*     - Conversion from Julian epoch 2000.0 to Besselian epoch 1950.0
+*     only is provided for.  Conversions involving other epochs will
+*     require use of the appropriate precession functions before and
+*     after this function is called.
+*     - The FK5 proper motions, the parallax and the radial velocity
+*      are presumed zero.
+*     - It is the intention that FK5 should be a close approximation
+*     to an inertial frame, so that distant objects have zero proper
+*     motion;  such objects have (in general) non-zero proper motion
+*     in FK4, and this function returns those fictitious proper
+*     motions.
+*     - The position returned by this function is in the B1950
+*     reference frame but at Besselian epoch BEPOCH.  For comparison
+*     with catalogues the "bepoch" argument will frequently be 1950.0.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-13 (DSB):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palFk54z( double r2000, double d2000, double bepoch, double *r1950,
+               double *d1950, double *dr1950, double *dd1950 ){
+
+/* Local Variables: */
+   double r, d, px, rv, y;
+
+/* FK5 equinox J2000 (any epoch) to FK4 equinox B1950 epoch B1950. */
+   palFk524( r2000, d2000, 0.0, 0.0, 0.0, 0.0, &r, &d, dr1950, dd1950,
+             &px, &rv );
+
+/* Fictitious proper motion to epoch "bepoch". */
+   y = bepoch - 1950.0;
+   *r1950 = r + *dr1950*y;
+   *d1950 = d + *dd1950*y;
+
+}
+
diff --git a/ast/pal/palGaleq.c b/ast/pal/palGaleq.c
new file mode 100644
index 0000000..d0da1ee
--- /dev/null
+++ b/ast/pal/palGaleq.c
@@ -0,0 +1,118 @@
+/*
+*+
+*  Name:
+*     palGaleq
+
+*  Purpose:
+*     Convert from galactic to J2000.0 equatorial coordinates
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palGaleq ( double dl, double db, double *dr, double *dd );
+
+*  Arguments:
+*     dl = double (Given)
+*       Galactic longitude (radians).
+*     db = double (Given)
+*       Galactic latitude (radians).
+*     dr = double * (Returned)
+*       J2000.0 RA (radians)
+*     dd = double * (Returned)
+*       J2000.0 Dec (radians)
+
+*  Description:
+*     Transformation from IAU 1958 galactic coordinates to
+*     J2000.0 equatorial coordinates.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     The equatorial coordinates are J2000.0.  Use the routine
+*     palGe50 if conversion to B1950.0 'FK4' coordinates is
+*     required.
+
+*  See Also:
+*     Blaauw et al, Mon.Not.R.Astron.Soc.,121,123 (1960)
+
+*  History:
+*     2012-02-12(TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1998 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palGaleq ( double dl, double db, double *dr, double *dd ) {
+
+  double v1[3];
+  double v2[3];
+
+/*
+*  L2,B2 system of galactic coordinates
+*
+*  P = 192.25       RA of galactic north pole (mean B1950.0)
+*  Q =  62.6        inclination of galactic to mean B1950.0 equator
+*  R =  33          longitude of ascending node
+*
+*  P,Q,R are degrees
+*
+*  Equatorial to galactic rotation matrix (J2000.0), obtained by
+*  applying the standard FK4 to FK5 transformation, for zero proper
+*  motion in FK5, to the columns of the B1950 equatorial to
+*  galactic rotation matrix:
+*/
+  double rmat[3][3] = {
+    { -0.054875539726,-0.873437108010,-0.483834985808 },
+    { +0.494109453312,-0.444829589425,+0.746982251810 },
+    { -0.867666135858,-0.198076386122,+0.455983795705 }
+  };
+
+  /* Spherical to Cartesian */
+  eraS2c( dl, db, v1 );
+
+  /* Galactic to equatorial */
+  eraTrxp( rmat, v1, v2 );
+
+  /* Cartesian to spherical */
+  eraC2s( v2, dr, dd );
+
+  /* Express in conventional ranges */
+  *dr = eraAnp( *dr );
+  *dd = eraAnpm( *dd );
+
+}
diff --git a/ast/pal/palGalsup.c b/ast/pal/palGalsup.c
new file mode 100644
index 0000000..e469fb7
--- /dev/null
+++ b/ast/pal/palGalsup.c
@@ -0,0 +1,116 @@
+/*
+*+
+*  Name:
+*     palGalsup
+
+*  Purpose:
+*     Convert from galactic to supergalactic coordinates
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palGalsup ( double dl, double db, double *dsl, double *dsb );
+
+*  Arguments:
+*     dl = double (Given)
+*       Galactic longitude.
+*     db = double (Given)
+*       Galactic latitude.
+*     dsl = double * (Returned)
+*       Supergalactic longitude.
+*     dsb = double * (Returned)
+*       Supergalactic latitude.
+
+*  Description:
+*     Transformation from IAU 1958 galactic coordinates to
+*     de Vaucouleurs supergalactic coordinates.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  See Also:
+*     - de Vaucouleurs, de Vaucouleurs, & Corwin, Second Reference
+*       Catalogue of Bright Galaxies, U. Texas, page 8.
+*     - Systems & Applied Sciences Corp., Documentation for the
+*       machine-readable version of the above catalogue,
+*       Contract NAS 5-26490.
+*
+*     (These two references give different values for the galactic
+*     longitude of the supergalactic origin.  Both are wrong;  the
+*     correct value is L2=137.37.)
+
+*  History:
+*     2012-02-12(TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1999 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palGalsup ( double dl, double db, double *dsl, double *dsb ) {
+
+  double v1[3];
+  double v2[3];
+
+/*
+*  System of supergalactic coordinates:
+*
+*    SGL   SGB        L2     B2      (deg)
+*     -    +90      47.37  +6.32
+*     0     0         -      0
+*
+*  Galactic to supergalactic rotation matrix:
+*/
+  double rmat[3][3] = {
+    { -0.735742574804,+0.677261296414,+0.000000000000 },
+    { -0.074553778365,-0.080991471307,+0.993922590400 },
+    { +0.673145302109,+0.731271165817,+0.110081262225 }
+  };
+
+  /* Spherical to Cartesian */
+  eraS2c( dl, db, v1 );
+
+  /* Galactic to Supergalactic */
+  eraRxp( rmat, v1, v2 );
+
+  /* Cartesian to spherical */
+  eraC2s( v2, dsl, dsb );
+
+  /* Express in conventional ranges */
+  *dsl = eraAnp( *dsl );
+  *dsb = eraAnpm( *dsb );
+
+}
diff --git a/ast/pal/palGeoc.c b/ast/pal/palGeoc.c
new file mode 100644
index 0000000..9954d92
--- /dev/null
+++ b/ast/pal/palGeoc.c
@@ -0,0 +1,83 @@
+/*
+*+
+*  Name:
+*     palGeoc
+
+*  Purpose:
+*     Convert geodetic position to geocentric
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palGeoc( double p, double h, double * r, double *z );
+
+*  Arguments:
+*     p = double (Given)
+*       latitude (radians)
+*     h = double (Given)
+*       height above reference spheroid (geodetic, metres)
+*     r = double * (Returned)
+*       distance from Earth axis (AU)
+*     z = double * (Returned)
+*       distance from plane of Earth equator (AU)
+
+*  Description:
+*     Convert geodetic position to geocentric.
+
+*  Authors:
+*     PTW: Patrick T. Wallace
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     - Geocentric latitude can be obtained by evaluating atan2(z,r)
+*     - Uses WGS84 reference ellipsoid and calls eraGd2gc
+
+*  History:
+*     2012-03-01 (TIMJ):
+*        Initial version moved from palOne2One
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2004 Patrick T. Wallace
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palGeoc ( double p, double h, double *r, double *z ) {
+  double xyz[3];
+  const double elong = 0.0;   /* Use zero longitude */
+  const double AU = 1.49597870E11;
+  /* WGS84 looks to be the closest match */
+  eraGd2gc( ERFA_WGS84, elong, p, h, xyz );
+  *r = xyz[0] / (AU * cos(elong) );
+  *z = xyz[2] / AU;
+}
diff --git a/ast/pal/palMappa.c b/ast/pal/palMappa.c
new file mode 100644
index 0000000..4e7ee64
--- /dev/null
+++ b/ast/pal/palMappa.c
@@ -0,0 +1,129 @@
+/*
+*+
+*  Name:
+*     palMappa
+
+*  Purpose:
+*     Compute parameters needed by palAmpqk and palMapqk.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palMappa( double eq, double date, double amprms[21] )
+
+*  Arguments:
+*     eq = double (Given)
+*        epoch of mean equinox to be used (Julian)
+*     date = double (Given)
+*        TDB (JD-2400000.5)
+*     amprms =   double[21]  (Returned)
+*        star-independent mean-to-apparent parameters:
+*        - (0)      time interval for proper motion (Julian years)
+*        - (1-3)    barycentric position of the Earth (AU)
+*        - (4-6)    heliocentric direction of the Earth (unit vector)
+*        - (7)      (Schwarzschild radius of Sun)/(Sun-Earth distance)
+*        - (8-10)   abv: barycentric Earth velocity in units of c
+*        - (11)     sqrt(1-v^2) where v=modulus(abv)
+*        - (12-20)  precession/nutation (3,3) matrix
+
+*  Description:
+*     Compute star-independent parameters in preparation for
+*     transformations between mean place and geocentric apparent place.
+*
+*     The parameters produced by this function are required in the
+*     parallax, aberration, and nutation/bias/precession parts of the
+*     mean/apparent transformations.
+*
+*     The reference systems and timescales used are IAU 2006.
+
+*  Notes:
+*     - For date, the distinction between the required TDB and TT
+*     is always negligible.  Moreover, for all but the most
+*     critical applications UTC is adequate.
+*     - The vector amprms(1-3) is referred to the mean equinox and
+*     equator of epoch eq.
+*     - The parameters amprms produced by this function are used by
+*     palAmpqk, palMapqk and palMapqkz.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-13 (PTW):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2003 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include "pal1sofa.h"
+
+#include <string.h>
+
+void palMappa( double eq, double date, double amprms[21] ){
+
+/* Local constants */
+
+/*  Gravitational radius of the Sun x 2 (2*mu/c**2, AU) */
+  const double GR2 = 2.0 * 9.87063e-9;
+
+/* Local Variables; */
+   int i;
+   double ebd[ 3 ], ehd[ 3 ], eh[ 3 ], e, vn[ 3 ], vm;
+
+/* Initialise so that unsused values are returned holding zero */
+   memset( amprms, 0, 21*sizeof( *amprms ) );
+
+/* Time interval for proper motion correction. */
+   amprms[ 0 ] = eraEpj( PAL__MJD0, date ) - eq;
+
+/* Get Earth barycentric and heliocentric position and velocity. */
+   palEvp( date, eq, ebd, &amprms[ 1 ], ehd, eh );
+
+/* Heliocentric direction of Earth (normalized) and modulus. */
+   eraPn( eh, &e, &amprms[ 4 ] );
+
+/* Light deflection parameter */
+   amprms[7] = GR2 / e;
+
+/* Aberration parameters. */
+   for( i = 0; i < 3; i++ ) {
+      amprms[ i + 8 ] = ebd[ i ]*PAL__CR;
+   }
+   eraPn( &amprms[8], &vm, vn );
+   amprms[ 11 ] = sqrt( 1.0 - vm*vm );
+
+/* NPB matrix. */
+   palPrenut( eq, date, (double(*)[ 3 ]) &amprms[ 12 ] );
+}
diff --git a/ast/pal/palMapqkz.c b/ast/pal/palMapqkz.c
new file mode 100644
index 0000000..44a2c79
--- /dev/null
+++ b/ast/pal/palMapqkz.c
@@ -0,0 +1,150 @@
+/*
+*+
+*  Name:
+*     palMapqkz
+
+*  Purpose:
+*     Quick mean to apparent place (no proper motion or parallax).
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palMapqkz( double rm, double dm, double amprms[21],
+*                     double *ra, double *da )
+
+*  Arguments:
+*     rm = double (Given)
+*        Mean RA (radians).
+*     dm = double (Given)
+*        Mean Dec (radians).
+*     amprms = double[21] (Given)
+*        Star-independent mean-to-apparent parameters (see palMappa):
+*        (0-3)    not used
+*        (4-6)    heliocentric direction of the Earth (unit vector)
+*        (7)      not used
+*        (8-10)   abv: barycentric Earth velocity in units of c
+*        (11)     sqrt(1-v^2) where v=modulus(abv)
+*        (12-20)  precession/nutation (3,3) matrix
+*     ra = double * (Returned)
+*        Apparent RA (radians).
+*     da = double * (Returned)
+*        Apparent Dec (radians).
+
+*  Description:
+*     Quick mean to apparent place:  transform a star RA,dec from
+*     mean place to geocentric apparent place, given the
+*     star-independent parameters, and assuming zero parallax
+*     and proper motion.
+*
+*     Use of this function is appropriate when efficiency is important
+*     and where many star positions, all with parallax and proper
+*     motion either zero or already allowed for, and all referred to
+*     the same equator and equinox, are to be transformed for one
+*     epoch.  The star-independent parameters can be obtained by
+*     calling the palMappa function.
+*
+*     The corresponding function for the case of non-zero parallax
+*     and proper motion is palMapqk.
+
+*  Notes:
+*     - The reference systems and timescales used are IAU 2006.
+*     - The mean place rm, dm and the vectors amprms[1-3] and amprms[4-6]
+*       are referred to the mean equinox and equator of the epoch
+*       specified when generating the precession/nutation matrix
+*       amprms[12-20].  In the call to palMappa (q.v.) normally used
+*       to populate amprms, this epoch is the first argument (eq).
+*     - The vector amprms(4-6) is referred to the mean equinox and
+*       equator of epoch eq.
+*     - Strictly speaking, the routine is not valid for solar-system
+*       sources, though the error will usually be extremely small.
+*       However, to prevent gross errors in the case where the
+*       position of the Sun is specified, the gravitational
+*       deflection term is restrained within about 920 arcsec of the
+*       centre of the Sun's disc.  The term has a maximum value of
+*       about 1.85 arcsec at this radius, and decreases to zero as
+*       the centre of the disc is approached.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-13 (PTW):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1999 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palMapqkz ( double rm, double dm, double amprms[21], double *ra,
+                 double *da ){
+
+/* Local Variables: */
+   int i;
+   double ab1, abv[3], p[3], w, p1dv, p2[3], p3[3];
+   double gr2e, pde, pdep1, ehn[3], p1[3];
+
+/* Unpack scalar and vector parameters. */
+   ab1 = amprms[11];
+   gr2e = amprms[7];
+   for( i = 0; i < 3; i++ ) {
+      abv[i] = amprms[i+8];
+      ehn[i] = amprms[i+4];
+   }
+
+/* Spherical to x,y,z. */
+   eraS2c( rm, dm, p );
+
+/* Light deflection (restrained within the Sun's disc) */
+   pde = eraPdp( p, ehn );
+   pdep1 = pde + 1.0;
+   w = gr2e / ( pdep1 > 1.0e-5 ? pdep1 : 1.0e-5 );
+   for( i = 0; i < 3; i++) {
+      p1[i] = p[i] + w * ( ehn[i] - pde * p[i] );
+   }
+
+/* Aberration. */
+   p1dv = eraPdp( p1, abv );
+   w = 1.0 + p1dv / ( ab1 + 1.0 );
+   for( i = 0; i < 3; i++ ) {
+      p2[i] = ( ( ab1 * p1[i] ) + ( w * abv[i] ) );
+   }
+
+/* Precession and nutation. */
+   eraRxp( (double(*)[3]) &amprms[12], p2, p3 );
+
+/* Geocentric apparent RA,dec. */
+   eraC2s( p3, ra, da );
+   *ra = eraAnp( *ra );
+}
diff --git a/ast/pal/palOne2One.c b/ast/pal/palOne2One.c
new file mode 100644
index 0000000..a8d8072
--- /dev/null
+++ b/ast/pal/palOne2One.c
@@ -0,0 +1,1482 @@
+/*
+*+
+*  Name:
+*     palOne2One
+
+*  Purpose:
+*     File containing simple PAL wrappers for SLA routines that are identical in SOFA
+
+*  Invocation:
+*     Matches SLA API
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Description:
+*     Some SOFA/ERFA routines are identical to their SLA counterparts. PAL provides
+*     direct counterparts to these although it is generally a better idea to
+*     use the SOFA/ERFA routine directly in new code.
+*
+*     The PAL routines with direct equivalents in SOFA/ERFA are:
+*     - palCldj
+*     - palDbear
+*     - palDaf2r
+*     - palDav2m
+*     - palDcc2s
+*     - palDcs2c
+*     - palDd2tf
+*     - palDimxv
+*     - palDm2av
+*     - palDjcl
+*     - palDmxm
+*     - palDmxv
+*     - palDpav
+*     - palDr2af
+*     - palDr2tf
+*     - palDranrm
+*     - palDsep
+*     - palDsepv
+*     - palDtf2d
+*     - palDtf2r
+*     - palDvdv
+*     - palDvn
+*     - palDvxv
+*     - palEpb
+*     - palEpb2d
+*     - palEpj
+*     - palEpj2d
+*     - palEqeqx
+*     - palFk5hz
+*     - palGmst
+*     - palGmsta
+*     - palHfk5z
+*     - palRefcoq
+
+*  Authors:
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     DSB: David S Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     - Do not call these functions from other PAL functions. Always use
+*       the SOFA/ERFA routines directly in new code.
+*     - These are implemented as real functions rather than C preprocessor
+*       macros so there may be a performance penalty in using the PAL
+*       version instead of the SOFA/ERFA version.
+*     - Routines that take MJDs have SOFA/ERFA equivalents that have an explicit
+*       MJD offset included.
+*     - palEqeqx, palGmst and palGmsta use the IAU 2006 precession model.
+
+*  History:
+*     2012-02-10 (TIMJ):
+*        Initial version
+*        Adapted with permission from the Fortran SLALIB library.
+*     2012-03-23 (TIMJ):
+*        Update prologue.
+*     2012-05-09 (DSBJ):
+*        Move palDrange into a separate file.
+*     2014-07-15 (TIMJ):
+*        SOFA now has palRefcoq equivalent.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2014 Tim Jenness
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include "pal1sofa.h"
+
+/*
+*+
+*  Name:
+*     palCldj
+
+*  Purpose:
+*     Gregorian Calendar to Modified Julian Date
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palCldj( int iy, int im, int id, double *djm, int *j );
+
+*  Arguments:
+*     iy = int (Given)
+*        Year in Gregorian calendar
+*     im = int (Given)
+*        Month in Gregorian calendar
+*     id = int (Given)
+*        Day in Gregorian calendar
+*     djm = double * (Returned)
+*        Modified Julian Date (JD-2400000.5) for 0 hrs
+*     j = int * (Returned)
+*        status: 0 = OK, 1 = bad year (MJD not computed),
+*        2 = bad month (MJD not computed), 3 = bad day (MJD computed).
+
+*  Description:
+*     Gregorian calendar to Modified Julian Date.
+
+*  Notes:
+*     - Uses eraCal2jd(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palCldj ( int iy, int im, int id, double *djm, int *j ) {
+  double djm0;
+  *j = eraCal2jd( iy, im, id, &djm0, djm );
+}
+
+/*
+*+
+*  Name:
+*     palDbear
+
+*  Purpose:
+*     Bearing (position angle) of one point on a sphere relative to another
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     pa = palDbear( double a1, double b1, double a2, double b2 );
+
+*  Arguments:
+*     a1 = double (Given)
+*        Longitude of point A (e.g. RA) in radians.
+*     a2 = double (Given)
+*        Latitude of point A (e.g. Dec) in radians.
+*     b1 = double (Given)
+*        Longitude of point B in radians.
+*     b2 = double (Given)
+*        Latitude of point B in radians.
+
+*  Returned Value:
+*     The result is the bearing (position angle), in radians, of point
+*     A2,B2 as seen from point A1,B1.  It is in the range +/- pi.  If
+*     A2,B2 is due east of A1,B1 the bearing is +pi/2.  Zero is returned
+*     if the two points are coincident.
+
+*  Description:
+*     Bearing (position angle) of one point in a sphere relative to another.
+
+*  Notes:
+*     - Uses eraPas(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+double palDbear ( double a1, double b1, double a2, double b2 ) {
+  return eraPas( a1, b1, a2, b2 );
+}
+
+/*
+*+
+*  Name:
+*     palDaf2r
+
+*  Purpose:
+*     Convert degrees, arcminutes, arcseconds to radians
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDaf2r( int ideg, int iamin, double asec, double *rad, int *j );
+
+*  Arguments:
+*     ideg = int (Given)
+*        Degrees.
+*     iamin = int (Given)
+*        Arcminutes.
+*     iasec = double (Given)
+*        Arcseconds.
+*     rad = double * (Returned)
+*        Angle in radians.
+*     j = int * (Returned)
+*        Status: 0 = OK, 1 = "ideg" out of range 0-359,
+*                2 = "iamin" outside of range 0-59,
+*                2 = "asec" outside range 0-59.99999
+
+*  Description:
+*     Convert degrees, arcminutes, arcseconds to radians.
+
+*  Notes:
+*     - Uses eraAf2a(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+/* Arguments differ slightly. Assumes that the sign is always positive
+   and dealt with externally. */
+void palDaf2r ( int ideg, int iamin, double asec, double *rad, int *j ) {
+  *j = eraAf2a( ' ', ideg, iamin, asec, rad );
+}
+
+/*
+*+
+*  Name:
+*     palDav2m
+
+*  Purpose:
+*     Form the rotation matrix corresponding to a given axial vector.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDav2m( double axvec[3], double rmat[3][3] );
+
+*  Arguments:
+*     axvec = double [3] (Given)
+*       Axial vector (radians)
+*     rmat = double [3][3] (Returned)
+*       Rotation matrix.
+
+*  Description:
+*     A rotation matrix describes a rotation about some arbitrary axis,
+*     called the Euler axis.  The "axial vector" supplied to this routine
+*     has the same direction as the Euler axis, and its magnitude is the
+*     amount of rotation in radians.
+
+*  Notes:
+*     - Uses eraRv2m(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDav2m ( double axvec[3], double rmat[3][3] ) {
+  eraRv2m( axvec, rmat );
+}
+
+/*
+*+
+*  Name:
+*     palDcc2s
+
+*  Purpose:
+*     Cartesian to spherical coordinates
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDcc2s( double v[3], double *a, double *b );
+
+*  Arguments:
+*     v = double [3] (Given)
+*        x, y, z vector.
+*     a = double * (Returned)
+*        Spherical coordinate (radians)
+*     b = double * (Returned)
+*        Spherical coordinate (radians)
+
+*  Description:
+*     The spherical coordinates are longitude (+ve anticlockwise looking
+*     from the +ve latitude pole) and latitude.  The Cartesian coordinates
+*     are right handed, with the x axis at zero longitude and latitude, and
+*     the z axis at the +ve latitude pole.
+
+*  Notes:
+*     - Uses eraC2s(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDcc2s ( double v[3], double *a, double *b ) {
+  eraC2s( v, a, b );
+}
+
+/*
+*+
+*  Name:
+*     palDcs2c
+
+*  Purpose:
+*     Spherical coordinates to direction cosines
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDcs2c( double a, double b, double v[3] );
+
+*  Arguments:
+*     a = double (Given)
+*        Spherical coordinate in radians (ra, long etc).
+*     b = double (Given)
+*        Spherical coordinate in radians (dec, lat etc).
+*     v = double [3] (Returned)
+*        x, y, z vector
+
+*  Description:
+*     The spherical coordinates are longitude (+ve anticlockwise looking
+*     from the +ve latitude pole) and latitude.  The Cartesian coordinates
+*     are right handed, with the x axis at zero longitude and latitude, and
+*     the z axis at the +ve latitude pole.
+
+*  Notes:
+*     - Uses eraS2c(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDcs2c ( double a, double b, double v[3] ) {
+  eraS2c( a, b, v );
+}
+
+/*
+*+
+*  Name:
+*     palDd2tf
+
+*  Purpose:
+*     Convert an interval in days into hours, minutes, seconds
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDd2tf( int ndp, double days, char *sign, int ihmsf[4] );
+
+*  Arguments:
+*     ndp = int (Given)
+*        Number of decimal places of seconds
+*     days = double (Given)
+*        Interval in days
+*     sign = char * (Returned)
+*        '+' or '-' (single character, not string)
+*     ihmsf = int [4] (Returned)
+*        Hours, minutes, seconds, fraction
+
+*  Description:
+*     Convert and interval in days into hours, minutes, seconds.
+
+*  Notes:
+*     - Uses eraD2tf(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDd2tf ( int ndp, double days, char *sign, int ihmsf[4] ) {
+  eraD2tf( ndp, days, sign, ihmsf );
+}
+
+/*
+*+
+*  Name:
+*     palDimxv
+
+*  Purpose:
+*     Perform the 3-D backward unitary transformation
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDimxv( double dm[3][3], double va[3], double vb[3] );
+
+*  Arguments:
+*     dm = double [3][3] (Given)
+*        Matrix
+*     va = double [3] (Given)
+*        vector
+*     vb = double [3] (Returned)
+*        Result vector
+
+*  Description:
+*     Perform the 3-D backward unitary transformation.
+
+*  Notes:
+*     - Uses eraTrxp(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDimxv ( double dm[3][3], double va[3], double vb[3] ) {
+  eraTrxp( dm, va, vb );
+}
+
+/*
+*+
+*  Name:
+*     palDm2av
+
+*  Purpose:
+*     From a rotation matrix, determine the corresponding axial vector
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDm2av( double rmat[3][3], double axvec[3] );
+
+*  Arguments:
+*     rmat = double [3][3] (Given)
+*        Rotation matrix
+*     axvec = double [3] (Returned)
+*        Axial vector (radians)
+
+*  Description:
+*     A rotation matrix describes a rotation about some arbitrary axis,
+*     called the Euler axis.  The "axial vector" returned by this routine
+*     has the same direction as the Euler axis, and its magnitude is the
+*     amount of rotation in radians.  (The magnitude and direction can be
+*     separated by means of the routine palDvn.)
+
+*  Notes:
+*     - Uses eraRm2v(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDm2av ( double rmat[3][3], double axvec[3] ) {
+  eraRm2v( rmat, axvec );
+}
+
+/*
+*+
+*  Name:
+*     palDjcl
+
+*  Purpose:
+*     Modified Julian Date to Gregorian year, month, day and fraction of day
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDjcl( double djm, int *iy, int *im, int *id, double *fd, int *j );
+
+*  Arguments:
+*     djm = double (Given)
+*        modified Julian Date (JD-2400000.5)
+*     iy = int * (Returned)
+*        year
+*     im = int * (Returned)
+*        month
+*     id = int * (Returned)
+*        day
+*     fd = double * (Returned)
+*        Fraction of day.
+
+*  Description:
+*     Modified Julian Date to Gregorian year, month, day and fraction of day.
+
+*  Notes:
+*     - Uses eraJd2cal(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+/* Requires additional SLA MJD reference date */
+void palDjcl ( double djm, int *iy, int *im, int *id, double *fd, int *j ) {
+  *j = eraJd2cal( PAL__MJD0, djm, iy, im, id, fd );
+}
+
+/*
+*+
+*  Name:
+*     palDmxm
+
+*  Purpose:
+*     Product of two 3x3 matrices
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDmxm( double a[3][3], double b[3][3], double c[3][3] );
+
+*  Arguments:
+*     a = double [3][3] (Given)
+*        Matrix
+*     b = double [3][3] (Given)
+*        Matrix
+*     c = double [3][3] (Returned)
+*        Matrix result
+
+*  Description:
+*     Product of two 3x3 matrices.
+
+*  Notes:
+*     - Uses eraRxr(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDmxm ( double a[3][3], double b[3][3], double c[3][3] ) {
+  eraRxr( a, b, c );
+}
+
+/*
+*+
+*  Name:
+*     palDmxv
+
+*  Purpose:
+*     Performs the 3-D forward unitary transformation
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDmxv( double dm[3][3], double va[3], double vb[3] );
+
+*  Arguments:
+*     dm = double [3][3] (Given)
+*        matrix
+*     va = double [3] (Given)
+*        vector
+*     dp = double [3] (Returned)
+*        result vector
+
+*  Description:
+*     Performs the 3-D forward unitary transformation.
+
+*  Notes:
+*     - Uses eraRxp(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDmxv ( double dm[3][3], double va[3], double vb[3] ) {
+  eraRxp( dm, va, vb );
+}
+
+/*
+*+
+*  Name:
+*     palDpav
+
+*  Purpose:
+*     Position angle of one celestial direction with respect to another
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     pa = palDpav( double v1[3], double v2[3] );
+
+*  Arguments:
+*     v1 = double [3] (Given)
+*        direction cosines of one point.
+*     v2 = double [3] (Given)
+*        direction cosines of the other point.
+
+*  Returned Value:
+*     The result is the bearing (position angle), in radians, of point
+*     V2 with respect to point V1.  It is in the range +/- pi.  The
+*     sense is such that if V2 is a small distance east of V1, the
+*     bearing is about +pi/2.  Zero is returned if the two points
+*     are coincident.
+
+*  Description:
+*     Position angle of one celestial direction with respect to another.
+
+*  Notes:
+*     - The coordinate frames correspond to RA,Dec, Long,Lat etc.
+*     - Uses eraPap(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+double palDpav ( double v1[3], double v2[3] ) {
+  return eraPap( v1, v2 );
+}
+
+/*
+*+
+*  Name:
+*     palDr2af
+
+*  Purpose:
+*     Convert an angle in radians to degrees, arcminutes, arcseconds
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDr2af( int ndp, double angle, char *sign, int idmsf[4] );
+
+*  Arguments:
+*     ndp = int (Given)
+*        number of decimal places of arcseconds
+*     angle = double (Given)
+*        angle in radians
+*     sign = char * (Returned)
+*        '+' or '-' (single character)
+*     idmsf = int [4] (Returned)
+*        Degrees, arcminutes, arcseconds, fraction
+
+*  Description:
+*     Convert an angle in radians to degrees, arcminutes, arcseconds.
+
+*  Notes:
+*     - Uses eraA2af(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDr2af ( int ndp, double angle, char *sign, int idmsf[4] ) {
+  eraA2af( ndp, angle, sign, idmsf );
+}
+
+/*
+*+
+*  Name:
+*     palDr2tf
+
+*  Purpose:
+*     Convert an angle in radians to hours, minutes, seconds
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDr2tf ( int ndp, double angle, char *sign, int ihmsf[4] );
+
+*  Arguments:
+*     ndp = int (Given)
+*        number of decimal places of arcseconds
+*     angle = double (Given)
+*        angle in radians
+*     sign = char * (Returned)
+*        '+' or '-' (single character)
+*     idmsf = int [4] (Returned)
+*        Hours, minutes, seconds, fraction
+
+*  Description:
+*     Convert an angle in radians to hours, minutes, seconds.
+
+*  Notes:
+*     - Uses eraA2tf(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDr2tf( int ndp, double angle, char *sign, int ihmsf[4] ) {
+  eraA2tf( ndp, angle, sign, ihmsf );
+}
+
+/*
+*+
+*  Name:
+*     palDranrm
+
+*  Purpose:
+*     Normalize angle into range 0-2 pi
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     norm = palDranrm( double angle );
+
+*  Arguments:
+*     angle = double (Given)
+*        angle in radians
+
+*  Returned Value:
+*     Angle expressed in the range 0-2 pi
+
+*  Description:
+*     Normalize angle into range 0-2 pi.
+
+*  Notes:
+*     - Uses eraAnp(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+double palDranrm ( double angle ) {
+  return eraAnp( angle );
+}
+
+/*
+*+
+*  Name:
+*     palDsep
+
+*  Purpose:
+*     Angle between two points on a sphere
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     ang = palDsep( double a1, double b1, double a2, double b2 );
+
+*  Arguments:
+*     a1 = double (Given)
+*        Spherical coordinate of one point (radians)
+*     b1 = double (Given)
+*        Spherical coordinate of one point (radians)
+*     a2 = double (Given)
+*        Spherical coordinate of other point (radians)
+*     b2 = double (Given)
+*        Spherical coordinate of other point (radians)
+
+*  Returned Value:
+*     Angle, in radians, between the two points. Always positive.
+
+*  Description:
+*     Angle between two points on a sphere.
+
+*  Notes:
+*     - The spherical coordinates are [RA,Dec], [Long,Lat] etc, in radians.
+*     - Uses eraSeps(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+double palDsep ( double a1, double b1, double a2, double b2 ) {
+  return eraSeps( a1, b1, a2, b2 );
+}
+
+/*
+*+
+*  Name:
+*     palDsepv
+
+*  Purpose:
+*     Angle between two vectors
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     ang = palDsepv( double v1[3], double v2[3] );
+
+*  Arguments:
+*     v1 = double [3] (Given)
+*        First vector
+*     v2 = double [3] (Given)
+*        Second vector
+
+*  Returned Value:
+*     Angle, in radians, between the two points. Always positive.
+
+*  Description:
+*     Angle between two vectors.
+
+*  Notes:
+*     - Uses eraSepp(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+double palDsepv ( double v1[3], double v2[3] ) {
+  return eraSepp( v1, v2 );
+}
+
+/*
+*+
+*  Name:
+*     palDtf2d
+
+*  Purpose:
+*     Convert hours, minutes, seconds to days
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDtf2d( int ihour, int imin, double sec, double *days, int *j );
+
+*  Arguments:
+*     ihour = int (Given)
+*        Hours
+*     imin = int (Given)
+*        Minutes
+*     sec = double (Given)
+*        Seconds
+*     days = double * (Returned)
+*        Interval in days
+*     j = int * (Returned)
+*        status: 0 = ok, 1 = ihour outside range 0-23,
+*        2 = imin outside range 0-59, 3 = sec outside range 0-59.999...
+
+*  Description:
+*     Convert hours, minutes, seconds to days.
+
+*  Notes:
+*     - Uses eraTf2d(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+/* Assumes that the sign is always positive and is dealt with externally */
+void palDtf2d ( int ihour, int imin, double sec, double *days, int *j ) {
+  *j = eraTf2d( ' ', ihour, imin, sec, days );
+}
+
+/*
+*+
+*  Name:
+*     palDtf2r
+
+*  Purpose:
+*     Convert hours, minutes, seconds to radians
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDtf2r( int ihour, int imin, double sec, double *rad, int *j );
+
+*  Arguments:
+*     ihour = int (Given)
+*        Hours
+*     imin = int (Given)
+*        Minutes
+*     sec = double (Given)
+*        Seconds
+*     days = double * (Returned)
+*        Angle in radians
+*     j = int * (Returned)
+*        status: 0 = ok, 1 = ihour outside range 0-23,
+*        2 = imin outside range 0-59, 3 = sec outside range 0-59.999...
+
+*  Description:
+*     Convert hours, minutes, seconds to radians.
+
+*  Notes:
+*     - Uses eraTf2a(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+/* Assumes that the sign is dealt with outside this routine */
+void palDtf2r ( int ihour, int imin, double sec, double *rad, int *j ) {
+  *j = eraTf2a( ' ', ihour, imin, sec, rad );
+}
+
+/*
+*+
+*  Name:
+*     palDvdv
+
+*  Purpose:
+*     Scalar product of two 3-vectors
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     prod = palDvdv ( double va[3], double vb[3] );
+
+*  Arguments:
+*     va = double [3] (Given)
+*        First vector
+*     vb = double [3] (Given)
+*        Second vector
+
+*  Returned Value:
+*     Scalar product va.vb
+
+*  Description:
+*     Scalar product of two 3-vectors.
+
+*  Notes:
+*     - Uses eraPdp(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+double palDvdv ( double va[3], double vb[3] ) {
+  return eraPdp( va, vb );
+}
+
+/*
+*+
+*  Name:
+*     palDvn
+
+*  Purpose:
+*     Normalizes a 3-vector also giving the modulus
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDvn( double v[3], double uv[3], double *vm );
+
+*  Arguments:
+*     v = double [3] (Given)
+*        vector
+*     uv = double [3] (Returned)
+*        unit vector in direction of "v"
+*     vm = double * (Returned)
+*        modulus of "v"
+
+*  Description:
+*     Normalizes a 3-vector also giving the modulus.
+
+*  Notes:
+*     - Uses eraPn(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+/* Note that the arguments are flipped */
+void palDvn ( double v[3], double uv[3], double *vm ) {
+  eraPn( v, vm, uv );
+}
+
+/*
+*+
+*  Name:
+*     palDvxv
+
+*  Purpose:
+*     Vector product of two 3-vectors
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palDvxv( double va[3], double vb[3], double vc[3] );
+
+*  Arguments:
+*     va = double [3] (Given)
+*        First vector
+*     vb = double [3] (Given)
+*        Second vector
+*     vc = double [3] (Returned)
+*        Result vector
+
+*  Description:
+*     Vector product of two 3-vectors.
+
+*  Notes:
+*     - Uses eraPxp(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palDvxv ( double va[3], double vb[3], double vc[3] ) {
+  eraPxp( va, vb, vc );
+}
+
+/*
+*+
+*  Name:
+*     palEpb
+
+*  Purpose:
+*     Conversion of modified Julian Data to Besselian Epoch
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     epb = palEpb ( double date );
+
+*  Arguments:
+*     date = double (Given)
+*        Modified Julian Date (JD - 2400000.5)
+
+*  Returned Value:
+*      Besselian epoch.
+
+*  Description:
+*     Conversion of modified Julian Data to Besselian Epoch.
+
+*  Notes:
+*     - Uses eraEpb(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+/* Requires additional SLA MJD reference date */
+double palEpb ( double date ) {
+  return eraEpb( PAL__MJD0, date );
+}
+
+/*
+*+
+*  Name:
+*     palEpb2d
+
+*  Purpose:
+*     Conversion of Besselian Epoch to Modified Julian Date
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     mjd = palEpb2d ( double epb );
+
+*  Arguments:
+*     epb = double (Given)
+*        Besselian Epoch
+
+*  Returned Value:
+*     Modified Julian Date (JD - 2400000.5)
+
+*  Description:
+*     Conversion of Besselian Epoch to Modified Julian Date.
+
+*  Notes:
+*     - Uses eraEpb2jd(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+
+double palEpb2d ( double epb ) {
+  double djm0, djm;
+  eraEpb2jd( epb, &djm0, &djm );
+  return djm;
+}
+
+/*
+*+
+*  Name:
+*     palEpj
+
+*  Purpose:
+*     Conversion of Modified Julian Date to Julian Epoch
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     epj = palEpj ( double date );
+
+*  Arguments:
+*     date = double (Given)
+*        Modified Julian Date (JD - 2400000.5)
+
+*  Returned Value:
+*     The Julian Epoch.
+
+*  Description:
+*     Conversion of Modified Julian Date to Julian Epoch.
+
+*  Notes:
+*     - Uses eraEpj(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+/* Requires additional SLA MJD reference date */
+double palEpj ( double date ) {
+  return eraEpj( PAL__MJD0, date );
+}
+
+/*
+*+
+*  Name:
+*     palEpj2d
+
+*  Purpose:
+*     Conversion of Julian Epoch to Modified Julian Date
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     mjd = palEpj2d ( double epj );
+
+*  Arguments:
+*     epj = double (Given)
+*        Julian Epoch.
+
+*  Returned Value:
+*     Modified Julian Date (JD - 2400000.5)
+
+*  Description:
+*     Conversion of Julian Epoch to Modified Julian Date.
+
+*  Notes:
+*     - Uses eraEpj2d(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+double palEpj2d ( double epj ) {
+  double djm0, djm;
+  eraEpj2jd( epj, &djm0, &djm );
+  return djm;
+}
+
+/*
+*+
+*  Name:
+*     palEqeqx
+
+*  Purpose:
+*     Equation of the equinoxes (IAU 2000/2006)
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palEqeqx( double date );
+
+*  Arguments:
+*     date = double (Given)
+*        TT as Modified Julian Date (JD-400000.5)
+
+*  Description:
+*     Equation of the equinoxes (IAU 2000/2006).
+
+*  Notes:
+*     - Uses eraEe06a(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+/* Requires additional SLA MJD reference date */
+double palEqeqx ( double date ) {
+  return eraEe06a( PAL__MJD0, date );
+}
+
+/*
+*+
+*  Name:
+*     palFk5hz
+
+*  Purpose:
+*     Transform an FK5 (J2000) star position into the frame of the
+*     Hipparcos catalogue.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palFk5hz ( double r5, double d5, double epoch,
+*                double *rh, double *dh );
+
+*  Arguments:
+*     r5 = double (Given)
+*        FK5 RA (radians), equinox J2000, epoch "epoch"
+*     d5 = double (Given)
+*        FK5 dec (radians), equinox J2000, epoch "epoch"
+*     epoch = double (Given)
+*        Julian epoch
+*     rh = double * (Returned)
+*        RA (radians)
+*     dh = double * (Returned)
+*        Dec (radians)
+
+*  Description:
+*     Transform an FK5 (J2000) star position into the frame of the
+*     Hipparcos catalogue.
+
+*  Notes:
+*     - Assumes zero Hipparcos proper motion.
+*     - Uses eraEpj2jd() and eraFk5hz.
+*       See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palFk5hz ( double r5, double d5, double epoch,
+                double *rh, double *dh ) {
+  /* Need to convert epoch to Julian date first */
+  double date1, date2;
+  eraEpj2jd( epoch, &date1, &date2 );
+  eraFk5hz( r5, d5, date1, date2, rh, dh );
+}
+
+/*
+*+
+*  Name:
+*     palGmst
+
+*  Purpose:
+*     Greenwich mean sidereal time (consistent with IAU 2006 precession).
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     mst = palGmst ( double ut1 );
+
+*  Arguments:
+*     ut1 = double (Given)
+*        Universal time (UT1) expressed as modified Julian Date (JD-2400000.5)
+
+*  Returned Value:
+*     Greenwich mean sidereal time
+
+*  Description:
+*     Greenwich mean sidereal time (consistent with IAU 2006 precession).
+
+*  Notes:
+*     - Uses eraGmst06(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+/* Note that SOFA/ERFA has more accurate time arguments
+   and we use the 2006 precession model */
+double palGmst ( double ut1 ) {
+  return eraGmst06( PAL__MJD0, ut1, PAL__MJD0, ut1 );
+}
+
+/*
+*+
+*  Name:
+*     palGmsta
+
+*  Purpose:
+*     Greenwich mean sidereal time (consistent with IAU 2006 precession).
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     mst = palGmsta ( double date, double ut1 );
+
+*  Arguments:
+*     date = double (Given)
+*        UT1 date (MJD: integer part of JD-2400000.5)
+*     ut1 = double (Given)
+*        UT1 time (fraction of a day)
+
+*  Returned Value:
+*     Greenwich mean sidereal time (in range 0 to 2 pi)
+
+*  Description:
+*     Greenwich mean sidereal time (consistent with IAU 2006 precession).
+
+*  Notes:
+*     - For best accuracy use eraGmst06() directly.
+*     - Uses eraGmst06(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+/* Slightly better but still not as accurate as SOFA/ERFA */
+
+double palGmsta( double date, double ut ) {
+  date += PAL__MJD0;
+  return eraGmst06( date, ut, date, ut );
+}
+
+/*
+*+
+*  Name:
+*     palHfk5z
+
+*  Purpose:
+*     Hipparcos star position to FK5 J2000
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palHfk5z( double rh, double dh, double epoch,
+*               double *r5, double *d5, double *dr5, double *dd5 );
+
+*  Arguments:
+*     rh = double (Given)
+*        Hipparcos RA (radians)
+*     dh = double (Given)
+*        Hipparcos Dec (radians)
+*     epoch = double (Given)
+*        Julian epoch (TDB)
+*     r5 = double * (Returned)
+*        RA (radians, FK5, equinox J2000, epoch "epoch")
+*     d5 = double * (Returned)
+*        Dec (radians, FK5, equinox J2000, epoch "epoch")
+
+*  Description:
+*     Transform a Hipparcos star position into FK5 J2000, assuming
+*     zero Hipparcos proper motion.
+
+*  Notes:
+*     - Uses eraEpj2jd and eraHfk5z(). See SOFA/ERFA documentation for details.
+
+*-
+*/
+
+void palHfk5z ( double rh, double dh, double epoch,
+                double *r5, double *d5, double *dr5, double *dd5 ) {
+  /* Need to convert epoch to Julian date first */
+  double date1, date2;
+  eraEpj2jd( epoch, &date1, &date2 );
+  eraHfk5z( rh, dh, date1, date2, r5, d5, dr5, dd5 );
+}
+
+/*
+*+
+*  Name:
+*     palRefcoq
+
+*  Purpose:
+*     Determine the constants A and B in the atmospheric refraction model
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palRefcoq( double tdk, double pmb, double rh, double wl,
+*                double *refa, double *refb );
+
+*  Arguments:
+*     tdk = double (Given)
+*        Ambient temperature at the observer (K)
+*     pmb = double (Given)
+*        Pressure at the observer (millibar)
+*     rh =  double (Given)
+*        Relative humidity at the observer (range 0-1)
+*     wl =  double (Given)
+*        Effective wavelength of the source (micrometre).
+*        Radio refraction is chosen by specifying wl > 100 micrometres.
+*     refa = double * (Returned)
+*        tan Z coefficient (radian)
+*     refb = double * (Returned)
+*        tan**3 Z coefficient (radian)
+
+*  Description:
+*     Determine the constants A and B in the atmospheric refraction
+*     model dZ = A tan Z + B tan**3 Z.  This is a fast alternative
+*     to the palRefco routine.
+*
+*     Z is the "observed" zenith distance (i.e. affected by refraction)
+*     and dZ is what to add to Z to give the "topocentric" (i.e. in vacuo)
+*     zenith distance.
+
+*  Notes:
+*     - Uses eraRefco(). See SOFA/ERFA documentation for details.
+*     - Note that the SOFA/ERFA routine uses different order of
+*       of arguments and uses deg C rather than K.
+
+*-
+*/
+
+void palRefcoq ( double tdk, double pmb, double rh, double wl,
+                 double *refa, double *refb ) {
+  /* Note that SLA (and therefore PAL) uses units of kelvin
+     but SOFA/ERFA uses deg C */
+  eraRefco( pmb, tdk - 273.15, rh, wl, refa, refb );
+}
diff --git a/ast/pal/palPrebn.c b/ast/pal/palPrebn.c
new file mode 100644
index 0000000..989ce59
--- /dev/null
+++ b/ast/pal/palPrebn.c
@@ -0,0 +1,98 @@
+/*
+*+
+*  Name:
+*     palPrebn
+
+*  Purpose:
+*     Generate the matrix of precession between two objects (old)
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palPrebn ( double bep0, double bep1, double rmatp[3][3] );
+
+*  Arguments:
+*     bep0 = double (Given)
+*        Beginning Besselian epoch.
+*     bep1 = double (Given)
+*        Ending Besselian epoch
+*     rmatp = double[3][3] (Returned)
+*        precession matrix in the sense V(BEP1) = RMATP * V(BEP0)
+
+*  Description:
+*     Generate the matrix of precession between two epochs,
+*     using the old, pre-IAU1976, Bessel-Newcomb model, using
+*     Kinoshita's formulation
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  See Also:
+*     Kinoshita, H. (1975) 'Formulas for precession', SAO Special
+*     Report No. 364, Smithsonian Institution Astrophysical
+*     Observatory, Cambridge, Massachusetts.
+
+*  History:
+*     2012-02-12(TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1996 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+
+void palPrebn ( double bep0, double bep1, double rmatp[3][3] ) {
+
+  double t,bigt, zeta, theta, z, tas2r, w;
+
+  /* Interval between basic epoch B1850.0 and beginning epoch in TC */
+  bigt = (bep0-1850)/100.;
+
+  /*  Interval over which precession required, in tropical centuries */
+  t = (bep1-bep0)/100.;
+
+  /* Euler angles */
+  tas2r = t * PAL__DAS2R;
+  w = 2303.5548 + ( 1.39720 + 0.000059 * bigt) * bigt;
+
+  zeta = ( w + ( 0.30242 - 0.000269 * bigt + 0.017996 * t ) * t ) * tas2r;
+  z = ( w + ( 1.09478 + 0.000387 * bigt + 0.018324 * t ) * t ) * tas2r;
+  theta = ( 2005.1125 + ( -0.85294 - 0.000365 * bigt ) * bigt +
+	    (-0.42647 - 0.000365 * bigt - 0.041802 * t ) * t ) * tas2r;
+
+  /*  Rotation matrix */
+  palDeuler("ZYZ", -zeta, theta, -z, rmatp);
+
+}
diff --git a/ast/pal/palPrec.c b/ast/pal/palPrec.c
new file mode 100644
index 0000000..678770d
--- /dev/null
+++ b/ast/pal/palPrec.c
@@ -0,0 +1,107 @@
+/*
+*+
+*  Name:
+*     palPrec
+
+*  Purpose:
+*     Form the matrix of precession between two epochs (IAU 2006)
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palPrec( double ep0, double ep1, double rmatp[3][3] )
+
+*  Arguments:
+*     ep0 = double (Given)
+*        Beginning epoch
+*     ep1 = double (Given)
+*        Ending epoch
+*     rmatp = double[3][3] (Returned)
+*        Precession matrix
+
+*  Description:
+*     The IAU 2006 precession matrix from ep0 to ep1 is found and
+*     returned. The matrix is in the sense  V(EP1)  =  RMATP * V(EP0).
+*     The epochs are TDB (loosely TT) Julian epochs.
+*
+*     Though the matrix method itself is rigorous, the precession
+*     angles are expressed through canonical polynomials which are
+*     valid only for a limited time span of a few hundred years around
+*     the current epoch.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-10 (DSB):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1996 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palPrec( double ep0, double ep1, double rmatp[3][3] ){
+
+/* Local Variables: */
+   double rmatq[3][3];
+   double ep0_days;
+   double ep1_days;
+
+/* Convert supplied dates to days since J2000 */
+   ep0_days = ( ep0 - 2000.0 )*ERFA_DJY;
+   ep1_days = ( ep1 - 2000.0 )*ERFA_DJY;
+
+/* If beginning epoch is J2000, just return the rotation matrix from
+   J2000 to EP1. */
+   if( ep0 == 2000.0 ) {
+      eraPmat06( ERFA_DJ00, ep1_days, rmatp );
+
+/* If end epoch is J2000, get the rotation matrix from J2000 to EP0 and
+   then transpose it to get the rotation matrix from EP0 to J2000. */
+   } else if( ep1 == 2000.0 ) {
+      eraPmat06( ERFA_DJ00, ep0_days, rmatp );
+      eraTr( rmatp, rmatp );
+
+/* Otherwise. get the two matrices used above and multiply them
+   together. */
+   } else {
+      eraPmat06( ERFA_DJ00, ep0_days, rmatp );
+      eraTr( rmatp, rmatp );
+      eraPmat06( ERFA_DJ00, ep1_days, rmatq );
+      eraRxr( rmatp, rmatq, rmatp );
+   }
+
+}
diff --git a/ast/pal/palPrenut.c b/ast/pal/palPrenut.c
new file mode 100644
index 0000000..04e36f3
--- /dev/null
+++ b/ast/pal/palPrenut.c
@@ -0,0 +1,111 @@
+/*
+*+
+*  Name:
+*     palPrenut
+
+*  Purpose:
+*     Form the matrix of bias-precession-nutation (IAU 2006/2000A)
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palPrenut( double epoch, double date, double rmatpn[3][3] )
+
+*  Arguments:
+*     epoch = double (Returned)
+*        Julian epoch for mean coordinates.
+*     date = double (Returned)
+*        Modified Julian Date (JD-2400000.5) for true coordinates.
+*     rmatpn = double[3][3] (Returned)
+*        combined NPB matrix
+
+*  Description:
+*     Form the matrix of bias-precession-nutation (IAU 2006/2000A).
+*     The epoch and date are TT (but TDB is usually close enough).
+*     The matrix is in the sense   v(true)  =  rmatpn * v(mean).
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-10 (PTW):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include "pal1sofa.h"
+
+void palPrenut ( double epoch, double date, double rmatpn[3][3] ){
+
+/* Local Variables: */
+   double bpa;
+   double bpia;
+   double bqa;
+   double chia;
+   double d1;
+   double d2;
+   double eps0;
+   double epsa;
+   double gam;
+   double oma;
+   double pa;
+   double phi;
+   double pia;
+   double psi;
+   double psia;
+   double r1[3][3];
+   double r2[3][3];
+   double thetaa;
+   double za;
+   double zetaa;
+
+/* Specified Julian epoch as a 2-part JD. */
+   eraEpj2jd( epoch, &d1, &d2 );
+
+/* P matrix, from specified epoch to J2000.0. */
+   eraP06e( d1, d2, &eps0, &psia, &oma, &bpa, &bqa, &pia, &bpia, &epsa,
+            &chia, &za, &zetaa, &thetaa, &pa, &gam, &phi, &psi );
+   eraIr( r1 );
+   eraRz( -chia, r1 );
+   eraRx( oma, r1 );
+   eraRz( psia, r1 );
+   eraRx( -eps0, r1 );
+
+/* NPB matrix, from J2000.0 to date. */
+   eraPnm06a( PAL__MJD0, date, r2 );
+
+/* NPB matrix, from specified epoch to date. */
+   eraRxr( r2, r1, rmatpn );
+}
diff --git a/ast/pal/palPvobs.c b/ast/pal/palPvobs.c
new file mode 100644
index 0000000..763d202
--- /dev/null
+++ b/ast/pal/palPvobs.c
@@ -0,0 +1,108 @@
+/*
+*+
+*  Name:
+*     palPvobs
+
+*  Purpose:
+*     Position and velocity of an observing station.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     palPvobs( double p, double h, double stl, double pv[6] )
+
+*  Arguments:
+*     p = double (Given)
+*        Latitude (geodetic, radians).
+*     h = double (Given)
+*        Height above reference spheroid (geodetic, metres).
+*     stl = double (Given)
+*        Local apparent sidereal time (radians).
+*     pv = double[ 6 ] (Returned)
+*        position/velocity 6-vector (AU, AU/s, true equator
+*                                    and equinox of date).
+
+*  Description:
+*     Returns the position and velocity of an observing station.
+
+*  Notes:
+*     - The WGS84 reference ellipsoid is used.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-16 (DSB):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "palmac.h"
+#include "palmac.h"
+#include "pal1sofa.h"
+
+void palPvobs( double p, double h, double stl, double pv[6] ){
+
+/* Local Variables: */
+   double xyz[3], z, r, s, c, v;
+
+/* Geodetic to geocentric conversion (WGS84 reference ellipsoid). */
+   eraGd2gc( ERFA_WGS84, 0.0, p, h, xyz );
+
+/* Convert from metres to AU */
+   r = xyz[ 0 ]/ERFA_DAU;
+   z = xyz[ 2 ]/ERFA_DAU;
+
+/* Functions of ST. */
+   s = sin( stl );
+   c = cos( stl );
+
+/* Speed. */
+   v = PAL__SR*r;
+
+/* Position. */
+   pv[ 0 ] = r*c;
+   pv[ 1 ] = r*s;
+   pv[ 2 ] = z;
+
+/* Velocity. */
+   pv[ 3 ] = -v*s;
+   pv[ 4 ] = v*c;
+   pv[ 5 ] = 0.0;
+
+}
+
+
diff --git a/ast/pal/palRvgalc.c b/ast/pal/palRvgalc.c
new file mode 100644
index 0000000..2f01576
--- /dev/null
+++ b/ast/pal/palRvgalc.c
@@ -0,0 +1,111 @@
+/*
+*+
+*  Name:
+*     palRvgalc
+
+*  Purpose:
+*     Velocity component in a given direction due to the rotation
+*     of the Galaxy.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     double palRvgalc( double r2000, double d2000 )
+
+*  Arguments:
+*     r2000 = double (Given)
+*        J2000.0 mean RA (radians)
+*     d2000 = double (Given)
+*        J2000.0 mean Dec (radians)
+
+*  Returned Value:
+*     Component of dynamical LSR motion in direction R2000,D2000 (km/s).
+
+*  Description:
+*     This function returns the Component of dynamical LSR motion in
+*     the direction of R2000,D2000. The result is +ve when the dynamical
+*     LSR is receding from the given point on the sky.
+*
+*  Notes:
+*     - The Local Standard of Rest used here is a point in the
+*     vicinity of the Sun which is in a circular orbit around
+*     the Galactic centre.  Sometimes called the "dynamical" LSR,
+*     it is not to be confused with a "kinematical" LSR, which
+*     is the mean standard of rest of star catalogues or stellar
+*     populations.
+*
+*  Reference:
+*     - The orbital speed of 220 km/s used here comes from Kerr &
+*     Lynden-Bell (1986), MNRAS, 221, p1023.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-16 (DSB):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+double palRvgalc( double r2000, double d2000 ){
+
+/* Local Variables: */
+   double vb[ 3 ];
+
+/*
+*  LSR velocity due to Galactic rotation
+*
+*  Speed = 220 km/s
+*  Apex  = L2,B2  90deg, 0deg
+*        = RA,Dec  21 12 01.1  +48 19 47  J2000.0
+*
+*  This is expressed in the form of a J2000.0 x,y,z vector:
+*
+*      VA(1) = X = -SPEED*COS(RA)*COS(DEC)
+*      VA(2) = Y = -SPEED*SIN(RA)*COS(DEC)
+*      VA(3) = Z = -SPEED*SIN(DEC)
+*/
+
+   double va[ 3 ] = { -108.70408, +97.86251, -164.33610 };
+
+/* Convert given J2000 RA,Dec to x,y,z. */
+   eraS2c( r2000, d2000, vb );
+
+/* Compute dot product with LSR motion vector. */
+   return eraPdp( va, vb );
+}
diff --git a/ast/pal/palRvlg.c b/ast/pal/palRvlg.c
new file mode 100644
index 0000000..255801e
--- /dev/null
+++ b/ast/pal/palRvlg.c
@@ -0,0 +1,106 @@
+/*
+*+
+*  Name:
+*     palRvlg
+
+*  Purpose:
+*     Velocity component in a given direction due to Galactic rotation
+*     and motion of the local group.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     double palRvlg( double r2000, double d2000 )
+
+*  Arguments:
+*     r2000 = double (Given)
+*        J2000.0 mean RA (radians)
+*     d2000 = double (Given)
+*        J2000.0 mean Dec (radians)
+
+*  Returned Value:
+*     Component of SOLAR motion in direction R2000,D2000 (km/s).
+
+*  Description:
+*     This function returns the velocity component in a given
+*     direction due to the combination of the rotation of the
+*     Galaxy and the motion of the Galaxy relative to the mean
+*     motion of the local group. The result is +ve when the Sun
+*     is receding from the given point on the sky.
+*
+*  Reference:
+*     - IAU Trans 1976, 168, p201.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-16 (DSB):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+double palRvlg( double r2000, double d2000 ){
+
+/* Local Variables: */
+   double vb[ 3 ];
+
+/*
+*
+*  Solar velocity due to Galactic rotation and translation
+*
+*  Speed = 300 km/s
+*
+*  Apex  = L2,B2  90deg, 0deg
+*        = RA,Dec  21 12 01.1  +48 19 47  J2000.0
+*
+*  This is expressed in the form of a J2000.0 x,y,z vector:
+*
+*      VA(1) = X = -SPEED*COS(RA)*COS(DEC)
+*      VA(2) = Y = -SPEED*SIN(RA)*COS(DEC)
+*      VA(3) = Z = -SPEED*SIN(DEC)
+*/
+
+   double va[ 3 ] = { -148.23284, +133.44888, -224.09467 };
+
+/* Convert given J2000 RA,Dec to x,y,z. */
+   eraS2c( r2000, d2000, vb );
+
+/* Compute dot product with Solar motion vector. */
+   return eraPdp( va, vb );
+}
diff --git a/ast/pal/palRvlsrd.c b/ast/pal/palRvlsrd.c
new file mode 100644
index 0000000..6302c4f
--- /dev/null
+++ b/ast/pal/palRvlsrd.c
@@ -0,0 +1,116 @@
+/*
+*+
+*  Name:
+*     palRvlsrd
+
+*  Purpose:
+*     Velocity component in a given direction due to the Sun's motion
+*     with respect to the dynamical Local Standard of Rest.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     double palRvlsrd( double r2000, double d2000 )
+
+*  Arguments:
+*     r2000 = double (Given)
+*        J2000.0 mean RA (radians)
+*     d2000 = double (Given)
+*        J2000.0 mean Dec (radians)
+
+*  Returned Value:
+*     Component of "peculiar" solar motion in direction R2000,D2000 (km/s).
+
+*  Description:
+*     This function returns the velocity component in a given direction
+*     due to the Sun's motion with respect to the dynamical Local Standard
+*     of Rest. The result is +ve when the Sun is receding from the given
+*     point on the sky.
+
+*  Notes:
+*     - The Local Standard of Rest used here is the "dynamical" LSR,
+*     a point in the vicinity of the Sun which is in a circular orbit
+*     around the Galactic centre.  The Sun's motion with respect to the
+*     dynamical LSR is called the "peculiar" solar motion.
+*     - There is another type of LSR, called a "kinematical" LSR.  A
+*     kinematical LSR is the mean standard of rest of specified star
+*     catalogues or stellar populations, and several slightly different
+*     kinematical LSRs are in use.  The Sun's motion with respect to an
+*     agreed kinematical LSR is known as the "standard" solar motion.
+*     To obtain a radial velocity correction with respect to an adopted
+*     kinematical LSR use the routine palRvlsrk.
+
+*  Reference:
+*     - Delhaye (1965), in "Stars and Stellar Systems", vol 5, p73.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-16 (DSB):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+double palRvlsrd( double r2000, double d2000 ){
+
+/* Local Variables: */
+   double vb[ 3 ];
+
+/*
+*  Peculiar solar motion from Delhaye 1965: in Galactic Cartesian
+*  coordinates (+9,+12,+7) km/s.  This corresponds to about 16.6 km/s
+*  towards Galactic coordinates L2 = 53 deg, B2 = +25 deg, or RA,Dec
+*  17 49 58.7 +28 07 04 J2000.
+*
+*  The solar motion is expressed here in the form of a J2000.0
+*  equatorial Cartesian vector:
+*
+*      VA(1) = X = -SPEED*COS(RA)*COS(DEC)
+*      VA(2) = Y = -SPEED*SIN(RA)*COS(DEC)
+*      VA(3) = Z = -SPEED*SIN(DEC)
+*/
+
+   double va[ 3 ] = { +0.63823, +14.58542, -7.80116 };
+
+/* Convert given J2000 RA,Dec to x,y,z. */
+   eraS2c( r2000, d2000, vb );
+
+/* Compute dot product with Solar motion vector. */
+   return eraPdp( va, vb );
+}
diff --git a/ast/pal/palRvlsrk.c b/ast/pal/palRvlsrk.c
new file mode 100644
index 0000000..968188a
--- /dev/null
+++ b/ast/pal/palRvlsrk.c
@@ -0,0 +1,116 @@
+/*
+*+
+*  Name:
+*     palRvlsrk
+
+*  Purpose:
+*     Velocity component in a given direction due to the Sun's motion
+*     with respect to an adopted kinematic Local Standard of Rest.
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     double palRvlsrk( double r2000, double d2000 )
+
+*  Arguments:
+*     r2000 = double (Given)
+*        J2000.0 mean RA (radians)
+*     d2000 = double (Given)
+*        J2000.0 mean Dec (radians)
+
+*  Returned Value:
+*     Component of "standard" solar motion in direction R2000,D2000 (km/s).
+
+*  Description:
+*     This function returns the velocity component in a given direction
+*     due to the Sun's motion with respect to an adopted kinematic
+*     Local Standard of Rest. The result is +ve when the Sun is receding
+*     from the given point on the sky.
+
+*  Notes:
+*     - The Local Standard of Rest used here is one of several
+*     "kinematical" LSRs in common use.  A kinematical LSR is the mean
+*     standard of rest of specified star catalogues or stellar
+*     populations.  The Sun's motion with respect to a kinematical LSR
+*     is known as the "standard" solar motion.
+*     - There is another sort of LSR, the "dynamical" LSR, which is a
+*     point in the vicinity of the Sun which is in a circular orbit
+*     around the Galactic centre.  The Sun's motion with respect to
+*     the dynamical LSR is called the "peculiar" solar motion.  To
+*     obtain a radial velocity correction with respect to the
+*     dynamical LSR use the routine palRvlsrd.
+
+*  Reference:
+*     - Delhaye (1965), in "Stars and Stellar Systems", vol 5, p73.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  History:
+*     2012-02-16 (DSB):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+double palRvlsrk( double r2000, double d2000 ){
+
+/* Local Variables: */
+   double vb[ 3 ];
+
+/*
+*  Standard solar motion (from Methods of Experimental Physics, ed Meeks,
+*  vol 12, part C, sec 6.1.5.2, p281):
+*
+*  20 km/s towards RA 18h Dec +30d (1900).
+*
+*  The solar motion is expressed here in the form of a J2000.0
+*  equatorial Cartesian vector:
+*
+*      VA(1) = X = -SPEED*COS(RA)*COS(DEC)
+*      VA(2) = Y = -SPEED*SIN(RA)*COS(DEC)
+*      VA(3) = Z = -SPEED*SIN(DEC)
+*/
+
+   double va[ 3 ] = { -0.29000, +17.31726, -10.00141 };
+
+/* Convert given J2000 RA,Dec to x,y,z. */
+   eraS2c( r2000, d2000, vb );
+
+/* Compute dot product with Solar motion vector. */
+   return eraPdp( va, vb );
+}
diff --git a/ast/pal/palSubet.c b/ast/pal/palSubet.c
new file mode 100644
index 0000000..ada122a
--- /dev/null
+++ b/ast/pal/palSubet.c
@@ -0,0 +1,112 @@
+/*
+*+
+*  Name:
+*     palSubet
+
+*  Purpose:
+*     Remove the E-terms from a pre IAU 1976 catalogue RA,Dec
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palSubet ( double rc, double dc, double eq,
+*                     double *rm, double *dm );
+
+*  Arguments:
+*     rc = double (Given)
+*        RA with E-terms included (radians)
+*     dc = double (Given)
+*        Dec with E-terms included (radians)
+*     eq = double (Given)
+*        Besselian epoch of mean equator and equinox
+*     rm = double * (Returned)
+*        RA without E-terms (radians)
+*     dm = double * (Returned)
+*        Dec without E-terms (radians)
+
+*  Description:
+*     Remove the E-terms (elliptic component of annual aberration)
+*     from a pre IAU 1976 catalogue RA,Dec to give a mean place.
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*     Most star positions from pre-1984 optical catalogues (or
+*     derived from astrometry using such stars) embody the
+*     E-terms.  This routine converts such a position to a
+*     formal mean place (allowing, for example, comparison with a
+*     pulsar timing position).
+
+*  See Also:
+*     Explanatory Supplement to the Astronomical Ephemeris,
+*     section 2D, page 48.
+
+*  History:
+*     2012-02-12(TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palSubet ( double rc, double dc, double eq, double *rm, double *dm ) {
+  double a[3];   /* The E-terms */
+  double v[3];
+  double f;
+  int i;
+
+  /* Note the preference for IAU routines */
+
+  /* Retrieve the E-terms */
+  palEtrms( eq, a );
+
+  /* Spherical to Cartesian */
+  eraS2c( rc, dc, v );
+
+  /* Include the E-terms */
+  f = 1.0 + eraPdp( v, a );
+  for (i=0; i<3; i++) {
+    v[i] = f*v[i] - a[i];
+  }
+
+  /* Cartesian to spherical */
+  eraC2s( v, rm, dm );
+
+  /* Bring RA into conventional range */
+  *rm = eraAnp( *rm );
+
+}
diff --git a/ast/pal/palSupgal.c b/ast/pal/palSupgal.c
new file mode 100644
index 0000000..1f4aeb2
--- /dev/null
+++ b/ast/pal/palSupgal.c
@@ -0,0 +1,116 @@
+/*
+*+
+*  Name:
+*     palSupgal
+
+*  Purpose:
+*     Convert from supergalactic to galactic coordinates
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Library routine
+
+*  Invocation:
+*     void palSupgal ( double dsl, double dsb, double *dl, double *db );
+
+*  Arguments:
+*     dsl = double (Given)
+*       Supergalactic longitude.
+*     dsb = double (Given)
+*       Supergalactic latitude.
+*     dl = double * (Returned)
+*       Galactic longitude.
+*     db = double * (Returned)
+*       Galactic latitude.
+
+*  Description:
+*     Transformation from de Vaucouleurs supergalactic coordinates
+*     to IAU 1958 galactic coordinates
+
+*  Authors:
+*     PTW: Pat Wallace (STFC)
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  See Also:
+*     - de Vaucouleurs, de Vaucouleurs, & Corwin, Second Reference
+*       Catalogue of Bright Galaxies, U. Texas, page 8.
+*     - Systems & Applied Sciences Corp., Documentation for the
+*       machine-readable version of the above catalogue,
+*       Contract NAS 5-26490.
+*
+*     (These two references give different values for the galactic
+*     longitude of the supergalactic origin.  Both are wrong;  the
+*     correct value is L2=137.37.)
+
+*  History:
+*     2012-02-12(TIMJ):
+*        Initial version with documentation taken from Fortran SLA
+*        Adapted with permission from the Fortran SLALIB library.
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 1995 Rutherford Appleton Laboratory
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+#include "pal.h"
+#include "pal1sofa.h"
+
+void palSupgal ( double dsl, double dsb, double *dl, double *db ) {
+
+  double v1[3];
+  double v2[3];
+
+/*
+*  System of supergalactic coordinates:
+*
+*    SGL   SGB        L2     B2      (deg)
+*     -    +90      47.37  +6.32
+*     0     0         -      0
+*
+*  Galactic to supergalactic rotation matrix:
+*/
+  double rmat[3][3] = {
+    { -0.735742574804,+0.677261296414,+0.000000000000 },
+    { -0.074553778365,-0.080991471307,+0.993922590400 },
+    { +0.673145302109,+0.731271165817,+0.110081262225 }
+  };
+
+  /* Spherical to Cartesian */
+  eraS2c( dsl, dsb, v1 );
+
+  /* Supergalactic to galactic */
+  eraTrxp( rmat, v1, v2 );
+
+  /* Cartesian to spherical */
+  eraC2s( v2, dl, db );
+
+  /* Express in conventional ranges */
+  *dl = eraAnp( *dl );
+  *db = eraAnpm( *db );
+
+}
diff --git a/ast/pal/palmac.h b/ast/pal/palmac.h
new file mode 100644
index 0000000..207b843
--- /dev/null
+++ b/ast/pal/palmac.h
@@ -0,0 +1,136 @@
+#ifndef PALMACDEF
+#define PALMACDEF
+
+/*
+*+
+*  Name:
+*     palmac.h
+
+*  Purpose:
+*     Macros used by the PAL library
+
+*  Language:
+*     Starlink ANSI C
+
+*  Type of Module:
+*     Include file
+
+*  Description:
+*     A collection of useful macros provided and used by the PAL library
+
+*  Authors:
+*     TIMJ: Tim Jenness (JAC, Hawaii)
+*     DSB: David Berry (JAC, Hawaii)
+*     {enter_new_authors_here}
+
+*  Notes:
+*
+
+*  History:
+*     2012-02-08 (TIMJ):
+*        Initial version.
+*        Adapted with permission from the Fortran SLALIB library.
+*     2012-04-13 (DSB):
+*        Added PAL__DR2H and PAL__DR2S
+*     {enter_further_changes_here}
+
+*  Copyright:
+*     Copyright (C) 2012 Science and Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Bugs:
+*     {note_any_bugs_here}
+*-
+*/
+
+/* Pi */
+static const double PAL__DPI = 3.1415926535897932384626433832795028841971693993751;
+
+/* 2Pi */
+static const double PAL__D2PI = 6.2831853071795864769252867665590057683943387987502;
+
+/* pi/2:  90 degrees in radians */
+static const double PAL__DPIBY2 = 1.5707963267948966192313216916397514420985846996876;
+
+/* pi/180:  degrees to radians */
+static const double PAL__DD2R = 0.017453292519943295769236907684886127134428718885417;
+
+/* Radians to arcseconds */
+static const double PAL__DR2AS = 2.0626480624709635515647335733077861319665970087963e5;
+
+/* Arcseconds to radians */
+static const double PAL__DAS2R = 4.8481368110953599358991410235794797595635330237270e-6;
+
+/* Radians to degrees */
+static const double PAL__DR2D = 57.295779513082320876798154814105170332405472466564;
+
+/* Hours to radians */
+static const double PAL__DH2R = 0.26179938779914943653855361527329190701643078328126;
+
+/* Radians to hours */
+static const double PAL__DR2H = 3.8197186342054880584532103209403446888270314977709;
+
+/* Radians to seconds of time */
+static const double PAL__DR2S = 1.3750987083139757010431557155385240879777313391975e4;
+
+/* Seconds of time to radians */
+static const double PAL__DS2R = 7.272205216643039903848712e-5;
+
+/* Start of SLA modified Julian date epoch */
+static const double PAL__MJD0 = 2400000.5;
+
+/* Light time for 1 AU (sec) */
+static const double PAL__CR = 499.004782;
+
+/* Seconds per day */
+static const double PAL__SPD = 86400.0;
+
+/* Km per sec to AU per tropical century
+   = 86400 * 36524.2198782 / 149597870 */
+static const double PAL__VF = 21.095;
+
+/*  Radians per year to arcsec per century. This needs to be a macro since it
+    is an expression including other constants. */
+#define PAL__PMF (100.0*60.0*60.0*360.0/PAL__D2PI);
+
+/* Mean sidereal rate - the rotational angular velocity of Earth
+   in radians/sec from IERS Conventions (2003). */
+static const double PAL__SR = 7.2921150e-5;
+
+/*  Gaussian gravitational constant (exact) */
+static const double PAL__GCON = 0.01720209895;
+
+/* DINT(A) - truncate to nearest whole number towards zero (double) */
+#define DINT(A) ((A)<0.0?ceil(A):floor(A))
+
+/* DNINT(A) - round to nearest whole number (double) */
+#define DNINT(A) ((A)<0.0?ceil((A)-0.5):floor((A)+0.5))
+
+/* DMAX(A,B) - return maximum value - evaluates arguments multiple times */
+#define DMAX(A,B) ((A) > (B) ? (A) : (B) )
+
+/* DMIN(A,B) - return minimum value - evaluates arguments multiple times */
+#define DMIN(A,B) ((A) < (B) ? (A) : (B) )
+
+/* We actually prefer to use C99 copysign() but we define this here as a backup
+   but it will not detect -0.0 so is not useful for palDfltin. */
+/* DSIGN(A,B) - magnitude of A with sign of B (double) */
+#define DSIGN(A,B) ((B)<0.0?-fabs(A):fabs(A))
+
+#endif
diff --git a/ast/pal2ast.h b/ast/pal2ast.h
new file mode 100644
index 0000000..83bab9a
--- /dev/null
+++ b/ast/pal2ast.h
@@ -0,0 +1,134 @@
+#if !defined( PAL2AST_INCLUDED )  /* Include this file only once */
+#define PAL2AST_INCLUDED
+/*
+*  Name:
+*     pal2ast.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Defines new names for symbols exported by the PAL library.
+
+*  Invocation:
+*     #include "pal2ast.h"
+
+*  Description:
+*     This include file defines a new name for each public function
+*     defined by the pal library. The names defined by PAL itself are
+*     of the form "palXxx" (e.g. palPvobs) - this include file defines
+*     a macro that translates each such name to the form "astPalXxx"
+*     (e.g. astPalPvobs). This is done so that the names do not clash
+*     with any external PAL library with which the application is linked.
+*
+*     It should be included at the start of any AST source file that refers
+*     to PAL functions using the standard names (e.g. palPvobs).
+
+*  Copyright:
+*     Copyright (C) 2012 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     16-FEB-2012 (DSB):
+*        Original version.
+*/
+
+/* Rename all PAL functions */
+#define palAddet   astPalAddet
+#define palAmpqk   astPalAmpqk
+#define palCaldj   astPalCaldj
+#define palDat	   astPalDat
+#define palDe2h	   astPalDe2h
+#define palDeuler  astPalDeuler
+#define palDh2e	   astPalDh2e
+#define palDjcal   astPalDjcal
+#define palDmat	   astPalDmat
+#define palDs2tp   astPalDs2tp
+#define palDtp2s   astPalDtp2s
+#define palDtps2c  astPalDtps2c
+#define palDtt	   astPalDtt
+#define palEcmat   astPalEcmat
+#define palEqgal   astPalEqgal
+#define palEtrms   astPalEtrms
+#define palEvp	   astPalEvp
+#define palFk45z   astPalFk45z
+#define palFk524   astPalFk524
+#define palFk54z   astPalFk54z
+#define palGaleq   astPalGaleq
+#define palGalsup  astPalGalsup
+#define palMappa   astPalMappa
+#define palMapqkz  astPalMapqkz
+#define palPrebn   astPalPrebn
+#define palPrec	   astPalPrec
+#define palPrenut  astPalPrenut
+#define palPvobs   astPalPvobs
+#define palRvgalc  astPalRvgalc
+#define palRvlg	   astPalRvlg
+#define palRvlsrd  astPalRvlsrd
+#define palRvlsrk  astPalRvlsrk
+#define palSubet   astPalSubet
+#define palSupgal  astPalSupgal
+#define palCldj    astPalCldj
+#define palDaf2r   astPalDaf2r
+#define palDav2m   astPalDav2m
+#define palDbear   astPalDbear
+#define palDcc2s   astPalDcc2s
+#define palDcs2c   astPalDcs2c
+#define palDd2tf   astPalDd2tf
+#define palDimxv   astPalDimxv
+#define palDjcl	   astPalDjcl
+#define palDm2av   astPalDm2av
+#define palDmxm	   astPalDmxm
+#define palDmxv	   astPalDmxv
+#define palDpav	   astPalDpav
+#define palDrange  astPalDrange
+#define palDranrm  astPalDranrm
+#define palDsep	   astPalDsep
+#define palDsepv   astPalDsepv
+#define palDtf2d   astPalDtf2d
+#define palDtf2r   astPalDtf2r
+#define palDvdv	   astPalDvdv
+#define palDvn	   astPalDvn
+#define palDvxv	   astPalDvxv
+#define palEpb	   astPalEpb
+#define palEpb2d   astPalEpb2d
+#define palEpj	   astPalEpj
+#define palEpj2d   astPalEpj2d
+#define palEqeqx   astPalEqeqx
+#define palFk5hz   astPalFk5hz
+#define palGeoc	   astPalGeoc
+#define palGmst	   astPalGmst
+#define palHfk5z   astPalHfk5z
+
+/* Rename all PAL global variables */
+#define PAL__DPI   AST__PALDPI
+#define PAL__D2PI  AST__PALD2PI
+#define PAL__DD2R  AST__PALDD2R
+#define PAL__DR2AS AST__PALDR2AS
+#define PAL__DAS2R AST__PALDAS2R
+#define PAL__MJD0  AST__PALMJD0
+#define PAL__CR    AST__PALCR
+#define PAL__VF    AST__PALVF
+#define PAL__SR    AST__PALSR
+
+
+#endif
diff --git a/ast/palwrap.c b/ast/palwrap.c
new file mode 100644
index 0000000..ca9a38f
--- /dev/null
+++ b/ast/palwrap.c
@@ -0,0 +1,301 @@
+#if !defined( PAL_INCLUDED )  /* Include this file only once */
+#define PAL_INCLUDED
+/*
+*  Name:
+*     pal.c
+
+*  Purpose:
+*     A wrapper for all PAL functions used by AST.
+
+*  Description:
+*     This file is a wrapper that includes all the source files from
+*     the PAL and ERFA libraries.  This is done so that the function
+*     names can be changed from the usual "palXxx" and "eraXxx" forms to
+*     the private "astPalXxx" and "astIauXxx" forms. This renaming is
+*     performed via the macros defined in the pal2ast.h and erfa2ast.h
+*     include files. This is done in order to prevent clashes with any
+*     external PAL or ERFA libraries with which an application is linked.
+*
+*     This file expects to find the publicly distributed and unmodified
+*     PAL and ERFA source code in a pair of subdirectory called "pal" and
+*     "erfa" within the current directory. The files within these
+*     subdirectories do not need to be compiled - only this wrapper file
+*     needs to be compiled.
+
+*  Copyright:
+*     Copyright (C) 2011 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (JAC, Hawaii)
+
+*  History:
+*     16-FEB-2012 (DSB):
+*        Original version.
+*/
+
+/* Include configuration results in order to get any definition for the
+   EXTERNAL_PAL macro. This macro is set if the --with-external_pal
+   option was set when AST was configured. */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* If an external PAL library is being used, we leave this file empty. */
+#ifndef EXTERNAL_PAL
+
+/* Rename all PAL functions so that references to "palXxx" in the files
+   included below get translated to "astPalXxx". */
+#include "pal2ast.h"
+
+/* Rename all ERFA functions so that references to "eraXxx" in the files
+   included below get translated to "astEraXxx". */
+#include "erfa2ast.h"
+
+/* Include code from all PAL source files */
+#include "pal/palAddet.c"
+#include "pal/palAmpqk.c"
+#include "pal/palCaldj.c"
+#include "pal/palDat.c"
+#include "pal/palDe2h.c"
+#include "pal/palDeuler.c"
+#include "pal/palDh2e.c"
+#include "pal/palDjcal.c"
+#include "pal/palDmat.c"
+#include "pal/palDrange.c"
+#include "pal/palDs2tp.c"
+#include "pal/palDtp2s.c"
+#include "pal/palDtps2c.c"
+#include "pal/palDtt.c"
+#include "pal/palEcmat.c"
+#include "pal/palEqgal.c"
+#include "pal/palEtrms.c"
+#include "pal/palEvp.c"
+#include "pal/palFk45z.c"
+#include "pal/palFk524.c"
+#include "pal/palFk54z.c"
+#include "pal/palGaleq.c"
+#include "pal/palGalsup.c"
+#include "pal/palGeoc.c"
+#include "pal/palMappa.c"
+#include "pal/palMapqkz.c"
+#include "pal/palOne2One.c"
+#include "pal/palPrebn.c"
+#include "pal/palPrec.c"
+#include "pal/palPrenut.c"
+#include "pal/palPvobs.c"
+#include "pal/palRvgalc.c"
+#include "pal/palRvlg.c"
+#include "pal/palRvlsrd.c"
+#include "pal/palRvlsrk.c"
+#include "pal/palSubet.c"
+#include "pal/palSupgal.c"
+
+/* Include code from all ERFA source files */
+#include "erfa/a2af.c"
+#include "erfa/a2tf.c"
+#include "erfa/af2a.c"
+#include "erfa/anp.c"
+#include "erfa/anpm.c"
+#include "erfa/bi00.c"
+#include "erfa/bp00.c"
+#include "erfa/bp06.c"
+#include "erfa/bpn2xy.c"
+#include "erfa/c2i00a.c"
+#include "erfa/c2i00b.c"
+#include "erfa/c2i06a.c"
+#include "erfa/c2ibpn.c"
+#include "erfa/c2ixy.c"
+#include "erfa/c2ixys.c"
+#include "erfa/c2s.c"
+#include "erfa/c2t00a.c"
+#include "erfa/c2t00b.c"
+#include "erfa/c2t06a.c"
+#include "erfa/c2tcio.c"
+#include "erfa/c2teqx.c"
+#include "erfa/c2tpe.c"
+#include "erfa/c2txy.c"
+#include "erfa/cal2jd.c"
+#include "erfa/cp.c"
+#include "erfa/cpv.c"
+#include "erfa/cr.c"
+#include "erfa/d2dtf.c"
+#include "erfa/d2tf.c"
+#include "erfa/dat.c"
+#include "erfa/dtdb.c"
+#include "erfa/dtf2d.c"
+#include "erfa/ee00.c"
+#include "erfa/ee00a.c"
+#include "erfa/ee00b.c"
+#include "erfa/ee06a.c"
+#include "erfa/eect00.c"
+#include "erfa/eform.c"
+#include "erfa/eo06a.c"
+#include "erfa/eors.c"
+#include "erfa/epb.c"
+#include "erfa/epb2jd.c"
+#include "erfa/epj.c"
+#include "erfa/epj2jd.c"
+#include "erfa/epv00.c"
+#include "erfa/eqeq94.c"
+#include "erfa/era00.c"
+#include "erfa/fad03.c"
+#include "erfa/fae03.c"
+#include "erfa/faf03.c"
+#include "erfa/faju03.c"
+#include "erfa/fal03.c"
+#include "erfa/falp03.c"
+#include "erfa/fama03.c"
+#include "erfa/fame03.c"
+#include "erfa/fane03.c"
+#include "erfa/faom03.c"
+#include "erfa/fapa03.c"
+#include "erfa/fasa03.c"
+#include "erfa/faur03.c"
+#include "erfa/fave03.c"
+#include "erfa/fk52h.c"
+#include "erfa/fk5hip.c"
+#include "erfa/fk5hz.c"
+#include "erfa/fw2m.c"
+#include "erfa/fw2xy.c"
+#include "erfa/gc2gd.c"
+#include "erfa/gc2gde.c"
+#include "erfa/gd2gc.c"
+#include "erfa/gd2gce.c"
+#include "erfa/gmst00.c"
+#include "erfa/gmst06.c"
+#include "erfa/gmst82.c"
+#include "erfa/gst00a.c"
+#include "erfa/gst00b.c"
+#include "erfa/gst06.c"
+#include "erfa/gst06a.c"
+#include "erfa/gst94.c"
+#include "erfa/h2fk5.c"
+#include "erfa/hfk5z.c"
+#include "erfa/ir.c"
+#include "erfa/jd2cal.c"
+#include "erfa/jdcalf.c"
+#include "erfa/num00a.c"
+#include "erfa/num00b.c"
+#include "erfa/num06a.c"
+#include "erfa/numat.c"
+#include "erfa/nut00a.c"
+#include "erfa/nut00b.c"
+#include "erfa/nut06a.c"
+#include "erfa/nut80.c"
+#include "erfa/nutm80.c"
+#include "erfa/obl06.c"
+#include "erfa/obl80.c"
+#include "erfa/p06e.c"
+#include "erfa/p2pv.c"
+#include "erfa/p2s.c"
+#include "erfa/pap.c"
+#include "erfa/pas.c"
+#include "erfa/pb06.c"
+#include "erfa/pdp.c"
+#include "erfa/pfw06.c"
+#include "erfa/plan94.c"
+#include "erfa/pm.c"
+#include "erfa/pmat00.c"
+#include "erfa/pmat06.c"
+#include "erfa/pmat76.c"
+#include "erfa/pmp.c"
+#include "erfa/pn.c"
+#include "erfa/pn00.c"
+#include "erfa/pn00a.c"
+#include "erfa/pn00b.c"
+#include "erfa/pn06.c"
+#include "erfa/pn06a.c"
+#include "erfa/pnm00a.c"
+#include "erfa/pnm00b.c"
+#include "erfa/pnm06a.c"
+#include "erfa/pnm80.c"
+#include "erfa/pom00.c"
+#include "erfa/ppp.c"
+#include "erfa/ppsp.c"
+#include "erfa/pr00.c"
+#include "erfa/prec76.c"
+#include "erfa/pv2p.c"
+#include "erfa/pv2s.c"
+#include "erfa/pvdpv.c"
+#include "erfa/pvm.c"
+#include "erfa/pvmpv.c"
+#include "erfa/pvppv.c"
+#include "erfa/pvstar.c"
+#include "erfa/pvu.c"
+#include "erfa/pvup.c"
+#include "erfa/pvxpv.c"
+#include "erfa/pxp.c"
+#include "erfa/refco.c"
+#include "erfa/rm2v.c"
+#include "erfa/rv2m.c"
+#include "erfa/rx.c"
+#include "erfa/rxp.c"
+#include "erfa/rxpv.c"
+#include "erfa/rxr.c"
+#include "erfa/ry.c"
+#include "erfa/rz.c"
+#include "erfa/s00.c"
+#include "erfa/s00a.c"
+#include "erfa/s00b.c"
+#include "erfa/s06.c"
+#include "erfa/s06a.c"
+#include "erfa/s2c.c"
+#include "erfa/s2p.c"
+#include "erfa/s2pv.c"
+#include "erfa/s2xpv.c"
+#include "erfa/sepp.c"
+#include "erfa/seps.c"
+#include "erfa/sp00.c"
+#include "erfa/starpm.c"
+#include "erfa/starpv.c"
+#include "erfa/sxp.c"
+#include "erfa/sxpv.c"
+#include "erfa/taitt.c"
+#include "erfa/taiut1.c"
+#include "erfa/taiutc.c"
+#include "erfa/tcbtdb.c"
+#include "erfa/tcgtt.c"
+#include "erfa/tdbtcb.c"
+#include "erfa/tdbtt.c"
+#include "erfa/tf2a.c"
+#include "erfa/tf2d.c"
+#include "erfa/tr.c"
+#include "erfa/trxp.c"
+#include "erfa/trxpv.c"
+#include "erfa/tttai.c"
+#include "erfa/tttcg.c"
+#include "erfa/tttdb.c"
+#include "erfa/ttut1.c"
+#include "erfa/ut1tai.c"
+#include "erfa/ut1tt.c"
+#include "erfa/ut1utc.c"
+#include "erfa/utctai.c"
+#include "erfa/utcut1.c"
+#include "erfa/xy06.c"
+#include "erfa/xys00a.c"
+#include "erfa/xys00b.c"
+#include "erfa/xys06a.c"
+#include "erfa/zp.c"
+#include "erfa/zpv.c"
+#include "erfa/zr.c"
+
+#endif
+
+#endif
diff --git a/ast/pcdmap.c b/ast/pcdmap.c
new file mode 100644
index 0000000..b7723f1
--- /dev/null
+++ b/ast/pcdmap.c
@@ -0,0 +1,3218 @@
+/*
+*class++
+*  Name:
+*     PcdMap
+
+*  Purpose:
+*     Apply 2-dimensional pincushion/barrel distortion.
+
+*  Constructor Function:
+c     astPcdMap
+f     AST_PCDMAP
+
+*  Description:
+*     A PcdMap is a non-linear Mapping which transforms 2-dimensional
+*     positions to correct for the radial distortion introduced by some
+*     cameras and telescopes. This can take the form either of pincushion
+*     or barrel distortion, and is characterized by a single distortion
+*     coefficient.
+*
+*     A PcdMap is specified by giving this distortion coefficient and the
+*     coordinates of the centre of the radial distortion. The forward
+*     transformation of a PcdMap applies the distortion:
+*
+*        RD = R * ( 1 + C * R * R )
+*
+*     where R is the undistorted radial distance from the distortion
+*     centre (specified by attribute PcdCen), RD is the radial distance
+*     from the same centre in the presence of distortion, and C is the
+*     distortion coefficient (given by attribute Disco).
+*
+*     The inverse transformation of a PcdMap removes the distortion
+*     produced by the forward transformation. The expression used to derive
+*     R from RD is an approximate inverse of the expression above.
+
+*  Inheritance:
+*     The PcdMap class inherits from the Mapping class.
+
+*  Attributes:
+*     In addition to those attributes common to all Mappings, every
+*     PcdMap also has the following attributes:
+*
+*     - Disco: PcdMap pincushion/barrel distortion coefficient
+*     - PcdCen(axis): Centre coordinates of pincushion/barrel distortion
+
+*  Functions:
+c     The PcdMap class does not define any new functions beyond those
+f     The PcdMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David Berry (Starlink)
+
+*  History:
+*     18-MAY-1999 (DSB):
+*        Original version.
+*     25-OCT-1999 (DSB):
+*        Fixed memory leak in MapMerge.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitPcdMapVtab
+*        method.
+*     23-AUG-2006 (DSB):
+*        Override astEqual.
+*     23-APR-2015 (DSB):
+*        Improve MapMerge. If a PcdMap can merge with its next-but-one
+*        neighbour, then swap the PcdMap with its neighbour, so that
+*        it is then next its next-but-one neighbour, and then merge the
+*        two Mappings into a single Mapping. Previously, only the swap
+*        was performed - not the merger. And the swap was only performed
+*        if the intervening neighbour could not itself merge. This could
+*        result in an infinite simplification loop, which was detected by
+*        CmpMap and and aborted, resulting in no useful simplification.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS PcdMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory management facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "unitmap.h"             /* Unit mappings */
+#include "zoommap.h"             /* Zoom mappings */
+#include "permmap.h"             /* Axis permutations */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "pcdmap.h"              /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(PcdMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(PcdMap,Class_Init)
+#define class_vtab astGLOBAL(PcdMap,Class_Vtab)
+#define getattrib_buff astGLOBAL(PcdMap,GetAttrib_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstPcdMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstPcdMap *astPcdMapId_( double, const double [2], const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static double GetDisco( AstPcdMap *, int * );
+static double GetPcdCen( AstPcdMap *, int, int * );
+static int CanMerge( AstMapping *, AstMapping *, int, int, int * );
+static int CanSwap( AstMapping *, AstMapping *, int, int, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestDisco( AstPcdMap *, int * );
+static int TestPcdCen( AstPcdMap *, int, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearDisco( AstPcdMap *, int * );
+static void ClearPcdCen( AstPcdMap *, int, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void PcdPerm( AstMapping **, int *, int, int * );
+static void PcdZoom( AstMapping **, int *, int, int * );
+static void PermGet( AstPermMap *, int **, int **, double **, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetDisco( AstPcdMap *, double, int * );
+static void SetPcdCen( AstPcdMap *, int, double, int * );
+
+/* Function Macros */
+/* =============== */
+/* Macros which return the maximum and minimum of two values. */
+#define MAX(aa,bb) ((aa)>(bb)?(aa):(bb))
+#define MIN(aa,bb) ((aa)<(bb)?(aa):(bb))
+
+/* Macro to check for equality of floating point values. We cannot
+compare bad values directory because of the danger of floating point
+exceptions, so bad values are dealt with explicitly. */
+#define EQUAL(aa,bb) (((aa)==AST__BAD)?(((bb)==AST__BAD)?1:0):(((bb)==AST__BAD)?0:(fabs((aa)-(bb))<=1.0E5*MAX((fabs(aa)+fabs(bb))*DBL_EPSILON,DBL_MIN))))
+
+/*
+*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Implement a method to clear a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     MAKE_CLEAR(attr,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the PcdMap class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstPcdMap *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( AstPcdMap *this, int axis )
+*
+*     which implement a method for clearing a single value in a specified
+*     multi-valued attribute for an axis of a PcdMap.
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. PcdCen in "astClearPcdCen").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attr,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attr( AstPcdMap *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astClear" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Report an error if the object has been cloned (i.e. has a reference \
+   count that is greater than one). */ \
+   } else if( astGetRefCount( this ) > 1 ) { \
+      astError( AST__IMMUT, "astClear(%s): The " #attr "attribute of " \
+                "the supplied %s cannot be cleared because the %s has " \
+                "been cloned (programming error).", status, \
+                astGetClass(this), astGetClass(this), astGetClass(this) ); \
+\
+/* Assign the "clear" value. */ \
+   } else { \
+      this->component[ axis ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attr##_( AstPcdMap *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,PcdMap,Clear##attr))( this, axis, status ); \
+}
+
+
+/*
+*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Implement a method to get a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     MAKE_GET(attr,type,bad_value,assign,nval)
+
+*  Class Membership:
+*     Defined by the PcdMap class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( AstPcdMap *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        <Type> astGet<Attribute>_( AstPcdMap *this, int axis )
+*
+*     which implement a method for getting a single value from a specified
+*     multi-valued attribute for an axis of a PcdMap.
+
+*  Parameters:
+*     attr
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. PcdCen in "astGetPcdCen").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set, or if
+*        the function fails.
+*     assign
+*        An expression that evaluates to the value to be returned. This can
+*        use the string "axis" to represent the zero-based value index.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attr,type,bad_value,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type Get##attr( AstPcdMap *this, int axis, int *status ) { \
+   type result;                  /* Result to be returned */ \
+\
+/* Initialise */ \
+   result = (bad_value); \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astGet" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Assign the result value. */ \
+   } else { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */  \
+type astGet##attr##_( AstPcdMap *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,PcdMap,Get##attr))( this, axis, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Implement a method to set a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     MAKE_SET(attr,type,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the PcdMap class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstPcdMap *this, int axis, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( AstPcdMap *this, int axis, <Type> value )
+*
+*     which implement a method for setting a single value in a specified
+*     multi-valued attribute for a PcdMap.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. PcdCen in "astSetPcdCen").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+*      nval
+*         Specifies the number of values in the multi-valued attribute. The
+*         "axis" values supplied to the created function should be in the
+*         range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attr,type,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attr( AstPcdMap *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astSet" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Report an error if the object has been cloned (i.e. has a reference \
+   count that is greater than one). */ \
+   } else if( astGetRefCount( this ) > 1 ) { \
+      astError( AST__IMMUT, "astSet(%s): The " #attr "attribute of " \
+                "the supplied %s cannot be changed because the %s has " \
+                "been cloned (programming error).", status, \
+                astGetClass(this), astGetClass(this), astGetClass(this) ); \
+\
+/* Store the new value in the structure component. */ \
+   } else { \
+      this->component[ axis ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attr##_( AstPcdMap *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,PcdMap,Set##attr))( this, axis, value, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_TEST
+
+*  Purpose:
+*     Implement a method to test if a single value has been set in a
+*     multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     MAKE_TEST(attr,assign,nval)
+
+*  Class Membership:
+*     Defined by the PcdMap class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static int Test<Attribute>( AstPcdMap *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        int astTest<Attribute>_( AstPcdMap *this, int axis )
+*
+*     which implement a method for testing if a single value in a specified
+*     multi-valued attribute has been set for a class.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be tested, as it appears in the function
+*         name (e.g. PcdCen in "astTestPcdCen").
+*      assign
+*         An expression that evaluates to 0 or 1, to be used as the returned
+*         value. This can use the string "axis" to represent the zero-based
+*         index of the value within the attribute.
+*      nval
+*         Specifies the number of values in the multi-valued attribute. The
+*         "axis" values supplied to the created function should be in the
+*         range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_TEST(attr,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static int Test##attr( AstPcdMap *this, int axis, int *status ) { \
+   int result;                   /* Value to return */ \
+\
+/* Initialise */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astTest" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Assign the result value. */ \
+   } else { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */ \
+int astTest##attr##_( AstPcdMap *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,PcdMap,Test##attr))( this, axis, status ); \
+}
+
+/* Member functions. */
+/* ================= */
+static int CanMerge( AstMapping *map1, AstMapping *map2, int inv1, int inv2, int *status ){
+/*
+*
+*  Name:
+*     CanMerge
+
+*  Purpose:
+*     Checks if two Mappings can be merged.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     int CanMerge( AstMapping *map1, AstMapping *map2, int inv1, int inv2, int *status )
+
+*  Class Membership:
+*     PcdMap internal utility function.
+
+*  Description:
+*     This function checks the two supplied Mappings to see if they
+*     can be merged into a single Mapping. One of the pair must be a PcdMap.
+
+*  Parameters:
+*     map1
+*        A pointer to the first mapping.
+*     map2
+*        A pointer to the second mapping.
+*     inv1
+*        The invert flag to use with the first mapping.
+*     inv2
+*        The invert flag to use with the second mapping.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     1 if the Mappings can be merged, zero otherwise.
+
+*/
+
+   AstPcdMap  *pcd;          /* Pointer to PcdMap Mapping */
+   AstPcdMap  *pcd2;         /* Pointer to second PcdMap Mapping */
+   AstMapping *nopcd;        /* Pointer to non-PcdMap Mapping */
+   const char *class1;       /* Pointer to map1 class string */
+   const char *class2;       /* Pointer to map2 class string */
+   const char *nopcd_class;  /* Pointer to non-PcdMap class string */
+   int invert[ 2 ];          /* Original invert flags */
+   int ret;                  /* Returned flag */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise */
+   ret = 0;
+   pcd = NULL;
+   nopcd = NULL;
+
+/* Temporarily set the Invert attributes of both Mappings to the supplied
+   values. */
+   invert[ 0 ] = astGetInvert( map1 );
+   astSetInvert( map1, inv1 );
+
+   invert[ 1 ] = astGetInvert( map2 );
+   astSetInvert( map2, inv2 );
+
+/* Get the classes of the two mappings. */
+   class1 = astGetClass( map1 );
+   class2 = astGetClass( map2 );
+   if( astOK ){
+
+/* Get pointers to the PcdMap and the non-PcdMap Mapping. */
+      if( !strcmp( class1, "PcdMap" ) ){
+         pcd = (AstPcdMap * ) map1;
+         nopcd = map2;
+         nopcd_class = class2;
+      } else if( !strcmp( class2, "PcdMap" ) ){
+         nopcd = map1;
+         pcd = (AstPcdMap * ) map2;
+         nopcd_class = class1;
+      } else {
+         nopcd_class = "unusable";
+      }
+
+/* The Mappings can merge if the other Mapping is a UnitMap. */
+      if( !strcmp( nopcd_class, "UnitMap" ) ){
+         ret = 1;
+
+/* They can also merge if the other Mapping is also a PcdMap, and is the
+   invert of the other. */
+      } else if( !strcmp( nopcd_class, "PcdMap" ) ){
+         pcd2 = (AstPcdMap *) nopcd;
+
+/* Check the distortion coefficients are equal. */
+         if( EQUAL( astGetDisco( pcd ), astGetDisco( pcd2 ) ) ){
+
+/* Check the axis 0 centres are equal. */
+            if( EQUAL( astGetPcdCen( pcd, 0 ), astGetPcdCen( pcd2, 0 ) ) ){
+
+/* Check the axis 1 centres are equal. */
+               if( EQUAL( astGetPcdCen( pcd, 1 ), astGetPcdCen( pcd2, 1 ) ) ){
+
+/* Check the Invert flags are different. */
+                  if( astGetInvert( pcd ) != astGetInvert( pcd2 ) ){
+
+/* If the Mappings have passed all these tests, they can be merged. */
+                     ret = 1;
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Re-instate the original settings of the Invert attributes for the
+   supplied Mappings. */
+   astSetInvert( map1, invert[ 0 ] );
+   astSetInvert( map2, invert[ 1 ] );
+
+/* Return the answer. */
+   return astOK ? ret : 0;
+}
+
+static int CanSwap( AstMapping *map1, AstMapping *map2, int inv1, int inv2, int *status ){
+/*
+*  Name:
+*     CanSwap
+
+*  Purpose:
+*     Determine if two Mappings could be swapped.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     int CanSwap( AstMapping *map1, AstMapping *map2, int inv1, int inv2, int *status )
+
+*  Class Membership:
+*     PcdMap member function
+
+*  Description:
+*     This function returns a flag indicating if the pair of supplied
+*     Mappings could be replaced by an equivalent pair of Mappings from the
+*     same classes as the supplied pair, but in reversed order. Each pair
+*     of Mappings is considered to be compounded in series. The supplied
+*     Mappings are not changed in any way.
+
+*  Parameters:
+*     map1
+*        The Mapping to be applied first.
+*     map2
+*        The Mapping to be applied second.
+*     inv1
+*        The invert flag to use with map1. A value of zero causes the forward
+*        mapping to be used, and a non-zero value causes the inverse
+*        mapping to be used.
+*     inv2
+*        The invert flag to use with map2.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     1 if the Mappings could be swapped, 0 otherwise.
+
+*  Notes:
+*     -  One of the supplied pair of Mappings must be a PcdMap.
+*     -  A value of 0 is returned if the two Mappings could be merged into
+*     a single Mapping.
+*     -  A value of 0 is returned if an error has already occurred, or if
+*     this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *nopcd;        /* Pointer to non-PcdMap Mapping */
+   const char *class1;       /* Pointer to map1 class string */
+   const char *class2;       /* Pointer to map2 class string */
+   const char *nopcd_class;  /* Pointer to non-PcdMap class string */
+   double *consts;           /* Pointer to constants array */
+   int *inperm;              /* Pointer to input axis permutation array */
+   int *outperm;             /* Pointer to output axis permutation array */
+   int invert[ 2 ];          /* Original invert flags */
+   int nin;                  /* No. of input coordinates for the PermMap */
+   int nout;                 /* No. of output coordinates for the PermMap */
+   int ret;                  /* Returned flag */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise */
+   ret = 0;
+
+/* Temporarily set the Invert attributes of both Mappings to the supplied
+   values. */
+   invert[ 0 ] = astGetInvert( map1 );
+   astSetInvert( map1, inv1 );
+
+   invert[ 1 ] = astGetInvert( map2 );
+   astSetInvert( map2, inv2 );
+
+/* Get the classes of the two mappings. */
+   class1 = astGetClass( map1 );
+   class2 = astGetClass( map2 );
+   if( astOK ){
+
+/* Get a pointer to the non-PcdMap Mapping. */
+      if( !strcmp( class1, "PcdMap" ) ){
+         nopcd = map2;
+         nopcd_class = class2;
+      } else {
+         nopcd = map1;
+         nopcd_class = class1;
+      }
+
+/* If the other Mapping is a ZoomMap, the Mappings can be swapped. */
+      if( !strcmp( nopcd_class, "ZoomMap" ) ){
+         ret = 1;
+
+/* If it is a PermMap, the Mappings can be swapped so long as the PermMap
+   simply swaps the two axes. */
+      } else if( !strcmp( nopcd_class, "PermMap" ) ){
+
+/* Get the number of input and output coordinates for the PermMap. */
+         nin = astGetNin( nopcd );
+         nout = astGetNout( nopcd );
+
+/* Must be 2-dimensional to swap. */
+         if( nin == 2 && nout == 2 ) {
+
+/* Get the axis permutation arrays and constants array for the PermMap. */
+            PermGet( (AstPermMap *) nopcd, &outperm, &inperm, &consts, status );
+            if( astOK ) {
+
+/* If the PermMap simply swaps the 2 axes (in both direction) we can
+   swap the two mappings. */
+               ret = ( outperm[0] == 1 && outperm[1] == 0 &&
+                       inperm[0] == 1 && inperm[1] == 0 );
+
+/* Free the axis permutation and constants arrays. */
+               outperm = (int *) astFree( (void *) outperm );
+               inperm = (int *) astFree( (void *) inperm );
+               consts = (double *) astFree( (void *) consts );
+            }
+         }
+      }
+   }
+
+/* Re-instate the original settings of the Invert attributes for the
+   supplied Mappings. */
+   astSetInvert( map1, invert[ 0 ] );
+   astSetInvert( map2, invert[ 1 ] );
+
+/* Return the answer. */
+   return astOK ? ret : 0;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a PcdMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PcdMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     PcdMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the PcdMap.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPcdMap *this;              /* Pointer to the PcdMap structure */
+   int axis;                     /* Axis number */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* No. characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PcdMap structure. */
+   this = (AstPcdMap *) this_object;
+
+/* Obtain the length of the "attrib" string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* PcdCen(axis). */
+/* ------------- */
+   if ( nc = 0,
+               ( 1 == astSscanf( attrib, "pcdcen(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearPcdCen( this, axis - 1 );
+
+/* PcdCen. */
+/* ------- */
+   } else if ( !strcmp( attrib, "pcdcen" ) ) {
+      astClearPcdCen( this, 0 );
+      astClearPcdCen( this, 1 );
+
+/* Disco. */
+/* ------ */
+   } else if ( !strcmp( attrib, "disco" ) ) {
+      astClearDisco( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two PcdMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     PcdMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two PcdMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a PcdMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the PcdMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPcdMap *that;
+   AstPcdMap *this;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two PcdMap structures. */
+   this = (AstPcdMap *) this_object;
+   that = (AstPcdMap *) that_object;
+
+/* Check the second object is a PcdMap. We know the first is a
+   PcdMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAPcdMap( that ) ) {
+
+/* Check the Invert flags for the two PcdMaps are equal. */
+      if( astGetInvert( this ) == astGetInvert( that ) ) {
+
+/* Check all the attributes are equal. */
+         if( astEQUAL( this->pcdcen[0], that->pcdcen[0] ) &&
+             astEQUAL( this->pcdcen[1], that->pcdcen[1] ) &&
+             astEQUAL( this->disco, that->disco ) ) {
+            result = 1;
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a PcdMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PcdMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a PcdMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the PcdMap.
+*     attrib
+*        Pointer to a null terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the PcdMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the PcdMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPcdMap *this;              /* Pointer to the PcdMap structure */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Double attribute value */
+   int axis;                     /* Axis number */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* No. characters read by astSscanf */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the PcdMap structure. */
+   this = (AstPcdMap *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* Disco. */
+/* ------ */
+   if ( !strcmp( attrib, "disco" ) ) {
+      dval = astGetDisco( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* PcdCen(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "pcdcen(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = astGetPcdCen( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* PcdCen. */
+/* ------- */
+   } else if ( !strcmp( attrib, "pcdcen" ) ) {
+      dval = astGetPcdCen( this, 0 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astInitPcdMapVtab_(  AstPcdMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPcdMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a PcdMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     void astInitPcdMapVtab( AstPcdMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     PcdMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the PcdMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAPcdMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->ClearDisco = ClearDisco;
+   vtab->SetDisco = SetDisco;
+   vtab->GetDisco = GetDisco;
+   vtab->TestDisco = TestDisco;
+   vtab->ClearPcdCen = ClearPcdCen;
+   vtab->SetPcdCen = SetPcdCen;
+   vtab->GetPcdCen = GetPcdCen;
+   vtab->TestPcdCen = TestPcdCen;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+   object->Equal = Equal;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   mapping->MapMerge = MapMerge;
+
+/* Declare the class dump function.*/
+   astSetDump( vtab, Dump, "PcdMap", "Apply pincushion distortion" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a PcdMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     PcdMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated PcdMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated PcdMap with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated PcdMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the PcdMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        PcdMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated PcdMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping **maplt;   /* New mappings list pointer */
+   AstMapping *map2;     /* First mapping to check */
+   AstMapping *newmap;   /* Pointer to replacement Mapping */
+   AstMapping *mc[2];    /* Copies of supplied Mappings to swap */
+   AstMapping *smc0;     /* Simplied Mapping */
+   AstMapping *smc1;     /* Simplied Mapping */
+   const char *class1;   /* Pointer to first Mapping class string */
+   const char *class2;   /* Pointer to second Mapping class string */
+   const char *nclass;   /* Pointer to neighbouring Mapping class */
+   int i1;               /* Index of first PcdMap to merge */
+   int i2;               /* Index of last PcdMap to merge */
+   int i;                /* Loop counter */
+   int ic[2];            /* Copies of supplied invert flags to swap */
+   int invert;           /* Should the inverted Mapping be used? */
+   int *invlt;           /* New invert flags list pointer */
+   int nmapt;            /* No. of Mappings in list */
+   int nstep1;           /* No. of Mappings backwards to next mergable Mapping */
+   int nstep2;           /* No. of Mappings forward to next mergable Mapping */
+   int result;           /* Result value to return */
+   int swaphi;           /* Can PcdMap be swapped with higher neighbour? */
+   int swaplo;           /* Can PcdMap be swapped with lower neighbour? */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   i1 = 0;
+   i2 = 0;
+
+/* First of all, see if the PcdMap can be replaced by a simpler Mapping,
+   without reference to the neighbouring Mappings in the list.           */
+/* ======================================================================*/
+/* If the distortion coefficient in the PcdMap is zero, the PcdMap can be
+   replaced by a UnitMap. */
+   if( EQUAL( astGetDisco( (AstPcdMap *) ( *map_list )[ where ] ), 0.0 ) ){
+
+/* Annul the PcdMap pointer in the list and replace it with a UnitMap
+   pointer, and indicate that the forward transformation of the returned
+   UnitMap should be used. */
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = (AstMapping *) astUnitMap( 2, "", status );
+      ( *invert_list )[ where ] = 0;
+
+/* Return the index of the first modified element. */
+      result = where;
+
+/* If the PcdMap itself could not be simplified, see if it can be merged
+   with the Mappings on either side of it in the list. */
+   } else {
+
+/* Store the classes of the neighbouring Mappings in the list. */
+       class1 = ( where > 0 ) ? astGetClass( ( *map_list )[ where - 1 ] ) : NULL;
+       class2 = ( where < *nmap - 1 ) ? astGetClass( ( *map_list )[ where + 1 ] ) : NULL;
+
+/* In series. */
+/* ========== */
+      if ( series ) {
+
+/* We first look to see if the PcdMap can be merged with one of its
+   neighbours, resulting in a reduction of one in the number of Mappings
+   in the list. A PcdMap can only merge directly with its own inverse, or
+   a UnitMap. */
+         if( class1 && CanMerge(  ( *map_list )[ where - 1 ],
+                                  ( *map_list )[ where ],
+                                  ( *invert_list )[ where - 1 ],
+                                  ( *invert_list )[ where ], status ) ){
+            nclass = class1;
+            i1 = where - 1;
+            i2 = where;
+
+         } else if( class2 && CanMerge(  ( *map_list )[ where ],
+                                  ( *map_list )[ where + 1 ],
+                                  ( *invert_list )[ where ],
+                                  ( *invert_list )[ where + 1 ], status ) ){
+            nclass = class2;
+            i1 = where;
+            i2 = where + 1;
+
+         } else {
+            nclass = NULL;
+         }
+
+/* If the PcdMap can merge with one of its neighbours, create the merged
+   Mapping. */
+         if( nclass ){
+
+/* If the neighbour is a PcdMap, it must be the inverse of the nominated
+   Mapping, and so they merge to form a UnitMap. */
+            if( !strcmp( nclass, "PcdMap" ) ){
+               newmap = (AstMapping *) astUnitMap( 2, "", status );
+               invert = 0;
+
+/* If the neighbour is a UnitMap, they merge to form a clone of the
+   nominated PcdMap. */
+            } else {
+               newmap = (AstMapping *) astClone( ( *map_list )[ where ] );
+               invert = ( *invert_list )[ where ];
+            }
+
+/* If succesfull... */
+            if( astOK ){
+
+/* Annul the first of the two Mappings, and replace it with the merged
+   Mapping. Also set the invert flag. */
+               (void) astAnnul( ( *map_list )[ i1 ] );
+               ( *map_list )[ i1 ] = newmap;
+               ( *invert_list )[ i1 ] = invert;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+               (void) astAnnul( ( *map_list )[ i2 ] );
+               for ( i = i2 + 1; i < *nmap; i++ ) {
+                  ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+                  ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+               }
+
+/* Clear the vacated element at the end. */
+               ( *map_list )[ *nmap - 1 ] = NULL;
+               ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+               ( *nmap )--;
+               result = i1;
+
+            }
+
+/* If the PcdMap could not merge directly with either of its neighbours,
+   we consider whether it would be worthwhile to swap the PcdMap with
+   either of its neighbours. This can only be done for certain classes
+   of Mapping (ZoomMaps & some PermMaps), and will usually require both
+   Mappings to be modified (unless they are commutative). The advantage of
+   swapping the order of the Mappings is that it may result in the PcdMap
+   being adjacent to a Mapping with which it can merge directly on the next
+   invocation of this function, thus reducing the number of Mappings
+   in the list. */
+         } else {
+
+/* Set a flag if we could swap the PcdMap with its higher neighbour. */
+            if( where + 1 < *nmap ){
+               swaphi = CanSwap(  ( *map_list )[ where ],
+                                  ( *map_list )[ where + 1 ],
+                                  ( *invert_list )[ where ],
+                                  ( *invert_list )[ where + 1 ], status );
+            } else {
+               swaphi = 0;
+            }
+
+/* If so, step through each of the Mappings which follow the PcdMap,
+   looking for a Mapping with which the PcdMap could merge directly. Stop
+   when such a Mapping is found, or if a Mapping is found with which the
+   PcdMap could definitely not swap. Note the number of Mappings which
+   separate the PcdMap from the Mapping with which it could merge (if
+   any). */
+            nstep2 = -1;
+            if( swaphi ){
+               for( i2 = where + 1; i2 < *nmap; i2++ ){
+
+/* See if we may be able to merge with this Mapping. If so, note the number
+   of steps between the two Mappings and leave the loop. */
+                  nclass = astGetClass( ( *map_list )[ i2 ] );
+
+                  if( !strcmp( nclass, "UnitMap" ) ||
+                      !strcmp( nclass, "PcdMap" ) ){
+                     nstep2 = i2 - where - 1;
+                     break;
+                  }
+
+/* If there is no chance that we can swap with this Mapping, leave the loop
+   with -1 for the number of steps to indicate that no merging is possible.
+   PcdMaps can swap with ZoomMaps and some PermMaps. */
+                  if( strcmp( nclass, "ZoomMap" ) &&
+                      strcmp( nclass, "PermMap" ) ) {
+                     break;
+                  }
+
+               }
+
+            }
+
+/* Do the same working forward from the PcdMap towards the start of the map
+   list. */
+            if( where > 0 ){
+               swaplo = CanSwap(  ( *map_list )[ where - 1 ],
+                                  ( *map_list )[ where ],
+                                  ( *invert_list )[ where - 1 ],
+                                  ( *invert_list )[ where ], status );
+            } else {
+               swaplo = 0;
+            }
+
+            nstep1 = -1;
+            if( swaplo ){
+               for( i1 = where - 1; i1 >= 0; i1-- ){
+
+                  nclass = astGetClass( ( *map_list )[ i1 ] );
+
+                  if( !strcmp( nclass, "UnitMap" ) ||
+                      !strcmp( nclass, "PcdMap" ) ){
+                     nstep1 = where - 1 - i1;
+                     break;
+                  }
+
+                  if( strcmp( nclass, "ZoomMap" ) &&
+                      strcmp( nclass, "PermMap" ) ) {
+                     break;
+                  }
+
+               }
+
+            }
+
+/* Choose which neighbour to swap with so that the PcdMap moves towards the
+   nearest Mapping with which it can merge. */
+            if( nstep1 != -1 && ( nstep2 == -1 || nstep2 > nstep1 ) ){
+               nclass = class1;
+               i1 = where - 1;
+               i2 = where;
+            } else if( nstep2 != -1 ){
+               nclass = class2;
+               i1 = where;
+               i2 = where + 1;
+            } else {
+               nclass = NULL;
+            }
+
+/* If there is a target Mapping in the list with which the PcdMap could
+   merge, replace the supplied Mappings with swapped Mappings to bring a
+   PcdMap closer to the target Mapping. */
+            if( nclass ){
+
+/* Swap the Mappings. */
+               if( !strcmp( nclass, "ZoomMap" ) ){
+                  PcdZoom( (*map_list) + i1, (*invert_list) + i1, where - i1, status );
+
+               } else if( !strcmp( nclass, "PermMap" ) ){
+                  PcdPerm( (*map_list) + i1, (*invert_list) + i1, where - i1, status );
+               }
+
+/* And then merge them. */
+               if( where == i1 && where + 1 < *nmap ) {    /* Merging upwards */
+                  map2 = astClone( (*map_list)[ where + 1 ] );
+                  nmapt = *nmap - where - 1;
+                  maplt = *map_list + where + 1;
+                  invlt = *invert_list + where + 1;
+
+                  (void) astMapMerge( map2, 0, series, &nmapt, &maplt, &invlt );
+                  map2 = astAnnul( map2 );
+                  *nmap = where + 1 + nmapt;
+
+               } else if( where - 2 >= 0 ) {               /* Merging downwards */
+                  map2 = astClone( (*map_list)[ where - 2 ] );
+                  nmapt = *nmap - where + 2;
+                  maplt = *map_list + where - 2 ;
+                  invlt = *invert_list + where - 2;
+
+                  (void) astMapMerge( map2, 0, series, &nmapt, &maplt, &invlt );
+                  map2 = astAnnul( map2 );
+                  *nmap = where - 2 + nmapt;
+               }
+
+               result = i1;
+
+/* If there is no Mapping available for merging, it may still be
+   advantageous to swap with a neighbour because the swapped Mapping may
+   be simpler than the original Mappings. */
+            } else if( swaphi || swaplo ) {
+
+/* Try swapping with each possible neighbour in turn. */
+               for( i = 0; i < 2; i++ ) {
+
+/*  Set up the class and pointers for the mappings to be swapped, first
+    the lower neighbour, then the upper neighbour. */
+                  if( i == 0 && swaplo ){
+                     nclass = class1;
+                     i1 = where - 1;
+                     i2 = where;
+
+                  } else if( i == 1 && swaphi ){
+                     nclass = class2;
+                     i1 = where;
+                     i2 = where + 1;
+
+                  } else {
+                     nclass = NULL;
+                  }
+
+/* If we have a Mapping to swap with... */
+                  if( nclass ) {
+
+/* Take copies of the Mapping and Invert flag arrays so we do not change
+   the supplied values. */
+                     mc[ 0 ] = (AstMapping *) astCopy( ( (*map_list) + i1 )[0] );
+                     mc[ 1 ] = (AstMapping *) astCopy( ( (*map_list) + i1 )[1] );
+                     ic[ 0 ] = ( (*invert_list) + i1 )[0];
+                     ic[ 1 ] = ( (*invert_list) + i1 )[1];
+
+/* Swap these Mappings. */
+                     if( !strcmp( nclass, "ZoomMap" ) ){
+                        PcdZoom( mc, ic, where - i1, status );
+                     } else if( !strcmp( nclass, "PermMap" ) ){
+                        PcdPerm( mc, ic, where - i1, status );
+                     }
+
+/* If neither of the swapped Mappings can be simplified further, then there
+   is no point in swapping the Mappings, so just annul the map copies. */
+                     smc0 = astSimplify( mc[0] );
+                     smc1 = astSimplify( mc[1] );
+
+                     if( astGetClass( smc0 ) == astGetClass( mc[0] ) &&
+                         astGetClass( smc1 ) == astGetClass( mc[1] ) ) {
+
+                        mc[ 0 ] = (AstMapping *) astAnnul( mc[ 0 ] );
+                        mc[ 1 ] = (AstMapping *) astAnnul( mc[ 1 ] );
+
+/* If one or both of the swapped Mappings could be simplified, then annul
+   the supplied Mappings and return the swapped mappings, storing the index
+   of the first modified Mapping. */
+                     } else {
+                        (void ) astAnnul( ( (*map_list) + i1 )[0] );
+                        (void ) astAnnul( ( (*map_list) + i1 )[1] );
+
+                        ( (*map_list) + i1 )[0] = mc[ 0 ];
+                        ( (*map_list) + i1 )[1] = mc[ 1 ];
+
+                        ( (*invert_list) + i1 )[0] = ic[ 0 ];
+                        ( (*invert_list) + i1 )[1] = ic[ 1 ];
+
+                        result = i1;
+                        break;
+                     }
+
+/* Annul the simplied Mappings */
+                     smc0 = astAnnul( smc0 );
+                     smc1 = astAnnul( smc1 );
+
+                  }
+               }
+            }
+         }
+
+/* In parallel. */
+/* ============ */
+/* PcdMaps cannot combine in parallel with any other Mappings. */
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void PermGet( AstPermMap *map, int **outperm, int **inperm,
+                     double **consts, int *status ){
+/*
+*  Name:
+*     PermGet
+
+*  Purpose:
+*     Get the axis permutation and constants array for a PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     void PermGet( AstPermMap *map, int **outperm, int **inperm,
+*                   double **const, int *status )
+
+*  Class Membership:
+*     PcdMap member function
+
+*  Description:
+*     This function returns axis permutation and constants arrays which can
+*     be used to create a PermMap which is equivalent to the supplied PermMap.
+
+*  Parameters:
+*     map
+*        The PermMap.
+*     outperm
+*        An address at which to return a popinter to an array of ints
+*        holding the output axis permutation array. The array should be
+*        released using astFree when no longer needed.
+*     inperm
+*        An address at which to return a popinter to an array of ints
+*        holding the input axis permutation array. The array should be
+*        released using astFree when no longer needed.
+*     consts
+*        An address at which to return a popinter to an array of doubles
+*        holding the constants array. The array should be released using
+*        astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  NULL pointers are returned if an error has already occurred, or if
+*     this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;       /* PointSet holding input positions for PermMap */
+   AstPointSet *pset2;       /* PointSet holding output positions for PermMap */
+   double **ptr1;            /* Pointer to pset1 data */
+   double **ptr2;            /* Pointer to pset2 data */
+   double *cnst;             /* Pointer to constants array */
+   double cn;                /* Potential new constant value */
+   double ip;                /* Potential output axis index */
+   double op;                /* Potential input axis index */
+   int *inprm;               /* Pointer to input axis permutation array */
+   int *outprm;              /* Pointer to output axis permutation array */
+   int i;                    /* Axis count */
+   int nc;                   /* Number of constants stored so far */
+   int nin;                  /* No. of input coordinates for the PermMap */
+   int nout;                 /* No. of output coordinates for the PermMap */
+
+/* Initialise. */
+   if( outperm ) *outperm = NULL;
+   if( inperm ) *inperm = NULL;
+   if( consts ) *consts = NULL;
+
+/* Check the global error status and the supplied pointers. */
+   if ( !astOK || !outperm || !inperm || !consts ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   nc = 0;
+
+/* Get the number of input and output axes for the supplied PermMap. */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+
+/* Allocate the memory for the returned arrays. */
+   outprm = (int *) astMalloc( sizeof( int )* (size_t) nout );
+   inprm = (int *) astMalloc( sizeof( int )* (size_t) nin );
+   cnst = (double *) astMalloc( sizeof( double )* (size_t) ( nout + nin ) );
+
+/* Returned the pointers to these arrays.*/
+   *outperm = outprm;
+   *inperm = inprm;
+   *consts = cnst;
+
+/* Create two PointSets, each holding two points, which can be used for
+   input and output positions with the PermMap. */
+   pset1 = astPointSet( 2, nin, "", status );
+   pset2 = astPointSet( 2, nout, "", status );
+
+/* Set up the two input positions to be [0,1,2...] and [-1,-1,-1,...]. The
+   first position is used to enumerate the axes, and the second is used to
+   check for constant axis values. */
+   ptr1 = astGetPoints( pset1 );
+   if( astOK ){
+      for( i = 0; i < nin; i++ ){
+         ptr1[ i ][ 0 ] = ( double ) i;
+         ptr1[ i ][ 1 ] = -1.0;
+      }
+   }
+
+/* Use the PermMap to transform these positions in the forward direction. */
+   (void) astTransform( map, pset1, 1, pset2 );
+
+/* Look at the mapped positions to determine the output axis permutation
+   array. */
+   ptr2 = astGetPoints( pset2 );
+   if( astOK ){
+
+/* No constant axis valeus found yet. */
+      nc = 0;
+
+/* Do each output axis. */
+      for( i = 0; i < nout; i++ ){
+
+/* If the output axis value is copied from an input axis value, the index
+   of the appropriate input axis will be in the mapped first position. */
+         op = ptr2[ i ][ 0 ];
+
+/* If the output axis value is assigned a constant value, the result of
+   mapping the two different input axis values will be the same. */
+         cn = ptr2[ i ][ 1 ];
+         if( op == cn ) {
+
+/* We have found another constant. Store it in the constants array, and
+   store the index of the constant in the output axis permutation array. */
+            cnst[ nc ] = cn;
+            outprm[ i ] = -( nc + 1 );
+            nc++;
+
+/* If the output axis values are different, then the output axis value
+   must be copied from the input axis value. */
+         } else {
+            outprm[ i ] = (int) ( op + 0.5 );
+         }
+      }
+   }
+
+/* Now do the same thing to determine the input permutation array. */
+   if( astOK ){
+      for( i = 0; i < nout; i++ ){
+         ptr2[ i ][ 0 ] = ( double ) i;
+         ptr2[ i ][ 1 ] = -1.0;
+      }
+   }
+
+   (void) astTransform( map, pset2, 0, pset1 );
+
+   if( astOK ){
+
+      for( i = 0; i < nin; i++ ){
+
+         ip = ptr1[ i ][ 0 ];
+         cn = ptr1[ i ][ 1 ];
+         if( ip == cn ) {
+
+            cnst[ nc ] = cn;
+            inprm[ i ] = -( nc + 1 );
+            nc++;
+
+         } else {
+            inprm[ i ] = (int) ( ip + 0.5 );
+         }
+      }
+   }
+
+/* Annul the PointSets. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* If an error has occurred, attempt to free the returned arrays. */
+   if( !astOK ) {
+      *outperm = (int *) astFree( (void *) *outperm );
+      *inperm = (int *) astFree( (void *) *inperm );
+      *consts = (double *) astFree( (void *) *consts );
+   }
+
+/* Return. */
+   return;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a PcdMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     PcdMap member function (over-rides the astSetAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a PcdMap, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the PcdMap.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPcdMap *this;              /* Pointer to the PcdMap structure */
+   double dval;                  /* Double attribute value */
+   int axis;                     /* Index for the axis */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PcdMap structure. */
+   this = (AstPcdMap *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* Disco. */
+/* ------ */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "disco= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetDisco( this, dval );
+
+/* PcdCen(axis). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "pcdcen(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetPcdCen( this, axis - 1, dval );
+
+/* PcdCen. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "pcdcen= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetPcdCen( this, 0, dval );
+      astSetPcdCen( this, 1, dval );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a PcdMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PcdMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a PcdMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the PcdMap.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPcdMap *this;              /* Pointer to the PcdMap structure */
+   int axis;                     /* Axis number */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* No. characters read by astSscanf */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the PcdMap structure. */
+   this = (AstPcdMap *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Disco. */
+/* ------ */
+   if ( !strcmp( attrib, "disco" ) ) {
+      result = astTestDisco( this );
+
+/* PcdCen. */
+/* ------- */
+   } else if ( !strcmp( attrib, "pcdcen" ) ) {
+      result = astTestPcdCen( this, 0 );
+
+/* PcdCen(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "pcdcen(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestPcdCen( this, axis - 1 );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a PcdMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     PcdMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a PcdMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to map them into the
+*     required window.
+
+*  Parameters:
+*     this
+*        Pointer to the PcdMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the PcdMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstPcdMap *map;               /* Pointer to PcdMap to be applied */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *axin_0;               /* Pointer to next input axis 0 value */
+   double *axin_1;               /* Pointer to next input axis 1 value */
+   double *axout_0;              /* Pointer to next output axis 0 value */
+   double *axout_1;              /* Pointer to next output axis 1 value */
+   int npoint;                   /* Number of points */
+   int point;                    /* Loop counter for points */
+   double dx;                    /* Undistorted X increment from centre  */
+   double dy;                    /* Undistorted Y increment from centre */
+   double dxp;                   /* Distorted X increment from centre  */
+   double dyp;                   /* Distorted Y increment from centre */
+   double disco;                 /* Distortion coefficient */
+   double cen0;                  /* Centre of distortion on axis 0 */
+   double cen1;                  /* Centre of distortion on axis 1 */
+   double cr2;                   /* Constant */
+   double a;                     /* Constant */
+   double cr2a2;                 /* Constant */
+   double f;                     /* Expansion factor */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the PcdMap. */
+   map = (AstPcdMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse mapping, according to the
+   direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* Get the distortion coefficient and centre. */
+   disco = astGetDisco( map );
+   cen0 = astGetPcdCen( map, 0 );
+   cen1 = astGetPcdCen( map, 1 );
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if( astOK ){
+
+/* Store pointers to the first input and output values on both axes. */
+      axin_0 = ptr_in[ 0 ];
+      axin_1 = ptr_in[ 1 ];
+      axout_0 = ptr_out[ 0 ];
+      axout_1 = ptr_out[ 1 ];
+
+/* First deal with forward transformations. */
+      if( forward ){
+
+         for( point = 0; point < npoint; point++ ){
+            if( *axin_0 != AST__BAD && *axin_1 != AST__BAD ){
+               dx = ( *axin_0 - cen0 );
+               dy = ( *axin_1 - cen1 );
+               f = 1.0 + disco*( dx*dx + dy*dy );
+               dxp = dx*f;
+               dyp = dy*f;
+               *(axout_0++) = dxp + cen0;
+               *(axout_1++) = dyp + cen1;
+
+            } else {
+               *(axout_0++) = AST__BAD;
+               *(axout_1++) = AST__BAD;
+            }
+            axin_0++;
+            axin_1++;
+         }
+
+/* Now deal with inverse transformations. */
+      } else {
+
+         for( point = 0; point < npoint; point++ ){
+            if( *axin_0 != AST__BAD && *axin_1 != AST__BAD ){
+               dxp = ( *axin_0 - cen0 );
+               dyp = ( *axin_1 - cen1 );
+
+               cr2 = disco*( dxp*dxp + dyp*dyp );
+               a = ( 2.0*cr2 + 1.0 )/( 3.0*cr2 + 1.0 );
+               cr2a2 = cr2*a*a;
+               f = ( 2.0*cr2a2*a + 1.0 )/( 3.0*cr2a2 + 1.0 );
+
+               dx = dxp*f;
+               dy = dyp*f;
+
+/* The above approximate inverse is taken from SLA_UNPCD. If more accuracy
+c   is needed, the following code can be uncommented to iterate to a better
+c   solution.
+c
+c               fl = 1.0 + disco*( dx*dx + dy*dy );
+c               dx = dxp/fl;
+c               dy = dyp/fl;
+c
+c               df = fabs( fl );
+c               while( df > fabs( fl*1.0E-6 ) ){
+c                  f = 1.0 + disco*( dx*dx + dy*dy );
+c                  df = fabs( f - fl );
+c                  fl = f;
+c
+c                  dx = dxp/f;
+c                  dy = dyp/f;
+c               }
+*/
+               *(axout_0++) = dx + cen0;
+               *(axout_1++) = dy + cen1;
+
+            } else {
+               *(axout_0++) = AST__BAD;
+               *(axout_1++) = AST__BAD;
+            }
+            axin_0++;
+            axin_1++;
+         }
+
+      }
+
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static void PcdZoom( AstMapping **maps, int *inverts, int ipc, int *status ){
+/*
+*  Name:
+*     PcdZoom
+
+*  Purpose:
+*     Swap a PcdMap and a ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     void PcdZoom( AstMapping **maps, int *inverts, int ipc, int *status )
+
+*  Class Membership:
+*     PcdMap member function
+
+*  Description:
+*     A list of two Mappings is supplied containing a PcdMap and a
+*     ZoomMap. These Mappings are annulled, and replaced with
+*     another pair of Mappings consisting of a PcdMap and a ZoomMap
+*     in the opposite order. These Mappings are chosen so that their
+*     combined effect is the same as the original pair of Mappings.
+
+*  Parameters:
+*     maps
+*        A pointer to an array of two Mapping pointers.
+*     inverts
+*        A pointer to an array of two invert flags.
+*     ipc
+*        The index within "maps" of the PcdMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPcdMap *pm2;               /* Pointer to the returned PcdMap */
+   AstPcdMap *pm;                /* Pointer to the supplied PcdMap */
+   AstZoomMap *zm2;              /* Pointer to the returned ZoomMap */
+   AstZoomMap *zm;               /* Pointer to the supplied ZoomMap */
+   double cen[2];                /* New distortion centre */
+   double disc;                  /* Distortion coeff. for returned PcdMap */
+   double disco;                 /* Distortion coeff. for supplied PcdMap */
+   double xcen;                  /* Axis 0 centre for supplied PcdMap */
+   double ycen;                  /* Axis 1 centre for supplied PcdMap */
+   double zoom;                  /* Zoom factor for supplied ZoomMap */
+   int old_pinv;                 /* Invert value for the supplied PcdMap */
+   int old_zinv;                 /* Invert value for the supplied ZoomMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store pointers to the supplied PcdMap and the ZoomMap. */
+   pm = (AstPcdMap *) maps[ ipc ];
+   zm = (AstZoomMap *) maps[ 1 - ipc ];
+
+/* Temporarily set the Invert attribute of the supplied Mappings to the
+   supplied values. */
+   old_pinv = astGetInvert( pm );
+   astSetInvert( pm, inverts[ ipc ] );
+
+   old_zinv = astGetInvert( zm );
+   astSetInvert( zm, inverts[ 1 - ipc ] );
+
+/* Get the zoom factor from the ZoomMap. */
+   zoom = astGetZoom( zm );
+
+/* Get the distortion coefficient from the PcdMap. */
+   disco = astGetDisco( pm );
+
+/* Get the distortion centre from the PcdMap. */
+   xcen = astGetPcdCen( pm, 0 );
+   ycen = astGetPcdCen( pm, 1 );
+
+/* Re-instate the original value of the Invert attributes of the supplied
+   Mappings. */
+   astSetInvert( pm, old_pinv );
+   astSetInvert( zm, old_zinv );
+
+/* Create the returned ZoomMap. */
+   zm2 = astZoomMap( 2, zoom, "", status );
+
+/* Find the attributes of the returned PcdMap. If the PCD map is applied
+   first... */
+   if( ipc == 0 ) {
+      cen[ 0 ] = xcen*zoom;
+      cen[ 1 ] = ycen*zoom;
+      disc = disco/(zoom*zoom);
+
+/* If the PCD map is applied second... */
+   } else {
+      cen[ 0 ] = xcen/zoom;
+      cen[ 1 ] = ycen/zoom;
+      disc = disco*zoom*zoom;
+   }
+
+/* Create the returned PcdMap. */
+   pm2 = astPcdMap( disc, cen, "", status );
+   if( inverts[ ipc ] ) astInvert( pm2 );
+
+/* Replace the supplied Mappings with the ones created above, swapping the
+   order. */
+   if( astOK ){
+      (void) astAnnul( pm );
+      (void) astAnnul( zm );
+
+      maps[ 1 - ipc ] = (AstMapping *) pm2;
+      inverts[ 1 - ipc  ] = inverts[ ipc ];
+
+      maps[ ipc ] = (AstMapping *) zm2;
+      inverts[ ipc ] = 0;
+
+   }
+
+/* Return. */
+   return;
+}
+
+static void PcdPerm( AstMapping **maps, int *inverts, int ipc, int *status ){
+/*
+*  Name:
+*     PcdPerm
+
+*  Purpose:
+*     Swap a PcdMap and a PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     void PcdPerm( AstMapping **maps, int *inverts, int ipc, int *status )
+
+*  Class Membership:
+*     PcdMap member function
+
+*  Description:
+*     A list of two Mappings is supplied containing a PcdMap and a
+*     PermMap. These Mappings are annulled, and replaced with
+*     another pair of Mappings consisting of a PcdMap and a PermMap
+*     in the opposite order. These Mappings are chosen so that their
+*     combined effect is the same as the original pair of Mappings.
+
+*  Parameters:
+*     maps
+*        A pointer to an array of two Mapping pointers.
+*     inverts
+*        A pointer to an array of two invert flags.
+*     ipc
+*        The index within "maps" of the PcdMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  It should have been checked previously that the PermMap simply
+*     swaps axes 0 and 1. This is the only sort of PermMap which can be
+*     used here.
+
+*/
+
+   AstPermMap *rm;               /* Pointer to the supplied PermMap */
+   AstPermMap *rm2;              /* Pointer to the returned PermMap */
+   AstPcdMap *pm;                /* Pointer to the supplied PcdMap */
+   AstPcdMap *pm2;               /* Pointer to the returned PcdMap */
+   double cen[2];                /* Centre for new PcdMap */
+   double disco;                 /* Distortion coeff. for supplied PcdMap */
+   double xcen;                  /* Axis 0 centre for supplied PcdMap */
+   double ycen;                  /* Axis 1 centre for supplied PcdMap */
+   int old_rinv;                 /* Invert value for the supplied PermMap */
+   int old_pinv;                 /* Invert value for the supplied PcdMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store pointers to the supplied PcdMap and the PermMap. */
+   pm = (AstPcdMap *) maps[ ipc ];
+   rm = (AstPermMap *) maps[ 1 - ipc ];
+
+/* Temporarily set the Invert attribute of the supplied Mappings to the
+   supplied values. */
+   old_pinv = astGetInvert( pm );
+   astSetInvert( pm, inverts[ ipc ] );
+
+   old_rinv = astGetInvert( rm );
+   astSetInvert( rm, inverts[ 1 - ipc ] );
+
+/* Get the distortion coefficient from the PcdMap. */
+   disco = astGetDisco( pm );
+
+/* Get the distortion centre from the PcdMap. */
+   xcen = astGetPcdCen( pm, 0 );
+   ycen = astGetPcdCen( pm, 1 );
+
+/* Re-instate the original value of the Invert attributes of the supplied
+   Mappings. */
+   astSetInvert( pm, old_pinv );
+   astSetInvert( rm, old_rinv );
+
+/* Create the returned PermMap (unchanged). */
+   rm2 = astClone( rm );
+
+/* Create the returned PcdMap. */
+   cen[ 0 ] = ycen;
+   cen[ 1 ] = xcen;
+   pm2 = astPcdMap( disco, cen, "", status );
+   if( inverts[ ipc ] ) astInvert( pm2 );
+
+/* Replace the supplied Mappings with the ones created above, swapping the
+   order. */
+   if( astOK ){
+      (void) astAnnul( pm );
+      (void) astAnnul( rm );
+
+      old_pinv = inverts[ ipc ];
+
+      maps[ ipc ] = (AstMapping *) rm2;
+      inverts[ ipc ] = inverts[ 1 - ipc ];
+
+      maps[ 1 - ipc ] = (AstMapping *) pm2;
+      inverts[ 1 - ipc  ] = old_pinv;
+   }
+
+/* Return. */
+   return;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+/*
+*att++
+*  Name:
+*     Disco
+
+*  Purpose:
+*     PcdMap pincushion/barrel distortion coefficient.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute specifies the pincushion/barrel distortion coefficient
+*     used by a PcdMap. This coefficient is set when the PcdMap is created,
+*     but may later be modified. If the attribute is cleared, its default
+*     value is zero, which gives no distortion. For pincushion distortion,
+*     the value should be positive. For barrel distortion, it should be
+*     negative.
+*
+*     Note that the forward transformation of a PcdMap applies the
+*     distortion specified by this attribute and the inverse
+*     transformation removes this distortion. If the PcdMap is inverted
+c     (e.g. using astInvert), then the forward transformation will
+f     (e.g. using AST_INVERT), then the forward transformation will
+*     remove the distortion and the inverse transformation will apply
+*     it. The distortion itself will still be given by the same value of
+*     Disco.
+*
+*     Note, the value of this attribute may changed only if the PcdMap
+*     has no more than one reference. That is, an error is reported if the
+*     PcdMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     PcdMap
+*        All PcdMaps have this attribute.
+
+*att--
+*/
+/* This ia a double value with a value of AST__BAD when undefined but
+   yielding a default of 0.0. */
+astMAKE_CLEAR1(PcdMap,Disco,disco,AST__BAD)
+astMAKE_GET(PcdMap,Disco,double,0.0,( ( this->disco == AST__BAD ) ?
+                                      0.0 : this->disco ))
+astMAKE_SET1(PcdMap,Disco,double,disco,value)
+astMAKE_TEST(PcdMap,Disco,( this->disco != AST__BAD ))
+
+
+/*
+*att++
+*  Name:
+*     PcdCen(axis)
+
+*  Purpose:
+*     Centre coordinates of pincushion/barrel distortion.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute specifies the centre of the pincushion/barrel
+*     distortion implemented by a PcdMap. It takes a separate value for
+*     each axis of the PcdMap so that, for instance, the settings
+*     "PcdCen(1)=345.0,PcdCen(2)=-104.4" specify that the pincushion
+*     distortion is centred at positions of 345.0 and -104.4 on axes 1 and 2
+*     respectively. This attribute is set when a PcdMap is created, but may
+*     later be modified. If the attribute is cleared, the default value for
+*     both axes is zero.
+*
+*     Note, the value of this attribute may changed only if the PcdMap
+*     has no more than one reference. That is, an error is reported if the
+*     PcdMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     PcdMap
+*        All PcdMaps have this attribute.
+
+*  Notes:
+*     - If no axis is specified, (e.g. "PcdCen" instead of
+*     "PcdCen(2)"), then a "set" or "clear" operation will affect
+*     the attribute value of both axes, while a "get" or "test"
+*     operation will use just the PcdCen(1) value.
+*att--
+*/
+/* The centre of the distortion. AST__BAD is stored if no value has been
+   supplied, resulting in default values of zero being used. */
+MAKE_CLEAR(PcdCen,pcdcen,AST__BAD,2)
+MAKE_SET(PcdCen,double,pcdcen,value,2)
+MAKE_TEST(PcdCen,( this->pcdcen[axis] != AST__BAD ),2)
+MAKE_GET(PcdCen,double,0.0,(( this->pcdcen[axis] == AST__BAD ) ?
+                              0.0 : this->pcdcen[axis] ),2)
+
+/* Copy constructor. */
+/* ----------------- */
+/* No copy constructor is needed, as a byte-by-byte copy suffices. */
+
+/* Destructor. */
+/* ----------- */
+/* No destructor is needed as no memory, etc. needs freeing. */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for PcdMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the PcdMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the PcdMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPcdMap *this;              /* Pointer to the PcdMap structure */
+   double dval;                  /* Attribute value */
+   int set;                      /* Is a value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PcdMap structure. */
+   this = (AstPcdMap *) this_object;
+
+/* Write out values representing the instance variables for the
+   PcdMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* PcdCen(0). */
+/* ---------- */
+   set = TestPcdCen( this, 0, status );
+   dval = set ? GetPcdCen( this, 0, status ) : astGetPcdCen( this, 0 );
+   astWriteDouble( channel, "PcdCn0", set, 1, dval, "Distortion centre on first axis" );
+
+/* PcdCen(1). */
+/* ---------- */
+   set = TestPcdCen( this, 1, status );
+   dval = set ? GetPcdCen( this, 1, status ) : astGetPcdCen( this, 1 );
+   astWriteDouble( channel, "PcdCn1", set, 1, dval, "Distortion centre on second axis" );
+
+/* Disco. */
+/* ------ */
+   set = TestDisco( this, status );
+   dval = set ? GetDisco( this, status ) : astGetDisco( this );
+   astWriteDouble( channel, "Disco", set, 1, dval, "Distortion coefficient" );
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAPcdMap and astCheckPcdMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(PcdMap,Mapping)
+astMAKE_CHECK(PcdMap)
+
+AstPcdMap *astPcdMap_( double disco, const double pcdcen[2],
+                       const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astPcdMap
+f     AST_PCDMAP
+
+*  Purpose:
+*     Create a PcdMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "pcdmap.h"
+c     AstPcdMap *astPcdMap( double disco, const double pcdcen[2],
+c                           const char *options, ... )
+f     RESULT = AST_PCDMAP( DISCO, PCDCEN, OPTIONS, STATUS )
+
+*  Class Membership:
+*     PcdMap constructor.
+
+*  Description:
+*     This function creates a new PcdMap and optionally initialises its
+*     attributes.
+*
+*     A PcdMap is a non-linear Mapping which transforms 2-dimensional
+*     positions to correct for the radial distortion introduced by some
+*     cameras and telescopes. This can take the form either of pincushion
+*     or barrel distortion, and is characterized by a single distortion
+*     coefficient.
+*
+*     A PcdMap is specified by giving this distortion coefficient and the
+*     coordinates of the centre of the radial distortion. The forward
+*     transformation of a PcdMap applies the distortion:
+*
+*        RD = R * ( 1 + C * R * R )
+*
+*     where R is the undistorted radial distance from the distortion
+*     centre (specified by attribute PcdCen), RD is the radial distance
+*     from the same centre in the presence of distortion, and C is the
+*     distortion coefficient (given by attribute Disco).
+*
+*     The inverse transformation of a PcdMap removes the distortion
+*     produced by the forward transformation. The expression used to derive
+*     R from RD is an approximate inverse of the expression above, obtained
+*     from two iterations of the Newton-Raphson method. The mismatch between
+*     the forward and inverse expressions is negligible for astrometric
+*     applications (to reach 1 milliarcsec at the edge of the Anglo-Australian
+*     Telescope triplet or a Schmidt field would require field diameters of
+*     2.4 and 42 degrees respectively).
+*
+c     If a PcdMap is inverted (e.g. using astInvert) then the roles of the
+f     If a PcdMap is inverted (e.g. using AST_INVERT) then the roles of the
+*     forward and inverse transformations are reversed; the forward
+*     transformation will remove the distortion, and the inverse
+*     transformation will apply it.
+
+*  Parameters:
+c     disco
+f     DISCO = DOUBLE PRECISION (Given)
+*        The distortion coefficient. Negative values give barrel
+*        distortion, positive values give pincushion distortion, and
+*        zero gives no distortion.
+c     pcdcen
+f     PCDCEN( 2 ) = DOUBLE PRECISION (Given)
+c        A 2-element array containing the coordinates of the centre of the
+f        An array containing the coordinates of the centre of the
+*        distortion.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new PcdMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new PcdMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astPcdMap()
+f     AST_PCDMAP = INTEGER
+*        A pointer to the new PcdMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPcdMap *new;              /* Pointer to new PcdMap */
+   va_list args;                /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the PcdMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPcdMap( NULL, sizeof( AstPcdMap ), !class_init, &class_vtab,
+                        "PcdMap", disco, pcdcen );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new PcdMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new PcdMap. */
+   return new;
+}
+
+AstPcdMap *astPcdMapId_( double disco, const double pcdcen[2],
+                         const char *options, ... ) {
+/*
+*  Name:
+*     astPcdMapId_
+
+*  Purpose:
+*     Create a PcdMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     AstPcdMap *astPcdMapId_( double disco, const double pcdcen[2],
+*                              const char *options, ... )
+
+*  Class Membership:
+*     PcdMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astPcdMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astPcdMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astPcdMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astPcdMap_.
+
+*  Returned Value:
+*     The ID value associated with the new PcdMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPcdMap *new;              /* Pointer to new PcdMap */
+   va_list args;                /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the PcdMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPcdMap( NULL, sizeof( AstPcdMap ), !class_init, &class_vtab,
+                        "PcdMap", disco, pcdcen );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new PcdMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new PcdMap. */
+   return astMakeId( new );
+}
+
+AstPcdMap *astInitPcdMap_( void *mem, size_t size, int init,
+                           AstPcdMapVtab *vtab, const char *name,
+                           double disco, const double pcdcen[2], int *status ){
+/*
+*+
+*  Name:
+*     astInitPcdMap
+
+*  Purpose:
+*     Initialise a PcdMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     AstPcdMap *astInitPcdMap( void *mem, size_t size, int init,
+*                               AstPcdMapVtab *vtab, const char *name,
+*                               double disco, const double pcdcen[2] )
+
+*  Class Membership:
+*     PcdMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new PcdMap object. It allocates memory (if necessary) to accommodate
+*     the PcdMap plus any additional data associated with the derived class.
+*     It then initialises a PcdMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a PcdMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the PcdMap is to be initialised.
+*        This must be of sufficient size to accommodate the PcdMap data
+*        (sizeof(PcdMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the PcdMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the PcdMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the PcdMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new PcdMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     disco
+*        Distortion coefficient.
+*     pcdcen
+*        Distortion centre.
+
+*  Returned Value:
+*     A pointer to the new PcdMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstPcdMap *new;              /* Pointer to new PcdMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitPcdMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the PcdMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstPcdMap *) astInitMapping( mem, size, 0,
+                                       (AstMappingVtab *) vtab, name,
+                                       2, 2, 1, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the PcdMap data. */
+/* ---------------------------- */
+/* Store the shift and scale for each axis. */
+      new->pcdcen[0] = pcdcen[0];
+      new->pcdcen[1] = pcdcen[1];
+      new->disco = disco;
+
+/* If an error occurred, clean up by deleting the new PcdMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new PcdMap. */
+   return new;
+}
+
+AstPcdMap *astLoadPcdMap_( void *mem, size_t size,
+                           AstPcdMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadPcdMap
+
+*  Purpose:
+*     Load a PcdMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pcdmap.h"
+*     AstPcdMap *astLoadPcdMap( void *mem, size_t size,
+*                               AstPcdMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     PcdMap loader.
+
+*  Description:
+*     This function is provided to load a new PcdMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     PcdMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a PcdMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the PcdMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        PcdMap data (sizeof(PcdMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the PcdMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the PcdMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstPcdMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new PcdMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the PcdMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "PcdMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new PcdMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPcdMap *new;              /* Pointer to the new PcdMap */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this PcdMap. In this case the
+   PcdMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstPcdMap );
+      vtab = &class_vtab;
+      name = "PcdMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitPcdMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built PcdMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "PcdMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* PcdCen(0). */
+/* ---------- */
+      new->pcdcen[0] = astReadDouble( channel, "pcdcn0", AST__BAD );
+      if ( TestPcdCen( new, 0, status ) ) SetPcdCen( new, 0, new->pcdcen[0], status );
+
+/* PcdCen(1). */
+/* ---------- */
+      new->pcdcen[1] = astReadDouble( channel, "pcdcn1", AST__BAD );
+      if ( TestPcdCen( new, 1, status ) ) SetPcdCen( new, 1, new->pcdcen[1], status );
+
+/* Disco. */
+/* ------ */
+      new->disco = astReadDouble( channel, "disco", AST__BAD );
+      if ( TestDisco( new, status ) ) SetDisco( new, new->disco, status );
+
+/* If an error occurred, clean up by deleting the new PcdMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new PcdMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
diff --git a/ast/pcdmap.h b/ast/pcdmap.h
new file mode 100644
index 0000000..ca8fc24
--- /dev/null
+++ b/ast/pcdmap.h
@@ -0,0 +1,357 @@
+#if !defined( PCDMAP_INCLUDED ) /* Include this file only once */
+#define PCDMAP_INCLUDED
+/*
+*+
+*  Name:
+*     pcdmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the PcdMap class.
+
+*  Invocation:
+*     #include "pcdmap.h"
+
+*  Description:
+*     This include file defines the interface to the PcdMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The PcdMap class implements Mappings which perform pincushion
+*     distortion.
+
+*  Inheritance:
+*     The PcdMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     Disco (double)
+*        This attribute holds the PcdMap distortion coefficient used by
+*        the forward transformation. This coefficient is set when a
+*        PcdMap is created, but may later be modified. The default value
+*        is zero, which gives no distortion. For pincushion distortion,
+*        the supplied value should be positive. For barrel distortion, it
+*        should be negative.
+*
+*        Note that the forward transformation of a PcdMap applies the
+*        distortion corresponding to this attribute, and the inverse
+*        transformation removes this distortion. If a PcdMap is inverted
+*        (e.g. by using astInvert), then the forward transformation will
+*        remove the distortion and the inverse transformation will apply
+*        it. The distortion itself will still be given by the same value of
+*        Disco.
+*     PcdCen(axis)
+*        This attribute specifies the centre of a pincushion distortion.
+*        It takes a separate value for each axis of the PcdMap so that, for
+*        instance, the settings "PcdCen(1)=345.0,PcdCen(2)=-104.4" specify
+*        that the pincushion distortion is centred at values of 345.0 and
+*        -104.4 on axes 1 and 2 of the PcdMap. The default for both axes is
+*        zero.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearAttrib
+*           Clear an attribute value for a PcdMap.
+*        astGetAttrib
+*           Get an attribute value for a PcdMap.
+*        astSetAttrib
+*           Set an attribute value for a PcdMap.
+*        astTestAttrib
+*           Test if an attribute value has been set for a PcdMap.
+*        astTransform
+*           Apply a PcdMap to transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearDisco
+*           Clear the Disco attribute value for a PcdMap.
+*        astGetDisco
+*           Get the Disco attribute value for a PcdMap.
+*        astSetDisco
+*           Set the Disco attribute value for a PcdMap.
+*        astTestDisco
+*           Test if a Disco attribute value has been set for a PcdMap.
+*        astClearPcdCen
+*           Clear the PcdCen attribute value for a PcdMap.
+*        astGetPcdCen
+*           Get the PcdCen attribute value for a PcdMap.
+*        astSetPcdCen
+*           Set the PcdCen attribute value for a PcdMap.
+*        astTestPcdCen
+*           Test if a PcdCen attribute value has been set for a PcdMap.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAPcdMap
+*           Test class membership.
+*        astPcdMap
+*           Create a PcdMap.
+*
+*     Protected:
+*        astCheckPcdMap
+*           Validate class membership.
+*        astInitPcdMap
+*           Initialise a PcdMap.
+*        astInitPcdMapVtab
+*           Initialise the virtual function table for the PcdMap class.
+*        astLoadPcdMap
+*           Load a PcdMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstPcdMap
+*           PcdMap object type.
+*
+*     Protected:
+*        AstPcdMapVtab
+*           PcdMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     18-MAY-1999 (DSB):
+*        Original version.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitPcdMapVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* PcdMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstPcdMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double disco;                 /* Distortion coefficient */
+   double pcdcen[2];             /* Distortion centre */
+
+} AstPcdMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstPcdMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   double (*GetDisco)( AstPcdMap *, int * );
+   int (* TestDisco)( AstPcdMap *, int * );
+   void (* ClearDisco)( AstPcdMap *, int * );
+   void (* SetDisco)( AstPcdMap *, double, int * );
+   double (*GetPcdCen)( AstPcdMap *, int, int * );
+   int (* TestPcdCen)( AstPcdMap *, int, int * );
+   void (* ClearPcdCen)( AstPcdMap *, int, int * );
+   void (* SetPcdCen)( AstPcdMap *, int, double, int * );
+} AstPcdMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstPcdMapGlobals {
+
+/* Define the thread-specific globals. */
+   char GetAttrib_Buff[ 101 ];
+   AstPcdMapVtab Class_Vtab;
+   int Class_Init;
+} AstPcdMapGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(PcdMap)          /* Check class membership */
+astPROTO_ISA(PcdMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstPcdMap *astPcdMap_( double, const double [2], const char *, int *, ...);
+#else
+AstPcdMap *astPcdMapId_( double, const double [2], const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstPcdMap *astInitPcdMap_( void *, size_t, int, AstPcdMapVtab *,
+                           const char *, double, const double [2], int * );
+
+/* Vtab initialiser. */
+void astInitPcdMapVtab_( AstPcdMapVtab *, const char *, int * );
+
+/* Loader. */
+AstPcdMap *astLoadPcdMap_( void *, size_t, AstPcdMapVtab *,
+                           const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitPcdMapGlobals_( AstPcdMapGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+double astGetDisco_( AstPcdMap *, int * );
+int astTestDisco_( AstPcdMap *, int * );
+void astClearDisco_( AstPcdMap *, int * );
+void astSetDisco_( AstPcdMap *, double, int * );
+double astGetPcdCen_( AstPcdMap *, int, int * );
+int astTestPcdCen_( AstPcdMap *, int, int * );
+void astClearPcdCen_( AstPcdMap *, int, int * );
+void astSetPcdCen_( AstPcdMap *, int, double, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckPcdMap(this) astINVOKE_CHECK(PcdMap,this,0)
+#define astVerifyPcdMap(this) astINVOKE_CHECK(PcdMap,this,1)
+
+/* Test class membership. */
+#define astIsAPcdMap(this) astINVOKE_ISA(PcdMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astPcdMap astINVOKE(F,astPcdMap_)
+#else
+#define astPcdMap astINVOKE(F,astPcdMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitPcdMap(mem,size,init,vtab,name,disco,pcdcen) \
+astINVOKE(O,astInitPcdMap_(mem,size,init,vtab,name,disco,pcdcen,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitPcdMapVtab(vtab,name) astINVOKE(V,astInitPcdMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadPcdMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadPcdMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckPcdMap to validate PcdMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astClearDisco(this) astINVOKE(V,astClearDisco_(astCheckPcdMap(this),STATUS_PTR))
+#define astGetDisco(this) astINVOKE(V,astGetDisco_(astCheckPcdMap(this),STATUS_PTR))
+#define astSetDisco(this,value) \
+astINVOKE(V,astSetDisco_(astCheckPcdMap(this),value,STATUS_PTR))
+#define astTestDisco(this) astINVOKE(V,astTestDisco_(astCheckPcdMap(this),STATUS_PTR))
+
+#define astClearPcdCen(this,axis) \
+astINVOKE(V,astClearPcdCen_(astCheckPcdMap(this),axis,STATUS_PTR))
+#define astGetPcdCen(this,axis) \
+astINVOKE(V,astGetPcdCen_(astCheckPcdMap(this),axis,STATUS_PTR))
+#define astSetPcdCen(this,axis,value) \
+astINVOKE(V,astSetPcdCen_(astCheckPcdMap(this),axis,value,STATUS_PTR))
+#define astTestPcdCen(this,axis) \
+astINVOKE(V,astTestPcdCen_(astCheckPcdMap(this),axis,STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/permmap.c b/ast/permmap.c
new file mode 100644
index 0000000..69b0962
--- /dev/null
+++ b/ast/permmap.c
@@ -0,0 +1,3204 @@
+/*
+*class++
+*  Name:
+*     PermMap
+
+*  Purpose:
+*     Coordinate permutation Mapping.
+
+*  Constructor Function:
+c     astPermMap
+f     AST_PERMMAP
+
+*  Description:
+*     A PermMap is a Mapping which permutes the order of coordinates,
+*     and possibly also changes the number of coordinates, between its
+*     input and output.
+*
+*     In addition to permuting the coordinate order, a PermMap may
+*     also assign constant values to coordinates. This is useful when
+*     the number of coordinates is being increased as it allows fixed
+*     values to be assigned to any new ones.
+
+*  Inheritance:
+*     The PermMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The PermMap class does not define any new attributes beyond
+*     those which are applicable to all Mappings.
+
+*  Functions:
+c     The PermMap class does not define any new functions beyond those
+f     The PermMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     29-FEB-1996 (RFWS):
+*        Original version.
+*     26-SEP-1996 (RFWS):
+*        Added external interface and I/O facilities.
+*     3-JUN-1997 (RFWS):
+*        Over-ride the MapMerge method.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitPermMapVtab
+*        method.
+*     11-SEP-2003 (DSB):
+*        Added methods astGetInPerm and astGetOutPerm.
+*     2-NOV-2004 (DSB):
+*        Added method astGetConstants.
+*     14-MAR-2006 (DSB):
+*        Override astEqual.
+*     2-MAY-2007 (DSB):
+*        Change MapSplit so that it does not try to use the
+*        implementation from the parent Mapping class, since this
+*        class can do a better job.
+*     11-SEP-2007 (DSB):
+*        In MapSplit, check that the permuted axis index is less than the
+*        number of axes available. Use AST__BAD otherwise.
+*     10-JAN-2011 (DSB):
+*        Add protected PermSplit attribute.
+*     11-FEB-2011 (DSB):
+*        Do not allow MapSplit to return a Mapping with zero outputs.
+*     22-NOV-2012 (DSB):
+*        When using a default inperm array (as indicated by a NULL pointer
+*        for inperm), ensure the array is padded with "-1" values if the
+*        number of inputs exceeds the number of outputs. Also do the
+*        equivalent for default outperm arrays.
+*     26-MAY-2016 (DSB):
+*        Allow the PermSplit attribute to be changed at any time. This is 
+*        because it does not directly affect either the forward or inverse
+*        transformation of the PermMap. The FitsCHan class needs to be able
+*        to change it to determine when checking if the -TAB algorithm can
+*        be used.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS PermMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "unitmap.h"             /* Unit Mappings */
+#include "permmap.h"             /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+/* Module Macros */
+/* ============= */
+/* A macro that returns the inperm or outperm value to use for a given
+   index, taking account of the possibility that the inperm or outperm may
+   be NULL (implying a unit permutation), and that he numbers of inputs
+   and outputs may not be equal. "perms" is a pointer to the integer
+   permutation array (inperm or outperm), "i" is the index of the required
+   element of the permutation array, and "maxperm" is one more than the
+   maximum value allowed in the permutation array (i.e. the number of
+   PermMap outputs if "perms" is inperm, or PermMap inputs if "perms" is
+   outperm). */
+#define PERMVAL( perms, i, maxperm ) ( perms ? perms[ i ] : (  i < maxperm ? i : -1 ))
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(PermMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(PermMap,Class_Init)
+#define class_vtab astGLOBAL(PermMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstPermMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstPermMap *astPermMapId_( int, const int [], int, const int [], const double [], const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double *GetConstants( AstPermMap *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int *GetInPerm( AstPermMap *, int * );
+static int *GetOutPerm( AstPermMap *, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int NullPerm( AstPermMap *, int, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+
+static void SetPermSplit( AstPermMap *, int, int * );
+static void ClearPermSplit( AstPermMap *, int * );
+static int TestPermSplit( AstPermMap *, int * );
+static int GetPermSplit( AstPermMap *, int * );
+
+
+/* Member functions. */
+/* ================= */
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two PermMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     PermMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two PermMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a PermMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the PermMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPermMap *that;
+   AstPermMap *this;
+   int *that_inp;
+   int *that_outp;
+   int *this_inp;
+   int *this_outp;
+   int i;
+   int nin;
+   int nin_that;
+   int nout;
+   int nout_that;
+   int p1;
+   int p2;
+   int result;
+   int that_inp_len;
+   int that_outp_len;
+   int this_inp_len;
+   int this_outp_len;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two PermMap structures. */
+   this = (AstPermMap *) this_object;
+   that = (AstPermMap *) that_object;
+
+/* Check the second object is a PermMap. We know the first is a
+   PermMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAPermMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNout( that ) == nout && astGetNin( that ) == nin ) {
+
+/* Assume the PermMaps are equivalent. */
+         result = 1;
+
+/* Get the number of inputs and outputs in the second PermMap. */
+         nin_that = astGetNin( that );
+         nout_that = astGetNout( that );
+
+/* Get pointers to the effective inperm and outperm array for each PermMap.
+   If the Invert flags of the two PermMaps are not equal, we swap the
+   arrays for the second PermMap in order to take account of the relative
+   inversion of the second PermMap. */
+         this_inp = this->inperm;
+         this_outp = this->outperm;
+
+         if(  astGetInvert( this ) ) {
+            this_inp_len =  nout;
+            this_outp_len =  nin;
+         } else {
+            this_inp_len =  nin;
+            this_outp_len =  nout;
+         }
+
+         if( astGetInvert( this ) != astGetInvert( that ) ) {
+            that_inp = that->outperm;
+            that_outp = that->inperm;
+
+            if(  astGetInvert( that ) ) {
+               that_inp_len =  nin_that;
+               that_outp_len =  nout_that;
+            } else {
+               that_inp_len =  nout_that;
+               that_outp_len =  nin_that;
+            }
+
+         } else {
+            that_inp = that->inperm;
+            that_outp = that->outperm;
+
+            if(  astGetInvert( that ) ) {
+               that_inp_len =  nout_that;
+               that_outp_len =  nin_that;
+            } else {
+               that_inp_len =  nin_that;
+               that_outp_len =  nout_that;
+            }
+         }
+
+/* Loop round every PermMap input. */
+         for( i = 0; i < nin; i++ ) {
+
+/* See what is fed to this input by the inverse transformation. A zero or
+   positive integer "p" value indicates that the input is fed from the
+   output with the corresponding index. A negative integer "p" value means
+   the input is fed a constant value stored at index (-p-1) in the
+   associated constants array. */
+            p1 = PERMVAL( this_inp, i, this_outp_len );
+            p2 = PERMVAL( that_inp, i, that_outp_len );
+
+/* If the "p" values differ, we may have evidence that the PermMaps are
+   not equivalent. */
+            if( p1 != p2 ) {
+
+/* If either "p" value is zero or positive, then the PermMaps are
+   definitely different since input "i" is fed from differing outputs, or
+   one is fed from an input and the other is fed a constant. */
+               if( p1 >= 0 || p2 >= 0 ) {
+                  result = 0;
+                  break;
+
+/* If both "p" values are negative, then both inputs are fed a constant
+   value. The PermMaps differ if these constants differ. */
+               } else if( this->constant[ -p1 - 1 ] !=
+                          that->constant[ -p2 - 1 ] ) {
+                  result = 0;
+                  break;
+               }
+            }
+         }
+
+/* If we have not yet discovered any evidence that the PermMaps differ,
+   go on to check each output in the same way that we have just checked the
+   inputs. */
+         if( result ) {
+            for( i = 0; i < nout; i++ ) {
+               p1 = PERMVAL( this_outp, i, this_inp_len );
+               p2 = PERMVAL( that_outp, i, that_inp_len );
+
+               if( p1 != p2 ) {
+                  if( p1 >= 0 || p2 >= 0 ) {
+                     result = 0;
+                     break;
+                  } else if( this->constant[ -p1 - 1 ] !=
+                             that->constant[ -p2 - 1 ] ) {
+                     result = 0;
+                     break;
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static double *GetConstants( AstPermMap *this, int *status ){
+/*
+*+
+*  Name:
+*     astGetConstants
+
+*  Purpose:
+*     Return a pointer to the constants array of a PermMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     double *astGetConstants( AstPermMap *this )
+
+*  Class Membership:
+*     PermMap method
+
+*  Description:
+*     This function returns a pointer to a dynamically allocated array
+*     holding a copy of the constants array supplied when the PermMap was
+*     created.
+*
+*     Negative values in the arrays returned by the astGetInPerm and
+*     astGetOutPerm methods can be used as indices into the constants
+*     array returned by this method, if they are first negated and then
+*     decrement by one. Thus an inperm value of -3 refers to element 2 of
+*     the constants array.
+
+*  Parameters:
+*     this
+*        Pointer to the PermMap.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array holding a copy of the
+*     constants array. The pointer should be freed using astFree when it is
+*     no longer needed. NULL will be returned if the PermMap has no
+*     constants.
+
+*  Notes:
+*     - A value of NULL will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   double *result;                /* Pointer to the returned array */
+
+/* Initialise the returned result. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Allocate memory and put a copy of the InPerm array in it. */
+   result = (double *) astStore( NULL, this->constant, astSizeOf( this->constant ) );
+
+/* Return the result. */
+   return result;
+}
+
+static int *GetInPerm( AstPermMap *this, int *status ){
+/*
+*  Name:
+*     GetInPerm
+
+*  Purpose:
+*     Return a pointer to the InPerm array of a PermMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     int *astGetInPerm( AstPermMap *this, int *status )
+
+*  Class Membership:
+*     PermMap method
+
+*  Description:
+*     This function returns a pointer to a dynamically allocated array
+*     holding a copy of the InPerm array supplied when thre PermMap was
+*     created.
+
+*  Parameters:
+*     this
+*        Pointer to the PermMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array holding a copy of the
+*     InPerm array. The pointer should be freed using astFree when it is
+*     no longer needed. The number of elements in the array will be given
+*     by the value of the Nin attribute. The value in element "i" is the
+*     zero-based index of the output axis which provides values for input
+*     "i" when the inverse transformation is used. If the value in element
+*     "i" is less than zero, then input "i" is fed a constant value. This
+*     constant value is stored in the "constants" array (see astGetConstants)
+*     at an index equal to the absolute value of inperm[i], minus 1. Thus
+*     if element 3 of the array returned by this function has value -2,
+*     then input axis 3 is fed the value held in constants[1]. If the
+*     value of element "i" of the returned inperm array is greater than
+*     or equal to the number of output axes, then input "i" will be fed
+*     the constant AST__BAD.
+
+*  Notes:
+*     - A value of NULL will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   int i;                      /* Loop count */
+   int nin;                    /* Number of inputs. */
+   int *result;                /* Pointer to the returned array */
+
+/* Initialise the returned result. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If no inperm array is stored, every input is derived from the
+   corresponding output. Therefore, return an array holding 0 to Nin-1. */
+   if( !this->inperm ) {
+      nin = astGetNin( this );
+      result = (int *) astMalloc( sizeof( int ) * (size_t) nin );
+      if( astOK ) for( i = 0; i < nin; i++ ) result[ i ] = i;
+
+/* Otherwise, allocate memoy and put a copy of the InPerm array in it. */
+   } else {
+      result = (int *) astStore( NULL, this->inperm,
+                              sizeof( int ) * (size_t) astGetNin( this ) );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int *GetOutPerm( AstPermMap *this, int *status ){
+/*
+*  Name:
+*     GetOutPerm
+
+*  Purpose:
+*     Return a pointer to the OutPerm array of a PermMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     int *astGetOutPerm( AstPermMap *this, int *status )
+
+*  Class Membership:
+*     PermMap method
+
+*  Description:
+*     This function returns a pointer to a dynamically allocated array
+*     holding a copy of the OutPerm array supplied when thre PermMap was
+*     created.
+
+*  Parameters:
+*     this
+*        Pointer to the PermMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array holding a copy of the
+*     OutPerm array. The pointer should be freed using astFree when it is
+*     no longer needed. The number of elements in the array will be given
+*     by the value of the Nout attribute. The value in element "i" is the
+*     zero-based index of the input axis which provides values for output
+*     "i" when the forward transformation is used. If the value in element
+*     "i" is less than zero, then output "i" is fed a constant value. This
+*     constant value is stored in the "constants" array (see astGetConstants)
+*     at an index equal to the absolute value of outperm[i], minus 1. Thus
+*     if element 3 of the array returned by this function has value -2,
+*     then output axis 3 is fed the value held in constants[1]. If the
+*     value of element "i" of the returned outperm array is greater than
+*     or equal to the number of input axes, then output "i" will be fed
+*     the constant AST__BAD.
+
+*  Notes:
+*     - A value of NULL will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   int i;                      /* Loop count */
+   int nout;                   /* Number of outputs. */
+   int *result;                /* Pointer to the returned array */
+
+/* Initialise the returned result. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If no outperm array is stored, every output is derived from the
+   corresponding input. Therefore, return an array holding 0 to Nout-1. */
+   if( !this->outperm ) {
+      nout = astGetNout( this );
+      result = (int *) astMalloc( sizeof( int ) * (size_t) nout );
+      if( astOK ) for( i = 0; i < nout; i++ ) result[ i ] = i;
+
+/* Otherwise, allocate memory and put a copy of the OutPerm array in it. */
+   } else {
+      result = (int *) astStore( NULL, this->outperm,
+                              sizeof( int ) * (size_t) astGetNout( this ) );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astInitPermMapVtab_(  AstPermMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPermMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a PermMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     void astInitPermMapVtab( AstPermMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     PermMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the PermMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAPermMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->GetConstants = GetConstants;
+   vtab->GetInPerm = GetInPerm;
+   vtab->GetOutPerm = GetOutPerm;
+   vtab->ClearPermSplit = ClearPermSplit;
+   vtab->GetPermSplit = GetPermSplit;
+   vtab->SetPermSplit = SetPermSplit;
+   vtab->TestPermSplit = TestPermSplit;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   mapping->MapSplit = MapSplit;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+   mapping->Rate = Rate;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "PermMap", "Coordinate permutation" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     PermMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated PermMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated PermMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated PermMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the PermMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        PermMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated PermMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map;              /* Pointer to Mapping */
+   AstMapping *new;              /* Pointer to replacement Mapping */
+   AstPermMap *permmap;          /* Pointer to PermMap */
+   const char *class;            /* Pointer to Mapping class string */
+   double *con;                  /* Pointer to constants array */
+   double constant;              /* Constant value */
+   int *inperm;                  /* Pointer to "inperm" permutation array */
+   int *newperm;                 /* Pointer to new permutation array */
+   int *outperm;                 /* Pointer to "outperm" permutation array */
+   int *perm;                    /* Pointer to individual permutation array */
+   int back;                     /* Considering inverse transformation? */
+   int coord;                    /* Loop counter for coordinates */
+   int icon;                     /* Loop counter for constants */
+   int iend;                     /* Loop ending value */
+   int imap1;                    /* Index of first Mapping */
+   int imap2;                    /* Index of last Mapping */
+   int imap;                     /* Loop counter for Mappings */
+   int inc;                      /* Loop increment */
+   int invert;                   /* Invert attribute value */
+   int istart;                   /* Loop starting value */
+   int maxperm;                  /* Max value (+1) allowed in permutation array */
+   int ncon;                     /* Number of constants */
+   int ncoord_in;                /* Effective number of input coordinates */
+   int ncoord_out;               /* Effective number of output coordinates */
+   int ngone;                    /* Number of Mappings eliminated */
+   int nin;                      /* Total number of input coordinates */
+   int ninsum;                   /* Accumulated count of input coordinates */
+   int nout;                     /* Total number of output coordinates */
+   int noutsum;                  /* Accumulated count of output coordinates */
+   int nperm;                    /* Number of permutation array elements */
+   int p;                        /* Permuted coordinate index */
+   int result;                   /* Result value to return */
+   int simpler;                  /* Mapping(s) simplified? */
+   int store_in;                 /* Need to store "inperm" array contents? */
+   int store_out;                /* Need to store "outperm" array contents? */
+   int unit;                     /* Replacement Mapping is a UnitMap? */
+
+/* Initialise the returned result. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialisation. */
+   con = NULL;
+   inperm = outperm = NULL;
+   ncon = 0;
+   permmap = NULL;
+
+/* In series. */
+/* ---------- */
+/* Handle the case where the Mappings are connected in series. */
+   if ( series ) {
+
+/* Search adjacent lower-numbered Mappings until one is found which is
+   not a PermMap or a UnitMap. */
+      imap1 = where;
+      while ( ( imap1 - 1 ) >= 0 ) {
+         class = astGetClass( ( *map_list )[ imap1 - 1 ] );
+         if ( astOK ) {
+            if ( strcmp( class, "PermMap" ) &&
+                 strcmp( class, "UnitMap" ) ) break;
+            imap1--;
+         }
+      }
+
+/* Similarly search adjacent higher-numbered Mappings. */
+      imap2 = where;
+      while ( ( imap2 + 1 ) < *nmap ) {
+         class = astGetClass( ( *map_list )[ imap2 + 1 ] );
+         if ( astOK ) {
+            if ( strcmp( class, "PermMap" ) &&
+                 strcmp( class, "UnitMap" ) ) break;
+            imap2++;
+         }
+      }
+
+/* Obtain a pointer to the first Mapping found and determine if it is
+   to be applied with its Invert attribute set. */
+      map = ( *map_list )[ imap1 ];
+      invert = ( *invert_list )[ imap1 ];
+
+/* Use this first Mapping (allowing for how its Invert attribute is
+   currently set) to determine the number of input coordinates that
+   the simplified Mapping should have. */
+      if ( astGetInvert( map ) ) {
+         nin = invert ? astGetNin( map ) : astGetNout( map );
+      } else {
+         nin = invert ? astGetNout( map ) : astGetNin( map );
+      }
+
+/* Repeat this process for the last Mapping found, to determine the
+   number of output coordinates for the simplified Mapping. */
+      map = ( *map_list )[ imap2 ];
+      invert = ( *invert_list )[ imap2 ];
+      if ( astGetInvert( map ) ) {
+         nout = invert ? astGetNout( map ) : astGetNin( map );
+      } else {
+         nout = invert ? astGetNin( map ) : astGetNout( map );
+      }
+
+/* Allocate memory to hold input and output permutation arrays for the
+   simplified Mapping, together with a list of constants. */
+      inperm = astMalloc( sizeof( int ) * (size_t) nin );
+      outperm = astMalloc( sizeof( int ) * (size_t) nout );
+      con = astMalloc( sizeof( double ) * (size_t) ( nin + nout ) );
+      if ( astOK ) {
+
+/* Initialise the number of constants. */
+         ncon = 0;
+
+/* Loop twice, to calculate the forward and inverse (backward)
+   simplified permutation arrays in turn. */
+         for ( back = 0; back <= 1; back++ ) {
+
+/* Obtain a pointer to the appropriate (forward/inverse) permutation
+   array that we wish to fill, and obtain the number of elements it
+   will contain. Initialise the array contents to represent a null
+   permutation.*/
+            newperm = back ? outperm : inperm;
+            nperm = back ? nout : nin;
+            for ( coord = 0; coord < nperm; coord++ ) newperm[ coord ] = coord;
+
+/* Set up limits to scan through the list of Mappings being merged in
+   either the forward or reverse order, as required. */
+            istart = back ? imap2 : imap1;
+            iend = back ? imap1 - 1 : imap2 + 1;
+            inc = back ? -1 : 1;
+
+/* Loop through the Mappings, obtaining a pointer to each, together
+   with the value to be used for its Invert attribute. Invert this
+   attribute value if calculating the overall inverse (backward)
+   permutation array. */
+            for ( imap = istart; imap != iend; imap += inc ) {
+               map = ( *map_list )[ imap ];
+               invert = ( *invert_list )[ imap ];
+               if ( back ) invert = !invert;
+
+/* Determine the class to which the Mapping belongs. */
+               class = astGetClass( map );
+               if ( astOK ) {
+
+/* If it is a PermMap, obtain a pointer to the PermMap structure and
+   hence to the relevant permutation array.  Otherwise (if it is a
+   UnitMap), leave the permutation array pointer NULL, which indicates
+   a null permutation. */
+                  perm = NULL;
+                  maxperm = astGetNout( map );
+                  if ( !strcmp( class, "PermMap" ) ) {
+                     permmap = (AstPermMap *) map;
+                     perm = invert ? permmap->outperm : permmap->inperm;
+                  }
+
+/* Obtain the effective number of output coordinates associated with
+   this individual Mapping (when transforming points in the direction
+   to which this permutation array applies). */
+                  if ( astGetInvert( map ) ) {
+                     ncoord_out = invert ? astGetNout( map ) :
+                                           astGetNin( map );
+                  } else {
+                     ncoord_out = invert ? astGetNin( map ) :
+                                           astGetNout( map );
+                  }
+
+/* Loop through the elements of the simplified permutation array to
+   accumulate the effects of the current individual Mapping. */
+                  if ( astOK ) {
+                     for ( coord = 0; coord < nperm; coord++ ) {
+
+/* Find the effective input coordinate for the current Mapping from
+   the permutation accumulated so far, and check this is not
+   negative. If it is, the accumulated permutation refers to a "bad"
+   coordinate value or a constant, so the current Mapping makes no
+   further difference. */
+                        p = newperm[ coord ];
+                        if ( p >= 0 ) {
+
+/* Otherwise, obtain the permuting effect of the current Mapping,
+   allowing for the possibility of its permutation array being NULL
+   (implying a null permutation). */
+                           p = PERMVAL( perm, p, maxperm );
+
+/* If the permuted index refers to a valid (effective) output
+   coordinate for the individual Mapping, then accumulate its effect
+   in the overall permutation array. */
+                           if ( ( p >= 0 ) && ( p < ncoord_out ) ) {
+                              newperm[ coord ] = p;
+
+/* Otherwise (this can only occur if the individual Mapping is a
+   PermMap), determine whether it refers to a "bad" coordinate value
+   or a constant. If the former, extract the constant's value,
+   otherwise use a constant value of AST__BAD. */
+                           } else {
+                              if ( ( p < 0 ) && permmap->constant ) {
+                                 constant = permmap->constant[ (-p) - 1 ];
+                              } else {
+                                 constant = AST__BAD;
+                              }
+
+/* If the result (however reached) is a coordinate value of AST__BAD,
+   then mark the accumulated permutation array with a value of -1 to
+   indicate this. */
+                              if ( constant == AST__BAD ) {
+                                 newperm[ coord ] = -1;
+
+/* Otherwise, search the array of constants to see if this one has
+   been encountered before. If not, append the new constant to the
+   list. */
+                              } else {
+                                 for ( icon = 0; icon < ncon; icon++ ) {
+                                    if ( con[ icon ] == constant ) break;
+                                 }
+                                 if ( icon == ncon ) con[ ncon++ ] = constant;
+
+/* Store a (negative) reference to the new constant in the accumulated
+   permutation array (note we use an extra offset of -1 here in
+   forming these references, so that the value -1 itself can be used
+   to indicate a "bad" coordinate value without an entry in the
+   constants array). */
+                                 newperm[ coord ] = (-icon) - 2;
+                              }
+                           }
+                        }
+                     }
+                  }
+               }
+            }
+         }
+      }
+
+/* In parallel. */
+/* ------------ */
+/* Handle the case where the Mappings are connected in parallel. */
+   } else {
+
+/* Obtain a pointer to the nominated Mapping (which is a PermMap) and
+   determine if it is to be applied with its Invert attribute set. */
+      map = ( *map_list )[ where ];
+      invert = ( *invert_list )[ where ];
+
+/* Use this nominated Mapping to initialise the counts of input and
+   output coordinates for the simplified Mapping (allowing for how its
+   Invert attribute is currently set). */
+      if ( astGetInvert( map ) ) {
+         nin = invert ? astGetNin( map ) : astGetNout( map );
+         nout = invert ? astGetNout( map ) : astGetNin( map );
+      } else {
+         nin = invert ? astGetNout( map ) : astGetNin( map );
+         nout = invert ? astGetNin( map ) : astGetNout( map );
+      }
+
+/* Search adjacent lower-numbered Mappings until one is found which is
+   not a PermMap or a UnitMap. */
+      imap1 = where;
+      while ( astOK && ( ( imap1 - 1 ) >= 0 ) ) {
+         map = ( *map_list )[ imap1 - 1 ];
+         class = astGetClass( map );
+         if ( astOK ) {
+            if ( strcmp( class, "PermMap" ) &&
+                 strcmp( class, "UnitMap" ) ) break;
+
+/* For each Mapping found, obtain the effective numbers of input and
+   output coordinates (allowing for all the direction flags, as above)
+   and accumulate the total count of input and output coordinates for
+   the overall simplified Mapping. */
+            invert = ( *invert_list )[ imap1 - 1 ];
+            if ( astGetInvert( map ) ) {
+               nin += ( invert ? astGetNin( map ) : astGetNout( map ) );
+               nout += ( invert ? astGetNout( map ) : astGetNin( map ) );
+            } else {
+               nin += ( invert ? astGetNout( map ) : astGetNin( map ) );
+               nout += ( invert ? astGetNin( map ) : astGetNout( map ) );
+            }
+            imap1--;
+         }
+      }
+
+/* Similarly search higher-numbered Mappings and accumulate their
+   coordinate counts. */
+      imap2 = where;
+      while ( astOK && ( ( imap2 + 1 ) < *nmap ) ) {
+         map = ( *map_list )[ imap2 + 1 ];
+         class = astGetClass( map );
+         if ( astOK ) {
+            if ( strcmp( class, "PermMap" ) &&
+                 strcmp( class, "UnitMap" ) ) break;
+            invert = ( *invert_list )[ imap2 + 1 ];
+            if ( astGetInvert( map ) ) {
+               nin += ( invert ? astGetNin( map ) : astGetNout( map ) );
+               nout += ( invert ? astGetNout( map ) : astGetNin( map ) );
+            } else {
+               nin += ( invert ? astGetNout( map ) : astGetNin( map ) );
+               nout += ( invert ? astGetNin( map ) : astGetNout( map ) );
+            }
+            imap2++;
+         }
+      }
+
+/* Allocate memory to hold input and output permutation arrays for the
+   simplified Mapping, together with a list of constants. */
+      inperm = astMalloc( sizeof( int ) * (size_t) nin );
+      outperm = astMalloc( sizeof( int ) * (size_t) nout );
+      con = astMalloc( sizeof( double ) * (size_t) ( nin + nout ) );
+      if ( astOK ) {
+
+/* Initialise the number of constants. */
+         ncon = 0;
+
+/* Loop twice, to calculate the forward and inverse (backward)
+   simplified permutation arrays in turn. */
+         for ( back = 0; back <= 1; back++ ) {
+
+/* Obtain a pointer to the appropriate (forward/inverse) permutation
+   array that we wish to fill, and obtain the number of elements it
+   will contain. */
+            newperm = back ? outperm : inperm;
+            nperm = back ? nout : nin;
+
+/* Initialise counts of (effective) input and output coordinates. */
+            ninsum = noutsum = 0;
+
+/* Loop through the Mappings, obtaining a pointer to each, together
+   with the value to be used for its Invert attribute.  Invert this
+   attribute value if calculating the overall inverse (backward)
+   permutation array. */
+            for ( imap = imap1; imap <= imap2; imap++ ) {
+               map = ( *map_list )[ imap ];
+               invert = ( *invert_list )[ imap ];
+               if ( back ) invert = !invert;
+
+/* Determine the class to which the Mapping belongs. */
+               class = astGetClass( map );
+               if ( astOK ) {
+
+/* If it is a PermMap, obtain a pointer to the PermMap structure and
+   hence to the relevant permutation array.  Otherwise (if it is a
+   UnitMap), leave the permutation array pointer NULL, which indicates
+   a null permutation. */
+                  perm = NULL;
+                  maxperm = astGetNout( map );
+                  if ( !strcmp( class, "PermMap" ) ) {
+                     permmap = (AstPermMap *) map;
+                     perm = invert ? permmap->outperm : permmap->inperm;
+                  }
+
+/* Obtain the effective number of input and output coordinates
+   associated with this individual Mapping (when transforming points
+   in the direction to which this permutation array applies). */
+                  if ( astGetInvert( map ) ) {
+                     ncoord_in = invert ? astGetNin( map ) :
+                                          astGetNout( map );
+                     ncoord_out = invert ? astGetNout( map ) :
+                                           astGetNin( map );
+                  } else {
+                     ncoord_in = invert ? astGetNout( map ) :
+                                          astGetNin( map );
+                     ncoord_out = invert ? astGetNin( map ) :
+                                           astGetNout( map );
+                  }
+
+/* Loop through the (effective) input coordinates of the current
+   individual Mapping to accumulate their effect on the overall
+   permutation array. */
+                  if ( astOK ) {
+                     for ( coord = 0; coord < ncoord_in; coord++ ) {
+
+/* Obtain the permuting effect of the current Mapping, allowing for
+   the possibility of its permutation array being NULL. */
+                        p = PERMVAL( perm, coord, maxperm );
+
+/* If the permuted index refers to a valid (effective) output
+   coordinate for the individual Mapping, then accumulate its effect
+   on the overall permutation array, allowing for the coordinate
+   numbering offset produced by any Mappings already accumulated. */
+                        if ( ( p >= 0 ) && ( p < ncoord_out ) ) {
+                           newperm[ coord + ninsum ] = p + noutsum;
+
+/* Otherwise (this can only occur if the individual Mapping is a
+   PermMap), determine whether it refers to a "bad" coordinate value
+   or a constant. If the former, extract the constant's value,
+   otherwise use a constant value of AST__BAD. */
+                        } else {
+                           if ( ( p < 0 ) && permmap->constant ) {
+                              constant = permmap->constant[ (-p) - 1 ];
+                           } else {
+                              constant = AST__BAD;
+                           }
+
+/* If the result (however reached) is a coordinate value of AST__BAD,
+   then mark the accumulated permutation array with a value of -1 to
+   indicate this. */
+                           if ( constant == AST__BAD ) {
+                              newperm[ coord + ninsum ] = -1;
+
+/* Otherwise, search the array of constants to see if this one has
+   been encountered before. If not, append the new constant to the
+   list. */
+                           } else {
+                              int icon;
+                              for ( icon = 0; icon < ncon; icon++ ) {
+                                 if ( con[ icon ] == constant ) break;
+                              }
+                              if ( icon == ncon ) con[ ncon++ ] = constant;
+
+/* Store a (negative) reference to the new constant in the accumulated
+   permutation array (note we use an extra offset of -1 here in
+   forming these references, so that the value -1 itself can be used
+   to indicate a "bad" coordinate value without an entry in the
+   constants array). */
+                              newperm[ coord + ninsum ] = (-icon) - 2;
+                           }
+                        }
+                     }
+                  }
+
+/* Accumulate the counts of (effective) input and output coordinates
+   for each individual Mapping. */
+                  ninsum += ncoord_in;
+                  noutsum += ncoord_out;
+               }
+            }
+         }
+      }
+   }
+
+/* Inspect each element of the accumulated "inperm" array to determine
+   if it needs to be stored by the replacement PermMap. */
+   if ( astOK ) {
+      store_in = 0;
+      for ( coord = 0; coord < nin; coord++ ) {
+
+/* It need not be stored if it produces a null permutation, where each
+   input coordinate takes its value from the corresponding output
+   coordinate (or where a "bad" value results if there is no
+   corresponding output coordinate). Note any deviation from this
+   pattern. */
+         if ( coord < nout ) {
+            store_in = store_in || ( inperm[ coord ] != coord );
+         } else {
+            store_in = store_in || ( inperm[ coord ] != -1 );
+         }
+
+/* Also convert permutation array values of -1 into non-existent
+   positive coordinate indices (indicating "bad" coordinate values)
+   and adjust (negative) references to constants by +1 to eliminate
+   the extra offset of -1 used temporarily above. This returns the
+   permutation array values to normal. */
+         if ( inperm[ coord ] < 0 ) {
+            if ( !++inperm[ coord ] ) inperm[ coord ] = nout;
+         }
+      }
+
+/* Similarly inspect the "outperm" array and return its values to
+   normal. */
+      store_out = 0;
+      for ( coord = 0; coord < nout; coord++ ) {
+         if ( coord < nin ) {
+            store_out = store_out || ( outperm[ coord ] != coord );
+         } else {
+            store_out = store_out || ( outperm[ coord ] != -1 );
+         }
+         if ( outperm[ coord ] < 0 ) {
+            if ( !++outperm[ coord ] ) outperm[ coord ] = nin;
+         }
+      }
+
+/* Determine how many adjacent Mappings can be eliminated by merging
+   them. */
+      ngone = imap2 - imap1;
+
+/* Determine if the resultant PermMap can be simplified still further
+   to become a UnitMap (a null Mapping). This will be the case if both
+   the forward and inverse coordinate permutations it produces are
+   null, and if the number of input and output coordinates are
+   equal. */
+      unit = !store_in && !store_out && ( nin == nout );
+
+/* We must now determine whether we have actually produced any
+   simplification. This is important, because if we indicate a
+   simplification when none has, in fact, been achieved, then this
+   function may get called over and over again without end. */
+
+/* Simplification is clearly evident if (a) Mappings have been
+   eliminated ("ngone" is non-zero), or (b) a PermMap has been reduced
+   to a UnitMap, or (c) where there was originally only one PermMap to
+   simplify, its invert flag was set (the replacement Mapping will
+   always have this flag cleared). */
+      simpler = ngone || unit || ( *invert_list )[ where ];
+
+/* If the above tests do not indicate simplification, then we can only
+   be considering the case where there was a single initial
+   PermMap. In this case we have also achieved simplification if
+   either the "inperm" or "outperm" array no longer needs storing
+   whereas previously it was stored. */
+      permmap = (AstPermMap *) ( *map_list )[ where ];
+      if ( !simpler ) {
+         simpler = ( !store_in && !NullPerm( permmap, 0, status ) ) ||
+                   ( !store_out && !NullPerm( permmap, 1, status ) );
+      }
+
+/* If we still haven't detected any simplification, then compare the
+   original and replacement "inperm" arrays (if present) in detail for
+   equality.  We declare simplification to have occurred if they
+   differ. */
+      if ( !simpler && store_in ) {
+         for ( coord = 0; coord < nin; coord++ ) {
+            simpler = ( inperm[ coord ] != permmap->inperm[ coord ] );
+            if ( simpler ) break;
+         }
+      }
+
+/* Similarly, if necessary, compare the original and replacement
+   "outperm" arrays. */
+      if ( !simpler && store_out ) {
+         for ( coord = 0; coord < nout; coord++ ) {
+            simpler = ( outperm[ coord ] != permmap->outperm[ coord ] );
+            if ( simpler ) break;
+         }
+      }
+
+/* Do nothing more unless there has been some simplification. */
+      if ( simpler ) {
+
+/* If the PermMaps (and UnitMaps) can be replaced by a UnitMap, then
+   create the replacement. */
+         if ( unit ) {
+            new = (AstMapping *) astUnitMap( nin, "", status );
+
+/* Otherwise, create a replacement PermMap, setting as many arguments
+   to NULL in the constructor function as can be achieved without
+   affecting the result. */
+         } else {
+            new = (AstMapping *) astPermMap( nin, store_in ? inperm : NULL,
+                                             nout, store_out ? outperm : NULL,
+                                             ncon ? con : NULL, "", status );
+         }
+
+/* Annul the pointers to all the Mappings that are being replaced. */
+         if ( astOK ) {
+            for ( imap = imap1; imap <= imap2; imap++ ) {
+               ( *map_list )[ imap ] = astAnnul( ( *map_list )[ imap ] );
+            }
+
+/* Insert the new pointer and the associated invert flag. */
+            ( *map_list )[ imap1 ] = new;
+            ( *invert_list )[ imap1 ] = 0;
+
+/* Loop to close the resulting gap by moving subsequent elements down
+   in the arrays. */
+            for ( imap = imap2 + 1; imap < *nmap; imap++ ) {
+               ( *map_list )[ imap - ngone ] = ( *map_list )[ imap ];
+               ( *invert_list )[ imap - ngone ] = ( *invert_list )[ imap ];
+            }
+
+/* Clear the vacated elements at the end. */
+            for ( imap = *nmap - ngone; imap < *nmap; imap++ ) {
+               ( *map_list )[ imap ] = NULL;
+               ( *invert_list )[ imap ] = 0;
+            }
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+            ( *nmap ) -= ngone;
+            result = imap1;
+         }
+      }
+   }
+
+/* Free the workspace arrays. */
+   inperm = astFree( inperm );
+   outperm = astFree( outperm );
+   con = astFree( con );
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     PermMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing PermMap. This is only possible if the specified inputs
+*     correspond to some subset of the PermMap outputs. That is, there
+*     must exist a subset of the PermMap outputs for which each output
+*     depends only on the selected PermMap inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied PermMap, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the PermMap to be split (the PermMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied PermMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied PermMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied PermMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstPermMap *this;          /* Pointer to PermMap structure */
+   double *con;               /* Pointer to constants array */
+   int *inp;                  /* Input perm array to use with supplied PermMap */
+   int *inpm;                 /* Input perm array to use with new PermMap */
+   int *outp;                 /* Output perm array to use with supplied PermMap */
+   int *outpm;                /* Output perm array to use with new PermMap */
+   int *result;               /* Pointer to returned array */
+   int i;                     /* Loop count */
+   int iin;                   /* Mapping input index */
+   int iout;                  /* Output index */
+   int j;                     /* Loop count */
+   int nout;                  /* No. of outputs in the new PermMap */
+   int npin;                  /* No. of inputs in the supplied Mapping */
+   int npout;                 /* No. of outputs in the supplied Mapping */
+   int ok;                    /* Are input indices OK? */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+
+/* Get a pointer to the PermMap structure. */
+   this = (AstPermMap *) this_map;
+
+/* Get the number of inputs and outputs in the supplied PermMap. */
+   npin = astGetNin( this );
+   npout = astGetNout( this );
+
+/* Check all input axis indices are valid. */
+   ok = 1;
+   for( i = 0; i < nin; i++ ) {
+      if( in[ i ] < 0 || in[ i ] >= npin ) {
+         ok = 0;
+         break;
+      }
+   }
+
+/* Get pointers to the input and output permutation arrays and constant
+   array taking account of whether the PermMap has been inverted. */
+   if( astGetInvert( this ) ) {
+      outp = this->inperm;
+      inp = this->outperm;
+   } else {
+      outp = this->outperm;
+      inp = this->inperm;
+   }
+   con = this->constant;
+
+/* The "normal" method, as described in the prologue. */
+   if( astGetPermSplit( this ) == 0 ) {
+
+/* Allocate memory for the returned array of output indices. */
+      result = astMalloc( sizeof( int )*(size_t) npout );
+
+/* Allocate memory for the inperm and outperm arrays of the returned
+   PermMap. Make these the largest they could possible need to be. */
+      inpm = astMalloc( sizeof( int )*(size_t) npin );
+      outpm = astMalloc( sizeof( int )*(size_t) npout );
+      if( astOK ) {
+
+/* Initialise number of outputs in returned PermMap. */
+         nout = 0;
+
+/* Loop round each output of the supplied PermMap. */
+         for( iout = 0; iout < npout; iout++ ) {
+
+/* Is this output fed by one of the selected inputs? If so store the input
+   index of the returned Mapping, which feeds this output and add this
+   output index to the list of returned outputs. */
+            iin = PERMVAL( outp, iout, npin );
+            if( iin >= 0 && iin < npin ) {
+               for( i = 0; i < nin; i++ ) {
+                  if( in[ i ] == iin ) {
+                     outpm[ nout ] = i;
+                     result[ nout ] = iout;
+                     nout++;
+                     break;
+                  }
+               }
+            }
+         }
+
+/* We now need to set up the inperm array for the returned PermMap. This
+   ensures that the inverse transformation in the returned Mapping provides
+   values for the selected inputs. Loop round all the selected inputs. */
+         for( i = 0; i < nin; i++ ) {
+            iin = in[ i ];
+
+/* Is this input constant or fed by one of the selected outputs? If so store
+   the output or constant index in the returned Mapping which feeds this
+   input. */
+            ok = 0;
+            iout = PERMVAL( inp, iin, npout );
+            if( iout >= 0 && iout < npout ) {
+               for( j = 0; j < nout; j++ ) {
+                  if( result[ j ] == iout ) {
+                     ok = 1;
+                     inpm[ i ] = j;
+                     break;
+                  }
+               }
+            } else {
+               inpm[ i ] = iout;
+               ok = 1;
+            }
+
+/* If this input is fed by an output which has not been selected, then we
+   cannot produce the required Mapping. */
+            if( !ok ) break;
+         }
+
+/* If possible produce the returned PermMap. Otherwise, free the returned
+   array. */
+         if( ok && nout > 0 ) {
+            *map = (AstMapping *) astPermMap( nin, inpm, nout, outpm, con, "", status );
+         } else {
+            result = astFree( result );
+         }
+
+/* Free other resources. */
+         inpm = astFree( inpm );
+         outpm = astFree( outpm );
+      }
+
+/* The "alternative" method. Only the inperm array is used - the outperm
+   array is assumed to be an exact inverse of the inperm array. In other
+   words, only the inverse transformation is used, and the forward
+   transformation is assumed to be the exact opposite. */
+   } else {
+
+/* The returned array of output indices holds the "inperm" values for the
+   selected inputs. */
+      result = astMalloc( sizeof( int )*(size_t) nin );
+      if( astOK ) {
+         for( i = 0; i < nin; i++ ) {
+            result[ i ] = PERMVAL( inp, in[ i ], npout );
+
+/* Check the input is not fed by a constant. */
+            if( result[ i ] < 0 ) {
+               result = astFree( result );
+               break;
+
+/* Check that the the output has not already been used. */
+            } else {
+               for( j = 0; j < i; j++ ) {
+                  if( result[ j ] == result[ i ] ) {
+                     result = astFree( result );
+                     break;
+                  }
+               }
+            }
+         }
+
+/* If the split was possible, the returned Mapping is a UnitMap. */
+         if( result ) *map = (AstMapping *) astUnitMap( nin, " ", status );
+      }
+   }
+
+/* If the returned Mapping has no outputs, do not return it. */
+   if( !result && *map ) {
+      *map = astAnnul( *map );
+   }
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static int NullPerm( AstPermMap *this, int forward, int *status ){
+/*
+*  Name:
+*     NullPerm
+
+*  Purpose:
+*     See if a PermMap transformation represents a null axis permutation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     int NullPerm( AstPermMap *this, int forward, int *status )
+
+*  Class Membership:
+*     PermMap method
+
+*  Description:
+*     This function returns a logical value indicating if the specified
+*     transformation of the supplied PermMap is a null (i.e. unit)
+*     transformation.
+
+*  Parameters:
+*     this
+*        Pointer to the PermMap.
+*     forward
+*        Check the forward transformation? Otherise, check the inverse
+*        transformation.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the specified transformation is a null axis permutation.
+*     Zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   int i;                     /* Coordinate index */
+   int nin;                   /* Number of Mapping inputs */
+   int nout;                  /* Number of Mapping outputs */
+   int result;                /* Returned value */
+
+/* Initialise the returned result. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* First check the forward transformation, given by the outperm array. */
+   if( forward ) {
+
+/* If no outperm array is stored, every output is derived from the
+   corresponding input. Therefore, return 1 indicating a null axis
+   permutation. */
+      if( !this->outperm ) {
+         result = 1;
+
+/* Otherwise, check that every element in the outperm array indicates
+   that the output is derived from the input with the saem index. */
+      } else {
+         result = 1;
+         nout = astGetNout( this );
+         for( i = 0; i < nout; i++ ) {
+            if( this->outperm[ i ] != i ) {
+               result = 0;
+               break;
+            }
+         }
+      }
+
+/* Now check the inverse transformation, given by the inperm array. */
+   } else {
+
+/* If no inperm array is stored, every input is derived from the
+   corresponding output. Therefore, return 1 indicating a null axis
+   permutation. */
+      if( !this->inperm ) {
+         result = 1;
+
+/* Otherwise, check that every element in the inperm array indicates
+   that the input is derived from the output with the same index. */
+      } else {
+         result = 1;
+         nin = astGetNin( this );
+         for( i = 0; i < nin; i++ ) {
+            if( this->inperm[ i ] != i ) {
+               result = 0;
+               break;
+            }
+         }
+      }
+   }
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     PermMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*/
+
+/* Local Variables: */
+   AstPermMap *map;
+   int *outperm;
+   int result;
+
+/* Check inherited status */
+   if( !astOK ) return AST__BAD;
+
+/* Get a pointer to the PermMap structure. */
+   map = (AstPermMap *) this;
+
+/* Obtain a pointer to the appropriate output coordinate permutation array,
+   according to whether the PermMap has been inverted. If the specified
+   output is derived from the specified input then the rate is unity.
+   Otherwise it is zero. */
+   outperm = astGetInvert( this ) ? map->inperm : map->outperm;
+   if( outperm ) {
+      result = ( ax2 == outperm[ ax1 ] ) ? 1.0 : 0.0;
+   } else {
+      result = ( ax2 == ax1 ) ? 1.0 : 0.0;
+   }
+
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *map, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a PermMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     AstPointSet *Transform( AstMapping *map, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     PermMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a PermMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required coordinate
+*     permutation.
+
+*  Parameters:
+*     map
+*        Pointer to the PermMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the PermMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPermMap *this;             /* Pointer to PermMap to be applied */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double constant;              /* Constant coordinate value */
+   int *perm;                    /* Pointer to permutation array */
+   int coord;                    /* Loop counter for coordinates */
+   int maxperm;                  /* Max value in permutation array */
+   int ncoord_in;                /* Number of coordinates per input point */
+   int ncoord_out;               /* Number of coordinates per output point */
+   int npoint;                   /* Number of points */
+   int p;                        /* Permuted coordinate index */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the PermMap. */
+   this = (AstPermMap *) map;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( map, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   permutation needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   and output PointSets and obtain pointers for accessing the input and output
+   coordinate values. */
+   ncoord_in = astGetNcoord( in );
+   ncoord_out = astGetNcoord( result );
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Obtain a pointer to the appropriate coordinate permutation array, according
+   to the direction of transformation required and whether the PermMap has
+   been inverted. Also get the maximum allowed value in the permutation array. */
+   if ( ( astGetInvert( this ) != 0 ) == ( forward != 0 ) ) {
+      perm = this->inperm;
+      maxperm = ncoord_out;
+   } else {
+      perm = this->outperm;
+      maxperm = ncoord_in;
+   }
+
+/* Perform coordinate permutation. */
+/* ------------------------------- */
+   if ( astOK ) {
+
+/* Loop to generate values for each output coordinate. */
+      for ( coord = 0; coord < ncoord_out; coord++ ) {
+
+/* If the permutation array is not NULL, use it to look up which input
+   coordinate to use. Otherwise, use the corresponding input coordinate. */
+         p = PERMVAL( perm, coord, maxperm );
+
+/* If a valid input coordinate has been identified, simply copy the required
+   coordinate values from input to output. */
+         if ( ( p >= 0 ) && ( p < ncoord_in ) ) {
+            (void) memcpy( ptr_out[ coord ], ptr_in[ p ],
+                           sizeof( double ) * (size_t) npoint );
+
+/* If the permuted coordinate index is negative, use it to index the "constant"
+   array to obtain a constant value to assign. If this array is NULL, use
+   AST__BAD as the constant. */
+	 } else if ( p < 0 ) {
+            constant = this->constant ? this->constant[ (-p) - 1 ] : AST__BAD;
+
+/* Assign the constant value to the output coordinate for all points. */
+            for ( point = 0; point < npoint; point++ ) {
+               ptr_out[ coord ][ point ] = constant;
+	    }
+
+/* In all other cases, simply assign the value AST__BAD to the output
+   coordinate for all points. */
+	 } else {
+            for ( point = 0; point < npoint; point++ ) {
+               ptr_out[ coord ][ point ] = AST__BAD;
+	    }
+	 }
+      }
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/*
+*att+
+*  Name:
+*     PermSplit
+
+*  Purpose:
+*     The method to use when splitting a PermMap using astMapSplit.
+
+*  Type:
+*     Protected attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute controls the behaviour of the implementation of the
+*     astMapSplit method provided by the PermMap class. If set to zero (the
+*     default), astMapSplit will split PermMaps according to the public
+*     documentation for the method. If set non-zero, the forward transformation
+*     of the PermMap defined by the "outperm" array will be ignored.
+*     Instead, the forward transformation is assumed to be the exact
+*     inverse of the inverse transformation. The Mapping returned will
+*     then be a UnitMap with Nin equal to the number of picked inputs,
+*     and the returned array of output indices will hold the "inperm"
+*     values for the picked inputs. Note, if any of these "inperm" values
+*     are negative (indicating that the inverse transformation supplies a
+*     constant value for the input), or if more than one of the selected
+*     inputs are fed (by the inverse transformation) by the same output,
+*     then the PermMap cannot be split.
+*
+*     Note, unlike most Mapping attributes, the value of this attribute
+*     may be changed at any time. This is because it does not change the
+*     nature of either the forward or inverse transformation of the Mapping.
+
+*  Applicability:
+*     PermMap
+*        All PermMaps have this attribute.
+*att-
+*/
+astMAKE_CLEAR(PermMap,PermSplit,permsplit,-INT_MAX)
+astMAKE_GET(PermMap,PermSplit,int,0,( this->permsplit != -INT_MAX ?
+                                      this->permsplit : 0 ))
+astMAKE_SET(PermMap,PermSplit,int,permsplit,( value != 0 ))
+astMAKE_TEST(PermMap,PermSplit,( this->permsplit != -INT_MAX ))
+
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for PermMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for PermMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstPermMap *in;               /* Pointer to input PermMap */
+   AstPermMap *out;              /* Pointer to output PermMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output PermMaps. */
+   in = (AstPermMap *) objin;
+   out = (AstPermMap *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output PermMap. */
+   out->inperm = NULL;
+   out->outperm = NULL;
+   out->constant = NULL;
+
+/* For each input array which is not NULL, make a copy in allocated memory,
+   storing a pointer to it in the output PermMap structure. */
+   if ( in->inperm ) out->inperm = astStore( NULL, in->inperm,
+                                             astSizeOf( in->inperm ) );
+   if ( in->outperm ) out->outperm = astStore( NULL, in->outperm,
+                                               astSizeOf( in->outperm ) );
+   if ( in->constant ) out->constant = astStore( NULL, in->constant,
+                                                 astSizeOf( in->constant ) );
+
+/* If an error occurred, clean up by freeing all memory allocated above. */
+   if ( !astOK ) {
+      out->inperm = astFree( out->inperm );
+      out->outperm = astFree( out->outperm );
+      out->constant = astFree( out->constant );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for PermMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for PermMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstPermMap *this;             /* Pointer to PermMap */
+
+/* Obtain a pointer to the PermMap structure. */
+   this = (AstPermMap *) obj;
+
+/* Free all memory allocated by the PermMap. */
+   this->inperm = astFree( this->inperm );
+   this->outperm = astFree( this->outperm );
+   this->constant = astFree( this->constant );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for PermMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the PermMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the PermMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define COMMENT_LEN 150          /* Maximum length of a comment string */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstPermMap *this;             /* Pointer to the PermMap structure */
+   char comment[ COMMENT_LEN + 1 ]; /* Buffer for comment strings */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword strings */
+   int coord;                    /* Loop counter for coordinates */
+   int iconst;                   /* Loop counter for constants */
+   int invert;                   /* Invert attribute value */
+   int ival;                     /* Integer value */
+   int nconst;                   /* Number of constants */
+   int nin;                      /* Number of input coordinates */
+   int nout;                     /* Number of output coordinates */
+   int set;                      /* Value is "set"? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PermMap structure. */
+   this = (AstPermMap *) this_object;
+
+/* Determine if the PermMap is inverted and obtain the "true" number
+   of input and output coordinates by un-doing the effects of any
+   inversion. */
+   invert = astGetInvert( this );
+   nin = !invert ? astGetNin( this ) : astGetNout( this );
+   nout = !invert ? astGetNout( this ) : astGetNin( this );
+
+/* Initialise the count of constants in use. */
+   nconst = 0;
+
+/* Write out values representing the instance variables for the
+   PermMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* PermSplit */
+/* --------- */
+   set = TestPermSplit( this, status );
+   ival = set ? GetPermSplit( this, status ) : astGetPermSplit( this );
+   astWriteInt( channel, "pmsplt", set, 0, ival,
+                ival ? "Use alternative astMapSplit implementation" :
+                       "Use normal astMapSplit implementation" );
+
+/* "outperm" array contents. */
+/* ------------------------- */
+/* Write the boolean "OutCpy" value to indicate if output coordinates
+   are obtained simply by copying corresponding input
+   coordinates. This will be the case if "this->outperm" is NULL. */
+   ival = this->outperm ? 0 : 1;
+   set = ( ival != 0 );
+   astWriteInt( channel, "OutCpy", set, 0, ival,
+                ival ? "Output coordinates = input coordinates" :
+                       "Output coordinates specified individually" );
+
+/* If output coordinates are specified individually, create a keyword
+   for each element of the "outperm" array. */
+   if ( this->outperm ) {
+      for ( coord = 0; coord < nout; coord++ ) {
+         (void) sprintf( key, "Out%d", coord + 1 );
+
+/* Obtain the array value. If it refers to a coordinate that does not
+   exist, change the value to zero (indicating a "bad" value for this
+   coordinate). Create an appropriate comment. */
+         ival = this->outperm[ coord ];
+         if ( ival >= nin ) {
+            ival = 0;
+            (void) sprintf( comment, "Output coordinate %d is \"bad\"",
+                            coord + 1 );
+
+/* If the coordinate reference is valid, convert to 1-based coordinate
+   numbering and create an appropriate comment. */
+         } else if ( ival >= 0 ) {
+            ival++;
+            (void) sprintf( comment,
+                            "Output coordinate %d = input coordinate %d",
+                            coord + 1, ival );
+
+/* If the reference is to a constant, create an appropriate comment
+   (which depends on whether there are any constants). */
+         } else {
+            if ( this->constant ) {
+               (void) sprintf( comment,
+                               "Output coordinate %d = constant no. %d",
+                               coord + 1, -ival );
+            } else {
+               (void) sprintf( comment, "Output coordinate %d is \"bad\"",
+                               coord + 1 );
+            }
+
+/* Update the top constant number referenced. */
+            if ( nconst < -ival ) nconst = -ival;
+         }
+
+/* Write out the array value with accompanying comment. */
+         astWriteInt( channel, key, 1, 1, ival, comment );
+      }
+   }
+
+/* "inperm" array contents. */
+/* ------------------------ */
+/* Write the boolean "InCpy" value to indicate if input coordinates
+   are obtained simply by copying corresponding output
+   coordinates. This will be the case if "this->inperm" is NULL. */
+   ival = this->inperm ? 0 : 1;
+   set = ( ival != 0 );
+   astWriteInt( channel, "InCpy", set, 0, ival,
+                ival ? "Input coordinates = output coordinates" :
+                       "Input coordinates specified individually" );
+
+/* If input coordinates are specified individually, create a keyword
+   for each element of the "inperm" array. */
+   if ( this->inperm ) {
+      for ( coord = 0; coord < nin; coord++ ) {
+         (void) sprintf( key, "In%d", coord + 1 );
+
+/* Obtain the array value. If it refers to a coordinate that does not
+   exist, change the value to zero (indicating a "bad" value for this
+   coordinate). Create an appropriate comment. */
+         ival = this->inperm[ coord ];
+         if ( ival >= nout ) {
+            ival = 0;
+            (void) sprintf( comment, "Input coordinate %d is \"bad\"",
+                            coord + 1 );
+
+/* If the coordinate reference is valid, convert to 1-based coordinate
+   numbering and create an appropriate comment. */
+         } else if ( ival >= 0 ) {
+            ival++;
+            (void) sprintf( comment,
+                            "Input coordinate %d = output coordinate %d",
+                            coord + 1, ival );
+
+/* If the reference is to a constant, create an appropriate comment
+   (which depends on whether there are any constants). */
+         } else {
+            if ( this->constant ) {
+               (void) sprintf( comment,
+                               "Input coordinate %d = constant no. %d",
+                               coord + 1, -ival );
+            } else {
+               (void) sprintf( comment, "Input coordinate %d is \"bad\"",
+                               coord + 1 );
+            }
+
+/* Update the top constant number referenced. */
+            if ( nconst < -ival ) nconst = -ival;
+         }
+
+/* Write out the array value with accompanying comment. */
+         astWriteInt( channel, key, 1, 1, ival, comment );
+      }
+   }
+
+/* Number of constants. */
+/* -------------------- */
+/* First check if there are any constants, then write out how many
+   there are. */
+   if ( !this->constant ) nconst = 0;
+   set = ( nconst != 0 );
+   astWriteInt( channel, "Nconst", set, 0, nconst, "Number of constants" );
+
+/* Constants. */
+/* ---------- */
+/* Loop to create a keyword and comment for each constant. */
+   for ( iconst = 0; iconst < nconst; iconst++ ) {
+      (void) sprintf( key, "Con%d", iconst + 1 );
+      (void) sprintf( comment, "Constant number %d", iconst + 1 );
+
+/* Write out each constant value and comment. */
+      set = ( this->constant[ iconst ] != AST__BAD );
+      if ( set ) {
+         astWriteDouble( channel, key, 1, 1, this->constant[ iconst ],
+                         comment );
+      } else {
+         astWriteString( channel, key, 0, 1, "<bad>", comment );
+      }
+   }
+
+/* Undefine macros local to this function. */
+#undef COMMENT_LEN
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAPermMap and astCheckPermMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(PermMap,Mapping)
+astMAKE_CHECK(PermMap)
+
+AstPermMap *astPermMap_( int nin, const int inperm[], int nout,
+                         const int outperm[], const double constant[],
+                         const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astPermMap
+f     AST_PERMMAP
+
+*  Purpose:
+*     Create a PermMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "permmap.h"
+c     AstPermMap *astPermMap( int nin, const int inperm[], int nout,
+c                             const int outperm[], double constant[],
+c                             const char *options, ... )
+f     RESULT = AST_PERMMAP( NIN, INPERM, NOUT, OUTPERM, CONSTANT, OPTIONS,
+f                           STATUS )
+
+*  Class Membership:
+*     PermMap constructor.
+
+*  Description:
+*     This function creates a new PermMap and optionally initialises its
+*     attributes.
+*
+*     A PermMap is a Mapping which permutes the order of coordinates,
+*     and possibly also changes the number of coordinates, between its
+*     input and output.
+*
+*     In addition to permuting the coordinate order, a PermMap may
+*     also assign constant values to coordinates. This is useful when
+*     the number of coordinates is being increased as it allows fixed
+*     values to be assigned to any new ones.
+
+*  Parameters:
+c     nin
+f     NIN = INTEGER (Given)
+*        The number of input coordinates.
+c     inperm
+f     INPERM = INTEGER( NIN ) (Given)
+c        An optional array with "nin" elements which, for each input
+f        An array which, for each input
+*        coordinate, should contain the number of the output
+*        coordinate whose value is to be used (note that this array
+*        therefore defines the inverse coordinate transformation).
+*        Coordinates are numbered starting from 1.
+*
+*        For details of additional special values that may be used in
+c        this array, see the description of the "constant" parameter.
+f        this array, see the description of the CONSTANT argument.
+c
+c        If a NULL pointer is supplied instead of an array, each input
+c        coordinate will obtain its value from the corresponding
+c        output coordinate (or will be assigned the value AST__BAD if
+c        there is no corresponding output coordinate).
+c     nout
+f     NOUT = INTEGER (Given)
+*        The number of output coordinates.
+c     outperm
+f     OUTPERM = INTEGER( NOUT ) (Given)
+c        An optional array with "nout" elements which, for each output
+f        An array which, for each output
+*        coordinate, should contain the number of the input coordinate
+*        whose value is to be used (note that this array therefore
+*        defines the forward coordinate transformation).  Coordinates
+*        are numbered starting from 1.
+*
+*        For details of additional special values that may be used in
+c        this array, see the description of the "constant" parameter.
+f        this array, see the description of the CONSTANT argument.
+c
+c        If a NULL pointer is supplied instead of an array, each output
+c        coordinate will obtain its value from the corresponding
+c        input coordinate (or will be assigned the value AST__BAD if
+c        there is no corresponding input coordinate).
+c     constant
+f     CONSTANT = DOUBLE PRECISION( * ) (Given)
+c        An optional array containing values which may be assigned to
+f        An array containing values which may be assigned to
+*        input and/or output coordinates instead of deriving them
+c        from other coordinate values. If either of the "inperm" or
+f        from other coordinate values. If either of the INPERM or
+c        "outperm" arrays contains a negative value, it is used to
+f        OUTPERM arrays contains a negative value, it is used to
+c        address this "constant" array (such that -1 addresses the
+f        address this CONSTANT array (such that -1 addresses the
+*        first element, -2 addresses the second element, etc.) and the
+*        value obtained is used as the corresponding coordinate value.
+*
+*        Care should be taken to ensure that locations lying outside
+*        the extent of this array are not accidentally addressed. The
+c        array is not used if the "inperm" and "outperm" arrays do not
+f        array is not used if the INPERM and OUTPERM arrays do not
+*        contain negative values.
+c
+c        If a NULL pointer is supplied instead of an array, the
+c        behaviour is as if the array were of infinite length and
+c        filled with the value AST__BAD.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new PermMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new PermMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astPermMap()
+f     AST_PERMMAP = INTEGER
+*        A pointer to the new PermMap.
+
+*  Notes:
+c     - If either of the "inperm" or "outperm" arrays contains a
+f     - If either of the INPERM or OUTPERM arrays contains a
+*     zero value (or a positive value which does not identify a valid
+*     output/input coordinate, as appropriate), then the value
+*     AST__BAD is assigned as the new coordinate value.
+*     - This function does not attempt to ensure that the forward and
+*     inverse transformations performed by the PermMap are
+*     self-consistent in any way. You are therefore free to supply
+*     coordinate permutation arrays that achieve whatever effect is
+*     desired.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPermMap *new;              /* Pointer to new PermMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the PermMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPermMap( NULL, sizeof( AstPermMap ), !class_init, &class_vtab,
+                         "PermMap", nin, inperm, nout, outperm, constant );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new PermMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new PermMap. */
+   return new;
+}
+
+AstPermMap *astPermMapId_( int nin, const int inperm[], int nout,
+                           const int outperm[], const double constant[],
+                           const char *options, ... ) {
+/*
+*  Name:
+*     astPermMapId_
+
+*  Purpose:
+*     Create a PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     AstPermMap *astPermMapId_( int nin, const int inperm[], int nout,
+*                                const int outperm[], const double constant[],
+*                                const char *options, ... )
+
+*  Class Membership:
+*     PermMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astPermMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astPermMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     This function also converts the coordinate numbering in the
+*     permutation arrays from 1-based (used externally) to zero-based
+*     (used internally).
+*
+*     The variable argument list also prevents this function from
+*     invoking astPermMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astPermMap_.
+
+*  Returned Value:
+*     The ID value associated with the new PermMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPermMap *new;              /* Pointer to new PermMap */
+   int *inperm1;                 /* Pointer to temporary copy of "inperm" */
+   int *outperm1;                /* Pointer to temporary copy of "outperm" */
+   int coord;                    /* Loop counter for coordinates */
+
+/* Variable argument list */
+   va_list args;                 /* Get a pointer to the thread specific global data structure. */
+
+   int *status;                  /* Pointer to inherited status value */
+
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If the "nin" and "nout" values are acceptable, allocate memory to
+   hold temporary copies of the "inperm" and "outperm" arrays (but
+   only if these arrays are not NULL). */
+   inperm1 = NULL;
+   outperm1 = NULL;
+   if ( ( nin >= 0 ) && ( nout >= 0 ) ) {
+      if ( inperm ) inperm1 = astMalloc( sizeof( int ) * (size_t) nin );
+      if ( outperm ) outperm1 = astMalloc( sizeof( int ) * (size_t) nout );
+      if ( astOK ) {
+
+/* If necessary, make a copy of the "inperm" array, converting any
+   zero values into (zero-based) coordinate numbers that do not exist,
+   indicating a "bad" coordinate value. */
+         if ( inperm ) {
+            for ( coord = 0; coord < nin; coord++ ) {
+               if ( inperm[ coord ] < 0 ) {
+                  inperm1[ coord ] = inperm[ coord ];
+               } else if ( inperm[ coord ] == 0 ) {
+                  inperm1[ coord ] = nout;
+
+/* Convert valid coordinate references from 1-based (used externally)
+   to zero-based (used internally). */
+               } else {
+                  inperm1[ coord ] = inperm[ coord ] - 1;
+               }
+            }
+         }
+
+/* Repeat this process on the "outperm" array. */
+         if ( outperm ) {
+            for ( coord = 0; coord < nout; coord++ ) {
+               if ( outperm[ coord ] < 0 ) {
+                  outperm1[ coord ] = outperm[ coord ];
+               } else if ( outperm[ coord ] == 0 ) {
+                  outperm1[ coord ] = nin;
+               } else {
+                  outperm1[ coord ] = outperm[ coord ] - 1;
+               }
+            }
+         }
+      }
+   }
+
+/* Initialise the PermMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPermMap( NULL, sizeof( AstPermMap ), !class_init, &class_vtab,
+                         "PermMap", nin, inperm1, nout, outperm1, constant );
+
+/* If necessary, free the temporary arrays allocated above. */
+   if ( ( nin >= 0 ) && ( nout >= 0 ) ) {
+      if ( inperm ) inperm1 = astFree( inperm1 );
+      if ( outperm ) outperm1 = astFree( outperm1 );
+   }
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new PermMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new PermMap. */
+   return astMakeId( new );
+}
+
+AstPermMap *astInitPermMap_( void *mem, size_t size, int init,
+                             AstPermMapVtab *vtab, const char *name,
+                             int nin, const int inperm[],
+                             int nout, const int outperm[],
+                             const double constant[], int *status ) {
+/*
+*+
+*  Name:
+*     astInitPermMap
+
+*  Purpose:
+*     Initialise a PermMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     AstPermMap *astInitPermMap( void *mem, size_t size, int init,
+*                                 AstPermMapVtab *vtab, const char *name,
+*                                 int nin, const int inperm[],
+*                                 int nout, const int outperm[],
+*                                 const double constant[] )
+
+*  Class Membership:
+*     PermMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new PermMap object. It allocates memory (if necessary) to accommodate
+*     the PermMap plus any additional data associated with the derived class.
+*     It then initialises a PermMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a PermMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the PermMap is to be initialised.
+*        This must be of sufficient size to accommodate the PermMap data
+*        (sizeof(PermMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the PermMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the PermMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the PermMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new PermMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     nin
+*        The number of input coordinate values per point.
+*     inperm
+*        Pointer to an array of int, with nin elements. For each input
+*        coordinate, the corresponding element of this array should contain the
+*        (zero-based) index of the output coordinate whose value is to be used.
+*        (Note that this array therefore defines the inverse coordinate
+*        transformation.) If a NULL value is supplied, the corresponding output
+*        coordinate value is used (or AST__BAD if there is no corresponding
+*        output coordinate).
+*
+*        For details of additional special values that may be used in this
+*        array, see the description of the "constant" parameter.
+*     nout
+*        The number of output coordinate values per point.
+*     outperm
+*        Pointer to an array of int, with nout elements. For each output
+*        coordinate, the corresponding element of this array should contain the
+*        (zero-based) index of the input coordinate whose value is to be used.
+*        (Note that this array therefore defines the forward coordinate
+*        transformation.) If a NULL value is supplied, the corresponding input
+*        coordinate value is used (or AST__BAD if there is no corresponding
+*        input coordinate).
+*
+*        For details of additional special values that may be used in this
+*        array, see the description of the "constant" parameter.
+*     constant
+*        Pointer to an array of double, which contains optional values which
+*        may be assigned to input and/or output coordinate values (instead of
+*        deriving them from other coordinate values). If either of the
+*        "inperm" or "outperm" arrays contains a negative value, it is used to
+*        address this "constant" array (such that -1 addresses the first
+*        element, -2 addresses the second element, etc.) and the value obtained
+*        is used as the corresponding coordinate value. Care should be taken
+*        to ensure that locations lying outside the extent of this array are
+*        not accidentally addressed.
+*
+*        If a NULL value is supplied for this parameter, the behaviour is as
+*        if the constant array were of infinite length and filled with the
+*        value AST__BAD.
+
+*  Returned Value:
+*     A pointer to the new PermMap.
+
+*  Notes:
+*     -  This function does not attempt to ensure that the forward and inverse
+*     transformations performed by the resulting PermMap are consistent in any
+*     way. The caller is therefore free to define the permutation arrays to
+*     achieve whatever effect is desired.
+*     -  If either of the "inperm" or "outperm" arrays contains a positive
+*     value which does not identify a valid output/input coordinate (as
+*     appropriate), then the value AST__BAD is assigned as the new coordinate
+*     value.
+*     -  This function makes a copy of the contents of the arrays supplied.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstPermMap *new;              /* Pointer to new PermMap */
+   int i;                        /* Loop counter for coordinates */
+   int neg;                      /* Most negative permutation index */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitPermMapVtab( vtab, name );
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the PermMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstPermMap *) astInitMapping( mem, size, 0,
+                                        (AstMappingVtab *) vtab, name,
+                                        nin, nout, 1, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the PermMap data. */
+/* ---------------------------- */
+      new->permsplit = -INT_MAX;
+
+/* Initialise the array pointers. */
+      new->inperm = NULL;
+      new->outperm = NULL;
+      new->constant = NULL;
+
+/* If an "inperm" and/or "outperm" array has been supplied, allocate memory
+   and store a copy. */
+      if ( inperm ) new->inperm = astStore( NULL, inperm, sizeof( int ) *
+                                                          (size_t) nin );
+      if ( outperm ) new->outperm = astStore( NULL, outperm, sizeof( int ) *
+                                                             (size_t) nout );
+
+/* If a "constant" array has been supplied, we must also store a copy of it,
+   but must first determine how many of its elements we need. */
+      if ( constant ) {
+
+/* Loop through the "inperm" array (if supplied) to find the most negative
+   value it contains. This corresponds with the maximum index into the
+   constant array. */
+         neg = 0;
+         if ( inperm ) {
+            for ( i = 0; i < nin; i++ ) {
+               if ( inperm[ i ] < neg ) neg = inperm[ i ];
+	    }
+	 }
+
+/* Also perform this process on the "outperm" array (if supplied). */
+         if ( outperm ) {
+            for ( i = 0; i < nout; i++ ) {
+               if ( outperm[ i ] < neg ) neg = outperm[ i ];
+	    }
+	 }
+
+/* If a negative value was found, use its size to determine how many elements
+   of the "constant" array to store in allocated memory. */
+         if ( neg < 0 ) {
+            new->constant = astStore( NULL, constant, sizeof( double ) *
+                                                      (size_t) (-neg) );
+	 }
+      }
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) {
+         new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstPermMap *astLoadPermMap_( void *mem, size_t size,
+                             AstPermMapVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadPermMap
+
+*  Purpose:
+*     Load a PermMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "permmap.h"
+*     AstPermMap *astLoadPermMap( void *mem, size_t size,
+*                                 AstPermMapVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     PermMap loader.
+
+*  Description:
+*     This function is provided to load a new PermMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     PermMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a PermMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the PermMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        PermMap data (sizeof(PermMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the PermMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the PermMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstPermMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new PermMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the PermMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "PermMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new PermMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstPermMap *new;              /* Pointer to the new PermMap */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword strings */
+   int coord;                    /* Loop counter for coordinates */
+   int iconst;                   /* Loop counter for constants */
+   int in_cpy;                   /* Input coordinates obtained by copying? */
+   int invert;                   /* Invert attribute value */
+   int ival;                     /* Integer value */
+   int nconst;                   /* Number of constants */
+   int nin;                      /* Number of input coordinates */
+   int nout;                     /* Number of output coordinates */
+   int out_cpy;                  /* Output coordinates obtained by copying? */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this PermMap. In this case the
+   PermMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstPermMap );
+      vtab = &class_vtab;
+      name = "PermMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitPermMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built PermMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "PermMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Initialise the PermMap's pointers. */
+      new->inperm = NULL;
+      new->outperm = NULL;
+      new->constant = NULL;
+
+/* Determine if the PermMap is inverted and obtain the "true" number
+   of input and output coordinates by un-doing the effects of any
+   inversion. */
+      invert = astGetInvert( new );
+      nin = !invert ? astGetNin( new ) : astGetNout( new );
+      nout = !invert ? astGetNout( new ) : astGetNin( new );
+
+/* PermSplit. */
+/* ---------- */
+      new->permsplit = astReadInt( channel, "pmsplt", -INT_MAX );
+      if ( TestPermSplit( new, status ) ) SetPermSplit( new, new->permsplit, status );
+
+/* InCpy and OutCpy. */
+/* ----------------- */
+/* Obtain boolean values via the "InCpy" and "OutCpy" keywords which
+   indicate if input/output coordinates should be obtained simply by
+   copying the corresponding output/input coordinates. */
+      in_cpy = astReadInt( channel, "incpy", 0 );
+      out_cpy = astReadInt( channel, "outcpy", 0 );
+
+/* If coordinates are specified individually (not simply copied), then
+   allocate memory for the coordinate permutation arrays. */
+      if ( !in_cpy ) new->inperm = astMalloc( sizeof( int ) * (size_t) nin );
+      if ( !out_cpy ) new->outperm = astMalloc( sizeof( int ) *
+                                                (size_t) nout );
+
+/* If an error occurred, ensure that all allocated memory is freed. */
+      if ( !astOK ) {
+         if ( !in_cpy ) new->inperm = astFree( new->inperm );
+         if ( !out_cpy ) new->outperm = astFree( new->outperm );
+
+/* Otherwise read data into these arrays... */
+      } else {
+
+/* "inperm" array contents. */
+/* ------------------------ */
+/* If required, create a keyword for each element of the "inperm"
+   array and read the element's value. */
+         if ( !in_cpy ) {
+            for ( coord = 0; coord < nin; coord++ ) {
+               (void) sprintf( key, "in%d", coord + 1 );
+               ival = astReadInt( channel, key, 0 );
+
+/* If the value is zero (indicating a "bad" coordinate), convert it to
+   a (zero-based) coordinate number that doesn't exist. */
+               if ( ival == 0 ) {
+                  ival = nout;
+
+/* If the coordinate reference is valid, convert to zero-based
+   coordinate numbering for internal use. */
+               } else if ( ival > 0 ) {
+                  ival--;
+               }
+
+/* Store the value. */
+               new->inperm[ coord ] = ival;
+            }
+         }
+
+/* "outperm" array contents. */
+/* ------------------------- */
+/* If required, create a keyword for each element of the "outperm"
+   array and read the element's value. */
+         if ( !out_cpy ) {
+            for ( coord = 0; coord < nout; coord++ ) {
+               (void) sprintf( key, "out%d", coord + 1 );
+               ival = astReadInt( channel, key, 0 );
+
+/* If the value is zero (indicating a "bad" coordinate), convert it to
+   a (zero-based) coordinate number that doesn't exist. */
+               if ( ival == 0 ) {
+                  ival = nin;
+
+/* If the coordinate reference is valid, convert to zero-based
+   coordinate numbering for internal use. */
+               } else if ( ival > 0 ) {
+                  ival--;
+               }
+
+/* Store the value. */
+               new->outperm[ coord ] = ival;
+            }
+         }
+
+/* Number of constants. */
+/* -------------------- */
+/* Determine the number of constants and allocate memory to hold
+   them. */
+         nconst = astReadInt( channel, "nconst", 0 );
+         if ( nconst < 0 ) nconst = 0;
+         new->constant = astMalloc( sizeof( double ) * (size_t) nconst );
+         if ( astOK ) {
+
+/* Constants. */
+/* ---------- */
+/* Create a keyword for each constant and read its value. */
+            for ( iconst = 0; iconst < nconst; iconst++ ) {
+               (void) sprintf( key, "con%d", iconst + 1 );
+               new->constant[ iconst ] =
+                  astReadDouble( channel, key, AST__BAD );
+            }
+         }
+      }
+
+/* If an error occurred, clean up by deleting the new PermMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new PermMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+double *astGetConstants_( AstPermMap *this, int *status ){
+   if( !astOK ) return NULL;
+   return (**astMEMBER(this,PermMap,GetConstants))( this, status );
+}
+
+int *astGetInPerm_( AstPermMap *this, int *status ){
+   if( !astOK ) return NULL;
+   return (**astMEMBER(this,PermMap,GetInPerm))( this, status );
+}
+
+int *astGetOutPerm_( AstPermMap *this, int *status ){
+   if( !astOK ) return NULL;
+   return (**astMEMBER(this,PermMap,GetOutPerm))( this, status );
+}
+
+
+
+
+
+
diff --git a/ast/permmap.h b/ast/permmap.h
new file mode 100644
index 0000000..0115783
--- /dev/null
+++ b/ast/permmap.h
@@ -0,0 +1,322 @@
+#if !defined( PERMMAP_INCLUDED ) /* Include this file only once */
+#define PERMMAP_INCLUDED
+/*
+*+
+*  Name:
+*     permmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the PermMap class.
+
+*  Invocation:
+*     #include "permmap.h"
+
+*  Description:
+*     This include file defines the interface to the PermMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The PermMap class implements Mappings that perform permutation
+*     of the order of coordinate values, possibly also accompanied by
+*     changes in the number of coordinates (between input and output).
+*
+*     In addition to permuting the coordinate order, coordinates may
+*     also be assigned constant values which are unrelated to other
+*     coordinate values.  This facility is useful when the number of
+*     coordinates is being increased, as it allows fixed values to be
+*     assigned to the new coordinates.
+
+*  Inheritance:
+*     The PermMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astTransform
+*           Transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astGetConstants
+*           Obtain a copy of the constants array
+*        astGetInPerm
+*           Obtain a copy of the input permutation array
+*        astGetOutPerm
+*           Obtain a copy of the output permutation array
+
+*  Other Class Functions:
+*     Public:
+*        astIsAPermMap
+*           Test class membership.
+*        astPermMap
+*           Create a PermMap.
+*
+*     Protected:
+*        astCheckPermMap
+*           Validate class membership.
+*        astInitPermMap
+*           Initialise a PermMap.
+*        astInitPermMapVtab
+*           Initialise the virtual function table for the PermMap class.
+*        astLoadPermMap
+*           Load a PermMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstPermMap
+*           PermMap object type.
+*
+*     Protected:
+*        AstPermMapVtab
+*           PermMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     29-FEB-1996 (RFWS):
+*        Original version.
+*     26-SEP-1996 (RFWS):
+*        Added external interface and I/O facilities.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitPermMapVtab
+*        method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* PermMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstPermMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int *inperm;                  /* Pointer to input permutation array */
+   int *outperm;                 /* Pointer to output permutation array */
+   double *constant;             /* Pointer to array of constant values */
+   int permsplit;                /* Method to use within MapSplit */
+} AstPermMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstPermMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   double *(* GetConstants)( AstPermMap *, int * );
+   int *(* GetInPerm)( AstPermMap *, int * );
+   int *(* GetOutPerm)( AstPermMap *, int * );
+   void (* SetPermSplit)( AstPermMap *, int, int * );
+   void (* ClearPermSplit)( AstPermMap *, int * );
+   int (* TestPermSplit)( AstPermMap *, int * );
+   int (* GetPermSplit)( AstPermMap *, int * );
+
+} AstPermMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstPermMapGlobals {
+   AstPermMapVtab Class_Vtab;
+   int Class_Init;
+} AstPermMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitPermMapGlobals_( AstPermMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(PermMap)          /* Check class membership */
+astPROTO_ISA(PermMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstPermMap *astPermMap_( int, const int [], int, const int [],
+                         const double [], const char *, int *, ...);
+#else
+AstPermMap *astPermMapId_( int, const int [], int, const int [],
+                           const double [], const char *, ... )__attribute__((format(printf,6,7)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstPermMap *astInitPermMap_( void *, size_t, int, AstPermMapVtab *,
+                             const char *, int, const int [], int,
+                             const int [], const double [], int * );
+
+/* Vtab initialiser. */
+void astInitPermMapVtab_( AstPermMapVtab *, const char *, int * );
+
+/* Loader. */
+AstPermMap *astLoadPermMap_( void *, size_t, AstPermMapVtab *,
+                             const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+double *astGetConstants_( AstPermMap *, int * );
+int *astGetInPerm_( AstPermMap *, int * );
+int *astGetOutPerm_( AstPermMap *, int * );
+void astSetPermSplit_( AstPermMap *, int, int * );
+void astClearPermSplit_( AstPermMap *, int * );
+int astTestPermSplit_( AstPermMap *, int * );
+int astGetPermSplit_( AstPermMap *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckPermMap(this) astINVOKE_CHECK(PermMap,this,0)
+#define astVerifyPermMap(this) astINVOKE_CHECK(PermMap,this,1)
+
+/* Test class membership. */
+#define astIsAPermMap(this) astINVOKE_ISA(PermMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astPermMap astINVOKE(F,astPermMap_)
+#else
+#define astPermMap astINVOKE(F,astPermMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitPermMap(mem,size,init,vtab,name,nin,inperm,nout,outperm,constant) \
+astINVOKE(O,astInitPermMap_(mem,size,init,vtab,name,nin,inperm,nout,outperm,constant,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitPermMapVtab(vtab,name) astINVOKE(V,astInitPermMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadPermMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadPermMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckPermMap to validate PermMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astGetConstants(this) astINVOKE(V,astGetConstants_(astCheckPermMap(this),STATUS_PTR))
+#define astGetInPerm(this) astINVOKE(V,astGetInPerm_(astCheckPermMap(this),STATUS_PTR))
+#define astGetOutPerm(this) astINVOKE(V,astGetOutPerm_(astCheckPermMap(this),STATUS_PTR))
+#define astSetPermSplit(this,permsplit) astINVOKE(V,astSetPermSplit_(astCheckPermMap(this),permsplit,STATUS_PTR))
+#define astClearPermSplit(this) astINVOKE(V,astClearPermSplit_(astCheckPermMap(this),STATUS_PTR))
+#define astTestPermSplit(this) astINVOKE(V,astTestPermSplit_(astCheckPermMap(this),STATUS_PTR))
+#define astGetPermSplit(this) astINVOKE(V,astGetPermSplit_(astCheckPermMap(this),STATUS_PTR))
+#endif
+
+#endif
+
+
+
+
+
diff --git a/ast/pg3d.h b/ast/pg3d.h
new file mode 100644
index 0000000..e4dab63
--- /dev/null
+++ b/ast/pg3d.h
@@ -0,0 +1,68 @@
+#if !defined( PG3D_INCLUDED ) /* Include this file only once */
+#define PG3D_INCLUDED
+/*
+*+
+*  Name:
+*     pg3d.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the pg3d module
+
+*  Invocation:
+*     #include "pg3d.h"
+
+*  Description:
+*     This include file defines the interface to the pg3d module
+*     (implemented in file grf3d_pgplot.c) and provides the type
+*     definitions, function prototypes and macros, etc. needed to
+*     use this module.
+*
+*     The functions in the pg3d interface provide control of the view
+*     of the 3D world coordinate system visible on the 2D PGPLOT
+*     viewport. They are provided for users of the PGPLOT implementation
+*     of the grf3D interface distributed with AST. No calls to these
+*     functions are made from within AST itself.
+
+*  Copyright:
+*     Copyright (C) 2007 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (JACH - UCLan)
+
+*  History:
+*     20-JUN-2007 (DSB):
+*        Original version.
+*-
+*/
+
+int PG3DRotateEye( int, float );
+int PG3DSetCamera( float[3], float[3], float[3], float );
+int PG3DSetEye( float[3] );
+int PG3DSetTarget( float[3] );
+int PG3DSetUp( float[3] );
+int PG3DSetScreen( float );
+int PG3DForward( float );
+int PG3DAutoCamera( float[3], float[3] );
+int PG3DFindNearest( int, float *, float *, float *, int * );
+
+#endif
diff --git a/ast/plot.c b/ast/plot.c
new file mode 100644
index 0000000..d8adcc8
--- /dev/null
+++ b/ast/plot.c
@@ -0,0 +1,32074 @@
+/*
+*class++
+*  Name:
+*     Plot
+
+*  Purpose:
+*     Provide facilities for 2D graphical output.
+
+*  Constructor Function:
+c     astPlot
+f     AST_PLOT
+
+*  Description:
+*     This class provides facilities for producing 2D graphical output.
+*     A Plot is a specialised form of FrameSet, in which the base
+*     Frame describes a "graphical" coordinate system and is
+*     associated with a rectangular plotting area in the underlying
+*     graphics system. This plotting area is where graphical output
+*     appears. It is defined when the Plot is created.
+*
+*     The current Frame of a Plot describes a "physical" coordinate
+*     system, which is the coordinate system in which plotting
+*     operations are specified. The results of each plotting operation
+*     are automatically transformed into graphical coordinates so as
+*     to appear in the plotting area (subject to any clipping which
+*     may be in effect).
+*
+*     Because the Mapping between physical and graphical coordinates
+*     may often be non-linear, or even discontinuous, most plotting
+*     does not result in simple straight lines. The basic plotting
+*     element is therefore not a straight line, but a geodesic curve
+c     (see astCurve, astGenCurve and astPolyCurve). A Plot also provides facilities for
+c     drawing markers or symbols (astMark), text (astText) and grid lines
+c     (astGridLine). It is also possible to draw curvilinear axes with
+c     optional coordinate grids (astGrid).
+f     (see AST_CURVE, AST_GENCURVE and AST_POLYCURVE). A Plot also provides facilities
+f     for drawing markers or symbols (AST_MARK), text (AST_TEXT) and grid
+f     lines (AST_GRIDLINE). It is also possible to draw curvilinear axes
+f     with optional coordinate grids (AST_GRID).
+*     A range of Plot attributes is available to allow precise control
+*     over the appearance of graphical output produced by these
+c     functions.
+f     routines.
+*
+*     You may select different physical coordinate systems in which to
+*     plot (including the native graphical coordinate system itself)
+*     by selecting different Frames as the current Frame of a Plot,
+*     using its Current attribute.  You may also set up clipping (see
+c     astClip) to limit the extent of any plotting you perform, and
+f     AST_CLIP) to limit the extent of any plotting you perform, and
+*     this may be done in any of the coordinate systems associated
+*     with the Plot, not necessarily the one you are plotting in.
+*
+*     Like any FrameSet, a Plot may also be used as a Frame. In this
+*     case, it behaves like its current Frame, which describes the
+*     physical coordinate system.
+*
+*     When used as a Mapping, a Plot describes the inter-relation
+*     between graphical coordinates (its base Frame) and physical
+*     coordinates (its current Frame).  It differs from a normal
+*     FrameSet, however, in that an attempt to transform points which
+*     lie in clipped areas of the Plot will result in bad coordinate
+*     values (AST__BAD).
+
+*  Inheritance:
+*     The Plot class inherits from the FrameSet class.
+
+*  Attributes:
+*     In addition to those attributes common to all FrameSets, every
+*     Plot also has the following attributes:
+*
+*     - Abbrev: Abbreviate leading fields?
+*     - Border: Draw a border around valid regions of a Plot?
+*     - Clip: Clip lines and/or markers at the Plot boundary?
+*     - ClipOp: Combine Plot clipping limits using a boolean OR?
+*     - Colour(element): Colour index for a Plot element
+*     - DrawAxes(axis): Draw axes for a Plot?
+*     - DrawTitle: Draw a title for a Plot?
+*     - Escape: Allow changes of character attributes within strings?
+*     - Edge(axis): Which edges to label in a Plot
+*     - Font(element): Character font for a Plot element
+*     - Gap(axis): Interval between linearly spaced major axis values
+*     - Grf: Select the graphics interface to use.
+*     - Grid: Draw grid lines for a Plot?
+*     - Invisible: Draw graphics in invisible ink?
+*     - LabelAt(axis): Where to place numerical labels for a Plot
+*     - LabelUnits(axis): Use axis unit descriptions in a Plot?
+*     - LabelUp(axis): Draw numerical Plot labels upright?
+*     - Labelling: Label and tick placement option for a Plot
+*     - LogGap(axis): Interval between logarithmically spaced major axis values
+*     - LogPlot(axis): Map the plot onto the screen logarithmically?
+*     - LogTicks(axis): Space the major tick marks logarithmically?
+*     - MajTickLen(axis): Length of major tick marks for a Plot
+*     - MinTickLen(axis): Length of minor tick marks for a Plot
+*     - MinTick(axis): Density of minor tick marks for a Plot
+*     - NumLab(axis): Draw numerical axis labels for a Plot?
+*     - NumLabGap(axis): Spacing of numerical axis labels for a Plot
+*     - Size(element): Character size for a Plot element
+*     - Style(element): Line style for a Plot element
+*     - TextLab(axis): Draw descriptive axis labels for a Plot?
+*     - TextLabGap(axis): Spacing of descriptive axis labels for a Plot
+*     - TickAll: Draw tick marks on all edges of a Plot?
+*     - TitleGap: Vertical spacing for a Plot title
+*     - Tol: Plotting tolerance
+*     - Width(element): Line width for a Plot element
+
+*  Functions:
+c     In addition to those functions applicable to all FrameSets, the
+c     following functions may also be applied to all Plots:
+f     In addition to those routines applicable to all FrameSets, the
+f     following routines may also be applied to all Plots:
+*
+c     - astBBuf: Begin a new graphical buffering context
+c     - astBorder: Draw a border around valid regions of a Plot
+c     - astBoundingBox: Returns a bounding box for previously drawn graphics
+c     - astClip: Set up or remove clipping for a Plot
+c     - astCurve: Draw a geodesic curve
+c     - astEBuf: End the current graphical buffering context
+c     - astGenCurve: Draw a generalized curve
+c     - astGetGrfContext: Get the graphics context for a Plot
+c     - astGrfPop: Retrieve previously saved graphics functions
+c     - astGrfPush: Save the current graphics functions
+c     - astGrfSet: Register a graphics routine for use by a Plot
+c     - astGrid: Draw a set of labelled coordinate axes
+c     - astGridLine: Draw a grid line (or axis) for a Plot
+c     - astMark: Draw a set of markers for a Plot
+c     - astPolyCurve: Draw a series of connected geodesic curves
+c     - astRegionOutline: Draw the outline of an AST Region
+c     - astText: Draw a text string for a Plot
+f     - AST_BBUF: Begin a new graphical buffering context
+f     - AST_BORDER: Draw a border around valid regions of a Plot
+f     - AST_BOUNDINGBOX: Returns a bounding box for previously drawn graphics
+f     - AST_CLIP: Set up or remove clipping for a Plot
+f     - AST_CURVE: Draw a geodesic curve
+f     - AST_EBUF: End the current graphical buffering context
+f     - AST_GENCURVE: Draw a generalized curve
+f     - AST_GETGRFCONTEXT: Get the graphics context for a Plot
+f     - AST_GRFPOP: Retrieve previously saved graphics functions
+f     - AST_GRFPUSH: Save the current graphics functions
+f     - AST_GRFSET: Register a graphics routine for use by the Plot class
+f     - AST_GRID: Draw a set of labelled coordinate axes
+f     - AST_GRIDLINE: Draw a grid line (or axis) for a Plot
+f     - AST_MARK: Draw a set of markers for a Plot
+f     - AST_POLYCURVE: Draw a series of connected geodesic curves
+f     - AST_REGIONOUTLINE: Draw the outline of an AST Region
+f     - AST_TEXT: Draw a text string for a Plot
+
+*  Graphical Elements:
+*     The colour index, character font, character size, line style and
+*     line width used for plotting can be set independently for
+*     various elements of the graphical output produced by a Plot.
+*     The different graphical elements are identified by appending the
+*     strings listed below as subscripts to the Plot attributes
+*     Colour(element), Font(element), Size(element), Style(element)
+*     and Width(element). These strings are case-insensitive and
+*     unambiguous abbreviations may be used. Elements of the graphical
+*     output which relate to individual axes can be referred to either
+*     independently (e.g. "(Grid1)" and "(Grid2)" ) or together (e.g.
+*     "(Grid)"):
+*
+c     - Axes: Axis lines drawn through tick marks using astGrid
+f     - Axes: Axis lines drawn through tick marks using AST_GRID
+c     - Axis1: Axis line drawn through tick marks on axis 1 using astGrid
+f     - Axis1: Axis line drawn through tick marks on axis 1 using AST_GRID
+c     - Axis2: Axis line drawn through tick marks on axis 2 using astGrid
+f     - Axis2: Axis line drawn through tick marks on axis 2 using AST_GRID
+c     - Border: The Plot border drawn using astBorder, astGrid or astRegionOutline
+f     - Border: The Plot border drawn using AST_BORDER, AST_GRID or AST_REGIONOUTLINE
+c     - Curves: Geodesic curves drawn using astCurve, astGenCurve or astPolyCurve
+f     - Curves: Geodesic curves drawn using AST_CURVE, AST_GENCURVE or AST_POLYCURVE
+c     - Grid: Grid lines drawn using astGridLine or astGrid
+f     - Grid: Grid lines drawn using AST_GRIDLINE or AST_GRID
+c     - Grid1: Grid lines which cross axis 1, drawn using astGridLine or astGrid
+f     - Grid1: Grid lines which cross axis 1, drawn using AST_GRIDLINE or AST_GRID
+c     - Grid2: Grid lines which cross axis 2, drawn using astGridLine or astGrid
+f     - Grid2: Grid lines which cross axis 2, drawn using AST_GRIDLINE or AST_GRID
+c     - Markers: Graphical markers (symbols) drawn using astMark
+f     - Markers: Graphical markers (symbols) drawn using AST_MARK
+c     - NumLab: Numerical axis labels drawn using astGrid
+f     - NumLab: Numerical axis labels drawn using AST_GRID
+c     - NumLab1: Numerical labels for axis 1 drawn using astGrid
+f     - NumLab1: Numerical labels for axis 1 drawn using AST_GRID
+c     - NumLab2: Numerical labels for axis 2 drawn using astGrid
+f     - NumLab2: Numerical labels for axis 2 drawn using AST_GRID
+c     - Strings: Text strings drawn using astText
+f     - Strings: Text strings drawn using AST_TEXT
+c     - TextLab: Descriptive axis labels drawn using astGrid
+f     - TextLab: Descriptive axis labels drawn using AST_GRID
+c     - TextLab1: Descriptive label for axis 1 drawn using astGrid
+f     - TextLab1: Descriptive label for axis 1 drawn using AST_GRID
+c     - TextLab2: Descriptive label for axis 2 drawn using astGrid
+f     - TextLab2: Descriptive label for axis 2 drawn using AST_GRID
+c     - Ticks: Tick marks (both major and minor) drawn using astGrid
+f     - Ticks: Tick marks (both major and minor) drawn using AST_GRID
+c     - Ticks1: Tick marks (both major and minor) for axis 1 drawn using astGrid
+f     - Ticks1: Tick marks (both major and minor) for axis 1 drawn using AST_GRID
+c     - Ticks2: Tick marks (both major and minor) for axis 2 drawn using astGrid
+f     - Ticks2: Tick marks (both major and minor) for axis 2 drawn using AST_GRID
+c     - Title: The Plot title drawn using astGrid
+f     - Title: The Plot title drawn using AST_GRID
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     18-SEP-1996 (DSB):
+*        Original version.
+*     25-FEB-1997 (RFWS):
+*        Tidied all public prologues.
+*     18-AUG-1997 (DSB):
+*        Changes made to ensure that the first label on each axis is
+*        never abbreviated, and to avoid segmentation violation when NumLab
+*        is set to zero.
+*     1-SEP-1997 (DSB):
+*        astGetGap changed so that it returns the default value which will
+*        be used (instead of AST__BAD) if no value has been set for Gap.
+*        The Border attribute modified so that it is off (zero) by default.
+*     19-SEP-1997 (DSB):
+*        o  Check that something has been plotted before using the bounding
+*        box to determine title and label positions.
+*        o  Fixed bug which caused a tick mark at the pole to be draw at
+*        a random angle.
+*        o  The size of the increment used to determine the tangent to a grid
+*        line at the position to a label has been reduced to make sure the
+*        labls are drawn parallel to grid line.
+*        o  Correct the logic for catering with reversed axes when determining
+*        the displacement of a label's reference point from the associated
+*        axis.
+*        o  Corrected logic which determined if two numerical labels overlap.
+*        o  Corrected logic for determining when to abbreviate numerical
+*        labels.
+*        o  Use of strtok replaced by local function FindWord.
+*        o  Correct logic which determines which side of the axis to draw
+*        tick marks when using interior labelling.
+*        o  If the base Frame of the FrameSet supplied to astPlot has more
+*        than 2 axes, then use a sub-frame formed from the first two axes,
+*        instead of simply reporting an error.
+*        o  If the current Frame of the Plot supplied to astGrid or
+*        astBorder has more than 2 axes, then use a sub-frame formed from
+*        the first two axes, instead of simply reporting an error.
+*        o  Default for Border is now to draw the border if exterior
+*        Labelling is used, but not to draw it if interior labelling is
+*        used.
+*        o  Public astGet function now returns actual used values for all
+*        attributes. Protected astGetXyz functions still return the requested
+*        value (which may differ from the used value), or the "unset" value
+*        if no value has been set for the attribute.
+*        o  The defaults for Edge now depend on Labelling. If exterior
+*        labelling was requested but cannot be produced using defaults of
+*        Edge(1)=Bottom and Edge(2)=Left, then these default Edge values
+*        are swapped. If exterior labelling is still not possible, the
+*        original default Edge values are re-instated.
+*        o  Unset attributes which use dynamic defaults are now flagged as
+*        "unhelpful" in the dump function.
+*        o  Added attribute Escape which allows text strings to include
+*        escape sequences (see function GrText). This attribute and
+*        associated functionality is currently not available for use, search
+*        for all occurences of ENABLE-ESCAPE for instructions on how to
+*        enable the facilities.
+*        o  Strings now used instead of integers to represent "choice"
+*        attributes externally (eg Edge and Labelling).
+*     24-NOV-1997 (DSB):
+*        o  Fixed bug in function Grid which caused units to be included in
+*        SkyFrame axis labels by default.
+*        o  Replaced calls to DrawText by calls to astGText, and replaced
+*        references to "U" and "D" justifications by "T" and "B". This
+*        stops labels drifting to the bottom left when GAIA zooms.
+*     23-MAR-1998 (DSB):
+*        Added extra checks on global status into routine Grid to avoid
+*        segmentation violations occuring due to null pointers being used.
+*     10-JUN-1998 (DSB):
+*        Modify DrawTicks so that ticks are drawn closer to singularities
+*        than previously. Also normalise this constraint to the screen size
+*        rather than the length of a major tick mark.
+*     28-OCT-1998 (DSB):
+*        o  Added method astPolyCurve.
+*        o  Extract the Current Frame from the Plot prior to using Frame
+*        methods such as astOffset, astNorm, etc.
+*        o  PlotLabel modified to ensure labels are abbreviated even if
+*        they are next to the "root" label (i.e. the label with most
+*        trailing zeros).
+*        o  Modified description of Width attribute. Width no longer gives
+*        the absolute line width in inches. Instead it is a scale factor,
+*        where 1.0 corresponds to a "typical thin line" on the device.
+*        o  Modified LabelUnits attribute so that the default value is zero
+*        for SkyAxes and non-zero for other Axes.
+*     10-DEC-1998 (DSB):
+*        Modified all calls to the "pow" maths function to avoid using
+*        literal constants as arguments. This seems to cause segmentation
+*        violations on some systems.
+*     16-JUL-1999 (DSB):
+*        Fixed memory leaks in EdgeCrossings and EdgeLabels.
+*     16-SEP-1999 (DSB):
+*        Avoid writing out clipping limits if they are undefined.
+*     12-OCT-1999 (DSB):
+*        o  Modified use of the NumLab attribute so that setting it to zero
+*        does not prevent exterior labels from being produced.
+*        o  Allow length of tick marks to be specified separately for
+*        both axes.
+*     13-OCT-2000 (DSB):
+*        o Purge zero length sections from CurveData structures.
+*        o Increase tolerance for edge labels from 0.0005 to 0.005.
+*     9-JAN-2001 (DSB):
+*        o  Change argument "in" for astMark and astPolyCurve from type
+*        "const double (*)[]" to "const double *".
+*        o  Check success of astReadString before using the returned
+*        pointer.
+*        o  Change method for choosing default LabelAt values to ignore
+*        values which produce no visible labels.
+*     10-JAN-2001 (DSB):
+*        o  Modified FindMajTick to choose the size of fillable holes in
+*        the axis range on the basis of the number of ticks on the axis.
+*        This avoids holes being visible in the displayed tick marks when
+*        using very small gaps.
+*     22-MAY-2001 (DSB):
+*        Added a check when using interior labelling, to ensure that the
+*        most appropriate edges are used for text labels.
+*     13-JUN-2001 (DSB):
+*        Added public method astGenCurve, astGrfSet, astGrfPop, astGrfPush.
+*        Made DrawAxes attribute axis specific.
+*     4-JUL-2001 (DSB):
+*        The Crv function used to have a restriction that if *any*
+*        subsection was very short, then *none* of the subsections were
+*        subdivided. This meant that long subsections which needed
+*        subdividing were not subdivided if there was also a very short
+*        subsection. To get round this problem the restriction was changed
+*        to "if *all* subsections are very short then none are divided.
+*        This was implemented by changing dl2_min to dl2_max, and adding
+*        a check for very short segments (which are then not sub-divided).
+*     16-AUG-2001 (DSB):
+*        Remove the check for very short segments introduced above, as it
+*        caused south pole tan projection to include some spurious lines.
+*     20-SEP-2001 (DSB):
+*        - Initialize baseframe to NULL in astInitPlot (prevents segvios).
+*        - Modified astInitPlot to allow the "frame" argument to the astPlot
+*        constructor to be a Plot.
+*     10-JAN-2002 (DSB):
+*       - Added axis-specific graphical elements "axis1", "axis2", etc.
+*       - FullForm returns a match without ambiguity if the test string
+*       matches an option exactly, including length.
+*     31-JAN-2002 (DSB):
+*       - Added RejectOOB to reject tick marks which are not in their primary
+*       domain.
+*     14-FEB-2002 (DSB):
+*       - Relaxed the conditions for equality within the EQUALS macro.
+*       Guard aginst no ticks being found.
+*     18-FEB-2002 (DSB):
+*       - Make a permanent copy of any old axis format string in TickMarks.
+*       Previously a mere pointer into the astGet string buffer was stored,
+*       which could be over-written after many calls to astGet.
+*       - If a user specifies an axis format, use it whether or not it
+*       results in any identical adjacent labels.
+*     4-MAR-2002 (DSB):
+*       - Made fairly extesive changes to the creation and use of tick
+*       mark values in order to circumvent problems with CAR projections,
+*       and "1 to many" mappings (such as 2D cartesian->polar). The
+*       policy now is that axis normalization is only performed when
+*       necessary (i.e. to create labels for display, etc). Tick mark
+*       values are stored and handled as non-normalized values as much as
+*       possible.
+*     13-JUN-2002 (DSB):
+*       Modified Norm1 to prevent major tick value from being removed if
+*       the supplied reference value is out of bounds, resulting in the
+*       Mapping producing bad values
+*     14-JUN-2002 (DSB):
+*       Re-wrote PlotLabels to improve abbreviation of labels and the
+*       choice of which labels not to print.
+*     14-AUG-2002 (DSB):
+*       - Added method astBoundingBox.
+*       - Added attribute Invisible.
+*       - Correct handling of "axis specific" plot elements cuch as
+*       (Axis1), (Axis2), etc.
+*     12-SEP-2002 (DSB):
+*       - Modified Map1 to remove slow normalization method (it is now
+*       faster but the changes result in some longer-than-needed grids
+*       lines when (e.g.) plotting pixel coordins in Polar coords).
+*       - Modified Axlot so that SkyFrames positions which are out of
+*       their normal ranges are not rejected by Map1.
+*     10-OCT-2002 (DSB):
+*       grfAttrs:Modified to test element attributes explicitly using the
+*       relevant TestUse<attr> functions, instead of relying on the
+*       "GetUse<attr>" function returning the NO<attr> constant if not set.
+*       - Modified Axplot so that SkyFrames positions which are out of
+*       their normal ranges are not rejected by Map1.
+*       - Only use tick marks which are within the axis range given by the
+*       Bottom and Top Axis attributes.
+*       - Norm1: If the normalized current frame coords are bad, do not
+*       reinstate the original unnormalized values. For instance, current
+*       Frame values which are outside the valid domain of the projection
+*       should result in bad values when normalized, not the original
+*       good values. The original comment stated "If the normalization
+*       produced bad coords (e.g. as may happen if the supplied refernce
+*       value corresponds to a point on the line through the tick mark
+*       which is outside the valid region of the mapping) leave the original
+*       tick mark values unchanged".
+*       - GetTicks: Limit maxticks to be no less than 8.
+*     8-JAN-2003 (DSB):
+*        - Changed private InitVtab method to protected astInitPlotVtab
+*        method.
+*        - Use private IsASkyFrame method in place of astIsASkyFrame.
+*        - Modify PlotLabels to excluding exponents when counting trailing
+*        zeros, and also to pad trailing fields with trailing zeros up to
+*        the max number of decimal places when estimating label priorities.
+*        - Modified Overlap to ensure that axis labels are speced by at
+*        least two spaces.
+*     22-JAN-2003 (DSB):
+*        - Modified PlotLabels so that labels are rejected in a regular
+*        pattern rather than semi-random.
+*        - Modified the way PlotLabels abbreviates leading fields.
+*        - Introdued the skipbad parameter for the Crv function, in order
+*        to provide some degree of protection against the Crv algorithm
+*        skipping over small sections of valid coordinates (such as when
+*        a curve crosses the plot very close to a corner of the plot).
+*     25-MAR-2003 (DSB):
+*        - Modified FindMajTicks to avoid losing tick marks when dealing
+*        with high precision data.
+*     8-AUG-2003 (DSB):
+*        - Modified PlotLabels to ensure that the root label for the
+*        second axis is not omitted due to it overlapping a label from
+*        the first axis (a different root label is now chosen if this would
+*        be the case).
+*        - Modify FindMajTicks to avoid tick marks which should be at
+*        exactly zero being placed at some very small non-zero axis value.
+*     22-OCT-2003 (DSB):
+*        - DrawTicks modified to correctly reset graphical attributes and
+*        pass on to the next axis if an axis has zero length major and minor
+*        tick marks.
+*     9-JAN-2004 (DSB):
+*        DrawGrid: Report error if no grid curves can be drawn.
+*        AxPlot: Initialise returned CDATA structure before checking argument
+*        validity.
+*        GetTicks: Calculate the reference value on the other axis using
+*        function "Typical" rather than simply using the man of the supplied
+*        values (the supplied values may be clustered around 0 and 2*PI if the
+*        field is centred on the origin, resulting in the mean being at about
+*        1.PI and therefore inappropriate).
+*     13-JAN-2004 (DSB):
+*        - Added LogPlot attribute, and the facility for mapping the base
+*        coordinate system logarithmically onto the plotting area instead of
+*        linearly.
+*        - Added LogTicks attribute, and the facility for spacing the
+*        major tick marks logarithmically instead of linearly.
+*        - Added LogGap attribute, and the facility for storing separate
+*        gap sizes for linear and log tick spacing.
+*     15-JAN-2004 (DSB):
+*        - Added LogLabel attribute.
+*        - Re-instated the inclusion of escape sequences in strings (see
+*        function GrText).
+*     12-FEB-2004 (DSB):
+*        - RightVector: Corrected usage of chh and chv.
+*        - GQch and GScales: Check that values returned by grf module are
+*          usable.
+*        - DrawAxis: Extend axis section by one section (if possible) at
+*        each end (overcomes problems where the axis does not reach a pole).
+*        - DrawAxis: Check axis does not extend beyond a pole.
+*        - Labels: Correct logic of loop which plots interior labels
+*        (previously it missed out labels if there were only 3)
+*        - Allow for some rounding error in FindMajTicks when comparing an
+*        axis value with a loweror upper axis limit.
+*     19-FEB-2004 (DSB):
+*        - Reduced the dynamic range restriction for log ticks from 2 decades
+*        to 1.
+*        - Temporarily clear any error status before re-instating the
+*        original Format in TickMarks.
+*        - Add LogTicks to the GetAttrib function so that the value of the
+*        LogTicks attribute can be got by the public.
+*        - Modify Crv to include a check that he vector scale has not
+*        changed much between adjacent segments.
+*        - Modify Crv so that a segment is only subdivided if at least
+*        half of the subsegments are longer than the shortest significant
+*        length. Also put a restriction on subdivision so that
+*        subdivision only occurs if the bounding box of the segment being
+*        sub-divided is smaller than the bounding box of its parent
+*        segment.
+*     27-FEB-2004 (DSB):
+*        - Reduce the default Tol value from 0.001 to 0.01 in order to
+*        speed up curve drawing..
+*        - Use 0.1*Tol in Boundary because the boundary tracing algorithm
+*        seems to produce much worse visible errors than it should do for a
+*        given Tol.
+*     2-MAR-2004 (DSB):
+*        - Corrected handling of bounding boxes in Crv so that
+*        subdivision is allowed if the bounding box shrinks on only 1 axis
+*        (previously required shrinkage on both axes but this fails if
+*        all the points are on a horizontal or vertical line).
+*        - Modified FindMajTicks to use a better algorithm for finding an
+*        appropriate nfill value (previously logplot=1 axes could have
+*        unfilled holes at the high end).
+*        - Modified GetTicks so that FindMajTicks is not called
+*        repeatedly with the same gap size.
+*        - Modify AxPlot/Map1 so that the axis curve is sampled logarithmically
+*        if the corresponding axis is mapped logarithmically.
+*     10-MAR-2004 (DSB):
+*        - Modified Typical to give less weight to vaalues close to the
+*        edges of the range covered by the plotting area.
+*        - Increased minimum angle between curve and edge required to
+*        create an edge label from 3 degs to 5 degs.
+*        - Modified PlotLabels to ignore duplicate adjacent labels which
+*        determining overlap of labels.
+*     17-MAR-2004 (DSB):
+*        - Modified Typical to give normal weight to edge bins in
+*        histogram if these bins contain all the counts.
+*        - Modified DrawTicks to add extra minor ticks below first major
+*        tick value and above last major tick value.
+*        - Norm1 can reject usable tick mark values because of an
+*        inappropriate value being used on the other axis (i.e. one for
+*        which the position is undefined in grapics coords). Therfoer
+*        Norm1 has been modified to use 3 different reference values
+*        in an attempt to find one which gives good axis values.
+*     25-AUG-2004 (DSB):
+*        - Correct handling of "fmt" pointer in TickMarks function (identified
+*        and reported by Bill Joye).
+*     14-SEP-2004 (DSB):
+*        - In EdgeLabels change definition of "distinct labels". Used to
+*        be that labels were distinct if they had different formatted
+*        labels. Now they are distinct if they have different floating
+*        point numerical values. Fixes a bug reported by Micah Johnson.
+*        - TickMarks re-structured to optimise the precision (no. of digits)
+*        even if a value has been assigned for the Format attribute, but only
+*        if the format specifier includes a wildcard precision specifier. For
+*        instance, to get graphical separators a format must be specified
+*        which included the "g" flag. As things were, this would prevent
+*        the optimisation of the digits value. Can now use "dms.*g" to
+*        allow the number of digits to be optimised.
+*     29-SEP-2004 (DSB):
+*        - In FindMajTicks, begin the process of increasing "nfill" from
+*        a value of zero rather than one (in many cases no filling is
+*        needed).
+*        - In GetTicks (linear tick marks section) ensure that 10
+*        *different* gap sizes are used before giving up. Previously, the
+*        10 tests could include duplicated gap values.
+*     8-NOV-2004 (DSB):
+*        - In Norm1, try more alternative "other axis" values before
+*        accepting that a tick mark value cannot be normalised.
+*     2-FEB-2005 (DSB):
+*        - Avoid using astStore to allocate more storage than is supplied
+*        in the "data" pointer. This can cause access violations since
+*        astStore will then read beyond the end of the "data" area.
+*     15-MAR-2005 (DSB):
+*        - Modified GridLines to use appropriate algorithm for choosing
+*        start of grid lines in cases where one axis has logarithmic tick
+*        spacing and the other has linear tick spacing.
+*     21-MAR-2005 (DSB):
+*        - Added the Clip attribute.
+*     12-JUL-2005 (DSB):
+*        - Modified AxPlot so that Map1 only normalises if neither axis
+*        is a SkyAxis. Previously it normalised if either axis was not a
+*        SkyAxis.
+*     7-DEC-2005 (DSB):
+*        Free memory allocated by calls to astReadString.
+*     18-JAN-2006 (DSB)
+*        Add Abbrev attribute.
+*     14-FEB-2006 (DSB)
+*        Correct EdgeLabels to use gap size rather than EQUAL macro when
+*        comparing label values.
+*     17-FEB-2006 (DSB)
+*        Added escape sequences "%h+" and "%g+".
+*     21-MAR-2006 (DSB)
+*        Added extra status checks in TickMarks.
+*     18-MAY-2006 (DSB)
+*        Trans: use correct PointSet when tranforming to arbitrary
+*        clipping frame.
+*     26-MAY-2006 (DSB)
+*        Added LabelAt to TestAttrib.
+*     2-JUN-2006 (DSB)
+*        - In MAKE_GET2, return the set value if a value has been set
+*        without recalculating the defaults.
+*        - Fix bug that could cause segvio in Grid if clipping is used.
+*     5-JUN-2006 (DSB)
+*        Do not change the box returned by astBoundBox as a consequence
+*        of calling astGetAttrib.
+*     19-JUN-2006 (DSB)
+*        Changed the default line 0.0 from zero to 1.0.
+*     22-JUN-2006 (DSB)
+*        Include axis textual labels and title in the bounding box
+*        created by AST_GRID and returned by AST_BOUNDINGBOX.
+*     26-JUN-2006 (DSB)
+*        Set the Direction attribute in the base Frame of a Plot if an
+*        axis is reversed.
+*     29-JUN-2006 (DSB)
+*        - Guard against astGap calls that reach a minimum gap size.
+*        - Sort out splitting of long axis labels (such as date/time
+*        strings produced by TimeFrames).
+*     30-JUN-2006 (DSB)
+*        If abbreviating labels, display the last field for identical
+*        neighbours rather than the whole value.
+*     10-JUL-2006 (DSB)
+*        Make astStripEscapes public so it can be used by the NDF library.
+*     7-AUG-2006 (DSB)
+*        Increase the number of attempts to find a new gap size from 5 to
+*        25 in GetTicks.
+*     24-OCT-2006 (DSB)
+*        Add the ForceExterior attribute so that SPLAT can have external
+*        axes even if there are no usable horizontal axis ticks (as requested
+*        by PWD). Currently this attribute is not included in the public
+*        documentation, as it may cause problems. If it seems to work OK
+*        then it can be made public.
+*     25-JAN-2006 (DSB)
+*        Do not draw ticks marks that start outside the bounds of the
+*        axis they are labelling.
+*     27-FEB-2007 (DSB)
+*        - Change nominal Crv_scerr value from 5.0 to 1.5 (this avoids gaps
+*        in HPX grid plots being bridged by grid lines).
+*        - Double the dimension of the grid used by GoodGrid to avoid
+*        missing the pointy bits in a HPX projection.
+*     17-MAY-2007 (DSB)
+*        Exclude corner positions when determining the range of axis
+*        values covered by the plot. This gives better default gap sizes.
+*     11-JUN-2007 (DSB)
+*        Plug memory leaks.
+*     20-JUN-2007 (DSB)
+*        - Add attribute GrfContext.
+*        - Pass the GrfContext attribute value to each external grf function.
+*        External code that uses the astGrfSet function must be changed
+*        so that the external grf functions registered using astGrfSet
+*        accept this new parameter.
+*     21-JUN-2007 (DSB)
+*        - Change GrfContext to be an Object rather than an integer.
+*     22-JUN-2007 (DSB)
+*        - Do not dump the GrfContext Object since it may cause an
+*        infinite dumping loop.
+*        - Allow a NULL vtab to be supplied when initialising a Plot
+*        structure. This causes the vtab defined locally within this
+*        class to be used so that the new object behaves as a simple Plot.
+*     25-JUN-2007 (DSB)
+*        - Free the graphics context object when then the Plot is deleted.
+*        - Fix memory leak in FullForm.
+*        - Since the grfcontext object is only used by external code, store
+*          a public object identifier for it in the Plot structure rather
+*          than a true C pointer.
+*     26-JUN-2007 (DSB)
+*        Honour the LabelUp attribute value even if labels are drawn
+*        around the edges of the plot.
+*     28-JUN-2007 (DSB)
+*        - Make all axis attribute arrays 3 elements long rather than 2.
+*        - Add the protected methods astCopyPlotDefaults and astMirror.
+*        - Add public method astGetGrfContext, remove astSetGrfContext.
+*        - Fix memory leak.
+*     6-SEP-2007 (DSB):
+*        Dump and load any user-specified tick mark values.
+*     20-OCT-2009 (DSB):
+*        - Modify SplitValue so that it only splits long values if
+*        previous long values were split, or if the value contains a
+*        space.
+*        - Take account of zero height bounding boxes in UpdateConcat.
+*        - Correct Dump so that it dumps attributes for all available
+*        axes (2 for a Plot, 3 for a Plot3D).
+*     12-JAN-2010 (DSB):
+*        Fix various memory leaks.
+*     4-MAR-2011 (DSB):
+*        - Added grf functions BBuf and EBuf.
+*        - Added public method astBBuf and astEBuf.
+*     23-AUG-2011 (DSB):
+*        - If exterior labelling was requested but would produced too
+*        few labels, only swap to interior labelling if doing so would
+*        allow more labels to be drawn (i.e. do not swap to interior if
+*        it does not gain us anything).
+*        - Slightly decrease the dynamic range of an axis needed to produce
+*        logarithmic ticks (this is to avoid problems with round errors).
+*     6-OCT-2011 (DSB):
+*        - Prevent grf qch and scales functions being called lots of times
+*        during each drawing operation (since the information returned by
+*        these functions will not change during the course of a single drawing
+*        operation).
+*     11-OCT-2011 (DSB):
+*        - Combine multiple continuous polylines into a single polyline
+*        before calling the grf polyline function. Reducing the number of
+*        calls to the underlying graphics system can make a big difference
+*        if the graphics system is written in an interpreted language
+*        such as python.
+*        - Take account of differing axis scales when rotating vectors by
+*        90 degrees.
+*     12-OCT-2011 (DSB):
+*        - Fix the change made yesterday to correct rotation of numerical axis
+*        rotations. I forgot that the astGText function in the grf module
+*        expects the up vector in equally scaled coords, not graphics coords.
+*        - Take account of unequal axis scales when working out the
+*        length of each grid curve.
+*     15-OCT-2011 (DSB):
+*        Always check that the grf module implements the scales function
+*        before trying to invoke the scales function.
+*     21-MAY-2012 (DSB):
+*        Correct text strings used to represent the "Labelling" attribute
+*        within dumps of a Plot. Previously they were reversed.
+*     7-JUN-2012 (DSB):
+*        Speed up plotting of CmpRegion boundaries by splitting the
+*        CmpRegion up into a set of disjoint Regions, and plotting each
+*        one separately.
+*     16-JUN-2014 (DSB):
+*        - Prevent seg fault in PlotLabels caused by accessing
+*        uninitialised "atext" field stored within purged labels.
+*        - Choose a label with non-negative priority as the fall-back root label.
+*     17-APR-2015 (DSB):
+*        Added method astRegionOutline.
+*     20-APR-2015 (DSB):
+*        Draw Regions with higher accuracy, because Regions (i.e. Polygons)
+*        can be very non-smooth.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to the header
+   files that define class interfaces that they should make "protected"
+   symbols available. */
+#define astCLASS Plot
+
+/* Values for constants used in this class. */
+#define CRV_NSEG       14 /* No. of curve segments drawn by function Crv */
+#define CRV_NPNT       15 /* CRV_NSEG plus one */
+#define CRV_MXENT      10 /* Max. no. of recursive entries into function Crv */
+#define MAJTICKS_OPT   10 /* Optimum number of major axiss or grid lines */
+#define MAJTICKS_MAX   14 /* Max. number of major ticks or grid lines */
+#define MAJTICKS_MIN    6 /* Min. number of major ticks or grid lines */
+#define EDGETICKS_DIM 100 /* No. of edge samples used to find tick marks */
+#define LEFT            0 /* Id for the left edge of the plotting area */
+#define TOP             1 /* Id for the top edge of the plotting area */
+#define RIGHT           2 /* Id for the right edge of the plotting area */
+#define BOTTOM          3 /* Id for the bottom edge of the plotting area */
+#define NOSTYLE      -999 /* A value which represents a null Style value */
+#define NOWIDTH     -99.9 /* A value which represents a null Style value */
+#define NOFONT       -999 /* A value which represents a null Style value */
+#define NOCOLOUR     -999 /* A value which represents a null Style value */
+#define NOSIZE      -99.9 /* A value which represents a null Style value */
+
+#if defined(THREAD_SAFE)
+#define GLOBALS_PROTO , AstGlobals *
+#define GLOBALS_ARG , AstGlobals *AST__GLOBALS
+#define GLOBALS_NAME , AST__GLOBALS
+#else
+#define GLOBALS_PROTO
+#define GLOBALS_ARG
+#define GLOBALS_NAME
+#endif
+
+/*
+*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Implement a method to clear a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot.h"
+*     MAKE_CLEAR(attr,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the Plot class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstPlot *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( AstPlot *this, int axis )
+*
+*     which implement a method for clearing a single value in a specified
+*     multi-valued attribute for an axis of a Plot.
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. LabelAt in "astClearLabelAt").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1). If a value of 0 is supplied, the
+*        value of the Plot's Nin attribute is used instead.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attr,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attr( AstPlot *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= ( nval ? nval : astGetNin( this ) ) ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astClear" #attr, astGetClass( this ), \
+                axis + 1, ( nval ? nval : astGetNin( this ) ) ); \
+\
+/* Assign the "clear" value. */ \
+   } else { \
+      this->component[ axis ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attr##_( AstPlot *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,Plot,Clear##attr))( this, axis, status ); \
+}
+
+
+/*
+*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Implement a method to get a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot.h"
+*     MAKE_GET(attr,type,bad_value,assign,nval)
+
+*  Class Membership:
+*     Defined by the Plot class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( AstPlot *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        <Type> astGet<Attribute>_( AstPlot *this, int axis )
+*
+*     which implement a method for getting a single value from a specified
+*     multi-valued attribute for an axis of a Plot.
+
+*  Parameters:
+*     attr
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. Label in "astGetLabel").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set, or if
+*        the function fails.
+*     assign
+*        An expression that evaluates to the value to be returned. This can
+*        use the string "axis" to represent the zero-based value index.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1). If a value of 0 is supplied, the
+*        value of the Plot's Nin attribute is used instead.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attr,type,bad_value,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type Get##attr( AstPlot *this, int axis, int *status ) { \
+   type result;                  /* Result to be returned */ \
+\
+/* Initialise */ \
+   result = (bad_value); \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= ( nval ? nval : astGetNin( this ) ) ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astGet" #attr, astGetClass( this ), \
+                axis + 1, ( nval ? nval : astGetNin( this ) ) ); \
+\
+/* Assign the result value. */ \
+   } else { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */  \
+type astGet##attr##_( AstPlot *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,Plot,Get##attr))( this, axis, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Implement a method to set a single value in a multi-valued attribute
+*     for a Plot.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot.h"
+*     MAKE_SET(attr,type,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the Plot class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstPlot *this, int axis, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( AstPlot *this, int axis, <Type> value )
+*
+*     which implement a method for setting a single value in a specified
+*     multi-valued attribute for a Plot.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. LabelAt in "astSetLabelAt").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+*      nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1). If a value of 0 is supplied, the
+*        value of the Plot's Nin attribute is used instead.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attr,type,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attr( AstPlot *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= ( nval ? nval : astGetNin( this ) ) ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astSet" #attr, astGetClass( this ), \
+                axis + 1, ( nval ? nval : astGetNin( this ) ) ); \
+\
+/* Store the new value in the structure component. */ \
+   } else { \
+      this->component[ axis ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attr##_( AstPlot *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,Plot,Set##attr))( this, axis, value, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_TEST
+
+*  Purpose:
+*     Implement a method to test if a single value has been set in a
+*     multi-valued attribute for a class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot.h"
+*     MAKE_TEST(attr,assign,nval)
+
+*  Class Membership:
+*     Defined by the Plot class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static int Test<Attribute>( AstPlot *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        int astTest<Attribute>_( AstPlot *this, int axis )
+*
+*     which implement a method for testing if a single value in a specified
+*     multi-valued attribute has been set for a class.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be tested, as it appears in the function
+*         name (e.g. LabelAt in "astTestLabelAt").
+*      assign
+*         An expression that evaluates to 0 or 1, to be used as the returned
+*         value. This can use the string "axis" to represent the zero-based
+*         index of the value within the attribute.
+*      nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1). If a value of 0 is supplied, the
+*        value of the Plot's Nin attribute is used instead.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_TEST(attr,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static int Test##attr( AstPlot *this, int axis, int *status ) { \
+   int result;                   /* Value to return */ \
+\
+/* Initialise */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= ( nval ? nval : astGetNin( this ) ) ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astTest" #attr, astGetClass( this ), \
+                axis + 1, ( nval ? nval : astGetNin( this ) ) ); \
+\
+/* Assign the result value. */ \
+   } else { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */ \
+int astTest##attr##_( AstPlot *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,Plot,Test##attr))( this, axis, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_GET3
+
+*  Purpose:
+*     Implement a method to get a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     MAKE_GET3(attr,attr,type,bad_value,assign,nval)
+
+*  Class Membership:
+*     Defined by the Plot class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( AstPlot *this, int axis )
+*
+*     which implements a method for getting a single value from a specified
+*     multi-valued attribute for an axis of a Plot. Note, no public
+*     interface function is created.
+*
+*     The value returned is the value which would actually be used if
+*     astGrid was called with the current set of Plot attributes. This
+*     includes calculating any dynamic defaults which would be used, and is
+*     consequently rather slow.
+
+*  Parameters:
+*     attr
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. Label in "astGetLabel"). The
+*        string "Used" is added on to the front of the supplied value.
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set, or if
+*        the function fails.
+*     assign
+*        An expression that evaluates to the value to be returned. This can
+*        use the string "axis" to represent the zero-based value index.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1). If a value of 0 is supplied, the
+*        value of the Plot's Nin attribute is used instead.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_GET3(attr,type,bad_value,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type GetUsed##attr( AstPlot *, int, int *status ); \
+static type GetUsed##attr( AstPlot *this, int axis, int *status ) { \
+   type result;                  /* Result to be returned */ \
+\
+/* Initialise */ \
+   result = (bad_value); \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= ( nval ? nval : astGetNin( this ) ) ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astGetUsed" #attr, astGetClass( this ), \
+                axis + 1, ( nval ? nval : astGetNin( this ) ) ); \
+\
+/* If the attribute is set, use its normal accessor. */\
+   } else if( astTest##attr( this, axis ) ) {\
+      result = astGet##attr( this, axis );\
+\
+/* Otherwise, re-calculate dynamic defaults by going through the motions of \
+   drawing the grid. Nothing is actually drawn because we set the protected \
+   attribute Ink to zero first. The calculated values are stored in the \
+   Plot structure. */ \
+   } else { \
+      astSetInk( this, 0 ); \
+      astGrid( this ); \
+      astClearInk( this ); \
+\
+/* Assign the result value. */ \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/*
+*
+*  Name:
+*     MAKE_SET3
+
+*  Purpose:
+*     Implement a method to set a single value in a multi-valued attribute
+*     for a Plot. This is identical to MAKE_SET except that no external
+*     interface function is created.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     MAKE_SET3(attr,type,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the Plot class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstPlot *this, int axis, <Type> value )
+*
+*     which implements a method for setting a single value in a specified
+*     multi-valued attribute for a Plot.
+
+*  Parameters:
+ *     attr
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. Label in "astSetLabel"). The
+*        string "Used" is added on to the front of the supplied value.
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+*      nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1). If a value of 0 is supplied, the
+*        value of the Plot's Nin attribute is used instead.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_SET3(attr,type,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void SetUsed##attr( AstPlot *, int, type, int *status ); \
+static void SetUsed##attr( AstPlot *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= ( nval ? nval : astGetNin( this ) ) ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astSetUsed" #attr, astGetClass( this ), \
+                axis + 1, ( nval ? nval : astGetNin( this ) ) ); \
+\
+/* Store the new value in the structure component. */ \
+   } else { \
+      this->component[ axis ] = (assign); \
+   } \
+}
+
+/*
+*+
+*  Name:
+*     MAKE_GET2
+
+*  Purpose:
+*     Implement a method to get an attribute value for a class.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     MAKE_GET2(class,attr,type,bad_value,assign)
+
+*  Class Membership:
+*     Defined by the Plot class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> GetUsed<Attr>( AstPlot *this )
+*
+*     which implement a method for getting a specified attribute value for a
+*     class. Note, no public interface function is created.
+*
+*     The value returned is the value which would actually be used if
+*     astGrid was called with the current set of Plot attributes. This
+*     includes calculating any dynamic defaults which would be used, and is
+*     consequently rather slow.
+
+*  Parameters:
+*     class
+*        The name (not the type) of the class to which the attribute belongs.
+*     attr
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. Label in "astGetLabel"). The
+*        string "Used" is added on to the front of the supplied value.
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set, or if
+*        the function fails.
+*     assign
+*        An expression that evaluates to the value to be returned.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_GET2(class,attr,type,bad_value,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type GetUsed##attr( Ast##class *, int *status ); \
+static type GetUsed##attr( Ast##class *this, int *status ) { \
+   type result;               /* Result to be returned */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* If the attribute is set, use its normal accessor. */\
+   if( astTest##attr( this ) ) {\
+      result = astGet##attr( this );\
+\
+/* Otherwise, re-calculate dynamic defaults by going through the motions of \
+   drawing the grid. Nothing is actually drawn because we set the protected \
+   attribute Ink to zero first. The calculated values are stored in the \
+   Plot structure. */ \
+   } else { \
+      astSetInk( this, 0 ); \
+      astGrid( this ); \
+      astClearInk( this ); \
+\
+/* Assign the result value. */ \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/*
+*+
+*  Name:
+*     MAKE_SET2
+
+*  Purpose:
+*     Implement a method to set an attribute value for a class. This
+*     is identical to astMAKE_SET except that it does not create an
+*     external interface function, and it does create a private function
+*     prototype.
+
+*  Type:
+*     Protected macro.
+
+*  Synopsis:
+*     MAKE_SET2(class,attr,type,component,assign)
+
+*  Class Membership:
+*     Defined by the Plot class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void SetUsed<Attr>( AstPlot *this, <Type> value )
+*
+*     which implements a method for setting a specified attribute value for a
+*     class.
+
+*  Parameters:
+*      class
+*         The name (not the type) of the class to which the attribute belongs.
+*      attr
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. Label in "astSetLabel"). The string "Used" is added
+*         to the front.
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_SET2(class,attr,type,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void SetUsed##attr( Ast##class *, type, int *status ); \
+static void SetUsed##attr( Ast##class *this, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Store the new value in the structure component. */ \
+   this->component = (assign); \
+}
+
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+#include "channel.h"             /* I/O channels */
+#include "cmpmap.h"              /* Compound mapping class */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "frame.h"               /* Coordinate frame descriptions */
+#include "frameset.h"            /* Parent FrameSet class */
+#include "grf.h"                 /* Low-level graphics interface */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "plot.h"                /* Interface definition for this class */
+#include "pointset.h"            /* Class holding lists of positions */
+#include "keymap.h"              /* Hash maps */
+#include "skyaxis.h"             /* Sky coordinate axes */
+#include "skyframe.h"            /* Sky coordinate frames */
+#include "winmap.h"              /* Scale and shift mappings */
+#include "mathmap.h"             /* Algebraic mappings */
+#include "wcsmap.h"              /* FITS-WCS projectsions */
+#include "unitmap.h"             /* Unit mappings */
+#include "permmap.h"             /* Axis permutations */
+#include "region.h"              /* Frame regions */
+#include "globals.h"             /* Thread-safe global data access */
+
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <float.h>
+#include <math.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Module Type Definitions */
+/* ======================= */
+typedef struct LabelList {
+   double index;
+   char *text;
+   double x;
+   double y;
+   char *just;
+   double upx;
+   double upy;
+   double val;
+   int priority;
+   const char *atext;
+   int saved_prio;
+} LabelList;
+
+/* Structure to hold static data used internally within the Crv function. */
+typedef struct CrvStatics {
+   double *pdl2;   /* Pointer to next squared segment length */
+   double *pdx;    /* Pointer to next segment X increment */
+   double *pdy;    /* Pointer to next segment Y increment */
+   double cosang;  /* Cosine of angle between adjacent segments */
+   double d0;      /* Distance to start of first sub-segment */
+   double delta;   /* Distance between adjacent sub-segments */
+   double dl;      /* Segment length in graphics coordinates */
+   double dll;     /* Segment length for previous segment */
+   double last_x;  /* Graphics X at the end of the previous segment */
+   double last_y;  /* Graphics Y at the end of the previous segment */
+   double limit2;  /* Shortest acceptable squared segment length */
+   double t1;      /* Increment in X */
+   double t2;      /* Increment in Y */
+   double t3;      /* Squared segment length */
+   double vx;      /* X component of unit vector for current segment */
+   double vxl;     /* X component of unit vector for previous segment */
+   double vy;      /* Y component of unit vector for current segment */
+   double vyl;     /* Y component of unit vector for previous segment */
+   int *seg0;      /* Pointer to current segment OK flag */
+   int *segm;      /* Pointer to previous segment OK flag */
+   int *segp;      /* Pointer to next segment OK flag */
+   int all_bad;    /* Are all supplied positions bad or clipped?  */
+   int el;         /* Total sub-segment count */
+   int j;          /* Sub-segment index */
+   int last_ok;    /* Was the previous position defined? */
+   int nel;        /* Total number of sub-segments */
+   int nlong;      /* No.of segments longer than limit2 */
+   int nseg;       /* Number of segments being sub-divided */
+   int nshort;     /* No.of segments shorter than limit2 */
+
+#ifdef CRV_TRACE
+   int levels[100];
+#endif
+
+} CrvStatics;
+
+/* Structure to hold static data used internally within the Crv function. */
+typedef struct GetTicksStatics {
+   AstFrame *frame;          /* Pointer to the current Frame */
+   AstMapping *map;          /* Pointer to Base->Current Mapping */
+   AstPointSet *pset;        /* Pointer to a PointSet holding physical coords */
+   double **ptr;             /* Pointer to physical coordinate values */
+   double defgaps[ 2 ];      /* Initial test gaps for each axis */
+   double typval[ 2 ];       /* Typical value on each axis */
+   double width[ 2 ];        /* Range of used axis values */
+   int maxticks;             /* Max. number of ticks on each axis */
+   int mintick;              /* Min. number of ticks on each axis */
+   int ngood[ 2 ];           /* No. of good physical values on each axis */
+   int bad;                  /* Were any bad pixels found? */
+} GetTicksStatics;
+
+/* Structure to hold static data used internally within the EdgeCrossings
+   function. */
+typedef struct EdgeCrossingsStatics {
+   AstFrame *frame;          /* Pointer to current Frame in Plot */
+   AstPointSet *pset1;       /* Graphics cooords at edge samples */
+   AstPointSet *pset2;       /* Physical cooords at edge samples */
+   AstPointSet *pset4;       /* Graphics cooords at offset edge samples */
+   double **ptr1;            /* Pointer to graphics coord. data */
+   double **ptr2;            /* Pointer to physical coord. data */
+   double **ptr4;            /* Pointer to graphics coord. data */
+   double edgehi;            /* High bound on varying graphics axis */
+   double edgelo;            /* Low bound on varying graphics axis */
+   double edgeval;           /* Constant graphics axis value along edge */
+   double limit;             /* Three times the RMS step size */
+   int dim;                  /* Extended number of samples */
+   int edgeax;               /* Graphics axis to which edgeval refers */
+   int paxis;                /* Axis used in first invocation */
+   int pedge;                /* Edge used in first invocation */
+} EdgeCrossingsStatics;
+
+
+/* Structure to hold static data used internally within the Map1
+   function. */
+typedef struct Map1Statics {
+   AstPointSet *pset1;       /* PointSet holding physical coords */
+   AstPointSet *pset2;       /* PointSet holding graphics coords */
+   double **ptr1;            /* Pointer to physical coord data */
+   double *pax;              /* Pointer to start of axis data */
+   double *ptr2[ 2 ];        /* Pointers to graphics coord data */
+   double *work1;            /* Pointer to work space */
+   double *work2;            /* Pointer to work space */
+   double axorig;            /* Distance offset */
+   double axscale;           /* Distance scale */
+   int neg;                  /* Negate axis values? */
+   int nl;                   /* No. of points in pset1 and pset2 */
+} Map1Statics;
+
+/* Structure to hold static data used internally within the Map2
+   function. */
+typedef struct Map2Statics {
+   AstPointSet *pset1;       /* PointSet holding graphics coords */
+   AstPointSet *pset2;       /* PointSet holding physical coords */
+   double **ptr2;            /* Pointer to physical coord data */
+   double *ptr1[ 2 ];        /* Pointers to graphics coord data */
+   int nl;                   /* No. of points in pset1 and pset2 */
+} Map2Statics;
+
+/* Structure to hold static data used internally within the Map3
+   function. */
+typedef struct Map3Statics {
+   AstPointSet *pset1;       /* PointSet holding physical coords */
+   AstPointSet *pset2;       /* PointSet holding graphics coords */
+   double **ptr1;            /* Pointer to physical coord data */
+   double *ptr2[ 2 ];        /* Pointers to graphics coord data */
+   int nc;                   /* No. of physical axes */
+   int nl;                   /* No. of points in pset1 and pset2 */
+   double *pos;              /* Pointer to memory for a single position */
+} Map3Statics;
+
+/* Structure to hold static data used internally within the Map4
+   function. */
+typedef struct Map4Statics {
+   AstPointSet *pset1;       /* PointSet holding distances */
+   AstPointSet *pset2;       /* PointSet holding physical coords */
+   AstPointSet *pset3;       /* PointSet holding graphics coords */
+   int nl;                   /* No. of points in pset1 and pset2 */
+} Map4Statics;
+
+/* Structure to hold static data used internally within the Map5
+   function. */
+typedef struct Map5Statics {
+   AstPointSet *pset1;       /* PointSet holding physical coords */
+   AstPointSet *pset2;       /* PointSet holding graphics coords */
+   double **ptr1;            /* Pointer to physical coord data */
+   double *ptr2[ 2 ];        /* Pointers to graphics coord data */
+   int nl;                   /* No. of points in pset1 and pset2 */
+} Map5Statics;
+
+/* Structure to hold information about tick marks for a single axis. */
+typedef struct TickInfo{
+   int nmajor;               /* No. of major tick marks */
+   int nminor;               /* No. of minor tick marks */
+   double *ticks;            /* Pointer to array of major tick mark values */
+   double *minticks;         /* Pointer to array of minor tick mark values */
+   char **labels;            /* Pointer to array of major tick mark labels */
+   double *start;            /* Start pos'n on other axis for each curve section */
+   double *length;           /* Length on other axis of each curve section */
+   int nsect;                /* No. of sections in curve */
+   char *fmt;                /* Pointer to format string used to create labels */
+   double gap;               /* The gap between major ticks */
+} TickInfo;
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static void (* parent_removeframe)( AstFrameSet *, int, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int,
+                                          AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Strings giving the label for the graphics items corresponding to
+   AST__BORDER_ID, AST__GRIDLINE_ID, etc. */
+static char *GrfLabels = "Border Curves Title Markers Strings Axis1 Axis2 Axis3 "
+                         "NumLab1 NumLab2 NumLab3 TextLab1 TextLab2 TextLab3 "
+                         "Ticks1 Ticks2 Ticks3 Grid1 Grid2 Grid3 Axes NumLab "
+                         "TextLab Grid Ticks";
+
+/* Text values used to represent edges externally. */
+static const char *xedge[4] = { "left", "top", "right", "bottom" };
+
+/* Text values used to represent Labelling externally. */
+static const char *xlbling[2] = { "exterior", "interior" };
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GrfAttrs_nesting_t = 0; \
+   globals->Crv_nent_t = 0; \
+   globals->Box_lbnd_t[ 0 ] = FLT_MAX; \
+   globals->Box_ubnd_t[ 0 ] = FLT_MIN; \
+   globals->Boxp_lbnd_t[ 0 ] = FLT_MAX; \
+   globals->Boxp_ubnd_t[ 0 ] = FLT_MIN; \
+   globals->Box_lbnd_t[ 1 ] = FLT_MAX; \
+   globals->Box_ubnd_t[ 1 ] = FLT_MIN; \
+   globals->Boxp_lbnd_t[ 1 ] = FLT_MAX; \
+   globals->Boxp_ubnd_t[ 1 ] = FLT_MIN; \
+   globals->Boxp_freeze_t = 0; \
+   globals->Map1_plot_t = NULL; \
+   globals->Map1_map_t = NULL; \
+   globals->Map1_frame_t = NULL; \
+   globals->Map1_origin_t = NULL; \
+   globals->Map1_statics_t = NULL; \
+   globals->Map2_plot_t = NULL; \
+   globals->Map2_map_t = NULL; \
+   globals->Map2_statics_t = NULL; \
+   globals->Map3_plot_t = NULL; \
+   globals->Map3_map_t = NULL; \
+   globals->Map3_frame_t = NULL; \
+   globals->Map3_origin_t = NULL; \
+   globals->Map3_end_t = NULL; \
+   globals->Map3_statics_t = NULL; \
+   globals->Map4_plot_t = NULL; \
+   globals->Map4_map_t = NULL; \
+   globals->Map4_umap_t = NULL; \
+   globals->Map4_statics_t = NULL; \
+   globals->Map5_plot_t = NULL; \
+   globals->Map5_region_t = NULL; \
+   globals->Map5_map_t = NULL; \
+   globals->Map5_statics_t = NULL; \
+   globals->Poly_n_t = 0; \
+   globals->Poly_x_t = NULL; \
+   globals->Poly_y_t = NULL; \
+   globals->Poly_npoly_t = 0; \
+   globals->Poly_np_t = NULL; \
+   globals->Poly_xp_t = NULL; \
+   globals->Poly_yp_t = NULL; \
+   globals->Curve_data_t.nbrk = -1; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->SplitValue_Buff[ 0 ] = 0; \
+   globals->StripEscapes_Buff[ 0 ] = 0; \
+   globals->Grf_chv_t = AST__BAD; \
+   globals->Grf_chh_t = AST__BAD; \
+   globals->Grf_alpha_t = 0.0; \
+   globals->Grf_beta_t = 0.0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Plot)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Plot,Class_Init)
+#define class_vtab astGLOBAL(Plot,Class_Vtab)
+#define grfattrs_nesting astGLOBAL(Plot,GrfAttrs_nesting_t)
+#define grfattrs_attrs  astGLOBAL(Plot,GrfAttrs_attrs_t)
+#define Crv_limit 	astGLOBAL(Plot,Crv_limit_t)
+#define Crv_scerr 	astGLOBAL(Plot,Crv_scerr_t)
+#define Crv_tol 	astGLOBAL(Plot,Crv_tol_t)
+#define Crv_ux0 	astGLOBAL(Plot,Crv_ux0_t)
+#define Crv_uy0 	astGLOBAL(Plot,Crv_uy0_t)
+#define Crv_vxl 	astGLOBAL(Plot,Crv_vxl_t)
+#define Crv_vyl 	astGLOBAL(Plot,Crv_vyl_t)
+#define Crv_xhi 	astGLOBAL(Plot,Crv_xhi_t)
+#define Crv_xl 		astGLOBAL(Plot,Crv_xl_t)
+#define Crv_xlo 	astGLOBAL(Plot,Crv_xlo_t)
+#define Crv_yhi 	astGLOBAL(Plot,Crv_yhi_t)
+#define Crv_yl 		astGLOBAL(Plot,Crv_yl_t)
+#define Crv_ylo 	astGLOBAL(Plot,Crv_ylo_t)
+#define Crv_vxbrk 	astGLOBAL(Plot,Crv_vxbrk_t)
+#define Crv_vybrk 	astGLOBAL(Plot,Crv_vybrk_t)
+#define Crv_xbrk 	astGLOBAL(Plot,Crv_xbrk_t)
+#define Crv_ybrk 	astGLOBAL(Plot,Crv_ybrk_t)
+#define Crv_len 	astGLOBAL(Plot,Crv_len_t)
+#define Crv_ink 	astGLOBAL(Plot,Crv_ink_t)
+#define Crv_nbrk 	astGLOBAL(Plot,Crv_nbrk_t)
+#define Crv_nent 	astGLOBAL(Plot,Crv_nent_t)
+#define Crv_out 	astGLOBAL(Plot,Crv_out_t)
+#define Crv_clip 	astGLOBAL(Plot,Crv_clip_t)
+#define Crv_map		astGLOBAL(Plot,Crv_map_t)
+#define Box_lbnd 	astGLOBAL(Plot,Box_lbnd_t)
+#define Box_ubnd 	astGLOBAL(Plot,Box_ubnd_t)
+#define Boxp_lbnd 	astGLOBAL(Plot,Boxp_lbnd_t)
+#define Boxp_ubnd 	astGLOBAL(Plot,Boxp_ubnd_t)
+#define Boxp_freeze 	astGLOBAL(Plot,Boxp_freeze_t)
+#define Poly_x 		astGLOBAL(Plot,Poly_x_t)
+#define Poly_y 		astGLOBAL(Plot,Poly_y_t)
+#define Poly_n 		astGLOBAL(Plot,Poly_n_t)
+#define Poly_xp 	astGLOBAL(Plot,Poly_xp_t)
+#define Poly_yp 	astGLOBAL(Plot,Poly_yp_t)
+#define Poly_np 	astGLOBAL(Plot,Poly_np_t)
+#define Poly_npoly      astGLOBAL(Plot,Poly_npoly_t)
+#define Map1_ncoord 	astGLOBAL(Plot,Map1_ncoord_t)
+#define Map1_plot 	astGLOBAL(Plot,Map1_plot_t)
+#define Map1_map 	astGLOBAL(Plot,Map1_map_t)
+#define Map1_frame 	astGLOBAL(Plot,Map1_frame_t)
+#define Map1_origin 	astGLOBAL(Plot,Map1_origin_t)
+#define Map1_length 	astGLOBAL(Plot,Map1_length_t)
+#define Map1_axis 	astGLOBAL(Plot,Map1_axis_t)
+#define Map1_statics 	astGLOBAL(Plot,Map1_statics_t)
+#define Map1_norm 	astGLOBAL(Plot,Map1_norm_t)
+#define Map1_log 	astGLOBAL(Plot,Map1_log_t)
+#define Map2_ncoord 	astGLOBAL(Plot,Map2_ncoord_t)
+#define Map2_plot 	astGLOBAL(Plot,Map2_plot_t)
+#define Map2_map 	astGLOBAL(Plot,Map2_map_t)
+#define Map2_x0 	astGLOBAL(Plot,Map2_x0_t)
+#define Map2_y0 	astGLOBAL(Plot,Map2_y0_t)
+#define Map2_deltax 	astGLOBAL(Plot,Map2_deltax_t)
+#define Map2_deltay 	astGLOBAL(Plot,Map2_deltay_t)
+#define Map2_statics 	astGLOBAL(Plot,Map1_statics_t)
+#define Map3_ncoord 	astGLOBAL(Plot,Map3_ncoord_t)
+#define Map3_plot 	astGLOBAL(Plot,Map3_plot_t)
+#define Map3_map 	astGLOBAL(Plot,Map3_map_t)
+#define Map3_frame 	astGLOBAL(Plot,Map3_frame_t)
+#define Map3_origin 	astGLOBAL(Plot,Map3_origin_t)
+#define Map3_end 	astGLOBAL(Plot,Map3_end_t)
+#define Map3_scale 	astGLOBAL(Plot,Map3_scale_t)
+#define Map3_statics 	astGLOBAL(Plot,Map3_statics_t)
+#define Map4_ncoord 	astGLOBAL(Plot,Map4_ncoord_t)
+#define Map4_plot 	astGLOBAL(Plot,Map4_plot_t)
+#define Map4_map 	astGLOBAL(Plot,Map4_map_t)
+#define Map4_umap 	astGLOBAL(Plot,Map4_umap_t)
+#define Map4_statics 	astGLOBAL(Plot,Map4_statics_t)
+#define Map5_plot 	astGLOBAL(Plot,Map5_plot_t)
+#define Map5_region 	astGLOBAL(Plot,Map5_region_t)
+#define Map5_map 	astGLOBAL(Plot,Map5_map_t)
+#define Map5_ncoord 	astGLOBAL(Plot,Map5_ncoord_t)
+#define Map5_statics 	astGLOBAL(Plot,Map5_statics_t)
+#define Curve_data 	astGLOBAL(Plot,Curve_data_t)
+#define getattrib_buff  astGLOBAL(Plot,GetAttrib_Buff)
+#define splitvalue_buff astGLOBAL(Plot,SplitValue_Buff)
+#define stripescapes_buff astGLOBAL(Plot,StripEscapes_Buff)
+#define Grf_chv 	astGLOBAL(Plot,Grf_chv_t)
+#define Grf_chh 	astGLOBAL(Plot,Grf_chh_t)
+#define Grf_alpha 	astGLOBAL(Plot,Grf_alpha_t)
+#define Grf_beta 	astGLOBAL(Plot,Grf_beta_t)
+
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Variables used within astGrfAttrs_ */
+static double grfattrs_attrs[ GRF__NATTR ]; /* Saved attribute values */
+static int grfattrs_nesting = 0;            /* Nesting level. */
+
+/* Variables used to pass information to the curve drawing functions. See
+   the prologues of functions Crv and CrvLine for details. */
+static double Crv_limit;
+static double Crv_scerr;
+static double Crv_tol;
+static double Crv_ux0;
+static double Crv_uy0;
+static double Crv_vxl;
+static double Crv_vyl;
+static double Crv_xhi;
+static double Crv_xl;
+static double Crv_xlo;
+static double Crv_yhi;
+static double Crv_yl;
+static double Crv_ylo;
+static float *Crv_vxbrk;
+static float *Crv_vybrk;
+static float *Crv_xbrk;
+static float *Crv_ybrk;
+static float Crv_len;
+static int Crv_ink;
+static int Crv_nbrk;
+static int Crv_nent = 0;
+static int Crv_out;
+static int Crv_clip;
+static void (*Crv_map)( int, double *, double *, double *, const char *, const char *, int * );
+
+/* A cache of information calculated by the grf module. */
+static double Grf_chv = AST__BAD;
+static double Grf_chh = AST__BAD;
+static float Grf_alpha = 0.0;
+static float Grf_beta = 0.0;
+
+/* The lower and upper bounds of the graphics coordinates enclosing all
+   lines and numerical labels drawn by astGrid. */
+static float Box_lbnd[ 2 ] = {FLT_MAX, FLT_MAX };
+static float Box_ubnd[ 2 ] = {FLT_MIN, FLT_MIN };
+
+/* The lower and upper bounds of the graphics coordinates enclosing all
+   drawn graphics primatives, maintained by functions GLine, GMark and
+   DrawText. */
+static float Boxp_lbnd[ 2 ] = {FLT_MAX, FLT_MAX };
+static float Boxp_ubnd[ 2 ] = {FLT_MIN, FLT_MIN };
+static int Boxp_freeze = 0;
+
+/* Variables used to stored buffered poly lines (see functions Opoly, Bpoly
+   and Apoly). */
+static float        *Poly_x = NULL;
+static float        *Poly_y = NULL;
+static int           Poly_n = 0;
+static float       **Poly_xp = NULL;
+static float       **Poly_yp = NULL;
+static int          *Poly_np = NULL;
+static int           Poly_npoly = 0;
+
+/* Variables used by function Map1. See the prologue of Map1 for details. */
+static int           Map1_ncoord;
+static AstPlot      *Map1_plot = NULL;
+static AstMapping   *Map1_map = NULL;
+static AstFrame     *Map1_frame = NULL;
+static const double *Map1_origin = NULL;
+static double        Map1_length;
+static void         *Map1_statics = NULL;
+static int           Map1_axis;
+static int           Map1_norm;
+static int           Map1_log;
+
+/* Variables used by function Map2. See the prologue of Map2 for details. */
+static int           Map2_ncoord;
+static AstPlot      *Map2_plot = NULL;
+static AstMapping   *Map2_map = NULL;
+static double        Map2_x0;
+static double        Map2_y0;
+static double        Map2_deltax;
+static double        Map2_deltay;
+static void         *Map2_statics = NULL;
+
+/* Variables used by function Map3. See the prologue of Map3 for details. */
+static int           Map3_ncoord;
+static AstPlot      *Map3_plot = NULL;
+static AstMapping   *Map3_map = NULL;
+static AstFrame     *Map3_frame = NULL;
+static const double *Map3_origin = NULL;
+static const double *Map3_end = NULL;
+static double        Map3_scale;
+static void         *Map3_statics = NULL;
+
+/* Variables used by function Map4. See the prologue of Map4 for details. */
+static int           Map4_ncoord;
+static AstPlot      *Map4_plot = NULL;
+static AstMapping   *Map4_map = NULL;
+static AstMapping   *Map4_umap = NULL;
+static void         *Map4_statics = NULL;
+
+/* Variables used by function Map5. See the prologue of Map5 for details. */
+static AstPlot      *Map5_plot = NULL;
+static AstMapping   *Map5_map = NULL;
+static AstRegion    *Map5_region = NULL;
+static void         *Map5_statics = NULL;
+static int           Map5_ncoord;
+
+/* A structure which stores information about the breaks in the last curve
+   drawn using the public methods "astGridLine" and "astCurve". */
+static AstPlotCurveData Curve_data;
+
+/* Buffers for strings returned by various functions. */
+static char splitvalue_buff[ 200 ];
+static char stripescapes_buff[ AST__PLOT_STRIPESCAPES_BUFF_LEN + 1 ];
+static char getattrib_buff[ 200 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstPlotVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+
+#endif
+
+/* Macro to reset the cache of values caclulated by the grf module. */
+#define RESET_GRF \
+   Grf_chh = AST__BAD; \
+   Grf_chv = AST__BAD; \
+   Grf_alpha = 0.0; \
+   Grf_beta = 0.0;
+
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+
+static double GetTol( AstPlot *, int * );
+static int TestTol( AstPlot *, int * );
+static void ClearTol( AstPlot *, int * );
+static void SetTol( AstPlot *, double, int * );
+
+static int GetGrid( AstPlot *, int * );
+static int TestGrid( AstPlot *, int * );
+static void ClearGrid( AstPlot *, int * );
+static void SetGrid( AstPlot *, int, int * );
+
+static int GetTickAll( AstPlot *, int * );
+static int TestTickAll( AstPlot *, int * );
+static void ClearTickAll( AstPlot *, int * );
+static void SetTickAll( AstPlot *, int, int * );
+
+static int GetForceExterior( AstPlot *, int * );
+static int TestForceExterior( AstPlot *, int * );
+static void ClearForceExterior( AstPlot *, int * );
+static void SetForceExterior( AstPlot *, int, int * );
+
+static int GetBorder( AstPlot *, int * );
+static int TestBorder( AstPlot *, int * );
+static void ClearBorder( AstPlot *, int * );
+static void SetBorder( AstPlot *, int, int * );
+
+static int GetInvisible( AstPlot *, int * );
+static int TestInvisible( AstPlot *, int * );
+static void ClearInvisible( AstPlot *, int * );
+static void SetInvisible( AstPlot *, int, int * );
+
+static int GetInk( AstPlot *, int * );
+static int TestInk( AstPlot *, int * );
+static void ClearInk( AstPlot *, int * );
+static void SetInk( AstPlot *, int, int * );
+
+static int GetClipOp( AstPlot *, int * );
+static int TestClipOp( AstPlot *, int * );
+static void ClearClipOp( AstPlot *, int * );
+static void SetClipOp( AstPlot *, int, int * );
+
+static int GetClip( AstPlot *, int * );
+static int TestClip( AstPlot *, int * );
+static void ClearClip( AstPlot *, int * );
+static void SetClip( AstPlot *, int, int * );
+
+static int GetGrf( AstPlot *, int * );
+static int TestGrf( AstPlot *, int * );
+static void ClearGrf( AstPlot *, int * );
+static void SetGrf( AstPlot *, int, int * );
+
+static int GetDrawTitle( AstPlot *, int * );
+static int TestDrawTitle( AstPlot *, int * );
+static void ClearDrawTitle( AstPlot *, int * );
+static void SetDrawTitle( AstPlot *, int, int * );
+
+static int GetDrawAxes( AstPlot *, int, int * );
+static int TestDrawAxes( AstPlot *, int, int * );
+static void ClearDrawAxes( AstPlot *, int, int * );
+static void SetDrawAxes( AstPlot *, int, int, int * );
+
+static int GetAbbrev( AstPlot *, int, int * );
+static int TestAbbrev( AstPlot *, int, int * );
+static void ClearAbbrev( AstPlot *, int, int * );
+static void SetAbbrev( AstPlot *, int, int, int * );
+
+static int GetEscape( AstPlot *, int * );
+static int TestEscape( AstPlot *, int * );
+static void ClearEscape( AstPlot *, int * );
+static void SetEscape( AstPlot *, int, int * );
+
+static double GetLabelAt( AstPlot *, int, int * );
+static int TestLabelAt( AstPlot *, int, int * );
+static void ClearLabelAt( AstPlot *, int, int * );
+static void SetLabelAt( AstPlot *, int, double, int * );
+
+static double GetNumLabGap( AstPlot *, int, int * );
+static int TestNumLabGap( AstPlot *, int, int * );
+static void ClearNumLabGap( AstPlot *, int, int * );
+static void SetNumLabGap( AstPlot *, int, double, int * );
+
+static double GetTextLabGap( AstPlot *, int, int * );
+static int TestTextLabGap( AstPlot *, int, int * );
+static void ClearTextLabGap( AstPlot *, int, int * );
+static void SetTextLabGap( AstPlot *, int, double, int * );
+
+static double GetCentre( AstPlot *, int, int * );
+static int TestCentre( AstPlot *, int, int * );
+static void ClearCentre( AstPlot *, int, int * );
+static void SetCentre( AstPlot *, int, double, int * );
+
+static double GetGap( AstPlot *, int, int * );
+static int TestGap( AstPlot *, int, int * );
+static void ClearGap( AstPlot *, int, int * );
+static void SetGap( AstPlot *, int, double, int * );
+
+static int GetLabelling( AstPlot *, int * );
+static int TestLabelling( AstPlot *, int * );
+static void ClearLabelling( AstPlot *, int * );
+static void SetLabelling( AstPlot *, int, int * );
+
+static double GetMajTickLen( AstPlot *, int, int * );
+static int TestMajTickLen( AstPlot *, int, int * );
+static void ClearMajTickLen( AstPlot *, int, int * );
+static void SetMajTickLen( AstPlot *, int, double, int * );
+
+static double GetLogGap( AstPlot *, int, int * );
+static int TestLogGap( AstPlot *, int, int * );
+static void ClearLogGap( AstPlot *, int, int * );
+static void SetLogGap( AstPlot *, int, double, int * );
+
+static double GetTitleGap( AstPlot *, int * );
+static int TestTitleGap( AstPlot *, int * );
+static void ClearTitleGap( AstPlot *, int * );
+static void SetTitleGap( AstPlot *, double, int * );
+
+static double GetMinTickLen( AstPlot *, int, int * );
+static int TestMinTickLen( AstPlot *, int, int * );
+static void ClearMinTickLen( AstPlot *, int, int * );
+static void SetMinTickLen( AstPlot *, int, double, int * );
+
+static int GetEdge( AstPlot *, int, int * );
+static int TestEdge( AstPlot *, int, int * );
+static void ClearEdge( AstPlot *, int, int * );
+static void SetEdge( AstPlot *, int, int, int * );
+
+static int GetLabelUp( AstPlot *, int, int * );
+static int TestLabelUp( AstPlot *, int, int * );
+static void ClearLabelUp( AstPlot *, int, int * );
+static void SetLabelUp( AstPlot *, int, int, int * );
+
+static int GetLogPlot( AstPlot *, int, int * );
+static int TestLogPlot( AstPlot *, int, int * );
+static void ClearLogPlot( AstPlot *, int, int * );
+static void SetLogPlot( AstPlot *, int, int, int * );
+
+static int GetLogTicks( AstPlot *, int, int * );
+static int TestLogTicks( AstPlot *, int, int * );
+static void ClearLogTicks( AstPlot *, int, int * );
+static void SetLogTicks( AstPlot *, int, int, int * );
+
+static int GetLogLabel( AstPlot *, int, int * );
+static int TestLogLabel( AstPlot *, int, int * );
+static void ClearLogLabel( AstPlot *, int, int * );
+static void SetLogLabel( AstPlot *, int, int, int * );
+
+static int GetNumLab( AstPlot *, int, int * );
+static int TestNumLab( AstPlot *, int, int * );
+static void ClearNumLab( AstPlot *, int, int * );
+static void SetNumLab( AstPlot *, int, int, int * );
+
+static int GetMinTick( AstPlot *, int, int * );
+static int TestMinTick( AstPlot *, int, int * );
+static void ClearMinTick( AstPlot *, int, int * );
+static void SetMinTick( AstPlot *, int, int, int * );
+
+static int GetTextLab( AstPlot *, int, int * );
+static int TestTextLab( AstPlot *, int, int * );
+static void ClearTextLab( AstPlot *, int, int * );
+static void SetTextLab( AstPlot *, int, int, int * );
+
+static int GetLabelUnits( AstPlot *, int, int * );
+static int TestLabelUnits( AstPlot *, int, int * );
+static void ClearLabelUnits( AstPlot *, int, int * );
+static void SetLabelUnits( AstPlot *, int, int, int * );
+
+static int GetStyle( AstPlot *, int, int * );
+static int TestStyle( AstPlot *, int, int * );
+static void ClearStyle( AstPlot *, int, int * );
+static void SetStyle( AstPlot *, int, int, int * );
+
+static int GetFont( AstPlot *, int, int * );
+static int TestFont( AstPlot *, int, int * );
+static void ClearFont( AstPlot *, int, int * );
+static void SetFont( AstPlot *, int, int, int * );
+
+static int GetColour( AstPlot *, int, int * );
+static int TestColour( AstPlot *, int, int * );
+static void ClearColour( AstPlot *, int, int * );
+static void SetColour( AstPlot *, int, int, int * );
+
+static double GetWidth( AstPlot *, int, int * );
+static int TestWidth( AstPlot *, int, int * );
+static void ClearWidth( AstPlot *, int, int * );
+static void SetWidth( AstPlot *, int, double, int * );
+
+static double GetSize( AstPlot *, int, int * );
+static int TestSize( AstPlot *, int, int * );
+static void ClearSize( AstPlot *, int, int * );
+static void SetSize( AstPlot *, int, double, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+static AstFrameSet *Fset2D( AstFrameSet *, int, int * );
+static AstKeyMap *GetGrfContext( AstPlot *, int * );
+static AstPlotCurveData **CleanCdata( AstPlotCurveData **, int * );
+static AstPlotCurveData **DrawGrid( AstPlot *, TickInfo **, int, const char *, const char *, int * );
+static AstPointSet *DefGap( AstPlot *, double *, int *, double *, int *, const char *, const char *, int * );
+static AstPointSet *GetDrawnTicks( AstPlot *, int, int, int * );
+static AstPointSet *Trans( AstPlot *, AstFrame *, AstMapping *, AstPointSet *, int, AstPointSet *, int, const char *, const char *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static TickInfo **CleanGrid( TickInfo **, int * );
+static TickInfo **GridLines( AstPlot *, double *, double *, int *, const char *, const char *, int * );
+static TickInfo *TickMarks( AstPlot *, int, double *, double *, int *, GetTicksStatics **, const char *, const char *, int * );
+static char **CheckLabels2( AstPlot *, AstFrame *, int, double *, int, char **, double, int * );
+static char *FindWord( char *, const char *, const char **, int * );
+static char *GrfItem( int, const char *, int *, int * );
+static const char *JustMB( AstPlot *, int, const char *, float *, float *, float, float, const char *, float, float, float, float, float *, float *, const char *, const char *, int * );
+static const char *SplitValue( AstPlot *, const char *, int, int *, int * );
+static double **MakeGrid( AstPlot *, AstFrame *, AstMapping *, int, int, double, double, double, double, int, AstPointSet **, AstPointSet**, int, const char *, const char *, int * );
+static double GetTicks( AstPlot *, int, double *, double **, int *, double **, int *, int, int *, double *, GetTicksStatics **, const char *, const char *, int * );
+static double GetUseSize( AstPlot *, int, int * );
+static double GetUseWidth( AstPlot *, int, int * );
+static double GoodGrid( AstPlot *, int *, AstPointSet **, AstPointSet **, const char *, const char *, int * );
+static double Typical( int, double *, double, double, double *, int * );
+static int Border( AstPlot *, int * );
+static int Boundary( AstPlot *, const char *, const char *, int * );
+static int BoxCheck( float *, float *, float *, float *, int * );
+static int CGAttrWrapper( AstPlot *, int, double, double *, int, int * );
+static int CGBBufWrapper( AstPlot *, int * );
+static int CGCapWrapper( AstPlot *, int, int, int * );
+static int CGEBufWrapper( AstPlot *, int * );
+static int CGFlushWrapper( AstPlot *, int * );
+static int CGLineWrapper( AstPlot *, int, const float *, const float *, int * );
+static int CGMarkWrapper( AstPlot *, int, const float *, const float *, int, int * );
+static int CGQchWrapper( AstPlot *, float *, float *, int * );
+static int CGScalesWrapper( AstPlot *, float *, float *, int * );
+static int CGTextWrapper( AstPlot *, const char *, float, float, const char *, float, float, int * );
+static int CGTxExtWrapper( AstPlot *, const char *, float, float, const char *, float, float, float *, float *, int * );
+static int CheckLabels( AstPlot *, AstFrame *, int, double *, int, int, char **, double, int * );
+static int ChrLen( const char *, int * );
+static int Compare_LL( const void *, const void * );
+static int Compared( const void *, const void * );
+static int CountGood( int, double *, int * );
+static int Cross( float, float, float, float, float, float, float, float, int * );
+static int CvBrk( AstPlot *, int, double *, double *, double *, int * );
+static int DrawRegion( AstPlot *, AstFrame *, const char *, const char *, int * );
+static int EdgeCrossings( AstPlot *, int, int, double, double *, double **, EdgeCrossingsStatics **, const char *, const char *, int * );
+static int EdgeLabels( AstPlot *, int, TickInfo **, AstPlotCurveData **, int, const char *, const char *, int * );
+static int FindDPTZ( AstFrame *, int, const char *, const char *, int *, int *, int * );
+static int FindMajTicks( AstMapping *, AstFrame *, int, double, double, double , double *, int, double *, double **, int * );
+static int FindMajTicks2( int, double, double, int, double *, double **, int * );
+static int FindString( int, const char *[], const char *, const char *, const char *, const char *, int * );
+static int Fpoly_ecmp( const void *, const void * );
+static int Fpoly_scmp( const void *, const void * );
+static int FullForm( const char *, const char *, const char *, const char *, const char *, int * );
+static int GCap( AstPlot *, int, int, int * );
+static int GVec( AstPlot *, AstMapping *, double *, int, double, AstPointSet **, AstPointSet **, double *, double *, double *, double *, int *, const char *, const char *, int * );
+static int GetUseColour( AstPlot *, int, int * );
+static int GetUseFont( AstPlot *, int, int * );
+static int GetUseStyle( AstPlot *, int, int * );
+static int GraphGrid( int, int, double, double, double, double, double **, int * );
+static int HasEscapes( const char *, int * );
+static int IdFind( int, int, int *, int *, int *, int * );
+static int Inside( int, float *, float *, float, float, int * );
+static int IsASkyAxis( AstFrame *, int, int * );
+static int IsASkyFrame( AstObject *, int * );
+static int Labelat( AstPlot *, TickInfo **, AstPlotCurveData **, double *, const char *, const char *, int * );
+static int Overlap( AstPlot *, int, int, const char *, float, float, const char *, float, float, float **, const char *, const char *, int * );
+static int PopGat( AstPlot *, float *, const char *, const char *, int * );
+static int TestUseColour( AstPlot *, int, int * );
+static int TestUseFont( AstPlot *, int, int * );
+static int TestUseSize( AstPlot *, int, int * );
+static int TestUseStyle( AstPlot *, int, int * );
+static int TestUseWidth( AstPlot *, int, int * );
+static int ToggleLogLin( AstPlot *, int, int, const char *, int * );
+static int TraceBorder( AstPlot *, AstMapping *, double, double, double, double, int, double, int[ 4 ], const char *, const char *, int * );
+static int Ustrcmp( const char *, const char *, int * );
+static int Ustrncmp( const char *, const char *, size_t, int * );
+static int swapEdges( AstPlot *, TickInfo **, AstPlotCurveData **, int * );
+static void AddCdt( AstPlotCurveData *, AstPlotCurveData *, const char *, const char *, int * );
+static void Apoly( AstPlot *, float, float, int * );
+static void AxPlot( AstPlot *, int, const double *, double, int, AstPlotCurveData *, const char *, const char *, int * );
+static void BBuf( AstPlot *, int * );
+static void BoundingBox( AstPlot *, float[2], float[2], int * );
+static void Bpoly( AstPlot *, float, float, int * );
+static void Clip( AstPlot *, int, const double [], const double [], int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void CopyPlotDefaults( AstPlot *, int, AstPlot *, int, int * );
+static void Crv( AstPlot *this, double *, double *, double *, int, double *, CrvStatics *, const char *, const char *, int * );
+static void CrvLine( AstPlot *this, double, double, double, double, const char *, const char *, int * );
+static void Curve( AstPlot *, const double [], const double [], int * );
+static void CurvePlot( AstPlot *, const double *, const double *, int , AstPlotCurveData *, const char *, const char *, int * );
+static void Delete( AstObject *, int * );
+static void DrawAxis( AstPlot *, TickInfo **, double *, double *, const char *, const char *, int * );
+static void DrawText( AstPlot *, int, int, const char *, float, float, const char *, float, float, float *, float *, float *, const char *, const char *, int * );
+static void DrawTicks( AstPlot *, TickInfo **, int, double *, double *, const char *, const char *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void EBuf( AstPlot *, int * );
+static void Fpoly( AstPlot *, const char *, const char *, int * );
+static void GAttr( AstPlot *, int, double, double *, int, const char *, const char *, int * );
+static void GBBuf( AstPlot *, const char *, const char *, int * )__attribute__((unused));
+static void GEBuf( AstPlot *, const char *, const char *, int * )__attribute__((unused));
+static void GFlush( AstPlot *, const char *, const char *, int * )__attribute__((unused));
+static void GLine( AstPlot *, int, const float *, const float *, const char *, const char *, int * );
+static void GMark( AstPlot *, int, const float *, const float *, int, const char *, const char *, int * );
+static void GQch( AstPlot *, float *, float *, const char *, const char *, int * );
+static void GScales( AstPlot *, float *, float *, const char *, const char *, int * );
+static void GText( AstPlot *, const char *, float, float, const char *, float, float, const char *, const char *, int * );
+static void GTxExt( AstPlot *, const char *, float , float, const char *, float, float, float *, float *, const char *, const char *, int * );
+static void GenCurve( AstPlot *, AstMapping *, int * );
+static void GrfPop( AstPlot *, int * );
+static void GrfPush( AstPlot *, int * );
+static void GrfSet( AstPlot *, const char *,  AstGrfFun, int * );
+static void GrfWrapper( AstPlot *, const char *,  AstGrfWrap, int * );
+static void Grid( AstPlot *, int * );
+static void GridLine( AstPlot *, int, const double [], double, int * );
+static void InterpEscape( AstPlot *, int, double, float *, float *, float, float, float, float, const char *, float *, double, double, double, double, double, const char *, const char *, int * );
+static void Labels( AstPlot *, TickInfo **, AstPlotCurveData **, double *, double *, const char *, const char *, int * );
+static void LinePlot( AstPlot *, double, double, double, double, int, AstPlotCurveData *, const char *, const char *, int * );
+static void Map1( int, double *, double *, double *, const char *, const char *, int * GLOBALS_PROTO );
+static void Map2( int, double *, double *, double *, const char *, const char *, int * GLOBALS_PROTO );
+static void Map3( int, double *, double *, double *, const char *, const char *, int * GLOBALS_PROTO );
+static void Map4( int, double *, double *, double *, const char *, const char *, int * GLOBALS_PROTO );
+static void Map5( int, double *, double *, double *, const char *, const char *, int * GLOBALS_PROTO );
+static void Mark( AstPlot *, int, int, int, const double *, int, int * );
+static void Mirror( AstPlot *, int, int * );
+static void Norm1( AstMapping *, int, int, double *, double, double, int * );
+static void Opoly( AstPlot *, int * );
+static void PlotLabels( AstPlot *, int, AstFrame *, int, LabelList *, char *, int, float **, const char *, const char *, int * );
+static void PolyCurve( AstPlot *, int, int, int, const double *, int * );
+static void PurgeCdata( AstPlotCurveData *, int * );
+static void PushGat( AstPlot *, float, const char *, const char *, int * );
+static void RegionOutline( AstPlot *, AstRegion *, int * );
+static void RemoveFrame( AstFrameSet *, int, int * );
+static void RightVector( AstPlot *, float *, float *, float *, float *, const char *, const char *, int * );
+static void SaveTick( AstPlot *, int, double, double, int, int * );
+static void SetTickValues( AstPlot *, int, int, double *, int, double *, int * );
+static void Text( AstPlot *, const char *, const double [], const float [], const char *, int * );
+static void TextLabels( AstPlot *, int, int *, const char *, const char *, int * );
+static void Ticker( AstPlot *, int, int, double, double *, double, int, int, EdgeCrossingsStatics **, const char *, const char *, int * );
+static void UpdateConcat( float *, float *, float, float, float, float, float *, float *, float, float, float *, float *, float *, float *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Functions which access class attributes. */
+/* =======================================  */
+/* Implement member functions to access the attributes associated with this
+   class using the macros defined for this purpose in the "object.h" file. For
+   a description of each attribute, see the class interface (in the associated
+   .h file). */
+
+/* Tol. */
+/* ---- */
+/*
+*att++
+*  Name:
+*     Tol
+
+*  Purpose:
+*     Plotting tolerance.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute specifies the plotting tolerance (or resolution)
+*     to be used for the graphical output produced by a Plot.  Smaller
+*     values will result in smoother and more accurate curves being
+*     drawn, but may slow down the plotting process. Conversely,
+*     larger values may speed up the plotting process in cases where
+*     high resolution is not required.
+*
+*     The Tol value should be given as a fraction of the minimum
+*     dimension of the plotting area, and should lie in the range
+c     from 1.0e-7 to 1.0. By default, a value of 0.01 is used.
+f     from 1.0E-7 to 1.0. By default, a value of 0.01 is used.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+*att--
+*/
+/* The plotting tolerance. Has a value of -1.0 when not set yielding a
+default value of 0.01. Usable values are in the range 1.0E-7 to 1.0. */
+astMAKE_CLEAR(Plot,Tol,tol,-1.0)
+astMAKE_GET(Plot,Tol,double,0.01,(this->tol == -1.0 ? 0.01 : this->tol))
+astMAKE_SET(Plot,Tol,double,tol,astMIN(astMAX(value,1.0E-7),1.0))
+astMAKE_TEST(Plot,Tol,( this->tol != -1.0 ))
+
+/* Grid. */
+/* ----- */
+/*
+*att++
+*  Name:
+*     Grid
+
+*  Purpose:
+*     Draw grid lines for a Plot?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether grid lines (a grid of curves marking the "major" values
+*     on each axis) are drawn across the plotting area.
+*
+*     If the Grid value of a Plot is non-zero, then grid lines will be
+*     drawn. Otherwise, short tick marks on the axes are used to mark
+*     the major axis values.  The default behaviour is to use tick
+*     marks if the entire plotting area is filled by valid physical
+*     coordinates, but to draw grid lines otherwise.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The spacing between major axis values, which determines the
+*     spacing of grid lines, may be set using the Gap(axis) attribute.
+*att--
+*/
+/* If non-zero use lines instead of tick marks in a coordinate grid. Has a
+value of -1 when not set yielding a default value of 0. */
+astMAKE_CLEAR(Plot,Grid,grid,-1)
+astMAKE_GET(Plot,Grid,int,0,(this->grid == -1 ? 0 : this->grid))
+astMAKE_SET(Plot,Grid,int,grid,( value ? 1 : 0 ))
+astMAKE_TEST(Plot,Grid,( this->grid != -1 ))
+
+MAKE_GET2(Plot,Grid,int,0,this->ugrid)
+MAKE_SET2(Plot,Grid,int,ugrid,( value ? 1 : 0 ))
+
+/* Invisible. */
+/* ---------- */
+/*
+*att++
+*  Name:
+*     Invisible
+
+*  Purpose:
+*     Draw graphics using invisible ink?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of all graphics produced by
+*     Plot methods by determining whether graphics should be visible or
+*     invisible.
+*
+*     If the Invisible value of a Plot is non-zero, then all the Plot
+*     methods which normally generate graphical output do not do so (you
+*     can think of them drawing with "invisible ink"). Such methods do,
+*     however, continue to do all the calculations which would be needed to
+*     produce the graphics. In particular, the bounding box enclosing the
+*     graphics is still calculated and can be retrieved as normal using
+c     astBoundingBox. The default value is zero, resulting in all methods
+f     AST_BOUNDINGBOX. The default value is zero, resulting in all methods
+*     drawing graphics as normal, using visible ink.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*att--
+*/
+/* If non-zero use invisible ink. Has a value of -1 when not set yielding
+a default value of 0. */
+astMAKE_CLEAR(Plot,Invisible,invisible,-1)
+astMAKE_GET(Plot,Invisible,int,0,(this->invisible == -1 ? 0 : this->invisible))
+astMAKE_SET(Plot,Invisible,int,invisible,( value ? 1 : 0 ))
+astMAKE_TEST(Plot,Invisible,( this->invisible != -1 ))
+
+/* TickAll */
+/* ------- */
+/*
+*att++
+*  Name:
+*     TickAll
+
+*  Purpose:
+*     Draw tick marks on all edges of a Plot?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether tick marks should be drawn on all edges of a Plot.
+*
+*     If the TickAll value of a Plot is non-zero (the default), then
+*     tick marks will be drawn on all edges of the Plot. Otherwise,
+*     they will be drawn only on those edges where the numerical and
+*     descriptive axis labels are drawn (see the Edge(axis)
+*     attribute).
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - In some circumstances, numerical labels and tick marks are
+*     drawn along grid lines inside the plotting area, rather than
+*     around its edges (see the Labelling attribute).  In this case,
+*     the value of the TickAll attribute is ignored.
+*att--
+*/
+/* If non-zero put tick marks on opposite edges. Has a value of -1 when not
+set yielding a default value of 1. */
+astMAKE_CLEAR(Plot,TickAll,tickall,-1)
+astMAKE_GET(Plot,TickAll,int,1,(this->tickall == -1 ? 1 : this->tickall))
+astMAKE_SET(Plot,TickAll,int,tickall,( value ? 1 : 0 ))
+astMAKE_TEST(Plot,TickAll,( this->tickall != -1 ))
+
+/* ForceExterior */
+/* ------------- */
+/*
+*att+
+*  Name:
+*     ForceExterior
+
+*  Purpose:
+*     Force the use of exterior labelling?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by forcing
+f     coordinate grid (drawn with the AST_GRID routine) by forcing
+*     labels and tick marks to be drawn round the edges of the plot
+*     (rather than across the middle of the plot), even if there appear
+*     to be insufficient edge crossings to justify the use of exterior
+*     labelling.
+*
+*     The default value of zero results in the decision about whether to
+*     use interior or exterior labelling being made purely on the basis
+*     of the value of the Labelling attribute. If ForceExterior is set to
+*     a non-zero value, then the Labelling attribute is ignored and exterior
+*     labelling will always be attempted, even if there appear to be
+*     insufficient edge labels to justify their use.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The value of this attribute is currently under investigation, and
+*     so this attribute prologue is currently marked as protected rather
+*     than public (in order to prevent it being included in the public
+*     documentation).
+*att-
+*/
+astMAKE_CLEAR(Plot,ForceExterior,forceexterior,-1)
+astMAKE_GET(Plot,ForceExterior,int,0,(this->forceexterior == -1 ? 0 : this->forceexterior))
+astMAKE_SET(Plot,ForceExterior,int,forceexterior,( value ? 1 : 0 ))
+astMAKE_TEST(Plot,ForceExterior,( this->forceexterior != -1 ))
+
+/*
+*att++
+*  Name:
+*     Border
+
+*  Purpose:
+*     Draw a border around valid regions of a Plot?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether a border is drawn around regions corresponding to the
+c     valid physical coordinates of a Plot (c.f. astBorder).
+f     valid physical coordinates of a Plot (c.f. AST_BORDER).
+*
+*     If the Border value of a Plot is non-zero, then this border will
+*     be drawn as part of the grid. Otherwise, the border is not drawn
+*     (although axis labels and tick marks will still appear, unless
+*     other relevant Plot attributes indicate that they should
+*     not). The default behaviour is to draw the border if tick marks
+*     and numerical labels will be drawn around the edges of the
+*     plotting area (see the Labelling attribute), but to omit it
+*     otherwise.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+*att--
+*/
+/* If non-zero draw the border. Has a value of -1 when not set, yeilding
+   a default of 1. */
+astMAKE_CLEAR(Plot,Border,border,-1)
+astMAKE_SET(Plot,Border,int,border,( value ? 1 : 0 ))
+astMAKE_TEST(Plot,Border,( this->border != -1 ))
+astMAKE_GET(Plot,Border,int,1,(this->border == -1 ? 1 : this->border))
+
+MAKE_SET2(Plot,Border,int,uborder,( value ? 1 : 0 ))
+MAKE_GET2(Plot,Border,int,1,this->uborder)
+
+/* Clip */
+/* ---- */
+/*
+*att++
+*  Name:
+*     Clip
+
+*  Purpose:
+*     Clip lines and/or markers at the Plot boundary?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute controls whether curves and markers are clipped at the
+*     boundary of the graphics box specified when the Plot was created. A
+*     value of 3 implies both markers and curves are clipped at the Plot
+*     boundary. A value of 2 implies markers are clipped, but not curves. A
+*     value of 1 implies curves are clipped, but not markers. A value of
+*     zero implies neither curves nor markers are clipped. The default
+*     value is 1. Note, this attributes controls only the clipping
+*     performed internally within AST. The underlying graphics system may
+*     also apply clipping. In such cases, removing clipping using this
+*     attribute does not guarantee that no clipping will be visible in the
+*     final plot.
+*
+c     The astClip function
+f     The AST_CLIP routine
+*     can be used to establish generalised clipping within arbitrary
+*     regions of the Plot.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+*att--
+*/
+astMAKE_CLEAR(Plot,Clip,clip,-1)
+astMAKE_GET(Plot,Clip,int,0,(this->clip == -1 ? 1 : this->clip))
+astMAKE_TEST(Plot,Clip,( this->clip != -1 ))
+astMAKE_SET(Plot,Clip,int,clip,((value>=0&&value<=3)?value:(astError( AST__ATTIN, "astSetClip(Plot): Invalid value %d supplied for Clip attribute", status, value ), this->clip)))
+
+/* ClipOp */
+/* ------ */
+/*
+*att++
+*  Name:
+*     ClipOp
+
+*  Purpose:
+*     Combine Plot clipping limits using a boolean OR?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls how the clipping limits specified for
+c     each axis of a Plot (using the astClip function) are
+f     each axis of a Plot (using the AST_CLIP routine) are
+*     combined. This, in turn, determines which parts of the graphical
+*     output will be visible.
+*
+*     If the ClipOp attribute of a Plot is zero (the default),
+*     graphical output is visible only if it satisfies the clipping
+*     limits on all the axes of the clipping Frame (a boolean
+*     AND). Otherwise, if ClipOp is non-zero, output is visible if it
+*     satisfies the clipping limits on one or more axes (a boolean
+*     OR).
+*
+*     An important use of this attribute is to allow areas of a Plot
+*     to be left clear (e.g. as a background for some text). To
+*     achieve this, the lower and upper clipping bounds supplied to
+c     astClip should be reversed, and the ClipOp attribute of the
+f     AST_CLIP should be reversed, and the ClipOp attribute of the
+*     Plot should be set to a non-zero value.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+*att--
+*/
+/* If non-zero only 1axis need be within the clipping bounds to avoid a
+point being clipped. Otherwise, all axes must be within bounds. */
+astMAKE_CLEAR(Plot,ClipOp,clipop,-1)
+astMAKE_GET(Plot,ClipOp,int,0,(this->clipop == -1 ? 0 : this->clipop))
+astMAKE_SET(Plot,ClipOp,int,clipop,( value ? 1 : 0 ))
+astMAKE_TEST(Plot,ClipOp,( this->clipop != -1 ))
+
+/* Grf. */
+/* ---- */
+/*
+*att++
+*  Name:
+*     Grf
+
+*  Purpose:
+c     Use Grf functions registered through astGrfSet?
+f     Use Grf routines registered through AST_GRFSET?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+c     This attribute selects the functions which are used to draw graphics by
+c     the Plot class. If it is zero, then the functions in the graphics
+c     interface selected at link-time are used (see the ast_link script).
+c     Otherwise, functions registered using astGrfSet are used. In this
+c     case, if a function is needed which has not been registered,
+c     then the function in the graphics interface selected at link-time is
+c     used.
+f     This attribute selects the routines which are used to draw graphics by
+f     the Plot class. If it is zero, then the routines in the graphics
+f     interface selected at link-time are used (see the ast_link script).
+f     Otherwise, routines registered using AST_GRFSET are used. In this
+f     case, if a routine is needed which has not been registered,
+f     then the routine in the graphics interface selected at link-time is
+f     used.
+
+*     The default is to use the graphics interface
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+*     Plot3D
+*        The Plot3D class ignores this attributes, assuming a value of
+*        zero.
+
+*  Notes:
+*     - The value of this attribute is not saved when the Plot is written
+*     out through a Channel to an external data store. On re-loading such
+c     a Plot using astRead, the attribute will be cleared, resulting in the
+f     a Plot using AST_READ, the attribute will be cleared, resulting in the
+*     graphics interface selected at link-time being used.
+
+*att--
+*/
+/* Use Grf routines registered using astGrfSet? Has a
+value of -1 when not set yielding a default of 0. */
+astMAKE_CLEAR(Plot,Grf,grf,-1)
+astMAKE_GET(Plot,Grf,int,0,(this->grf == -1 ? 0 : this->grf))
+astMAKE_SET(Plot,Grf,int,grf,( value ? 1 : 0 ))
+astMAKE_TEST(Plot,Grf,( this->grf != -1 ))
+
+/* DrawTitle */
+/* --------- */
+/*
+*att++
+*  Name:
+*     DrawTitle
+
+*  Purpose:
+*     Draw a title for a Plot?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether a title is drawn.
+*
+*     If the DrawTitle value of a Plot is non-zero (the default), then
+*     the title will be drawn, otherwise it will be omitted.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+*     Plot3D
+*        The Plot3D class ignores this attributes, assuming a value of
+*        zero.
+
+*  Notes:
+*     - The text used for the title is obtained from the Plot's Title
+*     attribute.
+*     - The vertical placement of the title can be controlled using
+*     the TitleGap attribute.
+*att--
+*/
+/* If non-zero add a title to the grid. Has a value of -1 when not
+set yielding a default value of 1. */
+astMAKE_CLEAR(Plot,DrawTitle,drawtitle,-1)
+astMAKE_GET(Plot,DrawTitle,int,1,(this->drawtitle == -1 ? 1 : this->drawtitle))
+astMAKE_SET(Plot,DrawTitle,int,drawtitle,( value ? 1 : 0 ))
+astMAKE_TEST(Plot,DrawTitle,( this->drawtitle != -1 ))
+
+/* LabelUp. */
+/* -------  */
+/*
+*att++
+*  Name:
+*     LabelUp(axis)
+
+*  Purpose:
+*     Draw numerical Plot labels upright?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether the numerical labels for each axis of a Plot should be
+*     drawn upright or not. It takes a separate value for each
+*     physical axis of a Plot so that, for instance, the setting
+*     "LabelUp(2)=1" specifies that numerical labels for the second
+*     axis should be drawn upright.
+*
+*     If the LabelUp value of a Plot axis is non-zero, it causes
+*     numerical labels for that axis to be plotted upright (i.e. as
+*     normal, horizontal text), otherwise labels are drawn parallel to
+*     the axis to which they apply.
+*
+*     The default is to produce upright labels if the labels are placed
+*     around the edge of the plot, and to produce labels that follow the
+*     axes if the labels are placed within the interior of the plot (see
+*     attribute Labelling).
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - In some circumstances, numerical labels and tick marks are
+*     drawn around the edges of the plotting area (see the Labelling
+*     attribute).  In this case, the value of the LabelUp attribute is
+*     ignored.
+*     - If no axis is specified, (e.g. "LabelUp" instead of
+*     "LabelUp(2)"), then a "set" or "clear" operation will affect the
+*     attribute value of all the Plot axes, while a "get" or "test"
+*     operation will use just the LabelUp(1) value.
+*att--
+*/
+/* Are numerical labels to be displayed on each axis? Has a value of -1 when
+not set yielding a value of 0 (no) for both axes. */
+MAKE_CLEAR(LabelUp,labelup,-1,0)
+MAKE_GET(LabelUp,int,0,( this->labelup[axis] == -1 ? 0 : this->labelup[axis] ),0)
+MAKE_TEST(LabelUp,( this->labelup[axis] != -1 ),0)
+MAKE_SET(LabelUp,int,labelup,( value ? 1 : 0 ),0)
+
+/* DrawAxes */
+/* -------- */
+/*
+*att++
+*  Name:
+*     DrawAxes(axis)
+
+*  Purpose:
+*     Draw axes for a Plot?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether curves representing coordinate axes should be drawn.
+*     It takes a separate value for each physical axis of a
+*     Plot so that, for instance, the setting "DrawAxes(2)=0"
+*     specifies that no axis should be drawn for the second axis.
+*
+*     If drawn, these axis lines will pass through any tick marks
+*     associated with numerical labels drawn to mark values on the
+*     axes. The location of these tick marks and labels (and hence the
+*     axis lines) is determined by the Plot's LabelAt(axis) attribute.
+*
+*     If the DrawAxes value of a Plot is non-zero (the default), then
+*     axis lines will be drawn, otherwise they will be omitted.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - Axis lines are drawn independently of any coordinate grid
+*     lines (see the Grid attribute) so grid lines may be used to
+*     substitute for axis lines if required.
+*     - In some circumstances, numerical labels and tick marks are
+*     drawn around the edges of the plotting area (see the Labelling
+*     attribute).  In this case, the value of the DrawAxes attribute
+*     is ignored.
+*     - If no axis is specified, (e.g. "DrawAxes" instead of
+*     "DrawAxes(2)"), then a "set" or "clear" operation will affect
+*     the attribute value of all the Plot axes, while a "get" or
+*     "test" operation will use just the DrawAxes(1) value.
+*att--
+*/
+/* If non-zero draw a curve through the tick marks. Has a value of -1
+   when not set yielding a default value of 1. */
+MAKE_CLEAR(DrawAxes,drawaxes,-1,0)
+MAKE_GET(DrawAxes,int,1,( this->drawaxes[axis] == -1 ? 1 : this->drawaxes[axis] ),0)
+MAKE_TEST(DrawAxes,( this->drawaxes[axis] != -1 ),0)
+MAKE_SET(DrawAxes,int,drawaxes,( value ? 1 : 0 ),0)
+
+/* Abbrev */
+/* -------- */
+/*
+*att++
+*  Name:
+*     Abbrev(axis)
+
+*  Purpose:
+*     Abbreviate leading fields within numerical axis labels?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether matching leading fields should be removed from adjacent
+*     numerical axis labels. It takes a separate value for each physical
+*     axis of a Plot so that, for instance, the setting "Abbrev(2)=0"
+*     specifies that matching leading fields should not be removed on
+*     the second axis.
+*
+*     If the Abbrev value of a Plot is non-zero (the default), then
+*     leading fields will be removed from adjacent axis labels if they
+*     are equal.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - If no axis is specified, (e.g. "Abbrev" instead of
+*     "Abbrev(2)"), then a "set" or "clear" operation will affect
+*     the attribute value of all the Plot axes, while a "get" or
+*     "test" operation will use just the Abbrev(1) value.
+*att--
+*/
+MAKE_CLEAR(Abbrev,abbrev,-1,0)
+MAKE_GET(Abbrev,int,1,( this->abbrev[axis] == -1 ? 1 : this->abbrev[axis] ),0)
+MAKE_TEST(Abbrev,( this->abbrev[axis] != -1 ),0)
+MAKE_SET(Abbrev,int,abbrev,( value ? 1 : 0 ),0)
+
+/* Escape. */
+/* ------- */
+/*
+*att++
+*  Name:
+*     Escape
+
+*  Purpose:
+*     Allow changes of character attributes within strings?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of text strings and numerical
+c     labels drawn by the astGrid and (for the Plot class) astText functions,
+f     labels drawn by the AST_GRID and (for the Plot class) AST_TEXT routines,
+*     by determining if any escape sequences contained within the strings
+*     should be used to control the appearance of the text, or should
+*     be printed literally. Note, the Plot3D class only interprets escape
+*     sequences within the
+c     astGrid function.
+f     AST_GRID routine.
+*
+*     If the Escape value of a Plot is one (the default), then any
+*     escape sequences in text strings produce the effects described
+*     below when printed. Otherwise, they are printed literally.
+*
+c     See also the astEscapes function.
+f     See also the AST_ESCAPES function.
+
+*  Escape Sequences:
+*     Escape sequences are introduced into the text string by a percent
+*     "%" character. Any unrecognised, illegal or incomplete escape sequences
+*     are printed literally. The following escape sequences are
+*     currently recognised ("..." represents a string of one or more
+*     decimal digits):
+*
+*       %%      - Print a literal "%" character.
+*
+*       %^...+  - Draw subsequent characters as super-scripts. The digits
+*                 "..." give the distance from the base-line of "normal"
+*                 text to the base-line of the super-script text, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text.
+*       %^+     - Draw subsequent characters with the normal base-line.
+*
+*       %v...+  - Draw subsequent characters as sub-scripts. The digits
+*                 "..." give the distance from the base-line of "normal"
+*                 text to the base-line of the sub-script text, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text.
+*
+*       %v+     - Draw subsequent characters with the normal base-line
+*                 (equivalent to %^+).
+*
+*       %>...+  - Leave a gap before drawing subsequent characters.
+*                 The digits "..." give the size of the gap, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text.
+*
+*       %<...+  - Move backwards before drawing subsequent characters.
+*                 The digits "..." give the size of the movement, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text.
+*
+*       %s...+  - Change the Size attribute for subsequent characters. The
+*                 digits "..." give the new Size as a fraction of the
+*                 "normal" Size, scaled so that a value of "100" corresponds
+*                 to 1.0;
+*
+*       %s+     - Reset the Size attribute to its "normal" value.
+*
+*       %w...+  - Change the Width attribute for subsequent characters. The
+*                 digits "..." give the new width as a fraction of the
+*                 "normal" Width, scaled so that a value of "100" corresponds
+*                 to 1.0;
+*
+*       %w+     - Reset the Size attribute to its "normal" value.
+*
+*       %f...+  - Change the Font attribute for subsequent characters. The
+*                 digits "..." give the new Font value.
+*
+*       %f+     - Reset the Font attribute to its "normal" value.
+*
+*       %c...+  - Change the Colour attribute for subsequent characters. The
+*                 digits "..." give the new Colour value.
+*
+*       %c+     - Reset the Colour attribute to its "normal" value.
+*
+*       %t...+  - Change the Style attribute for subsequent characters. The
+*                 digits "..." give the new Style value.
+*
+*       %t+     - Reset the Style attribute to its "normal" value.
+*
+*       %h+     - Remember the current horizontal position (see "%g+")
+*
+*       %g+     - Go to the horizontal position of the previous "%h+" (if any).
+*
+*       %-      - Push the current graphics attribute values onto the top of
+*                 the stack (see "%+").
+*
+*       %+      - Pop attributes values of the top the stack (see "%-"). If
+*                 the stack is empty, "normal" attribute values are restored.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+*att--
+*/
+
+/* Has a value of -1 when not set yeilding a default of 1. */
+astMAKE_GET(Plot,Escape,int,1,(this->escape == -1 ? 1 : this->escape))
+astMAKE_CLEAR(Plot,Escape,escape,-1)
+astMAKE_SET(Plot,Escape,int,escape,( value ? 1 : 0 ))
+astMAKE_TEST(Plot,Escape,( this->escape != -1 ))
+
+/* LabelAt(axis). */
+/* -------------- */
+/*
+*att++
+*  Name:
+*     LabelAt(axis)
+
+*  Purpose:
+*     Where to place numerical labels for a Plot
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     where numerical axis labels and associated tick marks are
+*     placed.  It takes a separate value for each physical axis of a
+*     Plot so that, for instance, the setting "LabelAt(2)=10.0"
+*     specifies where the numerical labels and tick marks for the
+*     second axis should be drawn.
+*
+*     For each axis, the LabelAt value gives the value on the other
+*     axis at which numerical labels and tick marks should be placed
+c     (remember that Plots suitable for use with astGrid may only
+f     (remember that Plots suitable for use with AST_GRID may only
+*     have two axes).  For example, in a celestial (RA,Dec) coordinate
+*     system, LabelAt(1) gives a Dec value which defines a line (of
+*     constant Dec) along which the numerical RA labels and their
+*     associated tick marks will be drawn. Similarly, LabelAt(2) gives
+*     the RA value at which the Dec labels and ticks will be drawn.
+*
+*     The default bahaviour is for the Plot to generate its own
+*     position for numerical labels and tick marks.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The LabelAt value should use the same units as are used
+*     internally for storing coordinate values on the appropriate
+*     axis. For example, with a celestial coordinate system, the
+*     LabelAt value should be in radians, not hours or degrees.
+*     - Normally, the LabelAt value also determines where the lines
+*     representing coordinate axes will be drawn, so that the tick
+*     marks will lie on these lines (but also see the DrawAxes
+*     attribute).
+*     - In some circumstances, numerical labels and tick marks are
+*     drawn around the edges of the plotting area (see the Labelling
+*     attribute).  In this case, the value of the LabelAt attribute is
+*     ignored.
+*att--
+*/
+/* The constant value on the other axis at which to place labels for
+   each axis. */
+MAKE_CLEAR(LabelAt,labelat,AST__BAD,0)
+MAKE_GET(LabelAt,double,AST__BAD,this->labelat[axis],0)
+MAKE_SET(LabelAt,double,labelat,value,0)
+MAKE_TEST(LabelAt,( this->labelat[axis] != AST__BAD ),0)
+
+MAKE_GET3(LabelAt,double,AST__BAD,this->ulblat[axis],0)
+MAKE_SET3(LabelAt,double,ulblat,value,0)
+
+/* Centre(axis). */
+/* ------------ */
+/* A value at which to place a tick mark. Other ticks marks are spaced at
+regular distances from this one. AST__BAD is stored if no value is supplied,
+resulting in Plot choosing its own value. */
+MAKE_CLEAR(Centre,centre,AST__BAD,0)
+MAKE_GET(Centre,double,AST__BAD,this->centre[axis],0)
+MAKE_SET(Centre,double,centre,value,0)
+MAKE_TEST(Centre,( this->centre[axis] != AST__BAD ),0)
+
+MAKE_GET3(Centre,double,AST__BAD,this->ucentre[axis],0)
+MAKE_SET3(Centre,double,ucentre,value,0)
+
+/* Ink */
+/* --- */
+/* A protected attribute indicating if astGrid should draw using visible
+ink. The unset value is -1, yeilding a default value of 1. */
+astMAKE_CLEAR(Plot,Ink,ink,-1)
+astMAKE_GET(Plot,Ink,int,1,(this->ink == -1 ? 1 : this->ink))
+astMAKE_SET(Plot,Ink,int,ink,( value ? 1 : 0 ))
+astMAKE_TEST(Plot,Ink,( this->ink != -1 ))
+
+/* Gap(axis). */
+/* ---------- */
+/*
+*att++
+*  Name:
+*     Gap(axis)
+
+*  Purpose:
+*     Interval between linearly spaced major axis values of a Plot.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     the linear interval between the "major" axis values of a Plot, at
+*     which (for example) major tick marks are drawn. It takes a separate
+*     value for each physical axis of the Plot so that, for instance,
+*     the setting "Gap(2)=3.0" specifies the difference between adjacent major
+*     values along the second axis. The Gap attribute is only used when
+*     the LogTicks attribute indicates that the spacing between major axis
+*     values is to be linear. If major axis values are logarithmically spaced
+*     then the gap is specified using attribute LogGap.
+*
+*     The Gap value supplied will usually be rounded to the nearest
+*     "nice" value, suitable (e.g.) for generating axis labels, before
+*     use. To avoid this "nicing" you should set an explicit format
+*     for the axis using the Format(axis) or Digits/Digits(axis)
+*     attribute. The default behaviour is for the Plot to generate its
+*     own Gap value when required, based on the range of axis values
+*     to be represented.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The Gap value should use the same units as are used internally
+*     for storing coordinate values on the corresponding axis. For
+*     example, with a celestial coordinate system, the Gap value
+*     should be in radians, not hours or degrees.
+*     - If no axis is specified, (e.g. "Gap" instead of "Gap(2)"),
+*     then a "set" or "clear" operation will affect the attribute
+*     value of all the Plot axes, while a "get" or "test" operation
+*     will use just the Gap(1) value.
+*att--
+*/
+/* The gap between tick marks on each axis. AST__BAD is stored if no
+value has been supplied, resulting in default values being found. */
+MAKE_CLEAR(Gap,gap,AST__BAD,0)
+MAKE_SET(Gap,double,gap,value,0)
+MAKE_TEST(Gap,( this->gap[axis] != AST__BAD ),0)
+MAKE_GET(Gap,double,AST__BAD,this->gap[axis],0)
+
+MAKE_SET3(Gap,double,ugap,value,0)
+MAKE_GET3(Gap,double,AST__BAD,this->ugap[axis],0)
+
+/* LogGap(axis). */
+/* ---------- */
+/*
+*att++
+*  Name:
+*     LogGap(axis)
+
+*  Purpose:
+*     Interval between major axis values of a Plot.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     the logarithmic interval between the "major" axis values of a Plot, at
+*     which (for example) major tick marks are drawn. It takes a separate
+*     value for each physical axis of the Plot so that, for instance,
+*     the setting "LogGap(2)=100.0" specifies the ratio between adjacent major
+*     values along the second axis. The LogGap attribute is only used when
+*     the LogTicks attribute indicates that the spacing between major axis
+*     values is to be logarithmic. If major axis values are linearly spaced
+*     then the gap is specified using attribute Gap.
+*
+*     The LogGap value supplied will be rounded to the nearest power of 10.
+*     The reciprocal of the supplied value may be used if this is necessary
+*     to produce usable major axis values. If a zero or negative value is
+*     supplied, an error will be reported when the grid is drawn. The default
+*     behaviour is for the Plot to generate its own LogGap value when
+*     required, based on the range of axis values to be represented.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The LogGap value is a ratio between axis values and is therefore
+*     dimensionless.
+*     - If no axis is specified, (e.g. "LogGap" instead of "LogGap(2)"),
+*     then a "set" or "clear" operation will affect the attribute
+*     value of all the Plot axes, while a "get" or "test" operation
+*     will use just the LogGap(1) value.
+*att--
+*/
+/* The logarithmic gap between tick marks on each axis. AST__BAD is stored if
+   no value has been supplied, resulting in default values being found. */
+MAKE_CLEAR(LogGap,loggap,AST__BAD,0)
+MAKE_SET(LogGap,double,loggap,value,0)
+MAKE_TEST(LogGap,( this->loggap[axis] != AST__BAD ),0)
+MAKE_GET(LogGap,double,AST__BAD,this->loggap[axis],0)
+
+MAKE_SET3(LogGap,double,uloggap,value,0)
+MAKE_GET3(LogGap,double,AST__BAD,this->uloggap[axis],0)
+
+/* LogPlot. */
+/* -------  */
+/*
+*att++
+*  Name:
+*     LogPlot(axis)
+
+*  Purpose:
+*     Map the plot logarithmically onto the screen?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of all graphics produced by
+*     the Plot, by determining whether the axes of the plotting surface
+*     are mapped logarithmically or linearly onto the base Frame of the
+*     FrameSet supplied when the Plot was constructed. It takes a separate
+*     value for each axis of the graphics coordinate system (i.e. the
+*     base Frame in the Plot) so that, for instance, the setting
+*     "LogPlot(2)=1" specifies that the second axis of the graphics
+*     coordinate system (usually the vertical axis) should be mapped
+*     logarithmically onto the second axis of the base Frame of the
+*     FrameSet supplied when the Plot was constructed.
+*
+*     If the LogPlot value of a Plot axis is non-zero, it causes that
+*     axis to be mapped logarithmically, otherwise (the default) the axis
+*     is mapped linearly.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The setting of the LogPlot attribute provides the default value
+*     for the related LogTicks attribute. By selecting suitable values for
+*     LogPlot and LogTicks, it is possible to have tick marks which are evenly
+*     spaced in value but which are mapped logarithmically onto the screen
+*     (and vice-versa).
+*     - An axis may only be mapped logarithmically if the visible part of
+*     the axis does not include the value zero. The visible part of the
+*     axis is that part which is mapped onto the plotting area, and is
+*     measured within the base Frame of the FrameSet which was supplied when
+*     the Plot was constructed. Any attempt to set LogPlot to a non-zero value
+*     will be ignored (without error) if the visible part of the axis
+*     includes the value zero
+*     - If no axis is specified, (e.g. "LogPlot" instead of
+*     "LogPlot(2)"), then a "set" or "clear" operation will affect the
+*     attribute value of all the Plot axes, while a "get" or "test"
+*     operation will use just the LogPlot(1) value.
+*att--
+*/
+/* Are plot axes to be mapped logarithmically? Has a value of -1 when
+not set yielding a value of 0 (no) for both axes. */
+MAKE_GET(LogPlot,int,0,( this->logplot[axis] == -1 ? 0 : this->logplot[axis] ),0)
+MAKE_TEST(LogPlot,( this->logplot[axis] != -1 ),0)
+
+/* LogTicks. */
+/* -------  */
+/*
+*att++
+*  Name:
+*     LogTicks(axis)
+
+*  Purpose:
+*     Space the major tick marks logarithmically?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether the major tick marks should be spaced logarithmically or
+*     linearly in axis value. It takes a separate value for each physical
+*     axis of the Plot so that, for instance, the setting "LogTicks(2)=1"
+*     specifies that the major tick marks on the second axis should be
+*     spaced logarithmically.
+*
+*     If the LogTicks value for a physical axis is non-zero, the major
+*     tick marks on that axis will be spaced logarithmically (that is,
+*     there will be a constant ratio between the axis values at adjacent
+*     major tick marks). An error will be reported if the dynamic range of
+*     the axis (the ratio of the largest to smallest displayed axis value)
+*     is less than 10.0. If the LogTicks value is zero, the major tick marks
+*     will be evenly spaced (that is, there will be a constant difference
+*     between the axis values at adjacent major tick marks). The default is
+*     to produce logarithmically spaced tick marks if the corresponding
+*     LogPlot attribute is non-zero and the ratio of maximum axis value
+*     to minimum axis value is 100 or more. If either of these conditions
+*     is not met, the default is to produce linearly spaced tick marks.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The setting of the LogTicks attribute does not affect the mapping
+*     of the plot onto the screen, which is controlled by attribute LogPlot.
+*     By selecting suitable values for LogPlot and LogTicks, it is possible to
+*     have tick marks which are evenly spaced in value but which are mapped
+*     logarithmically onto the screen (and vica-versa).
+*     - An error will be reported when drawing an annotated axis grid if
+*     the visible part of the physical axis includes the value zero.
+*     - If no axis is specified, (e.g. "LogTicks" instead of
+*     "LogTicks(2)"), then a "set" or "clear" operation will affect the
+*     attribute value of all the Plot axes, while a "get" or "test"
+*     operation will use just the LogTicks(1) value.
+*att--
+*/
+/* Are ticksto be spaced logarithmically? Has a value of -1 when
+   not set, yeielding a default value equal to the corresponding
+   LogPlot value. */
+MAKE_CLEAR(LogTicks,logticks,-1,0)
+MAKE_GET(LogTicks,int,0,( this->logticks[axis] == -1 ? astGetLogPlot(this,axis) : this->logticks[axis] ),0)
+MAKE_TEST(LogTicks,( this->logticks[axis] != -1 ),0)
+MAKE_SET(LogTicks,int,logticks,( value ? 1 : 0 ),0)
+
+MAKE_SET3(LogTicks,int,ulgtk,value,0)
+MAKE_GET3(LogTicks,int,0,this->ulgtk[axis],0)
+
+/* LogLabel. */
+/* --------  */
+/*
+*att++
+*  Name:
+*     LogLabel(axis)
+
+*  Purpose:
+*     Use exponential format for numerical axis labels?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether the numerical axis labels should be in normal decimal form
+*     or should be represented as 10 raised to the appropriate power.
+*     That is, an axis value of 1000.0 will be drawn as "1000.0" if
+*     LogLabel is zero, but as "10^3" if LogLabel is non-zero. If
+*     graphical escape sequences are supported (see attribute Escape),
+*     the power in such exponential labels will be drawn as a small
+*     superscript instead of using a "^" character to represent
+*     exponentiation.
+*
+*     The default is to produce exponential labels if the major tick
+*     marks are logarithmically spaced (see the LogTicks attribute).
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - If no axis is specified, (e.g. "LogLabel" instead of
+*     "LogLabel(2)"), then a "set" or "clear" operation will affect the
+*     attribute value of all the Plot axes, while a "get" or "test"
+*     operation will use just the LogLabel(1) value.
+*att--
+*/
+/* Are labels to be drawn as 10**x? Has a value of -1 when not set, yeielding
+   a default value equal to the corresponding LogTicks value. */
+MAKE_CLEAR(LogLabel,loglabel,-1,0)
+MAKE_GET(LogLabel,int,0,( this->loglabel[axis] == -1 ? astGetLogTicks(this,axis) : this->loglabel[axis] ),0)
+MAKE_TEST(LogLabel,( this->loglabel[axis] != -1 ),0)
+MAKE_SET(LogLabel,int,loglabel,( value ? 1 : 0 ),0)
+
+MAKE_SET3(LogLabel,int,ulglb,value,0)
+MAKE_GET3(LogLabel,int,0,this->ulglb[axis],0)
+
+/* MajTickLen. */
+/* ----------- */
+/*
+*att++
+*  Name:
+*     MajTickLen(axis)
+
+*  Purpose:
+*     Length of major tick marks for a Plot.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     the length of the major tick marks drawn on the axes of a Plot.
+*     It takes a separate value for each physical axis of the Plot so
+*     that, for instance, the setting "MajTickLen(2)=0" specifies the
+*     length of the major tick marks drawn on the second axis.
+*
+*     The MajTickLen value should be given as a fraction of the
+*     minimum dimension of the plotting area. Negative values cause
+*     major tick marks to be placed on the outside of the
+*     corresponding grid line or axis (but subject to any clipping
+*     imposed by the underlying graphics system), while positive
+*     values cause them to be placed on the inside.
+*
+*     The default behaviour depends on whether a coordinate grid is
+*     drawn inside the plotting area (see the Grid attribute). If so,
+*     the default MajTickLen value is zero (so that major ticks are
+*     not drawn), otherwise the default is +0.015.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - If no axis is specified, (e.g. "MajTickLen" instead of
+*     "MajTickLen(2)"), then a "set" or "clear" operation will affect
+*     the attribute value of all the Plot axes, while a "get" or "test"
+*     operation will use just the MajTickLen(1) value.
+
+*att--
+*/
+/* Fractional length of major tick marks. Has a value of AST__BAD when not
+set yielding a default value of 0.015. */
+MAKE_CLEAR(MajTickLen,majticklen,AST__BAD,0)
+MAKE_SET(MajTickLen,double,majticklen,value,0)
+MAKE_TEST(MajTickLen,( this->majticklen[axis] != AST__BAD ),0)
+MAKE_GET(MajTickLen,double,0.0,( this->majticklen[axis] == AST__BAD ? 0.015 : this->majticklen[axis]),0)
+
+MAKE_SET3(MajTickLen,double,umjtkln,value,0)
+MAKE_GET3(MajTickLen,double,0.0,this->umjtkln[axis],0)
+
+/* TitleGap. */
+/* --------- */
+/*
+*att++
+*  Name:
+*     TitleGap
+
+*  Purpose:
+*     Vertical spacing for a Plot title.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     where the title of a Plot is drawn.
+*
+*     Its value gives the spacing between the bottom edge of the title
+*     and the top edge of a bounding box containing all the other parts
+*     of the annotated grid. Positive values cause the title to be
+*     drawn outside the box, while negative values cause it to be drawn
+*     inside.
+*
+*     The TitleGap value should be given as a fraction of the minimum
+*     dimension of the plotting area, the default value being +0.05.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+*     Plot3D
+*        The Plot3D class ignores this attributes since it does not draw
+*        a Title.
+
+*  Notes:
+*     - The text used for the title is obtained from the Plot's Title
+*     attribute.
+*att--
+*/
+/* Fractional gap between titile and top edge. Has a value of AST__BAD when
+not set yielding a default value of 0.05. */
+astMAKE_CLEAR(Plot,TitleGap,titlegap,AST__BAD)
+astMAKE_GET(Plot,TitleGap,double,0.0,( this->titlegap == AST__BAD ? 0.05 : this->titlegap))
+astMAKE_SET(Plot,TitleGap,double,titlegap,value)
+astMAKE_TEST(Plot,TitleGap,( this->titlegap != AST__BAD ))
+
+/* MinTickLen. */
+/* ----------- */
+/*
+*att++
+*  Name:
+*     MinTickLen(axis)
+
+*  Purpose:
+*     Length of minor tick marks for a Plot.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     the length of the minor tick marks drawn on the axes of a Plot.
+*     It takes a separate value for each physical axis of the Plot so
+*     that, for instance, the setting "MinTickLen(2)=0" specifies the
+*     length of the minor tick marks drawn on the second axis.
+*
+*     The MinTickLen value should be given as a fraction of the
+*     minimum dimension of the plotting area. Negative values cause
+*     minor tick marks to be placed on the outside of the
+*     corresponding grid line or axis (but subject to any clipping
+*     imposed by the underlying graphics system), while positive
+*     values cause them to be placed on the inside.
+*
+*     The default value is +0.007.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The number of minor tick marks drawn is determined by the
+*     Plot's MinTick(axis) attribute.
+*     - If no axis is specified, (e.g. "MinTickLen" instead of
+*     "MinTickLen(2)"), then a "set" or "clear" operation will affect
+*     the attribute value of all the Plot axes, while a "get" or "test"
+*     operation will use just the MinTickLen(1) value.
+
+*att--
+*/
+/* Fractional length of minor tick marks. Has a value of AST__BAD when not
+set yielding a default value of 0.007. */
+MAKE_CLEAR(MinTickLen,minticklen,AST__BAD,0)
+MAKE_SET(MinTickLen,double,minticklen,value,0)
+MAKE_TEST(MinTickLen,( this->minticklen[axis] != AST__BAD ),0)
+MAKE_GET(MinTickLen,double,0.0,( this->minticklen[axis] == AST__BAD ? 0.007 : this->minticklen[axis]),0)
+
+/* Labelling. */
+/* ---------- */
+/*
+*att++
+*  Name:
+*     Labelling
+
+*  Purpose:
+*     Label and tick placement option for a Plot.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     the strategy for placing numerical labels and tick marks for a Plot.
+*
+*     If the Labelling value of a Plot is "exterior" (the default), then
+*     numerical labels and their associated tick marks are placed
+*     around the edges of the plotting area, if possible. If this is
+*     not possible, or if the Labelling value is "interior", then they
+*     are placed along grid lines inside the plotting area.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The LabelAt(axis) attribute may be used to determine the exact
+*     placement of labels and tick marks that are drawn inside the
+*     plotting area.
+*att--
+*/
+astMAKE_CLEAR(Plot,Labelling,labelling,-9999)
+astMAKE_SET(Plot,Labelling,int,labelling,(value?1:0))
+astMAKE_TEST(Plot,Labelling,( this->labelling != -9999 ))
+astMAKE_GET(Plot,Labelling,int,0,(this->labelling == -9999 ? 0 : this->labelling))
+
+MAKE_SET2(Plot,Labelling,int,ulbling,(value?1:0))
+MAKE_GET2(Plot,Labelling,int,0,this->ulbling)
+
+/* Edge. */
+/* ----- */
+/*
+*att++
+*  Name:
+*     Edge(axis)
+
+*  Purpose:
+*     Which edges to label in a Plot
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     which edges of a Plot are used for displaying numerical and
+*     descriptive axis labels. It takes a separate value for each
+*     physical axis of the Plot so that, for instance, the setting
+*     "Edge(2)=left" specifies which edge to use to display labels for
+*     the second axis.
+*
+*     The values "left", "top", "right" and "bottom" (or any
+*     abbreviation) can be supplied for this attribute. The default is
+*     usually "bottom" for the first axis and "left" for the second
+*     axis. However, if exterior labelling was requested (see the
+*     Labelling attribute) but cannot be produced using these default
+*     Edge values, then the default values will be swapped if this
+*     enables exterior labelling to be produced.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+*     Plot3D
+*        The Plot3D class ignores this attributes. Instead it uses its
+*        own RootCorner attribute to determine which edges of the 3D plot
+*        to label.
+
+*  Notes:
+*     - In some circumstances, numerical labels will be drawn along
+*     internal grid lines instead of at the edges of the plotting area
+*     (see the Labelling attribute). In this case, the Edge attribute
+*     only affects the placement of the descriptive labels (these are
+*     drawn at the edges of the plotting area, rather than along the
+*     axis lines).
+*att--
+*/
+/* The edges of the plotting area on which to place numerical labels
+   for axes 0 and 1. Has a value of -1 when not set yielding a value
+   of 3 (the bottom edge) for axis 0 and 0 (the left-hand edge) for
+   axis 1. */
+MAKE_CLEAR(Edge,edge,-1,0)
+MAKE_GET(Edge,int,0,( this->edge[axis] == -1 ? (axis?LEFT:BOTTOM) : this->edge[axis] ),0)
+MAKE_SET(Edge,int,edge,(abs( value % 4 )),0)
+MAKE_TEST(Edge,( this->edge[axis] != -1 ),0)
+
+MAKE_GET3(Edge,int,0,this->uedge[axis],0)
+MAKE_SET3(Edge,int,uedge,(abs( value % 4 )),0)
+
+/* NumLab. */
+/* -------- */
+/*
+*att++
+*  Name:
+*     NumLab(axis)
+
+*  Purpose:
+*     Draw numerical axis labels for a Plot?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether labels should be drawn to represent the numerical values
+*     along each axis of a Plot. It takes a separate value for each
+*     physical axis of a Plot so that, for instance, the setting
+*     "NumLab(2)=1" specifies that numerical labels should be drawn
+*     for the second axis.
+*
+*     If the NumLab value of a Plot axis is non-zero (the default),
+*     then numerical labels will be drawn for that axis, otherwise
+*     they will be omitted.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The drawing of associated descriptive axis labels for a Plot
+*     (describing the quantity being plotted along each axis) is
+*     controlled by the TextLab(axis) attribute.
+*     - If no axis is specified, (e.g. "NumLab" instead of
+*     "NumLab(2)"), then a "set" or "clear" operation will affect the
+*     attribute value of all the Plot axes, while a "get" or "test"
+*     operation will use just the NumLab(1) value.
+*att--
+*/
+/* Are numerical labels to be displayed on each axis? Has a value of
+   -1 when not set yielding a value of 1 (yes) for both axes. */
+MAKE_CLEAR(NumLab,numlab,-1,0)
+MAKE_GET(NumLab,int,1,( this->numlab[axis] == -1 ? 1 : this->numlab[axis] ),0)
+MAKE_TEST(NumLab,( this->numlab[axis] != -1 ),0)
+MAKE_SET(NumLab,int,numlab,( value ? 1 : 0 ),0)
+
+/* NumLabGap. */
+/* --------- */
+/*
+*att++
+*  Name:
+*     NumLabGap(axis)
+
+*  Purpose:
+*     Spacing of numerical labels for a Plot.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     where numerical axis labels are placed relative to the axes they
+*     describe.  It takes a separate value for each physical axis of a
+*     Plot so that, for instance, the setting "NumLabGap(2)=-0.01"
+*     specifies where the numerical label for the second axis should
+*     be drawn.
+*
+*     For each axis, the NumLabGap value gives the spacing between the
+*     axis line (or edge of the plotting area, if appropriate) and the
+*     nearest edge of the corresponding numerical axis
+*     labels. Positive values cause the descriptive label to be placed
+*     on the opposite side of the line to the default tick marks,
+*     while negative values cause it to be placed on the same side.
+*
+*     The NumLabGap value should be given as a fraction of the minimum
+*     dimension of the plotting area, the default value being +0.01.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - If no axis is specified, (e.g. "NumLabGap" instead of
+*     "NumLabGap(2)"), then a "set" or "clear" operation will affect
+*     the attribute value of all the Plot axes, while a "get" or
+*     "test" operation will use just the NumLabGap(1) value.
+*att--
+*/
+/* Fractional spacing between numeric labels and axes. Has a value of AST__BAD
+when not set yielding a default value of 0.01. */
+MAKE_CLEAR(NumLabGap,numlabgap,AST__BAD,0)
+MAKE_GET(NumLabGap,double,0.0,( this->numlabgap[ axis ] == AST__BAD ? 0.01 : this->numlabgap[axis]),0)
+MAKE_SET(NumLabGap,double,numlabgap,value,0)
+MAKE_TEST(NumLabGap,( this->numlabgap[axis] != AST__BAD ),0)
+
+/* MinTick. */
+/* -------- */
+/*
+*att++
+*  Name:
+*     MinTick(axis)
+
+*  Purpose:
+*     Density of minor tick marks for a Plot.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     the density of minor tick marks which appear between the major
+*     axis values of a Plot. It takes a separate value for each
+*     physical axis of a Plot so that, for instance, the setting
+*     "MinTick(2)=2" specifies the density of minor tick marks along
+*     the second axis.
+*
+*     The value supplied should be the number of minor divisions
+*     required between each pair of major axis values, this being one
+*     more than the number of minor tick marks to be drawn.  By
+*     default, a value is chosen that depends on the gap between major
+*     axis values and the nature of the axis.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - If no axis is specified, (e.g. "MinTick" instead of
+*     "MinTick(2)"), then a "set" or "clear" operation will affect
+*     the attribute value of all the Plot axes, while a "get" or
+*     "test" operation will use just the MinTick(1) value.
+*att--
+*/
+/* How many divisions are there between major tick marks? Has a value
+of -1 when not set yielding a value of 1 for both axes. */
+MAKE_CLEAR(MinTick,mintick,-1,0)
+MAKE_GET(MinTick,int,1,( this->mintick[axis] == -1 ? 1 : this->mintick[axis] ),0)
+MAKE_TEST(MinTick,( this->mintick[axis] != -1 ),0)
+MAKE_SET(MinTick,int,mintick,( (value < 1)? 1 : value ),0)
+
+MAKE_GET3(MinTick,int,1,this->umintk[axis],0)
+MAKE_SET3(MinTick,int,umintk,( (value < 1)? 1 : value ),0)
+
+/* TextLab. */
+/* --------- */
+/*
+*att++
+*  Name:
+*     TextLab(axis)
+
+*  Purpose:
+*     Draw descriptive axis labels for a Plot?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether textual labels should be drawn to describe the quantity
+*     being represented on each axis of a Plot. It takes a separate
+*     value for each physical axis of a Plot so that, for instance,
+*     the setting "TextLab(2)=1" specifies that descriptive labels
+*     should be drawn for the second axis.
+*
+*     If the TextLab value of a Plot axis is non-zero, then
+*     descriptive labels will be drawn for that axis, otherwise they
+*     will be omitted. The default behaviour is to draw descriptive
+*     labels if tick marks and numerical labels are being drawn around
+*     the edges of the plotting area (see the Labelling attribute),
+*     but to omit them otherwise.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The text used for the descriptive labels is derived from the
+*     Plot's Label(axis) attribute, together with its Unit(axis)
+*     attribute if appropriate (see the LabelUnits(axis) attribute).
+*     - The drawing of numerical axis labels for a Plot (which
+*     indicate values on the axis) is controlled by the NumLab(axis)
+*     attribute.
+*     - If no axis is specified, (e.g. "TextLab" instead of
+*     "TextLab(2)"), then a "set" or "clear" operation will affect
+*     the attribute value of all the Plot axes, while a "get" or
+*     "test" operation will use just the TextLab(1) value.
+*att--
+*/
+/* Are textual labels to be displayed on each axis? Has a value of -1
+   when not set yielding a value of 1 (yes) for both axes. */
+MAKE_CLEAR(TextLab,textlab,-1,0)
+MAKE_GET(TextLab,int,1,( this->textlab[axis] == -1 ? 1 : this->textlab[axis] ),0)
+MAKE_TEST(TextLab,( this->textlab[axis] != -1 ),0)
+MAKE_SET(TextLab,int,textlab,( value ? 1 : 0 ),0)
+
+MAKE_GET3(TextLab,int,1,this->utxtlb[axis],0)
+MAKE_SET3(TextLab,int,utxtlb,( value ? 1 : 0 ),0)
+
+/* TextLabGap. */
+/* ----------- */
+/*
+*att++
+*  Name:
+*     TextLabGap(axis)
+
+*  Purpose:
+*     Spacing of descriptive axis labels for a Plot.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     where descriptive axis labels are placed relative to the axes they
+*     describe.  It takes a separate value for each physical axis of a
+*     Plot so that, for instance, the setting "TextLabGap(2)=0.01"
+*     specifies where the descriptive label for the second axis should
+*     be drawn.
+*
+*     For each axis, the TextLabGap value gives the spacing between the
+*     descriptive label and the edge of a box enclosing all other parts
+*     of the annotated grid (excluding other descriptive labels). The gap
+*     is measured to the nearest edge of the label (i.e. the top or the
+*     bottom). Positive values cause the descriptive label to be placed
+*     outside the bounding box, while negative values cause it to be placed
+*     inside.
+*
+*     The TextLabGap value should be given as a fraction of the minimum
+*     dimension of the plotting area, the default value being +0.01.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - If drawn, descriptive labels are always placed at the edges of
+*     the plotting area, even although the corresponding numerical
+*     labels may be drawn along axis lines in the interior of the
+*     plotting area (see the Labelling attribute).
+*     - If no axis is specified, (e.g. "TextLabGap" instead of
+*     "TextLabGap(2)"), then a "set" or "clear" operation will affect
+*     the attribute value of all the Plot axes, while a "get" or
+*     "test" operation will use just the TextLabGap(1) value.
+*att--
+*/
+/* Fractional spacing between numeric labels and axes. Has a value of AST__BAD
+when not set yielding a default value of 0.01. */
+MAKE_CLEAR(TextLabGap,textlabgap,AST__BAD,0)
+MAKE_GET(TextLabGap,double,0.0,( this->textlabgap[ axis ] == AST__BAD ? 0.01 : this->textlabgap[axis]),0)
+MAKE_SET(TextLabGap,double,textlabgap,value,0)
+MAKE_TEST(TextLabGap,( this->textlabgap[axis] != AST__BAD ),0)
+
+/* LabelUnits. */
+/* ----------- */
+/*
+*att++
+*  Name:
+*     LabelUnits(axis)
+
+*  Purpose:
+*     Use axis unit descriptions in a Plot?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     whether the descriptive labels drawn for each axis of a Plot
+*     should include a description of the units being used on the
+*     axis.  It takes a separate value for each physical axis of a
+*     Plot so that, for instance, the setting "LabelUnits(2)=1"
+*     specifies that a unit description should be included in the
+*     label for the second axis.
+*
+*     If the LabelUnits value of a Plot axis is non-zero, a unit
+*     description will be included in the descriptive label for that
+*     axis, otherwise it will be omitted.  The default behaviour is to
+*     include a unit description unless the current Frame of the Plot
+*     is a SkyFrame representing equatorial, ecliptic, galactic or
+*     supergalactic coordinates, in which case it is omitted.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - The text used for the unit description is obtained from the
+*     Plot's Unit(axis) attribute.
+*     - If no axis is specified, (e.g. "LabelUnits" instead of
+*     "LabelUnits(2)"), then a "set" or "clear" operation will affect
+*     the attribute value of all the Plot axes, while a "get" or
+*     "test" operation will use just the LabelUnits(1) value.
+*     - If the current Frame of the Plot is not a SkyFrame, but includes
+*     axes which were extracted from a SkyFrame, then the default behaviour
+*     is to include a unit description only for those axes which were not
+*     extracted from a SkyFrame.
+*att--
+*/
+/* Are textual labels to include a string describing the axis units? Has a
+value of -1 when not set yielding a default of 1. */
+MAKE_CLEAR(LabelUnits,labelunits,-1,0)
+MAKE_TEST(LabelUnits,( this->labelunits[axis] != -1 ),0)
+MAKE_SET(LabelUnits,int,labelunits,( value ? 1 : 0 ),0)
+
+MAKE_GET3(LabelUnits,int,1,this->ulbunit[axis],0)
+MAKE_SET3(LabelUnits,int,ulbunit,( value ? 1 : 0 ),0)
+
+/* Style. */
+/* ------ */
+/*
+*att++
+*  Name:
+*     Style(element)
+
+*  Purpose:
+*     Line style for a Plot element.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute determines the line style used when drawing each
+*     element of graphical output produced by a Plot. It takes a
+*     separate value for each graphical element so that, for instance,
+*     the setting "Style(border)=2" causes the Plot border to be drawn
+*     using line style 2 (which might result in, say, a dashed line).
+*
+*     The range of integer line styles available and their appearance
+*     is determined by the underlying graphics system.  The default
+*     behaviour is for all graphical elements to be drawn using the
+*     default line style supplied by this graphics system (normally,
+*     this is likely to give a solid line).
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - For a list of the graphical elements available, see the
+*     description of the Plot class.
+*     - If no graphical element is specified, (e.g. "Style" instead of
+*     "Style(border)"), then a "set" or "clear" operation will affect
+*     the attribute value of all graphical elements, while a "get" or
+*     "test" operation will use just the Style(Border) value.
+*att--
+*/
+/* Line styles. Has a value of -1 when not set yielding a default of 1. */
+MAKE_CLEAR(Style,style,-1,AST__NPID)
+MAKE_GET(Style,int,1,( this->style[axis] == -1 ? 1 : this->style[axis] ),AST__NPID)
+MAKE_TEST(Style,( this->style[axis] != -1 ),AST__NPID)
+MAKE_SET(Style,int,style,value,AST__NPID)
+
+/* Font. */
+/* ----- */
+/*
+*att++
+*  Name:
+*     Font(element)
+
+*  Purpose:
+*     Character font for a Plot element.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute determines the character font index used when
+*     drawing each element of graphical output produced by a Plot. It
+*     takes a separate value for each graphical element so that, for
+*     instance, the setting "Font(title)=2" causes the Plot title to
+*     be drawn using font number 2.
+*
+*     The range of integer font indices available and the appearance
+*     of the resulting text is determined by the underlying graphics
+*     system.  The default behaviour is for all graphical elements to
+*     be drawn using the default font supplied by this graphics
+*     system.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - For a list of the graphical elements available, see the
+*     description of the Plot class.
+*     - If no graphical element is specified, (e.g. "Font" instead
+*     of "Font(title)"), then a "set" or "clear" operation will
+*     affect the attribute value of all graphical elements, while a
+*     "get" or "test" operation will use just the Font(TextLab)
+*     value.
+*att--
+*/
+/* Character fonts. Has a value of -1 when not set yielding a default of 1. */
+MAKE_CLEAR(Font,font,-1,AST__NPID)
+MAKE_GET(Font,int,1,( this->font[axis] == -1 ? 1 : this->font[axis] ),AST__NPID)
+MAKE_TEST(Font,( this->font[axis] != -1 ),AST__NPID)
+MAKE_SET(Font,int,font,value,AST__NPID)
+
+/* Colour. */
+/* ------- */
+/*
+*att++
+*  Name:
+*     Colour(element)
+
+*  Purpose:
+*     Colour index for a Plot element.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute determines the colour index used when drawing
+*     each element of graphical output produced by a Plot. It takes a
+*     separate value for each graphical element so that, for instance,
+*     the setting "Colour(title)=2" causes the Plot title to be drawn
+*     using colour index 2. The synonym "Color" may also be used.
+*
+*     The range of integer colour indices available and their
+*     appearance is determined by the underlying graphics system. The
+*     default behaviour is for all graphical elements to be drawn
+*     using the default colour index supplied by this graphics system
+*     (normally, this is likely to result in white plotting on a black
+*     background, or vice versa).
+d
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - For a list of the graphical elements available, see the
+*     description of the Plot class.
+*     - If no graphical element is specified, (e.g. "Colour" instead
+*     of "Colour(title)"), then a "set" or "clear" operation will
+*     affect the attribute value of all graphical elements, while a
+*     "get" or "test" operation will use just the Colour(TextLab)
+*     value.
+*att--
+*/
+/* Colours. Has a value of -1 when not set yielding a default of 1. */
+MAKE_CLEAR(Colour,colour,-1,AST__NPID)
+MAKE_GET(Colour,int,1,( this->colour[axis] == -1 ? 1 : this->colour[axis] ),AST__NPID)
+MAKE_TEST(Colour,( this->colour[axis] != -1 ),AST__NPID)
+MAKE_SET(Colour,int,colour,value,AST__NPID)
+
+/* Width. */
+/* ------ */
+/*
+*att++
+*  Name:
+*     Width(element)
+
+*  Purpose:
+*     Line width for a Plot element.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute determines the line width used when drawing each
+*     element of graphical output produced by a Plot.  It takes a
+*     separate value for each graphical element so that, for instance,
+*     the setting "Width(border)=2.0" causes the Plot border to be
+*     drawn using a line width of 2.0. A value of 1.0 results in a
+*     line thickness which is approximately 0.0005 times the length of
+*     the diagonal of the entire display surface.
+*
+*     The actual appearance of lines drawn with any particular width,
+*     and the range of available widths, is determined by the
+*     underlying graphics system.  The default behaviour is for all
+*     graphical elements to be drawn using the default line width
+*     supplied by this graphics system. This will not necessarily
+*     correspond to a Width value of 1.0.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - For a list of the graphical elements available, see the
+*     description of the Plot class.
+*     - If no graphical element is specified, (e.g. "Width" instead of
+*     "Width(border)"), then a "set" or "clear" operation will affect
+*     the attribute value of all graphical elements, while a "get" or
+*     "test" operation will use just the Width(Border) value.
+*att--
+*/
+/* Line widths. Has a value of AST__BAD when not set yielding a
+   default of 1.0. */
+MAKE_CLEAR(Width,width,AST__BAD,AST__NPID)
+MAKE_GET(Width,double,1.0,( this->width[axis] == AST__BAD ? 1.0 : this->width[axis] ),AST__NPID)
+MAKE_TEST(Width,( this->width[axis] != AST__BAD ),AST__NPID)
+MAKE_SET(Width,double,width,(value!=0.00)?value:(astError(AST__ATTIN,"astSetWidth(Plot):Invalid zero value supplied for Width(%s) attribute", status,GrfItem(axis,NULL,NULL, status )),this->width[axis]),AST__NPID)
+
+/* Size. */
+/* ----- */
+/*
+*att++
+*  Name:
+*     Size(element)
+
+*  Purpose:
+*     Character size for a Plot element.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating Point.
+
+*  Description:
+*     This attribute determines the character size used when drawing
+*     each element of graphical output produced by a Plot. It takes a
+*     separate value for each graphical element so that, for instance,
+*     the setting "Size(title)=2.0" causes the Plot title to be drawn
+*     using twice the default character size.
+*
+*     The range of character sizes available and the appearance of the
+*     resulting text is determined by the underlying graphics system.
+*     The default behaviour is for all graphical elements to be drawn
+*     using the default character size supplied by this graphics
+*     system.
+
+*  Applicability:
+*     Plot
+*        All Plots have this attribute.
+
+*  Notes:
+*     - For a list of the graphical elements available, see the
+*     description of the Plot class.
+*     - If no graphical element is specified, (e.g. "Size" instead
+*     of "Size(title)"), then a "set" or "clear" operation will
+*     affect the attribute value of all graphical elements, while a
+*     "get" or "test" operation will use just the Size(TextLab)
+*     value.
+*att--
+*/
+/* Character sizes. Has a value of AST__BAD when not set yielding a default
+   of 1.0. */
+MAKE_CLEAR(Size,size,AST__BAD,AST__NPID)
+MAKE_GET(Size,double,1.0,( this->size[axis] == AST__BAD ? 1.0 : this->size[axis] ),AST__NPID)
+MAKE_TEST(Size,( this->size[axis] != AST__BAD ),AST__NPID)
+MAKE_SET(Size,double,size,(value!=0.00)?value:(astError(AST__ATTIN,"astSetSize(Plot): Invalid zero value supplied for Size(%s) attribute", status,GrfItem(axis,NULL,NULL, status )),this->size[axis]),AST__NPID)
+
+/* Member functions. */
+/* ================= */
+static void AddCdt( AstPlotCurveData *cdt1, AstPlotCurveData *cdt2, const char *method,
+                    const char *class, int *status ){
+/*
+*
+*  Name:
+*     AddCdt
+
+*  Purpose:
+*     Append one AstPlotCurveData structure to another.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void AddCdt( AstPlotCurveData *cdt1, AstPlotCurveData *cdt2, const char *method,
+*                  const char *class, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     The contents of the structure pointed to by "cdt2" is appended
+*     to the structure pointed to by "cdt1".
+
+*  Parameters:
+*     cdt1
+*        Pointer to the AstPlotCurveData structure to be modified.
+*     cdt2
+*        Pointer to the AstPlotCurveData structure to be appended to cdt1.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  An error is reported if there is insufficient room in "cdt1" to
+*     store the information in "cdt2".
+
+*/
+
+/* Local Variables: */
+   int nbrk, i, j;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the total number of breaks described by both structures. */
+   nbrk = cdt1->nbrk + cdt2->nbrk;
+
+/* Report an error if this number of breaks cannot be stored in a AstPlotCurveData
+   structure. */
+   if( nbrk > AST__MXBRK ){
+      astError( AST__CVBRK, "%s(%s): Number of breaks in curve "
+                "exceeds %d.", status, method, class, AST__MXBRK );
+
+/* Otherwise, append the information. */
+   } else {
+
+/* Store the index within "cdt1" of the next break to be added. */
+      j = cdt1->nbrk;
+
+/* Add each the position and direction information for each of the breaks
+   in "cdt2". */
+      for( i = 0; i < cdt2->nbrk; i++ ){
+         cdt1->xbrk[ j ] = cdt2->xbrk[ i ];
+         cdt1->ybrk[ j ] = cdt2->ybrk[ i ];
+         cdt1->vxbrk[ j ] = cdt2->vxbrk[ i ];
+         cdt1->vybrk[ j ] = cdt2->vybrk[ i ];
+
+/* Increment the index of the next break in "cdt1". */
+         j++;
+      }
+
+/* Update the number of breaks in "cdt1". */
+      cdt1->nbrk = nbrk;
+
+/* Update the length of the curve described by "cdt1". */
+      cdt1->length += cdt2->length;
+
+/* Update the flag indicating if the entire curve is outside the plotting
+   zone. */
+      if( !cdt2->out ) cdt1->out = 0;
+
+   }
+
+/* Return. */
+   return;
+
+}
+
+static void Apoly( AstPlot *this, float x, float y, int *status ){
+/*
+*  Name:
+*     Apoly
+
+*  Purpose:
+*     Append a another point to a poly line.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Apoly( AstPlot *this, float x, float y, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function appends the supplied point to the current poly line.
+
+*  Parameters:
+*     x
+*        The graphics x coordinate.
+*     y
+*        The graphics y coordinate.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int ipoint;
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Extend the buffers, and add the supplied point to the end. */
+   ipoint = Poly_n++;
+   Poly_x = astGrow( Poly_x, Poly_n, sizeof(*Poly_x) );
+   Poly_y = astGrow( Poly_y, Poly_n, sizeof(*Poly_y) );
+   if( astOK ) {
+      Poly_x[ ipoint ] = x;
+      Poly_y[ ipoint ] = y;
+   }
+
+/* Update the box containing all plotted lines. */
+   Box_lbnd[ 0 ] = astMIN( x, Box_lbnd[ 0 ] );
+   Box_ubnd[ 0 ] = astMAX( x, Box_ubnd[ 0 ] );
+   Box_lbnd[ 1 ] = astMIN( y, Box_lbnd[ 1 ] );
+   Box_ubnd[ 1 ] = astMAX( y, Box_ubnd[ 1 ] );
+
+}
+
+static void AxPlot( AstPlot *this, int axis, const double *start, double length,
+                    int ink, AstPlotCurveData *cdata, const char *method, const char *class, int *status ){
+/*
+*
+*  Name:
+*     AxPlot
+
+*  Purpose:
+*     Draw a curve with constant axis value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void AxPlot( AstPlot *this, int axis, const double *start, double length,
+*                  int ink, AstPlotCurveData *cdata, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function draws a section of a curve of the specified length
+*     with constant value on a specified axis in the current Frame of the
+*     Plot, starting at the specified position. The algorithm used can handle
+*     discontinuities in the Mapping between the current Frame and graphics
+*     coordinates, and information describing any breaks in the curve
+*     (including the start and end of the curve) are returned in the supplied
+*     AstPlotCurveData structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     axis
+*        The zero-based index of an axis within the current Frame of the Plot.
+*        The curve has a varying value on this axis.
+*     start
+*        A pointer to a an array holding the coordinates of the start of the
+*        curve within the current Frame of the Plot.
+*     length
+*        The length of the section of the curve to be drawn, given as an
+*        increment along the axis specified by parameter "axis".
+*     ink
+*        If zero, the curve is not actually drawn, but information about
+*        the breaks is still returned. If non-zero, the curve is also drawn.
+*     cdata
+*        A pointer to a structure in which to return information about the
+*        breaks in the curve.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  No curve is draw if the "start" array contains any bad values
+*     (i.e. values equal to AST__BAD), or if the "length" value is bad,
+*     or if a NULL pointer is supplied for "cdata". No errors are reported
+*     in these cases.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   double d[ CRV_NPNT ];   /* Offsets to evenly spaced points along curve */
+   double x[ CRV_NPNT ];   /* X coords at evenly spaced points along curve */
+   double y[ CRV_NPNT ];   /* Y coords at evenly spaced points along curve */
+   double tol;             /* Absolute tolerance value */
+   int i;                  /* Loop count */
+   int naxes;              /* No. of axes in the base Frame */
+   int ok;                 /* Are all start coords good? */
+   int gridid;             /* Identifier value for element being drawn */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+#ifdef CRV_TRACE
+   printf("AXPLOT: axis %d, start (%.*g,%.*g), length %.*g\n",
+          axis, DBL_DIG, start[0], DBL_DIG, start[1], DBL_DIG, length );
+   getchar();
+#endif
+
+
+/* Initialise any supplied cdata structure to hold safe values. */
+   if( cdata ){
+      cdata->length = 0.0;
+      cdata->out = 1;
+      cdata->nbrk = 0;
+   }
+
+/* Get the number of axes in the current Frame. */
+   naxes = astGetNout( this );
+
+/* Check the "start" parameter for bad values. */
+   ok = 1;
+   for( i = 0; i < naxes; i++ ) {
+      if( start[ i ] == AST__BAD ){
+         ok = 0;
+         break;
+      }
+   }
+
+/* Check the "length" parameter for bad values. */
+   if( length == AST__BAD ) ok = 0;
+
+/* Check that the "cdata" pointer can be used. */
+   if( !cdata ) ok = 0;
+
+/* Only proceed if the parameters are OK, and there has been no error. */
+   if( ok && astOK ){
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+      if( axis == 0 ) {
+         gridid = AST__GRIDLINE2_ID;
+      } else {
+         gridid = AST__GRIDLINE1_ID;
+      }
+      astGrfAttrs( this, gridid, 1, GRF__LINE, method, class );
+
+/* Ensure the globals holding the scaling from graphics coords to equally
+   scaled coords are available. */
+      GScales( this, NULL, NULL, method, class, status );
+
+/* Set up the externals used to communicate with the Map1 function...
+   The number of axes in the physical coordinate system (i.e. the current
+   Frame). */
+      Map1_ncoord = naxes;
+
+/* See if tick marks are logarithmically or linearly spaced. */
+      Map1_log = astGetLogTicks( this, axis );
+
+/* A pointer to the Plot, the Current Frame and the Mapping. */
+      Map1_plot = this;
+      Map1_frame = astGetFrame( this, AST__CURRENT );
+      Map1_map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Physical coords at the start of the curve (dist=0). */
+      Map1_origin = start;
+
+/* Length of the curve. */
+      Map1_length = length;
+
+/* The index of the axis which the curve follows. */
+      Map1_axis = axis;
+
+/* Decide whether to omit points not in their normal ranges. */
+      Map1_norm = !IsASkyAxis( Map1_frame, 0, status ) &&
+                  !IsASkyAxis( Map1_frame, 1, status );
+
+/* Convert the tolerance from relative to absolute graphics coordinates. */
+      tol = astGetTol( this )*astMAX( this->xhi - this->xlo,
+                                   this->yhi - this->ylo );
+
+/* Now set up the external variables used by the Crv and CrvLine function. */
+      Crv_scerr = ( astGetLogPlot( this, 0 ) ||
+                    astGetLogPlot( this, 1 ) ) ? 100.0 : 1.5;
+      Crv_ux0 = AST__BAD;
+      Crv_tol = tol;
+      Crv_limit = 0.5*tol*tol;
+      Crv_map = Map1;
+      Crv_ink = ink;
+      Crv_xlo = this->xlo;
+      Crv_xhi = this->xhi;
+      Crv_ylo = this->ylo;
+      Crv_yhi = this->yhi;
+      Crv_out = 1;
+      Crv_xbrk = cdata->xbrk;
+      Crv_ybrk = cdata->ybrk;
+      Crv_vxbrk = cdata->vxbrk;
+      Crv_vybrk = cdata->vybrk;
+      Crv_clip = astGetClip( this ) & 1;
+
+/* Set up a list of points spread evenly over the curve. */
+      for( i = 0; i < CRV_NPNT; i++ ){
+        d[ i ] = ( (double) i)/( (double) CRV_NSEG );
+      }
+
+/* Map these points into graphics coordinates. */
+      Map1( CRV_NPNT, d, x, y, method, class, status GLOBALS_NAME );
+
+/* Use Crv and Map1 to draw the curve. */
+      Crv( this, d, x, y, 0, NULL, NULL, method, class, status );
+
+/* End the current poly line. */
+      Opoly( this, status );
+
+/* Tidy up the static data used by Map1. */
+      Map1( 0, NULL, NULL, NULL, method, class, status GLOBALS_NAME );
+
+/* If no part of the curve could be drawn, set the number of breaks and the
+   length of the drawn curve to zero. */
+      if( Crv_out ) {
+         Crv_nbrk = 0;
+         Crv_len = 0.0F;
+
+/* Otherwise, add an extra break to the returned structure at the position of
+   the last point to be plotted. */
+      } else {
+         Crv_nbrk++;
+         if( Crv_nbrk > AST__PLOT_CRV_MXBRK ){
+            astError( AST__CVBRK, "%s(%s): Number of breaks in curve "
+                      "exceeds %d.", status, method, class, AST__PLOT_CRV_MXBRK );
+         } else {
+            *(Crv_xbrk++) = (float) Crv_xl;
+            *(Crv_ybrk++) = (float) Crv_yl;
+            *(Crv_vxbrk++) = (float) -Crv_vxl;
+            *(Crv_vybrk++) = (float) -Crv_vyl;
+         }
+      }
+
+/* Store extra information about the curve in the returned structure, and
+   purge any zero length sections. */
+      if( cdata ){
+         cdata->length = Crv_len;
+         cdata->out = Crv_out;
+         cdata->nbrk = Crv_nbrk;
+         PurgeCdata( cdata, status );
+      }
+
+/* Annul the Frame and Mapping. */
+      Map1_frame = astAnnul( Map1_frame );
+      Map1_map = astAnnul( Map1_map );
+
+/* Re-establish the original graphical attributes. */
+      astGrfAttrs( this, gridid, 0, GRF__LINE, method, class );
+
+   }
+
+/* Return. */
+   return;
+
+}
+
+static void BBuf( AstPlot *this, int *status ) {
+/*
+*++
+*  Name:
+c     astBBuf
+f     AST_BBUF
+
+*  Purpose:
+*     Begin a new graphical buffering context.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astBBuf( AstPlot *this )
+f     CALL AST_BBUF( THIS STATUS )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+c     This function
+f     This routine
+*     starts a new graphics buffering context. A matching call to the
+c     function astEBuf
+f     routine AST_EBUF
+*     should be used to end the context.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - The nature of the buffering is determined by the underlying
+*     graphics system (as defined by the current grf module). Each call
+c     to this function
+f     to this routine
+c     to this function
+f     to this routine
+*     simply invokes the astGBBuf function in the grf module.
+
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the active GRF BBuf function. */
+   GBBuf( this, "astBBuf", astGetClass( this ), status );
+
+}
+
+static int Boundary( AstPlot *this, const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     Boundary
+
+*  Purpose:
+*     Draw a boundary around regions containing valid physical positions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Boundary( AstPlot *this, const char *method, const char *class, int *status  )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function draws a boundary around the regions of the plotting
+*     area which contain valid, unclipped, physical coordinates, but does
+*     not include the intersections with the edges of the plotting area.
+*
+*     Broadly, the algorithm is as follows: An initial coarse grid is
+*     created covering the entire plotting area. This grid consists of a
+*     regular square matrix of points in graphics coordinates, and the
+*     corresponding physical coordinates. An array of flags is created,
+*     one for each grid cell, indicating if the boundary passes through the
+*     cell. This is assumed to be the case if the cell has a mix of good and
+*     bad corners (i.e corners which have good or bad physical coordinates).
+*     This assumption does not locate all boundary cells though, since if
+*     the boundary passes into and out of a cell throught the same edge,
+*     the corners of the cell will be either all good or all bad. But for
+*     the moment, we just concentrate on the ones found using this simple
+*     assumption. For each such cell, a finer grid is then created covering
+*     the cell, and the boundary is drawn through this fine grid using
+*     TraceBorder. TraceBorder returns a set of four flags indicating which
+*     edges of the cell were intersected by the boundary. A check is then
+*     made on any of the four neighbouring cells into which the curve
+*     passes. If any of these cells were not flagged as boundary cells using
+*     the simple assumption described earlier, then they are flagged now
+*     (with a different flag value). Once all the cells located using the
+*     simple assumption have been processed, any further cells flagged
+*     with the new flag value are also processed using TraceBorder in the
+*     same way. This process is repeated until no extra boundary cells are
+*     found.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A flag indicating if any regions containing invalid physical
+*     coordinates were found within the plotting area.
+
+*  Notes:
+*     -  This function assumes the physical coordinate Frame is
+*     2-dimensional, and it should not be used if this is not the case.
+*     -  A value of zero is returned if an error has already occurred, or
+*     if this function should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;         /* Pointer to base Plot Frame */
+   AstFrame *cfrm;         /* Pointer to current Plot Frame */
+   AstMapping *map;        /* Pointer to Plot mapping (graphics -> physical) */
+   AstMapping *rmap;       /* Pointer to Plot mapping (graphics -> physical) */
+   AstRegion *breg;        /* Region mapped into base Plot Frame */
+   double blbnd[ 2 ];      /* Lower grid bounds in base frame */
+   double bubnd[ 2 ];      /* Upper grid bounds in base frame */
+   double dx;              /* Plotting area width */
+   double dy;              /* Plotting area height */
+   double power;           /* Exponent in pow call */
+   double rat;             /* Ratio by which to reduce DIM */
+   double tol;             /* Fractional plotting tolerance */
+   int dim;                /* No. of points along each edge of coarse grid */
+   int edges[ 4 ];         /* Flags indicating edges bisected by boundary */
+   int rate_disabled;      /* Was the astRate method initially disabled? */
+   int ret;                /* Any regions containing bad physical coords? */
+
+/* Check global status. */
+   if( !astOK ) return 0;
+
+/* Initialise the answer to indicate that no regions containing invalid
+   physical coordinates have been found. */
+   ret = 0;
+
+/* Get the current Frame from the Plot. */
+   cfrm = astGetFrame( this, AST__CURRENT );
+
+/* If it is a region, we use a special method, if possible, to trace the
+   Region boundary. Otherwise, we use a grid tracing method that makes no
+   assumptions about the nature of the Mapping or Frame. */
+   if( !DrawRegion( this, cfrm, method, class, status ) ) {
+
+/* Each basic element of the boundary drawn by the following algorithm
+   will be drawn at a multiple of 45 degrees to the horizontal. This can
+   cause noticable aliasing. For instance, if the border is a straight
+   line at 50 degrees to the horizontal, it will be drawn at 45 degrees
+   for long sections, followed by a vertical leap to catch up with where
+   it should be. Because of this we use a finer tolerance than for other
+   drawing. */
+      tol = 0.25*astGetTol( this );
+
+/* Set up the dimension of a coarse grid in graphics coordinates to cover the
+   whole plotting area. This is chosen to give a finer grid for smaller
+   plotting tolerances. Note, putting the power as a literal constant in
+   the call to pow seems to cause a segmentation violation on some systems. */
+      power = -0.666666666;
+      dim = (int) 4*pow( tol, power ) + 10;
+      if( dim > 400 ) dim = 400;
+      if( dim < 3 ) dim = 3;
+
+/* Store the required plotting tolerance as a distance in graphics
+   coords. */
+      dx = fabs( this->xhi - this->xlo );
+      dy = fabs( this->xhi - this->xlo );
+      tol *= ( ( dx > dy ) ? dx : dy );
+
+/* Extract the Mapping from the Plot. */
+      map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Select the area covered by the coarse grid. If the current Frame is a
+   Region, we use the bounding box of Region after being mapped into
+   graphics coords. */
+      if( astIsARegion( cfrm ) ) {
+         bfrm = astGetFrame( this, AST__BASE );
+
+/* Get the Mapping from the current to the base Frame in the Plot, and
+   remove the effects of any Regions. */
+
+         astInvert( map );
+         rmap = astRemoveRegions( map );
+         astInvert( map );
+
+/* Map the Region into the GRAPHICS frame. */
+         breg = astMapRegion( cfrm, rmap, bfrm );
+         astGetRegionBounds( breg, blbnd, bubnd );
+
+         rmap = astAnnul( rmap );
+         bfrm = astAnnul( bfrm );
+         breg = astAnnul( breg );
+
+         rat = ( ( bubnd[ 0 ] - blbnd[ 0 ] )*( bubnd[ 1 ] - blbnd[ 1 ] ) )/
+               ( ( this->xhi - this->xlo )*( this->yhi - this->ylo ) );
+         rat = sqrt( rat );
+         dim = (int) ( rat*dim );
+         if( dim < 3 ) dim = 3;
+
+/* If the current Frame is not a Region, use the whole plot. */
+      } else {
+         blbnd[ 0 ] = this->xlo;
+         blbnd[ 1 ] = this->ylo;
+         bubnd[ 0 ] = this->xhi;
+         bubnd[ 1 ] = this->yhi;
+      }
+
+/* Disable the astRate method in order to improve the speed of
+   evaluating the Mapping in cases where the Mapping includes an
+   AstRateMap. Note the original value of the flag so that it can be
+   re-instated at the end. */
+      rate_disabled = astRateState( 1 );
+
+/* Draw the boundary. */
+      ret = TraceBorder( this, map, blbnd[ 0 ], bubnd[ 0 ], blbnd[ 1 ],
+                         bubnd[ 1 ], dim, tol, edges, method, class, status );
+
+/* Re-instate the original setting of the "astRate disabled" flag. */
+      astRateState( rate_disabled );
+
+/* Release the remaining resources. */
+      map = astAnnul( map );
+   }
+   cfrm = astAnnul( cfrm );
+
+/* If an error has occurred, return 0. */
+   if( !astOK ) ret = 0;
+
+/* Return the answer. */
+   return ret;
+}
+
+static int Border( AstPlot *this_nd, int *status ){
+/*
+*++
+*  Name:
+c     astBorder
+f     AST_BORDER
+
+*  Purpose:
+*     Draw a border around valid regions of a Plot.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     int astBorder( AstPlot *this )
+f     RESULT = AST_BORDER( THIS, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function draws a (line) border around regions of the
+*     plotting area of a Plot which correspond to valid, unclipped
+*     physical coordinates. For example, when plotting using an
+*     all-sky map projection, this function could be used to draw the
+*     boundary of the celestial sphere when it is projected on to the
+*     plotting surface.
+*
+*     If the entire plotting area contains valid, unclipped physical
+*     coordinates, then the boundary will just be a rectangular box
+*     around the edges of the plotting area.
+*
+*     If the Plot is a Plot3D, this method is applied individually to
+*     each of the three 2D Plots encapsulated within the Plot3D (each of
+*     these Plots corresponds to a single 2D plane in the 3D graphics
+*     system). In addition, if the entire plotting volume has valid
+*     coordinates in the 3D current Frame of the Plot3D, then additional
+*     lines are drawn along the edges of the 3D plotting volume so that
+*     the entire plotting volume is enclosed within a cuboid grid.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astBorder()
+f     AST_BORDER = LOGICAL
+c        Zero is returned if the plotting space is completely filled by
+f        .FALSE. is returned if the plotting space is completely filled by
+*        valid, unclipped physical coordinates (so that only a
+c        rectangular box was drawn around the edge). Otherwise, one is
+f        rectangular box was drawn around the edge). Otherwise, .TRUE. is
+*        returned.
+
+*  Notes:
+c     - A value of zero will be returned if this function is invoked
+f     - A value of .FALSE. will be returned if this function is invoked
+c     with the AST error status set, or if it should fail for any
+f     with STATUS set to an error value, or if it should fail for any
+*     reason.
+*     - An error results if either the current Frame or the base Frame
+*     of the Plot is not 2-dimensional or (for a Plot3D) 3-dimensional.
+*     - An error also results if the transformation between the base
+*     and current Frames of the Plot is not defined (i.e. the Plot's
+*     TranForward attribute is zero).
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   AstPlot *this;          /* Plot with no more than 2 current axes */
+   AstPlotCurveData cdata; /* Structure to receive break information */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   int inval;              /* Were any bad regions found? */
+   int naxes;              /* No. of axes in the base Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_nd);
+
+/* Store the current method, and the class of the supplied object for use
+   in error messages.*/
+   method = "astBorder";
+   class = astGetClass( this_nd );
+
+/* Initialise the bounding box for primitives produced by this call. */
+   if( !Boxp_freeze ) {
+      Boxp_lbnd[ 0 ] = FLT_MAX;
+      Boxp_lbnd[ 1 ] = FLT_MAX;
+      Boxp_ubnd[ 0 ] = FLT_MIN;
+      Boxp_ubnd[ 1 ] = FLT_MIN;
+   }
+
+/* Check the base Frame of the Plot is 2-D. */
+   naxes = astGetNin( this_nd );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 2.", status, method, class, naxes, class );
+   }
+
+/* Get a Plot with a 2D (or 1D) current Frame. */
+   this = (AstPlot *) Fset2D( (AstFrameSet *) this_nd, AST__CURRENT, status );
+
+/* Check the current Frame of the Plot is 2-D. */
+   naxes = astGetNout( this );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the current "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 2.", status, method, class, naxes, class );
+   }
+
+/* Indicate that the GRF module should re-calculate it's cached values
+   (in case the state of the graphics system has changed since the last
+   thing was drawn). */
+   RESET_GRF;
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+   astGrfAttrs( this, AST__BORDER_ID, 1, GRF__LINE, method, class );
+
+/* We first draw the intersections of the regions containing valid
+   physical coordinates with the edges of the plotting area. First do
+   the bottom edge. */
+   LinePlot( this, this->xlo, this->ylo, this->xhi, this->ylo,
+             1, &cdata, method, class, status );
+
+/* Now do the right-hand edge. */
+   LinePlot( this, this->xhi, this->ylo, this->xhi, this->yhi,
+             1, &cdata, method, class, status );
+
+/* Now do the top edge. */
+   LinePlot( this, this->xhi, this->yhi, this->xlo, this->yhi,
+             1, &cdata, method, class, status );
+
+/* Now do the left-hand edge. */
+   LinePlot( this, this->xlo, this->yhi, this->xlo, this->ylo,
+             1, &cdata, method, class, status );
+
+/* Now draw a curve following the boundary through the interior of the
+   plotting area. If the current Frame in the Plot is a Region, we use a
+   shorter method if possible. If this is not possible, we use a longer
+   method. */
+   inval = Boundary( this, method, class, status );
+
+/* Ensure all lines are flushed to the graphics system. */
+   Fpoly( this, method, class, status );
+
+/* Re-establish the original graphical attributes. */
+   astGrfAttrs( this, AST__BORDER_ID, 0, GRF__LINE, method, class );
+
+/* Annul the 2d plot. */
+   this = astAnnul( this );
+
+/* Return. */
+   return inval;
+
+}
+
+static void BoundingBox( AstPlot *this, float lbnd[2], float ubnd[2], int *status ){
+/*
+*++
+*  Name:
+c     astBoundingBox
+f     AST_BOUNDINGBOX
+
+*  Purpose:
+*     Return a bounding box for previously drawn graphics.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astBoundingBox( AstPlot *this, float lbnd[2], float ubnd[2] )
+f     CALL AST_BOUNDINGBOX( THIS, LBND, UBND, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+c     This function returns the bounds of a box which just encompasess the
+f     This routine returns the bounds of a box which just encompasess the
+*     graphics produced by the previous call to any of the Plot methods
+*     which produce graphical output. If no such previous call has yet
+*     been made, or if the call failed for any reason, then the bounding box
+c     returned by this function is undefined.
+f     returned by this routine is undefined.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+c     lbnd
+f     LBND( 2 ) = REAL (Returned)
+*        A two element array in which is returned the lower limits of the
+*        bounding box on each of the two axes of the graphics coordinate
+*        system (the base Frame of the Plot).
+c     ubnd
+f     UBND( 2 ) = REAL (Returned)
+*        A two element array in which is returned the upper limits of the
+*        bounding box on each of the two axes of the graphics coordinate
+*        system (the base Frame of the Plot).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - An error results if the base Frame of the Plot is not
+*     2-dimensional.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   AstFrameSet *fset;      /* Pointer to the Plot's FrameSet */
+   int naxes;              /* No. of axes in the base Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Get a pointer to the FrameSet at the start of the Plot. */
+   fset = (AstFrameSet *) this;
+
+/* Check the base Frame of the Plot is 2-D. */
+   naxes = astGetNin( fset );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "astBoundingBox(%s): Number of axes (%d) in the "
+                "base Frame of the supplied %s is invalid - this number "
+                "should be 2.", status, astGetClass( this ), naxes,
+                astGetClass( this ) );
+   }
+
+/* Return the bounding box. */
+   lbnd[ 0 ] = Boxp_lbnd[ 0 ];
+   lbnd[ 1 ] = Boxp_lbnd[ 1 ];
+   ubnd[ 0 ] = Boxp_ubnd[ 0 ];
+   ubnd[ 1 ] = Boxp_ubnd[ 1 ];
+
+/* Return. */
+   return;
+
+}
+
+static int BoxCheck( float *bx, float *by, float *cx, float *cy, int *status ) {
+/*
+*  Name:
+*     BoxCheck
+
+*  Purpose:
+*     See if two boxes overlap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int BoxCheck( float *bx, float *by, float *cx, float *cy, int *status )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function returns a flag indicating if two trapezoidal boxes
+*     (box "b" and box "c") overlap or not.
+
+*  Parameters:
+*     bx
+*        Pointer to an array holding the X coordinates at the 4 corners
+*        of box "b".
+*     by
+*        Pointer to an array holding the Y coordinates at the 4 corners
+*        of box "b".
+*     cx
+*        Pointer to an array holding the X coordinates at the 4 corners
+*        of box "c".
+*     cy
+*        Pointer to an array holding the Y coordinates at the 4 corners
+*        of box "c".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero is returned if the boxes do not overlap or an error has
+*     already occurred. Otherwise, 1 is returned.
+
+*/
+
+/* Local Variables: */
+   float x2;
+   float y2;
+   int i;
+   int ip;
+   int j;
+   int jp;
+   int ret;
+
+/* Assume the boxes do not overlap. */
+   ret = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return ret;
+
+/* Check each corner of box b to see if it is inside box c. */
+   for( j = 0; j < 4 && ret == 0; j++ ){
+      if( Inside( 4, cx, cy, bx[ j ], by[ j ], status ) ) ret = 1;
+   }
+
+/* Now check each corner of box c to see if it is inside box b. */
+   for( j = 0; j < 4 && ret == 0; j++ ){
+      if( Inside( 4, bx, by, cx[ j ], cy[ j ], status ) ) ret = 1;
+   }
+
+/* If no overlap has yet been found, we need to see if any of the edges
+   of the boxes intersect. For instance, in the case of a cross formed by
+   a vertical rectangle crossing a horizontal rectangle, the above checks
+   on the corners would not have revealed any overlap. */
+   if( !ret ) {
+
+/* The following code assumes that the corners with indices 0, 1, 2, 3
+   are adjacent round the edge of the box. This is the case if the line
+   joining corners 0 and 1 does not cross the line joining corners 2 and
+   3 AND the line joining corners 1 and 2 does not cross the line joining
+   corners 3 and 0. If either of these conditions is not met swap the
+   corners around to correct it. First do box b. */
+      if( Cross( bx[0], by[0], bx[1], by[1],
+                 bx[2], by[2], bx[3], by[3], status ) ) {
+         x2 = bx[2];
+         y2 = by[2];
+         bx[2] = bx[1];
+         by[2] = by[1];
+         bx[1] = x2;
+         by[1] = y2;
+
+      } else if( Cross( bx[1], by[1], bx[2], by[2],
+                        bx[3], by[3], bx[0], by[0], status ) ) {
+         x2 = bx[2];
+         y2 = by[2];
+         bx[2] = bx[3];
+         by[2] = by[3];
+         bx[3] = x2;
+         by[3] = y2;
+      }
+
+/* Now do box c. */
+      if( Cross( cx[0], cy[0], cx[1], cy[1],
+                 cx[2], cy[2], cx[3], cy[3], status ) ) {
+         x2 = cx[2];
+         y2 = cy[2];
+         cx[2] = cx[1];
+         cy[2] = cy[1];
+         cx[1] = x2;
+         cy[1] = y2;
+
+      } else if( Cross( cx[1], cy[1], cx[2], cy[2],
+                        cx[3], cy[3], cx[0], cy[0], status ) ) {
+         x2 = cx[2];
+         y2 = cy[2];
+         cx[2] = cx[3];
+         cy[2] = cy[3];
+         cx[3] = x2;
+         cy[3] = y2;
+      }
+
+/* We now check each edge of box b to see if it overlaps any edge of box c. */
+      for( j = 0; j < 4 && ret == 0; j++ ) {
+
+/* This edge of box b starts at the corner with index j. Get the index of the
+   corner at which the edge ends. */
+         jp = j + 1;
+         if( jp == 4 ) jp = 0;
+
+/* Check to see if this edge of box b crosses each edge of box c in turn. */
+         for( i = 0; i < 4 && ret == 0; i++ ) {
+            ip = i + 1;
+            if( ip == 4 ) ip = 0;
+
+            ret = Cross( bx[j], by[j], bx[jp], by[jp],
+                         cx[i], cy[i], cx[ip], cy[ip], status );
+
+         }
+      }
+   }
+
+   return ret;
+}
+
+static void Bpoly( AstPlot *this, float x, float y, int *status ){
+/*
+*  Name:
+*     Bpoly
+
+*  Purpose:
+*     Begin a new poly line.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Bpoly( AstPlot *this, float x, float y, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function draws any current poly line, and then starts a new one
+*     at the supplied position.
+
+*  Parameters:
+*     x
+*        The graphics x coordinate.
+*     y
+*        The graphics y coordinate.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   int ignore;             /* Is the new point the end of the current polyline? */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* See if the new point is co-incident with the end of the current
+   polyline. If so we assume the current polyline is to be re-started,
+   rather than starting a new polyline. */
+   if( Poly_n > 0 ) {
+      ignore = ( astEQUALS( Poly_x[ Poly_n - 1 ], x, 1.0E8 ) &&
+                 astEQUALS( Poly_y[ Poly_n - 1 ], y, 1.0E8 ) );
+   } else {
+      ignore = 0;
+   }
+
+/* If the supplied point is not at the end of the current polyline, draw
+   any existing poly line. This will empty the buffer. Then add the
+   supplied point into the buffer. */
+   if( !ignore ) {
+      Opoly( this, status );
+      Apoly( this, x, y, status );
+   }
+
+}
+
+
+static int CGCapWrapper( AstPlot *this, int cap, int value, int *status ) {
+/*
+*
+*  Name:
+*     CGCapWrapper
+
+*  Purpose:
+*     Call a C implementation of the GCap Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGCapWrapper( AstPlot *this, int cap, int value, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GCap
+*     grf function to enquire or set a graphics attribute value.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     cap
+*        The capability to be inquired aboue.
+*     value
+*        The value to assign to the capability.
+*     status
+*        Pointer to the inherited status value.
+
+*  Returned Value:
+*     Non-zero if the grf module is capabale of performing the action
+*     requested by "cap".
+
+*/
+   if( !astOK ) return 0;
+   return ( (AstGCapFun) this->grffun[ AST__GCAP ] )( astGrfConID(this), cap, value );
+}
+
+static int CGAttrWrapper( AstPlot *this, int attr, double value,
+                          double *old_value, int prim, int *status ) {
+/*
+*
+*  Name:
+*     CGAttrWrapper
+
+*  Purpose:
+*     Call a C implementation of the GAttr Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGAttrWrapper( AstPlot *this, int attr, double value,
+*                        double *old_value, int prim, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GAttr
+*     grf function to enquire or set a graphics attribute value.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     attr
+*        An integer value identifying the required attribute. The
+*        following symbolic values are defined in grf.h:
+*
+*           GRF__STYLE  - Line style.
+*           GRF__WIDTH  - Line width.
+*           GRF__SIZE   - Character and marker size scale factor.
+*           GRF__FONT   - Character font.
+*           GRF__COLOUR - Colour index.
+*     value
+*        A new value to store for the attribute. If this is AST__BAD
+*        no value is stored.
+*     old_value
+*        A pointer to a double in which to return the attribute value.
+*        If this is NULL, no value is returned.
+*     prim
+*        The sort of graphics primitive to be drawn with the new attribute.
+*        Identified by the following values defined in grf.h:
+*           GRF__LINE
+*           GRF__MARK
+*           GRF__TEXT
+*     status
+*        Pointer to the inherited status value.
+
+*/
+   if( !astOK ) return 0;
+   return ( (AstGAttrFun) this->grffun[ AST__GATTR ] )( astGrfConID(this), attr, value, old_value, prim );
+}
+
+static int CGBBufWrapper( AstPlot *this, int *status ) {
+/*
+*
+*  Name:
+*     CGBBufWrapper
+
+*  Purpose:
+*     Call a C implementation of the GBBuf Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGBBufWrapper( AstPlot *this ) {
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GBBuf
+*     grf function to start a new graphics context.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     status
+*        Pointer to the inherited status value.
+
+*/
+   if( !astOK ) return 0;
+   return ( (AstGBBufFun) this->grffun[ AST__GBBUF ])( astGrfConID(this) );
+}
+
+static int CGEBufWrapper( AstPlot *this, int *status ) {
+/*
+*
+*  Name:
+*     CGEBufWrapper
+
+*  Purpose:
+*     Call a C implementation of the GEBuf Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGEBufWrapper( AstPlot *this ) {
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GEBuf
+*     grf function to start a new graphics context.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     status
+*        Pointer to the inherited status value.
+
+*/
+   if( !astOK ) return 0;
+   return ( (AstGEBufFun) this->grffun[ AST__GEBUF ])( astGrfConID(this) );
+}
+
+static int CGFlushWrapper( AstPlot *this, int *status ) {
+/*
+*
+*  Name:
+*     CGFlushWrapper
+
+*  Purpose:
+*     Call a C implementation of the GFlush Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGFlushWrapper( AstPlot *this ) {
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GFlush
+*     grf function to flush graphics.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     status
+*        Pointer to the inherited status value.
+
+*/
+   if( !astOK ) return 0;
+   return ( (AstGFlushFun) this->grffun[ AST__GFLUSH ])( astGrfConID(this) );
+}
+
+static int CGLineWrapper( AstPlot *this, int n, const float *x,
+                          const float *y, int *status ) {
+/*
+*
+*  Name:
+*     CGLineWrapper
+
+*  Purpose:
+*     Call a C implementation of the GLine Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGLineWrapper( AstPlot *this, int n, const float *x,
+*                        const float *y, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GLine
+*     grf function to draw a polyline.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     n
+*        The number of positions to be joined together.
+*     x
+*        A pointer to an array holding the "n" x values.
+*     y
+*        A pointer to an array holding the "n" y values.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+   if( !astOK ) return 0;
+   return ( (AstGLineFun) this->grffun[ AST__GLINE ])( astGrfConID(this), n, x, y );
+}
+
+static int CGMarkWrapper( AstPlot *this, int n, const float *x,
+                          const float *y, int type, int *status ) {
+/*
+*
+*  Name:
+*     CGMarkWrapper
+
+*  Purpose:
+*     Call a C implementation of the GMark Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGMarkWrapper( AstPlot *this, int n, const float *x,
+*                        const float *y, int type, int *status ) {
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GMark grf
+*     function to draw markers.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     n
+*        The number of positions to be joined together.
+*     x
+*        A pointer to an array holding the "n" x values.
+*     y
+*        A pointer to an array holding the "n" y values.
+*     type
+*        An integer which can be used to indicate the type of marker symbol
+*        required.
+*     status
+*        Pointer to the inherited status value.
+
+*/
+   if( !astOK ) return 0;
+   return ( (AstGMarkFun) this->grffun[ AST__GMARK ])( astGrfConID(this), n, x, y, type );
+}
+
+static int CGTextWrapper( AstPlot *this, const char *text, float x, float y,
+                          const char *just, float upx, float upy, int *status ) {
+/*
+*
+*  Name:
+*     CGTextWrapper
+
+*  Purpose:
+*     Call a C implementation of the GText Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGTextWrapper( AstPlot *this, const char *text, float x, float y,
+*                        const char *just, float upx, float upy, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GText grf
+*     function to draw a text string.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     x
+*        The reference x coordinate.
+*     y
+*        The reference y coordinate.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", or 'B' for "bottom", and specifies the
+*        vertical location of the reference position. Note, "bottom"
+*        corresponds to the base-line of normal text. Some characters
+*        (eg "y", "g", "p", etc) descend below the base-line. The second
+*        character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+*     upx
+*        The x component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        left to right on the screen.
+*     upy
+*        The y component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        bottom to top on the screen.
+*     status
+*        Pointer to the inherited status value.
+
+*/
+   if( !astOK ) return 0;
+   return ( (AstGTextFun) this->grffun[ AST__GTEXT ])( astGrfConID(this), text, x, y, just, upx, upy );
+}
+
+static int CGTxExtWrapper( AstPlot *this, const char *text, float x, float y,
+                           const char *just, float upx, float upy, float *xb,
+                           float *yb, int *status ) {
+/*
+*
+*  Name:
+*     CGTxExtWrapper
+
+*  Purpose:
+*     Call a C implementation of the GTxExt Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGTxExtWrapper( AstPlot *this, const char *text, float x, float y,
+*                         const char *just, float upx, float upy, float *xb,
+*                         float *yb, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GTxExt
+*     grf function to find the extent of a text string.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     x
+*        The reference x coordinate.
+*     y
+*        The reference y coordinate.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", or 'B' for "bottom", and specifies the
+*        vertical location of the reference position. Note, "bottom"
+*        corresponds to the base-line of normal text. Some characters
+*        (eg "y", "g", "p", etc) descend below the base-line. The second
+*        character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+*     upx
+*        The x component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        left to right on the screen.
+*     upy
+*        The y component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        bottom to top on the screen.
+*     xb
+*        An array of 4 elements in which to return the x coordinate of
+*        each corner of the bounding box.
+*     yb
+*        An array of 4 elements in which to return the y coordinate of
+*        each corner of the bounding box.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+   if( !astOK ) return 0;
+   return ( (AstGTxExtFun) this->grffun[ AST__GTXEXT ])( astGrfConID(this), text, x, y, just, upx, upy, xb, yb );
+}
+
+static int CGQchWrapper( AstPlot *this, float *chv, float *chh, int *status ) {
+/*
+*
+*  Name:
+*     CGQchWrapper
+
+*  Purpose:
+*     Call a C implementation of the GQch Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGQchWrapper( AstPlot *this, float *chv, float *chh, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GQch
+*     grf function to find the extent of a text string.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     chv
+*        A pointer to the double which is to receive the height of
+*        characters drawn vertically. This will be an increment in the X
+*        axis
+*     chh
+*        A pointer to the double which is to receive the height of
+*        characters drawn vertically. This will be an increment in the Y
+*        axis
+*     status
+*        Pointer to the inherited status value.
+*/
+   if( !astOK ) return 0;
+   return ( (AstGQchFun) this->grffun[ AST__GQCH ])( astGrfConID(this), chv, chh );
+}
+
+static int CGScalesWrapper( AstPlot *this, float *alpha, float *beta, int *status ) {
+/*
+*
+*  Name:
+*     CGScalesWrapper
+
+*  Purpose:
+*     Call a C implementation of the GScales Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CGScalesWrapper( AstPlot *this, float *alpha, float *beta, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function is a wrapper for a C implementation of the GScales
+*     grf function to find the extent of a text string.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     alpha
+*        A pointer to the location at which to return the scale for the
+*        X axis (i.e. Xnorm = alpha*Xworld).
+*     beta
+*        A pointer to the location at which to return the scale for the
+*        Y axis (i.e. Ynorm = beta*Yworld).
+*     status
+*        Pointer to the inherited status value.
+*/
+   if( !astOK ) return 0;
+   return ( (AstGScalesFun) this->grffun[ AST__GSCALES ])( astGrfConID(this), alpha, beta );
+}
+
+static int CheckLabels( AstPlot *this, AstFrame *frame, int axis,
+                        double *ticks, int nticks, int force, char **list,
+                        double refval, int *status ){
+/*
+*  Name:
+*     CheckLabels
+
+*  Purpose:
+*     Create tick mark labels and check that adjacent labels are different.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CheckLabels( AstPlot *this, AstFrame *frame, int axis, double *ticks,
+*                      int nticks, int force, char **list, double refval, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function formats the supplied ticks mark values using the
+*     astFormat method for the supplied Frame. Unless force is non-zero, it
+*     then checks all pairs of adjacent labels. If a pair is found which are
+*     identical then the memory holding the labels is released, and a value
+*     of zero is returned. Otherwise, a value of one is returned, indicating
+*     that adjacent labels are all different and the labels are returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     frame
+*        Pointer to the Frame.
+*     axis
+*        The zero-based index of the axis to which the tick marks refer.
+*     ticks
+*        Pointer to an array holding the tick mark values.
+*     nticks
+*        The number of tick marks supplied by parameter "ticks".
+*     force
+*        If non-zero, then no check for identical adjacent labels is
+*        performed, and the labels are always considered to be OK.
+*     list
+*        Pointer to the start of an array of pointers. Each of the
+*        elements in this array receives a pointer to a string holding a
+*        formatted label. Each of these strings should be freed using
+*        astFree when no longer needed.
+*     refval
+*        A value to use for the other axis when normalizing.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if any pairs of identical adjacent labels were found. One
+*     otherwise.
+
+*  Notes:
+*     -  No error is reported if a pair of identical adjacent labels is
+*     found.
+*     -  If an error has already occurred, or if this function should
+*     fail for any reason, a value of zero is returned, and the array of
+*     pointers identified by "list" is filled with NULL pointers.
+
+
+*/
+
+/* Local Variables: */
+   const char *label;        /* Pointer to formatted tick value */
+   double val[ 2 ];          /* Workspace for normalizing */
+   int i;                    /* Tick index */
+   int len;                  /* Number of characters in curent label */
+   int ok;                   /* The returned flag */
+
+/* Fill the supplied label list with NULL pointers. */
+   if( list ) {
+      for( i = 0; i < nticks; i++ ) list[ i ] = NULL;
+   }
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Initialise the returned flag to indicate that all adjacent labels are
+   different. */
+   ok = 1;
+
+/* Normalize and format the first tick mark value. */
+   val[ axis ] = ticks[ 0 ];
+   val[ 1 - axis ] = refval;
+   astNorm( frame, val );
+   label = astFormat( frame, axis, val[ axis ] );
+
+/* Allocate memory holding a copy of the formatted value, and store a
+   pointer to this copy in the list of labels. */
+   if( label ){
+      len = strlen( label ) + 1;
+      list[ 0 ] = (char *) astStore( NULL, (void *) label, len );
+   } else {
+      ok = 0;
+   }
+
+/* Normalize and format each of the tick mark values in this batch. */
+   for( i = 1; i < nticks && astOK && ok; i++ ){
+      val[ axis ] = ticks[ i ];
+      val[ 1 - axis ] = refval;
+      astNorm( frame, val );
+      label = astFormat( frame, axis, val[ axis ] );
+      if( label ){
+
+/* Unless checks have been supressed, compare this label with the previous
+   label. If they are identical clear the returned flag. */
+         if( !force && !strcmp( label, list[ i - 1 ] ) ) {
+            ok = 0;
+
+/* Allocate memory holding a copy of the label, and store a
+   pointer to this copy in the list of labels. */
+         } else {
+            list[ i ] = (char *) astStore( NULL, (void *) label, strlen( label ) + 1 );
+         }
+
+      } else {
+         ok = 0;
+      }
+
+   }
+
+/* If two adjacent labels were identical, or an error occurred, release the
+   memory used to store the labels. */
+   if( !ok || !astOK ){
+      for( i = 0; i < nticks; i++ ){
+         if( list[ i ] ) list[ i ] = (char *) astFree( (void *) list[ i ] );
+      }
+   }
+
+/* Ensure a value of zero is returned if an error has occurred. */
+   if( !astOK ) ok = 0;
+
+/* Return the answer. */
+   return ok;
+
+}
+
+static char **CheckLabels2( AstPlot *this, AstFrame *frame, int axis,
+                            double *ticks, int nticks, char **old_list,
+                            double refval, int *status ){
+/*
+*  Name:
+*     CheckLabels2
+
+*  Purpose:
+*     Check that labels cannot be shortened.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     char **CheckLabels2( AstPlot *this, AstFrame *frame, int axis,
+*                          double *ticks, int nticks, char **old_list,
+*                          double refval, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function formats the supplied ticks mark values using the
+*     astFormat method for the supplied Frame. It then compares the labels
+*     with the corresponding labels supplied in "old_list". If all of the
+*     new labels are shorter than, or equal in length to, the old labels,
+*     then memory is allocated to hold the new (shorter) labels, and a
+*     pointer to this memory is returned. If any new label is longer than
+*     the corresponding old label, then a NULL pointer is returned.
+*
+*     No check is performed on whether or not there are any identical
+*     adjacent labels.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     frame
+*        Pointer to the Frame.
+*     axis
+*        The zero-based index of the axis to which the tick marks refer.
+*     ticks
+*        Pointer to an array holding the tick mark values.
+*     nticks
+*        The number of tick marks supplied by parameter "ticks".
+*     old_list
+*        Pointer to the start of an array of pointers. Each of the
+*        elements in this array should hold a pointer to a string holding a
+*        formatted label.
+*     refval
+*        A value to use for the other axis when normalizing.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to an array of pointers. Each of these pointers points to
+*     a text string holding a shortened label. If a complete set of
+*     shortened labels could not be found (or if an error occurs), a NULL
+*     pointer is returned.
+
+*  Notes:
+*     -  The memory holding the returned shortened labels should be
+*     freed by cthe caller, together with the memory holding the pointers to
+*     the labels.
+*     -  No error is reported if a pair of identical adjacent labels is
+*     found.
+*     -  If an error has already occurred, or if this function should
+*     fail for any reason, a value of NULL is returned.
+
+*/
+
+/* Local Variables: */
+   char **list;              /* The returned pointer */
+   const char *label;        /* Pointer to formatted tick value */
+   double val[ 2 ];          /* Workspace for normalizing */
+   int i;                    /* Tick index */
+   int llen;                 /* Number of characters in curent label */
+   int ok;                   /* Are the old labels OK to be used? */
+
+/* Check the global status. */
+   if( !astOK ) return NULL;
+
+/* Allocate memory to hold the pointers to the new labels. */
+   list = (char **) astMalloc( sizeof( char * )*(size_t) nticks );
+   if( list ) {
+
+/* Fill this array with NULLs for safety. */
+      for( i = 0; i < nticks; i++ ) list[ i ] = NULL;
+
+/* Initialise a flag to indicate that all the new labels are
+   shorter than the old labels. */
+      ok = 0;
+
+/* Normalize and format each of the tick mark values in this batch. */
+      for( i = 0; i < nticks && astOK; i++ ){
+         val[ axis ] = ticks[ i ];
+         val[ 1 - axis ] = refval;
+         astNorm( frame, val );
+         label = astFormat( frame, axis, val[ axis ] );
+         if( label ){
+
+/* Get the length of the new label. */
+            llen = strlen( label );
+
+/* Compare this label with the corresponding old label. If the new one is
+   longer than the old one, set the flag and leave the loop. */
+            if( llen > strlen( old_list[ i ] ) ) {
+               ok = 1;
+               break;
+            }
+
+/* Store the new label. */
+            list[ i ] = (char *) astStore( NULL, (void *) label,
+                                           (size_t) (llen + 1) );
+         }
+      }
+
+/* If the old labels are to be used, or an error occurred, release the memory
+   used to store the new labels. */
+      if( ok || !astOK ){
+         for( i = 0; i < nticks; i++ ){
+            if( list[ i ] ) list[ i ] = (char *) astFree( (void *) list[ i ] );
+         }
+         list = (char **) astFree( (void *) list );
+      }
+
+   }
+
+/* Return the answer. */
+   return list;
+
+}
+
+static int ChrLen( const char *string, int *status ){
+/*
+*  Name:
+*     ChrLen
+
+*  Purpose:
+*     Return the length of a string excluding any trailing white space.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int ChrLen( const char *string, int *status )
+
+*  Class Membership:
+*     Plot
+
+*  Description:
+*     This function returns the length of a string excluding any trailing
+*     white space.
+
+*  Parameters:
+*     string
+*        Pointer to the string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The length of a string excluding any trailing white space.
+
+*  Notes:
+*     -  A value of zero is returned if a NULL pointer is supplied, or if an
+*     error has already occurred.
+
+*/
+
+/* Local Variables: */
+   const char *c;      /* Pointer to the next character to check */
+   int ret;            /* The returned string length */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Initialise the returned string length. */
+   ret = 0;
+
+/* Check a string has been supplied. */
+   if( string ){
+
+/* Check each character in turn, starting with the last one. */
+      ret = strlen( string );
+      c = string + ret - 1;
+      while( ret ){
+         if( !isspace( (int) *c ) ) break;
+         c--;
+         ret--;
+      }
+   }
+
+/* Return the answer. */
+   return ret;
+
+}
+
+static AstPlotCurveData **CleanCdata( AstPlotCurveData **cdata, int *status ){
+/*
+*  Name:
+*     CleanCdata
+
+*  Purpose:
+*     Release the structures holding curve break information.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     AstPlotCurveData **CleanCdata( AstPlotCurveData **cdata, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function releases the memory used to hold the curve break
+*     information returned by function DrawGrid, and returns a NULL pointer.
+
+*  Parameters:
+*     cdata
+*        Pointer to the information to be freed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A NULL pointer.
+
+*  Notes:
+*     -  This function attempts to execute even if an error has already
+*     occurred.
+
+*/
+
+/* Return if a NULL pointer has been supplied. */
+   if( !cdata ) return NULL;
+
+/* Release each of the two structures in turn (if they exist). */
+   (void) astFree( (void *) cdata[ 0 ] );
+   (void) astFree( (void *) cdata[ 1 ] );
+
+/* Release the memory used to hold the two AstPlotCurveData pointers. */
+   (void) astFree( (void *) cdata );
+
+/* Return. */
+   return NULL;
+
+}
+
+static TickInfo **CleanGrid( TickInfo **grid, int *status ){
+/*
+*  Name:
+*     CleanGrid
+
+*  Purpose:
+*     Release the structures holding grid information.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     TickInfo **CleanGrid( TickInfo **grid )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function releases the memory used to hold the grid information
+*     returned by function GridLines, and returns a NULL pointer.
+
+*  Parameters:
+*     grid
+*        Pointer to the information to be freed.
+
+*  Returned Value:
+*     A NULL pointer.
+
+*  Notes:
+*     -  This function attempts to execute even if an error has already
+*     occurred.
+
+*/
+
+/* Local Variables: */
+   TickInfo *info;             /* Pointer to TickInfo structure being freed */
+   int i;                      /* Axis index */
+   int j;                      /* Tick mark index */
+
+/* Return if a NULL pointer has been supplied. */
+   if( !grid ) return NULL;
+
+/* Release each of the TickInfo structures in turn (if they exist). */
+   for( i = 0; i < 2; i++ ){
+      if( ( info = grid[ i ] ) ){
+
+/* Release the memory holding major tick mark values. */
+         (void) astFree( (void *) info->ticks );
+
+/* Release the memory holding minor tick mark values. */
+         (void) astFree( (void *) info->minticks );
+
+/* Release the memory holding curve section starting positions. */
+         (void) astFree( (void *) info->start );
+
+/* Release the memory holding curve section lengths. */
+         (void) astFree( (void *) info->length );
+
+/* If there are any tick mark labels in the structure... */
+         if( info->labels ){
+
+/* Release the memory holding each tick mark label. */
+            for( j = 0; j < info->nmajor; j++ ){
+               (void) astFree( (void *) info->labels[ j ] );
+            }
+
+/* Release the memory holding the pointers to the tick mark labels. */
+            (void) astFree( (void *) info->labels );
+
+/* Release the memory holding the format specification string. */
+            (void) astFree( (void *) info->fmt );
+
+         }
+
+/* Release the TickInfo structure. */
+         (void) astFree( (void *) info );
+      }
+   }
+
+/* Release the memory used to hold the two TickInfo pointers. */
+   (void) astFree( (void *) grid );
+
+/* Return. */
+   return NULL;
+
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Plot.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Plot member function (over-rides the astClearAttrib protected
+*     method inherited from the FrameSet class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     Plot, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPlot *this;                /* Pointer to the Plot structure */
+   char label[21];               /* Graphics item label */
+   const char *class;            /* Pointer to class string */
+   int axis;                     /* Axis number */
+   int id1;                      /* Plot object id */
+   int id2;                      /* Plot object id */
+   int id;                       /* Plot object id */
+   int len;                      /* Length of attrib string */
+   int nax;                      /* Number of base Frame axes */
+   int nc;                       /* No. characters read by astSscanf */
+   int id3;                      /* Third genuine identifier */
+   int nid;                      /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Plot structure. */
+   this = (AstPlot *) this_object;
+
+/* Get the number of base Frame axis (2 for a Plot, 3 for a Plot3D). */
+   nax = astGetNin( this );
+
+/* Obtain the length of the "attrib" string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Edge(axis). */
+/* ------------ */
+   if ( nc = 0,
+               ( 1 == astSscanf( attrib, "edge(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearEdge( this, axis - 1 );
+
+/* Grid. */
+/* ----- */
+   } else if ( !strcmp( attrib, "grid" ) ) {
+      astClearGrid( this );
+
+/* LabelUp */
+/* ------- */
+   } else if ( !strcmp( attrib, "labelup" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearLabelUp( this, axis );
+
+/* LabelUp(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "labelup(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearLabelUp( this, axis - 1 );
+
+/* LogPlot */
+/* ------- */
+   } else if ( !strcmp( attrib, "logplot" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearLogPlot( this, axis );
+
+/* LogPlot(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "logplot(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearLogPlot( this, axis - 1 );
+
+/* LogTicks */
+/* ------- */
+   } else if ( !strcmp( attrib, "logticks" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearLogTicks( this, axis );
+
+/* LogTicks(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "logticks(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearLogTicks( this, axis - 1 );
+
+/* LogLabel */
+/* ------- */
+   } else if ( !strcmp( attrib, "loglabel" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearLogLabel( this, axis );
+
+/* LogLabel(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "loglabel(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearLogLabel( this, axis - 1 );
+
+/* NumLab. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "numlab" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearNumLab( this, axis );
+
+/* NumLab(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "numlab(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearNumLab( this, axis - 1 );
+
+/* MinTick. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "mintick" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearMinTick( this, axis );
+
+/* MinTick(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "mintick(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearMinTick( this, axis - 1 );
+
+/* TextLab. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "textlab" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearTextLab( this, axis );
+
+/* TextLab(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "textlab(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearTextLab( this, axis - 1 );
+
+/* LabelUnits. */
+/* --------- */
+   } else if ( !strcmp( attrib, "labelunits" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearLabelUnits( this, axis );
+
+/* LabelUnits(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "labelunits(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearLabelUnits( this, axis - 1 );
+
+/* Style. */
+/* ------ */
+   } else if ( !strcmp( attrib, "style" ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astClearStyle( this, id );
+
+/* Style(label). */
+/* --------------*/
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "style(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, attrib, "astClear", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astClearStyle( this, id1 );
+      if( nid > 1 ) astClearStyle( this, id2 );
+      if( nid > 2 ) astClearStyle( this, id3 );
+
+/* Font. */
+/* ----- */
+   } else if ( !strcmp( attrib, "font" ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astClearFont( this, id );
+
+/* Font(label). */
+/* -------------*/
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "font(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, attrib, "astClear", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astClearFont( this, id1 );
+      if( nid > 1 ) astClearFont( this, id2 );
+      if( nid > 2 ) astClearFont( this, id3 );
+
+/* Colour. */
+/* ------- */
+   } else if ( !strcmp( attrib, "colour" ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astClearColour( this, id );
+
+/* Colour(label). */
+/* ---------------*/
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "colour(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, attrib, "astClear", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astClearColour( this, id1 );
+      if( nid > 1 ) astClearColour( this, id2 );
+      if( nid > 2 ) astClearColour( this, id3 );
+
+/* Color. */
+/* ------ */
+   } else if ( !strcmp( attrib, "color" ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astClearColour( this, id );
+
+/* Color(label). */
+/* --------------*/
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "color(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, attrib, "astClear", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astClearColour( this, id1 );
+      if( nid > 1 ) astClearColour( this, id2 );
+      if( nid > 2 ) astClearColour( this, id3 );
+
+/* Width. */
+/* ------ */
+   } else if ( !strcmp( attrib, "width" ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astClearWidth( this, id );
+
+/* Width(label). */
+/* --------------*/
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "width(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, attrib, "astClear", class, status ),
+                    nax, &id1, &id2, &id3, status );
+
+      astClearWidth( this, id1 );
+      if( nid > 1 ) astClearWidth( this, id2 );
+      if( nid > 2 ) astClearWidth( this, id3 );
+
+/* Size. */
+/* ----- */
+   } else if ( !strcmp( attrib, "size" ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astClearSize( this, id );
+
+/* Size(label). */
+/* -------------*/
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "size(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, attrib, "astClear", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astClearSize( this, id1 );
+      if( nid > 1 ) astClearSize( this, id2 );
+      if( nid > 2 ) astClearSize( this, id3 );
+
+/* LabelAt(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "labelat(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearLabelAt( this, axis - 1 );
+
+/* Centre(axis). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "centre(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearCentre( this, axis - 1 );
+
+/* Gap. */
+/* ---- */
+   } else if ( !strcmp( attrib, "gap" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearGap( this, axis );
+
+/* Gap(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "gap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearGap( this, axis - 1 );
+
+/* LogGap. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "loggap" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearLogGap( this, axis );
+
+/* LogGap(axis). */
+/* ----------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "loggap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearLogGap( this, axis - 1 );
+
+/* NumLabGap. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "numlabgap" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearNumLabGap( this, axis );
+
+/* NumLabGap(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "numlabgap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearNumLabGap( this, axis - 1 );
+
+/* TextLabGap. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "textlabgap" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearTextLabGap( this, axis );
+
+/* TextLabGap(axis). */
+/* ----------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "textlabgap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearTextLabGap( this, axis - 1 );
+
+/* TitleGap. */
+/* --------- */
+   } else if ( !strcmp( attrib, "titlegap" ) ) {
+      astClearTitleGap( this );
+
+/* MajTickLen. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "majticklen" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearMajTickLen( this, axis );
+
+/* MajTickLen(axis). */
+/* ----------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "majticklen(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearMajTickLen( this, axis - 1 );
+
+/* MinTickLen. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "minticklen" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearMinTickLen( this, axis );
+
+/* MinTickLen(axis). */
+/* ----------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "minticklen(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearMinTickLen( this, axis - 1 );
+
+/* Labelling. */
+/* -------- */
+   } else if ( !strcmp( attrib, "labelling" ) ) {
+      astClearLabelling( this );
+
+/* TickAll. */
+/* -------- */
+   } else if ( !strcmp( attrib, "tickall" ) ) {
+      astClearTickAll( this );
+
+/* ForceExterior */
+/* ------------- */
+   } else if ( !strcmp( attrib, "forceexterior" ) ) {
+      astClearForceExterior( this );
+
+/* Invisible. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "invisible" ) ) {
+      astClearInvisible( this );
+
+/* Border. */
+/* ------- */
+   } else if ( !strcmp( attrib, "border" ) ) {
+      astClearBorder( this );
+
+/* ClipOp. */
+/* ------- */
+   } else if ( !strcmp( attrib, "clipop" ) ) {
+      astClearClipOp( this );
+
+/* Clip. */
+/* ----- */
+   } else if ( !strcmp( attrib, "clip" ) ) {
+      astClearClip( this );
+
+/* Grf. */
+/* ---- */
+   } else if ( !strcmp( attrib, "grf" ) ) {
+      astClearGrf( this );
+
+/* DrawTitle. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "drawtitle" ) ) {
+      astClearDrawTitle( this );
+
+/* DrawAxes. */
+/* --------- */
+   } else if ( !strcmp( attrib, "drawaxes" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearDrawAxes( this, axis );
+
+/* Abbrev */
+/* ------ */
+   } else if ( !strcmp( attrib, "abbrev" ) ) {
+      for( axis = 0; axis < nax; axis++ ) astClearAbbrev( this, axis );
+
+/* DrawAxes(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "drawaxes(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearDrawAxes( this, axis - 1 );
+
+/* Abbrev(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "abbrev(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearAbbrev( this, axis - 1 );
+
+/* Escape. */
+/* ------- */
+   } else if ( !strcmp( attrib, "escape" ) ) {
+      astClearEscape( this );
+
+/* Tol. */
+/* ---- */
+   } else if ( !strcmp( attrib, "tol" ) ) {
+      astClearTol( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearLogPlot( AstPlot *this, int axis, int *status ){
+/*
+*
+*  Name:
+*     ClearLogPlot
+
+*  Purpose:
+*     Clear the value for a LogPlot attribute
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void ClearLogPlot( AstPlot *this, int axis, int *status )
+
+*  Class Membership:
+*     Plot member function
+
+*  Description:
+*     Assigns the default value to the LogPlot attribute of the specified
+*     axis, and also re-maps the base Frame of the Plot if necessary.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     axis
+*        Zero based axis index.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int oldval;           /* Original value of the attribute */
+   int newval;           /* Cleared (default) value of the attribute */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the axis index. */
+   if( axis < 0 || axis >= 2 ){
+      astError( AST__AXIIN, "astClearLogPlot(%s): Index (%d) is invalid for "
+                "attribute LogPlot - it should be in the range 1 to 2.", status,
+                astGetClass( this ), axis + 1 );
+
+/* Do nothing if the attribute is not currently set. */
+   } else if( astTestLogPlot( this, axis ) ){
+
+/* Get the original value of the attribute. clear the value, and then get
+   the new (default) value. */
+      oldval = this->logplot[ axis ];
+      this->logplot[ axis ] = -1;
+      newval = astGetLogPlot( this, axis );
+
+/* If the effective value has changed, attempt to remap the axis. If this
+   fails, re-instate the original value. */
+      if( ( oldval != 0 ) != ( newval != 0 ) ) {
+         if( !ToggleLogLin( this, axis, oldval, "astClearLogPlot", status ) ) {
+            this->logplot[ axis ] = oldval;
+         }
+      }
+   }
+}
+
+static void Clip( AstPlot *this, int iframe, const double lbnd[],
+                  const double ubnd[], int *status ){
+/*
+*++
+*  Name:
+c     astClip
+f     AST_CLIP
+
+*  Purpose:
+*     Set up or remove clipping for a Plot.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astClip( AstPlot *this, int iframe, const double lbnd[],
+c                   const double ubnd[] )
+f     CALL AST_CLIP( THIS, IFRAME, LBND, UBND, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+c     This function defines regions of a Plot which are to be clipped.
+f     This routine defines regions of a Plot which are to be clipped.
+*     Any subsequent graphical output created using the Plot will then
+*     be visible only within the unclipped regions of the plotting
+*     area. See also the Clip attribute.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+c     iframe
+f     IFRAME = INTEGER (Given)
+*        The index of the Frame within the Plot to which the clipping
+c        limits supplied in "lbnd" and "ubnd" (below) refer. Clipping
+f        limits supplied in LBND and UBND (below) refer. Clipping
+*        may be applied to any of the coordinate systems associated
+*        with a Plot (as defined by the Frames it contains), so this
+*        index may take any value from 1 to the number of Frames in
+*        the Plot (Nframe attribute). In addition, the values
+*        AST__BASE and AST__CURRENT may be used to specify the base
+*        and current Frames respectively.
+*
+*        For example, a value of AST__CURRENT causes clipping to be
+*        performed in physical coordinates, while a value of AST__BASE
+*        would clip in graphical coordinates. Clipping may also be
+*        removed completely by giving a value of AST__NOFRAME. In this
+*        case any clipping bounds supplied (below) are ignored.
+c     lbnd
+f     LBND( * ) = DOUBLE PRECISION (Given)
+*        An array with one element for each axis of the clipping Frame
+c        (identified by the index "iframe"). This should contain the
+f        (identified by the index IFRAME). This should contain the
+*        lower bound, on each axis, of the region which is to remain
+*        visible (unclipped).
+c     ubnd
+f     UBND( * ) = DOUBLE PRECISION (Given)
+*        An array with one element for each axis of the clipping Frame
+c        (identified by the index "iframe"). This should contain the
+f        (identified by the index IFRAME). This should contain the
+*        upper bound, on each axis, of the region which is to remain
+*        visible (unclipped).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+c     - Only one clipping Frame may be active at a time. This function
+f     - Only one clipping Frame may be active at a time. This routine
+*     will deactivate any previously-established clipping Frame before
+*     setting up new clipping limits.
+c     - The clipping produced by this function is in addition to that
+f     - The clipping produced by this routine is in addition to that
+*     specified by the Clip attribute which occurs at the edges of the
+*     plotting area
+c     established when the Plot is created (see astPlot). The
+f     established when the Plot is created (see AST_PLOT). The
+*     underlying graphics system may also impose further clipping.
+*     - When testing a graphical position for clipping, it is first
+*     transformed into the clipping Frame. The resulting coordinate on
+*     each axis is then checked against the clipping limits (given by
+c     "lbnd" and "ubnd"). By default, a position is clipped if any
+f     LBND and UBND). By default, a position is clipped if any
+*     coordinate lies outside these limits. However, if a non-zero
+*     value is assigned to the Plot's ClipOp attribute, then a
+*     position is only clipped if the coordinates on all axes lie
+*     outside their clipping limits.
+*     - If the lower clipping limit exceeds the upper limit for any
+*     axis, then the sense of clipping for that axis is reversed (so
+*     that coordinate values lying between the limits are clipped
+*     instead of those lying outside the limits). To produce a "hole"
+*     in a coordinate space (that is, an internal region where nothing
+*     is plotted), you should supply all the bounds in reversed order,
+*     and set the ClipOp attribute for the Plot to a non-zero value.
+*     - Either clipping limit may be set to the value AST__BAD, which
+*     is equivalent to setting it to infinity (or minus infinity for a
+*     lower bound) so that it is not used.
+*     - If a graphical position results in any bad coordinate values
+*     (AST__BAD) when transformed into the clipping Frame, then it is
+*     treated (for the purposes of producing graphical output) as if
+*     it were clipped.
+*     - When a Plot is used as a Mapping to transform points
+c     (e.g. using astTran2), any clipped output points are assigned
+f     (e.g. using AST_TRAN2), any clipped output points are assigned
+*     coordinate values of AST__BAD.
+*     - An error results if the base Frame of the Plot is not
+*     2-dimensional.
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *fr;           /* Pointer to the clipping Frame */
+   AstFrameSet *fset;      /* Pointer to the Plot's FrameSet */
+   int i;                  /* Axis index */
+   int ifrm;               /* The validated frame index */
+   int naxes;              /* No. of axes in the base Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   ifrm = 0;
+
+/* Get a pointer to the FrameSet at the start of the Plot. */
+   fset = (AstFrameSet *) this;
+
+/* Check the base Frame of the Plot is 2-D. */
+   naxes = astGetNin( fset );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "astClip(%s): Number of axes (%d) in the "
+                "base Frame of the supplied %s is invalid - this number "
+                "should be 2.", status, astGetClass( this ), naxes,
+                astGetClass( this ) );
+   }
+
+/* If clipping is to be switched on, check the supplied frame index and
+   bounds. */
+   if( iframe != AST__NOFRAME && astOK ) {
+
+/* Report an error if either of the bounds pointers is NULL.*/
+      if( !lbnd ){
+         astError( AST__CLPAX, "astClip(%s): A NULL pointer was "
+                   "supplied for the array holding the lower bounds of "
+                   "the clipping volume.", status, astGetClass( this ) );
+      } else if( !ubnd ){
+         astError( AST__CLPAX, "astClip(%s): A NULL pointer was "
+                   "supplied for the array holding the upper bounds of "
+                   "the clipping volume.", status, astGetClass( this ) );
+      }
+
+/* Validate the clipping frame index. */
+      ifrm = astValidateFrameIndex( fset, iframe, "astClip" );
+
+/* Get the number of axes in the clipping frame. */
+      fr = astGetFrame( this, ifrm );
+      naxes = astGetNaxes( fr );
+      fr = astAnnul( fr );
+
+   }
+
+/* Leave the current clipping information unchanged if an error has
+   occurred. */
+   if( astOK ){
+
+/* Remove all clipping information from the Plot. */
+      this->clip_lbnd = (double *) astFree( (void *) this->clip_lbnd );
+      this->clip_ubnd = (double *) astFree( (void *) this->clip_ubnd );
+      this->clip_frame = AST__NOFRAME;
+      this->clip_axes = 0;
+
+/* If bounds have been supplied, set up new clipping information. */
+      if( iframe != AST__NOFRAME ){
+
+/* Store the information. */
+         this->clip_frame = ifrm;
+         this->clip_lbnd = astStore( NULL, lbnd, sizeof(double)*(size_t)naxes );
+         this->clip_ubnd = astStore( NULL, ubnd, sizeof(double)*(size_t)naxes );
+         this->clip_axes = naxes;
+
+/* If an error has occurred, remove all clipping information. */
+         if( !astOK ){
+            this->clip_lbnd = (double *) astFree( (void *) this->clip_lbnd );
+            this->clip_ubnd = (double *) astFree( (void *) this->clip_ubnd );
+            this->clip_frame = AST__NOFRAME;
+            this->clip_axes = 0;
+
+/* Otherwise, replace any bounds of AST__BAD with suitable default
+   values. */
+         } else {
+           for( i = 0; i < naxes; i++ ){
+              if( this->clip_lbnd[ i ] == AST__BAD ) this->clip_lbnd[ i ] = -DBL_MAX;
+              if( this->clip_ubnd[ i ] == AST__BAD ) this->clip_ubnd[ i ] = DBL_MAX;
+           }
+
+         }
+
+      }
+
+   }
+
+/* Return. */
+   return;
+
+}
+
+static int Compared( const void *elem1, const void *elem2 ){
+/*
+*  Name:
+*     Compared
+
+*  Purpose:
+*     Compare two "double" values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Compared( const void *elem1, const void *elem2 )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function compares the two "double" values to which pointers
+*     are supplied, and returns an integer indicating which is larger,
+*     checking for AST__BAD values.  It is intended for use with the C
+*     Run-Time-Library sorting function "qsort".
+
+*  Parameters:
+*     elem1
+*        Pointer to the first "double".
+*     elem2
+*        Pointer to the second "double".
+
+*  Returned Value:
+*     Zero is returned if the values are equal. If the first is larger
+*     than the second then +1 is returned. Otherwise, -1 is returned.
+
+*  Notes:
+*     -  Values of AST__BAD are considered to be larger than any other
+*     value (other than another value of AST__BAD).
+*     -  If both values are AST__BAD, then zero is returned.
+*     -  This function executes even if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   double *delem1;    /* Pointer to the first "double" value */
+   double *delem2;    /* Pointer to the second "double" value */
+   int ret;           /* The returned value */
+
+/* Get pointers to the two "double" values. */
+   delem1 = (double *) elem1;
+   delem2 = (double *) elem2;
+
+/* Check the values for equality (including both values being AST__BAD). */
+   if( *delem1 == *delem2 ){
+      ret = 0;
+
+/* If the first is bad, then it is considered to be larger than the
+   second. */
+   } else if( *delem1 == AST__BAD ){
+      ret = 1;
+
+/* If the second is bad, then it is considered to be larger than the
+   first. */
+   } else if( *delem2 == AST__BAD ){
+      ret = -1;
+
+/* If the first is larger than the second, return 1. */
+   } else if( *delem1 > *delem2 ){
+      ret = 1;
+
+/* If the first is smaller than the second, return -1. */
+   } else {
+      ret = -1;
+
+   }
+
+/* Return the answer. */
+   return ret;
+
+}
+
+static int Compare_LL( const void *elem1, const void *elem2 ){
+/*
+*  Name:
+*     Compare_LL
+
+*  Purpose:
+*     Compare two LabelList structures as used by function PlotLabels.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Compare_LL( const void *elem1, const void *elem2 )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function compares two "LabelList" structures as used by function
+*     PlotLabels, and returns an integer indicating which has a larger
+*     "index" value. This function is intended to be used with the C
+*     Run-Time-Library sorting function "qsort".
+
+*  Parameters:
+*     elem1
+*        Pointer to the first LabelList.
+*     elem2
+*        Pointer to the second LabelList.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero is returned if the values are equal. If the first is larger
+*     than the second then +1 is returned. Otherwise, -1 is returned.
+
+*  Notes:
+*     -  This function executes even if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   LabelList *ll1;           /* Pointer to the first LabelList */
+   LabelList *ll2;           /* Pointer to the second LabelList */
+   int ret;                  /* The returned value */
+
+/* Get pointers to the two LabelList structures. */
+   ll1 = (LabelList *) elem1;
+   ll2 = (LabelList *) elem2;
+
+/* Compare the indices for the two label's. */
+   if( ll1->index < ll2->index ){
+      ret = -1;
+
+   } else if( ll1->index > ll2->index ){
+      ret = 1;
+
+   } else {
+      ret = 0;
+   }
+
+/* Return the answer. */
+   return ret;
+
+}
+
+static void CopyPlotDefaults( AstPlot *this, int axis, AstPlot *dplot,
+                             int daxis, int *status ){
+/*
+*+
+*  Name:
+*     astCopyPlotDefaults
+
+*  Purpose:
+*     Copy used attribute defaults from one Plot to another.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void astCopyPlotDefaults( AstPlot *this, int axis, AstPlot *dplot,
+*                               int daxis )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     Some of the attributes used by the Plot class have dynamic default
+*     values that are determined during the process of drawing an annotated
+*     grid using astGrid. The dynamic default values are stored in a
+*     separate set of components within the Plot structure. This function
+*     copies these components from one Plot to another.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot containing the values to be copied.
+*     axis
+*        The zero-based index of the axis within "this" for which the
+*        used defaults are to be copied.
+*     dplot
+*        A pointer to another Plot into which the default attribute
+*        values are to be copied.
+*     daxis
+*        The zero based index of the axis within "dplot" which is to
+*        receive the new values.
+
+*-
+*/
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+   dplot->ulglb[ daxis ] = this->ulglb[ axis ];
+   dplot->ulgtk[ daxis ] = this->ulgtk[ axis ];
+   dplot->uloggap[ daxis ] = this->uloggap[ axis ];
+   dplot->ugap[ daxis ] = this->ugap[ axis ];
+   dplot->ucentre[ daxis ] = this->ucentre[ axis ];
+   dplot->uedge[ daxis ] = this->uedge[ axis ];
+   dplot->ulblat[ daxis ] = this->ulblat[ axis ];
+   dplot->ulbunit[ daxis ] = this->ulbunit[ axis ];
+   dplot->umintk[ daxis ] = this->umintk[ axis ];
+   dplot->utxtlb[ daxis ] = this->utxtlb[ axis ];
+   dplot->umjtkln[ daxis ] = this->umjtkln[ axis ];
+
+   dplot->ugrid = this->ugrid;
+   dplot->ulbling = this->ulbling;
+   dplot->uborder = this->uborder;
+}
+
+static int CountGood( int n, double *data, int *status ){
+/*
+*  Name:
+*     CountGood
+
+*  Purpose:
+*     Coount the number of non-bad values in an array.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CountGood( int n, double *data, int *status )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function returns the number of elements in the supplied array
+*     which do not have the value AST__BAD.
+
+*  Parameters:
+*     n
+*        The total number of elements in the array.
+*     data
+*        Pointer to the start of the array.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of good points in the array.
+
+*  Notes:
+*     -  A value of zero is returned if an error has already occurred.
+
+*/
+
+/* Local Variables: */
+   int i;
+   int ngood;
+   double *value;
+
+/* Check global status. */
+   if( !astOK ) return 0;
+
+/* Initialise a pointer to the next array element, and the number of
+   good elements found so far. */
+   value = data;
+   ngood = 0;
+
+/* Check each element. */
+   for( i = 0; i < n; i++ ){
+      if( *(value++) != AST__BAD ) ngood++;
+   }
+
+/* Return the answer. */
+   return ngood;
+
+}
+
+static int Cross( float ax, float ay, float bx, float by,
+                  float cx, float cy, float dx, float dy, int *status ){
+/*
+*  Name:
+*     Cross
+
+*  Purpose:
+*     See if two line segments intersect.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Cross( float ax, float ay, float bx, float by,
+*                float cx, float cy, float dx, float dy, int *status )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function sees if the line segment (A,B) intersects the line
+*     segment (C,D).
+
+*  Parameters:
+*     ax, ay
+*        The coordinates of A.
+*     bx, by
+*        The coordinates of B.
+*     cx, cy
+*        The coordinates of C.
+*     dx, dy
+*        The coordinates of D.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the line segments do not cross or if an error has already
+*     occurred, and 1 if they do.
+
+*/
+
+/* Local Variables: */
+   int ret;
+   float m1, m2, denom, num, t1, t2;
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* Get the fraction of the distance from A to B at which the line AB
+   intersects the line CD. */
+   m1 = dx - cx;
+   m2 = dy - cy;
+   denom = (bx - ax)*m2 - (by-ay)*m1;
+   num = (ay - cy)*m1 - (ax - cx)*m2;
+
+   if( denom != 0.0 ) {
+      t1 = num / denom;
+
+/* If the the intersection occurs within the segment of the line between A
+   and B... */
+      if( t1 >= 0.0 && t1 <= 1.0 ){
+
+/* ... then get the fraction of the distance from C to D at which the
+   line CD intersects the line AB. */
+         m1 = bx - ax;
+         m2 = by - ay;
+         denom = (dx - cx)*m2 - (dy-cy)*m1;
+         num = (cy - ay)*m1 - (cx - ax)*m2;
+
+         if( denom != 0.0 ) {
+            t2 = num / denom;
+
+/* If the the intersection occurs within the segment of the line between C
+   and D then the line segments intersect. */
+            if( t2 >= 0.0 && t2 <= 1.0 ){
+               ret = 1;
+            } else {
+               ret = 0;
+            }
+
+/* If the two lines are parallel, then they do not intersect. */
+         } else {
+            ret = 0;
+         }
+
+      } else {
+         ret = 0;
+      }
+
+   } else {
+      ret = 0;
+
+   }
+
+   return ret;
+}
+
+static void Crv( AstPlot *this, double *d, double *x, double *y, int skipbad,
+                 double *box, CrvStatics *pstatics, const char *method,
+                 const char *class, int *status ){
+/*
+*  Name:
+*     Crv
+
+*  Purpose:
+*     Draw a curve.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Crv( AstPlot *this, double *d, double *x, double *y, int skipbad,
+*               double *box, CrvStatics *pstatics, const char *method,
+*               const char *class, int *status  )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function draws a curve parameterised by the distance from some
+*     starting point. The function pointed to by the external variable
+*     Crv_map is used to transform distances along the curve into graphics
+*     coordinates (X,Y). The supplied function parameters defined the
+*     section of the curve to be drawn.
+*
+*     The algorithm used needs no knowledge about the nature of the mapping
+*     performed by Crv_map, and can handle discontinuities in the curve. It
+*     first of all determines if any of the segments of the curve can be
+*     adequately represented by simply drawing a straight line through the
+*     supplied end points. This decision is based on several requirements such
+*     as keeping the angle between adjacent sections low and both ends being
+*     defined (i.e. X and Y not equal to AST__BAD). Any segments of the curve
+*     which satisfy the requirements are draw as straight lines. If any of
+*     the supplied curve segments cannot be drawn in this way, then they are
+*     split up into a set of evenly-spaced sub-segments and the graphics
+*     coordinates at the ends of these sub-segments are found using Crv_map.
+*     This function is then called recursively to draw the sub-segments. This
+*     recursion is limited in depth by the requirement that all the
+*     sub-segments must be longer than a specified lower limit. If this is not
+*     the case, then the curve is assumed to be dis-continuous and and the
+*     sub-segments are ignored.
+
+*  Parameters:
+*     d
+*        Pointer to an array of CRV_NPNT values giving the distance along
+*        the curve from the starting point to each of CRV_NPNT points. They
+*        should increase monotonically, and should be in whatever units are
+*        used by the function pointed to by Crv_map. The curve is drawn from
+*        d[0] to d[CRV_NPNT].
+*     x
+*        Pointer to an array of CRV_NPNT values giving the graphics X
+*        coordinate for the positions supplied in the array pointed to by
+*        parameter "d".
+*     y
+*        Pointer to an array of CRV_NPNT values giving the graphics Y
+*        coordinate for the positions supplied in the array pointed to by
+*        parameter "d".
+*     skipbad
+*        Controls what happens if all the supplied points are bad or
+*        outside the plotting area. If skipbad is non-zero, then it is
+*        assumed that the supplied points represent an entirely bad (or
+*        out of bounds) section of the curve, and this function will
+*        return without attempt to sub-divide any of the supplied points.
+*        If skipbad is zero, then it is assumed that we may be able to find
+*        some good points between the supplied bad points, and therefore
+*        this function will attempt to sub-divide the supplied points.
+*        Should be supplied as zero on the initial invocation.
+*     box
+*        Pointer to an array of 4 doubles houlding a bounding box within
+*        which the current segment must reside if it is to be sub-divided.
+*        Supplied in the order xlo, xhi, ylo, yhi. May be NULL in which
+*        case, no check is made on the bounding box.
+*     pstatics
+*        Pointer to a structure holding values for variables which were
+*        statically defined within this function prior to the thread-safe
+*        version of AST. If a NULL pointer is supplied, a new structure
+*        is created in dynamic memory and initialised.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  External Variables:
+*     Crv_nent = int (Read/Write)
+*        The number of recursive entries which have been made into
+*        this function. This should be set to zero before entering
+*        this function for the first time.
+*     Crv_ux0 = double (Read/Write)
+*        The X component in graphics coordinates of the unit vector
+*        along the previous segment of the curve. This should be set
+*        to AST__BAD initially to indicate that the previous section
+*        is not defined.
+*     Crv_uy0 = double (Read/Write)
+*        The Y component of the unit vector along the previous segment.
+*     Crv_limit = double (Read)
+*        The square of the maximum acceptable residual between the
+*        drawn curve and the true curve, in graphics coordinates.
+*     Crv_scerr = double (Read)
+*        If the ratio of the lengths of adjacent sub-segments is larger
+*        than Crv_scerr,then the seub-segments will be sub-divided. Note,
+*        if either axis is mapped logarithmically onto the screen, then
+*        there will naturally be large changes in scale. Crv_scerr should
+*        always be larger than 1.0.
+*     Crv_map = void (*)( int n, double *dd, double *xx, double *yy,
+*                         const char *method, const char *class ) (Read)
+*        A pointer to a function which can be called to map "n" distances
+*        along the curve (supplied in "dd") into graphics coordinates
+*        (stored in "xx" and "yy"). See function "Map1" as an example.
+*     Crv_clip = int (Read)
+*        Should lines be clipped at the edge of the plotting area?
+
+*  Notes:
+*     - The CRV_TRACE conditional compilation blocks in this function
+*     provide code which displays the recursive entries made to this
+*     function (and also pauses on initial entry until return is pressed).
+*     It is useful for investigating the details of the drawing of a
+*     curve.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS     /* Pointer to thread-specific global data */
+   CrvStatics *statics;   /* Pointer to structure holding static values */
+   double *dd;            /* Pointer to array holding sub-segment distances */
+   double *pd;            /* Pointer to next sub-segment distance */
+   double *px;            /* Pointer to next sub-segment x coord. */
+   double *py;            /* Pointer to next sub-segment y coord. */
+   double *xx;            /* Pointer to array holding sub-segment x coord.s */
+   double *yy;            /* Pointer to array holding sub-segment x coord.s */
+   double bbox[4];        /* Bounding box for this segment */
+   double dl2[ CRV_NSEG ];/* Squred segment lengths */
+   double dx[ CRV_NSEG ]; /* X increment along each segment */
+   double dy[ CRV_NSEG ]; /* Y increment along each segment */
+   int i;                 /* Segment index */
+   int seg_ok[ CRV_NSEG ];/* Flags indicating which segments can be drawn */
+   int subdivide;         /* Flag indicating if segments can be subdivided */
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* If required, allocate memory for a structure to hold the static
+   variables need by this function. */
+   if( ! pstatics ) {
+      statics = astMalloc( sizeof( CrvStatics ) );
+   } else {
+      statics = pstatics;
+   }
+
+/* If this is the first entry, set up the minimum length for a
+   sub-segment in graphics coordinates. If any segment is less than
+   this minimum length, then recursion will stop and the curve will
+   be assumed to be dis-continuous. */
+   if( !Crv_nent ) {
+      statics->limit2 = 20.0*Crv_limit/(CRV_NSEG*CRV_NSEG);
+
+#ifdef CRV_TRACE
+      statics->levels[ 0 ] = 0;
+#endif
+   }
+
+
+/* Increment the number of entries into this function. */
+   Crv_nent++;
+
+#ifdef CRV_TRACE
+   for( i = 0; i < Crv_nent; i++ ) {
+      printf("%d ",statics->levels[ i ] );
+   }
+   printf("\n");
+
+   if( getchar() == 'm' ) {
+      float ffx,ffy;
+      for( i = 0; i < CRV_NPNT; i++ ) {
+         ffx = x[i]; ffy = y[i];
+         GMark( this, 1, &ffx, &ffy, 2, method, class, status );
+         GFlush( this, method, class, status );
+      }
+   }
+#endif
+
+/* ======================================================================
+   The first section of this function sets up some arrays holding
+   information which will be used later on. It looks at each of the segments
+   joing adjacent tabulated points, and finds and stores the increments in
+   X and Y along each segment, and the square of the segment length. It
+   also checks to see if the tabulated points are all bad, or if they are
+   all good. It also finds the lowest squared segment length.
+   ======================================================================*/
+
+/* Look at the first tabulated point. If it is good, set a flag to indicate
+   that it can be used, store it as "the previous position" (i.e. the start of
+   the current segment). Also set a flag ("all_bad") to indicate if all
+   points looked at so far have been bad, or outside the plotting area. */
+   if( *x != AST__BAD && *y != AST__BAD ){
+      statics->last_ok = 1;
+      statics->last_x = *x;
+      statics->last_y = *y;
+      statics->all_bad = ( *x < Crv_xlo || *x > Crv_xhi ||
+                  *y < Crv_ylo || *y > Crv_yhi ) && Crv_clip;
+   } else {
+      statics->last_ok = 0;
+      statics->all_bad = 1;
+   }
+
+/* Initialise the bouding box for the this segment. */
+   bbox[ 0 ] = DBL_MAX;
+   bbox[ 1 ] = -DBL_MAX;
+   bbox[ 2 ] = DBL_MAX;
+   bbox[ 3 ] = -DBL_MAX;
+
+/* Store pointers to the X and Y values for the "current position". This
+   is the position at the end of the current segment. This is initially
+   the second tabulated point. */
+   px = x + 1;
+   py = y + 1;
+
+/* Store pointers to the increments and squared length for the current
+   segment. */
+   statics->pdx = dx;
+   statics->pdy = dy;
+   statics->pdl2 = dl2;
+
+/* Initialise the number of long and short segments. */
+   statics->nlong = 0;
+   statics->nshort = 0;
+
+/* Loop round each segment. */
+   for( i = 0; i < CRV_NSEG; i++ ){
+
+/* If the tabulated point marking the end of the segment is good... */
+      if( *px != AST__BAD && *py != AST__BAD ){
+
+/* Update the bounding box. */
+         if( *px < bbox[ 0 ] ) bbox[ 0 ] = *px;
+         if( *px > bbox[ 1 ] ) bbox[ 1 ] = *px;
+         if( *py < bbox[ 2 ] ) bbox[ 2 ] = *py;
+         if( *py > bbox[ 3 ] ) bbox[ 3 ] = *py;
+
+/* If the point is within the plotting area, set the "statics->all_bad" flag to
+   indicate that at least 1 point is within the plotting area. */
+         if( !Crv_clip || ( *px >= Crv_xlo && *px <= Crv_xhi &&
+                            *py >= Crv_ylo && *py <= Crv_yhi ) ) statics->all_bad = 0;
+
+/* If the point marking the start of the segment was also good, find and
+   store the increments and squared length for the segment, incrementing
+   the pointers ready for the next segment. */
+         if( statics->last_ok ){
+            statics->t1 = *px - statics->last_x;
+            statics->t2 = *py - statics->last_y;
+            statics->t3 = statics->t1*statics->t1 + statics->t2*statics->t2;
+            *(statics->pdx++) = statics->t1;
+            *(statics->pdy++) = statics->t2;
+            *(statics->pdl2++) = statics->t3;
+
+/* Count the number of segments which are, and are not, shorter than the
+   minimum significant length. */
+            if( statics->t3 > statics->limit2 ) {
+               statics->nlong++;
+            } else {
+               statics->nshort++;
+            }
+
+/* If the start was bad, the length of the segment is not defined so store
+   bad values. */
+         } else {
+            *(statics->pdx++) = AST__BAD;
+            *(statics->pdy++) = AST__BAD;
+            *(statics->pdl2++) = AST__BAD;
+         }
+
+/* The point at the end of the current segment becomes the point at the
+   start of the next segment. */
+         statics->last_ok = 1;
+         statics->last_x = *(px++);
+         statics->last_y = *(py++);
+
+/* If the tabulated point marking the end of the current segment is bad,
+   the segment length is undefined so store bad values. */
+      } else {
+         *(statics->pdx++) = AST__BAD;
+         *(statics->pdy++) = AST__BAD;
+         *(statics->pdl2++) = AST__BAD;
+
+/* The point at the end of the current segment becomes the point at the
+   start of the next segment. */
+         statics->last_ok = 0;
+         px++;
+         py++;
+      }
+   }
+
+/* ======================================================================
+   The next section of this function checks to see lines can be drawn
+   directly through any of the tabulated points. The flags in  "seg_ok"
+   indicates if this is the case for each segment.
+   ======================================================================*/
+
+/* The unit vector along the previous segment is supplied in external
+   variables Crv_ux0 and Crv_uy0. These will be AST__BAD if the direction
+   of the previous segment is undefined. */
+   statics->vxl = Crv_ux0;
+   statics->vyl = Crv_uy0;
+
+/* The length of the previous segment is initially bad. */
+   statics->dll = AST__BAD;
+
+/* Set up some pointers used to walk through the arrays holding the lengths
+   of each segment. */
+   statics->pdl2 = dl2;
+   statics->pdx = dx;
+   statics->pdy = dy;
+
+/* Check each segment in turn to see if it can be drawn as a single
+   straight line. */
+   for( i = 0; i < CRV_NSEG; i++ ){
+
+/* A segment can only be drawn as a single line if both ends are good
+   and the distance between them is not zero. */
+      if( *statics->pdl2 != AST__BAD && *statics->pdl2 > 0.0 ){
+
+/* Get a unit vector in the direction of the current segment. */
+         statics->dl = sqrt( *statics->pdl2 );
+         statics->vx = *statics->pdx/statics->dl;
+         statics->vy = *statics->pdy/statics->dl;
+
+/* If a unit vector in the direction of the previous segment is available,
+   we check that the angle between the previous segment and the current
+   segment is not too high. */
+         if( statics->vxl != AST__BAD ){
+            statics->cosang = statics->vxl*statics->vx + statics->vyl*statics->vy;
+
+/* If the angle is too high, set a flag to indicate that the segment cannot
+   be drawn as a single line. Also, set the flag for the previous segment as
+   well. */
+            if( statics->cosang < 0.8 ||
+                ( *statics->pdl2 )*( 1.0 - statics->cosang*statics->cosang ) > Crv_limit ) {
+               seg_ok[ i ] = 0;
+               if( i > 0 ) seg_ok[ i - 1 ] = 0;
+
+
+/* If the angle between this segment and the previous segment is not too
+   high, check that the scale has not changed too much. */
+            } else {
+
+/* If the scale (=vector length) has changed a lot, set a flag to indicate
+   that the segment cannot be drawn as a single line. Also, set the flag for
+   the previous segment as well. */
+               if( statics->dll != AST__BAD && ( statics->dl < statics->dll/Crv_scerr || statics->dl > statics->dll*Crv_scerr ) ) {
+                  seg_ok[ i ] = 0;
+                  if( i > 0 ) seg_ok[ i - 1 ] = 0;
+
+/* If the orientation and scale of this segment has not changed much from
+   the previous segment, the segment can be drawn as a straight line. */
+               } else {
+                  seg_ok[ i ] = 1;
+               }
+            }
+
+/* If no unit vector is available for the previous segment, then assume
+   we are re-starting the curve after a discontinuity. In this case, we
+   can draw it as a straight line. */
+         } else {
+            seg_ok[ i ] = 1;
+         }
+
+/* Save the unit vector along the current segment for use next time. */
+         statics->vxl = statics->vx;
+         statics->vyl = statics->vy;
+
+/* Save the length if the current segment for use next time. */
+         statics->dll = statics->dl;
+
+/* If the length of the current segment is undefined, or zero, we cannot
+   draw it as a single line. Also, there is no direction vector to pass
+   on to the next time, so set them bad. */
+      } else {
+         seg_ok[ i ] = 0;
+         statics->vxl = AST__BAD;
+         statics->vyl = AST__BAD;
+         statics->dll = AST__BAD;
+      }
+
+/* Point to the next segment. */
+      statics->pdl2++;
+      statics->pdx++;
+      statics->pdy++;
+
+   }
+
+/* Do not allow isolated segments to be OK. If a segment is flagged as being
+   OK, but both its neighbours are not OK, set the segment not OK as well. */
+   statics->seg0 = seg_ok + 1;
+   statics->segm = seg_ok;
+   statics->segp = seg_ok + 2;
+
+   if( !(*statics->seg0) ) *statics->segm = 0;
+
+   for( i = 1; i < CRV_NSEG - 1; i++ ){
+      if( !(*statics->segm) && !(*statics->segp) ) *statics->seg0 = 0;
+      statics->seg0++;
+      statics->segm++;
+      statics->segp++;
+   }
+
+   if( !(*statics->segm) ) *statics->seg0 = 0;
+
+/* ======================================================================
+   The next section of this function draws the curve. Each segment is drawn
+   as a straight line if the corresponding flag in "seg_ok" is set.
+   Segments for which the flag is not set are drawn by calling this function
+   recursivly.
+   ======================================================================*/
+
+/* Get the parametric length (i.e. the increment in "d") of the sub-segments
+   within each subdivided segment. */
+   statics->delta = ( d[ CRV_NSEG ] - d[ 0 ] )/(double)( CRV_NSEG*CRV_NSEG );
+
+/* If we have made the maximum number of recursive entries into this
+   function, or if every supplied point was bad or outside the plotting
+   area, or if most of the segments were very short in graphics space, we will
+   not be attempting to subdivide any segments which cannot be drawn directly
+   as a straight line. If "skipbad" was supplied as zero, we ignore the
+   restriction which says that we must have some good points (since we
+   may find some good poits by a further sub-division). */
+   subdivide = ( Crv_nent < CRV_MXENT &&
+                 ( !statics->all_bad || !skipbad ) &&
+                 statics->nlong > statics->nshort );
+
+/* We do not sub-divide if the bounding box of the supplied points
+   is not at least 10% smaller than the supplied bouding box on either axis. */
+   if( box && bbox[ 0 ] != DBL_MAX ) {
+      if( bbox[ 1 ] - bbox[ 0 ] > 0.9*( box[ 1 ] - box[ 0 ] ) &&
+          bbox[ 3 ] - bbox[ 2 ] > 0.9*( box[ 3 ] - box[ 2 ] ) ) {
+         subdivide = 0;
+      }
+   }
+
+/* Initialise some pointers to the data defineding the subsegments. */
+   dd = NULL;
+   xx = NULL;
+   yy = NULL;
+
+/* If we may be subdividing any segments, find which segments they are
+   and set up the offset to each sub-segment. */
+   if( subdivide ){
+
+/* Initialise the number of segments being subdivided. */
+      statics->nseg = 0;
+
+/* Loop round each segment. */
+      for( i = 0; i < CRV_NSEG; i++ ){
+
+/* If the segment cannot be drawn directly as a straight line, we will
+   subdivide it. */
+         if( !seg_ok[ i ] ){
+
+/* Increment the number of segments being subdivided, and let the array
+   of subsegment offsets grow to accomodate it. */
+            statics->nseg++;
+            dd = (double *) astGrow( dd, statics->nseg, sizeof(double)*( CRV_NSEG + 1 ) );
+            if( !astOK ) break;
+
+/* Append the offset to each new subsegment to the "dd" array. */
+            statics->el = ( statics->nseg - 1 )*( CRV_NSEG + 1 );
+            statics->d0 = d[ i ];
+            for( statics->j = 0; statics->j <= CRV_NSEG; statics->j++ ){
+               dd[ statics->el++ ] = statics->d0;
+               statics->d0 += statics->delta;
+            }
+         }
+      }
+
+/* If any segments needed subdividing, get room to store the graphics
+   coordinates at each point, and then fill these arrays by calling
+   Crv_map to map the offsets in "dd" into graphics coordinates. */
+      if( statics->nseg > 0 ){
+         statics->nel = statics->nseg*( CRV_NSEG + 1 );
+         xx = (double *) astMalloc( sizeof(double)*(size_t)statics->nel );
+         yy = (double *) astMalloc( sizeof(double)*(size_t)statics->nel );
+         Crv_map( statics->nel, dd, xx, yy, method, class, status GLOBALS_NAME );
+      }
+   }
+
+/* If all has gone OK, we will draw each segment. Initialise pointers
+   used to walk through the "xx", "yy" and "dd" arrays. */
+   if( astOK ){
+      px = xx;
+      py = yy;
+      pd = dd;
+
+/* Draw each segment in turn. */
+      for( i = 0; i < CRV_NSEG; i++ ){
+
+/* If possible, draw it as a single straight line, and then store the
+   unit vector along the line in the appropriate external variables for
+   use by the next invocation of this function. */
+         if( seg_ok[ i ] ){
+            CrvLine( this, x[ i ], y[ i ], x[ i + 1 ], y[ i + 1 ], method, class, status );
+            statics->dl = sqrt( dl2[ i ] );
+            Crv_ux0 = dx[ i ]/statics->dl;
+            Crv_uy0 = dy[ i ]/statics->dl;
+
+/* Otherwise, if we are subdividing, and if the current segment is
+   not very short, we call this function recursively to draw the segment.
+   Increment pointers into the "xx", "yy" and "dd" arrays so that they
+   point to the start of the subsegment information for the next segment
+   to be subdivided. If all the graphics positions at this level were
+   bad or outside the plot, tell the next invocation of Crv to do no
+   further sub-divisions if it too finds all graphics positions to be bad or
+   outside the plot. */
+         } else if( subdivide ) {
+
+#ifdef CRV_TRACE
+            statics->levels[ Crv_nent ] = i;
+#endif
+
+            Crv( this, pd, px, py, statics->all_bad, bbox, statics, method,
+                 class, status );
+            pd += CRV_NSEG + 1;
+            px += CRV_NSEG + 1;
+            py += CRV_NSEG + 1;
+
+/* Otherwise, we assume we have hit a discontinuity in the curve. Store
+   bad values for the unit vector along the previous sgment, and do not
+   draw anything. */
+         } else {
+            Crv_ux0 = AST__BAD;
+            Crv_uy0 = AST__BAD;
+         }
+       }
+    }
+
+/* Free any memory used to store subsegment information. */
+   if( dd ) dd = (double *) astFree( (void *) dd );
+   if( xx ) xx = (double *) astFree( (void *) xx );
+   if( yy ) yy = (double *) astFree( (void *) yy );
+
+/* Decrement the number of recursive entries into this function. */
+   Crv_nent--;
+
+/* Free the memory holding the static data values if we are leaving the
+   final entry. */
+   if( ! pstatics ) statics = astFree( statics );
+
+/* Return. */
+   return;
+}
+
+static int CvBrk( AstPlot *this, int ibrk, double *brk, double *vbrk,
+                     double *len, int *status ){
+/*
+*+
+*  Name:
+*     astCvBrk
+
+*  Purpose:
+*     Return information about breaks in the last curve drawn by astGridLine,
+*     astCurve or astGenCurve.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int CvBrk( AstPlot *this, int ibrk, double *brk, double *vbrk,
+*                   double *len )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     Curves drawn by astGridLine, astCurve or astGenCurve may contain breaks
+*     for several reasons (for instance, it may go outside the plotting area,
+*     or the mapping between physical and graphics coordinates may be
+*     discontinuous). This function returns information about such breaks.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot.
+*     ibrk
+*        The index of the break for which information is required. The first
+*        break has index 1. An error is reported if no break with the
+*        required index exists. The exception to this is that zero can be
+*        supplied, in which case the "brk" and "vbrk" parameters
+*        are ignored, but all other information is returned.
+*     brk
+*        A pointer to an array of 2 elements
+*        in which to return the X and Y graphics coordinates of the break.
+*     vbrk
+*        A pointer to an array of 2 elements
+*        in which to return the X and Y components of a unit vector in the
+*        graphics coordinate system. The vector is tangential to the curve
+*        at the requested break, and points back along the drawn section of
+*        the curve.
+*     len
+*        A pointer to a "double" in which to return the
+*        length of the drawn curve, in the graphics coordinate system.
+
+*   Returned Value:
+*     astCvBrk()
+*        The number of breaks which occurred in the curve.
+
+*  Notes:
+*     -  Currently, this function may not be used to return information
+*     about curves drawn using astPolyCurve.
+*     -  All curves contain at least two breaks; one at the start and one
+*     at the end. This is true even if the start and end of the curve are
+*     coincident. However, if the entire curve was outside the plotting area
+*     (i.e. if the length of the drawn curve is zero), then it will have no
+*     breaks.
+*     -  If no curve has yet been drawn by astGridLine or astCurve, then -1 is
+*     returned for the function value, and the function parameter values are
+*     left unchanged.
+*     -  The returned information refers to the most recent curve drawn by
+*     astGridLine or astCurve, even if that curve was drawn by a Plot other than
+*     the one supplied to this function.
+*     -  NULL pointers may be supplied for "brk", "vbrk" or "len", in which
+*     case the corresponding values are not returned.
+*     -  Zero is returned by this function if an error has already occurred,
+*     or if this function should fail for any reason.
+
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS   /* Pointer to thread-specific global data */
+   int ret;             /* The number of breaks in the curve. */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Information about the most recent curve drawn by astGridLine or astCurve is
+   stored in the external structure "Curve_data". Get the number of breaks
+   in the last curve. This is initialised to -1 in astInitPlot when the
+   virtual function table for this class is initialised. */
+   ret = Curve_data.nbrk;
+
+/* If a curve has been drawn, store the length of the drawn curve if
+   required. */
+   if( ret != -1 ){
+      if( len ) *len = (double) Curve_data.length;
+
+/* If a legal break index has been supplied, return the position and
+   direction at the requested break (if required). */
+      if( ibrk > 0 && ibrk <= ret ){
+         if( brk ){
+            brk[ 0 ] =  (double) Curve_data.xbrk[ ibrk - 1 ];
+            brk[ 1 ] =  (double) Curve_data.ybrk[ ibrk - 1 ];
+         }
+         if( vbrk ){
+            vbrk[ 0 ] =  (double) Curve_data.vxbrk[ ibrk - 1 ];
+            vbrk[ 1 ] =  (double) Curve_data.vybrk[ ibrk - 1 ];
+         }
+
+/* If an illegal break index has been supplied (other than zero), report
+   an error, and set the number of breaks to zero. */
+      } else if( ibrk ){
+         if( ret > 0 ){
+            astError( AST__BDBRK, "astCvBrk(%s): The supplied break index "
+                      "(%d) should be in the range [1,%d].", status, astGetClass(this),
+                      ibrk, ret );
+            ret = 0;
+         } else {
+            astError( AST__BDBRK, "astCvBrk(%s): The most recent curve "
+                      "plotted by method astGridLine or astCurve had no breaks.", status,
+                      astGetClass(this) );
+         }
+      }
+    }
+
+/* If an error has occurred, return 0. */
+   if( !astOK ) ret = 0;
+
+/* Return the result. */
+   return ret;
+
+}
+
+static void CrvLine( AstPlot *this, double xa, double ya, double xb, double yb,
+                     const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     CrvLine
+
+*  Purpose:
+*     Draw a straight line between two points, with clipping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void CrvLine( AstPlot *this, double xa, double ya, double xb, double yb,
+*                   const char *method, const char *class )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This functions draws a straight line from (xa,ya) to (xb,yb), breaking
+*     the line at the edges of the plotting area defined by Crv_xlo, Crv_xhi,
+*     Crv_ylo and Crv_yhi if Crv_clip is non-zero. If the line does not start
+*     at the end of the previous line plotted by this function, then
+*     information describing the break in the curve is stored in external
+*     arrays.
+
+*  Parameters:
+*     xa
+*        The graphics X coordinate at the start of the line.
+*     ya
+*        The graphics Y coordinate at the start of the line.
+*     xb
+*        The graphics X coordinate at the end of the line.
+*     yb
+*        The graphics Y coordinate at the end of the line.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+
+*  External Variables:
+*     Crv_ink = int (Read)
+*        If zero then no line is drawn even if the line intersects the
+*        plotting space, but break information (etc) is still returned.
+*     Crv_clip = double (Read)
+*        Clip lines at boundary of plotting space?
+*     Crv_xlo = double (Read)
+*        Lower x limit of the plotting space.
+*     Crv_xhi = double (Read)
+*        Upper x limit of the plotting space.
+*     Crv_ylo = double (Read)
+*        Lower y limit of the plotting space.
+*     Crv_yhi = double (Read)
+*        Upper y limit of the plotting space.
+*     Crv_tol = double (Read)
+*        The tolerance for determining if 2 points are coincident.
+*     Crv_out = int (Read/Write)
+*        Returned as zero if the line intersects the plotting space.
+*        Unchanged otherwise.
+*     Crv_xbrk = float * (Read/Write)
+*        Pointer to the next available element in an array of AST__PLOT_CRV_MXBRK
+*        values containing the graphics X coordinates at which each break
+*        in the plotted curve occurred. A break is recorded if the starting
+*        point of the current line is not the same as the end point of
+*        the previous line.
+*     Crv_ybrk = float * (Read/Write)
+*        Pointer to the next available element in an array of AST__PLOT_CRV_MXBRK
+*        values containing the graphics Y coordinates at which each break
+*        in the plotted curve occurred.
+*     Crv_vxbrk = float * (Read/Write)
+*        Pointer to the next available element in an array of AST__PLOT_CRV_MXBRK
+*        values containing the X component of the unit vector (within the
+*        graphics coordinate system) parallel to the tangent to the curve
+*        at each break. The sense is such that the vector always points back
+*        along the plotted section of the curve.
+*     Crv_vybrk = float * (Read/Write)
+*        Pointer to the next available element in an array of AST__PLOT_CRV_MXBRK
+*        values containing the Y component of the unit vector parallel to
+*        the tangent to the curve at each break.
+*     Crv_nbrk = int (Write)
+*        The number of breaks for which information is returned in Crv_xbrk,
+*        etc.
+*     Crv_len = float (Write)
+*        The length of the section of the curve which has been drawn so far.
+*     Crv_xl = double (Write)
+*        The graphics X coordinate at the end of the last line drawn.
+*     Crv_yl = double (Write)
+*        The graphics Y coordinate at the end of the last line drawn.
+*     Crv_vxl = double (Write)
+*        The X component of the unit vector along the last line drawn.
+*     Crv_vyl = double (Write)
+*        The Y component of the unit vector along the last line drawn.
+*     Grf_alpha = float (Read)
+*        The factor for scaling graphics X axis values into equal scaled
+*        X axis values.
+*     Grf_beta = float (Read)
+*        The factor for scaling graphics Y axis values into equal scaled
+*        Y axis values.
+
+*/
+
+/* local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   double a1;    /* Distance from B to the lower x boundary */
+   double a2;    /* Distance from B to the upper x boundary */
+   double a3;    /* Distance from B to the lower y boundary */
+   double a4;    /* Distance from B to the upper y boundary */
+   double aamax; /* Distance from supplied point B to the plotable point A */
+   double aamin; /* Distance from supplied point B to the plotable point B */
+   double dl;    /* Length of plotted line segment */
+   double dx;    /* Difference in x between supplied points */
+   double dy;    /* Difference in y between supplied points */
+   double t;     /* Temporary storage */
+   double xam;   /* Modified xa position */
+   double xbm;   /* Modified xb position */
+   double yam;   /* Modified ya position */
+   double ybm;   /* Modified yb position */
+   int plot;     /* True if a line can be plotted */
+
+/* Check inherited global status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   dl = 0.0;
+   xam = 0.0;
+   xbm = 0.0;
+   yam = 0.0;
+   ybm = 0.0;
+
+/* Store the shifts in x and y. */
+   dx = xb - xa;
+   dy = yb - ya;
+
+/* Do nothing if the line is of zero length. */
+   if( dx == 0.0 && dy == 0.0 ) return;
+
+/* If either end is outside the zone, replace the given coordinates with
+   the end coordinates of the section of the line which lies within the
+   zone. */
+   if( Crv_clip && ( xa < Crv_xlo || xa > Crv_xhi ||
+                     xb < Crv_xlo || xb > Crv_xhi ||
+                     ya < Crv_ylo || ya > Crv_yhi ||
+                     yb < Crv_ylo || yb > Crv_yhi ) ){
+
+/* Find the distance from point B towards point A at which the
+   line cuts the two x bounds of the zone (distance at point B is
+   0.0, and at point A is 1.0). */
+      if( dx != 0.0 ){
+         a1 = ( xb - Crv_xlo )/dx;
+         a2 = ( xb - Crv_xhi )/dx;
+
+/* Ensure that a1 represents the highest plottable offset, and a2 the
+   lowest. */
+         if( a1 < a2 ){
+            t = a1;
+            a1 = a2;
+            a2 = t;
+         }
+
+/* If the line joining A and B is vertical... */
+      } else {
+
+/* If the line is within the plottable x range, indicate that all
+   offsets are plottable (as far as the x range is concerned at least). */
+         if( ( xa > Crv_xlo || astEQUALS( xa, Crv_xlo, 1.0E8 ) ) &&
+             ( xa < Crv_xhi || astEQUALS( xa, Crv_xhi, 1.0E8 ) ) ){
+            a1 = DBL_MAX;
+            a2 = -DBL_MAX;
+
+/* If the line is ouside the plottable x range, indicate that no
+   offsets are plottable. */
+         } else {
+            a1 = 0.0;
+            a2 = 0.0;
+         }
+      }
+
+/* Find the fractional distance from point A to point B at which the
+   line cuts the two y bounds of the zone. */
+      if( dy != 0.0 ){
+         a3 = ( yb - Crv_ylo )/dy;
+         a4 = ( yb - Crv_yhi )/dy;
+
+/* Ensure that a3 represents the highest plottable offset, and a4 the
+   lowest. */
+         if( a3 < a4 ){
+            t = a3;
+            a3 = a4;
+            a4 = t;
+         }
+
+/* If the line joining A and B is horizontal... */
+      } else {
+
+/* If the line is within the plottable y range, indicate that all
+   offsets are plottable (as far as the y range is concerned at least). */
+         if( ( ya > Crv_ylo || astEQUALS( ya, Crv_ylo, 1.0E8 ) ) &&
+             ( ya < Crv_yhi || astEQUALS( ya, Crv_yhi, 1.0E8 ) ) ){
+            a3 = DBL_MAX;
+            a4 = -DBL_MAX;
+
+/* If the line is ouside the plottable y range, indicate that no
+   offsets are plottable. */
+         } else {
+            a3 = 0.0;
+            a4 = 0.0;
+         }
+      }
+
+/* Find the fractional distances from point A to point B at the ends
+   of the plotable line. */
+      aamin = astMIN( 1.0, astMAX( 0.0, astMAX( a2, a4 ) ) );
+      aamax = astMAX( 0.0, astMIN( 1.0, astMIN( a1, a3 ) ) );
+
+/* Store the end coordinates of the line joining the plotable points. */
+      if( aamax > aamin ){
+         xam = xb - aamax*dx;
+         yam = yb - aamax*dy;
+         xbm = xb - aamin*dx;
+         ybm = yb - aamin*dy;
+         plot = 1;
+
+/* Get the unit vector along the line and the length of the plotted section. */
+         dx *= Grf_alpha;
+         dy *= Grf_beta;
+         dl = sqrt( dx*dx + dy*dy );
+         dx /= dl;
+         dy /= dl;
+         dl *= astMAX( 0.0, aamax - aamin );
+
+/* Clear the "plot" flag if the line does not intersect the plotting area. */
+      } else {
+         plot = 0;
+      }
+
+/* If both ends of the line are within the plotting zone, draw the whole
+   line between the supplied end points. */
+   } else {
+      xam = xa;
+      yam = ya;
+      xbm = xb;
+      ybm = yb;
+      plot = 1;
+
+/* Get the length of the line and the unit vector along the line. */
+      dx *= Grf_alpha;
+      dy *= Grf_beta;
+      dl = sqrt( dx*dx + dy*dy );
+      dx /= dl;
+      dy /= dl;
+    }
+
+/* If a line is to be plotted... */
+   if( plot ){
+
+/* If this is the first line to be plotted in the current curve, save
+   the start of the line as a break, and indicate that some of the curve
+   falls within the plotting zone. */
+      if( Crv_out ){
+         Crv_nbrk = 1;
+         *(Crv_xbrk++) = (float) xam;
+         *(Crv_ybrk++) = (float) yam;
+         *(Crv_vxbrk++) = (float) dx;
+         *(Crv_vybrk++) = (float) dy;
+         Crv_out = 0;
+
+/* Set the length of the curve plotted so far to the length of this first
+   segment. */
+         Crv_len = (float) dl;
+
+/* Start a poly line. */
+         if( Crv_ink ) Bpoly( this, (float) xam,  (float) yam, status );
+
+/* If this is not the first line to be plotted... */
+      } else {
+
+/* ... increment the length of the curve plotted so far. */
+         Crv_len += (float) dl;
+
+/* If the start of this line is not coincident with the end
+   of the previous line, save the previous and current positions as
+   breaks in the curve. Note, the previous vector is reversed so that
+   it points back towards the drawn section of the curve. Report an
+   error if the arrays are full. */
+         if( fabs( xam - Crv_xl ) > Crv_tol ||
+             fabs( yam - Crv_yl ) > Crv_tol ){
+            Crv_nbrk += 2;
+             if( Crv_nbrk > AST__PLOT_CRV_MXBRK ){
+               astError( AST__CVBRK, "%s(%s): Number of breaks in plotted "
+                         "curve exceeds %d.", status, method, class, AST__PLOT_CRV_MXBRK );
+            } else {
+               *(Crv_xbrk++) = (float) Crv_xl;
+               *(Crv_ybrk++) = (float) Crv_yl;
+               *(Crv_vxbrk++) = (float) -Crv_vxl;
+               *(Crv_vybrk++) = (float) -Crv_vyl;
+                *(Crv_xbrk++) = (float) xam;
+               *(Crv_ybrk++) = (float) yam;
+               *(Crv_vxbrk++) = (float) dx;
+               *(Crv_vybrk++) = (float) dy;
+            }
+
+/* Start a poly line. */
+            if( Crv_ink ) Bpoly( this, (float) xam,  (float) yam, status );
+          }
+      }
+
+/* Append a section to the current poly line. */
+      if( Crv_ink ) Apoly( this, (float) xbm,  (float) ybm, status );
+
+/* Save the position and vector at the end of the current line. */
+      Crv_xl = xbm;
+      Crv_yl = ybm;
+      Crv_vxl = dx;
+      Crv_vyl = dy;
+    }
+
+/* Return. */
+   return;
+}
+
+
+static void Curve( AstPlot *this, const double start[],
+                                  const double finish[], int *status ){
+/*
+*++
+*  Name:
+c     astCurve
+f     AST_CURVE
+
+*  Purpose:
+*     Draw a geodesic curve.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astCurve( AstPlot *this, const double start[],
+c                                   const double finish[] )
+f     CALL AST_CURVE( THIS, START, FINISH, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+c     This function draws a geodesic curve between two points in the
+f     This routine draws a geodesic curve between two points in the
+*     physical coordinate system of a Plot.  The curve drawn is the
+*     path of shortest distance joining the two points (as defined by
+c     the astDistance function for the current Frame of the Plot).
+f     the AST_DISTANCE function for the current Frame of the Plot).
+*     For example, if the current Frame is a basic Frame, then the
+*     curve joining the two points will be a straight line in physical
+*     coordinate space.  If the current Frame is more specialised and
+*     describes, for instance, a sky coordinate system, then the
+*     geodesic curve would be a great circle in physical coordinate
+*     space passing through the two sky positions given.
+*
+*     Note that the geodesic curve is transformed into graphical
+*     coordinate space for plotting, so that a straight line in
+*     physical coordinates may result in a curved line being drawn if
+*     the Mapping involved is non-linear. Any discontinuities in the
+*     Mapping between physical and graphical coordinates are
+c     catered for, as is any clipping established using astClip.
+f     catered for, as is any clipping established using AST_CLIP.
+*
+c     If you need to draw many geodesic curves end-to-end, then the
+c     astPolyCurve function is equivalent to repeatedly using
+c     astCurve, but will usually be more efficient.
+f     If you need to draw many geodesic curves end-to-end, then the
+f     AST_POLYCURVE routine is equivalent to repeatedly calling
+f     AST_CURVE, but will usually be more efficient.
+*
+c     If you need to draw curves which are not geodesics, see astGenCurve
+c     or astGridLine.
+f     If you need to draw curves which are not geodesics, see AST_GENCURVE
+f     or AST_GRIDLINE.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+c     start
+f     START( * ) = DOUBLE PRECISION (Given)
+*        An array, with one element for each axis of the Plot, giving
+*        the physical coordinates of the first point on the geodesic
+*        curve.
+c     finish
+f     FINISH( * ) = DOUBLE PRECISION (Given)
+*        An array, with one element for each axis of the Plot, giving
+*        the physical coordinates of the second point on the geodesic
+*        curve.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+c     - No curve is drawn if either of the "start" or "finish" arrays
+c     contains any coordinates with the value AST__BAD.
+f     - No curve is drawn if either of the START or FINISH arrays
+f     contains any coordinates with the value AST__BAD.
+*     - An error results if the base Frame of the Plot is not 2-dimensional.
+*     - An error also results if the transformation between the
+*     current and base Frames of the Plot is not defined (i.e. the
+*     Plot's TranInverse attribute is zero).
+*--
+*/
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   int naxes;              /* No. of axes in the base Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Store the current method, and the class of the supplied object for use
+   in error messages.*/
+   method = "astCurve";
+   class = astGetClass( this );
+
+/* Check the base Frame of the Plot is 2-D. */
+   naxes = astGetNin( this );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 2.", status, method, class, naxes, class );
+   }
+
+/* Initialise the bounding box for primitives produced by this call. */
+   if( !Boxp_freeze ) {
+      Boxp_lbnd[ 0 ] = FLT_MAX;
+      Boxp_lbnd[ 1 ] = FLT_MAX;
+      Boxp_ubnd[ 0 ] = FLT_MIN;
+      Boxp_ubnd[ 1 ] = FLT_MIN;
+   }
+
+/* Indicate that the GRF module should re-calculate it's cached values
+   (in case the state of the graphics system has changed since the last
+   thing was drawn). */
+   RESET_GRF;
+
+/* Draw the curve. The break information is stored in an external structure
+   where it can be accessed by public methods which return information
+   about the most recently drawn curve. */
+   CurvePlot( this, start, finish, 1, &Curve_data, method, class, status );
+
+/* Ensure all lines are flushed to the graphics system. */
+   Fpoly( this, method, class, status );
+
+/* Return. */
+   return;
+}
+
+static void CurvePlot( AstPlot *this, const double *start, const double *finish,
+                     int ink, AstPlotCurveData *cdata, const char *method,
+                     const char *class, int *status ){
+/*
+*
+*  Name:
+*     CurvePlot
+
+*  Purpose:
+*     Draw a geodesic curve.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void CurvePlot( AstPlot *this, const double *start, const double *finish,
+*                   int ink, AstPlotCurveData *cdata, const char *method,
+*                   const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function draws a geodesic curve between the supplied starting and
+*     finishing positions. The algorithm used can handle discontinuities in the
+*     Mapping between the current Frame and graphics coordinates, and
+*     information describing any breaks in the curve (including the start and
+*     end of the curve) are returned in the supplied AstPlotCurveData structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     start
+*        A pointer to a an array holding the coordinates of the start of the
+*        curve within the current Frame of the Plot.
+*     finish
+*        A pointer to a an array holding the coordinates of the finish of the
+*        curve within the current Frame of the Plot.
+*     ink
+*        If zero, the curve is not actually drawn, but information about
+*        the breaks is still returned. If non-zero, the curve is also drawn.
+*     cdata
+*        A pointer to a structure in which to return information about the
+*        breaks in the curve.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  No curve is draw if the "start" or "finish" arrays contains any bad
+*     values, or if a NULL pointer is supplied for "cdata". No errors are
+*     reported in these cases.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   double d[ CRV_NPNT ];   /* Offsets to evenly spaced points along curve */
+   double x[ CRV_NPNT ];   /* X coords at evenly spaced points along curve */
+   double y[ CRV_NPNT ];   /* Y coords at evenly spaced points along curve */
+   double tol;             /* Absolute tolerance value */
+   int i;                  /* Loop count */
+   int naxes;              /* No. of axes in the base Frame */
+   int ok;                 /* Are all start coords good? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Get the number of axes in the current Frame. */
+   naxes = astGetNout( this );
+
+/* Check the "start" and "finish" parameter for bad values. */
+   ok = 1;
+   for( i = 0; i < naxes; i++ ) {
+      if( start[ i ] == AST__BAD || finish[ i ] == AST__BAD ){
+         ok = 0;
+         break;
+      }
+   }
+
+/* Check that the "cdata" pointer can be used. */
+   if( !cdata ) ok = 0;
+
+/* Only proceed if the parameters are OK, and there has been no error. */
+   if( ok && astOK ){
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+      astGrfAttrs( this, AST__CURVE_ID, 1, GRF__LINE, method, class );
+
+/* Ensure the globals holding the scaling from graphics coords to equally
+   scaled coords are available. */
+      GScales( this, NULL, NULL, method, class, status );
+
+/* Set up the externals used to communicate with the Map3 function...
+   The number of axes in the physical coordinate system (i.e. the current
+   Frame). */
+      Map3_ncoord = naxes;
+
+/* A pointer to the Plot, the Curretn Frame, and and Mapping. */
+      Map3_plot = this;
+      Map3_frame = astGetFrame( this, AST__CURRENT );
+      Map3_map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* The physical coordinates at the start of the curve. */
+      Map3_origin = start;
+
+/* The physical coordinates at the end of the curve. */
+      Map3_end = finish;
+
+/* The scale factor to convert "dist" values into physical offset values. */
+      Map3_scale = astDistance( Map3_frame, start, finish );
+
+/* Convert the tolerance from relative to absolute graphics coordinates. */
+      tol = astGetTol( this )*astMAX( this->xhi - this->xlo,
+                                      this->yhi - this->ylo );
+
+/* Now set up the external variables used by the Crv and CrvLine function. */
+      Crv_scerr = ( astGetLogPlot( this, 0 ) ||
+                    astGetLogPlot( this, 1 ) ) ? 100.0 : 1.5;
+      Crv_ux0 = AST__BAD;
+      Crv_tol = tol;
+      Crv_limit = 0.5*tol*tol;
+      Crv_map = Map3;
+      Crv_ink = ink;
+      Crv_xlo = this->xlo;
+      Crv_xhi = this->xhi;
+      Crv_ylo = this->ylo;
+      Crv_yhi = this->yhi;
+      Crv_out = 1;
+      Crv_xbrk = cdata->xbrk;
+      Crv_ybrk = cdata->ybrk;
+      Crv_vxbrk = cdata->vxbrk;
+      Crv_vybrk = cdata->vybrk;
+      Crv_clip = astGetClip( this ) & 1;
+
+/* Set up a list of points spread evenly over the curve. */
+      for( i = 0; i < CRV_NPNT; i++ ){
+        d[ i ] = ( (double) i)/( (double) CRV_NSEG );
+      }
+
+/* Map these points into graphics coordinates. */
+      Map3( CRV_NPNT, d, x, y, method, class, status GLOBALS_NAME );
+
+/* Use Crv and Map3 to draw the curve. */
+      Crv( this, d, x, y, 0, NULL, NULL, method, class, status );
+
+/* End the current poly line. */
+      Opoly( this, status );
+
+/* Tidy up the static data used by Map3. */
+      Map3( 0, NULL, NULL, NULL, method, class, status GLOBALS_NAME );
+
+/* If no part of the curve could be drawn, set the number of breaks and the
+   length of the drawn curve to zero. */
+      if( Crv_out ) {
+         Crv_nbrk = 0;
+         Crv_len = 0.0F;
+
+/* Otherwise, add an extra break to the returned structure at the position of
+   the last point to be plotted. */
+      } else {
+         Crv_nbrk++;
+         if( Crv_nbrk > AST__PLOT_CRV_MXBRK ){
+            astError( AST__CVBRK, "%s(%s): Number of breaks in curve "
+                      "exceeds %d.", status, method, class, AST__PLOT_CRV_MXBRK );
+         } else {
+            *(Crv_xbrk++) = (float) Crv_xl;
+            *(Crv_ybrk++) = (float) Crv_yl;
+            *(Crv_vxbrk++) = (float) -Crv_vxl;
+            *(Crv_vybrk++) = (float) -Crv_vyl;
+         }
+      }
+
+/* Store extra information about the curve in the returned structure, and
+   purge any zero length sections. */
+      if( cdata ){
+         cdata->length = Crv_len;
+         cdata->out = Crv_out;
+         cdata->nbrk = Crv_nbrk;
+         PurgeCdata( cdata, status );
+      }
+
+/* Annul the Frame and Mapping. */
+      Map3_frame = astAnnul( Map3_frame );
+      Map3_map = astAnnul( Map3_map );
+
+/* Re-establish the original graphical attributes. */
+      astGrfAttrs( this, AST__CURVE_ID, 0, GRF__LINE, method, class );
+
+   }
+
+/* Return. */
+   return;
+
+}
+
+
+static AstPointSet *DefGap( AstPlot *this, double *gaps, int *ngood,
+                            double *frac, int *inval, const char *method,
+                            const char *class, int *status ){
+/*
+*  Name:
+*     DefGap
+
+*  Purpose:
+*     Find default gap sizes for the tick marks on the axes of a 2-D
+*     physical coordinate system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     AstPointSet *DefGap( AstPlot *this, double *gaps, int *ngood,
+*                          double *frac, int *inval, const char *method,
+*                          const char *class, int *status  )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function returns default gap sizes for each axis in a 2-D Frame.
+*     The values are found by first obtaining a grid of points spread over
+*     the region containing good physical coordinates. The physical
+*     coordinate values (non-normalized) for each axis are sorted into
+*     increasing order.
+*
+*     For linearly spaced tick marks, a set of quantile axis values is then
+*     found, and the median of the gaps between these quantiles is returned
+*     as the default gap for the axis.
+*
+*     For logarithmically spaced tick marks, the returned gap size is the
+*     ratio between adjacent tick mark values, chosen to give an optimal
+*     number of ticks between the maximum and minimum axis values found in
+*     the grid.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot.
+*     gaps
+*        Pointer to an array in which to return the default gap value for
+*        each axis.
+*     ngood
+*        Pointer to an array in which toi return the number of good
+*        values in the returned PointSet for each axis.
+*     frac
+*        Pointer to a double in which to return the fraction of the
+*        plotting area containing good physical coordinates.
+*     inval
+*        Pointer to a location at which to return a flag indicating if
+*        any bad physical coordinates were encountered.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a PointSet holding the physical coordinate values at a
+*     set of points spread across the plotting area. The values on each
+*     axis are sorted into increasing order. The values will not have
+*     been normalized.
+
+*  Notes:
+*     -  The returned PointSet should be annulled when no longer needed.
+*     -  This function assumes that the physical coordinate system is 2
+*     dimensional, and it should not be used if this is not the case.
+*     -  Gap sizes of 1.0, zero good points, and a NULL pointer are returned
+*     if an error has already occurred, or if this function should fail for
+*     any reason.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;        /* Pointer to PointSet holding graphics coords */
+   AstPointSet *pset2;        /* Pointer to PointSet holding physical coords */
+   double **ptr1;             /* Pointer to graphics axis values */
+   double **ptr2;             /* Pointer to physical axis values */
+   double dran;               /* Dynamic range */
+   double maxv;               /* Maximum axis value */
+   double minv;               /* Minimum axis value */
+   double qgap[ MAJTICKS_OPT ];/* Gaps between physical coordinate quantiles */
+   int dim;                   /* Dimension of grid */
+   int dk;                    /* The number of points between quantiles */
+   int i;                     /* The quantile index */
+   int j;                     /* Axis index */
+   int k;                     /* Index into the sorted array of axis values */
+   int logticks;              /* Logarithmically spaced tick marks? */
+   int n;                     /* Target number fo ticks */
+   int psize;                 /* Total number of axis value */
+
+/* Initialise the returned values. */
+   gaps[ 0 ] = 1.0;
+   gaps[ 1 ] = 1.0;
+   ngood[ 0 ] = 0;
+   ngood[ 1 ] = 0;
+   *frac = 0.0;
+   *inval = 0;
+
+/* Check global status. */
+   if( !astOK ) return NULL;
+
+/* Get two PointSets, one holding a grid of 2D graphics coordinates,
+   and one holding the corresponding (non-normalized) physical
+   coordinates. */
+   dim = 0;
+   *frac = GoodGrid( this, &dim, &pset1, &pset2, method, class, status );
+
+/* Get pointers to the data values in each PointSet. */
+   ptr1 = astGetPoints( pset1 );
+   ptr2 = astGetPoints( pset2 );
+
+/* Store the number of elements in each PointSet. */
+   psize = astGetNpoint( pset1 );
+
+/* For each axis... */
+   for( j = 0; j < 2 && astOK; j++ ){
+
+/* Sort the axis values into increasing order. Any bad values are stored at
+   the end of the array on return. */
+      qsort( (void *) ptr2[ j ], (size_t) psize, sizeof(double), Compared );
+
+/* Count the number of non-bad values returned. */
+      ngood[ j ] = CountGood( psize, ptr2[ j ], status );
+
+/* Set the returned flag to indicate if any bad values were found. */
+      if( ngood[ j ] < psize ) *inval = 1;
+
+/* Report an error if there are too few good points. */
+      if( ngood[ j ] < MAJTICKS_OPT ){
+         astError( AST__VSMAL, "%s(%s): The range of coordinate values "
+                   "covered by axis %d is too small to plot.", status, method,
+                   class, j + 1 );
+         break;
+      }
+
+/* Get the maximum and minimum axis value */
+      minv = ptr2[ j ][ 0 ];
+      maxv = ptr2[ j ][ ngood[ j ] - 1 ];
+
+/* See if ticks on this axis are spaced linearly or logarithmically. If a
+   value has been set for LogTicks used it, otherwise find a default value.
+   The default is 0 unless LogPlot is non-zero, the axis range does not
+   encompass zero and and the dynamic range is 90 or more. Set this
+   default value explicitly so that later functions will pick it up (it will
+   be cleared at the end of the astGrid function). */
+      if( astTestLogTicks( this, j ) ) {
+         logticks = astGetLogTicks( this, j );
+      } else {
+         logticks = 0;
+         if( astGetLogPlot( this, j ) && minv*maxv > 0.0 ) {
+            dran = maxv/minv;
+            if( dran >= 90.0 || dran <= 1.0/90.0 ) logticks = 1;
+         }
+         astSetLogTicks( this, j, logticks );
+      }
+
+/* If no value has been supplied for LogLabel use the value of LogTicks
+   as the default. */
+      if( !astTestLogLabel( this, j ) ) astSetLogLabel( this, j, logticks );
+
+/* For linear gaps, find the gaps between adjacent evenly spaced quantiles.
+   The number of quantiles used equals the optimal number of major tick
+   marks. */
+      if( !logticks ) {
+         dk = (int)( (double)ngood[ j ]/MAJTICKS_OPT );
+         i = 0;
+         for( k = dk; k < ngood[ j ] && i < MAJTICKS_OPT; k += dk ){
+            qgap[ i++ ] = ptr2[ j ][ k ] - ptr2[ j ][ k - dk ];
+         }
+
+/* Find the median of the gaps between adjacent quantiles. */
+         qsort( (void *) qgap, (size_t) i, sizeof(double), Compared );
+         gaps[ j ] = qgap[ i/2 ];
+
+/* If the test gap size is zero, use a fraction of the total range. Report
+   an error if the total range is zero. */
+         if( gaps[ j ] <= 0.0 ){
+            gaps[ j ] = ( ptr2[ j ][ ngood[ j ] - 1 ] - ptr2[ j ][ 0 ] )/MAJTICKS_OPT;;
+            if( gaps[ j ] <= 0.0 ){
+               astError( AST__VSMAL, "%s(%s): The range of coordinate values "
+                         "covered by axis %d is too small to plot.", status, method,
+                         class, j + 1 );
+            }
+         }
+
+/* For logarithmic gaps, use the Nth root of the ratio of the maximum and
+   minimum data value found. */
+      } else if( astOK ) {
+
+/* Report an error if the max and min values are of opposite signs or
+   zero or equal. */
+         if( maxv*minv <= 0.0 ) {
+            astError( AST__ZERAX, "%s(%s): The range of coordinate values "
+                         "covered by axis %d includes the origin and so "
+                         "logarithmic ticks cannot be produced.", status, method,
+                         class, j + 1 );
+
+         } else if( maxv == minv ) {
+            astError( AST__VSMAL, "%s(%s): The range of coordinate values "
+                      "covered by axis %d is too small to plot.", status, method,
+                      class, j + 1 );
+
+/* Otherwise find the gap to use. */
+         } else {
+
+/* Store the maximum and minimum number of major tick marks along each
+   axis. These numbers are reduced if only a small part of the plotting
+   area contains valid coordinates, so that the tick marks do not end up
+   to close together. */
+            n = (int) ( 0.5 + MAJTICKS_OPT*sqrt( *frac ) );
+            if( n < 5 ) n = 5;
+
+/* Choose a gap size which makes this many gaps. */
+            gaps[ j ] = pow( maxv/minv, 1.0/( n - 1.0 ) );
+         }
+      }
+   }
+
+/* Annul the PointSet holding Graphics coordinates. */
+   pset1 = astAnnul( pset1 );
+
+/* If an error has occurred, annul the PointSet holding physical
+   coordinates, and return gaps of 1.0. */
+   if( !astOK ) {
+      pset2 = astAnnul( pset2 );
+      gaps[ 0 ] = 1.0;
+      gaps[ 1 ] = 1.0;
+      ngood[ 0 ] = 0;
+      ngood[ 1 ] = 0;
+      *frac = 0.0;
+      *inval = 0;
+   }
+
+/* Return the physical PointSet. */
+   return pset2;
+
+}
+
+static void DrawAxis( AstPlot *this, TickInfo **grid, double *labelat,
+                      double *gap, const char *method, const char *class, int *status ){
+/*
+*
+*  Name:
+*     DrawAxis
+
+*  Purpose:
+*     Draw a curve joining the major tick marks.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void DrawAxis( AstPlot *this, TickInfo **grid, double *labelat,
+*                    double *gap, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function draws a curve through interior tick marks on both axes.
+*     The curve is drawn even if it has already been drawn as part of a
+*     grid of curves, because it may have been assigned different graphics
+*     attributes to the grid curves.
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     grid
+*        A pointer to an array of two TickInfo pointers (one for each axis),
+*        each pointing to a TickInfo structure holding information about
+*        tick marks on the axis. See function GridLines.
+*     labelat
+*        A pointer to a 2 element array giving the constant axis values at
+*        which tick marks are put. Element 0 should give the axis 1 value at
+*        which tick marks for axis 0 are placed. Element 1 should give the
+*        axis 0 value at which tick marks for axis 1 are placed.
+*     gap
+*        Pointer to array of two values holding the gap between major
+*        tick marks on the two axes.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This function assumes the current Frame of the Plot is 2
+*     dimensional, and it should not be called if this is not the case.
+
+*/
+
+/* Local Variables: */
+   AstFrame *frm;         /* Pointer to current Frame */
+   AstPlotCurveData cdata;/* Somewhere to put the unneeded curve information */
+   TickInfo *info;        /* Pointer to the TickInfo for the current axis */
+   double *value;         /* Current tick value */
+   double bot;            /* Lowest axis value to be displayed */
+   double diff;           /* Difference between adjacent tick marks */
+   double udiff;          /* Used section length */
+   double start[ 2 ];     /* The start of the curve in physical coordinates */
+   double tmp;            /* Temporary storage */
+   double top;            /* Highest axis value to be displayed */
+   int axis;              /* Current axis index */
+   int axisid;            /* ID value for current axis plotting attributes */
+   int logticks;          /* Are major ticks spaced logarithmically? */
+   int tick;              /* Current tick index */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Not the id value for the first axis. */
+   axisid = AST__AXIS1_ID;
+
+/* Get a pointer to the current Frame. */
+   frm = astGetFrame( this, AST__CURRENT );
+
+/* Consider drawing a curve parallel to each axis in turn. */
+   for( axis = 0; axis < 2; axis++ ){
+
+/* Establish the correct graphical attributes for this axis as defined by
+   attributes with the supplied Plot. */
+      astGrfAttrs( this, axisid, 1, GRF__LINE, method, class );
+
+/* Check the axis is required. */
+      if( astGetDrawAxes( this, axis ) ){
+
+/* If the tick marks have been placed round the edges of the plotting
+   area, we do not need to draw the curves. */
+         if( labelat[ axis ] != AST__BAD ){
+
+/* Get the max and min values allowed on this axis. */
+            bot = astGetBottom( frm, axis );
+            top = astGetTop( frm, axis );
+            if( bot > top ) {
+               tmp = top;
+               top = bot;
+               bot = tmp;
+            }
+
+/* Get a pointer to the structure containing information describing the
+   positions of the major tick marks along the current axis. */
+            info = grid[ axis ];
+
+/* Get a pointer to the axis value at the first major tick mark. */
+            value = info->ticks;
+
+/* See if the major tick marks are logarithmically spaced on this axis. */
+            logticks = astGetLogTicks( this, axis );
+
+/* Initialise the difference between major tick marks. */
+            diff = logticks ? 0.0 : gap[ axis ];
+
+/* Loop round all ticks. */
+            for( tick = 0; tick < info->nmajor; tick++, value++ ){
+
+/* Update the difference between major tick marks if we are producing
+   logarithmically spaced ticks (in which "gap" is a ratio, not a
+   difference). */
+               if( logticks ) diff = (*value)*( gap[ axis ] - 1.0 );
+
+/* Note the starting point for this section. */
+               start[ axis ] = *value;
+               start[ 1 - axis ] = labelat[ axis ];
+
+/* If this is the first tick, draw an axis section going "backwards" in
+   case the first tick isn't at the lower visible bound. Limit the length
+   of this backwards section so that it does not extend beyond the minimum
+   axis value. */
+               if( tick == 0 ) {
+                  udiff = *value - bot;
+                  if( udiff > diff ) udiff = diff;
+                  if( udiff > 0.0 ) {
+                     AxPlot( this, axis, start, -udiff, 1, &cdata, method,
+                             class, status );
+                  }
+               }
+
+/* Limit the length of the section so that it does not extend beyond the
+   maximum axis value. */
+               udiff = ( *value + diff > top ) ? top - *value : diff;
+
+/* Do not draw zero length sections. */
+               if( udiff > 0.0 ) {
+
+/* Draw a curve parallel to the current axis, starting at the tick mark,
+   with length equal to the gap between tick marks. Do not draw sections
+   of the curve which are outside the primary domains of the physical axes. */
+                  AxPlot( this, axis, start, udiff, 1, &cdata, method,
+                          class, status );
+               }
+
+            }
+
+/* Once the last section has been drawn, draw another axis section in case the
+   last tick isn't at the upper visible bound. Limit the length of this
+   section so that it does not extend beyond the maximum axis value. */
+            udiff = top - start[ axis ];
+            if( udiff > diff ) udiff = diff;
+            if( udiff > 0.0 ) {
+               AxPlot( this, axis, start, udiff, 1, &cdata, method,
+                       class, status );
+            }
+         }
+      }
+
+/* Re-establish the original graphical attributes. */
+      astGrfAttrs( this, axisid, 0, GRF__LINE, method, class );
+
+/* Set up the id value for the next axis. */
+      axisid = AST__AXIS2_ID;
+
+   }
+
+/* Free the pointer to the current Frame. */
+   frm = astAnnul( frm );
+
+}
+
+
+static AstPlotCurveData **DrawGrid( AstPlot *this, TickInfo **grid, int drawgrid,
+                             const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     DrawGrid
+
+*  Purpose:
+*     Draw a grid of lines at the major tick mark positions on both axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     AstPlotCurveData **DrawGrid( AstPlot *this, TickInfo **grid, int drawgrid,
+*                           const char *method, const char *class )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function draw a grid of curves at the major tick mark
+*     positions on both axes, and returns information describing the
+*     breaks in the curves. If short tick marks are required rather
+*     than long curves (as specified by the Grid attribute of the supplied
+*     Plot), then the curves are not drawn but the break information is
+*     still returned.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot.
+*     grid
+*        A pointer to an array of two pointers (one for each axis), each
+*        pointing to a TickInfo structure. These describe the positions
+*        of the tick marks and should have been produced by function
+*        GridLines.
+*     drawgrid
+*        If non-zero, draw a grid of curves marking the major axis
+*        values. Otherwise, tick marks will be drawn at these values later.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+
+*  Returned Value:
+*     A pointer to an array of two AstPlotCurveData pointers (one for each axis),
+*     each pointing to an array of AstPlotCurveData structures (one for each tick
+*     value).
+
+*  Notes:
+*     -  This function assumes that the physical coordinate system is 2
+*     dimensional, and it should not be used if this is not the case.
+*     -  If an error has already occurred, or if this function should fail
+*     for any reason, then a NULL pointer is returned.
+
+*/
+
+/* Local Variables: */
+   AstPlotCurveData **cdata;/* The returned pointer */
+   AstPlotCurveData *cdt;   /* Pointer to break info. for current tick mark */
+   AstPlotCurveData tcdt;   /* Pointer to break info. for current curve section */
+   TickInfo *info;          /* Tick mark information for a single axis */
+   double start[ 2 ];       /* Strting position for current curve section */
+   double total_length;     /* Total curve length for all axis ticks */
+   int i;                   /* Axis index */
+   int j;                   /* Tick mark index */
+   int k;                   /* Curve section index */
+
+/* Check the global status. */
+   if( !astOK ) return NULL;
+
+/* Allocate memory to hold two pointers, each pointing to an array of
+   AstPlotCurveData structure. */
+   cdata = (AstPlotCurveData **) astMalloc( 2*sizeof( AstPlotCurveData *) );
+
+/* If succesful, initialise the pointers. */
+   if( astOK ){
+      cdata[ 0 ] = NULL;
+      cdata[ 1 ] = NULL;
+
+/* Draw the curves marking the major tick values on each axis. If no grid is
+   required, we still do this in order to get information about the breaks
+   in the curves which will be used later to decide where to put the labels,
+   but we use "invisible ink". */
+      for( i = 0; i < 2; i++ ){
+
+/* Get a pointer to the TickInfo structure for this axis holding information
+   about where to put tick marks on this axis. */
+         info = grid[ i ];
+
+/* Allocate memory to hold information describing the breaks in each tick
+   mark curve. This takes the form of an array of AstPlotCurveData structures,
+   one for each tick mark. */
+         cdata[ i ] = (AstPlotCurveData *) astMalloc( sizeof(AstPlotCurveData)*
+                                               (size_t) info->nmajor );
+
+/* Check the pointer can be used. */
+         if( astOK ){
+
+/* Initialise a pointer to the first AstPlotCurveData structure for this axis. */
+            cdt = cdata[ i ];
+            total_length = 0.0;
+
+/* Do each tick mark. */
+            for( j = 0; j < info->nmajor; j++ ){
+
+/* Store the starting point of the first section of the curve. */
+               start[ i ] = (info->ticks)[ j ];
+               start[ 1 - i ] = (info->start)[ 0 ];
+
+/* Draw the first section of the curve parallel to the other axis, starting
+   at the values in "start", and extending for a length given in the TickInfo
+   structure. We use invisible ink if short tick marks are required instead
+   of a grid of curves. */
+               AxPlot( this, 1 - i, start, (info->length)[ 0 ],
+                       drawgrid, cdt, method, class, status );
+
+/* Now draw any other sections in the curve. */
+               for( k = 1; k < info->nsect; k++ ){
+
+/* Modify the starting value on the other axis. The starting value on
+   the current axis remains set to the tick mark value. */
+                  start[ 1 - i ] = (info->start)[ k ];
+
+/* Draw the curve, the information describing the breaks goes into
+   temporary storage in the local structure "tcdt". */
+                  AxPlot( this, 1 - i, start, (info->length)[ k ],
+                          drawgrid, &tcdt, method, class, status );
+
+/* Concatenate the break information for this section with the break
+   information describing the previous sections. */
+                  AddCdt( cdt, &tcdt, method, class, status );
+
+               }
+
+/* Increment the total length of curves drawn for all ticks on this axis. */
+               total_length += cdt->length;
+
+/* Point to the AstPlotCurveData structure for the next tick mark. */
+               cdt++;
+
+            }
+
+/* Report an error if the total length of all curves on this axis is zero.
+   This can be caused for instance by bugs in the algorithm for finding
+   major tick values (which may cause AST__BAD tick mark values). */
+            if( total_length == 0.0 && astOK ) {
+               astError( AST__INTER, "%s(%s): No grid curves can be drawn for "
+                         "axis %d.", status, method, class, i + 1 );
+            }
+
+         }
+
+      }
+
+   }
+
+/* If an error has occurred, clean up the returned structures. */
+   if( !astOK ) cdata = CleanCdata( cdata, status );
+
+/* Return. */
+   return cdata;
+
+}
+
+static int DrawRegion( AstPlot *this, AstFrame *frm, const char *method,
+                       const char *class, int *status ){
+/*
+*
+*  Name:
+*     DrawRegion
+
+*  Purpose:
+*     Draw the outline of a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int DrawRegion( AstPlot *this, AstFrame *frm, const char *method,
+*                     const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     If the current Frame in the supplied Plot is a Region, this function
+*     draws a curve marking the outline of the Region. It returns without
+*     action otherwise.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     frm
+*        Pointer to the current Frame in the Plot (possibly a Region).
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if and only if a Region outline was drawn.
+
+*/
+
+/* Local Variables: */
+   AstMapping *map;        /* Mapping with Region masking included */
+   AstPlotCurveData cdata; /* Stores information about curve breaks */
+   AstRegion **comps;      /* List of component Regions */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   double d[ CRV_NPNT ];   /* Offsets to evenly spaced points along curve */
+   double tol;             /* Absolute tolerance value */
+   double x[ CRV_NPNT ];   /* X coords at evenly spaced points along curve */
+   double y[ CRV_NPNT ];   /* Y coords at evenly spaced points along curve */
+   int i;                  /* Loop count */
+   int icomp;              /* Index of component Region */
+   int ncomp;              /* Number of component Regions */
+   int result;             /* The returned value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Check the current Frame is a Region, and is of a class that implements
+   the astRegTrace method. */
+   if( astIsARegion( frm ) &&
+       astRegTrace( (AstRegion *) frm, 0, NULL, NULL ) ){
+
+/* Set up the externals used to communicate with the Map5 function...
+   The number of axes in the physical coordinate system (i.e. the
+   Region). */
+      Map5_ncoord =  astGetNaxes( frm );
+
+/* A pointer to the Plot, the Region, and the Mapping. */
+      Map5_plot = this;
+      Map5_region = (AstRegion *) frm;
+
+/* Also store a pointer to the Mapping, ensuring that the Mapping does
+   not contain any masking effects from the Region. */
+      map = astGetMapping( this, AST__BASE, AST__CURRENT );
+      Map5_map = astRemoveRegions( map );
+      map = astAnnul( map );
+
+/* Convert the tolerance from relative to absolute graphics coordinates.
+   Make the tolerance smaller by a factor of 10 because Regions
+   (specifically Polygonsd) can have very crinkly edges. */
+      tol = 0.1* astGetTol( this )*astMAX( this->xhi - this->xlo,
+                                           this->yhi - this->ylo );
+
+/* Ensure the globals holding the scaling from graphics coords to equally
+   scaled coords are available. */
+      GScales( this, NULL, NULL, method, class, status );
+
+/* Now set up the external variables used by the Crv and CrvLine function. */
+      Crv_scerr = ( astGetLogPlot( this, 0 ) ||
+                    astGetLogPlot( this, 1 ) ) ? 100.0 : 1.5;
+      Crv_ux0 = AST__BAD;
+      Crv_tol = tol;
+      Crv_limit = 0.5*tol*tol;
+      Crv_map = Map5;
+      Crv_ink = 1;
+      Crv_xlo = this->xlo;
+      Crv_xhi = this->xhi;
+      Crv_ylo = this->ylo;
+      Crv_yhi = this->yhi;
+      Crv_out = 1;
+      Crv_xbrk = cdata.xbrk;
+      Crv_ybrk = cdata.ybrk;
+      Crv_vxbrk = cdata.vxbrk;
+      Crv_vybrk = cdata.vybrk;
+      Crv_clip = astGetClip( this ) & 1;
+
+/* Attempt to split the Region into a set of disjoint component Regions. */
+      comps = astRegSplit( (AstRegion *) frm, &ncomp );
+
+/* Draw each one. */
+      for( icomp = 0; icomp < ncomp; icomp++ ) {
+
+/* A pointer to the Region. */
+         Map5_region = comps[ icomp ];
+
+/* Set up a list of points spread evenly over the curve. */
+         for( i = 0; i < CRV_NPNT; i++ ){
+           d[ i ] = ( (double) i)/( (double) CRV_NSEG );
+         }
+
+/* Map these points into graphics coordinates. */
+         Map5( CRV_NPNT, d, x, y, method, class, status GLOBALS_NAME );
+
+/* Use Crv and Map5 to draw the curve. */
+         Crv( this, d, x, y, 0, NULL, NULL, method, class, status );
+
+/* End the current poly line. */
+         Opoly( this, status );
+
+/* Tidy up the static data used by Map5. */
+         Map5( 0, NULL, NULL, NULL, method, class, status GLOBALS_NAME );
+
+/* Annul the component Region pointer. */
+         comps[ icomp ] = astAnnul( Map5_region );
+      }
+
+/* Free the memory holding the list of component Region pointers. */
+      comps = astFree( comps );
+
+/* Annul the Mapping. */
+      Map5_map = astAnnul( Map5_map );
+
+/* Indicate the outline was drawn. */
+      result = 1;
+   }
+
+/* Return. */
+   return result;
+}
+
+static void DrawText( AstPlot *this, int ink, int esc, const char *text,
+                      float x, float y, const char *just, float upx,
+                      float upy, float *xbn, float *ybn, float *drop,
+                      const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     DrawText
+
+*  Purpose:
+*     Draw a character string, potentially including superscripts and
+*     subscripts.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void DrawText( AstPlot *this, int ink, int esc, const char *text,
+*                    float x, float y, const char *just, float upx,
+*                    float upy, float *xbn, float *ybn, float *drop,
+*                    const char *method, const char *class, int *status )
+
+*  Description:
+*     This function displays a character string at a given position
+*     using a specified up-vector, optionally interpreting any Plot escape
+*     sequences contained within the text. It also returns its bounding
+*     box.
+
+*  Parameters:
+*     this
+*        The plot.
+*     ink
+*        If zero, nothing is drawn but the bounding box is still returned.
+*     esc
+*        Should escape sequences be interpreted? They will be printed
+*        literally otherwise.
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     x
+*        The graphics X coordinate of the label's reference point.
+*     y
+*        The graphics Y coordinate of the label's reference point.
+*     just
+*        Pointer to a null-terminated character string identifying the
+*        reference point for the text being drawn. The first character in
+*        this string identifies the reference position in the "up" direction
+*        and may be "M", "B", "C" or "T" (for bottom, baseline, centre or
+*        top). The second character identifies the side-to-side reference
+*        position and may be "L", "C" or "R" (for left, centre or right). The
+*        string is case-insensitive, and only the first two characters are
+*        significant.
+*     upx
+*        The x component of the up-vector for the text. Positive values
+*        always refer to displacements from left to right on the screen,
+*        even if the graphics x axis increases in the opposite sense.
+*     upy
+*        The y component of the up-vector for the text. Positive values
+*        always refer to displacements from left to right on the screen,
+*        even if the graphics y axis increases in the opposite sense.
+*     xbn
+*        An array in which is returned the x coordinates at the corners
+*        of the bounding box. The order is: bottom left, top left, top
+*        right, bottom right.
+*     ybn
+*        An array in which is returned the Y coordinates at the corners
+*        of the bounding box (see xbn).
+*     drop
+*        Address of a float in which to return the distance between the
+*        bottom of the bounding box and the baseline of normal text. May
+*        be NULL.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  The "B" option for the justification in the "up" direction refers
+*     to the base-line on which the text is written. Some characters
+*     ("y", "p", "g", etc) descend below this line. In addition, if the
+*     supplied text string includes any escape sequences which produce
+*     sub-scripts, then these will also descend below the base-line. To
+*     justify the bottom of the entire string (instead of just the
+*     base-line), specify "M" instead of "B" in the justification string.
+*     -  See function astFindEscape for details of the supported escape
+*     sequences.
+*/
+
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   char *a;
+   char *lt;
+   char cc;
+   const char *lj;
+   double ncol;
+   double nfont;
+   double nsize;
+   double nstyle;
+   double nwidth;
+   float alpha_hi;
+   float alpha_lo;
+   float beta_lo;
+   float beta_hi;
+   float cy;
+   float cx;
+   float dy;
+   float dx;
+   float height;
+   float hmx;
+   float hmy;
+   float ly;
+   float lx;
+   float rx;
+   float rlen;
+   float rise;
+   float rxu;
+   float ryu;
+   float ry;
+   float tdrop;
+   float tybn[ 4 ];
+   float txbn[ 4 ];
+   float ulen;
+   float uyu;
+   float uxu;
+   float uy;
+   float ux;
+   float width;
+   float x0;
+   float y0;
+   int estype;
+   int esval;
+   int got_esc;
+   int grfcap;
+   int i;
+   int nc;
+
+/* Check the global error status, and that we have something to plot, and
+   the reference position is good, and that the up vector is not zero. */
+   if ( !astOK || !text || !text[ 0 ] ||  x == AST__BAD || y == AST__BAD ||
+        ( upx == 0.0 && upy == 0.0 ) ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Initialise variables to avoid compiler warnings. */
+   rx = 0.0f;
+   ry = 0.0f;
+   x0 = 0.0f;
+   y0 = 0.0f;
+
+/* Get an up vector which refers to the graphics coordinates in their correct
+   senses (the supplied values are reversed if the corresponding axis is
+   reversed). */
+   ux = ( this->xrev ) ? -upx : upx;
+   uy = ( this->yrev ) ? -upy : upy;
+
+/* Find a vector which points from left to right along the text
+   baseline, taking account of any difference in the scales of the x
+   and y axes (is possible). This also scales the up vector so that it
+   has a length equal to the height of normal text, and scales the right
+   vector to have the same length (on the screen) as the up vector. */
+   RightVector( this, &ux, &uy, &rx, &ry, method, class, status );
+
+/* Create a unit right vector. */
+   rlen = sqrt( rx*rx + ry*ry );
+   rxu = rx/rlen;
+   ryu = ry/rlen;
+
+/* Create a unit up vector. */
+   ulen = sqrt( ux*ux + uy*uy );
+   uxu = ux/ulen;
+   uyu = uy/ulen;
+
+/* Some older GRF modules cannot plot strings with vertical justification
+   of "M". Check the capabilities of the grf module, and, if necessary,
+   modify the justification and the coords of the reference point to use
+   "B" instead of "M". This call also returns us the coords at the left
+   end of the baseline of normal text. */
+   lx = x;
+   ly = y;
+   lj = JustMB( this, esc, text, &lx, &ly, upx, upy, just, uxu, uyu, rxu,
+                ryu, &x0, &y0, method, class, status );
+
+/* Initialise the horizontal and verical limits of the total bounding box. */
+   alpha_lo = FLT_MAX;
+   alpha_hi = -FLT_MAX;
+   beta_lo = FLT_MAX;
+   beta_hi = -FLT_MAX;
+
+/* Tell the grf module whether or not to interpret escape sequences,and
+   also note if the grf module is capable of interpreting escape
+   sequences. */
+   grfcap = GCap( this, GRF__ESC, esc, status );
+
+/* Forget the horizontal position remembered by any "%h+" escape sequences
+   from any previous string. */
+   this->hmarkx = FLT_MAX;
+   this->hmarky = FLT_MAX;
+
+/* If escape sequences are being interpreted and the string contains some
+   escape sequences, but the grf module cannot interpret escape sequences,
+   split the supplied text up into sub-strings delimited by escape sequences
+   and plot each sub-string individually, modifying the reference point and
+   graphics attributes as indicated by the escape sequences. */
+   if( esc && HasEscapes( text, status ) && !grfcap ) {
+
+/* Take a copy of the supplied text so that we can temporarily terminate
+   each sub-string by poking a null (0) character into it. */
+      lt = (char *) astStore( NULL, (void *) text, strlen( text ) + 1 );
+
+/* Indicate that the current baseline is at its normal height. */
+      rise = 0.0;
+
+/* Get the graphical attribute values for normal text. */
+      GAttr( this, GRF__SIZE, AST__BAD, &nsize, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__WIDTH, AST__BAD, &nwidth, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__FONT, AST__BAD, &nfont, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__STYLE, AST__BAD, &nstyle, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__COLOUR, AST__BAD, &ncol, GRF__TEXT, method, class, status );
+
+/* The "concatenation point" (cx,cy) is the point where the normal baseline
+   crosses the left hand edge of the substring bounding box. Initialise
+   this to the left edge of the first substring. */
+      cx = x0;
+      cy = y0;
+
+/* Loop round the whole string, drawing each sub-string. */
+      a = lt;
+      while( *a && astOK ) {
+
+/* Examine the start of the remaining string and note if it begins with
+   an escape sequence. If so, the type and value of the escape sequnece
+   is returned. In either case the number of characters to the next
+   delimiter is returned. */
+         got_esc = astFindEscape( a, &estype, &esval, &nc );
+
+/* If the string starts with an escaped percent sign, modify things so that
+   we can draw the percent sign with the normal text drawing code below. */
+         if( got_esc && estype == GRF__ESPER ) {
+            got_esc = 0;
+            a++;
+            nc = 1;
+         }
+
+/* If the string starts with any other escape sequence, modify the graphics
+   attributes and concatenation point as required by the escape sequence. */
+         if( got_esc ) {
+            InterpEscape( this, estype, (double) esval, &cx, &cy, ux, uy,
+                          rx, ry, lj, &rise, nsize, nstyle, nwidth, ncol,
+                          nfont, method, class, status );
+
+/* If the remaining string starts with normal text, draw it. */
+         } else {
+
+/* Temporarily terminate the sub-string which ends with the next escape
+   sequence. */
+            cc = a[ nc ];
+            a[ nc ] = 0;
+
+/* We now have to decide on the reference point for this sub-string. If
+   the justification is "BL" then the reference point is just the current
+   concatenation point. */
+            if( lj[ 0 ] == 'B' && lj[ 1 ] == 'L' ) {
+               lx = cx;
+               ly = cy;
+
+/* Otherwise, the reference point is offset from the concatenation point.
+   It would be simpler to draw all substrings with "BL" justification but
+   this causes problems with some grf modules (such as GAIA) which zoom
+   the display by modifying the position of text strings without also
+   modifying the size of text strings. Using the correct reference point
+   for all sub-strings minimises the drift which occurs when such a grf
+   modules zooms the display. */
+            } else {
+
+/* Find the width and height of this substring, and the distance between the
+   bottom of the bounding box and the baseline (the drop). We do this
+   by calling this function recursively, using "BL" justification to
+   avoid infinite recursion. */
+
+/* Forget the horizontal position remembered by any "%h+" escape sequences
+   from any previous string. Save and re-instate the position of the
+   horizontal mark since the call to DrawText may change it. */
+               hmx = this->hmarkx;
+               hmy = this->hmarky;
+
+               DrawText( this, 0, esc, a, cx, cy, "BL", upx, upy, txbn, tybn,
+                         &tdrop, method, class, status );
+
+               this->hmarkx = hmx;
+               this->hmarky = hmy;
+
+               dx = txbn[ 0 ] - txbn[ 3 ];
+               dy = tybn[ 0 ] - tybn[ 3 ];
+               width = sqrt( dx*dx + dy*dy );
+
+               dx = txbn[ 0 ] - txbn[ 1 ];
+               dy = tybn[ 0 ] - tybn[ 1 ];
+               height = sqrt( dx*dx + dy*dy );
+
+/* First move right from the concatenation point by a fraction of the width
+   of the substring (0.5 for "C" and 1.0 for "R"). */
+               if( lj[ 1 ] == 'C' ) {
+                  width *= 0.5;
+               } else if( lj[ 1 ] != 'R' ) {
+                  width = 0;
+               }
+               lx = cx + rxu*width;
+               ly = cy + ryu*width;
+
+/* Now move vertically by an amount which produes the requested vertical
+   justification. */
+               if( lj[ 0 ] == 'T' ) {
+                  height -= tdrop;
+                  lx += height*uxu;
+                  ly += height*uyu;
+
+               } else if( lj[ 0 ] == 'C' ) {
+                  height = 0.5*height - tdrop;
+                  lx += height*uxu;
+                  ly += height*uyu;
+
+               } else if( lj[ 0 ] == 'M' ) {
+                  lx -= tdrop*uxu;
+                  ly -= tdrop*uyu;
+               }
+            }
+
+/* Draw it, and then find its real bounding box (i.e. using the correct
+   reference position found above). */
+            if( ink ) GText( this, a, lx, ly, lj, upx, upy, method, class, status );
+            GTxExt( this, a, lx, ly, lj, upx, upy, txbn, tybn, method, class, status );
+
+/* Re-instate the orignal value of the terminator character.*/
+            a[ nc ] = cc;
+
+/* Move the concatenation point to the right (i.e. in the direction of the text
+   baseline) by an amount equal to the width of the bounding box. Also
+   update the total bounding box limits.*/
+            UpdateConcat( txbn, tybn, ux, uy, rx, ry, &cx, &cy,
+                           x0, y0, &alpha_lo, &alpha_hi, &beta_lo, &beta_hi, status );
+         }
+
+/* Move on to the next character. */
+         a += nc;
+      }
+
+/* Free resources. */
+      lt = astFree( lt );
+
+/* Reset all attributes to their normal values. */
+      GAttr( this, GRF__SIZE, nsize, NULL, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__WIDTH, nwidth, NULL, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__COLOUR, ncol, NULL, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__FONT, nfont, NULL, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__STYLE, nstyle, NULL, GRF__TEXT, method, class, status );
+
+/* If any escape sequences can be interpreted by the grf module, just pass
+   the text string on to the grf module. */
+   } else {
+      if( ink ) GText( this, text, lx, ly, lj, upx, upy, method, class, status );
+      GTxExt( this, text, lx, ly, lj, upx, upy, txbn, tybn, method, class, status );
+
+/* The corners in the bounding box returned by GTxExt are in no
+   particular order. But this function is contracted to return them
+   in a specified order. So we use UpdateConcat to find the verical and
+   horizontal limits of the box in a form which can be used to produce
+   the correct order. UpdateConcat will also update the concatenation point,
+   but that is irrelevant in this context. */
+      UpdateConcat( txbn, tybn, ux, uy, rx, ry, &lx, &ly, x0, y0, &alpha_lo,
+                     &alpha_hi, &beta_lo, &beta_hi, status );
+   }
+
+/* Return the total bounding box,in the order bottom left, topleft, top
+   right, bottom right. */
+   xbn[ 0 ] = x0 + alpha_lo*ux + beta_lo*rx;
+   ybn[ 0 ] = y0 + alpha_lo*uy + beta_lo*ry;
+
+   xbn[ 1 ] = x0 + alpha_hi*ux + beta_lo*rx;
+   ybn[ 1 ] = y0 + alpha_hi*uy + beta_lo*ry;
+
+   xbn[ 2 ] = x0 + alpha_hi*ux + beta_hi*rx;
+   ybn[ 2 ] = y0 + alpha_hi*uy + beta_hi*ry;
+
+   xbn[ 3 ] = x0 + alpha_lo*ux + beta_hi*rx;
+   ybn[ 3 ] = y0 + alpha_lo*uy + beta_hi*ry;
+
+/* Return the drop. */
+   if( drop ) *drop =  -alpha_lo*ulen;
+
+/* Free memory.*/
+   lj = astFree( (void *) lj );
+
+/* If OK, update the box containing all drawn graphics primitives. */
+   if( ink && astOK && !Boxp_freeze ) {
+      for( i = 0; i < 4; i++ ){
+         Boxp_lbnd[ 0 ] = astMIN( xbn[ i ], Boxp_lbnd[ 0 ] );
+         Boxp_ubnd[ 0 ] = astMAX( xbn[ i ], Boxp_ubnd[ 0 ] );
+         Boxp_lbnd[ 1 ] = astMIN( ybn[ i ], Boxp_lbnd[ 1 ] );
+         Boxp_ubnd[ 1 ] = astMAX( ybn[ i ], Boxp_ubnd[ 1 ] );
+      }
+   }
+}
+
+static void DrawTicks( AstPlot *this, TickInfo **grid, int drawgrid,
+                       double *labelat, double *gap, const char *method,
+                       const char *class, int *status ){
+/*
+*
+*  Name:
+*     DrawTicks
+
+*  Purpose:
+*     Draw tick marks for a 2-D annotated coordinate grid.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void DrawTicks( AstPlot *this, TickInfo **grid, int drawgrid,
+*                     double *labelat, double *gap, const char *method,
+*                     const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function draws major and minor tick marks. It uses a different
+*     technique depending on whether the tick marks are to be put along the
+*     edges of the plotting area, or along a curve through the middle of the
+*     plotting area. The physical axis values at which to put tick marks
+*     are supplied in parameter "grid".
+*
+*     If the ticks are placed on the edges of the plotting area, The
+*     EdgeCrossings function is used to find all points along the edge which
+*     have a physical axis value correspoinding to a tick value (there can in
+*     general be more than one point on an edge with a given physical axis
+*     value). Ticks are put at all such crossings.
+*
+*     If ticks are placed within the plotting area, then they are put
+*     along a curve defined by the "other axis" values supplied in
+*     parameter "labelat".
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     grid
+*        A pointer to an array of two TickInfo pointers (one for each axis),
+*        each pointing to a TickInfo structure holding information about
+*        tick marks on the axis. See function GridLines.
+*     drawgrid
+*        If non-zero, then a grid of curves has been drawn to mark the
+*        major axis values.
+*     labelat
+*        A pointer to a 2 element array in holding the constant axis
+*        values at which tick marks are to be put. Element 0 should hold
+*        the axis 1 value at which tick marks for axis 0 are placed. Element
+*        1 should hold the axis 0 value at which tick marks for axis
+*        1 are placed. If ticks are to be placed round the edges of the
+*        plotting zone instead of within the plotting zone, then values of
+*        AST__BAD should be supplied.
+*     gap
+*        Pointer to array of two values holding the gap between major
+*        tick marks on the two axes. This will be the difference between,
+*        or the ratio of, adjacent tick mark values, depending on the
+*        setting of the LogTicks attribute.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This function assumes the current Frame of the Plot is 2
+*     dimensional, and it should not be called if this is not the case.
+*/
+
+/* Local Variables: */
+   AstFrame *frame;       /* Pointer to current Frame in Plot */
+   AstMapping *mapping;   /* Pointer to graphics->physical Mapping */
+   AstPointSet *pset1;    /* Pointer to PointSet holding physical coords. */
+   AstPointSet *pset2;    /* Pointer to PointSet holding graphics coords. */
+   AstPointSet *pset3;    /* Pointer to PointSet holding clipped graphics coords. */
+   EdgeCrossingsStatics *ecstatics = NULL; /* For static data structure */
+   TickInfo *info;        /* Pointer to the TickInfo for the current axis */
+   double *ptr1[2];       /* Pointer to physical data */
+   double **ptr2;         /* Pointer to graphics data */
+   double **ptr3;         /* Pointer to clipped graphics data */
+   double *a;             /* Pointer to next current axis value */
+   double *b;             /* Pointer to next other axis value */
+   double *value;         /* Current tick value */
+   double *x;             /* Pointer to next X value */
+   double *xc;            /* Pointer to next clipped X value */
+   double *y;             /* Pointer to next Y value */
+   double *yc;            /* Pointer to next clipped Y value */
+   double a0;             /* Physical axis value at base of tick */
+   double axmax;          /* Upper axis limit */
+   double axmin;          /* Lower axis limit */
+   double delta1;         /* Increment between minor ticks above major tick */
+   double delta2;         /* Increment between minor ticks below major tick */
+   double diff;           /* Difference between adjacent major ticks */
+   double dl2;            /* Squared increment between points */
+   double dl2_limit;      /* Minimum usable squared increment between points */
+   double dl;             /* Increment between points */
+   double dx;             /* X component of increment along tick mark */
+   double dy;             /* Y component of increment along tick mark */
+   double ex;             /* X component of increment between points */
+   double ey;             /* Y component of increment between points */
+   double lblat2;         /* Other axis value part way up each tick */
+   double lblat;          /* Other axis value at base of each tick */
+   double mindim;         /* Shortest dimension of plotting area */
+   double minval;         /* Current axis value at next minor tick */
+   double mjsign;         /* Sign of the major tick mark length */
+   double mjtklen;        /* Length of major tick marks */
+   double mnsign;         /* Sign of the minor tick mark length */
+   double mntklen;        /* Length of minor tick marks */
+   double ux;             /* X component of unit vector along tick mark */
+   double uy;             /* Y component of unit vector along tick mark */
+   double val;            /* Major axis value */
+   double x0;             /* X at base of tick */
+   double x1;             /* X at end of tick */
+   double x2;             /* Clipped X at base of tick */
+   double y0;             /* Y at base of tick */
+   double y1;             /* Y at end of tick */
+   double y2;             /* Clipped Y at base of tick */
+   int *majflag;          /* Pointer to next major/minor flag */
+   int *majflags;         /* Pointer to aray of major/minor flags */
+   int axis;              /* Current axis index */
+   int ed;                /* Doing a secondary edge? */
+   int edge;              /* Index of edge being ticked */
+   int first;             /* Is this the first tick to be drawn? */
+   int gelid;             /* ID for next graphical element to be drawn */
+   int i;                 /* Minor tick mark index */
+   int logticks;          /* Are major tick marks logarithmically spaced? */
+   int minhi;             /* Highest minor tick mark index */
+   int minlo;             /* Lowest minor tick mark index */
+   int nedge;             /* No. of edges to be ticked for each axis */
+   int nel;               /* Actual number of tick marks to draw */
+   int ntot;              /* Maximum number of tick marks */
+   int tick;              /* Tick index */
+   int lasttick;          /* Index of last major tick mark */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   a = NULL;
+   delta1 = 0.0;
+   delta2 = 0.0;
+   lblat2 = 0.0;
+   uy = 0.0;
+   logticks = 0;
+
+/* Get the minimum dimension of the plotting ares. */
+   mindim = astMIN( this->xhi - this->xlo, this->yhi - this->ylo );
+
+/* Information about the drawn tick marks is saved in the Plot structure.
+   Reset this information now so that we are ready to store information
+   about the new ticks that will be drawn by this function invocation. */
+   SaveTick( this, -1, 0.0, 0.0, 0, status );
+
+/* If ticks are to be put round the edges of the plotting area... */
+/* ============================================================== */
+   if( labelat[ 0 ] == AST__BAD || labelat[ 1 ] == AST__BAD ){
+
+/* Store the number of edges to be ticked for each axis. */
+      nedge = astGetTickAll( this )? 2 : 1;
+
+/* Do each required edge. */
+      for( ed = 0; ed < nedge; ed++ ){
+
+/* Initialize the id value for graphical element being drawn. */
+         gelid = AST__TICKS1_ID;
+
+/* Do each axis. */
+         for( axis = 0; axis < 2; axis++ ){
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+            astGrfAttrs( this, gelid, 1, GRF__LINE, method, class );
+
+/* Store the length in graphics coordinates of major tick marks for this
+   axis. Use a default of zero if a grid has been drawn. */
+            if( astTestMajTickLen( this, axis ) || !drawgrid ){
+               mjtklen = astGetMajTickLen( this, axis )*mindim;
+            } else {
+               mjtklen = 0.0;
+            }
+
+/* Store the length in graphics coordinates of minor tick marks. */
+            mntklen = astGetMinTickLen( this, axis )*mindim;
+
+/* Get the edge to be labelled with the axis values. Edge 0 is the left hand
+   edge. Edge 1 is the top edge. Edge 2 is the right-hand edge. Edge 3 is
+   the bottom edge. */
+            edge = ( astGetEdge( this, axis ) + ed*2 ) % 4;
+            if( edge < 0 ) edge = -edge;
+
+/* Store a pointer to the structure containing information describing the
+   positions of the major tick marks along this axis. */
+            info = grid[ axis ];
+
+/* Store a pointer to the axis value at the first major tick mark. */
+            value = info->ticks;
+
+/* Minor tick marks are drawn on both sides of each major tick mark. They
+   are identified by an index number relative to major tick mark at zero.
+   Store the indices of the first and last minor tick marks. */
+            minlo = ( 1 - info->nminor )/2;
+            minhi = info->nminor/2;
+
+/* If major ticks are linear, store the difference between minor tick marks.
+   This value will be the same for all major ticks. */
+            logticks = astGetLogTicks( this, axis );
+            if( !logticks ) {
+               delta1 = gap[ axis ]/(double)info->nminor;
+               delta2 = delta1;
+            }
+
+/* Loop round until all ticks have been done. */
+            for( tick = 0; tick < info->nmajor; tick++ ){
+
+/* Draw tick marks at all occurrences of the current major tick value on
+   the selected edge of the plotting area. */
+               Ticker( this, edge, axis, *value, gap, mjtklen,
+                       1, ( ed == 0 ), &ecstatics, method, class, status );
+
+/* If minor tick mark values were not supplied, calculate them as even
+   intervals between the major tick values. */
+               if( !info->minticks ) {
+
+/* If major ticks are logarithmic, store the difference between minor tick
+   marks. This value will be different for each major tick. Also, since
+   the minor ticks are drawn on either side of the major tick, the minor
+   tick spacing above the major tick will be different to that below the
+   minor tick when using logarathmic ticks. "delta1" is the minor gap
+   above the major value, and "delta2" is the minor gap below the major
+   value. */
+                  if( logticks ) {
+                     delta1 = (*value) * ( gap[ axis ] - 1.0 )/
+                                         (double)info->nminor;
+                     delta2 = delta1 / gap[ axis ];
+                  }
+
+/* Extra minor tick marks are drawn below the first major tick mark and
+   above the last major tick mark to fill in any gaps caused by axis
+   limits being exceeded. */
+                  if( tick == 0 ) {
+                     minlo = 1 - info->nminor;
+                  } if( tick == 1 ) {
+                     minlo = ( 1 - info->nminor )/2;
+                  } else if( tick == info->nmajor - 1 ) {
+                     minhi = info->nminor - 1;
+                  }
+
+/* Store the axis value at the first minor tick mark. */
+                  minval = *value + minlo*delta2;
+
+/* Loop round all the minor tick marks, storing the physical coordinates
+   defining the tick mark. */
+                  for( i = minlo; i <= minhi; i++ ){
+
+/* Draw tick marks at all occurrences of the current minor tick value on
+   the selected edge of the plotting area. Do not do the minor tick mark
+   with index zero, since this corresponds to the position of the major
+   tick mark. */
+                     if( i ) Ticker( this, edge, axis, minval, gap, mntklen,
+                                     0, ( ed == 0 ), &ecstatics, method, class, status );
+
+/* Get the axis value at the next minor tick mark. */
+                     minval += ( i < 0 ) ? delta2 : delta1;
+                  }
+               }
+
+/* Point to the next major tick value. */
+               value++;
+
+            }
+
+/* If minor tick mark values have been supplied, used them. */
+            if( info->minticks ) {
+               value = info->minticks;
+               for( tick = 0; tick < info->nminor; tick++, value++ ){
+                  Ticker( this, edge, axis, *value, gap, mntklen, 0,
+                          ( ed == 0 ), &ecstatics, method, class, status );
+               }
+            }
+
+/* Re-establish the original graphical attributes. */
+            astGrfAttrs( this, gelid, 0, GRF__LINE, method, class );
+
+/* Set up the id for the next graphical element to be drawn. */
+            gelid = AST__TICKS2_ID;
+         }
+      }
+
+/* Free the static resources allocated within EdgeCrossings (called
+   by Ticker). */
+      (void) EdgeCrossings( NULL, 0, 0, 0.0, NULL, NULL, &ecstatics, method,
+                            class, status );
+
+/* If ticks are to put within the interior of the plotting area... */
+/* ============================================================== */
+   } else {
+
+/* Get the mapping from base Frame (graphics coords) to current Frame
+   (physical coords). */
+      mapping = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Get the current Frame from the Plot. */
+      frame = astGetFrame( this, AST__CURRENT );
+
+/* Initialize the id value for graphical element being drawn. */
+      gelid = AST__TICKS1_ID;
+
+/* Do each axis. */
+      for( axis = 0; axis < 2; axis++ ){
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+         astGrfAttrs( this, gelid, 1, GRF__LINE, method, class );
+
+/* Store the length in graphics coordinates of major tick marks for this
+   axis. Use a default of zero if a grid has been drawn. */
+         if( astTestMajTickLen( this, axis ) || !drawgrid ){
+            mjtklen = astGetMajTickLen( this, axis )*mindim;
+         } else {
+            mjtklen = 0.0;
+         }
+
+/* Store the length in graphics coordinates of minor tick marks. */
+         mntklen = astGetMinTickLen( this, axis )*mindim;
+
+/* Indicate that the tick mark lengths should not be negatated. */
+         mjsign = 1.0;
+         mnsign = 1.0;
+
+/* Store the smallest squared distance in graphics coordinates which
+   can reliably be used to determine the direction of a tick mark. */
+         dl2_limit = 0.0001*mindim;
+         dl2_limit *= dl2_limit;
+
+/* Store a pointer to the structure containing information describing the
+   positions of the major tick marks along this axis. */
+         info = grid[ axis ];
+
+/* Store a pointer to the axis value at the first major tick mark. */
+         value = info->ticks;
+
+/* Get the maximum number of tick marks to be drawn on this axis. */
+         ntot = 0;
+         ntot = info->nmajor;
+         if( info->minticks ) {
+            ntot += info->nmajor + info->nminor;
+         } else {
+            ntot += ( info->nmajor + 1 )*( info->nminor - 1 );
+         }
+
+/* Pass on to the next axis if no ticks are to be drawn. */
+         if( ntot ) {
+
+/* Allocate memory to hold the physical coordinates defining all the
+   required tick marks. Each tick mark is defined by 2 points. */
+            ptr1[ 0 ] = (double *) astMalloc( sizeof(double)*(size_t)(2*ntot) );
+            ptr1[ 1 ] = (double *) astMalloc( sizeof(double)*(size_t)(2*ntot) );
+
+/* Allocate memory to hold a set of flags indicating whether each tick
+   mark is minor or major. */
+            majflags = (int *) astMalloc( sizeof(int)*(size_t)ntot );
+
+/* Check the pointers can be used. */
+            if( astOK ){
+
+/* If the tick values have been supplied using astSetTickValues, just
+   copy them into the above arrays. */
+               if( info->minticks ) {
+
+                  a = ptr1[ axis ];
+                  b = ptr1[ 1 - axis ];
+                  majflag = majflags;
+
+                  for( tick = 0; tick <= info->nmajor; tick++ ){
+
+                     val =  info->ticks[ tick ];
+                     if( logticks ) {
+                        diff = val * ( gap[ axis ] - 1.0 );
+                        lblat2 = labelat[ axis ] + 0.2*diff;
+                     } else {
+                        lblat2 = labelat[ axis ] + 0.2*gap[ 1 - axis ];
+                     }
+
+                     *(a++) = val;
+                     *(b++) = labelat[ axis ];
+                     *(a++) = val;
+                     *(b++) = lblat2;
+                     *(majflag++) = 1;
+                  }
+
+                  for( tick = 0; tick <= info->nminor; tick++ ){
+
+                     val =  info->minticks[ tick ];
+                     if( logticks ) {
+                        diff = val * ( gap[ axis ] - 1.0 );
+                        lblat2 = labelat[ axis ] + 0.2*diff;
+                     } else {
+                        lblat2 = labelat[ axis ] + 0.2*gap[ 1 - axis ];
+                     }
+
+                     *(a++) = val;
+                     *(b++) = labelat[ axis ];
+                     *(a++) = val;
+                     *(b++) = lblat2;
+                     *(majflag++) = 0;
+                  }
+
+/* If the tick values were not supplied using astSetTickValues, then we
+   need to calculate the minor tick positions explicitly. */
+               } else {
+
+/* Store pointers to the next point on each axis. "a" always refers to the
+   current axis. Also store the value on the other axis at which the tick
+   marks starts, and another value on the other axis which is used to
+   defined the tick mark directions. */
+                  a = ptr1[ axis ];
+                  b = ptr1[ 1 - axis ];
+                  majflag = majflags;
+                  lblat = labelat[ axis ];
+
+/* Store another value on the other axis which is used to defined the tick
+   mark directions, and the difference between minor tick marks. For
+   linearly spaced tick marks these values will be the same for all major
+   ticks. Minor ticks are always drawn above the correponding major
+   value (i.e. minlo == 1 ) and so we do not need to set delta2. */
+                  logticks = astGetLogTicks( this, axis );
+                  if( !logticks ) {
+                     lblat2 = labelat[ axis ] + 0.2*gap[ 1 - axis ];
+                     delta1 = gap[ axis  ]/(double)info->nminor;
+                  }
+
+/* Store the indices of the first and last minor tick marks, relative to
+   a major tick mark. */
+                  minlo = 1;
+                  minhi = info->nminor - 1;
+
+/* Get the axis limits. */
+                  axmin = astGetBottom( frame, axis );
+                  axmax = astGetTop( frame, axis );
+
+/* Loop round until all ticks have been done. We include a hypothetical tick
+   at index -1 (i.e. one gap below the first listed tick value) in order
+   to get minor tick marks below the first major tick. But the
+   hypothetical major tick value is not included in the list of major tick
+   values to draw. */
+                  lasttick = info->nmajor - 1;
+                  for( tick = -1; tick <= lasttick; tick++ ){
+
+/* Get the major tick value. */
+                     if( tick == -1 ) {
+                        if( !logticks ) {
+                           val = (*value) - gap[ axis ];
+                        }else {
+                           val = (*value)/gap[ axis ];
+                        }
+                     } else {
+                        val = *(value++);
+                     }
+
+/* Now find the value on the other axis which is used to defined the tick
+   mark directions, and the difference between minor tick marks, in the
+   case of logarithmically spaced tick marks. These values will be
+   different for every major tick. Minor ticks are always drawn above the
+   correponding major value (i.e. minlo == 1 ) and so we do not need to set
+   delta2. */
+                     if( logticks ) {
+                        diff = val * ( gap[ axis ] - 1.0 );
+                        lblat2 = labelat[ axis ] + 0.2*diff;
+                        delta1 = diff / (double)info->nminor;
+                     }
+
+/* If major tick marks are required, store the physical coordinates at the
+   start of the major tick mark, and at a point a little way up the major
+   tick mark. */
+                     if( tick > -1 ){
+                        *(a++) = val;
+                        *(b++) = lblat;
+                        *(a++) = val;
+                        *(b++) = lblat2;
+                        *(majflag++) = 1;
+                     }
+
+/* Store the points defining the minor tick marks on either side of
+   this major tick mark. First store the axis value at the first minor
+   tick mark. */
+                     minval = val + minlo*delta1;
+
+/* Loop round all the minor tick marks, storing the physical coordinates
+   defining the tick mark. */
+                     for( i = minlo; i <= minhi; i++ ){
+
+/* Do not do the minor tick mark with index zero, since this corresponds
+   to the position of the major tick mark. Do not do any minor ticks that
+   are outside the axis range. */
+                        if( i && minval >= axmin && minval <= axmax ){
+                           *(a++) = minval;
+                           *(b++) = lblat;
+                           *(a++) = minval;
+                           *(b++) = lblat2;
+                           *(majflag++) = 0;
+                        }
+
+/* Get the axis value at the next minor tick mark. */
+                        minval += delta1;
+                     }
+                  }
+               }
+            }
+
+/* Adjust the size of the arrays to exclude any unused space. */
+            nel = a - ptr1[axis];
+            ptr1[axis] = (double *) astRealloc( (void *) ptr1[axis],
+                                                sizeof(double)*nel );
+            ptr1[1-axis] = (double *) astRealloc( (void *) ptr1[1-axis],
+                                                  sizeof(double)*nel );
+
+/* Create a pointset holding these coordinates. */
+            pset1 = astPointSet( nel, 2, "", status );
+            astSetPoints( pset1, ptr1 );
+
+/* Transform these physical coordinates into graphics coordinates, without
+   doing any clipping (this is so that tick marks are still drawn even if
+   they extend into the area containing clipped physical coordinates). */
+            pset2 = astTransform( mapping, pset1, 0, NULL );
+            ptr2 = astGetPoints( pset2 );
+
+/* Transform them again this time with clipping. */
+            pset3 = Trans( this, NULL, mapping, pset1, 0, NULL, 0, method, class, status );
+            ptr3 = astGetPoints( pset3 );
+
+/* Check the pointers can be used.*/
+            if( astOK ){
+
+/* Store pointers to the next point on each axis. */
+               a = ptr1[ axis ];
+
+               x = ptr2[ 0 ];
+               y = ptr2[ 1 ];
+
+               xc = ptr3[ 0 ];
+               yc = ptr3[ 1 ];
+
+               majflag = majflags;
+
+/* Loop round all ticks (major and minor). */
+               ux = AST__BAD;
+               first = 1;
+               for( tick = 0; tick < nel/2; tick++ ){
+
+/* Store the physical axis value at the base of the tick mark (skip over
+   the physical axis value at the point up the tick mark). */
+                  a0 = *(a++);
+                  a++;
+
+/* Store the x and y coordinates at the base of the tick mark. */
+                  x0 = *(x++);
+                  y0 = *(y++);
+
+/* Store the x and y coordinates at a point up the tick mark. */
+                  x1 = *(x++);
+                  y1 = *(y++);
+
+/* Store the clipped x and y coordinates at the base of the tick mark. */
+                  x2 = *(xc++);
+                  y2 = *(yc++);
+
+/* Skip over the clipped x and y coordinates at the point up the tick mark. */
+                  xc++;
+                  yc++;
+
+/* Check they are all valid, and that the start of the tick mark is within
+   the plotting area. */
+                  if( x0 != AST__BAD && y0 != AST__BAD &&
+                      x1 != AST__BAD && y1 != AST__BAD &&
+                      x2 != AST__BAD && y2 != AST__BAD &&
+                      x0 <= this->xhi && x0 >= this->xlo &&
+                      y0 <= this->yhi && y0 >= this->ylo ){
+
+/* Get the increments in X and Y beyween the two points, and the squared
+   distance between the two points. */
+                     dx = x1 - x0;
+                     dy = y1 - y0;
+                     dl2 = dx*dx + dy*dy;
+
+/* Check the two points are not co-incident. */
+                     if( dl2 > dl2_limit ){
+
+/* Store the distance between the two points. */
+                        dl = sqrt( dl2 );
+
+/* If this is the first tick to be drawn on this axis, decide which
+   direction to draw the tick mark so that they will appear on the right
+   hand side of the axis as seen by someone moving along the axis in the
+   positive direction (the numerical labels are also drawn on the same
+   side). */
+                        if( first ){
+                           first = 0;
+
+/* If the next tick mark is not defined, make an arbitrary decision by
+   leaving the sign of the tick mark length unchanged. */
+                           if( tick + 1 < nel/2 &&
+                               *x != AST__BAD && *y != AST__BAD &&
+                               a0 != AST__BAD && *a != AST__BAD ){
+
+/* Form the vector joining this tick mark to the next. */
+                               ex = *x - x0;
+                               ey = *y - y0;
+
+/* Ensure this vector is in the positive direction of the axis. */
+                               if( *a < a0 ) {
+                                  ex = -ex;
+                                  ey = -ey;
+                               }
+
+/* If a positive tick mark length would put the marks on the wrong side,
+   negate the tick mark length. */
+                               if( ex*dy > ey*dx ){
+                                  mjsign = -1.0;
+                                  mnsign = -1.0;
+                               }
+                           }
+                        }
+
+/* Store the unit vector in the direction of the tick mark. This is used
+   as the default vector for the next tick mark if the direction of the
+   next tick mark is indeterminate. */
+                        ux = dx/dl;
+                        uy = dy/dl;
+                     }
+
+/* Only draw this tickmark if its direction is known. */
+                     if( ux != AST__BAD ) {
+
+/* Get the position of the end of the tick mark. The length of the tick
+   mark depends on whether it is a major or minor tick mark. */
+                        if( *majflag ){
+                           x1 = x0 + mjsign*mjtklen*ux;
+                           y1 = y0 + mjsign*mjtklen*uy;
+                        } else {
+                           x1 = x0 + mnsign*mntklen*ux;
+                           y1 = y0 + mnsign*mntklen*uy;
+                        }
+
+/* Save and draw the tick mark. */
+                        SaveTick( this, axis, x0, y0, *majflag, status );
+                        if( x0 != x1 || y0 != y1 ) {
+                           Bpoly( this, (float) x0, (float) y0, status );
+                           Apoly( this, (float) x1, (float) y1, status );
+                           Opoly( this, status );
+                        }
+                     }
+                  }
+
+/* Point to the next major/minor flag. */
+                  majflag++;
+               }
+            }
+
+/* Free the memory holding the physical coordinates. */
+            ptr1[ 0 ] = (double *) astFree( ( void *) ptr1[ 0 ] );
+            ptr1[ 1 ] = (double *) astFree( ( void *) ptr1[ 1 ] );
+            majflags = (int *) astFree( (void *) majflags );
+
+/* Annul the PointSets. */
+            pset1 = astAnnul( pset1 );
+            pset2 = astAnnul( pset2 );
+            pset3 = astAnnul( pset3 );
+         }
+
+/* Re-establish the original graphical attributes. */
+         astGrfAttrs( this, gelid, 0, GRF__LINE, method, class );
+
+/* Set up the id for the next graphical element to be drawn. */
+         gelid = AST__TICKS2_ID;
+      }
+
+/* Annul the pointers to the Mapping and Frame. */
+      mapping = astAnnul( mapping );
+      frame = astAnnul( frame );
+
+   }
+
+/* Return. */
+   return;
+
+}
+
+static void EBuf( AstPlot *this, int *status ) {
+/*
+*++
+*  Name:
+c     astEBuf
+f     AST_EBUF
+
+*  Purpose:
+*     End the current graphical buffering context.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astEBuf( AstPlot *this )
+f     CALL AST_EBUF( THIS STATUS )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+c     This function
+f     This routine
+*     ends the current graphics buffering context. It should match a
+*     corresponding call to the
+c     astBBuf function.
+f     AST_EBUF routine.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - The nature of the buffering is determined by the underlying
+*     graphics system (as defined by the current grf module). Each call
+c     to this function
+f     to this routine
+*     simply invokes the astGEBuf function in the grf module.
+
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the active GRF EBuf function. */
+   GEBuf( this, "astEBuf", astGetClass( this ), status );
+}
+
+static int EdgeLabels( AstPlot *this, int ink, TickInfo **grid,
+                       AstPlotCurveData **cdata, int force, const char *method,
+                       const char *class, int *status ){
+/*
+*
+*  Name:
+*     EdgeLabels
+
+*  Purpose:
+*     Attempts to display labels for the major tick values around the edges
+*     of the plotting area.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int EdgeLabels( AstPlot *this, int ink, TickInfo **grid,
+*                     AstPlotCurveData **cdata, int force, const char *method,
+*                     const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function determines how many major tick value labels could be
+*     placed on the specified edges of the plotting area, and then if
+*     requested, and if sufficient such labels are found (more than 3 on
+*     each axis), they are drawn. To place a label on an edge, the curve
+*     defining the major tick value must cross the edge at a reasonably
+*     angle (at least 3 degrees). Labels are not drawn which would overlap
+*     other, previously drawn, labels. A flag is returned indicating if
+*     edge labels were (or could be) drawn.
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     ink
+*        If zero, then no labels are drawn, but the decision whether or
+*        not to draw them is still made and indicated in the returned function
+*        value.
+*     grid
+*        A pointer to an array of two TickInfo pointers (one for each axis),
+*        each pointing to a TickInfo structure holding information about
+*        tick marks on the axis. See function GridLines.
+*     cdata
+*        A pointer to an array of two AstPlotCurveData pointers (one for each axis),
+*        each pointing to an array of AstPlotCurveData structure (one for each
+*        major tick value on the axis), holding information about breaks
+*        in the curves drawn to mark the major tick values. See function
+*        DrawGrid.
+*     force
+*        If non-zero, then an attempt is made to draw edge labels even if
+*        it looks like insufficient edge labels can be produced.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If edge labels were drawn, 1 is returned. Otherwise 0 is returned.
+
+*  Notes:
+*     -  Zero is returned if an error has already occurred.
+*/
+
+
+/* Local Variables: */
+   AstFrame *frame;       /* Pointer to current Frame */
+   AstPlotCurveData *cdt; /* Pointer to the AstPlotCurveData for the next tick */
+   LabelList *labellist;  /* Pointer to a ingle list of labels to be plotted */
+   LabelList *ll;         /* Pointer to next label to be plotted */
+   LabelList *llist[2];   /* Pointers to both lists of labels to be plotted */
+   TickInfo *info;        /* Pointer to the TickInfo for the current axis */
+   const char *just[ 2 ]; /* Justification string */
+   const char *text;      /* Pointer to label text */
+   double edgeval;        /* Axis value at the labelled edge */
+   double mindim;         /* Minimum dimension of the plotting area */
+   double oppval;         /* Axis value on the edge opposite to the labels */
+   double tol;            /* Max. distance between a break and the edge */
+   double txtgap;         /* Absolute gap between labels and edges */
+   float *box;            /* Pointer to array of label bounding boxes */
+   float *vxbrk;          /* X component of unit vector at current break */
+   float *vybrk;          /* Y component of unit vector at current break */
+   float *xbrk;           /* X coord. of current break */
+   float *ybrk;           /* Y coord. of current break */
+   float xref;            /* X coordinate at label's reference position */
+   float yref;            /* Y coordinate at label's reference position */
+   int axis;              /* Current axis index */
+   int brk;               /* Current break index */
+   int edge;              /* The edge to be labelled */
+   int edgeax;            /* Index of axis parallel to the labelled edge */
+   int edgelabs;          /* Can edge labels be produced? */
+   int esc;               /* INterpret escape sequences? */
+   int gelid;             /* ID for next graphical element to be drawn */
+   int ii;                /* Index into existing labels */
+   int maxlab;            /* Number of distinct edge labels */
+   int medge[2];          /* No. of distinct edge labels for each axis */
+   int naxlab;            /* Number of edge labels */
+   int near;              /* Draw a label on the near edge? */
+   int nedge[2];          /* No. of edge labels for each axis */
+   int ok;                /* Can the current tick mark be labelled on the edge? */
+   int labfound;          /* Label value has already been used? */
+   int tick;              /* Tick index */
+   int upright;           /* Draw all labels upright? */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   xref = 0.0;
+   yref = 0.0;
+
+/* See if escape sequences in text strings are to be interpreted. */
+   esc = astGetEscape( this );
+
+/* Initialise the returned flag to indicate that edge labels cannot be
+   produced. */
+   edgelabs = 0;
+
+/* Get the minimum dimension of the plotting ares. */
+   mindim = astMIN( this->xhi - this->xlo, this->yhi - this->ylo );
+
+/* Set up the tolerance for curve breaks occuring on an edge of
+   the plotting zone. */
+   tol = 0.005*mindim;
+
+/* First, we get a list of all the labels which can be produced on each
+   axis. The list includes the labels reference position in graphics
+   coordinates, and the index of the major tick value which it
+   represents. We do not yet know whether enough of the grid lines cross
+   the required edge to make it feasable to use edge labelling, so we do
+   not yet draw the labels.
+   =====================================================================*/
+
+/* Initialise pointers to arrays of structures holding information
+   about the labels which can be draw round the edge for both axes. */
+   llist[ 0 ] = NULL;
+   llist[ 1 ] = NULL;
+
+/* Indicate that no labels can yet be drawn on either axis. */
+   nedge[ 0 ] = 0;
+   nedge[ 1 ] = 0;
+
+/* The "nedge" array counts the number of labels on each edge. But some
+   of these labels may be for the same tick mark (if the tick mark curve has
+   more than 1 intersection with the edge). The "medge" array counts the
+   number of *distinct* tick mark labels (i.e. the number of tick mark
+   values which have 1 or more interesections with the edge). */
+   medge[ 0 ] = 0;
+   medge[ 1 ] = 0;
+
+/* For each axis, identify the the usable edge labels. */
+   for( axis = 0; axis < 2; axis++ ){
+
+/* See if labels for this axis are to be drawn upright. */
+      if( astTestLabelUp( this, axis ) ) {
+         upright = astGetLabelUp( this, axis );
+      } else {
+         upright = 1;
+      }
+
+/* Store the required gap between the label text and the axis. */
+      txtgap = astGetNumLabGap( this, axis )*mindim;
+
+/* Get the edge to be labelled with the axis values. Edge 0 is the left hand
+   edge. Edge 1 is the top edge. Edge 2 is the right-hand edge. Edge 3 is
+   the bottom edge. */
+      edge = astGetEdge( this, axis ) % 4;
+      if( edge < 0 ) edge = -edge;
+
+/* If edge labels for the current axis are to go on the left hand edge of
+   the plotting area... */
+      if( edge == 0 ){
+
+/* Choose the justification based on the sign of the text gap. */
+         if( !upright ) {
+            just[ axis ] = "BC";
+         } else if( txtgap > 0.0 ){
+            just[ axis ] = "CR";
+         } else if( txtgap < 0.0 ){
+            just[ axis ] = "CL";
+         } else {
+            just[ axis ] = "CC";
+         }
+
+/* Store the constant X axis value at the edge being labelled. Also store
+   the X value to use for the reference position for all labels. Take into
+   account whether or not the X axis is displayed reversed (i.e. with high
+   X values at the left hand side of the screen ). */
+         if( !this->xrev ){
+            edgeval = this->xlo;
+            oppval = this->xhi;
+            xref = (float)( edgeval - txtgap );
+         } else {
+            edgeval = this->xhi;
+            oppval = this->xlo;
+            xref = (float)( edgeval + txtgap );
+         }
+
+/* Indicate that the "edgeval" value refers to axis 1 (the X axis). */
+         edgeax = 1;
+
+/* Do the same if the labels are to go on the top edge. */
+      } else if( edge == 1 ){
+         if( txtgap > 0.0 ){
+            just[ axis ] = "BC";
+         } else if( txtgap < 0.0 ){
+            just[ axis ] = "TC";
+         } else {
+            just[ axis ] = "CC";
+         }
+
+         if( !this->yrev ){
+            edgeval = this->yhi;
+            oppval = this->ylo;
+            yref = (float)( edgeval + txtgap );
+         } else {
+            edgeval = this->ylo;
+            oppval = this->yhi;
+            yref = (float)( edgeval - txtgap );
+         }
+
+         edgeax = 0;
+
+/* Do the same if the labels are to go on the right-hand edge. */
+      } else if( edge == 2 ){
+
+         if( !upright ) {
+            just[ axis ] = "BC";
+         } else if( txtgap > 0.0 ){
+            just[ axis ] = "CL";
+         } else if( txtgap < 0.0 ){
+            just[ axis ] = "CR";
+         } else {
+            just[ axis ] = "CC";
+         }
+
+         if( !this->xrev ){
+            edgeval = this->xhi;
+            oppval = this->xlo;
+            xref = (float)( edgeval + txtgap );
+         } else {
+            edgeval = this->xlo;
+            oppval = this->xhi;
+            xref = (float)( edgeval - txtgap );
+         }
+
+         edgeax = 1;
+
+/* Do the same if the labels are to go on the bottom edge. */
+      } else {
+         if( txtgap > 0.0 ){
+            just[ axis ] = "TC";
+         } else if( txtgap < 0.0 ){
+            just[ axis ] = "BC";
+         } else {
+            just[ axis ] = "CC";
+         }
+
+         if( !this->yrev ){
+            edgeval = this->ylo;
+            oppval = this->yhi;
+            yref = (float)( edgeval - txtgap );
+         } else {
+            edgeval = this->yhi;
+            oppval = this->ylo;
+            yref = (float)( edgeval + txtgap );
+         }
+
+         edgeax = 0;
+
+      }
+
+/* Get a pointer to the structure containing information describing the
+   positions of the major tick marks along this axis. */
+      info = grid[ axis ];
+
+/* Get a pointer to the structure containing information describing the
+   breaks in the curve which is parallel to the other axis and passes
+   through the first major tick mark. */
+      cdt = cdata[ axis ];
+
+/* Initialise the pointer to the list of text strings to be drawn. */
+      labellist = NULL;
+
+/* Initialise the number of labels which can be placed on the near edge of
+   the plotting zone (some of which may be the same). */
+      naxlab = 0;
+
+/* Initialise the number of distinct labelled tick mark values. */
+      maxlab = 0;
+
+/* Loop round each of the major tick marks on the current axis. */
+      for( tick = 0; cdt && info && tick < info->nmajor; tick++ ){
+
+/* Store pointers to the values giving the position and unit direction
+   vector of the curve at the first break. */
+         xbrk = cdt->xbrk;
+         ybrk = cdt->ybrk;
+         vxbrk = cdt->vxbrk;
+         vybrk = cdt->vybrk;
+
+/* Loop round each of the breaks in the curve which passes through the
+   current major tick mark, and is parallel to the other axis. */
+         ok = 0;
+         for( brk = 0; brk < cdt->nbrk; brk++ ){
+
+/* A label can be produced on the near edge of the plotting zone if the
+   current break occurs on, or close to, the edge, and the curve is not
+   nearly parallel to the axis (limit is 5 degs). */
+            near = ( ( edgeax == 0 &&
+                       fabs( (double) *ybrk - edgeval ) < tol &&
+                       fabs( (double) *vybrk ) > 0.09 ) ||
+                     ( edgeax == 1 &&
+                       fabs( (double) *xbrk - edgeval ) < tol &&
+                       fabs( (double) *vxbrk ) > 0.09 ) );
+
+/* Get the label text. */
+            if( info->labels ) {
+               text = (info->labels)[ tick ];
+            } else {
+               text = NULL;
+            }
+
+/* If a label can be produced, record the information needed to draw the
+   label. */
+            if( near && text ){
+
+               labellist = (LabelList *) astGrow( (void *) labellist, naxlab + 1, sizeof(LabelList) );
+               if ( !astOK ) break;
+
+               if( edgeax == 0 ){
+                  (labellist + naxlab)->index = (double) *xbrk;
+                  (labellist + naxlab)->x = (double) *xbrk;
+                  (labellist + naxlab)->y = (double) yref;
+               } else {
+                  (labellist + naxlab)->index = (double) *ybrk;
+                  (labellist + naxlab)->x = (double) xref;
+                  (labellist + naxlab)->y = (double) *ybrk;
+               }
+
+               (labellist + naxlab)->text = (char *) astStore( NULL, (void *) text, strlen(text) + 1 );
+               (labellist + naxlab)->just = (char *) astStore( NULL, (void *) just[ axis ], strlen(just[ axis ]) + 1 );
+
+/* The up vector depends on which edge is being labelled and whether all
+   labels are being drawn upright or not. */
+               if( edge == 1 || edge == 3 || upright ) {
+                  (labellist + naxlab)->upx = 0.0;
+                  (labellist + naxlab)->upy = 1.0;
+               } else if( edge == 0 ) {
+                  (labellist + naxlab)->upx = -1.0;
+                  (labellist + naxlab)->upy = 0.0;
+               } else {
+                  (labellist + naxlab)->upx = 1.0;
+                  (labellist + naxlab)->upy = 0.0;
+               }
+
+               (labellist + naxlab)->val = (info->ticks)[ tick ];
+               naxlab++;
+
+/* If this label has not already been included in the label list, indicate
+   that we have found another usable label. */
+               labfound = 0;
+               for( ii = 0; ii < naxlab-1; ii++ ) {
+                  if( fabs( (info->ticks)[ tick ] -
+                            (labellist + ii)->val ) < 0.2*info->gap ) {
+                     labfound = 1;
+                     break;
+                  }
+               }
+               if( !labfound ) ok = 1;
+
+            }
+
+/* Increment the pointers to the values giving the position and unit direction
+   vector of the next break. */
+            xbrk++;
+            ybrk++;
+            vxbrk++;
+            vybrk++;
+
+         }
+
+/* If an error has occurred, break out of the loop. */
+         if( !astOK ) break;
+
+/* If one or more labels could be produced for this tick mark value,
+   increment the number of labeled tick marks found. */
+         if( ok ) maxlab++;
+
+/* Get a pointer to the curve through the next major tick mark. */
+         cdt++;
+
+      }
+
+/* If an error has occurred, break out of the loop. */
+      if( !astOK ) break;
+
+/* Store the number of labels for this axis, and the pointer to the
+   drawable labels. */
+      nedge[ axis ] = naxlab;
+      medge[ axis ] = maxlab;
+      llist[ axis ] = labellist;
+   }
+
+/* We now know how many labels would be produced on each axis if edge
+   labelling were to be used. We also know what those labelled values are,
+   and where the labels would be drawn. We now take the decision as to
+   whether there are enough of these labels to make edge labelling
+   feasable. If so, we carry on and draw the labels. There need to be
+   at least 3 labels on each axis for linear tick spacing and 2 for log
+   tick spacing (or a non-zero value supplied for "force")...
+   ================================================================= */
+   if( astOK && ( ( medge[ 0 ] > ( astGetLogTicks( this, 0 ) ? 1 : 2 ) &&
+                    medge[ 1 ] > ( astGetLogTicks( this, 1 ) ? 1 : 2 ) )
+       || force ) ) {
+
+/* Set the returned flag to indicate that edge labelling is being used. */
+      edgelabs = 1;
+
+/* Initialise the pointer to the memory holding the bounding boxes for
+   all labels (used by function Overlap). */
+      box = NULL;
+
+/* Get a pointer to the current Frame in the Plot. */
+      frame = astGetFrame( this, AST__CURRENT );
+
+/* Initialize the id value for graphical element being drawn. */
+      gelid = AST__NUMLAB1_ID;
+
+/* If required, draw the labels for each axis in turn. */
+      for( axis = 0; axis < 2 && ink; axis++ ){
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+         astGrfAttrs( this, gelid, 1, GRF__TEXT, method, class );
+
+/* Plot them. */
+         info = grid[ axis ];
+         PlotLabels( this, esc, frame, axis, llist[ axis ], info->fmt,
+                     nedge[ axis ], &box, method, class, status );
+
+/* Re-establish the original graphical attributes. */
+         astGrfAttrs( this, gelid, 0, GRF__TEXT, method, class );
+
+/* Set up the id for the next graphical element to be drawn. */
+         gelid = AST__NUMLAB2_ID;
+
+      }
+
+/* Free the memory used to hold the bounding boxes. */
+      box = (float *) astFree( (void *) box );
+
+/* Annul the pointer to the Frame. */
+      frame = astAnnul( frame );
+   }
+
+/* Free the memory used to store the label information. */
+   for( axis = 0; axis < 2; axis++ ){
+      ll = llist[ axis ];
+      if( ll ) {
+         for( tick = 0; tick < nedge[ axis ]; tick ++ ) {
+            ll->text = (char *) astFree( (void *) ll->text );
+            ll->just = (char *) astFree( (void *) ll->just );
+            ll++;
+         }
+         llist[ axis ] = (LabelList *) astFree( (void *) llist[ axis ] );
+      }
+   }
+
+/* Return the flag indicating if edge labels were produced. */
+   return edgelabs;
+
+}
+
+static int EdgeCrossings( AstPlot *this, int edge, int axis, double axval,
+                          double *gap, double **cross,
+                          EdgeCrossingsStatics **pstatics, const char *method,
+                          const char *class, int *status ){
+/*
+*
+*  Name:
+*     EdgeCrossings
+
+*  Purpose:
+*     Find all occurrences of a given physical axis value on an edge of the
+*     plotting area.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int EdgeCrossings( AstPlot *this, int edge, int axis, double axval,
+*                        double *gap, double **cross,
+*                        EdgeCrossingsStatics **pstatics,
+*                        const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function finds all occurences of a given physical axis value
+*     along a specified edge of the plotting area. Firstly, a set of evenly
+*     spaced points ("edge samples") are placed along the edge and the
+*     corresponding physical coordinates are found. These physical coordinates
+*     are then offset slightly from their original positions in the direction
+*     of the "other" axis (i.e. index [ 1 - axis ] ), and transformed back
+*     into graphics coordinates. These coordinates give the tangent vector
+*     at each of the edge samples.
+*
+*     To find the crossings, the supplied axis value is compared with the axis
+*     value at each sample in turn, starting from one end of the edge and
+*     working through to the other end. When a crossing is found, linear
+*     interpolation is used between the two adjacent edge samples to find a
+*     more accurate estimate of the crossing. The vector at the crossing
+*     is also estimated by linear interpolation between the vectors at the two
+*     adjacent samples.
+*
+*     This basic algorithm fails if there is a discontinuity in the axis
+*     values along the edge. For instance, if the edge covers a range of
+*     Right Ascension from 23h to 1h, there will be a discontinuity at 0h
+*     at which the RA values suddenly jump from 2*PI to zero. This jump
+*     encompasses all normalised RA values and so every axis value would be
+*     given a crossing at this point. To avoid this, a bad sample is
+*     interposed between the two samples on either side of the
+*     discontinuity. This prevents any crossings from being placed at the
+*     discontinuity.
+*
+*     There is a second problem related to discontinuities. If the supplied
+*     axis value is zero (using the above RA example again), then no
+*     crossings will be found, not only because of the extra bad sample,
+*     but also because the samples will not quite cover the range of axis
+*     values covered by the discontinuity because of the discrete nature
+*     of the samples). To get round this, the sections on either side
+*     of the discontinity are extended by a single sample. These extra
+*     samples are assumed to be conincident with the neighbouring sample,
+*     except that the value for the searched axis is modified to be a
+*     linear extension from the neighbouring samples.
+
+
+*  Parameters:
+*     this
+*        A pointer to the Plot. Supply a NULL pointer to release resources.
+*     edge
+*        The edge of the plotting area to be used. Edge 0 is the left hand
+*        edge. Edge 1 is the top edge. Edge 2 is the right-hand edge. Edge 3
+*        is the bottom edge.
+*     axis
+*        The index of the axis to which "axval" refers.
+*     axval
+*        The physical axis value to be searched for.
+*     gap
+*        Pointer to array of two values holding the gap between major
+*        tick marks on the two axes.
+*     cross
+*        A pointer to the location at which to return a pointer to an
+*        array of doubles holding the crossing information. Each crossing
+*        is described by 4 doubles. The first pair are the graphiucs (x,y)
+*        coordinates of the point on the edge at which the crossing occurs.
+*        The second pair represents a unit vector in graphics coordinates
+*        which is tangential to the curve of constant axis value at the
+*        crossing. The memory allocated within this function to hold this
+*        data should be freed using astFree when no longer needed. If no
+*        crossings are found a NULL pointer is returned.
+*     pstatics
+*        Address of a pointer to a structure holding values for variables
+*        which were statically defined within this function prior to the
+*        thread-safe version of AST. If the pointer is supplied as NULL,
+*        then a new structure is allocated and initialised. Any supplied
+*        structure is freed if a NULL pointer is supplied for "this".
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Return Value:
+*     The number of crossings found.
+
+*  Notes:
+*     -  This function allocates static resource on the first invocation
+*     which should be freed when no more calls are to be made, by making a
+*     final call with a NULL pointer supplied for "this". All other parameters
+*     are then ignored.
+*     -  The static resources are re-initialised each time "edge" or
+*     "axis" changes, and so the calling function should be structure in
+*     order to minimise the number of times these parameter values change.
+*     -  If an error has already occurred, or if this function should
+*     fail for any reason, zero is returned, and a NULL pointer is stored at
+*     "cross".
+
+*/
+
+/* Local Variables: */
+   EdgeCrossingsStatics *statics; /* Structure holding static data */
+   AstMapping *mapping;       /* Pointer to graphics->physical mapping */
+   AstPointSet *pset1a;       /* Physical cooords at offset edge samples */
+   AstPointSet *pset2a;       /* Physical cooords at offset edge samples */
+   AstPointSet *pset3;        /* Physical cooords at offset edge samples */
+   AstPointSet *pset4a;       /* Physical cooords at offset edge samples */
+   double **ptr1a;            /* Pointer to physical coord. data */
+   double **ptr2a;            /* Pointer to physical coord. data */
+   double **ptr3;             /* Pointer to physical coord. data */
+   double **ptr4a;            /* Pointer to physical coord. data */
+   double *data;              /* Pointer to next item of crossing information */
+   double *p1;                /* Pointer to graphics axis with constant value */
+   double *p1a;               /* Pointer to graphics axis with constant value */
+   double *p2;                /* Pointer to graphics axis with varying value */
+   double *p2a;               /* Pointer to graphics axis with varying value */
+   double *q1;                /* Pointer to physical axis being searched */
+   double *q1a;               /* Pointer to physical axis being searched */
+   double *q2;                /* Pointer to other physical axis */
+   double *q2a;               /* Pointer to other physical axis */
+   double *v1;                /* Pointer to vector component on axis 0 */
+   double *v2;                /* Pointer to vector component on axis 1 */
+   double *v1a;               /* Pointer to vector component on axis 0 */
+   double *v2a;               /* Pointer to vector component on axis 1 */
+   double dd;                 /* The gap between edge samples */
+   double diff;               /* Squared differences between adjacent edge samples */
+   double dl2;                /* Squared vector length */
+   double dl;                 /* Vector length */
+   double dx;                 /* Vector X component */
+   double dy;                 /* Vector Y component */
+   double f;                  /* Weight for the current edge sample */
+   double offset;             /* Physical offset */
+   double pp2;                /* Varying graphics axis value at previous sample */
+   double pq1;                /* Required physical axis value at previous sample */
+   double pv1;                /* Previous vector component on axis 0 */
+   double pv2;                /* Previous vector component on axis 1 */
+   double sum;                /* Sum of squared differences between adjacent edge samples */
+   double value;              /* The current graphics axis value */
+   double vx;                 /* Vector component on axis 0 at crossing */
+   double vy;                 /* Vector component on axis 1 at crossing */
+   double z;                  /* Varying graphics axis value at crossing */
+   int i;                     /* Edge sample index */
+   int iter;                  /* Iteration index */
+   int larger;                /* Is current axis value larger than target? */
+   int logticks;              /* Are major ticks logarithmically spaced? */
+   int ncross;                /* No. of crossings */
+   int ndisc;                 /* No. of discontinuities along the edge */
+   int nsum;                  /* Number of values summed in "sum" */
+   int plarger;               /* Was previous axis value larger than target? */
+
+/* Get a pointer to the supplied statics object. */
+   statics = *pstatics;
+
+/* If a NULL Plot pointer has been supplied, release the static
+   resources, and return. */
+   if( !this ){
+      if( statics ){
+         if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+         if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+         if( statics->pset4 ) statics->pset4 = astAnnul( statics->pset4 );
+         if( statics->frame ) statics->frame = astAnnul( statics->frame );
+         *pstatics = astFree( statics );
+      }
+      return 0;
+   }
+
+/* Initialise the number of crossings found, and the pointer to the place
+   to store them. */
+   ncross = 0;
+   *cross = NULL;
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* If no statics structure was supplied, create one now and initialise it. */
+   if( !statics ) {
+      statics = astMalloc( sizeof( EdgeCrossingsStatics ) );
+      if( statics ) {
+         statics->frame = NULL;
+         statics->pset1 = NULL;
+         statics->pset2 = NULL;
+         statics->pset4 = NULL;
+         statics->ptr1 = NULL;
+         statics->ptr2 = NULL;
+         statics->ptr4 = NULL;
+         statics->paxis = -1;
+         statics->pedge = -1;
+         *pstatics = statics;
+      }
+   }
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   pp2 = 0.0;
+   pv1 = 0.0;
+   pv2 = 0.0;
+   plarger = 0;
+
+/* See if the major ticks on the other axis are logarithmically or
+   linearly spaced. */
+   logticks = astGetLogTicks( this, 1 - axis );
+
+/* Ensure that "edge" is in the range 0 - 3. */
+   edge = edge % 4;
+   if( edge < 0 ) edge = -edge;
+
+/* If the edge or axis has changed since the last invocation, or if this is
+   the first invocation, initialise some static data. */
+/* ======================================================================*/
+   if( statics->pedge == -1 || statics->pedge != edge || statics->paxis != axis ){
+
+/* Save the edge and axis. */
+      statics->pedge = edge;
+      statics->paxis = axis;
+
+/* Annull any previous static data objects */
+      if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+      if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+      if( statics->pset4 ) statics->pset4 = astAnnul( statics->pset4 );
+      if( statics->frame ) statics->frame = astAnnul( statics->frame );
+
+/* Store some values so that the code does not need to consider each edge
+   separately. First deal with the left hand edge. */
+      if( edge == 0 ){
+         statics->edgeax = 0;
+         if( this->xrev ){
+            statics->edgeval = this->xhi;
+         } else {
+            statics->edgeval = this->xlo;
+         }
+         statics->edgehi = this->yhi;
+         statics->edgelo = this->ylo;
+
+/* Now deal with the right hand edge. */
+      } else if( edge == 2 ){
+         statics->edgeax = 0;
+         if( this->xrev ){
+            statics->edgeval = this->xlo;
+         } else {
+            statics->edgeval = this->xhi;
+         }
+         statics->edgehi = this->yhi;
+         statics->edgelo = this->ylo;
+
+/* Now deal with the bottom edge. */
+      } else if( edge == 3 ){
+         statics->edgeax = 1;
+         if( this->yrev ){
+            statics->edgeval = this->yhi;
+         } else {
+            statics->edgeval = this->ylo;
+         }
+         statics->edgehi = this->xhi;
+         statics->edgelo = this->xlo;
+
+
+/* Finally deal with the top edge. */
+      } else {
+         statics->edgeax = 1;
+         if( this->yrev ){
+            statics->edgeval = this->ylo;
+         } else {
+            statics->edgeval = this->yhi;
+         }
+         statics->edgehi = this->xhi;
+         statics->edgelo = this->xlo;
+
+      }
+
+/* Get a pointer to the current Frame in the supplied Plot. */
+      statics->frame = astGetFrame( this, AST__CURRENT );
+
+/* Get a pointer to the mapping from base to current Frame in the supplied
+   Plot. */
+      mapping = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Create a PointSet to hold the graphics coordinates at a set of
+   regularly spaced points along the specified edge of the plotting area. */
+      pset1a = astPointSet( EDGETICKS_DIM, 2, "", status );
+      ptr1a = astGetPoints( pset1a );
+
+/* Create a PointSet to hold the corresponding physical coordinates. */
+      pset2a = astPointSet( EDGETICKS_DIM, 2, "", status );
+      ptr2a = astGetPoints( pset2a );
+
+/* Check they can be used. */
+      if( astOK ){
+
+/* Set up the graphics coordinates. */
+         dd = ( statics->edgehi - statics->edgelo )/(double)( EDGETICKS_DIM - 1 );
+         value = statics->edgelo;
+
+         p1 = ptr1a[ statics->edgeax ];
+         p2 = ptr1a[ 1 - statics->edgeax ];
+
+         for( i = 0; i < EDGETICKS_DIM; i++ ){
+            *(p1++) = statics->edgeval;
+            *(p2++) = value;
+            value += dd;
+         }
+      }
+
+/* Transform the graphics coordinates to physical coordinates,
+   *without* normalising them into their normal ranges. */
+      (void) Trans( this, statics->frame, mapping, pset1a, 1, pset2a, 0, method, class, status );
+
+/* Find the RMS step size along the axis. This is used to locate
+   discontinuities along the edge.  Do three rejection iterations. */
+      statics->limit = DBL_MAX;
+      for( iter = 0; iter < 3; iter ++ ){
+         q1 = ptr2a[ axis ];
+         pq1 = AST__BAD;
+         sum = 0.0;
+         nsum = 0;
+
+         for( i = 0; i < EDGETICKS_DIM; i++ ){
+            if( *q1 != AST__BAD && pq1 != AST__BAD ){
+               diff = *q1 - pq1;
+               if( fabs( diff ) < statics->limit ){
+                  sum += diff*diff;
+                  nsum++;
+               }
+            }
+            pq1 = *(q1++);
+         }
+
+         if( nsum == 0 ) break;
+         statics->limit = 3.0*sqrt( sum/(double)nsum );
+      }
+
+/* Now create another PointSet holding positions slightly offset from the
+   physical coordinates at the edge samples. The offset is in the direction
+   of the other physical axis. These positions are used to determine the
+   vector at the crossings. */
+      if( nsum > 0 ){
+         pset3 = astPointSet( EDGETICKS_DIM, 2, "", status );
+         ptr3 = astGetPoints( pset3 );
+
+/* Create a PointSet to hold the corresponding graphics coordinates. */
+         pset4a = astPointSet( EDGETICKS_DIM, 2, "", status );
+         ptr4a = astGetPoints( pset4a );
+
+/* Check they can be used. */
+         if( astOK ){
+
+/* Copy the physical coordinates from PointSet 2 to PointSet 3, offseting
+   them slightly along the other axis. */
+            p1 = ptr2a[ axis ];
+            p2 = ptr2a[ 1 - axis ];
+
+            q1 = ptr3[ axis ];
+            q2 = ptr3[ 1 - axis ];
+
+            offset = 0.2*gap[ 1 - axis ];
+
+            pq1 = AST__BAD;
+
+            for( i = 0; i < EDGETICKS_DIM; i++ ){
+               if( *p1 != AST__BAD && *p2 != AST__BAD ){
+                  if( logticks ) offset = 0.2*(*p2)*( gap[ 1 -axis ] - 1.0 );
+                  *(q2++) = *p2 + offset;
+               } else {
+                  *(q2++) = AST__BAD;
+               }
+               pq1 = *(p1++);
+               *(q1++) = pq1;
+               p2++;
+            }
+
+         }
+
+/* Transform the physical coordinates to graphics coordinates. */
+         (void) Trans( this, NULL, mapping, pset3, 0, pset4a, 0, method, class, status );
+
+/* Check they can be used. */
+         if( astOK ){
+
+/* Modify the contents of PointSet 4 to represent the unit vector in
+   graphics coordinates at each edge sample. */
+            p1 = ptr1a[ 0 ];
+            p2 = ptr1a[ 1 ];
+            q1 = ptr4a[ 0 ];
+            q2 = ptr4a[ 1 ];
+
+            for( i = 0; i < EDGETICKS_DIM; i++ ){
+               if( *p1 != AST__BAD && *p2 != AST__BAD &&
+                   *q1 != AST__BAD && *q2 != AST__BAD ){
+
+                  dx = *q1 - *p1;
+                  dy = *q2 - *p2;
+                  dl2 = dx*dx + dy*dy;
+
+                  if( dl2 > 0.0 ){
+                     dl = sqrt( dl2 );
+                     *q1 = dx/dl;
+                     *q2 = dy/dl;
+                  } else {
+                     *q1 = AST__BAD;
+                     *q2 = AST__BAD;
+                  }
+
+               } else {
+                  *q1 = AST__BAD;
+                  *q2 = AST__BAD;
+               }
+
+               p1++;
+               p2++;
+               q1++;
+               q2++;
+
+            }
+
+         }
+
+/* Annul the PointSet holding offset physical cooridnates. */
+         pset3 = astAnnul( pset3 );
+
+/* Discontinuities in the axis values can cause problems. For instance,
+   using the above PointSets, no tick mark could be put at 0 hours RA
+   because of the discontinuity there. To get round this, 3 extra samples
+   are added at each discontinuity, the first extends the continuous section
+   which ends at the discontinuity, and the third extends the secion which
+   starts at the discontinuity. This results in the two sections overlapping
+   by one sample. The second is placed between these two and has a bad
+   axis value. It prevents crossings from being found in between the values
+   at the ends of the two sections.
+
+   First count the number of discontinuities in the axis values.
+   Discontinuites are defined as steps of more than 9 times the RMS step
+   size. */
+         q1 = ptr2a[ axis ];
+         pq1 = AST__BAD;
+         statics->limit *= 3.0;
+         ndisc = 0;
+
+         for( i = 0; i < EDGETICKS_DIM; i++ ){
+            if( *q1 != AST__BAD && pq1 != AST__BAD ){
+               if( fabs( *q1 - pq1 ) > statics->limit ) ndisc++;
+            }
+            pq1 = *(q1++);
+         }
+
+/* Store the size of the new PointSets holding the extra samples. */
+         statics->dim = EDGETICKS_DIM + 3*ndisc;
+
+/* If there are no discontinuities, just clone the existing PointSets. */
+         if( !ndisc ){
+            statics->pset1 = astClone( pset1a );
+            statics->pset2 = astClone( pset2a );
+            statics->pset4 = astClone( pset4a );
+            statics->ptr1 = astGetPoints( statics->pset1 );
+            statics->ptr2 = astGetPoints( statics->pset2 );
+            statics->ptr4 = astGetPoints( statics->pset4 );
+
+/* Otherwise, create new PointSets. */
+         } else {
+            statics->pset1 = astPointSet( statics->dim, 2, "", status );
+            statics->ptr1 = astGetPoints( statics->pset1 );
+            statics->pset2 = astPointSet( statics->dim, 2, "", status );
+            statics->ptr2 = astGetPoints( statics->pset2 );
+            statics->pset4 = astPointSet( statics->dim, 2, "", status );
+            statics->ptr4 = astGetPoints( statics->pset4 );
+
+/* Set up pointers used to walk through the arrays in the original
+   PointSets and the new PointSets. */
+            p1 = statics->ptr1[ 0 ];
+            p2 = statics->ptr1[ 1 ];
+            q1 = statics->ptr2[ axis ];
+            q2 = statics->ptr2[ 1 - axis ];
+            v1 = statics->ptr4[ 0 ];
+            v2 = statics->ptr4[ 1 ];
+
+            p1a = ptr1a[ 0 ];
+            p2a = ptr1a[ 1 ];
+            q1a = ptr2a[ axis ];
+            q2a = ptr2a[ 1 - axis ];
+            v1a = ptr4a[ 0 ];
+            v2a = ptr4a[ 1 ];
+
+/* Initialise the axis value at the previous sample. */
+            pq1 = AST__BAD;
+
+/* Check all samples in the original PointSets. */
+            for( i = 0; i < EDGETICKS_DIM; i++ ){
+
+/* If this is the first point after a discontinuity... */
+               if( *q1a != AST__BAD && pq1 != AST__BAD ){
+                  if( fabs( *q1a - pq1 ) > statics->limit ) {
+
+/* Insert an extra sample with the coordinates of the previous sample,
+   but with an axis value which is linearly extrapolated from the previous
+   samples. */
+                     *(p1++) = p1a[ 0 ];
+                     *(p2++) = p2a[ 0 ];
+                     *(v1++) = v1a[ -1 ];
+                     *(v2++) = v2a[ -1 ];
+                     *(q2++) = q2a[ -1 ];
+                     if( i > 1 && q1a[ -2 ] != AST__BAD ){
+                        *(q1++) = 2.0*pq1 - q1a[ -2 ];
+                     } else {
+                        *(q1++) = pq1;
+                     }
+
+/* Insert an extra sample with bad coordinates. */
+                     *(p1++) = AST__BAD;
+                     *(p2++) = AST__BAD;
+                     *(v1++) = AST__BAD;
+                     *(v2++) = AST__BAD;
+                     *(q2++) = AST__BAD;
+                     *(q1++) = AST__BAD;
+
+/* Insert an extra sample with the cooridnates of the current sample,
+   but with an axis value which is linearly extrapolated from the
+   subsequent samples. */
+                     *(p1++) = p1a[ -1 ];
+                     *(p2++) = p2a[ -1 ];
+                     *(v1++) = *v1a;
+                     *(v2++) = *v2a;
+                     *(q2++) = *q2a;
+                     if( i < EDGETICKS_DIM - 1 && q1a[ 1 ] != AST__BAD ){
+                        *(q1++) = 2.0*(*q1a) - q1a[ 1 ];
+                     } else {
+                        *(q1++) = pq1;
+                     }
+
+                  }
+
+               }
+
+/* Save the current axis value. */
+               pq1 = *q1a;
+
+/* Copy the current input values to the new PointSets, and move on the next
+   point in the original PointSets. */
+               *(p1++) = *(p1a++);
+               *(p2++) = *(p2a++);
+               *(q1++) = *(q1a++);
+               *(q2++) = *(q2a++);
+               *(v1++) = *(v1a++);
+               *(v2++) = *(v2a++);
+
+            }
+
+         }
+
+/* Anull the original PointSets. */
+         pset4a = astAnnul( pset4a );
+
+/* If all the physical coordinates are bad, indicate this by setting the
+   limiting step size bad. */
+      } else {
+         statics->limit = AST__BAD;
+      }
+
+/* Anull the original PointSets. */
+      pset1a = astAnnul( pset1a );
+      pset2a = astAnnul( pset2a );
+
+/* Annul the pointer to the mapping from base to current Frame. */
+      mapping = astAnnul( mapping );
+
+   }
+
+/* ======================================================================*/
+/* The initialisation has now been done. Check the physical coordinate data
+   can be used. */
+   if( astOK && statics->limit != AST__BAD ){
+
+/* Store pointers to the graphics and physical coordinates at the first
+   edge sample. */
+      p1 = statics->ptr1[ statics->edgeax ];     /* Graphics axis with constant value */
+      p2 = statics->ptr1[ 1 - statics->edgeax ]; /* Graphics axis with varying value */
+      q1 = statics->ptr2[ axis ];       /* Physical axis values to be searched */
+      q2 = statics->ptr2[ 1 - axis ];   /* The other physical axis */
+
+/* Store pointers to the components of the unit vector at the first
+   edge sample. */
+      v1 = statics->ptr4[ 0 ];
+      v2 = statics->ptr4[ 1 ];
+
+/* Inidicate that there is currently no "previous sample". */
+      pq1 = AST__BAD;
+
+/* Check each point in turn... */
+      for( i = 0; i < statics->dim; i++ ){
+
+/* Skip this point if the physical coordinates are undefined. */
+         if( *q1 != AST__BAD && *q2 != AST__BAD ){
+
+/* Get a flag indicating if the required axis value has been exceeded at
+   the current edge sample. */
+            larger = ( *q1 > axval );
+
+/* If the state of this flag has changed since the previous edge sample,
+   and if we know where the previous sample was, we have found a
+   crossing. */
+            if( pq1 != AST__BAD && larger != plarger ){
+
+/* Find the distance from the previous physical axis value to the required
+   axis value, as a fraction of the distance from the previous axis value
+   to the current axis value. Since the flag has changed, we know that the
+   q1 value at this edge sample and the previous one must be different, so
+   we know that the denominator is not zero. */
+               f = ( axval - pq1 )/( *q1 - pq1 );
+
+/* Use linear interpolation to estimate the graphics axis value at the
+   crossing. */
+               if( f != -1.0 ){
+                  z = pp2 + f*( *p2 - pp2 );
+
+/* Use linear interpolation to estimate the two components of the unit
+   vector at the crossing. */
+                  if( *v1 != AST__BAD && pv1 != AST__BAD &&
+                      *v2 != AST__BAD && pv2 != AST__BAD ){
+                     vx = pv1 + f*( *v1 - pv1 );
+                     vy = pv2 + f*( *v2 - pv2 );
+
+/* Normalise the vector. */
+                     dl2 = vx*vx + vy*vy;
+                     if( dl2 > 0.0 ){
+                        dl = sqrt( dl2 );
+                        vx /= dl;
+                        vy /= dl;
+                     } else {
+                        vx = AST__BAD;
+                        vy = AST__BAD;
+                     }
+
+                  } else {
+                     vx = AST__BAD;
+                     vy = AST__BAD;
+                  }
+
+/* Grow the returned array to hold another crossing. */
+                  ncross++;
+                  *cross = (double *) astGrow( (void *) *cross, ncross,
+                                               4*sizeof( double ) );
+
+/* If succesful, store the crossing. */
+                 if( astOK ) {
+
+                    data = *cross + 4*( ncross - 1 );
+                    if( statics->edgeax ){
+                       *(data++) = z;
+                       *(data++) = statics->edgeval;
+                    } else {
+                       *(data++) = statics->edgeval;
+                       *(data++) = z;
+                    }
+                    *(data++) = vx;
+                    *(data++) = vy;
+
+                  }
+
+               }
+
+            }
+
+/* Save the flag for use on the next pass through this loop. */
+            plarger = larger;
+
+         }
+
+/* Save the varying graphics axis value and the required physical axis
+   value at the current edge sample (also save the vector). */
+         pp2 = *p2;
+         pq1 = *q1;
+         pv1 = *v1;
+         pv2 = *v2;
+
+/* Point to the next edge sample. */
+         p1++;
+         p2++;
+         q1++;
+         q2++;
+         v1++;
+         v2++;
+
+      }
+
+   }
+
+/* If an error has occurred, free the array holding the crossings, and
+   indicate that there are zero corssing. */
+   if( !astOK ) {
+      *cross = (double *) astFree( (void *) *cross );
+      ncross = 0;
+   }
+
+/* Return the answer. */
+   return ncross;
+
+}
+
+int astFindEscape_( const char *text, int *type, int *value, int *nc, int *status ){
+/*
+*+
+*  Name:
+*     astFindEscape
+
+*  Purpose:
+*     Check if a string starts with a graphics escape sequence.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int astFindEscape( const char *text, int *type, int *value, int *nc )
+
+*  Description:
+*     This function returns a flag indiciating if the first character in
+*     the supplied string is the start of a graphics escape sequence. If
+*     so, the type and associated value (if any) of the escape sequence
+*     are returned in "type" and "value", and the number of characters
+*     occupied by the escape sequence is returned in "nc". If the
+*     supplied text string does not begin with an escape sequence, the
+*     number of characters before the first escape sequence is returned in
+*     "nc" (the length of the string is returned in "nc" if the string
+*     contains no escape sequences).
+*
+*     This function can be used by grf modules which wish to implement
+*     interpretation of escape sequences internally, rather than allowing the
+*     Plot class to do the interpretation.
+
+*  Parameters:
+*     text
+*        Pointer to the string to be checked.
+*     type
+*        Pointer to a location at which to return the type of escape
+*        sequence. Each type is identified by a symbolic constant defined
+*        in grf.h. The returned value is undefined if the supplied text
+*        does not begin with an escape sequence.
+*     value
+*        Pointer to a lcation at which to return the integer value
+*        associated with the escape sequence. All usable values will be
+*        positive. Zero is returned if the escape sequence has no associated
+*        integer. A value of -1 indicates that the attribute identified by
+*        "type" should be reset to its "normal" value (as established using
+*        the astGAttr function, etc). The returned value is undefined if the
+*        supplied text does not begin with an escape sequence.
+*     nc
+*        Pointer to a location at which to return the number of
+*        characters read by this call. If the text starts with an escape
+*        sequence, the returned value will be the number of characters in
+*        the escape sequence. Otherwise, the returned value will be the
+*        number of characters prior to the first escape sequence, or the
+*        length of the supplied text if no escape sequence is found.
+
+*  Returned Value:
+*     A non-zero value is returned if the supplied text starts with a
+*     graphics escape sequence, and zero is returned otherwise.
+
+*  Escape Sequences:
+*     Escape sequences are introduced into the text string by a percent
+*     "%" character. The following escape sequences are currently recognised
+*     ("..." represents a string of one or more decimal digits):
+*
+*       %%      - Print a literal "%" character.
+*
+*       %^...+  - Draw subsequent characters as super-scripts. The digits
+*                 "..." give the distance from the base-line of "normal"
+*                 text to the base-line of the super-script text, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text.
+*       %^+     - Draw subsequent characters with the normal base-line.
+*
+*       %v...+  - Draw subsequent characters as sub-scripts. The digits
+*                 "..." give the distance from the base-line of "normal"
+*                 text to the base-line of the sub-script text, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text.
+*
+*       %v+     - Draw subsequent characters with the normal base-line
+*                 (equivalent to %^+).
+*
+*       %>...+  - Leave a gap before drawing subsequent characters.
+*                 The digits "..." give the size of the gap, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text.
+*
+*       %<...+  - Move backwards before drawing subsequent characters.
+*                 The digits "..." give the size of the movement, scaled
+*                 so that a value of "100" corresponds to the height of
+*                 "normal" text.
+*
+*       %s...+  - Change the Size attribute for subsequent characters. The
+*                 digits "..." give the new Size as a fraction of the
+*                 "normal" Size, scaled so that a value of "100" corresponds
+*                 to 1.0;
+*
+*       %s+     - Reset the Size attribute to its "normal" value.
+*
+*       %w...+  - Change the Width attribute for subsequent characters. The
+*                 digits "..." give the new width as a fraction of the
+*                 "normal" Width, scaled so that a value of "100" corresponds
+*                 to 1.0;
+*
+*       %w+     - Reset the Size attribute to its "normal" value.
+*
+*       %f...+  - Change the Font attribute for subsequent characters. The
+*                 digits "..." give the new Font value.
+*
+*       %f+     - Reset the Font attribute to its "normal" value.
+*
+*       %c...+  - Change the Colour attribute for subsequent characters. The
+*                 digits "..." give the new Colour value.
+*
+*       %c+     - Reset the Colour attribute to its "normal" value.
+*
+*       %t...+  - Change the Style attribute for subsequent characters. The
+*                 digits "..." give the new Style value.
+*
+*       %t+     - Reset the Style attribute to its "normal" value.
+*
+*       %h+     - Remember the current horizontal position (see "%g+")
+*
+*       %g+     - Go to the horizontal position of the previous "%h+" (if any).
+*
+*       %-      - Push the current graphics attribute values onto the top of
+*                 the stack (see "%+").
+*
+*       %+      - Pop attributes values of the top the stack (see "%-"). If
+*                 the stack is empty, "normal" attribute values are restored.
+
+*  Notes:
+*     -  Zero is returned if an error has already occurred.
+*-
+*/
+
+/* Local Variables: */
+   int result;
+   const char *a;
+   const char *b;
+   int nd;
+   const char *perc;
+
+/* Initialise */
+   result = 0;
+   *type = GRF__ESPER;
+   *value = 0;
+   *nc = 0;
+   perc = NULL;
+
+/* Check inherited status and supplied pointer. */
+   if( !astOK || !text ) return result;
+
+/* Loop round, looking for percent signs. Break out of the loop when a
+   complete escape sequence has been found and read, leaving the "b" pointer
+   pointing to the first character following the escape sequence. */
+   b = NULL;
+   a = text;
+   while( ( a = strchr( a, '%' ) ) ) {
+      perc = a;
+
+/* Compare the following character to each known escape sequence type. */
+      a++;
+      if( *a == '%') {
+         *type = GRF__ESPER;
+         b = a + 1;
+         break;
+
+      } else if( *a == '^') {
+         *type = GRF__ESSUP;
+
+      } else if( *a == 'v') {
+         *type = GRF__ESSUB;
+
+      } else if( *a == '>') {
+         *type = GRF__ESGAP;
+
+      } else if( *a == '<') {
+         *type = GRF__ESBAC;
+
+      } else if( *a == 's') {
+         *type = GRF__ESSIZ;
+
+      } else if( *a == 'w') {
+         *type = GRF__ESWID;
+
+      } else if( *a == 'f') {
+         *type = GRF__ESFON;
+
+      } else if( *a == 'c') {
+         *type = GRF__ESCOL;
+
+      } else if( *a == 'g') {
+         *type = GRF__ESG;
+
+      } else if( *a == 'h') {
+         *type = GRF__ESH;
+
+      } else if( *a == 't') {
+         *type = GRF__ESSTY;
+
+      } else if( *a == '-') {
+         *type = GRF__ESPSH;
+         b = a + 1;
+         break;
+
+      } else if( *a == '+') {
+         *type = GRF__ESPOP;
+         b = a + 1;
+         break;
+
+/* If the next character is illegal, skip to the next percent sign. */
+      } else {
+         continue;
+      }
+
+/* The escape sequence looks legal so far, so move on to the next
+   character (if any). */
+      if( *(++a) ){
+
+/* If the next character is a "+" sign, the attribute needs to be reset
+   to its "normal" value. Indicate this by returning a value of "-1" (all
+   usable values will be positive). */
+         if( *a == '+' ) {
+            *value = -1;
+            b = a + 1;
+            break;
+
+/* Otherwise, to be a legal escape sequence, this character must be the
+   first in a sequence of digits, terminated by a "+" sign.*/
+         } else if( (nd = 0, astSscanf( a, "%d%n+", value, &nd ))) {
+            b = a + nd + 1;
+            break;
+         }
+      }
+   }
+
+/* Was a usable escape sequence found at the start of the supplied text?
+   If so, return a function value of 1 and store the number of characters in
+   the escape sequence. */
+   if( b && perc == text ) {
+      result = 1;
+      *nc = b - perc;
+
+/* Otherwise, return the preset function value of zero. If an escape
+   sequence was found later in the text, return the number of characters
+   prior to the escape sequence. */
+   } else if( b ) {
+      *nc = perc - text;
+
+/* Otherwise, if no escape sequence was found, return the length of the
+   supplied text. */
+   } else {
+      *nc = strlen( text );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int FindMajTicks( AstMapping *map, AstFrame *frame, int axis,
+                         double refval, double width, double gap, double *cen, int ngood,
+                         double *data, double **tick_data, int *status ){
+/*
+*  Name:
+*     FindMajTicks
+
+*  Purpose:
+*     Place the major tick marks for a physical coordinate axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int FindMajTicks( AstMapping *map, AstFrame *frame, int axis,
+*                       double refval, double width, double gap, double *cen, int ngood,
+*                       double *data, double **tick_data )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The caller supplies an array of axis values (non-normalized), sorted
+*     into ascending order (with any bad values at the end), together with
+*     the gap size for the axis. The array of axis values is assumed to cover
+*     the entire range which the axis can take within the plotting zone. The
+*     first tick mark is placed just below the smallest axis value, at a
+*     position which is an integral number of gaps away from the value
+*     supplied in "cen" (if a value of AST__BAD is supplied for "cen" then
+*     "cen = 0.0" is assumed). Notionally, tick marks are then placed at
+*     intervals given by "gap" all the way upto, and just beyond, the
+*     largest axis value. However, it could be that large sections of the
+*     axis are not actually present within the plotting zone. For instance,
+*     an RA axis covering the two hour range from 23h to 1h (centred on
+*     0h), will have some values at zero and some at 23.999.., but there
+*     will be a large range inbetween these limits which is not represented
+*     in the plotting area (i.e. the 22h range from 1h to 23h centred on
+*     12h). For this reason, tick marks are removed if there are no axis
+*     values inbetween the tick mark and either of its neighbours. However,
+*     small "holes" in the axis coverage are allowed, and ticks marks are
+*     returned covering such small holes. Extra tick marks are also placed
+*     at each end of the range to guard against the supplied array of axis
+*     values not entirely covering the range of axis values in the plotting
+*     area.
+*
+*     For SkyFrames, positions which have latitude values outside the
+*     normal ranges are ignored. Longitude ranges are not checked to
+*     avoid problems with CAR projections.
+*
+*     The returned tick mark values are placed into their primary domain
+*     using the Norm1 method, but are NOT normalised using the astNorm
+*     method for the supplied Frame. Duplicate tick marks values are
+*     removed from the returned list.
+
+*  Parameters:
+*     map
+*        Mapping from the Plot Base Frame to Plot Current Frame.
+*     frame
+*        Pointer to the Frame.
+*     axis
+*        Zero-based index of the axis being used.
+*     refval
+*        Value to use for the other axis (index [1-axis]) when placing
+*        the tick mark values into their primary domain.
+*     width
+*        Range of used values on the other axis (index [1-axis]).
+*     gap
+*        The supplied value for the gaps between ticks on the axis.
+*     cen
+*        Pointer to the supplied axis value at which to put a central tick.
+*        Other ticks will be placed evenly on either side of this tick. If
+*        AST__BAD is provided, a value will be used which would put a tick
+*        at an axis value of zero. The used value is returned.
+*     ngood
+*        The number of good values in the array pointer to by "data" (i.e.
+*        values not equal to AST__BAD).
+*     data
+*        A pointer to an array holding sorted axis values (non-normalized)
+*        covering the entire plotting area.
+*     tick_data
+*        A pointer to a place at which to store a pointer to an array
+*        holding the returned tick mark values for the axis.
+
+*  Returned Value:
+*     The number of major tick mark values stored in the array pointer to
+*     by "*tick_data".
+
+*  Notes:
+*     -  If an error has already occurred, or if this function should fail
+*     for any reason, then a NULL pointer is returned in "tick_data", and zero
+*     is returned for the function value.
+*/
+
+/* Local Variables: */
+   double *r;         /* Pointer to next tick value to be read */
+   double *ticks;     /* Pointer to the axis values at the major tick marks */
+   double *w;         /* Pointer to last tick value to be written */
+   double bot;        /* Lowest axis value to be displayed */
+   double centre;     /* The axis value at the first tick mark */
+   double delta;      /* A safe distance from an axis limit */
+   double f;          /* The nearest acceptable tick mark index */
+   double tmp;        /* Temporary storage */
+   double top;        /* Highest axis value to be displayed */
+   int inc;           /* This times increase in nticks */
+   int k;             /* Tick mark index */
+   int linc;          /* Last times increase in nticks */
+   int lnfill;        /* Last used value for nfill */
+   int nfill;         /* No of tick marks to extend by at edges of coverage */
+   int nsame;         /* Number of equal inc values there have been */
+   int nticks;        /* Number of major tick marks used */
+   int ntnew;         /* This times new value of nticks */
+   int use_nfill;     /* nfill value which started this run of equal inc values */
+
+/* Initialise the returned pointer. */
+   *tick_data = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   nsame = 0;
+   use_nfill = 0;
+
+/* Decide where to put the first major tick. Use any value supplied by
+   the caller. Otherwise put it an integral number of gaps away from the
+   origin. This would result in the origin being at a major tick mark. */
+   if( cen && *cen != AST__BAD ) {
+      centre = *cen;
+   } else {
+      centre = astCentre( frame, axis, data[ 0 ], gap );
+      if( cen ) *cen = centre;
+   }
+
+/* Find the number of candidate tick marks assuming an nfill value of 0. */
+   nfill = 0;
+   nticks = FindMajTicks2( nfill, gap, centre, ngood, data, &ticks, status );
+
+/* Loop round increasing the nfill value until an unreasonably large value
+   of nfill is reached. The loop will exit early via a break statement when
+   all small holes in the axis coverage are filled in. */
+   lnfill = nfill;
+   linc = -100000;
+   while( nfill < 100 && astOK ){
+
+/* Increment the number of ticks added as "padding" at the edges of any
+   gaps in the coverage of axis values. */
+      nfill++;
+
+/* Form a new set of tick mark values using this new nfill value */
+      ticks = (double *) astFree( (void *) ticks );
+      ntnew = FindMajTicks2( nfill, gap, centre, ngood, data, &ticks, status );
+
+/* We need to know if the rate of increase of nticks has settled down to
+   a constant value. Inititially increasing nfill will cause the total
+   number of ticks (nticks) to increase rapidly. But this rate of
+   increase will get less as any small gaps in axis coverage are filled in.
+   We break out of the while loop when the rate of increase has settled
+   down to a constant value (indicating that only very large holes are left
+   in the axis coverage). Find the increase in the number of ticks caused by
+   the increase in the nfill value made in this loop. If this increase is the
+   same as the increase caused by the previous loop, increment the number of
+   equal increases there have been. If the increase is different to last time,
+   reset the number of equal increases to zero. */
+      inc = ntnew - nticks;
+      if( inc == linc ) {
+         nsame++;
+      } else {
+         nsame = 0;
+         use_nfill = nfill;
+      }
+
+/* If the past 3 increases in nfill has not caused any change in the rate
+   of increase of nticks, then re-create the ticks for the value of nfill
+   which started the current run of equal increment values, and leave the
+   loop. */
+      if( nsame == 3 ) {
+         ticks = (double *) astFree( (void *) ticks );
+         nticks = FindMajTicks2( use_nfill, gap, centre, ngood, data, &ticks, status );
+         break;
+      }
+
+/* Save this times values for use in the next loop. */
+      linc = inc;
+      nticks = ntnew;
+   }
+
+/* Remove ticks which are not within the axis ranges to be displayed.
+   Ticks which are very close to the limit are moved to a safe (but
+   visually negligable) distance away from the limit). */
+   bot = astGetBottom( frame, axis );
+   top = astGetTop( frame, axis );
+   if( bot > top ) {
+      tmp = top;
+      top = bot;
+      bot = tmp;
+   }
+   delta = 0.05*gap;
+   r = ticks;
+   for( k = 0; k < nticks; k++ ){
+      if( *r != AST__BAD ) {
+         if( fabs( *r - bot ) < delta ) {
+            *r = bot + delta;
+         } else if( fabs( *r - top ) < delta ) {
+            *r = top - delta;
+         } else if( *r < bot || *r > top ) {
+            *r = AST__BAD;
+         }
+      }
+      r++;
+   }
+
+/* Use the Mapping to place each tick mark value in its primary domain.
+   This is a sort of normalization, similar but different to that performed
+   by the astNorm method. */
+   Norm1( map, axis, nticks, ticks, refval, width, status );
+
+/* Check for success. */
+   if( astOK ){
+
+/* Ensure that all ticks marks are offset from the "centre" value by an
+   integer multiple of the gap size. This is done by changing each tick
+   value to the closest acceptable value. Also ensure that values close to
+   zero (i.e. less than 1E-10 of the gap size) are set exactly to zero. */
+      r = ticks;
+      for( k = 0; k < nticks; k++ ){
+         if( *r != AST__BAD ) {
+            f = floor( 0.5 + ( *r - centre )/gap );
+            *r = f*gap + centre;
+            if( fabs( *r ) < 1.0E-10*gap ) *r = 0.0;
+            r++;
+         } else {
+            r++;
+         }
+      }
+
+/* Sort the tick values into increasing order. */
+      qsort( (void *) ticks, (size_t) nticks, sizeof(double), Compared );
+
+/* Remove any duplicate or BAD tick values by shuffling the higher unique
+   values down to over-write them. We subtract the centre value of both
+   tick values before comparing them for equality in order to avoid
+   unnecessarily removing tick marks in high precsion data. */
+      r = ticks + 1;
+      w = ticks;
+      for( k = 1; k < nticks && astOK; k++ ){
+         if( *r != AST__BAD && !astEQUALS( *r-centre, *w-centre, 1.0E8 ) ){
+            w++;
+            *w = *r;
+         }
+         r++;
+      }
+
+/* Modify the number of ticks to exclude the duplicate ones. */
+      nticks = (int) ( w - ticks ) + 1;
+
+   }
+
+/* If an error has occurred, free the memory holding the major tick mark
+   values, and indicate that zero tick marks have been found. */
+   if( !astOK ){
+      ticks = (double *) astFree( (void *) ticks );
+      nticks = 0;
+   }
+
+/* Store the pointer to the major tick mark values. */
+   *tick_data = ticks;
+
+/* Return the number of major ticks. */
+   return nticks;
+
+}
+static int FindMajTicks2( int nfill, double gap, double centre, int ngood,
+                          double *data, double **tick_data, int *status ){
+/*
+*  Name:
+*     FindMajTicks2
+
+*  Purpose:
+*     Find candidate major tick marks for FindMajTicks.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int FindMajTicks2( int nfill, double gap, double centre, int ngood,
+*                        double *data, double **tick_data, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     A service routine for function FindMajTicks.
+
+*  Parameters:
+*     nfill
+*        Number of tick marks to extend by at edges of coverage
+*     gap
+*        The supplied value for the gaps between ticks on the axis.
+*     centre
+*        The supplied axis value at which to put a central tick.
+*     ngood
+*        The number of good values in the array pointer to by "data" (i.e.
+*        values not equal to AST__BAD).
+*     data
+*        A pointer to an array holding sorted axis values covering the
+*        entire plotting area.
+*     tick_data
+*        A pointer to a place at which to store a pointer to an array
+*        holding the returned tick mark values for the axis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of major tick mark values stored in the array pointer to
+*     by "*tick_data".
+
+*  Notes:
+*     -  If an error has already occurred, or if this function should fail
+*     for any reason, then a NULL pointer is returned in "tick_data", and zero
+*     is returned for the function value.
+*/
+
+/* Local Variables: */
+   double *ticks;     /* Pointer to the axis values at the major tick marks */
+   int i;             /* Index of current axis value */
+   int j;             /* Index of filled in tick */
+   int k;             /* Tick mark index */
+   int klast;         /* Index of the previous tick mark */
+   int nticks;        /* Number of major tick marks used */
+
+/* Initialise the returned pointer. */
+   *tick_data = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   nticks = 0;
+
+/* Reserve memory to hold a reasonable number of tick mark axis values.
+   This memory is later extended as necessary. */
+   ticks = (double *) astMalloc( sizeof(double)*( 6*nfill + 14 ) );
+
+/* Check that the pointer can be used. */
+   if( astOK ){
+
+/* Put the first tick marks just below the lowest axis value (in case
+   the grid did not sample the entire range of the axis). */
+      k = floor( ( data[ 0 ] - centre )/gap );
+
+      for ( i = 0; i < nfill; i++ ){
+         ticks[ i ] = gap*(double)( k - nfill + i ) + centre;
+      }
+      ticks[ nfill ] = gap*(double)( k ) + centre;
+
+/* Initialise the number of major tick marks found so far. */
+      nticks = nfill + 1;
+
+/* Loop round each of the remaining good ordered axis values. */
+      klast = k;
+      for( i = 1; i < ngood && astOK; i++ ) {
+
+/* Find the tick marks enclosing the axis value. The tick mark placed at
+   "centre" is called tick mark zero, and tick marks are indexed (positive
+   or negative) from an origin at "centre". Find the index of the more
+   negative of the two tick marks enclosing the axis value. */
+         k = floor( ( data[ i ] - centre )/gap );
+
+/* Ensure that the tick marks enclosing the current axis value are used.
+   Some extra tick marks are used at the start and end of any gaps in
+   the axis coverage. This is done to "fill in" small holes caused by the
+   grid of physical coordinate values not completely covering the
+   plotting area. Large holes, such as occur on an RA axis covering the 2
+   hour range from 23 hours to 1 hour are left without any tick marks in
+   them (the "hole" in this case is the 22 hours range from 1 hour to 23
+   hours). */
+         for( j = 0; j < nfill + 1; j++ ){
+            if( k - klast > nfill + 2 - j ) {
+               ticks = (double *) astGrow( ticks, nticks + 1, sizeof( double ) );
+               if( astOK ) ticks[ nticks++ ] =
+                                 gap*(double)( klast + nfill + 1 - j ) + centre;
+            }
+            if( k - klast > nfill - j ) {
+               ticks = (double *) astGrow( ticks, nticks + 1, sizeof( double ) );
+               if( astOK ) ticks[ nticks++ ] =
+                                 gap*(double)( k - nfill + j ) + centre;
+            }
+         }
+
+/* Save the index of the current tick mark. */
+         klast = k;
+
+      }
+
+/* Add extra tick marks beyond the end in case the grid did not sample
+   the entire range of the axis. */
+      ticks = (double *) astGrow( ticks, nticks + nfill + 1, sizeof( double ) );
+      for( i = 0; i < nfill && astOK; i++ ){
+         ticks[ nticks++ ] = gap*(double)( klast + i + 1 ) + centre;
+      }
+
+   }
+
+/* If an error has occurred, free the memory holding the major tick mark
+   values, and indicate that zero tick marks have been found. */
+   if( !astOK ){
+      ticks = (double *) astFree( (void *) ticks );
+      nticks = 0;
+   }
+
+/* Store the pointer to the major tick mark values. */
+   *tick_data = ticks;
+
+/* Return the number of major ticks. */
+   return nticks;
+
+}
+
+static int FindDPTZ( AstFrame *fr, int axis, const char *fmt,
+                      const char *text, int *ndp, int *ntz, int *status ) {
+/*
+*  Name:
+*     FindDPTZ
+
+*  Purpose:
+*     Find the number of decimal places and trailing zeros in a label.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int FindDPTZ( AstFrame *fr, int axis, const char *fmt,
+*                   const char *text, int *ndp, int *ntz, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The supplied label is split into fields using the astFields method of
+*     the supplied frame. The number of decimal places in the last
+*     field is returned in *ndp, and the total number of trailing zeros
+*     (excluding exponents) is returned in *ntz.
+
+*  Parameters:
+*     fr
+*        The frame.
+*     axis
+*        The axis index to which the label applies.
+*     fmt
+*        The format string used to format the label.
+*     text
+*        The text of the label.
+*     ndp
+*        Pointer to an int in which to return the number of decimal
+*        places in the final field.
+*     ntz
+*        Pointer to an int in which to return the number of trailing zeros.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if and only if a non-zero digit is found in any field.
+
+*/
+
+/* Local Constants: */
+#define MAXFLD 10
+
+/* Local Variables: */
+   char *fields[ MAXFLD ];
+   const char *a;
+   const char *dot;
+   const char *ff;
+   double junk;
+   int fnc;
+   int i;
+   int j;
+   int l;
+   int mxnd;
+   int nc[ MAXFLD ];
+   int nf;
+   int result;
+
+/* Initialise */
+   *ndp = 0;
+   *ntz = 0;
+   result = 0;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Split the label up into fields. */
+   nf = astFields( fr, axis, fmt, text, MAXFLD, fields, nc, &junk );
+   if( nf > 0 ) {
+
+/* Search the last fields (assumed to be the least significant) for a
+   decimal point. */
+      ff = fields[ nf - 1 ];
+      fnc = nc[ nf - 1 ];
+      dot = strchr( ff, '.' );
+      if( dot && ( ff - dot >= fnc ) ) dot = NULL;
+
+/* Find the number of digits following the decimal point. */
+      if( dot ) {
+         *ndp = strspn( dot + 1, "0123456789" );
+         mxnd = fnc - ( dot - ff ) - 1;
+         if( *ndp > mxnd ) *ndp =  mxnd;
+      } else {
+         *ndp = 0;
+      }
+
+/* Loop through all the fields, from least significant to most significant,
+   counting the number of trailing zeros. */
+      *ntz = 0;
+      for( i = nf - 1; i >= 0; i-- ) {
+         l = strspn( fields[ i ], "-+0123456789." );
+         if( l > nc[ i ] ) l = nc[ i ];
+         a = fields[ i ] + l - 1;
+         for( j = l - 1; j >= 0; j--,a-- ){
+            if( *a == '0' ) {
+               (*ntz)++;
+            } else if( isdigit( *a ) ) {
+               result = 1;
+               break;
+            }
+         }
+         if( j >= 0 ) break;
+      }
+   }
+
+/* Return the result. */
+   return result;
+
+/* Undefine local constants: */
+#undef MAXFLD
+
+}
+
+static int FindString( int n, const char *list[], const char *test,
+                       const char *text, const char *method,
+                       const char *class, int *status ){
+/*
+*  Name:
+*     FindString
+
+*  Purpose:
+*     Find a given string within an array of character strings.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int FindString( int n, const char *list[], const char *test,
+*                     const char *text, const char *method, const char *class,
+*                     int *status )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function identifies a supplied string within a supplied
+*     array of valid strings, and returns the index of the string within
+*     the array. The test option may not be abbreviated, but case is
+*     insignificant.
+
+*  Parameters:
+*     n
+*        The number of strings in the array pointed to be "list".
+*     list
+*        A pointer to an array of legal character strings.
+*     test
+*        A candidate string.
+*     text
+*        A string giving a description of the object, parameter,
+*        attribute, etc, to which the test value refers.
+*        This is only for use in constructing error messages. It should
+*        start with a lower case letter.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The index of the identified string within the supplied array, starting
+*     at zero.
+
+*  Notes:
+*     -  A value of -1 is returned if an error has already occurred, or
+*     if this function should fail for any reason (for instance if the
+*     supplied option is not specified in the supplied list).
+
+*/
+
+/* Local Variables: */
+   int ret;                /* The returned index */
+
+/* Check global status. */
+   if( !astOK ) return -1;
+
+/* Compare the test string with each element of the supplied list. Leave
+   the loop when a match is found. */
+   for( ret = 0; ret < n; ret++ ) {
+      if( !Ustrcmp( test, list[ ret ], status ) ) break;
+   }
+
+/* Report an error if the supplied test string does not match any element
+   in the supplied list. */
+   if( ret >= n ) {
+      astError( AST__OPT, "%s(%s): Illegal value '%s' supplied for %s.", status,
+                method, class, test, text );
+      ret = -1;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static char *FindWord( char *ptr, const char *d, const char **p, int *status ) {
+/*
+*  Name:
+*     FindWord
+
+*  Purpose:
+*     Return a copy of the next word in a supplied string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     char *FindWord( char *ptr, const char *d, const char **p, int *status )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function locates the start and end of the first word in the
+*     string pointed to by *p, and returns a copy of the word. The pointer
+*     *p is modified to point to the start of the following word (if any).
+*     The characters which delimit words are supplied in string "d".
+
+*  Parameters:
+*     ptr
+*        A pointer to a character string in which to store the returned
+*        word. The memory holding this string should have been allocated
+*        using one of the functions in the AST "memory" module. The memory
+*        area will be modified in size to fit the returned word. A NULL
+*        pointer may be supplied if no memory has yet been allocated.
+*        Any memory pointed to by ptr is freed if a NULL pointer is
+*        returned by this function (i.e. if no word is found).
+*     d
+*        A string holding the characters which are to be used as word
+*        delimiters.
+*     p
+*        The address of a character string pointer. On entry, this pointer
+*        identifies the start of the string to be searched. On exit, it is
+*        modified to point to the start of the following word. It is
+*        returned NULL if there are no more words.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated character string holding the
+*     next word, or NULL if no word could be found.
+
+*/
+
+/* Local Variables: */
+   const char *a, *b, *c;
+   char *ret;
+   int nc;
+
+/* Free any allocated memory and return if any of the supplied pointers
+   (except ptr) is NULL, or if an error has occurred. */
+   if( !astOK || !d || !p || !*p ) {
+      (void) astFree( (void *) ptr );
+      return NULL;
+   }
+
+/* Get a pointer to the first character which is not in "d". Terminate
+   the loop if a null character is encountered. */
+   a = *p;
+   while( *a && strchr( d, (int) *a ) ) a++;
+
+/* Get a pointer to the next character which is in "d". Terminate
+   the loop if a null character is encountered. */
+   b = a;
+   while( *b && !strchr( d, (int) *b ) ) b++;
+
+/* Get a pointer to the next character which is not in "d". Terminate
+   the loop if a null character is encountered. */
+   c = b;
+   while( *c && strchr( d, (int) *c ) ) c++;
+
+/* Adjust the supplied pointer so that it points to the start of the next
+   word. */
+   if( *c ){
+      *p = c;
+   } else {
+      *p = NULL;
+   }
+
+/* Get a null-terminated copy of the word between a and b. */
+   nc = b - a;
+   if( nc > 0 ) {
+      ret = (char *) astStore( (void *) ptr, (void *) a, (size_t) (nc + 1) );
+      ret[ nc ] = 0;
+   } else {
+      ret = astFree( (void *) ptr );
+   }
+
+   return ret;
+}
+
+static const char *SplitValue( AstPlot *this, const char *value, int axis,
+                               int *split, int *status ) {
+/*
+*  Name:
+*     FormatValue
+
+*  Purpose:
+*     Format a coordinate value for a Frame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     const char *SplitValue( AstPlot *this, const char *value,
+*                                    int axis, int *split )
+
+*  Class Membership:
+*     Plot member function
+
+*  Description:
+*     This function splits long formatted values (such as the date/time
+*     format produced by the TimeFrame class) if possible onto two lines
+*     by inclusion of Plot escape sequences.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     value
+*        The formatted coordinate value.
+*     axis
+*        Indicates whether or not short lines should be split by
+*        including a blank first line. If zero, and if "*split" is non-zero,
+*        then short lines are put onto the second line,and the first line
+*        is blank.
+*     split
+*        Pointer to an integer that controls behaviour:
+*
+*        0 - Split the line if it is too long, and return a value of +1
+*            in *split.
+*        1 - Split the line even if it does not need splitting, making
+*            the first line blank and the second line containing all the
+*            supplied text (*split is unchanged on exit).
+
+*  Returned Value:
+*     A pointer to a static buffer containing a null-terminated string
+*     holding the (possibly split) formatted value. This will be a copy of
+*     the supplied pointer if the string does not need to be split.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   char *d;
+   const char *result;
+   float rsp;
+   int aft_end;
+   int aft_start;
+   int bef_end;
+   int bef_start;
+   int i;
+   int id;
+   int idmin;
+   int imin;
+   int l;
+   int naft;
+   int nbef;
+   int nlong;
+   int nshort;
+   int nsp;
+
+/* Initialise */
+   result = value;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Do nothing more if the formatted value already contains graphical
+   escape sequences, or if graphical escapes sequences are not being
+   interpreted. */
+   if( value && astGetEscape( this ) && !HasEscapes( value, status ) ) {
+
+/* Attempt to find a space close to the centre of the formatted string. */
+      l = strlen( value );
+      idmin = 2*l;
+      imin = -1;
+      for( i = 0; i < l; i++ ) {
+         if( isspace( value[ i ] ) ) {
+            id = abs( i - l/2 );
+            if( id < idmin ) {
+               idmin = id;
+               imin = i;
+            }
+         }
+      }
+
+/* We split the line if previous lines have been split (i.e. if *split was
+   non-zero on entry) or if this line is long AND it contains a space. This
+   means that a sequence of long labels will not be split unless they contain
+   spaces. */
+      if( *split || ( l > 9 && imin != -1 ) ) {
+         *split = 1;
+
+/* Initialse the pointer into the returned buffer at which the next
+   character will be placed. */
+         d = splitvalue_buff;
+
+/* If no spaces were found... */
+         if( imin == -1 ) {
+
+/*  If axis is zero, we add a blank first line. */
+            if( axis == 0 ) {
+
+/* Fill the first line with spaces. */
+               for( i = 0; i < l; i++ ) *(d++) = ' ';
+
+/* Add an escape sequence that moves down by one character height. */
+               d += sprintf( d, "%%v170+" );
+            }
+
+/* Add the whole of the supplied text. */
+            for( i = 0; i < l; i++ ) *(d++) = value[ i ];
+
+/* If a space was found... */
+         } else {
+
+/* Find the first and last non-blank characters before the mid-space. */
+            bef_start = -1;
+            bef_end = -1;
+            for( i = 0; i < imin; i++ ) {
+               if( !isspace( value[ i ] ) ) {
+                  if( bef_start == -1 ) bef_start = i;
+                  bef_end = i;
+               }
+            }
+
+/* Find the first and last non-blank characters after the mid-space. */
+            aft_start = -1;
+            aft_end = -1;
+            for( i = imin + 1; i < l; i++ ) {
+               if( !isspace( value[ i ] ) ) {
+                  if( aft_start == -1 ) aft_start = i;
+                  aft_end = i;
+               }
+            }
+
+/* How many significant characters before and after the space? */
+            nbef = bef_end - bef_start + 1;
+            naft = aft_end - aft_start + 1;
+
+/* Get the lengths of the longer and shorter line. */
+            if( nbef > naft ) {
+               nlong = nbef;
+               nshort = naft;
+            } else {
+               nlong = naft;
+               nshort = nbef;
+            }
+
+/* Find the fractional number of spaces before the significant text of the
+   shorter line.*/
+            rsp = 0.5*( nlong - nshort + 1 );
+
+/* If the top line is the shorter line, put some spaces in at the start. */
+            if( nbef < naft ) {
+               nsp = (int) rsp;
+               for( i = 0; i < nsp; i++ ) *(d++) = ' ';
+            }
+
+/* Add the significant text from the top line. */
+            for( i = bef_start; i <= bef_end; i++ ) *(d++) = value[ i ];
+
+/* Add an escape sequence that moves down by one character height. */
+            d += sprintf( d, "%%v100+" );
+
+
+/* Add an escape sequence that moves to the left by the required amount. */
+            d += sprintf( d, "%%<%d+", (int) ( 60.0*( (float) nlong - rsp )) );
+
+/* Add the significant text from the bottom line. */
+            for( i = aft_start; i <= aft_end; i++ ) *(d++) = value[ i ];
+
+         }
+
+/* Terminate it. */
+         *d = 0;
+
+/* Return a pointer to the buffer. */
+         result = splitvalue_buff;
+      }
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static void Fpoly( AstPlot *this, const char *method, const char *class,
+                   int *status ){
+/*
+*  Name:
+*     Fpoly
+
+*  Purpose:
+*     Flush all stored poly lines to the graphics system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Fpoly( AstPlot *this, const char *method, const char *class,
+*                 int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function sends all previously drawn poly lines to the graphics
+*     system for rendering, and frees the memory used to hold the poly
+*     lines. It attempts to reduce the number of graphics calls by
+*     concatenating continuous polylines together.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   float xmid;
+   float xt;
+   float ymid;
+   float *xnew;
+   float *ynew;
+   int polylen;
+   float *xp1;
+   float *xp2;
+   float *yp1;
+   float *yp2;
+   float yt;
+   int *ekey;
+   int *p;
+   int *skey;
+   int *drawn;
+   int ihi;
+   int ikey;
+   int ilo;
+   int imid;
+   int ipass;
+   int ipoint;
+   int ipoly;
+   int jpoly;
+   int kpoly;
+   int *polylist;
+   int npoly;
+   int np;
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Output any pending polyline. */
+   Opoly( this, status );
+
+/* If there is just one polyline to output, just draw it and then free
+   the memory used to hold the polyline.  */
+   if( Poly_npoly == 1 ) {
+      GLine( this, Poly_np[ 0 ], Poly_xp[ 0 ], Poly_yp[ 0 ], method, class,
+             status );
+      Poly_xp[ 0 ] = astFree( Poly_xp[ 0 ] );
+      Poly_yp[ 0 ] = astFree( Poly_yp[ 0 ] );
+      Poly_np[ 0 ] = 0;
+
+/* If there are multiple polylines to output, see if any of them can be
+   combined before drawing them.  */
+   } else if( Poly_npoly > 1 ) {
+
+/* No polyline buffer allocated yet. */
+      xnew = NULL;
+      ynew = NULL;
+
+/* Allocate an array to hold the order in which polylines should be
+   concatenated. Each value in this array will be the index of one of the
+   original polylines. A positive index indicates that the polyline
+   should be appended in its original order. A negative index indicates
+   that the polyline should be appended in reversed order. Polyline zero
+   is always appended in its original order. */
+      polylist = astMalloc( Poly_npoly*sizeof( int ) );
+      npoly = 0;
+
+/* Create an array of drawn, one for each individual polyline. The flag
+   is zero if the corresponding polyline has not yet been drawn. */
+      drawn = astCalloc(  Poly_npoly, sizeof( int ) );
+
+/* Create two sorted keys for the polylines - one that sorts them into
+   increasing x at the start of the polyline, and another that sorts them
+   into increasing x at the end of the polyline. */
+      skey = astMalloc( Poly_npoly*sizeof( int ) );
+      ekey = astMalloc( Poly_npoly*sizeof( int ) );
+      if( astOK ) {
+
+         p = skey;
+         for( ipoly = 0; ipoly < Poly_npoly; ipoly++ ) *(p++) = ipoly;
+         qsort( skey, Poly_npoly, sizeof(int), Fpoly_scmp );
+
+         p = ekey;
+         for( ipoly = 0; ipoly < Poly_npoly; ipoly++ ) *(p++) = ipoly;
+         qsort( ekey, Poly_npoly, sizeof(int), Fpoly_ecmp );
+
+      }
+
+/* Continue to search for separate polylines that can be combined together
+   until we know there are no more. */
+      while( astOK ) {
+
+/* Search for the first polyline that has not already been drawn. */
+         for( ipoly = 0; ipoly < Poly_npoly; ipoly++ ) {
+            if( !drawn[ ipoly ] ) break;
+         }
+
+/* Leave the loop if no more polylines remain to be plotted. */
+         if( ipoly == Poly_npoly ) break;
+
+/* Initialise the list of polylines to hold the polyline found above, in
+   its forward sense. */
+         polylist[ 0 ] = ipoly;
+         npoly = 1;
+         drawn[ 0 ] = 1;
+
+/* Initialise the concatenation point to be the end of the polyline found
+   above. Also, initialise the total number of points in the combined
+   polyline (polylen). */
+         ipoint = Poly_np[ ipoly ] - 1;
+         xt = Poly_xp[ ipoly ][ ipoint ];
+         yt = Poly_yp[ ipoly ][ ipoint ];
+         polylen = ipoint + 1;
+
+/* Loop until we can find no more polylines to append to the list.
+   A polyline can be appended if it starts or ends at the current
+   concatenation point. */
+         while( astOK ) {
+
+/* On the first pass through the next loop, search for a polyline that
+   starts at the concatenation point. If no such polyline is found, do
+   a second pass in which we search for a polyline that ends at the
+   concatenation point. Do not include any previously drawn polylines
+   in the search. */
+            for( ipass = 0; ipass < 2; ipass++ ) {
+
+/* We use a binary chop to find a polyline which starts (or ends) at the
+   x value of the concatenation point. */
+               jpoly = -1;
+               ilo = 0;
+               ihi = Poly_npoly - 1;
+               while( 1 ) {
+                  imid = ( ilo + ihi )/2;
+                  if( ipass == 0 ) {
+                     jpoly = skey[ imid ];
+                     xmid = Poly_xp[ jpoly ][ 0 ];
+                  } else {
+                     jpoly = ekey[ imid ];
+                     xmid = Poly_xp[ jpoly ][ Poly_np[ jpoly ] - 1 ];
+                  }
+                  if( astEQUALS( xmid, xt, 1.0E8 ) ) {
+                     ikey = imid;
+                     break;
+                  } else if( xmid > xt ) {
+                     if( ihi == imid ) {
+                        if( ipass == 0 ) {
+                           jpoly = skey[ ilo ];
+                           xmid = Poly_xp[ jpoly ][ 0 ];
+                        } else {
+                           jpoly = ekey[ ilo ];
+                           xmid = Poly_xp[ jpoly ][ Poly_np[ jpoly ] - 1 ];
+                        }
+                        if( !astEQUALS( xmid, xt, 1.0E8 ) ) jpoly = -1;
+                        ikey = ilo;
+                        break;
+                     }
+                     ihi = imid;
+                  } else {
+                     if( ilo == imid ) {
+                        if( ipass == 0 ) {
+                           jpoly = skey[ ihi ];
+                           xmid = Poly_xp[ jpoly ][ 0 ];
+                        } else {
+                           jpoly = ekey[ ihi ];
+                           xmid = Poly_xp[ jpoly ][ Poly_np[ jpoly ] - 1 ];
+                        }
+                        if( !astEQUALS( xmid, xt, 1.0E8 ) ) jpoly = -1;
+                        ikey = ihi;
+                        break;
+                     }
+                     ilo = imid;
+                  }
+               }
+
+/* If found, there may be more than one such polyline. So we now search
+   for a polyline that also has the y value of the concatenation point. */
+               if( jpoly != -1 ) {
+
+/* If the polyline found above starts (or ends) at the same Y value as the
+   concatenation point, then we have found the required polyline. */
+                  if( ipass == 0 ) {
+                     ymid = Poly_yp[ jpoly ][ 0 ];
+                  } else {
+                     ymid = Poly_yp[ jpoly ][ Poly_np[ jpoly ] - 1 ];
+                  }
+                  if( astEQUALS( ymid, yt, 1.0E8 ) && !drawn[ jpoly ] ) break;
+                  jpoly = -1;
+
+/* Otherwise, search down the list, starting at the polyline found above. */
+                  if( imid > 0 ) {
+                     for( ikey = imid - 1; ikey >= 0; ikey-- ) {
+                        if( ipass == 0 ) {
+                           kpoly = skey[ ikey ];
+                           xmid = Poly_xp[ kpoly ][ 0 ];
+                           ymid = Poly_yp[ kpoly ][ 0 ];
+                        } else {
+                           kpoly = ekey[ ikey ];
+                           xmid = Poly_xp[ kpoly ][ Poly_np[ kpoly ] - 1 ];
+                           ymid = Poly_yp[ kpoly ][ Poly_np[ kpoly ] - 1 ];
+                        }
+                        if( astEQUALS( xmid, xt, 1.0E8 ) ) {
+                           if( astEQUALS( ymid, yt, 1.0E8 ) && !drawn[ kpoly ] ) {
+                              jpoly = kpoly;
+                              break;
+                           }
+                        } else {
+                           break;
+                        }
+                     }
+                     if( jpoly != -1 ) break;
+                  }
+
+/* Now search up the list, starting at the polyline found above. */
+                  if( imid < Poly_npoly - 1 ) {
+                     for( ikey = imid + 1; ikey < Poly_npoly; ikey++ ) {
+                        if( ipass == 0 ) {
+                           kpoly = skey[ ikey ];
+                           xmid = Poly_xp[ kpoly ][ 0 ];
+                           ymid = Poly_yp[ kpoly ][ 0 ];
+                        } else {
+                           kpoly = ekey[ ikey ];
+                           xmid = Poly_xp[ kpoly ][ Poly_np[ kpoly ] - 1 ];
+                           ymid = Poly_yp[ kpoly ][ Poly_np[ kpoly ] - 1 ];
+                        }
+                        if( astEQUALS( xmid, xt, 1.0E8 ) ) {
+                           if( astEQUALS( ymid, yt, 1.0E8 ) && !drawn[ kpoly ] ) {
+                              jpoly = kpoly;
+                              break;
+                           }
+                        } else {
+                           break;
+                        }
+                     }
+                     if( jpoly != -1 ) break;
+                  }
+               }
+            }
+
+/* If a polyline was found that can be combined with the total polyline,
+   increment the total number of points in the total polyline, add it to
+   the list, and update the concatenation point. Note, we can omit the
+   start or end point of the new polyline since it will already be
+   present in the total polyline, hence the " - 1 " below. */
+            if( ipass < 2 ) {
+               ipoint = Poly_np[ jpoly ] - 1;
+
+               if( ipass == 0 ) {
+                  polylist[ npoly++ ] = jpoly;
+                  xt = Poly_xp[ jpoly ][ ipoint ];
+                  yt = Poly_yp[ jpoly ][ ipoint ];
+               } else {
+                  polylist[ npoly++ ] = -jpoly;
+                  xt = Poly_xp[ jpoly ][ 0 ];
+                  yt = Poly_yp[ jpoly ][ 0 ];
+               }
+
+               polylen += ipoint;
+
+/* Indicate the polyline has been drawn. */
+               drawn[ jpoly ] = 1;
+
+/* If we cannot find any polyline that starts or ends at the
+   concatenation point, then we have completed the total polyline. So break
+   out of the loop, and move on to draw the total polyline. */
+            } else {
+               break;
+            }
+         }
+
+/* If a single polyline is to be drawn, just draw it. */
+         if( npoly == 1 ) {
+            jpoly = polylist[ 0 ];
+            GLine( this, Poly_np[ jpoly ], Poly_xp[ jpoly ],
+                   Poly_yp[ jpoly ], method, class, status );
+
+/* If more than one polyline is to be drawn, ensure we have arrays that
+   are large enough to hold all the vertices in the combined polyline. */
+         } else {
+            xnew = astRealloc( xnew, polylen*sizeof( float ) );
+            ynew = astRealloc( ynew, polylen*sizeof( float ) );
+            if( astOK ) {
+
+/* Loop round all the polylines that are to be combined to form the total
+   polyline, and copy all the vertex coordinates into the above arrays. */
+               xp1 = xnew;
+               yp1 = ynew;
+               for( ipoly = 0; ipoly < npoly; ipoly++ ) {
+
+/* Index of the next polyline to include in the total polyline. */
+                  jpoly = polylist[ ipoly ];
+
+/* The jpoly value is positive if the polylline is to be inclued in its
+   original direction. */
+                  if( jpoly >= 0 ) {
+
+/* Use the whole of the first polyline. */
+                     if( ipoly == 0 ) {
+                        np =  Poly_np[ jpoly ];
+                        xp2 = Poly_xp[ jpoly ];
+                        yp2 = Poly_yp[ jpoly ];
+
+/* Omit eh first point of subsequent polylines since it will be the same
+   as the last pointy already in the total polyline. */
+                     } else {
+                        np =  Poly_np[ jpoly ] - 1;
+                        xp2 = Poly_xp[ jpoly ] + 1;
+                        yp2 = Poly_yp[ jpoly ] + 1;
+                     }
+
+/* Copy the vertex values in their original order, and update the
+   pointers to the next element of the total polyline. */
+                     memcpy( xp1, xp2, np*sizeof(float) );
+                     memcpy( yp1, yp2, np*sizeof(float) );
+                     xp1 +=  np;
+                     yp1 +=  np;
+
+/* The jpoly value is negative if the polyline is to be included in its
+   reversed direction. */
+                  } else {
+                     jpoly = -jpoly;
+
+/* Get the number of points to copy. Omit the last point if this is not
+   the first polyline, since it is already in the total polyline. */
+                     if( ipoly == 0 ) {
+                        np =  Poly_np[ jpoly ];
+                     } else {
+                        np =  Poly_np[ jpoly ] - 1;
+                     }
+
+/* Copy the individual values in reversed order. */
+                     xp2 = Poly_xp[ jpoly ] + np - 1;
+                     yp2 = Poly_yp[ jpoly ] + np - 1;
+                     for( ipoint = 0; ipoint < np; ipoint++ ) {
+                        *(xp1++) = *(xp2--);
+                        *(yp1++) = *(yp2--);
+                     }
+                  }
+               }
+
+/* And finally, draw the total polyline. */
+               GLine( this, polylen, xnew, ynew, method, class, status );
+            }
+         }
+      }
+
+/*  Free all the individual polylines. */
+      for( ipoly = 0; ipoly < Poly_npoly; ipoly++ ) {
+         Poly_xp[ ipoly ] = astFree( Poly_xp[ ipoly ] );
+         Poly_yp[ ipoly ] = astFree( Poly_yp[ ipoly ] );
+         Poly_np[ ipoly ] = 0;
+      }
+
+/* Free other resources. */
+      polylist = astFree( polylist );
+      drawn = astFree( drawn );
+      xnew = astFree( xnew );
+      ynew = astFree( ynew );
+      skey = astFree( skey );
+      ekey = astFree( ekey );
+   }
+
+/* Indicate that all polylines have been sent to the graphics system. */
+   Poly_npoly = 0;
+}
+
+static int Fpoly_ecmp( const void *a, const void *b ){
+/*
+*  Name:
+*     Fpoly_ecmp
+
+*  Purpose:
+*     Compare two polylines ending X position
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Fpoly_ecmp( const void *a, const void *b )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function is designed to be used as a comparison function with
+*     the "qsort" function. It is used in function Fpoly.
+*
+*     If orders the two polylines on the basis of the X coordinate at
+*     their ends.
+
+*  Parameters:
+*     a
+*       Pointer to an int holding the index of the first polyline.
+*     b
+*       Pointer to an int holding the index of the second polyline.
+
+*  Returned Value:
+*     -1 if the first polyline ends at a lower X than the second.
+*     +1 if the first polyline ends at a higher X than the second.
+*     0 if the two polylines end at the same X.
+
+*/
+
+/* Local Variables: */
+   float xa;               /* X at end of first polyline */
+   float xb;               /* X at end of second polyline */
+   int result = 0;         /* Returned value */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get the x coord at the end of the two polylines. */
+   xa = Poly_xp[ *( (int *) a) ][ Poly_np[ *( (int *) a) ] - 1 ];
+   xb = Poly_xp[ *( (int *) b) ][ Poly_np[ *( (int *) b) ] - 1 ];
+
+/* Compare them. */
+   if( xa < xb ) {
+      result = -1;
+   } else if( xa > xb ){
+      result = 1;
+   }
+
+   return result;
+}
+
+static int Fpoly_scmp( const void *a, const void *b ){
+/*
+*  Name:
+*     Fpoly_scmp
+
+*  Purpose:
+*     Compare two polylines starting X position
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Fpoly_scmp( const void *a, const void *b )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function is designed to be used as a comparison function with
+*     the "qsort" function. It is used in function Fpoly.
+*
+*     If orders the two polylines on the basis of the X coordinate at
+*     their starts.
+
+*  Parameters:
+*     a
+*       Pointer to an int holding the index of the first polyline.
+*     b
+*       Pointer to an int holding the index of the second polyline.
+
+*  Returned Value:
+*     -1 if the first polyline starts at a lower X than the second.
+*     +1 if the first polyline starts at a higher X than the second.
+*     0 if the two polylines starts at the same X.
+
+*/
+
+/* Local Variables: */
+   float xa;               /* X at start of first polyline */
+   float xb;               /* X at start of second polyline */
+   int result = 0;         /* Returned value */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get the x coord at the start of the two polylines. */
+   xa = Poly_xp[ *( (int *) a) ][ 0 ];
+   xb = Poly_xp[ *( (int *) b) ][ 0 ];
+
+/* Compare them. */
+   if( xa < xb ) {
+      result = -1;
+   } else if( xa > xb ){
+      result = 1;
+   }
+
+   return result;
+}
+
+static AstFrameSet *Fset2D( AstFrameSet *fset, int ifrm, int *status ) {
+/*
+*  Name:
+*     Fset2D
+
+*  Purpose:
+*     Create a FrameSet with no more than 2 dimensions for a given Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*      AstFrameSet *Fset2D( AstFrameSet *fset, int ifrm, int *status )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function checks a specified Frame in the supplied FrameSet.
+*     If the Frame has more than 2 dimensions, a new Frame is added to
+*     the FrameSet containing just the first two axes of the specified
+*     Frame. A PermMap is used to connect this Frame to the specified
+*     Frame, which supplied bad values for any missing axes. If the
+*     specified Frame is the base Frame in the supplied FrameSet, then the
+*     new Frame becomes the base Frame in the returned FrameSet. Like-wise,
+*     if the specified Frame is the current Frame, then the new Frame
+*     will be the current Frame in the returned FrameSet.
+*
+*     If the specified Frame does not have more than 2 axes, then a clone
+*     of the FrameSet pointer is returned, otherwise the returned pointer
+*     points to a copy of the supplied FrameSet with the new 2-D Frame
+*     added.
+
+*  Parameters:
+*     fset
+*        Pointer to the FrameSet.
+*     ifrm
+*        The index of the Frame to check. This should be AST__BASE or
+*        AST_CURRENT.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a FrameSet in which the Frame with index given by ifrm
+*     has no more than 2 axes.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstFrame *newfrm;
+   AstFrameSet *ret;
+   AstPermMap *map;
+   double zero;
+   int *inperm;
+   int axes[2];
+   int i;
+   int ic;
+   int nax;
+
+/* Check the inherited status. */
+   if( !astOK ) return NULL;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   map = NULL;
+
+/* Get a pointer to the requested Frame in the supplied FrameSet. */
+   frm = astGetFrame( fset, ifrm );
+
+/* See how many dimensions the specified Frame of the supplied FrameSet
+   has. */
+   nax = astGetNaxes( frm );
+
+/* If it is more than 2-dimensionbal, create a 2D Frame by picking
+   axes 1 and 2 from the original Frame. */
+   if( nax > 2 ) {
+      axes[ 0 ] = 0;
+      axes[ 1 ] = 1;
+      newfrm = astPickAxes( frm, 2, axes, NULL );
+
+/* Create a PermMap to describe the mapping between the two Frames.
+   Use zero as the value for unknown axes (the optional mapping which
+   can be returned by astPickAxes uses AST__BAD for unknown axes). */
+      inperm = (int *) astMalloc( sizeof(int)*(size_t) nax );
+      if( astOK ){
+         inperm[ 0 ] = 0;
+         inperm[ 1 ] = 1;
+         for( i = 2; i < nax; i++ ) inperm[ i ] = -1;
+         zero = 0.0;
+         map = astPermMap( nax, inperm, 2, axes, &zero, "", status );
+         inperm = (int *) astFree( (void *) inperm );
+      }
+
+/* Get a copy of the supplied FrameSet. */
+      ret = astCopy( fset );
+
+/* Add the new Frame to the FrameSet (it becomes the current Frame). */
+      ic = astGetCurrent( ret );
+      astAddFrame( ret, ifrm, map, newfrm );
+      newfrm = astAnnul( newfrm );
+
+/* If the new Frame was derived from the base frame, set the new base
+   Frame, and re-instate the original current Frame */
+      if( ifrm == AST__BASE ){
+         astSetBase( ret, astGetCurrent( ret ) );
+         astSetCurrent( ret, ic );
+      }
+
+/* If the specified Frame in the supplied FrameSet is 2-dimensional, just
+   return a clone of it. */
+   } else {
+      ret = astClone( fset );
+   }
+
+/* Annul the pointer to the original Frame. */
+   frm = astAnnul( frm );
+
+   return ret;
+
+}
+
+static int FullForm( const char *list, const char *test, const char *text,
+                     const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     FullForm
+
+*  Purpose:
+*     Identify the full form of an option string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int FullForm( const char *list, const char *test, const char *text,
+*                   const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function identifies a supplied test option within a supplied
+*     list of valid options, and returns the index of the option within
+*     the list. The test option may be abbreviated, and case is
+*     insignificant.
+
+*  Parameters:
+*     list
+*        A list of space separated option strings.
+*     test
+*        A candidate option string.
+*     text
+*        A string giving the context in which the supplied test option
+*        was supplied. For instance, this may be an attribute setting string.
+*        This is only for use in constructing error messages.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The index of the identified option within the supplied list, starting
+*     at zero.
+
+*  Notes:
+*     -  A value of -1 is returned if an error has already occurred, or
+*     if this function should fail for any reason (for instance if the
+*     supplied option is not uniquely specified in the supplied list).
+
+*/
+
+/* Local Variables: */
+   char *option;           /* Pointer to a copy of the next option */
+   const char *p;          /* Pointer to the start of the next word */
+   int i;                  /* Current option index */
+   int len;                /* Length of supplied option */
+   int nmatch;             /* Number of matching options */
+   int ret;                /* The returned index */
+
+/* Initialise the answer to indicate that the option has not been
+   uniquely identified. */
+   ret = -1;
+
+/* Check global status. */
+   if( !astOK ) return ret;
+
+/* Save the number of characters in the supplied test option (excluding
+   trailing spaces). */
+   len = ChrLen( test, status );
+
+/* Compare the supplied test option against each of the known options in
+   turn. Count the number of matches. */
+   nmatch = 0;
+   p = list;
+   option = FindWord( NULL, " ", &p, status );
+   i = 0;
+   while( option ){
+
+/* If the test string and the current option are identical (including
+   length). use the current option. */
+
+/* If every character in the supplied label matches the corresponding
+   character in the current test label we have a match. Increment the
+   number of matches and save the current item index. If the test string
+   and the current option are identical (including length), use the
+   current option. */
+
+      if( !Ustrncmp( test, option, len, status ) ) {
+         ret = i;
+         if( ChrLen( option, status ) == len ) {
+            nmatch = 1;
+            option = astFree( option );
+            break;
+         } else {
+            nmatch++;
+         }
+      }
+
+/* Get a pointer to the next option. */
+      option = FindWord( option, " ", &p, status );
+      i++;
+   }
+
+/* Report an error if no match was found, and return -1. */
+   if( !nmatch ){
+      astError( AST__OPT, "%s(%s): Option '%.*s' is unknown in '%.*s'.", status,
+                method, class, len, test, ChrLen( text, status ), text );
+      ret = -1;
+
+/* Report an error if the label was ambiguous, and return -1. */
+   } else if( nmatch > 1 ){
+      astError( AST__OPT, "%s(%s): Option '%.*s' is ambiguous in '%.*s'.", status,
+                method, class, len, test, ChrLen( text, status ), text );
+      ret = -1;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static void GAttr( AstPlot *this, int attr, double value, double *old_value,
+                   int prim, const char *method, const char *class, int *status ) {
+/*
+*
+*  Name:
+*     GAttr
+
+*  Purpose:
+*     Call the GAttr Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void GAttr( AstPlot *this, int attr, double value, double *old_value,
+*                 int prim, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the GAttr grf function to enquire or set a
+*     graphics attribute value. It either calls the version registered using
+*     astGrfSet, or the version in the linked grf module. The linked version
+*     is used if the Grf attribute is zero, or if no function has been
+*     registered for GAttr using astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     attr
+*        An integer value identifying the required attribute. The
+*        following symbolic values are defined in grf.h:
+*
+*           GRF__STYLE  - Line style.
+*           GRF__WIDTH  - Line width.
+*           GRF__SIZE   - Character and marker size scale factor.
+*           GRF__FONT   - Character font.
+*           GRF__COLOUR - Colour index.
+*     value
+*        A new value to store for the attribute. If this is AST__BAD
+*        no value is stored.
+*     old_value
+*        A pointer to a double in which to return the attribute value.
+*        If this is NULL, no value is returned.
+*     prim
+*        The sort of graphics primitive to be drawn with the new attribute.
+*        Identified by the following values defined in grf.h:
+*           GRF__LINE
+*           GRF__MARK
+*           GRF__TEXT
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int grf_status;          /* Status retruned from Grf function */
+
+/* Check the global error status. Also return if there is nothing to do. */
+   if ( !astOK || ( !old_value && value == AST__BAD ) ) return;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+   if( astGetGrf( this ) && this->grffun[ AST__GATTR ] ) {
+      grf_status = ( *( this->GAttr ) )( this, attr, value, old_value, prim, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+   } else {
+      grf_status = astGAttr( attr, value, old_value, prim );
+   }
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Report an error if anything went wrong. */
+   if( !grf_status ) {
+      astError( AST__GRFER, "%s(%s): Graphics error in astGAttr. ", status, method,
+                class );
+   }
+
+}
+
+static AstKeyMap *GetGrfContext( AstPlot *this, int *status ){
+/*
+*++
+*  Name:
+c     astGetGrfContext
+f     AST_GETGRFCONTEXT
+
+*  Purpose:
+*     Return the KeyMap that describes a Plot's graphics context.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     AstKeyMap *astGetGrfContext( AstPlot *this )
+f     RESULT = AST_GETGRFCONTEXT( THIS, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns a reference to a KeyMap that will be passed to any drawing
+c     functions registered using astGrfSet.
+f     routines registered using AST_GRFSET.
+*     This KeyMap can be used by an application to pass information to
+c     the drawing functions
+f     the drawing routines
+*     about the context in which they are being called. The contents of
+*     the KeyMap are never accessed byt the Plot class itself.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetGrfContext()
+f     AST_GETGRFCONTEXT = INTEGER
+*        A pointer to the graphics context KeyMap. The returned pointer
+*        should be annulled when it is no longer needed.
+
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Ensure that a grfcon KeyMap exists. */
+   (void) astGrfConID(this);
+
+/* Return a cloned pointer to the KeyMap. */
+   return astClone( this->grfcontext );
+}
+
+AstKeyMap *astGrfConID_( AstPlot *this, int *status ) {
+/*
+*+
+*
+*  Name:
+*     astGrfConID
+
+*  Purpose:
+*     Ensure a GrfContext KeyMap exists and return an ID for it.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     AstKeyMap *astGrfConID( AstPlot *this )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function creates a GrfContext KeyMap if the Plot does not
+*     currently have one, and returns an ID for it.
+
+*  Parameters:
+*     this
+*        The Plot.
+
+*  Returned Value:
+*     ID for the GrfContext KeyMap.
+
+*-
+*/
+   if( !this->grfcontext ) {
+      this->grfcontext = astKeyMap("", status );
+      this->grfcontextID = astMakeId( astClone( this->grfcontext ) );
+      astExempt( this->grfcontextID );
+   }
+   return this->grfcontextID;
+}
+
+static void GScales( AstPlot *this, float *alpha, float *beta,
+                      const char *method, const char *class, int *status ) {
+/*
+*
+*  Name:
+*     GScales
+
+*  Purpose:
+*     Call the GScales Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void GScales( AstPlot *this, float *alpha, float *beta,
+*                    const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the GScales grf function, either calling the
+*     version registered using astGrfSet, or the version in the linked grf
+*     module. The linked version is used if the Grf attribute is zero, or if
+*     no function has been registered for GScales using astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     alpha
+*        A pointer to the location at which to return the scale for the
+*        X axis (i.e. Xnorm = alpha*Xworld).
+*     beta
+*        A pointer to the location at which to return the scale for the
+*        Y axis (i.e. Ynorm = beta*Yworld).
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   int grf_status;          /* Status retruned from Grf function */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* if we already have the required values, return them. */
+   if( Grf_alpha != 0.0 && Grf_beta != 0.0 ) {
+      if( alpha ) *alpha = Grf_alpha;
+      if( beta ) *beta = Grf_beta;
+      return;
+   }
+
+/* Check that the grf mdoule can give us scales information. */
+   if( GCap( this, GRF__SCALES, 1, status ) ) {
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+      LOCK_MUTEX2;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This is called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+      if( astGetGrf( this ) && this->grffun[ AST__GSCALES ] ) {
+         grf_status = ( *( this->GScales ) )( this, &Grf_alpha, &Grf_beta, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+      } else {
+         grf_status = astGScales( &Grf_alpha, &Grf_beta );
+      }
+
+/* Allow the next thread to proceed. */
+      UNLOCK_MUTEX2;
+
+/* Check neither value is zero. */
+      if( grf_status && ( Grf_alpha == 0.0 || Grf_beta == 0.0 ) ) {
+         astError( AST__GRFER, "astGScales: Returned axis scales are %g and %g "
+                   "but zero is illegal!", status, Grf_alpha, Grf_beta );
+         grf_status = 0;
+      }
+
+/* Report an error if anything went wrong, and return safe values. */
+      if( !grf_status ) {
+         astError( AST__GRFER, "%s(%s): Graphics error in astGScales. ", status, method,
+                   class );
+         Grf_alpha = 1.0;
+         Grf_beta = 1.0;
+      }
+
+/* If the grf module is not capable of giving us scale information, then
+   just assume the the axes are equally scaled, except for any axis reversal
+   indicated in the supplied Plot. */
+   } else {
+      Grf_alpha = ( this->xrev ) ? -1.0 : 1.0;
+      Grf_beta = ( this->yrev ) ? -1.0 : 1.0;
+   }
+
+/* Store them for future use. */
+   if( alpha ) *alpha = Grf_alpha;
+   if( beta ) *beta = Grf_beta;
+}
+
+static int GCap( AstPlot *this, int cap, int value, int *status ){
+/*
+*
+*  Name:
+*     GCap
+
+*  Purpose:
+*     Call the GCap Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int GCap( AstPlot *this, int cap, int value, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the GCap grf function to inquire a capability
+*     of the grf module, either calling the version registered using
+*     astGrfSet, or the version in the linked grf module. The linked
+*     version is used if the Grf attribute is zero, or if no function
+*     has been registered for GCap using astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     cap
+*        The capability to be inquired aboue.
+*     value
+*        The value to assign to the capability.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the grf module is capabale of performing the action
+*     requested by "cap".
+
+*/
+
+/* Local Variables: */
+   int result;          /* Value retruned from Grf function */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This is called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+   if( astGetGrf( this ) && this->grffun[ AST__GCAP ] ) {
+      result = ( *( this->GCap ) )( this, cap, value, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+   } else {
+      result = astGCap( cap, value );
+
+   }
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Return the result. */
+   return result;
+}
+
+static void GenCurve( AstPlot *this, AstMapping *map, int *status ){
+/*
+*++
+*  Name:
+c     astGenCurve
+f     AST_GENCURVE
+
+*  Purpose:
+*     Draw a generalized curve.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astGenCurve( AstPlot *this, astMapping *map )
+f     CALL AST_GENCURVE( THIS, MAP )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+c     This function draws a general user-defined curve defined by the
+f     This routine draws a general user-defined curve defined by the
+*     supplied Mapping. Note that the curve is transformed into graphical
+*     coordinate space for plotting, so that a straight line in
+*     physical coordinates may result in a curved line being drawn if
+*     the Mapping involved is non-linear. Any discontinuities in the
+*     Mapping between physical and graphical coordinates are
+c     catered for, as is any clipping established using astClip.
+f     catered for, as is any clipping established using AST_CLIP.
+*
+c     If you need to draw simple straight lines (geodesics), astCurve
+c     or astPolyCurve will usually be easier to use and faster.
+f     If you need to draw simple straight lines (geodesics), AST_CURVE
+f     or AST_POLYCURVE will usually be easier to use and faster.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+c     map
+f     MAP = INTEGER (Given)
+*        Pointer to a Mapping. This Mapping should have 1 input
+*        coordinate representing offset along the required curve,
+*        normalized so that the start of the curve is at offset 0.0,
+*        and the end of the curve is at offset 1.0. Note, this offset
+*        does not need to be linearly related to distance along the curve.
+*        The number of output coordinates should equal the number of axes
+*        in the current Frame of the Plot. The Mapping should map a
+*        specified offset along the curve, into the corresponding
+*        coordinates in the current Frame of the Plot. The inverse
+*        transformation need not be defined.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - An error results if the base Frame of the Plot is not 2-dimensional.
+*     - An error also results if the transformation between the
+*     current and base Frames of the Plot is not defined (i.e. the
+*     Plot's TranInverse attribute is zero).
+*--
+*/
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   double d[ CRV_NPNT ];   /* Offsets to evenly spaced points along curve */
+   double tol;             /* Absolute tolerance value */
+   double x[ CRV_NPNT ];   /* X coords at evenly spaced points along curve */
+   double y[ CRV_NPNT ];   /* Y coords at evenly spaced points along curve */
+   int i;                  /* Loop count */
+   int naxes;              /* No. of axes in the base Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Store the current method, and the class of the supplied object for use
+   in error messages.*/
+   method = "astGenCurve";
+   class = astGetClass( this );
+
+/* Check the base Frame of the Plot is 2-D. */
+   naxes = astGetNin( this );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 2.", status, method, class, naxes, class );
+   }
+
+/* Only proceed if there has been no error. */
+   if( astOK ){
+
+/* Initialise the bounding box for primitives produced by this call. */
+      if( !Boxp_freeze ) {
+         Boxp_lbnd[ 0 ] = FLT_MAX;
+         Boxp_lbnd[ 1 ] = FLT_MAX;
+         Boxp_ubnd[ 0 ] = FLT_MIN;
+         Boxp_ubnd[ 1 ] = FLT_MIN;
+      }
+
+/* Indicate that the GRF module should re-calculate it's cached values
+   (in case the state of the graphics system has changed since the last
+   thing was drawn). */
+      RESET_GRF;
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+      astGrfAttrs( this, AST__CURVE_ID, 1, GRF__LINE, method, class );
+
+/* Ensure the globals holding the scaling from graphics coords to equally
+   scaled coords are available. */
+      GScales( this, NULL, NULL, method, class, status );
+
+/* Set up the externals used to communicate with the Map4 function... */
+      Map4_ncoord = astGetNout( this );
+      Map4_plot = this;
+      Map4_map = astGetMapping( this, AST__BASE, AST__CURRENT );
+      Map4_umap = map;
+
+/* Convert the tolerance from relative to absolute graphics coordinates. */
+      tol = astGetTol( this )*astMAX( this->xhi - this->xlo,
+                                      this->yhi - this->ylo );
+
+/* Now set up the external variables used by the Crv and CrvLine function. */
+      Crv_scerr = ( astGetLogPlot( this, 0 ) ||
+                    astGetLogPlot( this, 1 ) ) ? 100.0 : 1.5;
+      Crv_ux0 = AST__BAD;
+      Crv_tol = tol;
+      Crv_limit = 0.5*tol*tol;
+      Crv_map = Map4;
+      Crv_ink = 1;
+      Crv_xlo = this->xlo;
+      Crv_xhi = this->xhi;
+      Crv_ylo = this->ylo;
+      Crv_yhi = this->yhi;
+      Crv_out = 1;
+      Crv_xbrk = Curve_data.xbrk;
+      Crv_ybrk = Curve_data.ybrk;
+      Crv_vxbrk = Curve_data.vxbrk;
+      Crv_vybrk = Curve_data.vybrk;
+      Crv_clip = astGetClip( this ) & 1;
+
+/* Set up a list of points spread evenly over the curve. */
+      for( i = 0; i < CRV_NPNT; i++ ){
+        d[ i ] = ( (double) i)/( (double) CRV_NSEG );
+      }
+
+/* Map these points into graphics coordinates. */
+      Map4( CRV_NPNT, d, x, y, method, class, status GLOBALS_NAME );
+
+/* Use Crv and Map4 to draw the curve. */
+      Crv( this, d, x, y, 0, NULL, NULL, method, class, status );
+
+/* End the current poly line. */
+      Opoly( this, status );
+
+/* Tidy up the static data used by Map4. */
+      Map4( 0, NULL, NULL, NULL, method, class, status GLOBALS_NAME );
+
+/* If no part of the curve could be drawn, set the number of breaks and the
+   length of the drawn curve to zero. */
+      if( Crv_out ) {
+         Crv_nbrk = 0;
+         Crv_len = 0.0F;
+
+/* Otherwise, add an extra break to the returned structure at the position of
+   the last point to be plotted. */
+      } else {
+         Crv_nbrk++;
+         if( Crv_nbrk > AST__PLOT_CRV_MXBRK ){
+            astError( AST__CVBRK, "%s(%s): Number of breaks in curve "
+                      "exceeds %d.", status, method, class, AST__PLOT_CRV_MXBRK );
+         } else {
+            *(Crv_xbrk++) = (float) Crv_xl;
+            *(Crv_ybrk++) = (float) Crv_yl;
+            *(Crv_vxbrk++) = (float) -Crv_vxl;
+            *(Crv_vybrk++) = (float) -Crv_vyl;
+         }
+      }
+
+/* Store extra information about the curve in the returned structure, and
+   purge any zero length sections. */
+      Curve_data.length = Crv_len;
+      Curve_data.out = Crv_out;
+      Curve_data.nbrk = Crv_nbrk;
+      PurgeCdata( &Curve_data, status );
+
+/* Annul the Mapping. */
+      Map4_map = astAnnul( Map4_map );
+
+/* Ensure all lines are flushed to the graphics system. */
+      Fpoly( this, method, class, status );
+
+/* Re-establish the original graphical attributes. */
+      astGrfAttrs( this, AST__CURVE_ID, 0, GRF__LINE, method, class );
+   }
+
+/* Return. */
+   return;
+
+}
+
+static int GetLabelUnits( AstPlot *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetLabelUnits
+
+*  Purpose:
+*     Return the value of the LabelUnits attribute for a Plot axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int GetLabelUnits( AstPlot *this, int axis, int *status )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function returns the value of the LabelUnits attribute for a
+*     Plot axis, supplying a suitable default if not set.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     axis
+*        The axis index (zero based).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The attribute value.
+
+*/
+
+/* Local Variables: */
+   AstFrame *fr;           /* The current Frame in the Plot */
+   AstFrame *primframe;    /* The primary Frame holding the requested axis */
+   AstSystemType system;   /* The SkyFrame System attribute */
+   int primaxis;           /* Index of requested axis in the primary frame */
+   int ret;                /* The returned value */
+
+/* Initialise. */
+   ret = 0;
+
+/* Check global status. */
+   if( !astOK ) return ret;
+
+/* If a value has been set, return it. */
+   ret = this->labelunits[ axis ];
+
+/* If no value has been set, find a default. */
+   if( ret == -1 ) {
+
+/* Assume "no" for any SkyAxis axes within the current frame of the Plot,
+   and "yes" for other axes. Get a pointer to the current Frame of the
+   Plot. */
+      fr = astGetFrame( this, AST__CURRENT );
+
+/* The current Frame may be a CmpFrame. So find the primary Frame containing
+   the requested axis. The primary Frame is guaranteed not to be a CmpFrame. */
+      astPrimaryFrame( fr, axis, &primframe, &primaxis );
+
+/* If the primary Frame is a SkyFrame representing ICRS, equatorial, ecliptic,
+   galactic or supergalactic coords, use a default of "no" for LabelUnits.
+   Otherwise use a default of "yes". */
+      ret = 1;
+      if( IsASkyFrame( (AstObject *) primframe, status ) ) {
+        system = astGetSystem( primframe );
+        if( system == AST__ICRS ||
+            system == AST__FK4 ||
+            system == AST__FK4_NO_E ||
+            system == AST__FK5 ||
+            system == AST__GAPPT ||
+            system == AST__ECLIPTIC ||
+            system == AST__GALACTIC ||
+            system == AST__SUPERGALACTIC ) ret = 0;
+      }
+
+/* Annul the frame pointers. */
+      primframe = astAnnul( primframe );
+      fr = astAnnul( fr );
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static void GBBuf( AstPlot *this, const char *method,
+                   const char *class, int *status ) {
+/*
+*
+*  Name:
+*     GBBuf
+
+*  Purpose:
+*     Call the GBBuf Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void GBBuf( AstPlot *this, const char *method,
+*                  const char *class, int *status ) {
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the GBBuf grf function to begin a new graphics
+*     context, either calling the version registered using astGrfSet, or
+*     the version in the linked grf module. The linked version is used
+*     if the Grf attribute is zero, or if no function has been registered
+*     for GBBuf using astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int grf_status;          /* Status retruned from Grf function */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+   if( astGetGrf( this ) && this->grffun[ AST__GBBUF ] ) {
+      grf_status = ( *( this->GBBuf ) )( this, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+   } else {
+      grf_status = astGBBuf();
+   }
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Report an error if anything went wrong. */
+   if( !grf_status ) {
+      astError( AST__GRFER, "%s(%s): Graphics error in astGBBuf. ", status, method,
+                class );
+   }
+
+}
+
+static void GEBuf( AstPlot *this, const char *method,
+                   const char *class, int *status ) {
+/*
+*
+*  Name:
+*     GEBuf
+
+*  Purpose:
+*     Call the GEBuf Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void GEBuf( AstPlot *this, const char *method,
+*                  const char *class, int *status ) {
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the GEBuf grf function to end the current graphics
+*     context, either calling the version registered using astGrfSet, or
+*     the version in the linked grf module. The linked version is used
+*     if the Grf attribute is zero, or if no function has been registered
+*     for GEBuf using astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int grf_status;          /* Status retruned from Grf function */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+   if( astGetGrf( this ) && this->grffun[ AST__GEBUF ] ) {
+      grf_status = ( *( this->GEBuf ) )( this, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+   } else {
+      grf_status = astGEBuf();
+   }
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Report an error if anything went wrong. */
+   if( !grf_status ) {
+      astError( AST__GRFER, "%s(%s): Graphics error in astGEBuf. ", status, method,
+                class );
+   }
+
+}
+
+static void GFlush( AstPlot *this, const char *method,
+                   const char *class, int *status ) {
+/*
+*
+*  Name:
+*     GFlush
+
+*  Purpose:
+*     Call the Gflush Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void GFlush( AstPlot *this, const char *method,
+*                  const char *class, int *status ) {
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the Gflush grf function to flush graphics, either
+*     calling the version registered using astGrfSet, or the version in the
+*     linked grf module. The linked version is used if the Grf attribute
+*     is zero, or if no function has been registered for Gflush using
+*     astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int grf_status;          /* Status retruned from Grf function */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+   if( astGetGrf( this ) && this->grffun[ AST__GFLUSH ] ) {
+      grf_status = ( *( this->GFlush ) )( this, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+   } else {
+      grf_status = astGFlush();
+   }
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Report an error if anything went wrong. */
+   if( !grf_status ) {
+      astError( AST__GRFER, "%s(%s): Graphics error in astGFlush. ", status, method,
+                class );
+   }
+
+}
+
+static void GLine( AstPlot *this, int n, const float *x,
+                   const float *y, const char *method,
+                   const char *class, int *status ) {
+/*
+*
+*  Name:
+*     GLine
+
+*  Purpose:
+*     Call the Gline Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void GLine( AstPlot *this, int n, const float *x,
+*                 const float *y, const char *method,
+*                 const char *class, int *status ) {
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the Gline grf function to draw a polyline, either
+*     calling the version registered using astGrfSet, or the version in the
+*     linked grf module. The linked version is used if the Grf attribute
+*     is zero, or if no function has been registered for Gline using
+*     astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     n
+*        The number of positions to be joined together.
+*     x
+*        A pointer to an array holding the "n" x values.
+*     y
+*        A pointer to an array holding the "n" y values.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS   /* Pointer to thread-specific global data */
+   int i;               /* Loop count */
+   int grf_status;      /* Status retruned from Grf function */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* Do not draw anything if we are using "invisible ink". */
+   if( astGetInvisible( this ) ) {
+      grf_status = 1;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This is called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+   } else if( astGetGrf( this ) && this->grffun[ AST__GLINE ] ) {
+      grf_status = ( *( this->GLine ) )( this, n, x, y, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+   } else {
+      grf_status = astGLine( n, x, y );
+   }
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Report an error if anything went wrong. */
+   if( !grf_status ) {
+      astError( AST__GRFER, "%s(%s): Graphics error in astGLine. ", status, method,
+                class );
+
+/* Otherwise, update the box containing all drawn graphics primitives. */
+   } else if( !Boxp_freeze ){
+      for( i = 0; i < n; i++ ) {
+         Boxp_lbnd[ 0 ] = astMIN( x[ i ], Boxp_lbnd[ 0 ] );
+         Boxp_ubnd[ 0 ] = astMAX( x[ i ], Boxp_ubnd[ 0 ] );
+         Boxp_lbnd[ 1 ] = astMIN( y[ i ], Boxp_lbnd[ 1 ] );
+         Boxp_ubnd[ 1 ] = astMAX( y[ i ], Boxp_ubnd[ 1 ] );
+      }
+   }
+
+}
+
+static void GMark( AstPlot *this, int n, const float *x,
+                   const float *y, int type, const char *method,
+                   const char *class, int *status ) {
+/*
+*
+*  Name:
+*     GMark
+
+*  Purpose:
+*     Call the GMark Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void GMark( AstPlot *this, int n, const float *x,
+*                 const float *y, int type, const char *method,
+*                 const char *class, int *status ) {
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the GMark grf function to draw markers, either
+*     calling the version registered using astGrfSet, or the version in the
+*     linked grf module. The linked version is used if the Grf attribute
+*     is zero, or if no function has been registered for GMark using
+*     astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     n
+*        The number of positions to be joined together.
+*     x
+*        A pointer to an array holding the "n" x values.
+*     y
+*        A pointer to an array holding the "n" y values.
+*     type
+*        An integer which can be used to indicate the type of marker symbol
+*        required.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS   /* Pointer to thread-specific global data */
+   int i;               /* Loop count */
+   int grf_status;      /* Status retruned from Grf function */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* Do not draw anything if we are using "invisible ink". */
+   if( astGetInvisible( this ) ) {
+      grf_status = 1;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This is called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+   } else if( astGetGrf( this ) && this->grffun[ AST__GMARK ] ) {
+      grf_status = ( *( this->GMark ) )( this, n, x, y, type, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+   } else {
+      grf_status = astGMark( n, x, y, type );
+   }
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Report an error if anything went wrong. */
+   if( !grf_status ) {
+      astError( AST__GRFER, "%s(%s): Graphics error in astGMark. ", status, method,
+                class );
+
+/* Otherwise, update the box containing all drawn graphics primitives. */
+   } else if( !Boxp_freeze ){
+      for( i = 0; i < n; i++ ) {
+         Boxp_lbnd[ 0 ] = astMIN( x[ i ], Boxp_lbnd[ 0 ] );
+         Boxp_ubnd[ 0 ] = astMAX( x[ i ], Boxp_ubnd[ 0 ] );
+         Boxp_lbnd[ 1 ] = astMIN( y[ i ], Boxp_lbnd[ 1 ] );
+         Boxp_ubnd[ 1 ] = astMAX( y[ i ], Boxp_ubnd[ 1 ] );
+      }
+   }
+
+}
+
+static void GQch( AstPlot *this, float *chv, float *chh, const char *method,
+                  const char *class, int *status ) {
+/*
+*
+*  Name:
+*     GQch
+
+*  Purpose:
+*     Call the GQch Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void GQch( AstPlot *this, float *chv, float *chh, const char *method,
+*                const char *class, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the GQch grf function, either calling the
+*     version registered using astGrfSet, or the version in the linked grf
+*     module. The linked version is used if the Grf attribute is zero, or if
+*     no function has been registered for GQch using astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     chv
+*        A pointer to the double which is to receive the height of
+*        characters drawn with a vertical baseline . This will be an
+*        increment in the X axis.
+*     chh
+*        A pointer to the double which is to receive the height of
+*        characters drawn with a horizontal baseline. This will be an
+*        increment in the Y axis.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   int grf_status;          /* Status retruned from Grf function */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* if we already have the required values, return them. */
+   if( Grf_chh != AST__BAD && Grf_chv != AST__BAD ) {
+      *chh = Grf_chh;
+      *chv = Grf_chv;
+      return;
+   }
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This is called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+   if( astGetGrf( this ) && this->grffun[ AST__GQCH ] ) {
+      grf_status = ( *( this->GQch ) )( this, chv, chh, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+   } else {
+      grf_status = astGQch( chv, chh );
+   }
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Check neither value is zero. */
+   if( grf_status && ( *chh == 0.0 || *chv == 0.0 ) ) {
+      astError( AST__GRFER, "astGQch: Returned text heights are %g and %g "
+                "but zero is illegal!", status, *chv, *chh );
+      grf_status = 0;
+   }
+
+/* Report an error if anything went wrong, and return safe values. */
+   if( !grf_status ) {
+      astError( AST__GRFER, "%s(%s): Graphics error in astGQch. ", status, method,
+                class );
+      *chh = 1.0;
+      *chv = 1.0;
+   }
+
+/* Store them for future use. */
+   Grf_chh = *chh;
+   Grf_chv = *chv;
+}
+
+static void GText( AstPlot *this, const char *text, float x, float y,
+                   const char *just, float upx, float upy,
+                   const char *method, const char *class, int *status ) {
+/*
+*
+*  Name:
+*     GText
+
+*  Purpose:
+*     Call the GText Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void GText( AstPlot *this, const char *text, float x, float y,
+*                 const char *just, float upx, float upy,
+*                 const char *method, const char *class, int *status ) {
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the GText grf function to draw a text string, either
+*     calling the version registered using astGrfSet, or the version in the
+*     linked grf module. The linked version is used if the Grf attribute
+*     is zero, or if no function has been registered for GText using
+*     astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     x
+*        The reference x coordinate.
+*     y
+*        The reference y coordinate.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", or 'B' for "bottom", and specifies the
+*        vertical location of the reference position. Note, "bottom"
+*        corresponds to the base-line of normal text. Some characters
+*        (eg "y", "g", "p", etc) descend below the base-line. The second
+*        character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+*     upx
+*        The x component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        left to right on the screen.
+*     upy
+*        The y component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        bottom to top on the screen.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int grf_status;          /* Status retruned from Grf function */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* Do not draw anything if we are using "invisible ink". */
+   if( astGetInvisible( this ) ) {
+      grf_status = 1;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This is called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+   } else if( astGetGrf( this ) && this->grffun[ AST__GTEXT ] ) {
+      grf_status = ( *( this->GText ) )( this, text, x, y, just, upx, upy, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+   } else {
+      grf_status = astGText( text, x, y, just, upx, upy );
+   }
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Report an error if anything went wrong. */
+   if( !grf_status ) {
+      astError( AST__GRFER, "%s(%s): Graphics error in astGText. ", status, method,
+                class );
+   }
+
+}
+
+static void GTxExt( AstPlot *this, const char *text, float x, float y,
+                    const char *just, float upx, float upy, float *xbn,
+                    float *ybn, const char *method, const char *class, int *status ) {
+/*
+*
+*  Name:
+*     GTxExt
+
+*  Purpose:
+*     Call the GTxExt Grf function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void GTxExt( AstPlot *this, const char *text, float x, float y,
+*                 const char *just, float upx, float upy, float *xbn,
+*                 float *ybn, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot private function.
+
+*  Description:
+*     This function calls the GTxExt grf function to find the extent
+*     of a text string, either calling the version registered using
+*     astGrfSet, or the version in the linked grf module. The linked
+*     version is used if the Grf attribute is zero, or if no function
+*     has been registered for GTxExt using astGrfSet.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     x
+*        The reference x coordinate.
+*     y
+*        The reference y coordinate.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", or 'B' for "bottom", and specifies the
+*        vertical location of the reference position. Note, "bottom"
+*        corresponds to the base-line of normal text. Some characters
+*        (eg "y", "g", "p", etc) descend below the base-line. The second
+*        character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+*     upx
+*        The x component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        left to right on the screen.
+*     upy
+*        The y component of the up-vector for the text, in graphics world
+*        coordinates. If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        bottom to top on the screen.
+*     xbn
+*        An array of 4 elements in which to return the x coordinate of
+*        each corner of the bounding box.
+*     ybn
+*        An array of 4 elements in which to return the y coordinate of
+*        each corner of the bounding box.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The corners are returned in no particular order.
+
+*/
+
+/* Local Variables: */
+   int grf_status;          /* Status retruned from Grf function */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* If the Grf attribute is set to a non-zero value, use the Grf function
+   registered using astGrfSet (so long as a function has been supplied).
+   This is called via a wrapper which adapts the interface to suit the
+   language in which the function is written. */
+   if( astGetGrf( this ) && this->grffun[ AST__GTXEXT ] ) {
+      grf_status = ( *( this->GTxExt ) )( this, text, x, y, just, upx, upy,
+                                          xbn, ybn, status );
+
+/* Otherwise, use the function in the external Grf module, selected at
+   link-time using ast_link options.*/
+   } else {
+      grf_status = astGTxExt( text, x, y, just, upx, upy, xbn, ybn );
+   }
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Report an error if anything went wrong. */
+   if( !grf_status ) {
+      astError( AST__GRFER, "%s(%s): Graphics error in astGTxExt. ", status, method,
+                class );
+   }
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a Plot.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Plot member function (over-rides the protected astGetAttrib
+*     method inherited from the FrameSet class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a Plot, formatted as a character string.
+*
+*     The value returned is the value which would actually be used if
+*     astGrid was called with the current set of attribute values. This
+*     may not always be the same as the value set by the user. For
+*     instance, if Labelling is set to "exterior" by the user, it may not
+*     be possible to produce exterior labels, in which case interior labels
+*     will be produced. If this function is used to get the value of
+*     Labelling in this situation, then the value actually used (i.e.
+*     interior) will be returned instead of the requested value (i.e.
+*     exterior).
+*
+*     Some attributes have dynamic defaults, (i.e. the behaviour if not
+*     set depends on the values of other attributes). If the value for
+*     such an attribute is enquired using this function, then the dynamic
+*     default value actually used will be returned if no value has been
+*     set explicitly for the attribute.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     attrib
+*        Pointer to a null terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Plot, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Plot. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPlot *this;                /* Pointer to the Plot structure */
+   const char *result;           /* Pointer value to return */
+   char label[21];               /* Graphics item label */
+   double dval;                  /* Double attribute value */
+   int axis;                     /* Axis number */
+   int ival;                     /* Int attribute value */
+   int len;                      /* Length of attrib string */
+   int nax;                      /* Number of base Frame axes */
+   int nc;                       /* No. characters read by astSscanf */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the Plot structure. */
+   this = (AstPlot *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Get the number of base Frame axis (2 for a Plot, 3 for a Plot3D). */
+   nax = astGetNin( this );
+
+/* Indicate that the current bound box should not be changed during the
+   execution of this function (this may happen if a grid is drawn to get
+   the default value for an attribute such as Labelling). */
+   Boxp_freeze = 1;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* Tol. */
+/* ---- */
+   if ( !strcmp( attrib, "tol" ) ) {
+      dval = astGetTol( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Grid. */
+/* ----- */
+   } else if ( !strcmp( attrib, "grid" ) ) {
+      ival = GetUsedGrid( this, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* TickAll. */
+/* -------- */
+   } else if ( !strcmp( attrib, "tickall" ) ) {
+      ival = astGetTickAll( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* ForceExterior. */
+/* -------------- */
+   } else if ( !strcmp( attrib, "forceexterior" ) ) {
+      ival = astGetForceExterior( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Invisible. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "invisible" ) ) {
+      ival = astGetInvisible( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Border. */
+/* ------- */
+   } else if ( !strcmp( attrib, "border" ) ) {
+      ival = GetUsedBorder( this, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* ClipOp. */
+/* ------- */
+   } else if ( !strcmp( attrib, "clipop" ) ) {
+      ival = astGetClipOp( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Clip. */
+/* ----- */
+   } else if ( !strcmp( attrib, "clip" ) ) {
+      ival = astGetClip( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Grf. */
+/* ---- */
+   } else if ( !strcmp( attrib, "grf" ) ) {
+      ival = astGetGrf( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* DrawTitle. */
+/* --------- */
+   } else if ( !strcmp( attrib, "drawtitle" ) ) {
+      ival = astGetDrawTitle( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Escape. */
+/* ------- */
+   } else if ( !strcmp( attrib, "escape" ) ) {
+      ival = astGetEscape( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LabelAt(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "labelat(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = GetUsedLabelAt( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Centre(axis). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "centre(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = GetUsedCentre( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Gap. */
+/* ---- */
+   } else if ( !strcmp( attrib, "gap" ) ) {
+      dval = GetUsedGap( this, 0, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Gap(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "gap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = GetUsedGap( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* LogGap. */
+/* ---- */
+   } else if ( !strcmp( attrib, "loggap" ) ) {
+      dval = GetUsedLogGap( this, 0, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* LogGap(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "loggap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = GetUsedLogGap( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* NumLabGap. */
+/* -------- */
+   } else if ( !strcmp( attrib, "numlabgap" ) ) {
+      dval = astGetNumLabGap( this, 0 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* NumLabGap(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "numlabgap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = astGetNumLabGap( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* TextLabGap. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "textlabgap" ) ) {
+      dval = astGetTextLabGap( this, 0 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* TextLabGap(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "textlabgap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = astGetTextLabGap( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* LabelUp. */
+/* -------- */
+   } else if ( !strcmp( attrib, "labelup" ) ) {
+      ival = astGetLabelUp( this, 0 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LabelUp(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "labelup(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = astGetLabelUp( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LogPlot. */
+/* -------- */
+   } else if ( !strcmp( attrib, "logplot" ) ) {
+      ival = astGetLogPlot( this, 0 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LogPlot(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "logplot(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = astGetLogPlot( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LogLabel. */
+/* -------- */
+   } else if ( !strcmp( attrib, "loglabel" ) ) {
+      ival = GetUsedLogLabel( this, 0, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LogLabel(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "loglabel(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetUsedLogLabel( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LogTicks. */
+/* -------- */
+   } else if ( !strcmp( attrib, "logticks" ) ) {
+      ival = GetUsedLogTicks( this, 0, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LogTicks(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "logticks(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetUsedLogTicks( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* NumLab. */
+/* -------- */
+   } else if ( !strcmp( attrib, "numlab" ) ) {
+      ival = astGetNumLab( this, 0 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* NumLab(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "numlab(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = astGetNumLab( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* MinTick. */
+/* -------- */
+   } else if ( !strcmp( attrib, "mintick" ) ) {
+      ival = GetUsedMinTick( this, 0, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* MinTick(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "mintick(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetUsedMinTick( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* TextLab. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "textlab" ) ) {
+      ival = GetUsedTextLab( this, 0, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* TextLab(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "textlab(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetUsedTextLab( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* DrawAxes. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "drawaxes" ) ) {
+      ival = astGetDrawAxes( this, 0 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* DrawAxes(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "drawaxes(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetDrawAxes( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Abbrev. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "abbrev" ) ) {
+      ival = astGetAbbrev( this, 0 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Abbrev(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "abbrev(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetAbbrev( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LabelUnits. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "labelunits" ) ) {
+      ival = GetUsedLabelUnits( this, 0, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LabelUnits(axis). */
+/* ----------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "labelunits(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetUsedLabelUnits( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Style. */
+/* ------ */
+   } else if ( !strcmp( attrib, "style" ) ) {
+      ival = GetUseStyle( this, AST__BORDER_ID, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Style(label). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "style(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetUseStyle( this, FullForm( GrfLabels, label, attrib, "astGet", astGetClass( this ), status ), status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Font. */
+/* ----- */
+   } else if ( !strcmp( attrib, "font" ) ) {
+      ival = GetUseFont( this, AST__TEXTLABS_ID, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Font(label). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "font(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetUseFont( this, FullForm( GrfLabels, label, attrib, "astGet", astGetClass( this ), status ), status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Colour. */
+/* ------- */
+   } else if ( !strcmp( attrib, "colour" ) ) {
+      ival = GetUseColour( this, AST__TEXTLABS_ID, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Colour(label). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "colour(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetUseColour( this, FullForm( GrfLabels, label, attrib, "astGet", astGetClass( this ), status ), status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Color. */
+/* ------ */
+   } else if ( !strcmp( attrib, "color" ) ) {
+      ival = GetUseColour( this, AST__TEXTLABS_ID, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Color(label). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "color(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetUseColour( this, FullForm( GrfLabels, label, attrib, "astGet", astGetClass( this ), status ), status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Width. */
+/* ------ */
+   } else if ( !strcmp( attrib, "width" ) ) {
+      dval = GetUseWidth( this, AST__BORDER_ID, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+
+/* Width(label). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "width(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      dval = GetUseWidth( this, FullForm( GrfLabels, label, attrib, "astGet", astGetClass( this ), status ), status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Size. */
+/* ----- */
+   } else if ( !strcmp( attrib, "size" ) ) {
+      dval = GetUseSize( this, AST__TEXTLABS_ID, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Size(label). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "size(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      dval = GetUseSize( this, FullForm( GrfLabels, label, attrib, "astGet", astGetClass( this ), status ), status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* TitleGap. */
+/* --------- */
+   } else if ( !strcmp( attrib, "titlegap" ) ) {
+      dval = astGetTitleGap( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* MajTickLen. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "majticklen" ) ) {
+      dval = GetUsedMajTickLen( this, 0, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* MajTickLen(axis). */
+/* ----------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "majticklen(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = GetUsedMajTickLen( this, axis - 1, status );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* MinTickLen. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "minticklen" ) ) {
+      dval = astGetMinTickLen( this, 0 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* MinTickLen(axis). */
+/* ----------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "minticklen(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      dval = astGetMinTickLen( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Labelling. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "labelling" ) ) {
+      ival = GetUsedLabelling( this, status );
+      if ( astOK ) {
+         result = ival ? xlbling[1] : xlbling[0];
+      }
+
+/* Edge(axis). */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "edge(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = GetUsedEdge( this, axis - 1, status );
+      if ( astOK ) {
+         if( ival == LEFT ){
+            result = "left";
+         } else if( ival == RIGHT ){
+            result = "right";
+         } else if( ival == TOP ){
+            result = "top";
+         } else if( ival == BOTTOM ){
+            result = "bottom";
+         } else {
+            result = "<bad>";
+         }
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Unfreeze the bound box so that it may be updated by subsequent
+   plotting functions. */
+   Boxp_freeze = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *GetDrawnTicks( AstPlot *this, int axis, int major, int *status ){
+/*
+*+
+*  Name:
+*     astGetDrawnTicks
+
+*  Purpose:
+*     Return information about the ticks last drawn by astGrid.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     AstPointSet *GetDrawnTicks( AstPlot *this, int axis, int major )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function returns a PointSet holding information about the
+*     tick marks (either major and minor) that were drawn by the previous
+*     invocation of astGrid. A NULL pointer is returned if astGrid has
+*     not yet been invoked.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot.
+*     axis
+*        The zero-based axis of the axis for which tick mark information
+*        is required.
+*     major
+*        Supply a non-zero value if information about the major tick
+*        marks is to be returned, and zero if information about the minor
+*        tick marks is to be returned.
+
+*  Returned Value:
+*     A pointSet with one point for every tick of the requested type drawn
+*     by astGrid for the specified axis. Each point has 2 coordinate values,
+*     being the graphics coordinates at the start of the tick mark. The
+*     returned PointSet pointer should be annulled when no longer needed.
+
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *result = NULL;
+   double *ptr[ 3 ];
+   int n;
+
+/* Check the global status. */
+   if( !astOK ) return result;
+
+/* Report an error if the supplied axis value is incorrect. */
+   if( axis < 0 || axis > 1 ) {
+      astError( AST__INTER, "astGetDrawnTicks(Plot): Supplied \"axis\" "
+                "value is %d - should 0 or 1 (internal AST programming "
+                "error).", status, axis );
+      n = 0;
+
+/* If OK, get the number of stored tick marks. */
+   } else {
+      n = major ? this->majtickcount[ axis ] : this->mintickcount[ axis ];
+   }
+
+/* Check that information is available. */
+   if( n > 0 && astOK ) {
+
+/* Create a PointSet with the required size. */
+      result = astPointSet( n, 2, "", status );
+
+/* Store pointers to the arrays within the Plot that hold the required
+   tick marks positions and types. */
+      ptr[ 0 ] = major ? this->majtickgx[ axis ] : this->mintickgx[ axis ];
+      ptr[ 1 ] = major ? this->majtickgy[ axis ] : this->mintickgy[ axis ];
+      astSetPoints( result, ptr );
+   }
+
+/* Return the PointSet. */
+   return result;
+}
+
+static double GetTicks( AstPlot *this, int axis, double *cen, double **ticks,
+                        int *nmajor, double **minticks, int *nminor,
+                        int format_set, int *inval, double *refval,
+                        GetTicksStatics **pstatics, const char *method,
+                        const char *class, int *status ){
+/*
+*  Name:
+*     GetTicks
+
+*  Purpose:
+*     Obtain a list of logarithmically or linearly spaced tick mark values for
+*     a single axis in a 2-D physical coordinate Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     double GetTicks( AstPlot *this, int axis, double *cen, double **ticks,
+*                      int *nmajor, double **minticks, int *nminor,
+*                      int format_set, int *inval, double *refval,
+*                      GetTicksStatics **pstatics, const char *method,
+*                      const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     For linearly spaced major ticks the "gap" returned by this function
+*     is the constant difference between adjacent major tick marks. For
+*     logarithmically spaced major ticks the "gap" returned by this
+*     function is the constant ratio between adjacent major tick marks.
+*
+*     If a gap size has been specified using attribute Gap (or LogGap for
+*     logarithmic ticks) supplied, the specified value is returned, and used
+*     to determine the tick values. If no gap size is supplied, a default
+*     gap size is used and returned.
+*
+*     All this is over-ridden if the astSetTickValues method has been
+*     called to store explicit tick mark values in the Plot structure.
+*     In this case, the values supplied using astSetTickValues are
+*     returned.
+
+*  Parameters:
+*     this
+*        The Plot. Supplying a NULL pointer will cause statics resources
+*        to be released.
+*     axis
+*        The zero-based index of the axis to use.
+*     cen
+*        Pointer to the supplied axis value at which to put a single
+*        central tick. Other ticks will be placed evenly on either side
+*        of this tick. If AST__BAD is provided, a value will be used
+*        which would put a tick at an axis value of one. The used value
+*        is returned.
+*     ticks
+*        Pointer to a place at which to return a pointer to the memory in
+*        which are stored the major tick values to be used. This pointer
+*        should be freed using astFree when no longer needed. The number of
+*        values in the array is given by the value returned by parameter
+*        "nmajor".
+*     nmajor
+*        A pointer to a location at which to return the number of major
+*        ticks.
+*     minticks
+*        Pointer to a place at which to return a pointer to the memory in
+*        which are stored the minor tick values to be used. This pointer
+*        should be freed using astFree when no longer needed. The number of
+*        values in the array is given by the value returned by parameter
+*        "nminor". The minor tick marks values returned in this array are
+*        the ones stored in the Plot via a call to the astSetTickValues
+*        function. If this function has not been called, then a NULL
+*        pointer is returned, and the "nminor" value is returned holding the
+*        number of divisions between major ticks.
+*     nminor
+*        A pointer to a location at which to return either the number of
+*        division into which each gap should be divided when drawing minor
+*        tick marks (if "*minticks" is returned holding NULL), or the
+*        total number of minor tick values stored in "*minticks" (if
+*        "*minticks" is returned non-NULL). The number of divisions
+*        between major tick values is one more than the number of minor
+*        tick marks.
+*     format_set
+*        Indicates if an explicit format has been set for the axis. If
+*        not, "cen" is always assumed to be AST__BAD, and any specified
+*        Gap value is rounded to the nearest "nice" value. This has
+*        to be done because the algorithm for choosing a format avoiding
+*        unnecessary precision only works if the gap size causes 1 digit to
+*        change between adjacent labels.
+*     inval
+*        A pointer to a location at which to return a flag indicating if
+*        any invalid physical coordinates were encountered.
+*     refval
+*        A pointer to a location at which to return a value for the other
+*        axis which can be used when normalizing the returned tick mark
+*        values.
+*     pstatics
+*        Address of a pointer to a structure holding static data values
+*        used within this function. A NULL pointer should be supplied on
+*        the first invocation (dynamic memory will then be allocated to
+*        hold ths structure). The memory is freed when a NULL value for
+*        "this" is supplied.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The used gap size.
+
+*  Notes:
+*     -  This function allocates some static resources on its first
+*     invocation, which should be released when no longer needed, or when
+*     a different Plot is supplied, by calling this function with a NULL
+*     pointer for parameter "this". All other parameters are ignored.
+*     -  This function assumes that the physical coordinate system is 2
+*     dimensional, and it should not be used if this is not the case.
+*     -  An error is reported if the region containing valid physical
+*     coordinates is too small to use.
+*     -  If an error has already occurred, or if this function should fail
+*     for any reason, then a NULL pointer is returned in "ticks", zero
+*     is returned for the number of major and minor ticks marks.
+*/
+
+/* Local Variables: */
+   GetTicksStatics *statics; /* Pointer to statics structure */
+   double *tick;             /* Pointer to next tick value */
+   double cen0;              /* Supplied value of cen */
+   double dran;              /* Dynamic range of axis values */
+   double frac;              /* Fraction of plot area holding good coords */
+   double gap;               /* Supplied value for Gap or LogGap */
+   double log_used_gap;      /* Log10( the used gap size ) */
+   double maxv;              /* Max axis value */
+   double minv;              /* Min axis value */
+   double new_used_gap;      /* New value for the used gap size */
+   double old_used_gap;      /* Old value for the used gap size */
+   double test_gap;          /* Trial gap size */
+   double used_cen;          /* Used value of cen */
+   double used_gap;          /* The used gap size */
+   int findcen;              /* Find a new centre value? */
+   int gap_too_small;        /* Test gap too small? */
+   int gap_too_large;        /* Test gap too large? */
+   int i;                    /* Axis index */
+   int ihi;                  /* Highest tick mark index */
+   int ilo;                  /* Lowest tick mark index */
+   int nochange;             /* No. of ineffective attempts to change gap size */
+
+/* Initialise the returned information. */
+   *ticks = NULL;
+   *minticks = NULL;
+   *nmajor = 0;
+   *nminor = 0;
+
+/* Get a pointer to the supplied statics object. */
+   statics = *pstatics;
+
+/* If a NULL pointer has been supplied for "this", release the resources
+   allocated on the first call to this function, and return. */
+   if( !this ){
+      if( statics ) {
+         if( statics->map ) statics->map = astAnnul( statics->map );
+         if( statics->pset ) statics->pset = astAnnul( statics->pset );
+         if( statics->frame ) statics->frame = astAnnul( statics->frame );
+         *pstatics = astFree( statics );
+      }
+      return 0.0;
+   }
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* If no statics structure was supplied, create one now and initialise it. */
+   if( !statics ) {
+      statics = astMalloc( sizeof( GetTicksStatics ) );
+      if( statics ) {
+         statics->pset=NULL;
+         *pstatics = statics;
+      }
+   }
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   maxv = 0.0;
+   minv = 0.0;
+   used_cen = 0.0;
+   used_gap = 0.0;
+   ihi = 0;
+   ilo = 0;
+
+/* If this is the first call to this function, do some initialisation. */
+   if( !statics->pset ){
+
+/* Get the Mapping from Base to Current Frame in the Plot. */
+      statics->map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Get a pointer to the current Frame from the Plot. */
+      statics->frame = astGetFrame( this, AST__CURRENT );
+
+/* Get initial guesses at suitable gaps for each axis. A PointSet is
+   returned holding sorted values (non-normalized) for the physical axes. */
+      statics->pset = DefGap( this, statics->defgaps, statics->ngood, &frac, &statics->bad, method, class, status );
+
+/* Store the maximum and minimum number of major tick marks along each
+   axis. These numbers are reduced if only a small part of the plotting
+   area contains valid coordinates, so that the tick marks do not end up
+   to close together. */
+      statics->maxticks = (int) ( 0.5 + MAJTICKS_MAX*sqrt( frac ) );
+      statics->mintick = (int) ( 0.5 + MAJTICKS_MIN*sqrt( frac ) );
+      if( statics->mintick < 3 ) statics->mintick = 3;
+      if( statics->maxticks < 8 ) statics->maxticks = 8;
+      if( statics->maxticks < statics->mintick ) statics->maxticks = statics->mintick;
+
+/* Get a pointer to the data in the PointSet. */
+      statics->ptr = astGetPoints( statics->pset );
+
+/* Find a typical value on each axis. */
+      for( i = 0; i < 2 && astOK; i++ ){
+         statics->typval[ i ] = Typical( statics->ngood[ i ], statics->ptr[ i ], -DBL_MAX, DBL_MAX,
+                                statics->width + i, status );
+      }
+   }
+
+/* Return the flag indicating if any regions of invalid physical coordinates
+   were found. */
+   *inval = statics->bad;
+
+/* Return the typical value on the other axis. */
+   *refval = statics->typval[ 1 - axis ];
+
+/* See if any tick marks values have been stored in the Plot structure
+   using astSetTickValues. If so, copy the tick mark values to the
+   returned arrays and exit with a gap size determined by the first two
+   ticks. */
+   if( this->nmajtickval[ axis ] > 0 ) {
+      *ticks = astStore( NULL, this->majtickval[ axis ],
+                         this->nmajtickval[ axis ]*sizeof(double) );
+      *minticks = astStore( NULL, this->mintickval[ axis ],
+                         this->nmintickval[ axis ]*sizeof(double) );
+      *nmajor = this->nmajtickval[ axis ];
+      *nminor = this->nmintickval[ axis ];
+
+      if( *nmajor > 1 && (*ticks)[ 0 ] != AST__BAD &&
+                         (*ticks)[ 1 ] != AST__BAD ) {
+         used_gap = fabs( (*ticks)[ 1 ] - (*ticks)[ 0 ] );
+      } else {
+         used_gap = AST__BAD;
+      }
+
+      return used_gap;
+
+   }
+
+/* See if the user has specified a gap size. The default for astGetLogTicks is
+   determined in DefGaps, so we can now decide whether to use attribute Gap
+   or LogGap to get the user-supplied gap size. Obtain the requested gap
+   attribute values for both physical axes. */
+   if( astGetLogTicks( this, axis ) ) {
+      gap = astGetLogGap( this, axis );
+   } else {
+      gap = astGetGap( this, axis );
+   }
+
+/* Find the maximum and minimum value in the plotting area. DefGap will
+   have reported an error if minv*maxv is negative or zero. */
+   if( astOK ) {
+      maxv = statics->ptr[ axis ][ statics->ngood[ axis ] - 1 ];
+      minv = statics->ptr[ axis ][ 0 ];
+   }
+
+/* First deal with logarithmically spaced ticks. */
+   dran = ( minv != 0.0 ) ? maxv/minv : 0.0;
+   if( astGetLogTicks( this, axis ) ) {
+
+/* If the ratio of max and min data value is not larger than 10, report an
+   error. */
+      dran = ( minv != 0.0 ) ? maxv/minv :0.0;
+      if( dran < 10.0 && dran > 0.1 ) {
+         astError( AST__VSMAL, "%s(%s): Cannot produce logarithmically "
+                   "spaced major tick marks on axis %d since the dynamic "
+                   "range of the axis is too small.", status, method, class, axis + 1 );
+      }
+
+/* Should we find a new value for "cen"? */
+      findcen = !cen || *cen == AST__BAD || !format_set;
+
+/* Try to find a "nice" gap size, so long as the caller has not supplied
+   a gap size. The default gap size obtained above is our initial guess. */
+      if( gap == AST__BAD && astOK ){
+
+/* Start off using the default gap found during the initialisation. */
+         test_gap = statics->defgaps[ axis ];
+
+/* Loop round until a gap size is found which gives an acceptable number
+   of tick marks. Upto 10 gap sizes are tried. */
+         for( i = 0; i < 10 && astOK; i++ ){
+
+/* Find a "nice" gap size close to the current test gap size. Also find
+   the number of minor tick marks to use with the nice gap size. Gaps for
+   logarithmic axes are always powers of ten. */
+            log_used_gap = (int) ( log10( test_gap ) + 0.5 );
+            if( log_used_gap == 0.0 ) {
+               log_used_gap = ( test_gap > 1.0 ) ? 1.0 : -1.0;
+            }
+            *nminor = 9;
+            used_gap = pow( 10.0, log_used_gap );
+
+/* If no value has been supplied for *cen, choose a value which would put
+   a major tick mark at the value 1 (or -1), and which is mid way between
+   the maximum and minimum axis value. */
+            if( findcen ) {
+               used_cen = pow( used_gap, (int) ( 0.5*log10( maxv*minv ) /
+                                                 log_used_gap ) );
+               if( maxv < 0 ) used_cen = -used_cen;
+            } else {
+               used_cen = *cen;
+            }
+
+/* Find the index of the highest tick which is not larger than the lowest
+   axis value. */
+            if( log_used_gap > 0.0 ) {
+               ilo = floor(  log10( minv/used_cen )/log_used_gap );
+            } else {
+               ilo = ceil(  log10( minv/used_cen )/log_used_gap );
+            }
+
+/* Find the index of the lowest tick which is not less than the highest
+   axis value. */
+            if( log_used_gap > 0.0 ) {
+               ihi = ceil(  log10( maxv/used_cen )/log_used_gap );
+            } else {
+               ihi = floor(  log10( maxv/used_cen )/log_used_gap );
+            }
+
+/* Find the total number of tick marks. */
+            *nmajor = ihi - ilo + 1;
+
+/* If the number of ticks is unacceptable, try a different gap size. If the
+   gap was too large to produce any ticks, try using half the gap size. */
+            if( *nmajor <= 0 ) {
+               test_gap = sqrt( test_gap );
+
+/* If there were some ticks, but not enough, decrease the gap size in
+   proportion to the shortfall. */
+            } else if( *nmajor < statics->mintick ){
+               test_gap = pow( test_gap, (double)( *nmajor )/(double)( statics->mintick ) );
+
+/* If there were too many ticks, increase the gap size in proportion to the
+   excess. */
+            } else if( *nmajor > statics->maxticks ){
+               test_gap = pow( test_gap, (double)( *nmajor )/(double)( statics->maxticks ) );
+
+/* If the number of ticks is acceptable, break out of the loop early.*/
+            } else {
+               break;
+            }
+         }
+
+/* Increase the tick coverage by one at each end to cover up the gaps. */
+         ilo--;
+         ihi++;
+         *nmajor += 2;
+
+/* If an explicit gap size was supplied, use it. */
+      } else if( astOK ) {
+
+/* Check it is usable. */
+         if( gap == 0.0 && astOK ) {
+            astError( AST__ATTIN, "%s(%s): Invalid value zero given for "
+                      "attribute LogGap(%d).", status, method, class, axis + 1 );
+
+         } else if( gap < 0.0 && astOK ) {
+            astError( AST__ATTIN, "%s(%s): Invalid negative value %f given for "
+                      "attribute LogGap(%d).", status, method, class, gap, axis + 1 );
+
+/* If necessary, take its reciprocal in order to ensure that the absolute
+   tick mark values get smaller or larger as required. */
+         } else {
+
+            used_gap = gap;
+            if( fabs( maxv ) < fabs( minv ) ) {
+               if( gap > 1.0 ) used_gap = 1.0/gap;
+            } else {
+               if( gap < 1.0 ) used_gap = 1.0/gap;
+            }
+
+/* Find the nearest power of 10 ( do not allow 10**0 (=1.0) to be used). */
+            log_used_gap = (int) ( log10( used_gap ) + 0.5 );
+            if( log_used_gap == 0.0 ) {
+              log_used_gap = ( gap > 1.0 ) ? 1.0 : -1.0;
+            }
+            used_gap = pow( 10.0, log_used_gap );
+
+/* We always use 9 minor intervals. */
+            *nminor = 9;
+
+/* If no value has been supplied for *cen, choose a value which would put
+   a major tick mark at the value 1 (or -1), and which is mid way between
+   the maximum and minimum axis value. */
+            if( findcen ) {
+               used_cen = pow( used_gap, (int) ( 0.5*log10( maxv*minv ) /
+                                                 log_used_gap ) );
+               if( maxv < 0 ) used_cen = -used_cen;
+            } else {
+               used_cen = *cen;
+            }
+
+/* Find the index of the highest tick which is not larger than the lowest
+   axis value. */
+            if( log_used_gap > 0.0 ) {
+               ilo = floor(  log10( minv/used_cen )/log_used_gap );
+            } else {
+               ilo = ceil(  log10( minv/used_cen )/log_used_gap );
+            }
+
+/* Find the index of the lowest tick which is not less than the highest
+   axis value. */
+            if( log_used_gap > 0.0 ) {
+               ihi = ceil(  log10( maxv/used_cen )/log_used_gap );
+            } else {
+               ihi = floor(  log10( maxv/used_cen )/log_used_gap );
+            }
+
+/* Find the total number of tick marks. */
+            *nmajor = ihi - ilo + 1;
+            if( *nmajor < 2 && astOK ) {
+               astError( AST__ATTIN, "%s(%s): Unusable value %f given for "
+                      "attribute LogGap(%d).", status, method, class, gap, axis + 1 );
+
+            }
+         }
+      }
+
+/* Allocate memory to hold the tick values themselves. */
+      *ticks = (double *) astMalloc( sizeof( double )*( *nmajor ) );
+      if( astOK ) {
+
+/* Store them. */
+         tick = *ticks;
+         for( i = ilo; i <= ihi; i++, tick++ ) {
+            *tick = used_cen*pow( used_gap, i );
+         }
+      }
+
+/* Store returned centre value. */
+      if( cen ) *cen = used_cen;
+
+/* Now deal with linearly spaced ticks */
+   } else {
+
+/* Store the supplied value of cen. */
+      cen0 = ( cen ) ? *cen : AST__BAD;
+
+/* If no format has been set for the axis, ensure AST__BAD is used for cen. */
+      if( !format_set ) cen0 = AST__BAD;
+
+/* Try to find a "nice" gap size, so long as the caller has not supplied
+   a gap size. The default gap size obtained above is our initial guess. */
+      if( gap == AST__BAD ){
+         old_used_gap = AST__BAD;
+
+/* Start of using the default gap found during the initialisation. */
+         test_gap = statics->defgaps[ axis ];
+         used_gap = 0.0;
+
+/* Initialise flags saying the test gap is too large or too small */
+         gap_too_large = 0;
+         gap_too_small = 0;
+
+/* So far, there have been no ineffective attempts to change the gap
+   size. */
+         nochange = 0;
+
+/* Loop round until a gap size is found which gives an acceptable number
+   of tick marks. Upto 10 gap sizes are tried. */
+         for( i = 0; i < 10 && astOK; i++ ){
+
+/* Find a "nice" gap size close to the current test gap size. Also find
+   the number of minor tick marks to use with the nice gap size. */
+            new_used_gap = astGap( statics->frame, axis, test_gap, nminor );
+
+/* Find the number and positions of major tick marks which would result
+   from using this gap size. Annul the memory used to hold any previous tick
+   data first. Only do this if the gap being used has actually changed,
+   otherwise we just retain the values created from the previous run with
+   this gap size. */
+            if( new_used_gap != used_gap ) {
+               nochange = 0;
+               old_used_gap = used_gap;
+               used_gap = new_used_gap;
+               if( *ticks ) *ticks = astFree( *ticks );
+               if( cen ) *cen = cen0;
+               *nmajor = FindMajTicks( statics->map, statics->frame, axis, *refval, statics->width[ 1-axis ],
+                                       used_gap, cen, statics->ngood[ axis ],
+                                       statics->ptr[ axis ], ticks, status );
+
+/* If the gap size has not changed do an extra pass through this loop,
+   but only do this a maximum of 25 times in succession. */
+            } else if( nochange < 25 ) {
+               nochange++;
+               i--;
+
+            } else if( astOK ){
+               astError( AST__VSMAL, "%s(%s): Cannot produce enough major "
+                         "tick marks on axis %d using the current axis "
+                         "format (\"%s\").", status, method, class, axis + 1,
+                         astGetFormat( statics->frame, axis ) );
+               break;
+            }
+
+/* If the number of ticks is unacceptable, try a different gap size. If the
+   gap was too large to produce any ticks, try using half the gap size. */
+            if( *nmajor == 0 ) {
+               test_gap *= 0.5;
+               gap_too_large = 1;
+               gap_too_small = 0;
+
+/* If there were some ticks, but not enough... */
+            } else if( *nmajor < statics->mintick ){
+
+/* If the previous test gap produced too many ticks, use the current gap
+   size. */
+               if( gap_too_small ) {
+                  break;
+
+/* Otherwise, decrease the gap size in proportion to the shortfall. */
+               } else {
+                  test_gap *= (double)( *nmajor )/(double)( statics->mintick );
+                  gap_too_large = 1;
+                  gap_too_small = 0;
+               }
+
+/* If there were too many ticks... */
+            } else if( *nmajor > statics->maxticks ){
+
+/* If the previous test gap produced too few ticks, use the previous gap
+   size. */
+               if( gap_too_large ) {
+                  used_gap = old_used_gap;
+                  if( *ticks ) *ticks = astFree( *ticks );
+                  if( cen ) *cen = cen0;
+                  *nmajor = FindMajTicks( statics->map, statics->frame, axis, *refval, statics->width[ 1-axis ],
+                                          used_gap, cen, statics->ngood[ axis ],
+                                          statics->ptr[ axis ], ticks, status );
+                  break;
+
+/* Otherwise, increase the gap size in proportion to the excess. */
+               } else {
+                  test_gap *= (double)( *nmajor )/(double)( statics->maxticks );
+                  gap_too_small = 1;
+                  gap_too_large = 0;
+               }
+
+/* If the number of ticks is acceptable, break out of the loop early.*/
+            } else {
+               break;
+            }
+         }
+
+/* If an explicit gap size was supplied, use it. */
+      } else {
+
+/* Find a likely value for the number of minor tick marks to use, and find
+   a nice gap close to the supplied gap (unless an explicit format has
+   been set). */
+         if( format_set ){
+            used_gap = gap;
+            (void) astGap( statics->frame, axis, used_gap, nminor );
+         } else {
+            used_gap = astGap( statics->frame, axis, gap, nminor );
+         }
+
+/* Find where the major ticks should be put. */
+         if( cen ) *cen = cen0;
+         *nmajor = FindMajTicks( statics->map, statics->frame, axis, *refval, statics->width[ 1-axis ],
+                                 used_gap, cen, statics->ngood[ axis ], statics->ptr[ axis ],
+                                 ticks, status );
+      }
+   }
+
+/* Report an error if no ticks can be found. */
+   if( *nmajor == 0 && astOK ) {
+      astError( AST__GRFER, "%s(%s): Cannot find any usable tick mark values. ", status, method,
+                class );
+   }
+
+/* If an error has occurred, annul the memory used to hold tick data, and
+   return zero ticks. */
+   if( !astOK ) {
+      *ticks = (double *) astFree( (void *) *ticks );
+      *nmajor = 0;
+      *nminor = 0;
+      used_gap = 0.0;
+   }
+
+/* Return. */
+   return used_gap;
+}
+
+static double GoodGrid( AstPlot *this, int *dim, AstPointSet **pset1,
+                        AstPointSet **pset2, const char *method,
+                        const char *class, int *status ){
+/*
+*  Name:
+*     GoodGrid
+
+*  Purpose:
+*     Create a grid covering the region containing good coordinates in a
+*     2-D physical coordinate Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     double GoodGrid( AstPlot *this, int *dim, AstPointSet **pset1,
+*                      AstPointSet **pset2, const char *method,
+*                      const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function creates two PointSets, one holding a square grid of
+*     graphics coordinates, and the other holding the corresponding physical
+*     coordinates (not normalized). The grid covers just the area containing
+*     good physical coordinates. The points are stored row by row in the
+*     returned PointSets.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     dim
+*        A pointer to an integer in which to store the number of samples
+*        along each edge of the returned grid.
+*     pset1
+*        A pointer to a location at which to store a pointer to the
+*        PointSet holding the graphics coordinates.
+*     pset2
+*        A pointer to a location at which to store a pointer to the
+*        PointSet holding the physical coordinates.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The fraction of the plotting area containing good physical
+*     coordinates.
+
+*  Notes:
+*     -  This function assumes that the physical coordinate system is 2
+*     dimensional, and it should not be used if this is not the case.
+*     -  The returned PointSets should be annulled when no longer needed,
+*     using astAnnul.
+*     -  An error is reported if the region containing valid physical
+*     coordinates is too small to use.
+*     -  A function value of zero, and NULL pointers are returned if an error
+*     has already occurred, or if this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;     /* Pointer to the Current Frame in the Plot */
+   AstMapping *map;   /* Pointer to "graphics to physical" mapping */
+   double **ptr1;     /* Pointer to physical axis value data */
+   double **ptr2;     /* Pointer to graphics axis value data */
+   double *pa;        /* Pointer to next value on 1st physical axis */
+   double *pb;        /* Pointer to next value on 2nd physical axis */
+   double *px;        /* Pointer to next value on 1st graphics axis */
+   double *py;        /* Pointer to next value on 2nd graphics axis */
+   double dx;         /* Cell size along graphics X (1st) axis */
+   double dy;         /* Cell size along graphics Y (2nd) axis */
+   double frac;       /* Fraction of good physical coordinates */
+   double xmax;       /* High X bound of region containing good phy. coords */
+   double xmin;       /* Low X bound of region containing good phy. coords */
+   double ymax;       /* High Y bound of region containing good phy. coords */
+   double ymin;       /* Low Y bound of region containing good phy. coords */
+   int j;             /* Element offset */
+   int ngood;         /* Number of grid points with good physical coords */
+   int size;          /* Number of grid points */
+
+/* Initialise the returned PointSet pointers. */
+   *pset1 = NULL;
+   *pset2 = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   ptr1 = NULL;
+   frac = 0.0;
+   xmax = 0.0;
+   xmin = 0.0;
+   ymax = 0.0;
+   ymin = 0.0;
+
+/* Get the Mapping from base (graphics) to current (physical) Frame in the
+   supplied Plot. */
+   map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Get a pointer to the Current Frame in the Plot. */
+   frm = astGetFrame( this, AST__CURRENT );
+
+/* Initialise the grid dimension. */
+   *dim = 16;
+
+/* We need a grid which has at least 4 good points. */
+   ngood = 0;
+   while( ngood < 4 && astOK ){
+
+/* Double the grid dimension. */
+      *dim *= 2;
+
+/* Report an error if the grid is now too big. */
+      if( *dim >= 512 ){
+         astError( AST__VSMAL, "%s(%s): The area of the plot containing "
+                   "usable coordinates on both axes is too small.", status, method,
+                   class );
+         break;
+      }
+
+/* Get two PointSets, one holding a regular grid of graphics coordinates,
+   and the other holding the corresponding physical coordinates. The grid
+   covers the entire plotting area with the current grid dimension. A
+   pointer to the physical axis values is returned. */
+      ptr2 = MakeGrid( this, frm, map, 1, *dim, this->xlo, this->xhi, this->ylo,
+                       this->yhi, 2, pset1, pset2, 0, method, class, status );
+
+/* Get the number of graphics axis values. */
+      size = astGetNpoint( *pset1 );
+
+/* Get a pointer to the graphics axis values. */
+      ptr1 = astGetPoints( *pset1 );
+
+/* Check the pointers can be used. */
+      if( astOK ){
+
+/* Find the bounds in graphics coordinates of the area enclosing the
+   good physical positions in the grid, and count the good positions. */
+         ngood = 0;
+
+         pa = ptr2[ 0 ];
+         pb = ptr2[ 1 ];
+         px = ptr1[ 0 ];
+         py = ptr1[ 1 ];
+
+         xmin = DBL_MAX;
+         xmax = -DBL_MAX;
+         ymin = DBL_MAX;
+         ymax = -DBL_MAX;
+
+         for( j = 0; j < size; j++ ){
+            if( *pa != AST__BAD && *pb != AST__BAD ){
+               if( *px < xmin ) xmin = *px;
+               if( *px > xmax ) xmax = *px;
+               if( *py < ymin ) ymin = *py;
+               if( *py > ymax ) ymax = *py;
+               ngood++;
+            }
+            px++;
+            py++;
+            pa++;
+            pb++;
+         }
+      }
+   }
+
+/* Store approximate fraction of the plotting area containing good
+   physical coordinates. */
+   if( astOK ) {
+      frac =  ( (double) ngood )/(double)( astGetNpoint( *pset1 ) );
+
+/* Get the size of each grid cell. */
+      dx = ptr1[0][1] - ptr1[0][0];
+      dy = ptr1[1][1] - ptr1[1][0];
+
+/* Extend the area containing good points by one grid cell. */
+      xmax += dx;
+      xmin -= dx;
+      ymax += dy;
+      ymin -= dy;
+
+/* If the area containing good points is significantly smaller than
+   the supplied area, create a new grid covering just the area containing
+   good positions. */
+      if( ( xmax - xmin ) < 0.9*( this->xhi - this->xlo ) ||
+                  ( ymax - ymin ) < 0.9*( this->yhi - this->ylo ) ){
+
+/* Find a new grid dimension which results in a cell size similar to
+   the one used to create the grid, but covering only the region containing
+   good physical coordinates. */
+         *dim *= astMAX( (xmax - xmin)/(this->xhi - this->xlo),
+                         (ymax - ymin)/(this->yhi - this->ylo) );
+         if( *dim < 32 ) *dim = 32;
+
+/* Annul the PointSet holding the current grid. */
+         *pset1 = astAnnul( *pset1 );
+         *pset2 = astAnnul( *pset2 );
+
+/* Create the new grid covering the region containing good physical
+   coordinates. */
+         (void) MakeGrid( this, frm, map, 1, *dim, xmin, xmax, ymin, ymax, 2,
+                          pset1, pset2, 0, method, class, status );
+      }
+   }
+
+/* Annul the Mapping from base to current Frame, and the pointer to the
+   Current Frame. */
+   map = astAnnul( map );
+   frm = astAnnul( frm );
+
+/* If an error has occurred, annul the two pointsets and indicate that
+   there are no good points in the plotting area. */
+   if( !astOK ){
+      *pset1 = astAnnul( *pset1 );
+      *pset2 = astAnnul( *pset2 );
+      frac = 0.0;
+   }
+
+/* Return. */
+   return frac;
+
+}
+
+static int GraphGrid( int dim, int disk, double xlo, double xhi, double ylo,
+                      double yhi, double **ptr1, int *status ){
+/*
+*  Name:
+*     GraphGrid
+
+*  Purpose:
+*     Fill an array with a square grid of graphics coordinates.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int GraphGrid( int dim, int disk, double xlo, double xhi, double ylo,
+*                    double yhi, double **ptr1, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function fills the supplied array with a square grid of graphics
+*     coordinates covering the supplied area. The points are stored row by
+*     row, i.e. if the cell size for the grid is (dx,dy), the first point
+*     is (xmin,ymin), followed by (xmin+dx,ymin), (xmin+2*dx,ymin), up to
+*     (xmin+(dim-1)*dx,ymin), followed by the next row (xmin,ymin+dy),
+*     (xmin+dx,ymin+dy), etc.
+
+*  Parameters:
+*     dim
+*        The number of samples along each edge of the grid.
+*     disk
+*        If non-zero, the corners of the grid are omitted, resulting in a
+*        grid that is more disk like than rectangular.
+*     xlo
+*        The lower bound on the first axis of the region to be covered
+*        by the grid.
+*     xhi
+*        The upper bound on the first axis of the region to be covered
+*        by the grid.
+*     ylo
+*        The lower bound on the second axis of the region to be covered
+*        by the grid.
+*     yhi
+*        The upper bound on the second axis of the region to be covered
+*        by the grid.
+*     ptr1
+*        A pointer to an array of two pointers giving the start of the two
+*        arrays to receive the values for each of the two axes of the graphics
+*        coordinate data.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of points in the grid. If "disk" is zero, this will be
+*     the square of "dim". If "disk" is non-zero, this will be less than
+*     the square of "dim" to account for the lack of corner points.
+
+*/
+
+/* Local Variables: */
+   double *px;
+   double *py;
+   double cen;
+   double dx;
+   double dy2;
+   double dy;
+   double dx2;
+   double r2;
+   double y;
+   int i;
+   int j;
+   int ok;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Find the cell size. */
+   dx = ( xhi - xlo )/(double)( dim - 1 );
+   dy = ( yhi - ylo )/(double)( dim - 1 );
+
+/* Store the mid cell index. */
+   cen = 0.5*( dim - 1 );
+
+/* Store the squared radius of the disk. */
+   r2 = 1.9*cen*cen;
+
+/* Initialise pointers to the start of the two arrays to recieve the
+   returned graphics values for each axis. */
+   px = ptr1[ 0 ];
+   py = ptr1[ 1 ];
+
+/* Loop round row. */
+   for( j = 0; j < dim; j++ ){
+      dy2 = j - cen;
+      dy2 *= dy2;
+
+/* Get the Y coordinate of the current row. */
+      y = ylo + j*dy;
+
+/* Loop round each column in the current row. */
+      for( i = 0; i < dim; i++ ){
+
+/* If we are forming a disk rather than a square, check if this point is
+   sufficiently close to the centre to be included in the disk. */
+         if( disk ) {
+            dx2 = i - cen;
+            dx2 *= dx2;
+            ok = ( dx2 + dy2 <= r2 );
+         } else {
+            ok = 1;
+         }
+
+/* Store the coordinates of the current grid point. */
+         if( ok ) {
+            *(px++) = xlo + i*dx;
+            *(py++) = y;
+         }
+      }
+   }
+
+/* Return the used length of the PointSet. */
+   return (int)( px - ptr1[ 0 ] );
+}
+
+static void GrfPop( AstPlot *this, int *status ) {
+/*
+*++
+*  Name:
+c     astGrfPop
+f     AST_GRFPOP
+
+*  Purpose:
+*     Restore previously saved graphics functions used by a Plot.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astGrfPop( AstPlot *this )
+f     CALL AST_GRFPOP( THIS STATUS )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+c     This function restores a snapshot of the graphics functions
+c     stored previously by calling astGrfPush. The restored graphics
+c     functions become the current graphics functions used by the Plot.
+*
+c     The astGrfPush and astGrfPop functions are intended for situations
+c     where it is necessary to make temporary changes to the graphics
+c     functions used by the Plot. The current functions should first be
+c     saved by calling astGrfPush. New functions should then be registered
+c     using astGrfSet. The required graphics should then be produced.
+c     Finally, astGrfPop should be called to restore the original graphics
+c     functions.
+f     The AST_GRFPUSH and AST_GRFPOP functions are intended for situations
+f     where it is necessary to make temporary changes to the graphics
+f     functions used by the Plot. The current functions should first be
+f     saved by calling AST_GRFPUSH. New functions should then be registered
+f     using AST_GRFSET. The required graphics should then be produced.
+f     Finally, AST_GRFPOP should be called to restore the original graphics
+f     functions.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+f     - This routine returns without action if there are no snapshots to
+c     - This function returns without action if there are no snapshots to
+*     restore. No error is reported in this case.
+
+*--
+*/
+
+/* Local Variables: */
+   AstGrfPtrs *newframe;         /* Pointer to the stack frame to restore */
+   int i;                        /* Loop count */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Check the stack is not already empty. */
+   if( this->grfnstack > 0 ) {
+      this->grfnstack--;
+
+      if( astOK ) {
+         newframe = this->grfstack + this->grfnstack;
+
+         for( i = 0; i < AST__NGRFFUN; i++ ) {
+            this->grffun[i] = (newframe->grffun)[i];
+         }
+         this->GAttr = newframe->GAttr;
+         this->GBBuf = newframe->GBBuf;
+         this->GEBuf = newframe->GEBuf;
+         this->GFlush = newframe->GFlush;
+         this->GLine = newframe->GLine;
+         this->GMark = newframe->GMark;
+         this->GText = newframe->GText;
+         this->GCap = newframe->GCap;
+         this->GTxExt = newframe->GTxExt;
+         this->GScales = newframe->GScales;
+         this->GQch = newframe->GQch;
+      }
+   }
+}
+
+static void GrfPush( AstPlot *this, int *status ) {
+/*
+*++
+*  Name:
+c     astGrfPush
+f     AST_GRFPUSH
+
+*  Purpose:
+*     Save the current graphics functions used by a Plot.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astGrfPush( AstPlot *this )
+f     CALL AST_GRFPUSH( THIS STATUS )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+c     This function takes a snapshot of the graphics functions which are
+f     This routine takes a snapshot of the graphics functions which are
+*     currently registered with the supplied Plot, and saves the snapshot
+*     on a first-in-last-out stack within the Plot. The snapshot can be
+*     restored later using function
+c     astGrfPop.
+f     AST_GRFPOP.
+*
+c     The astGrfPush and astGrfPop functions are intended for situations
+c     where it is necessary to make temporary changes to the graphics
+c     functions used by the Plot. The current functions should first be
+c     saved by calling astGrfPush. New functions should then be registered
+c     using astGrfSet. The required graphics should then be produced.
+c     Finally, astGrfPop should be called to restore the original graphics
+c     functions.
+f     The AST_GRFPUSH and AST_GRFPOP functions are intended for situations
+f     where it is necessary to make temporary changes to the graphics
+f     functions used by the Plot. The current functions should first be
+f     saved by calling AST_GRFPUSH. New functions should then be registered
+f     using AST_GRFSET. The required graphics should then be produced.
+f     Finally, AST_GRFPOP should be called to restore the original graphics
+f     functions.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Local Variables: */
+   AstGrfPtrs *newframe;         /* Pointer to the new stack frame */
+   int i;                        /* Loop count */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Increment the number of frames on the stack. */
+   this->grfnstack++;
+
+/* Ensure the stack is large enough to hold this many frames. */
+   this->grfstack = (AstGrfPtrs *) astGrow( (void *) this->grfstack,
+                     this->grfnstack, sizeof( AstGrfPtrs ) );
+   if( astOK ) {
+
+/* Get a pointer to the new stack frame. */
+      newframe = this->grfstack + this->grfnstack - 1;
+
+/* Copy the graphics function pointers from the main Plot attributes
+   to the new stack frame. */
+      for( i = 0; i < AST__NGRFFUN; i++ ) {
+         (newframe->grffun)[i] = this->grffun[i];
+      }
+      newframe->GAttr = this->GAttr;
+      newframe->GBBuf = this->GBBuf;
+      newframe->GEBuf = this->GEBuf;
+      newframe->GFlush = this->GFlush;
+      newframe->GLine = this->GLine;
+      newframe->GMark = this->GMark;
+      newframe->GText = this->GText;
+      newframe->GCap = this->GCap;
+      newframe->GTxExt = this->GTxExt;
+      newframe->GQch = this->GQch;
+      newframe->GScales = this->GScales;
+   }
+}
+
+static void GrfSet( AstPlot *this, const char *name, AstGrfFun fun, int *status ){
+/*
+*++
+*  Name:
+c     astGrfSet
+f     AST_GRFSET
+
+*  Purpose:
+c     Register a graphics function for use by a Plot.
+f     Register a graphics routine for use by a Plot.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astGrfSet( AstPlot *this, const char *name, AstGrfFun fun )
+f     CALL AST_GRFSET( THIS, NAME, FUN, STATUS )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+c     This function can be used to select the underlying graphics
+c     functions to be used when the supplied Plot produces graphical output.
+c     If this function is not called prior to producing graphical
+c     output, then the underlying graphics functions selected at
+c     link-time (using the ast_link command) will be used. To use
+c     alternative graphics functions, call this function before
+c     the graphical output is created, specifying the graphics
+c     functions to be used. This will register the function for future
+c     use, but the function will not actually be used until the Grf
+c     attribute is given a non-zero value.
+f     This routine can be used to select the underlying graphics
+f     routines to be used when the supplied Plot produces graphical output.
+f     If this routine is not called prior to producing graphical
+f     output, then the underlying graphics routines selected at
+f     link-time (using the ast_link command) will be used. To use
+f     alternative graphics routines, call this routine before
+f     the graphical output is created, specifying the graphics
+f     routines to be used. This will register the routine for future
+f     use, but the routine will not actually be used until the Grf
+f     attribute is given a non-zero value.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+c        A name indicating the graphics function to be replaced.
+c        Various graphics functions are used by the
+c        Plot class, and any combination of them may be supplied by calling
+c        this function once for each function to be replaced. If any of the
+c        graphics functions are not replaced in this way, the
+c        corresponding functions in the graphics interface selected at
+c        link-time (using the ast_link command) are used. The allowed
+c        names are:
+f        A name indicating the graphics routine to be replaced.
+f        Various graphics routines are used by the
+f        Plot class, and any combination of them may be supplied by calling
+f        this routine once for each routine to be replaced. If any of the
+f        graphics routines are not replaced in this way, the
+f        corresponding routines in the graphics interface selected at
+f        link-time (using the ast_link command) are used. The allowed
+f        function names are:
+*
+*        - Attr -  Enquire or set a graphics attribute value
+*        - BBuf - Start a new graphics buffering context
+*        - Cap -  Inquire a capability
+*        - EBuf - End the current graphics buffering context
+*        - Flush - Flush all pending graphics to the output device
+*        - Line - Draw a polyline (i.e. a set of connected lines)
+*        - Mark -  Draw a set of markers
+*        - Qch -  Return the character height in world coordinates
+*        - Scales -  Get the axis scales
+*        - Text - Draw a character string
+*        - TxExt -  Get the extent of a character string
+*
+*        The string is case insensitive. For details of the interface
+*        required for each, see the sections below.
+c     fun
+f     FUN = INTEGER FUNCTION (Given)
+c        A Pointer to the function to be used to provide the
+c        functionality indicated by parameter name. The interface for
+c        each function is described below, but the function pointer should
+c        be cast to a type of AstGrfFun when calling astGrfSet.
+f        The name of the routine to be used to provide the
+f        functionality indicated by parameter NAME (the name
+f        should also appear in a Fortran EXTERNAL statement in the
+f        routine which invokes AST_GRFSET).
+*
+c        Once a function has been provided, a null pointer can be supplied
+c        in a subsequent call to astGrfSet to reset the function to the
+c        corresponding function in the graphics interface selected at
+c        link-time.
+f        Once a routine has been provided, the "null" routine AST_NULL can
+f        be supplied in a subsequent call to astGrfSet to reset the routine
+f        to the corresponding routine in the graphics interface selected at
+f        link-time. AST_NULL is defined in the AST_PAR include file.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Function Interfaces:
+*     All the functions listed below (except for "Cap") should return an
+*     integer value of 0 if an error occurs, and 1 otherwise. All x and y
+*     values refer
+f     to "graphics cordinates" as defined by the GRAPHBOX parameter of
+f     the AST_PLOT call which created the Plot.
+c     to "graphics cordinates" as defined by the graphbox parameter of
+c     the astPlot call which created the Plot.
+*
+c     The first parameter ("grfcon")
+f     The first argument (GRFCON)
+*     for each function is an AST KeyMap pointer that can be used by the
+*     called function to establish the context in which it is being called.
+*     The contents of the KeyMap are determined by the calling
+*     application, which should obtain a pointer to the KeyMap using the
+f     AST_GETGRFCONTEXT routine,
+c     astGetGrfContext function,
+*     and then store any necessary information in the KeyMap using the
+*     methods of the KeyMap class. Note, the functions listed below
+*     should never annul or delete the supplied KeyMap pointer.
+
+*  Attr:
+*     The "Attr" function returns the current value of a specified graphics
+*     attribute, and optionally establishes a new value. The supplied
+*     value is converted to an integer value if necessary before use.
+*     It requires the following interface:
+*
+c     int Attr( AstObject *grfcon, int attr, double value, double *old_value, int prim )
+f     INTEGER FUNCTION ATTR( GRFCON, ATT, VAL, OLDVAL, PRIM )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+c     - attr - An integer value identifying the required attribute.
+c       The following symbolic values are defined in grf.h:
+f     - ATT = INTEGER (Given) - An integer identifying the required attribute.
+f       The following symbolic values are defined in GRF_PAR:
+*       GRF__STYLE (Line style),
+*       GRF__WIDTH (Line width),
+*       GRF__SIZE (Character and marker size scale factor),
+*       GRF__FONT (Character font),
+*       GRF__COLOUR (Colour index).
+c     - value -
+f     - VAL = DOUBLE PRECISION (Given) -
+c       A new value to store for the attribute. If this is AST__BAD
+*       no value is stored.
+c     - old_value - A pointer to a double in which to return
+f     - OLDVAL = DOUBLE PRECISION (Returned) - Returned holding
+*       the attribute value.
+c       If this is NULL, no value is returned.
+c     - prim -
+f     - PRIM = INTEGER (Given) -
+*       The sort of graphics primitive to be drawn with the new attribute.
+c       Identified by the following values defined in grf.h:
+f       Identified by the following values defined in GRF_PAR:
+*       GRF__LINE,
+*       GRF__MARK,
+*       GRF__TEXT.
+
+*  BBuf:
+*     The "BBuf" function should start a new graphics buffering context.
+*     A matching call to the function "EBuf" should be used to end the
+*     context. The nature of the buffering is determined by the underlying
+*     graphics system.
+*
+c     int BBuf( AstObject *grfcon )
+f     INTEGER FUNCTION BBUF( GRFCON )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+
+*  Cap:
+*     The "Cap" function is called to determine if the grf module has a
+*     given capability, as indicated by the "cap" argument:
+*
+c     int Cap( AstObject *grfcon, int cap, int value )
+f     INTEGER FUNCTION CAP( GRFCON, CAP, VALUE )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+c     - cap -
+f     - CAP = INTEGER (Given)
+*        The capability being inquired about. This will be one of the
+c        following constants defined in grf.h:
+f        following constants defined in GRF_PAR:
+*
+*        GRF__SCALES: This function should return a non-zero value if the
+*        "Scales" function is implemented, and zero otherwise. The supplied
+c        "value" argument should be ignored.
+f        VALUE argument should be ignored.
+*
+*        GRF__MJUST: This function should return a non-zero value if
+*        the "Text" and "TxExt" functions recognise "M" as a
+*        character in the justification string. If the first character of
+*        a justification string is "M", then the text should be justified
+*        with the given reference point at the bottom of the bounding box.
+*        This is different to "B" justification, which requests that the
+*        reference point be put on the baseline of the text, since some
+*        characters hang down below the baseline. If the "Text" or
+*        "TxExt" function cannot differentiate between "M" and "B",
+*        then this function should return zero, in which case "M"
+*        justification will never be requested by Plot. The supplied
+c        "value" argument should be ignored.
+f        VALUE argument should be ignored.
+*
+*        GRF__ESC: This function should return a non-zero value if the
+*        "Text" and "TxExt" functions can recognise and interpret
+*        graphics escape sequences within the supplied string (see
+*        attribute Escape). Zero should be returned if escape sequences
+*        cannot be interpreted (in which case the Plot class will interpret
+c        them itself if needed). The supplied "value" argument should be
+f        them itself if needed). The supplied VALUE argument should be
+*        ignored only if escape sequences cannot be interpreted by "Text" and
+c        "TxExt". Otherwise, "value" indicates whether "Text" and "TxExt"
+c        should interpret escape sequences in subsequent calls. If "value" is
+f        "TxExt". Otherwise, VALUE indicates whether "Text" and "TxExt"
+f        should interpret escape sequences in subsequent calls. If VALUE is
+*        non-zero then escape sequences should be interpreted by "Text" and
+*        "TxExt". Otherwise, they should be drawn as literal text.
+*
+c     - value -
+f     - VALUE = INTEGER (Given)
+c        The use of this parameter depends on the value of "cap" as
+f        The use of this parameter depends on the value of CAP as
+*        described above.
+
+*     - Returned Function Value:
+c        The value returned by the function depends on the value of "cap"
+f        The value returned by the function depends on the value of CAP
+*        as described above. Zero should be returned if the supplied
+*        capability is not recognised.
+
+*  EBuf:
+*     The "EBuf" function should end the current graphics buffering
+*     context. See the description of "BBuf" above for further details.
+*     It requires the following interface:
+*
+c     int EBuf( AstObject *grfcon )
+f     INTEGER FUNCTION EBUF( GRFCON )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+
+*  Flush:
+*     The "Flush" function ensures that the display device is up-to-date,
+*     by flushing any pending graphics to the output device. It
+*     requires the following interface:
+*
+c     int Flush( AstObject *grfcon )
+f     INTEGER FUNCTION FLUSH( GRFCON )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+
+*  Line:
+*     The "Line" function displays lines joining the given positions and
+*     requires the following interface:
+*
+c     int Line( AstObject *grfcon, int n, const float *x, const float *y )
+f     INTEGER FUNCTION LINE( GRFCON, N, X, Y )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+c     - n - The number of positions to be joined together.
+f     - N = INTEGER (Given) - The number of positions to be joined together.
+c     - x - A pointer to an array holding the "n" x values.
+f     - X( N ) = REAL (Given) - An array holding the "n" x values.
+c     - y - A pointer to an array holding the "n" y values.
+f     - Y( N ) = REAL (Given) - An array holding the "n" y values.
+
+*  Mark:
+*     The "Mark" function displays markers at the given positions. It
+*     requires the following interface:
+*
+c     int Mark( AstObject *grfcon, int n, const float *x, const float *y, int type )
+f     INTEGER FUNCTION MARK( GRFCON, N, X, Y, TYPE )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+c     - n - The number of positions to be marked.
+f     - N = INTEGER (Given) - The number of positions to be marked.
+c     - x - A pointer to an array holding the "n" x values.
+f     - X( N ) = REAL (Given) - An array holding the "n" x values.
+c     - y - A pointer to an array holding the "n" y values.
+f     - Y( N ) = REAL (Given) - An array holding the "n" y values.
+c     - type - An integer which can be used to indicate the type of marker
+c       symbol required.
+f     - TYPE = INTEGER (Given) - An integer which can be used to indicate
+f       the type of marker symbol required.
+
+*  Qch:
+*     The "Qch" function returns the heights of characters drawn vertically
+*     and horizontally in graphics coordinates. It requires the following
+*     interface:
+*
+c     int Qch( AstObject *grfcon, float *chv, float *chh )
+f     INTEGER FUNCTION QCH( GRFCON, CHV, CHH )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+c     - chv - A pointer to the float which is to receive the height of
+f     - CHV = REAL (Returned) The height of
+*     characters drawn with a vertical baseline. This will be an
+*     increment in the X axis.
+c     - chh - A pointer to the float which is to receive the height of
+f     - CHH = REAL (Returned) The height of
+*     characters drawn with a horizontal baseline. This will be an
+*     increment in the Y axis.
+
+*  Scales:
+*     The "Scales" function returns two values (one for each axis) which
+*     scale increments on the corresponding axis into a "normal" coordinate
+*     system in which: 1) the axes have equal scale in terms of (for instance)
+*     millimetres per unit distance, 2) X values increase from left to
+*     right, and 3) Y values increase from bottom to top. It requires the
+*     following interface:
+*
+c     int Scales( AstObject *grfcon, float *alpha, float *beta )
+f     INTEGER FUNCTION SCALES( GRFCON, ALPHA, BETA )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+c     - alpha - A pointer to the float which is to receive the
+f     - ALPHA = REAL (Returned) The
+*     scale for the X axis (i.e. Xnorm = alpha*Xworld).
+c     - beta - A pointer to the float which is to receive the
+f     - BETA = REAL (Returned) The
+*     scale for the Y axis (i.e. Ynorm = beta*Yworld).
+
+*  Text:
+*     The "Text" function displays a character string at a given
+*     position using a specified justification and up-vector. It
+*     requires the following interface:
+*
+c     int Text( AstObject *grfcon, const char *text, float x, float y, const char *just,
+c               float upx, float upy )
+f     INTEGER FUNCTION TEXT( GRFCON, TEXT, X, Y, JUST, UPX, UPY )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+c     - text - Pointer to a null-terminated character string to be displayed.
+f     - TEXT = CHARACTER * ( * ) (Given) - The string to be displayed.
+c     - x - The reference x coordinate.
+f     - X = REAL (Given) - The reference x coordinate.
+c     - y - The reference y coordinate.
+f     - Y = REAL (Given) - The reference y coordinate.
+c     - just - A character string which specifies the location within the
+f     - JUST = CHARACTER * ( * ) (Given ) - A string which specifies the
+f        location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", or 'B' for "bottom", and specifies the
+*        vertical location of the reference position. Note, "bottom"
+*        corresponds to the base-line of normal text. Some characters
+*        (eg "y", "g", "p", etc) descend below the base-line. The second
+*        character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+c     - upx - The x component of the up-vector for the text.
+f     - UPX = REAL (Given) - The x component of the up-vector for the text.
+*        If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        left to right on the screen.
+c     - upy - The y component of the up-vector for the text.
+f     - UPX = REAL (Given) - The y component of the up-vector for the text.
+*        If necessary the supplied value should be negated
+*        to ensure that positive values always refer to displacements from
+*        bottom to top on the screen.
+
+*  TxExt:
+*     The "TxExt" function returns the corners of a box which would enclose
+*     the supplied character string if it were displayed using the
+*     Text function described above. The returned box includes any leading
+*     or trailing spaces. It requires the following interface:
+*
+c     int TxExt( AstObject *grfcon, const char *text, float x, float y, const char *just,
+c                float upx, float upy, float *xb, float *yb )
+f     INTEGER FUNCTION TXEXT( GRFCON, TEXT, X, Y, JUST, UPX, UPY, XB, YB )
+*
+c     - grfcon -
+f     - GRFCON = INTEGER (Given) -
+*       A KeyMap containing information passed from the calling application.
+c     - text - Pointer to a null-terminated character string to be displayed.
+f     - TEXT = CHARACTER * ( * ) (Given) - The string to be displayed.
+c     - x - The reference x coordinate.
+f     - X = REAL (Given) - The reference x coordinate.
+c     - y - The reference y coordinate.
+f     - Y = REAL (Given) - The reference y coordinate.
+c     - just - A character string which specifies the location within the
+f     - JUST = CHARACTER * ( * ) (Given ) - A string which specifies the
+f        location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. See "Text" above.
+c     - upx - The x component of the up-vector for the text.
+f     - UPX = REAL (Given) - The x component of the up-vector for the text.
+*        See "Text" above.
+c     - upy - The y component of the up-vector for the text.
+f     - UPX = REAL (Given) - The y component of the up-vector for the text.
+*        See "Text" above.
+c     - xb - An array of 4 elements in which to return the x coordinate of
+f     - XB( 4 ) = REAL (Returned) - Returned holding the x coordinate of
+*        each corner of the bounding box.
+c     - yb - An array of 4 elements in which to return the y coordinate of
+f     - YB( 4 ) = REAL (Returned) - Returned holding the y coordinate of
+*        each corner of the bounding box.
+
+*--
+*/
+
+/* Local Variables: */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   int ifun;               /* Index into grf function list */
+   void (* wrapper)();     /* Wrapper function for C Grf routine*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   wrapper = NULL;
+
+/* Store the current method and class for inclusion in error messages
+   generated by lower level functions. */
+   method = "astGrfSet";
+   class = astClass( this );
+
+/* Identify the supplied function name and get its integer index into the
+   list of grf functions. */
+   ifun = astGrfFunID( name, method, class );
+
+/* Store the pointer. */
+   if( astOK ) {
+      this->grffun[ifun] = fun;
+
+/* In general, the interface to each Grf function will differ for
+   different languages. So we need a wrapper function with a known fixed
+   interface which can be used to invoke the actual Grf function with
+   an interface suited to the language in use. Call astGrfWrapper to store
+   a wrapper to a suitable function which can invoke the supplied
+   grf function. Here, we assume that the supplied function has a C
+   interface, so we set up a C wrapper. If this function is being called
+   from another language, then the interface for this function within
+   that language should set up an appropriate wrapper after calling this
+   function, thus over-riding the C wrapper set up here. */
+      if( ifun == AST__GATTR ) {
+         wrapper = (AstGrfWrap) CGAttrWrapper;
+
+      } else if( ifun == AST__GBBUF ) {
+         wrapper = (AstGrfWrap) CGBBufWrapper;
+
+      } else if( ifun == AST__GEBUF ) {
+         wrapper = (AstGrfWrap) CGEBufWrapper;
+
+      } else if( ifun == AST__GFLUSH ) {
+         wrapper = (AstGrfWrap) CGFlushWrapper;
+
+      } else if( ifun == AST__GLINE ) {
+         wrapper = (AstGrfWrap) CGLineWrapper;
+
+      } else if( ifun == AST__GMARK ) {
+         wrapper = (AstGrfWrap) CGMarkWrapper;
+
+      } else if( ifun == AST__GTEXT ) {
+         wrapper = (AstGrfWrap) CGTextWrapper;
+
+      } else if( ifun == AST__GCAP ) {
+         wrapper = (AstGrfWrap) CGCapWrapper;
+
+      } else if( ifun == AST__GTXEXT ) {
+         wrapper = (AstGrfWrap) CGTxExtWrapper;
+
+      } else if( ifun == AST__GSCALES ) {
+         wrapper = (AstGrfWrap) CGScalesWrapper;
+
+      } else if( ifun == AST__GQCH ) {
+         wrapper = (AstGrfWrap) CGQchWrapper;
+
+      } else if( astOK ) {
+         astError( AST__INTER, "%s(%s): AST internal programming error - "
+                   "Grf function id %d not yet supported.", status, method, class,
+                   ifun );
+      }
+      astGrfWrapper( this, name, wrapper );
+   }
+}
+
+int astGrfFunID_( const char *name, const char *method, const char *class, int *status ) {
+/*
+*+
+*  Name:
+*     astGrfFunID
+
+*  Purpose:
+*     Return the integer identifier for a given GRF routine.
+
+*  Type:
+*     Hidden public function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int astGrfFunID( const char *name, const char *method,
+*                      const char *class )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function returns an integer identifying the named grf function.
+*     An error is reported if the named function is unknown. This function
+*     is used by non-class modules within AST (e.g. fplot.c) which is why
+*     it is public. It is not intended to be used by the public.
+
+*  Parameters:
+*     name
+*        The grf function name. Any unambiguous abbreviation will do.
+*        Case is ignored. The full list of grf function names is:
+*        "Attr BBuf EBuf Scales Flush Line Mark Qch Text TxExt". See
+*        grf_pgplot.c for details of these functions.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+
+*-
+*/
+
+/* Note that the list of identifiers here must be in the same order as the
+   sorted values of the constants AST__GATTR, AST__GFLUSH, etc */
+   return FullForm( "Attr Flush Line Mark Text TxExt Scales Qch Cap BBuf "
+                    "EBuf", name, "Grf function name (programming error)",
+                    method, class, status );
+}
+
+static char *GrfItem( int item, const char *text, int *axis, int *status ){
+/*
+*  Name:
+*     GrfItem
+
+*  Purpose:
+*     Return the textual name corresponding to a specified graphical item
+*     index.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     char *GrfItem( int item, const char *text, int *axis, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function returns a textual description of the graphical item
+*     with the supplied index.
+
+*  Parameters:
+*     item
+*        The index of the graphical item.
+*     text
+*        A pointer to a string which will be appended to the textual
+*        description of the graphical item. May be NULL.
+*     axis
+*        Pointer to a place in which to return the index (0,1, or 2) of
+*        the axis to which the attribute refers, If the attribute does
+*        not refer to a specific axis, -1 is returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated string holding the textual
+*     description of the graphical item, followed by any supplied "text".
+
+*  Notes:
+*     - An error is reported and a NULL pointer returned if the
+*     index does not correspond to any graphical item.
+*     - The returned pointer should be freed using astFree when it is no
+*     longer needed.
+*     - This function attempts to execute even if an error has already
+*     occurred.
+
+*/
+
+/* Local Variables: */
+   char *desc;              /* Pointer to the item description */
+   char *ret;               /* Pointer to the returned string */
+   int dlen;                /* Length of the item description */
+
+   if( axis ) *axis = -1;
+
+   if( item == AST__BORDER_ID ) {
+      desc = "Border";
+
+   } else if ( item == AST__GRIDLINE_ID ) {
+      desc = "Gridline";
+
+   } else if ( item == AST__GRIDLINE1_ID ) {
+      desc = "Axis 1 gridline";
+      if( axis ) *axis = 0;
+
+   } else if ( item == AST__GRIDLINE2_ID ) {
+      desc = "Axis 2 gridline";
+      if( axis ) *axis = 1;
+
+   } else if ( item == AST__GRIDLINE3_ID ) {
+      desc = "Axis 3 gridline";
+      if( axis ) *axis = 2;
+
+   } else if ( item == AST__CURVE_ID ) {
+      desc = "Curve";
+
+   } else if ( item == AST__NUMLABS_ID ) {
+      desc = "Numerical labels";
+
+   } else if ( item == AST__TEXTLABS_ID ) {
+      desc = "Textual labels";
+
+   } else if ( item == AST__TITLE_ID ) {
+      desc = "Title";
+
+   } else if ( item == AST__MARKS_ID ) {
+      desc = "Markers";
+
+   } else if ( item == AST__TEXT_ID ) {
+      desc = "Text string";
+
+   } else if ( item == AST__TICKS_ID ) {
+      desc = "Major and minor ticks";
+
+   } else if ( item == AST__AXIS1_ID ) {
+      desc = "Axis 1";
+      if( axis ) *axis = 0;
+
+   } else if ( item == AST__AXIS2_ID ) {
+      desc = "Axis 2";
+      if( axis ) *axis = 1;
+
+   } else if ( item == AST__AXIS3_ID ) {
+      desc = "Axis 3";
+      if( axis ) *axis = 2;
+
+   } else if ( item == AST__NUMLAB1_ID ) {
+      desc = "Axis 1 numerical labels";
+      if( axis ) *axis = 0;
+
+   } else if ( item == AST__NUMLAB2_ID ) {
+      desc = "Axis 2 numerical labels";
+      if( axis ) *axis = 1;
+
+   } else if ( item == AST__NUMLAB3_ID ) {
+      desc = "Axis 3 numerical labels";
+      if( axis ) *axis = 2;
+
+   } else if ( item == AST__TEXTLAB1_ID ) {
+      desc = "Axis 1 textual label";
+      if( axis ) *axis = 0;
+
+   } else if ( item == AST__TEXTLAB2_ID ) {
+      desc = "Axis 2 textual label";
+      if( axis ) *axis = 1;
+
+   } else if ( item == AST__TEXTLAB3_ID ) {
+      desc = "Axis 3 textual label";
+      if( axis ) *axis = 2;
+
+   } else if ( item == AST__TICKS1_ID ) {
+      desc = "Axis 1 tick marks";
+      if( axis ) *axis = 0;
+
+   } else if ( item == AST__TICKS2_ID ) {
+      desc = "Axis 2 tick marks";
+      if( axis ) *axis = 1;
+
+   } else if ( item == AST__TICKS3_ID ) {
+      desc = "Axis 3 tick marks";
+      if( axis ) *axis = 2;
+
+   } else {
+      desc = NULL;
+      if( astOK ){
+         astError( AST__INTER, "GrfItem: AST internal programming error - "
+                   "Invalid graphical item index %d supplied to GrfItem.", status,
+                   item );
+      }
+   }
+
+   if( desc ) {
+      dlen = strlen( desc );
+
+      if( text ) {
+         ret = astStore( NULL, desc, dlen + strlen( text ) + 1 );
+         if( ret ) strcpy( ret + dlen, text );
+      } else {
+         ret = astStore( NULL, desc, dlen + 1 );
+      }
+
+   } else {
+      ret = NULL;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static void GrfWrapper( AstPlot *this, const char *name, AstGrfWrap wrapper, int *status ) {
+/*
+*+
+*  Name:
+*     astGrfWrapper
+
+*  Purpose:
+*     Register a wrapper function for a F77 or C Grf function.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void astGrfWrapper( AstPlot *this, const char *name, AstGrfWrap wrapper)
+
+*  Description:
+*     This function stores a pointer to the supplied wrapper function
+*     within the plot, associating it with the grf function indicated by
+*     the "name" parameter. The supplied wrapper function should call the
+*     named grf function, using an interface appropriate to the language
+*     in which the grf function is written.
+
+*  Parameters:
+*     this
+*        The plot.
+*     name
+*        A name indicating the graphics function which is called by the
+*        supplied wrapper function. See astGrfSet for details.
+*     wrapper
+*        A pointer to the wrapper function. This will be cast to a
+*        specific type for the named grf function before being store
+*        in the Plot.
+*-
+*/
+
+/* Local Variables: */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   int ifun;               /* Index into grf function list */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store the current method and class for inclusion in error messages
+   generated by lower level functions. */
+   method = "astGrfWrapper";
+   class = astClass( this );
+
+/* Identify the supplied function name and get its integer index into the
+   list of grf functions. */
+   ifun = astGrfFunID( name, method, class );
+
+/* Cast the wrapper to an interface appropriate for the wrapped grf
+   function, and store it in the appropriate component of the Plot. */
+   if( ifun == AST__GATTR ) {
+      this->GAttr = (AstGAttrWrap) wrapper;
+
+   } else if( ifun == AST__GBBUF ) {
+      this->GBBuf = (AstGBBufWrap) wrapper;
+
+   } else if( ifun == AST__GEBUF ) {
+      this->GEBuf = (AstGEBufWrap) wrapper;
+
+   } else if( ifun == AST__GFLUSH ) {
+      this->GFlush = (AstGFlushWrap) wrapper;
+
+   } else if( ifun == AST__GLINE ) {
+      this->GLine = (AstGLineWrap) wrapper;
+
+   } else if( ifun == AST__GMARK ) {
+      this->GMark = (AstGMarkWrap) wrapper;
+
+   } else if( ifun == AST__GTEXT ) {
+      this->GText = (AstGTextWrap) wrapper;
+
+   } else if( ifun == AST__GCAP ) {
+      this->GCap = (AstGCapWrap) wrapper;
+
+   } else if( ifun == AST__GTXEXT ) {
+      this->GTxExt = (AstGTxExtWrap) wrapper;
+
+   } else if( ifun == AST__GSCALES ) {
+      this->GScales = (AstGScalesWrap) wrapper;
+
+   } else if( ifun == AST__GQCH ) {
+      this->GQch = (AstGQchWrap) wrapper;
+
+   } else if( astOK ) {
+      astError( AST__INTER, "%s(%s): AST internal programming error - "
+                "Grf function id %d not yet supported.", status, method, class,
+                ifun );
+   }
+}
+
+static void Grid( AstPlot *this_nd, int *status ){
+/*
+*++
+*  Name:
+c     astGrid
+f     AST_GRID
+
+*  Purpose:
+*     Draw a set of labelled coordinate axes.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astGrid( AstPlot *this )
+f     CALL AST_GRID( THIS, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+c     This function draws a complete annotated set of
+f     This routine draws a complete annotated set of
+*     coordinate axes for a Plot with (optionally) a coordinate grid
+*     superimposed. Details of the axes and grid can be controlled by
+*     setting values for the various attributes defined by the Plot
+*     class (q.v.).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - If the supplied Plot is a Plot3D, the axes will be annotated
+*     using three 2-dimensional Plots, one for each 2D plane in the 3D
+*     current coordinate system. The plots will be "pasted" onto 3 faces
+*     of the cuboid graphics volume specified when the Plot3D was
+*     constructed. The faces to be used can be controlled by the "RootCorner"
+*     attribute.
+*     - An error results if either the current Frame or the base Frame
+*     of the Plot is not 2-dimensional or (for a Plot3D) 3-dimensional.
+*     - An error also results if the transformation between the base
+*     and current Frames of the Plot is not defined in either
+*     direction (i.e. the Plot's TranForward or TranInverse attribute
+*     is zero).
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   AstPlot *this;          /* Plot with 2d current Frame */
+   AstPlotCurveData **cdata;/* Pointer to info. about breaks in curves */
+   TickInfo **grid;        /* Pointer to info. about tick marks */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   double cen[ 2 ];        /* Position of first tick mark */
+   double gap[ 2 ];        /* Gap between tick marks */
+   double labelat[ 2 ];    /* Axis values at which tick marks are put */
+   int axis;               /* Physical axis index */
+   int border;             /* Draw a border? */
+   int clip;               /* Original Clip attribute value */
+   int dounits[2];         /* Include Units in each axis label? */
+   int drawgrid;           /* Is a grid of lines to be drawn? */
+   int clredge;            /* Clear the Edge attributes before returning? */
+   int edgeticks;          /* Draw labels round edges of plotting area? */
+   int escs;               /* Original astEscapes value */
+   int ink;                /* Draw the grid with visible ink? */
+   int inval;              /* Were areas of invalid coordinates found? */
+   int labelling;          /* Value of Labelling attribute */
+   int loglabelset[2];     /* Were the LogLabel attributes set initially? */
+   int logticksset[2];     /* Were the LogTicks attributes set initially? */
+   int naxes;              /* No. of axes in the base or current Frame */
+   int oldedge0;           /* Default value for Edge(1) */
+   int oldedge1;           /* Default value for Edge(2) */
+   int useint;             /* Do interior labels give us an advantage? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_nd);
+
+/* Store the current method and class for inclusion in error messages
+   generated by lower level functions. */
+   method = "astGrid";
+   class = astClass( this_nd );
+
+/* Check the base Frame of the Plot is 2-D. */
+   naxes = astGetNin( this_nd );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 2.", status, method, class, naxes, class );
+   }
+
+/* Ensure AST functions included graphical escape sequences in any
+   returned text strings. */
+   escs = astEscapes( 1 );
+
+/* Note if attributes which have complex dynamic defaults are set
+   initially. */
+   logticksset[0] = astTestLogTicks( this_nd, 0 );
+   logticksset[1] = astTestLogTicks( this_nd, 1 );
+   loglabelset[0] = astTestLogLabel( this_nd, 0 );
+   loglabelset[1] = astTestLogLabel( this_nd, 1 );
+
+/* Indicate that the GRF module should re-calculate it's cached values
+   (in case the state of the graphics system has changed since the last
+   thing was drawn). */
+   RESET_GRF;
+
+/* Get a Plot with a 2D (or 1D) current Frame. */
+   this = (AstPlot *) Fset2D( (AstFrameSet *) this_nd, AST__CURRENT, status );
+
+/* Check the current Frame of the Plot is 2-D. */
+   naxes = astGetNout( this );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the current "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 2.", status, method, class, naxes, class );
+   }
+
+/* Ensure that all lines are clipped at the plot boundary.*/
+   if( astTestClip( this ) ) {
+      clip = astGetClip( this );
+      astSetClip( this, 1 );
+   } else {
+      clip = -1;
+   }
+
+/* If the protected attribute "Ink" is set to zero, then the plot
+   is drawn in "invisble ink" (i.e. all the calculations needed to
+   produce the grid are performed, but nothing is actually drawn). */
+   ink = astGetInk( this );
+
+/* Initialise the bounds of the box containing all plotted lines and
+   numerical labels. */
+   Box_lbnd[ 0 ] = FLT_MAX;
+   Box_lbnd[ 1 ] = FLT_MAX;
+   Box_ubnd[ 0 ] = FLT_MIN;
+   Box_ubnd[ 1 ] = FLT_MIN;
+
+/* Obtain the requested centre attribute values for both physical axes. */
+   for( axis = 0; axis < 2; axis++ ){
+      cen[ axis ] = astGetCentre( this, axis );
+   }
+
+/* Determine where to put the major axis values. */
+   grid = GridLines( this, cen, gap, &inval, method, class, status );
+
+/* If the user has set an explicit value for Grid, use it. */
+   if( astTestGrid( this ) ){
+      drawgrid = astGetGrid( this );
+
+/* If not, the default for Grid is based on whether or not there are any
+   invalid regions. */
+   } else if( inval ){
+      drawgrid = 1;
+
+   } else {
+      drawgrid = 0;
+   }
+
+/* Draw the curves marking the major tick values on each axis. Information
+   is returned describing the positions of the breaks in these curves. */
+   cdata = DrawGrid( this, grid, ( ink && drawgrid ), method, class, status );
+
+/* See if labels and tick marks will be drawn round the edges of the
+   plotting area, rather than within it (no labels are actually drawn
+   yet). Interior labels can always be produced, in which case edgeticks
+   is set explicitly to zero to indicate that ticks will be internal.
+   Exterior labelling may or may not be possible. If it is requested,
+   check to see if it is possible. */
+   clredge = 0;
+   labelling = astGetLabelling( this );
+   if( labelling ){
+      edgeticks = 0;
+   } else {
+      edgeticks = EdgeLabels( this, 0, grid, cdata, 0, method, class, status );
+
+/* If the external labelling was requested, but could not be produced... */
+      if( !edgeticks ) {
+
+/* and if the Edge attributes have not been set... */
+         if( !astTestEdge( this, 0 ) && !astTestEdge( this, 1 ) ) {
+
+/* Try flipping the default Edge values, to see if this allows us to
+   honour the requested Labelling scheme. */
+            oldedge0 = astGetEdge( this, 0 );
+            oldedge1 = astGetEdge( this, 1 );
+            astSetEdge( this, 0, oldedge1 );
+            astSetEdge( this, 1, oldedge0 );
+
+/* See if exterior labels could be drawn with these new edges. */
+            edgeticks = EdgeLabels( this, 0, grid, cdata, 0, method, class, status );
+
+/* If this would allow us to use the requested labelling scheme, retain
+   the new Edge values, setting a flag to indicate that they will need to be
+   cleared before returning. Otherwise, clear them. */
+            if( edgeticks ) {
+               clredge = 1;
+
+            } else {
+               astClearEdge( this, 0 );
+               astClearEdge( this, 1 );
+            }
+         }
+      }
+   }
+
+/* If edge ticks can still not be produced, but the ForceExterior attribute
+   has a non-zero value, attempt to create exterior labels even though it
+   looks like there may be insufficient of them to justify their use. */
+   if( !edgeticks && astGetForceExterior( this ) ) {
+      edgeticks = EdgeLabels( this, 0, grid, cdata, 1, method, class, status );
+   }
+
+/* We may also need to swap edge values when using interior labelling in
+   order to ensure that the text labels are placed on appropriate edges of
+   the plotting box. */
+   if( !edgeticks && !astTestEdge( this, 0 ) && !astTestEdge( this, 1 ) ) {
+      if( swapEdges( this, grid, cdata, status ) ) {
+         oldedge0 = astGetEdge( this, 0 );
+         oldedge1 = astGetEdge( this, 1 );
+         astSetEdge( this, 0, oldedge1 );
+         astSetEdge( this, 1, oldedge0 );
+         clredge = 1;
+      }
+   }
+
+/* If edge ticks are being used, store bad values for the labelat values to
+   indicate that labels are not being drawn within the interior of the
+   plotting area. */
+   if( edgeticks ){
+      labelat[ 0 ] = AST__BAD;
+      labelat[ 1 ] = AST__BAD;
+
+/* Otherwise, see where interior labels and tick marks should go (the axis
+   values are put in "labelat"). */
+   } else {
+      useint = Labelat( this, grid, cdata, labelat, method, class, status );
+
+/* If interior labelling does not allow us to draw any more ticks, revert
+   to edge labelling if that is what the user requested. */
+      if( !useint && !labelling ) {
+         labelat[ 0 ] = AST__BAD;
+         labelat[ 1 ] = AST__BAD;
+         edgeticks = EdgeLabels( this, 0, grid, cdata, 1, method, class, status );
+      }
+   }
+
+/* See if a border is required. By default, a border is drawn only when
+   using exterior labelling. */
+   if( astTestBorder( this ) ) {
+      border = astGetBorder( this );
+   } else {
+      border = edgeticks;
+   }
+
+/* See if the Units string is to be inluded in the label. */
+   dounits[ 0 ] =  astGetLabelUnits( this, 0 );
+   dounits[ 1 ] =  astGetLabelUnits( this, 1 );
+
+/* The rest is not done if no output is required. */
+   if( ink ) {
+
+/* Draw tick marks (major and minor). */
+      DrawTicks( this, grid, drawgrid, labelat, gap, method, class, status );
+
+/* If required, ensure that curves through the tick marks have been drawn */
+      DrawAxis( this, grid, labelat, gap, method, class, status );
+
+/* If required, draw a curve around the edge of the area containing valid
+   physical coordinates. */
+      if( border ) (void) astBorder( this );
+
+/* Draw the numerical labels at the major tick values. */
+      Labels( this, grid, cdata, gap, labelat, method, class, status );
+
+/* Draw the textual labels for each axis and a title. */
+      TextLabels( this, edgeticks, dounits, method, class, status );
+   }
+
+/* Ensure all lines are flushed to the graphics system. */
+   Fpoly( this, method, class, status );
+
+/* Store the actual values used for all attributes which have dynamic
+   defaults. Check the global status to ensure the pointer "grid" can be
+   used without the possibility of a segmentation violation. */
+   for( axis = 0; axis < 2 && astOK ; axis++ ) {
+      SetUsedLogTicks( this_nd, axis, astGetLogTicks( this, axis ), status );
+      SetUsedLogLabel( this_nd, axis, astGetLogLabel( this, axis ), status );
+
+      if( astGetLogTicks( this, axis ) ) {
+         SetUsedLogGap( this_nd, axis, gap[ axis ], status );
+      } else {
+         SetUsedGap( this_nd, axis, gap[ axis ], status );
+      }
+      SetUsedCentre( this_nd, axis, cen[ axis ], status );
+      SetUsedEdge( this_nd, axis, astGetEdge( this, axis ), status );
+      SetUsedLabelAt( this_nd, axis, labelat[ axis ], status );
+      SetUsedLabelUnits( this_nd, axis, dounits[ axis ], status );
+
+/* If explicit minor tick values were supplied using astSetTickValues,
+   then set MinTick to the average number of minor tick divisions per major
+   tick division. */
+      if( grid[ axis ]->minticks ) {
+         SetUsedMinTick( this_nd, axis,
+                         ( grid[ axis ]->nminor + grid[ axis ]->nmajor )/
+                         ( grid[ axis ]->nmajor - 1 ), status );
+      } else {
+         SetUsedMinTick( this_nd, axis, grid[ axis ]->nminor, status );
+      }
+
+      if( astTestTextLab( this, axis ) ) {
+         SetUsedTextLab( this_nd, axis, astGetTextLab( this, axis ), status );
+      } else {
+         SetUsedTextLab( this_nd, axis, edgeticks, status );
+      }
+
+      if( astTestMajTickLen( this, axis ) ) {
+         SetUsedMajTickLen( this_nd, axis, astGetMajTickLen( this, axis ), status );
+      } else {
+         SetUsedMajTickLen( this_nd, axis, drawgrid ? 0.0 :
+                                           astGetMajTickLen( this, axis ), status );
+      }
+
+   }
+
+   SetUsedGrid( this_nd, drawgrid, status );
+   SetUsedLabelling( this_nd, edgeticks ? 0 : 1, status );
+   SetUsedBorder( this_nd, border, status );
+
+/* Free the memory used to hold information about the curves. */
+   cdata = CleanCdata( cdata, status );
+
+/* Free the memory used to hold information about the tick marks. */
+   grid = CleanGrid( grid, status );
+
+/* If required clear attributes. */
+   if( clredge ) {
+      astClearEdge( this, 0 );
+      astClearEdge( this, 1 );
+   }
+
+   if( !logticksset[ 0 ] ) astClearLogTicks( this, 0 );
+   if( !logticksset[ 1 ] ) astClearLogTicks( this, 1 );
+   if( !loglabelset[ 0 ] ) astClearLogLabel( this, 0 );
+   if( !loglabelset[ 1 ] ) astClearLogLabel( this, 1 );
+
+/* Restore the original value of the Clip attribute. */
+   if( clip != -1 ) astSetClip( this, clip );
+
+/* Free the 2D Plot. */
+   this = astAnnul( this );
+
+/* Restore the original value of the flag which says whether graphical
+   escape sequences should be incldued in any returned text strings. */
+   astEscapes( escs );
+
+/* Copy the total bounding box to the box which is returned by
+   astBoundingBox. */
+   if( !Boxp_freeze ){
+      Boxp_lbnd[ 0 ] = Box_lbnd[ 0 ];
+      Boxp_lbnd[ 1 ] = Box_lbnd[ 1 ];
+      Boxp_ubnd[ 0 ] = Box_ubnd[ 0 ];
+      Boxp_ubnd[ 1 ] = Box_ubnd[ 1 ];
+   }
+
+/* Return. */
+   return;
+
+}
+
+static void GridLine( AstPlot *this, int axis, const double start[],
+                      double length, int *status ){
+/*
+*++
+*  Name:
+c     astGridLine
+f     AST_GRIDLINE
+
+*  Purpose:
+*     Draw a grid line (or axis) for a Plot.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astGridLine( AstPlot *this, int axis, const double start[],
+c                       double length )
+f     CALL AST_GRIDLINE( THIS, AXIS, START, LENGTH, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+c     This function draws a curve in the physical coordinate system of
+f     This routine draws a curve in the physical coordinate system of
+*     a Plot by varying only one of the coordinates along the length
+*     of the curve. It is intended for drawing coordinate axes,
+*     coordinate grids, and tick marks on axes (but note that these
+c     are also available via the more comprehensive astGrid function).
+f     are also available via the more comprehensive AST_GRID routine).
+*
+*     The curve is transformed into graphical coordinate space for
+*     plotting, so that a straight line in physical coordinates may
+*     result in a curved line being drawn if the Mapping involved is
+*     non-linear. Any discontinuities in the Mapping between physical
+*     and graphical coordinates are catered for, as is any
+c     clipping established using astClip.
+f     clipping established using AST_CLIP.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+c     axis
+f     AXIS = INTEGER (Given)
+*        The index of the Plot axis whose physical coordinate value is
+*        to be varied along the length of the curve (all other
+*        coordinates will remain fixed). This value should lie in the
+*        range from 1 to the number of Plot axes (Naxes attribute).
+c     start
+f     START( * ) = DOUBLE PRECISION (Given)
+*        An array, with one element for each axis of the Plot, giving
+*        the physical coordinates of the start of the curve.
+c     length
+f     LENGTH = DOUBLE PRECISION (Given)
+*        The length of curve to be drawn, given as an increment along
+*        the selected physical axis. This may be positive or negative.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+c     - No curve is drawn if the "start" array contains any
+c     coordinates with the value AST__BAD, nor if "length" has this value.
+f     - No curve is drawn if the START array contains any
+f     coordinates with the value AST__BAD, nor if LENGTH has this value.
+*     - An error results if the base Frame of the Plot is not 2-dimensional.
+*     - An error also results if the transformation between the
+*     current and base Frames of the Plot is not defined (i.e. the
+*     Plot's TranInverse attribute is zero).
+*--
+*/
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   int naxes;              /* No. of axes in the base Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Store the current method, and the class of the supplied object for use
+   in error messages.*/
+   method = "astGridLine";
+   class = astGetClass( this );
+
+/* Check the base Frame of the Plot is 2-D. */
+   naxes = astGetNin( this );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 2.", status, method, class, naxes, class );
+   }
+
+/* Initialise the bounding box for primatives produced by this call. */
+   if( !Boxp_freeze ) {
+      Boxp_lbnd[ 0 ] = FLT_MAX;
+      Boxp_lbnd[ 1 ] = FLT_MAX;
+      Boxp_ubnd[ 0 ] = FLT_MIN;
+      Boxp_ubnd[ 1 ] = FLT_MIN;
+   }
+
+/* Validate the axis index, converting the axis index to be zero-based. */
+   (void) astValidateAxis( this, axis - 1, 1, method );
+
+/* Indicate that the GRF module should re-calculate it's cached values
+   (in case the state of the graphics system has changed since the last
+   thing was drawn). */
+   RESET_GRF;
+
+/* Draw the curve. The break information is stored in an external structure
+   where it can be accessed by public methods which return information
+   about the most recently drawn curve. */
+   AxPlot( this, axis - 1, start, length, 1, &Curve_data, method, class, status );
+
+/* Ensure all lines are flushed to the graphics system. */
+   Fpoly( this, method, class, status );
+}
+
+static TickInfo **GridLines( AstPlot *this, double *cen, double *gap,
+                             int *inval, const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     GridLines
+
+*  Purpose:
+*     Obtain information desribing the major tick values within the plotting
+*     area.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     TickInfo **GridLines( AstPlot *this, double *cen, double *gap,
+*                           int *inval, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     A pointer is returned which points to an array of two pointers. Each
+*     of these pointers points to a TickInfo structure which holds
+*     information about the ticks on a single axis. These structures,
+*     together with the array holding the two pointers, should be released
+*     when no longer needed using CleanGrid.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     cen
+*        A pointer to an array holding axis values at which to put a single
+*        central tick. Other ticks are placed evenly on either side of this
+*        tick. If AST__BAD is provided, a value will be used which would put a
+*        tick at an axis value of zero.
+*     gap
+*        A pointer to an array holding the gaps between ticks required on
+*        each axis. If this is AST__BAD a suitable default value will be used
+*        and returned in place of the AST__BAD value.
+*     inval
+*        A pointer to a location at which to return a flag indicating if
+*        any invalid physical coordinates were encountered while deciding on
+*        the tick values.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to an array of two TickInfo pointers.
+
+*  Notes:
+*     -  This function assumes that the physical coordinate system is 2
+*     dimensional, and it should not be used if this is not the case.
+*     -  If an error has already occurred, or if this function should fail
+*     for any reason, then a NULL pointer is returned.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;          /* Pointer to current Frame */
+   GetTicksStatics *statics = NULL; /* Pointer to static data for GetTicks */
+   TickInfo **info;       /* Returned array of two TickInfo pointers */
+   double *lengths;       /* Pointer to lengths of each curve section */
+   double *starts;        /* Pointer to start of each curve section */
+   double *ticks;         /* Pointer to tick mark values */
+   double bot;            /* Lowest axis value to display */
+   double end;            /* Axis value at end of curve section */
+   double tmp;            /* Temp storage */
+   double top;            /* Highest axis value to display */
+   int i;                 /* Tick mark index */
+   int j;                 /* Axis index */
+   int k;                 /* Section index */
+   int logticks[ 2 ];     /* Uses logarithmicaly spaced tick marks? */
+   int nticks;            /* Number of tick marks */
+
+/* Check the global status. */
+   if( !astOK ) return NULL;
+
+/* Get memory to hold two TickInfo pointers. */
+   info = (TickInfo **) astMalloc( 2*sizeof( TickInfo *) );
+
+/* If succesfull... */
+   if( astOK ){
+
+/* Initialise the two pointers. */
+      info[ 0 ] = NULL;
+      info[ 1 ] = NULL;
+
+/* Obtain the tick mark values, and the corresponding formatted labels for
+   each axis. */
+      for( j = 0; j < 2; j++ ){
+         info[ j ] = TickMarks( this, j, cen + j, gap + j, inval,
+                                &statics, method, class, status );
+         logticks[ j ] = astGetLogTicks( this, j );
+      }
+
+/* Release the resources allocated in the first call to TickMarks. */
+      for( j = 0; j < 2; j++ ){
+         (void) TickMarks( NULL, j, NULL, gap, NULL, &statics, method, class,
+                           status );
+      }
+
+/* Each major tick value for axis "j" may be marked with a curve parallel
+   to axis "1-j" drawn across the entire plotting area. We need to decide
+   where to start drawing this curve and how long it should be. We can
+   simply use the minimum value on axis "1-j" together with the tick value
+   on axis "j" to define the starting position. The length could be taken
+   as the difference between the maximum and minimum values on axis "1-j".
+   However, this may not be right in some situations. For instance if the
+   plotting area covers a small range of Right Ascension from 23:59:59 to
+   00:00:01. Using the difference between the maximum and minimum values
+   to give the length of the curve would result in the curve starting at
+   00:00:00 (the minimum axis value) and extending for a length of 23:59:59
+   (the axis range). To get round this sort of problem, the curve is split
+   up into sections with lengths and starting positions determined by the
+   tick mark values on axis "1-j". The first section starts at the minimum
+   axis value and extends upto the first missing tick mark (in the RA
+   example, this would be at 00:00:01). Subsequent sections starts at the
+   next tick mark (23:59:59 in the RA example) and extends upto the next
+   missing tick mark, or the last tick mark if none are missing. */
+
+/* Get the current frame. */
+      fr = astGetFrame( this, AST__CURRENT );
+
+/* If either axis has log tick spacing, use the simple approach which
+   assumes that each curve has only one section. */
+      if( logticks[ 0 ] || logticks[ 1 ] ) {
+
+/* Find the start and length of the curve for each tick mark on axis "j". */
+         for( j = 0; j < 2 && astOK; j++ ){
+
+/* Find the axis range to display on the other axis. */
+            bot = astGetBottom( fr, 1 - j );
+            top = astGetTop( fr, 1 - j );
+            if( bot > top ) {
+               tmp = top;
+               top = bot;
+               bot = tmp;
+            }
+
+/* Get a pointer to the major tick mark values on the other axis ("1-j"),
+   together with the number of them. */
+            ticks = info[ 1 - j ]->ticks;
+            nticks = info[ 1 - j ]->nmajor;
+
+/* Reserve memory to hold the starts and lengths of each section of the
+   grid line marking the major ticks on the axis "j". There will only be
+   one section. */
+            starts = (double *) astMalloc( sizeof(double) );
+            lengths = (double *) astMalloc( sizeof(double) );
+            info[ j ]->start = starts;
+            info[ j ]->length = lengths;
+
+/* Check that the pointers can be used. */
+            if( astOK ) {
+
+/* The section starts one gap below the first tick, and ends one gap above
+   the first tick. Limit both to the displayed range of the axis. */
+               if( logticks[ 1 - j ] ) {
+                  starts[ 0 ] = astMIN( top, astMAX( bot, ticks[ 0 ]/gap[ 1 - j ] ) );
+                  end = astMIN( top, astMAX( bot, ticks[ nticks - 1 ]*gap[ 1 - j ] ) );
+               } else {
+                  starts[ 0 ] = astMIN( top, astMAX( bot, ticks[ 0 ] - gap[ 1 - j ] ) );
+                  end = astMIN( top, astMAX( bot, ticks[ nticks - 1 ] + gap[ 1 - j ] ) );
+               }
+
+/* Store the length of the section. */
+               lengths[ 0 ] = end - starts[ 0 ];
+
+/* Store the number of sections in the returned structure. */
+               info[ 0 ]->nsect = 1;
+
+            }
+         }
+
+/* If both axes have linear tick spacing, use the complete approach. */
+      } else {
+
+/* Find the start and length of each section of the curve for each tick
+   mark on axis "j". */
+         for( j = 0; j < 2 && astOK; j++ ){
+
+/* Find the axis range to display on the other axis. */
+            bot = astGetBottom( fr, 1 - j );
+            top = astGetTop( fr, 1 - j );
+            if( bot > top ) {
+               tmp = top;
+               top = bot;
+               bot = tmp;
+            }
+
+/* Get a pointer to the major tick mark values on the other axis ("1-j"),
+   together with the number of them. */
+            ticks = info[ 1 - j ]->ticks;
+            nticks = info[ 1 - j ]->nmajor;
+
+/* Reserve memory to hold the starts and lengths of each section of the
+   grid line marking the major ticks on the axis "j". The allocated
+   arrays are the largest that could possibly be needed (i.e. if every
+   tick mark starts a new section). */
+            starts = (double *) astMalloc( sizeof(double)*(size_t) nticks );
+            lengths = (double *) astMalloc( sizeof(double)*(size_t) nticks );
+            info[ j ]->start = starts;
+            info[ j ]->length = lengths;
+
+/* Check that the pointers can be used. */
+            if( astOK ) {
+
+/* Loop round each of the major tick marks on axis "1-j". */
+               k = 0;
+               i = 0;
+               while( i < nticks ){
+
+/* Record the start of the next section of the grid lines. */
+                  starts[ k ] = ticks[ i++ ];
+
+/* Tick marks should be regularly spaced by the values in "gap". Check each
+   tick mark until a missing tick mark is  found. The section ends at the
+   start of the gap. */
+                  while( i < nticks &&
+                         ( ticks[ i ] - ticks[ i - 1 ] ) < 1.5*gap[ 1 - j ] ) i++;
+
+/* Record the length of the section. */
+                  lengths[ k ] = ticks[ i - 1 ] - starts[ k ];
+
+/* The section is extended at start and end by one gap in order to "cover
+   up the joins". Limit this to the displayed range of the axis. */
+                  starts[ k ] -= gap[ 1 - j];
+                  lengths[ k ] += 2.0*gap[ 1 - j ];
+
+/* Limit the start and end to the displayed range of the axis. */
+                  end = starts[ k ] + lengths[ k ];
+                  starts[ k ] = astMIN( top, astMAX( bot, starts[ k ] ) );
+                  lengths[ k ] = astMIN( top, astMAX( bot, end ) ) - starts[ k ];
+
+/* Increment the number of sections. */
+                  k++;
+               }
+
+/* Store the number of sections in the returned structure. */
+               info[j]->nsect = k;
+
+            }
+         }
+      }
+
+/* Annull the current frame. */
+      fr = astAnnul( fr );
+
+   }
+
+/* If an error has occurred, clean up the returned TickInfo structures. */
+   if( !astOK ) info = CleanGrid( info, status );
+
+/* Return. */
+   return info;
+
+}
+
+void astGrfAttrs_( AstPlot *this, int id, int set, int prim, const char *method, const char *class, int *status ){
+/*
+*+
+*  Name:
+*     astGrfAttrs
+
+*  Purpose:
+*     Establish values for the graphics attributes for a given object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void astGrfAttrs( AstPlot *this, int id, int set, int prim, const char *method, const char *class )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function should be called with "set=1" to establish the correct
+*     graphics attributes prior to drawing specific graphical objects. Once
+*     the object has been drawn, it should be called again with "set=0" to
+*     re-establish the original graphics attributes.
+*
+*     NOTE, each type of graphical object identified by "id" should be
+*     drawn entirely by calls to just one of GMark, GText or GLine
+*     as indicated by argument "prim".
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     id
+*        An integer specifying the graphical object to be drawn.
+*     set
+*        If non-zero then the attributes for the specified object are set
+*        to the values indicated by the corresponding Plot attributes,
+*        and the current values are saved in a static array. If zero, then
+*        the values are set to the values stored in the static array.
+*     prim
+*        Indicates the sort of graphics primative used to draw the
+*        object. This must be one of (defined in grf.h) :
+*
+*           GRF__LINE
+*           GRF__MARK
+*           GRF__TEXT
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+
+*  Notes:
+*     -  This function should always be called in pairs with set=1 on the
+*     first call and set=0 on the second call.
+*     -  If a pair of calls is nested within another pair of calls, the
+*     inner pair has no effect.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   double *attr;           /* Pointer to the next attribute value */
+   double dval;            /* Floating point attribute value */
+   int ival;               /* Integer attribute value */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Set up a pointer to the next element in "grfattrs_attrs". */
+   attr = grfattrs_attrs;
+
+/* If we are setting new values, increment the nesting level, otherwise
+   decrement it. */
+   if( set ){
+      grfattrs_nesting++;
+   } else {
+      grfattrs_nesting--;
+   }
+
+/* If we are changing any line attributes, ensure all lines are flushed to
+   the graphics system. */
+   if( prim == GRF__LINE ) Fpoly( this, method, class, status );
+
+/* First deal with cases where we are establishing new values for the
+   graphics attributes by setting them to the values of the corresponding
+   Plot attributes. Only do this if we are at nesting level one. */
+   if( set && grfattrs_nesting == 1 ){
+
+/* See if a value has been set in the Plot for the line style attribute for
+   the specified object, If so, use the value. */
+      if( TestUseStyle( this, id, status ) ) {
+         ival = GetUseStyle( this, id, status );
+
+/* Save the current value, and establish the new value. */
+         GAttr( this, GRF__STYLE, (double) ival, attr++, prim, method,
+                class, status );
+
+/* If no style was specified, retain the current value. Indicate that no
+   new value has been established by setting the old value bad. */
+      } else {
+         *(attr++) = AST__BAD;
+      }
+
+/* Do the same for the line width attribute. */
+      if( TestUseWidth( this, id, status ) ){
+         dval = GetUseWidth( this, id, status );
+         GAttr( this, GRF__WIDTH, dval, attr++, prim, method, class, status );
+      } else {
+         *(attr++) = AST__BAD;
+      }
+
+/* Do the same for the character size attribute. */
+      if( TestUseSize( this, id, status ) ) {
+         dval = GetUseSize( this, id, status );
+         GAttr( this, GRF__SIZE, dval, attr++, prim, method, class, status );
+      } else {
+         *(attr++) = AST__BAD;
+      }
+
+/* Do the same for the character font attribute. */
+      if( TestUseFont( this, id, status ) ){
+         ival = GetUseFont( this, id, status );
+         GAttr( this, GRF__FONT, (double) ival, attr++, prim, method, class, status );
+      } else {
+         *(attr++) = AST__BAD;
+      }
+
+/* Do the same for the colour attribute. */
+      if( TestUseColour( this, id, status ) ) {
+         ival = GetUseColour( this, id, status );
+         GAttr( this, GRF__COLOUR, (double) ival, attr++, prim, method,
+                class, status );
+      } else {
+         *(attr++) = AST__BAD;
+      }
+
+   }
+
+/* Now deal with cases where we are re-establishing old values saved on a
+   previous call to this function. Only do this if we are at nesting
+   level zero. */
+   if( !set && !grfattrs_nesting ){
+      GAttr( this, GRF__STYLE, *(attr++), NULL, prim, method, class, status );
+      GAttr( this, GRF__WIDTH, *(attr++), NULL, prim, method, class, status );
+      GAttr( this, GRF__SIZE, *(attr++), NULL, prim, method, class, status );
+      GAttr( this, GRF__FONT, *(attr++), NULL, prim, method, class, status );
+      GAttr( this, GRF__COLOUR, *(attr++), NULL, prim, method, class, status );
+   }
+
+/* Return. */
+   return;
+
+}
+
+static int GVec( AstPlot *this, AstMapping *mapping, double *phy,
+                 int axis, double delta, AstPointSet **pset1,
+                 AstPointSet **pset2, double *gx, double *gy,
+                 double *dx, double *dy, int *flag, const char *method,
+                 const char *class, int *status ){
+/*
+*  Name:
+*     GVec
+
+*  Purpose:
+*     Returns a unit vector parallel to a physical axis at a given point.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int GVec( AstPlot *this, AstMapping *mapping, double *phy,
+*               int axis, double delta, AstPointSet **pset1,
+*               AstPointSet **pset2, double *gx, double *gy,
+*               double *dx, double *dy, int *flag, const char *method,
+*               const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function returns a unit vector (in the graphics coordinate
+*     system) in the positive direction of the specified physical axis,
+*     at the given physical position. It works by transforming the given
+*     physical position, together with another very close position, and
+*     returning the vector between them. It is possible for a
+*     discontinuity to occur between these two close positions,
+*     resulting in a very large meaningless vector prior to
+*     normalisation. For this reason two vectors are found, one joining
+*     the given position to a close position higher up the axis, and one
+*     joining a close position lower down the axis to the given position.
+*     If these two vectors differ in magnitude by a large factor, then
+*     the shorter of the two vectors is normalised and returned.
+*     Otherwise, the vector which is closest in direction to the vector
+*     supplied in [dx,dy] is returned. The returned vector is reversed if
+*     necessary so that it always points in the positive direction of the
+*     axis.
+*
+*     If neither of the two vectors can be found (i.e. if the graphics
+*     coordinates are bad, or coincident), then the vector supplied in
+*     [dx,dy] is returned unchanged, and a function value of zero is
+*     returned. Otherwise, a function value of one is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     mapping
+*        Pointer to the Mapping from the base Frame of the Plot to the
+*        current Frame.
+*     phy
+*        Pointer to an array holding the coordinates in the current Frame
+*        of the Plot at which the tangent vector is required.
+*     axis
+*        The index of the axis within the current Frame for which the
+*        tangent vector is required.
+*     delta
+*        The increment in the axis value to use between positions defining
+*        the vectors.
+*     pset1
+*        A pointer to a place at which to store a pointer to a PointSet
+*        holding current Frame coordinates. This PointSet pointer should
+*        be supplied as NULL on the first call to this function, resulting
+*        in a new PointSet being created and a pointer to it returned.
+*        Subsequent calls to this function shopuld retain the pointer
+*        returned by the first call. The PointSet pointer should be
+*        annulled using astAnnul when no longer needed.
+*     pset2
+*        A pointer to a place at which to store a pointer to a PointSet
+*        holding base Frame coordinates. This PointSet is managed in the
+*        same way as "pset1".
+*     gx
+*        A pointer to a double in which to return the graphics X
+*        coordinate of the position supplied by "phy".
+*     gy
+*        A pointer to a double in which to return the graphics Y
+*        coordinate of the position supplied by "phy".
+*     dx
+*        A pointer to a double in which to return the X component
+*        of the unit tangent vector. This should be supplied holding a
+*        "default" unit vector which is left unchanged if no new vector
+*        can be found.
+*     dy
+*        A pointer to a double in which to return the Y component
+*        of the unit tangent vector. This should be supplied holding a
+*        "default" unit vector which is left unchanged if no new vector
+*        can be found.
+*     flag
+*        A pointer to an int in which to return a flag indicating which
+*        of the two vectors was returned. Zero is returned if the vector
+*        was estimated by moving in a positive direction along the axis
+*        from the position supplied by "phy". One is returned if the vector
+*        was estimated by moving in a negative direction along the axis
+*        from the position supplied by "phy" (in this case the returned
+*        vector will have been negated so that it refers to the positive
+*        direction).
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One is returned if the vector was found succesfully, Zero is returned
+*     if the vector could not be estimated for any reason. No error is
+*     reported if the failure was due to the nature of the mapping.
+
+*  Notes:
+*     -  If an error has already occurred, or if this function should fail
+*     for any reason, then a NULL pointer is returned.
+*/
+
+/* Local Variables: */
+   double dd1;        /* Length of positive vector */
+   double dd2;        /* Length of negative vector */
+   double dx1;        /* X component of positive vector */
+   double dx2;        /* X component of negative vector */
+   double dy1;        /* Y component of positive vector */
+   double dy2;        /* Y component of negative vector */
+   double **ptr1;     /* Pointers to physical coordinate data */
+   double **ptr2;     /* Pointers to graphics coordinate data */
+   int i;             /* Axis index */
+   int nphy;          /* No. of axes in current (physical) Frame */
+   int ret;           /* Was a vector estimated succesfully? */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   dx1 = 0.0;
+   dx2 = 0.0;
+   dy1 = 0.0;
+   dy2 = 0.0;
+
+/* Initialise the returned value to indicate that the vector can not
+   be found. */
+   ret = 0;
+
+/* Get the number of physical coordinates from the mapping. */
+   nphy = astGetNout( mapping );
+
+/* If no PointSets have been supplied, create some now. PointSet 1
+   contains physical coordinates, PointSet 2 contains graphics
+   coordinates. */
+   if( !(*pset1) ) *pset1 = astPointSet( 3, nphy, "", status );
+   if( !(*pset2) ) *pset2 = astPointSet( 3, 2, "", status );
+
+/* Get pointers to the PointSet data. */
+   ptr1 = astGetPoints( *pset1 );
+   ptr2 = astGetPoints( *pset2 );
+
+/* Check the PointSets can be used. */
+   if( astOK ){
+
+/* Store the physical coordinates of three close points on a curve
+   parallel to the given axis, with the centre point at the given
+   position. */
+      for( i = 0; i < nphy; i++ ){
+         ptr1[ i ][ 0 ] = phy[ i ];
+         ptr1[ i ][ 1 ] = phy[ i ];
+         ptr1[ i ][ 2 ] = phy[ i ];
+      }
+
+      if( phy[ axis ] != AST__BAD ){
+         ptr1[ axis ][ 0 ] = phy[ axis ] - delta;
+         ptr1[ axis ][ 2 ] = phy[ axis ] + delta;
+      }
+
+/* Find the corresponding graphics coordinates. */
+      (void) Trans( this, NULL, mapping, *pset1, 0, *pset2, 0, method, class, status );
+
+/* Check the central position is OK. */
+      *gx = ptr2[ 0 ][ 1 ];
+      *gy = ptr2[ 1 ][ 1 ];
+      if( astOK && *gx != AST__BAD && *gy != AST__BAD ){
+
+/* Get the unit vector between the central position and the position at
+   the higher physical axis value. Also get the length of the vector
+   joining the two positions. */
+         if( ptr2[ 0 ][ 2 ] != AST__BAD && ptr2[ 1 ][ 2 ] != AST__BAD ){
+            dx1 = ptr2[ 0 ][ 2 ] - *gx;
+            dy1 = ptr2[ 1 ][ 2 ] - *gy;
+            dd1 = dx1*dx1 + dy1*dy1;
+
+            if( dd1 > 0.0 ) {
+               dd1 = sqrt( dd1 );
+               dx1 /= dd1;
+               dy1 /= dd1;
+            } else {
+               dd1 = AST__BAD;
+            }
+
+         } else {
+            dd1 = AST__BAD;
+         }
+
+/* Do the same for the position with lower physical axis value,
+   reversing the direction of the vector so that it refers to the
+   positive direction. */
+         if( ptr2[ 0 ][ 0 ] != AST__BAD && ptr2[ 1 ][ 0 ] != AST__BAD ){
+            dx2 = *gx - ptr2[ 0 ][ 0 ];
+            dy2 = *gy - ptr2[ 1 ][ 0 ];
+            dd2 = dx2*dx2 + dy2*dy2;
+
+            if( dd2 > 0.0 ) {
+               dd2 = sqrt( dd2 );
+               dx2 /= dd2;
+               dy2 /= dd2;
+            } else {
+               dd2 = AST__BAD;
+            }
+
+         } else {
+            dd2 = AST__BAD;
+         }
+
+/* Only overwrite the supplied vector if at least one of the two tangent
+   vectors was defined. */
+         if( dd1 != AST__BAD || dd2 != AST__BAD ){
+
+/* If the first vector was not defined, return the second. */
+            if( dd1 == AST__BAD ){
+               *dx = dx2;
+               *dy = dy2;
+               *flag = 1;
+               ret = 1;
+
+/* If the second vector was not defined, return the first. */
+            } else if( dd2 == AST__BAD ){
+               *dx = dx1;
+               *dy = dy1;
+               *flag = 0;
+               ret = 1;
+
+/* If the first vector is much longer than the second, return the
+   second. */
+            } else if( dd1 > 100.0*dd2 ){
+               *dx = dx2;
+               *dy = dy2;
+               *flag = 1;
+               ret = 1;
+
+/* If the second vector is much longer than the first, return the
+   first. */
+            } else if( dd2 > 100.0*dd1 ){
+               *dx = dx1;
+               *dy = dy1;
+               *flag = 0;
+               ret = 1;
+
+/* If both vectors are defined and of comparable length, return the
+   vector which is most nearly parallel to the supplied vector. Note, we
+   assume that the supplied vector [dx,dy] is a unit vector. */
+            } else if( *dx != AST__BAD && *dx != AST__BAD ){
+               if( ( dx1*(*dx) + dy1*(*dy) )/dd1 >
+                   ( dx2*(*dx) + dy2*(*dy) )/dd2 ){
+                  *dx = dx1;
+                  *dy = dy1;
+                  *flag = 0;
+                  ret = 1;
+
+               } else {
+                  *dx = dx2;
+                  *dy = dy2;
+                  *flag = 1;
+                  ret = 1;
+               }
+
+/* If no vector was supplied, return the shorter of the two vectors. */
+            } else if( dd1 < dd2 ){
+               *dx = dx1;
+               *dy = dy1;
+               *flag = 0;
+               ret = 1;
+
+            } else {
+               *dx = dx2;
+               *dy = dy2;
+               *flag = 1;
+               ret = 1;
+
+            }
+
+         }
+
+      }
+
+   }
+
+/* Return the answer. */
+   return ret;
+
+}
+
+static int HasEscapes( const char *text, int *status ) {
+/*
+*  Name:
+*     HasEscapes
+
+*  Purpose:
+*     Check if a text string contains any escape sequences.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int HasEscapes( const char *text, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function checks if a text string contains any escape sequences
+*     (see attribute Escape).
+
+*  Parameters:
+*     text
+*        The text to check.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned:
+*     Non-zero if any escape sequences are found in the text. Zero
+*     otherwise.
+
+*/
+
+/* Local Variables: */
+   int result;
+   int type;
+   int value;
+   int nc;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status and the supplied pointer. */
+   if ( !astOK || ! text ) return result;
+
+/* Does the string begin with an escape sequence? */
+   if( astFindEscape( text, &type, &value, &nc ) ){
+      result = 1;
+
+/* If not, there must be an escape sequence later in the string if the
+   number of characters skipped by the above call to astFindEscape is less
+   than the length of the string. */
+   } else if( nc < strlen( text ) ) {
+      result = 1;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int IdFind( int id, int nax, int *id1, int *id2, int *id3, int *status ) {
+/*
+*  Name:
+*     IdFind
+
+*  Purpose:
+*     Find the numerical identifiers to use for a given identifier.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int IdFind( int id, int nax, int *id1, int *id2, int *id3, int *status  )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The supplied integer should be a numerical identifier for a
+*     graphical element of a plot (AST__MARKS_ID, AST__CURVES_ID, etc), or a
+*     "psuedo-identifier" which represents two other genuine identifiers.
+*     If the supplied value is a genuine identifier then it is returned
+*     in *id1, and *id2 is returned equal to -1. If the supplied value
+*     is a pseudo-identifier then the two corresponding genuine
+*     identifiers are returned in *id1 and *id2
+
+*     For instance, if "id" is AST__AXIS1_ID (a genuine id), then *id1 is
+*     returned equal to AST__AXIS1_ID and *id2 is returned equal to -1. If
+*     "id" is AST__AXES_ID (a pseudo-identifier), then *id1 is returned equal
+*     to AST__AXIS1_ID and *id2 is returned equal to AST__AXIS2_ID.
+
+*     Genuine identifiers all have values which are less than the value
+*     of AST__NPID. Pseudo-identifiers have values which are greater than
+*     or equal to the value of AST__NPID.
+
+*  Parameters:
+*     id
+*        The supplied identifier (genuine or pseudo).
+*     nax
+*        The number of axes spanning graphics space (2 or 3).
+*     id1
+*        Pointer to the int at which to return the first genuine
+*        identifier corresponding to "id".
+*     id2
+*        Pointer to the int at which to return the second genuine
+*        identifier corresponding to "id" (or -1 if "id" is a genuine
+*        identifier).
+*     id3
+*        Pointer to the int at which to return the third genuine
+*        identifier corresponding to "id" (or -1 if "id" has no third
+*        genuine identifier).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of genuine identifiers corresponding to the supplied
+*     (possibly Pseudo-) identifier. This will be in the range 1 to 3.
+
+*/
+
+/* Local Variables: */
+   int ret;
+
+/* Initialise returned values. */
+   *id1 = id;
+   *id2 = -1;
+   *id3 = -1;
+   ret = 0;
+
+/* Check the local error status. */
+   if ( !astOK ) return ret;
+
+/* Assume a genuine identifier was supplied. */
+   ret = 1;
+
+/* Check against all known pseudo-identifier. */
+   if( id == AST__AXES_ID ) {
+      ret = nax;
+      *id1 = AST__AXIS1_ID;
+      *id2 = AST__AXIS2_ID;
+      if( nax == 3 ) *id3 = AST__AXIS3_ID;
+
+   } else if( id == AST__GRIDLINE_ID ) {
+      ret = nax;
+      *id1 = AST__GRIDLINE1_ID;
+      *id2 = AST__GRIDLINE2_ID;
+      if( nax == 3 ) *id3 = AST__GRIDLINE3_ID;
+
+   } else if( id == AST__NUMLABS_ID ) {
+      ret = nax;
+      *id1 = AST__NUMLAB1_ID;
+      *id2 = AST__NUMLAB2_ID;
+      if( nax == 3 ) *id3 = AST__NUMLAB3_ID;
+
+   } else if( id == AST__TEXTLABS_ID ) {
+      ret = nax;
+      *id1 = AST__TEXTLAB1_ID;
+      *id2 = AST__TEXTLAB2_ID;
+      if( nax == 3 ) *id3 = AST__TEXTLAB3_ID;
+
+   } else if( id == AST__TICKS_ID ) {
+      ret = nax;
+      *id1 = AST__TICKS1_ID;
+      *id2 = AST__TICKS2_ID;
+      if( nax == 3 ) *id3 = AST__TICKS3_ID;
+
+   } else if( id >= AST__NPID ) {
+      astError( AST__INTER, "AST internal programming error - "
+                "function IdFind in class Plot does not yet support "
+                "pseudo-identifier value %d", status, id );
+   }
+
+/* Return the answer. */
+   return ret;
+
+}
+
+void astInitPlotVtab_(  AstPlotVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPlotVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Plot.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void astInitPlotVtab( AstPlotVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Plot vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Plot class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrameSetVtab *fset;        /* Pointer to FrameSet component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitFrameSetVtab( (AstFrameSetVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This will be
+   used (by astIsAPlot) to determine if an object belongs to this class.
+   We can conveniently use the address of the (static) class_init variable to
+   generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstFrameSetVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->BBuf = BBuf;
+   vtab->Border = Border;
+   vtab->BoundingBox = BoundingBox;
+   vtab->ClearGrid = ClearGrid;
+   vtab->ClearTol = ClearTol;
+   vtab->Clip = Clip;
+   vtab->CopyPlotDefaults = CopyPlotDefaults;
+   vtab->Curve = Curve;
+   vtab->CvBrk = CvBrk;
+   vtab->EBuf = EBuf;
+   vtab->GenCurve = GenCurve;
+   vtab->GetDrawnTicks = GetDrawnTicks;
+   vtab->GetGrid = GetGrid;
+   vtab->GetTol = GetTol;
+   vtab->GrfPop = GrfPop;
+   vtab->GrfPush = GrfPush;
+   vtab->GrfSet = GrfSet;
+   vtab->GrfWrapper = GrfWrapper;
+   vtab->Grid = Grid;
+   vtab->GridLine = GridLine;
+   vtab->Mark = Mark;
+   vtab->Mirror = Mirror;
+   vtab->PolyCurve = PolyCurve;
+   vtab->RegionOutline = RegionOutline;
+   vtab->SetGrid = SetGrid;
+   vtab->SetTickValues = SetTickValues;
+   vtab->SetTol = SetTol;
+   vtab->TestGrid = TestGrid;
+   vtab->TestTol = TestTol;
+   vtab->Text = Text;
+
+   vtab->ClearTickAll = ClearTickAll;
+   vtab->SetTickAll = SetTickAll;
+   vtab->GetTickAll = GetTickAll;
+   vtab->TestTickAll = TestTickAll;
+
+   vtab->ClearForceExterior = ClearForceExterior;
+   vtab->SetForceExterior = SetForceExterior;
+   vtab->GetForceExterior = GetForceExterior;
+   vtab->TestForceExterior = TestForceExterior;
+
+   vtab->ClearInvisible = ClearInvisible;
+   vtab->SetInvisible = SetInvisible;
+   vtab->GetInvisible = GetInvisible;
+   vtab->TestInvisible = TestInvisible;
+   vtab->ClearBorder = ClearBorder;
+   vtab->SetBorder = SetBorder;
+   vtab->GetBorder = GetBorder;
+   vtab->TestBorder = TestBorder;
+   vtab->ClearInk = ClearInk;
+   vtab->SetInk = SetInk;
+   vtab->GetInk = GetInk;
+   vtab->TestInk = TestInk;
+   vtab->ClearClipOp = ClearClipOp;
+   vtab->SetClipOp = SetClipOp;
+   vtab->GetClipOp = GetClipOp;
+   vtab->TestClipOp = TestClipOp;
+   vtab->ClearClip = ClearClip;
+   vtab->SetClip = SetClip;
+   vtab->GetClip = GetClip;
+   vtab->TestClip = TestClip;
+   vtab->ClearGrf = ClearGrf;
+   vtab->SetGrf = SetGrf;
+   vtab->GetGrf = GetGrf;
+   vtab->TestGrf = TestGrf;
+   vtab->ClearDrawTitle = ClearDrawTitle;
+   vtab->SetDrawTitle = SetDrawTitle;
+   vtab->GetDrawTitle = GetDrawTitle;
+   vtab->TestDrawTitle = TestDrawTitle;
+   vtab->ClearLabelUp = ClearLabelUp;
+   vtab->SetLabelUp = SetLabelUp;
+   vtab->GetLabelUp = GetLabelUp;
+   vtab->TestLabelUp = TestLabelUp;
+   vtab->ClearLogPlot = ClearLogPlot;
+   vtab->SetLogPlot = SetLogPlot;
+   vtab->GetLogPlot = GetLogPlot;
+   vtab->TestLogPlot = TestLogPlot;
+   vtab->ClearLogTicks = ClearLogTicks;
+   vtab->SetLogTicks = SetLogTicks;
+   vtab->GetLogTicks = GetLogTicks;
+   vtab->TestLogTicks = TestLogTicks;
+   vtab->ClearLogLabel = ClearLogLabel;
+   vtab->SetLogLabel = SetLogLabel;
+   vtab->GetLogLabel = GetLogLabel;
+   vtab->TestLogLabel = TestLogLabel;
+   vtab->ClearDrawAxes = ClearDrawAxes;
+   vtab->SetDrawAxes = SetDrawAxes;
+   vtab->GetDrawAxes = GetDrawAxes;
+   vtab->TestDrawAxes = TestDrawAxes;
+   vtab->ClearAbbrev = ClearAbbrev;
+   vtab->SetAbbrev = SetAbbrev;
+   vtab->GetAbbrev = GetAbbrev;
+   vtab->TestAbbrev = TestAbbrev;
+   vtab->ClearEscape = ClearEscape;
+   vtab->SetEscape = SetEscape;
+   vtab->GetEscape = GetEscape;
+   vtab->TestEscape = TestEscape;
+   vtab->ClearLabelling = ClearLabelling;
+   vtab->SetLabelling = SetLabelling;
+   vtab->GetLabelling = GetLabelling;
+   vtab->TestLabelling = TestLabelling;
+   vtab->ClearMajTickLen = ClearMajTickLen;
+   vtab->SetMajTickLen = SetMajTickLen;
+   vtab->GetMajTickLen = GetMajTickLen;
+   vtab->TestMajTickLen = TestMajTickLen;
+   vtab->ClearLogGap = ClearLogGap;
+   vtab->SetLogGap = SetLogGap;
+   vtab->GetLogGap = GetLogGap;
+   vtab->TestLogGap = TestLogGap;
+   vtab->ClearTitleGap = ClearTitleGap;
+   vtab->SetTitleGap = SetTitleGap;
+   vtab->GetTitleGap = GetTitleGap;
+   vtab->TestTitleGap = TestTitleGap;
+   vtab->ClearMinTickLen = ClearMinTickLen;
+   vtab->SetMinTickLen = SetMinTickLen;
+   vtab->GetMinTickLen = GetMinTickLen;
+   vtab->TestMinTickLen = TestMinTickLen;
+   vtab->ClearNumLabGap = ClearNumLabGap;
+   vtab->SetNumLabGap = SetNumLabGap;
+   vtab->GetNumLabGap = GetNumLabGap;
+   vtab->TestNumLabGap = TestNumLabGap;
+   vtab->ClearTextLabGap = ClearTextLabGap;
+   vtab->SetTextLabGap = SetTextLabGap;
+   vtab->GetTextLabGap = GetTextLabGap;
+   vtab->TestTextLabGap = TestTextLabGap;
+   vtab->ClearLabelAt = ClearLabelAt;
+   vtab->SetLabelAt = SetLabelAt;
+   vtab->GetLabelAt = GetLabelAt;
+   vtab->TestLabelAt = TestLabelAt;
+   vtab->ClearCentre = ClearCentre;
+   vtab->SetCentre = SetCentre;
+   vtab->GetCentre = GetCentre;
+   vtab->TestCentre = TestCentre;
+   vtab->ClearGap = ClearGap;
+   vtab->SetGap = SetGap;
+   vtab->GetGap = GetGap;
+   vtab->TestGap = TestGap;
+   vtab->ClearEdge = ClearEdge;
+   vtab->SetEdge = SetEdge;
+   vtab->GetEdge = GetEdge;
+   vtab->TestEdge = TestEdge;
+   vtab->ClearNumLab = ClearNumLab;
+   vtab->SetNumLab = SetNumLab;
+   vtab->GetNumLab = GetNumLab;
+   vtab->TestNumLab = TestNumLab;
+   vtab->ClearMinTick = ClearMinTick;
+   vtab->SetMinTick = SetMinTick;
+   vtab->GetMinTick = GetMinTick;
+   vtab->TestMinTick = TestMinTick;
+   vtab->ClearTextLab = ClearTextLab;
+   vtab->SetTextLab = SetTextLab;
+   vtab->GetTextLab = GetTextLab;
+   vtab->TestTextLab = TestTextLab;
+   vtab->ClearLabelUnits = ClearLabelUnits;
+   vtab->SetLabelUnits = SetLabelUnits;
+   vtab->GetLabelUnits = GetLabelUnits;
+   vtab->TestLabelUnits = TestLabelUnits;
+   vtab->ClearStyle = ClearStyle;
+   vtab->SetStyle = SetStyle;
+   vtab->GetStyle = GetStyle;
+   vtab->TestStyle = TestStyle;
+   vtab->ClearFont = ClearFont;
+   vtab->SetFont = SetFont;
+   vtab->GetFont = GetFont;
+   vtab->TestFont = TestFont;
+   vtab->ClearColour = ClearColour;
+   vtab->SetColour = SetColour;
+   vtab->GetColour = GetColour;
+   vtab->TestColour = TestColour;
+   vtab->ClearWidth = ClearWidth;
+   vtab->SetWidth = SetWidth;
+   vtab->GetWidth = GetWidth;
+   vtab->TestWidth = TestWidth;
+   vtab->ClearSize = ClearSize;
+   vtab->SetSize = SetSize;
+   vtab->GetSize = GetSize;
+   vtab->TestSize = TestSize;
+   vtab->GetGrfContext = GetGrfContext;
+
+/* Save the inherited pointers to methods that will be extended, and replace
+   them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   fset = (AstFrameSetVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_removeframe = fset->RemoveFrame;
+   fset->RemoveFrame = RemoveFrame;
+
+/* Declare the destructor and copy constructor. */
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+
+/* Declare the class dump function. */
+   astSetDump( vtab, Dump, "Plot", "Provide facilities for 2D graphical output" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int Inside( int n, float *cx, float *cy, float x, float y, int *status ){
+/*
+*  Name:
+*     Inside
+
+*  Purpose:
+*     See if a given point is inside a 2-d polygon.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Inside( int n, float *cx, float *cy, float x, float y, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function determines if the position given by x and y, is inside
+*     or outside the polygon specified by the vertices given in arrays cx
+*     and cy.
+
+*  Parameters:
+*     n
+*        The number of vertices in the polygon.
+*     cx
+*        A pointer to an array holding the x coordinates at the "n" vertices.
+*     cy
+*        A pointer to an array holding the y coordinates at the "n" vertices.
+*     x
+*        The x coordinate of the test point.
+*     y
+*        The y coordinate of the test point.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A boolean flag indicating if the test point is inside the polygon.
+
+*  Notes:
+*     -  The algorithm used only works for convex polygons.
+*     -  The polygon is closed by joining the last vertex to the first
+*     vertex.
+*     -  Zero is returned if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   int i;        /* Index of the current vertex */
+   int j;        /* Index of the next vertex */
+   int ret;      /* Is the test point inside the polygon? */
+   int sgn;      /* Which side of the first edge is the test point? */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Initialise the returned value to indicate that the point is inside the
+   box. */
+   ret = 1;
+
+/* Get the sign of the angle between the vector joining vertex 1 to vertex
+   0, and the vector joining the test point to vertex zero. */
+   if( ( cx[ 1 ] - cx[ 0 ] )*( y - cy[ 0 ] ) >
+       ( x - cx[ 0 ] )*( cy[ 1 ] - cy[ 0 ] ) ){
+      sgn = 1;
+   } else {
+      sgn = -1;
+   }
+
+/* Check that the remaining test point is on the same side of the remaining
+   sides. */
+   for( i = 1; i < n; i++ ){
+
+/* Get the index of the next vertex, joining the last vertex up with
+   vertex zero. */
+      j = i + 1;
+      if( j >= n ) j -= n;
+
+/* Get the sign of the angle between the vector joining vertex j to vertex
+   i, and the vector joining the test point to vertex i. If the sign is
+   opposite to that found for vertex zero, then the test point is outside
+   the polygon. Break out of the loop if this is the case. */
+      if( ( cx[ j ] - cx[ i ] )*( y - cy[ i ] ) >
+          ( x - cx[ i ] )*( cy[ j ] - cy[ i ] ) ){
+
+         if( sgn == -1 ) {
+            ret = 0;
+            break;
+         }
+
+      } else {
+
+         if( sgn == 1 ) {
+            ret = 0;
+            break;
+         }
+
+      }
+
+   }
+
+
+/* Return the answer. */
+   return ret;
+
+}
+
+static void InterpEscape( AstPlot *this, int type, double value, float *x,
+                          float *y, float ux, float uy, float rx, float ry,
+                          const char *just, float *rise, double nsize,
+                          double nstyle, double nwidth, double ncol,
+                          double nfont, const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     InterpEscape
+
+*  Purpose:
+*     Prepare things for drawing the next part of a string which includes
+*     graphics escape sequences.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void InterpEscape( AstPlot *this, int type, double value, float *x,
+*                        float *y, float ux, float uy, float rx, float ry,
+*                        const char *just, float *rise, double nsize,
+*                        double nstyle, double nwidth, double ncol,
+*                        double nfont, const char *method, const char *class, int *status )
+
+*  Description:
+*     This function modifies the current graphics attributes, the supplied
+*     reference position, in preparation for drawing another sub-string
+*     from a string containing graphics escape sequences. The type and
+*     value of an escape sequence preceding the substring is supplied.
+*     Note, this function ignored escape sequences which represent an
+*     escaped percent sign. Such escape sequences are drawn as normal
+*     text by the claling function.
+
+*  Parameters:
+*     this
+*        The plot.
+*     type
+*        The type of escape sequence. Each type is identified by a symbolic
+*        constant defined in grf.h.
+*     value
+*        The value associated with the escape sequence. All usable values
+*        will be positive. A value of -1 shold be supplied if the attribute
+*        identified by "type" should be reset to its "normal" value (as
+*        established using the astGAttr function, etc).
+*     x
+*        Pointer to a double holding the x coordinate at the concatenation
+*        point. This will be modified on exit if the escape sequence
+*        requires it.
+*     y
+*        Pointer to a double holding the y coordinate at the concatenation
+*        point. This will be modified on exit if the escape sequence
+*        requires it.
+*     ux
+*        The x component of the up-vector for the text, in graphics coords.
+*        The length of this vector should be equal to the height of normal
+*        text drawn with this up-vector.
+*     uy
+*        The y component of the up-vector for the text. See "ux".
+*     rx
+*        The x component of the right-vector for the text. The length of this
+*        vector should be equal to the height of normal text drawn with the
+*        supplied up-vector.
+*     ry
+*        The y component of the right-vector for the text. see "rx".
+*     just
+*        The justification being used for each substring.
+*     rise
+*        Pointer to a float holding the height of the current baseline
+*        above the normal baseline, given as a percentage of the height of
+*        normal text. May be negative for sub-scripts. May be modified on
+*        exit if the escape sequence effects the height of the baseline.
+*     nsize
+*        The size of normal text.
+*     nstyle
+*        The style of normal text.
+*     nwidth
+*        The width of normal text.
+*     ncol
+*        The colour of normal text.
+*     nfont
+*        The font of normal text.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   float new_rise;
+   float t1, t2;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Type GRF__ESSUP: Move the concatenation point in the up direction, and
+   update the current baseline height. If the value is -1, then reset the
+   baseline to its normal height. */
+   if( type == GRF__ESSUP ) {
+      if( value > 0 ) {
+         *x += 0.01*ux*( value - *rise );
+         *y += 0.01*uy*( value - *rise );
+         *rise = value;
+      } else {
+         *x -= 0.01*ux*(*rise);
+         *y -= 0.01*uy*(*rise);
+         *rise = 0;
+      }
+
+/* Type GRF__ESSUB: Move the concatenation point in the down direction, and
+   update the current baseline height. If the value is -1, then reset the
+   baseline to its normal height. */
+   } else if( type == GRF__ESSUB ) {
+      if( value > 0 ) {
+         *x -= 0.01*ux*( value + *rise );
+         *y -= 0.01*uy*( value + *rise );
+         *rise = -value;
+      } else {
+         *x -= 0.01*ux*(*rise);
+         *y -= 0.01*uy*(*rise);
+         *rise = 0;
+      }
+
+/* Type GRF__ESGAP: Move the concatenation point to the right. */
+   } else if( type == GRF__ESGAP ) {
+      *x += 0.01*rx*value;
+      *y += 0.01*ry*value;
+
+/* Type GRF__ESBAC: Move the concatenation point to the left. */
+   } else if( type == GRF__ESBAC ) {
+      *x -= 0.01*rx*value;
+      *y -= 0.01*ry*value;
+
+/* Remember the current horizontal position. */
+   } else if( type == GRF__ESH ) {
+      this->hmarkx = *x;
+      this->hmarky = *y;
+
+/* Go to the previously stored horizontal position. */
+   } else if( type == GRF__ESG ) {
+      if( this->hmarkx != FLT_MAX && this->hmarky != FLT_MAX ) {
+         t1 = ( *x - this->hmarkx )*rx + ( *y - this->hmarky )*ry;
+         t2 = rx*rx + ry*ry;
+         *x -= rx*t1/t2;
+         *y -= ry*t1/t2;
+      }
+
+/* Type GRF__ESSIZ: Change the text size. */
+   } else if( type == GRF__ESSIZ ) {
+      if( value < 0 ) value = 100.0;
+      GAttr( this, GRF__SIZE, 0.01*value*nsize, NULL, GRF__TEXT,
+             method, class, status );
+
+/* Type GRF__ESWID: Change the text width. */
+   } else if( type == GRF__ESWID ) {
+      if( value < 0 ) value = 100.0;
+      GAttr( this, GRF__WIDTH, 0.01*value*nwidth, NULL, GRF__TEXT,
+             method, class, status );
+
+/* Type GRF__ESFON: Change the text font. */
+   } else if( type == GRF__ESFON ) {
+      if( value < 0 ) value = nfont;
+      GAttr( this, GRF__FONT, value, NULL, GRF__TEXT, method, class, status );
+
+/* Type GRF__ESCOL: Change the text colour. */
+   } else if( type == GRF__ESCOL ) {
+      if( value < 0 ) value = ncol;
+      GAttr( this, GRF__COLOUR, value, NULL, GRF__TEXT, method, class, status );
+
+/* Type GRF__ESSTY: Change the text style. */
+   } else if( type == GRF__ESSTY ) {
+      if( value < 0 ) value = nstyle;
+      GAttr( this, GRF__STYLE, value, NULL, GRF__TEXT, method, class, status );
+
+/* Type GRF__ESPSH: Push current attributes onto a stack. */
+   } else if( type == GRF__ESPSH ) {
+      PushGat( this, *rise, method, class, status );
+
+/* Type GRF__ESSTY: Reset everything to normal. */
+   } else if( type == GRF__ESPOP ) {
+
+      if( !PopGat( this, &new_rise, method, class, status ) ) {
+         new_rise = 0.0;
+         GAttr( this, GRF__SIZE, nsize, NULL, GRF__TEXT, method, class, status );
+         GAttr( this, GRF__WIDTH, nwidth, NULL, GRF__TEXT, method, class, status );
+         GAttr( this, GRF__COLOUR, ncol, NULL, GRF__TEXT, method, class, status );
+         GAttr( this, GRF__FONT, nfont, NULL, GRF__TEXT, method, class, status );
+         GAttr( this, GRF__STYLE, nstyle, NULL, GRF__TEXT, method, class, status );
+      }
+
+      *x -= 0.01*ux*( *rise - new_rise );
+      *y -= 0.01*uy*( *rise - new_rise );
+      *rise = new_rise;
+
+   }
+}
+
+static int IsASkyAxis( AstFrame *frm, int axis, int *status ) {
+/*
+*  Name:
+*     IsASkyAxis
+
+*  Purpose:
+*     Checks if a specified axis of the supplied Frame is a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int IsASkyAxis( AstFrame *frm, int axis, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function checks if if a specified axis of the supplied Frame is
+*     a SkyAxis.
+
+*  Parameters:
+*     frm
+*        The Frame.
+*     axis
+*        The zero-based axis index.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A boolean flag indicating if the axis is a  SkyAxis.
+
+*/
+
+/* Local Variables: */
+   int ret;
+   AstAxis *ax;
+
+/* initialise */
+   ret = 0;
+
+/* Check the global status. */
+   if( !astOK ) return ret;
+
+/* Extract the required axis from the Frame and test if it is a SkyAxis.
+   Then free the axis memory. */
+   ax = astGetAxis( frm, axis );
+   ret = astIsASkyAxis( ax );
+   ax = astAnnul( ax );
+
+/* Return the answer. */
+   return ret;
+
+}
+
+static int IsASkyFrame( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     IsASkyFrame
+
+*  Purpose:
+*     Checks if the supplied Object ca be treated as a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int IsASkyFrame( AstObject *obj, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function checks if the supplied Object is a SkyFrame or a
+*     FrameSet which has a SkyFrame as its current Frame.
+
+*  Parameters:
+*     obj
+*        The object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A boolean flag indicating if the Object can be treated as SkyFrame.
+
+*/
+
+/* Local Variables: */
+   int ret;
+   AstFrame *frm;
+
+/* initialise */
+   ret = 0;
+
+/* Check the global status. */
+   if( !astOK ) return ret;
+
+/* If the Object is a SkyFrame, return 1. */
+   if( astIsASkyFrame( obj ) ) {
+      ret = 1;
+
+/* If the Object is a FrameSet, check its current Frame recursively,
+   using this method. */
+   } else if( astIsAFrameSet( obj ) ) {
+      frm = astGetFrame( (AstFrameSet *) obj, AST__CURRENT );
+      ret = IsASkyFrame( (AstObject *) frm, status );
+      frm = astAnnul( frm );
+   }
+
+/* Return the answer. */
+   return ret;
+
+}
+
+static const char *JustMB( AstPlot *this, int esc, const char *text, float *x,
+                           float *y, float upx, float upy, const char *just,
+                           float uxu, float uyu, float rxu, float ryu,
+                           float *x0, float *y0, const char *method,
+                           const char *class, int *status ){
+/*
+*  Name:
+*     JustMB
+
+*  Purpose:
+*     Modifies a "M" vertical reference point to be a "B" reference point,
+*     if necessary.
+
+*  Synopsis:
+*     #include "plot.h"
+*     const char *JustMB( AstPlot *this, int esc, const char *text, float *x,
+*                         float *y, float upx, float upy, const char *just,
+*                         float uxu, float uyu, float rxu, float ryu,
+*                         float *x0, float *y0, const char *method,
+*                         const char *class, int *status )
+
+*  Description:
+*     This function is used to modify the reference point and justification
+*     of a string by converting the vertical "M" justification option (which
+*     indicates that the reference point refers to the bottom of the
+*     bounding box) into a corresponding "B" option (which indicates that
+*     the reference point refers to the text baseline). The reference
+*     point is modified accordingly.
+*
+*     This is only done if the grf module does not support "M"
+*     justification. Otherwise, the supplied justification string and
+*     reference point coordinates are returned unchanged.
+
+*  Parameters:
+*     this
+*        The plot.
+*     esc
+*        Should escape sequences be interpreted? They will be printed
+*        literally otherwise.
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     x
+*        Pointer to a double holding the x coordinate at the reference
+*        point. This is modified on exit if the supplied "just" string
+*        indicates that the supplied value refers to the bottom of the
+*        bounding box, and the grf module does not support such
+*        justification. In this case, the returned value is a point on
+*        the baseline of the text which would result in the bottom of
+*        the bounding box being at the supplied position.
+*     y
+*        Pointer to a double holding the y coordinate at the reference
+*        point. This is modified on exit if the supplied "just" string
+*        indicates that the supplied value refers to the bottom of the
+*        bounding box, and the grf module does not support such
+*        justification. In this case, the returned value is a point on
+*        the baseline of the text which would result in the bottom of
+*        the bounding box being at the supplied position.
+*     upx
+*        The x component of the up-vector for the text. Positive values
+*        always refer to displacements from left to right on the screen,
+*        even if the graphics x axis increases in the opposite sense.
+*     upy
+*        The y component of the up-vector for the text. Positive values
+*        always refer to displacements from left to right on the screen,
+*        even if the graphics y axis increases in the opposite sense.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", 'B' for "baseline" or "M" for "bottom", and
+*        specifies the vertical location of the reference position. Note,
+*        "baseline" corresponds to the base-line of normal text,and "M"
+*        corresponds to the bottom of the bounding box. Some characters
+*        (eg "y", "g", "p", etc) and sub-scripts descend below the base-line.
+*        The second character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+*     uxu
+*        X component of normalised up-vector, in graphics coords.
+*     uyu
+*        Y component of normalised up-vector, in graphics coords.
+*     rxu
+*        X component of normalised right-vector, in graphics coords.
+*     ryu
+*        Y component of normalised right-vector, in graphics coords.
+*     x0
+*        Address of a float at which to return the x coordinate at the
+*        left end of the baseline of the whole string.
+*     y0
+*        Address of a float at which to return the y coordinate at the
+*        left end of the baseline of the whole string.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated string which contains the
+*     justification to use in future. This pointer should be freed using
+*     astFree when no longer needed. This string will contain a full
+*     upper-case justification string which can be used by the current
+*     grf module.
+
+*  Notes;
+*     - NULL is returned if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   char cc;
+   float drop;
+   float dx;
+   float dy;
+   float f;
+   float height;
+   float width;
+   float xbn[ 4 ];
+   float ybn[ 4 ];
+   int called;
+   char *result;
+
+/* Check inherited status */
+   if( !astOK ) return NULL;
+
+/* Allocate memory for the returned string. */
+   result = astMalloc( sizeof( char )*3 );
+   if( astOK ) {
+
+/* Fill in any missing parts of the justification string. */
+      if( just ){
+         cc = toupper( just[ 0 ] );
+         result[ 0 ] = ( cc == 'T' || cc == 'C' || cc == 'B' || cc == 'M' ) ? cc : 'C';
+
+         cc = toupper( just[ 1 ] );
+         result[ 1 ] = ( cc == 'L' || cc == 'C' || cc == 'R' ) ? cc : 'C';
+
+      } else {
+         result[ 0 ] = 'C';
+         result[ 1 ] = 'C';
+      }
+
+      result[ 2 ] = 0;
+
+/* Indicate that DrawText has not been called. */
+     called = 0;
+
+/* The justfication need not be changed unless the requested vertical
+   justification is "bottom" (m), AND the grf module does not support "M"
+   justification. */
+      if( ( result[ 0 ] == 'M' ) && !GCap( this, GRF__MJUST, 1, status ) ){
+
+/* Find the bounding box which would result from putting the left end of
+   the baseline at the specified position. */
+         DrawText( this, 0, esc, text, *x, *y, "BL", upx, upy, xbn, ybn,
+                   &drop, method, class, status );
+
+/* Indicate that DrawText has not been called. */
+         called = 1;
+
+/* Get the vector from the bottom left corner of the bounding box to the
+   reference point (on the base-line), and add this vector on to the reference
+   point. */
+         *x += *x - xbn[ 0 ];
+         *y += *y - ybn[ 0 ];
+
+/* Modified the returned justification string. */
+         result[ 0 ] = 'B';
+      }
+
+/* If the returned justification is "BL", then the left end of the
+   baseline is just the returned reference point. */
+      if( result[ 0 ] == 'B' && result[ 1 ] == 'L' ) {
+         *x0 = *x;
+         *y0 = *y;
+
+/* Otherwise, we work out the coords of the left end of the baseline from
+   the values returned by DrawText above. Call DrawText now if it was not
+   called above. */
+      } else {
+         if( ! called ) {
+            DrawText( this, 0, esc, text, *x, *y, "BL", upx, upy, xbn, ybn,
+                      &drop, method, class, status );
+         }
+
+/* Find the height and width of the bounding box. */
+         dx = xbn[ 0 ] - xbn[ 3 ];
+         dy = ybn[ 0 ] - ybn[ 3 ];
+         width = sqrt( dx*dx + dy*dy );
+
+         dx = xbn[ 0 ] - xbn[ 1 ];
+         dy = ybn[ 0 ] - ybn[ 1 ];
+         height = sqrt( dx*dx + dy*dy );
+
+/* For "C" and "R" horizontal justification we first need to move the
+   returned reference point left by 0.5 or 1.0 times the width of the whole
+   string respectively. */
+         if( result[ 1 ] == 'C' ) {
+            f = 0.5;
+
+         } else if( result[ 1 ] == 'R' ) {
+            f = 1.0;
+
+         } else {
+            f = 0.0;
+         }
+
+         f *= width;
+
+         *x0 = *x - f*rxu;
+         *y0 = *y - f*ryu;
+
+/* Unless the vertical justification is "B", we also need to move the
+   concatenation point vertically to put it on the baseline. */
+         if( result[ 0 ] == 'T' ) {
+            f = height - drop;
+
+         } else if( result[ 0 ] == 'C' ) {
+            f = 0.5*height - drop;
+
+         } else if( result[ 0 ] == 'M' ) {
+            f = -drop;
+
+         } else {
+            f = 0.0;
+         }
+
+         *x0 -= f*uxu;
+         *y0 -= f*uyu;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int Labelat( AstPlot *this, TickInfo **grid, AstPlotCurveData **cdata,
+                    double *labelat, const char *method, const char *class,
+                    int *status ){
+/*
+*
+*  Name:
+*     Labelat
+
+*  Purpose:
+*     Determine the other axis value at which to place interior labels
+*     and tick marks.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Labelat( AstPlot *this, TickInfo **grid, AstPlotCurveData **cdata,
+*                  double *labelat, const char *method, const char *class )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     If tick marks and labels are to be placed within the plotting area,
+*     the tick values stored in "grid" determine their position on one
+*     axis, and their position on the other axis is determined by this
+*     function. If a value has been set for the "LabelAt" attribute, then
+*     it is used, otherwise the "other axis" value on the longest curve
+*     parallel to the "other axis" is used (although the curve "other axis
+*     = zero" is used if it passes through the plotting area and is not too
+*     short). The effective length assigned to each curve is reduced in
+*     proportion to the number of tick marks which are close to the edge
+*     of the plotting area.
+*
+*     A flag is returned indicating if the two axes appear to be
+*     independent, in which case there is nothing to be gained by using
+*     interior labelling.
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     grid
+*        A pointer to an array of two TickInfo pointers (one for each axis),
+*        each pointing to a TickInfo structure holding information about
+*        tick values on the axis. See function GridLines.
+*     cdata
+*        A pointer to an array of two AstPlotCurveData pointers (one for each axis),
+*        each pointing to an array of AstPlotCurveData structure (one for each
+*        major tick value on the axis), holding information about breaks
+*        in the curves drawn to mark the major tick values. See function
+*        DrawGrid.
+*     labelat
+*        A pointer to a 2 element array in which to store the constant axis
+*        values at which tick marks are put. Element 0 is returned holding
+*        the axis 1 value at which tick marks for axis 0 are placed. Element
+*        1 is returned holding the axis 0 value at which tick marks for axis
+*        1 are placed.
+*     flags
+*        A pointer to a 2 element array. Each element is a flag which is
+*        returned non-zero if the corresponding axis
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+
+*  Returned Value:
+*     Zero if and only if the lines of constant axis value are all the
+*     same length for all tick marks on either axis. If this is so, using
+*     interior labelling will not enable any more major ticks to be
+*     drawn, and so there is no reason to switch to interior labelling (unless
+*     the user has specifically requested interior labelling).
+
+*  Notes:
+*     -  This function assumes the current Frame of the Plot is 2
+*     dimensional, and it should not be called if this is not the case.
+*/
+
+/* Local Variables: */
+   AstMapping *mapping;   /* Mapping from graphics to physical coords */
+   AstPointSet *pset2;    /* Pointset for graphical tick positions */
+   AstPointSet *pset[ 2 ];/* Pointsets for physical tick positions */
+   AstPlotCurveData *cdt; /* Pointer to the AstPlotCurveData for the next tick */
+   TickInfo *info;        /* Pointer to the TickInfo for the current axis */
+   double **ptr2;         /* Pointers to graphics pointset data */
+   double *ptr1[ 2 ];     /* Pointers to physical pointset data */
+   double *tvals[ 2 ];    /* Pointers to arrays of other axis values */
+   double *value;         /* Current tick value */
+   double efflen;         /* Effective length of current curve */
+   double lim;            /* Largest insignificant axis value */
+   double margin;         /* Width of margin around plotting area */
+   double maxlen;         /* Effective length of longest curve */
+   double minlen;         /* Effective length of shortest (non-zero) curve */
+   double x;              /* Tick X value */
+   double xhi;            /* Upper limit on acceptable X range */
+   double xlo;            /* Lower limit on acceptable X range */
+   double y;              /* Tick Y value */
+   double yhi;            /* Upper limit on acceptable Y range */
+   double ylo;            /* Lower limit on acceptable Y range */
+   double zerolen;        /* Effective length of curve for other axis = 0.0 */
+   int axis;              /* Current axis index */
+   int i;                 /* Tick index for this axis */
+   int nin;               /* No. of counted ticks */
+   int result;            /* Does interior labelling allow more ticks to be drawn? */
+   int tick;              /* Tick index */
+
+/* Check the global status. */
+   if( !astOK ) return 0;
+
+/* Initialise */
+   result = 1;
+
+/* Create two PointSets to hold a set of tick mark positions along each
+   axis. The values on "axis" will be taken from the info structure. For
+   each axis create an array to hold values for the "other" axis. */
+   for( axis = 0; axis < 2; axis++ ){
+      info = grid[ axis ];
+      pset[ axis ] = astPointSet( info->nmajor, 2, "", status );
+      tvals[ axis ] = (double *) astMalloc( sizeof(double)*(size_t)(info->nmajor) );
+   }
+
+/* Get the mapping from Base (graphics) frame the Current (physical) */
+   mapping = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Get the bounds of the area in which tick marks must occur to be
+   counted. This is the total plotting area minus a 5% margin at each
+   edge. */
+   margin = 0.05*( this->xhi - this->xlo );
+   xlo = this->xlo + margin;
+   xhi = this->xhi - margin;
+
+   margin = 0.05*( this->yhi - this->ylo );
+   ylo = this->ylo + margin;
+   yhi = this->yhi - margin;
+
+/* Do each axis. */
+   for( axis = 0; axis < 2 && astOK; axis++ ){
+
+/* Find the longest and shortest curves parallel to the axis being labelled.
+   Also find the length of the curve which passes through the origin of the
+   other axis which is within the plotting area. We need to do this even
+   if LabelAt has been set since we need to calculate the returned flag. */
+
+/* Store pointers to the arrays holding tick mark physical coordinates,
+   and set these in the PointSet. */
+      ptr1[ axis ] = grid[ axis ]->ticks;
+      ptr1[ 1 - axis ] = tvals[ axis ];
+      astSetPoints( pset[ axis ], ptr1 );
+
+/* Get a pointer to the structure containing information describing the
+   positions of the major tick marks along the other axis. */
+      info = grid[ 1 - axis ];
+
+/* Get a pointer to the other axis value at the first other axis major tick
+   mark. */
+      value = info->ticks;
+
+/* Get a limit on absolute magnitude for an axis value to be consider
+   equal to zero. */
+      lim = 1.0E-6*fabs( value[ 1 ] - value [ 0 ] );
+
+/* Get a pointer to the structure containing information describing the
+   breaks in the curve which passes through the first major tick mark. */
+      cdt = cdata[ 1 - axis ];
+
+/* Initialise the effective length of the longest and shortest curves, and
+   the curve passing through the origin. */
+      maxlen = -1.0;
+      minlen = DBL_MAX;
+      zerolen = 0.0;
+      labelat[ axis ] = AST__BAD;
+
+/* Loop round each of the major tick marks on the other axis. */
+      for( tick = 0; tick < info->nmajor && astOK; tick++ ){
+
+/* Fill the array of other axis values with the current other axis value. */
+         for( i = 0; i < grid[ axis ]->nmajor; i++ ){
+            tvals[ axis ][ i ] = *value;
+         }
+
+/* Transform the tick positions from the current frame (i.e. physical
+   coordinates) to the base frame (i.e. graphics coordinates) using
+   the inverse Mapping. */
+         pset2 = Trans( this, NULL, mapping, pset[ axis ], 0, NULL, 0,
+                        method, class, status );
+
+/* Get pointers to the graphics coordinates. */
+         ptr2 = astGetPoints( pset2 );
+         if( astOK ) {
+
+/* Count the number of graphics positions which are well within the plotting
+   area. */
+            nin = 0;
+            for( i = 0; i < grid[ axis ]->nmajor; i++ ){
+               x = ptr2[ 0 ][ i ];
+               y = ptr2[ 1 ][ i ];
+               if( x != AST__BAD && x > xlo && x < xhi &&
+                   y != AST__BAD && y > ylo && y < yhi ) nin++;
+            }
+
+/*  Find the effective length of this curve.*/
+            efflen = sqrt( (float) nin )*cdt->length;
+
+/* If the curve through this tick mark has a greater effective length than any
+   other found so far, record it. */
+            if( efflen > maxlen ){
+               maxlen = efflen;
+               labelat[ axis ] = *value;
+            }
+
+/* If the curve through this tick mark has a smaller non-zero effective length
+   than any other found so far, record it. */
+            if( efflen < minlen && efflen > 0.0 ) minlen = efflen;
+
+/* If this tick mark is at the origin, note the effective length. */
+            if( fabs( *value ) <= lim ) zerolen = efflen;
+
+/* Get a pointer to the curve through the next major tick mark. */
+            cdt++;
+
+/* Get a pointer to the axis value at the next major tick mark. */
+            value++;
+
+         }
+
+/* Free resources. */
+         pset2 = astAnnul( pset2 );
+      }
+
+/* Use the curve through the origin unless it is significantly shorter
+   than the longest curve. */
+      if( zerolen > 0.4*maxlen ) labelat[ axis ] = 0.0;
+
+/* Return a flag if the lengths of the shortest and longest curves are nearly
+   equal. */
+      if( ( maxlen - minlen )/( maxlen + minlen ) < 1.0E-5 ) result = 0;
+
+/* If the LabelAt attribute has been set, use it in preference to the
+   value found above. */
+      if( astTestLabelAt( this, axis ) ){
+         labelat[ axis ] = astGetLabelAt( this, axis );
+      }
+   }
+
+/* Release resources. */
+   for( axis = 0; axis < 2; axis++ ){
+      if( pset[ axis ] ) pset[ axis ] = astAnnul( pset[ axis ] );
+      if( tvals[ axis ] ) tvals[ axis ] = (double *) astFree( (void *) tvals[ axis ] );
+   }
+   mapping = astAnnul( mapping );
+
+/* Return. */
+   return result;
+
+}
+
+static void Labels( AstPlot *this, TickInfo **grid, AstPlotCurveData **cdata,
+                    double *gap, double *labelat, const char *method,
+                    const char *class, int *status ){
+/*
+*
+*  Name:
+*     Labels
+
+*  Purpose:
+*     Draw numerical axis labels for a 2-D annotated coordinate grid.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Labels( AstPlot *this, TickInfo **grid, AstPlotCurveData **cdata,
+*                  double *gap, double *labelat, const char *method,
+*                  const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The policy for placing labels for the major tick values is broadly as
+*     follows: if possible, labels for a given physical axis are placed on
+*     one edge of the plotting area, at the place where the curve for a
+*     major tick value crosses the edge. If very few of the curves cross
+*     the edge, then the label for a curve is placed at the intersection
+*     of that curve with the longest of the curves representing the major
+*     tick values on the other axis.
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     grid
+*        A pointer to an array of two TickInfo pointers (one for each axis),
+*        each pointing to a TickInfo structure holding information about
+*        tick values on the axis. See function GridLines.
+*     cdata
+*        A pointer to an array of two AstPlotCurveData pointers (one for each axis),
+*        each pointing to an array of AstPlotCurveData structure (one for each
+*        major tick value on the axis), holding information about breaks
+*        in the curves drawn to mark the major tick values. See function
+*        DrawGrid.
+*     gap
+*        Pointer to array of two values holding the gap between major
+*        tick values on the two axes.
+*     labelat
+*        A pointer to a 2 element array holding the constant axis
+*        values at which tick marks are put. Element 0 should hold
+*        the axis 1 value at which tick marks for axis 0 are placed. Element
+*        1 should hold the axis 0 value at which tick marks for axis
+*        1 are placed. If labels are to be placed round the edges of the
+*        plotting zone instead of within the plotting zone, then values of
+*        AST__BAD should be supplied.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This function assumes the current Frame of the Plot is 2
+*     dimensional, and it should not be called if this is not the case.
+*/
+
+/* Local Variables: */
+   AstFrame *frame;       /* Pointer to current Frame */
+   AstMapping *mapping;   /* Pointer to graphics->physical Mapping */
+   AstPointSet *pset1;    /* Pointer to PointSet holding physical coords. */
+   AstPointSet *pset2;    /* Pointer to PointSet holding graphics coords. */
+   AstPlotCurveData *cdt; /* Pointer to the AstPlotCurveData for the next tick */
+   LabelList *labellist;  /* Pointer to list of labels to be plotted */
+   LabelList *ll;         /* Pointer to next label to be plotted */
+   TickInfo *info;        /* Pointer to the TickInfo for the current axis */
+   char just_buf[3];      /* Buffer to hold a justification string */
+   const char *just;      /* Justification string */
+   const char *text;      /* Pointer to label text */
+   double *used;          /* Pointer to list of used label values */
+   double *value;         /* Current tick value */
+   double diff;           /* Difference between adjacent major tick marks */
+   double dx;             /* Text base-line X component */
+   double dy;             /* Text base-line Y component */
+   double gx;             /* Reference position graphics X coord. */
+   double gy;             /* Reference position graphics Y coord. */
+   double mindim;         /* Shortest dimension of plotting area */
+   double offx;           /* X component of offset vector */
+   double offy;           /* Y component of offset vector */
+   double rlen;           /* Length of perpendicular vector */
+   double rx;             /* X comp of vector perpendicular to (dx,dy) */
+   double ry;             /* Y comp of vector perpendicular to (dx,dy) */
+   double sin45;          /* Sine of 45 degrees */
+   double txtgap;         /* Absolute gap between labels and edges */
+   double upx;            /* Text up-vector X component */
+   double upy;            /* Text up-vector Y component */
+   double val[ 2 ];       /* Physical coordinates */
+   float *box;            /* Pointer to array of label bounding boxes */
+   float alpha;           /* Factor to convert graphics X to equal scaled X */
+   float beta;            /* Factor to convert graphics Y to equal scaled Y */
+   int axis;              /* Current axis index */
+   int esc;               /* Interpret escape sequences? */
+   int flag;              /* Flag indicating which way the base-vector points */
+   int iused;             /* Index into list of used axis values */
+   int last;              /* The index of the last tick to use */
+   int logticks;          /* ARe major ticks spaced logarithmically? */
+   int nlab;              /* The number of labels to be plotted */
+   int nused;             /* Number of used axis values */
+   int t0;                /* Index of central tick */
+   int tick;              /* Current tick index */
+   int tinc;              /* Increment between ticks */
+   int upfree;            /* Are we free to change the up-vector? */
+   int gelid;             /* ID for next graphical element to be drawn */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* See if escape sequences in text strings are to be interpreted */
+   esc = astGetEscape( this );
+
+/* Empty the list of bounding boxes kept by the Overlap function. */
+   (void) Overlap( this, 0, 0, NULL, 0.0, 0.0, NULL, 0.0, 0.0, NULL,
+                   method, class, status );
+
+/* If required, draw the labels around the edges of the plotting area. */
+   if( labelat[ 0 ] == AST__BAD || labelat[ 1 ] == AST__BAD ){
+      (void) EdgeLabels( this, 1, grid, cdata, 1, method, class, status );
+
+/* Otherwise, draw labels within the interior of the plotting area. */
+   } else {
+
+/* Find the scale factors for the two axes which scale graphics coordinates
+   into a "standard" equal scaled coordinate system in which: 1) the axes
+   have equal scale in terms of (for instance) millimetres per unit distance,
+   2) X values increase from left to right, 3) Y values increase from bottom
+   to top. */
+      GScales( this, &alpha, &beta, method, class, status );
+
+/* Get the minimum dimension of the plotting area in equal scaled coords. */
+      mindim = astMIN( fabs( alpha*(this->xhi - this->xlo) ),
+                    fabs( beta*(this->yhi - this->ylo) ) );
+
+/* Store a value for the sine of 45 degrees. */
+      sin45 = 1.0/sqrt( 2.0 );
+
+/* Initialise the pointer to the memory holding the bounding boxes for
+   all labels (used by function Overlap). */
+      box = NULL;
+
+/* Get a pointer to the current Frame in the Plot. */
+      frame = astGetFrame( this, AST__CURRENT );
+
+/* Get a pointer to the mapping form the base Frame to the current Frame in
+   the Plot. */
+      mapping = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Initialize the id value for graphical element being drawn. */
+      gelid = AST__NUMLAB1_ID;
+
+/* Do each axis. */
+      for( axis = 0; axis < 2; axis++ ){
+
+/* See of major ticks are spaced logarithmically on this axis. */
+         logticks = astGetLogTicks( this, axis );
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+         astGrfAttrs( this, gelid, 1, GRF__TEXT, method, class );
+
+/* Get a pointer to the structure containing information describing the
+   positions of the major tick marks along this axis. */
+         info = grid[ axis ];
+
+/* Only progress if there are some labels stored within the structure. */
+         if( info->labels ) {
+
+/* Initialise the pointer to the list of text strings to be drawn. */
+            labellist = NULL;
+            nlab = 0;
+
+/* See if numerical labels are always to be drawn horizontal. If so, set
+   a flag and initialise a vertical up-vector. */
+            if( astGetLabelUp( this, axis ) ){
+               upfree = 0;
+               upx = 0.0;
+               upy = 1.0;
+
+/* Otherwise, clear the flag and indicate that we do not yet have an
+   up-vector. */
+            } else {
+               upfree = 1;
+               upx = AST__BAD;
+               upy = AST__BAD;
+            }
+
+/* Indicate that the tangent vector to the other axis is not yet
+   known. */
+            dx = AST__BAD;
+            dy = AST__BAD;
+            gx = AST__BAD;
+            gy = AST__BAD;
+
+/* Store the gap to put next to the label text. This is in equal scaled
+   coords, not graphics coords. */
+            txtgap = astGetNumLabGap( this, axis )*mindim;
+
+/* Get a pointer to the structure containing information describing the
+   breaks in the curve which passes through the first major tick mark. */
+            cdt = cdata[ axis ];
+
+/* Get a pointer to the axis value at the first major tick mark. */
+            value = info->ticks;
+
+/* Initialise pointers to two PointSets which will be created and used
+   within function GVec. */
+            pset1 = NULL;
+            pset2 = NULL;
+
+/* Get memory to hold the axis values at which labels have been put. */
+            used = (double *) astMalloc( sizeof(double)*(size_t)info->nmajor );
+            nused = 0;
+
+/* The tick marks are done in two batches, each batch working out from the
+   middle. This is done because there may be extra tick marks outside the
+   normal ranges at the extremes, and these should not be given the
+   priority caused by doing them first. Store the mid-tick index, the
+   current tick index, and the increment between ticks. The ticks from the
+   middle up to the highest index are done first. */
+            t0 = info->nmajor/2;
+            tick = t0 - 1;
+            tinc = 1;
+
+/* Loop round until all ticks have been done. */
+            last = info->nmajor - 1;
+            while( (tick += tinc) >= 0 && astOK ){
+
+/* If we have done the highest tick index, start again at the tick just
+   below middle, and work done towards index zero. */
+               if( tick == info->nmajor ){
+                  tick = t0 - 1;
+                  tinc = -1;
+               }
+
+/* Store the reference position for the label . */
+               val[ axis ] = value[ tick ];
+               val[ 1 - axis ] = labelat[ axis ];
+
+/* Store the difference between this tick and the next. */
+               if( logticks ) {
+                  diff = value[ tick ]*( gap[ axis ] - 1.0 );
+               } else {
+                  diff = gap[ axis ];
+               }
+
+/* See if this axis value has already been used. */
+               for( iused = 0; iused < nused; iused++ ){
+                  if( fabs( val[ axis ] - used[ iused ] ) <
+                      1.0E-3*diff ) break;
+               }
+
+/* If the axis value has already been used, don't use it again. */
+               if( iused >= nused || nused == 0 ){
+                  used[ nused++ ] = val[ axis ];
+
+/* We now need to decide where to put the reference point for the text
+   string, and what justification to use. Assuming that NumLabGap is +ve,
+   the labels are drawn on the left hand side of the axis as seen by
+   someone moving along the axis in the positive direction, with an
+   up-vector which is normal to the axis tangent. First, find the graphics
+   coordinates at the point being labelled, and the unit tangent-vector
+   parallel to the axis being labelled. If the tangent vector is not defined,
+   then the tangent vector used for the previous label is re-used. This
+   unit tangent vector is expressed in graphics coords. */
+                  GVec( this, mapping, val, axis, 0.01*diff, &pset1,
+                        &pset2, &gx, &gy, &dx, &dy, &flag, method, class,
+                        status );
+
+/* If we now have a tangent vector and good graphics coordinates for the
+   label's reference position... */
+                  if( dx != AST__BAD && dy != AST__BAD &&
+                      gx != AST__BAD && gy != AST__BAD ){
+
+/* Convert the unit tangent vector from graphics coords to equal-scaled coords. */
+                     dx *= alpha;
+                     dy *= beta;
+
+/* Rotate through 90 degrees to get a vector perpendicular to the axis in
+   equal scaled coords. This vector points to the left as you move along
+   the physical axis in the positive direction. Find its length. */
+                     rx = -dy;
+                     ry = dx;
+                     rlen = sqrt( rx*rx + ry*ry );
+
+/* The reference position for the text is displaced away from the
+   reference position normal to the axis on the left hand side by the
+   "txtgap" value. */
+                     offx = rx*txtgap/rlen;
+                     offy = ry*txtgap/rlen;
+                     gx += offx/alpha;
+                     gy += offy/beta;
+
+/* The up-vector and justification for the text depends on whether or
+   not the up-vector is free to rotate. If it is free, the up-vector is
+   chosen so that the text is not upside-down. Note, the up-vector is
+   specified in the equally scaled coordinate system. */
+                     if( upfree ){
+
+                        if( dx < -0.01*fabs( alpha ) ){
+                           upx = -rx;
+                           upy = -ry;
+                           just = ( txtgap < 0.0 )? "BC" : "TC";
+                        } else {
+                           upx = rx;
+                           upy = ry;
+                           just = ( txtgap < 0.0 )? "TC" : "BC";
+                        }
+                        if( txtgap == 0.0 ) just = "CC";
+
+/* If the up vector is required to be vertical, a system is used which
+   tries to put the centre of the text string on or near the offset
+   vector. */
+                     } else {
+                        upx = 0.0;
+                        upy = 1.0;
+
+                        if( offy > fabs(txtgap)*sin45 ){
+                           just_buf[0] = 'B';
+                        } else if( offy < -fabs(txtgap)*sin45 ){
+                           just_buf[0] = 'T';
+                        } else {
+                           just_buf[0] = 'C';
+                        }
+                        if( txtgap == 0.0 ) just_buf[0] = 'C';
+
+                        if( offx < -fabs(txtgap)*sin45 ){
+                           just_buf[1] = 'R';
+                        } else if( offx > fabs(txtgap)*sin45 ){
+                           just_buf[1] = 'L';
+                        } else {
+                           just_buf[1] = 'C';
+                        }
+                        if( txtgap == 0.0 ) just_buf[1] = 'C';
+
+                        just_buf[2] = 0;
+                        just = just_buf;
+                     }
+
+/* Get the label text. */
+                     text = (info->labels)[ tick ];
+                     if( text ){
+
+/* Check that the reference position is within the plotting area.
+   If so, add it to the list of labels to be drawn. */
+                        if( gx >= this->xlo && gx <= this->xhi &&
+                            gy >= this->ylo && gy <= this->yhi ){
+
+                           labellist = (LabelList *) astGrow( (void *) labellist, nlab + 1, sizeof(LabelList) );
+                           if ( astOK ) {
+                              (labellist + nlab)->index = tick;
+                              (labellist + nlab)->text = (char *) astStore( NULL, (void *) text, strlen(text) + 1 );
+                              (labellist + nlab)->x = gx;
+                              (labellist + nlab)->y = gy;
+                              (labellist + nlab)->just = (char *) astStore( NULL, (void *) just, strlen(just) + 1 );
+                              (labellist + nlab)->upx = upx;
+                              (labellist + nlab)->upy = upy;
+                              (labellist + nlab)->val = val[ axis ];
+                              nlab++;
+                           } else {
+                              break;
+                           }
+                        }
+                     }
+                  }
+               }
+            }
+
+/* If any labels were stored, draw the text strings, and then release the
+   memory used to hold the text, etc. */
+            if( nlab > 0 ) {
+               PlotLabels( this, esc, frame, axis, labellist, info->fmt, nlab,
+                           &box, method, class, status );
+               ll = labellist;
+               for( tick = 0; tick < nlab; tick ++ ) {
+                  ll->text = (char *) astFree( (void *) ll->text );
+                  ll->just = (char *) astFree( (void *) ll->just );
+                  ll++;
+               }
+               labellist = (LabelList *) astFree( (void *) labellist );
+            }
+
+/* Free the memory used to hold the axis values at which labels have
+   been put. */
+            used = (double *) astFree( (void *) used );
+
+/* Annul the PointSets (if used). */
+            if( pset1 ) pset1 = astAnnul( pset1 );
+            if( pset2 ) pset2 = astAnnul( pset2 );
+
+/* Re-establish the original graphical attributes. */
+            astGrfAttrs( this, gelid, 0, GRF__TEXT, method, class );
+
+/* Set up the id for the next graphical element to be drawn. */
+            gelid = AST__NUMLAB2_ID;
+
+         }
+      }
+
+/* Free the memory used to hold the bounding boxes. */
+      box = (float *) astFree( (void *) box );
+
+/* Annul the pointers to the Frame and the Mapping. */
+      mapping = astAnnul( mapping );
+      frame = astAnnul( frame );
+
+   }
+
+/* Return. */
+   return;
+
+}
+
+static void LinePlot( AstPlot *this, double xa, double ya, double xb,
+                      double yb, int ink, AstPlotCurveData *cdata,
+                      const char *method, const char *class, int *status ){
+/*
+*
+*  Name:
+*     LinePlot
+
+*  Purpose:
+*     Draws a straight line omitting bad regions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void LinePlot( AstPlot *this, double xa, double ya, double xb,
+*                    double yb, int ink, AstPlotCurveData *cdata,
+*                    const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function draws a straight line between two positions in graphics
+*     coordinates but leaves gaps in the line where it passes through
+*     regions which have no corresponding physical coordinates.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     xa
+*        The graphics X coordinate at the start of the line.
+*     ya
+*        The graphics Y coordinate at the start of the line.
+*     xb
+*        The graphics X coordinate at the end of the line.
+*     yb
+*        The graphics Y coordinate at the end of the line.
+*     ink
+*        If zero, the line is not actually drawn, but information about
+*        the breaks is still returned. If non-zero, the line is also drawn.
+*     cdata
+*        A pointer to a structure in which to return information about the
+*        breaks in the line.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  No curve is draw if any of the start or end positions are bad
+*     (i.e. equal to AST__BAD), or if a NULL pointer is supplied for "cdata".
+*     No errors are reported in these cases.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   double d[ CRV_NPNT ];   /* Offsets to evenly spaced points along curve */
+   double x[ CRV_NPNT ];   /* X coords at evenly spaced points along curve */
+   double y[ CRV_NPNT ];   /* Y coords at evenly spaced points along curve */
+   double tol;             /* Absolute tolerance value */
+   int i;                  /* Loop count */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Check the supplied values are usable. */
+   if( xa == AST__BAD || ya == AST__BAD ||
+       xb == AST__BAD || yb == AST__BAD ||
+      !cdata ) return;
+
+/* Convert the tolerance from relative to absolute graphics coordinates. */
+   tol = astGetTol( this )*astMAX( this->xhi - this->xlo, this->yhi - this->ylo );
+
+/* Ensure the globals holding the scaling from graphics coords to equally
+   scaled coords are available. */
+   GScales( this, NULL, NULL, method, class, status );
+
+/* Set up the external variables used by the Crv and CrvLine function (see
+   their prologues for details). */
+   Crv_scerr = ( astGetLogPlot( this, 0 ) ||
+                 astGetLogPlot( this, 1 ) ) ? 100.0 : 1.5;
+   Crv_ux0 = AST__BAD;
+   Crv_limit = 0.5*tol*tol;
+   Crv_tol = tol;
+   Crv_map = Map2;
+   Crv_ink = ink;
+   Crv_len = 0.0F;
+   Crv_xlo = this->xlo;
+   Crv_xhi = this->xhi;
+   Crv_ylo = this->ylo;
+   Crv_yhi = this->yhi;
+   Crv_out = 1;
+   Crv_xbrk = cdata->xbrk;
+   Crv_ybrk = cdata->ybrk;
+   Crv_vxbrk = cdata->vxbrk;
+   Crv_vybrk = cdata->vybrk;
+   Crv_clip = astGetClip( this ) & 1;
+
+/* Create a set of evenly spaced values between 0.0 and 1.0. These are the
+   offsets the edge of the plotting zone at which the mapping is tested. */
+   for( i = 0; i < CRV_NPNT; i++ ){
+     d[ i ] = ( (double) i)/( (double) CRV_NSEG );
+   }
+
+/* Now set up the externals used to communicate with the Map2 function.
+   Map2 transforms a set of offsets between zero and one into a set of
+   corresponding graphics coordinates, with bad values substituted for any
+   offsets which correspond to points outside the domain of the mapping. */
+
+/* The number of axes in the physical coordinate system (i.e. the current
+   Frame). */
+   Map2_ncoord = astGetNout( this );
+
+/* A pointer to the mapping from graphics world cordinates to physical
+   coordinates. */
+   Map2_plot = this;
+   Map2_map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* The graphics coordinates corresponding to an offset of zero (i.e.
+   the start of the line). */
+   Map2_x0 = xa;
+   Map2_y0 = ya;
+
+/* The increments in X and Y between offset zero (the start of the
+   line) and offset 1 (the end of the line). */
+   Map2_deltax = xb - xa;
+   Map2_deltay = yb - ya;
+
+/* Get the graphics coordinates corresponding to the initial set of
+   offsets. */
+   Map2( CRV_NPNT, d, x, y, method, class, status GLOBALS_NAME );
+
+/* Use Crv and Map2 to draw the intersection of the straight line with
+   the region containing valid physical coordinates. */
+   Crv( this, d, x, y, 0, NULL, NULL, method, class, status );
+
+/* End the current poly line. */
+   Opoly( this, status );
+
+/* Tidy up the static data used by Map2. */
+   Map2( 0, NULL, NULL, NULL, method, class, status GLOBALS_NAME );
+
+/* If no part of the curve could be drawn, set the number of breaks and the
+   length of the drawn curve to zero. */
+   if( Crv_out ) {
+      Crv_nbrk = 0;
+      Crv_len = 0.0F;
+
+/* Otherwise, add an extra break to the returned structure at the position of
+   the last point to be plotted. */
+   } else {
+      Crv_nbrk++;
+      if( Crv_nbrk > AST__PLOT_CRV_MXBRK ){
+         astError( AST__CVBRK, "%s(%s): Number of breaks in curve "
+                   "exceeds %d.", status, method, class, AST__PLOT_CRV_MXBRK );
+      } else {
+         *(Crv_xbrk++) = (float) Crv_xl;
+         *(Crv_ybrk++) = (float) Crv_yl;
+         *(Crv_vxbrk++) = (float) -Crv_vxl;
+         *(Crv_vybrk++) = (float) -Crv_vyl;
+      }
+   }
+
+/* Store extra information about the curve in the returned structure, and
+   purge any zero length sections. */
+   if( cdata ){
+      cdata->length = Crv_len;
+      cdata->out = Crv_out;
+      cdata->nbrk = Crv_nbrk;
+      PurgeCdata( cdata, status );
+   }
+
+/* Annul the Mapping. */
+   Map2_map = astAnnul( Map2_map );
+
+/* Return. */
+   return;
+
+}
+
+static double **MakeGrid( AstPlot *this, AstFrame *frm, AstMapping *map,
+                          int disk, int dim, double xlo, double xhi,
+                          double ylo, double yhi, int nphy, AstPointSet **pset1,
+                          AstPointSet **pset2, int norm, const char *method,
+                          const char *class, int *status ){
+/*
+*  Name:
+*     MakeGrid
+
+*  Purpose:
+*     Create a square grid of graphics coordinates and the corresponding
+*     physical coordinates.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     double **MakeGrid( AstPlot *this, AstFrame *frm, AstMapping *map,
+*                        int disk, int dim, double xlo, double xhi, double ylo,
+*                        double yhi, int nphy, AstPointSet **pset1,
+*                        AstPointSet **pset2, int norm, const char *method,
+*                        const char *class, int *status ){
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function creates two PointSets, one holding a square grid of
+*     graphics coordinates covering the supplied area, and the other
+*     holding the corresponding physical coordinates. The points are
+*     stored row by row in the returned PointSets, i.e. if the cell size
+*     for the grid is (dx,dy), the first point is (xmin,ymin), followed
+*     by (xmin+dx,ymin), (xmin+2*dx,ymin), up to (xmin+(dim-1)*dx,ymin),
+*     followed by the next row (xmin,ymin+dy), (xmin+dx,ymin+dy), etc.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     frm
+*        A pointer to the Current Frame in the Plot. If this is supplied
+*        NULL, then a pointer is found within this function if required (i.e.
+*        if "norm" is non-zero).
+*     map
+*        The Mapping from graphics to physical coordinates, extracted from
+*        the Plot.
+*     disk
+*        If non-zero, the corners of the grid are omitted form the
+*        returned PointSets, resulting in a grid that is more disk like than
+*        rectangular.
+*     dim
+*        The number of samples along each edge of the grid.
+*     xlo
+*        The lower bound on the first axis of the region to be covered
+*        by the grid.
+*     xhi
+*        The upper bound on the first axis of the region to be covered
+*        by the grid.
+*     ylo
+*        The lower bound on the second axis of the region to be covered
+*        by the grid.
+*     yhi
+*        The upper bound on the second axis of the region to be covered
+*        by the grid.
+*     nphy
+*        The number of axes in the physical cooridinate system.
+*     pset1
+*        A pointer to a location at which to store a pointer to the
+*        PointSet holding the graphics coordinates.
+*     pset2
+*        A pointer to a location at which to store a pointer to the
+*        PointSet holding the physical coordinates.
+*     norm
+*        If non-zero the physical cooridnates are normalised using the
+*        Plot's astNorm method.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the physical coordinate data stored in the PointSet
+*     "pset2".
+
+*  Notes:
+*     - The returned PointSets should be annulled when no longer needed,
+*     using astAnnul.
+*     -  NULL pointers are returned if an error has already occurred, or
+*     if this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   double **ptr1;    /* Pointers to graphics axis values */
+   double **ptr2;    /* Pointers to physical axis values */
+   int size;         /* No. of points in the grid */
+
+/* Initialise the returned pointers. */
+   *pset1 = NULL;
+   *pset2 = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Create two PointSets. We assume for the moment that they cover the
+   full grid, including corners. */
+   size = dim*dim;
+   *pset1 = astPointSet( size, 2, "", status );
+   *pset2 = astPointSet( size, nphy, "", status );
+
+/* Get pointers to the data arrays for the two PointSets. */
+   ptr1 = astGetPoints( *pset1 );
+   ptr2 = astGetPoints( *pset2 );
+
+/* Create a grid covering the supplied area. */
+   size = GraphGrid( dim, disk, xlo, xhi, ylo, yhi, ptr1, status );
+
+/* If the corners are being omitted, reduce the number of points in the
+   two PointSets. */
+   if( disk ) {
+      astSetNpoint( *pset1, size );
+      astSetNpoint( *pset2, size );
+   }
+
+/* Transform these graphics positions to physical coordinates. */
+   Trans( this, frm, map, *pset1, 1, *pset2, norm, method, class, status );
+
+/* If an error has occurred, annul the two pointsets. */
+   if( !astOK ){
+      *pset1 = astAnnul( *pset1 );
+      *pset2 = astAnnul( *pset2 );
+      ptr2 = NULL;
+   }
+
+/* Return. */
+   return ptr2;
+
+}
+
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     Plot member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstPlot *this;    /* Pointer to Plot structure */
+   int result;       /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the Plot structure. */
+   this = (AstPlot *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* If defined, ensure the grfcontext KeyMap contained within the Plot is
+   locked, unlocked or checked. */
+   if( this->grfcontext ) {
+      if( !result ) result = astManageLock( this->grfcontext, mode, extra, fail );
+
+/* Also lock or unlock the associated object handle. */
+      if( mode == AST__LOCK ) {
+         if( !result ) astLock( this->grfcontextID, extra );
+
+      } else if( mode == AST__UNLOCK ) {
+         if( !result ) astUnlock( this->grfcontextID, 0 );
+
+      }
+   }
+
+   return result;
+
+}
+#endif
+
+static void Map1( int n, double *dist, double *x, double *y,
+                  const char *method, const char *class,
+                  int *status GLOBALS_ARG ){
+/*
+*  Name:
+*     Map1
+
+*  Purpose:
+*     Find graphics coordinates at given distances along a curve
+*     parallel to a physical axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Map1( int n, double *dist, double *x, double *y,
+*                const char *method, const char *class,
+*                int *status [,AstGlobals *AST__GLOBALS] )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The supplied distances are converted into physical coordinates
+*     using the scalings described by various external variables, and then
+*     these physical coordinates are mapped into graphics coordinates.
+
+*  Parameters:
+*     n
+*        The number of points to map. Static resources are released but
+*        no points are mapped if zero is supplied.
+*     dist
+*        A pointer to an array holding "n" distances. A "dist" value of
+*        zero corresponds to the starting position supplied in external
+*        variable Map1_origin. A "dist" value of one corresponds to the
+*        finishing position which is a distance Map1_length away from
+*        Map1_origin, moving in the positive direction of the axis given
+*        by Map1_axis. "dist" values can be either linearly or
+*        logarithmically related to axis values (see Map1_log).
+*     x
+*        A pointer to an array in which to store the "n" graphics X
+*        coordinate values corresponding to the positions in "dist".
+*     y
+*        A pointer to an array in which to store the "n" graphics Y
+*        coordinate values corresponding to the positions in "dist".
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*     AST__GLOBALS
+*        Only present if compiled with -DTHREAD_SAFE. It is a pointer to
+*        the structure holding the global data for the executing thread.
+*        It is passed as a function parameter, rather than being accessed
+*        within this function using the astGET_GLOBALS(NULL) macro (as
+*        other Object-less functions do) in order to avoid the time
+*        overheads of calling astGET_GLOBALS(NULL) . This function is
+*        time-critical.
+
+*  External Variables:
+*     Map1_log = int (Read)
+*        If zero, then "dist" in learly related to axis value. Otherwise
+*        it is linearly related to log10(axis value).
+*     Map1_ncoord = int (Read)
+*        The number of axes in the physical coordinate system.
+*     Map1_axis = int (Read)
+*        The zero-based index of the axis which the curve follows (i.e.
+*        the axis which changes value along the curve).
+*     Map1_statics = Map1Statics * (Read and Write)
+*        Pointer to a structure holding other static data used by Map1.
+*     Map1_origin = const double * (Read)
+*        A pointer to an array holding the physical coordinate value on
+*        each axis at the start of the curve (i.e. at dist = 0.0).
+*     Map1_length = double (Read)
+*        The scale factor to convert "dist" values into increments
+*        along the physical axis given by Map1_axis.
+*     Map1_plot = AstPlot * (Read)
+*        A pointer to the Plot defining the mapping from graphics cordinates
+*        to physical coordinates.
+*     Map1_map = AstMapping * (Read)
+*        A pointer to the mapping from graphics cordinates to physical
+*        coordinates extracted from the Plot.
+*     Map1_frame = AstFrame * (Read)
+*        A pointer to the Current Frame in the Plot.
+*     Map1_norm = int (Read)
+*        A flag indicating if physical coordinate values which are not in
+*        the normal ranges of the corresponding axes should be considered
+*        bad.
+
+*  Notes:
+*     -  On the first call, this function allocates static resources which
+*     are used by subsequent invocation. These resources should be freed before
+*     calling this function with new values for any of the external variables,
+*     or when no longer needed, by calling this function with "n" supplied as
+*     zero.
+*     -  If an error has already occurred, this runction returns without
+*     action ,except that if "n" is supplied as zero then static resources
+*     are released even if an error has already occurred.
+
+*/
+
+/* Local Constants: */
+   Map1Statics *statics;     /* Pointer to structure holding static data */
+   double *p;                /* Pointer to next value */
+   double axval;             /* Axis origin value */
+   int i, j;                 /* Loop counts */
+
+/* Convert the global "void *" pointer to a Map1Statics pointer */
+   statics = (Map1Statics *) Map1_statics;
+
+/* If zero points were supplied, release static resources and return. */
+   if( n == 0 ){
+      if( statics ) {
+         if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+         if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+         if( statics->work1 ) statics->work1 = (double *) astFree( (void *) statics->work1 );
+         if( statics->work2 ) statics->work2 = (double *) astFree( (void *) statics->work2 );
+         Map1_statics = astFree( statics );
+      }
+      return;
+   }
+
+/* Otherwise, check the inherited global status. */
+   if( !astOK ) return;
+
+/* Create and initialise a structure to hold extra static information if
+   this has not already been done. */
+   if( !statics ) {
+      statics = astMalloc( sizeof( Map1Statics ) );
+      if( statics ) {
+         statics->pset1 = NULL;
+         statics->pset2 = NULL;
+         statics->ptr1 = NULL;
+         statics->pax = NULL;
+         statics->ptr2[ 0 ] = NULL;
+         statics->ptr2[ 1 ] = NULL;
+         statics->work1 = NULL;
+         statics->work2 = NULL;
+         statics->nl = 0;
+         Map1_statics = statics;
+      }
+   }
+
+/* If the number of points to be mapped is different to last time,
+   set up some PointSets to store the specified number of points. */
+   if( n != statics->nl ){
+      statics->nl = n;
+
+/* Create a PointSet to hold the physical coordinates corresponding to
+   the supplied offsets. First annul any existing PointSet. */
+      if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+      statics->pset1 = astPointSet( n, Map1_ncoord, "", status );
+      statics->ptr1 = astGetPoints( statics->pset1 );
+
+/* Create a PointSet to hold the corresponding graphics coordinates.
+   The supplied "x" and "y" arrays will be used to store the data
+   so we do not need to get pointers to the data using astGetPoints. First
+   annul any existing PointSet. */
+      if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+      statics->pset2 = astPointSet( n, 2, "", status );
+
+/* Get work space to hold two positions. */
+      statics->work1 = (double *) astRealloc( (void *) statics->work1,
+                                          sizeof(double)*(size_t)Map1_ncoord );
+      statics->work2 = (double *) astRealloc( (void *) statics->work2,
+                                          sizeof(double)*(size_t)Map1_ncoord );
+
+/* Check the pointer can be used. */
+      if( astOK ){
+
+/* Store a pointer to the start of the memory which will be used to store
+   the physical data for the axis being drawn. */
+         statics->pax = statics->ptr1[ Map1_axis ];
+
+/* Fill the PointSet which is used to hold physical data with the physical
+   coordinates at the start of the curve. */
+         for( i = 0; i < Map1_ncoord; i++ ){
+            axval = Map1_origin[ i ];
+            p = statics->ptr1[ i ];
+            for( j = 0; j < n; j++ ) *(p++) = axval;
+         }
+
+/* Store the scale and offset to apply to the "dist" values. If Map1_log is
+   zero (linear axes) then applying these values gives axis value directly.
+   If Map1_log is non-zero (log axes) then applying these values gives
+   log10( axis value). */
+         if( Map1_log ) {
+            statics->neg = ( Map1_origin[ Map1_axis ] < 0 );
+            statics->axorig = log10( fabs( Map1_origin[ Map1_axis ] ) );
+            statics->axscale = log10( fabs( Map1_origin[ Map1_axis ] +
+                                   Map1_length ) ) - statics->axorig;
+         } else {
+            statics->axorig = Map1_origin[ Map1_axis ];
+            statics->axscale = Map1_length;
+         }
+       }
+    }
+
+/* Check the initialisation went OK (if done). */
+   if( astOK ){
+
+/* Loop round each offset along the curve, converting the normalised offset
+   in the range [0,1] to a physical coordinate and storing in PointSet 1. */
+      p = statics->pax;
+      for( i = 0; i < n; i++){
+         *(p++) = statics->axorig + statics->axscale*dist[ i ];
+      }
+      if( Map1_log ) {
+         p = statics->pax;
+         for( i = 0; i < n; i++,p++ ){
+            *p = statics->neg ? -pow( 10.0, *p ) : pow( 10.0, *p );
+         }
+      }
+
+/* Store pointers to the results arrays in PointSet 2. */
+      statics->ptr2[ 0 ] = x;
+      statics->ptr2[ 1 ] = y;
+      astSetPoints( statics->pset2, statics->ptr2 );
+
+/* Map all the positions into graphics coordinates. */
+      (void) Trans( Map1_plot, NULL, Map1_map, statics->pset1, 0, statics->pset2, 1, method, class, status );
+
+/* If points not in their normal ranges are to be set bad... */
+      if( Map1_norm ) {
+
+/* The following code simply normalizes the physical position, and if this
+   produces any change, the graphics positions are set bad. */
+         for( i = 0; i < n; i++){
+            for( j = 0; j < Map1_ncoord; j++) statics->work1[j] = statics->ptr1[j][i];
+            astNorm( Map1_frame, statics->work1 );
+            for( j = 0; j < Map1_ncoord; j++) {
+               if( !astEQUALS( statics->work1[j], statics->ptr1[j][i], 1.0E8 ) ) {
+                  statics->ptr2[0][i] = AST__BAD;
+                  statics->ptr2[1][i] = AST__BAD;
+                  break;
+               }
+            }
+         }
+      }
+   }
+
+/* Return. */
+   return;
+
+}
+
+static void Map2( int n, double *dist, double *x, double *y,
+                  const char *method, const char *class,
+                  int *status GLOBALS_ARG ){
+/*
+*  Name:
+*     Map2
+
+*  Purpose:
+*     Find which graphics coordinates have good physical coordinates
+*     at given distances along a straight line.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Map2( int n, double *dist, double *x, double *y,
+*                const char *method, const char *class,
+*                int *status [,AstGlobals *AST__GLOBALS] )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The supplied distances refer to the distance along a straight line
+*     in the graphics coordinate system. The returned graphics coordinates
+*     correspond to the supplied distances, except that any position for
+*     which there are no defined physical coordinates is returned bad.
+
+*  Parameters:
+*     n
+*        The number of points to map. Static resources are released but
+*        no points are mapped if zero is supplied.
+*     dist
+*        A pointer to an array holding "n" distances. A "dist" value of
+*        zero corresponds to the graphics position supplied in external
+*        variables (Map2_x0, Map2_y0). A "dist" value of one corresponds to
+*        the graphics position which is offset from the start by the vector
+*        (Map2_deltax, Map2_deltay).
+*     x
+*        A pointer to an array in which to store the "n" graphics X
+*        coordinate values corresponding to the positions in "dist",
+*        except that any which have no corresponding physical coordinates
+*        are set to AST__BAD.
+*     y
+*        A pointer to an array in which to store the "n" graphics Y
+*        coordinate values corresponding to the positions in "dist",
+*        except that any which have no corresponding physical coordinates
+*        are set to AST__BAD.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*     AST__GLOBALS
+*        Only present if compiled with -DTHREAD_SAFE. It is a pointer to
+*        the structure holding the global data for the executing thread.
+*        It is passed as a function parameter, rather than being accessed
+*        within this function using the astGET_GLOBALS(NULL) macro (as
+*        other Object-less functions do) in order to avoid the time
+*        overheads of calling astGET_GLOBALS(NULL) . This function is
+*        time-critical.
+
+*  External Variables:
+*     Map2_ncoord = int (Read)
+*        The number of axes in the physical coordinate system.
+*     Map2_x0 = double (Read)
+*        The graphics X coordinate at the start of the line (i.e. at dist
+*        = 0.0).
+*     Map2_y0 = double (Read)
+*        The graphics Y coordinate at the start of the line (i.e. at dist
+*        = 0.0).
+*     Map2_deltax = double (Read)
+*        The increment along the graphics X axis between the start and
+*        end of the line.
+*     Map2_deltay = double (Read)
+*        The increment along the graphics Y axis between the start and
+*        end of the line.
+*     Map2_plot = AstPlot * (Read)
+*        A pointer to the Plot defining the mapping from graphics cordinates
+*        to physical coordinates.
+*     Map2_map = AstMapping * (Read)
+*        A pointer to the mapping from graphics cordinates to physical
+*        coordinates, extracted from the Plot.
+*     Map2_statics = Map2Statics * (Read and Write)
+*        Pointer to a structure holding other static data used by Map2.
+
+*  Notes:
+*     -  On the first call, this function allocates static resources which
+*     are used by subsequent invocation. These resources should be freed before
+*     calling this function with new values for any of the external variables,
+*     or when no longer needed, by calling this function with "n" supplied as
+*     zero.
+*     -  If an error has already occurred, this runction returns without
+*     action ,except that if "n" is supplied as zero then static resources
+*     are released even if an error has already occurred.
+
+*/
+/* Local Constants: */
+   Map2Statics *statics;           /* Pointer to structure holding static data */
+   int i, j;                       /* Loop counts */
+   double *p;                      /* Pointer to next physical value */
+   double *px;                     /* Pointer to next x graphics value */
+   double *py;                     /* Pointer to next y graphics value */
+
+/* Convert the global "void *" pointer to a Map2Statics pointer */
+   statics = (Map2Statics *) Map2_statics;
+
+/* If zero points were supplied, release static resources and return. */
+   if( n == 0 ){
+      if( statics ) {
+         if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+         if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+         Map2_statics = astFree( statics );
+      }
+      return;
+   }
+
+/* Otherwise, check the inherited global status. */
+   if( !astOK ) return;
+
+/* Create and initialise a structure to hold extra static information if
+   this has not already been done. */
+   if( !statics ) {
+      statics = astMalloc( sizeof( Map2Statics ) );
+      if( statics ) {
+         statics->pset1 = NULL;
+         statics->pset2 = NULL;
+         statics->ptr2 = NULL;
+         statics->ptr1[ 0 ] = NULL;
+         statics->ptr1[ 1 ] = NULL;
+         statics->nl = 0;
+         Map2_statics = statics;
+      }
+   }
+
+/* If the number of points to be mapped is different to last time,
+   set up some PointSets to store the specified number of points. */
+   if( n != statics->nl ){
+      statics->nl = n;
+
+/* Create a PointSet to hold the graphics coordinates corresponding to
+   the supplied offsets. The supplied arrays will be used to hold the
+   data for this PointSet, and so astGetPoints is not called. */
+      if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+      statics->pset1 = astPointSet( n, 2, "", status );
+
+/* Create a PointSet to hold the corresponding physical coordinates, and
+   get pointers to the associated axis values. */
+      if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+      statics->pset2 = astPointSet( n, Map2_ncoord, "", status );
+      statics->ptr2 = astGetPoints( statics->pset2 );
+    }
+
+
+/* Check the initialisation went OK (if done). */
+   if( astOK ){
+
+/* Store pointers to the results arrays in PointSet 1. */
+      statics->ptr1[ 0 ] = x;
+      statics->ptr1[ 1 ] = y;
+      astSetPoints( statics->pset1, statics->ptr1 );
+
+/* Loop round each offset along the curve, converting the normalised offset
+      in the range [0,1] to graphics coordinate and storing in PointSet 1. */
+      px = x;
+      py = y;
+      for( i = 0; i < n; i++){
+         *(px++) = Map2_x0 + Map2_deltax*dist[ i ];
+         *(py++) = Map2_y0 + Map2_deltay*dist[ i ];
+      }
+
+/* Map all the positions into physical coordinates. */
+      (void) Trans( Map2_plot, NULL, Map2_map, statics->pset1, 1, statics->pset2, 0, method, class, status );
+
+/* Check the physical coordinates for bad values, setting the corresponding
+   graphics coordinates bad. */
+      for( j = 0; j < Map2_ncoord; j++ ){
+         p = statics->ptr2[ j ];
+         px = x;
+         py = y;
+
+         for( i = 0; i < n; i++){
+            if( *(p++) == AST__BAD ){
+               *(px++) = AST__BAD;
+               *(py++) = AST__BAD;
+            } else {
+               px++;
+               py++;
+            }
+         }
+      }
+   }
+
+/* Return. */
+   return;
+
+}
+
+static void Map3( int n, double *dist, double *x, double *y,
+                  const char *method, const char *class,
+                  int *status GLOBALS_ARG ){
+/*
+*  Name:
+*     Map3
+
+*  Purpose:
+*     Find graphics coordinates at given distances along a geodesic curve
+*     between two physical coordinate positions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Map3( int n, double *dist, double *x, double *y,
+*                const char *method, const char *class,
+*                int *status [,AstGlobals *AST__GLOBALS] )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The supplied distances are converted into physical offsets along the
+*     geodesic curve joining the starting and finishing points given by
+*     externals Map3_origin and Map3_end. The physical coordinates at these
+*     offsets are found, and transformed into graphics coordinates.
+
+*  Parameters:
+*     n
+*        The number of points to map. Static resources are released but
+*        no points are mapped if zero is supplied.
+*     dist
+*        A pointer to an array holding "n" distances. A "dist" value of
+*        zero corresponds to the starting position supplied in external
+*        variable Map3_origin. A "dist" value of one corresponds to the
+*        finishing position given by Map3_end.
+*     x
+*        A pointer to an array in which to store the "n" graphics X
+*        coordinate values corresponding to the positions in "dist".
+*     y
+*        A pointer to an array in which to store the "n" graphics Y
+*        coordinate values corresponding to the positions in "dist".
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*     AST__GLOBALS
+*        Only present if compiled with -DTHREAD_SAFE. It is a pointer to
+*        the structure holding the global data for the executing thread.
+*        It is passed as a function parameter, rather than being accessed
+*        within this function using the astGET_GLOBALS(NULL) macro (as
+*        other Object-less functions do) in order to avoid the time
+*        overheads of calling astGET_GLOBALS(NULL) . This function is
+*        time-critical.
+
+*  External Variables:
+*     Map3_ncoord = int (Read)
+*        The number of axes in the physical coordinate system.
+*     Map3_origin = const double * (Read)
+*        A pointer to an array holding the physical coordinate value on
+*        each axis at the start of the curve (i.e. at dist = 0.0).
+*     Map3_end = const double * (Read)
+*        A pointer to an array holding the physical coordinate value on
+*        each axis at the end of the curve (i.e. at dist = 1.0).
+*     Map3_scale = double (Read)
+*        The scale factor to convert "dist" values into physical offsets
+*        along the geodesic curve.
+*     Map3_statics = Map3Statics * (Read and Write)
+*        Pointer to a structure holding other static data used by Map3.
+*     Map3_plot = AstPlot * (Read)
+*        A pointer to the Plot defining the mapping from graphics cordinates
+*        to physical coordinates.
+*     Map3_map = AstMapping * (Read)
+*        A pointer to the mapping from graphics cordinates to physical
+*        coordinates extracted from the Plot.
+*     Map3_frame = AstFrame * (Read)
+*        A pointer to the Current Frame in the Plot.
+
+*  Notes:
+*     -  On the first call, this function allocates static resources which
+*     are used by subsequent invocation. These resources should be freed before
+*     calling this function with new values for any of the external variables,
+*     or when no longer needed, by calling this function with "n" supplied as
+*     zero.
+*     -  If an error has already occurred, this runction returns without
+*     action ,except that if "n" is supplied as zero then static resources
+*     are released even if an error has already occurred.
+
+*/
+
+/* Local Constants: */
+   Map3Statics *statics;             /* Pointer to structure holding static data */
+   int i, j;                         /* Loop counts */
+
+/* Convert the global "void *" pointer to a Map3Statics pointer */
+   statics = (Map3Statics *) Map3_statics;
+
+/* If zero points were supplied, release static resources and return. */
+   if( n == 0 ){
+      if( statics ) {
+         if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+         if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+         if( statics->pos ) statics->pos = (double *) astFree( (void *) statics->pos );
+         Map3_statics = astFree( statics );
+      }
+      return;
+   }
+
+/* Otherwise, check the inherited global status. */
+   if( !astOK ) return;
+
+/* Create and initialise a structure to hold extra static information if
+   this has not already been done. */
+   if( !statics ) {
+      statics = astMalloc( sizeof( Map3Statics ) );
+      if( statics ) {
+         statics->pset1 = NULL;
+         statics->pset2 = NULL;
+         statics->ptr1 = NULL;
+         statics->ptr2[ 0 ] = NULL;
+         statics->ptr2[ 1 ] = NULL;
+         statics->nc = 0;
+         statics->nl = 0;
+         statics->pos = NULL;
+         Map3_statics = statics;
+      }
+   }
+
+/* If the number of points to be mapped is different to last time,
+   set up some PointSets to store the specified number of points. */
+   if( n != statics->nl ){
+      statics->nl = n;
+
+/* Create a PointSet to hold the physical coordinates corresponding to
+   the supplied offsets. First annul any existing PointSet. */
+      if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+      statics->pset1 = astPointSet( n, Map3_ncoord, "", status );
+      statics->ptr1 = astGetPoints( statics->pset1 );
+
+/* Create a PointSet to hold the corresponding graphics coordinates.
+   The supplied "x" and "y" arrays will be used to store the data
+   so we do not need to get pointers to the data using astGetPoints. First
+   annul any existing PointSet. */
+      if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+      statics->pset2 = astPointSet( n, 2, "", status );
+
+   }
+
+/* If the number of physical axes is different to last time,  allocate
+   memory to hold a single physical position. */
+   if( statics->nc != Map3_ncoord ){
+      statics->nc = Map3_ncoord;
+      statics->pos = (double *) astMalloc( sizeof(double)*(size_t)Map3_ncoord );
+   }
+
+/* Check the initialisation went OK (if done). */
+   if( astOK ){
+
+/* Loop round each offset along the curve, converting the normalised offset
+   in the range [0,1] to a physical offset, and then into a physical
+   position, and store in PointSet 1. */
+      for( i = 0; i < n; i++){
+         astOffset( Map3_frame, Map3_origin, Map3_end, Map3_scale*dist[ i ],
+                    statics->pos );
+
+         for( j = 0; j < Map3_ncoord; j++ ){
+            statics->ptr1[ j ][ i ] = statics->pos[ j ];
+         }
+
+      }
+
+/* Store pointers to the results arrays in PointSet 2. */
+      statics->ptr2[ 0 ] = x;
+      statics->ptr2[ 1 ] = y;
+      astSetPoints( statics->pset2, statics->ptr2 );
+
+/* Map all the positions into graphics coordinates. */
+      (void) Trans( Map3_plot, NULL, Map3_map, statics->pset1, 0, statics->pset2, 1, method, class, status );
+   }
+
+/* Return. */
+   return;
+
+}
+
+static void Map4( int n, double *dist, double *x, double *y,
+                  const char *method, const char *class,
+                  int *status GLOBALS_ARG ){
+/*
+*  Name:
+*     Map4
+
+*  Purpose:
+*     Find graphics coordinates at given distances along a user
+*     specified curve.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Map4( int n, double *dist, double *x, double *y,
+*                const char *method, const char *class,
+*                int *status [,AstGlobals *AST__GLOBALS] )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The supplied distances are converted into physical coordinates using
+*     the Mapping Map4_umap. These physical coordinates are transformed into
+*     graphics coordinates.
+
+*  Parameters:
+*     n
+*        The number of points to map. Static resources are released but
+*        no points are mapped if zero is supplied.
+*     dist
+*        A pointer to an array holding "n" distances. A "dist" value of
+*        zero corresponds to the starting position supplied in external
+*        variable Map3_origin. A "dist" value of one corresponds to the
+*        finishing position given by Map3_end.
+*     x
+*        A pointer to an array in which to store the "n" graphics X
+*        coordinate values corresponding to the positions in "dist".
+*     y
+*        A pointer to an array in which to store the "n" graphics Y
+*        coordinate values corresponding to the positions in "dist".
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*     AST__GLOBALS
+*        Only present if compiled with -DTHREAD_SAFE. It is a pointer to
+*        the structure holding the global data for the executing thread.
+*        It is passed as a function parameter, rather than being accessed
+*        within this function using the astGET_GLOBALS(NULL) macro (as
+*        other Object-less functions do) in order to avoid the time
+*        overheads of calling astGET_GLOBALS(NULL) . This function is
+*        time-critical.
+
+*  External Variables:
+*     Map4_ncoord = int (Read)
+*        The number of axes in the physical coordinate system.
+*     Map4_plot = AstPlot * (Read)
+*        A pointer to the Plot defining the mapping from graphics cordinates
+*        to physical coordinates.
+*     Map4_map = AstMapping * (Read)
+*        A pointer to the mapping from graphics cordinates to physical
+*        coordinates extracted from the Plot.
+*     Map4_statics = Map4Statics * (Read and Write)
+*        Pointer to a structure holding other static data used by Map4.
+*     Map4_umap = AstMapping * (Read)
+*        A pointer to the mapping from distance along the curve to physical
+*        coordinates.
+
+*  Notes:
+*     -  On the first call, this function allocates static resources which
+*     are used by subsequent invocation. These resources should be freed before
+*     calling this function with new values for any of the external variables,
+*     or when no longer needed, by calling this function with "n" supplied as
+*     zero.
+*     -  If an error has already occurred, this runction returns without
+*     action ,except that if "n" is supplied as zero then static resources
+*     are released even if an error has already occurred.
+
+*/
+
+/* Local Variables: */
+   Map4Statics *statics;             /* Pointer to structure holding static data */
+   double *ptr1[ 1 ];                /* Pointer to distances data */
+   double *ptr3[ 2 ];                /* Pointers to graphics coord data */
+
+/* Convert the global "void *" pointer to a Map4Statics pointer */
+   statics = (Map4Statics *) Map4_statics;
+
+/* If zero points were supplied, release static resources and return. */
+   if( n == 0 ){
+      if( statics ) {
+         if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+         if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+         if( statics->pset3 ) statics->pset3 = astAnnul( statics->pset3 );
+         Map4_statics = astFree( statics );
+      }
+      return;
+   }
+
+/* Otherwise, check the inherited global status. */
+   if( !astOK ) return;
+
+/* Create and initialise a structure to hold extra static information if
+   this has not already been done. */
+   if( !statics ) {
+      statics = astMalloc( sizeof( Map4Statics ) );
+      if( statics ) {
+         statics->pset1 = NULL;
+         statics->pset2 = NULL;
+         statics->pset3 = NULL;
+         statics->nl = 0;
+         Map4_statics = statics;
+      }
+   }
+
+/* If the number of points to be mapped is different to last time,
+   set up some PointSets to store the specified number of points. */
+   if( n != statics->nl ){
+      statics->nl = n;
+
+/* Create a PointSet to hold the distances along the curve. First annul any
+   existing PointSet. */
+      if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+      statics->pset1 = astPointSet( n, 1, "", status );
+
+/* Create a PointSet to hold the physical coordinates corresponding to
+   the supplied distances. First annul any existing PointSet. */
+      if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+      statics->pset2 = astPointSet( n, Map4_ncoord, "", status );
+
+/* Create a PointSet to hold the corresponding graphics coordinates.
+   First annul any existing PointSet. */
+      if( statics->pset3 ) statics->pset3 = astAnnul( statics->pset3 );
+      statics->pset3 = astPointSet( n, 2, "", status );
+
+   }
+
+/* Check the initialisation went OK (if done). */
+   if( astOK ){
+
+/* Use Map4_umap to convert the supplied distances into physical coords
+   (i.e. coords in the current Frame of the Plot). */
+      ptr1[ 0 ] = dist;
+      astSetPoints( statics->pset1, ptr1 );
+      (void) astTransform( Map4_umap, statics->pset1, 1, statics->pset2 );
+
+/* Store pointers to the results arrays in PointSet 2. */
+      ptr3[ 0 ] = x;
+      ptr3[ 1 ] = y;
+      astSetPoints( statics->pset3, ptr3 );
+
+/* Now transform these physical coords into graphical coords,
+   incorporating clipping. */
+      (void) Trans( Map4_plot, NULL, Map4_map, statics->pset2, 0, statics->pset3, 1, method, class, status );
+   }
+
+/* Return. */
+   return;
+
+}
+
+static void Map5( int n, double *dist, double *x, double *y,
+                  const char *method, const char *class,
+                  int *status GLOBALS_ARG ){
+/*
+*  Name:
+*     Map5
+
+*  Purpose:
+*     Find graphics coordinates at given distances along the boundary of
+*     a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Map5( int n, double *dist, double *x, double *y,
+*                const char *method, const char *class,
+*                int *status [,AstGlobals *AST__GLOBALS] )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The supplied distances are converted into physical coordinates
+*     using the Region specified by an external variable, and then
+*     these physical coordinates are mapped into graphics coordinates.
+
+*  Parameters:
+*     n
+*        The number of points to map. Static resources are released but
+*        no points are mapped if zero is supplied.
+*     dist
+*        A pointer to an array holding "n" distances. A "dist" value of
+*        zero corresponds to the starting position supplied in external
+*        variable Map1_origin. A "dist" value of one corresponds to the
+*        finishing position which is a distance Map1_length away from
+*        Map1_origin, moving in the positive direction of the axis given
+*        by Map1_axis. "dist" values can be either linearly or
+*        logarithmically related to axis values (see Map1_log).
+*     x
+*        A pointer to an array in which to store the "n" graphics X
+*        coordinate values corresponding to the positions in "dist".
+*     y
+*        A pointer to an array in which to store the "n" graphics Y
+*        coordinate values corresponding to the positions in "dist".
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*     AST__GLOBALS
+*        Only present if compiled with -DTHREAD_SAFE. It is a pointer to
+*        the structure holding the global data for the executing thread.
+*        It is passed as a function parameter, rather than being accessed
+*        within this function using the astGET_GLOBALS(NULL) macro (as
+*        other Object-less functions do) in order to avoid the time
+*        overheads of calling astGET_GLOBALS(NULL) . This function is
+*        time-critical.
+
+*  External Variables:
+*     Map1_log = int (Read)
+*        If zero, then "dist" in learly related to axis value. Otherwise
+*        it is linearly related to log10(axis value).
+*     Map1_ncoord = int (Read)
+*        The number of axes in the physical coordinate system.
+*     Map1_axis = int (Read)
+*        The zero-based index of the axis which the curve follows (i.e.
+*        the axis which changes value along the curve).
+*     Map1_statics = Map1Statics * (Read and Write)
+*        Pointer to a structure holding other static data used by Map1.
+*     Map1_origin = const double * (Read)
+*        A pointer to an array holding the physical coordinate value on
+*        each axis at the start of the curve (i.e. at dist = 0.0).
+*     Map1_length = double (Read)
+*        The scale factor to convert "dist" values into increments
+*        along the physical axis given by Map1_axis.
+*     Map1_plot = AstPlot * (Read)
+*        A pointer to the Plot defining the mapping from graphics cordinates
+*        to physical coordinates.
+*     Map1_map = AstMapping * (Read)
+*        A pointer to the mapping from graphics cordinates to physical
+*        coordinates extracted from the Plot.
+*     Map1_frame = AstFrame * (Read)
+*        A pointer to the Current Frame in the Plot.
+*     Map1_norm = int (Read)
+*        A flag indicating if physical coordinate values which are not in
+*        the normal ranges of the corresponding axes should be considered
+*        bad.
+
+*  Notes:
+*     -  On the first call, this function allocates static resources which
+*     are used by subsequent invocation. These resources should be freed before
+*     calling this function with new values for any of the external variables,
+*     or when no longer needed, by calling this function with "n" supplied as
+*     zero.
+*     -  If an error has already occurred, this runction returns without
+*     action ,except that if "n" is supplied as zero then static resources
+*     are released even if an error has already occurred.
+
+*/
+
+/* Local Constants: */
+   Map5Statics *statics;             /* Pointer to structure holding static data */
+
+/* Convert the global "void *" pointer to a Map5Statics pointer */
+   statics = (Map5Statics *) Map5_statics;
+
+/* If zero points were supplied, release static resources and return. */
+   if( n == 0 ){
+      if( statics ) {
+         if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+         if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+         Map5_statics = astFree( statics );
+      }
+      return;
+   }
+
+/* Otherwise, check the inherited global status. */
+   if( !astOK ) return;
+
+/* Create and initialise a structure to hold extra static information if
+   this has not already been done. */
+   if( !statics ) {
+      statics = astMalloc( sizeof( Map3Statics ) );
+      if( statics ) {
+         statics->pset1 = NULL;
+         statics->pset2 = NULL;
+         statics->ptr1 = NULL;
+         statics->ptr2[ 0 ] = NULL;
+         statics->ptr2[ 1 ] = NULL;
+         statics->nl = 0;
+         Map5_statics = statics;
+      }
+   }
+
+/* If the number of points to be mapped is different to last time,
+   set up some PointSets to store the specified number of points. */
+   if( n != statics->nl ){
+      statics->nl = n;
+
+/* Create a PointSet to hold the physical coordinates corresponding to
+   the supplied offsets. First annul any existing PointSet. */
+      if( statics->pset1 ) statics->pset1 = astAnnul( statics->pset1 );
+      statics->pset1 = astPointSet( n, Map5_ncoord, "", status );
+      statics->ptr1 = astGetPoints( statics->pset1 );
+
+/* Create a PointSet to hold the corresponding graphics coordinates.
+   The supplied "x" and "y" arrays will be used to store the data
+   so we do not need to get pointers to the data using astGetPoints. First
+   annul any existing PointSet. */
+      if( statics->pset2 ) statics->pset2 = astAnnul( statics->pset2 );
+      statics->pset2 = astPointSet( n, 2, "", status );
+   }
+
+/* Get the physical coords at the required positions along the Region
+   border. */
+   astRegTrace( Map5_region, n, dist, statics->ptr1 );
+
+/* Store pointers to the results arrays in PointSet 2. */
+   statics->ptr2[ 0 ] = x;
+   statics->ptr2[ 1 ] = y;
+   astSetPoints( statics->pset2, statics->ptr2 );
+
+/* Map all the positions into graphics coordinates. */
+   (void) Trans( Map5_plot, NULL, Map5_map, statics->pset1, 0,
+                 statics->pset2, 1, method, class, status );
+
+/* Return. */
+   return;
+}
+
+static void Mark( AstPlot *this, int nmark, int ncoord, int indim,
+                  const double *in, int type, int *status ){
+/*
+*++
+*  Name:
+c     astMark
+f     AST_MARK
+
+*  Purpose:
+*     Draw a set of markers for a Plot.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astMark( AstPlot *this, int nmark, int ncoord, int indim,
+c                   const double *in, int type )
+f     CALL AST_MARK( THIS, NMARK, NCOORD, INDIM, IN, TYPE, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+c     This function draws a set of markers (symbols) at positions
+f     This routine draws a set of markers (symbols) at positions
+*     specified in the physical coordinate system of a Plot. The
+*     positions are transformed into graphical coordinates to
+*     determine where the markers should appear within the plotting
+*     area.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+c     nmark
+f     NMARK = INTEGER (Given)
+*        The number of markers to draw. This may be zero, in which
+*        case nothing will be drawn.
+c     ncoord
+f     NCOORD = INTEGER (Given)
+*        The number of coordinates being supplied for each mark
+*        (i.e. the number of axes in the current Frame of the Plot, as
+*        given by its Naxes attribute).
+c     indim
+f     INDIM = INTEGER (Given)
+c        The number of elements along the second dimension of the "in"
+f        The number of elements along the first dimension of the IN
+*        array (which contains the marker coordinates). This value is
+*        required so that the coordinate values can be correctly
+*        located if they do not entirely fill this array. The value
+c        given should not be less than "nmark".
+f        given should not be less than NMARK.
+c     in
+f     IN( INDIM, NCOORD ) = DOUBLE PRECISION (Given)
+c        The address of the first element of a 2-dimensional array of
+c        shape "[ncoord][indim]" giving the
+c        physical coordinates of the points where markers are to be
+c        drawn. These should be stored such that the value of
+c        coordinate number "coord" for input mark number "mark" is
+c        found in element "in[coord][mark]".
+f        A 2-dimensional array giving the physical coordinates of the
+f        points where markers are to be drawn. These should be
+f        stored such that the value of coordinate number COORD for
+f        input mark number MARK is found in element IN(MARK,COORD).
+c     type
+f     TYPE = INTEGER (Given)
+*        A value specifying the type (e.g. shape) of marker to be
+*        drawn. The set of values which may be used (and the shapes
+*        that will result) is determined by the underlying graphics
+*        system.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Markers are not drawn at positions which have any coordinate
+*     equal to the value AST__BAD (or where the transformation into
+*     graphical coordinates yields coordinates containing the value
+*     AST__BAD).
+c     - If any marker position is clipped (see astClip), then the
+f     - If any marker position is clipped (see AST_CLIP), then the
+*     entire marker is not drawn.
+*     - An error results if the base Frame of the Plot is not 2-dimensional.
+*     - An error also results if the transformation between the
+*     current and base Frames of the Plot is not defined (i.e. the
+*     Plot's TranInverse attribute is zero).
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   AstMapping *mapping;    /* Pointer to graphics->physical mapping */
+   AstPointSet *pset1;     /* PointSet holding physical positions */
+   AstPointSet *pset2;     /* PointSet holding graphics positions */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   const double **ptr1;    /* Pointer to physical positions */
+   double **ptr2;          /* Pointer to graphics positions */
+   double *xpd;            /* Pointer to next double precision x value */
+   double *ypd;            /* Pointer to next double precision y value */
+   double xx;              /* X axis value */
+   double yy;              /* Y axis value */
+   float *x;               /* Pointer to single precision x values */
+   float *xpf;             /* Pointer to next single precision x value */
+   float *y;               /* Pointer to single precision y values */
+   float *ypf;             /* Pointer to next single precision y value */
+   int axis;               /* Axis index */
+   int clip;               /* Clips marks at plot boundary? */
+   int i;                  /* Loop count */
+   int naxes;              /* No. of axes in the base Frame */
+   int nn;                 /* Number of good marker positions */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Store the current method and class for inclusion in error messages
+   generated by lower level functions. */
+   method = "astMark";
+   class = astClass( this );
+
+/* Check the base Frame of the Plot is 2-D. */
+   naxes = astGetNin( this );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 2.", status, method, class, naxes, class );
+   }
+
+/* Also validate the input array dimension argument. */
+   if ( astOK && ( indim < nmark ) ) {
+      astError( AST__DIMIN, "%s(%s): The input array dimension value "
+                "(%d) is invalid.", status, method, class, indim );
+      astError( AST__DIMIN, "This should not be less than the number of "
+                "markers being drawn (%d).", status, nmark );
+   }
+
+/* Initialise the bounding box for primatives produced by this call. */
+   if( !Boxp_freeze ) {
+      Boxp_lbnd[ 0 ] = FLT_MAX;
+      Boxp_lbnd[ 1 ] = FLT_MAX;
+      Boxp_ubnd[ 0 ] = FLT_MIN;
+      Boxp_ubnd[ 1 ] = FLT_MIN;
+   }
+
+/* Indicate that the GRF module should re-calculate it's cached values
+   (in case the state of the graphics system has changed since the last
+   thing was drawn). */
+   RESET_GRF;
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+   astGrfAttrs( this, AST__MARKS_ID, 1, GRF__MARK, method, class );
+
+/* Create a PointSet to hold the supplied physical coordinates. */
+   pset1 = astPointSet( nmark, ncoord, "", status );
+
+/* Allocate memory to hold pointers to the first value on each axis. */
+   ptr1 = (const double **) astMalloc( sizeof( const double * )*
+                                       (size_t)( ncoord ));
+
+/* Check the pointer can be used, then store pointers to the first value
+   on each axis. */
+   if( astOK ){
+      for( axis = 0; axis < ncoord; axis++ ){
+         ptr1[ axis ] = in + axis*indim;
+      }
+   }
+
+/* Store these pointers in the PointSet. */
+   astSetPoints( pset1, (double **) ptr1 );
+
+/* Transform the supplied data from the current frame (i.e. physical
+   coordinates) to the base frame (i.e. graphics coordinates) using
+   the inverse Mapping defined by the Plot. */
+   mapping = astGetMapping( this, AST__BASE, AST__CURRENT );
+   pset2 = Trans( this, NULL, mapping, pset1, 0, NULL, 0, method, class, status );
+   mapping = astAnnul( mapping );
+
+/* Get pointers to the graphics coordinates. */
+   ptr2 = astGetPoints( pset2 );
+
+/* Allocate memory to hold single precision versions of the graphics
+   coordinates. */
+   x = (float *) astMalloc( sizeof( float )*(size_t) nmark );
+   y = (float *) astMalloc( sizeof( float )*(size_t) nmark );
+
+/* Check the pointers can be used. */
+   if( astOK ){
+
+/* Store pointers to the next single and double precision x and y
+   values. */
+      xpf = x;
+      ypf = y;
+      xpd = ptr2[ 0 ];
+      ypd = ptr2[ 1 ];
+
+/* Convert the double precision values to single precision, rejecting
+   any bad marker positions. If clipping is switched on, also clip any
+   markers with centres outside the plotting area. */
+      clip = astGetClip( this ) & 2;
+      nn = 0;
+      for( i = 0; i < nmark; i++ ){
+         if( *xpd != AST__BAD && *ypd != AST__BAD ){
+            xx = *(xpd++);
+            yy = *(ypd++);
+            if( !clip || ( xx >= this->xlo && xx <= this->xhi &&
+                           yy >= this->ylo && yy <= this->yhi ) ) {
+               nn++;
+               *(xpf++) = (float) xx;
+               *(ypf++) = (float) yy;
+            }
+         } else {
+            xpd++;
+            ypd++;
+         }
+      }
+
+/* Draw the remaining markers. */
+      GMark( this, nn, x, y, type, method, class, status );
+
+   }
+
+/* Free the memory used to store single precision graphics coordinates. */
+   x = (float *) astFree( (void *) x );
+   y = (float *) astFree( (void *) y );
+
+/* Annul the PointSets. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* Free the memory holding the pointers to the first value on each axis. */
+   ptr1 = (const double **) astFree( (void *) ptr1 );
+
+/* Re-establish the original graphical attributes. */
+   astGrfAttrs( this, AST__MARKS_ID, 0, GRF__MARK, method, class );
+
+/* Return */
+   return;
+}
+
+static void Mirror( AstPlot *this, int axis, int *status ){
+/*
+*+
+*  Name:
+*     astMirror
+
+*  Purpose:
+*     Flip a graphics axis of a Plot.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void astMirror( AstPlot *this, int axis )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function referses the direction of a specified graphics axis
+*     in the Plot.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot.
+*     axis
+*        The zero-based axis of the axis to mirror.
+
+*-
+*/
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+   if( axis == 0 ) {
+      this->xrev = ( this->xrev == 0 );
+
+   } else if( axis == 1 ){
+      this->yrev = ( this->yrev == 0 );
+
+   } else {
+      astError( AST__INTER, "astMirror(%s): Illegal axis index (%d) "
+                "supplied (internal AST programming error).", status,
+                astGetClass( this ), axis );
+   }
+}
+
+static void Norm1( AstMapping *map, int axis, int nv, double *vals,
+                   double refval, double width, int *status ){
+/*
+*  Name:
+*     Norm1
+
+*  Purpose:
+*     Use a Mapping to normalize an array of axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Norm1( AstMapping *map, int axis, int nv, double *vals,
+*                 double refval, double width, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The normalization of a position in physical space has two parts;
+*     firstly, the Mapping may determine a form of normalization;
+*     secondly, the Frame may provide an additional normalizion by the
+*     astNorm method. This function implements normalization using a
+*     Mapping, by transforming the physical position into Graphics position,
+*     and then back into a physical position. For instance, if the Mapping
+*     represents a mapping of Cartesian graphics axes onto a 2D polar
+*     coordinate system, a physical theta value of 3.PI will be normalized by
+*     the Mapping into a theta value of 1.PI (probably, but it depends on
+*     the Mapping). In this case, the Mapping normalization may well be the
+*     only normalization available, since the 2D polar coord. system will
+*     probably use a simple Frame to represent the (radius,theta) system,
+*     and a simple Frame defines no normalization (i.e. the astNorm method
+*     returns the supplied position unchanged).
+*
+*     Complications arise though because it is not possible to normalise
+*     a single axis value - you can only normalize a complete position.
+*     Therefore some value must be supplied for the other axis. We
+*     should use the LabelAt value, but we do not yet know what the LabelAt
+*     value will be. Instead, we try first using the supplied "refval"
+*     which should be close to the mode of the other aixs values. Usually
+*     the value used is not very important. However, for some complex
+*     projections (such as quad-cubes, TSC, etc) the choice can be more
+*     critical since some positions on the ksy correspond to undefined
+*     graphics positions (e.g the face edges in a TSC projection).
+*     Therefore, if the supplied refval results in any positions being
+*     undefined we refine the process by transforming the undefined
+*     positaons again using a different refval. We do this twice to bump
+*     up the likelihood of finding a suitable reference value.
+
+*  Parameters:
+*     mapping
+*        The Mapping from Graphics Frame to the current Frame.
+*     axis
+*        The index of the axis for which values are supplied in "vals".
+*     nv
+*        The number of values supplied in "vals".
+*     vals
+*        Pointer to an array of axis values. On exit they are normalized.
+*     refval
+*        The preffered constant value to use for the other axis when
+*        normalizing the values in "vals".
+*     width
+*        The range of used values for the other axis.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;        /* PointSet holding physical coords */
+   AstPointSet *pset2;        /* PointSet holding graphics coords */
+   double **ptr1;             /* Pointer to physical coords data */
+   double *a;                 /* Pointer to next axis value */
+   double *b;                 /* Pointer to next axis value */
+   int i;                     /* Loop count */
+   int itry;                  /* Loop count for re-try loop */
+   int nbad;                  /* No. of bad values found after transformation */
+   int *flags;                /* Pointer to flags array */
+
+/* Check the inherited global status. */
+   if( !astOK ) return;
+
+/* Store the supplied positions in a PointSet. */
+   pset1 = astPointSet( nv, 2, "", status );
+   ptr1 = astGetPoints( pset1 );
+   if( astOK ) {
+      a = ptr1[ axis ];
+      b = ptr1[ 1 - axis ];
+      for( i = 0; i < nv; i++){
+         *(a++) = vals[ i ];
+         *(b++) = refval;
+      }
+   }
+
+/* Transform the supplied positions into the Base Frame. */
+   pset2 = astTransform( map, pset1, 0, NULL );
+
+/* Transform the Base Frame positions back into the Current Frame. */
+   (void) astTransform( map, pset2, 1, pset1 );
+
+/* Allocate memory to hold a flag for each position which is non-zero if
+   we currently have a good axis value to return for the position. */
+   flags = (int *) astMalloc( sizeof(int)* (size_t) nv );
+
+/* If good, store these values back in the supplied array. If the
+   transformed values are bad, retain the original good values for the
+   moment in "vals", and also copy the good values back into pset1. So
+   at the end, pset1 will contain the original good values at any points
+   which produced bad values after the above transformation - the other
+   points in pset1 will be bad.  */
+   nbad = 0;
+   if( astOK ) {
+      a = ptr1[ axis ];
+      for( i = 0; i < nv; i++, a++ ){
+         if( *a != AST__BAD ) {
+            vals[ i ] = *a;
+            *a = AST__BAD;
+            flags[ i ] = 1;
+         } else if( vals[ i ] != AST__BAD ) {
+            nbad++;
+            *a = vals[ i ];
+            flags[ i ] = 0;
+         } else {
+            flags[ i ] = 1;
+         }
+      }
+   }
+
+/* We now try normalising any remaining bad positions using different
+   values for the other axis. This may result in some or all of the
+   remaining points being normalised succesfully. */
+   for( itry = 0; itry < 10; itry++ ) {
+
+/* If the above transformation produced any bad values, try again with a
+   different value on the other axis. */
+      if( astOK && nbad > 0 ) {
+         b = ptr1[ 1 - axis ];
+         for( i = 0; i < nv; i++){
+            *(b++) = refval + 0.1*( itry + 1 )*width;
+         }
+
+/* Transform to graphics coords and back to world coords. */
+         (void) astTransform( map, pset1, 0, pset2 );
+         (void) astTransform( map, pset2, 1, pset1 );
+
+/* Copy any good positions back into the returned vals array. Count
+   remaining bad positions. */
+         a = ptr1[ axis ];
+         nbad = 0;
+         for( i = 0; i < nv; i++, a++ ){
+            if( *a != AST__BAD ) {
+               vals[ i ] = *a;
+               flags[ i ] = 1;
+               *a = AST__BAD;
+            } else if( !flags[ i ] ) {
+               nbad++;
+               *a = vals[ i ];
+            }
+         }
+      }
+
+/* If the above transformation produced any bad values, try again with a
+   different value on the other axis. */
+      if( astOK && nbad > 0 ) {
+         b = ptr1[ 1 - axis ];
+         for( i = 0; i < nv; i++){
+            *(b++) = refval - 0.1*( itry + 1 )*width;
+         }
+
+/* Transform to graphics coords and back to world coords. */
+         (void) astTransform( map, pset1, 0, pset2 );
+         (void) astTransform( map, pset2, 1, pset1 );
+
+/* Copy any good positions back into the returned vals array. Count
+   remaining bad positions. */
+         a = ptr1[ axis ];
+         nbad = 0;
+         for( i = 0; i < nv; i++, a++ ){
+            if( *a != AST__BAD ) {
+               vals[ i ] = *a;
+               flags[ i ] = 1;
+               *a = AST__BAD;
+            } else if( !flags[ i ] ) {
+               nbad++;
+               *a = vals[ i ];
+            }
+         }
+      }
+   }
+
+/* Free resources. */
+   flags = (int *) astFree( flags );
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+}
+
+static void Opoly( AstPlot *this, int *status ){
+/*
+*  Name:
+*     Opoly
+
+*  Purpose:
+*     Draws the current poly line.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Opoly( AstPlot *this, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function draws the current poly line, and empties the buffer.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int ipoly;              /* Index of new polyline */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Draw the poly-line if needed. */
+   if( Poly_n > 0 ) {
+
+/* Extend the global arrays that hold pointers to the polylines already
+   drawn. */
+      ipoly = Poly_npoly++;
+      astBeginPM;
+      Poly_xp = astGrow( Poly_xp, Poly_npoly, sizeof(float*) );
+      Poly_yp = astGrow( Poly_yp, Poly_npoly, sizeof(float*) );
+      Poly_np = astGrow( Poly_np, Poly_npoly, sizeof(int) );
+      astEndPM;
+
+      if( astOK ) {
+
+/* Add pointers to the new polyline to the end of the above extended
+   arrays. */
+         Poly_xp[ ipoly ] = Poly_x;
+         Poly_yp[ ipoly ] = Poly_y;
+         Poly_np[ ipoly ] = Poly_n;
+
+/* Indicate that the current polyline is now empty. */
+         Poly_x = NULL;
+         Poly_y = NULL;
+         Poly_n = 0;
+      }
+   }
+}
+
+static int Overlap( AstPlot *this, int mode, int esc, const char *text, float x,
+                    float y, const char *just, float upx, float upy,
+                    float **work, const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     Overlap
+
+*  Purpose:
+*     See if a major tick value label would overlap any of the previously
+*     drawn labels.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Overlap( AstPlot *this, int mode, int esc, const char *text, float x,
+*                  float y, const char *just, float upx, float upy,
+*                  float **work, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     The operation of this function is determined by the "mode" parameter.
+
+*     A record is kept of the bounding boxes enclosing all the displayed
+*     labels. If the bounding box of the new label defined by the given
+*     parameter values would overlap any of the old bounding boxes, 0 is
+*     returned. Otherwise 1 is returned and the bounding box for the new
+*     label is added to the list of old bounding boxes.
+
+*     This function also updates the external variables Box_lbnd and
+*     Box_ubnd which hold the lower and upper bounds of the area enclosing
+*     all used labels.
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     mode
+*        - If -1, find the bounding box of the supplied label, add it
+*        to the list of stored bounding box, and return 1 if it overlaps
+*        any previously stored bounding boxes.
+*        - If -2, leave the bounding boxes unchanged and return the
+*        number of bounding boxes currently stored. No other action is taken
+*        and all other arguments are ignored.
+*        - Otherwise, reset the number of stored bounding boxes to the
+*        value of mode, and return the new number of bounding boxes. No
+*        action is taken if mode is less than zero or greater than the current
+*        number of stored boxes. No other action is taken and all other
+*        arguments are ignored.
+*     esc
+*        Should escape sequences in the text be interpreted?
+*     text
+*        A pointer to the label text string.
+*     x
+*        The graphics X coordinate of the label's reference point.
+*     y
+*        The graphics Y coordinate of the label's reference point.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y. The first character may be 'T' for "top",
+*        'C' for "centre", or 'B' for "bottom", and specifies the
+*        vertical location of the reference position. The second
+*        character may be 'L' for "left", 'C' for "centre", or 'R'
+*        for "right", and specifies the horizontal location of the
+*        reference position. If the string has less than 2 characters
+*        then 'C' is used for the missing characters.
+*     upx
+*        The x component of the up-vector for the text.
+*     upy
+*        The y component of the up-vector for the text.
+*     work
+*        A pointer to a place at which to store a pointer to an array of
+*        floats holding the old bounding boxes. Memory to hold this array
+*        is allocated automatically within this function. The pointer to
+*        the array should be supplied as NULL on the first call to this
+*        function, and the array should be freed using astFree when no
+*        longer needed.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     See parameter "mode."
+
+*  Notes:
+*     -  Zero is returned if an error has occurred, or if this function
+*     should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS     /* Pointer to thread-specific global data */
+   int nbox = 0;          /* Number of boxes stored in "work" */
+   int ret;               /* Does the new label overlap a previous label? */
+   int i;                 /* Box index */
+   float *cx;             /* Pointer to next corner's X value */
+   float *cy;             /* Pointer to next corner's Y value */
+   float xbn[ 4 ];        /* X coords at corners of new label's bounding box */
+   float ybn[ 4 ];        /* Y coords at corners of new label's bounding box */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Initialise the returned value to indicate no overlap has been found. */
+   ret = 0;
+
+/* Get the number of bounding boxes in the supplied work array. */
+   if( work && *work ) {
+      nbox = (*work)[ 0 ];
+   } else {
+      nbox = 0;
+   }
+
+/* If required, return the number of bounding boxes currently stored. */
+   if( mode == -2 ) return nbox;
+
+/* If required, reset the number of bounding boxes currently stored, and
+   return the new number. */
+   if( mode >= 0 ) {
+      if( mode < nbox && work && *work ) {
+         nbox = mode;
+         (*work)[ 0 ] = nbox;
+      }
+      return nbox;
+   }
+
+/* If no work array has been supplied, allocate one now with room for
+   10 boxes. Each box requires 8 floats, 2 for each of the 4 corners. The
+   X graphics coordinates at the 4 corners are stored in the first 4 floats,
+   and the corresponding Y graphics coordinates in the second group of 4
+   floats. */
+   if( work && !(*work) ) {
+      *work = (float *) astMalloc( 81*sizeof(float) );
+      if( astOK ) {
+         nbox = 0;
+         (*work)[ 0 ] = 0;
+      }
+   }
+
+/* Check the global status. */
+   if( !astOK ) return ret;
+
+/* Get the bounds of the box containing the new label. */
+   DrawText( this, 0, esc, text, x, y, just, upx, upy,
+             xbn, ybn, NULL, method, class, status );
+
+/* If the bounding box was obtained succesfully... */
+   if( astOK ) {
+
+/* Check for an overlap between the box and each of the previous boxes. */
+      cx = *work + 1;
+      cy = cx + 4;
+      for( i = 0; i < nbox; i++ ){
+
+         if( BoxCheck( xbn, ybn, cx, cy, status ) ) {
+            ret = 1;
+            break;
+         }
+
+/* Increment the pointers to the next box. */
+         cx += 8;
+         cy += 8;
+
+      }
+
+/* If no overlap was found, add the new box to the list. */
+      if( !ret ){
+         *work = (float *) astGrow( (void *) *work, 8*nbox + 9, sizeof(float) );
+         cx = *work + 1 + 8*nbox;
+         cy = cx + 4;
+         for( i = 0; i < 4; i++ ){
+            cx[ i ] = xbn[ i ];
+            cy[ i ] = ybn[ i ];
+         }
+         (*work)[ 0 ]++;
+
+/* Extend the bounds of the global bounding box held externally to include
+   the new box. */
+         for( i = 0; i < 4; i++ ){
+            Box_lbnd[ 0 ] = astMIN( xbn[ i ], Box_lbnd[ 0 ] );
+            Box_ubnd[ 0 ] = astMAX( xbn[ i ], Box_ubnd[ 0 ] );
+            Box_lbnd[ 1 ] = astMIN( ybn[ i ], Box_lbnd[ 1 ] );
+            Box_ubnd[ 1 ] = astMAX( ybn[ i ], Box_ubnd[ 1 ] );
+         }
+      }
+   }
+
+/* If an error has occur, return a value of 0. */
+   if( !astOK ) ret = 0;
+
+/* Return the answer. */
+   return ret;
+
+}
+
+static void PlotLabels( AstPlot *this, int esc, AstFrame *frame, int axis,
+                        LabelList *list, char *fmt, int nlab, float **box,
+                        const char *method, const char *class, int *status ) {
+/*
+*
+*  Name:
+*     PlotLabels
+
+*  Purpose:
+*     Draws the numerical labels which have been selected for display.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void PlotLabels( AstPlot *this, int esc, AstFrame *frame, int axis,
+*                      LabelList *list, char *fmt, int nlab, float **box,
+*                      const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function displays the numerical labels supplied in the
+*     structure pointed to by "list". Overlapping labels are omitted,
+*     and redundant leading fields are removed from adjacent labels.
+*     Nothing is plotted if the NumLab attribute for the axis is false.
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     esc
+*        Interpret escape sequences in labels?
+*     frame
+*        A pointer to the current Frame of the Plot.
+*     axis
+*        The axis index (0 or 1).
+*     list
+*        A pointer to the LabelList structure holding information about
+*        the selected numerical labels.
+*     fmt
+*        A pointer to a null terminated string holding the format
+*        specification used to create the labels.
+*     nlab
+*        The number of labels described by "list".
+*     box
+*        A pointer to a place at which to store a pointer to an array of
+*        floats holding the bounding boxes of displayed labels. Memory to
+*        hold this array is allocated automatically within this function.
+*        The pointer to the array should be supplied as NULL on the first
+*        call to this function, and the array should be freed using astFree
+*        when no longer needed.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   LabelList *ll;         /* Pointer to next label structure */
+   LabelList *llhi;       /* Pointer to higher neighbouring label structure */
+   LabelList *lllo;       /* Pointer to lower neighbouring label structure */
+   char *text;            /* Pointer to label text */
+   const char *latext;    /* Axis label at previous label */
+   const char *texthi;    /* Pointer to text abbreviated with higher neighbour */
+   const char *textlo;    /* Pointer to text abbreviated with lower neighbour */
+   float tolx;            /* Min allowed X interval between labels */
+   float toly;            /* Min allowed Y interval between labels */
+   float xbn[ 4 ];        /* X coords at corners of new label's bounding box */
+   float ybn[ 4 ];        /* Y coords at corners of new label's bounding box */
+   int abb;               /* Abbreviate leading fields? */
+   int dp;                /* Number of decimal places */
+   int found;             /* Non-zero digit found? */
+   int hilen;             /* Length of texthi */
+   int i;                 /* Label index */
+   int j;                 /* Label index offset */
+   int jgap;              /* Gap in index between rejected labels */
+   int lab0;              /* Index of middle label */
+   int lolen;             /* Length of textlo */
+   int mxdp;              /* Maximum number of decimal places */
+   int nbox;              /* The number of boinding boxes supplied */
+   int nexti;             /* Index of next label to retain */
+   int nleft;             /* No. of labels left */
+   int nz;                /* Number of trailing zeros in this label */
+   int nzmax;             /* Max. number of trailing zeros */
+   int odd;               /* DO we have a strange axis? */
+   int off;               /* Offset from central label */
+   int olap;              /* Any overlap found? */
+   int prio;              /* Current priority */
+   int root;              /* Index of unabbreviated label */
+   int root_found;        /* Has the root label been decided on? */
+   int rootoff;           /* Distance from middle to root label */
+   int split;             /* Indicates whether to split labels into 2 lines */
+
+/* Return without action if an error has occurred, or there are no labels to
+   draw. */
+   if( !astOK || nlab == 0 || !list || !astGetNumLab( this, axis ) ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   rootoff = 0;
+
+/* Get the number of bounding boxes describing the labels already drawn
+   (this will be non-zero only if this is the second axis to be labelled). */
+   nbox = Overlap( this, -2, 0, NULL, 0.0, 0.0, NULL, 0.0, 0.0, box, method,
+                   class, status );
+
+/* Ensure the labels are sorted into increasing index order. */
+   qsort( (void *) list, (size_t) nlab, sizeof(LabelList), Compare_LL );
+
+/* Complex curves can have multiple edge crossings very close together.
+   This means that the same label can sometimes be included more than once
+   in the supplied list at the same (x,y) position. Purge duplicate labels
+   by setting their priority to -1. Initialise the priority of the remaining
+   labels to zero. */
+   tolx = 0.02*fabs( this->xhi - this->xlo );
+   toly = 0.02*fabs( this->yhi - this->ylo );
+   ll = list;
+   ll->priority = 0;
+   ll->saved_prio = 0;
+
+   for( i = 1; i < nlab; i++ ) {
+      ll++;
+      ll->priority = 0;
+      ll->saved_prio = 0;
+      for( j = 0; j < i; j++ ){
+         if( !strcmp( ll->text, list[ j ].text ) ) {
+            if( fabs( ll->x - list[ j ].x ) < tolx &&
+                fabs( ll->y - list[ j ].y ) < toly ) {
+               ll->priority = -1;
+               ll->saved_prio = -1;
+               break;
+            }
+         }
+      }
+   }
+
+/* Find the maximum number of decimal places in any label. */
+   mxdp = 0;
+   ll = list - 1;
+   for( i = 0; i < nlab; i++ ) {
+      ll++;
+      FindDPTZ( frame, axis, fmt, ll->text, &dp, &nz, status );
+      if( dp > mxdp ) mxdp = dp;
+   }
+
+/* Indicate that we do not yet know whether SplitValue should split labels
+   into two lines or not. */
+   split = 0;
+
+/* Find the highest priority label (the "root" label). This label is
+   never abbreviated to remove leading fields, and is never omitted due to
+   overlaps with other labels. To find this label, each label is assigned a
+   priority equal to the number of trailing zeros in the label text. If the
+   text has fewer than the maximum number of decimal places, pretend the text
+   is padded with trailing zeros to bring the number of decimal places up to
+   the maximum. The root label is the highest priority label, giving
+   preference to labels which occur in the middle of the index order. At the
+   same time, initialize the abbreviated text for each label to be equal to
+   the unabbreviated text. */
+   lab0 = nlab/2;
+   nzmax = -1;
+   ll = list - 1;
+   root = -1;
+   root_found = 0;
+   for( i = 0; i < nlab; i++ ) {
+      ll++;
+      if( ll->priority > -1 ) {
+         text = ll->text;
+
+/* Find the number of decimal places and the number of trailing zeros in
+   this label. Note if a non-zero digit was found in the label. */
+         found = FindDPTZ( frame, axis, fmt, text, &dp, &nz, status );
+
+/* Add on some extra trailing zeros to make the number of decimal places
+   up to the maximum value. */
+         nz += mxdp - dp;
+
+/* Store the priority for this label. */
+         ll->priority = nz;
+         ll->saved_prio = nz;
+
+/* Note the highest priority of any label. */
+         if( nz > nzmax ) nzmax = nz;
+
+/* We will use this label as the root label if:
+
+   - We have not already found the root label
+
+   AND
+
+   - It does not overlap any labels drawn for a previous axis
+
+   AND
+
+   - We do not currently have a candidate root label, or
+   - The priority for this label is higher than the priority of the current
+   root label, or
+   - The priority for this label is equal to the priority of the current
+   root label and this label is closer to the centre of the axis, or
+   - The label value is zero.  */
+
+         if( root == -1 ||
+             nz > list[ root ].priority ||
+             ( nz == list[ root ].priority && abs( i - lab0 ) < rootoff ) ||
+             !found ) {
+
+            if( !root_found ) {
+
+               if( axis == 0 || !Overlap( this, -1, esc,
+                                          SplitValue( this, ll->text,
+                                                      axis, &split, status ),
+                                          (float) ll->x, (float) ll->y,
+                                          ll->just, (float) ll->upx,
+                                          (float) ll->upy, box, method,
+                                          class, status ) ) {
+                  root = i;
+                  rootoff = abs( i - lab0 );
+
+/* If the label value was zero, we will use label as the root label,
+   regardless of the priorities of later labels. */
+                  if( !found ) root_found = 1;
+               }
+
+/* Reset the list of bounding boxes to exclude any box added above. */
+               Overlap( this, nbox, esc, NULL, 0.0, 0.0, NULL, 0.0, 0.0, box,
+                        method, class, status );
+
+            }
+         }
+      }
+
+/* Initialise the abbreviated text to be the same as the full text. */
+      ll->atext = ll->text;
+   }
+
+/* If all the labels overlapped labels on a previous axis, arbitrarily
+   use the label with non-genative priority that is closest to the middle
+   as the root label (this should never happen but is included to avoid
+   segmentation violations occurring in error conditions such as the
+   txExt function being buggy and cuasing spurious overlaps). */
+   if( root == -1 ) {
+      for( off = 0; off < (nlab-1)/2; off++ ) {
+         root = nlab/2 + off;
+         if( list[ root ].priority >= 0 ) break;
+         root = nlab/2 - off;
+         if( list[ root ].priority >= 0 ) break;
+      }
+      if( root == -1 ) {
+         astError( AST__PLFMT, "%s(%s): Cannot produce labels for axis %d.",
+                   status, method, class, axis + 1 );
+         root = nlab/2;
+      }
+   }
+
+/* Assign a priority higher than any other priority to the root label. */
+   list[ root ].priority = nzmax + 1;
+   list[ root ].saved_prio = nzmax + 1;
+
+/* See if leading fields are to be abbreviated */
+   abb = astGetAbbrev( this, axis );
+
+/* The following process may have removed some labels which define the
+   missing fields in neighbouring abbreviated fields, so that the user
+   would not be able to tell what value the abbvreviated leading fields
+   should have. We therefore loop back and perform the abbreviation
+   process again, omitting the removed labels this time. Continue doing
+   this until no further labels are removed. */
+   jgap = 1;
+   olap = 1;
+   odd = 0;
+   while( olap && !odd ) {
+
+/* We now attempt to abbreviate the remaining labels (i.e. those which
+   have not been rejected on an earlier pass through this loop). Labels
+   are abbreviated in order of their priority. Higher priority labels are
+   abbreviated first (except that the root label, which has the highest
+   priority, is never abbreviated). Each label is abbreviated by comparing
+   it with the nearest label with a higher priority. */
+
+/* Loop through all the priority values, starting with the highest
+   priority (excluding the root label so that the root label is never
+   abbreviated), and working downwards to finish with zero priority. */
+      prio = nzmax + 1;
+      while( prio-- > 0 ) {
+
+/* Look for labels which have the current priority. */
+         ll = list - 1;
+         for( i = 0; i < nlab; i++ ) {
+            ll++;
+            if( ll->priority == prio ) {
+
+/* Find the closest label to this one on the high index side which has a
+   higher priority. */
+               llhi = NULL;
+               for( j = i + 1; j < nlab; j++ ) {
+                  if( list[ j ].priority > prio ) {
+                     llhi = list + j;
+                     break;
+                  }
+               }
+
+/* If no higher priority neighbour was found on the high index side,
+   use the nearest label with the current priority on the high index side. */
+               if( !llhi ) {
+                  for( j = i + 1; j < nlab; j++ ) {
+                     if( list[ j ].priority == prio ) {
+                        llhi = list + j;
+                        break;
+                     }
+                  }
+               }
+
+/* Find the closest label to this one on the low index side which has a
+   higher priority. */
+               lllo = NULL;
+               for( j = i - 1; j >= 0; j-- ) {
+                  if( list[ j ].priority > prio ) {
+                     lllo = list + j;
+                     break;
+                  }
+               }
+
+/* If no higher priority neighbour was found on the low index side,
+   use the nearest label with the current priority on the low index side. */
+               if( !lllo ) {
+                  for( j = i - 1; j >= 0; j-- ) {
+                     if( list[ j ].priority == prio ) {
+                        lllo = list + j;
+                        break;
+                     }
+                  }
+               }
+
+/* If we are not abbreviating, use the full text as the abbreviated text.*/
+               if( !abb ) {
+                  ll->atext = ll->text;
+
+/* Otherwise, if only one of these two neighbouring labels was found, we
+   abbreviate the current label by comparing it with the one found
+   neighbouring label. If they are identical, we use the last field as
+   the abbreviated text. */
+               } else if( !lllo ) {
+                  ll->atext = astAbbrev( frame, axis, fmt, llhi->text,
+                                         ll->text );
+
+               } else if( !llhi ) {
+                  ll->atext = astAbbrev( frame, axis, fmt, lllo->text,
+                                         ll->text );
+
+/* If two neighbouring labels were found, we abbreviate the current label
+   by comparing it with both neighbouring labels, and choosing the shorter
+   abbreviation. */
+               } else {
+                  textlo = abb ? astAbbrev( frame, axis, fmt, lllo->text,
+                                            ll->text ) : ll->text;
+                  texthi = abb ? astAbbrev( frame, axis, fmt, llhi->text,
+                                            ll->text ) : ll->text;
+
+                  lolen = strlen( textlo );
+                  hilen = strlen( texthi );
+                  if( lolen > 0 && lolen < hilen ) {
+                     ll->atext = textlo;
+                  } else {
+                     ll->atext = texthi;
+                  }
+               }
+
+/* If the two fields are identical, the abbreviated text returned by
+   astAbbrev will be a null string. In this case, find the start of the
+   last field in the formatted value (using astAbbrev again), and use
+   that as the abbreviated text. */
+               if( !(ll->atext)[0] ) {
+                  ll->atext = astAbbrev( frame, axis, fmt, NULL, ll->text );
+               }
+            }
+         }
+      }
+
+/* Find the bounding box of the root label and add it to the list of bounding
+   boxes. */
+      nleft = 1;
+      ll = list + root;
+      olap = Overlap( this, -1, esc,
+                      SplitValue( this, ll->atext, axis, &split, status ),
+                      (float) ll->x, (float) ll->y, ll->just, (float) ll->upx,
+                      (float) ll->upy, box, method, class, status );
+
+/* Now look for labels which would overlap. First, check labels above the root
+   label. Do not count overlaps where the two abbreviated labels have the same text. */
+      ll = list + root;
+      latext = ll->atext;
+      for( i = root + 1; i < nlab; i++ ) {
+         ll++;
+         if( ll->priority >= 0 ) {
+            if( strcmp( ll->atext, latext ) ) {
+               if( Overlap( this, -1, esc,
+                            SplitValue( this, ll->atext, axis, &split, status ),
+                            (float) ll->x, (float) ll->y, ll->just,
+                            (float) ll->upx, (float) ll->upy, box, method,
+                            class, status ) ){
+                  olap++;
+               } else {
+                  nleft++;
+               }
+            }
+            latext = ll->atext;
+         }
+      }
+
+/* Now check the labels below the root label. */
+      ll = list + root;
+      latext = ll->atext;
+      for( i = root - 1; i >= 0; i-- ) {
+         ll--;
+         if( ll->priority >= 0 ) {
+            if( strcmp( ll->atext, latext ) ) {
+               if( Overlap( this, -1, esc,
+                            SplitValue( this, ll->atext, axis, &split, status ),
+                            (float) ll->x, (float) ll->y, ll->just,
+                            (float) ll->upx, (float) ll->upy, box, method,
+                            class, status ) ){
+                  olap++;
+               } else {
+                  nleft++;
+               }
+            }
+            latext = ll->atext;
+         }
+      }
+
+/* If only one overlap was found, and this is the second axis, ignore it
+   since it is probably caused by the crossing of the two axes. */
+      if( axis == 1 && olap == 1 ) olap = 0;
+
+/* If we are now only plotting every 3rd label, or if there are less than
+   3 labels left, and there are still overlapping labels, then we must have
+   a very odd axis (such as logarithmically spaced ticks on a linearly mapped
+   axis). In this case, we will re-instate the orignal label priorities and
+   then leave this loop so that we attempt to plot all labels. Also retain
+   original priorities if the axis is mapped logarithmically onto the
+   screen. */
+      if( olap && ( jgap == 3 || nleft < 3 || astGetLogPlot( this, axis ) ) ){
+         jgap = 0;
+         odd = 1;
+      } else {
+         odd = 0;
+      }
+
+/* If any labels overlapped, re-instate the priority of all previously
+   excluded labels (using the copy of the label's real priority stored in
+   "saved_prio"), and then remove labels (by setting their priorities
+   negative) to increase the gap between labels, and try again. */
+      if( olap ) {
+         jgap++;
+
+         nexti = root + jgap;
+         for( i = root + 1; i < nlab; i++ ) {
+            if( i == nexti ) {
+               nexti += jgap;
+               list[ i ].priority = list[ i ].saved_prio;
+            } else {
+               list[ i ].priority = -1;
+            }
+         }
+
+         nexti = root - jgap;
+         for( i = root - 1; i >= 0; i-- ) {
+            if( i == nexti ) {
+               nexti -= jgap;
+               list[ i ].priority = list[ i ].saved_prio;
+            } else {
+               list[ i ].priority = -1;
+            }
+         }
+
+/* Reset the abbreviated text to be the full text. */
+         for( i = 0; i < nlab - 1; i++ ) list[ i ].atext = list[ i ].text;
+
+      }
+
+/* Rest the list of bounding boxes to exclude the boxes added above. */
+      Overlap( this, nbox, esc, NULL, 0.0, 0.0, NULL, 0.0, 0.0, box, method,
+               class, status );
+   }
+
+/* We can now draw the abbreviated labels (ignoring rejected labels). */
+   ll = list-1;
+   for( i = 0; i < nlab; i++ ) {
+      ll++;
+      if( ll->priority >= 0 ) {
+
+/* Check that this label does not overlap any labels drawn for previous
+   axes (we know from the above processing that it will not overlap any
+   other label on the current axis). */
+         if( !Overlap( this, -1, esc,
+                       SplitValue( this, ll->atext, axis, &split, status ),
+                       (float) ll->x, (float) ll->y, ll->just, (float) ll->upx,
+                       (float) ll->upy, box, method, class, status ) ) {
+
+/* Draw the abbreviated label text, and get the bounds of the box containing
+   the new label, splitting long formatted values (such as produced by
+   TimeFrames) into two lines. */
+            DrawText( this, 1, esc,
+                      SplitValue( this, ll->atext, axis, &split, status ),
+                      (float) ll->x, (float) ll->y, ll->just, (float) ll->upx,
+                      (float) ll->upy, xbn, ybn, NULL, method, class, status );
+         }
+      }
+   }
+}
+
+static void PolyCurve( AstPlot *this, int npoint, int ncoord, int indim,
+                       const double *in, int *status ){
+/*
+*++
+*  Name:
+c     astPolyCurve
+f     AST_POLYCURVE
+
+*  Purpose:
+*     Draw a series of connected geodesic curves.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astPolyCurve( AstPlot *this, int npoint, int ncoord, int indim,
+c                        const double *in )
+f     CALL AST_POLYCURVE( THIS, NPOINT, NCOORD, INDIM, IN, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+c     This function joins a series of points specified in the physical
+c     coordinate system of a Plot by drawing a sequence of geodesic
+c     curves.  It is equivalent to making repeated use of the astCurve
+c     function (q.v.), except that astPolyCurve will generally be more
+c     efficient when drawing many geodesic curves end-to-end. A
+c     typical application of this might be in drawing contour lines.
+f     This routine joins a series of points specified in the physical
+f     coordinate system of a Plot by drawing a sequence of geodesic
+f     curves.  It is equivalent to making repeated calls to the
+f     AST_CURVE routine (q.v.), except that AST_POLYCURVE will
+f     generally be more efficient when drawing many geodesic curves
+f     end-to-end. A typical application of this might be in drawing
+f     contour lines.
+*
+c     As with astCurve, full account is taken of the Mapping between
+c     physical and graphical coordinate systems. This includes any
+c     discontinuities and clipping established using astClip.
+f     As with AST_CURVE, full account is taken of the Mapping between
+f     physical and graphical coordinate systems. This includes any
+f     discontinuities and clipping established using AST_CLIP.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+c     npoint
+f     NPOINT = INTEGER (Given)
+*        The number of points between which geodesic curves are to be drawn.
+c     ncoord
+f     NCOORD = INTEGER (Given)
+*        The number of coordinates being supplied for each point (i.e.
+*        the number of axes in the current Frame of the Plot, as given
+*        by its Naxes attribute).
+c     indim
+f     INDIM = INTEGER (Given)
+c        The number of elements along the second dimension of the "in"
+f        The number of elements along the first dimension of the IN
+*        array (which contains the input coordinates). This value is
+*        required so that the coordinate values can be correctly
+*        located if they do not entirely fill this array. The value
+c        given should not be less than "npoint".
+f        given should not be less than NPOINT.
+c     in
+f     IN( INDIM, NCOORD ) = DOUBLE PRECISION (Given)
+c        The address of the first element in a 2-dimensional array of shape
+c        "[ncoord][indim]" giving the
+c        physical coordinates of the points which are to be joined in
+c        sequence by geodesic curves. These should be stored such that
+c        the value of coordinate number "coord" for point number
+c        "point" is found in element "in[coord][point]".
+f        A 2-dimensional array giving the physical coordinates of the
+f        points which are to be joined in sequence by geodesic
+f        curves. These should be stored such that the value of
+f        coordinate number COORD for input point number POINT is found
+f        in element IN(POINT,COORD).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - No curve is drawn on either side of any point which has any
+*     coordinate equal to the value AST__BAD.
+*     - An error results if the base Frame of the Plot is not
+*     2-dimensional.
+*     - An error also results if the transformation between the
+*     current and base Frames of the Plot is not defined (i.e. the
+*     Plot's TranInverse attribute is zero).
+*--
+*/
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   const double **in_ptr;  /* Pointer to array of data pointers */
+   double *finish;         /* Pointer to array holding segment end position */
+   double *start;          /* Pointer to array holding segment start position */
+   double d[ CRV_NPNT ];   /* Offsets to evenly spaced points along curve */
+   double tol;             /* Absolute tolerance value */
+   double x[ CRV_NPNT ];   /* X coords at evenly spaced points along curve */
+   double y[ CRV_NPNT ];   /* Y coords at evenly spaced points along curve */
+   int coord;              /* Loop counter for coordinates */
+   int i;                  /* Loop count */
+   int naxes;              /* No. of Frame axes */
+   int ok;                 /* Are all start and end coords good? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Store the current method, and the class of the supplied object for use
+   in error messages.*/
+   method = "astPolyCurve";
+   class = astGetClass( this );
+
+/* Check the base Frame of the Plot is 2-D. */
+   naxes = astGetNin( this );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 2.", status, method, class, naxes, class );
+   }
+
+/* Initialise the bounding box for primatives produced by this call. */
+   if( !Boxp_freeze ) {
+      Boxp_lbnd[ 0 ] = FLT_MAX;
+      Boxp_lbnd[ 1 ] = FLT_MAX;
+      Boxp_ubnd[ 0 ] = FLT_MIN;
+      Boxp_ubnd[ 1 ] = FLT_MIN;
+   }
+
+/* Check the current Frame of the Plot has ncoord axes. */
+   naxes = astGetNout( this );
+   if( naxes != ncoord && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the current "
+                "Frame of the supplied %s is invalid - this number should "
+                "be %d (possible programming error).", status, method, class, naxes,
+                class, ncoord );
+   }
+
+/* Check the array dimension argument. */
+   if ( astOK && ( indim < npoint ) ) {
+      astError( AST__DIMIN, "%s(%s): The array dimension value "
+                "(%d) is invalid.", status, method, class, indim );
+      astError( AST__DIMIN, "This should not be less than the number of "
+                "points being drawn (%d).", status, npoint );
+   }
+
+/* Indicate that the GRF module should re-calculate it's cached values
+   (in case the state of the graphics system has changed since the last
+   thing was drawn). */
+   RESET_GRF;
+
+/* Allocate memory to hold the array of data pointers, the start position,
+   and the end position. */
+   if ( astOK ) {
+      in_ptr = (const double **) astMalloc( sizeof( const double * ) *
+                                            (size_t) ncoord );
+      start = (double *) astMalloc( sizeof( double ) * (size_t) ncoord );
+      finish = (double *) astMalloc( sizeof( double ) * (size_t) ncoord );
+
+/* Set up externals used to communicate with the Map3 function...
+   The number of axes in the physical coordinate system (i.e. the current
+   Frame). */
+      Map3_ncoord = ncoord;
+
+/* A pointer to the Plot, the Current Frame, and and Mapping. */
+      Map3_plot = this;
+      Map3_frame = astGetFrame( this, AST__CURRENT );
+      Map3_map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Convert the tolerance from relative to absolute graphics coordinates. */
+      tol = astGetTol( this )*astMAX( this->xhi - this->xlo,
+                                      this->yhi - this->ylo );
+
+/* Ensure the globals holding the scaling from graphics coords to equally
+   scaled coords are available. */
+      GScales( this, NULL, NULL, method, class, status );
+
+/* Now set up the external variables used by the Crv and CrvLine function. */
+      Crv_scerr = ( astGetLogPlot( this, 0 ) ||
+                    astGetLogPlot( this, 1 ) ) ? 100.0 : 1.5;
+      Crv_tol = tol;
+      Crv_limit = 0.5*tol*tol;
+      Crv_map = Map3;
+      Crv_ink = 1;
+      Crv_xlo = this->xlo;
+      Crv_xhi = this->xhi;
+      Crv_ylo = this->ylo;
+      Crv_yhi = this->yhi;
+      Crv_clip = astGetClip( this ) & 1;
+
+/* Set up a list of points spread evenly over each curve segment. */
+      for( i = 0; i < CRV_NPNT; i++ ){
+        d[ i ] = ( (double) i)/( (double) CRV_NSEG );
+      }
+
+/* Initialise the data pointers to locate the coordinate data in
+   the "in" array. */
+      if ( astOK ) {
+         for ( coord = 0; coord < ncoord; coord++ ) {
+            in_ptr[ coord ] = in + coord * indim;
+         }
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+         astGrfAttrs( this, AST__CURVE_ID, 1, GRF__LINE, method, class );
+
+/* Loop round each curve segment. */
+         for( i = 1 ; i < npoint; i++ ) {
+
+/* Store the start position and check it for bad values. Increment the
+   pointers to the next position on each axis, so that they refer to the
+   finish point of the current curve segment. */
+            ok = 1;
+            for( coord = 0; coord < ncoord; coord++ ) {
+               if( *( in_ptr[coord] ) == AST__BAD ){
+                  ok = 0;
+               } else {
+                  start[ coord ] = *( in_ptr[coord] );
+               }
+               ( in_ptr[coord] )++;
+            }
+
+/* Store the end position and check it for bad values. Do not increment
+   the axis pointers. This means that they will refer to the start position
+   of the next curve segment on the next pass through this loop. */
+            for( coord = 0; coord < ncoord; coord++ ) {
+               if( *( in_ptr[coord] ) == AST__BAD ){
+                  ok = 0;
+               } else {
+                  finish[ coord ] = *( in_ptr[coord] );
+               }
+            }
+
+/* Pass on to the next curve segment if either the start or finish position
+   was bad. */
+            if( ok ) {
+
+/* Set up the remaining externals used to communicate with the Map3
+   function... */
+
+/* The physical coordinates at the start of the curve. */
+               Map3_origin = start;
+
+/* The physical coordinates at the end of the curve. */
+               Map3_end = finish;
+
+/* The scale factor to convert "dist" values into physical offset values. */
+               Map3_scale = astDistance( Map3_frame, start, finish );
+
+/* Now set up the remaining external variables used by the Crv and CrvLine
+   function. */
+               Crv_ux0 = AST__BAD;
+               Crv_out = 1;
+               Crv_xbrk = Curve_data.xbrk;
+               Crv_ybrk = Curve_data.ybrk;
+               Crv_vxbrk = Curve_data.vxbrk;
+               Crv_vybrk = Curve_data.vybrk;
+
+/* Map the evenly spread distances between "start" and "finish" into graphics
+   coordinates. */
+               Map3( CRV_NPNT, d, x, y, method, class, status GLOBALS_NAME );
+
+/* Use Crv and Map3 to draw the curve segment. */
+               Crv( this, d, x, y, 0, NULL, NULL, method, class, status );
+
+/* If no part of the curve could be drawn, set the number of breaks and the
+   length of the drawn curve to zero. */
+               if( Crv_out ) {
+                  Crv_nbrk = 0;
+                  Crv_len = 0.0F;
+
+/* Otherwise, add an extra break to the returned structure at the position of
+   the last point to be plotted. */
+               } else {
+                  Crv_nbrk++;
+                  if( Crv_nbrk > AST__PLOT_CRV_MXBRK ){
+                     astError( AST__CVBRK, "%s(%s): Number of breaks in curve "
+                               "exceeds %d.", status, method, class, AST__PLOT_CRV_MXBRK );
+                  } else {
+                     *(Crv_xbrk++) = (float) Crv_xl;
+                     *(Crv_ybrk++) = (float) Crv_yl;
+                     *(Crv_vxbrk++) = (float) -Crv_vxl;
+                     *(Crv_vybrk++) = (float) -Crv_vyl;
+                  }
+               }
+
+/* Store extra information about the curve in the returned structure, and
+   purge any zero length sections. */
+               Curve_data.length = Crv_len;
+               Curve_data.out = Crv_out;
+               Curve_data.nbrk = Crv_nbrk;
+               PurgeCdata( &Curve_data, status );
+            }
+         }
+
+/* End the last poly line. */
+         Opoly( this, status );
+
+/* Tidy up the static data used by Map3. */
+         Map3( 0, NULL, NULL, NULL, method, class, status GLOBALS_NAME );
+
+/* Ensure all lines are flushed to the graphics system. */
+         Fpoly( this, method, class, status );
+
+/* Re-establish the original graphical attributes. */
+         astGrfAttrs( this, AST__CURVE_ID, 0, GRF__LINE, method, class );
+      }
+
+/* Annul the Frame and Mapping. */
+      Map3_frame = astAnnul( Map3_frame );
+      Map3_map = astAnnul( Map3_map );
+
+/* Free the memory used for the data pointers, and start and end positions. */
+      in_ptr = (const double **) astFree( (void *) in_ptr );
+      start = (double *) astFree( (void *) start );
+      finish = (double *) astFree( (void *) finish );
+   }
+}
+
+static int PopGat( AstPlot *this, float *rise, const char *method,
+                   const char *class, int *status ) {
+/*
+*  Name:
+*     PopGat
+
+*  Purpose:
+*     Pop current graphical attributes for text from a stack.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int PopGat( AstPlot *this, float *rise, const char *method,
+*                 const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function restores the current graphical attributes for text
+*     from the values on a stack. Current attributes are left unchanged if
+*     the stack is empty.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     rise
+*        Pointer to a location at which to return thhe height of the baseline
+*        above the normal baseline, expressed as a percentage of the normal
+*        character height.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Returns zero if the stack is empty, and 1 otherwise.
+
+*/
+
+/* Local Variables: */
+   AstGat *gat;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Check there is at least one AstGat structure on the stack. */
+   if( this->ngat ) {
+
+/* Decrement the number of entries in the stack, and get a pointer to the
+   AstGat structure. Nullify the pointer on the stack. */
+      gat = (this->gat)[ --(this->ngat) ];
+      (this->gat)[ this->ngat ] = NULL;
+
+/* Restore the values in the AstGat structure */
+      *rise = gat->rise;
+      GAttr( this, GRF__SIZE, gat->size, NULL, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__WIDTH, gat->width, NULL, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__COLOUR, gat->col, NULL, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__FONT, gat->font, NULL, GRF__TEXT, method, class, status );
+      GAttr( this, GRF__STYLE, gat->style, NULL, GRF__TEXT, method, class, status );
+
+/* Free the AstGat structure. */
+      gat = astFree( gat );
+
+/* Indicate success.*/
+      result = 1;
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static void PurgeCdata( AstPlotCurveData *cdata, int *status ){
+/*
+*
+*  Name:
+*     AstPlotCurveData
+
+*  Purpose:
+*     Remove any zero length sections from the description of a curve.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void PurgeCdata( AstPlotCurveData *cdata )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function removes any zero length sections from the supplied
+*     AstPlotCurveData struture, which describes a multi-section curve.
+
+*  Parameters:
+*     cdata
+*        A pointer to the structure containing information about the
+*        breaks in a curve.
+
+*/
+
+/* Local Variables: */
+   int brk;                       /*Break index */
+   int i;                       /*Break index */
+
+/* Check the global error status. */
+   if ( !astOK || !cdata ) return;
+
+/* Loop round all breaks. */
+   brk = 0;
+   while( brk < cdata->nbrk ) {
+
+/* If this break and the next one are co-incident, remove both breaks. */
+      if( cdata->xbrk[ brk ] == cdata->xbrk[ brk + 1 ] &&
+          cdata->ybrk[ brk ] == cdata->ybrk[ brk + 1 ] ) {
+
+/* Shuffle down the higher elements of all the arrays in the curve data. */
+         for( i = brk + 2; i < cdata->nbrk; i++ ){
+            cdata->xbrk[ i - 2 ] = cdata->xbrk[ i ];
+            cdata->ybrk[ i - 2 ] = cdata->ybrk[ i ];
+            cdata->vxbrk[ i - 2 ] = cdata->vxbrk[ i ];
+            cdata->vybrk[ i - 2 ] = cdata->vybrk[ i ];
+         }
+
+/*  Decrement the number of breaks in the curve data. */
+         cdata->nbrk -= 2;
+
+/* If the section is not zero length, move on to the next pair of breaks. */
+      } else {
+         brk += 2;
+      }
+   }
+}
+
+static void PushGat( AstPlot *this, float rise, const char *method,
+                     const char *class, int *status ) {
+/*
+*  Name:
+*     PushGat
+
+*  Purpose:
+*     Push current graphical attributes for text onto a stack.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void PushGat( AstPlot *this, float rise, const char *method,
+*                   const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function stores the current graphical attributes for text
+*     on a stack.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     rise
+*        The height of the baseline above the normal baseline, expressed
+*        as a percentage of the normal character height.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstGat *new_gat;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Allocate memory for a new AstGat structure to store the graphical
+   attributes. */
+   new_gat = astMalloc( sizeof( AstGat ) );
+   if( astOK ) {
+
+/* Store the height of the current baseline above the normal baseline,
+   expressed as a percentage of a normal character height. */
+      new_gat->rise = rise;
+
+/* Store the current graphical attribute values. */
+      GAttr( this, GRF__SIZE, AST__BAD, &(new_gat->size), GRF__TEXT, method, class, status );
+      GAttr( this, GRF__WIDTH, AST__BAD, &(new_gat->width), GRF__TEXT, method, class, status );
+      GAttr( this, GRF__FONT, AST__BAD, &(new_gat->font), GRF__TEXT, method, class, status );
+      GAttr( this, GRF__STYLE, AST__BAD, &(new_gat->style), GRF__TEXT, method, class, status );
+      GAttr( this, GRF__COLOUR, AST__BAD, &(new_gat->col), GRF__TEXT, method, class, status );
+
+/* Increment the number of AstGat structures on the stack.*/
+      this->ngat++;
+
+/* Extend the array of AstGat pointers in the Plot structure so that there
+   is room for the new one. */
+      this->gat = (AstGat **) astGrow( this->gat, this->ngat, sizeof( AstGat * ) );
+      if( astOK ) {
+
+/* Store the new pointer. */
+         (this->gat)[ this->ngat - 1 ] = new_gat;
+
+      }
+   }
+}
+
+static void RegionOutline( AstPlot *this, AstRegion *region, int *status ){
+/*
+*++
+*  Name:
+c     astRegionOutline
+f     AST_RegionOutline
+
+*  Purpose:
+*     Draw the outline of an AST Region.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astRegionOutline( AstPlot *this, AstRegion *region )
+f     CALL AST_REGIONOUTLINE( THIS, REGION, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function draws an outline around the supplied AST Region object.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+c     region
+f     REGION = INTEGER (Given)
+*        Pointer to the Region.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+/* Local Variables: */
+   AstFrameSet *fs;
+   AstMapping *map;
+   const char *class;
+   const char *method;
+   int ibase;
+   int icurr;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store the current method, and the class of the supplied object for use
+   in error messages.*/
+   method = "astRegionOutline";
+   class = astGetClass( this );
+
+/* Save the base Frame index within the Plot, since astConvert will
+   change it. */
+   ibase = astGetBase( this );
+
+/* Get the FrameSet that converts from the current Frame of the Plot, to the
+   Frame represented by the Region. Check a conversion was found. */
+   fs = astConvert( this, region, " " );
+
+/* Re-instate the original base Frame. */
+   astSetBase( this, ibase );
+
+/* Report an error if the Region could not be mapped into the current
+   Frame of the Plot. */
+   if( !fs ) {
+      if( astOK ) {
+         astError( AST__NOCNV, "%s(%s): Cannot find a mapping from the "
+                   "%d-dimensional Plot coordinate system (%s) to the "
+                   "%d-dimensional Region coordinate system (%s).", status,
+                   method, class, astGetNout( this ), astGetTitle( this ),
+                   astGetNout( region ), astGetTitle( region ) );
+      }
+
+/* If a transformation from Plot to Region was found... */
+   } else {
+
+/* Add the Region as a new Frame into the Plot, connecting it to the
+   current Frame using the FrameSet found above. It becomes the new current
+   Frame. First record the index of the original current Frame since
+   astAddFrame will modify the FrameSet to use a different current Frame. */
+      icurr = astGetCurrent( this );
+      map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+      astAddFrame( this, icurr, map, region );
+
+/* Draw the outline of the Region (now the current Frame in the Plot). */
+      astBorder( this );
+
+/* Tidy up by removing the Region (i.e. the current Frame) from the Plot and
+   re-instating the original current Frame. */
+      astRemoveFrame( this, AST__CURRENT );
+      astSetCurrent( this, icurr );
+
+/* Free resources. */
+      map = astAnnul( map );
+      fs = astAnnul( fs );
+   }
+}
+
+static void RemoveFrame( AstFrameSet *this_fset, int iframe, int *status ) {
+/*
+*  Name:
+*     RemoveFrame
+
+*  Purpose:
+*     Remove a Frame from a Plot.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void RemoveFrame( AstFrameSet *this_fset, int iframe, int *status )
+
+*  Class Membership:
+*     Plot member function (over-rides the astRemoveFrame public
+*     method inherited from the FrameSet class).
+
+*  Description:
+*     This function removes a Frame from a Plot. All other Frames
+*     in the Plot have their indices re-numbered from one (if
+*     necessary), but are otherwise unchanged.
+*
+*     If the index of the clipping Frame is changed, the index value
+*     stored in the Plot is updated. If the clipping Frame itself is
+*     removed, all clipping information is removed from the Plot.
+
+*  Parameters:
+*     this_fset
+*        Pointer to the FrameSet component of the Plot.
+*     iframe
+*        The index within the Plot of the Frame to be removed.
+*        This value should lie in the range from 1 to the number of
+*        Frames in the Plot (as given by its Nframe attribute).
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPlot *this;          /* Pointer to Plot structure */
+   int ifrm;               /* Validated frame index */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Plot structure. */
+   this = (AstPlot *) this_fset;
+
+/* Validate the frame index. */
+   ifrm = astValidateFrameIndex( this_fset, iframe, "astRemoveFrame" );
+
+/* Invoke the parent astRemoveFrame method to remove the Frame. */
+   (*parent_removeframe)( this_fset, iframe, status );
+
+/* If the index of the removed Frame is smaller than the clipping Frame
+   index, then decrement the clipping Frame index so that the same Frame
+   will be used in future. */
+   if( astOK ){
+      if( ifrm < this->clip_frame ){
+         (this->clip_frame)--;
+
+/* If the clipping fgrame itself has been removed, indicate that no
+   clipping should nbow be performed. */
+      } else if( ifrm == this->clip_frame ){
+         astClip( this, AST__NOFRAME, NULL, NULL );
+      }
+   }
+}
+
+static void RightVector( AstPlot *this, float *ux, float *uy, float *rx,
+                         float *ry, const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     RightVector
+
+*  Purpose:
+*     Return a vector in the direction of the base line of normal text.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void RightVector( AstPlot *this, float *ux, float *uy, float *rx,
+*                       float *ry, const char *method, const char *class, int *status )
+
+*  Description:
+*     This function returns a vector which points from left to right along
+*     the text baseline, taking account of any difference in the scales of
+*     the x and y axes. It also scales the supplied up vector so that it has
+*     a length equal to the height of normal text, and scales the returned
+*     right vector to have the same length (on the screen) as the up vector.
+
+*  Parameters:
+*     this
+*        The plot.
+*     ux
+*        Pointer to a float holding the x component of the up-vector for the
+*        text, in graphics coords. Scaled on exit so that the up vector has
+*        length equal to the height of normal text.
+*     uy
+*        Pointer to a float holding the y component of the up-vector for the
+*        text, in graphics coords. Scaled on exit so that the up vector has
+*        length equal to the height of normal text.
+*     rx
+*        Pointer to a double in which will be put the x component of a
+*        vector parallel to the baseline of normal text.
+*     ry
+*        Pointer to a double in which will be put the y component of a
+*        vector parallel to the baseline of normal text.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+
+/* Local Variables: */
+   float alpha;         /* Scale factor for X axis */
+   float beta;          /* Scale factor for Y axis */
+   float chv;
+   float chh;
+   float l;             /* Normalisation constant */
+   float a;
+   float b;
+   float nl;		/* Character height in standard coordinates */
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Find the scale factors for the two axes which scale graphics coordinates
+   into a "standard" coordinate system in which: 1) the axes have equal scale
+   in terms of (for instance) millimetres per unit distance, 2) X values
+   increase from left to right, 3) Y values increase from bottom to top. */
+   GScales( this, &alpha, &beta, method, class, status );
+
+/* Convert the up-vector into "standard" system in which the axes have
+   equal scales, and increase left-to-right, bottom-to-top. */
+   *ux *= alpha;
+   *uy *= beta;
+
+/* Normalise this up-vector. */
+   l = sqrt( (*ux)*(*ux) + (*uy)*(*uy) );
+   if( l <= 0.0 ) {
+     *ux = 0.0;
+     *uy = 1.0;
+   } else {
+     *ux /= l;
+     *uy /= l;
+   }
+
+/* Find the height in "standard" coordinates of "normal" text draw with the
+   requested up-vector. */
+   GQch( this, &chv, &chh, method, class, status );
+   a = (*ux)/(chv*alpha);
+   b = (*uy)/(chh*beta);
+   nl = 1.0/sqrt( a*a + b*b );
+
+/* Scale the up-vector so that is has length equal to the height of "normal"
+   text with the specified up-vector. */
+   *ux *= nl;
+   *uy *= nl;
+
+/* Get the vector along the base-line of the text, by rotating the
+   up-vector by 90 degrees clockwise. */
+   *rx = *uy;
+   *ry = -*ux;
+
+/* Convert both vectors back from standard coords to normal world coords */
+   *ux /= alpha;
+   *uy /= beta;
+   *rx /= alpha;
+   *ry /= beta;
+
+}
+
+static void SaveTick( AstPlot *this, int axis, double gx, double gy,
+                      int major, int *status ){
+/*
+*  Name:
+*     SaveTick
+
+*  Purpose:
+*     Save info about a drawn tick in the Plot structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void SaveTick( AstPlot *this, int axis, double gx, double gy,
+*                    int major, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function stores the start position and type of each drawn
+*     tick in the given Plot.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     axis
+*        The zero-based axis index to which the tick refers. If a
+*        negative value is specified then all information about drawn ticks
+*        curently stored in the Plot is erased.
+*     gx
+*        The graphics X position at the start of the tick mark.
+*     gy
+*        The graphics Y position at the start of the tick mark.
+*     major
+*        Non-zero if the tick mark is a major tick.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int i;
+   int *count;
+   double *tickgx;
+   double *tickgy;
+
+/* Free the tick info in the supplied Plot if required. Do this before
+   checking the error status. */
+   if( axis < 0 ) {
+      for( i = 0; i < 3; i++ ) {
+         this->majtickgx[ i ] = astFree( this->majtickgx[ i ] );
+         this->majtickgy[ i ] = astFree( this->majtickgy[ i ] );
+         this->mintickgx[ i ] = astFree( this->mintickgx[ i ] );
+         this->mintickgy[ i ] = astFree( this->mintickgy[ i ] );
+         this->mintickcount[ i ] = 0;
+         this->majtickcount[ i ] = 0;
+      }
+      return;
+   }
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get pointers to the arrays to use, and ensure the arrays are big
+   enough to hold the new tick. */
+   if( major ) {
+      count = this->majtickcount + axis;
+      tickgx = this->majtickgx[ axis ];
+      tickgy = this->majtickgy[ axis ];
+   } else {
+      count = this->mintickcount + axis;
+      tickgx = this->mintickgx[ axis ];
+      tickgy = this->mintickgy[ axis ];
+   }
+
+/* Ensure the arrays are big enough to hold the new tick. */
+   i = *count;
+   tickgx = astGrow( tickgx, i + 1, sizeof( double ) );
+   tickgy = astGrow( tickgy, i + 1, sizeof( double ) );
+
+/* If memory was allocated succesfully, store the new tick information in
+   the expanded arrays. */
+   if( astOK ) {
+      tickgx[ i ] = gx;
+      tickgy[ i ] = gy;
+      *count = i + 1;
+
+/* Store the potentially updated array pointers. */
+      if( major ) {
+         this->majtickgx[ axis ] = tickgx;
+         this->majtickgy[ axis ] = tickgy;
+      } else {
+         this->mintickgx[ axis ] = tickgx;
+         this->mintickgy[ axis ] = tickgy;
+      }
+   }
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Plot.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     Plot member function (over-rides the astSetAttrib protected
+*     method inherited from the FrameSet class).
+
+*  Description:
+*     This function assigns an attribute value for a Plot, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPlot *this;                /* Pointer to the Plot structure */
+   char label[21];               /* Graphics item label */
+   const char *class;            /* Pointer to class string */
+   double dval;                  /* Double attribute value */
+   int axis;                     /* Index for the axis */
+   int edge;                     /* Index of edge within list */
+   int id1;                      /* Plot object id */
+   int id2;                      /* Plot object id */
+   int id;                       /* Plot object id */
+   int ival;                     /* Int attribute value */
+   int len;                      /* Length of setting string */
+   int nax;                      /* Number of graphics frame axes */
+   int nc;                       /* Number of characters read by astSscanf */
+   int id3;                      /* Third genuine identifier */
+   int nid;                      /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Plot structure. */
+   this = (AstPlot *) this_object;
+
+/* Get the number of base Frame axis (2 for a Plot, 3 for a Plot3D). */
+   nax = astGetNin( this );
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* Tol. */
+/* ---- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "tol= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetTol( this, dval );
+
+/* Grid. */
+/* ----- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "grid= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetGrid( this, ival );
+
+/* TickAll. */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "tickall= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetTickAll( this, ival );
+
+/* ForceExterior */
+/* ------------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "forceexterior= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetForceExterior( this, ival );
+
+/* Invisible. */
+/* ---------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "invisible= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetInvisible( this, ival );
+
+/* Border. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "border= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetBorder( this, ival );
+
+/* ClipOp. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "clipop= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetClipOp( this, ival );
+
+/* Clip. */
+/* ----- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "clip= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetClip( this, ival );
+
+/* Grf. */
+/* ---- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "grf= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetGrf( this, ival );
+
+/* DrawTitle. */
+/* ---------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "drawtitle= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetDrawTitle( this, ival );
+
+/* DrawAxes. */
+/* --------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "drawaxes= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetDrawAxes( this, axis, ival );
+
+/* DrawAxes(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "drawaxes(%d)= %d %n",
+                              &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetDrawAxes( this, axis - 1, ival );
+
+/* Abbrev. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "abbrev= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetAbbrev( this, axis, ival );
+
+/* Abbrev(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "abbrev(%d)= %d %n",
+                              &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetAbbrev( this, axis - 1, ival );
+
+/* Escape. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "escape= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetEscape( this, ival );
+
+/* Edge(axis). */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "edge(%d)= %n%*s %n", &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      edge = FullForm( "left right top bottom", setting + ival, setting,
+                       "astSet", astGetClass( this ), status );
+      if( edge == 0 ) {
+         astSetEdge( this, axis - 1, LEFT );
+      } else if( edge == 1 ) {
+         astSetEdge( this, axis - 1, RIGHT );
+      } else if( edge == 2 ) {
+         astSetEdge( this, axis - 1, TOP );
+      } else if( edge == 3 ) {
+         astSetEdge( this, axis - 1, BOTTOM );
+      }
+
+/* LabelAt (axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "labelat(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetLabelAt( this, axis - 1, dval );
+
+/* Centre(axis). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "centre(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetCentre( this, axis - 1, dval );
+
+/* Gap. */
+/* ---- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "gap= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetGap( this, axis, dval );
+
+/* Gap(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "gap(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetGap( this, axis - 1, dval );
+
+/* LogGap. */
+/* ---- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "loggap= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetLogGap( this, axis, dval );
+
+/* LogGap(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "loggap(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetLogGap( this, axis - 1, dval );
+
+/* NumLabGap. */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "numlabgap= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetNumLabGap( this, axis, dval );
+
+/* NumLabGap(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "numlabgap(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetNumLabGap( this, axis - 1, dval );
+
+/* TextLabGap. */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "textlabgap= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetTextLabGap( this, axis, dval );
+
+/* TextLabGap(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "textlabgap(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetTextLabGap( this, axis - 1, dval );
+
+/* LabelUp. */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "labelup= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetLabelUp( this, axis, ival );
+
+/* LabelUp(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "labelup(%d)= %d %n",
+                              &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetLabelUp( this, axis - 1, ival );
+
+/* LogPlot. */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "logplot= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetLogPlot( this, axis, ival );
+
+/* LogPlot(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "logplot(%d)= %d %n",
+                              &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetLogPlot( this, axis - 1, ival );
+
+/* LogTicks. */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "logticks= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetLogTicks( this, axis, ival );
+
+/* LogTicks(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "logticks(%d)= %d %n",
+                              &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetLogTicks( this, axis - 1, ival );
+
+/* LogLabel. */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "loglabel= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetLogLabel( this, axis, ival );
+
+/* LogLabel(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "loglabel(%d)= %d %n",
+                              &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetLogLabel( this, axis - 1, ival );
+
+/* NumLab. */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "numlab= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetNumLab( this, axis, ival );
+
+/* NumLab(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "numlab(%d)= %d %n",
+                              &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetNumLab( this, axis - 1, ival );
+
+/* MinTick. */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "mintick= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetMinTick( this, axis, ival );
+
+/* MinTick(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "mintick(%d)= %d %n",
+                              &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetMinTick( this, axis - 1, ival );
+
+/* TextLab. */
+/* ---------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "textlab= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetTextLab( this, axis, ival );
+
+/* TextLab(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "textlab(%d)= %d %n",
+                              &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetTextLab( this, axis - 1, ival );
+
+/* LabelUnits. */
+/* --------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "labelunits= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetLabelUnits( this, axis, ival );
+
+/* LabelUnits(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "labelunits(%d)= %d %n",
+                              &axis, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetLabelUnits( this, axis - 1, ival );
+
+/* Style. */
+/* ------ */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "style= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astSetStyle( this, id, ival );
+
+/* Style(label). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "style(%20[^()])= %d %n",
+                              label, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, "Style", "astSet", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astSetStyle( this, id1, ival );
+      if( nid > 1 ) astSetStyle( this, id2, ival );
+      if( nid > 2 ) astSetStyle( this, id3, ival );
+
+/* Font. */
+/* ----- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "font= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astSetFont( this, id, ival );
+
+/* Font(label). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "font(%20[^()])= %d %n",
+                              label, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, "Font", "astSet", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astSetFont( this, id1, ival );
+      if( nid > 1 ) astSetFont( this, id2, ival );
+      if( nid > 2 ) astSetFont( this, id3, ival );
+
+/* Colour. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "colour= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astSetColour( this, id, ival );
+
+/* Colour(label). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "colour(%20[^()])= %d %n",
+                              label, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, "Colour", "astSet", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astSetColour( this, id1, ival );
+      if( nid > 1 ) astSetColour( this, id2, ival );
+      if( nid > 2 ) astSetColour( this, id3, ival );
+
+/* Color. */
+/* ------ */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "color= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astSetColour( this, id, ival );
+
+/* Color(label). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "color(%20[^()])= %d %n",
+                              label, &ival, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, "Color", "astSet", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astSetColour( this, id1, ival );
+      if( nid > 1 ) astSetColour( this, id2, ival );
+      if( nid > 2 ) astSetColour( this, id3, ival );
+
+/* Width. */
+/* ------ */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "width= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astSetWidth( this, id, dval );
+
+/* Width(label). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "width(%20[^()])= %lg %n",
+                              label, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, "Width", "astSet", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astSetWidth( this, id1, dval );
+      if( nid > 1 ) astSetWidth( this, id2, dval );
+      if( nid > 2 ) astSetWidth( this, id3, dval );
+
+/* Size. */
+/* ----- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "size= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      for( id = 0; id < AST__NPID; id++ ) astSetSize( this, id, dval );
+
+/* Size(label). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "size(%20[^()])= %lg %n",
+                              label, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      class =  astGetClass( this );
+
+      nid = IdFind( FullForm( GrfLabels, label, "Size", "astSet", class, status ),
+                    nax, &id1, &id2, &id3, status );
+      astSetSize( this, id1, dval );
+      if( nid > 1 ) astSetSize( this, id2, dval );
+      if( nid > 2 ) astSetSize( this, id3, dval );
+
+/* TitleGap. */
+/* ----------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "titlegap= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetTitleGap( this, dval );
+
+/* MajTickLen. */
+/* ----------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "majticklen= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetMajTickLen( this, axis, dval );
+
+/* MajTickLen(axis). */
+/* ----------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "majticklen(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetMajTickLen( this, axis - 1, dval );
+
+/* MinTickLen. */
+/* ----------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "minticklen= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      for( axis = 0; axis < nax; axis++ ) astSetMinTickLen( this, axis, dval );
+
+/* MinTickLen(axis). */
+/* ----------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "minticklen(%d)= %lg %n",
+                              &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetMinTickLen( this, axis - 1, dval );
+
+/* Labelling. */
+/* -------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "labelling= %n%*s %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetLabelling( this, FullForm( "exterior interior",
+                                        setting + ival, setting,
+                                        "astSet", astGetClass( this ), status )
+                      );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+static void SetLogPlot( AstPlot *this, int axis, int ival, int *status ){
+/*
+*
+*  Name:
+*     SetLogPlot
+
+*  Purpose:
+*     Set a new value for a LogPlot attribute
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void SetLogPlot( AstPlot *this, int axis, int ival, int *status )
+
+*  Class Membership:
+*     Plot member function
+
+*  Description:
+*     Assigns a new value to the LogPlot attribute of the specified axis,
+*     and also re-maps the base Frame of the Plot if necessary.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     axis
+*        Zero based axis index.
+*     ival
+*        The new value for the ogPlot attribute.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int oldval;              /* Original attribute value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the axis index. */
+   if( axis < 0 || axis >= 2 ){
+      astError( AST__AXIIN, "astSetLogPlot(%s): Index (%d) is invalid for "
+                "attribute LogPlot - it should be in the range 1 to 2.", status,
+                astGetClass( this ), axis + 1 );
+
+/* If the axis index is OK, store the original attribute value. We use
+   astGetLogPlot to get the value rather than directly accessing
+   "this->logplot" in order to get the any default value in the case of
+   no value having been set. */
+   } else {
+      oldval = astGetLogPlot( this, axis );
+
+/* If the current attribute value will change, attempt to re-map the plot
+   axis. If the attempt succeeds, toggle the current attribute value. */
+      if( ( ival != 0 ) != ( oldval != 0 ) ){
+         if( ToggleLogLin( this, axis, oldval, "astSetLogPlot", status ) ){
+            this->logplot[ axis ] = ( !oldval );
+         }
+
+/* If the current attribute value will not change, just store the supplied
+   value (this is not redundant because it may cause a previously defaulted
+   value to become an explicitly set value ). */
+      } else {
+         this->logplot[ axis ] = oldval;
+      }
+   }
+}
+
+static void SetTickValues( AstPlot *this, int axis, int nmajor, double *major,
+                           int nminor, double *minor, int *status ){
+/*
+*+
+*  Name:
+*     astSetTickValues
+
+*  Purpose:
+*     Store the tick mark values to use for a given Plot axis.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void astSetTickValues( AstPlot *this, int axis, int nmajor,
+*                            double *major, int nminor, double *minor )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function stores a set of tick mark values that should be used by
+*     subsequent calls to astGrid.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot.
+*     axis
+*        The zero-based index of the axis for which tick marks values
+*        have been supplied.
+*     nmajor
+*        The number of major tick mark values. If zero is supplied then
+*        the other parameters are ignored, and subsequent calls to
+*        astGrid will itself determine the tick values to be used.
+*     major
+*        Pointer to an array holding "nmajor" values for axis "axis" in
+*        the current Frame of the suppled Plot. Major tick marks will be
+*        drawn at these values.
+*     nminor
+*        The number of minor tick mark values.
+*     minor
+*        Pointer to an array holding "nminor" values for axis "axis" in
+*        the current Frame of the suppled Plot. Minor tick marks will be
+*        drawn at these values.
+
+*-
+*/
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Report an error if the supplied axis value is incorrect. */
+   if( axis < 0 || axis >= astGetNin( this ) ) {
+      astError( AST__INTER, "astSetTickValues(Plot): Supplied \"axis\" "
+                "value is %d - should in the range 0 to %d (internal AST "
+                "programming error).", status, axis, astGetNin( this ) - 1 );
+
+/* Otherwise store or clear the values. */
+   } else if( nmajor > 0 ){
+      this->nmajtickval[ axis ] = nmajor;
+      this->majtickval[ axis ] = astStore( this->majtickval[ axis ], major,
+                                           sizeof( double )*nmajor );
+      this->nmintickval[ axis ] = nminor;
+      this->mintickval[ axis ] = astStore( this->mintickval[ axis ], minor,
+                                           sizeof( double )*nminor );
+
+/* Sort them into increasing order. */
+      qsort( (void *) this->majtickval[ axis ], (size_t) nmajor,
+             sizeof(double), Compared );
+
+      qsort( (void *) this->mintickval[ axis ], (size_t) nminor,
+             sizeof(double), Compared );
+
+   } else {
+      this->nmajtickval[ axis ] = 0;
+      this->majtickval[ axis ] = astFree( this->majtickval[ axis ] );
+      this->nmintickval[ axis ] = 0;
+      this->mintickval[ axis ] = astFree( this->mintickval[ axis ] );
+   }
+}
+
+const char *astStripEscapes_( const char *text, int *status ) {
+/*
+*++
+*  Name:
+c     astStripEscapes
+f     AST_STRIPESCAPES
+
+*  Purpose:
+*     Remove AST escape sequences from a string.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     const char *astStripEscapes( const char *text )
+f     RESULT = AST_STRIPESCAPES( TEXT )
+
+*  Description:
+*     This function removes AST escape sequences from a supplied string,
+*     returning the resulting text as the function value. The behaviour
+*     of this function can be controlled by invoking the
+c     astEscapes function,
+f     AST_ESCAPES routine,
+*     which can be used to supress or enable the removal of escape
+*     sequences by this function.
+*
+*     AST escape sequences are used by the Plot class to modify the
+*     appearance and position of sub-strings within a plotted text string.
+*     See the "Escape" attribute for further information.
+
+*  Parameters:
+c     text
+c        Pointer to the string to be checked.
+f     TEXT
+f        The string to be checked.
+
+*  Returned Value:
+c     astStripEscapes()
+f     AST_STRIPESCAPES = CHARACTER
+c        Pointer to the modified string. If no escape sequences were found
+c        in the supplied string, then a copy of the supplied pointer is
+c        returned. Otherwise, the pointer will point to a static buffer
+c        holding the modified text. This text will be over-written by
+c        subsequent invocations of this function. If the astEscapes function
+f        The modified string. If the AST_ESCAPES routine
+*        has been called indicating that escape sequences should not be
+*        stripped, then the supplied string is returned without change.
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   const char *a;
+   char *b;
+   int nc;
+   int ncc;
+   int type;
+   int value;
+   const char *result;
+
+/* Initialise */
+   result= text;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check inherited status and supplied pointer. Also return if the
+   string contains no escape sequences or if stripping of escapes has
+   been supressed. */
+   if( !astOK || astEscapes( -1 ) || !text || !HasEscapes( text, status ) ) return result;
+
+/* Initialise a pointer to the next character to be read from the
+   supplied string. */
+   a = text;
+
+/* Initialise a pointer to the next character to be written to the
+   returned string. */
+   b = stripescapes_buff;
+
+/* Note the available space left in the buffer. */
+   ncc = AST__PLOT_STRIPESCAPES_BUFF_LEN;
+
+/* Loop until all the string has been read, or the buffer is full. */
+   while( *a && ncc > 0 ){
+
+/* If the remaining string starts with an escape sequence, increment the
+   read point by the length of the escape sequence, but leave the write
+   pointer where it is. */
+      if( astFindEscape( a, &type, &value, &nc ) ) {
+         a += nc;
+
+/* If the remaining string does not start with an escape sequence, copy
+   the following text from the read position to the write position. */
+      } else {
+         if( nc > ncc ) nc = ncc;
+         memcpy( b, a, sizeof( char )*nc );
+         a += nc;
+         b += nc;
+         ncc -= nc;
+      }
+   }
+
+/* Terminate the returned string. */
+   *b = 0;
+
+/* Return the result.*/
+   return stripescapes_buff;
+}
+
+static int swapEdges( AstPlot *this, TickInfo **grid, AstPlotCurveData **cdata, int *status ) {
+/*
+*  Name:
+*     swapEdges
+
+*  Purpose:
+*     Determine if edges for text labels should be swapped.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int swapEdges( AstPlot *this, TickInfo **grid, AstPlotCurveData **cdata, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     or not it is necessary to swap the Edges(0) and Edges(1) attributes
+*     in order to place textual labels on appropriate edges. This should
+*     only be used if the attributes have not explicitly been set, and if
+*     interior labelling is being used. The sides are determines by
+*     looking at the bounding box of tick marks on axis 0, in graphics
+*     coordinates. The returned value causes the label for axis 0 to be
+*     placed on the edge parallel to the longest side of this bounding box.
+*     The label for axis 1 is placed parallel to the shortest side of this
+*     bounding box.
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     grid
+*        A pointer to an array of two TickInfo pointers (one for each axis),
+*        each pointing to a TickInfo structure holding information about
+*        tick marks on the axis. See function GridLines.
+*     cdata
+*        A pointer to an array of two AstPlotCurveData pointers (one for each axis),
+*        each pointing to an array of AstPlotCurveData structure (one for each
+*        major tick value on the axis), holding information about breaks
+*        in the curves drawn to mark the major tick values. See function
+*        DrawGrid.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the edges should be swapped, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPlotCurveData *cdt; /* Pointer to the AstPlotCurveData for the next tick */
+   TickInfo *info;        /* Pointer to the TickInfo for the current axis */
+   float xmax;            /* Max graphics X value */
+   float xmin;            /* Min graphics X value */
+   float ymax;            /* Max graphics Y value */
+   float ymin;            /* Min graphics Y value */
+   int oldedge;           /* The original edge for axis 0 */
+   int result;            /* Swap edges? */
+   int tick;              /* Tick index */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure containing information describing the
+   positions of the major tick marks along axis 0. */
+   info = grid[ 0 ];
+
+/* Get a pointer to the structure containing information describing
+   the breaks in the curve which is parallel to axis 1 and passes
+   through the first major tick mark on axis 0. */
+   cdt = cdata[ 0 ];
+
+/* Initialise the graphiocs X and Y bounds of the area covered by the
+   axis. */
+   xmax = -1.0E10;
+   xmin = 1.0E10;
+   ymax = -1.0E10;
+   ymin = 1.0E10;
+
+/* Loop round each of the major tick marks on axis 0. */
+   for( tick = 0; cdt && info && tick < info->nmajor; tick++ ){
+
+/* Update the max and min graphics X and Y coords covered by the axis. */
+      if( cdt->nbrk > 0 ) {
+         xmax = astMAX( xmax, cdt->xbrk[0] );
+         xmin = astMIN( xmin, cdt->xbrk[0] );
+         ymax = astMAX( ymax, cdt->ybrk[0] );
+         ymin = astMIN( ymin, cdt->ybrk[0] );
+      }
+
+/* Get a pointer to the curve through the next major tick mark. */
+      cdt++;
+
+   }
+
+/* See which edge axis 0 would normally be labelled on. */
+   oldedge = astGetEdge( this, 0 );
+
+/* If the X range is larger than the Y range, the textual label should be
+   placed on the bottom edge. If required, indicate that the edges must
+   be swapped to achieve this. */
+   if( xmax - xmin > ymax - ymin ) {
+      if( oldedge == 0 || oldedge == 2 ) result = 1;
+
+/* If the X range is smaller  than the Y range, the textual label should be
+   placed on the left edge. If required, indicate that the edges must
+   be swapped to achieve this. */
+   } else {
+      if( oldedge == 1 || oldedge == 3 ) result = 1;
+   }
+
+   return result;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a Plot.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Plot member function (over-rides the astTestAttrib protected
+*     method inherited from the FrameSet class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a Plot's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPlot *this;                /* Pointer to the Plot structure */
+   char label[21];               /* Graphics item label */
+   int axis;                     /* Axis number */
+   int len;                      /* Length of attrib string */
+   int nax;                      /* Number of base Frame axes */
+   int nc;                       /* No. characters read by astSscanf */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Plot structure. */
+   this = (AstPlot *) this_object;
+
+/* Get the number of base Frame axis (2 for a Plot, 3 for a Plot3D). */
+   nax = astGetNin( this );
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Tol. */
+/* ---- */
+   if ( !strcmp( attrib, "tol" ) ) {
+      result = astTestTol( this );
+
+/* Edge(axis). */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "edge(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestEdge( this, axis - 1 );
+
+/* Grid. */
+/* ----- */
+   } else if ( !strcmp( attrib, "grid" ) ) {
+      result = astTestGrid( this );
+
+/* TickAll. */
+/* -------- */
+   } else if ( !strcmp( attrib, "tickall" ) ) {
+      result = astTestTickAll( this );
+
+/* ForceExterior */
+/* ------------- */
+   } else if ( !strcmp( attrib, "forceexterior" ) ) {
+      result = astTestForceExterior( this );
+
+/* Invisible. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "invisible" ) ) {
+      result = astTestInvisible( this );
+
+/* Border. */
+/* ------- */
+   } else if ( !strcmp( attrib, "border" ) ) {
+      result = astTestBorder( this );
+
+/* ClipOp. */
+/* ------- */
+   } else if ( !strcmp( attrib, "clipop" ) ) {
+      result = astTestClipOp( this );
+
+/* Clip. */
+/* ----- */
+   } else if ( !strcmp( attrib, "clip" ) ) {
+      result = astTestClip( this );
+
+/* Grf. */
+/* ---- */
+   } else if ( !strcmp( attrib, "grf" ) ) {
+      result = astTestGrf( this );
+
+/* DrawTitle. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "drawtitle" ) ) {
+      result = astTestDrawTitle( this );
+
+/* DrawAxes. */
+/* --------- */
+   } else if ( !strcmp( attrib, "drawaxes" ) ) {
+      result = astTestDrawAxes( this, 0 );
+
+/* DrawAxes(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "drawaxes(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestDrawAxes( this, axis - 1 );
+
+/* Abbrev. */
+/* --------- */
+   } else if ( !strcmp( attrib, "abbrev" ) ) {
+      result = astTestAbbrev( this, 0 );
+
+/* Abbrev(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "abbrev(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestAbbrev( this, axis - 1 );
+
+/* Escape. */
+/* ------- */
+   } else if ( !strcmp( attrib, "escape" ) ) {
+      result = astTestEscape( this );
+
+/* Gap. */
+/* ---- */
+   } else if ( !strcmp( attrib, "gap" ) ) {
+      result = astTestGap( this, 0 );
+
+/* Gap(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "gap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestGap( this, axis - 1 );
+
+/* LabelAt(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "labelat(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestLabelAt( this, axis - 1 );
+
+/* LogGap. */
+/* ---- */
+   } else if ( !strcmp( attrib, "loggap" ) ) {
+      result = astTestLogGap( this, 0 );
+
+/* LogGap(axis). */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "loggap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestLogGap( this, axis - 1 );
+
+/* NumLabGap. */
+/* --------- */
+   } else if ( !strcmp( attrib, "numlabgap" ) ) {
+      result = astTestNumLabGap( this, 0 );
+
+/* NumLabGap(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "numlabgap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestNumLabGap( this, axis - 1 );
+
+/* TextLabGap. */
+/* --------- */
+   } else if ( !strcmp( attrib, "textlabgap" ) ) {
+      result = astTestTextLabGap( this, 0 );
+
+/* TextLabGap(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "textlabgap(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestTextLabGap( this, axis - 1 );
+
+/* LabelUp. */
+/* -------- */
+   } else if ( !strcmp( attrib, "labelup" ) ) {
+      result = astTestLabelUp( this, 0 );
+
+/* LabelUp(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "labelup(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestLabelUp( this, axis - 1 );
+
+/* LogPlot. */
+/* -------- */
+   } else if ( !strcmp( attrib, "logplot" ) ) {
+      result = astTestLogPlot( this, 0 );
+
+/* LogPlot(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "logplot(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestLogPlot( this, axis - 1 );
+
+/* LogTicks. */
+/* -------- */
+   } else if ( !strcmp( attrib, "logticks" ) ) {
+      result = astTestLogTicks( this, 0 );
+
+/* LogTicks(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "logticks(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestLogTicks( this, axis - 1 );
+
+/* LogLabel. */
+/* -------- */
+   } else if ( !strcmp( attrib, "loglabel" ) ) {
+      result = astTestLogLabel( this, 0 );
+
+/* LogLabel(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "loglabel(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestLogLabel( this, axis - 1 );
+
+/* NumLab. */
+/* -------- */
+   } else if ( !strcmp( attrib, "numlab" ) ) {
+      result = astTestNumLab( this, 0 );
+
+/* NumLab(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "numlab(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestNumLab( this, axis - 1 );
+
+/* MinTick. */
+/* -------- */
+   } else if ( !strcmp( attrib, "mintick" ) ) {
+      result = astTestMinTick( this, 0 );
+
+/* MinTick(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "mintick(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestMinTick( this, axis - 1 );
+
+/* TextLab. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "textlab" ) ) {
+      result = astTestTextLab( this, 0 );
+
+/* TextLab(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "textlab(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestTextLab( this, axis - 1 );
+
+/* LabelUnits. */
+/* --------- */
+   } else if ( !strcmp( attrib, "labelunits" ) ) {
+      result = astTestLabelUnits( this, 0 );
+
+/* LabelUnits(axis). */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "labelunits(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestLabelUnits( this, axis - 1 );
+
+/* Style. */
+/* ------ */
+   } else if ( !strcmp( attrib, "style" ) ) {
+      result = TestUseStyle( this, AST__BORDER_ID, status );
+
+/* Style(label). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "style(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      result = TestUseStyle( this, FullForm( GrfLabels, label, attrib, "astTest", astGetClass( this ), status ), status );
+
+/* Font. */
+/* ----- */
+   } else if ( !strcmp( attrib, "font" ) ) {
+      result = TestUseFont( this, AST__TEXTLABS_ID, status );
+
+/* Font(label). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "font(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      result = TestUseFont( this, FullForm( GrfLabels, label, attrib, "astTest", astGetClass( this ), status ), status );
+
+/* Colour. */
+/* ------- */
+   } else if ( !strcmp( attrib, "colour" ) ) {
+      result = TestUseColour( this, AST__TEXTLABS_ID, status );
+
+/* Color. */
+/* ------- */
+   } else if ( !strcmp( attrib, "color" ) ) {
+      result = TestUseColour( this, AST__TEXTLABS_ID, status );
+
+/* Colour(label). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "colour(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      result = TestUseColour( this, FullForm( GrfLabels, label, attrib, "astTest", astGetClass( this ), status ), status );
+
+/* Color(label). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "color(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      result = TestUseColour( this, FullForm( GrfLabels, label, attrib, "astTest", astGetClass( this ), status ), status );
+
+/* Width. */
+/* ------ */
+   } else if ( !strcmp( attrib, "width" ) ) {
+      result = TestUseWidth( this, AST__BORDER_ID, status );
+
+/* Width(label). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "width(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      result = TestUseWidth( this, FullForm( GrfLabels, label, attrib, "astTest", astGetClass( this ), status ), status );
+
+/* Size. */
+/* ----- */
+   } else if ( !strcmp( attrib, "size" ) ) {
+      result = TestUseSize( this, AST__TEXTLABS_ID, status );
+
+/* Size(label). */
+/* ------------ */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "size(%20[^()])%n", label, &nc ) )
+               && ( nc >= len ) ) {
+      result = TestUseSize( this, FullForm( GrfLabels, label, attrib, "astTest", astGetClass( this ), status ), status );
+
+/* TitleGap. */
+/* --------- */
+   } else if ( !strcmp( attrib, "titlegap" ) ) {
+      result = astTestTitleGap( this );
+
+/* MajTickLen. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "majticklen" ) ) {
+      result = astTestMajTickLen( this, 0 );
+
+/* MajTickLen(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "majticklen(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestMajTickLen( this, axis - 1 );
+
+/* MinTickLen. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "minticklen" ) ) {
+      result = astTestMinTickLen( this, 0 );
+
+/* MinTickLen(axis). */
+/* ---------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "minticklen(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestMinTickLen( this, axis - 1 );
+
+/* Labelling. */
+/* -------- */
+   } else if ( !strcmp( attrib, "labelling" ) ) {
+      result = astTestLabelling( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static void Text( AstPlot *this, const char *text, const double pos[],
+                  const float up[], const char *just, int *status ){
+/*
+*++
+*  Name:
+c     astText
+f     AST_TEXT
+
+*  Purpose:
+*     Draw a text string for a Plot.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     void astText( AstPlot *this, const char *text, const double pos[],
+c                   const float up[], const char *just )
+f     CALL AST_TEXT( THIS, TEXT, POS, UP, JUST, STATUS )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function draws a string of text at a position specified in
+*     the physical coordinate system of a Plot. The physical position
+*     is transformed into graphical coordinates to determine where the
+*     text should appear within the plotting area.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Plot.
+c     text
+f     TEXT = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string containing the
+f        A character string containing the
+*        text to be drawn. Trailing white space is ignored.
+c     pos
+f     POS( * ) = DOUBLE PRECISION (Given)
+*        An array, with one element for each axis of the Plot, giving
+*        the physical coordinates of the point where the reference
+*        position of the text string is to be placed.
+c     up
+f     UP( * ) = REAL (Given)
+*        An array holding the components of a vector in the "up"
+*        direction of the text (in graphical coordinates). For
+c        example, to get horizontal text, the vector {0.0f,1.0f} should
+f        example, to get horizontal text, the vector [0.0,1.0] should
+*        be supplied. For a basic Plot, 2 values should be supplied. For
+*        a Plot3D, 3 values should be supplied, and the actual up vector
+*        used is the projection of the supplied up vector onto the text plane
+*        specified by the current value of the Plot3D's Norm attribute.
+c     just
+f     JUST = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated character string identifying the
+f        A character string identifying the
+*        reference point for the text being drawn. The first character in
+*        this string identifies the reference position in the "up" direction
+*        and may be "B" (baseline), "C" (centre), "T" (top) or "M" (bottom).
+*        The second character identifies the side-to-side reference position
+*        and may be "L" (left), "C" (centre) or "R" (right ). The string is
+*        case-insensitive, and only the first two characters are significant.
+*
+*        For example, a value of "BL" means that the left end of the
+*        baseline of the original (un-rotated) text is to be drawn at the
+c        position given by "pos".
+f        position given by POS.
+
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - The Plot3D class currently does not interpret graphical escape
+*     sequences contained within text displayed using this method.
+*     - Text is not drawn at positions which have any coordinate equal
+*     to the value AST__BAD (or where the transformation into
+*     graphical coordinates yields coordinates containing the value
+*     AST__BAD).
+c     - If the plotting position is clipped (see astClip), then no
+f     - If the plotting position is clipped (see AST_CLIP), then no
+*     text is drawn.
+*     - An error results if the base Frame of the Plot is not
+*     2-dimensional or (for a Plot3D) 3-dimensional.
+*     - An error also results if the transformation between the
+*     current and base Frames of the Plot is not defined (i.e. the
+*     Plot's TranInverse attribute is zero).
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   AstMapping *mapping;    /* Pointer to graphics->physical mapping */
+   AstPointSet *pset1;     /* PointSet holding physical positions */
+   AstPointSet *pset2;     /* PointSet holding graphics positions */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   const double **ptr1;    /* Pointer to physical positions */
+   char ljust[3];          /* Upper case copy of "just" */
+   char *ltext;            /* Local copy of "text" excluding trailing spaces */
+   double **ptr2;          /* Pointer to graphics positions */
+   float xbn[ 4 ];         /* X coords of text bounding box. */
+   float ybn[ 4 ];         /* Y coord of text bounding box. */
+   int axis;               /* Axis index */
+   int escs;               /* Original astEscapes value */
+   int naxes;              /* No. of axes in the base Frame */
+   int ncoord;             /* No. of axes in the current Frame */
+   int ulen;               /* Length of "text" excluding trailing spaces */
+
+/* Check the global error status. */
+   if ( !astOK || !text ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Store the current method and class for inclusion in error messages
+   generated by lower level functions. */
+   method = "astText";
+   class = astClass( this );
+
+/* Check the base Frame of the Plot is 2-D. */
+   naxes = astGetNin( this );
+   if( naxes != 2 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 2.", status, method, class, naxes, class );
+   }
+
+/* Ensure AST functions included graphical escape sequences in any
+   returned text strings. */
+   escs = astEscapes( 1 );
+
+/* Initialise the bounding box for primatives produced by this call. */
+   if( !Boxp_freeze ) {
+      Boxp_lbnd[ 0 ] = FLT_MAX;
+      Boxp_lbnd[ 1 ] = FLT_MAX;
+      Boxp_ubnd[ 0 ] = FLT_MIN;
+      Boxp_ubnd[ 1 ] = FLT_MIN;
+   }
+
+/* Indicate that the GRF module should re-calculate it's cached values
+   (in case the state of the graphics system has changed since the last
+   thing was drawn). */
+   RESET_GRF;
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+   astGrfAttrs( this, AST__TEXT_ID, 1, GRF__TEXT, method, class );
+
+/* Get the number of coordinates in the physical coordinate frame. */
+   ncoord = astGetNout( this );
+
+/* Create a PointSet to hold the supplied physical coordinates. */
+   pset1 = astPointSet( 1, ncoord, "", status );
+
+/* Allocate memory to hold pointers to the first value on each axis. */
+   ptr1 = (const double **) astMalloc( sizeof( const double * )*
+                                       (size_t)( ncoord ));
+
+/* Check the pointer can be used, then store pointers to the first value
+   on each axis. */
+   if( astOK ){
+      for( axis = 0; axis < ncoord; axis++ ){
+         ptr1[ axis ] = pos + axis;
+      }
+   }
+
+/* Store these pointers in the PointSet. */
+   astSetPoints( pset1, (double **) ptr1 );
+
+/* Transform the supplied data from the current frame (i.e. physical
+   coordinates) to the base frame (i.e. graphics coordinates) using
+   the inverse Mapping defined by the Plot. */
+   mapping = astGetMapping( this, AST__BASE, AST__CURRENT );
+   pset2 = Trans( this, NULL, mapping, pset1, 0, NULL, 0, method, class, status );
+   mapping = astAnnul( mapping );
+
+/* Get pointers to the graphics coordinates. */
+   ptr2 = astGetPoints( pset2 );
+
+/* Take a copy of the string excluding any trailing white space. */
+   ulen = ChrLen( text, status );
+   ltext = (char *) astStore( NULL, (void *) text, ulen + 1 );
+
+/* Check the pointers can be used. */
+   if( astOK ){
+
+/* Terminate the local copy of the text string. */
+      ltext[ ulen ] = 0;
+
+/* Produce an upper-case copy of the first two characters in "just". */
+      ljust[0] = (char) toupper( (int) just[0] );
+      ljust[1] = (char) toupper( (int) just[1] );
+      ljust[2] = 0;
+
+/* Convert the double precision values to single precision, checking for
+   bad positions. */
+      if( ptr2[0][0] != AST__BAD && ptr2[1][0] != AST__BAD ){
+
+/* Draw the text string. */
+         DrawText( this, 1, astGetEscape( this ), ltext, (float) ptr2[0][0],
+                   (float) ptr2[1][0], ljust, up[ 0 ], up[ 1 ], xbn, ybn,
+                   NULL, method, class, status );
+      }
+
+/* Free the local copy of the string. */
+      ltext = (char *) astFree( (void *) ltext );
+
+   }
+
+/* Annul the PointSets. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* Free the memory holding the pointers to the first value on each axis. */
+   ptr1 = (const double **) astFree( (void *) ptr1 );
+
+/* Re-establish the original graphical attributes. */
+   astGrfAttrs( this, AST__TEXT_ID, 0, GRF__TEXT, method, class );
+
+/* Restore the original value of the flag which says whether graphical
+   escape sequences should be incldued in any returned text strings. */
+   astEscapes( escs );
+
+/* Return */
+   return;
+}
+
+static void TextLabels( AstPlot *this, int edgeticks, int dounits[2],
+                        const char *method, const char *class, int *status ){
+/*
+*
+*  Name:
+*     TextLabels
+
+*  Purpose:
+*     Draw textual labels round a grid.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void TextLabels( AstPlot *this, int edgeticks, int dounits[2],
+*                      const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function displays a textual label for each physical axis, and a
+*     title. The text strings are obtained from the Label and Title
+*     attributes of the current Frame in the Plot.
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     edgeticks
+*        If tick marks and numerical labels were drawn around the edges
+*        of the plotting area, this should be supplied as 1. Otherwise it
+*        should be supplied as zero.
+*     dounits
+*        Flags indicating if the axis Units string should be included in
+*        the edge labels.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   char *new_text;         /* Pointer to complete text string (inc. units) */
+   char *just;             /* Pointer to axis label justification string */
+   const char *text;       /* Pointer to text string to be displayed */
+   const char *units;      /* Pointer to text string describing the units */
+   double mindim;          /* Minimum dimension of plotting area */
+   double xrange;          /* Width of plotting area */
+   double yrange;          /* Height of plotting area */
+   float txtgap;           /* Gap between bounding box and text string */
+   float upx;              /* X component of text up-vector */
+   float upy;              /* Y component of text up-vector */
+   float xbn[ 4 ];         /* X coords at corners of new label's bounding box */
+   float ybn[ 4 ];         /* Y coords at corners of new label's bounding box */
+   float xref;             /* Graphics X coord. at text ref. position */
+   float yref;             /* Graphics Y coord. at text ref. position */
+   float xlo, xhi, ylo, yhi;/* The original bounding box (excluding labels) */
+   int axis;               /* Axis index */
+   int draw;               /* Should label be drawn? */
+   int edge;               /* Edge to be labelled */
+   int esc;                /* Interpret escape sequences? */
+   int gelid;              /* ID for next graphical element to be drawn */
+   int tlen;               /* No. of characetsr in label */
+   int ulen;               /* No. of characetsr in units */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Get the minimum dimension of the plotting area. */
+   xrange = this->xhi - this->xlo;
+   yrange = this->yhi - this->ylo;
+   mindim = astMIN( xrange, yrange );
+
+/* Take a copy of the bounding box which encloses all other parts of the
+   annotated grid. If nothing has been plotted, use an area 20 % smaller
+   than the plotting area. */
+   if( Box_lbnd[ 0 ] != FLT_MAX ) {
+      xlo = Box_lbnd[ 0 ];
+      xhi = Box_ubnd[ 0 ];
+      ylo = Box_lbnd[ 1 ];
+      yhi = Box_ubnd[ 1 ];
+   } else {
+      xlo = this->xlo + 0.2*xrange;
+      xhi = this->xhi - 0.2*xrange;
+      ylo = this->ylo + 0.2*yrange;
+      yhi = this->yhi - 0.2*yrange;
+   }
+
+/* See if escape sequences are to be interpreted within the labels. */
+   esc = astGetEscape( this );
+
+/* Initialize the id value for graphical element being drawn. */
+   gelid = AST__TEXTLAB1_ID;
+
+/* Now write a label for each physical axis. */
+   for( axis = 0; axis < 2 && astOK; axis++ ){
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+      astGrfAttrs( this, gelid, 1, GRF__TEXT, method, class );
+
+/* See if the label is to be drawn. If an explicit value has not been set
+   for the TextLab attribute, the default is to draw the label if tick
+   marks were draw round the edge of the plotting area, and not to
+   otherwise. */
+      if( astTestTextLab( this, axis ) ){
+         draw = astGetTextLab( this, axis );
+      } else {
+         draw = edgeticks;
+      }
+
+/* If so get the label. */
+      if( draw ){
+         text = astGetLabel( this, axis );
+         tlen = ChrLen( text, status );
+
+/* If required, get the units string and concatenate it with the label (inside
+   parenthesise). Ignore trailing spaces. */
+         if( dounits[ axis ] ){
+            units = astGetUnit( this, axis );
+            if( units && units[0] ){
+               ulen = ChrLen( units, status );
+               new_text = astMalloc( tlen + ulen + 4 );
+               if( new_text ) memcpy( new_text, text, tlen );
+
+               text = new_text + tlen;
+
+               memcpy( (void *) text, " (", 2 );
+               text += 2;
+
+               memcpy( (char *) text, units, ulen );
+               text += ulen;
+
+               memcpy( (char *) text, ")", 1 );
+               text += 1;
+
+               ( (char *) text )[0] = 0;
+
+               text = new_text;
+
+            } else {
+               new_text = astStore( NULL, (void *) text, tlen + 1 );
+               new_text[ tlen ] = 0;
+               text = new_text;
+               units = NULL;
+            }
+
+         } else {
+            new_text = astStore( NULL, (void *) text, tlen + 1 );
+            new_text[ tlen ] = 0;
+            text = new_text;
+            units = NULL;
+         }
+
+/* Get the gap between the edge of the bounding box and the closest edge
+   of the text string. */
+         txtgap = (float)( mindim*astGetTextLabGap( this, axis ) );
+
+/* Get the edge to be labelled. Edge 0 is the left hand edge. Edge 1 is the
+   top edge. Edge 2 is the right-hand edge. Edge 3 is the bottom edge. */
+         edge = astGetEdge( this, axis ) % 4;
+         if( edge < 0 ) edge = -edge;
+
+/* If the label is to be put on the left hand edge... */
+         if( edge == 0 ){
+
+/* Set the up vector so that the text is written from bottom to top. */
+            upx = -1.0;
+            upy = 0.0;
+
+/* Justify the bottom of the whole bounding box (not just the text
+   base-line). */
+            just = "MC";
+
+/* Set the x reference position just outside the box enclosing all the
+   graphics drawn so far. The reference point refers to the centre of the
+   text string in both directions ("CC" justification). Take account of
+   whether or not the x axis is reversed. Do not allow the text to be
+   written outside the whole plotting surface. */
+            if( this->xrev ){
+               xref = xhi + txtgap;
+            } else {
+               xref = xlo - txtgap;
+            }
+
+/* The Y reference position is at the mid point vertically. */
+            yref = 0.5*( astMIN( yhi, this->yhi ) +
+                         astMAX( ylo, this->ylo ) );
+
+/* Do the same for the top edge. */
+         } else if( edge == 1 ){
+            upx = 0.0;
+            upy = 1.0;
+            just = "MC";
+            if( this->yrev ){
+               yref = ylo - txtgap;
+            } else {
+               yref = yhi + txtgap;
+            }
+            xref = 0.5*( astMIN( xhi, this->xhi ) +
+                         astMAX( xlo, this->xlo ) );
+
+/* Do the same for the right-hand edge. */
+         } else if( edge == 2 ){
+            upx = 1.0;
+            upy = 0.0;
+            just = "MC";
+            if( this->xrev ){
+               xref = xlo - txtgap;
+            } else {
+               xref = xhi + txtgap;
+            }
+            yref = 0.5*( astMIN( yhi, this->yhi ) +
+                         astMAX( ylo, this->ylo ) );
+
+/* Do the same for the bottom edge. */
+         } else {
+            upx = 0.0;
+            upy = 1.0;
+            just = "TC";
+            if( this->yrev ){
+               yref = yhi + txtgap;
+            } else {
+               yref = ylo - txtgap;
+            }
+            xref = 0.5*( astMIN( xhi, this->xhi ) +
+                         astMAX( xlo, this->xlo ) );
+         }
+
+/* Display the label. */
+         DrawText( this, 1, esc, text, xref, yref, just,
+                   upx, upy, xbn, ybn, NULL, method, class, status );
+
+/* Release the memory allocated to store the axis label;. */
+         new_text = (char *) astFree( (void *) new_text );
+         text = NULL;
+
+      }
+
+/* Re-establish the original graphical attributes. */
+      astGrfAttrs( this, gelid, 0, GRF__TEXT, method, class );
+
+/* Set up the id for the next graphical element to be drawn. */
+      gelid = AST__TEXTLAB2_ID;
+
+   }
+
+/* See if the title is to be drawn. */
+   if( astOK && astGetDrawTitle( this ) ){
+
+/* If so get the title. */
+      text = astGetTitle( this );
+
+/* Create a copy from which trailing spaces have been removed. */
+      tlen = ChrLen( text, status );
+      new_text = (char *) astStore( NULL, (void *) text, tlen + 1 );
+      new_text[ tlen ] = 0;
+      text = new_text;
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+      astGrfAttrs( this, AST__TITLE_ID, 1, GRF__TEXT, method, class );
+
+/* Take a copy of the bounding box which encloses all other parts of the
+   annotated grid (this may have been extended by the above code). If
+   nothing has been plotted, use an area 20 % smaller than the plotting
+   area. */
+      if( Box_lbnd[ 0 ] != FLT_MAX ) {
+         xlo = Box_lbnd[ 0 ];
+         xhi = Box_ubnd[ 0 ];
+         ylo = Box_lbnd[ 1 ];
+         yhi = Box_ubnd[ 1 ];
+      } else {
+         xlo = this->xlo + 0.2*xrange;
+         xhi = this->xhi - 0.2*xrange;
+         ylo = this->ylo + 0.2*yrange;
+         yhi = this->yhi - 0.2*yrange;
+      }
+
+/* Get the graphics coordinates of the bottom centre point of the title.
+   The X centre is put at the mid point of the used x axis range
+   (restricted to the range of the plotting area). */
+      xref = 0.5*( astMIN( xhi, this->xhi ) +
+                   astMAX( xlo, this->xlo ) );
+
+/* The Y centre is put a "TitleGap" distance outside the box containing
+   the everything else. */
+      if( this->yrev ){
+         yref = ylo - (float)( mindim*astGetTitleGap( this ) );
+      } else {
+         yref = yhi + (float)( mindim*astGetTitleGap( this ) );
+      }
+
+/* Display the title. Justify the bottom of the whole bounding box (not
+   just the text base-line). */
+      DrawText( this, 1, esc, text, xref, yref, "MC", 0.0F, 1.0F,
+                xbn, ybn, NULL, method, class, status );
+
+/* Re-establish the original graphical attributes. */
+      astGrfAttrs( this, AST__TITLE_ID, 0, GRF__TEXT, method, class );
+
+/* Release the memory allocated to store the title. */
+      new_text = (char *) astFree( (void *) new_text );
+      text = NULL;
+   }
+
+/* Include the labels in the bounding box held in global variables
+   Box_lbnd/ubnd. */
+   if( Box_lbnd[ 0 ] != FLT_MAX ) {
+      Box_lbnd[ 0 ] = astMIN( Box_lbnd[ 0 ], Boxp_lbnd[ 0 ] );
+      Box_ubnd[ 0 ] = astMAX( Box_ubnd[ 0 ], Boxp_ubnd[ 0 ] );
+      Box_lbnd[ 1 ] = astMIN( Box_lbnd[ 1 ], Boxp_lbnd[ 1 ] );
+      Box_ubnd[ 1 ] = astMAX( Box_ubnd[ 1 ], Boxp_ubnd[ 1 ] );
+   } else {
+      Box_lbnd[ 0 ] = Boxp_lbnd[ 0 ];
+      Box_ubnd[ 0 ] = Boxp_ubnd[ 0 ];
+      Box_lbnd[ 1 ] = Boxp_lbnd[ 1 ];
+      Box_ubnd[ 1 ] = Boxp_ubnd[ 1 ];
+   }
+
+/* Return. */
+   return;
+
+}
+
+static void UpdateConcat( float *xbn, float *ybn, float ux, float uy,
+                          float rx, float ry, float *x, float *y,
+                          float x0, float y0, float *alpha_lo,
+                          float *alpha_hi, float *beta_lo, float *beta_hi,
+                          int *status ){
+
+/*
+*  Name:
+*     UpdateConcat
+
+*  Purpose:
+*     Update the concatenation point for a text string in preparation for
+*     appending another string.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void UpdateConcat( float *xbn, float *ybn, float ux, float uy,
+*                        float rx, float ry, float *x, float *y,
+*                        float x0, float y0, float *alpha_lo, float *alpha_hi,
+*                        float *beta_lo, float *beta_hi, int *status )
+
+*  Description:
+*     This function modifies the supplied concatenation point (x,y) by moving
+*     it to the right along the baseline of the text by an amount equal
+*     to the width of the supplied sub-string bounding box. It also updates
+*     the supplied total bounding box so that it includes the supplied
+*     sub-string bounding box.
+
+*  Parameters:
+*     xbn
+*        Pointer to an array holding the x coord at the four corners of
+*        the sub-string bounding box.
+*     ybn
+*        Pointer to an array holding the y coord at the four corners of
+*        the sub-string bounding box.
+*     ux
+*        The x component of the up-vector for the text, in graphics coords.
+*        The length of this vector should be equal to the height of normal
+*        text drawn with this up-vector.
+*     uy
+*        The y component of the up-vector for the text. See "ux".
+*     rx
+*        The x component of the right-vector for the text. The length of this
+*        vector should be equal to the height of normal text drawn with the
+*        supplied up-vector.
+*     ry
+*        The y component of the right-vector for the text. see "rx".
+*     x
+*        Pointer to a float holding the x coord of the concatenation point
+*        of the text just drawn. This is changed on exit so that the next
+*        string will be appended to the end of the text just drawn.
+*     y
+*        Pointer to a float holding the y coord of the concatenation point
+*        of the text just drawn. This is changed on exit so that the next
+*        string will be appended to the end of the text just drawn.
+*     x0
+*        The X coord at the left end of the baseline of the total string.
+*     y0
+*        The Y coord at the left end of the baseline of the total string.
+*     alpha_lo
+*        Pointer to a double holding a value which gives the position of the
+*        bottom edge of the total bounding box. The value is such that
+*        (x0,y0) + alpha_lo*(ux,uy) is a point on the bottom edge of the
+*        total bounding box. The returned value is updated so that the
+*        total bounding box includes the supplied sub-string bounding box.
+*     alpha_hi
+*        Pointer to a double holding a value which gives the position of the
+*        top edge of the total bounding box. The value is such that
+*        (x0,y0) + alpha_hi*(ux,uy) is a point on the top edge of the
+*        total bounding box. The returned value is updated so that the
+*        total bounding box includes the supplied sub-string bounding box.
+*     beta_lo
+*        Pointer to a double holding a value which gives the position of the
+*        left edge of the total bounding box. The value is such that
+*        (x0,y0) + beta_lo*(rx,ry) is a point on the left edge of the
+*        total bounding box. The returned value is updated so that the
+*        total bounding box includes the supplied sub-string bounding box.
+*     beta_hi
+*        Pointer to a double holding a value which gives the position of the
+*        right edge of the total bounding box. The value is such that
+*        (x0,y0) + beta_hi*(rx,ry) is a point on the right edge of the
+*        total bounding box. The returned value is updated so that the
+*        total bounding box includes the supplied sub-string bounding box.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   float ahi;
+   float alo;
+   float alpha;
+   float beta;
+   float bhi;
+   float blo;
+   float denom;
+   float dx;
+   float dy;
+   float xc;
+   float yc;
+   int ic;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Check the supplied up and right vectors are not parallel. */
+   denom = ux*ry - uy*rx;
+   if( denom != 0.0 ) {
+
+/* Get the coords at the centre of the sub-string bounding box. */
+      xc = ( xbn[ 0 ] + xbn[ 1 ] + xbn[ 2 ] + xbn[ 3 ] )/4.0;
+      yc = ( ybn[ 0 ] + ybn[ 1 ] + ybn[ 2 ] + ybn[ 3 ] )/4.0;
+
+/* For each of the 4 corners of the sub-string bounding box, we consider the
+   vector from the centre of the bounding box to the corner. We want to express
+   this vector, vx, as a sum of a vector in the up direction and a vector
+   in the right direction:
+
+      vx = alpha*ux + beta+rx
+      vy = alpha*uy + beta+ry
+
+   where alpha and beta are constants which are different for each
+   corner vector. We also want to find the minimum and maximum alpha and
+   beta covered by the box. First initialise the limits. */
+      alo = 0.0;
+      ahi = 0.0;
+      blo = 0.0;
+      bhi = 0.0;
+
+/* Now find alpha and beta for each of the four corners. */
+      for( ic = 0; ic < 4; ic++ ) {
+         dx = xbn[ ic ] - xc;
+         dy = ybn[ ic ] - yc;
+         alpha = ( dx*ry - dy*rx ) /denom;
+         beta = ( dy*ux - dx*uy ) /denom;
+
+/* Extend the bounds in alpha and beta. */
+         if( alpha < alo ) alo = alpha;
+         if( alpha > ahi ) ahi = alpha;
+         if( beta < blo ) blo = beta;
+         if( beta > bhi ) bhi = beta;
+
+/* The bottom left corner has negative values for both alpha and beta.
+   Commence the process of updating the concatenation point by subtracting
+   off the coordinates at the bottom left corner. For zero height bounding
+   boxes (such as may be produced if the text is completely blank), the
+   "alpha" value should be zero, but may be slightly non-zero due to
+   rounding errors. Allow for this (assuming non-blank text will always
+   produce a boundiong box that is at least 1.0E-4 of the up vector in
+   height). We do the same for bounding box width (although zero width
+   boxes will probably not occur). */
+         if( alpha < 1.0E-4 ) {
+            if( beta < 1.0E-4 ) {
+               *x -= xbn[ ic ];
+               *y -= ybn[ ic ];
+
+/* The bottom right corner has negative alpha and positive beta. Complete
+   the process of updating the concatenation point by adding on the
+   coordinates at the bottom right corner. */
+            } else if( beta > -1.0E-4 ) {
+               *x += xbn[ ic ];
+               *y += ybn[ ic ];
+            }
+         }
+      }
+
+/* Also express the vector from (x0,y0) to (xc,yc) as a sum of a vector
+   in the up direction and a vector in the right direction:
+
+      xc - x0 = alpha*ux + beta*rx
+      yc - y0 = alpha*uy + beta*ry
+
+   Find alpha and beta. */
+
+      dx = xc - x0;
+      dy = yc - y0;
+      alpha = ( dx*ry - dy*rx ) /denom;
+      beta = ( dy*ux - dx*uy ) /denom;
+
+/* We can now express the vector from (x0,y0) to each of the 4 corners as
+   a sum of alpha*up and beta*right. Form the bounds of the sub-string in
+   terms of up-vectors and right vectors from (x0,y0) instead of from the
+   centre of the box. */
+      alo += alpha;
+      ahi += alpha;
+      blo += beta;
+      bhi += beta;
+
+/* Extend the supplied alpha and beta bounding box to include these limits. */
+      if( alo < *alpha_lo ) *alpha_lo = alo;
+      if( ahi > *alpha_hi ) *alpha_hi = ahi;
+      if( blo < *beta_lo ) *beta_lo = blo;
+      if( bhi > *beta_hi ) *beta_hi = bhi;
+   }
+}
+
+static void Ticker( AstPlot *this, int edge, int axis, double value,
+                    double *gap, double tklen, int majtick, int save,
+                    EdgeCrossingsStatics **pstatics, const char *method,
+                    const char *class, int *status ){
+/*
+*
+*  Name:
+*     Ticker
+
+*  Purpose:
+*     Draw edge tick marks for a given axis value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void Ticker( AstPlot *this, int edge, int axis, double value,
+*                  double *gap, double tklen, int majtick, int save,
+*                  EdgeCrossingsStatics **pstatics, const char *method,
+*                  const char *class, int *status ){
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function draws tick marks at all occurences of a given
+*     physical axis value on a given edge of the plotting area.
+
+*  Parameters:
+*     this
+*        A pointer to the Plot.
+*     edge
+*        The edge of the plotting area to be ticked. Edge 0 is the left hand
+*        edge. Edge 1 is the top edge. Edge 2 is the right-hand edge. Edge 3
+*        is the bottom edge.
+*     axis
+*        The index of the axis to which "value" refers. The tick mark extends
+*        parallel to this axis.
+*     value
+*        The physical axis value at which to place the tick mark.
+*     gap
+*        Pointer to array of two values holding the gap between major
+*        tick marks on the two axes.
+*     tklen
+*        The length of the tick, in graphics units.
+*     majtick
+*        Non-zero if the tick mark being drawn is a major tick, and zero
+*        if it is a minor tick.
+*     save
+*        If non-zero, the tick mark position will be saved in the Plot structure.
+*     pstatics
+*        Address of a pointer to a structure holding values for variables
+*        which were statically defined within this function prior to the
+*        thread-safe version of AST. If the pointer is supplied as NULL,
+*        then a new structure is allocated and initialised.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double *cross;         /* Pointer to crossings information */
+   double *vx;            /* Pointer to next X vector component value */
+   double *vy;            /* Pointer to next Y vector component value */
+   double *x;             /* Pointer to next X value */
+   double *y;             /* Pointer to next Y value */
+   double xe;             /* X at tick end */
+   double ye;             /* Y at tick end */
+   int j;                 /* Crossing index */
+   int ncross;            /* No. of crossings of tick value and edge */
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* See where the current major tick value crosses the edge. */
+   ncross = EdgeCrossings( this, edge, axis, value, gap, &cross, pstatics,
+                           method, class, status );
+
+/* Do nothing if the supplied axis value does not occur on the specified
+   edge of the plotting area. */
+   if( ncross ){
+
+/* Draw major tick marks at the crossings. */
+      x = cross;
+      y = cross + 1;
+      vx = cross + 2;
+      vy = cross + 3;
+
+/* Draw a tick mark at each occurence of the axis value on the specified
+   edge. */
+      for( j = 0; j < ncross; j++ ){
+
+/* Check the tick mark position and directionm are defined. */
+         if( *vx != AST__BAD && *vy != AST__BAD &&
+              *x != AST__BAD &&  *y != AST__BAD ){
+
+/* Ensure the tick mark will point into the plotting area, no matter which
+   edge it is on. First ensure the direction vector refers to a system in
+   which X increases to the left and Y increases upwards. */
+            if( this->xrev ) *vx = -*vx;
+            if( this->yrev ) *vy = -*vy;
+
+/* If necessary reverse the vector so that it points into the plotting
+   area. How to do this depends on which edge is being ticked. */
+            if( ( edge == 0 && *vx < 0.0 ) ||   /* left-hand edge */
+                ( edge == 1 && *vy > 0.0 ) ||   /* Top edge */
+                ( edge == 2 && *vx > 0.0 ) ||   /* Right-hand edge */
+                ( edge == 3 && *vy < 0.0 ) ){   /* Bottom edge */
+
+               *vx = -*vx;
+               *vy = -*vy;
+            }
+
+/* Now ensure the direction vector refers to a the native graphics system
+   taking account of any reversal of axes. */
+            if( this->xrev ) *vx = -*vx;
+            if( this->yrev ) *vy = -*vy;
+
+/* Do not draw the tick if the start of the tick is outside the bounds of
+   the axis it is labelling. */
+            if( ( ( edge == 1 || edge == 3 ) &&
+                   *x < this->xhi && *x > this->xlo ) ||
+                ( ( edge == 0 || edge == 2 ) &&
+                   *y < this->yhi && *y > this->ylo ) ) {
+
+/* Store the x and y graphics coords of the far end of the tick mark */
+               xe = *x + tklen*(*vx);
+               ye = *y + tklen*(*vy);
+
+/* Ensure the far end of the tick mark is within the bounds of the axis
+   it is labelling. If not, redice the length of the tick mark until it is.*/
+               if( edge == 1 || edge == 3 ) {  /* Top or bottom edge */
+                  if( xe > this->xhi ) {
+                     ye = *y + tklen*(*vy)*( this->xhi - *x )/(xe - *x );
+                     xe = this->xhi;
+                  } else if( xe < this->xlo ) {
+                     ye = *y + tklen*(*vy)*( this->xlo - *x )/(xe - *x );
+                     xe = this->xlo;
+                  }
+
+               } else {                        /* Left or right edge */
+                  if( ye > this->yhi ) {
+                     xe = *x + tklen*(*vx)*( this->yhi - *y )/(ye - *y );
+                     ye = this->yhi;
+                  } else if( ye < this->ylo ) {
+                     xe = *x + tklen*(*vx)*( this->ylo - *y )/(ye - *y );
+                     ye = this->ylo;
+                  }
+               }
+
+/* Draw the tick mark as a straight line of the specified length. */
+               if( save ) SaveTick( this, axis, *x, *y, majtick, status );
+               if( *x != xe || *y != ye ) {
+                  Bpoly( this, (float) *x, (float) *y, status );
+                  Apoly( this, (float) xe, (float) ye, status );
+                  Opoly( this, status );
+               }
+            }
+
+/* Move on to the next crossing. */
+            x += 4;
+            y += 4;
+            vx += 4;
+            vy += 4;
+         }
+      }
+
+/* Free the memory holding the crossings. */
+      if( cross ) cross = (double *) astFree( (void *) cross );
+
+   }
+
+/* Return. */
+   return;
+
+}
+
+static TickInfo *TickMarks( AstPlot *this, int axis, double *cen, double *gap,
+                            int *inval, GetTicksStatics **pstatics,
+                            const char *method, const char *class, int *status ){
+/*
+*  Name:
+*     TickMarks
+
+*  Purpose:
+*     Obtain a list of tick mark values and labels for a single axis in a 2-D
+*     physical coordinate Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     TickInfo *TickMarks( AstPlot *this, int axis, double *cen, double *gap,
+*                          int *inval, GetTicksStatics **pstatics,
+*                          const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     A set of tick marks values and corresponding formatted labels are
+*     found for an axis which result in all adjacent labels being different,
+*     but using the minimum number of digits of precision in the formatting.
+*     This algorithm is over-ridden if the caller has set an explicit Format
+*     string for the axis. The gap between tick marks can be specified by
+*     the caller or a default value may be found automatically.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     axis
+*        The zero-based index of the axis to use.
+*     cen
+*        Pointer to the supplied axis value at which to put a single
+*        central tick. Other ticks will be placed evenly on either side of
+*        this tick. If AST__BAD is provided, a value will be used which
+*        would put a tick at an axis value of zero. The used value is
+*        returned.
+*     gap
+*        The supplied values for the gaps between ticks on the axis. If
+*        this is AST__BAD a suitable default value will be used and
+*        returned in place of the AST__BAD value.
+*     inval
+*        A pointer to a location at which to return a flag indicating if
+*        any invalid physical coordinates were encountered while deciding on
+*        the tick values.
+*     pstatics
+*        Address of a pointer to a structure holding static data values
+*        used within the GetTicks function. A NULL pointer should be supplied
+*        on the first invocation (dynamic memory will then be allocated to
+*        hold ths structure). The memory is freed when a NULL value for
+*        "this" is supplied.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a TickInfo structure holding information about the tick
+*     marks (no. of major and minor ticks, the major tick mark values and
+*     labels). This structure should be freed, together with its contents,
+*     using astFree when it is no longer needed.
+
+*  Notes:
+*     -  The returned labels are NOT abbreviated to remove identical
+*     leading fields.
+*     -  This function allocates some static resources on its first
+*     invocation, which should be released when no longer needed, or when
+*     a different Plot is supplied, by calling this function with a NULL
+*     pointer for parameter "this". All other parameters (except axis) are
+*     ignored.
+*     -  This function assumes that the physical coordinate system is 2
+*     dimensional, and it should not be used if this is not the case.
+*     -  An error is reported if the region containing valid physical
+*     coordinates is too small to use.
+*     -  If an error has already occurred, or if this function should fail
+*     for any reason, then a NULL pointer is returned.
+*/
+
+/* Local Constants: */
+#define MAXFLD 10
+
+/* Local Variables: */
+   AstAxis *ax;        /* Pointer to the axis. */
+   AstFrame *frame;    /* Pointer to the current Frame in the Plot */
+   TickInfo *ret;      /* Pointer to the returned structure. */
+   char **labels;      /* Pointer to list of formatted labels */
+   char **newlabels;   /* Pointer to new list of shortened formatted labels */
+   char **oldlabels;   /* Pointer to old list of formatted labels */
+   char *fields[ MAXFLD ]; /* Pointers to starts of fields in a label */
+   char *old_format;   /* Original Format string */
+   char *used_fmt;     /* Copy of format string actually used */
+   const char *a;      /* Pointer to next character to consider */
+   const char *fmt;    /* Format string actually used */
+   double *ticks;      /* Pointer to major tick mark values */
+   double *minticks;   /* Pointer to minor tick mark values */
+   double cen0;        /* Supplied value of cen */
+   double junk;        /* Unused value */
+   double refval;      /* Value for other axis to use when normalizing */
+   double used_gap;    /* The gap size actually used */
+   int bot_digits;     /* Digits value which makes labels as short as possible */
+   int digits;         /* New Digits value */
+   int digset;         /* Did the format string fix the no. of digits to use? */
+   int fmtset;         /* Was a format set? */
+   int i;              /* Tick index. */
+   int nc[ MAXFLD ];   /* Lengths of fields in a label */
+   int nf;             /* Number of fields in a label */
+   int nmajor;         /* No. of major tick marks */
+   int nminor;         /* No. of minor tick marks */
+   int ok;             /* Are all adjacent labels different? */
+   int reset_fmt;      /* Re-instate the original state of the Format attribute? */
+
+
+/* If a NULL pointer has been supplied for "this", release the resources
+   allocated within GetTicks, and return. */
+   if( !this ){
+      (void) GetTicks( NULL, axis, NULL, &ticks, &nmajor, &minticks, &nminor,
+                       0, inval, &refval, pstatics, method, class, status );
+      return NULL;
+   }
+
+/* Check the global status. */
+   if( !astOK ) return NULL;
+
+/* Initialise the returned pointer. */
+   ret = NULL;
+
+/* Store the supplied value of cen. */
+   cen0 = cen ? *cen : AST__BAD ;
+
+/* Get a pointer to the current Frame from the Plot. */
+   frame = astGetFrame( this, AST__CURRENT );
+
+/* Initialise a flag to indicate that all adjacent labels are different. */
+   ok = 0;
+
+/* Initialise the pointer to the list of formatted tick mark values to
+   indicate that no memory has yet been obtained. */
+   labels = NULL;
+
+/* Initialise the pointer to a copy of the used format string to indicate
+   that no memory has yet been obtained. */
+   used_fmt = NULL;
+
+/* Get a pointer to the axis. */
+   ax = astGetAxis( frame, axis );
+
+/* See if a value has been set for the axis Format. */
+   fmtset = astTestFormat( frame, axis );
+
+/* Get an initial set of tick mark values. This also establishes defaults for
+   LogTicks and LogLabel attributes, and so must be done before the
+   following block which uses the LogLabel attribute. */
+   used_gap = GetTicks( this, axis, cen, &ticks, &nmajor, &minticks, &nminor,
+                        fmtset, inval, &refval, pstatics, method, class, status );
+
+/* See if exponential labels using superscript powers are required.  */
+   old_format = NULL;
+   reset_fmt = 0;
+   if( astGetLogLabel( this, axis ) && astGetEscape( this ) &&
+       GCap( this, GRF__SCALES, 1, status ) ) {
+
+/* Save a copy of the Frame's Format value, if set. It will be
+   re-instated at the end of this function. */
+      if( fmtset ) {
+         fmt = astGetFormat( frame, axis );
+         old_format = astStore( NULL, (void *) fmt, strlen(fmt) + 1 );
+      }
+
+/* Temporarily use a format of "%&g" to get "10**x" style axis labels,
+   with super-scripted "x". */
+      astSetFormat( frame, axis, "%&g" );
+
+/* Not all subclasses of Frame support this format specifier, so format a
+   test value, and see if it has two fields, the first of which is "10".
+   If not, we cannot use log labels so re-instate the original format. */
+      nf = astFields( frame, axis, "%&g", astFormat( frame, axis, 1.0E4 ),
+                      MAXFLD, fields, nc, &junk );
+      if( nf != 2 || nc[ 0 ] != 2 || strncmp( fields[ 0 ], "10", 2 ) ) {
+         if( old_format ) {
+            astSetFormat( frame, axis, old_format );
+            old_format = astFree( old_format);
+         } else {
+            astClearFormat( frame, axis );
+         }
+
+/* If the "%&g" format is usable, note that we should reset the Format
+   back to its original state before leaving this function. */
+      } else {
+         reset_fmt = 1;
+      }
+   }
+
+/* If a value has been set for the axis Format, see if the format string
+   contains a wildcard precision specifier ".*". If so, we are free to
+   vary the number of digits used in the label in order to produce
+   distinct labels. If no value has been set for the axis Format, we are
+   also free to vary the number of digits. */
+   digset = 0;
+   if( fmtset ) {
+      fmt = astGetFormat( frame, axis );
+      if( fmt ) {
+         digset = 1;
+         a = fmt;
+         while( (a = strchr( a, '.' )) ){
+            if( *(++a) == '*' ) {
+               digset = 0;
+               break;
+            }
+         }
+      }
+   }
+
+/* If the axis precision has been specified, either through the Format string
+   or Digits value, or the Frame Digits value, we should use it so that the
+   user's attempts to get a specific result are not foiled. */
+   if( digset || astTestAxisDigits( ax ) || astTestDigits( frame ) ){
+
+/* Reserve memory to hold pointers to the formatted labels. */
+      labels = (char **) astMalloc( sizeof(char *)*(size_t)nmajor );
+
+/* Format the labels. We do not check that all adjacent labels are distinct
+   in order not to foil the users choice of format. That is, "ok" is set
+   non-zero by the call to CheckLabels, even if some identical adjacent
+   labels are found. */
+      ok = CheckLabels( this, frame, axis, ticks, nmajor, 1, labels, refval, status );
+
+/* Note the format used. */
+      fmt = astGetFormat( frame, axis );
+      if( fmt ) used_fmt = (char *) astStore( used_fmt, (void *) fmt, strlen( fmt ) + 1 );
+
+/* If no precision has been specified for the axis, we need to find a
+   Digits value which gives different labels, but without using any more
+   digits than necessary. */
+   } else if( astOK ){
+
+/* Reserve memory to hold pointers to an initial set of labels formatted
+   with the default digits value. */
+      labels = (char **) astMalloc( sizeof(char *)*(size_t)nmajor );
+
+/* Produce these default labels. */
+      CheckLabels( this, frame, axis, ticks, nmajor, 1, labels, refval, status );
+
+/* The first task is to decide what the smallest usable number of digits
+   is. Starting at the default number of digits used above to produce the
+   default labels, we reduce the number of digits until one or more of the
+   formatted labels *increases* in length. This can happen for instance if
+   printf decides to include an exponent in the label. The *absolute*
+   minimum value 1. Set this first. */
+      bot_digits = 1;
+      oldlabels = labels;
+      for( digits = astGetDigits( frame ) - 1; digits > 0; digits-- ){
+         astSetAxisDigits( ax, digits );
+
+/* CheckLabels2 formats the labels with the decreased number of digits,
+   and compares them with the old labels in "labels". If any of the new labels
+   are longer than the corresponding old labels, then a null pointer is
+   returned. Otherwise, a pointer is returned to the new set of labels. */
+         newlabels = CheckLabels2( this, frame, axis, ticks, nmajor,
+                                   oldlabels, refval, status );
+
+/* Free the old labels unless they are the orignal labels (which are
+   needed below). */
+         if( oldlabels != labels ) {
+            for( i = 0; i < nmajor; i++ ){
+               if( oldlabels[ i ] ) oldlabels[ i ] = (char *) astFree( (void *) oldlabels[ i ] );
+            }
+            oldlabels = (char **) astFree( (void *) oldlabels );
+         }
+
+/* If any of the labels got longer as a result of reducing the digits
+   value, then use the previous number of digits as the lowest possible
+   number of digits. Break out of the loop. */
+         if( !newlabels ) {
+            bot_digits = digits + 1;
+            break;
+         }
+
+/* If none of the labels got longer, we arrive here. Use the shorter labels
+   for the next pass round this loop. */
+         oldlabels = newlabels;
+      }
+
+/* Free any remaining labels. */
+      if( oldlabels && oldlabels != labels ) {
+         for( i = 0; i < nmajor; i++ ){
+            if( oldlabels[ i ] ) oldlabels[ i ] = (char *) astFree( (void *) oldlabels[ i ] );
+         }
+         oldlabels = (char **) astFree( (void *) oldlabels );
+      }
+
+/* Now loop round increasing the number of digits in the formatted labels
+   from the lowest usable value found above until all adjacent labels are
+   different. An arbitrary upper limit of 1000 is used for Digits to stop it
+   looping for ever. */
+      for( digits = bot_digits; digits < 1000; digits++ ){
+
+/* Store the new Digits value. */
+         astSetAxisDigits( ax, digits );
+
+/* Free memory used to hold the current set of labels. A new set will be
+   created by the following call to CheckLabels. */
+         if( labels ) {
+            for( i = 0; i < nmajor; i++ ) labels[ i ] = astFree( labels[ i ] );
+         }
+
+/* Break out of the loop if a Digits value has been found which results
+   in all adjacent labels being different. Note the format used (we know
+   the Format attribute is currently unset, but the default Format string
+   reflects the current value of the Digits attribute). */
+         if( CheckLabels( this, frame, axis, ticks, nmajor, 0, labels, refval, status ) ) {
+            ok = 1;
+            fmt = astGetFormat( frame, axis );
+            used_fmt = (char *) astStore( NULL, (void *) fmt, strlen( fmt ) + 1 );
+            break;
+         }
+      }
+
+/* Clear the Digits value. */
+      astClearAxisDigits( ax );
+   }
+
+/* Annul the pointer to the Axis. */
+   ax = astAnnul( ax );
+
+/* Re-instate the original format specifier if required. */
+   if( reset_fmt ) {
+      if( old_format ) {
+         astSetFormat( frame, axis, old_format );
+         old_format = astFree( old_format);
+      } else {
+         astClearFormat( frame, axis );
+      }
+   }
+
+/* If suitable labels were found... */
+   if( ok && astOK ) {
+
+/* Store the used gap size. */
+      *gap = used_gap;
+
+/* If the caller has specified the number of minor tick marks to use,
+   use the specified value rather than the value found above. */
+      if( astTestMinTick( this, axis ) ){
+         nminor = astGetMinTick( this, axis );
+      }
+
+/* Allocate memory for the returned structure. */
+      ret = (TickInfo *) astMalloc( sizeof( TickInfo ) );
+
+/* If the pointer can be used, store the information. */
+      if( astOK ){
+         ret->nmajor = nmajor;
+         ret->nminor = nminor;
+         ret->ticks = ticks;
+         ret->minticks = minticks;
+         ret->labels = labels;
+         ret->fmt = used_fmt;
+         used_fmt = NULL;
+         ret->start = NULL;
+         ret->length = NULL;
+         ret->nsect = 0;
+         ret->gap = used_gap;
+      }
+
+/* If no suitable labels were found report an error. */
+   } else if( astOK ){
+      if( fmtset ){
+         astError( AST__PLFMT, "%s(%s): No numerical labels can be produced "
+                   "for axis %d using the supplied %s format string '%s'.", status,
+                   method, class, axis + 1, astGetClass( frame ),
+                   astGetFormat( frame, axis ) );
+      } else {
+         astError( AST__PLFMT, "%s(%s): No suitable format can be found to "
+                   "produce numerical labels for axis %d of a %s.", status,
+                   method, class, axis + 1, astGetClass( frame ) );
+      }
+   }
+
+/* Release any remaining resources. */
+   frame = astAnnul( frame );
+   if( used_fmt ) used_fmt = astFree( used_fmt );
+
+/* If an error has occurred, release the returned information. */
+   if( !astOK ){
+      ticks = (double *) astFree( (void *) ticks );
+      minticks = (double *) astFree( (void *) minticks );
+      if( labels ){
+         for( i = 0; i < nmajor; i++ ) {
+            labels[ i ] = (char *) astFree( (void *) labels[ i ] );
+         }
+         labels = (char **) astFree( (void *) labels );
+         if( ret ) (void ) astFree( (void *) ret->fmt );
+      }
+      ret = (TickInfo *) astFree( (void *) ret );
+   }
+
+/* Return the structure. */
+   return ret;
+
+/* Undefine local constants. */
+#undef MAXFLD
+
+}
+
+static int TraceBorder( AstPlot *this, AstMapping *map, double xlo, double xhi,
+                        double ylo, double yhi, int dim, double tol,
+                        int edges[ 4 ], const char *method, const char *class,
+                        int *status ) {
+/*
+*  Name:
+*     TraceBorder
+
+*  Purpose:
+*     Trace the boundary between good and bad physical coordinates through a
+*     fine grid.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int TraceBorder( AstPlot *this, AstMapping *map, double xlo, double xhi,
+*                      double ylo, double yhi, int dim, double tol,
+*                      int edges[ 4 ], const char *method, const char *class,
+*                      int *status ) {
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     A rectangular grid of points in graphics coords is created covering
+*     the region specified by (xlo,xhi,ylo,yhi), using "dim" points along
+*     each axis. This grid of points is converted into physical (WCS)
+*     coords, and a flag is associatred with each point saying whether
+*     the WCS coords are good or bad. The cells in this grid are then
+*     scanned from bottom left to top right in raster fashion (each cell
+*     has a grid point at each of its 4 corners). If a cell has a mix of
+*     bad and good corners, the good/bad boundary must pass through it.
+*     If the grid is sufficiently fine (as defined by "tol") then this
+*     function draws a single straight line through each cell as an
+*     approximation to the good bad boundary. This line joins the centres
+*     of the two cells edges through which the boundary passes (as
+*     indicated by the fact that one end of the edge has good WCS coords
+*     and the other end has bad WCS coords). If the grid is not
+*     sufficiently fine to meet the "tol" requirement, then this function
+*     is invoked recursively to draw the curve through each cell through
+*     which the boundary passes.
+
+*  Parameters:
+*     this
+*        The plot.
+*     map
+*        The Graphics->WCS mapping.
+*     xlo
+*        The lower bounds on the graphics X axis of the rectangle being
+*        considered.
+*     xhi
+*        The upper bounds on the graphics X axis of the rectangle being
+*        considered.
+*     ylo
+*        The lower bounds on the graphics Y axis of the rectangle being
+*        considered.
+*     yhi
+*        The upper bounds on the graphics Y axis of the rectangle being
+*        considered.
+*     dim
+*        The number of points along one edge of the fine grid.
+*     tol
+*        The plotting tolerance. Once each cell is smaller than this
+*        distance (in graphics coords), the cell is drawn. Otherwise,
+*        this function is invoked recursively to draw the cell using a
+*        finer grid.
+*     edges
+*        A pointer to an array of 4 int, in which will be returned
+*        flags indicating if the good/bad boundary intersects any of the
+*        edges of the grid. These flags are stored in the order left,
+*        top, right, bottom.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;
+   AstPointSet *pset2;
+   double **ptr2;
+   double *px1;
+   double *px2;
+   double *py1;
+   double *py2;
+   double cxhi;
+   double cxlo;
+   double cyhi;
+   double cylo;
+   double dx;
+   double dy;
+   float xc;
+   float yc;
+   int *bndry;
+   int *drawn;
+   int bad1;
+   int bad2;
+   int bad3;
+   int bad4;
+   int icell;
+   int icol;
+   int irow;
+   int lastcell;
+   int ncell;
+   int recurse;
+   int result;
+   int sedges[ 4 ];
+   int totcells;
+
+/* Initialise returned edge flags */
+   edges[ 0 ] = 0;
+   edges[ 1 ] = 0;
+   edges[ 2 ] = 0;
+   edges[ 3 ] = 0;
+
+/* Initialise the returned flag to indicate that no bad regions were
+   found. */
+   result = 0;
+
+/* Check the global status. */
+   if ( !astOK ) return result;
+
+/* Create a grid of graphics and WCS coords covering the specified region
+   of graphics coords. "ptr2" is used to access the WCS coords at each
+   point in the grid. */
+   ptr2 = MakeGrid( this, NULL, map, 0, dim, xlo, xhi, ylo, yhi,
+                    2, &pset1, &pset2, 0, method, class, status );
+
+/* The number of cells along each axis of the grid is one less than the
+   number of points. Also get the number of cells in the whole grid. */
+   ncell = dim - 1;
+   totcells = ncell*ncell;
+
+/* Store the dimensions of each cell in graphics coords. */
+   dx = ( xhi - xlo )/ncell;
+   dy = ( yhi - ylo )/ncell;
+
+/* Set a flag indicating if the cell size is larger than the required
+   plotting tolerance. If so, we will call this function recursively to
+   draw the curve using a finer grid. */
+   recurse = ( dx > tol || dy > tol );
+
+/* If we have not yet reached the plotting tolerance, allocate work arrays
+   with one element for each cell in the grid. */
+   if( recurse ) {
+      bndry = astMalloc( sizeof( int )*totcells );
+      drawn = astMalloc( sizeof( int )*totcells );
+   } else {
+      bndry = NULL;
+      drawn = NULL;
+   }
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* If required, initialise work arrays to hold zero. */
+      if( recurse ) {
+
+/* Initialise "boundary passes through cell" flags to zero. */
+         memset( bndry, 0, sizeof( int )*totcells );
+
+/* Initialise "cell has been drawn" flags to zero. */
+         memset( drawn, 0, sizeof( int )*totcells );
+      }
+
+/* Store the Y graphics coords at the bottom and top of the first row. */
+      cylo = ylo;
+      cyhi = ylo + dy;
+
+/* Store pointers to the physical coords at the bottom left corner of the
+   first cell in the first row. */
+      px1 = ptr2[ 0 ];
+      py1 = ptr2[ 1 ];
+
+/* Store pointers to the physical coords at the top left corner of the
+   first cell in the first row. */
+      px2 = px1 + dim;
+      py2 = py1 + dim;
+
+/* Store the index of the last cell in a row or column. */
+      lastcell = ncell - 1;
+
+/* Initialise index of next cell. */
+      icell = 0;
+
+/* Loop round every row of cells in this grid. */
+      for( irow = 0; irow < ncell; irow++ ) {
+
+/* See if the physical coords are bad at the bottom left corner of the
+   first cell in this row. At the same time, increment the pointers so
+   they refer to the bottom right corner of the first cell in this row. */
+         bad1 = ( *px1 == AST__BAD || *py1 == AST__BAD );
+
+/* Increment the pointers. Do not do it in the above "if" statement since
+   the or (!!) means that the second expression may never be evaluated. */
+         px1++;
+         py1++;
+
+/* See if the physical coords are bad at the top left corner of the
+   first cell in this row. At the same time, increment the pointers so
+   they refer to the top right corner of the first cell in this row. */
+         bad2 = ( *px2 == AST__BAD || *py2 == AST__BAD );
+         px2++;
+         py2++;
+
+/* Loop round every cell in the current row of cells. */
+         for( icol = 0; icol < ncell; icol++, icell++ ) {
+
+/* See if the physical coords are bad at the bottom right corner of the
+   current cell in this row. At the same time, increment the pointers so
+   they refer to the bottom right corner of the next cell in this row. */
+            bad3 = ( *px1 == AST__BAD || *py1 == AST__BAD );
+            px1++;
+            py1++;
+
+/* See if the physical coords are bad at the top right corner of the
+   current cell in this row. At the same time, increment the pointers so
+   they refer to the top right corner of the next cell in this row. */
+            bad4 = ( *px2 == AST__BAD || *py2 == AST__BAD );
+            px2++;
+            py2++;
+
+/* Set the returned flag non-zero if any invalidpositions are found. */
+            if( bad1 || bad2 || bad3 || bad4 ) result = 1;
+
+/* If there are a mixture of good and bad corners, the good/bad boundary
+   must pass through the current cell. */
+            if( bad2 != bad1 || bad3 != bad1 || bad4 != bad1 ) {
+
+/* If we have not yet reached the required plotting tolerance, set a flag
+   to indicate that the boundary should be plotted through this cell
+   using a recirsive call to this function. */
+               if( recurse ) {
+                  bndry[ icell ] = 1;
+
+/* If we have reached the required plotting tolerance, draw the boundary
+   as a straight line between the centres of the edges through which the
+   boundary enteres and leaves the current cell. */
+               } else {
+
+/* Get the upper and lower graphics X bounds of the current cell. */
+                  cxlo = xlo + icol*dx;
+                  cxhi = cxlo + dx;
+
+/* If an edge of the current cell has good coords at one end but bad
+   coords at the other, the boundary is assumed to pass through the edge
+   at its centre. Normally, we expect only two cell edges to have this
+   property (i.e the boundary enters the cell through one edge and leaves
+   through the other). However, sometimes all four edges may have this
+   property. In this case, two sections of the boundary must pass through
+   the cell, and there is no way of knowing which edges connect together
+   (short of further recursion), and we arbitrarily decide to join opposite
+   edges. */
+                  if( bad1 == bad4 && bad2 == bad3 ) {
+
+/* Draw a horizontal line through the cell centre */
+                     yc = 0.5*( cylo + cyhi );
+                     Bpoly( this, (float) cxlo, yc, status );
+                     Apoly( this, (float) cxhi, yc, status );
+
+/* Draw a vertical line through the cell centre */
+                     xc = 0.5*( cxlo + cxhi );
+                     Bpoly( this, xc, (float) cylo, status );
+                     Apoly( this, xc, (float) cyhi, status );
+
+/* If the boundary passes through the left hand edge, it must also have
+   passed through the right edge of the previous cell in the row (unless
+   this is the first cell in the row), so we do not need to begin a new
+   polyline (we can just extend the existing polyline). */
+                  } else if( bad1 != bad2 ) {
+
+/* If this is the first cell in the row, begin a new polyline. */
+                     yc = 0.5*( cylo + cyhi );
+                     if( icol == 0 ) Bpoly( this, (float) cxlo, yc, status );
+
+/* and through the top edge, draw a line between the centres of the left
+   and top edges. */
+                     if( bad2 != bad4 ) {
+                        xc = 0.5*( cxlo + cxhi );
+                        Apoly( this, xc, (float) cyhi, status );
+
+/* or through the right edge, draw a line between the centres of the left
+   and right edges. */
+                     } else if( bad3 != bad4 ) {
+                        Apoly( this, (float) cxhi, yc, status );
+
+/* Otherwise, draw a line between the centres of the left and bottom edges. */
+                     } else {
+                        xc = 0.5*( cxlo + cxhi );
+                        Apoly( this, xc, (float) cylo, status );
+                     }
+
+/* If the boundary passes through the top edge (we do not need to check
+   the left edge because that was done above)... */
+                  } else if( bad4 != bad2 ) {
+
+/* and through the right edge, draw a line between the centres of the top
+   and right edges. */
+                     if( bad3 != bad4 ) {
+                        xc = 0.5*( cxlo + cxhi );
+                        yc = 0.5*( cylo + cyhi );
+                        Bpoly( this, xc, (float) cyhi, status );
+                        Apoly( this, (float) cxhi, yc, status );
+
+/* Otherwise, draw a line between the centres of the top and bottom edges. */
+                     } else {
+                        xc = 0.5*( cxlo + cxhi );
+                        Bpoly( this, xc, (float) cyhi, status );
+                        Apoly( this, xc, (float) cylo, status );
+                     }
+
+/* If the boundary passes through the right edge it must also pass
+   throught the bottom edge since all other combinations will have been
+   trapped above. */
+                  } else {
+                     xc = 0.5*( cxlo + cxhi );
+                     yc = 0.5*( cylo + cyhi );
+                     Bpoly( this, xc, (float) cylo, status );
+                     Apoly( this, (float) cxhi, yc, status );
+                  }
+
+/* If the current cell is on the edge of the grid, set flags in the
+   returned "edges" array to indicate that the boundary passes out of
+   the grid on the appropriate edge. */
+                  if( icol == 0 ) {
+                     if( bad1 != bad2 ) edges[ 0 ] = 1;  /* Left edge */
+                  } else if( icol == lastcell ) {
+                     if( bad3 != bad4 ) edges[ 2 ] = 1;  /* Right edge */
+                  }
+
+                  if( irow == 0 ) {
+                     if( bad1 != bad3 ) edges[ 3 ] = 1;  /* Bottom edge */
+                  } else if( irow == lastcell ) {
+                     if( bad2 != bad4 ) edges[ 1 ] = 1;  /* Top edge */
+                  }
+               }
+            }
+
+/* The flags for the right hand corners of the current cell can be
+   re-used as the flags for the left hand corners of the next cell. */
+            bad1 = bad3;
+            bad2 = bad4;
+         }
+
+/* Store the Y graphics coords at the bottom and top of the next row. */
+         cylo = cyhi;
+         cyhi = cylo + dy;
+      }
+
+/* If we have not yet reached the required plotting tolerance, call this
+   function recursively to draw the boundary through the cells identified
+   above. On each pass through this loop, we may discover more boundary
+   cells in the grid, in addition to those found above. Continue looping
+   until no further boundary cells are found. */
+      while( recurse ) {
+
+/* Assume that the current pass though this loop will result in all boundary
+   cells being draw, in which case we can then leave the loop. */
+         recurse = 0;
+
+/* Store the Y graphics coords at the bottom and top of the first row. */
+         cylo = ylo;
+         cyhi = ylo + dy;
+
+/* Initialise the cell index */
+         icell = 0;
+
+/* Loop round every row of cells in this grid. */
+         for( irow = 0; irow < ncell; irow++ ) {
+
+/* Loop round every cell in the current row of cells. */
+            for( icol = 0; icol < ncell; icol++, icell++ ) {
+
+/* If the good/bad boundary passes through the current cell we need to
+   draw it unless it has already been drawn. */
+               if( bndry[ icell ] && ! drawn[ icell ] ){
+
+/* Get the upper and lower graphics X bounds of the current cell. */
+                  cxlo = xlo + icol*dx;
+                  cxhi = cxlo + dx;
+
+/* Call this function recursively to draw the boundary through the current
+   cell, setting the returned flag non-zero if any bad positions are found. */
+                  if( TraceBorder( this, map, cxlo, cxhi, cylo, cyhi, 3, tol,
+                                   sedges, method, class, status ) ) result = 1;
+
+/* The boundary may have passed out of the current cell and then back
+   into the cell on the same edge (i.e. a small loop that pokes out into
+   a neighbouring cell). Such neighbouring cells may not have been
+   identified by the earlier section of this function, so we now ensure
+   that any such cells are flagged as boundary cells. */
+
+/* If the boundary passed out of the left edge of the cell... */
+                  if( sedges[ 0 ] ) {
+
+/* If the current cell is at the left edge of the grid, indicate that the
+   boundary passes out of the left edge of the grid. */
+                     if( icol == 0 ) {
+                        edges[ 0 ] = 1;  /* Left edge */
+
+/* Otherwise, if the left hand neighbour of the current cell is not
+   flagged as a boundary cell, flag it now and indicate that another pass
+   though the loop is needed to draw the extra cell. */
+                     } else if( ! bndry[ icell - 1 ] ) {
+                        bndry[ icell - 1 ] = 1;
+                        recurse = 1;
+                     }
+                  }
+
+/* If the boundary passed out of the top edge of the cell... */
+                  if( sedges[ 1 ] ) {
+
+/* If the current cell is at the top edge of the grid, indicate that the
+   boundary passes out of the top edge of the grid. */
+                     if( irow == lastcell ) {
+                        edges[ 1 ] = 1;  /* Top edge */
+
+/* Otherwise, ensure that the upper neighbour of the current cell is
+   flagged as a boundary cell. */
+                     } else {
+                        bndry[ icell + ncell ] = 1;
+                     }
+                  }
+
+/* If the boundary passed out of the right edge of the cell... */
+                  if( sedges[ 2 ] ) {
+
+/* If the current cell is at the right edge of the grid, indicate that the
+   boundary passes out of the right edge of the grid. */
+                     if( icol == lastcell ) {
+                        edges[ 2 ] = 1;  /* Right edge */
+
+/* Otherwise, ensure that the right hand neighbour of the current cell is
+   flagged as a boundary cell. */
+                     } else {
+                        bndry[ icell + 1 ] = 1;
+                     }
+                  }
+
+/* If the boundary passed out of the bottom edge of the cell... */
+                  if( sedges[ 3 ] ) {
+
+/* If the current cell is at the bottom edge of the grid, indicate that the
+   boundary passes out of the bottom edge of the grid. */
+                     if( irow == 0 ) {
+                        edges[ 3 ] = 1;  /* Bottom edge */
+
+/* Otherwise, if the lower neighbour of the current cell is not flagged
+   as a boundary cell, flag it now and indicate that another pass though
+   the loop is needed to draw the extra cell. */
+                     } else if( ! bndry[ icell - ncell ] ) {
+                        bndry[ icell - ncell ] = 1;
+                        recurse = 1;
+                     }
+                  }
+
+/* Indicate this cell has been drawn. */
+                  drawn[ icell ] = 1;
+               }
+            }
+
+/* Store the Y graphics coords at the bottom and top of the next row. */
+            cylo += dy;
+            cyhi = cylo + dy;
+         }
+      }
+   }
+
+/* Free resources */
+   bndry = astFree( bndry );
+   drawn = astFree( drawn );
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Trans( AstPlot *this, AstFrame *frm, AstMapping *mapping,
+                           AstPointSet *in, int forward, AstPointSet *out,
+                           int norm, const char *method, const char *class, int *status ) {
+/*
+*  Name:
+*     Trans
+
+*  Purpose:
+*     Use a Mapping to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     AstPointSet *Trans( AstPlot *this, AstFrame *frm, AstMapping *mapping,
+*                         AstPointSet *in, int forward, AstPointSet *out,
+*                         int norm, const char *method, const char *class )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This performs the same task as the protected method astTransform
+*     but uses the astTransform method for the supplied Mapping instead
+*     the parent method for the Plot. This allows the Mapping to be
+*     extracted from the Plot using astGetMapping once, rather than every
+*     time a mapping is performed.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot (only used to access clipping attributes and
+*        other methods).
+*     frm
+*        Pointer to the Current Frame in the Plot. If this is NULL, then
+*        a pointer for the Current Frame is found within this function if
+*        required (i.e. if "forward" and "norm" are both non-zero).
+*     mapping
+*        Pointer to the Mapping extracted from the Plot. If this is NULL, then
+*        a pointer for the base->current Mapping is found within this function.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate
+*        transformation should be applied while a zero value requests the
+*        inverse transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     norm
+*        The normalisation of returned physical coordinates is only done
+*        if "norm" is non-zero. Otherwise they are left as returned by
+*        astTransform.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  Clipping is only performed as set up using the astClip method.
+*     In particular, the clipping specified by the arguments to the astPlot
+*     constructor function is NOT performed. This is done in order to improve
+*     the efficiency of the curve drawing method astGridLine.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the Plot being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstFrame *cfr;                /* Pointer to the Current Frame */
+   AstFrame *fr;                 /* Pointer to the clipping Frame */
+   AstMapping *map;              /* Pointer to output->clip mapping */
+   AstPointSet *clip;            /* Positions in clipping Frame */
+   AstPointSet *result;          /* Positions in output Frame */
+   double **ptr_clip;            /* Pointer to clipping Frame data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *work;                 /* Pointer to array holding an o/p position */
+   double axval;                 /* Axis value in clipping frame */
+   double lbnd;                  /* Lower bound on current clipping axis */
+   double ubnd;                  /* Upper bound on current clipping axis */
+   int axin;                     /* Is the axis value within the allowed range? */
+   int clip_norm;                /* Normalise the clipping positions? */
+   int clip_or;                  /* Combine axes using a logical OR? */
+   int clipit;                   /* Should the current point be clipped? */
+   int i;                        /* Point index */
+   int iframe;                   /* Validated index for clipping Frame */
+   int j;                        /* Axis index */
+   int naxes;                    /* Number of axes in clipping Frame */
+   int ncoord_out;               /* Number of coordinates per output point */
+   int npoint;                   /* Number of points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Ensure we have a Mapping */
+   if( !mapping ) mapping = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent FrameSet class. */
+   result = astTransform( mapping, in, forward, out );
+
+/* Get the dimensions of the returned data, and an array of pointers to
+   the axis values. */
+   ncoord_out = astGetNcoord( result );
+   npoint = astGetNpoint( result );
+   ptr_out = astGetPoints( result );
+
+/* If we have done a forward mapping, we now normalise the returned physical
+   positions if required using the astNorm method for the supplied object. */
+   if( forward && norm ){
+
+/* If no Frame was supplied, get a pointer to the Current Frame. Otherwise,
+   use the supplied pointer. */
+      if( !frm ) {
+         cfr = astGetFrame( this, AST__CURRENT );
+      } else {
+         cfr = frm;
+      }
+
+/* Get work space to hold a single positions. */
+      work = (double *) astMalloc( sizeof(double)*(size_t)ncoord_out );
+
+/* Check the work space and axis pointers can be used. */
+      if( astOK ){
+
+/* Now loop through every position, copying the axis values to the work array,
+   normalising them using astNorm, and copying them back to the returned
+   PointSet. */
+         for( i = 0; i < npoint; i++ ){
+            for( j = 0; j < ncoord_out; j++ ) work[ j ] = ptr_out[ j ][ i ];
+            astNorm( cfr, work );
+            for( j = 0; j < ncoord_out; j++ ) ptr_out[ j ][ i ] = work[ j ];
+         }
+      }
+
+/* Free the work space. */
+      work = (double *) astFree( (void *) work );
+
+/* Annul the pointer to the Current Frame if it was obtained in this
+   function. */
+      if( !frm ) cfr = astAnnul( cfr );
+
+   }
+
+/* Clipping is only performed if the bounds of a clipping region are
+   available for both axes. */
+   if( this->clip_lbnd && this->clip_ubnd ){
+
+/* Validate and translate the index of the clipping Frame. */
+      iframe = astValidateFrameIndex( this, this->clip_frame, method );
+
+/* Obtain a pointer to the clipping Frame and determine how many axes it
+   has. */
+      fr = astGetFrame( this, iframe );
+      naxes = astGetNaxes( fr );
+
+/* Report an error if the number of bounds does not equal the number of
+   axes in the clipping Frame. */
+      if( astOK && naxes != this->clip_axes ){
+         astError( AST__CLPAX, "%s%s): The supplied %s specifies clipping "
+                   "in %d dimensions, but the clipping Frame ('%s') has "
+                   "%d axes.", status, method, class, class, this->clip_axes,
+                   astGetTitle( fr ), naxes );
+      }
+
+/* Set a flag indicating if the coordinates in the clipping frame need to
+   be normalised. */
+      clip_norm = 1;
+
+/* We now obtain a pointer to a PointSet holding the corresponding
+   coordinates in the clipping frame.  If the clipping frame is the
+   base frame, then take a clone of the PointSet holding base frame
+   coordinates. */
+      if( iframe == astGetBase( this ) ){
+         if( forward ){
+            clip = astClone( in );
+         } else {
+            clip = astClone( result );
+         }
+
+/* If the clipping frame is the current frame, then take a clone of the
+   PointSet holding current coordinates. Note, if the returned physical
+   coordinates have already been normalised, we don't need to normalise
+   the clipping coordinates. */
+      } else if( iframe == astGetCurrent( this ) ){
+         if( forward ){
+            clip = astClone( result );
+            if( norm ) clip_norm = 0;
+         } else {
+            clip = astClone( in );
+         }
+
+/* If the clipping Frame is neither the base nor the current Frame, we need
+   to map the returned normalised points into the clipping Frame. */
+      } else {
+         if( forward ){
+            map = astGetMapping( this, AST__CURRENT, iframe );
+         } else {
+            map = astGetMapping( this, AST__BASE, iframe );
+         }
+         clip = astTransform( map, result, 1, NULL );
+         map = astAnnul( map );
+       }
+
+/* Get a pointer to the coordinate data in the clipping Frame. */
+      ptr_clip = astGetPoints( clip );
+
+/* If necessary, normalise the coordinates in the clipping frame. */
+      if( clip_norm ){
+
+/* Get work space to hold a single position. */
+         work = (double *) astMalloc( sizeof(double)*(size_t)naxes );
+
+/* Check the work space and axis pointers can be used. */
+         if( astOK ){
+
+/* Now loop through every position, copying the axis values to the work array,
+   normalising them using astNorm, and copying them back to the clipping
+   PointSet. */
+            for( i = 0; i < npoint; i++ ){
+               for( j = 0; j < naxes; j++ ) work[ j ] = ptr_clip[ j ][ i ];
+               astNorm( fr, work );
+               for( j = 0; j < naxes; j++ ) ptr_clip[ j ][ i ] = work[ j ];
+            }
+         }
+
+/* Free the work space. */
+         work = (double *) astFree( (void *) work );
+
+      }
+
+/* If all has gone ok, we will now clip the returned points. */
+      if( astOK ){
+
+/* Get the logical operation to be used to determine if a point is to be
+   clipped. A zero value means that a logical AND is to be performed
+   between the axes (i.e. all axes must be within the supplied bounds for a
+   point to be retained). A non-zero value means that a logical OR is to be
+   performed between the axes (i.e. only a single axis need be within the
+   supplied bounds for a point to be retained). */
+         clip_or = astGetClipOp( this );
+
+/* Do each point in turn. */
+         for( j = 0; j < npoint; j++ ){
+
+/* If all axes must fall within the supplied range to avoid the point being
+   clipped (i.e. if clip_or is 0), then assume initially that the point
+   is not to be clipped. This will be toggled as soon as the first
+   out-of-bounds point is found. If, on the other hand, the point is
+   only clipped if all axis values are out-of-bounds, then assume
+   initially that the point is to be clipped. This will be toggled as
+   soon as the first axis value is found which is not out-of-bounds. */
+            clipit = clip_or;
+
+/* Check each axis value for the current point. */
+            for( i = 0; i < naxes; i++ ){
+               axval =  ptr_clip[ i ][ j ];
+
+/* Chekc that it is not bad. */
+               if( axval != AST__BAD ){
+
+/* Store the bounds of the clipping volume on this axis. */
+                  lbnd = this->clip_lbnd[ i ];
+                  ubnd = this->clip_ubnd[ i ];
+
+/* Set a flag indicating if the axis value is within the specified range.
+   If the supplied bounds are reversed, they specify the range to exclude,
+   otherwise they specify the range to include.  */
+                  if( lbnd <= ubnd ){
+                     axin = ( axval >= lbnd && axval <= ubnd );
+                  } else {
+                     axin = ( axval < ubnd || axval > lbnd );
+                  }
+
+/* If the point is within the range and only one such point is
+   required to avoid the point being clipped, indicate that the point
+   should not be clipped, and leave the loop. */
+                  if( axin && clip_or ){
+                     clipit = 0;
+                     break;
+
+/* If the point is not within the range and we only one such point is
+   required to cause the point to be clipped, indicate that the point
+   should be clipped, and leave the loop. */
+                  } else if( !axin && !clip_or ){
+                     clipit = 1;
+                     break;
+                  }
+
+/* Clip the point if any axis value is bad in the clipping Frame. */
+               } else {
+                  clipit = 1;
+                  break;
+               }
+
+            }
+
+/* If the point is to be clipped, set all returned axis values bad. */
+            if( clipit ) {
+               for( i = 0; i < naxes; i++ ) ptr_out[ i ][ j ] = AST__BAD;
+            }
+
+         }
+
+      }
+
+/* Annul the PointSet holding clipping Frame positions. */
+      if( clip ) clip = astAnnul( clip );
+
+/* Annul the clipping Frame pointer. */
+      fr = astAnnul( fr );
+
+    }
+
+/* If an error has occurred, annul the result. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Use a Plot to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out )
+
+*  Class Membership:
+*     Plot member function (over-rides the astTransform protected
+*     method inherited from the FrameSet class).
+
+*  Description:
+*     This function takes a Plot and a set of points encapsulated in a
+*     PointSet and transforms the points from graphics coordinates to
+*     physical coordinates (in the forward direction). If the returned
+*     positions are physical coordinates (i.e. if a forward mapping is
+*     performed) they are normalised using the astNorm method of the supplied
+*     Plot. The returned axis values are set to AST__BAD for any positions
+*     which are outside the clipping volume set up by the astClip method.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate
+*        transformation should be applied while a zero value requests the
+*        inverse transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  Clipping is only performed as set up using the astClip method.
+*     In particular, the clipping specified by the arguments to the astPlot
+*     constructor function is NOT performed. This is done in order to improve
+*     the efficiency of the curve drawing method astGridLine.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the Plot being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstMapping *map;              /* Pointer to the mapping */
+   AstPointSet *result;          /* Positions in output Frame */
+   AstPlot *plot;                /* The Plot */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the Plot. */
+   plot = (AstPlot *) this;
+
+/* Get the Mapping from the base to the current Frame. */
+   map = astGetMapping( plot, AST__BASE, AST__CURRENT );
+
+/* Do the transformation. */
+   result = Trans( plot, NULL, map, in, forward, out, 1, "astTransform",
+                   astGetClass( this ), status );
+
+/* Annul the mapping. */
+   map = astAnnul( map );
+
+/* If an error has occurred, annul the result. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+
+}
+
+static double Typical( int n, double *value, double lolim, double hilim,
+                       double *width, int *status ) {
+/*
+*  Name:
+*     Typical
+
+*  Purpose:
+*     Return a typical value within the supplied array of values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     double Typical( int n, double *value, double lolim, double hilim,
+*                     double *width, int *status  )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This function locates the approximate mode of the supplied values,
+*     and returns one of the supplied values which is close to the modal
+*     value. Values outside an indicated range are ignored.
+
+*  Parameters:
+*     n
+*        The number of data values.
+*     value
+*        A pointer to an array of "n" values.
+*     lolim
+*        Values less than lolim are ignored. Supply as -DBL_MAX if there
+*        is no lower limit.
+*     hilim
+*        Values greater than hilim are ignored. Supply as DBL_MAX if there
+*        is no upper limit.
+*     width
+*        Pointer to a double in which to return the width (i,e, data range)
+*        of the non-empty histogram cells. This is an estimate of the
+*        range of used values in the supplied array. NULL may be supplied.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A typical value from the supplied array. AST__BAD is returned only
+*     if an error has occurred, or if all the supplied values are AST__BAD
+*     or outside the specified range.
+
+*/
+
+/* Local Variables: */
+   double *a;             /* Pointer to next value */
+   double cnt;            /* Modified count */
+   double delta;          /* Bin size */
+   double maxval;         /* Maximum supplied value */
+   double mean;           /* Mean supplied value */
+   double minval;         /* Minimum supplied value */
+   double result;         /* The returned value. */
+   double w0;             /* Rate of increase of weight with dist from edge */
+   double w1;             /* Weight for left edge */
+   double w2;             /* Weight for right edge */
+   double w;              /* Weight for this bin */
+   int *hist;             /* Pointer to first cell of histogram array */
+   int i;                 /* Loop count */
+   int ibin;              /* Bin index */
+   int maxcnt;            /* Maximum no. of values in any bin */
+   int modify;            /* Modify the effect of the edge bins? */
+   int nbin;              /* No. of bins in histogram */
+   int nc;                /* Total number of points in histogram */
+   int ngood;             /* No. of good values supplied */
+   int nonemp;            /* No. of non-empty bins in hstogram */
+
+/* Initialise. */
+   result = AST__BAD;
+   if( width ) *width = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   ibin = 0;
+
+/* Find the minimum and maximum value in the supplied array, which are
+   also within the supplied limits. Also store the first good value
+   encountered in "result". */
+   minval = DBL_MAX;
+   maxval = -DBL_MAX;
+   a = value;
+   ngood = 0;
+   for( i = 0; i < n; i++, a++ ) {
+      if( *a != AST__BAD ) {
+         if( *a >= lolim && *a <= hilim ) {
+            if( *a < minval ) minval = *a;
+            if( *a > maxval ) maxval = *a;
+            if( ngood == 0 ) result = *a;
+            ngood++;
+         }
+      }
+   }
+
+/* Initialise the returned width to the total data range. */
+   if( width && maxval != -DBL_MAX ) *width = maxval - minval;
+
+/* If less than 3 points were found, we will return the first. Otherwise, if
+   3 or more good values were found, find a typical value. */
+   if( ngood > 2 ) {
+
+/* We will form a histogram of the supplied values in order to find the
+   mode. The number of bins in this histogram is chosen so that there
+   is an average of 2 points per bin. Find the number of bins. */
+      nbin = ( ngood + 1 )/2;
+
+/* Find the bin size. If zero (i.e. if all values are equal), return the
+   first good value established above. */
+      delta = ( maxval - minval )/ nbin;
+      if( delta > 0.0 ) {
+
+/* Allocat ememory for the histogram. */
+         hist = astMalloc( sizeof(int)*(size_t)nbin );
+         if( hist ) {
+
+/* Initialise the histogram. */
+            for( i = 0; i < nbin; i++ ) hist[ i ] = 0;
+
+/* Form the histogram. Form the mean data value at the same time. */
+            mean = 0.0;
+            a = value;
+            nc = 0;
+            for( i = 0; i < n; i++, a++ ){
+               if( *a != AST__BAD ) {
+                  if( *a >= lolim && *a <= hilim ) {
+                     mean += *a;
+                     ibin = (int) ( ( *a - minval )/ delta );
+                     if( ibin == nbin ) ibin--;
+                     hist[ ibin ]++;
+                     nc++;
+                  }
+               }
+            }
+
+            mean /= ngood;
+
+/* We tend to prefer not to use reference values which are very close the
+   the limits since they can give problems with regard to normalization
+   (rounding errors can knock them over the edge), so we modify the counts
+   in each bin of the histogram to reduce the impact of bins near the edge.
+   However, we do not do this if the number of bins is very small or if
+   all the counts are in the edge bins. */
+            modify = ( nbin > 4 &&
+                      ( hist[ 0 ] + hist[ nbin - 1 ] < 0.98*ngood ) );
+
+/* Find the bin with the highest modified count. If there is more than one bin
+   with the highest modified count, choose the one which is closest to the
+   mean data value found above. Also count the number of non-empty bins. */
+            nonemp = 0;
+            maxcnt = 0;
+            w0 = nbin/2;
+            for( i = 0; i < nbin; i++ ) {
+
+               cnt = hist[ i ];
+               if( cnt ) nonemp++;
+
+               if( modify ) {
+                  w1 = i*w0;
+                  w2 = ( nbin - 1 - i )*w0;
+                  w = ( w1 < w2 ) ? w1 :w2;
+                  if( w < 1.0 ) cnt *= w;
+               }
+
+               if( cnt > maxcnt ) {
+                  maxcnt = cnt;
+                  ibin = i;
+
+               } else if( cnt == maxcnt ) {
+                  if( fabs( minval + ( i - 0.5 )*delta - mean ) <
+                      fabs( minval + ( ibin - 0.5 )*delta - mean ) ) {
+                     maxcnt = cnt;
+                     ibin = i;
+                  }
+               }
+            }
+
+/* Free the histogram memory. */
+            hist = astFree( hist );
+
+/* If required, return the width of the non-empty bins. */
+            if( width ) *width = nonemp*delta;
+
+/* Call this function recursively to refine the value, restricting
+   attention to those data values which are within the range of the bin
+   found above. */
+            if( maxcnt < nc && ibin*delta > 1000.0*DBL_EPSILON*fabs(maxval) ) {
+               minval += ibin*delta;
+               maxval = minval + delta;
+               result = Typical( n, value, minval, maxval, NULL, status );
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetUseColour( AstPlot *this, int id, int *status ) {
+/*
+*  Name:
+*     GetUseColour
+
+*  Purpose:
+*     Get the Colour value to use for a specified graphical element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int GetUseColour( AstPlot *this, int id, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This returns the Colour value for the graphical element specified by
+*     id. If an element related to a generic value is being accessed (e.g
+*     "Axes" is generic, "Axis1" and "Axis2" are not), then the colour
+*     for the first set specific value is returned. For example, if the
+*     Colour for AST__AXES_ID is requested, then the colour for AST__AXIS1_ID
+*     will be returned if set, and otherwise the colour for AST__AXIS2_ID will
+*     be returned. If AST__AXIS2_ID is not set either, then the default for
+*     AST__AXIS2_ID will be returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     id
+*        An integer specifying the graphical element to be drawn.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Colour value to use.
+
+*/
+
+/* Local Variables: */
+   int id1;        /* First genuine identifier */
+   int id2;        /* Second genuine identifier */
+   int id3;        /* Third genuine identifier */
+   int nid;        /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return NOCOLOUR;
+
+/* See if the supplied identifier is a psuedo-identifier representing two
+   or three other genuine identifiers. If so, return the value of the first
+   set genuine identifier. */
+   nid = IdFind( id, astGetNin( this ), &id1, &id2, &id3, status );
+   if( nid > 1 ) {
+      if(  astTestColour( this, id1 ) ) {
+         id = id1;
+
+      } else if(  nid > 1 && astTestColour( this, id2 ) ) {
+         id = id2;
+
+      } else if(  nid > 2 && astTestColour( this, id3 ) ) {
+         id = id3;
+
+      } else {
+         id = id1;
+      }
+   }
+
+/* Return the result. */
+   return astGetColour( this, id );
+
+}
+
+static int GetUseFont( AstPlot *this, int id, int *status ) {
+/*
+*  Name:
+*     GetUseFont
+
+*  Purpose:
+*     Get the Font value to use for a specified graphical element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int GetUseFont( AstPlot *this, int id, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This returns the Font value for the graphical element specified by
+*     id. If an element related to a generic value is being accessed (e.g
+*     "Axes" is generic, "Axis1" and "Axis2" are not), then the Font
+*     for the first set specific value is returned. For example, if the
+*     Font for AST__AXES_ID is requested, then the Font for AST__AXIS1_ID
+*     will be returned if set, and otherwise the Font for AST__AXIS2_ID will
+*     be returned. If AST__AXIS2_ID is not set either, then the default for
+*     AST__AXIS2_ID will be returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     id
+*        An integer specifying the graphical element to be drawn.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Font value to use.
+
+*/
+
+/* Local Variables: */
+   int id1;        /* First genuine identifier */
+   int id2;        /* Second genuine identifier */
+   int id3;        /* Third genuine identifier */
+   int nid;        /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return NOFONT;
+
+/* See if the supplied identifier is a psuedo-identifier representing two
+   or three other genuine identifiers. If so, return the value of the first set
+   genuine identifier. */
+   nid = IdFind( id, astGetNin( this ), &id1, &id2, &id3, status );
+   if( nid > 1 ) {
+      if(  astTestFont( this, id1 ) ) {
+         id = id1;
+
+      } else if(  nid > 1 && astTestFont( this, id2 ) ) {
+         id = id2;
+
+      } else if(  nid > 2 && astTestFont( this, id3 ) ) {
+         id = id3;
+
+      } else {
+         id = id1;
+      }
+   }
+
+/* Return the result. */
+   return astGetFont( this, id );
+
+}
+
+static double GetUseSize( AstPlot *this, int id, int *status ) {
+/*
+*  Name:
+*     GetUseSize
+
+*  Purpose:
+*     Get the Size value to use for a specified graphical element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     double GetUseSize( AstPlot *this, int id, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This returns the Size value for the graphical element specified by
+*     id. If an element related to a generic value is being accessed (e.g
+*     "Axes" is generic, "Axis1" and "Axis2" are not), then the Size
+*     for the first set specific value is returned. For example, if the
+*     Size for AST__AXES_ID is requested, then the Size for AST__AXIS1_ID
+*     will be returned if set, and otherwise the Size for AST__AXIS2_ID will
+*     be returned. If AST__AXIS2_ID is not set either, then the default for
+*     AXIS2_ID will be returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     id
+*        An integer specifying the graphical element to be drawn.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Size value to use.
+
+*/
+
+/* Local Variables: */
+   int id1;        /* First genuine identifier */
+   int id2;        /* Second genuine identifier */
+   int id3;        /* Third genuine identifier */
+   int nid;        /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return NOSIZE;
+
+/* See if the supplied identifier is a psuedo-identifier representing two
+   or three other genuine identifiers. If so, return the value of the first set
+   genuine identifier. */
+   nid = IdFind( id, astGetNin( this ), &id1, &id2, &id3, status );
+   if( nid > 1 ) {
+      if(  astTestSize( this, id1 ) ) {
+         id = id1;
+
+      } else if(  nid > 1 && astTestSize( this, id2 ) ) {
+         id = id2;
+
+      } else if(  nid > 2 && astTestSize( this, id3 ) ) {
+         id = id3;
+
+      } else {
+         id = id1;
+      }
+   }
+
+/* Return the result. */
+   return astGetSize( this, id );
+
+}
+
+static int GetUseStyle( AstPlot *this, int id, int *status ) {
+/*
+*  Name:
+*     GetUseStyle
+
+*  Purpose:
+*     Get the Style value to use for a specified graphical element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int GetUseStyle( AstPlot *this, int id, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This returns the Style value for the graphical element specified by
+*     id. If an element related to a generic value is being accessed (e.g
+*     "Axes" is generic, "Axis1" and "Axis2" are not), then the style
+*     for the first set specific value is returned. For example, if the
+*     Style for AST__AXES_ID is requested, then the style for AST__AXIS1_ID
+*     will be returned if set, and otherwise the style for AST__AXIS2_ID will
+*     be returned. If AST__AXIS2_ID is not set either, then the default for
+*     AST__AXIS2_ID will be returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     id
+*        An integer specifying the graphical element to be drawn.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Style value to use.
+
+*/
+
+/* Local Variables: */
+   int id1;        /* First genuine identifier */
+   int id2;        /* Second genuine identifier */
+   int id3;        /* Third genuine identifier */
+   int nid;        /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return NOSTYLE;
+
+/* See if the supplied identifier is a psuedo-identifier representing two
+   or three other genuine identifiers. If so, return the value of the first set
+   genuine identifier. */
+   nid = IdFind( id, astGetNin( this ), &id1, &id2, &id3, status );
+   if( nid > 1 ) {
+      if(  astTestStyle( this, id1 ) ) {
+         id = id1;
+
+      } else if(  nid > 1 && astTestStyle( this, id2 ) ) {
+         id = id2;
+
+      } else if(  nid > 2 && astTestStyle( this, id3 ) ) {
+         id = id3;
+
+      } else {
+         id = id1;
+      }
+   }
+
+/* Return the result. */
+   return astGetStyle( this, id );
+
+}
+
+static double GetUseWidth( AstPlot *this, int id, int *status ) {
+/*
+*  Name:
+*     GetUseWidth
+
+*  Purpose:
+*     Get the Width value to use for a specified graphical element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     double GetUseWidth( AstPlot *this, int id, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This returns the Width value for the graphical element specified by
+*     id. If an element related to a generic value is being accessed (e.g
+*     "Axes" is generic, "Axis1" and "Axis2" are not), then the Width
+*     for the first set specific value is returned. For example, if the
+*     Width for AST__AXES_ID is requested, then the Width for AST__AXIS1_ID
+*     will be returned if set, and otherwise the Width for AST__AXIS2_ID will
+*     be returned. If AST__AXIS2_ID is not set either, then the default for
+*     AST__AXIS2_ID will be returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     id
+*        An integer specifying the graphical element to be drawn.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Width value to use.
+
+*/
+
+/* Local Variables: */
+   int id1;        /* First genuine identifier */
+   int id2;        /* Second genuine identifier */
+   int id3;        /* Third genuine identifier */
+   int nid;        /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return NOWIDTH;
+
+/* See if the supplied identifier is a psuedo-identifier representing two
+   or three other genuine identifiers. If so, return the value of the first set
+   genuine identifier. */
+   nid = IdFind( id, astGetNin( this ), &id1, &id2, &id3, status );
+   if( nid > 1 ) {
+      if(  astTestWidth( this, id1 ) ) {
+         id = id1;
+
+      } else if(  nid > 1 && astTestWidth( this, id2 ) ) {
+         id = id2;
+
+      } else if(  nid > 2 && astTestWidth( this, id3 ) ) {
+         id = id3;
+
+      } else {
+         id = id1;
+      }
+   }
+
+/* Return the result. */
+   return astGetWidth( this, id );
+
+}
+
+static int TestUseColour( AstPlot *this, int id, int *status ) {
+/*
+*  Name:
+*     TestUseColour
+
+*  Purpose:
+*     Test the Colour value for a specified graphical element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int TestUseColour( AstPlot *this, int id, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This tests the Colour value for the graphical element specified by
+*     id. If an element related to a generic value is being accessed (e.g
+*     "Axes" is generic, "Axis1" and "Axis2" are not), then the element
+*     is considered to be set if all the corresponding specific values are
+*     set.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     id
+*        An integer specifying the graphical element to be drawn.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Colour value state (1 if set, zero otherwise).
+
+*/
+
+/* Local Variables: */
+   int ret;
+
+/* Local Variables: */
+   int id1;        /* First genuine identifier */
+   int id2;        /* Second genuine identifier */
+   int id3;        /* Third genuine identifier */
+   int nid;        /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* See if the supplied identifier is a psuedo-identifier representing two
+   or three other genuine identifiers. If so, return the logical AND of the
+   test flags for the genuine identifiers. */
+   nid = IdFind( id, astGetNin( this ), &id1, &id2, &id3, status );
+   ret = astTestColour( this, id1 );
+   if( nid > 1 ) ret = ret && astTestColour( this, id2 );
+   if( nid > 2 ) ret = ret && astTestColour( this, id3 );
+
+/* Return the result. */
+   return ret;
+
+}
+
+static int TestUseFont( AstPlot *this, int id, int *status ) {
+/*
+*  Name:
+*     TestUseFont
+
+*  Purpose:
+*     Test the Font value for a specified graphical element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int TestUseFont( AstPlot *this, int id, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This tests the Font value for the graphical element specified by
+*     id. If an element related to a generic value is being accessed (e.g
+*     "Axes" is generic, "Axis1" and "Axis2" are not), then the element
+*     is considered to be set if all the corresponding specific values are
+*     set.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     id
+*        An integer specifying the graphical element to be drawn.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Font value state (1 if set, zero otherwise).
+
+*/
+
+/* Local Variables: */
+   int ret;
+
+/* Local Variables: */
+   int id1;        /* First genuine identifier */
+   int id2;        /* Second genuine identifier */
+   int id3;        /* Third genuine identifier */
+   int nid;        /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* See if the supplied identifier is a psuedo-identifier representing two
+   or three other genuine identifiers. If so, return the logical AND of the
+   test flags for the genuine identifiers. */
+   nid = IdFind( id, astGetNin( this ), &id1, &id2, &id3, status );
+   ret = astTestFont( this, id1 );
+   if( nid > 1 ) ret = ret && astTestFont( this, id2 );
+   if( nid > 2 ) ret = ret && astTestFont( this, id3 );
+
+/* Return the result. */
+   return ret;
+
+}
+
+static int TestUseSize( AstPlot *this, int id, int *status ) {
+/*
+*  Name:
+*     TestUseSize
+
+*  Purpose:
+*     Test the Size value for a specified graphical element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int TestUseSize( AstPlot *this, int id, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This tests the Size value for the graphical element specified by
+*     id. If an element related to a generic value is being accessed (e.g
+*     "Axes" is generic, "Axis1" and "Axis2" are not), then the element
+*     is considered to be set if all the corresponding specific values are
+*     set.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     id
+*        An integer specifying the graphical element to be drawn.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Size value state (1 if set, zero otherwise).
+
+*/
+
+/* Local Variables: */
+   int ret;
+
+/* Local Variables: */
+   int id1;        /* First genuine identifier */
+   int id2;        /* Second genuine identifier */
+   int id3;        /* Third genuine identifier */
+   int nid;        /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* See if the supplied identifier is a psuedo-identifier representing two
+   or three other genuine identifiers. If so, return the logical AND of the
+   test flags for the genuine identifiers. */
+   nid = IdFind( id, astGetNin( this ), &id1, &id2, &id3, status );
+   ret = astTestSize( this, id1 );
+   if( nid > 1 ) ret = ret && astTestSize( this, id2 );
+   if( nid > 2 ) ret = ret && astTestSize( this, id3 );
+
+/* Return the result. */
+   return ret;
+
+}
+
+static int TestUseStyle( AstPlot *this, int id, int *status ) {
+/*
+*  Name:
+*     TestUseStyle
+
+*  Purpose:
+*     Test the Style value for a specified graphical element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int TestUseStyle( AstPlot *this, int id, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This tests the Style value for the graphical element specified by
+*     id. If an element related to a generic value is being accessed (e.g
+*     "Axes" is generic, "Axis1" and "Axis2" are not), then the element
+*     is considered to be set if all the corresponding specific values are
+*     set.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     id
+*        An integer specifying the graphical element to be drawn.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Style value state (1 if set, zero otherwise).
+
+*/
+
+/* Local Variables: */
+   int ret;
+
+/* Local Variables: */
+   int id1;        /* First genuine identifier */
+   int id2;        /* Second genuine identifier */
+   int id3;        /* Third genuine identifier */
+   int nid;        /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* See if the supplied identifier is a psuedo-identifier representing two
+   or three other genuine identifiers. If so, return the logical AND of the
+   test flags for the genuine identifiers. */
+   nid = IdFind( id, astGetNin( this ), &id1, &id2, &id3, status );
+   ret = astTestStyle( this, id1 );
+   if( nid > 1 ) ret = ret && astTestStyle( this, id2 );
+   if( nid > 2 ) ret = ret && astTestStyle( this, id3 );
+
+/* Return the result. */
+   return ret;
+
+}
+
+static int TestUseWidth( AstPlot *this, int id, int *status ) {
+/*
+*  Name:
+*     TestUseWidth
+
+*  Purpose:
+*     Test the Width value for a specified graphical element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int TestUseWidth( AstPlot *this, int id, int *status )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     This tests the Width value for the graphical element specified by
+*     id. If an element related to a generic value is being accessed (e.g
+*     "Axes" is generic, "Axis1" and "Axis2" are not), then the element
+*     is considered to be set if all the corresponding specific values are
+*     set.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     id
+*        An integer specifying the graphical element to be drawn.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Width value state (1 if set, zero otherwise).
+
+*/
+
+/* Local Variables: */
+   int ret;
+
+/* Local Variables: */
+   int id1;        /* First genuine identifier */
+   int id2;        /* Second genuine identifier */
+   int id3;        /* Third genuine identifier */
+   int nid;        /* Number of genuine attributes */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* See if the supplied identifier is a psuedo-identifier representing two
+   or three other genuine identifiers. If so, return the logical AND of the
+   test flags for the genuine identifiers. */
+   nid = IdFind( id, astGetNin( this ), &id1, &id2, &id3, status );
+   ret = astTestWidth( this, id1 );
+   if( nid > 1 ) ret = ret && astTestWidth( this, id2 );
+   if( nid > 2 ) ret = ret && astTestWidth( this, id3 );
+
+/* Return the result. */
+   return ret;
+
+}
+
+static int ToggleLogLin( AstPlot *this, int axis, int islog,
+                         const char *method, int *status ){
+/*
+*
+*  Name:
+*     ToggleLogLin
+
+*  Purpose:
+*     Toggle the nature of the Plot axis mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int ToggleLogLin( AstPlot *this, int axis, int islog,
+*                       const char *method, int *status )
+
+*  Class Membership:
+*     Plot member function
+
+*  Description:
+*     Each axis in the graphics Frame of a Plot can be mapped linearly or
+*     logarithmically onto the corresponding axis in the base Frame of
+*     the FrameSet supplied whtn the Plot was constructed. This function
+*     toggles the nature of the specified axis; if it is currently
+*     logarithmic it becomes linear, and if it is linear it becomes
+*     logarithmic.
+*
+*     If the Frame canot be re-maped (for instance because the visible
+*     part of the axis includes the value zero), then zero is returned
+*     but no error is reported.
+
+*  Parameters:
+*     this
+*        The Plot.
+*     axis
+*        Zero based axis index.
+*     islog
+*        Is the axis currently logarithmic? If so, this function remaps
+*        it so that it is linear (and vice-versa).
+*     method
+*        Pointer to a null-terminated string holding the name of the calling
+*        method (only used within error mesages).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the attempt to re-map the graphics Frame was succesful,
+*     zero otherwise.
+
+*/
+
+/* Local Variables: */
+   AstCmpMap *remap1;   /* 1D Mapping to re-map the graphics Frame */
+   AstCmpMap *remap2;   /* 2D Mapping to re-map the graphics Frame */
+   AstMathMap *logmap;  /* 1D Logarithmic axis Mapping */
+   AstUnitMap *unitmap;  /* 1D Unit mapping */
+   AstWinMap *linmap;   /* 1D Linear axis Mapping */
+   char fwdexp[ 25 + 2*DBL_DIG ];  /* Forward log mapping expression */
+   char invexp[ 28 + 2*DBL_DIG ];  /* Inverse log mapping expression */
+   const char *fwd[1];  /* Pointer to pass to MathMap constructor */
+   const char *inv[1];  /* Pointer to pass to MathMap constructor */
+   double a;            /* Constant for log expression */
+   double b1;           /* Original base Frame axis value at first edge */
+   double b2;           /* Original base Frame axis value at second edge */
+   double b;            /* Constant for log expression */
+   double c;            /* Constant for log expression */
+   double g1;           /* Graphics axis value at first edge */
+   double g2;           /* Graphics axis value at second edge */
+   int result;          /* Returned value */
+
+/* Inotialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the corresponding axis limits in the graphics coordinate system
+   and the original base Frame coordinate system. */
+   if( axis == 0 ) {
+      if( this->xrev ) {
+         g1 = this->xhi;
+         g2 = this->xlo;
+      } else {
+         g1 = this->xlo;
+         g2 = this->xhi;
+      }
+      b1 = this->bbox[ 0 ];
+      b2 = this->bbox[ 2 ];
+
+   } else {
+      if( this->yrev ) {
+         g1 = this->yhi;
+         g2 = this->ylo;
+      } else {
+         g1 = this->ylo;
+         g2 = this->yhi;
+      }
+      b1 = this->bbox[ 1 ];
+      b2 = this->bbox[ 3 ];
+   }
+
+/* Check the limits are usable (e.g. the base Frame values will be bad
+   if this Plot was restored from a dump of a Plot created before the
+   LogPlot attributes were added). */
+   if( b1 != AST__BAD && b2 != AST__BAD && g1 != g2 && b1 != b2 &&
+       b1*b2 > 0.0 ) {
+
+/* Form the 1D Mapping which maps the specified axis linearly onto the plotting
+   surface. The forward transformation goes from graphics to base Frame. */
+      linmap = astWinMap( 1, &g1, &g2, &b1, &b2, "", status );
+
+/* Form the 1D Mapping which maps the specified axis logarithmically onto the
+   plotting surface. The forward transformation goes from graphics to base
+   Frame. */
+      c = log10( b1/b2 );
+      a = ( g1 - g2 )/c;
+
+      if( b1 > 0.0 ) {
+         b = ( g2*log10( b1 ) - g1*log10( b2 ) )/c;
+         (void) sprintf( invexp, "g=%.*g*log10(b)+%.*g", DBL_DIG, a, DBL_DIG, b );
+         (void) sprintf( fwdexp, "b=pow(10,(g-%.*g)/%.*g)", DBL_DIG, b, DBL_DIG, a );
+
+      } else {
+         b = ( g2*log10( -b1 ) - g1*log10( -b2 ) )/c;
+         (void) sprintf( invexp, "g=%.*g*log10(-b)+%.*g", DBL_DIG, a, DBL_DIG, b );
+         (void) sprintf( fwdexp, "b=-pow(10,(g-%.*g)/%.*g)", DBL_DIG, b, DBL_DIG, a );
+      }
+
+      fwd[ 0 ] = (const char *) fwdexp;
+      inv[ 0 ] = (const char *) invexp;
+      logmap = astMathMap( 1, 1, 1, fwd, 1, inv, "SimpFI=1,SimpIF=1", status );
+
+/* If the axis was previously logarithmic, get the Mapping with which to remap
+   the graphics Frame so that it becomes linearly related to the base Frame
+   in the FrameSet supplied when the Plot was constructed. */
+      if( islog ) {
+         astInvert( linmap );
+         remap1 = astCmpMap( logmap, linmap, 1, "", status );
+
+/* If the axis was previously linear, store the new value and get the Mapping
+   with which to remap the graphics Frame so that it becomes logarithmically
+   related to the base Frame in the FrameSet supplied when the Plot was
+   constructed. */
+      } else {
+         astInvert( logmap );
+         remap1 = astCmpMap( linmap, logmap, 1, "", status );
+      }
+
+/* Add a 1D UnitMap to map the unaltered mapping. */
+      unitmap = astUnitMap( 1, "", status );
+      if( axis == 0 ) {
+         remap2 = astCmpMap( remap1, unitmap, 0, "", status );
+      } else {
+         remap2 = astCmpMap( unitmap, remap1, 0, "", status );
+      }
+
+/* Remap the base (graphics) Frame in the Plot. */
+      astRemapFrame( this, AST__BASE, remap2 );
+
+/* Free resources. */
+      remap1 = astAnnul( remap1 );
+      remap2 = astAnnul( remap2 );
+      logmap = astAnnul( logmap );
+      linmap = astAnnul( linmap );
+      unitmap = astAnnul( unitmap );
+
+/* Indicate success. */
+      if( astOK ) result = 1;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int Ustrcmp( const char *a, const char *b, int *status ){
+/*
+*  Name:
+*     Ustrncmp
+
+*  Purpose:
+*     A case blind version of strcmp.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Ustrcmp( const char *a, const char *b )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     Returns 0 if there are no differences between the two strings, and 1
+*     otherwise. Comparisons are case blind.
+
+*  Parameters:
+*     a
+*        Pointer to first string.
+*     b
+*        Pointer to second string.
+
+*  Returned Value:
+*     Zero if the strings match, otherwise one.
+
+*  Notes:
+*     -  This function does not consider the sign of the difference between
+*     the two strings, whereas "strcmp" does.
+*     -  This function attempts to execute even if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   const char *aa;         /* Pointer to next "a" character */
+   const char *bb;         /* Pointer to next "b" character */
+   int ret;                /* Returned value */
+
+/* Initialise the returned value to indicate that the strings match. */
+   ret = 0;
+
+/* Initialise pointers to the start of each string. */
+   aa = a;
+   bb = b;
+
+/* Loop round each character. */
+   while( 1 ){
+
+/* We leave the loop if either of the strings has been exhausted. */
+      if( !(*aa ) || !(*bb) ){
+
+/* If one of the strings has not been exhausted, indicate that the
+   strings are different. */
+         if( *aa || *bb ) ret = 1;
+
+/* Break out of the loop. */
+         break;
+
+/* If neither string has been exhausted, convert the next characters to
+   upper case and compare them, incrementing the pointers to the next
+   characters at the same time. If they are different, break out of the
+   loop. */
+      } else {
+
+         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
+            ret = 1;
+            break;
+         }
+
+      }
+
+   }
+
+/* Return the result. */
+   return ret;
+
+}
+
+static int Ustrncmp( const char *a, const char *b, size_t n, int *status ){
+/*
+*  Name:
+*     Ustrncmp
+
+*  Purpose:
+*     A case blind version of strncmp.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     int Ustrncmp( const char *a, const char *b, size_t n )
+
+*  Class Membership:
+*     Plot member function.
+
+*  Description:
+*     Returns 0 if there are no differences between the first "n"
+*     characters of the two strings, and 1 otherwise. Comparisons are
+*     case blind.
+
+*  Parameters:
+*     a
+*        Pointer to first string.
+*     b
+*        Pointer to second string.
+*     n
+*        The maximum number of characters to compare.
+
+*  Returned Value:
+*     Zero if the strings match, otherwise one.
+
+*  Notes:
+*     -  This function does not consider the sign of the difference
+*     between the two strings, whereas "strncmp" does.
+*     -  This function attempts to execute even if an error has
+*     occurred.
+
+*/
+
+/* Local Variables: */
+   const char *aa;         /* Pointer to next "a" character */
+   const char *bb;         /* Pointer to next "b" character */
+   int i;                  /* Character index */
+   int ret;                /* Returned value */
+
+/* Initialise the returned value to indicate that the strings match. */
+   ret = 0;
+
+/* Initialise pointers to the start of each string. */
+   aa = a;
+   bb = b;
+
+/* Compare up to "n" characters. */
+   for( i = 0; i < (int) n; i++ ){
+
+/* We leave the loop if either of the strings has been exhausted. */
+      if( !(*aa ) || !(*bb) ){
+
+/* If one of the strings has not been exhausted, indicate that the
+   strings are different. */
+         if( *aa || *bb ) ret = 1;
+
+/* Break out of the loop. */
+         break;
+
+/* If neither string has been exhausted, convert the next characters to
+   upper case and compare them, incrementing the pointers to the next
+   characters at the same time. If they are different, break out of the
+   loop. */
+      } else {
+
+         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
+            ret = 1;
+            break;
+         }
+
+      }
+
+   }
+
+/* Return the result. */
+   return ret;
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Plot objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Plot objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstPlot *this;               /* Pointer to Plot */
+   int i;
+
+/* Obtain a pointer to the Plot structure. */
+   this = (AstPlot *) obj;
+
+/* Free the clipping bounds arrays. */
+   this->clip_lbnd = (double *) astFree( (void *) this->clip_lbnd );
+   this->clip_ubnd = (double *) astFree( (void *) this->clip_ubnd );
+
+/* Free the Grf function stack */
+   this->grfstack = (AstGrfPtrs *) astFree( (void *) this->grfstack );
+
+/* Free the graphics attribute stack. */
+   for( i = this->ngat - 1; i >= 0; i-- ) {
+     this->gat[ i ] = astFree( this->gat[ i ] );
+   }
+
+/* Free the graphics context pointer. */
+   if( this->grfcontext ) {
+      this->grfcontext = astAnnul( this->grfcontext );
+      this->grfcontextID = astAnnulId( this->grfcontextID );
+   }
+
+/* Free the information about the tick marks to draw. */
+   for( i = 0; i < 3; i++ ) {
+      this->majtickval[ i ] = astFree( this->majtickval[ i ] );
+      this->mintickval[ i ] = astFree( this->mintickval[ i ] );
+      this->nmajtickval[ i ] = 0;
+      this->nmintickval[ i ] = 0;
+   }
+
+/* Free the information about the drawn tick marks. */
+   SaveTick( this, -1, 0.0, 0.0, 0, status );
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Plot objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Plot objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstPlot *in;                 /* Pointer to input Plot */
+   AstPlot *out;                /* Pointer to output Plot */
+   int axis;                    /* Zero based axis index */
+   int n;                       /* Number of ticks saved */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Plots. */
+   in = (AstPlot *) objin;
+   out = (AstPlot *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output Plot. */
+   out->clip_lbnd = NULL;
+   out->clip_ubnd = NULL;
+   out->gat = NULL;
+   out->ngat = 0;
+
+   for( axis = 0; axis < 3; axis++ ) {
+      out->majtickgx[ axis ] = NULL;
+      out->majtickgy[ axis ] = NULL;
+      out->majtickcount[ axis ] = 0;
+      out->mintickgx[ axis ] = NULL;
+      out->mintickgy[ axis ] = NULL;
+      out->mintickcount[ axis ] = 0;
+      out->majtickval[ axis ] = NULL;
+      out->nmajtickval[ axis ] = 0;
+      out->mintickval[ axis ] = NULL;
+      out->nmintickval[ axis ] = 0;
+   }
+
+/* Copy the clipping bounds arrays. */
+   out->clip_lbnd = (double *) astStore( NULL, (void *) in->clip_lbnd,
+                                      sizeof(double)*(size_t)(in->clip_axes) );
+   out->clip_ubnd = (double *) astStore( NULL, (void *) in->clip_ubnd,
+                                      sizeof(double)*(size_t)(in->clip_axes) );
+
+/* Copy the Grf function stack */
+   out->grfstack = (AstGrfPtrs *) astStore( NULL, (void *) in->grfstack,
+                                  sizeof(AstGrfPtrs)*(size_t)(in->grfnstack ));
+
+/* Copy the information about drawn tick marks. */
+   for( axis = 0; axis < 3; axis++ ) {
+      n = in->majtickcount[ axis ];
+      out->majtickgx[ axis ] = (double *) astStore( NULL, in->majtickgx[ axis ],
+                                                    n*sizeof( double ) );
+      out->majtickgy[ axis ] = (double *) astStore( NULL, in->majtickgy[ axis ],
+                                                    n*sizeof( double ) );
+      out->majtickcount[ axis ] = n;
+
+      n = in->mintickcount[ axis ];
+      out->mintickgx[ axis ] = (double *) astStore( NULL, in->mintickgx[ axis ],
+                                                    n*sizeof( double ) );
+      out->mintickgy[ axis ] = (double *) astStore( NULL, in->mintickgy[ axis ],
+                                                    n*sizeof( double ) );
+      out->mintickcount[ axis ] = n;
+
+      n = in->nmajtickval[ axis ];
+      out->majtickval[ axis ] = (double *) astStore( NULL, in->majtickval[ axis ],
+                                                     n*sizeof( double ) );
+      out->nmajtickval[ axis ] = n;
+
+      n = in->nmintickval[ axis ];
+      out->mintickval[ axis ] = (double *) astStore( NULL, in->mintickval[ axis ],
+                                                     n*sizeof( double ) );
+      out->nmintickval[ axis ] = n;
+   }
+
+/* If an error occurred, free any allocated memory. */
+   if ( !astOK ) {
+      out->clip_lbnd = (double *) astFree( out->clip_lbnd );
+      out->clip_ubnd = (double *) astFree( out->clip_ubnd );
+      out->grfstack = (AstGrfPtrs *) astFree( out->grfstack );
+      SaveTick( out, -1, 0.0, 0.0, 0, status );
+    }
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Plot objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Plot class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstPlot *this;                /* Pointer to the Plot structure */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   char *comment;                /* Pointer to comment string */
+   double dval;                  /* Double precision value */
+   int ax;                       /* Axis to which element refers */
+   int axis;                     /* Zero based axis index */
+   int id;                       /* Zero based graphical object id */
+   int ival;                     /* Integer value */
+   int itick;                    /* Tick mark index */
+   int nax;                      /* Number of base Frame axes */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Plot structure. */
+   this = (AstPlot *) this_object;
+
+/* Get the number of graphics (base) frame axes - 2 for a Plot, 3 for a
+   Plot3D. */
+   nax = astGetNin( this );
+
+/* Write out values representing the instance variables for the
+   Plot class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Tol. */
+/* ---- */
+   set = TestTol( this, status );
+   dval = set ? GetTol( this, status ) : astGetTol( this );
+   astWriteDouble( channel, "Tol", set, 0, dval, "Plotting tolerance" );
+
+/* Grid. */
+/* ----- */
+   set = TestGrid( this, status );
+   ival = set ? GetGrid( this, status ) : astGetGrid( this );
+   astWriteInt( channel, "Grid", set, 0, ival, "Is a grid required?" );
+
+/* TickAll. */
+/* -------- */
+   set = TestTickAll( this, status );
+   ival = set ? GetTickAll( this, status ) : astGetTickAll( this );
+   astWriteInt( channel, "TckAll", set, 1, ival, "Put ticks on all edges?" );
+
+/* ForceExterior. */
+/* -------------- */
+   set = TestForceExterior( this, status );
+   ival = set ? GetForceExterior( this, status ) : astGetForceExterior( this );
+   astWriteInt( channel, "FrcExt", set, 1, ival, "Force exterior labelling?" );
+
+/* Invisible. */
+/* ---------- */
+   set = TestInvisible( this, status );
+   ival = set ? GetInvisible( this, status ) : astGetInvisible( this );
+   astWriteInt( channel, "Invsbl", set, 1, ival, "Use invisible ink?" );
+
+/* Border. */
+/* ------- */
+   set = TestBorder( this, status );
+   ival = set ? GetBorder( this, status ) : astGetBorder( this );
+   astWriteInt( channel, "Border", set, 0, ival, "Draw a border round the grid?" );
+
+/* ClipOp. */
+/* ------- */
+   set = TestClipOp( this, status );
+   ival = set ? GetClipOp( this, status ) : astGetClipOp( this );
+   astWriteInt( channel, "ClpOp", set, 0, ival, "Clip using logical OR?" );
+
+/* Clip. */
+/* ----- */
+   set = TestClip( this, status );
+   ival = set ? GetClip( this, status ) : astGetClip( this );
+   astWriteInt( channel, "Clip", set, 0, ival,
+                ((ival == 0)?"Do not clip at plot edges":
+                ((ival == 1)?"Clip curves at plot edges":
+                ((ival == 2)?"Clip markers at plot edges":
+                             "Clip markers and curves at plot edges"))));
+
+/* DrawTitle. */
+/* --------- */
+   set = TestDrawTitle( this, status );
+   ival = set ? GetDrawTitle( this, status ) : astGetDrawTitle( this );
+   astWriteInt( channel, "DrwTtl", set, 1, ival, "Add a title to the grid?" );
+
+/* DrawAxesUnits(axis). */
+/* ----------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestDrawAxes( this, axis, status );
+      ival = set ? GetDrawAxes( this, axis, status ) : astGetDrawAxes( this, axis );
+      (void) sprintf( buff, "DrwAxs%d", axis + 1 );
+      astWriteInt( channel, buff, set, 0, ival, "Draw axis through ticks?" );
+   }
+
+/* Abbrev(axis). */
+/* ------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestAbbrev( this, axis, status );
+      ival = set ? GetAbbrev( this, axis, status ) : astGetAbbrev( this, axis );
+      (void) sprintf( buff, "Abbrv%d", axis + 1 );
+      astWriteInt( channel, buff, set, 0, ival, "Abbreviate numerical axis labels?" );
+   }
+
+/* Escape. */
+/* ------- */
+   set = TestEscape( this, status );
+   ival = set ? GetEscape( this, status ) : astGetEscape( this );
+   astWriteInt( channel, "Escape", set, 1, ival, "Interpret escape sequences?" );
+
+/* LabelAt(axis). */
+/* -------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestLabelAt( this, axis, status );
+      dval = set ? GetLabelAt( this, axis, status ) : astGetLabelAt( this, axis );
+      if( dval != AST__BAD ){
+         (void) sprintf( buff, "LblAt%d", axis + 1 );
+         astWriteDouble( channel, buff, set, 0, dval, "Put numerical labels at" );
+      }
+   }
+
+/* Centre(axis). */
+/* ------------ */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestCentre( this, axis, status );
+      dval = set ? GetCentre( this, axis, status ) : astGetCentre( this, axis );
+      if( dval != AST__BAD ){
+         (void) sprintf( buff, "Cen%d", axis + 1 );
+         astWriteDouble( channel, buff, set, 0, dval, "Tick mark origin" );
+      }
+   }
+
+/* Gap(axis). */
+/* ---------- */
+/* Discovering the default value requires a lot of calculation. Only
+   write out this attribute if an explicit value has been set. */
+   for( axis = 0; axis < nax; axis++ ){
+      if( astTestGap( this, axis ) ) {
+         dval = astGetGap( this, axis );
+         if( dval != AST__BAD ){
+            (void) sprintf( buff, "Gap%d", axis + 1 );
+            astWriteDouble( channel, buff, set, 0, dval, "Difference between ticks" );
+         }
+      }
+   }
+
+/* LogGap(axis). */
+/* ------------- */
+/* Discovering the default value requires a lot of calculation. Only
+   write out this attribute if an explicit value has been set. */
+   for( axis = 0; axis < nax; axis++ ){
+      if( astTestLogGap( this, axis ) ) {
+         dval = astGetLogGap( this, axis );
+         if( dval != AST__BAD ){
+            (void) sprintf( buff, "LgGap%d", axis + 1 );
+            astWriteDouble( channel, buff, set, 0, dval, "Ratio between ticks" );
+         }
+      }
+   }
+
+/* NumLabGap(axis). */
+/* ---------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestNumLabGap( this, axis, status );
+      dval = set ? GetNumLabGap( this, axis, status ) : astGetNumLabGap( this, axis );
+      if( dval != AST__BAD ) {
+         (void) sprintf( buff, "NmGap%d", axis + 1 );
+         astWriteDouble( channel, buff, set, 1, dval, "Spacing of numerical labels" );
+      }
+   }
+
+/* TextLabGap(axis). */
+/* ----------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestTextLabGap( this, axis, status );
+      dval = set ? GetTextLabGap( this, axis, status ) : astGetTextLabGap( this, axis );
+      if( dval != AST__BAD ) {
+         (void) sprintf( buff, "TxGap%d", axis + 1 );
+         astWriteDouble( channel, buff, set, 1, dval, "Spacing of descriptive labels" );
+      }
+   }
+
+/* LabelUp(axis). */
+/* -------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestLabelUp( this, axis, status );
+      ival = set ? GetLabelUp( this, axis, status ) : astGetLabelUp( this, axis );
+      (void) sprintf( buff, "LblUp%d", axis + 1 );
+      astWriteInt( channel, buff, set, 1, ival, "Draw numerical labels upright?" );
+   }
+
+/* LogPlot(axis). */
+/* -------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestLogPlot( this, axis, status );
+      ival = set ? GetLogPlot( this, axis, status ) : astGetLogPlot( this, axis );
+      (void) sprintf( buff, "LgPlt%d", axis + 1 );
+      astWriteInt( channel, buff, set, 1, ival, "Map plot axis logarithmically?" );
+   }
+
+/* LogTicks(axis). */
+/* -------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestLogTicks( this, axis, status );
+      ival = set ? GetLogTicks( this, axis, status ) : astGetLogTicks( this, axis );
+      (void) sprintf( buff, "LgTck%d", axis + 1 );
+      astWriteInt( channel, buff, set, 1, ival, "Space ticks logarithmically?" );
+   }
+
+/* LogLabel(axis). */
+/* -------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestLogLabel( this, axis, status );
+      ival = set ? GetLogLabel( this, axis, status ) : astGetLogLabel( this, axis );
+      (void) sprintf( buff, "LgLbl%d", axis + 1 );
+      astWriteInt( channel, buff, set, 1, ival, "Scientific notation for labels?" );
+   }
+
+/* NumLab(axis). */
+/* -------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestNumLab( this, axis, status );
+      ival = set ? GetNumLab( this, axis, status ) : astGetNumLab( this, axis );
+      (void) sprintf( buff, "NmLbl%d", axis + 1 );
+      astWriteInt( channel, buff, set, 1, ival, "Draw numerical labels?" );
+   }
+
+/* MinTick(axis). */
+/* -------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestMinTick( this, axis, status );
+      ival = set ? GetMinTick( this, axis, status ) : astGetMinTick( this, axis );
+      (void) sprintf( buff, "MnTks%d", axis + 1 );
+      astWriteInt( channel, buff, set, 0, ival, "No. of sub-divisions "
+                   "between major tick marks" );
+   }
+
+/* TextLab(axis). */
+/* -------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestTextLab( this, axis, status );
+      ival = set ? GetTextLab( this, axis, status ) : astGetTextLab( this, axis );
+      (void) sprintf( buff, "TxLbl%d", axis + 1 );
+      astWriteInt( channel, buff, set, 0, ival, "Draw textual label?" );
+   }
+
+/* LabelUnits(axis). */
+/* ----------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestLabelUnits( this, axis, status );
+      ival = set ? GetLabelUnits( this, axis, status ) : astGetLabelUnits( this, axis );
+      (void) sprintf( buff, "LbUnt%d", axis + 1 );
+      astWriteInt( channel, buff, set, 0, ival, "Add units to axis label?" );
+   }
+
+/* Style(object). */
+/* -------------- */
+   for( id = 0; id < AST__NPID; id++ ){
+      set = TestStyle( this, id, status );
+      ival = set ? GetStyle( this, id, status ) : astGetStyle( this, id );
+      (void) sprintf( buff, "Style%d", id + 1 );
+      comment = GrfItem( id, " line style", &ax, status );
+      if( ax < nax ) astWriteInt( channel, buff, set, 0, ival, comment );
+      comment = (char *) astFree( (void *) comment );
+   }
+
+/* Font(object). */
+/* ------------- */
+   for( id = 0; id < AST__NPID; id++ ){
+      set = TestFont( this, id, status );
+      ival = set ? GetFont( this, id, status ) : astGetFont( this, id );
+      (void) sprintf( buff, "Font%d", id + 1 );
+      comment = GrfItem( id, " character font", &ax, status );
+      if( ax < nax ) astWriteInt( channel, buff, set, 0, ival, comment );
+      comment = (char *) astFree( (void *) comment );
+   }
+
+/* Colour(object). */
+/* --------------- */
+   for( id = 0; id < AST__NPID; id++ ){
+      set = TestColour( this, id, status );
+      ival = set ? GetColour( this, id, status ) : astGetColour( this, id );
+      (void) sprintf( buff, "Col%d", id + 1 );
+      comment = GrfItem( id, " colour index", &ax, status );
+      if( ax < nax ) astWriteInt( channel, buff, set, 0, ival, comment );
+      comment = (char *) astFree( (void *) comment );
+   }
+
+/* Width(object). */
+/* -------------- */
+   for( id = 0; id < AST__NPID; id++ ){
+      set = TestWidth( this, id, status );
+      dval = set ? GetWidth( this, id, status ) : astGetWidth( this, id );
+      if( dval != AST__BAD ) {
+         (void) sprintf( buff, "Width%d", id + 1 );
+         comment = GrfItem( id, " line width", &ax, status );
+         if( ax < nax ) astWriteDouble( channel, buff, set, 0, dval, comment );
+         comment = (char *) astFree( (void *) comment );
+      }
+   }
+
+/* Size(object). */
+/* ------------- */
+   for( id = 0; id < AST__NPID; id++ ){
+      set = TestSize( this, id, status );
+      dval = set ? GetSize( this, id, status ) : astGetSize( this, id );
+      if( dval != AST__BAD ) {
+         (void) sprintf( buff, "Size%d", id + 1 );
+         comment = GrfItem( id, " character size", &ax, status );
+         if( ax < nax ) astWriteDouble( channel, buff, set, 0, dval, comment );
+         comment = (char *) astFree( (void *) comment );
+      }
+   }
+
+/* TitleGap. */
+/* --------- */
+   set = TestTitleGap( this, status );
+   dval = set ? GetTitleGap( this, status ) : astGetTitleGap( this );
+   if( dval != AST__BAD ) astWriteDouble( channel, "TtlGap", set, 1, dval,
+                                         "Gap between title and edge" );
+
+/* MajTickLen(axis). */
+/* ----------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestMajTickLen( this, axis, status );
+      dval = set ? GetMajTickLen( this, axis, status ) : astGetMajTickLen( this, axis );
+      if( dval != AST__BAD ) {
+         (void) sprintf( buff, "MjTkLn%d", axis + 1 );
+         astWriteDouble( channel, buff, set, 0, dval, "Major tick length" );
+      }
+   }
+
+/* MinTickLen(axis). */
+/* ----------------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestMinTickLen( this, axis, status );
+      dval = set ? GetMinTickLen( this, axis, status ) : astGetMinTickLen( this, axis );
+      if( dval != AST__BAD ) {
+         (void) sprintf( buff, "MnTkLn%d", axis + 1 );
+         astWriteDouble( channel, buff, set, 1, dval, "Minor tick length" );
+      }
+   }
+
+/* Labelling. */
+/* ---------- */
+   set = TestLabelling( this, status );
+   ival = set ? GetLabelling( this, status ) : astGetLabelling( this );
+   comment = "Labelling scheme";
+   astWriteString( channel, "Lbling", set, 0, xlbling[ival], comment );
+
+/* Edge(axis). */
+/* ----------- */
+   for( axis = 0; axis < nax; axis++ ){
+      set = TestEdge( this, axis, status );
+      ival = set ? GetEdge( this, axis, status ) : astGetEdge( this, axis );
+      (void) sprintf( buff, "Edge%d", axis + 1 );
+      comment = "Edge used to label an axis";
+      astWriteString( channel, buff, set, 0, xedge[ival], comment );
+   }
+
+/* Now do instance variables which are not attributes. */
+/* =================================================== */
+
+/* Only write out clipping information if set. */
+   if( this->clip_lbnd && this->clip_ubnd ){
+
+/* The lower bounds of the clipping volume. */
+      for( axis = 0; axis < this->clip_axes; axis++ ){
+         (void) sprintf( buff, "ClpLb%d", axis + 1 );
+         if( this->clip_lbnd && (this->clip_lbnd)[ axis ] != AST__BAD ){
+            astWriteDouble( channel, buff, 1, 0, (this->clip_lbnd)[ axis ],
+                            "Lower bound of clipping region" );
+         }
+      }
+
+/* The upper bounds of the clipping volume. */
+      for( axis = 0; axis < this->clip_axes; axis++ ){
+         (void) sprintf( buff, "ClpUb%d", axis + 1 );
+         if( this->clip_ubnd && (this->clip_ubnd)[ axis ] != AST__BAD ){
+            astWriteDouble( channel, buff, 1, 0, (this->clip_ubnd)[ axis ],
+                            "Upper bound of clipping region" );
+         }
+      }
+
+/* The number of bounds supplied for the clipping volume. */
+      astWriteInt( channel, "ClpAxs", 1, 0, this->clip_axes,
+                   "No. of bounds for clipping region" );
+
+/* The index of the clipping Frame within the Plot. */
+      astWriteInt( channel, "ClpFrm", 1, 0, this->clip_frame,
+                   "Index of clipping Frame" );
+   }
+
+/* The bounds of the plotting area in graphics coords. */
+   astWriteDouble( channel, "Xlo", 1, 1, this->xlo,
+                   "Lower X bound of plotting area" );
+   astWriteDouble( channel, "Ylo", 1, 1, this->ylo,
+                   "Lower Y bound of plotting area" );
+   astWriteDouble( channel, "Xhi", 1, 1, this->xhi,
+                   "Upper X bound of plotting area" );
+   astWriteDouble( channel, "Yhi", 1, 1, this->yhi,
+                   "Upper Y bound of plotting area" );
+
+/* Axis reversal flags. */
+   astWriteInt( channel, "Xrev", 1, 0, this->xrev, "X axis reversed?" );
+   astWriteInt( channel, "Yrev", 1, 0, this->yrev, "Y axis reversed?" );
+
+/* The bounds of the plotting area in the base Frame of the FrameSet
+   supplied when the Plot was constructed. */
+   astWriteDouble( channel, "Xb1", 1, 1, this->bbox[ 0 ],
+                   "Lower X bound of supplied base Frame" );
+   astWriteDouble( channel, "Yb1", 1, 1, this->bbox[ 1 ],
+                   "Lower Y bound of supplied base Frame" );
+   astWriteDouble( channel, "Xb2", 1, 1, this->bbox[ 2 ],
+                   "Upper X bound of supplied base Frame" );
+   astWriteDouble( channel, "Yb2", 1, 1, this->bbox[ 3 ],
+                   "Upper Y bound of supplied base Frame" );
+
+/* User-specified tick values */
+   for( axis = 0; axis < 3; axis++ ) {
+
+      if( this->nmajtickval[ axis ] > 0 ) {
+         sprintf( buff, "NMjTk%d", axis + 1 );
+         astWriteInt( channel, buff, 1, 1, this->nmajtickval[ axis ], "" );
+
+         for( itick = 0; itick < this->nmajtickval[ axis ]; itick++ ) {
+            sprintf( buff, "MjTk%d_%d", axis + 1, itick + 1 );
+            astWriteDouble( channel, buff, 1, 1,
+                            this->majtickval[ axis ][ itick ], "" );
+         }
+      }
+
+      if( this->nmintickval[ axis ] > 0 ) {
+         sprintf( buff, "NMnTk%d", axis + 1 );
+         astWriteInt( channel, buff, 1, 1, this->nmintickval[ axis ], "" );
+
+         for( itick = 0; itick < this->nmintickval[ axis ]; itick++ ) {
+            sprintf( buff, "MnTk%d_%d", axis + 1, itick + 1 );
+            astWriteDouble( channel, buff, 1, 1,
+                            this->mintickval[ axis ][ itick ], "" );
+         }
+      }
+   }
+
+/* Return. */
+   return;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAPlot and astCheckPlot functions using
+   the macros defined for this purpose in the "object.h" header
+   file. */
+astMAKE_ISA(Plot,FrameSet)
+astMAKE_CHECK(Plot)
+
+AstPlot *astPlot_( void *frame_void, const float *graphbox,
+                   const double *basebox, const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astPlot
+
+*  Purpose:
+*     Create a Plot.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     AstPlot *astPlot( AstFrame *frame, const float *graphbox,
+*                       const double *basebox, const char *options, ..., int *status )
+
+*  Class Membership:
+*     Plot constructor.
+
+*  Description:
+*     This function creates a new Plot and optionally initialises
+*     its attributes.
+*
+*     The supplied Frame (or the base frame if a FrameSet was supplied) is
+*     assumed to be related to the graphics world coordinate system by a
+*     simple shift and scale along each axis. The mapping between graphics
+*     world coordinates and this Frame is specified by supplying the
+*     coordinates in both systems at the bottom left and top right corners
+*     of a box on the graphics device. By default, no graphics will be
+*     produced outside the supplied box, but this default behaviour can be
+*     changed by setting explicit values for the various clipping attributes.
+
+*  Parameters:
+*     frame
+*        A pointer to a Frame or FrameSet to be annotated. If a NULL pointer
+*        is supplied, then a default 2-D Frame will be created to which labels,
+*        etc, can be attached by setting the relevant Frame attributes.
+*     graphbox
+*        A pointer to an array of 4 values giving the graphics world
+*        coordinates of the bottom left and top right corners of a box on
+*        the graphics output device. The first pair of values should be the
+*        coordinates of the bottom left corner of the box and the second
+*        pair of values should be the coordinates of the top right corner.
+*        The horizontal axis should be given first in each pair.
+*     basebox
+*        A pointer to an array of 4 values giving the coordinates in the
+*        supplied Frame, or base frame of the supplied FrameSet, at the
+*        bottom left and top right corners of the box specified by parameter
+*        graphbox. These should be supplied in the same order as for
+*        parameter "graphbox".
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new Plot. The syntax used is the same as
+*        for the astSet method and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of arguments may follow it in order to
+*        supply values to be substituted for these specifiers. The
+*        rules for supplying these are identical to those for the
+*        astSet method (and for the C "printf" function).
+
+*  Returned Value:
+*     A pointer to the new Plot.
+
+*  Notes:
+*     -  The base Frame of the created Plot corresponds to the graphics world
+*     coordinate system, and should not, in general, be changed.
+*     -  The current Frame of the created Plot corresponds to the Frame
+*     given by parameter "frame". If a FrameSet was supplied then its
+*     current Frame becomes the current Frame of the created Plot.
+*     - If the supplied Frame, or base Frame if a FrameSet was supplied,
+*     has more than 2 axes, then the sub-Frame defined by the first 2 axes
+*     is used.
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic Plot constructor which
+*     is available via the protected interface to the Plot class.
+*     A public interface is provided by the astPlotId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "frame" parameter is of type (void *) and is converted and
+*     validated within the function itself.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstPlot *new;                 /* Pointer to new Plot */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   new = NULL;
+
+/* Obtain and validate a pointer to any supplied Frame structure. */
+   if( frame_void ){
+      frame = astCheckFrame( frame_void );
+   } else {
+      frame = NULL;
+   }
+
+/* Check the pointer can be used. */
+   if ( astOK ) {
+
+/* Initialise the Plot, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitPlot( NULL, sizeof( AstPlot ), !class_init,
+                         &class_vtab, "Plot", frame, graphbox,
+                         basebox );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   Plot's attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new Plot. */
+   return new;
+}
+
+AstPlot *astInitPlot_( void *mem, size_t size, int init, AstPlotVtab *vtab,
+                       const char *name, AstFrame *frame, const float *graphbox,
+                       const double *basebox, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPlot
+
+*  Purpose:
+*     Initialise a Plot.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     AstPlot *astInitPlot( void *mem, size_t size, int init,
+*                           AstPlotVtab *vtab, const char *name,
+*                           AstFrame *frame, const float *graphbox,
+*                           const double *basebox )
+
+*  Class Membership:
+*     Plot initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Plot object. It allocates memory (if necessary) to accommodate
+*     the Plot plus any additional data associated with the derived class.
+*     It then initialises a Plot structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a Plot at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Plot is to be created. This
+*        must be of sufficient size to accommodate the Plot data
+*        (sizeof(Plot)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Plot (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Plot
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Plot's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Plot. If NULL, the vtab associated with this class
+*        (Plot) will be used.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     frame
+*        A pointer to the Frame or Frameset to be annotated.
+*     graphbox
+*        A pointer to an array of 4 values giving the graphics coordinates
+*        of the bottom left and top right corners of a box on the graphics
+*        output device. The first pair of values should be the graphics
+*        coordinates of the bottom left corner of the box and the second
+*        pair of values are the graphics coordinates of the top right corner.
+*        The horizontal axis should be given first in each pair.
+*     basebox
+*        A pointer to an array of 4 values giving the coordinates in the
+*        supplied Frame or base Frame of the supplied FrameSet at the bottom
+*        left and top right corners of the box specified by parameter graphbox.
+*        These should be supplied in the same order as for parameter "graphbox".
+
+*  Returned Value:
+*     A pointer to the new Plot.
+
+*  Notes:
+*     - If the supplied Frame, or base Frame if a FrameSet was supplied,
+*     has more than 2 axes, then the sub-Frame defined by the first 2 axes
+*     is used.
+*     -  The current Frame of the supplied FrameSet need not be 2-dimensional.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *baseframe;         /* Pointer to base frame */
+   AstFrame *graphicsframe;     /* Pointer to graphics frame */
+   AstFrameSet *fset0;          /* The n-D FrameSet to be annotated */
+   AstFrameSet *fset;           /* The 2-D FrameSet to be annotated */
+   AstPlot *new;                /* Pointer to new Plot */
+   AstWinMap *map;              /* Mapping for converting bbox -> gbox */
+   char *mess;                  /* Pointer to a descriptive message */
+   double gbox[ 4 ];            /* Double precision version of "graphbox" */
+   int axis;                    /* Axis index, 0 or 1 */
+   int bi;                      /* Index of base frame */
+   int ci;                      /* Index of current frame */
+   int i;                       /* Loop count */
+   int id;                      /* Plot object id */
+   int naxes;                   /* No. of axes in frame */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(frame);
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   fset = NULL;
+   mess = NULL;
+
+/* If no vtab was supplied, use the vtab for this class (Plot). */
+   if( !vtab ) {
+      vtab = &class_vtab;
+      if ( !class_init ) {
+         astInitPlotVtab( vtab, "Plot" );
+         class_init = 1;
+      }
+
+/* If necessary, initialise the virtual function table. */
+   } else if ( init ) {
+      astInitPlotVtab( vtab, name );
+   }
+
+/* Initialise. */
+   new = NULL;
+   baseframe = NULL;
+
+/* First of all we need to ensure that we have a FrameSet and a base
+   Frame on which to base the new Plot. If a NULL Frame pointer was
+   supplied, create a default 2-D Frame, and then create a FrameSet
+   containing just this default Frame. Also store a pointer to a
+   message which can be used to describe the object within error
+   messages. */
+   if( !frame ){
+      baseframe = astFrame( 2, "", status );
+      fset = astFrameSet( baseframe, "", status );
+      mess = "default 2-d Frame";
+
+/* If an object was supplied, report an error if it is not a Frame or
+   an object derived from a Frame (such as a FrameSet). */
+   } else if( !astIsAFrame( frame ) ){
+      if( astOK ){
+         astError( AST__BDOBJ, "astInitPlot(%s): Supplied Object (class '%s') "
+                   "is not a Frame.", status, name, astGetClass( frame ) );
+      }
+
+/* If the supplied object is a Plot or an object derived from a Plot (a Plot
+   is a sort of Frame and so will pass the above test), extract a
+   FrameSet from the Plot, and clear the Domain attribute for any existing
+   Frames which have Domain GRAPHICS. */
+   } else if( astIsAPlot( frame ) ){
+      fset0 = astFrameSet( frame, "", status );
+      fset = astCopy( fset0 );
+      fset0 = astAnnul( fset0 );
+
+      for( i = 0; i < astGetNframe( fset ); i++ ) {
+         graphicsframe = astGetFrame( fset, i );
+         if( !strcmp( astGetDomain( graphicsframe ), "GRAPHICS" ) ) {
+            astClearDomain( graphicsframe );
+         }
+         graphicsframe = astAnnul( graphicsframe );
+      }
+
+      baseframe = astGetFrame( fset, astGetBase( fset ) );
+      mess = "base Frame of the supplied Plot";
+
+/* If the object is not a FrameSet, create a FrameSet holding the
+   supplied Frame. If the Frame is not 2D, an extra 2D Frame is
+   included in the FrameSet derived from axes 1 and 2 of the supplied
+   Frame. This new Frame becomes the base Frame. */
+   } else if( !astIsAFrameSet( frame ) ){
+      fset0 = astFrameSet( frame, "", status );
+      mess = "supplied Frame";
+      fset = Fset2D( fset0, AST__BASE, status );
+      fset0 = astAnnul( fset0 );
+      baseframe = astGetFrame( fset, astGetBase( fset ) );
+
+/* If a FrameSet was supplied, ensure it has a 2D base Frame.
+   If the supplied FrameSet is not 2D, then a new base Frame is
+   inserted into it which is derived from axes 1 and 2 of the
+   original base Frame. */
+   } else {
+      fset = Fset2D( (AstFrameSet *) frame, AST__BASE, status );
+      baseframe = astGetFrame( fset, astGetBase( fset ) );
+      mess = "base Frame of the supplied FrameSet";
+   }
+
+/* Check that there are 2 axes in the base frame of the FrameSet. */
+   naxes = astGetNaxes( baseframe );
+   if ( naxes != 2 && astOK ) {
+      astError( AST__NAXIN, "astInitPlot(%s): Number of axes (%d) in the %s "
+                "is invalid - this number should be 2.", status, name, naxes, mess );
+   }
+
+/* Check that neither dimension of the graphbox is zero. */
+   if( ( graphbox[ 2 ] == graphbox[ 0 ] ||
+         graphbox[ 3 ] == graphbox[ 1 ] ) && astOK ){
+      astError( AST__BADBX, "astInitPlot(%s): The plotting area has zero size "
+                "in the graphics world coordinate system.", status, name );
+   }
+
+/* Check that neither dimension of the graphbox is bad. */
+   if( astISBAD(graphbox[0]) || astISBAD(graphbox[1]) ||
+       astISBAD(graphbox[2]) || astISBAD(graphbox[3]) ) {
+      astError( AST__BADBX, "astInitPlot(%s): The plotting area has undefined limits "
+                "in the graphics world coordinate system.", status, name );
+   }
+
+/* Check that neither dimension of the basebox is zero. */
+   if( astISBAD(basebox[2]) || astISBAD(basebox[0]) ) {
+      astError( AST__BADBX, "astInitPlot(%s): The limits of the horizontal "
+                "axis of the %s are undefined or bad.", status, name, name );
+   } else if( astISBAD(basebox[3]) || astISBAD(basebox[1]) ) {
+      astError( AST__BADBX, "astInitPlot(%s): The limits of the vertical "
+                "axis of the %s are undefined or bad.", status, name, name );
+   }
+
+/* Create a Frame which describes the graphics world coordinate system. */
+   graphicsframe = astFrame( 2,
+                             "Domain=GRAPHICS,Title=Graphical Coordinates", status );
+
+/* Initialise a FrameSet structure (the parent class) as the first
+   component within the Plot structure, allocating memory if necessary.
+   The new FrameSet is initialised to hold the graphics Frame created
+   above. */
+   new = (AstPlot *) astInitFrameSet( mem, size, 0, (AstFrameSetVtab *) vtab,
+                                      name, graphicsframe );
+
+   if ( astOK ) {
+
+/* Initialise the Plot data. */
+/* ----------------------------- */
+
+/* Get a double precision version of "graphbox". */
+      gbox[ 0 ] = (double) graphbox[ 0 ];
+      gbox[ 1 ] = (double) graphbox[ 1 ];
+      gbox[ 2 ] = (double) graphbox[ 2 ];
+      gbox[ 3 ] = (double) graphbox[ 3 ];
+
+/* Store the bounds in graphics coordinates of the clipping box, ensuring
+   that the low bound is lower than the high bound. Set flags to indicate
+   if the supplied bounds has to be reversed to do this (some graphics
+   system have the Y axis increasing from the top of the screen to the
+   bottom). */
+      if( graphbox[ 0 ] <= graphbox[ 2 ] ){
+         new->xlo = gbox[ 0 ];
+         new->xhi = gbox[ 2 ];
+         new->xrev = 0;
+      } else {
+         new->xhi = gbox[ 0 ];
+         new->xlo = gbox[ 2 ];
+         new->xrev = 1;
+         astSetDirection( graphicsframe, 0, 0 );
+      }
+       if( graphbox[ 1 ] <= graphbox[ 3 ] ){
+         new->ylo = gbox[ 1 ];
+         new->yhi = gbox[ 3 ];
+         new->yrev = 0;
+      } else {
+         new->yhi = gbox[ 1 ];
+         new->ylo = gbox[ 3 ];
+         new->yrev = 1;
+         astSetDirection( graphicsframe, 1, 0 );
+      }
+
+/* Store the bounds of the Plot within the base Frame of the supplied
+   FrameSet. */
+      new->bbox[ 0 ] = basebox[ 0 ];
+      new->bbox[ 1 ] = basebox[ 1 ];
+      new->bbox[ 2 ] = basebox[ 2 ];
+      new->bbox[ 3 ] = basebox[ 3 ];
+
+/* We initially assume that the base Frame of the supplied FrameSet is
+   mapped lineary onto the graphics frame. This may be changed later by
+   assigning values to the LogPlot attributes. Create a WinMap which
+   maps the base box (within the base Frame of the supplied FrameSet)
+   onto the graphics box. */
+      map = astWinMap( 2, gbox, gbox + 2, basebox, basebox + 2, "", status );
+
+/* Get the index of the current (physical) and base (pixel) Frames in
+   the supplied FrameSet. */
+      bi = astGetBase( fset );
+      ci = astGetCurrent( fset );
+
+/* Temporarily set the current Frame to be the pixel frame. */
+      astSetCurrent( fset, bi );
+
+/* Add the supplied FrameSet into the Plot (i.e. FrameSet) created
+   earlier. This leaves the graphics frame with index 1 in the
+   returned  Plot. We use the linear mapping initially. */
+      astAddFrame( (AstFrameSet *) new, 1, map, fset );
+      map = astAnnul( map );
+
+/* Set the current Frame in the Plot to be the physical coordinate Frame
+   (with index incremented by one because the graphics Frame has been added). */
+      astSetCurrent( (AstFrameSet *) new, ci + 1 );
+
+/* Re-establish the original current Frame in the supplied FrameSet. */
+      astSetCurrent( fset, ci );
+
+/* Store a value of -1.0 for Tol to indicate that no value has yet been
+   set. This will cause a default value of 0.001 to be used. */
+      new->tol = -1.0;
+
+/* Set up default clipping information which gives no clipping. */
+      new->clip_frame = AST__NOFRAME;
+      new->clip_lbnd = NULL;
+      new->clip_ubnd = NULL;
+      new->clip_axes = 0;
+
+/* Is a grid covering the plotting area required? Store a value of -1
+   to indicate that no value has yet been set. This will cause a default
+   value of 0 (no) to be used. */
+      new->grid = -1;
+
+/* Are tick marks to be placed on both edges in a pair of opposite edges?
+   Store a value of -1 to indicate that no value has yet been set. This will
+   cause a default value of 1 (yes) to be used. */
+      new->tickall = -1;
+
+/* Graphics context identifier */
+      new->grfcontext = NULL;
+      new->grfcontextID = NULL;
+
+/* Shoudl ast Grid draw a boundary round the regions of valid coordinates?
+   Store a value of -1 to indicate that no value has yet been set. This will
+   cause a default value of 1 (yes) to be used. */
+      new->border = -1;
+
+/* Should graphics be drawn invisible? Store a value of -1 to indicate that
+   no value has yet been set. This will cause a default value of 0 (no) to
+   be used. */
+      new->invisible = -1;
+
+/* By default clip markers but not curves at the boundary of the plotting
+   area. This was the only behaviour available prior to the introduction of
+   the Clip attribute, and is chosen as the default to maintain backwards
+   compatibility. */
+      new->clip = -1;
+
+/* Is clipping to be done using a logical OR operation between the axes?
+   Store a value of -1 to indicate that no value has yet been set. This will
+   cause a default value of 0 (no, i.e. a logical AND) to be used. */
+      new->clipop = -1;
+
+/* Is the graphics interface registered using astGrfSet to be used?
+   Store a value of -1 to indicate that no value has yet been set. This will
+   cause a default value of 0 (no, i.e. use the graphics interface
+   selected at link-time) to be used. */
+      new->grf = -1;
+
+/* Wrapper functions to call the drawing routines. These are the
+   default wrappers which call GRF routines written in C. Alternative
+   wrappers are defined in fplot.c for use with GRF routines written in
+   F77. */
+      new->GAttr = CGAttrWrapper;
+      new->GBBuf = CGBBufWrapper;
+      new->GEBuf = CGEBufWrapper;
+      new->GFlush = CGFlushWrapper;
+      new->GLine = CGLineWrapper;
+      new->GMark = CGMarkWrapper;
+      new->GText = CGTextWrapper;
+      new->GCap = CGCapWrapper;
+      new->GTxExt = CGTxExtWrapper;
+      new->GScales = CGScalesWrapper;
+      new->GQch = CGQchWrapper;
+
+      for( i = 0; i < AST__NGRFFUN; i++ ) new->grffun[i] = NULL;
+      new->grfstack = NULL;
+      new->grfnstack = 0;
+
+/* Is a title to be added to an annotated grid? Store a value of -1 to
+   indicate that no value has yet been set. This will cause a default value
+   of 1 (yes) to be used. */
+      new->drawtitle = -1;
+
+/* Are escape sequences within text strings to be interpreted? If not,
+   they are printed literally. Store a value of -1 when not set.
+   This will cause a default value of 1 (yes) to be used. */
+      new->escape = -1;
+
+/* A boolean attribute indicating where numerical labels are to be
+   placed; zero implies round the edges of the plotting area; non-zero
+   implies within the plotting area. The unset value of -9999 yields a
+   default of zero. */
+      new->labelling = -9999;
+
+/* Graphics attributes. Default behaviour is to use the current values. */
+      for( id = 0; id < AST__NPID; id++ ){
+         new->style[ id ] = -1;
+         new->font[ id ] = -1;
+         new->colour[ id ] = -1;
+         new->width[ id ] = AST__BAD;
+         new->size[ id ] = AST__BAD;
+      }
+
+/* The space between the top edge and the grid title as a fraction of the
+   minimum dimension of the plotting area. Store AST__BAD to indicate that no
+   value has been set. This will cause a default of 0.05 to be used. */
+      new->titlegap = AST__BAD;
+
+/* Initialise the protected Ink attribute so that visible ink is used. */
+      new->ink = -1;
+
+/* A stack of AstGat pointers used to store the graphical attributes for
+   text within strings which include graphical escape sequences. */
+      new->gat = NULL;
+      new->ngat = 0;
+
+/* Now set the attribute values for each axis. The arrays stored in the
+   Plot struture allow for 3 graphics axes (e.g. as used by a Plot3D) so
+   we initialise 3 axes here even though the Plot class only uses 2. */
+      for( axis = 0; axis < 3; axis++ ) {
+
+/* Are curves to be drawn through the tick marks even if no grid is
+   produced? Store a value of -1 to indicate that no value has yet been
+   set. This will cause a default value of 1 (yes) to be used. */
+         new->drawaxes[ axis ] = -1;
+
+/* Are adjacent numerical axis labels to be abbreviated by removing matching
+   leading fields? Store a value of -1 to indicate that no value has yet been
+   set. This will cause a default value of 1 (yes) to be used. */
+         new->abbrev[ axis ] = -1;
+
+/* The length of the major tick marks as a fraction of the minimum
+   dimension of the plotting area. Store AST__BAD to indicate that no
+   value has been set. This will cause a default of 0.015 to be used. */
+         new->majticklen[ axis ] = AST__BAD;
+
+/* The length of the minor tick marks as a fraction of the minimum
+   dimension of the plotting area. Store AST__BAD to indicate that no
+   value has been set. This will cause a default of 0.007 to be used. */
+         new->minticklen[ axis ] = AST__BAD;
+
+/* Are numeric labels to be drawn upright? Store a value of -1 to indicate
+   that no value has yet been set. This will cause a default value of 0 (no)
+   to be used. */
+         new->labelup[ axis ] = -1;
+
+/* The space between an axis and its numeric labels as a fraction of the
+   minimum dimension of the plotting area. Store AST__BAD to indicate that no
+   value has been set. This will cause a default of 0.01 to be used. */
+         new->numlabgap[ axis ] = AST__BAD;
+         new->textlabgap[ axis ] = AST__BAD;
+
+/* The edges on which to put labels for axes 1 and 2. Store values of -1
+   to indicate that no values have been set. This will cause default values
+   to be used. */
+         new->edge[ axis ] = -1;
+
+/* The placement of labels within the plotting area will be done
+   automatically by default. */
+         new->labelat[ axis ] = AST__BAD;
+
+/* The central tick is placed automatically by default. */
+         new->centre[ axis ] = AST__BAD;
+
+/* The gap between tick marks and the number of minor tick marks will be
+   found automatically by default. */
+         new->gap[ axis ] = AST__BAD;
+         new->loggap[ axis ] = AST__BAD;
+         new->mintick[ axis ] = -1;
+
+/* Both axes will be labelled by default. */
+         new->numlab[ axis ] = -1;
+         new->textlab[ axis ] = -1;
+         new->labelunits[ axis ] = -1;
+
+/* Log/lin attributes. Default value is to use linear axes. */
+         new->logplot[ axis ] = -1;
+         new->logticks[ axis ] = -1;
+         new->loglabel[ axis ] = -1;
+
+/* Initialise the components used to store the values actually used
+   for attributes which have dynamic defaults. */
+         new->ulglb[ axis ] = new->loglabel[ axis ];
+         new->ulgtk[ axis ] = new->logticks[ axis ];
+         new->uloggap[ axis ] = new->loggap[ axis ];
+         new->ugap[ axis ] = new->gap[ axis ];
+         new->ucentre[ axis ] = new->centre[ axis ];
+         new->uedge[ axis ] = new->edge[ axis ];
+         new->ulblat[ axis ] = new->labelat[ axis ];
+         new->ulbunit[ axis ] = new->labelunits[ axis ];
+         new->umintk[ axis ] = new->mintick[ axis ];
+         new->utxtlb[ axis ] = new->textlab[ axis ];
+         new->umjtkln[ axis ] = new->majticklen[ axis ];
+
+/* Initialise the arrays used to hold information describing the tick
+   marks that have been drawn for the axis. */
+         new->majtickgx[ axis ] = NULL;
+         new->majtickgy[ axis ] = NULL;
+         new->majtickcount[ axis ] = 0;
+         new->mintickgx[ axis ] = NULL;
+         new->mintickgy[ axis ] = NULL;
+         new->mintickcount[ axis ] = 0;
+         new->nmajtickval[ axis ] = 0;
+         new->majtickval[ axis ] = NULL;
+         new->nmintickval[ axis ] = 0;
+         new->mintickval[ axis ] = NULL;
+      }
+
+      new->ugrid = new->grid;
+      new->ulbling = new->labelling;
+      new->uborder = new->border;
+
+   }
+
+/* Annul the frame. */
+   graphicsframe = astAnnul( graphicsframe );
+
+/* If an error occurred, clean up by deleting the new object. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Annul the pointer to the base Frame and FrameSet. */
+   baseframe = astAnnul( baseframe );
+   fset = astAnnul( fset );
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstPlot *astLoadPlot_( void *mem, size_t size,
+                       AstPlotVtab *vtab, const char *name,
+                       AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadPlot
+
+*  Purpose:
+*     Load a Plot.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "Plot.h"
+*     AstPlot *astLoadPlot( void *mem, size_t size,
+*                           AstPlotVtab *vtab, const char *name,
+*                           AstChannel *channel )
+
+*  Class Membership:
+*     Plot loader.
+
+*  Description:
+*     This function is provided to load a new Plot using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Plot structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Plot at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Plot is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Plot data (sizeof(Plot)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Plot (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Plot structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstPlot) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Plot. If this is NULL, a pointer
+*        to the (static) virtual function table for the Plot class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Plot" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Plot.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPlot *new;                 /* Pointer to the new Plot */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   char *text;                   /* Textual version of integer value */
+   int axis;                     /* Zero based axis index */
+   int id;                       /* Zero based graphical object id */
+   int i;                        /* Loop count */
+   int itick;                    /* Tick mark index */
+   int nax;                      /* Number of base Frame axes */
+   int ntick;                    /* Total number of ticks */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Plot. In this case the
+   Plot belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstPlot );
+      vtab = &class_vtab;
+      name = "Plot";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitPlotVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Plot. */
+   new = astLoadFrameSet( mem, size, (AstFrameSetVtab *) vtab, name,
+                          channel );
+
+   if ( astOK ) {
+
+/* Get the number of graphics (base) frame axes - 2 for a Plot, 3 for a
+   Plot3D. */
+   nax = astGetNin( new );
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Plot" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Tol. */
+/* ---- */
+      new->tol = astReadDouble( channel, "tol", -1.0 );
+      if ( TestTol( new, status ) ) SetTol( new, new->tol, status );
+
+/* Grid. */
+/* ----- */
+      new->grid = astReadInt( channel, "grid", -1 );
+      if ( TestGrid( new, status ) ) SetGrid( new, new->grid, status );
+
+/* TickAll. */
+/* -------- */
+      new->tickall = astReadInt( channel, "tckall", -1 );
+      if ( TestTickAll( new, status ) ) SetTickAll( new, new->tickall, status );
+
+/* ForceExterior. */
+/* -------- */
+      new->forceexterior = astReadInt( channel, "frcext", -1 );
+      if ( TestForceExterior( new, status ) ) SetForceExterior( new, new->forceexterior, status );
+
+/* Invisible. */
+/* ---------- */
+      new->invisible = astReadInt( channel, "invsbl", -1 );
+      if ( TestInvisible( new, status ) ) SetInvisible( new, new->invisible, status );
+
+/* Border. */
+/* -------- */
+      new->border = astReadInt( channel, "border", -1 );
+      if ( TestBorder( new, status ) ) SetBorder( new, new->border, status );
+
+/* ClipOp. */
+/* ------- */
+      new->clipop = astReadInt( channel, "clpop", -1 );
+      if ( TestClipOp( new, status ) ) SetClipOp( new, new->clipop, status );
+
+/* Clip. */
+/* ----- */
+      new->clip = astReadInt( channel, "clip", -1 );
+      if ( TestClip( new, status ) ) SetClip( new, new->clip, status );
+
+/* DrawTitle. */
+/* --------- */
+      new->drawtitle = astReadInt( channel, "drwttl", -1 );
+      if ( TestDrawTitle( new, status ) ) SetDrawTitle( new, new->drawtitle, status );
+
+/* LabelUp(axis). */
+/* -------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "lblup%d", axis + 1 );
+         new->labelup[ axis ] = astReadInt( channel, buff, -1 );
+         if ( TestLabelUp( new, axis, status ) ) SetLabelUp( new, axis,
+                                                     new->labelup[ axis ], status );
+      }
+
+/* LogPlot(axis). */
+/* -------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "lgplt%d", axis + 1 );
+         new->logplot[ axis ] = astReadInt( channel, buff, -1 );
+         if ( TestLogPlot( new, axis, status ) ) SetLogPlot( new, axis,
+                                                     new->logplot[ axis ], status );
+      }
+
+/* LogTicks(axis). */
+/* -------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "lgtck%d", axis + 1 );
+         new->logticks[ axis ] = astReadInt( channel, buff, -1 );
+         if ( TestLogTicks( new, axis, status ) ) SetLogTicks( new, axis,
+                                                       new->logticks[ axis ], status );
+      }
+
+/* LogLabel(axis). */
+/* -------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "lglbl%d", axis + 1 );
+         new->loglabel[ axis ] = astReadInt( channel, buff, -1 );
+         if ( TestLogLabel( new, axis, status ) ) SetLogLabel( new, axis,
+                                                       new->loglabel[ axis ], status );
+      }
+
+/* DrawAxes. */
+/* --------- */
+      new->drawaxes[ 0 ] = astReadInt( channel, "drwaxs", -1 );
+
+      if(  new->drawaxes[ 0 ] != -1 ) {
+         new->drawaxes[ 1 ] = new->drawaxes[ 0 ];
+         if ( TestDrawAxes( new, 0, status ) ) SetDrawAxes( new, 0, new->drawaxes[ 0 ], status );
+         if ( TestDrawAxes( new, 1, status ) ) SetDrawAxes( new, 1, new->drawaxes[ 1 ], status );
+
+      } else {
+         for( axis = 0; axis < nax; axis++ ){
+            (void) sprintf( buff, "drwaxs%d", axis + 1 );
+            new->drawaxes[ axis ] = astReadInt( channel, buff, -1 );
+            if ( TestDrawAxes( new, axis, status ) ) SetDrawAxes( new, axis,
+                                                       new->drawaxes[ axis ], status );
+         }
+      }
+
+/* Abbrev. */
+/* ------- */
+      new->abbrev[ 0 ] = astReadInt( channel, "abbrv", -1 );
+
+      if(  new->abbrev[ 0 ] != -1 ) {
+         new->abbrev[ 1 ] = new->abbrev[ 0 ];
+         if ( TestAbbrev( new, 0, status ) ) SetAbbrev( new, 0, new->abbrev[ 0 ], status );
+         if ( TestAbbrev( new, 1, status ) ) SetAbbrev( new, 1, new->abbrev[ 1 ], status );
+
+      } else {
+         for( axis = 0; axis < nax; axis++ ){
+            (void) sprintf( buff, "abbrv%d", axis + 1 );
+            new->abbrev[ axis ] = astReadInt( channel, buff, -1 );
+            if ( TestAbbrev( new, axis, status ) ) SetAbbrev( new, axis,
+                                                       new->abbrev[ axis ], status );
+         }
+      }
+
+
+/* Escape. */
+/* ------- */
+      new->escape = astReadInt( channel, "escape", -1 );
+      if ( TestEscape( new, status ) ) SetEscape( new, new->escape, status );
+
+/* LabelAt(axis). */
+/* -------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "lblat%d", axis + 1 );
+         new->labelat[ axis ] = astReadDouble( channel, buff, AST__BAD );
+         if ( TestLabelAt( new, axis, status ) ) SetLabelAt( new, axis,
+                                                     new->labelat[ axis ], status );
+      }
+
+/* Centre(axis). */
+/* ------------ */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "cen%d", axis + 1 );
+         new->centre[ axis ] = astReadDouble( channel, buff, AST__BAD );
+         if ( TestCentre( new, axis, status ) ) SetCentre( new, axis,
+                                                   new->centre[ axis ], status );
+      }
+
+/* Gap(axis). */
+/* ---------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "gap%d", axis + 1 );
+         new->gap[ axis ] = astReadDouble( channel, buff, AST__BAD );
+         if ( TestGap( new, axis, status ) ) SetGap( new, axis, new->gap[ axis ], status );
+      }
+
+/* LogGap(axis). */
+/* ------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "lggap%d", axis + 1 );
+         new->loggap[ axis ] = astReadDouble( channel, buff, AST__BAD );
+         if ( TestLogGap( new, axis, status ) ) SetLogGap( new, axis, new->loggap[ axis ], status );
+      }
+
+/* NumLabGap(axis). */
+/* -------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "nmgap%d", axis + 1 );
+         new->numlabgap[ axis ] = astReadDouble( channel, buff, AST__BAD );
+         if ( TestNumLabGap( new, axis, status ) ) SetNumLabGap( new, axis,
+                                                     new->numlabgap[ axis ], status );
+      }
+
+/* TextLabGap(axis). */
+/* -------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "txgap%d", axis + 1 );
+         new->textlabgap[ axis ] = astReadDouble( channel, buff, AST__BAD );
+         if ( TestTextLabGap( new, axis, status ) ) SetTextLabGap( new, axis,
+                                                     new->textlabgap[ axis ], status );
+      }
+
+/* NumLab(axis). */
+/* ---------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "nmlbl%d", axis + 1 );
+         new->numlab[ axis ] = astReadInt( channel, buff, -1 );
+         if ( TestNumLab( new, axis, status ) ) SetNumLab( new, axis,
+                                                     new->numlab[ axis ], status );
+      }
+
+/* MinTick(axis). */
+/* --------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "mntks%d", axis + 1 );
+         new->mintick[ axis ] = astReadInt( channel, buff, -1 );
+         if ( TestMinTick( new, axis, status ) ) SetMinTick( new, axis,
+                                                       new->mintick[ axis ], status );
+      }
+
+/* TextLab(axis). */
+/* -------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "txlbl%d", axis + 1 );
+         new->textlab[ axis ] = astReadInt( channel, buff, -1 );
+         if ( TestTextLab( new, axis, status ) ) SetTextLab( new, axis,
+                                                      new->textlab[ axis ], status );
+      }
+
+/* LabelUnits(axis). */
+/* --------------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "lbunt%d", axis + 1 );
+         new->labelunits[ axis ] = astReadInt( channel, buff, -1 );
+         if ( TestLabelUnits( new, axis, status ) ) SetLabelUnits( new, axis,
+                                                      new->labelunits[ axis ], status );
+      }
+
+/* Style(object). */
+/* ------------ */
+      for( id = 0; id < AST__NPID; id++ ){
+         (void) sprintf( buff, "style%d", id + 1 );
+         new->style[ id ] = astReadInt( channel, buff, -1 );
+         if ( TestStyle( new, id, status ) ) SetStyle( new, id, new->style[ id ], status );
+      }
+
+/* Font(object). */
+/* ----------- */
+      for( id = 0; id < AST__NPID; id++ ){
+         (void) sprintf( buff, "font%d", id + 1 );
+         new->font[ id ] = astReadInt( channel, buff, -1 );
+         if ( TestFont( new, id, status ) ) SetFont( new, id, new->font[ id ], status );
+      }
+
+/* Colour(object). */
+/* --------------- */
+      for( id = 0; id < AST__NPID; id++ ){
+         (void) sprintf( buff, "col%d", id + 1 );
+         new->colour[ id ] = astReadInt( channel, buff, -1 );
+         if ( TestColour( new, id, status ) ) SetColour( new, id, new->colour[ id ], status );
+      }
+
+/* Width(object). */
+/* ------------ */
+      for( id = 0; id < AST__NPID; id++ ){
+         (void) sprintf( buff, "width%d", id + 1 );
+         new->width[ id ] = astReadDouble( channel, buff, AST__BAD );
+         if ( TestWidth( new, id, status ) ) SetWidth( new, id, new->width[ id ], status );
+      }
+
+/* Size(object). */
+/* ----------- */
+      for( id = 0; id < AST__NPID; id++ ){
+         (void) sprintf( buff, "size%d", id + 1 );
+         new->size[ id ] = astReadDouble( channel, buff, AST__BAD );
+         if ( TestSize( new, id, status ) ) SetSize( new, id, new->size[ id ], status );
+      }
+
+/* TitleGap. */
+/* --------- */
+      new->titlegap = astReadDouble( channel, "ttlgap", AST__BAD );
+      if ( TestTitleGap( new, status ) ) SetTitleGap( new, new->titlegap, status );
+
+/* MajTickLen. */
+/* ----------- */
+/* Retained in order to read old Plots - new Plots use MajTickLen(axis). */
+      new->majticklen[ 0 ] = astReadDouble( channel, "mjtkln", AST__BAD );
+      if( new->majticklen[ 0 ] != AST__BAD ) {
+         new->majticklen[ 1 ] = new->majticklen[ 0 ];
+         if ( TestMajTickLen( new, 0, status ) ) SetMajTickLen( new, 0, new->majticklen[ 0 ], status );
+         if ( TestMajTickLen( new, 1, status ) ) SetMajTickLen( new, 1, new->majticklen[ 1 ], status );
+
+/* MajTickLen(axis). */
+/* ----------------- */
+      } else {
+         for( axis = 0; axis < nax; axis++ ){
+            (void) sprintf( buff, "mjtkln%d", axis + 1 );
+            new->majticklen[ axis ] = astReadDouble( channel, buff, AST__BAD );
+            if ( TestMajTickLen( new, axis, status ) ) SetMajTickLen( new, axis,
+                                                        new->majticklen[ axis ], status );
+         }
+      }
+
+/* MinTickLen. */
+/* ----------- */
+/* Retained in order to read old Plots - new Plots use MinTickLen(axis). */
+      new->minticklen[ 0 ] = astReadDouble( channel, "mntkln", AST__BAD );
+      if( new->minticklen[ 0 ] != AST__BAD ) {
+         new->minticklen[ 1 ] = new->minticklen[ 0 ];
+         if ( TestMinTickLen( new, 0, status ) ) SetMinTickLen( new, 0, new->minticklen[ 0 ], status );
+         if ( TestMinTickLen( new, 1, status ) ) SetMinTickLen( new, 1, new->minticklen[ 1 ], status );
+
+/* MinTickLen(axis). */
+/* ----------------- */
+      } else {
+         for( axis = 0; axis < nax; axis++ ){
+            (void) sprintf( buff, "mntkln%d", axis + 1 );
+            new->minticklen[ axis ] = astReadDouble( channel, buff, AST__BAD );
+            if ( TestMinTickLen( new, axis, status ) ) SetMinTickLen( new, axis,
+                                                        new->minticklen[ axis ], status );
+         }
+      }
+
+/* Labelling. */
+/* ---------- */
+      text = astReadString( channel, "lbling", " " );
+      if( astOK && strcmp( text, " " ) ) {
+         new->labelling = FindString( 2, xlbling, text,
+                                      "the Plot component 'Lbling'",
+                                      "astRead", astGetClass( channel ), status );
+      } else {
+         new->labelling = -9999;
+      }
+      if ( TestLabelling( new, status ) ) SetLabelling( new, new->labelling, status );
+      text = astFree( text );
+
+/* Edge(axis). */
+/* ----------- */
+      for( axis = 0; axis < nax; axis++ ){
+         (void) sprintf( buff, "edge%d", axis + 1 );
+         text = astReadString( channel, buff, " " );
+         if( astOK && strcmp( text, " " ) ) {
+            new->edge[ axis ] = FindString( 4, xedge, text,
+                                            "the Plot component 'Edge'",
+                                            "astRead", astGetClass( channel ), status );
+         } else {
+            new->edge[ axis ] = -1;
+         }
+         if ( TestEdge( new, axis, status ) ) SetEdge( new, axis,
+                                               new->edge[ axis ], status );
+         text = astFree( text );
+      }
+
+/* Now do instance variables which are not attributes. */
+/* =================================================== */
+
+/* We have no graphics context. */
+      new->grfcontext = NULL;
+      new->grfcontextID = NULL;
+
+/* Initialise the protected Ink attribute so that visible ink is used. */
+      new->ink = -1;
+
+/* The number of bounds supplied for the clipping volume. */
+      new->clip_axes = astReadInt( channel, "clpaxs", 0 );
+      if ( new->clip_axes < 0 ) new->clip_axes = 0;
+
+/* The index of the clipping Frame within the Plot. */
+      new->clip_frame = astReadInt( channel, "clpfrm", AST__NOFRAME );
+
+/* If necessary, allocate memory to hold the bounds of the clipping volume. */
+      if( new->clip_axes > 0 ){
+         new->clip_lbnd = astMalloc( sizeof( double ) * (size_t) new->clip_axes );
+         new->clip_ubnd = astMalloc( sizeof( double ) * (size_t) new->clip_axes );
+
+/* If an error occurs, ensure that all allocated memory is freed. */
+         if ( !astOK ) {
+            new->clip_lbnd = (double *) astFree( (void *) new->clip_lbnd );
+            new->clip_ubnd = (double *) astFree( (void *) new->clip_ubnd );
+
+/* Otherwise, store the bounds. Use extreme defaults if no values are
+   available. */
+         } else {
+            for( axis = 0; axis < new->clip_axes; axis++ ){
+               (void) sprintf( buff, "clplb%d", axis + 1 );
+               new->clip_lbnd[ axis ] = astReadDouble( channel, buff, -DBL_MAX );
+
+               (void) sprintf( buff, "clpub%d", axis + 1 );
+               new->clip_ubnd[ axis ] = astReadDouble( channel, buff, DBL_MAX );
+            }
+         }
+
+/* If there are no clipping axes, store NULL pointers for the bounds
+   arrays. */
+      } else {
+         new->clip_lbnd = NULL;
+         new->clip_ubnd = NULL;
+      }
+
+/* The bounds of the plotting area in graphics coords. */
+      new->xlo = astReadDouble( channel, "xlo", 0.0 );
+      new->xhi = astReadDouble( channel, "xhi", 1.0 );
+      new->ylo = astReadDouble( channel, "ylo", 0.0 );
+      new->yhi = astReadDouble( channel, "yhi", 1.0 );
+
+/* Axis reversal flags. */
+      new->xrev = astReadInt( channel, "xrev", 0 );
+      new->yrev = astReadInt( channel, "yrev", 0 );
+
+/* The bounds of the plotting area in the base Frame of the FrameSet
+   supplied when the Plot was constructed. */
+      new->bbox[ 0 ] = astReadDouble( channel, "xb1", AST__BAD );
+      new->bbox[ 1 ] = astReadDouble( channel, "yb1", AST__BAD );
+      new->bbox[ 2 ] = astReadDouble( channel, "xb2", AST__BAD );
+      new->bbox[ 3 ] = astReadDouble( channel, "yb2", AST__BAD );
+
+/* Grf. */
+      new->grf = -1;
+      new->GAttr = CGAttrWrapper;
+      new->GBBuf = CGBBufWrapper;
+      new->GEBuf = CGEBufWrapper;
+      new->GFlush = CGFlushWrapper;
+      new->GLine = CGLineWrapper;
+      new->GMark = CGMarkWrapper;
+      new->GText = CGTextWrapper;
+      new->GCap = CGCapWrapper;
+      new->GTxExt = CGTxExtWrapper;
+      new->GScales = CGScalesWrapper;
+      new->GQch = CGQchWrapper;
+      for( i = 0; i < AST__NGRFFUN; i++ ) new->grffun[i] = NULL;
+      new->grfstack = NULL;
+      new->grfnstack = 0;
+
+/* A stack of AstGat pointers used to store the graphical attributes for
+   text within strings which include graphical escape sequences. */
+      new->gat = NULL;
+      new->ngat = 0;
+
+/* Arrays holding user-specified major and minot tick mark values. */
+      for( axis = 0; axis < 3; axis++ ) {
+         sprintf( buff, "nmjtk%d", axis + 1 );
+         ntick = astReadInt( channel, buff, 0 );
+         new->nmajtickval[ axis ] = ntick;
+         new->majtickval[ axis ] = astMalloc( ntick*sizeof( double ) );
+
+         for( itick = 0; itick < ntick; itick++ ) {
+            sprintf( buff, "mjtk%d_%d", axis + 1, itick + 1 );
+            new->majtickval[ axis ][ itick ] = astReadDouble( channel, buff,
+                                                              AST__BAD );
+         }
+
+         sprintf( buff, "nmntk%d", axis + 1 );
+         ntick = astReadInt( channel, buff, 0 );
+         new->nmintickval[ axis ] = ntick;
+         new->mintickval[ axis ] = astMalloc( ntick*sizeof( double ) );
+
+         for( itick = 0; itick < ntick; itick++ ) {
+            sprintf( buff, "mntk%d_%d", axis + 1, itick + 1 );
+            new->mintickval[ axis ][ itick ] = astReadDouble( channel, buff,
+                                                              AST__BAD );
+         }
+
+      }
+
+/* Initialise the arrays used to hold information describing the tick
+   marks that have already been drawn for each axis. */
+      for( axis = 0; axis < 3; axis++ ) {
+         new->majtickgx[ axis ] = NULL;
+         new->majtickgy[ axis ] = NULL;
+         new->majtickcount[ axis ] = 0;
+         new->mintickgx[ axis ] = NULL;
+         new->mintickgy[ axis ] = NULL;
+         new->mintickcount[ axis ] = 0;
+      }
+
+/* If an error occurred, clean up by deleting the new Plot. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Plot pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+    Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+void astBBuf_( AstPlot *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,BBuf))(this, status );
+}
+
+int astBorder_( AstPlot *this, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,Plot,Border))(this, status );
+}
+
+void astBoundingBox_( AstPlot *this, float lbnd[2], float ubnd[2], int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,BoundingBox))(this,lbnd,ubnd, status );
+}
+
+void astClip_( AstPlot *this, int iframe, const double lbnd[],
+const double ubnd[], int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,Clip))(this,iframe,lbnd,ubnd, status );
+}
+
+void astGrid_( AstPlot *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,Grid))(this, status );
+}
+
+int astCvBrk_( AstPlot *this, int ibrk, double *brk, double *vbrk,
+                   double *len, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,Plot,CvBrk))(this,ibrk,brk,vbrk,len, status );
+}
+
+void astEBuf_( AstPlot *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,EBuf))(this, status );
+}
+
+void astMirror_( AstPlot *this, int axis, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,Mirror))(this,axis, status );
+}
+
+AstPointSet *astGetDrawnTicks_( AstPlot *this, int axis, int major, int *status ){
+   if( !astOK ) return NULL;
+   return (**astMEMBER(this,Plot,GetDrawnTicks))(this,axis,major, status );
+}
+
+void astSetTickValues_( AstPlot *this, int axis, int nmajor, double *major,
+                           int nminor, double *minor, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,SetTickValues))(this,axis,nmajor,major,nminor,minor, status );
+}
+
+void astCopyPlotDefaults_( AstPlot *this, int axis, AstPlot *dplot,
+                           int daxis, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,CopyPlotDefaults))(this,axis,dplot,daxis, status );
+}
+
+int astGetLabelUnits_( AstPlot *this, int axis, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,Plot,GetLabelUnits))(this,axis, status );
+}
+
+void astMark_( AstPlot *this, int nmark, int ncoord, int indim,
+               const double *in, int type, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Plot,Mark))( this, nmark, ncoord, indim, in, type, status );
+}
+
+void astText_( AstPlot *this, const char *text, const double pos[],
+               const float up[], const char *just, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Plot,Text))( this, text, pos, up, just, status );
+}
+
+void astGridLine_( AstPlot *this, int axis, const double start[], double length, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,GridLine))(this,axis,start,length, status );
+}
+
+void astCurve_( AstPlot *this, const double start[], const double finish[], int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,Curve))(this,start,finish, status );
+}
+
+void astGenCurve_( AstPlot *this, AstMapping *map, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,GenCurve))(this,map, status );
+}
+
+void astPolyCurve_( AstPlot *this, int npoint, int ncoord, int dim,
+                    const double *in, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,PolyCurve))(this,npoint,ncoord,dim,in, status );
+}
+
+void astRegionOutline_( AstPlot *this, AstRegion *region, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,RegionOutline))(this,region,status);
+}
+
+void astGrfSet_( AstPlot *this, const char *name, AstGrfFun fun, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,GrfSet))(this,name,fun, status );
+}
+
+void astGrfPush_( AstPlot *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,GrfPush))(this, status );
+}
+
+void astGrfPop_( AstPlot *this, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,GrfPop))(this, status );
+}
+
+void astGrfWrapper_( AstPlot *this, const char *name, AstGrfWrap wrapper, int *status ){
+   if( !astOK ) return;
+   (**astMEMBER(this,Plot,GrfWrapper))(this,name,wrapper, status );
+}
+
+void astSetLogPlot_( AstPlot *this, int axis, int value, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Plot,SetLogPlot))( this, axis, value, status );
+}
+
+void astClearLogPlot_( AstPlot *this, int axis, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Plot,ClearLogPlot))( this, axis, status );
+}
+
+AstKeyMap *astGetGrfContext_( AstPlot *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Plot,GetGrfContext))( this, status );
+}
+
+/* Special public interface functions. */
+/* =================================== */
+/* These provide the public interface to certain special functions
+   whose public interface cannot be handled using macros (such as
+   astINVOKE) alone. In general, they are named after the
+   corresponding protected version of the function, but with "Id"
+   appended to the name. */
+
+/* Special interface function implementations. */
+/* ------------------------------------------- */
+AstPlot *astPlotId_( void *frame_void, const float graphbox[4],
+                     const double basebox[4], const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astPlot
+f     AST_PLOT
+
+*  Purpose:
+*     Create a Plot.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "plot.h"
+c     AstPlot *astPlot( AstFrame *frame, const float graphbox[ 4 ],
+c                       const double basebox[ 4 ], const char *options, ... )
+f     RESULT = AST_PLOT( FRAME, GRAPHBOX, BASEBOX, OPTIONS, STATUS )
+
+*  Class Membership:
+*     Plot constructor.
+
+*  Description:
+*     This function creates a new Plot and optionally initialises its
+*     attributes.
+*
+*     A Plot is a specialised form of FrameSet, in which the base
+*     Frame describes a "graphical" coordinate system and is
+*     associated with a rectangular plotting area in the underlying
+*     graphics system. This plotting area is where graphical output
+*     appears. It is defined when the Plot is created.
+*
+*     The current Frame of a Plot describes a "physical" coordinate
+*     system, which is the coordinate system in which plotting
+*     operations are specified. The results of each plotting operation
+*     are automatically transformed into graphical coordinates so as
+*     to appear in the plotting area (subject to any clipping which
+*     may be in effect).
+*
+*     Because the Mapping between physical and graphical coordinates
+*     may often be non-linear, or even discontinuous, most plotting
+*     does not result in simple straight lines. The basic plotting
+*     element is therefore not a straight line, but a geodesic curve
+c     (see astCurve). A Plot also provides facilities for drawing
+c     markers or symbols (astMark), text (astText) and grid lines
+c     (astGridLine). It is also possible to draw curvilinear axes with
+c     optional coordinate grids (astGrid).
+f     (see AST_CURVE). A Plot also provides facilities for drawing
+f     markers or symbols (AST_MARK), text (AST_TEXT) and grid lines
+f     (AST_GRIDLINE). It is also possible to draw curvilinear axes
+f     with optional coordinate grids (AST_GRID).
+*     A range of Plot attributes is available to allow precise control
+*     over the appearance of graphical output produced by these
+c     functions.
+f     routines.
+*
+*     You may select different physical coordinate systems in which to
+*     plot (including the native graphical coordinate system itself)
+*     by selecting different Frames as the current Frame of a Plot,
+*     using its Current attribute.  You may also set up clipping (see
+c     astClip) to limit the extent of any plotting you perform, and
+f     AST_CLIP) to limit the extent of any plotting you perform, and
+*     this may be done in any of the coordinate systems associated
+*     with the Plot, not necessarily the one you are plotting in.
+*
+*     Like any FrameSet, a Plot may also be used as a Frame. In this
+*     case, it behaves like its current Frame, which describes the
+*     physical coordinate system.
+*
+*     When used as a Mapping, a Plot describes the inter-relation
+*     between graphical coordinates (its base Frame) and physical
+*     coordinates (its current Frame).  It differs from a normal
+*     FrameSet, however, in that an attempt to transform points which
+*     lie in clipped areas of the Plot will result in bad coordinate
+*     values (AST__BAD).
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        Pointer to a Frame describing the physical coordinate system
+*        in which to plot. A pointer to a FrameSet may also be given,
+*        in which case its current Frame will be used to define the
+*        physical coordinate system and its base Frame will be mapped
+*        on to graphical coordinates (see below).
+*
+*        If a null Object pointer (AST__NULL) is given, a default
+*        2-dimensional Frame will be used to describe the physical
+*        coordinate system. Labels, etc. may then be attached to this
+*        by setting the appropriate Frame attributes
+*        (e.g. Label(axis)) for the Plot.
+c     graphbox
+f     GRAPHBOX( 4 ) = REAL (Given)
+*        An array giving the position and extent of the plotting area
+*        (on the plotting surface of the underlying graphics system)
+*        in which graphical output is to appear. This must be
+*        specified using graphical coordinates appropriate to the
+*        underlying graphics system.
+*
+*        The first pair of values should give the coordinates of the
+*        bottom left corner of the plotting area and the second pair
+*        should give the coordinates of the top right corner. The
+*        coordinate on the horizontal axis should be given first in
+*        each pair. Note that the order in which these points are
+*        given is important because it defines up, down, left and
+*        right for subsequent graphical operations.
+c     basebox
+f     BASEBOX( 4 ) = DOUBLE PRECISION (Given)
+*        An array giving the coordinates of two points in the supplied
+*        Frame (or in the base Frame if a FrameSet was supplied) which
+*        correspond to the bottom left and top right corners of the
+*        plotting area, as specified above. This range of coordinates
+*        will be mapped linearly on to the plotting area. The
+*        coordinates should be given in the same order as above.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Plot. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Plot. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astPlot()
+f     AST_PLOT
+*        A pointer to the new Plot.
+
+*  Notes:
+*     - The base Frame of the returned Plot will be a new Frame which
+*     is created by this function to represent the coordinate system
+*     of the underlying graphics system (graphical coordinates). It is
+*     given a Frame index of 1 within the Plot. The choice of base
+*     Frame (Base attribute) should not, in general, be changed once a
+*     Plot has been created (although you could use this as a way of
+*     moving the plotting area around on the plotting surface).
+c     - If a Frame is supplied (via the "frame" pointer), then it
+f     - If a Frame is supplied (via the FRAME pointer), then it
+*     becomes the current Frame of the new Plot and is given a Frame
+*     index of 2.
+c     - If a FrameSet is supplied (via the "frame" pointer), then
+f     - If a FrameSet is supplied (via the FRAME pointer), then
+*     all the Frames within this FrameSet become part of the new Plot
+*     (where their Frame indices are increased by 1), with the
+*     FrameSet's current Frame becoming the current Frame of the Plot.
+*     - If a null Object pointer (AST__NULL) is supplied (via the
+c     "frame" pointer), then the returned Plot will contain two
+f     FRAME pointer), then the returned Plot will contain two
+*     Frames, both created by this function. The base Frame will
+*     describe graphics coordinates (as above) and the current Frame
+*     will be a basic Frame with no attributes set (this will
+*     therefore give default values for such things as the Plot Title
+*     and the Label on each axis). Physical coordinates will be mapped
+*     linearly on to graphical coordinates.
+*     - An error will result if the Frame supplied (or the base Frame
+*     if a FrameSet was supplied) is not 2-dimensional.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astPlot constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astPlot_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - Because no checking or casting of arguments is performed
+*     before the function is invoked, the "frame" parameter is of type
+*     (void *) and is converted from an ID value to a pointer and
+*     validated within the function itself.
+*     - The variable argument list also prevents this function from
+*     invoking astPlot_ directly, so it must be a
+*     re-implementation of it in all respects, except for the
+*     conversions between IDs and pointers on input/output of Objects.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstPlot *new;                 /* Pointer to new Plot */
+   va_list args;                 /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get apointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   new = NULL;
+
+/* Obtain a Frame pointer from any ID supplied and validate the
+   pointer to ensure it identifies a valid Frame. */
+   if( frame_void ){
+      frame = astVerifyFrame( astMakePointer( frame_void ) );
+   } else {
+      frame = NULL;
+   }
+
+/* Check the pointer can be used. */
+   if ( astOK ) {
+
+/* Initialise the Plot, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitPlot( NULL, sizeof( AstPlot ), !class_init,
+                         &class_vtab, "Plot", frame, graphbox,
+                         basebox );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   Plot's attributes. */
+
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return an ID value for the new Plot. */
+   return astMakeId( new );
+
+}
+
+
+
+
diff --git a/ast/plot.h b/ast/plot.h
new file mode 100644
index 0000000..6f702ab
--- /dev/null
+++ b/ast/plot.h
@@ -0,0 +1,1417 @@
+#if !defined( PLOT_INCLUDED ) /* Include this file only once */
+#define PLOT_INCLUDED
+/*
+*+
+*  Name:
+*     plot.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Plot class.
+
+*  Invocation:
+*     #include "plot.h"
+
+*  Description:
+*     This include file defines the interface to the Plot class and
+*     provides the type definitions, function prototypes and macros, etc.
+*     needed to use this class.
+*
+*     The Plot class provides facilities for producing graphical information
+*     describing positions within coordinate systems. These include the
+*     creation of annotated coordinate axes, the plotting of markers at given
+*     physical positions, etc.
+
+*  Inheritance:
+*     The Plot class inherits from the FrameSet class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     18-SEP-1996 (DSB):
+*        Original version.
+*     28-OCT-1998 (DSB):
+*        Added method astPolyCurve.
+*     12-OCT-1999 (DSB):
+*        Allow tick marks to be specified separately for both axes.
+*     9-JAN-2001 (DSB):
+*        Change argument "in" for astMark and astPolyCurve from type
+*        "const double (*)[]" to "const double *".
+*     13-JUN-2001 (DSB):
+*        Added methods astGenCurve, astGrfSet, astGrfPop, astGrfPush and
+*        attribute Grf.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitPlotVtab method.
+*     13-JAN-2004 (DSB):
+*        Added bbox, logticks and logplot to the AstPlot structure. Added
+*        LogPlot and LogTicks accessor methods.
+*     19-JAN-2004 (DSB):
+*        Added loggap and loglabel to the AstPlot structure. Added
+*        LogGap and LogLabel accessor methods.
+*     21-MAR-2005 (DSB):
+*        - Added the Clip attribute.
+*     24-OCT-2006 (DSB):
+*        - Remove duplicated documentation from prologue.
+*        - Add ForceExterior attribute.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "frameset.h"              /* Parent FrameSet class */
+#include "keymap.h"
+#include "region.h"
+
+#if defined(astCLASS)       /* Protected */
+#include "grf.h"
+#endif
+
+/* C header files. */
+/* --------------- */
+#include <stddef.h>
+
+/* Macros. */
+/* ======= */
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+#define AST__NPID      20   /* No. of different genuine plot object id's */
+
+#define AST__GATTR	0   /* Identifiers for GRF functions */
+#define AST__GFLUSH	1   /* Note, if any items are added or changed here, */
+#define AST__GLINE	2   /* make sure that the astGrfFunID function is */
+#define AST__GMARK	3   /* updated in plot.c */
+#define AST__GTEXT	4
+#define AST__GTXEXT	5
+#define AST__GSCALES	6
+#define AST__GQCH	7
+#define AST__GCAP	8
+#define AST__GBBUF	9
+#define AST__GEBUF	10
+
+#define AST__NGRFFUN    11   /* No. of Grf functions used by Plot */
+
+#if defined(astCLASS)       /* Protected */
+#define AST__MXBRK     100  /* Max. no. of breaks in a drawn curve */
+
+/* Identifiers for the graphical elements of an annotated coord grid.
+   "Pseudo-elements" (i.e. values used to indicate a group of other
+   genuine elements) should come at the end of the list. The number of
+   genuine elements should be stored in AST__NPID. */
+#define AST__BORDER_ID       0 /* Id for astBorder curves */
+#define AST__CURVE_ID        1 /* Id for astCurve, astGenCurve or astPolyCurve curves */
+#define AST__TITLE_ID        2 /* Id for textual title */
+#define AST__MARKS_ID        3 /* Id for marks drawn by astMark */
+#define AST__TEXT_ID         4 /* Id for text strings drawn by astText */
+#define AST__AXIS1_ID        5 /* Id for axis 1 through interior tick marks */
+#define AST__AXIS2_ID        6 /* Id for axis 2 through interior tick marks */
+#define AST__AXIS3_ID        7 /* Id for axis 2 through interior tick marks */
+#define AST__NUMLAB1_ID      8 /* Id for numerical labels */
+#define AST__NUMLAB2_ID      9 /* Id for numerical labels */
+#define AST__NUMLAB3_ID     10 /* Id for numerical labels */
+#define AST__TEXTLAB1_ID    11 /* Id for textual axis labels */
+#define AST__TEXTLAB2_ID    12 /* Id for textual axis labels */
+#define AST__TEXTLAB3_ID    13 /* Id for textual axis labels */
+#define AST__TICKS1_ID      14 /* Id for major and minor tick marks */
+#define AST__TICKS2_ID      15 /* Id for major and minor tick marks */
+#define AST__TICKS3_ID      16 /* Id for major and minor tick marks */
+#define AST__GRIDLINE1_ID   17 /* Id for axis 1 astGridLine AST__curves */
+#define AST__GRIDLINE2_ID   18 /* Id for axis 2 astGridLine AST__curves */
+#define AST__GRIDLINE3_ID   19 /* Id for axis 2 astGridLine AST__curves */
+#define AST__AXES_ID        20 /* Id for axes through interior tick marks */
+#define AST__NUMLABS_ID     21 /* Id for numerical labels */
+#define AST__TEXTLABS_ID    22 /* Id for textual axis labels */
+#define AST__GRIDLINE_ID    23 /* Id for astGridLine AST__curves */
+#define AST__TICKS_ID       24 /* Id for major and minor tick marks */
+
+/* Define constants used to size global arrays in this module. */
+#define AST__PLOT_CRV_MXBRK 1000 /* Max. no. of breaks allowed in a plotted curve */
+#define AST__PLOT_STRIPESCAPES_BUFF_LEN 50 /* Length of string returned by astStripEscapes */
+
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions */
+/* ================ */
+
+/* Pre-declare the AstPlot structure so that it can be used within the
+   GRF function typedefs. */
+struct AstPlot;
+
+/* Interfaces for GRF functions */
+/* ---------------------------- */
+/* A general interface into which actual Grf functions should be cast
+   before being passed as an argument to astGrfSet. */
+typedef void (* AstGrfFun)( void );
+
+/* Interfaces for specific Grf funstions implemented in C (other languages
+   may have different interfaces). */
+typedef int (* AstGAttrFun)( AstKeyMap *, int, double, double *, int );
+typedef int (* AstGFlushFun)( AstKeyMap * );
+typedef int (* AstGBBufFun)( AstKeyMap * );
+typedef int (* AstGEBufFun)( AstKeyMap * );
+typedef int (* AstGLineFun)( AstKeyMap *, int, const float *, const float * );
+typedef int (* AstGMarkFun)( AstKeyMap *, int, const float *, const float *, int );
+typedef int (* AstGTextFun)( AstKeyMap *, const char *, float, float, const char *, float, float );
+typedef int (* AstGCapFun)( AstKeyMap *, int, int );
+typedef int (* AstGTxExtFun)( AstKeyMap *, const char *, float, float, const char *, float, float, float *, float * );
+typedef int (* AstGScalesFun)( AstKeyMap *, float *, float * );
+typedef int (* AstGQchFun)( AstKeyMap *, float *, float * );
+
+/* A general interface into which Grf Wrapper functions should be cast
+   before being passed as an argument to astGrfWrapper. */
+typedef void (* AstGrfWrap)( void );
+
+/* Interfaces for the wrapper functions which wrap specific Grf funstions. */
+typedef int (* AstGAttrWrap)( struct AstPlot *, int, double, double *, int, int * );
+typedef int (* AstGBBufWrap)( struct AstPlot *, int * );
+typedef int (* AstGEBufWrap)( struct AstPlot *, int * );
+typedef int (* AstGFlushWrap)( struct AstPlot *, int * );
+typedef int (* AstGLineWrap)( struct AstPlot *, int, const float *, const float *, int * );
+typedef int (* AstGMarkWrap)( struct AstPlot *, int, const float *, const float *, int, int * );
+typedef int (* AstGTextWrap)( struct AstPlot *, const char *, float, float, const char *, float, float, int * );
+typedef int (* AstGCapWrap)( struct AstPlot *, int, int, int * );
+typedef int (* AstGTxExtWrap)( struct AstPlot *, const char *, float, float, const char *, float, float, float *, float *, int * );
+typedef int (* AstGScalesWrap)( struct AstPlot *, float *, float *, int * );
+typedef int (* AstGQchWrap)( struct AstPlot *, float *, float *, int * );
+
+/* A structure in which a collection of Grf function pointers can be
+   stored. */
+typedef struct AstGrfPtrs {
+   AstGrfFun grffun[AST__NGRFFUN];
+   AstGAttrWrap GAttr;
+   AstGBBufWrap GBBuf;
+   AstGEBufWrap GEBuf;
+   AstGFlushWrap GFlush;
+   AstGLineWrap GLine;
+   AstGMarkWrap GMark;
+   AstGTextWrap GText;
+   AstGCapWrap GCap;
+   AstGTxExtWrap GTxExt;
+   AstGScalesWrap GScales;
+   AstGQchWrap GQch;
+} AstGrfPtrs;
+
+/* Structure holding current graphical attribute values for text. */
+typedef struct AstGat {
+   float rise;
+   double size;
+   double width;
+   double col;
+   double font;
+   double style;
+} AstGat;
+
+/* Plot structure. */
+/* ------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstPlot {
+
+/* Attributes inherited from the parent class. */
+   AstFrameSet parent;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *clip_lbnd;
+   double *clip_ubnd;
+   double centre[ 3 ];
+   double gap[ 3 ];
+   double loggap[ 3 ];
+   double labelat[ 3 ];
+   double majticklen[ 3 ];
+   double minticklen[ 3 ];
+   double numlabgap[ 3 ];
+   double size[ AST__NPID ];
+   double textlabgap[ 3 ];
+   double titlegap;
+   double tol;
+   double ucentre[ 3 ];
+   double ugap[ 3 ];
+   double uloggap[ 3 ];
+   double ulblat[ 3 ];
+   double umjtkln[ 3 ];
+   double width[ AST__NPID ];
+   double *majtickgx[ 3 ];
+   double *majtickgy[ 3 ];
+   double *mintickgx[ 3 ];
+   double *mintickgy[ 3 ];
+   int majtickcount[ 3 ];
+   int mintickcount[ 3 ];
+   int nmajtickval[ 3 ];
+   double *majtickval[ 3 ];
+   int nmintickval[ 3 ];
+   double *mintickval[ 3 ];
+   double xhi;
+   double xlo;
+   double yhi;
+   double ylo;
+   double bbox[ 4 ];
+   int border;
+   int clip_axes;
+   int clip_frame;
+   int clip;
+   int clipop;
+   int colour[ AST__NPID ];
+   int drawaxes[ 3 ];
+   int abbrev[ 3 ];
+   int escape;
+   int drawtitle;
+   int edge[ 3 ];
+   int font[ AST__NPID ];
+   int grf;
+   int grid;
+   int invisible;
+   int labelling;
+   int labelunits[ 3 ];
+   int labelup[ 3 ];
+   int mintick[ 3 ];
+   int numlab[ 3 ];
+   int style[ AST__NPID ];
+   int textlab[ 3 ];
+   int tickall;
+   int forceexterior;
+   int uborder;
+   int uedge[ 3 ];
+   int ugrid;
+   int ulbling;
+   int ulbunit[ 3 ];
+   int ulgtk[ 3 ];
+   int ulglb[ 3 ];
+   int umintk[ 3 ];
+   int utxtlb[ 3 ];
+   int xrev;
+   int yrev;
+   int ink;
+   int logplot[ 3 ];
+   int logticks[ 3 ];
+   int loglabel[ 3 ];
+   AstGrfFun grffun[AST__NGRFFUN];
+   AstGAttrWrap GAttr;
+   AstGBBufWrap GBBuf;
+   AstGEBufWrap GEBuf;
+   AstGFlushWrap GFlush;
+   AstGLineWrap GLine;
+   AstGMarkWrap GMark;
+   AstGTextWrap GText;
+   AstGCapWrap GCap;
+   AstGTxExtWrap GTxExt;
+   AstGScalesWrap GScales;
+   AstGQchWrap GQch;
+   AstGrfPtrs *grfstack;
+   int grfnstack;
+   AstGat **gat;
+   int ngat;
+   AstKeyMap *grfcontext;
+   AstKeyMap *grfcontextID;
+   float hmarkx;
+   float hmarky;
+
+} AstPlot;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+
+typedef struct AstPlotVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstFrameSetVtab FrameSet_vtab;/* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstKeyMap *(* GetGrfContext)( AstPlot *, int * );
+   AstPointSet *(* GetDrawnTicks)( AstPlot *, int, int, int * );
+   int (* Border)( AstPlot *, int * );
+   int (* CvBrk)( AstPlot *, int, double *, double *, double *, int * );
+   void (* BBuf)( AstPlot *, int * );
+   void (* BoundingBox)( AstPlot *, float[2], float[2], int * );
+   void (* Clip)( AstPlot *, int, const double [], const double [], int * );
+   void (* CopyPlotDefaults)( AstPlot *, int, AstPlot *, int, int * );
+   void (* Curve)( AstPlot *, const double [], const double [], int * );
+   void (* DrawExtraTicks)( AstPlot *, int, AstPointSet *, int * );
+   void (* EBuf)( AstPlot *, int * );
+   void (* GenCurve)( AstPlot *, AstMapping *, int * );
+   void (* GrfPop)( AstPlot *, int * );
+   void (* GrfPush)( AstPlot *, int * );
+   void (* GrfSet)( AstPlot *, const char *, AstGrfFun, int * );
+   void (* GrfWrapper)( AstPlot *, const char *, AstGrfWrap, int * );
+   void (* Grid)( AstPlot *, int * );
+   void (* GridLine)( AstPlot *, int, const double [], double, int * );
+   void (* Mark)( AstPlot *, int, int, int, const double *, int, int * );
+   void (* Mirror)( AstPlot *, int, int * );
+   void (* PolyCurve)( AstPlot *, int, int, int, const double *, int * );
+   void (* RegionOutline)( AstPlot *, AstRegion *, int * );
+   void (* SetTickValues)( AstPlot *, int, int, double *, int, double *, int * );
+   void (* Text)( AstPlot *, const char *, const double [], const float [], const char *, int * );
+
+   double (* GetTol)( AstPlot *, int * );
+   int (* TestTol)( AstPlot *, int * );
+   void (* SetTol)( AstPlot *, double, int * );
+   void (* ClearTol)( AstPlot *, int * );
+
+   int (* GetGrid)( AstPlot *, int * );
+   int (* TestGrid)( AstPlot *, int * );
+   void (* SetGrid)( AstPlot *, int, int * );
+   void (* ClearGrid)( AstPlot *, int * );
+
+   int (* GetTickAll)( AstPlot *, int * );
+   int (* TestTickAll)( AstPlot *, int * );
+   void (* SetTickAll)( AstPlot *, int, int * );
+   void (* ClearTickAll)( AstPlot *, int * );
+
+   int (* GetForceExterior)( AstPlot *, int * );
+   int (* TestForceExterior)( AstPlot *, int * );
+   void (* SetForceExterior)( AstPlot *, int, int * );
+   void (* ClearForceExterior)( AstPlot *, int * );
+
+   int (* GetInvisible)( AstPlot *, int * );
+   int (* TestInvisible)( AstPlot *, int * );
+   void (* SetInvisible)( AstPlot *, int, int * );
+   void (* ClearInvisible)( AstPlot *, int * );
+
+   int (* GetBorder)( AstPlot *, int * );
+   int (* TestBorder)( AstPlot *, int * );
+   void (* SetBorder)( AstPlot *, int, int * );
+   void (* ClearBorder)( AstPlot *, int * );
+
+   int (* GetClipOp)( AstPlot *, int * );
+   int (* TestClipOp)( AstPlot *, int * );
+   void (* SetClipOp)( AstPlot *, int, int * );
+   void (* ClearClipOp)( AstPlot *, int * );
+
+   int (* GetClip)( AstPlot *, int * );
+   int (* TestClip)( AstPlot *, int * );
+   void (* SetClip)( AstPlot *, int, int * );
+   void (* ClearClip)( AstPlot *, int * );
+
+   int (* GetGrf)( AstPlot *, int * );
+   int (* TestGrf)( AstPlot *, int * );
+   void (* SetGrf)( AstPlot *, int, int * );
+   void (* ClearGrf)( AstPlot *, int * );
+
+   int (* GetDrawTitle)( AstPlot *, int * );
+   int (* TestDrawTitle)( AstPlot *, int * );
+   void (* SetDrawTitle)( AstPlot *, int, int * );
+   void (* ClearDrawTitle)( AstPlot *, int * );
+
+   int (* GetLabelUp)( AstPlot *, int, int * );
+   int (* TestLabelUp)( AstPlot *, int, int * );
+   void (* SetLabelUp)( AstPlot *, int, int, int * );
+   void (* ClearLabelUp)( AstPlot *, int, int * );
+
+   int (* GetLogPlot)( AstPlot *, int, int * );
+   int (* TestLogPlot)( AstPlot *, int, int * );
+   void (* SetLogPlot)( AstPlot *, int, int, int * );
+   void (* ClearLogPlot)( AstPlot *, int, int * );
+
+   int (* GetLogTicks)( AstPlot *, int, int * );
+   int (* TestLogTicks)( AstPlot *, int, int * );
+   void (* SetLogTicks)( AstPlot *, int, int, int * );
+   void (* ClearLogTicks)( AstPlot *, int, int * );
+
+   int (* GetLogLabel)( AstPlot *, int, int * );
+   int (* TestLogLabel)( AstPlot *, int, int * );
+   void (* SetLogLabel)( AstPlot *, int, int, int * );
+   void (* ClearLogLabel)( AstPlot *, int, int * );
+
+   int (* GetDrawAxes)( AstPlot *, int, int * );
+   int (* TestDrawAxes)( AstPlot *, int, int * );
+   void (* SetDrawAxes)( AstPlot *, int, int, int * );
+   void (* ClearDrawAxes)( AstPlot *, int, int * );
+
+   int (* GetAbbrev)( AstPlot *, int, int * );
+   int (* TestAbbrev)( AstPlot *, int, int * );
+   void (* SetAbbrev)( AstPlot *, int, int, int * );
+   void (* ClearAbbrev)( AstPlot *, int, int * );
+
+   int (* GetEscape)( AstPlot *, int * );
+   int (* TestEscape)( AstPlot *, int * );
+   void (* SetEscape)( AstPlot *, int, int * );
+   void (* ClearEscape)( AstPlot *, int * );
+
+   int (* GetLabelling)( AstPlot *, int * );
+   int (* TestLabelling)( AstPlot *, int * );
+   void (* SetLabelling)( AstPlot *, int, int * );
+   void (* ClearLabelling)( AstPlot *, int * );
+
+   double (* GetMajTickLen)( AstPlot *, int, int * );
+   int (* TestMajTickLen)( AstPlot *, int, int * );
+   void (* SetMajTickLen)( AstPlot *, int, double, int * );
+   void (* ClearMajTickLen)( AstPlot *, int, int * );
+
+   double (* GetMinTickLen)( AstPlot *, int, int * );
+   int (* TestMinTickLen)( AstPlot *, int, int * );
+   void (* SetMinTickLen)( AstPlot *, int, double, int * );
+   void (* ClearMinTickLen)( AstPlot *, int, int * );
+
+   double (* GetNumLabGap)( AstPlot *, int, int * );
+   int (* TestNumLabGap)( AstPlot *, int, int * );
+   void (* SetNumLabGap)( AstPlot *, int, double, int * );
+   void (* ClearNumLabGap)( AstPlot *, int, int * );
+
+   double (* GetTextLabGap)( AstPlot *, int, int * );
+   int (* TestTextLabGap)( AstPlot *, int, int * );
+   void (* SetTextLabGap)( AstPlot *, int, double, int * );
+   void (* ClearTextLabGap)( AstPlot *, int, int * );
+
+   double (* GetTitleGap)( AstPlot *, int * );
+   int (* TestTitleGap)( AstPlot *, int * );
+   void (* SetTitleGap)( AstPlot *, double, int * );
+   void (* ClearTitleGap)( AstPlot *, int * );
+
+   double (* GetLabelAt)( AstPlot *, int, int * );
+   int (* TestLabelAt)( AstPlot *, int, int * );
+   void (* SetLabelAt)( AstPlot *, int, double, int * );
+   void (* ClearLabelAt)( AstPlot *, int, int * );
+
+   double (* GetGap)( AstPlot *, int, int * );
+   int (* TestGap)( AstPlot *, int, int * );
+   void (* SetGap)( AstPlot *, int, double, int * );
+   void (* ClearGap)( AstPlot *, int, int * );
+
+   double (* GetLogGap)( AstPlot *, int, int * );
+   int (* TestLogGap)( AstPlot *, int, int * );
+   void (* SetLogGap)( AstPlot *, int, double, int * );
+   void (* ClearLogGap)( AstPlot *, int, int * );
+
+   double (* GetCentre)( AstPlot *, int, int * );
+   int (* TestCentre)( AstPlot *, int, int * );
+   void (* SetCentre)( AstPlot *, int, double, int * );
+   void (* ClearCentre)( AstPlot *, int, int * );
+
+   int (* GetEdge)( AstPlot *, int, int * );
+   int (* TestEdge)( AstPlot *, int, int * );
+   void (* SetEdge)( AstPlot *, int, int, int * );
+   void (* ClearEdge)( AstPlot *, int, int * );
+
+   int (* GetNumLab)( AstPlot *, int, int * );
+   int (* TestNumLab)( AstPlot *, int, int * );
+   void (* SetNumLab)( AstPlot *, int, int, int * );
+   void (* ClearNumLab)( AstPlot *, int, int * );
+
+   int (* GetMinTick)( AstPlot *, int, int * );
+   int (* TestMinTick)( AstPlot *, int, int * );
+   void (* SetMinTick)( AstPlot *, int, int, int * );
+   void (* ClearMinTick)( AstPlot *, int, int * );
+
+   int (* GetTextLab)( AstPlot *, int, int * );
+   int (* TestTextLab)( AstPlot *, int, int * );
+   void (* SetTextLab)( AstPlot *, int, int, int * );
+   void (* ClearTextLab)( AstPlot *, int, int * );
+
+   int (* GetLabelUnits)( AstPlot *, int, int * );
+   int (* TestLabelUnits)( AstPlot *, int, int * );
+   void (* SetLabelUnits)( AstPlot *, int, int, int * );
+   void (* ClearLabelUnits)( AstPlot *, int, int * );
+
+   int (* GetStyle)( AstPlot *, int, int * );
+   int (* TestStyle)( AstPlot *, int, int * );
+   void (* SetStyle)( AstPlot *, int, int, int * );
+   void (* ClearStyle)( AstPlot *, int, int * );
+
+   int (* GetFont)( AstPlot *, int, int * );
+   int (* TestFont)( AstPlot *, int, int * );
+   void (* SetFont)( AstPlot *, int, int, int * );
+   void (* ClearFont)( AstPlot *, int, int * );
+
+   int (* GetColour)( AstPlot *, int, int * );
+   int (* TestColour)( AstPlot *, int, int * );
+   void (* SetColour)( AstPlot *, int, int, int * );
+   void (* ClearColour)( AstPlot *, int, int * );
+
+   double (* GetWidth)( AstPlot *, int, int * );
+   int (* TestWidth)( AstPlot *, int, int * );
+   void (* SetWidth)( AstPlot *, int, double, int * );
+   void (* ClearWidth)( AstPlot *, int, int * );
+
+   double (* GetSize)( AstPlot *, int, int * );
+   int (* TestSize)( AstPlot *, int, int * );
+   void (* SetSize)( AstPlot *, int, double, int * );
+   void (* ClearSize)( AstPlot *, int, int * );
+
+   int (* GetInk)( AstPlot *, int * );
+   int (* TestInk)( AstPlot *, int * );
+   void (* SetInk)( AstPlot *, int, int * );
+   void (* ClearInk)( AstPlot *, int * );
+
+} AstPlotVtab;
+
+/* Structure holding information about curves drawn by astGridLine and
+   astCurve. */
+typedef struct AstPlotCurveData{
+   int out;          /* Was the curve completely outside the clipping area? */
+   int nbrk;         /* The number of breaks in the curve. */
+   float xbrk[ AST__PLOT_CRV_MXBRK ];  /* Graphics X coordinate at each break. */
+   float ybrk[ AST__PLOT_CRV_MXBRK ];  /* Graphics Y coordinate at each break. */
+   float vxbrk[ AST__PLOT_CRV_MXBRK ]; /* X comp. of unit tangent vector */
+   float vybrk[ AST__PLOT_CRV_MXBRK ]; /* Y comp. of unit tangent vector */
+   float length;     /* Drawn length of the curve in graphics coordinates */
+} AstPlotCurveData;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstPlotGlobals {
+   AstPlotVtab Class_Vtab;
+   int Class_Init;
+   double GrfAttrs_attrs_t[ GRF__NATTR ];
+   int GrfAttrs_nesting_t;
+   double Crv_limit_t;
+   double Crv_scerr_t;
+   double Crv_tol_t;
+   double Crv_ux0_t;
+   double Crv_uy0_t;
+   double Crv_vxl_t;
+   double Crv_vyl_t;
+   double Crv_xhi_t;
+   double Crv_xl_t;
+   double Crv_xlo_t;
+   double Crv_yhi_t;
+   double Crv_yl_t;
+   double Crv_ylo_t;
+   float *Crv_vxbrk_t;
+   float *Crv_vybrk_t;
+   float *Crv_xbrk_t;
+   float *Crv_ybrk_t;
+   float Crv_len_t;
+   int Crv_ink_t;
+   int Crv_nbrk_t;
+   int Crv_nent_t;
+   int Crv_out_t;
+   int Crv_clip_t;
+   void (*Crv_map_t)( int, double *, double *, double *, const char *, const char *, int *, struct AstGlobals * );
+   void *Crv_mapstatics_t;
+   float Box_lbnd_t[ 2 ];
+   float Box_ubnd_t[ 2 ];
+   float Boxp_lbnd_t[ 2 ];
+   float Boxp_ubnd_t[ 2 ];
+   int Boxp_freeze_t;
+   float 	*Poly_x_t;
+   float 	*Poly_y_t;
+   int   	 Poly_n_t;
+   float       **Poly_xp_t;
+   float       **Poly_yp_t;
+   int          *Poly_np_t;
+   int   	 Poly_npoly_t;
+   int           Map1_ncoord_t;
+   AstPlot      *Map1_plot_t;
+   AstMapping   *Map1_map_t;
+   AstFrame     *Map1_frame_t;
+   const double *Map1_origin_t;
+   double        Map1_length_t;
+   int           Map1_axis_t;
+   void         *Map1_statics_t;
+   int           Map1_norm_t;
+   int           Map1_log_t;
+   int           Map2_ncoord_t;
+   AstPlot      *Map2_plot_t;
+   AstMapping   *Map2_map_t;
+   double        Map2_x0_t;
+   double        Map2_y0_t;
+   double        Map2_deltax_t;
+   double        Map2_deltay_t;
+   void         *Map2_statics_t;
+   int           Map3_ncoord_t;
+   AstPlot      *Map3_plot_t;
+   AstMapping   *Map3_map_t;
+   AstFrame     *Map3_frame_t;
+   const double *Map3_origin_t;
+   const double *Map3_end_t;
+   double        Map3_scale_t;
+   void         *Map3_statics_t;
+   int           Map4_ncoord_t;
+   AstPlot      *Map4_plot_t;
+   AstMapping   *Map4_map_t;
+   AstMapping   *Map4_umap_t;
+   void         *Map4_statics_t;
+   int           Map5_ncoord_t;
+   AstPlot      *Map5_plot_t;
+   AstMapping   *Map5_map_t;
+   AstRegion    *Map5_region_t;
+   void         *Map5_statics_t;
+   AstPlotCurveData Curve_data_t;
+   char 	GetAttrib_Buff[ 200 ];
+   char 	SplitValue_Buff[ 200 ];
+   char 	StripEscapes_Buff[ AST__PLOT_STRIPESCAPES_BUFF_LEN + 1 ];
+   double 	Grf_chv_t;
+   double 	Grf_chh_t;
+   float 	Grf_alpha_t;
+   float 	Grf_beta_t;
+} AstPlotGlobals;
+
+#endif
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Plot)          /* Check class membership */
+astPROTO_ISA(Plot)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstPlot *astPlot_( void *, const float *, const double *, const char *, int *, ...);
+#else
+AstPlot *astPlotId_( void *, const float [], const double [], const char *, ... )__attribute__((format(printf,4,5)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstPlot *astInitPlot_( void *, size_t, int, AstPlotVtab *,
+                       const char *, AstFrame *, const float *, const double *, int * );
+
+/* Vtab initialiser. */
+void astInitPlotVtab_( AstPlotVtab *, const char *, int * );
+
+/* Loader. */
+AstPlot *astLoadPlot_( void *, size_t, AstPlotVtab *,
+                       const char *, AstChannel *channel, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitPlotGlobals_( AstPlotGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+   AstKeyMap *astGetGrfContext_( AstPlot *, int * );
+   AstKeyMap *astGrfConID_( AstPlot *, int * );
+   const char *astStripEscapes_( const char *, int * );
+   int astBorder_( AstPlot *, int * );
+   int astFindEscape_( const char *, int *, int *, int *, int * );
+   void astBBuf_( AstPlot *, int * );
+   void astBoundingBox_( AstPlot *, float[2], float[2], int * );
+   void astClip_( AstPlot *, int, const double [], const double [], int * );
+   void astCurve_( AstPlot *, const double [], const double [], int * );
+   void astEBuf_( AstPlot *, int * );
+   void astGenCurve_( AstPlot *, AstMapping *, int * );
+   void astGrfPop_( AstPlot *, int * );
+   void astGrfPush_( AstPlot *, int * );
+   void astGrfSet_( AstPlot *, const char *, AstGrfFun, int * );
+   void astGridLine_( AstPlot *, int, const double [], double, int * );
+   void astGrid_( AstPlot *, int * );
+   void astMark_( AstPlot *, int, int, int, const double *, int, int * );
+   void astPolyCurve_( AstPlot *, int, int, int, const double *, int * );
+   void astRegionOutline_( AstPlot *, AstRegion *, int * );
+   void astText_( AstPlot *, const char *, const double [], const float [], const char *, int * );
+
+   void astGrfWrapper_( AstPlot *, const char *, AstGrfWrap, int * );
+   int astGrfFunID_( const char *, const char *, const char *, int * );
+
+#if defined(astCLASS)            /* Protected */
+   int astCvBrk_( AstPlot *, int, double *, double *, double *, int * );
+   void astCopyPlotDefaults_( AstPlot *, int, AstPlot *, int, int * );
+   void astMirror_( AstPlot *, int, int * );
+   AstPointSet *astGetDrawnTicks_( AstPlot *, int, int, int * );
+   void astSetTickValues_( AstPlot *, int, int, double *, int, double *, int * );
+   void astDrawExtraTicks_( AstPlot *, int, AstPointSet *, int * );
+   void astGrfAttrs_( AstPlot *, int, int, int, const char *, const char *, int * );
+
+   double astGetTol_( AstPlot *, int * );
+   int astTestTol_( AstPlot *, int * );
+   void astSetTol_( AstPlot *, double, int * );
+   void astClearTol_( AstPlot *, int * );
+
+   int astGetGrid_( AstPlot *, int * );
+   int astTestGrid_( AstPlot *, int * );
+   void astSetGrid_( AstPlot *, int, int * );
+   void astClearGrid_( AstPlot *, int * );
+
+   int astGetTickAll_( AstPlot *, int * );
+   int astTestTickAll_( AstPlot *, int * );
+   void astSetTickAll_( AstPlot *, int, int * );
+   void astClearTickAll_( AstPlot *, int * );
+
+   int astGetForceExterior_( AstPlot *, int * );
+   int astTestForceExterior_( AstPlot *, int * );
+   void astSetForceExterior_( AstPlot *, int, int * );
+   void astClearForceExterior_( AstPlot *, int * );
+
+   int astGetInvisible_( AstPlot *, int * );
+   int astTestInvisible_( AstPlot *, int * );
+   void astSetInvisible_( AstPlot *, int, int * );
+   void astClearInvisible_( AstPlot *, int * );
+
+   int astGetBorder_( AstPlot *, int * );
+   int astTestBorder_( AstPlot *, int * );
+   void astSetBorder_( AstPlot *, int, int * );
+   void astClearBorder_( AstPlot *, int * );
+
+   int astGetClip_( AstPlot *, int * );
+   int astTestClip_( AstPlot *, int * );
+   void astSetClip_( AstPlot *, int, int * );
+   void astClearClip_( AstPlot *, int * );
+
+   int astGetClipOp_( AstPlot *, int * );
+   int astTestClipOp_( AstPlot *, int * );
+   void astSetClipOp_( AstPlot *, int, int * );
+   void astClearClipOp_( AstPlot *, int * );
+
+   int astGetGrf_( AstPlot *, int * );
+   int astTestGrf_( AstPlot *, int * );
+   void astSetGrf_( AstPlot *, int, int * );
+   void astClearGrf_( AstPlot *, int * );
+
+   int astGetDrawTitle_( AstPlot *, int * );
+   int astTestDrawTitle_( AstPlot *, int * );
+   void astSetDrawTitle_( AstPlot *, int, int * );
+   void astClearDrawTitle_( AstPlot *, int * );
+
+   int astGetLabelUp_( AstPlot *, int, int * );
+   int astTestLabelUp_( AstPlot *, int, int * );
+   void astSetLabelUp_( AstPlot *, int, int, int * );
+   void astClearLabelUp_( AstPlot *, int, int * );
+
+   int astGetLogPlot_( AstPlot *, int, int * );
+   int astTestLogPlot_( AstPlot *, int, int * );
+   void astSetLogPlot_( AstPlot *, int, int, int * );
+   void astClearLogPlot_( AstPlot *, int, int * );
+
+   int astGetLogTicks_( AstPlot *, int, int * );
+   int astTestLogTicks_( AstPlot *, int, int * );
+   void astSetLogTicks_( AstPlot *, int, int, int * );
+   void astClearLogTicks_( AstPlot *, int, int * );
+
+   int astGetLogLabel_( AstPlot *, int, int * );
+   int astTestLogLabel_( AstPlot *, int, int * );
+   void astSetLogLabel_( AstPlot *, int, int, int * );
+   void astClearLogLabel_( AstPlot *, int, int * );
+
+   int astGetDrawAxes_( AstPlot *, int, int * );
+   int astTestDrawAxes_( AstPlot *, int, int * );
+   void astSetDrawAxes_( AstPlot *, int, int, int * );
+   void astClearDrawAxes_( AstPlot *, int, int * );
+
+   int astGetAbbrev_( AstPlot *, int, int * );
+   int astTestAbbrev_( AstPlot *, int, int * );
+   void astSetAbbrev_( AstPlot *, int, int, int * );
+   void astClearAbbrev_( AstPlot *, int, int * );
+
+   int astGetEscape_( AstPlot *, int * );
+   int astTestEscape_( AstPlot *, int * );
+   void astSetEscape_( AstPlot *, int, int * );
+   void astClearEscape_( AstPlot *, int * );
+
+   double astGetLabelAt_( AstPlot *, int, int * );
+   int astTestLabelAt_( AstPlot *, int, int * );
+   void astSetLabelAt_( AstPlot *, int, double, int * );
+   void astClearLabelAt_( AstPlot *, int, int * );
+
+   double astGetGap_( AstPlot *, int, int * );
+   int astTestGap_( AstPlot *, int, int * );
+   void astSetGap_( AstPlot *, int, double, int * );
+   void astClearGap_( AstPlot *, int, int * );
+
+   double astGetLogGap_( AstPlot *, int, int * );
+   int astTestLogGap_( AstPlot *, int, int * );
+   void astSetLogGap_( AstPlot *, int, double, int * );
+   void astClearLogGap_( AstPlot *, int, int * );
+
+   double astGetCentre_( AstPlot *, int, int * );
+   int astTestCentre_( AstPlot *, int, int * );
+   void astSetCentre_( AstPlot *, int, double, int * );
+   void astClearCentre_( AstPlot *, int, int * );
+
+   int astGetLabelling_( AstPlot *, int * );
+   int astTestLabelling_( AstPlot *, int * );
+   void astSetLabelling_( AstPlot *, int, int * );
+   void astClearLabelling_( AstPlot *, int * );
+
+   double astGetMajTickLen_( AstPlot *, int, int * );
+   int astTestMajTickLen_( AstPlot *, int, int * );
+   void astSetMajTickLen_( AstPlot *, int, double, int * );
+   void astClearMajTickLen_( AstPlot *, int, int * );
+
+   double astGetMinTickLen_( AstPlot *, int, int * );
+   int astTestMinTickLen_( AstPlot *, int, int * );
+   void astSetMinTickLen_( AstPlot *, int, double, int * );
+   void astClearMinTickLen_( AstPlot *, int, int * );
+
+   double astGetNumLabGap_( AstPlot *, int, int * );
+   int astTestNumLabGap_( AstPlot *, int, int * );
+   void astSetNumLabGap_( AstPlot *, int, double, int * );
+   void astClearNumLabGap_( AstPlot *, int, int * );
+
+   double astGetTextLabGap_( AstPlot *, int, int * );
+   int astTestTextLabGap_( AstPlot *, int, int * );
+   void astSetTextLabGap_( AstPlot *, int, double, int * );
+   void astClearTextLabGap_( AstPlot *, int, int * );
+
+   double astGetTitleGap_( AstPlot *, int * );
+   int astTestTitleGap_( AstPlot *, int * );
+   void astSetTitleGap_( AstPlot *, double, int * );
+   void astClearTitleGap_( AstPlot *, int * );
+
+   int astGetEdge_( AstPlot *, int, int * );
+   int astTestEdge_( AstPlot *, int, int * );
+   void astSetEdge_( AstPlot *, int, int, int * );
+   void astClearEdge_( AstPlot *, int, int * );
+
+   int astGetMinTick_( AstPlot *, int, int * );
+   int astTestMinTick_( AstPlot *, int, int * );
+   void astSetMinTick_( AstPlot *, int, int, int * );
+   void astClearMinTick_( AstPlot *, int, int * );
+
+   int astGetNumLab_( AstPlot *, int, int * );
+   int astTestNumLab_( AstPlot *, int, int * );
+   void astSetNumLab_( AstPlot *, int, int, int * );
+   void astClearNumLab_( AstPlot *, int, int * );
+
+   int astGetTextLab_( AstPlot *, int, int * );
+   int astTestTextLab_( AstPlot *, int, int * );
+   void astSetTextLab_( AstPlot *, int, int, int * );
+   void astClearTextLab_( AstPlot *, int, int * );
+
+   int astGetLabelUnits_( AstPlot *, int, int * );
+   int astTestLabelUnits_( AstPlot *, int, int * );
+   void astSetLabelUnits_( AstPlot *, int, int, int * );
+   void astClearLabelUnits_( AstPlot *, int, int * );
+
+   int astGetStyle_( AstPlot *, int, int * );
+   int astTestStyle_( AstPlot *, int, int * );
+   void astSetStyle_( AstPlot *, int, int, int * );
+   void astClearStyle_( AstPlot *, int, int * );
+
+   int astGetFont_( AstPlot *, int, int * );
+   int astTestFont_( AstPlot *, int, int * );
+   void astSetFont_( AstPlot *, int, int, int * );
+   void astClearFont_( AstPlot *, int, int * );
+
+   int astGetColour_( AstPlot *, int, int * );
+   int astTestColour_( AstPlot *, int, int * );
+   void astSetColour_( AstPlot *, int, int, int * );
+   void astClearColour_( AstPlot *, int, int * );
+
+   double astGetWidth_( AstPlot *, int, int * );
+   int astTestWidth_( AstPlot *, int, int * );
+   void astSetWidth_( AstPlot *, int, double, int * );
+   void astClearWidth_( AstPlot *, int, int * );
+
+   double astGetSize_( AstPlot *, int, int * );
+   int astTestSize_( AstPlot *, int, int * );
+   void astSetSize_( AstPlot *, int, double, int * );
+   void astClearSize_( AstPlot *, int, int * );
+
+   int astGetInk_( AstPlot *, int * );
+   int astTestInk_( AstPlot *, int * );
+   void astSetInk_( AstPlot *, int, int * );
+   void astClearInk_( AstPlot *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class to make
+   them easier to invoke (e.g. to avoid type mis-matches when passing pointers
+   to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them to
+   validate their own arguments. We must use a cast when passing object
+   pointers (so that they can accept objects from derived classes). */
+
+/* Check class membership. */
+#define astCheckPlot(this) astINVOKE_CHECK(Plot,this,0)
+#define astVerifyPlot(this) astINVOKE_CHECK(Plot,this,1)
+
+/* Test class membership. */
+#define astIsAPlot(this) astINVOKE_ISA(Plot,this)
+
+#if defined(astCLASS)            /* Protected */
+#define astPlot astINVOKE(F,astPlot_)
+#else
+#define astPlot astINVOKE(F,astPlotId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitPlot(mem,size,init,vtab,name,frame,graph,base) \
+astINVOKE(O,astInitPlot_(mem,size,init,vtab,name,frame,graph,base,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitPlotVtab(vtab,name) astINVOKE(V,astInitPlotVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadPlot(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadPlot_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to member functions. */
+/* ------------------------------- */
+/* Here we make use of astCheckPlot (et al.) to validate Plot
+   pointers before use. This provides a contextual error report if a
+   pointer to the wrong sort of object is supplied. */
+
+
+#define astGetGrfContext(this) \
+astINVOKE(O,astGetGrfContext_(astCheckPlot(this),STATUS_PTR))
+
+#define astBorder(this) \
+astINVOKE(V,astBorder_(astCheckPlot(this),STATUS_PTR))
+
+#define astBoundingBox(this,lbnd,ubnd) \
+astINVOKE(V,astBoundingBox_(astCheckPlot(this),lbnd,ubnd,STATUS_PTR))
+
+#define astClip(this,iframe,lbnd,ubnd) \
+astINVOKE(V,astClip_(astCheckPlot(this),iframe,lbnd,ubnd,STATUS_PTR))
+
+#define astMark(this,nmark,ncoord,indim,in,type) \
+astINVOKE(V,astMark_(astCheckPlot(this),nmark,ncoord,indim,in,type,STATUS_PTR))
+
+#define astText(this,text,pos,up,just) \
+astINVOKE(V,astText_(astCheckPlot(this),text,pos,up,just,STATUS_PTR))
+
+#define astGrid(this) \
+astINVOKE(V,astGrid_(astCheckPlot(this),STATUS_PTR))
+
+#define astGridLine(this,axis,start,length) \
+astINVOKE(V,astGridLine_(astCheckPlot(this),axis,start,length,STATUS_PTR))
+
+#define astCurve(this,start,finish) \
+astINVOKE(V,astCurve_(astCheckPlot(this),start,finish,STATUS_PTR))
+
+#define astGenCurve(this,map) \
+astINVOKE(V,astGenCurve_(astCheckPlot(this),astCheckMapping(map),STATUS_PTR))
+
+#define astPolyCurve(this,npoint,ncoord,dim,in) \
+astINVOKE(V,astPolyCurve_(astCheckPlot(this),npoint,ncoord,dim,in,STATUS_PTR))
+
+#define astRegionOutline(this,region) \
+astINVOKE(V,astRegionOutline_(astCheckPlot(this),astCheckRegion(region),STATUS_PTR))
+
+#define astGrfSet(this,name,fun) \
+astINVOKE(V,astGrfSet_(astCheckPlot(this),name,fun,STATUS_PTR))
+
+#define astGrfPush(this) \
+astINVOKE(V,astGrfPush_(astCheckPlot(this),STATUS_PTR))
+
+#define astGrfPop(this) \
+astINVOKE(V,astGrfPop_(astCheckPlot(this),STATUS_PTR))
+
+#define astBBuf(this) \
+astINVOKE(V,astBBuf_(astCheckPlot(this),STATUS_PTR))
+
+#define astEBuf(this) \
+astINVOKE(V,astEBuf_(astCheckPlot(this),STATUS_PTR))
+
+
+#define astGrfFunID(name,method,class) astGrfFunID_(name,method,class,STATUS_PTR)
+#define astFindEscape(text,type,value,nc) astFindEscape_(text,type,value,nc,STATUS_PTR)
+#define astStripEscapes(text) astStripEscapes_(text,STATUS_PTR)
+#define astGrfConID(this) astGrfConID_(this,STATUS_PTR)
+
+#define astGrfWrapper(this,name,wrapper) \
+astINVOKE(V,astGrfWrapper_(astCheckPlot(this),name,wrapper,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+
+#define astGrfAttrs(this,id,set,prim,method,class) \
+astGrfAttrs_(astCheckPlot(this),id,set,prim,method,class,STATUS_PTR)
+
+#define astCvBrk(this,ibrk,brk,vbrk,len) \
+astINVOKE(V,astCvBrk_(astCheckPlot(this),ibrk,brk,vbrk,len,STATUS_PTR))
+
+#define astCopyPlotDefaults(this,axis,dplot,daxis) \
+astINVOKE(V,astCopyPlotDefaults_(astCheckPlot(this),axis,astCheckPlot(dplot),daxis,STATUS_PTR))
+
+#define astMirror(this,axis) \
+astINVOKE(V,astMirror_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astGetDrawnTicks(this,axis,major) \
+astINVOKE(O,astGetDrawnTicks_(astCheckPlot(this),axis,major,STATUS_PTR))
+
+#define astSetTickValues(this,axis,nmajor,major,nminor,minor) \
+astINVOKE(V,astSetTickValues_(astCheckPlot(this),axis,nmajor,major,nminor,minor,STATUS_PTR))
+
+#define astDrawExtraTicks(this,axis,pset) \
+astINVOKE(V,astDrawExtraTicks_(astCheckPlot(this),axis,astCheckPointSet(pset),STATUS_PTR))
+
+#define astClearTol(this) \
+astINVOKE(V,astClearTol_(astCheckPlot(this),STATUS_PTR))
+#define astGetTol(this) \
+astINVOKE(V,astGetTol_(astCheckPlot(this),STATUS_PTR))
+#define astSetTol(this,tol) \
+astINVOKE(V,astSetTol_(astCheckPlot(this),tol,STATUS_PTR))
+#define astTestTol(this) \
+astINVOKE(V,astTestTol_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearGrid(this) \
+astINVOKE(V,astClearGrid_(astCheckPlot(this),STATUS_PTR))
+#define astGetGrid(this) \
+astINVOKE(V,astGetGrid_(astCheckPlot(this),STATUS_PTR))
+#define astSetGrid(this,grid) \
+astINVOKE(V,astSetGrid_(astCheckPlot(this),grid,STATUS_PTR))
+#define astTestGrid(this) \
+astINVOKE(V,astTestGrid_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearInk(this) \
+astINVOKE(V,astClearInk_(astCheckPlot(this),STATUS_PTR))
+#define astGetInk(this) \
+astINVOKE(V,astGetInk_(astCheckPlot(this),STATUS_PTR))
+#define astSetInk(this,ink) \
+astINVOKE(V,astSetInk_(astCheckPlot(this),ink,STATUS_PTR))
+#define astTestInk(this) \
+astINVOKE(V,astTestInk_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearTickAll(this) \
+astINVOKE(V,astClearTickAll_(astCheckPlot(this),STATUS_PTR))
+#define astGetTickAll(this) \
+astINVOKE(V,astGetTickAll_(astCheckPlot(this),STATUS_PTR))
+#define astSetTickAll(this,tickall) \
+astINVOKE(V,astSetTickAll_(astCheckPlot(this),tickall,STATUS_PTR))
+#define astTestTickAll(this) \
+astINVOKE(V,astTestTickAll_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearForceExterior(this) \
+astINVOKE(V,astClearForceExterior_(astCheckPlot(this),STATUS_PTR))
+#define astGetForceExterior(this) \
+astINVOKE(V,astGetForceExterior_(astCheckPlot(this),STATUS_PTR))
+#define astSetForceExterior(this,frcext) \
+astINVOKE(V,astSetForceExterior_(astCheckPlot(this),frcext,STATUS_PTR))
+#define astTestForceExterior(this) \
+astINVOKE(V,astTestForceExterior_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearBorder(this) \
+astINVOKE(V,astClearBorder_(astCheckPlot(this),STATUS_PTR))
+#define astGetBorder(this) \
+astINVOKE(V,astGetBorder_(astCheckPlot(this),STATUS_PTR))
+#define astSetBorder(this,border) \
+astINVOKE(V,astSetBorder_(astCheckPlot(this),border,STATUS_PTR))
+#define astTestBorder(this) \
+astINVOKE(V,astTestBorder_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearClip(this) \
+astINVOKE(V,astClearClip_(astCheckPlot(this),STATUS_PTR))
+#define astGetClip(this) \
+astINVOKE(V,astGetClip_(astCheckPlot(this),STATUS_PTR))
+#define astSetClip(this,clip) \
+astINVOKE(V,astSetClip_(astCheckPlot(this),clip,STATUS_PTR))
+#define astTestClip(this) \
+astINVOKE(V,astTestClip_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearClipOp(this) \
+astINVOKE(V,astClearClipOp_(astCheckPlot(this),STATUS_PTR))
+#define astGetClipOp(this) \
+astINVOKE(V,astGetClipOp_(astCheckPlot(this),STATUS_PTR))
+#define astSetClipOp(this,clipop) \
+astINVOKE(V,astSetClipOp_(astCheckPlot(this),clipop,STATUS_PTR))
+#define astTestClipOp(this) \
+astINVOKE(V,astTestClipOp_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearInvisible(this) \
+astINVOKE(V,astClearInvisible_(astCheckPlot(this),STATUS_PTR))
+#define astGetInvisible(this) \
+astINVOKE(V,astGetInvisible_(astCheckPlot(this),STATUS_PTR))
+#define astSetInvisible(this,invisible) \
+astINVOKE(V,astSetInvisible_(astCheckPlot(this),invisible,STATUS_PTR))
+#define astTestInvisible(this) \
+astINVOKE(V,astTestInvisible_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearGrf(this) \
+astINVOKE(V,astClearGrf_(astCheckPlot(this),STATUS_PTR))
+#define astGetGrf(this) \
+astINVOKE(V,astGetGrf_(astCheckPlot(this),STATUS_PTR))
+#define astSetGrf(this,grf) \
+astINVOKE(V,astSetGrf_(astCheckPlot(this),grf,STATUS_PTR))
+#define astTestGrf(this) \
+astINVOKE(V,astTestGrf_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearDrawTitle(this) \
+astINVOKE(V,astClearDrawTitle_(astCheckPlot(this),STATUS_PTR))
+#define astGetDrawTitle(this) \
+astINVOKE(V,astGetDrawTitle_(astCheckPlot(this),STATUS_PTR))
+#define astSetDrawTitle(this,drawtitle) \
+astINVOKE(V,astSetDrawTitle_(astCheckPlot(this),drawtitle,STATUS_PTR))
+#define astTestDrawTitle(this) \
+astINVOKE(V,astTestDrawTitle_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearDrawAxes(this,axis) \
+astINVOKE(V,astClearDrawAxes_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetDrawAxes(this,axis) \
+astINVOKE(V,astGetDrawAxes_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetDrawAxes(this,axis,drawaxes) \
+astINVOKE(V,astSetDrawAxes_(astCheckPlot(this),axis,drawaxes,STATUS_PTR))
+#define astTestDrawAxes(this,axis) \
+astINVOKE(V,astTestDrawAxes_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearAbbrev(this,axis) \
+astINVOKE(V,astClearAbbrev_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetAbbrev(this,axis) \
+astINVOKE(V,astGetAbbrev_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetAbbrev(this,axis,abbrev) \
+astINVOKE(V,astSetAbbrev_(astCheckPlot(this),axis,abbrev,STATUS_PTR))
+#define astTestAbbrev(this,axis) \
+astINVOKE(V,astTestAbbrev_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearEscape(this) \
+astINVOKE(V,astClearEscape_(astCheckPlot(this),STATUS_PTR))
+#define astGetEscape(this) \
+astINVOKE(V,astGetEscape_(astCheckPlot(this),STATUS_PTR))
+#define astSetEscape(this,escape) \
+astINVOKE(V,astSetEscape_(astCheckPlot(this),escape,STATUS_PTR))
+#define astTestEscape(this) \
+astINVOKE(V,astTestEscape_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearLabelAt(this,axis) \
+astINVOKE(V,astClearLabelAt_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetLabelAt(this,axis) \
+astINVOKE(V,astGetLabelAt_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetLabelAt(this,axis,labelat) \
+astINVOKE(V,astSetLabelAt_(astCheckPlot(this),axis,labelat,STATUS_PTR))
+#define astTestLabelAt(this,axis) \
+astINVOKE(V,astTestLabelAt_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearGap(this,axis) \
+astINVOKE(V,astClearGap_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetGap(this,axis) \
+astINVOKE(V,astGetGap_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetGap(this,axis,gap) \
+astINVOKE(V,astSetGap_(astCheckPlot(this),axis,gap,STATUS_PTR))
+#define astTestGap(this,axis) \
+astINVOKE(V,astTestGap_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearLogGap(this,axis) \
+astINVOKE(V,astClearLogGap_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetLogGap(this,axis) \
+astINVOKE(V,astGetLogGap_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetLogGap(this,axis,gap) \
+astINVOKE(V,astSetLogGap_(astCheckPlot(this),axis,gap,STATUS_PTR))
+#define astTestLogGap(this,axis) \
+astINVOKE(V,astTestLogGap_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearCentre(this,axis) \
+astINVOKE(V,astClearCentre_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetCentre(this,axis) \
+astINVOKE(V,astGetCentre_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetCentre(this,axis,centre) \
+astINVOKE(V,astSetCentre_(astCheckPlot(this),axis,centre,STATUS_PTR))
+#define astTestCentre(this,axis) \
+astINVOKE(V,astTestCentre_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearMajTickLen(this,axis) \
+astINVOKE(V,astClearMajTickLen_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetMajTickLen(this,axis) \
+astINVOKE(V,astGetMajTickLen_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetMajTickLen(this,axis,majticklen) \
+astINVOKE(V,astSetMajTickLen_(astCheckPlot(this),axis,majticklen,STATUS_PTR))
+#define astTestMajTickLen(this,axis) \
+astINVOKE(V,astTestMajTickLen_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearMinTickLen(this,axis) \
+astINVOKE(V,astClearMinTickLen_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetMinTickLen(this,axis) \
+astINVOKE(V,astGetMinTickLen_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetMinTickLen(this,axis,minticklen) \
+astINVOKE(V,astSetMinTickLen_(astCheckPlot(this),axis,minticklen,STATUS_PTR))
+#define astTestMinTickLen(this,axis) \
+astINVOKE(V,astTestMinTickLen_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearNumLabGap(this,axis) \
+astINVOKE(V,astClearNumLabGap_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetNumLabGap(this,axis) \
+astINVOKE(V,astGetNumLabGap_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetNumLabGap(this,axis,numlabgap) \
+astINVOKE(V,astSetNumLabGap_(astCheckPlot(this),axis,numlabgap,STATUS_PTR))
+#define astTestNumLabGap(this,axis) \
+astINVOKE(V,astTestNumLabGap_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearTextLabGap(this,axis) \
+astINVOKE(V,astClearTextLabGap_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetTextLabGap(this,axis) \
+astINVOKE(V,astGetTextLabGap_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetTextLabGap(this,axis,textlabgap) \
+astINVOKE(V,astSetTextLabGap_(astCheckPlot(this),axis,textlabgap,STATUS_PTR))
+#define astTestTextLabGap(this,axis) \
+astINVOKE(V,astTestTextLabGap_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearTitleGap(this) \
+astINVOKE(V,astClearTitleGap_(astCheckPlot(this),STATUS_PTR))
+#define astGetTitleGap(this) \
+astINVOKE(V,astGetTitleGap_(astCheckPlot(this),STATUS_PTR))
+#define astSetTitleGap(this,titlegap) \
+astINVOKE(V,astSetTitleGap_(astCheckPlot(this),titlegap,STATUS_PTR))
+#define astTestTitleGap(this) \
+astINVOKE(V,astTestTitleGap_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearLabelling(this) \
+astINVOKE(V,astClearLabelling_(astCheckPlot(this),STATUS_PTR))
+#define astGetLabelling(this) \
+astINVOKE(V,astGetLabelling_(astCheckPlot(this),STATUS_PTR))
+#define astSetLabelling(this,labelling) \
+astINVOKE(V,astSetLabelling_(astCheckPlot(this),labelling,STATUS_PTR))
+#define astTestLabelling(this) \
+astINVOKE(V,astTestLabelling_(astCheckPlot(this),STATUS_PTR))
+
+#define astClearEdge(this,axis) \
+astINVOKE(V,astClearEdge_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetEdge(this,axis) \
+astINVOKE(V,astGetEdge_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetEdge(this,axis,edge) \
+astINVOKE(V,astSetEdge_(astCheckPlot(this),axis,edge,STATUS_PTR))
+#define astTestEdge(this,axis) \
+astINVOKE(V,astTestEdge_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearMinTick(this,axis) \
+astINVOKE(V,astClearMinTick_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetMinTick(this,axis) \
+astINVOKE(V,astGetMinTick_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetMinTick(this,axis,mintick) \
+astINVOKE(V,astSetMinTick_(astCheckPlot(this),axis,mintick,STATUS_PTR))
+#define astTestMinTick(this,axis) \
+astINVOKE(V,astTestMinTick_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearNumLab(this,axis) \
+astINVOKE(V,astClearNumLab_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetNumLab(this,axis) \
+astINVOKE(V,astGetNumLab_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetNumLab(this,axis,numlab) \
+astINVOKE(V,astSetNumLab_(astCheckPlot(this),axis,numlab,STATUS_PTR))
+#define astTestNumLab(this,axis) \
+astINVOKE(V,astTestNumLab_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearLabelUp(this,axis) \
+astINVOKE(V,astClearLabelUp_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetLabelUp(this,axis) \
+astINVOKE(V,astGetLabelUp_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetLabelUp(this,axis,labelup) \
+astINVOKE(V,astSetLabelUp_(astCheckPlot(this),axis,labelup,STATUS_PTR))
+#define astTestLabelUp(this,axis) \
+astINVOKE(V,astTestLabelUp_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearLogPlot(this,axis) \
+astINVOKE(V,astClearLogPlot_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetLogPlot(this,axis) \
+astINVOKE(V,astGetLogPlot_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetLogPlot(this,axis,logplot) \
+astINVOKE(V,astSetLogPlot_(astCheckPlot(this),axis,logplot,STATUS_PTR))
+#define astTestLogPlot(this,axis) \
+astINVOKE(V,astTestLogPlot_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearLogTicks(this,axis) \
+astINVOKE(V,astClearLogTicks_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetLogTicks(this,axis) \
+astINVOKE(V,astGetLogTicks_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetLogTicks(this,axis,logticks) \
+astINVOKE(V,astSetLogTicks_(astCheckPlot(this),axis,logticks,STATUS_PTR))
+#define astTestLogTicks(this,axis) \
+astINVOKE(V,astTestLogTicks_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearLogLabel(this,axis) \
+astINVOKE(V,astClearLogLabel_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetLogLabel(this,axis) \
+astINVOKE(V,astGetLogLabel_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetLogLabel(this,axis,loglabel) \
+astINVOKE(V,astSetLogLabel_(astCheckPlot(this),axis,loglabel,STATUS_PTR))
+#define astTestLogLabel(this,axis) \
+astINVOKE(V,astTestLogLabel_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearTextLab(this,axis) \
+astINVOKE(V,astClearTextLab_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetTextLab(this,axis) \
+astINVOKE(V,astGetTextLab_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetTextLab(this,axis,textlab) \
+astINVOKE(V,astSetTextLab_(astCheckPlot(this),axis,textlab,STATUS_PTR))
+#define astTestTextLab(this,axis) \
+astINVOKE(V,astTestTextLab_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearLabelUnits(this,axis) \
+astINVOKE(V,astClearLabelUnits_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetLabelUnits(this,axis) \
+astINVOKE(V,astGetLabelUnits_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetLabelUnits(this,axis,labelunits) \
+astINVOKE(V,astSetLabelUnits_(astCheckPlot(this),axis,labelunits,STATUS_PTR))
+#define astTestLabelUnits(this,axis) \
+astINVOKE(V,astTestLabelUnits_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearStyle(this,axis) \
+astINVOKE(V,astClearStyle_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetStyle(this,axis) \
+astINVOKE(V,astGetStyle_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetStyle(this,axis,style) \
+astINVOKE(V,astSetStyle_(astCheckPlot(this),axis,style,STATUS_PTR))
+#define astTestStyle(this,axis) \
+astINVOKE(V,astTestStyle_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearFont(this,axis) \
+astINVOKE(V,astClearFont_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetFont(this,axis) \
+astINVOKE(V,astGetFont_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetFont(this,axis,font) \
+astINVOKE(V,astSetFont_(astCheckPlot(this),axis,font,STATUS_PTR))
+#define astTestFont(this,axis) \
+astINVOKE(V,astTestFont_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearColour(this,axis) \
+astINVOKE(V,astClearColour_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetColour(this,axis) \
+astINVOKE(V,astGetColour_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetColour(this,axis,colour) \
+astINVOKE(V,astSetColour_(astCheckPlot(this),axis,colour,STATUS_PTR))
+#define astTestColour(this,axis) \
+astINVOKE(V,astTestColour_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearWidth(this,axis) \
+astINVOKE(V,astClearWidth_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetWidth(this,axis) \
+astINVOKE(V,astGetWidth_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetWidth(this,axis,width) \
+astINVOKE(V,astSetWidth_(astCheckPlot(this),axis,width,STATUS_PTR))
+#define astTestWidth(this,axis) \
+astINVOKE(V,astTestWidth_(astCheckPlot(this),axis,STATUS_PTR))
+
+#define astClearSize(this,axis) \
+astINVOKE(V,astClearSize_(astCheckPlot(this),axis,STATUS_PTR))
+#define astGetSize(this,axis) \
+astINVOKE(V,astGetSize_(astCheckPlot(this),axis,STATUS_PTR))
+#define astSetSize(this,axis,size) \
+astINVOKE(V,astSetSize_(astCheckPlot(this),axis,size,STATUS_PTR))
+#define astTestSize(this,axis) \
+astINVOKE(V,astTestSize_(astCheckPlot(this),axis,STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/plot3d.c b/ast/plot3d.c
new file mode 100644
index 0000000..a2c83ff
--- /dev/null
+++ b/ast/plot3d.c
@@ -0,0 +1,8587 @@
+/*
+*class++
+*  Name:
+*     Plot3D
+
+*  Purpose:
+*     Provide facilities for 3D graphical output.
+
+*  Constructor Function:
+c     astPlot3D
+f     AST_PLOT3D
+
+*  Description:
+*     A Plot3D is a specialised form of Plot that provides facilities
+*     for producing 3D graphical output, including fully annotated 3D
+*     coordinate grids. The base Frame in a Plot3D describes a 3-dimensional
+*     "graphical" coordinate system. The axes of this coordinate system are
+*     assumed to be right-handed (that is, if X appears horizontally to the
+*     right and Y vertically upwards, then Z is out of the screen towards
+*     the viewer), and are assumed to be equally scaled (that is, the same
+*     units are used to measure positions on each of the 3 axes). The upper
+*     and lower bounds of a volume within this graphical coordinate system
+*     is specified when the Plot3D is created, and all subsequent graphics
+*     are "drawn" in this volume.
+*
+*     The Plot3D class does not itself include any ability to draw on a
+*     graphics device. Instead it calls upon function in an externally
+*     supplied module (the "grf3d" module) to do the required drawing.
+*     A module should be written that implements the functions of the
+*     grf3d interface using the facilities of a specific graphics system
+*     This module should then be linked into the application so that the
+*     Plot3D class can use its functions (see the description of the
+*     ast_link commands for details of how to do this). The grf3d interface
+*     defines a few simple functions for drawing primitives such as straight
+*     lines, markers and character strings. These functions all accept
+*     positions in the 3D graphics coordinate system (the base Frame of the
+*     Plot3D), and so the grf3d module must also manage the projection of
+*     these 3D coordinates onto the 2D viewing surface, including the choice
+*     of "eye"/"camera" position, direction of viewing, etc. The AST
+*     library includes a sample implementation of the grf3d interface
+*     based on the PGPLOT graphics system (see file grf3d_pgplot.c). This
+*     implementation also serves to document the grf3d interface itself and
+*     should be consulted for details before writing a new implementation.
+*
+*     The current Frame of a Plot3D describes a "physical" 3-dimensional
+*     coordinate system, which is the coordinate system in which plotting
+*     operations are specified when invoking the methods of the Plot3D
+*     class. The results of each plotting operation are automatically
+*     transformed into 3D graphical coordinates before being plotted
+*     using the facilities of the grf3d module linked into the application.
+*     Note, at least one of the three axes of the current Frame must be
+*     independent of the other two current Frame axes.
+*
+*     You may select different physical coordinate systems in which to
+*     plot (including the native graphical coordinate system itself)
+*     by selecting different Frames as the current Frame of a Plot3D,
+*     using its Current attribute.
+*
+*     Like any FrameSet, a Plot3D may also be used as a Frame. In this
+*     case, it behaves like its current Frame, which describes the
+*     physical coordinate system.
+*
+*     When used as a Mapping, a Plot3D describes the inter-relation
+*     between 3D graphical coordinates (its base Frame) and 3D physical
+*     coordinates (its current Frame).
+*
+*     Although the Plot3D class inherits from the Plot class, several of
+*     the facilities of the Plot class are not available in the Plot3D
+*     class, and an error will be reported if any attempt is made to use
+*     them. Specifically, the Plot3D class does not support clipping
+*     using the
+*     astClip function.
+f     AST_CLIP routine.
+*     Nor does it support the specification of graphics primitive functions
+*     at run-time using the
+c     astGrfSet, astGrfPop, astGrfPush and astGetGrfContext functions.
+f     AST_GRFSET, AST_GRFPOP, AST_GRFPUSH, and AST_GETGRFCONTEXT routines.
+
+*  Inheritance:
+*     The Plot3D class inherits from the Plot class.
+
+*  Attributes:
+*     In addition to those attributes common to all Plots, every
+*     Plot3D also has the following attributes:
+*
+*     - Norm: Normal vector defining the 2D plane used for text and markers
+*     - RootCorner: Specifies which edges of the 3D box should be annotated.
+*
+*     Some attributes of the Plot class refer to specific physical
+*     coordinate axes (e.g. Gap, LabelUp, DrawAxes, etc). For a basic
+*     Plot, the axis index must be 1 or 2, but for a Plot3D the axis index
+*     can be 1, 2 or 3.
+*
+*     Certain Plot attributes are ignored by the Plot3D class (e.g. Edge,
+*     DrawTitle, TitleGap, etc). Consult the Plot attribute documentation
+*     for details. All other Plot attributes can be set for a specific
+*     plane of the 3-d plot by appending one of the strings "_XY", "_XZ"
+*     or "_YZ" to the end of the Plot attribute name. For instance,
+*     "Grid_YZ" refers to the "Grid" attribute for the plane spanning
+*     the second (Y) and third (Z) axes of the 3-d plot.
+
+
+*  Functions:
+c     The Plot3D class does not define any new functions beyond those
+f     The Plot3D class does not define any new routines beyond those
+*     which are applicable to all Plots. Note, however, that the
+*     following methods inherited from the Plot class cannot be used with
+*     a Plot3D and will report an error if called:
+c    - astBoundingBox, astClip, astCurve, astGenCurve,
+c    astGetGrfContext, astGrfPop, astGrfPush, astGrfSet, astGridLine,
+c    astPolyCurve.
+f    - AST_BOUNDINGBOX, AST_CLIP, AST_CURVE, AST_GENCURVE,
+f    AST_GETGRFCONTEXT, AST_GRFPOP, AST_GRFPUSH, AST_GRFSET,
+f    AST_GRIDLINE, AST_POLYCURVE.
+
+*  Copyright:
+*     Copyright (C) 2007 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     6-JUN-2007 (DSB):
+*        Original version.
+*     6-SEP-2007 (DSB):
+*        Re-code the astGrid function.
+*     12-NOV-2007 (DSB):
+*        Clear up compiler warnings.
+*     4-MAY-2012 (DSB):
+*        Avoid segvio in Grid if no ticks are drawn.
+*     21-MAY-2012 (DSB):
+*        In astLoadPlot3D, do not call SetRootCorner as it requires an
+*        active graphics system to be present which may not yet have been
+*        established. Also establish the grf routines to be used.
+*     5-OCT-2015 (DSB):
+*        Allow Plot attributes to be set for specific planes.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Plot3D
+
+/* Integers identifying the 3 plotting surfaces */
+#define XY 1
+#define XZ 2
+#define YZ 3
+
+/* Integers identifying the 8 corners of the graphics cube. */
+#define LLL 0
+#define ULL 1
+#define LUL 2
+#define UUL 3
+#define LLU 4
+#define ULU 5
+#define LUU 6
+#define UUU 7
+
+/* Identify the 4 edges of a Plot */
+#define LEFT   0
+#define TOP    1
+#define RIGHT  2
+#define BOTTOM 3
+
+/* A macros that returns a pointer to the Plot that spans a given plane. */
+#define GET_PLOT(plane) ( \
+     ( plane == XY ) ? this->plotxy : ( \
+     ( plane == XZ ) ? this->plotxz : ( \
+     ( plane == YZ ) ? this->plotyz : NULL ) ) )
+
+
+/*
+*
+*  Name:
+*     MAKE_CLEAR3
+
+*  Purpose:
+*     Implement a method to clear a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     MAKE_CLEAR3(attr,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the Plot3D class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstPlot *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( AstPlot *this, int axis )
+*
+*     which implement a method for clearing a single value in a specified
+*     multi-valued attribute for an axis of a Plot3D.
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. LabelAt in "astClearLabelAt").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR3(attr,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attr( AstPlot3D *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astClear" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Assign the "clear" value. */ \
+   } else { \
+      this->component[ axis ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attr##_( AstPlot3D *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,Plot3D,Clear##attr))( this, axis, status ); \
+}
+
+
+/*
+*
+*  Name:
+*     MAKE_GET3
+
+*  Purpose:
+*     Implement a method to get a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     MAKE_GET3(attr,type,bad_value,assign,nval)
+
+*  Class Membership:
+*     Defined by the Plot3D class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( AstPlot3D *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        <Type> astGet<Attribute>_( AstPlot3D *this, int axis )
+*
+*     which implement a method for getting a single value from a specified
+*     multi-valued attribute for an axis of a Plot3D.
+
+*  Parameters:
+*     attr
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. Label in "astGetLabel").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set, or if
+*        the function fails.
+*     assign
+*        An expression that evaluates to the value to be returned. This can
+*        use the string "axis" to represent the zero-based value index.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_GET3(attr,type,bad_value,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type Get##attr( AstPlot3D *this, int axis, int *status ) { \
+   type result;                  /* Result to be returned */ \
+\
+/* Initialise */ \
+   result = (bad_value); \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astGet" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Assign the result value. */ \
+   } else { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */  \
+type astGet##attr##_( AstPlot3D *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,Plot3D,Get##attr))( this, axis, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_SET3
+
+*  Purpose:
+*     Implement a method to set a single value in a multi-valued attribute
+*     for a Plot3D.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     MAKE_SET3(attr,type,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the Plot3D class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstPlot3D *this, int axis, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( AstPlot3D *this, int axis, <Type> value )
+*
+*     which implement a method for setting a single value in a specified
+*     multi-valued attribute for a Plot3D.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. LabelAt in "astSetLabelAt").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+*      nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1). If a value of 0 is supplied, the
+*        value of the Plot3D's Nin attribute is used instead.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_SET3(attr,type,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attr( AstPlot3D *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astSet" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Store the new value in the structure component. */ \
+   } else { \
+      this->component[ axis ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attr##_( AstPlot3D *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,Plot3D,Set##attr))( this, axis, value, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_TEST3
+
+*  Purpose:
+*     Implement a method to test if a single value has been set in a
+*     multi-valued attribute for a class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     MAKE_TEST3(attr,assign,nval)
+
+*  Class Membership:
+*     Defined by the Plot3D class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static int Test<Attribute>( AstPlot3D *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        int astTest<Attribute>_( AstPlot3D *this, int axis )
+*
+*     which implement a method for testing if a single value in a specified
+*     multi-valued attribute has been set for a class.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be tested, as it appears in the function
+*         name (e.g. LabelAt in "astTestLabelAt").
+*      assign
+*         An expression that evaluates to 0 or 1, to be used as the returned
+*         value. This can use the string "axis" to represent the zero-based
+*         index of the value within the attribute.
+*      nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_TEST3(attr,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static int Test##attr( AstPlot3D *this, int axis, int *status ) { \
+   int result;                   /* Value to return */ \
+\
+/* Initialise */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astTest" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Assign the result value. */ \
+   } else { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */ \
+int astTest##attr##_( AstPlot3D *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,Plot3D,Test##attr))( this, axis, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_CLEAR2
+
+*  Purpose:
+*     Implement a method to clear an element-specific attribute inherited
+*     from the Plot class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     MAKE_CLEAR2(attr)
+
+*  Class Membership:
+*     Defined by the Plot3d class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstPlot *this, int element )
+*
+*     which implements a method for clearing one of the element-specific
+*     attributes (e.g. Size, Colour, Width, etc) inherited from the
+*     parent Plot class.
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. LabelAt in "astClearSize").
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR2(attr) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attr( AstPlot *this_plot, int element, int *status ) { \
+\
+/* Local Variables: */ \
+   AstPlot3D *this; \
+   int axis3d; \
+   int elem2d1; \
+   int elem2d2; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Clear the attribute value in the parent Plot structure. */ \
+   (*parent_clear##attr)( this_plot, element, status ); \
+\
+/* If OK, clear the attribute in the encapsulated Plots. */ \
+   if( astOK ) { \
+      this = (AstPlot3D *) this_plot; \
+\
+/* Get the zero-based index of the 3D axis to which the supplied element \
+   refers. Use an index of -1 to indicate that the element does not \
+   relate to a specific axis. Also get the corresponding elements to use \
+   with the two Plots that share the specified 3D axis. */ \
+      axis3d = Element2D( this, element, &elem2d1, &elem2d2, status ); \
+\
+/* If the element is not axis-specific, clear the attribute value in all \
+   three plots. */ \
+      if( axis3d == -1 ) { \
+         astClear##attr( this->plotxy, element ); \
+         astClear##attr( this->plotxz, element ); \
+         astClear##attr( this->plotyz, element ); \
+\
+/* Otherwise, clear the attribute in the two plots that share the \
+   specified 3D axis. */ \
+      } else { \
+         astClear##attr( GET_PLOT(this->axis_plot1[ axis3d ]), elem2d1 ); \
+         astClear##attr( GET_PLOT(this->axis_plot2[ axis3d ]), elem2d2 ); \
+      } \
+   } \
+}
+
+/*
+*
+*  Name:
+*     MAKE_SET2
+
+*  Purpose:
+*     Implement a method to set an element-specific attribute inherited
+*     from the Plot class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     MAKE_SET2(attr,type)
+
+*  Class Membership:
+*     Defined by the Plot3d class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstPlot *this, int element, type value )
+*
+*     which implements a method for setting one of the element-specific
+*     attributes (e.g. Size, Colour, Width, etc) inherited from the
+*     parent Plot class.
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. LabelAt in "astClearSize").
+*     type
+*        The attribute data type.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_SET2(attr,type) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attr( AstPlot *this_plot, int element, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstPlot3D *this; \
+   int axis3d; \
+   int elem2d1; \
+   int elem2d2; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Set the attribute value in the parent Plot structure. */ \
+   (*parent_set##attr)( this_plot, element, value, status ); \
+\
+/* If OK, set the attribute in the encapsulated Plots. */ \
+   if( astOK ) { \
+      this = (AstPlot3D *) this_plot; \
+\
+/* Get the zero-based index of the 3D axis to which the supplied element \
+   refers. Use an index of -1 to indicate that the element does not \
+   relate to a specific axis. Also get the corresponding elements to use \
+   with the two Plots that share the specified 3D axis. */ \
+      axis3d = Element2D( this, element, &elem2d1, &elem2d2, status ); \
+\
+/* If the element is not axis-specific, clear the attribute value in all \
+   three plots. */ \
+      if( axis3d == -1 ) { \
+         astSet##attr( this->plotxy, element, value ); \
+         astSet##attr( this->plotxz, element, value ); \
+         astSet##attr( this->plotyz, element, value ); \
+\
+/* Otherwise, clear the attribute in the two plots that share the \
+   specified 3D axis. */ \
+      } else { \
+         astSet##attr( GET_PLOT(this->axis_plot1[ axis3d ]), elem2d1, value ); \
+         astSet##attr( GET_PLOT(this->axis_plot2[ axis3d ]), elem2d2, value ); \
+      } \
+   } \
+}
+
+
+/*
+*
+*  Name:
+*     MAKE_CLEAR1
+
+*  Purpose:
+*     Implement a method to clear an attribute inherited from the Plot class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     MAKE_CLEAR1(attr)
+
+*  Class Membership:
+*     Defined by the Plot3d class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstPlot *this )
+*
+*     which implements a method for clearing a Plot3D attribute inherited
+*     from the parent Plot class. It clears the attribute in all three
+*     plots encapsulated within the Plot3D.
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. LabelAt in "astClearLabelAt").
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR1(attr) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attr( AstPlot *this_plot, int *status ) { \
+\
+/* Local Variables: */ \
+   AstPlot3D *this; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Clear the attribute value in the parent Plot structure. */ \
+   (*parent_clear##attr)( this_plot, status ); \
+\
+/* If OK, clear the attribute in all three of the encapsulated Plots. */ \
+   if( astOK ) { \
+      this = (AstPlot3D *) this_plot; \
+      astClear##attr( this->plotxy ); \
+      astClear##attr( this->plotxz ); \
+      astClear##attr( this->plotyz ); \
+   } \
+}
+
+/*
+*
+*  Name:
+*     MAKE_SET1
+
+*  Purpose:
+*     Implement a method to set an attribute inherited from the Plot class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     MAKE_SET1(attr,type)
+
+*  Class Membership:
+*     Defined by the Plot3d class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstPlot *this, type value )
+*
+*     which implements a method for setting a Plot3D attribute inherited
+*     from the parent Plot class. It sets the attribute in all three
+*     plots encapsulated within the Plot3D.
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be set, as it appears in the function
+*        name (e.g. LabelAt in "astSetLabelAt").
+*     type
+*        The C data type for the attribute value.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_SET1(attr,type) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attr( AstPlot *this_plot, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstPlot3D *this; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Set the attribute value in the parent Plot structure. */ \
+   (*parent_set##attr)( this_plot, value, status ); \
+\
+/* If OK, set the attribute in all three of the encapsulated Plots. */ \
+   if( astOK ) { \
+      this = (AstPlot3D *) this_plot; \
+      astSet##attr( this->plotxy, value ); \
+      astSet##attr( this->plotxz, value ); \
+      astSet##attr( this->plotyz, value ); \
+   } \
+}
+
+/*
+*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Implement a method to clear an attribute inherited from the Plot class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     MAKE_CLEAR(attr,whichplots)
+
+*  Class Membership:
+*     Defined by the Plot3d class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstPlot *this, int axis )
+*
+*     which implements a method for clearing an axis specific Plot3D
+*     attribute inherited from the parent Plot class.
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. LabelAt in "astClearLabelAt").
+*     whichplots
+*        A value indicating which Plots should be affected. A negative value
+*        means "all threee plots", a value of zero means "just the two plots
+*        that touch at the specified axis in 3D space", a positive value
+*        means "just the Plot that is used to label th 3D axis."
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attr,whichplots) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attr( AstPlot *this_plot, int axis, int *status ) { \
+\
+/* Local Variables: */ \
+   AstPlot *plot; \
+   AstPlot3D *this; \
+   int axis2d; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Clear the attribute value in the parent Plot structure. This will \
+   validate the axis index. */ \
+   (*parent_clear##attr)( this_plot, axis, status ); \
+\
+/* If OK, clear the attribute for the relevant axis, or axes, of the Plots  \
+   encapsulated inside the Plot3D. First get a pointer to the Plot3D \
+   structure. */ \
+   if( astOK ) { \
+      this = (AstPlot3D *) this_plot; \
+\
+/* If requested clear the attribute in all three Plots. */ \
+      if( whichplots < 0 ) { \
+         astClear##attr( this->plotxy, axis ); \
+         astClear##attr( this->plotxz, axis ); \
+         astClear##attr( this->plotyz, axis ); \
+\
+/* Each axis in 3D graphics space is described by two of the encapsulated \
+   Plots, but only one of these two Plots is used to generate labels for \
+   the axis. Now deal with cases where we are clearing the attribute \
+   value in both of the two Plots that describe the axis. */ \
+      } else if ( whichplots == 0 ) { \
+         if( axis == 0 ) { \
+            astClear##attr( this->plotxy, 0 ); \
+            astClear##attr( this->plotxz, 0 ); \
+\
+         } else if( axis == 1 ) { \
+            astClear##attr( this->plotxy, 1 ); \
+            astClear##attr( this->plotyz, 0 ); \
+\
+         } else { \
+            astClear##attr( this->plotxz, 1 ); \
+            astClear##attr( this->plotyz, 1 ); \
+         } \
+\
+/* Now deal with cases where we are clearing the attribute value only in  \
+   the Plot that is used to label the axis. */ \
+      } else { \
+         plot = AxisPlot( this, axis, &axis2d, status ); \
+         astClear##attr( plot, axis2d ); \
+      } \
+   } \
+}
+
+
+/*
+*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Implement a method to get the value of an attribute inherited from the
+*     Plot class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot.h"
+*     MAKE_GET(attr,type,bad_value)
+
+*  Class Membership:
+*     Defined by the Plot3D class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( AstPlot *this, int axis )
+*
+*     which implements a method for getting a value for an axis specific
+*     attribute for a Plot3D.
+
+*  Parameters:
+*     attr
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. Label in "astGetLabel").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set, or if
+*        the function fails.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attr,type,bad_value) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type Get##attr( AstPlot *this_plot, int axis, int *status ) { \
+\
+/* Local Variables: */ \
+   AstPlot *plot; \
+   AstPlot3D *this; \
+   int axis2d; \
+   type result; \
+\
+/* Initialise */ \
+   result = (bad_value); \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* See if the attribute value is set in the parent Plot structure. If so, \
+   use the parent get method to get its value. */ \
+   if( astTest##attr( this_plot, axis ) ) { \
+      result = (*parent_get##attr)( this_plot, axis, status ); \
+\
+/* If the attribute value is not set in the parent Plot structure, get \
+   the default value from the Plot that is used to label the 3D axis. The \
+   parent test method called above will have reported an error if the axis \
+   index is invalid, so check astOK here. */ \
+   } else if( astOK ) { \
+      this = (AstPlot3D *) this_plot; \
+      plot = AxisPlot( this, axis, &axis2d, status ); \
+      result = astGet##attr( plot, axis2d ); \
+   } \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/*
+*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Implement a method to set a value for an attribute inherited from the
+*     Plot class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     MAKE_SET(attr,type,whichplots)
+
+*  Class Membership:
+*     Defined by the Plot3d class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstPlot *this, int axis, <Type> value )
+*
+*     which implements a method for setting a value for an axis specific
+*     attribute inherited from the parent Plot class.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. LabelAt in "astSetLabelAt").
+*      type
+*         The C type of the attribute.
+*     whichplots
+*        A value indicating which Plots should be affected. A negative value
+*        means "all threee plots", a value of zero means "just the two plots
+*        that touch at the specified axis in 3D space", a positive value
+*        means "just the Plot that is used to label the 3D axis."
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attr,type,whichplots) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attr( AstPlot *this_plot, int axis, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstPlot3D *this; \
+   AstPlot *plot; \
+   int axis2d; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Set the supplied value in the parent Plot class. This will validate \
+   the axis index. */ \
+   (*parent_set##attr)( this_plot, axis, value, status ); \
+\
+/* If this went OK, also set the value for the appropriate axis of the \
+   appropriate encapsulated Plot(s). First get a pointer to the Plot3D \
+   structure. */ \
+   if( astOK ) { \
+      this = (AstPlot3D *) this_plot; \
+\
+/* If requested set the attribute in all three Plots. */ \
+      if( whichplots < 0 ) { \
+         astSet##attr( this->plotxy, axis, value ); \
+         astSet##attr( this->plotxz, axis, value ); \
+         astSet##attr( this->plotyz, axis, value ); \
+\
+/* Each axis in 3D graphics space is described by two of the encapsulated \
+   Plots, but only one of these two Plots is used to generate labels for \
+   the axis. First deal with cases where we are setting the attribute \
+   value in both of the two Plots that describe the axis. */ \
+      } else if( whichplots == 0 ) { \
+         if( axis == 0 ) { \
+            astSet##attr( this->plotxy, 0, value ); \
+            astSet##attr( this->plotxz, 0, value ); \
+\
+         } else if( axis == 1 ) { \
+            astSet##attr( this->plotxy, 1, value ); \
+            astSet##attr( this->plotyz, 0, value ); \
+\
+         } else { \
+            astSet##attr( this->plotxz, 1, value ); \
+            astSet##attr( this->plotyz, 1, value ); \
+         } \
+\
+/* Now deal with cases where we are setting the attribute value only in  \
+   the Plot that is used to label the axis. */ \
+      } else { \
+         plot = AxisPlot( this, axis, &axis2d, status ); \
+         astSet##attr( plot, axis2d, value ); \
+      } \
+   } \
+}
+
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "cmpframe.h"            /* Compound Frames */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "unitmap.h"             /* Unit mappings */
+#include "permmap.h"             /* Axis permutations */
+#include "winmap.h"              /* Scale and shift mappings */
+#include "frame.h"               /* Coordinate systems */
+#include "frameset.h"            /* Inter-related coordinate systems */
+#include "keymap.h"              /* Hash array */
+#include "plot.h"                /* Interface definition for parent class */
+#include "plot3d.h"              /* Interface definition for this class */
+#include "grf3d.h"               /* The grf3D interface */
+#include "pointset.h"            /* Sets of points */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <math.h>
+#include <limits.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are used or extended by this
+   class. */
+static AstObject *(* parent_cast)( AstObject *, AstObject *, int * );
+static void (* parent_removeframe)( AstFrameSet *, int, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+static int (* parent_equal)( AstObject *, AstObject *, int * );
+static void (* parent_vset)( AstObject *, const char *, char **, va_list, int * );
+static void (* parent_clear)( AstObject *, const char *, int * );
+static void (* parent_clearcurrent)( AstFrameSet *, int * );
+static void (* parent_setcurrent)( AstFrameSet *, int, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+/* A FrameSet pointer that is used when calling astCast. */
+static AstFrameSet *dummy_frameset = NULL;
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Plot3D)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Plot3D,Class_Init)
+#define class_vtab astGLOBAL(Plot3D,Class_Vtab)
+#define getattrib_buff astGLOBAL(Plot3D,GetAttrib_Buff)
+
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+static pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX3 pthread_mutex_lock( &mutex3 );
+#define UNLOCK_MUTEX3 pthread_mutex_unlock( &mutex3 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstPlot3DVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+
+#define LOCK_MUTEX3
+#define UNLOCK_MUTEX3
+
+#endif
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstFrameSet *Fset3D( AstFrameSet *, int, int * );
+static AstKeyMap *GetGrfContext( AstPlot *, int * );
+static AstObject *Cast( AstObject *, AstObject *, int * );
+static AstPlot *AxisPlot( AstPlot3D *, int, int *, int * );
+static AstPointSet *ExtendTicks( AstPlot *, AstPointSet *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *RootCornerString( int, int * );
+static int Attr3D( AstKeyMap *, int, double, double *, int, int * );
+static int Border( AstPlot *, int * );
+static int Element2D( AstPlot3D *, int, int *, int *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetObjSize( AstObject *, int * );
+static int Plot3DAttr( AstKeyMap *, int, double, double *, int );
+static int Plot3DCap( AstKeyMap *, int, int );
+static int Plot3DFlush( AstKeyMap * );
+static int Plot3DLine( AstKeyMap *, int, const float *, const float * );
+static int Plot3DMark( AstKeyMap *, int, const float *, const float *, int );
+static int Plot3DQch( AstKeyMap *, float *, float * );
+static int Plot3DScales( AstKeyMap *, float *, float * );
+static int Plot3DText( AstKeyMap *, const char *, float, float, const char *, float, float );
+static int Plot3DTxExt( AstKeyMap *, const char *, float, float, const char *, float, float, float *, float * );
+static int RootCornerInt( const char *, int * );
+static void BoundingBox( AstPlot *, float[2], float[2], int * );
+static void ChangeRootCorner( AstPlot3D *, int, int, int * );
+static void Clear( AstObject *, const char *, int * );
+static void ClearCurrent( AstFrameSet *, int * );
+static void Clip( AstPlot *, int, const double [], const double [], int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void CreatePlots( AstPlot3D *, AstFrameSet *, const float *, const double *, int * );
+static void Curve( AstPlot *, const double [], const double [], int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void GenCurve( AstPlot *, AstMapping *, int * );
+static void GrfPop( AstPlot *, int * );
+static void GrfPush( AstPlot *, int * );
+static void GrfSet( AstPlot *, const char *,  AstGrfFun, int * );
+static void Grid( AstPlot *, int * );
+static void GridLine( AstPlot *, int, const double [], double, int * );
+static void Mark( AstPlot *, int, int, int, const double *, int, int * );
+static void PolyCurve( AstPlot *, int, int, int, const double *, int * );
+static void RemoveFrame( AstFrameSet *, int, int * );
+static void Set3DGrf( AstPlot3D *, AstPlot *, int, int * );
+static void SetCurrent( AstFrameSet *, int, int * );
+static void SetPlotAttr( AstPlot *, int, int[ 2 ], int * );
+static void SetTickValues( AstPlot *, int, int, double *, int, double *, int * );
+static void SplitFrameSet( AstFrameSet *, AstFrameSet **, int[2], int[2], AstFrameSet **, int[2], int[2], AstFrameSet **, int[2], int[2], int *, int * );
+static void StoreAxisInfo( AstPlot3D *, int[2], int[2], int[2], int[2], int[2], int[2], int * );
+static void Text( AstPlot *, const char *, const double [], const float [2], const char *, int * );
+static void UpdatePlots( AstPlot3D *, int * );
+static void VSet( AstObject *, const char *, char **, va_list, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Declare private member functions that access Plot3D attributes.
+   --------------------------------------------------------------*/
+
+/* Axis independent... */
+
+#define DECLARE_PLOT3D_ACCESSORS(attr,type) \
+   static type Get##attr(AstPlot3D *,int *); \
+   static void Set##attr(AstPlot3D *,type,int *); \
+   static void Clear##attr(AstPlot3D *,int *); \
+   static int Test##attr(AstPlot3D *,int *);
+
+DECLARE_PLOT3D_ACCESSORS(RootCorner,int)
+
+#undef DECLARE_PLOT3D_ACCESSORS
+
+
+/* Axis specific... */
+
+#define DECLARE_PLOT3D_ACCESSORS(attr,type) \
+   static type Get##attr(AstPlot3D *,int,int *); \
+   static void Set##attr(AstPlot3D *,int,type,int *); \
+   static void Clear##attr(AstPlot3D *,int,int *); \
+   static int Test##attr(AstPlot3D *,int,int *);
+
+DECLARE_PLOT3D_ACCESSORS(Norm,double)
+
+#undef DECLARE_PLOT3D_ACCESSORS
+
+
+/* Declare private member functions that access axis-specific attributes
+   inherited from the Plot class. Also declare pointers to hold the parent
+   function pointers.
+   ----------------------------------------------------------------------*/
+
+#define DECLARE_PLOT_ACCESSORS(attr,type) \
+   static type Get##attr(AstPlot *,int,int *); \
+   static void Set##attr(AstPlot *,int,type,int *); \
+   static void Clear##attr(AstPlot *,int,int *); \
+   static type (*parent_get##attr)(AstPlot *,int,int *); \
+   static void (*parent_set##attr)(AstPlot *,int,type,int *); \
+   static void (*parent_clear##attr)(AstPlot *,int,int *);
+
+DECLARE_PLOT_ACCESSORS(MinTick,int)
+DECLARE_PLOT_ACCESSORS(Abbrev,int)
+DECLARE_PLOT_ACCESSORS(Gap,double)
+DECLARE_PLOT_ACCESSORS(LogGap,double)
+DECLARE_PLOT_ACCESSORS(LogPlot,int)
+DECLARE_PLOT_ACCESSORS(LogTicks,int)
+DECLARE_PLOT_ACCESSORS(LogLabel,int)
+DECLARE_PLOT_ACCESSORS(LabelUp,int)
+DECLARE_PLOT_ACCESSORS(DrawAxes,int)
+DECLARE_PLOT_ACCESSORS(LabelUnits,int)
+DECLARE_PLOT_ACCESSORS(MinTickLen,double)
+DECLARE_PLOT_ACCESSORS(MajTickLen,double)
+DECLARE_PLOT_ACCESSORS(NumLab,int)
+DECLARE_PLOT_ACCESSORS(NumLabGap,double)
+DECLARE_PLOT_ACCESSORS(TextLab,int)
+DECLARE_PLOT_ACCESSORS(TextLabGap,double)
+
+#undef DECLARE_PLOT_ACCESSORS
+
+
+/* Declare private member functions that access element-specific attributes
+   inherited from the Plot class. Also declare pointers to hold the parent
+   function pointers.
+   ----------------------------------------------------------------------*/
+
+#define DECLARE_PLOT_ACCESSORS(attr,type) \
+   static void Set##attr(AstPlot *,int,type,int *); \
+   static void Clear##attr(AstPlot *,int,int *); \
+   static void (*parent_set##attr)(AstPlot *,int,type,int *); \
+   static void (*parent_clear##attr)(AstPlot *,int,int *);
+
+DECLARE_PLOT_ACCESSORS(Style,int)
+DECLARE_PLOT_ACCESSORS(Font,int)
+DECLARE_PLOT_ACCESSORS(Colour,int)
+DECLARE_PLOT_ACCESSORS(Width,double)
+DECLARE_PLOT_ACCESSORS(Size,double)
+
+#undef DECLARE_PLOT_ACCESSORS
+
+
+/* Declare private member functions that access attributes inherited from
+   the Plot class that do not need to override the Get method. This
+   includes attributes that are not axis-specific or that do not have
+   dynamic defaults. Also declare pointers to hold the parent function
+   pointers.
+   ----------------------------------------------------------------------*/
+#define DECLARE_PLOT_ACCESSORS(attr,type) \
+   static void Set##attr(AstPlot *,type,int *); \
+   static void Clear##attr(AstPlot *,int *); \
+   static void (*parent_set##attr)(AstPlot *,type,int *); \
+   static void (*parent_clear##attr)(AstPlot *,int *);
+
+DECLARE_PLOT_ACCESSORS(Ink,int)
+DECLARE_PLOT_ACCESSORS(Tol,double)
+DECLARE_PLOT_ACCESSORS(Invisible,int)
+DECLARE_PLOT_ACCESSORS(TickAll,int)
+DECLARE_PLOT_ACCESSORS(ForceExterior,int)
+DECLARE_PLOT_ACCESSORS(Border,int)
+DECLARE_PLOT_ACCESSORS(Clip,int)
+DECLARE_PLOT_ACCESSORS(ClipOp,int)
+DECLARE_PLOT_ACCESSORS(Escape,int)
+DECLARE_PLOT_ACCESSORS(Grid,int)
+DECLARE_PLOT_ACCESSORS(Labelling,int)
+
+#undef DECLARE_PLOT_ACCESSORS
+
+
+
+/* Member functions. */
+/* ================= */
+
+static int Attr3D( AstKeyMap *grfconID, int attr, double value,
+                   double *old_value, int prim, int *status ){
+/*
+*  Name:
+*     Attr3D
+
+*  Purpose:
+*     Get or set the value of a 3D grf attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     Attr3D( AstKeyMap *grfconID, int attr, double value, double *old_value,
+*             int prim, int *status )
+
+*  Class Membership:
+*     Plot3D member function.
+
+*  Description:
+*     This function gets or sets the current value of a specified graphics
+*     attribute in the parent Plot structure of a Plot3D. It forwards the
+*     call to the grf3D module being used by this Plot3D. It should be
+*     registered with the parent Plot using astGrfSet.
+
+*  Parameters:
+*     grfconID
+*       The Plot's GrfContext KeyMap.
+*     attr
+*       An integer value identifying the required attribute. This should
+*       be one of the symbolic values defined in grf.h.
+*     value
+*       A new value to store for the attribute. If this is AST__BAD
+*       no value is stored.
+*     old_value
+*       A pointer to a double in which to return the attribute value.
+*       If this is NULL, no value is returned.
+*     prim
+*       The sort of graphics primitive to be drawn with the new attribute.
+*       Identified by one of the values defined in grf.h.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     An integer value of 0 is returned if an error occurs, and 1 otherwise.
+
+*/
+
+/* Local Variables: */
+   int result;
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* Use the function in the external Grf3D module, selected at link-time
+   using ast_link options. */
+   result = astG3DAttr( attr, value, old_value, prim );
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Return the result. */
+   return result;
+}
+
+static AstPlot *AxisPlot( AstPlot3D *this, int axis3d, int *axis2d, int *status ){
+/*
+*  Name:
+*     AxisPlot
+
+*  Purpose:
+*     Find the Plot used to label a 3D axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     AstPlot *AxisPlot( AstPlot3D *this, int axis3d, int *axis2d, int *status )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function returns a pointer to the encapsulated 2D Plot that
+*     is used to label the given 3D axis. It also returns the index
+*     of the labelled axis within the 2D Plot.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot3D.
+*     axis3d
+*        A zero-based axis index within the Plot3D.
+*     axis2d
+*        Pointer to an int in which to put the index of the labelled axis
+*        within the returned 2D Plot.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Plot used to label the 3D axis. Do not annul this
+*     pointer.
+
+*-
+*/
+
+/* Local Variables: */
+   AstPlot *plot;
+
+/* Check the global status. */
+   if( !astOK ) return NULL;
+
+/* Return the required information form the Plot3D structure. */
+   plot = GET_PLOT( this->axis_plot1[ axis3d ] );
+   if( ! plot ) {
+      astError( AST__INTER, "AxisPlot(Plot3D): Illegal value %d "
+                "for axis3d (internal AST programming error).", status,
+                this->axis_plot1[ axis3d ] );
+   }
+
+   *axis2d = this->axis_index1[ axis3d ];
+
+   return plot;
+}
+
+static int Border( AstPlot *this_plot, int *status ){
+/*
+*  Name:
+*     Border
+
+*  Purpose:
+*     Draw a border around valid regions of a Plot.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Border( AstPlot *this, int *status )
+
+*  Class Membership:
+*     Plot method (overrides the astBorder method inherited from the
+*     Plot class)
+
+*  Description:
+*     This function draws a (line) border around regions of the
+*     plotting area of a Plot which correspond to valid, unclipped
+*     physical coordinates. For example, when plotting using an
+*     all-sky map projection, this function could be used to draw the
+*     boundary of the celestial sphere when it is projected on to the
+*     plotting surface.
+*
+*     If the entire plotting area contains valid, unclipped physical
+*     coordinates, then the boundary will just be a rectangular box
+*     around the edges of the plotting area.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero is returned if the plotting area is completely filled by
+*     valid, unclipped physical coordinates (so that only a
+*     rectangular box was drawn around the edge). Otherwise, one is
+*     returned.
+
+*  Notes:
+*     - The Plot3D implementation of this method, invokes the astBorder
+*     method on each of the three encapsulated Plots, and returns the
+*     logical OR of the three returned flags.
+*     - A value of zero will be returned if this function is invoked
+*     with the AST error status set, or if it should fail for any
+*     reason.
+*     - An error results if either the current Frame or the base Frame
+*     of the Plot is not 2-dimensional, or (for a Plot3D) 3-dimensional.
+*     - An error also results if the transformation between the base
+*     and current Frames of the Plot is not defined (i.e. the Plot's
+*     TranForward attribute is zero).
+*/
+
+/* Local Variables: */
+   AstPlot3D *this;
+   const char *class;
+   const char *method;
+   float x1;
+   float y1;
+   float z1;
+   float x[ 2 ];
+   float y[ 2 ];
+   float z[ 2 ];
+   int flag1;
+   int flag2;
+   int flag3;
+   int naxes;
+   int ok;
+   int result;
+   int root_corner;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get a pointer to the Plot3D structure. */
+   this = (AstPlot3D *) this_plot;
+
+/* Store the current method, and the class of the supplied object for use
+   in error messages.*/
+   method = "astBorder";
+   class = astGetClass( this );
+
+/* Check the base Frame of the Plot is 3-D. */
+   naxes = astGetNin( this );
+   if( naxes != 3 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 3.", status, method, class, naxes, class );
+   }
+
+/* Check the current Frame of the Plot is 3-D. */
+   naxes = astGetNout( this );
+   if( naxes != 3 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the current "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 3.", status, method, class, naxes, class );
+   }
+
+/* Invoke the astBorder method on each of the three encapsulated Plots. */
+   flag1 = astBorder( this->plotxy );
+   flag2 = astBorder( this->plotxz );
+   flag3 = astBorder( this->plotyz );
+
+/* If no bad values were encountered in any of the Plots, draw lines
+   along the remaining plot edges. */
+   result = ( flag1 || flag2 || flag3 );
+   if( !result ) {
+
+/* The three remaining edges to be drawn all meet at the corner
+   diagonally opposite the root corner. Get the root corner. */
+      root_corner = astGetRootCorner( this );
+
+/* The (x0,y0,z0) position is the graphics coords at the corner
+   diagonally opposite the root corner. The x1, y1 and z1 values
+   are the graphics x, y and z values at the ends of the three
+   lines that remain to be drawn. */
+      if( root_corner & 1 ) {
+         x[ 0 ] = this->gbox[ 0 ];
+         x1 = this->gbox[ 3 ];
+      } else {
+         x[ 0 ] = this->gbox[ 3 ];
+         x1 = this->gbox[ 0 ];
+      }
+
+      if( root_corner & 2 ) {
+         y[ 0 ] = this->gbox[ 1 ];
+         y1 = this->gbox[ 4 ];
+      } else {
+         y[ 0 ] = this->gbox[ 4 ];
+         y1 = this->gbox[ 1 ];
+      }
+
+      if( root_corner & 4 ) {
+         z[ 0 ] = this->gbox[ 2 ];
+         z1 = this->gbox[ 5 ];
+      } else {
+         z[ 0 ] = this->gbox[ 5 ];
+         z1 = this->gbox[ 2 ];
+      }
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+      astGrfAttrs( this, AST__BORDER_ID, 1, GRF__LINE, method, class );
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+      LOCK_MUTEX2;
+
+/* Draw the remaining line parallel to the X axis. */
+      x[ 1 ] = x1;
+      y[ 1 ] = y[ 0 ];
+      z[ 1 ] = z[ 0 ];
+      ok = astG3DLine( 2, x, y, z );
+
+/* Draw the remaining line parallel to the Y axis. */
+      x[ 1 ] = x[ 0 ];
+      y[ 1 ] = y1;
+      z[ 1 ] = z[ 0 ];
+      ok = ok && astG3DLine( 2, x, y, z );
+
+/* Draw the remaining line parallel to the X axis. */
+      x[ 1 ] = x[ 0 ];
+      y[ 1 ] = y[ 0 ];
+      z[ 1 ] = z1;
+      ok = ok && astG3DLine( 2, x, y, z );
+
+/* Allow the next thread to proceed. */
+      UNLOCK_MUTEX2;
+
+/* Re-establish the original graphical attributes. */
+      astGrfAttrs( this, AST__BORDER_ID, 0, GRF__LINE, method, class );
+
+/* Report an error if anything went wrong in the grf3d module. */
+      if( !ok && astOK ) {
+         astError( AST__GRFER, "%s(%s): Graphics error in astG3DLine. ", status,
+                   method, class );
+      }
+   }
+
+/* Return zero if an error has occurrred. */
+   if( !astOK ) result = 0;
+
+/* Return a flag indicating if any bad values were encountered in any of
+   the Plots. */
+   return result;
+}
+
+static AstObject *Cast( AstObject *this_object, AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Cast
+
+*  Purpose:
+*     Cast an Object into an instance of a sub-class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     AstObject *Cast( AstObject *this, AstObject *obj, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the protected astCast
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a deep copy of an ancestral component of the
+*     supplied object. The required class of the ancestral component is
+*     specified by another object. Specifically, if "this" and "new" are
+*     of the same class, a copy of "this" is returned. If "this" is an
+*     instance of a subclass of "obj", then a copy of the component
+*     of "this" that matches the class of "obj" is returned. Otherwise,
+*     a NULL pointer is returned without error.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be cast.
+*     obj
+*        Pointer to an Object that defines the class of the returned Object.
+*        The returned Object will be of the same class as "obj".
+
+*  Returned Value:
+*     A pointer to the new Object. NULL if "this" is not a sub-class of
+*     "obj", or if an error occurs.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables; */
+   AstObject *new;
+   astDECLARE_GLOBALS
+   int generation_gap;
+
+/* Initialise */
+   new = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* See how many steps up the class inheritance ladder it is from "obj"
+   to this class (Plot3D). A positive value is returned if Plot3D
+   is a sub-class of "obj". A negative value is returned if "obj" is
+   a sub-class of Plot3D. Zero is returned if "obj" is a Plot3D.
+   AST__COUSIN is returned if "obj" is not on the same line of descent
+   as Plot3D. */
+   generation_gap = astClassCompare( (AstObjectVtab *) &class_vtab,
+                                     astVTAB( obj ) );
+
+/* If "obj" is a Plot3D or a sub-class of Plot3D, we can cast by
+   truncating the vtab for "this" so that it matches the vtab of "obJ",
+   and then taking a deep copy of "this". */
+   if( generation_gap <= 0 && generation_gap != AST__COUSIN ) {
+      new = astCastCopy( this_object, obj );
+
+/* If "obj" is a Plot (the parent class), we cast by returning a deep
+   copy of the Plot covering the XY face. */
+   } else if( generation_gap == 1 ) {
+      new = astCopy( ( (AstPlot3D *) this_object)->plotxy );
+
+/* If "obj" is a FrameSet or higher, we attempt to use the implementation
+   inherited from the parent class to cast the FrameSet component into the
+   class indicated by "obj". */
+   } else {
+      new = (*parent_cast)( this_object, obj, status );
+   }
+
+/* Return the new pointer. */
+   return new;
+}
+
+static void ChangeRootCorner( AstPlot3D *this, int old, int new, int *status ){
+/*
+*  Name:
+*     ChangeRootCorner
+
+*  Purpose:
+*     Use a new RootCorner value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void ChangeRootCorner( AstPlot3D *this, int old, int new, int *status )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function sets the attributes of the encapsulated Plots so that
+*     labels appear on the edges of the 3D graphics cube that join at the
+*     specified new root corner. It also reverses the graphics axes in
+*     the encapsulated Plots as required in order to ensure that the Plots
+*     look "normal" when viewed from the outside of the 3D graphics cube.
+*     This happens if a the Plot used to label a specific axis moves from
+*     one face the the 3D graphics cube to the opposite face.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot3D.
+*     old
+*        The old RootCorner value.
+*     new
+*        The new RootCorner value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Each RootCorner value is in the range 0 to 7 and is a 3 bit
+*     bit-mask. Bit 0 describes the 3D graphics X axis, bit 1 describes
+*     the graphics Y axis and bit 2 describes the graphics Z axis. If a
+*     bit is set it means that the corner is at the upper bound on the
+*     corresponding axis. If a bit is unset it means that the corner is
+*     at the lower bound on the corresponding axis.
+
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *grfcon;
+   AstPlot *plot;
+   AstPlot *plots[ 24 ];
+   int edges[ 24 ];
+   int axes[ 24 ];
+   int axis2d;
+   int edge;
+   int i;
+   int np;
+   int xeqy;
+   int xeqz;
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* If the corner has moved on the 3D X axis (from upper X bound to lower X
+   bound or vice-versa), mirror the axis of the encapsulated Plot that is
+   perpendicular to the 3D X axis. This means that the Plot can be thought
+   of as being viewed from the outside of the 3D graphics cube. Also,
+   update the constant X axis value at which the YZ plane is drawn. */
+   if( ( old & 1 ) != ( new & 1 ) ) astMirror( this->plotyz, 0 );
+   grfcon = (AstKeyMap *) astGetGrfContext( this->plotyz );
+   astMapPut0D( grfcon, "Gcon", this->gbox[ ( new & 1 ) ? 3 : 0 ], "Constant X value" );
+   astMapPut0I( grfcon, "RootCorner", new, "Labelled corner" );
+   grfcon= astAnnul( grfcon );
+
+/* Likewise mirror the other two axes if required. */
+   if( ( old & 2 ) != ( new & 2 ) ) astMirror( this->plotxz, 0 );
+   grfcon = (AstKeyMap *) astGetGrfContext( this->plotxz );
+   astMapPut0D( grfcon, "Gcon", this->gbox[ ( new & 2 ) ? 4 : 1 ], "Constant Y value" );
+   astMapPut0I( grfcon, "RootCorner", new, "Labelled corner" );
+   grfcon= astAnnul( grfcon );
+
+   if( ( old & 4 ) != ( new & 4 ) ) astMirror( this->plotxy, 0 );
+   grfcon = (AstKeyMap *) astGetGrfContext( this->plotxy );
+   astMapPut0D( grfcon, "Gcon", this->gbox[ ( new & 4 ) ? 5 : 2 ], "Constant Z value" );
+   astMapPut0I( grfcon, "RootCorner", new, "Labelled corner" );
+   grfcon= astAnnul( grfcon );
+
+/* Set a flag saying whether the limits are equal at the new corner for
+   the X and Y axes. */
+   xeqy = ( ( ( new & 1 ) > 0 ) == ( ( new & 2 ) > 0 ) );
+
+/* Set a flag saying whether the limits are equal at the new corner for
+   the X and Z axes. */
+   xeqz = ( ( ( new & 1 ) > 0 ) == ( ( new & 4 ) > 0 ) );
+
+/* Ensure all Edge attributes are clear. This means that the public
+   attribute accessors routines used below will return dynamic defaults for
+   the Edge attributes. */
+   astClearEdge( this->plotxy, 0 );
+   astClearEdge( this->plotxy, 1 );
+   astClearEdge( this->plotxz, 0 );
+   astClearEdge( this->plotxz, 1 );
+   astClearEdge( this->plotyz, 0 );
+   astClearEdge( this->plotyz, 1 );
+
+/* So far we have recorded no edges changes. */
+   np = 0;
+
+/* We now adjust the Edge attributes in the Plot used to annotate the 3D
+   X axis in order to get the X axis labels on the correct edge of the 3D
+   graphics cube. Get the Plot used to produce X axis labels (this will
+   be either this->plotxy or this->plotxz). */
+   plot = AxisPlot( this, 0, &axis2d, status );
+
+/* See what edge of the Plot is used to annotate the first of the two WCS
+   axis described by the 2D Plot. If the Edge(1) attribute has not been
+   assigned a value, then a dynamic default will be used by the Plot class.
+   We want to know what this dynamic default is, so we first cleared the
+   attribute above. Now we set the attribute value explicitly to the dynamic
+   default returned by astGetC. Note, we use astGetC rather than astGetEdge
+   because the dynamic default is not calculated when calling astGetEdge. */
+   astSetC( plot, "Edge(1)", astGetC( plot, "Edge(1)" ));
+   edge = astGetEdge( plot, 0 );
+   astClearEdge( plot, 0 );
+
+/* If the 3D X axis is labelled using the Plot that spans the XY plane... */
+   if( plot == this->plotxy ) {
+
+/* ... and if the new root corner is at the upper limit on the Y axis... */
+      if( new & 2 ) {
+
+/* If the first WCS axis is currently labelled on either the top or bottom
+   edge, ensure it is labelled on the upper Y (top) edge. */
+         if( edge == 3 || edge == 1 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = TOP;
+
+/* Otherwise ensure that the second WCS axis is labelled on the upper Y (top)
+   edge. */
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = TOP;
+         }
+
+/* If the new root corner is at the lower limit on the Y axis... */
+      } else {
+
+/* If the first WCS axis is currently labelled on either the top or bottom
+   edge, ensure it is labelled on the lower Y (bottom) edge. */
+         if( edge == 3 || edge == 1 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = BOTTOM;
+
+/* Otherwise ensure that the second WCS axis is labelled on the lower Y
+   (bottom) edge. */
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = BOTTOM;
+         }
+      }
+
+/* If the 3D X axis is labelled using the Plot that spans the XZ plane... */
+   } else {
+
+/* ... and if the new root corner is at the upper limit on the Z axis... */
+      if( new & 4 ) {
+
+/* If the first WCS axis is currently labelled on either the top or bottom
+   edge, ensure it is labelled on the upper Z (top) edge. */
+         if( edge == 3 || edge == 1 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = TOP;
+
+/* Otherwise ensure that the second WCS axis is labelled on the upper Y (top)
+   edge. */
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = TOP;
+         }
+
+/* If the new root corner is at the lower limit on the Z axis... */
+      } else {
+
+/* If the first WCS axis is currently labelled on either the top or bottom
+   edge, ensure it is labelled on the lower Z (bottom) edge. */
+         if( edge == 3 || edge == 1 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = BOTTOM;
+
+/* Otherwise ensure that the second WCS axis is labelled on the lower Z
+   (bottom) edge. */
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = BOTTOM;
+         }
+      }
+   }
+
+/* We now adjust the Edge attributes in the Plot used to annotate the 3D
+   Y axis in order to get the Y axis labels on the correct edge of the 3D
+   graphics cube. Get the Plot used to produce Y axis labels. */
+   plot = AxisPlot( this, 1, &axis2d, status );
+
+/* See what edge of the Plot is used to annotate the first of the two WCS
+   axis described by the Plot. */
+   astSetC( plot, "Edge(1)", astGetC( plot, "Edge(1)" ));
+   edge = astGetEdge( plot, 0 );
+   astClearEdge( plot, 0 );
+
+/* If the 3D Y axis is labelled using the Plot that spans the XY plane... */
+   if( plot == this->plotxy ) {
+
+/* ... and if the new root corner is at the same limit on the X and Z axes,
+   put Y labels on the right side of the Plot.  */
+      if( xeqz ) {
+         if( edge == 0 || edge == 2 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = RIGHT;
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = RIGHT;
+         }
+
+/* If the new root corner is at a different limit on the X and Z axes,
+   put Y labels on the left side of the Plot.  */
+      } else {
+         if( edge == 0 || edge == 2 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = LEFT;
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = LEFT;
+         }
+      }
+
+/* If the 3D Y axis is labelled using the Plot that spans the YZ plane... */
+   } else {
+
+/* ... and if the new root corner is at the upper Z limit, put Y labels on
+   the top of the Plot.  */
+      if( new & 4 ) {
+         if( edge == 1 || edge == 3 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = TOP;
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = TOP;
+         }
+
+/* If the new root corner is at the lower Z limit, put Y labels on the
+   bottom of the Plot.  */
+      } else {
+         if( edge == 1 || edge == 3 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = BOTTOM;
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = BOTTOM;
+         }
+      }
+   }
+
+/* We now adjust the Edge attributes in the Plot used to annotate the 3D
+   Z axis in order to get the Z axis labels on the correct edge of the 3D
+   graphics cube. Get the Plot used to produce Z axis labels. */
+   plot = AxisPlot( this, 2, &axis2d, status );
+
+/* See what edge of the Plot is used to annotate the first of the two WCS
+   axis described by the Plot. */
+   astSetC( plot, "Edge(1)", astGetC( plot, "Edge(1)" ));
+   edge = astGetEdge( plot, 0 );
+   astClearEdge( plot, 0 );
+
+/* If the 3D Z axis is labelled using the Plot that spans the XZ plane... */
+   if( plot == this->plotxz ) {
+
+/* ... and if the new root corner is at the same limit on the X and Y axes,
+   put Z labels on the left side of the Plot.  */
+      if( xeqy ) {
+         if( edge == 0 || edge == 2 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = LEFT;
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = LEFT;
+         }
+
+/* If the new root corner is at a different limit on the X and Y axes,
+   put Y labels on the right side of the Plot.  */
+      } else {
+         if( edge == 0 || edge == 2 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = RIGHT;
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = RIGHT;
+         }
+      }
+
+/* If the 3D Z axis is labelled using the Plot that spans the YZ plane... */
+   } else {
+
+/* ... and if the new root corner is at the same limit on the X and Y axes,
+   put Z labels on the right side of the Plot.  */
+      if( xeqz ) {
+         if( edge == 0 || edge == 2 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = RIGHT;
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = RIGHT;
+         }
+
+/* If the new root corner is at a different limit on the X and Y axes,
+   put Y labels on the left side of the Plot.  */
+      } else {
+         if( edge == 0 || edge == 2 ) {
+            plots[ np ] = plot;
+            axes[ np ] = 0;
+            edges[ np++ ] = LEFT;
+         } else {
+            plots[ np ] = plot;
+            axes[ np ] = 1;
+            edges[ np++ ] = LEFT;
+         }
+      }
+   }
+
+/* Apply the set of edge changes determined above. */
+   for( i = 0; i < np; i++ ) {
+      astSetEdge( plots[ i ], axes[ i ], edges[ i ] );
+   }
+
+/* Ensure that the 2 Plot axes that are not being used have suitable values
+   for their attributes. That is, no labels are drawn, and the ticked
+   edges are the one that meet at the new RootCorner. */
+
+   if( !astTestEdge( this->plotxy, 0 ) ) {
+      astSetEdge( this->plotxy, 0, ( new & 2 ) ? TOP : BOTTOM );
+   }
+
+   if( !astTestEdge( this->plotxy, 1 ) ) {
+      astSetEdge( this->plotxy, 1, xeqz ? RIGHT: LEFT );
+   }
+
+   if( !astTestEdge( this->plotxz, 0 ) ) {
+      astSetEdge( this->plotxz, 0, ( new & 4 ) ? TOP : BOTTOM );
+   }
+
+   if( !astTestEdge( this->plotxz, 1 ) ) {
+      astSetEdge( this->plotxz, 1, xeqy ? LEFT : RIGHT );
+   }
+
+   if( !astTestEdge( this->plotyz, 0 ) ) {
+      astSetEdge( this->plotyz, 0, ( new & 4 ) ? TOP : BOTTOM );
+   }
+
+   if( !astTestEdge( this->plotyz, 1 ) ) {
+      astSetEdge( this->plotyz, 1, xeqy ? RIGHT : LEFT );
+   }
+
+
+}
+
+static void Clear( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     Clear
+
+*  Purpose:
+*     Clear attribute values for a Plot3D.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void Clear( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the public astClear method
+*     inherited from the Object class).
+
+*  Description:
+*     This function clears the values of a specified set of attributes
+*     for a Plot3D. Clearing an attribute cancels any value that has
+*     previously been explicitly set for it, so that the standard
+*     default attribute value will subsequently be used instead. This
+*     also causes the astTest function to return the value zero for
+*     the attribute, indicating that no value has been set.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     attrib
+*        Pointer to a null-terminated character string containing a
+*        comma-separated list of the names of the attributes to be
+*        cleared.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function preserves the integrity of the Plot3D (if
+*     possible) by appropriately modifying the three encapsulated Plots.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent astClear method to clear the Plot3D's attribute values. */
+   (*parent_clear)( this_object, attrib, status );
+
+/* Update the three 2D Plots stored in the Plot3D structure so that they
+   reflect this modified FrameSet. */
+   UpdatePlots( (AstPlot3D *) this_object, status );
+
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Plot3D.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void ClearAttrib( AstObject *this, const char *attrib )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the astClearAttrib protected
+*     method inherited from the Plot class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     Plot3D, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*/
+
+/* Local Variables: */
+   AstPlot3D *this;              /* Pointer to the Plot3D structure */
+   AstPlot *plot;                /* Pointer to specific Plot */
+   char attname[50];             /* Plot attribute base name */
+   char patt[50];                /* Plot attribute full name */
+   char spec[10];                /* Plane specification */
+   int axis;                     /* Axis index */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* Number of characters read */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Plot3D structure. */
+   this = (AstPlot3D *) this_object;
+
+/* Obtain the length of the "attrib" string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Norm. */
+/* ----------- */
+   if ( !strcmp( attrib, "norm" ) ) {
+      astClearNorm( this, 0 );
+      astClearNorm( this, 1 );
+      astClearNorm( this, 2 );
+
+/* Norm(axis). */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "norm(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      astClearNorm( this, axis - 1 );
+
+/* RootCorner. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "rootcorner" ) ) {
+      astClearRootCorner( this );
+
+/* ..._XY etc */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( attrib, "%[a-z]_%[xyz]%n", attname, spec,
+                 &nc ) ) ) {
+
+      if( !strcmp( spec, "xy" ) || !strcmp( spec, "yx" ) ) {
+         plot = this->plotxy;
+      } else if( !strcmp( spec, "xz" ) || !strcmp( spec, "zx" ) ) {
+         plot = this->plotyz;
+      } else if( !strcmp( spec, "yz" ) || !strcmp( spec, "zy" ) ) {
+         plot = this->plotxz;
+      } else {
+         plot = NULL;
+      }
+
+      if( plot ) {
+         sprintf( patt, "%s%s", attname, attrib + nc );
+         astClearAttrib( plot, patt );
+
+      } else {
+         (*parent_clearattrib)( this_object, attrib, status );
+      }
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearCurrent( AstFrameSet *this_frameset, int *status ) {
+/*
+*  Name:
+*     ClearCurrent
+
+*  Purpose:
+*     Clear the value of the Current attribute for a Plot3D.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int astClearCurrent( AstFrameSet *this )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the public astClearCurrent method
+*     inherited from the FrameSet class).
+
+*  Description:
+*     This function clears the value of the Current attribute for a
+*     Plot3D. This attribute is an index that identifies the current
+*     Frame for the Plot3D.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*/
+
+/* Invoke the parent astClearCurrent method. */
+   (*parent_clearcurrent)( this_frameset, status );
+
+/* Update the three 2D Plots stored in the Plot3D structure so that they
+   reflect this modified FrameSet. */
+   UpdatePlots( (AstPlot3D *) this_frameset, status );
+}
+
+static void ClearRootCorner( AstPlot3D *this, int *status ){
+/*
+*+
+*  Name:
+*     astClearRootCorner
+
+*  Purpose:
+*     Clear the RootCorner attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void astClearRootCorner( AstPlot3D *this )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function clears the RootCorner attribute.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot3D.
+
+*-
+*/
+
+/* Local Variables: */
+   int old;
+   int new;
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Get the current rootcorner value. */
+   old = astGetRootCorner( this );
+
+/* Clear the RootCorner attribute. */
+   this->rootcorner = -1;
+
+/* Get the new (default) rootcorner value. */
+   new = astGetRootCorner( this );
+
+/* If the root corner has changed, mirror any axes of the encapsulated Plots
+   that need mirroring (this is done to ensure that Plots look right when
+   viewed from the outside of the graphics cube), and modify the Edge
+   attributes in the encapsulated Plots to ensure the labels appear on the
+   requested edges of the 3D graphics cube. . */
+   if( old != new ) ChangeRootCorner( this, old, new, status );
+}
+
+static void CreatePlots( AstPlot3D *this, AstFrameSet *fset, const float *gbox,
+                         const double *bbox, int *status ) {
+/*
+*  Name:
+*     CreatePlots
+
+*  Purpose:
+*     Create three 2D plots and store in the Plot3D.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void CreatePlots( AstPlot3D *this, AstFrameSet *fset, const float *gbox,
+                        const double *bbox, int *status )
+
+*  Class Membership:
+*     Plot3D method.
+
+*  Description:
+*     This function splits the supplied FrameSet up into 3 independent 2D
+*     FrameSets, each describing a 2D plane in the supplied 3D FrameSet.
+*     It then uses these 2D FrameSets to create three Plots, one for each
+*     plane in the graphics plotting space, and stores them in the Plot3D.
+*
+*     Each of the three Plots is notionally pasted onto one face of the
+*     3D graphics cube (the RootCorner attribute is used to determine which
+*     of the two parallel faces a particular Plot is pasted onto). The
+*     Plot is pasted in such a way that, when viewed from the outside of
+*     the graphics cube, the first graphics axis increases left to right
+*     and the second increases bottom to top (this assumes that "up" is
+*     parallel to the 3D Z axis).
+*
+*     Initially, the Plots are created assuming the default RootCorner
+*     value ("LLL"). They will be changed later if the value of the
+*     RootCorner attribute is changed.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     fset
+*        Pointer to the FrameSet.
+*     gbox
+*        A pointer to an array of 6 values giving the graphics coordinates
+*        of the bottom left and top right corners of a box on the graphics
+*        output device. The first triple of values should be the graphics
+*        coordinates of the bottom left corner of the box and the second
+*        triple of values are the graphics coordinates of the top right corner.
+*     bbox
+*        A pointer to an array of 6 values giving the coordinates in the
+*        supplied Frame or base Frame of the supplied FrameSet at the bottom
+*        left and top right corners of the box specified by parameter gbox.
+*        These should be supplied in the same order as for parameter "gbox".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Each returned plot has 3 Frames: Frame 1 is the base (GRAPHICS)
+*     Frame; Frame 2 is spanned by 2 of the 3 axes in the base Frame of
+*     the supplied FrameSet; Frame 3 is the current Frame and is spanned
+*     by 2 of the 3 axes in the current Frame of the supplied FrameSet.
+*     Any future changes to this function that alter this structure should
+*     reflected in equivalent changes to function UpdatePlots.
+
+*/
+
+/* Local Variables: */
+   AstFrameSet *fsetxy;
+   AstFrameSet *fsetxz;
+   AstFrameSet *fsetyz;
+   double basebox2d[ 4 ];
+   float graphbox2d[ 4 ];
+   int baseplane;
+   int labelxy[ 2 ];
+   int labelxz[ 2 ];
+   int labelyz[ 2 ];
+   int wcsxy[ 2 ];
+   int wcsxz[ 2 ];
+   int wcsyz[ 2 ];
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Split the supplied FrameSet up into 3 FrameSets, each with a 2D base
+   and current Frame. Each of these FrameSets describes one plane of
+   the 3D cube. One of them will be spanned by two axes picked from the
+   supplied 3D FrameSet. The other two FrameSets will each include a copy
+   of the remaining 3rd axis from the supplied FrameSet, plus an extra
+   dummy axis. These dummy axes will never be labelled. */
+   SplitFrameSet( fset, &fsetxy, labelxy, wcsxy, &fsetxz, labelxz, wcsxz,
+                  &fsetyz, labelyz, wcsyz, &baseplane, status );
+
+/* If OK, annul any existing 2D plots. */
+   if( astOK ) {
+      if( this->plotxy ) this->plotxy = astAnnul( this->plotxy );
+      if( this->plotxz ) this->plotxz = astAnnul( this->plotxz );
+      if( this->plotyz ) this->plotyz = astAnnul( this->plotyz );
+
+/* Create three Plots; one for each 2D plane in the graphics plotting
+   space. Set the attributes of these plots so that the required axes are
+   labelled and other axes are left blank. The "graphbox2d" and "basebox2d"
+   values used to create each Plot define the sense, as well as the extent,
+   of each axis. The first pair of values in each give the lower left corner
+   of the Plot and the second pair give the top right corner. We want each
+   Plot to have X increasing left to right and Y increasing bottom to
+   top when viewed from the outside of the cube. We assume an initial
+   RootCorner value of "LLL" (that is, the Plots are pasted onto the cube
+   faces that meet at the lower limit on every axis). */
+      graphbox2d[ 0 ] = gbox[ 3 ];
+      graphbox2d[ 1 ] = gbox[ 1 ];
+      graphbox2d[ 2 ] = gbox[ 0 ];
+      graphbox2d[ 3 ] = gbox[ 4 ];
+
+      basebox2d[ 0 ] = bbox[ 3 ];
+      basebox2d[ 1 ] = bbox[ 1 ];
+      basebox2d[ 2 ] = bbox[ 0 ];
+      basebox2d[ 3 ] = bbox[ 4 ];
+
+      if( this->plotxy ) this->plotxy = astAnnul( this->plotxy );
+      this->plotxy = astPlot( fsetxy, graphbox2d, basebox2d, "", status );
+      SetPlotAttr( this->plotxy, XY, labelxy, status );
+
+      graphbox2d[ 0 ] = gbox[ 0 ];
+      graphbox2d[ 1 ] = gbox[ 2 ];
+      graphbox2d[ 2 ] = gbox[ 3 ];
+      graphbox2d[ 3 ] = gbox[ 5 ];
+
+      basebox2d[ 0 ] = bbox[ 0 ];
+      basebox2d[ 1 ] = bbox[ 2 ];
+      basebox2d[ 2 ] = bbox[ 3 ];
+      basebox2d[ 3 ] = bbox[ 5 ];
+
+      this->plotxz = astPlot( fsetxz, graphbox2d, basebox2d, "", status );
+      SetPlotAttr( this->plotxz, XZ, labelxz, status );
+
+      graphbox2d[ 0 ] = gbox[ 4 ];
+      graphbox2d[ 1 ] = gbox[ 2 ];
+      graphbox2d[ 2 ] = gbox[ 1 ];
+      graphbox2d[ 3 ] = gbox[ 5 ];
+
+      basebox2d[ 0 ] = bbox[ 4 ];
+      basebox2d[ 1 ] = bbox[ 2 ];
+      basebox2d[ 2 ] = bbox[ 1 ];
+      basebox2d[ 3 ] = bbox[ 5 ];
+
+      this->plotyz = astPlot( fsetyz, graphbox2d, basebox2d, "", status );
+      SetPlotAttr( this->plotyz, YZ, labelyz, status );
+
+/* Store information that allows each 3D WCS axis to be associatedf with
+   a pair of Plots. Also store the WCS axis within each Plot that
+   corresponds to the 3D WCS axis. */
+      StoreAxisInfo( this, labelxy, wcsxy, labelxz, wcsxz, labelyz, wcsyz, status );
+
+/* Store the Plot that spans two connected 3D axes. */
+      this->baseplot = baseplane;
+
+/* Free resources */
+      fsetxy = astAnnul( fsetxy );
+      fsetxz = astAnnul( fsetxz );
+      fsetyz = astAnnul( fsetyz );
+
+   }
+}
+
+static int Element2D( AstPlot3D *this, int element, int *elem2d1,
+                      int *elem2d2, int *status ){
+/*
+*  Name:
+*     Element2D
+
+*  Purpose:
+*     Convert a 3D graphics element identifier to a corresponding pair of
+*     2D identifiers.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Element2D( AstPlot3D *this, int element, int *elem2d1,
+*                    int *elem2d2, int *status )
+
+*  Class Membership:
+*     Plot3D method.
+
+*  Description:
+*     This function takes an integer identifier for an element of a 3D
+*     annotated grid (e.g. ticks, axis 1 labels, border, etc), and returns
+*     a element identifers that can be used with the encapsualted 2D Plots.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot2D structure.
+*     element
+*        The 3D element identifier to convert.
+*     elem2d1
+*        Pointer to an int in which to return the 2D element identifier
+*        to use with the first of the two Plots that span the axis to
+*        which the 3D element identifier refers. Returned holding 0 if
+*        the given 3D element identifier is not axis specific.
+*     elem2d2
+*        Pointer to an int in which to return the 2D element identifier
+*        to use with the second of the two Plots that span the axis to
+*        which the 3D element identifier refers. Returned holding 0 if
+*        the given 3D element identifier is not axis specific.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The zero-based index of the 3D axis to which the given element
+*     identifier refers, or -1 if the element identifier is not axis
+*     specific.
+
+*/
+
+/* Local Variables: */
+   int axis3d;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get the zero-based index of the 3D axis to which the supplied element
+   refers. Use an index of -1 to indicate that the element does not
+   relate to a specific axis. Also get the corresponding element to use
+   with the two Plots that share the speified 3D axis. */
+
+/* Define a macro used to set the 2d element identifiers for a given 3d
+   element identifier. */
+
+#define SET_ELEM2D(id1,id2) \
+   *elem2d1 = this->axis_index1[ axis3d ] ? id2 : id1; \
+   *elem2d2 = this->axis_index2[ axis3d ] ? id2 : id1;
+
+   if( element == AST__BORDER_ID ){
+      axis3d = -1;
+
+   } else if( element == AST__CURVE_ID ){
+      axis3d = -1;
+
+   } else if( element == AST__TITLE_ID ){
+      axis3d = -1;
+
+   } else if( element == AST__MARKS_ID ){
+      axis3d = -1;
+
+   } else if( element == AST__TEXT_ID ){
+      axis3d = -1;
+
+   } else if( element == AST__AXIS1_ID ){
+      axis3d = 0;
+      SET_ELEM2D(AST__AXIS1_ID,AST__AXIS2_ID)
+
+   } else if( element == AST__AXIS2_ID ){
+      axis3d = 1;
+      SET_ELEM2D(AST__AXIS1_ID,AST__AXIS2_ID)
+
+   } else if( element == AST__AXIS3_ID ){
+      axis3d = 2;
+      SET_ELEM2D(AST__AXIS1_ID,AST__AXIS2_ID)
+
+   } else if( element == AST__NUMLAB1_ID ){
+      axis3d = 0;
+      SET_ELEM2D(AST__NUMLAB1_ID,AST__NUMLAB2_ID)
+
+   } else if( element == AST__NUMLAB2_ID ){
+      axis3d = 1;
+      SET_ELEM2D(AST__NUMLAB1_ID,AST__NUMLAB2_ID)
+
+   } else if( element == AST__NUMLAB3_ID ){
+      axis3d = 2;
+      SET_ELEM2D(AST__NUMLAB1_ID,AST__NUMLAB2_ID)
+
+   } else if( element == AST__TEXTLAB1_ID ){
+      axis3d = 0;
+      SET_ELEM2D(AST__TEXTLAB1_ID,AST__TEXTLAB2_ID)
+
+   } else if( element == AST__TEXTLAB2_ID ){
+      axis3d = 1;
+      SET_ELEM2D(AST__TEXTLAB1_ID,AST__TEXTLAB2_ID)
+
+   } else if( element == AST__TEXTLAB3_ID ){
+      axis3d = 2;
+      SET_ELEM2D(AST__TEXTLAB1_ID,AST__TEXTLAB2_ID)
+
+   } else if( element == AST__TICKS1_ID ){
+      axis3d = 0;
+      SET_ELEM2D(AST__TICKS1_ID,AST__TICKS2_ID)
+
+   } else if( element == AST__TICKS2_ID ){
+      axis3d = 1;
+      SET_ELEM2D(AST__TICKS1_ID,AST__TICKS2_ID)
+
+   } else if( element == AST__TICKS3_ID ){
+      axis3d = 2;
+      SET_ELEM2D(AST__TICKS1_ID,AST__TICKS2_ID)
+
+   } else if( element == AST__GRIDLINE1_ID ){
+      axis3d = 0;
+      SET_ELEM2D(AST__GRIDLINE1_ID,AST__GRIDLINE2_ID)
+
+   } else if( element == AST__GRIDLINE2_ID ){
+      axis3d = 1;
+      SET_ELEM2D(AST__GRIDLINE1_ID,AST__GRIDLINE2_ID)
+
+   } else if( element == AST__GRIDLINE3_ID ){
+      axis3d = 2;
+      SET_ELEM2D(AST__GRIDLINE1_ID,AST__GRIDLINE2_ID)
+
+   } else {
+      axis3d = 0;
+      astError( AST__INTER, "Element2D(Plot3D): The MAKE_CLEAR2 macro "
+                "does not yet support element index %d (internal "
+                "AST programming error).", status, element );
+   }
+
+#undef SET_ELEM2D
+
+   return axis3d;
+
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two Plot3Ds are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the astEqual protected
+*     method inherited from the Plot Object class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two Plot3Ds are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Plot3D.
+*     that
+*        Pointer to the second Plot3D.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the Plot3Ds are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPlot3D *that;         /* Pointer to the second Plot3D structure */
+   AstPlot3D *this;         /* Pointer to the first Plot3D structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the Equal method inherited from the parent Plot class. This checks
+   that the Plots are both of the same class (amongst other things). */
+   if( (*parent_equal)( this_object, that_object, status ) ) {
+
+/* Obtain pointers to the two Plot3D structures. */
+      this = (AstPlot3D *) this_object;
+      that = (AstPlot3D *) that_object;
+
+/* Check the encapsulated Plots for equality. */
+      result = ( astEqual( this->plotxz, that->plotxz ) &&
+                 astEqual( this->plotyz, that->plotyz ) &&
+                 astEqual( this->plotxy, that->plotxy ) );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *ExtendTicks( AstPlot *plot, AstPointSet *ticks, int *status ){
+/*
+*  Name:
+*     ExtendTicks
+
+*  Purpose:
+*     Add an extra tick to the start and end of a list of tick marks.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     AstPointSet *ExtendTicks( AstPlot *plot, AstPointSet *ticks, int *status )
+
+*  Class Membership:
+*     Plot3D method.
+
+*  Description:
+*     This function takes a list of tick marks drawn using the supplied
+*     Plot, and adds in an extra tick mark at the start and end of the
+*     supplied list of ticks, returning the expanded list in a new
+*     PointSet. The extra points are guaranteed to fall outside the area
+*     enclosed within the supplied Plot.
+
+*  Parameters:
+*     plot
+*        The Plot that was used to generate the list of tick marks.
+*     ticks
+*        A PointSet holding the 2D graphics coordinates (within the base
+*        Frame of the supplied Plot) at which each tick mark starts.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a new PointSet that has 2 more entries than the
+*     supplied PointSet. The first point is a new tick mark, then comes
+*     all the ticks mark positions supplied in "ticks", and finally there
+*     is another new tick mark.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *result;
+   double **ptr_in;
+   double **ptr_out;
+   double *a_in;
+   double *a_out;
+   double *b_in;
+   double *b_out;
+   double *p;
+   double delta;
+   double hi;
+   double lo;
+   double range[ 2 ];
+   double v;
+   int axis;
+   int i;
+   int increasing;
+   int np;
+
+/* Check inherited status */
+   if( !astOK || !ticks ) return NULL;
+
+/* Get the number of tick marsk in the supplied PointSet and get pointers
+   to the 3D Graphics values contained in the PointSet. */
+   np = astGetNpoint( ticks );
+   ptr_in = astGetPoints( ticks );
+
+/* Create the returned PointSet with room for an extra pair of ticks. Get
+   pointers to its data arrays */
+   result = astPointSet( np + 2, 2, "", status );
+   ptr_out = astGetPoints( result );
+
+/* Check the pointers can be used safely. */
+   if( astOK ) {
+
+/* Find the index of the 2D graphics axis (0 or 1) that varies along the
+   set of tick marks. We do this by finding the max and min value
+   supplied for each axis, and then choosing the axis that has the highest
+   range. */
+      for( axis = 0; axis < 2; axis++ ) {
+         hi = -DBL_MAX;
+         lo = DBL_MAX;
+         p = ptr_in[ axis ];
+
+         for( i = 0; i < np; i++, p++ ) {
+            v = *p;
+            if( v != AST__BAD ) {
+               if( v > hi ) hi = v;
+               if( v < lo ) lo = v;
+            }
+         }
+
+         if( lo != DBL_MAX ) {
+            range[ axis ] = hi - lo;
+         } else {
+            astError( AST__INTER, "ExtendTicks{Plot3D): no good ticks on "
+                      "axis %d (internal AST prgramming error).", status, axis );
+         }
+      }
+
+
+/* Get the index of the axis with the largest range (the other range
+   should be zero). */
+      axis = ( range[ 0 ] > range[ 1 ] ) ? 0 : 1;
+
+/* Copy the input graphics positions to the output PointSet, and add an
+   extra position at the beginning and end of the output PointSet. */
+      if( axis == 0 ) {
+         lo = plot->xlo;
+         hi = plot->xhi;
+         a_in = ptr_in[ 0 ];
+         b_in = ptr_in[ 1 ];
+         a_out = ptr_out[ 0 ];
+         b_out = ptr_out[ 1 ];
+
+      } else {
+         lo = plot->ylo;
+         hi = plot->yhi;
+         a_in = ptr_in[ 1 ];
+         b_in = ptr_in[ 0 ];
+         a_out = ptr_out[ 1 ];
+         b_out = ptr_out[ 0 ];
+      }
+
+      delta = 0.2*( hi - lo );
+
+      if( a_in[ 0 ] < a_in[ 1 ] ) {
+         increasing = 1;
+         *(a_out++) = lo - delta;
+      } else {
+         increasing = 0;
+         *(a_out++) = hi + delta;
+      }
+
+      *(b_out++) = *b_in;
+      for( i = 0; i < np; i++ ) {
+         *(a_out++) = *(a_in++);
+         *(b_out++) = *(b_in++);
+      }
+      *(b_out++) = b_in[ -1 ];
+
+
+      if( increasing ) {
+         *(a_out++) = hi + delta;
+      } else {
+         *(a_out++) = lo - delta;
+      }
+   }
+
+/* Return the extended PointSet. */
+   return result;
+}
+
+static AstFrameSet *Fset3D( AstFrameSet *fset, int ifrm, int *status ) {
+/*
+*  Name:
+*     Fset3D
+
+*  Purpose:
+*     Create a FrameSet with no more than 3 dimensions for a given Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     AstFrameSet *Fset3D( AstFrameSet *fset, int ifrm, int *status )
+
+*  Class Membership:
+*     Plot3D method.
+
+*  Description:
+*     This function checks a specified Frame in the supplied FrameSet.
+*     If the Frame has more than 3 dimensions, a new Frame is added to
+*     the FrameSet containing just the first three axes of the specified
+*     Frame. A PermMap is used to connect this Frame to the specified
+*     Frame, which supplied bad values for any missing axes. If the
+*     specified Frame is the base Frame in the supplied FrameSet, then the
+*     new Frame becomes the base Frame in the returned FrameSet. Like-wise,
+*     if the specified Frame is the current Frame, then the new Frame
+*     will be the current Frame in the returned FrameSet.
+*
+*     If the specified Frame does not have more than 3 axes, then a clone
+*     of the FrameSet pointer is returned, otherwise the returned pointer
+*     points to a copy of the supplied FrameSet with the new 3-D Frame
+*     added.
+
+*  Parameters:
+*     fset
+*        Pointer to the FrameSet.
+*     ifrm
+*        The index of the Frame to check. This should be AST__BASE or
+*        AST_CURRENT.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a FrameSet in which the Frame with index given by ifrm
+*     has no more than 3 axes.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstFrame *newfrm;
+   AstFrameSet *ret;
+   AstPermMap *map;
+   double zero;
+   int *inperm;
+   int axes[3];
+   int i;
+   int ic;
+   int nax;
+
+/* Check the inherited status. */
+   if( !astOK ) return NULL;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   map = NULL;
+
+/* Get a pointer to the requested Frame in the supplied FrameSet. */
+   frm = astGetFrame( fset, ifrm );
+
+/* See how many dimensions the specified Frame of the supplied FrameSet
+   has. */
+   nax = astGetNaxes( frm );
+
+/* If it is more than 3-dimensionbal, create a 3D Frame by picking
+   axes 1, 2 and 3 from the original Frame. */
+   if( nax > 3 ) {
+      axes[ 0 ] = 0;
+      axes[ 1 ] = 1;
+      axes[ 2 ] = 2;
+      newfrm = astPickAxes( frm, 3, axes, NULL );
+
+/* Create a PermMap to describe the mapping between the two Frames.
+   Use zero as the value for unknown axes (the optional mapping which
+   can be returned by astPickAxes uses AST__BAD for unknown axes). */
+      inperm = (int *) astMalloc( sizeof(int)*(size_t) nax );
+      if( astOK ){
+         inperm[ 0 ] = 0;
+         inperm[ 1 ] = 1;
+         inperm[ 2 ] = 2;
+         for( i = 3; i < nax; i++ ) inperm[ i ] = -1;
+         zero = 0.0;
+         map = astPermMap( nax, inperm, 3, axes, &zero, "", status );
+         inperm = (int *) astFree( (void *) inperm );
+      }
+
+/* Get a copy of the supplied FrameSet. */
+      ret = astCopy( fset );
+
+/* Add the new Frame to the FrameSet (it becomes the current Frame). */
+      ic = astGetCurrent( ret );
+      astAddFrame( ret, ifrm, map, newfrm );
+      newfrm = astAnnul( newfrm );
+
+/* If the new Frame was derived from the base frame, set the new base
+   Frame, and re-instate the original current Frame */
+      if( ifrm == AST__BASE ){
+         astSetBase( ret, astGetCurrent( ret ) );
+         astSetCurrent( ret, ic );
+      }
+
+/* If the specified Frame in the supplied FrameSet is 3-dimensional, just
+   return a clone of it. */
+   } else {
+      ret = astClone( fset );
+   }
+
+/* Annul the pointer to the original Frame. */
+   frm = astAnnul( frm );
+
+   return ret;
+
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a Plot3D.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the protected astGetAttrib
+*     method inherited from the FrameSet class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a Plot3D, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     attrib
+*        Pointer to a null terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Plot3D, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Plot3D. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstPlot *plot;                /* Pointer to specific Plot */
+   AstPlot3D *this;              /* Pointer to the Plot3D structure */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   char attname[50];             /* Plot attribute base name */
+   char patt[50];                /* Plot attribute full name */
+   char spec[10];                /* Plane specification */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Floating point attribute value */
+   int axis;                     /* Axis index */
+   int ival;                     /* Int attribute value */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* Number of character read */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the Plot3D structure. */
+   this = (AstPlot3D *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* Norm(axis). */
+/* ----------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( attrib, "norm(%d)%n", &axis, &nc ) )
+          && ( nc >= len ) ) {
+      dval = astGetNorm( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* RootCorner. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "rootcorner" ) ) {
+      ival = astGetRootCorner( this );
+      result = RootCornerString( ival, status );
+      if( !result && astOK ) {
+         astError( AST__INTER, "astGetAttrib(Plot3D): Illegal value %d "
+                   "for RootCorner attribute (internal AST programming "
+                   "error).", status, ival );
+      }
+
+/* ..._XY etc */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( attrib, "%[a-z]_%[xyz]%n", attname, spec,
+                 &nc ) ) ) {
+
+      if( !strcmp( spec, "xy" ) || !strcmp( spec, "yx" ) ) {
+         plot = this->plotxy;
+      } else if( !strcmp( spec, "xz" ) || !strcmp( spec, "zx" ) ) {
+         plot = this->plotyz;
+      } else if( !strcmp( spec, "yz" ) || !strcmp( spec, "zy" ) ) {
+         plot = this->plotxz;
+      } else {
+         plot = NULL;
+      }
+
+      if( plot ) {
+         sprintf( patt, "%s%s", attname, attrib + nc );
+         result = astGetAttrib( plot, patt );
+
+      } else {
+         result = (*parent_getattrib)( this_object, attrib, status );
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied Plot3D,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPlot3D *this;         /* Pointer to Plot3D structure */
+   int result;              /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the Plot3D structure. */
+   this = (AstPlot3D *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by this class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astGetObjSize( this->plotxy );
+   result += astGetObjSize( this->plotxz );
+   result += astGetObjSize( this->plotyz );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static void Grid( AstPlot *this_plot, int *status ) {
+/*
+*  Name:
+*     Grid
+
+*  Purpose:
+*     Draw a set of labelled coordinate axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void Grid( AstPlot *this, int *status )
+
+*  Class Membership:
+*     Plot member function (over-rides the astGrid method inherited from
+*     the Plot class).
+
+*  Description:
+*     This function draws a complete annotated set of 3-dimensional
+*     coordinate axes for a Plot3D with (optionally) a coordinate grid
+*     superimposed.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPlot *baseplot;
+   AstPlot *plot;
+   AstPlot3D *this;
+   AstPointSet *majticks;
+   AstPointSet *minticks;
+   AstPointSet *tmp;
+   AstPointSet *wcsmajticks;
+   AstPointSet *wcsminticks;
+   const char *edge;
+   double **ptrmin;
+   double **ptrmaj;
+   double gcon;
+   int base_wax2d;
+   int itick;
+   int new_gaxis;
+   int new_plot;
+   int new_wax2d;
+   int nmaj;
+   int nmin;
+   int perm[ 2 ];
+   int rootcorner;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Plot3D structure. */
+   this = (AstPlot3D *) this_plot;
+
+/* Invoke the astGrid method on the 2D base Plot. Both WCS axes in this
+   Plot are inherited from the 3D FrameSet that was supplied when the Plot3D
+   was constructed, and will be labelled. The other two Plots encapsulated
+   in the Plot3D only inherit a single axis from the original 3D FrameSet
+   (a dummy axis is used for the second WCS axis in these Plots). */
+   baseplot = GET_PLOT( this->baseplot );
+   astGrid( baseplot );
+
+/* We next use astGrid to draw a grid on the 2D plane that shares the first
+   base plot graphics axis. Get the identifier for this Plot and the 2D
+   graphics axis index within the Plot that corresponds to the first base
+   plot graphics axis. Also get the constant value on the 3rd graphics
+   axis over the base plot */
+   rootcorner = astGetRootCorner( this );
+   if( this->baseplot == XY ) {
+      new_plot = XZ;
+      new_gaxis = 0;
+      gcon = this->gbox[ ( rootcorner & 4 ) ? 5 : 2 ];
+
+   } else if( this->baseplot == XZ ) {
+      new_plot = XY;
+      new_gaxis = 0;
+      gcon = this->gbox[ ( rootcorner & 2 ) ? 4 : 1 ];
+
+   } else {
+      new_plot = XY;
+      new_gaxis = 1;
+      gcon = this->gbox[ ( rootcorner & 1 ) ? 3 : 0 ];
+   }
+
+/* Get a pointer to the Plot upon which astGrid is about to be invoked. */
+   plot = GET_PLOT( new_plot );
+
+/* Find which 2D WCS axis was labelled on graphics axis 0 (the bottom or
+   top edge) of the base plot. */
+   if( ( edge = astGetC( baseplot, "Edge(1)" ) ) ) {
+      base_wax2d = ( !strcmp( edge, "bottom" ) || !strcmp( edge, "top" ) ) ? 0 : 1;
+   } else {
+      base_wax2d = 0;
+   }
+
+/* Get the 2D graphics coords within the base Plot at which the tick
+   marks were drawn for this 2D WCS axis. Extend the PointSets holding
+   the major tick values to include an extra tick above and below the
+   ticks drawn by astGrid. These extra ticks are placed outside the
+   bounds of the plot. This ensures that the curves on the other axis
+   extend the full width of the plot. */
+   tmp = astGetDrawnTicks( baseplot, base_wax2d, 1 );
+   if( tmp ) {
+      majticks = ExtendTicks( baseplot, tmp, status );
+      nmaj = astGetNpoint( majticks );
+      ptrmaj = astGetPoints( majticks );
+      tmp = astAnnul( tmp );
+   } else {
+      majticks = NULL;
+      nmaj = 0;
+      ptrmaj = NULL;
+   }
+
+   minticks = astGetDrawnTicks( baseplot, base_wax2d, 0 );
+   if( minticks ) {
+      nmin = astGetNpoint( minticks );
+      ptrmin = astGetPoints( minticks );
+   } else {
+      nmin = 0;
+      ptrmin = NULL;
+   }
+
+/* All the tick marks will have a constant value for the 2D graphics axis
+   that is not being ticked (axis 1 at the moment). Change this constant
+   value to be the value appropriate to the new Plot. */
+   if( ptrmaj && ptrmin ) {
+      for( itick = 0; itick < nmaj; itick++ ) ptrmaj[ 1 ][ itick ] = gcon;
+      for( itick = 0; itick < nmin; itick++ ) ptrmin[ 1 ][ itick ] = gcon;
+   }
+
+/* If required, permute the axes in the tick mark positions. */
+   if( new_gaxis != 0 ) {
+      perm[ 0 ] = 1;
+      perm[ 1 ] = 0;
+      if( majticks ) astPermPoints( majticks, 1, perm );
+      if( minticks ) astPermPoints( minticks, 1, perm );
+   }
+
+/* Transform the tick mark positions from 2D graphics coords to 2D WCS
+   coords. */
+   wcsmajticks = majticks ? astTransform( plot, majticks, 1, NULL ) : NULL;
+   wcsminticks = minticks ? astTransform( plot, minticks, 1, NULL ) : NULL;
+
+/* Find the index of the 2D WCS axis that will be labelled on the bottom
+   or top edge (i.e. 2D graphics axis zero) of the new Plot. */
+   if( ( edge = astGetC( plot, "Edge(1)" ) ) ) {
+      new_wax2d = ( !strcmp( edge, "bottom" ) || !strcmp( edge, "top" ) ) ? 0 : 1;
+   } else {
+      new_wax2d = 0;
+   }
+
+/* Use the other WCS axis if we are ticking the left or right edge (i.e.
+   2D graphics axis one) of the new Plot. This gives us the index of the
+   2D WCS axis for which tick values are to be stored in the Plot. */
+   if( new_gaxis == 1 ) new_wax2d = 1 - new_wax2d;
+
+/* Store the tick mark values to be used on this WCS axis. */
+   ptrmaj = wcsmajticks ? astGetPoints( wcsmajticks ) : NULL;
+   ptrmin = wcsminticks ? astGetPoints( wcsminticks ) : NULL;
+   if( ptrmaj && ptrmin ) {
+      astSetTickValues( plot, new_wax2d, nmaj, ptrmaj[ new_gaxis ],
+                                         nmin, ptrmin[ new_gaxis ] );
+   }
+
+/* Invoke the astGrid method on this plot. */
+   astGrid( plot );
+
+/* Clear the stored tick values in the plot. */
+   astSetTickValues( plot, new_wax2d, 0, NULL, 0, NULL );
+
+/* Free resources */
+   if( wcsmajticks ) wcsmajticks = astAnnul( wcsmajticks );
+   if( wcsminticks ) wcsminticks = astAnnul( wcsminticks );
+   if( majticks ) majticks = astAnnul( majticks );
+   if( minticks ) minticks = astAnnul( minticks );
+
+/* We next use astGrid to draw a grid on the 2D plane that shares the
+   second base plot graphics axis. Get the identifier for this Plot and the
+   2D graphics axis index within the Plot that corresponds to the first
+   base plot graphics axis. */
+   if( this->baseplot == XY ) {
+      new_plot = YZ;
+      new_gaxis = 0;
+
+   } else if( this->baseplot == XZ ) {
+      new_plot = YZ;
+      new_gaxis = 1;
+
+   } else {
+      new_plot = XZ;
+      new_gaxis = 1;
+   }
+
+/* Get a pointer to the Plot upon which astGrid is about to be invoked. */
+   plot = GET_PLOT( new_plot );
+
+/* Find which 2D WCS axis was labelled on graphics axis 1 (the left or
+   right edge) of the base plot. */
+   base_wax2d = 1 - base_wax2d;
+
+/* Get the 2D graphics coords within the base Plot at which the tick
+   marks were drawn for this 2D WCS axis. Extend the PointSets holding
+   the major tick values to include an extra tick above and below the
+   ticks drawn by astGrid. These extra ticks are placed outside the
+   bounds of the plot. This ensures that the curves on the other axis
+   extend the full width of the plot. */
+   tmp = astGetDrawnTicks( baseplot, base_wax2d, 1 );
+   if( tmp ) {
+      majticks = ExtendTicks( baseplot, tmp, status );
+      nmaj = astGetNpoint( majticks );
+      ptrmaj = astGetPoints( majticks );
+      tmp = astAnnul( tmp );
+   } else {
+      majticks = NULL;
+      nmaj = 0;
+      ptrmaj = NULL;
+   }
+
+   minticks = astGetDrawnTicks( baseplot, base_wax2d, 0 );
+   if( minticks ) {
+      nmin = astGetNpoint( minticks );
+      ptrmin = astGetPoints( minticks );
+   } else {
+      nmin = 0;
+      ptrmin = NULL;
+   }
+
+/* All the tick marks will have a constant value for the 2D graphics axis
+   that is not being ticked (axis 0 at the moment). Change this constant
+   value to be the value appropriate to the new Plot. */
+   if( ptrmaj && ptrmin ) {
+      for( itick = 0; itick < nmaj; itick++ ) ptrmaj[ 0 ][ itick ] = gcon;
+      for( itick = 0; itick < nmin; itick++ ) ptrmin[ 0 ][ itick ] = gcon;
+   }
+
+/* If required, permute the axes in the tick mark positions. */
+   if( new_gaxis != 1 ) {
+      perm[ 0 ] = 1;
+      perm[ 1 ] = 0;
+      if( majticks ) astPermPoints( majticks, 1, perm );
+      if( minticks ) astPermPoints( minticks, 1, perm );
+   }
+
+/* Transform the tick mark positions from 2D graphics coords to 2D WCS
+   coords. */
+   wcsmajticks = majticks ? astTransform( plot, majticks, 1, NULL ) : NULL;
+   wcsminticks = minticks ? astTransform( plot, minticks, 1, NULL ) : NULL;
+
+/* Find the index of the 2D WCS axis that will be labelled on the bottom
+   or top edge (i.e. 2D graphics axis zero) of the new Plot. */
+   if( ( edge = astGetC( plot, "Edge(1)" ) ) ) {
+      new_wax2d = ( !strcmp( edge, "bottom" ) || !strcmp( edge, "top" ) ) ? 0 : 1;
+   } else {
+      new_wax2d = 0;
+   }
+
+/* Use the other WCS axis if we are ticking the left or right edge (i.e.
+   2D graphics axis one) of the new Plot. This gives us the index of the
+   2D WCS axis for which tick values are to be stored in the Plot. */
+   if( new_gaxis == 1 ) new_wax2d = 1 - new_wax2d;
+
+/* Store the tick mark values to be used on this WCS axis. */
+   ptrmaj = wcsmajticks ? astGetPoints( wcsmajticks ) : NULL;
+   ptrmin = wcsminticks ? astGetPoints( wcsminticks ) : NULL;
+   if( ptrmaj && ptrmin ) {
+      astSetTickValues( plot, new_wax2d, nmaj, ptrmaj[ new_gaxis ],
+                                         nmin, ptrmin[ new_gaxis ] );
+   }
+
+/* Invoke the astGrid method on this plot. */
+   astGrid( plot );
+
+/* Clear the stored tick values in the plot. */
+   astSetTickValues( plot, new_wax2d, 0, NULL, 0, NULL );
+
+/* Free resources */
+   if( wcsmajticks ) wcsmajticks = astAnnul( wcsmajticks );
+   if( wcsminticks ) wcsminticks = astAnnul( wcsminticks );
+   if( majticks ) majticks = astAnnul( majticks );
+   if( minticks ) minticks = astAnnul( minticks );
+
+}
+
+void astInitPlot3DVtab_(  AstPlot3DVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPlot3DVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Plot3D.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void astInitPlot3DVtab( AstPlot3DVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Plot3D vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Plot3D class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS          /* Pointer to thread-specific global data */
+   AstFrame *dummy_frame;      /* The Frame to put in dummy_frameset */
+   AstPlotVtab *plot;          /* Pointer to Plot component of Vtab */
+   AstFrameSetVtab *fset;      /* Pointer to FrameSet component of Vtab */
+   AstMappingVtab *mapping;    /* Pointer to Mapping component of Vtab */
+   AstObjectVtab *object;      /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitPlotVtab( (AstPlotVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAPlot3D) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstPlotVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+#define SET_PLOT3D_ACCESSORS(attr) \
+   vtab->Set##attr = Set##attr; \
+   vtab->Get##attr = Get##attr; \
+   vtab->Test##attr = Test##attr; \
+   vtab->Clear##attr = Clear##attr;
+
+SET_PLOT3D_ACCESSORS(RootCorner)
+SET_PLOT3D_ACCESSORS(Norm)
+
+#undef SET_PLOT3D_ACCESSORS
+
+
+
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   fset = (AstFrameSetVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   plot = (AstPlotVtab *) vtab;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_equal = object->Equal;
+   object->Equal = Equal;
+
+   parent_vset = object->VSet;
+   object->VSet = VSet;
+
+   parent_cast = object->Cast;
+   object->Cast = Cast;
+
+   parent_clear = object->Clear;
+   object->Clear = Clear;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_clearcurrent = fset->ClearCurrent;
+   fset->ClearCurrent = ClearCurrent;
+
+   parent_setcurrent = fset->SetCurrent;
+   fset->SetCurrent = SetCurrent;
+
+   parent_removeframe = fset->RemoveFrame;
+   fset->RemoveFrame = RemoveFrame;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+/* Define a macro to override attribute accessors inherited from the
+   parent Plot class. First do axis specific attributes. */
+
+#define SET_PLOT_ACCESSORS(attr) \
+   parent_get##attr = plot->Get##attr; \
+   plot->Get##attr = Get##attr; \
+   parent_set##attr = plot->Set##attr; \
+   plot->Set##attr = Set##attr; \
+   parent_clear##attr = plot->Clear##attr; \
+   plot->Clear##attr = Clear##attr;
+
+/* Use the above macro to override all the inherited attribute accessors. */
+SET_PLOT_ACCESSORS(MinTick)
+SET_PLOT_ACCESSORS(Abbrev)
+SET_PLOT_ACCESSORS(Gap)
+SET_PLOT_ACCESSORS(LogGap)
+SET_PLOT_ACCESSORS(LogPlot)
+SET_PLOT_ACCESSORS(LogTicks)
+SET_PLOT_ACCESSORS(LogLabel)
+SET_PLOT_ACCESSORS(LabelUp)
+SET_PLOT_ACCESSORS(DrawAxes)
+SET_PLOT_ACCESSORS(LabelUnits)
+SET_PLOT_ACCESSORS(MinTickLen)
+SET_PLOT_ACCESSORS(MajTickLen)
+SET_PLOT_ACCESSORS(NumLab)
+SET_PLOT_ACCESSORS(NumLabGap)
+SET_PLOT_ACCESSORS(TextLab)
+SET_PLOT_ACCESSORS(TextLabGap)
+
+#undef SET_PLOT_ACCESSORS
+
+
+/* Now do attributes that are not axis specific. */
+
+#define SET_PLOT_ACCESSORS(attr) \
+   parent_set##attr = plot->Set##attr; \
+   plot->Set##attr = Set##attr; \
+   parent_clear##attr = plot->Clear##attr; \
+   plot->Clear##attr = Clear##attr;
+
+SET_PLOT_ACCESSORS(Ink)
+SET_PLOT_ACCESSORS(Tol)
+SET_PLOT_ACCESSORS(Invisible)
+SET_PLOT_ACCESSORS(TickAll)
+SET_PLOT_ACCESSORS(ForceExterior)
+SET_PLOT_ACCESSORS(Border)
+SET_PLOT_ACCESSORS(Clip)
+SET_PLOT_ACCESSORS(ClipOp)
+SET_PLOT_ACCESSORS(Escape)
+SET_PLOT_ACCESSORS(Grid)
+SET_PLOT_ACCESSORS(Labelling)
+SET_PLOT_ACCESSORS(Style)
+SET_PLOT_ACCESSORS(Font)
+SET_PLOT_ACCESSORS(Colour)
+SET_PLOT_ACCESSORS(Width)
+SET_PLOT_ACCESSORS(Size)
+
+#undef SET_PLOT_ACCESSORS
+
+
+/* Store replacement pointers for methods which will be over-ridden by new
+   member functions implemented here. */
+   plot->Grid = Grid;
+   plot->Text = Text;
+   plot->SetTickValues = SetTickValues;
+   plot->PolyCurve = PolyCurve;
+   plot->Border = Border;
+   plot->BoundingBox = BoundingBox;
+   plot->GetGrfContext = GetGrfContext;
+   plot->GrfPop = GrfPop;
+   plot->GrfPush = GrfPush;
+   plot->GrfSet = GrfSet;
+   plot->GridLine = GridLine;
+   plot->Mark = Mark;
+   plot->Curve = Curve;
+   plot->GenCurve = GenCurve;
+   plot->Clip = Clip;
+   mapping->Transform = Transform;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "Plot3D", "Provide facilities for 3D graphical output" );
+
+/* Create a FrameSet that can be used when calling astCast to indicate
+   the class to which we want to cast. */
+   LOCK_MUTEX3
+   if( !dummy_frameset ) {
+      dummy_frame = astFrame( 1, " ", status );
+      dummy_frameset = astFrameSet( dummy_frame, " ", status );
+      dummy_frame = astAnnul( dummy_frame );
+   }
+   UNLOCK_MUTEX3
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     CmpMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstPlot3D *this;       /* Pointer to Plot3D structure */
+   int result;            /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the Plot3D structure. */
+   this = (AstPlot3D *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->plotxy, mode, extra, fail );
+   if( !result ) result = astManageLock( this->plotxz, mode, extra, fail );
+   if( !result ) result = astManageLock( this->plotyz, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static void Mark( AstPlot *this_plot, int nmark, int ncoord, int indim,
+                  const double *in, int type, int *status ){
+/*
+*  Name:
+*     Mark
+
+*  Purpose:
+*     Draw a set of markers for a Plot3D.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void Mark( AstPlot *this, int nmark, int ncoord, int indim,
+*                const double *in, int type, int *status )
+
+*  Class Membership:
+*     Plot3d member function (overrides the astMark method inherited form
+*     the parent Plot class).
+
+*  Description:
+*     This function draws a set of markers (symbols) at positions
+*     specified in the physical coordinate system of a Plot3D. The
+*     positions are transformed into graphical coordinates to
+*     determine where the markers should appear within the plotting
+*     area.
+*
+*     They are drawn on a 2D plane that has a normal vector given by the
+*     current value of the Plot3D's "Norm" attribute.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     nmark
+*        The number of markers to draw. This may be zero, in which
+*        case nothing will be drawn.
+*     ncoord
+*        The number of coordinates being supplied for each mark
+*        (i.e. the number of axes in the current Frame of the Plot, as
+*        given by its Naxes attribute).
+*     indim
+*        The number of elements along the second dimension of the "in"
+*        array (which contains the marker coordinates). This value is
+*        required so that the coordinate values can be correctly
+*        located if they do not entirely fill this array. The value
+*        given should not be less than "nmark".
+*     in
+*        The address of the first element of a 2-dimensional array of
+*        shape "[ncoord][indim]" giving the
+*        physical coordinates of the points where markers are to be
+*        drawn. These should be stored such that the value of
+*        coordinate number "coord" for input mark number "mark" is
+*        found in element "in[coord][mark]".
+*     type
+*        A value specifying the type (e.g. shape) of marker to be
+*        drawn. The set of values which may be used (and the shapes
+*        that will result) is determined by the underlying graphics
+*        system.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Markers are not drawn at positions which have any coordinate
+*     equal to the value AST__BAD (or where the transformation into
+*     graphical coordinates yields coordinates containing the value
+*     AST__BAD).
+*     - An error results if the base Frame of the Plot is not 3-dimensional.
+*     - An error also results if the transformation between the
+*     current and base Frames of the Plot is not defined (i.e. the
+*     Plot's TranInverse attribute is zero).
+*/
+
+/* Local Variables: */
+   AstMapping *mapping;    /* Pointer to graphics->physical mapping */
+   AstPlot3D *this;        /* Pointer to the Plot3D structure */
+   AstPointSet *pset1;     /* PointSet holding physical positions */
+   AstPointSet *pset2;     /* PointSet holding graphics positions */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   const double **ptr1;    /* Pointer to physical positions */
+   double **ptr2;          /* Pointer to graphics positions */
+   double *xpd;            /* Pointer to next double precision x value */
+   double *ypd;            /* Pointer to next double precision y value */
+   double *zpd;            /* Pointer to next double precision z value */
+   float *x;               /* Pointer to single precision x values */
+   float *xpf;             /* Pointer to next single precision x value */
+   float *y;               /* Pointer to single precision y values */
+   float *ypf;             /* Pointer to next single precision y value */
+   float *z;               /* Pointer to single precision z values */
+   float *zpf;             /* Pointer to next single precision z value */
+   float norm[ 3 ];        /* Single precision normal vector */
+   int axis;               /* Axis index */
+   int i;                  /* Loop count */
+   int naxes;              /* No. of axes in the base Frame */
+   int nn;                 /* Number of good marker positions */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Plot3D structure. */
+   this = (AstPlot3D *) this_plot;
+
+/* Store the current method and class for inclusion in error messages
+   generated by lower level functions. */
+   method = "astMark";
+   class = astClass( this );
+
+/* Check the base Frame of the Plot is 3-D. */
+   naxes = astGetNin( this );
+   if( naxes != 3 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 3.", status, method, class, naxes, class );
+   }
+
+/* Also validate the input array dimension argument. */
+   if ( astOK && ( indim < nmark ) ) {
+      astError( AST__DIMIN, "%s(%s): The input array dimension value "
+                "(%d) is invalid.", status, method, class, indim );
+      astError( AST__DIMIN, "This should not be less than the number of "
+                "markers being drawn (%d).", status, nmark );
+   }
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+   astGrfAttrs( this, AST__MARKS_ID, 1, GRF__MARK, method, class );
+
+/* Create a PointSet to hold the supplied physical coordinates. */
+   pset1 = astPointSet( nmark, ncoord, "", status );
+
+/* Allocate memory to hold pointers to the first value on each axis. */
+   ptr1 = (const double **) astMalloc( sizeof( const double * )*
+                                       (size_t)( ncoord ));
+
+/* Check the pointer can be used, then store pointers to the first value
+   on each axis. */
+   if( astOK ){
+      for( axis = 0; axis < ncoord; axis++ ){
+         ptr1[ axis ] = in + axis*indim;
+      }
+   }
+
+/* Store these pointers in the PointSet. */
+   astSetPoints( pset1, (double **) ptr1 );
+
+/* Transform the supplied data from the current frame (i.e. physical
+   coordinates) to the base frame (i.e. graphics coordinates) using
+   the inverse Mapping defined by the Plot. */
+   mapping = astGetMapping( this, AST__BASE, AST__CURRENT );
+   pset2 = astTransform( mapping, pset1, 0, NULL );
+   mapping = astAnnul( mapping );
+
+/* Get pointers to the graphics coordinates. */
+   ptr2 = astGetPoints( pset2 );
+
+/* Allocate memory to hold single precision versions of the graphics
+   coordinates. */
+   x = (float *) astMalloc( sizeof( float )*(size_t) nmark );
+   y = (float *) astMalloc( sizeof( float )*(size_t) nmark );
+   z = (float *) astMalloc( sizeof( float )*(size_t) nmark );
+
+/* Check the pointers can be used. */
+   if( astOK ){
+
+/* Store pointers to the next single and double precision x, y and z
+   values. */
+      xpf = x;
+      ypf = y;
+      zpf = z;
+      xpd = ptr2[ 0 ];
+      ypd = ptr2[ 1 ];
+      zpd = ptr2[ 2 ];
+
+/* Convert the double precision values to single precision, rejecting
+   any bad marker positions. */
+      nn = 0;
+      for( i = 0; i < nmark; i++ ){
+         if( *xpd != AST__BAD && *ypd != AST__BAD && *zpd != AST__BAD ){
+            nn++;
+            *(xpf++) = (float) *(xpd++);
+            *(ypf++) = (float) *(ypd++);
+            *(zpf++) = (float) *(zpd++);
+         } else {
+            xpd++;
+            ypd++;
+            zpd++;
+         }
+      }
+
+/* If the nornmal vector has non-zero length, draw the remaining markers. */
+      norm[ 0 ] = (float) astGetNorm( this, 0 );
+      norm[ 1 ] = (float) astGetNorm( this, 1 );
+      norm[ 2 ] = (float) astGetNorm( this, 2 );
+
+      if( norm[ 0 ] != 0.0 || norm[ 1 ] != 0.0 || norm[ 2 ] != 0.0 ){
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+         LOCK_MUTEX2;
+
+         if( !astG3DMark( nn, x, y, z, type, norm ) ) {
+            astError( AST__GRFER, "%s(%s): Graphics error in astG3DMark. ", status,
+                   method, class );
+         }
+
+/* Allow the next thread to proceed. */
+         UNLOCK_MUTEX2;
+
+      } else if( astOK ) {
+         astError( AST__ATTIN, "%s(%s): The vector specified by the Norm "
+                   "attribute has zero length.", status, method, class );
+      }
+   }
+
+/* Free the memory used to store single precision graphics coordinates. */
+   x = (float *) astFree( (void *) x );
+   y = (float *) astFree( (void *) y );
+   z = (float *) astFree( (void *) z );
+
+/* Annul the PointSets. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* Free the memory holding the pointers to the first value on each axis. */
+   ptr1 = (const double **) astFree( (void *) ptr1 );
+
+/* Re-establish the original graphical attributes. */
+   astGrfAttrs( this, AST__MARKS_ID, 0, GRF__MARK, method, class );
+
+/* Return */
+   return;
+}
+
+static int Plot3DAttr( AstKeyMap *grfconID, int attr, double value,
+                       double *old_value, int prim ){
+/*
+*  Name:
+*     Plot3DAttr
+
+*  Purpose:
+*     Get or set the value of a 3D grf attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Plot3DAttr( AstKeyMap *grfconID, int attr, double value,
+*                     double *old_value, int prim )
+
+*  Class Membership:
+*     Plot3D member function.
+
+*  Description:
+*     This function is called by one of the three encapsulated 2D Plots to
+*     get or set the current value of a specified graphics attribute. It
+*     forwards the call to the grf3D module being used by this Plot3D. It
+*     should be registered with each of the 2D Plots using astGrfSet.
+
+*  Parameters:
+*     grfconID
+*       The Plot's GrfContext KeyMap. This is used to identify which of
+*       the three Plots is calling this function.
+*     attr
+*       An integer value identifying the required attribute. This should
+*       be one of the symbolic values defined in grf.h.
+*     value
+*       A new value to store for the attribute. If this is AST__BAD
+*       no value is stored.
+*     old_value
+*       A pointer to a double in which to return the attribute value.
+*       If this is NULL, no value is returned.
+*     prim
+*       The sort of graphics primitive to be drawn with the new attribute.
+*       Identified by one of the values defined in grf.h.
+
+*  Returned Value:
+*     An integer value of 0 is returned if an error occurs, and 1 otherwise.
+
+*/
+
+/* Local Variables: */
+   int result;
+   int *status;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* Use the function in the external Grf3D module, selected at link-time
+   using ast_link options. Note, attribute values are the same for each
+   of the three Plot. */
+   result = astG3DAttr( attr, value, old_value, prim );
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Return the result. */
+   return result;
+}
+
+static int Plot3DCap( AstKeyMap *grfconID, int cap, int value ){
+/*
+*  Name:
+*     Plot3DCap
+
+*  Purpose:
+*     Determine if the 3D grf module has a given capability.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Plot3DCap( AstKeyMap *grfconID, int cap, int value )
+
+*  Class Membership:
+*     Plot3D member function.
+
+*  Description:
+*     This function is called by one of the three encapsulated 2D Plots to
+*     determine if a given graphics capability is available. It forwards
+*     the call to the grf3D module being used by this Plot3D. It should be
+*     registered with each of the 2D Plots using astGrfSet.
+
+*  Parameters:
+*     grfconID
+*       The Plot's GrfContext KeyMap. This is
+*       used to identify which of the three Plots is calling this function.
+*     cap
+*       The capability being inquired about. This will be one of the
+*       following constants defined in grf.h: GRF__SCALES, GRF__MJUST,
+*       GRF__ESC,
+*     value
+*       The use of this parameter depends on the value of "cap" as
+*       described in the documentation for the astGrfSet function in the
+*       Plot class.
+
+*  Returned Value:
+*     The value returned by the function depends on the value of "cap"
+*     as described in the astGrfSet documentation. Zero is returned if
+*     the supplied capability is not recognised.
+
+*/
+
+/* Local Variables: */
+   int result;
+   int *status;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* The astGScales function is implemented by code within the Plot3D class
+   (not within the grf3D module). The Plot3D class assumes all axes are
+   equally scaled. */
+   if( cap == GRF__SCALES ) {
+      result = 1;
+
+/* All grf3D modules are assumed to support "M" justification. */
+   } else if( cap == GRF__MJUST ) {
+      result = 1;
+
+/* Forward all other capability requests to the grf3D module. */
+   } else {
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+      LOCK_MUTEX2;
+
+      result = astG3DCap( cap, value );
+
+/* Allow the next thread to proceed. */
+      UNLOCK_MUTEX2;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int Plot3DFlush( AstKeyMap *grfconID ){
+/*
+*  Name:
+*     Plot3DFlush
+
+*  Purpose:
+*     Flush any buffered graphical output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Plot3DFlush( AstKeyMap *grfconID )
+
+*  Class Membership:
+*     Plot3D member function.
+
+*  Description:
+*     This function is called by one of the three encapsulated 2D Plots to
+*     ensure that the display device is up-to-date by flushing any pending
+*     graphics to the output device. It forwards the call to the grf3D module
+*     being used by this Plot3D. It should be registered with each of the
+*     2D Plots using astGrfSet.
+
+*  Parameters:
+*     grfconID
+*       The Plot's GrfContext KeyMap. This is
+*       used to identify which of the three Plots is calling this function.
+
+*  Returned Value:
+*     An integer value of 0 is returned if an error occurs, and 1 otherwise.
+
+*/
+
+/* Local Variables: */
+   int result;
+   int *status;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* Use the function in the external Grf3D module, selected at link-time
+   using ast_link options. */
+   result = astG3DFlush();
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Return the result. */
+   return result;
+}
+
+static int Plot3DLine( AstKeyMap *grfconID, int n, const float *x, const float *y ){
+/*
+*  Name:
+*     Plot3DLine
+
+*  Purpose:
+*     Draw a polyline on a 2D surface.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Plot3DLine( AstKeyMap *grfconID, int n, const float *x,
+*                     const float *y )
+
+*  Class Membership:
+*     Plot3D member function.
+
+*  Description:
+*     This function is called by one of the three encapsulated 2D Plots to
+*     draw a polyline. It forwards the call to the grf3D module being used
+*     by this Plot3D. It should be registered with each of the 2D Plots
+*     using astGrfSet.
+
+*  Parameters:
+*     grfconID
+*       The Plot's GrfContext KeyMap. This is
+*       used to identify which of the three Plots is calling this function.
+*     n
+*       The number of positions to be joined together.
+*     x
+*       A pointer to an array holding the "n" x values.
+*     y
+*       A pointer to an array holding the "n" y values.
+
+*  Returned Value:
+*     An integer value of 0 is returned if an error occurs, and 1 otherwise.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *grfcon;
+   double gcon;
+   float *work;
+   float *x3d = NULL;
+   float *y3d = NULL;
+   float *z3d = NULL;
+   int i;
+   int plane;
+   int result = 0;
+   int *status;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a genuine pointer from the supplied grfcon identifier. */
+   grfcon = astMakePointer( grfconID );
+
+/* Report an error if no grfcon object was supplied. */
+   if( !grfcon ) {
+      astError( AST__INTER, "astG3DLine(Plot3D): No grfcon Object supplied "
+                "(internal AST programming error)." , status);
+
+/* If a grfcon Object was supplied, get the graphics box array out of it. */
+   } else if( !astMapGet0D( grfcon, "Gcon", &gcon ) ) {
+      astError( AST__INTER, "astG3DLine(Plot3D): No \"Gcon\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+
+/* Also get the plane index out of it. */
+   } else if( !astMapGet0I( grfcon, "Plane", &plane ) ) {
+      astError( AST__INTER, "astG3DLine(Plot3D): No \"Plane\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+   }
+
+/* Allocate memory to hold the "n" values for the missing coordinate. */
+   work = astMalloc( sizeof( float )*(size_t) n );
+   if( work ) {
+
+/* Set up pointers to the x, y and z arrays in the 3D graphics system.
+   Fill the missing array with suitable values (the constant value of
+   the third axis on the plane being drawn). */
+      if( plane == XY ) {
+         x3d = (float *) x;
+         y3d = (float *) y;
+         z3d = work;
+
+         for( i = 0; i < n; i++ ) z3d[ i ] = gcon;
+
+      } else if( plane == XZ ) {
+         x3d = (float *) x;
+         y3d = work;
+         z3d = (float *) y;
+         for( i = 0; i < n; i++ ) y3d[ i ] = gcon;
+
+      } else if( plane == YZ ) {
+         x3d = work;
+         y3d = (float *) x;
+         z3d = (float *) y;
+         for( i = 0; i < n; i++ ) x3d[ i ] = gcon;
+
+      } else {
+         astError( AST__INTER, "astG3DLine(Plot3D): Illegal plane "
+                   "identifier %d supplied (internal AST programming "
+                   "error).", status, plane );
+      }
+
+/* If ok, draw the lines in the 3D graphics coordinate space. */
+      if( x3d ) {
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+         LOCK_MUTEX2;
+
+/* Draw the line */
+         result = astG3DLine( n, x3d, y3d, z3d );
+
+/* Allow the next thread to proceed. */
+         UNLOCK_MUTEX2;
+      }
+   }
+
+/* Free resources. */
+   work = astFree( work );
+
+/* Return the result. */
+   return result;
+}
+
+static int Plot3DMark( AstKeyMap *grfconID, int n, const float *x,
+                       const float *y, int type ){
+/*
+*  Name:
+*     Plot3DMark
+
+*  Purpose:
+*     Draw a set of markers.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Plot3DMark( AstKeyMap *grfconID, int n, const float *x,
+*                     const float *y, int type )
+
+*  Class Membership:
+*     Plot3D member function.
+
+*  Description:
+*     This function is called by one of the three encapsulated 2D Plots to
+*     draw a set of markers. It forwards the call to the grf3D module being
+*     used by this Plot3D. It should be registered with each of the 2D Plots
+*     using astGrfSet.
+
+*  Parameters:
+*     grfconID
+*       The Plot's GrfContext KeyMap. This is
+*       used to identify which of the three Plots is calling this function.
+*     n
+*       The number of markers to draw.
+*     x
+*       A pointer to an array holding the "n" x values.
+*     y
+*       A pointer to an array holding the "n" y values.
+*     type
+*       An integer which can be used to indicate the type of marker symbol
+*       required.
+
+*  Returned Value:
+*     An integer value of 0 is returned if an error occurs, and 1 otherwise.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *grfcon;
+   double gcon;
+   float *work;
+   float *x3d = NULL;
+   float *y3d = NULL;
+   float *z3d = NULL;
+   float norm[ 3 ];
+   int i;
+   int plane;
+   int rc;
+   int result = 0;
+   int *status;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a genuine pointer from the supplied grfcon identifier. */
+   grfcon = astMakePointer( grfconID );
+
+/* Report an error if no grfcon object was supplied. */
+   if( !grfcon ) {
+      astError( AST__INTER, "astG3DMark(Plot3D): No grfcon Object supplied "
+                "(internal AST programming error)." , status);
+
+/* If a grfcon Object was supplied, get the graphics box array out of it. */
+   } else if( !astMapGet0D( grfcon, "Gcon", &gcon ) ) {
+      astError( AST__INTER, "astG3DMark(Plot3D): No \"Gcon\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+
+/* If a grfcon Object was supplied, get the RootCorner value out of it. */
+   } else if( !astMapGet0I( grfcon, "RootCorner", &rc ) ) {
+      astError( AST__INTER, "astG3DLine(Plot3D): No \"RootCornern\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+
+/* Also get the plane index out of it. */
+   } else if( !astMapGet0I( grfcon, "Plane", &plane ) ) {
+      astError( AST__INTER, "astG3DMark(Plot3D): No \"Plane\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+   }
+
+/* Allocate memory to hold the "n" values for the missing coordinate. */
+   work = astMalloc( sizeof( float )*(size_t) n );
+   if( work ) {
+
+/* Set up pointers to the x, y and z arrays in the 3D graphics system.
+   Fill the missing array with suitable values (the constant value of
+   the third axis on the plane being drawn). Set the normal vector for
+   the markers so that they are drawn in the plane described by the Plot.*/
+      if( plane == XY ) {
+         x3d = (float *) x;
+         y3d = (float *) y;
+         z3d = work;
+         for( i = 0; i < n; i++ ) z3d[ i ] = gcon;
+         norm[ 0 ] = 0;
+         norm[ 1 ] = 0;
+         norm[ 2 ] = ( rc & 4 ) ? 1 : -1;
+
+      } else if( plane == XZ ) {
+         x3d = (float *) x;
+         y3d = work;
+         z3d = (float *) y;
+         for( i = 0; i < n; i++ ) y3d[ i ] = gcon;
+         norm[ 0 ] = 0;
+         norm[ 1 ] = ( rc & 2 ) ? 1 : -1;
+         norm[ 2 ] = 0;
+
+      } else if( plane == YZ ) {
+         x3d = work;
+         y3d = (float *) x;
+         z3d = (float *) y;
+         for( i = 0; i < n; i++ ) x3d[ i ] = gcon;
+         norm[ 0 ] = ( rc & 1 ) ? 1 : -1;
+         norm[ 1 ] = 0;
+         norm[ 2 ] = 0;
+
+      } else {
+         astError( AST__INTER, "astG3DMark(Plot3D): Illegal plane "
+                   "identifier %d supplied (internal AST programming "
+                   "error).", status, plane );
+      }
+
+/* If ok, draw the markers in the 3D graphics coordinate space. */
+      if( x3d ) {
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+         LOCK_MUTEX2;
+
+/* Draw the markers */
+         result = astG3DMark( n, x3d, y3d, z3d, type, norm );
+
+/* Allow the next thread to proceed. */
+         UNLOCK_MUTEX2;
+      }
+   }
+
+/* Free resources. */
+   work = astFree( work );
+
+/* Return the result. */
+   return result;
+}
+
+static int Plot3DQch( AstKeyMap *grfconID, float *chv, float *chh ){
+/*
+*  Name:
+*     Plot3DQch
+
+*  Purpose:
+*     Get the current text size.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Plot3DQch( AstKeyMap *grfconID, float *chv, float *chh )
+
+*  Class Membership:
+*     Plot3D member function.
+
+*  Description:
+*     This function is called by one of the three encapsulated 2D Plots to
+*     get the current text size. It forwards the call to the grf3D module
+*     being used by this Plot3D. It should be registered with each of the
+*     2D Plots using astGrfSet.
+*
+*     The grf3D module assumes that the 3D graphics axes are equally
+*     scaled and therefore the text height does not depend on the text
+*     orientation. Therefore, "chv" and "chh" are returned holding the
+*     same value.
+
+*  Parameters:
+*     grfconID
+*       The Plot's GrfContext KeyMap. This is
+*       used to identify which of the three Plots is calling this function.
+*     chv
+*        A pointer to the float which is to receive the height of
+*        characters drawn with a vertical baseline in the 2D Plot. This
+*        will be an increment in the 2D X axis.
+*     chh
+*       A pointer to the float which is to receive the height of
+*       characters drawn with a horizontal baseline in the 2D Plot. This
+*       will be an increment in the 2D Y axis.
+
+*  Returned Value:
+*     An integer value of 0 is returned if an error occurs, and 1 otherwise.
+
+*/
+
+/* Local Variables: */
+   int result;
+   float ch;
+   int *status;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+   LOCK_MUTEX2;
+
+/* Use the function in the external Grf3D module, selected at link-time
+   using ast_link options. Note, text height is the same for each
+   of the three Plot. */
+   result = astG3DQch( &ch );
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2;
+
+/* Store the value in both the returned values. */
+   *chv = ch;
+   *chh = ch;
+
+/* Return the error flag. */
+   return result;
+}
+
+static int Plot3DScales( AstKeyMap *grfconID, float *alpha, float *beta ){
+/*
+*  Name:
+*     Plot3DScales
+
+*  Purpose:
+*     Get the 2D axis scales.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*      int Plot3DScales( AstKeyMap *grfconID, float *alpha, float *beta )
+
+*  Class Membership:
+*     Plot3D member function.
+
+*  Description:
+*     This function is called by one of the three encapsulated 2D Plots to
+*     get the relative scales of the 2D axes. Since the grf3D module
+*     assumes that all graphics axes are equally scaled, it just returns 1.0
+*     for alpha and beta.
+
+*  Parameters:
+*     grfconID
+*       The Plot's GrfContext KeyMap. This is
+*       used to identify which of the three Plots is calling this function.
+*     alpha
+*       A pointer to the float which is to receive the scale for the X
+*       axis
+*     beta
+*       A pointer to the float which is to receive the scale for the Y
+*       axis
+
+*  Returned Value:
+*     An integer value of 0 is returned if an error occurs, and 1 otherwise.
+
+*/
+
+   *alpha = 1.0;
+   *beta = 1.0;
+   return 1;
+}
+
+static int Plot3DText( AstKeyMap *grfconID, const char *text, float x, float y,
+                       const char *just, float upx, float upy ){
+/*
+*  Name:
+*     Plot3DText
+
+*  Purpose:
+*     Draw a text string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Plot3DText( AstKeyMap *grfconID, const char *text, float x, float y,
+*                     const char *just, float upx, float upy )
+
+*  Class Membership:
+*     Plot3D member function.
+
+*  Description:
+*     This function is called by one of the three encapsulated 2D Plots to
+*     draw a text string. It forwards the call to the grf3D module being
+*     used by this Plot3D. It should be registered with each of the 2D Plots
+*     using astGrfSet.
+
+*  Parameters:
+*     grfconID
+*        The Plot's GrfContext KeyMap. This is
+*        used to identify which of the three Plots is calling this function.
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     x
+*        The reference x coordinate.
+*     y
+*        The reference y coordinate.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y.
+*     upx
+*        The x component of the up-vector for the text.
+*     upy
+*        The y component of the up-vector for the text.
+
+*  Returned Value:
+*     An integer value of 0 is returned if an error occurs, and 1 otherwise.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *grfcon;
+   double gcon;
+   float norm[ 3 ];
+   float ref[ 3 ];
+   float up[ 3 ];
+   int plane;
+   int rc;
+   int result = 0;
+   int *status;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a genuine pointer from the supplied grfcon identifier. */
+   grfcon = astMakePointer( grfconID );
+
+/* Report an error if no grfcon object was supplied. */
+   if( !grfcon ) {
+      astError( AST__INTER, "astG3DText(Plot3D): No grfcon Object supplied "
+                "(internal AST programming error)." , status);
+
+/* If a grfcon Object was supplied, get the graphics box array out of it. */
+   } else if( !astMapGet0D( grfcon, "Gcon", &gcon ) ) {
+      astError( AST__INTER, "astG3DText(Plot3D): No \"Gcon\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+
+/* If a grfcon Object was supplied, get the RootCorner value out of it. */
+   } else if( !astMapGet0I( grfcon, "RootCorner", &rc ) ) {
+      astError( AST__INTER, "astG3DLine(Plot3D): No \"RootCornern\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+
+/* Also get the plane index out of it. */
+   } else if( !astMapGet0I( grfcon, "Plane", &plane ) ) {
+      astError( AST__INTER, "astG3DText(Plot3D): No \"Plane\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+
+/* If OK, draw the text. */
+   } else {
+
+/* Set up the reference, up and normal vectors so that the text appears
+   on the required plane. */
+      if( plane == XY ) {
+         ref[ 0 ] = x;
+         ref[ 1 ] = y;
+         ref[ 2 ] = gcon;
+         norm[ 0 ] = 0;
+         norm[ 1 ] = 0;
+         norm[ 2 ] = ( rc & 4 ) ? 1 : -1;
+         up[ 0 ] = upx;
+         up[ 1 ] = upy;
+         up[ 2 ] = 0;
+
+      } else if( plane == XZ ) {
+         ref[ 0 ] = x;
+         ref[ 1 ] = gcon;
+         ref[ 2 ] = y;
+         norm[ 0 ] = 0;
+         norm[ 1 ] = ( rc & 2 ) ? 1 : -1;
+         norm[ 2 ] = 0;
+         up[ 0 ] = upx;
+         up[ 1 ] = 0;
+         up[ 2 ] = upy;
+
+      } else if( plane == YZ ) {
+         ref[ 0 ] = gcon;
+         ref[ 1 ] = x;
+         ref[ 2 ] = y;
+         norm[ 0 ] = ( rc & 1 ) ? 1 : -1;
+         norm[ 1 ] = 0;
+         norm[ 2 ] = 0;
+         up[ 0 ] = 0;
+         up[ 1 ] = upx;
+         up[ 2 ] = upy;
+
+      } else {
+         astError( AST__INTER, "astG3DText(Plot3D): Illegal plane "
+                   "identifier %d supplied (internal AST programming "
+                   "error).", status, plane );
+      }
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+      LOCK_MUTEX2;
+
+/* If ok, draw the markers in the 3D graphics coordinate space. */
+      if( astOK ) result = astG3DText( text, ref, just, up, norm );
+
+/* Allow the next thread to proceed. */
+      UNLOCK_MUTEX2;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int Plot3DTxExt( AstKeyMap *grfconID, const char *text, float x, float y,
+                        const char *just, float upx, float upy, float *xb,
+                        float *yb ){
+/*
+*  Name:
+*     Plot3DTxExt
+
+*  Purpose:
+*     Determine the plotted extent of a text string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int Plot3DTxExt( AstKeyMap *grfconID, const char *text, float x,
+*                      float y, const char *just, float upx, float upy,
+*                      float *xb, float *yb )
+
+*  Class Membership:
+*     Plot3D member function.
+
+*  Description:
+*     This function is called by one of the three encapsulated 2D Plots to
+*     determine the extent a string would have if plotted using Plot3DText.
+*     It forwards the call to the grf3D module being used by this Plot3D. It
+*     should be registered with each of the 2D Plots using astGrfSet.
+
+*  Parameters:
+*     grfconID
+*        The Plot's GrfContext KeyMap. This is
+*        used to identify which of the three Plots is calling this function.
+*     text
+*        Pointer to a null-terminated character string to be displayed.
+*     x
+*        The reference x coordinate.
+*     y
+*        The reference y coordinate.
+*     just
+*        A character string which specifies the location within the
+*        text string which is to be placed at the reference position
+*        given by x and y.
+*     upx
+*        The x component of the up-vector for the text.
+*     upy
+*        The y component of the up-vector for the text.
+*     xb
+*        An array of 4 elements in which to return the x coordinate of
+*        each corner of the bounding box.
+*     yb
+*        An array of 4 elements in which to return the y coordinate of
+*        each corner of the bounding box.
+
+*  Returned Value:
+*     An integer value of 0 is returned if an error occurs, and 1 otherwise.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *grfcon;
+   double gcon;
+   float *xb3d = NULL;
+   float *yb3d = NULL;
+   float *zb3d = NULL;
+   float bl[ 3 ];
+   float norm[ 3 ];
+   float ref[ 3 ];
+   float unused[ 4 ];
+   float up[ 3 ];
+   int plane;
+   int rc;
+   int result = 0;
+   int *status;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a genuine pointer from the supplied grfcon identifier. */
+   grfcon = astMakePointer( grfconID );
+
+/* Report an error if no grfcon object was supplied. */
+   if( !grfcon ) {
+      astError( AST__INTER, "astG3DTxExt(Plot3D): No grfcon Object supplied "
+                "(internal AST programming error)." , status);
+
+/* If a grfcon Object was supplied, get the graphics box array out of it. */
+   } else if( !astMapGet0D( grfcon, "Gcon", &gcon ) ) {
+      astError( AST__INTER, "astG3DTxExt(Plot3D): No \"Gcon\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+
+/* If a grfcon Object was supplied, get the RootCorner value out of it. */
+   } else if( !astMapGet0I( grfcon, "RootCorner", &rc ) ) {
+      astError( AST__INTER, "astG3DLine(Plot3D): No \"RootCornern\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+
+/* Also get the plane index out of it. */
+   } else if( !astMapGet0I( grfcon, "Plane", &plane ) ) {
+      astError( AST__INTER, "astG3DTxExt(Plot3D): No \"Plane\" key found "
+                "in the supplied grfcon Object (internal AST programming "
+                "error)." , status);
+
+/* If OK, get the extent of the text. */
+   } else {
+
+/* Set up the reference, up and normal vectors so that the text appears
+   on the required plane. */
+      if( plane == XY ) {
+         ref[ 0 ] = x;
+         ref[ 1 ] = y;
+         ref[ 2 ] = gcon;
+         norm[ 0 ] = 0;
+         norm[ 1 ] = 0;
+         norm[ 2 ] = ( rc & 4 ) ? 1 : -1;
+         up[ 0 ] = upx;
+         up[ 1 ] = upy;
+         up[ 2 ] = 0;
+         xb3d = xb;
+         yb3d = yb;
+         zb3d = unused;
+
+      } else if( plane == XZ ) {
+         ref[ 0 ] = x;
+         ref[ 1 ] = gcon;
+         ref[ 2 ] = y;
+         norm[ 0 ] = 0;
+         norm[ 1 ] = ( rc & 2 ) ? 1 : -1;
+         norm[ 2 ] = 0;
+         up[ 0 ] = upx;
+         up[ 1 ] = 0;
+         up[ 2 ] = upy;
+         xb3d = xb;
+         yb3d = unused;
+         zb3d = yb;
+
+      } else if( plane == YZ ) {
+         ref[ 0 ] = gcon;
+         ref[ 1 ] = x;
+         ref[ 2 ] = y;
+         norm[ 0 ] = ( rc & 1 ) ? 1 : -1;
+         norm[ 1 ] = 0;
+         norm[ 2 ] = 0;
+         up[ 0 ] = 0;
+         up[ 1 ] = upx;
+         up[ 2 ] = upy;
+         xb3d = unused;
+         yb3d = xb;
+         zb3d = yb;
+
+      } else {
+         astError( AST__INTER, "astG3DTxExt(Plot3D): Illegal plane "
+                   "identifier %d supplied (internal AST programming "
+                   "error).", status, plane );
+      }
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+      LOCK_MUTEX2;
+
+/* If ok, get the extent of the text. */
+      if( astOK ) result = astG3DTxExt( text, ref, just, up, norm, xb3d, yb3d,
+                                        zb3d, bl );
+/* Allow the next thread to proceed. */
+      UNLOCK_MUTEX2;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void PolyCurve( AstPlot *this, int npoint, int ncoord, int indim,
+                       const double *in, int *status ){
+/*
+*  Name:
+*     PolyCurve
+
+*  Purpose:
+*     Draw a series of connected geodesic curves.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void PolyCurve( AstPlot *this, int npoint, int ncoord, int indim,
+*                     const double *in, int *status )
+
+*  Class Membership:
+*     Plot method (overrides the astPolyCurve method inherited form the
+*     Plot class)
+
+*  Description:
+*     This function joins a series of points specified in the physical
+*     coordinate system of a Plot by drawing a sequence of geodesic
+*     curves.  It is equivalent to making repeated use of the astCurve
+*     function (q.v.), except that astPolyCurve will generally be more
+*     efficient when drawing many geodesic curves end-to-end. A
+*     typical application of this might be in drawing contour lines.
+*
+*     As with astCurve, full account is taken of the Mapping between
+*     physical and graphical coordinate systems. This includes any
+*     discontinuities and clipping established using astClip.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot.
+*     npoint
+*        The number of points between which geodesic curves are to be drawn.
+*     ncoord
+*        The number of coordinates being supplied for each point (i.e.
+*        the number of axes in the current Frame of the Plot, as given
+*        by its Naxes attribute).
+*     indim
+*        The number of elements along the second dimension of the "in"
+*        array (which contains the input coordinates). This value is
+*        required so that the coordinate values can be correctly
+*        located if they do not entirely fill this array. The value
+*        given should not be less than "npoint".
+*     in
+*        The address of the first element in a 2-dimensional array of shape
+*        "[ncoord][indim]" giving the
+*        physical coordinates of the points which are to be joined in
+*        sequence by geodesic curves. These should be stored such that
+*        the value of coordinate number "coord" for point number
+*        "point" is found in element "in[coord][point]".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - No curve is drawn on either side of any point which has any
+*     coordinate equal to the value AST__BAD.
+*     - An error results if the base Frame of the Plot is not
+*     2-dimensional.
+*     - An error also results if the transformation between the
+*     current and base Frames of the Plot is not defined (i.e. the
+*     Plot's TranInverse attribute is zero).
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+   astError( AST__INTER, "astPolyCurve(%s): The astPolyCurve "
+             "method cannot be used with a %s (programming error).", status,
+             astGetClass( this ), astGetClass( this ) );
+}
+
+static void RemoveFrame( AstFrameSet *this_fset, int iframe, int *status ) {
+/*
+*  Name:
+*     RemoveFrame
+
+*  Purpose:
+*     Remove a Frame from a Plot3D.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "plot.h"
+*     void RemoveFrame( AstFrameSet *this_fset, int iframe, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the astRemoveFrame public
+*     method inherited from the Plot class).
+
+*  Description:
+*     This function removes a Frame from a Plot3D. All other Frames
+*     in the Plot3D have their indices re-numbered from one (if
+*     necessary), but are otherwise unchanged.
+*
+*     If the index of the original base Frame is changed, the index value
+*     stored in the Plot3D is updated. If the base Frame itself is
+*     removed, an error is reported.
+
+*  Parameters:
+*     this_fset
+*        Pointer to the FrameSet component of the Plot3D.
+*     iframe
+*        The index within the Plot3D of the Frame to be removed.
+*        This value should lie in the range from 1 to the number of
+*        Frames in the Plot3D (as given by its Nframe attribute).
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPlot3D *this;          /* Pointer to Plot3D structure */
+   int ifrm;                 /* Validated frame index */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Plot3D structure. */
+   this = (AstPlot3D *) this_fset;
+
+/* Validate the frame index. This translated AST__BASE and AST__CURRENT
+   into actual Frame indices. */
+   ifrm = astValidateFrameIndex( this_fset, iframe, "astRemoveFrame" );
+
+/* Report an error if an attempt is made to delete the Frame that defines
+   the mapping onto the screen (the original base Frame in the FrameSet
+   supplied when the Plot3D was constructed). */
+   if( ifrm == this->pix_frame ){
+      astError( AST__PXFRRM, "astRemoveFrame(%s): Cannot delete Frame "
+                "number %d from the supplied %s since it is the Frame "
+                "that defines the mapping onto the graphics plane.", status,
+                 astGetClass( this ), iframe, astGetClass( this ) );
+
+/* Otherwise, invoke the parent astRemoveFrame method to remove the Frame. */
+   } else {
+      (*parent_removeframe)( this_fset, iframe, status );
+
+/* If the index of the removed Frame is smaller than the original base Frame
+   index, then decrement the original base Frame index so that the same Frame
+   will be used in future. */
+      if( astOK ){
+         if( ifrm < this->pix_frame ) (this->pix_frame)--;
+      }
+   }
+}
+
+static int RootCornerInt( const char *rootcorner, int *status ){
+/*
+*  Name:
+*     RootCornerInt
+
+*  Purpose:
+*     Convert a RootCorner string to an integer.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int RootCornerInt( const char *rootcorner, int *status )
+
+*  Class Membership:
+*     Plot3D method.
+
+*  Description:
+*     This function converts a RootCorner string to an integer.
+
+*  Parameters:
+*     rootcorner
+*        The string value to convert. Should be 3 characters long
+*        and contain nothing but "U" or "L" (upper or lower case).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The integer value that is is the protected equivalent of the
+*     supplied string. A negative value is returned if an error has
+*     already occurred, of the supplied string is not legal.
+
+*/
+
+/* Local Variables: */
+   int result;
+
+/* Initialise */
+   result = -1;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Compare thr supplied value with every legal value. */
+   if( astChrMatch( rootcorner, "LLL" ) ) {
+      result = LLL;
+
+   } else if( astChrMatch( rootcorner, "ULL" ) ) {
+      result = ULL;
+
+   } else if( astChrMatch( rootcorner, "LUL" ) ) {
+      result = LUL;
+
+   } else if( astChrMatch( rootcorner, "UUL" ) ) {
+      result = UUL;
+
+   } else if( astChrMatch( rootcorner, "LLU" ) ) {
+      result = LLU;
+
+   } else if( astChrMatch( rootcorner, "ULU" ) ) {
+      result = ULU;
+
+   } else if( astChrMatch( rootcorner, "LUU" ) ) {
+      result = LUU;
+
+   } else if( astChrMatch( rootcorner, "UUU" ) ) {
+      result = UUU;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *RootCornerString( int rootcorner, int *status ){
+/*
+*  Name:
+*     RootCornerString
+
+*  Purpose:
+*     Convert a RootCorner integer to a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     const char *RootCornerString( int rootcorner, int *status )
+
+*  Class Membership:
+*     Plot3D method.
+
+*  Description:
+*     This function converts a RootCorner integer to a string.
+
+*  Parameters:
+*     rootcorner
+*        The integer value to convert. Should be in the range 0 to 7.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a static string that is the public equivalent of the
+*     supplied integer. A NULL pointer is returned if an error has
+*     already occurred, of the supplied integer is not legal.
+
+*/
+
+/* Local Variables: */
+   char *result;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Compare thr supplied value with every legal value. */
+   if( rootcorner == LLL ) {
+      result = "LLL";
+
+   } else if( rootcorner == ULL ) {
+      result = "ULL";
+
+   } else if( rootcorner == LUL ) {
+      result = "LUL";
+
+   } else if( rootcorner == UUL ) {
+      result = "UUL";
+
+   } else if( rootcorner == LLU ) {
+      result = "LLU";
+
+   } else if( rootcorner == ULU ) {
+      result = "ULU";
+
+   } else if( rootcorner == LUU ) {
+      result = "LUU";
+
+   } else if( rootcorner == UUU ) {
+      result = "UUU";
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void Set3DGrf( AstPlot3D *this, AstPlot *plot, int plane, int *status ){
+/*
+*  Name:
+*     Set3DGrf
+
+*  Purpose:
+*     Associate GRF functions with a Plot.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void Set3DGrf( AstPlot3D *this, AstPlot *plot, int plane, int *status )
+
+*  Class Membership:
+*     Plot3D method.
+
+*  Description:
+*     This function registers grf functions defined in this class with
+*     the supplied Plot, so that, whenever the Plot draws anything, the
+*     plotting request is caught by this class and converted into an
+*     appropriate grf3D call.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     plot
+*        Pointer to the Plot.
+*     plane
+*        An integer identifier for the plane within 3D GRAPHICS
+*        coordinates upon which the supplied Plot should draw.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *grfcon;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Register the plotting functions defined in this class, so that the
+   plot will call these functions to do its 2D plotting. */
+   astGrfSet( plot, "Attr", (AstGrfFun) Plot3DAttr );
+   astGrfSet( plot, "Cap", (AstGrfFun) Plot3DCap );
+   astGrfSet( plot, "Flush", (AstGrfFun) Plot3DFlush );
+   astGrfSet( plot, "Line", (AstGrfFun) Plot3DLine );
+   astGrfSet( plot, "Mark", (AstGrfFun) Plot3DMark );
+   astGrfSet( plot, "Qch", (AstGrfFun) Plot3DQch );
+   astGrfSet( plot, "Scales", (AstGrfFun) Plot3DScales );
+   astGrfSet( plot, "Text", (AstGrfFun) Plot3DText );
+   astGrfSet( plot, "TxExt", (AstGrfFun) Plot3DTxExt );
+
+/* Ensure that the Plot uses the grf interface registered using
+   astGrfSet. */
+   astSetGrf( plot, 1 );
+
+/* When the above functions are called, they need to know which plane
+   they are drawing on. So we put this information into the GrfContext
+   KeyMap stored in the Plot. This KeyMap will be passed to the above
+   drawing functions when they are called from within the Plot class. */
+   grfcon = astGetGrfContext( plot );
+   astMapPut0I( grfcon, "Plane", plane, "The 2D plane being drawn on" );
+   if( plane == XY ) {
+      astMapPut0D( grfcon, "Gcon", this->gbox[2], "Constant Z value" );
+   } else if( plane == XZ ) {
+      astMapPut0D( grfcon, "Gcon", this->gbox[1], "Constant Y value" );
+   } else {
+      astMapPut0D( grfcon, "Gcon", this->gbox[0], "Constant X value" );
+   }
+   astMapPut0I( grfcon, "RootCorner", astGetRootCorner(this), "The labelled corner" );
+   grfcon = astAnnul( grfcon );
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Plot3D.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the astSetAttrib protected
+*     method inherited from the Plot class).
+
+*  Description:
+*     This function assigns an attribute value for a Plot3D, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPlot *plot;                /* Pointer to specific Plot */
+   AstPlot3D *this;              /* Pointer to the Plot3D structure */
+   char pat[30];                 /* Regular expression pattern */
+   char spec[10];                /* Plane specification */
+   const char *null = "";        /* Pointer to null string */
+   const char *psetting;         /* Pointer to plot-specific attrib setting */
+   double dval;                  /* Floating point attribute value */
+   int axis;                     /* Axis index */
+   int ival;                     /* Int attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Plot3D structure. */
+   this = (AstPlot3D *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* Norm(axis). */
+/* ----------- */
+   if ( nc = 0,
+        ( 2 == astSscanf( setting, "norm(%d)= %lg %n",
+                          &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetNorm( this, axis - 1, dval );
+
+/* RootCorner. */
+/* ----------- */
+   } else if( nc = 0,
+        ( 0 == astSscanf( setting, "rootcorner=%n%*[^\n]%n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      ival = RootCornerInt( setting + ival, status );
+      if( astOK && ival < 0 ) {
+         astError( AST__ATTIN, "astSetAttrib(Plot3D): Unusable value \"%s\" "
+                   "given for attribute RootCorner.", status, setting + ival );
+      } else {
+         astSetRootCorner( this, ival );
+      }
+
+/* ..._XY etc */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "%*[a-z]_%[xyz]%n", spec, &nc ) ) ) {
+
+      if( !strcmp( spec, "xy" ) || !strcmp( spec, "yx" ) ) {
+         plot = this->plotxy;
+      } else if( !strcmp( spec, "xz" ) || !strcmp( spec, "zx" ) ) {
+         plot = this->plotyz;
+      } else if( !strcmp( spec, "yz" ) || !strcmp( spec, "zy" ) ) {
+         plot = this->plotxz;
+      } else {
+         plot = NULL;
+      }
+
+      if( plot ) {
+         sprintf( pat, ".*(_%s).*", spec );
+         psetting = astChrSub( setting, pat, &null, 1 );
+         astSetAttrib( plot, psetting );
+         psetting = astFree( (void *) psetting );
+
+      } else {
+         (*parent_setattrib)( this_object, setting, status );
+      }
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+static void SetCurrent( AstFrameSet *this_frameset, int iframe, int *status ) {
+/*
+*  Name:
+*     SetCurrent
+
+*  Purpose:
+*     Set a value for the Current attribute of a Plot3D.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int astSetCurrent( AstFrameSet *this, int iframe, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the public astSetCurrent method
+*     inherited from the FrameSet class).
+
+*  Description:
+*     This function sets a value for the Current attribute of a
+*     Plot3D. This attribute is an index that identifies the current
+*     Frame for the Plot3D.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     iframe
+*        Value to be set for the Current attribute.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Invoke the parent astSetCurrent method. */
+   (*parent_setcurrent)( this_frameset, iframe, status );
+
+/* Update the three 2D Plots stored in the Plot3D structure so that they
+   reflect this modified FrameSet. */
+   UpdatePlots( (AstPlot3D *) this_frameset, status );
+}
+
+static void SetPlotAttr( AstPlot *plot, int plane, int label[ 2 ], int *status ){
+/*
+*  Name:
+*     SetPlotAttr
+
+*  Purpose:
+*     Set the attributes ofr one of the encapsulated Plots.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void SetPlotAttr( AstPlot *plot, int plane, int label[ 2 ], int *status )
+
+*  Class Membership:
+*     Plot3D method.
+
+*  Description:
+*     This function sets the attributes of one of the encapsulated Plots.
+
+*  Parameters:
+*     plot
+*        Pointer to the Plot to modify.
+*     plane
+*        The 3D plane spanned by the 2D plot.
+*     label
+*        Array indicating if each WCS axis should be labelled or not.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int axis;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Ensure that the Plot uses the grf interface registered using
+   astGrfSet. */
+   astSetGrf( plot, 1 );
+
+/* Ensure that no title is drawn. */
+   astSetDrawTitle( plot, 0 );
+
+/* For each axis, ensure that no axis labels or ticks are produced unless
+   the axis is indicated as a labelled axis in the supplied array. */
+   for( axis = 0; axis < 2; axis++ ) {
+      if( !label[ axis ] ) {
+         astSetLabelUnits( plot, axis, 0 );
+         astSetNumLab( plot, axis, 0 );
+         astSetTextLab( plot, axis, 0 );
+      }
+   }
+}
+
+static void SetRootCorner( AstPlot3D *this, int rootcorner, int *status ){
+/*
+*+
+*  Name:
+*     astSetRootCorner
+
+*  Purpose:
+*     Set a new value for the RootCorner attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void astSetRootCorner( AstPlot3D *this, int rootcorner )
+
+*  Class Membership:
+*     Plot method.
+
+*  Description:
+*     This function sets a new value for the RootCorner attribute.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot3D.
+*     rootcorner
+*        The new RootCorner value.
+
+*-
+*/
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+/* Report an error if the new value is out of bounds. */
+   if( rootcorner < 0 || rootcorner > 7 ){
+      astError( AST__ATTIN, "astSetRootCorner(Plot3D): Invalid value %d "
+                "supplied for RootCorner attribute", status,rootcorner);
+
+/* If the new corner is OK, mirror any axes of the encapsulated Plots
+   that need mirroring (this is done to ensure that Plots look right when
+   viewed from the outside of the graphics cube), and modify the Edge
+   attributes in the encapsulated Plots to ensure the labels appear on the
+   requested edges of the 3D graphics cube. . */
+   } else {
+      ChangeRootCorner( this, astGetRootCorner(this), rootcorner, status );
+
+/* Store the new value. */
+      this->rootcorner = rootcorner;
+   }
+}
+
+static void SetTickValues( AstPlot *this, int axis, int nmajor, double *major,
+                           int nminor, double *minor, int *status ){
+/*
+*  Name:
+*     SetTickValues
+
+*  Purpose:
+*     Store the tick mark values to use for a given Plot axis.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void SetTickValues( AstPlot *this, int axis, int nmajor,
+*                         double *major, int nminor, double *minor, int *status )
+
+*  Class Membership:
+*     Plot method (overrides the astSetTickValues method inherited form
+*     the Plot class)
+
+*  Description:
+*     This function stores a set of tick mark values that should be used by
+*     subsequent calls to astGrid.
+
+*  Parameters:
+*     this
+*        Pointer to a Plot.
+*     axis
+*        The zero-based index of the axis for which tick marks values
+*        have been supplied.
+*     nmajor
+*        The number of major tick mark values. If zero is supplied then
+*        the other parameters are ignored, and subsequent calls to
+*        astGrid will itself determine the tick values to be used.
+*     major
+*        Pointer to an array holding "nmajor" values for axis "axis" in
+*        the current Frame of the suppled Plot. Major tick marks will be
+*        drawn at these values.
+*     nminor
+*        The number of minor tick mark values.
+*     minor
+*        Pointer to an array holding "nminor" values for axis "axis" in
+*        the current Frame of the suppled Plot. Minor tick marks will be
+*        drawn at these values.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global status. */
+   if( !astOK ) return;
+
+   astError( AST__INTER, "astSetTickValues(%s): The astSetTickValues "
+             "method cannot be used with a %s (programming error).", status,
+             astGetClass( this ), astGetClass( this ) );
+}
+
+static void SplitFrameSet( AstFrameSet *fset,
+                           AstFrameSet **fsetxy, int labelxy[2], int wcsxy[2],
+                           AstFrameSet **fsetxz, int labelxz[2], int wcsxz[2],
+                           AstFrameSet **fsetyz, int labelyz[2], int wcsyz[2],
+                           int *baseplane, int *status ){
+/*
+*  Name:
+*     SplitFrameSet
+
+*  Purpose:
+*     Split a 3D FrameSet into three 2D FrameSets.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void SplitFrameSet( AstFrameSet *fset,
+*                         AstFrameSet **fsetxy, int labelxy[2], int wcsxy[2],
+*                         AstFrameSet **fsetxz, int labelxz[2], int wcsxz[2],
+*                         AstFrameSet **fsetyz, int labelyz[2], int wcsyz[2],
+*                         int *baseplane, int *status )
+
+*  Class Membership:
+*     Plot3D member function
+
+*  Description:
+*     This function returns 3 FrameSets, each of which has 2-dimensional
+*     base and current Frames. Each of the 2D base Frames span a single
+*     plane in the 3D base Frame of the supplied FrameSet. Likewise, each
+*     of the 2D current Frames span a single plane in the 3D current Frame
+*     of the supplied FrameSet. An error is reported if there is no
+*     one-to-one association between the three planes in the supplied
+*     current Frame and the 3 planes in the supplied base Frame.
+
+*  Parameters:
+*     fset
+*        Pointer to a FrameSet that has a 3D base Frame and a 3D current
+*        Frame.
+*     fsetxy
+*        Pointer to a location at which to return a pointer to a new FrameSet
+*        that has a 2D base Frame and a 2D current. The base Frame spans
+*        axes 1 and 2 of the 3D base Frame in "fset".
+*     labelxy
+*        Returned holding flags indicating if the two axes of the current
+*        Frame in *fsetxy should be labelled or not.
+*     wcsxy
+*        Returned holding the zero based axis index within the current Frame
+*        of the supplied FrameSet that corresponds to each of the two
+*        current Frame axes in the "fsetxy" FrameSet.
+*     fsetxz
+*        Pointer to a location at which to return a pointer to a new FrameSet
+*        that has a 2D base Frame and a 2D current. The base Frame spans
+*        axes 1 and 3 of the 3D base Frame in "fset".
+*     labelxz
+*        Returned holding flags indicating if the two axes of the current
+*        Frame in *fsetxz should be labelled or not.
+*     wcsxz
+*        Returned holding the zero based axis index within the current Frame
+*        of the supplied FrameSet that corresponds to each of the two
+*        current Frame axes in the "fsetxz" FrameSet.
+*     fsetyz
+*        Pointer to a location at which to return a pointer to a new FrameSet
+*        that has a 2D base Frame and a 2D current. The base Frame spans
+*        axes 2 and 3 of the 3D base Frame in "fset".
+*     labelyz
+*        Returned holding flags indicating if the two axes of the current
+*        Frame in *fsetyz should be labelled or not.
+*     wcsyz
+*        Returned holding the zero based axis index within the current Frame
+*        of the supplied FrameSet that corresponds to each of the two
+*        current Frame axes in the "fsetxy" FrameSet.
+*     baseplane
+*        Index of the plane that is spanned by two connected 3D axes.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Null pointers will be returned for the three new FrameSets if this
+*     function is invoked with the global status set, or if it should fail
+*     for any reason.
+*     - Each returned FrameSet has 2 Frames: Frame 1 is the base Frame
+*     and is spanned by 2 of the 3 axes in the base Frame of the supplied
+*     FrameSet; Frame 2 is the current Frame and is spanned by 2 of the 3
+*     axes in the current Frame of the supplied FrameSet. Any future changes
+*     to this function that alter this structure should reflected in
+*     equivalent changes to functions CreatePlots and UpdatePlots.
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm2d;
+   AstFrame *bfrm;
+   AstFrame *cfrm1d;
+   AstFrame *cfrm2d;
+   AstFrame *cfrm;
+   AstFrame *dummy;
+   AstFrameSet *fset1;
+   AstFrameSet *fset2;
+   AstFrameSet *fset3;
+   AstMapping *map1d;
+   AstMapping *map2d;
+   AstMapping *map;
+   AstMapping *smap;
+   AstUnitMap *unit1d;
+   int *axout;
+   int *other_axout;
+   int axin2[ 2 ];
+   int axin[ 2 ];
+   int i;
+   int label1[ 2 ];
+   int label2[ 2 ];
+   int label3[ 2 ];
+   int other_axin;
+   int wcsax1[ 2 ];
+   int wcsax2[ 2 ];
+   int wcsax3[ 2 ];
+
+/* Initialise. */
+   *fsetxy = NULL;
+   *fsetxz = NULL;
+   *fsetyz = NULL;
+   *baseplane = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the simplified base -> current Mapping form the supplied FrameSet.
+   Also get the base and current Frames themselves. */
+   map = astGetMapping( fset, AST__BASE, AST__CURRENT );
+   smap = astSimplify( map );
+   map = astAnnul( map );
+   cfrm = astGetFrame( fset, AST__CURRENT );
+   bfrm = astGetFrame( fset, AST__BASE );
+
+/* Create a 1D UnitMap. */
+   unit1d = astUnitMap( 1, "", status );
+
+/* First try to identify a pair of base Frame axes that map onto a pair
+   of current Frame axes. This will be the case for instance if the 3D
+   FrameSet describes a spectral cube in which two of the axes are spanned
+   by a SkyFrame. We try all three possible pairs of axes in turn until a
+   pair is found succesfully. */
+   for( i = 0; i < 3  && ! *fsetxy; i++ ) {
+      axin[ 0 ] = ( i < 2 ) ? 0 : 1;
+      axin[ 1 ] = ( i == 0 ) ? 1 : 2;
+
+/* Try to get a Mapping that connects the inputs given by axin to a
+   distinct subset of outputs. */
+      axout = astMapSplit( smap, 2, axin, &map2d );
+
+/* If succesful, check that the 2 inputs correspond to exactly 2 outputs. */
+      if( map2d ){
+         if( astGetNout( map2d ) == 2 ) {
+
+/* Get the index of the other input axis (the one not included in the pair). */
+            other_axin = 3 - axin[ 0 ] - axin[ 1 ];
+
+/* Try to get a Mapping that connects this remaining input to a distinct
+   subset of outputs. */
+            other_axout = astMapSplit( smap, 1, &other_axin, &map1d );
+
+/* If succesful, check that the 1 input correspond to exactly 1 output. */
+            if( map1d ){
+               if( astGetNout( map1d ) == 1 ) {
+
+/* Pick the two axes from the 3D base and current Frames that correspond to
+   the paired axes found above. */
+                  bfrm2d = astPickAxes( bfrm, 2, axin, NULL );
+                  cfrm2d = astPickAxes( cfrm, 2, axout, NULL );
+
+/* Pick the axis from the 3D current Frame that correspond to
+   the other axis. */
+                  cfrm1d = astPickAxes( cfrm, 1, other_axout, NULL );
+
+/* Construct a FrameSet using these 2D Frames and Mapping. */
+                  fset1 = astFrameSet( bfrm2d, "", status );
+                  astAddFrame( fset1, AST__BASE, map2d, cfrm2d );
+
+                  bfrm2d = astAnnul( bfrm2d );
+                  cfrm2d = astAnnul( cfrm2d );
+                  map2d = astAnnul( map2d );
+
+/* Indicate that both axes in the current Frame of this FrameSet should
+   be labelled. */
+                  label1[ 0 ] = 1;
+                  label1[ 1 ] = 1;
+
+/* Store the index of the axis within the supplied current Frame that
+   corresponds to each axis in the current Frame of the FrameSet created
+   above. */
+                  wcsax1[ 0 ] = axout[ 0 ];
+                  wcsax1[ 1 ] = axout[ 1 ];
+
+/* Pick the two axes from the 3D base Frame that correspond to the first of
+   the paired axes, and the unpaired axis. Order them so that we get the
+   2 axes in the order xy, xz or yz. Also, store the index of the axis
+   within the supplied  current Frame that corresponds to each axis in
+   the current Frame of the FrameSet created below. */
+                  if( i < 2 ) {
+                     axin2[ 0 ] = axin[ 0 ];
+                     axin2[ 1 ] = other_axin;
+                     wcsax2[ 0 ] = axout[ 0 ];
+                     wcsax2[ 1 ] = other_axout[ 0 ];
+                  } else {
+                     axin2[ 0 ] = other_axin;
+                     axin2[ 1 ] = axin[ 0 ];
+                     wcsax2[ 0 ] = other_axout[ 0 ];
+                     wcsax2[ 1 ] = axout[ 0 ];
+                  }
+                  bfrm2d = astPickAxes( bfrm, 2, axin2, NULL );
+
+/* The corresponding current Frame in the new FrameSet will be a compound
+   2D Frame holding the other current Frame axis and a copy of the input
+   base Frame axis. Combine them so that we get the 2 axes in the order
+   xy, xz or yz. */
+                  dummy = astPickAxes( bfrm, 1, axin, NULL );
+                  if( i < 2 ) {
+                     cfrm2d = (AstFrame *) astCmpFrame( dummy, cfrm1d, "", status );
+                  } else {
+                     cfrm2d = (AstFrame *) astCmpFrame( cfrm1d, dummy, "", status );
+                  }
+                  dummy = astAnnul( dummy );
+
+/* The Mapping that joins this 2D base Frame to the 2D current Frame uses
+   the above 1D mapping for the other axis, and a UnitMap for the copied
+   base Frame axis. */
+                  if( i < 2 ) {
+                     map2d = (AstMapping *) astCmpMap( unit1d, map1d, 0, "", status );
+                  } else {
+                     map2d = (AstMapping *) astCmpMap( map1d, unit1d, 0, "", status );
+                  }
+
+/* Construct a FrameSet using these 2D Frames and Mapping. */
+                  fset2 = astFrameSet( bfrm2d, "", status );
+                  astAddFrame( fset2, AST__BASE, map2d, cfrm2d );
+
+                  bfrm2d = astAnnul( bfrm2d );
+                  cfrm2d = astAnnul( cfrm2d );
+                  map2d = astAnnul( map2d );
+
+/* Indicate that only one of the axes in the current Frame of this FrameSet
+   should be labelled. */
+                  if( i < 2 ) {
+                     label2[ 0 ] = 0;
+                     label2[ 1 ] = 1;
+                  } else {
+                     label2[ 0 ] = 1;
+                     label2[ 1 ] = 0;
+                  }
+
+/* Pick the two axes from the 3D base Frame that correspond to the second
+   of the paired axes, and the unpaired axis. Order them so that we get the
+   2 axes in the order xy, xz or yz. Also, store the index of the axis
+   within the supplied  current Frame that corresponds to each axis in
+   the current Frame of the FrameSet created below. */
+                  if( i == 0 ) {
+                     axin2[ 0 ] = axin[ 1 ];
+                     axin2[ 1 ] = other_axin;
+                     wcsax3[ 0 ] = axout[ 1 ];
+                     wcsax3[ 1 ] = other_axout[ 0 ];
+                  } else {
+                     axin2[ 0 ] = other_axin;
+                     axin2[ 1 ] = axin[ 1 ];
+                     wcsax3[ 0 ] = other_axout[ 0 ];
+                     wcsax3[ 1 ] = axout[ 1 ];
+                  }
+                  bfrm2d = astPickAxes( bfrm, 2, axin2, NULL );
+
+/* The corresponding current Frame in the new FrameSet will be a compound
+   2D Frame holding the other current Frame axis and a copy of the input
+   base Frame axis. Combine them so that we get the 2 axes in the order
+   xy, xz or yz. */
+                  dummy = astPickAxes( bfrm, 1, axin + 1, NULL );
+                  if( i == 0 ) {
+                     cfrm2d = (AstFrame *) astCmpFrame( dummy, cfrm1d, "", status );
+                  } else {
+                     cfrm2d = (AstFrame *) astCmpFrame( cfrm1d, dummy, "", status );
+                  }
+                  dummy = astAnnul( dummy );
+
+/* The Mapping that joins this 2D base Frame to the 2D current Frame uses
+   the above 1D mapping for the other axis, and a UnitMap for the copied
+   base Frame axis. */
+                  if( i == 0 ) {
+                     map2d = (AstMapping *) astCmpMap( unit1d, map1d, 0, "", status );
+                  } else {
+                     map2d = (AstMapping *) astCmpMap( map1d, unit1d, 0, "", status );
+                  }
+
+/* Construct a FrameSet using these 2D Frames and Mapping. */
+                  fset3 = astFrameSet( bfrm2d, "", status );
+                  astAddFrame( fset3, AST__BASE, map2d, cfrm2d );
+
+                  bfrm2d = astAnnul( bfrm2d );
+                  cfrm2d = astAnnul( cfrm2d );
+                  map2d = astAnnul( map2d );
+
+/* Indicate that neither axis in the current Frame of this FrameSet
+   should be labelled. */
+                  label3[ 0 ] = 0;
+                  label3[ 1 ] = 0;
+
+/* Store each FrameSet in the correct returned pointer. */
+                  if( i == 0 ) {
+                     *baseplane = XY;
+                     *fsetxy = fset1;
+                     *fsetxz = fset2;
+                     *fsetyz = fset3;
+
+                     labelxy[ 0 ] = label1[ 0 ];
+                     labelxy[ 1 ] = label1[ 1 ];
+                     labelxz[ 0 ] = label2[ 0 ];
+                     labelxz[ 1 ] = label2[ 1 ];
+                     labelyz[ 0 ] = label3[ 0 ];
+                     labelyz[ 1 ] = label3[ 1 ];
+
+                     wcsxy[ 0 ] = wcsax1[ 0 ];
+                     wcsxy[ 1 ] = wcsax1[ 1 ];
+                     wcsxz[ 0 ] = wcsax2[ 0 ];
+                     wcsxz[ 1 ] = wcsax2[ 1 ];
+                     wcsyz[ 0 ] = wcsax3[ 0 ];
+                     wcsyz[ 1 ] = wcsax3[ 1 ];
+
+                  } else if( i == 1 ) {
+                     *baseplane = XZ;
+                     *fsetxy = fset2;
+                     *fsetxz = fset1;
+                     *fsetyz = fset3;
+
+                     labelxy[ 0 ] = label2[ 0 ];
+                     labelxy[ 1 ] = label2[ 1 ];
+                     labelxz[ 0 ] = label1[ 0 ];
+                     labelxz[ 1 ] = label1[ 1 ];
+                     labelyz[ 0 ] = label3[ 0 ];
+                     labelyz[ 1 ] = label3[ 1 ];
+
+                     wcsxy[ 0 ] = wcsax2[ 0 ];
+                     wcsxy[ 1 ] = wcsax2[ 1 ];
+                     wcsxz[ 0 ] = wcsax1[ 0 ];
+                     wcsxz[ 1 ] = wcsax1[ 1 ];
+                     wcsyz[ 0 ] = wcsax3[ 0 ];
+                     wcsyz[ 1 ] = wcsax3[ 1 ];
+
+                  } else {
+                     *baseplane = YZ;
+                     *fsetxy = fset2;
+                     *fsetxz = fset3;
+                     *fsetyz = fset1;
+
+                     labelxy[ 0 ] = label2[ 0 ];
+                     labelxy[ 1 ] = label2[ 1 ];
+                     labelxz[ 0 ] = label3[ 0 ];
+                     labelxz[ 1 ] = label3[ 1 ];
+                     labelyz[ 0 ] = label1[ 0 ];
+                     labelyz[ 1 ] = label1[ 1 ];
+
+                     wcsxy[ 0 ] = wcsax2[ 0 ];
+                     wcsxy[ 1 ] = wcsax2[ 1 ];
+                     wcsxz[ 0 ] = wcsax3[ 0 ];
+                     wcsxz[ 1 ] = wcsax3[ 1 ];
+                     wcsyz[ 0 ] = wcsax1[ 0 ];
+                     wcsyz[ 1 ] = wcsax1[ 1 ];
+
+                  }
+
+/*  Free resources */
+                  cfrm1d = astAnnul( cfrm1d );
+               }
+
+/*  Free resources */
+               map1d = astAnnul( map1d );
+               other_axout = astFree( other_axout );
+            }
+         }
+
+/*  Free resources */
+         if( map2d ) map2d = astAnnul( map2d );
+         axout = astFree( axout );
+
+/* Leave the loop if we now have the required FrameSets, or an error has
+   occurred. */
+         if( *fsetxy || !astOK ) break;
+
+      }
+   }
+
+/* Free resources */
+   cfrm = astAnnul( cfrm );
+   bfrm = astAnnul( bfrm );
+   smap = astAnnul( smap );
+   unit1d = astAnnul( unit1d );
+
+/* Return null pointers if an error has occurred. */
+   if( !astOK ) {
+      *fsetxy = astAnnul( *fsetxy );
+      *fsetxz = astAnnul( *fsetxz );
+      *fsetyz = astAnnul( *fsetyz );
+
+/* Report an error if the supplied FrameSet could not be split into 3
+   independent 2D planes. */
+   } if( ! *fsetxy ) {
+      astError( AST__3DFSET, "astInitPlot3D(Plot3D): Supplied %s contains "
+                             "no independent axes.", status, astGetClass( fset ) );
+   }
+}
+
+static void StoreAxisInfo( AstPlot3D *this, int labelxy[2], int wcsxy[2],
+                           int labelxz[2], int wcsxz[2], int labelyz[2],
+                           int wcsyz[2], int *status ) {
+/*
+*  Name:
+*     StoreAxisInfo
+
+*  Purpose:
+*     Store information connecting 3D axis with associuated 2D Plot axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void StoreAxisInfo( AstPlot3D *this, int labelxy[2], int wcsxy[2],
+*                         int labelxz[2], int wcsxz[2], int labelyz[2],
+*                         int wcsyz[2], int *status )
+
+*  Class Membership:
+*     Plot3D method.
+
+*  Description:
+*     This function stores information inside the Plot3D that allows each
+*     3D axis to be associated with the 2 Plots that share the 3D axis,
+*     and with the axis index within each of these two Plots.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     labelxy
+*        Flags indicating if the two axes of the current Frame in the XY
+*        Plot should be labelled or not.
+*     wcsxy
+*        The zero based axis index within the 3D current Frame that
+*        corresponds to each of the two current Frame axes in the XY Plot.
+*        A value of -1 should be used for 2D axes that do not correspond
+*        to any 3D axis.
+*     labelxz
+*        Flags indicating if the two axes of the current Frame in the XZ
+*        Plot should be labelled or not.
+*     wcsxz
+*        The zero based axis index within the 3D current Frame that
+*        corresponds to each of the two current Frame axes in the XZ Plot.
+*        A value of -1 should be used for 2D axes that do not correspond
+*        to any 3D axis.
+*     labelyz
+*        Flags indicating if the two axes of the current Frame in the YZ
+*        Plot should be labelled or not.
+*     wcsyz
+*        The zero based axis index within the 3D current Frame that
+*        corresponds to each of the two current Frame axes in the YZ Plot.
+*        A value of -1 should be used for 2D axes that do not correspond
+*        to any 3D axis.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int axis2d;
+   int axis3d;
+   int gotfirst;
+   int gotsecond;
+   int temp;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Store information that allows each 3D WCS axis to be associated with
+   a pair of Plots. Also store the WCS axis within each Plot that
+   corresponds to the 3D WCS axis. Do each 3D WCS axis in turn. */
+   for( axis3d = 0; axis3d < 3; axis3d++ ) {
+
+/* Indicate we have not yet found either of the two Plots that share the
+   current 3D axis. */
+      gotfirst = 0;
+      gotsecond = 0;
+
+/* Check each of the 2 axes in the plot spanning the XY face of the 3D
+   graphics cube. Break early if we have found both Plots for the current
+   3D WCS axis. */
+      for( axis2d = 0; axis2d < 2 && !gotsecond; axis2d++ ) {
+
+/* See if this 2D axis corresponds to the required 3D axis. If so, store
+   the Plot identifier and the 2D axis index. */
+         if( wcsxy[ axis2d ] == axis3d ) {
+            if( gotfirst ) {
+               this->axis_plot2[ axis3d ] = XY;
+               this->axis_index2[ axis3d ] = axis2d;
+               gotsecond = 1;
+            } else {
+               this->axis_plot1[ axis3d ] = XY;
+               this->axis_index1[ axis3d ] = axis2d;
+               gotfirst = 1;
+            }
+         }
+      }
+
+/* Check the plot spanning the XZ face in the same way. */
+      for( axis2d = 0; axis2d < 2 && !gotsecond; axis2d++ ) {
+         if( wcsxz[ axis2d ] == axis3d ) {
+            if( gotfirst ) {
+               this->axis_plot2[ axis3d ] = XZ;
+               this->axis_index2[ axis3d ] = axis2d;
+               gotsecond = 1;
+            } else {
+               this->axis_plot1[ axis3d ] = XZ;
+               this->axis_index1[ axis3d ] = axis2d;
+               gotfirst = 1;
+            }
+         }
+      }
+
+/* Check the plot spanning the YZ face in the same way. */
+      for( axis2d = 0; axis2d < 2 && !gotsecond; axis2d++ ) {
+         if( wcsyz[ axis2d ] == axis3d ) {
+            if( gotfirst ) {
+               this->axis_plot2[ axis3d ] = YZ;
+               this->axis_index2[ axis3d ] = axis2d;
+               gotsecond = 1;
+            } else {
+               this->axis_plot1[ axis3d ] = YZ;
+               this->axis_index1[ axis3d ] = axis2d;
+               gotfirst = 1;
+            }
+         }
+      }
+   }
+
+/* Ensure that the first Plot within each pair is the one that is used to
+   generate labels for the 3D axis. */
+   for( axis2d = 0; axis2d < 2; axis2d++ ) {
+      if( labelxy[ axis2d ] ) {
+         axis3d = wcsxy[ axis2d ];
+         if( this->axis_plot1[ axis3d ] != XY ){
+            temp = this->axis_plot1[ axis3d ];
+            this->axis_plot1[ axis3d ] = this->axis_plot2[ axis3d ];
+            this->axis_plot2[ axis3d ] = temp;
+
+            temp = this->axis_index1[ axis3d ];
+            this->axis_index1[ axis3d ] = this->axis_index2[ axis3d ];
+            this->axis_index1[ axis3d ] = temp;
+         }
+      }
+   }
+
+   for( axis2d = 0; axis2d < 2; axis2d++ ) {
+      if( labelxz[ axis2d ] ) {
+         axis3d = wcsxz[ axis2d ];
+         if( this->axis_plot1[ axis3d ] != XZ ){
+            temp = this->axis_plot1[ axis3d ];
+            this->axis_plot1[ axis3d ] = this->axis_plot2[ axis3d ];
+            this->axis_plot2[ axis3d ] = temp;
+
+            temp = this->axis_index1[ axis3d ];
+            this->axis_index1[ axis3d ] = this->axis_index2[ axis3d ];
+            this->axis_index1[ axis3d ] = temp;
+         }
+      }
+   }
+
+   for( axis2d = 0; axis2d < 2; axis2d++ ) {
+      if( labelyz[ axis2d ] ) {
+         axis3d = wcsyz[ axis2d ];
+         if( this->axis_plot1[ axis3d ] != YZ ){
+            temp = this->axis_plot1[ axis3d ];
+            this->axis_plot1[ axis3d ] = this->axis_plot2[ axis3d ];
+            this->axis_plot2[ axis3d ] = temp;
+
+            temp = this->axis_index1[ axis3d ];
+            this->axis_index1[ axis3d ] = this->axis_index2[ axis3d ];
+            this->axis_index1[ axis3d ] = temp;
+         }
+      }
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a Plot3D.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the astTestAttrib protected
+*     method inherited from the Plot class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a Plot3D's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPlot *plot;                /* Pointer to specific Plot */
+   AstPlot3D *this;              /* Pointer to the Plot3D structure */
+   char attname[50];             /* Plot attribute base name */
+   char patt[50];                /* Plot attribute full name */
+   char spec[10];                /* Plane specification */
+   int axis;                     /* Axis index */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* Number of character read */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Plot3D structure. */
+   this = (AstPlot3D *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Norm. */
+/* ----- */
+   if ( !strcmp( attrib, "norm" ) ) {
+      result = astTestNorm( this, 0 ) ||
+               astTestNorm( this, 1 ) ||
+               astTestNorm( this, 2 );
+
+/* Norm(axis). */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "norm(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      result = astTestNorm( this, axis -  1 );
+
+/* RootCorner. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "rootcorner" ) ) {
+      result = astTestRootCorner( this );
+
+
+/* ..._XY etc */
+/* ---------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( attrib, "%[a-z]_%[xyz]%n", attname, spec,
+                 &nc ) ) ) {
+
+      if( !strcmp( spec, "xy" ) || !strcmp( spec, "yx" ) ) {
+         plot = this->plotxy;
+      } else if( !strcmp( spec, "xz" ) || !strcmp( spec, "zx" ) ) {
+         plot = this->plotyz;
+      } else if( !strcmp( spec, "yz" ) || !strcmp( spec, "zy" ) ) {
+         plot = this->plotxz;
+      } else {
+         plot = NULL;
+      }
+
+      if( plot ) {
+         sprintf( patt, "%s%s", attname, attrib + nc );
+         result = astTestAttrib( plot, patt );
+
+      } else {
+         result = (*parent_testattrib)( this_object, attrib, status );
+      }
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static void Text( AstPlot *this_plot, const char *text, const double pos[],
+                  const float up[], const char *just, int *status ){
+/*
+*  Name:
+*     Text
+
+*  Purpose:
+*     Draw a text string for a Plot3D.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void Text( AstPlot *this, const char *text, const double pos[],
+*                const float up[], const char *just, int *status )
+
+*  Class Membership:
+*     Plot3D method (overrides the Text method inherited form the Plot
+*     class).
+
+*  Description:
+*     This function draws a string of text at a position specified in
+*     the physical coordinate system of a Plot3D. The physical position
+*     is transformed into graphical coordinates to determine where the
+*     text should appear within the plotting area.
+*
+*     The text is drawn on a 2D plane that has a normal vector given by the
+*     current value of the Plot3D's "Norm" attribute.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     text
+*        Pointer to a null-terminated character string containing the
+*        text to be drawn. Trailing white space is ignored.
+*     pos
+*        An array, with one element for each axis of the Plot3D, giving
+*        the physical coordinates of the point where the reference
+*        position of the text string is to be placed.
+*     up
+*        An array holding the components of a vector in the "up"
+*        direction of the text (in graphical coordinates). For
+*        example, to get horizontal text, the vector {0.0f,1.0f} should
+*        be supplied. "Up" is taken to be the projection of the positive
+*        Z axis onto the plane specified by the current value of the
+*     just
+*        Pointer to a null-terminated character string identifying the
+*        reference point for the text being drawn. The first character in
+*        this string identifies the reference position in the "up" direction
+*        and may be "B" (baseline), "C" (centre), "T" (top) or "M" (bottom).
+*        The second character identifies the side-to-side reference position
+*        and may be "L" (left), "C" (centre) or "R" (right ). The string is
+*        case-insensitive, and only the first two characters are significant.
+*
+*        For example, a value of "BL" means that the left end of the
+*        baseline of the original (un-rotated) text is to be drawn at the
+*        position given by "pos".
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMapping *mapping;    /* Pointer to graphics->physical mapping */
+   AstPlot3D *this;        /* Pointer to the Plot3D structure */
+   AstPointSet *pset1;     /* PointSet holding physical positions */
+   AstPointSet *pset2;     /* PointSet holding graphics positions */
+   char *ltext;            /* Local copy of "text" excluding trailing spaces */
+   char ljust[3];          /* Upper case copy of "just" */
+   const char *class;      /* Object class */
+   const char *method;     /* Current method */
+   const double **ptr1;    /* Pointer to physical positions */
+   double **ptr2;          /* Pointer to graphics positions */
+   float norm[ 3 ];        /* Single precision normal vector */
+   float ref[ 3 ];         /* Single precision ref position */
+   int axis;               /* Axis index */
+   int escs;               /* Original astEscapes value */
+   int naxes;              /* No. of axes in the base Frame */
+   int ncoord;             /* No. of axes in the current Frame */
+   int ulen;               /* Length of "text" excluding trailing spaces */
+
+/* Check the global error status. */
+   if ( !astOK || !text ) return;
+
+/* Store a pointer to the Plot3D structure. */
+   this = (AstPlot3D *) this_plot;
+
+/* Store the current method and class for inclusion in error messages
+   generated by lower level functions. */
+   method = "astText";
+   class = astClass( this );
+
+/* Check the base Frame of the Plot is 3-D. */
+   naxes = astGetNin( this );
+   if( naxes != 3 && astOK ){
+      astError( AST__NAXIN, "%s(%s): Number of axes (%d) in the base "
+                "Frame of the supplied %s is invalid - this number should "
+                "be 3.", status, method, class, naxes, class );
+   }
+
+/* Ensure AST functions do not included graphical escape sequences in any
+   returned text strings. This is because the Plot3D implementation of
+   this method cannot currently handle graphical escape sequences. */
+   escs = astEscapes( 0 );
+
+/* Establish the correct graphical attributes as defined by attributes
+   with the supplied Plot. */
+   astGrfAttrs( this, AST__TEXT_ID, 1, GRF__TEXT, method, class );
+
+/* Get the number of coordinates in the physical coordinate frame. */
+   ncoord = astGetNout( this );
+
+/* Create a PointSet to hold the supplied physical coordinates. */
+   pset1 = astPointSet( 1, ncoord, "", status );
+
+/* Allocate memory to hold pointers to the first value on each axis. */
+   ptr1 = (const double **) astMalloc( sizeof( const double * )*
+                                       (size_t)( ncoord ));
+
+/* Check the pointer can be used, then store pointers to the first value
+   on each axis. */
+   if( astOK ){
+      for( axis = 0; axis < ncoord; axis++ ){
+         ptr1[ axis ] = pos + axis;
+      }
+   }
+
+/* Store these pointers in the PointSet. */
+   astSetPoints( pset1, (double **) ptr1 );
+
+/* Transform the supplied data from the current frame (i.e. physical
+   coordinates) to the base frame (i.e. graphics coordinates) using
+   the inverse Mapping defined by the Plot. */
+   mapping = astGetMapping( this, AST__BASE, AST__CURRENT );
+   pset2 = astTransform( mapping, pset1, 0, NULL );
+   mapping = astAnnul( mapping );
+
+/* Get pointers to the graphics coordinates. */
+   ptr2 = astGetPoints( pset2 );
+
+/* Take a copy of the string excluding any trailing white space. */
+   ulen = astChrLen( text );
+   ltext = (char *) astStore( NULL, (void *) text, ulen + 1 );
+
+/* Check the pointers can be used. */
+   if( astOK ){
+
+/* Terminate the local copy of the text string. */
+      ltext[ ulen ] = 0;
+
+/* Produce an upper-case copy of the first two characters in "just". */
+      ljust[0] = (char) toupper( (int) just[0] );
+      ljust[1] = (char) toupper( (int) just[1] );
+      ljust[2] = 0;
+
+/* Convert the double precision values to single precision, checking for
+   bad positions. */
+      if( ptr2[0][0] != AST__BAD && ptr2[1][0] != AST__BAD &&
+          ptr2[2][0] != AST__BAD ){
+         ref[ 0 ] = (float) ptr2[0][0];
+         ref[ 1 ] = (float) ptr2[1][0];
+         ref[ 2 ] = (float) ptr2[2][0];
+
+/* If the nornmal vector has non-zero length, draw the text. */
+         norm[ 0 ] = (float) astGetNorm( this, 0 );
+         norm[ 1 ] = (float) astGetNorm( this, 1 );
+         norm[ 2 ] = (float) astGetNorm( this, 2 );
+
+/* Since we are about to call an external function which may not be
+   thread safe, prevent any other thread from executing the following code
+   until the current thread has finished executing it. */
+         LOCK_MUTEX2;
+
+         if( norm[ 0 ] != 0.0 || norm[ 1 ] != 0.0 || norm[ 2 ] != 0.0 ){
+            if( !astG3DText( ltext, ref, ljust, (float *) up, norm ) ) {
+               astError( AST__GRFER, "%s(%s): Graphics error in astG3DText. ", status,
+                         method, class );
+            }
+         } else if( astOK ) {
+            astError( AST__ATTIN, "%s(%s): The vector specified by the Norm "
+                      "attribute has zero length.", status, method, class );
+         }
+
+/* Allow the next thread to proceed. */
+         UNLOCK_MUTEX2;
+      }
+
+/* Free the local copy of the string. */
+      ltext = (char *) astFree( (void *) ltext );
+
+   }
+
+/* Annul the PointSets. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* Free the memory holding the pointers to the first value on each axis. */
+   ptr1 = (const double **) astFree( (void *) ptr1 );
+
+/* Re-establish the original graphical attributes. */
+   astGrfAttrs( this, AST__TEXT_ID, 0, GRF__TEXT, method, class );
+
+/* Restore the original value of the flag which says whether graphical
+   escape sequences should be incldued in any returned text strings. */
+   astEscapes( escs );
+
+/* Return */
+   return;
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Use a Plot to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the astTransform protected
+*     method inherited from the Plot class).
+
+*  Description:
+*     This function takes a Plot3D and a set of points encapsulated in a
+*     PointSet and transforms the points from graphics coordinates to
+*     physical coordinates (in the forward direction). No clipping or
+*     normalisation is applied.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate
+*        transformation should be applied while a zero value requests the
+*        inverse transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the Plot being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstMapping *map;              /* Pointer to the mapping */
+   AstPointSet *result;          /* Positions in output Frame */
+   AstPlot3D *this;              /* The Plot3D */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the Plot3D. */
+   this = (AstPlot3D *) this_mapping;
+
+/* Get the Mapping from the base to the current Frame. */
+   map = astGetMapping( this, AST__BASE, AST__CURRENT );
+
+/* Do the transformation. */
+   result = astTransform( map, in, forward, out );
+
+/* Annul the mapping. */
+   map = astAnnul( map );
+
+/* If an error has occurred, annul the result. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+
+}
+
+static void UpdatePlots( AstPlot3D *this, int *status ) {
+/*
+*  Name:
+*     UpdatePlots
+
+*  Purpose:
+*     Update the three 2D plots stored in the Plot3D.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     void UpdatePlots( AstPlot3D *this )
+
+*  Class Membership:
+*     Plot3D method.
+
+*  Description:
+*     This function splits the parent FrameSet up into 3 independent 2D
+*     FrameSets, each describing a 2D plane in the supplied 3D FrameSet.
+*     It then uses these 2D FrameSets to update the three Plots in the
+*     Plot3D, by removing the existing current Frame and adding in the
+*     new current Frame with the associated Mapping.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+
+*  Notes:
+*     - Each of the 3 plots has 3 Frames: Frame 1 is the base (GRAPHICS)
+*     Frame; Frame 2 is spanned by 2 of the 3 axes in the base Frame of
+*     the supplied FrameSet; Frame 3 is the current Frame and is spanned
+*     by 2 of the 3 axes in the current Frame of the supplied FrameSet.
+*     Any future changes to this function that alter this structure should
+*     reflected in equivalewnt changes to function CreatePlots.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstFrameSet *fsetxy;
+   AstFrameSet *fsetxz;
+   AstFrameSet *fsetyz;
+   AstFrameSet *fset;
+   AstMapping *map;
+   int baseplane;
+   int labelxy[ 2 ];
+   int labelxz[ 2 ];
+   int labelyz[ 2 ];
+   int wcsxy[ 2 ];
+   int wcsxz[ 2 ];
+   int wcsyz[ 2 ];
+   int rootcorner;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Return without action if the Plot3D does not contain the three 2D
+   Plots. This may be the case for instance if this function is called
+   before the Plot3D is fully constructed. */
+   if( this->plotxy && this->plotxz && this->plotyz ){
+
+/* We need a FrameSet that is equivalent to the one that was used to
+   construct the Plot3D (i.e. a FrameSet that does not have the GRAPHICS
+   Frame that was added by the Plot3D constructor). So take a copy of the
+   parent FrameSet, remove the GRAPHICS Frame (Frame 1) and set the base
+   Frame to be the Frame that was the base Frame when the Plot3D was
+   constructed. */
+      fset = (AstFrameSet *) astCast( this, dummy_frameset );
+      astSetBase( fset, this->pix_frame );
+      astRemoveFrame( fset, 1 );
+
+/* Split the supplied FrameSet up into 3 FrameSets, each with a 2D base
+   and current Frame. Each of these FrameSets describes one plane of
+   the 3D cube. */
+      SplitFrameSet( fset, &fsetxy, labelxy, wcsxy, &fsetxz, labelxz, wcsxz,
+                     &fsetyz, labelyz, wcsyz, &baseplane, status );
+
+/* First do the XY Plot. Extract the current Frame and base->current
+   Mapping from the new FrameSet. It is assumed that he base Frame in the
+   new FrameSet is equivalent to Frame 2 in the existing Plot. This
+   assumption is based on the way the CreatePlots and SplitFrameSet
+   functions work, and should be reviewed if either of these functions
+   is changed. */
+      frm = astGetFrame( fsetxy, 2 );
+      map = astGetMapping( fsetxy, 1, 2 );
+
+/* Add the new Frame into the existing Plot using the Mapping to connect
+   it to Frame 2 in the Plot. It becomes the current Frame in the Plot. */
+      astAddFrame( this->plotxy, 2, map, frm );
+
+/* Delete the original current Frame in the Plot since it is not needed
+   any more. */
+      astRemoveFrame( this->plotxy, 3 );
+
+/* Set the Plot attributes. */
+      SetPlotAttr( this->plotxy, XY, labelxy, status );
+
+/* Free resources */
+      fsetxy = astAnnul( fsetxy );
+      map = astAnnul( map );
+      frm = astAnnul( frm );
+
+/* Do the same for the XZ plot. */
+      frm = astGetFrame( fsetxz, 2 );
+      map = astGetMapping( fsetxz, 1, 2 );
+      astAddFrame( this->plotxz, 2, map, frm );
+      astRemoveFrame( this->plotxz, 3 );
+      SetPlotAttr( this->plotxz, XZ, labelxz, status );
+      fsetxz = astAnnul( fsetxz );
+      map = astAnnul( map );
+      frm = astAnnul( frm );
+
+/* Do the same for the YZ plot. */
+      frm = astGetFrame( fsetyz, 2 );
+      map = astGetMapping( fsetyz, 1, 2 );
+      astAddFrame( this->plotyz, 2, map, frm );
+      astRemoveFrame( this->plotyz, 3 );
+      SetPlotAttr( this->plotyz, YZ, labelyz, status );
+      fsetyz = astAnnul( fsetyz );
+      map = astAnnul( map );
+      frm = astAnnul( frm );
+
+/* Store information that allows each 3D WCS axis to be associatedf with
+   a pair of Plots. Also store the WCS axis within each Plot that
+   corresponds to the 3D WCS axis. */
+      StoreAxisInfo( this, labelxy, wcsxy, labelxz, wcsxz, labelyz, wcsyz, status );
+
+/* Set up the Edges attributes in the encapsulated Plots to produce labels
+   on the required edges of the 3D graphics cube. */
+      rootcorner = astGetRootCorner( this );
+      ChangeRootCorner( this, rootcorner, rootcorner, status );
+
+/* Store the plane spanned by two connected 3D axes. */
+      this->baseplot = baseplane;
+
+/* Free remaining resources */
+      fset = astAnnul( fset );
+   }
+}
+
+static void VSet( AstObject *this_object, const char *settings,
+                  char **text, va_list args, int *status ) {
+/*
+*  Name:
+*     VSet
+
+*  Purpose:
+*     Set values for a Plot3D's attributes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frameset.h"
+*     void VSet( AstObject *this, const char *settings, char **text,
+*                va_list args, int *status )
+
+*  Class Membership:
+*     Plot3D member function (over-rides the protected astVSet
+*     method inherited from the Object class).
+
+*  Description:
+*     This function assigns a set of attribute values for a Plot3D,
+*     the attributes and their values being specified by means of a
+*     string containing a comma-separated list of the form:
+*
+*        "attribute1 = value1, attribute2 = value2, ... "
+*
+*     Here, "attribute" specifies an attribute name and the value to
+*     the right of each "=" sign should be a suitable textual
+*     representation of the value to be assigned to that attribute. This
+*     will be interpreted according to the attribute's data type.
+*
+*     The string supplied may also contain "printf"-style format
+*     specifiers identified by a "%" sign in the usual way. If
+*     present, these will be substituted by values supplied as
+*     optional arguments (as a va_list variable argument list), using
+*     the normal "printf" rules, before the string is used.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D.
+*     settings
+*        Pointer to a null-terminated string containing a
+*        comma-separated list of attribute settings.
+*     text
+*        Pointer to a location at which to return a pointer to dynamic
+*        memory holding a copy of the expanded setting string. This memory
+*        should be freed using astFree when no longer needed. If a NULL
+*        pointer is supplied, no string is created.
+*     args
+*        The variable argument list which contains values to be
+*        substituted for any "printf"-style format specifiers that
+*        appear in the "settings" string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function preserves the integrity of the Plot3D (if
+*     possible) by appropriately modifying the three encapsulated Plots.
+*/
+
+/* Invoke the parent astVSet method to set the Plot3D's attribute values. */
+   (*parent_vset)( this_object, settings, text, args, status );
+
+/* Update the three 2D Plots stored in the Plot3D structure so that they
+   reflect this modified FrameSet. */
+   UpdatePlots( (AstPlot3D *) this_object, status );
+}
+
+/* Functions which access Plot3D class attributes. */
+/* ----------------------------------------------- */
+
+/* RootCorner. */
+/* ----------- */
+/*
+*att++
+*  Name:
+*     RootCorner
+
+*  Purpose:
+*     Specifies which edges of the 3D box should be annotated.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute controls the appearance of an annotated
+c     coordinate grid (drawn with the astGrid function) by determining
+f     coordinate grid (drawn with the AST_GRID routine) by determining
+*     which edges of the cube enclosing the 3D graphics space are used
+*     for displaying numerical and descriptive axis labels. The attribute
+*     value identifies one of the eight corners of the cube within
+*     which graphics are being drawn (i.e. the cube specified by the
+c     "graphbox" parameter when astPlot3D
+f     GRAPHBOX argument when AST_PLOT3D
+*     was called tp create the Plot3D). Axis labels and tick marks will
+*     be placed on the three cube edges that meet at the given corner.
+*
+*     The attribute value should consist of three character, each of
+*     which must be either "U" or "L". The first character in the string
+*     specifies the position of the corner on the first graphics axis.
+*     If the character is "U" then the corner is at the upper bound on the
+*     first graphics axis. If it is "L", then the corner is at the lower
+*     bound on the first axis. Likewise, the second and third characters
+*     in the string specify the location of the corner on the second and
+*     third graphics axes.
+*
+*     For instance, corner "LLL" is the corner that is at the lower bound
+*     on all three graphics axes, and corner "ULU" is at the upper bound
+*     on axes 1 and 3 but at the lower bound on axis 2.
+*
+*     The default value is "LLL".
+
+*  Applicability:
+*     Plot3D
+*        All Plot3Ds have this attribute.
+
+*att--
+*/
+
+/* Internally, the RootCorner is represented as an integer bit mask, with
+   bit zero for the X axis, bit 1 for the Y axis and bit 2 for the Z axis.
+   A bit is set if the corner is at the upper bound on the axis and unset
+   if it is at the lower bound. A value of -1 indicates that the attribue
+   has not been assigned a value. */
+astMAKE_GET(Plot3D,RootCorner,int,0,( this->rootcorner == -1 ? 0 : this->rootcorner))
+astMAKE_TEST(Plot3D,RootCorner,( this->rootcorner != -1 ))
+
+
+/* Norm(axis). */
+/* ----------- */
+/*
+*att++
+*  Name:
+*     Norm(axis)
+
+*  Purpose:
+*     Specifies the plane upon which a Plot3D draws text and markers.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute controls the appearance of text and markers drawn
+*     by a Plot3D. It specifies the orientation of the plane upon which
+*     text and markers will be drawn by all subsequent invocations of the
+c     astText and astMark functions.
+f     AST_TEXT and AST_MARK functions.
+*
+*     When setting or getting the Norm attribute, the attribute name must
+*     be qualified by an axis index in the range 1 to 3. The 3 elements of
+*     the Norm attribute are together interpreted as a vector in 3D graphics
+*     coordinates that is normal to the plane upon which text and marks
+*     should be drawn. When testing or clearing the attribute, the axis
+*     index is optional. If no index is supplied, then clearing the Norm
+*     attribute will clear all three elements, and testing the Norm attribute
+*     will return a non-zero value if any of the three elements are set.
+*
+*     The default value is 1.0 for each of the 3 elements. The length of
+*     the vector is insignificant, but an error will be reported when
+*     attempting to draw text or markers if the vector has zero length.
+
+*  Applicability:
+*     Plot
+*        All Plot3Ds have this attribute.
+
+*att--
+*/
+MAKE_CLEAR3(Norm,norm,AST__BAD,3)
+MAKE_SET3(Norm,double,norm,value,3)
+MAKE_TEST3(Norm,( this->norm[axis] != AST__BAD ),3)
+MAKE_GET3(Norm,double,AST__BAD,(this->norm[axis]!=AST__BAD?this->norm[axis]:1.0),3)
+
+
+
+/* Functions which access Plot class attributes. */
+/* --------------------------------------------- */
+
+/* First do axis specific attributes. */
+
+#define MAKE_ALL(attr,type,badval,whichplots) \
+   MAKE_CLEAR(attr,whichplots) \
+   MAKE_SET(attr,type,whichplots) \
+   MAKE_GET(attr,type,badval )
+
+MAKE_ALL(MinTick,int,0,0)
+MAKE_ALL(Abbrev,int,0,1)
+MAKE_ALL(Gap,double,AST__BAD,1)
+MAKE_ALL(LogGap,double,AST__BAD,1)
+MAKE_ALL(LogPlot,int,0,0)
+MAKE_ALL(LogTicks,int,0,0)
+MAKE_ALL(LogLabel,int,0,1)
+MAKE_ALL(LabelUp,int,0,1)
+MAKE_ALL(DrawAxes,int,0,0)
+MAKE_ALL(LabelUnits,int,0,1)
+MAKE_ALL(MinTickLen,double,0.0,0)
+MAKE_ALL(MajTickLen,double,0.0,0)
+MAKE_ALL(NumLab,int,0,1)
+MAKE_ALL(NumLabGap,double,AST__BAD,1)
+MAKE_ALL(TextLab,int,0,1)
+MAKE_ALL(TextLabGap,double,AST__BAD,1)
+
+#undef MAKE_ALL
+
+
+/* Now do attributes that are not axis specific. */
+
+#define MAKE_ALL(attr,type) \
+   MAKE_CLEAR1(attr) \
+   MAKE_SET1(attr,type)
+
+MAKE_ALL(Ink,int)
+MAKE_ALL(Tol,double)
+MAKE_ALL(Invisible,int)
+MAKE_ALL(TickAll,int)
+MAKE_ALL(ForceExterior,int)
+MAKE_ALL(Border,int)
+MAKE_ALL(Clip,int)
+MAKE_ALL(ClipOp,int)
+MAKE_ALL(Escape,int)
+MAKE_ALL(Grid,int)
+MAKE_ALL(Labelling,int)
+
+#undef MAKE_ALL
+
+
+
+/* First do element-specific attributes. */
+
+#define MAKE_ALL(attr,type) \
+   MAKE_CLEAR2(attr) \
+   MAKE_SET2(attr,type)
+
+MAKE_ALL(Style,int)
+MAKE_ALL(Font,int)
+MAKE_ALL(Colour,int)
+MAKE_ALL(Width,double)
+MAKE_ALL(Size,double)
+
+#undef MAKE_ALL
+
+
+
+
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Plot3D objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Plot3D objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstPlot3D *in;             /* Pointer to input Plot3D */
+   AstPlot3D *out;            /* Pointer to output Plot3D */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Plot3Ds. */
+   in = (AstPlot3D *) objin;
+   out = (AstPlot3D *) objout;
+
+/* Nullify the pointers stored in the output object since these will
+   currently be pointing at the input data (since the output is a simple
+   byte-for-byte copy of the input). Otherwise, the input data could be
+   freed by accidient if the output object is deleted due to an error
+   occuring in this function. */
+   out->plotxy = NULL;
+   out->plotxz = NULL;
+   out->plotyz = NULL;
+
+/* Copy the encapsulated Plots */
+   if( in->plotxy ) out->plotxy = astCopy( in->plotxy );
+   if( in->plotxz ) out->plotxz = astCopy( in->plotxz );
+   if( in->plotyz ) out->plotyz = astCopy( in->plotyz );
+
+/* If an error has occurred, free the output resources. */
+   if( !astOK ) Delete( (AstObject *) out, status );
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Plot3D objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Plot3D objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstPlot3D *this;
+
+/* Release the memory referred to in the Plot3D structure. */
+   this = (AstPlot3D *) obj;
+   if( this ) {
+      this->plotxy = astDelete( this->plotxy );
+      this->plotxz = astDelete( this->plotxz );
+      this->plotyz = astDelete( this->plotyz );
+   }
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Plot3D objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Plot3D class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Plot3D whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstPlot3D *this;
+   double dval;
+   char key[ KEY_LEN + 1 ];
+   int axis;
+   int helpful;
+   int ival;
+   int set;
+   const char *text;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Plot3D structure. */
+   this = (AstPlot3D *) this_object;
+
+/* Write out values representing the instance variables for the
+   Plot3D class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+
+/* Norm. */
+/* ----- */
+   for ( axis = 0; axis < 3; axis++ ) {
+      dval = astGetNorm( this, axis );
+      helpful = ( dval != -DBL_MAX );
+      (void) sprintf( key, "Norm%d", axis + 1 );
+      astWriteDouble( channel, key, 0, helpful, dval, "Text plane normal vector");
+   }
+
+/* RootCorner. */
+/* ----------- */
+   set = TestRootCorner( this, status );
+   ival = set ? GetRootCorner( this, status ) : astGetRootCorner( this );
+   text = RootCornerString( ival, status );
+   if( text ) {
+      astWriteString( channel, "RootCn", set, 1, text, "Corner where labelled axes meet" );
+   } else if( astOK ) {
+      astError( AST__INTER, "astDump(Plot3D): Illegal value %d found for "
+                "RootCorner attribute (interbal AST programming error).", status,
+                ival );
+   }
+
+/* Labelled axes */
+   astWriteInt( channel, "AxPlX1", 1, 1, this->axis_plot1[0],
+                "Plot used to label the 3D X axis" );
+   astWriteInt( channel, "AxPlY1", 1, 1, this->axis_plot1[1],
+                "Plot used to label the 3D Y axis" );
+   astWriteInt( channel, "AxPlZ1", 1, 1, this->axis_plot1[2],
+                "Plot used to label the 3D Z axis" );
+   astWriteInt( channel, "AxInX1", 1, 1, this->axis_index1[0],
+                "Plot axis index used to label the 3D X axis" );
+   astWriteInt( channel, "AxInY1", 1, 1, this->axis_index1[1],
+                "Plot axis index used to label the 3D Y axis" );
+   astWriteInt( channel, "AxInZ1", 1, 1, this->axis_index1[2],
+                "Plot axis index used to label the 3D Z axis" );
+
+/* Unlabelled axes */
+   astWriteInt( channel, "AxPlX2", 1, 1, this->axis_plot2[0],
+                "Other Plot touching the 3D X axis" );
+   astWriteInt( channel, "AxPlY2", 1, 1, this->axis_plot2[1],
+                "Other Plot touching the 3D Y axis" );
+   astWriteInt( channel, "AxPlZ2", 1, 1, this->axis_plot2[2],
+                "Other Plot touching the 3D Z axis" );
+   astWriteInt( channel, "AxInX2", 1, 1, this->axis_index2[0],
+                "Other Plot axis index touching the 3D X axis" );
+   astWriteInt( channel, "AxInY2", 1, 1, this->axis_index2[1],
+                "Other Plot axis index touching the 3D Y axis" );
+   astWriteInt( channel, "AxInZ2", 1, 1, this->axis_index2[2],
+                "Other Plot axis index touching the 3D Z axis" );
+
+/* The Plot that spans the two connected axes. */
+   astWriteInt( channel, "BasePl", 1, 1, this->baseplot,
+                "Plot spanning two connected 3D axes" );
+
+/* XY Plot */
+   astWriteObject( channel, "PlotXY", 1, 1, this->plotxy,
+                   "Plot describing the XY plane" );
+
+/* XZ Plot */
+   astWriteObject( channel, "PlotXZ", 1, 1, this->plotxz,
+                   "Plot describing the XZ plane" );
+
+/* YZ Plot */
+   astWriteObject( channel, "PlotYZ", 1, 1, this->plotyz,
+                   "Plot describing the YZ plane" );
+
+/* The index within the Plot3D FrameSet, of the original base Frame in
+   the FrameSet supplied when the Plot3D was constructed. */
+   astWriteInt( channel, "PixFrm", 1, 0, this->pix_frame,
+                "Index of original base Frame" );
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAPlot3D and astCheckPlot3D functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Plot3D,Plot)
+astMAKE_CHECK(Plot3D)
+
+
+AstPlot3D *astPlot3D_( void *frame_void, const float *graphbox,
+                       const double *basebox, const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astPlot3D
+
+*  Purpose:
+*     Create a Plot3D.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     AstPlot3D *astPlot3D( AstFrame *frame, const float *graphbox,
+*                           const double *basebox, const char *options, ..., int *status )
+
+*  Class Membership:
+*     Plot3D constructor.
+
+*  Description:
+*     This function creates a new Plot3D and optionally initialises its
+*     attributes.
+*
+*     The supplied Frame (or the base frame if a FrameSet was supplied) is
+*     assumed to be related to the graphics world coordinate system by a
+*     simple shift and scale along each axis. The mapping between graphics
+*     world coordinates and this Frame is specified by supplying the
+*     coordinates in both systems at the lower bounds and upper bounds corners
+*     of a box on the graphics device. By default, no graphics will be
+*     produced outside the supplied box, but this default behaviour can be
+*     changed by setting explicit values for the various clipping attributes.
+
+*  Parameters:
+*     frame
+*        A pointer to a Frame or FrameSet to be annotated. If a NULL pointer
+*        is supplied, then a default 3-D Frame will be created to which labels,
+*        etc, can be attached by setting the relevant Frame attributes.
+*     graphbox
+*        A pointer to an array of 6 values giving the graphics world
+*        coordinates of the lower bounds and upper bound corners of a box on
+*        the graphics output device. The first triple of values should be the
+*        coordinates of the lower bounds corner of the box and the second
+*        triple of values should be the coordinates of the upper bounds corner.
+*     basebox
+*        A pointer to an array of 6 values giving the coordinates in the
+*        supplied Frame, or base frame of the supplied FrameSet, at the
+*        lower bounds corner and upper bounds corners of the box specified
+*        by parameter graphbox. These should be supplied in the same order as
+*        for parameter "graphbox".
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new Plot3D. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new Plot3D.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic Plot3D constructor which
+*     is available via the protected interface to the Plot3D class.
+*     A public interface is provided by the astPlot3DId_ function.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstPlot3D *new;               /* Pointer to new Plot3D */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   new = NULL;
+
+/* Obtain and validate a pointer to any supplied Frame structure. */
+   if( frame_void ){
+      frame = astCheckFrame( frame_void );
+   } else {
+      frame = NULL;
+   }
+
+/* Check the pointer can be used. */
+   if ( astOK ) {
+
+/* Initialise the Plot3D, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitPlot3D( NULL, sizeof( AstPlot3D ), !class_init,
+                           &class_vtab, "Plot3D", frame, graphbox,
+                           basebox );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   Plot's attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new Plot. */
+   return new;
+}
+
+AstPlot3D *astInitPlot3D_( void *mem, size_t size, int init,
+                           AstPlot3DVtab *vtab, const char *name,
+                           AstFrame *frame, const float *graphbox,
+                           const double *basebox, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPlot3D
+
+*  Purpose:
+*     Initialise a Plot3D.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     AstPlot3D *astInitPlot3D( void *mem, size_t size, int init,
+*                               AstPlotVtab *vtab, const char *name,
+*                               AstFrame *frame, const float *graphbox,
+*                               const double *basebox )
+
+*  Class Membership:
+*     Plot3D initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new Plot3D object. It allocates memory (if
+*     necessary) to accommodate the Plot3D plus any additional data
+*     associated with the derived class. It then initialises a
+*     Plot3D structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual function
+*     table for a Plot3D at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Plot3D is to be
+*        created. This must be of sufficient size to accommodate the
+* 	 Plot3D data (sizeof(Plot3D)) plus any data used by
+* 	 the derived class. If a value of NULL is given, this function
+*	 will allocate the memory itself using the "size" parameter to
+*	 determine its size.
+*     size
+*        The amount of memory used by the Plot3D (plus derived
+*	 class data). This will be used to allocate memory if a value of
+*	 NULL is given for the "mem" parameter. This value is also stored
+*	 in the Plot3D structure, so a valid value must be supplied
+*	 even if not required for allocating memory.
+*     init
+*        A logical flag indicating if the Plot3D's virtual function
+*	 table is to be initialised. If this value is non-zero, the
+*	 virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*	 associated with the new Plot3D.
+*     name
+*        Pointer to a constant null-terminated character string which
+*	 contains the name of the class to which the new object belongs
+*	 (it is this pointer value that will subsequently be returned by
+*	 the astGetClass method).
+*     fset
+*        A pointer to the Frame or Frameset to be annotated.
+*     graphbox
+*        A pointer to an array of 6 values giving the graphics coordinates
+*        of the bottom left and top right corners of a box on the graphics
+*        output device. The first triple of values should be the graphics
+*        coordinates of the bottom left corner of the box and the second
+*        triple of values are the graphics coordinates of the top right corner.
+*     basebox
+*        A pointer to an array of 6 values giving the coordinates in the
+*        supplied Frame or base Frame of the supplied FrameSet at the bottom
+*        left and top right corners of the box specified by parameter graphbox.
+*        These should be supplied in the same order as for parameter "graphbox".
+
+*  Returned Value:
+*     A pointer to the new Plot3D.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *baseframe = NULL;  /* Pointer to base frame */
+   AstFrame *graphicsframe = NULL; /* Pointer to graphics frame */
+   AstFrameSet *fset0 = NULL;   /* The n-D FrameSet to be annotated */
+   AstFrameSet *fset = NULL;    /* The 2-D FrameSet to be annotated */
+   AstMapping *map = NULL;      /* Mapping for converting bbox -> gbox */
+   AstPlot3D *new = NULL;       /* Pointer to new Plot3D */
+   char *mess = NULL;           /* Pointer to a descriptive message */
+   double djunkbox[ 4 ] = { 0.0, 0.0, 1.0, 1.0 }; /* Dummy 2D basebox */
+   float fjunkbox[ 4 ] = { 0.0, 0.0, 1.0, 1.0 }; /* Dummy 2D graphbox */
+   int bi;                      /* Index of base frame */
+   int ci;                      /* Index of current frame */
+   int i;                       /* Loop count */
+   int ii;                      /* Loop count */
+   int naxes;                   /* No. of axes in frame */
+   int nfrm;                    /* Number of Frames in frameset */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitPlot3DVtab( vtab, name );
+
+/* First of all we need to ensure that we have a FrameSet and a base
+   Frame on which to base the new Plot3D. If a NULL Frame pointer was
+   supplied, create a default 3-D Frame, and then create a FrameSet
+   containing just this default Frame. Also store a pointer to a
+   message which can be used to describe the object within error
+   messages. */
+   if( !frame ){
+      baseframe = astFrame( 3, "", status );
+      fset = astFrameSet( baseframe, "", status );
+      mess = "default 3-d Frame";
+
+/* If an object was supplied, report an error if it is not a Frame or
+   an object derived from a Frame (such as a FrameSet). */
+   } else if( !astIsAFrame( frame ) ){
+      if( astOK ){
+         astError( AST__BDOBJ, "astInitPlot3D(%s): Supplied Object (class '%s') "
+                   "is not a Frame.", status, name, astGetClass( frame ) );
+      }
+
+/* If the supplied object is a Plot3D or an object derived from a Plot3D
+   (a Plot3D is a sort of Frame and so will pass the above test), extract a
+   FrameSet from the Plot3D, and clear the Domain attribute for any existing
+   Frames which have Domain GRAPHICS. */
+   } else if( astIsAPlot3D( frame ) ){
+      fset0 = astFrameSet( frame, "", status );
+      fset = astCopy( fset0 );
+      fset0 = astAnnul( fset0 );
+
+      for( i = 0; i < astGetNframe( fset ); i++ ) {
+         graphicsframe = astGetFrame( fset, i );
+         if( !strcmp( astGetDomain( graphicsframe ), "GRAPHICS" ) ) {
+            astClearDomain( graphicsframe );
+         }
+         graphicsframe = astAnnul( graphicsframe );
+      }
+
+      baseframe = astGetFrame( fset, astGetBase( fset ) );
+      mess = "base Frame of the supplied Plot3D";
+
+/* If the object is not a FrameSet, create a FrameSet holding the
+   supplied Frame. If the Frame is not 3D, an extra 3D Frame is
+   included in the FrameSet derived from axes 1, 2 and 3 of the supplied
+   Frame. This new Frame becomes the base Frame. */
+   } else if( !astIsAFrameSet( frame ) ){
+      fset0 = astFrameSet( frame, "", status );
+      mess = "supplied Frame";
+      fset = Fset3D( fset0, AST__BASE, status );
+      fset0 = astAnnul( fset0 );
+      baseframe = astGetFrame( fset, astGetBase( fset ) );
+
+/* If a FrameSet was supplied, ensure it has a 3D base Frame.
+   If the supplied FrameSet is not 3D, then a new base Frame is
+   inserted into it which is derived from axes 1, 2 and 3 of the
+   original base Frame. */
+   } else {
+      fset = Fset3D( (AstFrameSet *) frame, AST__BASE, status );
+      baseframe = astGetFrame( fset, astGetBase( fset ) );
+      mess = "base Frame of the supplied FrameSet";
+   }
+
+/* Check that there are 3 axes in the base frame of the FrameSet. */
+   naxes = astGetNaxes( baseframe );
+   if ( naxes != 3 && astOK ) {
+      astError( AST__NAXIN, "astInitPlot3D(%s): Number of axes (%d) in the %s "
+                "is invalid - this number should be 3.", status, name, naxes, mess );
+   }
+
+/* Check that no dimension of the graphbox is bad. */
+   if( astISBAD(graphbox[0]) || astISBAD(graphbox[1]) ||
+       astISBAD(graphbox[2]) || astISBAD(graphbox[3]) ||
+       astISBAD(graphbox[4]) || astISBAD(graphbox[5]) ) {
+      astError( AST__BADBX, "astInitPlot3D(%s): The plotting volume has undefined limits "
+                "in the graphics world coordinate system.", status, name );
+   }
+
+/* Check that no dimension of the graphbox is zero. */
+   if( ( graphbox[ 3 ] == graphbox[ 0 ] ||
+         graphbox[ 4 ] == graphbox[ 1 ] ||
+         graphbox[ 5 ] == graphbox[ 2 ] ) && astOK ){
+      astError( AST__BADBX, "astInitPlot3D(%s): The plotting volume has zero size "
+                "in the graphics world coordinate system.", status, name );
+   }
+
+/* Check that no dimension of the basebox is bad. */
+   if( astISBAD(basebox[0]) || astISBAD(basebox[1]) ||
+       astISBAD(basebox[2]) || astISBAD(basebox[3]) ||
+       astISBAD(basebox[4]) || astISBAD(basebox[5]) ) {
+      astError( AST__BADBX, "astInitPlot3D(%s): The limits of "
+                "the %s are undefined or bad.", status, name, name );
+   }
+
+/* Create a Frame which describes the graphics world coordinate system. */
+   graphicsframe = astFrame( 3, "Domain=GRAPHICS,Title=Graphical Coordinates", status );
+
+/* Initialise a 2D Plot structure (the parent class) as the first component
+   within the Plot3D structure, allocating memory if necessary. We supply
+   dummy arguments since we will not be using the parent Plot class to
+   draw anything. We supply a NULL vtab pointer so that methods defined by
+   the Plot class will be used during the construction of the Plot3D. Once
+   the Plot3D is fully constructed, we will use astSetVtab to establish
+   the correct vtab. */
+   new = (AstPlot3D *) astInitPlot( mem, size, 0, NULL, name, NULL, fjunkbox,
+                                    djunkbox );
+   if ( astOK ) {
+
+/* Initialise the Plot3D data. */
+/* ----------------------------- */
+
+/* Remove all Frames from the parent FrameSet except for the base (2D graphics)
+   Frame (we leave this last Frame since an error is reported if the last
+   Frame is removed from a FrameSet). We do this by repeatedly removing the
+   first Frame, causing all remaining Frames to have their index reduced by 1.
+   When the base Frame arrives at index 1, we skip it and start removing the
+   second frame instead. */
+      nfrm = astGetNframe( new );
+      i = 1;
+      for( ii = 0; ii < nfrm; ii++ ) {
+         if( i > 1 || astGetBase( new ) != 1 ) {
+            astRemoveFrame( new, i );
+         } else {
+            i = 2;
+         }
+      }
+
+/* Add in the 3D graphics Frame, using a PermMap to connect it to the
+   2D graphics Frame. */
+      map = (AstMapping *) astPermMap( 2, NULL, 3, NULL, NULL, "", status );
+      astAddFrame( new, 1, map, graphicsframe );
+      map = astAnnul( map );
+
+/* And remove the 2D GRAPHICS Frame, leaving just the 3D GRAPHICS Frame
+   in the FrameSet, with index 1. */
+      astRemoveFrame( new, 1 );
+
+/* Get the index of the current (physical) and base (pixel) Frames in
+   the supplied FrameSet. */
+      bi = astGetBase( fset );
+      ci = astGetCurrent( fset );
+
+/* Temporarily set the current Frame to be the pixel frame. */
+      astSetCurrent( fset, bi );
+
+/* Get a double precision version of "graphbox", and store it in the
+   Plot3D structure. */
+      new->gbox[ 0 ] = (double) graphbox[ 0 ];
+      new->gbox[ 1 ] = (double) graphbox[ 1 ];
+      new->gbox[ 2 ] = (double) graphbox[ 2 ];
+      new->gbox[ 3 ] = (double) graphbox[ 3 ];
+      new->gbox[ 4 ] = (double) graphbox[ 4 ];
+      new->gbox[ 5 ] = (double) graphbox[ 5 ];
+
+/* The base Frame of the supplied FrameSet is mapped linearly onto the
+   graphics frame. Create a WinMap that maps the base box (within the
+   base Frame of the supplied FrameSet) onto the graphics box. */
+      map = (AstMapping *) astWinMap( 3, new->gbox, new->gbox + 3, basebox,
+                                      basebox + 3, "", status );
+
+/* Add the supplied FrameSet into the Plot3D (i.e. FrameSet) created
+   earlier. This leaves the graphics frame with index 1 in the
+   returned  Plot3D. */
+      astAddFrame( (AstFrameSet *) new, 1, map, fset );
+      map = astAnnul( map );
+
+/* Set the current Frame in the Plot to be the physical coordinate Frame
+   (with index incremented by one because the graphics Frame has been added). */
+      astSetCurrent( (AstFrameSet *) new, ci + 1 );
+
+/* Note the index of the original base Frame in the Plot3D FrameSet */
+      new->pix_frame = bi + 1;
+
+/* Re-establish the original current Frame in the supplied FrameSet. */
+      astSetCurrent( fset, ci );
+
+/* Initialise the Plot pointers. */
+      new->plotxy = NULL;
+      new->plotxz = NULL;
+      new->plotyz = NULL;
+
+/* Initialise other attributes */
+      new->rootcorner = -1;
+
+/* Initialise the normal vector to the plane used by astText and astMark. */
+      new->norm[ 0 ] = AST__BAD;
+      new->norm[ 1 ] = AST__BAD;
+      new->norm[ 2 ] = AST__BAD;
+
+/* Create three 2D Plots to describe the three planes in the cube. */
+      CreatePlots( new, fset, graphbox, basebox, status );
+
+/* Ensure that attempts to use the graphics interface of the parent
+   Plot structure get forwarded to the relevant 3D routines defined in
+   this class. */
+      astGrfSet( new, "Attr", (AstGrfFun) Attr3D );
+      astSetGrf( new, 1 );
+
+/* Change the virtual function table stored in the new Plot3D, from the Plot
+   vtab (established when astINitPlot was called above), to the supplied
+   vtab. */
+      if( vtab ) astSetVtab( new, vtab );
+
+/* Ensure that these Plots use the grf functions defined by this class
+   (Plot3D). This means that whenever a Plot draws anything, it will use
+   the appropriate grf function defined in this class to do the drawing.
+   The grf functions defined in this class, convert the grf call into a
+   grf3D call apprpriate the plane spanned by the Plot. */
+      Set3DGrf( new, new->plotxy, XY, status );
+      Set3DGrf( new, new->plotxz, XZ, status );
+      Set3DGrf( new, new->plotyz, YZ, status );
+
+/* Set up the Edges attributes in the encapsulated Plots so that labels
+   appear on the requited edges. Initially, the root corner is "LLL"
+   (i.e. the lower bound on every axis). */
+      ChangeRootCorner( new, 0, 0, status );
+   }
+
+/* Annul the frame. */
+   graphicsframe = astAnnul( graphicsframe );
+
+/* If an error occurred, clean up by deleting the new object. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Annul the pointer to the base Frame and FrameSet. */
+   baseframe = astAnnul( baseframe );
+   fset = astAnnul( fset );
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+
+AstPlot3D *astLoadPlot3D_( void *mem, size_t size, AstPlot3DVtab *vtab,
+                           const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadPlot3D
+
+*  Purpose:
+*     Load a Plot3D.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "plot3d.h"
+*     AstPlot3D *astLoadPlot3D( void *mem, size_t size,
+*                               AstPlot3DVtab *vtab,
+*                               const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     Plot3D loader.
+
+*  Description:
+*     This function is provided to load a new Plot3D using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Plot3D structure in this memory, using data read from the
+*     input Channel.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Plot3D is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Plot3D data (sizeof(Plot3D)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Plot3D (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Plot3D structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstPlot3D) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Plot3D. If this is NULL, a pointer
+*        to the (static) virtual function table for the Plot3D class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Plot3D" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Plot3D.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   AstPlot3D *new;
+   char key[ KEY_LEN + 1 ];
+   char *text;
+   int axis;
+   int i;
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Plot3D. In this case the
+   Plot3D belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstPlot3D );
+      vtab = &class_vtab;
+      name = "Plot3D";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitPlot3DVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Allocate memory to hold the new Object. We allocate it now rather than
+   waiting for astInitObject to allocate it so that we can pass a NULL
+   vtab pointer to the Plot loader, thus causing the Plot loader to use the
+   function implementations provided by the Plot class rather than those
+   provided by the class being instantiated. */
+   if( !mem ) mem = astMalloc( size );
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Plot3D. Pass a NULL vtab pointer so that the "new" object
+   will be loaded using Plot methods rather than than Plot3D methods.
+   This is important because the implementations provided by the Plot3D
+   class for the Plot attribute accessors require the existence of the
+   encapsulated Plots held within the Plot3D, but these have not yet been
+   created. */
+   new = astLoadPlot( mem, size, NULL, name, channel );
+   if ( astOK ) {
+
+/* Now modify the new object to use the supplied vtab. */
+      astSetVtab( new, vtab );
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Plot3D" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Norm. */
+/* ----- */
+      for( axis = 0; axis < 3; axis++ ) {
+         (void) sprintf( key, "norm%d", axis + 1 );
+         new->norm[ axis ] = astReadDouble( channel, key, AST__BAD );
+         if( TestNorm( new, axis, status ) ) SetNorm( new, axis, new->norm[ axis ], status );
+      }
+
+/* RootCorner. */
+/* ----------- */
+      text = astReadString( channel, "rootcn", " " );
+      if( astOK && strcmp( text, " " ) ) {
+         new->rootcorner = RootCornerInt( text, status );
+         if( new->rootcorner < 0 && astOK ) {
+            astError( AST__INTER, "astRead(Plot3D): Corrupt Plot3D contains "
+                      "invalid RootCorner attribute value (%s).", status, text );
+         }
+      } else {
+         new->rootcorner = -1;
+      }
+      text = astFree( text );
+
+/* Labelled axes */
+      new->axis_plot1[0] = astReadInt( channel, "axplx1", -1 );
+      new->axis_plot1[1] = astReadInt( channel, "axply1", -1 );
+      new->axis_plot1[2] = astReadInt( channel, "axplz1", -1 );
+
+      new->axis_index1[0] = astReadInt( channel, "axinx1", -1 );
+      new->axis_index1[1] = astReadInt( channel, "axiny1", -1 );
+      new->axis_index1[2] = astReadInt( channel, "axinz1", -1 );
+
+/* Unlabelled axes */
+      new->axis_plot2[0] = astReadInt( channel, "axplx2", -1 );
+      new->axis_plot2[1] = astReadInt( channel, "axply2", -1 );
+      new->axis_plot2[2] = astReadInt( channel, "axplz2", -1 );
+
+      new->axis_index2[0] = astReadInt( channel, "axinx2", -1 );
+      new->axis_index2[1] = astReadInt( channel, "axiny2", -1 );
+      new->axis_index2[2] = astReadInt( channel, "axinz2", -1 );
+
+/* Plot that spans two connected 3D axes. */
+      new->baseplot = astReadInt( channel, "basepl", -1 );
+
+/* XY Plot */
+      new->plotxy = astReadObject( channel, "plotxy", NULL );
+
+/* XZ Plot */
+      new->plotxz = astReadObject( channel, "plotxz", NULL );
+
+/* YZ Plot */
+      new->plotyz = astReadObject( channel, "plotyz", NULL );
+
+/* The index within the Plot3D FrameSet, of the original base Frame in
+   the FrameSet supplied when the Plot3D was constructed. */
+      new->pix_frame = astReadInt( channel, "pixfrm", AST__NOFRAME );
+
+/* Ensure that these Plots use the grf functions defined by this class
+   (Plot3D). This means that whener a Plot draws anything, it will use
+   the appropriate grf function defined in this class to do the drawing.
+   The grf functions defined in this class, convert the grf call into a
+   grf3D call apprpriate the plane spanned by the Plot. */
+      Set3DGrf( new, new->plotxy, XY, status );
+      Set3DGrf( new, new->plotxz, XZ, status );
+      Set3DGrf( new, new->plotyz, YZ, status );
+
+/* For attributes of the parent Plot class will have been loaded
+   each attribute that has a set value in the parent Plot structure,
+   re-set the value so that it gets copied to the copy the  to the
+   encapsulated Plots. First do axis specific attributes. */
+
+#define COPY_ATTR(attr,nval) \
+   for( i = 0; i < nval; i++ ) { \
+      if( astTest##attr(new,i) ) astSet##attr(new,i,astGet##attr(new,i)); \
+   }
+
+   COPY_ATTR(MinTick,3)
+   COPY_ATTR(Abbrev,3)
+   COPY_ATTR(Gap,3)
+   COPY_ATTR(LogGap,3)
+   COPY_ATTR(LogPlot,3)
+   COPY_ATTR(LogTicks,3)
+   COPY_ATTR(LogLabel,3)
+   COPY_ATTR(LabelUp,3)
+   COPY_ATTR(DrawAxes,3)
+   COPY_ATTR(LabelUnits,3)
+   COPY_ATTR(MinTickLen,3)
+   COPY_ATTR(MajTickLen,3)
+   COPY_ATTR(NumLab,3)
+   COPY_ATTR(NumLabGap,3)
+   COPY_ATTR(TextLab,3)
+   COPY_ATTR(TextLabGap,3)
+
+   COPY_ATTR(Style,AST__NPID)
+   COPY_ATTR(Font,AST__NPID)
+   COPY_ATTR(Colour,AST__NPID)
+   COPY_ATTR(Width,AST__NPID)
+   COPY_ATTR(Size,AST__NPID)
+
+#undef COPY_ATTR
+
+/* Now do attributes that are not axis specific. */
+
+#define COPY_ATTR(attr) \
+   if( astTest##attr(new) ) astSet##attr(new,astGet##attr(new));
+
+   COPY_ATTR(Ink)
+   COPY_ATTR(Tol)
+   COPY_ATTR(Invisible)
+   COPY_ATTR(TickAll)
+   COPY_ATTR(ForceExterior)
+   COPY_ATTR(Border)
+   COPY_ATTR(Clip)
+   COPY_ATTR(ClipOp)
+   COPY_ATTR(Escape)
+   COPY_ATTR(Grid)
+   COPY_ATTR(Labelling)
+
+#undef COPY_ATTR
+
+/* If an error occurred, clean up by deleting the new Plot3D. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Plot3D pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+void astClearRootCorner_( AstPlot3D *this, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Plot3D,ClearRootCorner))( this, status );
+}
+
+void astSetRootCorner_( AstPlot3D *this, int value, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Plot3D,SetRootCorner))( this, value, status );
+}
+
+
+
+
+/* Special public interface functions. */
+/* =================================== */
+/* These provide the public interface to certain special functions
+   whose public interface cannot be handled using macros (such as
+   astINVOKE) alone. In general, they are named after the
+   corresponding protected version of the function, but with "Id"
+   appended to the name. */
+
+/* Public Interface Function Prototypes. */
+/* ------------------------------------- */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstPlot3D *astPlot3DId_( void *frame_void, const float graphbox[6],
+                         const double basebox[6], const char *, ... );
+
+/* Special interface function implementations. */
+/* ------------------------------------------- */
+AstPlot3D *astPlot3DId_( void *frame_void, const float graphbox[6],
+                         const double basebox[6], const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astPlot3D
+f     AST_PLOT3D
+
+*  Purpose:
+*     Create a Plot3D.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "plot3d.h"
+c     AstPlot3D *astPlot3D( AstFrame *frame, const float graphbox[ 6 ],
+c                           const double basebox[ 6 ], const char *options, ... )
+f     RESULT = AST_PLOT3D( FRAME, GRAPHBOX, BASEBOX, OPTIONS, STATUS )
+
+*  Class Membership:
+*     Plot3D constructor.
+
+*  Description:
+*     This function creates a new Plot3D and optionally initialises
+*     its attributes.
+*
+*     A Plot3D is a specialised form of Plot that provides facilities
+*     for producing 3D graphical output.
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        Pointer to a Frame describing the physical coordinate system
+*        in which to plot. A pointer to a FrameSet may also be given,
+*        in which case its current Frame will be used to define the
+*        physical coordinate system and its base Frame will be mapped
+*        on to graphical coordinates (see below).
+*
+*        If a null Object pointer (AST__NULL) is given, a default
+*        3-dimensional Frame will be used to describe the physical
+*        coordinate system. Labels, etc. may then be attached to this
+*        by setting the appropriate Frame attributes
+*        (e.g. Label(axis)) for the Plot.
+c     graphbox
+f     GRAPHBOX( 6 ) = REAL (Given)
+*        An array giving the position and extent of the plotting volume
+*        (within the plotting space of the underlying graphics system)
+*        in which graphical output is to appear. This must be
+*        specified using graphical coordinates appropriate to the
+*        underlying graphics system.
+*
+*        The first triple of values should give the coordinates of the
+*        bottom left corner of the plotting volume and the second triple
+*        should give the coordinates of the top right corner. The
+*        coordinate on the horizontal axis should be given first in
+*        each pair. Note that the order in which these points are
+*        given is important because it defines up, down, left and
+*        right for subsequent graphical operations.
+c     basebox
+f     BASEBOX( 6 ) = DOUBLE PRECISION (Given)
+*        An array giving the coordinates of two points in the supplied
+*        Frame (or in the base Frame if a FrameSet was supplied) which
+*        correspond to the bottom left and top right corners of the
+*        plotting volume, as specified above. This range of coordinates
+*        will be mapped linearly on to the plotting area. The
+*        coordinates should be given in the same order as above.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Plot3D. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Plot3D. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astPlot3D()
+f     AST_PLOT3D = INTEGER
+*        A pointer to the new Plot3D.
+
+*  Notes:
+*     - The base Frame of the returned Plot3D will be a new Frame which
+*     is created by this function to represent the coordinate system
+*     of the underlying graphics system (graphical coordinates). It is
+*     given a Frame index of 1 within the Plot3D. The choice of base
+*     Frame (Base attribute) should not, in general, be changed once a
+*     Plot3D has been created (although you could use this as a way of
+*     moving the plotting area around on the plotting surface).
+c     - If a Frame is supplied (via the "frame" pointer), then it
+f     - If a Frame is supplied (via the FRAME pointer), then it
+*     becomes the current Frame of the new Plot3D and is given a Frame
+*     index of 2.
+c     - If a FrameSet is supplied (via the "frame" pointer), then
+f     - If a FrameSet is supplied (via the FRAME pointer), then
+*     all the Frames within this FrameSet become part of the new Plot3D
+*     (where their Frame indices are increased by 1), with the
+*     FrameSet's current Frame becoming the current Frame of the Plot3D.
+*     - At least one of the three axes of the current Frame must be
+*     independent of the other two current Frame axes.
+*     - If a null Object pointer (AST__NULL) is supplied (via the
+c     "frame" pointer), then the returned Plot3D will contain two
+f     FRAME pointer), then the returned Plot3D will contain two
+*     Frames, both created by this function. The base Frame will
+*     describe graphics coordinates (as above) and the current Frame
+*     will be a basic Frame with no attributes set (this will
+*     therefore give default values for such things as the Plot3D Title
+*     and the Label on each axis). Physical coordinates will be mapped
+*     linearly on to graphical coordinates.
+*     - An error will result if the Frame supplied (or the base Frame
+*     if a FrameSet was supplied) is not 3-dimensional.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astPlot3D constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astPlot3D_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - Because no checking or casting of arguments is performed
+*     before the function is invoked, the "frame" parameter is of type
+*     (void *) and is converted from an ID value to a pointer and
+*     validated within the function itself.
+*     - The variable argument list also prevents this function from
+*     invoking astPlot3D_ directly, so it must be a
+*     re-implementation of it in all respects, except for the final
+*     conversion of the result to an ID value.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstPlot3D *new;               /* Pointer to new Plot3D */
+   va_list args;                 /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   new = NULL;
+
+/* Obtain a Frame pointer from any ID supplied and validate the
+   pointer to ensure it identifies a valid Frame. */
+   if( frame_void ){
+      frame = astVerifyFrame( astMakePointer( frame_void ) );
+   } else {
+      frame = NULL;
+   }
+
+/* Check the pointer can be used. */
+   if ( astOK ) {
+
+/* Initialise the Plot3D, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitPlot3D( NULL, sizeof( AstPlot3D ), !class_init,
+                           &class_vtab, "Plot3D", frame, graphbox,
+                           basebox );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   Plot3D's attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return an ID value for the new Plot3D. */
+   return astMakeId( new );
+
+}
+
+
+
+/* Macros that define method to override the methods of the Plot class
+   that are not currently implemented by this class. They just report an
+   error if called. */
+
+#define METHOD1(name) \
+static void name(ARGLIST,int *status){ \
+   if( !astOK ) return; \
+   astError( AST__INTER, "ast##name(%s): The ast##name " \
+             "method cannot be used with a %s (programming error).", status,  \
+             astGetClass( this ), astGetClass( this ) ); \
+}
+
+#define METHOD2(name,rettype,retval) \
+static rettype name(ARGLIST,int *status){ \
+   if( !astOK ) return retval; \
+   astError( AST__INTER, "ast##name(%s): The ast##name " \
+             "method cannot be used with a %s (programming error).", status,  \
+             astGetClass( this ), astGetClass( this ) ); \
+   return retval; \
+}
+
+
+#define ARGLIST AstPlot *this
+METHOD2(GetGrfContext,AstKeyMap *,NULL)
+#undef ARGLIST
+
+#define ARGLIST AstPlot *this
+METHOD1(GrfPop)
+#undef ARGLIST
+
+#define ARGLIST AstPlot *this
+METHOD1(GrfPush)
+#undef ARGLIST
+
+#define ARGLIST AstPlot *this, const char *name, AstGrfFun fun
+METHOD1(GrfSet)
+#undef ARGLIST
+
+#define ARGLIST AstPlot *this, int axis, const double start[], double length
+METHOD1(GridLine)
+#undef ARGLIST
+
+#define ARGLIST AstPlot *this, float lbnd[2], float ubnd[2]
+METHOD1(BoundingBox)
+#undef ARGLIST
+
+#define ARGLIST AstPlot *this, int iframe, const double lbnd[], const double ubnd[]
+METHOD1(Clip)
+#undef ARGLIST
+
+#define ARGLIST AstPlot *this, const double start[], const double finish[]
+METHOD1(Curve)
+#undef ARGLIST
+
+#define ARGLIST AstPlot *this, AstMapping *map
+METHOD1(GenCurve)
+#undef ARGLIST
+
+
+
+
+
+
+
+
diff --git a/ast/plot3d.h b/ast/plot3d.h
new file mode 100644
index 0000000..4826b44
--- /dev/null
+++ b/ast/plot3d.h
@@ -0,0 +1,258 @@
+#if !defined( PLOT3D_INCLUDED ) /* Include this file only once */
+#define PLOT3D_INCLUDED
+/*
+*+
+*  Name:
+*     plot3d.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Plot3D class.
+
+*  Invocation:
+*     #include "plot3d.h"
+
+*  Description:
+*     This include file defines the interface to the Plot3D class
+*     and provides the type definitions, function prototypes and
+*     macros, etc. needed to use this class.
+
+*  Copyright:
+*     Copyright (C) 2007 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     6-JUN-2007 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+#include "plot.h"               /* Parent Plot class */
+
+/* Macros. */
+/* ======= */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+
+#if defined(astCLASS)            /* Protected */
+
+#endif
+
+/* Type Definitions. */
+/* ================= */
+
+/* Plot3D structure. */
+/* ------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstPlot3D {
+
+/* Attributes inherited from the parent class. */
+   AstPlot plot;               /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstPlot *plotxy;            /* Plot describing the XY plane */
+   AstPlot *plotxz;            /* Plot describing the XZ plane */
+   AstPlot *plotyz;            /* Plot describing the YZ plane */
+   double gbox[6];             /* Graphics box */
+   int pix_frame;              /* Index of original base Frame */
+   int rootcorner;             /* Corner at junction of the annotated axes */
+   int baseplot;               /* The Plot that is used to label 2 3D axes */
+   int axis_plot1[3];          /* The Plot used to label each 3D axis */
+   int axis_index1[3];         /* The axis index within the axis_plot1 Plot */
+   int axis_plot2[3];          /* The other Plot touching each 3D axis */
+   int axis_index2[3];         /* The axis index within the axis_plot2 Plot */
+   double norm[3];             /* Normal vector for text plane */
+} AstPlot3D;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all objects in the
+   class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstPlot3DVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstPlotVtab plot_vtab;      /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+   int (* GetRootCorner)( AstPlot3D *, int * );
+   int (* TestRootCorner)( AstPlot3D *, int * );
+   void (* SetRootCorner)( AstPlot3D *, int, int * );
+   void (* ClearRootCorner)( AstPlot3D *, int * );
+
+   double (* GetNorm)( AstPlot3D *, int, int * );
+   int (* TestNorm)( AstPlot3D *, int, int * );
+   void (* SetNorm)( AstPlot3D *, int, double, int * );
+   void (* ClearNorm)( AstPlot3D *, int, int * );
+
+} AstPlot3DVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstPlot3DGlobals {
+   AstPlot3DVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 101 ];
+} AstPlot3DGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Plot3D)         /* Check class membership */
+astPROTO_ISA(Plot3D)           /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+AstPlot3D *astPlot3D_( void *, const float *, const double *, const char *, int *, ...);
+#else
+AstPlot3D *astPlot3DId_( void *, const float [], const double [], const char *, ... );
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstPlot3D *astInitPlot3D_( void *, size_t, int, AstPlot3DVtab *,
+                           const char *, AstFrame *, const float *,
+                           const double *, int * );
+
+/* Vtab initialiser. */
+void astInitPlot3DVtab_( AstPlot3DVtab *, const char *, int * );
+
+/* Loader. */
+AstPlot3D *astLoadPlot3D_( void *, size_t,
+                                         AstPlot3DVtab *,
+                                         const char *, AstChannel *channel, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitPlot3DGlobals_( AstPlot3DGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+#if defined(astCLASS)            /* Protected */
+
+   int astGetRootCorner_( AstPlot3D *, int * );
+   int astTestRootCorner_( AstPlot3D *, int * );
+   void astSetRootCorner_( AstPlot3D *, int, int * );
+   void astClearRootCorner_( AstPlot3D *, int * );
+
+   double astGetNorm_( AstPlot3D *, int, int * );
+   int astTestNorm_( AstPlot3D *, int, int * );
+   void astSetNorm_( AstPlot3D *, int, double, int * );
+   void astClearNorm_( AstPlot3D *, int, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckPlot3D(this) astINVOKE_CHECK(Plot3D,this,0)
+#define astVerifyPlot3D(this) astINVOKE_CHECK(Plot3D,this,1)
+
+/* Test class membership. */
+#define astIsAPlot3D(this) astINVOKE_ISA(Plot3D,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+#define astPlot3D astINVOKE(F,astPlot3D_)
+#else
+#define astPlot3D astINVOKE(F,astPlot3DId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitPlot3D(mem,size,init,vtab,name,frame,graph,base) \
+astINVOKE(O,astInitPlot3D_(mem,size,init,vtab,name,frame,graph,base,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitPlot3DVtab(vtab,name) astINVOKE(V,astInitPlot3DVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadPlot3D(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadPlot3D_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+
+/* Interfaces to protected member functions. */
+/* ----------------------------------------- */
+/* Here we make use of astCheckPlot3D to validate Plot3D pointers
+   before use. This provides a contextual error report if a pointer to
+   the wrong sort of object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+
+#define astGetRootCorner(this) astINVOKE(V,astGetRootCorner_(astCheckPlot3D(this),STATUS_PTR))
+#define astTestRootCorner(this) astINVOKE(V,astTestRootCorner_(astCheckPlot3D(this),STATUS_PTR))
+#define astClearRootCorner(this) astINVOKE(V,astClearRootCorner_(astCheckPlot3D(this),STATUS_PTR))
+#define astSetRootCorner(this,value) astINVOKE(V,astSetRootCorner_(astCheckPlot3D(this),value,STATUS_PTR))
+
+#define astGetNorm(this,axis) astINVOKE(V,astGetNorm_(astCheckPlot3D(this),axis,STATUS_PTR))
+#define astTestNorm(this,axis) astINVOKE(V,astTestNorm_(astCheckPlot3D(this),axis,STATUS_PTR))
+#define astClearNorm(this,axis) astINVOKE(V,astClearNorm_(astCheckPlot3D(this),axis,STATUS_PTR))
+#define astSetNorm(this,axis,value) astINVOKE(V,astSetNorm_(astCheckPlot3D(this),axis,value,STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/pointlist.c b/ast/pointlist.c
new file mode 100644
index 0000000..61117c2
--- /dev/null
+++ b/ast/pointlist.c
@@ -0,0 +1,3407 @@
+/*
+*class++
+*  Name:
+*     PointList
+
+*  Purpose:
+*     A collection of points in a Frame.
+
+*  Constructor Function:
+c     astPointList
+f     AST_POINTLIST
+
+*  Description:
+*     The PointList class implements a Region which represents a collection
+*     of points in a Frame.
+
+*  Inheritance:
+*     The PointList class inherits from the Region class.
+
+*  Attributes:
+*     In addition to those attributes common to all Regions, every
+*     PointList also has the following attributes:
+*
+*     - ListSize: The number of positions stored in the PointList
+
+
+*  Functions:
+c     The PointList class does not define any new functions beyond those
+f     The PointList class does not define any new routines beyond those
+*     which are applicable to all Regions.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-MAR-2004 (DSB):
+*        Original version.
+*     20-JAN-2009 (DSB):
+*        Over-ride astRegBasePick.
+*     21-JAN-2009 (DSB):
+*        - Add methods astGetEnclosure, astSetEnclosure and astPoints, and
+*        attribute ListSize.
+*        - Override astGetObjSize and astEqual.
+*     26-JAN-2009 (DSB):
+*        Change protected constructor to accept a PointSet rather than an
+*        array of doubles.
+*     6-FEB-2009 (DSB):
+*        Over-ride astMapMerge.
+*     9-FEB-2009 (DSB):
+*        Move methods astGetEnclosure and astSetEnclosure to Region class.
+*     8-JUL-2009 (DSB):
+*        In Transform, use "ptr2", not "ptr", if we are creating a mask.
+*class--
+
+*  Implementation Deficiencies:
+*     - Use of simple arrays to hold lists  of points is probably not
+*     efficient for large numbers of points. For instance, use of k-tree
+*     structures instead of arrays could result in a much  more efficient
+*     implementation of the Transform function. Maybe the PointSet class
+*     should be extended to provide a k-tree representation as well as a
+*     simple array.
+
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS PointList
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "region.h"              /* Coordinate regions (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "pointlist.h"           /* Interface definition for this class */
+#include "mapping.h"             /* Position mappings */
+#include "unitmap.h"             /* Unit Mapping */
+#include "frame.h"               /* Coordinate systems */
+#include "cmpframe.h"            /* Compound Frames */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "prism.h"               /* Extruded Regions */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstMapping *(* parent_simplify)( AstMapping *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(PointList)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(PointList,Class_Init)
+#define class_vtab astGLOBAL(PointList,Class_Vtab)
+#define getattrib_buff astGLOBAL(PointList,GetAttrib_Buff)
+
+
+#include <pthread.h>
+
+
+#else
+
+static char getattrib_buff[ 101 ];
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstPointListVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstPointList *astPointListId_( void *, int, int, int, const double *, void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+static int MaskLD( AstRegion *, AstMapping *, int, int, const int[], const int ubnd[], long double [], long double, int * );
+#endif
+static int MaskB( AstRegion *, AstMapping *, int, int, const int[], const int[], signed char[], signed char, int * );
+static int MaskD( AstRegion *, AstMapping *, int, int, const int[], const int[], double[], double, int * );
+static int MaskF( AstRegion *, AstMapping *, int, int, const int[], const int[], float[], float, int * );
+static int MaskI( AstRegion *, AstMapping *, int, int, const int[], const int[], int[], int, int * );
+static int MaskL( AstRegion *, AstMapping *, int, int, const int[], const int[], long int[], long int, int * );
+static int MaskS( AstRegion *, AstMapping *, int, int, const int[], const int[], short int[], short int, int * );
+static int MaskUB( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned char[], unsigned char, int * );
+static int MaskUI( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned int[], unsigned int, int * );
+static int MaskUL( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned long int[], unsigned long int, int * );
+static int MaskUS( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned short int[], unsigned short int, int * );
+
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstRegion *RegBasePick( AstRegion *, int, const int *, int * );
+static int GetClosed( AstRegion *, int * );
+static int GetListSize( AstPointList *, int * );
+static int GetObjSize( AstObject *, int * );
+static int RegPins( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void PointListPoints( AstPointList *, AstPointSet **, int *);
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void RegBaseBox( AstRegion *, double *, double *, int * );
+static AstRegion *MergePointList( AstPointList *, AstRegion *, int, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+
+/* Member functions. */
+/* ================= */
+
+static void ClearAttrib( AstObject *this_object, const char *attrib,
+                         int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a PointList.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PointList member function (over-rides the astClearAttrib
+*     protected method inherited from the Object class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     PointList, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPointList *this;            /* Pointer to the PointList structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PointList structure. */
+   this = (AstPointList *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Test if the name matches any of the read-only attributes of this
+   class. If it does, then report an error. */
+   if ( !strcmp( attrib, "listsize" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib,
+                              int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a PointList.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PointList member function (over-rides the protected astGetAttrib
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a PointList, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the PointList, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the PointList. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPointList *this;           /* Pointer to the PointList structure */
+   const char *result;           /* Pointer value to return */
+   int ival;                     /* Integer attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the PointList structure. */
+   this = (AstPointList *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an
+   appropriate format.  Set "result" to point at the result string. */
+
+/* ListSize. */
+/* -------- */
+   if ( !strcmp( attrib, "listsize" ) ) {
+      ival = astGetListSize( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetClosed( AstRegion *this, int *status ) {
+/*
+*  Name:
+*     GetClosed
+
+*  Purpose:
+*     Get the value of the CLosed attribute for a PointList.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     int GetClosed( AstRegion *this, int *status )
+
+*  Class Membership:
+*     PointList member function (over-rides the astGetClosed method
+*     inherited from the Region class).
+
+*  Description:
+*     This function returns the value of the Closed attribute for a
+*     PointList. Since points have zero volume they consist entirely of
+*     boundary. Therefore the Region is always considered to be closed
+*     unless it has been negated, in which case it is always assumed to
+*     be open.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The value to use for the Closed attribute.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* The value to be used for the Closed attribute is always the opposite
+   of the Negated attribute. */
+   return ( astGetNegated( this ) == 0 );
+}
+
+static int GetListSize( AstPointList *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetListSize
+
+*  Purpose:
+*     Determine how many points there are in a PointList.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     int astGetListSize( AstPointList *this )
+
+*  Class Membership:
+*     PointList method.
+
+*  Description:
+*     This function returns the number of points stored in a Point|List.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList.
+
+*  Returned Value:
+*     The number of points in the PointList.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Return the number of points by querying the PointSet that holds the
+   points. */
+   return astGetNpoint( ((AstRegion *) this)->points );
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     PointList member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied PointList,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPointList *this;         /* Pointer to PointList structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the PointList structure. */
+   this = (AstPointList *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by this class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astGetObjSize( this->lbnd );
+   result += astGetObjSize( this->ubnd );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+void astInitPointListVtab_(  AstPointListVtab *vtab, const char *name,
+                             int *status ) {
+/*
+*+
+*  Name:
+*     astInitPointListVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a PointList.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     void astInitPointListVtab( AstPointListVtab *vtab, const char *name )
+
+*  Class Membership:
+*     PointList vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the PointList class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstRegionVtab *region;        /* Pointer to Region component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitRegionVtab( (AstRegionVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAPointList) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstRegionVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->GetListSize = GetListSize;
+   vtab->PointListPoints = PointListPoints;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   region = (AstRegionVtab *) vtab;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_simplify = mapping->Simplify;
+   mapping->Simplify = Simplify;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   mapping->MapMerge = MapMerge;
+
+   region->RegBaseMesh = RegBaseMesh;
+   region->RegBaseBox = RegBaseBox;
+   region->RegBasePick = RegBasePick;
+   region->RegPins = RegPins;
+   region->GetClosed = GetClosed;
+   region->MaskB = MaskB;
+   region->MaskD = MaskD;
+   region->MaskF = MaskF;
+   region->MaskI = MaskI;
+   region->MaskL = MaskL;
+   region->MaskS = MaskS;
+   region->MaskUB = MaskUB;
+   region->MaskUI = MaskUI;
+   region->MaskUL = MaskUL;
+   region->MaskUS = MaskUS;
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+   region->MaskLD = MaskLD;
+#endif
+
+/* Declare the class dump function. There is no copy constructor or
+   destructor. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "PointList", "Collection of points" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+/*
+*  Name:
+*     Mask<X>
+
+*  Purpose:
+*     Mask a region of a data grid.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     int Mask<X>( AstRegion *this, AstMapping *map, int inside, int ndim,
+*                  const int lbnd[], const int ubnd[], <Xtype> in[],
+*                  <Xtype> val )
+
+*  Class Membership:
+*     PointList function method (replaces the astMask<X> methods
+*     inherited from the parent Region class).
+
+*  Description:
+*     This is a set of functions for masking out regions within gridded data
+*     (e.g. an image). The functions modifies a given data grid by
+*     assigning a specified value to all samples which are inside (or outside
+*     if "inside" is zero) the specified Region.
+*
+*     You should use a masking function which matches the numerical
+*     type of the data you are processing by replacing <X> in
+*     the generic function name astMask<X> by an appropriate 1- or
+*     2-character type code. For example, if you are masking data
+*     with type "float", you should use the function astMaskF (see
+*     the "Data Type Codes" section below for the codes appropriate to
+*     other numerical types).
+
+*  Parameters:
+*     this
+*        Pointer to a Region.
+*     map
+*        Pointer to a Mapping. The forward transformation should map
+*        positions in the coordinate system of the supplied Region
+*        into pixel coordinates as defined by the "lbnd" and "ubnd"
+*        parameters. A NULL pointer can be supplied if the coordinate
+*        system of the supplied Region corresponds to pixel coordinates.
+*        This is equivalent to supplying a UnitMap.
+*
+*        The number of inputs for this Mapping (as given by its Nin attribute)
+*        should match the number of axes in the supplied Region (as given
+*        by the Naxes attribute of the Region). The number of outputs for the
+*        Mapping (as given by its Nout attribute) should match the number of
+*        grid dimensions given by the value of "ndim" below.
+*     inside
+*        A boolean value which indicates which pixel are to be masked. If
+*        a non-zero value is supplied, then all grid pixels which are inside
+*        the supplied Region are assigned the value given by "val",
+*        and all other pixels are left unchanged. If zero is supplied, then
+*        all grid pixels which are not inside the supplied Region are
+*        assigned the value given by "val", and all other pixels are left
+*        unchanged. Note, the Negated attribute of the Region is used to
+*        determine which pixel are inside the Region and which are outside.
+*        So the inside of a Region which has not been negated is the same as
+*        the outside of the corresponding negated Region.
+*     ndim
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+*     lbnd
+*        Pointer to an array of integers, with "ndim" elements,
+*        containing the coordinates of the centre of the first pixel
+*        in the input grid along each dimension.
+*     ubnd
+*        Pointer to an array of integers, with "ndim" elements,
+*        containing the coordinates of the centre of the last pixel in
+*        the input grid along each dimension.
+*
+*        Note that "lbnd" and "ubnd" together define the shape
+*        and size of the input grid, its extent along a particular
+*        (j'th) dimension being ubnd[j]-lbnd[j]+1 (assuming the
+*        index "j" to be zero-based). They also define
+*        the input grid's coordinate system, each pixel having unit
+*        extent along each dimension with integral coordinate values
+*        at its centre.
+*     in
+*        Pointer to an array, with one element for each pixel in the
+*        input grid, containing the data to be masked.  The
+*        numerical type of this array should match the 1- or
+*        2-character type code appended to the function name (e.g. if
+*        you are using astMaskF, the type of each array element
+*        should be "float").
+*
+*        The storage order of data within this array should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+*        (i.e. Fortran array indexing is used).
+*
+*        On exit, the samples specified by "inside" are set to the value
+*        of "val". All other samples are left unchanged.
+*     val
+*        This argument should have the same type as the elements of
+*        the "in" array. It specifies the value used to flag the
+*        masked data (see "inside").
+
+*  Returned Value:
+*     The number of pixels to which a value of "badval" has been assigned.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+
+*  Data Type Codes:
+*     To select the appropriate masking function, you should
+*     replace <X> in the generic function name astMask<X> with a
+*     1- or 2-character data type code, so as to match the numerical
+*     type <Xtype> of the data you are processing, as follows:
+*     - D: double
+*     - F: float
+*     - L: long int
+*     - UL: unsigned long int
+*     - I: int
+*     - UI: unsigned int
+*     - S: short int
+*     - US: unsigned short int
+*     - B: byte (signed char)
+*     - UB: unsigned byte (unsigned char)
+*
+*     For example, astMaskD would be used to process "double"
+*     data, while astMaskS would be used to process "short int"
+*     data, etc.
+*/
+/* Define a macro to implement the function for a specific data
+   type. */
+#define MAKE_MASK(X,Xtype) \
+static int Mask##X( AstRegion *this, AstMapping *map, int inside, int ndim, \
+                    const int lbnd[], const int ubnd[], \
+                    Xtype in[], Xtype val, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *grid_frame;         /* Pointer to Frame describing grid coords */ \
+   AstPointSet *pset1;           /* Pointer to base Frame positions */ \
+   AstPointSet *pset2;           /* Pointer to current Frame positions */ \
+   AstRegion *used_region;       /* Pointer to Region to be used by astResample */ \
+   Xtype *temp;                  /* Pointer to temp storage for retained points */ \
+   double **ptr2;                /* Pointer to pset2 data values */ \
+   int *iv;                      /* Pointer to index array */ \
+   int i;                        /* Point index */ \
+   int idim;                     /* Loop counter for coordinate dimensions */ \
+   int ii;                       /* Vectorised point index */ \
+   int j;                        /* Axis index */ \
+   int nax;                      /* Number of Region axes */ \
+   int negated;                  /* Has Region been negated? */ \
+   int nin;                      /* Number of Mapping input coordinates */ \
+   int nout;                     /* Number of Mapping output coordinates */ \
+   int npnt;                     /* Number of points in PointList */ \
+   int result;                   /* Result value to return */ \
+   int vlen;                     /* Length of vectorised array */ \
+\
+/* Initialise. */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Obtain value for the Naxes attribute of the Region. */ \
+   nax = astGetNaxes( this ); \
+\
+/* If supplied, obtain values for the Nin and Nout attributes of the Mapping. */ \
+   if( map ) { \
+      nin = astGetNin( map ); \
+      nout = astGetNout( map ); \
+\
+/* If OK, check that the number of mapping inputs matches the \
+   number of axes in the Region. Report an error if necessary. */ \
+      if ( astOK && ( nax != nin ) ) { \
+         astError( AST__NGDIN, "astMask"#X"(%s): Bad number of mapping " \
+                   "inputs (%d).", status, astGetClass( this ), nin ); \
+         astError( AST__NGDIN, "The %s given requires %d coordinate value%s " \
+                   "to specify a position.", status, \
+                   astGetClass( this ), nax, ( nax == 1 ) ? "" : "s" ); \
+      } \
+\
+/* If OK, check that the number of mapping outputs matches the \
+   number of grid dimensions. Report an error if necessary. */ \
+      if ( astOK && ( ndim != nout ) ) { \
+         astError( AST__NGDIN, "astMask"#X"(%s): Bad number of mapping " \
+                   "outputs (%d).", status, astGetClass( this ), nout ); \
+         astError( AST__NGDIN, "The pixel grid requires %d coordinate value%s " \
+                   "to specify a position.", status, \
+                   ndim, ( ndim == 1 ) ? "" : "s" ); \
+      } \
+\
+/* Create a new Region by mapping the supplied Region with the supplied \
+   Mapping.*/ \
+      grid_frame = astFrame( ndim, "Domain=grid", status ); \
+      used_region = astMapRegion( this, map, grid_frame ); \
+      grid_frame = astAnnul( grid_frame ); \
+\
+/* If no Mapping was supplied check that the number of grid dimensions \
+   matches the number of axes in the Region.*/ \
+   } else if ( astOK && ( ( ndim != nax ) || ( ndim < 1 ) ) ) { \
+      used_region = NULL; \
+      astError( AST__NGDIN, "astMask"#X"(%s): Bad number of input grid " \
+                "dimensions (%d).", status, astGetClass( this ), ndim ); \
+      if ( ndim != nax ) { \
+         astError( AST__NGDIN, "The %s given requires %d coordinate value%s " \
+                   "to specify an input position.", status, \
+                   astGetClass( this ), nax, ( nax == 1 ) ? "" : "s" ); \
+      } \
+\
+/* If no Mapping was supplied and the parameters look OK, clone the \
+   supplied Region pointer for use later on. */  \
+   } else { \
+      used_region = astClone( this );  \
+   }  \
+\
+/* Check that the lower and upper bounds of the input grid are \
+   consistent. Report an error if any pair is not. */ \
+   if ( astOK ) { \
+      for ( idim = 0; idim < ndim; idim++ ) { \
+         if ( lbnd[ idim ] > ubnd[ idim ] ) { \
+            astError( AST__GBDIN, "astMask"#X"(%s): Lower bound of " \
+                      "input grid (%d) exceeds corresponding upper bound " \
+                      "(%d).", status, astGetClass( this ), \
+                      lbnd[ idim ], ubnd[ idim ] ); \
+            astError( AST__GBDIN, "Error in input dimension %d.", status, \
+                      idim + 1 ); \
+            break; \
+         } \
+      } \
+   } \
+\
+/* Get the PointSet in the base Frame of the Region's FrameSet, and \
+   transform to the current (GRID) Frame. */ \
+   pset1 = used_region->points; \
+   pset2 = astRegTransform( used_region, pset1, 1, NULL, NULL ); \
+   ptr2 =astGetPoints( pset2 ); \
+\
+/* Allocate memory to hold the corresponding vector indices. */ \
+   npnt = astGetNpoint( pset2 ); \
+   iv = astMalloc( sizeof(int)*(size_t) npnt ); \
+   if( astOK ) { \
+\
+/* Convert the transformed GRID positions into integer indices into the \
+   vectorised data array. Also form the total size of the data array. */ \
+      vlen = 0; \
+      for( i = 0; i < npnt; i++ ) { \
+         vlen = 1; \
+         ii = 0; \
+         for( j = 0; j < ndim; j++ ) { \
+            ii += vlen*( (int)( ptr2[ j ][ i ] + 0.5 ) - lbnd[ j ] ); \
+            vlen *= ubnd[ i ] - lbnd[ i ] + 1; \
+         }  \
+         iv[ i ] = ii; \
+      } \
+\
+/* See if the Region is negated. */ \
+      negated = astGetNegated( used_region ); \
+\
+/* If necessary, set the transformed pixel coords to the supplied value. */ \
+      if( ( inside && !negated ) || ( !inside && negated ) ) { \
+         for( i = 0; i < npnt; i++ ) in[ iv[ i ] ] = val; \
+         result = npnt; \
+\
+/* If necessary, set all except the transformed pixel coords to the supplied  \
+   value. */ \
+      } else { \
+         temp = astMalloc( sizeof( Xtype )*(size_t)npnt ); \
+         if( astOK ) { \
+            for( i = 0; i < npnt; i++ ) temp[ i ] = in[ iv[ i ] ]; \
+            for( i = 0; i < vlen; i++ ) in[ i ] = val; \
+            for( i = 0; i < npnt; i++ ) in[ iv[ i ] ] = temp[ i ]; \
+            result = vlen - npnt; \
+         } \
+         temp = astFree( temp ); \
+      } \
+   } \
+\
+/* Free resources */ \
+   iv = astFree( iv ); \
+   pset2 = astAnnul( pset2 ); \
+   used_region = astAnnul( used_region ); \
+\
+/* If an error occurred, clear the returned result. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_MASK(LD,long double)
+#endif
+MAKE_MASK(D,double)
+MAKE_MASK(F,float)
+MAKE_MASK(L,long int)
+MAKE_MASK(UL,unsigned long int)
+MAKE_MASK(I,int)
+MAKE_MASK(UI,unsigned int)
+MAKE_MASK(S,short int)
+MAKE_MASK(US,unsigned short int)
+MAKE_MASK(B,signed char)
+MAKE_MASK(UB,unsigned char)
+
+/* Undefine the macro. */
+#undef MAKE_MASK
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a PointList.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     PointList method (over-rides the protected astMapMerge method
+*     inherited from the Region class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated PointList in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated PointList with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated PointList which is to be merged with
+*        its neighbours. This should be a cloned copy of the PointList
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        PointList it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated PointList resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstPointList *oldint;  /* Pointer to supplied PointList */
+   AstMapping *map;      /* Pointer to adjacent Mapping */
+   AstMapping *new;      /* Simplified or merged Region */
+   int i1;               /* Index of first Mapping merged */
+   int i;                /* Loop counter */
+   int result;           /* Result value to return */
+
+/* Initialise. */
+   result = -1;
+   i1 = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the PointList. */
+   oldint = (AstPointList *) this;
+
+/* First of all, see if the PointList can be replaced by a simpler Region,
+   without reference to the neighbouring Regions in the list.           */
+/* =====================================================================*/
+
+/* Try to simplify the PointList. If the pointer value has changed, we assume
+   some simplification took place. */
+   new = astSimplify( oldint );
+   if( new != (AstMapping *) oldint ) {
+
+/* Annul the PointList pointer in the list and replace it with the new Region
+   pointer, and indicate that the forward transformation of the returned
+   Region should be used (not really needed but keeps things clean). */
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = new;
+      ( *invert_list )[ where ] = 0;
+
+/* Return the index of the first modified element. */
+      result = where;
+
+/* If the PointList itself could not be simplified, see if it can be merged
+   with the Regions on either side of it in the list. We can only merge
+   in parallel. */
+/* =====================================================================*/
+   } else if( ! series ){
+      new = astAnnul( new );
+
+/* Attempt to merge the PointList with its lower neighbour (if any). */
+      if( where > 0 ) {
+         i1 = where - 1;
+         map = ( *map_list )[ where - 1 ];
+         if( astIsARegion( map ) ) {
+            new = (AstMapping *) MergePointList( oldint, (AstRegion *) map,
+                                                  0, status );
+         }
+      }
+
+/* If this did not produced a merged Region, attempt to merge the PointList
+   with its upper neighbour (if any). */
+      if( !new && where < *nmap - 1 ) {
+         i1 = where;
+         map = ( *map_list )[ where + 1 ];
+         if( astIsARegion( map ) ) {
+            new = (AstMapping *) MergePointList( oldint, (AstRegion *) map,
+                                                  1, status );
+         }
+      }
+
+/* If succesfull... */
+      if( new ){
+
+/* Annul the first of the two Mappings, and replace it with the merged
+   Region. Also clear the invert flag. */
+         (void) astAnnul( ( *map_list )[ i1 ] );
+         ( *map_list )[ i1 ] = new;
+         ( *invert_list )[ i1 ] = 0;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+         (void) astAnnul( ( *map_list )[ i1 + 1 ] );
+         for ( i = i1 + 2; i < *nmap; i++ ) {
+            ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+            ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+         }
+
+/* Clear the vacated element at the end. */
+         ( *map_list )[ *nmap - 1 ] = NULL;
+         ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+         ( *nmap )--;
+         result = i1;
+      }
+
+   } else {
+      new = astAnnul( new );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstRegion *MergePointList( AstPointList *this, AstRegion *reg,
+                                   int plsfirst, int *status ) {
+/*
+*  Name:
+*     MergePointList
+
+*  Purpose:
+*     Attempt to merge a PointList with another Region to form a Region of
+*     higher dimensionality.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     AstRegion *MergePointList( AstPointList *this, AstRegion *reg,
+*                                 int plsfirst, int *status )
+
+*  Class Membership:
+*     PointList member function.
+
+*  Description:
+*     This function attempts to combine the supplied Regions together
+*     into a Region of higher dimensionality.
+
+*  Parameters:
+*     this
+*        Pointer to a PointList.
+*     reg
+*        Pointer to another Region.
+*     plsfirst
+*        If non-zero, then the PointList axes are put first in the new Region.
+*        Otherwise, the other Region's axes are put first.
+*     status
+*        Pointer to the inherited status value.
+
+*  Returned Value:
+*     A pointer to a new region, or NULL if the supplied Regions could
+*     not be merged.
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;           /* Pointer to base Frame for "result" */
+   AstFrame *cfrm;           /* Pointer to current Frame for "result" */
+   AstFrame *frm_reg;        /* Pointer to Frame from "reg" */
+   AstFrame *frm_this;       /* Pointer to Frame from "this" */
+   AstMapping *bcmap;        /* Base->current Mapping for "result" */
+   AstMapping *map_reg;      /* Base->current Mapping from "reg" */
+   AstMapping *map_this;     /* Base->current Mapping from "this" */
+   AstMapping *sbunc;        /* Simplified uncertainty */
+   AstPointSet *pset_new;    /* PointSet for new PointList */
+   AstPointSet *pset_reg;    /* PointSet from reg */
+   AstPointSet *pset_this;   /* PointSet from this */
+   AstRegion *bunc;          /* Base Frame uncertainty Region */
+   AstRegion *new;           /* Pointer to new PointList in base Frame */
+   AstRegion *result;        /* Pointer to returned PointList in current Frame */
+   AstRegion *unc_reg;       /* Current Frame uncertainty Region from "reg" */
+   AstRegion *unc_this;      /* Current Frame uncertainty Region from "this" */
+   double **ptr_new;         /* PointSet data pointers for new PointList */
+   double **ptr_reg;         /* PointSet data pointers for reg */
+   double **ptr_this;        /* PointSet data pointers for this */
+   double fac_reg;           /* Ratio of used to default MeshSize for "reg" */
+   double fac_this;          /* Ratio of used to default MeshSize for "this" */
+   int i;                    /* Axis index */
+   int msz_reg;              /* Original MeshSize for "reg" */
+   int msz_reg_set;          /* Was MeshSize originally set for "reg"? */
+   int msz_this;             /* Original MeshSize for "this" */
+   int msz_this_set;         /* Was MeshSize originally set for "this"? */
+   int nax;                  /* Number of axes in "result" */
+   int nax_reg;              /* Number of axes in "reg" */
+   int nax_this;             /* Number of axes in "this" */
+   int neg_reg;              /* Negated attribute value for other supplied Region */
+   int neg_this;             /* Negated attribute value for supplied PointList  */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get the Closed attributes of the two Regions. They must be the same in
+   each Region if we are to merge the Regions. In addition, in order to
+   merge, either both Regions must have a defined uncertainty, or neither
+   Region must have a defined Uncertainty. */
+   if( astGetClosed( this ) == astGetClosed( reg ) &&
+       astTestUnc( this ) == astTestUnc( reg ) ) {
+
+/* Get the Nagated attributes of the two Regions. */
+      neg_this = astGetNegated( this );
+      neg_reg = astGetNegated( reg );
+
+/* We only check for merging with another PointList (other classes such
+   as Box and Interval check for merging of PointLists with other classes).
+   The result will be an PointList. Both Regions must have the same value
+   for the Negated flag, and can contain only a single point. */
+      if( astIsAPointList( reg ) && neg_this == neg_reg &&
+          astGetListSize( this ) == 1 &&
+          astGetListSize( (AstPointList *) reg ) == 1 ) {
+
+/* Get the number of axes in the two supplied Regions. */
+         nax_reg = astGetNaxes( reg );
+         nax_this = astGetNaxes( this );
+
+/* Get the number of axes the combination will have. */
+         nax = nax_reg + nax_this;
+
+/* Get the base Frames from the two Region FrameSets, and combine them
+   into a single CmpFrame that will be used to create the new Region. */
+         frm_this = astGetFrame( ((AstRegion *) this)->frameset, AST__BASE );
+         frm_reg = astGetFrame( reg->frameset, AST__BASE );
+
+         if( plsfirst ) {
+            bfrm = (AstFrame *) astCmpFrame( frm_this, frm_reg, "", status );
+         } else {
+            bfrm = (AstFrame *) astCmpFrame( frm_reg, frm_this, "", status );
+         }
+
+         frm_this = astAnnul( frm_this );
+         frm_reg = astAnnul( frm_reg );
+
+/* Get pointers to the coordinate data in the two PointLists */
+         pset_this = ((AstRegion *) this)->points;
+         ptr_this = astGetPoints( pset_this );
+         pset_reg = reg->points;
+         ptr_reg = astGetPoints( pset_reg );
+
+/* Create a PointSet to hold the points for the returned PointList. */
+         pset_new = astPointSet( 1, nax, "", status );
+
+/* Get pointers to its data. */
+         ptr_new = astGetPoints( pset_new );
+
+/* Check pointers can be used safely. */
+         if( astOK ) {
+
+/* Copy the point positions fon the selected axes into the arrays allocated
+   above, in the requested order. */
+            if( plsfirst ) {
+               for( i = 0; i < nax_this; i++ ) {
+                  ptr_new[ i ][ 0 ] = ptr_this[ i ][ 0 ];
+               }
+               for( ; i < nax; i++ ) {
+                  ptr_new[ i ][ 0 ] = ptr_reg[ i - nax_this ][ 0 ];
+               }
+
+            } else {
+               for( i = 0; i < nax_reg; i++ ) {
+                  ptr_new[ i ][ 0 ] = ptr_reg[ i ][ 0 ];
+               }
+               for( ; i < nax; i++ ) {
+                  ptr_new[ i ][ 0 ] = ptr_this[ i - nax_reg ][ 0 ];
+               }
+            }
+
+/* Create the new PointList. */
+            new = (AstRegion *) astPointList( bfrm, pset_new, NULL, "",
+                                              status );
+
+/* Propagate remaining attributes of the supplied Region to it. */
+            astRegOverlay( new, this, 1 );
+
+/* Ensure the Negated flag is set correctly in the returned PointList. */
+            if( neg_this ) {
+               astSetNegated( new, neg_this );
+            } else {
+               astClearNegated( new );
+            }
+
+/* If both the supplied Regions have uncertainty, assign the new Region an
+   uncertainty. */
+            if( astTestUnc( this ) && astTestUnc( reg ) ) {
+
+/* Get the uncertainties from the two supplied Regions. */
+               unc_this = astGetUncFrm( this, AST__BASE );
+               unc_reg = astGetUncFrm( reg, AST__BASE );
+
+/* Combine them into a single Region (a Prism), in the correct order. */
+               if( plsfirst ) {
+                  bunc = (AstRegion *) astPrism( unc_this, unc_reg, "", status );
+               } else {
+                  bunc = (AstRegion *) astPrism( unc_reg, unc_this, "", status );
+               }
+
+/* Attempt to simplify the Prism. */
+               sbunc = astSimplify( bunc );
+
+/* Use the simplified Prism as the uncertainty for the returned Region. */
+               astSetUnc( new, sbunc );
+
+/* Free resources. */
+               sbunc = astAnnul( sbunc );
+               bunc = astAnnul( bunc );
+               unc_reg = astAnnul( unc_reg );
+               unc_this = astAnnul( unc_this );
+            }
+
+/* Get the current Frames from the two Region FrameSets, and combine them
+   into a single CmpFrame. */
+            frm_this = astGetFrame( ((AstRegion *) this)->frameset, AST__CURRENT );
+            frm_reg = astGetFrame( reg->frameset, AST__CURRENT );
+
+            if( plsfirst ) {
+               cfrm = (AstFrame *) astCmpFrame( frm_this, frm_reg, "", status );
+            } else {
+               cfrm = (AstFrame *) astCmpFrame( frm_reg, frm_this, "", status );
+            }
+
+/* Get the base -> current Mappings from the two Region FrameSets, and
+   combine them into a single parallel CmpMap that connects bfrm and cfrm. */
+            map_this = astGetMapping( ((AstRegion *) this)->frameset, AST__BASE,
+                                      AST__CURRENT );
+            map_reg = astGetMapping( reg->frameset, AST__BASE, AST__CURRENT );
+
+            if( plsfirst ) {
+               bcmap = (AstMapping *) astCmpMap( map_this, map_reg, 0, "",
+                                                 status );
+            } else {
+               bcmap = (AstMapping *) astCmpMap( map_reg, map_this, 0, "",
+                                                 status );
+            }
+
+/* Map the new Region into the new current Frame. */
+            result = astMapRegion( new, bcmap, cfrm );
+
+/* The filling factor in the returned is the product of the filling
+   factors for the two supplied Regions. */
+            if( astTestFillFactor( reg ) || astTestFillFactor( this ) ) {
+               astSetFillFactor( result, astGetFillFactor( reg )*
+                                         astGetFillFactor( this ) );
+            }
+
+/* If the MeshSize value is set in either supplied Region, set a value
+   for the returned Region which scales the default value by the
+   product of the scaling factors for the two supplied Regions. First see
+   if either MeshSize value is set. */
+            msz_this_set = astTestMeshSize( this );
+            msz_reg_set = astTestMeshSize( reg );
+            if( msz_this_set || msz_reg_set ) {
+
+/* If so, get the two MeshSize values (one of which may be a default
+   value), and then clear them so that the default value will be returned
+   in future. */
+               msz_this = astGetMeshSize( this );
+               msz_reg = astGetMeshSize( reg );
+               astClearMeshSize( this );
+               astClearMeshSize( reg );
+
+/* Get the ratio of the used MeshSize to the default MeshSize for both
+   Regions. */
+               fac_this = (double)msz_this/(double)astGetMeshSize( this );
+               fac_reg = (double)msz_reg/(double)astGetMeshSize( reg );
+
+/* The MeshSize of the returned Returned is the default value scaled by
+   the product of the two ratios found above. */
+               astSetMeshSize( result, fac_this*fac_reg*astGetMeshSize( result ) );
+
+/* Re-instate the original MeshSize values for the supplied Regions (if
+   set) */
+               if( msz_this_set ) astSetMeshSize( this, msz_this );
+               if( msz_reg_set ) astSetMeshSize( reg, msz_reg );
+            }
+
+/* Free remaining resources */
+            frm_this = astAnnul( frm_this );
+            frm_reg = astAnnul( frm_reg );
+            map_this = astAnnul( map_this );
+            map_reg = astAnnul( map_reg );
+            bcmap = astAnnul( bcmap );
+            cfrm = astAnnul( cfrm );
+            new = astAnnul( new );
+         }
+         bfrm = astAnnul( bfrm );
+         pset_new = astAnnul( pset_new );
+
+      }
+   }
+
+/* If an error has occurred, annul the returned pointer. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+void PointListPoints( AstPointList *this, AstPointSet **pset, int *status) {
+/*
+*+
+*  Name:
+*     astPointListPoints
+
+*  Purpose:
+*     Return the defining points of a PointList.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     astPointListPoints( AstPointList *this, AstPointSet **pset )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns the axis values at the points defining the
+*     supplied PointList.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList.
+*     pset
+*        Address of a location at which to return a pointer to a PointSet
+*        holding the points in the PointList, in the base Frame of the
+*        encapsulated FrameSet. The returned Pointer should be annulled
+*        when no longer needed.
+
+*-
+*/
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Return a clone of the PointSet holding the points defining the PointList. */
+   *pset = astClone( ((AstRegion *) this)->points );
+
+}
+
+static void RegBaseBox( AstRegion *this_region, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     RegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     void astRegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     PointList member function (over-rides the astRegBaseBox protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrame *frm;                /* Pointer to encapsulated Frame */
+   AstPointList *this;           /* Pointer to PointList structure */
+   AstPointSet *pset;            /* Pointer to PointSet defining the Region */
+   double **ptr;                 /* Pointer to PointSet data */
+   double *p;                    /* Pointer to next axis value */
+   double *lb;                   /* Pointer to lower limit array */
+   double *ub;                   /* Pointer to upper limit array */
+   double d;                     /* Axis offset from refernce value */
+   double p0;                    /* Reference axis value */
+   int ic;                       /* Axis index */
+   int ip;                       /* Point index */
+   int nb;                       /* Number of bytes to be copied */
+   int nc;                       /* No. of axes in base Frame */
+   int np;                       /* No. of points in PointSet */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the PointList structure. */
+   this = (AstPointList *) this_region;
+
+/* Calculate the number of bytes in each array. */
+   nb = sizeof( double )*(size_t) astGetNaxes( this );
+
+/* If the base Frame bounding box has not yet been found, find it now and
+   store it in the PointList structure. */
+   if( !this->lbnd || !this->ubnd ) {
+
+/* Allocate memory to store the bounding box in the PointList structure. */
+      lb = astMalloc( nb );
+      ub = astMalloc( nb );
+
+/* Get the axis values for the PointSet which defines the location and
+   extent of the region in the base Frame of the encapsulated FrameSet. */
+      pset = this_region->points;
+      ptr = astGetPoints( pset );
+      nc = astGetNcoord( pset );
+      np = astGetNpoint( pset );
+
+/* Get a pointer to the base Frame in the encaposulated FrameSet. */
+      frm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Check pointers can be used safely. */
+      if( astOK ) {
+
+/* Find the bounds on each axis in turn. */
+         for( ic = 0; ic < nc; ic++ ) {
+
+/* We first find the max and min axis offsets from the first point. We
+   used astAxDistance to cater for the possbility that the Frame may be a
+   SkyFrame and thus have circular redundancy. */
+            p = ptr[ ic ] + 1;
+            p0 = p[ -1 ];
+            lb[ ic ] = 0.0;
+            ub[ ic ] = 0.0;
+            for( ip = 1; ip < np; ip++, p++ ) {
+               d = astAxDistance( frm, ic + 1, p0, *p );
+               if( d < lb[ ic ] ) lb[ ic ] = d;
+               if( d > ub[ ic ] ) ub[ ic ] = d;
+            }
+
+/* Now convert these offsets to actual axis values. */
+            lb[ ic ] = astAxOffset( frm, ic + 1, p0, lb[ ic ] );
+            ub[ ic ] = astAxOffset( frm, ic + 1, p0, ub[ ic ] );
+
+         }
+      }
+
+/* Free resources */
+      frm = astAnnul( frm );
+
+/* Store the pointers in the PointList structure. */
+      if( astOK ) {
+         this->lbnd = lb;
+         this->ubnd = ub;
+      } else {
+         this->lbnd = astFree( this->lbnd );
+         this->ubnd = astFree( this->ubnd );
+      }
+   }
+
+/* If the bounding box has been found succesfully, copy it into the supplied
+   arrays. */
+   if( astOK ) {
+      memcpy( lbnd, this->lbnd, nb );
+      memcpy( ubnd, this->ubnd, nb );
+   }
+
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this, int *status ){
+/*
+*  Name:
+*     RegBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing a mesh of points on the boundary of a
+*     Region in its base Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     AstPointSet *astRegBaseMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     PointList member function (over-rides the astRegBaseMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. The axis values in this PointSet will have
+*     associated accuracies derived from the accuracies which were
+*     supplied when the Region was created.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *result;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* If the Region structure contains a pointer to a PointSet holding
+   a previously created mesh, return it. */
+   if( this->basemesh ) {
+      result = astClone( this->basemesh );
+
+/* Otherwise, create a new mesh. */
+   } else {
+
+/* It is just a copy of the encapsulated PointSet. */
+      result = astCopy( this->points );
+
+/* Same the returned pointer in the Region structure so that it does not
+   need to be created again next time this function is called. */
+      if( astOK && result ) this->basemesh = astClone( result );
+   }
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static AstRegion *RegBasePick( AstRegion *this_region, int naxes,
+                               const int *axes, int *status ){
+/*
+*  Name:
+*     RegBasePick
+
+*  Purpose:
+*     Return a Region formed by picking selected base Frame axes from the
+*     supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     AstRegion *RegBasePick( AstRegion *this, int naxes, const int *axes,
+*                             int *status )
+
+*  Class Membership:
+*     PointList member function (over-rides the astRegBasePick protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function attempts to return a Region that is spanned by selected
+*     axes from the base Frame of the encapsulated FrameSet of the supplied
+*     Region. This may or may not be possible, depending on the class of
+*     Region. If it is not possible a NULL pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     naxes
+*        The number of base Frame axes to select.
+*     axes
+*        An array holding the zero-based indices of the base Frame axes
+*        that are to be selected.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Region, or NULL if no region can be formed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;         /* The base Frame in the supplied Region */
+   AstFrame *frm;          /* The base Frame in the returned Region */
+   AstPointSet *pset;      /* Holds axis values defining the supplied Region */
+   AstPointSet *pset_new;  /* Holds axis values defining the returned Region */
+   AstRegion *bunc;        /* The uncertainty in the supplied Region */
+   AstRegion *result;      /* Returned Region */
+   AstRegion *unc;         /* The uncertainty in the returned Region */
+   double **ptr;           /* Holds axis values defining the supplied Region */
+   double **ptr_new;       /* Holds axis values defining the returned Region */
+   double *p;              /* Pointer to next input axis value */
+   double *q;              /* Pointer to next output axis value */
+   int i;                  /* Index of axis within returned Region */
+   int j;                  /* Point index */
+   int npnt;               /* Number of points in PointList */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the base Frame of the encapsulated FrameSet. */
+   bfrm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Create a Frame by picking the selected axes from the base Frame of the
+   encapsulated FrameSet. */
+   frm = astPickAxes( bfrm, naxes, axes, NULL );
+
+/* Get the uncertainty Region (if any) within the base Frame of the supplied
+   Region, and select the required axes from it. If the resulting Object
+   is not a Region, annul it so that the returned Region will have no
+   uncertainty. */
+   if( astTestUnc( this_region ) ) {
+      bunc = astGetUncFrm( this_region, AST__BASE );
+      unc = astPickAxes( bunc, naxes, axes, NULL );
+      bunc = astAnnul( bunc );
+
+      if( ! astIsARegion( unc ) ) unc = astAnnul( unc );
+
+   } else {
+      unc = NULL;
+   }
+
+/* Get pointers to the coordinate data in the parent Region structure. */
+   pset = this_region->points;
+   ptr = astGetPoints( pset );
+   npnt = astGetNpoint( pset );
+
+/* Create a PointSet to hold the points for the returned PointList. */
+   pset_new = astPointSet( npnt, naxes, "", status );
+
+/* Get pointers to its data. */
+   ptr_new = astGetPoints( pset_new );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Copy the point positions on the selected axes into the arrays allocated
+   above. */
+      for( i = 0; i < naxes; i++ ) {
+         p = ptr[ axes[ i ] ];
+         q = ptr_new[ i ];
+         for( j = 0; j < npnt; j++ ) *(q++) = *(p++);
+      }
+
+/* Create the new PointList. */
+      result = (AstRegion *) astPointList( frm, pset_new, unc, "", status );
+   }
+
+/* Free resources */
+   frm = astAnnul( frm );
+   bfrm = astAnnul( bfrm );
+   if( unc ) unc = astAnnul( unc );
+   pset_new = astAnnul( pset_new );
+
+/* Return a NULL pointer if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int RegPins( AstRegion *this_region, AstPointSet *pset, AstRegion *unc,
+                    int **mask, int *status ){
+/*
+*  Name:
+*     RegPins
+
+*  Purpose:
+*     Check if a set of points fall on the boundary of a given PointList.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+*                  int **mask, int *status )
+
+*  Class Membership:
+*     PointList member function (over-rides the astRegPins protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied set of
+*     points all fall on the boundary of the given PointList.
+*
+*     Some tolerance is allowed, as specified by the uncertainty Region
+*     stored in the supplied PointList "this", and the supplied uncertainty
+*     Region "unc" which describes the uncertainty of the supplied points.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList.
+*     pset
+*        Pointer to the PointSet. The points are assumed to refer to the
+*        base Frame of the FrameSet encapsulated by "this".
+*     unc
+*        Pointer to a Region representing the uncertainties in the points
+*        given by "pset". The Region is assumed to represent the base Frame
+*        of the FrameSet encapsulated by "this". Zero uncertainity is assumed
+*        if NULL is supplied.
+*     mask
+*        Pointer to location at which to return a pointer to a newly
+*        allocated dynamic array of ints. The number of elements in this
+*        array is equal to the value of the Npoint attribute of "this".
+*        Each element in the returned array is set to 1 if the
+*        corresponding position in "this" is on the boundary of the Region
+*        and is set to zero otherwise. A NULL value may be supplied
+*        in which case no array is created. If created, the array should
+*        be freed using astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the points all fall on the boundary of the given
+*     Region, to within the tolerance specified. Zero otherwise.
+
+*/
+
+/* Local variables: */
+   AstPointList *pl;            /* Pointer to PointList holding supplied points */
+   AstPointList *this;          /* Pointer to the PointList structure. */
+   AstPointSet *pset2;          /* Supplied points masked by this PointList */
+   AstPointSet *pset3;          /* This PointList masked by supplied points */
+   double **ptr2;               /* Pointer to axis values in "pset2" */
+   double **ptr3;               /* Pointer to axis values in "pset3" */
+   double **ptr;                /* Pointer to axis values in "this" */
+   double *p;                   /* Pointer to next axis value to read */
+   int ic;                      /* Axis index */
+   int icurr;                   /* Index of original current Frame in "this" */
+   int ip;                      /* Point index */
+   int nc;                      /* No. of axes in Box base frame */
+   int neg_old;                 /* Original Negated flag for "this" */
+   int np;                      /* No. of supplied points */
+   int result;                  /* Returned flag */
+
+/* Initialise */
+   result = 0;
+   if( mask ) *mask = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a pointer to the Box structure. */
+   this = (AstPointList *) this_region;
+
+/* Temporarily ensure that the current Frame in "this" is the same as the
+   base Frame. We need to do this since the supplied points are in the
+   base Frame of "this", but the astTransform method below assumes
+   that it is transforming points in the current Frame of the Region. */
+   icurr = astGetCurrent( this_region->frameset );
+   astSetCurrent( this_region->frameset, AST__BASE );
+
+/* Get pointer to the supplied axis values, the number of points and the
+   number of axis values per point. */
+   ptr = astGetPoints( pset );
+   np = astGetNpoint( pset );
+   nc = astGetNcoord( pset );
+
+/* All the supplied points should be within the supplied PointsList region
+   (given that "within" implies some tolerance). Transform the positions
+   using this PointList and check if any of the resulting points fell
+   outside this PointList. We need to ensure that the PointList is not
+   negated first. */
+   neg_old = astGetNegated( this );
+   astSetNegated( this, 0 );
+   pset2 = astTransform( this, pset, 1, NULL );
+   ptr2 = astGetPoints( pset2 );
+
+/* Check pointers can be used. */
+   if( astOK ) {
+
+/* Check there are no bad points (i.e. check that none of the points are
+   outside the supplied PointList). The algorithm used to do this depends
+   on whether we need to create an output mask. */
+      result = 1;
+      if( mask ) {
+
+/* Create the returned mask array. */
+         *mask = astMalloc( np );
+         if( astOK ) {
+
+/* Initialise the mask elements on the basis of the first axis values */
+            result = 1;
+            p = ptr2[ 0 ];
+            for( ip = 0; ip < np; ip++ ) {
+               if( *(p++) == AST__BAD ) {
+                  result = 0;
+                  (*mask)[ ip ] = 0;
+               } else {
+                  (*mask)[ ip ] = 1;
+               }
+            }
+
+/* Now check for bad values on other axes. */
+            for( ic = 1; ic < nc; ic++ ) {
+               p = ptr2[ ic ];
+               for( ip = 0; ip < np; ip++ ) {
+                  if( *(p++) == AST__BAD ) {
+                     result = 0;
+                     (*mask)[ ip ] = 0;
+                  }
+               }
+            }
+         }
+
+/* If no output mask is to be made, we can break out of the check as soon
+   as the first bad value is found. */
+      } else {
+         for( ic = 0; ic < nc && result; ic++ ){
+            p = ptr2[ ic ];
+            for( ip = 0; ip < np; ip++,p++ ){
+               if( *p == AST__BAD ) {
+                  result = 0;
+                  break;
+               }
+            }
+         }
+      }
+
+/* If this check was passed, we perform a similar check in the opposite
+   direction: we create a new PointList from the supplied list of points,
+   and then we transform the points associated with the supplied PointList
+   using the new PointList. This checks that all the points in the
+   supplied PointList are close to the supplied points. Create the new
+   PointList holding the supplied points. */
+      if( result ) {
+         pl = astPointList( unc, pset, unc, "", status );
+
+/* Transform the points in "this" PointList using the new PointList as a
+   Mapping. */
+         pset3 = astTransform( pl, this_region->points, 1, NULL );
+         ptr3 = astGetPoints( pset3 );
+
+/* Check pointers can be used. */
+         if( astOK ) {
+            for( ic = 0; ic < nc && result; ic++ ){
+               p = ptr3[ ic ];
+               for( ip = 0; ip < np; ip++,p++ ){
+                  if( *p == AST__BAD ) {
+                     result = 0;
+                     break;
+                  }
+               }
+            }
+         }
+
+/* Free resources. */
+         pl = astAnnul( pl );
+         pset3 = astAnnul( pset3 );
+
+      }
+   }
+
+   pset2 = astAnnul( pset2 );
+
+/* Re-instate the original current Frame in "this". */
+   astSetCurrent( this_region->frameset, icurr );
+
+/* Re-instate the original Negated flag for "this". */
+   astSetNegated( this, neg_old );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) {
+      result = 0;
+      if( mask ) *mask = astAnnul( *mask );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting,
+                       int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a PointList.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     PointList member function (over-rides the astSetAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function assigns an attribute value for a PointList, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList.
+*     setting
+*        Pointer to a null-terminated string specifying the new
+*        attribute value.
+*/
+
+/* Local Variables: */
+   AstPointList *this;            /* Pointer to the PointList structure */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PointList structure. */
+   this = (AstPointList *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* Use this macro to report an error if a read-only attribute has been
+   specified. */
+   if ( MATCH( "listsize" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.",
+                status, setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify the Mapping represented by a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     PointList method (over-rides the astSimplify method inherited
+*     from the Region class).
+
+*  Description:
+*     This function invokes the parent Region Simplify method, and then
+*     performs any further region-specific simplification.
+*
+*     If the Mapping from base to current Frame is not a UnitMap, this
+*     will include attempting to fit a new Region to the boundary defined
+*     in the current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the simplified Region. A cloned pointer to the
+*     supplied Region will be returned if no simplication could be
+*     performed.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to encapsulated Frame */
+   AstMapping *map;              /* Pointer to frameset Mapping */
+   AstMapping *result;           /* Result pointer to return */
+   AstPointList *new2;           /* Pointer to simplified Region */
+   AstPointSet *pset1;           /* Original base Frame positions */
+   AstPointSet *pset2;           /* Current Frame Frame positions */
+   AstRegion *new;               /* Pointer to simplified Region */
+   AstRegion *this;              /* Pointer to original Region structure */
+   AstRegion *unc;               /* Pointer to new uncertainty Region */
+   double **ptr2;                /* Pointer to current Frame points */
+   int simpler;                  /* Has some simplication taken place? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_mapping;
+
+/* Invoke the parent Simplify method inherited from the Region class. This
+   will simplify the encapsulated FrameSet and uncertainty Region. */
+   new = (AstRegion *) (*parent_simplify)( this_mapping, status );
+
+/* Note if any simplification took place. This is assumed to be the case
+   if the pointer returned by the above call is different to the supplied
+   pointer. */
+   simpler = ( new != this );
+
+/* Get the Mapping from base to current Frame. We can modify the PointList so
+   that a UnitMap can be used. This is good because it means that the
+   serialised PointList is simpler since the Dump function only needs to
+   record one Frame instead of the whole FrameSet. */
+   map = astGetMapping( new->frameset, AST__BASE, AST__CURRENT );
+   if( !astIsAUnitMap( map ) ){
+
+/* Get a pointer to the current Region Frame */
+      fr = astGetFrame( this->frameset, AST__CURRENT );
+
+/* Get the PointSet which holds the base Frame positions defining this
+   PointList. */
+      pset1 = this->points;
+
+/* Transform the PointSet using this Mapping. */
+      pset2 = astTransform( map, pset1, 1, NULL );
+      ptr2 = astGetPoints( pset2 );
+
+/* Get the Region describing the positional uncertainty within the
+   supplied PointList, in its current Frame. */
+      unc = astGetUncFrm( new, AST__CURRENT );
+
+/* Create a new PointList, and use it in place of the original. */
+      new2 = astPointList( fr, pset2, unc, "", status );
+      (void) astAnnul( new );
+      new = (AstRegion *) new2;
+      simpler = 1;
+
+/* Free resources. */
+      fr = astAnnul( fr );
+      pset2 = astAnnul( pset2 );
+      unc = astAnnul( unc );
+   }
+
+/* Free resources. */
+   map = astAnnul( map );
+
+/* If any simplification could be performed, copy Region attributes from
+   the supplied Region to the returned Region, and return a pointer to it.
+   If the supplied Region had no uncertainty, ensure the returned Region
+   has no uncertainty. Otherwise, return a clone of the supplied pointer. */
+   if( simpler ){
+      astRegOverlay( new, this, 1 );
+      result = (AstMapping *) new;
+
+   } else {
+      new = astAnnul( new );
+      result = astClone( this );
+   }
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib,
+                       int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a PointList.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PointList member function (over-rides the astTestAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate
+*     whether a value has been set for one of a PointList's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPointList *this;            /* Pointer to the PointList structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the PointList structure. */
+   this = (AstPointList *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Test if the name matches any of the read-only attributes of this
+   class. If it does, then return zero. */
+   if ( !strcmp( attrib, "listsize" ) ){
+      result = 0;
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a PointList to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     PointList member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a PointList and a set of points encapsulated in a
+*     PointSet and transforms the points by setting axis values to
+*     AST__BAD for all points which are outside the region covered by the
+*     PointList. PointList inside the region are copied unchanged from input
+*     to output.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  The forward and inverse transformations are identical for a
+*     Region.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of axes in the Frame represented by the PointList.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *in_base;         /* Pointer to PointSet holding base Frame positions*/
+   AstPointSet *ps1;             /* Pointer to accumulation PointSet */
+   AstPointSet *ps2;             /* Pointer to accumulation PointSet */
+   AstPointSet *ps3;             /* Pointer for swapping PointSet pointers */
+   AstPointSet *pset_base;       /* PointList positions in "unc" base Frame */
+   AstPointSet *pset_reg;        /* Pointer to Region PointSet */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstRegion *this;              /* Pointer to the Region structure */
+   AstRegion *unc;               /* Pointer to uncertainty Region */
+   double **ptr1;                /* Pointer to mask pointer array */
+   double **ptr_base;            /* Pointer to axis values for "pset_base" */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *cen_orig;             /* Pointer to array holding original centre coords */
+   double *mask;                 /* Pointer to mask axis values */
+   int coord;                    /* Zero-based index for coordinates */
+   int ncoord_base;              /* No. of coordinates per base Frame point */
+   int ncoord_out;               /* No. of coordinates per output point */
+   int npoint;                   /* No. of supplied input test points */
+   int nrp;                      /* No. of points in Region PointSet */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Avoid -Wall compiler warnings. */
+   ps1 = NULL;
+   ps2 = NULL;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_mapping;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Region class. This function validates
+   all arguments and generates an output PointSet if necessary,
+   containing a copy of the input PointSet. */
+   result = (*parent_transform)( this_mapping, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* First use the encapsulated FrameSet to transform the supplied positions
+   from the current Frame in the encapsulated FrameSet (the Frame
+   represented by the Region), to the base Frame (the Frame in which the
+   Region is defined). */
+   in_base = astRegTransform( this, in, 0, NULL, NULL );
+
+/* The PointSet pointer returned above may be a clone of the "in"
+   pointer. If so take a copy of the PointSet so we can change it safely. */
+   if( in_base == in ) {
+      ps3 = astCopy( in_base );
+      (void) astAnnul( in_base );
+      in_base = ps3;
+      ps3 = NULL;
+   }
+
+/* Determine the numbers of points and coordinates per point from the base
+   Frame PointSet and obtain pointers for accessing the base Frame and output
+   coordinate values. */
+   npoint = astGetNpoint( in_base );
+   ncoord_base = astGetNcoord( in_base );
+   ncoord_out = astGetNcoord( result );
+   ptr_out = astGetPoints( result );
+
+/* Get the axis values for the PointSet which defines the location and
+   extent of the region in the base Frame, and check them. */
+   pset_reg = this->points;
+   nrp = astGetNpoint( pset_reg );
+   if( astGetNcoord( pset_reg ) != ncoord_base && astOK ) {
+      astError( AST__INTER, "astTransform(PointList): Illegal number of "
+                "coords (%d) in the Region - should be %d "
+                "(internal AST programming error).", status, astGetNcoord( pset_reg ),
+                ncoord_base );
+   }
+
+/* Get the base Frame uncertainty Region. Temporarily set its negated flag. */
+   unc = astGetUncFrm( this, AST__BASE );
+   astSetNegated( unc, 1 );
+
+/* Transform the PointList PointSet into the base Frame of the uncertainty
+   Region, and get pointers to the corresponding axis value. */
+   pset_base = astRegTransform( unc, pset_reg, 0, NULL, NULL );
+   ptr_base = astGetPoints( pset_base );
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+
+/* Save the original base Frame centre coords of the uncertainty Region. */
+      cen_orig = astRegCentre( unc, NULL, NULL, 0, AST__BASE );
+
+/* We use the PointSet created above as the initial input to astTransform
+   below. Also indicate we currently have no output PointSet. This will
+   cause a new PointSet to be created on the first pass through the loop
+   below. */
+      ps1 = astClone( in_base );
+      ps2 = NULL;
+
+/* Loop round all the points in the PointList. */
+      for ( point = 0; point < nrp; point++ ) {
+
+/* Centre the uncertainty Region at this PointList position. Note, the
+   base Frame of the PointList should be the same as the current Frame
+   of the uncertainty Region. */
+         astRegCentre( unc, NULL, ptr_base, point, AST__BASE );
+
+/* Use the uncertainty Region to transform the supplied PointSet. This
+   will set supplied points bad if they are within the uncertainty Region
+   (since the uncertainty Region has been negated above). */
+         ps2 = astTransform( unc, ps1, 0, ps2 );
+
+/* Use the output PointSet created above as the input for the next
+   position. This causes bad points to be accumulated in the output
+   PointSet. */
+         ps3 = ps2;
+         ps2 = ps1;
+         ps1 = ps3;
+
+      }
+
+/* Re-instate the original centre coords of the uncertainty Region. */
+      astRegCentre( unc, cen_orig, NULL, 0, AST__BASE );
+      cen_orig = astFree( cen_orig );
+
+/* The ps1 PointSet will now be a copy of the supplied PointSet but with
+   positions set to bad if they are inside any of the re-centred uncertainty
+   Regions.  If this PointList has been negated, this is what we want so
+   we just transfer this bad position mask to the result PointSet. If this
+   PointList has not been negated we need to invert the bad position
+   mask. Get a pointer to the first axis of the resulting PointSet. */
+      ptr1 = astGetPoints( ps1 );
+      if( astOK ) {
+         mask = ptr1[ 0 ];
+
+/* Apply the mask to the returned PointSet, inverted if necessary. */
+         if( astGetNegated( this ) ) {
+            for ( point = 0; point < npoint; point++, mask++ ) {
+               if( *mask == AST__BAD ) {
+                  for( coord = 0; coord < ncoord_out; coord++ ) {
+                     ptr_out[ coord ][ point ] = AST__BAD;
+                  }
+               }
+            }
+
+         } else {
+            for ( point = 0; point < npoint; point++, mask++ ) {
+               if( *mask != AST__BAD ) {
+                  for( coord = 0; coord < ncoord_out; coord++ ) {
+                     ptr_out[ coord ][ point ] = AST__BAD;
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Clear the negated flag for the uncertainty Region. */
+   astClearNegated( unc );
+
+/* Free resources */
+   in_base = astAnnul( in_base );
+   pset_base = astAnnul( pset_base );
+   unc = astAnnul( unc );
+   if( ps2 ) ps2 = astAnnul( ps2 );
+   if( ps1 ) ps1 = astAnnul( ps1 );
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     ListSize
+
+*  Purpose:
+*     Number of points in a PointList.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This is a read-only attribute giving the number of points in a
+*     PointList. This value is determined when the PointList is created.
+
+*  Applicability:
+*     PointList
+*        All PointLists have this attribute.
+*att--
+*/
+
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for PointList objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for PointList objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstPointList *in;             /* Pointer to input PointList */
+   AstPointList *out;            /* Pointer to output PointList */
+   int nb;                       /* Number of bytes */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output PointLists. */
+   in = (AstPointList *) objin;
+   out = (AstPointList *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output PointList. */
+   out->lbnd = NULL;
+   out->ubnd = NULL;
+
+/* Copy dynamic memory contents */
+   if( in->lbnd && in->ubnd ) {
+      nb = sizeof( double )*astGetNaxes( in );
+      out->lbnd = astStore( NULL, in->lbnd, nb );
+      out->ubnd = astStore( NULL, in->ubnd, nb );
+   }
+}
+
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for PointList objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for PointList objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstPointList *this;                 /* Pointer to PointList */
+
+/* Obtain a pointer to the PointList structure. */
+   this = (AstPointList *) obj;
+
+/* Annul all resources. */
+   this->lbnd = astFree( this->lbnd );
+   this->ubnd = astFree( this->ubnd );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for PointList objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the PointList class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the PointList whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPointList *this;                 /* Pointer to the PointList structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PointList structure. */
+   this = (AstPointList *) this_object;
+
+/* Write out values representing the instance variables for the
+   PointList class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAPointList and astCheckPointList functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(PointList,Region)
+astMAKE_CHECK(PointList)
+
+AstPointList *astPointList_( void *frame_void, AstPointSet *points,
+                             AstRegion *unc, const char *options,
+                             int *status, ...) {
+/*
+*+
+*  Name:
+*     astPointList
+
+*  Purpose:
+*     Create a PointList.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     AstPointList *astPointList( AstFrame *frame, AstPointSet *points,
+*                                 AstRegion *unc, const char *options,
+*                                 int *status, ...) {
+
+*  Class Membership:
+*     PointList constructor.
+
+*  Description:
+*     This function implements the protected interface to the astPointList
+*     constructor function, returning a true C pointer. The parameter list
+*     differs from the public constructor, in that the positions are
+*     defined by a PointSet rather than an array of doubles.
+
+*  Parameters:
+*     frame
+*        A pointer to the Frame in which the region is defined. A deep
+*        copy is taken of the supplied Frame. This means that any
+*        subsequent changes made to the Frame using the supplied pointer
+*        will have no effect the Region.
+*     points
+*        A PointSet holding the physical coordinates of the points.
+*     unc
+*        An optional pointer to an existing Region which specifies the
+*        uncertainties associated with each point in the PointList being
+*        created. The uncertainty at any point in the PointList is found by
+*        shifting the supplied "uncertainty" Region so that it is centred at
+*        the point being considered. The area covered by the shifted
+*        uncertainty Region then represents the uncertainty in the position.
+*        The uncertainty is assumed to be the same for all points.
+*
+*        If supplied, the uncertainty Region must be of a class for which
+*        all instances are centro-symetric (e.g. Box, Circle, Ellipse, etc.)
+*        or be a Prism containing centro-symetric component Regions. A deep
+*        copy of the supplied Region will be taken, so subsequent changes to
+*        the uncertainty Region using the supplied pointer will have no
+*        effect on the created Box. Alternatively, a NULL Object pointer
+*        may be supplied, in which case a default uncertainty is used
+*        equivalent to a box 1.0E-6 of the size of the bounding box of the
+*        PointList being created.
+*
+*        The uncertainty Region has two uses: 1) when the astOverlap
+*        function compares two Regions for equality the uncertainty Region
+*        is used to determine the tolerance on the comparison, and 2)
+*        when a Region is mapped into a different coordinate system and
+*        subsequently simplified (using astSimplify), the uncertainties are
+*        used to determine if the transformed boundary can be accurately
+*        represented by a specific shape of Region.
+*     options
+*        Pointer to a null-terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new PointList. The syntax used is identical to
+*        that for the astSet function and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status value.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of additional arguments may follow it in
+*        order to supply values to be substituted for these
+*        specifiers. The rules for supplying these are identical to
+*        those for the astSet function (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new PointList.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstPointList *new;            /* Pointer to new PointList */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the supplied Frame structure. */
+   frame = astCheckFrame( frame_void );
+
+/* Initialise the PointList, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPointList( NULL, sizeof( AstPointList ), !class_init,
+                           &class_vtab, "PointList", frame, points, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new PointList's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new PointList. */
+   return new;
+}
+
+AstPointList *astPointListId_( void *frame_void, int npnt, int ncoord, int dim,
+                               const double *points, void *unc_void,
+                               const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astPointList
+f     AST_POINTLIST
+
+*  Purpose:
+*     Create a PointList.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "pointlist.h"
+c     AstPointList *astPointList( AstFrame *frame, int npnt, int ncoord, int dim,
+c                                 const double *points, AstRegion *unc,
+c                                 const char *options, ... )
+f     RESULT = AST_POINTLIST( FRAME, NPNT, COORD, DIM, POINTS, UNC, OPTIONS, STATUS )
+
+*  Class Membership:
+*     PointList constructor.
+
+*  Description:
+*     This function creates a new PointList object and optionally initialises
+*     its attributes.
+*
+*     A PointList object is a specialised type of Region which represents a
+*     collection of points in a coordinate Frame.
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        A pointer to the Frame in which the region is defined. A deep
+*        copy is taken of the supplied Frame. This means that any
+*        subsequent changes made to the Frame using the supplied pointer
+*        will have no effect the Region.
+c     npnt
+f     NPNT = INTEGER (Given)
+*        The number of points in the Region.
+c     ncoord
+f     NCOORD = INTEGER (Given)
+*        The number of coordinates being supplied for each point. This
+*        must equal the number of axes in the supplied Frame, given by
+*        its Naxes attribute.
+c     dim
+f     DIM = INTEGER (Given)
+c        The number of elements along the second dimension of the "points"
+f        The number of elements along the first dimension of the POINTS
+*        array (which contains the point coordinates). This value is
+*        required so that the coordinate values can be correctly
+*        located if they do not entirely fill this array. The value
+c        given should not be less than "npnt".
+f        given should not be less than NPNT.
+c     points
+f     POINTS( DIM, NCOORD ) = DOUBLE PRECISION (Given)
+c        The address of the first element of a 2-dimensional array of
+c        shape "[ncoord][dim]" giving the physical coordinates of the
+c        points. These should be stored such that the value of coordinate
+c        number "coord" for point number "pnt" is found in element
+c        "in[coord][pnt]".
+f        A 2-dimensional array giving the physical coordinates of the
+f        points. These should be stored such that the value of coordinate
+f        number COORD for point number PNT is found in element IN(PNT,COORD).
+c     unc
+f     UNC = INTEGER (Given)
+*        An optional pointer to an existing Region which specifies the uncertainties
+*        associated with each point in the PointList being created. The
+*        uncertainty at any point in the PointList is found by shifting the
+*        supplied "uncertainty" Region so that it is centred at the point
+*        being considered. The area covered by the shifted uncertainty Region
+*        then represents the uncertainty in the position. The uncertainty is
+*        assumed to be the same for all points.
+*
+*        If supplied, the uncertainty Region must be of a class for which
+*        all instances are centro-symetric (e.g. Box, Circle, Ellipse, etc.)
+*        or be a Prism containing centro-symetric component Regions. A deep
+*        copy of the supplied Region will be taken, so subsequent changes to
+*        the uncertainty Region using the supplied pointer will have no
+*        effect on the created Box. Alternatively,
+f        a null Object pointer (AST__NULL)
+c        a NULL Object pointer
+*        may be supplied, in which case a default uncertainty is used
+*        equivalent to a box 1.0E-6 of the size of the bounding box of the
+*        PointList being created.
+*
+*        The uncertainty Region has two uses: 1) when the
+c        astOverlap
+f        AST_OVERLAP
+*        function compares two Regions for equality the uncertainty
+*        Region is used to determine the tolerance on the comparison, and 2)
+*        when a Region is mapped into a different coordinate system and
+*        subsequently simplified (using
+c        astSimplify),
+f        AST_SIMPLIFY),
+*        the uncertainties are used to determine if the transformed boundary
+*        can be accurately represented by a specific shape of Region.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new PointList. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new PointList. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astPointList()
+f     AST_POINTLIST = INTEGER
+*        A pointer to the new PointList.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *frame;          /* Pointer to Frame structure */
+   AstPointList *new;        /* Pointer to new PointList */
+   AstPointSet *pset;        /* Pointer to PointSet holding points */
+   AstRegion *unc;           /* Pointer to Region structure */
+   astDECLARE_GLOBALS        /* Pointer to thread-specific global data */
+   const double *q;          /* Pointer to next supplied axis value */
+   double **ptr;             /* Pointer to data in pset */
+   double *p;                /* Pointer to next PointSet axis value */
+   int *status;              /* Pointer to inherited status value */
+   int i;                    /* Axis index */
+   int j;                    /* Point index */
+   va_list args;             /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Frame pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Frame. */
+   frame = astVerifyFrame( astMakePointer( frame_void ) );
+
+/* Create a PointSet and store the supplied points in it. */
+   pset = astPointSet( npnt, ncoord , "", status );
+   ptr = astGetPoints( pset );
+   if( astOK ) {
+      for( i = 0; i < ncoord; i++ ) {
+         p = ptr[ i ];
+         q = points + i*dim;
+         for( j = 0; j < npnt; j++ ) *(p++) = *(q++);
+      }
+   }
+
+/* Obtain a Region pointer from the supplied "unc" ID and validate the
+   pointer to ensure it identifies a valid Region . */
+   unc = unc_void ? astCheckRegion( astMakePointer( unc_void ) ) : NULL;
+
+/* Initialise the PointList, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPointList( NULL, sizeof( AstPointList ), !class_init,
+                           &class_vtab, "PointList", frame, pset, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new PointList's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Free resources. */
+   pset = astAnnul( pset );
+
+/* Return an ID value for the new PointList. */
+   return astMakeId( new );
+}
+
+AstPointList *astInitPointList_( void *mem, size_t size, int init,
+                                 AstPointListVtab *vtab, const char *name,
+                                 AstFrame *frame, AstPointSet *points,
+                                 AstRegion *unc, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPointList
+
+*  Purpose:
+*     Initialise a PointList.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     AstPointList *astInitPointList( void *mem, size_t size, int init,
+*                                     AstPointListVtab *vtab, const char *name,
+*                                     AstFrame *frame, AstPointSet *points,
+*                                     AstRegion *unc, int *status )
+
+*  Class Membership:
+*     PointList initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new PointList object. It allocates memory (if necessary) to accommodate
+*     the PointList plus any additional data associated with the derived class.
+*     It then initialises a PointList structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a PointList at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the PointList is to be initialised.
+*        This must be of sufficient size to accommodate the PointList data
+*        (sizeof(PointList)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the PointList (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the PointList
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the PointList's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new PointList.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     frame
+*        A pointer to the Frame in which the region is defined.
+*     points
+*        A PointSet containing the Points for the PointList.
+*     unc
+*        A pointer to a Region which specifies the uncertainty in the
+*        supplied positions (all points in the new PointList being
+*        initialised are assumed to have the same uncertainty). A NULL
+*        pointer can be supplied, in which case default uncertainties equal
+*        to 1.0E-6 of the dimensions of the new PointList's bounding box are
+*        used. If an uncertainty Region is supplied, it must be either a Box,
+*        a Circle or an Ellipse, and its encapsulated Frame must be related
+*        to the Frame supplied for parameter "frame" (i.e. astConvert
+*        should be able to find a Mapping between them). Two positions
+*        the "frame" Frame are considered to be co-incident if their
+*        uncertainty Regions overlap. The centre of the supplied
+*        uncertainty Region is immaterial since it will be re-centred on the
+*        point being tested before use. A deep copy is taken of the supplied
+*        Region.
+
+*  Returned Value:
+*     A pointer to the new PointList.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstPointList *new;        /* Pointer to new PointList */
+   int ncoord;               /* No. of axes in PointSet */
+   int nin;                  /* No. of axes in Frame */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitPointListVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the number of axis values per position is correct. */
+   nin = astGetNaxes( frame );
+   ncoord = astGetNcoord( points );
+   if( nin != ncoord ) {
+      astError( AST__NCPIN, "astInitPointList(): Bad number of coordinate "
+                "values (%d).", status, ncoord );
+      astError( AST__NCPIN, "The %s given requires %d coordinate value(s) for "
+                "each input point.", status, astGetClass( frame ), nin );
+   }
+
+/* Initialise a Region structure (the parent class) as the first component
+   within the PointList structure, allocating memory if necessary. */
+   if( astOK ) {
+      new = (AstPointList *) astInitRegion( mem, size, 0, (AstRegionVtab *) vtab,
+                                            name, frame, points, unc );
+      if ( astOK ) {
+
+/* Initialise the PointList data. */
+/* ------------------------------ */
+         new->lbnd = NULL;
+         new->ubnd = NULL;
+
+/* If an error occurred, clean up by deleting the new PointList. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new PointList. */
+   return new;
+}
+
+AstPointList *astLoadPointList_( void *mem, size_t size, AstPointListVtab *vtab,
+                                 const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadPointList
+
+*  Purpose:
+*     Load a PointList.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointlist.h"
+*     AstPointList *astLoadPointList( void *mem, size_t size, AstPointListVtab *vtab,
+*                                     const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     PointList loader.
+
+*  Description:
+*     This function is provided to load a new PointList using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     PointList structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a PointList at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the PointList is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        PointList data (sizeof(PointList)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the PointList (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the PointList structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstPointList) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new PointList. If this is NULL, a pointer
+*        to the (static) virtual function table for the PointList class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "PointList" is used instead.
+
+*  Returned Value:
+*     A pointer to the new PointList.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPointList *new;              /* Pointer to the new PointList */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this PointList. In this case the
+   PointList belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstPointList );
+      vtab = &class_vtab;
+      name = "PointList";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitPointListVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built PointList. */
+   new = astLoadRegion( mem, size, (AstRegionVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "PointList" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* If an error occurred, clean up by deleting the new PointList. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new PointList pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+int astGetListSize_( AstPointList *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,PointList,GetListSize))( this, status );
+}
+void astPointListPoints_( AstPointList *this, AstPointSet **pset, int *status) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,PointList,PointListPoints))( this, pset, status );
+   return;
+}
+
diff --git a/ast/pointlist.h b/ast/pointlist.h
new file mode 100644
index 0000000..a2e35b7
--- /dev/null
+++ b/ast/pointlist.h
@@ -0,0 +1,239 @@
+#if !defined( POINTLIST_INCLUDED ) /* Include this file only once */
+#define POINTLIST_INCLUDED
+/*
+*+
+*  Name:
+*     pointlist.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the PointList class.
+
+*  Invocation:
+*     #include "pointlist.h"
+
+*  Description:
+*     This include file defines the interface to the PointList class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The PointList class implements a Region which represents a collection
+*     of points in a Frame.
+
+*  Inheritance:
+*     The PointList class inherits from the Region class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     23-AUG-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "region.h"              /* Coordinate regions (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* PointList structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstPointList {
+
+/* Attributes inherited from the parent class. */
+   AstRegion region;          /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *lbnd;              /* Lower axis limits of bounding box */
+   double *ubnd;              /* Upper axis limits of bounding box */
+} AstPointList;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstPointListVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstRegionVtab region_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   int (* GetListSize)( AstPointList *, int * );
+   void (* PointListPoints)( AstPointList *, AstPointSet **, int * );
+} AstPointListVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstPointListGlobals {
+   AstPointListVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 101 ];
+} AstPointListGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitPointListGlobals_( AstPointListGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(PointList)          /* Check class membership */
+astPROTO_ISA(PointList)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstPointList *astPointList_( void *, AstPointSet *, AstRegion *, const char *, int *, ...);
+#else
+AstPointList *astPointListId_( void *, int, int, int, const double *, AstRegion *, const char *, ... )__attribute__((format(printf,7,8)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstPointList *astInitPointList_( void *, size_t, int, AstPointListVtab *,
+                                 const char *, AstFrame *, AstPointSet *,
+                                 AstRegion *, int * );
+
+/* Vtab initialiser. */
+void astInitPointListVtab_( AstPointListVtab *, const char *, int * );
+
+/* Loader. */
+AstPointList *astLoadPointList_( void *, size_t, AstPointListVtab *,
+                                 const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+int astGetListSize_( AstPointList *, int * );
+void astPointListPoints_( AstPointList *, AstPointSet **, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckPointList(this) astINVOKE_CHECK(PointList,this,0)
+#define astVerifyPointList(this) astINVOKE_CHECK(PointList,this,1)
+
+/* Test class membership. */
+#define astIsAPointList(this) astINVOKE_ISA(PointList,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astPointList astINVOKE(F,astPointList_)
+#else
+#define astPointList astINVOKE(F,astPointListId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitPointList(mem,size,init,vtab,name,frame,points,unc) \
+astINVOKE(O,astInitPointList_(mem,size,init,vtab,name,frame,points,unc,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitPointListVtab(vtab,name) astINVOKE(V,astInitPointListVtab_(vtab,name,STATUS_PTR))
+
+/* Loader. */
+#define astLoadPointList(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadPointList_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckPointList to validate PointList pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astGetListSize(this) \
+astINVOKE(V,astGetListSize_(astCheckPointList(this),STATUS_PTR))
+#define astPointListPoints(this,pset) \
+astINVOKE(V,astPointListPoints_(astCheckPointList(this),pset,STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/pointset.c b/ast/pointset.c
new file mode 100644
index 0000000..50d0d95
--- /dev/null
+++ b/ast/pointset.c
@@ -0,0 +1,3285 @@
+/*
+*  Name:
+*     pointset.c
+
+*  Purpose:
+*     Implement the PointSet class.
+
+*  Description:
+*     This file implements the PointSet class. For a description of
+*     the class and its interface, see the .h file of the same name.
+
+*  Inheritance:
+*     The PointSet class inherits from the Object class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     1-FEB-1996 (RFWS):
+*        Original version.
+*     27-SEP-1996 (RFWS):
+*        Added external interface and I/O facilities.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitPointSetVtab
+*        method.
+*     9-SEP-2004 (DSB):
+*        Added astPermPoints.
+*     5-OCT-2004 (DSB):
+*        Bug fix in astLoadPointSet - npoint was used as size for new array
+*        of pointers (changed to ncoord).
+*     19-OCT-2004 (DSB):
+*        Added astSetNpoint.
+*     2-NOV-2004 (DSB):
+*        - Do not write out AST__BAD axis values in the Dump function.
+*        - Override astEqual method.
+*        - Add protected PointAccuracy attribute.
+*     7-JAN-2005 (DSB):
+*        Added astAppendPoints.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     22-FEB-2006 (DSB):
+*        Avoid allocating memory for "acc" unless needed.
+*     1-MAY-2009 (DSB):
+*        Added astBndPoints.
+*     16-JUN-2011 (DSB):
+*        Added astReplaceNan.
+*     2-OCT-2012 (DSB):
+*        Check for Infs as well as NaNs.
+*     24-MAY-2016 (DSB):
+*        Added astShowPoints.
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS PointSet
+
+/* Values that control the behaviour of the astReplaceNaN method. */
+#define IGNORE_NANS  0
+#define REPLACE_NANS 1
+#define REPORT_NANS  2
+
+/*
+*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Implement a method to clear a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     MAKE_CLEAR(attr,component,assign)
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstPointSet *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( AstPointSet *this, int axis )
+*
+*     which implement a method for clearing a single value in a specified
+*     multi-valued attribute for an axis of a PointSet. The "axis" value
+*     must be in the range 0 to (ncoord-1).
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. PointAccuracy in "astClearPointAccuracy").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attr,component,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attr( AstPointSet *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= this->ncoord ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astClear" #attr, astGetClass( this ), \
+                axis + 1, this->ncoord ); \
+\
+/* Assign the "clear" value. */ \
+   } else if( this->component ){ \
+      this->component[ axis ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attr##_( AstPointSet *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,PointSet,Clear##attr))( this, axis, status ); \
+}
+
+
+/*
+*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Implement a method to get a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     MAKE_GET(attr,type,bad_value,assign)
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( AstPointSet *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        <Type> astGet<Attribute>_( AstPointSet *this, int axis )
+*
+*     which implement a method for getting a single value from a specified
+*     multi-valued attribute for an axis of a PointSet. The "axis" value
+*     must be in the range 0 to (ncoord-1).
+
+
+*  Parameters:
+*     attr
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. PointAccuracy in "astGetPointAccuracy").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set, or if
+*        the function fails.
+*     assign
+*        An expression that evaluates to the value to be returned. This can
+*        use the string "axis" to represent the zero-based value index.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attr,type,bad_value,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type Get##attr( AstPointSet *this, int axis, int *status ) { \
+   type result;                  /* Result to be returned */ \
+\
+/* Initialise */ \
+   result = bad_value; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= this->ncoord ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astGet" #attr, astGetClass( this ), \
+                axis + 1, this->ncoord ); \
+\
+/* Assign the result value. */ \
+   } else { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */  \
+type astGet##attr##_( AstPointSet *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,PointSet,Get##attr))( this, axis, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Implement a method to set a single value in a multi-valued attribute
+*     for a PointSet.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     MAKE_SET(attr,type,component,assign,null)
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstPointSet *this, int axis, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( AstPointSet *this, int axis, <Type> value )
+*
+*     which implement a method for setting a single value in a specified
+*     multi-valued attribute for a PointSet. The "axis" value must be in
+*     the range 0 to (ncoord-1).
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. PointAccuracy in "astSetPointAccuracy").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+*      null
+*         The value to initialise newly created array elements to.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attr,type,component,assign,null) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attr( AstPointSet *this, int axis, type value, int *status ) { \
+   int i; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= this->ncoord ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astSet" #attr, astGetClass( this ), \
+                axis + 1, this->ncoord ); \
+\
+/* Store the new value in the structure component. */ \
+   } else { \
+      if( !this->component ){ \
+         this->component = astMalloc( this->ncoord*sizeof( type ) ); \
+         for( i = 0; i < this->ncoord; i++ ) { \
+            this->component[ i ] = null; \
+         } \
+      } \
+      this->component[ axis ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attr##_( AstPointSet *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,PointSet,Set##attr))( this, axis, value, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_TEST
+
+*  Purpose:
+*     Implement a method to test if a single value has been set in a
+*     multi-valued attribute for a class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     MAKE_TEST(attr,assign)
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static int Test<Attribute>( AstPointSet *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        int astTest<Attribute>_( AstPointSet *this, int axis )
+*
+*     which implement a method for testing if a single value in a specified
+*     multi-valued attribute has been set for a class. The "axis" value
+*     must be in the range 0 to (ncoord-1).
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be tested, as it appears in the function
+*         name (e.g. PointAccuracy in "astTestPointAccuracy").
+*      assign
+*         An expression that evaluates to 0 or 1, to be used as the returned
+*         value. This can use the string "axis" to represent the zero-based
+*         index of the value within the attribute.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_TEST(attr,assign) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static int Test##attr( AstPointSet *this, int axis, int *status ) { \
+   int result;                   /* Value to return */ \
+\
+   result= 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= this->ncoord ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astTest" #attr, astGetClass( this ), \
+                axis + 1, this->ncoord ); \
+\
+/* Assign the result value. */ \
+   } else { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */ \
+int astTest##attr##_( AstPointSet *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,PointSet,Test##attr))( this, axis, status ); \
+}
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* This static variable is used to hold an IEEE 754 quiet double precision
+   Nan value. */
+static double ast_nan;
+
+/* This static variable is used to hold an IEEE 754 quiet single precision
+   Nan value. */
+static float ast_nanf;
+
+/* Enable or disable the astReplaceNan method. */
+static int replace_nan = -1;
+
+/* Pointers to parent class methods which are extended by this class. */
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static int (* parent_equal)( AstObject *, AstObject *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(PointSet)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(PointSet,Class_Init)
+#define class_vtab astGLOBAL(PointSet,Class_Vtab)
+#define getattrib_buff astGLOBAL(PointSet,GetAttrib_Buff)
+
+static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX1 pthread_mutex_lock( &mutex1 );
+#define UNLOCK_MUTEX1 pthread_mutex_unlock( &mutex1 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstPointSetVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#define LOCK_MUTEX1
+#define UNLOCK_MUTEX1
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstPointSet *astPointSetId_( int, int, const char *, int *, ...);
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static const char *GetAttrib( AstObject *, const char *, int * );
+static double **GetPoints( AstPointSet *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetNcoord( const AstPointSet *, int * );
+static int GetNpoint( const AstPointSet *, int * );
+static int GetObjSize( AstObject *, int * );
+static int ReplaceNaN( AstPointSet *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static AstPointSet *AppendPoints( AstPointSet *, AstPointSet *, int * );
+static void BndPoints( AstPointSet *, double *, double *, int * );
+static void CheckPerm( AstPointSet *, const int *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void PermPoints( AstPointSet *, int, const int[], int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetNpoint( AstPointSet *, int, int * );
+static void SetPoints( AstPointSet *, double **, int * );
+static void SetSubPoints( AstPointSet *, int, int, AstPointSet *, int * );
+static void ShowPoints( AstPointSet *, int * );
+
+static double GetPointAccuracy( AstPointSet *, int, int * );
+static int TestPointAccuracy( AstPointSet *, int, int * );
+static void ClearPointAccuracy( AstPointSet *, int, int * );
+static void SetPointAccuracy( AstPointSet *, int, double, int * );
+
+/* Member functions. */
+/* ================= */
+static AstPointSet *AppendPoints( AstPointSet *this, AstPointSet *that, int *status ) {
+/*
+*+
+*  Name:
+*     astAppendPoints
+
+*  Purpose:
+*     Append one PointSet to another.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     AstPointSet *astAppendPoints( AstPointSet *this, AstPointSet *that )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function creates a new PointSet containing all the points in
+*     "this" followed by all the points in "that".
+
+*  Parameters:
+*     this
+*        Pointer to the first PointSet.
+*     that
+*        Pointer to the second PointSet.
+
+*  Returned Value:
+*     Pointer to the new PointSet.
+
+*  Notes:
+*     - Axis accuracies are copied from "this".
+*     - The Ncoord attribute of the two PointSets must match.
+*     - NULL will be returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *result;
+   double **ptr;
+   double **ptr1;
+   double **ptr2;
+   int ic;
+   int n1;
+   int n2;
+   int ncoord;
+   size_t nb2;
+   size_t nb1;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check the two PointSets have the same Ncoord value. */
+   ncoord = astGetNcoord( this );
+   if( ncoord != astGetNcoord( that ) ) {
+      astError( AST__NPTIN, "astAppendPoints(%s): Number of coordinates "
+                "per point differ in the two supplied PointSets.", status,
+                astGetClass( this ) );
+
+/* Calculate the new size for the PointSet. */
+   } else {
+      n1 = astGetNpoint( this );
+      n2 = astGetNpoint( that );
+
+/* Create the new PointSet and get pointers to its data. */
+      result = astPointSet( n1 + n2, ncoord, "", status );
+      ptr1 = astGetPoints( this );
+      ptr2 = astGetPoints( that );
+      ptr = astGetPoints( result );
+      if( astOK ) {
+
+/* Copy the axis values for each coordinate in turn. */
+         nb1 = sizeof( double )*(size_t) n1;
+         nb2 = sizeof( double )*(size_t) n2;
+         for( ic = 0; ic < ncoord; ic++ ) {
+            memcpy( ptr[ ic ], ptr1[ ic ], nb1 );
+            memcpy( ptr[ ic ] + n1, ptr2[ ic ], nb2 );
+         }
+
+/* Copy any axis accuracies from "this". */
+         result->acc = this->acc ?
+                  astStore( NULL, this->acc, sizeof( double )*(size_t) ncoord )
+                  : NULL;
+      }
+   }
+
+/* Annul the result if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void BndPoints( AstPointSet *this, double *lbnd, double *ubnd, int *status ) {
+/*
+*+
+*  Name:
+*     astBndPoints
+
+*  Purpose:
+*     Find the axis bounds of the points in a PointSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     void astBndPoints( AstPointSet *this, double *lbnd, double *ubnd )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function returns the lower and upper limits of the axis values
+*     of the points in a PointSet.
+
+*  Parameters:
+*     this
+*        Pointer to the first PointSet.
+*     lbnd
+*        Pointer to an array in which to return the lowest value for
+*        each coordinate. The length of the array should equal the number
+*        returned by astGetNcoord.
+*     ubnd
+*        Pointer to an array in which to return the highest value for
+*        each coordinate. The length of the array should equal the number
+*        returned by astGetNcoord.
+
+*-
+*/
+
+/* Local Variables: */
+   double **ptr;
+   double *p;
+   double lb;
+   double ub;
+   int ic;
+   int ip;
+   int nc;
+   int np;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get pointers to the PointSet data, the number of axes adn the number
+   of points. */
+   ptr = astGetPoints( this );
+   nc = astGetNcoord( this );
+   np = astGetNpoint( this );
+
+/* Check the pointers can be used safely. */
+   if( astOK ) {
+
+/* Loop round each axis. */
+      for( ic = 0; ic < nc; ic++ ) {
+
+/* Initialise the bounds for this axis. */
+         lb = AST__BAD;
+         ub = AST__BAD;
+
+/* Search for the first good point. Use it to initialise the bounds and
+   break out of the loop. */
+         p = ptr[ ic ];
+         for( ip = 0; ip < np; ip++,p++ ) {
+            if( *p != AST__BAD ) {
+               lb = ub = *p;
+               break;
+            }
+         }
+
+/* Search through the remaining points. Update the bounds if the axis
+   value is good. */
+         for( ; ip < np; ip++,p++ ) {
+            if( *p != AST__BAD ) {
+               if( *p < lb ) {
+                  lb = *p;
+               } else if( *p > ub ) {
+                  ub = *p;
+               }
+            }
+         }
+
+/* Store the returned bounds. */
+         lbnd[ ic ] = lb;
+         ubnd[ ic ] = ub;
+      }
+   }
+}
+
+static void CheckPerm( AstPointSet *this, const int *perm, const char *method, int *status ) {
+/*
+*+
+*  Name:
+*     astCheckPerm
+
+*  Purpose:
+*     Check that an array contains a valid permutation.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     void astCheckPerm( AstPointSet *this, const int *perm, const char *method )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function checks the validity of a permutation array that
+*     will be used to permute the order of a PointSet's axes. If the
+*     permutation specified by the array is not valid, an error is
+*     reported and the global error status is set. Otherwise, the
+*     function returns without further action.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+*     perm
+*        Pointer to an array of integers with the same number of
+*        elements as there are axes in the PointSet. For each axis, the
+*        corresponding integer gives the (zero based) axis index to be
+*        used to identify the axis values for that axis (using the
+*        un-permuted axis numbering). To be valid, the integers in
+*        this array should therefore all lie in the range zero to
+*        (ncoord-1) inclusive, where "ncoord" is the number of PointSet
+*        axes, and each value should occur exactly once.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate a permutation array. This method name is used
+*        solely for constructing error messages.
+
+*  Notes:
+*     - Error messages issued by this function refer to the external
+*     (public) numbering system used for axes (which is one-based),
+*     whereas zero-based axis indices are used internally.
+*-
+*/
+
+/* Local Variables: */
+   int *there;                   /* Pointer to temporary array */
+   int coord;                     /* Loop counter for axes */
+   int ncoord;                   /* Number of PointSet axes */
+   int valid;                    /* Permutation array is valid? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise. */
+   valid = 1;
+
+/* Obtain the number of PointSet axes and allocate a temporary array of
+   integers with the same number of elements. */
+   ncoord = astGetNcoord( this );
+   there = astMalloc( sizeof( int ) * (size_t) ncoord );
+   if ( astOK ) {
+
+/* Initialise the temporary array to zero. */
+      for ( coord = 0; coord < ncoord; coord++ ) there[ coord ] = 0;
+
+/* Scan the permutation array, checking that each permuted axis index it
+   contains is within the correct range. Note an error and quit checking
+   if an invalid value is found. */
+      for ( coord = 0; coord < ncoord; coord++ ) {
+         if ( ( perm[ coord ] < 0 ) || ( perm[ coord ] >= ncoord ) ) {
+            valid = 0;
+            break;
+
+/* Use the temporary array to count how many times each valid axis index
+   occurs. */
+	 } else {
+            there[ perm[ coord ] ]++;
+	 }
+      }
+
+/* If all the axis indices were within range, check to ensure that each value
+   occurred only once. */
+      if ( valid ) {
+         for ( coord = 0; coord < ncoord; coord++ ) {
+
+/* Note an error and quit checking if any value did not occur exactly once. */
+            if ( there[ coord ] != 1 ) {
+               valid = 0;
+               break;
+	    }
+	 }
+      }
+   }
+
+/* Free the temporary array. */
+   there = astFree( there );
+
+/* If an invalid permutation was detected and no other error has
+   occurred, then report an error (note we convert to one-based axis
+   numbering in the error message). */
+   if ( !valid && astOK ) {
+      astError( AST__PRMIN, "%s(%s): Invalid coordinate permutation array.", status,
+                method, astGetClass( this ) );
+      astError( AST__PRMIN, "Each coordinate index should lie in the range 1 to %d "
+                "and should occur only once.", status, ncoord );
+   }
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a PointSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PointSet member function (over-rides the astClearAttrib
+*     protected method inherited from the Object class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     PointSet, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPointSet *this;            /* Pointer to the PointSet structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PointSet structure. */
+   this = (AstPointSet *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Test if the name matches any of the read-only attributes of this
+   class. If it does, then report an error. */
+   if ( !strcmp( attrib, "ncoord" ) ||
+        !strcmp( attrib, "npoint" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two PointSets are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     PointSet member function (over-rides the astEqual protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two PointSets are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first PointSet.
+*     that
+*        Pointer to the second PointSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the PointSets are equivalent, zero otherwise.
+
+*  Notes:
+*     - The two PointSets are considered equivalent if they have the same
+*     number of points, the same number of axis values per point, and the
+*     same axis values to within the absolute tolerance specified by the
+*     Accuracy attribute of the two PointSets.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local constants: */
+#define SMALL sqrt(DBL_MIN)
+
+/* Local Variables: */
+   AstPointSet *that;         /* Pointer to the second PointSet structure */
+   AstPointSet *this;         /* Pointer to the first PointSet structure */
+   double **ptr_that;         /* Pointer to axis values in second PointSet */
+   double **ptr_this;         /* Pointer to axis values in first PointSet */
+   double *p_that;            /* Pointer to next axis value in second PointSet */
+   double *p_this;            /* Pointer to next axis value in first PointSet */
+   double acc1;               /* Absolute accuracy for 1st PointSet axis value */
+   double acc2;               /* Absolute accuracy for 2nd PointSet axis value */
+   double acc;                /* Combined absolute accuracy */
+   double acc_that;           /* PointAccuracy attribute for 2nd PointSet */
+   double acc_this;           /* PointAccuracy attribute for 1st PointSet */
+   int ic;                    /* Axis index */
+   int ip;                    /* Point index */
+   int nc;                    /* No. of axis values per point */
+   int np;                    /* No. of points in each PointSet */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the Equal method inherited from the parent Object class. This checks
+   that the Objects are both of the same class (amongst other things). */
+   if( (*parent_equal)( this_object, that_object, status ) ) {
+
+/* Obtain pointers to the two PointSet structures. */
+      this = (AstPointSet *) this_object;
+      that = (AstPointSet *) that_object;
+
+/* Check the number of points and the number of axis values per point are
+   equal in the two PointSets. */
+      np = astGetNpoint( this );
+      nc = astGetNcoord( this );
+      if( np == astGetNpoint( that ) && nc == astGetNcoord( that ) ) {
+
+/* Get pointers to the axis values.*/
+         ptr_this = astGetPoints( this );
+         ptr_that = astGetPoints( that );
+         if( astOK ) {
+
+/* Assume the PointSets are equal until proven otherwise. */
+            result = 1;
+
+/* Loop round each axis, until we find a difference. */
+            for( ic = 0; ic < nc && result; ic++ ) {
+
+/* Get pointers to the next value for this axis. */
+               p_this = ptr_this[ ic ];
+               p_that = ptr_that[ ic ];
+
+/* Get the absolute accuracies for this axis. The default value for this
+   attribute is AST__BAD. */
+               acc_this = astGetPointAccuracy( this, ic );
+               acc_that = astGetPointAccuracy( that, ic );
+
+/* If both accuracies are available, combine them in quadrature. */
+               if( acc_this != AST__BAD && acc_that != AST__BAD ) {
+                  acc = sqrt( acc_this*acc_this + acc_that*acc_that );
+
+/* Loop round all points on this axis */
+                  for( ip = 0; ip < np; ip++, p_this++, p_that++ ){
+
+/* If either value is bad we do not need to compare values. */
+                     if( *p_this == AST__BAD || *p_that == AST__BAD ) {
+
+/* If one value is bad and one is good, they differ, so break. If both
+   values are bad they are equal so we continue. */
+                        if( *p_this != AST__BAD || *p_that != AST__BAD ) {
+                           result = 0;
+                           break;
+                        }
+
+/* Otherwise (if both axis values are good), compare axis values, and break if
+   they differ by more than the absolute accuracy. */
+                     } else if( fabs( *p_this - *p_that ) > acc ) {
+                        result = 0;
+                        break;
+                     }
+                  }
+
+/* If either accuracy is unavailable, we use a default relative accuracy. */
+               } else {
+
+/* Loop round all points on this axis */
+                  for( ip = 0; ip < np; ip++, p_this++, p_that++ ){
+
+/* If either value is bad we do not need to compare values. */
+                     if( *p_this == AST__BAD || *p_that == AST__BAD ) {
+
+/* If one value is bad and one is good, they differ, so break. If both
+   values are bad they are equal so we continue. */
+                        if( *p_this != AST__BAD || *p_that != AST__BAD ) {
+                           result = 0;
+                           break;
+                        }
+
+/* Otherwise (if both axis values are good), find the absolute error for
+   both values. */
+                     } else {
+
+                        if( acc_this == AST__BAD ) {
+                           acc1 = fabs(*p_this)*DBL_EPSILON;
+                           if( acc1 < SMALL ) acc1 = SMALL;
+                           acc1 *= 1.0E3;
+                        } else {
+                           acc1 = acc_this;
+                        }
+
+                        if( acc_that == AST__BAD ) {
+                           acc2 = fabs(*p_that)*DBL_EPSILON;
+                           if( acc2 < SMALL ) acc2 = SMALL;
+                           acc2 *= 1.0E3;
+                        } else {
+                           acc2 = acc_that;
+                        }
+
+/* Combine them in quadrature. */
+                        acc = sqrt( acc1*acc1 + acc2*acc2 );
+
+/* Compare axis values, and break if they differ by more than the
+   absolute accuracy. */
+                        if( fabs( *p_this - *p_that ) > acc ) {
+                           result = 0;
+                           break;
+                        }
+                     }
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+#undef SMALL
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a PointSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PointSet member function (over-rides the protected astGetAttrib
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a PointSet, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the PointSet, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the PointSet. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
+   AstPointSet *this;            /* Pointer to the PointSet structure */
+   const char *result;           /* Pointer value to return */
+   int ncoord;                   /* Ncoord attribute value */
+   int npoint;                   /* Npoint attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the PointSet structure. */
+   this = (AstPointSet *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* Ncoord. */
+/* ------- */
+   if ( !strcmp( attrib, "ncoord" ) ) {
+      ncoord = astGetNcoord( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ncoord );
+         result = getattrib_buff;
+      }
+
+/* Npoint. */
+/* ------- */
+   } else if ( !strcmp( attrib, "npoint" ) ) {
+      npoint = astGetNpoint( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", npoint );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     PointSet member function (over-rides the astGetObjSize protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied PointSet,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPointSet *this;         /* Pointer to PointSet structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the PointSet structure. */
+   this = (AstPointSet *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astTSizeOf( this->ptr );
+   result += astTSizeOf( this->values );
+   result += astTSizeOf( this->acc );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetNcoord( const AstPointSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNcoord
+
+*  Purpose:
+*     Get the number of coordinate values per point from a PointSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     int astGetNcoord( const AstPointSet *this )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function returns the number of coordinate values per point (1 or
+*     more) for a PointSet.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+
+*  Returned Value:
+*     The number of coordinate values per point.
+
+*  Notes:
+*     -  A value of zero is returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Return the number of coordinate values. */
+   return this->ncoord;
+}
+
+static int GetNpoint( const AstPointSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNpoint
+
+*  Purpose:
+*     Get the number of points in a PointSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     int astGetNpoint( const AstPointSet *this )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function returns the number of points (1 or more) in a PointSet.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+
+*  Returned Value:
+*     The number of points.
+
+*  Notes:
+*     -  A value of zero is returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Return the number of points. */
+   return this->npoint;
+}
+
+static double **GetPoints( AstPointSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetPoints
+
+*  Purpose:
+*     Get a pointer for the coordinate values associated with a PointSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     double **astGetPoints( AstPointSet *this )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function returns a pointer which grants access to the coordinate
+*     values associated with a PointSet. If the PointSet has previously had
+*     coordinate values associated with it, this pointer will identify these
+*     values. Otherwise, it will point at a newly-allocated region of memory
+*     (associated with the PointSet) in which new coordinate values may be
+*     stored.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+
+*  Returned Value:
+*     A pointer to an array of type double* with ncoord elements (where ncoord
+*     is the number of coordinate values per point). Each element of this array
+*     points at an array of double, of size npoint (where npoint is the number
+*     of points in the PointSet), containing the values of that coordinate for
+*     each point in the set. Hence, the value of the i'th coordinate for the
+*     j'th point (where i and j are counted from zero) is given by ptr[i][j]
+*     where ptr is the returned pointer value.
+
+*  Notes:
+*     -  The returned pointer points at an array of pointers allocated
+*     internally within the PointSet. The values in this array may be changed
+*     by the caller, who is reponsible for ensuring that they continue to
+*     point at valid arrays of coordinate values.
+*     -  No attempt should be made to de-allocate memory allocated by a
+*     PointSet to store coordinate values or pointers to them. This memory
+*     will be freed when the PointSet is deleted.
+*     -  No count is kept of the number of pointers issued for the PointSet
+*     coordinate values. The caller must keep track of these.
+*     -  A NULL pointer is returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   int i;                        /* Loop counter for coordinates */
+   int nval;                     /* Number of values to be stored */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* If the PointSet has an existing array of pointers (which point at coordinate
+   values), we will simply return a pointer to it. Otherwise, we must allocate
+   space to hold new coordinate values. */
+   if( !this->ptr ) {
+
+/* Determine the number of coordinate values to be stored and allocate memory
+   to hold them, storing the pointer to this values array in the PointSet
+   structure. */
+      nval = this->npoint * this->ncoord;
+      this->values = (double *) astMalloc( sizeof( double ) * (size_t) nval );
+
+#ifdef DEBUG
+      for( i = 0; i < nval; i++ ) this->values[ i ] = 0.0;
+#endif
+
+/* If OK, also allocate memory for the array of pointers into this values
+   array, storing a pointer to this pointer array in the PointSet structure. */
+      if ( astOK ) {
+         this->ptr = (double **) astMalloc( sizeof( double * )
+                                            * (size_t) this->ncoord );
+
+/* If OK, initialise the pointer array to point into the values array. */
+         if ( astOK ) {
+            for ( i = 0; i < this->ncoord; i++ ) {
+               this->ptr[ i ] = this->values + ( i * this->npoint );
+            }
+
+/* If we failed to allocate the pointer array, then free the values array. */
+         } else {
+            this->values = (double *) astFree( (void *) this->values );
+         }
+      }
+
+#ifdef DEBUG
+   } else {
+
+/* Check for bad values */
+      if( this->values ) {
+         int i, j;
+         for( i = 0; astOK && i < this->ncoord; i++ ) {
+            for( j = 0; j < this->npoint; j++ ) {
+               if( !finite( (this->ptr)[ i ][ j ] ) ) {
+                  astError( AST__INTER, "astGetPoints(PointSet): Non-finite "
+                            "axis value returned.", status);
+                  break;
+               }
+            }
+         }
+      }
+
+#endif
+   }
+
+/* Return the required pointer. */
+   return this->ptr;
+}
+
+void astInitPointSetVtab_(  AstPointSetVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPointSetVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a PointSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     void astInitPointSetVtab( AstPointSetVtab *vtab, const char *name )
+
+*  Class Membership:
+*     PointSet vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the PointSet class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   const char *envvar;           /* Pointer to environment variable value */
+   size_t i;                     /* Index of next byte in NaN value */
+   unsigned char *p;             /* Pointer to next byte in NaN value */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitObjectVtab( (AstObjectVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAPointSet) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstObjectVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->AppendPoints = AppendPoints;
+   vtab->BndPoints = BndPoints;
+   vtab->GetNcoord = GetNcoord;
+   vtab->GetNpoint = GetNpoint;
+   vtab->GetPoints = GetPoints;
+   vtab->PermPoints = PermPoints;
+   vtab->ReplaceNaN = ReplaceNaN;
+   vtab->SetPoints = SetPoints;
+   vtab->SetNpoint = SetNpoint;
+   vtab->SetSubPoints = SetSubPoints;
+   vtab->ShowPoints = ShowPoints;
+
+   vtab->GetPointAccuracy = GetPointAccuracy;
+   vtab->SetPointAccuracy = SetPointAccuracy;
+   vtab->TestPointAccuracy = TestPointAccuracy;
+   vtab->ClearPointAccuracy = ClearPointAccuracy;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+   parent_equal = object->Equal;
+   object->Equal = Equal;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "PointSet", "Container for a set of points" );
+
+/* Calculate single and double precision NaN values and store in static
+   module variables. Setting all bits to 1 produces a quiet NaN. */
+   LOCK_MUTEX1
+   p = (unsigned char *) &ast_nan;
+   for( i = 0; i < sizeof( ast_nan ); i++ ) *(p++) = 255;
+   p = (unsigned char *) &ast_nanf;
+   for( i = 0; i < sizeof( ast_nanf ); i++ ) *(p++) = 255;
+
+/* See what  action the astReplaceNaN method should perform. This
+   is determined by the value of the AST_REPLACE_NAN environment
+   variable. Not set = do not check for NaNs, "1" = replace NaNs with
+   AST__BAD silently, anything else = report an error if any NaNs are
+   encountered. */
+   if( replace_nan == -1 ) {
+      envvar = getenv( "AST_REPLACE_NAN" );
+      if( !envvar ) {
+         replace_nan = IGNORE_NANS;
+      } else if( !strcmp( envvar, "1" ) ) {
+         replace_nan = REPLACE_NANS;
+      } else {
+         replace_nan = REPORT_NANS;
+      }
+   }
+   UNLOCK_MUTEX1
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+double astCheckNaN_( double value ) {
+/*
+*+
+*  Name:
+*     astCheckNaN
+
+*  Purpose:
+*     Substitute a NaN for a supplied value if the supplied value is
+*     AST__NAN.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     double astCheckNaN( double value )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     If the supplied double is AST__NAN then this function returns an
+*     IEEE double precision NaN value. Otherwise it returns the supplied
+*     value.
+
+*  Parameters:
+*     valuethis
+*        The value to check.
+
+*  Returned Value:
+*     The suppleid value, or NaN.
+
+*  Notes:
+*     - This function does not check the inherited status.
+
+*-
+*/
+   return ( value == AST__NAN ) ? ast_nan : value;
+}
+
+float astCheckNaNF_( float value ) {
+/*
+*+
+*  Name:
+*     astCheckNaNF
+
+*  Purpose:
+*     Substitute a NaN for a supplied value if the supplied value is
+*     AST__NANF.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     float astCheckNaNF( float value )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     If the supplied float is AST__NANF then this function returns an
+*     IEEE single precision NaN value. Otherwise it returns the supplied
+*     value.
+
+*  Parameters:
+*     valuethis
+*        The value to check.
+
+*  Returned Value:
+*     The suppleid value, or NaN.
+
+*  Notes:
+*     - This function does not check the inherited status.
+
+*-
+*/
+   return ( value == AST__NANF ) ? ast_nanf : value;
+}
+
+static void PermPoints( AstPointSet *this, int forward, const int perm[], int *status ) {
+/*
+*+
+*  Name:
+*     astPermPoints
+
+*  Purpose:
+*     Permute the order of a PointSet's axes.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     void astPermPoints( AstPointSet *this, int forward, const int perm[] )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function permutes the order in which a PointSet's axes occur.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+*     forward
+*        The direction in which the permutation is to be applied. This
+*        controls the use of the "perm" arrays. If a non-zero value is
+*        given, then the indices into the "perm" array correspond to the
+*        indices of the coordinates in the returned PointSet, and the
+*        values stored in the "perm" array correspond to the indices of
+*        the coordinates in the supplied PointSet. If a zero value is
+*        given, then the indices into the "perm" array correspond to the
+*        indices of the coordinates in the supplied PointSet, and the
+*        values stored in the "perm" array correspond to the indices of
+*        the coordinates in the returnedPointSet.
+*     perm
+*        An array of int (with one element for each axis of the PointSet)
+*        which lists the axes in their new order. How this array is use
+*        depends on the value supplied for "forward".
+
+*  Notes:
+*     - Only genuine permutations of the axis order are permitted, so
+*     each axis must be referenced exactly once in the "perm" array.
+*     - If more than one axis permutation is applied to a PointSet, the
+*     effects are cumulative.
+*-
+*/
+
+/* Local Variables: */
+   double **old;                 /* Pointer to copy of old pointer array */
+   int coord;                    /* Loop counter for axes */
+   int ncoord;                   /* Number of axes */
+
+/* Check the global error status. Return without action if no data is
+   associated with the PointSet. */
+   if ( !astOK || !this->ptr ) return;
+
+/* Validate the permutation array, to check that it describes a genuine
+   permutation. */
+   CheckPerm( this, perm, "astPermPoints", status );
+
+/* Obtain the number of PointSet axes. */
+   ncoord = astGetNcoord( this );
+
+/* Allocate memory and use it to store a copy of the old pointers array for
+   the PointSet. */
+   old = astStore( NULL, this->ptr, sizeof( double * ) * (size_t) ncoord );
+
+/* Apply the new axis permutation cumulatively to the old one and store the
+   result in the PointSet. */
+   if ( astOK ) {
+      if( forward ) {
+         for ( coord = 0; coord < ncoord; coord++ ) {
+            this->ptr[ coord ] = old[ perm[ coord ] ];
+         }
+      } else {
+         for ( coord = 0; coord < ncoord; coord++ ) {
+            this->ptr[ perm[ coord ] ] = old[ coord ];
+         }
+      }
+   }
+
+/* Free the temporary copy of the old array. */
+   old = astFree( old );
+}
+
+static int ReplaceNaN( AstPointSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astReplaceNaN
+
+*  Purpose:
+*     Check for NaNs in a PointSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     int astReplaceNaNs( AstPointSet *this )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     The behaviour of this method is determined by the AST_REPLACE_NAN
+*     environment variable. If AST_REPLACE_NAN is undefined, then the
+*     method returns without action. If AST_REPLACE_NAN is "1", then the
+*     method examines the supplied PointSet and replaces any NaN values
+*     with AST__BAD. If AST_REPLACE_NAN has any other value, any NaNs in
+*     the supplied PointSet are still replaced, but in addition an error
+*     is reported.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+
+*  Returned Value:
+*     Non-zero if any NaN values were found in the PointSet. If AST_REPLACE_NAN
+*     is undefined, then zero is always returned.
+
+*  Notes:
+*    The value of the AST_REPLACE_NAN environment variable is obtained
+*    only once, when the PointSet virtual function table is first
+*    created. This value is saved for all subsequent invocations of this
+*    method.
+
+*-
+*/
+
+/* Local Variables: */
+   double **ptr;
+   double *p0;
+   double *p;
+   int ic;
+   int nc;
+   int np;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Unless the AST_REPALCE_NAN environment variable is undefined, check
+   for NaNs in the supplied PointSet, replacing any with AST__BAD. */
+   if( replace_nan != IGNORE_NANS ) {
+      ptr = astGetPoints( this );
+      if( ptr ) {
+         nc = astGetNcoord( this );
+         np = astGetNpoint( this );
+         for( ic = 0; ic < nc; ic++ ) {
+            p = ptr[ ic ];
+            p0 = p + np;
+            for( ; p < p0; p++ ) {
+               if( !astISFINITE(*p) ) {
+                  result = 1;
+                  *p = AST__BAD;
+               }
+            }
+         }
+
+/* If any NaNs were found, and AST_REPLACE_NAN is not set to "1", report
+   an error. */
+         if( result && replace_nan == REPORT_NANS ) {
+            astError( AST__ISNAN, "astReplaceNan(%s): One or more NaN values "
+                      "were encountered within an AST PointSet.", status,
+                      astGetClass( this ) );
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a PointSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     PointSet member function (over-rides the astSetAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function assigns an attribute value for a PointSet, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+*     setting
+*        Pointer to a null-terminated string specifying the new
+*        attribute value.
+*/
+
+/* Local Variables: */
+   AstPointSet *this;            /* Pointer to the PointSet structure */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PointSet structure. */
+   this = (AstPointSet *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* Use this macro to report an error if a read-only attribute has been
+   specified. */
+   if ( MATCH( "ncoord" ) ||
+        MATCH( "npoint" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+static void SetNpoint( AstPointSet *this, int npoint, int *status ) {
+/*
+*+
+*  Name:
+*     astSetNpoint
+
+*  Purpose:
+*     Reduce the number of points in a PointSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     void astSetNpoint( AstPointSet *this, int npoint )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function reduces the number of points stored in a PointSet.
+*     Points with indices beyond the new size will be discarded.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+*     npoint
+*        The new value for the number of points in the PointSet. Must be
+*        less than or equal to the original size of the PointSet, and
+*        greater than zero.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Check the new size is valid. */
+   if( npoint < 1 || npoint > this->npoint ) {
+      astError( AST__NPTIN, "astSetNpoint(%s): Number of points (%d) is "
+                "not valid.", status, astGetClass( this ), npoint );
+      astError( AST__NPTIN, "Should be in the range 1 to %d.", status, this->npoint );
+
+/* Store the new size. */
+   } else {
+      this->npoint = npoint;
+   }
+}
+
+static void SetPoints( AstPointSet *this, double **ptr, int *status ) {
+/*
+*+
+*  Name:
+*     astSetPoints
+
+*  Purpose:
+*     Associate coordinate values with a PointSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     void astSetPoints( AstPointSet *this, double **ptr )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function associates coordinate values with a PointSet by storing an
+*     array of pointers to the values within the PointSet object. A pointer to
+*     this pointer array will later be returned when astGetPoints is used to
+*     locate the coordinate values. If values are already associated with the
+*     PointSet, the array of pointers to them is over-written by the new values
+*     (any internally allocated memory holding the actual coordinate values
+*     first being freed).
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+*     ptr
+*        Pointer to an array of type double* with ncoord elements (where ncoord
+*        is the number of coordinate values per point in the PointSet). Each
+*        element of this array should point at an array of double with npoint
+*        elements (where npoint is the number of points in the PointSet),
+*        containing the values of that coordinate for each point in the set.
+*        Hence, the value of the i'th coordinate for the j'th point (where i
+*        and j are counted from zero) should be given by ptr[i][j].
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  It is the caller's responsibility to ensure that the pointer supplied
+*     points at a valid array of pointers that point at arrays of coordinate
+*     values. This is only superficially validated by this function, which then
+*     simply stores a copy of the supplied array of pointers for later use.
+*     The caller must also manage any allocation (and freeing) of memory for
+*     these coordinate values.
+*     -  This functon makes a copy of the array of pointers supplied, but does
+*     not copy the coordinate values they point at. If a PointSet containing a
+*     copy of the coordinate values is required, internal memory should be
+*     allocated within the PointSet by calling astGetPoints before storing any
+*     pointer, and then copying the values into this memory. Alternatively,
+*     using astCopy to produce a deep copy of a PointSet will also copy the
+*     coordinate values.
+*     -  A NULL pointer may be supplied as the "ptr" argument, in which case
+*     any previously stored array of pointers will be cancelled (and internal
+*     memory freed if necessary) and subsequent use of astGetPoints will then
+*     cause memory to be allocated internally by the PointSet to hold new
+*     values.
+*-
+*/
+
+/* Local Variables: */
+   int i;                        /* Loop counter for coordinates */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If the pointer supplied is not NULL, inspect each pointer in the array it
+   points at to check that none of these are NULL. This validates (to some
+   extent) the caller's data structure. Report an error and quit checking if a
+   NULL pointer is found. */
+   if ( ptr ) {
+      for ( i = 0; i < this->ncoord; i++ ) {
+         if ( !ptr[ i ] ) {
+            astError( AST__PDSIN, "astSetPoints(%s): Invalid NULL pointer in "
+                      "element %d of array of pointers to coordinate values.", status,
+                      astGetClass( this ), i );
+            break;
+         }
+      }
+   }
+
+/* Do not carry on if the data structure is obviously invalid. */
+   if ( astOK ) {
+
+/* Free any memory previously allocated to store coordinate values. */
+      this->values = (double *) astFree( (void *) this->values );
+
+/* If a new array of pointers has been provided, (re)allocate memory and store
+   a copy of the array in it, saving a pointer to this copy in the PointSet
+   structure. */
+      if ( ptr ) {
+         this->ptr = (double **) astStore( (void *) this->ptr,
+                                           (const void *) ptr,
+                                           sizeof( double * )
+                                           * (size_t) this->ncoord );
+
+/* If no pointer array was provided, free the previous one (if any). */
+      } else {
+         this->ptr = (double **) astFree( (void *) this->ptr );
+      }
+   }
+}
+
+static void SetSubPoints( AstPointSet *point1, int point, int coord,
+                          AstPointSet *point2, int *status ) {
+/*
+*+
+*  Name:
+*     astSetSubPoints
+
+*  Purpose:
+*     Associate a subset of one PointSet with another PointSet.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     void astSetSubPoints( AstPointSet *point1, int point, int coord,
+*                           AstPointSet *point2 )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function selects a subset of the coordinate values associated with
+*     one PointSet and associates them with another PointSet. The second
+*     PointSet may then be used to access the subset. Any previous coordinate
+*     value association with the second PointSet is replaced.
+
+*  Parameters:
+*     point1
+*        Pointer to the first PointSet, from which a subset is to be selected.
+*     point
+*        The index of the first point (counting from zero) which is to appear
+*        in the subset (the number of points is determined by the size of the
+*        second PointSet).
+*     coord
+*        The index of the first coordinate (counting from zero) which is to
+*        appear in the subset (the number of coordinates is determined by the
+*        size of the second PointSet).
+*     point2
+*        Second PointSet, with which the subset of coordinate values is to be
+*        associated.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  The range of points and coordinates selected must lie entirely within
+*     the first PointSet.
+*     -  This function does not make a copy of the coordinate values, but
+*     merely stores pointers to the required subset of values associated with
+*     the first PointSet. If a PointSet containing a copy of the subset's
+*     coordinate values is required, then astCopy should be used to make a
+*     deep copy from the second PointSet.
+*     -  If the first PointSet does not yet have coordinate values associated
+*     with it, then space will be allocated within it to hold values (so that
+*     the second PointSet has somewhere to point at).
+*-
+*/
+
+/* Local Variables: */
+   double ** ptr2;               /* Pointer to new pointer array */
+   double **ptr1;                /* Pointer to original pointer array */
+   int i;                        /* Loop counter for coordinates */
+   int ncoord1;                  /* Number of coordinates in first PointSet */
+   int ncoord2;                  /* Number of coordinates in second PointSet */
+   int npoint1;                  /* Number of points in first PointSet */
+   int npoint2;                  /* Number of points in second PointSet */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain the sizes of both PointSets. */
+   npoint1 = astGetNpoint( point1 );
+   npoint2 = astGetNpoint( point2 );
+   ncoord1 = astGetNcoord( point1 );
+   ncoord2 = astGetNcoord( point2 );
+
+/* Check if the range of points required lies within the first PointSet and
+   report an error if it does not. */
+   if ( astOK ) {
+      if ( ( point < 0 ) || ( point + npoint2 > npoint1 ) ) {
+         astError( AST__PTRNG, "astSetSubPoints(%s): Range of points in "
+                   "output %s (%d to %d) lies outside the input %s extent "
+                   "(0 to %d).", status,
+                   astGetClass( point1 ), astGetClass( point2 ), point,
+                   point + npoint2, astGetClass( point1 ), npoint1 );
+
+/* Similarly check that the range of coordinates is valid. */
+      } else if ( ( coord < 0 ) || ( coord + ncoord2 > ncoord1 ) ) {
+         astError( AST__CORNG, "astSetSubPoints(%s): Range of coordinates in "
+                   "output %s (%d to %d) lies outside the input %s extent "
+                   "(0 to %d).", status,
+                   astGetClass( point1 ), astGetClass( point2 ), coord,
+                   coord + ncoord2, astGetClass( point1 ), ncoord1 );
+
+/* Obtain a pointer for the coordinate values associated with the first
+   PointSet (this will cause internal memory to be allocated if it is not
+   yet associated with coordinate values). */
+      } else {
+         ptr1 = astGetPoints( point1 );
+
+/* Allocate a temporary array to hold new pointer values. */
+         ptr2 = (double **) astMalloc( sizeof( double * ) * (size_t) ncoord2 );
+
+/* Initialise this pointer array to point at the required subset of coordinate
+   values. */
+         if ( astOK ) {
+            for ( i = 0; i < ncoord2; i++ ) {
+               ptr2[ i ] = ptr1[ i + coord ] + point;
+            }
+
+/* Associate the second PointSet with this new pointer array. This will free
+   any internally allocated memory and replace any existing coordinate value
+   association. */
+            astSetPoints( point2, ptr2 );
+	 }
+
+/* Free the temporary pointer arry. */
+         ptr2 = (double **) astFree( (void * ) ptr2 );
+      }
+   }
+}
+
+static void ShowPoints( AstPointSet *this, int *status ) {
+/*
+*+
+*  Name:
+*     astShowPoints
+
+*  Purpose:
+*     Display the contents of a PointSet on standard output as a table
+*     in TOPCAT ASCII format.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     void astShowPoints( AstPointSet *this )
+
+*  Class Membership:
+*     PointSet method.
+
+*  Description:
+*     This function displays the contents of the supplied PointSet on
+*     standard output as a table in TOPCAT "ascii" format. The first row
+*     is a comment that defines the column (axis) names as AXIS1, AXIS2,
+*     etc. Each subsequent row contains the axis values for one point.
+*     Bad axis values are represented by the string "null".
+
+*  Parameters:
+*     this
+*        Pointer to the first PointSet.
+*-
+*/
+
+/* Local Variables: */
+   double **ptr;
+   double *p;
+   int ic;
+   int ip;
+   int nc;
+   int np;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get pointers to the PointSet data, the number of axes and the number
+   of points. */
+   ptr = astGetPoints( this );
+   nc = astGetNcoord( this );
+   np = astGetNpoint( this );
+
+/* Check the pointers can be used safely. */
+   if( astOK ) {
+
+/* Display the header. */
+      printf("# ");
+      for( ic = 0; ic < nc; ic++ ) {
+         printf("Axis%d ", ic + 1 );
+      }
+      printf("\n");
+
+/* Display each row. */
+      for( ip = 0; ip < np; ip++,p++ ) {
+         for( ic = 0; ic < nc; ic++ ) {
+            if( ptr[ic][ip] != AST__BAD ) {
+               printf("%.*g ", DBL_DIG, ptr[ic][ip] );
+            } else {
+               printf("%*s ", -DBL_DIG, "null");
+            }
+         }
+         printf("\n");
+      }
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a PointSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PointSet member function (over-rides the astTestAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate
+*     whether a value has been set for one of a PointSet's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Test if the name matches any of the read-only attributes of this
+   class. If it does, then return zero. */
+   if ( !strcmp( attrib, "ncoord" ) ||
+        !strcmp( attrib, "npoint" ) ) {
+      result = 0;
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+
+/*
+*att+
+*  Name:
+*     PointAccuracy
+
+*  Purpose:
+*     The absolute accuracies for all points in the PointSet.
+
+*  Type:
+*     Protected attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute holds the absolute accuracy for each axis in the
+*     PointSet. It has a separate value for each axis. It is used when
+*     comparing two PointSets using the protected astEqual method inherited
+*     from the Object class. The default value for each axis is AST__BAD
+*     which causes the a default accuracy of each axis value to be calculated
+*     as  1.0E8*min( abs(axis value)*DBL_EPSILON, DBL_MIN ).
+
+*  Applicability:
+*     PointSet
+*        All PointSets have this attribute.
+*att-
+*/
+MAKE_CLEAR(PointAccuracy,acc,AST__BAD)
+MAKE_GET(PointAccuracy,double,AST__BAD,this->acc?this->acc[axis]:AST__BAD)
+MAKE_SET(PointAccuracy,double,acc,((value!=AST__BAD)?fabs(value):AST__BAD),AST__BAD)
+MAKE_TEST(PointAccuracy,(this->acc?this->acc[axis]!=AST__BAD:0))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for PointSet objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for PointSet objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the coordinate
+*     values (if any) associated with the input PointSet.
+*/
+
+/* Local Variables: */
+   AstPointSet *in;              /* Pointer to input PointSet */
+   AstPointSet *out;             /* Pointer to output PointSet */
+   int i;                        /* Loop counter for coordinates */
+   int nval;                     /* Number of values to store */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output PointSets. */
+   in = (AstPointSet *) objin;
+   out = (AstPointSet *) objout;
+
+/* For safety, first clear any references to the input coordinate values from
+   the output PointSet. */
+   out->ptr = NULL;
+   out->values = NULL;
+   out->acc = NULL;
+
+/* Copy axis accuracies. */
+   if( in->acc ){
+      out->acc = astStore( NULL, in->acc, sizeof( double )*(size_t) in->ncoord );
+   }
+
+/* If the input PointSet is associated with coordinate values, we must
+   allocate memory in the output PointSet to hold a copy of them. */
+   if ( in->ptr ) {
+
+/* Determine the number of coordinate values to be stored and allocate memory
+   to hold them, storing a pointer to this memory in the output PointSet. */
+      nval = in->npoint * in->ncoord;
+      out->values = (double *) astMalloc( sizeof( double ) * (size_t) nval );
+
+/* If OK, also allocate memory for the array of pointers into this values
+   array, storing a pointer to this pointer array in the output PointSet. */
+      if ( astOK ) {
+         out->ptr = (double **) astMalloc( sizeof( double * )
+                                           * (size_t) in->ncoord );
+
+/* If OK, initialise the new pointer array. */
+         if ( astOK ) {
+            for ( i = 0; i < in->ncoord; i++ ) {
+               out->ptr[ i ] = out->values + ( i * in->npoint );
+            }
+
+/* If we failed to allocate the pointer array, then free the values array. */
+         } else {
+            out->values = (double *) astFree( (void *) out->values );
+         }
+      }
+
+/* Copy the values for each coordinate from the input to the output. Use a
+   memory copy to avoid floating point errors if the data are
+   un-initialised. */
+      if ( astOK ) {
+         for ( i = 0; i < in->ncoord; i++ ) {
+            (void) memcpy( (void *) out->ptr[ i ],
+                           (const void *) in->ptr[ i ],
+                           sizeof( double ) * (size_t) in->npoint );
+         }
+      }
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for PointSet objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for PointSet objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstPointSet *this;            /* Pointer to PointSet */
+
+/* Obtain a pointer to the PointSet structure. */
+   this = (AstPointSet *) obj;
+
+/* Free memory holding axis accuracies. */
+   this->acc = astFree( this->acc );
+
+/* Free any pointer array and associated coordinate values array, */
+   this->ptr = (double **) astFree( (void *) this->ptr );
+   this->values = (double *) astFree( (void *) this->values );
+
+/* Clear the remaining PointSet variables. */
+   this->npoint = 0;
+   this->ncoord = 0;
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for PointSet objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the PointSet class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the PointSet whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - It is not recommended that PointSets containing large numbers
+*     of points be written out, as the coordinate data will be
+*     formatted as text and this will not be very efficient.
+*/
+
+/* Local Constants: */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstPointSet *this;            /* Pointer to the PointSet structure */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keywords */
+   int coord;                    /* Loop counter for coordinates */
+   int i;                        /* Counter for coordinate values */
+   int ival;                     /* Integer value */
+   int makeComment;              /* Include a comment? */
+   int point;                    /* Loop counter for points */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PointSet structure. */
+   this = (AstPointSet *) this_object;
+
+/* Write out values representing the instance variables for the
+   PointSet class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Npoint. */
+/* ------- */
+   astWriteInt( channel, "Npoint", 1, 1, this->npoint,
+                "Number of points" );
+
+/* Ncoord. */
+/* ------- */
+   astWriteInt( channel, "Ncoord", 1, 1, this->ncoord,
+                "Number of coordinates per point" );
+
+/* Axis Acuracies. */
+/* --------------- */
+   for ( coord = 0; coord < this->ncoord; coord++ ) {
+      if( astTestPointAccuracy( this, coord ) ) {
+         (void) sprintf( key, "Acc%d", coord + 1 );
+         astWriteDouble( channel, key, 1, 1, astGetPointAccuracy( this, coord ),
+                         (coord == 0 ) ? "Axis accuracies..." : "" );
+      }
+   }
+
+/* Coordinate data. */
+/* ---------------- */
+/* Write an "Empty" value to indicate whether or not the PointSet
+   contains data. */
+   ival = ( this->ptr == NULL );
+   set = ( ival != 0 );
+   astWriteInt( channel, "Empty", set, 0, ival,
+                ival ? "PointSet is empty" :
+                       "PointSet contains data" );
+
+/* If it contains data, create a suitable keyword for each coordinate
+   value in turn. */
+   if ( this->ptr ) {
+      makeComment = 1;
+      i = 0;
+      for ( point = 0; point < this->npoint; point++ ) {
+         for ( coord = 0; coord < this->ncoord; coord++ ) {
+            i++;
+            (void) sprintf( key, "X%d", i );
+
+/* Write the value out if good. Only supply a comment for the first good value. */
+            if( this->ptr[ coord ][ point ] != AST__BAD ) {
+               astWriteDouble( channel, key, 1, 1, this->ptr[ coord ][ point ],
+                               ( makeComment ) ? "Coordinate values..." : "" );
+               makeComment = 0;
+            }
+         }
+      }
+   }
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAPointSet and astCheckPointSet functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(PointSet,Object)
+astMAKE_CHECK(PointSet)
+
+AstPointSet *astPointSet_( int npoint, int ncoord, const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astPointSet
+
+*  Purpose:
+*     Create a PointSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     AstPointSet *astPointSet( int npoint, int ncoord,
+*                               const char *options, ..., int *status )
+
+*  Class Membership:
+*     PointSet constructor.
+
+*  Description:
+*     This function creates a new PointSet and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     npoint
+*        The number of points to be stored in the PointSet (must be at
+*        least 1).
+*     ncoord
+*        The number of coordinate values associated with each point
+*        (must be at least 1).
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new PointSet. The syntax used is the same as
+*        for the astSet method and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of arguments may follow it in order to
+*        supply values to be substituted for these specifiers. The
+*        rules for supplying these are identical to those for the
+*        astSet method (and for the C "printf" function).
+
+*  Returned Value:
+*     A pointer to the new PointSet.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPointSet *new;             /* Pointer to new PointSet */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the PointSet, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPointSet( NULL, sizeof( AstPointSet ), !class_init,
+                          &class_vtab, "PointSet", npoint, ncoord );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   PointSet's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new PointSet. */
+   return new;
+}
+
+AstPointSet *astPointSetId_( int npoint, int ncoord,
+                             const char *options, int *status, ...) {
+/*
+*  Name:
+*     astPointSetId_
+
+*  Purpose:
+*     Create a PointSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     AstPointSet *astPointSetId_( int npoint, int ncoord,
+*                                  const char *options, ... )
+
+*  Class Membership:
+*     PointSet constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astPointSet constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astPointSet_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astPointSet_ directly, so it must be a
+*     re-implementation of it in all respects, except for the final
+*     conversion of the result to an ID value.
+
+*  Parameters:
+*     As for astPointSet_.
+
+*  Returned Value:
+*     The ID value associated with the new PointSet.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPointSet *new;             /* Pointer to new PointSet */
+   va_list args;                 /* Variable argument list */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise the PointSet, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPointSet( NULL, sizeof( AstPointSet ), !class_init,
+                          &class_vtab, "PointSet", npoint, ncoord );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   PointSet's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new PointSet. */
+   return astMakeId( new );
+}
+
+AstPointSet *astInitPointSet_( void *mem, size_t size, int init,
+                               AstPointSetVtab *vtab, const char *name,
+                               int npoint, int ncoord, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPointSet
+
+*  Purpose:
+*     Initialise a PointSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     AstPointSet *astInitPointSet( void *mem, size_t size, int init,
+*                                   AstPointSetVtab *vtab, const char *name,
+*                                   int npoint, int ncoord )
+
+*  Class Membership:
+*     PointSet initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new PointSet object. It allocates memory (if necessary) to accommodate
+*     the PointSet plus any additional data associated with the derived class.
+*     It then initialises a PointSet structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a PointSet at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the PointSet is to be created. This
+*        must be of sufficient size to accommodate the PointSet data
+*        (sizeof(PointSet)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the PointSet (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the PointSet
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the PointSet's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new PointSet.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     npoint
+*        The number of points in the PointSet (must be at least 1).
+*     ncoord
+*        The number of coordinate values associated with each point (must be
+*        at least 1).
+
+*  Returned Value:
+*     A pointer to the new PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *new;             /* Pointer to new PointSet */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitPointSetVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the initialisation values for validity, reporting an error if
+   necessary. */
+   if ( npoint < 1 ) {
+      astError( AST__NPTIN, "astInitPointSet(%s): Number of points (%d) is "
+                "not valid.", status, name, npoint );
+   } else if ( ncoord < 1 ) {
+      astError( AST__NCOIN, "astInitPointSet(%s): Number of coordinates per "
+                "point (%d) is not valid.", status, name, ncoord );
+   }
+
+/* Initialise an Object structure (the parent class) as the first component
+   within the PointSet structure, allocating memory if necessary. */
+   new = (AstPointSet *) astInitObject( mem, size, 0,
+                                        (AstObjectVtab *) vtab, name );
+
+   if ( astOK ) {
+
+/* Initialise the PointSet data. */
+/* ----------------------------- */
+/* Store the number of points and number of coordinate values per point. */
+      new->npoint = npoint;
+      new->ncoord = ncoord;
+
+/* Initialise pointers to the pointer array and associated coordinate
+   values array. */
+      new->ptr = NULL;
+      new->values = NULL;
+      new->acc = NULL;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstPointSet *astLoadPointSet_( void *mem, size_t size,
+                               AstPointSetVtab *vtab, const char *name,
+                               AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadPointSet
+
+*  Purpose:
+*     Load a PointSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     AstPointSet *astLoadPointSet( void *mem, size_t size,
+*                                   AstPointSetVtab *vtab, const char *name,
+*                                   AstChannel *channel )
+
+*  Class Membership:
+*     PointSet loader.
+
+*  Description:
+*     This function is provided to load a new PointSet using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     PointSet structure in this memory, using data read from the
+*     input Channel.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the PointSet is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        PointSet data (sizeof(PointSet)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the PointSet (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the PointSet structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstPointSet) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new PointSet. If this is NULL, a pointer
+*        to the (static) virtual function table for the PointSet class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "PointSet" is used instead.
+
+*  Returned Value:
+* A pointer to the new PointSet.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstPointSet *new;             /* Pointer to the new PointSet */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keywords */
+   double acc;                   /* Accuracy value */
+   int coord;                    /* Loop counter for coordinates */
+   int empty;                    /* PointSet empty? */
+   int i;                        /* Counter for coordinate values */
+   int point;                    /* Loop counter for points */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this PointSet. In this case the
+   PointSet belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstPointSet );
+      vtab = &class_vtab;
+      name = "PointSet";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitPointSetVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built PointSet. */
+   new = astLoadObject( mem, size, (AstObjectVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Initialise the PointSet's data pointers. */
+      new->ptr = NULL;
+      new->values = NULL;
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "PointSet" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Npoint. */
+/* ------- */
+      new->npoint = astReadInt( channel, "npoint", 1 );
+      if ( new->npoint < 1 ) new->npoint = 1;
+
+/* Ncoord. */
+/* ------- */
+      new->ncoord = astReadInt( channel, "ncoord", 1 );
+      if ( new->ncoord < 1 ) new->ncoord = 1;
+
+/* Axis Acuracies. */
+/* --------------- */
+      new->acc = NULL;
+      for ( coord = 0; coord < new->ncoord; coord++ ) {
+         (void) sprintf( key, "acc%d", coord + 1 );
+         acc = astReadDouble( channel, key, AST__BAD );
+         if( !new->acc && acc != AST__BAD ) {
+            new->acc = astMalloc( sizeof( double )*(size_t) new->ncoord );
+            if( new->acc ) {
+               for( i = 0; i < coord - 1; i++ ) new->acc[ i ] = AST__BAD;
+            }
+         }
+         if( new->acc ) new->acc[ coord ] = acc;
+      }
+
+/* Coordinate data. */
+/* ---------------- */
+/* Read a value for the "Empty" keyword to see whether the PointSet
+   contains data. */
+      empty = astReadInt( channel, "empty", 0 );
+
+/* If it does, allocate memory to hold the coordinate data and
+   pointers. */
+      if ( astOK && !empty ) {
+         new->ptr = astMalloc( sizeof( double * ) * (size_t) new->ncoord );
+         new->values = astMalloc( sizeof( double ) *
+                                  (size_t) ( new->npoint * new->ncoord ) );
+         if ( astOK ) {
+
+/* Initialise the array of pointers into the main data array. */
+            for ( coord = 0; coord < new->ncoord; coord++ ) {
+               new->ptr[ coord ] = new->values + ( coord * new->npoint );
+            }
+
+/* Create a keyword for each coordinate value to be read. */
+            i = 0;
+            for ( point = 0; point < new->npoint; point++ ) {
+               for ( coord = 0; coord < new->ncoord; coord++ ) {
+                  i++;
+                  (void) sprintf( key, "x%d", i );
+
+/* Read and assign the values. */
+                  new->ptr[ coord ][ point ] =
+                     astReadDouble( channel, key, AST__BAD );
+               }
+            }
+         }
+
+/* If an error occurred, clean up by freeing the memory allocated
+   above, thus emptying the PointSet. */
+         if ( !astOK ) {
+            new->ptr = astFree( new->ptr );
+            new->values = astFree( new->values );
+         }
+      }
+
+/* If an error occurred, clean up by deleting the new PointSet. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new PointSet pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+int astGetNpoint_( const AstPointSet *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,PointSet,GetNpoint))( this, status );
+}
+int astGetNcoord_( const AstPointSet *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,PointSet,GetNcoord))( this, status );
+}
+double **astGetPoints_( AstPointSet *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,PointSet,GetPoints))( this, status );
+}
+void astPermPoints_( AstPointSet *this, int forward, const int perm[], int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,PointSet,PermPoints))( this, forward, perm, status );
+}
+void astSetPoints_( AstPointSet *this, double **ptr, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,PointSet,SetPoints))( this, ptr, status );
+}
+void astSetNpoint_( AstPointSet *this, int npoint, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,PointSet,SetNpoint))( this, npoint, status );
+}
+void astSetSubPoints_( AstPointSet *point1, int point, int coord,
+                       AstPointSet *point2, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(point1,PointSet,SetSubPoints))( point1, point, coord, point2, status );
+}
+AstPointSet *astAppendPoints_( AstPointSet *this, AstPointSet *that, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,PointSet,AppendPoints))( this, that, status );
+}
+void astBndPoints_( AstPointSet *this, double *lbnd, double *ubnd, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,PointSet,BndPoints))( this, lbnd, ubnd, status );
+}
+
+int astReplaceNaN_( AstPointSet *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,PointSet,ReplaceNaN))( this, status );
+}
+void astShowPoints_( AstPointSet *this, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,PointSet,ShowPoints))( this, status );
+}
+
+
+
+
+
diff --git a/ast/pointset.h b/ast/pointset.h
new file mode 100644
index 0000000..422a077
--- /dev/null
+++ b/ast/pointset.h
@@ -0,0 +1,711 @@
+#if !defined( POINTSET_INCLUDED ) /* Include this file only once */
+#define POINTSET_INCLUDED
+/*
+*+
+*  Name:
+*     pointset.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the PointSet class.
+
+*  Invocation:
+*     #include "pointset.h"
+
+*  Description:
+*     This include file defines the interface to the PointSet class
+*     and provides the type definitions, function prototypes and
+*     macros, etc.  needed to use this class.
+*
+*     The PointSet class encapsulates sets of coordinate values
+*     representing points in an N-dimensional space, to which
+*     coordinate transformations may be applied. It also provides
+*     memory allocation facilities for coordinate values.
+
+*  Inheritance:
+*     The PointSet class inherits from the Object class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     Ncoord (integer)
+*        A read-only attribute that gives the number of coordinates
+*        for each point in a PointSet (i.e. the number of dimensions
+*        of the space in which the points reside). This value is
+*        determined when the PointSet is created.
+*     Npoint (integer)
+*        A read-only attribute that gives the number of points that
+*        can be stored in the PointSet. This value is determined when
+*        the PointSet is created.
+*     PointAccuracy (floating point)
+*        This stores the absolute accuracies for each axis in the PointSet.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        ClearAttrib
+*           Clear an attribute value for a PointSet.
+*        GetAttrib
+*           Get an attribute value for a PointSet.
+*        SetAttrib
+*           Set an attribute value for a PointSet.
+*        TestAttrib
+*           Test if an attribute value has been set for a PointSet.
+
+*  New Methods Defined:
+*     Public:
+*        astAppendPoints
+*           Append one PointSet to another.
+*        astBndPoints
+*           Find the axis bounds of the points in a PointSet.
+*        astGetPoints
+*           Get a pointer to the coordinate values associated with a PointSet.
+*        astPermPoints
+*           Permute coordinates within a PointSet.
+*        astSetPoints
+*           Associate coordinate values with a PointSet.
+*        astSetNpoint
+*           Reduce the size of a PointSet.
+*        astSetSubPoints
+*           Associate one PointSet with a subset of another.
+*
+*     Protected:
+*        astGetNpoint
+*           Get the number of points in a PointSet.
+*        astGetNcoord
+*           Get the number of coordinate values per point from a PointSet.
+*        astGetPointAccuracy
+*           Get the curent value of the PointAcuracy attribute for an axis.
+*        astSetPointAccuracy
+*           Set a new value for the PointAcuracy attribute for an axis.
+*        astTestPointAccuracy
+*           Test the value of the PointAcuracy attribute for an axis.
+*        astClearPointAccuracy
+*           Clear the value of the PointAcuracy attribute for an axis.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAPointSet
+*           Test class membership.
+*        astPointSet
+*           Create a PointSet.
+*
+*     Protected:
+*        astCheckPointSet
+*           Validate class membership.
+*        astInitPointSet
+*           Initialise a PointSet.
+*        astInitPointSetVtab
+*           Initialise the virtual function table for the PointSet class.
+*        astLoadPointSet
+*           Load a PointSet.
+
+*  Macros:
+*     Public:
+*        AST__BAD
+*           Bad value flag for coordinate data.
+*
+*     Protected:
+*        astISBAD
+*           Check if a value is AST__BAD or NaN.
+*        astISGOOD
+*           Check if a value is not AST__BAD or NaN.
+*        astISNAN
+*           Check if a value is NaN.
+
+*  Type Definitions:
+*     Public:
+*        AstPointSet
+*           PointSet object type.
+*
+*     Protected:
+*        AstPointSetVtab
+*           PointSet virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     30-JAN-1996 (RFWS):
+*        Original version.
+*     27-SEP-1996 (RFWS):
+*        Added external interface and I/O facilities.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitPointSetVtab method.
+*     2-NOV-2004 (DSB):
+*        Added PointAccuracy attribute.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+
+/* Configuration results. */
+/* ---------------------- */
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#include <math.h>
+
+#if !HAVE_DECL_ISNAN
+#  if HAVE_ISNAN
+     /* Seems that math.h does not include a prototype for isnan etc */
+     int isnan( double );
+#  else
+     /* isnan is not available prior to C99 so define
+        alternative macros Note multiple evaluations of "x" in these
+        macros!!! */
+#    define isnan(x) ((x) != (x))
+#  endif
+#endif
+
+#if !HAVE_DECL_ISFINITE
+#  if HAVE_ISFINITE
+     /* Seems that math.h does not include a prototype for isfinite */
+     int isfinite( double );
+#  else
+     /* isfinite is not available prior to C99 so define
+        alternative macros. Note multiple evaluations of "x" in these
+        macros!!! */
+#    define isfinite(x) (!isnan(x) && ((x) != (1.0/0.0)) && ((x) != (-1.0/0.0)))
+#  endif
+#endif
+#endif
+
+/* Macros. */
+/* ======= */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/*
+*+
+*  Name:
+*     AST__BAD
+
+*  Type:
+*     Public macro.
+
+*  Purpose:
+*     Bad value flag for coordinate data.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     const double AST__BAD
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to a const double value that is used to flag
+*     coordinate values that are "bad" (i.e. undefined or
+*     meaningless). Classes that implement coordinate transformations
+*     should test coordinate values against this value, and
+*     appropriately propagate bad values to their output.
+*-
+*/
+
+/* Define AST__BAD to be the most negative (normalised) double
+   value. */
+
+#define AST__BAD (-(DBL_MAX))
+
+/*
+*+
+*  Name:
+*     AST__NAN
+
+*  Type:
+*     Public macro.
+
+*  Purpose:
+*     A value representing the double precision IEEE NaN value.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     const double AST__NAN
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to a const double value that is used to indicate
+*     that a IEEE NaN value should be used. Note, AST__NAN itself is a finite
+*     double precision floating point value a little below the maximum
+*     allowed value for a double. This value can be used as flag to
+*     indicate that the corresponding IEEE NaN value should be used in its
+*     place.
+
+*-
+*/
+#define AST__NAN (-(0.95*DBL_MAX))
+
+/*
+*+
+*  Name:
+*     AST__NANF
+
+*  Type:
+*     Public macro.
+
+*  Purpose:
+*     A value representing the single precision IEEE NaN value.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     const double AST__NANF
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to a const float value that is used to indicate
+*     that a IEEE NaN value should be used. Note, AST__NANF itself is a finite
+*     single precision floating point value a little below the maximum
+*     allowed value for a float. This value can be used as flag to
+*     indicate that the corresponding IEEE NaN value should be used in its
+*     place.
+
+*-
+*/
+#define AST__NANF ((float)-(0.95*FLT_MAX))
+
+#if defined(astCLASS)            /* Protected */
+
+/*
+*+
+*  Name:
+*     astISNAN
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Test if a double is NaN.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     astISNAN(value)
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to a integer valued expression which is zero
+*     if and only if the supplied value equals NaN ("Not a Number").
+
+*  Parameters:
+*     value
+*        The value to be tested. This should be a double.
+
+*  Examples:
+*     if( astISNAN(x) ) x = AST__BAD;
+*        If "x" is NaN replace it with AST__BAD.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*     - On some system it is possible that the supplied macro argument
+*     "x" may be evaluated multiple times. Therefore the evaluation of "x"
+*     should have no side effects.
+*-
+*/
+
+#define astISNAN(value) isnan(value)
+
+/*
+*+
+*  Name:
+*     astISFINITE
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Test if a double is neither NaN nor Inf.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     astISFINITE(value)
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to a integer valued expression which is zero
+*     if and only if the supplied value equals NaN ("Not a Number") or Inf.
+
+*  Parameters:
+*     value
+*        The value to be tested. This should be a double.
+
+*  Examples:
+*     if( !astISFINITE(x) ) x = AST__BAD;
+*        If "x" is NaN or Inf replace it with AST__BAD.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*     - On some system it is possible that the supplied macro argument
+*     "x" may be evaluated multiple times. Therefore the evaluation of "x"
+*     should have no side effects.
+*-
+*/
+
+#define astISFINITE(value) isfinite(value)
+
+/*
+*+
+*  Name:
+*     astISGOOD
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Test if a double is neither AST__BAD, NaN or Inf.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     astISGOOD(value)
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to a integer valued expression which is zero
+*     if and only if the supplied value equals AST__BAD or is NaN ("Not a
+*     Number") or "Inf".
+
+*  Parameters:
+*     value
+*        The value to be tested. This should be a double.
+
+*  Examples:
+*     if( astISGOOD(x) ) y = x;
+*        Checks that "x" is usable before assigning it to y.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*     - On some system it is possible that the supplied macro argument
+*     "x" may be evaluated multiple times. Therefore the evaluation of "x"
+*     should have no side effects.
+*-
+*/
+
+#define astISGOOD(value) ( (value) != AST__BAD && astISFINITE(value) )
+
+
+/*
+*+
+*  Name:
+*     astISBAD
+
+*  Type:
+*     Protected macro.
+
+*  Purpose:
+*     Test if a double is either AST__BAD, NaN, or Inf.
+
+*  Synopsis:
+*     #include "pointset.h"
+*     astISBAD(value)
+
+*  Class Membership:
+*     Defined by the PointSet class.
+
+*  Description:
+*     This macro expands to a integer valued expression which is non-zero
+*     if and only if the supplied value equals AST__BAD or is NaN ("Not a
+*     Number"), or is Inf.
+
+*  Parameters:
+*     value
+*        The value to be tested. This should be a double.
+
+*  Examples:
+*     if( astISBAD(x) ) astError( ... );
+*        Reports an error if "x" is bad.
+
+*  Notes:
+*     - To avoid problems with some compilers, you should not leave
+*     any white space around the macro arguments.
+*     - On some system it is possible that the supplied macro argument
+*     "x" may be evaluated multiple times. Therefore the evaluation of "x"
+*     should have no side effects.
+*-
+*/
+
+#define astISBAD(value) ( (value) == AST__BAD || !astISFINITE(value))
+
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* PointSet structure. */
+/* ------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstPointSet {
+
+/* Attributes inherited from the parent class. */
+   AstObject object;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double **ptr;                 /* Pointer to array of pointers to values */
+   double *values;               /* Pointer to array of coordinate values */
+   int ncoord;                   /* Number of coordinate values per point */
+   int npoint;                   /* Number of points */
+   double *acc;                  /* Axis accuracies */
+} AstPointSet;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstPointSetVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstObjectVtab object_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstPointSet *(* AppendPoints)( AstPointSet *, AstPointSet *, int * );
+   double **(* GetPoints)( AstPointSet *, int * );
+   int (* GetNcoord)( const AstPointSet *, int * );
+   int (* GetNpoint)( const AstPointSet *, int * );
+   void (* BndPoints)( AstPointSet *, double *, double *, int * );
+   void (* PermPoints)( AstPointSet *, int, const int[], int * );
+   void (* SetNpoint)( AstPointSet *, int, int * );
+   void (* SetPoints)( AstPointSet *, double **, int * );
+   void (* SetSubPoints)( AstPointSet *, int, int, AstPointSet *, int * );
+   void (* ShowPoints)( AstPointSet *, int * );
+   int (* ReplaceNaN)( AstPointSet *, int * );
+
+   double (* GetPointAccuracy)( AstPointSet *, int, int * );
+   int (* TestPointAccuracy)( AstPointSet *, int, int * );
+   void (* ClearPointAccuracy)( AstPointSet *, int, int * );
+   void (* SetPointAccuracy)( AstPointSet *, int, double, int * );
+
+} AstPointSetVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstPointSetGlobals {
+   AstPointSetVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 101 ];
+} AstPointSetGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(PointSet)         /* Check class membership */
+astPROTO_ISA(PointSet)           /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstPointSet *astPointSet_( int, int, const char *, int *, ...);
+#else
+AstPointSet *astPointSetId_( int, int, const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstPointSet *astInitPointSet_( void *, size_t, int, AstPointSetVtab *,
+                               const char *, int, int, int * );
+
+/* Vtab initialiser. */
+void astInitPointSetVtab_( AstPointSetVtab *, const char *, int * );
+
+/* Loader. */
+AstPointSet *astLoadPointSet_( void *, size_t, AstPointSetVtab *,
+                               const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitPointSetGlobals_( AstPointSetGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+double **astGetPoints_( AstPointSet *, int * );
+void astPermPoints_( AstPointSet *, int, const int[], int * );
+void astSetPoints_( AstPointSet *, double **, int * );
+void astSetNpoint_( AstPointSet *, int, int * );
+void astSetSubPoints_( AstPointSet *, int, int, AstPointSet *, int * );
+AstPointSet *astAppendPoints_( AstPointSet *, AstPointSet *, int * );
+void astBndPoints_( AstPointSet *, double *, double *, int * );
+int astReplaceNaN_( AstPointSet *, int * );
+void astShowPoints_( AstPointSet *, int * );
+
+# if defined(astCLASS)           /* Protected */
+int astGetNcoord_( const AstPointSet *, int * );
+int astGetNpoint_( const AstPointSet *, int * );
+
+double astGetPointAccuracy_( AstPointSet *, int, int * );
+int astTestPointAccuracy_( AstPointSet *, int, int * );
+void astClearPointAccuracy_( AstPointSet *, int, int * );
+void astSetPointAccuracy_( AstPointSet *, int, double, int * );
+
+double astCheckNaN_( double );
+float astCheckNaNF_( float );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckPointSet(this) astINVOKE_CHECK(PointSet,this,0)
+#define astVerifyPointSet(this) astINVOKE_CHECK(PointSet,this,1)
+
+/* Test class membership. */
+#define astIsAPointSet(this) astINVOKE_ISA(PointSet,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astPointSet astINVOKE(F,astPointSet_)
+#else
+#define astPointSet astINVOKE(F,astPointSetId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitPointSet(mem,size,init,vtab,name,npoint,ncoord) \
+astINVOKE(O,astInitPointSet_(mem,size,init,vtab,name,npoint,ncoord,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitPointSetVtab(vtab,name) astINVOKE(V,astInitPointSetVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadPointSet(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadPointSet_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckPointSet to validate PointSet pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#define astGetPoints(this) \
+astINVOKE(V,astGetPoints_(astCheckPointSet(this),STATUS_PTR))
+#define astPermPoints(this,forward,perm) \
+astINVOKE(V,astPermPoints_(astCheckPointSet(this),forward,perm,STATUS_PTR))
+#define astSetPoints(this,ptr) \
+astINVOKE(V,astSetPoints_(astCheckPointSet(this),ptr,STATUS_PTR))
+#define astSetNpoint(this,np) \
+astINVOKE(V,astSetNpoint_(astCheckPointSet(this),np,STATUS_PTR))
+#define astSetSubPoints(point1,point,coord,point2) \
+astINVOKE(V,astSetSubPoints_(astCheckPointSet(point1),point,coord,astCheckPointSet(point2),STATUS_PTR))
+#define astAppendPoints(this,that) \
+astINVOKE(O,astAppendPoints_(astCheckPointSet(this),astCheckPointSet(that),STATUS_PTR))
+#define astBndPoints(this,lbnd,ubnd) \
+astINVOKE(V,astBndPoints_(astCheckPointSet(this),lbnd,ubnd,STATUS_PTR))
+#define astReplaceNaN(this) \
+astINVOKE(V,astReplaceNaN_(astCheckPointSet(this),STATUS_PTR))
+#define astShowPoints(this) \
+astINVOKE(V,astShowPoints_(astCheckPointSet(this),STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#define astGetNpoint(this) \
+astINVOKE(V,astGetNpoint_(astCheckPointSet(this),STATUS_PTR))
+#define astGetNcoord(this) \
+astINVOKE(V,astGetNcoord_(astCheckPointSet(this),STATUS_PTR))
+
+#define astClearPointAccuracy(this,axis) \
+astINVOKE(V,astClearPointAccuracy_(astCheckPointSet(this),axis,STATUS_PTR))
+#define astGetPointAccuracy(this,axis) \
+astINVOKE(V,astGetPointAccuracy_(astCheckPointSet(this),axis,STATUS_PTR))
+#define astSetPointAccuracy(this,axis,value) \
+astINVOKE(V,astSetPointAccuracy_(astCheckPointSet(this),axis,value,STATUS_PTR))
+#define astTestPointAccuracy(this,axis) \
+astINVOKE(V,astTestPointAccuracy_(astCheckPointSet(this),axis,STATUS_PTR))
+
+#define astCheckNaNF(value) astCheckNaNF_(value)
+#define astCheckNaN(value) astCheckNaN_(value)
+
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/polygon.c b/ast/polygon.c
new file mode 100644
index 0000000..1dfae39
--- /dev/null
+++ b/ast/polygon.c
@@ -0,0 +1,7087 @@
+/*
+*class++
+*  Name:
+*     Polygon
+
+*  Purpose:
+*     A polygonal region within a 2-dimensional Frame.
+
+*  Constructor Function:
+c     astPolygon
+f     AST_POLYGON
+
+*  Description:
+*     The Polygon class implements a polygonal area, defined by a
+*     collection of vertices, within a 2-dimensional Frame. The vertices
+*     are connected together by geodesic curves within the encapsulated Frame.
+*     For instance, if the encapsulated Frame is a simple Frame then the
+*     geodesics will be straight lines, but if the Frame is a SkyFrame then
+*     the geodesics will be great circles. Note, the vertices must be
+*     supplied in an order such that the inside of the polygon is to the
+*     left of the boundary as the vertices are traversed. Supplying them
+*     in the reverse order will effectively negate the polygon.
+*
+*     Within a SkyFrame, neighbouring vertices are always joined using the
+*     shortest path. Thus if an edge of 180 degrees or more in length is
+*     required, it should be split into section each of which is less
+*     than 180 degrees. The closed path joining all the vertices in order
+*     will divide the celestial sphere into two disjoint regions. The
+*     inside of the polygon is the region which is circled in an
+*     anti-clockwise manner (when viewed from the inside of the celestial
+*     sphere) when moving through the list of vertices in the order in
+*     which they were supplied when the Polygon was created (i.e. the
+*     inside is to the left of the boundary when moving through the
+*     vertices in the order supplied).
+
+*  Inheritance:
+*     The Polygon class inherits from the Region class.
+
+*  Attributes:
+*     In addition to those attributes common to all Regions, every
+*     Polygon also has the following attributes:
+*     - SimpVertices: Simplify by transforming the vertices?
+
+*  Functions:
+c     In addition to those functions applicable to all Regions, the
+c     following functions may also be applied to all Polygons:
+f     In addition to those routines applicable to all Regions, the
+f     following routines may also be applied to all Polygons:
+*
+c     - astDownsize: Reduce the number of vertices in a Polygon.
+f     - AST_DOWNSIZE: Reduce the number of vertices in a Polygon.
+c     - astConvex<X>: Create a Polygon giving the convex hull of a pixel array
+f     - AST_CONVEX<X>: Create a Polygon giving the convex hull of a pixel array
+c     - astOutline<X>: Create a Polygon outlining values in a pixel array
+f     - AST_OUTLINE<X>: Create a Polygon outlining values in a pixel array
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     26-OCT-2004 (DSB):
+*        Original version.
+*     28-MAY-2009 (DSB):
+*        Added astDownsize.
+*     29-MAY-2009 (DSB):
+*        Added astOutline<X>.
+*     30-JUN-2009 (DSB):
+*        Override astGetBounded.
+*     4-NOV-2013 (DSB):
+*        Modify RegPins so that it can handle uncertainty regions that straddle
+*        a discontinuity. Previously, such uncertainty Regions could have a huge
+*        bounding box resulting in matching region being far too big.
+*     6-DEC-2013 (DSB):
+*        Reverse the order of the vertices when the Polygon is created,
+*        if necessary, to ensure that the unnegated Polygon is bounded.
+*        The parent Region class assumes that unnegated regions are
+*        bounded.
+*     6-JAN-2014 (DSB):
+*        Free edges when clearing the cache, not when establishing a new
+*        cache, as the number of edges may have changed.
+*     10-JAN-2014 (DSB):
+*        - Remove unused parameter description in prologue of for astOutline<X>
+*     24-FEB-2014 (DSB):
+*        Added astConvex<X>.
+*     25-FEB-2014 (DSB):
+*        Added attribute SimpVertices.
+*     7-SEP-2015 (DSB):
+*        Shrink outline polygons by a small fraction of a pixel, in order
+*        to avoid placing vertices exactly on pixel edges. This is because
+*        rounding errors in subsequent code may push the vertices into
+*        neighbouring pixels, which may have bad WCS coords (e.g.
+*        vertices on the boundary of a polar cusp in an HPX map).
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Polygon
+
+/* Define a macro for testing if a pixel value <V> satisfies the requirements
+   specified by <Oper> and <Value>. Compiler optimisation should remove
+   all the "if" testing from this expression. */
+#define ISVALID(V,OperI,Value) ( \
+   ( OperI == AST__LT ) ? ( (V) < Value ) : ( \
+      ( OperI == AST__LE ) ? ( (V) <= Value ) : ( \
+         ( OperI == AST__EQ ) ? ( (V) == Value ) : ( \
+            ( OperI == AST__GE ) ? ( (V) >= Value ) : ( \
+               ( OperI == AST__NE ) ? ( (V) != Value ) : ( \
+                  (V) > Value \
+               ) \
+            ) \
+         ) \
+      ) \
+   ) \
+)
+
+/* Macros specifying whether a point is inside, outside or on the
+   boundary of the polygon. */
+#define UNKNOWN 0
+#define IN      1
+#define OUT     2
+#define ON      3
+
+/* Size of pertubation (in pixels) used to avoid placing vertices exactly
+   on a pixel edge. */
+#define DELTA 0.01
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "region.h"              /* Coordinate regions (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "box.h"                 /* Box Regions */
+#include "wcsmap.h"              /* Definitons of AST__DPI etc */
+#include "polygon.h"             /* Interface definition for this class */
+#include "mapping.h"             /* Position mappings */
+#include "unitmap.h"             /* Unit Mapping */
+#include "pal.h"                 /* SLALIB library interface */
+#include "frame.h"               /* Coordinate system description */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+/* Type definitions. */
+/* ================= */
+
+/* A structure that holds information about an edge of the new Polygon
+   being created by astDownsize. The edge is a line betweeen two of the
+   vertices of the original Polygon. */
+typedef struct Segment {
+   int i1;        /* Index of starting vertex within old Polygon */
+   int i2;        /* Index of ending vertex within old Polygon */
+   double error;  /* Max geodesic distance from any old vertex to the line */
+   int imax;      /* Index of the old vertex at which max error is reached */
+   struct Segment *next;/* Pointer to next Segment in a double link list */
+   struct Segment *prev;/* Pointer to previous Segment in a double link list */
+} Segment;
+
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstMapping *(* parent_simplify)( AstMapping *, int * );
+static void (* parent_setregfs)( AstRegion *, AstFrame *, int * );
+static void (* parent_resetcache)( AstRegion *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Polygon)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Polygon,Class_Init)
+#define class_vtab astGLOBAL(Polygon,Class_Vtab)
+#define getattrib_buff astGLOBAL(Polygon,GetAttrib_Buff)
+
+
+#include <pthread.h>
+
+
+#else
+
+static char getattrib_buff[ 51 ];
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstPolygonVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstPolygon *astPolygonId_( void *, int, int, const double *, void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+
+/* Define a macro that expands to a single prototype for function
+   FindInsidePoint for a given data type and operation. */
+#define FINDINSIDEPOINT_PROTO0(X,Xtype,Oper) \
+static void FindInsidePoint##Oper##X( Xtype, const Xtype *, const int[2], const int[2], int *, int *, int *, int * );
+
+/* Define a macro that expands to a set of prototypes for all operations
+   for function FindInsidePoint for a given data type. */
+#define FINDINSIDEPOINT_PROTO(X,Xtype) \
+FINDINSIDEPOINT_PROTO0(X,Xtype,LT) \
+FINDINSIDEPOINT_PROTO0(X,Xtype,LE) \
+FINDINSIDEPOINT_PROTO0(X,Xtype,EQ) \
+FINDINSIDEPOINT_PROTO0(X,Xtype,GE) \
+FINDINSIDEPOINT_PROTO0(X,Xtype,GT) \
+FINDINSIDEPOINT_PROTO0(X,Xtype,NE)
+
+/* Use the above macros to define all FindInsidePoint prototypes for all
+   data types and operations. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+FINDINSIDEPOINT_PROTO(LD,long double)
+#endif
+FINDINSIDEPOINT_PROTO(D,double)
+FINDINSIDEPOINT_PROTO(L,long int)
+FINDINSIDEPOINT_PROTO(UL,unsigned long int)
+FINDINSIDEPOINT_PROTO(I,int)
+FINDINSIDEPOINT_PROTO(UI,unsigned int)
+FINDINSIDEPOINT_PROTO(S,short int)
+FINDINSIDEPOINT_PROTO(US,unsigned short int)
+FINDINSIDEPOINT_PROTO(B,signed char)
+FINDINSIDEPOINT_PROTO(UB,unsigned char)
+FINDINSIDEPOINT_PROTO(F,float)
+
+/* Define a macro that expands to a single prototype for function
+   TraceEdge for a given data type and operation. */
+#define TRACEEDGE_PROTO0(X,Xtype,Oper) \
+static AstPointSet *TraceEdge##Oper##X( Xtype, const Xtype *, const int[2], const int[2], int, int, int, int, int, int * );
+
+/* Define a macro that expands to a set of prototypes for all operations
+   for function TraceEdge for a given data type. */
+#define TRACEEDGE_PROTO(X,Xtype) \
+TRACEEDGE_PROTO0(X,Xtype,LT) \
+TRACEEDGE_PROTO0(X,Xtype,LE) \
+TRACEEDGE_PROTO0(X,Xtype,EQ) \
+TRACEEDGE_PROTO0(X,Xtype,GE) \
+TRACEEDGE_PROTO0(X,Xtype,GT) \
+TRACEEDGE_PROTO0(X,Xtype,NE)
+
+/* Use the above macros to define all TraceEdge prototypes for all
+   data types and operations. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+TRACEEDGE_PROTO(LD,long double)
+#endif
+TRACEEDGE_PROTO(D,double)
+TRACEEDGE_PROTO(L,long int)
+TRACEEDGE_PROTO(UL,unsigned long int)
+TRACEEDGE_PROTO(I,int)
+TRACEEDGE_PROTO(UI,unsigned int)
+TRACEEDGE_PROTO(S,short int)
+TRACEEDGE_PROTO(US,unsigned short int)
+TRACEEDGE_PROTO(B,signed char)
+TRACEEDGE_PROTO(UB,unsigned char)
+TRACEEDGE_PROTO(F,float)
+
+/* Define a macro that expands to a single prototype for function
+   PartHull for a given data type and operation. */
+#define PARTHULL_PROTO0(X,Xtype,Oper) \
+static void PartHull##Oper##X( Xtype, const Xtype[], int, int, int, int, int, int, int, const int[2], double **, double **, int *, int * );
+
+/* Define a macro that expands to a set of prototypes for all operations
+   for function PartHull for a given data type. */
+#define PARTHULL_PROTO(X,Xtype) \
+PARTHULL_PROTO0(X,Xtype,LT) \
+PARTHULL_PROTO0(X,Xtype,LE) \
+PARTHULL_PROTO0(X,Xtype,EQ) \
+PARTHULL_PROTO0(X,Xtype,GE) \
+PARTHULL_PROTO0(X,Xtype,GT) \
+PARTHULL_PROTO0(X,Xtype,NE)
+
+/* Use the above macros to define all PartHull prototypes for all
+   data types and operations. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+PARTHULL_PROTO(LD,long double)
+#endif
+PARTHULL_PROTO(D,double)
+PARTHULL_PROTO(L,long int)
+PARTHULL_PROTO(UL,unsigned long int)
+PARTHULL_PROTO(I,int)
+PARTHULL_PROTO(UI,unsigned int)
+PARTHULL_PROTO(S,short int)
+PARTHULL_PROTO(US,unsigned short int)
+PARTHULL_PROTO(B,signed char)
+PARTHULL_PROTO(UB,unsigned char)
+PARTHULL_PROTO(F,float)
+
+/* Define a macro that expands to a single prototype for function
+   ConvexHull for a given data type and operation. */
+#define CONVEXHULL_PROTO0(X,Xtype,Oper) \
+static AstPointSet *ConvexHull##Oper##X( Xtype, const Xtype[], const int[2], int, int, int, int * );
+
+/* Define a macro that expands to a set of prototypes for all operations
+   for function ConvexHull for a given data type. */
+#define CONVEXHULL_PROTO(X,Xtype) \
+CONVEXHULL_PROTO0(X,Xtype,LT) \
+CONVEXHULL_PROTO0(X,Xtype,LE) \
+CONVEXHULL_PROTO0(X,Xtype,EQ) \
+CONVEXHULL_PROTO0(X,Xtype,GE) \
+CONVEXHULL_PROTO0(X,Xtype,GT) \
+CONVEXHULL_PROTO0(X,Xtype,NE)
+
+/* Use the above macros to define all ConvexHull prototypes for all
+   data types and operations. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+CONVEXHULL_PROTO(LD,long double)
+#endif
+CONVEXHULL_PROTO(D,double)
+CONVEXHULL_PROTO(L,long int)
+CONVEXHULL_PROTO(UL,unsigned long int)
+CONVEXHULL_PROTO(I,int)
+CONVEXHULL_PROTO(UI,unsigned int)
+CONVEXHULL_PROTO(S,short int)
+CONVEXHULL_PROTO(US,unsigned short int)
+CONVEXHULL_PROTO(B,signed char)
+CONVEXHULL_PROTO(UB,unsigned char)
+CONVEXHULL_PROTO(F,float)
+
+/* Define a macro that expands to a single prototype for function
+   FindBoxEdge for a given data type and operation. */
+#define FINDBOXEDGE_PROTO0(X,Xtype,Oper) \
+static void FindBoxEdge##Oper##X( Xtype, const Xtype[], int, int, int, int, int *, int *, int *, int * );
+
+/* Define a macro that expands to a set of prototypes for all operations
+   for function FindBoxEdge for a given data type. */
+#define FINDBOXEDGE_PROTO(X,Xtype) \
+FINDBOXEDGE_PROTO0(X,Xtype,LT) \
+FINDBOXEDGE_PROTO0(X,Xtype,LE) \
+FINDBOXEDGE_PROTO0(X,Xtype,EQ) \
+FINDBOXEDGE_PROTO0(X,Xtype,GE) \
+FINDBOXEDGE_PROTO0(X,Xtype,GT) \
+FINDBOXEDGE_PROTO0(X,Xtype,NE)
+
+/* Use the above macros to define all FindBoxEdge prototypes for all
+   data types and operations. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+FINDBOXEDGE_PROTO(LD,long double)
+#endif
+FINDBOXEDGE_PROTO(D,double)
+FINDBOXEDGE_PROTO(L,long int)
+FINDBOXEDGE_PROTO(UL,unsigned long int)
+FINDBOXEDGE_PROTO(I,int)
+FINDBOXEDGE_PROTO(UI,unsigned int)
+FINDBOXEDGE_PROTO(S,short int)
+FINDBOXEDGE_PROTO(US,unsigned short int)
+FINDBOXEDGE_PROTO(B,signed char)
+FINDBOXEDGE_PROTO(UB,unsigned char)
+FINDBOXEDGE_PROTO(F,float)
+
+
+
+
+
+
+
+
+
+/* Non-generic function prototypes. */
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *DownsizePoly( AstPointSet *, double, int, AstFrame *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstPolygon *Downsize( AstPolygon *, double, int, int * );
+static Segment *AddToChain( Segment *, Segment *, int * );
+static Segment *NewSegment( Segment *, int, int, int, int * );
+static Segment *RemoveFromChain( Segment *, Segment *, int * );
+static double Polywidth( AstFrame *, AstLineDef **, int, int, double[ 2 ], int * );
+static int GetBounded( AstRegion *, int * );
+static int IntCmp( const void *, const void * );
+static int RegPins( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+static int RegTrace( AstRegion *, int, double *, double **, int * );
+static void Cache( AstPolygon *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void EnsureInside( AstPolygon *, int * );
+static void FindMax( Segment *, AstFrame *, double *, double *, int, int, int * );
+static void RegBaseBox( AstRegion *this, double *, double *, int * );
+static void ResetCache( AstRegion *this, int * );
+static void SetPointSet( AstPolygon *, AstPointSet *, int * );
+static void SetRegFS( AstRegion *, AstFrame *, int * );
+static void SmoothPoly( AstPointSet *, int, double, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+
+static int GetSimpVertices( AstPolygon *, int * );
+static int TestSimpVertices( AstPolygon *, int * );
+static void ClearSimpVertices( AstPolygon *, int * );
+static void SetSimpVertices( AstPolygon *, int, int * );
+
+
+/* Member functions. */
+/* ================= */
+static Segment *AddToChain( Segment *head, Segment *seg, int *status ){
+/*
+*  Name:
+*     AddToChain
+
+*  Purpose:
+*     Add a Segment into the linked list of Segments, maintaining the
+*     required order in the list.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     Segment *AddToChain( Segment *head, Segment *seg, int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     The linked list of Segments maintained by astDownsize is searched
+*     from the high error end (the head), until a Segment is foound which
+*     has a lower error than the supplied segment. The supplied Segment
+*     is then inserted into the list at that point.
+
+*  Parameters:
+*     head
+*        The Segment structure at the head of the list (i.e. the segment
+*        with maximum error).
+*     seg
+*        The Segment to be added into the list.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the link head (which will have changed if "seg" has a
+*     higher error than the original head).
+
+*/
+
+/* Local Variables: */
+   Segment *tseg;
+
+/* Check the global error status. */
+   if ( !astOK ) return head;
+
+/* If the list is empty, return the supplied segment as the new list
+   head. */
+   if( !head ) {
+      head = seg;
+
+/* If the supplied segment has a higher error than the original head,
+   insert the new segment in front of the original head. */
+   } else if( seg->error > head->error ){
+      seg->next = head;
+      head->prev = seg;
+      head = seg;
+
+/* Otherwise, move down the list from the head until a segment is found
+   which has a lower error than the supplied Segment. Then insert the
+   supplied segment into the list in front of it. */
+   } else {
+      tseg = head;
+      seg->next = NULL;
+
+      while( tseg->next ) {
+         if( seg->error > tseg->next->error ) {
+            seg->next = tseg->next;
+            seg->prev = tseg;
+            tseg->next->prev = seg;
+            tseg->next = seg;
+            break;
+         }
+         tseg = tseg->next;
+      }
+
+      if( !seg->next ) {
+         tseg->next = seg;
+         seg->prev = tseg;
+      }
+   }
+
+/* Return the new head. */
+   return head;
+}
+
+static void Cache( AstPolygon *this, int *status ){
+/*
+*  Name:
+*     Cache
+
+*  Purpose:
+*     Calculate intermediate values and cache them in the Polygon structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void Cache( AstPolygon *this, int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     This function uses the PointSet stored in the parent Region to calculate
+*     some intermediate values which are useful in other methods. These
+*     values are stored within the Polygon structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Polygon.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrame *frm;       /* Pointer to base Frame in Polygon */
+   double **ptr;        /* Pointer to data in the encapsulated PointSet */
+   double end[ 2 ];     /* Start position for edge */
+   double maxwid;       /* Maximum polygon width found so far */
+   double polcen[ 2 ];  /* Polygon centre perpendicular to current edge */
+   double polwid;       /* Polygon width perpendicular to current edge */
+   double start[ 2 ];   /* Start position for edge */
+   int i;               /* Axis index */
+   int nv;              /* Number of vertices in Polygon */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Do nothing if the cached information is up to date. */
+   if( this->stale ) {
+
+/* Get a pointer to the base Frame. */
+      frm = astGetFrame( ((AstRegion *) this)->frameset, AST__BASE );
+
+/* Get the number of vertices. */
+      nv = astGetNpoint( ((AstRegion *) this)->points );
+
+/* Get pointers to the coordinate data in the parent Region structure. */
+      ptr = astGetPoints( ((AstRegion *) this)->points );
+
+/* Free any existing edge information in the Polygon structure. */
+      if( this->edges ) {
+         for( i = 0; i < nv; i++ ) {
+            this->edges[ i ] = astFree( this->edges[ i ] );
+         }
+
+/* Allocate memory to store new edge information if necessary. */
+      } else {
+         this->edges = astMalloc( sizeof( AstLineDef *)*(size_t) nv );
+         this->startsat = astMalloc( sizeof( double )*(size_t) nv );
+      }
+
+/* Check pointers can be used safely. */
+      if( this->edges ) {
+
+/* Create and store a description of each edge. Also form the total
+   distance round the polygon, and the distance from the first vertex
+   at which each edge starts. */
+         this->totlen = 0.0;
+         start[ 0 ] = ptr[ 0 ][ nv - 1 ];
+         start[ 1 ] = ptr[ 1 ][ nv - 1 ];
+
+         for( i = 0; i < nv; i++ ) {
+            end[ 0 ] = ptr[ 0 ][ i ];
+            end[ 1 ] = ptr[ 1 ][ i ];
+            this->edges[ i ] = astLineDef( frm, start, end );
+            start[ 0 ] = end[ 0 ];
+            start[ 1 ] = end[ 1 ];
+
+            this->startsat[ i ] = this->totlen;
+            this->totlen += this->edges[ i ]->length;
+         }
+
+/* We now look for a point that is inside the polygon. We want a point
+   that is well within the polygon, since points that are only just inside
+   the polygon can give numerical problems. Loop round each edge with
+   non-zero length. */
+         maxwid = -1.0;
+         for( i = 0; i < nv; i++ ) {
+            if( this->edges[ i ]->length > 0.0 ) {
+
+/* We define another line perpendicular to the current edge, passing
+   through the mid point of the edge, extending towards the inside of the
+   polygon. The following function returns the distance we can travel
+   along this line before we hit any of the other polygon edges. It also
+   puts the position corresponding to half that distance into "polcen". */
+               polwid = Polywidth( frm, this->edges, i, nv, polcen, status );
+
+/* If the width of the polygon perpendicular to the current edge is
+   greater than the width perpdeicular to any other edge, record the
+   width and also store the current polygon centre. */
+               if( polwid > maxwid && polwid != AST__BAD ) {
+                  maxwid = polwid;
+                  (this->in)[ 0 ] = polcen[ 0 ];
+                  (this->in)[ 1 ] = polcen[ 1 ];
+               }
+            }
+         }
+
+/* If no width was found it probably means that the polygon vertices were
+   given in clockwise order, resulting in the above process probing the
+   infinite extent outside the polygonal hole. In this case any point
+   outside the hole will do, so we use the current contents of the
+   "polcen" array. Set a flag indicating if the vertices are stored in
+   anti-clockwise order. */
+         if( maxwid < 0.0 ) {
+            (this->in)[ 0 ] = polcen[ 0 ];
+            (this->in)[ 1 ] = polcen[ 1 ];
+            this->acw = 0;
+         } else {
+            this->acw = 1;
+         }
+      }
+
+/* Free resources */
+      frm = astAnnul( frm );
+
+/* Indicate cached information is up to date. */
+      this->stale = 0;
+   }
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Polygon.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Polygon member function (over-rides the astClearAttrib protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     Polygon, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Polygon.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPolygon *this;             /* Pointer to the Polygon structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Polygon structure. */
+   this = (AstPolygon *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* SimpVertices. */
+/* ------------- */
+   if ( !strcmp( attrib, "simpvertices" ) ) {
+      astClearSimpVertices( this );
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+/*
+*++
+*  Name:
+c     astConvex<X>
+f     AST_CONVEX<X>
+
+*  Purpose:
+*     Create a new Polygon representing the convex hull of a 2D data grid.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "polygon.h"
+c     AstPolygon *astConvex<X>( <Xtype> value, int oper, const <Xtype> array[],
+c                                const int lbnd[2], const int ubnd[2], int starpix )
+f     RESULT = AST_CONVEX<X>( VALUE, OPER, ARRAY, LBND, UBND, STARPIX, STATUS )
+
+*  Class Membership:
+*     Polygon method.
+
+*  Description:
+*     This is a set of functions that create the shortest Polygon that
+*     encloses all pixels with a specified value within a gridded
+*     2-dimensional data array (e.g. an image).
+*
+*     A basic 2-dimensional Frame is used to represent the pixel coordinate
+*     system in the returned Polygon. The Domain attribute is set to
+*     "PIXEL", the Title attribute is set to "Pixel coordinates", and the
+*     Unit attribute for each axis is set to "pixel". All other
+*     attributes are left unset. The nature of the pixel coordinate system
+*     is determined by parameter
+c     "starpix".
+f     STARPIX.
+*
+*     You should use a function which matches the numerical type of the
+*     data you are processing by replacing <X> in the generic function
+*     name
+c     astConvex<X>
+f     AST_CONVEX<X>
+c     by an appropriate 1- or 2-character type code. For example, if you
+*     are procesing data with type
+c     "float", you should use the function astConvexF
+f     REAL, you should use the function AST_CONVEXR
+*     (see the "Data Type Codes" section below for the codes appropriate to
+*     other numerical types).
+
+*  Parameters:
+c     value
+f     VALUE = <Xtype> (Given)
+*        A data value that specifies the pixels to be included within the
+*        convex hull.
+c     oper
+f     OPER = INTEGER (Given)
+*        Indicates how the
+c        "value"
+f        VALUE
+*        parameter is used to select the required pixels. It can
+*        have any of the following values:
+c        - AST__LT: include pixels with value less than "value".
+c        - AST__LE: include pixels with value less than or equal to "value".
+c        - AST__EQ: include pixels with value equal to "value".
+c        - AST__NE: include pixels with value not equal to "value".
+c        - AST__GE: include pixels with value greater than or equal to "value".
+c        - AST__GT: include pixels with value greater than "value".
+f        - AST__LT: include pixels with value less than VALUE.
+f        - AST__LE: include pixels with value less than or equal to VALUE.
+f        - AST__EQ: include pixels with value equal to VALUE.
+f        - AST__NE: include pixels with value not equal to VALUE.
+f        - AST__GE: include pixels with value greater than or equal to VALUE.
+f        - AST__GT: include pixels with value greater than VALUE.
+c     array
+f     ARRAY( * ) = <Xtype> (Given)
+c        Pointer to a
+f        A
+*        2-dimensional array containing the data to be processed.  The
+*        numerical type of this array should match the 1- or
+*        2-character type code appended to the function name (e.g. if
+c        you are using astConvexF, the type of each array element
+c        should be "float").
+f        you are using AST_CONVEXR, the type of each array element
+f        should be REAL).
+*
+*        The storage order of data within this array should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the second dimension least rapidly
+c        (i.e. Fortran array indexing is used).
+f        (i.e. normal Fortran array storage order).
+c     lbnd
+f     LBND( 2 ) = INTEGER (Given)
+c        Pointer to an array of two integers
+f        An array
+*        containing the coordinates of the centre of the first pixel
+*        in the input grid along each dimension.
+c     ubnd
+f     UBND( 2) = INTEGER (Given)
+c        Pointer to an array of two integers
+f        An array
+*        containing the coordinates of the centre of the last pixel in
+*        the input grid along each dimension.
+*
+c        Note that "lbnd" and "ubnd" together define the shape
+f        Note that LBND and UBND together define the shape
+*        and size of the input grid, its extent along a particular
+c        (j'th) dimension being ubnd[j]-lbnd[j]+1 (assuming the
+c        index "j" to be zero-based). They also define
+f        (J'th) dimension being UBND(J)-LBND(J)+1. They also define
+*        the input grid's coordinate system, each pixel having unit
+*        extent along each dimension with integral coordinate values
+*        at its centre or upper corner, as selected by parameter
+c        "starpix".
+f        STARPIX.
+c     starpix
+f     STARPIX = LOGICAL (Given)
+*        A flag indicating the nature of the pixel coordinate system used
+*        to describe the vertex positions in the returned Polygon. If
+c        non-zero,
+f        .TRUE.,
+*        the standard Starlink definition of pixel coordinate is used in
+*        which a pixel with integer index I spans a range of pixel coordinate
+*        from (I-1) to I (i.e. pixel corners have integral pixel coordinates).
+c        If zero,
+f        If .FALSE.,
+*        the definition of pixel coordinate used by other AST functions
+c        such as astResample, astMask,
+f        such as AST_RESAMPLE, AST_MASK,
+*        etc., is used. In this definition, a pixel with integer index I
+*        spans a range of pixel coordinate from (I-0.5) to (I+0.5) (i.e.
+*        pixel centres have integral pixel coordinates).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astConvex<X>()
+f     AST_CONVEX<X> = INTEGER
+*        A pointer to the required Polygon.
+c        NULL
+f        AST__NULL
+*        is returned without error if the array contains no pixels that
+*        satisfy the criterion specified by
+c        "value" and "oper".
+f        VALUE and OPER.
+
+*  Notes:
+c     - NULL
+f     - AST__NULL
+*     will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+
+*  Data Type Codes:
+*     To select the appropriate masking function, you should
+c     replace <X> in the generic function name astConvex<X> with a
+f     replace <X> in the generic function name AST_CONVEX<X> with a
+*     1- or 2-character data type code, so as to match the numerical
+*     type <Xtype> of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - L: long int
+c     - UL: unsigned long int
+c     - I: int
+c     - UI: unsigned int
+c     - S: short int
+c     - US: unsigned short int
+c     - B: byte (signed char)
+c     - UB: unsigned byte (unsigned char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - UI: INTEGER (treated as unsigned)
+f     - S: INTEGER*2 (short integer)
+f     - US: INTEGER*2 (short integer, treated as unsigned)
+f     - B: BYTE (treated as signed)
+f     - UB: BYTE (treated as unsigned)
+*
+c     For example, astConvexD would be used to process "double"
+c     data, while astConvexS would be used to process "short int"
+c     data, etc.
+f     For example, AST_CONVEXD would be used to process DOUBLE
+f     PRECISION data, while AST_CONVEXS would be used to process
+f     short integer data (stored in an INTEGER*2 array), etc.
+f
+f     For compatibility with other Starlink facilities, the codes W
+f     and UW are provided as synonyms for S and US respectively (but
+f     only in the Fortran interface to AST).
+
+*--
+*/
+/* Define a macro to implement the function for a specific data
+   type. Note, this function cannot be a virtual function since the
+   argument list does not include a Polygon, and so no virtual function
+   table is available. */
+#define MAKE_CONVEX(X,Xtype) \
+AstPolygon *astConvex##X##_( Xtype value, int oper, const Xtype array[], \
+                             const int lbnd[2], const int ubnd[2], \
+                             int starpix, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *frm;            /* Frame in which to define the Polygon */ \
+   AstPointSet *candidate;   /* Candidate polygon vertices */ \
+   AstPolygon *result;       /* Result value to return */ \
+   int xdim;                 /* Number of pixels per row */ \
+   int ydim;                 /* Number of rows */ \
+   static double junk[ 6 ] = {0.0, 0.0, 1.0, 1.0, 0.0, 1.0 }; /* Junk poly */ \
+\
+/* Initialise. */ \
+   result = NULL; \
+   candidate = NULL; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Get the array dimensions. */ \
+   xdim = ubnd[ 0 ] - lbnd[ 0 ] + 1; \
+   ydim = ubnd[ 1 ] - lbnd[ 1 ] + 1; \
+\
+/* Get the basic concvex hull. */ \
+   if( oper == AST__LT ) { \
+      candidate = ConvexHullLT##X( value, array, lbnd, starpix, xdim, ydim, status ); \
+\
+   } else if( oper == AST__LE ) { \
+      candidate = ConvexHullLE##X( value, array, lbnd, starpix, xdim, ydim, status ); \
+\
+   } else if( oper == AST__EQ ) { \
+      candidate = ConvexHullEQ##X( value, array, lbnd, starpix, xdim, ydim, status ); \
+\
+   } else if( oper == AST__NE ) { \
+      candidate = ConvexHullNE##X( value, array, lbnd, starpix, xdim, ydim, status ); \
+\
+   } else if( oper == AST__GE ) { \
+      candidate = ConvexHullGE##X( value, array, lbnd, starpix, xdim, ydim, status ); \
+\
+   } else if( oper == AST__GT ) { \
+      candidate = ConvexHullGT##X( value, array, lbnd, starpix, xdim, ydim, status ); \
+\
+   } else if( astOK ){ \
+      astError( AST__OPRIN, "astConvex"#X": Invalid operation code " \
+                "(%d) supplied (programming error).", status, oper ); \
+   } \
+\
+/* Check some good selected values were found. */ \
+   if( candidate ) { \
+\
+/* Create a default Polygon with 3 junk vertices. */ \
+      frm = astFrame( 2, "Domain=PIXEL,Unit(1)=pixel,Unit(2)=pixel," \
+                      "Title=Pixel coordinates", status ); \
+      result = astPolygon( frm, 3, 3, junk, NULL, " ", status ); \
+\
+/* Change the PointSet within the Polygon to the one created above. */ \
+      SetPointSet( result, candidate, status ); \
+\
+/* Free resources. */ \
+      frm = astAnnul( frm ); \
+      candidate = astAnnul( candidate ); \
+   } \
+\
+/* If an error occurred, clear the returned result. */ \
+   if ( !astOK ) result = astAnnul( result ); \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_CONVEX(LD,long double)
+#endif
+MAKE_CONVEX(D,double)
+MAKE_CONVEX(L,long int)
+MAKE_CONVEX(UL,unsigned long int)
+MAKE_CONVEX(I,int)
+MAKE_CONVEX(UI,unsigned int)
+MAKE_CONVEX(S,short int)
+MAKE_CONVEX(US,unsigned short int)
+MAKE_CONVEX(B,signed char)
+MAKE_CONVEX(UB,unsigned char)
+MAKE_CONVEX(F,float)
+
+/* Undefine the macros. */
+#undef MAKE_CONVEX
+
+/*
+*  Name:
+*     ConvexHull
+
+*  Purpose:
+*     Find the convex hull enclosing selected pixels in a 2D array.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     AstPointSet *ConvexHull<Oper><X>( <Xtype> value, const <Xtype> array[],
+*                                       const int lbnd[2], int starpix,
+*                                       int xdim, int ydim, int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     This function uses an algorithm similar to "Andrew's Monotone Chain
+*     Algorithm" to create a list of vertices describing the convex hull
+*     enclosing the selected pixels in the supplied array. The vertices
+*     are returned in a PointSet.
+
+*  Parameters:
+*     value
+*        A data value that specifies the pixels to be selected.
+*     array
+*        Pointer to a 2-dimensional array containing the data to be
+*        processed. The numerical type of this array should match the 1-
+*        or 2-character type code appended to the function name.
+*     lbnd
+*        The lower pixel index bounds of the array.
+*     starpix
+*        If non-zero, the usual Starlink definition of pixel coordinate
+*        is used (integral values at pixel corners). Otherwise, the
+*        system used by other AST functions such as astResample is used
+*        (integral values at pixel centres).
+*     xdim
+*        The number of pixels along each row of the array.
+*     ydim
+*        The number of rows in the array.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A PointSet holding the vertices of the convex hull, or NULL if an
+*     error occurs.
+
+*  Notes:
+*     - The following code has been designed with a view to it being
+*     multi-threaded at some point.
+
+*/
+
+/* Define a macro to implement the function for a specific data
+   type and operation. */
+#define MAKE_CONVEXHULL(X,Xtype,Oper,OperI) \
+static AstPointSet *ConvexHull##Oper##X( Xtype value, const Xtype array[], \
+                                         const int lbnd[2], int starpix, \
+                                         int xdim, int ydim, int *status ) { \
+\
+/* Local Variables: */ \
+   AstPointSet *result; \
+   double **ptr; \
+   double *xv1; \
+   double *xv2; \
+   double *xv3; \
+   double *xv4; \
+   double *xvert; \
+   double *yv1; \
+   double *yv2; \
+   double *yv3; \
+   double *yv4; \
+   double *yvert; \
+   int nv1; \
+   int nv2; \
+   int nv3; \
+   int nv4; \
+   int nv; \
+   int xhi; \
+   int xhiymax; \
+   int xhiymin; \
+   int xlo; \
+   int xloymax; \
+   int xloymin; \
+   int yhi; \
+   int yhixmax; \
+   int yhixmin; \
+   int ylo; \
+   int yloxmax; \
+   int yloxmin; \
+\
+/* Initialise */ \
+   result = NULL; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Find the lowest Y value at any selected pixel, and find the max and \
+   min X value of the selected pixels at that lowest Y value. */ \
+   FindBoxEdge##Oper##X( value, array, xdim, ydim, 1, 1, &ylo, &yloxmax,  \
+                         &yloxmin, status ); \
+\
+/* Skip if there are no selected values in the array. */ \
+   if( ylo > 0 ) { \
+\
+/* Find the highest Y value at any selected pixel, and find the max and \
+   min X value of the selected pixels at that highest Y value. */ \
+      FindBoxEdge##Oper##X( value, array, xdim, ydim, 1, 0, &yhi, &yhixmax,  \
+                            &yhixmin, status ); \
+\
+/* Find the lowest X value at any selected pixel, and find the max and \
+   min Y value of the selected pixels at that lowest X value. */ \
+      FindBoxEdge##Oper##X( value, array, xdim, ydim, 0, 1, &xlo, &xloymax,  \
+                            &xloymin, status ); \
+\
+/* Find the highest X value at any selected pixel, and find the max and \
+   min Y value of the selected pixels at that highest X value. */ \
+      FindBoxEdge##Oper##X( value, array, xdim, ydim, 0, 0, &xhi, &xhiymax,  \
+                            &xhiymin, status ); \
+\
+/* Create a list of vertices for the bottom right corner of the bounding \
+   box of the selected pixels. */ \
+      PartHull##Oper##X( value, array, xdim, ydim, yloxmax, ylo, xhi, xhiymin, \
+                         starpix, lbnd, &xv1, &yv1, &nv1, status ); \
+\
+/* Create a list of vertices for the top right corner of the bounding \
+   box of the selected pixels. */ \
+      PartHull##Oper##X( value, array, xdim, ydim, xhi, xhiymax, yhixmax, yhi, \
+                         starpix, lbnd, &xv2, &yv2, &nv2, status ); \
+\
+/* Create a list of vertices for the top left corner of the bounding \
+   box of the selected pixels. */ \
+      PartHull##Oper##X( value, array, xdim, ydim, yhixmin, yhi, xlo, xloymax, \
+                         starpix, lbnd, &xv3, &yv3, &nv3, status ); \
+\
+/* Create a list of vertices for the bottom left corner of the bounding \
+   box of the selected pixels. */ \
+      PartHull##Oper##X( value, array, xdim, ydim, xlo, xloymin, yloxmin, ylo, \
+                         starpix, lbnd, &xv4, &yv4, &nv4, status ); \
+\
+/* Concatenate the four vertex lists and store them in the returned \
+   PointSet. */ \
+      nv =  nv1 + nv2 + nv3 + nv4; \
+      result = astPointSet( nv, 2, " ", status ); \
+      ptr = astGetPoints( result ); \
+      if( astOK ) { \
+         xvert = ptr[ 0 ]; \
+         yvert = ptr[ 1 ]; \
+\
+         memcpy( xvert, xv1, nv1*sizeof( double ) ); \
+         memcpy( yvert, yv1, nv1*sizeof( double ) ); \
+         xvert += nv1; \
+         yvert += nv1; \
+\
+         memcpy( xvert, xv2, nv2*sizeof( double ) ); \
+         memcpy( yvert, yv2, nv2*sizeof( double ) ); \
+         xvert += nv2; \
+         yvert += nv2; \
+\
+         memcpy( xvert, xv3, nv3*sizeof( double ) ); \
+         memcpy( yvert, yv3, nv3*sizeof( double ) ); \
+         xvert += nv3; \
+         yvert += nv3; \
+\
+         memcpy( xvert, xv4, nv4*sizeof( double ) ); \
+         memcpy( yvert, yv4, nv4*sizeof( double ) ); \
+      } \
+\
+/* Free resources. */ \
+      xv1 = astFree( xv1 ); \
+      xv2 = astFree( xv2 ); \
+      xv3 = astFree( xv3 ); \
+      xv4 = astFree( xv4 ); \
+      yv1 = astFree( yv1 ); \
+      yv2 = astFree( yv2 ); \
+      yv3 = astFree( yv3 ); \
+      yv4 = astFree( yv4 ); \
+   } \
+\
+/* Free the returned PointSet if an error occurred. */ \
+   if( result && !astOK ) result = astAnnul( result ); \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* Define a macro that uses the above macro to to create implementations
+   of ConvexHull for all operations. */
+#define MAKEALL_CONVEXHULL(X,Xtype) \
+MAKE_CONVEXHULL(X,Xtype,LT,AST__LT) \
+MAKE_CONVEXHULL(X,Xtype,LE,AST__LE) \
+MAKE_CONVEXHULL(X,Xtype,EQ,AST__EQ) \
+MAKE_CONVEXHULL(X,Xtype,NE,AST__NE) \
+MAKE_CONVEXHULL(X,Xtype,GE,AST__GE) \
+MAKE_CONVEXHULL(X,Xtype,GT,AST__GT)
+
+/* Expand the above macro to generate a function for each required
+   data type and operation. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKEALL_CONVEXHULL(LD,long double)
+#endif
+MAKEALL_CONVEXHULL(D,double)
+MAKEALL_CONVEXHULL(L,long int)
+MAKEALL_CONVEXHULL(UL,unsigned long int)
+MAKEALL_CONVEXHULL(I,int)
+MAKEALL_CONVEXHULL(UI,unsigned int)
+MAKEALL_CONVEXHULL(S,short int)
+MAKEALL_CONVEXHULL(US,unsigned short int)
+MAKEALL_CONVEXHULL(B,signed char)
+MAKEALL_CONVEXHULL(UB,unsigned char)
+MAKEALL_CONVEXHULL(F,float)
+
+/* Undefine the macros. */
+#undef MAKE_CONVEXHULL
+#undef MAKEALL_CONVEXHULL
+
+static AstPolygon *Downsize( AstPolygon *this, double maxerr, int maxvert,
+                             int *status ) {
+/*
+*++
+*  Name:
+c     astDownsize
+f     AST_DOWNSIZE
+
+*  Purpose:
+*     Reduce the number of vertices in a Polygon.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "polygon.h"
+c     AstPolygon *astDownsize( AstPolygon *this, double maxerr, int maxvert )
+f     RESULT = AST_DOWNSIZE( THIS, MAXERR, MAXVERT, STATUS )
+
+*  Class Membership:
+*     Polygon method.
+
+*  Description:
+*     This function returns a pointer to a new Polygon that contains a
+*     subset of the vertices in the supplied Polygon. The subset is
+*     chosen so that the returned Polygon is a good approximation to
+*     the supplied Polygon, within the limits specified by the supplied
+*     parameter values. That is, the density of points in the returned
+*     Polygon is greater at points where the curvature of the boundary of
+*     the supplied Polygon is greater.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Polygon.
+c     maxerr
+f     MAXERR = DOUBLE PRECISION (Given)
+*        The maximum allowed discrepancy between the supplied and
+*        returned Polygons, expressed as a geodesic distance within the
+*        Polygon's coordinate frame. If this is zero or less, the
+*        returned Polygon will have the number of vertices specified by
+c        maxvert.
+f        MAXVERT.
+c     maxvert
+f     MAXVERT = INTEGER (Given)
+*        The maximum allowed number of vertices in the returned Polygon.
+*        If this is less than 3, the number of vertices in the returned
+*        Polygon will be the minimum needed to achieve the maximum
+*        discrepancy specified by
+c        maxerr.
+f        MAXERR.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astDownsize()
+f     AST_DOWNSIZE = INTEGER
+*        Pointer to the new Polygon.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *frm;         /* Base Frame from the Polygon */
+   AstPointSet *pset;     /* PointSet holding vertices of downsized polygon */
+   AstPolygon *result;    /* Returned pointer to new Polygon */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the base Frame of the Polygon. */
+   frm = astGetFrame( ((AstRegion *) this)->frameset, AST__BASE );
+
+/* Create a PointSet holding the vertices of the downsized polygon. */
+   pset = DownsizePoly( ((AstRegion *) this)->points, maxerr, maxvert,
+                        frm, status );
+
+/* Take a deep copy of the supplied Polygon. */
+   result = astCopy( this );
+
+/* Change the PointSet within the result Polygon to the one created above. */ \
+   SetPointSet( result, pset, status ); \
+
+/* Free resources. */
+   frm = astAnnul( frm );
+   pset = astAnnul( pset );
+
+/* If an error occurred, annul the returned Polygon. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *DownsizePoly( AstPointSet *pset, double maxerr,
+                                  int maxvert, AstFrame *frm, int *status ) {
+/*
+*  Name:
+*     DownsizePoly
+
+*  Purpose:
+*     Reduce the number of vertices in a Polygon.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*    #include "polygon.h"
+*    AstPointSet *DownsizePoly( AstPointSet *pset, double maxerr, int maxvert,
+*                               AstFrame *frm, int *status )
+
+*  Class Membership:
+*     Polygon member function.
+
+*  Description:
+*     This function returns a pointer to a new PointSet that contains a
+*     subset of the vertices in the supplied PointSet. The subset is
+*     chosen so that the returned polygon is a good approximation to
+*     the supplied polygon, within the limits specified by the supplied
+*     parameter values. That is, the density of points in the returned
+*     polygon is greater at points where the curvature of the boundary of
+*     the supplied polygon is greater.
+
+*  Parameters:
+*     pset
+*        Pointer to the PointSet holding the polygon vertices.
+*     maxerr
+*        The maximum allowed discrepancy between the supplied and
+*        returned Polygons, expressed as a geodesic distance within the
+*        Polygon's coordinate frame. If this is zero or less, the
+*        returned Polygon will have the number of vertices specified by
+*        maxvert.
+*     maxvert
+*        The maximum allowed number of vertices in the returned Polygon.
+*        If this is less than 3, the number of vertices in the returned
+*        Polygon will be the minimum needed to achieve the maximum
+*        discrepancy specified by
+*        maxerr.
+*     frm
+*        Pointer to the Frame in which the polygon is defined.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*        Pointer to the new PointSet.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;   /* Returned pointer to new PointSet */
+   Segment *head;         /* Pointer to new polygon edge with highest error */
+   Segment *seg1;         /* Pointer to new polygon edge */
+   Segment *seg2;         /* Pointer to new polygon edge */
+   Segment *seg3;         /* Pointer to new polygon edge */
+   double **ptr;          /* Pointer to arrays of axis values */
+   double *x;             /* Pointer to array of X values for old Polygon */
+   double *xnew;          /* Pointer to array of X values for new Polygon */
+   double *y;             /* Pointer to array of Y values for old Polygon */
+   double *ynew;          /* Pointer to array of Y values for new Polygon */
+   double x1;             /* Lowest X value at any vertex */
+   double x2;             /* Highest X value at any vertex */
+   double y1;             /* Lowest Y value at any vertex */
+   double y2;             /* Highest Y value at any vertex */
+   int *newpoly;          /* Holds indices of retained input vertices */
+   int i1;                /* Index of first vertex added to output polygon */
+   int i1x;               /* Index of vertex with lowest X */
+   int i1y;               /* Index of vertex with lowest Y */
+   int i2;                /* Index of second vertex added to output polygon */
+   int i2x;               /* Index of vertex with highest X */
+   int i2y;               /* Index of vertex with highest Y */
+   int i3;                /* Index of third vertex added to output polygon */
+   int iadd;              /* Normalised vertex index */
+   int iat;               /* Index at which to store new vertex index */
+   int newlen;            /* Number of vertices currently in new Polygon */
+   int nv;                /* Number of vertices in old Polygon */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the number of vertices in the supplied polygon. */
+   nv = astGetNpoint( pset );
+
+/* If the maximum error allowed is zero, and the maximum number of
+   vertices is equal to or greater than the number in the supplied
+   polygon, just return a deep copy of the supplied PointSet. */
+   if( maxerr <= 0.0 && ( maxvert < 3 || maxvert >= nv ) ) {
+      result = astCopy( pset );
+
+/* Otherwise... */
+   } else {
+
+/* Get pointers to the X and Y arrays holding the vertex coordinates in
+   the supplied polygon. */
+      ptr = astGetPoints( pset );
+      x = ptr[ 0 ];
+      y = ptr[ 1 ];
+
+/* Allocate memory for an array to hold the original indices of the vertices
+   to be used to create the returned Polygon. This array is expanded as
+   needed. */
+      newpoly = astMalloc( 10*sizeof( int ) );
+
+/* Check the pointers can be used safely. */
+      if( astOK ) {
+
+/* We first need to decide on three widely spaced vertices which form a
+   reasonable triangular approximation to the whole polygon. First find
+   the vertices with the highest and lowest Y values, and the highest and
+   lowest X values. */
+         x1 = DBL_MAX;
+         x2 = -DBL_MAX;
+         y1 = DBL_MAX;
+         y2 = -DBL_MAX;
+
+         i1y = i1x = 0;
+         i2y = i2x = nv/2;
+
+         for( i3 = 0; i3 < nv; i3++ ) {
+            if( y[ i3 ] < y1 ) {
+               y1 = y[ i3 ];
+               i1y = i3;
+            } else if( y[ i3 ] > y2 ) {
+               y2 = y[ i3 ];
+               i2y = i3;
+            }
+
+            if( x[ i3 ] < x1 ) {
+               x1 = x[ i3 ];
+               i1x = i3;
+            } else if( x[ i3 ] > x2 ) {
+               x2 = x[ i3 ];
+               i2x = i3;
+            }
+         }
+
+/* Use the axis which spans the greater range. */
+         if( y2 - y1 > x2 - x1 ) {
+            i1 = i1y;
+            i2 = i2y;
+         } else {
+            i1 = i1x;
+            i2 = i2x;
+         }
+
+/* The index with vertex i1 is definitely going to be one of our three
+   vertices. We are going to use the line from i1 to i2 to choose the two
+   other vertices to use. Create a structure describing the segment of the
+   Polygon from the lowest value on the selected axis (X or Y) to the
+   highest value. As always, the polygons is traversed in an anti-clockwise
+   direction. */
+         seg1 = NewSegment( NULL, i1, i2, nv, status );
+
+/* Find the vertex within this segment which is furthest away from the
+   line on the right hand side (as moving from vertex i1 to vertex i2). */
+         FindMax( seg1, frm, x, y, nv, 0, status );
+
+/* Likewise, create a structure describing the remained of the Polygon
+   (i.e. the segment from the highest value on the selected axis to the
+   lowest value). Then find the vertex within this segment which is
+   furthest away from the line on the right hand side. */
+         seg2 = NewSegment( NULL, i2, i1, nv, status );
+         FindMax( seg2, frm, x, y, nv, 0, status );
+
+/* Select the segment for which the found vertex is furthest from the
+   line. */
+         if( seg2->error > seg1->error ) {
+
+/* If the second segment, we will use the vertex that is farthest from
+   the line as one of our threee vertices. To ensure that movement from
+   vertex i1 to i2 to i3 is anti-clockwise, we must use this new vertex
+   as vertex i3, not i2. */
+            i3 = seg2->imax;
+
+/* Create a description of the polygon segment from vertex i1 to i3, and
+   find the vertex which is furthest to the right of the line joining the
+   two vertices. We use this as the middle vertex (i2). */
+            seg1 = NewSegment( seg1, i1, i3, nv, status );
+            FindMax( seg1, frm, x, y, nv, 0, status );
+            i2 = seg1->imax;
+
+/* Do the same if we are choosing the other segment, ordering the
+   vertices to retain anti-clockwise movement from i1 to i2 to i3. */
+         } else {
+            i2 = seg1->imax;
+            seg1 = NewSegment( seg1, i2, i1, nv, status );
+            FindMax( seg1, frm, x, y, nv, 0, status );
+            i3 = seg1->imax;
+         }
+
+/* Ensure the vertex indices are in the first cycle. */
+         if( i2 >= nv ) i2 -= nv;
+         if( i3 >= nv ) i3 -= nv;
+
+/* Create Segment structures to describe each of these three edges. */
+         seg1 = NewSegment( seg1, i1, i2, nv, status );
+         seg2 = NewSegment( seg2, i2, i3, nv, status );
+         seg3 = NewSegment( NULL, i3, i1, nv, status );
+
+/* Record these 3 vertices in an array holding the original indices of
+   the vertices to be used to create the returned Polygon. */
+         newpoly[ 0 ] = i1;
+         newpoly[ 1 ] = i2;
+         newpoly[ 2 ] = i3;
+
+/* Indicate the new polygon currently has 3 vertices. */
+         newlen = 3;
+
+/* Search the old vertices between the start and end of segment 3, looking
+   for the vertex which lies furthest from the line of segment 3. The
+   residual between this point and the line is stored in the Segment
+   structure, as is the index of the vertex at which this maximum residual
+   occurred. */
+         FindMax( seg3, frm, x, y, nv, 1, status );
+
+/* The "head" variable points to the head of a double linked list of
+   Segment structures. This list is ordered by residual, so that the
+   Segment with the maximum residual is at the head, and the Segment
+   with the minimum residual is at the tail. Initially "seg3" is at the
+   head. */
+         head = seg3;
+
+/* Search the old vertices between the start and end of segment 1, looking
+   for the vertex which lies furthest from the line of segment 1. The
+   residual between this point and the line is stored in the Segment
+   structure, as is the index of the vertex at which this maximum residual
+   occurred. */
+         FindMax( seg1, frm, x, y, nv, 1, status );
+
+/* Insert segment 1 into the linked list of Segments, at a position that
+   maintains the ordering of the segments by error. Thus the head of the
+   list will still have the max error. */
+         head = AddToChain( head, seg1, status );
+
+/* Do the same for segment 2. */
+         FindMax( seg2, frm, x, y, nv, 1, status );
+         head = AddToChain( head, seg2, status );
+
+/* If the maximum allowed number of vertices in the output Polygon is
+   less than 3, allow any number of vertices up to the number in the
+   input Polygon (termination will then be determined just by "maxerr"). */
+         if( maxvert < 3 ) maxvert = nv;
+
+/* Loop round adding an extra vertex to the returned Polygon until the
+   maximum residual between the new and old polygons is no more than
+   "maxerr". Abort early if the specified maximum number of vertices is
+   reached. */
+         while( head->error > maxerr && newlen < maxvert ) {
+
+/* The segment at the head of the list has the max error (that is, it is
+   the segment that departs most from the supplied Polygon). To make the
+   new polygon a better fit to the old polygon, we add the vertex that is
+   furthest away from this segment to the new polygon. Remember that a
+   polygon is cyclic so if the vertex has an index that is greater than the
+   number of vertices in the old polygon, reduce the index by the number
+   of vertices in the old polygon. */
+            iadd = head->imax;
+            if( iadd >= nv ) iadd -= nv;
+            iat = newlen++;
+            newpoly = astGrow( newpoly, newlen, sizeof( int ) );
+            if( !astOK ) break;
+            newpoly[ iat ] = iadd;
+
+/* We now split the segment that had the highest error into two segments.
+   The split occurs at the vertex that had the highest error. */
+            seg1 = NewSegment( NULL, head->imax, head->i2, nv, status );
+            seg2 = head;
+            seg2->i2 = head->imax;
+
+/* We do not know where these two new segments should be in the ordered
+   linked list, so remove them from the list. */
+            head = RemoveFromChain( head, seg1, status );
+            head = RemoveFromChain( head, seg2, status );
+
+/* Find the vertex that deviates most from the first of these two new
+   segments, and then add the segment into the list of vertices, using
+   the maximum deviation to determine the position of the segment within
+   the list. */
+            FindMax( seg1, frm, x, y, nv, 1, status );
+            head = AddToChain( head, seg1, status );
+
+/* Do the same for the second new segment. */
+            FindMax( seg2, frm, x, y, nv, 1, status );
+            head = AddToChain( head, seg2, status );
+         }
+
+/* Now we have reached the required accuracy, free resources. */
+         while( head ) {
+            seg1 = head;
+            head = head->next;
+            seg1 = astFree( seg1 );
+         }
+
+/* If no vertices have been left out, return a deep copy of the supplied
+   PointSet. */
+         if( newlen == nv ) {
+            result = astCopy( pset );
+
+/* Otherwise, sort the indices of the vertices to be retained so that they
+   are in the same order as they were in the supplied Polygon. */
+         } else if( astOK ){
+            qsort( newpoly, newlen, sizeof( int ), IntCmp );
+
+/* Create a new PointSet and get pointers to its axis values. */
+            result = astPointSet( newlen, 2, " ", status );
+            ptr = astGetPoints( result );
+            xnew = ptr[ 0 ];
+            ynew = ptr[ 1 ];
+
+/* Copy the axis values for the retained vertices from the old to the new
+   PointSet. */
+            if( astOK ) {
+               for( iat = 0; iat < newlen; iat++ ) {
+                  *(xnew++) = x[ newpoly[ iat ] ];
+                  *(ynew++) = y[ newpoly[ iat ] ];
+               }
+            }
+         }
+      }
+
+/* Free resources. */
+      newpoly = astFree( newpoly );
+   }
+
+/* If an error occurred, annul the returned PointSet. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void EnsureInside( AstPolygon *this, int *status ){
+/*
+*  Name:
+*     EnsureInside
+
+*  Purpose:
+*     Ensure the unnegated Polygon represents the inside of the polygon.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void EnsureInside( AstPolygon *this, int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     Reversing the order of the vertices of a Polygon is like negating
+*     the Polygon. But the parent Region class assumes that an unnegated
+*     region bounded by closed curves (e.g. boxes, circles, ellipses, etc)
+*     is bounded. So we need to have a way to ensure that a Polygon also
+*     follows this convention. So this function reverses the order of the
+*     vertices in the Polygon, if necessary, to ensure that the unnegated
+*     Polygon is bounded.
+
+*  Parameters:
+*     this
+*        The Polygon.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstRegion *this_region;
+   double **ptr;
+   double *p;
+   double *q;
+   double tmp;
+   int bounded;
+   int i;
+   int j;
+   int jmid;
+   int negated;
+   int np;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Is the unnegated Polygon unbounded? If so, we need to reverse the
+   vertices. */
+   bounded = astGetBounded( this );
+   negated = astGetNegated( this );
+   if( ( bounded && negated ) || ( !bounded && !negated ) ) {
+      this_region = (AstRegion *) this;
+
+/* Get a pointer to the arrays holding the coordinates at the Polygon
+   vertices. */
+      ptr = astGetPoints( this_region->points );
+
+/* Get the number of vertices. */
+      np = astGetNpoint( this_region->points );
+
+/* Store the index of the last vertex to swap. For odd "np" the central
+   vertex does not need to be swapped. */
+      jmid = np/2;
+
+/* Loop round the two axes spanned by the Polygon. */
+      for( i = 0; i < 2; i++ ) {
+
+/* Get pointers to the first pair of axis values to be swapped - i.e. the
+   first and last axis values. */
+         p = ptr[ i ];
+         q = p + np - 1;
+
+/* Loop round all pairs of axis values. */
+         for( j = 0; j < jmid; j++ ) {
+
+/* Swap the pair. */
+            tmp = *p;
+            *(p++) = *q;
+            *(q--) = tmp;
+         }
+      }
+
+/* Invert the value of the "Negated" attribute to cancel out the effect
+   of the above vertex reversal. */
+      astNegate( this );
+
+/* Indicate the cached information in the Polygon structure is stale. */
+      this->stale = 1;
+   }
+}
+
+/*
+*  Name:
+*     FindBoxEdge
+
+*  Purpose:
+*     Find an edge of the bounding box containing the selected pixels.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void FindBoxEdge<Oper><X>( <Xtype> value, const <Xtype> array[],
+*                                int xdim, int ydim, int axis, int low,
+*                                int *val, int *valmax, int *valmin,
+*                                int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     This function search for an edge of the bounding box containing the
+*     selected pixels in the supplied array.
+
+*  Parameters:
+*     value
+*        A data value that specifies the pixels to be selected.
+*     array
+*        Pointer to a 2-dimensional array containing the data to be
+*        processed. The numerical type of this array should match the 1-
+*        or 2-character type code appended to the function name.
+*     xdim
+*        The number of pixels along each row of the array.
+*     ydim
+*        The number of rows in the array.
+*     axis
+*        The index (0 or 1) of the pixel axis perpendicular to the edge of the
+*        bounding box being found.
+*     low
+*        If non-zero, the lower edge of the bounding box on the axis
+*        specified by "axis" is found and returned. Otherwise, the higher
+*        edge of the bounding box on the axis specified by "axis" is
+*        found and returned.
+*     val
+*        Address of an int in which to return the value on axis "axis" of
+*        the higher or lower (as specified by "low") edge of the bounding
+*        box.
+*     valmax
+*        Address of an int in which to return the highest value on axis
+*        "1-axis" of the selected pixels on the returned edge.
+*     valmin
+*        Address of an int in which to return the lowest value on axis
+*        "1-axis" of the selected pixels on the returned edge.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes;
+*     - Zero is returned for "*val" if no good  values are found, or if
+*     an error occurs.
+
+*/
+
+/* Define a macro to implement the function for a specific data
+   type and operation. */
+#define MAKE_FINDBOXEDGE(X,Xtype,Oper,OperI) \
+static void FindBoxEdge##Oper##X( Xtype value, const Xtype array[], int xdim, \
+                                  int ydim, int axis, int low, int *val, \
+                                  int *valmax, int *valmin, int *status ) { \
+\
+/* Local Variables: */ \
+   int astep; \
+   int bstep; \
+   int a0; \
+   int a1; \
+   int b0; \
+   int b1; \
+   int inc; \
+   int a; \
+   int b; \
+   const Xtype *pc; \
+\
+/* Initialise. */ \
+   *val = 0; \
+   *valmin = 0; \
+   *valmax = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* If we are finding an edge that is parallel to the X axis... */ \
+   if(  axis ) { \
+\
+/* Get the vector step between adjacent pixels on the selected axis, and \
+   on the other axis. */ \
+      astep = xdim; \
+      bstep = 1; \
+\
+/* Get the first and last value to check on the selected axis, and the  \
+   increment between checks. */ \
+      if( low ) { \
+         a0 = 1; \
+         a1 = ydim; \
+         inc = 1; \
+      } else { \
+         a0 = ydim; \
+         a1 = 1; \
+         inc = -1; \
+      } \
+\
+/* The first and last value to check on the other axis. */ \
+      b0 = 1; \
+      b1 = xdim; \
+\
+/* Do the same if we are finding an edge that is parallel to the Y axis. */ \
+   } else {  \
+      astep = 1; \
+      bstep = xdim; \
+\
+      if( low ) { \
+         a0 = 1; \
+         a1 = xdim; \
+         inc = 1; \
+      } else { \
+         a0 = xdim; \
+         a1 = 1; \
+         inc = -1; \
+      } \
+\
+      b0 = 1; \
+      b1 = ydim; \
+   }  \
+\
+/* Loop round the axis values. */ \
+   a = a0; \
+   while( 1 ) { \
+\
+/* Get a pointer to the first value to be checked at this axis value. */ \
+      pc = array + (a - 1)*astep + (b0 - 1)*bstep; \
+\
+/* Scan the other axis to find the first and last selected pixel. */ \
+      for( b = b0; b <= b1; b++ ) { \
+         if( ISVALID(*pc,OperI,value) ) {  \
+            if( *valmin == 0 ) *valmin = b; \
+            *valmax = b; \
+         } \
+         pc += bstep; \
+      } \
+\
+/* If any selected pixels were found, store the axis value and exit. */ \
+      if( *valmax ) { \
+         *val = a; \
+         break; \
+      } \
+\
+/* Move on to the next axis value. */ \
+      if( a != a1 ) { \
+         a += inc; \
+      } else { \
+         break; \
+      } \
+\
+   } \
+}
+
+/* Define a macro that uses the above macro to to create implementations
+   of FindBoxEdge for all operations. */
+#define MAKEALL_FINDBOXEDGE(X,Xtype) \
+MAKE_FINDBOXEDGE(X,Xtype,LT,AST__LT) \
+MAKE_FINDBOXEDGE(X,Xtype,LE,AST__LE) \
+MAKE_FINDBOXEDGE(X,Xtype,EQ,AST__EQ) \
+MAKE_FINDBOXEDGE(X,Xtype,NE,AST__NE) \
+MAKE_FINDBOXEDGE(X,Xtype,GE,AST__GE) \
+MAKE_FINDBOXEDGE(X,Xtype,GT,AST__GT)
+
+/* Expand the above macro to generate a function for each required
+   data type and operation. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKEALL_FINDBOXEDGE(LD,long double)
+#endif
+MAKEALL_FINDBOXEDGE(D,double)
+MAKEALL_FINDBOXEDGE(L,long int)
+MAKEALL_FINDBOXEDGE(UL,unsigned long int)
+MAKEALL_FINDBOXEDGE(I,int)
+MAKEALL_FINDBOXEDGE(UI,unsigned int)
+MAKEALL_FINDBOXEDGE(S,short int)
+MAKEALL_FINDBOXEDGE(US,unsigned short int)
+MAKEALL_FINDBOXEDGE(B,signed char)
+MAKEALL_FINDBOXEDGE(UB,unsigned char)
+MAKEALL_FINDBOXEDGE(F,float)
+
+/* Undefine the macros. */
+#undef MAKE_FINDBOXEDGE
+#undef MAKEALL_FINDBOXEDGE
+
+/*
+*  Name:
+*     FindInsidePoint
+
+*  Purpose:
+*     Find a point that is inside the required outline.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void FindInsidePoint<Oper><X>( <Xtype> value, const <Xtype> array[],
+*                                    const int lbnd[ 2 ], const int ubnd[ 2 ],
+*                                    int *inx, int *iny, int *iv,
+*                                    int *status );
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     The central pixel in the array is checked to see if its value meets
+*     the requirements implied by <Oper> and "value". If so, its pixel
+*     indices and vector index are returned> if not, a spiral search is
+*     made until such a pixel value is found.
+
+*  Parameters:
+*     value
+*        The data value defining valid pixels.
+*     array
+*        The data array.
+*     lbnd
+*        The lower pixel index bounds of the array.
+*     ubnd
+*        The upper pixel index bounds of the array.
+*     inx
+*        Pointer to an int in which to return the X pixel index of the
+*        first point that meets the requirements implied by <oper> and
+*        "value".
+*     iny
+*        Pointer to an int in which to return the Y pixel index of the
+*        first point that meets the requirements implied by <oper> and
+*        "value".
+*     iv
+*        Pointer to an int in which to return the vector index of the
+*        first point that meets the requirements implied by <oper> and
+*        "value".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - <Oper> must be one of LT, LE, EQ, NE, GE, GT.
+
+
+*/
+
+/* Define a macro to implement the function for a specific data
+   type and operation. */
+#define MAKE_FINDINSIDEPOINT(X,Xtype,Oper,OperI) \
+static void FindInsidePoint##Oper##X( Xtype value, const Xtype array[], \
+                                      const int lbnd[ 2 ], const int ubnd[ 2 ], \
+                                      int *inx, int *iny, int *iv, \
+                                      int *status ){ \
+\
+/* Local Variables: */ \
+   const Xtype *pv;  /* Pointer to next data value to test */ \
+   const char *text; /* Pointer to text describing oper */ \
+   int cy;           /* Central row index */ \
+   int iskin;        /* Index of spiral layer being searched */ \
+   int nskin;        /* Number of spiral layers to search */ \
+   int nx;           /* Pixel per row */ \
+   int tmp;          /* Temporary storage */ \
+   int xhi;          /* High X pixel index bound of current skin */ \
+   int xlo;          /* Low X pixel index bound of current skin */ \
+   int yhi;          /* High X pixel index bound of current skin */ \
+   int ylo;          /* Low X pixel index bound of current skin */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Find number of pixels in one row of the array. */ \
+   nx = ( ubnd[ 0 ] - lbnd[ 0 ] + 1 ); \
+\
+/* Find the pixel indices of the central pixel */ \
+   *inx = ( ubnd[ 0 ] + lbnd[ 0 ] )/2; \
+   *iny = ( ubnd[ 1 ] + lbnd[ 1 ] )/2; \
+\
+/* Initialise the vector index and pointer to refer to the central pixel. */ \
+   *iv = ( *inx - lbnd[ 0 ] ) + nx*( *iny - lbnd[ 1 ] ) ; \
+   pv = array + (*iv); \
+\
+/* Test the pixel value, returning if it is valid. This \
+   relies on the compiler optimisation to remove the "if" statements \
+   for all but the operation appropriate to the function being defined. */ \
+   if( OperI == AST__LT ) { \
+      if( *pv < value ) return; \
+\
+   } else if( OperI == AST__LE ) { \
+      if( *pv <= value ) return; \
+\
+   } else if( OperI == AST__EQ ) { \
+      if( *pv == value ) return; \
+\
+   } else if( OperI == AST__NE ) { \
+      if( *pv != value ) return; \
+\
+   } else if( OperI == AST__GE ) { \
+      if( *pv >= value ) return; \
+\
+   } else { \
+      if( *pv > value ) return; \
+   } \
+\
+/* The central pixel is invalid if we arrive here. So we need to do a \
+   spiral search out from the centre looking for a valid pixel. Record \
+   the central row index (this is the row at which we jump to the next \
+   outer skin when doing the spiral search below). */ \
+   cy = *iny; \
+\
+/* Find how many skins can be searched as part of the spiral search \
+   before the edge of the array is encountered. */ \
+   nskin = ubnd[ 0 ] - *inx; \
+   tmp = *inx - lbnd[ 0 ]; \
+   if( tmp < nskin ) nskin = tmp; \
+   tmp = ubnd[ 1 ] - *iny; \
+   if( tmp < nskin ) nskin = tmp; \
+   tmp = *iny - lbnd[ 1 ]; \
+   if( tmp < nskin ) nskin = tmp; \
+\
+/* Initialise the skin box bounds to be just the central pixel. */ \
+   xlo = xhi = *inx; \
+   ylo = yhi = *iny; \
+\
+/* Loop round each skin looking for a valid test pixel. */ \
+   for( iskin = 0; iskin < nskin; iskin++ ) { \
+\
+/* Increment the upper and lower bounds of the box forming the next \
+   skin. */ \
+      xhi++; \
+      xlo--; \
+      yhi++; \
+      ylo--; \
+\
+/* Initialise things for the first pixel in the new skin by moving one \
+   pixel to the right. */ \
+      pv++; \
+      (*iv)++; \
+      (*inx)++; \
+\
+/* Move up the right hand edge of the box corresponding to the current \
+   skin. We start at the middle of the right hand edge. */ \
+      for( *iny = cy; *iny <= yhi; (*iny)++ ) { \
+\
+/* Test the pixel value, returning if it is valid. This relies on the \
+   compiler optimisation to remove the "if" statements for all but the \
+   operation appropriate to the function being defined. */ \
+         if( OperI == AST__LT ) { \
+            if( *pv < value ) return; \
+\
+         } else if( OperI == AST__LE ) { \
+            if( *pv <= value ) return; \
+\
+         } else if( OperI == AST__EQ ) { \
+            if( *pv == value ) return; \
+\
+         } else if( OperI == AST__NE ) { \
+            if( *pv != value ) return; \
+\
+         } else if( OperI == AST__GE ) { \
+            if( *pv >= value ) return; \
+\
+         } else { \
+            if( *pv > value ) return; \
+         } \
+\
+/* Move up a pixel. */ \
+         pv += nx; \
+         *iv += nx; \
+      } \
+\
+/* Move down a pixel so that *iny == yhi. */ \
+      pv -= nx; \
+      *iv -= nx; \
+      (*iny)--; \
+\
+/* Move left along the top edge of the box corresponding to the current \
+   skin. */ \
+      for( *inx = xhi; *inx >= xlo; (*inx)-- ) { \
+\
+/* Test the pixel value, returning if it is valid. */ \
+         if( OperI == AST__LT ) { \
+            if( *pv < value ) return; \
+\
+         } else if( OperI == AST__LE ) { \
+            if( *pv <= value ) return; \
+\
+         } else if( OperI == AST__EQ ) { \
+            if( *pv == value ) return; \
+\
+         } else if( OperI == AST__NE ) { \
+            if( *pv != value ) return; \
+\
+         } else if( OperI == AST__GE ) { \
+            if( *pv >= value ) return; \
+\
+         } else { \
+            if( *pv > value ) return; \
+         } \
+\
+/* Move left a pixel. */ \
+         pv--; \
+         (*iv)--; \
+      } \
+\
+/* Move right a pixel so that *inx == xlo. */ \
+      pv++; \
+      (*iv)++; \
+      (*inx)++; \
+\
+/* Move down along the left hand edge of the box corresponding to the current \
+   skin. */ \
+      for( *iny = yhi; *iny >= ylo; (*iny)-- ) { \
+\
+/* Test the pixel value, returning if it is valid. */ \
+         if( OperI == AST__LT ) { \
+            if( *pv < value ) return; \
+\
+         } else if( OperI == AST__LE ) { \
+            if( *pv <= value ) return; \
+\
+         } else if( OperI == AST__EQ ) { \
+            if( *pv == value ) return; \
+\
+         } else if( OperI == AST__NE ) { \
+            if( *pv != value ) return; \
+\
+         } else if( OperI == AST__GE ) { \
+            if( *pv >= value ) return; \
+\
+         } else { \
+            if( *pv > value ) return; \
+         } \
+\
+/* Move down a pixel. */ \
+         pv -= nx; \
+         *iv -= nx; \
+      } \
+\
+/* Move up a pixel so that *iny == ylo. */ \
+      pv += nx; \
+      *iv += nx; \
+      (*iny)++; \
+\
+/* Move right along the bottom edge of the box corresponding to the current \
+   skin. */ \
+      for( *inx = xlo; *inx <= xhi; (*inx)++ ) { \
+\
+/* Test the pixel value, returning if it is valid. */ \
+         if( OperI == AST__LT ) { \
+            if( *pv < value ) return; \
+\
+         } else if( OperI == AST__LE ) { \
+            if( *pv <= value ) return; \
+\
+         } else if( OperI == AST__EQ ) { \
+            if( *pv == value ) return; \
+\
+         } else if( OperI == AST__NE ) { \
+            if( *pv != value ) return; \
+\
+         } else if( OperI == AST__GE ) { \
+            if( *pv >= value ) return; \
+\
+         } else { \
+            if( *pv > value ) return; \
+         } \
+\
+/* Move right a pixel. */ \
+         pv++; \
+         (*iv)++; \
+      } \
+\
+/* Move left a pixel so that *inx == xhi. */ \
+      pv--; \
+      (*iv)--; \
+      (*inx)--; \
+\
+/* Move up the right hand edge of the box correspionding to the current \
+   skin. We stop just below the middle of the right hand edge. */ \
+      for( *iny = ylo; *iny < cy; (*iny)++ ) { \
+\
+/* Test the pixel value, returning if it is valid. This relies on the \
+   compiler optimisation to remove the "if" statements for all but the \
+   operation appropriate to the function being defined. */ \
+         if( OperI == AST__LT ) { \
+            if( *pv < value ) return; \
+\
+         } else if( OperI == AST__LE ) { \
+            if( *pv <= value ) return; \
+\
+         } else if( OperI == AST__EQ ) { \
+            if( *pv == value ) return; \
+\
+         } else if( OperI == AST__NE ) { \
+            if( *pv != value ) return; \
+\
+         } else if( OperI == AST__GE ) { \
+            if( *pv >= value ) return; \
+\
+         } else { \
+            if( *pv > value ) return; \
+         } \
+\
+/* Move up a pixel. */ \
+         pv += nx; \
+         *iv += nx; \
+      } \
+   } \
+\
+/* Report an error if no inside pooint could be found. */ \
+   if( OperI == AST__LT ) { \
+      text = "less than"; \
+   } else if( OperI == AST__LE ) { \
+      text = "less than or equal to"; \
+   } else if( OperI == AST__EQ ) { \
+      text = "equal to"; \
+   } else if( OperI == AST__NE ) { \
+      text = "not equal to"; \
+   } else if( OperI == AST__GE ) { \
+      text = "greater than or equal to"; \
+   } else { \
+      text = "greater than"; \
+   } \
+   astError( AST__NONIN, "astOutline"#X": Could not find a pixel value %s " \
+             "%g in the supplied array.", status, text, (double) value ); \
+}
+
+/* Define a macro that uses the above macro to to create implementations
+   of FindInsidePoint for all operations. */
+#define MAKEALL_FINDINSIDEPOINT(X,Xtype) \
+MAKE_FINDINSIDEPOINT(X,Xtype,LT,AST__LT) \
+MAKE_FINDINSIDEPOINT(X,Xtype,LE,AST__LE) \
+MAKE_FINDINSIDEPOINT(X,Xtype,EQ,AST__EQ) \
+MAKE_FINDINSIDEPOINT(X,Xtype,GE,AST__GE) \
+MAKE_FINDINSIDEPOINT(X,Xtype,GT,AST__GT) \
+MAKE_FINDINSIDEPOINT(X,Xtype,NE,AST__NE)
+
+/* Expand the above macro to generate a function for each required
+   data type and operation. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKEALL_FINDINSIDEPOINT(LD,long double)
+#endif
+MAKEALL_FINDINSIDEPOINT(D,double)
+MAKEALL_FINDINSIDEPOINT(L,long int)
+MAKEALL_FINDINSIDEPOINT(UL,unsigned long int)
+MAKEALL_FINDINSIDEPOINT(I,int)
+MAKEALL_FINDINSIDEPOINT(UI,unsigned int)
+MAKEALL_FINDINSIDEPOINT(S,short int)
+MAKEALL_FINDINSIDEPOINT(US,unsigned short int)
+MAKEALL_FINDINSIDEPOINT(B,signed char)
+MAKEALL_FINDINSIDEPOINT(UB,unsigned char)
+MAKEALL_FINDINSIDEPOINT(F,float)
+
+/* Undefine the macros. */
+#undef MAKE_FINDINSIDEPOINT
+#undef MAKEALL_FINDINSIDEPOINT
+
+static void FindMax( Segment *seg, AstFrame *frm, double *x, double *y,
+                     int nv, int abs, int *status ){
+/*
+*  Name:
+*     FindMax
+
+*  Purpose:
+*     Find the maximum discrepancy between a given line segment and the
+*     Polygon being downsized.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void FindMax( Segment *seg, AstFrame *frm, double *x, double *y,
+*                   int nv, int abs, int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     The supplied Segment structure describes a range of vertices in
+*     the polygon being downsized. This function checks each of these
+*     vertices to find the one that lies furthest from the line joining the
+*     first and last vertices in the segment. The maximum error, and the
+*     vertex index at which this maximum error is found, is stored in the
+*     Segment structure.
+
+*  Parameters:
+*     seg
+*        The structure describing the range of vertices to check. It
+*        corresponds to a candidate edge in the downsized polygon.
+*     frm
+*        The Frame in which the positions are defined.
+*     x
+*        Pointer to the X axis values in the original Polygon.
+*     y
+*        Pointer to the Y axis values in the original Polygon.
+*     nv
+*        Total number of vertics in the old Polygon..
+*     abs
+*        If non-zero, then the stored maximum is the position with
+*        maximum absolute error. Otherwise, the stored maximum is the
+*        position with maximum positive error (positive errors are to the
+*        right when travelling from start to end of the segment).
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1; /* PointSet holding vertex positions */
+   AstPointSet *pset2; /* PointSet holding offset par/perp components */
+   double **ptr1;      /* Pointers to "pset1" data arrays */
+   double **ptr2;      /* Pointers to "pset2" data arrays */
+   double *px;         /* Pointer to next X value */
+   double *py;         /* Pointer to next Y value */
+   double ax;          /* X value at start */
+   double ay;          /* Y value at start */
+   double ba;          /* Distance between a and b */
+   double bax;         /* X increment from a to b */
+   double bay;         /* Y increment from a to b */
+   double cax;         /* X increment from a to c */
+   double cay;         /* Y increment from a to c */
+   double end[ 2 ];    /* Position of starting vertex */
+   double error;       /* Error value for current vertex */
+   double start[ 2 ];  /* Position of starting vertex */
+   int i1;             /* Starting index (always in first cycle) */
+   int i2;             /* Ending index converted to first cycle */
+   int i2b;            /* Upper vertex limit in first cycle */
+   int i;              /* Loop count */
+   int n;              /* Number of vertices to check */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Stuff needed for handling cyclic redundancy of vertex indices. */
+   i1 = seg->i1;
+   i2 = seg->i2;
+   n = i2 - i1 - 1;
+   i2b = i2;
+   if( i2 >= nv ) {
+      i2 -= nv;
+      i2b = nv;
+   }
+
+/* If the segment has no intermediate vertices, set the segment error to
+   zero and return. */
+   if( n < 1 ) {
+      seg->error = 0.0;
+      seg->imax = i1;
+
+/* For speed, we use simple plane geometry if the Polygon is defined in a
+   simple Frame. */
+   } else if( !strcmp( astGetClass( frm ), "Frame" ) ) {
+
+/* Point "a" is the vertex that marks the start of the segment. Point "b"
+   is the vertex that marks the end of the segment. */
+      ax = x[ i1 ];
+      ay = y[ i1 ];
+      bax = x[ i2 ] - ax;
+      bay = y[ i2 ] - ay;
+      ba = sqrt( bax*bax + bay*bay );
+
+/* Initialise the largest error found so far. */
+      seg->error = -1.0;
+
+/* Check the vertices from the start (plus one) up to the end (minus one)
+   or the last vertex (which ever comes first). */
+      for( i = i1 + 1; i < i2b; i++ ) {
+
+/* Position "c" is the vertex being tested. Find the squared distance from
+   "c" to the line joining "a" and "b". */
+         cax = x[ i ] - ax;
+         cay = y[ i ] - ay;
+         error = ( bay*cax - cay*bax )/ba;
+         if( abs ) error = fabs( error );
+
+/* If this is the largest value found so far, record it. Note the error
+   here is a squared distance. */
+         if( error > seg->error ) {
+            seg->error = error;
+            seg->imax = i;
+         }
+      }
+
+/* If the end vertex is in the next cycle, check the remaining vertex
+   posI would have thought a telentitions in the same way. */
+      if( i2b != i2 ) {
+
+         for( i = 0; i < i2; i++ ) {
+
+            cax = x[ i ] - ax;
+            cay = y[ i ] - ay;
+            error = ( bay*cax - cay*bax )/ba;
+            if( abs ) error = fabs( error );
+
+            if( error > seg->error ) {
+               seg->error = error;
+               seg->imax = i + i2b;
+            }
+
+         }
+      }
+
+/* If the polygon is not defined in a simple Frame, we use the overloaded
+   Frame methods to do the geometry. */
+   } else {
+
+/* Create a PointSet to hold the positions of the vertices to test. We do
+   not need to test the start or end vertex. */
+      pset1 = astPointSet( n, 2, " ", status );
+      ptr1 = astGetPoints( pset1 );
+      if( astOK ) {
+
+/* Copy the vertex axis values form the start (plus one) up to the end
+   (minus one) vertex or the last vertex (which ever comes first). */
+         px = ptr1[ 0 ];
+         py = ptr1[ 1 ];
+
+         for( i = i1 + 1; i < i2b; i++ ){
+            *(px++) = x[ i ];
+            *(py++) = y[ i ];
+         }
+
+/* If the end vertex is in the next cycle, copy the remaining vertex
+   positions into the PointSet. */
+         if( i2b != i2 ) {
+            for( i = 0; i < i2; i++ ) {
+               *(px++) = x[ i ];
+               *(py++) = y[ i ];
+            }
+         }
+
+/* Record the start and end vertex positions. */
+         start[ 0 ] = x[ i1 ];
+         start[ 1 ] = y[ i1 ];
+         end[ 0 ] = x[ i2 ];
+         end[ 1 ] = y[ i2 ];
+
+/* Resolve the vector from the start to each vertex into two components,
+   parallel and perpendicular to the start->end vector. */
+         pset2 = astResolvePoints( frm, start, end, pset1, NULL );
+         ptr2 = astGetPoints( pset2 );
+         if( astOK ) {
+
+/* Find the vertex with largest perpendicular component. */
+            seg->error = -1.0;
+            py = ptr2[ 1 ];
+            for( i = 1; i <= n; i++ ) {
+
+               error = *(py++);
+               if( abs ) error = fabs( error );
+
+               if( error > seg->error ) {
+                  seg->error = error;
+                  seg->imax = i + i1;
+               }
+
+            }
+         }
+
+/* Free resources. */
+         pset2 = astAnnul( pset2 );
+      }
+      pset1 = astAnnul( pset1 );
+   }
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a Polygon.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Polygon member function (over-rides the protected astGetAttrib
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a Polygon, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the Polygon.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Polygon, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Polygon. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPolygon *this;             /* Pointer to the Polygon structure */
+   const char *result;           /* Pointer value to return */
+   int ival;                     /* Integer attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the Polygon structure. */
+   this = (AstPolygon *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* SimpVertices. */
+/* ------------- */
+   if ( !strcmp( attrib, "simpvertices" ) ) {
+      ival = astGetSimpVertices( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static int GetBounded( AstRegion *this, int *status ) {
+/*
+*  Name:
+*     GetBounded
+
+*  Purpose:
+*     Is the Region bounded?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     int GetBounded( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Polygon method (over-rides the astGetBounded method inherited from
+*     the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the Region is bounded.
+*     The implementation provided by the base Region class is suitable
+*     for Region sub-classes representing the inside of a single closed
+*     curve (e.g. Circle, Interval, Box, etc). Other sub-classes (such as
+*     CmpRegion, PointList, etc ) may need to provide their own
+*     implementations.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the Region is bounded. Zero otherwise.
+
+*/
+
+/* Local Variables: */
+   int neg;                  /* Has the Polygon been negated? */
+   int result;               /* Returned result */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Ensure cached information is available. */
+   Cache( (AstPolygon *) this, status );
+
+/* See if the Polygon has been negated. */
+   neg = astGetNegated( this );
+
+/* If the polygon vertices are stored in anti-clockwise order, then the
+   polygon is bounded if it has not been negated. */
+   if( ( (AstPolygon *) this)->acw ) {
+      result = (! neg );
+
+/* If the polygon vertices are stored in clockwise order, then the
+   polygon is bounded if it has been negated. */
+   } else {
+      result = neg;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astInitPolygonVtab_(  AstPolygonVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPolygonVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Polygon.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void astInitPolygonVtab( AstPolygonVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Polygon vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Polygon class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstRegionVtab *region;        /* Pointer to Region component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitRegionVtab( (AstRegionVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAPolygon) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstRegionVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->Downsize = Downsize;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   region = (AstRegionVtab *) vtab;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_simplify = mapping->Simplify;
+   mapping->Simplify = Simplify;
+
+   parent_setregfs = region->SetRegFS;
+   region->SetRegFS = SetRegFS;
+
+   parent_resetcache = region->ResetCache;
+   region->ResetCache = ResetCache;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   region->RegPins = RegPins;
+   region->RegBaseMesh = RegBaseMesh;
+   region->RegBaseBox = RegBaseBox;
+   region->RegTrace = RegTrace;
+   region->GetBounded = GetBounded;
+
+   vtab->ClearSimpVertices = ClearSimpVertices;
+   vtab->GetSimpVertices = GetSimpVertices;
+   vtab->SetSimpVertices = SetSimpVertices;
+   vtab->TestSimpVertices = TestSimpVertices;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDump( vtab, Dump, "Polygon", "Polygonal region" );
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int IntCmp( const void *a, const void *b ){
+/*
+*  Name:
+*     IntCmp
+
+*  Purpose:
+*     An integer comparison function for the "qsort" function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     int IntCmp( const void *a, const void *b )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     See the docs for "qsort".
+
+*  Parameters:
+*     a
+*        Pointer to the first int
+*     b
+*        Pointer to the second int
+
+*  Returnd Value:
+*     Positive, negative or zero, depending on whether a is larger than,
+*     equal to, or less than b.
+
+*/
+
+   return *((int*)a) - *((int*)b);
+}
+
+static Segment *NewSegment( Segment *seg, int i1, int i2, int nvert,
+                            int *status ){
+/*
+*  Name:
+*     NewSegment
+
+*  Purpose:
+*     Initialise a structure describing a segment of the new Polygon
+*     created by astDownsize.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     Segment *NewSegment( Segment *seg, int i1, int i2, int nvert,
+*                          int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     This function initialises the contents of a structure describing
+*     the specified range of vertices within a Polygon. The cyclic nature
+*     of vertex indices is taken into account.
+*
+*     If no structure is supplied, memory is allocated to hold a new
+*     structure.
+
+*  Parameters:
+*     seg
+*        Pointer to a structure to initialise, or NULL if a new structure
+*        is to be allocated.
+*     i1
+*        The index of a vertex within the old Polygon (supplied to
+*        astDownsize) that marks the start of the new line segment in
+*        the downsized polygon.
+*     i2
+*        The index of a vertex within the old Polygon (supplied to
+*        astDownsize) that marks the end of the new line segment in
+*        the downsized polygon.
+*     nvert
+*        Total number of vertics in the old Polygon..
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returnd Value:
+*     Pointer to the initialised Segment structure. It should be freed using
+*     astFree when no longer needed.
+
+*/
+
+/* Local Variables: */
+   Segment *result;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure to be initialised, allocating memory
+   for a new structure if none was supplied. */
+   result = seg ? seg : astMalloc( sizeof( Segment ) );
+
+/* Check the pointer can be used safely. */
+   if( result ) {
+
+/* If the supplied ending index is less than the starting index, the
+   ending index must have gone all the way round the polygon and started
+   round again. Increase the ending index by the number of vertices to
+   put it in the same cycle as the starting index. */
+      if( i2 < i1 ) i2 += nvert;
+
+/* If the supplied starting index is within the first cycle (i.e. zero ->
+   nvert-1 ), use the indices as they are (which may mean that the ending
+   index is greater than nvert, but this is handled correctly by other
+   functions). */
+      if( i1 < nvert ) {
+         result->i1 = i1;
+         result->i2 = i2;
+
+/* If the supplied starting index is within the second cycle (i.e. nvert
+   or greater) the ending index will be even greater, so we can reduce
+   both by "nvert" to put them both in the first cycle. The goal is that
+   the starting index should always be in the first cycle, but the ending
+   index may possibly be in the second cycle. */
+      } else {
+         result->i1 = i1 - nvert;
+         result->i2 = i2 - nvert;
+      }
+
+/* Nullify the links to other Segments */
+      result->next = NULL;
+      result->prev = NULL;
+   }
+
+/* Return the pointer to the new Segment structure. */
+   return result;
+}
+
+/*
+*++
+*  Name:
+c     astOutline<X>
+f     AST_OUTLINE<X>
+
+*  Purpose:
+*     Create a new Polygon outling values in a 2D data grid.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "polygon.h"
+c     AstPolygon *astOutline<X>( <Xtype> value, int oper, const <Xtype> array[],
+c                                const int lbnd[2], const int ubnd[2], double maxerr,
+c                                int maxvert, const int inside[2], int starpix )
+f     RESULT = AST_OUTLINE<X>( VALUE, OPER, ARRAY, LBND, UBND, MAXERR,
+f                              MAXVERT, INSIDE, STARPIX, STATUS )
+
+*  Class Membership:
+*     Polygon method.
+
+*  Description:
+*     This is a set of functions that create a Polygon enclosing a single
+*     contiguous set of pixels that have a specified value within a gridded
+*     2-dimensional data array (e.g. an image).
+*
+*     A basic 2-dimensional Frame is used to represent the pixel coordinate
+*     system in the returned Polygon. The Domain attribute is set to
+*     "PIXEL", the Title attribute is set to "Pixel coordinates", and the
+*     Unit attribute for each axis is set to "pixel". All other
+*     attributes are left unset. The nature of the pixel coordinate system
+*     is determined by parameter
+c     "starpix".
+f     STARPIX.
+*
+*     The
+c     "maxerr" and "maxvert"
+f     MAXERR and MAXVERT
+*     parameters can be used to control how accurately the returned
+*     Polygon represents the required region in the data array. The
+*     number of vertices in the returned Polygon will be the minimum
+*     needed to achieve the required accuracy.
+*
+*     You should use a function which matches the numerical type of the
+*     data you are processing by replacing <X> in the generic function
+*     name
+c     astOutline<X>
+f     AST_OUTLINE<X>
+c     by an appropriate 1- or 2-character type code. For example, if you
+*     are procesing data with type
+c     "float", you should use the function astOutlineF
+f     REAL, you should use the function AST_OUTLINER
+*     (see the "Data Type Codes" section below for the codes appropriate to
+*     other numerical types).
+
+*  Parameters:
+c     value
+f     VALUE = <Xtype> (Given)
+*        A data value that specifies the pixels to be outlined.
+c     oper
+f     OPER = INTEGER (Given)
+*        Indicates how the
+c        "value"
+f        VALUE
+*        parameter is used to select the outlined pixels. It can
+*        have any of the following values:
+c        - AST__LT: outline pixels with value less than "value".
+c        - AST__LE: outline pixels with value less than or equal to "value".
+c        - AST__EQ: outline pixels with value equal to "value".
+c        - AST__NE: outline pixels with value not equal to "value".
+c        - AST__GE: outline pixels with value greater than or equal to "value".
+c        - AST__GT: outline pixels with value greater than "value".
+f        - AST__LT: outline pixels with value less than VALUE.
+f        - AST__LE: outline pixels with value less than or equal to VALUE.
+f        - AST__EQ: outline pixels with value equal to VALUE.
+f        - AST__NE: outline pixels with value not equal to VALUE.
+f        - AST__GE: outline pixels with value greater than or equal to VALUE.
+f        - AST__GT: outline pixels with value greater than VALUE.
+c     array
+f     ARRAY( * ) = <Xtype> (Given)
+c        Pointer to a
+f        A
+*        2-dimensional array containing the data to be processed.  The
+*        numerical type of this array should match the 1- or
+*        2-character type code appended to the function name (e.g. if
+c        you are using astOutlineF, the type of each array element
+c        should be "float").
+f        you are using AST_OUTLINER, the type of each array element
+f        should be REAL).
+*
+*        The storage order of data within this array should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the second dimension least rapidly
+c        (i.e. Fortran array indexing is used).
+f        (i.e. normal Fortran array storage order).
+c     lbnd
+f     LBND( 2 ) = INTEGER (Given)
+c        Pointer to an array of two integers
+f        An array
+*        containing the pixel index of the first pixel in the input grid
+*        along each dimension.
+c     ubnd
+f     UBND( 2) = INTEGER (Given)
+c        Pointer to an array of two integers
+f        An array
+*        containing the pixel index of the last pixel in the input grid
+*        along each dimension.
+*
+c        Note that "lbnd" and "ubnd" together define the shape
+f        Note that LBND and UBND together define the shape
+*        and size of the input pixel grid, its extent along a particular
+c        (j'th) dimension being ubnd[j]-lbnd[j]+1 pixels.
+f        (J'th) dimension being UBND(J)-LBND(J)+1 pixels.
+*        For FITS images,
+c        the lbnd values will be 1 and the ubnd
+f        the LBND values will be 1 and the UBND
+*        values will be equal to the NAXISi header values. Other
+*        data systems, such as the Starlink NDF system, allow an
+c        arbitrary pixel origin to be used (i.e. lbnd
+f        arbitrary pixel origin to be used (i.e. LBND
+*        is not necessarily 1).
+*
+*        These bounds also define the input grid's floating point coordinate
+*        system, each pixel having unit extent along each dimension with
+*        integral coordinate values at its centre or upper corner, as selected
+*        by parameter
+c        "starpix".
+f        STARPIX.
+c     maxerr
+f     MAXERR = DOUBLE PRECISION (Given)
+*        Together with
+c        "maxvert",
+f        MAXVERT,
+*        this determines how accurately the returned Polygon represents
+*        the required region of the data array. It gives the target
+*        discrepancy between the returned Polygon and the accurate outline
+*        in the data array, expressed as a number of pixels. Insignificant
+*        vertices are removed from the accurate outline, one by one, until
+*        the number of vertices remaining in the returned Polygon equals
+c        "maxvert",
+f        MAXVERT,
+*        or the largest discrepancy between the accurate outline and the
+*        returned Polygon is greater than
+c        "maxerr". If "maxerr"
+f        MAXERR. If MAXERR
+*        is zero or less, its value is ignored and the returned Polygon will
+*        have the number of vertices specified by
+c        "maxvert".
+f        MAXVERT.
+c     maxvert
+f     MAXVERT = INTEGER (Given)
+*        Together with
+c        "maxerr",
+f        MAXERR,
+*        this determines how accurately the returned Polygon represents
+*        the required region of the data array. It gives the maximum
+*        allowed number of vertices in the returned Polygon. Insignificant
+*        vertices are removed from the accurate outline, one by one, until
+*        the number of vertices remaining in the returned Polygon equals
+c        "maxvert",
+f        MAXVERT,
+*        or the largest discrepancy between the accurate outline and the
+*        returned Polygon is greater than
+c        "maxerr". If "maxvert"
+f        MAXERR. If MAXVERT
+*        is less than 3, its value is ignored and the number of vertices in
+*        the returned Polygon will be the minimum needed to ensure that the
+*        discrepancy between the accurate outline and the returned
+*        Polygon is less than
+c        "maxerr".
+f        MAXERR.
+c     inside
+f     INSIDE( 2 ) = INTEGER (Given)
+c        Pointer to an array of two integers
+f        An array
+*        containing the pixel indices of a pixel known to be inside the
+*        required region. This is needed because the supplied data
+*        array may contain several disjoint areas of pixels that satisfy
+*        the criterion specified by
+c        "value" and "oper".
+f        VALUE and OPER.
+*        In such cases, the area described by the returned Polygon will
+*        be the one that contains the pixel specified by
+c        "inside".
+f        INSIDE.
+*        If the specified pixel is outside the bounds given by
+c        "lbnd" and "ubnd",
+f        LBND and UBND,
+*        or has a value that does not meet the criterion specified by
+c        "value" and "oper",
+f        VALUE and OPER,
+*        then this function will search for a suitable pixel. The search
+*        starts at the central pixel and proceeds in a spiral manner until
+*        a pixel is found that meets the specified crierion.
+c     starpix
+f     STARPIX = LOGICAL (Given)
+*        A flag indicating the nature of the pixel coordinate system used
+*        to describe the vertex positions in the returned Polygon. If
+c        non-zero,
+f        .TRUE.,
+*        the standard Starlink definition of pixel coordinate is used in
+*        which a pixel with integer index I spans a range of pixel coordinate
+*        from (I-1) to I (i.e. pixel corners have integral pixel coordinates).
+c        If zero,
+f        If .FALSE.,
+*        the definition of pixel coordinate used by other AST functions
+c        such as astResample, astMask,
+f        such as AST_RESAMPLE, AST_MASK,
+*        etc., is used. In this definition, a pixel with integer index I
+*        spans a range of pixel coordinate from (I-0.5) to (I+0.5) (i.e.
+*        pixel centres have integral pixel coordinates).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astOutline<X>()
+f     AST_OUTLINE<X> = INTEGER
+*        A pointer to the required Polygon.
+
+*  Notes:
+*     - This function proceeds by first finding a very accurate polygon,
+*     and then removing insignificant vertices from this fine polygon
+*     using
+c     astDownsize.
+f     AST_DOWNSIZE.
+*     - The returned Polygon is the outer boundary of the contiguous set
+*     of pixels that includes ths specified "inside" point, and satisfy
+*     the specified value requirement. This set of pixels may potentially
+*     include "holes" where the pixel values fail to meet the specified
+*     value requirement. Such holes will be ignored by this function.
+c     - NULL
+f     - AST__NULL
+*     will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+
+*  Data Type Codes:
+*     To select the appropriate masking function, you should
+c     replace <X> in the generic function name astOutline<X> with a
+f     replace <X> in the generic function name AST_OUTLINE<X> with a
+*     1- or 2-character data type code, so as to match the numerical
+*     type <Xtype> of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - L: long int
+c     - UL: unsigned long int
+c     - I: int
+c     - UI: unsigned int
+c     - S: short int
+c     - US: unsigned short int
+c     - B: byte (signed char)
+c     - UB: unsigned byte (unsigned char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - UI: INTEGER (treated as unsigned)
+f     - S: INTEGER*2 (short integer)
+f     - US: INTEGER*2 (short integer, treated as unsigned)
+f     - B: BYTE (treated as signed)
+f     - UB: BYTE (treated as unsigned)
+*
+c     For example, astOutlineD would be used to process "double"
+c     data, while astOutlineS would be used to process "short int"
+c     data, etc.
+f     For example, AST_OUTLINED would be used to process DOUBLE
+f     PRECISION data, while AST_OUTLINES would be used to process
+f     short integer data (stored in an INTEGER*2 array), etc.
+f
+f     For compatibility with other Starlink facilities, the codes W
+f     and UW are provided as synonyms for S and US respectively (but
+f     only in the Fortran interface to AST).
+
+*--
+*/
+/* Define a macro to implement the function for a specific data
+   type. Note, this function cannot be a virtual function since the
+   argument list does not include a Polygon, and so no virtual function
+   table is available. */
+#define MAKE_OUTLINE(X,Xtype) \
+AstPolygon *astOutline##X##_( Xtype value, int oper, const Xtype array[], \
+                              const int lbnd[2], const int ubnd[2], double maxerr, \
+                              int maxvert, const int inside[2], int starpix, \
+                              int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *frm;            /* Frame in which to define the Polygon */ \
+   AstPointSet *candidate;   /* Candidate polygon vertices */ \
+   AstPointSet *pset;        /* PointSet holding downsized polygon vertices */ \
+   AstPolygon *result;       /* Result value to return */ \
+   const Xtype *pv;          /* Pointer to next test point */ \
+   Xtype v;                  /* Value of current pixel */ \
+   double **ptr;             /* Pointers to PointSet arrays */ \
+   int boxsize;              /* Half width of smoothign box in vertices */ \
+   int inx;                  /* X index of inside point */ \
+   int iny;                  /* Y index of inside point */ \
+   int iv;                   /* Vector index of next test pixel */ \
+   int ixv;                  /* X pixel index of next test point */ \
+   int nv0;                  /* Number of vertices in accurate outline */ \
+   int nx;                   /* Length of array x axis */ \
+   int smooth;               /* Do we need to smooth the polygon? */ \
+   int stop_at_invalid;      /* Indicates when to stop rightwards search */ \
+   int tmp;                  /* Alternative boxsize */ \
+   int valid;                /* Does current pixel meet requirements? */ \
+   static double junk[ 6 ] = {0.0, 0.0, 1.0, 1.0, 0.0, 1.0 }; /* Junk poly */ \
+\
+/* Initialise. */ \
+   result = NULL; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Avoid compiler warnings. */ \
+   iv = 0; \
+\
+/* If we are going to be smoothing the polygon before downsizing it, we \
+   need to ensure that the full polygon is retained within TraceEdge. if \
+   this is not the case, TraceEdge can remove all vertices from straight \
+   lines, except for the vertices that mark the beinning and end of the \
+   straight line. */ \
+   smooth = ( maxerr > 0.0 || maxvert > 2 ); \
+\
+/* Store the X dimension of the array. */ \
+   nx = ubnd[ 0 ] - lbnd[ 0 ] + 1; \
+\
+/* See if a valid inside point was supplied. It must be inside the bounds \
+   of the array, and must have a pixel value that meets the specified \
+   requirement. */ \
+   inx = inside[ 0 ]; \
+   iny = inside[ 1 ]; \
+   valid = ( inx >= lbnd[ 0 ] && inx <= ubnd[ 0 ] && \
+             iny >= lbnd[ 1 ] && iny <= ubnd[ 1 ] ); \
+\
+   if( valid  ) { \
+      iv = ( inx - lbnd[ 0 ] ) + (iny - lbnd[ 1 ] )*nx; \
+      v = array[ iv ]; \
+\
+      if( oper == AST__LT ) { \
+         valid = ( v < value ); \
+\
+      } else if( oper == AST__LE ) { \
+         valid = ( v <= value ); \
+\
+      } else if( oper == AST__EQ ) { \
+         valid = ( v == value ); \
+\
+      } else if( oper == AST__NE ) { \
+         valid = ( v != value ); \
+\
+      } else if( oper == AST__GE ) { \
+         valid = ( v >= value ); \
+\
+      } else if( oper == AST__GT ) { \
+         valid = ( v > value ); \
+\
+      } else if( astOK ){ \
+         astError( AST__OPRIN, "astOutline"#X": Invalid operation code " \
+                   "(%d) supplied (programming error).", status, oper ); \
+      } \
+   } \
+\
+/* If no valid inside point was supplied, find one now. */ \
+   if( !valid ) { \
+\
+      if( oper == AST__LT ) { \
+         FindInsidePointLT##X( value, array, lbnd, ubnd, &inx, &iny, &iv, status ); \
+\
+      } else if( oper == AST__LE ) { \
+         FindInsidePointLE##X( value, array, lbnd, ubnd, &inx, &iny, &iv, status ); \
+\
+      } else if( oper == AST__EQ ) { \
+         FindInsidePointEQ##X( value, array, lbnd, ubnd, &inx, &iny, &iv, status ); \
+\
+      } else if( oper == AST__NE ) { \
+         FindInsidePointNE##X( value, array, lbnd, ubnd, &inx, &iny, &iv, status ); \
+\
+      } else if( oper == AST__GE ) { \
+         FindInsidePointGE##X( value, array, lbnd, ubnd, &inx, &iny, &iv, status ); \
+\
+      } else if( oper == AST__GT ) { \
+         FindInsidePointGT##X( value, array, lbnd, ubnd, &inx, &iny, &iv, status ); \
+\
+      } else if( astOK ){ \
+         astError( AST__OPRIN, "astOutline"#X": Invalid operation code " \
+                   "(%d) supplied (programming error).", status, oper ); \
+      } \
+   } \
+\
+/* We now need to find a point on the boundary of the region containing \
+   the inside point. Starting at the inside point, move to the right \
+   through the array until a pixel is found which fails to meet the value \
+   requirement or the edge of the array is reached. */ \
+\
+   candidate = NULL; \
+   pv = array + iv; \
+   ixv = inx; \
+   stop_at_invalid = 1; \
+\
+   while( ++ixv <= ubnd[ 0 ] ) { \
+\
+/* Get the next pixel value. */ \
+      v = *(++pv); \
+\
+/* See if it meets the value requirement. */ \
+      if( oper == AST__LT ) { \
+         valid = ( v < value ); \
+\
+      } else if( oper == AST__LE ) { \
+         valid = ( v <= value ); \
+\
+      } else if( oper == AST__EQ ) { \
+         valid = ( v == value ); \
+\
+      } else if( oper == AST__NE ) { \
+         valid = ( v != value ); \
+\
+      } else if( oper == AST__GE ) { \
+         valid = ( v >= value ); \
+\
+      } else if( oper == AST__GT ) { \
+         valid = ( v > value ); \
+\
+      } else if( astOK ){ \
+         astError( AST__OPRIN, "astOutline"#X": Invalid operation code " \
+                   "(%d) supplied (programming error).", status, oper ); \
+         break; \
+      } \
+\
+/* If we are currently looking for the next invalid pixel, and this pixel \
+   is invalid... */ \
+      if( stop_at_invalid ) { \
+         if( ! valid ) { \
+\
+/* The current pixel may be on the required polygon, or it may be on the  \
+   edge of a hole contained within the region being outlined. We would  \
+   like to jump over such holes so that we can continue to look for the  \
+   real edge of the region being outlined. In order to determine if we  \
+   have reached a hole, we trace the edge that passes through the current  \
+   pixel, forming a candidate polygon in the process. In the process, We  \
+   see if the inside point falls within this candidate polygon. If it does  \
+   then the polygon is accepted as the required polygon. Otherwise, it is  \
+   rejected as a mere hole, and we continue moving away from the inside  \
+   point, looking for a new edge. */ \
+            if( oper == AST__LT ) { \
+               candidate = TraceEdgeLT##X( value, array, lbnd, ubnd, iv - 1, \
+                                           ixv - 1, iny, starpix, smooth, status ); \
+\
+            } else if( oper == AST__LE ) { \
+               candidate = TraceEdgeLE##X( value, array, lbnd, ubnd, iv - 1, \
+                                           ixv - 1, iny, starpix, smooth, status ); \
+\
+            } else if( oper == AST__EQ ) { \
+               candidate = TraceEdgeEQ##X( value, array, lbnd, ubnd, iv - 1, \
+                                           ixv - 1, iny, starpix, smooth, status ); \
+\
+            } else if( oper == AST__NE ) { \
+               candidate = TraceEdgeNE##X( value, array, lbnd, ubnd, iv - 1, \
+                                           ixv - 1, iny, starpix, smooth, status ); \
+\
+            } else if( oper == AST__GE ) { \
+               candidate = TraceEdgeGE##X( value, array, lbnd, ubnd, iv - 1, \
+                                           ixv - 1, iny, starpix, smooth, status ); \
+\
+            } else if( oper == AST__GT ) { \
+               candidate = TraceEdgeGT##X( value, array, lbnd, ubnd, iv - 1, \
+                                           ixv - 1, iny, starpix, smooth, status ); \
+\
+            } else if( astOK ){ \
+               astError( AST__OPRIN, "astOutline"#X": Invalid operation code " \
+                         "(%d) supplied (programming error).", status, oper ); \
+            } \
+\
+/* If the candidate polygon is the required polygon, break out of the \
+   loop. Otherwise, indicate that we want to continue moving right, \
+   across the hole, until we reach the far side of the hole (i.e. find \
+   the next valid pixel). */ \
+            if( candidate ) { \
+               break; \
+            } else { \
+               stop_at_invalid = 0; \
+            } \
+         } \
+\
+/* If we are currently looking for the next valid pixel, and the current \
+   pixel is valid... */ \
+      } else if( valid ) { \
+\
+/* We have reached the far side of a hole. Continue moving right, looking \
+   now for the next invalid pixel (which may be on the required polygon). */ \
+         stop_at_invalid = 1; \
+      } \
+   } \
+\
+/* If we have not yet found the required polygon, we must have reached \
+   the right hand edge of the array. So we now follow the edge of the \
+   array round until we meet the boundary of the required region. */ \
+   if( !candidate ) { \
+      if( oper == AST__LT ) { \
+         candidate = TraceEdgeLT##X( value, array, lbnd, ubnd, iv - 1, \
+                                     ixv - 1, iny, starpix, smooth, status ); \
+\
+      } else if( oper == AST__LE ) { \
+         candidate = TraceEdgeLE##X( value, array, lbnd, ubnd, iv - 1, \
+                                     ixv - 1, iny, starpix, smooth, status ); \
+\
+      } else if( oper == AST__EQ ) { \
+         candidate = TraceEdgeEQ##X( value, array, lbnd, ubnd, iv - 1, \
+                                     ixv - 1, iny, starpix, smooth, status ); \
+\
+      } else if( oper == AST__NE ) { \
+         candidate = TraceEdgeNE##X( value, array, lbnd, ubnd, iv - 1, \
+                                     ixv - 1, iny, starpix, smooth, status ); \
+\
+      } else if( oper == AST__GE ) { \
+         candidate = TraceEdgeGE##X( value, array, lbnd, ubnd, iv - 1, \
+                                     ixv - 1, iny, starpix, smooth, status ); \
+\
+      } else if( oper == AST__GT ) { \
+         candidate = TraceEdgeGT##X( value, array, lbnd, ubnd, iv - 1, \
+                                     ixv - 1, iny, starpix, smooth, status ); \
+\
+      } else if( astOK ){ \
+         astError( AST__OPRIN, "astOutline"#X": Invalid operation code " \
+                   "(%d) supplied (programming error).", status, oper ); \
+      } \
+   } \
+\
+/* If required smooth the full resolution polygon before downsizing it. */ \
+   if( smooth ) { \
+\
+/* Initially, set the boxsize to be equal to the required accouracy. */ \
+      if( maxerr > 0 ) { \
+         boxsize = (int) maxerr; \
+      } else { \
+         boxsize = INT_MAX; \
+      } \
+\
+/* Determine a second box size equal to the average number of vertices in \
+   the accurate outline, per vertex in the returned Polygon. */ \
+      nv0 = astGetNpoint( candidate ); \
+      if( maxvert > 2 ) { \
+         tmp = nv0/(2*maxvert); \
+      } else { \
+         tmp = INT_MAX; \
+      } \
+\
+/* Use the minimum of the two box sizes. */ \
+      if( tmp < boxsize ) boxsize = tmp; \
+\
+/* Ensure the box is sufficiently small to allow at least 10 full boxes \
+   (=20 half boxes) around the polygon. */ \
+      tmp = nv0/20; \
+      if( tmp < boxsize ) boxsize = tmp; \
+      if( boxsize == 0 ) boxsize = 1; \
+\
+/* Smooth the polygon. */ \
+      SmoothPoly( candidate, boxsize, 1.0, status ); \
+   } \
+\
+/* Reduce the number of vertices in the outline. */ \
+   frm = astFrame( 2, "Domain=PIXEL,Unit(1)=pixel,Unit(2)=pixel," \
+                   "Title=Pixel coordinates", status ); \
+   pset = DownsizePoly( candidate, maxerr, maxvert, frm, status ); \
+\
+/* Create a default Polygon with 3 junk vertices. */ \
+   result = astPolygon( frm, 3, 3, junk, NULL, " ", status ); \
+\
+/* Change the PointSet within the Polygon to the one created above. */ \
+   SetPointSet( result, pset, status ); \
+\
+/* Free resources. Note, we need to free the arrays within the candidate \
+   PointSet explicitly, since they were not created as part of the \
+   construction of the PointSet (see TraceEdge). */ \
+   pset = astAnnul( pset ); \
+   frm = astAnnul( frm ); \
+   ptr = astGetPoints( candidate ); \
+   if( astOK ) { \
+      astFree( ptr[ 0 ] ); \
+      astFree( ptr[ 1 ] ); \
+   } \
+   candidate = astAnnul( candidate ); \
+\
+/* If an error occurred, clear the returned result. */ \
+   if ( !astOK ) result = astAnnul( result ); \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_OUTLINE(LD,long double)
+#endif
+MAKE_OUTLINE(D,double)
+MAKE_OUTLINE(L,long int)
+MAKE_OUTLINE(UL,unsigned long int)
+MAKE_OUTLINE(I,int)
+MAKE_OUTLINE(UI,unsigned int)
+MAKE_OUTLINE(S,short int)
+MAKE_OUTLINE(US,unsigned short int)
+MAKE_OUTLINE(B,signed char)
+MAKE_OUTLINE(UB,unsigned char)
+MAKE_OUTLINE(F,float)
+
+/* Undefine the macros. */
+#undef MAKE_OUTLINE
+
+/*
+*  Name:
+*     PartHull
+
+*  Purpose:
+*     Find the convex hull enclosing selected pixels in one corner of a 2D array.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void PartHull<Oper><X>( <Xtype> value, const <Xtype> array[], int xdim,
+*                             int ydim, int xs, int ys, int xe, int ye,
+*                             int starpix, const int lbnd[2], double **xvert,
+*                             double **yvert, int *nvert, int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     This function uses an algorithm similar to "Andrew's Monotone Chain
+*     Algorithm" to create a list of vertices describing one corner of the
+*     convex hull enclosing the selected pixels in the supplied array.
+*     The corner is defined to be the area of the array to the right of
+*     the line from (xs,ys) to (xe,ye).
+
+*  Parameters:
+*     value
+*        A data value that specifies the pixels to be selected.
+*     array
+*        Pointer to a 2-dimensional array containing the data to be
+*        processed. The numerical type of this array should match the 1-
+*        or 2-character type code appended to the function name.
+*     xdim
+*        The number of pixels along each row of the array.
+*     ydim
+*        The number of rows in the array.
+*     xs
+*        The X GRID index of the first pixel on the line to be checked.
+*     ys
+*        The Y GRID index of the first pixel on the line to be checked.
+*     xe
+*        The X GRID index of the last pixel on the line to be checked.
+*     ye
+*        The Y GRID index of the last pixel on the line to be checked.
+*     starpix
+*        If non-zero, the usual Starlink definition of pixel coordinate
+*        is used (integral values at pixel corners). Otherwise, the
+*        system used by other AST functions such as astResample is used
+*        (integral values at pixel centres).
+*     lbnd
+*        The lower pixel index bounds of the array.
+*     xvert
+*        Address of a pointer in which to return a pointer to the list
+*        of GRID x values on the hull.
+*     yvert
+*        Address of a pointer in which to return a pointer to the list
+*        of GRID y values on the hull.
+*     nvert
+*        Address of a pointer in which to return the number of points in
+*        the returned xvert and yvert arrays.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Define a macro to implement the function for a specific data
+   type and operation. */
+#define MAKE_PARTHULL(X,Xtype,Oper,OperI) \
+static void PartHull##Oper##X( Xtype value, const Xtype array[], int xdim, \
+                               int ydim, int xs, int ys, int xe, int ye, \
+                               int starpix, const int lbnd[2], \
+                               double **xvert, double **yvert, int *nvert, \
+                               int *status ) { \
+\
+/* Local Variables: */ \
+   const Xtype *pc; \
+   double *pxy; \
+   double dx2; \
+   double dx1; \
+   double dy1; \
+   double dy2; \
+   double off; \
+   double xdelta; \
+   int ivert; \
+   int ix; \
+   int iy; \
+   int x0; \
+   int x1; \
+   int xl; \
+   int xlim; \
+   int xr; \
+   int yinc; \
+\
+/* Initialise */ \
+   *yvert = NULL; \
+   *xvert = NULL; \
+   *nvert = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* If the line has zero length. just return a single vertex. */ \
+   if( xs == xe && ys == ye ) { \
+      *xvert = astMalloc( sizeof( double ) ); \
+      *yvert = astMalloc( sizeof( double ) ); \
+      if( astOK ) { \
+         if( starpix ) { \
+            (*xvert)[ 0 ] = xs + lbnd[ 0 ] - 1.5; \
+            (*yvert)[ 0 ] = ys + lbnd[ 1 ] - 1.5; \
+         } else { \
+            (*xvert)[ 0 ] = xs + lbnd[ 0 ] - 1.0; \
+            (*yvert)[ 0 ] = ys + lbnd[ 1 ] - 1.0; \
+         } \
+         *nvert = 1; \
+      } \
+      return; \
+   } \
+\
+/* Otherwise check the line is sloping. */ \
+   if( xs == xe ) { \
+      astError( AST__INTER, "astOutline(Polygon): Bounding box " \
+                "has zero width (internal AST programming error).", \
+                status ); \
+      return; \
+   } else if( ys == ye ) { \
+      astError( AST__INTER, "astOutline(Polygon): Bounding box " \
+                "has zero height (internal AST programming error).", \
+                status ); \
+      return; \
+   } \
+\
+/* Calculate the difference in length between adjacent rows of the area \
+   to be tested. */ \
+   xdelta = ((double)( xe - xs ))/((double)( ye - ys )); \
+\
+/* The left and right X limits */ \
+   if( xe > xs ) { \
+      xl = xs; \
+      xr = xe; \
+   } else { \
+      xl = xe; \
+      xr = xs; \
+   } \
+\
+/* Get the increment in row number as we move from the start to the end \
+   of the line. */ \
+   yinc = ( ye > ys ) ? 1 : -1; \
+\
+/* Loop round all rows that cross the region to be tested, from start to \
+   end of the supplied line. */ \
+   iy = ys; \
+   while( astOK ) { \
+\
+/* Get the GRID X coord where the line crosses this row. */ \
+      xlim = (int)( 0.5 + xs + xdelta*( iy - ys ) ); \
+\
+/* Get the index of the first and last columns to be tested on this row. */ \
+      if( yinc < 0 ) { \
+         x0 = xl; \
+         x1 = xlim; \
+      } else { \
+         x0 = xlim; \
+         x1 = xr; \
+      } \
+\
+/* Get a pointer to the first pixel to be tested at this row. */ \
+      pc = array + ( iy - 1 )*xdim + x0 - 1; \
+\
+/* Loop round all columns to be tested in this row. */ \
+      for( ix = x0; ix <= x1 && astOK; ix++,pc++ ) { \
+\
+/* Ignore pixels that are not selected. */ \
+         if( ISVALID(*pc,OperI,value) ) { \
+\
+/* If this is the very first pixel, initialise the hull to contain just \
+   the first pixel. */ \
+            if( *nvert == 0 ){ \
+               *xvert = astMalloc( 200*sizeof( double ) ); \
+               *yvert = astMalloc( 200*sizeof( double ) ); \
+               if( astOK ) { \
+                  (*xvert)[ 0 ] = ix; \
+                  (*yvert)[ 0 ] = iy; \
+                  *nvert = 1; \
+               } else { \
+                  break; \
+               } \
+\
+/* Otherwise.... */ \
+            } else { \
+\
+/* Loop until the hull has been corrected to include the current pixel. */ \
+               while( 1 ) { \
+\
+/* If the hull currently contains only one pixel, add the current pixel to \
+   the end of the hull. */ \
+                  if( *nvert == 1 ){ \
+                     (*xvert)[ 1 ] = ix; \
+                     (*yvert)[ 1 ] = iy; \
+                     *nvert = 2; \
+                     break; \
+\
+/* Otherwise... */ \
+                  } else { \
+\
+/* Extend the line from the last-but-one pixel on the hull to the last \
+   pixel on the hull, and see if the current pixel is to the left of \
+   this line. If it is, it too is on the hull and so push it onto the end \
+   of the list of vertices. */ \
+                     dx1 = (*xvert)[ *nvert - 1 ] - (*xvert)[ *nvert - 2 ]; \
+                     dy1 = (*yvert)[ *nvert - 1 ] - (*yvert)[ *nvert - 2 ]; \
+                     dx2 = ix - (*xvert)[ *nvert - 2 ]; \
+                     dy2 = iy - (*yvert)[ *nvert - 2 ]; \
+\
+                     if( dx1*dy2 > dx2*dy1 ) { \
+                        ivert = (*nvert)++; \
+                        *xvert = astGrow( *xvert, *nvert, sizeof( double ) );  \
+                        *yvert = astGrow( *yvert, *nvert, sizeof( double ) );  \
+                        if( astOK ) {  \
+                           (*xvert)[ ivert ] = ix; \
+                           (*yvert)[ ivert ] = iy; \
+                        }  \
+\
+/* Leave the loop now that the new point is on the hull. */ \
+                        break; \
+\
+/* If the new point is to the left of the line, then the last point \
+   previously thought to be on hull is in fact not on the hull, so remove \
+   it. We then loop again to compare the new pixel with modified hull. */ \
+                     } else { \
+                        (*nvert)--; \
+                     } \
+                  } \
+               } \
+            } \
+         } \
+      } \
+\
+      if( iy == ye ) { \
+         break; \
+      } else { \
+         iy += yinc; \
+      } \
+\
+   } \
+\
+/* Convert GRID coords to PIXEL coords. */ \
+   if( astOK ) { \
+      pxy = *xvert; \
+      off = starpix ? lbnd[ 0 ] - 1.5 : lbnd[ 0 ] - 1.0; \
+      for( ivert = 0; ivert < *nvert; ivert++ ) *(pxy++) += off; \
+\
+      pxy = *yvert; \
+      off = starpix ? lbnd[ 1 ] - 1.5 : lbnd[ 1 ] - 1.0; \
+      for( ivert = 0; ivert < *nvert; ivert++ ) *(pxy++) += off; \
+\
+/* Free lists if an error has occurred. */ \
+   } else { \
+      *xvert = astFree( *xvert ); \
+      *yvert = astFree( *yvert ); \
+      *nvert = 0; \
+   } \
+}
+
+/* Define a macro that uses the above macro to to create implementations
+   of PartHull for all operations. */
+#define MAKEALL_PARTHULL(X,Xtype) \
+MAKE_PARTHULL(X,Xtype,LT,AST__LT) \
+MAKE_PARTHULL(X,Xtype,LE,AST__LE) \
+MAKE_PARTHULL(X,Xtype,EQ,AST__EQ) \
+MAKE_PARTHULL(X,Xtype,NE,AST__NE) \
+MAKE_PARTHULL(X,Xtype,GE,AST__GE) \
+MAKE_PARTHULL(X,Xtype,GT,AST__GT)
+
+/* Expand the above macro to generate a function for each required
+   data type and operation. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKEALL_PARTHULL(LD,long double)
+#endif
+MAKEALL_PARTHULL(D,double)
+MAKEALL_PARTHULL(L,long int)
+MAKEALL_PARTHULL(UL,unsigned long int)
+MAKEALL_PARTHULL(I,int)
+MAKEALL_PARTHULL(UI,unsigned int)
+MAKEALL_PARTHULL(S,short int)
+MAKEALL_PARTHULL(US,unsigned short int)
+MAKEALL_PARTHULL(B,signed char)
+MAKEALL_PARTHULL(UB,unsigned char)
+MAKEALL_PARTHULL(F,float)
+
+/* Undefine the macros. */
+#undef MAKE_PARTHULL
+#undef MAKEALL_PARTHULL
+
+static double Polywidth( AstFrame *frm, AstLineDef **edges, int i, int nv,
+                         double cen[ 2 ], int *status ){
+/*
+*  Name:
+*     Polywidth
+
+*  Purpose:
+*     Find the width of a polygon perpendicular to a given edge.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     double Polywidth( AstFrame *frm, AstLineDef **edges, int i, int nv,
+*                       double cen[ 2 ], int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     This function defines a line perpendicular to a given polygon edge,
+*     passing through the mid point of the edge, extending towards the
+*     inside of the polygon. It returns the distance that can be travelled
+*     along this line before any of the other polygon edges are hit (the
+*     "width" of the polygon perpendicular to the given edge). It also
+*     puts the position corresponding to half that distance into "cen".
+
+*  Parameters:
+*     frm
+*        The Frame in which the lines are defined.
+*     edges
+*        Array of "nv" pointers to AstLineDef structures, each defining an
+*        edge of the polygon.
+*     i
+*        The index of the edge that is to define the polygon width.
+*     nv
+*        Total number of edges.
+*     cen
+*        An array into which are put the coords of the point half way
+*        along the polygon width line.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returnd Value:
+*     The width of the polygon perpendicular to the given edge, or
+*     AST__BAD if the width cannot be determined (usually because the
+*     vertices been supplied in a clockwise direction, effectively
+*     negating the Polygon).
+
+*/
+
+/* Local Variables: */
+   AstLineDef *line;
+   double *cross;
+   double d;
+   double end[ 2 ];
+   double l1;
+   double l2;
+   double result;
+   double start[ 2 ];
+   int j;
+
+/* Check the global error status. */
+   result = AST__BAD;
+   if ( !astOK ) return result;
+
+/* Create a Line description for a line perpendicular to the specified
+   edge, passing through the mid point of the edge, and extending towards
+   the inside of the polygon. First move away from the start along the
+   line to the mid point. This gives the start of the new line. */
+   l1 = 0.5*( edges[ i ]->length );
+   astLineOffset( frm, edges[ i ], l1, 0.0, start );
+
+/* We now move away from this position at right angles to the line. We
+   start off by moving 5 times the length of the specified edge. For
+   some Frames (e.g. SkyFrames) this may result in a position that is
+   much too close (i.e. if it goes all the way round the great circle
+   and comes back to the beginning). Therefore, we check that the end
+   point is the requested distance from the start point, and if not, we
+   halve the length of the line and try again. */
+   l2 = 10.0*l1;
+   while( 1 ) {
+      astLineOffset( frm, edges[ i ], l1, l2, end );
+      d = astDistance( frm, start, end );
+      if( d != AST__BAD && fabs( d - l2 ) < 1.0E-6*l2 ) break;
+      l2 *= 0.5;
+   }
+
+/* Create a description of the required line. */
+   line = astLineDef( frm, start, end );
+
+/* Loop round every edge, except for the supplied edge. */
+   for( j = 0; j < nv; j++ ) {
+      if( j != i ) {
+
+/* Find the position at which the line created above crosses the current
+   edge. Skip to the next edge if the line does not intersect the edge
+   within the length of the edge. */
+         if( astLineCrossing( frm, line, edges[ j ], &cross ) ) {
+
+/* Find the distance between the crossing point and the line start. */
+            d = astDistance( frm, start, cross );
+
+/* If this is less than the smallest found so far, record it. */
+            if( d != AST__BAD && ( d < result || result == AST__BAD ) ) {
+               result = d;
+            }
+         }
+
+/* Free resources */
+         cross = astFree( cross );
+      }
+   }
+   line = astFree( line );
+
+/* If a width was found, return the point half way across the polygon. */
+   if( result != AST__BAD ) {
+      astOffset( frm, start, end, 0.5*result, cen );
+
+/* The usual reason for not finding a width is if the polygon vertices
+   are supplied in clockwise order, effectively negating the polygon, and
+   resulting in the "inside" of the polygon being the infinite region
+   outside a polygonal hole. In this case, the end point of the line
+   perpendicular to the initial edge can be returned as a representative
+   "inside" point. */
+   } else {
+      cen[ 0 ] = end[ 0 ];
+      cen[ 1 ] = end[ 1 ];
+   }
+
+/* Return the width. */
+   return result;
+
+}
+
+static void RegBaseBox( AstRegion *this_region, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     RegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     Polygon member function (over-rides the astRegBaseBox protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrame *frm;             /* Pointer to encapsulated Frame */
+   AstPointSet *pset;         /* Pointer to PointSet defining the Region */
+   AstPolygon *this;          /* Pointer to Polygon structure */
+   AstRegion *reg;            /* Base Frame equivalent of supplied Polygon */
+   double **ptr;              /* Pointer to PointSet data */
+   double *x;                 /* Pointer to next X axis value */
+   double *y;                 /* Pointer to next Y axis value */
+   double dist;               /* Offset along an axis */
+   double x0;                 /* The first X axis value */
+   double y0;                 /* The first Y axis value */
+   int ip;                    /* Point index */
+   int np;                    /* No. of points in PointSet */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Polygon structure. */
+   this = (AstPolygon *) this_region;
+
+/* If the base Frame bounding box has already been found, return the
+   values stored in the Polygon structure. */
+   if( this->lbnd[ 0 ] != AST__BAD ) {
+      lbnd[ 0 ] = this->lbnd[ 0 ];
+      lbnd[ 1 ] = this->lbnd[ 1 ];
+      ubnd[ 0 ] = this->ubnd[ 0 ];
+      ubnd[ 1 ] = this->ubnd[ 1 ];
+
+/* If the base Frame bounding box has not yet been found, find it now and
+   store it in the Polygon structure. */
+   } else {
+
+/* Get the axis values for the PointSet which defines the location and
+   extent of the region in the base Frame of the encapsulated FrameSet. */
+      pset = this_region->points;
+      ptr = astGetPoints( pset );
+      np = astGetNpoint( pset );
+
+/* Get a pointer to the base Frame in the frameset encapsulated by the
+   parent Region structure. */
+      frm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Find the upper and lower bounds of the box enclosing all the vertices.
+   The box is expressed in terms of axis offsets from the first vertex, in
+   order to avoid problems with boxes that cross RA=0 or RA=12h */
+      lbnd[ 0 ] = 0.0;
+      lbnd[ 1 ] = 0.0;
+      ubnd[ 0 ] = 0.0;
+      ubnd[ 1 ] = 0.0;
+
+      x = ptr[ 0 ];
+      y = ptr[ 1 ];
+
+      x0 = *x;
+      y0 = *y;
+
+      for( ip = 0; ip < np; ip++, x++, y++ ) {
+
+         dist =  astAxDistance( frm, 1, x0, *x );
+         if( dist < lbnd[ 0 ] ) {
+            lbnd[ 0 ] = dist;
+         } else if( dist > ubnd[ 0 ] ) {
+            ubnd[ 0 ] = dist;
+         }
+
+         dist =  astAxDistance( frm, 2, y0, *y );
+         if( dist < lbnd[ 1 ] ) {
+            lbnd[ 1 ] = dist;
+         } else if( dist > ubnd[ 1 ] ) {
+            ubnd[ 1 ] = dist;
+         }
+
+      }
+
+/* Convert the box bounds to absolute values, rather than values relative
+   to the first vertex. */
+      lbnd[ 0 ] += x0;
+      lbnd[ 1 ] += y0;
+      ubnd[ 0 ] += x0;
+      ubnd[ 1 ] += y0;
+
+/* The astNormBox requires a Mapping which can be used to test points in
+   this base Frame. Create a copy of the Polygon and then set its
+   FrameSet so that the current Frame in the copy is the same as the base
+   Frame in the original. */
+      reg = astCopy( this );
+      astSetRegFS( reg, frm );
+      astSetNegated( reg, 0 );
+
+/* Normalise this box. */
+      astNormBox( frm, lbnd, ubnd, reg );
+
+/* Free resources */
+      reg = astAnnul( reg );
+      frm = astAnnul( frm );
+
+/* Store it in the olygon structure for future use. */
+      this->lbnd[ 0 ] = lbnd[ 0 ];
+      this->lbnd[ 1 ] = lbnd[ 1 ];
+      this->ubnd[ 0 ] = ubnd[ 0 ];
+      this->ubnd[ 1 ] = ubnd[ 1 ];
+
+   }
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this_region, int *status ){
+/*
+*  Name:
+*     RegBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing a mesh of points on the boundary of a
+*     Region in its base Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     AstPointSet *astRegBaseMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Polygon member function (over-rides the astRegBaseMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. Annul the pointer using astAnnul when it
+*     is no longer needed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   AstFrame *frm;                 /* Base Frame in encapsulated FrameSet */
+   AstPointSet *result;           /* Returned pointer */
+   AstPolygon *this;              /* The Polygon structure */
+   double **rptr;                 /* Pointers to returned mesh data */
+   double **vptr;                 /* Pointers to vertex data */
+   double *lens;                  /* Pointer to work space holding edge lengths */
+   double d;                      /* Length of this edge */
+   double delta;                  /* Angular separation of points */
+   double end[ 2 ];               /* End position */
+   double mp;                     /* No. of mesh points per unit distance */
+   double p[ 2 ];                 /* Position in 2D Frame */
+   double start[ 2 ];             /* Start position */
+   double total;                  /* Total length of polygon */
+   int ip;                        /* Point index */
+   int iv;                        /* Vertex index */
+   int n;                         /* No. of points on this edge */
+   int next;                      /* Index of next point in returned PointSet */
+   int np;                        /* No. of points in returned PointSet */
+   int nv;                        /* No. of polygon vertices */
+
+/* Initialise */
+   result= NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the Region structure contains a pointer to a PointSet holding
+   a previously created mesh, return it. */
+   if( this_region->basemesh ) {
+      result = astClone( this_region->basemesh );
+
+/* Otherwise, create a new mesh. */
+   } else {
+
+/* Get a pointer to the Polygon structure. */
+      this = (AstPolygon *) this_region;
+
+/* Get a pointer to the base Frame in the encapsulated FrameSet. */
+      frm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* Get the number of vertices and pointers to the vertex axis values. */
+      nv = astGetNpoint( this_region->points );
+      vptr = astGetPoints( this_region->points );
+
+/* Allocate memory to hold the geodesic length of each edge. */
+      lens = astMalloc( sizeof( double )*(size_t) nv );
+
+      if( astOK ) {
+
+/* Find the total geodesic distance around the boundary. */
+         total = 0.0;
+
+         start[ 0 ] = vptr[ 0 ][ 0 ];
+         start[ 1 ] = vptr[ 1 ][ 0 ];
+
+         for( iv = 1; iv < nv; iv++ ) {
+            end[ 0 ] = vptr[ 0 ][ iv ];
+            end[ 1 ] = vptr[ 1 ][ iv ];
+
+            d = astDistance( frm, start, end );
+            if( d != AST__BAD ) total += fabs( d );
+            lens[ iv ] = d;
+            start[ 0 ] = end[ 0 ];
+            start[ 1 ] = end[ 1 ];
+         }
+
+         end[ 0 ] = vptr[ 0 ][ 0 ];
+         end[ 1 ] = vptr[ 1 ][ 0 ];
+
+         d = astDistance( frm, start, end );
+         if( d != AST__BAD ) total += fabs( d );
+         lens[ 0 ] = d;
+
+/* Find the number of mesh points per unit geodesic distance. */
+         if( total > 0.0 ){
+            mp = astGetMeshSize( this )/total;
+
+/* Find the total number of mesh points required. */
+            np = 0;
+            for( iv = 0; iv < nv; iv++ ) {
+               if( lens[ iv ] != AST__BAD ) np += 1 + (int)( lens[ iv ]*mp );
+            }
+
+/* Create a suitable PointSet to hold the returned positions. */
+            result = astPointSet( np, 2, "", status );
+            rptr = astGetPoints( result );
+            if( astOK ) {
+
+/* Initialise the index of the next point to be added to the returned
+   PointSet. */
+               next = 0;
+
+/* Loop round each good edge of the polygon. The edge ends at vertex "iv". */
+               start[ 0 ] = vptr[ 0 ][ 0 ];
+               start[ 1 ] = vptr[ 1 ][ 0 ];
+
+               for( iv = 1; iv < nv; iv++ ) {
+                  end[ 0 ] = vptr[ 0 ][ iv ];
+                  end[ 1 ] = vptr[ 1 ][ iv ];
+                  if( lens[ iv ] != AST__BAD ) {
+
+/* Add the position of the starting vertex to the returned mesh. */
+                     rptr[ 0 ][ next ] = start[ 0 ];
+                     rptr[ 1 ][ next ] = start[ 1 ];
+                     next++;
+
+/* Find the number of points on this edge, and the geodesic distance
+   between them. */
+                     n = 1 + (int) ( lens[ iv ]*mp );
+
+/* If more than one point, find the distance between points. */
+                     if( n > 1 ) {
+                        delta = lens[ iv ]/n;
+
+/* Loop round the extra points. */
+                        for( ip = 1; ip < n; ip++ ) {
+
+/* Find the position of the next mesh point. */
+                           astOffset( frm, start, end, delta*ip, p );
+
+/* Add it to the mesh. */
+                           rptr[ 0 ][ next ] = p[ 0 ];
+                           rptr[ 1 ][ next ] = p[ 1 ];
+                           next++;
+
+                        }
+                     }
+                  }
+
+/* The end of this edge becomes the start of the next. */
+                  start[ 0 ] = end[ 0 ];
+                  start[ 1 ] = end[ 1 ];
+               }
+
+/* Now do the edge which ends at the first vertex. */
+               end[ 0 ] = vptr[ 0 ][ 0 ];
+               end[ 1 ] = vptr[ 1 ][ 0 ];
+               if( lens[ 0 ] != AST__BAD ) {
+                  rptr[ 0 ][ next ] = start[ 0 ];
+                  rptr[ 1 ][ next ] = start[ 1 ];
+                  next++;
+
+                  n = 1 + (int)( lens[ 0 ]*mp );
+                  if( n > 1 ) {
+                     delta = lens[ 0 ]/n;
+                     for( ip = 1; ip < n; ip++ ) {
+                        astOffset( frm, start, end, delta*ip, p );
+                        rptr[ 0 ][ next ] = p[ 0 ];
+                        rptr[ 1 ][ next ] = p[ 1 ];
+                        next++;
+                     }
+                  }
+               }
+
+/* Check the PointSet size was correct. */
+               if( next != np && astOK ) {
+                  astError( AST__INTER, "astRegBaseMesh(%s): Error in the "
+                            "allocated PointSet size (%d) - should have "
+                            "been %d (internal AST programming error).", status,
+                            astGetClass( this ), np, next );
+               }
+
+/* Save the returned pointer in the Region structure so that it does not
+   need to be created again next time this function is called. */
+               if( astOK ) this_region->basemesh = astClone( result );
+
+            }
+
+         } else if( astOK ) {
+            astError( AST__BADIN, "astRegBaseMesh(%s): The boundary of "
+                      "the supplied %s has an undefined length.", status,
+                      astGetClass( this ), astGetClass( this ) );
+         }
+
+      }
+
+/* Free resources. */
+      frm = astAnnul( frm );
+      lens = astFree( lens );
+   }
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static int RegPins( AstRegion *this_region, AstPointSet *pset, AstRegion *unc,
+                    int **mask, int *status ){
+/*
+*  Name:
+*     RegPins
+
+*  Purpose:
+*     Check if a set of points fall on the boundary of a given Polygon.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+*                  int **mask, int *status )
+
+*  Class Membership:
+*     Polygon member function (over-rides the astRegPins protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied set of
+*     points all fall on the boundary of the given Polygon.
+*
+*     Some tolerance is allowed, as specified by the uncertainty Region
+*     stored in the supplied Polygon "this", and the supplied uncertainty
+*     Region "unc" which describes the uncertainty of the supplied points.
+
+*  Parameters:
+*     this
+*        Pointer to the Polygon.
+*     pset
+*        Pointer to the PointSet. The points are assumed to refer to the
+*        base Frame of the FrameSet encapsulated by "this".
+*     unc
+*        Pointer to a Region representing the uncertainties in the points
+*        given by "pset". The Region is assumed to represent the base Frame
+*        of the FrameSet encapsulated by "this". Zero uncertainity is assumed
+*        if NULL is supplied.
+*     mask
+*        Pointer to location at which to return a pointer to a newly
+*        allocated dynamic array of ints. The number of elements in this
+*        array is equal to the value of the Npoint attribute of "pset".
+*        Each element in the returned array is set to 1 if the
+*        corresponding position in "pset" is on the boundary of the Region
+*        and is set to zero otherwise. A NULL value may be supplied
+*        in which case no array is created. If created, the array should
+*        be freed using astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the points all fall on the boundary of the given
+*     Region, to within the tolerance specified. Zero otherwise.
+
+*/
+
+/* Local variables: */
+   AstFrame *frm;               /* Base Frame in supplied Polygon */
+   AstPointSet *pset1;          /* Pointer to copy of supplied PointSet */
+   AstPointSet *pset2;          /* Pointer to PointSet holding resolved components */
+   AstPolygon *this;            /* Pointer to the Polygon structure. */
+   AstRegion *tunc;             /* Uncertainity Region from "this" */
+   double **ptr1;               /* Pointer to axis values in "pset1" */
+   double **ptr2;               /* Pointer to axis values in "pset2" */
+   double **vptr;               /* Pointer to axis values at vertices */
+   double *safe;                /* An interior point in "this" */
+   double edge_len;             /* Length of current edge */
+   double end[2];               /* Position of end of current edge */
+   double l1;                   /* Length of bounding box diagonal */
+   double l2;                   /* Length of bounding box diagonal */
+   double lbnd_tunc[2];         /* Lower bounds of "this" uncertainty Region */
+   double lbnd_unc[2];          /* Lower bounds of supplied uncertainty Region */
+   double par;                  /* Parallel component */
+   double parmax;               /* Max acceptable parallel component */
+   double prp;                  /* Perpendicular component */
+   double start[2];             /* Position of start of current edge */
+   double ubnd_tunc[2];         /* Upper bounds of "this" uncertainty Region */
+   double ubnd_unc[2];          /* Upper bounds of supplied uncertainty Region */
+   double wid;                  /* Width of acceptable margin around polygon */
+   int *m;                      /* Pointer to next mask value */
+   int i;                       /* Edge index */
+   int ip;                      /* Point index */
+   int np;                      /* No. of supplied points */
+   int nv;                      /* No. of vertices */
+   int result;                  /* Returned flag */
+
+/* Initialise */
+   result = 0;
+   if( mask ) *mask = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a pointer to the Polygon structure. */
+   this = (AstPolygon *) this_region;
+
+/* Check the supplied PointSet has 2 axis values per point. */
+   if( astGetNcoord( pset ) != 2 && astOK ) {
+      astError( AST__INTER, "astRegPins(%s): Illegal number of axis "
+                "values per point (%d) in the supplied PointSet - should be "
+                "2 (internal AST programming error).", status, astGetClass( this ),
+                astGetNcoord( pset ) );
+   }
+
+/* Get the number of axes in the uncertainty Region and check it is also 2. */
+   if( unc && astGetNaxes( unc ) != 2 && astOK ) {
+      astError( AST__INTER, "astRegPins(%s): Illegal number of axes (%d) "
+                "in the supplied uncertainty Region - should be 2 "
+                "(internal AST programming error).", status, astGetClass( this ),
+                astGetNaxes( unc ) );
+   }
+
+/* Get pointers to the axis values at the polygon vertices. */
+   vptr = astGetPoints( this_region->points );
+
+/* Get the number of vertices. */
+   nv = astGetNpoint( this_region->points );
+
+/* Take a copy of the supplied PointSet and get pointers to its axis
+   values,and its size */
+   pset1 = astCopy( pset );
+   ptr1 = astGetPoints( pset1 );
+   np = astGetNpoint( pset1 );
+
+/* Create a PointSet to hold the resolved components and get pointers to its
+   axis data. */
+   pset2 = astPointSet( np, 2, "", status );
+   ptr2 = astGetPoints( pset2 );
+
+/* Create a mask array if required. */
+   if( mask ) *mask = astMalloc( sizeof(int)*(size_t) np );
+
+/* Get the centre of the region in the base Frame. We use this as a "safe"
+   interior point within the region. */
+   safe = astRegCentre( this, NULL, NULL, 0, AST__BASE );
+
+/* We now find the maximum distance on each axis that a point can be from the
+   boundary of the Polygon for it still to be considered to be on the boundary.
+   First get the Region which defines the uncertainty within the Polygon
+   being checked (in its base Frame), re-centre it on the interior point
+   found above (to avoid problems if the uncertainty region straddles a
+   discontinuity), and get its bounding box. The current Frame of the
+   uncertainty Region is the same as the base Frame of the Polygon. */
+   tunc = astGetUncFrm( this, AST__BASE );
+   if( safe ) astRegCentre( tunc, safe, NULL, 0, AST__CURRENT );
+   astGetRegionBounds( tunc, lbnd_tunc, ubnd_tunc );
+
+/* Find the geodesic length within the base Frame of "this" of the diagonal of
+   the bounding box. */
+   frm = astGetFrame( this_region->frameset, AST__BASE );
+   l1 = astDistance( frm, lbnd_tunc, ubnd_tunc );
+
+/* Also get the Region which defines the uncertainty of the supplied
+   points and get its bounding box. First re-centre the uncertainty at the
+   interior position to avoid problems from uncertainties that straddle a
+   discontinuity. */
+   if( unc ) {
+      if( safe ) astRegCentre( unc, safe, NULL, 0, AST__CURRENT );
+      astGetRegionBounds( unc, lbnd_unc, ubnd_unc );
+
+/* Find the geodesic length of the diagonal of this bounding box. */
+      l2 = astDistance( frm, lbnd_unc, ubnd_unc );
+
+/* Assume zero uncertainty if no "unc" Region was supplied. */
+   } else {
+      l2 = 0.0;
+   }
+
+/* The required border width is half of the total diagonal of the two bounding
+   boxes. */
+   if( astOK ) {
+      wid = 0.5*( l1 + l2 );
+
+/* Loop round each edge of the polygon. Edge "i" starts at vertex "i-1"
+   and ends at vertex "i". Edge zero starts at vertex "nv-1" and ends at
+   vertex zero. */
+      start[ 0 ] = vptr[ 0 ][ nv - 1 ];
+      start[ 1 ] = vptr[ 1 ][ nv - 1 ];
+      for( i = 0; i < nv; i++ ) {
+         end[ 0 ] = vptr[ 0 ][ i ];
+         end[ 1 ] = vptr[ 1 ][ i ];
+
+/* Find the length of this edge. */
+         edge_len = astDistance( frm, start, end );
+
+/* Resolve all the supplied mesh points into components parallel and
+   perpendicular to this edge. */
+         (void) astResolvePoints( frm, start, end, pset1, pset2 );
+
+/* A point is effectively on this edge if the parallel component is
+   greater than (-wid) and less than (edge_len+wid) AND the perpendicular
+   component has an absolute value less than wid. Identify such positions
+   and set them bad in pset1. */
+         parmax = edge_len + wid;
+         for( ip = 0; ip < np; ip++ ) {
+            par = ptr2[ 0 ][ ip ];
+            prp = ptr2[ 1 ][ ip ];
+
+            if( par != AST__BAD && prp != AST__BAD ) {
+               if( par > -wid && par < parmax && prp > -wid && prp < wid ) {
+                  ptr1[ 0 ][ ip ] = AST__BAD;
+                  ptr1[ 1 ][ ip ] = AST__BAD;
+               }
+            }
+         }
+
+/* The end of the current edge becomes the start of the next. */
+         start[ 0 ] = end[ 0 ];
+         start[ 1 ] = end[ 1 ];
+      }
+
+/* See if any good points are left in pset1. If so, it means that those
+   points were not on any edge of hte Polygon. We use two alogorithms
+   here depending on whether we are creating a mask array, since we can
+   abort the check upon finding the first good point if we are not
+   producing a mask. */
+      result = 1;
+      if( mask ) {
+         m = *mask;
+         for( ip = 0; ip < np; ip++, m++ ) {
+            if( ptr1[ 0 ][ ip ] != AST__BAD &&
+                ptr1[ 1 ][ ip ] != AST__BAD ) {
+               *m = 0;
+               result = 0;
+            } else {
+               *m = 1;
+            }
+         }
+      } else {
+         for( ip = 0; ip < np; ip++ ) {
+            if( ptr1[ 0 ][ ip ] != AST__BAD &&
+                ptr1[ 1 ][ ip ] != AST__BAD ) {
+               result = 0;
+               break;
+            }
+         }
+      }
+   }
+
+/* Free resources. */
+   tunc = astAnnul( tunc );
+   frm = astAnnul( frm );
+   safe = astFree( safe );
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) {
+      result = 0;
+      if( mask ) *mask = astAnnul( *mask );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int RegTrace( AstRegion *this_region, int n, double *dist, double **ptr,
+                     int *status ){
+/*
+*+
+*  Name:
+*     RegTrace
+
+*  Purpose:
+*     Return requested positions on the boundary of a 2D Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     int astTraceRegion( AstRegion *this, int n, double *dist, double **ptr );
+
+*  Class Membership:
+*     Polygon member function (overrides the astTraceRegion method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function returns positions on the boundary of the supplied
+*     Region, if possible. The required positions are indicated by a
+*     supplied list of scalar parameter values in the range zero to one.
+*     Zero corresponds to some arbitrary starting point on the boundary,
+*     and one corresponds to the end (which for a closed region will be
+*     the same place as the start).
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     n
+*        The number of positions to return. If this is zero, the function
+*        returns without action (but the returned function value still
+*        indicates if the method is supported or not).
+*     dist
+*        Pointer to an array of "n" scalar parameter values in the range
+*        0 to 1.0.
+*     ptr
+*        A pointer to an array of pointers. The number of elements in
+*        this array should equal tthe number of axes in the Frame spanned
+*        by the Region. Each element of the array should be a pointer to
+*        an array of "n" doubles, in which to return the "n" values for
+*        the corresponding axis. The contents of the arrays are unchanged
+*        if the supplied Region belongs to a class that does not
+*        implement this method.
+
+*  Returned Value:
+*     Non-zero if the astTraceRegion method is implemented by the class
+*     of Region supplied, and zero if not.
+
+*-
+*/
+
+/* Local Variables; */
+   AstFrame *frm;
+   AstMapping *map;
+   AstPointSet *bpset;
+   AstPointSet *cpset;
+   AstPolygon *this;
+   double **bptr;
+   double d;
+   double p[ 2 ];
+   int i;
+   int j0;
+   int j;
+   int ncur;
+   int nv;
+   int monotonic;
+
+/* Check inherited status, and the number of points to return, returning
+   a non-zero value to indicate that this class supports the astRegTrace
+   method. */
+   if( ! astOK || n == 0 ) return 1;
+
+/* Get a pointer to the Polygon structure. */
+   this = (AstPolygon *) this_region;
+
+/* Ensure cached information is available. */
+   Cache( this, status );
+
+/* Get a pointer to the base Frame in the encapsulated FrameSet. */
+   frm = astGetFrame( this_region->frameset, AST__BASE );
+
+/* We first determine the required positions in the base Frame of the
+   Region, and then transform them into the current Frame. Get the
+   base->current Mapping, and the number of current Frame axes. */
+   map = astGetMapping( this_region->frameset, AST__BASE, AST__CURRENT );
+
+/* If it's a UnitMap we do not need to do the transformation, so put the
+   base Frame positions directly into the supplied arrays. */
+   if( astIsAUnitMap( map ) ) {
+      bpset = NULL;
+      bptr = ptr;
+      ncur = 2;
+
+/* Otherwise, create a PointSet to hold the base Frame positions (known
+   to be 2D since this is an polygon). */
+   } else {
+      bpset = astPointSet( n, 2, " ", status );
+      bptr = astGetPoints( bpset );
+      ncur = astGetNout( map );
+   }
+
+/* Check the pointers can be used safely. */
+   if( astOK ) {
+
+/* Get the number of vertices. */
+      nv = astGetNpoint( this_region->points );
+
+/* If we have a reasonable number of pointsand there are a reasonable
+   number of vertices, we can be quicker if we know if the parameteric
+   distances are monotonic increasing. Find out now. */
+      if( n > 5 && nv > 5 ) {
+
+         monotonic = 1;
+         for( i = 1; i < n; i++ ) {
+            if( dist[ i ] < dist[ i - 1 ] ) {
+               monotonic = 0;
+               break;
+            }
+         }
+
+      } else {
+         monotonic = 0;
+      }
+
+/* Loop round each point. */
+      j0 = 1;
+      for( i = 0; i < n; i++ ) {
+
+/* Get the required round the polygon, starting from vertex zero. */
+         d = dist[ i ]*this->totlen;
+
+/* Loop round each vertex until we find one which is beyond the required
+   point. If the supplied distances are monotonic increasing, we can
+   start the checks at the same vertex that was used for the previous
+   since we know there will never be a backward step. */
+         for( j = j0; j < nv; j++ ) {
+            if( this->startsat[ j ] > d ) break;
+         }
+
+/* If the distances are monotonic increasing, record the vertex that we
+   have reached so far. */
+         if( monotonic ) j0 = j;
+
+/* Find the distance to travel beyond the previous vertex. */
+         d -= this->startsat[ j - 1 ];
+
+/* Find the position, that is the required distance from the previous
+   vertex towards the next vertex. */
+         astLineOffset( frm, this->edges[ j - 1 ], d, 0.0, p );
+
+/* Store the resulting axis values. */
+         bptr[ 0 ][ i ] = p[ 0 ];
+         bptr[ 1 ][ i ] = p[ 1 ];
+      }
+   }
+
+/* If required, transform the base frame positions into the current
+   Frame, storing them in the supplied array. Then free resources. */
+   if( bpset ) {
+      cpset = astPointSet( n, ncur, " ", status );
+      astSetPoints( cpset, ptr );
+
+      (void) astTransform( map, bpset, 1, cpset );
+
+      cpset = astAnnul( cpset );
+      bpset = astAnnul( bpset );
+   }
+
+/* Free remaining resources. */
+   map = astAnnul( map );
+   frm = astAnnul( frm );
+
+/* Return a non-zero value to indicate that this class supports the
+   astRegTrace method. */
+   return 1;
+}
+
+static Segment *RemoveFromChain( Segment *head, Segment *seg, int *status ){
+/*
+*  Name:
+*     RemoveFromChain
+
+*  Purpose:
+*     Remove a Segment from the link list maintained by astDownsize.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     Segment *RemoveFromChain( Segment *head, Segment *seg, int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     The supplied Segment is removed form the list, and the gap is
+*     closed up.
+
+*  Parameters:
+*     head
+*        The Segment structure at the head of the list.
+*     seg
+*        The Segment to be removed from the list.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the link head (which will have changed if "seg" was the
+*     original head).
+
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return head;
+
+/* If the Segment was the original head, make the next segment the new
+   head. */
+   if( head == seg ) head = seg->next;
+
+/* Close up the links between the Segments on either side of the segment
+   being removed. */
+   if( seg->prev ) seg->prev->next = seg->next;
+   if( seg->next ) seg->next->prev = seg->prev;
+
+/* Nullify the links in the segment being removed. */
+   seg->next = NULL;
+   seg->prev = NULL;
+
+/* Return the new head. */
+   return head;
+}
+
+static void ResetCache( AstRegion *this_region, int *status ){
+/*
+*  Name:
+*     ResetCache
+
+*  Purpose:
+*     Clear cached information within the supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void ResetCache( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Region member function (overrides the astResetCache method
+*     inherited from the parent Region class).
+
+*  Description:
+*     This function clears cached information from the supplied Region
+*     structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPolygon *this;
+   int i;
+   int nv;
+
+/* Get a pointer to the Polygon structure. */
+   this = (AstPolygon *) this_region;
+
+/* If a Polygon was supplied, indicate cached information needs to be
+   recalculated. */
+   if( this ) {
+      this->stale = 1;
+      this->lbnd[ 0 ] = AST__BAD;
+
+/* Free any edge structures (number of vertices may be about to change so
+   this cannot be left until the next call to "Cache()". */
+      if( this->edges ) {
+         nv = astGetNpoint( this_region->points );
+         for( i = 0; i < nv; i++ ) {
+            this->edges[ i ] = astFree( this->edges[ i ] );
+         }
+         this->edges = astFree( this->edges );
+      }
+
+/* Clear the cache of the parent class. */
+      (*parent_resetcache)( this_region, status );
+   }
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Polygon.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     Polygon member function (extends the astSetAttrib method inherited from
+*     the Region class).
+
+*  Description:
+*     This function assigns an attribute value for a Polygon, the attribute
+*     and its value being specified by means of a string of the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in lower
+*     case with no white space present. The value to the right of the "="
+*     should be a suitable textual representation of the value to be assigned
+*     and this will be interpreted according to the attribute's data type.
+*     White space surrounding the value is only significant for string
+*     attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Polygon.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+*/
+
+/* Local Vaiables: */
+   AstPolygon *this;             /* Pointer to the Polygon structure */
+   int ival;                     /* Integer attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Polygon structure. */
+   this = (AstPolygon *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* SimpVertices. */
+/* ------------- */
+   if ( nc = 0,
+               ( 1 == astSscanf( setting, "simpvertices= %d %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetSimpVertices( this, ival );
+
+/* Pass any unrecognised setting to the parent method for further
+   interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SetPointSet( AstPolygon *this, AstPointSet *pset, int *status ){
+/*
+*  Name:
+*     SetPointSet
+
+*  Purpose:
+*     Store a new set of vertices in an existing Polygon.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void SetPointSet( AstPolygon *this, AstPointSet *pset, int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     The PointSet in the supplied Polygon is annulled, and replaced by a
+*     clone of the supplied PointSet pointer.
+
+*  Parameters:
+*     this
+*        Pointer to the Polygon to be changed.
+*     pset
+*        The PointSet containing the new vertex information.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Indicate the cached information in the polygon will need to be
+   re-calculated when needed. */
+   astResetCache( this );
+
+/* Annul the pointer to the PointSet already in the supplied Polygon. */
+   (void) astAnnul( ((AstRegion *) this)->points );
+
+/* Store a clone of the supplied new PointSet pointer. */
+   ((AstRegion *) this)->points = astClone( pset );
+
+}
+
+static void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status ) {
+/*
+*  Name:
+*     SetRegFS
+
+*  Purpose:
+*     Stores a new FrameSet in a Region
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     Polygon method (over-rides the astSetRegFS method inherited from
+*     the Region class).
+
+*  Description:
+*     This function creates a new FrameSet and stores it in the supplied
+*     Region. The new FrameSet contains two copies of the supplied
+*     Frame, connected by a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     frm
+*        The Frame to use.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Indicate cached information eeds re-calculating. */
+   astResetCache( this_region );
+
+/* Invoke the parent method to store the FrameSet in the parent Region
+   structure. */
+   (* parent_setregfs)( this_region, frm, status );
+
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify the Mapping represented by a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Polygon method (over-rides the astSimplify method inherited
+*     from the Region class).
+
+*  Description:
+*     This function invokes the parent Region Simplify method, and then
+*     performs any further region-specific simplification.
+*
+*     If the Mapping from base to current Frame is not a UnitMap, this
+*     will include attempting to fit a new Region to the boundary defined
+*     in the current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the simplified Region. A cloned pointer to the
+*     supplied Region will be returned if no simplication could be
+*     performed.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;             /* Current Frame */
+   AstMapping *map;           /* Base -> current Mapping */
+   AstMapping *result;        /* Result pointer to return */
+   AstPointSet *mesh;         /* Mesh of current Frame positions */
+   AstPointSet *ps2;          /* Polygon PointSet in current Frame */
+   AstPolygon *newpol;        /* New Polygon */
+   AstRegion *new;            /* Pointer to simplified Region */
+   AstRegion *this;           /* Pointer to supplied Region structure */
+   AstRegion *unc;            /* Pointer to uncertainty Region */
+   double **ptr2;             /* Pointer axis values in "ps2" */
+   double *mem;               /* Pointer to array holding new vertex coords */
+   double *p;                 /* Pointer to next vertex coords */
+   double *q;                 /* Pointer to next vertex coords */
+   int iv;                    /* Vertex index */
+   int nv;                    /* Number of vertices in polygon */
+   int ok;                    /* Are the new polygon vertices good? */
+   int simpler;               /* Has some simplication taken place? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the supplied Region structure. */
+   this = (AstRegion *) this_mapping;
+
+/* Invoke the parent Simplify method inherited from the Region class. This
+   will simplify the encapsulated FrameSet and uncertainty Region. */
+   new = (AstRegion *) (*parent_simplify)( this_mapping, status );
+
+/* Note if any simplification took place. This is assumed to be the case
+   if the pointer returned by the above call is different to the supplied
+   pointer. */
+   simpler = ( new != this );
+
+/* We attempt to simplify the Polygon by re-defining it within its current
+   Frame. Transforming the Polygon from its base to its current Frame may
+   result in the region no having the same edges. If required, we test this
+   by transforming a set of bounds on the Polygon boundary. This can only
+   be done if the current Frame is 2-dimensional. Also, there is only any
+   point in doing it if the Mapping from base to current Frame in the
+   Polygon is not a UnitMap. */
+   map = astGetMapping(  new->frameset, AST__BASE, AST__CURRENT );
+   if( !astIsAUnitMap( map ) && astGetNout( map ) == 2 ) {
+
+/* Get a pointer to the Frame represented by the Polgon. */
+      frm = astGetFrame( new->frameset, AST__CURRENT );
+
+/* Get the Region describing the positional uncertainty in this Frame. */
+      unc = astGetUncFrm( new, AST__CURRENT );
+
+/* Get the positions of the vertices within this Frame. */
+      ps2 = astRegTransform( this, this->points, 1, NULL, NULL );
+      ptr2 = astGetPoints( ps2 );
+
+/* Get the number of vertices. */
+      nv = astGetNpoint( ps2 );
+
+/* Re-organise the vertex axis values into the form required by the
+   Polygon constructor function. */
+      mem = astMalloc( sizeof( double)*(size_t)( 2*nv ) );
+      if( astOK ) {
+         ok = 1;
+         p = mem;
+         q = ptr2[ 0 ];
+         for( iv = 0; iv < nv; iv++ ) {
+            if( ( *(p++) = *(q++) ) == AST__BAD ) ok = 0;
+         }
+         q = ptr2[ 1 ];
+         for( iv = 0; iv < nv; iv++ ) *(p++) = *(q++);
+
+/* Create a new Polygon using these transformed vertices. */
+         if( ok ) {
+            newpol = astPolygon( frm, nv, nv, mem, unc, "", status );
+
+/* If the SimpVertices attribute is zero, we now check that the
+   transformation has not bent the edges of the polygon significantly.
+   If it has, we annul the new Polygon. */
+            if( !astGetSimpVertices( this ) ) {
+
+/* Get a mesh of points covering the Polygon in this Frame. */
+               mesh = astRegMesh( new );
+
+/* See if all points within the mesh created from the original Polygon fall
+   on the boundary of the new Polygon, to within the uncertainty of the
+   Region. If not, annul the new Polgon. */
+               if( !astRegPins( newpol, mesh, NULL, NULL ) ) {
+                  newpol = astAnnul( newpol );
+               }
+
+/* Free the mesh. */
+               mesh = astAnnul( mesh );
+            }
+
+/* If we still have a new polygon, use the new Polygon in place of the
+   original Region. */
+            if( newpol ) {
+               (void) astAnnul( new );
+               new = (AstRegion *) newpol;
+               simpler = 1;
+            }
+         }
+      }
+
+/* Free other resources. */
+      frm = astAnnul( frm );
+      unc = astAnnul( unc );
+      ps2 = astAnnul( ps2 );
+      mem = astFree( mem );
+   }
+   map = astAnnul( map );
+
+/* If any simplification could be performed, copy Region attributes from
+   the supplied Region to the returned Region, and return a pointer to it.
+   If the supplied Region had no uncertainty, ensure the returned Region
+   has no uncertainty. Otherwise, return a clone of the supplied pointer. */
+   if( simpler ){
+      astRegOverlay( new, this, 1 );
+      result = (AstMapping *) new;
+
+   } else {
+      new = astAnnul( new );
+      result = astClone( this );
+   }
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void SmoothPoly( AstPointSet *pset, int boxsize, double strength,
+                        int *status ) {
+/*
+*  Name:
+*     SmoothPoly
+
+*  Purpose:
+*     Smoooth a polygon assuming plane geometry.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void SmoothPoly( AstPointSet *pset, int boxsize, double strength,
+*                      int *status )
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     This function smooths a polygon, without changing the number of
+*     vertices. It assumes plane geometry, so should not be used to
+*     smooth polygons defined within a SkyFrame or CmpFrame.
+*
+*     Each vertex is replaced by a new vertex determined as follows: the
+*     mean X and Y axis value of the vertices in a section of the polygon
+*     centred on the vertex being replaced are found (the length of the
+*     section is given by parameter "boxsize"). The new vertex position
+*     is then the weighted mean of this mean (X,Y) position and the old
+*     vertex position. The weight for the mean (X,Y) position is given
+*     by parameter "strength", and the weight for the old vertex
+*     position is (1.0 - strength)
+
+*  Parameters:
+*     pset
+*        A PointSet holding the polygon vertices.
+*     boxsize
+*        Half width of the box filter, given as a number of vertices.
+*     strength
+*        The weight to use for the mean (X,Y) position when finding each
+*        new vertex position. Should be in the range 0.0 to 1.0. A value
+*        of zero results in no change being made to the polygon. A value
+*        of 1.0 results in the returned polygon being fully smoothed.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double **ptr;
+   double *newx;
+   double *newy;
+   double *nx;
+   double *ny;
+   double *oldx;
+   double *oldy;
+   double *ox;
+   double *oy;
+   double *px;
+   double *py;
+   double *qx;
+   double *qy;
+   double a;
+   double b;
+   double sx;
+   double sy;
+   int half_width;
+   int i;
+   int nv;
+   int top;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the number of vertices. */
+   nv = astGetNpoint( pset );
+
+/* Get pointers to arrays holding the supplied vertex positions. */
+   ptr = astGetPoints( pset );
+   oldx = ptr[ 0 ];
+   oldy = ptr[ 1 ];
+
+/* Allocate arrays to hold the returned vertex positions. */
+   newx = astMalloc( nv*sizeof( double ) );
+   newy = astMalloc( nv*sizeof( double ) );
+
+/* Check these pointers can be used safely. */
+   if( astOK ) {
+
+/* Get weighting factors for the fully smoothed and unsmoothed positions. */
+      a = strength;
+      b = 1.0 - a;
+
+/* Ensure the box size is sufficiently small for there to be room for
+   two boxes along the polygon. */
+      half_width = nv/4 - 1;
+      if( boxsize < half_width ) half_width = boxsize;
+      if( half_width < 1 ) half_width = 1;
+
+/* Modify the weight for the fully smoothed position to include the
+   normalisation factor needed to account for the box width. */
+      a /= 2*half_width + 1;
+
+/* Find the sum of the x and y coordinates within a box centred on the
+   first vertex. This includes vertices from the end of the polygon. */
+      px = oldx + 1;
+      qx = oldx + nv;
+      sx = (oldx)[ 0 ];
+
+      py = oldy + 1;
+      qy = oldy + nv;
+      sy = (oldy)[ 0 ];
+
+      for( i = 0; i < half_width; i++ ) {
+         sx += *(px++) + *(--qx);
+         sy += *(py++) + *(--qy);
+      }
+
+/* Replacing vertices within the first half box will include vertices at
+   both ends of the polygon. Set up the pointers accordingly, and then
+   find replacements for each vertex in the first half box.*/
+      ox = oldx;
+      oy = oldy;
+      nx = newx;
+      ny = newy;
+      for( i = 0; i < half_width; i++ ) {
+
+/* Form the new vertex (x,y) values as the weighted mean of the mean
+   (x,y) values in the box, and the old (x,y) values. */
+         *(nx++) = a*sx + b*( *(ox++) );
+         *(ny++) = a*sy + b*( *(oy++) );
+
+/* Add in the next vertex X and Y axis values to the running sums, and
+   remove the position that has now passed out of the box. */
+         sx += *(px++) - *(qx++);
+         sy += *(py++) - *(qy++);
+      }
+
+/* Adjust the pointer for the rest of the polygon, up to one half box away
+   from the end. In this section, the smoothing box does not touch either
+   end of the polygon. */
+      top = nv - half_width - 1;
+      qx = oldx;
+      qy = oldy;
+      for( ; i < top; i++ ){
+
+/* Form the new vertex (x,y) values as the weighted mean of the mean
+   (x,y) values in the box, and the old (x,y) values. */
+         *(nx++) = a*sx + b*( *(ox++) );
+         *(ny++) = a*sy + b*( *(oy++) );
+
+/* Add in the next vertex X and Y axis values to the running sums, and
+   remove the position that has now passed out of the box. */
+         sx += *(px++) - *(qx++);
+         sy += *(py++) - *(qy++);
+      }
+
+/* Now do the last half box (which includes vertices from the start of
+   the polygon). */
+      top = nv;
+      px = oldx;
+      py = oldy;
+      for( ; i < top; i++ ){
+         *(nx++) = a*sx + b*( *(ox++) );
+         *(ny++) = a*sy + b*( *(oy++) );
+         sx += *(px++) - *(qx++);
+         sy += *(py++) - *(qy++);
+      }
+
+/* Replace the data points in the PointSet. */
+      ptr[ 0 ] = newx;
+      ptr[ 1 ] = newy;
+      oldx = astFree( oldx );
+      oldy = astFree( oldy );
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a Polygon.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Polygon member function (over-rides the astTestAttrib protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a Polygon's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Polygon.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPolygon *this;             /* Pointer to the Polygon structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Polygon structure. */
+   this = (AstPolygon *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* SimpVertices. */
+/* ------------- */
+   if ( !strcmp( attrib, "simpvertices" ) ) {
+      result = astTestSimpVertices( this );
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+/*
+*  Name:
+*     TraceEdge
+
+*  Purpose:
+*     Find a point that is inside the required outline.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     void TraceEdge<Oper><X>( <Xtype> value, const <Xtype> array[],
+*                              const int lbnd[ 2 ], const int ubnd[ 2 ],
+*                              int iv0, int ix0, int iy0, int starpix,
+*                              int full, int *status );
+
+*  Class Membership:
+*     Polygon member function
+
+*  Description:
+*     This function forms a polygon enclosing the region of the data
+*     array specified by <Oper> and "value". If this polygon contains
+*     the point "(inx,iny)", then a PointSet is returned holding the
+*     pixel coordinates of the Polygon vertices. If the polygon
+*     does not contain "(inx,iny)", a NULL pointer is returned.
+*
+*     Each vertex in the polygon corresponds to a corner of a pixel in
+*     the data array.
+
+*  Parameters:
+*     value
+*        The data value defining valid pixels.
+*     array
+*        The data array.
+*     lbnd
+*        The lower pixel index bounds of the array.
+*     ubnd
+*        The upper pixel index bounds of the array.
+*     iv0
+*        The vector index of a pixel inside the region such that the
+*        pixel to the right is NOT inside the region. This defines the
+*        start of the polygon.
+*     ix0
+*        The X pixel index of the pixel specified by "iv0".
+*     inx
+*        The X pixel index of a point which must be inside the polygon
+*        for the polygon to be acceptable.
+*     iny
+*        The Y pixel index of a point which must be inside the polygon
+*        for the polygon to be acceptable.
+*     starpix
+*        If non-zero, the usual Starlink definition of pixel coordinate
+*        is used (integral values at pixel corners). Otherwise, the
+*        system used by other AST functions such as astResample is used
+*        (integral values at pixel centres).
+*     full
+*        If non-zero, the full polygon is stored. If zero, vertices in the
+*        middle of straight sections of the Polygon are omitted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a PointSet holding the vertices of the polygon, or
+*     NULL if the polygon did not contain "(inx,iny)".
+
+*  Notes:
+*     - <Oper> must be one of LT, LE, EQ, GE, GT, NE.
+
+
+*/
+
+/* Define a macro to implement the function for a specific data
+   type and operation. */
+#define MAKE_TRACEEDGE(X,Xtype,Oper,OperI) \
+static AstPointSet *TraceEdge##Oper##X( Xtype value, const Xtype array[], \
+                                        const int lbnd[ 2 ], const int ubnd[ 2 ], \
+                                        int iv0, int ix0, int iy0, \
+                                        int starpix, int full, \
+                                        int *status ){ \
+\
+/* Local Variables: */ \
+   AstPointSet *result; /* Pointer to text describing oper */ \
+   const Xtype *pa;     /* Pointer to current valid pixel value */ \
+   const Xtype *pb;     /* Pointer to neigbouring valid pixel value */ \
+   const Xtype *pc;     /* Pointer to neigbouring valid pixel value */ \
+   double *ptr[ 2 ];    /* PointSet data pointers */ \
+   double *xvert;       /* Pointer to array holding vertex X axis values */ \
+   double *yvert;       /* Pointer to array holding vertex Y axis values */ \
+   double dx;           /* Pertubation in X (pixels) to avoid the pixel edge */ \
+   double dy;           /* Pertubation in Y (pixels) to avoid the pixel edge */ \
+   double xx;           /* Pixel X coord at corner */ \
+   double yy;           /* Pixel Y coord at corner */ \
+   int at;              /* The pixel corner to draw to */ \
+   int done;            /* Have we arrived back at the start of the polygon? */ \
+   int ii;              /* Index of new vertex */ \
+   int ix;              /* X pixel index of current valid pixel */ \
+   int iy;              /* Y pixel index of current valid pixel */ \
+   int nright;          /* Overall number of right hand turns along polygon */ \
+   int nvert;           /* Number of vertices */ \
+   int nx;              /* Pixels per row */ \
+\
+/* Initialise */ \
+   result = NULL; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+\
+/* Initialise pointers to arrays holding the X and Y pixel coordinates at \
+   the vertices of the polygon. */ \
+   xvert = NULL; \
+   yvert = NULL; \
+   nvert = 0; \
+\
+/* Find number of pixels in one row of the array. */ \
+   nx = ( ubnd[ 0 ] - lbnd[ 0 ] + 1 ); \
+\
+/* The four corners of a pixel are numbered as follows: 0=bottom left, \
+   1=top left, 2=top right, 3=bottom right. The following algorithm moves \
+   along pixel edges, from corner to corner, using the above numbering \
+   scheme to identify the corners. We start the polygon by moving from the \
+   bottom right to the top right corner of pixel "(ix0,iy0)". */ \
+   ix = ix0; \
+   iy = iy0; \
+   at = 2; \
+\
+/* Store a pointer to the first good pixel value. */ \
+   pa = array + ( ix - lbnd[ 0 ] ) + nx*( iy - lbnd[ 1 ] ) ; \
+\
+/* We count the number of times the polygon turns to the right compared \
+   to the left. Initialise it to zero. */ \
+   nright = 0; \
+\
+/* Loop round tracing out the polygon until we arrive back at the \
+   beginning. The Polygon class requires that the inside of the polygon \
+   is to the left as we move round the polygon in an anti-clockwise \
+   direction. So at each corner, we attempt to move to the next \
+   anti-clockwise good pixel corner. */ \
+   done = 0; \
+   while( !done ) { \
+\
+/* If we have arrived at the bottom left corner of the good pixel, we must \
+   have come from the top left corner since all movements around a pixel \
+   must be anti-clockwise. */ \
+      if( at == 0 ) { \
+\
+/* Note the pixel coordinates at the bottom left corner of the current \
+   pixel. */ \
+         if( starpix ) { \
+            xx = ix - 1.0; \
+            yy = iy - 1.0; \
+         } else { \
+            xx = ix - 0.5; \
+            yy = iy - 0.5; \
+         } \
+\
+/* Get a pointer to lower left pixel value */ \
+         pb = pa - nx - 1; \
+\
+/* Get a pointer to lower mid pixel value. */ \
+         pc = pb + 1; \
+\
+/* If the lower left pixel is within the array and meets the validity \
+   requirements, move to the left along its top edge. */ \
+         if( iy > lbnd[ 1 ] && ix > lbnd[ 0 ] && ISVALID(*pb,OperI,value) ) { \
+            nright++; \
+            pa = pb; \
+            at = 1; \
+            ix--; \
+            iy--; \
+            dx = DELTA; \
+            dy = -DELTA; \
+\
+/* Otherwise, if lower mid pixel is good, move down its left edge. */ \
+         } else if( iy > lbnd[ 1 ] && ISVALID(*pc,OperI,value) ) { \
+            pa = pc; \
+            at = 0; \
+            iy--; \
+            dx = DELTA; \
+            dy = 0.0; \
+\
+/* Otherwise, move to the right along the bottom edge of the current pixel. */ \
+         } else { \
+            nright--; \
+            at = 3; \
+            dx = DELTA; \
+            dy = DELTA; \
+         } \
+\
+/* If the polygon bends at this point, or if we will be smoothing the \
+   polygon, append the pixel coordinates at this pixel corner to the \
+   polygon. */ \
+         if( full || pa != pc ) ADD( xx, yy ); \
+\
+/* If we have arrived at the top left corner of the good pixel, we must \
+   have come from the top right corner. */ \
+      } else if( at == 1 ) { \
+\
+/* Note the pixel coordinates at the top left corner of the current \
+   pixel. */ \
+         if( starpix ) { \
+            xx = ix - 1.0; \
+            yy = iy; \
+         } else { \
+            xx = ix - 0.5; \
+            yy = iy + 0.5; \
+         } \
+\
+/* Get a pointer to upper left pixel value */ \
+         pb = pa + nx - 1; \
+\
+/* Get a pointer to mid left pixel value. */ \
+         pc = pa - 1; \
+\
+/* If upper left pixel is good, move up its left edge. */ \
+         if( iy < ubnd[ 1 ] && ix > lbnd[ 0 ] && ISVALID(*pb,OperI,value) ) { \
+            nright++; \
+            pa = pb; \
+            at = 2; \
+            ix--; \
+            iy++; \
+            dx = -DELTA; \
+            dy = -DELTA; \
+\
+/* Otherwise, if left mid pixel is good, move left along its top edge. */ \
+         } else if( ix > lbnd[ 0 ] && ISVALID(*pc,OperI,value) ) { \
+            pa = pc; \
+            at = 1; \
+            ix--; \
+            dx = 0.0; \
+            dy = -DELTA; \
+\
+/* Otherwise, move down the left edge of the current pixel. */ \
+         } else { \
+            nright--; \
+            at = 0; \
+            dx = DELTA; \
+            dy = -DELTA; \
+         } \
+\
+/* If the polygon bends at this point, or if we will be smoothing the \
+   polygon, append the pixel coordinates at this pixel corner to the \
+   polygon. */ \
+         if( full || pa != pc ) ADD( xx, yy ); \
+\
+/* If we have arrived at the top right corner of the good pixel, we must \
+   have come from the bottom right corner. */ \
+      } else if( at == 2 ) { \
+\
+/* Note the pixel coordinates at the top right corner of the current \
+   pixel. */ \
+         if( starpix ) { \
+            xx = ix; \
+            yy = iy; \
+         } else { \
+            xx = ix + 0.5; \
+            yy = iy + 0.5; \
+         } \
+\
+/* Pointer to upper right pixel value */ \
+         pb = pa + nx + 1; \
+\
+/* Pointer to top mid pixel value. */ \
+         pc = pa + nx; \
+\
+/* If upper right pixel is good, move right along its bottom edge. */ \
+         if( iy < ubnd[ 1 ] && ix < ubnd[ 0 ] && ISVALID(*pb,OperI,value) ){ \
+            nright++; \
+            pa = pb; \
+            at = 3; \
+            ix++; \
+            iy++; \
+            dx = -DELTA; \
+            dy = DELTA; \
+\
+/* Otherwise, if upper mid pixel is good, move up its right edge. */ \
+         } else if( iy < ubnd[ 1 ] && ISVALID(*pc,OperI,value) ) { \
+            pa = pc; \
+            at = 2; \
+            iy++; \
+            dx = -DELTA; \
+            dy = 0.0; \
+\
+/* Otherwise, move left along the top edge of the current pixel. */ \
+         } else { \
+            nright--; \
+            at = 1; \
+            dx = -DELTA; \
+            dy = -DELTA; \
+         } \
+\
+/* If the polygon bends at this point, or if we will be smoothing the \
+   polygon, append the pixel coordinates at this pixel corner to the \
+   polygon. */ \
+         if( full || pa != pc ) ADD( xx, yy ); \
+\
+/* Arrived at bottom right corner of good pixel from lower left. */ \
+      } else { \
+\
+/* Note the pixel coordinates at the bottom right corner of the current \
+   pixel. */ \
+         if( starpix ) { \
+            xx = ix; \
+            yy = iy - 1.0; \
+         } else { \
+            xx = ix + 0.5; \
+            yy = iy - 0.5; \
+         } \
+\
+/* Pointer to lower right pixel value */ \
+         pb = pa - ( nx - 1 ); \
+\
+/* Pointer to mid right pixel value. */ \
+         pc = pa + 1; \
+\
+/* If lower right pixel is good, move down its left edge. */ \
+         if( iy > lbnd[ 1 ] && ix < ubnd[ 0 ] && ISVALID(*pb,OperI,value) ) { \
+            nright++; \
+            pa = pb; \
+            at = 0; \
+            ix++; \
+            iy--; \
+            dx = DELTA; \
+            dy = DELTA; \
+\
+/* Otherwise, if right mid pixel is good, move right along its lower edge. */ \
+         } else if( ix < ubnd[ 0 ] && ISVALID(*pc,OperI,value) ) { \
+            pa = pc; \
+            at = 3; \
+            ix++; \
+            dx = 0.0; \
+            dy = DELTA; \
+\
+/* Otherwise, move up the right edge of the current pixel. */ \
+         } else { \
+            nright--; \
+            at = 2; \
+            dx = -DELTA; \
+            dy = DELTA; \
+         } \
+\
+/* If the polygon bends at this point, or if we will be smoothing the \
+   polygon, append the pixel coordinates at this pixel corner to the \
+   polygon. */ \
+         if( full || pa != pc ) ADD( xx, yy ); \
+      } \
+\
+/* If we have arrived back at the start, break out of the loop. */ \
+      if( ix == ix0 && iy == iy0 && at == 2 ) done = 1; \
+   } \
+\
+/* If we have circled round to the right, the polygon will not enclosed \
+   the specified position, so free resources and return a NULL pointer. */ \
+   if( nright > 0 ) { \
+      xvert = astFree( xvert ); \
+      yvert = astFree( yvert ); \
+\
+/* If we have circled round to the left, the polygon will enclose \
+   the specified position, so create and return a PointSet. */ \
+   } else { \
+      result = astPointSet( nvert, 2, " ", status ); \
+      ptr[ 0 ] = xvert; \
+      ptr[ 1 ] = yvert; \
+      astSetPoints( result, ptr ); \
+   } \
+\
+/* Annul the returned PointSet if anythign went wrong. */ \
+   if( !astOK && result ) result = astAnnul( result ); \
+\
+/* Return the PointSet pointer. */ \
+   return result; \
+}
+
+/* Define a macro to add a vertex position to dynamically allocated
+   arrays of X and Y positions. We offset the supplied position by
+   a small fraction of a pixel towards the centre of hte polygon to
+   avoid placing vertices exactly on the edge, which may cause problems
+   later for pixels that are on the edge of an area of bad pixel. */
+#define ADD(X,Y) {\
+   ii = nvert++; \
+   xvert = (double *) astGrow( xvert, nvert, sizeof( double ) ); \
+   yvert = (double *) astGrow( yvert, nvert, sizeof( double ) ); \
+   if( astOK ) { \
+      xvert[ ii ] = (X+dx); \
+      yvert[ ii ] = (Y+dy); \
+   } \
+}
+
+/* Define a macro that uses the above macro to to create implementations
+   of TraceEdge for all operations. */
+#define MAKEALL_TRACEEDGE(X,Xtype) \
+MAKE_TRACEEDGE(X,Xtype,LT,AST__LT) \
+MAKE_TRACEEDGE(X,Xtype,LE,AST__LE) \
+MAKE_TRACEEDGE(X,Xtype,EQ,AST__EQ) \
+MAKE_TRACEEDGE(X,Xtype,NE,AST__NE) \
+MAKE_TRACEEDGE(X,Xtype,GE,AST__GE) \
+MAKE_TRACEEDGE(X,Xtype,GT,AST__GT)
+
+/* Expand the above macro to generate a function for each required
+   data type and operation. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKEALL_TRACEEDGE(LD,long double)
+#endif
+MAKEALL_TRACEEDGE(D,double)
+MAKEALL_TRACEEDGE(L,long int)
+MAKEALL_TRACEEDGE(UL,unsigned long int)
+MAKEALL_TRACEEDGE(I,int)
+MAKEALL_TRACEEDGE(UI,unsigned int)
+MAKEALL_TRACEEDGE(S,short int)
+MAKEALL_TRACEEDGE(US,unsigned short int)
+MAKEALL_TRACEEDGE(B,signed char)
+MAKEALL_TRACEEDGE(UB,unsigned char)
+MAKEALL_TRACEEDGE(F,float)
+
+/* Undefine the macros. */
+#undef MAKE_TRACEEDGE
+#undef MAKEALL_TRACEEDGE
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a Polygon to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     Polygon member function (over-rides the astTransform protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function takes a Polygon and a set of points encapsulated in a
+*     PointSet and transforms the points by setting axis values to
+*     AST__BAD for all points which are outside the region. Points inside
+*     the region are copied unchanged from input to output.
+
+*  Parameters:
+*     this
+*        Pointer to the Polygon.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  The forward and inverse transformations are identical for a
+*     Region.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of axes in the Frame represented by the Polygon.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;                /* Pointer to base Frame in FrameSet */
+   AstLineDef *a;                /* Line from inside point to test point */
+   AstLineDef *b;                /* Polygon edge */
+   AstPointSet *in_base;         /* PointSet holding base Frame input positions*/
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstPolygon *this;             /* Pointer to Polygon */
+   double **ptr_in;              /* Pointer to input base Frame coordinate data */
+   double **ptr_out;             /* Pointer to output current Frame coordinate data */
+   double *px;                   /* Pointer to array of first axis values */
+   double *py;                   /* Pointer to array of second axis values */
+   double p[ 2 ];                /* Current test position */
+   int closed;                   /* Is the boundary part of the Region? */
+   int i;                        /* Edge index */
+   int icoord;                   /* Coordinate index */
+   int in_region;                /* Is the point inside the Region? */
+   int ncoord_out;               /* No. of current Frame axes */
+   int ncross;                   /* Number of crossings */
+   int neg;                      /* Has the Region been negated? */
+   int npoint;                   /* No. of input points */
+   int nv;                       /* No. of vertices */
+   int point;                    /* Loop counter for input points */
+   int pos;                      /* Is test position in, on, or outside boundary? */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the Polygon structure. */
+   this = (AstPolygon *) this_mapping;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Region class. This function validates
+   all arguments and generates an output PointSet if necessary,
+   containing a copy of the input PointSet. */
+   result = (*parent_transform)( this_mapping, in, forward, out, status );
+
+/* Get the number of points to be transformed. */
+   npoint = astGetNpoint( result );
+
+/* Get a pointer to the output axis values. */
+   ptr_out = astGetPoints( result );
+
+/* Find the number of axes in the current Frame. This need not be 2 (the
+   number of axes in the *base* Frame must be 2 however). */
+   ncoord_out = astGetNcoord( result );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* First use the encapsulated FrameSet to transform the supplied positions
+   from the current Frame in the encapsulated FrameSet (the Frame
+   represented by the Region), to the base Frame (the Frame in which the
+   Region is defined). This call also returns a pointer to the base Frame
+   of the encapsulated FrameSet. Note, the returned pointer may be a
+   clone of the "in" pointer, and so we must be carefull not to modify the
+   contents of the returned PointSet. */
+   in_base = astRegTransform( this, in, 0, NULL, &frm );
+   ptr_in = astGetPoints( in_base );
+
+/* Get the number of vertices in the polygon. */
+   nv = astGetNpoint( ((AstRegion *) this)->points );
+
+/* See if the boundary is part of the Region. */
+   closed = astGetClosed( this );
+
+/* See if the Region has been negated. */
+   neg = astGetNegated( this );
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+      px = ptr_in[ 0 ];
+      py = ptr_in[ 1 ];
+
+/* Loop round each supplied point in the base Frame of the polygon. */
+      for ( point = 0; point < npoint; point++, px++, py++ ) {
+
+/* If the input point is bad, indicate that bad output values should be
+   returned. */
+         if( *px == AST__BAD || *py == AST__BAD ) {
+            in_region = 0;
+
+/* Otherwise, we first determine if the point is inside, outside, or on,
+   the Polygon boundary. Initialially it is unknown. */
+         } else {
+
+/* Ensure cached information is available.*/
+            Cache( this, status );
+
+/* Create a definition of the line from a point which is inside the
+   polygon to the supplied point. This is a structure which includes
+   cached intermediate information which can be used to speed up
+   subsequent calculations. */
+            p[ 0 ] = *px;
+            p[ 1 ] = *py;
+            a = astLineDef( frm, this->in, p );
+
+/* We now determine the number of times this line crosses the polygon
+   boundary. Initialise the number of crossings to zero. */
+            ncross = 0;
+            pos = UNKNOWN;
+
+/* Loop rouind all edges of the polygon. */
+            for( i = 0; i < nv; i++ ) {
+               b = this->edges[ i ];
+
+/* If this point is on the current edge, then we need do no more checks
+   since we know it is either inside or outside the polygon (depending on
+   whether the polygon is closed or not). */
+               if( astLineContains( frm, b, 0, p ) ) {
+                  pos = ON;
+                  break;
+
+/* Otherwise, see if the two lines cross within their extent. If so,
+   increment the number of crossings. */
+               } else if( astLineCrossing( frm, b, a, NULL ) ) {
+                  ncross++;
+               }
+            }
+
+/* Free resources */
+            a = astFree( a );
+
+/* If the position is not on the boundary, it is inside the boundary if
+   the number of crossings is even, and outside otherwise. */
+            if( pos == UNKNOWN ) pos = ( ncross % 2 == 0 )? IN : OUT;
+
+/* Whether the point is in the Region depends on whether the point is
+   inside the polygon boundary, whether the Polygon has been negated, and
+   whether the polygon is closed. */
+            if( neg ) {
+               if( pos == IN ) {
+                  in_region = 0;
+               } else if( pos == OUT ) {
+                  in_region = 1;
+               } else if( closed ) {
+                  in_region = 1;
+               } else {
+                  in_region = 0;
+               }
+
+            } else {
+               if( pos == IN ) {
+                  in_region = 1;
+               } else if( pos == OUT ) {
+                  in_region = 0;
+               } else if( closed ) {
+                  in_region = 1;
+               } else {
+                  in_region = 0;
+               }
+            }
+         }
+
+/* If the point is not inside the Region, store bad output values. */
+         if( !in_region ) {
+            for ( icoord = 0; icoord < ncoord_out; icoord++ ) {
+               ptr_out[ icoord ][ point ] = AST__BAD;
+            }
+         }
+      }
+   }
+
+/* Free resources */
+   in_base = astAnnul( in_base );
+   frm = astAnnul( frm );
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     SimpVertices
+
+*  Purpose:
+*     Simplify a Polygon by transforming its vertices?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls the behaviour of the
+c     astSimplify
+f     AST_SIMPLIFY
+*     method when applied to a Polygon. The simplified Polygon is created
+*     by transforming the vertices from the Frame in which the Polygon
+*     was originally defined into the Frame currently represented by the
+*     Polygon. If SimpVertices is non-zero (the default) then this
+*     simplified Polygon is returned without further checks. If SimpVertices
+*     is zero, a check is made that the edges of the new Polygon do not
+*     depart significantly from the edges of the original Polygon (as
+*     determined by the uncertainty associated with the Polygon). This
+*     could occur, for instance, if the Mapping frrm the original to the
+*     current Frame is highly non-linear. If this check fails, the
+*     original unsimplified Polygon is returned without change.
+
+*  Applicability:
+*     Polygon
+*        All Polygons have this attribute.
+
+*att--
+*/
+astMAKE_CLEAR(Polygon,SimpVertices,simp_vertices,-INT_MAX)
+astMAKE_GET(Polygon,SimpVertices,int,0,( ( this->simp_vertices != -INT_MAX ) ?
+                                   this->simp_vertices : 1 ))
+astMAKE_SET(Polygon,SimpVertices,int,simp_vertices,( value != 0 ))
+astMAKE_TEST(Polygon,SimpVertices,( this->simp_vertices != -INT_MAX ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Polygon objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Polygon objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstPolygon *out;               /* Pointer to output Polygon  */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the output Polygon. */
+   out = (AstPolygon *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output Polygon. */
+   out->edges = NULL;
+   out->startsat = NULL;
+
+/* Indicate cached information needs nre-calculating. */
+   astResetCache( (AstPolygon *) out );
+}
+
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Polygon objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Polygon objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstPointSet *ps;                  /* Pointer to PointSet inside Region */
+   AstPolygon *this;                 /* Pointer to Polygon */
+   int i;                            /* Index of vertex */
+   int istat;                        /* Original AST error status */
+   int nv;                           /* Number of vertices */
+   int rep;                          /* Original error reporting state */
+
+/* Obtain a pointer to the Polygon structure. */
+   this = (AstPolygon *) obj;
+
+/* Annul all resources. */
+   ps = ((AstRegion *) this)->points;
+   if( this->edges && ps ) {
+
+/* Ensure we get a value for "nv" even if an error has occurred. */
+      istat = astStatus;
+      astClearStatus;
+      rep = astReporting( 0 );
+
+      nv = astGetNpoint( ps );
+
+      astSetStatus( istat );
+      astReporting( rep );
+
+/* Free the structures holding the edge information. */
+      for( i = 0; i < nv; i++ ) {
+         this->edges[ i ] = astFree( this->edges[ i ] );
+      }
+      this->edges = astFree( this->edges );
+      this->startsat = astFree( this->startsat );
+
+   }
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Polygon objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Polygon class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Polygon whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPolygon *this;             /* Pointer to the Polygon structure */
+   int ival;                     /* Integer attribute value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Polygon structure. */
+   this = (AstPolygon *) this_object;
+
+/* Write out values representing the instance variables for the
+   Polygon class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* SimpVertices. */
+/* ------------ */
+/* Write out the forward-inverse simplification flag. */
+   set = TestSimpVertices( this, status );
+   ival = set ? GetSimpVertices( this, status ) : astGetSimpVertices( this );
+   astWriteInt( channel, "SimpVT", set, 0, ival, "Simplify by transforming vertices?" );
+
+/* A flag indicating the convention used for determining the interior of
+   the polygon. A zero value indicates that the old AST system is in
+   use (inside to the left when moving anti-clockwise round the vertices
+   as viewed from the outside of the celestial sphere). A non-zero value
+   indicates the STC system is in use (inside to the left when moving
+   anti-clockwise round the vertices as viewed from the inside of the
+   celestial sphere). AST currently uses the STC system. */
+   astWriteInt( channel, "Order", 1, 0, 1, "Polygon uses STC vertex order convention" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAPolygon and astCheckPolygon functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Polygon,Region)
+astMAKE_CHECK(Polygon)
+
+AstPolygon *astPolygon_( void *frame_void, int npnt, int dim,
+                         const double *points, AstRegion *unc,
+                         const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astPolygon
+f     AST_POLYGON
+
+*  Purpose:
+*     Create a Polygon.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "polygon.h"
+c     AstPolygon *astPolygon( AstFrame *frame, int npnt, int dim,
+c                             const double *points, AstRegion *unc,
+c                             const char *options, ... )
+f     RESULT = AST_POLYGON( FRAME, NPNT, DIM, POINTS, UNC, OPTIONS, STATUS )
+
+*  Class Membership:
+*     Polygon constructor.
+
+*  Description:
+*     This function creates a new Polygon object and optionally initialises
+*     its attributes.
+*
+*     The Polygon class implements a polygonal area, defined by a
+*     collection of vertices, within a 2-dimensional Frame. The vertices
+*     are connected together by geodesic curves within the encapsulated Frame.
+*     For instance, if the encapsulated Frame is a simple Frame then the
+*     geodesics will be straight lines, but if the Frame is a SkyFrame then
+*     the geodesics will be great circles. Note, the vertices must be
+*     supplied in an order such that the inside of the polygon is to the
+*     left of the boundary as the vertices are traversed. Supplying them
+*     in the reverse order will effectively negate the polygon.
+*
+*     Within a SkyFrame, neighbouring vertices are always joined using the
+*     shortest path. Thus if an edge of 180 degrees or more in length is
+*     required, it should be split into section each of which is less
+*     than 180 degrees. The closed path joining all the vertices in order
+*     will divide the celestial sphere into two disjoint regions. The
+*     inside of the polygon is the region which is circled in an
+*     anti-clockwise manner (when viewed from the inside of the celestial
+*     sphere) when moving through the list of vertices in the order in
+*     which they were supplied when the Polygon was created (i.e. the
+*     inside is to the left of the boundary when moving through the
+*     vertices in the order supplied).
+
+*  Parameters:
+c     frame
+f     FRAME = INTEGER (Given)
+*        A pointer to the Frame in which the region is defined. It must
+*        have exactly 2 axes. A deep copy is taken of the supplied Frame.
+*        This means that any subsequent changes made to the Frame using the
+*        supplied pointer will have no effect the Region.
+c     npnt
+f     NPNT = INTEGER (Given)
+*        The number of points in the Region.
+c     dim
+f     DIM = INTEGER (Given)
+c        The number of elements along the second dimension of the "points"
+f        The number of elements along the first dimension of the POINTS
+*        array (which contains the point coordinates). This value is
+*        required so that the coordinate values can be correctly
+*        located if they do not entirely fill this array. The value
+c        given should not be less than "npnt".
+f        given should not be less than NPNT.
+c     points
+f     POINTS( DIM, 2 ) = DOUBLE PRECISION (Given)
+c        The address of the first element of a 2-dimensional array of
+c        shape "[2][dim]" giving the physical coordinates of the vertices.
+c        These should be stored such that the value of coordinate
+c        number "coord" for point number "pnt" is found in element
+c        "in[coord][pnt]".
+f        A 2-dimensional array giving the physical coordinates of the
+f        vertices. These should be stored such that the value of coordinate
+f        number COORD for point number PNT is found in element IN(PNT,COORD).
+c     unc
+f     UNC = INTEGER (Given)
+*        An optional pointer to an existing Region which specifies the
+*        uncertainties associated with the boundary of the Polygon being created.
+*        The uncertainty in any point on the boundary of the Polygon is found by
+*        shifting the supplied "uncertainty" Region so that it is centred at
+*        the boundary point being considered. The area covered by the
+*        shifted uncertainty Region then represents the uncertainty in the
+*        boundary position. The uncertainty is assumed to be the same for
+*        all points.
+*
+*        If supplied, the uncertainty Region must be of a class for which
+*        all instances are centro-symetric (e.g. Box, Circle, Ellipse, etc.)
+*        or be a Prism containing centro-symetric component Regions. A deep
+*        copy of the supplied Region will be taken, so subsequent changes to
+*        the uncertainty Region using the supplied pointer will have no
+*        effect on the created Polygon. Alternatively,
+f        a null Object pointer (AST__NULL)
+c        a NULL Object pointer
+*        may be supplied, in which case a default uncertainty is used
+*        equivalent to a box 1.0E-6 of the size of the Polygon being created.
+*
+*        The uncertainty Region has two uses: 1) when the
+c        astOverlap
+f        AST_OVERLAP
+*        function compares two Regions for equality the uncertainty
+*        Region is used to determine the tolerance on the comparison, and 2)
+*        when a Region is mapped into a different coordinate system and
+*        subsequently simplified (using
+c        astSimplify),
+f        AST_SIMPLIFY),
+*        the uncertainties are used to determine if the transformed boundary
+*        can be accurately represented by a specific shape of Region.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Polygon. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Polygon. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astPolygon()
+f     AST_POLYGON = INTEGER
+*        A pointer to the new Polygon.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstPolygon *new;            /* Pointer to new Polygon */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the supplied Frame structure. */
+   frame = astCheckFrame( frame_void );
+
+/* Initialise the Polygon, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPolygon( NULL, sizeof( AstPolygon ), !class_init,
+                         &class_vtab, "Polygon", frame, npnt,
+                         dim, points, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Polygon's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Polygon. */
+   return new;
+}
+
+AstPolygon *astPolygonId_( void *frame_void, int npnt, int dim,
+                           const double *points, void *unc_void,
+                           const char *options, ... ) {
+/*
+*  Name:
+*     astPolygonId_
+
+*  Purpose:
+*     Create a Polygon.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     AstPolygon *astPolygonId_( void *frame_void, int npnt,
+*                                int dim, const double *points, void *unc_void,
+*                                const char *options, ... )
+
+*  Class Membership:
+*     Polygon constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astPolygon constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astPolygon_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astPolygon_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astPolygon_.
+
+*  Returned Value:
+*     The ID value associated with the new Polygon.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrame *frame;              /* Pointer to Frame structure */
+   AstPolygon *new;              /* Pointer to new Polygon */
+   AstRegion *unc;               /* Pointer to Region structure */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Frame pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Frame. */
+   frame = astVerifyFrame( astMakePointer( frame_void ) );
+
+/* Obtain a Region pointer from the supplied "unc" ID and validate the
+   pointer to ensure it identifies a valid Region . */
+   unc = unc_void ? astCheckRegion( astMakePointer( unc_void ) ) : NULL;
+
+/* Initialise the Polygon, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPolygon( NULL, sizeof( AstPolygon ), !class_init,
+                         &class_vtab, "Polygon", frame, npnt, dim,
+                         points, unc );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Polygon's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new Polygon. */
+   return astMakeId( new );
+}
+
+
+AstPolygon *astInitPolygon_( void *mem, size_t size, int init, AstPolygonVtab *vtab,
+                             const char *name, AstFrame *frame, int npnt,
+                             int dim, const double *points, AstRegion *unc, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPolygon
+
+*  Purpose:
+*     Initialise a Polygon.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     AstPolygon *astInitPolygon( void *mem, size_t size, int init, AstPolygonVtab *vtab,
+*                                 const char *name, AstFrame *frame, int npnt,
+*                                 int dim, const double *points, AstRegion *unc )
+
+*  Class Membership:
+*     Polygon initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Polygon object. It allocates memory (if necessary) to accommodate
+*     the Polygon plus any additional data associated with the derived class.
+*     It then initialises a Polygon structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a Polygon at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Polygon is to be initialised.
+*        This must be of sufficient size to accommodate the Polygon data
+*        (sizeof(Polygon)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Polygon (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Polygon
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Polygon's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Polygon.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     frame
+*        A pointer to the Frame in which the region is defined.
+*     npnt
+*        The number of points in the Region.
+*     dim
+*        The number of elements along the second dimension of the "points"
+*        array (which contains the point coordinates). This value is
+*        required so that the coordinate values can be correctly
+*        located if they do not entirely fill this array. The value
+*        given should not be less than "npnt".
+*     points
+*        The address of the first element of a 2-dimensional array of
+*        shape "[2][dim]" giving the physical coordinates of the
+*        points. These should be stored such that the value of coordinate
+*        number "coord" for point number "pnt" is found in element
+*        "in[coord][pnt]".
+*     unc
+*        A pointer to a Region which specifies the uncertainty in the
+*        supplied positions (all points in the new Polygon being
+*        initialised are assumed to have the same uncertainty). A NULL
+*        pointer can be supplied, in which case default uncertainties equal
+*        to 1.0E-6 of the dimensions of the new Polygon's bounding box are
+*        used. If an uncertainty Region is supplied, it must be either a Box,
+*        a Circle or an Ellipse, and its encapsulated Frame must be related
+*        to the Frame supplied for parameter "frame" (i.e. astConvert
+*        should be able to find a Mapping between them). Two positions
+*        the "frame" Frame are considered to be co-incident if their
+*        uncertainty Regions overlap. The centre of the supplied
+*        uncertainty Region is immaterial since it will be re-centred on the
+*        point being tested before use. A deep copy is taken of the supplied
+*        Region.
+
+*  Returned Value:
+*     A pointer to the new Polygon.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstPolygon *new;        /* Pointer to new Polygon */
+   AstPointSet *pset;        /* Pointer to PointSet holding points */
+   const double *q;          /* Pointer to next supplied axis value */
+   double **ptr;             /* Pointer to data in pset */
+   double *p;                /* Pointer to next PointSet axis value */
+   int i;                    /* Axis index */
+   int j;                    /* Point index */
+   int nin;                  /* No. of axes */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitPolygonVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the number of axis values per position is correct. */
+   nin = astGetNaxes( frame );
+   if( nin != 2 ) {
+      astError( AST__BADIN, "astInitPolygon(%s): The supplied %s has %d "
+                "axes - polygons must have exactly 2 axes.", status, name,
+                astGetClass( frame ), nin );
+
+/* If so create a PointSet and store the supplied points in it. Check
+   none are bad. */
+   } else {
+      pset = astPointSet( npnt, 2, "", status );
+      ptr = astGetPoints( pset );
+      for( i = 0; i < 2 && astOK; i++ ) {
+         p = ptr[ i ];
+         q = points + i*dim;
+         for( j = 0; j < npnt; j++ ) {
+            if( (*(p++) = *(q++)) == AST__BAD ) {
+               astError( AST__BADIN, "astInitPolygon(%s): One or more "
+                         "bad axis values supplied for the vertex "
+                         "number %d.", status, name, j + 1 );
+               break;
+            }
+         }
+      }
+
+/* Initialise a Region structure (the parent class) as the first component
+   within the Polygon structure, allocating memory if necessary. */
+      new = (AstPolygon *) astInitRegion( mem, size, 0, (AstRegionVtab *) vtab,
+                                          name, frame, pset, unc );
+      if ( astOK ) {
+
+/* Initialise the Polygon data. */
+/* ------------------------------ */
+         new->lbnd[ 0 ] = AST__BAD;
+         new->ubnd[ 0 ] = AST__BAD;
+         new->lbnd[ 1 ] = AST__BAD;
+         new->ubnd[ 1 ] = AST__BAD;
+         new->simp_vertices = -INT_MAX;
+         new->edges = NULL;
+         new->startsat = NULL;
+         new->totlen = 0.0;
+         new->acw = 1;
+         new->stale = 1;
+
+/* Ensure the vertices are stored such that the unnegated Polygon
+   represents the inside of the polygon. */
+         EnsureInside( new, status );
+
+/* If an error occurred, clean up by deleting the new Polygon. */
+         if ( !astOK ) new = astDelete( new );
+      }
+
+/* Free resources. */
+      pset = astAnnul( pset );
+
+   }
+
+/* Return a pointer to the new Polygon. */
+   return new;
+}
+
+AstPolygon *astLoadPolygon_( void *mem, size_t size, AstPolygonVtab *vtab,
+                             const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadPolygon
+
+*  Purpose:
+*     Load a Polygon.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "polygon.h"
+*     AstPolygon *astLoadPolygon( void *mem, size_t size, AstPolygonVtab *vtab,
+*                                 const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     Polygon loader.
+
+*  Description:
+*     This function is provided to load a new Polygon using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Polygon structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Polygon at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Polygon is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Polygon data (sizeof(Polygon)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Polygon (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Polygon structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstPolygon) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Polygon. If this is NULL, a pointer
+*        to the (static) virtual function table for the Polygon class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Polygon" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Polygon.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPolygon *new;              /* Pointer to the new Polygon */
+   int order;                    /* Is the new (STC) order convention used? */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Polygon. In this case the
+   Polygon belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstPolygon );
+      vtab = &class_vtab;
+      name = "Polygon";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitPolygonVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Polygon. */
+   new = astLoadRegion( mem, size, (AstRegionVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Polygon" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+   new->simp_vertices = astReadInt( channel, "simpvt", -INT_MAX );
+   if ( TestSimpVertices( new, status ) ) SetSimpVertices( new, new->simp_vertices, status );
+
+/* A flag indicating what order the vertices are stored in. See the Dump
+   function. */
+      order = astReadInt( channel, "order", 0 );
+
+/* Initialise other class properties. */
+      new->lbnd[ 0 ] = AST__BAD;
+      new->ubnd[ 0 ] = AST__BAD;
+      new->lbnd[ 1 ] = AST__BAD;
+      new->ubnd[ 1 ] = AST__BAD;
+      new->edges = NULL;
+      new->startsat = NULL;
+      new->totlen = 0.0;
+      new->acw = 1;
+      new->stale = 1;
+
+/* If the order in which the vertices were written used the old AST
+   convention, negate the Polygon so that it is consistent with the
+   current conevtion (based on STC). */
+      if( ! order ) astNegate( new );
+
+/* Ensure the vertices are stored such that the unnegated Polygon
+   represents the inside of the polygon. */
+      EnsureInside( new, status );
+
+/* If an error occurred, clean up by deleting the new Polygon. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Polygon pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+AstPolygon *astDownsize_( AstPolygon *this, double maxerr, int maxvert,
+                          int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Polygon,Downsize))( this, maxerr, maxvert, status );
+}
+
+
diff --git a/ast/polygon.h b/ast/polygon.h
new file mode 100644
index 0000000..2cd46b6
--- /dev/null
+++ b/ast/polygon.h
@@ -0,0 +1,353 @@
+#if !defined( POLYGON_INCLUDED ) /* Include this file only once */
+#define POLYGON_INCLUDED
+/*
+*+
+*  Name:
+*     polygon.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Polygon class.
+
+*  Invocation:
+*     #include "polygon.h"
+
+*  Description:
+*     This include file defines the interface to the Polygon class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The Polygon class implements a Region which represents a collection
+*     of points in a Frame.
+
+*  Inheritance:
+*     The Polygon class inherits from the Region class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     26-OCT-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "frame.h"               /* Coordinate systems */
+#include "region.h"              /* Coordinate regions (parent class) */
+#include "timeframe.h"           /* For AST__LT definition */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Flags used to indicate how astOutline<X> should define the pixel
+   region to be outlined. We omit AST__LT here since it is defined in
+   timeframe.h (with value 11). */
+#define AST__LE 2
+#define AST__EQ 3
+#define AST__GE 4
+#define AST__GT 5
+#define AST__NE 6
+
+/* Type Definitions. */
+/* ================= */
+/* Polygon structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstPolygon {
+
+/* Attributes inherited from the parent class. */
+   AstRegion region;          /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double in[2];           /* A point which is inside the polygon */
+   double lbnd[2];         /* Lower axis limits of bounding box */
+   double ubnd[2];         /* Upper axis limits of bounding box */
+   AstLineDef **edges;     /* Cached description of edges */
+   double *startsat;       /* Perimeter distance to each vertex */
+   double totlen;          /* Total perimeter distance round polygon */
+   int acw;                /* Are vertices stored in anti-clockwise order? */
+   int stale;              /* Is cached information stale? */
+   int simp_vertices;      /* Simplify by transforming vertices? */
+} AstPolygon;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstPolygonVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstRegionVtab region_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstPolygon *(* Downsize)( AstPolygon *, double, int, int * );
+
+   int (* GetSimpVertices)( AstPolygon *, int * );
+   int (* TestSimpVertices)( AstPolygon *, int * );
+   void (* ClearSimpVertices)( AstPolygon *, int * );
+   void (* SetSimpVertices)( AstPolygon *, int, int * );
+
+} AstPolygonVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstPolygonGlobals {
+   AstPolygonVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 51 ];
+} AstPolygonGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitPolygonGlobals_( AstPolygonGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Polygon)          /* Check class membership */
+astPROTO_ISA(Polygon)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstPolygon *astPolygon_( void *, int, int, const double *, AstRegion *, const char *, int *, ...);
+#else
+AstPolygon *astPolygonId_( void *, int, int, const double *, AstRegion *, const char *, ... )__attribute__((format(printf,6,7)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstPolygon *astInitPolygon_( void *, size_t, int, AstPolygonVtab *, const char *, AstFrame *, int, int, const double *, AstRegion *, int * );
+
+/* Vtab initialiser. */
+void astInitPolygonVtab_( AstPolygonVtab *, const char *, int * );
+
+/* Loader. */
+AstPolygon *astLoadPolygon_( void *, size_t, AstPolygonVtab *,
+                             const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+AstPolygon *astDownsize_( AstPolygon *, double, int, int * );
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+AstPolygon *astOutlineLD_( long double, int, const long double[], const int[2], const int[2], double, int, const int[2], int, int * );
+#endif
+AstPolygon *astOutlineB_( signed char, int, const signed char[], const int[2], const int[2], double, int, const int[2], int, int * );
+AstPolygon *astOutlineD_( double, int, const double[], const int[2], const int[2], double, int, const int[2], int, int * );
+AstPolygon *astOutlineF_( float, int, const float[], const int[2], const int[2], double, int, const int[2], int, int * );
+AstPolygon *astOutlineI_( int, int, const int[], const int[2], const int[2], double, int, const int[2], int, int * );
+AstPolygon *astOutlineL_( long int, int, const long int[], const int[2], const int[2], double, int, const int[2], int, int * );
+AstPolygon *astOutlineS_( short int, int, const short int[], const int[2], const int[2], double, int, const int[2], int, int * );
+AstPolygon *astOutlineUB_( unsigned char, int, const unsigned char[], const int[2], const int[2], double, int, const int[2], int, int * );
+AstPolygon *astOutlineUI_( unsigned int, int, const unsigned int[], const int[2], const int[2], double, int, const int[2], int, int * );
+AstPolygon *astOutlineUL_( unsigned long int, int, const unsigned long int[], const int[2], const int[2], double, int, const int[2], int, int * );
+AstPolygon *astOutlineUS_( unsigned short int, int, const unsigned short int[], const int[2], const int[2], double, int, const int[2], int, int * );
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+AstPolygon *astConvexLD_( long double, int, const long double[], const int[2], const int[2], int, int * );
+#endif
+AstPolygon *astConvexB_( signed char, int, const signed char[], const int[2], const int[2], int, int * );
+AstPolygon *astConvexD_( double, int, const double[], const int[2], const int[2], int, int * );
+AstPolygon *astConvexF_( float, int, const float[], const int[2], const int[2], int, int * );
+AstPolygon *astConvexI_( int, int, const int[], const int[2], const int[2], int, int * );
+AstPolygon *astConvexL_( long int, int, const long int[], const int[2], const int[2], int, int * );
+AstPolygon *astConvexS_( short int, int, const short int[], const int[2], const int[2], int, int * );
+AstPolygon *astConvexUB_( unsigned char, int, const unsigned char[], const int[2], const int[2], int, int * );
+AstPolygon *astConvexUI_( unsigned int, int, const unsigned int[], const int[2], const int[2], int, int * );
+AstPolygon *astConvexUL_( unsigned long int, int, const unsigned long int[], const int[2], const int[2], int, int * );
+AstPolygon *astConvexUS_( unsigned short int, int, const unsigned short int[], const int[2], const int[2], int, int * );
+
+# if defined(astCLASS)           /* Protected */
+int astGetSimpVertices_( AstPolygon *, int * );
+int astTestSimpVertices_( AstPolygon *, int * );
+void astClearSimpVertices_( AstPolygon *, int * );
+void astSetSimpVertices_( AstPolygon *, int, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckPolygon(this) astINVOKE_CHECK(Polygon,this,0)
+#define astVerifyPolygon(this) astINVOKE_CHECK(Polygon,this,1)
+
+/* Test class membership. */
+#define astIsAPolygon(this) astINVOKE_ISA(Polygon,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astPolygon astINVOKE(F,astPolygon_)
+#else
+#define astPolygon astINVOKE(F,astPolygonId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitPolygon(mem,size,init,vtab,name,frame,npnt,indim,points,unc) \
+astINVOKE(O,astInitPolygon_(mem,size,init,vtab,name,frame,npnt,indim,points,unc,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitPolygonVtab(vtab,name) astINVOKE(V,astInitPolygonVtab_(vtab,name,STATUS_PTR))
+
+/* Loader. */
+#define astLoadPolygon(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadPolygon_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckPolygon to validate Polygon pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#define astDownsize(this,maxerr,maxvert) \
+astINVOKE(O,astDownsize_(astCheckPolygon(this),maxerr,maxvert,STATUS_PTR))
+
+
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+#define astOutlineLD(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineLD_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+#endif
+
+#define astOutlineB(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineB_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+#define astOutlineD(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineD_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+#define astOutlineF(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineF_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+#define astOutlineI(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineI_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+#define astOutlineL(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineL_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+#define astOutlineS(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineS_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+#define astOutlineUB(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineUB_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+#define astOutlineUI(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineUI_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+#define astOutlineUL(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineUL_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+#define astOutlineUS(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix) \
+astINVOKE(O,astOutlineUS_(value,oper,array,lbnd,ubnd,maxerr,maxvert,inside,starpix,STATUS_PTR))
+
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+#define astConvexLD(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O,astConvexLD_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+#endif
+
+#define  astConvexB(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O, astConvexB_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+#define  astConvexD(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O, astConvexD_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+#define  astConvexF(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O, astConvexF_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+#define  astConvexI(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O, astConvexI_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+#define  astConvexL(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O, astConvexL_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+#define  astConvexS(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O, astConvexS_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+#define  astConvexUB(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O, astConvexUB_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+#define  astConvexUI(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O, astConvexUI_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+#define  astConvexUL(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O, astConvexUL_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+#define  astConvexUS(value,oper,array,lbnd,ubnd,starpix) \
+astINVOKE(O, astConvexUS_(value,oper,array,lbnd,ubnd,starpix,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#define astClearSimpVertices(this) \
+astINVOKE(V,astClearSimpVertices_(astCheckPolygon(this),STATUS_PTR))
+#define astGetSimpVertices(this) \
+astINVOKE(V,astGetSimpVertices_(astCheckPolygon(this),STATUS_PTR))
+#define astSetSimpVertices(this,value) \
+astINVOKE(V,astSetSimpVertices_(astCheckPolygon(this),value,STATUS_PTR))
+#define astTestSimpVertices(this) \
+astINVOKE(V,astTestSimpVertices_(astCheckPolygon(this),STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/polymap.c b/ast/polymap.c
new file mode 100644
index 0000000..196004b
--- /dev/null
+++ b/ast/polymap.c
@@ -0,0 +1,5511 @@
+/*
+*class++
+*  Name:
+*     PolyMap
+
+*  Purpose:
+*     Map coordinates using polynomial functions.
+
+*  Constructor Function:
+c     astPolyMap
+f     AST_POLYMAP
+
+*  Description:
+*     A PolyMap is a form of Mapping which performs a general polynomial
+*     transformation.  Each output coordinate is a polynomial function of
+*     all the input coordinates. The coefficients are specified separately
+*     for each output coordinate. The forward and inverse transformations
+*     are defined independantly by separate sets of coefficients. If no
+*     inverse transformation is supplied, an iterative method can be used
+*     to evaluate the inverse based only on the forward transformation.
+
+*  Inheritance:
+*     The PolyMap class inherits from the Mapping class.
+
+*  Attributes:
+*     In addition to those attributes common to all Mappings, every
+*     PolyMap also has the following attributes:
+*
+*     - IterInverse: Provide an iterative inverse transformation?
+*     - NiterInverse: Maximum number of iterations for iterative inverse
+*     - TolInverse: Target relative error for iterative inverse
+
+*  Functions:
+c     In addition to those functions applicable to all Objects, the
+c     following functions may also be applied to all Mappings:
+f     In addition to those routines applicable to all Objects, the
+f     following routines may also be applied to all Mappings:
+*
+c     - astPolyTran: Fit a PolyMap inverse or forward transformation
+f     - AST_POLYTRAN: Fit a PolyMap inverse or forward transformation
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2011 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     27-SEP-2003 (DSB):
+*        Original version.
+*     13-APR-2005 (DSB):
+*        Changed the keys used by the Dump/astLoadPolyMap functions. They
+*        used to exceed 8 characters and consequently caused problems for
+*        FitsChans.
+*     20-MAY-2005 (DSB):
+*        Correct the indexing of keywords produced in the Dump function.
+*     20-APR-2006 (DSB):
+*        Guard against undefined transformations in Copy.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*     4-JUL-2008 (DSB):
+*        Fixed loop indexing problems in Equal function.
+*     27-MAY-2011 (DSB):
+*        Added public method astPolyTran.
+*     18-JUL-2011 (DSB):
+*        - Added attributes IterInverse, NiterInverse and TolInverse.
+*        - Do not report an error if astPolyTran fails to fit an inverse.
+*     15-OCT-2011 (DSB):
+*        Improve argument checking and error reporting in PolyTran
+*     8-MAY-2014 (DSB):
+*        Move to using CMinPack for minimisations.
+*     11-NOV-2016 (DSB):
+*        - Fix bug in MapMerge that could cause a seg fault. It did not
+*        check that the PolyMap had a defined transformation before accessing
+*        the transformation's coefficient array.
+*        - Fix similar bugs in Equal that could cause seg faults. 
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS PolyMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "cmpmap.h"              /* Compound mappings */
+#include "polymap.h"             /* Interface definition for this class */
+#include "unitmap.h"             /* Unit mappings */
+#include "cminpack/cminpack.h"   /* Levenberg - Marquardt minimization */
+#include "pal.h"                 /* SLALIB function definitions */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <float.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(PolyMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(PolyMap,Class_Init)
+#define class_vtab astGLOBAL(PolyMap,Class_Vtab)
+#define getattrib_buff astGLOBAL(LutMap,GetAttrib_Buff)
+
+#include <pthread.h>
+
+
+#else
+
+static char getattrib_buff[ 101 ];
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstPolyMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+
+/* Type Definitions */
+/* ================ */
+
+/* Structure used to pass data to the Levenberg - Marquardt non-linear
+   minimization algorithm. */
+typedef struct MinPackData {
+   int order;      /* Max power of X1 or X2, plus one. */
+   int nsamp;      /* No. of polynomial samples to fit */
+   int init_jac;   /* Has the constant Jacobian been found yet? */
+   double *xp1;    /* Pointer to powers of X1 (1st poly i/p) at all samples */
+   double *xp2;    /* Pointer to powers of X2 (2nd poly i/p) at all samples */
+   double *y[ 2 ]; /* Pointers to Y1 and Y2 values at all samples */
+} MinPackData;
+
+
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstPolyMap *astPolyMapId_( int, int, int, const double[], int, const double[], const char *, ... );
+
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstPolyMap **GetJacobian( AstPolyMap *, int * );
+static AstPolyMap *PolyTran( AstPolyMap *, int, double, double, int, const double *, const double *, int * );
+static double **SamplePoly1D( AstPolyMap *, int, double **, double, double, int, int *, double[2], int * );
+static double **SamplePoly2D( AstPolyMap *, int, double **, const double *, const double *, int, int *, double[4], int * );
+static double *FitPoly1D( int, double, int, double **, double[2], int *, double *, int * );
+static double *FitPoly2D( int, double, int, double **, double[4], int *, double *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetObjSize( AstObject *, int * );
+static int GetTranForward( AstMapping *, int * );
+static int GetTranInverse( AstMapping *, int * );
+static int MPFunc1D( void *, int, int, const double *, double *, double *, int, int );
+static int MPFunc2D( void *, int, int, const double *, double *, double *, int, int );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int ReplaceTransformation( AstPolyMap *, int, double, double, int, const double *, const double *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *obj, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void FreeArrays( AstPolyMap *, int, int * );
+static void IterInverse( AstPolyMap *, AstPointSet *, AstPointSet *, int * );
+static void LMFunc1D(  const double *, double *, int, int, void * );
+static void LMFunc2D(  const double *, double *, int, int, void * );
+static void LMJacob1D( const double *, double *, int, int, void * );
+static void LMJacob2D( const double *, double *, int, int, void * );
+static void StoreArrays( AstPolyMap *, int, int, const double *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+static int GetIterInverse( AstPolyMap *, int * );
+static int TestIterInverse( AstPolyMap *, int * );
+static void ClearIterInverse( AstPolyMap *, int * );
+static void SetIterInverse( AstPolyMap *, int, int * );
+
+static int GetNiterInverse( AstPolyMap *, int * );
+static int TestNiterInverse( AstPolyMap *, int * );
+static void ClearNiterInverse( AstPolyMap *, int * );
+static void SetNiterInverse( AstPolyMap *, int, int * );
+
+static double GetTolInverse( AstPolyMap *, int * );
+static int TestTolInverse( AstPolyMap *, int * );
+static void ClearTolInverse( AstPolyMap *, int * );
+static void SetTolInverse( AstPolyMap *, double, int * );
+
+
+/* Member functions. */
+/* ================= */
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a PolyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PolyMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     PolyMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the PolyMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPolyMap *this;             /* Pointer to the PolyMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PolyMap structure. */
+   this = (AstPolyMap *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* IterInverse. */
+/* ------------ */
+   if ( !strcmp( attrib, "iterinverse" ) ) {
+      astClearIterInverse( this );
+
+/* NiterInverse. */
+/* ------------- */
+   } else if ( !strcmp( attrib, "niterinverse" ) ) {
+      astClearNiterInverse( this );
+
+/* TolInverse. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "tolinverse" ) ) {
+      astClearTolInverse( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two PolyMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     PolyMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two PolyMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a PolyMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the PolyMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPolyMap *that;
+   AstPolyMap *this;
+   int i, j, k;
+   int nin;
+   int nout;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two PolyMap structures. */
+   this = (AstPolyMap *) this_object;
+   that = (AstPolyMap *) that_object;
+
+/* Check the second object is a PolyMap. We know the first is a
+   PolyMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAPolyMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two PolyMaps differ, it may still be possible
+   for them to be equivalent. First compare the PolyMaps if their Invert
+   flags are the same. In this case all the attributes of the two PolyMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+
+            result = 1;
+
+/* Check properties of the forward transformation. */
+            if( this->ncoeff_f && that->ncoeff_f ) {
+               for( i = 0; i < nout && result; i++ ) {
+                  if( this->ncoeff_f[ i ] != that->ncoeff_f[ i ] ){
+                     result = 0;
+                  }
+               }
+            } else if( this->ncoeff_f || that->ncoeff_f ) {
+               result = 0;
+            }
+
+            if( this->mxpow_f && that->mxpow_f ) {
+               for( i = 0; i < nout && result; i++ ) {
+                  if( this->mxpow_f[ i ] != that->mxpow_f[ i ] ){
+                     result = 0;
+                  }
+               }
+            } else if( this->mxpow_f || that->mxpow_f ) {
+               result = 0;
+            }
+
+            if( this->coeff_f && that->coeff_f ) {
+               for( i = 0; i < nout && result; i++ ) {
+                  for( j = 0; j < this->ncoeff_f[ i ] && result; j++ ) {
+                     if( !astEQUAL( this->coeff_f[ i ][ j ],
+                                    that->coeff_f[ i ][ j ] ) ) {
+                        result = 0;
+                     }
+                  }
+               }
+            } else if( this->coeff_f || that->coeff_f ) {
+               result = 0;
+            }
+
+            if( this->power_f && that->power_f ) {
+               for( i = 0; i < nout && result; i++ ) {
+                  for( j = 0; j < this->ncoeff_f[ i ] && result; j++ ) {
+                     for( k = 0; k < nin && result; k++ ) {
+                        if( this->power_f[ i ][ j ][ k ] !=
+                            that->power_f[ i ][ j ][ k ] ) {
+                           result = 0;
+                        }
+                     }
+                  }
+               }
+            } else if( this->power_f || that->power_f ) {
+               result = 0;
+            }
+
+/* Check properties of the inverse transformation. */
+            if( this->ncoeff_i && that->ncoeff_i ) {
+               for( i = 0; i < nout && result; i++ ) {
+                  if( this->ncoeff_i[ i ] != that->ncoeff_i[ i ] ){
+                     result = 0;
+                  }
+               }
+            } else if( this->ncoeff_i || that->ncoeff_i ) {
+               result = 0;
+            }
+
+            if( this->mxpow_i && that->mxpow_i ) {
+               for( i = 0; i < nout && result; i++ ) {
+                  if( this->mxpow_i[ i ] != that->mxpow_i[ i ] ){
+                     result = 0;
+                  }
+               }
+            } else if( this->mxpow_i || that->mxpow_i ) {
+               result = 0;
+            }
+
+            if( this->coeff_f && that->coeff_f ) {
+               for( i = 0; i < nout && result; i++ ) {
+                  for( j = 0; j < this->ncoeff_f[ i ] && result; j++ ) {
+                     if( !astEQUAL( this->coeff_f[ i ][ j ],
+                                    that->coeff_f[ i ][ j ] ) ) {
+                        result = 0;
+                     }
+                  }
+               }
+            } else if( this->coeff_f || that->coeff_f ) {
+               result = 0;
+            }
+
+            if( this->power_f && that->power_f ) {
+               for( i = 0; i < nout && result; i++ ) {
+                  for( j = 0; j < this->ncoeff_f[ i ] && result; j++ ) {
+                     for( k = 0; k < nin && result; k++ ) {
+                        if( this->power_f[ i ][ j ][ k ] !=
+                            that->power_f[ i ][ j ][ k ] ) {
+                           result = 0;
+                        }
+                     }
+                  }
+               }
+            } else if( this->power_f || that->power_f ) {
+               result = 0;
+            }
+
+/* If the Invert flags for the two PolyMaps differ, the attributes of the two
+   PolyMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a PolyMap, Invert flags must be equal. */
+            result = 0;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static double *FitPoly1D( int nsamp, double acc, int order, double **table,
+                          double scales[2], int *ncoeff, double *racc,
+                          int *status ){
+/*
+*  Name:
+*     FitPoly1D
+
+*  Purpose:
+*     Fit a (1-in,1-out) polynomial to a supplied set of data.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     double *FitPoly1D( int nsamp, double acc, int order, double **table,
+*                        double scales[2], int *ncoeff, double *racc,
+*                        int *status )
+
+*  Description:
+*     This function fits a least squares 1D polynomial curve to the
+*     positions in a supplied table. For the purposes of this function,
+*     the polynomial input is refered to as x1 and the output as y1. So
+*     the polynomial is:
+*
+*     y1 = P1( x1 )
+
+*  Parameters:
+*     nsamp
+*        The number of (x1,y1) positions in the supplied table.
+*     acc
+*        The required accuracy, expressed as an offset within the polynomials
+*        output space.
+*     order
+*        The maximum power (plus one) of x1 within P1. So for instance, a
+*        value of 3 refers to a quadratic polynomial.
+*     table
+*        Pointer to an array of 2 pointers. Each of these pointers points
+*        to an array of "nsamp" doubles, being the scaled and sampled values
+*        for x1 and y1 in that order.
+*     scales
+*        Array holding the scaling factors for the two columns of the table.
+*        Multiplying the table values by the scale factor produces PolyMap
+*        input or output axis values.
+*     ncoeff
+*        Pointer to an ant in which to return the number of coefficients
+*        described by the returned array.
+*     racc
+*        Pointer to a double in which to return the achieved accuracy
+*        (which may be greater than "acc").
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to an array of doubles defining the polynomial in the
+*     form required by the PolyMap contructor. The number of coefficients
+*     is returned via "ncoeff". If the polynomial could not be found,
+*     then NULL is returned. The returned pointer should be freed using
+*     astFree when no longer needed.
+
+*/
+
+/* Local Variables: */
+   MinPackData data;
+   double *coeffs;
+   double *pc;
+   double *pr;
+   double *px1;
+   double *pxp1;
+   double *result;
+   double *work1;
+   double *work2;
+   double *work4;
+   double f1;
+   double f2;
+   double maxterm;
+   double term;
+   double tv;
+   double x1;
+   int *work3;
+   int info;
+   int k;
+   int ncof;
+   int w1;
+
+/* Initialise returned value */
+   result = NULL;
+   *ncoeff = 0;
+   *racc = 10*acc;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Number of coefficients per poly. */
+   ncof = order;
+
+/* Initialise the elements of the structure. */
+   data.order = order;
+   data.nsamp = nsamp;
+   data.init_jac = 1;
+   data.xp1 = astMalloc( nsamp*order*sizeof( double ) );
+   data.xp2 = NULL;
+   data.y[ 0 ] = table[ 1 ];
+   data.y[ 1 ] = NULL;
+
+/* Work space to hold coefficients. */
+   coeffs = astMalloc( ncof*sizeof( double ) );
+
+/* Other work space. */
+   work1 = astMalloc( nsamp*sizeof( double ) );
+   work2 = astMalloc( ncof*nsamp*sizeof( double ) );
+   work3 = astMalloc( ncof*sizeof( int ) );
+   work4 = astMalloc( (5*ncof+nsamp)*sizeof( double ) );
+   if( astOK ) {
+
+/* Get pointers to the supplied x1 values. */
+      px1 = table[ 0 ];
+
+/* Get pointers to the location for the next power of x1. */
+      pxp1 = data.xp1;
+
+/* Loop round all samples. */
+      for( k = 0; k < nsamp; k++ ) {
+
+/* Get the current x1 value. */
+         x1 = *(px1++);
+
+/* Find all the required powers of x1 and store them in the "xp1"
+   component of the data structure. */
+         tv = 1.0;
+         for( w1 = 0; w1 < order; w1++ ) {
+            *(pxp1++) = tv;
+            tv *= x1;
+         }
+      }
+
+/* The initial guess at the coefficient values represents a unit
+   transformation in PolyMap axis values. */
+      for( k = 0; k < ncof; k++ ) coeffs[ k ] = 0.0;
+      coeffs[ 1 ] = scales[ 0 ]/scales[ 1 ];
+
+/* Find the best coefficients */
+      info = lmder1( MPFunc1D, &data, nsamp, ncof, coeffs, work1, work2, nsamp,
+                     sqrt(DBL_EPSILON), work3, work4, (5*ncof+nsamp) );
+      if( info == 0 ) astError( AST__MNPCK, "astPolyMap(PolyTran): Minpack error "
+                                "detected (possible programming error).", status );
+
+/* Return the achieved accuracy. */
+      pr = work1;
+      tv = 0.0;
+      for( k = 0; k < nsamp; k++,pr++ ) tv += (*pr)*(*pr);
+      *racc = scales[ 1 ]*sqrt( tv/nsamp );
+
+/* The best fitting polynomial coefficient found above relate to the
+   polynomial between the scaled positions stored in "table". These
+   scaled positions are related to PolyMap input/output axis values via
+   the scale factors supplied in "scales". Find the initial factor for the
+   current output. */
+      f1 = scales[ 1 ];
+      f2 = 1.0;
+
+/* Look at each coefficient. */
+      pc = coeffs;
+      for( w1 = 0; w1 < order; w1++,pc++ ) {
+
+/* Get a pointer to the powers of X1 appropriate for the current coefficient,
+    at the first sample. */
+         pxp1 = data.xp1 + w1;
+
+/* We find the contribution which this coefficient makes to the total
+   polynomial value. Find the maximum contribution made at any sample
+   points. */
+         maxterm = 0.0;
+         for( k = 0; k < nsamp; k++ ) {
+
+/* Get the absolute value of the polynomial term that uses the current
+   coefficient. */
+            term = fabs( ( *pc )*( *pxp1 ) );
+
+/* Update the maximum term found at any sample. */
+            if( term > maxterm ) maxterm = term;
+
+/* Increment the pointers to refer to the next sample. */
+            pxp1 += order;
+         }
+
+/* If the maximum contribution made by this term is less than the
+   required accuracy, set the coefficient value to zero. */
+         if( maxterm*f1 < acc ) {
+            *pc = 0.0;
+
+/* Scale the best fitting polynomial coefficient found above to take
+   account of the fact that the tabulated input and output positions in
+   "table" were are not actual PolyMap input and output axis values, but
+   are scaled by the factors stored in "scales". */
+         } else {
+            *pc *= f1/f2;
+         }
+
+         f2 *= scales[ 0 ];
+      }
+
+/* Convert the array of coefficients into PolyMap form. */
+      result = astMalloc( ncof*3*sizeof( double ) );
+
+      *ncoeff = 0;
+      pr = result;
+      pc = coeffs;
+      for( w1 = 0; w1 < order; w1++,pc++ ) {
+         if( *pc != 0.0 ) {
+            *(pr++) = *pc;
+            *(pr++) = 1;
+            *(pr++) = w1;
+            (*ncoeff)++;
+         }
+      }
+
+/* Truncate the returned array. */
+      result = astRealloc( result, (*ncoeff)*3*sizeof( double ) );
+   }
+
+/* Free resources. */
+   coeffs = astFree( coeffs );
+   data.xp1 = astFree( data.xp1 );
+   work1 = astFree( work1 );
+   work2 = astFree( work2 );
+   work3 = astFree( work3 );
+   work4 = astFree( work4 );
+
+/* Return the coefficient array. */
+   return result;
+
+}
+
+static double *FitPoly2D( int nsamp, double acc, int order, double **table,
+                          double scales[4], int *ncoeff, double *racc,
+                          int *status ){
+/*
+*  Name:
+*     FitPoly2D
+
+*  Purpose:
+*     Fit a (2-in,2-out) polynomial to a supplied set of data.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     double *FitPoly2D( int nsamp, double acc, int order, double **table,
+*                        double scales[4], int *ncoeff, double *racc,
+*                        int *status )
+
+*  Description:
+*     This function fits a pair of least squares 2D polynomial surfaces
+*     to the positions in a supplied table. For the purposes of this
+*     function, the polynomial inputs are refered to as (x1,x2) and the
+*     outputs as (y1,y2). So the two polynomials are:
+*
+*     y1 = P1( x1, x2 )
+*     y2 = P2( x1, x2 )
+*
+*     P1 and P2 have the same maximum power on each input (specified by
+*     the "order" parameter).
+
+*  Parameters:
+*     nsamp
+*        The number of (x1,x2,y1,y2) positions in the supplied table.
+*     acc
+*        The required accuracy, expressed as a geodesic distance within
+*        the polynomials output space.
+*     order
+*        The maximum power (plus one) of x1 or x2 within P1 and P2. So for
+*        instance, a value of 3 refers to a quadratic polynomial. Note, cross
+*        terms with total powers greater than or equal to "order" are not
+*        included in the fit. So the maximum number of terms in the fitted
+*        polynomial is order*(order+1)/2.
+*     table
+*        Pointer to an array of 4 pointers. Each of these pointers points
+*        to an array of "nsamp" doubles, being the scaled and sampled values
+*        for x1, x2, y1 or y2 in that order.
+*     scales
+*        Array holding the scaling factors for the four columns of the table.
+*        Multiplying the table values by the scale factor produces PolyMap
+*        input or output axis values.
+*     ncoeff
+*        Pointer to an ant in which to return the number of coefficients
+*        described by the returned array.
+*     racc
+*        Pointer to a double in which to return the achieved accuracy
+*        (which may be greater than "acc").
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to an array of doubles defining the polynomial in the
+*     form required by the PolyMap contructor. The number of coefficients
+*     is returned via "ncoeff". If the polynomial could not be found with
+*     sufficient accuracy , then NULL is returned. The returned pointer
+*     should be freed using astFree when no longer needed.
+
+*/
+
+/* Local Variables: */
+   MinPackData data;
+   double *coeffs;
+   double *pc;
+   double *pr;
+   double *px1;
+   double *px2;
+   double *pxp1;
+   double *pxp2;
+   double *result;
+   double *work1;
+   double *work2;
+   double *work4;
+   double f1;
+   double f20;
+   double f2;
+   double f3;
+   double facc;
+   double maxterm;
+   double term;
+   double tv;
+   double x1;
+   double x2;
+   int *work3;
+   int info;
+   int iout;
+   int k;
+   int ncof;
+   int w12;
+   int w1;
+   int w2;
+
+/* Initialise returned value */
+   result = NULL;
+   *ncoeff = 0;
+   *racc = 10*acc;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Number of coefficients per poly. */
+   ncof = order*( order + 1 )/2;
+
+/* Initialise the elements of the structure. */
+   data.order = order;
+   data.nsamp = nsamp;
+   data.init_jac = 1;
+   data.xp1 = astMalloc( nsamp*order*sizeof( double ) );
+   data.xp2 = astMalloc( nsamp*order*sizeof( double ) );
+   data.y[ 0 ] = table[ 2 ];
+   data.y[ 1 ] = table[ 3 ];
+
+/* Work space to hold coefficients. */
+   coeffs = astMalloc( 2*ncof*sizeof( double ) );
+
+/* Other work space. */
+   work1 = astMalloc( 2*nsamp*sizeof( double ) );
+   work2 = astMalloc( 4*ncof*nsamp*sizeof( double ) );
+   work3 = astMalloc( 2*ncof*sizeof( int ) );
+   work4 = astMalloc( 2*(5*ncof+nsamp)*sizeof( double ) );
+   if( astOK ) {
+
+/* Get pointers to the supplied x1 and x2 values. */
+      px1 = table[ 0 ];
+      px2 = table[ 1 ];
+
+/* Get pointers to the location for the next power of x1 and x2. */
+      pxp1 = data.xp1;
+      pxp2 = data.xp2;
+
+/* Loop round all samples. */
+      for( k = 0; k < nsamp; k++ ) {
+
+/* Get the current x1 and x2 values. */
+         x1 = *(px1++);
+         x2 = *(px2++);
+
+/* Find all the required powers of x1 and store them in the "xp1"
+   component of the data structure. */
+         tv = 1.0;
+         for( w1 = 0; w1 < order; w1++ ) {
+            *(pxp1++) = tv;
+            tv *= x1;
+         }
+
+/* Find all the required powers of x2 and store them in the "xp2"
+   comonent of the data structure. */
+         tv = 1.0;
+         for( w2 = 0; w2 < order; w2++ ) {
+            *(pxp2++) = tv;
+            tv *= x2;
+         }
+      }
+
+/* The initial guess at the coefficient values represents a unit
+   transformation in PolyMap axis values. */
+      for( k = 0; k < 2*ncof; k++ ) coeffs[ k ] = 0.0;
+      coeffs[ 1 ] = scales[ 0 ]/scales[ 2 ];
+      coeffs[ 5 ] = scales[ 1 ]/scales[ 3 ];
+
+/* Find the best coefficients */
+      info = lmder1( MPFunc2D, &data, 2*nsamp, 2*ncof, coeffs, work1, work2,
+                     2*nsamp, sqrt(DBL_EPSILON), work3, work4, 2*(5*ncof+nsamp) );
+      if( info == 0 ) astError( AST__MNPCK, "astPolyMap(PolyTran): Minpack error "
+                                "detected (possible programming error).", status );
+
+/* Return the achieved accuracy. */
+      pr = work1;
+      tv = 0.0;
+      for( k = 0; k < 2*nsamp; k++,pr++ ) tv += (*pr)*(*pr);
+      facc = 1.0/(scales[2]*scales[2]) + 1.0/(scales[3]*scales[3]);
+      *racc = sqrt( tv/(2*nsamp*facc) );
+
+/* Pointer to the first coefficient. */
+      pc = coeffs;
+
+/* Look at coefficients for each output in turn. */
+      for( iout = 0; iout < 2 && astOK; iout++ ) {
+
+/* The best fitting polynomial coefficient found above relate to the
+   polynomial between the scaled positions stored in "table". These
+   scaled positions are related to PolyMap input/output axis values via
+   the scale factors supplied in "scales". Find the initial factor for the
+   current output. */
+         f1 = scales[ 2 + iout ];
+
+/* Look at each coefficient for the current output. */
+         f20 = 1.0;
+         for( w12 = 0; w12 < order; w12++ ) {
+            f3 = 1.0;
+            f2 = f20;
+            for( w2 = 0; w2 <= w12; w2++,pc++ ) {
+               w1 = w12 - w2;
+
+/* Get pointers to the powers of X1 and X2 appropriate for the current
+   coefficient, at the first sample. */
+               pxp1 = data.xp1 + w1;
+               pxp2 = data.xp2 + w2;
+
+/* We find the contribution which this coefficient makes to the total
+   polynomial value. Find the maximum contribution made at any sample
+   points. */
+               maxterm = 0.0;
+               for( k = 0; k < nsamp; k++ ) {
+
+/* Get the absolute value of the polynomial term that uses the current
+   coefficient. */
+                  term = fabs( ( *pc )*( *pxp1 )*( *pxp2 ) );
+
+/* Update the maximum term found at any sample. */
+                  if( term > maxterm ) maxterm = term;
+
+/* Increment the pointers to refer to the next sample. */
+                  pxp1 += order;
+                  pxp2 += order;
+               }
+
+/* If the maximum contribution made by this term is less than the
+   required accuracy, set the coefficient value to zero. */
+               if( maxterm*f1 < acc ) {
+                  *pc = 0.0;
+
+/* Scale the best fitting polynomial coefficient found above to take
+   account of the fact that the tabulated input and output positions in
+   "table" were are not actual PolyMap input and output axis values, but
+   are scaled by the factors stored in "scales". */
+               } else {
+                  *pc *= f1/( f2*f3 );
+               }
+
+               f2 /= scales[ 0 ];
+               f3 *= scales[ 1 ];
+            }
+
+            f20 *= scales[ 0 ];
+         }
+      }
+
+/* Convert the array of coefficients into PolyMap form. */
+      result = astMalloc( 2*ncof*4*sizeof( double ) );
+
+      *ncoeff = 0;
+      pr = result;
+      pc = coeffs;
+      for( iout = 0; iout < 2 && astOK; iout++ ) {
+         for( w12 = 0; w12 < order; w12++ ) {
+            for( w2 = 0; w2 <= w12; w2++,pc++ ) {
+               w1 = w12 - w2;
+               if( *pc != 0.0 ) {
+                  *(pr++) = *pc;
+                  *(pr++) = iout + 1;
+                  *(pr++) = w1;
+                  *(pr++) = w2;
+                  (*ncoeff)++;
+               }
+            }
+         }
+      }
+
+/* Truncate the returned array. */
+      result = astRealloc( result, (*ncoeff)*4*sizeof( double ) );
+   }
+
+/* Free resources. */
+   coeffs = astFree( coeffs );
+   data.xp1 = astFree( data.xp1 );
+   data.xp2 = astFree( data.xp2 );
+   work1 = astFree( work1 );
+   work2 = astFree( work2 );
+   work3 = astFree( work3 );
+   work4 = astFree( work4 );
+
+/* Return the coefficient array. */
+   return result;
+
+}
+
+static void FreeArrays( AstPolyMap *this, int forward, int *status ) {
+/*
+*  Name:
+*     FreeArrays
+
+*  Purpose:
+*     Free the dynamic arrays contained within a PolyMap
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void FreeArrays( AstPolyMap *this, int forward, int *status )
+
+*  Description:
+*     This function frees all the dynamic arrays allocated as part of a
+*     PolyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the PolyMap.
+*     forward
+*        If non-zero, the arrays for the forward transformation are freed.
+*        Otherwise, the arrays for the inverse transformation are freed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   int nin;                     /* Number of inputs */
+   int nout;                    /* Number of outputs */
+   int i;                       /* Loop count */
+   int j;                       /* Loop count */
+
+/* Get the number of inputs and outputs of the uninverted Mapping. */
+   nin = ( (AstMapping *) this )->nin;
+   nout = ( (AstMapping *) this )->nout;
+
+/* Free the dynamic arrays for the forward transformation. */
+   if( forward ) {
+
+      if( this->coeff_f ) {
+         for( i = 0; i < nout; i++ ) {
+            this->coeff_f[ i ] = astFree( this->coeff_f[ i ] );
+         }
+         this->coeff_f = astFree( this->coeff_f );
+      }
+
+      if( this->power_f ) {
+         for( i = 0; i < nout; i++ ) {
+            if( this->ncoeff_f && this->power_f[ i ] ) {
+               for( j = 0; j < this->ncoeff_f[ i ]; j++ ) {
+                  this->power_f[ i ][ j ] = astFree( this->power_f[ i ][ j ] );
+               }
+            }
+            this->power_f[ i ] = astFree( this->power_f[ i ] );
+         }
+         this->power_f = astFree( this->power_f );
+      }
+
+      this->ncoeff_f = astFree( this->ncoeff_f );
+      this->mxpow_f = astFree( this->mxpow_f );
+
+/* Free the dynamic arrays for the inverse transformation. */
+   } else {
+
+      if( this->coeff_i ) {
+         for( i = 0; i < nin; i++ ) {
+            this->coeff_i[ i ] = astFree( this->coeff_i[ i ] );
+         }
+         this->coeff_i = astFree( this->coeff_i );
+      }
+
+      if(this->power_i ) {
+         for( i = 0; i < nin; i++ ) {
+            if( this->ncoeff_i && this->power_i[ i ] ) {
+               for( j = 0; j < this->ncoeff_i[ i ]; j++ ) {
+                  this->power_i[ i ][ j ] = astFree( this->power_i[ i ][ j ] );
+               }
+            }
+            this->power_i[ i ] = astFree( this->power_i[ i ] );
+         }
+         this->power_i = astFree( this->power_i );
+      }
+
+      this->ncoeff_i = astFree( this->ncoeff_i );
+      this->mxpow_i = astFree( this->mxpow_i );
+   }
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a PolyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PolyMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a PolyMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the PolyMap.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the PolyMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the PolyMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
+   AstPolyMap *this;            /* Pointer to the PolyMap structure */
+   const char *result;          /* Pointer value to return */
+   double dval;                 /* Floating point attribute value */
+   int ival;                    /* Integer attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the PolyMap structure. */
+   this = (AstPolyMap *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* IterInverse. */
+/* ------------ */
+   if ( !strcmp( attrib, "iterinverse" ) ) {
+      ival = astGetIterInverse( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* NiterInverse. */
+/* ------------- */
+   } else if ( !strcmp( attrib, "niterinverse" ) ) {
+      ival = astGetNiterInverse( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* TolInverse. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "tolinverse" ) ) {
+      dval = astGetTolInverse( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static AstPolyMap **GetJacobian( AstPolyMap *this, int *status ){
+/*
+*  Name:
+*     GetJacobian
+
+*  Purpose:
+*     Get a description of a Jacobian matrix for the fwd transformation
+*     of a PolyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     AstPolyMap **GetJacobian( AstPolyMap *this, int *status )
+
+*  Description:
+*     This function returns a set of PolyMaps which define the Jacobian
+*     matrix of the forward transformation of the supplied PolyMap.
+*
+*     The Jacobian matrix has "nout" rows and "nin" columns, where "nin"
+*     and "nout" are the number of inputs and outputs of the supplied PolyMap.
+*     Row "i", column "j" of the matrix holds the rate of change of the
+*     i'th PolyMap output with respect to the j'th PolyMap input.
+*
+*     Since the values in the Jacobian matrix vary across the input space
+*     of the PolyMap, the matrix is returned in the form of a set of new
+*     PolyMaps which generate the elements of the Jacobian for any given
+*     position in the input space. The "nout" values in a single column of
+*     the Jacobian matrix are generated by the "nout" outputs from a single
+*     new PolyMap. The whole matrix is described by "nin" PolyMaps.
+*
+*     The returned PolyMaps are cached in the supplied PolyMap object in
+*     order to speed up subsequent calls to this function.
+
+*  Parameters:
+*     this
+*        The PolyMap for which the Jacbian is required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to an array of "nin" PolyMap pointers, where "nin" is the number
+*     of inputs for the sipplied PolyMap. The returned array should not be changed
+*     in any way, and the PolyMaps should not be freed (they will be freed when
+*     the supplied PolyMap is deleted).
+
+*/
+
+/* Local Variables: */
+   double *coeffs;
+   double *pc;
+   int icof;
+   int icol;
+   int iin;
+   int irow;
+   int ncof;
+   int ncof_row;
+   int ncof_total;
+   int nin;
+   int nout;
+   int power;
+
+/* Check inherited status */
+   if( !astOK ) return NULL;
+
+/* Ensure there is a Jacobian stored in the PolyMap. */
+   if( !this->jacobian ) {
+
+/* Get the number of inputs and outputs. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+
+/* Allocate memory to hold pointers to the PolyMaps used to describe the
+   Jacobian matrix. */
+      this->jacobian = astCalloc( nin, sizeof(AstPolyMap *) );
+
+/* Find the total number of coefficients used to describe the supplied
+   PolyMap (forward transformation) and allocate work space to hold the
+   coefficients for a single new PolyMap forward transformation. */
+      ncof = 0;
+      for( irow = 0; irow <  nout; irow++ ) {
+         ncof += this->ncoeff_f[ irow ];
+      }
+      coeffs = astMalloc( ncof*( 2 + nin )*sizeof( double ) );
+
+/* Check pointers can be used safely. */
+      if( astOK ) {
+
+/* The Jacobian matrix has "nout" rows and "nin" columns. The "nout" values
+   in a single column of the Jacobian matrix corresponds to the "nout" outputs
+   from a single PolyMap. The whole matrix is described by "nin" PolyMaps.
+   Loop over each column of the Matrix, creating the corresponding PolyMap
+   for each. */
+         for( icol = 0; icol <  nin; icol++ ) {
+
+/* Initialise the total number of coefficients used to describe the
+   element of the PolyMap. */
+            ncof_total = 0;
+
+/* Loop over each row of the Jacobian matrix (i.e. each PolyMap output). */
+            pc = coeffs;
+            for( irow = 0; irow <  nout; irow++ ) {
+
+/* Loop over each coefficient used in the polynomial that generates the
+   current PolyMap output. */
+               ncof_row = this->ncoeff_f[ irow ];
+               for( icof = 0; icof <  ncof_row; icof++ ) {
+
+/* Get the power of input "icol" associated with the current coefficient. */
+                  power = (int)( this->power_f[ irow ][ icof ][ icol ] + 0.5 );
+
+/* We can skip the coefficient if the power is zero. */
+                  if( power > 0 ) {
+                     ncof_total++;
+
+/* Store the coefficient value, modified so that it describes a
+   polynomial that has been differentiated with respect to input "icol". */
+                     *(pc++) = this->coeff_f[ irow ][ icof ]*power;
+
+/* Store the output PolyMap to which the coeff relates. */
+                     *(pc++) = irow + 1;
+
+/* Store the powers of the inputs associated with the coeff. These are
+   the same as the original powers, except that the power of "icol"
+   (the input with respect to which the output has been differentiated)
+   is reduced by one. */
+                     for( iin = 0; iin <  nin; iin++ ) {
+                        if( iin != icol ) {
+                           *(pc++) = this->power_f[ irow ][ icof ][ iin ];
+                        } else {
+                           *(pc++) = this->power_f[ irow ][ icof ][ iin ] - 1;
+                        }
+                     }
+                  }
+               }
+            }
+
+/* Create the PolyMap and store a pointer to it in the jacobian array in
+   the supplied PolyMap. */
+            (this->jacobian)[ icol ] = astPolyMap( nin, nout, ncof_total, coeffs,
+                                                   0, NULL, " ", status );
+         }
+      }
+
+/* Free resources */
+      coeffs = astFree( coeffs );
+   }
+
+/* Return the Jacobian. */
+   return this->jacobian;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     PolyMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied PolyMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the PolyMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPolyMap *this;
+   int ic;
+   int nc;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the PolyMap structure. */
+   this = (AstPolyMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by this class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   if( this->jacobian ) {
+      nc = astGetNin( this );
+      for( ic = 0; ic < nc; ic++ ) {
+         result +=  astGetObjSize( (this->jacobian)[ ic ] );
+      }
+      result += sizeof( AstPolyMap * )*nc;
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetTranForward( AstMapping *this, int *status ) {
+/*
+*
+*  Name:
+*     GetTranForward
+
+*  Purpose:
+*     Determine if a PolyMap defines a forward coordinate transformation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int GetTranForward( AstMapping *this, int *status )
+
+*  Class Membership:
+*     PolyMap member function (over-rides the astGetTranForward method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function returns a value indicating if the PolyMap is able
+*     to perform a forward coordinate transformation.
+
+*  Parameters:
+*     this
+*        Pointer to the PolyMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the forward coordinate transformation is not defined, or 1 if it
+*     is.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPolyMap *map;            /* Pointer to PolyMap to be queried */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the PolyMap. */
+   map = (AstPolyMap *) this;
+
+/* Return the result. */
+   return map->ncoeff_f ? 1 : 0;
+}
+
+static int GetTranInverse( AstMapping *this, int *status ) {
+/*
+*
+*  Name:
+*     GetTranInverse
+
+*  Purpose:
+*     Determine if a PolyMap defines an inverse coordinate transformation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "matrixmap.h"
+*     int GetTranInverse( AstMapping *this, int *status )
+
+*  Class Membership:
+*     PolyMap member function (over-rides the astGetTranInverse method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function returns a value indicating if the PolyMap is able
+*     to perform an inverse coordinate transformation.
+
+*  Parameters:
+*     this
+*        Pointer to the PolyMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the inverse coordinate transformation is not defined, or 1 if it
+*     is.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPolyMap *map;            /* Pointer to PolyMap to be queried */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the PolyMap. */
+   map = (AstPolyMap *) this;
+
+/* Return the result. */
+   return ( map->ncoeff_i || astGetIterInverse( map ) ) ? 1 : 0;
+}
+
+void astInitPolyMapVtab_(  AstPolyMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPolyMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a PolyMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     void astInitPolyMapVtab( AstPolyMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     PolyMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the PolyMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAPolyMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->PolyTran = PolyTran;
+
+   vtab->ClearIterInverse = ClearIterInverse;
+   vtab->GetIterInverse = GetIterInverse;
+   vtab->SetIterInverse = SetIterInverse;
+   vtab->TestIterInverse = TestIterInverse;
+
+   vtab->ClearNiterInverse = ClearNiterInverse;
+   vtab->GetNiterInverse = GetNiterInverse;
+   vtab->SetNiterInverse = SetNiterInverse;
+   vtab->TestNiterInverse = TestNiterInverse;
+
+   vtab->ClearTolInverse = ClearTolInverse;
+   vtab->GetTolInverse = GetTolInverse;
+   vtab->SetTolInverse = SetTolInverse;
+   vtab->TestTolInverse = TestTolInverse;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+   mapping->GetTranForward = GetTranForward;
+   mapping->GetTranInverse = GetTranInverse;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the destructor and copy constructor. */
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+
+/* Declare the class dump function. */
+   astSetDump( vtab, Dump, "PolyMap", "Polynomial transformation" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void IterInverse( AstPolyMap *this, AstPointSet *out, AstPointSet *result,
+                         int *status ){
+/*
+*  Name:
+*     IterInverse
+
+*  Purpose:
+*     Use an iterative method to evaluate the inverse transformation of a
+*     PolyMap at a set of output positions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void IterInverse( AstPolyMap *this, AstPointSet *out, AstPointSet *result,
+*                       int *status )
+
+*  Description:
+*     This function transforms a set of PolyMap output positions using
+*     the inverse transformation of the PolyMap, to generate the corresponding
+*     input positions. An iterative Newton-Raphson method is used which
+*     only required the forward transformation of the PolyMap to be deifned.
+
+*  Parameters:
+*     this
+*        The PolyMap.
+*     out
+*        A PointSet holding the PolyMap output positions that are to be
+*        transformed using the inverse transformation.
+*     result
+*        A PointSet into which the generated PolyMap input positions are to be
+*        stored.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *work;
+   AstPointSet **ps_jac;
+   AstPolyMap **jacob;
+   double *vec;
+   double *pb;
+   double **ptr_work;
+   double ***ptr_jac;
+   double *mat;
+   double **ptr_out;
+   double **ptr_in;
+   double *pa;
+   double det;
+   double maxerr;
+   double vlensq;
+   double xlensq;
+   double xx;
+   int *flags;
+   int *iw;
+   int fwd;
+   int icol;
+   int icoord;
+   int ipoint;
+   int irow;
+   int iter;
+   int maxiter;
+   int nconv;
+   int ncoord;
+   int npoint;
+   int sing;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Check the PolyMap has equal numbers of inputs and outputs. */
+   ncoord = astGetNin( this );
+   if( ncoord != astGetNout( this ) ) {
+      astError( AST__INTER, "astTransform(%s): Supplied %s has unequal numbers"
+                " of inputs and outputs and therefore an iterative inverse "
+                "cannot be used (internal AST Programming errpr).", status,
+                astGetClass(this), astGetClass(this) );
+   }
+
+/* Get information about the Jacobian matrix for the forward polynomial
+   transformation. This matrix is a ncoord X ncoord matrix, in which
+   element (row=I,col=J) is the rate of change of output coord I with
+   respect to input coord J, of the supplied PolyMap's forward transformation.
+   The numerical values of the matrix vary depending on where it is
+   evaluated within the input space of the PolyMap. For this reason, the
+   "jacob" variable holds a vector of "ncoord" PolyMaps. The outputs of
+   each of these PolyMaps corresponds to a single column in the Jacobian
+   matrix. */
+   jacob = GetJacobian( this, status );
+
+/* Get the number of points to be transformed. */
+   npoint = astGetNpoint( out );
+
+/* Get another PointSet to hold intermediate results. */
+   work = astPointSet( npoint, ncoord, " ", status );
+
+/* See if the PolyMap has been inverted. */
+   fwd = !astGetInvert( this );
+
+/* Get pointers to the data arrays for all PointSets. Note, here "in" and
+   "out" refer to inputs and outputs of the PolyMap (i.e. the forward
+   transformation). These are respectively *outputs* and *inputs* of the
+   inverse transformation. */
+   ptr_in = astGetPoints( result );  /* Returned input positions */
+   ptr_out = astGetPoints( out );    /* Supplied output positions */
+   ptr_work = astGetPoints( work );  /* Work space */
+
+/* Allocate an array of PointSets to hold the elements of the Jacobian
+   matrix. */
+   ptr_jac = astMalloc( sizeof( double ** )*ncoord );
+   ps_jac = astCalloc( ncoord, sizeof( AstPointSet * ) );
+   if( astOK ) {
+      for( icoord = 0; icoord < ncoord; icoord++ ) {
+         ps_jac[ icoord ] = astPointSet( npoint, ncoord, " ", status );
+         ptr_jac[ icoord ] = astGetPoints( ps_jac[ icoord ] );
+      }
+   }
+
+/* Allocate an array to hold flags indicating if each position has
+   converged. Initialise it to hold zero at every element. */
+   flags = astCalloc( npoint, sizeof( int ) );
+
+/* Allocate memory to hold the Jacobian matrix at a single point. */
+   mat = astMalloc( sizeof( double )*ncoord*ncoord );
+
+/* Allocate memory to hold the offset vector. */
+   vec = astMalloc( sizeof( double )*ncoord );
+
+/* Allocate memory to hold work space for palDmat. */
+   iw = astMalloc( sizeof( int )*ncoord );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Store the initial guess at the required input positions. We assume initially
+   that the inverse transformation is a unit mapping, and so we just copy
+   the supplied outputs positions to the results PointSet holding the
+   corresponding input positions. */
+      for( icoord = 0; icoord < ncoord; icoord++ ) {
+         memcpy( ptr_in[ icoord ], ptr_out[ icoord ], sizeof( double )*npoint );
+      }
+
+/* Get the maximum number of iterations to perform. */
+      maxiter = astGetNiterInverse( this );
+
+/* Get the target relative error for the returned input axis values, and
+   square it. */
+      maxerr = astGetTolInverse( this );
+      maxerr *= maxerr;
+
+/* Initialise the number of positions which have reached the required
+   accuracy. */
+      nconv = 0;
+
+/* Loop round doing iterations of a Newton-Raphson algorithm, until
+   all points have achieved the required relative error, or the
+   maximum number of iterations have been performed. */
+      for( iter = 0; iter < maxiter && nconv < npoint && astOK; iter++ ) {
+
+/* Use the forward transformation of the supplied PolyMap to transform
+   the current guesses at the required input positions into the
+   corresponding output positions. Store the results in the "work"
+   PointSet. */
+         (void) astTransform( this, result, fwd, work );
+
+/* Modify the work PointSet so that it holds the offsets from the output
+   positions produced by the current input position guesses, and the
+   required output positions. */
+         for( icoord = 0; icoord < ncoord; icoord++ ) {
+            pa = ptr_out[ icoord ];
+            pb = ptr_work[ icoord ];
+            for( ipoint = 0; ipoint< npoint; ipoint++,pa++,pb++ ) {
+               if( *pa != AST__BAD && *pb != AST__BAD ){
+                  *pb = *pa - *pb;
+               } else {
+                  *pb = AST__BAD;
+               }
+            }
+         }
+
+/* Evaluate the elements of the Jacobian matrix at the current input
+   position guesses. */
+         for( icoord = 0; icoord < ncoord; icoord++ ) {
+            (void) astTransform( jacob[ icoord ], result, 1, ps_jac[ icoord ] );
+         }
+
+/* For each position, we now invert the matrix equation
+
+    Dy = Jacobian.Dx
+
+   to find a guess at the vector (dx) holding the offsets from the
+   current input positions guesses to their required values. Loop over all
+   points. */
+         for( ipoint = 0; ipoint < npoint; ipoint++ ) {
+
+/* Do not change positions that have already converged. */
+            if( !flags[ ipoint ] ) {
+
+/* Get the numerical values for the elements of the Jacobian matrix at
+   the current point. */
+               pa = mat;
+               for( irow = 0; irow < ncoord; irow++ ) {
+                  for( icol = 0; icol < ncoord; icol++ ) {
+                     *(pa++) = ptr_jac[ icol ][ irow ][ ipoint ];
+                  }
+
+/* Store the offset from the current output position to the required
+   output position. */
+                  vec[ irow ] = ptr_work[ irow ][ ipoint ];
+               }
+
+/* Find the corresponding offset from the current input position to the required
+   input position. */
+               palDmat( ncoord, mat, vec, &det, &sing, iw );
+
+/* If the matrix was singular, the input position cannot be evaluated so
+   store a bad value for it and indicate it has converged. */
+               if( sing ) {
+                  for( icoord = 0; icoord < ncoord; icoord++ ) {
+                     ptr_in[ icoord ][ ipoint ] = AST__BAD;
+                  }
+                  flags[ ipoint ] = 1;
+                  nconv++;
+
+/* Otherwise, update the input position guess. */
+               } else {
+                  vlensq = 0.0;
+                  xlensq = 0.0;
+                  pa = vec;
+                  for( icoord = 0; icoord < ncoord; icoord++,pa++ ) {
+                     xx = ptr_in[ icoord ][ ipoint ] + (*pa);
+                     ptr_in[ icoord ][ ipoint ] = xx;
+                     xlensq += xx*xx;
+                     vlensq += (*pa)*(*pa);
+                  }
+
+/* Check for convergence. */
+                  if( vlensq < maxerr*xlensq ) {
+                     flags[ ipoint ] = 1;
+                     nconv++;
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Free resources. */
+   vec = astFree( vec );
+   iw = astFree( iw );
+   mat = astFree( mat );
+   flags = astFree( flags );
+   work = astAnnul( work );
+
+   if( ps_jac ) {
+      for( icoord = 0; icoord < ncoord; icoord++ ) {
+         ps_jac[ icoord ] = astAnnul( ps_jac[ icoord ] );
+      }
+      ps_jac = astFree( ps_jac );
+   }
+
+   ptr_jac = astFree( ptr_jac );
+
+}
+
+static void LMFunc1D( const double *p, double *hx, int m, int n, void *adata ){
+/*
+*  Name:
+*     LMFunc1D
+
+*  Purpose:
+*     Evaluate a test 1D polynomal.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void LMFunc1D( const double *p, double *hx, int m, int n, void *adata )
+
+*  Description:
+*     This function finds the residuals implied by a supplied set of
+*     candidate polynomial coefficients. Each residual is a candidate
+*     polynomial evaluated at one of the sample points, minus the
+*     supplied target value for the polynomial at that test point.
+*
+*     The minimisation process minimises the sum of the squared residuals.
+
+*  Parameters:
+*     p
+*        An array of "m" coefficients for the candidate polynomial. The
+*        coefficients are ordered C0, C1, C2, etc.
+*     hx
+*        An array in which to return the "n" residuals. The residual at
+*        sample "k" is returned in element (k).
+*     m
+*        The length of the "p" array. This should be equal to order.
+*     n
+*        The length of the "hx" array. This should be equal to nsamp.
+*     adata
+*        Pointer to a structure holding the sample positions and values,
+*        and other information.
+
+*/
+
+/* Local Variables: */
+   MinPackData *data;
+   double *px1;
+   double *py;
+   const double *vp;
+   double *vr;
+   double res;
+   int k;
+   int w1;
+
+/* Get a pointer to the data structure. */
+   data = (MinPackData *) adata;
+
+/* Initialise a pointer to the current returned residual value. */
+   vr = hx;
+
+/* Initialise a pointer to the sampled Y values for the polynomial output. */
+   py = data->y[ 0 ];
+
+/* Initialise a pointer to the powers of the input X values at the curent
+   (i.e. first) sample. */
+   px1 = data->xp1;
+
+/* Loop over the index of the sample to which this residual refers. */
+   for( k = 0; k < data->nsamp; k++ ) {
+
+/* Initialise a pointer to the first coefficient (the  constant term) for the
+   polynomial output coordinate. */
+      vp = p;
+
+/* Initialise this residual to hold the sampled Y value. Increment the
+   pointer to the next sampled value for the current polynomial output. */
+      res = -( *(py++) );
+
+/* Loop over the coefficients. */
+      for( w1 = 0; w1 < data->order; w1++ ) {
+
+/* Increment the residual by the value of the current term Cw1*(x1^w1).
+   Increment the pointer to the next coefficient (C). Also increment the
+   pointer to the next higher power of X1. */
+         res += ( *(vp++) )*( *(px1++) );
+      }
+
+/* Store the complete residual in the returned array, and increment the
+   pointer to the next residual. */
+      *(vr++) = res;
+   }
+}
+
+static void LMFunc2D( const double *p, double *hx, int m, int n, void *adata ){
+/*
+*  Name:
+*     LMFunc2D
+
+*  Purpose:
+*     Evaluate a test 2D polynomal.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void LMFunc2D( const double *p, double *hx, int m, int n, void *adata )
+
+*  Description:
+*     This function finds the residuals implied by a supplied set of
+*     candidate polynomial coefficients. Each residual is a candidate
+*     polynomial (either P1 or P2) evaluated at one of the sample points
+*     (x1,x2), minus the supplied target value for the polynomial at that
+*     test point.
+*
+*     The minimisation process minimises the sum of the squared residuals.
+
+*  Parameters:
+*     p
+*        An array of "m" coefficients for the candidate polynomial. All the
+*        coefficients for polynomial P1 come first, followed by those for P2.
+*        Within each each polynomial, the coefficients are order C00, C10,
+*        C01, C20, C11, C02, C30, C21, C12, C03, etc. So the coefficient
+*        of (x1^j*x2^k) (=Cjk) for polynomial Pi is stored in element
+*        [k + (j + k)*(j + k + 1)/2 + i*order*(order+1)/2] of the "p" array.
+*     hx
+*        An array in which to return the "n" residuals. The residual at
+*        sample "k" for polynomial "i" is returned in element (k + nsamp*i).
+*     m
+*        The length of the "p" array. This should be equal to order*(order+1).
+*     n
+*        The length of the "hx" array. This should be equal to 2*nsamp.
+*     adata
+*        Pointer to a structure holding the sample positions and values,
+*        and other information.
+
+*/
+
+/* Local Variables: */
+   MinPackData *data;
+   const double *vp0;
+   const double *vp;
+   double *py;
+   double *px1;
+   double *px10;
+   double *px20;
+   double *vr;
+   double res;
+   double *px2;
+   int iout;
+   int k;
+   int w12;
+   int w2;
+
+/* Get a pointer to the data structure. */
+   data = (MinPackData *) adata;
+
+/* Initialise a pointer to the current returned residual value. */
+   vr = hx;
+
+/* Initilise a pointer to the first coefficient (the  constant term) for the
+   current (i.e. first) polynomial output  coordinate. */
+   vp0 = p;
+
+/* Loop over each polynomial output coordinate. */
+   for( iout = 0; iout < 2; iout++ ) {
+
+/* Initialise a pointer to the sampled Y values for the first polynomial
+   output. */
+      py = data->y[ iout ];
+
+/* Initialise pointers to the powers of the input X values at the curent
+   (i.e. first) sample. */
+      px10 = data->xp1;
+      px20 = data->xp2;
+
+/* Loop over the index of the sample to which this residual refers. */
+      for( k = 0; k < data->nsamp; k++ ) {
+
+/* Reset the pointer to the first coefficient (the  constant term)
+   for the current polynomial output  coordinate. */
+         vp = vp0;
+
+/* Initialise this residual to hold the sampled Y value. Increment the
+   pointer to the next sampled value for the current polynomial output. */
+         res = -( *(py++) );
+
+/* The w12 value is the sum of the powers of X1 and X2. So w12=0
+   corresponds to the constant term in the polynomial, and (e.g.) w12=6
+   corresponds to all the terms for which the sum of the powerss (w1+w2)
+   is 6. Loop over all possible w12 values. */
+         for( w12 = 0; w12 < data->order; w12++ ) {
+
+/* The next coeff refers to (x1^w12)*(x2^0). Get pointers to the values
+   holding x1^w12 and x2^0. */
+            px1 = px10++;
+            px2 = px20;
+
+/* Loop over powers of x2. The corresponding power of x1 is "w12-x2", but
+   is not explicitly needed here. So x1 moves down from w12 to zero, as
+   w2 moves up from zero to w12. */
+            for( w2 = 0; w2 <= w12; w2++ ) {
+
+/* Increment the residual by the value of the current term Cw1w2*(x1^w1)*(x2^w2).
+   Increment the pointer tio the next coefficient (C). Also decrement the
+   pointer to the next lower power of X1, and increment the pointer to the next
+   higher power of X2. */
+               res += ( *(vp++) )*( *(px1--) )*( *(px2++) );
+            }
+         }
+
+/* Move on to the x2 powers for the next sample. Don't need to do this
+   for x1 since px10 is incremented within the above loop. */
+         px20 += data->order;
+
+/* Store the complete residual in the returned array, and increment the
+   pointer to the next residual. */
+         *(vr++) = res;
+      }
+
+/* Get a pointer to the first coefficient (the  constant term) for the
+   next polynomial output  coordinate. */
+      vp0 += data->order*( 1 + data->order )/2;
+   }
+}
+
+static void LMJacob1D( const double *p, double *jac, int m, int n, void *adata ){
+/*
+*  Name:
+*     LMJacob1D
+
+*  Purpose:
+*     Evaluate the Jacobian matrix of a test 1D polynomal.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void LMJacob1D( const double *p, double *jac, int m, int n, void *adata )
+
+*  Description:
+*     This function finds the Jacobian matrix that describes the rate of
+*     change of every residual with respect to every polynomial coefficient.
+*     Each residual is a candidate polynomial evaluated at one of the sample
+*     points minus the supplied target value for the polynomial at that test
+*     point.
+*
+*     For a polynomial the Jacobian matrix is constant (i.e. does not
+*     depend on the values of the polynomial coefficients). So we only
+*     evaluate it on the first call.
+
+*  Parameters:
+*     p
+*        An array of "m" coefficients for the candidate polynomial.
+*     jac
+*        An array in which to return the "m*n" elements of the Jacobian
+*        matrix. The rate of change of residual "r" with respect to
+*        coefficient "c" is returned in element "r + c*n". The residual
+*        at sample "k" of polynomial Pi has an "r" index of (k + nsamp*i).
+*        The coefficient of (x1^j) for polynomial Pi has a "c" index
+*        of j.
+*     m
+*        The length of the "p" array. This should be equal to order.
+*     n
+*        The number of residuals. This should be equal to nsamp.
+*     adata
+*        Pointer to a structure holding the sample positions and values,
+*        and other information.
+
+*/
+
+/* Local Variables: */
+   MinPackData *data;
+   int k;
+   int w1;
+
+/* Get a pointer to the data structure. */
+   data = (MinPackData *) adata;
+
+/* The Jacobian of the residuals with respect to the polynomial
+   coefficients is constant (i.e. does not depend on the values of the
+   polynomial coefficients). So we only need to calculate it once. If
+   this is the first call, calculate the Jacobian and return it in "jac".
+   otherwise, just return immediately retaining the supplied "jac" values
+   (which will be the values returned by the previous call to this
+   function). */
+   if( data->init_jac ) {
+      data->init_jac = 0;
+
+/* Loop over all residuals. */
+      for( k = 0; k < n; k++ ) {
+
+/* Loop over all parameters (i.e. polynomial coefficients). */
+         for( w1 = 0; w1 < m; w1++ ) {
+
+/* Store the Jacobian. */
+            jac[ k + w1*n ] = (data->xp1)[ w1 + k*data->order ];
+         }
+      }
+   }
+}
+
+static void LMJacob2D( const double *p, double *jac, int m, int n, void *adata ){
+/*
+*  Name:
+*     LMJacob2D
+
+*  Purpose:
+*     Evaluate the Jacobian matrix of a test 2D polynomal.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void LMJacob2D( const double *p, double *jac, int m, int n, void *adata )
+
+*  Description:
+*     This function finds the Jacobian matrix that describes the rate of
+*     change of every residual with respect to every polynomial coefficient.
+*     Each residual is a candidate polynomial (either P1 or P2) evaluated
+*     at one of the sample points (x1,x2), minus the supplied target value
+*     for the polynomial at that test point.
+*
+*     For a polynomial the Jacobian matrix is constant (i.e. does not
+*     depend on the values of the polynomial coefficients). So we only
+*     evaluate it on the first call.
+
+*  Parameters:
+*     p
+*        An array of "m" coefficients for the candidate polynomial. All the
+*        coefficients for polynomial P1 come first, followed by those for P2.
+*        Within each each polynomial, the coefficients are order C00, C10,
+*        C01, C20, C11, C02, C30, C21, C12, C03, etc. So the coefficient
+*        of (x1^j*x2^k) (=Cjk) for polynomial Pi is stored in element
+*        [k + (j + k)*(j + k + 1)/2 + i*order*(order+1)/2] of the "p" array.
+*     jac
+*        An array in which to return the "m*n" elements of the Jacobian
+*        matrix. The rate of change of residual "r" with respect to
+*        coefficient "c" is returned in element "r + c*n". The residual
+*        at sample "k" of polynomial Pi has an "r" index of (k + nsamp*i).
+*        The coefficient of (x1^j*x2^k) for polynomial Pi has a "c" index
+*        of [k + (j + k)*(j + k + 1)/2 + i*order*(order+1)/2].
+*     m
+*        The length of the "p" array. This should be equal to order*(order+1).
+*     n
+*        The number of residuals. This should be equal to 2*nsamp.
+*     adata
+*        Pointer to a structure holdin gthe sample positions and values,
+*        and other information.
+
+*/
+
+/* Local Variables: */
+   MinPackData *data;
+   int iout;
+   int k;
+   int ncof;
+   int vp;
+   int vr;
+   int w1;
+   int w12;
+   int w2;
+
+/* Get a pointer to the data structure. */
+   data = (MinPackData *) adata;
+
+/* The Jacobian of the residuals with respect to the polynomial
+   coefficients is constant (i.e. does not depend on the values of the
+   polynomial coefficients). So we only need to calculate it once. If
+   this is the first call, calculate the Jacobian and return it in "jac".
+   otherwise, just return immediately retaining the supplied "jac" values
+   (which will be the values returned by the previous call to this
+   function). */
+   if( data->init_jac ) {
+      data->init_jac = 0;
+
+/* Store the number of coefficients in one polynomial. */
+      ncof = data->order*( 1 + data->order )/2;
+
+/* Loop over all residuals. */
+      for( vr = 0; vr < n; vr++ ) {
+
+/* Calculate the polynomial output index, and sample index, that creates
+   the current residual. */
+         iout = vr/data->nsamp;
+         k = vr - iout*data->nsamp;
+
+/* Loop over all parameters (i.e. polynomial coefficients). */
+         for( vp = 0; vp < m; vp++ ) {
+
+/* If this coefficient is not used in the creation of the current
+   polynomial output value, then the Jacobian value is zero. */
+            if( vp/ncof != iout ) {
+               jac[ vr + vp*n ] = 0.0;
+
+/* Otherwise, get the powers of the two polynomial inputs, to which
+   the current coefficient relates. */
+            } else {
+               w12 = ( -1.0 + sqrt( 1.0 + 8.0*(vp - iout*ncof) ) )/2.0;
+               w2 = vp - iout*ncof - w12*( w12 + 1 )/2;
+               w1 = w12 - w2;
+
+/* Store the Jacobian. */
+               jac[ vr + vp*n ] = (data->xp1)[ w1 + k*data->order ]*
+                                  (data->xp2)[ w2 + k*data->order ];
+            }
+         }
+      }
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     PolyMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstPolyMap *this;
+   int ic;
+   int result;
+   int nc;
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointer to the PolyMap structure. */
+   this = (AstPolyMap *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( this->jacobian ) {
+      nc = astGetNin( this );
+      for( ic = 0; ic < nc && result; ic++ ) {
+         result = astManageLock( (this->jacobian)[ ic ], mode,
+                                 extra, fail );
+      }
+   }
+
+   return result;
+
+}
+#endif
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a PolyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     PolyMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated PolyMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated PolyMap with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated PolyMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the PolyMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        PolyMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated PolyMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstPolyMap *pmap0;    /* Nominated PolyMap */
+   AstPolyMap *pmap1;    /* Neighbouring PolyMap */
+   int i;                /* Index of neighbour */
+   int nin;              /* Number of input coordinates for nominated PolyMap */
+   int nout;             /* Number of output coordinates for nominated PolyMap */
+   int ok;               /* Are PolyMaps equivalent? */
+   int oldinv0;          /* Original Invert value in pmap0 */
+   int oldinv1;          /* Original Invert value in pmap1 */
+   int result;           /* Result value to return */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Save a pointer to the nominated PolyMap. */
+   pmap0 = (AstPolyMap *) ( *map_list )[ where ];
+
+/* The only simplification which can currently be performed is to merge a PolyMap
+   with its own inverse. This can only be done in series. Obviously,
+   there are potentially other simplications which could be performed, but
+   time does not currently allow these to be coded. */
+   if( series ) {
+
+/* Temporarily set the Invert flag of the nominated PolyMap to the
+   required value, first saving the original value so that it can be
+   re-instated later. */
+      oldinv0 = astGetInvert( pmap0 );
+      astSetInvert( pmap0, ( *invert_list )[ where ] );
+
+/* Get the number of inputs and outputs to the nominated PolyMap. */
+      nin = astGetNin( pmap0 );
+      nout = astGetNout( pmap0 );
+
+/* Check each neighbour. */
+      for( i = where - 1; i <= where + 1; i += 2 ) {
+
+/* Continue with the next pass if the neighbour does not exist. */
+         if( i < 0 || i >= *nmap ) continue;
+
+/* Continue with the next pass if this neighbour is not a PermMap. */
+         if( strcmp( "PolyMap", astGetClass( ( *map_list )[ i ] ) ) ) continue;
+
+/* Get a pointer to it. */
+         pmap1 = (AstPolyMap *) ( *map_list )[ i ];
+
+/* Check it is used in the opposite direction to the nominated PolyMap. */
+         if( ( *invert_list )[ i ] == ( *invert_list )[ where ] ) continue;
+
+/* We use the astEqual method to check that the two PolyMaps are equal.
+   But at the moment they may not be equal because they may have
+   different Invert flags. Therefore, temporarily set the invert flag
+   of the neighbour so that it is the same as the nominated PolyMap,
+   first saving the original value so that it can be re-instated later.
+   Note, we have already checked that the two PolyMaps are used in opposite
+   directions within the CmpMap. */
+         oldinv1 = astGetInvert( pmap1 );
+         astSetInvert( pmap1, ( *invert_list )[ where ] );
+
+/* Use astEqual to check that the coefficients etc are equal in the two
+   PolyMaps. */
+         ok = astEqual( pmap0, pmap1 );
+
+/* Re-instate the original value of the Invert flag in the neighbour. */
+         astSetInvert( pmap1, oldinv1 );
+
+/* Pass on to the next neighbour if the current neighbour differs from
+   the nominated PolyMap. */
+         if( !ok ) continue;
+
+/* If we get this far, then the nominated PolyMap and the current
+   neighbour cancel each other out, so replace each by a UnitMap. */
+         pmap0 = astAnnul( pmap0 );
+         pmap1 = astAnnul( pmap1 );
+         if( i < where ) {
+            ( *map_list )[ where ] = (AstMapping *) astUnitMap( nout, "", status );
+            ( *map_list )[ i ] = (AstMapping *) astUnitMap( nout, "", status );
+            ( *invert_list )[ where ] = 0;
+            ( *invert_list )[ i ] = 0;
+            result = i;
+         } else {
+            ( *map_list )[ where ] = (AstMapping *) astUnitMap( nin, "", status );
+            ( *map_list )[ i ] = (AstMapping *) astUnitMap( nin, "", status );
+            ( *invert_list )[ where ] = 0;
+            ( *invert_list )[ i ] = 0;
+            result = where;
+         }
+
+/* Leave the loop. */
+         break;
+      }
+
+/* If the nominated PolyMap was not replaced by a UnitMap, then
+   re-instate its original value for the Invert flag. */
+      if( pmap0 ) astSetInvert( pmap0, oldinv0 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int MPFunc1D( void *p, int m, int n, const double *x, double *fvec,
+                     double *fjac, int ldfjac, int iflag ) {
+/*
+*  Name:
+*     MPFunc1D
+
+*  Purpose:
+*     Evaluate a test 1D polynomal or its Jacobian.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int MPFunc1D( void *p, int m, int n, const double *x, double *fvec,
+*                   double *fjac, int ldfjac, int iflag )
+
+*  Description:
+*     This function finds the residuals or Jacobian implied by a supplied
+*     set of candidate polynomial coefficients. Each residual is a candidate
+*     polynomial evaluated at one of the sample points, minus the
+*     supplied target value for the polynomial at that test point.
+*
+*     The minimisation process minimises the sum of the squared residuals.
+
+*  Parameters:
+*     p
+*        A pointer to data needed to evaluate the required residuals or
+*        Jacobians.
+*     m
+*        The number of residuals.
+*     n
+*        The number of coefficients.
+*     x
+*        An array of "n" coefficients for the candidate polynomial.
+*     fvec
+*        An array in which to return the "m" residuals.
+*     fjac
+*        An array in which to return the "mxn" Jacobian values.
+*     ldflac
+*        Unused (should always be equal to "m").
+*     iflag
+*        If 1 calculate the residuals, otherwise calculate the Jacobians.
+
+*/
+
+/* If required, calculate the function values at X, and return this
+   vector in fvec. Do not alter fjac. */
+   if( iflag == 1 ) {
+      LMFunc1D( x, fvec, n, m, p );
+
+/* Otherwise, calculate the Jacobian values at X, and return this
+   matrix in fjac. Do not alter fvec. */
+   } else {
+      LMJacob1D( x, fjac, n, m, p );
+   }
+
+   return 0;
+}
+
+static int MPFunc2D( void *p, int m, int n, const double *x, double *fvec,
+                     double *fjac, int ldfjac, int iflag ) {
+/*
+*  Name:
+*     MPFunc1D
+
+*  Purpose:
+*     Evaluate a test 2D polynomal or its Jacobian.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int MPFunc2D( void *p, int m, int n, const double *x, double *fvec,
+*                   double *fjac, int ldfjac, int iflag )
+
+*  Description:
+*     This function finds the residuals or Jacobian implied by a supplied
+*     set of candidate polynomial coefficients. Each residual is a candidate
+*     polynomial evaluated at one of the sample points, minus the
+*     supplied target value for the polynomial at that test point.
+*
+*     The minimisation process minimises the sum of the squared residuals.
+
+*  Parameters:
+*     p
+*        A pointer to data needed to evaluate the required residuals or
+*        Jacobians.
+*     m
+*        The number of residuals.
+*     n
+*        The number of coefficients.
+*     x
+*        An array of "n" coefficients for the candidate polynomial.
+*     fvec
+*        An array in which to return the "m" residuals.
+*     fjac
+*        An array in which to return the "mxn" Jacobian values.
+*     ldflac
+*        Unused (should always be equal to "m").
+*     iflag
+*        If 1 calculate the residuals, otherwise calculate the Jacobians.
+
+*/
+
+/* If required, calculate the function values at X, and return this
+   vector in fvec. Do not alter fjac. */
+   if( iflag == 1 ) {
+      LMFunc2D( x, fvec, n, m, p );
+
+/* Otherwise, calculate the Jacobian values at X, and return this
+   matrix in fjac. Do not alter fvec. */
+   } else {
+      LMJacob2D( x, fjac, n, m, p );
+
+   }
+
+   return 0;
+}
+
+static AstPolyMap *PolyTran( AstPolyMap *this, int forward, double acc,
+                             double maxacc, int maxorder, const double *lbnd,
+                             const double *ubnd, int *status ){
+/*
+*++
+*  Name:
+c     astPolyTran
+f     AST_POLYTRAN
+
+*  Purpose:
+*     Fit a PolyMap inverse or forward transformation.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "polymap.h"
+c     AstPolyMap *astPolyTran( AstPolyMap *this, int forward, double acc,
+c                              double maxacc, int maxorder, const double *lbnd,
+c                              const double *ubnd )
+f     RESULT = AST_POLYTRAN( THIS, FORWARD, ACC, MAXACC, MAXORDER, LBND,
+f                            UBND, STATUS )
+
+*  Class Membership:
+*     PolyMap method.
+
+*  Description:
+*     This function creates a new PolyMap which is a copy of the supplied
+*     PolyMap, in which a specified transformation (forward or inverse)
+*     has been replaced by a new polynomial transformation. The
+*     coefficients of the new transformation are estimated by sampling
+*     the other transformation and performing a least squares polynomial
+*     fit in the opposite direction to the sampled positions and values.
+*
+*     This method can only be used on (1-input,1-output) or (2-input,2-output)
+*     PolyMaps.
+*
+*     The transformation to create is specified by the
+c     "forward" parameter.
+f     FORWARD parameter.
+*     In what follows "X" refers to the inputs of the PolyMap, and "Y" to
+*     the outputs of the PolyMap. The forward transformation transforms
+*     input values (X) into output values (Y), and the inverse transformation
+*     transforms output values (Y) into input values (X). Within a PolyMap,
+*     each transformation is represented by an independent set of
+*     polynomials, P_f or P_i: Y=P_f(X) for the forward transformation and
+*     X=P_i(Y) for the inverse transformation.
+*
+c     The "forward"
+f     The FORWARD
+*     parameter specifies the transformation to be replaced. If it is
+c     non-zero,
+f     is .TRUE.,
+*     a new forward transformation is created
+*     by first finding the input values (X) using the inverse transformation
+*     (which must be available) at a regular grid of points (Y) covering a
+*     rectangular region of the PolyMap's output space. The coefficients of
+*     the required forward polynomial, Y=P_f(X), are chosen in order to
+*     minimise the sum of the squared residuals between the sampled values
+*     of Y and P_f(X).
+*
+c     If "forward" is zero (probably the most likely case),
+f     If FORWARD is .FALSE. (probably the most likely case),
+*     a new inverse transformation is created by
+*     first finding the output values (Y) using the forward transformation
+*     (which must be available) at a regular grid of points (X) covering a
+*     rectangular region of the PolyMap's input space. The coefficients of
+*     the required inverse polynomial, X=P_i(Y), are chosen in order to
+*     minimise the sum of the squared residuals between the sampled values
+*     of X and P_i(Y).
+*
+*     This fitting process is performed repeatedly with increasing
+*     polynomial orders (starting with linear) until the target
+*     accuracy is achieved, or a specified maximum order is reached. If
+*     the target accuracy cannot be achieved even with this maximum-order
+*     polynomial, the best fitting maximum-order polynomial is returned so
+*     long as its accuracy is better than
+c     "maxacc".
+f     MAXACC.
+*     If it is not, an error is reported.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the original Mapping.
+c     forward
+f     FORWARD = LOGICAL (Given)
+c        If non-zero,
+f        If .TRUE.,
+*        the forward PolyMap transformation is replaced. Otherwise the
+*        inverse transformation is replaced.
+c     acc
+f     ACC = DOUBLE (Given)
+*        The target accuracy, expressed as a geodesic distance within
+*        the PolyMap's input space (if
+c        "forward" is zero)  or output space (if "forward" is non-zero).
+f        FORWARD is .FALSE.) or output space (if FORWARD is .TRUE.).
+c     maxacc
+f     MAXACC = DOUBLE (Given)
+*        The maximum allowed accuracy for an acceptable polynomial,
+*        expressed as a geodesic distance within the PolyMap's input
+*        space (if
+c        "forward" is zero)  or output space (if "forward" is non-zero).
+f        FORWARD is .FALSE.) or output space (if FORWARD is .TRUE.).
+c     maxorder
+f     MAXORDER = INTEGER (Given)
+*        The maximum allowed polynomial order. This is one more than the
+*        maximum power of either input axis. So for instance, a value of
+*        3 refers to a quadratic polynomial. Note, cross terms with total
+*        powers greater than or equal to
+c        maxorder
+f        MAXORDER
+*        are not inlcuded in the fit. So the maximum number of terms in
+*        each of the fitted polynomials is
+c        maxorder*(maxorder+1)/2.
+f        MAXORDER*(MAXORDER+1)/2.
+c     lbnd
+f     LBND( * ) = DOUBLE PRECISION (Given)
+c        Pointer to an
+f        An
+*        array holding the lower bounds of a rectangular region within
+*        the PolyMap's input space (if
+c        "forward" is zero)  or output space (if "forward" is non-zero).
+f        FORWARD is .FALSE.) or output space (if FORWARD is .TRUE.).
+*        The new polynomial will be evaluated over this rectangle. The
+*        length of this array should equal the value of the PolyMap's Nin
+*        or Nout attribute, depending on
+c        "forward".
+f        FORWARD.
+c     ubnd
+f     UBND( * ) = DOUBLE PRECISION (Given)
+c        Pointer to an
+f        An
+*        array holding the upper bounds of a rectangular region within
+*        the PolyMap's input space (if
+c        "forward" is zero)  or output space (if "forward" is non-zero).
+f        FORWARD is .FALSE.) or output space (if FORWARD is .TRUE.).
+*        The new polynomial will be evaluated over this rectangle.  The
+*        length of this array should equal the value of the PolyMap's Nin
+*        or Nout attribute, depending on
+c        "forward".
+f        FORWARD.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astPolyTran()
+f     AST_POLYTRAN = INTEGER
+*        A pointer to the new PolyMap.
+c        A NULL pointer
+f        AST__NULL
+*        will be returned if the fit fails to achieve the accuracy
+*        specified by
+c        "maxacc",
+f        MAXACC,
+*        but no error will be reported.
+
+*  Notes:
+*     - This function can only be used on 1D or 2D PolyMaps which have
+*     the same number of inputs and outputs.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstPolyMap *result;
+   int ok;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the inherited status. */
+   if ( !astOK ) return result;
+
+/* Take a copy of the supplied PolyMap. */
+   result = astCopy( this );
+
+/* Replace the required transformation. */
+   ok = ReplaceTransformation( result, forward, acc, maxacc, maxorder, lbnd,
+                               ubnd, status );
+
+/* If an error occurred, or the fit was not good enough, annul the returned
+   PolyMap. */
+   if ( !ok || !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int ReplaceTransformation( AstPolyMap *this, int forward, double acc,
+                                  double maxacc, int maxorder, const double *lbnd,
+                                  const double *ubnd, int *status ){
+/*
+*  Name:
+*     ReplaceTransformation
+
+*  Purpose:
+*     Create a new inverse or forward transformation for a PolyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     int ReplaceTransformation( AstPolyMap *this, int forward, double acc,
+*                                double maxacc, int maxorder, const double *lbnd,
+*                                const double *ubnd, int *status )
+
+*  Description:
+*     This function creates a new forward or inverse transformation for
+*     the supplied PolyMap (replacing any existing transformation), by
+*     sampling the other transformation and performing a least squares
+*     polynomial fit to the sample positions and values.
+*
+*     The transformation to create is specified by the "forward" parameter.
+*     In what follows "X" refers to the inputs of the PolyMap, and "Y" to
+*     the outputs of the PolyMap. The forward transformation transforms
+*     input values (X) into output values (Y), and the inverse transformation
+*     transforms output values (Y) into input values (X). Within a PolyMap,
+*     each transformation is represented by an independent set of
+*     polynomials: Y=P_f(X) for the forward transformation and X=P_i(Y)
+*     for the inverse transformation.
+*
+*     If "forward" is zero then a new inverse transformation is created by
+*     first finding the output values (Y) using the forward transformation
+*     (which must be available) at a regular grid of points (X) covering a
+*     rectangular region of the PolyMap's input space. The coefficients of
+*     the required inverse polynomial, X=P_i(Y), are chosen in order to
+*     minimise the sum of the squared residuals between the sampled values
+*     of X and P_i(Y).
+*
+*     If "forward" is non-zero then a new forward transformation is created
+*     by first finding the input values (X) using the inverse transformation
+*     (which must be available) at a regular grid of points (Y) covering a
+*     rectangular region of the PolyMap's output space. The coefficients of
+*     the required forward polynomial, Y=P_f(X), are chosen in order to
+*     minimise the sum of the squared residuals between the sampled values
+*     of Y and P_f(X).
+*
+*     This fitting process is performed repeatedly with increasing
+*     polynomial orders (starting with linear) until the target
+*     accuracy is achieved, or a specified maximum order is reached. If
+*     the target accuracy cannot be achieved even with this maximum-order
+*     polynomial, the best fitting maximum-order polynomial is returned so
+*     long as its accuracy is better than "maxacc".
+
+*  Parameters:
+*     this
+*        The PolyMap.
+*     forward
+*        If non-zero, then the forward PolyMap transformation is
+*        replaced. Otherwise the inverse transformation is replaced.
+*     acc
+*        The target accuracy, expressed as a geodesic distance within
+*        the PolyMap's input space (if "forward" is zero) or output
+*        space (if "forward" is non-zero).
+*     maxacc
+*        The maximum allowed accuracy for an acceptable polynomial,
+*        expressed as a geodesic distance within the PolyMap's input
+*        space (if "forward" is zero) or output space (if "forward" is
+*        non-zero).
+*     maxorder
+*        The maximum allowed polynomial order. This is one more than the
+*        maximum power of either input axis. So for instance, a value of
+*        3 refers to a quadratic polynomial. Note, cross terms with total
+*        powers greater than or equal to maxorder are not inlcuded in the
+*        fit. So the maximum number of terms in each of the fitted polynomials
+*        is maxorder*(maxorder+1)/2.
+*     lbnd
+*        An array holding the lower bounds of a rectangular region within
+*        the PolyMap's input space (if "forward" is zero) or output
+*        space (if "forward" is non-zero). The new polynomial will be
+*        evaluated over this rectangle.
+*     ubnd
+*        An array holding the upper bounds of a rectangular region within
+*        the PolyMap's input space (if "forward" is zero) or output
+*        space (if "forward" is non-zero). The new polynomial will be
+*        evaluated over this rectangle.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Non-zero if a fit was performed succesfully, to at least the
+*     maximum allowed accuracy. Zero if the fit failed, or an error
+*     occurred.
+
+*  Notes:
+*     - No error is reported if the fit fails to achieve the required
+*     maximum accuracy.
+*     - An error is reported if the transformation that is not being
+*     replaced is not defined.
+*     - An error is reported if the PolyMap does not have equal numbers
+*     of inputs and outputs.
+*     - An error is reported if the PolyMap has more than 2 inputs or outputs.
+
+*/
+
+/* Local Variables: */
+   double **table;
+   double *cofs;
+   double racc;
+   double scales[ 4 ];
+   int ndim;
+   int ncof;
+   int nsamp;
+   int order;
+   int result;
+
+/* Check inherited status */
+   if( !astOK ) return 0;
+
+/* Check the PolyMap can be used. */
+   ndim = astGetNin( this );
+   if( astGetNout( this ) != ndim && astOK ) {
+      astError( AST__BADNI, "astPolyTran(%s): Supplied %s has "
+                "different number of inputs (%d) and outputs (%d).",
+                status, astGetClass( this ), astGetClass( this ), ndim,
+                astGetNout( this ) );
+
+   } else if( ndim > 2 && astOK ) {
+      astError( AST__BADNI, "astPolyTran(%s): Supplied %s has "
+                "too many inputs and outputs (%d) - must be 1 or 2.",
+                status, astGetClass( this ), astGetClass( this ), ndim );
+   }
+
+   if( forward != astGetInvert( this ) ){
+      if( ! this->ncoeff_i && astOK ) {
+         astError( AST__NODEF, "astPolyTran(%s): Supplied %s has "
+                   "no inverse transformation.", status, astGetClass( this ),
+                   astGetClass( this ) );
+      }
+   } else {
+      if( ! this->ncoeff_f && astOK ) {
+         astError( AST__NODEF, "astPolyTran(%s): Supplied %s has "
+                   "no forward transformation.", status, astGetClass( this ),
+                   astGetClass( this ) );
+      }
+   }
+
+/* Check the bounds can be used. */
+   if( lbnd[ 0 ] >= ubnd[ 0 ] && astOK ) {
+      astError( AST__NODEF, "astPolyTran(%s): Supplied upper "
+                "bound for the first axis (%g) is less than or equal to the "
+                "supplied lower bound (%g).", status, astGetClass( this ),
+                lbnd[ 0 ], ubnd[ 0 ] );
+   } else if( ndim == 2 && lbnd[ 1 ] >= ubnd[ 1 ] && astOK ) {
+      astError( AST__NODEF, "astPolyTran(%s): Supplied upper "
+                "bound for the second axis (%g) is less than or equal to the "
+                "supplied lower bound (%g).", status, astGetClass( this ),
+                lbnd[ 1 ], ubnd[ 1 ] );
+   }
+
+/* Initialise pointer to work space. */
+   table = NULL;
+
+/* Loop over increasing polynomial orders until the required accuracy is
+   achieved, up to the specified maximum order. The "order" value is one more
+   than the maximum power in the polynomial (so a quadratic has "order" 3). */
+   if( maxorder < 2 ) maxorder = 2;
+   for( order = 2; order <= maxorder; order++ ) {
+
+/* First do 2D PolyMaps. */
+      if( ndim == 2 ) {
+
+/* Sample the requested polynomial transformation at a grid of points. This
+   grid covers the user-supplied region, using 2*order points on each
+   axis. If the PolyMap is 1D, then it will be treated as a 2D polynomial
+   in which the second output is a unit transformation. */
+         table = SamplePoly2D( this, !forward, table, lbnd, ubnd, 2*order,
+                               &nsamp, scales, status );
+
+/* Fit the polynomial. Always fit a linear polynomial ("order" 2) to any
+   dummy second axis. If successfull, replace the PolyMap transformation
+   and break out of the order loop. */
+         cofs = FitPoly2D( nsamp, acc, order, table, scales, &ncof, &racc,
+                           status );
+
+/* Now do 1D PolyMaps. */
+      } else {
+         table = SamplePoly1D( this, !forward, table, lbnd[ 0 ], ubnd[ 0 ],
+                               2*order, &nsamp, scales, status );
+         cofs = FitPoly1D( nsamp, acc, order, table, scales, &ncof, &racc,
+                           status );
+      }
+
+/* If the fit was succesful, replace the PolyMap transformation and break
+   out of the order loop. */
+      if( cofs && ( racc < acc || ( racc < maxacc && order == maxorder ) ) ) {
+         StoreArrays( this, forward, ncof, cofs, status );
+         break;
+      } else {
+        cofs = astFree( cofs );
+      }
+   }
+
+/* If no fit was produced, return zero. */
+   result = cofs ? 1 : 0;
+
+/* Free resources. */
+   cofs = astFree( cofs );
+   table = astFreeDouble( table );
+
+/* Return the result. */
+   return result;
+}
+
+static double **SamplePoly1D( AstPolyMap *this, int forward, double **table,
+                              double lbnd, double ubnd, int npoint, int *nsamp,
+                              double scales[2], int *status ){
+/*
+*  Name:
+*     SamplePoly1D
+
+*  Purpose:
+*     Create a table of input and output positions for a 1D PolyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     double **SamplePoly1D( AstPolyMap *this, int forward, double **table,
+*                            double lbnd, double ubnd, int npoint, int *nsamp,
+*                            double scales[2], int *status )
+
+*  Description:
+*     This function creates a table containing samples of the requested
+*     polynomial transformation at a grid of input points. This grid covers
+*     the user-supplied region, using "npoint" points.
+
+*  Parameters:
+*     this
+*        The PolyMap.
+*     forward
+*        If non-zero, then the forward PolyMap transformation is sampled.
+*        Otherwise the inverse transformation is sampled.
+*     table
+*        Pointer to a previous table created by this function, which is
+*        to be re-used, or NULL.
+*     lbnd
+*        The lower bounds of the region within the PolyMap's 1D input space
+*        (if "forward" is non-zero) or output space (if "forward" is zero).
+*        The new polynomial will be evaluated over this region.
+*     ubnd
+*        The upper bounds of the region within the PolyMap's 1D input space
+*        (if "forward" is non-zero) or output space (if "forward" is zero).
+*        The new polynomial will be evaluated over this region.
+*     npoint
+*        The number of points to use.
+*     nsamp
+*        Address of an int in which to return the total number of samples
+*        in the returned table.
+*     scales
+*        Array in which to return the scaling factors for the two columns
+*        of the returned table. Multiplying the returned table values by
+*        the scale factor produces PolyMap input or output axis values.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*        Pointer to an array of 2 pointers. Each of these pointers points
+*        to an array of "nsamp" doubles, being the sampled values for y1
+*        and x1 in that order. Here x1 is the input value for the sampled
+*        transformation (these are spaced on the regular grid specified
+*        by lbnd, ubnd and npoint), and y1 is the output position produced
+*        by the sampled transformation. The returned values are scaled so
+*        that each column has an RMS value of 1.0. The scaling factors that
+*        convert scaled values into original values are returned in "scales".
+*        The returned pointer should be freed using astFreeDouble when no
+*        longer needed.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *ps1;
+   AstPointSet *ps2;
+   double **result;
+   double *p0;
+   double *p1;
+   double *ptr1[ 1 ];
+   double *ptr2[ 1 ];
+   double delta0;
+   double rms;
+   double sum;
+   double val0;
+   int i;
+   int icol;
+
+/* Initialise returned value */
+   result = table;
+   *nsamp = 0;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Ensure we have a table of the correct size. */
+   *nsamp = npoint;
+   if( !result ) result = astCalloc( 2, sizeof( double * ) );
+   if( result ) {
+      for( i = 0; i < 2; i++ ) {
+         result[ i ] = astRealloc( result[ i ] , (*nsamp)*sizeof( double ) );
+      }
+   }
+
+/* Work out the step sizes for the grid. */
+   delta0 = ( ubnd - lbnd )/( npoint - 1 );
+
+/* Create a PointSet to hold the grid of input positions. Use column 1
+   of the table to hold the PointSet values. */
+   ps1 = astPointSet( *nsamp, 1, " ", status );
+   ptr1[ 0 ] = result[ 1 ];
+   astSetPoints( ps1, ptr1 );
+
+/* Create a PointSet to hold the grid of output positions. Use column 0
+   of the table to hold the PointSet values. */
+   ps2 = astPointSet( *nsamp, 1, " ", status );
+   ptr2[ 0 ] = result[ 0 ];
+   astSetPoints( ps2, ptr2 );
+   if( astOK ) {
+
+/* Calculate the grid of input positions and store in the PointSet and
+   therefore also in the returned table. */
+      val0 = lbnd;
+      p0 = ptr1[ 0 ];
+      for( i = 0; i < npoint; i++ ) {
+         *(p0++) = val0;
+         val0 += delta0;
+      }
+
+/* Transform the input grid to get the output grid. */
+      (void) astTransform( this, ps1, forward, ps2 );
+
+/* Scale each column in turn. */
+      for( icol = 0; icol < 2; icol++ ) {
+
+/* Find the RMS of the values in the column. */
+         sum = 0.0;
+         p0 = result[ icol ];
+         p1 = p0 + (*nsamp);
+         for( ; p0 < p1; p0++ ) sum += ( *p0 )*( *p0 );
+         rms = sqrt( sum/(*nsamp) );
+
+/* Divide the table values by the RMS. */
+         p0 = result[ icol ];
+         p1 = p0 + (*nsamp);
+         for( ; p0 < p1; p0++ ) *p0 /= rms;
+
+/* Return the RMS as the scale factor. */
+         scales[ icol ] = rms;
+      }
+   }
+
+/* Free resources */
+   ps1 = astAnnul( ps1 );
+   ps2 = astAnnul( ps2 );
+
+/* If an error occurred, free the returned array. */
+   if( !astOK ) result = astFreeDouble( result );
+
+/* Return a pointer to the table. */
+   return result;
+}
+
+static double **SamplePoly2D( AstPolyMap *this, int forward, double **table,
+                              const double *lbnd, const double *ubnd, int npoint,
+                              int *nsamp, double scales[4], int *status ){
+/*
+*  Name:
+*     SamplePoly2D
+
+*  Purpose:
+*     Create a table of input and output positions for a 2D PolyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     double **SamplePoly2D( AstPolyMap *this, int forward, double **table,
+*                            const double *lbnd, const double *ubnd, int npoint,
+*                            int *nsamp, double scales[4], int *status )
+
+*  Description:
+*     This function creates a table containing samples of the requested
+*     polynomial transformation at a grid of input points. This grid covers
+*     the user-supplied region, using "npoint" points on each axis.
+
+*  Parameters:
+*     this
+*        The PolyMap.
+*     forward
+*        If non-zero, then the forward PolyMap transformation is sampled.
+*        Otherwise the inverse transformation is sampled.
+*     table
+*        Pointer to a previous table created by this function, which is
+*        to be re-used, or NULL.
+*     lbnd
+*        An array holding the lower bounds of a rectangular region within
+*        the PolyMap's input space (if "forward" is non-zero) or output
+*        space (if "forward" is zero). The new polynomial will be
+*        evaluated over this rectangle.
+*     ubnd
+*        An array holding the upper bounds of a rectangular region within
+*        the PolyMap's input space (if "forward" is non-zero) or output
+*        space (if "forward" is zero). The new polynomial will be
+*        evaluated over this rectangle.
+*     npoint
+*        The number of points along each edge of the grid.
+*     nsamp
+*        Address of an int in which to return the total number of samples
+*        in the returned table.
+*     scales
+*        Array in which to return the scaling factors for the four
+*        columns of the returned table. Multiplying the returned table
+*        values by the scale factor produces PolyMap input or output axis
+*        values.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*        Pointer to an array of 4 pointers. Each of these pointers points
+*        to an array of "nsamp" doubles, being the sampled values for y1,
+*        y2, x1 or x2 in that order. Here (x1,x2) are the input values
+*        for the sampled transformation (these are spaced on the regular
+*        grid specified by lbnd, ubnd and npoint), and (y1,y2) are the
+*        output positions produced by the sampled transformation. The
+*        returned values are scaled so that each column has an RMS value
+*        of 1.0. The scaling factors that convert scaled values into
+*        original values are returned in "scales". The returned pointer
+*        should be freed using astFreeDouble when no longer needed.
+
+*/
+
+/* Local Variables: */
+   AstPointSet *ps1;
+   AstPointSet *ps2;
+   double **result;
+   double *p0;
+   double *p1;
+   double *ptr1[ 2 ];
+   double *ptr2[ 2 ];
+   double delta1;
+   double delta0;
+   double rms;
+   double sum;
+   double val0;
+   double val1;
+   int i;
+   int icol;
+   int j;
+
+/* Initialise returned value */
+   result = table;
+   *nsamp = 0;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Ensure we have a table of the correct size. */
+   *nsamp = npoint*npoint;
+   if( !result ) result = astCalloc( 4, sizeof( double * ) );
+   if( result ) {
+      for( i = 0; i < 4; i++ ) {
+         result[ i ] = astRealloc( result[ i ] , (*nsamp)*sizeof( double ) );
+      }
+   }
+
+/* Work out the step sizes for the grid. */
+   delta0 = ( ubnd[ 0 ] - lbnd[ 0 ] )/( npoint - 1 );
+   delta1 = ( ubnd[ 1 ] - lbnd[ 1 ] )/( npoint - 1 );
+
+/* Create a PointSet to hold the grid of input positions. Use columns 2
+   and 3 of the table to hold the PointSet values. */
+   ps1 = astPointSet( *nsamp, 2, " ", status );
+   ptr1[ 0 ] = result[ 2 ];
+   ptr1[ 1 ] = result[ 3 ];
+   astSetPoints( ps1, ptr1 );
+
+/* Create a PointSet to hold the grid of output positions. Use columns 0
+   and 1 of the table to hold the PointSet values. */
+   ps2 = astPointSet( *nsamp, 2, " ", status );
+   ptr2[ 0 ] = result[ 0 ];
+   ptr2[ 1 ] = result[ 1 ];
+   astSetPoints( ps2, ptr2 );
+   if( astOK ) {
+
+/* Calculate the grid of input positions and store in the PointSet and
+   therefore also in the returned table. */
+      val0 = lbnd[ 0 ];
+      p0 = ptr1[ 0 ];
+      p1 = ptr1[ 1 ];
+      for( i = 0; i < npoint; i++ ) {
+         val1 = lbnd[ 1 ];
+         for( j = 0; j < npoint; j++ ) {
+             *(p0++) = val0;
+             *(p1++) = val1;
+             val1 += delta1;
+         }
+         val0 += delta0;
+      }
+
+/* Transform the input grid to get the output grid. */
+      (void) astTransform( this, ps1, forward, ps2 );
+
+/* Scale each pair of columns in turn. Use the ssame scale factor for
+   each axis in order to ensure an isotropic metric. */
+      for( icol = 0; icol < 4; icol += 2 ) {
+
+/* Find the RMS of the values in the two columns. */
+         sum = 0.0;
+         p0 = result[ icol ];
+         p1 = p0 + (*nsamp);
+         for( ; p0 < p1; p0++ ) sum += ( *p0 )*( *p0 );
+
+         p0 = result[ icol + 1 ];
+         p1 = p0 + (*nsamp);
+         for( ; p0 < p1; p0++ ) sum += ( *p0 )*( *p0 );
+
+         rms = sqrt( sum/(2*(*nsamp)) );
+
+/* Divide the table values by the RMS. */
+         p0 = result[ icol ];
+         p1 = p0 + (*nsamp);
+         for( ; p0 < p1; p0++ ) *p0 /= rms;
+
+         p0 = result[ icol + 1 ];
+         p1 = p0 + (*nsamp);
+         for( ; p0 < p1; p0++ ) *p0 /= rms;
+
+/* Return the RMS as the scale factor. */
+         scales[ icol ] = rms;
+         scales[ icol + 1 ] = rms;
+      }
+   }
+
+/* Free resources */
+   ps1 = astAnnul( ps1 );
+   ps2 = astAnnul( ps2 );
+
+/* If an error occurred, free the returned array. */
+   if( !astOK ) result = astFreeDouble( result );
+
+/* Return a pointer to the table. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a PolyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     PolyMap member function (over-rides the astSetAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a PolyMap, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the PolyMap.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstPolyMap *this;             /* Pointer to the PolyMap structure */
+   double dval;                  /* Floating point attribute value */
+   int ival;                     /* Integer attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PolyMap structure. */
+   this = (AstPolyMap *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* IterInverse. */
+/* ------------ */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "iterinverse= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetIterInverse( this, ival );
+
+/* NiterInverse. */
+/* ------------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "niterinverse= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetNiterInverse( this, ival );
+
+/* TolInverse. */
+/* ----------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "tolinverse= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetTolInverse( this, dval );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void StoreArrays( AstPolyMap *this, int forward, int ncoeff,
+                         const double *coeff, int *status ){
+/*
+*  Name:
+*     StoreArrays
+
+*  Purpose:
+*     Store the dynamic arrays for a single transformation within a PolyMap
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     void StoreArrays( AstPolyMap *this, int forward, int ncoeff,
+*                       const double *coeff, int *status )
+
+*  Class Membership:
+*     PolyMap initialiser.
+
+*  Description:
+*     This function sets up the arrays within a PolyMap structure that
+*     describes either the forward or inverse transformation.
+
+*  Parameters:
+*     this
+*        The PolyMap.
+*     forward
+*        If non-zero, replace the forward transformation. Otherwise,
+*        replace the inverse transformation.
+*     ncoeff
+*        The number of non-zero coefficients necessary to define the
+*        specified transformation of the PolyMap. If zero is supplied, the
+*        transformation will be undefined.
+*     coeff
+*        An array containing "ncof*( 2 + nin )" elements. Each group
+*	 of "2 + nin" adjacent elements describe a single coefficient of
+*	 the transformation. Within each such group, the first
+*	 element is the coefficient value; the next element is the
+*	 integer index of the PolyMap output which uses the coefficient
+*	 within its defining polynomial (the first output has index 1);
+*	 the remaining elements of the group give the integer powers to
+*	 use with each input coordinate value (powers must not be
+*	 negative).
+*     status
+*        Pointer to inherited status.
+*/
+
+/* Local Variables: */
+   const double *group;          /* Pointer to start of next coeff. description */
+   int *pows;                    /* Pointer to powers for current coeff. */
+   int gsize;                    /* Length of each coeff. description */
+   int i;                        /* Loop count */
+   int ico;                      /* Index of next coeff. for current input or output */
+   int iin;                      /* Input index extracted from coeff. description */
+   int iout;                     /* Output index extracted from coeff. description */
+   int j;                        /* Loop count */
+   int nin;                      /* Number of inputs */
+   int nout;                     /* Number of outputs */
+   int pow;                      /* Power extracted from coeff. description */
+
+/* Check the global status. */
+   if ( !astOK ) return;
+
+/* Get the number of inputs and outputs. */
+   nin = astGetNin( this );
+   nout = astGetNout( this );
+
+/* First Free any existing arrays. */
+   FreeArrays( this, forward, status );
+
+/* Now initialise the forward transformation, if required. */
+   if( forward && ncoeff > 0 ) {
+
+/* Create the arrays decribing the forward transformation. */
+      this->ncoeff_f = astMalloc( sizeof( int )*(size_t) nout );
+      this->mxpow_f = astMalloc( sizeof( int )*(size_t) nin );
+      this->power_f = astMalloc( sizeof( int ** )*(size_t) nout );
+      this->coeff_f = astMalloc( sizeof( double * )*(size_t) nout );
+      if( astOK ) {
+
+/* Initialise the count of coefficients for each output coordinate to zero. */
+         for( i = 0; i < nout; i++ ) this->ncoeff_f[ i ] = 0;
+
+/* Initialise max power for each input coordinate to zero. */
+         for( j = 0; j < nin; j++ ) this->mxpow_f[ j ] = 0;
+
+/* Scan through the supplied forward coefficient array, counting the
+   number of coefficients which relate to each output. Also find the
+   highest power used for each input axis. Report errors if any unusable
+   values are found in the supplied array. */
+         group = coeff;
+         gsize = 2 + nin;
+         for( i = 0; i < ncoeff && astOK; i++, group += gsize ) {
+
+            iout = floor( group[ 1 ] + 0.5 );
+            if( iout < 1 || iout > nout ) {
+               astError( AST__BADCI, "astInitPolyMap(%s): Forward "
+                         "coefficient %d referred to an illegal output "
+                         "coordinate %d.", status, astGetClass( this ), i + 1,
+                         iout );
+               astError( AST__BADCI, "This number should be in the "
+                         "range 1 to %d.", status, nout );
+               break;
+            }
+
+            this->ncoeff_f[ iout - 1 ]++;
+
+            for( j = 0; j < nin; j++ ) {
+               pow = floor( group[ 2 + j ] + 0.5 );
+               if( pow < 0 ) {
+                  astError( AST__BADPW, "astInitPolyMap(%s): Forward "
+                            "coefficient %d has a negative power (%d) "
+                            "for input coordinate %d.", status,
+                            astGetClass( this ), i + 1, pow, j + 1 );
+                  astError( AST__BADPW, "All powers should be zero or "
+                            "positive." , status);
+                  break;
+               }
+               if( pow > this->mxpow_f[ j ] ) this->mxpow_f[ j ] = pow;
+            }
+         }
+
+/* Allocate the arrays to store the input powers associated with each
+   coefficient, and the coefficient values. Reset the coefficient count
+   for each axis to zero afterwards so that we can use the array as an index
+   to the next vacant slot withint he following loop. */
+         for( i = 0; i < nout; i++ ) {
+            this->power_f[ i ] = astMalloc( sizeof( int * )*
+                                           (size_t) this->ncoeff_f[ i ] );
+            this->coeff_f[ i ] = astMalloc( sizeof( double )*
+                                           (size_t) this->ncoeff_f[ i ] );
+            this->ncoeff_f[ i ] = 0;
+         }
+
+         if( astOK ) {
+
+/* Extract the coefficient values and powers form the supplied array and
+   store them in the arrays created above. */
+            group = coeff;
+            for( i = 0; i < ncoeff && astOK; i++, group += gsize ) {
+               iout = floor( group[ 1 ] + 0.5 ) - 1;
+               ico = ( this->ncoeff_f[ iout ] )++;
+               this->coeff_f[ iout ][ ico ] = group[ 0 ];
+
+               pows = astMalloc( sizeof( int )*(size_t) nin );
+               this->power_f[ iout ][ ico ] = pows;
+               if( astOK ) {
+                  for( j = 0; j < nin; j++ ) {
+                     pows[ j ] = floor( group[ 2 + j ] + 0.5 );
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Now initialise the inverse transformation, if required. */
+   if( !forward && ncoeff > 0 ) {
+
+/* Create the arrays decribing the inverse transformation. */
+      this->ncoeff_i = astMalloc( sizeof( int )*(size_t) nin );
+      this->mxpow_i = astMalloc( sizeof( int )*(size_t) nout );
+      this->power_i = astMalloc( sizeof( int ** )*(size_t) nin );
+      this->coeff_i = astMalloc( sizeof( double * )*(size_t) nin );
+      if( astOK ) {
+
+/* Initialise the count of coefficients for each input coordinate to zero. */
+         for( i = 0; i < nin; i++ ) this->ncoeff_i[ i ] = 0;
+
+/* Initialise max power for each output coordinate to zero. */
+         for( j = 0; j < nout; j++ ) this->mxpow_i[ j ] = 0;
+
+/* Scan through the supplied inverse coefficient array, counting the
+   number of coefficients which relate to each input. Also find the
+   highest power used for each output axis. Report errors if any unusable
+   values are found in the supplied array. */
+         group = coeff;
+
+         gsize = 2 + nout;
+         for( i = 0; i < ncoeff && astOK; i++, group += gsize ) {
+
+            iin = floor( group[ 1 ] + 0.5 );
+            if( iin < 1 || iin > nin ) {
+               astError( AST__BADCI, "astInitPolyMap(%s): Inverse "
+                         "coefficient %d referred to an illegal input "
+                         "coordinate %d.", status, astGetClass( this ),
+                         i + 1, iin );
+               astError( AST__BADCI, "This number should be in the "
+                         "range 1 to %d.", status, nin );
+               break;
+            }
+
+            this->ncoeff_i[ iin - 1 ]++;
+
+            for( j = 0; j < nout; j++ ) {
+               pow = floor( group[ 2 + j ] + 0.5 );
+               if( pow < 0 ) {
+                  astError( AST__BADPW, "astInitPolyMap(%s): Inverse "
+                            "coefficient %d has a negative power (%d) "
+                            "for output coordinate %d.", status,
+                            astGetClass( this ), i + 1, pow, j + 1 );
+                  astError( AST__BADPW, "All powers should be zero or "
+                            "positive." , status);
+                  break;
+               }
+               if( pow > this->mxpow_i[ j ] ) this->mxpow_i[ j ] = pow;
+            }
+         }
+
+/* Allocate the arrays to store the output powers associated with each
+   coefficient, and the coefficient values. Reset the coefficient count
+   for each axis to zero afterwards so that we can use the array as an index
+   to the next vacant slot within the following loop. */
+         for( i = 0; i < nin; i++ ) {
+            this->power_i[ i ] = astMalloc( sizeof( int * )*
+                                           (size_t) this->ncoeff_i[ i ] );
+            this->coeff_i[ i ] = astMalloc( sizeof( double )*
+                                           (size_t) this->ncoeff_i[ i ] );
+            this->ncoeff_i[ i ] = 0;
+         }
+
+         if( astOK ) {
+
+/* Extract the coefficient values and powers form the supplied array and
+   store them in the arrays created above. */
+            group = coeff;
+            for( i = 0; i < ncoeff && astOK; i++, group += gsize ) {
+               iin = floor( group[ 1 ] + 0.5 ) - 1;
+               ico = ( this->ncoeff_i[ iin ] )++;
+               this->coeff_i[ iin ][ ico ] = group[ 0 ];
+
+               pows = astMalloc( sizeof( int )*(size_t) nout );
+               this->power_i[ iin ][ ico ] = pows;
+               if( astOK ) {
+                  for( j = 0; j < nout; j++ ) {
+                     pows[ j ] = floor( group[ 2 + j ] + 0.5 );
+                  }
+               }
+            }
+         }
+      }
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a PolyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     PolyMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a PolyMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the PolyMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPolyMap *this;             /* Pointer to the PolyMap structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the PolyMap structure. */
+   this = (AstPolyMap *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* IterInverse. */
+/* ------------ */
+   if ( !strcmp( attrib, "iterinverse" ) ) {
+      result = astTestIterInverse( this );
+
+/* NiterInverse. */
+/* ------------- */
+   } else if ( !strcmp( attrib, "niterinverse" ) ) {
+      result = astTestNiterInverse( this );
+
+/* TolInverse. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "tolinverse" ) ) {
+      result = astTestTolInverse( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a PolyMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     PolyMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a PolyMap and a set of points encapsulated in a
+*     PointSet and transforms the points.
+
+*  Parameters:
+*     this
+*        Pointer to the PolyMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of columns in the PolyMap being applied.
+*     -  The number of coordinate values per point in the output PointSet will
+*     equal the number of rows in the PolyMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstPolyMap *map;              /* Pointer to PolyMap to be applied */
+   double **coeff;               /* Pointer to coefficient value arrays */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double **work;                /* Pointer to exponentiated axis values */
+   double *outcof;               /* Pointer to next coefficient value */
+   double *pwork;                /* Pointer to exponentiated axis values */
+   double outval;                /* Output axis value */
+   double term;                  /* Term to be added to output value */
+   double x;                     /* Input axis value */
+   double xp;                    /* Exponentiated input axis value */
+   int ***power;                 /* Pointer to coefficient power arrays */
+   int **outpow;                 /* Pointer to next set of axis powers */
+   int *mxpow;                   /* Pointer to max used power for each input */
+   int *ncoeff;                  /* Pointer to no. of coefficients */
+   int in_coord;                 /* Index of output coordinate */
+   int ico;                      /* Coefficient index */
+   int ip;                       /* Axis power */
+   int nc;                       /* No. of coefficients in polynomial */
+   int ncoord_in;                /* Number of coordinates per input point */
+   int ncoord_out;               /* Number of coordinates per output point */
+   int npoint;                   /* Number of points */
+   int out_coord;                /* Index of output coordinate */
+   int point;                    /* Loop counter for points */
+   int pow;                      /* Next axis power */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the PolyMap. */
+   map = (AstPolyMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* Determine whether to apply the forward or inverse mapping, according to the
+   direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* If we are using the inverse transformatiom, and the IterInverse
+   attribute is non-zero, use an iterative inverse algorithm rather than any
+   inverse transformation defined within the PolyMap. */
+   if( !forward && astGetIterInverse(map) ) {
+      IterInverse( map, in, result, status );
+
+/* Otherwise, determine the numbers of points and coordinates per point from
+   the input and output PointSets and obtain pointers for accessing the input
+   and output coordinate values. */
+   } else {
+      ncoord_in = astGetNcoord( in );
+      ncoord_out = astGetNcoord( result );
+      npoint = astGetNpoint( in );
+      ptr_in = astGetPoints( in );
+      ptr_out = astGetPoints( result );
+
+/* Get a pointer to the arrays holding the required coefficient
+   values and powers, according to the direction of mapping required. */
+      if ( forward ) {
+         ncoeff = map->ncoeff_f;
+         coeff = map->coeff_f;
+         power = map->power_f;
+         mxpow = map->mxpow_f;
+      } else {
+         ncoeff = map->ncoeff_i;
+         coeff = map->coeff_i;
+         power = map->power_i;
+         mxpow = map->mxpow_i;
+      }
+
+/* Allocate memory to hold the required powers of the input axis values. */
+      work = astMalloc( sizeof( double * )*(size_t) ncoord_in );
+      for( in_coord = 0; in_coord < ncoord_in; in_coord++ ) {
+         work[ in_coord ] = astMalloc( sizeof( double )*(size_t) ( mxpow[ in_coord ] + 1 ) );
+      }
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+      if ( astOK ) {
+
+/* Loop to apply the polynomial to each point in turn.*/
+         for ( point = 0; point < npoint; point++ ) {
+
+/* Find the required powers of the input axis values and store them in
+   the work array. */
+            for( in_coord = 0; in_coord < ncoord_in; in_coord++ ) {
+               pwork = work[ in_coord ];
+               pwork[ 0 ] = 1.0;
+               x = ptr_in[ in_coord ][ point ];
+               if( x == AST__BAD ) {
+                  for( ip = 1; ip <= mxpow[ in_coord ]; ip++ ) pwork[ ip ] = AST__BAD;
+               } else {
+                  for( ip = 1; ip <= mxpow[ in_coord ]; ip++ ) {
+                     pwork[ ip ] = pwork[ ip - 1 ]*x;
+                  }
+               }
+            }
+
+/* Loop round each output. */
+            for( out_coord = 0; out_coord < ncoord_out; out_coord++ ) {
+
+/* Initialise the output value. */
+               outval = 0.0;
+
+/* Get pointers to the coefficients and powers for this output. */
+               outcof = coeff[ out_coord ];
+               outpow = power[ out_coord ];
+
+/* Loop round all polynomial coefficients.*/
+               nc = ncoeff[ out_coord ];
+               for ( ico = 0; ico < nc && outval != AST__BAD;
+                     ico++, outcof++, outpow++ ) {
+
+/* Initialise the current term to be equal to the value of the coefficient.
+   If it is bad, store a bad output value. */
+                  term = *outcof;
+                  if( term == AST__BAD ) {
+                     outval = AST__BAD;
+
+/* Otherwise, loop round all inputs */
+                  } else {
+                     for( in_coord = 0; in_coord < ncoord_in; in_coord++ ) {
+
+/* Get the power of the current input axis value used by the current
+   coefficient. If it is zero, pass on. */
+                        pow = (*outpow)[ in_coord ];
+                        if( pow > 0 ) {
+
+/* Get the axis value raised to the appropriate power. */
+                           xp = work[ in_coord ][ pow ];
+
+/* If bad, set the output value bad and break. */
+                           if( xp == AST__BAD ) {
+                              outval = AST__BAD;
+                              break;
+
+/* Otherwise multiply the current term by the exponentiated axis value. */
+                           } else {
+                              term *= xp;
+                           }
+                        }
+                     }
+                  }
+
+/* Increment the output value by the current term of the polynomial. */
+                  outval += term;
+
+               }
+
+/* Store the output value. */
+               ptr_out[ out_coord ][ point ] = outval;
+
+            }
+         }
+      }
+
+/* Free resources. */
+      for( in_coord = 0; in_coord < ncoord_in; in_coord++ ) {
+         work[ in_coord ] = astFree( work[ in_coord ] );
+      }
+      work = astFree( work );
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     IterInverse
+
+*  Purpose:
+*     Provide an iterative inverse transformation?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute indicates whether the inverse transformation of
+*     the PolyMap should be implemented via an iterative Newton-Raphson
+*     approximation that uses the forward transformation to transform
+*     candidate input positions until an output position is found which
+*     is close to the required output position. By default, an iterative
+*     inverse is provided if, and only if, no inverse polynomial was supplied
+*     when the PolyMap was constructed.
+*
+*     The NiterInverse and TolInverse attributes provide parameters that
+*     control the behaviour of the inverse approximation method.
+
+*  Applicability:
+*     PolyMap
+*        All PolyMaps have this attribute.
+
+*  Notes:
+*     - An iterative inverse can only be used if the PolyMap has equal
+*     numbers of inputs and outputs, as given by the Nin and Nout
+*     attributes. An error will be reported if IterInverse is set non-zero
+*     for a PolyMap that does not meet this requirement.
+
+*att--
+*/
+astMAKE_CLEAR(PolyMap,IterInverse,iterinverse,-INT_MAX)
+astMAKE_GET(PolyMap,IterInverse,int,0,( ( this->iterinverse == -INT_MAX ) ?
+                                          (this->ncoeff_i == 0) : this->iterinverse ))
+astMAKE_SET(PolyMap,IterInverse,int,iterinverse,
+  (((astGetNin(this)==astGetNout(this))||!value)?((value?1:0)):(astError(AST__ATTIN,"astSetIterInverse(%s):"
+  "Cannot use an iterative inverse because the %s has unequal numbers of "
+  "inputs and outputs.", status, astGetClass(this),astGetClass(this)),this->iterinverse)))
+astMAKE_TEST(PolyMap,IterInverse,( this->iterinverse != -INT_MAX ))
+
+/* NiterInverse. */
+/* --------- */
+/*
+*att++
+*  Name:
+*     NiterInverse
+
+*  Purpose:
+*     Maximum number of iterations for the iterative inverse transformation.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute controls the iterative inverse transformation
+*     used if the IterInverse attribute is non-zero.
+*
+*     Its value gives the maximum number of iterations of the
+*     Newton-Raphson algorithm to be used for each transformed position.
+*     The default value is 4. See also attribute TolInverse.
+
+*  Applicability:
+*     PolyMap
+*        All PolyMaps have this attribute.
+
+*att--
+*/
+astMAKE_CLEAR(PolyMap,NiterInverse,niterinverse,-INT_MAX)
+astMAKE_GET(PolyMap,NiterInverse,int,0,( this->niterinverse == -INT_MAX ? 4 : this->niterinverse))
+astMAKE_SET(PolyMap,NiterInverse,int,niterinverse,value)
+astMAKE_TEST(PolyMap,NiterInverse,( this->niterinverse != -INT_MAX ))
+
+/* TolInverse. */
+/* ----------- */
+/*
+*att++
+*  Name:
+*     TolInverse
+
+*  Purpose:
+*     Target relative error for the iterative inverse transformation.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute controls the iterative inverse transformation
+*     used if the IterInverse attribute is non-zero.
+*
+*     Its value gives the target relative error in teh axis values of
+*     each transformed position. Further iterations will be performed
+*     until the target relative error is reached, or the maximum number
+*     of iterations given by attribute NiterInverse is reached.
+
+*     The default value is 1.0E-6.
+
+*  Applicability:
+*     PolyMap
+*        All PolyMaps have this attribute.
+*att--
+*/
+astMAKE_CLEAR(PolyMap,TolInverse,tolinverse,AST__BAD)
+astMAKE_GET(PolyMap,TolInverse,double,0.0,( this->tolinverse == AST__BAD ? 1.0E-6 : this->tolinverse))
+astMAKE_SET(PolyMap,TolInverse,double,tolinverse,value)
+astMAKE_TEST(PolyMap,TolInverse,( this->tolinverse != AST__BAD ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for PolyMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for PolyMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the
+*     coefficients associated with the input PolyMap.
+*/
+
+
+/* Local Variables: */
+   AstPolyMap *in;               /* Pointer to input PolyMap */
+   AstPolyMap *out;              /* Pointer to output PolyMap */
+   int nin;                      /* No. of input coordinates */
+   int nout;                     /* No. of output coordinates */
+   int i;                        /* Loop count */
+   int j;                        /* Loop count */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output PolyMaps. */
+   in = (AstPolyMap *) objin;
+   out = (AstPolyMap *) objout;
+
+/* Nullify the pointers stored in the output object since these will
+   currently be pointing at the input data (since the output is a simple
+   byte-for-byte copy of the input). Otherwise, the input data could be
+   freed by accidient if the output object is deleted due to an error
+   occuring in this function. */
+   out->ncoeff_f = NULL;
+   out->power_f = NULL;
+   out->coeff_f = NULL;
+   out->mxpow_f = NULL;
+
+   out->ncoeff_i = NULL;
+   out->power_i = NULL;
+   out->coeff_i = NULL;
+   out->mxpow_i = NULL;
+
+   out->jacobian = NULL;
+
+/* Get the number of inputs and outputs of the uninverted Mapping. */
+   nin = ( (AstMapping *) in )->nin;
+   nout = ( (AstMapping *) in )->nout;
+
+/* Copy the number of coefficients associated with each output of the forward
+   transformation. */
+   if( in->ncoeff_f ) {
+      out->ncoeff_f = (int *) astStore( NULL, (void *) in->ncoeff_f,
+                                        sizeof( int )*(size_t) nout );
+
+/* Copy the maximum power of each input axis value used by the forward
+   transformation. */
+      out->mxpow_f = (int *) astStore( NULL, (void *) in->mxpow_f,
+                                       sizeof( int )*(size_t) nin );
+
+/* Copy the coefficient values used by the forward transformation. */
+      if( in->coeff_f ) {
+         out->coeff_f = astMalloc( sizeof( double * )*(size_t) nout );
+         if( astOK ) {
+            for( i = 0; i < nout; i++ ) {
+               out->coeff_f[ i ] = (double *) astStore( NULL, (void *) in->coeff_f[ i ],
+                                                     sizeof( double )*(size_t) in->ncoeff_f[ i ] );
+            }
+         }
+      }
+
+/* Copy the input axis powers associated with each coefficient of the forward
+   transformation. */
+      if( in->power_f ) {
+         out->power_f = astMalloc( sizeof( int ** )*(size_t) nout );
+         if( astOK ) {
+            for( i = 0; i < nout; i++ ) {
+               out->power_f[ i ] = astMalloc( sizeof( int * )*(size_t) in->ncoeff_f[ i ] );
+               if( astOK ) {
+                  for( j = 0; j < in->ncoeff_f[ i ]; j++ ) {
+                     out->power_f[ i ][ j ] = (int *) astStore( NULL, (void *) in->power_f[ i ][ j ],
+                                                                sizeof( int )*(size_t) nin );
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Do the same for the inverse transformation. */
+   if( in->ncoeff_i ) {
+      out->ncoeff_i = (int *) astStore( NULL, (void *) in->ncoeff_i,
+                                        sizeof( int )*(size_t) nin );
+
+      out->mxpow_i = (int *) astStore( NULL, (void *) in->mxpow_i,
+                                       sizeof( int )*(size_t) nout );
+
+      if( in->coeff_i ) {
+         out->coeff_i = astMalloc( sizeof( double * )*(size_t) nin );
+         if( astOK ) {
+            for( i = 0; i < nin; i++ ) {
+               out->coeff_i[ i ] = (double *) astStore( NULL, (void *) in->coeff_i[ i ],
+                                                     sizeof( double )*(size_t) in->ncoeff_i[ i ] );
+            }
+         }
+      }
+
+      if( in->power_i ) {
+         out->power_i = astMalloc( sizeof( int ** )*(size_t) nin );
+         if( astOK ) {
+            for( i = 0; i < nin; i++ ) {
+               out->power_i[ i ] = astMalloc( sizeof( int * )*(size_t) in->ncoeff_i[ i ] );
+               if( astOK ) {
+                  for( j = 0; j < in->ncoeff_i[ i ]; j++ ) {
+                     out->power_i[ i ][ j ] = (int *) astStore( NULL, (void *) in->power_i[ i ][ j ],
+                                                                sizeof( int )*(size_t) nout );
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* If an error has occurred, free al the resources allocated above. */
+   if( !astOK ) {
+      FreeArrays( out, 1, status );
+      FreeArrays( out, 0, status );
+   }
+
+   return;
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for PolyMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for PolyMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstPolyMap *this;
+   int nc;
+   int ic;
+   int lstat;
+   int error;
+
+/* Obtain a pointer to the PolyMap structure. */
+   this = (AstPolyMap *) obj;
+
+/* Free the arrays. */
+   FreeArrays( this, 1, status );
+   FreeArrays( this, 0, status );
+
+/* Free the resources used to store the Jacobian of the forward
+   transformation. */
+   if( this->jacobian ) {
+
+/* Get the number of PolyMap inputs. We need to clear any error status
+   first since astGetNin returns zero if an error has occurred. The
+   Jacobian will only be non-NULL if the number of inputs and outputs
+   are equal. */
+      error = !astOK;
+      if( error ) {
+         lstat = astStatus;
+         astClearStatus;
+      }
+      nc = astGetNin( this );
+      if( error ) astSetStatus( lstat );
+
+      for( ic = 0; ic < nc; ic++ ) {
+         (this->jacobian)[ ic ] = astAnnul( (this->jacobian)[ ic ] );
+      }
+      this->jacobian = astFree( this->jacobian );
+   }
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for PolyMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the PolyMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the PolyMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstPolyMap *this;             /* Pointer to the PolyMap structure */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   char comm[ 100 ];             /* Buffer for comment string */
+   double dval;                  /* Floating point attribute value */
+   int i;                        /* Loop index */
+   int iv;                       /* Vectorised keyword index */
+   int ival;                     /* Integer value */
+   int j;                        /* Loop index */
+   int k;                        /* Loop index */
+   int nin;                      /* No. of input coords */
+   int nout;                     /* No. of output coords */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the PolyMap structure. */
+   this = (AstPolyMap *) this_object;
+
+/* Find the number of inputs and outputs of the uninverted Mapping. */
+   nin = ( (AstMapping *) this )->nin;
+   nout = ( (AstMapping *) this )->nout;
+
+/* Write out values representing the instance variables for the
+   PolyMap class.  */
+
+/* First do the forward transformation arrays. Check they are used. */
+   if( this->ncoeff_f ) {
+
+/* Store the maximum power of each input axis value used by the forward
+   transformation. */
+      for( i = 0; i < nin; i++ ){
+         (void) sprintf( buff, "MPF%d", i + 1 );
+         (void) sprintf( comm, "Max. power of input %d in any forward polynomial", i + 1 );
+          astWriteInt( channel, buff, 1, 1, (this->mxpow_f)[ i ], comm );
+      }
+
+/* Store the number of coefficients associated with each output of the forward
+   transformation. */
+      for( i = 0; i < nout; i++ ){
+         (void) sprintf( buff, "NCF%d", i + 1 );
+         (void) sprintf( comm, "No. of coeff.s for forward polynomial %d", i + 1 );
+         astWriteInt( channel, buff, 1, 1, (this->ncoeff_f)[ i ], comm );
+      }
+
+/* Store the coefficient values used by the forward transformation. */
+      iv = 1;
+      for( i = 0; i < nout; i++ ){
+         for( j = 0; j < this->ncoeff_f[ i ]; j++, iv++ ){
+            if( (this->coeff_f)[ i ][ j ] != AST__BAD ) {
+               (void) sprintf( buff, "CF%d", iv );
+               (void) sprintf( comm, "Coeff %d of forward polynomial %d", j + 1, i + 1 );
+               astWriteDouble( channel, buff, 1, 1, (this->coeff_f)[ i ][ j ], comm );
+            }
+         }
+      }
+
+/* Store the input axis powers associated with each coefficient of the forward
+   transformation. */
+      iv = 1;
+      for( i = 0; i < nout; i++ ){
+         for( j = 0; j < this->ncoeff_f[ i ]; j++ ){
+            for( k = 0; k < nin; k++, iv++ ){
+               if( (this->power_f)[ i ][ j ][ k ] > 0 ) {
+                  (void) sprintf( buff, "PF%d", iv );
+                  (void) sprintf( comm, "Power of i/p %d for coeff %d of fwd poly %d", k + 1, j + 1, i + 1 );
+                  astWriteDouble( channel, buff, 1, 1, (this->power_f)[ i ][ j ][ k ], comm );
+               }
+            }
+         }
+      }
+   }
+
+/* Now do the inverse transformation arrays. Check they are used. */
+   if( this->ncoeff_i ) {
+
+/* Store the maximum power of each output axis value used by the inverse
+   transformation. */
+      for( i = 0; i < nout; i++ ){
+         (void) sprintf( buff, "MPI%d", i + 1 );
+         (void) sprintf( comm, "Max. power of output %d in any inverse polynomial", i + 1 );
+          astWriteInt( channel, buff, 1, 1, (this->mxpow_i)[ i ], comm );
+      }
+
+/* Store the number of coefficients associated with each input of the inverse
+   transformation. */
+      for( i = 0; i < nin; i++ ){
+         (void) sprintf( buff, "NCI%d", i + 1 );
+         (void) sprintf( comm, "No. of coeff.s for inverse polynomial %d", i + 1 );
+         astWriteInt( channel, buff, 1, 1, (this->ncoeff_i)[ i ], comm );
+      }
+
+/* Store the coefficient values used by the inverse transformation. */
+      iv = 1;
+      for( i = 0; i < nin; i++ ){
+         for( j = 0; j < this->ncoeff_i[ i ]; j++, iv++ ){
+            if( (this->coeff_i)[ i ][ j ] != AST__BAD ) {
+               (void) sprintf( buff, "CI%d", iv );
+               (void) sprintf( comm, "Coeff %d of inverse polynomial %d", j + 1, i + 1 );
+               astWriteDouble( channel, buff, 1, 1, (this->coeff_i)[ i ][ j ], comm );
+            }
+         }
+      }
+
+/* Store the output axis powers associated with each coefficient of the inverse
+   transformation. */
+      iv = 1;
+      for( i = 0; i < nin; i++ ){
+         for( j = 0; j < this->ncoeff_i[ i ]; j++ ){
+            for( k = 0; k < nout; k++, iv++ ){
+               if( (this->power_i)[ i ][ j ][ k ] > 0 ) {
+                  (void) sprintf( buff, "PI%d", iv );
+                  (void) sprintf( comm, "Power of o/p %d for coeff %d of inv poly %d", k + 1, j + 1, i + 1 );
+                  astWriteDouble( channel, buff, 1, 1, (this->power_i)[ i ][ j ][ k ], comm );
+               }
+            }
+         }
+      }
+   }
+
+/* Use an iterative inverse? */
+   set = TestIterInverse( this, status );
+   ival = set ? GetIterInverse( this, status ) : astGetIterInverse( this );
+   astWriteInt( channel, "IterInv", set, 0, ival, ival ? "Use an iterative inverse" : "Do not use an iterative inverse" );
+
+/* Max number of iterations for iterative inverse. */
+   set = TestNiterInverse( this, status );
+   ival = set ? GetNiterInverse( this, status ) : astGetNiterInverse( this );
+   astWriteInt( channel, "NiterInv", set, 0, ival, "Max number of iterations for iterative inverse" );
+
+/* Target relative error for iterative inverse. */
+   set = TestTolInverse( this, status );
+   dval = set ? GetTolInverse( this, status ) : astGetTolInverse( this );
+   astWriteDouble( channel, "TolInv", set, 0, dval, "Target relative error for iterative inverse" );
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAPolyMap and astCheckPolyMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(PolyMap,Mapping)
+astMAKE_CHECK(PolyMap)
+
+AstPolyMap *astPolyMap_( int nin, int nout, int ncoeff_f, const double coeff_f[],
+                         int ncoeff_i, const double coeff_i[], const char *options, int *status, ...){
+/*
+*++
+*  Name:
+c     astPolyMap
+f     AST_POLYMAP
+
+*  Purpose:
+*     Create a PolyMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "polymap.h"
+c     AstPolyMap *astPolyMap( int nin, int nout, int ncoeff_f, const double coeff_f[],
+c                             int ncoeff_i, const double coeff_i[],
+c                             const char *options, ... )
+f     RESULT = AST_POLYMAP( NIN, NOUT, NCOEFF_F, COEFF_F, NCOEFF_I, COEFF_I,
+f                           OPTIONS, STATUS )
+
+*  Class Membership:
+*     PolyMap constructor.
+
+*  Description:
+*     This function creates a new PolyMap and optionally initialises
+*     its attributes.
+*
+*     A PolyMap is a form of Mapping which performs a general polynomial
+*     transformation.  Each output coordinate is a polynomial function of
+*     all the input coordinates. The coefficients are specified separately
+*     for each output coordinate. The forward and inverse transformations
+*     are defined independantly by separate sets of coefficients. If no
+*     inverse transformation is supplied, an iterative method can be used
+*     to evaluate the inverse based only on the forward transformation.
+
+*  Parameters:
+c     nin
+f     NIN = INTEGER (Given)
+*        The number of input coordinates.
+c     nout
+f     NOUT = INTEGER (Given)
+*        The number of output coordinates.
+c     ncoeff_f
+f     NCOEFF_F = INTEGER (Given)
+*        The number of non-zero coefficients necessary to define the
+*        forward transformation of the PolyMap. If zero is supplied, the
+*        forward transformation will be undefined.
+c     coeff_f
+f     COEFF_F( * ) = DOUBLE PRECISION (Given)
+*        An array containing
+c        "ncoeff_f*( 2 + nin )" elements. Each group of "2 + nin"
+f        "NCOEFF_F*( 2 + NIN )" elements. Each group of "2 + NIN"
+*        adjacent elements describe a single coefficient of the forward
+*        transformation. Within each such group, the first element is the
+*        coefficient value; the next element is the integer index of the
+*        PolyMap output which uses the coefficient within its defining
+*        polynomial (the first output has index 1); the remaining elements
+*        of the group give the integer powers to use with each input
+*        coordinate value (powers must not be negative, and floating
+*        point values are rounded to the nearest integer).
+c        If "ncoeff_f" is zero, a NULL pointer may be supplied for "coeff_f".
+*
+*        For instance, if the PolyMap has 3 inputs and 2 outputs, each group
+*        consisting of 5 elements, A groups such as "(1.2, 2.0, 1.0, 3.0, 0.0)"
+*        describes a coefficient with value 1.2 which is used within the
+*        definition of output 2. The output value is incremented by the
+*        product of the coefficient value, the value of input coordinate
+*        1 raised to the power 1, and the value of input coordinate 2 raised
+*        to the power 3. Input coordinate 3 is not used since its power is
+*        specified as zero. As another example, the group "(-1.0, 1.0,
+*        0.0, 0.0, 0.0 )" describes adds a constant value -1.0 onto
+*        output 1 (it is a constant value since the power for every input
+*        axis is given as zero).
+*
+c        Each final output coordinate value is the sum of the "ncoeff_f" terms
+c        described by the "ncoeff_f" groups within the supplied array.
+f        Each final output coordinate value is the sum of the "NCOEFF_F" terms
+f        described by the "NCOEFF_F" groups within the supplied array.
+c     ncoeff_i
+f     NCOEFF_I = INTEGER (Given)
+*        The number of non-zero coefficients necessary to define the
+*        inverse transformation of the PolyMap. If zero is supplied, the
+*        inverse transformation will be undefined.
+c     coeff_i
+f     COEFF_I( * ) = DOUBLE PRECISION (Given)
+*        An array containing
+c        "ncoeff_i*( 2 + nout )" elements. Each group of "2 + nout"
+f        "NCOEFF_I*( 2 + NOUT )" elements. Each group of "2 + NOUT"
+*        adjacent elements describe a single coefficient of the inverse
+c        transformation, using the same schame as "coeff_f",
+f        transformation, using the same schame as "COEFF_F",
+*        except that "inputs" and "outputs" are transposed.
+c        If "ncoeff_i" is zero, a NULL pointer may be supplied for "coeff_i".
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new PolyMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new PolyMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astPolyMap()
+f     AST_POLYMAP = INTEGER
+*        A pointer to the new PolyMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS          /* Pointer to thread-specific global data */
+   AstPolyMap *new;            /* Pointer to new PolyMap */
+   va_list args;               /* Variable argument list */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise the PolyMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPolyMap( NULL, sizeof( AstPolyMap ), !class_init,
+                         &class_vtab, "PolyMap", nin, nout,
+                         ncoeff_f, coeff_f, ncoeff_i, coeff_i );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new PolyMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new PolyMap. */
+   return new;
+}
+
+AstPolyMap *astPolyMapId_( int nin, int nout, int ncoeff_f, const double coeff_f[],
+                           int ncoeff_i, const double coeff_i[], const char *options, ... ){
+/*
+*  Name:
+*     astPolyMapId_
+
+*  Purpose:
+*     Create a PolyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     AstPolyMap *astPolyMap( int nin, int nout, int ncoeff_f, const double coeff_f[],
+*                             int ncoeff_i, const double coeff_i[],
+*                             const char *options, ... )
+
+*  Class Membership:
+*     PolyMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astPolyMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astPolyMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astPolyMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astPolyMap_.
+
+*  Returned Value:
+*     The ID value associated with the new PolyMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPolyMap *new;              /* Pointer to new PolyMap */
+   va_list args;                 /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the PolyMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitPolyMap( NULL, sizeof( AstPolyMap ), !class_init,
+                         &class_vtab, "PolyMap", nin, nout,
+                         ncoeff_f, coeff_f, ncoeff_i, coeff_i );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new PolyMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new PolyMap. */
+   return astMakeId( new );
+}
+
+AstPolyMap *astInitPolyMap_( void *mem, size_t size, int init,
+                             AstPolyMapVtab *vtab, const char *name,
+                             int nin, int nout, int ncoeff_f, const double coeff_f[],
+                             int ncoeff_i, const double coeff_i[], int *status ){
+/*
+*+
+*  Name:
+*     astInitPolyMap
+
+*  Purpose:
+*     Initialise a PolyMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     AstPolyMap *astInitPolyMap( void *mem, size_t size, int init,
+*                                 AstPolyMapVtab *vtab, const char *name,
+*                                 int nin, int nout, int ncoeff_f,
+*                                 const double coeff_f[], int ncoeff_i,
+*                                 const double coeff_i[] )
+
+*  Class Membership:
+*     PolyMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new PolyMap object. It allocates memory (if necessary) to accommodate
+*     the PolyMap plus any additional data associated with the derived class.
+*     It then initialises a PolyMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a PolyMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the PolyMap is to be initialised.
+*        This must be of sufficient size to accommodate the PolyMap data
+*        (sizeof(PolyMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the PolyMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the PolyMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the PolyMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new PolyMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     nin
+*        The number of input coordinate values per point. This is the
+*        same as the number of columns in the matrix.
+*     nout
+*        The number of output coordinate values per point. This is the
+*        same as the number of rows in the matrix.
+*     ncoeff_f
+*        The number of non-zero coefficients necessary to define the
+*        forward transformation of the PolyMap. If zero is supplied, the
+*        forward transformation will be undefined.
+*     coeff_f
+*        An array containing "ncoeff_f*( 2 + nin )" elements. Each group
+*	 of "2 + nin" adjacent elements describe a single coefficient of
+*	 the forward transformation. Within each such group, the first
+*	 element is the coefficient value; the next element is the
+*	 integer index of the PolyMap output which uses the coefficient
+*	 within its defining polynomial (the first output has index 1);
+*	 the remaining elements of the group give the integer powers to
+*	 use with each input coordinate value (powers must not be
+*	 negative)
+*
+*        For instance, if the PolyMap has 3 inputs and 2 outputs, each group
+*        consisting of 5 elements, A groups such as "(1.2, 2.0, 1.0, 3.0, 0.0)"
+*        describes a coefficient with value 1.2 which is used within the
+*        definition of output 2. The output value is incremented by the
+*        product of the coefficient value, the value of input coordinate
+*        1 raised to the power 1, and the value of input coordinate 2 raised
+*        to the power 3. Input coordinate 3 is not used since its power is
+*        specified as zero. As another example, the group "(-1.0, 1.0,
+*        0.0, 0.0, 0.0 )" describes adds a constant value -1.0 onto
+*        output 1 (it is a constant value since the power for every input
+*        axis is given as zero).
+*
+*        Each final output coordinate value is the sum of the "ncoeff_f" terms
+*        described by the "ncoeff_f" groups within the supplied array.
+*     ncoeff_i
+*        The number of non-zero coefficients necessary to define the
+*        inverse transformation of the PolyMap. If zero is supplied, the
+*        inverse transformation will be undefined.
+*     coeff_i
+*        An array containing
+*        "ncoeff_i*( 2 + nout )" elements. Each group of "2 + nout"
+*        adjacent elements describe a single coefficient of the inverse
+*        transformation, using the same schame as "coeff_f", except that
+*        "inputs" and "outputs" are transposed.
+
+*  Returned Value:
+*     A pointer to the new PolyMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstPolyMap *new;              /* Pointer to new PolyMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitPolyMapVtab( vtab, name );
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the PolyMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstPolyMap *) astInitMapping( mem, size, 0,
+                                        (AstMappingVtab *) vtab, name,
+                                        nin, nout, 1, 1 );
+   if ( astOK ) {
+
+/* Initialise the PolyMap data. */
+/* ---------------------------- */
+
+/* First initialise the pointers in case of errors. */
+      new->ncoeff_f = NULL;
+      new->power_f = NULL;
+      new->coeff_f = NULL;
+      new->mxpow_f = NULL;
+
+      new->ncoeff_i = NULL;
+      new->power_i = NULL;
+      new->coeff_i = NULL;
+      new->mxpow_i = NULL;
+
+/* Store the forward transformation. */
+      StoreArrays( new, 1, ncoeff_f, coeff_f, status );
+
+/* Store the inverse transformation. */
+      StoreArrays( new, 0, ncoeff_i, coeff_i, status );
+
+/* Other class attributes. */
+      new->iterinverse = -INT_MAX;
+      new->niterinverse = -INT_MAX;
+      new->tolinverse = AST__BAD;
+      new->jacobian = NULL;
+
+/* If an error occurred, clean up by deleting the new PolyMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new PolyMap. */
+   return new;
+}
+
+AstPolyMap *astLoadPolyMap_( void *mem, size_t size,
+                             AstPolyMapVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadPolyMap
+
+*  Purpose:
+*     Load a PolyMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "polymap.h"
+*     AstPolyMap *astLoadPolyMap( void *mem, size_t size,
+*                                 AstPolyMapVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     PolyMap loader.
+
+*  Description:
+*     This function is provided to load a new PolyMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     PolyMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a PolyMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the PolyMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        PolyMap data (sizeof(PolyMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the PolyMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the PolyMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstPolyMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new PolyMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the PolyMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "PolyMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new PolyMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+/* Local Variables: */
+   AstPolyMap *new;              /* Pointer to the new PolyMap */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   int i;                        /* Loop index */
+   int iv;                       /* Vectorised keyword index */
+   int j;                        /* Loop index */
+   int k;                        /* Loop index */
+   int nin;                      /* No. of input coords */
+   int nout;                     /* No. of output coords */
+   int undef;                    /* Is the transformation undefined? */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this PolyMap. In this case the
+   PolyMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstPolyMap );
+      vtab = &class_vtab;
+      name = "PolyMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitPolyMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built PolyMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Get the number of inputs and outputs for the uninverted Mapping. */
+   nin = ( (AstMapping *) new )->nin;
+   nout = ( (AstMapping *) new )->nout;
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "PolyMap" );
+
+/* Allocate memory to hold the forward arrays. */
+      new->ncoeff_f = astMalloc( sizeof( int )*(size_t) nout );
+      new->mxpow_f = astMalloc( sizeof( int )*(size_t) nin );
+      new->power_f = astMalloc( sizeof( int ** )*(size_t) nout );
+      new->coeff_f = astMalloc( sizeof( double * )*(size_t) nout );
+      if( astOK ) {
+
+/* Assume the forward transformation is defined. */
+         undef = 0;
+
+/* Get the maximum power of each input axis value used by the forward
+   transformation. Set a flag "undef" if no values relating to the
+   forward transformation are found (this indicates that the forward
+   transformation is not defined). */
+         for( i = 0; i < nin && !undef; i++ ){
+            (void) sprintf( buff, "mpf%d", i + 1 );
+            (new->mxpow_f)[ i ] = astReadInt( channel, buff, INT_MAX );
+            if( (new->mxpow_f)[ i ] == INT_MAX ) undef = 1;
+         }
+
+/* Get the number of coefficients associated with each output of the forward
+   transformation. */
+         for( i = 0; i < nout && !undef; i++ ){
+            (void) sprintf( buff, "ncf%d", i + 1 );
+            (new->ncoeff_f)[ i ] = astReadInt( channel, buff, INT_MAX );
+            if( (new->ncoeff_f)[ i ] == INT_MAX ) undef = 1;
+         }
+
+/* Get the coefficient values used by the forward transformation. This
+   uses new style vectorised key names if available. Otherwise it uses
+   old style indexed names (which were superceded by vectorised names
+   because they are shorter and so work better with FitsChans). */
+         iv = 0;
+         for( i = 0; i < nout && !undef; i++ ){
+
+            (new->coeff_f)[ i ] = astMalloc( sizeof( double )*
+                                           (size_t) new->ncoeff_f[ i ] );
+            if( astOK ) {
+               for( j = 0; j < new->ncoeff_f[ i ]; j++ ){
+                  (void) sprintf( buff, "cf%d", ++iv );
+                  (new->coeff_f)[ i ][ j ] = astReadDouble( channel, buff, AST__BAD );
+                  if( (new->coeff_f)[ i ][ j ] == AST__BAD ) {
+                     (void) sprintf( buff, "cf%d_%d", i + 1, j + 1 );
+                     (new->coeff_f)[ i ][ j ] = astReadDouble( channel, buff, AST__BAD );
+                  }
+               }
+            }
+         }
+
+/* Get the input axis powers associated with each coefficient of the forward
+   transformation. */
+         iv = 0;
+         for( i = 0; i < nout && !undef; i++ ){
+            (new->power_f)[ i ] = astMalloc( sizeof( int * )*
+                                             (size_t) new->ncoeff_f[ i ] );
+            if( astOK ) {
+               for( j = 0; j < new->ncoeff_f[ i ]; j++ ){
+                  (new->power_f)[ i ][ j ] = astMalloc( sizeof( int )* (size_t) nin );
+                  if( astOK ) {
+                     for( k = 0; k < nin; k++ ){
+                        (void) sprintf( buff, "pf%d", ++iv );
+                        (new->power_f)[ i ][ j ][ k ] = astReadInt( channel, buff, 0 );
+                        if( (new->power_f)[ i ][ j ][ k ] == 0 ) {
+                           (void) sprintf( buff, "pf%d_%d_%d", i + 1, j + 1, k + 1 );
+                           (new->power_f)[ i ][ j ][ k ] = astReadInt( channel, buff, 0 );
+                        }
+                     }
+                  }
+               }
+            }
+         }
+
+/* Free the arrays if the forward transformation is undefined. */
+         if( undef ) {
+            new->ncoeff_f = astFree( new->ncoeff_f );
+            new->mxpow_f = astFree( new->mxpow_f );
+            new->power_f = astFree( new->power_f );
+            new->coeff_f = astFree( new->coeff_f );
+         }
+      }
+
+/* Allocate memory to hold the inverse arrays. */
+      new->ncoeff_i = astMalloc( sizeof( int )*(size_t) nin );
+      new->mxpow_i = astMalloc( sizeof( int )*(size_t) nout );
+      new->power_i = astMalloc( sizeof( int ** )*(size_t) nin );
+      new->coeff_i = astMalloc( sizeof( double * )*(size_t) nin );
+      if( astOK ) {
+
+/* Assume the inverse transformation is defined. */
+         undef = 0;
+
+/* Get the maximum power of each output axis value used by the inverse
+   transformation. Set a flag "undef" if no values relating to the
+   inverse transformation are found (this indicates that the inverse
+   transformation is not defined). */
+         for( i = 0; i < nout && !undef; i++ ){
+            (void) sprintf( buff, "mpi%d", i + 1 );
+            (new->mxpow_i)[ i ] = astReadInt( channel, buff, INT_MAX );
+            if( (new->mxpow_i)[ i ] == INT_MAX ) undef = 1;
+         }
+
+/* Get the number of coefficients associated with each input of the inverse
+   transformation. */
+         for( i = 0; i < nin && !undef; i++ ){
+            (void) sprintf( buff, "nci%d", i + 1 );
+            (new->ncoeff_i)[ i ] = astReadInt( channel, buff, INT_MAX );
+            if( (new->ncoeff_i)[ i ] == INT_MAX ) undef = 1;
+         }
+
+/* Get the coefficient values used by the inverse transformation. */
+         iv = 0;
+         for( i = 0; i < nin && !undef; i++ ){
+
+            (new->coeff_i)[ i ] = astMalloc( sizeof( double )*
+                                           (size_t) new->ncoeff_i[ i ] );
+            if( astOK ) {
+               for( j = 0; j < new->ncoeff_i[ i ]; j++ ){
+                  (void) sprintf( buff, "ci%d", ++iv );
+                  (new->coeff_i)[ i ][ j ] = astReadDouble( channel, buff, AST__BAD );
+                  if( (new->coeff_i)[ i ][ j ] == AST__BAD ) {
+                     (void) sprintf( buff, "ci%d_%d", i + 1, j + 1 );
+                     (new->coeff_i)[ i ][ j ] = astReadDouble( channel, buff, AST__BAD );
+                  }
+               }
+            }
+         }
+
+/* Get the output axis powers associated with each coefficient of the inverse
+   transformation. */
+         iv = 0;
+         for( i = 0; i < nin && !undef; i++ ){
+            (new->power_i)[ i ] = astMalloc( sizeof( int * )*
+                                             (size_t) new->ncoeff_i[ i ] );
+            if( astOK ) {
+               for( j = 0; j < new->ncoeff_i[ i ]; j++ ){
+                  (new->power_i)[ i ][ j ] = astMalloc( sizeof( int )* (size_t) nout );
+                  if( astOK ) {
+                     for( k = 0; k < nout; k++ ){
+                        (void) sprintf( buff, "pi%d", ++iv );
+                        (new->power_i)[ i ][ j ][ k ] = astReadInt( channel, buff, 0 );
+                        if( (new->power_i)[ i ][ j ][ k ] == 0 ) {
+                           (void) sprintf( buff, "pi%d_%d_%d", i + 1, j + 1, k + 1 );
+                           (new->power_i)[ i ][ j ][ k ] = astReadInt( channel, buff, 0 );
+                        }
+                     }
+                  }
+               }
+            }
+         }
+
+/* Free the arrays if the inverse transformation is undefined. */
+         if( undef ) {
+            new->ncoeff_i = astFree( new->ncoeff_i );
+            new->mxpow_i = astFree( new->mxpow_i );
+            new->power_i = astFree( new->power_i );
+            new->coeff_i = astFree( new->coeff_i );
+         }
+      }
+
+/* Whether to use an iterative inverse transformation. */
+      new->iterinverse = astReadInt( channel, "iterinv", -INT_MAX );
+      if ( TestIterInverse( new, status ) ) SetIterInverse( new, new->iterinverse, status );
+
+/* Max number of iterations for iterative inverse transformation. */
+      new->niterinverse = astReadInt( channel, "niterinv", -INT_MAX );
+      if ( TestNiterInverse( new, status ) ) SetNiterInverse( new, new->niterinverse, status );
+
+/* Target relative error for iterative inverse transformation. */
+      new->tolinverse = astReadDouble( channel, "tolinv", AST__BAD );
+      if ( TestTolInverse( new, status ) ) SetTolInverse( new, new->tolinverse, status );
+
+/* The Jacobian of the PolyMap's forward transformation has not yet been
+   found. */
+      new->jacobian = NULL;
+
+/* If an error occurred, clean up by deleting the new PolyMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new PolyMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+AstPolyMap *astPolyTran_( AstPolyMap *this, int forward, double acc,
+                          double maxacc, int maxorder, const double *lbnd,
+                          const double *ubnd, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,PolyMap,PolyTran))( this, forward, acc,
+                                                maxacc, maxorder, lbnd,
+                                                ubnd, status );
+}
+
+
+
+
diff --git a/ast/polymap.h b/ast/polymap.h
new file mode 100644
index 0000000..65138b8
--- /dev/null
+++ b/ast/polymap.h
@@ -0,0 +1,354 @@
+#if !defined( POLYMAP_INCLUDED ) /* Include this file only once */
+#define POLYMAP_INCLUDED
+/*
+*+
+*  Name:
+*     polymap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the PolyMap class.
+
+*  Invocation:
+*     #include "polymap.h"
+
+*  Description:
+*     This include file defines the interface to the PolyMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     A PolyMap is a form of Mapping which performs a general polynomial
+*     transformation.  Each output coordinate is a polynomial function of
+*     all the input coordinates. The coefficients are specified separately
+*     for each output coordinate. The forward and inverse transformations
+*     are defined independantly by separate sets of coefficients.
+
+*  Inheritance:
+*     The PolyMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astTransform
+*           Apply a PolyMap to transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAPolyMap
+*           Test class membership.
+*        astPolyMap
+*           Create a PolyMap.
+*
+*     Protected:
+*        astCheckPolyMap
+*           Validate class membership.
+*        astInitPolyMap
+*           Initialise a PolyMap.
+*        astInitPolyMapVtab
+*           Initialise the virtual function table for the PolyMap class.
+*        astLoadPolyMap
+*           Load a PolyMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstPolyMap
+*           PolyMap object type.
+*
+*     Protected:
+*        AstPolyMapVtab
+*           PolyMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     28-SEP-2003 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* PolyMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstPolyMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int *ncoeff_f;             /* No. of coeffs for each forward polynomial */
+   int *mxpow_f;              /* Max power of each i/p axis for each forward polynomial */
+   int ***power_f;            /* Pointer to i/p powers for all forward coefficients */
+   double **coeff_f;          /* Pointer to values of all forward coefficients */
+   int *ncoeff_i;             /* No. of coeffs for each inverse polynomial */
+   int *mxpow_i;              /* Max power of each i/p axis for each inverse polynomial */
+   int ***power_i;            /* Pointer to i/p powers for all inverse coefficients */
+   double **coeff_i;          /* Pointer to values of all inverse coefficients */
+   int iterinverse;           /* Use an iterative inverse? */
+   int niterinverse;          /* Max number of iterations for iterative inverse */
+   double tolinverse;         /* Target relative error for iterative inverse */
+   struct AstPolyMap **jacobian;/* PolyMaps defining Jacobian of forward transformation */
+} AstPolyMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstPolyMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstPolyMap *(* PolyTran)( AstPolyMap *, int, double, double, int, const double *, const double *, int * );
+   int (*GetIterInverse)( AstPolyMap *, int * );
+   int (* TestIterInverse)( AstPolyMap *, int * );
+   void (* ClearIterInverse)( AstPolyMap *, int * );
+   void (* SetIterInverse)( AstPolyMap *, int, int * );
+
+   int (*GetNiterInverse)( AstPolyMap *, int * );
+   int (* TestNiterInverse)( AstPolyMap *, int * );
+   void (* ClearNiterInverse)( AstPolyMap *, int * );
+   void (* SetNiterInverse)( AstPolyMap *, int, int * );
+
+   double (*GetTolInverse)( AstPolyMap *, int * );
+   int (* TestTolInverse)( AstPolyMap *, int * );
+   void (* ClearTolInverse)( AstPolyMap *, int * );
+   void (* SetTolInverse)( AstPolyMap *, double, int * );
+
+} AstPolyMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstPolyMapGlobals {
+   AstPolyMapVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ AST__GETATTRIB_BUFF_LEN + 1 ];
+} AstPolyMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitPolyMapGlobals_( AstPolyMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(PolyMap)          /* Check class membership */
+astPROTO_ISA(PolyMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstPolyMap *astPolyMap_( int, int, int, const double[], int, const double[], const char *, int *, ...);
+#else
+AstPolyMap *astPolyMapId_( int, int, int, const double[], int, const double[], const char *, ... )__attribute__((format(printf,7,8)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstPolyMap *astInitPolyMap_( void *, size_t, int, AstPolyMapVtab *, const char *, int, int, int, const double[], int, const double[], int * );
+
+/* Vtab initialiser. */
+void astInitPolyMapVtab_( AstPolyMapVtab *, const char *, int * );
+
+/* Loader. */
+AstPolyMap *astLoadPolyMap_( void *, size_t, AstPolyMapVtab *,
+                                 const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+AstPolyMap *astPolyTran_( AstPolyMap *, int, double, double, int, const double *, const double *, int * );
+
+# if defined(astCLASS)           /* Protected */
+   int astGetIterInverse_( AstPolyMap *, int * );
+   int astTestIterInverse_( AstPolyMap *, int * );
+   void astClearIterInverse_( AstPolyMap *, int * );
+   void astSetIterInverse_( AstPolyMap *, int, int * );
+
+   int astGetNiterInverse_( AstPolyMap *, int * );
+   int astTestNiterInverse_( AstPolyMap *, int * );
+   void astClearNiterInverse_( AstPolyMap *, int * );
+   void astSetNiterInverse_( AstPolyMap *, int, int * );
+
+   double astGetTolInverse_( AstPolyMap *, int * );
+   int astTestTolInverse_( AstPolyMap *, int * );
+   void astClearTolInverse_( AstPolyMap *, int * );
+   void astSetTolInverse_( AstPolyMap *, double, int * );
+#endif
+
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckPolyMap(this) astINVOKE_CHECK(PolyMap,this,0)
+#define astVerifyPolyMap(this) astINVOKE_CHECK(PolyMap,this,1)
+
+/* Test class membership. */
+#define astIsAPolyMap(this) astINVOKE_ISA(PolyMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astPolyMap astINVOKE(F,astPolyMap_)
+#else
+#define astPolyMap astINVOKE(F,astPolyMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitPolyMap(mem,size,init,vtab,name,nin,nout,ncoeff_f,coeff_f,ncoeff_i,coeff_i) \
+astINVOKE(O,astInitPolyMap_(mem,size,init,vtab,name,nin,nout,ncoeff_f,coeff_f,ncoeff_i,coeff_i,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitPolyMapVtab(vtab,name) astINVOKE(V,astInitPolyMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadPolyMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadPolyMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckPolyMap to validate PolyMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#define astPolyTran(this,forward,acc,maxacc,maxorder,lbnd,ubnd) \
+astINVOKE(O,astPolyTran_(astCheckPolyMap(this),forward,acc,maxacc,maxorder,lbnd,ubnd,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+
+#define astClearIterInverse(this) \
+        astINVOKE(V,astClearIterInverse_(astCheckPolyMap(this),STATUS_PTR))
+#define astGetIterInverse(this) \
+        astINVOKE(V,astGetIterInverse_(astCheckPolyMap(this),STATUS_PTR))
+#define astSetIterInverse(this,value) \
+        astINVOKE(V,astSetIterInverse_(astCheckPolyMap(this),value,STATUS_PTR))
+#define astTestIterInverse(this) \
+        astINVOKE(V,astTestIterInverse_(astCheckPolyMap(this),STATUS_PTR))
+
+#define astClearNiterInverse(this) \
+        astINVOKE(V,astClearNiterInverse_(astCheckPolyMap(this),STATUS_PTR))
+#define astGetNiterInverse(this) \
+        astINVOKE(V,astGetNiterInverse_(astCheckPolyMap(this),STATUS_PTR))
+#define astSetNiterInverse(this,value) \
+        astINVOKE(V,astSetNiterInverse_(astCheckPolyMap(this),value,STATUS_PTR))
+#define astTestNiterInverse(this) \
+        astINVOKE(V,astTestNiterInverse_(astCheckPolyMap(this),STATUS_PTR))
+
+#define astClearTolInverse(this) \
+        astINVOKE(V,astClearTolInverse_(astCheckPolyMap(this),STATUS_PTR))
+#define astGetTolInverse(this) \
+        astINVOKE(V,astGetTolInverse_(astCheckPolyMap(this),STATUS_PTR))
+#define astSetTolInverse(this,value) \
+        astINVOKE(V,astSetTolInverse_(astCheckPolyMap(this),value,STATUS_PTR))
+#define astTestTolInverse(this) \
+        astINVOKE(V,astTestTolInverse_(astCheckPolyMap(this),STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/prism.c b/ast/prism.c
new file mode 100644
index 0000000..f4e26c9
--- /dev/null
+++ b/ast/prism.c
@@ -0,0 +1,4448 @@
+/*
+*class++
+*  Name:
+*     Prism
+
+*  Purpose:
+*     An extrusion of a region into higher dimensions.
+
+*  Constructor Function:
+c     astPrism
+f     AST_PRISM
+
+*  Description:
+*     A Prism is a Region which represents an extrusion of an existing Region
+*     into one or more orthogonal dimensions (specified by another Region).
+*     If the Region to be extruded has N axes, and the Region defining the
+*     extrusion has M axes, then the resulting Prism will have (M+N) axes.
+*     A point is inside the Prism if the first N axis values correspond to
+*     a point inside the Region being extruded, and the remaining M axis
+*     values correspond to a point inside the Region defining the extrusion.
+*
+*     As an example, a cylinder can be represented by extruding an existing
+*     Circle, using an Interval to define the extrusion. Ih this case, the
+*     Interval would have a single axis and would specify the upper and
+*     lower limits of the cylinder along its length.
+
+*  Inheritance:
+*     The Prism class inherits from the Region class.
+
+*  Attributes:
+*     The Prism class does not define any new attributes beyond those
+*     which are applicable to all Regions.
+
+*  Functions:
+c     The Prism class does not define any new functions beyond those
+f     The Prism class does not define any new routines beyond those
+*     which are applicable to all Regions.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     17-DEC-2004 (DSB):
+*        Original version.
+*     11-MAY-2005 (DSB):
+*        Overlap modified to allow testing of overlap between prisms and
+*        intervals.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     9-OCT-2007 (DSB):
+*        Guard against all axes being extrusion axes in EquivPrism.
+*     20-JAN-2009 (DSB):
+*        Over-ride astRegBasePick.
+*     22-JAN-2009 (DSB):
+*        - Allow any class of Region to be used to define the extrusion axes.
+*        - Over-ride the astMapList method.
+*     19-MAR-2009 (DSB):
+*        Over-ride the astDecompose method.
+*     14-AUG-2014 (DSB):
+*        Over-ride the astGetRegionBounds method.
+*     9-SEP-2014 (DSB):
+*        Record the pointer to the Prism implementation of RegBaseMesh
+*        within the class virtual function table.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Prism
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "region.h"              /* Regions (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "prism.h"               /* Interface definition for this class */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "cmpframe.h"            /* Compound Frames */
+#include "unitmap.h"             /* Unit Mappings */
+#include "interval.h"            /* Axis intervals */
+#include "pointlist.h"           /* Points within Frames */
+#include "permmap.h"             /* Axis permutations */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+#include <limits.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstMapping *(* parent_simplify)( AstMapping *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstRegion *(* parent_getdefunc)( AstRegion *, int * );
+static double (*parent_getfillfactor)( AstRegion *, int * );
+static int (* parent_equal)( AstObject *, AstObject *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+static int (* parent_maplist)( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int (* parent_overlapx)( AstRegion *, AstRegion *, int * );
+static void (* parent_clearclosed)( AstRegion *, int * );
+static void (* parent_clearmeshsize)( AstRegion *, int * );
+static void (* parent_setclosed)( AstRegion *, int, int * );
+static void (* parent_setmeshsize)( AstRegion *, int, int * );
+static void (* parent_setregfs)( AstRegion *, AstFrame *, int * );
+static void (*parent_getregionbounds)( AstRegion *, double *, double *, int * );
+static void (*parent_regclearattrib)( AstRegion *, const char *, char **, int * );
+static void (*parent_regsetattrib)( AstRegion *, const char *, char **, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Prism)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Prism,Class_Init)
+#define class_vtab astGLOBAL(Prism,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstPrismVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstPrism *astPrismId_( void *, void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstRegion *GetDefUnc( AstRegion *, int * );
+static AstRegion *RegBasePick( AstRegion *this, int, const int *, int * );
+static double *RegCentre( AstRegion *this, double *, double **, int, int, int * );
+static double GetFillFactor( AstRegion *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetBounded( AstRegion *, int * );
+static int GetObjSize( AstObject *, int * );
+static int MapList( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int Overlap( AstRegion *, AstRegion *, int * );
+static int OverlapX( AstRegion *, AstRegion *, int * );
+static int RegPins( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+static void ClearClosed( AstRegion *, int * );
+static void ClearMeshSize( AstRegion *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Decompose( AstMapping *, AstMapping **, AstMapping **, int *, int *, int *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void GetRegions( AstPrism *, AstRegion **, AstRegion **, int *, int * );
+static void GetRegionBounds( AstRegion *, double *, double *, int * );
+static void RegBaseBox( AstRegion *, double *, double *, int * );
+static void RegClearAttrib( AstRegion *, const char *, char **, int * );
+static void RegSetAttrib( AstRegion *, const char *, char **, int * );
+static void SetClosed( AstRegion *, int, int * );
+static void SetMeshSize( AstRegion *, int, int * );
+static void SetRegFS( AstRegion *, AstFrame *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+/* Member functions. */
+/* ================= */
+AstRegion *astConvertToPrism_( AstRegion *this, int *status ) {
+/*
+*+
+*  Name:
+*     astConvertToPrism
+
+*  Purpose:
+*     Convert a supplied Region into a Prism if possible.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     AstRegion *astConvertToPrism( AstRegion *this, int *status )
+
+*  Description:
+*     This function attempts to split the supplied Region into two
+*     regions defined within separate coordinate system. If this is
+*     possible, and if either one of the two resulting Regions can be
+*     simplified, then the two simplified Regions are joined into a Prism
+*     equivalent to the supplied Region. The Prism is then simplified and
+*     returned.
+*
+*     If the supplied Region cannot be split into two components, or if
+*     neither of the two components can eb simplified, then a clone of the
+*     supplied Region pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the equivalent simplified Prism, or a clone of the
+*     supplied Region pointer.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *frm;                /* Current Frame in supplied Region */
+   AstFrame *pickfrm1;           /* Frame formed by picking current subset of axes */
+   AstFrame *pickfrm2;           /* Frame formed by picking all other axes */
+   AstMapping *junk;             /* Unused Mapping pointer */
+   AstMapping *map;              /* Base -> current Mapping */
+   AstPrism *prism;              /* Prism combining all axes */
+   AstPrism *newprism;           /* Prism combining all axes, in original Frame */
+   AstRegion *result;            /* Result pointer to return */
+   AstRegion *sp1;               /* Simplified region spanning selected axes */
+   AstRegion *sp2;               /* Simplified region spanning unselected axes */
+   AstUnitMap *um;               /* A UnitMap */
+   int *ax;                      /* Pointer to array of selecte axis indices */
+   int *perm;                    /* Axis permutation array */
+   int axis;                     /* Axis index */
+   int bitmask;                  /* Integer with set bits for selected axes */
+   int i;                        /* Loop index */
+   int mask;                     /* Integer with a set bit at current axis */
+   int nax;                      /* Number of selected axes */
+   int nin;                      /* No. of base Frame axes (Mapping inputs) */
+   int nout;                     /* No. of current Frame axes (Mapping outputs) */
+   int topmask;                  /* Integer that selects all axes */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the Mapping from base to current Frame. */
+   map = astGetMapping( this->frameset, AST__BASE, AST__CURRENT );
+
+/* Get the number of inputs and outputs for the Mapping. */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+
+/* Allocate memory to hold the indices of the current Frame axes in the
+   current subset */
+   ax = astMalloc( sizeof( int )* nout );
+   if( ax ) {
+
+/* We need to scan through all possible subsets of the current Frame
+   axes, looking for a subset that results in the Region being split.
+   We use the binary pattern of bits in "bitmask" to indicate if the
+   corresponding axes should be included in the subset of axes.
+   Loop round all possible combinations, until a combination is found
+   that results in a Prism being formed. */
+      topmask = pow( 2, nout );
+      for( bitmask = 1; bitmask < topmask  && !result; bitmask++ ) {
+
+/* Store the indices of the axes forming the current subset. */
+         nax = 0;
+         mask = 1;
+         for( axis = 0; axis < nout; axis++  ) {
+            if( bitmask & mask ) ax[ nax++ ] = axis;
+            mask <<= 1;
+         }
+
+/* See if the current subset of current Frame axes can be split off from
+   the Region. If it can, the Frame pointer returned by astPickAxes will identify
+   a Region. */
+         pickfrm1 = astPickAxes( this, nax, ax, &junk );
+         junk = astAnnul( junk );
+         if( astIsARegion( pickfrm1 ) ) {
+
+/* Check that the remaining (unselected) axes can also be picked into a
+   new Region. */
+            nax = 0;
+            mask = 1;
+            for( axis = 0; axis < nout; axis++  ) {
+               if( ( bitmask & mask ) == 0 ) ax[ nax++ ] = axis;
+               mask <<= 1;
+            }
+
+            pickfrm2 = astPickAxes( this, nax, ax, &junk );
+            junk = astAnnul( junk );
+            if( astIsARegion( pickfrm2 ) ) {
+
+/* See if either of these picked Regions can be simplified. */
+               sp1 = astSimplify( pickfrm1 );
+               sp2 = astSimplify( pickfrm2 );
+               if( (AstFrame *) sp1 != pickfrm1 ||
+                   (AstFrame *) sp2 != pickfrm2 ) {
+
+/* If so form a Prism containing the simplified Regions. */
+                  prism = astPrism( sp1, sp2, " ", status );
+
+/* Permute the axes of the Prism so that they are in the same order as
+   in the Box. */
+                  perm = astMalloc( sizeof( int )*nout );
+                  if( perm ) {
+
+                     for( i = 0; i < nout; i++ ) perm[ i ] = -1;
+
+                     for( i = 0; i < nax; i++ ) {
+                        perm[ ax[ i ] ] = i + ( nout - nax );
+                     }
+
+                     nax = 0;
+                     for( i = 0; i < nout; i++ ) {
+                        if( perm[ i ] == -1 ) perm[ i ] = nax++;
+                     }
+
+                     astPermAxes( prism, perm );
+                     perm = astFree( perm );
+                  }
+
+/* Put the original current Frame back in (in place of the CmpFrame
+   containined in the Prism). */
+                  frm = astGetFrame( this->frameset, AST__CURRENT );
+                  um = astUnitMap( nout, " ", status );
+                  newprism = astMapRegion( prism, um, frm );
+                  um = astAnnul( um );
+                  frm = astAnnul( frm );
+
+/* Attempt to simplify the Prism. */
+                  result = astSimplify( newprism );
+
+/* Free resources */
+                  prism = astAnnul( prism );
+                  newprism = astAnnul( newprism );
+               }
+
+               sp1 = astAnnul( sp1 );
+               sp2 = astAnnul( sp2 );
+            }
+            pickfrm2 = astAnnul( pickfrm2 );
+         }
+
+         pickfrm1 = astAnnul( pickfrm1 );
+      }
+
+      ax = astFree( ax );
+   }
+
+   map = astAnnul( map );
+
+/* If no Prism could be made, return a clone of the supplied Region
+   pointer. */
+   if( !result ) result = astClone( this );
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void Decompose( AstMapping *this_mapping, AstMapping **map1,
+                       AstMapping **map2, int *series, int *invert1,
+                       int *invert2, int *status ) {
+/*
+*
+*  Name:
+*     Decompose
+
+*  Purpose:
+*     Decompose a Prism into two component Regions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void Decompose( AstMapping *this, AstMapping **map1,
+*                     AstMapping **map2, int *series,
+*                     int *invert1, int *invert2, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the protected astDecompose
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns pointers to two Mappings which, when applied
+*     either in series or parallel, are equivalent to the supplied Mapping.
+*
+*     Since the Frame class inherits from the Mapping class, Frames can
+*     be considered as special types of Mappings and so this method can
+*     be used to decompose either CmpMaps, CmpFrames, CmpRegions or Prisms.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     map1
+*        Address of a location to receive a pointer to first component
+*        Mapping.
+*     map2
+*        Address of a location to receive a pointer to second component
+*        Mapping.
+*     series
+*        Address of a location to receive a value indicating if the
+*        component Mappings are applied in series or parallel. A non-zero
+*        value means that the supplied Mapping is equivalent to applying map1
+*        followed by map2 in series. A zero value means that the supplied
+*        Mapping is equivalent to applying map1 to the lower numbered axes
+*        and map2 to the higher numbered axes, in parallel.
+*     invert1
+*        The value of the Invert attribute to be used with map1.
+*     invert2
+*        The value of the Invert attribute to be used with map2.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Any changes made to the component rames using the returned
+*     pointers will be reflected in the supplied CmpFrame.
+
+*-
+*/
+
+/* Local Variables: */
+   AstPrism *this;              /* Pointer to Prism structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the CmpMap structure. */
+   this = (AstPrism *) this_mapping;
+
+/* The components Frames of a Prism are considered to be parallel
+   Mappings. */
+   if( series ) *series = 0;
+
+/* The Frames are returned in their original order whether or not the
+   Prism has been inverted. */
+   if( map1 ) *map1 = astClone( this->region1 );
+   if( map2 ) *map2 = astClone( this->region2 );
+
+/* The invert flags dont mean anything for a Region, but we return them
+   anyway. If the Prism has been inverted, return inverted Invert flags. */
+   if( astGetInvert( this ) ) {
+      if( invert1 ) *invert1 = astGetInvert( this->region1 ) ? 0 : 1;
+      if( invert2 ) *invert2 = astGetInvert( this->region2 ) ? 0 : 1;
+
+/* If the Prism has not been inverted, return the current Invert flags. */
+   } else {
+      if( invert1 ) *invert1 = astGetInvert( this->region1 );
+      if( invert2 ) *invert2 = astGetInvert( this->region2 );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two Objects are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     int Equal( AstObject *this_object, AstObject *that_object, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astEqual protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two Prisms are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Prism.
+*     that
+*        Pointer to the second Prism.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the Prisms are equivalent, zero otherwise.
+
+*  Notes:
+*     - The Prisms are equivalent if their component Regions are
+*     equivalent and if they have the same boolean operation, negation
+*     and closed flags.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPrism *that;
+   AstPrism *this;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the Equal method inherited from the parent Region class. This checks
+   that the Objects are both of the same class, and have the same Negated
+   and Closed flags (amongst other things). */
+   if( (*parent_equal)( this_object, that_object, status ) ) {
+
+/* Obtain pointers to the two Prism structures. */
+      this = (AstPrism *) this_object;
+      that = (AstPrism *) that_object;
+
+/* Test their first component Regions for equality. */
+      if( astEqual( this->region1, that->region1 ) ) {
+
+/* Test their second component Regions for equality. */
+         if( astEqual( this->region2, that->region2 ) ) result = 1;
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+/*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Define a function to set an attribute value for a Prism.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "prism.h"
+*     MAKE_SET(attribute,lattribute,type)
+
+*  Class Membership:
+*     Defined by the Prism class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Set<Attribute>( AstRegion *this, <Type> value )
+*
+*     that sets the value of a specified Region attribute in the parent
+*     Region structure and also in the component Regions.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     lattribute
+*        Name of the attribute, all in lower case.
+*     type
+*        The C type of the attribute.
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attribute,lattribute,type) \
+static void Set##attribute( AstRegion *this_region, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstPrism *this;         /* Pointer to the Prism structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Use the parent method to set the value in the parent Region structure. */ \
+   (*parent_set##lattribute)( this_region, value, status ); \
+\
+/* Also set the value in the two component Regions. */ \
+   this = (AstPrism *) this_region; \
+   astSet##attribute( this->region1, value ); \
+   astSet##attribute( this->region2, value ); \
+}
+
+/* Use the above macro to create accessors for the MeshSize and Closed
+   attributes. */
+MAKE_SET(MeshSize,meshsize,int)
+MAKE_SET(Closed,closed,int)
+
+/* Undefine the macro. */
+#undef MAKE_SET
+
+/*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Define a function to clear an attribute value for a Prism.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "prism.h"
+*     MAKE_CLEAR(attribute,lattribute)
+
+*  Class Membership:
+*     Defined by the Prism class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Clear<Attribute>( AstRegion *this )
+*
+*     that sets the value of a specified Region attribute in the parent
+*     Region structure and also in the component Regions.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     lattribute
+*        Name of the attribute, all in lower case.
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attribute,lattribute) \
+static void Clear##attribute( AstRegion *this_region, int *status ) { \
+\
+/* Local Variables: */ \
+   AstPrism *this;         /* Pointer to the Prism structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Use the parent method to clear the value in the parent Region structure. */ \
+   (*parent_clear##lattribute)( this_region, status ); \
+\
+/* Also clear the value in the two component Regions. */ \
+   this = (AstPrism *) this_region; \
+   astClear##attribute( this->region1 ); \
+   astClear##attribute( this->region2 ); \
+}
+
+/* Use the above macro to create accessors for the MeshSize and Closed
+   attributes. */
+MAKE_CLEAR(MeshSize,meshsize)
+MAKE_CLEAR(Closed,closed)
+
+/* Undefine the macro. */
+#undef MAKE_CLEAR
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied Prism,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the Prism.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPrism *this;         /* Pointer to Prism structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the Prism structure. */
+   this = (AstPrism *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astGetObjSize( this->region1 );
+   result += astGetObjSize( this->region2 );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetBounded( AstRegion *this_region, int *status ) {
+/*
+*  Name:
+*     GetBounded
+
+*  Purpose:
+*     Is the Region bounded?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     int GetBounded( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Prism method (over-rides the astGetBounded method inherited from
+*     the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the Region is bounded.
+*     The implementation provided by the base Region class is suitable
+*     for Region sub-classes representing the inside of a single closed
+*     curve (e.g. Circle, Ellipse, Box, etc). Other sub-classes (such as
+*     Prism, PointList, etc ) may need to provide their own
+*     implementations.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the Region is bounded. Zero otherwise.
+
+*/
+
+/* Local Variables: */
+   AstPrism *this;            /* Pointer to Prism structure */
+   AstRegion *reg1;           /* Pointer to first component Region */
+   AstRegion *reg2;           /* Pointer to second component Region */
+   int neg;                   /* Negated flag to use with the Prism */
+   int reg1b;                 /* Is the first component Region bounded?*/
+   int reg2b;                 /* Is the second component Region bounded?*/
+   int result;                /* Returned result */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Prism structure. */
+   this = (AstPrism *) this_region;
+
+/* Get the component Regions, and the Negated value for the Prism. The
+   returned Regions represent a region within the base Frame of the FrameSet
+   encapsulated by the parent Region structure. */
+   GetRegions( this, &reg1, &reg2, &neg, status );
+
+/* If the Prism has been inverted, temporarily invert the components. */
+   if( neg ) {
+      astNegate( reg1 );
+      astNegate( reg2 );
+   }
+
+/* See if either of the component Regions is bounded. */
+   reg1b = astGetBounded( reg1 );
+   reg2b = astGetBounded( reg2 );
+
+/* If the Prism has been inverted, re-invert the components to bring them
+   back to their original states. */
+   if( neg ) {
+      astNegate( reg1 );
+      astNegate( reg2 );
+   }
+
+/* The Prism is bounded only if both of the component Regions are bounded. */
+   result = ( reg1b && reg2b );
+
+/* Free resources. */
+   reg1 = astAnnul( reg1 );
+   reg2 = astAnnul( reg2 );
+
+/* Return zero if an error occurred. */
+   if( !astOK ) result = 0;
+
+/* Return the required pointer. */
+   return result;
+}
+
+static double GetFillFactor( AstRegion *this_region, int *status ) {
+/*
+*  Name:
+*     GetFillFactor
+
+*  Purpose:
+*     Obtain the value of the FillFactor attribute for a Prism.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     double GetFillFactor( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astGetFillFactor method inherited
+*     from the Region class).
+
+*  Description:
+*     This function returns the value of the FillFactor attribute for a
+*     Prism.  A suitable default value is returned if no value has
+*     previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the Prism.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The FillFactor value to use.
+
+*/
+
+/* Local Variables: */
+   AstPrism *this;
+   double f1;
+   double f2;
+   double result;
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Obtain a pointer to the Prism structure. */
+   this = (AstPrism *) this_region;
+
+/* See if a FillFactor value has been set. If so, use the parent
+   astGetFillFactor  method to obtain it. */
+   if ( astTestFillFactor( this ) ) {
+      result = (*parent_getfillfactor)( this_region, status );
+
+/* Otherwise, we will generate a default value equal to the product of
+   the FillFactor values of the two component Regions. */
+   } else {
+      f1 = astGetFillFactor( this->region1 );
+      f2 = astGetFillFactor( this->region2 );
+      if( f1 != AST__BAD && f2 != AST__BAD ) result = f1*f2;
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static void GetRegionBounds( AstRegion *this_region, double *lbnd,
+                             double *ubnd, int *status ){
+/*
+*
+*  Name:
+*     GetRegionBounds
+
+*  Purpose:
+*     Returns the bounding box of Prism.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "cmpregion.h"
+*     void GetRegionBounds( AstRegion *this_region, double *lbnd,
+*                           double *ubnd, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the protected astGetRegionBounds
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower limits of a box which just
+*     encompasses the supplied Prism. The limits are returned as axis
+*     values within the Frame represented by the Prism. The value of the
+*     Negated attribute is ignored (i.e. it is assumed that the Prism has
+*     not been negated).
+
+*  Parameters:
+*     this
+*        Pointer to the Prism.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Prism. It should have at least as many elements as
+*        there are axes in the Prism. If an axis has no lower limit, the
+*        returned value will be the largest possible negative value.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Prism. It should have at least as many elements as
+*        there are axes in the Prism. If an axis has no upper limit, the
+*        returned value will be the largest possible positive value.
+
+*  Notes:
+*    - The value of the Negated attribute is ignored (i.e. it is assumed that
+*    the Prism has not been negated).
+*    - If an axis has no extent on an axis then the lower limit will be
+*    returned larger than the upper limit. Note, this is different to an
+*    axis which has a constant value (in which case both lower and upper
+*    limit will be returned set to the constant value).
+*    - If the bounds on an axis cannot be determined, AST__BAD is returned for
+*    both upper and lower bounds
+*    - The implementation of this method for Prisms attempts to split the Prism
+*    into two separate Regions spanning indepenent sets of axes, and then uses
+*    the astGetRegionBouinds method to get the bounds on these two Regions. Care
+*    has to be taken because the Prism may have been remapped into a different
+*    Frame since it was created.
+
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm1;             /* Frame spanning current axes for 1st component Region */
+   AstFrame *cfrm2;             /* Frame spanning current axes for 2nd component Region */
+   AstFrame *cfrm;              /* Current Frame for total Prism */
+   AstMapping *map1;            /* Base->Current Mapping for axes of 1st component Region */
+   AstMapping *map2;            /* Base->Current Mapping for axes of 2nd component Region */
+   AstMapping *map;             /* Case->Current mapping for total Prism */
+   AstPrism *this;              /* Pointer to Prism structure */
+   AstRegion *reg1;             /* 1st component Region Mapping into Prism's Frame */
+   AstRegion *reg2;             /* 2nd component Region Mapping into Prism's Frame */
+   int *baxes;                  /* Indicies of Base Frame axes to be picked */
+   int *caxes;                  /* Indicies of Current Frame axes to be picked */
+   int iax;                     /* Axis index */
+   int nax1;                    /* Number of axes in first component Region */
+   int nax1_out;                /* Number of current Frame axes in first component Region */
+   int nax2;                    /* Number of axes in second component Region */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Prism structure. */
+   this = (AstPrism *) this_region;
+
+/* Initialise */
+   cfrm1 = NULL;
+   cfrm2 = NULL;
+   map1 = NULL;
+   map2 = NULL;
+   nax1_out = 0;
+
+/* The number of base-frame axes spanned by the two component Regions. */
+   nax1 = astGetNaxes( this->region1 );
+   nax2 = astGetNaxes( this->region2 );
+
+/* Work space to hold the base frame indices for a single component
+   FrameSet. */
+   baxes = astMalloc( ( nax1 + nax2 )*sizeof( int ) );
+   if( astOK ) {
+
+/* Get the Mapping from Base to Current Frame from the Prism's FrameSet,
+   and get a pointer to the current Frame. */
+      map = astGetMapping( this_region->frameset, AST__BASE, AST__CURRENT );
+      cfrm = astGetFrame( this_region->frameset, AST__CURRENT );
+
+/* Attempt to split the FrameSet encapsulated within the Prism into two -
+   one containing the axes spanned by the first component Region and
+   another containing the axes spanned by the second component Region.
+   First store the zero-based indices of the base Frame axes
+   corresponding to the first component Region. */
+      for( iax = 0; iax < nax1; iax++ ) baxes[ iax ] = iax;
+
+/* Attempt to split these inputs from the total Mapping, thus creating a
+   Mapping that converts just the axes spanned by the first component
+   Region. */
+      caxes = astMapSplit( map, nax1, baxes, &map1 );
+
+/* If the Mapping could be split, get a Frame spanning the current Frame
+   axes corresponding to the first component Region. */
+      if( caxes ) {
+         nax1_out = astGetNout( map1 );
+         cfrm1 = astPickAxes( cfrm, nax1_out, caxes, NULL );
+         caxes = astFree( caxes );
+      }
+
+/* Do the same thing for the second component Region. */
+      for( iax = 0; iax < nax2; iax++ ) baxes[ iax ] = iax + nax1;
+      caxes = astMapSplit( map, nax2, baxes, &map2 );
+      if( caxes ) {
+         cfrm2 = astPickAxes( cfrm, astGetNout( map2 ), caxes, NULL );
+         caxes = astFree( caxes );
+      }
+
+/* Free resources. */
+      cfrm = astAnnul( cfrm );
+      map = astAnnul( map );
+   }
+   baxes = astFree( baxes );
+
+/* If the Prism mapping could not be split, use the parent GetRegionBounds
+   method. */
+   if( !map1 || !map2 ) {
+      (*parent_getregionbounds)( this_region, lbnd, ubnd, status );
+
+/* Otherwise, we get the bounds of the two component Regions separately. */
+   } else {
+
+/* Remap the first component Region using the FrameSet created above. */
+      reg1 = astMapRegion( this->region1, map1, cfrm1 );
+
+/* Get the bounds of this Region, storing the results at the start of the
+   returned lbnd/ubnd arrays. */
+      astGetRegionBounds( reg1, lbnd, ubnd );
+      reg1 = astAnnul( reg1 );
+
+/* Do the same thing for the second component Region, storing the results
+   at the end of the returned lbnd/ubnd arrays. */
+      reg2 = astMapRegion( this->region2, map2, cfrm2 );
+      astGetRegionBounds( reg2, lbnd + nax1_out, ubnd + nax1_out );
+      reg2 = astAnnul( reg2 );
+
+/* What about the possibility that the axes of the Prism have been
+   permuted? */
+
+   }
+
+/* Free resources. */
+   if( map1 ) map1 = astAnnul( map1 );
+   if( map2 ) map2 = astAnnul( map2 );
+   if( cfrm1 ) cfrm1 = astAnnul( cfrm1 );
+   if( cfrm2 ) cfrm2 = astAnnul( cfrm2 );
+
+}
+
+static void GetRegions( AstPrism *this, AstRegion **reg1, AstRegion **reg2,
+                        int *neg, int *status ) {
+/*
+*
+*  Name:
+*     GetRegions
+
+*  Purpose:
+*     Get the component Regions of a Prism.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void GetRegions( AstPrism *this, AstRegion **reg1, AstRegion **reg2,
+*                      int *neg, int *status )
+
+*  Class Membership:
+*     Prism member function
+
+*  Description:
+*     This function returns pointers to the two Regions in a Prism, together
+*     with the Negated flag for the Prism.
+*
+*     The current Frames in both the returned component Regions will be
+*     equivalent to componets of the base Frame in the FrameSet encapsulated
+*     by the parent Region structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Prism.
+*     reg1
+*        Address of a location to receive a pointer to first component
+*        Region. This is the region which is to be extruded.
+*     reg2
+*        Address of a location to receive a pointer to second component
+*        Region. This will be an Interval defining the axes along which
+*        the first Region is to be extruded.
+*     neg
+*        Pointer to an int in which to return the value of the Negated
+*        attribute of the Prism.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The returned pointers should be annuled using astAnnul when no
+*     longer needed.
+*     - The Frames represented by the returned Regions (that is, the
+*     current Frames in their encapsulated FrameSets) are equivalent to the
+*     base Frame in the FrameSet encapsulated within the parent Region.
+*     - Any changes made to the component Regions using the returned
+*     pointers will be reflected in the supplied Prism.
+
+*-
+*/
+
+/* Initialise */
+   if( reg1 ) *reg1 = NULL;
+   if( reg2 ) *reg2 = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store the required values.*/
+   *reg1 = astClone( this->region1 );
+   *reg2 = astClone( this->region2 );
+   *neg = astGetNegated( (AstRegion *) this );
+}
+
+static AstRegion *GetDefUnc( AstRegion *this_region, int *status ) {
+/*
+*  Name:
+*     GetDefUnc
+
+*  Purpose:
+*     Obtain a pointer to the default uncertainty Region for a given Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     AstRegion *GetDefUnc( AstRegion *this )
+
+*  Class Membership:
+*     Prism method (over-rides the astGetDefUnc method inherited from
+*     the Region class).
+
+*     This function returns a pointer to a Region which represents the
+*     default uncertainty associated with a position on the boundary of the
+*     given  Region. The returned Region refers to the base Frame within the
+*     FrameSet encapsulated by the supplied Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     A pointer to the Region. This should be annulled (using astAnnul)
+*     when no longer needed.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstPrism *this;            /* Pointer to Prism structure */
+   AstRegion *bunc1;          /* Uncertainty Region for 1st component */
+   AstRegion *bunc2;          /* Uncertainty Region for 2nd component */
+   AstRegion *result;         /* Returned pointer */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Prism structure. */
+   this = (AstPrism *) this_region;
+
+/* Construct a default uncertainty Region from the uncertainty Regions
+   in the two component Regions. The current Frames in the component
+   Regions are equivalent to the base Frame in the parent Region structure.
+   So we may need to map the component uncertainty into the current Region of
+   the parent if required later on. */
+   bunc1 = astGetUncFrm( this->region1, AST__CURRENT );
+   bunc2 = astGetUncFrm( this->region2, AST__CURRENT );
+
+/* Combine them into a Prism. */
+   result = (AstRegion *) astPrism( bunc1, bunc2, "", status );
+
+/* Free resources. */
+   bunc1 = astAnnul( bunc1 );
+   bunc2 = astAnnul( bunc2 );
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the required pointer. */
+   return result;
+}
+
+void astInitPrismVtab_(  AstPrismVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPrismVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Prism.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     void astInitPrismVtab( AstPrismVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Prism vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Prism class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrameVtab *frame;          /* Pointer to Frame component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstRegionVtab *region;        /* Pointer to Region component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitRegionVtab( (AstRegionVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAPrism) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstRegionVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+/* None. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   region = (AstRegionVtab *) vtab;
+   frame = (AstFrameVtab *) vtab;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_simplify = mapping->Simplify;
+   mapping->Simplify = Simplify;
+
+   parent_maplist = mapping->MapList;
+   mapping->MapList = MapList;
+
+   parent_getdefunc = region->GetDefUnc;
+   region->GetDefUnc = GetDefUnc;
+
+   parent_setregfs = region->SetRegFS;
+   region->SetRegFS = SetRegFS;
+
+   parent_equal = object->Equal;
+   object->Equal = Equal;
+
+   parent_clearclosed = region->ClearClosed;
+   region->ClearClosed = ClearClosed;
+
+   parent_clearmeshsize = region->ClearMeshSize;
+   region->ClearMeshSize = ClearMeshSize;
+
+   parent_setclosed = region->SetClosed;
+   region->SetClosed = SetClosed;
+
+   parent_setmeshsize = region->SetMeshSize;
+   region->SetMeshSize = SetMeshSize;
+
+   parent_getfillfactor = region->GetFillFactor;
+   region->GetFillFactor = GetFillFactor;
+
+   parent_overlapx = region->OverlapX;
+   region->OverlapX = OverlapX;
+
+   parent_regsetattrib = region->RegSetAttrib;
+   region->RegSetAttrib = RegSetAttrib;
+
+   parent_regclearattrib = region->RegClearAttrib;
+   region->RegClearAttrib = RegClearAttrib;
+
+   parent_getregionbounds = region->GetRegionBounds;
+   region->GetRegionBounds = GetRegionBounds;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   mapping->Decompose = Decompose;
+   region->RegBaseBox = RegBaseBox;
+   region->RegBaseMesh = RegBaseMesh;
+   region->RegPins = RegPins;
+   region->GetBounded = GetBounded;
+   region->RegCentre = RegCentre;
+   region->Overlap = Overlap;
+   region->RegBasePick = RegBasePick;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "Prism", "Region extrusion into higher dimensions" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     CmpMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstPrism *this;       /* Pointer to Prism structure */
+   int result;           /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the Prism structure. */
+   this = (AstPrism *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->region1, mode, extra, fail );
+   if( !result ) result = astManageLock( this->region2, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static int MapList( AstMapping *this_mapping, int series, int invert,
+                    int *nmap, AstMapping ***map_list, int **invert_list,
+                    int *status ) {
+/*
+*  Name:
+*     MapList
+
+*  Purpose:
+*     Decompose a Prism into a sequence of simpler Regions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapList( AstMapping *this, int series, int invert, int *nmap,
+*                  AstMapping ***map_list, int **invert_list )
+
+*  Class Membership:
+*     Prism member function (over-rides the protected astMapList
+*     method inherited from the Region class).
+
+*  Description:
+*     This function decomposes a Prism into a sequence of simpler
+*     Regions which may be applied in parallel to achieve the same
+*     effect. The Prism is decomposed as far as possible, but it is
+*     not guaranteed that this will necessarily yield any more than
+*     one Region, which may actually be the original Prism supplied.
+
+*  Parameters:
+*     this
+*        Pointer to the Prism to be decomposed (the Prism is not
+*        actually modified by this function).
+*     series
+*        Prisms always apply their component Regions in parallel, so this
+*        value should always be zero. This function will return without
+*        action if it is non-zero.
+*     invert
+*        Inverting a Region has no effect on the properties of the Region
+*        and so this parameter is ignored.
+*     nmap
+*        The address of an int which holds a count of the number of
+*        individual Regions in the decomposition. On entry, this
+*        should count the number of Regions already in the
+*        "*map_list" array (below). On exit, it is updated to include
+*        any new Regions appended by this function.
+*     map_list
+*        Address of a pointer to an array of Region pointers. On
+*        entry, this array pointer should either be NULL (if no
+*        Regions have yet been obtained) or should point at a
+*        dynamically allocated array containing Region pointers
+*        ("*nmap" in number) which have been obtained from a previous
+*        invocation of this function.
+*
+*        On exit, the dynamic array will be enlarged to contain any
+*        new Region pointers that result from the decomposition
+*        requested. These pointers will be appended to any previously
+*        present, and the array pointer will be updated as necessary
+*        to refer to the enlarged array (any space released by the
+*        original array will be freed automatically).
+*
+*        The new Region pointers returned will identify a sequence of
+*        Regions which, when applied (in parallel) in order, will be
+*        equivalent to the original Prism.
+*
+*        All the Region pointers returned by this function should be
+*        annulled by the caller, using astAnnul, when no longer
+*        required. The dynamic array holding these pointers should
+*        also be freed, using astFree.
+*     invert_list
+*        Address of a pointer to an array of int. On entry, this array
+*        pointer should either be NULL (if no Regions have yet been
+*        obtained) or should point at a dynamically allocated array
+*        containing Invert attribute values ("*nmap" in number) which
+*        have been obtained from a previous invocation of this
+*        function.
+*
+*        On exit, the dynamic array will be enlarged to contain any
+*        new Invert attribute values that result from the
+*        decomposition requested. These values will be appended to any
+*        previously present, and the array pointer will be updated as
+*        necessary to refer to the enlarged array (any space released
+*        by the original array will be freed automatically).
+*
+*        The new Invert values returned will always be zero since a
+*        Region is unaffected by its Invert setting.
+*
+*        The dynamic array holding these values should be freed by the
+*        caller, using astFree, when no longer required.
+
+*  Returned Value:
+*     Zero is always returned.
+
+*  Notes:
+*     - It is unspecified to what extent the original Prism and the
+*     individual (decomposed) Regions are
+*     inter-dependent. Consequently, the individual Regions cannot be
+*     modified without risking modification of the original Prism.
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then the *nmap value, the
+*     list of Region pointers and the list of Invert values will all
+*     be returned unchanged.
+*/
+
+/* Local Variables: */
+   AstPrism *this;              /* Pointer to Prism structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the Prism structure. */
+   this = (AstPrism *) this_mapping;
+
+/* When considered as a CmpMap, a Prism always combines its component
+   Mappings (Regions) in parallel. So check that a parallel decomposition
+   was requested. */
+   if ( ! series ) {
+
+/* Concatenate the Mapping lists obtained from each component Region. */
+      (void) astMapList( (AstMapping *) this->region1, 0, 0, nmap, map_list,
+                         invert_list );
+      (void) astMapList( (AstMapping *) this->region2, 0, 0, nmap, map_list,
+                         invert_list );
+
+/* If the Prism does not combine its components in the way required
+   by the decomposition (series or parallel), then we cannot decompose
+   it. In this case it must be appended to the Mapping list as a
+   single entity. We can use the parent class method to do this. */
+   } else {
+      (void) ( *parent_maplist )( this_mapping, series, invert, nmap,
+                                  map_list, invert_list, status );
+   }
+
+   return 0;
+}
+
+static int Overlap( AstRegion *this, AstRegion *that, int *status ){
+/*
+*  Name:
+*     Overlap
+
+*  Purpose:
+*     Test if two regions overlap each other.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     int Overlap( AstRegion *this, AstRegion *that, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astOverlap method inherited
+*     from the Region class).
+
+*  Description:
+*     This function returns an integer value indicating if the two
+*     supplied Regions overlap. The two Regions are converted to a commnon
+*     coordinate system before performing the check. If this conversion is
+*     not possible (for instance because the two Regions represent areas in
+*     different domains), then the check cannot be performed and a zero value
+*     is returned to indicate this.
+
+*  Parameters:
+*     this
+*        Pointer to the first Region.
+*     that
+*        Pointer to the second Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     astOverlap()
+*        A value indicating if there is any overlap between the two Regions.
+*        Possible values are:
+*
+*        0 - The check could not be performed because the second Region
+*            could not be mapped into the coordinate system of the first
+*            Region.
+*
+*        1 - There is no overlap between the two Regions.
+*
+*        2 - The first Region is completely inside the second Region.
+*
+*        3 - The second Region is completely inside the first Region.
+*
+*        4 - There is partial overlap between the two Regions.
+*
+*        5 - The Regions are identical.
+*
+*        6 - The second Region is the negation of the first Region.
+
+*  Notes:
+*     - The returned values 5 and 6 do not check the value of the Closed
+*     attribute in the two Regions.
+*     - A value of zero will be returned if this function is invoked with the
+*     AST error status set, or if it should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;
+   AstFrame *efrm;
+   AstFrameSet *fs;
+   AstMapping *bmap;
+   AstMapping *emap;
+   AstMapping *map1;
+   AstMapping *map2;
+   AstMapping *map;
+   AstMapping *smap;
+   AstRegion *that_base2;
+   AstRegion *that_base;
+   AstRegion *that_ext2;
+   AstRegion *that_ext;
+   AstRegion *this_reg1;
+   AstRegion *this_reg2;
+   int *inax;
+   int *outax;
+   int i;
+   int nbase;
+   int next;
+   int rbase;
+   int result;
+   int rext;
+   int that_neg;
+   int this_neg;
+
+/* A table indicating how to combine together the overlap state of the
+   extrusion Regions with the overlap state of the other (base) Region.
+   The first index represents the value returned by the astOverlap method
+   when used to determine the overlap of the base Regions in the two
+   supplied Prisms. The second index represents the value returned by the
+   astOverlap method when used to determine the overlap of the extrusion
+   Regions in the two supplied Prisms. The integer values stored in the
+   array represent the astOverlap value describing the overlap of the two
+   Prisms. */
+   static int rtable[ 7 ][ 7 ] = { { 0, 0, 0, 0, 0, 0, 0 },
+                                   { 0, 1, 1, 1, 1, 1, 1 },
+                                   { 0, 1, 2, 4, 4, 2, 1 },
+                                   { 0, 1, 4, 3, 4, 3, 1 },
+                                   { 0, 1, 4, 4, 4, 4, 1 },
+                                   { 0, 1, 2, 3, 4, 5, 1 },
+                                   { 0, 1, 1, 1, 1, 1, 6 } };
+
+/* Initialise */
+   result = 0;
+
+/* Check the inherited status. */
+   if ( !astOK ) return result;
+
+/* Get pointers to the base and extrusion Regions within "this", and also
+   the nagated flag. */
+   GetRegions( (AstPrism *) this, &this_reg1, &this_reg2, &this_neg, status );
+
+/* Get the number of axes in the base and extrusion Regions of "this". */
+   nbase = astGetNaxes( this_reg1 );
+   next = astGetNaxes( this_reg2 );
+
+/* Get a FrameSet which goes from the Frame represented by "this" to the
+   Frame represented by "that". Check that the conection is defined. */
+   fs = astConvert( this, that, "" );
+   if( fs ) {
+
+/* Get the forward Mapping from the above FrameSet. */
+      map2 = astGetMapping( fs, AST__BASE, AST__CURRENT );
+
+/* Get a pointer to the Mapping from base to current Frame in "this". */
+      map1 = astGetMapping( this->frameset, AST__BASE, AST__CURRENT );
+
+/* Combine these Mappings to get the Mapping from the base Frame of "this"
+   to the current Frame of "that". */
+      map = (AstMapping *) astCmpMap( map1, map2, 1, "", status );
+
+/* Simplify this Mapping. */
+      smap = astSimplify( map );
+
+/* See if the extrusion axes in "this" correspond to a unique set of axes
+   in the current Frame of "that". */
+      inax = astMalloc( sizeof(int)*(size_t)next );
+      for( i = 0; i < next; i++ ) inax[ i ] = nbase + i;
+      outax = astMapSplit( smap, next, inax, &emap );
+
+/* If they do, attempt to create a Region by picking the axes from "that"
+   that correspond to the extrusion axes in "this". */
+      if( emap && astGetNout( emap ) == next ) {
+         that_ext = (AstRegion *) astPickAxes( that, next, outax, NULL );
+
+/* If the picked axes formed a Region, see if the base axes can also be
+   picked in the same way. */
+         if( astIsARegion( that_ext ) ) {
+            outax = astFree( outax );
+            inax = astGrow( inax, (size_t)nbase, sizeof( int ) );
+            for( i = 0; i < nbase; i++ ) inax[ i ] = i;
+            outax = astMapSplit( smap, nbase, inax, &bmap );
+            if( bmap && astGetNout( bmap ) == nbase ) {
+               that_base = (AstRegion *) astPickAxes( that, nbase, outax, NULL );
+               if( astIsARegion( that_base ) ) {
+
+/* Map the two Regions picked from "that" into the Frames of the two
+   sub-regions within "this". */
+                  astInvert( emap );
+                  efrm = astGetFrame( this_reg2->frameset, AST__CURRENT );
+                  that_ext2 = astMapRegion( that_ext, emap, efrm );
+
+                  astInvert( bmap );
+                  bfrm = astGetFrame( this_reg1->frameset, AST__CURRENT );
+                  that_base2 = astMapRegion( that_base, bmap, bfrm );
+
+/* We can test separately for overlap of the two extrusion Regions, and for
+   overlap of the two base Regions, and then combine the returned flags to
+   represent overlap of the whole Prism. */
+                  rbase = astOverlap( this_reg1, that_base2 );
+                  rext = astOverlap( this_reg2, that_ext2 );
+                  result = rtable[ rbase ][ rext ];
+
+/* The values in the rtable array assume that neither of the supplied
+   Prisms have been negated. Modify the value obtained from rtable to
+   take account of negation of either or both of the supplied Prisms. */
+                  that_neg = astGetNegated( that );
+                  if( this_neg ) {
+                     if( that_neg ) {
+                        if( result == 1 ) {
+                           result = 4;
+                        } else if( result == 2 ) {
+                           result = 3;
+                        } else if( result == 3 ) {
+                           result = 2;
+                        }
+                     } else {
+                        if( result == 1 ) {
+                           result = 3;
+                        } else if( result == 2 ) {
+                           result = 4;
+                        } else if( result == 3 ) {
+                           result = 1;
+                        } else if( result == 5 ) {
+                           result = 6;
+                        } else if( result == 6 ) {
+                           result = 5;
+                        }
+                     }
+                  } else if( that_neg ){
+                     if( result == 1 ) {
+                        result = 2;
+                     } else if( result == 2 ) {
+                        result = 1;
+                     } else if( result == 3 ) {
+                        result = 4;
+                     } else if( result == 5 ) {
+                        result = 6;
+                     } else if( result == 6 ) {
+                        result = 5;
+                     }
+                  }
+
+/* Free resources */
+                  efrm = astAnnul( efrm );
+                  that_ext2 = astAnnul( that_ext2 );
+                  bfrm = astAnnul( bfrm );
+                  that_base2 = astAnnul( that_base2 );
+               }
+               that_base = astAnnul( that_base );
+               bmap = astAnnul( bmap );
+            }
+         }
+         emap = astAnnul( emap );
+         that_ext = astAnnul( that_ext );
+      }
+      outax = astFree( outax );
+      inax = astFree( inax );
+      smap = astAnnul( smap );
+      map = astAnnul( map );
+      map1 = astAnnul( map1 );
+      map2 = astAnnul( map2 );
+      fs = astAnnul( fs );
+   }
+   this_reg1 = astAnnul( this_reg1 );
+   this_reg2 = astAnnul( this_reg2 );
+
+/* If overlap could not be determined using the above implementation, try
+   using the implementation inherited from the parent Region class. Use
+   OverlapX rather than Overlap since a) it is OverlapX that does the work,
+   and b) calling Overlap could end us in an infinite loop. */
+   if( !result ) result = (*parent_overlapx)( this, that, status );
+
+/* If not OK, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int OverlapX( AstRegion *that, AstRegion *this, int *status ){
+/*
+*  Name:
+*     OverlapX
+
+*  Purpose:
+*     Test if two regions overlap each other.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     int OverlapX( AstRegion *that, AstRegion *this )
+
+*  Class Membership:
+*     Prism member function (over-rides the astOverlapX method inherited
+*     from the Region class).
+
+*  Description:
+*     This function performs the processing for the public astOverlap
+*     method and has exactly the same interface except that the order
+*     of the two arguments is swapped. This is a trick to allow
+*     the astOverlap method to be over-ridden by derived classes on
+*     the basis of the class of either of its two arguments.
+*
+*     See the astOverlap method for details of the interface.
+*/
+
+/* Local Variables; */
+   int result;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* We know that "that" is a Prism, so call the private Overlap method,
+   and then modify the returned value to take account of the fact that the
+   two Regions are swapped. */
+   result = Overlap( that, this, status );
+
+   if( result == 2 ){
+      result = 3;
+   } else if( result == 3 ){
+      result = 2;
+   }
+
+   return result;
+}
+
+
+static void RegBaseBox( AstRegion *this_region, double *lbnd, double *ubnd,
+                        int *status ){
+/*
+*  Name:
+*     RegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd,
+*                      int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astRegBaseBox protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPrism *this;              /* Pointer to Prism structure */
+   AstRegion *reg1;             /* Pointer to first component Region */
+   AstRegion *reg2;             /* Pointer to second component Region */
+   int nax;                     /* Number of axes in Frame */
+   int neg;                     /* Negated flag for Prism */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Prism structure */
+   this = (AstPrism *) this_region;
+
+/* Get pointers to the component Regions. */
+   GetRegions( this, &reg1, &reg2, &neg, status );
+
+/* The base Frame of the parent Region structure is equivalent to a
+   CmpFrame containing the current Frames of the component Regions.
+   Get the no. of axes in the first component Frame. */
+   nax = astGetNaxes( reg1 );
+
+/* Get the bounding boxes of the component Regions in these Frame,
+   storing the values in the supplied arrays. */
+   astGetRegionBounds( reg1, lbnd, ubnd );
+   astGetRegionBounds( reg2, lbnd + nax, ubnd + nax );
+
+/* Free resources.*/
+   reg1 = astAnnul( reg1 );
+   reg2 = astAnnul( reg2 );
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this_region, int *status ){
+/*
+*  Name:
+*     RegBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing a mesh of points on the boundary of a
+*     Region in its base Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     AstPointSet *RegBaseMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astRegBaseMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. Annul the pointer using astAnnul when it
+*     is no longer needed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*/
+
+
+/* Local Variables: */
+   AstPointSet *grid1;            /* PointSet holding grid for region1 */
+   AstPointSet *grid2;            /* PointSet holding grid for region2 */
+   AstPointSet *mesh1;            /* PointSet holding mesh for region1 */
+   AstPointSet *mesh2;            /* PointSet holding mesh for region2 */
+   AstPointSet *result;           /* Returned pointer */
+   AstPrism *this;                /* The Prism structure */
+   AstRegion *reg1;               /* Pointer to first component Region */
+   AstRegion *reg2;               /* Pointer to second component Region */
+   double **pg1;                  /* Pointer to grid1 arrays */
+   double **pg2;                  /* Pointer to grid2 arrays */
+   double **pm1;                  /* Pointer to mesh1 arrays */
+   double **pm2;                  /* Pointer to mesh2 arrays */
+   double **ptr;                  /* Pointer to returned mesh arrays */
+   int gsz1;                      /* Preferred grid size for region1 */
+   int gsz2;                      /* Preferred grid size for region2 */
+   int hasMesh1;                  /* Does 1st component Region have a mesh? */
+   int hasMesh2;                  /* Does 2nd component Region have a mesh? */
+   int i;                         /* Index of next mesh position */
+   int ii;                        /* Index of next results position */
+   int j;                         /* Index of next grid position */
+   int jc;                        /* Axis index */
+   int msz1;                      /* Preferred mesh size for region1 */
+   int msz2;                      /* Preferred mesh size for region2 */
+   int msz;                       /* Original MeshSize */
+   int mszp;                      /* MeshSize value for Prism */
+   int nax1;                      /* Number of axes in region1 */
+   int nax2;                      /* Number of axes in region2 */
+   int nax;                       /* Number of axes in Prism */
+
+/* Initialise */
+   result= NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Prism structure. */
+   this = (AstPrism *) this_region;
+
+/* If the Region structure contains a pointer to a PointSet holding
+   a previously created mesh, return it. */
+   if( this_region->basemesh ) {
+      result = astClone( this_region->basemesh );
+
+/* Otherwise, create a new mesh. */
+   } else {
+
+/* Get pointers to the component regions. */
+      reg1 = this->region1;
+      reg2 = this->region2;
+
+/* A mesh can only be produced for a Region if it is bounded when either
+   negated or un-negated. See if meshes can be produced for the component
+   Regions. */
+      hasMesh1 = astGetBounded( reg1 );
+      if( !hasMesh1 ){
+         astNegate( reg1 );
+         hasMesh1 = astGetBounded( reg1 );
+         astNegate( reg1 );
+      }
+
+      hasMesh2 = astGetBounded( reg2 );
+      if( !hasMesh2 ){
+         astNegate( reg2 );
+         hasMesh2 = astGetBounded( reg2 );
+         astNegate( reg2 );
+      }
+
+/* If either Region does not have a mesh we cannot produce a mesh for the
+   Prism. */
+      if( !hasMesh1 || !hasMesh2 ) {
+         if( astOK ) astError( AST__INTER, "astRegBaseMesh(%s): No mesh "
+                   "can be produced for the %s bacause one of its component "
+                   "Regions is unbounded.", status, astGetClass( this ), astGetClass( this ) );
+
+/* Otherwise we can create a mesh for the Prism. */
+      } else {
+
+/* Determine the grid sizes and mesh sizes to use for the two components.
+   This aims to produce a total number of points in the returned Prism
+   mesh roughly equal to the MeshSize attribute of the Prism. It also
+   aims to divide the mesh points equally between the end faces of the
+   prism, and the side walls. We remember that the grid used to represent
+   any 1-D region always has a size of 2, regardless of the setting of
+   MeshSize. */
+         mszp = astGetMeshSize( this );
+         msz1 = ( astGetNaxes( reg1 ) == 1 ) ? 2 : sqrt( 0.5*mszp );
+         gsz2 = 0.5*mszp/msz1;
+         msz2 = ( astGetNaxes( reg2 ) == 1 ) ? 2 : sqrt( 0.5*mszp );
+         gsz1 = 0.5*mszp/msz2;
+
+/* First, get a boundary mesh for the second component Region defining the
+   prism extrusion. For instance, if the Region is 1-dimensional, this mesh
+   will consist of the two values on the Region axis: the lower and upper
+   bounds of the Region. */
+         msz = astTestMeshSize( reg2 ) ? astGetMeshSize( reg2 ) : -1;
+         astSetMeshSize( reg2, msz2 );
+         mesh2 = astRegMesh( reg2 );
+
+/* Also get a grid of points spread throughout the extent (i.e. not
+   merely on the boundary) of the second Region. */
+         astSetMeshSize( reg2, gsz2 );
+         grid2 = astRegGrid( reg2 );
+
+/* Re-instate the original MeshSize for the second Region. */
+         if( msz == -1 ) {
+            astClearMeshSize( reg2 );
+         } else {
+            astSetMeshSize( reg2, msz );
+         }
+
+/* Similarly, get a boundary mesh and a volume grid for the first Region. */
+         msz = astTestMeshSize( reg1 ) ? astGetMeshSize( reg1 ) : -1;
+         astSetMeshSize( reg1, msz1 );
+         mesh1 = astRegMesh( reg1 );
+
+         astSetMeshSize( reg1, gsz1 );
+         grid1 = astRegGrid( reg1 );
+
+         if( msz == -1 ) {
+            astClearMeshSize( reg1 );
+         } else {
+            astSetMeshSize( reg1, msz );
+         }
+
+/* Note the number of axes in the two component Regions. */
+         nax1 = astGetNcoord( mesh1 );
+         nax2 = astGetNcoord( mesh2 );
+
+/* The above mesh and grid sizes are only approximate. Find the values
+   actually used. */
+         msz1 = astGetNpoint( mesh1 );
+         gsz1 = astGetNpoint( grid1 );
+         msz2 = astGetNpoint( mesh2 );
+         gsz2 = astGetNpoint( grid2 );
+
+/* Create the returned PointSet. */
+         msz = gsz1*msz2 + msz1*gsz2;
+         nax= astGetNaxes( this );
+         result = astPointSet( msz, nax, "", status );
+
+/* Get pointers to the data in all 5 PointSets. */
+         ptr = astGetPoints( result );
+         pm1 = astGetPoints( mesh1 );
+         pg1 = astGetPoints( grid1 );
+         pm2 = astGetPoints( mesh2 );
+         pg2 = astGetPoints( grid2 );
+
+/* Check pointers can be de-referenced safely. */
+         if( astOK ) {
+
+/* Initialise the index of the next point to be written to the results
+   array. */
+            ii = 0;
+
+/* First, replicate the volume grid covering the first region at every
+   point in the boundary mesh covering the second Region. */
+            for( i = 0; i < msz2; i++ ) {
+               for( j = 0; j < gsz1; j++, ii++ ) {
+                  for( jc = 0; jc < nax1; jc++ ) {
+                     ptr[ jc ][ ii ] = pg1[ jc ][ j ];
+                  }
+                  for( ; jc < nax; jc++ ) {
+                     ptr[ jc ][ ii ] = pm2[ jc - nax1 ][ i ];
+                  }
+               }
+            }
+
+/* Now, replicate the volume grid covering the second region at every
+   point in the boundary mesh covering the first Region. */
+            for( i = 0; i < msz1; i++ ) {
+               for( j = 0; j < gsz2; j++, ii++ ) {
+                  for( jc = 0; jc < nax1; jc++ ) {
+                     ptr[ jc ][ ii ] = pm1[ jc ][ i ];
+                  }
+                  for( ; jc < nax; jc++ ) {
+                     ptr[ jc ][ ii ] = pg2[ jc -nax1 ][ j ];
+                  }
+               }
+            }
+         }
+
+/* Free resources. */
+         mesh1 = astAnnul( mesh1 );
+         mesh2 = astAnnul( mesh2 );
+         grid1 = astAnnul( grid1 );
+         grid2 = astAnnul( grid2 );
+      }
+
+/* Save the returned pointer in the Region structure so that it does not
+   need to be created again next time this function is called. */
+      if( astOK && result ) this_region->basemesh = astClone( result );
+   }
+
+/* Annul the result if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static AstRegion *RegBasePick( AstRegion *this_region, int naxes,
+                               const int *axes, int *status ){
+/*
+*  Name:
+*     RegBasePick
+
+*  Purpose:
+*     Return a Region formed by picking selected base Frame axes from the
+*     supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     AstRegion *RegBasePick( AstRegion *this, int naxes, const int *axes,
+*                             int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astRegBasePick protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function attempts to return a Region that is spanned by selected
+*     axes from the base Frame of the encapsulated FrameSet of the supplied
+*     Region. This may or may not be possible, depending on the class of
+*     Region. If it is not possible a NULL pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     naxes
+*        The number of base Frame axes to select.
+*     axes
+*        An array holding the zero-based indices of the base Frame axes
+*        that are to be selected.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Region, or NULL if no region can be formed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frm1;      /* Axes picked from the 1st encapsulated Region */
+   AstFrame *frm2;      /* Axes picked from the 2nd encapsulated Region */
+   AstPrism *this;      /* Pointer to Prism structure */
+   AstRegion *result;   /* Returned Region */
+   int *axes1;          /* Axes to pick from 1st input encapsulated Region */
+   int *axes2;          /* Axes to pick from 2nd input encapsulated Region */
+   int i;               /* Output axis index */
+   int j;               /* Input axis index */
+   int nax1;            /* No. of axes in 1st input encapsulated Region */
+   int nax2;            /* No. of axes in 2nd input encapsulated Region */
+   int naxes1;          /* No. of axes in 1st output encapsulated Region */
+   int naxes2;          /* No. of axes in 2nd output encapsulated Region */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Prism information. */
+   this = (AstPrism *) this_region;
+
+/* Get the number of axes in each of the two encapsulated Regions. */
+   nax1 = astGetNaxes( this->region1 );
+   nax2 = astGetNaxes( this->region2 );
+
+/* Allocate memory to hold the indices of the axes selected from each of
+   the two encapsulated Regions. */
+   axes1 = astMalloc( sizeof( *axes1 )*nax1 );
+   axes2 = astMalloc( sizeof( *axes2 )*nax2 );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Initialise the counters that count the number of axes selected from
+   each of the two encapsulated Regions. */
+      naxes1 = 0;
+      naxes2 = 0;
+
+/* For each of the axes to be selected from the prism... */
+      for( i = 0; i < naxes; i++ ) {
+         j = axes[ i ];
+
+/* ... if the current selected axis is part of the first encapsulated
+   Region, add its index into the array that holds the axes to be selected
+   from the first encapsulated region. Increment the number of axes
+   selected from the first encapsulated region. */
+         if( j < nax1 ) {
+            axes1[ naxes1++ ] = j;
+
+/* If the current selected axis is part of the second encapsulated Region,
+   add its index into the array that holds the axes to be selected from
+   the second encapsulated region. Note, the index is converted from a
+   Prism axis index to a sub-region axis index by subtracting "nax1".
+   Also increment the number of axes selected from the second encapsulated
+   region. */
+         } else {
+            axes2[ naxes2++ ] = j - nax1;
+         }
+      }
+
+/* Pick any required axes from the first sub-region. If the result of the
+   selection is not a Region, we cannot pick the required axes, so annul
+   the object holding the selected axes. */
+      if( naxes1 ) {
+         frm1 = astPickAxes( this->region1, naxes1, axes1, NULL );
+         if( frm1 && !astIsARegion( frm1 ) ) frm1 = astAnnul( frm1 );
+      } else {
+         frm1 = NULL;
+      }
+
+/* Pick any required axes from the second sub-region. If the result of the
+   selection is not a Region, we cannot pick the required axes, so annul
+   the object holding the selected axes. */
+      if( naxes2 ) {
+         frm2 = astPickAxes( this->region2, naxes2, axes2, NULL );
+         if( frm2 && !astIsARegion( frm2 ) ) frm2 = astAnnul( frm2 );
+      } else {
+         frm2 = NULL;
+      }
+
+/* If we are selecting axes only from the first sub-region, and the above
+   selection was succesful, just return a clone of the Region holding the
+   axes selected from the first sub-region. */
+      if( naxes1 > 0 && naxes2 == 0 && frm1 ) {
+         result = astClone( frm1 );
+
+/* If we are selecting axes only from the second sub-region, and the above
+   selection was succesful, just return a clone of the Region holding the
+   axes selected from the second sub-region. */
+      } else if( naxes1 == 0 && naxes2 > 0 && frm2 ) {
+         result = astClone( frm2 );
+
+/* If we are selecting axes from both sub-regions, and both the above
+   selections were succesful, joing them together into a Prism. */
+      } else if( naxes1 > 0 && naxes2 > 0 && frm1 && frm2 ) {
+         result = (AstRegion *) astPrism( (AstRegion *) frm1,
+                                          (AstRegion *) frm2,
+                                          "", status );
+      }
+
+/* Free resources */
+      if( frm1 ) frm1 = astAnnul( frm1 );
+      if( frm2 ) frm2 = astAnnul( frm2 );
+   }
+
+   axes1 = astFree( axes1 );
+   axes2 = astFree( axes2 );
+
+/* Return a NULL pointer if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static double *RegCentre( AstRegion *this_region, double *cen, double **ptr,
+                          int index, int ifrm, int *status ){
+/*
+*  Name:
+*     RegCentre
+
+*  Purpose:
+*     Re-centre a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     double *RegCentre( AstRegion *this, double *cen, double **ptr,
+*                        int index, int ifrm, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astRegCentre protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function shifts the centre of the supplied Region to a
+*     specified position, or returns the current centre of the Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     cen
+*        Pointer to an array of axis values, giving the new centre.
+*        Supply a NULL value for this in order to use "ptr" and "index" to
+*        specify the new centre.
+*     ptr
+*        Pointer to an array of points, one for each axis in the Region.
+*        Each pointer locates an array of axis values. This is the format
+*        returned by the PointSet method astGetPoints. Only used if "cen"
+*        is NULL.
+*     index
+*        The index of the point within the arrays identified by "ptr" at
+*        which is stored the coords for the new centre position. Only used
+*        if "cen" is NULL.
+*     ifrm
+*        Should be AST__BASE or AST__CURRENT. Indicates whether the centre
+*        position is supplied and returned in the base or current Frame of
+*        the FrameSet encapsulated within "this".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If both "cen" and "ptr" are NULL then a pointer to a newly
+*     allocated dynamic array is returned which contains the centre
+*     coords of the Region. This array should be freed using astFree when
+*     no longer needed. If either of "ptr" or "cen" is not NULL, then a
+*     NULL pointer is returned.
+
+*  Notes:
+*    - Some Region sub-classes do not have a centre. Such classes will report
+*    an AST__INTER error code if this method is called with either "ptr" or
+*    "cen" not NULL. If "ptr" and "cen" are both NULL, then no error is
+*    reported if this method is invoked on a Region of an unsuitable class,
+*    but NULL is always returned.
+*/
+
+/* Local Variables: */
+   AstPrism *this;     /* Pointer to Prism structure */
+   AstRegion *reg1;    /* Pointer to first component Region */
+   AstRegion *reg2;    /* Pointer to second component Region */
+   double *bc;         /* Base Frame centre position */
+   double *cen1;       /* Centre of first component Region */
+   double *cen2;       /* Centre of second component Region */
+   double *result;     /* Returned pointer */
+   double *tmp;        /* Temporary array pointer */
+   double *total;      /* Pointer to total base Frame centre array */
+   int i;              /* Coordinate index */
+   int nax1;           /* Number of axes in first component Region */
+   int nax2;           /* Number of axes in second component Region */
+   int ncb;            /* Number of base frame coordinate values per point */
+   int ncc;            /* Number of current frame coordinate values per point */
+   int neg;            /* Prism negated flag */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Prism structure. */
+   this = (AstPrism *) this_region;
+
+/* Get the component Regions, and the Negated flag for the Prism. */
+   GetRegions( this, &reg1, &reg2, &neg, status );
+
+/* Get the number of current Frame axes in each component Region. The sum
+   of these will equal the number of base Frame axes in the parent Region
+   structure. */
+   nax1 = astGetNaxes( reg1 );
+   nax2 = astGetNaxes( reg2 );
+   ncb = nax1 + nax2;
+
+/* If we are getting (not setting) the current centre... */
+   if( !ptr && !cen ) {
+
+/* Get the centre from the two component Regions in their current Frames. */
+      cen1 = astRegCentre( reg1, NULL, NULL, 0, AST__CURRENT );
+      cen2 = astRegCentre( reg2, NULL, NULL, 0, AST__CURRENT );
+
+/* If both component regions are re-centerable, join the two centre
+   arrays together into a single array. */
+      if( cen1 && cen2 ) {
+         total = astMalloc( sizeof(double)*(size_t)ncb );
+         if( total ) {
+            for( i = 0; i < nax1; i++ ) total[ i ] = cen1[ i ];
+            for( i = 0; i < nax2; i++ ) total[ i + nax1 ] = cen2[ i ];
+
+/* The current Frames of the component Regions correspond to the base
+   Frame of the parent Region structure. If the original centre is
+   required in the current Frame, transform it using the base->current
+   Mapping from the parent Region structure. */
+            if( ifrm == AST__CURRENT ) {
+               result = astRegTranPoint( this_region, total, 1, 1 );
+               total = astFree( total );
+            } else {
+               result = total;
+            }
+         }
+      }
+
+/* Free the individual centre arrays. */
+      cen1 = astFree( cen1 );
+      cen2 = astFree( cen2 );
+
+/* If we are setting a new centre... */
+   } else {
+
+/* If the new centre is supplied in the current Frame of the parent
+   Region structure, transform it into the base Frame (i.e. the Frames
+   represented by the component Regions). */
+      if( ifrm == AST__CURRENT ) {
+         if( cen ) {
+            bc = astRegTranPoint( this_region, cen, 1, 0 );
+         } else {
+            ncc = astGetNaxes( this_region );
+            tmp = astMalloc( sizeof( double)*(size_t)ncc );
+            if( astOK ) {
+               for( i = 0; i < ncc; i++ ) tmp[ i ] = ptr[ i ][ index ];
+            }
+            bc = astRegTranPoint( this_region, tmp, 1, 0 );
+            tmp = astFree( tmp );
+         }
+
+      } else {
+         if( cen ) {
+            bc = cen;
+         } else {
+            bc = astMalloc( sizeof( double)*(size_t)ncb );
+            if( astOK ) {
+               for( i = 0; i < ncb; i++ ) bc[ i ] = ptr[ i ][ index ];
+            }
+         }
+      }
+
+/* Set the centre in the two component Regions in their current Frames. */
+      astRegCentre( reg1, bc, NULL, 0, AST__CURRENT );
+      astRegCentre( reg2, bc + nax1, NULL, 0, AST__CURRENT );
+
+/* Free resources. */
+     if( bc != cen ) bc = astFree( bc );
+   }
+
+   reg1 = astAnnul( reg1 );
+   reg2 = astAnnul( reg2 );
+
+/* Return the result. */
+   return result;
+}
+
+static int RegPins( AstRegion *this_region, AstPointSet *pset, AstRegion *unc,
+                    int **mask, int *status ){
+/*
+*  Name:
+*     RegPins
+
+*  Purpose:
+*     Check if a set of points fall on the boundary of a given Prism.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+*                  int **mask, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astRegPins protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied set of
+*     points all fall on the boundary of the given Prism.
+*
+*     Some tolerance is allowed, as specified by the uncertainty Region
+*     stored in the supplied Prism "this", and the supplied uncertainty
+*     Region "unc" which describes the uncertainty of the supplied points.
+
+*  Parameters:
+*     this
+*        Pointer to the Prism.
+*     pset
+*        Pointer to the PointSet. The points are assumed to refer to the
+*        base Frame of the FrameSet encapsulated by "this".
+*     unc
+*        Pointer to a Region representing the uncertainties in the points
+*        given by "pset". The Region is assumed to represent the base Frame
+*        of the FrameSet encapsulated by "this". Zero uncertainity is assumed
+*        if NULL is supplied.
+*     mask
+*        Pointer to location at which to return a pointer to a newly
+*        allocated dynamic array of ints. The number of elements in this
+*        array is equal to the value of the Npoint attribute of "pset".
+*        Each element in the returned array is set to 1 if the
+*        corresponding position in "pset" is on the boundary of the Region
+*        and is set to zero otherwise. A NULL value may be supplied
+*        in which case no array is created. If created, the array should
+*        be freed using astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the points all fall on the boundary of the given
+*     Region, to within the tolerance specified. Zero otherwise.
+
+*/
+
+/* Local variables: */
+   AstPointSet *ps1;            /* Points projected into 1st component Region */
+   AstPointSet *ps1b;           /* "ps1" in base Frame of 1st component Region */
+   AstPointSet *ps1b2;          /* "ps1b" transformed using 1st Region */
+   AstPointSet *ps2b2;          /* "ps2b" transformed using 2nd Region */
+   AstPointSet *ps2;            /* Points projected into 2nd component Region */
+   AstPointSet *ps2b;           /* "ps2" in base Frame of 2nd component Region */
+   AstPrism *this;              /* Pointer to the Prism structure. */
+   AstRegion *reg1;             /* Pointer to first component Region */
+   AstRegion *reg2;             /* Pointer to second component Region */
+   AstRegion *unc1;             /* Base Frame uncertainty in 1st component Region */
+   AstRegion *unc2;             /* Base Frame uncertainty in 2nd component Region */
+   double **ptr1b2;             /* Pointer to axis values in "ps1b2" */
+   double **ptr2b2;             /* Pointer to axis values in "ps2b2" */
+   int *mask1;                  /* Mask for first component boundary */
+   int *mask2;                  /* Mask for second component boundary */
+   int cl1;                     /* Original Closed flag for reg1 */
+   int cl2;                     /* Original Closed flag for reg2 */
+   int i;                       /* Point index */
+   int j;                       /* Axis index */
+   int nax1;                    /* Number of axes in first component Region */
+   int nax2;                    /* Number of axes in second component Region */
+   int np;                      /* Number of points in supplied PointSet */
+   int on;                      /* Is this point on the Prism boundary? */
+   int result;                  /* Returned flag */
+
+/* Initialise */
+   result = 0;
+   if( mask ) *mask = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get a pointer to the Prism structure. */
+   this = (AstPrism *) this_region;
+
+/* Get pointers to the two component Regions. */
+   reg1 = this->region1;
+   reg2 = this->region2;
+
+/* Temporarily ensure the components are closed. */
+   cl1 = astTestClosed( reg1 ) ? astGetClosed( reg1 ) : INT_MAX;
+   cl2 = astTestClosed( reg2 ) ? astGetClosed( reg2 ) : INT_MAX;
+   astSetClosed( reg1, cl1 );
+   astSetClosed( reg2, cl2 );
+
+/* A point in the coordinate system represented by the Prism is on the
+   boundary of the Prism if:
+   1) it is on the boundary of one of the coomponent Regions AND
+   2) it is on or within the boundary of the other component Region.
+
+   First look for points on the boundary of the first component Region.
+   Create a PointSet holding just the axes from the supplied PointSet
+   which relate to the first component Region. */
+   np = astGetNpoint( pset );
+   nax1 = astGetNaxes( reg1 );
+   ps1 = astPointSet( np, nax1, "", status );
+   astSetSubPoints( pset, 0, 0, ps1 );
+
+/* Get a mask which indicates if each supplied point is on or off the
+   boundary of the first component Region. astRegPins expects its "pset"
+   argument to contain positions in the base Frame of the Region, so
+   we must first transform the supplied points into the base Frame of
+   "reg1". We must also get the uncertainty in the base Frame of the
+   component Region. */
+   ps1b = astRegTransform( reg1, ps1, 0, NULL, NULL );
+   unc1 = astGetUncFrm( reg1, AST__BASE );
+   astRegPins( reg1, ps1b, unc1, &mask1 );
+
+/* Also determine which of the points are on or in the boundary by using
+   "reg1" as a Mapping to transform the supplied points. */
+   ps1b2 = astTransform( reg1, ps1b, 1, NULL );
+
+/* Do the same for the second component Region */
+   nax2 = astGetNaxes( reg2 );
+   ps2 = astPointSet( np, nax2, "", status );
+   astSetSubPoints( pset, 0, nax1, ps2 );
+   ps2b = astRegTransform( reg2, ps2, 0, NULL, NULL );
+   unc2 = astGetUncFrm( reg2, AST__BASE );
+   astRegPins( reg2, ps2b, unc2, &mask2 );
+   ps2b2 = astTransform( reg2, ps2b, 1, NULL );
+
+/* Get pointers to the data in all the relevant PointSets. */
+   ptr1b2 = astGetPoints( ps1b2 );
+   ptr2b2 = astGetPoints( ps2b2 );
+
+/* Check pointers can be dereferenced safely. */
+   if( astOK ) {
+
+/* Assume all points are on the boundary of the Prism. */
+      result = 1;
+
+/* Check each point. */
+      for( i = 0; i < np; i++ ) {
+
+/* Assume this point is on the boundary of the Prism. */
+         on = 1;
+
+/* If this point is on the boundary of both component Regions, it is on
+   the boundary of the Prism. If it is on the boundary of the first
+   component Region but not on the boundary of the second, then it is
+   still on the boundary of the Prism if it is within the volume
+   reporesented by the second. */
+         if( mask1[ i ] ) {
+            if( !mask2[ i ] ) {
+               for( j = 0; j < nax2; j++ ) {
+                  if( ptr2b2[ j ][ i ] == AST__BAD ) {
+                     on = 0;
+                     break;
+                  }
+               }
+            }
+
+/* If this point is on the boundary of the second component Region but
+   not the first, it is on the boundary of the Prism if it is within the
+   volume reporesented by the first. */
+         } else {
+            if( mask2[ i ] ) {
+               for( j = 0; j < nax1; j++ ) {
+                  if( ptr1b2[ j ][ i ] == AST__BAD ) {
+                     on = 0;
+                     break;
+                  }
+               }
+
+/* If this point is on the boundary of neither component Region, it is not
+   on the boundary of the Prism. */
+            } else {
+               on = 0;
+            }
+         }
+
+/* Use "mask1" to return the Prism's mask. Clear the returned flag if
+   this point is not on the boundary of the Prism. */
+         mask1[ i ] = on;
+         if( !on ) result = 0;
+      }
+   }
+
+/* Re-instate the original values of the Closed attribute for the
+   component Regions. */
+   if( cl1 == INT_MAX ) {
+      astClearClosed( reg1 );
+   } else {
+      astSetClosed( reg1, cl1 );
+   }
+   if( cl2 == INT_MAX ) {
+      astClearClosed( reg2 );
+   } else {
+      astSetClosed( reg2, cl2 );
+   }
+
+/* Return "mask1" as the Prism's mask if required. Otherwise free it. */
+   if( mask ) {
+      *mask = mask1;
+   } else {
+      mask1 = astFree( mask1 );
+   }
+
+/* Free other resources */
+   mask2 = astFree( mask2 );
+   ps1 = astAnnul( ps1 );
+   ps1b = astAnnul( ps1b );
+   ps1b2 = astAnnul( ps1b2 );
+   ps2 = astAnnul( ps2 );
+   unc1 = astAnnul( unc1 );
+   ps2b = astAnnul( ps2b );
+   ps2b2 = astAnnul( ps2b2 );
+   unc2 = astAnnul( unc2 );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) {
+      result = 0;
+      if( mask ) *mask = astAnnul( *mask );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void RegClearAttrib( AstRegion *this_region, const char *attrib,
+                            char **base_attrib, int *status ) {
+/*
+*  Name:
+*     RegClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     void RegClearAttrib( AstRegion *this, const char *attrib,
+*                          char **base_attrib, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astRegClearAttrib method
+*     inherited from the Region class).
+
+*  Description:
+*     This function clears the value of a named attribute in both the base
+*     and current Frame in the FrameSet encapsulated within a Region, without
+*     remapping either Frame.
+*
+*     No error is reported if the attribute is not recognised by the base
+*     Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     attrib
+*        Pointer to a null terminated string holding the attribute name.
+*        NOTE, IT SHOULD BE ENTIRELY LOWER CASE.
+*     base_attrib
+*        Address of a location at which to return a pointer to the null
+*        terminated string holding the attribute name which was cleared in
+*        the base Frame of the encapsulated FrameSet. This may differ from
+*        the supplied attribute if the supplied attribute contains an axis
+*        index and the current->base Mapping in the FrameSet produces an
+*        axis permutation. The returned pointer should be freed using
+*        astFree when no longer needed. A NULL pointer may be supplied in
+*        which case no pointer is returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPrism *this;
+   AstRegion *creg;
+   char *batt;
+   char buf1[ 100 ];
+   char buf2[ 255 ];
+   int axis;
+   int len;
+   int nax1;
+   int nc;
+   int rep;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Prism structure. */
+   this = (AstPrism *) this_region;
+
+/* Use the RegClearAttrib method inherited from the parent class to clear the
+   attribute in the current and base Frames in the FrameSet encapsulated by
+   the parent Region structure. */
+   (*parent_regclearattrib)( this_region, attrib, &batt, status );
+
+/* We now propagate the setting down to the component Regions. The current
+   Frames in the component Regions together form a CmpFrame which is
+   equivalent to the base Frame in the parent FrameSet. Switch off error
+   reporting whilst we apply the setting to the component Regions. */
+   rep = astReporting( 0 );
+
+/* If the setting which was applied to the base Frame of the parent FrameSet
+   is qualified by an axis index, modify the axis index to refer to component
+   Region which defines the axis. First parse the base Frame attribute setting
+   to locate any axis index. */
+   len = strlen( batt );
+   if( nc = 0, ( 2 == astSscanf( batt, "%[^(](%d) %n", buf1, &axis,
+                                 &nc ) ) && ( nc >= len ) ) {
+
+/* If found, convert the axis index from one-based to zero-based. */
+      axis--;
+
+/* Get a pointer to the component Region containing the specified axis, and
+   create a new setting with the same attribute name but with the axis index
+   appropriate to the component Region which defines the axis. */
+      nax1 = astGetNaxes( this->region1 );
+      if( axis < nax1 ) {
+         creg = this->region1;
+      } else {
+         creg = this->region2;
+         axis -= nax1;
+      }
+      sprintf( buf2, "%s(%d)", buf1, axis + 1 );
+
+/* Apply the setting to the relevant component Region. */
+      astRegClearAttrib( creg, buf2, NULL );
+
+/* If the setting is not qualified by an axis index, apply it to both
+   component Regions. */
+   } else {
+      astRegClearAttrib( this->region1, batt, NULL );
+      astRegClearAttrib( this->region2, batt, NULL );
+   }
+
+/* Annul the error if the attribute was not recognised by the component
+   Regions. Then switch error reporting back on. */
+   if( astStatus == AST__BADAT ) astClearStatus;
+   astReporting( rep );
+
+/* If required, return the base Frame setting string. Otherwise free it. */
+   if( base_attrib ) {
+      *base_attrib = batt;
+   } else {
+      batt = astFree( batt );
+   }
+
+}
+
+static void RegSetAttrib( AstRegion *this_region, const char *setting,
+                          char **base_setting, int *status ) {
+/*
+*  Name:
+*     RegSetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     void RegSetAttrib( AstRegion *this, const char *setting,
+*                        char **base_setting, int *status )
+
+*  Class Membership:
+*     Prism method (over-rides the astRegSetAttrib method inherited from
+*     the Region class).
+
+*  Description:
+*     This function assigns an attribute value to both the base and
+*     current Frame in the FrameSet encapsulated within a Region, without
+*     remapping either Frame.
+*
+*     No error is reported if the attribute is not recognised by the base
+*     Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     setting
+*        Pointer to a null terminated attribute setting string. NOTE, IT
+*        SHOULD BE ENTIRELY LOWER CASE. The supplied string will be
+*        interpreted using the public interpretation implemented by
+*        astSetAttrib. This can be different to the interpretation of the
+*        protected accessor functions. For instance, the public
+*        interpretation of an unqualified floating point value for the
+*        Epoch attribute is to interpet the value as a gregorian year,
+*        but the protected interpretation is to interpret the value as an
+*        MJD.
+*     base_setting
+*        Address of a location at which to return a pointer to the null
+*        terminated attribute setting string which was applied to the
+*        base Frame of the encapsulated FrameSet. This may differ from
+*        the supplied setting if the supplied setting contains an axis
+*        index and the current->base Mapping in the FrameSet produces an
+*        axis permutation. The returned pointer should be freed using
+*        astFree when no longer needed. A NULL pointer may be supplied in
+*        which case no pointer is returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstPrism *this;
+   AstRegion *creg;
+   char *bset;
+   char buf1[ 100 ];
+   char buf2[ 255 ];
+   int axis;
+   int len;
+   int nax1;
+   int nc;
+   int rep;
+   int value;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Prism structure. */
+   this = (AstPrism *) this_region;
+
+/* Use the RegSetAttrib method inherited from the parent class to apply the
+   setting to the current and base Frames in the FrameSet encapsulated by the
+   parent Region structure. */
+   (*parent_regsetattrib)( this_region, setting, &bset, status );
+
+/* We now propagate the setting down to the component Regions. The current
+   Frames in the component Regions together form a CmpFrame which is
+   equivalent to the base Frame in the parent FrameSet. Switch off error
+   reporting whilst we apply the setting to the component Regions. */
+   rep = astReporting( 0 );
+
+/* If the setting which was applied to the base Frame of the parent FrameSet
+   is qualified by an axis index, modify the axis index to refer to component
+   Region which defines the axis. First parse the base Frame attribute setting
+   to locate any axis index. */
+   len = strlen( bset );
+   if( nc = 0, ( 2 == astSscanf( bset, "%[^(](%d)= %n%*s %n", buf1, &axis,
+                                 &value, &nc ) ) && ( nc >= len ) ) {
+
+/* If found, convert the axis index from one-based to zero-based. */
+      axis--;
+
+/* Get a pointer to the component Region containing the specified axis, and
+   create a new setting with the same attribute name but with the axis index
+   appropriate to the component Region which defines the axis. */
+      nax1 = astGetNaxes( this->region1 );
+      if( axis < nax1 ) {
+         creg = this->region1;
+      } else {
+         creg = this->region2;
+         axis -= nax1;
+      }
+      sprintf( buf2, "%s(%d)=%s", buf1, axis + 1, bset + value );
+
+/* Apply the setting to the relevant component Region. */
+      astRegSetAttrib( creg, buf2, NULL );
+
+/* If the setting is not qualified by an axis index, apply it to both
+   component Regions. */
+   } else {
+      astRegSetAttrib( this->region1, bset, NULL );
+      astRegSetAttrib( this->region2, bset, NULL );
+   }
+
+/* Annul the error if the attribute was not recognised by the component
+   Regions. Then switch error reporting back on. */
+   if( astStatus == AST__BADAT ) astClearStatus;
+   astReporting( rep );
+
+/* If required, return the base Frame setting string. Otherwise free it. */
+   if( base_setting ) {
+      *base_setting = bset;
+   } else {
+      bset = astFree( bset );
+   }
+
+}
+
+static void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status ) {
+/*
+*  Name:
+*     SetRegFS
+
+*  Purpose:
+*     Stores a new FrameSet in a Region
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     Prism method (over-rides the astSetRegFS method inherited from
+*     the Region class).
+
+*  Description:
+*     This function creates a new FrameSet and stores it in the supplied
+*     Region. The new FrameSet contains two copies of the supplied
+*     Frame, connected by a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     frm
+*        The Frame to use.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;         /* Frame containing required axes */
+   AstRegion *creg;        /* Pointer to component Region structure */
+   int *axes;              /* Pointer to array of axis indices */
+   int i;                  /* Loop count */
+   int nax1;               /* No.of axes in 1st component Frame */
+   int nax2;               /* No.of axes in 2nd component Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent method to store the FrameSet in the parent Region
+   structure. */
+   (* parent_setregfs)( this_region, frm, status );
+
+/* If either component Region has a dummy FrameSet use this method
+   recursively to give them a FrameSet containing the corresponding axes
+   from the supplied Frame. */
+   creg = ((AstPrism *) this_region )->region1;
+   if( creg ) {
+      nax1 = astGetNaxes( creg );
+      if( !astGetRegionFS( creg ) ) {
+         axes = astMalloc( sizeof( int )*(size_t) nax1 );
+         if( astOK ) for( i = 0; i < nax1; i++ ) axes[ i ] = i;
+         cfrm = astPickAxes( frm, nax1, axes, NULL );
+         astSetRegFS( creg, cfrm );
+         axes = astFree( axes );
+         cfrm = astAnnul( cfrm );
+      }
+
+   } else {
+      nax1 = 0;
+   }
+
+   creg = ((AstPrism *) this_region )->region2;
+   if( creg && !astGetRegionFS( creg ) ) {
+      nax2 = astGetNaxes( creg );
+      axes = astMalloc( sizeof( int )*(size_t) nax2 );
+      if( astOK ) for( i = 0; i < nax2; i++ ) axes[ i ] = nax1 + i;
+      cfrm = astPickAxes( frm, nax2, axes, NULL );
+      astSetRegFS( creg, cfrm );
+      axes = astFree( axes );
+      cfrm = astAnnul( cfrm );
+   }
+
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Prism method (over-rides the astSimplify method inherited from
+*     the Region class).
+
+*  Description:
+*     This function simplifies a Prism to eliminate redundant
+*     computational steps, or to merge separate steps which can be
+*     performed more efficiently in a single operation.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A new pointer to the (possibly simplified) Region.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+
+*  Deficiencies:
+*     - Currently, this function does not attempt to map the component
+*     Regions into the current Frame of the parent Region structure.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Current Frame */
+   AstFrame *newfrm1;            /* Current Frame axes for reg1 */
+   AstFrame *newfrm2;            /* Current Frame axes for reg2 */
+   AstFrameSet *fs;              /* Parent FrameSet */
+   AstMapping *bcmap;            /* Base->current Mapping */
+   AstMapping *nmap1;            /* Reg1->current Mapping */
+   AstMapping *map1;             /* First component Region from a CmpMap */
+   AstMapping *map2;             /* Second component Region from a CmpMap */
+   int series;                   /* Apply component Mappings in series? */
+   int invert1;                  /* Use inverted first component Mapping? */
+   int invert2;                  /* Use inverted second component Mapping? */
+   AstMapping *nmap2;            /* Reg2->current Mapping */
+   AstMapping *result;           /* Result pointer to return */
+   AstCmpMap *cmpmap;            /* Result pointer to return */
+   AstMapping *smap;             /* Simplified CmpMap */
+   AstRegion *new2;              /* New simplified Region */
+   AstRegion *new;               /* New Region */
+   AstRegion *newreg1;           /* Reg1 mapped into current Frame */
+   AstRegion *newreg2;           /* Reg2 mapped into current Frame */
+   AstRegion *reg1;              /* First component Region */
+   AstRegion *reg2;              /* Second component Region */
+   AstRegion *reg;               /* This Region */
+   AstRegion *snewreg1;          /* Simplified newreg1 */
+   AstRegion *snewreg2;          /* Simplified newreg2 */
+   AstRegion *unc;               /* Uncertainty Region from supplied Prism */
+   int *axin;                    /* Indices of Mapping inputs to use */
+   int *axout1;                  /* Indices of cfrm axes corresponding to reg1 */
+   int *axout2;                  /* Indices of cfrm axes corresponding to reg2 */
+   int *perm;                    /* Axis permutation array */
+   int i;                        /* Loop count */
+   int nax1;                     /* Number of axes in first component Region */
+   int nax2;                     /* Number of axes in second component Region */
+   int naxt;                     /* Total number of axes in current Frame */
+   int naxout1;                  /* Number of current axes for reg1 */
+   int naxout2;                  /* Number of current axes for reg2 */
+   int neg;                      /* Negated flag for supplied Prism */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Region structure */
+   reg = (AstRegion *) this_mapping;
+
+/* Get the component Regions, and the Negated flag for the Prism. */
+   GetRegions( (AstPrism *) this_mapping, &reg1, &reg2, &neg, status );
+
+/* The above Regions describe areas within the base Frame of the FrameSet
+   encapsulated by the parent Region structure. Get the current Frame in
+   this FrameSet and the base->current Mapping. */
+   fs = reg->frameset;
+   cfrm = astGetFrame( fs, AST__CURRENT );
+   bcmap = astGetMapping( fs, AST__BASE, AST__CURRENT );
+
+/* Combine the two Regions into a parallel CmpMap (this uses the fact
+   that a Region is a type of Mapping). Then simplify the CmpMap. This
+   will result in the astMapMerge methods defined by sub-classes of
+   Regions being used to merge adjacent regions. */
+   cmpmap = astCmpMap( reg1, reg2, 0, "", status );
+   smap = astSimplify( cmpmap );
+   cmpmap = astAnnul( cmpmap );
+
+/* Initially, assume we cannot form a new Region from the simplified
+   CmpMap. */
+   new = NULL;
+
+/* If the result is a region, use it. */
+   if( astIsARegion( smap ) ) {
+      new = astClone( smap );
+
+/* If the result is a parallel CmpMap, get its two components and check
+   they are Regions. If so, and if they are not the same as the original
+   two component Regions, form a new Prism from them. */
+   } else if( astIsACmpMap( smap ) ) {
+      astDecompose( smap, &map1, &map2, &series, &invert1, &invert2  );
+      if( ! series && astIsARegion( map1 ) && astIsARegion( map2 ) ) {
+         if( (AstRegion *) map1 != reg1 || (AstRegion *) map2 != reg2 ) {
+            new = (AstRegion *) astPrism( (AstRegion *) map1,
+                                          (AstRegion *) map2, "", status );
+         }
+      }
+
+/* Free resources */
+      map1 = astAnnul( map1 );
+      map2 = astAnnul( map2 );
+   }
+
+   smap = astAnnul( smap );
+
+/* If the above produced a simplified Region, map it into the current Frame
+   of "this" and simplify it. */
+   if( new ) {
+      new2 = astMapRegion( new, bcmap, cfrm );
+      (void) astAnnul( new );
+      new = astSimplify( new2 );
+      new2 = astAnnul( new2 );
+
+/* If the above did not produced a result, try a different approach. */
+   } else {
+
+/* Get the number of axes in each component Region. */
+      nax1 = astGetNaxes( reg1 );
+      nax2 = astGetNaxes( reg2 );
+
+/* Use astMapSplit to see if the axes of the first component Region correspond
+   to a distinct set of axes within the current Frame. If this is the case,
+   then a Mapping is returned by astMapSplit which  maps the axes of the first
+   component Region into the corresponding current Frame axes. Also returned
+   is a list of the axes within the current Frame which correspnd to the
+   axes of the first component Region. */
+      nmap1 = NULL;
+      axout1 = NULL;
+      axin = astMalloc( sizeof( int )*(size_t)nax1 );
+      if( astOK ) {
+         for( i = 0; i < nax1; i++ ) axin[ i ] = i;
+         axout1 = astMapSplit( bcmap, nax1, axin, &nmap1 );
+         axin = astFree( axin );
+      }
+
+/* Do the same for the second component. */
+      nmap2 = NULL;
+      axout2 = NULL;
+      axin = astMalloc( sizeof( int )*(size_t)nax2 );
+      if( astOK ) {
+         for( i = 0; i < nax2; i++ ) axin[ i ] = i + nax1;
+         axout2 = astMapSplit( bcmap, nax2, axin, &nmap2 );
+         axin = astFree( axin );
+      }
+
+/* Assume for the moment that the component Regions cannot be simplified.
+   In this case we will use a clone of the supplied Prism. */
+      new = astClone( this_mapping );
+
+/* Determine the number of outputs from these Mappings. */
+      if( nmap1 ){
+         naxout1 = astGetNout( nmap1 );
+      } else {
+         naxout1 = 0;
+      }
+      if( nmap2 ){
+         naxout2 = astGetNout( nmap2 );
+      } else {
+         naxout2 = 0;
+      }
+
+/* Determine the number of axes in the current Frame of the Prism. */
+      naxt = astGetNout( bcmap );
+
+/* If the second component does not contribute any axes to the total
+   Prism, we can ignore it. */
+      if( naxout1 == naxt && naxout2 == 0 ) {
+         newfrm1 = astPickAxes( cfrm, naxout1, axout1, NULL );
+         newreg1 = astMapRegion( reg1, nmap1, newfrm1 );
+         (void) astAnnul( new );
+         new = astSimplify( newreg1 );
+         if( neg ) astNegate( new );
+         perm = astMalloc( sizeof( int )*(size_t) ( naxout1 ) );
+         if( astOK ) {
+            for( i = 0; i < naxout1; i++ ) perm[ i ] = axout1[ i ];
+            astPermAxes( new, perm );
+            perm = astFree( perm );
+         }
+         newfrm1 = astAnnul( newfrm1 );
+         newreg1 = astAnnul( newreg1 );
+
+/* If the first component does not contribute any axes to the total
+   Prism, we can ignore it. */
+      } else if( naxout1 == 0 && naxout2 == naxt ) {
+         newfrm2 = astPickAxes( cfrm, naxout2, axout2, NULL );
+         newreg2 = astMapRegion( reg2, nmap2, newfrm2 );
+         (void) astAnnul( new );
+         new = astSimplify( newreg2 );
+         if( neg ) astNegate( new );
+         perm = astMalloc( sizeof( int )*(size_t) ( naxout2 ) );
+         if( astOK ) {
+            for( i = 0; i < naxout2; i++ ) perm[ i ] = axout2[ i ];
+            astPermAxes( new, perm );
+            perm = astFree( perm );
+         }
+         newfrm2 = astAnnul( newfrm2 );
+         newreg2 = astAnnul( newreg2 );
+
+/* If both component Regions correspond to a distinct subspace within the
+   current Frame, then we can try to express each component Region within
+   the current Frame. */
+      } else if( nmap1 && nmap2 ) {
+
+/* Create a Frame representing the subspace of the current Frame which
+   corresponds to the axes of the first component Region. */
+         newfrm1 = astPickAxes( cfrm, naxout1, axout1, NULL );
+
+/* Remap the first component Region so that it represents an area in this
+   subspace. */
+         newreg1 = astMapRegion( reg1, nmap1, newfrm1 );
+
+/* Attempt to simplify the remapped Region. */
+         snewreg1 = astSimplify( newreg1 );
+
+/* Do the same for the second component Region. */
+         naxout2 = astGetNout( nmap2 );
+         newfrm2 = astPickAxes( cfrm, naxout2, axout2, NULL );
+         newreg2 = astMapRegion( reg2, nmap2, newfrm2 );
+         snewreg2 = astSimplify( newreg2 );
+
+/* If either component Region was simplified, create a new Prism from the
+   simplified Regions. */
+         if( snewreg1 != newreg1 || snewreg2 != newreg2 ) {
+            (void) astAnnul( new );
+            new = (AstRegion *) astPrism( snewreg1, snewreg2, "", status );
+
+/* Ensure the new Prism has the same Negated attribute as the original. */
+            if( neg ) astNegate( new );
+
+/* Ensure that the new Prism has the same axis order as the original
+   current Frame. */
+            perm = astMalloc( sizeof( int )*(size_t) ( naxout1 + naxout2 ) );
+            if( astOK ) {
+               for( i = 0; i < naxout1; i++ ) perm[ i ] = axout1[ i ];
+               for( ; i < naxout1 + naxout2; i++ ) perm[ i ] = axout2[ i - naxout1 ];
+               astPermAxes( new, perm );
+               perm = astFree( perm );
+            }
+         }
+
+/* Free resources. */
+         newfrm1 = astAnnul( newfrm1 );
+         newfrm2 = astAnnul( newfrm2 );
+         newreg1 = astAnnul( newreg1 );
+         newreg2 = astAnnul( newreg2 );
+         snewreg1 = astAnnul( snewreg1 );
+         snewreg2 = astAnnul( snewreg2 );
+      }
+
+/* Free resources. */
+      if( axout1 ) axout1 = astFree( axout1 );
+      if( axout2 ) axout2 = astFree( axout2 );
+      if( nmap1 ) nmap1 = astAnnul( nmap1 );
+      if( nmap2 ) nmap2 = astAnnul( nmap2 );
+   }
+
+/* If we have created a new Region, ensure any user-supplied uncertainty
+   that has been stored explicitly with the supplied Prism is passed on
+   to the new Region. */
+   if( new ) {
+      if( astTestUnc( reg ) ) {
+         unc = astGetUnc( reg, 0 );
+         astSetUnc( new, unc );
+      }
+
+/* Now invoke the parent Simplify method inherited from the Region class.
+   This will simplify the encapsulated FrameSet and uncertainty Region. */
+      result = (*parent_simplify)( (AstMapping *) new, status );
+      new = astAnnul( new );
+   }
+
+/* Free resources. */
+   reg1 = astAnnul( reg1 );
+   reg2 = astAnnul( reg2 );
+   cfrm = astAnnul( cfrm );
+   bcmap = astAnnul( bcmap );
+
+/* If any simplification could be performed, copy Region attributes from
+   the supplied Region to the returned Region, and return a pointer to it. */
+   if( result != this_mapping ) astRegOverlay( result, (AstRegion *) this_mapping, 0 );
+
+/* If an error occurred, annul the returned Mapping. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a Prism to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     Prism member function (over-rides the astTransform method inherited
+*     from the Region class).
+
+*  Description:
+*     This function takes a Prism and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required Region.
+*     This implies applying each of the Prism's component Regions in turn,
+*     either in series or in parallel.
+
+*  Parameters:
+*     this
+*        Pointer to the Prism.
+*     in
+*        Pointer to the PointSet associated with the input coordinate values.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the Prism being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstCmpMap *map;               /* CmpMap containing component Regions */
+   AstPointSet *psb;             /* Pointer to base Frame PointSet */
+   AstPointSet *pset_tmp;        /* Pointer to PointSet holding base Frame positions*/
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstPrism *this;               /* Pointer to the Prism structure */
+   AstRegion *reg1;              /* Pointer to first component Region */
+   AstRegion *reg2;              /* Pointer to second component Region */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double **ptrb;                /* Pointer to base Frame axis values */
+   int coord;                    /* Zero-based index for coordinates */
+   int good;                     /* Is the point inside the Prism? */
+   int ncoord_out;               /* No. of coordinates per output point */
+   int ncoord_tmp;               /* No. of coordinates per base Frame point */
+   int neg;                      /* Has Prism been negated? */
+   int npoint;                   /* No. of points */
+   int point;                    /* Loop counter for points */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a Pointer to the Prism structure */
+   this = (AstPrism *) this_mapping;
+
+/* Get the component Regions, and the Negated value for the Prism. */
+   GetRegions( this, &reg1, &reg2, &neg, status );
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Region class. This function validates
+   all arguments and generates an output PointSet if necessary, containing
+   a copy of the input PointSet. */
+   result = (*parent_transform)( this_mapping, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* First use the encapsulated FrameSet in the parent Region structure to
+   transform the supplied positions from the current Frame in the
+   encapsulated FrameSet (the Frame represented by the Prism), to the
+   base Frame (the Frame in which the component Regions are defined). Note,
+   the returned pointer may be a clone of the "in" pointer, and so we
+   must be carefull not to modify the contents of the returned PointSet. */
+   pset_tmp = astRegTransform( this, in, 0, NULL, NULL );
+
+/* Form a parallel CmpMap from the two component Regions. */
+   map = astCmpMap( reg1, reg2, 0, "", status );
+
+/* Apply the Mapping to the PointSet containing positions in the base Frame
+   of the parent Region structure (which is the same as the combination of
+   the current Frames of the component Regions). */
+   psb = astTransform( map, pset_tmp, 1, NULL );
+
+/* Annul the Mapping pointer. */
+   map = astAnnul( map );
+
+/* Determine the numbers of points and coordinates per point for these base
+   Frame PointSets and obtain pointers for accessing the base Frame and output
+   coordinate values. */
+   npoint = astGetNpoint( pset_tmp );
+   ncoord_tmp = astGetNcoord( pset_tmp );
+   ptrb = astGetPoints( psb );
+   ncoord_out = astGetNcoord( result );
+   ptr_out = astGetPoints( result );
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+      for ( point = 0; point < npoint; point++ ) {
+         good = 1;
+
+         for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+            if( ptrb[ coord ][ point ] == AST__BAD ){
+               good = 0;
+               break;
+            }
+         }
+
+         if( good == neg ) {
+            for ( coord = 0; coord < ncoord_out; coord++ ) {
+               ptr_out[ coord ][ point ] = AST__BAD;
+            }
+         }
+      }
+   }
+
+/* Free resources. */
+   reg1 = astAnnul( reg1 );
+   reg2 = astAnnul( reg2 );
+   psb = astAnnul( psb );
+   pset_tmp = astAnnul( pset_tmp );
+
+/* If an error occurred, clean up by deleting the output PointSet (if
+   allocated by this function) and setting a NULL result pointer. */
+   if ( !astOK ) {
+      if ( !out ) result = astDelete( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Prism objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Prism objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the component
+*     Regions within the Prism.
+*/
+
+/* Local Variables: */
+   AstPrism *in;                /* Pointer to input Prism */
+   AstPrism *out;               /* Pointer to output Prism */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Prisms. */
+   in = (AstPrism *) objin;
+   out = (AstPrism *) objout;
+
+/* For safety, start by clearing any references to the input component
+   Regions from the output Prism. */
+   out->region1 = NULL;
+   out->region2 = NULL;
+
+/* Make copies of these Regions and store pointers to them in the output
+   Prism structure. */
+   out->region1 = astCopy( in->region1 );
+   out->region2 = astCopy( in->region2 );
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Prism objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Prism objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstPrism *this;              /* Pointer to Prism */
+
+/* Obtain a pointer to the Prism structure. */
+   this = (AstPrism *) obj;
+
+/* Annul the pointers to the component Regions. */
+   this->region1 = astAnnul( this->region1 );
+   this->region2 = astAnnul( this->region2 );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Prism objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Prism class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Prism whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstPrism *this;               /* Pointer to the Prism structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Prism structure. */
+   this = (AstPrism *) this_object;
+
+/* Write out values representing the instance variables for the Prism
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* First Region. */
+/* -------------- */
+   astWriteObject( channel, "RegionA", 1, 1, this->region1,
+                   "First component Region" );
+
+/* Second Region. */
+/* --------------- */
+   astWriteObject( channel, "RegionB", 1, 1, this->region2,
+                   "Second component Region" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAPrism and astCheckPrism functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Prism,Region)
+astMAKE_CHECK(Prism)
+
+AstPrism *astPrism_( void *region1_void, void *region2_void,
+                     const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astPrism
+
+*  Purpose:
+*     Create a Prism.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     AstPrism *astPrism( AstRegion *region1, AstRegion *region2,
+*                         const char *options, ..., int *status )
+
+*  Class Membership:
+*     Prism constructor.
+
+*  Description:
+*     This function creates a new Prism and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     region1
+*        Pointer to the Region to be extruded.
+*     region2
+*        Pointer to the Region defining the extent of the extrusion.
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new Prism. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new Prism.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic Prism constructor which is
+*     available via the protected interface to the Prism class.  A
+*     public interface is provided by the astPrismId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "region1" and "region2" parameters are of type (void *) and are
+*     converted and validated within the function itself.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPrism *new;                  /* Pointer to new Prism */
+   AstRegion *region1;             /* Pointer to first Region structure */
+   AstRegion *region2;             /* Pointer to second Region structure */
+   va_list args;                   /* Variable argument list */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain and validate pointers to the Region structures provided. */
+   region1 = astCheckRegion( region1_void );
+   region2 = astCheckRegion( region2_void );
+   if ( astOK ) {
+
+/* Initialise the Prism, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitPrism( NULL, sizeof( AstPrism ), !class_init,
+                          &class_vtab, "Prism", region1, region2 );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new Prism's
+   attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new Prism. */
+   return new;
+}
+
+AstPrism *astPrismId_( void *region1_void, void *region2_void,
+                       const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astPrism
+f     AST_PRISM
+
+*  Purpose:
+*     Create a Prism.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "prism.h"
+c     AstPrism *astPrism( AstRegion *region1, AstRegion *region2,
+c                         const char *options, ... )
+f     RESULT = AST_PRISM( REGION1, REGION2, OPTIONS, STATUS )
+
+*  Class Membership:
+*     Prism constructor.
+
+*  Description:
+*     This function creates a new Prism and optionally initialises
+*     its attributes.
+*
+*     A Prism is a Region which represents an extrusion of an existing Region
+*     into one or more orthogonal dimensions (specified by another Region).
+*     If the Region to be extruded has N axes, and the Region defining the
+*     extrusion has M axes, then the resulting Prism will have (M+N) axes.
+*     A point is inside the Prism if the first N axis values correspond to
+*     a point inside the Region being extruded, and the remaining M axis
+*     values correspond to a point inside the Region defining the extrusion.
+*
+*     As an example, a cylinder can be represented by extruding an existing
+*     Circle, using an Interval to define the extrusion. Ih this case, the
+*     Interval would have a single axis and would specify the upper and
+*     lower limits of the cylinder along its length.
+
+*  Parameters:
+c     region1
+f     REGION1 = INTEGER (Given)
+*        Pointer to the Region to be extruded.
+c     region2
+f     REGION2 = INTEGER (Given)
+*        Pointer to the Region defining the extent of the extrusion.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Prism. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Prism. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astPrism()
+f     AST_PRISM = INTEGER
+*        A pointer to the new Prism.
+
+*  Notes:
+*     - Deep copies are taken of the supplied Regions. This means that
+*     any subsequent changes made to the component Regions using the
+*     supplied pointers will have no effect on the Prism.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astPrism constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astPrism_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - Because no checking or casting of arguments is performed
+*     before the function is invoked, the "region1" and "region2" parameters
+*     are of type (void *) and are converted from an ID value to a
+*     pointer and validated within the function itself.
+*     - The variable argument list also prevents this function from
+*     invoking astPrism_ directly, so it must be a re-implementation
+*     of it in all respects, except for the conversions between IDs
+*     and pointers on input/output of Objects.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS              /* Pointer to thread-specific global data */
+   AstPrism *new;                  /* Pointer to new Prism */
+   AstRegion *region1;             /* Pointer to first Region structure */
+   AstRegion *region2;             /* Pointer to second Region structure */
+   int *status;                    /* Pointer to inherited status value */
+   va_list args;                   /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain the Region pointers from the ID's supplied and validate the
+   pointers to ensure they identify valid Regions. */
+   region1 = astVerifyRegion( astMakePointer( region1_void ) );
+   region2 = astVerifyRegion( astMakePointer( region2_void ) );
+   if ( astOK ) {
+
+/* Initialise the Prism, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitPrism( NULL, sizeof( AstPrism ), !class_init,
+                          &class_vtab, "Prism", region1, region2 );
+
+/* If successful, note that the virtual function table has been initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new Prism's
+   attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return an ID value for the new Prism. */
+   return astMakeId( new );
+}
+
+AstPrism *astInitPrism_( void *mem, size_t size, int init, AstPrismVtab *vtab,
+                         const char *name, AstRegion *region1,
+                         AstRegion *region2, int *status ) {
+/*
+*+
+*  Name:
+*     astInitPrism
+
+*  Purpose:
+*     Initialise a Prism.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     AstPrism *astInitPrism_( void *mem, size_t size, int init,
+*                              AstPrismVtab *vtab, const char *name,
+*                              AstRegion *region1, AstRegion *region2 )
+
+*  Class Membership:
+*     Prism initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Prism object. It allocates memory (if necessary) to
+*     accommodate the Prism plus any additional data associated with the
+*     derived class. It then initialises a Prism structure at the start
+*     of this memory. If the "init" flag is set, it also initialises the
+*     contents of a virtual function table for a Prism at the start of
+*     the memory passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Prism is to be initialised.
+*        This must be of sufficient size to accommodate the Prism data
+*        (sizeof(Prism)) plus any data used by the derived class. If a
+*        value of NULL is given, this function will allocate the memory itself
+*        using the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Prism (plus derived class
+*        data). This will be used to allocate memory if a value of NULL is
+*        given for the "mem" parameter. This value is also stored in the
+*        Prism structure, so a valid value must be supplied even if not
+*        required for allocating memory.
+*     init
+*        A logical flag indicating if the Prism's virtual function table
+*        is to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Prism.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     region1
+*        Pointer to the first Region.
+*     region2
+*        Pointer to the second Region.
+
+*  Returned Value:
+*     A pointer to the new Prism.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstPrism *new;                /* Pointer to new Prism */
+   AstFrame *frm1;               /* Frame encapsulated by 1st Region */
+   AstFrame *frm2;               /* Frame encapsulated by 2nd Region */
+   AstFrame *frm;                /* CmpFrame formed from frm1 and frm2 */
+   AstMapping *map;              /* Mapping between two supplied Regions */
+   AstRegion *reg1;              /* Copy of first supplied Region */
+   AstRegion *reg2;              /* Copy of second supplied Region */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitPrismVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+   reg2 = NULL;
+
+/* Take a copy of the two supplied Regions. */
+   reg1 = astCopy( region1 );
+   reg2 = astCopy( region2 );
+
+/* Form a CmpFrame representing the combined Frame of these two Regions. */
+   frm1 = astRegFrame( reg1 );
+   frm2 = astRegFrame( reg2 );
+   frm = (AstFrame *) astCmpFrame( frm1, frm2, "", status );
+
+/* Initialise a Region structure (the parent class) as the first component
+   within the Prism structure, allocating memory if necessary. A NULL
+   PointSet is suppled as the two component Regions will perform the function
+   of defining the Region shape. The base Frame of the FrameSet in the
+   parent Region structure will be the CmpFrame formed from the two component
+   Regions. */
+   if ( astOK ) {
+      new = (AstPrism *) astInitRegion( mem, size, 0, (AstRegionVtab *) vtab,
+                                        name, frm, NULL, NULL );
+
+/* Initialise the Prism data. */
+/* --------------------------- */
+/* Store pointers to the component Regions. */
+      new->region1 = reg1;
+      new->region2 = reg2;
+
+/* If the base->current Mapping in the FrameSet within a component Region
+   is a UnitMap, then the FrameSet does not need to be included in the
+   Dump of the new Prism. Set the RegionFS attribute of the component
+   Region to zero to flag this. */
+      map = astGetMapping( reg1->frameset, AST__BASE, AST__CURRENT );
+      if( astIsAUnitMap( map ) ) astSetRegionFS( reg1, 0 );
+      map = astAnnul( map );
+
+      map = astGetMapping( reg2->frameset, AST__BASE, AST__CURRENT );
+      if( astIsAUnitMap( map ) ) astSetRegionFS( reg2, 0 );
+      map = astAnnul( map );
+
+/* If an error occurred, clean up by annulling the Region pointers and
+   deleting the new object. */
+      if ( !astOK ) {
+         new->region1 = astAnnul( new->region1 );
+         new->region2 = astAnnul( new->region2 );
+         new = astDelete( new );
+      }
+   }
+
+/* Free resources */
+   frm = astAnnul( frm );
+   frm1 = astAnnul( frm1 );
+   frm2 = astAnnul( frm2 );
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstPrism *astLoadPrism_( void *mem, size_t size, AstPrismVtab *vtab,
+                         const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadPrism
+
+*  Purpose:
+*     Load a Prism.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "prism.h"
+*     AstPrism *astLoadPrism( void *mem, size_t size, AstPrismVtab *vtab,
+*                             const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     Prism loader.
+
+*  Description:
+*     This function is provided to load a new Prism using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Prism structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Prism at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Prism is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Prism data (sizeof(Prism)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Prism (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Prism structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstPrism) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Prism. If this is NULL, a pointer to
+*        the (static) virtual function table for the Prism class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Prism" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Prism.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS      /* Pointer to thread-specific global data */
+   AstFrame *cfrm;         /* Frame containing required axes */
+   AstFrame *f1;           /* Base Frame in parent Region */
+   AstPrism *new;          /* Pointer to the new Prism */
+   AstRegion *creg;        /* Pointer to component Region */
+   int *axes;              /* Pointer to array of axis indices */
+   int i;                  /* Loop count */
+   int nax1;               /* No.of axes in 1st component Frame */
+   int nax2;               /* No.of axes in 2nd component Frame */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Prism. In this case the
+   Prism belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstPrism );
+      vtab = &class_vtab;
+      name = "Prism";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitPrismVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Prism. */
+   new = astLoadRegion( mem, size, (AstRegionVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Prism" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* First Region. */
+/* -------------- */
+      new->region1 = astReadObject( channel, "regiona", NULL );
+
+/* Second Region. */
+/* --------------- */
+      new->region2 = astReadObject( channel, "regionb", NULL );
+
+/* Either component Region may currently contain a dummy FrameSet rather than
+   the correct FrameSet (see the Dump function for this class). In this case,
+   the correct FrameSet will have copies of selected axes from the base Frame
+   of the new Prism as both its current and base Frames, and these are
+   connected by a UnitMap (this is equivalent to a FrameSet containing a
+   single Frame). However if the new Prism being loaded has itself got a dummy
+   FrameSet, then we do not do this since we do not yet know what the correct
+   FrameSet is. In this case we wait until the parent Region invokes the
+   astSetRegFS method on the new Prism. */
+      if( !astRegDummyFS( new ) ) {
+         f1 = astGetFrame( ((AstRegion *) new)->frameset, AST__BASE );
+
+         creg = new->region1;
+         nax1 = astGetNaxes( creg );
+         if( astRegDummyFS( creg ) ) {
+            axes = astMalloc( sizeof( int )*(size_t) nax1 );
+            if( astOK ) for( i = 0; i < nax1; i++ ) axes[ i ] = i;
+            cfrm = astPickAxes( f1, nax1, axes, NULL );
+            astSetRegFS( creg, cfrm );
+            axes = astFree( axes );
+            cfrm = astAnnul( cfrm );
+         }
+
+         creg = new->region2;
+         if( astRegDummyFS( creg ) ) {
+            nax2 = astGetNaxes( creg );
+            axes = astMalloc( sizeof( int )*(size_t) nax2 );
+            if( astOK ) for( i = 0; i < nax2; i++ ) axes[ i ] = nax1 + i;
+            cfrm = astPickAxes( f1, nax2, axes, NULL );
+            astSetRegFS( creg, cfrm );
+            axes = astFree( axes );
+            cfrm = astAnnul( cfrm );
+         }
+
+         f1 = astAnnul( f1 );
+      }
+
+/* If an error occurred, clean up by deleting the new Prism. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Prism pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+/* None. */
+
+
+
diff --git a/ast/prism.h b/ast/prism.h
new file mode 100644
index 0000000..ab14c77
--- /dev/null
+++ b/ast/prism.h
@@ -0,0 +1,238 @@
+#if !defined( PRISM_INCLUDED ) /* Include this file only once */
+#define PRISM_INCLUDED
+/*
+*+
+*  Name:
+*     prism.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Prism class.
+
+*  Invocation:
+*     #include "prism.h"
+
+*  Description:
+*     This include file defines the interface to the Prism class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The Prism class implement a Region which represents an extrusion of
+*     another Region into higher dimensions. For instance, a Prism can be
+*     used to represent a cylinder, which is an extrusion of a circle into a
+*     3rd dimension.
+
+*  Inheritance:
+*     The Prism class inherits from the Region class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     17-DEC-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "region.h"              /* Coordinate regions (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros. */
+/* ------- */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* Prism structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+typedef struct AstPrism {
+
+/* Attributes inherited from the parent class. */
+   AstRegion region;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstRegion *region1;           /* First component Region */
+   AstRegion *region2;           /* Second component Region */
+
+} AstPrism;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstPrismVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstRegionVtab region_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+} AstPrismVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstPrismGlobals {
+   AstPrismVtab Class_Vtab;
+   int Class_Init;
+} AstPrismGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitPrismGlobals_( AstPrismGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Prism)          /* Check class membership */
+astPROTO_ISA(Prism)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstPrism *astPrism_( void *, void *, const char *, int *, ...);
+#else
+AstPrism *astPrismId_( void *, void *, const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstPrism *astInitPrism_( void *, size_t, int, AstPrismVtab *,
+                     const char *, AstRegion *, AstRegion *, int * );
+
+/* Vtab initialiser. */
+void astInitPrismVtab_( AstPrismVtab *, const char *, int * );
+
+/* Loader. */
+AstPrism *astLoadPrism_( void *, size_t, AstPrismVtab *,
+                         const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+AstRegion *astConvertToPrism_( AstRegion *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckPrism(this) astINVOKE_CHECK(Prism,this,0)
+#define astVerifyPrism(this) astINVOKE_CHECK(Prism,this,1)
+
+/* Test class membership. */
+#define astIsAPrism(this) astINVOKE_ISA(Prism,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astPrism astINVOKE(F,astPrism_)
+#else
+#define astPrism astINVOKE(F,astPrismId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitPrism(mem,size,init,vtab,name,reg1,reg2) \
+astINVOKE(O,astInitPrism_(mem,size,init,vtab,name,reg1,reg2,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitPrismVtab(vtab,name) astINVOKE(V,astInitPrismVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadPrism(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadPrism_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckPrism to validate Prism pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astConvertToPrism(this) astConvertToPrism_(this,STATUS_PTR)
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/proj.c b/ast/proj.c
new file mode 100644
index 0000000..8b92c21
--- /dev/null
+++ b/ast/proj.c
@@ -0,0 +1,4840 @@
+/*============================================================================
+*
+*   WCSLIB - an implementation of the FITS WCS proposal.
+*   Copyright (C) 1995-2002, Mark Calabretta
+*
+*   This library is free software; you can redistribute it and/or modify it
+*   under the terms of the GNU Library General Public License as published
+*   by the Free Software Foundation; either version 2 of the License, or (at
+*   your option) any later version.
+*
+*   This library is distributed in the hope that it will be useful, but
+*   WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library
+*   General Public License for more details.
+*
+*   You should have received a copy of the GNU Library General Public License
+*   along with this library; if not, write to the Free Software Foundation,
+*   Inc., 51 Franklin Street,Fifth Floor, Boston, MA 02110-1301, USA
+*
+*   Correspondence concerning WCSLIB may be directed to:
+*      Internet email: [email-masked]
+*      Postal address: Dr. Mark Calabretta,
+*                      Australia Telescope National Facility,
+*                      P.O. Box 76,
+*                      Epping, NSW, 2121,
+*                      AUSTRALIA
+*
+*
+*=============================================================================
+*
+*  This version of proj.c is based on the version in wcslib-2.9, but has
+*  been modified in the following ways by the Starlink project (e-mail:
+*  [email-masked]):
+*     -  The copysign macro is now always defined within this file
+*        instead of only being defined if the COPYSIGN macro has previously
+*        been defined.
+*     -  Sine values which are slightly larger than 1.0 are now treated
+*        as 1.0 in function astCYPrev.
+*     -  The maximum number of projection parameters has been changed from
+*        10 to 100.
+*     -  The maximum number of projection parameters is given by the
+*        WCSLIB_MXPAR macro (defined in proj.h) instead of being hard-wired.
+*     -  The names of all functions and structures have been chanegd to avoid
+*        clashes with wcslib. This involves adding "Ast" or "ast" at the
+*        front and changing the capitalisation.
+*     -  Include string.h (for strcpy and strcmp prototypes).
+*     -  Include stdlib.h (for abs prototype).
+*     -  Comment out declarations of npcode and pcodes variables (they
+*        are not needed by AST) in order to avoid clash with similar names
+*        in other modules imported as part of other software systems (e.g.
+*        SkyCat).
+*     -  astZPNfwd: Loop from prj->n to zero, not from MAXPAR to zero.
+*     -  astZPNfwd: Only return "2" if prj->n is larger than 2.
+*     -  Lots of variables are initialised to null values in order to
+*        avoid "use of uninitialised variable" messages from compilers which
+*        are not clever enough to work out that the uninitialised variable is
+*        not in fact ever used.
+*     -  Use dynamic rather than static memory for the parameter arrays in
+*        the AstPrjPrm structure.Override astGetObjSize. This is to
+*        reduce the in-memory size of a WcsMap.
+*     -  HPX and XPH projections included from a more recent version of WCSLIB,
+*        and modified to use scalar instead of vector positions
+*     -  The expressions for xc in astHPXrev and phic in astHPXfwd have
+*        been conditioned differently to the WCSLIB code in order to improve
+*        accuracy of the floor function for arguments very slightly below an
+*        integer value.
+
+*=============================================================================
+*
+*   C implementation of the spherical map projections recognized by the FITS
+*   "World Coordinate System" (WCS) convention.
+*
+*   Summary of routines
+*   -------------------
+*   Each projection is implemented via separate functions for the forward,
+*   *fwd(), and reverse, *rev(), transformation.
+*
+*   Initialization routines, *set(), compute intermediate values from the
+*   projection parameters but need not be called explicitly - see the
+*   explanation of prj.flag below.
+*
+*      astPRJset astPRJfwd astPRJrev   Driver routines (see below).
+*
+*      astAZPset astAZPfwd astAZPrev   AZP: zenithal/azimuthal perspective
+*      astSZPset astSZPfwd astSZPrev   SZP: slant zenithal perspective
+*      astTANset astTANfwd astTANrev   TAN: gnomonic
+*      astSTGset astSTGfwd astSTGrev   STG: stereographic
+*      astSINset astSINfwd astSINrev   SIN: orthographic/synthesis
+*      astARCset astARCfwd astARCrev   ARC: zenithal/azimuthal equidistant
+*      astZPNset astZPNfwd astZPNrev   ZPN: zenithal/azimuthal polynomial
+*      astZEAset astZEAfwd astZEArev   ZEA: zenithal/azimuthal equal area
+*      astAIRset astAIRfwd astAIRrev   AIR: Airy
+*      astCYPset astCYPfwd astCYPrev   CYP: cylindrical perspective
+*      astCEAset astCEAfwd astCEArev   CEA: cylindrical equal area
+*      astCARset astCARfwd astCARrev   CAR: Cartesian
+*      astMERset astMERfwd astMERrev   MER: Mercator
+*      astSFLset astSFLfwd astSFLrev   SFL: Sanson-Flamsteed
+*      astPARset astPARfwd astPARrev   PAR: parabolic
+*      astMOLset astMOLfwd astMOLrev   MOL: Mollweide
+*      astAITset astAITfwd astAITrev   AIT: Hammer-Aitoff
+*      astCOPset astCOPfwd astCOPrev   COP: conic perspective
+*      astCOEset astCOEfwd astCOErev   COE: conic equal area
+*      astCODset astCODfwd astCODrev   COD: conic equidistant
+*      astCOOset astCOOfwd astCOOrev   COO: conic orthomorphic
+*      astBONset astBONfwd astBONrev   BON: Bonne
+*      astPCOset astPCOfwd astPCOrev   PCO: polyconic
+*      astTSCset astTSCfwd astTSCrev   TSC: tangential spherical cube
+*      astCSCset astCSCfwd astCSCrev   CSC: COBE quadrilateralized spherical cube
+*      astQSCset astQSCfwd astQSCrev   QSC: quadrilateralized spherical cube
+*      astHPXset astHPXfwd astHPXrev   HPX: HEALPix projection
+*      astXPHset astXPHfwd astXPHrev   XPH: HEALPix polar, aka "butterfly"
+*
+*
+*   Driver routines; astPRJset(), astPRJfwd() & astPRJrev()
+*   ----------------------------------------------
+*   A set of driver routines are available for use as a generic interface to
+*   the specific projection routines.  The interfaces to astPRJfwd() and astPRJrev()
+*   are the same as those of the forward and reverse transformation routines
+*   for the specific projections (see below).
+*
+*   The interface to astPRJset() differs slightly from that of the initialization
+*   routines for the specific projections and unlike them it must be invoked
+*   explicitly to use astPRJfwd() and astPRJrev().
+*
+*   Given:
+*      pcode[4] const char
+*                        WCS projection code.
+*
+*   Given and/or returned:
+*      prj      AstPrjPrm*  Projection parameters (see below).
+*
+*   Function return value:
+*               int      Error status
+*                           0: Success.
+*
+*
+*   Initialization routine; *set()
+*   ------------------------------
+*   Initializes members of a AstPrjPrm data structure which hold intermediate
+*   values.  Note that this routine need not be called directly; it will be
+*   invoked by astPRJfwd() and astPRJrev() if the "flag" structure member is
+*   anything other than a predefined magic value.
+*
+*   Given and/or returned:
+*      prj      AstPrjPrm*  Projection parameters (see below).
+*
+*   Function return value:
+*               int      Error status
+*                           0: Success.
+*                           1: Invalid projection parameters.
+*
+*   Forward transformation; *fwd()
+*   -----------------------------
+*   Compute (x,y) coordinates in the plane of projection from native spherical
+*   coordinates (phi,theta).
+*
+*   Given:
+*      phi,     const double
+*      theta             Longitude and latitude of the projected point in
+*                        native spherical coordinates, in degrees.
+*
+*   Given and returned:
+*      prj      AstPrjPrm*  Projection parameters (see below).
+*
+*   Returned:
+*      x,y      double*  Projected coordinates.
+*
+*   Function return value:
+*               int      Error status
+*                           0: Success.
+*                           1: Invalid projection parameters.
+*                           2: Invalid value of (phi,theta).
+*
+*   Reverse transformation; *rev()
+*   -----------------------------
+*   Compute native spherical coordinates (phi,theta) from (x,y) coordinates in
+*   the plane of projection.
+*
+*   Given:
+*      x,y      const double
+*                        Projected coordinates.
+*
+*   Given and returned:
+*      prj      AstPrjPrm*  Projection parameters (see below).
+*
+*   Returned:
+*      phi,     double*  Longitude and latitude of the projected point in
+*      theta             native spherical coordinates, in degrees.
+*
+*   Function return value:
+*               int      Error status
+*                           0: Success.
+*                           1: Invalid projection parameters.
+*                           2: Invalid value of (x,y).
+*                           1: Invalid projection parameters.
+*
+*   Projection parameters
+*   ---------------------
+*   The AstPrjPrm struct consists of the following:
+*
+*      int flag
+*         This flag must be set to zero whenever any of p[] or r0 are set
+*         or changed.  This signals the initialization routine to recompute
+*         intermediaries.  flag may also be set to -1 to disable strict bounds
+*         checking for the AZP, SZP, TAN, SIN, ZPN, and COP projections.
+*
+*      double r0
+*         r0; The radius of the generating sphere for the projection, a linear
+*         scaling parameter.  If this is zero, it will be reset to the default
+*         value of 180/pi (the value for FITS WCS).
+*
+*      double p[]
+*         Contains the projection parameters associated with the
+*         longitude axis.
+*
+*   The remaining members of the AstPrjPrm struct are maintained by the
+*   initialization routines and should not be modified.  This is done for the
+*   sake of efficiency and to allow an arbitrary number of contexts to be
+*   maintained simultaneously.
+*
+*      char code[4]
+*         Three-letter projection code.
+*
+*      double phi0, theta0
+*         Native longitude and latitude of the reference point, in degrees.
+*
+*      double w[10]
+*      int n
+*         Intermediate values derived from the projection parameters.
+*
+*      int (*astPRJfwd)()
+*      int (*astPRJrev)()
+*         Pointers to the forward and reverse projection routines.
+*
+*   Usage of the p[] array as it applies to each projection is described in
+*   the prologue to each trio of projection routines.
+*
+*   Argument checking
+*   -----------------
+*   Forward routines:
+*
+*      The values of phi and theta (the native longitude and latitude)
+*      normally lie in the range [-180,180] for phi, and [-90,90] for theta.
+*      However, all forward projections will accept any value of phi and will
+*      not normalize it.
+*
+*      The forward projection routines do not explicitly check that theta lies
+*      within the range [-90,90].  They do check for any value of theta which
+*      produces an invalid argument to the projection equations (e.g. leading
+*      to division by zero).  The forward routines for AZP, SZP, TAN, SIN,
+*      ZPN, and COP also return error 2 if (phi,theta) corresponds to the
+*      overlapped (far) side of the projection but also return the
+*      corresponding value of (x,y).  This strict bounds checking may be
+*      relaxed by setting prj->flag to -1 (rather than 0) when these
+*      projections are initialized.
+*
+*   Reverse routines:
+*
+*      Error checking on the projected coordinates (x,y) is limited to that
+*      required to ascertain whether a solution exists.  Where a solution does
+*      exist no check is made that the value of phi and theta obtained lie
+*      within the ranges [-180,180] for phi, and [-90,90] for theta.
+*
+*   Accuracy
+*   --------
+*   Closure to a precision of at least 1E-10 degree of longitude and latitude
+*   has been verified for typical projection parameters on the 1 degree grid
+*   of native longitude and latitude (to within 5 degrees of any latitude
+*   where the projection may diverge).
+*
+*   Author: Mark Calabretta, Australia Telescope National Facility
+*   $Id$
+*===========================================================================*/
+
+/* Set the name of the module we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. NB, this module is not a proper AST
+   class, but it defines this macro sanyway in order to get the protected
+   symbols defined in memory.h */
+
+#include <math.h>
+#include <string.h>
+#include <stdlib.h>
+#include "wcsmath.h"
+#include "wcstrig.h"
+#include "memory.h"
+#include "proj.h"
+
+/* Following variables are not needed in AST and are commented out to
+   avoid name clashes with other software systems (e.g. SkyCat) which
+   defines them.
+
+int  npcode = 28;
+char pcodes[28][4] =
+      {"AZP", "SZP", "TAN", "STG", "SIN", "ARC", "ZPN", "ZEA", "AIR", "CYP",
+       "CEA", "CAR", "MER", "COP", "COE", "COD", "COO", "SFL", "PAR", "MOL",
+       "AIT", "BON", "PCO", "TSC", "CSC", "QSC", "HPX", "XPH"};
+*/
+
+const int WCS__AZP = 101;
+const int WCS__SZP = 102;
+const int WCS__TAN = 103;
+const int WCS__STG = 104;
+const int WCS__SIN = 105;
+const int WCS__ARC = 106;
+const int WCS__ZPN = 107;
+const int WCS__ZEA = 108;
+const int WCS__AIR = 109;
+const int WCS__CYP = 201;
+const int WCS__CEA = 202;
+const int WCS__CAR = 203;
+const int WCS__MER = 204;
+const int WCS__SFL = 301;
+const int WCS__PAR = 302;
+const int WCS__MOL = 303;
+const int WCS__AIT = 401;
+const int WCS__COP = 501;
+const int WCS__COE = 502;
+const int WCS__COD = 503;
+const int WCS__COO = 504;
+const int WCS__BON = 601;
+const int WCS__PCO = 602;
+const int WCS__TSC = 701;
+const int WCS__CSC = 702;
+const int WCS__QSC = 703;
+const int WCS__HPX = 801;
+const int WCS__XPH = 802;
+
+/* Map error number to error message for each function. */
+const char *astPRJset_errmsg[] = {
+   0,
+   "Invalid projection parameters"};
+
+const char *astPRJfwd_errmsg[] = {
+   0,
+   "Invalid projection parameters",
+   "Invalid value of (phi,theta)"};
+
+const char *astPRJrev_errmsg[] = {
+   0,
+   "Invalid projection parameters",
+   "Invalid value of (x,y)"};
+
+
+#define copysign(X, Y) ((Y) < 0.0 ? -fabs(X) : fabs(X))
+#define icopysign(X, Y) ((Y) < 0.0 ? -abs(X) : abs(X))
+
+
+
+/*==========================================================================*/
+
+int astPRJset(pcode, prj)
+
+const char pcode[4];
+struct AstPrjPrm *prj;
+
+{
+   /* Set pointers to the forward and reverse projection routines. */
+   if (strcmp(pcode, "AZP") == 0) {
+      astAZPset(prj);
+   } else if (strcmp(pcode, "SZP") == 0) {
+      astSZPset(prj);
+   } else if (strcmp(pcode, "TAN") == 0) {
+      astTANset(prj);
+   } else if (strcmp(pcode, "STG") == 0) {
+      astSTGset(prj);
+   } else if (strcmp(pcode, "SIN") == 0) {
+      astSINset(prj);
+   } else if (strcmp(pcode, "ARC") == 0) {
+      astARCset(prj);
+   } else if (strcmp(pcode, "ZPN") == 0) {
+      astZPNset(prj);
+   } else if (strcmp(pcode, "ZEA") == 0) {
+      astZEAset(prj);
+   } else if (strcmp(pcode, "AIR") == 0) {
+      astAIRset(prj);
+   } else if (strcmp(pcode, "CYP") == 0) {
+      astCYPset(prj);
+   } else if (strcmp(pcode, "CEA") == 0) {
+      astCEAset(prj);
+   } else if (strcmp(pcode, "CAR") == 0) {
+      astCARset(prj);
+   } else if (strcmp(pcode, "MER") == 0) {
+      astMERset(prj);
+   } else if (strcmp(pcode, "SFL") == 0) {
+      astSFLset(prj);
+   } else if (strcmp(pcode, "PAR") == 0) {
+      astPARset(prj);
+   } else if (strcmp(pcode, "MOL") == 0) {
+      astMOLset(prj);
+   } else if (strcmp(pcode, "AIT") == 0) {
+      astAITset(prj);
+   } else if (strcmp(pcode, "COP") == 0) {
+      astCOPset(prj);
+   } else if (strcmp(pcode, "COE") == 0) {
+      astCOEset(prj);
+   } else if (strcmp(pcode, "COD") == 0) {
+      astCODset(prj);
+   } else if (strcmp(pcode, "COO") == 0) {
+      astCOOset(prj);
+   } else if (strcmp(pcode, "BON") == 0) {
+      astBONset(prj);
+   } else if (strcmp(pcode, "PCO") == 0) {
+      astPCOset(prj);
+   } else if (strcmp(pcode, "TSC") == 0) {
+      astTSCset(prj);
+   } else if (strcmp(pcode, "CSC") == 0) {
+      astCSCset(prj);
+   } else if (strcmp(pcode, "QSC") == 0) {
+      astQSCset(prj);
+   } else if (strcmp(pcode, "HPX") == 0) {
+      astHPXset(prj);
+   } else if (strcmp(pcode, "XPH") == 0) {
+      astXPHset(prj);
+   } else {
+      /* Unrecognized projection code. */
+      return 1;
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astPRJfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   return prj->astPRJfwd(phi, theta, prj, x, y);
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astPRJrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   return prj->astPRJrev(x, y, prj, phi, theta);
+}
+
+/*============================================================================
+*   AZP: zenithal/azimuthal perspective projection.
+*
+*   Given:
+*      prj->p[1]    Distance parameter, mu in units of r0.
+*      prj->p[2]    Tilt angle, gamma in degrees.
+*
+*   Given and/or returned:
+*      prj->flag    AZP, or -AZP if prj->flag is given < 0.
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "AZP"
+*      prj->phi0     0.0
+*      prj->theta0  90.0
+*      prj->w[0]    r0*(mu+1)
+*      prj->w[1]    tan(gamma)
+*      prj->w[2]    sec(gamma)
+*      prj->w[3]    cos(gamma)
+*      prj->w[4]    sin(gamma)
+*      prj->w[5]    asin(-1/mu) for |mu| >= 1, -90 otherwise
+*      prj->w[6]    mu*cos(gamma)
+*      prj->w[7]    1 if |mu*cos(gamma)| < 1, 0 otherwise
+*      prj->astPRJfwd  Pointer to astAZPfwd().
+*      prj->astPRJrev  Pointer to astAZPrev().
+*===========================================================================*/
+
+int astAZPset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "AZP");
+   prj->flag   = icopysign(WCS__AZP, prj->flag);
+   prj->phi0   =  0.0;
+   prj->theta0 = 90.0;
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   prj->w[0] = prj->r0*(prj->p[1] + 1.0);
+   if (prj->w[0] == 0.0) {
+      return 1;
+   }
+
+   prj->w[3] = astCosd(prj->p[2]);
+   if (prj->w[3] == 0.0) {
+      return 1;
+   }
+
+   prj->w[2] = 1.0/prj->w[3];
+   prj->w[4] = astSind(prj->p[2]);
+   prj->w[1] = prj->w[4] / prj->w[3];
+
+   if (fabs(prj->p[1]) > 1.0) {
+      prj->w[5] = astASind(-1.0/prj->p[1]);
+   } else {
+      prj->w[5] = -90.0;
+   }
+
+   prj->w[6] = prj->p[1] * prj->w[3];
+   prj->w[7] = (fabs(prj->w[6]) < 1.0) ? 1.0 : 0.0;
+
+   prj->astPRJfwd = astAZPfwd;
+   prj->astPRJrev = astAZPrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astAZPfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double a, b, cphi, cthe, r, s, t;
+
+   if (abs(prj->flag) != WCS__AZP) {
+      if (astAZPset(prj)) return 1;
+   }
+
+   cphi = astCosd(phi);
+   cthe = astCosd(theta);
+
+   s = prj->w[1]*cphi;
+   t = (prj->p[1] + astSind(theta)) + cthe*s;
+   if (t == 0.0) {
+      return 2;
+   }
+
+   r  =  prj->w[0]*cthe/t;
+   *x =  r*astSind(phi);
+   *y = -r*cphi*prj->w[2];
+
+   /* Bounds checking. */
+   if (prj->flag > 0) {
+      /* Overlap. */
+      if (theta < prj->w[5]) {
+         return 2;
+      }
+
+      /* Divergence. */
+      if (prj->w[7] > 0.0) {
+         t = prj->p[1] / sqrt(1.0 + s*s);
+
+         if (fabs(t) <= 1.0) {
+            s = astATand(-s);
+            t = astASind(t);
+            a = s - t;
+            b = s + t + 180.0;
+
+            if (a > 90.0) a -= 360.0;
+            if (b > 90.0) b -= 360.0;
+
+            if (theta < ((a > b) ? a : b)) {
+               return 2;
+            }
+         }
+      }
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astAZPrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double a, b, r, s, t, ycosg;
+   const double tol = 1.0e-13;
+
+   if (abs(prj->flag) != WCS__AZP) {
+      if (astAZPset(prj)) return 1;
+   }
+
+   ycosg = y*prj->w[3];
+
+   r = sqrt(x*x + ycosg*ycosg);
+   if (r == 0.0) {
+      *phi   =  0.0;
+      *theta = 90.0;
+   } else {
+      *phi = astATan2d(x, -ycosg);
+
+      s = r / (prj->w[0] + y*prj->w[4]);
+      t = s*prj->p[1]/sqrt(s*s + 1.0);
+
+      s = astATan2d(1.0, s);
+
+      if (fabs(t) > 1.0) {
+         t = copysign(90.0,t);
+         if (fabs(t) > 1.0+tol) {
+            return 2;
+         }
+      } else {
+         t = astASind(t);
+      }
+
+      a = s - t;
+      b = s + t + 180.0;
+
+      if (a > 90.0) a -= 360.0;
+      if (b > 90.0) b -= 360.0;
+
+      *theta = (a > b) ? a : b;
+   }
+
+   return 0;
+}
+
+/*============================================================================
+*   SZP: slant zenithal perspective projection.
+*
+*   Given:
+*      prj->p[1]    Distance of the point of projection from the centre of the
+*                   generating sphere, mu in units of r0.
+*      prj->p[2]    Native longitude, phi_c, and ...
+*      prj->p[3]    Native latitude, theta_c, on the planewards side of the
+*                   intersection of the line through the point of projection
+*                   and the centre of the generating sphere, phi_c in degrees.
+*
+*   Given and/or returned:
+*      prj->flag    SZP, or -SZP if prj->flag is given < 0.
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "SZP"
+*      prj->phi0     0.0
+*      prj->theta0  90.0
+*      prj->w[0]    1/r0
+*      prj->w[1]    xp = -mu*cos(theta_c)*sin(phi_c)
+*      prj->w[2]    yp =  mu*cos(theta_c)*cos(phi_c)
+*      prj->w[3]    zp =  mu*sin(theta_c) + 1
+*      prj->w[4]    r0*xp
+*      prj->w[5]    r0*yp
+*      prj->w[6]    r0*zp
+*      prj->w[7]    (zp - 1)^2
+*      prj->w[8]    asin(1-zp) if |1 - zp| < 1, -90 otherwise
+*      prj->astPRJfwd  Pointer to astSZPfwd().
+*      prj->astPRJrev  Pointer to astSZPrev().
+*===========================================================================*/
+
+int astSZPset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "SZP");
+   prj->flag   = icopysign(WCS__SZP, prj->flag);
+   prj->phi0   =  0.0;
+   prj->theta0 = 90.0;
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   prj->w[0] = 1.0/prj->r0;
+
+   prj->w[3] = prj->p[1] * astSind(prj->p[3]) + 1.0;
+   if (prj->w[3] == 0.0) {
+      return 1;
+   }
+
+   prj->w[1] = -prj->p[1] * astCosd(prj->p[3]) * astSind(prj->p[2]);
+   prj->w[2] =  prj->p[1] * astCosd(prj->p[3]) * astCosd(prj->p[2]);
+   prj->w[4] =  prj->r0 * prj->w[1];
+   prj->w[5] =  prj->r0 * prj->w[2];
+   prj->w[6] =  prj->r0 * prj->w[3];
+   prj->w[7] =  (prj->w[3] - 1.0) * prj->w[3] - 1.0;
+
+   if (fabs(prj->w[3] - 1.0) < 1.0) {
+      prj->w[8] = astASind(1.0 - prj->w[3]);
+   } else {
+      prj->w[8] = -90.0;
+   }
+
+   prj->astPRJfwd = astSZPfwd;
+   prj->astPRJrev = astSZPrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astSZPfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double a, b, cphi, cthe, s, sphi, t;
+
+   if (abs(prj->flag) != WCS__SZP) {
+      if (astSZPset(prj)) return 1;
+   }
+
+   cphi = astCosd(phi);
+   sphi = astSind(phi);
+   cthe = astCosd(theta);
+   s = 1.0 - astSind(theta);
+
+   t = prj->w[3] - s;
+   if (t == 0.0) {
+      return 2;
+   }
+
+   *x =  (prj->w[6]*cthe*sphi - prj->w[4]*s)/t;
+   *y = -(prj->w[6]*cthe*cphi + prj->w[5]*s)/t;
+
+   /* Bounds checking. */
+   if (prj->flag > 0) {
+      /* Divergence. */
+      if (theta < prj->w[8]) {
+         return 2;
+      }
+
+      /* Overlap. */
+      if (fabs(prj->p[1]) > 1.0) {
+         s = prj->w[1]*sphi - prj->w[2]*cphi;
+         t = 1.0/sqrt(prj->w[7] + s*s);
+
+         if (fabs(t) <= 1.0) {
+            s = astATan2d(s, prj->w[3] - 1.0);
+            t = astASind(t);
+            a = s - t;
+            b = s + t + 180.0;
+
+            if (a > 90.0) a -= 360.0;
+            if (b > 90.0) b -= 360.0;
+
+            if (theta < ((a > b) ? a : b)) {
+               return 2;
+            }
+         }
+      }
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astSZPrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double a, b, c, d, r2, sth1, sth2, sthe, sxy, t, x1, xp, y1, yp, z;
+   const double tol = 1.0e-13;
+
+   if (abs(prj->flag) != WCS__SZP) {
+      if (astSZPset(prj)) return 1;
+   }
+
+   xp = x*prj->w[0];
+   yp = y*prj->w[0];
+   r2 = xp*xp + yp*yp;
+
+   x1 = (xp - prj->w[1])/prj->w[3];
+   y1 = (yp - prj->w[2])/prj->w[3];
+   sxy = xp*x1 + yp*y1;
+
+   if (r2 < 1.0e-10) {
+      /* Use small angle formula. */
+      z = r2/2.0;
+      *theta = 90.0 - R2D*sqrt(r2/(1.0 + sxy));
+
+   } else {
+      t = x1*x1 + y1*y1;
+      a = t + 1.0;
+      b = sxy - t;
+      c = r2 - sxy - sxy + t - 1.0;
+      d = b*b - a*c;
+
+      /* Check for a solution. */
+      if (d < 0.0) {
+         return 2;
+      }
+      d = sqrt(d);
+
+      /* Choose solution closest to pole. */
+      sth1 = (-b + d)/a;
+      sth2 = (-b - d)/a;
+      sthe = (sth1 > sth2) ? sth1 : sth2;
+      if (sthe > 1.0) {
+         if (sthe-1.0 < tol) {
+            sthe = 1.0;
+         } else {
+            sthe = (sth1 < sth2) ? sth1 : sth2;
+         }
+      }
+
+      if (sthe < -1.0) {
+         if (sthe+1.0 > -tol) {
+            sthe = -1.0;
+         }
+      }
+
+      if (sthe > 1.0 || sthe < -1.0) {
+         return 2;
+      }
+
+      *theta = astASind(sthe);
+
+      z = 1.0 - sthe;
+   }
+
+   *phi = astATan2d(xp - x1*z, -(yp - y1*z));
+
+   return 0;
+}
+
+/*============================================================================
+*   TAN: gnomonic projection.
+*
+*   Given and/or returned:
+*      prj->flag    TAN, or -TAN if prj->flag is given < 0.
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "TAN"
+*      prj->phi0     0.0
+*      prj->theta0  90.0
+*      prj->astPRJfwd  Pointer to astTANfwd().
+*      prj->astPRJrev  Pointer to astTANrev().
+*===========================================================================*/
+
+int astTANset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "TAN");
+   prj->flag   = icopysign(WCS__TAN, prj->flag);
+   prj->phi0   =  0.0;
+   prj->theta0 = 90.0;
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   prj->astPRJfwd = astTANfwd;
+   prj->astPRJrev = astTANrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astTANfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double r, s;
+
+   if (abs(prj->flag) != WCS__TAN) {
+      if(astTANset(prj)) return 1;
+   }
+
+   s = astSind(theta);
+   if (s == 0.0) {
+      return 2;
+   }
+
+   r =  prj->r0*astCosd(theta)/s;
+   *x =  r*astSind(phi);
+   *y = -r*astCosd(phi);
+
+   if (prj->flag > 0 && s < 0.0) {
+      return 2;
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astTANrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double r;
+
+   if (abs(prj->flag) != WCS__TAN) {
+      if (astTANset(prj)) return 1;
+   }
+
+   r = sqrt(x*x + y*y);
+   if (r == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = astATan2d(x, -y);
+   }
+   *theta = astATan2d(prj->r0, r);
+
+   return 0;
+}
+
+/*============================================================================
+*   STG: stereographic projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "STG"
+*      prj->flag     STG
+*      prj->phi0     0.0
+*      prj->theta0  90.0
+*      prj->w[0]    2*r0
+*      prj->w[1]    1/(2*r0)
+*      prj->astPRJfwd  Pointer to astSTGfwd().
+*      prj->astPRJrev  Pointer to astSTGrev().
+*===========================================================================*/
+
+int astSTGset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "STG");
+   prj->flag   =  WCS__STG;
+   prj->phi0   =  0.0;
+   prj->theta0 = 90.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 360.0/PI;
+      prj->w[1] = PI/360.0;
+   } else {
+      prj->w[0] = 2.0*prj->r0;
+      prj->w[1] = 1.0/prj->w[0];
+   }
+
+   prj->astPRJfwd = astSTGfwd;
+   prj->astPRJrev = astSTGrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astSTGfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double r, s;
+
+   if (prj->flag != WCS__STG) {
+      if (astSTGset(prj)) return 1;
+   }
+
+   s = 1.0 + astSind(theta);
+   if (s == 0.0) {
+      return 2;
+   }
+
+   r =  prj->w[0]*astCosd(theta)/s;
+   *x =  r*astSind(phi);
+   *y = -r*astCosd(phi);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astSTGrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double r;
+
+   if (prj->flag != WCS__STG) {
+      if (astSTGset(prj)) return 1;
+   }
+
+   r = sqrt(x*x + y*y);
+   if (r == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = astATan2d(x, -y);
+   }
+   *theta = 90.0 - 2.0*astATand(r*prj->w[1]);
+
+   return 0;
+}
+
+/*============================================================================
+*   SIN: orthographic/synthesis projection.
+*
+*   Given:
+*      prj->p[1:2]  Obliqueness parameters, xi and eta.
+*
+*   Given and/or returned:
+*      prj->flag    SIN, or -SIN if prj->flag is given < 0.
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "SIN"
+*      prj->phi0     0.0
+*      prj->theta0  90.0
+*      prj->w[0]    1/r0
+*      prj->w[1]    xi**2 + eta**2
+*      prj->w[2]    xi**2 + eta**2 + 1
+*      prj->w[3]    xi**2 + eta**2 - 1
+*      prj->astPRJfwd  Pointer to astSINfwd().
+*      prj->astPRJrev  Pointer to astSINrev().
+*===========================================================================*/
+
+int astSINset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "SIN");
+   prj->flag   = icopysign(WCS__SIN, prj->flag);
+   prj->phi0   =  0.0;
+   prj->theta0 = 90.0;
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   prj->w[0] = 1.0/prj->r0;
+   prj->w[1] = prj->p[1]*prj->p[1] + prj->p[2]*prj->p[2];
+   prj->w[2] = prj->w[1] + 1.0;
+   prj->w[3] = prj->w[1] - 1.0;
+
+   prj->astPRJfwd = astSINfwd;
+   prj->astPRJrev = astSINrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astSINfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double cphi, cthe, sphi, t, z;
+
+   if (abs(prj->flag) != WCS__SIN) {
+      if (astSINset(prj)) return 1;
+   }
+
+   t = (90.0 - fabs(theta))*D2R;
+   if (t < 1.0e-5) {
+      if (theta > 0.0) {
+         z = t*t/2.0;
+      } else {
+         z = 2.0 - t*t/2.0;
+      }
+      cthe = t;
+   } else {
+      z =  1.0 - astSind(theta);
+      cthe = astCosd(theta);
+   }
+
+   cphi = astCosd(phi);
+   sphi = astSind(phi);
+   *x =  prj->r0*(cthe*sphi + prj->p[1]*z);
+   *y = -prj->r0*(cthe*cphi - prj->p[2]*z);
+
+   /* Validate this solution. */
+   if (prj->flag > 0) {
+      if (prj->w[1] == 0.0) {
+         /* Orthographic projection. */
+         if (theta < 0.0) {
+            return 2;
+         }
+      } else {
+         /* "Synthesis" projection. */
+         t = -astATand(prj->p[1]*sphi - prj->p[2]*cphi);
+         if (theta < t) {
+            return 2;
+         }
+      }
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astSINrev (x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   const double tol = 1.0e-13;
+   double a, b, c, d, r2, sth1, sth2, sthe, sxy, x0, x1, xp, y0, y1, yp, z;
+
+   if (abs(prj->flag) != WCS__SIN) {
+      if (astSINset(prj)) return 1;
+   }
+
+   /* Compute intermediaries. */
+   x0 = x*prj->w[0];
+   y0 = y*prj->w[0];
+   r2 = x0*x0 + y0*y0;
+
+   if (prj->w[1] == 0.0) {
+      /* Orthographic projection. */
+      if (r2 != 0.0) {
+         *phi = astATan2d(x0, -y0);
+      } else {
+         *phi = 0.0;
+      }
+
+      if (r2 < 0.5) {
+         *theta = astACosd(sqrt(r2));
+      } else if (r2 <= 1.0) {
+         *theta = astASind(sqrt(1.0 - r2));
+      } else {
+         return 2;
+      }
+
+   } else {
+      /* "Synthesis" projection. */
+      x1 = prj->p[1];
+      y1 = prj->p[2];
+      sxy = x0*x1 + y0*y1;
+
+      if (r2 < 1.0e-10) {
+         /* Use small angle formula. */
+         z = r2/2.0;
+         *theta = 90.0 - R2D*sqrt(r2/(1.0 + sxy));
+
+      } else {
+         a = prj->w[2];
+         b = sxy - prj->w[1];
+         c = r2 - sxy - sxy + prj->w[3];
+         d = b*b - a*c;
+
+         /* Check for a solution. */
+         if (d < 0.0) {
+            return 2;
+         }
+         d = sqrt(d);
+
+         /* Choose solution closest to pole. */
+         sth1 = (-b + d)/a;
+         sth2 = (-b - d)/a;
+         sthe = (sth1 > sth2) ? sth1 : sth2;
+         if (sthe > 1.0) {
+            if (sthe-1.0 < tol) {
+               sthe = 1.0;
+            } else {
+               sthe = (sth1 < sth2) ? sth1 : sth2;
+            }
+         }
+
+         if (sthe < -1.0) {
+            if (sthe+1.0 > -tol) {
+               sthe = -1.0;
+            }
+         }
+
+         if (sthe > 1.0 || sthe < -1.0) {
+            return 2;
+         }
+
+         *theta = astASind(sthe);
+         z = 1.0 - sthe;
+      }
+
+      xp = -y0 + prj->p[2]*z;
+      yp =  x0 - prj->p[1]*z;
+      if (xp == 0.0 && yp == 0.0) {
+         *phi = 0.0;
+      } else {
+         *phi = astATan2d(yp,xp);
+      }
+   }
+
+   return 0;
+}
+
+/*============================================================================
+*   ARC: zenithal/azimuthal equidistant projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "ARC"
+*      prj->flag     ARC
+*      prj->phi0     0.0
+*      prj->theta0  90.0
+*      prj->w[0]    r0*(pi/180)
+*      prj->w[1]    (180/pi)/r0
+*      prj->astPRJfwd  Pointer to astARCfwd().
+*      prj->astPRJrev  Pointer to astARCrev().
+*===========================================================================*/
+
+int astARCset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "ARC");
+   prj->flag   =  WCS__ARC;
+   prj->phi0   =  0.0;
+   prj->theta0 = 90.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 1.0;
+      prj->w[1] = 1.0;
+   } else {
+      prj->w[0] = prj->r0*D2R;
+      prj->w[1] = 1.0/prj->w[0];
+   }
+
+   prj->astPRJfwd = astARCfwd;
+   prj->astPRJrev = astARCrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astARCfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double r;
+
+   if (prj->flag != WCS__ARC) {
+      if (astARCset(prj)) return 1;
+   }
+
+   r =  prj->w[0]*(90.0 - theta);
+   *x =  r*astSind(phi);
+   *y = -r*astCosd(phi);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astARCrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double r;
+
+   if (prj->flag != WCS__ARC) {
+      if (astARCset(prj)) return 1;
+   }
+
+   r = sqrt(x*x + y*y);
+   if (r == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = astATan2d(x, -y);
+   }
+   *theta = 90.0 - r*prj->w[1];
+
+   return 0;
+}
+
+/*============================================================================
+*   ZPN: zenithal/azimuthal polynomial projection.
+*
+*   Given:
+*      prj->p[0:WCSLIB_MXPAR-1]  Polynomial coefficients.
+*
+*   Given and/or returned:
+*      prj->flag    ZPN, or -ZPN if prj->flag is given < 0.
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "ZPN"
+*      prj->phi0     0.0
+*      prj->theta0  90.0
+*      prj->n       Degree of the polynomial, N.
+*      prj->w[0]    Co-latitude of the first point of inflection (N > 2).
+*      prj->w[1]    Radius of the first point of inflection (N > 2).
+*      prj->astPRJfwd  Pointer to astZPNfwd().
+*      prj->astPRJrev  Pointer to astZPNrev().
+*===========================================================================*/
+
+int astZPNset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   int   i, j, k, plen;
+   double d, d1, d2, r, zd, zd1, zd2;
+   const double tol = 1.0e-13;
+
+   strcpy(prj->code, "ZPN");
+   prj->flag   = icopysign(WCS__ZPN, prj->flag);
+   prj->phi0   =  0.0;
+   prj->theta0 = 90.0;
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   /* Find the highest non-zero coefficient. */
+   plen = astSizeOf( prj->p )/sizeof( double );
+   for (k = plen-1; k >= 0 && prj->p[k] == 0.0; k--);
+   if (k < 0) return 1;
+
+   prj->n = k;
+
+   if (k >= 3) {
+      /* Find the point of inflection closest to the pole. */
+      zd1 = 0.0;
+      d1  = prj->p[1];
+      if (d1 <= 0.0) {
+         return 1;
+      }
+
+      /* Find the point where the derivative first goes negative. */
+      for (i = 0; i < 180; i++) {
+         zd2 = i*D2R;
+         d2  = 0.0;
+         for (j = k; j > 0; j--) {
+            d2 = d2*zd2 + j*prj->p[j];
+         }
+
+         if (d2 <= 0.0) break;
+         zd1 = zd2;
+         d1  = d2;
+      }
+
+      if (i == 180) {
+         /* No negative derivative -> no point of inflection. */
+         zd = PI;
+      } else {
+         /* Find where the derivative is zero. */
+         for (i = 1; i <= 10; i++) {
+            zd = zd1 - d1*(zd2-zd1)/(d2-d1);
+
+            d = 0.0;
+            for (j = k; j > 0; j--) {
+               d = d*zd + j*prj->p[j];
+            }
+
+            if (fabs(d) < tol) break;
+
+            if (d < 0.0) {
+               zd2 = zd;
+               d2  = d;
+            } else {
+               zd1 = zd;
+               d1  = d;
+            }
+         }
+      }
+
+      r = 0.0;
+      for (j = k; j >= 0; j--) {
+         r = r*zd + prj->p[j];
+      }
+      prj->w[0] = zd;
+      prj->w[1] = r;
+   }
+
+   prj->astPRJfwd = astZPNfwd;
+   prj->astPRJrev = astZPNrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astZPNfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   int   j;
+   double r, s;
+
+   if (abs(prj->flag) != WCS__ZPN) {
+      if (astZPNset(prj)) return 1;
+   }
+
+   s = (90.0 - theta)*D2R;
+
+   r = 0.0;
+   for (j = prj->n; j >= 0; j--) {
+      r = r*s + prj->p[j];
+   }
+   r = prj->r0*r;
+
+   *x =  r*astSind(phi);
+   *y = -r*astCosd(phi);
+
+   if (prj->flag > 0 && s > prj->w[0] && prj->n > 2 ) {
+      return 2;
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astZPNrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   int   i, j, k;
+   double a, b, c, d, lambda, r, r1, r2, rt, zd, zd1, zd2;
+   const double tol = 1.0e-13;
+
+   if (abs(prj->flag) != WCS__ZPN) {
+      if (astZPNset(prj)) return 1;
+   }
+
+   k = prj->n;
+
+   r = sqrt(x*x + y*y)/prj->r0;
+
+   if (k < 1) {
+      /* Constant - no solution. */
+      return 1;
+   } else if (k == 1) {
+      /* Linear. */
+      zd = (r - prj->p[0])/prj->p[1];
+   } else if (k == 2) {
+      /* Quadratic. */
+      a = prj->p[2];
+      b = prj->p[1];
+      c = prj->p[0] - r;
+
+      d = b*b - 4.0*a*c;
+      if (d < 0.0) {
+         return 2;
+      }
+      d = sqrt(d);
+
+      /* Choose solution closest to pole. */
+      zd1 = (-b + d)/(2.0*a);
+      zd2 = (-b - d)/(2.0*a);
+      zd  = (zd1<zd2) ? zd1 : zd2;
+      if (zd < -tol) zd = (zd1>zd2) ? zd1 : zd2;
+      if (zd < 0.0) {
+         if (zd < -tol) {
+            return 2;
+         }
+         zd = 0.0;
+      } else if (zd > PI) {
+         if (zd > PI+tol) {
+            return 2;
+         }
+         zd = PI;
+      }
+   } else {
+      /* Higher order - solve iteratively. */
+      zd1 = 0.0;
+      r1  = prj->p[0];
+      zd2 = prj->w[0];
+      r2  = prj->w[1];
+
+      if (r < r1) {
+         if (r < r1-tol) {
+            return 2;
+         }
+         zd = zd1;
+      } else if (r > r2) {
+         if (r > r2+tol) {
+            return 2;
+         }
+         zd = zd2;
+      } else {
+         /* Disect the interval. */
+         for (j = 0; j < 100; j++) {
+            lambda = (r2 - r)/(r2 - r1);
+            if (lambda < 0.1) {
+               lambda = 0.1;
+            } else if (lambda > 0.9) {
+               lambda = 0.9;
+            }
+
+            zd = zd2 - lambda*(zd2 - zd1);
+
+            rt = 0.0;
+            for (i = k; i >= 0; i--) {
+                rt = (rt * zd) + prj->p[i];
+            }
+
+            if (rt < r) {
+                if (r-rt < tol) break;
+                r1 = rt;
+                zd1 = zd;
+            } else {
+                if (rt-r < tol) break;
+                r2 = rt;
+                zd2 = zd;
+            }
+
+            if (fabs(zd2-zd1) < tol) break;
+         }
+      }
+   }
+
+   if (r == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = astATan2d(x, -y);
+   }
+   *theta = 90.0 - zd*R2D;
+
+   return 0;
+}
+
+/*============================================================================
+*   ZEA: zenithal/azimuthal equal area projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "ZEA"
+*      prj->flag     ZEA
+*      prj->phi0     0.0
+*      prj->theta0  90.0
+*      prj->w[0]    2*r0
+*      prj->w[1]    1/(2*r0)
+*      prj->astPRJfwd  Pointer to astZEAfwd().
+*      prj->astPRJrev  Pointer to astZEArev().
+*===========================================================================*/
+
+int astZEAset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "ZEA");
+   prj->flag   =  WCS__ZEA;
+   prj->phi0   =  0.0;
+   prj->theta0 = 90.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 360.0/PI;
+      prj->w[1] = PI/360.0;
+   } else {
+      prj->w[0] = 2.0*prj->r0;
+      prj->w[1] = 1.0/prj->w[0];
+   }
+
+   prj->astPRJfwd = astZEAfwd;
+   prj->astPRJrev = astZEArev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astZEAfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double r;
+
+   if (prj->flag != WCS__ZEA) {
+      if (astZEAset(prj)) return 1;
+   }
+
+   r =  prj->w[0]*astSind((90.0 - theta)/2.0);
+   *x =  r*astSind(phi);
+   *y = -r*astCosd(phi);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astZEArev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double r, s;
+   const double tol = 1.0e-12;
+
+   if (prj->flag != WCS__ZEA) {
+      if (astZEAset(prj)) return 1;
+   }
+
+   r = sqrt(x*x + y*y);
+   if (r == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = astATan2d(x, -y);
+   }
+
+   s = r*prj->w[1];
+   if (fabs(s) > 1.0) {
+      if (fabs(r - prj->w[0]) < tol) {
+         *theta = -90.0;
+      } else {
+         return 2;
+      }
+   } else {
+      *theta = 90.0 - 2.0*astASind(s);
+   }
+
+   return 0;
+}
+
+/*============================================================================
+*   AIR: Airy's projection.
+*
+*   Given:
+*      prj->p[1]    Latitude theta_b within which the error is minimized, in
+*                   degrees.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "AIR"
+*      prj->flag     AIR
+*      prj->phi0     0.0
+*      prj->theta0  90.0
+*      prj->w[0]    2*r0
+*      prj->w[1]    ln(cos(xi_b))/tan(xi_b)**2, where xi_b = (90-theta_b)/2
+*      prj->w[2]    1/2 - prj->w[1]
+*      prj->w[3]    2*r0*prj->w[2]
+*      prj->w[4]    tol, cutoff for using small angle approximation, in
+*                   radians.
+*      prj->w[5]    prj->w[2]*tol
+*      prj->w[6]    (180/pi)/prj->w[2]
+*      prj->astPRJfwd  Pointer to astAIRfwd().
+*      prj->astPRJrev  Pointer to astAIRrev().
+*===========================================================================*/
+
+int astAIRset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   const double tol = 1.0e-4;
+   double cxi;
+
+   strcpy(prj->code, "AIR");
+   prj->flag   =  WCS__AIR;
+   prj->phi0   =  0.0;
+   prj->theta0 = 90.0;
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   prj->w[0] = 2.0*prj->r0;
+   if (prj->p[1] == 90.0) {
+      prj->w[1] = -0.5;
+      prj->w[2] =  1.0;
+   } else if (prj->p[1] > -90.0) {
+      cxi = astCosd((90.0 - prj->p[1])/2.0);
+      prj->w[1] = log(cxi)*(cxi*cxi)/(1.0-cxi*cxi);
+      prj->w[2] = 0.5 - prj->w[1];
+   } else {
+      return 1;
+   }
+
+   prj->w[3] = prj->w[0] * prj->w[2];
+   prj->w[4] = tol;
+   prj->w[5] = prj->w[2]*tol;
+   prj->w[6] = R2D/prj->w[2];
+
+   prj->astPRJfwd = astAIRfwd;
+   prj->astPRJrev = astAIRrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astAIRfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double cxi, r, txi, xi;
+
+   if (prj->flag != WCS__AIR) {
+      if (astAIRset(prj)) return 1;
+   }
+
+   if (theta == 90.0) {
+      r = 0.0;
+   } else if (theta > -90.0) {
+      xi = D2R*(90.0 - theta)/2.0;
+      if (xi < prj->w[4]) {
+         r = xi*prj->w[3];
+      } else {
+         cxi = astCosd((90.0 - theta)/2.0);
+         txi = sqrt(1.0-cxi*cxi)/cxi;
+         r = -prj->w[0]*(log(cxi)/txi + prj->w[1]*txi);
+      }
+   } else {
+      return 2;
+   }
+
+   *x =  r*astSind(phi);
+   *y = -r*astCosd(phi);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astAIRrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   int   j;
+   double cxi, lambda, r, r1, r2, rt, txi, x1, x2, xi;
+   const double tol = 1.0e-12;
+
+   if (prj->flag != WCS__AIR) {
+      if (astAIRset(prj)) return 1;
+   }
+
+   r = sqrt(x*x + y*y)/prj->w[0];
+
+   if (r == 0.0) {
+      xi = 0.0;
+   } else if (r < prj->w[5]) {
+      xi = r*prj->w[6];
+   } else {
+      /* Find a solution interval. */
+      x1 = 1.0;
+      r1 = 0.0;
+      for (j = 0; j < 30; j++) {
+         x2 = x1/2.0;
+         txi = sqrt(1.0-x2*x2)/x2;
+         r2 = -(log(x2)/txi + prj->w[1]*txi);
+
+         if (r2 >= r) break;
+         x1 = x2;
+         r1 = r2;
+      }
+      if (j == 30) return 2;
+
+      for (j = 0; j < 100; j++) {
+         /* Weighted division of the interval. */
+         lambda = (r2-r)/(r2-r1);
+         if (lambda < 0.1) {
+            lambda = 0.1;
+         } else if (lambda > 0.9) {
+            lambda = 0.9;
+         }
+         cxi = x2 - lambda*(x2-x1);
+
+         txi = sqrt(1.0-cxi*cxi)/cxi;
+         rt = -(log(cxi)/txi + prj->w[1]*txi);
+
+         if (rt < r) {
+             if (r-rt < tol) break;
+             r1 = rt;
+             x1 = cxi;
+         } else {
+             if (rt-r < tol) break;
+             r2 = rt;
+             x2 = cxi;
+         }
+      }
+      if (j == 100) return 2;
+
+      xi = astACosd(cxi);
+   }
+
+   if (r == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = astATan2d(x, -y);
+   }
+   *theta = 90.0 - 2.0*xi;
+
+   return 0;
+}
+
+/*============================================================================
+*   CYP: cylindrical perspective projection.
+*
+*   Given:
+*      prj->p[1]    Distance of point of projection from the centre of the
+*                   generating sphere, mu, in units of r0.
+*      prj->p[2]    Radius of the cylinder of projection, lambda, in units of
+*                   r0.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "CYP"
+*      prj->flag    CYP
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    r0*lambda*(pi/180)
+*      prj->w[1]    (180/pi)/(r0*lambda)
+*      prj->w[2]    r0*(mu + lambda)
+*      prj->w[3]    1/(r0*(mu + lambda))
+*      prj->astPRJfwd  Pointer to astCYPfwd().
+*      prj->astPRJrev  Pointer to astCYPrev().
+*===========================================================================*/
+
+int astCYPset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "CYP");
+   prj->flag   = WCS__CYP;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+
+      prj->w[0] = prj->p[2];
+      if (prj->w[0] == 0.0) {
+         return 1;
+      }
+
+      prj->w[1] = 1.0/prj->w[0];
+
+      prj->w[2] = R2D*(prj->p[1] + prj->p[2]);
+      if (prj->w[2] == 0.0) {
+         return 1;
+      }
+
+      prj->w[3] = 1.0/prj->w[2];
+   } else {
+      prj->w[0] = prj->r0*prj->p[2]*D2R;
+      if (prj->w[0] == 0.0) {
+         return 1;
+      }
+
+      prj->w[1] = 1.0/prj->w[0];
+
+      prj->w[2] = prj->r0*(prj->p[1] + prj->p[2]);
+      if (prj->w[2] == 0.0) {
+         return 1;
+      }
+
+      prj->w[3] = 1.0/prj->w[2];
+   }
+
+   prj->astPRJfwd = astCYPfwd;
+   prj->astPRJrev = astCYPrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCYPfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double s;
+
+   if (prj->flag != WCS__CYP) {
+      if (astCYPset(prj)) return 1;
+   }
+
+   s = prj->p[1] + astCosd(theta);
+   if (s == 0.0) {
+         return 2;
+      }
+
+   *x = prj->w[0]*phi;
+   *y = prj->w[2]*astSind(theta)/s;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCYPrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double eta;
+   double a;
+   const double tol = 1.0e-13;
+
+   if (prj->flag != WCS__CYP) {
+      if (astCYPset(prj)) return 1;
+   }
+
+   *phi   = x*prj->w[1];
+   eta    = y*prj->w[3];
+
+   a = eta*prj->p[1]/sqrt(eta*eta+1.0);
+   if( fabs( a ) < 1.0 ) {
+      *theta = astATan2d(eta,1.0) + astASind( a );
+
+   } else if( fabs( a ) < 1.0 + tol ) {
+      if( a > 0.0 ){
+         *theta = astATan2d(eta,1.0) + 90.0;
+      } else {
+         *theta = astATan2d(eta,1.0) - 90.0;
+      }
+
+   } else {
+      return 2;
+   }
+
+   return 0;
+}
+
+/*============================================================================
+*   CEA: cylindrical equal area projection.
+*
+*   Given:
+*      prj->p[1]    Square of the cosine of the latitude at which the
+*                   projection is conformal, lambda.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "CEA"
+*      prj->flag    CEA
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    r0*(pi/180)
+*      prj->w[1]    (180/pi)/r0
+*      prj->w[2]    r0/lambda
+*      prj->w[3]    lambda/r0
+*      prj->astPRJfwd  Pointer to astCEAfwd().
+*      prj->astPRJrev  Pointer to astCEArev().
+*===========================================================================*/
+
+int astCEAset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "CEA");
+   prj->flag   = WCS__CEA;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 1.0;
+      prj->w[1] = 1.0;
+      if (prj->p[1] <= 0.0 || prj->p[1] > 1.0) {
+         return 1;
+      }
+      prj->w[2] = prj->r0/prj->p[1];
+      prj->w[3] = prj->p[1]/prj->r0;
+   } else {
+      prj->w[0] = prj->r0*D2R;
+      prj->w[1] = R2D/prj->r0;
+      if (prj->p[1] <= 0.0 || prj->p[1] > 1.0) {
+         return 1;
+      }
+      prj->w[2] = prj->r0/prj->p[1];
+      prj->w[3] = prj->p[1]/prj->r0;
+   }
+
+   prj->astPRJfwd = astCEAfwd;
+   prj->astPRJrev = astCEArev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCEAfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   if (prj->flag != WCS__CEA) {
+      if (astCEAset(prj)) return 1;
+   }
+
+   *x = prj->w[0]*phi;
+   *y = prj->w[2]*astSind(theta);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCEArev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double s;
+   const double tol = 1.0e-13;
+
+   if (prj->flag != WCS__CEA) {
+      if (astCEAset(prj)) return 1;
+   }
+
+   s = y*prj->w[3];
+   if (fabs(s) > 1.0) {
+      if (fabs(s) > 1.0+tol) {
+         return 2;
+      }
+      s = copysign(1.0,s);
+   }
+
+   *phi   = x*prj->w[1];
+   *theta = astASind(s);
+
+   return 0;
+}
+
+/*============================================================================
+*   CAR: Cartesian projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "CAR"
+*      prj->flag    CAR
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    r0*(pi/180)
+*      prj->w[1]    (180/pi)/r0
+*      prj->astPRJfwd  Pointer to astCARfwd().
+*      prj->astPRJrev  Pointer to astCARrev().
+*===========================================================================*/
+
+int astCARset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "CAR");
+   prj->flag   = WCS__CAR;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 1.0;
+      prj->w[1] = 1.0;
+   } else {
+      prj->w[0] = prj->r0*D2R;
+      prj->w[1] = 1.0/prj->w[0];
+   }
+
+   prj->astPRJfwd = astCARfwd;
+   prj->astPRJrev = astCARrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCARfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   if (prj->flag != WCS__CAR) {
+      if (astCARset(prj)) return 1;
+   }
+
+   *x = prj->w[0]*phi;
+   *y = prj->w[0]*theta;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCARrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   if (prj->flag != WCS__CAR) {
+      if (astCARset(prj)) return 1;
+   }
+
+   *phi   = prj->w[1]*x;
+   *theta = prj->w[1]*y;
+
+   return 0;
+}
+
+/*============================================================================
+*   MER: Mercator's projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "MER"
+*      prj->flag    MER
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    r0*(pi/180)
+*      prj->w[1]    (180/pi)/r0
+*      prj->astPRJfwd  Pointer to astMERfwd().
+*      prj->astPRJrev  Pointer to astMERrev().
+*===========================================================================*/
+
+int astMERset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "MER");
+   prj->flag   = WCS__MER;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 1.0;
+      prj->w[1] = 1.0;
+   } else {
+      prj->w[0] = prj->r0*D2R;
+      prj->w[1] = 1.0/prj->w[0];
+   }
+
+   prj->astPRJfwd = astMERfwd;
+   prj->astPRJrev = astMERrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astMERfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   if (prj->flag != WCS__MER) {
+      if (astMERset(prj)) return 1;
+   }
+
+   if (theta <= -90.0 || theta >= 90.0) {
+      return 2;
+   }
+
+   *x = prj->w[0]*phi;
+   *y = prj->r0*log(astTand((90.0+theta)/2.0));
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astMERrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   if (prj->flag != WCS__MER) {
+      if (astMERset(prj)) return 1;
+   }
+
+   *phi   = x*prj->w[1];
+   *theta = 2.0*astATand(exp(y/prj->r0)) - 90.0;
+
+   return 0;
+}
+
+/*============================================================================
+*   SFL: Sanson-Flamsteed ("global sinusoid") projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "SFL"
+*      prj->flag    SFL
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    r0*(pi/180)
+*      prj->w[1]    (180/pi)/r0
+*      prj->astPRJfwd  Pointer to astSFLfwd().
+*      prj->astPRJrev  Pointer to astSFLrev().
+*===========================================================================*/
+
+int astSFLset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "SFL");
+   prj->flag   = WCS__SFL;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 1.0;
+      prj->w[1] = 1.0;
+   } else {
+      prj->w[0] = prj->r0*D2R;
+      prj->w[1] = 1.0/prj->w[0];
+   }
+
+   prj->astPRJfwd = astSFLfwd;
+   prj->astPRJrev = astSFLrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astSFLfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   if (prj->flag != WCS__SFL) {
+      if (astSFLset(prj)) return 1;
+   }
+
+   *x = prj->w[0]*phi*astCosd(theta);
+   *y = prj->w[0]*theta;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astSFLrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double w;
+
+   if (prj->flag != WCS__SFL) {
+      if (astSFLset(prj)) return 1;
+   }
+
+   w = cos(y/prj->r0);
+   if (w == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = x*prj->w[1]/cos(y/prj->r0);
+   }
+   *theta = y*prj->w[1];
+
+   return 0;
+}
+
+/*============================================================================
+*   PAR: parabolic projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "PAR"
+*      prj->flag    PAR
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    r0*(pi/180)
+*      prj->w[1]    (180/pi)/r0
+*      prj->w[2]    pi*r0
+*      prj->w[3]    1/(pi*r0)
+*      prj->astPRJfwd  Pointer to astPARfwd().
+*      prj->astPRJrev  Pointer to astPARrev().
+*===========================================================================*/
+
+int astPARset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "PAR");
+   prj->flag   = WCS__PAR;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 1.0;
+      prj->w[1] = 1.0;
+      prj->w[2] = 180.0;
+      prj->w[3] = 1.0/prj->w[2];
+   } else {
+      prj->w[0] = prj->r0*D2R;
+      prj->w[1] = 1.0/prj->w[0];
+      prj->w[2] = PI*prj->r0;
+      prj->w[3] = 1.0/prj->w[2];
+   }
+
+   prj->astPRJfwd = astPARfwd;
+   prj->astPRJrev = astPARrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astPARfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double s;
+
+   if (prj->flag != WCS__PAR) {
+      if (astPARset(prj)) return 1;
+   }
+
+   s = astSind(theta/3.0);
+   *x = prj->w[0]*phi*(1.0 - 4.0*s*s);
+   *y = prj->w[2]*s;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astPARrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double s, t;
+
+   if (prj->flag != WCS__PAR) {
+      if (astPARset(prj)) return 1;
+   }
+
+   s = y*prj->w[3];
+   if (s > 1.0 || s < -1.0) {
+      return 2;
+   }
+
+   t = 1.0 - 4.0*s*s;
+   if (t == 0.0) {
+      if (x == 0.0) {
+         *phi = 0.0;
+      } else {
+         return 2;
+      }
+   } else {
+      *phi = prj->w[1]*x/t;
+   }
+
+   *theta = 3.0*astASind(s);
+
+   return 0;
+}
+
+/*============================================================================
+*   MOL: Mollweide's projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "MOL"
+*      prj->flag    MOL
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    sqrt(2)*r0
+*      prj->w[1]    sqrt(2)*r0/90
+*      prj->w[2]    1/(sqrt(2)*r0)
+*      prj->w[3]    90/r0
+*      prj->astPRJfwd  Pointer to astMOLfwd().
+*      prj->astPRJrev  Pointer to astMOLrev().
+*===========================================================================*/
+
+int astMOLset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "MOL");
+   prj->flag   = WCS__MOL;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   prj->w[0] = SQRT2*prj->r0;
+   prj->w[1] = prj->w[0]/90.0;
+   prj->w[2] = 1.0/prj->w[0];
+   prj->w[3] = 90.0/prj->r0;
+   prj->w[4] = 2.0/PI;
+
+   prj->astPRJfwd = astMOLfwd;
+   prj->astPRJrev = astMOLrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astMOLfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   int   j;
+   double gamma, resid, u, v, v0, v1;
+   const double tol = 1.0e-13;
+
+   if (prj->flag != WCS__MOL) {
+      if (astMOLset(prj)) return 1;
+   }
+
+   if (fabs(theta) == 90.0) {
+      *x = 0.0;
+      *y = copysign(prj->w[0],theta);
+   } else if (theta == 0.0) {
+      *x = prj->w[1]*phi;
+      *y = 0.0;
+   } else {
+      u  = PI*astSind(theta);
+      v0 = -PI;
+      v1 =  PI;
+      v  = u;
+      for (j = 0; j < 100; j++) {
+         resid = (v - u) + sin(v);
+         if (resid < 0.0) {
+            if (resid > -tol) break;
+            v0 = v;
+         } else {
+            if (resid < tol) break;
+            v1 = v;
+         }
+         v = (v0 + v1)/2.0;
+      }
+
+      gamma = v/2.0;
+      *x = prj->w[1]*phi*cos(gamma);
+      *y = prj->w[0]*sin(gamma);
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astMOLrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double s, y0, z;
+   const double tol = 1.0e-12;
+
+   if (prj->flag != WCS__MOL) {
+      if (astMOLset(prj)) return 1;
+   }
+
+   y0 = y/prj->r0;
+   s  = 2.0 - y0*y0;
+   if (s <= tol) {
+      if (s < -tol) {
+         return 2;
+      }
+      s = 0.0;
+
+      if (fabs(x) > tol) {
+         return 2;
+      }
+      *phi = 0.0;
+   } else {
+      s = sqrt(s);
+      *phi = prj->w[3]*x/s;
+   }
+
+   z = y*prj->w[2];
+   if (fabs(z) > 1.0) {
+      if (fabs(z) > 1.0+tol) {
+         return 2;
+      }
+      z = copysign(1.0,z) + y0*s/PI;
+   } else {
+      z = asin(z)*prj->w[4] + y0*s/PI;
+   }
+
+   if (fabs(z) > 1.0) {
+      if (fabs(z) > 1.0+tol) {
+         return 2;
+      }
+      z = copysign(1.0,z);
+   }
+
+   *theta = astASind(z);
+
+   return 0;
+}
+
+/*============================================================================
+*   AIT: Hammer-Aitoff projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "AIT"
+*      prj->flag    AIT
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    2*r0**2
+*      prj->w[1]    1/(2*r0)**2
+*      prj->w[2]    1/(4*r0)**2
+*      prj->w[3]    1/(2*r0)
+*      prj->astPRJfwd  Pointer to astAITfwd().
+*      prj->astPRJrev  Pointer to astAITrev().
+*===========================================================================*/
+
+int astAITset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "AIT");
+   prj->flag   = WCS__AIT;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   prj->w[0] = 2.0*prj->r0*prj->r0;
+   prj->w[1] = 1.0/(2.0*prj->w[0]);
+   prj->w[2] = prj->w[1]/4.0;
+   prj->w[3] = 1.0/(2.0*prj->r0);
+
+   prj->astPRJfwd = astAITfwd;
+   prj->astPRJrev = astAITrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astAITfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double cthe, w;
+
+   if (prj->flag != WCS__AIT) {
+      if (astAITset(prj)) return 1;
+   }
+
+   cthe = astCosd(theta);
+   w = sqrt(prj->w[0]/(1.0 + cthe*astCosd(phi/2.0)));
+   *x = 2.0*w*cthe*astSind(phi/2.0);
+   *y = w*astSind(theta);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astAITrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double s, u, xp, yp, z;
+   const double tol = 1.0e-13;
+
+   if (prj->flag != WCS__AIT) {
+      if (astAITset(prj)) return 1;
+   }
+
+   u = 1.0 - x*x*prj->w[2] - y*y*prj->w[1];
+   if (u < 0.0) {
+      if (u < -tol) {
+         return 2;
+      }
+
+      u = 0.0;
+   }
+
+   z = sqrt(u);
+   s = z*y/prj->r0;
+   if (fabs(s) > 1.0) {
+      if (fabs(s) > 1.0+tol) {
+         return 2;
+      }
+      s = copysign(1.0,s);
+   }
+
+   xp = 2.0*z*z - 1.0;
+   yp = z*x*prj->w[3];
+   if (xp == 0.0 && yp == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = 2.0*astATan2d(yp, xp);
+   }
+   *theta = astASind(s);
+
+   return 0;
+}
+
+/*============================================================================
+*   COP: conic perspective projection.
+*
+*   Given:
+*      prj->p[1]    sigma = (theta2+theta1)/2
+*      prj->p[2]    delta = (theta2-theta1)/2, where theta1 and theta2 are the
+*                   latitudes of the standard parallels, in degrees.
+*
+*   Given and/or returned:
+*      prj->flag    COP, or -COP if prj->flag is given < 0.
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "COP"
+*      prj->phi0     0.0
+*      prj->theta0  sigma
+*      prj->w[0]    C  = sin(sigma)
+*      prj->w[1]    1/C
+*      prj->w[2]    Y0 = r0*cos(delta)*cot(sigma)
+*      prj->w[3]    r0*cos(delta)
+*      prj->w[4]    1/(r0*cos(delta)
+*      prj->w[5]    cot(sigma)
+*      prj->astPRJfwd  Pointer to astCOPfwd().
+*      prj->astPRJrev  Pointer to astCOPrev().
+*===========================================================================*/
+
+int astCOPset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "COP");
+   prj->flag   = icopysign(WCS__COP, prj->flag);
+   prj->phi0   = 0.0;
+   prj->theta0 = prj->p[1];
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   prj->w[0] = astSind(prj->p[1]);
+   if (prj->w[0] == 0.0) {
+      return 1;
+   }
+
+   prj->w[1] = 1.0/prj->w[0];
+
+   prj->w[3] = prj->r0*astCosd(prj->p[2]);
+   if (prj->w[3] == 0.0) {
+      return 1;
+   }
+
+   prj->w[4] = 1.0/prj->w[3];
+   prj->w[5] = 1.0/astTand(prj->p[1]);
+
+   prj->w[2] = prj->w[3]*prj->w[5];
+
+   prj->astPRJfwd = astCOPfwd;
+   prj->astPRJrev = astCOPrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCOPfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double a, r, s, t;
+
+   if (abs(prj->flag) != WCS__COP) {
+      if (astCOPset(prj)) return 1;
+   }
+
+   t = theta - prj->p[1];
+   s = astCosd(t);
+   if (s == 0.0) {
+      return 2;
+   }
+
+   a = prj->w[0]*phi;
+   r = prj->w[2] - prj->w[3]*astSind(t)/s;
+
+   *x =             r*astSind(a);
+   *y = prj->w[2] - r*astCosd(a);
+
+   if (prj->flag > 0 && r*prj->w[0] < 0.0) {
+      return 2;
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCOPrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double a, dy, r;
+
+   if (abs(prj->flag) != WCS__COP) {
+      if (astCOPset(prj)) return 1;
+   }
+
+   dy = prj->w[2] - y;
+   r  = sqrt(x*x + dy*dy);
+   if (prj->p[1] < 0.0) r = -r;
+
+   if (r == 0.0) {
+      a = 0.0;
+   } else {
+      a = astATan2d(x/r, dy/r);
+   }
+
+   *phi   = a*prj->w[1];
+   *theta = prj->p[1] + astATand(prj->w[5] - r*prj->w[4]);
+
+   return 0;
+}
+
+/*============================================================================
+*   COE: conic equal area projection.
+*
+*   Given:
+*      prj->p[1]    sigma = (theta2+theta1)/2
+*      prj->p[2]    delta = (theta2-theta1)/2, where theta1 and theta2 are the
+*                   latitudes of the standard parallels, in degrees.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "COE"
+*      prj->flag    COE
+*      prj->phi0    0.0
+*      prj->theta0  sigma
+*      prj->w[0]    C = (sin(theta1) + sin(theta2))/2
+*      prj->w[1]    1/C
+*      prj->w[2]    Y0 = chi*sqrt(psi - 2C*astSind(sigma))
+*      prj->w[3]    chi = r0/C
+*      prj->w[4]    psi = 1 + sin(theta1)*sin(theta2)
+*      prj->w[5]    2C
+*      prj->w[6]    (1 + sin(theta1)*sin(theta2))*(r0/C)**2
+*      prj->w[7]    C/(2*r0**2)
+*      prj->w[8]    chi*sqrt(psi + 2C)
+*      prj->astPRJfwd  Pointer to astCOEfwd().
+*      prj->astPRJrev  Pointer to astCOErev().
+*===========================================================================*/
+
+int astCOEset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   double theta1, theta2;
+
+   strcpy(prj->code, "COE");
+   prj->flag   = WCS__COE;
+   prj->phi0   = 0.0;
+   prj->theta0 = prj->p[1];
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   theta1 = prj->p[1] - prj->p[2];
+   theta2 = prj->p[1] + prj->p[2];
+
+   prj->w[0] = (astSind(theta1) + astSind(theta2))/2.0;
+   if (prj->w[0] == 0.0) {
+      return 1;
+   }
+
+   prj->w[1] = 1.0/prj->w[0];
+
+   prj->w[3] = prj->r0/prj->w[0];
+   prj->w[4] = 1.0 + astSind(theta1)*astSind(theta2);
+   prj->w[5] = 2.0*prj->w[0];
+   prj->w[6] = prj->w[3]*prj->w[3]*prj->w[4];
+   prj->w[7] = 1.0/(2.0*prj->r0*prj->w[3]);
+   prj->w[8] = prj->w[3]*sqrt(prj->w[4] + prj->w[5]);
+
+   prj->w[2] = prj->w[3]*sqrt(prj->w[4] - prj->w[5]*astSind(prj->p[1]));
+
+   prj->astPRJfwd = astCOEfwd;
+   prj->astPRJrev = astCOErev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCOEfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double a, r;
+
+   if (prj->flag != WCS__COE) {
+      if (astCOEset(prj)) return 1;
+   }
+
+   a = phi*prj->w[0];
+   if (theta == -90.0) {
+      r = prj->w[8];
+   } else {
+      r = prj->w[3]*sqrt(prj->w[4] - prj->w[5]*astSind(theta));
+   }
+
+   *x =             r*astSind(a);
+   *y = prj->w[2] - r*astCosd(a);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCOErev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double a, dy, r, w;
+   const double tol = 1.0e-12;
+
+   if (prj->flag != WCS__COE) {
+      if (astCOEset(prj)) return 1;
+   }
+
+   dy = prj->w[2] - y;
+   r  = sqrt(x*x + dy*dy);
+   if (prj->p[1] < 0.0) r = -r;
+
+   if (r == 0.0) {
+      a = 0.0;
+   } else {
+      a = astATan2d(x/r, dy/r);
+   }
+
+   *phi = a*prj->w[1];
+   if (fabs(r - prj->w[8]) < tol) {
+      *theta = -90.0;
+   } else {
+      w = (prj->w[6] - r*r)*prj->w[7];
+      if (fabs(w) > 1.0) {
+         if (fabs(w-1.0) < tol) {
+            *theta = 90.0;
+         } else if (fabs(w+1.0) < tol) {
+            *theta = -90.0;
+         } else {
+            return 2;
+         }
+      } else {
+         *theta = astASind(w);
+      }
+   }
+
+   return 0;
+}
+
+/*============================================================================
+*   COD: conic equidistant projection.
+*
+*   Given:
+*      prj->p[1]    sigma = (theta2+theta1)/2
+*      prj->p[2]    delta = (theta2-theta1)/2, where theta1 and theta2 are the
+*                   latitudes of the standard parallels, in degrees.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "COD"
+*      prj->flag    COD
+*      prj->phi0    0.0
+*      prj->theta0  sigma
+*      prj->w[0]    C = r0*sin(sigma)*sin(delta)/delta
+*      prj->w[1]    1/C
+*      prj->w[2]    Y0 = delta*cot(delta)*cot(sigma)
+*      prj->w[3]    Y0 + sigma
+*      prj->astPRJfwd  Pointer to astCODfwd().
+*      prj->astPRJrev  Pointer to astCODrev().
+*===========================================================================*/
+
+int astCODset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "COD");
+   prj->flag   = WCS__COD;
+   prj->phi0   = 0.0;
+   prj->theta0 = prj->p[1];
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   if (prj->p[2] == 0.0) {
+      prj->w[0] = prj->r0*astSind(prj->p[1])*D2R;
+   } else {
+      prj->w[0] = prj->r0*astSind(prj->p[1])*astSind(prj->p[2])/prj->p[2];
+   }
+
+   if (prj->w[0] == 0.0) {
+      return 1;
+   }
+
+   prj->w[1] = 1.0/prj->w[0];
+   prj->w[2] = prj->r0*astCosd(prj->p[2])*astCosd(prj->p[1])/prj->w[0];
+   prj->w[3] = prj->w[2] + prj->p[1];
+
+   prj->astPRJfwd = astCODfwd;
+   prj->astPRJrev = astCODrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCODfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double a, r;
+
+   if (prj->flag != WCS__COD) {
+      if (astCODset(prj)) return 1;
+   }
+
+   a = prj->w[0]*phi;
+   r = prj->w[3] - theta;
+
+   *x =             r*astSind(a);
+   *y = prj->w[2] - r*astCosd(a);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCODrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double a, dy, r;
+
+   if (prj->flag != WCS__COD) {
+      if (astCODset(prj)) return 1;
+   }
+
+   dy = prj->w[2] - y;
+   r  = sqrt(x*x + dy*dy);
+   if (prj->p[1] < 0.0) r = -r;
+
+   if (r == 0.0) {
+      a = 0.0;
+   } else {
+      a = astATan2d(x/r, dy/r);
+   }
+
+   *phi   = a*prj->w[1];
+   *theta = prj->w[3] - r;
+
+   return 0;
+}
+
+/*============================================================================
+*   COO: conic orthomorphic projection.
+*
+*   Given:
+*      prj->p[1]    sigma = (theta2+theta1)/2
+*      prj->p[2]    delta = (theta2-theta1)/2, where theta1 and theta2 are the
+*                   latitudes of the standard parallels, in degrees.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "COO"
+*      prj->flag    COO
+*      prj->phi0    0.0
+*      prj->theta0  sigma
+*      prj->w[0]    C = ln(cos(theta2)/cos(theta1))/ln(tan(tau2)/tan(tau1))
+*                       where tau1 = (90 - theta1)/2
+*                             tau2 = (90 - theta2)/2
+*      prj->w[1]    1/C
+*      prj->w[2]    Y0 = psi*tan((90-sigma)/2)**C
+*      prj->w[3]    psi = (r0*cos(theta1)/C)/tan(tau1)**C
+*      prj->w[4]    1/psi
+*      prj->astPRJfwd  Pointer to astCOOfwd().
+*      prj->astPRJrev  Pointer to astCOOrev().
+*===========================================================================*/
+
+int astCOOset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   double cos1, cos2, tan1, tan2, theta1, theta2;
+
+   strcpy(prj->code, "COO");
+   prj->flag   = WCS__COO;
+   prj->phi0   = 0.0;
+   prj->theta0 = prj->p[1];
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   theta1 = prj->p[1] - prj->p[2];
+   theta2 = prj->p[1] + prj->p[2];
+
+   tan1 = astTand((90.0 - theta1)/2.0);
+   cos1 = astCosd(theta1);
+
+   if (theta1 == theta2) {
+      prj->w[0] = astSind(theta1);
+   } else {
+      tan2 = astTand((90.0 - theta2)/2.0);
+      cos2 = astCosd(theta2);
+      prj->w[0] = log(cos2/cos1)/log(tan2/tan1);
+   }
+   if (prj->w[0] == 0.0) {
+      return 1;
+   }
+
+   prj->w[1] = 1.0/prj->w[0];
+
+   prj->w[3] = prj->r0*(cos1/prj->w[0])/pow(tan1,prj->w[0]);
+   if (prj->w[3] == 0.0) {
+      return 1;
+   }
+   prj->w[2] = prj->w[3]*pow(astTand((90.0 - prj->p[1])/2.0),prj->w[0]);
+   prj->w[4] = 1.0/prj->w[3];
+
+   prj->astPRJfwd = astCOOfwd;
+   prj->astPRJrev = astCOOrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCOOfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double a, r;
+
+   if (prj->flag != WCS__COO) {
+      if (astCOOset(prj)) return 1;
+   }
+
+   a = prj->w[0]*phi;
+   if (theta == -90.0) {
+      if (prj->w[0] < 0.0) {
+         r = 0.0;
+      } else {
+         return 2;
+      }
+   } else {
+      r = prj->w[3]*pow(astTand((90.0 - theta)/2.0),prj->w[0]);
+   }
+
+   *x =             r*astSind(a);
+   *y = prj->w[2] - r*astCosd(a);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCOOrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double a, dy, r;
+
+   if (prj->flag != WCS__COO) {
+      if (astCOOset(prj)) return 1;
+   }
+
+   dy = prj->w[2] - y;
+   r  = sqrt(x*x + dy*dy);
+   if (prj->p[1] < 0.0) r = -r;
+
+   if (r == 0.0) {
+      a = 0.0;
+   } else {
+      a = astATan2d(x/r, dy/r);
+   }
+
+   *phi = a*prj->w[1];
+   if (r == 0.0) {
+      if (prj->w[0] < 0.0) {
+         *theta = -90.0;
+      } else {
+         return 2;
+      }
+   } else {
+      *theta = 90.0 - 2.0*astATand(pow(r*prj->w[4],prj->w[1]));
+   }
+
+   return 0;
+}
+
+/*============================================================================
+*   BON: Bonne's projection.
+*
+*   Given:
+*      prj->p[1]    Bonne conformal latitude, theta1, in degrees.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "BON"
+*      prj->flag    BON
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[1]    r0*pi/180
+*      prj->w[2]    Y0 = r0*(cot(theta1) + theta1*pi/180)
+*      prj->astPRJfwd  Pointer to astBONfwd().
+*      prj->astPRJrev  Pointer to astBONrev().
+*===========================================================================*/
+
+int astBONset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "BON");
+   prj->flag   = WCS__BON;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[1] = 1.0;
+      prj->w[2] = prj->r0*astCosd(prj->p[1])/astSind(prj->p[1]) + prj->p[1];
+   } else {
+      prj->w[1] = prj->r0*D2R;
+      prj->w[2] = prj->r0*(astCosd(prj->p[1])/astSind(prj->p[1]) + prj->p[1]*D2R);
+   }
+
+   prj->astPRJfwd = astBONfwd;
+   prj->astPRJrev = astBONrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astBONfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double a, r;
+
+   if (prj->p[1] == 0.0) {
+      /* Sanson-Flamsteed. */
+      return astSFLfwd(phi, theta, prj, x, y);
+   }
+
+   if (prj->flag != WCS__BON) {
+      if (astBONset(prj)) return 1;
+   }
+
+   r = prj->w[2] - theta*prj->w[1];
+   a = prj->r0*phi*astCosd(theta)/r;
+
+   *x =             r*astSind(a);
+   *y = prj->w[2] - r*astCosd(a);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astBONrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double a, cthe, dy, r;
+
+   if (prj->p[1] == 0.0) {
+      /* Sanson-Flamsteed. */
+      return astSFLrev(x, y, prj, phi, theta);
+   }
+
+   if (prj->flag != WCS__BON) {
+      if (astBONset(prj)) return 1;
+   }
+
+   dy = prj->w[2] - y;
+   r = sqrt(x*x + dy*dy);
+   if (prj->p[1] < 0.0) r = -r;
+
+   if (r == 0.0) {
+      a = 0.0;
+   } else {
+      a = astATan2d(x/r, dy/r);
+   }
+
+   *theta = (prj->w[2] - r)/prj->w[1];
+   cthe = astCosd(*theta);
+   if (cthe == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = a*(r/prj->r0)/cthe;
+   }
+
+   return 0;
+}
+
+/*============================================================================
+*   PCO: polyconic projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "PCO"
+*      prj->flag    PCO
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    r0*(pi/180)
+*      prj->w[1]    1/r0
+*      prj->w[2]    2*r0
+*      prj->astPRJfwd  Pointer to astPCOfwd().
+*      prj->astPRJrev  Pointer to astPCOrev().
+*===========================================================================*/
+
+int astPCOset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "PCO");
+   prj->flag   = WCS__PCO;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 1.0;
+      prj->w[1] = 1.0;
+      prj->w[2] = 360.0/PI;
+   } else {
+      prj->w[0] = prj->r0*D2R;
+      prj->w[1] = 1.0/prj->w[0];
+      prj->w[2] = 2.0*prj->r0;
+   }
+
+   prj->astPRJfwd = astPCOfwd;
+   prj->astPRJrev = astPCOrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astPCOfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double a, cthe, cotthe, sthe;
+
+   if (prj->flag != WCS__PCO) {
+      if (astPCOset(prj)) return 1;
+   }
+
+   cthe = astCosd(theta);
+   sthe = astSind(theta);
+   a = phi*sthe;
+
+   if (sthe == 0.0) {
+      *x = prj->w[0]*phi;
+      *y = 0.0;
+   } else {
+      cotthe = cthe/sthe;
+      *x = prj->r0*cotthe*astSind(a);
+      *y = prj->r0*(cotthe*(1.0 - astCosd(a)) + theta*D2R);
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astPCOrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   int   j;
+   double f, fneg, fpos, lambda, tanthe, theneg, thepos, w, xp, xx, ymthe, yp;
+   const double tol = 1.0e-12;
+
+   if (prj->flag != WCS__PCO) {
+      if (astPCOset(prj)) return 1;
+   }
+
+   w = fabs(y*prj->w[1]);
+   if (w < tol) {
+      *phi = x*prj->w[1];
+      *theta = 0.0;
+   } else if (fabs(w-90.0) < tol) {
+      *phi = 0.0;
+      *theta = copysign(90.0,y);
+   } else {
+      /* Iterative solution using weighted division of the interval. */
+      if (y > 0.0) {
+         thepos =  90.0;
+      } else {
+         thepos = -90.0;
+      }
+      theneg = 0.0;
+
+      xx = x*x;
+      ymthe = y - prj->w[0]*thepos;
+      fpos = xx + ymthe*ymthe;
+      fneg = -999.0;
+
+      for (j = 0; j < 64; j++) {
+         if (fneg < -100.0) {
+            /* Equal division of the interval. */
+            *theta = (thepos+theneg)/2.0;
+         } else {
+            /* Weighted division of the interval. */
+            lambda = fpos/(fpos-fneg);
+            if (lambda < 0.1) {
+               lambda = 0.1;
+            } else if (lambda > 0.9) {
+               lambda = 0.9;
+            }
+            *theta = thepos - lambda*(thepos-theneg);
+         }
+
+         /* Compute the residue. */
+         ymthe = y - prj->w[0]*(*theta);
+         tanthe = astTand(*theta);
+         f = xx + ymthe*(ymthe - prj->w[2]/tanthe);
+
+         /* Check for convergence. */
+         if (fabs(f) < tol) break;
+         if (fabs(thepos-theneg) < tol) break;
+
+         /* Redefine the interval. */
+         if (f > 0.0) {
+            thepos = *theta;
+            fpos = f;
+         } else {
+            theneg = *theta;
+            fneg = f;
+         }
+      }
+
+      xp = prj->r0 - ymthe*tanthe;
+      yp = x*tanthe;
+      if (xp == 0.0 && yp == 0.0) {
+         *phi = 0.0;
+      } else {
+         *phi = astATan2d(yp, xp)/astSind(*theta);
+      }
+   }
+
+   return 0;
+}
+
+/*============================================================================
+*   TSC: tangential spherical cube projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "TSC"
+*      prj->flag    TSC
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    r0*(pi/4)
+*      prj->w[1]    (4/pi)/r0
+*      prj->astPRJfwd  Pointer to astTSCfwd().
+*      prj->astPRJrev  Pointer to astTSCrev().
+*===========================================================================*/
+
+int astTSCset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "TSC");
+   prj->flag   = WCS__TSC;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 45.0;
+      prj->w[1] = 1.0/45.0;
+   } else {
+      prj->w[0] = prj->r0*PI/4.0;
+      prj->w[1] = 1.0/prj->w[0];
+   }
+
+   prj->astPRJfwd = astTSCfwd;
+   prj->astPRJrev = astTSCrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astTSCfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   int   face;
+   double cthe, l, m, n, rho, x0, xf, y0, yf;
+   const double tol = 1.0e-12;
+
+   x0 = 0.0;
+   xf = 0.0;
+   y0 = 0.0;
+   yf = 0.0;
+
+   if (prj->flag != WCS__TSC) {
+      if (astTSCset(prj)) return 1;
+   }
+
+   cthe = astCosd(theta);
+   l = cthe*astCosd(phi);
+   m = cthe*astSind(phi);
+   n = astSind(theta);
+
+   face = 0;
+   rho  = n;
+   if (l > rho) {
+      face = 1;
+      rho  = l;
+   }
+   if (m > rho) {
+      face = 2;
+      rho  = m;
+   }
+   if (-l > rho) {
+      face = 3;
+      rho  = -l;
+   }
+   if (-m > rho) {
+      face = 4;
+      rho  = -m;
+   }
+   if (-n > rho) {
+      face = 5;
+      rho  = -n;
+   }
+
+   if (face == 0) {
+      xf =  m/rho;
+      yf = -l/rho;
+      x0 =  0.0;
+      y0 =  2.0;
+   } else if (face == 1) {
+      xf =  m/rho;
+      yf =  n/rho;
+      x0 =  0.0;
+      y0 =  0.0;
+   } else if (face == 2) {
+      xf = -l/rho;
+      yf =  n/rho;
+      x0 =  2.0;
+      y0 =  0.0;
+   } else if (face == 3) {
+      xf = -m/rho;
+      yf =  n/rho;
+      x0 =  4.0;
+      y0 =  0.0;
+   } else if (face == 4) {
+      xf =  l/rho;
+      yf =  n/rho;
+      x0 =  6.0;
+      y0 =  0.0;
+   } else if (face == 5) {
+      xf =  m/rho;
+      yf =  l/rho;
+      x0 =  0.0;
+      y0 = -2.0;
+   }
+
+   if (fabs(xf) > 1.0) {
+      if (fabs(xf) > 1.0+tol) {
+         return 2;
+      }
+      xf = copysign(1.0,xf);
+   }
+   if (fabs(yf) > 1.0) {
+      if (fabs(yf) > 1.0+tol) {
+         return 2;
+      }
+      yf = copysign(1.0,yf);
+   }
+
+   *x = prj->w[0]*(xf + x0);
+   *y = prj->w[0]*(yf + y0);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astTSCrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double l, m, n, xf, yf;
+
+   if (prj->flag != WCS__TSC) {
+      if (astTSCset(prj)) return 1;
+   }
+
+   xf = x*prj->w[1];
+   yf = y*prj->w[1];
+
+   /* Check bounds. */
+   if (fabs(xf) <= 1.0) {
+      if (fabs(yf) > 3.0) return 2;
+   } else {
+      if (fabs(xf) > 7.0) return 2;
+      if (fabs(yf) > 1.0) return 2;
+   }
+
+   /* Map negative faces to the other side. */
+   if (xf < -1.0) xf += 8.0;
+
+   /* Determine the face. */
+   if (xf > 5.0) {
+      /* face = 4 */
+      xf = xf - 6.0;
+      m  = -1.0/sqrt(1.0 + xf*xf + yf*yf);
+      l  = -m*xf;
+      n  = -m*yf;
+   } else if (xf > 3.0) {
+      /* face = 3 */
+      xf = xf - 4.0;
+      l  = -1.0/sqrt(1.0 + xf*xf + yf*yf);
+      m  =  l*xf;
+      n  = -l*yf;
+   } else if (xf > 1.0) {
+      /* face = 2 */
+      xf = xf - 2.0;
+      m  =  1.0/sqrt(1.0 + xf*xf + yf*yf);
+      l  = -m*xf;
+      n  =  m*yf;
+   } else if (yf > 1.0) {
+      /* face = 0 */
+      yf = yf - 2.0;
+      n  = 1.0/sqrt(1.0 + xf*xf + yf*yf);
+      l  = -n*yf;
+      m  =  n*xf;
+   } else if (yf < -1.0) {
+      /* face = 5 */
+      yf = yf + 2.0;
+      n  = -1.0/sqrt(1.0 + xf*xf + yf*yf);
+      l  = -n*yf;
+      m  = -n*xf;
+   } else {
+      /* face = 1 */
+      l  =  1.0/sqrt(1.0 + xf*xf + yf*yf);
+      m  =  l*xf;
+      n  =  l*yf;
+   }
+
+   if (l == 0.0 && m == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = astATan2d(m, l);
+   }
+   *theta = astASind(n);
+
+   return 0;
+}
+
+/*============================================================================
+*   CSC: COBE quadrilateralized spherical cube projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "CSC"
+*      prj->flag    CSC
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    r0*(pi/4)
+*      prj->w[1]    (4/pi)/r0
+*      prj->astPRJfwd  Pointer to astCSCfwd().
+*      prj->astPRJrev  Pointer to astCSCrev().
+*===========================================================================*/
+
+int astCSCset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "CSC");
+   prj->flag   = WCS__CSC;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 45.0;
+      prj->w[1] = 1.0/45.0;
+   } else {
+      prj->w[0] = prj->r0*PI/4.0;
+      prj->w[1] = 1.0/prj->w[0];
+   }
+
+   prj->astPRJfwd = astCSCfwd;
+   prj->astPRJrev = astCSCrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCSCfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   int   face;
+   float cthe, eta, l, m, n, rho, xi;
+   const float tol = 1.0e-7;
+
+   float a, a2, a2b2, a4, ab, b, b2, b4, ca2, cb2, x0, xf, y0, yf;
+   const float gstar  =  1.37484847732;
+   const float mm     =  0.004869491981;
+   const float gamma  = -0.13161671474;
+   const float omega1 = -0.159596235474;
+   const float d0  =  0.0759196200467;
+   const float d1  = -0.0217762490699;
+   const float c00 =  0.141189631152;
+   const float c10 =  0.0809701286525;
+   const float c01 = -0.281528535557;
+   const float c11 =  0.15384112876;
+   const float c20 = -0.178251207466;
+   const float c02 =  0.106959469314;
+
+   eta = 0.0;
+   xi = 0.0;
+   x0 = 0.0;
+   y0 = 0.0;
+
+   if (prj->flag != WCS__CSC) {
+      if (astCSCset(prj)) return 1;
+   }
+
+   cthe = astCosd(theta);
+   l = cthe*astCosd(phi);
+   m = cthe*astSind(phi);
+   n = astSind(theta);
+
+   face = 0;
+   rho  = n;
+   if (l > rho) {
+      face = 1;
+      rho  = l;
+   }
+   if (m > rho) {
+      face = 2;
+      rho  = m;
+   }
+   if (-l > rho) {
+      face = 3;
+      rho  = -l;
+   }
+   if (-m > rho) {
+      face = 4;
+      rho  = -m;
+   }
+   if (-n > rho) {
+      face = 5;
+      rho  = -n;
+   }
+
+   if (face == 0) {
+      xi  =  m;
+      eta = -l;
+      x0  =  0.0;
+      y0  =  2.0;
+   } else if (face == 1) {
+      xi  =  m;
+      eta =  n;
+      x0  =  0.0;
+      y0  =  0.0;
+   } else if (face == 2) {
+      xi  = -l;
+      eta =  n;
+      x0  =  2.0;
+      y0  =  0.0;
+   } else if (face == 3) {
+      xi  = -m;
+      eta =  n;
+      x0  =  4.0;
+      y0  =  0.0;
+   } else if (face == 4) {
+      xi  =  l;
+      eta =  n;
+      x0  =  6.0;
+      y0  =  0.0;
+   } else if (face == 5) {
+      xi  =  m;
+      eta =  l;
+      x0  =  0.0;
+      y0  = -2.0;
+   }
+
+   a =  xi/rho;
+   b = eta/rho;
+
+   a2 = a*a;
+   b2 = b*b;
+   ca2 = 1.0 - a2;
+   cb2 = 1.0 - b2;
+
+   /* Avoid floating underflows. */
+   ab   = fabs(a*b);
+   a4   = (a2 > 1.0e-16) ? a2*a2 : 0.0;
+   b4   = (b2 > 1.0e-16) ? b2*b2 : 0.0;
+   a2b2 = (ab > 1.0e-16) ? a2*b2 : 0.0;
+
+   xf = a*(a2 + ca2*(gstar + b2*(gamma*ca2 + mm*a2 +
+          cb2*(c00 + c10*a2 + c01*b2 + c11*a2b2 + c20*a4 + c02*b4)) +
+          a2*(omega1 - ca2*(d0 + d1*a2))));
+   yf = b*(b2 + cb2*(gstar + a2*(gamma*cb2 + mm*b2 +
+          ca2*(c00 + c10*b2 + c01*a2 + c11*a2b2 + c20*b4 + c02*a4)) +
+          b2*(omega1 - cb2*(d0 + d1*b2))));
+
+   if (fabs(xf) > 1.0) {
+      if (fabs(xf) > 1.0+tol) {
+         return 2;
+      }
+      xf = copysign(1.0,xf);
+   }
+   if (fabs(yf) > 1.0) {
+      if (fabs(yf) > 1.0+tol) {
+         return 2;
+      }
+      yf = copysign(1.0,yf);
+   }
+
+   *x = prj->w[0]*(x0 + xf);
+   *y = prj->w[0]*(y0 + yf);
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astCSCrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   int   face;
+   float l, m, n;
+
+   float     a, b, xf, xx, yf, yy, z0, z1, z2, z3, z4, z5, z6;
+   const float p00 = -0.27292696;
+   const float p10 = -0.07629969;
+   const float p20 = -0.22797056;
+   const float p30 =  0.54852384;
+   const float p40 = -0.62930065;
+   const float p50 =  0.25795794;
+   const float p60 =  0.02584375;
+   const float p01 = -0.02819452;
+   const float p11 = -0.01471565;
+   const float p21 =  0.48051509;
+   const float p31 = -1.74114454;
+   const float p41 =  1.71547508;
+   const float p51 = -0.53022337;
+   const float p02 =  0.27058160;
+   const float p12 = -0.56800938;
+   const float p22 =  0.30803317;
+   const float p32 =  0.98938102;
+   const float p42 = -0.83180469;
+   const float p03 = -0.60441560;
+   const float p13 =  1.50880086;
+   const float p23 = -0.93678576;
+   const float p33 =  0.08693841;
+   const float p04 =  0.93412077;
+   const float p14 = -1.41601920;
+   const float p24 =  0.33887446;
+   const float p05 = -0.63915306;
+   const float p15 =  0.52032238;
+   const float p06 =  0.14381585;
+
+   l = 0.0;
+   m = 0.0;
+   n = 0.0;
+
+   if (prj->flag != WCS__CSC) {
+      if (astCSCset(prj)) return 1;
+   }
+
+   xf = x*prj->w[1];
+   yf = y*prj->w[1];
+
+   /* Check bounds. */
+   if (fabs(xf) <= 1.0) {
+      if (fabs(yf) > 3.0) return 2;
+   } else {
+      if (fabs(xf) > 7.0) return 2;
+      if (fabs(yf) > 1.0) return 2;
+   }
+
+   /* Map negative faces to the other side. */
+   if (xf < -1.0) xf += 8.0;
+
+   /* Determine the face. */
+   if (xf > 5.0) {
+      face = 4;
+      xf = xf - 6.0;
+   } else if (xf > 3.0) {
+      face = 3;
+      xf = xf - 4.0;
+   } else if (xf > 1.0) {
+      face = 2;
+      xf = xf - 2.0;
+   } else if (yf > 1.0) {
+      face = 0;
+      yf = yf - 2.0;
+   } else if (yf < -1.0) {
+      face = 5;
+      yf = yf + 2.0;
+   } else {
+      face = 1;
+   }
+
+   xx  =  xf*xf;
+   yy  =  yf*yf;
+
+   z0 = p00 + xx*(p10 + xx*(p20 + xx*(p30 + xx*(p40 + xx*(p50 + xx*(p60))))));
+   z1 = p01 + xx*(p11 + xx*(p21 + xx*(p31 + xx*(p41 + xx*(p51)))));
+   z2 = p02 + xx*(p12 + xx*(p22 + xx*(p32 + xx*(p42))));
+   z3 = p03 + xx*(p13 + xx*(p23 + xx*(p33)));
+   z4 = p04 + xx*(p14 + xx*(p24));
+   z5 = p05 + xx*(p15);
+   z6 = p06;
+
+   a = z0 + yy*(z1 + yy*(z2 + yy*(z3 + yy*(z4 + yy*(z5 + yy*z6)))));
+   a = xf + xf*(1.0 - xx)*a;
+
+   z0 = p00 + yy*(p10 + yy*(p20 + yy*(p30 + yy*(p40 + yy*(p50 + yy*(p60))))));
+   z1 = p01 + yy*(p11 + yy*(p21 + yy*(p31 + yy*(p41 + yy*(p51)))));
+   z2 = p02 + yy*(p12 + yy*(p22 + yy*(p32 + yy*(p42))));
+   z3 = p03 + yy*(p13 + yy*(p23 + yy*(p33)));
+   z4 = p04 + yy*(p14 + yy*(p24));
+   z5 = p05 + yy*(p15);
+   z6 = p06;
+
+   b = z0 + xx*(z1 + xx*(z2 + xx*(z3 + xx*(z4 + xx*(z5 + xx*z6)))));
+   b = yf + yf*(1.0 - yy)*b;
+
+   if (face == 0) {
+      n =  1.0/sqrt(a*a + b*b + 1.0);
+      l = -b*n;
+      m =  a*n;
+   } else if (face == 1) {
+      l =  1.0/sqrt(a*a + b*b + 1.0);
+      m =  a*l;
+      n =  b*l;
+   } else if (face == 2) {
+      m =  1.0/sqrt(a*a + b*b + 1.0);
+      l = -a*m;
+      n =  b*m;
+   } else if (face == 3) {
+      l = -1.0/sqrt(a*a + b*b + 1.0);
+      m =  a*l;
+      n = -b*l;
+   } else if (face == 4) {
+      m = -1.0/sqrt(a*a + b*b + 1.0);
+      l = -a*m;
+      n = -b*m;
+   } else if (face == 5) {
+      n = -1.0/sqrt(a*a + b*b + 1.0);
+      l = -b*n;
+      m = -a*n;
+   }
+
+   if (l == 0.0 && m == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = astATan2d(m, l);
+   }
+   *theta = astASind(n);
+
+   return 0;
+}
+
+/*============================================================================
+*   QSC: quadrilaterilized spherical cube projection.
+*
+*   Given and/or returned:
+*      prj->r0      r0; reset to 180/pi if 0.
+*
+*   Returned:
+*      prj->code    "QSC"
+*      prj->flag    QSC
+*      prj->phi0    0.0
+*      prj->theta0  0.0
+*      prj->w[0]    r0*(pi/4)
+*      prj->w[1]    (4/pi)/r0
+*      prj->astPRJfwd  Pointer to astQSCfwd().
+*      prj->astPRJrev  Pointer to astQSCrev().
+*===========================================================================*/
+
+int astQSCset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "QSC");
+   prj->flag   = WCS__QSC;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 45.0;
+      prj->w[1] = 1.0/45.0;
+   } else {
+      prj->w[0] = prj->r0*PI/4.0;
+      prj->w[1] = 1.0/prj->w[0];
+   }
+
+   prj->astPRJfwd = astQSCfwd;
+   prj->astPRJrev = astQSCrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astQSCfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   int   face;
+   double cthe, eta, l, m, n, omega, p, rho, rhu, t, tau, x0, xf, xi, y0, yf;
+   const double tol = 1.0e-12;
+
+   eta = 0.0;
+   x0 = 0.0;
+   xf = 0.0;
+   xi = 0.0;
+   y0 = 0.0;
+   yf = 0.0;
+
+   if (prj->flag != WCS__QSC) {
+      if (astQSCset(prj)) return 1;
+   }
+
+   if (fabs(theta) == 90.0) {
+      *x = 0.0;
+      *y = copysign(2.0*prj->w[0],theta);
+      return 0;
+   }
+
+   cthe = astCosd(theta);
+   l = cthe*astCosd(phi);
+   m = cthe*astSind(phi);
+   n = astSind(theta);
+
+   face = 0;
+   rho  = n;
+   if (l > rho) {
+      face = 1;
+      rho  = l;
+   }
+   if (m > rho) {
+      face = 2;
+      rho  = m;
+   }
+   if (-l > rho) {
+      face = 3;
+      rho  = -l;
+   }
+   if (-m > rho) {
+      face = 4;
+      rho  = -m;
+   }
+   if (-n > rho) {
+      face = 5;
+      rho  = -n;
+   }
+
+   rhu = 1.0 - rho;
+
+   if (face == 0) {
+      xi  =  m;
+      eta = -l;
+      if (rhu < 1.0e-8) {
+         /* Small angle formula. */
+         t = (90.0 - theta)*D2R;
+         rhu = t*t/2.0;
+      }
+      x0  =  0.0;
+      y0  =  2.0;
+   } else if (face == 1) {
+      xi  =  m;
+      eta =  n;
+      if (rhu < 1.0e-8) {
+         /* Small angle formula. */
+         t = theta*D2R;
+         p = fmod(phi,360.0);
+         if (p < -180.0) p += 360.0;
+         if (p >  180.0) p -= 360.0;
+         p *= D2R;
+         rhu = (p*p + t*t)/2.0;
+      }
+      x0  =  0.0;
+      y0  =  0.0;
+   } else if (face == 2) {
+      xi  = -l;
+      eta =  n;
+      if (rhu < 1.0e-8) {
+         /* Small angle formula. */
+         t = theta*D2R;
+         p = fmod(phi,360.0);
+         if (p < -180.0) p += 360.0;
+         p = (90.0 - p)*D2R;
+         rhu = (p*p + t*t)/2.0;
+      }
+      x0  =  2.0;
+      y0  =  0.0;
+   } else if (face == 3) {
+      xi  = -m;
+      eta =  n;
+      if (rhu < 1.0e-8) {
+         /* Small angle formula. */
+         t = theta*D2R;
+         p = fmod(phi,360.0);
+         if (p < 0.0) p += 360.0;
+         p = (180.0 - p)*D2R;
+         rhu = (p*p + t*t)/2.0;
+      }
+      x0  =  4.0;
+      y0  =  0.0;
+   } else if (face == 4) {
+      xi  =  l;
+      eta =  n;
+      if (rhu < 1.0e-8) {
+         /* Small angle formula. */
+         t = theta*D2R;
+         p = fmod(phi,360.0);
+         if (p > 180.0) p -= 360.0;
+         p *= (90.0 + p)*D2R;
+         rhu = (p*p + t*t)/2.0;
+      }
+      x0  =  6;
+      y0  =  0.0;
+   } else if (face == 5) {
+      xi  =  m;
+      eta =  l;
+      if (rhu < 1.0e-8) {
+         /* Small angle formula. */
+         t = (90.0 + theta)*D2R;
+         rhu = t*t/2.0;
+      }
+      x0  =  0.0;
+      y0  = -2;
+   }
+
+   if (xi == 0.0 && eta == 0.0) {
+      xf  = 0.0;
+      yf  = 0.0;
+   } else if (-xi >= fabs(eta)) {
+      omega = eta/xi;
+      tau = 1.0 + omega*omega;
+      xf  = -sqrt(rhu/(1.0-1.0/sqrt(1.0+tau)));
+      yf  = (xf/15.0)*(astATand(omega) - astASind(omega/sqrt(tau+tau)));
+   } else if (xi >= fabs(eta)) {
+      omega = eta/xi;
+      tau = 1.0 + omega*omega;
+      xf  =  sqrt(rhu/(1.0-1.0/sqrt(1.0+tau)));
+      yf  = (xf/15.0)*(astATand(omega) - astASind(omega/sqrt(tau+tau)));
+   } else if (-eta > fabs(xi)) {
+      omega = xi/eta;
+      tau = 1.0 + omega*omega;
+      yf  = -sqrt(rhu/(1.0-1.0/sqrt(1.0+tau)));
+      xf  = (yf/15.0)*(astATand(omega) - astASind(omega/sqrt(tau+tau)));
+   } else if (eta > fabs(xi)) {
+      omega = xi/eta;
+      tau = 1.0 + omega*omega;
+      yf  =  sqrt(rhu/(1.0-1.0/sqrt(1.0+tau)));
+      xf  = (yf/15.0)*(astATand(omega) - astASind(omega/sqrt(tau+tau)));
+   }
+
+   if (fabs(xf) > 1.0) {
+      if (fabs(xf) > 1.0+tol) {
+         return 2;
+      }
+      xf = copysign(1.0,xf);
+   }
+   if (fabs(yf) > 1.0) {
+      if (fabs(yf) > 1.0+tol) {
+         return 2;
+      }
+      yf = copysign(1.0,yf);
+   }
+
+   *x = prj->w[0]*(xf + x0);
+   *y = prj->w[0]*(yf + y0);
+
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astQSCrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   int   direct, face;
+   double l, m, n, omega, rho, rhu, tau, xf, yf, w;
+   const double tol = 1.0e-12;
+
+   l = 0.0;
+   m = 0.0;
+   n = 0.0;
+
+   if (prj->flag != WCS__QSC) {
+      if (astQSCset(prj)) return 1;
+   }
+
+   xf = x*prj->w[1];
+   yf = y*prj->w[1];
+
+   /* Check bounds. */
+   if (fabs(xf) <= 1.0) {
+      if (fabs(yf) > 3.0) return 2;
+   } else {
+      if (fabs(xf) > 7.0) return 2;
+      if (fabs(yf) > 1.0) return 2;
+   }
+
+   /* Map negative faces to the other side. */
+   if (xf < -1.0) xf += 8.0;
+
+   /* Determine the face. */
+   if (xf > 5.0) {
+      face = 4;
+      xf = xf - 6.0;
+   } else if (xf > 3.0) {
+      face = 3;
+      xf = xf - 4.0;
+   } else if (xf > 1.0) {
+      face = 2;
+      xf = xf - 2.0;
+   } else if (yf > 1.0) {
+      face = 0;
+      yf = yf - 2.0;
+   } else if (yf < -1.0) {
+      face = 5;
+      yf = yf + 2.0;
+   } else {
+      face = 1;
+   }
+
+   direct = (fabs(xf) > fabs(yf));
+   if (direct) {
+      if (xf == 0.0) {
+         omega = 0.0;
+         tau = 1.0;
+         rho = 1.0;
+         rhu = 0.0;
+      } else {
+         w = 15.0*yf/xf;
+         omega = astSind(w)/(astCosd(w) - SQRT2INV);
+         tau = 1.0 + omega*omega;
+         rhu = xf*xf*(1.0 - 1.0/sqrt(1.0 + tau));
+         rho = 1.0 - rhu;
+      }
+   } else {
+      if (yf == 0.0) {
+         omega = 0.0;
+         tau = 1.0;
+         rho = 1.0;
+         rhu = 0.0;
+      } else {
+         w = 15.0*xf/yf;
+         omega = astSind(w)/(astCosd(w) - SQRT2INV);
+         tau = 1.0 + omega*omega;
+         rhu = yf*yf*(1.0 - 1.0/sqrt(1.0 + tau));
+         rho = 1.0 - rhu;
+      }
+   }
+
+   if (rho < -1.0) {
+      if (rho < -1.0-tol) {
+         return 2;
+      }
+
+      rho = -1.0;
+      rhu =  2.0;
+      w   =  0.0;
+   } else {
+      w = sqrt(rhu*(2.0-rhu)/tau);
+   }
+
+   if (face == 0) {
+      n = rho;
+      if (direct) {
+         m = w;
+         if (xf < 0.0) m = -m;
+         l = -m*omega;
+      } else {
+         l = w;
+         if (yf > 0.0) l = -l;
+         m = -l*omega;
+      }
+   } else if (face == 1) {
+      l = rho;
+      if (direct) {
+         m = w;
+         if (xf < 0.0) m = -m;
+         n = m*omega;
+      } else {
+         n = w;
+         if (yf < 0.0) n = -n;
+         m = n*omega;
+      }
+   } else if (face == 2) {
+      m = rho;
+      if (direct) {
+         l = w;
+         if (xf > 0.0) l = -l;
+         n = -l*omega;
+      } else {
+         n = w;
+         if (yf < 0.0) n = -n;
+         l = -n*omega;
+      }
+   } else if (face == 3) {
+      l = -rho;
+      if (direct) {
+         m = w;
+         if (xf > 0.0) m = -m;
+         n = -m*omega;
+      } else {
+         n = w;
+         if (yf < 0.0) n = -n;
+         m = -n*omega;
+      }
+   } else if (face == 4) {
+      m = -rho;
+      if (direct) {
+         l = w;
+         if (xf < 0.0) l = -l;
+         n = l*omega;
+      } else {
+         n = w;
+         if (yf < 0.0) n = -n;
+         l = n*omega;
+      }
+   } else if (face == 5) {
+      n = -rho;
+      if (direct) {
+         m = w;
+         if (xf < 0.0) m = -m;
+         l = m*omega;
+      } else {
+         l = w;
+         if (yf < 0.0) l = -l;
+         m = l*omega;
+      }
+   }
+
+   if (l == 0.0 && m == 0.0) {
+      *phi = 0.0;
+   } else {
+      *phi = astATan2d(m, l);
+   }
+   *theta = astASind(n);
+
+   return 0;
+}
+
+/*============================================================================
+*   HPX: HEALPix projection.
+*
+*   Given:
+*      prj->p[1]   H - the number of facets in longitude.
+*      prj->p[2]   K - the number of facets in latitude
+*
+*   Given and/or returned:
+*      prj->r0      Reset to 180/pi if 0.
+*      prj->phi0    Reset to 0.0
+*      prj->theta0  Reset to 0.0
+*
+*   Returned:
+*      prj->flag     HPX
+*      prj->code    "HPX"
+*      prj->n       True if K is odd.
+*      prj->w[0]    r0*(pi/180)
+*      prj->w[1]    (180/pi)/r0
+*      prj->w[2]    (K-1)/K
+*      prj->w[3]    90*K/H
+*      prj->w[4]    (K+1)/2
+*      prj->w[5]    90*(K-1)/H
+*      prj->w[6]    180/H
+*      prj->w[7]    H/360
+*      prj->w[8]    (90*K/H)*r0*(pi/180)
+*      prj->w[9]     (180/H)*r0*(pi/180)
+*      prj->astPRJfwd  Pointer to astHPXfwd().
+*      prj->astPRJrev  Pointer to astHPXrev().
+
+
+*===========================================================================*/
+
+int astHPXset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   strcpy(prj->code, "HPX");
+   prj->flag   = WCS__HPX;
+   prj->phi0   = 0.0;
+   prj->theta0 = 0.0;
+
+   prj->n = ((int)prj->p[2])%2;
+
+   if (prj->r0 == 0.0) {
+      prj->r0 = R2D;
+      prj->w[0] = 1.0;
+      prj->w[1] = 1.0;
+   } else {
+      prj->w[0] = prj->r0*D2R;
+      prj->w[1] = R2D/prj->r0;
+   }
+
+   prj->w[2] = (prj->p[2] - 1.0) / prj->p[2];
+   prj->w[3] = 90.0 * prj->p[2] / prj->p[1];
+   prj->w[4] = (prj->p[2] + 1.0) / 2.0;
+   prj->w[5] = 90.0 * (prj->p[2] - 1.0) / prj->p[1];
+   prj->w[6] = 180.0 / prj->p[1];
+   prj->w[7] = prj->p[1] / 360.0;
+   prj->w[8] = prj->w[3] * prj->w[0];
+   prj->w[9] = prj->w[6] * prj->w[0];
+
+   prj->astPRJfwd = astHPXfwd;
+   prj->astPRJrev = astHPXrev;
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astHPXfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+   double abssin, sigma, sinthe, phic;
+   int hodd;
+
+   if( prj->flag != WCS__HPX ) {
+      if( astHPXset( prj ) ) return 1;
+   }
+
+   sinthe = astSind( theta );
+   abssin = fabs( sinthe );
+
+/* Equatorial zone */
+   if( abssin <= prj->w[2] ) {
+      *x =  prj->w[0] * phi;
+      *y = prj->w[8] * sinthe;
+
+/* Polar zone */
+   } else {
+
+/* DSB - The expression for phic is conditioned differently to the
+   WCSLIB code in order to improve accuracy of the floor function for
+   arguments very slightly below an integer value. */
+      hodd =  ((int)prj->p[1]) % 2;
+      if( !prj->n && theta <= 0.0 ) hodd = 1 - hodd;
+      if( hodd ) {
+         phic = -180.0 + (2.0*floor( prj->w[7] * phi + 1/2 ) + prj->p[1] ) * prj->w[6];
+      } else {
+         phic = -180.0 + (2.0*floor( prj->w[7] * phi ) +  prj->p[1] + 1 ) * prj->w[6];
+      }
+
+      sigma = sqrt( prj->p[2]*( 1.0 - abssin ));
+
+      *x = prj->w[0] *( phic + ( phi - phic )*sigma );
+
+      *y = prj->w[9] * ( prj->w[4] - sigma );
+      if( theta < 0 ) *y = -*y;
+
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astHPXrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+   double absy, sigma, t, yr, xc;
+   int hodd;
+
+   if (prj->flag != WCS__HPX) {
+      if (astHPXset(prj)) return 1;
+   }
+
+   yr = prj->w[1]*y;
+   absy = fabs( yr );
+
+/* Equatorial zone */
+   if( absy <= prj->w[5] ) {
+      *phi = prj->w[1] * x;
+      t = yr/prj->w[3];
+      if( t < -1.0 || t > 1.0 ) {
+         return 2;
+      } else {
+         *theta = astASind( t );
+      }
+
+/* Polar zone */
+   } else if( absy <= 90 ){
+
+
+/* DSB - The expression for xc is conditioned differently to the
+   WCSLIB code in order to improve accuracy of the floor function for
+   arguments very slightly below an integer value. */
+      hodd =  ((int)prj->p[1]) % 2;
+      if( !prj->n && yr <= 0.0 ) hodd = 1 - hodd;
+      if( hodd ) {
+         xc = -180.0 + (2.0*floor( prj->w[7] * x + 1/2 ) + prj->p[1] ) * prj->w[6];
+      } else {
+         xc = -180.0 + (2.0*floor( prj->w[7] * x ) +  prj->p[1] + 1 ) * prj->w[6];
+      }
+
+      sigma = prj->w[4] - absy / prj->w[6];
+
+      if( sigma == 0.0 ) {
+         return 2;
+      } else {
+
+         t = ( x - xc )/sigma;
+         if( fabs( t ) <= prj->w[6] ) {
+            *phi = prj->w[1] *( xc + t );
+         } else {
+            return 2;
+         }
+      }
+
+      t = 1.0 - sigma*sigma/prj->p[2];
+      if( t < -1.0 || t > 1.0 ) {
+         return 2;
+      } else {
+         *theta = astASind( t );
+         if( y < 0 ) *theta = -*theta;
+      }
+
+   } else {
+      return 2;
+   }
+
+   return 0;
+}
+
+/*============================================================================
+*   XPH: HEALPix polar, aka "butterfly" projection.
+*
+*   Given and/or returned:
+*      prj->r0      Reset to 180/pi if 0.
+*      prj->phi0    Reset to 0.0 if undefined.
+*      prj->theta0  Reset to 0.0 if undefined.
+*
+*   Returned:
+*      prj->flag     XPH
+*      prj->code    "XPH"
+*      prj->w[0]    r0*(pi/180)/sqrt(2)
+*      prj->w[1]    (180/pi)/r0/sqrt(2)
+*      prj->w[2]    2/3
+*      prj->w[3]    tol (= 1e-4)
+*      prj->w[4]    sqrt(2/3)*(180/pi)
+*      prj->w[5]    90 - tol*sqrt(2/3)*(180/pi)
+*      prj->w[6]    sqrt(3/2)*(pi/180)
+*      prj->astPRJfwd Pointer to astXPHfwd().
+*      prj->astPRJrev Pointer to astXPHrev().
+*===========================================================================*/
+
+int astXPHset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+  strcpy(prj->code, "XPH");
+  prj->flag = WCS__XPH;
+
+  if (prj->r0 == 0.0) {
+    prj->r0 = R2D;
+    prj->w[0] = 1.0;
+    prj->w[1] = 1.0;
+  } else {
+    prj->w[0] = prj->r0*D2R;
+    prj->w[1] = R2D/prj->r0;
+  }
+
+  prj->w[0] /= sqrt(2.0);
+  prj->w[1] /= sqrt(2.0);
+  prj->w[2]  = 2.0/3.0;
+  prj->w[3]  = 1e-4;
+  prj->w[4]  = sqrt(prj->w[2])*R2D;
+  prj->w[5]  = 90.0 - prj->w[3]*prj->w[4];
+  prj->w[6]  = sqrt(1.5)*D2R;
+
+  prj->astPRJfwd = astXPHfwd;
+  prj->astPRJrev = astXPHrev;
+
+  return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astXPHfwd(phi, theta, prj, x, y)
+
+const double phi, theta;
+struct AstPrjPrm *prj;
+double *x, *y;
+
+{
+  double abssin, chi, eta, psi, sigma, sinthe, xi;
+
+  if (prj->flag != WCS__XPH) {
+    if (astXPHset(prj)) return 1;
+  }
+
+  /* Do phi dependence. */
+  chi = phi;
+  if (180.0 <= fabs(chi)) {
+    chi = fmod(chi, 360.0);
+    if (chi < -180.0) {
+      chi += 360.0;
+    } else if (180.0 <= chi) {
+      chi -= 360.0;
+    }
+  }
+
+  /* phi is also recomputed from chi to avoid rounding problems. */
+  chi += 180.0;
+  psi = fmod(chi, 90.0);
+
+  /* y is used to hold phi (rounded). */
+  *x = psi;
+  *y = chi - 180.0;
+
+  /* Do theta dependence. */
+  sinthe = astSind(theta);
+  abssin = fabs(sinthe);
+
+  if (abssin <= prj->w[2]) {
+    /* Equatorial regime. */
+    xi  = *x;
+    eta = 67.5 * sinthe;
+
+  } else {
+    /* Polar regime. */
+    if (theta < prj->w[5]) {
+      sigma = sqrt(3.0*(1.0 - abssin));
+    } else {
+      sigma = (90.0 - theta)*prj->w[6];
+    }
+
+    xi  = 45.0 + (*x - 45.0)*sigma;
+    eta = 45.0 * (2.0 - sigma);
+    if (theta < 0.0) eta = -eta;
+  }
+
+  xi  -= 45.0;
+  eta -= 90.0;
+
+  /* Recall that y holds phi. */
+  if (*y < -90.0) {
+    *x = prj->w[0]*(-xi + eta);
+    *y = prj->w[0]*(-xi - eta);
+
+  } else if (*y <  0.0) {
+    *x = prj->w[0]*(+xi + eta);
+    *y = prj->w[0]*(-xi + eta);
+
+  } else if (*y < 90.0) {
+    *x = prj->w[0]*( xi - eta);
+    *y = prj->w[0]*( xi + eta);
+
+  } else {
+    *x = prj->w[0]*(-xi - eta);
+    *y = prj->w[0]*( xi - eta);
+  }
+
+  return 0;
+
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astXPHrev(x, y, prj, phi, theta)
+
+const double x, y;
+struct AstPrjPrm *prj;
+double *phi, *theta;
+
+{
+  double abseta, eta, eta1, sigma, xi, xi1, xr, yr;
+  const double tol = 1.0e-12;
+
+  if (prj->flag != WCS__XPH) {
+     if (astXPHset(prj)) return 1;
+  }
+
+
+  xr = x*prj->w[1];
+  yr = y*prj->w[1];
+  if (xr <= 0.0 && 0.0 < yr) {
+    xi1  = -xr - yr;
+    eta1 =  xr - yr;
+    *phi = -180.0;
+  } else if (xr < 0.0 && yr <= 0.0) {
+    xi1  =  xr - yr;
+    eta1 =  xr + yr;
+    *phi = -90.0;
+  } else if (0.0 <= xr && yr < 0.0) {
+    xi1  =  xr + yr;
+    eta1 = -xr + yr;
+    *phi = 0.0;
+  } else {
+    xi1  = -xr + yr;
+    eta1 = -xr - yr;
+    *phi = 90.0;
+  }
+
+  xi  = xi1  + 45.0;
+  eta = eta1 + 90.0;
+  abseta = fabs(eta);
+
+  if (abseta <= 90.0) {
+    if (abseta <= 45.0) {
+      /* Equatorial regime. */
+      *phi  += xi;
+      *theta = astASind(eta/67.5);
+
+      /* Bounds checking. */
+      if (45.0+tol < fabs(xi1)) return 2;
+
+    } else {
+      /* Polar regime. */
+      sigma = (90.0 - abseta) / 45.0;
+
+      /* Ensure an exact result for points on the boundary. */
+      if (xr == 0.0) {
+        if (yr <= 0.0) {
+          *phi = 0.0;
+        } else {
+          *phi = 180.0;
+        }
+      } else if (yr == 0.0) {
+        if (xr < 0.0) {
+          *phi = -90.0;
+        } else {
+          *phi =  90.0;
+        }
+      } else {
+        *phi += 45.0 + xi1/sigma;
+      }
+
+      if (sigma < prj->w[3]) {
+        *theta = 90.0 - sigma*prj->w[4];
+      } else {
+        *theta = astASind(1.0 - sigma*sigma/3.0);
+      }
+      if (eta < 0.0) *theta = -(*theta);
+
+      /* Bounds checking. */
+      if (eta < -45.0 && eta+90.0+tol < fabs(xi1)) return 2;
+    }
+
+  } else {
+    /* Beyond latitude range. */
+    *phi   = 0.0;
+    *theta = 0.0;
+    return 2;
+  }
+
+  return 0;
+}
+
+
diff --git a/ast/proj.h b/ast/proj.h
new file mode 100644
index 0000000..61e4746
--- /dev/null
+++ b/ast/proj.h
@@ -0,0 +1,181 @@
+/*=============================================================================
+*
+*   WCSLIB - an implementation of the FITS WCS proposal.
+*   Copyright (C) 1995-2002, Mark Calabretta
+*
+*   This library is free software; you can redistribute it and/or modify it
+*   under the terms of the GNU Library General Public License as published
+*   by the Free Software Foundation; either version 2 of the License, or (at
+*   your option) any later version.
+*
+*   This library is distributed in the hope that it will be useful, but
+*   WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library
+*   General Public License for more details.
+*
+*   You should have received a copy of the GNU Library General Public License
+*   along with this library; if not, write to the Free Software Foundation,
+*   Inc., 51 Franklin Street,Fifth Floor, Boston, MA 02110-1301, USA
+*
+*   Correspondence concerning WCSLIB may be directed to:
+*      Internet email: [email-masked]
+*      Postal address: Dr. Mark Calabretta,
+*                      Australia Telescope National Facility,
+*                      P.O. Box 76,
+*                      Epping, NSW, 2121,
+*                      AUSTRALIA
+*
+*   Author: Mark Calabretta, Australia Telescope National Facility
+*   $Id$
+*=============================================================================
+*
+*  This version of proj.h is based on the version in wcslib-2.9, but has
+*  been modified in the following ways by the Starlink project (e-mail:
+*  [email-masked]):
+*     -  Support for non-ANSI C prototypes removed
+*     -  Changed the name of the WCSLIB_PROJ macro to WCSLIB_PROJ_INCLUDED
+*     -  Changed names of all functions and structures to avoid name
+*        clashes with wcslib.
+*     -  Change the maximum number of projection parameters to 100.
+*     -  Added definition of macro WCSLIB_MXPAR, and use it to define
+*        size of projection parameter array within AstPrjPrm structure.
+*     -  Added component "p2" to the AstPrjPrm structure to hold projection
+*        parameters associated with the longitude axis (for use within
+*        the tpn.c file which holds an implementation of the old "TAN with
+*        correction terms" projection).
+*     -  Added prototypes for TPN projection functions (defined in file
+*        tpn.c).
+*     -  Added prototypes for HPX projection functions.
+*     -  Added prototypes for XPH projection functions.
+*===========================================================================*/
+
+#ifndef WCSLIB_PROJ_INCLUDED
+#define WCSLIB_PROJ_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define WCSLIB_MXPAR 100
+
+extern int npcode;
+extern char pcodes[26][4];
+
+struct AstPrjPrm {
+   char   code[4];
+   int    flag;
+   double phi0, theta0;
+   double r0;
+   double *p;
+   double *p2;
+   double w[20];
+   int    n;
+   int (*astPRJfwd)(const double, const double,
+                 struct AstPrjPrm *,
+                 double *, double *);
+   int (*astPRJrev)(const double, const double,
+                 struct AstPrjPrm *,
+                 double *, double *);
+};
+
+   int astPRJset(const char [], struct AstPrjPrm *);
+   int astPRJfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astPRJrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astAZPset(struct AstPrjPrm *);
+   int astAZPfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astAZPrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astSZPset(struct AstPrjPrm *);
+   int astSZPfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astSZPrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astTANset(struct AstPrjPrm *);
+   int astTANfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astTANrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astSTGset(struct AstPrjPrm *);
+   int astSTGfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astSTGrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astSINset(struct AstPrjPrm *);
+   int astSINfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astSINrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astARCset(struct AstPrjPrm *);
+   int astARCfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astARCrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astZPNset(struct AstPrjPrm *);
+   int astZPNfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astZPNrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astZEAset(struct AstPrjPrm *);
+   int astZEAfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astZEArev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astAIRset(struct AstPrjPrm *);
+   int astAIRfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astAIRrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCYPset(struct AstPrjPrm *);
+   int astCYPfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCYPrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCEAset(struct AstPrjPrm *);
+   int astCEAfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCEArev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCARset(struct AstPrjPrm *);
+   int astCARfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCARrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astMERset(struct AstPrjPrm *);
+   int astMERfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astMERrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astSFLset(struct AstPrjPrm *);
+   int astSFLfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astSFLrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astPARset(struct AstPrjPrm *);
+   int astPARfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astPARrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astMOLset(struct AstPrjPrm *);
+   int astMOLfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astMOLrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astAITset(struct AstPrjPrm *);
+   int astAITfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astAITrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCOPset(struct AstPrjPrm *);
+   int astCOPfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCOPrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCOEset(struct AstPrjPrm *);
+   int astCOEfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCOErev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCODset(struct AstPrjPrm *);
+   int astCODfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCODrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCOOset(struct AstPrjPrm *);
+   int astCOOfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCOOrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astBONset(struct AstPrjPrm *);
+   int astBONfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astBONrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astPCOset(struct AstPrjPrm *);
+   int astPCOfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astPCOrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astTSCset(struct AstPrjPrm *);
+   int astTSCfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astTSCrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCSCset(struct AstPrjPrm *);
+   int astCSCfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astCSCrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astQSCset(struct AstPrjPrm *);
+   int astQSCfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astQSCrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astHPXset(struct AstPrjPrm *);
+   int astHPXfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astHPXrev(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astXPHset(struct AstPrjPrm *);
+   int astXPHfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astXPHrev(const double, const double, struct AstPrjPrm *, double *, double *);
+
+   int astTPNset(struct AstPrjPrm *);
+   int astTPNfwd(const double, const double, struct AstPrjPrm *, double *, double *);
+   int astTPNrev(const double, const double, struct AstPrjPrm *, double *, double *);
+
+extern const char *astPRJset_errmsg[];
+extern const char *astPRJfwd_errmsg[];
+extern const char *astPRJrev_errmsg[];
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* WCSLIB_PROJ_INCLUDED */
diff --git a/ast/ratemap.c b/ast/ratemap.c
new file mode 100644
index 0000000..1e343a6
--- /dev/null
+++ b/ast/ratemap.c
@@ -0,0 +1,2011 @@
+/*
+*class++
+*  Name:
+*     RateMap
+
+*  Purpose:
+*     Mapping which represents differentiation.
+
+*  Constructor Function:
+c     astRateMap
+f     AST_RATEMAP
+
+*  Description:
+*     A RateMap is a Mapping which represents a single element of the
+*     Jacobian matrix of another Mapping. The Mapping for which the
+*     Jacobian is required is specified when the new RateMap is created,
+*     and is referred to as the "encapsulated Mapping" below.
+*
+*     The number of inputs to a RateMap is the same as the number of inputs
+*     to its encapsulated Mapping. The number of outputs from a RateMap
+*     is always one. This one output equals the rate of change of a
+*     specified output of the encapsulated Mapping with respect to a
+*     specified input of the encapsulated Mapping (the input and output
+*     to use are specified when the RateMap is created).
+*
+*     A RateMap which has not been inverted does not define an inverse
+*     transformation. If a RateMap has been inverted then it will define
+*     an inverse transformation but not a forward transformation.
+
+*  Inheritance:
+*     The RateMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The RateMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     The RateMap class does not define any new functions beyond those
+f     The RateMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     10-FEB-2004 (DSB):
+*        Original version.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS RateMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "ratemap.h"             /* Interface definition for this class */
+#include "unitmap.h"             /* Unit Mappings */
+#include "frame.h"               /* Frames */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int *(* parent_mapsplit)( AstMapping *, int, const int *, AstMapping **, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(RateMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(RateMap,Class_Init)
+#define class_vtab astGLOBAL(RateMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstRateMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstRateMap *astRateMapId_( void *, int, int, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *RemoveRegions( AstMapping *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetObjSize( AstObject *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two RateMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ratemap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     RateMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two RateMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a RateMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the RateMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstRateMap *that;
+   AstRateMap *this;
+   int nin;
+   int nout;
+   int result;
+   int that_inv;
+   int this_inv;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two RateMap structures. */
+   this = (AstRateMap *) this_object;
+   that = (AstRateMap *) that_object;
+
+/* Check the second object is a RateMap. We know the first is a
+   RateMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsARateMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two RateMaps differ, it may still be possible
+   for them to be equivalent. First compare the RateMaps if their Invert
+   flags are the same. In this case all the attributes of the two RateMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+
+/* Temporarily re-instate the original Invert flag values. */
+            this_inv = astGetInvert( this->map );
+            that_inv = astGetInvert( that->map );
+            astSetInvert( this->map, this->invert );
+            astSetInvert( that->map, that->invert );
+
+            if( astEqual( this->map, that->map ) &&
+                this->iin == that->iin &&
+                this->iout == that->iout ){
+               result = 1;
+            }
+
+/* Restore the original Invert flag values. */
+            astSetInvert( this->map, this_inv );
+            astSetInvert( that->map, that_inv );
+
+/* If the Invert flags for the two RateMaps differ, the attributes of the two
+   RateMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a RateMap, Invert flags must be equal. */
+            result = 0;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ratemap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     RateMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied RateMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the RateMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstRateMap *this;         /* Pointer to RateMap structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the RateMap structure. */
+   this = (AstRateMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astGetObjSize( this->map );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+void astInitRateMapVtab_(  AstRateMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitRateMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a RateMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "ratemap.h"
+*     void astInitRateMapVtab( AstRateMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     RateMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the RateMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsARateMap) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+/* None. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   mapping->RemoveRegions = RemoveRegions;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_mapsplit = mapping->MapSplit;
+   mapping->MapSplit = MapSplit;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "RateMap", "Differential Mapping" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     RateMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstRateMap *this;       /* Pointer to RateMap structure */
+   int result;             /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the RateMap structure. */
+   this = (AstRateMap *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->map, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a RateMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     RateMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated RateMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated RateMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated RateMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the RateMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        RateMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated RateMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *emap1;
+   AstMapping *emap2;
+   AstMapping *emap;
+   AstMapping *smap;
+   AstRateMap *map;
+   AstRateMap *rmap1;
+   AstRateMap *rmap2;
+   int cancel;
+   int map_inv;
+   int nax;
+   int old_inv2;
+   int old_inv;
+   int old_winv;
+   int result;
+
+/* Initialise. */
+   result = -1;
+
+/* Check the inherited status. */
+   if ( !astOK ) return result;
+
+/* Initialisation to avoid compiler warnings. */
+   nax = 0;
+
+/* Get a pointer to this RateMap. */
+   map = (AstRateMap *) this;
+
+/* Temporarily set its Invert flag to the requested value. */
+   map_inv = astGetInvert( map );
+   astSetInvert( map, ( *invert_list )[ where ] );
+
+/* Get the encapsulated Mapping, and temporarily set its Invert attribute
+   back to the value it had when the RateMap was created, saving the current
+   Invert value so that it can be re-instated later. */
+   emap = map->map;
+   old_inv = astGetInvert( emap );
+   astSetInvert( emap, map->invert );
+
+/* First try to simplify the RateMap by simplifying its encapsulated
+   Mapping. */
+   smap = astSimplify( emap );
+
+/* If any simplification took place, create a new RateMap with the
+   simplified mapping. */
+   if( smap != emap ) {
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = (AstMapping *) astRateMap( smap, map->iout, map->iin, "", status );
+      result = where;
+
+/* The only other simplication which can be performed is to cancel a RateMap
+   with its own inverse in series. */
+   } else if( series ) {
+
+/* Indicate we have nothing to cancel with as yet. */
+      cancel = -1;
+
+/* First consider the lower neighbour. */
+      if( where > 0 && astIsARateMap( ( *map_list )[ where - 1 ] ) ) {
+
+/* Check the Invert flags are opposite */
+         if( ( *invert_list )[ where ] != ( *invert_list )[ where - 1 ] ) {
+            rmap1 = map;
+            rmap2 = (AstRateMap *) ( *map_list )[ where - 1 ];
+
+/* Check the input and output indices are equal. */
+            if( rmap1->iin == rmap2->iin &&
+                rmap1->iout == rmap2->iout ) {
+
+/* Check the encapsulated Mappings are equal. */
+               emap1 = emap;
+               emap2 = rmap2->map;
+               old_winv = astGetInvert( rmap2 );
+               astSetInvert( rmap2, ( *invert_list )[ where - 1 ] );
+               old_inv2 = astGetInvert( emap2 );
+               astSetInvert( emap2,  rmap2->invert );
+
+               if( astEqual( emap1, emap2 ) ) cancel = where - 1;
+
+               astSetInvert( emap2,  old_inv2 );
+               astSetInvert( rmap2, old_winv );
+
+               nax = astGetNout( rmap1 );
+            }
+         }
+      }
+
+/* Likewise consider the upper neighbour. */
+      if( cancel == -1 && where + 1 < *nmap &&
+          astIsARateMap( ( *map_list )[ where + 1 ] ) ) {
+
+         if( ( *invert_list )[ where ] != ( *invert_list )[ where + 1 ] ) {
+            rmap1 = map;
+            rmap2 = (AstRateMap *) ( *map_list )[ where + 1 ];
+            if( rmap1->iin == rmap2->iin &&
+                rmap1->iout == rmap2->iout ) {
+               emap1 = emap;
+               emap2 = rmap2->map;
+               old_winv = astGetInvert( rmap2 );
+               astSetInvert( rmap2, ( *invert_list )[ where + 1 ] );
+               old_inv2 = astGetInvert( emap2 );
+               astSetInvert( emap2,  rmap2->invert );
+
+               if( astEqual( emap1, emap2 ) ) cancel = where + 1;
+
+               astSetInvert( emap2,  old_inv2 );
+               astSetInvert( rmap2, old_winv );
+
+               nax = astGetNin( rmap1 );
+            }
+         }
+      }
+
+/* If we can cancel with a neightbour, do so. */
+      if( cancel != -1 ) {
+         (void) astAnnul( ( *map_list )[ where ] );
+         (void) astAnnul( ( *map_list )[ cancel ] );
+         ( *map_list )[ where ] = (AstMapping *) astUnitMap( nax, "", status );
+         ( *invert_list )[ where ] = 0;
+         ( *map_list )[ cancel ] = (AstMapping *) astUnitMap( nax, "", status );
+         ( *invert_list )[ cancel ] = 0;
+          result = ( cancel < where ) ? cancel : where;
+      }
+   }
+
+/* Free resources. */
+   smap = astAnnul( smap );
+
+/* Reset the original Invert attribute for the encapsulated Mapping. */
+   astSetInvert( emap, old_inv );
+
+/* Reset the original Invert attribute for the specified RateMap */
+   astSetInvert( map, map_inv );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     RateMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ratemap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     RateMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing RateMap. This is only possible if the specified inputs
+*     correspond to some subset of the RateMap outputs. That is, there
+*     must exist a subset of the RateMap outputs for which each output
+*     depends only on the selected RateMap inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied RateMap, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the RateMap to be split (the RateMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied RateMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied RateMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied RateMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstMapping *emap;          /* Pointer to Mapping encapsulated by RateMap */
+   AstMapping *remap;         /* Split Mapping encapsulated by RateMap */
+   AstRateMap *this;          /* Pointer to RateMap structure */
+   int *eres;                 /* Outputs used by split Mapping */
+   int *result;               /* Array holding returned output inedx */
+   int ax1;                   /* New index of output being differentiated */
+   int ax2;                   /* New index of output being varied */
+   int i;                     /* Loop count */
+   int nout;                  /* No. of outputs in the split Mapping */
+   int old_inv;               /* Original Invert flag for emap */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the parent astMapSplit method to see if it can do the job. */
+   result = (*parent_mapsplit)( this_map, nin, in, map, status );
+
+/* If not, we provide a special implementation here. Note we cannot
+   produce the Mapping if the RaterMap has been inverted. */
+   if( !result && !astGetInvert( this_map ) ) {
+
+/* Get a pointer to the RateMap structure. */
+      this = (AstRateMap *) this_map;
+
+/* Temporarily reset the Invert attribute of the encapsulated Mapping
+   back to the value it had when the RateMap was created. */
+      emap = this->map;
+      old_inv = astGetInvert( emap );
+      astSetInvert( emap, this->invert );
+
+/* Attempt to split the encapsulated Mapping */
+      eres = astMapSplit( emap, nin, in, &remap );
+
+/* We can only continue if this was succesful. */
+      if( eres ) {
+
+/* Check that the input which the RateMap varies is one of the selected
+   inputs. */
+         ax2 = -1;
+         for( i = 0; i < nin; i++ ) {
+            if( in[ i ] == this->iin ) {
+               ax2 = i;
+               break;
+            }
+         }
+
+/* Check that the output which the RateMap differentiates is one of the
+   outputs of the "remap" Mapping. */
+         ax1 = -1;
+         nout = astGetNout( remap );
+         for( i = 0; i < nout; i++ ) {
+            if( eres[ i ] == this->iout ) {
+               ax1 = i;
+               break;
+            }
+         }
+
+/* If possible create the required Mapping and returned array. */
+         if( ax1 != -1 && ax2 != -1 ) {
+            *map = (AstMapping *) astRateMap( remap, ax1, ax2, "", status );
+            result = astMalloc( sizeof( int ) );
+            if( astOK ) *result= 0;
+         }
+
+/* Free resources */
+         eres = astFree( eres );
+         remap = astAnnul( remap );
+      }
+
+/* Re-instate the original Invert flag in the Mapping encapsulated by the
+   supplied RateMap. */
+      astSetInvert( emap, old_inv );
+   }
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static AstMapping *RemoveRegions( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     RemoveRegions
+
+*  Purpose:
+*     Remove any Regions from a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ratemap.h"
+*     AstMapping *RemoveRegions( AstMapping *this, int *status )
+
+*  Class Membership:
+*     RateMap method (over-rides the astRemoveRegions method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function searches the supplied Mapping (which may be a
+*     compound Mapping such as a CmpMap) for any component Mappings
+*     that are instances of the AST Region class. It then creates a new
+*     Mapping from which all Regions have been removed. If a Region
+*     cannot simply be removed (for instance, if it is a component of a
+*     parallel CmpMap), then it is replaced with an equivalent UnitMap
+*     in the returned Mapping.
+*
+*     The implementation provided by the RateMap class invokes the
+*     astRemoveRegions method on the encapsulated Mapping, and returns a
+*     new RateMap containing the resulting Mapping.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the modified mapping.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *newmap;           /* New component Mapping */
+   AstMapping *result;           /* Result pointer to return */
+   AstRateMap *new;              /* Pointer to new RateMap */
+   AstRateMap *this;             /* Pointer to RateMap structure */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the RateMap. */
+   this = (AstRateMap *) this_mapping;
+
+/* Invoke the astRemoveRegions method on the component Mapping. */
+   newmap = astRemoveRegions( this->map );
+
+/* If the Mapping was not modified, just return a clone of the supplied
+   pointer. */
+   if( this->map == newmap ) {
+      result = astClone( this );
+
+/* Otherwise, we need to create a new RateMap to return. */
+   } else {
+
+/* If the new Mapping is a Frame (as will be the case if the original
+   Mapping was a Region), use a UnitMap instead. */
+      if( astIsAFrame( newmap ) ) {
+         (void) astAnnul( newmap );
+         newmap = (AstMapping *) astUnitMap( astGetNin( this ), " ", status );
+      }
+
+/* Take a deep copy of the supplied RateMap and then modify the Mapping
+   so that we retain any extra information in the supplied RateMap. */
+      new = astCopy( this );
+      (void) astAnnul( new->map );
+      new->map = astClone( newmap );
+      result = (AstMapping *) new;
+   }
+
+/* Free resources. */
+   newmap = astAnnul( newmap );
+
+/* Annul the returned Mapping if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a RateMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "ratemap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     RateMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a RateMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required Mapping.
+*     This implies applying each of the RateMap's component Mappings in turn,
+*     either in series or in parallel.
+
+*  Parameters:
+*     this
+*        Pointer to the RateMap.
+*     in
+*        Pointer to the PointSet associated with the input coordinate values.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the RateMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstMapping *emap;
+   AstPointSet *result;
+   AstRateMap *map;
+   double **ptr2;
+   double **ptr;
+   double *pout;
+   double *work;
+   int ic;
+   int iin;
+   int iout;
+   int ipoint;
+   int ncoord;
+   int npoint;
+   int old_inv;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the RateMap. */
+   map = (AstRateMap *) this;
+
+/* Apply the parent Mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We now extend the parent astTransform method by applying the component
+   Mappings of the RateMap to generate the output coordinate values. */
+
+/* Determine whether to apply the forward or inverse Mapping, according to the
+   direction specified and whether the Mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* The RateMap class does not have an inverse transformation. */
+   if( !forward ) {
+      astError( AST__INTER, "astTransform(%s): The %s class does not have "
+                "an inverse transformation (AST internal programming error).", status,
+                astGetClass( this ), astGetClass( this ) );
+
+/* Otherwise use the astRate method on the encapsulated Maping to
+   determine the required rate of change at each supplied input point. */
+   } else {
+
+/* Temporarily reset the Invert attribute of the encapsulated Mapping
+   back to the value it had when the RateMap was created. */
+      emap = map->map;
+      old_inv = astGetInvert( emap );
+      astSetInvert( emap, map->invert );
+
+/* Note the indices of the input and output to use. */
+      iin = map->iin;
+      iout = map->iout;
+
+/* Get pointers to the axis values in the supplied PointSet. */
+      ptr = astGetPoints( in );
+      ncoord = astGetNcoord( in );
+      npoint = astGetNpoint( in );
+
+/* Work space to hold an input position. */
+      work = astMalloc( sizeof( double )*(size_t) ncoord );
+
+/* Get a pointer to the axis values in the results PointSet. */
+      ptr2 = astGetPoints( result );
+      pout = ptr2[ 0 ];
+      if( astOK ) {
+
+/* Loop round each point in the supplied PointSet. */
+         for( ipoint = 0; ipoint < npoint; ipoint++ ) {
+
+/* Copy this point into the work array. */
+            for( ic = 0; ic < ncoord; ic++ ) work[ ic ] = ptr[ ic ][ ipoint ];
+
+/* Find the rate of change of the specified output of the encapsulated
+   Mapping with respect to the specified input. */
+            *(pout++) = astRate( emap, work, iout, iin );
+         }
+      }
+
+/* Re-instate the original Invert flag. */
+      astSetInvert( emap, old_inv );
+
+/* Free resources */
+      work = astFree( work );
+
+   }
+
+/* If an error occurred, clean up by deleting the output PointSet (if
+   allocated by this function) and setting a NULL result pointer. */
+   if ( !astOK ) {
+      if ( !out ) result = astDelete( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for RateMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for RateMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the component
+*     Mappings within the RateMap.
+*/
+
+/* Local Variables: */
+   AstRateMap *in;                /* Pointer to input RateMap */
+   AstRateMap *out;               /* Pointer to output RateMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output RateMaps. */
+   in = (AstRateMap *) objin;
+   out = (AstRateMap *) objout;
+
+/* For safety, start by clearing any references to the input component
+   Mappings from the output RateMap. */
+   out->map = NULL;
+
+/* Make copies of these Mappings and store pointers to them in the output
+   RateMap structure. */
+   out->map = astCopy( in->map );
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for RateMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for RateMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstRateMap *this;              /* Pointer to RateMap */
+
+/* Obtain a pointer to the RateMap structure. */
+   this = (AstRateMap *) obj;
+
+/* Annul the pointers to the component Mappings. */
+   this->map = astAnnul( this->map );
+
+/* Clear the remaining RateMap variables. */
+   this->invert = 0;
+   this->iin = 0;
+   this->iout = 0;
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for RateMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the RateMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the RateMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstRateMap *this;              /* Pointer to the RateMap structure */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the RateMap structure. */
+   this = (AstRateMap *) this_object;
+
+/* Write out values representing the instance variables for the RateMap
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Input axis. */
+/* ------------ */
+   ival = this->iin;
+   set = ( ival != 0 );
+   astWriteInt( channel, "IIn", set, 0, ival, "Index of Mapping input" );
+
+/* Output axis. */
+/* ------------ */
+   ival = this->iout;
+   set = ( ival != 0 );
+   astWriteInt( channel, "IOut", set, 0, ival, "Index of Mapping output" );
+
+/* Invert flag. */
+/* ------------ */
+   ival = this->invert;
+   set = ( ival != 0 );
+   astWriteInt( channel, "Inv", set, 0, ival,
+                ival ? "Mapping used in inverse direction" :
+                       "Mapping used in forward direction" );
+
+/* Mapping. */
+/* -------- */
+   astWriteObject( channel, "Map", 1, 1, this->map,
+                   "Mapping to be differentiated" );
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsARateMap and astCheckRateMap functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(RateMap,Mapping)
+astMAKE_CHECK(RateMap)
+
+AstRateMap *astRateMap_( void *map_void, int ax1, int ax2, const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astRateMap
+
+*  Purpose:
+*     Create a RateMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "ratemap.h"
+*     AstRateMap *astRateMap( AstMapping *map, int ax1, int ax2, const char *options, int *status, ... )
+
+*  Class Membership:
+*     RateMap constructor.
+
+*  Description:
+*     This function creates a new RateMap and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     map
+*        Pointer to the Mapping to differentiate.
+*     ax1
+*        zero-based index of the "map" output which is to be differentiated.
+*     ax2
+*        Zero-based index of the "map" input which is to be varied.
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new RateMap. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new RateMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic RateMap constructor which is
+*     available via the protected interface to the RateMap class.  A
+*     public interface is provided by the astRateMapId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "map" parameter is of type (void *) and is converted and validated
+*     within the function itself.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstRateMap *new;              /* Pointer to new RateMap */
+   AstMapping *map;              /* Pointer to Mapping structure */
+   va_list args;                 /* Variable argument list */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain and validate pointers to the Mapping structures provided. */
+   map = astCheckMapping( map_void );
+   if ( astOK ) {
+
+/* Initialise the RateMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitRateMap( NULL, sizeof( AstRateMap ), !class_init, &class_vtab,
+                           "RateMap", map, ax1, ax2 );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new RateMap's
+   attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new RateMap. */
+   return new;
+}
+
+AstRateMap *astRateMapId_( void *map_void, int ax1, int ax2,
+                           const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astRateMap
+f     AST_RATEMAP
+
+*  Purpose:
+*     Create a RateMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "ratemap.h"
+c     AstRateMap *astRateMap( AstMapping *map, int ax1, int ax2,
+c                             const char *options, ... )
+f     RESULT = AST_RATEMAP( MAP, AX1, AX2, OPTIONS, STATUS )
+
+*  Class Membership:
+*     RateMap constructor.
+
+*  Description:
+*     This function creates a new RateMap and optionally initialises
+*     its attributes.
+*
+*     A RateMap is a Mapping which represents a single element of the
+*     Jacobian matrix of another Mapping. The Mapping for which the
+*     Jacobian is required is specified when the new RateMap is created,
+*     and is referred to as the "encapsulated Mapping" below.
+*
+*     The number of inputs to a RateMap is the same as the number of inputs
+*     to its encapsulated Mapping. The number of outputs from a RateMap
+*     is always one. This one output equals the rate of change of a
+*     specified output of the encapsulated Mapping with respect to a
+*     specified input of the encapsulated Mapping (the input and output
+*     to use are specified when the RateMap is created).
+*
+*     A RateMap which has not been inverted does not define an inverse
+*     transformation. If a RateMap has been inverted then it will define
+*     an inverse transformation but not a forward transformation.
+
+*  Parameters:
+c     map
+f     MAP = INTEGER (Given)
+*        Pointer to the encapsulated Mapping.
+c     ax1
+f     AX1 = INTEGER (Given)
+*        Index of the output from the encapsulated Mapping for which the
+*        rate of change is required. This corresponds to the delta
+*        quantity forming the numerator of the required element of the
+*        Jacobian matrix. The first axis has index 1.
+c     ax2
+f     AX2 = INTEGER (Given)
+*        Index of the input to the encapsulated Mapping which is to be
+*        varied. This corresponds to the delta quantity forming the
+*        denominator of the required element of the Jacobian matrix.
+*        The first axis has index 1.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new RateMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new RateMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astRateMap()
+f     AST_RATEMAP = INTEGER
+*        A pointer to the new RateMap.
+
+*  Notes:
+*     - The forward transformation of the encapsulated Mapping must be
+*     defined.
+c     - Note that the component Mappings supplied are not copied by
+c     astRateMap (the new RateMap simply retains a reference to
+c     them). They may continue to be used for other purposes, but
+c     should not be deleted. If a RateMap containing a copy of its
+c     component Mappings is required, then a copy of the RateMap should
+c     be made using astCopy.
+f     - Note that the component Mappings supplied are not copied by
+f     AST_RATEMAP (the new RateMap simply retains a reference to
+f     them). They may continue to be used for other purposes, but
+f     should not be deleted. If a RateMap containing a copy of its
+f     component Mappings is required, then a copy of the RateMap should
+f     be made using AST_COPY.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astRateMap constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astRateMap_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - Because no checking or casting of arguments is performed
+*     before the function is invoked, the "map" parameter is of type
+*     (void *) and is converted from an ID value to a pointer and
+*     validated within the function itself.
+*     - The variable argument list also prevents this function from
+*     invoking astRateMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the conversions between IDs
+*     and pointers on input/output of Objects.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstRateMap *new;              /* Pointer to new RateMap */
+   AstMapping *map;              /* Pointer to Mapping structure */
+   va_list args;                 /* Variable argument list */
+
+/* Pointer to inherited status value */
+   int *status;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain the Mapping pointer from the ID supplied and validate the
+   pointer to ensure it identifies a valid Mapping. */
+   map = astVerifyMapping( astMakePointer( map_void ) );
+   if ( astOK ) {
+
+/* Initialise the RateMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitRateMap( NULL, sizeof( AstRateMap ), !class_init, &class_vtab,
+                           "RateMap", map, ax1 - 1, ax2 - 1 );
+
+/* If successful, note that the virtual function table has been initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new RateMap's
+   attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return an ID value for the new RateMap. */
+   return astMakeId( new );
+}
+
+AstRateMap *astInitRateMap_( void *mem, size_t size, int init,
+                           AstRateMapVtab *vtab, const char *name,
+                           AstMapping *map, int ax1, int ax2, int *status ) {
+/*
+*+
+*  Name:
+*     astInitRateMap
+
+*  Purpose:
+*     Initialise a RateMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "ratemap.h"
+*     AstRateMap *astInitRateMap( void *mem, size_t size, int init,
+*                                 AstRateMapVtab *vtab, const char *name,
+*                                 AstMapping *map, int ax1, int ax2 )
+
+*  Class Membership:
+*     RateMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new RateMap object. It allocates memory (if necessary) to
+*     accommodate the RateMap plus any additional data associated with the
+*     derived class. It then initialises a RateMap structure at the start
+*     of this memory. If the "init" flag is set, it also initialises the
+*     contents of a virtual function table for a RateMap at the start of
+*     the memory passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the RateMap is to be initialised.
+*        This must be of sufficient size to accommodate the RateMap data
+*        (sizeof(RateMap)) plus any data used by the derived class. If a
+*        value of NULL is given, this function will allocate the memory itself
+*        using the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the RateMap (plus derived class
+*        data). This will be used to allocate memory if a value of NULL is
+*        given for the "mem" parameter. This value is also stored in the
+*        RateMap structure, so a valid value must be supplied even if not
+*        required for allocating memory.
+*     init
+*        A logical flag indicating if the RateMap's virtual function table
+*        is to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new RateMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     map
+*        Pointer to the Mapping.
+*     ax1
+*        Zero-based index of output axis.
+*     ax2
+*        Zero-based index of input axis.
+
+*  Returned Value:
+*     A pointer to the new RateMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstRateMap *new;              /* Pointer to new RateMap */
+   int nin;                      /* No. input coordinates for RateMap */
+   int nout;                     /* No. output coordinates for RateMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitRateMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Report an error if "map" has no forward transformation. */
+   if( !astGetTranForward( map ) && astOK ) {
+      astError( AST__INTRD, "astInitRateMap(%s): The supplied Mapping "
+              "is not able to transform coordinates in the forward direction.", status,
+              name );
+   }
+
+/* Check that the input and output axis indices are valid. */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+   if( ( ax1 < 0 || ax1 >= nout ) && astOK ) {
+      astError( AST__INNCO, "astInitRateMap(%s): The output axis %d is out "
+                "of range - it should be in the range 1 to %d.", status, name,
+                ax1 + 1, nout );
+   }
+   if( ( ax2 < 0 || ax2 >= nin ) && astOK ) {
+      astError( AST__INNCO, "astInitRateMap(%s): The input axis %d is out "
+                "of range - it should be in the range 1 to %d.", status, name,
+                ax2 + 1, nin );
+   }
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the RateMap structure, allocating memory if necessary. Specify
+   the number of input and output coordinates and in which directions the
+   Mapping should be defined. */
+   if ( astOK ) {
+      new = (AstRateMap *) astInitMapping( mem, size, 0,
+                                          (AstMappingVtab *) vtab, name,
+                                          nin, 1, 1, 0 );
+
+      if ( astOK ) {
+
+/* Initialise the RateMap data. */
+/* --------------------------- */
+/* Store a pointer to the encapsulated Mapping. */
+         new->map = astClone( map );
+
+/* Save the initial values of the inversion flag for this Mapping. */
+         new->invert = astGetInvert( map );
+
+/* Save the input and output axis indices. */
+         new->iout = ax1;
+         new->iin = ax2;
+
+/* If an error occurred, clean up by annulling the Mapping pointers and
+   deleting the new object. */
+         if ( !astOK ) {
+            new->map = astAnnul( new->map );
+            new = astDelete( new );
+         }
+      }
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstRateMap *astLoadRateMap_( void *mem, size_t size,
+                             AstRateMapVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadRateMap
+
+*  Purpose:
+*     Load a RateMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "ratemap.h"
+*     AstRateMap *astLoadRateMap( void *mem, size_t size,
+*                                 AstRateMapVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     RateMap loader.
+
+*  Description:
+*     This function is provided to load a new RateMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     RateMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a RateMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the RateMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        RateMap data (sizeof(RateMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the RateMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the RateMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstRateMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new RateMap. If this is NULL, a pointer to
+*        the (static) virtual function table for the RateMap class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "RateMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new RateMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstRateMap *new;               /* Pointer to the new RateMap */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this RateMap. In this case the
+   RateMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstRateMap );
+      vtab = &class_vtab;
+      name = "RateMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitRateMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built RateMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "RateMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Invert flag. */
+/* ------------ */
+      new->invert = astReadInt( channel, "inv", 0 );
+      new->invert = ( new->invert != 0 );
+
+/* Input and output axes. */
+/* ---------------------- */
+      new->iin = astReadInt( channel, "iin", 0 );
+      new->iout = astReadInt( channel, "iout", 0 );
+
+/* Mapping. */
+/* -------- */
+      new->map = astReadObject( channel, "map", NULL );
+
+/* If an error occurred, clean up by deleting the new RateMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new RateMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+/* None. */
+
+
+
+
diff --git a/ast/ratemap.h b/ast/ratemap.h
new file mode 100644
index 0000000..b26bbd6
--- /dev/null
+++ b/ast/ratemap.h
@@ -0,0 +1,276 @@
+#if !defined( RATEMAP_INCLUDED )  /* Include this file only once */
+#define RATEMAP_INCLUDED
+/*
+*+
+*  Name:
+*     ratemap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the RateMap class.
+
+*  Invocation:
+*     #include "ratemap.h"
+
+*  Description:
+*     This include file defines the interface to the RateMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc. needed to use this class.
+
+*  Inheritance:
+*     The RateMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astMapMerge
+*           Merge a RateMap within a sequence of Mappings.
+*        astTransform
+*           Transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsARateMap
+*           Test class membership.
+*        astRateMap
+*           Create a RateMap.
+*
+*     Protected:
+*        astCheckRateMap
+*           Validate class membership.
+*        astInitRateMap
+*           Initialise a RateMap.
+*        astInitRateMapVtab
+*           Initialise the virtual function table for the RateMap class.
+*        astLoadRateMap
+*           Load a RateMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstRateMap
+*           RateMap object type.
+*
+*     Protected:
+*        AstRateMapVtab
+*           RateMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     7-DEC-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* RateMap structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstRateMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstMapping *map;              /* Pointer to the Mapping */
+   int invert;                   /* Inversion flag for Mapping */
+   int iin;                      /* Index of Mapping input to vary */
+   int iout;                     /* Index of Mapping output to measure */
+} AstRateMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstRateMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+/* None. */
+} AstRateMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstRateMapGlobals {
+   AstRateMapVtab Class_Vtab;
+   int Class_Init;
+} AstRateMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitRateMapGlobals_( AstRateMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(RateMap)           /* Check class membership */
+astPROTO_ISA(RateMap)             /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstRateMap *astRateMap_( void *, int, int, const char *, int *, ...);
+#else
+AstRateMap *astRateMapId_( void *, int, int, const char *, ... )__attribute__((format(printf,4,5)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstRateMap *astInitRateMap_( void *, size_t, int, AstRateMapVtab *,
+                             const char *, AstMapping *, int, int, int * );
+
+/* Vtab initialiser. */
+void astInitRateMapVtab_( AstRateMapVtab *, const char *, int * );
+
+/* Loader. */
+AstRateMap *astLoadRateMap_( void *, size_t, AstRateMapVtab *,
+                             const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+/* None. */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckRateMap(this) astINVOKE_CHECK(RateMap,this,0)
+#define astVerifyRateMap(this) astINVOKE_CHECK(RateMap,this,1)
+
+/* Test class membership. */
+#define astIsARateMap(this) astINVOKE_ISA(RateMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astRateMap astINVOKE(F,astRateMap_)
+#else
+#define astRateMap astINVOKE(F,astRateMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitRateMap(mem,size,init,vtab,name,map,iin,iout) \
+astINVOKE(O,astInitRateMap_(mem,size,init,vtab,name,astCheckMapping(map),iin,iout,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitRateMapVtab(vtab,name) astINVOKE(V,astInitRateMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadRateMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadRateMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckRateMap to validate RateMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+/* None. */
+#endif
+
+
+
+
+
diff --git a/ast/region.c b/ast/region.c
new file mode 100644
index 0000000..675a1ad
--- /dev/null
+++ b/ast/region.c
@@ -0,0 +1,13502 @@
+/*
+*class++
+*  Name:
+*     Region
+
+*  Purpose:
+*     Represents a region within a coordinate system.
+
+*  Constructor Function:
+*     None.
+
+*  Description:
+*     This class provides the basic facilities for describing a region within
+*     a specified coordinate system. However, the Region class does not
+*     have a constructor function of its own, as it is simply a container
+*     class for a family of specialised sub-classes such as Circle, Box, etc,
+*     which implement Regions with particular shapes.
+*
+*     All sub-classes of Region require a Frame to be supplied when the Region
+*     is created. This Frame describes the coordinate system in which the
+*     Region is defined, and is referred to as the "encapsulated Frame" below.
+*     Constructors will also typically required one or more positions to be
+*     supplied which define the location and extent of the region. These
+*     positions must be supplied within the encapsulated Frame.
+*
+*     The Region class inherits from the Frame class, and so a Region can be
+*     supplied where-ever a Frame is expected. In these cases, supplying a
+*     Region is equivalent to supplying a reference to its encapsulated Frame.
+*     Thus all the methods of the Frame class can be used on the Region class.
+*     For instance, the
+c     astFormat function
+f     AST_FORMAT routine
+*     may be used on a Region to format an axis value.
+*
+*     In addition, since Frame inherits from Mapping, a Region is also a sort
+*     of Mapping. Transforming positions by supplying a Region to one of the
+c     astTran<X> functions
+f     AST_TRAN<X> routines
+*     is the way to determine if a given position is inside or outside the
+*     Region. When used as a Mapping, most classes of Frame are equivalent to
+*     a UnitMap. However, the Region class modifies this behaviour so that a
+*     Region acts like a UnitMap only for input positions which are within the
+*     area represented by the Region. Input positions which are outside the
+*     area produce bad output values (i.e. the output values are equal to
+*     AST__BAD). This behaviour is the same for both the forward and the
+*     inverse transformation. In this sense the "inverse transformation"
+*     is not a true inverse of the forward transformation, since applying
+*     the forward transformation to a point outside the Region, and then
+*     applying the inverse transformation results, in a set of AST__BAD axis
+*     values rather than the original axis values. If required, the
+c     astRemoveRegions
+f     AST_REMOVEREGIONS
+*     function can be used to remove the "masking" effect of any Regions
+*     contained within a compound Mapping or FrameSet. It does this by
+*     replacing each Region with a UnitMap or equivalent Frame (depending
+*     on the context in which the Region is used).
+*
+*     If the coordinate system represented by the Region is changed (by
+*     changing the values of one or more of the attribute which the Region
+*     inherits from its encapsulated Frame), the area represented by
+*     the Region is mapped into the new coordinate system. For instance, let's
+*     say a Circle (a subclass of Region) is created, a SkyFrame being
+*     supplied to the constructor so that the Circle describes a circular
+*     area on the sky in FK4 equatorial coordinates. Since Region inherits
+*     from Frame, the Circle will have a System attribute and this attribute
+*     will be set to "FK4". If the System attribute of the Region is then
+*     changed from FK4 to FK5, the circular area represented by the Region
+*     will automatically be mapped from the FK4 system into the FK5 system.
+*     In general, changing the coordinate system in this way may result in the
+*     region changing shape - for instance, a circle may change into an
+*     ellipse if the transformation from the old to the new coordinate system
+*     is linear but with different scales on each axis. Thus the specific
+*     class of a Region cannot be used as a guarantee of the shape in any
+*     particular coordinate system. If the
+c     astSimplify function
+f     AST_SIMPLIFY routine
+*     is used on a Region, it will endeavour to return a new Region of
+*     a sub-class which accurately describes the shape in the current
+*     coordinate system of the Region (but this may not always be possible).
+*
+*     It is possible to negate an existing Region so that it represents all
+*     areas of the encapsulated Frame except for the area specified when
+*     the Region was created.
+
+*  Inheritance:
+*     The Region class inherits from the Frame class.
+
+*  Attributes:
+*     In addition to those attributes common to all Frames, every
+*     Region also has the following attributes:
+*
+*     - Adaptive: Should the area adapt to changes in the coordinate system?
+*     - Negated: Has the original region been negated?
+*     - Closed: Should the boundary be considered to be inside the region?
+*     - MeshSize: Number of points used to create a mesh covering the Region
+*     - FillFactor: Fraction of the Region which is of interest
+*     - Bounded: Is the Region bounded?
+*
+*     Every Region also inherits any further attributes that belong
+*     to the encapsulated Frame, regardless of that Frame's class. (For
+*     example, the Equinox attribute, defined by the SkyFrame class, is
+*     inherited by any Region which represents a SkyFrame.)
+
+*  Functions:
+c     In addition to those functions applicable to all Frames, the
+c     following functions may also be applied to all Regions:
+f     In addition to those routines applicable to all Frames, the
+f     following routines may also be applied to all Regions:
+*
+c     - astGetRegionBounds: Get the bounds of a Region
+f     - AST_GETREGIONBOUNDS: Get the bounds of a Region
+c     - astGetRegionFrame: Get a copy of the Frame represent by a Region
+f     - AST_GETREGIONFRAME: Get a copy of the Frame represent by a Region
+c     - astGetRegionFrameSet: Get a copy of the Frameset encapsulated by a Region
+f     - AST_GETREGIONFRAMESET: Get a copy of the Frameset encapsulated by a Region
+c     - astGetRegionMesh: Get a mesh of points covering a Region
+f     - AST_GETREGIONMESH: Get a mesh of points covering a Region
+c     - astGetRegionPoints: Get the positions that define a Region
+f     - AST_GETREGIONPOINTS: Get the positions that define a Region
+c     - astGetUnc: Obtain uncertainty information from a Region
+f     - AST_GETUNC: Obtain uncertainty information from a Region
+c     - astMapRegion: Transform a Region into a new coordinate system
+f     - AST_MAPREGION: Transform a Region into a new coordinate system
+c     - astNegate: Toggle the value of the Negated attribute
+f     - AST_NEGATE: Toggle the value of the Negated attribute
+c     - astOverlap: Determines the nature of the overlap between two Regions
+f     - AST_OVERLAP: Determines the nature of the overlap between two Regions
+c     - astMask<X>: Mask a region of a data grid
+f     - AST_MASK<X>: Mask a region of a data grid
+c     - astSetUnc: Associate a new uncertainty with a Region
+f     - AST_SETUNC: Associate a new uncertainty with a Region
+c     - astShowMesh: Display a mesh of points on the surface of a Region
+f     - AST_SHOWMESH: Display a mesh of points on the surface of a Region
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (STARLINK)
+
+*  History:
+*     3-DEC-2003 (DSB):
+*        Original version.
+*     12-MAY-2005 (DSB):
+*        Override astNormBox method.
+*     12-AUG-2005 (DSB):
+*        Override ObsLat and ObsLon accessor methods.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     2-MAR-2006 (DSB):
+*        Changed AST_LONG_DOUBLE to HAVE_LONG_DOUBLE.
+*     14-MAR-2006 (DSB):
+*        Added astGetRefFS.
+*     28-MAY-2007 (DSB):
+*        - Added protected function astBndMesh.
+*     14-JAN-2009 (DSB):
+*        Override the astIntersect method.
+*     20-JAN-2009 (DSB):
+*        Change astPickAxes so that it returns a Region rather than a
+*        Frame if possible. This included adding method astRegBasePick.
+*     9-FEB-2009 (DSB):
+*        Move PointList methods astGetEnclosure and astSetEnclosure to
+*        Region.
+*     18-FEB-2009 (DSB):
+*        Remove methods astGetEnclosure and astSetEnclosure.
+*     15-JUN-2009 (DSB):
+*        Modify MapRegion to use FrameSets properly.
+*     18-JUN-2009 (DSB):
+*        Override ObsAlt accessor methods.
+*     7-SEP-2009 (DSB):
+*        Fix astMask to avoid reading variance values from the data array.
+*     8-SEP-2009 (DSB):
+*        Fix bugs in astOverlap that could result in wrong results if
+*        either region is unbounded.
+*     4-JAN-2010 (DSB):
+*        Fix bug in GetRegionBounds (it was assumed implicitly that the base
+*        Frame had the same number of axes as the current Frame).
+*     18-MAR-2011 (DSB):
+*        Added astGetRegionMesh public method.
+*     22-MAR-2011 (DSB):
+*        Improve uniformity of points produced by astRegBaseGrid method.
+*     29-APR-2011 (DSB):
+*        Prevent astFindFrame from matching a subclass template against a
+*        superclass target.
+*     17-MAY-2011 (DSB):
+*        In RegBaseGrid, accept the final try even if it is not within 5%
+*        of the required meshsize.
+*     27-APR-2012 (DSB):
+*        Store a negated copy of itself with each Region. Changing the Negated
+*        attribute of a Region causes the cached information to be reset, and
+*        re-calculating it can be an expensive operation. So instead of changing
+*        "Negatated" in "this", access the negated copy of "this" using the
+*        new protected method astGetNegation.
+*     7-JUN-2012 (DSB):
+*        Added protected astRegSplit method to split a Region into disjoint
+*        component regions.
+*     15-JUN-2012 (DSB):
+*        Guard against division by zero in RegBase Grid if "ipr" is zero.
+*     7-NOV-2013 (DSB):
+*        Added method astGetRegionFrameSet.
+*     3-FEB-2014 (DSB):
+*        Fix bug masking regions that have no overlap with the supplied array.
+*     17-APR-2015 (DSB):
+*        Added Centre.
+*     26-OCT-2016 (DSB):
+*        - Override the astAxNorm method.
+*        - Use astAxNorm to fix a bug in astGetRegionBounds for cases where
+*        the Region cross a longitude=0 singularity.
+*     11-NOV-2016 (DSB):
+*        When loading a Region, use the dimensionality of the base Frame
+*        of the FrameSet (rather than the current Frame as it used to be)
+*        to define the number of axes required in the PointSet.
+*     1-DEC-2016 (DSB):
+*        Changed MapRegion to remove any unnecessary base frame axes in
+*        the returned Region.
+*class--
+
+*  Implementation Notes:
+*     - All sub-classes must over-ride the following abstract methods declared
+*     in this class: astRegBaseBox, astRegBaseMesh, astRegPins, astRegCentre.
+*     They must also extend the astTransform method. In addition they should
+*     usually extend astSimplify.
+
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Region
+
+/* Value for Ident attribute of of an encapsulated FrameSet which
+   indicates that it is a dummy FrameSet (see astRegDummy). */
+#define DUMMY_FS "ASTREGION-DUMMY"
+
+/*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Define a function to clear an attribute value for a Region.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "region.h"
+*     MAKE_CLEAR(attribute)
+
+*  Class Membership:
+*     Defined by the Region class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Clear<Attribute>( AstFrame *this )
+*
+*     that clears the value of a specified attribute for the encapsulated
+*     FrameSet within a Region (this). This function is intended to over-ride
+*     the astClear<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attribute) \
+static void Clear##attribute( AstFrame *this_frame, int *status ) { \
+\
+/* Local Variables: */ \
+   AstRegion *this;            /* Pointer to the Region structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the Region structure. */ \
+   this = (AstRegion *) this_frame; \
+\
+/* Obtain a pointer to the encapsulated FrameSet and invoke its \
+   astClear method. The protected astClear##attribute method is not used \
+   because we want the current Frame of the FrameSet tp be re-mapped if \
+   necessary. */ \
+   astClear( this->frameset, #attribute ); \
+}
+
+/*
+*  Name:
+*     MAKE_CLEAR_AXIS
+
+*  Purpose:
+*     Define a function to clear an attribute value for a Region axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "region.h"
+*     MAKE_CLEAR_AXIS(attribute)
+
+*  Class Membership:
+*     Defined by the Region class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Clear<Attribute>( AstFrame *this, int axis )
+*
+*     that clears the value of a specified attribute for an axis of
+*     the encapsulated FrameSet within a Region (this). This function is
+*     intended to over-ride the astClear<Attribute> method inherited
+*     from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR_AXIS(attribute) \
+static void Clear##attribute( AstFrame *this_frame, int axis, int *status ) { \
+\
+/* Local Variables: */ \
+   AstRegion *this;            /* Pointer to the Region structure */ \
+   char buf[100];              /* Buffer for attribute name */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the Region structure. */ \
+   this = (AstRegion *) this_frame; \
+\
+/* Validate the axis index supplied. */ \
+   (void) astValidateAxis( this, axis, 1, "astClear" #attribute ); \
+\
+/* We use the public astSetx method rather than the protected \
+   astSet#attribute method so that the current Frame in the encapsulated \
+   FrameSet will be re-mapped if necessary. Construct the attribute name. */ \
+   sprintf( buf, "%s(%d)", #attribute, axis + 1 ); \
+\
+/* Obtain a pointer to the Region's encapsulated FrameSet and invoke its \
+   astClear method. The protected astClear#attribute method is notused \
+   since we want the current Frame of the encapsulated FrameSet to be \
+   remapped if required. */ \
+   astClear( this->frameset, buf ); \
+}
+
+/*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Define a function to get an attribute value for a Region.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "region.h"
+*     MAKE_GET(attribute,type)
+
+*  Class Membership:
+*     Defined by the Region class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static <type> Get<Attribute>( AstFrame *this )
+*
+*     that gets the value of a specified attribute for the encapsulated
+*     FrameSet of a Region (this). This function is intended to over-ride
+*     the astGet<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     type
+*        The C type of the attribute.
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attribute,type) \
+static type Get##attribute( AstFrame *this_frame, int *status ) { \
+\
+/* Local Variables: */ \
+   AstRegion *this;            /* Pointer to the Region structure */ \
+   type result;                /* Value to return */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (type) 0; \
+\
+/* Obtain a pointer to the Region structure. */ \
+   this = (AstRegion *) this_frame; \
+\
+/* Obtain a pointer to the encapsulated FrameSet and invoke its \
+   astGet<Attribute> method.  */ \
+   result = astGet##attribute( this->frameset ); \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = (type) 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/*
+*  Name:
+*     MAKE_GET_AXIS
+
+*  Purpose:
+*     Define a function to get an attribute value for a Region axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "region.h"
+*     MAKE_GET_AXIS(attribute,type)
+
+*  Class Membership:
+*     Defined by the Region class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static <type> Get<Attribute>( AstFrame *this, int axis )
+*
+*     that gets the value of a specified attribute for an axis of the
+*     encapsulated FrameSet within a Region (this). This function is intended
+*     to over-ride the astGet<Attribute> method inherited from the Frame
+*     class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     type
+*        The C type of the attribute.
+*/
+
+/* Define the macro. */
+#define MAKE_GET_AXIS(attribute,type) \
+static type Get##attribute( AstFrame *this_frame, int axis, int *status ) { \
+\
+/* Local Variables: */ \
+   AstRegion *this;            /* Pointer to the Region structure */ \
+   type result;                /* Value to return */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (type) 0; \
+\
+/* Obtain a pointer to the Region structure. */ \
+   this = (AstRegion *) this_frame; \
+\
+/* Validate the axis index supplied. */ \
+   (void) astValidateAxis( this, axis, 1, "astGet" #attribute ); \
+\
+/* Obtain a pointer to the Region's encapsulated FrameSet and invoke its \
+   astGet<Attribute> method.  */ \
+   result = astGet##attribute( this->frameset, axis ); \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = (type) 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/*
+*  Name:
+*     MAKE_SET_SYSTEM
+
+*  Purpose:
+*     Define a function to set a System attribute value for a Region.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "region.h"
+*     MAKE_SET_SYSTEM(attribute)
+
+*  Class Membership:
+*     Defined by the Region class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Set<Attribute>( AstFrame *this, AstSystemType value )
+*
+*     that sets the value of a specified attribute for the encapsulated
+*     FrameSet of a Region (this). This function is intended to over-ride the
+*     astSet<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*/
+
+/* Define the macro. */
+#define MAKE_SET_SYSTEM(attribute) \
+static void Set##attribute( AstFrame *this_frame, AstSystemType value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstRegion *this;            /* Pointer to the Region structure */ \
+   const char *text;           /* Pointer to system string */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the Region structure. */ \
+   this = (AstRegion *) this_frame; \
+\
+/* Convert the supplied value to a string using the astSystemString
+   method of the current Frame in the encapsulated FrameSet. */ \
+   text = astSystemString( this->frameset, value ); \
+\
+/* Set the value by invoking the public astSetC method on the encapusulated \
+   FrameSet. This ensures that the current Frame of the encapsulated \
+   FrameSet is re-mapped if necessary. */ \
+   astSetC( this->frameset, #attribute, text ); \
+}
+
+/*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Define a function to set an attribute value for a Region.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "region.h"
+*     MAKE_SET(attribute,type,x)
+
+*  Class Membership:
+*     Defined by the Region class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Set<Attribute>( AstFrame *this, <type> value )
+*
+*     that sets the value of a specified attribute for the encapsulated
+*     FrameSet of a Region (this). This function is intended to over-ride the
+*     astSet<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     type
+*        The C type of the attribute.
+*     x
+*        The single character code for the astSetx function for the given C
+*        type.
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attribute,type,x) \
+static void Set##attribute( AstFrame *this_frame, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstRegion *this;            /* Pointer to the Region structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the Region structure. */ \
+   this = (AstRegion *) this_frame; \
+\
+/* Set the value by invoking the public astSetx method on the encapusulated \
+   FrameSet. This ensures that the current Frame of the encapsulated \
+   FrameSet is re-mapped if necessary. */ \
+   astSet##x( this->frameset, #attribute, value ); \
+}
+
+/*
+*  Name:
+*     MAKE_SET_AXIS
+
+*  Purpose:
+*     Define a function to set an attribute value for a Region axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "region.h"
+*     MAKE_SET_AXIS(attribute,type,x)
+
+*  Class Membership:
+*     Defined by the Region class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Set<Attribute>( AstFrame *this, int axis, <type> value )
+*
+*     that sets the value of a specified attribute for an axis of the
+*     encapsulated FrameSet within a Region (this). This function is intended
+*     to over-ride the astSet<Attribute> method inherited from the Frame
+*     class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     type
+*        The C type of the attribute.
+*     x
+*        The single character code for the astSetx function for the given C
+*        type.
+*/
+
+/* Define the macro. */
+#define MAKE_SET_AXIS(attribute,type,x) \
+static void Set##attribute( AstFrame *this_frame, int axis, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstRegion *this;            /* Pointer to the Region structure */ \
+   char buf[100];              /* Buffer for attribute name */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Obtain a pointer to the Region structure. */ \
+   this = (AstRegion *) this_frame; \
+\
+/* Validate the axis index supplied. */ \
+   (void) astValidateAxis( this, axis, 1, "astSet" #attribute ); \
+\
+/* We use the public astSetx method rather than the protected \
+   astSet#attribute method so that the current Frame in the encapsulated \
+   FrameSet will be re-mapped if necessary. Construct the attribute name. */ \
+   sprintf( buf, "%s(%d)", #attribute, axis + 1 ); \
+\
+/* Obtain a pointer to the Region's encapsulated FrameSet and invoke its \
+   astSet<x> method.  */ \
+   astSet##x( this->frameset, buf, value ); \
+}
+
+/*
+*  Name:
+*     MAKE_TEST
+
+*  Purpose:
+*     Define a function to test if an attribute value is set for a Region.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "region.h"
+*     MAKE_TEST(attribute)
+
+*  Class Membership:
+*     Defined by the Region class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static int Test<Attribute>( AstFrame *this )
+*
+*     that returns a boolean result (0 or 1) to indicate if the value
+*     of a specified attribute for the encapsulated FrameSet within a
+*     Region (this) is set. This function is intended to over-ride the
+*     astTest<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*/
+
+/* Define the macro. */
+#define MAKE_TEST(attribute) \
+static int Test##attribute( AstFrame *this_frame, int *status ) { \
+\
+/* Local Variables: */ \
+   AstRegion *this;            /* Pointer to Region structure */ \
+   int result;                 /* Result to return */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Obtain a pointer to the Region structure. */ \
+   this = (AstRegion *) this_frame; \
+\
+/* Obtain a pointer to the Region's encapsulated FrameSet and invoke its \
+   astTest<Attribute> method.  */ \
+   result = astTest##attribute( this->frameset ); \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/*
+*  Name:
+*     MAKE_TEST_AXIS
+
+*  Purpose:
+*     Define a function to test if an attribute value is set for a Region
+*     axis.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "region.h"
+*     MAKE_TEST_AXIS(attribute)
+
+*  Class Membership:
+*     Defined by the Region class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static int Test<Attribute>( AstFrame *this, int axis )
+*
+*     that returns a boolean result (0 or 1) to indicate if the value
+*     of a specified attribute for an axis of the encapsulated FrameSet
+*     within a Region (this) is set. This function is intended to over-ride
+*     the astTest<Attribute> method inherited from the Frame class.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*/
+
+/* Define the macro. */
+#define MAKE_TEST_AXIS(attribute) \
+static int Test##attribute( AstFrame *this_frame, int axis, int *status ) { \
+\
+/* Local Variables: */ \
+   AstRegion *this;              /* Pointer to the Region structure */ \
+   int result;                   /* Value to return */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Obtain a pointer to the Region structure. */ \
+   this = (AstRegion *) this_frame; \
+\
+/* Validate the axis index supplied. */ \
+   (void) astValidateAxis( this, axis, 1, "astTest" #attribute ); \
+\
+/* Obtain a pointer to the Region's encapsulated FrameSet and invoke its \
+   astTest<Attribute> method.  */ \
+   result = astTest##attribute( this->frameset, axis ); \
+\
+/* If an error occurred, clear the result value. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "mapping.h"             /* Coordinate Mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "permmap.h"             /* Coordinate permutation Mappings */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "frame.h"               /* Parent Frame class */
+#include "frameset.h"            /* Interconnected coordinate systems */
+#include "region.h"              /* Interface definition for this class */
+#include "circle.h"              /* Circular regions */
+#include "box.h"                 /* Box regions */
+#include "cmpregion.h"           /* Compound regions */
+#include "ellipse.h"             /* Elliptical regions */
+#include "pointset.h"            /* Sets of points */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static int (* parent_getusedefs)( AstObject *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Region)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Region,Class_Init)
+#define class_vtab astGLOBAL(Region,Class_Vtab)
+#define getattrib_buff astGLOBAL(Region,GetAttrib_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstRegionVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+static int MaskLD( AstRegion *, AstMapping *, int, int, const int[], const int ubnd[], long double [], long double, int * );
+#endif
+static int MaskB( AstRegion *, AstMapping *, int, int, const int[], const int[], signed char[], signed char, int * );
+static int MaskD( AstRegion *, AstMapping *, int, int, const int[], const int[], double[], double, int * );
+static int MaskF( AstRegion *, AstMapping *, int, int, const int[], const int[], float[], float, int * );
+static int MaskI( AstRegion *, AstMapping *, int, int, const int[], const int[], int[], int, int * );
+static int MaskL( AstRegion *, AstMapping *, int, int, const int[], const int[], long int[], long int, int * );
+static int MaskS( AstRegion *, AstMapping *, int, int, const int[], const int[], short int[], short int, int * );
+static int MaskUB( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned char[], unsigned char, int * );
+static int MaskUI( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned int[], unsigned int, int * );
+static int MaskUL( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned long int[], unsigned long int, int * );
+static int MaskUS( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned short int[], unsigned short int, int * );
+
+static AstAxis *GetAxis( AstFrame *, int, int * );
+static AstFrame *GetRegionFrame( AstRegion *, int * );
+static AstFrameSet *GetRegionFrameSet( AstRegion *, int * );
+static AstFrame *PickAxes( AstFrame *, int, const int[], AstMapping **, int * );
+static AstFrame *RegFrame( AstRegion *, int * );
+static AstFrameSet *Conv( AstFrameSet *, AstFrameSet *, int * );
+static AstFrameSet *Convert( AstFrame *, AstFrame *, const char *, int * );
+static AstFrameSet *ConvertX( AstFrame *, AstFrame *, const char *, int * );
+static AstFrameSet *FindFrame( AstFrame *, AstFrame *, const char *, int * );
+static AstFrameSet *GetRegFS( AstRegion *, int * );
+static AstLineDef *LineDef( AstFrame *, const double[2], const double[2], int * );
+static AstMapping *RegMapping( AstRegion *, int * );
+static AstMapping *RemoveRegions( AstMapping *, int * );
+static AstMapping *Simplify( AstMapping *, int * );
+static AstObject *Cast( AstObject *, AstObject *, int * );
+static AstPointSet *BTransform( AstRegion *, AstPointSet *, int, AstPointSet *, int * );
+static AstPointSet *BndBaseMesh( AstRegion *, double *, double *, int * );
+static AstPointSet *BndMesh( AstRegion *, double *, double *, int * );
+static AstPointSet *GetSubMesh( int *, AstPointSet *, int * );
+static AstPointSet *RegBaseGrid( AstRegion *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *RegGrid( AstRegion *, int * );
+static AstPointSet *RegMesh( AstRegion *, int * );
+static AstPointSet *RegTransform( AstRegion *, AstPointSet *, int, AstPointSet *, AstFrame **, int * );
+static AstPointSet *ResolvePoints( AstFrame *, const double [], const double [], AstPointSet *, AstPointSet *, int * );
+static AstRegion *MapRegion( AstRegion *, AstMapping *, AstFrame *, int * );
+static AstRegion *RegBasePick( AstRegion *, int, const int *, int * );
+static AstRegion **RegSplit( AstRegion *, int *, int * );
+static AstSystemType SystemCode( AstFrame *, const char *, int * );
+static AstSystemType ValidateSystem( AstFrame *, AstSystemType, const char *, int * );
+static const char *Abbrev( AstFrame *, int, const char *, const char *, const char *, int * );
+static const char *Format( AstFrame *, int, double, int * );
+static const char *SystemString( AstFrame *, AstSystemType, int * );
+static const int *GetPerm( AstFrame *, int * );
+static double *RegCentre( AstRegion *, double *, double **, int, int, int * );
+static double Angle( AstFrame *, const double[], const double[], const double[], int * );
+static double AxAngle( AstFrame *, const double[], const double[], int, int * );
+static double AxDistance( AstFrame *, int, double, double, int * );
+static double AxOffset( AstFrame *, int, double, double, int * );
+static double Distance( AstFrame *, const double[], const double[], int * );
+static double Centre( AstFrame *, int, double, double, int * );
+static double Gap( AstFrame *, int, double, int *, int * );
+static double Offset2( AstFrame *, const double[2], double, double, double[2], int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetNaxes( AstFrame *, int * );
+static int GetObjSize( AstObject *, int * );
+static int GetUseDefs( AstObject *, int * );
+static int IsUnitFrame( AstFrame *, int * );
+static int LineContains( AstFrame *, AstLineDef *, int, double *, int * );
+static int LineCrossing( AstFrame *, AstLineDef *, AstLineDef *, double **, int * );
+static int Match( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int Overlap( AstRegion *, AstRegion *, int * );
+static int OverlapX( AstRegion *, AstRegion *, int * );
+static int RegDummyFS( AstRegion *, int * );
+static int RegPins( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+static int SubFrame( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int RegTrace( AstRegion *, int, double *, double **, int * );
+static int Unformat( AstFrame *, int, const char *, double *, int * );
+static int ValidateAxis( AstFrame *, int, int, const char *, int * );
+static void AxNorm( AstFrame *, int, int, int, double *, int * );
+static void CheckPerm( AstFrame *, const int *, const char *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void GetRegionBounds( AstRegion *, double *, double *, int * );
+static void GetRegionBounds2( AstRegion *, double *, double *, int * );
+static void GetRegionMesh( AstRegion *, int, int, int, int *, double *, int * );
+static void GetRegionPoints( AstRegion *, int, int, int *, double *, int * );
+static void Intersect( AstFrame *, const double[2], const double[2], const double[2], const double[2], double[2], int * );
+static void LineOffset( AstFrame *, AstLineDef *, double, double, double[2], int * );
+static void MatchAxes( AstFrame *, AstFrame *, int *, int * );
+static void MatchAxesX( AstFrame *, AstFrame *, int *, int * );
+static void Negate( AstRegion *, int * );
+static void Norm( AstFrame *, double[], int * );
+static void NormBox( AstFrame *, double[], double[], AstMapping *, int * );
+static void Offset( AstFrame *, const double[], const double[], double, double[], int * );
+static void Overlay( AstFrame *, const int *, AstFrame *, int * );
+static void PermAxes( AstFrame *, const int[], int * );
+static void RegBaseBox( AstRegion *, double *, double *, int * );
+static void RegBaseBox2( AstRegion *, double *, double *, int * );
+static void RegClearAttrib( AstRegion *, const char *, char **, int * );
+static void RegOverlay( AstRegion *, AstRegion *, int, int * );
+static void RegSetAttrib( AstRegion *, const char *, char **, int * );
+static void ReportPoints( AstMapping *, int, AstPointSet *, AstPointSet *, int * );
+static void ResetCache( AstRegion *, int * );
+static void Resolve( AstFrame *, const double [], const double [], const double [], double [], double *, double *, int * );
+static void SetAxis( AstFrame *, int, AstAxis *, int * );
+static void SetRegFS( AstRegion *, AstFrame *, int * );
+static void ShowMesh( AstRegion *, int, const char *, int * );
+static void ValidateAxisSelection( AstFrame *, int, const int *, const char *, int * );
+static AstRegion *GetNegation( AstRegion *, int * );
+
+static int GetBounded( AstRegion *, int * );
+static AstRegion *GetDefUnc( AstRegion *, int * );
+
+static AstRegion *GetUncFrm( AstRegion *, int, int * );
+static AstRegion *GetUnc( AstRegion *, int, int * );
+static int TestUnc( AstRegion *, int * );
+static void ClearUnc( AstRegion *, int * );
+static void SetUnc( AstRegion *, AstRegion *, int * );
+
+static const char *GetDomain( AstFrame *, int * );
+static int TestDomain( AstFrame *, int * );
+static void ClearDomain( AstFrame *, int * );
+static void SetDomain( AstFrame *, const char *, int * );
+
+static const char *GetFormat( AstFrame *, int, int * );
+static int TestFormat( AstFrame *, int, int * );
+static void ClearFormat( AstFrame *, int, int * );
+static void SetFormat( AstFrame *, int, const char *, int * );
+
+static const char *GetLabel( AstFrame *, int, int * );
+static int TestLabel( AstFrame *, int, int * );
+static void ClearLabel( AstFrame *, int, int * );
+static void SetLabel( AstFrame *, int, const char *, int * );
+
+static const char *GetSymbol( AstFrame *, int, int * );
+static int TestSymbol( AstFrame *, int, int * );
+static void ClearSymbol( AstFrame *, int, int * );
+static void SetSymbol( AstFrame *, int, const char *, int * );
+
+static const char *GetTitle( AstFrame *, int * );
+static void SetTitle( AstFrame *, const char *, int * );
+static void ClearTitle( AstFrame *, int * );
+static int TestTitle( AstFrame *, int * );
+
+static const char *GetUnit( AstFrame *, int, int * );
+static int TestUnit( AstFrame *, int, int * );
+static void ClearUnit( AstFrame *, int, int * );
+static void SetUnit( AstFrame *, int, const char *, int * );
+
+static int GetDigits( AstFrame *, int * );
+static int TestDigits( AstFrame *, int * );
+static void ClearDigits( AstFrame *, int * );
+static void SetDigits( AstFrame *, int, int * );
+
+static int GetDirection( AstFrame *, int, int * );
+static int TestDirection( AstFrame *, int, int * );
+static void ClearDirection( AstFrame *, int, int * );
+static void SetDirection( AstFrame *, int, int, int * );
+
+static int GetActiveUnit( AstFrame *, int * );
+static int TestActiveUnit( AstFrame *, int * );
+static void SetActiveUnit( AstFrame *, int, int * );
+
+static int GetMatchEnd( AstFrame *, int * );
+static int TestMatchEnd( AstFrame *, int * );
+static void ClearMatchEnd( AstFrame *, int * );
+static void SetMatchEnd( AstFrame *, int, int * );
+
+static int GetMaxAxes( AstFrame *, int * );
+static int TestMaxAxes( AstFrame *, int * );
+static void ClearMaxAxes( AstFrame *, int * );
+static void SetMaxAxes( AstFrame *, int, int * );
+
+static int GetMinAxes( AstFrame *, int * );
+static int TestMinAxes( AstFrame *, int * );
+static void ClearMinAxes( AstFrame *, int * );
+static void SetMinAxes( AstFrame *, int, int * );
+
+static int GetPermute( AstFrame *, int * );
+static int TestPermute( AstFrame *, int * );
+static void ClearPermute( AstFrame *, int * );
+static void SetPermute( AstFrame *, int, int * );
+
+static int GetPreserveAxes( AstFrame *, int * );
+static int TestPreserveAxes( AstFrame *, int * );
+static void ClearPreserveAxes( AstFrame *, int * );
+static void SetPreserveAxes( AstFrame *, int, int * );
+
+static double GetBottom( AstFrame *, int, int * );
+static int TestBottom( AstFrame *, int, int * );
+static void ClearBottom( AstFrame *, int, int * );
+static void SetBottom( AstFrame *, int, double, int * );
+
+static double GetTop( AstFrame *, int, int * );
+static int TestTop( AstFrame *, int, int * );
+static void ClearTop( AstFrame *, int, int * );
+static void SetTop( AstFrame *, int, double, int * );
+
+static double GetEpoch( AstFrame *, int * );
+static int TestEpoch( AstFrame *, int * );
+static void ClearEpoch( AstFrame *, int * );
+static void SetEpoch( AstFrame *, double, int * );
+
+static double GetObsAlt( AstFrame *, int * );
+static int TestObsAlt( AstFrame *, int * );
+static void ClearObsAlt( AstFrame *, int * );
+static void SetObsAlt( AstFrame *, double, int * );
+
+static double GetObsLat( AstFrame *, int * );
+static int TestObsLat( AstFrame *, int * );
+static void ClearObsLat( AstFrame *, int * );
+static void SetObsLat( AstFrame *, double, int * );
+
+static double GetObsLon( AstFrame *, int * );
+static int TestObsLon( AstFrame *, int * );
+static void ClearObsLon( AstFrame *, int * );
+static void SetObsLon( AstFrame *, double, int * );
+
+static AstSystemType GetSystem( AstFrame *, int * );
+static int TestSystem( AstFrame *, int * );
+static void ClearSystem( AstFrame *, int * );
+static void SetSystem( AstFrame *, AstSystemType, int * );
+
+static AstSystemType GetAlignSystem( AstFrame *, int * );
+static int TestAlignSystem( AstFrame *, int * );
+static void ClearAlignSystem( AstFrame *, int * );
+static void SetAlignSystem( AstFrame *, AstSystemType, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+static int GetNegated( AstRegion *, int * );
+static int TestNegated( AstRegion *, int * );
+static void ClearNegated( AstRegion *, int * );
+static void SetNegated( AstRegion *, int, int * );
+
+static int GetClosed( AstRegion *, int * );
+static int TestClosed( AstRegion *, int * );
+static void ClearClosed( AstRegion *, int * );
+static void SetClosed( AstRegion *, int, int * );
+
+static int GetMeshSize( AstRegion *, int * );
+static int TestMeshSize( AstRegion *, int * );
+static void ClearMeshSize( AstRegion *, int * );
+static void SetMeshSize( AstRegion *, int, int * );
+
+static double GetFillFactor( AstRegion *, int * );
+static int TestFillFactor( AstRegion *, int * );
+static void ClearFillFactor( AstRegion *, int * );
+static void SetFillFactor( AstRegion *, double, int * );
+
+static int GetRegionFS( AstRegion *, int * );
+static int TestRegionFS( AstRegion *, int * );
+static void ClearRegionFS( AstRegion *, int * );
+static void SetRegionFS( AstRegion *, int, int * );
+
+static int GetAdaptive( AstRegion *, int * );
+static int TestAdaptive( AstRegion *, int * );
+static void ClearAdaptive( AstRegion *, int * );
+static void SetAdaptive( AstRegion *, int, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+/* Member functions. */
+/* ================= */
+
+static const char *Abbrev( AstFrame *this_frame, int axis, const char *fmt,
+                           const char *str1, const char *str2, int *status ) {
+/*
+*  Name:
+*     Abbrev
+
+*  Purpose:
+*     Abbreviate a formatted Region axis value by skipping leading fields.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     const char *Abbrev( AstFrame *this, int axis, const char *fmt,
+*                         const char *str1, const char *str2, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astAbbrev
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function compares two Region axis values that have been
+*     formatted (using astFormat) and determines if they have any
+*     redundant leading fields (i.e. leading fields in common which
+*     can be suppressed when tabulating the values or plotting them on
+*     the axis of a graph).
+
+*  Parameters:
+*     this
+*        Pointer to the Region
+*     axis
+*        The number of the Region axis for which the values have
+*        been formatted (axis numbering starts at zero for the first
+*        axis).
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format specification used to format the two values.
+*     str1
+*        Pointer to a constant null-terminated string containing the
+*        first formatted value.
+*     str1
+*        Pointer to a constant null-terminated string containing the
+*        second formatted value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer into the "str2" string which locates the first
+*     character in the first field that differs between the two
+*     formatted values.
+*
+*     If the two values have no leading fields in common, the returned
+*     value will point at the start of string "str2". If the two
+*     values are equal, it will point at the terminating null at the
+*     end of this string.
+
+*  Notes:
+*     - This function assumes that the format specification used was
+*     the same when both values were formatted and that they both
+*     apply to the same Region axis.
+*     - A pointer to the start of "str2" will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   const char *result;           /* Pointer value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return str2;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astAbbrev" );
+
+/* Obtain a pointer to the Region's current Frame and invoke this
+   Frame's astAbbrev method to perform the processing. Annul the Frame
+   pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astAbbrev( fr, axis, fmt, str1, str2 );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = str2;
+
+/* Return the result. */
+   return result;
+}
+
+static double Angle( AstFrame *this_frame, const double a[],
+                     const double b[], const double c[], int *status ) {
+/*
+*  Name:
+*     Angle
+
+*  Purpose:
+*     Calculate the angle subtended by two points at a third point.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     double Angle( AstFrame *this, const double a[], const double b[],
+*                   const double c[], int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astAngle
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function finds the angle at point B between the line joining points
+*     A and B, and the line joining points C and B. These lines will in fact be
+*     geodesic curves appropriate to the Frame in use. For instance, in
+*     SkyFrame, they will be great circles.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     a
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the first point.
+*     b
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the second point.
+*     c
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the third point.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     astAngle
+*        The angle in radians, from the line AB to the line CB. If the
+*        Frame is 2-dimensional, it will be in the range $\pm \pi$,
+*        and positive rotation is in the same sense as rotation from
+*        the positive direction of axis 2 to the positive direction of
+*        axis 1. If the Frame has more than 2 axes, a positive value will
+*        always be returned in the range zero to $\pi$.
+
+*  Notes:
+*     - A value of AST__BAD will also be returned if points A and B are
+*     co-incident, or if points B and C are co-incident.
+*     - A value of AST__BAD will also be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke this
+   Frame's astAngle method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astAngle( fr, a, b, c );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static double AxAngle( AstFrame *this_frame, const double a[], const double b[], int axis, int *status ) {
+/*
+*  Name:
+*     AxAngle
+
+*  Purpose:
+*     Returns the angle from an axis, to a line through two points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     double AxAngle( AstFrame *this, const double a[], const double b[], int axis, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astAxAngle
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function finds the angle, as seen from point A, between the positive
+*     direction of a specified axis, and the geodesic curve joining point
+*     A to point B.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     a
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the first point.
+*     b
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute) containing the coordinates of the second point.
+*     axis
+*        The number of the Frame axis from which the angle is to be
+*        measured (one-based)
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*        The angle in radians, from the positive direction of the
+*        specified axis, to the line AB. If the Frame is 2-dimensional,
+*        it will be in the range $\pm \pi$, and positive rotation is in
+*        the same sense as rotation from the positive direction of axis 2
+*        to the positive direction of axis 1. If the Frame has more than 2
+*        axes, a positive value will always be returned in the range zero
+*        to $\pi$.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value, or if the require
+*     position angle is undefined.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxAngle" );
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke the
+   astAxAngle method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astAxAngle( fr, a, b, axis );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static double AxDistance( AstFrame *this_frame, int axis, double v1, double v2, int *status ) {
+/*
+*  Name:
+*     AxDistance
+
+*  Purpose:
+*     Find the distance between two axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     double AxDistance( AstFrame *this, int axis, double v1, double v2, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astAxDistance
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a signed value representing the axis increment
+*     from axis value v1 to axis value v2.
+*
+*     For a simple Frame, this is a trivial operation returning the
+*     difference between the two axis values. But for other derived classes
+*     of Frame (such as a SkyFrame) this is not the case.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The index of the axis to which the supplied values refer. The
+*        first axis has index 1.
+*     v1
+*        The first axis value.
+*     v2
+*        The second axis value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The distance between the two axis values.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input vaues has this value.
+*     - A "bad" value will also be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxDistance" );
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke the
+   astAxDistance method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astAxDistance( fr, axis, v1, v2 );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static void AxNorm( AstFrame *this_frame, int axis, int oper, int nval,
+                    double *values, int *status ){
+/*
+*  Name:
+*     AxNorm
+
+*  Purpose:
+*     Normalise an array of axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void AxNorm( AstFrame *this, int axis, int oper, int nval,
+*                  double *values, int *status )
+
+*  Class Membership:
+*     FrameSet member function (over-rides the protected astAxNorm
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function modifies a supplied array of axis values so that
+*     they are normalised in the manner indicated by parameter "oper".
+*
+*     No normalisation is possible for a simple Frame and so the supplied
+*     values are returned unchanged. However, this may not be the case for
+*     specialised sub-classes of Frame. For instance, a SkyFrame has a
+*     discontinuity at zero longitude and so a longitude value can be
+*     expressed in the range [-Pi,+PI] or the range [0,2*PI].
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The index of the axis to which the supplied values refer. The
+*        first axis has index 1.
+*     oper
+*        Indicates the type of normalisation to be applied. If zero is
+*        supplied, the normalisation will be the same as that performed by
+*        function astNorm. If 1 is supplied, the normalisation will be
+*        chosen automatically so that the resulting list has the smallest
+*        range.
+*     nval
+*        The number of points in the values array.
+*     values
+*        On entry, the axis values to be normalised. Modified on exit to
+*        hold the normalised values.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxNorm" );
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke
+   the astAxNorm method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   astAxNorm( fr, axis, oper, nval, values );
+   fr = astAnnul( fr );
+}
+
+static double AxOffset( AstFrame *this_frame, int axis, double v1, double dist, int *status ) {
+/*
+*  Name:
+*     AxOffset
+
+*  Purpose:
+*     Add an increment onto a supplied axis value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     double AxOffset( AstFrame *this, int axis, double v1, double dist, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astAxOffset
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns an axis value formed by adding a signed axis
+*     increment onto a supplied axis value.
+*
+*     For a simple Frame, this is a trivial operation returning the
+*     sum of the two supplied values. But for other derived classes
+*     of Frame (such as a SkyFrame) this is not the case.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The index of the axis to which the supplied values refer. The
+*        first axis has index 1.
+*     v1
+*        The original axis value.
+*     dist
+*        The axis increment to add to the original axis value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The incremented axis value.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input vaues has this value.
+*     - A "bad" value will also be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis - 1, 1, "astAxOffset" );
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke the
+   astAxOffset method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astAxOffset( fr, axis, v1, dist );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *BndBaseMesh( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+/*
+*+
+*  Name:
+*     astBndBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing points spread around part of the boundary
+*     of a Region, in the base Frame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstPointSet *astBndBaseMesh( AstRegion *this, double *lbnd, double *ubnd )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a PointSet containing a set of points on the
+*     boundary of the intersection between the supplied Region and the
+*     supplied (current Frame) box. The mesh points refer to the base
+*     Frame. If the boundary of the supplied Region does not intersect the
+*     supplied box, then a PointSet containing a single bad point is
+*     returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array holding the lower limits of the axis values
+*        within the required box. Defined in the current Frame of the Region.
+*     ubnd
+*        Pointer to an array holding the upper limits of the axis values
+*        within the required box. Defined in the current Frame of the Region.
+
+*  Returned Value:
+*     Pointer to the PointSet holding the base Frame mesh. The axis values
+*     in this PointSet will have associated accuracies derived from the
+*     uncertainties which were supplied when the Region was created.
+*
+*     If the Region does not intersect the supplied box, the returned
+*     PointSet will contain a single point with a value of AST__BAD on
+*     every axis.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstBox *box;
+   AstCmpRegion *cmpreg;
+   AstPointSet *result;
+   double **ptr;
+   int ic;
+   int nc;
+
+/* Check the local error status. */
+   if ( !astOK ) return NULL;
+
+/* Form a Box describing the required box. */
+   box = astBox( this, 1, lbnd, ubnd, NULL, "", status );
+
+/* Check there is partial overlap between the Regions.*/
+   if( astOverlap( this, box ) > 3 ) {
+
+/* Form a CmpRegion representing the intersection between the supplied
+   Region and the above box. */
+      cmpreg = astCmpRegion( this, box, AST__AND, "", status );
+
+/* Get the boundary mesh. */
+      result = astRegBaseMesh( cmpreg );
+
+/* Free resources. */
+      cmpreg = astAnnul( cmpreg );
+
+/* If the boundary of the supplied Region does not intersect the box,
+   return a PointSet containing a single bad position. */
+   } else {
+      nc = astGetNin( this->frameset );
+      result = astPointSet( 1, nc, "", status );
+      ptr = astGetPoints( result );
+      if( ptr ) {
+         for( ic = 0; ic < nc; ic++ ) ptr[ ic ][ 0 ] = AST__BAD;
+      }
+   }
+
+/* Free resources. */
+   box = astAnnul( box );
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the required pointer. */
+   return result;
+}
+
+static AstPointSet *BndMesh( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+/*
+*+
+*  Name:
+*     astBndMesh
+
+*  Purpose:
+*     Return a PointSet containing points spread around part of the boundary
+*     of a Region, in the current Frame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstPointSet *astBndMesh( AstRegion *this, double *lbnd, double *ubnd )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a PointSet containing a set of points on the
+*     boundary of the intersection between the supplied Region and the
+*     supplied box. The points refer to the current Frame of the
+*     encapsulated FrameSet. If the boundary of the supplied Region does
+*     not intersect the supplied box, then a PointSet containing a single
+*     bad point is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array holding the lower limits of the axis values
+*        within the required box. Defined in the current Frame of the Region.
+*     ubnd
+*        Pointer to an array holding the upper limits of the axis values
+*        within the required box. Defined in the current base Frame of the
+*        Region.
+
+*  Returned Value:
+*     Pointer to the PointSet. The axis values in this PointSet will have
+*     associated accuracies derived from the uncertainties which were
+*     supplied when the Region was created.
+*
+*    If the Region does not intersect the supplied box, the returned
+*    PointSet will contain a single point with a value of AST__BAD on
+*    every axis.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   AstPointSet *ps1;
+   AstPointSet *result;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get the current->base Mapping from the Region. */
+   map = astGetMapping( this->frameset, AST__CURRENT, AST__BASE );
+
+/* Use astBndBaseMesh to get a mesh of base Frame points within this base
+   Frame bounding box. */
+   ps1 = astBndBaseMesh( this, lbnd, ubnd );
+
+/* Transform it into the current Frame. */
+   if( ps1 ) result = astTransform( map, ps1, 0, NULL );
+
+/* Free resources. */
+   map = astAnnul( map );
+   ps1 = astAnnul( ps1 );
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the required pointer. */
+   return result;
+}
+
+static AstPointSet *BTransform( AstRegion *this, AstPointSet *in,
+                                int forward, AstPointSet *out, int *status ) {
+/*
+*+
+*  Name:
+*     astBTransform
+
+*  Purpose:
+*     Use a Region to transform a set of points in the base Frame.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "circle.h"
+*     AstPointSet *astBTransform( AstRegion *this, AstPointSet *in,
+                                  int forward, AstPointSet *out )
+
+*  Class Membership:
+*     Region member function
+
+*  Description:
+*     This function takes a Region and a set of points within the base
+*     Frame of the Region, and transforms the points by setting axis values
+*     to AST__BAD for all points which are outside the region. Points inside
+*     the region are copied unchanged from input to output.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  This is identical to the astTransform method for a Region except
+*     that the supplied and returned points refer to the base Frame of
+*     the Region, rather than the current Frame.
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   int old;                      /* Origial value of "nomap" flag */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Save the current value of the "nomap" flag for this Region,and then
+   set it. Doing this tells the astRegMapping function (called by
+   astRegTransform) to assume a unit map connects base and current Frame. */
+   old = this->nomap;
+   this->nomap = 1;
+
+/* Invoke the usual astTransform method. The above setting of the "nomap"
+   flag will cause the astTransform method to treat the base Frame as the
+   current Frame. */
+   result = astTransform( this, in, forward, out );
+
+/* Reset the "nomap" flag. */
+   this->nomap = old;
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static AstObject *Cast( AstObject *this_object, AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Cast
+
+*  Purpose:
+*     Cast an Object into an instance of a sub-class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstObject *Cast( AstObject *this, AstObject *obj, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astCast
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a deep copy of an ancestral component of the
+*     supplied object. The required class of the ancestral component is
+*     specified by another object. Specifically, if "this" and "new" are
+*     of the same class, a copy of "this" is returned. If "this" is an
+*     instance of a subclass of "obj", then a copy of the component
+*     of "this" that matches the class of "obj" is returned. Otherwise,
+*     a NULL pointer is returned without error.
+
+*  Parameters:
+*     this
+*        Pointer to the Object to be cast.
+*     obj
+*        Pointer to an Object that defines the class of the returned Object.
+*        The returned Object will be of the same class as "obj".
+
+*  Returned Value:
+*     A pointer to the new Object. NULL if "this" is not a sub-class of
+*     "obj", or if an error occurs.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables; */
+   AstFrame *cfrm;
+   AstObject *new;
+   astDECLARE_GLOBALS
+   int generation_gap;
+
+/* Initialise */
+   new = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* See how many steps up the class inheritance ladder it is from "obj"
+   to this class (Region). A positive value is returned if Region is
+   a sub-class of "obj". A negative value is returned if "obj" is a
+   sub-class of Region. Zero is returned if "obj" is a Region.
+   AST__COUSIN is returned if "obj" is not on the same line of descent
+   as Region. */
+   generation_gap = astClassCompare( (AstObjectVtab *) &class_vtab,
+                                     astVTAB( obj ) );
+
+/* If "obj" is a Region or a sub-class of Region, we can cast by
+   truncating the vtab for "this" so that it matches the vtab of "obJ",
+   and then taking a deep copy of "this". */
+   if( generation_gap <= 0 && generation_gap != AST__COUSIN ) {
+      new = astCastCopy( this_object, obj );
+
+/* If "obj" is not a Region or a sub-class of Region (e.g. a Frame or
+   some sub-class of Frame), we attempt to cast the current Frame of the
+   encapsulated FrameSet into the class indicated by "obj". */
+   } else {
+      cfrm = astGetFrame( ((AstRegion *) this_object)->frameset, AST__CURRENT );
+      new = astCast( cfrm, obj );
+      cfrm = astAnnul( cfrm );
+   }
+
+/* Return the new pointer. */
+   return new;
+}
+
+static double Centre( AstFrame *this_frame, int axis, double value, double gap, int *status ) {
+/*
+*  Name:
+*     Centre
+
+*  Purpose:
+*     Find a "nice" central value for tabulating Frame axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     double  Centre( AstFrame *this_frame, int axis, double value,
+*                     double gap, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astCentre method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns an axis value which produces a nice formatted
+*     value suitable for a major tick mark on a plot axis, close to the
+*     supplied axis value.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the axis (zero-based) for which a central value
+*        is to be found.
+*     value
+*        An arbitrary axis value in the section that is being plotted.
+*     gap
+*        The gap size.
+
+*  Returned Value:
+*     The nice central axis value.
+
+*  Notes:
+*     - A value of zero is returned if the supplied gap size is zero.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astCentre" );
+
+/* Obtain a pointer to the Region's current Frame and invoke this
+   Frame's astCentre method to obtain the required value. Annul the
+   Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astCentre( fr, axis, value, gap );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static void CheckPerm( AstFrame *this_frame, const int *perm, const char *method, int *status ) {
+/*
+*  Name:
+*     CheckPerm
+
+*  Purpose:
+*     Check that an array contains a valid permutation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void CheckPerm( AstFrame *this, const int *perm, const char *method, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astCheckPerm
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of a permutation array that
+*     will be used to permute the order of a Frame's axes. If the
+*     permutation specified by the array is not valid, an error is
+*     reported and the global error status is set. Otherwise, the
+*     function returns without further action.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     perm
+*        Pointer to an array of integers with the same number of
+*        elements as there are axes in the Frame. For each axis, the
+*        corresponding integer gives the (zero based) axis index to be
+*        used to identify the information for that axis (using the
+*        un-permuted axis numbering). To be valid, the integers in
+*        this array should therefore all lie in the range zero to
+*        (naxes-1) inclusive, where "naxes" is the number of Frame
+*        axes, and each value should occur exactly once.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate a permutation array. This method name is used
+*        solely for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Error messages issued by this function refer to the external
+*     (public) numbering system used for axes (which is one-based),
+*     whereas zero-based axis indices are used internally.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke this
+   Frame's astCheckPerm method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   astCheckPerm( fr, perm, method );
+   fr = astAnnul( fr );
+
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astClearAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     Region, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstRegion *this;            /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* We first handle attributes that apply to the Region as a whole
+   (rather than to the encapsulated FrameSet). */
+
+/* Negated */
+/* ------- */
+   if ( !strcmp( attrib, "negated" ) ) {
+      astClearNegated( this );
+
+/* Closed */
+/* ------ */
+   } else if ( !strcmp( attrib, "closed" ) ) {
+      astClearClosed( this );
+
+/* FillFactor */
+/* ---------- */
+   } else if ( !strcmp( attrib, "fillfactor" ) ) {
+      astClearFillFactor( this );
+
+/* MeshSize */
+/* -------- */
+   } else if ( !strcmp( attrib, "meshsize" ) ) {
+      astClearMeshSize( this );
+
+/* Adaptive */
+/* -------- */
+   } else if ( !strcmp( attrib, "adaptive" ) ) {
+      astClearAdaptive( this );
+
+
+/* We now check for atttributes of superclasses which apply to the Region
+   as a whole. We do not want to pass these on to the encapsulated FrameSet. */
+
+/* ID. */
+/* --- */
+   } else if ( !strcmp( attrib, "id" ) ) {
+      astClearID( this );
+
+/* Ident. */
+/* ------ */
+   } else if ( !strcmp( attrib, "ident" ) ) {
+      astClearIdent( this );
+
+/* Invert. */
+/* ------- */
+   } else if ( !strcmp( attrib, "invert" ) ) {
+      astClearInvert( this );
+
+/* Report. */
+/* ------- */
+   } else if ( !strcmp( attrib, "report" ) ) {
+      astClearReport( this );
+
+
+/* If the name was not recognised, test if it matches any of the
+   read-only attributes of this class (including those of all superclasses).
+   If it does, then report an error. */
+   } else if ( !strcmp( attrib, "class" ) ||
+               !strcmp( attrib, "nin" ) ||
+               !strcmp( attrib, "nobject" ) ||
+               !strcmp( attrib, "nout" ) ||
+               !strcmp( attrib, "bounded" ) ||
+               !strcmp( attrib, "refcount" ) ||
+               !strcmp( attrib, "tranforward" ) ||
+               !strcmp( attrib, "traninverse" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Pass unrecognised attributes on to the Region's encapsulated FrameSet for
+   further interpretation. Do not pass on FrameSet attributes since we
+   pretend to the outside world that the encapsulated FrameSet is actually a
+   Frame. */
+   } else if ( strcmp( attrib, "base" ) &&
+               strcmp( attrib, "current" ) &&
+               strcmp( attrib, "nframe" ) ) {
+
+/* If the Region is to adapt to coordinate system chanmges, use the public
+   astClear method so that the current Frame in the encapsulated FrameSet will
+   be re-mapped if the attribute changes require it. */
+      if( astGetAdaptive( this ) ) {
+         astClear( this->frameset, attrib );
+
+/* If the Region is not to adapt to coordinate system chanmges, use the
+   astRegSetAttrib method which assigns the attribute setting to both
+   current and base Frames in the FrameSet without causing any remapping to
+   be performed. */
+      } else {
+         astRegClearAttrib( this, attrib, NULL );
+      }
+   }
+}
+
+static AstFrameSet *Conv( AstFrameSet *from, AstFrameSet *to, int *status ){
+/*
+*  Name:
+*     Conv
+
+*  Purpose:
+*     Find Mapping between Frames
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstFrameSet *Conv( AstFrameSet *from, AstFrameSet *to, int *status );
+
+*  Class Membership:
+*     Region member function
+
+*  Description:
+*     This function provides a convenient interface for astConvert.
+*     It is like astConvert except it does not alter the base Frames of
+*     the supplied FrameSets and does not require a Domain list.
+
+*  Parameters:
+*     from
+*        Pointer to the source FrameSet.
+*     to
+*        Pointer to the source FrameSet.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*        The conversion FrameSet (see astConvert).
+
+*/
+
+/* Local Variables: */
+   AstFrameSet *result;        /* FrameSet to return */
+   int from_base;              /* Index of original base Frame in "from" */
+   int to_base;                /* Index of original base Frame in "to" */
+
+/* Check the global error status. */
+   if( !astOK ) return NULL;
+
+/* Note the indices of the base Frames in the FrameSets. */
+   to_base = astGetBase( to );
+   from_base = astGetBase( from );
+
+/* Invoke astConvert. */
+   result = astConvert( from, to, "" );
+
+/* Re-instate original base Frames. */
+   astSetBase( to, to_base );
+   astSetBase( from, from_base );
+
+/* Return the result. */
+   return result;
+}
+
+static AstFrameSet *Convert( AstFrame *from, AstFrame *to,
+                             const char *domainlist, int *status ) {
+/*
+*  Name:
+*     Convert
+
+*  Purpose:
+*     Determine how to convert between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstFrameSet *Convert( AstFrame *from, AstFrame *to,
+*                           const char *domainlist, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the public astConvert
+*     method inherited fromm the Frame class).
+
+*  Description:
+*     This function compares two Regions and determines whether it
+*     is possible to convert between the coordinate systems which
+*     their current Frames represent. If conversion is possible, it
+*     returns a FrameSet which describes the conversion and which may
+*     be used (as a Mapping) to transform coordinate values in either
+*     direction.
+
+*  Parameters:
+*     from
+*        Pointer to a Region whose current Frame represents the
+*        "source" coordinate system.  Note that the Base attribute of
+*        the Region may be modified by this function.
+*     to
+*        Pointer to a Region whose current Frame represents the
+*        "destination" coordinate system.  Note that the Base
+*        attribute of the Region may be modified by this function.
+*     domainlist
+*        Pointer to a null-terminated character string containing a
+*        comma-separated list of Frame domains. This may be used to
+*        define a priority order for the different intermediate
+*        coordinate systems that might be used to perform the
+*        conversion.
+*
+*        The function will first try to obtain a conversion by making
+*        use only of intermediate Frames whose Domain attribute
+*        matches the first domain in this list. If this fails, the
+*        second domain in the list will be used, and so on, until
+*        conversion is achieved. A blank domain (e.g. two consecutive
+*        commas) indicates that all Frames should be considered,
+*        regardless of their Domain attributes. The list is
+*        case-insensitive and all white space is ignored.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*        If the requested coordinate conversion is possible, the
+*        function returns a pointer to a FrameSet which describes the
+*        conversion. Otherwise, a null Object pointer (AST__NULL) is
+*        returned without error.
+*
+*        If a FrameSet is returned, it will contain two Frames. Frame
+*        number 1 (its base Frame) will describe the source coordinate
+*        system, corresponding to the "from" parameter. Frame number 2
+*        (its current Frame) will describe the destination coordinate
+*        system, corresponding to the "to" parameter. The Mapping
+*        which inter-relates these Frames will perform the required
+*        conversion between the two coordinate systems.
+
+*  Notes:
+*     - The returned FrameSet will not contain any Regions. If one or
+*     more of the supplied Frames are in fact Regions, the corresponding
+*     Frames in any returned FrameSet will described the encapsulated
+*     Frame, without any region information.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrameSet *result;       /* Returned FrameSet */
+
+/* Check the inherited status. */
+   if ( !astOK ) return NULL;
+
+/* If the "from" pointer is a Region, get a pointer to the current Frame of
+   the encapsulated FrameSet and use it instead of the supplied pointer. */
+   if( astIsARegion( from ) ) {
+      from = astGetFrame( ((AstRegion *) from)->frameset, AST__CURRENT );
+   } else {
+      from = astClone( from );
+   }
+
+/* If the "to" pointer is a Region, get a pointer to the current Frame of
+   the encapsulated FrameSet and use it instead of the supplied pointer. */
+   if( astIsARegion( to ) ) {
+      to = astGetFrame( ((AstRegion *) to)->frameset, AST__CURRENT );
+   } else {
+      to = astClone( to );
+   }
+
+/* Now invoke astConvert on the above Frames. */
+   result = astConvert( from, to, domainlist );
+
+/* Annul the pointers used above. */
+   from = astAnnul( from );
+   to = astAnnul( to );
+
+/* Return the result */
+   return result;
+}
+
+static AstFrameSet *ConvertX( AstFrame *to, AstFrame *from,
+                              const char *domainlist, int *status ) {
+/*
+*  Name:
+*     ConvertX
+
+*  Purpose:
+*     Determine how to convert between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstFrameSet *astConvertX( AstFrame *to, AstFrame *from,
+*                               const char *domainlist )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astConvertX
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function performs the processing for the public astConvert
+*     method and has exactly the same interface except that the order
+*     of the first two arguments is swapped. This is a trick to allow
+*     the astConvert method to be over-ridden by derived classes on
+*     the basis of the class of either of its first two arguments.
+*
+*     See the astConvert method for details of the interface.
+*-
+*/
+
+/* Local Variables: */
+   AstFrameSet *result;       /* Returned FrameSet */
+
+/* Check the inherited status. */
+   if ( !astOK ) return NULL;
+
+/* If the "to" pointer is a Region, get a pointer to the current Frame of
+   the encapsulated FrameSet and use it instead of the supplied pointer. */
+   if( astIsARegion( to ) ) {
+      to = astGetFrame( ((AstRegion *) to)->frameset, AST__CURRENT );
+   } else {
+      to = astClone( to );
+   }
+
+/* If the "from" pointer is a Region, get a pointer to the current Frame of
+   the encapsulated FrameSet and use it instead of the supplied pointer. */
+   if( astIsARegion( from ) ) {
+      from = astGetFrame( ((AstRegion *) from)->frameset, AST__CURRENT );
+   } else {
+      from = astClone( from );
+   }
+
+/* Now invoke astConvertX on the above Frames. */
+   result = astConvertX( to, from, domainlist );
+
+/* Annul the pointers used above. */
+   from = astAnnul( from );
+   to = astAnnul( to );
+
+/* Return the result */
+   return result;
+}
+
+static double Distance( AstFrame *this_frame, const double point1[],
+                        const double point2[], int *status ) {
+/*
+*  Name:
+*     Distance
+
+*  Purpose:
+*     Calculate the distance between two points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     double Distance( AstFrame *this, const double point1[],
+*                      const double point2[], int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astDistance
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function finds the distance between two points whose
+*     Region coordinates are given. The distance calculated is that
+*     along the geodesic curve that joins the two points.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     point1
+*        An array of double, with one element for each Region axis
+*        containing the coordinates of the first point.
+*     point2
+*        An array of double, with one element for each Region axis
+*        containing the coordinates of the second point.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The distance between the two points.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input coordinates has this value.
+*     - A "bad" value will also be returned if this function is
+*     invoked with the AST error status set or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's current Frame and invoke this
+   Frame's astDistance method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astDistance( fr, point1, point2 );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two Objects are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int Equal( AstObject *this_object, AstObject *that_object, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astEqual protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two Regions are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Region.
+*     that
+*        Pointer to the second Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the Regions are equivalent, zero otherwise.
+
+*  Notes:
+*     - The Regions are equivalent if they are of the same class, have
+*     equal PointSets, have equal base Frames, have equal current Frames,
+*     and if the Mapping between base Frames is a UnitMap. In addition, the
+*     Negated attribute must have the same value in both Regions, as must
+*     the Closed attribute.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *bf1;
+   AstFrame *bf2;
+   AstFrame *cf1;
+   AstFrame *cf2;
+   AstMapping *m1;
+   AstMapping *m2;
+   AstRegion *that;
+   AstRegion *this;
+   const char *class1;
+   const char *class2;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check that the two objects have the same class. */
+   class1 = astGetClass( this_object );
+   class2 = astGetClass( that_object );
+   if( astOK && !strcmp( class1, class2 ) ) {
+
+/* Obtain pointers to the two Region structures. */
+      this = (AstRegion *) this_object;
+      that = (AstRegion *) that_object;
+
+/* Test their PointSets for equality. */
+      if( astEqual( this->points, that->points ) ){
+
+/* Test their base Frames for equality. */
+         bf1 = astGetFrame( this->frameset, AST__BASE );
+         bf2 = astGetFrame( that->frameset, AST__BASE );
+         if( astEqual( bf1, bf2 ) ){
+
+/* Test their current Frames for equality. */
+            cf1 = astGetFrame( this->frameset, AST__CURRENT );
+            cf2 = astGetFrame( that->frameset, AST__CURRENT );
+            if( astEqual( cf1, cf2 ) ){
+
+/* Get the two Mappings and check that they are equal */
+               m1 = astGetMapping( this->frameset, AST__BASE, AST__CURRENT );
+               m2 = astGetMapping( that->frameset, AST__BASE, AST__CURRENT );
+               if( astEqual( m1, m2 ) ) {
+
+/* Test the Negated and Closed flags are equal */
+                  if( astGetNegated( this ) == astGetNegated( that ) &&
+                       astGetClosed( this ) == astGetClosed( that ) ) {
+                     result = 1;
+                  }
+               }
+
+/* Free resources. */
+               m1 = astAnnul( m1 );
+               m2 = astAnnul( m2 );
+            }
+
+            cf1 = astAnnul( cf1 );
+            cf2 = astAnnul( cf2 );
+         }
+
+         bf1 = astAnnul( bf1 );
+         bf2 = astAnnul( bf2 );
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static void ClearUnc( AstRegion *this, int *status ){
+/*
+*+
+*  Name:
+*     astClearUnc
+
+*  Purpose:
+*     Erase any uncertainty information in a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astClearUnc( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function erases all uncertainty information, whether default
+*     or not, from a Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*-
+*/
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Annul any user-supplied uncertainty. Also indicate that cached
+   information may now be out of date. */
+   if( this->unc ) {
+      this->unc = astAnnul( this->unc );
+      astResetCache( this );
+   }
+
+/* Annul any default uncertainty. */
+   if( this->defunc ) this->defunc = astAnnul( this->defunc );
+
+}
+
+static AstFrameSet *FindFrame( AstFrame *target_frame, AstFrame *template,
+                               const char *domainlist, int *status ) {
+/*
+*  Name:
+*     FindFrame
+
+*  Purpose:
+*     Find a coordinate system with specified characteristics.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstFrameSet *FindFrame( AstFrame *target, AstFrame *template,
+*                             const char *domainlist, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astFindFrame method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function uses a "template" Frame to search a Region to
+*     identify a coordinate system which has a specified set of
+*     characteristics. If a suitable coordinate system can be found,
+*     the function returns a pointer to a FrameSet which describes the
+*     required coordinate system and how to convert coordinates to and
+*     from it.
+
+*  Parameters:
+*     target
+*        Pointer to the target Region.
+*     template
+*        Pointer to the template Frame, which should be an instance of
+*        the type of Frame you wish to find.
+*     domainlist
+*        Pointer to a null-terminated character string containing a
+*        comma-separated list of Frame domains. This may be used to
+*        establish a priority order for the different types of
+*        coordinate system that might be found.
+*
+*        The function will first try to find a suitable coordinate
+*        system whose Domain attribute equals the first domain in this
+*        list. If this fails, the second domain in the list will be
+*        used, and so on, until a result is obtained. A blank domain
+*        (e.g. two consecutive commas) indicates that any coordinate
+*        system is acceptable (subject to the template) regardless of
+*        its domain.
+*
+*        This list is case-insensitive and all white space is ignored.
+*        If you do not wish to restrict the domain in this way, you
+*        should supply an empty string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If the search is successful, the function returns a pointer to a
+*     FrameSet which contains the Frame found and a description of how
+*     to convert to (and from) the coordinate system it
+*     represents. Otherwise, a null Object pointer (AST__NULL) is
+*     returned without error.
+*
+*     If a FrameSet is returned, it will contain two Frames. Frame
+*     number 1 (its base Frame) represents the target coordinate
+*     system and will be the same as the target. Frame number 2 (its
+*     current Frame) will be a Frame representing the coordinate system
+*     which the function found. The Mapping which inter-relates these two
+*     Frames will describe how to convert between their respective coordinate
+*     systems. Note, the Frames in this FrameSet will not be Regions -
+*     that is, they will be simple Frames or other derived classes.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrameSet *result;          /* Pointer to result FrameSet */
+   AstFrame *fr;                 /* Pointer to encapsulated Frame */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the astFindFrame method on the current Frame of the
+   encapsulated FrameSet within the target Region. */
+   fr = astGetFrame( ((AstRegion *) target_frame)->frameset, AST__CURRENT );
+   result = astFindFrame( fr, template, domainlist );
+   fr = astAnnul( fr );
+
+/* Return the result. */
+   return result;
+}
+
+static const char *Format( AstFrame *this_frame, int axis, double value, int *status ) {
+/*
+*  Name:
+*     Format
+
+*  Purpose:
+*     Format a coordinate value for a Region axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     const char *Format( AstFrame *this, int axis, double value, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astFormat method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to a string containing the
+*     formatted (character) version of a coordinate value for a
+*     Region axis. The formatting applied is that specified by a
+*     previous invocation of the astSetFormat method. A suitable
+*     default format is applied if necessary.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     axis
+*        The number of the axis (zero-based) for which formatting is
+*        to be performed.
+*     value
+*        The coordinate value to be formatted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the formatted
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Region object, or at static memory. The contents of
+*     the string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or deletion
+*     of the Region. A copy of the string should therefore be made
+*     if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   const char *result;           /* Pointer value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astFormat" );
+
+/* Obtain a pointer to the Region's current Frame and invoke the
+   astFormat method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astFormat( fr, axis, value );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static double Gap( AstFrame *this_frame, int axis, double gap, int *ntick, int *status ) {
+/*
+*  Name:
+*     Gap
+
+*  Purpose:
+*     Find a "nice" gap for tabulating Region axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     double Gap( AstFrame *this, int axis, double gap, int *ntick, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astGap method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a gap size which produces a nicely spaced
+*     series of formatted values for a Region axis, the returned gap
+*     size being as close as possible to the supplied target gap
+*     size. It also returns a convenient number of divisions into
+*     which the gap can be divided.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     axis
+*        The number of the axis (zero-based) for which a gap is to be found.
+*     gap
+*        The target gap size.
+*     ntick
+*        Address of an int in which to return a convenient number of
+*        divisions into which the gap can be divided.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The nice gap size.
+
+*  Notes:
+*     - A value of zero is returned if the target gap size is zero.
+*     - A negative gap size is returned if the supplied gap size is negative.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   double result;                /* Gap value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astGap" );
+
+/* Obtain a pointer to the Region's current Frame and invoke this
+   Frame's astGap method to obtain the required gap value. Annul the
+   Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astGap( fr, axis, gap, ntick );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied Region,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstRegion *this;         /* Pointer to Region structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the Region structure. */
+   this = (AstRegion *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astGetObjSize( this->frameset );
+   result += astGetObjSize( this->points );
+   result += astGetObjSize( this->basemesh );
+   result += astGetObjSize( this->basegrid );
+   result += astGetObjSize( this->unc );
+   result += astGetObjSize( this->negation );
+   result += astGetObjSize( this->defunc );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astGetAttrib
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a Region, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Region, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Region. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
+   AstRegion *this;              /* Pointer to the Region structure */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Floating point attribute value */
+   int ival;                     /* Integer attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* We first handle attributes that apply to the Region as a whole
+   (rather than to the encapsulated FrameSet). */
+
+/* Negated */
+/* ------- */
+   if ( !strcmp( attrib, "negated" ) ) {
+      ival = astGetNegated( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Closed */
+/* ------ */
+   } else if ( !strcmp( attrib, "closed" ) ) {
+      ival = astGetClosed( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Adaptive */
+/* -------- */
+   } else if ( !strcmp( attrib, "adaptive" ) ) {
+      ival = astGetAdaptive( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* FillFactor */
+/* ---------- */
+   } else if ( !strcmp( attrib, "fillfactor" ) ) {
+      dval = astGetFillFactor( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* MeshSize */
+/* -------- */
+   } else if ( !strcmp( attrib, "meshsize" ) ) {
+      ival = astGetMeshSize( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Bounded */
+/* ------- */
+   } else if ( !strcmp( attrib, "bounded" ) ) {
+      ival = astGetBounded( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Now get the values of attributes inherited from parent classes. We do
+   this to avoid the request being passed on to the encapsulated FrameSet
+   below. */
+
+/* Class. */
+/* ------ */
+   } else if ( !strcmp( attrib, "class" ) ) {
+      result = astGetClass( this );
+
+/* ID. */
+/* --- */
+   } else if ( !strcmp( attrib, "id" ) ) {
+      result = astGetID( this );
+
+/* Ident. */
+/* ------ */
+   } else if ( !strcmp( attrib, "ident" ) ) {
+      result = astGetIdent( this );
+
+/* Invert. */
+/* ------- */
+   } else if ( !strcmp( attrib, "invert" ) ) {
+      ival = astGetInvert( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Nin. */
+/* ---- */
+   } else if ( !strcmp( attrib, "nin" ) ) {
+      ival = astGetNin( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Nobject. */
+/* -------- */
+   } else if ( !strcmp( attrib, "nobject" ) ) {
+      ival = astGetNobject( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Nout. */
+/* ----- */
+   } else if ( !strcmp( attrib, "nout" ) ) {
+      ival = astGetNout( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* RefCount. */
+/* --------- */
+   } else if ( !strcmp( attrib, "refcount" ) ) {
+      ival = astGetRefCount( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Report. */
+/* ------- */
+   } else if ( !strcmp( attrib, "report" ) ) {
+      ival = astGetReport( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* TranForward. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "tranforward" ) ) {
+      ival = astGetTranForward( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* TranInverse. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "traninverse" ) ) {
+      ival = astGetTranInverse( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Pass unrecognised attributes on to the Region's encapsulated FrameSet for
+   further interpretation. Do not pass on FrameSet attributes since we
+   pretend to the outside world that the encapsulated FrameSet is actually a
+   Frame. */
+   } else if ( strcmp( attrib, "base" ) &&
+               strcmp( attrib, "current" ) &&
+               strcmp( attrib, "nframe" ) ) {
+      result = astGetAttrib( this->frameset, attrib );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetBounded( AstRegion *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetBounded
+
+*  Purpose:
+*     Is the Region bounded?
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     int astGetBounded( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a flag indicating if the Region is bounded.
+*     The implementation provided by the base Region class is suitable
+*     for Region sub-classes representing the inside of a single closed
+*     curve (e.g. Circle, Ellipse, Box, etc). Other sub-classes (such as
+*     CmpRegion, PointList, etc ) may need to provide their own
+*     implementations.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     Non-zero if the Region is bounded. Zero otherwise.
+
+*-
+*/
+
+/* For Regions which are defined by one or more closed curves such as Circles,
+   Boxes, etc, the Region is bounded so long as it has not been negated.
+   Classes for which this is not true should over-ride this implementation. */
+   return !astGetNegated( this );
+}
+
+static AstAxis *GetAxis( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetAxis
+
+*  Purpose:
+*     Obtain a pointer to a specified Axis from a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstAxis *GetAxis( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astGetAxis method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Axis object associated
+*     with one of the axes of the current Frame of a Region. This
+*     object describes the quantity which is represented along that
+*     axis.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     axis
+*        The number of the axis (zero-based) for which an Axis pointer
+*        is required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the requested Axis object.
+
+*  Notes:
+*     - The reference count of the requested Axis object will be
+*     incremented by one to reflect the additional pointer returned by
+*     this function.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstAxis *result;              /* Pointer to Axis */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astGetAxis" );
+
+/* Obtain a pointer to the Region's encapsulated FrameSet and invoke
+   this FrameSet's astGetAxis method to obtain the required Axis
+   pointer. */
+   result = astGetAxis( this->frameset, axis );
+
+/* If an error occurred, annul the result. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstRegion *GetDefUnc( AstRegion *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetDefUnc
+
+*  Purpose:
+*     Obtain a pointer to the default uncertainty Region for a given Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstRegion *astGetDefUnc( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a pointer to a Region which represents the
+*     default uncertainty associated with a position on the boundary of the
+*     given  Region. The returned Region refers to the base Frame within the
+*     FrameSet encapsulated by the supplied Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     A pointer to the Region. This should be annulled (using astAnnul)
+*     when no longer needed.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;            /* Base Frame of supplied Region */
+   AstRegion *result;         /* Returned pointer */
+   double *lbnd;              /* Ptr. to array holding axis lower bounds */
+   double *ubnd;              /* Ptr. to array holding axis upper bounds */
+   double c;                  /* Central axis value */
+   double hw;                 /* Half width of uncertainty interval */
+   int i;                     /* Axis index */
+   int nax;                   /* Number of base Frame axes */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the base Frame in the supplied Region. */
+   bfrm = astGetFrame( this->frameset, AST__BASE );
+
+/* Get the number of base Frame axes. */
+   nax = astGetNaxes( bfrm );
+
+/* Get the base frame bounding box of the supplied Region. The astRegBaseBox
+   assumes the supplied Region has not been inverted. But if the Region
+   contains other Regions (e.g. a Prism or CmpRegion, etc) then this
+   assumption needs to be propagated to the component Regions, which
+   astRegBaseBox does not do. For this reason we use astRegBaseBox2
+   instead. */
+   lbnd = astMalloc( sizeof( double)*(size_t) nax );
+   ubnd = astMalloc( sizeof( double)*(size_t) nax );
+   astRegBaseBox2( this, lbnd, ubnd );
+
+/* Create a Box covering 1.0E-6 of this bounding box, centred on the
+   centre of the box. */
+   if( astOK ) {
+      for( i = 0; i < nax; i++ ) {
+         if( ubnd[ i ] != DBL_MAX && lbnd[ i ] != -DBL_MAX ) {
+            hw = fabs( 0.5E-6*(  ubnd[ i ] - lbnd[ i ] ) );
+            c = 0.5*(  ubnd[ i ] + lbnd[ i ] );
+            if( hw == 0.0 ) hw = c*0.5E-6;
+            ubnd[ i ] = c + hw;
+            lbnd[ i ] = c - hw;
+         } else {
+            ubnd[ i ] = 0.0;
+            lbnd[ i ] = 0.0;
+         }
+      }
+      result = (AstRegion *) astBox( bfrm, 1, lbnd, ubnd, NULL, "", status );
+   }
+
+/* Free resources. */
+   lbnd = astFree( lbnd );
+   ubnd = astFree( ubnd );
+   bfrm = astAnnul( bfrm );
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the required pointer. */
+   return result;
+}
+
+static AstRegion *GetNegation( AstRegion *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNegation
+
+*  Purpose:
+*     Obtain a pointer to a negated copy of the supplied Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstRegion *GetNegation( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a pointer to a Region which is a negated
+*     copy of "this". The copy is cached in the Region structure for
+*     future use.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     A pointer to the Region. This should be annulled (using astAnnul)
+*     when no longer needed.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* If the Region struture does not contain a pointer to a negated copy of
+   itself, create one now. */
+   if( ! this->negation ) {
+      this->negation = astCopy( this );
+      astNegate( this->negation );
+   }
+
+/* Return a clone of the negation pointer. */
+   return astClone( this->negation );
+}
+
+static AstFrameSet *GetRegFS( AstRegion *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetRegFS
+
+*  Purpose:
+*     Obtain a pointer to the FrameSet encapsulated within a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstFrameSet *astGetRegFS( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function
+
+*  Description:
+*     This function returns a pointer to the FrameSet encapsulated by the
+*     Region. This is a clone, not a deep copy, of the pointer stored
+*     in the Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     A pointer to the FrameSet.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Return the required pointer. */
+   return astClone( this->frameset );
+}
+
+static AstPointSet *GetSubMesh( int *mask, AstPointSet *in, int *status ) {
+/*
+*  Name:
+*     GetSubMesh
+
+*  Purpose:
+*     Extract a selection of points from a PointSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstPointSet *GetSubMesh( int *mask, AstPointSet *in, int *status )
+
+*  Class Membership:
+*     Region member function
+
+*  Description:
+*     This function creates a new PointSet holding points selected from a
+*     supplied PointSet. An integer mask is supplied to indicate which
+*     points should be selected.
+
+*  Parameters:
+*     mask
+*        Pointer to a mask array, Its size should be equal to the number
+*        of points in the supplied PointSet. Each corresponding point will
+*        be copied if the mask value is zero.
+*     in
+*        Pointer to the PointSet holding the input positions.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output PointSet.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   double **ptr_in;              /* Pointers to input axis values */
+   double **ptr_out;             /* Pointers to output axis values */
+   double *pin;                  /* Pointer to next input axis value */
+   double *pout;                 /* Pointer to next output axis value */
+   int *m;                       /* Pointer to next mask element */
+   int ic;                       /* Axis index */
+   int ip;                       /* Point index */
+   int nc;                       /* Number of axes in both PointSets */
+   int npin;                     /* Number of points in input PointSet */
+   int npout;                    /* Number of points in output PointSet */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the length of the mask. */
+   npin = astGetNpoint( in );
+
+/* Count the number of zeros in the mask. */
+   npout = 0;
+   m = mask;
+   for( ip = 0; ip < npin; ip++ ) {
+      if( *(m++) == 0 ) npout++;
+   }
+
+/* Create the output PointSet and get pointers to its data arrays. */
+   nc = astGetNcoord( in );
+   result = astPointSet( npout, nc, "", status );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Check pointers can be dereferenced safely. */
+   if( astOK ) {
+
+/* Copy the required axis values from the input to the output. */
+      for( ic = 0; ic < nc; ic++ ) {
+         pin = ptr_in[ ic ];
+         pout = ptr_out[ ic ];
+         m = mask;
+         for( ip = 0; ip < npin; ip++, pin++, m++ ) {
+            if( *m == 0 ) *(pout++) = *pin;
+         }
+      }
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+
+}
+
+static AstRegion *GetUnc( AstRegion *this, int def, int *status ){
+/*
+*++
+*  Name:
+c     astGetUnc
+f     AST_GETUNC
+
+*  Purpose:
+*     Obtain uncertainty information from a Region.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     AstRegion *astGetUnc( AstRegion *this, int def )
+f     RESULT = AST_GETUNC( THIS, DEF, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     This function returns a Region which represents the uncertainty
+*     associated with positions within the supplied Region. See
+c     astSetUnc
+f     AST_SETUNC
+*     for more information about Region uncertainties and their use.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+c     def
+f     DEF = LOGICAL (Given)
+*        Controls what is returned if no uncertainty information has been
+*        associated explicitly with the supplied Region. If
+c        a non-zero value
+f        .TRUE.
+*        is supplied, then the default uncertainty Region used internally
+*        within AST is returned (see "Applicability" below). If
+c        zero is supplied, then NULL
+f        .FALSE. is supplied, then AST__NULL
+*        will be returned (without error).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetUnc()
+f     AST_GETUNC = INTEGER
+*        A pointer to a Region describing the uncertainty in the supplied
+*        Region.
+
+*  Applicability:
+*     CmpRegion
+*        The default uncertainty for a CmpRegion is taken from one of the
+*        two component Regions. If the first component Region has a
+*        non-default uncertainty, then it is used as the default uncertainty
+*        for the parent CmpRegion. Otherwise, if the second component Region
+*        has a non-default uncertainty, then it is used as the default
+*        uncertainty for the parent CmpRegion. If neither of the
+*        component Regions has non-default uncertainty, then the default
+*        uncertainty for the CmpRegion is 1.0E-6 of the bounding box of
+*        the CmpRegion.
+*     Prism
+*        The default uncertainty for a Prism is formed by combining the
+*        uncertainties from the two component Regions. If a component
+*        Region does not have a non-default uncertainty, then its default
+*        uncertainty will be used to form the default uncertainty of the
+*        parent Prism.
+*     Region
+*        For other classes of Region, the default uncertainty is 1.0E-6
+*        of the bounding box of the Region. If the bounding box has zero
+*        width on any axis, then the uncertainty will be 1.0E-6 of the
+*        axis value.
+
+*  Notes:
+*     - If uncertainty information is associated with a Region, and the
+*     coordinate system described by the Region is subsequently changed
+*     (e.g. by changing the value of its System attribute, or using the
+c     astMapRegion
+f     AST_MAPREGION
+*     function), then the uncertainty information returned by this function
+*     will be modified so that it refers to the coordinate system currently
+*     described by the supplied Region.
+f     - A null Object pointer (AST__NULL) will be returned if this
+f     function is invoked with STATUS set to an error value, or if it
+c     - A null Object pointer (NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+
+*--
+*/
+
+/* Local Variables: */
+   AstRegion *result;       /* Pointer to returned uncertainty Region */
+   AstRegion *unc;          /* Pointer to original uncertainty Region */
+
+/* Initialise */
+   result = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Check that we have an uncertainty Region to return (either assigned or
+   default). */
+   if( def || astTestUnc( this ) ) {
+
+/* Obtain the uncertainty Region and take a copy so that we can modify it
+   without affecting the supplied Region. */
+      unc = astGetUncFrm( this, AST__CURRENT );
+      result = astCopy( unc );
+      unc = astAnnul( unc );
+
+/* In its current context, the uncertainty region is known to refer to
+   the Frame of the supplied Region and so its RegionFS attribute will be
+   set to zero, indicating that the uncertainty FrameSet need not be
+   dumped. However, outside of AST this information cannot be implied, so
+   clear the RegionFS attribute so that the returned pointer will include
+   Frame information if it is dumped to a Channel. */
+      astClearRegionFS( result );
+
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static AstRegion *GetUncFrm( AstRegion *this, int ifrm, int *status ) {
+/*
+*+
+*  Name:
+*     astGetUncFrm
+
+*  Purpose:
+*     Obtain a pointer to the uncertainty Region for a given Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstRegion *astGetUncFrm( AstRegion *this, int ifrm )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a pointer to a Region which represents the
+*     uncertainty associated with a position on the boundary of the given
+*     Region. The returned Region can refer to the either the base or
+*     the current Frame within the FrameSet encapsulated by the supplied
+*     Region as specified by the "ifrm" parameter. If the returned Region is
+*     re-centred at some point on the boundary of the supplied Region, then
+*     the re-centred Region will represent the region in which the true
+*     boundary position could be.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     ifrm
+*        The index of a Frame within the FrameSet encapsulated by "this".
+*        The returned Region will refer to the requested Frame. It should
+*        be either AST__CURRENT or AST__BASE.
+
+*  Returned Value:
+*     A pointer to the Region. This should be annulled (using astAnnul)
+*     when no longer needed.
+
+*  Notes:
+*     - A default uncertainty Region will be created if the supplied Region
+*     does not have an uncertainty Region.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *frm;             /* Current Frame from supplied Region */
+   AstMapping *map;           /* Supplied to uncertainty Mapping */
+   AstRegion *result;         /* Returned pointer */
+   AstRegion *unc;            /* Base frame uncertainty Region to use */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the Region has an explicitly assigned base-frame uncertainty Region,
+   use it. */
+   if( this->unc ) {
+      unc = this->unc;
+
+/* If not, use the default base-frame uncertainty Region, creating it if
+   necessary. */
+   } else {
+      if( !this->defunc ) this->defunc = astGetDefUnc( this );
+      unc = this->defunc;
+   }
+
+/* If the uncertainty Region is the base Frame is required, just return a
+   clone of the uncertainty Region pointer. The Frame represented by an
+   uncertainty Region will always (barring bugs!) be the base Frame of
+   its parent Region. */
+   if( ifrm == AST__BASE ) {
+      result = astClone( unc );
+
+/* If the uncertainty Region is the current Frame is required... */
+   } else {
+
+/* Get a Mapping from the Frame represented by the uncertainty Region
+   (the Region base Frame) to the Region current Frame. */
+      map = astGetMapping( this->frameset, AST__BASE, AST__CURRENT );
+
+/* If this is a UnitMap, the uncertainty Region is already in the correct
+   Frame, so just return the stored pointer. */
+      if( astIsAUnitMap( map ) ) {
+         result = astClone( unc );
+
+/* Otherwise, use this Mapping to map the uncertainty Region into the current
+   Frame. */
+      } else {
+         frm = astGetFrame( this->frameset, AST__CURRENT );
+         result = astMapRegion( unc, map, frm );
+
+/* Free resources. */
+         frm = astAnnul( frm );
+      }
+
+      map = astAnnul( map );
+   }
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the required pointer. */
+   return result;
+}
+
+static int GetUseDefs( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetUseDefs
+
+*  Purpose:
+*     Get the value of the UseDefs attribute for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int GetUseDefs( AstObject *this_object, int *status ) {
+
+*  Class Membership:
+*     Region member function (over-rides the protected astGetUseDefs
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the value of the UseDefs attribute for a
+*     Region. supplying a suitable default.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - The USeDefs value.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   int result;                   /* Value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_object;
+
+/* If the UseDefs value for the Region has been set explicitly, use the
+   Get method inherited from the parent Frame class to get its value. */
+   if( astTestUseDefs( this ) ) {
+      result = (*parent_getusedefs)( this_object, status );
+
+/* Otherwise, supply a default value equal to the UseDefs value of the
+   encapsulated Frame. */
+   } else {
+      fr = astGetFrame( this->frameset, AST__CURRENT );
+      result = astGetUseDefs( fr );
+      fr = astAnnul( fr );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int TestUnc( AstRegion *this, int *status ) {
+/*
+*+
+*  Name:
+*     astTestUnc
+
+*  Purpose:
+*     Does the Region contain non-default uncertainty information?
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     int astTestUnc( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a flag indicating if the uncertainty Region in
+*     the supplied Region was supplied explicit (i.e. is not a default
+*     uncertainty Region).
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     Non-zero if the uncertainty Region was supplied explicitly.
+*     Zero otherwise.
+
+*  Notes:
+*     - Classes of Region that encapsulate two or more other Regions
+*     inherit their default uncertainty from the encapsulated Regions.
+*     Non-default uncertainty in the component Regions does not imply
+*     that the parent Region has non-default uncertainty.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+   return ( this->unc != NULL );
+}
+
+static AstFrame *RegFrame( AstRegion *this, int *status ) {
+/*
+*+
+*  Name:
+*     astRegFrame
+
+*  Purpose:
+*     Obtain a pointer to the current Frame for a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstFrame *astRegFrame( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function
+
+*  Description:
+*     This function returns a pointer to the current Frame in the encapsulated
+*     FrameSet. This is a clone, not a deep copy, of the pointer stored
+*     in the FrameSet. For a deep copy, use astGetRegionFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     A pointer to the Frame.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Return the required pointer. */
+   return astGetFrame( this->frameset, AST__CURRENT );
+}
+
+static AstMapping *RegMapping( AstRegion *this, int *status ) {
+/*
+*+
+*  Name:
+*     astRegMapping
+
+*  Purpose:
+*     Obtain a pointer to the simplified base->current Mapping for a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstMapping *astRegMapping( AstRegion *this )
+
+*  Class Membership:
+*     Region member function
+
+*  Description:
+*     This function returns a pointer to the Mapping from the base to the
+*     current Frame int he encapsulated FrameSet. The returned Mapping is
+*     simplified before being returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     A pointer to the Mapping.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstMapping *map;           /* Unsimplified Mapping */
+   AstMapping *result;        /* Simplified Mapping */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the "nomap" flag is set in the Region structure, re return a
+   UnitMap. */
+   if( this->nomap ) {
+      result = (AstMapping *) astUnitMap( astGetNin( this->frameset ), "", status );
+
+/* Otherwise use the Mapping from the Region's FrameSet. */
+   } else {
+
+/* Get the Mapping */
+      map = astGetMapping( this->frameset, AST__BASE, AST__CURRENT );
+
+/* Simplify it. */
+      result = astSimplify( map );
+
+/* Annul the pointer to the unsimplified Mapping */
+      map = astAnnul( map );
+   }
+
+/* Return the required pointer. */
+   return result;
+}
+
+static int GetNaxes( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetNaxes
+
+*  Purpose:
+*     Determine how many axes a Region has.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int GetNaxes( AstFrame *this, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astGetNaxes method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns the number of axes for a Region. This is equal
+*     to the number of axes in its current Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of Region axes (zero or more).
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's current Frame. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+
+/* Obtain the number of axes in this Frame. */
+   result = astGetNaxes( fr );
+
+/* Annul the current Frame pointer. */
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static const int *GetPerm( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetPerm
+
+*  Purpose:
+*     Access the axis permutation array for the current Frame of a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     const int *GetPerm( AstFrame *this, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astGetPerm protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the axis permutation array
+*     for the current Frame of a Region. This array constitutes a
+*     lookup-table that converts between an axis number supplied
+*     externally and the corresponding index in the Frame's internal
+*     axis arrays.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the current Frame's axis permutation array (a
+*     constant array of int).  Each element of this contains the
+*     (zero-based) internal axis index to be used in place of the
+*     external index which is used to address the permutation
+*     array. If the current Frame has zero axes, this pointer will be
+*     NULL.
+
+*  Notes:
+*     - The pointer returned by this function gives direct access to
+*     data internal to the Frame object. It remains valid only so long
+*     as the Frame exists. The permutation array contents may be
+*     modified by other functions which operate on the Frame and this
+*     may render the returned pointer invalid.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to Region structure */
+   const int *result;            /* Result pointer value */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's current Frame and then obtain a
+   pointer to its axis permutation array. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astGetPerm( fr );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static AstFrame *GetRegionFrame( AstRegion *this, int *status ) {
+/*
+*++
+*  Name:
+c     astGetRegionFrame
+f     AST_GETREGIONFRAME
+
+*  Purpose:
+*     Obtain a pointer to the encapsulated Frame within a Region.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     AstFrame *astGetRegionFrame( AstRegion *this )
+f     RESULT = AST_GETREGIONFRAME( THIS, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     This function returns a pointer to the Frame represented by a
+*     Region.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetRegionFrame()
+f     AST_GETREGIONFRAME = INTEGER
+*        A pointer to a deep copy of the Frame represented by the Region.
+*        Using this pointer to modify the Frame will have no effect on
+*        the Region. To modify the Region, use the Region pointer directly.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstFrame *result;             /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the current Frame of the encapsulated FrameSet. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+
+/* Take a deep copy of it, and then annul the original pointer. */
+   result = astCopy( fr );
+   fr = astAnnul( fr );
+
+/* If not OK, annul the returned pointer. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstFrameSet *GetRegionFrameSet( AstRegion *this, int *status ) {
+/*
+*++
+*  Name:
+c     astGetRegionFrameSet
+f     AST_GETREGIONFRAMESET
+
+*  Purpose:
+*     Obtain a pointer to the encapsulated FrameSet within a Region.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     AstFrame *astGetRegionFrameSet( AstRegion *this )
+f     RESULT = AST_GETREGIONFRAMESET( THIS, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     This function returns a pointer to the FrameSet encapsulated by a
+*     Region. The base Frame is the Frame in which the box was originally
+*     defined, and the current Frame is the Frame into which the Region
+*     is currently mapped (i.e. it will be the same as the Frame returned
+c     by astGetRegionFrame).
+f     by AST_GETREGIONFRAME).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetRegionFrameSet()
+f     AST_GETREGIONFRAMESET = INTEGER
+*        A pointer to a deep copy of the FrameSet represented by the Region.
+*        Using this pointer to modify the FrameSet will have no effect on
+*        the Region.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Return a deep copy of the encapsulated FrameSet. */
+   return astCopy( this->frameset );
+}
+
+void astInitRegionVtab_(  AstRegionVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitRegionVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astInitRegionVtab( AstRegionVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Region vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Region class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrameVtab *frame;          /* Pointer to Frame component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitFrameVtab( (AstFrameVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsARegion) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstFrameVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->ClearNegated = ClearNegated;
+   vtab->GetNegated = GetNegated;
+   vtab->SetNegated = SetNegated;
+   vtab->TestNegated = TestNegated;
+
+   vtab->ClearRegionFS = ClearRegionFS;
+   vtab->GetRegionFS = GetRegionFS;
+   vtab->SetRegionFS = SetRegionFS;
+   vtab->TestRegionFS = TestRegionFS;
+
+   vtab->ClearClosed = ClearClosed;
+   vtab->GetClosed = GetClosed;
+   vtab->SetClosed = SetClosed;
+   vtab->TestClosed = TestClosed;
+
+   vtab->ClearMeshSize = ClearMeshSize;
+   vtab->GetMeshSize = GetMeshSize;
+   vtab->SetMeshSize = SetMeshSize;
+   vtab->TestMeshSize = TestMeshSize;
+
+   vtab->ClearAdaptive = ClearAdaptive;
+   vtab->GetAdaptive = GetAdaptive;
+   vtab->SetAdaptive = SetAdaptive;
+   vtab->TestAdaptive = TestAdaptive;
+
+   vtab->ClearFillFactor = ClearFillFactor;
+   vtab->GetFillFactor = GetFillFactor;
+   vtab->SetFillFactor = SetFillFactor;
+   vtab->TestFillFactor = TestFillFactor;
+
+   vtab->ResetCache = ResetCache;
+   vtab->RegTrace = RegTrace;
+   vtab->GetBounded = GetBounded;
+   vtab->TestUnc = TestUnc;
+   vtab->ClearUnc = ClearUnc;
+   vtab->GetRegionFrame = GetRegionFrame;
+   vtab->GetRegionFrameSet = GetRegionFrameSet;
+   vtab->MapRegion = MapRegion;
+   vtab->Overlap = Overlap;
+   vtab->OverlapX = OverlapX;
+   vtab->Negate = Negate;
+   vtab->BndMesh = BndMesh;
+   vtab->BndBaseMesh = BndBaseMesh;
+   vtab->RegBaseGrid = RegBaseGrid;
+   vtab->RegBaseMesh = RegBaseMesh;
+   vtab->RegSplit = RegSplit;
+   vtab->RegBaseBox = RegBaseBox;
+   vtab->RegBaseBox2 = RegBaseBox2;
+   vtab->RegBasePick = RegBasePick;
+   vtab->RegCentre = RegCentre;
+   vtab->RegGrid = RegGrid;
+   vtab->RegMesh = RegMesh;
+   vtab->RegClearAttrib = RegClearAttrib;
+   vtab->RegSetAttrib = RegSetAttrib;
+   vtab->GetDefUnc = GetDefUnc;
+   vtab->GetNegation = GetNegation;
+   vtab->GetUncFrm = GetUncFrm;
+   vtab->SetUnc = SetUnc;
+   vtab->GetUnc = GetUnc;
+   vtab->ShowMesh = ShowMesh;
+   vtab->GetRegionBounds = GetRegionBounds;
+   vtab->GetRegionBounds2 = GetRegionBounds2;
+   vtab->GetRegionMesh = GetRegionMesh;
+   vtab->GetRegionPoints = GetRegionPoints;
+   vtab->RegOverlay = RegOverlay;
+   vtab->RegFrame = RegFrame;
+   vtab->RegDummyFS = RegDummyFS;
+   vtab->RegMapping = RegMapping;
+   vtab->RegPins = RegPins;
+   vtab->RegTransform = RegTransform;
+   vtab->BTransform = BTransform;
+   vtab->GetRegFS = GetRegFS;
+   vtab->SetRegFS = SetRegFS;
+   vtab->MaskB = MaskB;
+   vtab->MaskD = MaskD;
+   vtab->MaskF = MaskF;
+   vtab->MaskI = MaskI;
+   vtab->MaskL = MaskL;
+   vtab->MaskS = MaskS;
+   vtab->MaskUB = MaskUB;
+   vtab->MaskUI = MaskUI;
+   vtab->MaskUL = MaskUL;
+   vtab->MaskUS = MaskUS;
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+   vtab->MaskLD = MaskLD;
+#endif
+
+/* Save the inherited pointers to methods that will be extended, and store
+   replacement pointers for methods which will be over-ridden by new member
+   functions implemented here. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   frame = (AstFrameVtab *) vtab;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_getusedefs = object->GetUseDefs;
+   object->GetUseDefs = GetUseDefs;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   object->Cast = Cast;
+   object->Equal = Equal;
+   object->ClearAttrib = ClearAttrib;
+   object->GetAttrib = GetAttrib;
+   object->SetAttrib = SetAttrib;
+   object->TestAttrib = TestAttrib;
+
+   mapping->ReportPoints = ReportPoints;
+   mapping->RemoveRegions = RemoveRegions;
+   mapping->Simplify = Simplify;
+
+   frame->Abbrev = Abbrev;
+   frame->Angle = Angle;
+   frame->AxAngle = AxAngle;
+   frame->AxDistance = AxDistance;
+   frame->AxNorm = AxNorm;
+   frame->AxOffset = AxOffset;
+   frame->CheckPerm = CheckPerm;
+   frame->ClearDigits = ClearDigits;
+   frame->ClearDirection = ClearDirection;
+   frame->ClearDomain = ClearDomain;
+   frame->ClearFormat = ClearFormat;
+   frame->ClearLabel = ClearLabel;
+   frame->ClearMatchEnd = ClearMatchEnd;
+   frame->ClearMaxAxes = ClearMaxAxes;
+   frame->ClearMinAxes = ClearMinAxes;
+   frame->ClearPermute = ClearPermute;
+   frame->ClearPreserveAxes = ClearPreserveAxes;
+   frame->ClearSymbol = ClearSymbol;
+   frame->ClearTitle = ClearTitle;
+   frame->ClearUnit = ClearUnit;
+   frame->Convert = Convert;
+   frame->ConvertX = ConvertX;
+   frame->Distance = Distance;
+   frame->FindFrame = FindFrame;
+   frame->Format = Format;
+   frame->Centre = Centre;
+   frame->Gap = Gap;
+   frame->GetAxis = GetAxis;
+   frame->GetDigits = GetDigits;
+   frame->GetDirection = GetDirection;
+   frame->GetDomain = GetDomain;
+   frame->GetFormat = GetFormat;
+   frame->GetLabel = GetLabel;
+   frame->GetMatchEnd = GetMatchEnd;
+   frame->GetMaxAxes = GetMaxAxes;
+   frame->GetMinAxes = GetMinAxes;
+   frame->GetNaxes = GetNaxes;
+   frame->GetPerm = GetPerm;
+   frame->GetPermute = GetPermute;
+   frame->GetPreserveAxes = GetPreserveAxes;
+   frame->GetSymbol = GetSymbol;
+   frame->GetTitle = GetTitle;
+   frame->GetUnit = GetUnit;
+   frame->Intersect = Intersect;
+   frame->IsUnitFrame = IsUnitFrame;
+   frame->Match = Match;
+   frame->Norm = Norm;
+   frame->NormBox = NormBox;
+   frame->Offset = Offset;
+   frame->Offset2 = Offset2;
+   frame->Overlay = Overlay;
+   frame->PermAxes = PermAxes;
+   frame->PickAxes = PickAxes;
+   frame->Resolve = Resolve;
+   frame->ResolvePoints = ResolvePoints;
+   frame->SetAxis = SetAxis;
+   frame->SetDigits = SetDigits;
+   frame->SetDirection = SetDirection;
+   frame->SetDomain = SetDomain;
+   frame->SetFormat = SetFormat;
+   frame->SetLabel = SetLabel;
+   frame->SetMatchEnd = SetMatchEnd;
+   frame->SetMaxAxes = SetMaxAxes;
+   frame->SetMinAxes = SetMinAxes;
+   frame->SetPermute = SetPermute;
+   frame->SetPreserveAxes = SetPreserveAxes;
+   frame->SetSymbol = SetSymbol;
+   frame->SetTitle = SetTitle;
+   frame->SetUnit = SetUnit;
+   frame->SubFrame = SubFrame;
+   frame->SystemCode = SystemCode;
+   frame->SystemString = SystemString;
+   frame->TestDigits = TestDigits;
+   frame->TestDirection = TestDirection;
+   frame->TestDomain = TestDomain;
+   frame->TestFormat = TestFormat;
+   frame->TestLabel = TestLabel;
+   frame->TestMatchEnd = TestMatchEnd;
+   frame->TestMaxAxes = TestMaxAxes;
+   frame->TestMinAxes = TestMinAxes;
+   frame->TestPermute = TestPermute;
+   frame->TestPreserveAxes = TestPreserveAxes;
+   frame->TestSymbol = TestSymbol;
+   frame->TestTitle = TestTitle;
+   frame->TestUnit = TestUnit;
+   frame->Unformat = Unformat;
+   frame->ValidateAxis = ValidateAxis;
+   frame->ValidateAxisSelection = ValidateAxisSelection;
+   frame->ValidateSystem = ValidateSystem;
+   frame->LineDef = LineDef;
+   frame->LineContains = LineContains;
+   frame->LineCrossing = LineCrossing;
+   frame->LineOffset = LineOffset;
+   frame->MatchAxes = MatchAxes;
+   frame->MatchAxesX = MatchAxesX;
+
+   frame->GetActiveUnit = GetActiveUnit;
+   frame->SetActiveUnit = SetActiveUnit;
+   frame->TestActiveUnit = TestActiveUnit;
+
+   frame->GetTop = GetTop;
+   frame->SetTop = SetTop;
+   frame->TestTop = TestTop;
+   frame->ClearTop = ClearTop;
+
+   frame->GetBottom = GetBottom;
+   frame->SetBottom = SetBottom;
+   frame->TestBottom = TestBottom;
+   frame->ClearBottom = ClearBottom;
+
+   frame->GetEpoch = GetEpoch;
+   frame->SetEpoch = SetEpoch;
+   frame->TestEpoch = TestEpoch;
+   frame->ClearEpoch = ClearEpoch;
+
+   frame->ClearObsAlt = ClearObsAlt;
+   frame->TestObsAlt = TestObsAlt;
+   frame->GetObsAlt = GetObsAlt;
+   frame->SetObsAlt = SetObsAlt;
+
+   frame->ClearObsLat = ClearObsLat;
+   frame->TestObsLat = TestObsLat;
+   frame->GetObsLat = GetObsLat;
+   frame->SetObsLat = SetObsLat;
+
+   frame->ClearObsLon = ClearObsLon;
+   frame->TestObsLon = TestObsLon;
+   frame->GetObsLon = GetObsLon;
+   frame->SetObsLon = SetObsLon;
+
+   frame->GetSystem = GetSystem;
+   frame->SetSystem = SetSystem;
+   frame->TestSystem = TestSystem;
+   frame->ClearSystem = ClearSystem;
+
+   frame->GetAlignSystem = GetAlignSystem;
+   frame->SetAlignSystem = SetAlignSystem;
+   frame->TestAlignSystem = TestAlignSystem;
+   frame->ClearAlignSystem = ClearAlignSystem;
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "Region",
+               "An area within a coordinate system" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void Intersect( AstFrame *this_frame, const double a1[2],
+                       const double a2[2], const double b1[2],
+                       const double b2[2], double cross[2],
+                       int *status ) {
+/*
+*  Name:
+*     Intersect
+
+*  Purpose:
+*     Find the point of intersection between two geodesic curves.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void Intersect( AstFrame *this_frame, const double a1[2],
+*                      const double a2[2], const double b1[2],
+*                      const double b2[2], double cross[2],
+*                      int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astIntersect method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function finds the coordinate values at the point of
+*     intersection between two geodesic curves. Each curve is specified
+*     by two points on the curve.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     a1
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a point on the first
+*        geodesic curve.
+*     a2
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a second point on the
+*        first geodesic curve.
+*     b1
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a point on the second
+*        geodesic curve.
+*     b2
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a second point on
+*        the second geodesic curve.
+*     cross
+*        An array of double, with one element for each Frame axis
+*        in which the coordinates of the required intersection
+*        point will be returned. These will be AST__BAD if the curves do
+*        not intersect.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*     - For SkyFrames each curve will be a great circle, and in general
+*     each pair of curves will intersect at two diametrically opposite
+*     points on the sky. The returned position is the one which is
+*     closest to point "a1".
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke the
+   astIntersect method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   astIntersect( fr, a1, a2, b1, b2, cross );
+   fr = astAnnul( fr );
+}
+
+static int IsUnitFrame( AstFrame *this, int *status ){
+/*
+*  Name:
+*     IsUnitFrame
+
+*  Purpose:
+*     Is this Frame equivalent to a UnitMap?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int IsUnitFrame( AstFrame *this, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astIsUnitFrame
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied Frame is
+*     equivalent to a UnitMap when treated as a Mapping (note, the Frame
+*     class inherits from Mapping and therefore every Frame is also a Mapping).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the supplied Frame is equivalent to
+*     a UnitMap when treated as a Mapping.
+
+*-
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return 0;
+
+/* The Region class is never equivalent to a UnitMap. */
+   return 0;
+}
+
+static int LineContains( AstFrame *this_frame, AstLineDef *l, int def, double *point, int *status ) {
+/*
+*  Name:
+*     LineContains
+
+*  Purpose:
+*     Determine if a line contains a point.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int LineContains( AstFrame *this, AstLineDef *l, int def, double *point, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astLineContains
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function determines if the supplied point is on the supplied
+*     line within the supplied Frame. The start point of the line is
+*     considered to be within the line, but the end point is not. The tests
+*     are that the point of closest approach of the line to the point should
+*     be between the start and end, and that the distance from the point to
+*     the point of closest aproach should be less than 1.0E-7 of the length
+*     of the line.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     l
+*        Pointer to the structure defining the line.
+*     def
+*        Should be set non-zero if the "point" array was created by a
+*        call to astLineCrossing (in which case it may contain extra
+*        information following the axis values),and zero otherwise.
+*     point
+*        Point to an array containing the axis values of the point to be
+*        tested, possibly followed by extra cached information (see "def").
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the line contains the point.
+
+*  Notes:
+*     - The pointer supplied for "l" should have been created using the
+*     astLineDef method. These structures contained cached information about
+*     the lines which improve the efficiency of this method when many
+*     repeated calls are made. An error will be reported if the structure
+*     does not refer to the Frame specified by "this".
+*     - Zero will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   int result;                   /* Returned value */
+
+/* Initialise */
+   result =0;
+
+/* Obtain a pointer to the Region's current Frame and then invoke the
+   method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( ((AstRegion *) this_frame)->frameset, AST__CURRENT );
+   result = astLineContains( fr, l, def, point );
+   fr = astAnnul( fr );
+
+/* Return the result. */
+   return result;
+}
+
+static int LineCrossing( AstFrame *this_frame, AstLineDef *l1, AstLineDef *l2,
+                         double **cross, int *status ) {
+/*
+*  Name:
+*     LineCrossing
+
+*  Purpose:
+*     Determine if two lines cross.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int LineCrossing( AstFrame *this, AstLineDef *l1, AstLineDef *l2,
+*                       double **cross, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astLineCrossing
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function determines if the two suplied line segments cross,
+*     and if so returns the axis values at the point where they cross.
+*     A flag is also returned indicating if the crossing point occurs
+*     within the length of both line segments, or outside one or both of
+*     the line segments.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     l1
+*        Pointer to the structure defining the first line.
+*     l2
+*        Pointer to the structure defining the second line.
+*     cross
+*        Pointer to a location at which to put a pointer to a dynamically
+*        alocated array containing the axis values at the crossing. If
+*        NULL is supplied no such array is returned. Otherwise, the returned
+*        array should be freed using astFree when no longer needed. If the
+*        lines are parallel (i.e. do not cross) then AST__BAD is returned for
+*        all axis values. Note usable axis values are returned even if the
+*        lines cross outside the segment defined by the start and end points
+*        of the lines. The order of axes in the returned array will take
+*        account of the current axis permutation array if appropriate. Note,
+*        sub-classes such as SkyFrame may append extra values to the end
+*        of the basic frame axis values. A NULL pointer is returned if an
+*        error occurs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the lines cross at a point which is
+*     within the [start,end) segment of both lines. If the crossing point
+*     is outside this segment on either line, or if the lines are parallel,
+*     zero is returned. Note, the start point is considered to be inside
+*     the length of the segment, but the end point is outside.
+
+*  Notes:
+*     - The pointers supplied for "l1" and "l2" should have been created
+*     using the astLineDef method. These structures contained cached
+*     information about the lines which improve the efficiency of this method
+*     when many repeated calls are made. An error will be reported if
+*     either structure does not refer to the Frame specified by "this".
+*     - Zero will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   int result;                   /* Returned value */
+
+/* Initialise */
+   result =0;
+
+/* Obtain a pointer to the Region's current Frame and then invoke the
+   method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( ((AstRegion *) this_frame)->frameset, AST__CURRENT );
+   result = astLineCrossing( fr, l1, l2, cross );
+   fr = astAnnul( fr );
+
+/* Return the result. */
+   return result;
+}
+
+static AstLineDef *LineDef( AstFrame *this_frame, const double start[2],
+                            const double end[2], int *status ) {
+/*
+*  Name:
+*     LineDef
+
+*  Purpose:
+*     Creates a structure describing a line segment in a 2D Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstLineDef *LineDef( AstFrame *this, const double start[2],
+*                             const double end[2], int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astLineDef
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function creates a structure containing information describing a
+*     given line segment within the supplied 2D Frame. This may include
+*     information which allows other methods such as astLineCrossing to
+*     function more efficiently. Thus the returned structure acts as a
+*     cache to store intermediate values used by these other methods.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame. Must have 2 axes.
+*     start
+*        An array of 2 doubles marking the start of the line segment.
+*     end
+*        An array of 2 doubles marking the end of the line segment.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the memory structure containing the description of the
+*     line. This structure should be freed using astFree when no longer
+*     needed. A NULL pointer is returned (without error) if any of the
+*     supplied axis values are AST__BAD.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstLineDef *result;           /* Returned value */
+
+/* Initialise */
+   result = NULL;
+
+/* Obtain a pointer to the Region's current Frame and then invoke the
+   method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( ((AstRegion *) this_frame)->frameset, AST__CURRENT );
+   result = astLineDef( fr, start, end );
+   fr = astAnnul( fr );
+
+/* Return the result. */
+   return result;
+}
+
+static void LineOffset( AstFrame *this_frame, AstLineDef *line, double par,
+                        double prp, double point[2], int *status ){
+/*
+*  Name:
+*     LineOffset
+
+*  Purpose:
+*     Find a position close to a line.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void LineOffset( AstFrame *this, AstLineDef *line, double par,
+*                      double prp, double point[2], int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astLineOffset
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a position formed by moving a given distance along
+*     the supplied line, and then a given distance away from the supplied line.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     line
+*        Pointer to the structure defining the line.
+*     par
+*        The distance to move along the line from the start towards the end.
+*     prp
+*        The distance to move at right angles to the line. Positive
+*        values result in movement to the left of the line, as seen from
+*        the observer, when moving from start towards the end.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The pointer supplied for "line" should have been created using the
+*     astLineDef method. This structure contains cached information about the
+*     line which improves the efficiency of this method when many repeated
+*     calls are made. An error will be reported if the structure does not
+*     refer to the Frame specified by "this".
+*/
+
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+
+/* Obtain a pointer to the Region's current Frame and then invoke the
+   method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( ((AstRegion *) this_frame)->frameset, AST__CURRENT );
+   astLineOffset( fr, line, par, prp, point );
+   fr = astAnnul( fr );
+}
+
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstRegion *this;       /* Pointer to Region structure */
+   int result;            /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the Region structure. */
+   this = (AstRegion *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->frameset, mode, extra, fail );
+   if( !result ) result = astManageLock( this->points, mode, extra, fail );
+   if( !result ) result = astManageLock( this->unc, mode, extra, fail );
+   if( !result ) result = astManageLock( this->negation, mode, extra, fail );
+   if( !result ) result = astManageLock( this->defunc, mode, extra, fail );
+   if( !result ) result = astManageLock( this->basemesh, mode, extra, fail );
+   if( !result ) result = astManageLock( this->basegrid, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static AstRegion *MapRegion( AstRegion *this, AstMapping *map0,
+                             AstFrame *frame0, int *status ) {
+/*
+*+
+*  Name:
+*     astMapRegion
+
+*  Purpose:
+*     Transform a Region into a new Frame using a given Mapping.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstRegion *astMapRegion( AstRegion *this, AstMapping *map,
+*                              AstFrame *frame )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     This function returns a pointer to a new Region which corresponds to
+*     supplied Region in some other specified coordinate system. A
+*     Mapping is supplied which transforms positions between the old and new
+*     coordinate systems. The new Region may not be of the same class as
+*     the original region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     map
+*        Pointer to a Mapping which transforms positions from the
+*        coordinate system represented by the supplied Region to the
+*        coordinate system specified by "frame". The supplied Mapping should
+*        define both forward and inverse transformations, and these
+*        transformations should form a genuine inverse pair. That is,
+*        transforming a position using the forward transformation and then
+*        using the inverse transformation should produce the original input
+*        position. Some Mapping classes (such as PermMap, MathMap, SphMap)
+*        can result in Mappings for which this is not true.
+*     frame
+*        Pointer to a Frame describing the coordinate system in which
+*        the new Region is required.
+
+*  Returned Value:
+*     astMapRegion()
+*        A pointer to a new Region. This Region will represent the area
+*        within the coordinate system specified by "frame" which corresponds
+*        to the supplied Region.
+
+*  Notes:
+*     - This is the protected implementation of this function - it does
+*     not simplify the returned Region. The public implementation is
+*     astMapRegionID, which simplifies the returned Region.
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *frame;
+   AstFrameSet *fs;
+   AstMapping *tmap;
+   AstMapping *usemap;
+   AstMapping *map;
+   AstPointSet *ps1;
+   AstPointSet *pst;
+   AstPointSet *ps2;
+   AstRegion *usethis;
+   AstRegion *result;
+   double **ptr1;
+   double **ptr2;
+   int *axflags;
+   int *inax;
+   int *keep;
+   int *outax;
+   int i;
+   int icurr;
+   int j;
+   int nax1;
+   int nkept;
+   int nnew;
+   int nold;
+   int np;
+   int ntotal;
+   int ok;
+
+/* Initialise returned value. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise local variables */
+   axflags = NULL;
+
+/* If a FrameSet was supplied for the Mapping, use the base->current
+   Mapping */
+   if( astIsAFrameSet( map0 ) ) {
+      map = astGetMapping( (AstFrameSet *) map0, AST__BASE, AST__CURRENT );
+   } else {
+      map = astClone( map0 );
+   }
+
+/* If a FrameSet was supplied for the Frame, use the current Frame. */
+   if( astIsAFrameSet( frame0 ) ) {
+      frame = astGetFrame( (AstFrameSet *) frame0, AST__CURRENT );
+   } else {
+      frame = astClone( frame0 );
+   }
+
+/* First check the Mapping is suitable. It must defined both a forward
+   and an inverse Mapping. */
+   if( !astGetTranInverse( map ) ) {
+      astError( AST__NODEF, "astMapRegion(%s): The supplied %s does not "
+                "define an inverse transformation.", status, astGetClass( this ),
+                astGetClass( map ) );
+   } else if( !astGetTranForward( map ) ) {
+      astError( AST__NODEF, "astMapRegion(%s): The supplied %s does not "
+                "define a forward transformation.", status, astGetClass( this ),
+                astGetClass( map ) );
+   }
+
+
+/* Get the number of axes in the supplied Region. */
+   nold = astGetNaxes( this );
+
+/* Get the number of axes in the returned Region. */
+   nnew = astGetNaxes( frame );
+
+/* The forward transformation must not introduce any bad axis values. We
+   can only perform this test reliably if the supplied Region has no bad
+   axis values. */
+   ps1 = this->points;
+   if( ps1 ) {
+      nax1 = astGetNcoord( ps1 );
+      np = astGetNpoint( ps1 );
+      ptr1 = astGetPoints( ps1 );
+      if( ptr1 ) {
+
+/* Check the axis values defining the Region are good. */
+         ok = 1;
+         for( i = 0; i < nax1 && ok; i++ ){
+            for( j = 0; j < np; j++ ) {
+               if( ptr1[ i ][ j ] == AST__BAD ){
+                  ok = 0;
+                  break;
+               }
+            }
+         }
+         if( ok ) {
+
+/* Transform the points defining the supplied Region into the current Frame
+   of the Region. */
+            pst = astRegTransform( this, ps1, 1, NULL, NULL );
+
+/* The use the supplied Mapping to transfom them into the new Frame. */
+            ps2 = astTransform( map, pst, 1, NULL );
+
+/* Test if any of these axis values are bad. */
+            ptr2 = astGetPoints( ps2 );
+            if( ptr2 ) {
+               for( i = 0; i < nnew && ok; i++ ){
+                  for( j = 0; j < np; j++ ) {
+                     if( ptr2[ i ][ j ] == AST__BAD ){
+                        ok = 0;
+                        break;
+                     }
+                  }
+               }
+               if( !ok ) {
+                  astError( AST__NODEF, "astMapRegion(%s): The region which "
+                            "results from using the supplied %s to transform "
+                            "the supplied %s is undefined.", status, astGetClass( this ),
+                            astGetClass( map ), astGetClass( this ) );
+
+/* If all axis values are good, use the inverse transformation of the
+   supplied Mapping to transform them back to the Frame of the supplied
+   Region. */
+               } else {
+                  pst = astAnnul( pst );
+                  pst = astTransform( map, ps2, 0, NULL );
+
+/* Get a flag for each input of the supplied Mapping (i.e. each axis of
+   the supplied Region) which is non-zero if the inverse transformation
+   of the Mapping has produced any good axis values. */
+                  ptr1 = astGetPoints( pst );
+                  axflags = astCalloc( nold, sizeof(int) );
+                  if( astOK ) {
+                     for( i = 0; i < nold; i++ ){
+                        for( j = 0; j < np; j++ ) {
+                           if( ptr1[ i ][ j ] != AST__BAD ){
+                              axflags[ i ] = 1;
+                              break;
+                           }
+                        }
+                     }
+                  }
+               }
+            }
+            ps2 = astAnnul( ps2 );
+            pst = astAnnul( pst );
+         }
+      }
+   }
+
+/* Assume we will be using the supplied Region (this) and Mapping (map). */
+   usethis = astClone( this );
+   usemap = astClone( map );
+
+/* If the new Frame for the Region has fewer axes than the old Frame, see
+   if we can discard some base-frame axes. We only do this if the inverse
+   transformation would otherwise supply bad values for the unused axes.
+   Using bad axis values is not a good idea as some operations cannot be
+   performed if any bad values are supplied. Also having more axes than
+   needed is bad as it results in fewer mesh points per axis. */
+   if( nnew < nold ) {
+
+/* First invert the Mapping since astMapSplit only allows selection of
+   inputs, and we want to select outputs. */
+      astInvert( map );
+
+/* Create an array holding the indices of the required inputs. */
+      inax = astMalloc( nnew*sizeof(int) );
+      if( astOK ) for( i = 0; i < nnew; i++ ) inax[i] = i;
+
+/* Attempt to split the mapping to extract a new mapping that omits any
+   unnecessary outputs (i.e. outputs that are indepenent of the selected
+   inputs). Check the Mapping was split successfully. */
+      outax = astMapSplit( map, nnew, inax, &tmap );
+      if( outax ) {
+
+/* Get the number of old axes that have been retained in the Mapping. */
+         nkept = astGetNout( tmap );
+
+/* Now we need to ensure that any unused axes for which the Mapping
+   creates non-bad values are retained (such values are significant
+   since they may determine whether the new Region is null or not).
+   We only need to do this if any of the outputs that were split off
+   above have been found to generate good values, as indicated by the
+   "axflags" array. Count the number of extra axes that need to be kept,
+   over and above those that are kept by the Mapping returned by
+   astMapSplit above. */
+         ntotal = 0;
+         keep = NULL;
+         if( axflags ) {
+            keep = astMalloc( nold*sizeof(int) );
+            if( astOK ) {
+
+/* Loop round each axis in the supplied Region. */
+               for( i = 0; i < nold; i++ ) {
+
+/* Search the "outax" array to see if this axis was retained by astMapSplit.
+   If it was, append its index to the total list of axes to keep. */
+                  ok = 0;
+                  for( j = 0; j < nkept; j++ ) {
+                     if( outax[ j ] == i ) {
+                        keep[ ntotal++ ] = i;
+                        ok = 1;
+                        break;
+                     }
+                  }
+
+/* If the axis was not retained by astMapSplit, but generates good axis
+   values, also append its index to the total list of axes to keep. */
+                  if( !ok && axflags[ i ] ) {
+                     keep[ ntotal++ ] = i;
+                  }
+               }
+            }
+         }
+
+/* If there are no extra axes to keep, then the Mapping returned by
+   astMapSplit above can be used as it is. */
+         if( ntotal == nkept ) {
+
+/* The values in the "outax" array will hold the zero-based indices of
+   the original old axes that are retained by the new Mapping. We need to
+   create a copy of the supplied Region that includes only these axes. */
+            usethis = astAnnul( usethis );
+            usethis = astPickAxes( this, nkept, outax, NULL );
+
+/* Use the temportary Mapping returned by astMapSplit in place of the
+   supplied Mapping (remember to invert to undo the inversion performed
+   above). */
+            usemap = astAnnul( usemap );
+            usemap = astClone( tmap );
+            astInvert( usemap );
+
+/* Free temporary resources. */
+            tmap = astAnnul( tmap );
+            outax = astFree( outax );
+
+/* If we need to retain some extra axes because they generate good values
+   (even though they are independent of the new Frame axes)... */
+         } else if( ntotal > nkept ){
+
+/* We know the old Frame axes that we want to keep, so use astMapSplit
+   in the opposite direction - i.e. use it on the Mapping form old to
+   new.  */
+            astInvert( map );
+
+            tmap = astAnnul( tmap );
+            outax = astFree( outax );
+
+            outax = astMapSplit( map, ntotal, keep, &tmap );
+            if( outax ) {
+
+               usethis = astAnnul( usethis );
+               usethis = astPickAxes( this, ntotal, keep, NULL );
+
+               usemap = astAnnul( usemap );
+               usemap = astClone( tmap );
+
+               tmap = astAnnul( tmap );
+               outax = astFree( outax );
+
+            }
+
+            astInvert( map );
+         }
+         keep = astFree( keep );
+      }
+      inax = astFree( inax );
+
+/* Invert the Mapping again to bring it back to its original state. */
+      astInvert( map );
+   }
+
+/* Take a deep copy of the supplied Region. */
+   result = astCopy( usethis );
+
+/* Get a pointer to the encapsulated FrameSet. */
+   if( astOK ) {
+      fs = result->frameset;
+
+/* Add in the new Frame and Mapping. First note the index of the original
+   current Frame. */
+      icurr = astGetCurrent( fs );
+      astAddFrame( fs, AST__CURRENT, usemap, frame );
+
+/* Remove the original current Frame. */
+      astRemoveFrame( fs, icurr );
+
+/* The base and current Frames of the resulting FrameSet are now (in
+   general) different and so the Region should include its FrameSet in any
+   Dump. */
+      astSetRegionFS( result, 1 );
+   }
+
+/* Since the Mapping has been changed, any cached information calculated
+   on the basis of the Mapping properties may no longer be up to date. */
+   astResetCache( result );
+
+/* Free resources */
+   usemap = astAnnul( usemap );
+   usethis = astAnnul( usethis );
+   map = astAnnul( map );
+   frame = astAnnul( frame );
+   axflags = astFree( axflags );
+
+/* If not OK, annul the returned pointer. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+/*
+*++
+*  Name:
+c     astMask<X>
+f     AST_MASK<X>
+
+*  Purpose:
+*     Mask a region of a data grid.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     int astMask<X>( AstRegion *this, AstMapping *map, int inside, int ndim,
+c                     const int lbnd[], const int ubnd[], <Xtype> in[],
+c                     <Xtype> val )
+f     RESULT = AST_MASK<X>( THIS, MAP, INSIDE, NDIM, LBND, UBND, IN, VAL,
+f                           STATUS )
+
+*  Class Membership:
+*     Mapping method.
+
+*  Description:
+*     This is a set of functions for masking out regions within gridded data
+*     (e.g. an image). The functions modifies a given data grid by
+*     assigning a specified value to all samples which are inside (or outside
+c     if "inside" is zero)
+f     if INSIDE is .FALSE.)
+*     the specified Region.
+*
+*     You should use a masking function which matches the numerical
+*     type of the data you are processing by replacing <X> in
+c     the generic function name astMask<X> by an appropriate 1- or
+f     the generic function name AST_MASK<X> by an appropriate 1- or
+*     2-character type code. For example, if you are masking data
+c     with type "float", you should use the function astMaskF (see
+f     with type REAL, you should use the function AST_MASKR (see
+*     the "Data Type Codes" section below for the codes appropriate to
+*     other numerical types).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to a Region.
+c     map
+f     MAP = INTEGER (Given)
+*        Pointer to a Mapping. The forward transformation should map
+*        positions in the coordinate system of the supplied Region
+*        into pixel coordinates as defined by the
+c        "lbnd" and "ubnd" parameters. A NULL pointer
+f        LBND and UBND arguments. A value of AST__NULL
+*        can be supplied if the coordinate system of the supplied Region
+*        corresponds to pixel coordinates. This is equivalent to
+*        supplying a UnitMap.
+*
+*        The number of inputs for this Mapping (as given by its Nin attribute)
+*        should match the number of axes in the supplied Region (as given
+*        by the Naxes attribute of the Region).
+*        The number of outputs for the Mapping (as given by its Nout attribute)
+*        should match the number of
+c        grid dimensions given by the value of "ndim"
+f        grid dimensions given by the value of NDIM
+*        below.
+c     inside
+f     INSIDE = INTEGER (Given)
+*        A boolean value which indicates which pixel are to be masked. If
+c        a non-zero value
+f        .TRUE.
+*        is supplied, then all grid pixels with centres inside the supplied
+*        Region are assigned the value given by
+c        "val",
+f        VAL,
+*        and all other pixels are left unchanged. If
+c        zero
+f        .FALSE.
+*        is supplied, then all grid pixels with centres not inside the supplied
+*        Region are assigned the value given by
+c        "val",
+f        VAL,
+*        and all other pixels are left unchanged. Note, the Negated
+*        attribute of the Region is used to determine which pixel are
+*        inside the Region and which are outside. So the inside of a Region
+*        which has not been negated is the same as the outside of the
+*        corresponding negated Region.
+*
+*        For types of Region such as PointList which have zero volume,
+*        pixel centres will rarely fall exactly within the Region. For
+*        this reason, the inclusion criterion is changed for zero-volume
+*        Regions so that pixels are included (or excluded) if any part of
+*        the Region passes through the pixel. For a PointList, this means
+*        that pixels are included (or excluded) if they contain at least
+*        one of the points listed in the PointList.
+c     ndim
+f     NDIM = INTEGER (Given)
+*        The number of dimensions in the input grid. This should be at
+*        least one.
+c     lbnd
+f     LBND( NDIM ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim" elements,
+f        An array
+*        containing the coordinates of the centre of the first pixel
+*        in the input grid along each dimension.
+c     ubnd
+f     UBND( NDIM ) = INTEGER (Given)
+c        Pointer to an array of integers, with "ndim" elements,
+f        An array
+*        containing the coordinates of the centre of the last pixel in
+*        the input grid along each dimension.
+*
+c        Note that "lbnd" and "ubnd" together define the shape
+f        Note that LBND and UBND together define the shape
+*        and size of the input grid, its extent along a particular
+c        (j'th) dimension being ubnd[j]-lbnd[j]+1 (assuming the
+c        index "j" to be zero-based). They also define
+f        (J'th) dimension being UBND(J)-LBND(J)+1. They also define
+*        the input grid's coordinate system, each pixel having unit
+*        extent along each dimension with integral coordinate values
+*        at its centre.
+c     in
+f     IN( * ) = <Xtype> (Given and Returned)
+c        Pointer to an array, with one element for each pixel in the
+f        An array, with one element for each pixel in the
+*        input grid, containing the data to be masked.  The
+*        numerical type of this array should match the 1- or
+*        2-character type code appended to the function name (e.g. if
+c        you are using astMaskF, the type of each array element
+c        should be "float").
+f        you are using AST_MASKR, the type of each array element
+f        should be REAL).
+*
+*        The storage order of data within this array should be such
+*        that the index of the first grid dimension varies most
+*        rapidly and that of the final dimension least rapidly
+c        (i.e. Fortran array indexing is used).
+f        (i.e. normal Fortran array storage order).
+*
+*        On exit, the samples specified by
+c        "inside" are set to the value of "val".
+f        INSIDE are set to the value of VAL.
+*        All other samples are left unchanged.
+c     val
+f     VAL = <Xtype> (Given)
+*        This argument should have the same type as the elements of
+c        the "in" array. It specifies the value used to flag the
+f        the IN array. It specifies the value used to flag the
+*        masked data (see
+c        "inside").
+f        INSIDE).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMask<X>()
+f     AST_MASK<X> = INTEGER
+*        The number of pixels to which a value of
+c        "badval"
+f        BADVAL
+*        has been assigned.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*     - An error will be reported if the overlap of the Region and
+*     the array cannot be determined.
+
+*  Data Type Codes:
+*     To select the appropriate masking function, you should
+c     replace <X> in the generic function name astMask<X> with a
+f     replace <X> in the generic function name AST_MASK<X> with a
+*     1- or 2-character data type code, so as to match the numerical
+*     type <Xtype> of the data you are processing, as follows:
+c     - D: double
+c     - F: float
+c     - L: long int
+c     - UL: unsigned long int
+c     - I: int
+c     - UI: unsigned int
+c     - S: short int
+c     - US: unsigned short int
+c     - B: byte (signed char)
+c     - UB: unsigned byte (unsigned char)
+f     - D: DOUBLE PRECISION
+f     - R: REAL
+f     - I: INTEGER
+f     - UI: INTEGER (treated as unsigned)
+f     - S: INTEGER*2 (short integer)
+f     - US: INTEGER*2 (short integer, treated as unsigned)
+f     - B: BYTE (treated as signed)
+f     - UB: BYTE (treated as unsigned)
+*
+c     For example, astMaskD would be used to process "double"
+c     data, while astMaskS would be used to process "short int"
+c     data, etc.
+f     For example, AST_MASKD would be used to process DOUBLE
+f     PRECISION data, while AST_MASKS would be used to process
+f     short integer data (stored in an INTEGER*2 array), etc.
+f
+f     For compatibility with other Starlink facilities, the codes W
+f     and UW are provided as synonyms for S and US respectively (but
+f     only in the Fortran interface to AST).
+
+*--
+*/
+/* Define a macro to implement the function for a specific data
+   type. */
+#define MAKE_MASK(X,Xtype) \
+static int Mask##X( AstRegion *this, AstMapping *map, int inside, int ndim, \
+                    const int lbnd[], const int ubnd[], \
+                    Xtype in[], Xtype val, int *status ) { \
+\
+/* Local Variables: */ \
+   AstFrame *grid_frame;         /* Pointer to Frame describing grid coords */ \
+   AstRegion *used_region;       /* Pointer to Region to be used by astResample */ \
+   Xtype *c;                     /* Pointer to next array element */ \
+   Xtype *d;                     /* Pointer to next array element */ \
+   Xtype *out;                   /* Pointer to the array used for resample output */ \
+   Xtype *tmp_out;               /* Pointer to temporary output array */ \
+   double *lbndgd;               /* Pointer to array holding lower grid bounds */ \
+   double *ubndgd;               /* Pointer to array holding upper grid bounds */ \
+   int *lbndg;                   /* Pointer to array holding lower grid bounds */ \
+   int *ubndg;                   /* Pointer to array holding upper grid bounds */ \
+   int idim;                     /* Loop counter for coordinate dimensions */ \
+   int ipix;                     /* Loop counter for pixel index */ \
+   int nax;                      /* Number of Region axes */ \
+   int nin;                      /* Number of Mapping input coordinates */ \
+   int nout;                     /* Number of Mapping output coordinates */ \
+   int npix;                     /* Number of pixels in supplied array */ \
+   int npixg;                    /* Number of pixels in bounding box */ \
+   int result;                   /* Result value to return */ \
+\
+/* Initialise. */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Obtain value for the Naxes attribute of the Region. */ \
+   nax = astGetNaxes( this ); \
+\
+/* If supplied, obtain values for the Nin and Nout attributes of the Mapping. */ \
+   if( map ) { \
+      nin = astGetNin( map ); \
+      nout = astGetNout( map ); \
+\
+/* If OK, check that the number of mapping inputs matches the \
+   number of axes in the Region. Report an error if necessary. */ \
+      if ( astOK && ( nax != nin ) ) { \
+         astError( AST__NGDIN, "astMask"#X"(%s): Bad number of mapping " \
+                   "inputs (%d).", status, astGetClass( this ), nin ); \
+         astError( AST__NGDIN, "The %s given requires %d coordinate value%s " \
+                   "to specify a position.", status, \
+                   astGetClass( this ), nax, ( nax == 1 ) ? "" : "s" ); \
+      } \
+\
+/* If OK, check that the number of mapping outputs matches the \
+   number of grid dimensions. Report an error if necessary. */ \
+      if ( astOK && ( ndim != nout ) ) { \
+         astError( AST__NGDIN, "astMask"#X"(%s): Bad number of mapping " \
+                   "outputs (%d).", status, astGetClass( this ), nout ); \
+         astError( AST__NGDIN, "The pixel grid requires %d coordinate value%s " \
+                   "to specify a position.", status, \
+                   ndim, ( ndim == 1 ) ? "" : "s" ); \
+      } \
+\
+/* Create a new Region by mapping the supplied Region with the supplied \
+   Mapping. The resulting Region represents a region in grid coordinates. */ \
+      grid_frame = astFrame( ndim, "Domain=grid", status ); \
+      used_region = astMapRegion( this, map, grid_frame ); \
+      grid_frame = astAnnul( grid_frame ); \
+\
+/* If no Mapping was supplied check that the number of grid dimensions \
+   matches the number of axes in the Region.*/ \
+   } else if ( astOK && ( ( ndim != nax ) || ( ndim < 1 ) ) ) { \
+      used_region = NULL; \
+      astError( AST__NGDIN, "astMask"#X"(%s): Bad number of input grid " \
+                "dimensions (%d).", status, astGetClass( this ), ndim ); \
+      if ( ndim != nax ) { \
+         astError( AST__NGDIN, "The %s given requires %d coordinate value%s " \
+                   "to specify an input position.", status, \
+                   astGetClass( this ), nax, ( nax == 1 ) ? "" : "s" ); \
+      } \
+\
+/* If no Mapping was supplied and the parameters look OK, clone the \
+   supplied Region pointer for use later on. */ \
+   } else { \
+      used_region = astClone( this ); \
+   } \
+\
+/* Check that the lower and upper bounds of the input grid are \
+   consistent. Report an error if any pair is not. */ \
+   if ( astOK ) { \
+      for ( idim = 0; idim < ndim; idim++ ) { \
+         if ( lbnd[ idim ] > ubnd[ idim ] ) { \
+            astError( AST__GBDIN, "astMask"#X"(%s): Lower bound of " \
+                      "input grid (%d) exceeds corresponding upper bound " \
+                      "(%d).", status, astGetClass( this ), \
+                      lbnd[ idim ], ubnd[ idim ] ); \
+            astError( AST__GBDIN, "Error in input dimension %d.", status, \
+                      idim + 1 ); \
+            break; \
+         } \
+      } \
+   } \
+\
+/* Allocate memory, and then get the bounding box of this new Region in its \
+   current Frame (grid coordinates). This bounding box assumes the region \
+   has not been negated. */ \
+   lbndg = astMalloc( sizeof( int )*(size_t) ndim ); \
+   ubndg = astMalloc( sizeof( int )*(size_t) ndim ); \
+   lbndgd = astMalloc( sizeof( double )*(size_t) ndim ); \
+   ubndgd = astMalloc( sizeof( double )*(size_t) ndim ); \
+   if( astOK ) { \
+      astGetRegionBounds( used_region, lbndgd, ubndgd ); \
+\
+/* We convert the floating point bounds to integer pixel bounds, and at \
+   the same time expand the box by 2 pixels at each edge to ensure that \
+   rounding errors etc do not cause any of the Region to fall outside (or \
+   on) the box. Do not let the expanded box extend outside the supplied \
+   array bounds. Also note the total number of pixels in the supplied \
+   array, and in the bounding box. */ \
+      npix = 1; \
+      npixg = 1; \
+      for ( idim = 0; idim < ndim; idim++ ) { \
+         if( lbndgd[ idim ] != AST__BAD && ubndgd[ idim ] != AST__BAD ) { \
+            lbndg[ idim ] = astMAX( lbnd[ idim ], (int)( lbndgd[ idim ] + 0.5 ) - 2 ); \
+            ubndg[ idim ] = astMIN( ubnd[ idim ], (int)( ubndgd[ idim ] + 0.5 ) + 2 ); \
+         } else { \
+            lbndg[ idim ] = lbnd[ idim ]; \
+            ubndg[ idim ] = ubnd[ idim ]; \
+         } \
+         npix *= ( ubnd[ idim ] - lbnd[ idim ] + 1 ); \
+         if( npixg >= 0 ) npixg *= ( ubndg[ idim ] - lbndg[ idim ] + 1 ); \
+      } \
+\
+/* If the bounding box is null, fill the mask with the supplied value if \
+   we assigning the value to the outside of the region (do the opposite if \
+   the Region has been negated). */ \
+      if( npixg <= 0 && astOK ) { \
+         if( ( inside != 0 ) == ( astGetNegated( used_region ) != 0 ) ) { \
+            c = in; \
+            for( ipix = 0; ipix < npix; ipix++ ) *(c++) = val; \
+            result = npix; \
+         } \
+\
+/* If the bounding box is null, return without action. */ \
+      } else if( npixg > 0 && astOK ) { \
+\
+/* All points outside this box are either all inside, or all outside, the \
+   Region. So we can speed up processing by setting all the points which are \
+   outside the box to the supplied data value (if required). This is \
+   faster than checking each point individually using the Transform method \
+   of the Region. We do this by supplying an alternative output array to \
+   the resampling function below, which has been pre-filled with "val" at \
+   every pixel. */ \
+         if( ( inside != 0 ) == ( astGetNegated( used_region ) != 0 ) ) { \
+\
+/* Allocate memory for the alternative output array, and fill it with \
+   "val". */ \
+            tmp_out = astMalloc( sizeof( Xtype )*(size_t) npix ); \
+            if( tmp_out ) { \
+               c = tmp_out; \
+               for( ipix = 0; ipix < npix; ipix++ ) *(c++) = val; \
+               result = npix - npixg; \
+            } \
+\
+/* Indicate that we will use this temporary array rather than the \
+   supplied array. */ \
+            out = tmp_out; \
+\
+/* If the outside of the grid box is outside the region of interest it \
+   will be unchanged in the resturned array. Therefore we can use the \
+   supplied array as the output array below. */ \
+         } else { \
+            tmp_out = NULL; \
+            out = in; \
+         } \
+\
+/* Temporarily invert the Region if required. The Region Transform methods \
+   leave interior points unchanged and assign AST__BAD to exterior points. \
+   This is the opposite of what we want (which is to leave exterior \
+   points unchanged and assign VAL to interior points), so we negate the \
+   region if the inside is to be assigned the value VAL.*/ \
+         if( inside ) astNegate( used_region ); \
+\
+/* Invoke astResample to mask just the region inside the bounding box found \
+   above (specified by lbndg and ubndg), since all the points outside this \
+   box will already contain their required value. */ \
+         result += astResample##X( used_region, ndim, lbnd, ubnd, in, NULL, AST__NEAREST, \
+                                   NULL, NULL, 0, 0.0, 100, val, ndim, \
+                                   lbnd, ubnd, lbndg, ubndg, out, NULL ); \
+\
+/* Revert to the original setting of the Negated attribute. */ \
+         if( inside ) astNegate( used_region ); \
+\
+/* If required, copy the output data from the temporary output array to \
+   the supplied array, and then free the temporary output array. */ \
+         if( tmp_out ) { \
+            c = tmp_out; \
+            d = in; \
+            for( ipix = 0; ipix < npix; ipix++ ) *(d++) = *(c++); \
+            tmp_out = astFree( tmp_out ); \
+         }\
+      }\
+   } \
+\
+/* Free resources */ \
+   ubndg = astFree( ubndg ); \
+   lbndg = astFree( lbndg ); \
+   ubndgd = astFree( ubndgd ); \
+   lbndgd = astFree( lbndgd ); \
+   used_region = astAnnul( used_region ); \
+\
+/* If an error occurred, clear the returned result. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_MASK(LD,long double)
+#endif
+MAKE_MASK(D,double)
+MAKE_MASK(L,long int)
+MAKE_MASK(UL,unsigned long int)
+MAKE_MASK(I,int)
+MAKE_MASK(UI,unsigned int)
+MAKE_MASK(S,short int)
+MAKE_MASK(US,unsigned short int)
+MAKE_MASK(B,signed char)
+MAKE_MASK(UB,unsigned char)
+MAKE_MASK(F,float)
+
+/* Undefine the macro. */
+#undef MAKE_MASK
+
+
+
+static int Match( AstFrame *this_frame, AstFrame *target, int matchsub,
+                  int **template_axes, int **target_axes,
+                  AstMapping **map, AstFrame **result, int *status ) {
+/*
+*  Name:
+*     Match
+
+*  Purpose:
+*     Determine if conversion is possible between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int Match( AstFrame *template, AstFrame *target, int matchsub,
+*                int **template_axes, int **target_axes,
+*                AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astMatch
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function matches the current Frame of a "template" Region
+*     to a "target" frame and determines whether it is possible to
+*     convert coordinates between them.  If it is, a Mapping that
+*     performs the transformation is returned along with a new Frame
+*     that describes the coordinate system that results when this
+*     Mapping is applied to the current Frame of the target
+*     Region. In addition, information is returned to allow the axes
+*     in this "result" Frame to be associated with the corresponding
+*     axes in the target and template Frames from which they are
+*     derived.
+
+*  Parameters:
+*     template
+*        Pointer to the template Region, whose current Frame
+*        describes the coordinate system (or set of possible
+*        coordinate systems) into which we wish to convert our
+*        coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate
+*        system in which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case.
+*     template_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the index of the axis in the
+*        template Region's current Frame from which it is
+*        derived. If it is not derived from any template Region
+*        axis, a value of -1 will be returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the index of the target Frame axis
+*        from which it is derived. If it is not derived from any
+*        target Frame axis, a value of -1 will be returned instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will
+*        be returned if the requested coordinate conversion is
+*        possible. If returned, the forward transformation of this
+*        Mapping may be used to convert coordinates between the target
+*        Frame and the result Frame (see below) and the inverse
+*        transformation will convert in the opposite direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be
+*        returned if the requested coordinate conversion is
+*        possible. If returned, this Frame describes the coordinate
+*        system that results from applying the returned Mapping
+*        (above) to the "target" coordinate system. In general, this
+*        Frame will combine attributes from (and will therefore be
+*        more specific than) both the target Frame and the current
+*        Frame of the template Region. In particular, when the
+*        template allows the possibility of transformaing to any one
+*        of a set of alternative coordinate systems, the "result"
+*        Frame will indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate
+*     conversion is possible. Otherwise zero is returned (this will
+*     not in itself result in an error condition).
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to Region's current Frame */
+   int match;                    /* Result to be returned */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Invoke the parent astMatch method on the current Frame within the
+   encapsulated FrameSet within the Region. */
+   fr = astGetFrame( ((AstRegion *) this_frame)->frameset, AST__CURRENT );
+   match = astMatch( fr, target, matchsub, template_axes, target_axes, map, result );
+   fr = astAnnul( fr );
+
+/* Return the result. */
+   return match;
+}
+
+static void MatchAxes( AstFrame *frm1_frame, AstFrame *frm2, int *axes,
+                       int *status ) {
+/*
+*  Name:
+*     MatchAxes
+
+*  Purpose:
+*     Find any corresponding axes in two Frames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void MatchAxes( AstFrame *frm1, AstFrame *frm2, int *axes )
+*                     int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astMatchAxes
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function looks for corresponding axes within two supplied
+*     Frames. An array of integers is returned that contains an element
+*     for each axis in the second supplied Frame. An element in this array
+*     will be set to zero if the associated axis within the second Frame
+*     has no corresponding axis within the first Frame. Otherwise, it
+*     will be set to the index (a non-zero positive integer) of the
+*     corresponding axis within the first supplied Frame.
+
+*  Parameters:
+*     frm1
+*        Pointer to the first Frame.
+*     frm2
+*        Pointer to the second Frame.
+*     axes
+*        Pointer to an
+*        integer array in which to return the indices of the axes (within
+*        the second Frame) that correspond to each axis within the first
+*        Frame. Axis indices start at 1. A value of zero will be stored
+*        in the returned array for each axis in the first Frame that has
+*        no corresponding axis in the second Frame.
+*
+*        The number of elements in this array must be greater than or
+*        equal to the number of axes in the first Frame.
+*     status
+*        Pointer to inherited status value.
+
+*  Notes:
+*     -  Corresponding axes are identified by the fact that a Mapping
+*     can be found between them using astFindFrame or astConvert. Thus,
+*     "corresponding axes" are not necessarily identical. For instance,
+*     SkyFrame axes in two Frames will match even if they describe
+*     different celestial coordinate systems
+*/
+
+/* Local Variables: */
+   AstFrame *frm1;               /* Pointer to Region's current Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the astMatchAxesX method on frm2, passing it the current Frame
+   within the encapsulated FrameSet within the Region as "frm1". */
+   frm1 = astGetFrame( ((AstRegion *) frm1_frame)->frameset, AST__CURRENT );
+   astMatchAxesX( frm2, frm1, axes );
+   frm1 = astAnnul( frm1 );
+}
+
+static void MatchAxesX( AstFrame *frm2_frame, AstFrame *frm1, int *axes,
+                        int *status ) {
+/*
+*  Name:
+*     MatchAxesX
+
+*  Purpose:
+*     Find any corresponding axes in two Frames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void MatchAxesX( AstFrame *frm2, AstFrame *frm1, int *axes )
+*                      int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astMatchAxesX
+*     method inherited from the Frame class).
+
+*     This function looks for corresponding axes within two supplied
+*     Frames. An array of integers is returned that contains an element
+*     for each axis in the second supplied Frame. An element in this array
+*     will be set to zero if the associated axis within the second Frame
+*     has no corresponding axis within the first Frame. Otherwise, it
+*     will be set to the index (a non-zero positive integer) of the
+*     corresponding axis within the first supplied Frame.
+
+*  Parameters:
+*     frm2
+*        Pointer to the second Frame.
+*     frm1
+*        Pointer to the first Frame.
+*     axes
+*        Pointer to an integer array in which to return the indices of
+*        the axes (within the first Frame) that correspond to each axis
+*        within the second Frame. Axis indices start at 1. A value of zero
+*        will be stored in the returned array for each axis in the second
+*        Frame that has no corresponding axis in the first Frame.
+*
+*        The number of elements in this array must be greater than or
+*        equal to the number of axes in the second Frame.
+*     status
+*        Pointer to inherited status value.
+
+*  Notes:
+*     -  Corresponding axes are identified by the fact that a Mapping
+*     can be found between them using astFindFrame or astConvert. Thus,
+*     "corresponding axes" are not necessarily identical. For instance,
+*     SkyFrame axes in two Frames will match even if they describe
+*     different celestial coordinate systems
+*/
+
+/* Local Variables: */
+   AstFrame *frm2;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the current Frame in the FrameSet. */
+   frm2 = astGetFrame( ((AstRegion *) frm2_frame)->frameset, AST__CURRENT );
+
+/* Invoke the astMatchAxesX on the current Frame. */
+   astMatchAxesX( frm2, frm1, axes );
+
+/* Free resources */
+   frm2 = astAnnul( frm2 );
+}
+
+static void Negate( AstRegion *this, int *status ) {
+/*
+*++
+*  Name:
+c     astNegate
+f     AST_NEGATE
+
+*  Purpose:
+*     Negate the area represented by a Region.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     void astNegate( AstRegion *this )
+f     CALL AST_NEGATE( THIS, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     This function negates the area represented by a Region. That is,
+*     points which were previously inside the region will then be
+*     outside, and points which were outside will be inside. This is
+*     acomplished by toggling the state of the Negated attribute for
+*     the supplied region.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Toggle the Negated attribute. */
+   astSetNegated( this, astGetNegated( this ) ? 0 : 1 );
+
+}
+
+static void Norm( AstFrame *this_frame, double value[], int *status ) {
+/*
+*  Name:
+*     Norm
+
+*  Purpose:
+*     Normalise a set of Region coordinates.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void Norm( AstAxis *this, double value[], int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astNorm method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a set of coordinate values for the
+*     current Frame of a Region, which might potentially be
+*     unsuitable for display to a user (for instance, may lie outside
+*     the expected range of values) into a set of acceptable
+*     alternative values suitable for display.
+*
+*     Typically, for Frames whose axes represent cyclic values (such
+*     as angles or positions on the sky), this function wraps an
+*     arbitrary set of coordinates, so that they lie within the first
+*     cycle (say zero to 2*pi or -pi/2 to +pi/2). For Frames with
+*     ordinary linear axes, without constraints, this function will
+*     typically return the original coordinate values unchanged.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     value
+*        An array of double, with one element for each Region axis.
+*        This should contain the initial set of coordinate values,
+*        which will be modified in place.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to the current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's current Frame and invoke this
+   Frame's astNorm method to obtain the new values. Annul the Frame
+   pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   astNorm( fr, value );
+   fr = astAnnul( fr );
+}
+
+static void NormBox( AstFrame *this_frame, double lbnd[], double ubnd[],
+                     AstMapping *reg, int *status ) {
+/*
+*  Name:
+*     NormBox
+
+*  Purpose:
+*     Extend a box to include effect of any singularities in the Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astNormBox( AstFrame *this, double lbnd[], double ubnd[],
+*                      AstMapping *reg, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astNormBox method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function modifies a supplied box to include the effect of any
+*     singularities in the co-ordinate system represented by the Frame.
+*     For a normal Cartesian coordinate system, the box will be returned
+*     unchanged. Other classes of Frame may do other things. For instance,
+*     a SkyFrame will check to see if the box contains either the north
+*     or south pole and extend the box appropriately.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     lbnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        lower axis bounds for the box. They will be modified on exit
+*        to include the effect of any singularities within the box.
+*     ubnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        upper axis bounds for the box. They will be modified on exit
+*        to include the effect of any singularities within the box.
+*     reg
+*        A Mapping which should be used to test if any singular points are
+*        inside or outside the box. The Mapping should leave an input
+*        position unchanged if the point is inside the box, and should
+*        set all bad if the point is outside the box.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to the current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's current Frame and invoke this
+   Frame's astNormBox method to obtain the new values. Annul the Frame
+   pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   astNormBox( fr, lbnd, ubnd, reg );
+   fr = astAnnul( fr );
+}
+
+static void Offset( AstFrame *this_frame, const double point1[],
+                    const double point2[], double offset, double point3[], int *status ) {
+/*
+*  Name:
+*     Offset
+
+*  Purpose:
+*     Calculate an offset along a geodesic curve.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void Offset( AstFrame *this,
+*                  const double point1[], const double point2[],
+*                  double offset, double point3[], int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astOffset
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function finds the Region coordinate values of a point
+*     which is offset a specified distance along the geodesic curve
+*     between two other points.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     point1
+*        An array of double, with one element for each Region axis.
+*        This should contain the coordinates of the point marking the
+*        start of the geodesic curve.
+*     point2
+*        An array of double, with one element for each Region axis
+*        This should contain the coordinates of the point marking the
+*        end of the geodesic curve.
+*     offset
+*        The required offset from the first point along the geodesic
+*        curve. If this is positive, it will be towards the second
+*        point. If it is negative, it will be in the opposite
+*        direction. This offset need not imply a position lying
+*        between the two points given, as the curve will be
+*        extrapolated if necessary.
+*     point3
+*        An array of double, with one element for each Region axis
+*        in which the coordinates of the required point will be
+*        returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*     - "Bad" coordinate values will also be returned if the two
+*     points supplied are coincident (or otherwise fail to uniquely
+*     specify a geodesic curve) but the requested offset is non-zero.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's current Frame and invoke this
+   Frame's astOffset method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   astOffset( fr, point1, point2, offset, point3 );
+   fr = astAnnul( fr );
+}
+
+static double Offset2( AstFrame *this_frame, const double point1[2],
+                       double angle, double offset, double point2[2], int *status ){
+/*
+*  Name:
+*     Offset2
+
+*  Purpose:
+*     Calculate an offset along a geodesic curve in a 2D Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     double Offset2( AstFrame *this, const double point1[2], double angle,
+*                     double offset, double point2[2], int *status );
+
+*  Class Membership:
+*     Region member function (over-rides the protected astOffset2
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function finds the Frame coordinate values of a point which
+*     is offset a specified distance along the geodesic curve at a
+*     given angle from a specified starting point. It can only be
+*     used with 2-dimensional Frames.
+*
+*     For example, in a basic Frame, this offset will be along the
+*     straight line joining two points. For a more specialised Frame
+*     describing a sky coordinate system, however, it would be along
+*     the great circle passing through two sky positions.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This should contain the coordinates of the
+*        point marking the start of the geodesic curve.
+*     angle
+*        The angle (in radians) from the positive direction of the second
+*        axis, to the direction of the required position, as seen from
+*        the starting position. Positive rotation is in the sense of
+*        rotation from the positive direction of axis 2 to the positive
+*        direction of axis 1.
+*     offset
+*        The required offset from the first point along the geodesic
+*        curve. If this is positive, it will be in the direction of the
+*        given angle. If it is negative, it will be in the opposite
+*        direction.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        in which the coordinates of the required point will be returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The direction of the geodesic curve at the end point. That is, the
+*     angle (in radians) between the positive direction of the second
+*     axis and the continuation of the geodesic curve at the requested
+*     end point. Positive rotation is in the sense of rotation from
+*     the positive direction of axis 2 to the positive direction of axis 1.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - An error will be reported if the Frame is not 2-dimensional.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   double result;                /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke the
+   astOffset2 method for this Frame. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astOffset2( fr, point1, angle, offset, point2 );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static int Overlap( AstRegion *this, AstRegion *that, int *status ){
+/*
+*++
+*  Name:
+c     astOverlap
+f     AST_OVERLAP
+
+*  Purpose:
+*     Test if two regions overlap each other.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     int astOverlap( AstRegion *this, AstRegion *that )
+f     RESULT = AST_OVERLAP( THIS, THAT, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     This function returns an integer value indicating if the two
+*     supplied Regions overlap. The two Regions are converted to a commnon
+*     coordinate system before performing the check. If this conversion is
+*     not possible (for instance because the two Regions represent areas in
+*     different domains), then the check cannot be performed and a zero value
+*     is returned to indicate this.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the first Region.
+c     that
+f     THAT = INTEGER (Given)
+*        Pointer to the second Region.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astOverlap()
+f     AST_OVERLAP = INTEGER
+*        A value indicating if there is any overlap between the two Regions.
+*        Possible values are:
+*
+*        0 - The check could not be performed because the second Region
+*            could not be mapped into the coordinate system of the first
+*            Region.
+*
+*        1 - There is no overlap between the two Regions.
+*
+*        2 - The first Region is completely inside the second Region.
+*
+*        3 - The second Region is completely inside the first Region.
+*
+*        4 - There is partial overlap between the two Regions.
+*
+*        5 - The Regions are identical to within their uncertainties.
+*
+*        6 - The second Region is the exact negation of the first Region
+*            to within their uncertainties.
+
+*  Notes:
+*     - The returned values 5 and 6 do not check the value of the Closed
+*     attribute in the two Regions.
+*     - A value of zero will be returned if this function is invoked with the
+*     AST error status set, or if it should fail for any reason.
+*--
+
+* Implementation Notes:
+*    This function is simply a wrap-up for the protected astOverlapX
+*    method which performs the required processing but swaps the order
+*    of the two arguments. This is a trick to allow the astOverlap method
+*    to be over-ridden by derived classes on the basis of the class of either
+*    of the two arguments.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Invoke the "astOverlapX" method with the two arguments swapped. */
+   return astOverlapX( that, this );
+}
+
+static int OverlapX( AstRegion *that, AstRegion *this, int *status ){
+/*
+*+
+*  Name:
+*     astOverlapX
+
+*  Purpose:
+*     Test if two regions overlap each other.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int astOverlapX( AstRegion *that, AstRegion *this )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     This function performs the processing for the public astOverlap
+*     method and has exactly the same interface except that the order
+*     of the two arguments is swapped. This is a trick to allow
+*     the astOverlap method to be over-ridden by derived classes on
+*     the basis of the class of either of its two arguments.
+*
+*     See the astOverlap method for details of the interface.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm_reg1;           /* Pointer to base Frame in "reg1" Frame */
+   AstFrame *frm_reg1;            /* Pointer to current Frame in "reg1" Frame */
+   AstFrameSet *fs0;              /* FrameSet connecting Region Frames */
+   AstFrameSet *fs;               /* FrameSet connecting Region Frames */
+   AstMapping *cmap;              /* Mapping connecting Region Frames */
+   AstMapping *map;               /* Mapping form "reg2" current to "reg1" base */
+   AstMapping *map_reg1;          /* Pointer to current->base Mapping in "reg1" */
+   AstPointSet *ps1;              /* Mesh covering second Region */
+   AstPointSet *ps3;              /* Mesh covering first Region */
+   AstPointSet *ps4;              /* Mesh covering first Region */
+   AstPointSet *ps2;              /* Mesh covering second Region */
+   AstPointSet *reg2_mesh;        /* Mesh covering second Region */
+   AstPointSet *reg1_mesh;        /* Mesh covering first Region */
+   AstPointSet *reg2_submesh;     /* Second Region mesh minus boundary points */
+   AstRegion *reg1;               /* Region to use as the first Region */
+   AstRegion *reg2;               /* Region to use as the second Region */
+   AstRegion *unc1;               /* "unc" mapped into Frame of first Region */
+   AstRegion *unc;                /* Uncertainty in second Region */
+   double **ptr1;                 /* Pointer to mesh axis values */
+   double **ptr;                  /* Pointer to pointset data */
+   double *p;                     /* Pointer to next axis value */
+   int *mask;                     /* Mask identifying common boundary points */
+   int allbad;                    /* Were all axis values bad? */
+   int allgood;                   /* Were all axis values good? */
+   int bnd1;                      /* Does reg1 have a finite boundary */
+   int bnd2;                      /* Does reg2 have a finite boundary */
+   int bnd_that;                  /* Does "that" have a finite boundary */
+   int bnd_this;                  /* Does "this" have a finite boundary */
+   int case1;                     /* First region inside second region? */
+   int first;                     /* First pass? */
+   int good;                      /* Any good axis values found? */
+   int i;                         /* Mesh axis index */
+   int iax;                       /* Axis index */
+   int inv0;                      /* Original FrameSet Invert flag */
+   int ip;                        /* Index of point */
+   int j;                         /* Mesh point index */
+   int nc;                        /* Number of axis values per point */
+   int np;                        /* Number of points in mesh */
+   int result;                    /* Value to return */
+   int reg1_neg;                  /* Was "reg1" negated to make it bounded? */
+   int reg2_neg;                  /* Was "reg2" negated to make it bounded? */
+   int that_neg;                  /* Was "that" negated to make it bounded? */
+   int this_neg;                  /* Was "this" negated to make it bounded? */
+   int touch;                     /* Do the Regions touch? */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Return 5 if the two Regions are equal using the astEqual method. */
+   if( astEqual( this, that ) ) {
+      return 5;
+
+/* Return 6 if the two Regions are equal using the Equal method after
+   temporarily negating the first. */
+   } else {
+      astNegate( this );
+      result = astEqual( this, that );
+      astNegate( this );
+      if( result ) return 6;
+   }
+
+/* Get a FrameSet which connects the Frame represented by the second Region
+   to the Frame represented by the first Region. Check that the conection is
+   defined. */
+   fs0 = astConvert( that, this, "" );
+   if( !fs0 ) return 0;
+   inv0 = astGetInvert( fs0 );
+
+/* The rest of this function tests for overlap by representing one of the
+   Regions as a mesh of points along its boundary, and then checking to see
+   if any of the points in this mesh fall inside or outside the other Region.
+   This can only be done if the Region has a boundary of finite length (e.g.
+   Circles, Boxes, etc). Other Regions (e.g. some Intervals) do not have
+   finite boundaries and consequently report an error if an attempt is made
+   to represent them using a boundary mesh. We now therefore check to see if
+   either of the two Regions has a finite boundary length. This will be the
+   case if the region is bounded, or if it can be made bounded simply by
+   negating it. If a Region is unbounded regardless of the setting of its
+   Negated flag, then it does not have a finite boundary. We leave the
+   Negated attributes (temporaily) set to the values that cause the
+   Regions to be bounded. Set flags to indicate if the Regions have been
+   negated. */
+   bnd_this = astGetBounded( this );
+   if( !bnd_this ) {
+      astNegate( this );
+      bnd_this = astGetBounded( this );
+      if( ! bnd_this ) {
+         astNegate( this );
+         this_neg = 0;
+      } else {
+         this_neg = 1;
+      }
+   } else {
+      this_neg = 0;
+   }
+
+   bnd_that = astGetBounded( that );
+   if( !bnd_that ) {
+      astNegate( that );
+      bnd_that = astGetBounded( that );
+      if( ! bnd_that ) {
+         astNegate( that );
+         that_neg = 0;
+      } else {
+         that_neg = 1;
+      }
+   } else {
+      that_neg = 0;
+   }
+
+/* If neither Regions has a finite boundary, then we cannot currently
+   determine any overlap, so report an error. Given more time, it
+   is probably possible to think of some way of determining overlap
+   between two unbounded Regions, but it will probably not be a common
+   requirement and so is currently put off to a rainy day. */
+   if( !bnd_this && !bnd_that && astOK ) {
+      astError( AST__INTER, "astOverlap(Region): Neither of the two "
+                "supplied Regions (classes %s and %s) has a finite "
+                "boundary.", status, astGetClass(this), astGetClass(that) );
+      astError( AST__INTER, "The current implementation of astOverlap "
+                "cannot determine the overlap between two Regions "
+                "unless at least one of them has a finite boundary." , status);
+   }
+
+/* If only one of the two Regions has a finite boundary, we must use its
+   mesh first. Choose the finite boundary Region as the "second" region.
+   Also store a flag indicating if the first Region has a finite boundary. */
+   if( bnd_that ) {
+      reg1 = this;
+      reg2 = that;
+      bnd1 = bnd_this;
+      bnd2 = bnd_that;
+      reg1_neg = this_neg;
+      reg2_neg = that_neg;
+   } else {
+      reg1 = that;
+      reg2 = this;
+      bnd1 = bnd_that;
+      bnd2 = bnd_this;
+      reg1_neg = that_neg;
+      reg2_neg = this_neg;
+   }
+
+/* We may need to try again with the above selections swapped. We only do
+   this once though. Set a flag to indicate that we are about to start the
+   first pass. */
+   first = 1;
+L1:
+
+/* Get a FrameSet which connects the Frame represented by the second Region
+   to the Frame represented by the first Region. Check that the conection is
+   defined. */
+   fs = astClone( fs0 );
+   astSetInvert( fs, (reg2 == that ) ? inv0 : 1 - inv0 );
+   if( fs ) {
+
+/* Get a pointer to the Frame represented by the first Region. */
+      frm_reg1 = astGetFrame( reg1->frameset, AST__CURRENT );
+
+/* Get a pointer to the Mapping from current to base Frame in the first
+   Region. */
+      map_reg1 = astGetMapping( reg1->frameset, AST__CURRENT, AST__BASE );
+
+/* Get the Mapping from the current Frame of the second Region to the
+   current Frame of the first Region. */
+      cmap = astGetMapping( fs, AST__BASE, AST__CURRENT );
+
+/* Combine these Mappings to get the Mapping from current Frame of the
+   second region to the base Frame of the first Region. */
+      map = (AstMapping *) astCmpMap( cmap, map_reg1, 1, "", status );
+
+/* Get a mesh of points covering the second Region. These points are
+   within the current Frame of the second Region. */
+      reg2_mesh = astRegMesh( reg2 );
+
+/* Transform this mesh into the base Frame of the first Region. */
+      ps1 = astTransform( map, reg2_mesh, 1, NULL );
+
+/* Check there are some good points in the transformed pointset. */
+      good = 0;
+      np = astGetNpoint( ps1 );
+      nc = astGetNcoord( ps1 );
+      ptr1 = astGetPoints( ps1 );
+      if( ptr1 ) {
+         for( i = 0; i < nc && !good; i++ ) {
+            for( j = 0; j < np; j++ ) {
+               if( ptr1[ i ][ j ] != AST__BAD ) {
+                  good = 1;
+                  break;
+               }
+            }
+         }
+      }
+
+/* If the transformed mesh contains no good points, swap the regions and
+   try again. */
+      if( !good ) {
+         fs = astAnnul( fs );
+         frm_reg1 = astAnnul( frm_reg1 );
+         map_reg1 = astAnnul( map_reg1 );
+         cmap = astAnnul( cmap );
+         map = astAnnul( map );
+         reg2_mesh = astAnnul( reg2_mesh );
+         ps1 = astAnnul( ps1 );
+
+         if( first ) {
+            first = 0;
+
+            if( !bnd_that ) {
+               reg1 = this;
+               reg2 = that;
+               bnd1 = bnd_this;
+               bnd2 = bnd_that;
+               reg1_neg = this_neg;
+               reg2_neg = that_neg;
+            } else {
+               reg1 = that;
+               reg2 = this;
+               bnd1 = bnd_that;
+               bnd2 = bnd_this;
+               reg1_neg = that_neg;
+               reg2_neg = this_neg;
+            }
+            goto L1;
+
+         } else {
+            return 0;
+         }
+      }
+
+/* Also transform the Region describing the positional uncertainty within
+   the second supplied Region into the base Frame of the first supplied
+   Region. */
+      unc = astGetUncFrm( reg2, AST__CURRENT );
+      bfrm_reg1 = astGetFrame( reg1->frameset, AST__BASE );
+      unc1 = astMapRegion( unc, map, bfrm_reg1 );
+
+/* See if all points within this transformed mesh fall on the boundary of
+   the first Region, to within the joint uncertainty of the two Regions. If
+   so the two Regions have equivalent boundaries. We can only do this is
+   the first region is bounded. */
+      if( astRegPins( reg1, ps1, unc1, &mask ) && good ) {
+
+/* If the boundaries are equivalent, the Regions are either identical or
+   are mutually exclusive. To distinguish between these cases, we
+   looked at the Bounded attributes. If the Bounded attribute is the same
+   for both Regions then they are identical, otherwise they are mutually
+   exclusive. */
+         result = ( ( !reg1_neg && bnd1 ) == ( !reg2_neg && bnd2 ) ) ? 5 : 6;
+
+/* If the boundaries of the two Regions are not equivalent. */
+      } else {
+
+/* Create a new PointSet containing those points from the mesh which are
+   not on the boundary of the first Region. These points are identified by
+   the mask array created by the astRegPins method above. */
+         reg2_submesh = GetSubMesh( mask, reg2_mesh, status );
+
+/* Transform the points in the submesh of the second Region into the
+   current Frame of the first Region. */
+         (void ) astAnnul( ps1 );
+         ps1 = astTransform( cmap, reg2_submesh, 1, NULL );
+
+/* Transform this submesh using the first Region as a Mapping. Any points
+   outside the first region will be set bad in the output PointSet. */
+         ps2 = astTransform( (AstMapping *) reg1, ps1, 1, NULL );
+
+/* Get the number of axes and points in this PointSet. */
+         nc = astGetNcoord( ps2 );
+         np = astGetNpoint( ps2 );
+
+/* Note if there were any common points (i.e. points on the boundary of
+   both regions). */
+         touch = ( astGetNpoint( reg2_mesh ) != np );
+
+/* Get pointers to the axis data in this PointSet, and check they can be
+   used safely. */
+         ptr = astGetPoints( ps2 );
+         if( astOK ) {
+
+/* Loop round all points checking if the axis values are bad. We want a
+   flag saying if there are any good axis values and another flag saying if
+   there are any bad axis values. */
+            allbad = 1;
+            allgood = 1;
+            for( iax = 0; iax < nc; iax++ ) {
+               p = ptr[ iax ];
+               for( ip = 0; ip < np; ip++,p++ ) {
+                  if( *p == AST__BAD ) {
+                     allgood = 0;
+                     if( !allbad ) break;
+                  } else {
+                     allbad = 0;
+                     if( !allgood ) break;
+                  }
+               }
+            }
+
+/* If the entire mesh of the (potentially negated) second Region was either
+   on the boundary of, or inside, the (potentially negated) first region,
+   determine the result depending on whether the regions have been
+   negated and whether they are bounded. Check for impossible states (or
+   maybe just errors in my logic). */
+            if( allgood ) {
+
+/* Second region has a mesh so it must be bounded. */
+               if( !bnd2 && astOK ) {
+                     astError( AST__INTER, "astOverlap(%s): Inconsistent "
+                               "state 1 (internal AST programming error).",
+                               status, astGetClass( this ) );
+
+/* If the first region has been made bounded by negating it... */
+               } else if( reg1_neg ) {
+                  if( bnd1 ) {
+
+/* If the second region has been made bounded by negating it, then the
+   unnegated first region is completely inside the unnegated second region. */
+                     if( reg2_neg ) {
+                        result = 2;
+
+/* If the second region was bounded without negating it, then there is
+   no overlap between the unnegated first region and the second region. */
+                     } else {
+                        result = 1;
+                     }
+
+/* If the first region has been negated then it should not be unbounded.
+   This is ensured by the nature of the code that sets the "this_neg" and
+   "that_neg" flags above. */
+                  } else if( astOK ) {
+                     astError( AST__INTER, "astOverlap(%s): Inconsistent "
+                               "state 2 (internal AST programming error).",
+                               status, astGetClass( this ) );
+                  }
+
+/* If the first region was bounded without negating it, but the second
+   region was made bounded by negating it, there is partial overlap. */
+               } else if( reg2_neg ) {
+                  result = 4;
+
+/* If the first region was bounded without negating it, but the second
+   region was also bounded without negating it, the second region is
+   completely inside the first region. */
+               } else {
+                  result = 3;
+               }
+
+/* If part of the mesh of the second Region was inside the first region,
+   and part was outside, then there is partial ocverlap. */
+            } else if( !allbad ) {
+               result = 4;
+
+/* If no part of the mesh of the (possibly negated) second Region was inside
+   the (possibly negated) first region ... */
+            } else {
+
+/* First deal with cases where the first region is unbounded. */
+               if( !bnd1 ) {
+                  if( reg1_neg && astOK ) {
+                     astError( AST__INTER, "astOverlap(%s): Inconsistent "
+                               "state 5 (internal AST programming error).",
+                               status, astGetClass( this ) );
+                  } else if( reg2_neg ){
+                     result = 2;
+                  } else {
+                     result = 1;
+                  }
+
+/* The second region has a mesh so it must be bounded. */
+               } else if( !bnd2 && astOK ) {
+                     astError( AST__INTER, "astOverlap(%s): Inconsistent "
+                               "state 6 (internal AST programming error).",
+                               status, astGetClass( this ) );
+
+/* So now we know both (possibly negated) regions are bounded. */
+               } else {
+
+/* We know that none of the reg2 mesh points are inside the bounded reg1.
+   But this still leaves two cases: 1) reg1 could be contained completely
+   within reg2, or 2) there is no overlap between reg2 and reg1. To
+   distinguish between these two cases we use reg2 to transform a point
+   on the boundary of reg1. First get a mesh on the boundary of reg1. */
+                  reg1_mesh = astRegMesh( reg1 );
+
+/* Transform this mesh into the coordinate system of the second Region. */
+                  ps3 = astTransform( cmap, reg1_mesh, 0, NULL );
+
+/* Transform the points in this mesh using the second Region as a Mapping.
+   Any points outside the second region will be set bad in the output
+   PointSet. */
+                  ps4 = astTransform( (AstMapping *) reg2, ps3, 1, NULL );
+
+/* Get pointers to the axis data in this PointSet,and check they can be
+   used safely. */
+                  ptr = astGetPoints( ps4 );
+                  if( astOK ) {
+
+/* Test the firts point and set a flag indicating if we are in case 1 (if
+   not, we must be in case 2). */
+                     case1 = ( ptr[ 0 ][ 0 ] != AST__BAD );
+
+/* Apply logic similar to the other cases to determine the result. */
+                     if( reg1_neg ) {
+                        if( case1 == ( reg2_neg != 0 ) ) {
+                           result = 3;
+                        } else {
+                           result = 4;
+                        }
+                     } else {
+                        if( case1 == ( reg2_neg != 0 ) ) {
+                           result = 1;
+                        } else {
+                           result = 2;
+                        }
+                     }
+                  }
+
+/* Free resources. */
+                  reg1_mesh = astAnnul( reg1_mesh );
+                  ps3 = astAnnul( ps3 );
+                  ps4 = astAnnul( ps4 );
+               }
+	    }
+        }
+
+/* If there was no intersection or overlap, but the regions touch, then we
+   consider there to be an intersection if either region is closed. */
+	if( touch && result == 1 ) {
+	   if( astGetClosed( this) || astGetClosed( that ) ) result = 4;
+	}
+
+/* Free resources.*/
+         reg2_submesh = astAnnul( reg2_submesh );
+         ps2 = astAnnul( ps2 );
+      }
+
+/* Free resources.*/
+      fs = astAnnul( fs );
+      bfrm_reg1 = astAnnul( bfrm_reg1 );
+      frm_reg1 = astAnnul( frm_reg1 );
+      map_reg1 = astAnnul( map_reg1 );
+      cmap = astAnnul( cmap );
+      map = astAnnul( map );
+      ps1 = astAnnul( ps1 );
+      reg2_mesh = astAnnul( reg2_mesh );
+      unc = astAnnul( unc );
+      unc1 = astAnnul( unc1 );
+      if( mask) mask = astFree( mask );
+   }
+   fs0 = astAnnul( fs0 );
+
+/* The returned value should take account of whether "this" or "that" is
+   the first Region. If "this" was used as the first Region, then the
+   result value calculated above is already correct. If "that" was used as
+   the first Region, then we need to change the result to swap "this" and
+   "that". */
+   if( reg1 == that ) {
+      if( result == 2 ) {
+         result = 3;
+      } else if( result == 3 ) {
+         result = 2;
+      }
+   }
+
+/* Re-instate the original Negated flags. */
+   if( this_neg ) astNegate( this );
+   if( that_neg ) astNegate( that );
+
+/* If not OK, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static void Overlay( AstFrame *template_frame, const int *template_axes,
+                     AstFrame *result, int *status ) {
+/*
+*  Name:
+*     Overlay
+
+*  Purpose:
+*     Overlay the attributes of a template Region on to another Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void Overlay( AstFrame *template, const int *template_axes,
+*                   AstFrame *result, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astOverlay
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function overlays attributes from the current Frame of a
+*     Region on to another Frame, so as to over-ride selected
+*     attributes of that second Frame. Normally only those attributes
+*     which have been specifically set in the template will be
+*     transferred. This implements a form of defaulting, in which a
+*     Frame acquires attributes from the template, but retains its
+*     original attributes (as the default) if new values have not
+*     previously been explicitly set in the template.
+
+*  Parameters:
+*     template
+*        Pointer to the template Region, for whose current Frame
+*        values should have been explicitly set for any attribute
+*        which is to be transferred.
+*     template_axes
+*        Pointer to an array of int, with one element for each axis of
+*        the "result" Frame (see below). For each axis in the result
+*        frame, the corresponding element of this array should contain
+*        the (zero-based) index of the axis in the current Frame of
+*        the template Region to which it corresponds. This array is
+*        used to establish from which template Frame axis any
+*        axis-dependent attributes should be obtained.
+*
+*        If any axis in the result Frame is not associated with a
+*        template Frame axis, the corresponding element of this array
+*        should be set to -1.
+*
+*        If a NULL pointer is supplied, the template and result axis
+*        indices are assumed to be identical.
+*     result
+*        Pointer to the Frame which is to receive the new attribute values.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the current Frame in the Region and invoke its
+   astOverlay method to overlay its attributes. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( ((AstRegion *) template_frame)->frameset, AST__CURRENT );
+   astOverlay( fr, template_axes, result );
+   fr = astAnnul( fr );
+}
+
+static void PermAxes( AstFrame *this_frame, const int perm[], int *status ) {
+/*
+*  Name:
+*     PermAxes
+
+*  Purpose:
+*     Permute the order of a Region's axes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void PermAxes( AstFrame *this, const int perm[], int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astPermAxes method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function permutes the order in which the axes in the
+*     current Frame of a Region occur.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     perm
+*        An array of int (with one element for each axis of the
+*        Region's current Frame) which lists the axes in their new
+*        order. Each element of this array should be a (zero-based)
+*        axis index identifying the axes according to their old
+*        (un-permuted) order.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Only genuine permutations of the axis order are permitted, so
+*     each axis must be referenced exactly once in the "perm" array.
+*     - If more than one axis permutation is applied to the same Frame
+*     in a Region, the effects are cumulative.
+*/
+
+/* Local Variables: */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Invoke the astPermAxes method on the encapsulated FrameSet. */
+   astPermAxes( this->frameset, perm );
+
+}
+
+static AstFrame *PickAxes( AstFrame *this_frame, int naxes, const int axes[],
+                           AstMapping **map, int *status ) {
+/*
+*  Name:
+*     PickAxes
+
+*  Purpose:
+*     Create a new Frame by picking axes from a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstFrame *PickAxes( AstFrame *this, int naxes, const int axes[],
+*                         AstMapping **map, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astPickAxes protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function creates a new Frame whose axes are copies of axes
+*     picked from the encapsulated Frame of an existing Region. Other
+*     Frame attributes are also copied from this existing Frame to the
+*     new Frame. Zero or more of the original axes may be picked in
+*     any order, but each can be used only once. Additional axes (with
+*     default characteristics) may be included in the new Frame if
+*     required.
+*
+*     Optionally, a Mapping that converts between the original Frame's
+*     axes and those of the new Frame may also be returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     naxes
+*        The number of axes required in the new Frame.
+*     axes
+*        Pointer to an array of int with naxes elements. This should
+*        contain (zero based) axis indices specifying the axes which
+*        are to be included in the new Frame, in the order
+*        required. Each axis index may occur only once.
+*
+*        If additional (default) axes are also to be included, the
+*        corresponding elements of this array should be set to -1.
+*     map
+*        Address of a location to receive a pointer to a new
+*        Mapping. This will be a PermMap (or a UnitMap as a special
+*        case) that describes the axis permutation that has taken
+*        place between the current Frame of the Region and the new
+*        Frame.  The forward transformation will convert from the
+*        original Region's axes to the new one's, and vice versa.
+*
+*        If this Mapping is not required, a NULL value may be supplied
+*        for this parameter.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the new Frame.
+
+*  Notes:
+*     - The class of object returned may differ from that of the
+*     original current Frame, depending on which axes are
+*     selected. For example, if a single axis is picked from a
+*     SkyFrame (which always has two axes), the resulting Frame cannot
+*     be a valid SkyFrame, so will revert to the parent class (Frame)
+*     instead.
+*     - The new Frame contains a deep copy of all the data selected
+*     from the original current Frame. Modifying the new Frame will
+*     therefore not affect the Region or the Frames it contains.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;            /* Current Frame from input Region */
+   AstFrame *frame;           /* Pointer to Frame to be returned */
+   AstMapping *cbmap;         /* Base->current Mapping from input Region */
+   AstMapping *fsmap;         /* Mapping from selected current to base axes */
+   AstRegion *breg;           /* Region spanning selected base Frame axes */
+   AstRegion *creg;           /* Region spanning selected current Frame axes */
+   AstRegion *this;           /* Pointer to Region structure */
+   int *base_axes;            /* Holds selected base frame axis indices */
+   int def;                   /* Were any default axes requested? */
+   int i;                     /* Axis index */
+   int nbase;                 /* No. of selected base Frame axes */
+
+/* Initialise the returned pointers. */
+   if ( map ) *map = NULL;
+   frame = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return frame;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Check that a valid set of axes is being selected . */
+   astValidateAxisSelection( this, naxes, axes, "astPickAxes" );
+
+/* Pick the required axes from the current Frame of the encapsulated
+   FrameSet. */
+   cfrm = astGetFrame( this->frameset, AST__CURRENT );
+   frame = astPickAxes( cfrm, naxes, axes, map );
+
+/* See if any default axes are to be included in the returned Frame. */
+   def = 0;
+   for( i = 0; i < naxes; i++ ) {
+      if( axes[ i ] < 0 ) def = 1;
+   }
+
+/* Regions cannot yet include extra default axes in the returned Frame
+   so return a basic Frame if any default axes were requested. */
+   if( ! def ) {
+
+/* We now see if the requested set of current Frame axes correspond to a
+   unique set of base Frame axes. If they do, we may be able to return a
+   Region spanning the selected axes rather than just a Frame. The check
+   is performed by attempting to split the current->base Mapping. */
+      cbmap = astGetMapping( this->frameset, AST__CURRENT, AST__BASE );
+      base_axes = astMapSplit( cbmap, naxes, axes, &fsmap );
+
+/* Check the Mapping could be split. */
+      if( base_axes ) {
+
+/* Store the number of base Frame axes that correspond to the requested
+   set of current Frame axes. */
+         nbase = astGetNout( fsmap );
+
+/* Attempt to create a new Region that spans the corresponding set of
+   base Frame axes. */
+         breg = astRegBasePick( this, nbase, base_axes );
+         if( breg ) {
+
+/* Use the split Mapping to map the base Frame region into the requested
+   Frame. We invert the "fsmap" first so that it maps the selected base
+   Frame axes into the selected current Frame axes. */
+            astInvert( fsmap );
+            creg = astMapRegion( breg, fsmap, frame );
+
+/* Copy properties from the old Region to the new Region. */
+            astRegOverlay( creg, this, 0 );
+
+/* Return this new Region in place of the simple Frame found above. */
+            (void) astAnnul( frame );
+            frame = (AstFrame *) creg;
+
+/* Free resources */
+            breg = astAnnul( breg );
+         }
+         fsmap = astAnnul( fsmap );
+         base_axes = astFree( base_axes );
+      }
+      cbmap = astAnnul( cbmap );
+   }
+   cfrm = astAnnul( cfrm );
+
+/* If an error occurred, annul the Mapping pointer (if requested) and
+   the new Frame pointer. */
+   if ( !astOK ) {
+      if ( map ) *map = astAnnul( *map );
+      frame = astAnnul( frame );
+   }
+
+/* Return the pointer to the new Frame. */
+   return frame;
+}
+
+static void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+/*
+*+
+*  Name:
+*     astRegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astRegBaseBox( AstRegion *this, double *lbnd, double *ubnd )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+
+*-
+*/
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* This abstract implementation simply reports an error. All sub-classes of
+   Region should over-ride this to return appropriate values. */
+   astError( AST__INTER, "astRegBaseBox(%s): The %s class does not implement "
+             "the astRegBaseBox method inherited from the Region class "
+             "(internal AST programming error).", status, astGetClass( this ),
+             astGetClass( this ) );
+}
+
+static void RegBaseBox2( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+/*
+*+
+*  Name:
+*     astRegBaseBox2
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astRegBaseBox2( AstRegion *this, double *lbnd, double *ubnd )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function is similar to astRegBaseBox in that it returns the
+*     upper and lower axis bounds of a Region in the base Frame of the
+*     encapsulated FrameSet. But, in addition to assuming that the
+*     supplied Region has not been negated, it also assumes that any
+*     component Regions contained within the supplied Region have not been
+*     negated.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+
+*-
+*/
+
+/* This base class implementation simply calls astRegBaseBox. Sub-classes
+   which contain component Regions should override it. */
+   astRegBaseBox( this, lbnd, ubnd );
+
+}
+
+static AstPointSet *RegBaseGrid( AstRegion *this, int *status ){
+/*
+*+
+*  Name:
+*     astRegBaseGrid
+
+*  Purpose:
+*     Return a PointSet containing points spread through the volume of a
+*     Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstPointSet *astRegBaseGrid( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a PointSet containing a set of points spread
+*     through the volume of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     Pointer to the PointSet. If the Region is unbounded, a NULL pointer
+*     will be returned.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstBox *box;
+   AstFrame *frmb;
+   AstPointSet *ps1;
+   AstPointSet *ps2;
+   AstPointSet *result;
+   double **ptr2;
+   double **ptr;
+   double *lbnd;
+   double *ubnd;
+   int good;
+   int ic;
+   int ip;
+   int ipr;
+   int meshsize;
+   int naxb;
+   int np;
+   int npnt2;
+   int ntry;
+
+/* Initialise */
+   result= NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return NULL;
+
+/* If the Region structure contains a pointer to a PointSet holding
+   positions spread over the volume of the Region in the base Frame,
+   return it. */
+   if( this->basegrid ) {
+      result = astClone( this->basegrid );
+
+/* Otherwise, check the Region is bounded. */
+   } else if( astGetBounded( this ) ) {
+
+/* Get the original MeshSize attribute. */
+      meshsize = astGetMeshSize( this );
+
+/* Get the base Frame bounding box. */
+      naxb = astGetNin( this->frameset );
+      lbnd = astMalloc( sizeof( double )*(size_t)naxb );
+      ubnd = astMalloc( sizeof( double )*(size_t)naxb );
+      astRegBaseBox( this, lbnd, ubnd );
+
+/* Create a Box covering this bounding box. */
+      frmb = astGetFrame( this->frameset, AST__BASE );
+      box = astBox( frmb, 1, lbnd, ubnd, NULL, "", status );
+
+/* Loop until we have a grid of nearly the right size. Make at most three
+   attempts. */
+      ipr = 0;
+      np = meshsize;
+      ntry = 0;
+      while( ntry++ < 3 ) {
+
+/* Copy the MeshSize attribute to the new Box since this will be used by
+   the invocation of astRegBaseGrid below. */
+         astSetMeshSize( box, np );
+
+/* Invoke the Box astRegGrid method. Note, the Box class overrides this
+   implementation of astRegBaseGrid and does not (must not) invoke this
+   implementation, in order to avoid an infinite loop. */
+         ps1 = astRegBaseGrid( box );
+
+/* Some of the base Frame points in the above bounding box will fall outside
+   the supplied Region. Use the Region as a Mapping to determine which they
+   are. Since the points are base Frame points, use astBTransform rather
+   than astTransform. */
+         ps2 = astBTransform( this, ps1, 1, NULL );
+
+/* We now create a PointSet which is a copy of "ps2" but with all the bad
+   points (i.e. the points in the bounding box grid which are not inside
+   the supplied Region) removed. Create a result PointSet which is the same
+   size as "ps2", then copy just the good points from "ps2" to the result
+   PointSet, keeping a record of the number of points copied. */
+         ptr2 = astGetPoints( ps2 );
+         npnt2 = astGetNpoint( ps2 );
+         result = astPointSet( npnt2, naxb, "", status );
+         ptr = astGetPoints( result );
+         if( astOK ) {
+
+/* Initialise the index of the next point to be stored in "result". */
+            ipr = 0;
+
+/* Loop round all points in "ps2" */
+            for( ip = 0; ip < npnt2; ip++ ) {
+
+/* Copy each axis value for this point from "ps2" to "result". If a bad
+   axis value is encountered, flag that the point is bad and break out of
+   the axis loop. */
+               good = 1;
+               for( ic = 0; ic < naxb; ic++ ) {
+                  if( ptr2[ ic ][ ip ] == AST__BAD ) {
+                     good = 0;
+                     break;
+                  } else {
+                     ptr[ ic ][ ipr ] = ptr2[ ic ][ ip ];
+                  }
+               }
+
+/* If the current point has no bad axis values, increment the index of
+   the next point to be stored in "result". */
+               if( good ) ipr++;
+            }
+         }
+
+/* Free resources */
+         ps1 = astAnnul( ps1 );
+         ps2 = astAnnul( ps2 );
+
+/* Leave the loop if an error has occurred. */
+         if( !astOK ) break;
+
+/* If the number of points in the grid is within 5% of the target value,
+   it is good enough, so break. */
+         if( fabs( (double)( ipr - meshsize ) )/meshsize < 0.05 ) break;
+
+/* Otherwise, adjust the target size of the grid by the ratio by which it
+   is in error. Don't do this if we have reached the maximum number of
+   re-tries. */
+         if( ntry < 3 ) {
+            if( ipr == 0 ) {
+               np *= 10;
+            } else {
+               np *= (double)meshsize/(double)ipr;
+            }
+            result = astAnnul( result );
+         }
+      }
+
+/* Truncate the "result" PointSet to exclude any unused space at the end
+   of the axis values arrays. */
+      if( astOK ) astSetNpoint( result, ipr );
+
+/* Free resources */
+      lbnd = astFree( lbnd );
+      ubnd = astFree( ubnd );
+      frmb = astAnnul( frmb );
+      box = astAnnul( box );
+
+/* Cache the new grid for future use. */
+      if( astOK ) this->basegrid = astClone( result );
+   }
+
+/* Annul the result if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result */
+   return result;
+}
+
+static AstRegion **RegSplit( AstRegion *this, int *nlist, int *status ){
+/*
+*+
+*  Name:
+*     astRegSplit
+
+*  Purpose:
+*     Split a Region into a list of disjoint component Regions.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstRegion **astRegSplit( AstRegion *this, int *nlist )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function splits the supplied Region into a set of disjoint
+*     component Regions. If the Region cannot be split, then the returned
+*     array contains only one pointer - a clone of the supplied Region
+*     pointer.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     nlist
+*        Pointer to an int in which to return the number of elements in
+*        the returned array.
+
+*  Returned Value:
+*     Pointer to dynamically alloctaed memory holding an array of Region
+*     pointers. The length of this array is given by the value returned
+*     in "*nlist". The pointers in the returned array should be annulled
+*     using astAnnul when no longer needed, and the memory used to hold
+*     the array should be freed using astFree.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*-
+*/
+
+/* Local Variables; */
+   AstRegion **result;
+
+/* Initialise. */
+   result = NULL;
+   *nlist = 0;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* The base class just returns an array containing a clone of the
+   supplied Region pointer. */
+   result = astMalloc( sizeof( *result ) );
+   if( astOK ) {
+      result[ 0 ] = astClone( this );
+      *nlist = 1;
+   }
+
+   if( !astOK ) {
+      result = astFree( result );
+      *nlist = 0;
+   }
+
+   return result;
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this, int *status ){
+/*
+*+
+*  Name:
+*     astRegBaseMesh
+
+*  Purpose:
+*     Return a PointSet containing points spread around the boundary of a
+*     Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstPointSet *astRegBaseMesh( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a PointSet containing a set of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     Pointer to the PointSet. The axis values in this PointSet will have
+*     associated accuracies derived from the uncertainties which were
+*     supplied when the Region was created.
+*
+*    If the Region has no boundary (i.e. is equivalent to a NullRegion), the
+*    returned PointSet will contain a single point with a value of AST__BAD
+*    on every axis.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*-
+*/
+
+/* Check the local error status. */
+   if ( !astOK ) return NULL;
+
+/* This abstract method must be over-ridden by each concrete sub-class.
+   Report an error if this null imlementation is called.*/
+   astError( AST__INTER, "astRegBaseMesh(%s): The %s class does not implement "
+             "the astRegBaseMesh method inherited from the Region class "
+             "(internal AST programming error).", status, astGetClass( this ),
+             astGetClass( this ) );
+   return NULL;
+}
+
+static AstRegion *RegBasePick( AstRegion *this, int naxes, const int *axes,
+                               int *status ){
+/*
+*+
+*  Name:
+*     astRegBasePick
+
+*  Purpose:
+*     Return a Region formed by picking selected base Frame axes from the
+*     supplied Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstRegion *astRegBasePick( AstRegion *this, int naxes, const int *axes )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function attempts to return a Region that is spanned by selected
+*     axes from the base Frame of the encapsulated FrameSet of the supplied
+*     Region. This may or may not be possible, depending on the class of
+*     Region. If it is not possible a NULL pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     naxes
+*        The number of base Frame axes to select.
+*     axes
+*        An array holding the zero-based indices of the base Frame axes
+*        that are to be selected.
+
+*  Returned Value:
+*     Pointer to the Region, or NULL if no region can be formed.
+
+*  Notes:
+*    - This base implementation returns NULL unless all base Frame axes
+*    are selected (possibly in a permuted order).
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*-
+*/
+/* Local Variables: */
+   AstFrame *fr;        /* Pointer to the Region's base Frame */
+   AstRegion *result;   /* The returned Region pointer */
+   int found;           /* Has the current axis index been found yet? */
+   int i;               /* Axis index */
+   int j;               /* Index into the "axes" array */
+   int nax;             /* No. of base Frame axes */
+   int ok;              /* Are we doing a genuine axis permutation? */
+   int unit;            /* Is the axis permutation a unit map? */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the base Frame int he encapsulated FrameSet. */
+   fr = astGetFrame( this->frameset, AST__BASE );
+
+/* See how many axes it has. We only proceed if we are selecting all axes
+   in the base Frame. */
+   nax = astGetNaxes( fr );
+   if( nax == naxes ) {
+
+/* We now check that the axes array is a genuine permutation of all axes.
+   This means that all axis indices must occur once, and only once, within
+   the "axes" array. Look for each axis index in turn. */
+      unit = 1;
+      ok = 1;
+      for( i = 0; i < nax && ok; i++ ) {
+
+/* Check each element of the axes array to see if it holds the axis index
+   currently being looked for. */
+         found = 0;
+         for( j = 0; j < nax; j++ ) {
+
+/* If so, if this axis index has already been found, break out of the
+   loop. */
+            if( axes[ j ] == i ) {
+               if( found ) {
+                  ok = 0;
+                  break;
+               }
+               found = 1;
+
+/* Note if we do not have a unit map (i.e. each axis is permuted onto itself). */
+               if( i != j ) unit = 0;
+            }
+         }
+
+/* If the axis index was not found, we do not have a genuine axis
+   permutation. */
+         if( !found ) ok = 0;
+      }
+
+/* If we have a genuine axis permutation, create a Region which is a copy
+   of the supplied region and set it to represent its base Frame. */
+      if( ok ) {
+         result = astCopy( this );
+         astSetRegFS( result, fr );
+
+/* If the axis selection is not equivalent to a unit mapping, we now
+   permute the axes. */
+         if( !unit ) astPermAxes( result, axes );
+      }
+   }
+
+/* Free resources. */
+   fr = astAnnul( fr );
+
+/* Returned the result. */
+   return result;
+}
+
+static double *RegCentre( AstRegion *this, double *cen, double **ptr,
+                          int index, int ifrm, int *status ){
+/*
+*+
+*  Name:
+*     astRegCentre
+
+*  Purpose:
+*     Re-centre a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     double *astRegCentre( AstRegion *this, double *cen, double **ptr,
+*                           int index, int ifrm )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function shifts the centre of the supplied Region to a
+*     specified position, or returns the current centre of the Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     cen
+*        Pointer to an array of axis values, giving the new centre.
+*        Supply a NULL value for this in order to use "ptr" and "index" to
+*        specify the new centre.
+*     ptr
+*        Pointer to an array of pointers, one for each axis in the Region.
+*        Each pointer locates an array of axis values. This is the format
+*        returned by the PointSet method astGetPoints. Only used if "cen"
+*        is NULL.
+*     index
+*        The index of the point within the arrays identified by "ptr" at
+*        which is stored the coords for the new centre position. Only used
+*        if "cen" is NULL.
+*     ifrm
+*        Should be AST__BASE or AST__CURRENT. Indicates whether the centre
+*        position is supplied and returned in the base or current Frame of
+*        the FrameSet encapsulated within "this".
+
+*  Returned Value:
+*     If both "cen" and "ptr" are NULL then a pointer to a newly
+*     allocated dynamic array is returned which contains the centre
+*     coords of the Region. This array should be freed using astFree when
+*     no longer needed. If either of "ptr" or "cen" is not NULL, then a
+*     NULL pointer is returned.
+
+*  Notes:
+*    - Any bad (AST__BAD) centre axis values are ignored. That is, the
+*    centre value on such axes is left unchanged.
+*    - Some Region sub-classes do not have a centre. Such classes will report
+*    an AST__INTER error code if this method is called with either "ptr" or
+*    "cen" not NULL. If "ptr" and "cen" are both NULL, then no error is
+*    reported if this method is invoked on a Region of an unsuitable class,
+*    but NULL is always returned.
+
+*-
+*/
+
+/* Local Variables: */
+   double *result;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* This abstract method must be over-ridden by each concrete sub-class
+   which allows the centre to be shifted. Report an error if this null
+   imlementation is called to set a new centre. If it is called to
+   enquire the current centre, then return a NULL pointer. */
+   if( ptr || cen ) astError( AST__INTER, "astRegCentre(%s): The %s "
+                       "class does not implement the astRegCentre method "
+                       "inherited from the Region class (internal AST "
+                       "programming error).", status, astGetClass( this ),
+                       astGetClass( this ) );
+
+   return NULL;
+}
+
+static void RegClearAttrib( AstRegion *this, const char *aattrib,
+                            char **base_attrib, int *status ) {
+/*
+*+
+*  Name:
+*     astRegClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astRegClearAttrib( AstRegion *this, const char *aattrib,
+*                             char **base_attrib )
+
+*  Class Membership:
+*     Region virtual function
+
+*  Description:
+*     This function clears the value of a named attribute in both the base
+*     and current Frame in the FrameSet encapsulated within a Region, without
+*     remapping either Frame.
+*
+*     No error is reported if the attribute is not recognised by the base
+*     Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     aattrib
+*        Pointer to a null terminated string holding the attribute name.
+*     base_attrib
+*        Address of a location at which to return a pointer to the null
+*        terminated string holding the attribute name which was cleared in
+*        the base Frame of the encapsulated FrameSet. This may differ from
+*        the supplied attribute if the supplied attribute contains an axis
+*        index and the current->base Mapping in the FrameSet produces an
+*        axis permutation. The returned pointer should be freed using
+*        astFree when no longer needed. A NULL pointer may be supplied in
+*        which case no pointer is returned.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstMapping *junkmap;
+   AstMapping *map;
+   AstRegion *unc;
+   char *attrib;
+   char *battrib;
+   char buf1[ 100 ];
+   int *outs;
+   int axis;
+   int baxis;
+   int i;
+   int len;
+   int nc;
+   int rep;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Produce a lower case version of the attribute name string */
+   nc = strlen( aattrib );
+   attrib = astMalloc( nc + 1 );
+   for( i = 0; i < nc; i++ ) attrib[ i ] = tolower( aattrib[ i ] );
+   attrib[ nc ] = 0;
+
+/* Clear the attribute in the current Frame in the encapsulated FrameSet.
+   Use the protected astClearAttrib method which does not cause the Frame
+   to be remapped within the FrameSet. */
+   frm = astGetFrame( this->frameset, AST__CURRENT );
+   astClearAttrib( frm, attrib );
+   frm = astAnnul( frm );
+
+/* Indicate that we should use the supplied attribute name  with the base Frame. */
+   battrib = NULL;
+
+/* If the attribute name contains an axis number, we need to create a new
+   attribute name which refers to the corresponding base Frame axis
+   (since the base<->current Mapping may permute the axes). First parse the
+   supplied attribute name to locate any axis index. */
+   len = strlen( attrib );
+   if( nc = 0, ( 2 == astSscanf( attrib, "%[^(](%d) %n", buf1, &axis,
+                                 &nc ) ) && ( nc >= len ) ) {
+
+/* If found, convert the axis index from one-based to zero-based. */
+      axis--;
+
+/* See if the specified current Frame axis is connected to one and only
+   one base Frame axis. If so, get the index of the base Frame axis. */
+      map = astGetMapping( this->frameset, AST__CURRENT, AST__BASE );
+      outs = astMapSplit( map, 1, &axis, &junkmap );
+      if( junkmap && astGetNout( junkmap ) == 1 ) {
+         baxis = outs[ 0 ];
+
+/* If the base Frame axis index is different to the current Frame axis
+   index, create a new attribute name string using the base Frame axis index. */
+         if( baxis != axis ) {
+            battrib = astMalloc( strlen( attrib ) + 10 );
+            if( battrib ) sprintf( battrib, "%s(%d)", buf1, baxis + 1 );
+         }
+
+/* If there is no one base Frame axis which corresponds to the supplied
+   current Frame axis, report an error. */
+      } else if( astOK ) {
+         astError( AST__INTER, "astRegClearAttrib(%s): Unable to clear "
+                   "attribute \"%s\" in the base Frame of the %s", status,
+                   astGetClass( this ), attrib, astGetClass( this ) );
+         astError( AST__INTER, "There is no base Frame axis corresponding "
+                   "to current Frame axis %d\n", status, axis + 1 );
+      }
+
+/* Free resources */
+      outs = astFree( outs );
+      if( junkmap ) junkmap = astAnnul( junkmap );
+      map = astAnnul( map );
+   }
+
+/* Clear the appropriate attribute name in the base Frame. This time ensure
+   that any error caused by the attribute name is annulled. Also clear it in
+   any uncertainty Region (the current Frame of the uncertainty Region is
+   assumed to be equivalent to the base Frame of the parent Region). */
+   frm = astGetFrame( this->frameset, AST__BASE );
+   if( frm ) {
+      rep = astReporting( 0 );
+      astClearAttrib( frm, battrib ? battrib : attrib );
+      if( astTestUnc( this ) ) {
+         unc = astGetUncFrm( this, AST__BASE );
+         astRegClearAttrib( unc, battrib ? battrib : attrib, NULL );
+         unc = astAnnul( unc );
+      }
+      if( astStatus == AST__BADAT ) astClearStatus;
+      astReporting( rep );
+   }
+   frm = astAnnul( frm );
+
+/* If required return the modified base Frame attribute name. Otherwise,
+   free it. */
+   if( base_attrib ) {
+      if( battrib ) {
+         *base_attrib = battrib;
+      } else {
+         *base_attrib = astStore( NULL, attrib, strlen( attrib ) + 1 );
+      }
+   } else {
+      battrib = astFree( battrib );
+   }
+
+/* Since the base Frame has been changed, any cached information calculated
+   on the basis of the base Frame properties may no longer be up to date. */
+   astResetCache( this );
+
+/* Free resources. */
+   attrib = astFree( attrib );
+
+}
+
+static AstPointSet *RegGrid( AstRegion *this, int *status ){
+/*
+*+
+*  Name:
+*     astRegGrid
+
+*  Purpose:
+*     Return a PointSet containing points spread through the volume of a
+*     Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstPointSet *astRegGrid( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points spread
+*     throughout the volume of the Region. The points refer to the current
+*     Frame of the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     Pointer to the PointSet. The axis values in this PointSet will have
+*     associated accuracies derived from the uncertainties which were
+*     supplied when the Region was created. Annul the pointer using
+*     astAnnul when it is no longer needed.
+
+*  Notes:
+*    - It should not be assumed that the returned points are evenly
+*    spaced withint he volume.
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*-
+*/
+
+/* Local Variables; */
+   AstMapping *map;          /* Base -> current Frame Mapping */
+   AstPointSet *result;      /* Pointer to returned PointSet */
+
+/* Initialise the returned pointer */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* If the Region structure does not contain a pointer to a PointSet holding
+   positions evenly spread over the volume of the Region in the base
+   Frame, create one now. Note, we cannot cache the grid in the current
+   Frame in this way since the current Frame grid depends on the proprties
+   of the current Frame (e.g. System) which can be changed at any time. */
+   if( !this->basegrid ) this->basegrid = astRegBaseGrid( this );
+
+/* Get the simplified base->current Mapping */
+   map = astRegMapping( this );
+
+/* If the Mapping is a UnitMap, just return a clone of the PointSet
+   pointer stored in the Region structure. */
+   if( astIsAUnitMap( map ) ){
+      result = astClone( this->basegrid );
+
+/* Otherwise, create a new PointSet holding the above points transformed
+   into the current Frame. */
+   } else {
+      result = astTransform( map, this->basegrid, 1, NULL );
+   }
+
+/* Free resources.*/
+   map = astAnnul( map );
+
+/* If an error has occurred, annul the returned PointSet. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *RegMesh( AstRegion *this, int *status ){
+/*
+*+
+*  Name:
+*     astRegMesh
+
+*  Purpose:
+*     Return a PointSet containing points spread over the boundary of a
+*     Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstPointSet *astRegMesh( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the current Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     Pointer to the PointSet. The axis values in this PointSet will have
+*     associated accuracies derived from the uncertainties which were
+*     supplied when the Region was created. Annul the pointer using
+*     astAnnul when it is no longer needed.
+
+*  Notes:
+*    - It should not be assumed that the returned points are evenly
+*    spaced on the boundary.
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*-
+*/
+
+/* Local Variables; */
+   AstMapping *map;          /* Base -> current Frame Mapping */
+   AstPointSet *bmesh;       /* Base Frame mesh */
+   AstPointSet *result;      /* Pointer to returned PointSet */
+
+/* Initialise the returned pointer */
+   result = NULL;
+
+/* Check the local error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to a PointSet holding positions evenly spread over the
+   boundary of the Region in the base Frame. */
+   bmesh = astRegBaseMesh( this );
+
+/* Get the simplified base->current Mapping */
+   map = astRegMapping( this );
+
+/* If the Mapping is a UnitMap, just return a clone of the mesh PointSet
+   pointer. */
+   if( astIsAUnitMap( map ) ){
+      result = astClone( bmesh );
+
+/* Otherwise, create a new PointSet holding the above points transformed
+   into the current Frame. */
+   } else {
+      result = astTransform( map, bmesh, 1, NULL );
+   }
+
+/* Free resources.*/
+   bmesh = astAnnul( bmesh );
+   map = astAnnul( map );
+
+/* If an error has occurred, annul the returned PointSet. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int RegDummyFS( AstRegion *this, int *status ){
+/*
+*+
+*  Name:
+*     astRegDummyFS
+
+*  Purpose:
+*     Check if a Region has a dummy FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int astRegDummyFS( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a flag indicating if the supplied Region has
+*     a dummy FrameSet.
+*
+*     The astDump method for a Region may choose not to include the
+*     Region's FrameSet in the dump, depending on the value of the
+*     RegionFS attribute and the nature of the FrameSet. If the FrameSet
+*     is omitted from the Dump, then special action has to be taken when
+*     the dump is subsequently read in and used to re-create the Region.
+*     On encounterting such a dump, the astLoadRegion function will create
+*     a dummy FrameSet and associate it with the reconstructed  Region.
+*     The new Region should not be used however until this dummy FrameSet
+*     has been replaced by the correct FrameSet. Performing this replacement
+*     is the responsibility of the parent class (i.e. the class which choose
+*     to omit the FrameSet from the dump). These will usually be Region
+*     classes which encapsulate other Regions, such as CmpRegion, Prism,
+*     Stc, etc.
+*
+*     This function can be used by astLoad... methods in sub-classes to
+*     determine if a newly loaded component Region has a dummy FrameSet. If
+*     so the astLoad function should either use the astSetRegFS method to
+*     store a new FrameSet in the component Region. If the parent Region
+*     itself has a dummy FrameSet (i.e. is a component Region contained
+*     within a higher level Region) then it cannot do this and should
+*     ignore the presence of the dummy FrameSet (it then becomes the
+*     responsibility of hte parent Region to load appropriate FrameSets
+*     into all its components).
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     Non-zero if the Region has a dummy FrameSet.
+
+*-
+*/
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* The Ident attribute of the FrameSet will be set to DUMMY_FS if the
+   FrameSet is a dummy. */
+   return !strcmp( astGetIdent( this->frameset ), DUMMY_FS );
+}
+
+static int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+                    int **mask, int *status ){
+/*
+*+
+*  Name:
+*     astRegPins
+
+*  Purpose:
+*     Check if a set of points fall on the boundary of a given Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int astRegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+*                     int **mask )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function returns a flag indicating if the supplied set of
+*     points all fall on the boundary of the given Region.
+*
+*     Some tolerance is allowed, as specified by the uncertainty Region
+*     stored in the supplied Region "this", and the supplied uncertainty
+*     Region "unc" which describes the uncertainty of the supplied points.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     pset
+*        Pointer to the PointSet. The points are assumed to refer to the
+*        base Frame of the FrameSet encapsulated by "this".
+*     unc
+*        Pointer to a Region representing the uncertainties in the points
+*        given by "pset". The Region is assumed to represent the base Frame
+*        of the FrameSet encapsulated by "this". Zero uncertainity is assumed
+*        if NULL is supplied.
+*     mask
+*        Pointer to location at which to return a pointer to a newly
+*        allocated dynamic array of ints. The number of elements in this
+*        array is equal to the value of the Npoint attribute of "pset".
+*        Each element in the returned array is set to 1 if the
+*        corresponding position in "pset" is on the boundary of the Region
+*        and is set to zero otherwise. A NULL value may be supplied
+*        in which case no array is created. If created, the array should
+*        be freed using astFree when no longer needed.
+
+*  Returned Value:
+*     Non-zero if the points all fall on the boundary of the given
+*     Region, to within the tolerance specified. Zero otherwise.
+
+*-
+*/
+
+/* Check the inherited status. */
+   if( !astOK ) return 0;
+
+/* This abstract implementation simply reports an error. All sub-classes of
+   Region should over-ride this to return appropriate values. */
+   astError( AST__INTER, "astRegPins(%s): The %s class does not implement "
+             "the astRegPins method inherited from the Region class "
+             "(internal AST programming error).", status, astGetClass( this ),
+             astGetClass( this ) );
+   return 0;
+}
+
+static void GetRegionBounds( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+/*
+*++
+*  Name:
+c     astGetRegionBounds
+f     AST_GETREGIONBOUNDS
+
+*  Purpose:
+*     Returns the bounding box of Region.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     void astGetRegionBounds( AstRegion *this, double *lbnd, double *ubnd )
+f     CALL AST_GETREGIONBOUNDS( THIS, LBND, UBND, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns the upper and lower limits of a box which just encompasses
+*     the supplied Region. The limits are returned as axis values within
+*     the Frame represented by the Region. The value of the Negated
+*     attribute is ignored (i.e. it is assumed that the Region has not
+*     been negated).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+c     lbnd
+f     LBND() = DOUBLE PRECISION (Returned)
+c        Pointer to an
+f        An
+*        array in which to return the lower axis bounds covered by the Region.
+*        It should have at least as many elements as there are axes in the
+*        Region. If an axis has no lower limit, the returned value will
+*        be the largest possible negative value.
+c     ubnd
+f     UBND() = DOUBLE PRECISION (Returned)
+c        Pointer to an
+f        An
+*        array in which to return the upper axis bounds covered by the Region.
+*        It should have at least as many elements as there are axes in the
+*        Region. If an axis has no upper limit, the returned value will
+*        be the largest possible positive value.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*    - The value of the Negated attribute is ignored (i.e. it is assumed that
+*    the Region has not been negated).
+*    - If an axis has no extent on an axis then the lower limit will be
+*    returned larger than the upper limit. Note, this is different to an
+*    axis which has a constant value (in which case both lower and upper
+*    limit will be returned set to the constant value).
+*    - If the bounds on an axis cannot be determined, AST__BAD is returned for
+*    both upper and lower bounds
+
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *frm;             /* Current Frame */
+   AstMapping *smap;          /* Simplified base -> current Mapping */
+   AstPointSet *bmesh;        /* PointSet holding base Frame mesh */
+   AstPointSet *cmesh;        /* PointSet holding current Frame mesh */
+   double **bptr;             /* Pointer to PointSet coord arrays */
+   double **cptr;             /* Pointer to PointSet coord arrays */
+   double *blbnd;             /* Lower bounds in base Frame */
+   double *bubnd;             /* Upper bounds in base Frame */
+   double *p;                 /* Array of values for current axis */
+   double width;              /* Width of bounding box on i'th axis */
+   int i;                     /* Axis count */
+   int ip;                    /* Index of current corner */
+   int j;                     /* Timer for low/high swaps */
+   int jmax;                  /* Increment between low/high swaps */
+   int lo;                    /* Assign low bound to next corner? */
+   int nbase;                 /* Number of base Frame axes */
+   int ncur;                  /* Number of current Frame axes */
+   int npos;                  /* Number of box corners */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Get the simplified base to current Mapping. */
+   smap = astRegMapping( this );
+
+/* If the simplified Mapping is a UnitMap, just store the base box bounds
+   in the returned arrays */
+   if( astIsAUnitMap( smap ) ) {
+      astRegBaseBox( this, lbnd, ubnd );
+
+/* Otherwise, we get a mesh of points over the boundary of the Region within
+   the base Frame, transform them into the current Frame, and find their bounds. */
+   } else {
+
+/* If the Region is bounded, we can get a genuine mesh of points on the
+   boundary of the Region. */
+      if( astGetBounded( this ) ) {
+         bmesh = astRegBaseMesh( this );
+
+/* If the Region is not bounded, no mesh can be created so we use the
+   corners of the base frame bounding box instead. */
+      } else {
+
+/* Get workspace to hold the bounds of the region within the base Frame. */
+         nbase = astGetNin( smap );
+         blbnd = astMalloc( sizeof( double )*nbase );
+         bubnd = astMalloc( sizeof( double )*nbase );
+
+/* Get the base Frame bounding box. */
+         astRegBaseBox( this, blbnd, bubnd );
+
+/* Get the number of corners in the base Frame bounding box. */
+         npos = pow( 2, nbase );
+
+/* Create a PointSet to hold the positions at the corners in the base
+   frame box. */
+         bmesh = astPointSet( npos, nbase, " ", status );
+         bptr = astGetPoints( bmesh );
+         if( bptr ) {
+
+/* Store the coordinates of the box corners in the PointSet. */
+            jmax = 1;
+            for( i = 0; i < nbase; i++ ) {
+               p = bptr[ i ];
+
+               lo = 1;
+               j = 0;
+               for( ip = 0; ip < npos; ip++,j++ ) {
+                  if( j == jmax ) {
+                     lo = 1 - lo;
+                     j = 0;
+                  }
+                  p[ ip ] = lo ? blbnd[ i ] : bubnd[ i ];
+               }
+
+               jmax *= 2;
+            }
+         }
+
+/* Release resources. */
+         blbnd = astFree( blbnd );
+         bubnd = astFree( bubnd );
+      }
+
+/* Create a new PointSet holding the above points transformed into the
+   current Frame. */
+      cmesh = astTransform( smap, bmesh, 1, NULL );
+
+/* There is a possibility that these points may span a singularity in the
+   coordinate system such as the RA=0 line in a SkyFrame. So ensure the
+   axis values are normalised into the shortest possible range. */
+      frm = astGetFrame( this->frameset, AST__CURRENT );
+      ncur = astGetNaxes( frm );
+
+      cptr = astGetPoints( cmesh );
+      npos = astGetNpoint( cmesh );
+      for( i = 0; i < ncur; i++ ) {
+         astAxNorm( frm, i+1, 1, npos, cptr[i] );
+      }
+
+/* Get the axis bounds of this PointSet. */
+      astBndPoints( cmesh, lbnd, ubnd );
+
+/* There is again a possibility that these bounds may span a singularity in
+   the coordinate system such as the RA=0 line in a SkyFrame. So for each
+   axis we ensure the width (i.e. "ubnd-lbnd" ) is correct. */
+      for( i = 0; i < ncur; i++ ) {
+         width = astAxDistance( frm, i + 1, lbnd[ i ], ubnd[ i ] );
+         if( width != AST__BAD ) {
+            ubnd[ i ] = lbnd[ i ] + width;
+         } else {
+            ubnd[ i ] = AST__BAD;
+            lbnd[ i ] = AST__BAD;
+         }
+      }
+
+/* Release resources. */
+      frm = astAnnul( frm );
+      bmesh = astAnnul( bmesh );
+      cmesh = astAnnul( cmesh );
+   }
+   smap = astAnnul( smap );
+}
+
+static void GetRegionBounds2( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+/*
+*+
+*  Name:
+*     astGetRegionBounds
+
+*  Purpose:
+*     Returns the bounding box of Region.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astGetRegionBounds2( AstRegion *this, double *lbnd, double *ubnd )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     This function is like astGetRegionBounds, in that it returns the upper
+*     and lower limits of a box which just encompasses the supplied Region,
+*     as axis values within the Frame represented by the Region. But, in
+*     addition to assuming that the supplied Region has not been negated, it
+*     also assumes that any component Regions contained within the supplied
+*     Region have not been negated.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region. It should have at least as many elements
+*        as there are axes in the Region.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region. It should have at least as many elements
+*        as there are axes in the Region.
+
+*  Notes:
+*    - The value of the Negated attribute is ignored (i.e. it is assumed that
+*    the Region has not been negated). The Nagated attributes of any
+*    component Regions are also ignored.
+
+*-
+*/
+
+/* Local Variables: */
+   AstMapping *smap;          /* Simplified base -> current Mapping */
+   double *lbndb;             /* Pointer to lower bounds on base box */
+   double *ubndb;             /* Pointer to upper bounds on base box */
+   int i;                     /* Axis count */
+   int nbase;                 /* Number of base Frame axes */
+   int ncur;                  /* Number of current Frame axes */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Find the number of axes in the base and current Frames of the
+   encapsulated FrameSet. */
+   nbase = astGetNin( this->frameset );
+   ncur = astGetNout( this->frameset );
+
+/* Get the bounding box in the base Frame of the encapsulated FrameSet. */
+   lbndb = astMalloc( sizeof( double )*(size_t) nbase );
+   ubndb = astMalloc( sizeof( double )*(size_t) nbase );
+   astRegBaseBox2( this, lbndb, ubndb );
+
+/* Get the simplified base to current Mapping. */
+   smap = astRegMapping( this );
+
+/* Check pointers can be used safely. */
+   if( smap ) {
+
+/* If the simplified Mapping is a UnitMap, just copy the base box bounds
+   to the returned arrays */
+      if( astIsAUnitMap( smap ) ) {
+         for( i = 0; i < ncur; i++ ) {
+            lbnd[ i ] = lbndb[ i ];
+            ubnd[ i ] = ubndb[ i ];
+         }
+
+/* Otherwise, use astMapBox to find the corresponding current Frame
+   limits. */
+      } else {
+         for( i = 0; i < ncur; i++ ) {
+            astMapBox( smap, lbndb, ubndb, 1, i, lbnd + i, ubnd + i,
+                       NULL, NULL );
+         }
+      }
+   }
+
+/* Release resources. */
+   smap = astAnnul( smap );
+   lbndb = astFree( lbndb );
+   ubndb = astFree( ubndb );
+}
+
+static void GetRegionMesh( AstRegion *this, int surface, int maxpoint,
+                           int maxcoord, int *npoint, double *points,
+                           int *status ){
+/*
+*++
+*  Name:
+c     astGetRegionMesh
+f     AST_GETREGIONMESH
+
+*  Purpose:
+*     Return a mesh of points covering the surface or volume of a Region.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     void astGetRegionMesh( AstRegion *this, int surface, int maxpoint,
+c                            int maxcoord, int *npoint, double *points )
+f     CALL AST_GETREGIONMESH( THIS, SURFACE, MAXPOINT, MAXCOORD, NPOINT,
+f                             POINTS, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns the axis values at a mesh of points either covering the
+*     surface (i.e. boundary) of the supplied Region, or filling the
+*     interior (i.e. volume) of the Region. The number of points in
+*     the mesh is approximately equal to the MeshSize attribute.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+c     surface
+f     SURFACE = LOGICAL (Given)
+c        If non-zero,
+f        If .TRUE.,
+*        the returned points will cover the surface or the Region.
+*        Otherwise, they will fill the interior of the Region.
+c     maxpoint
+f     MAXPOINT = INTEGER (Given)
+*        If zero, the number of points in the mesh is returned in
+c        "*npoint",
+f        NPOINT,
+*        but no axis values are returned and all other parameters are ignored.
+*        If not zero, the supplied value should be the length of the
+c        second dimension of the "points"
+f        first dimension of the POINTS
+*        array. An error is reported if the number of points in the mesh
+*        exceeds this number.
+c     maxcoord
+f     MAXCOORD = INTEGER (Given)
+*        The length of the
+c        first dimension of the "points" array.
+f        second dimension of the POINTS array.
+*        An error is reported if the number of axes in the supplied Region
+*        exceeds this number.
+c     npoint
+f     NPOINT = INTEGER (Returned)
+c        A pointer to an integer in which to return the
+f        The
+*        number of points in the returned mesh.
+c     points
+f     POINTS( MAXPOINT, MAXCOORD ) = DOUBLE PRECISION (Returned)
+c        The address of the first element in a 2-dimensional array of
+c        shape "[maxcoord][maxpoint]", in which to return the coordinate
+c        values at the mesh positions. These are stored such that the
+c        value of coordinate number "coord" for point number "point" is
+c        found in element "points[coord][point]".
+f        An array in which to return the coordinates values at the mesh
+f        positions. These are stored such that the value of coordinate
+f        number COORD for point number POINT is found in element
+f        POINTS(POINT,COORD).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - An error is reported if the Region is unbounded.
+*     - If the coordinate system represented by the Region has been
+*     changed since it was first created, the returned axis values refer
+*     to the new (changed) coordinate system, rather than the original
+*     coordinate system. Note however that if the transformation from
+*     original to new coordinate system is non-linear, the shape within
+*     the new coordinate system may be distorted, and so may not match
+*     that implied by the name of the Region subclass (Circle, Box, etc).
+
+*--
+*/
+
+/* Local Variables: */
+   AstPointSet *pset;       /* PointSet holding mesh/grid axis values */
+   double **ptr;            /* Pointer to mesh/grid axes values */
+   double *p;               /* Pointer to next input axis value */
+   double *q;               /* Pointer to next output axis value */
+   int j;                   /* Axis index */
+   int nc;                  /* No. of axes to copy */
+
+/* Initialise */
+   *npoint = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Report an error if the Region is unbounded. */
+   if( !astGetBounded( this ) ) {
+      if( astOK ) astError( AST__MBBNF, "astGetRegionMesh(%s): The supplied %s"
+                            " is unbounded so no mesh can be created to cover "
+                            "it.", status, astGetClass( this ), astGetClass( this ) );
+   } else {
+
+/* Get the mesh or grid as required. If only the size of the mesh or grid
+   is required, get it in the base Frame as there is no need to spend the
+   extra time transforming it into the current Frame. */
+      if( maxpoint == 0  ){
+         if( surface ) {
+            pset = astRegBaseMesh( this );
+         } else {
+            pset = astRegBaseGrid( this );
+         }
+      } else {
+         if( surface ) {
+            pset = astRegMesh( this );
+         } else {
+            pset = astRegGrid( this );
+         }
+      }
+
+/* Return the number of points in the mesh or grid. */
+      *npoint = astGetNpoint( pset );
+
+/* Do nothing more unless a non-zero array size was supplied. */
+      if( *npoint > 0 && maxpoint != 0 && astOK ) {
+
+/* Check the supplied array is large enough. */
+         if( *npoint > maxpoint ) {
+            astError( AST__DIMIN, "astGetRegionMesh(%s): The supplied "
+                      "array can hold up to %d points but the %s supplied "
+                      "has %d points on its mesh (programming error).",
+                      status, astGetClass( this ), maxpoint, astGetClass( this ),
+                      *npoint );
+         }
+
+/* Get the dimensionality of the PointSet, and get a pointer to the axis
+   values. */
+         nc = astGetNcoord( pset );
+         ptr = astGetPoints( pset );
+
+/* Check pointers can be used safely. */
+         if ( astOK ) {
+
+/* Check the supplied array has room for all the axes. */
+            if( nc > maxcoord ) {
+               astError( AST__DIMIN, "astGetRegionMesh(%s): The supplied "
+                         "array can hold up to %d axes but the %s supplied "
+                         "has %d axes (programming error).", status,
+                         astGetClass( this ), maxcoord, astGetClass( this ), nc );
+
+/* If all is OK, copy the current Frame axis values into the supplied array. */
+            } else {
+
+/* Loop round the axes to be copied. */
+               for( j = 0; j < nc; j++ ) {
+
+/* Get points to the first element of the input and output arrays. */
+                  p = ptr[ j ];
+                  q = points + j*maxpoint;
+
+/* Copying the axis values. */
+                  (void) memcpy( q, p, sizeof( double )*( *npoint ) );
+               }
+            }
+         }
+      }
+
+/* Free resources. */
+      pset = astAnnul( pset );
+   }
+}
+
+static void GetRegionPoints( AstRegion *this, int maxpoint, int maxcoord,
+                             int *npoint, double *points, int *status ){
+/*
+*++
+*  Name:
+c     astGetRegionPoints
+f     AST_GETREGIONPOINTS
+
+*  Purpose:
+*     Returns the positions that define the given Region.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     void astGetRegionPoints( AstRegion *this, int maxpoint, int maxcoord,
+c                              int *npoint, double *points )
+f     CALL AST_GETREGIONPOINTS( THIS, MAXPOINT, MAXCOORD, NPOINT, POINTS,
+f                               STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns the axis values at the points that define the supplied
+*     Region. The particular meaning of these points will depend on the
+*     type of class supplied, as listed below under "Applicability:".
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+c     maxpoint
+f     MAXPOINT = INTEGER (Given)
+*        If zero, the number of points needed to define the Region is
+*        returned in
+c        "*npoint",
+f        NPOINT,
+*        but no axis values are returned and all other parameters are ignored.
+*        If not zero, the supplied value should be the length of the
+c        second dimension of the "points"
+f        first dimension of the POINTS
+*        array. An error is reported if the number of points needed to define
+*        the Region exceeds this number.
+c     maxcoord
+f     MAXCOORD = INTEGER (Given)
+*        The length of the
+c        first dimension of the "points" array.
+f        second dimension of the POINTS array.
+*        An error is reported if the number of axes in the supplied Region
+*        exceeds this number.
+c     npoint
+f     NPOINT = INTEGER (Returned)
+c        A pointer to an integer in which to return the
+f        The
+*        number of points defining the Region.
+c     points
+f     POINTS( MAXPOINT, MAXCOORD ) = DOUBLE PRECISION (Returned)
+c        The address of the first element in a 2-dimensional array of
+c        shape "[maxcoord][maxpoint]", in which to return
+c        the coordinate values at the positions that define the Region.
+c        These are stored such that the value of coordinate number
+c        "coord" for point number "point" is found in element
+c        "points[coord][point]".
+f        An array in which to return the coordinates values at the
+f        positions that define the Region. These are stored such that the
+f        value of coordinate number COORD for point number POINT
+f        is found in element POINTS(POINT,COORD).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Region
+*        All Regions have this attribute.
+*     Box
+*        The first returned position is the Box centre, and the second is
+*        a Box corner.
+*     Circle
+*        The first returned position is the Circle centre, and the second is
+*        a point on the circumference.
+*     CmpRegion
+*        Returns a value of zero for
+c        "*npoint"
+f        NPOINT
+*        and leaves the supplied array contents unchanged. To find the
+*        points defining a CmpRegion, use this method on the component
+*        Regions, which can be accessed by invoking
+c        astDecompose
+f        AST_DECOMPOSE
+*        on the CmpRegion.
+*     Ellipse
+*        The first returned position is the Ellipse centre. The second is
+*        the end of one of the axes of the ellipse. The third is some
+*        other point on the circumference of the ellipse, distinct from
+*        the second point.
+*     Interval
+*        The first point corresponds to the lower bounds position, and
+*        the second point corresponds to the upper bounds position. These
+*        are reversed to indicate an extcluded interval rather than an
+*        included interval. See the Interval constructor for more
+*        information.
+*     NullRegion
+*        Returns a value of zero for
+c        "*npoint"
+f        NPOINT
+*        and leaves the supplied array contents unchanged.
+*     PointList
+*        The positions returned are those that were supplied when the
+*        PointList was constructed.
+*     Polygon
+*        The positions returned are the vertex positions that were supplied
+*        when the Polygon was constructed.
+*     Prism
+*        Returns a value of zero for
+c        "*npoint"
+f        NPOINT
+*        and leaves the supplied array contents unchanged. To find the
+*        points defining a Prism, use this method on the component
+*        Regions, which can be accessed by invoking
+c        astDecompose
+f        AST_DECOMPOSE
+*        on the CmpRegion.
+
+*  Notes:
+*     - If the coordinate system represented by the Region has been
+*     changed since it was first created, the returned axis values refer
+*     to the new (changed) coordinate system, rather than the original
+*     coordinate system. Note however that if the transformation from
+*     original to new coordinate system is non-linear, the shape within
+*     the new coordinate system may be distorted, and so may not match
+*     that implied by the name of the Region subclass (Circle, Box, etc).
+
+*--
+*/
+
+/* Local Variables: */
+   AstPointSet *pset;       /* PointSet holding PointList axis values */
+   double **ptr;            /* Pointer to axes values in the PointList */
+   double *p;               /* Pointer to next input axis value */
+   double *q;               /* Pointer to next output axis value */
+   int j;                   /* Axis index */
+   int nc;                  /* No. of axes to copy */
+
+/* Initialise */
+   *npoint = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Return the number of points used to define the Region, if any. */
+   *npoint = this->points ? astGetNpoint( this->points ) : 0;
+
+/* Do nothing more unless a non-zero array size was supplied. */
+   if( *npoint > 0 && maxpoint != 0 ) {
+
+/* Transform the base Frame axis values into the current Frame. */
+      pset = astTransform( this->frameset, this->points, 1, NULL );
+
+/* Get the dimensionality of this PointList, and get a pointer to the axis
+   values. */
+      nc = astGetNcoord( pset );
+      ptr = astGetPoints( pset );
+
+/* Check pointers can be used safely. */
+      if ( astOK ) {
+
+/* Check the supplied array has room for all the axis values. */
+         if( nc > maxcoord ) {
+            astError( AST__DIMIN, "astGetRegionPoints(%s): The supplied "
+                      "array can hold up to %d axes but the %s supplied "
+                      "has %d axes (programming error).", status,
+                      astGetClass( this ), maxcoord, astGetClass( this ), nc );
+
+         } else if( *npoint > maxpoint ) {
+            astError( AST__DIMIN, "astGetRegionPoints(%s): The supplied "
+                      "array can hold up to %d points but the %s supplied "
+                      "requires %d points to describe it (programming "
+                      "error).", status, astGetClass( this ), maxpoint,
+                       astGetClass( this ), *npoint );
+
+/* If all is OK, copy the transformed axis values into the supplied array. */
+         } else {
+
+/* Loop round the axes to be copied. */
+            for( j = 0; j < nc; j++ ) {
+
+/* Get points to the first element of the input and output arrays. */
+               p = ptr[ j ];
+               q = points + j*maxpoint;
+
+/* Copying the axis values. */
+               (void) memcpy( q, p, sizeof( double )*( *npoint ) );
+            }
+         }
+      }
+
+/* Free resources. */
+      pset = astAnnul( pset );
+
+   }
+}
+
+static void RegOverlay( AstRegion *this, AstRegion *that, int unc, int *status ){
+/*
+*+
+*  Name:
+*     astRegOverlay
+
+*  Purpose:
+*     Copy properties from one Region to another.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astRegOverlay( AstRegion *this, AstRegion *that, int unc )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function copies selected properties from "that" to "this".
+*     It is intended to be called by sub-classes which need to create a
+*     similar copy of an existing Region. For instance, subclass
+*     implementations of the Simplify method will usually use this
+*     function to ensure that the simplified Region loooks like the original
+*     Region.
+
+*  Parameters:
+*     this
+*        Pointer to the new Region.
+*     that
+*        Pointer to the old Region.
+*     unc
+*        If non-zero, any uncertainty in "this" is cleared if "that" has
+*        no uncertainty. If zero, any uncertainty in "this" is left
+*        unchanged.
+*-
+*/
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Copy the required attribute values. */
+   this->negated = that->negated;
+   this->closed = that->closed;
+   this->regionfs = that->regionfs;
+   this->adaptive = that->adaptive;
+
+/* Clear things that depend on the number of axes. */
+   if( astGetNaxes( this ) == astGetNaxes( that ) ) {
+      if( astTestMeshSize( that ) ) astSetMeshSize( this, astGetMeshSize( that ) );
+      if( astTestFillFactor( that ) ) astSetFillFactor( this, astGetFillFactor( that ) );
+   } else {
+      astClearMeshSize( this );
+      astClearFillFactor( this );
+   }
+
+/* If required, clear uncertainty in "this" if "that" has no uncertainty. */
+   if( unc && !astTestUnc( that ) ) astClearUnc( this );
+
+}
+
+static void RegSetAttrib( AstRegion *this, const char *asetting,
+                          char **base_setting, int *status ) {
+/*
+*+
+*  Name:
+*     astRegSetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astRegSetAttrib( AstRegion *this, const char *asetting,
+*                           char **base_setting )
+
+*  Class Membership:
+*     Region virtual function
+
+*  Description:
+*     This function assigns an attribute value to both the base and
+*     current Frame in the FrameSet encapsulated within a Region, without
+*     remapping either Frame.
+*
+*     No error is reported if the attribute is not recognised by the base
+*     Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     asetting
+*        Pointer to a null terminated attribute setting string. The supplied
+*        string will be interpreted using the public interpretation
+*        implemented by astSetAttrib. This can be different to the
+*        interpretation of the protected accessor functions. For instance,
+*        the public interpretation of an unqualified floating point value for
+*        the Epoch attribute is to interpet the value as a gregorian year,
+*        but the protected interpretation is to interpret the value as an
+*        MJD.
+*     base_setting
+*        Address of a location at which to return a pointer to the null
+*        terminated attribute setting string which was applied to the
+*        base Frame of the encapsulated FrameSet. This may differ from
+*        the supplied setting if the supplied setting contains an axis
+*        index and the current->base Mapping in the FrameSet produces an
+*        axis permutation. The returned pointer should be freed using
+*        astFree when no longer needed. A NULL pointer may be supplied in
+*        which case no pointer is returned.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstMapping *junkmap;
+   AstMapping *map;
+   AstRegion *unc;
+   char *setting;
+   char *bsetting;
+   char buf1[ 100 ];
+   int *outs;
+   int axis;
+   int baxis;
+   int i;
+   int len;
+   int nc;
+   int rep;
+   int value;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Produce a lower case version of the setting string */
+   nc = strlen( asetting );
+   setting = astMalloc( nc + 1 );
+   for( i = 0; i < nc; i++ ) setting[ i ] = tolower( asetting[ i ] );
+   setting[ nc ] = 0;
+
+/* Apply the setting to the current Frame in the encapsulated FrameSet.
+   Use the protected astSetAttrib method which does not cause the Frame
+   to be remapped within the FrameSet. */
+   frm = astGetFrame( this->frameset, AST__CURRENT );
+   astSetAttrib( frm, setting );
+   frm = astAnnul( frm );
+
+/* Indicate that we should use the supplied setting with the base Frame. */
+   bsetting = NULL;
+
+/* If the attribute name contains an axis number, we need to create a new
+   attribute setting which refers to the corresponding base Frame axis
+   (since the base<->current Mapping may permute the axes). First parse the
+   supplied attribute setting to locate any axis index. */
+   len = strlen( setting );
+   if( nc = 0, ( 2 == astSscanf( setting, "%[^(](%d)= %n%*s %n", buf1, &axis,
+                                 &value, &nc ) ) && ( nc >= len ) ) {
+
+/* If found, convert the axis index from one-based to zero-based. */
+      axis--;
+
+/* See if the specified current Frame axis is connected to one and only
+   one base Frame axis. If so, get the index of the base Frame axis. */
+      map = astGetMapping( this->frameset, AST__CURRENT, AST__BASE );
+      outs = astMapSplit( map, 1, &axis, &junkmap );
+      if( junkmap && astGetNout( junkmap ) == 1 ) {
+         baxis = outs[ 0 ];
+
+/* If the base Frame axis index is different to the current Frame axis
+   index, create a new setting string using the base Frame axis index. */
+         if( baxis != axis ) {
+            bsetting = astMalloc( strlen( setting ) + 10 );
+            if( bsetting ) {
+               sprintf( bsetting, "%s(%d)=%s", buf1, baxis + 1, setting + value );
+            }
+         }
+
+/* If there is no one base Frame axis which corresponds to the supplied
+   current Frame axis, report an error. */
+      } else if( astOK ) {
+         astError( AST__INTER, "astRegSetAttrib(%s): Unable to apply "
+                   "attribute setting \"%s\" to the base Frame in the %s", status,
+                   astGetClass( this ), setting, astGetClass( this ) );
+         astError( AST__INTER, "There is no base Frame axis corresponding "
+                   "to current Frame axis %d\n", status, axis + 1 );
+      }
+
+/* Free resources */
+      outs = astFree( outs );
+      if( junkmap ) junkmap = astAnnul( junkmap );
+      map = astAnnul( map );
+   }
+
+/* Apply the appropriate attribute setting to the base Frame. This time
+   ensure that any error caused by the attribute setting is annulled.
+   Also apply it to any uncertainty Region (the current Frame of the
+   uncertainty Region is assumed to be equivalent to the base Frame of the
+   parent Region). */
+   frm = astGetFrame( this->frameset, AST__BASE );
+   if( frm ) {
+      rep = astReporting( 0 );
+      astSetAttrib( frm, bsetting ? bsetting : setting );
+      if( astTestUnc( this ) ) {
+         unc = astGetUncFrm( this, AST__BASE );
+         astRegSetAttrib( unc, bsetting ? bsetting : setting, NULL );
+         unc = astAnnul( unc );
+      }
+      if( astStatus == AST__BADAT ) astClearStatus;
+      astReporting( rep );
+   }
+   frm = astAnnul( frm );
+
+/* If required return the modified base Frame setting. Otherwise, free it. */
+   if( base_setting ) {
+      if( bsetting ) {
+         *base_setting = bsetting;
+      } else {
+         *base_setting = astStore( NULL, setting, strlen( setting ) + 1 );
+      }
+   } else {
+      bsetting = astFree( bsetting );
+   }
+
+/* Since the base Frame has been changed, any cached information calculated
+   on the basis of the base Frame properties may no longer be up to date. */
+   astResetCache( this );
+
+/* Free resources. */
+   setting = astFree( setting );
+
+}
+
+static AstMapping *RemoveRegions( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     RemoveRegions
+
+*  Purpose:
+*     Remove any Regions from a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstMapping *RemoveRegions( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Region method (over-rides the astRemoveRegions method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function searches the supplied Mapping (which may be a
+*     compound Mapping such as a CmpMap) for any component Mappings
+*     that are instances of the AST Region class. It then creates a new
+*     Mapping from which all Regions have been removed. If a Region
+*     cannot simply be removed (for instance, if it is a component of a
+*     parallel CmpMap), then it is replaced with an equivalent UnitMap
+*     in the returned Mapping.
+*
+*     The implementation provided by the Region class just returns the
+*     equivalent Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the modified mapping.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* The Region class just returns a pointer to a deep copy of the Region's
+   equivalent Frame. */
+   return astGetRegionFrame( (AstRegion *)this_mapping );
+}
+
+static void ReportPoints( AstMapping *this_mapping, int forward,
+                          AstPointSet *in_points, AstPointSet *out_points, int *status ) {
+/*
+*  Name:
+*     ReportPoints
+
+*  Purpose:
+*     Report the effect of transforming a set of points using a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void ReportPoints( AstMapping *this, int forward,
+*                        AstPointSet *in_points, AstPointSet *out_points, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astReportPoints
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function reports the coordinates of a set of points before
+*     and after being transformed by a Region, by writing them to
+*     standard output.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     forward
+*        A non-zero value indicates that the Region's forward
+*        coordinate transformation has been applied, while a zero
+*        value indicates the inverse transformation.
+*     in_points
+*        Pointer to a PointSet which is associated with the
+*        coordinates of a set of points before the Region was
+*        applied.
+*     out_points
+*        Pointer to a PointSet which is associated with the
+*        coordinates of the same set of points after the Region has
+*        been applied.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_mapping;
+
+/* Obtain a pointer to the Region's current Frame and invoke its
+   astReportPoints method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   astReportPoints( (AstMapping *) fr, forward, in_points, out_points );
+   fr = astAnnul( fr );
+
+}
+
+static void ResetCache( AstRegion *this, int *status ){
+/*
+*+
+*  Name:
+*     astResetCache
+
+*  Purpose:
+*     Clear cached information within the supplied Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astResetCache( AstRegion *this )
+
+*  Class Membership:
+*     Region virtual function
+
+*  Description:
+*     This function clears cached information from the supplied Region
+*     structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*-
+*/
+   if( this ) {
+      if( this->basemesh ) this->basemesh = astAnnul( this->basemesh );
+      if( this->basegrid ) this->basegrid = astAnnul( this->basegrid );
+      if( this->negation ) this->negation = astAnnul( this->negation );
+   }
+}
+
+
+static void Resolve( AstFrame *this_frame, const double point1[],
+                     const double point2[], const double point3[],
+                     double point4[], double *d1, double *d2, int *status ){
+/*
+*  Name:
+*     Resolve
+
+*  Purpose:
+*     Resolve a vector into two orthogonal components
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void Resolve( AstFrame *this, const double point1[],
+*                   const double point2[], const double point3[],
+*                   double point4[], double *d1, double *d2, int *status );
+
+*  Class Membership:
+*     Region member function (over-rides the protected astResolve
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function resolves a vector into two perpendicular components.
+*     The vector from point 1 to point 2 is used as the basis vector.
+*     The vector from point 1 to point 3 is resolved into components
+*     parallel and perpendicular to this basis vector. The lengths of the
+*     two components are returned, together with the position of closest
+*     aproach of the basis vector to point 3.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the start of the basis vector,
+*        and of the vector to be resolved.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the basis vector.
+*     point3
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the vector to be
+*        resolved.
+*     point4
+*        An array of double, with one element for each Frame axis
+*        in which the coordinates of the point of closest approach of the
+*        basis vector to point 3 will be returned.
+*     d1
+*        The address of a location at which to return the distance from
+*        point 1 to point 4 (that is, the length of the component parallel
+*        to the basis vector). Positive values are in the same sense as
+*        movement from point 1 to point 2.
+*     d2
+*        The address of a location at which to return the distance from
+*        point 4 to point 3 (that is, the length of the component
+*        perpendicular to the basis vector). The value is always positive.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Each vector used in this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value, or if the required
+*     output values are undefined.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke this
+   Frame's astResolve method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   astResolve( fr, point1, point2, point3, point4, d1, d2 );
+   fr = astAnnul( fr );
+
+}
+
+static AstPointSet *ResolvePoints( AstFrame *this_frame, const double point1[],
+                                   const double point2[], AstPointSet *in,
+                                   AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     ResolvePoints
+
+*  Purpose:
+*     Resolve a set of vectors into orthogonal components
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstPointSet *ResolvePoints( AstFrame *this, const double point1[],
+*                                 const double point2[], AstPointSet *in,
+*                                 AstPointSet *out )
+
+*  Class Membership:
+*     Region member function (over-rides the astResolvePoints method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function takes a Frame and a set of vectors encapsulated
+*     in a PointSet, and resolves each one into two orthogonal components,
+*     returning these two components in another PointSet.
+*
+*     This is exactly the same as the public astResolve method, except
+*     that this method allows many vectors to be processed in a single call,
+*     thus reducing the computational cost of overheads of many
+*     individual calls to astResolve.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the start of the basis vector,
+*        and of the vectors to be resolved.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the basis vector.
+*     in
+*        Pointer to the PointSet holding the ends of the vectors to be
+*        resolved.
+*     out
+*        Pointer to a PointSet which will hold the length of the two
+*        resolved components. A NULL value may also be given, in which
+*        case a new PointSet will be created by this function.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet. The first axis will
+*     hold the lengths of the vector components parallel to the basis vector.
+*     These values will be signed (positive values are in the same sense as
+*     movement from point 1 to point 2. The second axis will hold the lengths
+*     of the vector components perpendicular to the basis vector. These
+*     values will always be positive.
+
+*  Notes:
+*     - The number of coordinate values per point in the input
+*     PointSet must match the number of axes in the supplied Frame.
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points and 2 coordinate values per point.
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke this
+   Frame's astResolve method. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astResolvePoints( fr, point1, point2, in, out );
+   fr = astAnnul( fr );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     Region member function (extends the astSetAttrib method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function assigns an attribute value for a Region, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     setting
+*        Pointer to a null terminated string specifying the new
+*        attribute value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This protected method is intended to be invoked by the Object
+*     astSet method and makes additional attributes accessible to it.
+*/
+
+/* Local Variables: */
+   AstRegion *this;              /* Pointer to the Region structure */
+   double dval;                  /* Floating point attribute value */
+   int ival;                     /* Integer attribute value */
+   int id;                       /* Offset of ID string */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* We first handle attributes that apply to the Region as a whole
+   (rather than to the encapsulated Frame). */
+
+/* Negated */
+/* ------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "negated= %d %n", &ival, &nc ) )
+          && ( nc >= len ) ) {
+      astSetNegated( this, ival );
+
+/* Closed */
+/*------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "closed= %d %n", &ival, &nc ) )
+          && ( nc >= len ) ) {
+      astSetClosed( this, ival );
+
+/* FillFactor */
+/* ---------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "fillfactor= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetFillFactor( this, dval );
+
+/* MeshSize */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "meshsize= %d %n", &ival, &nc ) )
+          && ( nc >= len ) ) {
+      astSetMeshSize( this, ival );
+
+/* Adaptive */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "adaptive= %d %n", &ival, &nc ) )
+          && ( nc >= len ) ) {
+      astSetAdaptive( this, ival );
+
+/* Now do attributes inherited from parent classes. We do these here to
+   avoid the settings being passed on to the encapsulated FrameSet below. */
+
+/* ID. */
+/* --- */
+   } else if ( nc = 0, ( 0 == astSscanf( setting, "id=%n%*[^\n]%n", &id, &nc ) )
+                       && ( nc >= len ) ) {
+      astSetID( this, setting + id );
+
+/* Ident. */
+/* ------ */
+   } else if ( nc = 0, ( 0 == astSscanf( setting, "ident=%n%*[^\n]%n", &id, &nc ) )
+                       && ( nc >= len ) ) {
+      astSetIdent( this, setting + id );
+
+/* Invert. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "invert= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetInvert( this, ival );
+
+/* Report. */
+/* ------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "report= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetReport( this, ival );
+
+/* Define macros to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+#define AXISMATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "(%*d)=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* If the attribute was not recognised, use this macro to report an error
+   if a read-only attribute has been specified. */
+   } else if ( MATCH( "class" ) ||
+               MATCH( "nin" ) ||
+               MATCH( "nobject" ) ||
+               MATCH( "bounded" ) ||
+               MATCH( "nout" ) ||
+               MATCH( "refcount" ) ||
+               MATCH( "tranforward" ) ||
+               MATCH( "traninverse" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Pass unrecognised attributes on to the Region's encapsulated FrameSet for
+   further interpretation. Do not pass on FrameSet attributes since we
+   pretend to the outside world that the encapsulated FrameSet is actually a
+   Frame. */
+   } else if ( !MATCH( "base" ) &&
+               !MATCH( "current" ) &&
+               !MATCH( "nframe" ) ) {
+
+/* If the Region is to adapt to coordinate system chanmges, use the public
+   astSet method so that the current Frame in the encapsulated FrameSet will
+   be re-mapped if the attribute changes require it. */
+      if( astGetAdaptive( this ) ) {
+         astSet( this->frameset, setting, status );
+
+/* If the Region is not to adapt to coordinate system chanmges, use the
+   astRegSetAttrib method which assigns the attribute setting to both
+   current and base Frames in the FrameSet without causing any remapping to
+   be performed. */
+      } else {
+         astRegSetAttrib( this, setting, NULL );
+      }
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+static void SetAxis( AstFrame *this_frame, int axis, AstAxis *newaxis, int *status ) {
+/*
+*  Name:
+*     SetAxis
+
+*  Purpose:
+*     Set a new Axis for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void SetAxis( AstFrame *this, int axis, AstAxis *newaxis, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astSetAxis method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function allows a new Axis object to be associated with one
+*     of the axes of the current Frame in a Region, replacing the
+*     previous one. Each Axis object contains a description of the
+*     quantity represented along one of the Frame's axes, so this
+*     function allows this description to be exchanged for another
+*     one.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     axis
+*        The index (zero-based) of the axis whose associated Axis
+*        object is to be replaced.
+*     newaxis
+*        Pointer to the new Axis object.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index supplied. */
+   (void) astValidateAxis( this, axis, 1, "astSetAxis" );
+
+/* Obtain a pointer to the Region's current Frame and invoke this
+   Frame's astSetAxis method to assign the new Axis object. Annul the
+   Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   astSetAxis( fr, axis, newaxis );
+   fr = astAnnul( fr );
+}
+
+static void SetRegFS( AstRegion *this, AstFrame *frm, int *status ) {
+/*
+*+
+*  Name:
+*     astSetRegFS
+
+*  Purpose:
+*     Stores a new FrameSet in a Region
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astSetRegFS( AstRegion *this, AstFrame *frm )
+
+*  Class Membership:
+*     Region virtual function.
+
+*  Description:
+*     This function creates a new FrameSet and stores it in the supplied
+*     Region. The new FrameSet contains two copies of the supplied
+*     Frame, connected by a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     frm
+*        The Frame to use.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *f1;         /* Copy of supplied Frame */
+   AstFrame *f2;         /* Copy of supplied Frame */
+   AstFrameSet *fs;      /* New FrameSet */
+   AstRegion *unc;       /* Uncertainty Region */
+   AstUnitMap *um;       /* UnitMap connecting base anc current Frames */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Take a copy of the supplied Frame. */
+   f1 = astCopy( frm );
+
+/* Create the new FrameSet. First take another copy of the supplied Frame
+   so that modifications using the supplied pointer will not affect the new
+   FrameSet. We create two copies (rather than 1) because the base and
+   current Frames must be independant objects - otherwise attribute changes
+   done to one will also appear in the other. Then construct the FrameSet
+   containing the two Frame copies connected by a UnitMap. */
+   f2 = astCopy( f1 );
+   fs = astFrameSet( f1, "", status );
+   um = astUnitMap( astGetNaxes( f1 ), "", status );
+   astAddFrame( fs, AST__BASE, um, f2 );
+   um = astAnnul( um );
+   f2 = astAnnul( f2 );
+
+/* Annul any existing FrameSet */
+   if( this->frameset ) (void) astAnnul( this->frameset );
+
+/* Use the new FrameSet */
+   this->frameset = fs;
+
+/* If any uncertainty Region has a zero value for its RegionFS attribute,
+   it will currently contain a dummy FrameSet rather than the correct
+   FrameSet. The correct FrameSet has copies of the base Frame of the new
+   Region as both its current and base Frames, and these are connected by
+   a UnitMap (this is equivalent to a FrameSet containing a single Frame). */
+   if( astTestUnc( this ) ) {
+      unc = astGetUncFrm( this, AST__BASE );
+      if( unc && !astGetRegionFS( unc ) ) astSetRegFS( unc, f1 );
+      unc = astAnnul( unc );
+   }
+
+/* Free remaining resourvces */
+   f1 = astAnnul( f1 );
+
+}
+
+static void SetUnc( AstRegion *this, AstRegion *unc, int *status ){
+/*
+*++
+*  Name:
+c     astSetUnc
+f     AST_SETUNC
+
+*  Purpose:
+*     Store uncertainty information in a Region.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     void astSetUnc( AstRegion *this, AstRegion *unc )
+f     CALL AST_SETUNC( THIS, UNC, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     Each Region (of any class) can have an "uncertainty" which specifies
+*     the uncertainties associated with the boundary of the Region. This
+*     information is supplied in the form of a second Region. The uncertainty
+*     in any point on the boundary of a Region is found by shifting the
+*     associated "uncertainty" Region so that it is centred at the boundary
+*     point being considered. The area covered by the shifted uncertainty
+*     Region then represents the uncertainty in the boundary position.
+*     The uncertainty is assumed to be the same for all points.
+*
+*     The uncertainty is usually specified when the Region is created, but
+*     this
+c     function
+f     routine
+*     allows it to be changed at any time.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region which is to be assigned a new uncertainty.
+c     unc
+f     UNC = INTEGER (Given)
+*        Pointer to the new uncertainty Region. This must be of a class for
+*        which all instances are centro-symetric (e.g. Box, Circle, Ellipse,
+*        etc.) or be a Prism containing centro-symetric component Regions.
+*        A deep copy of the supplied Region will be taken, so subsequent
+*        changes to the uncertainty Region using the supplied pointer will
+*        have no effect on the Region
+c        "this".
+f        THIS.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *frm;           /* Current Frame from FrameSet */
+   AstFrameSet *fs2;        /* FrameSet from "unc" current Frame to "this" base Frame */
+   AstFrameSet *fs;         /* FrameSet in "this" supplied Region */
+   AstMapping *map2;        /* Base->current Mapping from FrameSet */
+   AstMapping *map;         /* Base->current Mapping from FrameSet */
+   AstMapping *smap;        /* Simplified base->current Mapping */
+   double *cen0;            /* Pointer to array holding original centre */
+   double **ptr_reg;        /* Pointer to axis values for Region's Pointset */
+   int changed;             /* Has the uncertainty been changed? */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Annul any existing uncertainty Region. */
+   if( this->unc ) {
+      this->unc = astIsAObject( this->unc ) ?
+                                astAnnul( this->unc ) : NULL;
+      changed = 1;
+   } else {
+      changed = 0;
+   }
+
+/* Check an uncertainty Region was supplied, and is of a usable class
+   (i.e. a class which can be re-centred). */
+   cen0 = unc ? astRegCentre( unc, NULL, NULL, 0, 0 ) : NULL;
+   if( cen0 ) {
+      cen0 = astFree( cen0 );
+
+/* Map it into the same Frame as that represented by the base Frame in
+   the supplied Region. */
+      fs = this->frameset;
+      astInvert( fs );
+      fs2 = Conv( unc->frameset, fs, status );
+      astInvert( fs );
+
+      if( fs2 ) {
+         map = astGetMapping( fs2, AST__BASE, AST__CURRENT );
+         frm = astGetFrame( fs2, AST__CURRENT );
+         this->unc = astMapRegion( unc, map, frm );
+         if( this->unc ) {
+
+/* Ensure the Region is bounded. We know that negating an unbounded
+   Region will make it bounded because we know that the Region consists of
+   Circles, Boxes and/or Ellipses, all of which have this property. */
+            if( !astGetBounded( this->unc ) ) astNegate( this->unc );
+
+/* If the base Frame in the uncertainty Region is the same as the base
+   Frame in the Region being dumped, then we do no need to include the
+   FrameSet in the dump of the uncertainty Region. Since the current
+   Frame in the uncertainty Region always corresponds to the base Frame of
+   its parent Region, we only need to check if the base->current Mapping
+   in the uncertainty Region's FrameSet is a UnitMap or not (after
+   simplification). If it is, set the RegionFS attribute of the uncertainty
+   Region to zero (i.e. false). This will cause the FrameSet to be omitted
+   from the Dump. */
+            map2 = astGetMapping( this->unc->frameset, AST__BASE, AST__CURRENT );
+            smap = astSimplify( map2 );
+            if( astIsAUnitMap( smap ) ) astSetRegionFS( this->unc, 0 );
+
+/* Re-centre the uncertainty Region at the first position in the PointSet
+   associated with the Region structure (if any). */
+            if( this->points ) {
+               ptr_reg = astGetPoints( this->points );
+               astRegCentre( this->unc, NULL, ptr_reg, 0, AST__CURRENT );
+            }
+
+/* Set a flag indicating that the uncertainty in the Region has changed. */
+            changed = 1;
+
+/* Free resources */
+            map2 = astAnnul( map2 );
+            smap = astAnnul( smap );
+         }
+         frm = astAnnul( frm );
+         fs2 = astAnnul( fs2 );
+         map = astAnnul( map );
+
+/* Report error if conversion between Frames is not possible. */
+      } else if( astOK ) {
+         astError( AST__BADIN, "astSetUnc(%s): Bad %d dimensional "
+                   "uncertainty Frame (%s %s) supplied.", status, astGetClass(this),
+                   astGetNaxes(unc), astGetDomain(unc), astGetTitle(unc) );
+         astError( AST__NCPIN, "Cannot convert it to the Frame of the "
+                   "new %s.", status, astGetClass( this ) );
+      }
+
+/* Report an error if it is not of a usable class. */
+   } else if( unc && astOK ){
+      astError( AST__BADIN, "astSetUnc(%s): Bad uncertainty shape "
+                "(%s) supplied.", status, astGetClass( this ), astGetClass(unc) );
+      astError( AST__NCPIN, "The uncertainty Region must be an instance of "
+                "a centro-symetric subclass of Region (e.g. Box, Circle, "
+                "Ellipse, etc)." , status);
+   }
+
+/* If the uncertainty in the Region has changed, indicate that any cached
+   information in the Region is now out of date. */
+   if( changed ) astResetCache( this );
+
+}
+
+static void ShowMesh( AstRegion *this, int format, const char *ttl, int *status ){
+/*
+*++
+*  Name:
+c     astShowMesh
+f     AST_SHOWMESH
+
+*  Purpose:
+*     Display a mesh of points covering the surface of a Region.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     void astShowMesh( AstRegion *this, int format, const char *ttl )
+f     CALL AST_SHOWMESH( THIS, FORMAT, TTL, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+c     This function
+f     This routine
+*     writes a table to standard output containing the axis values at a
+*     mesh of points covering the surface of the supplied Region. Each row
+*     of output contains a tab-separated list of axis values, one for
+*     each axis in the Frame encapsulated by the Region. The number of
+*     points in the mesh is determined by the MeshSize attribute.
+*
+*     The table is preceded by a given title string, and followed by a
+*     single line containing the word "ENDMESH".
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+c     format
+f     FORMAT = LOGICAL (Given)
+*        A boolean value indicating if the displayed axis values should
+*        be formatted according to the Format attribute associated with
+*        the Frame's axis. Otherwise, they are displayed as simple
+*        floating point values.
+c     ttl
+f     TTL = CHARACTER * ( * ) (Given)
+*        A title to display before displaying the first position.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Local Variables: */
+   AstPointSet *ps;           /* PointSet holding mesh */
+   char *buffer = NULL;       /* Buffer for line output text */
+   char buf[ 40 ];            /* Buffer for floating poitn value */
+   double **ptr;              /* Pointers to the mesh data */
+   int i;                     /* Axis index */
+   int j;                     /* Position index */
+   int nax;                   /* Number of axes */
+   int nc;                    /* Number of characters in buffer */
+   int np;                    /* Number of axis values per position */
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+/* Get a PointSet holding the mesh */
+   ps = astRegMesh( this );
+   if( ps ) {
+
+/* Get the number of axis values per position, and the number of positions. */
+      nax = astGetNcoord( ps );
+      np = astGetNpoint( ps );
+
+/* Get a pointer to the mesh data, and check it can be used. */
+      ptr = astGetPoints( ps );
+      if( ptr ) {
+
+/* Display the title. */
+         if( ttl ) printf( "\n%s\n\n", ttl );
+
+/* Loop round all positions. */
+         for( j = 0; j < np; j++ ) {
+
+/* Reset the current buffer length to zero. */
+            nc = 0;
+
+/* Loop round all axes */
+            for( i = 0; i < nax; i++ ){
+
+/* If the axis value is bad, append "<bad> in the end of the output buffer. */
+               if( ptr[ i ][ j ] == AST__BAD ){
+                  buffer = astAppendString( buffer, &nc, "<bad>" );
+
+/* Otherwise, if required, append the formatted value to the end of the
+   buffer. */
+               } else if( format ){
+                  buffer = astAppendString( buffer, &nc,
+                                         astFormat( this, i, ptr[ i ][ j ] ) );
+
+/* Otherwise, append the floating point value to the end of the buffer. */
+               } else {
+                  sprintf( buf, "%g", ptr[ i ][ j ] );
+                  buffer = astAppendString( buffer, &nc, buf );
+               }
+/* Add a separating tab to the end of the buffer. */
+               buffer = astAppendString( buffer, &nc, "\t" );
+            }
+
+/* Display the line buffer. */
+            printf( "%s\n", buffer );
+         }
+      }
+
+/* Print out a marker for th eend of the list. */
+      printf( "ENDMESH\n\n" );
+
+/* Release resources. */
+      ps = astAnnul( ps );
+      buffer = astFree( buffer );
+   }
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify the Mapping represented by a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Region method (over-rides the astSimplify method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function simplifies the encapsulated FrameSet and any
+*     uncertainty Region in the supplied Region. This is different to
+*     the Simplify method in the parent Frame class which always returns
+*     a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the simplified Region. A cloned pointer to the
+*     supplied Region will be returned if no simplication could be
+*     performed.
+
+*  Notes:
+*     - This implementation just simplifies the encapsulated FrameSet
+*     and uncertainty Region. Sub-classes should usually provide their own
+*     implementation which invokes this implemetation, and then continues to
+*     check for further simplifications (such as fitting a new region to the
+*     current Frame).
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *bfrm;               /* Pointer to "this" baseFrame */
+   AstFrameSet *fs;              /* Pointer to encapsulated FrameSet */
+   AstMapping *map;              /* Base->current Mapping for "this" */
+   AstMapping *result;           /* Result pointer to return */
+   AstPointSet *pset1;           /* Base Frame centre position */
+   AstPointSet *pset2;           /* Current Frame centre position */
+   AstRegion *new;               /* Pointer to simplified Region */
+   AstRegion *sunc;              /* Simplified uncertainty Region */
+   AstRegion *this;              /* Pointer to original Region structure */
+   AstRegion *unc;               /* Original uncertainty Region */
+   double **ptr1;                /* Pointer to axis values in "pset1" */
+   double *cen;                  /* Original centre of uncertainty Region */
+   double *lbnd;                 /* Lower bounds of "this" bounding box */
+   double *orig_cen;             /* Original centre for uncertainty Region */
+   double *s1_lbnd;              /* Lower bounds of "unc" when centred at lbnd */
+   double *s1_ubnd;              /* Upper bounds of "unc" when centred at lbnd */
+   double *s2_lbnd;              /* Lower bounds of "unc" when centred at ubnd */
+   double *s2_ubnd;              /* Upper bounds of "unc" when centred at ubnd */
+   double *ubnd;                 /* Upper bounds of "this" bounding box */
+   double delta;                 /* Half width of test box */
+   double w1;                    /* Width of "s1" bounding box */
+   double w2;                    /* Width of "s2" bounding box */
+   int ic;                       /* Axis index */
+   int naxb;                     /* No. of base Frame axes in "this" */
+   int nin;                      /* Number of base Frame axes in "this" */
+   int nout;                     /* Number of current Frame axes in "this" */
+   int ok;                       /* Can we use the simplified uncertainty? */
+   int simpler;                  /* Has some simplication taken place? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_mapping;
+
+/* Take a deep copy of the supplied Region. This is so that the returned
+   pointer will have a diferent value to the supplied pointer if any
+   simplication takes place. */
+   new = astCopy( this );
+
+/* Simplify the encapsulated FrameSet, and note if any simplification took
+   place. */
+   fs = astSimplify( new->frameset );
+   simpler = ( fs != new->frameset );
+
+/* If so, annull the existing FrameSet and use the simpler FrameSet. */
+   if( simpler ) {
+      (void) astAnnul( new->frameset );
+      new->frameset = astClone( fs );
+   }
+   fs = astAnnul( fs );
+
+/* If the Region has default uncertainty, we simplify the uncertainty
+   Region simply by deleting it. It will be regenerated when needed,
+   using the simplified Region. */
+   if( new->defunc ) new->defunc = astAnnul( new->defunc );
+
+/* If the Region's uncertainty was supplied explicitly, try simplifying
+   the unncertainty Region. */
+   if( astTestUnc( new ) ){
+
+/* Obtain the Region's uncertainty. */
+      unc = astGetUncFrm( new, AST__BASE );
+
+/* Get the base->current Mapping from "this". */
+      map = astGetMapping( this->frameset, AST__BASE, AST__CURRENT );
+
+/* If it has different numbers of inputs and outputs (e.g. a PermMap used
+   to take a slice through a Region), we need to ensure that the
+   uncertainty Region is centred on the slice. */
+      nin = astGetNin( map );
+      nout = astGetNout( map );
+      if( nin != nout ) {
+
+/* Get the current centre of the uncertainty Region in its current Frame
+   (the same as the base Frame of "this"). */
+         cen = astRegCentre( unc, NULL, NULL, 0, AST__CURRENT );
+
+/* Store it in a PointSet so it can be transformed. */
+         pset1 = astPointSet( 1, nin, "", status );
+         ptr1 = astGetPoints( pset1 );
+         if( astOK ) for( ic = 0; ic < nin; ic++ ) ptr1[ ic ][ 0 ] = cen[ ic ];
+
+/* Transform into the curent Frame of "this", and then back into the base
+   Frame. */
+         pset2 = astTransform( map, pset1, 1, NULL );
+         (void) astTransform( map, pset2, 0, pset1 );
+
+/* Re-centre the uncertainty Region at this position. */
+         astRegCentre( unc, NULL, ptr1, 0, AST__CURRENT );
+
+/* Free resources. */
+         cen = astFree( cen );
+         pset1 = astAnnul( pset1 );
+         pset2 = astAnnul( pset2 );
+      }
+
+/* Free resources. */
+      map = astAnnul( map );
+
+/* Try simplifying the uncertainty. Only proceed if the uncertainty can
+   be simplified. */
+      sunc = astSimplify( unc );
+      if( sunc != unc ) {
+
+/* If the uncertainty can be simplified it means that the base->current
+   Mapping in the uncertainty Region is sufficiently linear to allow the
+   uncertainty shape to retain its form when transformed from the base to
+   the current Frane. But this has only been tested at the current centre
+   position in the uncertainty Region. The uncertainty Region should
+   describe the whole of "this" Region, and so we need to check that the
+   simplified uncertainty does not change as we move it around within "this"
+   Region. To do this, we re-centre the uncertainty region at opposite
+   corners of a large test box, and then we find the bounding box of the
+   re-centred uncertainty Region. If this uncertainty bounding box changes
+   from corner to corner of the test box, then we do not simplify the
+   uncertainty Region. If "this" is bounded, we use the bounding box of
+   "this" as the test box. Otherwise we use a box 100 times the size of the
+   uncertainty Region. */
+
+/* Note the original base Frame centre of the simplified uncertainty Region. */
+         orig_cen = astRegCentre( sunc, NULL, NULL, 0, AST__BASE );
+
+/* Allocate memory to hold the bounds of the test box. */
+         naxb = astGetNin( this->frameset );
+         lbnd = astMalloc( sizeof( double )*(size_t)naxb );
+         ubnd = astMalloc( sizeof( double )*(size_t)naxb );
+
+/* If possible, get the base Frame bounding box of "this" and use it as
+   the test box. */
+         if( astGetBounded( this ) ) {
+            astRegBaseBox( this, lbnd, ubnd );
+
+/* Otherwise, store the bounds of a box which is 100 times the size of
+   the uncertainty region, centred on the current centre of the uncertainty
+   region (we know all uncertainty regions are bounded). */
+         } else {
+            astGetRegionBounds( sunc, lbnd, ubnd );
+            for( ic = 0; ic < naxb; ic++ ) {
+               delta = 0.5*fabs( ubnd[ ic ] - lbnd[ ic ] );
+               lbnd[ ic ] = orig_cen[ ic ] - delta;
+               ubnd[ ic ] = orig_cen[ ic ] + delta;
+            }
+         }
+
+/* Re-centre it at the lower bounds of the test box. This is in the base Frame
+   of "this" which is the same as the current Frame of "sunc". */
+         astRegCentre( sunc, lbnd, NULL, 0, AST__CURRENT );
+
+/* Get the bounding box of the re-centred uncertainty Region, within its
+   current Frame, which is the same as the base Frame of "this". */
+         s1_lbnd = astMalloc( sizeof( double )*(size_t)naxb );
+         s1_ubnd = astMalloc( sizeof( double )*(size_t)naxb );
+         astGetRegionBounds( sunc, s1_lbnd, s1_ubnd );
+
+/* Now re-centre the uncertainty Region at the upper bounds of the test
+   box. */
+         astRegCentre( sunc, ubnd, NULL, 0, AST__CURRENT );
+
+/* Get the bounding box of the re-centred uncertainty Region. */
+         s2_lbnd = astMalloc( sizeof( double )*(size_t)naxb );
+         s2_ubnd = astMalloc( sizeof( double )*(size_t)naxb );
+         astGetRegionBounds( sunc, s2_lbnd, s2_ubnd );
+
+/* Get a pointer to the base Frame of "this". */
+         bfrm = astGetFrame( this->frameset, AST__BASE );
+
+/* The "ok" flag is initialised to indicate that the simplified uncertainty
+   Region should not be used. */
+         ok = 0;
+
+/* Check pointers can be referenced safely */
+         if( astOK ) {
+
+/* Now indicate that the simplified uncertainty Region should be used. */
+            ok = 1;
+
+/* Loop round all axes of the base Frame of "this". */
+            for( ic = 0; ic < naxb; ic++ ) {
+
+/* Get the width of the two bounding boxes on this axis. */
+               w1 = s1_ubnd[ ic ] - s1_lbnd[ ic ];
+               w2 = s2_ubnd[ ic ] - s2_lbnd[ ic ];
+
+/* If these differ by more than 0.1% then we determine that the simplified
+   uncertainty Region varies in size across the bounding box of "this", and
+   so we do not use the simplified uncertainty Region. The figure of 0.1%
+   is arbitrary. */
+               if( fabs( w1 - w2 ) > 0.005*( fabs( w1 ) + fabs( w2 ) ) ) {
+                  ok = 0;
+                  break;
+               }
+            }
+         }
+
+/* Reinstate the original base Frame centre of the simplified uncertainty Region. */
+         astRegCentre( sunc, orig_cen, NULL, 0, AST__BASE );
+
+/* Free resources. */
+         orig_cen = astFree( orig_cen );
+         lbnd = astFree( lbnd );
+         ubnd = astFree( ubnd );
+         s1_lbnd = astFree( s1_lbnd );
+         s1_ubnd = astFree( s1_ubnd );
+         s2_lbnd = astFree( s2_lbnd );
+         s2_ubnd = astFree( s2_ubnd );
+         bfrm = astAnnul( bfrm );
+
+/* If we can use the simplified uncertainty Region, indicate that we have
+   performed some simplification, and store the new uncertainty Region. */
+         if( ok ) {
+            simpler = 1;
+            astSetUnc( new, sunc );
+         }
+      }
+
+/* Free resources */
+      unc = astAnnul( unc );
+      sunc = astAnnul( sunc );
+   }
+
+/* If any simplification could be performed, return the new Region.
+   Otherwise, return a clone of the supplied pointer. */
+   if( simpler ){
+      result = (AstMapping *) new;
+   } else {
+      new = astAnnul( new );
+      result = astClone( this );
+   }
+
+/* If an error occurred, annul the returned pointer. */
+   if ( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static int SubFrame( AstFrame *this_frame, AstFrame *template,
+                     int result_naxes,
+                     const int *target_axes, const int *template_axes,
+                     AstMapping **map, AstFrame **result, int *status ) {
+/*
+*  Name:
+*     SubFrame
+
+*  Purpose:
+*     Select axes from a Region and convert to the new coordinate system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int SubFrame( AstFrame *target, AstFrame *template, int result_naxes,
+*                   const int *target_axes, const int *template_axes,
+*                   AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astSubFrame
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function selects a requested sub-set (or super-set) of the
+*     axes from the current Frame of a "target" Region and creates a
+*     new Frame with copies of the selected axes assembled in the
+*     requested order. It then optionally overlays the attributes of a
+*     "template" Frame on to the result. It returns both the resulting
+*     Frame and a Mapping that describes how to convert between the
+*     coordinate systems described by the current Frame of the target
+*     Region and the result Frame. If necessary, this Mapping takes
+*     account of any differences in the Frames' attributes due to the
+*     influence of the template.
+
+*  Parameters:
+*     target
+*        Pointer to the target Region, from whose current Frame the
+*        axes are to be selected.
+*     template
+*        Pointer to the template Frame, from which new attributes for
+*        the result Frame are to be obtained. Optionally, this may be
+*        NULL, in which case no overlaying of template attributes will
+*        be performed.
+*     result_naxes
+*        Number of axes to be selected from the target Region. This
+*        number may be greater than or less than the number of axes in
+*        the Region's current Frame (or equal).
+*     target_axes
+*        Pointer to an array of int with result_naxes elements, giving
+*        a list of the (zero-based) axis indices of the axes to be
+*        selected from the current Frame of the target Region. The
+*        order in which these are given determines the order in which
+*        the axes appear in the result Frame. If any of the values in
+*        this array is set to -1, the corresponding result axis will
+*        not be derived from the target Region, but will be assigned
+*        default attributes instead.
+*     template_axes
+*        Pointer to an array of int with result_naxes elements. This
+*        should contain a list of the template axes (given as
+*        zero-based axis indices) with which the axes of the result
+*        Frame are to be associated. This array determines which axes
+*        are used when overlaying axis-dependent attributes of the
+*        template on to the result. If any element of this array is
+*        set to -1, the corresponding result axis will not receive any
+*        template attributes.
+*
+*        If the template argument is given as NULL, this array is not
+*        used and a NULL pointer may also be supplied here.
+*     map
+*        Address of a location to receive a pointer to the returned
+*        Mapping.  The forward transformation of this Mapping will
+*        describe how to convert coordinates from the coordinate
+*        system described by the current Frame of the target Region
+*        to that described by the result Frame. The inverse
+*        transformation will convert in the opposite direction.
+*     result
+*        Address of a location to receive a pointer to the result Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if coordinate conversion is
+*     possible between the current Frame of the target Region and
+*     the result Frame. Otherwise zero is returned and *map and
+*     *result are returned as NULL (but this will not in itself result
+*     in an error condition). In general, coordinate conversion should
+*     always be possible if no template Frame is supplied but may not
+*     always be possible otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to Region's current Frame */
+   int match;                    /* Result to be returned */
+
+/* Initialise. */
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Invoke the parent astSubFrame method on the Frame represented by the
+   region. */
+   fr = astGetFrame( ((AstRegion *) this_frame)->frameset, AST__CURRENT );
+   match = astSubFrame( fr, template, result_naxes, target_axes, template_axes,
+                        map, result );
+   fr = astAnnul( fr );
+
+/* Return the result. */
+   return match;
+}
+
+static AstSystemType SystemCode( AstFrame *this_frame, const char *system, int *status ) {
+/*
+*  Name:
+*     SystemCode
+
+*  Purpose:
+*     Convert a string into a coordinate system type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstSystemType SystemCode( AstFrame *this, const char *system, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astSystemCode
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function converts a string used for the external description of
+*     a coordinate system into a Frame coordinate system type code (System
+*     attribute value). It is the inverse of the astSystemString function.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        Pointer to a constant null-terminated string containing the
+*        external description of the coordinate system.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System type code.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM is returned if the coordinate system
+*     description was not recognised. This does not produce an error.
+*     - A value of AST__BADSYSTEM is also returned if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;      /* Result value to return */
+   AstFrame *fr;              /* Pointer to FrameSet's current Frame */
+   AstRegion *this;           /* Pointer to the Region structure */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke the
+   astSystemCode method for this Frame. Annul the Frame pointer afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astSystemCode( fr, system );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BADSYSTEM;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *SystemString( AstFrame *this_frame, AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SystemString
+
+*  Purpose:
+*     Convert a coordinate system type code into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     const char *SystemString( AstFrame *this, AstSystemType system, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astSystemString
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function converts a Frame coordinate system type code
+*     (System attribute value) into a string suitable for use as an
+*     external representation of the coordinate system type.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to FrameSet's current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke the
+   astSystemString method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astSystemString( fr, system );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result pointer. */
+   return result;
+
+}
+
+static int RegTrace( AstRegion *this, int n, double *dist, double **ptr, int *status ){
+/*
+*+
+*  Name:
+*     astRegTrace
+
+*  Purpose:
+*     Return requested positions on the boundary of a 2D Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int astRegTrace( AstRegion *this, int n, double *dist, double **ptr );
+
+*  Class Membership:
+*     Region virtual function
+
+*  Description:
+*     This function returns positions on the boundary of the supplied
+*     Region, if possible. The required positions are indicated by a
+*     supplied list of scalar parameter values in the range zero to one.
+*     Zero corresponds to some arbitrary starting point on the boundary,
+*     and one corresponds to the end (which for a closed region will be
+*     the same place as the start).
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     n
+*        The number of positions to return. If this is zero, the function
+*        returns without action (but the returned function value still
+*        indicates if the method is supported or not).
+*     dist
+*        Pointer to an array of "n" scalar parameter values in the range
+*        0 to 1.0.
+*     ptr
+*        A pointer to an array of pointers. The number of elements in
+*        this array should equal tthe number of axes in the Frame spanned
+*        by the Region. Each element of the array should be a pointer to
+*        an array of "n" doubles, in which to return the "n" values for
+*        the corresponding axis. The contents of the arrays are unchanged
+*        if the supplied Region belongs to a class that does not
+*        implement this method.
+
+*  Returned Value:
+*     Non-zero if the astRegTrace method is implemented by the class
+*     of Region supplied, and zero if not.
+
+*-
+*/
+
+/* Concrete sub-classes of Region must over-ride this method. */
+   return 0;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the astTestAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a Region's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstRegion *this;              /* Pointer to the Region structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* We first handle attributes that apply to the Region as a whole
+   (rather than to the encapsulated FrameSet). */
+
+/* Negated. */
+/* -------- */
+   if ( !strcmp( attrib, "negated" ) ) {
+      result = astTestNegated( this );
+
+/* Closed. */
+/* ------- */
+   } else if ( !strcmp( attrib, "closed" ) ) {
+      result = astTestClosed( this );
+
+/* FillFactor */
+/* ---------- */
+   } else if ( !strcmp( attrib, "fillfactor" ) ) {
+      result = astTestFillFactor( this );
+
+/* MeshSize */
+/* -------- */
+   } else if ( !strcmp( attrib, "meshsize" ) ) {
+      result = astTestMeshSize( this );
+
+/* Adaptive */
+/* -------- */
+   } else if ( !strcmp( attrib, "adaptive" ) ) {
+      result = astTestAdaptive( this );
+
+/* Now do attributes inherited from parent classes. This is so that the
+   attribute test will not be passed on to the encpasulated FrameSet below. */
+
+/* ID. */
+/* --- */
+   } else if ( !strcmp( attrib, "id" ) ) {
+      result = astTestID( this );
+
+/* Ident. */
+/* ------ */
+   } else if ( !strcmp( attrib, "ident" ) ) {
+      result = astTestIdent( this );
+
+/* Invert. */
+/* ------- */
+   } else if ( !strcmp( attrib, "invert" ) ) {
+      result = astTestInvert( this );
+
+/* Report. */
+/* ------- */
+   } else if ( !strcmp( attrib, "report" ) ) {
+      result = astTestReport( this );
+
+/* If the name is not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then return
+   zero. */
+   } else if ( !strcmp( attrib, "class" ) ||
+               !strcmp( attrib, "nin" ) ||
+               !strcmp( attrib, "nobject" ) ||
+               !strcmp( attrib, "bounded" ) ||
+               !strcmp( attrib, "nout" ) ||
+               !strcmp( attrib, "refcount" ) ||
+               !strcmp( attrib, "tranforward" ) ||
+               !strcmp( attrib, "traninverse" ) ) {
+      result = 0;
+
+/* Pass unrecognised attributes on to the Region's encapsulated FrameSet for
+   further interpretation. Do not pass on FrameSet attributes since we
+   pretend to the outside world that the encapsulated FrameSet is actually a
+   Frame. */
+   } else if ( strcmp( attrib, "base" ) &&
+               strcmp( attrib, "current" ) &&
+               strcmp( attrib, "nframe" ) ) {
+      result = astTestAttrib( this->frameset, attrib );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+double *astRegTranPoint_( AstRegion *this, double *in, int np, int forward, int *status ){
+/*
+*+
+*  Name:
+*     astRegTranPoint
+
+*  Purpose:
+*     Transform points between the base and current Frames in a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     double *astRegTranPoint( AstRegion *this, double *in, int np, int forward )
+
+*  Class Membership:
+*     Region member function
+
+*  Description:
+*     This function transforms one or more points between the base and
+*     current Frames of the FrameSet encapsulated by the supplied Region.
+
+*  Parameters:
+*     this
+*        The Region pointer.
+*     in
+*        Pointer to a 1-d array holding the axis values to be transformed.
+*        If "forward" is non-zero, the number of axis values supplied for
+*        each position should equal the number of axes in the base Frame
+*        of the FrameSet encapsulated by "this". If "forward" is zero, the
+*        number of axis values supplied for each position should equal the
+*        number of axes in the current Frame of the FrameSet encapsulated by
+*        "this". All the axis values for a position should be in adjacent
+*        elements of the array.
+*     np
+*        The number of points supplied in "in".
+*     forward
+*        If non-zero, the supplied points are assumed to refer to the base
+*        Frame of the encapsulated FrameSet, and they are transformed to the
+*        current Frame. If zero, the supplied points are assumed to refer to
+*        the current Frame of the encapsulated FrameSet, and they are
+*        transformed to the base Frame.
+
+*  Returned Value:
+*     Pointer to a new dynamically allocated array holding the
+*     transformed axis values. If "forward" is non-zero, the number of axis
+*     values for each position will be equal the number of axes in the
+*     current Frame of the FrameSet encapsulated by "this". If "forward" is
+*     zero, the number of axis values for each position will be equal to the
+*     number of axes in the base Frame of the FrameSet encapsulated by "this".
+*     All the axis values for a position will be in adjacent elements of the
+*     array. The array should be freed using astFree when no longer needed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*-
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   AstPointSet *pset_in;
+   AstPointSet *pset_out;
+   double **ptr_in;
+   double **ptr_out;
+   double *p;
+   double *result;
+   int ic;
+   int ip;
+   int naxin;
+   int naxout;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the required Mapping. */
+   if( forward ) {
+      map = astGetMapping( this->frameset, AST__BASE, AST__CURRENT );
+   } else {
+      map = astGetMapping( this->frameset, AST__CURRENT, AST__BASE );
+   }
+
+/* Get the number of axis values per input and per output point. */
+   naxin = astGetNin( map );
+   naxout = astGetNout( map );
+
+/* Create a pointSet holding the supplied axis values. */
+   pset_in = astPointSet( np, naxin, "", status );
+
+/* Get pointers to the memory used to store axis values within this
+   PointSet. */
+   ptr_in = astGetPoints( pset_in );
+
+/* Allocate the output array. */
+   result = astMalloc( sizeof( double )*(size_t)( naxout*np ) );
+
+/* Check the pointers can be used. */
+   if( astOK ) {
+
+/* Store the supplied axis values in the PointSet memory. */
+      p = in;
+      for( ip = 0; ip < np; ip++ ) {
+         for( ic = 0; ic < naxin; ic++ ) ptr_in[ ic ][ ip ] = *(p++);
+      }
+
+/* Transform the PointSet. */
+      pset_out = astTransform( map, pset_in, 1, NULL );
+
+/* Get a pointer to the memory in the transformed PointSet. */
+      ptr_out = astGetPoints( pset_out );
+
+      if( pset_out && astStatus == AST__INTER ) {
+         p = in;
+         for( ip = 0; ip < np; ip++ ) {
+            for( ic = 0; ic < naxin; ic++ ) printf("%.*g\n", DBL_DIG, *(p++) );
+         }
+      }
+
+      if( astOK ) {
+
+/* Store the resulting axis values in the output array. */
+         p = result;
+         for( ip = 0; ip < np; ip++ ) {
+            for( ic = 0; ic < naxout; ic++ ) *(p++) = ptr_out[ ic ][ ip ];
+         }
+      }
+
+/* Free resources. */
+      pset_out = astAnnul( pset_out );
+   }
+   pset_in = astAnnul( pset_in );
+   map = astAnnul( map );
+
+/* Return NULL if anything went wrong. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result.*/
+   return result;
+}
+
+static AstPointSet *RegTransform( AstRegion *this, AstPointSet *in,
+                                  int forward, AstPointSet *out, AstFrame **frm, int *status ) {
+/*
+*+
+*  Name:
+*     astRegTransform
+
+*  Purpose:
+*     Transform a set of points using the encapsulated FrameSet.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstPointSet *astRegTransform( AstRegion *this, AstPointSet *in,
+*                                   int forward, AstPointSet *out,
+*                                   AstFrameSet **frm )
+
+*  Class Membership:
+*     Region virtual function
+
+*  Description:
+*     This function takes a Region and a set of points encapsulated
+*     in a PointSet, and applies either the forward or inverse
+*     coordinate transformation represented by the encapsulated FrameSet.
+*     It also returned a pointer to either the current or base Frame in
+*     the FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     in
+*        Pointer to the PointSet holding the input coordinate data. If
+*        NULL then the "points" PointSet within the supplied Region
+*        ("this") is used.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        (from base to current) should be applied, while a zero value requests
+*        the inverse transformation (from current to base).
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     frm
+*        Location at which to return a pointer to a Frame. If "forward"
+*        is non-zero, the current Frame in the encapsulated FrameSet will
+*        be returned. Otherwise, the base Frame is returned. The returned
+*        pointer should be annulled when no longer needed. May be NULL if
+*        no pointer is needed.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet. If "out" is NULL,
+*     the returned pointer will be a clone of "in" if the Mapping is a
+*     UnitMap. If "out" is not NULL, then the supplied "out" PointSet will
+*     be used and returned.
+
+*  Notes:
+*     - An error will result if the Region supplied does not define
+*     the requested coordinate transformation (either forward or
+*     inverse).
+*     - The number of coordinate values per point in the input
+*     PointSet must match the number of input coordinates for the
+*     Region being applied (or number of output coordinates if the
+*     inverse transformation is requested).  This will be equal to the
+*     number of axes in the Region's base Frame (or the current
+*     Frame for the inverse transformation).
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points and coordinate values per point to
+*     accommodate the result (e.g. the number of Region output
+*     coordinates, or number of input coordinates if the inverse
+*     transformation is requested). Any excess space will be ignored.
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstMapping *smap;           /* Pointer to simplified Mapping */
+   AstPointSet *result;        /* Pointer value to return */
+
+/* Initialise */
+   if( frm ) *frm = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* If no input PointSet was provided, use the PointSet in the Region. */
+   if( !in ) {
+      if( this->points ) {
+         in = this->points;
+      } else {
+         astError( AST__INTER, "astRegTransform(%s): No PointSet supplied "
+                   "and the supplied %s has no PointSet (internal AST "
+                   "programming error)", status, astGetClass( this ),astGetClass( this ) );
+      }
+   }
+
+/* Get the simplified Mapping from base to current Frame. */
+   smap = astRegMapping( this );
+
+/* If it is a UnitMap, return a clone of the input PointSet unless an
+   explicit output PointSet has been supplied. */
+   if( astIsAUnitMap( smap ) && !out ) {
+      result = astClone( in );
+
+/* Otherwise use the Mapping to transform the supplied positions. */
+   } else {
+      result = astTransform( smap, in, forward, out );
+   }
+
+/* Return a pointer to the appropriate Frame. */
+   if( frm ) *frm = astGetFrame( this->frameset, forward ? AST__CURRENT : AST__BASE );
+
+/* Release resources. */
+   smap = astAnnul( smap );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static int Unformat( AstFrame *this_frame, int axis, const char *string,
+                     double *value, int *status ) {
+/*
+*  Name:
+*     Unformat
+
+*  Purpose:
+*     Read a formatted coordinate value for a Region axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int Unformat( AstFrame *this, int axis, const char *string,
+*                   double *value, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the public astUnformat
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function reads a formatted coordinate value for a Region
+*     axis (supplied as a string) and returns the equivalent numerical
+*     value as a double. It also returns the number of characters read
+*     from the string.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     axis
+*        The number of the Region axis for which the coordinate
+*        value is to be read (axis numbering starts at zero for the
+*        first axis).
+*     string
+*        Pointer to a constant null-terminated string containing the
+*        formatted coordinate value.
+*     value
+*        Pointer to a double in which the coordinate value read will be
+*        returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of characters read from the string to obtain the
+*     coordinate value.
+
+*  Notes:
+*     - Any white space at the beginning of the string will be
+*     skipped, as also will any trailing white space following the
+*     coordinate value read. The function's return value will reflect
+*     this.
+*     - A function value of zero (and no coordinate value) will be
+*     returned, without error, if the string supplied does not contain
+*     a suitably formatted value.
+*     - The string "<bad>" is recognised as a special case and will
+*     generate the value AST__BAD, without error. The test for this
+*     string is case-insensitive and permits embedded white space.
+*     - A function result of zero will be returned and no coordinate
+*     value will be returned via the "value" pointer if this function
+*     is invoked with the global error status set, or if it should
+*     fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   double coord;                 /* Coordinate value read */
+   int nc;                       /* Number of characters read */
+
+/* Initialise. */
+   nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return nc;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astUnformat" );
+
+/* Obtain a pointer to the Region's current Frame and invoke the
+   astUnformat method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   nc = astUnformat( fr, axis, string, &coord );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the number of characters read. */
+   if ( !astOK ) {
+      nc = 0;
+
+/* Otherwise, if characters were read, return the coordinate value. */
+   } else if ( nc ) {
+      *value = coord;
+   }
+
+/* Return the number of characters read. */
+   return nc;
+}
+
+static int ValidateAxis( AstFrame *this_frame, int axis, int fwd,
+                         const char *method, int *status ) {
+/*
+*  Name:
+*     ValidateAxis
+
+*  Purpose:
+*     Validate and permute a Region's axis index.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int ValidateAxis( AstFrame *this, int axis, int fwd, const char *method,
+*                       int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected
+*     astValidateAxis method inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of an index (zero-based) which
+*     is to be used to address one of the coordinate axes of the
+*     current Frame in a Region. If the index is valid, it is
+*     permuted using the axis permutation array associated with the
+*     Region's current Frame and the (zero-based) permuted axis
+*     index is returned.  This gives the index the axis had when the
+*     Frame was first created. If the axis index supplied is not
+*     valid, an error is reported and the global error status is set.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     axis
+*        The axis index (zero-based) to be checked. To be valid, it
+*        must lie between zero and (naxes-1) inclusive, where "naxes"
+*        is the number of coordinate axes associated with the
+*        Region's current Frame.
+*     fwd
+*        If non-zero, the suppplied axis index is assumed to be an
+*        "external" axis index, and the corresponding "internal" axis index
+*        is returned as the function value. Otherwise, the suppplied axis
+*        index is assumed to be an "internal" axis index, and the
+*        corresponding "external" axis index is returned as the function
+*        value.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The permuted axis index - either "internal" or "external" as
+*     specified by "fwd".
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+   int naxes;                    /* Number of Region axes */
+   int result;                   /* Permuted axis index */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_frame;
+
+/* Determine the number of Region axes. */
+   naxes = astGetNaxes( this );
+   if ( astOK ) {
+
+/* If the Region has no axes, report an error (convert to 1-based
+   axis numbering for the benefit of the public interface). */
+      if ( naxes == 0 ) {
+         astError( AST__AXIIN, "%s(%s): Invalid attempt to use an axis index "
+                   "(%d) for a %s which has no axes.", status, method,
+                   astGetClass( this ), axis + 1, astGetClass( this ) );
+
+/* Otherwise, check the axis index for validity and report an error if
+   it is not valid (again, convert to 1-based axis numbering). */
+      } else if ( ( axis < 0 ) || ( axis >= naxes ) ) {
+         astError( AST__AXIIN, "%s(%s): Axis index (%d) invalid - it should "
+                   "be in the range 1 to %d.", status, method, astGetClass( this ),
+                   axis + 1, naxes );
+
+/* If the axis index was valid, obtain a pointer to the Region's
+   current Frame and invoke this Frame's astValidateAxis method to
+   obtain the permuted axis index. Annul the Frame pointer
+   afterwards. */
+      } else {
+         fr = astGetFrame( this->frameset, AST__CURRENT );
+         result = astValidateAxis( fr, axis, fwd, "astValidateAxis" );
+         fr = astAnnul( fr );
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static void ValidateAxisSelection( AstFrame *this_frame, int naxes,
+                                   const int *axes, const char *method, int *status ) {
+/*
+*  Name:
+*     ValidateAxisSelection
+
+*  Purpose:
+*     Check that a set of axes selected from a Frame is valid.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void ValidateAxisSelection( AstFrame *this, int naxes,
+*                                 const int *axes, const char *method, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astValidateAxisSelection
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of an array of (zero-based)
+*     axis indices that specify a set of axes to be selected from a
+*     Frame. To be valid, no axis should be selected more than
+*     once. In assessing this, any axis indices that do not refer to
+*     valid Frame axes (e.g. are set to -1) are ignored.
+*
+*     If the axis selection is valid, this function returns without further
+*     action. Otherwise, an error is reported and the global error status is
+*     set.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     naxes
+*        The number of axes to be selected (may be zero).
+*     axes
+*        Pointer to an array of int with naxes elements that contains the
+*        (zero based) axis indices to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis selection. This method name is used
+*        solely for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to current Frame */
+   AstRegion *this;              /* Pointer to the Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke this
+   Frame's astValidateAxisSelection method. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   astValidateAxisSelection( fr, naxes, axes, method );
+   fr = astAnnul( fr );
+
+}
+
+static int ValidateSystem( AstFrame *this_frame, AstSystemType system, const char *method, int *status ) {
+/*
+*  Name:
+*     ValidateSystem
+
+*  Purpose:
+*     Validate a value for a Frame's System attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     int ValidateSystem( AstFrame *this, AstSystemType system,
+*                         const char *method, int *status )
+
+*  Class Membership:
+*     Region member function (over-rides the protected astValidateSystem
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of the supplied system value.
+*     If the value is valid, it is returned unchanged. Otherwise, an
+*     error is reported and a value of AST__BADSYSTEM is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The system value to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The validated system value.
+
+*  Notes:
+*     - A value of AST_BADSYSTEM will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSystemType result;      /* Validated system value */
+   AstFrame *fr;              /* Pointer to FrameSet's current Frame */
+   AstRegion *this;           /* Pointer to the Region structure */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the FrameSet structure. */
+   this = (AstRegion *) this_frame;
+
+/* Obtain a pointer to the Region's encapsulated Frame and invoke the
+   astValidateSystem method for this Frame. Annul the Frame pointer
+   afterwards. */
+   fr = astGetFrame( this->frameset, AST__CURRENT );
+   result = astValidateSystem( this, system, method );
+   fr = astAnnul( fr );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BADSYSTEM;
+
+/* Return the result. */
+   return result;
+}
+
+/* Region Attributes. */
+/* -------------------- */
+
+/*
+*att++
+*  Name:
+*     Adaptive
+
+*  Purpose:
+*     Should the area adapt to changes in the coordinate system?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     The coordinate system represented by a Region may be changed by
+*     assigning new values to attributes such as System, Unit, etc.
+*     For instance, a Region representing an area on the sky in ICRS
+*     coordinates may have its System attribute changed so that it
+*     represents (say) Galactic coordinates instead of ICRS. This
+*     attribute controls what happens when the coordinate system
+*     represented by a Region is changed in this way.
+*
+*     If Adaptive is non-zero (the default), then area represented by the
+*     Region adapts to the new coordinate system. That is, the numerical
+*     values which define the area represented by the Region are changed
+*     by mapping them from the old coordinate system into the new coordinate
+*     system. Thus the Region continues to represent the same physical
+*     area.
+*
+*     If Adaptive is zero, then area represented by the Region does not adapt
+*     to the new coordinate system. That is, the numerical values which
+*     define the area represented by the Region are left unchanged. Thus
+*     the physical area represented by the Region will usually change.
+*
+*     As an example, consider a Region describe a range of wavelength from
+*     2000 Angstrom to 4000 Angstrom. If the Unit attribute for the Region
+*     is changed from Angstrom to "nm" (nanometre), what happens depends
+*     on the setting of Adaptive. If Adaptive is non-zero, the Mapping
+*     from the old to the new coordinate system is found. In this case it
+*     is a simple scaling by a factor of 0.1 (since 1 Angstrom is 0.1 nm).
+*     This Mapping is then used to modify the numerical values within the
+*     Region, changing 2000 to 200 and 4000 to 400. Thus the modified
+*     region represents 200 nm to 400 nm, the same physical space as
+*     the original 2000 Angstrom to 4000 Angstrom. However, if Adaptive
+*     had been zero, then the numerical values would not have been changed,
+*     resulting in the final Region representing 2000 nm to 4000 nm.
+*
+*     Setting Adaptive to zero can be necessary if you want correct
+*     inaccurate attribute settings in an existing Region. For instance,
+*     when creating a Region you may not know what Epoch value to use, so
+*     you would leave Epoch unset resulting in some default value being used.
+*     If at some later point in the application, the correct Epoch value
+*     is determined, you could assign the correct value to the Epoch
+*     attribute. However, you would first need to set Adaptive temporarily
+*     to zero, because otherwise the area represented by the Region would
+*     be Mapped from the spurious default Epoch to the new correct Epoch,
+*     which is not what is required.
+
+*  Applicability:
+*     Region
+*        All Regions have this attribute.
+*att--
+*/
+
+/* This is a boolean value (0 or 1) with a value of -INT_MAX when
+   undefined but yielding a default of 1. */
+astMAKE_CLEAR(Region,Adaptive,adaptive,-INT_MAX)
+astMAKE_GET(Region,Adaptive,int,1,( ( this->adaptive == -INT_MAX ) ?
+                                   1 : this->adaptive ))
+astMAKE_SET(Region,Adaptive,int,adaptive,( value != 0 ))
+astMAKE_TEST(Region,Adaptive,( this->adaptive != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     Negated
+
+*  Purpose:
+*     Region negation flag.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls whether a Region represents the "inside" or
+*     the "outside" of the area which was supplied when the Region was
+*     created. If the attribute value is zero (the default), the Region
+*     represents the inside of the original area. However, if it is non-zero,
+*     it represents the outside of the original area. The value of this
+*     attribute may be toggled using the
+c     astNegate function.
+f     AST_NEGATE routine.
+
+*     Note, whether the boundary is considered to be inside the Region or
+*     not is controlled by the Closed attribute. Changing the value of
+*     the Negated attribute does not change the value of the Closed attribute.
+*     Thus, if Region is closed, then the boundary of the Region will be
+*     inside the Region, whatever the setting of the Negated attribute.
+
+*  Applicability:
+*     Region
+*        All Regions have this attribute.
+*att--
+*/
+
+/* This is a boolean value (0 or 1) with a value of -INT_MAX when
+   undefined but yielding a default of zero. */
+astMAKE_CLEAR(Region,Negated,negated,(astResetCache(this),-INT_MAX))
+astMAKE_GET(Region,Negated,int,0,( ( this->negated == -INT_MAX ) ?
+                                   0 : this->negated ))
+astMAKE_SET(Region,Negated,int,negated,(astResetCache(this),( value != 0 )))
+astMAKE_TEST(Region,Negated,( this->negated != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     Bounded
+
+*  Purpose:
+*     Is the Region bounded?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean), read-only.
+
+*  Description:
+*     This is a read-only attribute indicating if the Region is bounded.
+*     A Region is bounded if it is contained entirely within some
+*     finite-size bounding box.
+
+*  Applicability:
+*     Region
+*        All Regions have this attribute.
+*att--
+*/
+
+/*
+*att+
+*  Name:
+*     RegionFS
+
+*  Purpose:
+*     Should Region FrameSet be dumped?
+
+*  Type:
+*     Protected attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute indicates whether the FrameSet encapsulated by the
+*     Region should be included in the dump produced by the Dump function.
+*
+*     If set to a non-zero value (the default), the FrameSet in the Region
+*     will always be included in the dump as usual. If set to zero, the
+*     FrameSet will only be included in the dump if the Mapping from base
+*     to current Frame is not a UnitMap. If the base->current Mapping is
+*     a UnitMap, the FrameSet is omitted from the dump. If the dump is
+*     subsequently used to re-create the Region, the new Region will have a
+*     default FrameSet containing a single default Frame with the appropriate
+*     number of axes.
+*
+*     This facility is indended to reduce the size of textual dumps of
+*     Regions in situations where the Frame to which the Region refers can
+*     be implied by the context in which the Region is used. This is
+*     often the case when a Region is encapsulated within another Region.
+*     In such cases the current Frame of the encapsulated Region will
+*     usually be equivalent to the base Frame of the parent Region
+*     structure, and so can be re-instated (by calling the astSetRegFS
+*     method) even if the FrameSet is omitted from the dump of the
+*     encapsulated Region. Note if the base->current Mapping in the FrameSet
+*     in the encapsulated Region is not a UnitMap, then we should always
+*     dump the FrameSet regardless of the setting of RegionFS. This is because
+*     the parent Region structure will not know how to convert the PointSet
+*     stored in the encapsulated Region into its own base Frame if the
+*     FrameSet is not available.
+
+*  Applicability:
+*     Region
+*        All Regions have this attribute.
+*att-
+*/
+
+/* This is a boolean value (0 or 1) with a value of -INT_MAX when
+   undefined but yielding a default of one. */
+astMAKE_CLEAR(Region,RegionFS,regionfs,-INT_MAX)
+astMAKE_TEST(Region,RegionFS,( this->regionfs != -INT_MAX ))
+astMAKE_SET(Region,RegionFS,int,regionfs,( value != 0 ))
+astMAKE_GET(Region,RegionFS,int,1,( ( this->regionfs == -INT_MAX ) ?
+                                   1 : this->regionfs ))
+
+/*
+*att++
+*  Name:
+*     FillFactor
+
+*  Purpose:
+*     Fraction of the Region which is of interest.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute indicates the fraction of the Region which is of
+*     interest. AST does not use this attribute internally for any purpose.
+*     Typically, it could be used to indicate the fraction of the Region for
+*     which data is available.
+*
+*     The supplied value must be in the range 0.0 to 1.0, and the default
+*     value is 1.0 (except as noted below).
+
+*  Applicability:
+*     Region
+*        All Regions have this attribute.
+*     CmpRegion
+*        The default FillFactor for a CmpRegion is the FillFactor of its
+*        first component Region.
+*     Prism
+*        The default FillFactor for a Prism is the product of the
+*        FillFactors of its two component Regions.
+*     Stc
+*        The default FillFactor for an Stc is the FillFactor of its
+*        encapsulated Region.
+*att--
+*/
+
+astMAKE_CLEAR(Region,FillFactor,fillfactor,AST__BAD)
+astMAKE_GET(Region,FillFactor,double,1.0,( ( this->fillfactor == AST__BAD ) ?
+                                        1.0 : this->fillfactor ))
+astMAKE_TEST(Region,FillFactor,( this->fillfactor != AST__BAD ))
+astMAKE_SET(Region,FillFactor,double,fillfactor,((value<0.0||value>1.0)?(
+       astError(AST__ATSER,"astSetFillFactor(%s): Invalid value (%g) supplied "
+                "for attribute FillFactor.", status,astGetClass(this),value),
+       astError(AST__ATSER,"FillFactor values should be in the range 0.0 to 1.0", status),
+       this->fillfactor):value))
+
+/*
+*att++
+*  Name:
+*     MeshSize
+
+*  Purpose:
+*     Number of points used to represent the boundary of a Region.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute controls how many points are used when creating a
+*     mesh of points covering the boundary or volume of a Region. Such a
+*     mesh is returned by the
+c     astGetRegionMesh
+f     AST_GETREGIONMESH
+*     method. The boundary mesh is also used when testing for overlap
+*     between two Regions: each point in the bomdary mesh of the first
+*     Region is checked to see if it is inside or outside the second Region.
+*     Thus, the reliability of the overlap check depends on the value assigned
+*     to this attribute. If the value used is very low, it is possible for
+*     overlaps to go unnoticed. High values produce more reliable results, but
+*     can result in the overlap test being very slow. The default value is 200
+*     for two dimensional Regions and 2000 for three or more dimensional
+*     Regions (this attribute is not used for 1-dimensional regions since the
+*     boundary of a simple 1-d Region can only ever have two points). A
+*     value of five is used if the supplied value is less than five.
+
+*  Applicability:
+*     Region
+*        All Regions have this attribute.
+*     CmpRegion
+*        The default MeshSize for a CmpRegion is the MeshSize of its
+*        first component Region.
+*     Stc
+*        The default MeshSize for an Stc is the MeshSize of its
+*        encapsulated Region.
+*att--
+*/
+/* If the value of MeshSize is set or cleared, annul the PointSet used to
+   cache a mesh of base Frame boundary points. This will force a new
+   PointSet to be created next time it is needed. See function RegMesh. */
+astMAKE_CLEAR(Region,MeshSize,meshsize,(astResetCache(this),-INT_MAX))
+astMAKE_SET(Region,MeshSize,int,meshsize,(astResetCache(this),( value > 5 ? value : 5 )))
+astMAKE_TEST(Region,MeshSize,( this->meshsize != -INT_MAX ))
+astMAKE_GET(Region,MeshSize,int,0,( ( this->meshsize == -INT_MAX)?((astGetNaxes(this)==1)?2:((astGetNaxes(this)==2)?200:2000)): this->meshsize ))
+
+/*
+*att++
+*  Name:
+*     Closed
+
+*  Purpose:
+*     Should the boundary be considered to be inside the region?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls whether points on the boundary of a Region
+*     are considered to be inside or outside the region. If the attribute
+*     value is non-zero (the default), points on the boundary are considered
+*     to be inside the region (that is, the Region is "closed"). However,
+*     if the attribute value is zero, points on the bounary are considered
+*     to be outside the region.
+
+*  Applicability:
+*     Region
+*        All Regions have this attribute.
+*     PointList
+*        The value of the Closed attribute is ignored by PointList regions.
+*        If the PointList region has not been negated, then it is always
+*        assumed to be closed. If the PointList region has been negated, then
+*        it is always assumed to be open. This is required since points
+*        have zero volume and therefore consist entirely of boundary.
+*     CmpRegion
+*        The default Closed value for a CmpRegion is the Closed value of its
+*        first component Region.
+*     Stc
+*        The default Closed value for an Stc is the Closed value of its
+*        encapsulated Region.
+*att--
+*/
+/* This is a boolean value (0 or 1) with a value of -INT_MAX when
+   undefined but yielding a default of 1. */
+astMAKE_CLEAR(Region,Closed,closed,(astResetCache(this),-INT_MAX))
+astMAKE_GET(Region,Closed,int,1,( ( this->closed == -INT_MAX ) ?
+                                   1 : this->closed ))
+astMAKE_SET(Region,Closed,int,closed,(astResetCache(this),( value != 0 )))
+astMAKE_TEST(Region,Closed,( this->closed != -INT_MAX ))
+
+/* Access to attributes of the encapsulated Frame. */
+/* ----------------------------------------------- */
+/* Use the macros defined at the start of this file to implement
+   private member functions that give access to the attributes of the
+   encapsulated Frame of a Region and its axes. These functions over-ride
+   the attribute access methods inherited from the Frame class. */
+
+/* Clear, Get, Set and Test axis-independent Frame attributes. */
+MAKE_CLEAR(Digits)
+MAKE_CLEAR(Domain)
+MAKE_CLEAR(MatchEnd)
+MAKE_CLEAR(MaxAxes)
+MAKE_CLEAR(MinAxes)
+MAKE_CLEAR(Permute)
+MAKE_CLEAR(PreserveAxes)
+MAKE_CLEAR(Title)
+
+MAKE_GET(Digits,int)
+MAKE_GET(Domain,const char *)
+MAKE_GET(MatchEnd,int)
+MAKE_GET(MaxAxes,int)
+MAKE_GET(MinAxes,int)
+MAKE_GET(Permute,int)
+MAKE_GET(PreserveAxes,int)
+MAKE_GET(Title,const char *)
+MAKE_SET(Digits,int,I)
+MAKE_SET(Domain,const char *,C)
+MAKE_SET(MatchEnd,int,I)
+MAKE_SET(MaxAxes,int,I)
+MAKE_SET(MinAxes,int,I)
+MAKE_SET(Permute,int,I)
+MAKE_SET(PreserveAxes,int,I)
+MAKE_SET(Title,const char *,C)
+MAKE_TEST(Digits)
+MAKE_TEST(Domain)
+MAKE_TEST(MatchEnd)
+MAKE_TEST(MaxAxes)
+MAKE_TEST(MinAxes)
+MAKE_TEST(Permute)
+MAKE_TEST(PreserveAxes)
+MAKE_TEST(Title)
+
+MAKE_GET(ActiveUnit,int)
+MAKE_SET(ActiveUnit,int,I)
+MAKE_TEST(ActiveUnit)
+
+MAKE_GET(System,AstSystemType)
+MAKE_SET_SYSTEM(System)
+MAKE_TEST(System)
+MAKE_CLEAR(System)
+
+MAKE_GET(AlignSystem,AstSystemType)
+MAKE_SET_SYSTEM(AlignSystem)
+MAKE_TEST(AlignSystem)
+MAKE_CLEAR(AlignSystem)
+
+MAKE_GET(Epoch,double)
+MAKE_SET(Epoch,double,D)
+MAKE_TEST(Epoch)
+MAKE_CLEAR(Epoch)
+
+MAKE_GET(ObsLon,double)
+MAKE_SET(ObsLon,double,D)
+MAKE_TEST(ObsLon)
+MAKE_CLEAR(ObsLon)
+
+MAKE_GET(ObsLat,double)
+MAKE_SET(ObsLat,double,D)
+MAKE_TEST(ObsLat)
+MAKE_CLEAR(ObsLat)
+
+MAKE_GET(ObsAlt,double)
+MAKE_SET(ObsAlt,double,D)
+MAKE_TEST(ObsAlt)
+MAKE_CLEAR(ObsAlt)
+
+/* Clear, Get, Set and Test axis-dependent Frame attributes. */
+MAKE_CLEAR_AXIS(Direction)
+MAKE_CLEAR_AXIS(Format)
+MAKE_CLEAR_AXIS(Label)
+MAKE_CLEAR_AXIS(Symbol)
+MAKE_CLEAR_AXIS(Unit)
+MAKE_GET_AXIS(Direction,int)
+MAKE_GET_AXIS(Format,const char *)
+MAKE_GET_AXIS(Label,const char *)
+MAKE_GET_AXIS(Symbol,const char *)
+MAKE_GET_AXIS(Unit,const char *)
+MAKE_SET_AXIS(Direction,int,I)
+MAKE_SET_AXIS(Format,const char *,C)
+MAKE_SET_AXIS(Label,const char *,C)
+MAKE_SET_AXIS(Symbol,const char *,C)
+MAKE_SET_AXIS(Unit,const char *,C)
+MAKE_TEST_AXIS(Direction)
+MAKE_TEST_AXIS(Format)
+MAKE_TEST_AXIS(Label)
+MAKE_TEST_AXIS(Symbol)
+MAKE_TEST_AXIS(Unit)
+
+MAKE_GET_AXIS(Bottom,double)
+MAKE_SET_AXIS(Bottom,double,D)
+MAKE_TEST_AXIS(Bottom)
+MAKE_CLEAR_AXIS(Bottom)
+
+MAKE_GET_AXIS(Top,double)
+MAKE_SET_AXIS(Top,double,D)
+MAKE_TEST_AXIS(Top)
+MAKE_CLEAR_AXIS(Top)
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Region objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Region objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstRegion *in;                /* Pointer to input Region */
+   AstRegion *out;               /* Pointer to output Region */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Regions. */
+   in = (AstRegion *) objin;
+   out = (AstRegion *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output Region. */
+   out->basemesh = NULL;
+   out->basegrid = NULL;
+   out->frameset = NULL;
+   out->points = NULL;
+   out->unc = NULL;
+   out->negation = NULL;
+   out->defunc = NULL;
+
+/* Now copy each of the above structures. */
+   out->frameset = astCopy( in->frameset );
+   if( in->points ) out->points = astCopy( in->points );
+   if( in->basemesh ) out->basemesh = astCopy( in->basemesh );
+   if( in->basegrid ) out->basegrid = astCopy( in->basegrid );
+   if( in->unc ) out->unc = astCopy( in->unc );
+   if( in->negation ) out->negation = astCopy( in->negation );
+   if( in->defunc ) out->defunc = astCopy( in->defunc );
+}
+
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Region objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Region objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstRegion *this;                 /* Pointer to Region */
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) obj;
+
+/* Annul all resources. */
+   this->frameset = astAnnul( this->frameset );
+   if( this->points ) this->points = astAnnul( this->points );
+   if( this->basemesh ) this->basemesh = astAnnul( this->basemesh );
+   if( this->basegrid ) this->basegrid = astAnnul( this->basegrid );
+   if( this->unc ) this->unc = astAnnul( this->unc );
+   if( this->negation ) this->negation = astAnnul( this->negation );
+   if( this->defunc ) this->defunc = astAnnul( this->defunc );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Region objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Region class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Region whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+#define COM_LEN 50               /* Maximum length of a comment */
+
+/* Local Variables: */
+   AstFrame *fr;                 /* Pointer to the current Frame */
+   AstMapping *smap;             /* Base->current Mapping */
+   AstRegion *this;              /* Pointer to the Region structure */
+   AstRegion *unc;               /* Pointer to the uncertainty Region */
+   double dval;                  /* Floating point attribute value */
+   int ival;                     /* Integer attribute value */
+   int set;                      /* Attribute value set? */
+   int unit;                     /* Base->current is unitmap? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Region structure. */
+   this = (AstRegion *) this_object;
+
+/* Write out values representing the instance variables for the
+   Region class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Negated. */
+/* -------- */
+   set = TestNegated( this, status );
+   ival = set ? GetNegated( this, status ) : astGetNegated( this );
+   astWriteInt( channel, "Negate", (ival != 0), 0, ival,
+                ival ? "Region negated" : "Region not negated" );
+
+/* FillFactor */
+/* ---------- */
+   set = TestFillFactor( this, status );
+   dval = set ? GetFillFactor( this, status ) : astGetFillFactor( this );
+   astWriteDouble( channel, "Fill", set, 0, dval,"Region fill factor" );
+
+/* MeshSize. */
+/* --------- */
+   set = TestMeshSize( this, status );
+   ival = set ? GetMeshSize( this, status ) : astGetMeshSize( this );
+   astWriteInt( channel, "MeshSz", set, 0, ival,
+                "No. of points used to represent boundary" );
+
+/* Closed. */
+/* ------- */
+   set = TestClosed( this, status );
+   ival = set ? GetClosed( this, status ) : astGetClosed( this );
+   astWriteInt( channel, "Closed", set, 0, ival,
+                ival ? "Boundary is inside" : "Boundary is outside" );
+
+/* Adaptive */
+/* -------- */
+   set = TestAdaptive( this, status );
+   ival = set ? GetAdaptive( this, status ) : astGetAdaptive( this );
+   astWriteInt( channel, "Adapt", (ival != 0), 0, ival,
+                ival ? "Region adapts to coord sys changes" : "Region does not adapt to coord sys changes" );
+
+/* FrameSet */
+/* -------- */
+
+/* If the vertices are the same in both base and current Frames (i.e.
+   if the Frames are connected by a UnitMap), then just dump the current
+   Frame (unless the RegionFS attribute is zero, in which case the
+   current Frame can be determined from the higher level context of the
+   Region and so does not need to be dumped- e.g. if the Region is contained
+   within another Region the parent Region will define the current Frame).
+   Otherwise, dump the whole FrameSet. */
+   ival = astGetRegionFS( this );
+   smap = astRegMapping( this );
+   if( ( unit = astIsAUnitMap( smap ) ) ){
+      set = 0;
+      if( ival ) {
+         fr = astGetFrame( this->frameset, AST__CURRENT );
+         astWriteObject( channel, "Frm", 1, 1, fr, "Coordinate system" );
+         fr = astAnnul( fr );
+      }
+   } else {
+      set = ( ival == 0 );
+      astWriteObject( channel, "FrmSet", 1, 1, this->frameset,
+                      "Original & current coordinate systems" );
+   }
+
+/* Annul the Mapping pointers */
+   smap = astAnnul( smap );
+
+/* RegionFS */
+/* -------- */
+   astWriteInt( channel, "RegFS", set, 0, ival,
+                ival ? "Include Frame in dump" : "Do not include Frame in dump" );
+
+/* Points */
+/* ------ */
+   if( this->points ) {
+      astWriteObject( channel, "Points", 1, 1, this->points,
+                      "Points defining the shape" );
+
+/* If the FrameSet was not included in the dump, then the loader will use
+   the PointSet to determine the number of axes in the frame spanned by
+   the Region. If there is no PointSet, then we must explicitly include
+   an item giving the number of axes.*/
+   } else {
+      astWriteInt( channel, "RegAxes", 1, 1, astGetNaxes( this ),
+                   "Number of axes spanned by the Region" );
+   }
+
+/* Uncertainty */
+/* ----------- */
+/* Only dump the uncertinaty Region if required. */
+   if( astTestUnc( this ) ) {
+      unc = astGetUncFrm( this, AST__BASE );
+      astWriteObject( channel, "Unc", 1, 1, unc,
+                      "Region defining positional uncertainties." );
+      unc = astAnnul( unc );
+   }
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsARegion and astCheckRegion functions using
+   the macros defined for this purpose in the "object.h" header
+   file. */
+astMAKE_ISA(Region,Frame)
+astMAKE_CHECK(Region)
+
+AstRegion *astInitRegion_( void *mem, size_t size, int init,
+                           AstRegionVtab *vtab, const char *name,
+                           AstFrame *frame, AstPointSet *pset,
+                           AstRegion *unc, int *status ){
+/*
+*+
+*  Name:
+*     astInitRegion
+
+*  Purpose:
+*     Initialise a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstRegion *astInitRegion( void *mem, size_t size, int init,
+*                               AstRegionVtab *vtab, const char *name,
+*                               AstFrame *frame, AstpointSet *pset,
+*                               AstRegion *unc )
+
+*  Class Membership:
+*     Region initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new Region object. It allocates memory (if
+*     necessary) to accommodate the Region plus any additional data
+*     associated with the derived class.  It then initialises a
+*     Region structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual
+*     function table for a Region at the start of the memory passed
+*     via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Region is to be
+*        created. This must be of sufficient size to accommodate the
+*        Region data (sizeof(Region)) plus any data used by the
+*        derived class. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Region (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Region structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*     init
+*        A logical flag indicating if the Region's virtual function
+*        table is to be initialised. If this value is non-zero, the
+*        virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Region.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*     frame
+*        Pointer to the encapsulated Frame. A deep copy of this Frame is
+*        taken. This means that subsequent changes to the supplied Frame
+*        will have no effect on the new Region.
+*     pset
+*        A PointSet holding the points which define the Region. These
+*        positions should refer to the given Frame. May be NULL.
+*     unc
+*        A pointer to a Region which specifies the uncertainty in the
+*        supplied positions (all points on the boundary of the new Region
+*        being initialised are assumed to have the same uncertainty). A NULL
+*        pointer can be supplied, in which case default uncertainties equal to
+*        1.0E-6 of the dimensions of the new Region's bounding box are used.
+*        If an uncertainty Region is supplied, it must be of a class for
+*        which all instances are centro-symetric (e.g. Box, Circle, Ellipse,
+*        etc.) or be a Prism containing centro-symetric component Regions.
+*        Its encapsulated Frame must be related to the Frame supplied for
+*        parameter "frame" (i.e. astConvert should be able to find a Mapping
+*        between them). Two positions in the "frame" Frame are considered to be
+*        co-incident if their uncertainty Regions overlap. The centre of the
+*        supplied uncertainty Region is immaterial since it will be re-centred
+*        on the point being tested before use. A deep copy is taken of the
+*        supplied Region.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *f0;                  /* Frame to use */
+   AstRegion *new;                /* Pointer to new Region */
+   int nax;                       /* No. of axes in supplied Frame */
+   int ncoord;                    /* Coords per point */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if( init ) astInitRegionVtab( vtab, name );
+
+/* Note the number of axes in the supplied Frame. */
+   nax = astGetNaxes( frame );
+
+/* Check the pointset if supplied. */
+   if( pset ) {
+
+/* Note the number of axes per point in the supplied PointSet */
+      ncoord = astGetNcoord( pset );
+
+/* If OK, check that the number of coordinates per point matches the number
+   of axes in the Frame. Report an error if these numbers do not match. */
+      if ( astOK && ( ncoord != nax ) ) {
+         astError( AST__NCPIN, "astInitRegion(%s): Bad number of coordinate "
+                   "values per point (%d).", status, name, ncoord );
+         astError( AST__NCPIN, "The %s given requires %d coordinate value(s) "
+                   "for each point.", status, astGetClass( frame ), nax );
+      }
+   }
+
+/* Initialise a Frame structure (the parent class) as the first
+   component within the Region structure, allocating memory if
+   necessary. Give this Frame zero axes as the Frame information will be
+   specified by the encapsulated FrameSet. */
+   new = (AstRegion *) astInitFrame( mem, size, 0, (AstFrameVtab *) vtab,
+                                     name, 0 );
+   if ( astOK ) {
+
+/* Initialise the Region data. */
+/* ----------------------------- */
+      new->frameset = NULL;
+      new->points = NULL;
+      new->unc = NULL;
+      new->meshsize = -INT_MAX;
+      new->adaptive = -INT_MAX;
+      new->basemesh = NULL;
+      new->basegrid = NULL;
+      new->negated = -INT_MAX;
+      new->closed = -INT_MAX;
+      new->regionfs = -INT_MAX;
+      new->fillfactor = AST__BAD;
+      new->defunc = NULL;
+      new->nomap = 0;
+      new->negation = NULL;
+
+/* If the supplied Frame is a Region, gets its encapsulated Frame. If a
+   FrameSet was supplied, use its current Frame, otherwise use the
+   supplied Frame. */
+      if( astIsARegion( frame ) ) {
+         f0 = astGetFrame( ((AstRegion *) frame)->frameset, AST__CURRENT );
+
+      } else if( astIsAFrameSet( frame ) ) {
+         f0 = astGetFrame( (AstFrameSet *) frame, AST__CURRENT );
+
+      } else {
+         f0 = astClone( frame );
+      }
+
+/* Store a clone of the supplied PointSet pointer. */
+      new->points = pset ? astClone( pset ) : NULL;
+
+
+#ifdef DEBUG
+      if( pset ) {
+         double **ptr;
+         double lim;
+         int ii,jj, np;
+         ptr = astGetPoints( pset );
+         np = astGetNpoint( pset );
+         lim = sqrt( DBL_MAX );
+         for( ii = 0; astOK && ii < ncoord; ii++ ) {
+            for( jj = 0; jj < np; jj++ ) {
+               if( fabs( ptr[ ii ][ jj ] ) > lim ) {
+                  if( !strcmp( name, "Interval" ) ) {
+                     if( ptr[ ii ][ jj ] != AST__BAD &&
+                         ptr[ ii ][ jj ] != DBL_MAX &&
+                         ptr[ ii ][ jj ] != -DBL_MAX ) {
+                        astError( AST__INTER, "astInitRegion(%s): suspicious "
+                          "axis value (%g) supplied.", status, name, ptr[ ii ][ jj ] );
+                        break;
+                     }
+                  } else {
+                     astError( AST__INTER, "astInitRegion(%s): suspicious "
+                            "axis value (%g) supplied.", status, name,
+                            ptr[ ii ][ jj ] );
+                     break;
+                  }
+               }
+            }
+         }
+      }
+#endif
+
+/* Form a FrameSet consisting of two copies of the supplied Frame connected
+   together by a UnitMap, and store in the Region structure. We use the
+   private SetRegFS rather than the protected astSetRegFS because this
+   initialiser may be being called from a subclass which over-rides
+   astSetRegFS. If this were the case, then the implementation of
+   astSetRegFS provided by the subclass may access information within the
+   subclass structure which has not yet been initialised. */
+      SetRegFS( new, f0, status );
+      f0 = astAnnul( f0 );
+
+/* Store any uncertainty Region. Use the private SetUnc rather than
+   astSetUnc to avoid subclass implementations using subclass data which
+   has not yet been initialised. */
+      SetUnc( new, unc, status );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstRegion *astLoadRegion_( void *mem, size_t size,
+                           AstRegionVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadRegion
+
+*  Purpose:
+*     Load a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstRegion *astLoadRegion( void *mem, size_t size,
+*                               AstRegionVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     Region loader.
+
+*  Description:
+*     This function is provided to load a new Region using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Region structure in this memory, using data read from the
+*     input Channel.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Region is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Region data (sizeof(Region)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Region (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Region structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstRegion) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Region. If this is NULL, a pointer
+*        to the (static) virtual function table for the Region class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Region" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstFrame *f1;                  /* Base Frame for encapsulated FrameSet */
+   AstRegion *new;                /* Pointer to the new Region */
+   int nax;                       /* No. of axes in Frame, or FrameSet base Frame */
+   int naxpt;                     /* No. of axes in per point */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Region. In this case the
+   Region belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstRegion );
+      vtab = &class_vtab;
+      name = "Region";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitRegionVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Region. */
+   new = astLoadFrame( mem, size, (AstFrameVtab *) vtab, name,
+                       channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Region" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Negated */
+/* ------- */
+      new->negated = astReadInt( channel, "negate", -INT_MAX );
+      if ( TestNegated( new, status ) ) SetNegated( new, new->negated, status );
+
+/* FillFactor */
+/* ---------- */
+      new->fillfactor = astReadDouble( channel, "fill", AST__BAD );
+      if ( TestFillFactor( new, status ) ) SetFillFactor( new, new->fillfactor, status );
+
+/* MeshSize */
+/* -------- */
+      new->meshsize = astReadInt( channel, "meshsz", -INT_MAX );
+      if ( TestMeshSize( new, status ) ) SetMeshSize( new, new->meshsize, status );
+
+/* Closed */
+/* ------ */
+      new->closed = astReadInt( channel, "closed", -INT_MAX );
+      if ( TestClosed( new, status ) ) SetClosed( new, new->closed, status );
+
+/* Adaptive */
+/* -------- */
+      new->adaptive = astReadInt( channel, "adapt", -INT_MAX );
+      if ( TestAdaptive( new, status ) ) SetAdaptive( new, new->adaptive, status );
+
+/* Points */
+/* ------ */
+      new->points = astReadObject( channel, "points", NULL );
+
+/* If some points were found, ensure that they are in a PointSet and get
+   the number of axis values per point. */
+      if( new->points ){
+         if( astIsAPointSet( new->points) ) {
+            naxpt = astGetNcoord( new->points );
+         } else {
+            naxpt = 0;
+            astError( AST__REGIN, "astLoadRegion(%s): Corrupt %s specifies points "
+                      "using a %s (should be a PointSet).", status, astGetClass( new ),
+                      astGetClass( new ), astGetClass( new->points ) );
+         }
+
+/* If no PointSet was loaded, attempt to determine the number of axes
+   spanned by the Region by reading the RegAxes value. */
+      } else {
+         naxpt = astReadInt( channel, "regaxes", 0 );
+      }
+
+/* Uncertainty */
+/* ----------- */
+      new->unc = astReadObject( channel, "unc", NULL );
+      new->defunc = NULL;
+
+/* FrameSet */
+/* -------- */
+/* First see if the dump contains a single Frame. If so, create a
+   FrameSet from it and a copy of itself, using a UnitMap to connect the
+   two. */
+      new->nomap = 0;
+      new->frameset = NULL;
+      f1 = astReadObject( channel, "frm", NULL );
+      if( f1 ) {
+         new->regionfs = 1;
+         nax = astGetNaxes( f1 );
+         astSetRegFS( new, f1 );
+         f1 = astAnnul( f1 );
+
+/* If no Frame was found in the dump, look for a FrameSet. Get the number
+   of axes spanning its base Frame ("Nin"). */
+      } else {
+         new->frameset = astReadObject( channel, "frmset", NULL );
+         if( new->frameset ) {
+            nax = astGetNin( new->frameset );
+
+/* If a FrameSet was found, the value of the RegionFS attribute is still
+   unknown and so we must read it from an attribute as normal. */
+            new->regionfs = astReadInt( channel, "regfs", 1 );
+            if ( TestRegionFS( new, status ) ) SetRegionFS( new, new->regionfs, status );
+
+         } else {
+            nax = 0;
+         }
+      }
+
+/* If neither a Frame nor a FrameSet was found, create a default FrameSet
+   and set the RegionFS attribute false, to indicate that the FrameSet
+   should not be used. */
+      if( !new->frameset ){
+         nax = naxpt ? naxpt : 1;
+         f1 = astFrame( nax, "", status );
+         new->frameset = astFrameSet( f1, "", status );
+         astSetIdent( new->frameset, DUMMY_FS );
+         f1 = astAnnul( f1 );
+         new->regionfs = 0;
+      }
+
+/* Report an error if the number of axis values per point in the pointset is
+   incorrect. */
+      if ( astOK && new->points && ( naxpt != nax ) ) {
+         astError( AST__REGIN, "astLoadRegion(%s): Corrupt %s contains "
+                   " incorrect number of coordinate values per point (%d).", status,
+                   astGetClass( new ), astGetClass( new ), naxpt );
+         astError( AST__REGIN, "The %s requires %d coordinate value(s) "
+                   "for each point.", status, astGetClass( new ), nax );
+      }
+
+/* Initialise other fields which are used as caches for values derived
+   from the attributes set above. */
+      new->basemesh = NULL;
+      new->basegrid = NULL;
+
+/* If an error occurred, clean up by deleting the new Region. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Region pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+void astRegClearAttrib_( AstRegion *this, const char *attrib, char **base_attrib, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,RegClearAttrib))( this, attrib, base_attrib, status );
+}
+void astRegSetAttrib_( AstRegion *this, const char *setting, char **base_setting, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,RegSetAttrib))( this, setting, base_setting, status );
+}
+void astNegate_( AstRegion *this, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,Negate))( this, status );
+}
+AstFrame *astGetRegionFrame_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,GetRegionFrame))( this, status );
+}
+AstFrameSet *astGetRegionFrameSet_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,GetRegionFrameSet))( this, status );
+}
+AstRegion *astMapRegion_( AstRegion *this, AstMapping *map, AstFrame *frame, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,MapRegion))( this, map, frame, status );
+}
+int astOverlap_( AstRegion *this, AstRegion *that, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Region,Overlap))( this, that, status );
+}
+int astOverlapX_( AstRegion *that, AstRegion *this, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(that,Region,OverlapX))( that, this, status );
+}
+AstFrame *astRegFrame_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,RegFrame))( this, status );
+}
+AstRegion *astRegBasePick_( AstRegion *this, int naxes, const int *axes, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,RegBasePick))( this, naxes, axes, status );
+}
+AstPointSet *astBTransform_( AstRegion *this, AstPointSet *in,
+                             int forward, AstPointSet *out, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,BTransform))( this, in, forward, out, status );
+}
+AstPointSet *astRegTransform_( AstRegion *this, AstPointSet *in,
+                               int forward, AstPointSet *out,
+                               AstFrame **frm, int *status ) {
+   if( frm ) *frm = NULL;
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,RegTransform))( this, in, forward, out, frm, status );
+}
+int astRegPins_( AstRegion *this, AstPointSet *pset, AstRegion *unc, int **mask, int *status ){
+   if( mask ) *mask = NULL;
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Region,RegPins))( this, pset, unc, mask, status );
+}
+AstMapping *astRegMapping_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,RegMapping))( this, status );
+}
+int astRegDummyFS_( AstRegion *this, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Region,RegDummyFS))( this, status );
+}
+int astGetBounded_( AstRegion *this, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Region,GetBounded))( this, status );
+}
+int astTestUnc_( AstRegion *this, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Region,TestUnc))( this, status );
+}
+void astClearUnc_( AstRegion *this, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,ClearUnc))( this, status );
+}
+void astRegBaseBox_( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,RegBaseBox))( this, lbnd, ubnd, status );
+}
+void astRegBaseBox2_( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,RegBaseBox2))( this, lbnd, ubnd, status );
+}
+void astResetCache_( AstRegion *this, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,ResetCache))( this, status );
+}
+int astRegTrace_( AstRegion *this, int n, double *dist, double **ptr, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Region,RegTrace))( this, n, dist, ptr, status );
+}
+void astGetRegionBounds_( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,GetRegionBounds))( this, lbnd, ubnd, status );
+}
+void astGetRegionMesh_( AstRegion *this, int surface, int maxpoint,
+                        int maxcoord, int *npoint, double *points,
+                        int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,GetRegionMesh))( this, surface, maxpoint, maxcoord,
+                                             npoint, points, status );
+}
+void astGetRegionPoints_( AstRegion *this, int maxpoint, int maxcoord,
+                          int *npoint, double *points, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,GetRegionPoints))( this, maxpoint, maxcoord,
+                                               npoint, points, status );
+}
+void astShowMesh_( AstRegion *this, int format, const char *ttl, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,ShowMesh))( this, format,ttl, status );
+}
+void astGetRegionBounds2_( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,GetRegionBounds2))( this, lbnd, ubnd, status );
+}
+void astRegOverlay_( AstRegion *this, AstRegion *that, int unc, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,RegOverlay))( this, that, unc, status );
+}
+AstPointSet *astRegGrid_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,RegGrid))( this, status );
+}
+AstPointSet *astRegMesh_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,RegMesh))( this, status );
+}
+double *astRegCentre_( AstRegion *this, double *cen, double **ptr, int index,
+                       int ifrm, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,RegCentre))( this, cen, ptr, index, ifrm, status );
+}
+AstRegion *astGetNegation_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,GetNegation))( this, status );
+}
+AstRegion *astGetUncFrm_( AstRegion *this, int ifrm, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,GetUncFrm))( this, ifrm, status );
+}
+AstRegion *astGetDefUnc_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,GetDefUnc))( this, status );
+}
+AstRegion *astGetUnc_( AstRegion *this, int def, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,GetUnc))( this, def, status );
+}
+void astSetUnc_( AstRegion *this, AstRegion *unc, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,SetUnc))( this, unc, status );
+}
+AstFrameSet *astGetRegFS_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,GetRegFS))( this, status );
+}
+void astSetRegFS_( AstRegion *this, AstFrame *frm, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Region,SetRegFS))( this, frm, status );
+}
+AstPointSet *astRegBaseMesh_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,RegBaseMesh))( this, status );
+}
+AstRegion **astRegSplit_( AstRegion *this, int *nlist, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,RegSplit))( this, nlist, status );
+}
+AstPointSet *astRegBaseGrid_( AstRegion *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,RegBaseGrid))( this, status );
+}
+AstPointSet *astBndBaseMesh_( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,BndBaseMesh))( this, lbnd, ubnd, status );
+}
+AstPointSet *astBndMesh_( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Region,BndMesh))( this, lbnd, ubnd, status );
+}
+
+#define MAKE_MASK_(X,Xtype) \
+int astMask##X##_( AstRegion *this, AstMapping *map, int inside, int ndim, \
+                   const int lbnd[], const int ubnd[], Xtype in[], \
+                   Xtype val, int *status ) { \
+   if ( !astOK ) return 0; \
+   return (**astMEMBER(this,Region,Mask##X))( this, map, inside, ndim, lbnd, \
+                                              ubnd, in, val, status ); \
+}
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+MAKE_MASK_(LD,long double)
+#endif
+MAKE_MASK_(D,double)
+MAKE_MASK_(F,float)
+MAKE_MASK_(L,long int)
+MAKE_MASK_(UL,unsigned long int)
+MAKE_MASK_(I,int)
+MAKE_MASK_(UI,unsigned int)
+MAKE_MASK_(S,short int)
+MAKE_MASK_(US,unsigned short int)
+MAKE_MASK_(B,signed char)
+MAKE_MASK_(UB,unsigned char)
+#undef MAKE_MASK_
+
+/* Special public interface functions. */
+/* =================================== */
+/* These provide the public interface to certain special functions
+   whose public interface cannot be handled using macros (such as
+   astINVOKE) alone. In general, they are named after the
+   corresponding protected version of the function, but with "Id"
+   appended to the name. */
+
+/* Public Interface Function Prototypes. */
+/* ------------------------------------- */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+
+/* Special interface function implementations. */
+/* ------------------------------------------- */
+
+
+AstRegion *astMapRegionId_( AstRegion *this, AstMapping *map, AstFrame *frame, int *status ) {
+/*
+*++
+*  Name:
+c     astMapRegion
+f     AST_MAPREGION
+
+*  Purpose:
+*     Transform a Region into a new Frame using a given Mapping.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "region.h"
+c     AstRegion *astMapRegion( AstRegion *this, AstMapping *map,
+c                              AstFrame *frame )
+f     RESULT = AST_MAPREGION( THIS, MAP, FRAME, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     This function returns a pointer to a new Region which corresponds to
+*     supplied Region described by some other specified coordinate system. A
+*     Mapping is supplied which transforms positions between the old and new
+*     coordinate systems. The new Region may not be of the same class as
+*     the original region.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Region.
+c     map
+f     MAP = INTEGER (Given)
+*        Pointer to a Mapping which transforms positions from the
+*        coordinate system represented by the supplied Region to the
+*        coordinate system specified by
+c        "frame".
+f        FRAME.
+*        The supplied Mapping should define both forward and inverse
+*        transformations, and these transformations should form a genuine
+*        inverse pair. That is, transforming a position using the forward
+*        transformation and then using the inverse transformation should
+*        produce the original input position. Some Mapping classes (such
+*        as PermMap, MathMap, SphMap) can result in Mappings for which this
+*        is not true.
+c     frame
+f     FRAME = INTEGER (Given)
+*        Pointer to a Frame describing the coordinate system in which
+*        the new Region is required.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astMapRegion()
+f     AST_MAPREGION = INTEGER
+*        A pointer to a new Region. This Region will represent the area
+*        within the coordinate system specified by
+c        "frame"
+f        FRAME
+*        which corresponds to the supplied Region.
+
+*  Notes:
+*     - The uncertainty associated with the supplied Region is modified
+*     using the supplied Mapping.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - The only difference between this public interface and the protected
+*     astMapRegion interface is that this implementation additionally
+*     simplifies the returned Region. The protected implementation does
+*     not do this since doing so can lead to infinite recursion because
+*     it is sometimes necessary for Simplify to call astMapRegion.
+
+*/
+
+/* Local Variables: */
+   AstRegion *new;                /* Pointer to new Region */
+   AstRegion *result;             /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the protected astMapRegion function. */
+   new = astMapRegion( this, map, frame );
+
+/* Simplify the resulting Region. */
+   result = astSimplify( new );
+
+/* Free resources. */
+   new = astAnnul( new );
+
+/* If not OK, annul the returned pointer. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ast/region.h b/ast/region.h
new file mode 100644
index 0000000..b6ffd4e
--- /dev/null
+++ b/ast/region.h
@@ -0,0 +1,515 @@
+#if !defined( REGION_INCLUDED ) /* Include this file only once */
+#define REGION_INCLUDED
+/*
+*+
+*  Name:
+*     region.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Region class.
+
+*  Invocation:
+*     #include "region.h"
+
+*  Description:
+*     This include file defines the interface to the Region class and
+*     provides the type definitions, function prototypes and macros, etc.
+*     needed to use this class.
+
+*  Inheritance:
+*     The Region class inherits from the Frame class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     5-DEC-2003 (DSB):
+*        Original version.
+*     2-MAR-2006 (DSB):
+*        Changed AST_LONG_DOUBLE to HAVE_LONG_DOUBLE.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "frame.h"               /* Parent Frame class */
+
+/* Macros. */
+/* ======= */
+
+/* Type Definitions. */
+/* ================= */
+/* Region structure. */
+/* ------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+typedef struct AstRegion {
+
+/* Attributes inherited from the parent class. */
+   AstFrame parent;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstFrameSet *frameset;     /* FrameSet holding original and current Frames */
+   AstPointSet *points;       /* Points defining region location and extent */
+   struct AstRegion *unc;     /* Region specifying position uncertainties */
+   double fillfactor;         /* Fill factor (0.0->1.0) */
+   int regionfs;              /* Include FrameSet in dump? */
+   int negated;               /* Has the Region been negated? */
+   int closed;                /* Is the boundary part of the Region? */
+   int meshsize;              /* No. of points on boundary mesh */
+   struct AstRegion *defunc;  /* Default uncertainty Region */
+   AstPointSet *basemesh;     /* Base frame mesh covering the boundary */
+   AstPointSet *basegrid;     /* Base frame grid covering the boundary */
+   int adaptive;              /* Does the Region adapt to coord sys changes? */
+   int nomap;                 /* Ignore the Region's FrameSet? */
+   struct AstRegion *negation;/* Negated copy of "this" */
+} AstRegion;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all objects in the
+   class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstRegionVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstFrameVtab frame_vtab;      /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   int (* Overlap)( AstRegion *, AstRegion *, int * );
+   int (* OverlapX)( AstRegion *, AstRegion *, int * );
+   AstRegion *(* MapRegion)( AstRegion *, AstMapping *, AstFrame *, int * );
+   AstFrame *(* GetRegionFrame)( AstRegion *, int * );
+   AstFrameSet *(* GetRegionFrameSet)( AstRegion *, int * );
+   AstFrame *(* RegFrame)( AstRegion *, int * );
+   AstFrameSet *(* GetRegFS)( AstRegion *, int * );
+   AstPointSet *(* RegTransform)( AstRegion *, AstPointSet *, int, AstPointSet *, AstFrame **, int * );
+   AstPointSet *(* BTransform)( AstRegion *, AstPointSet *, int, AstPointSet *, int * );
+   void (* Negate)( AstRegion *, int * );
+   void (* RegBaseBox)( AstRegion *, double *, double *, int * );
+   void (* RegBaseBox2)( AstRegion *, double *, double *, int * );
+   void (* RegSetAttrib)( AstRegion *, const char *, char **, int * );
+   void (* RegClearAttrib)( AstRegion *, const char *, char **, int * );
+   void (* GetRegionBounds)( AstRegion *, double *, double *, int * );
+   void (* ShowMesh)( AstRegion *, int, const char *, int * );
+   void (* GetRegionBounds2)( AstRegion *, double *, double *, int * );
+   void (* ClearUnc)( AstRegion *, int * );
+   void (* RegOverlay)( AstRegion *, AstRegion *, int, int * );
+   void (* GetRegionMesh)( AstRegion *, int, int, int, int *, double *, int * );
+   void (* GetRegionPoints)( AstRegion *, int, int, int *, double *, int * );
+   int (* GetBounded)( AstRegion *, int * );
+   int (* TestUnc)( AstRegion *, int * );
+   int (* RegDummyFS)( AstRegion *, int * );
+   int (* RegPins)( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+   AstMapping *(* RegMapping)( AstRegion *, int * );
+   AstPointSet *(* RegMesh)( AstRegion *, int * );
+   AstPointSet *(* RegGrid)( AstRegion *, int * );
+   AstPointSet *(* RegBaseMesh)( AstRegion *, int * );
+   AstPointSet *(* RegBaseGrid)( AstRegion *, int * );
+   AstRegion **(* RegSplit)( AstRegion *, int *, int * );
+   AstPointSet *(* BndBaseMesh)( AstRegion *, double *, double *, int * );
+   AstPointSet *(* BndMesh)( AstRegion *, double *, double *, int * );
+   AstRegion *(* GetNegation)( AstRegion *, int * );
+   AstRegion *(* GetUncFrm)( AstRegion *, int, int * );
+   AstRegion *(* GetUnc)( AstRegion *, int, int * );
+   AstRegion *(* GetDefUnc)( AstRegion *, int * );
+   AstRegion *(* RegBasePick)( AstRegion *this, int, const int *, int * );
+   void (* ResetCache)( AstRegion *, int * );
+   int (* RegTrace)( AstRegion *, int, double *, double **, int * );
+   void (* SetUnc)( AstRegion *, AstRegion *, int * );
+   void (* SetRegFS)( AstRegion *, AstFrame *, int * );
+   double *(* RegCentre)( AstRegion *, double *, double **, int, int, int * );
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+   int (* MaskLD)( AstRegion *, AstMapping *, int, int, const int[], const int ubnd[], long double [], long double, int * );
+#endif
+   int (* MaskB)( AstRegion *, AstMapping *, int, int, const int[], const int[], signed char[], signed char, int * );
+   int (* MaskD)( AstRegion *, AstMapping *, int, int, const int[], const int[], double[], double, int * );
+   int (* MaskF)( AstRegion *, AstMapping *, int, int, const int[], const int[], float[], float, int * );
+   int (* MaskI)( AstRegion *, AstMapping *, int, int, const int[], const int[], int[], int, int * );
+   int (* MaskL)( AstRegion *, AstMapping *, int, int, const int[], const int[], long int[], long int, int * );
+   int (* MaskS)( AstRegion *, AstMapping *, int, int, const int[], const int[], short int[], short int, int * );
+   int (* MaskUB)( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned char[], unsigned char, int * );
+   int (* MaskUI)( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned int[], unsigned int, int * );
+   int (* MaskUL)( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned long int[], unsigned long int, int * );
+   int (* MaskUS)( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned short int[], unsigned short int, int * );
+
+   int (* GetNegated)( AstRegion *, int * );
+   int (* TestNegated)( AstRegion *, int * );
+   void (* ClearNegated)( AstRegion *, int * );
+   void (* SetNegated)( AstRegion *, int, int * );
+
+   int (* GetRegionFS)( AstRegion *, int * );
+   int (* TestRegionFS)( AstRegion *, int * );
+   void (* ClearRegionFS)( AstRegion *, int * );
+   void (* SetRegionFS)( AstRegion *, int, int * );
+
+   int (* GetClosed)( AstRegion *, int * );
+   int (* TestClosed)( AstRegion *, int * );
+   void (* ClearClosed)( AstRegion *, int * );
+   void (* SetClosed)( AstRegion *, int, int * );
+
+   int (* GetMeshSize)( AstRegion *, int * );
+   int (* TestMeshSize)( AstRegion *, int * );
+   void (* ClearMeshSize)( AstRegion *, int * );
+   void (* SetMeshSize)( AstRegion *, int, int * );
+
+   double (* GetFillFactor)( AstRegion *, int * );
+   int (* TestFillFactor)( AstRegion *, int * );
+   void (* ClearFillFactor)( AstRegion *, int * );
+   void (* SetFillFactor)( AstRegion *, double, int * );
+
+   int (* GetAdaptive)( AstRegion *, int * );
+   int (* TestAdaptive)( AstRegion *, int * );
+   void (* ClearAdaptive)( AstRegion *, int * );
+   void (* SetAdaptive)( AstRegion *, int, int * );
+
+} AstRegionVtab;
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstRegionGlobals {
+   AstRegionVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 101 ];
+} AstRegionGlobals;
+
+#endif
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Region)         /* Check class membership */
+astPROTO_ISA(Region)           /* Test class membership */
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstRegion *astInitRegion_( void *, size_t, int, AstRegionVtab *, const char *,
+                           AstFrame *, AstPointSet *, AstRegion *, int * );
+
+/* Vtab initialiser. */
+void astInitRegionVtab_( AstRegionVtab *, const char *, int * );
+
+/* Loader. */
+AstRegion *astLoadRegion_( void *, size_t, AstRegionVtab *,
+                           const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitRegionGlobals_( AstRegionGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+AstFrame *astGetRegionFrame_( AstRegion *, int * );
+AstFrameSet *astGetRegionFrameSet_( AstRegion *, int * );
+int astOverlap_( AstRegion *, AstRegion *, int * );
+void astNegate_( AstRegion *, int * );
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+int astMaskLD_( AstRegion *, AstMapping *, int, int, const int[], const int[], long double [], long double, int * );
+#endif
+int astMaskB_( AstRegion *, AstMapping *, int, int, const int[], const int[], signed char[], signed char, int * );
+int astMaskD_( AstRegion *, AstMapping *, int, int, const int[], const int[], double[], double, int * );
+int astMaskF_( AstRegion *, AstMapping *, int, int, const int[], const int[], float[], float, int * );
+int astMaskI_( AstRegion *, AstMapping *, int, int, const int[], const int[], int[], int, int * );
+int astMaskL_( AstRegion *, AstMapping *, int, int, const int[], const int[], long int[], long int, int * );
+int astMaskS_( AstRegion *, AstMapping *, int, int, const int[], const int[], short int[], short int, int * );
+int astMaskUB_( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned char[], unsigned char, int * );
+int astMaskUI_( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned int[], unsigned int, int * );
+int astMaskUL_( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned long int[], unsigned long int, int * );
+int astMaskUS_( AstRegion *, AstMapping *, int, int, const int[], const int[], unsigned short int[], unsigned short int, int * );
+void astSetUnc_( AstRegion *, AstRegion *, int * );
+AstRegion *astGetNegation_( AstRegion *, int * );
+AstRegion *astGetUnc_( AstRegion *, int, int * );
+void astGetRegionBounds_( AstRegion *, double *, double *, int * );
+void astShowMesh_( AstRegion *, int, const char *, int * );
+void astGetRegionMesh_( AstRegion *, int, int, int, int *, double *, int * );
+void astGetRegionPoints_( AstRegion *, int, int, int *, double *, int * );
+
+#if defined(astCLASS)            /* Protected */
+void astGetRegionBounds2_( AstRegion *, double *, double *, int * );
+AstRegion *astMapRegion_( AstRegion *, AstMapping *, AstFrame *, int * );
+AstFrame *astRegFrame_( AstRegion *, int * );
+AstPointSet *astRegTransform_( AstRegion *, AstPointSet *, int, AstPointSet *, AstFrame **, int * );
+AstPointSet *astBTransform_( AstRegion *, AstPointSet *, int, AstPointSet *, int * );
+void astRegBaseBox_( AstRegion *, double *, double *, int * );
+void astRegBaseBox2_( AstRegion *, double *, double *, int * );
+void astRegSetAttrib_( AstRegion *, const char *, char **, int * );
+void astRegClearAttrib_( AstRegion *, const char *, char **, int * );
+void astClearUnc_( AstRegion *, int * );
+void astRegOverlay_( AstRegion *, AstRegion *, int, int * );
+int astGetBounded_( AstRegion *, int * );
+int astTestUnc_( AstRegion *, int * );
+int astRegDummyFS_( AstRegion *, int * );
+int astRegPins_( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+AstMapping *astRegMapping_( AstRegion *, int * );
+AstPointSet *astRegMesh_( AstRegion *, int * );
+AstPointSet *astRegGrid_( AstRegion *, int * );
+AstPointSet *astRegBaseMesh_( AstRegion *, int * );
+AstPointSet *astRegBaseGrid_( AstRegion *, int * );
+AstPointSet *astBndBaseMesh_( AstRegion *, double *, double *, int * );
+AstRegion **astRegSplit_( AstRegion *, int *, int * );
+AstPointSet *astBndMesh_( AstRegion *, double *, double *, int * );
+AstRegion *astGetUncFrm_( AstRegion *, int, int * );
+AstRegion *astGetDefUnc_( AstRegion *, int * );
+AstRegion *astRegBasePick_( AstRegion *this, int, const int *, int * );
+int astOverlapX_( AstRegion *, AstRegion *, int * );
+AstFrameSet *astGetRegFS_( AstRegion *, int * );
+void astSetRegFS_( AstRegion *, AstFrame *, int * );
+double *astRegCentre_( AstRegion *, double *, double **, int, int, int * );
+double *astRegTranPoint_( AstRegion *, double *, int, int, int * );
+void astResetCache_( AstRegion *, int * );
+int astRegTrace_( AstRegion *, int, double *, double **, int * );
+
+int astGetNegated_( AstRegion *, int * );
+int astTestNegated_( AstRegion *, int * );
+void astClearNegated_( AstRegion *, int * );
+void astSetNegated_( AstRegion *, int, int * );
+
+int astGetRegionFS_( AstRegion *, int * );
+int astTestRegionFS_( AstRegion *, int * );
+void astClearRegionFS_( AstRegion *, int * );
+void astSetRegionFS_( AstRegion *, int, int * );
+
+int astGetMeshSize_( AstRegion *, int * );
+int astTestMeshSize_( AstRegion *, int * );
+void astClearMeshSize_( AstRegion *, int * );
+void astSetMeshSize_( AstRegion *, int, int * );
+
+int astGetClosed_( AstRegion *, int * );
+int astTestClosed_( AstRegion *, int * );
+void astClearClosed_( AstRegion *, int * );
+void astSetClosed_( AstRegion *, int, int * );
+
+double astGetFillFactor_( AstRegion *, int * );
+int astTestFillFactor_( AstRegion *, int * );
+void astClearFillFactor_( AstRegion *, int * );
+void astSetFillFactor_( AstRegion *, double, int * );
+
+int astGetAdaptive_( AstRegion *, int * );
+int astTestAdaptive_( AstRegion *, int * );
+void astClearAdaptive_( AstRegion *, int * );
+void astSetAdaptive_( AstRegion *, int, int * );
+
+#else   /* Public only */
+AstRegion *astMapRegionId_( AstRegion *, AstMapping *, AstFrame *, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class to make
+   them easier to invoke (e.g. to avoid type mis-matches when passing pointers
+   to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them to
+   validate their own arguments. We must use a cast when passing object
+   pointers (so that they can accept objects from derived classes). */
+
+/* Check class membership. */
+#define astCheckRegion(this) astINVOKE_CHECK(Region,this,0)
+#define astVerifyRegion(this) astINVOKE_CHECK(Region,this,1)
+
+/* Test class membership. */
+#define astIsARegion(this) astINVOKE_ISA(Region,this)
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitRegion(mem,size,init,vtab,name,frame,pset,acc)\
+astINVOKE(O,astInitRegion_(mem,size,init,vtab,name,astCheckFrame(frame),pset,acc,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitRegionVtab(vtab,name) astINVOKE(V,astInitRegionVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadRegion(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadRegion_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckRegion to validate Region pointers before
+   use. This provides a contextual error report if a pointer to the wrong sort
+   of object is supplied. */
+#define astGetRegionFrame(this) \
+astINVOKE(O,astGetRegionFrame_(astCheckRegion(this),STATUS_PTR))
+#define astGetRegionFrameSet(this) \
+astINVOKE(O,astGetRegionFrameSet_(astCheckRegion(this),STATUS_PTR))
+#define astNegate(this) \
+astINVOKE(V,astNegate_(astCheckRegion(this),STATUS_PTR))
+#define astOverlap(this,that) \
+astINVOKE(V,astOverlap_(astCheckRegion(this),astCheckRegion(that),STATUS_PTR))
+
+#if HAVE_LONG_DOUBLE     /* Not normally implemented */
+#define astMaskLD(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskLD_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#endif
+
+#define astMaskB(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskB_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#define astMaskD(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskD_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#define astMaskF(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskF_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#define astMaskI(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskI_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#define astMaskL(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskL_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#define astMaskS(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskS_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#define astMaskUB(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskUB_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#define astMaskUI(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskUI_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#define astMaskUL(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskUL_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#define astMaskUS(this,map,inside,ndim,lbnd,ubnd,in,val) \
+astINVOKE(V,astMaskUS_(astCheckRegion(this),(map?astCheckMapping(map):NULL),inside,ndim,lbnd,ubnd,in,val,STATUS_PTR))
+#define astSetUnc(this,unc) astINVOKE(V,astSetUnc_(astCheckRegion(this),unc?astCheckRegion(unc):NULL,STATUS_PTR))
+#define astGetUnc(this,def) astINVOKE(O,astGetUnc_(astCheckRegion(this),def,STATUS_PTR))
+#define astGetRegionBounds(this,lbnd,ubnd) astINVOKE(V,astGetRegionBounds_(astCheckRegion(this),lbnd,ubnd,STATUS_PTR))
+#define astShowMesh(this,format,ttl) astINVOKE(V,astShowMesh_(astCheckRegion(this),format,ttl,STATUS_PTR))
+#define astGetRegionMesh(this,surface,maxpoint,maxcoord,npoint,points) \
+astINVOKE(V,astGetRegionMesh_(astCheckRegion(this),surface,maxpoint,maxcoord,npoint,points,STATUS_PTR))
+#define astGetRegionPoints(this,maxpoint,maxcoord,npoint,points) \
+astINVOKE(V,astGetRegionPoints_(astCheckRegion(this),maxpoint,maxcoord,npoint,points,STATUS_PTR))
+
+/* Interfaces to protected member functions. */
+/* ----------------------------------------- */
+#if defined(astCLASS)            /* Protected */
+
+#define astGetNegation(this) astINVOKE(O,astGetNegation_(astCheckRegion(this),STATUS_PTR))
+#define astGetRegionBounds2(this,lbnd,ubnd) astINVOKE(V,astGetRegionBounds2_(astCheckRegion(this),lbnd,ubnd,STATUS_PTR))
+#define astClearUnc(this) astINVOKE(V,astClearUnc_(astCheckRegion(this),STATUS_PTR))
+#define astGetBounded(this) astINVOKE(V,astGetBounded_(astCheckRegion(this),STATUS_PTR))
+#define astGetUncFrm(this,ifrm) astINVOKE(O,astGetUncFrm_(astCheckRegion(this),ifrm,STATUS_PTR))
+#define astGetDefUnc(this) astINVOKE(O,astGetDefUnc_(astCheckRegion(this),STATUS_PTR))
+#define astMapRegion(this,map,frame) astINVOKE(O,astMapRegion_(astCheckRegion(this),astCheckMapping(map),astCheckFrame(frame),STATUS_PTR))
+#define astOverlapX(that,this) astINVOKE(V,astOverlapX_(astCheckRegion(that),astCheckRegion(this),STATUS_PTR))
+#define astRegBaseBox(this,lbnd,ubnd) astINVOKE(V,astRegBaseBox_(astCheckRegion(this),lbnd,ubnd,STATUS_PTR))
+#define astRegBaseBox2(this,lbnd,ubnd) astINVOKE(V,astRegBaseBox2_(astCheckRegion(this),lbnd,ubnd,STATUS_PTR))
+#define astRegSetAttrib(this,setting,bset) astINVOKE(V,astRegSetAttrib_(astCheckRegion(this),setting,bset,STATUS_PTR))
+#define astRegClearAttrib(this,setting,batt) astINVOKE(V,astRegClearAttrib_(astCheckRegion(this),setting,batt,STATUS_PTR))
+#define astRegBaseMesh(this) astINVOKE(O,astRegBaseMesh_(astCheckRegion(this),STATUS_PTR))
+#define astRegBasePick(this,naxes,axes) astINVOKE(O,astRegBasePick_(astCheckRegion(this),naxes,axes,STATUS_PTR))
+#define astRegBaseGrid(this) astINVOKE(O,astRegBaseGrid_(astCheckRegion(this),STATUS_PTR))
+#define astRegSplit(this,nlist) astINVOKE(V,astRegSplit_(astCheckRegion(this),nlist,STATUS_PTR))
+#define astBndBaseMesh(this,lbnd,ubnd) astINVOKE(O,astBndBaseMesh_(astCheckRegion(this),lbnd,ubnd,STATUS_PTR))
+#define astBndMesh(this,lbnd,ubnd) astINVOKE(O,astBndMesh_(astCheckRegion(this),lbnd,ubnd,STATUS_PTR))
+#define astRegCentre(this,cen,ptr,index,ifrm) astINVOKE(V,astRegCentre_(astCheckRegion(this),cen,ptr,index,ifrm,STATUS_PTR))
+#define astRegFrame(this) astINVOKE(O,astRegFrame_(astCheckRegion(this),STATUS_PTR))
+#define astRegGrid(this) astINVOKE(O,astRegGrid_(astCheckRegion(this),STATUS_PTR))
+#define astRegMesh(this) astINVOKE(O,astRegMesh_(astCheckRegion(this),STATUS_PTR))
+#define astRegOverlay(this,that,unc) astINVOKE(V,astRegOverlay_(astCheckRegion(this),astCheckRegion(that),unc,STATUS_PTR))
+#define astRegDummyFS(this) astINVOKE(V,astRegDummyFS_(astCheckRegion(this),STATUS_PTR))
+#define astRegMapping(this) astINVOKE(O,astRegMapping_(astCheckRegion(this),STATUS_PTR))
+#define astRegPins(this,pset,unc,mask) astINVOKE(V,astRegPins_(astCheckRegion(this),astCheckPointSet(pset),unc?astCheckRegion(unc):unc,mask,STATUS_PTR))
+#define astRegTranPoint(this,in,np,forward) astRegTranPoint_(this,in,np,forward,STATUS_PTR)
+#define astGetRegFS(this) astINVOKE(O,astGetRegFS_(astCheckRegion(this),STATUS_PTR))
+#define astSetRegFS(this,frm) astINVOKE(V,astSetRegFS_(astCheckRegion(this),astCheckFrame(frm),STATUS_PTR))
+#define astTestUnc(this) astINVOKE(V,astTestUnc_(astCheckRegion(this),STATUS_PTR))
+#define astResetCache(this) astINVOKE(V,astResetCache_(astCheckRegion(this),STATUS_PTR))
+#define astRegTrace(this,n,dist,ptr) astINVOKE(V,astRegTrace_(astCheckRegion(this),n,dist,ptr,STATUS_PTR))
+
+/* Since a NULL PointSet pointer is acceptable for "out", we must omit the
+   argument checking in that case. (But unfortunately, "out" then gets
+   evaluated twice - this is unlikely to matter, but is there a better way?) */
+
+#define astRegTransform(this,in,forward,out,frm) \
+astINVOKE(O,astRegTransform_(astCheckRegion(this),in?astCheckPointSet(in):NULL,forward,(out)?astCheckPointSet(out):NULL,frm,STATUS_PTR))
+
+#define astBTransform(this,in,forward,out) \
+astINVOKE(O,astBTransform_(astCheckRegion(this),in?astCheckPointSet(in):NULL,forward,(out)?astCheckPointSet(out):NULL,STATUS_PTR))
+
+#define astClearNegated(this) astINVOKE(V,astClearNegated_(astCheckRegion(this),STATUS_PTR))
+#define astGetNegated(this) astINVOKE(V,astGetNegated_(astCheckRegion(this),STATUS_PTR))
+#define astSetNegated(this,negated) astINVOKE(V,astSetNegated_(astCheckRegion(this),negated,STATUS_PTR))
+#define astTestNegated(this) astINVOKE(V,astTestNegated_(astCheckRegion(this),STATUS_PTR))
+
+#define astClearAdaptive(this) astINVOKE(V,astClearAdaptive_(astCheckRegion(this),STATUS_PTR))
+#define astGetAdaptive(this) astINVOKE(V,astGetAdaptive_(astCheckRegion(this),STATUS_PTR))
+#define astSetAdaptive(this,adaptive) astINVOKE(V,astSetAdaptive_(astCheckRegion(this),adaptive,STATUS_PTR))
+#define astTestAdaptive(this) astINVOKE(V,astTestAdaptive_(astCheckRegion(this),STATUS_PTR))
+
+#define astClearRegionFS(this) astINVOKE(V,astClearRegionFS_(astCheckRegion(this),STATUS_PTR))
+#define astGetRegionFS(this) astINVOKE(V,astGetRegionFS_(astCheckRegion(this),STATUS_PTR))
+#define astSetRegionFS(this,fs) astINVOKE(V,astSetRegionFS_(astCheckRegion(this),fs,STATUS_PTR))
+#define astTestRegionFS(this) astINVOKE(V,astTestRegionFS_(astCheckRegion(this),STATUS_PTR))
+
+#define astClearMeshSize(this) astINVOKE(V,astClearMeshSize_(astCheckRegion(this),STATUS_PTR))
+#define astGetMeshSize(this) astINVOKE(V,astGetMeshSize_(astCheckRegion(this),STATUS_PTR))
+#define astSetMeshSize(this,meshsize) astINVOKE(V,astSetMeshSize_(astCheckRegion(this),meshsize,STATUS_PTR))
+#define astTestMeshSize(this) astINVOKE(V,astTestMeshSize_(astCheckRegion(this),STATUS_PTR))
+
+#define astClearClosed(this) astINVOKE(V,astClearClosed_(astCheckRegion(this),STATUS_PTR))
+#define astGetClosed(this) astINVOKE(V,astGetClosed_(astCheckRegion(this),STATUS_PTR))
+#define astSetClosed(this,closed) astINVOKE(V,astSetClosed_(astCheckRegion(this),closed,STATUS_PTR))
+#define astTestClosed(this) astINVOKE(V,astTestClosed_(astCheckRegion(this),STATUS_PTR))
+
+#define astClearFillFactor(this) astINVOKE(V,astClearFillFactor_(astCheckRegion(this),STATUS_PTR))
+#define astGetFillFactor(this) astINVOKE(V,astGetFillFactor_(astCheckRegion(this),STATUS_PTR))
+#define astSetFillFactor(this,ff) astINVOKE(V,astSetFillFactor_(astCheckRegion(this),ff,STATUS_PTR))
+#define astTestFillFactor(this) astINVOKE(V,astTestFillFactor_(astCheckRegion(this),STATUS_PTR))
+
+#else  /* Public only */
+#define astMapRegion(this,map,frame) astINVOKE(O,astMapRegionId_(astCheckRegion(this),astCheckMapping(map),astCheckFrame(frame),STATUS_PTR))
+#endif
+
+#endif
+
+
+
+
+
diff --git a/ast/selectormap.c b/ast/selectormap.c
new file mode 100644
index 0000000..87a6f45
--- /dev/null
+++ b/ast/selectormap.c
@@ -0,0 +1,1838 @@
+/*
+*class++
+*  Name:
+*     SelectorMap
+
+*  Purpose:
+*     A Mapping that locates positions within one of a set of alternate
+*     Regions.
+
+*  Constructor Function:
+c     astSelectorMap
+f     AST_SELECTORMAP
+
+*  Description:
+*     A SelectorMap is a Mapping that identifies which Region contains
+*     a given input position.
+*
+*     A SelectorMap encapsulates a number of Regions that all have the same
+*     number of axes and represent the same coordinate Frame. The number of
+*     inputs (Nin attribute) of the SelectorMap equals the number of axes
+*     spanned by one of the encapsulated Region. All SelectorMaps have only
+*     a single output. SelectorMaps do not define an inverse transformation.
+*
+*     For each input position, the forward transformation of a SelectorMap
+*     searches through the encapsulated Regions (in the order supplied when
+*     the SelectorMap was created) until a Region is found which contains
+*     the input position. The index associated with this Region is
+*     returned as the SelectorMap output value (the index value is the
+*     position of the Region within the list of Regions supplied when the
+*     SelectorMap was created, starting at 1 for the first Region). If an
+*     input position is not contained within any Region, a value of zero is
+*     returned by the forward transformation.
+*
+*     If a compound Mapping contains a SelectorMap in series with its own
+*     inverse, the combination of the two adjacent SelectorMaps will be
+*     replaced by a UnitMap when the compound Mapping is simplified using
+c     astSimplify.
+f     AST_SIMPLIFY.
+*
+*     In practice, SelectorMaps are often used in conjunction with SwitchMaps.
+
+*  Inheritance:
+*     The SelectorMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The SelectorMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     The SelectorMap class does not define any new functions beyond those
+f     The SelectorMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     15-MAR-2006 (DSB):
+*        Original version.
+*     18-MAY-2006 (DSB):
+*        - Change logic for detecting interior points in function Transform.
+*        - Added BADVAL to contructor argument list.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS SelectorMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+#include "unitmap.h"             /* Unit Mappings */
+#include "channel.h"             /* I/O channels */
+#include "selectormap.h"         /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(SelectorMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(SelectorMap,Class_Init)
+#define class_vtab astGLOBAL(SelectorMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstSelectorMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstSelectorMap *astSelectorMapId_( int, void **, double, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetObjSize( AstObject *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two SelectorMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "selectormap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     SelectorMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two SelectorMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a SelectorMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the SelectorMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSelectorMap *that;
+   AstSelectorMap *this;
+   int i;
+   int nin;
+   int nreg;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two SelectorMap structures. */
+   this = (AstSelectorMap *) this_object;
+   that = (AstSelectorMap *) that_object;
+
+/* Check the second object is a SelectorMap. We know the first is a
+   SelectorMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsASelectorMap( that ) ) {
+
+/* Check they have the same number of inputs. */
+      nin = astGetNin( this );
+      if( astGetNin( that ) == nin ) {
+
+/* Check they contain the same number of Regions, and have the same badval. */
+         nreg = this->nreg;
+         if( that->nreg == nreg ||
+             astEQUAL( that->badval, this->badval) ) {
+
+/* Loop over the Regions, breaking as soon as two unequal Regions are
+   found. */
+            result = 1;
+            for( i = 0; i < nreg; i++ ) {
+               if( !astEqual( this->reg[ i ], that->reg[ i ] ) ) {
+                  result = 0;
+                  break;
+               }
+            }
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "selectormap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     SelectorMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied SelectorMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the SelectorMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSelectorMap *this;
+   int i;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the SelectorMap structure. */
+   this = (AstSelectorMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by this class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   for( i = 0; i < this->nreg; i++ ) {
+      result += astGetObjSize( this->reg[ i ] );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+void astInitSelectorMapVtab_(  AstSelectorMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSelectorMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a SelectorMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "selectormap.h"
+*     void astInitSelectorMapVtab( AstSelectorMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SelectorMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the SelectorMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsASelectorMap) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+/* None. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "SelectorMap", "Region identification Mapping" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     SelectorMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstSelectorMap *this;       /* Pointer to SelectorMap structure */
+   int i;                      /* Loop count */
+   int result;                 /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the SelectorMap structure. */
+   this = (AstSelectorMap *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   for( i = 0; i < this->nreg; i++ ) {
+      if( !result ) result = astManageLock( this->reg[ i ], mode, extra, fail );
+   }
+
+   return result;
+
+}
+#endif
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a SelectorMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     SelectorMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated SelectorMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated SelectorMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated SelectorMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the SelectorMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        SelectorMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated SelectorMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *new;
+   AstRegion **sreg;
+   AstSelectorMap *map;
+   AstSelectorMap *slneb;
+   int equal;
+   int i;
+   int ilo;
+   int nreg;
+   int result;
+   int simp;
+
+/* Initialise.*/
+   result = -1;
+
+/* Check the inherited status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to this SelectorMap, and note the number of Regions. */
+   map = (AstSelectorMap *) this;
+   nreg = map->nreg;
+
+/* Attempt to simplify the SelectorMap on its own. */
+/* ============================================= */
+
+/* Try to simplify each of the encapsulated Regions, noting if any
+   simplification takes place. */
+   simp = 0;
+   sreg = astMalloc( sizeof( AstRegion * )*nreg );
+   if( astOK ) {
+      for( i = 0; i < nreg; i++ ) {
+         sreg[ i ] = astSimplify( map->reg[ i ] );
+         simp = simp || ( sreg[ i ] != map->reg[ i ] );
+      }
+
+/* If any simplification took place, construct a new SelectorMap from these
+   simplified Mappings. */
+      if( simp ) {
+         (void) astAnnul( ( *map_list )[ where ] );
+         ( *map_list )[ where ] = (AstMapping *) astSelectorMap( nreg,
+                                                          (void **) sreg,
+                                                          map->badval, "", status );
+         result = where;
+      }
+
+/* Release resources. */
+      if( sreg ) {
+         for( i = 0; i < nreg; i++ ) sreg[ i ] = astAnnul( sreg[ i ] );
+         sreg = astFree( sreg );
+      }
+   }
+
+/* If possible, merge the SelectorMap with a neighbouring SelectorMap. */
+/* =============================================================== */
+/* Only do this if no change was made above, and we are combining the
+   Mappings in series. */
+   if( result == -1 && series ) {
+
+/* Is the higher neighbour a SelectorMap? If so get a pointer to it, and
+   note the index of the lower of the two adjacent SelectorMaps. */
+      if( where < ( *nmap - 1 ) &&
+          astIsASelectorMap( ( *map_list )[ where + 1 ] ) ){
+         slneb = (AstSelectorMap *) ( *map_list )[ where + 1 ];
+         ilo = where;
+
+/* If not, is the lower neighbour a SelectorMap? If so get a pointer to it, and
+   note the index of the lower of the two adjacent SelectorMaps. */
+      } else if( where > 0 &&
+                 astIsASelectorMap( ( *map_list )[ where - 1 ] ) ){
+         slneb = (AstSelectorMap *) ( *map_list )[ where - 1 ];
+         ilo =  where - 1;
+
+      } else {
+         slneb = NULL;
+      }
+
+/* If a neighbouring SelectorMap was found, we can replace the pair by a
+   UnitMap if the two SelectorMaps are equal but have opposite values for
+   their Invert flags. Temporarily invert the neighbour, then compare
+   the two SelectorMaps for equality, then re-invert the neighbour. */
+      if( slneb ) {
+         astInvert( slneb );
+         equal = astEqual( map, slneb );
+         astInvert( slneb );
+
+/* If the two SelectorMaps are equal but opposite, annul the first of the two
+   Mappings, and replace it with a UnitMap. Also set the invert flag. */
+         if( equal ) {
+            new = (AstMapping *) astUnitMap( astGetNin( ( *map_list )[ ilo ] ), "", status );
+            (void) astAnnul( ( *map_list )[ ilo ] );
+            ( *map_list )[ ilo ] = new;
+            ( *invert_list )[ ilo ] = 0;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+            (void) astAnnul( ( *map_list )[ ilo + 1 ] );
+            for ( i = ilo + 2; i < *nmap; i++ ) {
+               ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+               ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+            }
+
+/* Clear the vacated element at the end. */
+            ( *map_list )[ *nmap - 1 ] = NULL;
+            ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+            ( *nmap )--;
+            result = where;
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a SelectorMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "selectormap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     SelectorMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a SelectorMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required Mapping.
+*     This implies applying each of the SelectorMap's component Mappings in turn,
+*     either in series or in parallel.
+
+*  Parameters:
+*     this
+*        Pointer to the SelectorMap.
+*     in
+*        Pointer to the PointSet associated with the input coordinate values.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the SelectorMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *ps1;
+   AstPointSet *ps2;
+   AstPointSet *result;
+   AstPointSet *tps;
+   AstRegion *reg;
+   AstSelectorMap *map;
+   double **ptr_out;
+   double **ptr1;
+   double **ptr2;
+   double **tptr;
+   double *p2;
+   double *pout;
+   double badval;
+   int bad;
+   int closed;
+   int icoord;
+   int ipoint;
+   int ireg;
+   int ncoord;
+   int npoint;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the SelectorMap. */
+   map = (AstSelectorMap *) this;
+
+/* Apply the parent Mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We now extend the parent astTransform method by applying the component
+   Mappings of the SelectorMap to generate the output coordinate values. */
+
+/* Check we are implementing the original forward transformation (the
+   inverse transformation is not defined). */
+   if( forward != astGetInvert( this ) ) {
+
+/* Get the number of input axes and the number of points. */
+      ncoord = astGetNcoord( in );
+      npoint = astGetNpoint( in );
+
+/* Create two temporary PointSets to hold copies of the input points. */
+      ps1 = astCopy( in );
+      ptr1 = astGetPoints( ps1 );
+      ps2 = astPointSet( npoint, ncoord, "", status );
+      ptr2 = astGetPoints( ps2 );
+
+/* Get a pointer to the output data */
+      ptr_out = astGetPoints( result );
+      if( astOK ) {
+
+/* Initialise the output array to hold -1 at any points that have
+   bad input axis values, and zero at all other points. */
+         pout = ptr_out[ 0 ];
+         for( ipoint = 0; ipoint < npoint; ipoint++ ) {
+            bad = 0;
+            for( icoord = 0; icoord < ncoord; icoord++ ) {
+               if( ptr1[ icoord ][ ipoint ] == AST__BAD ) {
+                  bad = 1;
+                  break;
+               }
+            }
+            *(pout++) = bad ? -1 : 0;
+         }
+
+/* Loop round all Regions. */
+         for( ireg = 1; ireg <= map->nreg; ireg++ ) {
+            reg = map->reg[ ireg - 1 ];
+
+/* Temporarily Negate the Region. */
+            astNegate( reg );
+            closed = astGetClosed( reg );
+            astSetClosed( reg, !closed );
+
+/* Transform the remaining input positions. Good input positions which
+   are within the Region will be bad in the output. */
+            ps2 = astTransform( reg, ps1, 1, ps2 );
+
+/* Loop round all positions. */
+            p2 = ptr2[ 0 ];
+            pout = ptr_out[ 0 ];
+            for( ipoint = 0; ipoint < npoint; ipoint++, p2++, pout++ ) {
+
+/* Any position that has not already been assigned to a Region and is bad
+   in the output PointSet must be contained within the current Region, so
+   assign the (one-based) index of the current Region to the output element. */
+               if( *pout == 0 && *p2 == AST__BAD ) *pout = ireg;
+            }
+
+/* Negate the Region to get it back to its original state. */
+            astSetClosed( reg, closed );
+            astNegate( reg );
+
+/* Swap the input and output PointSets. */
+            tps = ps1;
+            ps1 = ps2;
+            ps2 = tps;
+            tptr = ptr1;
+            ptr1 = ptr2;
+            ptr2 = tptr;
+         }
+
+/* Replace -1 values in the output (that indicate that the input position
+   had at least one bad axis value) with the "badval".*/
+         badval = map->badval;
+         pout = ptr_out[ 0 ];
+         for( ipoint = 0; ipoint < npoint; ipoint++, pout++ ) {
+            if( *pout == -1 ) *pout = badval;
+         }
+      }
+
+/* Free resources. */
+      ps1 = astAnnul( ps1 );
+      ps2 = astAnnul( ps2 );
+   }
+
+/* If an error occurred, clean up by deleting the output PointSet (if
+   allocated by this function) and setting a NULL result pointer. */
+   if ( !astOK ) {
+      if ( !out ) result = astDelete( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for SelectorMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for SelectorMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the
+*     Regions within the SelectorMap.
+*/
+
+/* Local Variables: */
+   AstSelectorMap *in;                /* Pointer to input SelectorMap */
+   AstSelectorMap *out;               /* Pointer to output SelectorMap */
+   int i;                             /* Loop count */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output SelectorMaps. */
+   in = (AstSelectorMap *) objin;
+   out = (AstSelectorMap *) objout;
+
+/* For safety, start by clearing any references to the input Regions. */
+   out->reg = NULL;
+   out->nreg = 0;
+
+/* Make copies of the Regions, and store pointers to them in the output
+   SelectorMap structure. */
+   out->reg = astMalloc( sizeof( AstRegion * )*( in->nreg ) );
+   if( astOK ) {
+      for( i = 0; i < in->nreg; i++ ) {
+         out->reg[ i ] = astCopy( in->reg[ i ] );
+      }
+      out->nreg = in->nreg;
+   }
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for SelectorMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for SelectorMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstSelectorMap *this;              /* Pointer to SelectorMap */
+   int i;
+
+/* Obtain a pointer to the SelectorMap structure. */
+   this = (AstSelectorMap *) obj;
+
+/* Free dynamically allocated resources. */
+   for( i = 0; i < this->nreg; i++ ) {
+      this->reg[ i ] = astAnnul( this->reg[ i ] );
+   }
+   this->reg = astFree( this->reg );
+
+/* Clear the remaining SelectorMap variables. */
+   this->nreg = 0;
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for SelectorMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the SelectorMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the SelectorMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSelectorMap *this;
+   int i;
+   char buf[ 20 ];
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SelectorMap structure. */
+   this = (AstSelectorMap *) this_object;
+
+/* Write out values representing the instance variables for the SelectorMap
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Loop to dump each Region. */
+/* ------------------------- */
+/* The coordinate Frame of the Regions is defined by the first Region.
+   The Frame information is omitted from the second and subsequent
+   Regions by setting the protected RegionFS attribute to zero. */
+   for( i = 0; i < this->nreg; i++ ) {
+      sprintf( buf, "Reg%d", i + 1 );
+      if( i > 0 ) astSetRegionFS( this->reg[ i ], 0 );
+      astWriteObject( channel, buf, 1, 1, this->reg[ i ],
+                      "Region of input space" );
+      if( i > 0 ) astClearRegionFS( this->reg[ i ] );
+   }
+
+/* BadVal. */
+/* ------- */
+   if( this->badval != AST__BAD ) {
+      astWriteDouble( channel, "BadVal", 1, 1, this->badval,
+                      "Output value for bad input positions" );
+   }
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsASelectorMap and astCheckSelectorMap functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(SelectorMap,Mapping)
+astMAKE_CHECK(SelectorMap)
+
+AstSelectorMap *astSelectorMap_( int nreg, void **regs_void, double badval,
+                                 const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astSelectorMap
+
+*  Purpose:
+*     Create a SelectorMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "selectormap.h"
+*     AstSelectorMap *astSelectorMap( int nreg, AstRegion **regs,
+*                                     double badval, const char *options, ... )
+
+*  Class Membership:
+*     SelectorMap constructor.
+
+*  Description:
+*     This function creates a new SelectorMap and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     nreg
+*        The number of Regions supplied.
+*     regs
+*        An array of pointers to the Regions. Deep copies of these
+*        Regions are taken.
+*     badval
+*        The value to be returned by the forward transformation of the
+*        SelectorMap for any input positions that have a bad (AST__BAD)
+*        value on any axis.
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new SelectorMap. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new SelectorMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic SelectorMap constructor which is
+*     available via the protected interface to the SelectorMap class.  A
+*     public interface is provided by the astSelectorMapId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "map1" and "map2" parameters are of type (void *) and are
+*     converted and validated within the function itself.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSelectorMap *new;          /* Pointer to new SelectorMap */
+   AstRegion **regs;             /* Array of Region pointers */
+   int i;                        /* Region index */
+   va_list args;                 /* Variable argument list */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Report an error if no Regions have been supplied. */
+   if( nreg <= 0 ) astError( AST__BDPAR, "astSelectorMap(SelectorMap): "
+                            "Bad number of Regions (%d) specified.", status, nreg );
+
+/* Otherwise create an array to hold the Region pointers. */
+   regs = astMalloc( sizeof( AstRegion * )*nreg );
+
+/* Obtain and validate pointers to the Region structures provided. */
+   if( astOK ) {
+      for( i = 0; i < nreg; i++ ) {
+         regs[ i ] = astCheckRegion( regs_void[ i ] );
+      }
+   }
+
+   if ( astOK ) {
+
+/* Initialise the SelectorMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitSelectorMap( NULL, sizeof( AstSelectorMap ), !class_init, &class_vtab,
+                              "SelectorMap", nreg, regs, badval );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new SelectorMap's
+   attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Free memory used to hold the Regions pointers. */
+   regs = astFree( regs );
+
+/* Return a pointer to the new SelectorMap. */
+   return new;
+}
+
+AstSelectorMap *astSelectorMapId_( int nreg, void **regs_void, double badval,
+                                   const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astSelectorMap
+f     AST_SELECTORMAP
+
+*  Purpose:
+*     Create a SelectorMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "selectormap.h"
+c     AstSelectorMap *astSelectorMap( int nreg, AstRegion *regs[],
+c                                     double badval, const char *options, ... )
+f     RESULT = AST_SELECTORMAP( NREG, REGS, BADVAL, OPTIONS, STATUS )
+
+*  Class Membership:
+*     SelectorMap constructor.
+
+*  Description:
+*     This function creates a new SelectorMap and optionally initialises
+*     its attributes.
+*
+*     A SelectorMap is a Mapping that identifies which Region contains
+*     a given input position.
+*
+*     A SelectorMap encapsulates a number of Regions that all have the same
+*     number of axes and represent the same coordinate Frame. The number of
+*     inputs (Nin attribute) of the SelectorMap equals the number of axes
+*     spanned by one of the encapsulated Region. All SelectorMaps have only
+*     a single output. SelectorMaps do not define an inverse transformation.
+*
+*     For each input position, the forward transformation of a SelectorMap
+*     searches through the encapsulated Regions (in the order supplied when
+*     the SelectorMap was created) until a Region is found which contains
+*     the input position. The index associated with this Region is
+*     returned as the SelectorMap output value (the index value is the
+*     position of the Region within the list of Regions supplied when the
+*     SelectorMap was created, starting at 1 for the first Region). If an
+*     input position is not contained within any Region, a value of zero is
+*     returned by the forward transformation.
+*
+*     If a compound Mapping contains a SelectorMap in series with its own
+*     inverse, the combination of the two adjacent SelectorMaps will be
+*     replaced by a UnitMap when the compound Mapping is simplified using
+c     astSimplify.
+f     AST_SIMPLIFY.
+*
+*     In practice, SelectorMaps are often used in conjunction with SwitchMaps.
+
+*  Parameters:
+c     nreg
+f     NREG = INTEGER (Given)
+*        The number of supplied Regions.
+c     regs
+f     REGS( NREG ) = INTEGER (Given)
+*        An array of pointers to the Regions. All the supplied Regions must
+*        relate to the same coordinate Frame. The number of axes in this
+*        coordinate Frame defines the number of inputs for the SelectorMap.
+c     badval
+f     BADVAL = DOUBLE PRECISION (Given)
+*        The value to be returned by the forward transformation of the
+*        SelectorMap for any input positions that have a bad (AST__BAD)
+*        value on any axis.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new SelectorMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new SelectorMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSelectorMap()
+f     AST_SELECTORMAP = INTEGER
+*        A pointer to the new SelectorMap.
+
+*  Notes:
+*     - Deep copies are taken of the supplied Regions. This means that
+*     any subsequent changes made to the component Regions using the
+*     supplied pointers will have no effect on the SelectorMap.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astSelectorMap constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astSelectorMap_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - Because no checking or casting of arguments is performed
+*     before the function is invoked, the "map1" and "map2" parameters
+*     are of type (void *) and are converted from an ID value to a
+*     pointer and validated within the function itself.
+*     - The variable argument list also prevents this function from
+*     invoking astSelectorMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the conversions between IDs
+*     and pointers on input/output of Objects.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSelectorMap *new;          /* Pointer to new SelectorMap */
+   AstRegion **regs;             /* Array of Region pointers */
+   int i;                        /* Region index */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Report an error if no Regions have been supplied. */
+   if( nreg <= 0 ) astError( AST__BDPAR, "astSelectorMap(SelectorMap): "
+                             "Bad number of Regions (%d) specified.", status, nreg );
+
+/* Create an array to hold the Region pointers. */
+   regs = astMalloc( sizeof( AstRegion * )*nreg );
+
+/* Obtain and validate pointers to the Region structures provided. */
+   if( astOK ) {
+      for( i = 0; i < nreg; i++ ) {
+         regs[ i ] = astVerifyRegion( astMakePointer(regs_void[ i ]) );
+      }
+   }
+
+   if ( astOK ) {
+
+/* Initialise the SelectorMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitSelectorMap( NULL, sizeof( AstSelectorMap ), !class_init,
+                                &class_vtab, "SelectorMap", nreg, regs,
+                                badval );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new SelectorMap's
+   attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Free memory used to hold the Regions pointers. */
+   regs = astFree( regs );
+
+/* Return an ID value for the new SelectorMap. */
+   return astMakeId( new );
+}
+
+AstSelectorMap *astInitSelectorMap_( void *mem, size_t size, int init,
+                                     AstSelectorMapVtab *vtab, const char *name,
+                                     int nreg, AstRegion **regs, double badval, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSelectorMap
+
+*  Purpose:
+*     Initialise a SelectorMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "selectormap.h"
+*     AstSelectorMap *astInitSelectorMap( void *mem, size_t size, int init,
+*                                         AstSelectorMapVtab *vtab, const char *name,
+*                                         int nreg, AstRegion **regs, double badval )
+
+*  Class Membership:
+*     SelectorMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new SelectorMap object. It allocates memory (if necessary) to
+*     accommodate the SelectorMap plus any additional data associated with the
+*     derived class. It then initialises a SelectorMap structure at the start
+*     of this memory. If the "init" flag is set, it also initialises the
+*     contents of a virtual function table for a SelectorMap at the start of
+*     the memory passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the SelectorMap is to be initialised.
+*        This must be of sufficient size to accommodate the SelectorMap data
+*        (sizeof(SelectorMap)) plus any data used by the derived class. If a
+*        value of NULL is given, this function will allocate the memory itself
+*        using the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the SelectorMap (plus derived class
+*        data). This will be used to allocate memory if a value of NULL is
+*        given for the "mem" parameter. This value is also stored in the
+*        SelectorMap structure, so a valid value must be supplied even if not
+*        required for allocating memory.
+*     init
+*        A logical flag indicating if the SelectorMap's virtual function table
+*        is to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new SelectorMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     nreg
+*        The number of Regions supplied.
+*     regs
+*        An array holdiong pointers to the Regions. Deep copies are taken
+*        of these Regions.
+*     badval
+*        The value to be returned by the forward transformation of the
+*        SelectorMap for any input positions that have a bad (AST__BAD)
+*        value on any axis.
+
+*  Returned Value:
+*     A pointer to the new SelectorMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstFrame *f0;                 /* Frame from first Region */
+   AstFrame *f1;                 /* Frame from current Region */
+   AstSelectorMap *new;          /* Pointer to new SelectorMap */
+   int equal;                    /* Are Frames equal? */
+   int i;                        /* Loop count */
+   int nin;                      /* No. input coordinates for SelectorMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitSelectorMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check that all Regions refer to the same Frame. */
+   f0 = astRegFrame( regs[ 0 ] );
+   for( i = 1; i < nreg; i++ ) {
+      f1 = astRegFrame( regs[ i ] );
+      equal = astEqual( f1, f0 );
+      f1 = astAnnul( f1 );
+
+      if( !equal ) {
+         if( astOK ) {
+            astError( AST__BADNI, "astInitSelectorMap(%s): Region "
+                      "number %d does not refer to the same coordinate "
+                      "Frame as the first Region.", status, name, i + 1 );
+         }
+      }
+   }
+
+   nin = astGetNin( regs[ 0 ] );
+   f0 = astAnnul( f0 );
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the SelectorMap structure, allocating memory if necessary. Specify
+   the number of input and output coordinates and in which directions the
+   Mapping should be defined. */
+   if ( astOK ) {
+      new = (AstSelectorMap *) astInitMapping( mem, size, 0,
+                                             (AstMappingVtab *) vtab, name,
+                                             nin, 1, 1, 0 );
+      if ( astOK ) {
+
+/* Initialise the SelectorMap data. */
+/* -------------------------------- */
+
+/* Create an array for the Region pointers. */
+         new->reg = astMalloc( sizeof( AstRegion * )*nreg );
+
+/* Store pointers to deep copies of the Regions. */
+         if( astOK ) {
+            new->nreg = nreg;
+            for( i = 0; i < nreg; i++ ) {
+               new->reg[ i ] = astCopy( regs[ i ] );
+            }
+         } else {
+            new->nreg = 0;
+         }
+
+/* Store other items */
+         new->badval = badval;
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstSelectorMap *astLoadSelectorMap_( void *mem, size_t size,
+                                     AstSelectorMapVtab *vtab, const char *name,
+                                     AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadSelectorMap
+
+*  Purpose:
+*     Load a SelectorMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "selectormap.h"
+*     AstSelectorMap *astLoadSelectorMap( void *mem, size_t size,
+*                                         AstSelectorMapVtab *vtab, const char *name,
+*                                         AstChannel *channel )
+
+*  Class Membership:
+*     SelectorMap loader.
+
+*  Description:
+*     This function is provided to load a new SelectorMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     SelectorMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a SelectorMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the SelectorMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        SelectorMap data (sizeof(SelectorMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the SelectorMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the SelectorMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstSelectorMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new SelectorMap. If this is NULL, a pointer to
+*        the (static) virtual function table for the SelectorMap class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "SelectorMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new SelectorMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSelectorMap *new;
+   AstFrameSet *fs;
+   AstRegion *reg;
+   int i;
+   char buf[ 20 ];
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this SelectorMap. In this case the
+   SelectorMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstSelectorMap );
+      vtab = &class_vtab;
+      name = "SelectorMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitSelectorMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built SelectorMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "SelectorMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+
+/* Loop to load each Region. */
+/* ------------------------- */
+      new->reg = NULL;
+      i = 0;
+      fs = NULL;
+      while( astOK ) {
+         sprintf( buf, "reg%d", i + 1 );
+         reg = astReadObject( channel, buf, NULL );
+         if( reg ) {
+            new->reg = astGrow( new->reg, i + 1, sizeof( AstRegion *) );
+            if( astOK ) {
+               new->reg[ i ] = reg;
+
+/* All but the first Region may have a dummy FrameSet rather than the
+   correct FrameSet. The correct FrameSet will be a copy of the FrameSet
+   from the first Region. */
+               if( i == 0 ) {
+                  fs = astGetRegFS( reg );
+               } else if( astRegDummyFS( reg ) ){
+                  astSetRegFS( reg, fs );
+               }
+
+               i++;
+            }
+         } else {
+            break;
+         }
+      }
+
+      fs = astAnnul( fs );
+
+/* Number of Regions. */
+      new->nreg = i;
+
+
+/* BadVal. */
+/* ------- */
+      new->badval = astReadDouble( channel, "badval", AST__BAD );
+
+/* If an error occurred, clean up by deleting the new SelectorMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new SelectorMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+/* None. */
+
+
+
+
diff --git a/ast/selectormap.h b/ast/selectormap.h
new file mode 100644
index 0000000..36d83a5
--- /dev/null
+++ b/ast/selectormap.h
@@ -0,0 +1,277 @@
+#if !defined( SELECTORMAP_INCLUDED )  /* Include this file only once */
+#define SELECTORMAP_INCLUDED
+/*
+*+
+*  Name:
+*     selectormap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the SelectorMap class.
+
+*  Invocation:
+*     #include "selectormap.h"
+
+*  Description:
+*     This include file defines the interface to the SelectorMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc. needed to use this class.
+
+*  Inheritance:
+*     The SelectorMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astMapMerge
+*           Merge a SelectorMap within a sequence of Mappings.
+*        astTransform
+*           Transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsASelectorMap
+*           Test class membership.
+*        astSelectorMap
+*           Create a SelectorMap.
+*
+*     Protected:
+*        astCheckSelectorMap
+*           Validate class membership.
+*        astInitSelectorMap
+*           Initialise a SelectorMap.
+*        astInitSelectorMapVtab
+*           Initialise the virtual function table for the SelectorMap class.
+*        astLoadSelectorMap
+*           Load a SelectorMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstSelectorMap
+*           SelectorMap object type.
+*
+*     Protected:
+*        AstSelectorMapVtab
+*           SelectorMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     13-MAR-2006 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+#include "region.h"              /* Coordinate Regions (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* SelectorMap structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstSelectorMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;       /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int nreg;                /* The number of Regions in the SelectorMap */
+   AstRegion **reg;         /* Array of Region pointers */
+   double badval;           /* Output value for positions with bad axis values */
+
+} AstSelectorMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstSelectorMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+/* None. */
+} AstSelectorMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstSelectorMapGlobals {
+   AstSelectorMapVtab Class_Vtab;
+   int Class_Init;
+} AstSelectorMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitSelectorMapGlobals_( AstSelectorMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(SelectorMap)           /* Check class membership */
+astPROTO_ISA(SelectorMap)             /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstSelectorMap *astSelectorMap_( int, void **, double, const char *, int *, ...);
+#else
+AstSelectorMap *astSelectorMapId_( int, void **, double, const char *, ... )__attribute__((format(printf,4,5)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstSelectorMap *astInitSelectorMap_( void *, size_t, int, AstSelectorMapVtab *,
+                                     const char *, int, AstRegion **, double, int * );
+
+/* Vtab initialiser. */
+void astInitSelectorMapVtab_( AstSelectorMapVtab *, const char *, int * );
+
+/* Loader. */
+AstSelectorMap *astLoadSelectorMap_( void *, size_t, AstSelectorMapVtab *,
+                                     const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+/* None. */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckSelectorMap(this) astINVOKE_CHECK(SelectorMap,this,0)
+#define astVerifySelectorMap(this) astINVOKE_CHECK(SelectorMap,this,1)
+
+/* Test class membership. */
+#define astIsASelectorMap(this) astINVOKE_ISA(SelectorMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astSelectorMap astINVOKE(F,astSelectorMap_)
+#else
+#define astSelectorMap astINVOKE(F,astSelectorMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitSelectorMap(mem,size,init,vtab,name,nreg,regs,badval) \
+astINVOKE(O,astInitSelectorMap_(mem,size,init,vtab,name,nreg,regs,badval,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitSelectorMapVtab(vtab,name) astINVOKE(V,astInitSelectorMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadSelectorMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadSelectorMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckSelectorMap to validate SelectorMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+/* None. */
+#endif
+
+
+
+
+
diff --git a/ast/shiftmap.c b/ast/shiftmap.c
new file mode 100644
index 0000000..eed6e1d
--- /dev/null
+++ b/ast/shiftmap.c
@@ -0,0 +1,1617 @@
+/*
+*class++
+*  Name:
+*     ShiftMap
+
+*  Purpose:
+*     Add a constant value to each coordinate.
+
+*  Constructor Function:
+c     astShiftMap
+f     AST_SHIFTMAP
+
+*  Description:
+*     A ShiftMap is a linear Mapping which shifts each axis by a
+*     specified constant value.
+
+*  Inheritance:
+*     The ShiftMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The ShiftMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     The ShiftMap class does not define any new functions beyond those
+f     The ShiftMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David Berry (Starlink)
+
+*  History:
+*     15-AUG-2003 (DSB):
+*        Original version.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS ShiftMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory management facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "matrixmap.h"           /* Linear mappings */
+#include "unitmap.h"             /* Unit mappings */
+#include "zoommap.h"             /* Zoom mappings */
+#include "permmap.h"             /* Axis permutations */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "winmap.h"              /* Window mappings */
+#include "shiftmap.h"            /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(ShiftMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(ShiftMap,Class_Init)
+#define class_vtab astGLOBAL(ShiftMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstShiftMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstShiftMap *astShiftMapId_( int, const double [], const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int GetObjSize( AstObject *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetIsLinear( AstMapping *, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+
+/* Member functions. */
+/* ================= */
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two ShiftMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "shiftmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     ShiftMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two ShiftMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a ShiftMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the ShiftMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstShiftMap *that;
+   AstShiftMap *this;
+   int i;
+   int nin;
+   int nout;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two ShiftMap structures. */
+   this = (AstShiftMap *) this_object;
+   that = (AstShiftMap *) that_object;
+
+/* Check the second object is a ShiftMap. We know the first is a
+   ShiftMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAShiftMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two ShiftMaps differ, it may still be possible
+   for them to be equivalent. First compare the ShiftMaps if their Invert
+   flags are the same. In this case all the attributes of the two ShiftMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+            result = 1;
+            for( i = 0; i < nin; i++ ) {
+               if( !astEQUAL( this->shift[ i ], that->shift[ i ] ) ) {
+                  result = 0;
+                  break;
+               }
+            }
+
+/* If the Invert flags for the two ShiftMaps differ, the attributes of the two
+   ShiftMaps must be inversely related to each other. */
+         } else {
+
+            result = 1;
+            for( i = 0; i < nin; i++ ) {
+               if( !astEQUAL( this->shift[ i ], -(that->shift[ i ] ) ) ) {
+                  result = 0;
+                  break;
+               }
+            }
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetIsLinear( AstMapping *this_mapping, int *status ){
+/*
+*  Name:
+*     GetIsLinear
+
+*  Purpose:
+*     Return the value of the IsLinear attribute for a ShiftMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GetIsLinear( AstMapping *this, int *status )
+
+*  Class Membership:
+*     ShiftMap member function (over-rides the protected astGetIsLinear
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns the value of the IsLinear attribute for a
+*     Frame, which is always one.
+
+*  Parameters:
+*     this
+*        Pointer to the ShiftMap.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   return 1;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "shiftmap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     ShiftMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied ShiftMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the ShiftMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstShiftMap *this;         /* Pointer to ShiftMap structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the ShiftMap structure. */
+   this = (AstShiftMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astTSizeOf( this->shift );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+void astInitShiftMapVtab_(  AstShiftMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitShiftMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a ShiftMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "shiftmap.h"
+*     void astInitShiftMapVtab( AstShiftMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     ShiftMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the ShiftMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAShiftMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+   mapping->Rate = Rate;
+   mapping->MapSplit = MapSplit;
+   mapping->GetIsLinear = GetIsLinear;
+
+/* Declare the class dump, copy and delete functions.*/
+   astSetDump( vtab, Dump, "ShiftMap", "Shift each coordinate axis" );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a ShiftMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     ShiftMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated ShiftMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated ShiftMap with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated ShiftMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the ShiftMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        ShiftMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated ShiftMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstWinMap *w1;        /* Pointer to replacement Mapping */
+   AstShiftMap *sm;      /* Pointer to this ShiftMap */
+   double *aa;           /* Pointer to shift terms for new WinMap */
+   double *bb;           /* Pointer to scale terms for new WinMap */
+   int i;                /* Axis count */
+   int nin;              /* Number of axes */
+   int result;           /* Returned value */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* A ShiftMap is equivalent to a WinMap with unit scaling. The policy on
+   simplifying a ShiftMap is to convert it to the equivalent WinMap and let
+   the WinMap class do the simplifying. Create the returned WinMap, initially
+   with undefined corners. */
+   nin = astGetNin( this );
+   w1 = astWinMap( nin, NULL, NULL, NULL, NULL, "", status );
+
+/* If succesful, store the scale and shift terms in the WinMap. The scale
+   terms are unity. */
+   if( astOK ){
+      sm = (AstShiftMap *) this;
+
+      bb = w1->b;
+      aa = w1->a;
+      for( i = 0; i < nin; i++ ) {
+         *(bb++) = 1.0;
+         *(aa++) = ( *invert_list )[ where ] ? -(sm->shift)[ i ] : (sm->shift)[ i ];
+      }
+
+/* Replace the supplied ShiftMap with the new WinMap and reset the invert
+   flag. */
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = (AstMapping *) w1;
+      ( *invert_list )[ where ] = 0;
+
+/* Return the index of the first modified element. */
+      result = where;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     ShiftMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "shiftmap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     ShiftMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing ShiftMap. This is only possible if the specified inputs
+*     correspond to some subset of the ShiftMap outputs. That is, there
+*     must exist a subset of the ShiftMap outputs for which each output
+*     depends only on the selected ShiftMap inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied ShiftMap, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the ShiftMap to be split (the ShiftMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied ShiftMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied ShiftMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied ShiftMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstShiftMap *newsm;        /* Pointer to returned ShiftMap */
+   AstShiftMap *this;         /* Pointer to ShiftMap structure */
+   int *result;               /* Pointer to returned array */
+   int i;                     /* Loop count */
+   int iin;                   /* Mapping input index */
+   int mnin;                  /* No. of Mapping inputs */
+   int ok;                    /* Are input indices OK? */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the ShiftMap structure. */
+   this = (AstShiftMap *) this_map;
+
+/* Allocate memory for the returned array and create a ShiftMap with the
+   required number of axes and initially unsorted shifts. */
+   result = astMalloc( sizeof( int )*(size_t) nin );
+   newsm = astShiftMap( nin, this->shift, "", status );
+   *map = (AstMapping *) newsm;
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Store the required shifts in the new ShiftMap. At the same time check
+   that each axis is valid. */
+      mnin = astGetNin( this );
+      ok = 1;
+      for( i = 0; i < nin; i++ ) {
+         iin = in[ i ];
+         if( iin >= 0 && iin < mnin ) {
+            (newsm->shift)[ i ] = (this->shift)[ iin ];
+            result[ i ] = iin;
+         } else {
+            ok = 0;
+            break;
+         }
+      }
+
+/* If the "in" array contained any invalid values, free the returned
+   resources. */
+      if( !ok ) {
+         result = astFree( result );
+         *map = astAnnul( *map );
+
+/* If the indices are good, invert the returned ShiftMap if the supplied
+   ShiftMap is inverted. */
+      } else {
+         if( astGetInvert( this ) ) astInvert( *map );
+      }
+   }
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "shiftmap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     ShiftMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*/
+
+   return ( ax1 == ax2 ) ? 1.0 : 0.0;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a ShiftMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "shiftmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     ShiftMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a ShiftMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to map them into the
+*     required window.
+
+*  Parameters:
+*     this
+*        Pointer to the ShiftMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the ShiftMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstShiftMap *map;             /* Pointer to ShiftMap to be applied */
+   const char *class;            /* Object class */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *axin;                 /* Pointer to next input axis value */
+   double *axout;                /* Pointer to next output axis value */
+   double a;                     /* Shift for current axis */
+   int coord;                    /* Loop counter for coordinates */
+   int ncoord;                   /* Number of coordinates per point */
+   int npoint;                   /* Number of points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the ShiftMap. */
+   map = (AstShiftMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   ncoord = astGetNcoord( in );
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse mapping, according to the
+   direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* Report an error if the ShiftMap does not contain any shifts. */
+   if( !map->shift && astOK ){
+      class = astGetClass( this );
+      astError( AST__BADSM, "astTransform(%s): The supplied %s does not "
+                "contain any shift information.", status, class, class );
+   }
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if( astOK ){
+
+/* Apply the mapping to each axis. */
+      for( coord = 0; coord < ncoord; coord++ ){
+
+/* Store pointers to the first input and output values on this axis. */
+         axin = ptr_in[ coord ];
+         axout = ptr_out[ coord ];
+
+/* Get the value to add to each axis value. */
+         a = (map->shift)[ coord ];
+
+/* If the shift is bad store bad output values. */
+         if( a == AST__BAD ){
+            for( point = 0; point < npoint; point++ ) *(axout++) = AST__BAD;
+
+/* Otherwise, shift this axis, taking account of whether the mapping is
+   inverted or not. */
+         } else {
+            if( !forward ) a = -a;
+
+            for( point = 0; point < npoint; point++ ){
+               if( *axin != AST__BAD ){
+                  *(axout++) = (*axin) + a;
+               } else {
+                  *(axout++) = AST__BAD;
+               }
+               axin++;
+            }
+         }
+      }
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for ShiftMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for ShiftMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the ShiftMap to be copied.
+*     objout
+*        Pointer to the ShiftMap being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstShiftMap *out;              /* Pointer to output ShiftMap */
+   AstShiftMap *in;               /* Pointer to input ShiftMap */
+   int ncoord;                  /* No. of axes for the mapping */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the input and output ShiftMaps. */
+   in= (AstShiftMap *) objin;
+   out = (AstShiftMap *) objout;
+
+/* Get the number of coordinates mapped by the ShiftMap. */
+   ncoord = astGetNin( in );
+
+/* Allocate memory holding copies of the shifts defining the mapping. */
+   out->shift = (double *) astStore( NULL, (void *) in->shift,
+                                     sizeof(double)*(size_t)ncoord );
+
+/* If an error occurred, free any allocated memory. */
+   if ( !astOK ) {
+      out->shift = (double *) astFree( (void *) out->shift );
+   }
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for ShiftMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for ShiftMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the ShiftMap to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This destructor does nothing and exists only to maintain a
+*     one-to-one correspondence between destructors and copy
+*     constructors.
+*/
+
+/* Local Variables: */
+   AstShiftMap *this;              /* Pointer to ShiftMap */
+
+/* Obtain a pointer to the ShiftMap structure. */
+   this = (AstShiftMap *) obj;
+
+/* Free the memory holding the shifts. */
+   this->shift = (double *) astFree( (void *) this->shift );
+
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for ShiftMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the ShiftMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the ShiftMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define COMMENT_LEN 50           /* Maximum length of a comment string */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstShiftMap *this;              /* Pointer to the ShiftMap structure */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   char comment[ COMMENT_LEN + 1 ]; /* Buffer for comment string */
+   int axis;                     /* Axis index */
+   int ncoord;                   /* No. of axes for mapping */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the ShiftMap structure. */
+   this = (AstShiftMap *) this_object;
+
+/* Get the number of coordinates to be mapped. */
+   ncoord = astGetNin( this );
+
+/* Write out values representing the instance variables for the
+   ShiftMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* The shifts. */
+   for( axis = 0; axis < ncoord; axis++ ){
+      (void) sprintf( buff, "Sft%d", axis + 1 );
+      (void) sprintf( comment, "Shift for axis %d", axis + 1 );
+      astWriteDouble( channel, buff, (this->shift)[ axis ] != 0.0, 0,
+                      (this->shift)[ axis ], comment );
+   }
+
+/* Undefine macros local to this function. */
+#undef COMMENT_LEN
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAShiftMap and astCheckShiftMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(ShiftMap,Mapping)
+astMAKE_CHECK(ShiftMap)
+
+AstShiftMap *astShiftMap_( int ncoord, const double shift[], const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astShiftMap
+f     AST_SHIFTMAP
+
+*  Purpose:
+*     Create a ShiftMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "shiftmap.h"
+c     AstShiftMap *astShiftMap( int ncoord, const double shift[],
+c                               const char *options, ... )
+f     RESULT = AST_SHIFTMAP( NCOORD, SHIFT, OPTIONS, STATUS )
+
+*  Class Membership:
+*     ShiftMap constructor.
+
+*  Description:
+*     This function creates a new ShiftMap and optionally initialises its
+*     attributes.
+*
+*     A ShiftMap is a linear Mapping which shifts each axis by a
+*     specified constant value.
+
+*  Parameters:
+c     ncoord
+f     NCOORD = INTEGER (Given)
+*        The number of coordinate values for each point to be
+*        transformed (i.e. the number of dimensions of the space in
+*        which the points will reside). The same number is applicable
+*        to both input and output points.
+c     shift
+f     SHIFT( NCOORD ) = DOUBLE PRECISION (Given)
+*        An array containing the values to be added on to the input
+*        coordinates in order to create the output coordinates. A separate
+*        value should be supplied for each coordinate.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new ShiftMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new ShiftMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astShiftMap()
+f     AST_SHIFTMAP = INTEGER
+*        A pointer to the new ShiftMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstShiftMap *new;              /* Pointer to new ShiftMap */
+   va_list args;                  /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the ShiftMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitShiftMap( NULL, sizeof( AstShiftMap ), !class_init, &class_vtab,
+                        "ShiftMap", ncoord, shift );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new ShiftMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new ShiftMap. */
+   return new;
+}
+
+AstShiftMap *astShiftMapId_( int ncoord, const double shift[],
+                             const char *options, ... ) {
+/*
+*  Name:
+*     astShiftMapId_
+
+*  Purpose:
+*     Create a ShiftMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "shiftmap.h"
+*     AstShiftMap *astShiftMapId_( int ncoord, const double shift[],
+*                                  const char *options, ... )
+
+*  Class Membership:
+*     ShiftMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astShiftMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astShiftMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astShiftMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astShiftMap_.
+
+*  Returned Value:
+*     The ID value associated with the new ShiftMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstShiftMap *new;            /* Pointer to new ShiftMap */
+   va_list args;                /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the ShiftMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitShiftMap( NULL, sizeof( AstShiftMap ), !class_init, &class_vtab,
+                          "ShiftMap", ncoord, shift );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new ShiftMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new ShiftMap. */
+   return astMakeId( new );
+}
+
+AstShiftMap *astInitShiftMap_( void *mem, size_t size, int init,
+                              AstShiftMapVtab *vtab, const char *name,
+                              int ncoord, const double *shift, int *status ) {
+/*
+*+
+*  Name:
+*     astInitShiftMap
+
+*  Purpose:
+*     Initialise a ShiftMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "shiftmap.h"
+*     AstShiftMap *astInitShiftMap( void *mem, size_t size, int init,
+*                                   AstShiftMapVtab *vtab, const char *name,
+*                                   int ncoord, const double *shift )
+
+*  Class Membership:
+*     ShiftMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new ShiftMap object. It allocates memory (if necessary) to accommodate
+*     the ShiftMap plus any additional data associated with the derived class.
+*     It then initialises a ShiftMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a ShiftMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the ShiftMap is to be initialised.
+*        This must be of sufficient size to accommodate the ShiftMap data
+*        (sizeof(ShiftMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the ShiftMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the ShiftMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the ShiftMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new ShiftMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     ncoord
+*        The number of coordinate values per point.
+*     shift
+*        Pointer to an array of shifts, one for each coordinate.
+
+*  Returned Value:
+*     A pointer to the new ShiftMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstShiftMap *new;            /* Pointer to new ShiftMap */
+   int axis;                    /* Axis index */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitShiftMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the ShiftMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstShiftMap *) astInitMapping( mem, size, 0,
+                                         (AstMappingVtab *) vtab, name,
+                                         ncoord, ncoord, 1, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the ShiftMap data. */
+/* ---------------------------- */
+/* Allocate memory to hold the shift for each axis. */
+      new->shift = (double *) astMalloc( sizeof(double)*(size_t)ncoord );
+
+/* Check the pointers can be used */
+      if( astOK ){
+
+/* Store the shift and scale for each axis. */
+         for( axis = 0; axis < ncoord; axis++ ){
+            (new->shift)[ axis ] = shift ? shift[ axis ] : AST__BAD;
+         }
+
+      }
+
+/* If an error occurred, clean up by deleting the new ShiftMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new ShiftMap. */
+   return new;
+}
+
+AstShiftMap *astLoadShiftMap_( void *mem, size_t size,
+                               AstShiftMapVtab *vtab, const char *name,
+                               AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadShiftMap
+
+*  Purpose:
+*     Load a ShiftMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "shiftmap.h"
+*     AstShiftMap *astLoadShiftMap( void *mem, size_t size,
+*                                   AstShiftMapVtab *vtab, const char *name,
+*                                   AstChannel *channel )
+
+*  Class Membership:
+*     ShiftMap loader.
+
+*  Description:
+*     This function is provided to load a new ShiftMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     ShiftMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a ShiftMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the ShiftMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        ShiftMap data (sizeof(ShiftMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the ShiftMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the ShiftMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstShiftMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new ShiftMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the ShiftMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "ShiftMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new ShiftMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants. */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstShiftMap *new;            /* Pointer to the new ShiftMap */
+   char buff[ KEY_LEN + 1 ];    /* Buffer for keyword string */
+   int axis;                    /* Axis index */
+   int ncoord;                  /* The number of coordinate axes */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this ShiftMap. In this case the
+   ShiftMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstShiftMap );
+      vtab = &class_vtab;
+      name = "ShiftMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitShiftMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built ShiftMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Get the number of axis for the mapping. */
+      ncoord = astGetNin( (AstMapping *) new );
+
+/* Allocate memory to hold the shifts. */
+      new->shift = (double *) astMalloc( sizeof(double)*(size_t)ncoord );
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "ShiftMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* The shifts. */
+      for( axis = 0; axis < ncoord; axis++ ){
+         (void) sprintf( buff, "sft%d", axis + 1 );
+         (new->shift)[ axis ] = astReadDouble( channel, buff, 0.0 );
+      }
+   }
+
+/* If an error occurred, clean up by deleting the new ShiftMap. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the new ShiftMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
diff --git a/ast/shiftmap.h b/ast/shiftmap.h
new file mode 100644
index 0000000..a9b1346
--- /dev/null
+++ b/ast/shiftmap.h
@@ -0,0 +1,290 @@
+#if !defined( SHIFTMAP_INCLUDED ) /* Include this file only once */
+#define SHIFTMAP_INCLUDED
+/*
+*+
+*  Name:
+*     shiftmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the ShiftMap class.
+
+*  Invocation:
+*     #include "shiftmap.h"
+
+*  Description:
+*     This include file defines the interface to the ShiftMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The ShiftMap class implements Mappings which shift each coordinate
+*     by a fixed amount.
+
+*  Inheritance:
+*     The ShiftMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        ClearAttrib
+*           Clear an attribute value for a ShiftMap.
+*        GetAttrib
+*           Get an attribute value for a ShiftMap.
+*        SetAttrib
+*           Set an attribute value for a ShiftMap.
+*        TestAttrib
+*           Test if an attribute value has been set for a ShiftMap.
+*        astMapMerge
+*           Simplify a sequence of Mappings containing a ShiftMap.
+*        astTransform
+*           Apply a ShiftMap to transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAShiftMap
+*           Test class membership.
+*        astShiftMap
+*           Create a ShiftMap.
+*
+*     Protected:
+*        astCheckShiftMap
+*           Validate class membership.
+*        astInitShiftMap
+*           Initialise a ShiftMap.
+*        astInitShiftMapVtab
+*           Initialise the virtual function table for the ShiftMap class.
+*        astLoadShiftMap
+*           Load a ShiftMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstShiftMap
+*           ShiftMap object type.
+*
+*     Protected:
+*        AstShiftMapVtab
+*           ShiftMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     14-AUG-2003 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* ShiftMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstShiftMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *shift;                /* Pointer to array of shifts */
+
+} AstShiftMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstShiftMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+
+} AstShiftMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstShiftMapGlobals {
+   AstShiftMapVtab Class_Vtab;
+   int Class_Init;
+} AstShiftMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitShiftMapGlobals_( AstShiftMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(ShiftMap)          /* Check class membership */
+astPROTO_ISA(ShiftMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstShiftMap *astShiftMap_( int, const double [], const char *, int *, ...);
+#else
+AstShiftMap *astShiftMapId_( int, const double [], const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstShiftMap *astInitShiftMap_( void *, size_t, int, AstShiftMapVtab *,
+                               const char *, int, const double *, int * );
+
+/* Vtab initialiser. */
+void astInitShiftMapVtab_( AstShiftMapVtab *, const char *, int * );
+
+/* Loader. */
+AstShiftMap *astLoadShiftMap_( void *, size_t, AstShiftMapVtab *,
+                               const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckShiftMap(this) astINVOKE_CHECK(ShiftMap,this,0)
+#define astVerifyShiftMap(this) astINVOKE_CHECK(ShiftMap,this,1)
+
+/* Test class membership. */
+#define astIsAShiftMap(this) astINVOKE_ISA(ShiftMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astShiftMap astINVOKE(F,astShiftMap_)
+#else
+#define astShiftMap astINVOKE(F,astShiftMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define \
+astInitShiftMap(mem,size,init,vtab,name,ncoord,shift) \
+astINVOKE(O,astInitShiftMap_(mem,size,init,vtab,name,ncoord,shift,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitShiftMapVtab(vtab,name) astINVOKE(V,astInitShiftMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadShiftMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadShiftMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckShiftMap to validate ShiftMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#endif
+
+#endif
+
+
+
+
+
diff --git a/ast/skyaxis.c b/ast/skyaxis.c
new file mode 100644
index 0000000..fd6d6f6
--- /dev/null
+++ b/ast/skyaxis.c
@@ -0,0 +1,5150 @@
+/*
+*class++
+*  Name:
+*     SkyAxis
+
+*  Purpose:
+*     Store celestial axis information.
+
+*  Constructor Function:
+*     None.
+
+*  Description:
+*     The SkyAxis class is used to store information associated with a
+*     particular axis of a SkyFrame. It is used internally by the AST
+*     library and has no constructor function. You should encounter it
+c     only within textual output (e.g. from astWrite).
+f     only within textual output (e.g. from AST_WRITE).
+
+*  Inheritance:
+*     The SkyAxis class inherits from the Axis class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2008 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: B.S. Berry (Starlink)
+
+*  History:
+*     1-MAR-1996 (RFWS):
+*        Original version.
+*     19-APR-1996 (RFWS):
+*        Tidied up, etc.
+*     8-MAY-1996 (RFWS):
+*        Remove leading minus sign from formatted HMS string if all
+*        fields are zero.
+*     9-MAY-1996 (RFWS):
+*        Fixed bug in rounding of fractional parts of HMS strings and
+*        improved algorithm to cope gracefully with requests for
+*        excessive numbers of decimal places.
+*     13-MAY-1996 (RFWS):
+*        Over-ride the astGetAxisDirection method so that a SkyAxis
+*        with the AsTime attribute set is displayed in reverse by
+*        default.
+*     17-MAY-1996 (RFWS):
+*        Change AxisNorm to return a bad coordinate value if a bad
+*        value is given.
+*     11-SEP-1996 (RFWS):
+*        Added AxisGap and DHmsGap (written by DSB).
+*     26-FEB-1998 (RFWS):
+*        Over-ride the astAxisUnformat method.
+*     6-MAR-1998 (RFWS):
+*        Add formatting options to omit degrees/hours field and change
+*        all affected functions.
+*     10-AUG-2000 (DSB):
+*        Fixed bug in DHmsFormat which could cause (for instance) a formatted
+*        galactic longitude value of zero to be formated as "-0.-0".
+*     29-AUG-2001 (DSB):
+*        Added AxisDistance and AxisOffset.
+*     10-OCT-2002 (DSB):
+*        Over-ride the astGetAxisTop and astGetAxisBottom methods so that a
+*        SkyAxis with the IsLatitude attribute set is legal between plus
+*        and minus 90 degrees.
+*     8-JAN-2003 (DSB):
+*        - Changed private InitVtab method to protected astInitSkyAxisVtab
+*        method.
+*        - Modify DHmsGap to avoid decimal gap "4" which produces things
+*        like "0.0 0.4 0.8 1.2 1.6 2.0" ("4" replaced by "5").
+*     24-JAN-2004 (DSB):
+*        o  Added AxisFields.
+*        o  Added 'g' format character which produces graphical separators.
+*        o  Modified AxisAbbrev to use AxisFields so that delimiters which
+*           include digits can be recognised.
+*     13-SEP-20904 (DSB):
+*        Modify AxisFields to correct usage of the "p" pointer in the
+*        case that the first and only field begins with a minus sign.
+*     15-SEP-2004 (DSB):
+*        Modified ParseDHmsFormat so that the number of decimal places
+*        is specified by Digits if the given format string include a ".*"
+*        precision (e.g. "dms.*").
+*     18-MAR-2005 (DSB):
+*        Invoke methods inherited from parent Axis class if the format
+*        string starts with a '%' character.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     30-JUN-2006 (DSB):
+*        Guard against a null "str1" value in AxisAbbrev.
+*     7-AUG-2007 (DSB):
+*        Added CentreZero attribute.
+*     1-FEB-2008 (DSB):
+*        Modified AxisUnformat to allow the final numerical field to include
+*        an exponent.
+*     13-OCT-2011 (DSB):
+*        Use tuning parameters to store graphical delimiters.
+*     27-APR-2015 (DSB):
+*        Added InternalUNit attribute..
+*     26-OCT-2016 (DSB):
+*        Override astAxisNormValues.
+*     7-NOV-2016 (DSB):
+*        Ensure astAxisNormValues ignores bad axis values.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to the header
+   files that define class interfaces that they should make "protected"
+   symbols available. */
+#define astCLASS SkyAxis
+
+/* Header files. */
+/* ============= */
+#include "ast_err.h"             /* Error code definitions */
+
+/* Interface definitions. */
+/* ---------------------- */
+#include "pal.h"                 /* SLALIB interface */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "pointset.h"            /* Sets of points (for AST__BAD) */
+#include "axis.h"                /* Axis (parent) class interface */
+#include "skyaxis.h"             /* Interface definition for this class */
+#include "globals.h"             /* Thread-safe global data access */
+#include "wcsmap.h"              /* For constants */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <limits.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static const char *(* parent_getaxislabel)( AstAxis *, int * );
+static const char *(* parent_getaxissymbol)( AstAxis *, int * );
+static const char *(* parent_getaxisunit)( AstAxis *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static int (*parent_getaxisdirection)( AstAxis *this, int * );
+static void (* parent_axisoverlay)( AstAxis *, AstAxis *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static double (*parent_getaxisbottom)( AstAxis *this, int * );
+static double (*parent_getaxistop)( AstAxis *this, int * );
+static const char *(* parent_axisformat)( AstAxis *, double, int * );
+static double (*parent_axisgap)( AstAxis *, double, int *, int * );
+static int (*parent_axisunformat)( AstAxis *, const char *, double *, int * );
+static int (*parent_axisfields)( AstAxis *, const char *, const char *, int, char **, int *, double *, int * );
+
+/* Factors for converting between hours, degrees and radians. */
+static double hr2rad;
+static double deg2rad;
+static double pi;
+static double piby2;
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->DHmsFormat_Buff[ 0 ] = 0; \
+   globals->DHmsUnit_Buff[ 0 ] = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->GetAxisFormat_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(SkyAxis)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(SkyAxis,Class_Init)
+#define class_vtab astGLOBAL(SkyAxis,Class_Vtab)
+#define dhmsformat_buff astGLOBAL(SkyAxis,DHmsFormat_Buff)
+#define dhmsunit_buff astGLOBAL(SkyAxis,DHmsUnit_Buff)
+#define getattrib_buff astGLOBAL(SkyAxis,GetAttrib_Buff)
+#define getaxisformat_buff astGLOBAL(SkyAxis,GetAxisFormat_Buff)
+
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char dhmsformat_buff[ AST__SKYAXIS_DHMSFORMAT_BUFF_LEN + 1 ];
+static char dhmsunit_buff[ AST__SKYAXIS_DHMSUNIT_BUFF_LEN + 1 ];
+static char getattrib_buff[ AST__SKYAXIS_GETATTRIB_BUFF_LEN + 1 ];
+static char getaxisformat_buff[ AST__SKYAXIS_GETAXISFORMAT_BUFF_LEN + 1 ];
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstSkyAxisVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+
+#endif
+
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstSkyAxis *astSkyAxisId_( const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static const char *AxisAbbrev( AstAxis *, const char *, const char *, const char *, int * );
+static const char *AxisFormat( AstAxis *, double, int * );
+static int GetObjSize( AstObject *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static const char *GetAxisFormat( AstAxis *, int * );
+static const char *GetAxisInternalUnit( AstAxis *, int * );
+static const char *GetAxisLabel( AstAxis *, int * );
+static const char *GetAxisSymbol( AstAxis *, int * );
+static const char *GetAxisUnit( AstAxis *, int * );
+static const char *DHmsFormat( const char *, int, double, int * );
+static const char *DHmsUnit( const char *, int, int, int * );
+static double AxisGap( AstAxis *, double, int *, int * );
+static double AxisDistance( AstAxis *, double, double, int * );
+static double AxisOffset( AstAxis *, double, double, int * );
+static double DHmsGap( const char *, int, double, int *, int * );
+static double GetAxisTop( AstAxis *, int * );
+static double GetAxisBottom( AstAxis *, int * );
+static int AxisIn( AstAxis *, double, double, double, int, int * );
+static int AxisFields( AstAxis *, const char *, const char *, int, char **, int *, double *, int * );
+static int AxisUnformat( AstAxis *, const char *, double *, int * );
+static int GetAxisAsTime( AstSkyAxis *, int * );
+static int GetAxisDirection( AstAxis *, int * );
+static int GetAxisIsLatitude( AstSkyAxis *, int * );
+static int GetAxisCentreZero( AstSkyAxis *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestAxisAsTime( AstSkyAxis *, int * );
+static int TestAxisFormat( AstAxis *, int * );
+static int TestAxisInternalUnit( AstAxis *, int * );
+static int TestAxisIsLatitude( AstSkyAxis *, int * );
+static int TestAxisCentreZero( AstSkyAxis *, int * );
+static void AxisNorm( AstAxis *, double *, int * );
+static void AxisNormValues( AstAxis *, int, int, double *, int * );
+static void AxisOverlay( AstAxis *, AstAxis *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearAxisAsTime( AstSkyAxis *, int * );
+static void ClearAxisFormat( AstAxis *, int * );
+static void ClearAxisIsLatitude( AstSkyAxis *, int * );
+static void ClearAxisCentreZero( AstSkyAxis *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void ParseDHmsFormat( const char *, int, char *, int *, int *, int *, int *, int *, int *, int *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetAxisAsTime( AstSkyAxis *, int, int * );
+static void SetAxisFormat( AstAxis *, const char *, int * );
+static void SetAxisIsLatitude( AstSkyAxis *, int, int * );
+static void SetAxisCentreZero( AstSkyAxis *, int, int * );
+
+/* Member functions. */
+/* ================= */
+static const char *AxisAbbrev( AstAxis *this_axis, const char *fmt,
+                               const char *str1, const char *str2, int *status ) {
+/*
+*  Name:
+*     AxisAbbrev
+
+*  Purpose:
+*     Abbreviate a formatted SkyAxis value by skipping leading fields.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     const char *AxisAbbrev( AstAxis *this, const char *fmt,
+*                             const char *str1, const char *str2 )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the protected astAxisAbbrev
+*     method inherited from the Axis class).
+
+*  Description:
+*     This function compares two SkyAxis values that have been
+*     formatted with the supplied format specifier (using astAxisFormat)
+*     and determines if they have any redundant leading fields (i.e.
+*     leading fields in common which can be suppressed when tabulating
+*     the values or plotting them on the axis of a graph).
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format specifier used to format the two values.
+*     str1
+*        Pointer to a constant null-terminated string containing the
+*        first formatted value. If this is null, the returned pointer
+*        points to the start of the final field in str2.
+*     str2
+*        Pointer to a constant null-terminated string containing the
+*        second formatted value.
+
+*  Returned Value:
+*     A pointer into the "str2" string which locates the first
+*     character in the first field that differs between the two
+*     formatted values.
+*
+*     If the two values have no leading fields in common, the returned
+*     value will point at the start of string "str2". If the two
+*     values are equal, it will point at the terminating null at the
+*     end of this string.
+
+*  Notes:
+*     - A pointer to the start of "str2" will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   char *fld1[ 3 ];              /* Pointers to start of each field in str1 */
+   char *fld2[ 3 ];              /* Pointers to start of each field in str2 */
+   const char *result;           /* Result pointer to return */
+   int i;                        /* Loop counter for string fields */
+   int nf1;                      /* Number of fields found in str1 */
+   int nf2;                      /* Number of fields found in str2 */
+   int nc1[ 3 ];                 /* Length of each field in str1 */
+   int nc2[ 3 ];                 /* Length of each field in str2 */
+
+/* Initialise. */
+   result = str2;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Find the fields within the "str2" string. */
+   nf2 = astAxisFields( this_axis, fmt, str2, 3, fld2, nc2, NULL );
+
+/* If "str1" was not supplied, return a pointer to the final field in
+   "str2". */
+   if( !str1 ) {
+      result = fld2[ nf2 - 1 ];
+
+/* Otherwise, find the fields within the "str1" string. */
+   } else {
+      nf1 = astAxisFields( this_axis, fmt, str1, 3, fld1, nc1, NULL );
+
+/* Loop to inspect corresponding fields from each string. */
+      for ( i = 0; i < nf1 && i < nf2; i++ ) {
+
+/* If the fields are different, break out of the loop. */
+         if ( nc1[ i ] != nc2[ i ] ||
+              strncmp( fld1[ i ], fld2[ i ], nc1[ i ] ) ) {
+            break;
+
+/* Otherwise, move the returned poitner on to point to the start of the
+   next field in str2. If we are already at the last field in str2,
+   return a pointer to the terminating null. */
+         } else {
+            if ( i + 1 < nf2 ) {
+               result = fld2[ i + 1 ];
+            } else {
+               result = strchr( str2, '\0' );
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double AxisDistance( AstAxis *this_axis, double v1, double v2, int *status ) {
+/*
+*  Name:
+*     AxisDistance
+
+*  Purpose:
+*     Find the distance between two axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     AxisDistance( AstAxis *this, double v1, double v2 )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the protected astAxisDistance
+*     method inherited from the Axis class).
+
+*  Description:
+*     This function returns a signed value representing the axis increment
+*     from axis value v1 to axis value v2.
+*
+*     For a SkyAxis, the angular difference between the two supplied axis
+*     values is normalized into the range +PI to -PI.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     v1
+*        The first axis value
+*     v2
+*        The second axis value
+
+*  Returned Value:
+*     The axis increment from v1 to v2.
+
+*  Notes:
+*     - A value of AST__BAD is returned if either axis value is AST__BAD.
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   double result;                /* Returned gap size */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check both axis values are OK, and form the returned increment,
+   normalizing it into the range +PI to -PI. */
+   if( v1 != AST__BAD && v2 != AST__BAD ) result = palDrange( v2 - v1 );
+
+/* Return the result. */
+   return result;
+}
+
+static int AxisFields( AstAxis *this_axis, const char *fmt, const char *str,
+                       int maxfld, char **fields, int *nc, double *val, int *status ) {
+/*
+*  Name:
+*     AxisFields
+
+*  Purpose:
+*     Identify numerical fields within a formatted SkyAxis value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     int AxisFields( AstAxis *this_axis, const char *fmt, const char *str,
+*                     int maxfld, char **fields, int *nc, double *val )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the protected astAxisFields
+*     method inherited from the Axis class).
+
+*  Description:
+*     This function identifies the numerical fields within a SkyAxis value
+*     that have been formatted using astAxisFormat. It assumes that the
+*     value was formatted using the supplied format string. It also
+*     returns the equivalent floating point value in radians.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format used when creating "str".
+*     str
+*        Pointer to a constant null-terminated string containing the
+*        formatted value.
+*     maxfld
+*        The maximum number of fields to identify within "str".
+*     fields
+*        A pointer to an array of at least "maxfld" character pointers.
+*        Each element is returned holding a pointer to the start of the
+*        corresponding field  in "str" (in the order in which they occur
+*        within "str"), or NULL if no corresponding field can be found.
+*     nc
+*        A pointer to an array of at least "maxfld" integers. Each
+*        element is returned holding the number of characters in the
+*        corresponding field, or zero if no corresponding field can be
+*        found.
+*     val
+*        Pointer to a location at which to store the radians value
+*        equivalent to the returned field values. If this is NULL,
+*        it is ignored.
+
+*  Returned Value:
+*     The number of fields succesfully identified and returned.
+
+*  Notes:
+*     - Leading and trailing spaces are ignored.
+*     - If the formatted value is not consistent with the supplied format
+*     string, then a value of zero will be returned, "fields" will be
+*     returned holding NULLs, "nc" will be returned holding zeros, and
+*     "val" is returned holding VAL__BAD.
+*     - Fields are counted from the start of the formatted string. If the
+*     string contains more than "maxfld" fields, then trailing fields are
+*     ignored.
+*     - If this function is invoked with the global error status set, or
+*     if it should fail for any reason, then a value of zero will be returned
+*     as the function value, and "fields", "nc" and "val"  will be returned
+*     holding their supplied values
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   char sep;                     /* Field separator character */
+   char tbuf[50];                /* Buffer for terminator string */
+   char *p;                      /* Pointer to next character */
+   char *t;                      /* Pointer to start of terminator string */
+   char *term;                   /* Pointer to terminator string */
+   double dval;                  /* Value read from string */
+   double value;                 /* Equivalent radians value */
+   int as_time;                  /* Format the value as a time? */
+   int dh;                       /* Hours field required? */
+   int ifld;                     /* Field index */
+   int lead_zero;                /* Add leading zeros? */
+   int min;                      /* Minutes field required? */
+   int ndp;                      /* Number of decimal places */
+   int ok;                       /* Value and format consistent? */
+   int plus;                     /* Add leading plus sign? */
+   int result;                   /* Result fields count to return */
+   int sec;                      /* Seconds field required? */
+   int sign;                     /* The sign of the radians value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_axis);
+
+/* If the format string starts with a "%" call the method inherited from
+   the parent Axis class. */
+   if( fmt[ 0 ] == '%' ) {
+      return (*parent_axisfields)( this_axis, fmt, str, maxfld, fields, nc,
+                                   val, status );
+   }
+
+/* Initialise. */
+   result = 0;
+   for( ifld = 0; ifld < maxfld; ifld++ ) {
+      fields[ ifld ] = NULL;
+      nc[ ifld ] = 0;
+   }
+   if( val ) *val = AST__BAD;
+
+/* Parse the format specifier. */
+   ParseDHmsFormat( fmt, astGetAxisDigits( this_axis ), &sep, &plus, &lead_zero,
+                    &as_time, &dh, &min, &sec, &ndp, status );
+
+/* Only proceed if the format was parsed succesfully, and the supplied arrays
+   are not of zero size. */
+   if( astOK && maxfld > 0 ) {
+
+/* Indicate that we have not yet found any inconsistency between the
+   formatted value and the forat string. */
+      ok = 1;
+
+/* Variable "p" points to the next character to be read from the
+   formatted string. Initialise it to point to the first non-space
+   character. */
+      p = (char *) str;
+      while( *p == ' ' ) p++;
+
+/* Note the start of the first field. */
+      fields[ 0 ] = p;
+
+/* If the first non-blank character is a + or - sign, skip it and note
+   the sign of the final value. */
+      sign = 1;
+      if( *p == '-' ) {
+         sign = -1;
+         p++;
+      } else if( *p == '+' ) {
+         p++;
+      }
+
+/* Initialise the equivalent radian value. */
+      value = 0.0;
+
+/* If the format string specifies a degrees or hours field, it should be
+   the first field. */
+      if( dh ) {
+
+/* If the format indicates that fields are separated by characters, or if
+   there is a minutes or seconds field, then the first field should end with
+   the appropriate separator. In these cases locate the terminator,and
+   store the length of the first field. */
+         if( sep == 'l' || sep == 'g' || min || sec ) {
+
+            if( sep == 'l' ) {
+               term = as_time ? "h" : "d";
+
+            } else if( sep == 'g' ) {
+               astTuneC( as_time ? "hrdel":"dgdel", NULL, tbuf,
+                         sizeof( tbuf ) );
+               term = tbuf;
+
+            } else {
+               tbuf[ 0 ] = sep;
+               tbuf[ 1 ] = '\0';
+               term = tbuf;
+            }
+
+            t = strstr( p, term );
+            if( t ) {
+               nc[ 0 ] = t - fields[ 0 ];
+            } else {
+               ok = 0;
+            }
+
+/* Move on to the first character following the terminator. */
+            p = t + strlen( term );
+
+/* In all other cases, the first field is the only field and is not
+   terminated. Note its length (ignoring trailing spaces). Move the
+   pointer on by the length of the field, remembering that any leading
+   minus sign has already been skipped. */
+         } else {
+            nc[ 0 ] = astChrLen( fields[ 0 ] );
+            p += nc[ result ];
+            if( sign == -1 ) p--;
+         }
+
+/* Read a numerical value from the first field. */
+         if( astSscanf( fields[ 0 ], "%lg", &dval ) ) {
+            value = fabs( dval );
+         } else {
+            ok = 0;
+         }
+
+/* Increment then number of returned fields if OK */
+         if( ok ) result++;
+
+      }
+
+/* If the format string specifies a minutes field, it should be the next
+   field. */
+      if( min && ok ) {
+
+/* Note the start of the next field. */
+         fields[ result ] = p;
+
+/* If the format indicates that fields are separated by characters, or if
+   there is a seconds field, then this field should end with the appropriate
+   separator. In these cases locate the terminator,and store the length of
+   this field. */
+         if( sep == 'l' || sep == 'g' || sec ) {
+            if( sep == 'l' ) {
+               term = "m";
+
+            } else if( sep == 'g' ) {
+               astTuneC( as_time ? "mndel":"amdel", NULL, tbuf,
+                         sizeof( tbuf ) );
+               term = tbuf;
+
+            } else {
+               tbuf[ 0 ] = sep;
+               tbuf[ 1 ] = '\0';
+               term = tbuf;
+            }
+
+            t = strstr( p, term );
+            if( t ) {
+               nc[ result ] = t - fields[ result ];
+            } else {
+               ok = 0;
+            }
+
+/* Move on to the first character following the terminator. */
+            p = t + strlen( term );
+
+/* In all other cases, this field is not terminated. Note its length
+   (ignoring trailing spaces). */
+         } else {
+            nc[ result ] = astChrLen( fields[ result ] );
+            p += nc[ result ];
+         }
+
+/* Read a numerical value from this field. */
+         if( astSscanf( fields[ result ], "%lg", &dval ) ) {
+            value += dval/60.0;
+         } else {
+            ok = 0;
+         }
+
+/* Increment then number of returned fields if OK */
+         if( ok ) result++;
+
+      }
+
+/* If the format string specifies a seconds field, it should be the next
+   field. */
+      if( sec && ok ) {
+
+/* Note the start of the next field. */
+         fields[ result ] = p;
+
+/* If the format indicates that fields are separated by characters, then this
+   field should end with the appropriate separator. In this case locate the
+   terminator,and store the length of this field. */
+         if( sep == 'l' || sep == 'g' ) {
+            if( sep == 'l' ) {
+               term = "s";
+            } else {
+               astTuneC( as_time ? "scdel":"asdel", NULL, tbuf,
+                         sizeof( tbuf ) );
+               term = tbuf;
+            }
+
+            t = strstr( p, term );
+            if( t ) {
+               nc[ result ] = t - fields[ result ];
+            } else {
+               ok = 0;
+            }
+
+/* Move on to the first character following the terminator. */
+            p = t + strlen( term );
+
+/* In all other cases, this field is not terminated. Note its length
+   (ignoring trailing spaces). */
+         } else {
+            nc[ result ] = astChrLen( fields[ result ] );
+            p += nc[ result ];
+         }
+
+/* Read a numerical value from this field. */
+         if( astSscanf( fields[ result ], "%lg", &dval ) ) {
+            value += dval/3600.0;
+         } else {
+            ok = 0;
+         }
+
+/* Increment then number of returned fields if OK */
+         if( ok ) result++;
+
+      }
+
+/* Check that nothing is left.*/
+      if( astChrLen( p ) > 0 ) ok = 0;
+
+/* If OK, convert the axis value to radians. */
+      if( ok ) {
+         if( val ) {
+            *val = sign*value*( as_time ? hr2rad : deg2rad );
+         }
+
+/* Otherwise, return zero. */
+      } else {
+         result = 0;
+         for( ifld = 0; ifld < maxfld; ifld++ ) {
+            fields[ ifld ] = NULL;
+            nc[ ifld ] = 0;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *AxisFormat( AstAxis *this_axis, double value, int *status ) {
+/*
+*  Name:
+*     AxisFormat
+
+*  Purpose:
+*     Format a coordinate value for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     const char *AxisFormat( AstAxis *this, double value, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astAxisFormat method inherited
+*     from the Axis class).
+
+*  Description:
+*     This function returns a pointer to a string containing the formatted
+*     (character) version of a coordinate value for a SkyAxis. The formatting
+*     applied is that specified by a previous invocation of the
+*     astSetAxisFormat method. A suitable default format is applied if
+*     necessary.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     value
+*        The coordinate value to be formatted (in radians).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the formatted value.
+
+*  Notes:
+*     -  The returned string pointer may point at memory allocated within
+*     the SkyAxis object, or at static memory. The contents of the string may
+*     be over-written or the pointer may become invalid following a further
+*     invocation of the same function or deletion of the SkyAxis. A copy of the
+*     string should therefore be made if necessary.
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   const char *fmt;              /* Pointer to format specifier */
+   const char *result;           /* Pointer to result string */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise. */
+   result = NULL;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Obtain a pointer to the format specifier to be used. Note we use a private
+   member function to obtain this (not a method) in case derived classes have
+   extended the syntax of this string. */
+   fmt = GetAxisFormat( this_axis, status );
+
+/* If the format string starts with a percent, use the AxisFormat method
+   inherited from the parent Axis class. Otherwise, format using the
+   syntax of this class. */
+   if ( astOK ) {
+      if( fmt[ 0 ] == '%' ) {
+         result = (*parent_axisformat)( this_axis, value, status );
+      } else {
+         result = DHmsFormat( fmt, astGetAxisDigits( this ), value, status );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double AxisGap( AstAxis *this_axis, double gap, int *ntick, int *status ) {
+/*
+*  Name:
+*     AxisGap
+
+*  Purpose:
+*     Find a "nice" gap for tabulating SkyAxis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     double AxisGap( AstAxis *this, double gap, int *ntick, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the protected astAxisGap
+*     method inherited from the Axis class).
+
+*  Description:
+*     This function returns a gap size in radians which produces a
+*     nicely spaced series of formatted SkyAxis values, the returned
+*     gap size being as close as possible to the supplied target gap
+*     size. It also returns a convenient number of divisions into
+*     which the gap can be divided.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     gap
+*        The target gap size.
+*     ntick
+*        Address of an int in which to return a convenient number of
+*        divisions into which the gap can be divided.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The nice gap size.
+
+*  Notes:
+*     - The returned gap size is influenced by the format string
+*     specified for the SkyAxis by a previous invocation of the
+*     astSetAxisFormat method. A suitable default format is used if
+*     necessary.
+*     - A value of zero is returned if the supplied gap size is zero.
+*     - A negative gap size is returned if the supplied gap size is negative.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   const char *fmt;              /* Pointer to Format string */
+   double result;                /* Returned gap size */
+
+/* Initialise. */
+   result = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Obtain a pointer to the format string to be used. Note we use a
+   private member function to obtain this (not a method) in case
+   derived classes have extended the syntax of this string. */
+   fmt = GetAxisFormat( this_axis, status );
+
+/* Obtain the closest "nice" gap size. */
+   if ( astOK ) result = DHmsGap( fmt, astGetAxisDigits( this ), gap, ntick, status );
+
+/* If the format string starts with a percent, use the AxisGap method
+   inherited from the parent Axis class. Otherwise, use the method
+   provided by this class. */
+   if ( astOK ) {
+      if( fmt[ 0 ] == '%' ) {
+         result = (*parent_axisgap)( this_axis, gap, ntick, status );
+      } else {
+         result = DHmsGap( fmt, astGetAxisDigits( this ), gap, ntick, status );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int AxisIn( AstAxis *this, double lo, double hi, double val, int closed, int *status ){
+/*
+*  Name:
+*     AxisIn
+
+*  Purpose:
+*     Test if an axis value lies within a given interval.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     int AxisIn( AstAxis *this, double lo, double hi, double val, int closed, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astAxisIn method inherited
+*     from the Axis class).
+
+*  Description:
+*     This function returns non-zero if a given axis values lies within a
+*     given axis interval.
+*
+*     The SkyAxis implementation of this method treats the supplied
+*     numerical values as non-cyclic (e.g. lo=10, hi = 350 implies that
+*     val = 180 is inside and zero is outside: lo = 10, hi = 400 would imply
+*     that all angles are inside: lo = -10, hi = 10 would imply that 180 is
+*     outside and zero is inside). But when testing a supplied value, adding
+*     or subtracting multiples of 2.PI from the supplied value will make no
+*     difference to whether the point is inside or outside).
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     lo
+*        The lower axis limit of the interval.
+*     hi
+*        The upper axis limit of the interval.
+*     val
+*        The axis value to be tested.
+*     closed
+*        If non-zero, then the lo and hi axis values are themselves
+*        considered to be within the interval. Otherwise they are outside.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the test value is inside the interval.
+
+*/
+
+/* For speed, omit the astOK check since no pointers are being used. */
+
+/* Deal with closed intervals. */
+   if( closed ) {
+
+/* If the supplied value is greater than the upper limit, subtract 2.PI until
+   it is not. */
+      while( val > hi ) val -= 2*pi;
+
+/* If the value is now less than the lower limit, add 2.PI until it is not. */
+      while( val < lo ) val += 2*pi;
+
+/* The axis value is in the range if its numerical value is less than or
+   equal to the end value. */
+      return ( val <= hi );
+
+/* Now deal with open intervals. */
+   } else {
+
+/* If the supplied value is greater than or equal to the upper limit, subtract
+   2.PI until it is not. */
+      while( val >= hi ) val -= 2*pi;
+
+/* If the value is now less than or equal to the lower limit, add 2.PI until
+   it is not. */
+      while( val <= lo ) val += 2*pi;
+
+/* The axis value is in the range if its numerical value is less than the
+   end value. */
+      return ( val < hi );
+   }
+}
+
+static void AxisNorm( AstAxis *this_axis, double *value, int *status ) {
+/*
+*  Name:
+*     AxisNorm
+
+*  Purpose:
+*     Normalise a SkyAxis coordinate value.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     void AxisNorm( AstAxis *this, double *value, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astAxisNorm method inherited
+*     from the Axis class).
+
+*  Description:
+*     This function converts a SkyAxis coordinate value which might
+*     potentially be unsuitable for display to a user (for instance,
+*     may lie outside the expected range of values) into an acceptable
+*     alternative value suitable for display.
+*
+*     For a SkyAxis that is a longitude axis, values are wrapped into
+*     the range zero to 2*pi, while for a latitude axis, they are
+*     wrapped into the range -pi to +pi. The astAxisCentreZero method
+*     is used to determine which algorithm to apply.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     value
+*        Pointer to the coordinate value to be normalised, which will
+*        be modified in place.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   int centrezero;               /* SkyAxis range centred on zero? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* If the coordinate value is bad, then return it unchanged. Otherwise,
+   determine if the SkyAxis range is centred on zero or PI. */
+   if ( *value != AST__BAD ) {
+      centrezero = astGetAxisCentreZero( this );
+
+/* Wrap the value into the appropriate range. */
+      if ( astOK ) *value = centrezero ? palDrange( *value ) :
+                                         palDranrm( *value );
+   }
+}
+
+static void AxisNormValues( AstAxis *this_axis, int oper, int nval,
+                            double *values, int *status ){
+/*
+*  Name:
+*     astAxisNormValues
+
+*  Purpose:
+*     Normalise an array of axis coordinate values.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     void astAxisNormValues( AstAxis *this, int oper, int nval,
+*                             double *values )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astAxisNormValues method
+*     inherited from the Axis class).
+
+*  Description:
+*     This function modifies a supplied array of axis values so that
+*     they are normalised in the manner indicated by parameter "oper".
+*
+*     For a SkyAxis, if "oper" is 0, longitude values are returned in
+*     the range [0,2*PI]. If "oper" is 1, longitude values are returned
+*     in either the range [0,2*PI] or [-PI,PI]. The choice is made so
+*     that the resulting list has the smallest range. Latitude values
+*     are always returned in the range [-PI,PI].
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     oper
+*        Indicates the type of normalisation to be applied. If zero is
+*        supplied, the normalisation will be the same as that performed by
+*        function astAxisNorm. If 1 is supplied, the normalisation will be
+*        chosen automatically so that the resulting list has the smallest
+*        range.
+*     nval
+*        The number of points in the values array.
+*     values
+*        On entry, the axis values to be normalised. Modified on exit to
+*        hold the normalised values.
+
+*  Notes:
+*     - Bad axis values, and axis values outside the range -1E6 to +1E6
+*     radians (such as DBL_MAX and DBL_MIN) are returned unchanged.
+
+*/
+
+/* Local macros */
+#define VAL_OK(v) ((v)!=AST__BAD&&fabs(v)<1.0E6)
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   double *pv;                   /* Pointer to next axis value */
+   double hi;                    /* Max axis value after normalisation */
+   double lo;                    /* Min axis value after normalisation */
+   double mn1;                   /* Min value in range [-pi,0] */
+   double mn2;                   /* Min value in range [0,pi] */
+   double mn3;                   /* Min value in range [pi,2pi] */
+   double mx1;                   /* Max value in range [-pi,0] */
+   double mx2;                   /* Max value in range [0,pi] */
+   double mx3;                   /* Max value in range [pi,2pi] */
+   double range1;                /* Range after normalising to [0,2*pi] */
+   double range2;                /* Range after normalising to [-pi,pi] */
+   double twopi;                 /* Two PI */
+   int centrezero;               /* SkyAxis range centred on zero? */
+   int i;                        /* Index of next axis value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Oper 0 - always normalise according to the value of the CentreZero
+   attribute (i.e. mimic AxisNorm). */
+   if( oper == 0 ) {
+
+/* Determine if the SkyAxis range is centred on zero or PI. */
+      centrezero = astGetAxisCentreZero( this );
+
+/* Loop over axis values. */
+      pv = values;
+      for( i = 0; i < nval; i++,pv++ ) {
+
+/* If the coordinate value is bad, or unusually large, then return it
+   unchanged. Otherwise, wrap the value into the appropriate range. */
+         if( VAL_OK(*pv) ) *pv = centrezero ? palDrange( *pv ) :
+                                              palDranrm( *pv );
+      }
+
+/* Oper 1 - choose a range that leaves most values unchanged. */
+   } else if( oper == 1 ) {
+
+/* Normalise latitude axes into [-PI,+PI]. */
+      if( astGetAxisIsLatitude( this ) ) {
+         pv = values;
+         for( i = 0; i < nval; i++,pv++ ) {
+            if( VAL_OK(*pv) ) *pv = palDrange( *pv );
+         }
+
+/* Now deal with longitude axes. */
+      } else {
+
+/* First ensure all values are in the range [-PI,2*PI] and find the max
+   and min value in each of the three ranges [-PI,0], [0,PI], [PI, 2PI]. */
+         twopi = 2*AST__DPI;
+         mx1 = -DBL_MAX;
+         mn1 = DBL_MAX;
+         mx2 = -DBL_MAX;
+         mn2 = DBL_MAX;
+         mx3 = -DBL_MAX;
+         mn3 = DBL_MAX;
+
+         pv = values;
+         for( i = 0; i < nval; i++,pv++ ) {
+            if( VAL_OK(*pv) ) {
+               while( *pv > twopi ) *pv -= twopi;
+               while( *pv < -AST__DPIBY2 ) *pv += twopi;
+
+               if( *pv > AST__DPI ) {
+                  mx3 = astMAX( mx3, *pv );
+                  mn3 = astMIN( mn3, *pv );
+               } else if( *pv > 0 ) {
+                  mx2 = astMAX( mx2, *pv );
+                  mn2 = astMIN( mn2, *pv );
+               } else {
+                  mx1 = astMAX( mx1, *pv );
+                  mn1 = astMIN( mn1, *pv );
+               }
+            }
+         }
+
+/* What would the range be if we normalised into [0,2.PI] ? */
+         if( mx1 != -DBL_MAX ) {
+            hi = astMAX( mx2, astMAX( mx1 + twopi, mx3) );
+            lo = astMIN( mn2, astMIN( mn1 + twopi, mn3) );
+         } else {
+            hi = astMAX( mx2, mx3 );
+            lo = astMIN( mn2, mn3 );
+         }
+         range1 = hi - lo;
+
+/* What would the range be if we normalised into [-PI,PI] ? */
+         if( mn3 != -DBL_MAX ) {
+            hi = astMAX( mx2, astMAX( mx3 - twopi, mx1) );
+            lo = astMIN( mn2, astMIN( mn3 - twopi, mn1) );
+         } else {
+            hi = astMAX( mx2, mx1 );
+            lo = astMIN( mn2, mn1 );
+         }
+         range2 = hi - lo;
+
+/* If [-PI,PI] produces the smaller range, normalise into [-PI,PI]. */
+         if( range2 < range1 ) {
+            pv = values;
+            for( i = 0; i < nval; i++,pv++ ) {
+               if( VAL_OK(*pv) ) *pv = palDrange( *pv );
+            }
+
+/* Otherwise, normalise all into the range [0,2PI]. */
+         } else {
+            pv = values;
+            for( i = 0; i < nval; i++,pv++ ) {
+               if( VAL_OK(*pv) ) *pv = palDranrm( *pv );
+            }
+         }
+      }
+
+/* Report an error if the supplied operation is invalid. */
+   } else if( astOK ) {
+      astError( AST__INTER, "astAxisNormValues: Invalid oper value %d "
+                "supplied (internal AST programming error).", status, oper );
+   }
+
+#undef VAL_OK
+}
+
+static double AxisOffset( AstAxis *this_axis, double v1, double dist, int *status ) {
+/*
+*
+*  Name:
+*     AxisOffset
+
+*  Purpose:
+*     Add an increment onto a supplied axis value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     AxisOffset( AstSkyAxis *this, double v1, double dist )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the protected astAxisOffset
+*     method inherited from the Axis class).
+
+*  Description:
+*     This function returns an axis value formed by adding a signed axis
+*     increment onto a supplied axis value.
+*
+*     For a SkyFrame, the result is normalized into the correct angular
+*     range (+PI to -PI for latitude axes, and 0 to 2*PI for longitude axes).
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     v1
+*        The supplied axis value
+*     dist
+*        The axis increment
+
+*  Returned Value:
+*     The axis value which is the specified increment away from v1.
+
+*  Notes:
+*     - A value of AST__BAD is returned if either axis value is AST__BAD.
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   double result;                /* Returned gap size */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check both axis values are OK, and form the returned axis value. */
+   if( v1 != AST__BAD && dist != AST__BAD ) {
+      result = v1 + dist;
+      AxisNorm( this_axis, &result, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void AxisOverlay( AstAxis *template_axis, AstAxis *result, int *status ) {
+/*
+*  Name:
+*     AxisOverlay
+
+*  Purpose:
+*     Overlay the attributes of a template SkyAxis on to another Axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     void AxisOverlay( AstAxis *template, AstAxis *result, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astAxisOverlay method inherited
+*     from the Axis class).
+
+*  Description:
+*     This function overlays attributes of a SkyAxis (the "template") on to
+*     another Axis, so as to over-ride selected attributes of that second
+*     Axis. Normally only those attributes which have been specifically set
+*     in the template will be transferred. This implements a form of
+*     defaulting, in which an Axis acquires attributes from the template, but
+*     retains its original attributes (as the default) if new values have not
+*     previously been explicitly set in the template.
+
+*  Parameters:
+*     template
+*        Pointer to the template SkyAxis, for which values should have been
+*        explicitly set for any attribute which is to be transferred.
+*     result
+*        Pointer to the Axis which is to receive the new attribute values.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+*/
+
+/* Local Variables: */
+   AstSkyAxis *template;         /* Pointer to the SkyAxis structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the template SkyAxis structure. */
+   template = (AstSkyAxis *) template_axis;
+
+/* Invoke the parent astAstOverlay method to overlay inherited attributes. */
+   (*parent_axisoverlay)( template_axis, result, status );
+
+/* Test if the "result" Axis is a SkyAxis (if not, it cannot acquire any
+   further attributes, so there is nothing more to do). */
+   if ( astIsASkyAxis( result ) && astOK ) {
+
+/* Overlay the Format attribute if it is set in the template. Note that we
+   use private member functions (not methods) to access the Format value, since
+   derived classes may extend the syntax of this string and we should not
+   overlay a string whose syntax cannot be interpreted by the result Axis. */
+      if ( TestAxisFormat( template_axis, status ) ) {
+         SetAxisFormat( result, GetAxisFormat( template_axis, status ), status );
+      }
+
+/* Overlay the AsTime attribute in the same way, but this time using methods
+   to access it. */
+      if ( astTestAxisAsTime( template ) ) {
+         astSetAxisAsTime( result, astGetAxisAsTime( template ) );
+      }
+
+/* Also overlay the IsLatitude attribute. */
+      if ( astTestAxisIsLatitude( template ) ) {
+         astSetAxisIsLatitude( result, astGetAxisIsLatitude( template ) );
+      }
+
+/* Also overlay the CentreZero attribute. */
+      if ( astTestAxisCentreZero( template ) ) {
+         astSetAxisCentreZero( result, astGetAxisCentreZero( template ) );
+      }
+   }
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astClearAttrib protected
+*     method inherited from the Axis class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     SkyAxis, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* AsTime. */
+/* ------- */
+   if ( !strcmp( attrib, "astime" ) ) {
+      astClearAxisAsTime( this );
+
+/* IsLatitude. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "islatitude" ) ) {
+      astClearAxisIsLatitude( this );
+
+/* CentreZero. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "centrezero" ) ) {
+      astClearAxisCentreZero( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearAxisFormat( AstAxis *this_axis, int *status ) {
+/*
+*  Name:
+*     ClearAxisFormat
+
+*  Purpose:
+*     Clear the Format attribute for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     void ClearAxisFormat( AstAxis *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astClearAxisFormat method
+*     inherited from the Axis class).
+
+*  Description:
+*     This function clears the Format attribute of a SkyAxis, as if no value
+*     had ever been set for it.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Free any memory allocated to hold the Format string and reset the string
+   pointer to NULL. */
+   this->skyformat = astFree( this->skyformat );
+}
+
+static const char *DHmsFormat( const char *fmt, int digs, double value, int *status ) {
+/*
+*  Name:
+*     DHmsFormat
+
+*  Purpose:
+*     Format a value representing degrees/hours, minutes and seconds.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     const char *DHmsFormat( const char *fmt, int digs, double value, int *status )
+
+*  Class Membership:
+*     SkyAxis member function.
+
+*  Description:
+*     This function formats a value representing an angle in radians
+*     into a text string giving degrees/hours, minutes and seconds
+*     according to a format specifier supplied. See the "Format
+*     Specifier" section for details of the formats available.
+
+*  Parameters:
+*     fmt
+*        Pointer to a null terminated string containing the format
+*        specifier.
+*     digs
+*        The default number of digits of precision to use. This is used
+*        if the given format specifier indicates the number of decimal
+*        places to use with the string ".*". In this case, the number of
+*        decimal places produced will be chosen so that the total number
+*        of digits of precision is equal to "digs".
+*     double
+*        The value to be formatted (in radians).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a null terminated character string containing the
+*     formatted value.
+
+*  Format Specifier:
+*     The format specifier supplied via the "fmt" parameter should
+*     contain zero or more of the following characters to specify the
+*     format required.  These characters may occur in any order, but
+*     the following is recommended for clarity:
+*
+*     '+'
+*        Indicates that a plus sign should be prefixed to positive
+*        values. By default, no plus sign is used.
+*     'z'
+*        Indicates that leading zeros should be prefixed to the value
+*        so that the first field is always of constant width, as would
+*        be required in a fixed-width table. (Leading zeros are always
+*        prefixed to any fields that follow.) By default, no leading
+*        zeros are added.
+*     'i'
+*        Use the standard ISO field separator (a colon) between
+*        fields. This is the default behaviour.
+*     'b'
+*        Use a blank to separate fields.
+*     'l'
+*        Use a letter ('d'/'h', 'm' or 's' as appropriate) to separate
+*        and identify fields.
+*     'g'
+*        As 'l', but escape sequences are included in the returned
+*        character string which cause the separators ('h', 'd', 'm', etc)
+*        to be drawn as small super-scripts when plotted by the astText
+*        or astGrid.
+*     'd'
+*        Express the value as an angle and include a degrees
+*        field. Expressing the value as an angle is also the default
+*        behaviour if neither 'h' nor 't' is given, and expressing it
+*        in degrees is the default if neither 'm' nor 's' is given.
+*     'h'
+*        Express the value as a time instead of an angle (where 24
+*        hours correspond to 360 degrees) and include an hours
+*        field. Expressing times in hours is the default if 't' is
+*        given without either 'm' or 's'.
+*     'm'
+*        Include a minutes field. By default this is not included.
+*     's'
+*        Include a seconds field. By default this is not
+*        included. This request is ignored if 'd' or 'h' is given,
+*        unless a minutes field is also included.
+*     't'
+*        Express the value as a time instead of an angle (where 24
+*        hours correspond to 360 degrees). This option is ignored if
+*        either 'd' or 'h' is given and is intended for use in cases
+*        where the value is to be expressed purely in minutes and/or
+*        seconds of time (no hours field). If 't' is given without
+*        'd', 'h', 'm' or 's' being present, then it is equivalent to
+*        'h'.
+*     '.'
+*        Indicates that decimal places are to be given for the final
+*        field in the formatted string (whichever field this is). The
+*        '.' should be followed immediately by a zero or positive integer
+*        which gives the number of decimal places required. The '.' may
+*        also be followed by asterisk (i.e. '.*') which causes the number
+*        of decimal places to be chosen so that the total number of digits
+*        is equal to the value of Digits.
+*
+*     Format specifiers are not case sensitive. If several characters
+*     make conflicting requests (e.g. if both 'i' and 'l' appear in a
+*     format specifier), then the character occurring last takes
+*     precedence, except that 'd' and 'h' always override 't'.
+
+*  Notes:
+*     - The result string may be stored in static memory. Its contents
+*     may be over-written or the returned pointer may become invalid
+*     following a further invocation of this function. A copy of the
+*     string should therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+
+*  Acknowledgements:
+*     - This function is a close approximation to a Fortran 77 routine
+*     written by Clive Davenhall which implements the system of format
+*     specifiers for angles described in his document on the CAT
+*     catalogue access library (Starlink User Note 181). Some minor
+*     improvements have been made to ensure better behaviour when
+*     results are rounded to a specified number of decimal places.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   char *term;                   /* Pointer to terminator string */
+   char sep;                     /* Field separator character */
+   char tbuf[50];                /* Buffer for terminator string */
+   const char *result;           /* Pointer to result string */
+   double absvalue;              /* Absolute value in radians */
+   double fract;                 /* Fractional part of final field */
+   double idh;                   /* Integer number of degrees/hours */
+   double ifract;                /* Fractional part expressed as an integer */
+   double imin;                  /* Integer number of minutes */
+   double isec;                  /* Integer number of seconds */
+   double shift;                 /* Factor for rounding fractional part */
+   double test;                  /* Test value to determine rounding */
+   int as_time;                  /* Format the value as a time? */
+   int dh;                       /* Degrees/hours field required? */
+   int lead_zero;                /* Add leading zeros? */
+   int min;                      /* Minutes field required? */
+   int ndp;                      /* Number of decimal places */
+   int plus;                     /* Add leading plus sign? */
+   int pos;                      /* Position to add next character */
+   int positive;                 /* Value is positive (or zero)? */
+   int sec;                      /* Seconds field required? */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   result = NULL;
+
+/* Check if a bad coordinate value has been given and return an
+   appropriate string. */
+   if ( value == AST__BAD ) {
+      result = "<bad>";
+
+/* Otherwise... */
+   } else {
+
+/* Parse the format specifier. */
+      ParseDHmsFormat( fmt, digs, &sep, &plus, &lead_zero,
+                       &as_time, &dh, &min, &sec, &ndp, status );
+
+/* Break the value into fields. */
+/* ---------------------------- */
+/* Restrict the number of decimal places requested, if necessary, so
+   that under the worst case the buffer for the result string is not
+   likely to overflow. */
+      if ( astOK ) {
+         if ( ( ndp + 11 ) > AST__SKYAXIS_DHMSFORMAT_BUFF_LEN ) ndp = AST__SKYAXIS_DHMSFORMAT_BUFF_LEN - 11;
+
+/* Some operating systems have a "minus zero" value (for instance
+   "-1.2*0" would give "-0"). This value is numerically equivalent to
+   zero, but is formated as "-0" instead of "0". The leading minus sign
+   confuses the following code, and so ensure now that all zero values
+   are the usual  "+0". */
+         if ( value == 0.0 ) value = 0.0;
+
+/* Determine if the value to be formatted is positive and obtain its
+   absolute value in radians. */
+         positive = ( value >= 0.0 );
+         absvalue = positive ? value : -value;
+
+/* Convert this to an absolute number of degrees or hours, as
+   required. */
+         fract = absvalue / ( as_time ? hr2rad : deg2rad );
+
+/* If a degrees/hours field is required, extract the whole number of
+   degrees/hours and the remaining fractional part of a
+   degree/hour. */
+         idh = 0.0;
+         if ( dh ) fract = modf( fract, &idh );
+
+/* If a minutes field is required, convert the value remaining to
+   minutes and extract the whole number of minutes and the remaining
+   fractional part of a minute. */
+         imin = 0.0;
+         if ( min ) fract = modf( fract * 60.0, &imin );
+
+/* If a seconds field is required, convert the value remaining to
+   seconds (allowing for the absence of a minutes field if necessary)
+   and extract the whole number of seconds and the remaining
+   fractional part of a second. */
+         isec = 0.0;
+         if ( sec ) {
+            if ( !min ) fract *= 60.0;
+            fract = modf( fract * 60.0, &isec );
+         }
+
+/* Round to the required number of decimal places. */
+/* ----------------------------------------------- */
+/* We must now round the fractional part (of whichever field) to the
+   required number of decimal places. Calculate the power of 10 that
+   brings the least significant digit into the units column. Scale the
+   fractional part by this factor and truncate to an integer (but
+   stored as a double to prevent possible integer overflow if the
+   number of decimal places is excessive). */
+         shift = pow( 10.0, (double) ndp );
+         ifract = floor( fract * shift );
+
+/* Next we must determine if truncation was adequate, or whether we
+   should round upwards instead. This process is more subtle than it
+   seems because if a value with a 5 as the final digit is converted
+   to radians and then back again, it may no longer end in 5 (because
+   it cannot be represented exactly in radians) and so may round
+   either up or down. If we want to recover the original (textual)
+   value, we must compare the value we are formatting not with a test
+   value whose last digit is 5, but with the closest number to this
+   that can be represented exactly in radians.
+
+   To do this, we add 0.5 to our truncated value, divide by the scale
+   factor (to get the truncated fractional part, but now with a
+   trailing digit 5 appended) and then combine this fractional part
+   with the value of all the other fields. Finally, we convert this
+   test value back into radians. */
+         test = ( 0.5 + ifract ) / shift;
+         if ( sec ) test = ( isec + test ) / 60.0;
+         if ( min ) {
+            test = ( imin + test ) / 60.0;
+         } else if ( sec ) {
+            test /= 60.0;
+         }
+         if ( dh ) test += idh;
+         test *= ( as_time ? hr2rad : deg2rad );
+
+/* We now compare the absolute value we are formatting with this test
+   value.  If it is not smaller than it, we should have rounded up
+   instead of truncating the final digit of the fractional part, so
+   increment the integer representation of the truncated fractional
+   part by 1.0 to compensate. */
+         if ( absvalue >= test ) ifract += 1.0;
+
+/* Divide by the scale factor to obtain the correctly rounded
+   fractional part.  Then check if this fractional part is 1.0. If so,
+   rounding has caused it to overflow into the units column of the
+   final field, so clear the fractional part. */
+         fract = ( ifract / shift );
+         if ( fract >= 1.0 ) {
+            ifract = 0.0;
+
+/* If a seconds field is present, propagate the overflow up through
+   each field in turn, but omitting fields which are not required. Be
+   careful about possible rounding errors when comparing integer
+   values stored as double. */
+            if ( sec ) {
+               isec += 1.0;
+               if ( ( floor( isec + 0.5 ) > 59.5 ) && min ) {
+                  isec = 0.0;
+                  imin += 1.0;
+                  if ( ( floor( imin + 0.5 ) > 59.5 ) && dh ) {
+                     imin = 0.0;
+                     idh += 1.0;
+                  }
+               }
+
+/* Omit the seconds field if it is not present. */
+            } else if ( min ) {
+               imin += 1.0;
+               if ( ( floor( imin + 0.5 ) > 59.5 ) && dh ) {
+                  imin = 0.0;
+                  idh += 1.0;
+               }
+
+/* If only the degree/hour field is present, simply increment it. */
+            } else {
+               idh += 1.0;
+            }
+         }
+
+/* Construct the result string. */
+/* ---------------------------- */
+/* We now have the value of each field and the information about how
+   they are to be formatted, so we can combine them into the required
+   string. */
+
+/* If each field is either not required or equal to zero, disregard
+   any sign. */
+         if ( !positive && ( !dh || floor( idh + 0.5 ) < 0.5 ) &&
+                           ( !min || floor( imin + 0.5 ) < 0.5 ) &&
+                           ( !sec || floor( isec + 0.5 ) < 0.5 ) &&
+                           ( floor( ifract + 0.5 ) < 0.5 ) ) {
+            positive = 1;
+         }
+
+/* Use "pos" to identify where the next character should be
+   added. Insert a leading '+' or '-' sign if required. */
+         pos = 0;
+         if ( !positive ) {
+            dhmsformat_buff[ pos++ ] = '-';
+         } else if ( plus ) {
+            dhmsformat_buff[ pos++ ] = '+';
+         }
+
+/* Use "sprintf" to format the degrees/hours field, if required. Set
+   the minimum field width according to whether padding with leading
+   zeros is required and whether the value represents hours (2 digits)
+   or degrees (3 digits). */
+         if ( dh ) {
+            pos += sprintf( dhmsformat_buff + pos, "%0*.0f",
+                            lead_zero ? ( as_time ? 2 : 3 ) : 1, idh );
+
+/* If letters are being used as field separators, and there are more
+   fields to follow, append "d" or "h" as necessary. */
+            if ( min || sec ) {
+               if ( sep == 'l' ) {
+                  dhmsformat_buff[ pos++ ] = ( as_time ? 'h' : 'd' );
+               } else if( sep == 'g' ) {
+                  astTuneC( as_time ? "hrdel":"dgdel", NULL, tbuf,
+                            sizeof( tbuf ) );
+                  term = tbuf;
+                  pos += sprintf( dhmsformat_buff + pos, "%s", term );
+               }
+            }
+         }
+
+/* If a minutes field is required, first add an appropriate non-letter
+   field separator if needed. */
+         if ( min ) {
+            if ( ( sep != 'l' && sep != 'g' ) && dh ) dhmsformat_buff[ pos++ ] = sep;
+
+/* Then format the minutes field with a leading zero to make it two
+   digits if necessary. */
+            pos += sprintf( dhmsformat_buff + pos, "%0*.0f", ( dh || lead_zero ) ? 2 : 1,
+                            imin );
+
+/* If letters are being used as field separators, and there is another
+   field to follow, append the separator. */
+            if ( sec ) {
+               if ( sep == 'l' ) {
+                  dhmsformat_buff[ pos++ ] = 'm';
+               } else if( sep == 'g' ) {
+                  astTuneC( as_time ? "mndel":"amdel", NULL, tbuf,
+                            sizeof( tbuf ) );
+                  term = tbuf;
+                  pos += sprintf( dhmsformat_buff + pos, "%s", term );
+               }
+            }
+         }
+
+/* Similarly, if a seconds field is required, first add an appropriate
+   non-letter field separator if needed. */
+         if ( sec ) {
+            if ( ( sep != 'l' && sep != 'g' ) && ( dh || min ) ) dhmsformat_buff[ pos++ ] = sep;
+
+/* Then format the seconds field with a leading zero to make it two
+   digits if necessary. */
+            pos += sprintf( dhmsformat_buff + pos, "%0*.0f",
+                            ( dh || min || lead_zero ) ? 2 : 1, isec );
+         }
+
+/* If decimal places are needed, add a decimal point followed by the
+   integer representation of the correctly rounded fractional part,
+   padded with leading zeros if necessary. */
+         if ( ndp > 0 ) {
+            dhmsformat_buff[ pos++ ] = '.';
+            pos += sprintf( dhmsformat_buff + pos, "%0*.0f", ndp, ifract );
+         }
+
+/* If letters are being used as separators, append the appropriate one
+   to the final field. */
+         if ( sep == 'l' ) {
+            dhmsformat_buff[ pos++ ] = ( sec ? 's' : ( min ? 'm' :
+                                                  ( as_time ? 'h' : 'd' ) ) );
+         } else if ( sep == 'g' ) {
+            astTuneC( as_time ? ( sec ? "scdel" : ( min ? "mndel" : "hrdel" ) ) :
+                      ( sec ? "asdel" : ( min ? "amdel" : "dgdel" ) ),
+                      NULL, tbuf, sizeof( tbuf ) );
+            term = tbuf;
+            pos += sprintf( dhmsformat_buff + pos, "%s", term );
+         }
+
+/* Terminate the result string and return a pointer to it. */
+         dhmsformat_buff[ pos ] = '\0';
+         result = dhmsformat_buff;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double DHmsGap( const char *fmt, int digs, double gap, int *ntick, int *status ) {
+/*
+*  Name:
+*     DHmsGap
+
+*  Purpose:
+*     Find a "nice" gap for formatted SkyAxis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     double DHmsGap( const char *fmt, int digs, double gap, int *ntick, int *status )
+
+*  Class Membership:
+*     SkyAxis member function.
+
+*  Description:
+*     This function returns a gap size in radians which produces a
+*     nicely spaced series of formatted SkyAxis values, the returned
+*     gap size being as close as possible to the supplied target gap
+*     size. It also returns a convenient number of divisions into
+*     which the gap can be divided.
+
+*  Parameters:
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format specifier which will be used to format the SkyAxis
+*        values.
+*     digs
+*        The default number of digits of precision to use. This is used
+*        if the given format specifier indicates the number of decimal
+*        places to use with the string ".*". In this case, the number of
+*        decimal places produced will be chosen so that the total number
+*        of digits of precision is equal to "digs".
+*     gap
+*        The target gap size.
+*     ntick
+*        Address of an int in which to return a convenient number of
+*        divisions into which the gap can be divided.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The nice gap size.
+
+*  Notes:
+*     - A value of zero is returned if the target gap size is zero.
+*     - A negative gap size is returned if the supplied gap size is
+*     negative.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Constants: */
+#define BUFF_LEN 50              /* Length of character buffer */
+
+/* Local Variables: */
+   char buff[ BUFF_LEN + 1 ];    /* Buffer for formatted scaled "nice" value */
+   char sep;                     /* Field separator character */
+   const double *table;          /* Pointer to nice gap table */
+   const int *nticks;            /* Pointer to number of subdivisions table */
+   double field_value[ 3 ];      /* Formatted field values in radians */
+   double scale;                 /* Power of ten scaling factor */
+   double scaled_table_value;    /* Scaled "nice" value to test against */
+   int as_time;                  /* Format the value as a time? */
+   int decimal;                  /* Use nice decimal gap value? */
+   int dh;                       /* Degrees/hours field required? */
+   int field;                    /* ID of most significant formatted field */
+   int i;                        /* Look-up-table index */
+   int iter;                     /* Iteration count */
+   int lead_zero;                /* Add leading zeros? */
+   int min;                      /* Minutes field required? */
+   int ndp;                      /* Number of decimal places */
+   int plus;                     /* Add leading plus sign? */
+   int positive;                 /* Value is positive (or zero)? */
+   int sec;                      /* Seconds field required? */
+
+/* Local Data: */
+/* ----------- */
+/* Table of nice decimal gaps. */
+   const double dec_table[] = { 1.0, 2.0, 5.0, 5.0, 10.0, -1.0 };
+   const int dec_nticks[] =   { 5,   4,   5,   5,   5 };
+
+/* Table of nice degrees gaps. */
+   const double deg_table[] =
+      { 1.0, 2.0, 5.0, 10.0, 30.0, 45.0, 60.0, 90.0, 180.0, 360.0, -1.0 };
+   const int deg_nticks[] =
+      { 4,   4,   5,   5,    6,    3,    6,    3,    3,     4 };
+
+/* Table of nice hours gaps. */
+   const double hr_table[] = { 1.0, 2.0, 3.0, 6.0, 12.0, 24.0, -1.0 };
+   const int hr_nticks[] =   { 4,   4,   6,   6,   4,    4 };
+
+/* Table of nice minutes or seconds gaps. */
+   const double minsec_table[] = { 1.0, 2.0, 5.0, 10.0, 30.0, 60.0, -1.0 };
+   const int minsec_nticks[] =   { 4,   4,   5,   5,    6,    4 };
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Check that the supplied gap size is not zero. */
+   if ( gap != 0.0 ) {
+
+/* Parse the format specifier. */
+      ParseDHmsFormat( fmt, digs, &sep, &plus, &lead_zero, &as_time, &dh, &min,
+                       &sec, &ndp, status );
+
+/* If OK, calculate the value of each formatted field in radians. */
+      if ( astOK ) {
+         field_value[ 0 ] = ( as_time ? hr2rad : deg2rad );
+         field_value[ 1 ] = field_value[ 0 ] / 60.0;
+         field_value[ 2 ] = field_value[ 0 ] / 3600.0;
+
+/* Determine if the suggested gap size is positive and obtain its
+   absolute value. */
+         positive = ( gap >= 0.0 );
+         if ( !positive ) gap = -gap;
+
+/* Perform two iterations to determine the optimum gap value. This is
+   because the method of choosing the gap value depends on the initial
+   value.  If a nice decimal gap is chosen on the first iteration,
+   this may round the suggested gap value downwards, making it
+   preferable to choose the gap value using a different method on the
+   second iteration. */
+         for ( iter = 0; iter < 2; iter++ ) {
+
+/* Decide which is the most significant field that the suggested gap
+   value will occupy when formatted. Also decide whether to use a
+   special "nice" gap value specific to that field, or simply to use a
+   generic nice decimal gap value. Perform all tests on the gap size
+   in radians, so as to avoid any rounding problems from conversion
+   into degrees/hours, minutes or seconds. */
+            decimal = 0;
+
+/* Suggested values exceeding one degree/hour. */
+/* ------------------------------------------- */
+            if ( gap > field_value[ 0 ] ) {
+
+/* If a degree/hour field is present, use a special gap value, unless
+   the suggested value exceeds the normal range of this field (in
+   which case use a decimal gap). */
+               if ( dh ) {
+                  field = 1;
+                  decimal = ( gap > ( field_value[ 0 ] *
+                                      ( as_time ? 24.0 : 360.0 ) ) );
+
+/* If the most significant field is not degrees/hours, then its normal
+   range will be exceeded, so use a decimal gap. */
+               } else if ( min ) {
+                  field = 2;
+                  decimal = 1;
+               } else {
+                  field = 3;
+                  decimal = 1;
+               }
+
+/* Suggested values exceeding one minute. */
+/* -------------------------------------- */
+            } else if ( gap > field_value[ 1 ] ) {
+
+/* If a minutes field is present, the suggested value will lie within
+   its normal range, so use a special gap value. */
+               if ( min ) {
+                  field = 2;
+
+/* Otherwise, if the most significant field is seconds, its normal
+   range will be exceeded, so use a decimal gap value. */
+               } else if ( sec ) {
+                  field = 3;
+                  decimal = 1;
+
+/* If only a degrees/hours field is present, then only digits after
+   the decimal point can be affected, so use a decimal gap value. */
+               } else {
+                  field = 1;
+                  decimal = 1;
+               }
+
+/* Suggested values exceeding one second. */
+/* -------------------------------------- */
+            } else if ( gap > field_value[ 2 ] ) {
+
+/* If a seconds field is present, the suggested value will lie within
+   its normal range, so use a special gap value. */
+               if ( sec ) {
+                  field = 3;
+
+/* If the least significant field is degrees/hours or minutes, then
+   only digits after the decimal point can be affected, so use a
+   decimal gap value. */
+               } else if ( min ) {
+                  field = 2;
+                  decimal = 1;
+               } else {
+                  field = 1;
+                  decimal = 1;
+               }
+
+/* Suggested values less than one second. */
+/* -------------------------------------- */
+            } else {
+
+/* Only digits after the decimal point can be affected, so decide
+   which is the least significant field present and use a decimal
+   gap. */
+               if ( sec ) {
+                  field = 3;
+               } else if ( min ) {
+                  field = 2;
+               } else {
+                  field = 1;
+               }
+               decimal = 1;
+            }
+
+/* If a decimal gap value is required, select the appropriate table of
+   gap values and numbers of subdivisions. */
+            if ( decimal ) {
+               table = dec_table;
+               nticks = dec_nticks;
+
+/* Find a power of ten divisor which scales the suggested value (when
+   formatted) into the range 1.0 to 10.0. */
+               scale = pow( 10.0,
+                            floor( log10( gap / field_value[ field - 1 ] ) ) );
+
+/* Look the scaled value up in the table, comparing values in radians
+   to avoid rounding problems due to conversion to/from
+   degrees/radians, etc. */
+               for ( i = 0; table[ i + 1 ] > 0.0; i++ ) {
+
+/* We must be careful about rounding errors here. If, for example, we
+   read in a value of 0.15 as the suggested gap value, the scaled
+   "nice" value we would be comparing it with would be 0.1 times the
+   values in the nice values table. The relevant value in this table
+   is 1.5 (i.e. 0.5 * ( 1.0 + 2.0 ) ), so we would compute 0.1 * 1.5
+   as the test value. However, this is probably not equal (to machine
+   precision) to the number that results when a formatted value of
+   0.15 is read, because 0.1 isn't exactly representable. Since it is
+   the formatted appearance of the numbers which matters, we want a
+   new scaled nice table containing the numbers that result from
+   reading the formatted values 0.1, 0.2, etc. To achieve this effect,
+   we format the scaled table value using the default floating point
+   precision (which rounds to a relatively small number of decimal
+   digits) and then read the value back again. */
+                  (void ) sprintf( buff, "%g", scale *
+                                   0.5 * ( table[ i ] + table[ i + 1 ] ) );
+                  (void) astSscanf( buff, "%lf", &scaled_table_value );
+
+/* Now test the suggested gap value against the scaled table value. */
+                  if ( gap < ( field_value[ field - 1 ] *
+                               scaled_table_value ) ) break;
+               }
+
+/* Return the nice gap value and the number of subdivisions. */
+               gap = scale * field_value[ field - 1 ] * table[ i ];
+               if ( ntick ) *ntick = nticks[ i ];
+
+/* If a special gap value appropriate to the field is required, then
+   select the table of gap values and numbers of subdivisions
+   according to which field we are considering and whether it contains
+   degrees or hours. */
+            } else {
+               if ( field == 1 ) {
+                  if ( as_time ) {
+                     table = hr_table;
+                     nticks = hr_nticks;
+                  } else {
+                     table = deg_table;
+                     nticks = deg_nticks;
+                  }
+               } else {
+                  table = minsec_table;
+                  nticks = minsec_nticks;
+               }
+
+/* Search the table for a suitable gap. We do not need to format and
+   unformat the test value here (as we did above) because the table
+   values are being used literally and not being scaled. */
+               for ( i = 0; table[ i + 1 ] > 0.0; i++ ) {
+                  if ( gap < ( field_value[ field - 1 ] *
+                               0.5 * ( table[ i ] + table[ i + 1 ] ) ) ) break;
+               }
+
+/* Return the nice gap value and the number of subdivisions. */
+               gap = field_value[ field - 1 ] * table[ i ];
+               if ( ntick ) *ntick = nticks[ i ];
+            }
+         }
+
+/* After iterations are complete, restore the original sign. */
+         if ( !positive ) gap = -gap;
+      }
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) gap = 0.0;
+
+/* Return the result. */
+   return gap;
+
+/* Undefine macros local to this function */
+#undef BUFF_LEN
+}
+
+static const char *DHmsUnit( const char *fmt, int digs, int output, int *status ) {
+/*
+*  Name:
+*     DHmsUnit
+
+*  Purpose:
+*     Generate a unit string to describe a formatted angle or time.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     const char *DHmsUnit( const char *fmt, int digs, int output, int *status )
+
+*  Class Membership:
+*     SkyAxis member function.
+
+*  Description:
+*     This function generates a string that may be used to describe
+*     either (a) the units of an angle or time that has been formatted
+*     for output using the DHmsFormat function, or (b) a suitable
+*     format to be used for an angle or time that is to be supplied as
+*     an input coordinate value.
+
+*  Parameters:
+*     fmt
+*        Pointer to a null terminated string containing the format
+*        specifier used to format coordinate values. For details of
+*        the syntax of this string, see the DHmsFormat function.
+*     digs
+*        The default number of digits of precision to use. This is used
+*        if the given format specifier indicates the number of decimal
+*        places to use with the string ".*". In this case, the number of
+*        decimal places produced will be chosen so that the total number
+*        of digits of precision is equal to "digs".
+*     output
+*        If non-zero, the returned string will be in a form suitable
+*        for describing the units/format of output produced using
+*        DHmsFormat.
+*
+*        If zero, the returned string will be in a form suitable for
+*        describing a suggested input format, which will subsequently
+*        be read using AxisUnformat.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a null terminated string containing the unit description.
+
+*  Notes:
+*     - The result string may be stored in static memory. Its contents
+*     may be over-written or the returned pointer may become invalid
+*     following a further invocation of this function. A copy of the
+*     string should therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   char dpchar;                  /* Character to indicate decimal places */
+   char sep;                     /* Field separator character */
+   const char *result;           /* Pointer to result string */
+   const int maxdp = 6;          /* Maximum number of decimal places to show */
+   int as_time;                  /* Value formatted as a time? */
+   int dh;                       /* Degrees/hours field required? */
+   int dp;                       /* Loop counter for decimal places */
+   int lead_zero;                /* Add leading zeros? */
+   int min;                      /* Minutes field required? */
+   int ndp;                      /* Number of decimal places */
+   int plus;                     /* Leading plus sign required? */
+   int pos;                      /* Position to add next character */
+   int sec;                      /* Seconds field required? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Parse the format specifier. */
+   ParseDHmsFormat( fmt, digs, &sep, &plus, &lead_zero, &as_time, &dh, &min,
+                    &sec, &ndp, status );
+
+/* If the units string is required to describe formatted output and
+   the field separators are letters (e.g. giving "01h23m45s" or
+   "012d34m56s"), then the units will already be clear so return a
+   pointer to an empty units string. */
+   if ( astOK ) {
+      if ( output && ( sep == 'l' || sep == 'g' ) ) {
+         result = "";
+
+/* Otherwise, if the units string is required to describe formatted
+   output and there is only one field present, then select an
+   appropriate string. */
+      } else if ( output && dh && !min && !sec ) {
+         result = as_time ? "hours" : "degrees";
+
+      } else if ( output && !dh && min && !sec ) {
+         result = as_time ? "minutes of time" : "arcminutes";
+
+      } else if ( output && !dh && !min && sec ) {
+         result = as_time ? "seconds of time" : "arcseconds";
+
+/* If there is more than one field present, or we want to describe how
+   to supply formatted input, then we will generate a units string of
+   the general form "ddd:mm:ss.sss" or "hh:mm:ss.s" or
+   similar. Initialise the output character count and the character to
+   be used to represent decimal places. */
+      } else {
+         pos = 0;
+         dpchar = 'd';
+
+/* Decide which field separator to use (use a space if letters were
+   requested since it is easier to input). */
+         if ( sep == 'l' || sep == 'g' ) sep = ' ';
+
+/* Start with the "ddd" or "hh" field, if required, and update the
+   decimal places character appropriately. */
+         if ( dh ) {
+            pos += sprintf( dhmsunit_buff, "%s", as_time ? "hh" : "ddd" );
+            dpchar = as_time ? 'h' : 'd';
+         }
+
+/* If a minutes field is present, add a separator if necessary and
+   "mm" and update the decimal places character. */
+         if ( min ) {
+            if ( dh ) dhmsunit_buff[ pos++ ] = sep;
+            dhmsunit_buff[ pos++ ] = 'm';
+            dhmsunit_buff[ pos++ ] = 'm';
+            dpchar = 'm';
+         }
+
+/* Repeat this process for the seconds field, if present. */
+         if ( sec ) {
+            if ( dh || min ) dhmsunit_buff[ pos++ ] = sep;
+            dhmsunit_buff[ pos++ ] = 's';
+            dhmsunit_buff[ pos++ ] = 's';
+            dpchar = 's';
+         }
+
+/* If decimal places are present, add a decimal point and then loop to
+   add further instances of the decimal places character to represent
+   the digits that follow. */
+         if ( ndp > 0 ) {
+            dhmsunit_buff[ pos++ ] = '.';
+            for ( dp = 0; dp < ndp; dp++ ) {
+               if ( dp < maxdp ) {
+                  dhmsunit_buff[ pos++ ] = dpchar;
+
+/* After showing the maximum number of decimal places, simply add an
+   ellipsis and quit (otherwise the result gets boring to look at). */
+               } else {
+                  dhmsunit_buff[ pos - 1 ] = '.';
+                  dhmsunit_buff[ pos - 2 ] = '.';
+                  dhmsunit_buff[ pos - 3 ] = '.';
+                  break;
+               }
+	    }
+         }
+
+/* Terminate the result string and return a pointer to it. */
+         dhmsunit_buff[ pos ] = '\0';
+         result = dhmsunit_buff;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied SkyAxis,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;         /* Pointer to SkyAxis structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astTSizeOf( this->skyformat );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the protected astGetAttrib
+*     method inherited from the Axis class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a SkyAxis, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the SkyAxis, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the SkyAxis. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   const char *result;           /* Pointer value to return */
+   int as_time;                  /* AsTime attribute value */
+   int centrezero;               /* CentreZero attribute value */
+   int is_latitude;              /* IsLatitude attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* AsTime. */
+/* ------- */
+   if ( !strcmp( attrib, "astime" ) ) {
+      as_time = astGetAxisAsTime( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", as_time );
+         result = getattrib_buff;
+      }
+
+/* IsLatitude. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "islatitude" ) ) {
+      is_latitude = astGetAxisIsLatitude( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", is_latitude );
+         result = getattrib_buff;
+      }
+
+/* CentreZero. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "centrezero" ) ) {
+      centrezero= astGetAxisCentreZero( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", centrezero );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetAxisBottom( AstAxis *this_axis, int *status ) {
+/*
+*  Name:
+*     GetAxisBottom
+
+*  Purpose:
+*     Obtain the value of the Bottom attribute for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     double GetAxisBottom( AstAxis *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astGetAxisBottom method
+*     inherited from the Axis class).
+
+*  Description:
+*     This function returns a value for the Bottom attribute of a SkyAxis.
+*     This attribute indicates the lowest legal value for the axis.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The atribute value. A suitable default value is supplied if necessary.
+
+*  Notes:
+*     -  A value of -DBL_MAX will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables. */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   double result;                /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return -DBL_MAX;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Check if a value has been set for the Bottom attribute. If so, obtain
+   this value. */
+   if ( astTestAxisBottom( this ) ) {
+      result = (*parent_getaxisbottom)( this_axis, status );
+
+/* Otherwise, supply a default of -pi/2 for latitude axes, and -DBL_MAX
+   for longitude axes. */
+   } else {
+      result = astGetAxisIsLatitude( this ) ? -piby2 : -DBL_MAX;
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = -DBL_MAX;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetAxisInternalUnit( AstAxis *this, int *status ){
+/*
+*  Name:
+*     GetAxisInternalUnit
+
+*  Purpose:
+*     Return the unit string for unformatted Axis values
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     const char *GetAxisInternalUnit( AstAxis *this )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astGetAxisInternalUnit method
+*     inherited from the Axis class).
+
+*  Description:
+*     This function returns the axis InternalUnit attribute. For sky
+*     axes, the InternalUnit is always "rad" (radians).
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the internal
+*     unit string.
+
+*  Notes:
+*     - The returned pointer points to a static memory buffer. The
+*     contents of this buffer will be over-written on each invocation of
+*     this function. A copy of the returned string should therefore be
+*     taken if it will be needed later.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+   return "rad";
+}
+
+static double GetAxisTop( AstAxis *this_axis, int *status ) {
+/*
+*  Name:
+*     GetAxisTop
+
+*  Purpose:
+*     Obtain the value of the Top attribute for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     double GetAxisTop( AstAxis *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astGetAxisTop method
+*     inherited from the Axis class).
+
+*  Description:
+*     This function returns a value for the Top attribute of a SkyAxis.
+*     This attribute indicates the highest legal value for the axis.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The atribute value. A suitable default value is supplied if necessary.
+
+*  Notes:
+*     -  A value of DBL_MAX will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables. */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   double result;                /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return DBL_MAX;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Check if a value has been set for the Top attribute. If so, obtain
+   this value. */
+   if ( astTestAxisTop( this ) ) {
+      result = (*parent_getaxistop)( this_axis, status );
+
+/* Otherwise, supply a default of pi/2 for latitude axes, and DBL_MAX
+   for longitude axes. */
+   } else {
+      result = astGetAxisIsLatitude( this ) ? piby2 : DBL_MAX;
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = DBL_MAX;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetAxisDirection( AstAxis *this_axis, int *status ) {
+/*
+*  Name:
+*     GetAxisDirection
+
+*  Purpose:
+*     Obtain the value of the Direction attribute for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     int GetAxisDirection( AstAxis *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astGetAxisDirection method
+*     inherited from the Axis class).
+
+*  Description:
+*     This function returns a value for the Direction attribute of a SkyAxis.
+*     This attribute indicates in which direction the SkyAxis's values should
+*     increase when represented on a graph (1 for the conventional direction,
+*     0 for reverse direction).
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero or one, according to the attribute setting. A suitable default
+*     value is supplied if necessary.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables. */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Check if a value has been set for the Direction attribute. If so, obtain
+   this value. */
+   if ( astTestAxisDirection( this ) ) {
+      result = (*parent_getaxisdirection)( this_axis, status );
+
+/* Otherwise, supply a default of 1 unless the SkyAxis values are being
+   formatted as times (instead of angles) by default. */
+   } else {
+      result = !astGetAxisAsTime( this );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetAxisFormat( AstAxis *this_axis, int *status ) {
+/*
+*  Name:
+*     GetAxisFormat
+
+*  Purpose:
+*     Obtain a pointer to the Format attribute for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     const char *GetAxisFormat( AstAxis *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astGetAxisFormat method inherited
+*     from the Axis class).
+
+*  Description:
+*     This function returns a pointer to the Format attribute associated with
+*     a SkyAxis and provides a suitable default if necessary. This string
+*     attribute contains the format specifier that will be interpreted by the
+*     astAxisFormat method when formatting a value for the SkyAxis. The default
+*     Format may depend on other attribute settings, in particular on the
+*     Digits and AsTime attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Format string (null terminated).
+
+*  Notes:
+*     -  The pointer returned may point at memory allocated within the SkyAxis
+*     object, or at static memory. The contents of the string may be
+*     over-written or the pointer may become invalid following a further
+*     invocation of the same function, deletion of the SkyAxis, or assignment
+*     of a new Format value. A copy of the string should therefore be made if
+*     necessary.
+*     -  This function will return a NULL pointer if it is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   const char *result;           /* Pointer to result string */
+   int as_time;                  /* Format SkyAxis values as times? */
+   int digits;                   /* Number of digits of precision */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_axis);
+
+/* Initialise. */
+   result = NULL;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Obtain a pointer to the Format string stored in the SkyAxis structure. Note
+   we do not use a method to obtain this, because we want a string with a
+   syntax appropriate to this class, and derived classes may have extended the
+   syntax. */
+   result = this->skyformat;
+
+/* If no Format string has been set, we must generate a default one. Determine
+   how many digits of precision are to be used by default and whether the
+   SkyAxis values are to be formatted as times (instead of angles). */
+   if ( !result ) {
+      digits = astGetAxisDigits( this );
+      as_time = astGetAxisAsTime( this );
+      if ( astOK ) {
+
+/* If formatting values as times, use the number of digits to select an
+   appropriate Format string and obtain a pointer to it. */
+         if ( as_time ) {
+            if ( digits <= 2 ) {
+               result = "h";
+	    } else if ( digits == 3 ) {
+               result = "hm";
+	    } else if ( digits == 4 ) {
+               result = "hm";
+	    } else if ( digits == 5 ) {
+               result = "hms";
+	    } else if ( digits == 6 ) {
+               result = "hms";
+
+/* Construct the Format string in a buffer if necessary. */
+            } else {
+               (void) sprintf( getaxisformat_buff, "hms.%d", digits - 6 );
+               result = getaxisformat_buff;
+   	    }
+
+/* Similarly, select a Format for expressing an angle if necessary. */
+         } else {
+            if ( digits <= 3 ) {
+               result = "d";
+   	    } else if ( digits == 4 ) {
+               result = "dm";
+   	    } else if ( digits == 5 ) {
+               result = "dm";
+   	    } else if ( digits == 6 ) {
+               result = "dms";
+   	    } else if ( digits == 7 ) {
+               result = "dms";
+            } else {
+               (void) sprintf( getaxisformat_buff, "dms.%d", digits - 7 );
+               result = getaxisformat_buff;
+   	    }
+	 }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetAxisLabel( AstAxis *this_axis, int *status ) {
+/*
+*  Name:
+*     GetAxisLabel
+
+*  Purpose:
+*     Obtain a pointer to the Label attribute for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     const char *GetAxisLabel( AstAxis *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astGetAxisLabel method inherited
+*     from the Axis class).
+
+*  Description:
+*     This function returns a pointer to the Label attribute associated with
+*     a SkyAxis and provides a suitable default if necessary. This string
+*     attribute specifies the label to be attached to the SkyAxis when it is
+*     represented in (e.g.) a graph. It is intended purely for interpretation
+*     by human readers and not by software.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Label string (null terminated).
+
+*  Notes:
+*     -  The pointer returned may point at memory allocated within the SkyAxis
+*     object, or at static memory. The contents of the string may be
+*     over-written or the pointer may become invalid following a further
+*     invocation of the same function, deletion of the SkyAxis, or assignment
+*     of a new Label value. A copy of the string should therefore be made if
+*     necessary.
+*     -  This function will return a NULL pointer if it is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   const char *result;           /* Pointer value to be returned */
+   int as_time;                  /* SkyAxis values formatted as times? */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise. */
+   result = NULL;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Test if the Label attribute is set. If so, use the parent astGetAxisLabel
+   method to get a pointer to it. */
+   if ( astTestAxisLabel( this ) ) {
+      result = (*parent_getaxislabel)( this_axis, status );
+
+/* Otherwise, return a pointer to a suitable default string, using the result
+   of the astGetAxisAsTime method to determine whether a string describing
+   time or angle is more appropriate. */
+   } else {
+      as_time = astGetAxisAsTime( this );
+      if ( !astTestAxisIsLatitude( this ) ) {
+         result = as_time ? "Angle on sky expressed as time" :
+                            "Angle on sky";
+      } else if ( astGetAxisIsLatitude( this ) ) {
+         result = as_time ? "Sky latitude expressed as time" :
+                            "Sky latitude";
+      } else {
+         result = as_time ? "Sky longitude expressed as time" :
+                            "Sky longitude";
+      }
+   }
+
+/* If an error occurred, clear the result pointer. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetAxisSymbol( AstAxis *this_axis, int *status ) {
+/*
+*  Name:
+*     GetAxisSymbol
+
+*  Purpose:
+*     Obtain a pointer to the Symbol attribute for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     const char *GetAxisSymbol( AstAxis *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astGetAxisSymbol method inherited
+*     from the Axis class).
+
+*  Description:
+*     This function returns a pointer to the Symbol attribute associated with
+*     a SkyAxis and provides a suitable default if necessary. This string
+*     attribute specifies the symbol to be used to represent coordinate values
+*     for the SkyAxis in "short form", such as in algebraic expressions where a
+*     full description would be inappropriate.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Symbol string (null terminated).
+
+*  Notes:
+*     -  The pointer returned may point at memory allocated within the SkyAxis
+*     object, or at static memory. The contents of the string may be
+*     over-written or the pointer may become invalid following a further
+*     invocation of the same function, deletion of the SkyAxis, or assignment
+*     of a new Symbol value. A copy of the string should therefore be made if
+*     necessary.
+*     -  This function will return a NULL pointer if it is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   const char *result;           /* Pointer value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise. */
+   result = NULL;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Test if the Symbol attribute is set. If so, use the parent astGetAxisSymbol
+   method to get a pointer to it. */
+   if ( astTestAxisSymbol( this ) ) {
+      result = (*parent_getaxissymbol)( this_axis, status );
+
+/* If a value has been set for the IsLatitude attribute, use it to decide
+   whether to use "delta" (for latitude) or "alpha" (for longitude). */
+   } else if ( astTestAxisIsLatitude( this ) ) {
+      result = astGetAxisIsLatitude( this ) ? "delta" : "alpha";
+
+/* Otherwise, use the AsTime attribute to decide whether the SkyAxis is
+   likely to be a longitude or latitude axis (the former usually having values
+   formatted as times). */
+   } else {
+      result = astGetAxisAsTime( this ) ? "alpha" : "delta";
+   }
+
+/* If an error occurred, clear the result pointer. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetAxisUnit( AstAxis *this_axis, int *status ) {
+/*
+*  Name:
+*     GetAxisUnit
+
+*  Purpose:
+*     Obtain a pointer to the Unit attribute for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     const char *GetAxisUnit( AstAxis *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astGetAxisUnit method inherited
+*     from the Axis class).
+
+*  Description:
+*     This function returns a pointer to the Unit attribute associated with
+*     a SkyAxis and provides a suitable default if necessary. This string
+*     attribute describes the unit used to represent formatted coordinate
+*     values on the SkyAxis.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Unit string (null terminated).
+
+*  Notes:
+*     -  The pointer returned may point at memory allocated within the SkyAxis
+*     object, or at static memory. The contents of the string may be
+*     over-written or the pointer may become invalid following a further
+*     invocation of the same function, deletion of the SkyAxis, or assignment
+*     of a new Unit value. A copy of the string should therefore be made if
+*     necessary.
+*     -  This function will return a NULL pointer if it is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   const char *fmt;              /* Pointer to format specifier */
+   const char *result;           /* Pointer to result string */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise */
+   result = NULL;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Test if the Unit attribute is set. If so, invoke the parent astGetAxisUnit
+   method to obtain a pointer to it. */
+   if ( astTestAxisUnit( this ) ) {
+      result = (*parent_getaxisunit)( this_axis, status );
+
+/* If we must provide a default, obtain a pointer to the format specifier used
+   to format SkyAxis values. Use a private member function (not a method) to
+   access this, in case derived classes have extended the syntax of this
+   string. */
+   } else {
+      fmt = GetAxisFormat( this_axis, status );
+
+/* If the format string starts with a percent, use "rad" as the default units
+   string. Otherwise, use the format specifier to generate a matching
+   default Unit string and obtain a pointer to it. */
+      if ( astOK ) {
+         if( fmt[ 0 ] == '%' ) {
+            result = "rad";
+         } else {
+            result = DHmsUnit( fmt, astGetAxisDigits( this_axis ), 1, status );
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astInitSkyAxisVtab_(  AstSkyAxisVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSkyAxisVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a SkyAxis.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     void astInitSkyAxisVtab( AstSkyAxisVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SkyAxis vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the SkyAxis class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstAxisVtab *axis;            /* Pointer to Axis component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   int stat;                     /* SLALIB status */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitAxisVtab( (AstAxisVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsASkyAxis) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstAxisVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->ClearAxisAsTime = ClearAxisAsTime;
+   vtab->ClearAxisIsLatitude = ClearAxisIsLatitude;
+   vtab->ClearAxisCentreZero = ClearAxisCentreZero;
+   vtab->GetAxisAsTime = GetAxisAsTime;
+   vtab->GetAxisIsLatitude = GetAxisIsLatitude;
+   vtab->GetAxisCentreZero = GetAxisCentreZero;
+   vtab->SetAxisAsTime = SetAxisAsTime;
+   vtab->SetAxisIsLatitude = SetAxisIsLatitude;
+   vtab->SetAxisCentreZero = SetAxisCentreZero;
+   vtab->TestAxisAsTime = TestAxisAsTime;
+   vtab->TestAxisIsLatitude = TestAxisIsLatitude;
+   vtab->TestAxisCentreZero = TestAxisCentreZero;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   axis = (AstAxisVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_axisoverlay = axis->AxisOverlay;
+   axis->AxisOverlay = AxisOverlay;
+   parent_getaxisdirection = axis->GetAxisDirection;
+   axis->GetAxisDirection = GetAxisDirection;
+   parent_getaxislabel = axis->GetAxisLabel;
+   axis->GetAxisLabel = GetAxisLabel;
+   parent_getaxissymbol = axis->GetAxisSymbol;
+   axis->GetAxisSymbol = GetAxisSymbol;
+   parent_getaxisunit = axis->GetAxisUnit;
+   axis->GetAxisUnit = GetAxisUnit;
+
+   parent_getaxistop = axis->GetAxisTop;
+   axis->GetAxisTop = GetAxisTop;
+
+   parent_getaxisbottom = axis->GetAxisBottom;
+   axis->GetAxisBottom = GetAxisBottom;
+
+   parent_axisformat = axis->AxisFormat;
+   axis->AxisFormat = AxisFormat;
+
+   parent_axisunformat = axis->AxisUnformat;
+   axis->AxisUnformat = AxisUnformat;
+
+   parent_axisgap = axis->AxisGap;
+   axis->AxisGap = AxisGap;
+
+   parent_axisfields = axis->AxisFields;
+   axis->AxisFields = AxisFields;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   axis->AxisAbbrev = AxisAbbrev;
+   axis->AxisIn = AxisIn;
+   axis->AxisDistance = AxisDistance;
+   axis->AxisOffset = AxisOffset;
+   axis->AxisNorm = AxisNorm;
+   axis->AxisNormValues = AxisNormValues;
+   axis->ClearAxisFormat = ClearAxisFormat;
+   axis->GetAxisFormat = GetAxisFormat;
+   axis->SetAxisFormat = SetAxisFormat;
+   axis->TestAxisFormat = TestAxisFormat;
+   axis->GetAxisInternalUnit = GetAxisInternalUnit;
+   axis->TestAxisInternalUnit = TestAxisInternalUnit;
+
+/* Declare the destructor, copy constructor and dump function. */
+   astSetDelete( vtab, Delete );
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "SkyAxis", "Celestial coordinate axis" );
+
+/* Initialize constants for converting between hours, degrees and radians. */
+   LOCK_MUTEX2
+   palDtf2r( 1, 0, 0.0, &hr2rad, &stat );
+   palDaf2r( 1, 0, 0.0, &deg2rad, &stat );
+   palDaf2r( 180, 0, 0.0, &pi, &stat );
+   piby2 = 0.5*pi;
+   UNLOCK_MUTEX2
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void ParseDHmsFormat( const char *fmt, int digs, char *sep, int *plus,
+                             int *lead_zero, int *as_time, int *dh, int *min,
+                             int *sec, int *ndp, int *status ) {
+/*
+*  Name:
+*     ParseDHmsFormat
+
+*  Purpose:
+*     Parse a format specifier for degrees/hours, minutes and seconds.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     void ParseDHmsFormat( const char *fmt, int digs, char *sep, int *plus,
+*                           int *lead_zero, int *as_time, int *dh, int *min,
+*                           int *sec, int *ndp, int *status )
+
+*  Class Membership:
+*     SkyAxis member function.
+
+*  Description:
+*     This function parses a SkyAxis format specifier which describes
+*     how to convert an angle in radians into a text string with
+*     separate fields for degrees/hours, minutes and seconds.
+
+*  Parameters:
+*     fmt
+*        Pointer to a null terminated string containing the format
+*        specifier.  For details of the syntax of this string, see the
+*        DHmsFormat function.
+*     digs
+*        The default number of digits of precision to use. This is used
+*        if the given format specifier indicates the number of decimal
+*        places to use with the string ".*". In this case, the returned
+*        value for "ndp" will be set to produce the number of digits of
+*        precision given by "digs".
+*     sep
+*        Pointer to a location in which a single character will be
+*        returned to indicate which separator should be used to
+*        separate the fields.  The returned value will be one of ' '
+*        (use a blank as the separator), ':' (use a colon as the
+*        separator) or 'l' (use one of the letters "hdms" as
+*        appropriate)  or 'g' (use one of the letters "hdms" but
+*        include suitable escape sequences to allow the Plot class to draw
+*        the letter as a small super-script).
+*     plus
+*        Pointer to an int in which a boolean value will be returned
+*        to indicate if a plus sign should be prefixed to positive
+*        values.
+*     lead_zero
+*        Pointer to an int in which a boolean value will be returned
+*        to indicate if leading zeros should be prefixed to the value
+*        so that the first field is always of constant (maximum)
+*        width, as would be required in a fixed-width table. Leading
+*        zeros are always prefixed to any fields that follow.
+*     as_time
+*        Pointer to an int in which a boolean value will be returned
+*        to indicate whether the value is to be formatted as a time
+*        (e.g. in hours) rather than as an angle (in degrees).
+*     dh
+*        Pointer to an int in which a boolean value will be returned
+*        to indicate whether a degrees or hours field is required.
+*     min
+*        Pointer to an int in which a boolean value will be returned
+*        to indicate whether a minutes field is required.
+*     sec
+*        Pointer to an int in which a boolean value will be returned
+*        to indicate whether a seconds field is required.
+*     ndp
+*        Pointer to an int in which to return the number of digits
+*        required following the decimal point in the final field. A
+*        value of zero indicates that the decimal point should be
+*        omitted. See parameter "digs".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Acknowledgements:
+*     - This function is a close approximation to a Fortran 77 routine
+*     written by Clive Davenhall which implements the system of format
+*     specifiers for angles described in his document on the CAT
+*     catalogue access library (Starlink User Note 181). It supports
+*     the same format specifiers.
+*/
+
+/* Local Variables: */
+   int decpos;                   /* Offset of decimal point */
+   int i;                        /* Loop counter for format characters */
+   int ndpval;                   /* Number of decimal places required */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise. */
+   *as_time = -1;
+   *lead_zero = 0;
+   *dh = 0;
+   *min = 0;
+   *ndp = 0;
+   *plus = 0;
+   *sec = 0;
+   *sep = ':';
+   decpos = -1;
+
+/* Loop to inspect and classify each character. */
+   for ( i = 0; fmt[ i ]; i++ ) {
+      switch ( fmt[ i ] ) {
+
+/* Note if a '+' sign is needed. */
+         case '+':
+            *plus = 1;
+            break;
+
+/* Note if leading zeros are needed. */
+         case 'Z': case 'z':
+            *lead_zero = 1;
+            break;
+
+/* Set the required separator. Note we only use graphical separators if
+   astEscapes indicates that escape sequences are currently being used. */
+         case 'I': case 'i':
+            *sep = ':';
+            break;
+         case 'B': case 'b':
+            *sep = ' ';
+            break;
+         case 'L': case 'l':
+            *sep = 'l';
+            break;
+         case 'G': case 'g':
+            *sep = astEscapes( -1 ) ? 'g' : 'l';
+            break;
+
+/* Note if the value is to be formatted as a time (but not if a
+   degrees or hours field has already been specified). */
+         case 'T': case 't':
+            if ( *as_time == -1 ) *as_time = 1;
+            break;
+
+/* Note if a degrees or hours field is required (and hence whether the
+   value is to be formatted as a time or an angle). */
+         case 'H': case 'h':
+            *dh = 1;
+            *as_time = 1;
+            break;
+         case 'D': case 'd':
+            *dh = 1;
+            *as_time = 0;
+            break;
+
+/* Note if a minutes field is required. */
+         case 'M': case 'm':
+            *min = 1;
+            break;
+
+/* Note if a seconds field is required. */
+         case 'S': case 's':
+            *sec = 1;
+            break;
+
+/* Note if decimal places are required. */
+	 case '.':
+            decpos = i;
+      }
+   }
+
+/* Format the value as an angle by default. */
+   if ( *as_time == -1 ) *as_time = 0;
+
+/* Use degrees (or hours) as the default field. */
+   if ( !*min && !*sec ) *dh = 1;
+
+/* Omit the seconds field if the degrees/hours field is present but
+   the minutes field is not. */
+   if ( *dh && !*min ) *sec = 0;
+
+/* Determine the default number of decimal places following the final field.
+   This is the number which will be used if the format specifier does not
+   indicate how many decimal places should be produced. It is shosen to
+   produce the requested total number of digits of precision. */
+
+/* If decimal places are required, attempt to read the integer value
+   following the decimal point which specifies how many. If successful,
+   and a valid (positive or zero) result was obtained, note its value. If
+   an asterisk follows the decimal point, use a value determined by the
+   supplied "digs" value. */
+   if ( ( decpos >= 0 ) && ( decpos < ( i - 1 ) ) ) {
+
+      if ( astSscanf( fmt + decpos + 1, "%d", &ndpval ) == 1 ) {
+         if ( ndpval >= 0 ) *ndp = ndpval;
+
+      } else if ( fmt[ decpos + 1 ] == '*' ) {
+         *ndp = digs;
+         if( *as_time ) {
+            *ndp = ( digs > 2 ) ? digs : 2;
+            if( *dh ) *ndp -= 2;
+         } else {
+            *ndp = ( digs > 3 ) ? digs : 3;
+            if( *dh ) *ndp -= 3;
+         }
+         if( *min ) *ndp -= 2;
+         if( *sec ) *ndp -= 2;
+         if( *ndp < 0 ) *ndp = 0;
+      }
+   }
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astSetAttrib method
+*     inherited from the Axis class).
+
+*  Description:
+*     This function assigns an attribute value for a SkyAxis, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     setting
+*        Pointer to a null terminated string specifying the new
+*        attribute value.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   int as_time;                  /* Format values as times? */
+   int centrezero;               /* SkyAxis range centred on zero? */
+   int is_latitude;              /* SkyAxis is a latitude axis? */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* AsTime. */
+/* ------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "astime= %d %n", &as_time, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisAsTime( this, as_time );
+
+/* IsLatitude. */
+/* ----------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "islatitude= %d %n", &is_latitude, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisIsLatitude( this, is_latitude );
+
+/* CentreZero. */
+/* ----------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "centrezero= %d %n", &centrezero, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAxisCentreZero( this, centrezero );
+
+/* Pass any unrecognised attribute setting to the parent method for further
+   interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SetAxisFormat( AstAxis *this_axis, const char *format, int *status ) {
+/*
+*  Name:
+*     SetAxisFormat
+
+*  Purpose:
+*     Set a value for the Format attribute of a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     void SetAxisFormat( AstAxis *this, const char *format )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astSetAxisFormat method inherited
+*     from the Axis class).
+
+*  Description:
+*     This function sets a new value for the Format attribute of a SkyAxis.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     format
+*        Pointer to a null terminated string containing the new Format value.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  For details of the syntax of the Format string, see the DHmsFormat
+*     function.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* Store a pointer to a copy of the Format string in the SkyAxis structure. */
+   this->skyformat = astStore( this->skyformat, format,
+                               strlen( format ) + (size_t) 1 );
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astTestAttrib protected
+*     method inherited from the Axis class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate
+*     whether a value has been set for one of a SkyAxis' attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* AsTime. */
+/* ------- */
+   if ( !strcmp( attrib, "astime" ) ) {
+      result = astTestAxisAsTime( this );
+
+/* IsLatitude. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "islatitude" ) ) {
+      result = astTestAxisIsLatitude( this );
+
+/* CentreZero. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "centrezero" ) ) {
+      result = astTestAxisCentreZero( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static int TestAxisFormat( AstAxis *this_axis, int *status ) {
+/*
+*  Name:
+*     TestAxisFormat
+
+*  Purpose:
+*     Test if a value has been set for the Format attribute of a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     int TestAxisFormat( AstAxis *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astTestAxisFormat method
+*     inherited from the Axis class).
+
+*  Description:
+*     This function returns 0 or 1 to indicate whether a value has been set
+*     for the Format attribute of a SkyAxis.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if no Format value has been set, otherwise one.
+
+*  Notes:
+*     -  This function will return a value of zero if it is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_axis;
+
+/* The Format string has been set if the pointer to it is not NULL. */
+   result = ( this->skyformat != NULL );
+
+/* Return the result. */
+   return result;
+}
+
+static int TestAxisInternalUnit( AstAxis *this, int *status ){
+/*
+*  Name:
+*     TestAxisInternalUnit
+
+*  Purpose:
+*     Test if a InternalUnit attribute value is set for an Axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "axis.h"
+*     int TestAxisInternalUnit( AstAxis *this, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astTestAxisInternalUnit
+*     protected method inherited from the Axis class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate
+*     whether a value has been set for the InternalUnit string.
+
+*  Parameters:
+*     this
+*        Pointer to the Axis.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Tell the world that we know what value to use for InternalUnit (i.e.
+   "rad"). */
+   return 1;
+}
+
+static int AxisUnformat( AstAxis *this_axis, const char *string,
+                         double *value, int *status ) {
+/*
+*  Name:
+*     AxisUnformat
+
+*  Purpose:
+*     Read a formatted coordinate value for a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     int AxisUnformat( AstAxis *axis, const char *string, double *value, int *status )
+
+*  Class Membership:
+*     SkyAxis member function (over-rides the astAxisUnformat method
+*     inherited from the Axis class).
+
+*  Description:
+*     This function reads a formatted coordinate value for a SkyAxis
+*     (supplied as a string) and returns the equivalent numerical
+*     value as a double. It also returns the number of characters read
+*     from the string.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis.
+*     string
+*        Pointer to a constant null-terminated string containing the
+*        formatted coordinate value.
+*     value
+*        Pointer to a double in which the coordinate value read will be
+*        returned (in radians).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of characters read from the string to obtain the
+*     coordinate value.
+
+*  Notes:
+*     - Any white space at the beginning of the string will be
+*     skipped, as also will any trailing white space following the
+*     coordinate value read. The function's return value will reflect
+*     this.
+*     - A function value of zero (and no coordinate value) will be
+*     returned, without error, if the string supplied does not contain
+*     a suitably formatted value.
+*     - The string "<bad>" is recognised as a special case and will
+*     generate the value AST__BAD, without error. The test for this
+*     string is case-insensitive and permits embedded white space.
+*     - A function result of zero will be returned and no coordinate
+*     value will be returned via the "value" pointer if this function
+*     is invoked with the global error status set, or if it should
+*     fail for any reason.
+*-
+*/
+
+/* Local Constants: */
+#define FMT_LEN 50               /* Length of format buffer */
+
+/* Local Variables: */
+   char fmtbuf[ FMT_LEN + 1 ];   /* Buffer for C format specification */
+   char fmtsep;                  /* Format field separator character */
+   char last_sep;                /* Previous separator character */
+   char sep;                     /* Separator character */
+   char sep_used;                /* Separator character being used */
+   char sign[ 2 ];               /* Sign character as string */
+   const char *field_start[ 3 ]; /* Pointer to start of each field */
+   const char *fmt;              /* Pointer to SkyAxis Format string */
+   const char *s;                /* Pointer to current reading position */
+   const char *string_start;     /* Pointer to first significant character */
+   double field[ 3 ];            /* Field values */
+   double testval;               /* Value to test for invalid fields */
+   int angle_or_time;            /* Value known to be angle or time? */
+   int as_time;                  /* Value is a time (else an angle)? */
+   int decimal;                  /* Decimal point in field? */
+   int dh;                       /* Hours field required? */
+   int digs;                     /* Default no. of digits of precision */
+   int exponent;                 /* Exponent at end of field? */
+   int field_id[ 3 ];            /* Field identification (0 = don't know) */
+   int final;                    /* Final field read? */
+   int good_sep;                 /* Separator character valid? */
+   int i;                        /* Loop counter for characters */
+   int ifield;                   /* Loop counter for fields */
+   int lead_zero;                /* Add leading zeros? */
+   int len;                      /* Significant length of string */
+   int m;                        /* Number of characters read by astSscanf */
+   int match;                    /* Character pattern matches? */
+   int min;                      /* Minutes field required? */
+   int n;                        /* Number of characters read by astSscanf */
+   int nc;                       /* Total no. characters read */
+   int nchar;                    /* Number of characters in erroneous value */
+   int ndp;                      /* Number of decimal places */
+   int next_id;                  /* Next field ID to use (0 = don't know) */
+   int nfield;                   /* Number of fields read */
+   int nread;                    /* No. characters read for current field */
+   int plus;                     /* Add leading plus sign? */
+   int positive;                 /* Value is positive? */
+   int sec;                      /* Seconds field required? */
+   int sep_angle_or_time;        /* Separator indicates angle or time? */
+   int sep_field_id;             /* Field ID from separator (0 = don't know) */
+   int sep_index;                /* Index of separator character in table */
+   int sep_len;                  /* Length of separator plus trailing space */
+   int suffix_sep;               /* Field has a suffix separator? */
+
+/* Local Data: */
+   const char *sep_list =        /* List of separator characters recognised */
+              " :hHdDmM'sS\"";
+
+   const int angle_or_time_list[] = /* Whether separator indicates angle or
+                                       time (1 or 2). Zero => don't know. */
+             { 0, 0, 2, 2, 1, 1, 0, 0, 1, 0, 0, 1 };
+
+   const int field_id_list[] =   /* Whether separator identifies previous field
+                                    (1, 2, or 3). Zero => doesn't identify. */
+             { 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3 };
+
+   const double fieldvalue[ 3 ] = /* Nominal field values (degrees/hours) */
+                { 1.0, 1.0 / 60.0, 1.0 / 3600.0 };
+
+/* Initialise. */
+   nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return nc;
+
+/* Obtain the SkyAxis Format string. If its starts with a "%" sign, use
+   the parent AxisUnformat method inherited from the Axis class. Use
+   a private method to obtain the Format string, in case the syntax has been
+   over-ridden by a derived class. */
+   fmt = GetAxisFormat( this_axis, status );
+   if( fmt && fmt[0] == '%' ) {
+      nc = (*parent_axisunformat)( this_axis, string, value, status );
+
+/* Otherwise, parse it to determine the default choice of input format. */
+   } else if( astOK ){
+      digs = astGetAxisDigits( this_axis );
+      ParseDHmsFormat( fmt, digs, &fmtsep, &plus, &lead_zero, &as_time, &dh,
+                       &min, &sec, &ndp, status );
+
+/* Initialise a pointer into the string and advance it to the first
+   non-white space character. Save a copy of this pointer. */
+      s = string;
+      while ( isspace( (int) *s ) ) s++;
+      string_start = s;
+
+/* Read sign information. */
+/* ---------------------- */
+/* Attempt to read an optional sign character ("+" or "-"), possibly
+   surrounded by white space. Set a flag to indicate if the returned
+   value should be positive or not. Increment the string pointer to
+   the next significant character. */
+   positive = 1;
+      n = 0;
+      if ( 1 == astSscanf( s, " %1[+-] %n", sign, &n ) ) {
+         positive = ( *sign == '+' );
+         s += n;
+      }
+
+/* Loop to read field information. */
+/* ------------------------------- */
+/* Initialise, then loop to read the values of up to three fields and
+   to identify the separators that accompany them. */
+      angle_or_time = 0;
+      last_sep = '\0';
+      next_id = 0;
+      nfield = 0;
+      sep_used = '\0';
+      suffix_sep = 0;
+      sep_len = 0;
+      for ( ifield = 0; ifield < 3; ifield++ ) {
+
+/* Set the default field value. */
+         field[ ifield ] = 0.0;
+
+/* If a prefix separator was identified for the second and subsequent
+   fields (when the previous field was being read), then step over the
+   prefix, including any following white space. */
+         if ( ifield && !suffix_sep ) s += sep_len;
+
+/* Note where in the input string the field's numerical value
+   starts. */
+         field_start[ ifield ] = s;
+
+/* Each field must consist of a string of digits, possibly surrounded
+   by white space, except that an optional decimal point may also be
+   present (in which case it indicates the final field). Since we want
+   to exclude signs, etc. from these fields, we must first identify a
+   valid sequence of digits, before attempting to read them as a number.
+   Start by assuming that we will find a decimal point but not an
+   exponent. */
+         decimal = 1;
+         exponent = 0;
+
+/* Match a field and obtain its value. */
+/* ----------------------------------- */
+/* Look for a character sequence like "12.345", or similar, setting a
+   flag to identify a match. */
+         n = 0;
+         match = ( 0 == astSscanf( s, "%*[0123456789].%*[0123456789]%n", &n ) )
+                   && n;
+
+/* If that failed, then look for a sequence like "12.", or similar. */
+         if ( !match ) {
+            n = 0;
+            match = ( 0 == astSscanf( s, "%*[0123456789].%n", &n ) ) && n;
+         }
+
+/* If that also failed, then look for a sequence like ".12", or similar. */
+         if ( !match ) {
+            n = 0;
+            match = ( 0 == astSscanf( s, ".%*[0123456789]%n", &n ) ) && n;
+         }
+
+/* If that also failed, then look for a sequence containing digits only. */
+         if ( !match ) {
+            n = 0;
+            match = ( 0 == astSscanf( s, "%*[0123456789]%n", &n ) ) && n;
+
+/* Note we have not found a decimal point. */
+            decimal = 0;
+         }
+
+/* Now look for numbers that end with an exponent. First check that the
+   string starts with a sequence of digits with or without a decimal point. */
+         if( match ) {
+
+/* See if the numbers are followed by an exponent with an explicit sign
+   character. If so, increment the number of characters in the numerical
+   string prefix. */
+            m = 0;
+            if( ( 0 == astSscanf( s + n, "%*1[Ee]%*1[+-]%*[0123456789]%n", &m ) )
+                && m ) {
+               n += m;
+               exponent = 1;
+
+/* If the above check failed, see if the numbers are followed by an exponent
+   without an explicit sign character. If so, increment the number of
+   characters in the numerical string prefix. */
+            } else {
+               m = 0;
+               if( ( 0 == astSscanf( s + n, "%*1[Ee]%*[0123456789]%n", &m ) )
+                   && m ) {
+                  n += m;
+                  exponent = 1;
+               }
+            }
+         }
+
+/* If we identified a suitable sequence of characters above, we will
+   now read them as a number. To prevent any subsequent characters
+   being included as part of this number, the field width must be
+   restricted to the length of the sequence we found. Write a format
+   specification to read a double with this field width, followed by
+   optional white space, and to return the total number of characters
+   read. */
+         nread = 0;
+         if ( match ) {
+            (void) sprintf( fmtbuf, "%%%dlf %%n", n );
+
+/* Use this format specification to read the field value. If
+   successful, increment the string pointer to the next significant
+   character. */
+            if ( 1 == astSscanf( s, fmtbuf, field + ifield, &nread ) ) s += nread;
+         }
+
+/* Note the total number of characters read up to the end of the
+   numerical value in this field (including any following white
+   space). */
+         nc = s - string;
+
+/* Identify the following separator. */
+/* --------------------------------- */
+/* We will now attempt to identify the field separator (if any) which
+   follows the field we have just read. By default, we behave as if
+   the separator is a space. Note we have actually found a space (at
+   least) if extra white space characters were read as part of the
+   field value above. */
+         sep = ' ';
+         good_sep = ( nread > n );
+
+/* Look for one of the recognised separator characters. If one is
+   found, save a copy of it and note we appear (so far) to have a
+   valid separator. */
+         sep_len = 0;
+         if ( *s && strchr( sep_list, *s ) ) {
+            sep = *s;
+            good_sep = 1;
+
+/* Set "sep_len" to the number of characters associated with the
+   separator. This includes any following white space. */
+            while ( isspace( (int) s[ ++sep_len ] ) );
+         }
+
+/* Identify the separator character by looking it up in the separator
+   list (this just uses a space if no valid separator has been
+   found). */
+         sep_index = strchr( sep_list, sep ) - sep_list;
+
+/* Determine if the separator can be used to identify the field which
+   preceded it and if it allows us to determine whether an angle or a
+   time is being read. Both of these properties are specified in data
+   tables (with zero indicating that the separator didn't supply any
+   information). */
+         sep_field_id = field_id_list[ sep_index ];
+         sep_angle_or_time = angle_or_time_list[ sep_index ];
+
+/* Validate the separator. */
+/* ----------------------- */
+/* Now perform further checks that the separator is valid
+   (i.e. conforms to the required syntax). If it appears to identify
+   the previous field (i.e. is a "suffix" separator like "m" or "s"),
+   then it is valid only if its field ID is no less than the ID value
+   that would be used next, based on previous fields (if any), and no
+   less than the current field number. This ensures that fields occur
+   in the correct order without duplication. */
+         if ( good_sep ) {
+            if ( sep_field_id ) {
+               good_sep = ( sep_field_id >= next_id ) &&
+                          ( sep_field_id > ifield );
+
+/* Otherwise (i.e. we appear to have a "prefix" separator like ":" or
+   " "), it is valid if it is the first one used, or if it matches the
+   previous one used. Keep a note of the first such separator used for
+   checking subsequent ones. */
+            } else {
+               good_sep = !sep_used || ( sep == sep_used );
+               if ( !sep_used ) sep_used = sep;
+            }
+         }
+
+/* If the separator seems OK and we don't yet know whether we are reading
+   an angle or a time, then use whatever information the separator
+   provides about this. */
+         if ( good_sep ) {
+            if ( !angle_or_time ) {
+               angle_or_time = sep_angle_or_time;
+
+/* If we already know whether we are reading an angle or a time and
+   the current separator also contains information about this, then
+   check that these sources of information are compatible. This
+   prevents inconsistent use of angle/time field separators. */
+            } else {
+               good_sep = !sep_angle_or_time ||
+                          ( sep_angle_or_time == angle_or_time );
+            }
+         }
+
+/* Update the count of characters read for this field and note if we
+   have identified a valid suffix separator. */
+         if ( good_sep ) nread += sep_len;
+         suffix_sep = good_sep && sep_field_id;
+
+/* Identify which field was read. */
+/* ------------------------------ */
+/* If we have a valid suffix separator, store the field ID. Also make
+   a note of the ID to use for the next field. */
+         if ( suffix_sep ) {
+            field_id[ ifield ] = sep_field_id;
+            next_id = sep_field_id + 1;
+
+/* Step over the separator (plus any following white space) and update
+   the total number of characters read (prefix separators are not
+   accounted for until we start to read the next field). */
+            s += sep_len;
+            nc = s - string;;
+
+/* If the separator does not identify the current field, then assign a
+   field ID based on the previous field (if any). Update the ID to use
+   for the next field, if known. */
+         } else {
+            field_id[ ifield ] = next_id;
+            if ( next_id ) next_id++;
+         }
+
+/* Count fields and exit when done. */
+/* -------------------------------- */
+/* If no characters have been read for the current field, then
+   disregard the field if: (a) it is the first one (i.e. there is
+   nothing to read), or (b) it follows a white space separator
+   (because trailing space does not delimit an extra field). In either
+   case, we have now read all the fields. Otherwise, increment the
+   count of fields read. */
+         final = 0;
+         if ( !nread && ( !ifield || isspace( (int) last_sep ) ) ) {
+            final = 1;
+         } else {
+            nfield++;
+         }
+
+/* We have also read all the fields if: (a) the last one contained a
+   decimal point, or (b) the last one ended with an exponent, or (c)
+   the next character is not a valid field separator, or (d) we have
+   read the seconds field so the next field ID would exceed 3. */
+         final = final || decimal || exponent || !good_sep || ( next_id > 3 );
+
+/* Quit reading if we have read the final field. Otherwise, save the
+   separator character and attempt to read the next field. */
+         if ( final ) break;
+         last_sep = sep;
+      }
+
+/* Complete the identification of fields. */
+/* -------------------------------------- */
+/* Although we have propagated field IDs from earlier ones to later
+   ones in the loop above, we have still not done the reverse. This
+   means there there may still be some leading fields which have not
+   been positively identified (i.e. still have a field ID of zero). In
+   fact, all the fields we have read might still be unidentified at
+   this point. */
+
+/* Calculate the field ID that would apply to the final field we have
+   read in the absence of any other information. This depends on the
+   number of leading fields that are expected to be missing. */
+      next_id = nfield + ( dh ? 0 : ( min ? 1 : 2 ) );
+      if ( next_id > 3 ) next_id = 3;
+
+/* Loop through the fields in reverse order, propagating any positive
+   identifications backwards towards the first field. If no fields
+   have been positively identified, then they are simply numbered
+   consecutively based on the value calculated above. */
+      for ( ifield = nfield - 1; ifield >= 0; ifield-- ) {
+         if ( field_id[ ifield ] ) {
+            next_id = field_id[ ifield ] - 1;
+         } else {
+            field_id[ ifield ] = next_id--;
+         }
+      }
+
+/* Handle inability to read any value. */
+/* ----------------------------------- */
+/* If no fields were read, then check to see if we are trying to read
+   the string "<bad>" (or similar) possibly surrounded by, or
+   containing, white space. If so, return the coordinate value
+   AST__BAD. */
+      if ( !nfield ) {
+         if ( n = 0,
+              ( 0 == astSscanf( string, " < %*1[Bb] %*1[Aa] %*1[Dd] > %n", &n )
+                && n ) ) {
+            *value = AST__BAD;
+            nc = n;
+
+/* If the string still cannot be read, then return a function value of
+   zero. */
+         } else {
+            nc = 0;
+         }
+
+/* Finally determine angle or time. */
+/* -------------------------------- */
+/* If one or more fields have been read, check if we know whether to
+   interpret the value as an angle or a time (if not, we continue to
+   use the default choice obtained from the SkyAxis Format string). */
+      } else {
+         if ( angle_or_time ) as_time = ( angle_or_time == 2 );
+
+/* Validate field values. */
+/* ---------------------- */
+/* If OK, check all fields except the first one for a valid value (we
+   allow the first field to be unconstrained, so that angles and times
+   outside the conventional ranges can be represented). We only need
+   to test for values over 60.0, since negative values can't be
+   read. */
+         if ( astOK ) {
+            for ( ifield = 1; ifield < nfield; ifield++ ) {
+               if ( field[ ifield ] >= 60.0 ) {
+
+/* If a suspect field is found, we must now re-read it. This is
+   because values like "59.9999..." are valid, even if they round up
+   to 60, whereas "60" isn't. To distinguish these cases, we read the
+   digits that occur before the decimal point (if any). Determine how
+   many such digits there are. */
+                  n = 0;
+                  if ( ( 0 == astSscanf( field_start[ ifield ],
+                                      "%*[0123456789]%n", &n ) ) && n ) {
+
+/* If there are none (this shouldn't happen), the field is
+   valid. Otherwise, construct a format specification to read these
+   digits as a floating point number. */
+                     (void) sprintf( fmtbuf, "%%%dlf", n );
+
+/* Read the digits and compare the result with 60.0. Report an error
+   and quit if necessary, limiting the string length in the error
+   message to include just the significant characters in the value
+   read. */
+                     if ( ( 1 == astSscanf( field_start[ ifield ], fmtbuf,
+                                         &testval ) )
+                          && ( testval >= 60.0 ) ) {
+                        nchar = nc - ( string_start - string );
+                        for ( i = len = 0; i < nchar; i++ ) {
+                           if ( !isspace( (int) string_start[ i ] ) ) {
+                              len = i + 1;
+                           }
+                        }
+                        astError( AST__UNFER, "Invalid %s%s value in sky "
+                                  "coordinate \"%.*s\".", status, as_time ? "" : "arc",
+                                  ( field_id[ ifield ] == 2 ) ? "minutes" :
+                                                                "seconds",
+                                  len, string_start );
+                        break;
+                     }
+                  }
+               }
+            }
+         }
+
+/* Calculate final result. */
+/* ----------------------- */
+/* If OK, calculate the result by summing the field values and converting
+   to radians. */
+         if ( astOK ) {
+            *value = 0.0;
+            for ( ifield = 0; ifield < nfield; ifield++ ) {
+               *value += field[ ifield ] *
+                         fieldvalue[ field_id[ ifield ] - 1 ] *
+                         ( as_time ? hr2rad : deg2rad );
+            }
+
+/* Change sign if necessary. */
+            if ( !positive ) *value = - *value;
+         }
+      }
+   }
+
+/* If an error occurred, set the number of characters read to zero. */
+   if ( !astOK ) nc = 0;
+
+/* Return the number of characters read. */
+   return nc;
+
+/* Undefine macros local to this function. */
+#undef FMT_LEN
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with the
+   SkyAxis class using the macros defined for this purpose in the "object.h"
+   file. For a description of each attribute, see the class interface (in the
+   associated .h file). */
+
+/* AsTime. */
+/* ------- */
+/* The value is constrained to be -INT_MAX, 0 or 1, with -INT_MAX for
+   "undefined". The default value is 0 unless the "IsLatitude"
+   attribute has been explicitly set to 0, in which case "AsTime"
+   defaults to 1. */
+astMAKE_CLEAR(SkyAxis,AxisAsTime,as_time,-INT_MAX)
+astMAKE_GET(SkyAxis,AxisAsTime,int,0,( ( this->as_time != -INT_MAX ) ?
+                                       this->as_time :
+                                       ( astTestAxisIsLatitude( this ) &&
+                                         !astGetAxisIsLatitude( this ) ) ))
+astMAKE_SET(SkyAxis,AxisAsTime,int,as_time,( value != 0 ))
+astMAKE_TEST(SkyAxis,AxisAsTime,( this->as_time != -INT_MAX ))
+
+/* IsLatitude. */
+/* ----------- */
+/* The value is constrained to be -INT_MAX, 0 or 1, with -INT_MAX for
+   "undefined". The default value is 0. */
+astMAKE_CLEAR(SkyAxis,AxisIsLatitude,is_latitude,-INT_MAX)
+astMAKE_GET(SkyAxis,AxisIsLatitude,int,0,( this->is_latitude != -INT_MAX ?
+                                           this->is_latitude : 0 ))
+astMAKE_SET(SkyAxis,AxisIsLatitude,int,is_latitude,( value != 0 ))
+astMAKE_TEST(SkyAxis,AxisIsLatitude,( this->is_latitude != -INT_MAX ))
+
+/* CentreZero. */
+/* ----------- */
+/* The value is constrained to be -INT_MAX, 0 or 1, with -INT_MAX for
+   "undefined". The default value is equal to the value of IsLatitude. */
+astMAKE_CLEAR(SkyAxis,AxisCentreZero,centrezero,-INT_MAX)
+astMAKE_GET(SkyAxis,AxisCentreZero,int,0,( this->centrezero != -INT_MAX ?
+                                           this->centrezero : astGetAxisIsLatitude( this ) ))
+astMAKE_SET(SkyAxis,AxisCentreZero,int,centrezero,( value != 0 ))
+astMAKE_TEST(SkyAxis,AxisCentreZero,( this->centrezero != -INT_MAX ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for SkyAxis objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for SkyAxis objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *in;                  /* Pointer to input SkyAxis */
+   AstSkyAxis *out;                 /* Pointer to output SkyAxis */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output SkyAxis structures. */
+   in = (AstSkyAxis *) objin;
+   out = (AstSkyAxis *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output SkyAxis. */
+   out->skyformat = NULL;
+
+/* Make copies of the allocated strings. */
+   if ( in->skyformat ) out->skyformat = astStore( NULL, in->skyformat,
+                                       strlen( in->skyformat ) + (size_t) 1 );
+
+/* If an error occurred, clean up by freeing all memory allocated above. */
+   if ( !astOK ) {
+      out->skyformat = astFree( out->skyformat );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for SkyAxis objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for SkyAxis objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) obj;
+
+/* Free all allocated memory. */
+   this->skyformat = astFree( this->skyformat );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for SkyAxis objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the SkyAxis class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyAxis whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstAxis *this_axis;           /* Pointer to Axis structure */
+   AstSkyAxis *this;             /* Pointer to the SkyAxis structure */
+   const char *sval;             /* Pointer to string value */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyAxis structure. */
+   this = (AstSkyAxis *) this_object;
+
+/* Write out values representing the instance variables for the
+   SkyAxis class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Format. */
+/* ------- */
+/* We must write out the Format value stored locally as it over-rides
+   that provided by the Axis class. */
+   this_axis = (AstAxis *) this;
+   set = TestAxisFormat( this_axis, status );
+   sval = set ? GetAxisFormat( this_axis, status ) : astGetAxisFormat( this );
+   astWriteString( channel, "Format", set, 0, sval, "Format specifier" );
+
+/* IsLatitude. */
+/* ----------- */
+   set = TestAxisIsLatitude( this, status );
+   ival = set ? GetAxisIsLatitude( this, status ) : astGetAxisIsLatitude( this );
+   astWriteInt( channel, "IsLat", set, 0, ival,
+                ival ? "Latitude axis (not longitude)" :
+                       "Longitude axis (not latitude)" );
+
+/* CentreZero. */
+/* ----------- */
+   set = TestAxisCentreZero( this, status );
+   ival = set ? GetAxisCentreZero( this, status ) : astGetAxisCentreZero( this );
+   astWriteInt( channel, "CnZer", set, 0, ival,
+                ival ? "Display axis values in range -PI -> +PI" :
+                       "Display axis values in range 0 -> 2.PI" );
+
+/* AsTime. */
+/* ------- */
+   set = TestAxisAsTime( this, status );
+   ival = set ? GetAxisAsTime( this, status ) : astGetAxisAsTime( this );
+   astWriteInt( channel, "AsTime", set, 0, ival,
+                ival ? "Display values as times (not angles)" :
+                       "Display values as angles (not times)" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsASkyAxis and astCheckSkyAxis functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(SkyAxis,Axis)
+astMAKE_CHECK(SkyAxis)
+
+AstSkyAxis *astSkyAxis_( const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astSkyAxis
+
+*  Purpose:
+*     Create a SkyAxis.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     AstSkyAxis *astSkyAxis( const char *options, int *status, ... )
+
+*  Class Membership:
+*     SkyAxis constructor.
+
+*  Description:
+*     This function creates a new SkyAxis and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new SkyAxis. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new SkyAxis.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSkyAxis *new;              /* Pointer to new SkyAxis */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SkyAxis, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitSkyAxis( NULL, sizeof( AstSkyAxis ), !class_init, &class_vtab,
+                         "SkyAxis" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new SkyAxis'
+   attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new SkyAxis. */
+   return new;
+}
+
+AstSkyAxis *astSkyAxisId_( const char *options, ... ) {
+/*
+*  Name:
+*     astSkyAxisId_
+
+*  Purpose:
+*     Create a SkyAxis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     AstSkyAxis *astSkyAxisId_( const char *options, ... )
+
+*  Class Membership:
+*     SkyAxis constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astSkyAxis constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astSkyAxis_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astSkyAxis_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astSkyAxis_.
+
+*  Returned Value:
+*     The ID value associated with the new SkyAxis.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSkyAxis *new;              /* Pointer to new SkyAxis */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SkyAxis, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitSkyAxis( NULL, sizeof( AstSkyAxis ), !class_init, &class_vtab,
+                         "SkyAxis" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new SkyAxis'
+   attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new SkyAxis. */
+   return astMakeId( new );
+}
+
+AstSkyAxis *astInitSkyAxis_( void *mem, size_t size, int init,
+                             AstSkyAxisVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSkyAxis
+
+*  Purpose:
+*     Initialise a SkyAxis.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     AstSkyAxis *astInitSkyAxis( void *mem, size_t size, int init,
+*                                 AstSkyAxisVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SkyAxis initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new SkyAxis object. It allocates memory (if necessary) to accommodate
+*     the SkyAxis plus any additional data associated with the derived class.
+*     It then initialises a SkyAxis structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a SkyAxis at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the SkyAxis is to be created. This
+*        must be of sufficient size to accommodate the SkyAxis data
+*        (sizeof(SkyAxis)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the SkyAxis (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the SkyAxis
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the SkyAxis's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new SkyAxis.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astClass
+*        method).
+
+*  Returned Value:
+*     A pointer to the new SkyAxis.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSkyAxis *new;              /* Pointer to the new SkyAxis */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitSkyAxisVtab( vtab, name );
+
+/* Initialise an Axis structure (the parent class) as the first component
+   within the SkyAxis structure, allocating memory if necessary. */
+   new = (AstSkyAxis *) astInitAxis( mem, size, 0, (AstAxisVtab *) vtab,
+                                     name );
+
+   if ( astOK ) {
+
+/* Initialise the SkyAxis data. */
+/* ---------------------------- */
+/* Initialise all attributes to their "undefined" values. */
+      new->as_time = -INT_MAX;
+      new->is_latitude = -INT_MAX;
+      new->centrezero = -INT_MAX;
+      new->skyformat = NULL;
+
+/* If an error occurred, clean up by deleting the new SkyAxis. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new SkyAxis. */
+   return new;
+}
+
+AstSkyAxis *astLoadSkyAxis_( void *mem, size_t size,
+                             AstSkyAxisVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadSkyAxis
+
+*  Purpose:
+*     Load a SkyAxis.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "skyaxis.h"
+*     AstSkyAxis *astLoadSkyAxis( void *mem, size_t size,
+*                                 AstSkyAxisVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     SkyAxis loader.
+
+*  Description:
+*     This function is provided to load a new SkyAxis using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     SkyAxis structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a SkyAxis at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the SkyAxis is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        SkyAxis data (sizeof(SkyAxis)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the SkyAxis (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the SkyAxis structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstSkyAxis) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new SkyAxis. If this is NULL, a pointer
+*        to the (static) virtual function table for the SkyAxis class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "SkyAxis" is used instead.
+
+*  Returned Value:
+*     A pointer to the new SkyAxis.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSkyAxis *new;              /* Pointer to the new SkyAxis */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this SkyAxis. In this case the
+   SkyAxis belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstSkyAxis );
+      vtab = &class_vtab;
+      name = "SkyAxis";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitSkyAxisVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built SkyAxis. */
+   new = astLoadAxis( mem, size, (AstAxisVtab *) vtab, name, channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "SkyAxis" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Format. */
+/* ------- */
+/* Note that string values do not require any additional processing. */
+      new->skyformat = astReadString( channel, "format", NULL );
+
+/* IsLatitude. */
+/* ----------- */
+      new->is_latitude = astReadInt( channel, "islat", -INT_MAX );
+      if ( TestAxisIsLatitude( new, status ) ) {
+         SetAxisIsLatitude( new, new->is_latitude, status );
+      }
+
+/* CentreZero. */
+/* ----------- */
+      new->centrezero = astReadInt( channel, "cnzer", -INT_MAX );
+      if ( TestAxisCentreZero( new, status ) ) {
+         SetAxisCentreZero( new, new->centrezero, status );
+      }
+
+/* AsTime. */
+/* ------- */
+      new->as_time = astReadInt( channel, "astime", -INT_MAX );
+      if ( TestAxisAsTime( new, status ) ) SetAxisAsTime( new, new->as_time, status );
+
+/* If an error occurred, clean up by deleting the new SkyAxis. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new SkyAxis pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+/* (No more to define at present.) */
+
+
+
+
+
diff --git a/ast/skyaxis.h b/ast/skyaxis.h
new file mode 100644
index 0000000..5c2f7ac
--- /dev/null
+++ b/ast/skyaxis.h
@@ -0,0 +1,428 @@
+#if !defined( SKYAXIS_INCLUDED ) /* Include this file only once */
+#define SKYAXIS_INCLUDED
+/*
+*+
+*  Name:
+*     skyaxis.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the SkyAxis class.
+
+*  Invocation:
+*     #include "skyaxis.h"
+
+*  Description:
+*     This include file defines the interface to the SkyAxis class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The SkyAxis class extends the Axis class to represent angles on
+*     the sky measured in radians. It provides alternative formatting
+*     facilities for representing these coordinate values either as
+*     angles (in degrees) or as time (in hours) using sexagesimal
+*     notation. It also provides alternative defaults for certain
+*     attributes and adds new attributes and methods of its own which
+*     are needed to manipulate angular coordinates on the sky.
+
+*  Inheritance:
+*     The SkyAxis class inherits from the Axis class.
+
+*  Attributes Over-Ridden:
+*     Format (string)
+*        The SkyAxis class defines a new syntax for this string.
+*     Label (string)
+*        The SkyAxis class defines new default values. These may
+*        depend on other attribute settings.
+*     Symbol (string)
+*        The SkyAxis class defines new default values. These may
+*        depend on other attribute settings.
+*     Unit (string)
+*        The SkyAxis class defines new default values. These may
+*        depend on other attribute settings.
+
+*  New Attributes Defined:
+*     AsTime (integer)
+*        A boolean value which indicates whether SkyAxis coordinate
+*        values should be formatted for display as times (instead of
+*        angles). It is used to determine the default format to use if
+*        no explicit value has been set for the Format attribute.
+*     CentreZero (integer)
+*        A boolean value which indicates whether a SkyAxis value should
+*        be normalised into the range [-PI,+PI] or [0,2.PI] when astNorm
+*        is used.
+*     IsLatitude (integer)
+*        A boolean value which indicates whether a SkyAxis is a
+*        latitude axis (as opposed to a longitude axis). It is used to
+*        determine default axis labels and symbols. It also determines the
+*        default value for the "AsTime" attribute (since longitudes on
+*        the sky are usually expressed as times).
+
+*  Methods Over-Ridden:
+*     Public:
+*        astAxisFormat
+*           Format a coordinate value for a SkyAxis.
+*        astAxisNorm
+*           Normalise a SkyAxis coordinate value.
+*        astAxisUnformat
+*           Read a formatted coordinate value for a SkyAxis.
+
+*     Protected:
+*        astAxisAbbrev
+*           Abbreviate a formatted SkyAxis value by skipping leading fields.
+*        astAxisDistance
+*           Find the distance between two SkyAxis values.
+*        astAxisGap
+*           Find a "nice" gap for tabulating SkyAxis values.
+*        astClearAxisFormat
+*           Clear the Format attribute for a SkyAxis.
+*        astGetAxisDirection
+*           Obtain the value of the Direction attribute for a SkyAxis.
+*        astGetAxisFormat
+*           Obtain a pointer to the Format attribute for a SkyAxis.
+*        astGetAxisLabel
+*           Obtain a pointer to the Label attribute for a SkyAxis.
+*        astGetAxisSymbol
+*           Obtain a pointer to the Symbol attribute for a SkyAxis.
+*        astGetAxisUnit
+*           Obtain a pointer to the Unit attribute for a SkyAxis.
+*        astSetAxisFormat
+*           Set a value for the Format attribute of a SkyAxis.
+*        astTestAxisFormat
+*           Test if a value has been set for the Format attribute of a SkyAxis.
+*        astAxisOffset
+*           Add an increment onto a supplied SkyAxis value.
+*        astAxisOverlay
+*           Overlay the attributes of a template SkyAxis on to another Axis.
+*        astSetAttrib
+*           Set an attribute value for a SkyAxis.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+
+*     Protected:
+*        astClearAxisAsTime
+*           Clear the AsTime attribute for a SkyAxis.
+*        astClearAxisCentreZero
+*           Clear the CentreZero attribute for a SkyAxis.
+*        astClearAxisIsLatitude
+*           Clear the IsLatitude attribute for a SkyAxis.
+*        astGetAxisAsTime
+*           Obtain the value of the AsTime attribute for a SkyAxis.
+*        astGetAxisIsLatitude
+*           Obtain the value of the IsLatitude attribute for a SkyAxis.
+*        astGetAxisCentreZero
+*           Obtain the value of the CentreZero attribute for a SkyAxis.
+*        astSetAxisAsTime
+*           Set a value for the AsTime attribute of a SkyAxis.
+*        astSetAxisIsLatitude
+*           Set a value for the IsLatitude attribute of a SkyAxis.
+*        astSetAxisCentreZero
+*           Set a value for the CentreZero attribute of a SkyAxis.
+*        astTestAxisAsTime
+*           Test if a value has been set for the AsTime attribute of a SkyAxis.
+*        astTestAxisIsLatitude
+*           Test if a value has been set for the IsLatitude attribute of a
+*           SkyAxis.
+*        astTestAxisCentreZero
+*           Test if a value has been set for the CentreZero attribute of a
+*           SkyAxis.
+
+*  Other Class Functions:
+*     Public:
+*        astIsASkyAxis
+*           Test class membership.
+*        astSkyAxis
+*           Create an SkyAxis.
+
+*     Protected:
+*        astCheckSkyAxis
+*           Validate class membership.
+*        astInitSkyAxis
+*           Initialise an SkyAxis.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstSkyAxis
+*           SkyAxis object type.
+
+*     Protected:
+*        AstSkyAxisVtab
+*           SkyAxis virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     29-MAR-1996 (RFWS):
+*        Original version.
+*     25-APR-1996 (RFWS):
+*        Made all attribute access functions protected.
+*     13-MAY-1996 (RFWS):
+*        Documented over-riding of the astGetAxisDirection method.
+*     26-FEB-1998 (RFWS):
+*        Over-ride the astAxisUnformat method.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitSkyAxisVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "axis.h"                /* Coordinate axes (parent class) */
+
+/* Macros */
+/* ====== */
+
+/* Define constants used to size global arrays in this module. */
+/* Define numerical constants for use in thie module. */
+#define AST__SKYAXIS_GETAXISFORMAT_BUFF_LEN 50
+#define AST__SKYAXIS_DHMSFORMAT_BUFF_LEN 70
+#define AST__SKYAXIS_DHMSUNIT_BUFF_LEN 17
+#define AST__SKYAXIS_GETATTRIB_BUFF_LEN 50
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+
+/* Type Definitions. */
+/* ================= */
+/* SkyAxis structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstSkyAxis {
+
+/* Attributes inherited from the parent class. */
+   AstAxis axis;                 /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   char *skyformat;              /* Pointer to sky format string */
+   int as_time;                  /* Format angles as time (hours)? */
+   int is_latitude;              /* SkyAxis is a latitude axis? */
+   int centrezero;               /* Normalised range is zero-centred? */
+} AstSkyAxis;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstSkyAxisVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstAxisVtab axis_vtab;        /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   int (* GetAxisAsTime)( AstSkyAxis *, int * );
+   int (* GetAxisIsLatitude)( AstSkyAxis *, int * );
+   int (* GetAxisCentreZero)( AstSkyAxis *, int * );
+   int (* TestAxisAsTime)( AstSkyAxis *, int * );
+   int (* TestAxisIsLatitude)( AstSkyAxis *, int * );
+   int (* TestAxisCentreZero)( AstSkyAxis *, int * );
+   void (* ClearAxisAsTime)( AstSkyAxis *, int * );
+   void (* ClearAxisIsLatitude)( AstSkyAxis *, int * );
+   void (* ClearAxisCentreZero)( AstSkyAxis *, int * );
+   void (* SetAxisAsTime)( AstSkyAxis *, int, int * );
+   void (* SetAxisIsLatitude)( AstSkyAxis *, int, int * );
+   void (* SetAxisCentreZero)( AstSkyAxis *, int, int * );
+} AstSkyAxisVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstSkyAxisGlobals {
+   AstSkyAxisVtab Class_Vtab;
+   int Class_Init;
+   char DHmsFormat_Buff[ AST__SKYAXIS_DHMSFORMAT_BUFF_LEN + 1 ];
+   char DHmsUnit_Buff[ AST__SKYAXIS_DHMSUNIT_BUFF_LEN + 1 ];
+   char GetAttrib_Buff[ AST__SKYAXIS_GETATTRIB_BUFF_LEN + 1 ];
+   char GetAxisFormat_Buff[ AST__SKYAXIS_GETAXISFORMAT_BUFF_LEN + 1 ];
+   char *GhDelim;
+   char *GmDelim;
+   char *GsDelim;
+   char *GdDelim;
+   char *GamDelim;
+   char *GasDelim;
+} AstSkyAxisGlobals;
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(SkyAxis)          /* Check class membership */
+astPROTO_ISA(SkyAxis)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstSkyAxis *astSkyAxis_( const char *, int *, ...);
+#else
+AstSkyAxis *astSkyAxisId_( const char *, ... )__attribute__((format(printf,1,2)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstSkyAxis *astInitSkyAxis_( void *, size_t, int, AstSkyAxisVtab *,
+                             const char *, int * );
+
+/* Vtab initialiser. */
+void astInitSkyAxisVtab_( AstSkyAxisVtab *, const char *, int * );
+
+/* Loader. */
+AstSkyAxis *astLoadSkyAxis_( void *, size_t, AstSkyAxisVtab *,
+                             const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitSkyAxisGlobals_( AstSkyAxisGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+#if defined(astCLASS)            /* Protected */
+int astGetAxisAsTime_( AstSkyAxis *, int * );
+int astGetAxisIsLatitude_( AstSkyAxis *, int * );
+int astGetAxisCentreZero_( AstSkyAxis *, int * );
+int astTestAxisAsTime_( AstSkyAxis *, int * );
+int astTestAxisIsLatitude_( AstSkyAxis *, int * );
+int astTestAxisCentreZero_( AstSkyAxis *, int * );
+void astClearAxisAsTime_( AstSkyAxis *, int * );
+void astClearAxisIsLatitude_( AstSkyAxis *, int * );
+void astClearAxisCentreZero_( AstSkyAxis *, int * );
+void astSetAxisAsTime_( AstSkyAxis *, int, int * );
+void astSetAxisIsLatitude_( AstSkyAxis *, int, int * );
+void astSetAxisCentreZero_( AstSkyAxis *, int, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckSkyAxis(this) astINVOKE_CHECK(SkyAxis,this,0)
+#define astVerifySkyAxis(this) astINVOKE_CHECK(SkyAxis,this,1)
+
+/* Test class membership. */
+#define astIsASkyAxis(this) astINVOKE_ISA(SkyAxis,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astSkyAxis astINVOKE(F,astSkyAxis_)
+#else
+#define astSkyAxis astINVOKE(F,astSkyAxisId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitSkyAxis(mem,size,init,vtab,name) \
+astINVOKE(O,astInitSkyAxis_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitSkyAxisVtab(vtab,name) astINVOKE(V,astInitSkyAxisVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadSkyAxis(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadSkyAxis_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+
+/* Here we make use of astCheckSkyAxis to validate SkyAxis pointers
+   before use. This provides a contextual error report if a pointer to
+   the wrong sort of object is supplied. */
+#if defined(astCLASS)            /* Protected */
+
+#define astClearAxisAsTime(this) \
+astINVOKE(V,astClearAxisAsTime_(astCheckSkyAxis(this),STATUS_PTR))
+#define astClearAxisIsLatitude(this) \
+astINVOKE(V,astClearAxisIsLatitude_(astCheckSkyAxis(this),STATUS_PTR))
+#define astGetAxisAsTime(this) \
+astINVOKE(V,astGetAxisAsTime_(astCheckSkyAxis(this),STATUS_PTR))
+#define astGetAxisIsLatitude(this) \
+astINVOKE(V,astGetAxisIsLatitude_(astCheckSkyAxis(this),STATUS_PTR))
+#define astSetAxisAsTime(this,value) \
+astINVOKE(V,astSetAxisAsTime_(astCheckSkyAxis(this),value,STATUS_PTR))
+#define astSetAxisIsLatitude(this,value) \
+astINVOKE(V,astSetAxisIsLatitude_(astCheckSkyAxis(this),value,STATUS_PTR))
+#define astTestAxisAsTime(this) \
+astINVOKE(V,astTestAxisAsTime_(astCheckSkyAxis(this),STATUS_PTR))
+#define astTestAxisIsLatitude(this) \
+astINVOKE(V,astTestAxisIsLatitude_(astCheckSkyAxis(this),STATUS_PTR))
+
+#define astClearAxisCentreZero(this) \
+astINVOKE(V,astClearAxisCentreZero_(astCheckSkyAxis(this),STATUS_PTR))
+#define astGetAxisCentreZero(this) \
+astINVOKE(V,astGetAxisCentreZero_(astCheckSkyAxis(this),STATUS_PTR))
+#define astSetAxisCentreZero(this,value) \
+astINVOKE(V,astSetAxisCentreZero_(astCheckSkyAxis(this),value,STATUS_PTR))
+#define astTestAxisCentreZero(this) \
+astINVOKE(V,astTestAxisCentreZero_(astCheckSkyAxis(this),STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/skyframe.c b/ast/skyframe.c
new file mode 100644
index 0000000..6d67710
--- /dev/null
+++ b/ast/skyframe.c
@@ -0,0 +1,12440 @@
+/*
+*class++
+*  Name:
+*     SkyFrame
+
+*  Purpose:
+*     Celestial coordinate system description.
+
+*  Constructor Function:
+c     astSkyFrame
+f     AST_SKYFRAME
+
+*  Description:
+*     A SkyFrame is a specialised form of Frame which describes
+*     celestial longitude/latitude coordinate systems. The particular
+*     celestial coordinate system to be represented is specified by
+*     setting the SkyFrame's System attribute (currently, the default
+*     is ICRS) qualified, as necessary, by a mean Equinox value and/or
+*     an Epoch.
+*
+*     For each of the supported celestial coordinate systems, a SkyFrame
+*     can apply an optional shift of origin to create a coordinate system
+*     representing offsets within the celestial coordinate system from some
+*     specified reference point. This offset coordinate system can also be
+*     rotated to define new longitude and latitude axes. See attributes
+*     SkyRef, SkyRefIs, SkyRefP and AlignOffset.
+*
+*     All the coordinate values used by a SkyFrame are in
+*     radians. These may be formatted in more conventional ways for
+c     display by using astFormat.
+f     display by using AST_FORMAT.
+*     For a SkyFrame, the Unit attribute describes the formatted value of
+*     a SkyFrame axis, and may for instance be "h:m:s", indicating that a
+*     formatted axis value contains colon-separated fields for hours, minutes
+*     and seconds. On the other hand, the InternalUnit attribute for a
+*     SkyFrame is always set to "rad" (i.e. radians), indicating that the
+*     unformatted (i.e. floating point) axis values used by application code
+*     are always in units of radians
+
+*  Inheritance:
+*     The SkyFrame class inherits from the Frame class.
+
+*  Attributes:
+*     In addition to those attributes common to all Frames, every
+*     SkyFrame also has the following attributes:
+*
+*     - AlignOffset: Align SkyFrames using the offset coordinate system?
+*     - AsTime(axis): Format celestial coordinates as times?
+*     - Equinox: Epoch of the mean equinox
+*     - IsLatAxis: Is the specified axis the latitude axis?
+*     - IsLonAxis: Is the specified axis the longitude axis?
+*     - LatAxis: Index of the latitude axis
+*     - LonAxis: Index of the longitude axis
+*     - NegLon: Display longitude values in the range [-pi,pi]?
+*     - Projection: Sky projection description.
+*     - SkyRef: Position defining location of the offset coordinate system
+*     - SkyRefIs: Selects the nature of the offset coordinate system
+*     - SkyRefP: Position defining orientation of the offset coordinate system
+*     - SkyTol: Smallest significant shift in sky coordinates
+
+*  Functions:
+*     In addition to those
+c     functions
+f     routines
+*     applicable to all Frames, the following
+c     functions
+f     routines
+*     may also be applied to all SkyFrames:
+*
+c     - astSkyOffsetMap: Obtain a Mapping from absolute to offset coordinates
+f     - AST_SKYOFFSETMAP: Obtain a Mapping from absolute to offset coordinates
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+*     BEC: Brad Cavanagh (JAC, Hawaii)
+
+*  History:
+*     4-MAR-1996 (RFWS):
+*        Original version.
+*     17-MAY-1996 (RFWS):
+*        Tidied up, etc.
+*     31-JUL-1996 (RFWS):
+*        Added support for attributes and a public interface.
+*     11-SEP-1996 (RFWS):
+*        Added Gap (written by DSB).
+*     24-SEP-1996 (RFWS):
+*        Added I/O facilities.
+*     27-FEB-1997 (RFWS):
+*        Improved the public prologues.
+*     27-MAY-1997 (RFWS):
+*        Modified to use a new public interface to the SlaMap class
+*        and to use the astSimplify method to remove redundant
+*        conversions.
+*     16-JUN-1997 (RFWS):
+*        Fixed bug in axis associations returned by astMatch if axes
+*        were swapped.
+*     16-JUL-1997 (RFWS):
+*        Added Projection attribute.
+*     14-NOV-1997 (RFWS):
+*        Corrected the omission of axis permutations from astNorm.
+*     21-JAN-1998 (RFWS):
+*        Ensure that Title and Domain values appropriate to a SkyFrame
+*        are preserved if a Frame result is generated by SubFrame.
+*     26-FEB-1998 (RFWS):
+*        Over-ride the astUnformat method.
+*     3-APR-2001 (DSB):
+*        Added "Unknown" option for the System attribute. Added read-only
+*        attributes LatAxis and LonAxis.
+*     21-JUN-2001 (DSB):
+*        Added astAngle and astOffset2.
+*     4-SEP-2001 (DSB):
+*        Added NegLon attribute, and astResolve method.
+*     9-SEP-2001 (DSB):
+*        Added astBear method.
+*     21-SEP-2001 (DSB):
+*        Removed astBear method.
+*     10-OCT-2002 (DSB):
+*        Moved definitions of macros for SkyFrame system values from
+*        this file into skyframe.h.
+*     24-OCT-2002 (DSB):
+*        Modified MakeSkyMapping so that any two SkyFrames with system=unknown
+*        are assumed to be related by a UnitMap. previously, they were
+*        considered to be unrelated, resulting in no ability to convert from
+*        one to the other. This could result for instance in astConvert
+*        being unable to find a maping from a SkyFrame to itself.
+*     15-NOV-2002 (DSB):
+*        Moved System and Epoch attributes to the Frame class.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitSkyFrameVtab
+*        method.
+*     11-JUN-2003 (DSB):
+*        Added ICRS option for System attribute, and made it the default
+*        in place of FK5.
+*     27-SEP-2003 (DSB):
+*        Added HELIOECLIPTIC option for System attribute.
+*     19-APR-2004 (DSB):
+*        Added SkyRef, SkyRefIs, SkyRefP and AlignOffset attributes.
+*     8-SEP-2004 (DSB):
+*        Added astResolvePoints method.
+*     2-DEC-2004 (DSB):
+*        Added System "J2000"
+*     27-JAN-2005 (DSB):
+*        Fix memory leaks in astLoadSkyFrame_ and Match.
+*     7-APR-2005 (DSB):
+*        Allow SkyRefIs to be set to "Ignored".
+*     12-MAY-2005 (DSB):
+*        Override astNormBox method.
+*     15-AUG-2005 (DSB):
+*        Added AZEL system.
+*     13-SEP-2005 (DSB):
+*        Override astClearSystem so that SkyRef/SkyRefPcan be converted
+*        from the original System to the default System.
+*     19-SEP-2005 (DSB):
+*        Changed default for SkyRefIs from ORIGIN to IGNORED.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     22-FEB-2006 (DSB):
+*        Store the Local Apparent Sidereal Time in the SkyFrame structure
+*        in order to avoid expensive re-computations.
+*     22-AUG-2006 (DSB):
+*        Ensure the cached Local Apparent Siderial Time is initialised
+*        when initialising or loading a SkyFrame.
+*     22-SEP-2006 (DSB):
+*        Report an error in SetSystem if it is not possible to convert
+*        from old to new systems.
+*     3-OCT-2006 (DSB):
+*        Added Equation of Equinoxes to the SkyFrame structure.
+*     6-OCT-2006 (DSB):
+*        - Guard against annulling null pointers in subFrame.
+*        - Add Dut1 attribute
+*        - Use linear approximation for LAST over short periods (less
+*          than 0.001 of a day)
+*        - Remove Equation of Equinoxes from the SkyFrame structure.
+*     10-OCT-2006 (DSB):
+*        Use "AlOff" instead of "AlignOffset" as the external channel name
+*        for the AlignOffset attribute. The longer form exceeded the
+*        limit that can be used by the Channel class.
+*     14-OCT-2006 (DSB):
+*        - Move Dut1 attribute to the Frame class.
+*        - Use the TimeFrame class to do the TDB->LAST conversions.
+*     17-JAN-2007 (DSB):
+*        - Use a UnitMap to align offset coordinate systems in two
+*        SkyFrames, regardless of other attribute values.
+*        - Only align in offset coordinates if both target and template
+*        have a non-zero value for AlignOffset.
+*     23-JAN-2007 (DSB):
+*        Modified so that a SkyFrame can be used as a template to find a
+*        SkyFrame contained within a CmpFrame. This involves changes in
+*        Match and the removal of the local versions of SetMaxAxes and
+*        SetMinAxes.
+*     4-JUL-2007 (DSB):
+*        Modified GetLast to use the correct solar to sidereal conversion
+*        factor. As a consequence the largest acceptable epoch gap before
+*        the LAST needs to be recalculated has been increased.
+*     11-JUL-2007 (DSB):
+*        Override astSetEpoch and astClearEpoch by implementations which
+*        update the LAST value stored in the SkyFrame.
+*     7-AUG-2007 (DSB):
+*        - Set a value for the CentreZero attribute when extracting a
+*        SkyAxis from a SkyFrame in function SubFrame.
+*        - In SubFrame, clear extended attributes such as System after
+*        all axis attributes have been "fixated.
+*     30-AUG-2007 (DSB):
+*        Override astSetDut1 and astClearDut1 by implementations which
+*        update the LAST value stored in the SkyFrame.
+*     31-AUG-2007 (DSB):
+*        - Cache the magnitude of the diurnal aberration vector in the
+*        SkyFrame structure for use when correcting for diurnal aberration.
+*        - Modify the azel conversions to include correction for diurnal
+*        aberration.
+*        - Override astClearObsLat and astSetObsLat by implementations which
+*        reset the magnitude of the diurnal aberration vector.
+*     3-SEP-2007 (DSB):
+*        In SubFrame, since AlignSystem is extended by the SkyFrame class
+*        it needs to be cleared before invoking the parent SubFrame
+*        method in cases where the result Frame is not a SkyFrame.
+*     2-OCT-2007 (DSB):
+*        In Overlay, clear AlignSystem as well as System before calling
+*        the parent overlay method.
+*     10-OCT-2007 (DSB):
+*        In MakeSkyMapping, correct the usage of variables "system" and
+*        "align_sys" when aligning in AZEL.
+*     18-OCT-2007 (DSB):
+*        Compare target and template AlignSystem values in Match, rather
+*        than comparing target and result AlignSystem values in MakeSkyMapping
+*        (since result is basically a copy of target).
+*     27-NOV-2007 (DSB):
+*        - Modify SetSystem to ensure that SkyRef and SkyRefP position are
+*        always transformed as absolute values, rather than as offset
+*        values.
+*        - Modify SubMatch so that a value of zero is assumed for
+*        AlignOffset when restoring thre integrity of a FrameSet.
+*     15-DEC-2008 (DSB):
+*        Improve calculation of approximate Local Apparent Sidereal time
+*        by finding and using the ratio of solar to sidereal time
+*        independently for each approximation period.
+*     14-JAN-2009 (DSB):
+*        Override the astIntersect method.
+*     21-JAN-2009 (DSB):
+*        Fix mis-use of results buffers for GetFormat and GetAttrib.
+*     16-JUN-2009 (DSB):
+*        All sky coordinate systems currently supported by SkyFrame are
+*        left handed. So fix GetDirection method to return zero for all
+*        longitude axes and 1 for all latitude axes.
+*     18-JUN-2009 (DSB):
+*        Incorporate the new ObsAlt attribute.
+*     23-SEP-2009 (DSB):
+*        Allow some rounding error when checking for changes in SetObsLon
+*        and SetDut1. This reduces the number of times the expensive
+*        calculation of LAST is performed.
+*     24-SEP-2009 (DSB);
+*        Create a static cache of LAST values stored in the class virtual
+*        function table. These are used in preference to calculating a new
+*        value from scratch.
+*     25-SEP-2009 (DSB);
+*        Do not calculate LAST until it is needed.
+*     12-OCT-2009 (DSB);
+*        - Handle 2.PI->0 discontinuity in cached LAST values.
+*     12-OCT-2009 (BEC);
+*        - Fix bug in caching LAST value.
+*     31-OCT-2009 (DSB);
+*        Correct SetCachedLAST to handle cases where the epoch to be
+*        stored is smaller than any epoch already in the table.
+*     24-NOV-2009 (DSB):
+*        - In CalcLast, only use end values form the table of stored
+*        LAST values if the corresponding epochs are within 0.001 of
+*        a second of the required epoch (this tolerance used to be
+*        0.1 seconds).
+*        - Do not clear the cached LAST value in SetEpoch and ClearEpoch.
+*     8-MAR-2010 (DSB):
+*        Add astSkyOffsetMap method.
+*     7-APR-2010 (DSB):
+*        Add IsLatAxis and IsLonAxis attributes.
+*     11-MAY-2010 (DSB):
+*        In SetSystem, clear SkyRefP as well as SkyRef.
+*     22-MAR-2011 (DSB):
+*        Override astFrameGrid method.
+*     29-APR-2011 (DSB):
+*        Prevent astFindFrame from matching a subclass template against a
+*        superclass target.
+*     23-MAY-2011 (DSB):
+*        Truncate returned PointSet in function FrameGrid to exclude unused points.
+*     24-MAY-2011 (DSB):
+*        When clearing or setting the System attribute, clear SkyRef rather
+*        than reporting an error if the Mapping from the old System to the
+*        new System is unknown.
+*     30-NOV-2011 (DSB):
+*        When aligning two SkyFrames in the system specified by AlignSystem,
+*        do not assume inappropriate default equinox values for systems
+*        that are not referred to the equinox specified by the Equinox attribute.
+*     26-APR-2012 (DSB):
+*        - Correct Dump function so that any axis permutation is taken into
+*        account when dumping SkyFrame attributes that have a separate value
+*        for each axis (e.g. SkyRef and SkyRefP).
+*        - Take axis permutation into account when setting a new value
+*        for attributes that have a separate value for each axis (e.g.
+*        SkyRef and SkyRefP).
+*        - Remove the code that overrides ClearEpoch and SetEpoch (these
+*        overrides have not been needed since the changes made on
+*        24/11/2009).
+*     27-APR-2012 (DSB):
+*        - Correct astLoadSkyFrame function so that any axis permutation is
+*        taken into account when loading SkyFrame attributes that have a
+*        separate value for each axis.
+*     25-JUL-2013 (DSB):
+*        Use a single table of cached LAST values for all threads, rather
+*        than a separate table for each thread. The problem with a table per
+*        thread  is that if you have N threads, each table contains only
+*        one N'th of the total number of cached values, resulting in
+*        poorer accuracy, and small variations in interpolated LAST value
+*        depending on the way the cached values are distributed amongst the
+*        N threads.
+*     6-AST-2013 (DSB):
+*        Fix the use of the read-write lock that is used to serialise
+*        access to the table of cached LAST values. This bug could
+*        cause occasional problems where an AST pointer would became
+*        invalid for no apparent reason.
+*     21-FEB-2014 (DSB):
+*        Rounding errors in the SkyLineDef constructor could result in the line
+*        between coincident points being given a non-zero length.
+*     6-JUL-2015 (DSB):
+*        Added SkyTol attribute.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS SkyFrame
+
+/* Define the first and last acceptable System values. */
+#define FIRST_SYSTEM AST__FK4
+#define LAST_SYSTEM AST__AZEL
+
+/* Speed of light (AU per day) (from SLA_AOPPA) */
+#define C 173.14463331
+
+/* Ratio between solar and sidereal time (from SLA_AOPPA) */
+#define SOLSID 1.00273790935
+
+/* Define values for the different values of the SkyRefIs attribute. */
+#define POLE_STRING "Pole"
+#define ORIGIN_STRING "Origin"
+#define IGNORED_STRING "Ignored"
+
+/* Define other numerical constants for use in this module. */
+#define GETATTRIB_BUFF_LEN 200
+#define GETFORMAT_BUFF_LEN 50
+#define GETLABEL_BUFF_LEN 40
+#define GETSYMBOL_BUFF_LEN 20
+#define GETTITLE_BUFF_LEN 200
+
+/* A macro which returns a flag indicating if the supplied system is
+   references to the equinox specified by the Equinox attribute. */
+#define EQREF(system) \
+((system==AST__FK4||system==AST__FK4_NO_E||system==AST__FK5||system==AST__ECLIPTIC)?1:0)
+
+/*
+*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Implement a method to clear a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     MAKE_CLEAR(attr,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the SkyFrame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstSkyFrame *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( AstSkyFrame *this, int axis )
+*
+*     which implement a method for clearing a single value in a specified
+*     multi-valued attribute for an axis of a SkyFrame.
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. Label in "astClearLabelAt").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attr,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attr( AstSkyFrame *this, int axis, int *status ) { \
+\
+   int axis_p; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate and permute the axis index. */ \
+   axis_p = astValidateAxis( this, axis, 1, "astClear" #attr ); \
+\
+/* Assign the "clear" value. */ \
+   if( astOK ) { \
+      this->component[ axis_p ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attr##_( AstSkyFrame *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,SkyFrame,Clear##attr))( this, axis, status ); \
+}
+
+
+/*
+*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Implement a method to get a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     MAKE_GET(attr,type,bad_value,assign,nval)
+
+*  Class Membership:
+*     Defined by the SkyFrame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( AstSkyFrame *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        <Type> astGet<Attribute>_( AstSkyFrame *this, int axis )
+*
+*     which implement a method for getting a single value from a specified
+*     multi-valued attribute for an axis of a SkyFrame.
+
+*  Parameters:
+*     attr
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. Label in "astGetLabel").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set, or if
+*        the function fails.
+*     assign
+*        An expression that evaluates to the value to be returned. This can
+*        use the string "axis" to represent the zero-based value index.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attr,type,bad_value,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type Get##attr( AstSkyFrame *this, int axis, int *status ) { \
+   int axis_p;                   /* Permuted axis index */ \
+   type result;                  /* Result to be returned */ \
+\
+/* Initialise */\
+   result = (bad_value); \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate and permute the axis index. */ \
+   axis_p = astValidateAxis( this, axis, 1, "astGet" #attr ); \
+\
+/* Assign the result value. */ \
+   if( astOK ) { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */  \
+type astGet##attr##_( AstSkyFrame *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,SkyFrame,Get##attr))( this, axis, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Implement a method to set a single value in a multi-valued attribute
+*     for a SkyFrame.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     MAKE_SET(attr,type,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the SkyFrame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstSkyFrame *this, int axis, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( AstSkyFrame *this, int axis, <Type> value )
+*
+*     which implement a method for setting a single value in a specified
+*     multi-valued attribute for a SkyFrame.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. Label in "astSetLabelAt").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+*      nval
+*         Specifies the number of values in the multi-valued attribute. The
+*         "axis" values supplied to the created function should be in the
+*         range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attr,type,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attr( AstSkyFrame *this, int axis, type value, int *status ) { \
+\
+   int axis_p; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate and permute the axis index. */ \
+   axis_p = astValidateAxis( this, axis, 1, "astSet" #attr ); \
+\
+/* Store the new value in the structure component. */ \
+   if( astOK ) { \
+      this->component[ axis_p ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attr##_( AstSkyFrame *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,SkyFrame,Set##attr))( this, axis, value, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_TEST
+
+*  Purpose:
+*     Implement a method to test if a single value has been set in a
+*     multi-valued attribute for a class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     MAKE_TEST(attr,assign,nval)
+
+*  Class Membership:
+*     Defined by the SkyFrame class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static int Test<Attribute>( AstSkyFrame *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        int astTest<Attribute>_( AstSkyFrame *this, int axis )
+*
+*     which implement a method for testing if a single value in a specified
+*     multi-valued attribute has been set for a class.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be tested, as it appears in the function
+*         name (e.g. Label in "astTestLabelAt").
+*      assign
+*         An expression that evaluates to 0 or 1, to be used as the returned
+*         value. This can use the string "axis" to represent the zero-based
+*         index of the value within the attribute.
+*      nval
+*         Specifies the number of values in the multi-valued attribute. The
+*         "axis" values supplied to the created function should be in the
+*         range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_TEST(attr,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static int Test##attr( AstSkyFrame *this, int axis, int *status ) { \
+   int result;                   /* Value to return */ \
+   int axis_p;                   /* Permuted axis index */ \
+\
+/* Initialise */ \
+   result =0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate and permute the axis index. */ \
+   axis_p = astValidateAxis( this, axis, 1, "astTest" #attr ); \
+\
+/* Assign the result value. */ \
+   if( astOK ) { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */ \
+int astTest##attr##_( AstSkyFrame *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,SkyFrame,Test##attr))( this, axis, status ); \
+}
+
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points (for AST__BAD) */
+#include "unitmap.h"             /* Unit Mappings */
+#include "permmap.h"             /* Coordinate permutations */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "slamap.h"              /* SLALIB sky coordinate Mappings */
+#include "timemap.h"             /* Time conversions */
+#include "skyaxis.h"             /* Sky axes */
+#include "frame.h"               /* Parent Frame class */
+#include "matrixmap.h"           /* Matrix multiplication */
+#include "sphmap.h"              /* Cartesian<->Spherical transformations */
+#include "skyframe.h"            /* Interface definition for this class */
+#include "pal.h"                 /* SLALIB library interface */
+#include "wcsmap.h"              /* Factors of PI */
+#include "timeframe.h"           /* Time system transformations */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Type Definitions. */
+/* ================= */
+
+/* Cached Line structure. */
+/* ---------------------- */
+/* This structure contains information describing a line segment within a
+   SkyFrame. It differs from the AstLineDef defined in frame.h because
+   positions are represented by 3D (x,y,z) cartesian coords rather than
+   2D (long,lat) coords. */
+
+typedef struct SkyLineDef {
+   AstFrame *frame;            /* Pointer to Frame in which the line is defined */
+   double length;              /* Line length */
+   int infinite;               /* Disregard the start and end of the line? */
+   double start[3];            /* Unit vector defining start of line */
+   double end[3];              /* Unit vector defining end of line */
+   double dir[3];              /* Unit vector defining line direction */
+   double q[3];                /* Unit vector perpendicular to line */
+   double start_2d[2];
+   double end_2d[2];
+} SkyLineDef;
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are used or extended by this
+   class. */
+static AstSystemType (* parent_getalignsystem)( AstFrame *, int * );
+static AstSystemType (* parent_getsystem)( AstFrame *, int * );
+static const char *(* parent_format)( AstFrame *, int, double, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static const char *(* parent_getdomain)( AstFrame *, int * );
+static const char *(* parent_getformat)( AstFrame *, int, int * );
+static const char *(* parent_getlabel)( AstFrame *, int, int * );
+static const char *(* parent_getsymbol)( AstFrame *, int, int * );
+static const char *(* parent_gettitle)( AstFrame *, int * );
+static const char *(* parent_getunit)( AstFrame *, int, int * );
+static double (* parent_gap)( AstFrame *, int, double, int *, int * );
+static double (* parent_getbottom)( AstFrame *, int, int * );
+static double (* parent_getepoch)( AstFrame *, int * );
+static double (* parent_gettop)( AstFrame *, int, int * );
+static int (* parent_getdirection)( AstFrame *, int, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+static int (* parent_match)( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int (* parent_subframe)( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static int (* parent_testformat)( AstFrame *, int, int * );
+static int (* parent_unformat)( AstFrame *, int, const char *, double *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_cleardut1)( AstFrame *, int * );
+static void (* parent_clearformat)( AstFrame *, int, int * );
+static void (* parent_clearobsalt)( AstFrame *, int * );
+static void (* parent_clearobslat)( AstFrame *, int * );
+static void (* parent_clearobslon)( AstFrame *, int * );
+static void (* parent_clearsystem)( AstFrame *, int * );
+static void (* parent_overlay)( AstFrame *, const int *, AstFrame *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static void (* parent_setdut1)( AstFrame *, double, int * );
+static void (* parent_setformat)( AstFrame *, int, const char *, int * );
+static void (* parent_setobsalt)( AstFrame *, double, int * );
+static void (* parent_setobslat)( AstFrame *, double, int * );
+static void (* parent_setobslon)( AstFrame *, double, int * );
+static void (* parent_setsystem)( AstFrame *, AstSystemType, int * );
+
+/* Factors for converting between hours, degrees and radians. */
+static double hr2rad;
+static double deg2rad;
+static double pi;
+static double piby2;
+
+/* Table of cached Local Apparent Sidereal Time values and corresponding
+   epochs. */
+static int nlast_tables = 0;
+static AstSkyLastTable **last_tables = NULL;
+
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->GetFormat_Buff[ 0 ] = 0; \
+   globals->GetLabel_Buff[ 0 ] = 0; \
+   globals->GetSymbol_Buff[ 0 ] = 0; \
+   globals->GetTitle_Buff[ 0 ] = 0; \
+   globals->GetTitle_Buff2[ 0 ] = 0; \
+   globals->TDBFrame = NULL; \
+   globals->LASTFrame = NULL; \
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(SkyFrame)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(SkyFrame,Class_Init)
+#define class_vtab astGLOBAL(SkyFrame,Class_Vtab)
+#define getattrib_buff astGLOBAL(SkyFrame,GetAttrib_Buff)
+#define getformat_buff astGLOBAL(SkyFrame,GetFormat_Buff)
+#define getlabel_buff astGLOBAL(SkyFrame,GetLabel_Buff)
+#define getsymbol_buff astGLOBAL(SkyFrame,GetSymbol_Buff)
+#define gettitle_buff astGLOBAL(SkyFrame,GetTitle_Buff)
+#define gettitle_buff2 astGLOBAL(SkyFrame,GetTitle_Buff2)
+#define tdbframe astGLOBAL(SkyFrame,TDBFrame)
+#define lastframe astGLOBAL(SkyFrame,LASTFrame)
+
+
+
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+/* A read-write lock used to protect the table of cached LAST values so
+   that multiple threads can read simultaneously so long as no threads are
+   writing to the table. */
+static pthread_rwlock_t rwlock1=PTHREAD_RWLOCK_INITIALIZER;
+#define LOCK_WLOCK1 pthread_rwlock_wrlock( &rwlock1 );
+#define LOCK_RLOCK1 pthread_rwlock_rdlock( &rwlock1 );
+#define UNLOCK_RWLOCK1 pthread_rwlock_unlock( &rwlock1 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Buffer returned by GetAttrib. */
+static char getattrib_buff[ GETATTRIB_BUFF_LEN + 1 ];
+
+/* Buffer returned by GetFormat. */
+static char getformat_buff[ GETFORMAT_BUFF_LEN + 1 ];
+
+/* Default GetLabel string buffer */
+static char getlabel_buff[ GETLABEL_BUFF_LEN + 1 ];
+
+/* Default GetSymbol buffer */
+static char getsymbol_buff[ GETSYMBOL_BUFF_LEN + 1 ];
+
+/* Default Title string buffer */
+static char gettitle_buff[ AST__SKYFRAME_GETTITLE_BUFF_LEN + 1 ];
+static char gettitle_buff2[ AST__SKYFRAME_GETTITLE_BUFF_LEN + 1 ];
+
+/* TimeFrames for doing TDB<->LAST conversions. */
+static AstTimeFrame *tdbframe = NULL;
+static AstTimeFrame *lastframe = NULL;
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstSkyFrameVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+
+#define LOCK_WLOCK1
+#define LOCK_RLOCK1
+#define UNLOCK_RWLOCK1
+
+#endif
+
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstLineDef *LineDef( AstFrame *, const double[2], const double[2], int * );
+static AstMapping *SkyOffsetMap( AstSkyFrame *, int * );
+static AstPointSet *FrameGrid( AstFrame *, int, const double *, const double *, int * );
+static AstPointSet *ResolvePoints( AstFrame *, const double [], const double [], AstPointSet *, AstPointSet *, int * );
+static AstSystemType GetAlignSystem( AstFrame *, int * );
+static AstSystemType GetSystem( AstFrame *, int * );
+static AstSystemType SystemCode( AstFrame *, const char *, int * );
+static AstSystemType ValidateSystem( AstFrame *, AstSystemType, const char *, int * );
+static const char *Format( AstFrame *, int, double, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static const char *GetDomain( AstFrame *, int * );
+static const char *GetFormat( AstFrame *, int, int * );
+static const char *GetLabel( AstFrame *, int, int * );
+static const char *GetProjection( AstSkyFrame *, int * );
+static const char *GetSymbol( AstFrame *, int, int * );
+static const char *GetTitle( AstFrame *, int * );
+static const char *GetUnit( AstFrame *, int, int * );
+static const char *SystemString( AstFrame *, AstSystemType, int * );
+static double Angle( AstFrame *, const double[], const double[], const double[], int * );
+static double CalcLAST( AstSkyFrame *, double, double, double, double, double, int * );
+static double Distance( AstFrame *, const double[], const double[], int * );
+static double Gap( AstFrame *, int, double, int *, int * );
+static double GetBottom( AstFrame *, int, int * );
+static double GetCachedLAST( AstSkyFrame *, double, double, double, double, double, int * );
+static double GetEpoch( AstFrame *, int * );
+static double GetEquinox( AstSkyFrame *, int * );
+static void SetCachedLAST( AstSkyFrame *, double, double, double, double, double, double, int * );
+static void SetLast( AstSkyFrame *, int * );
+static double GetTop( AstFrame *, int, int * );
+static double Offset2( AstFrame *, const double[2], double, double, double[2], int * );
+static double GetDiurab( AstSkyFrame *, int * );
+static double GetLAST( AstSkyFrame *, int * );
+static int GetActiveUnit( AstFrame *, int * );
+static int GetAsTime( AstSkyFrame *, int, int * );
+static int GetDirection( AstFrame *, int, int * );
+static int GetIsLatAxis( AstSkyFrame *, int, int * );
+static int GetIsLonAxis( AstSkyFrame *, int, int * );
+static int GetLatAxis( AstSkyFrame *, int * );
+static int GetLonAxis( AstSkyFrame *, int * );
+static int GetNegLon( AstSkyFrame *, int * );
+static int GetObjSize( AstObject *, int * );
+static int IsEquatorial( AstSystemType, int * );
+static int LineContains( AstFrame *, AstLineDef *, int, double *, int * );
+static int LineCrossing( AstFrame *, AstLineDef *, AstLineDef *, double **, int * );
+static int LineIncludes( SkyLineDef *, double[3], int * );
+static int MakeSkyMapping( AstSkyFrame *, AstSkyFrame *, AstSystemType, AstMapping **, int * );
+static int Match( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int SubFrame( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int TestActiveUnit( AstFrame *, int * );
+static int TestAsTime( AstSkyFrame *, int, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestEquinox( AstSkyFrame *, int * );
+static int TestNegLon( AstSkyFrame *, int * );
+static int TestProjection( AstSkyFrame *, int * );
+static int TestSlaUnit( AstSkyFrame *, AstSkyFrame *, AstSlaMap *, int * );
+static int Unformat( AstFrame *, int, const char *, double *, int * );
+static void ClearAsTime( AstSkyFrame *, int, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearDut1( AstFrame *, int * );
+static void ClearEquinox( AstSkyFrame *, int * );
+static void ClearNegLon( AstSkyFrame *, int * );
+static void ClearObsAlt( AstFrame *, int * );
+static void ClearObsLat( AstFrame *, int * );
+static void ClearObsLon( AstFrame *, int * );
+static void ClearProjection( AstSkyFrame *, int * );
+static void ClearSystem( AstFrame *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void Intersect( AstFrame *, const double[2], const double[2], const double[2], const double[2], double[2], int * );
+static void LineOffset( AstFrame *, AstLineDef *, double, double, double[2], int * );
+static void MatchAxesX( AstFrame *, AstFrame *, int *, int * );
+static void Norm( AstFrame *, double[], int * );
+static void NormBox( AstFrame *, double[], double[], AstMapping *, int * );
+static void Offset( AstFrame *, const double[], const double[], double, double[], int * );
+static void Overlay( AstFrame *, const int *, AstFrame *, int * );
+static void Resolve( AstFrame *, const double [], const double [], const double [], double [], double *, double *, int * );
+static void SetAsTime( AstSkyFrame *, int, int, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetDut1( AstFrame *, double, int * );
+static void SetEquinox( AstSkyFrame *, double, int * );
+static void SetNegLon( AstSkyFrame *, int, int * );
+static void SetObsAlt( AstFrame *, double, int * );
+static void SetObsLat( AstFrame *, double, int * );
+static void SetObsLon( AstFrame *, double, int * );
+static void SetProjection( AstSkyFrame *, const char *, int * );
+static void SetSystem( AstFrame *, AstSystemType, int * );
+static void Shapp( double, double *, double *, double, double *, int * );
+static void Shcal( double, double, double, double *, double *, int * );
+static void VerifyMSMAttrs( AstSkyFrame *, AstSkyFrame *, int, const char *, const char *, int * );
+
+static double GetSkyRef( AstSkyFrame *, int, int * );
+static int TestSkyRef( AstSkyFrame *, int, int * );
+static void SetSkyRef( AstSkyFrame *, int, double, int * );
+static void ClearSkyRef( AstSkyFrame *, int, int * );
+
+static double GetSkyRefP( AstSkyFrame *, int, int * );
+static int TestSkyRefP( AstSkyFrame *, int, int * );
+static void SetSkyRefP( AstSkyFrame *, int, double, int * );
+static void ClearSkyRefP( AstSkyFrame *, int, int * );
+
+static int GetSkyRefIs( AstSkyFrame *, int * );
+static int TestSkyRefIs( AstSkyFrame *, int * );
+static void SetSkyRefIs( AstSkyFrame *, int, int * );
+static void ClearSkyRefIs( AstSkyFrame *, int * );
+
+static int GetAlignOffset( AstSkyFrame *, int * );
+static int TestAlignOffset( AstSkyFrame *, int * );
+static void SetAlignOffset( AstSkyFrame *, int, int * );
+static void ClearAlignOffset( AstSkyFrame *, int * );
+
+static double GetSkyTol( AstSkyFrame *, int * );
+static int TestSkyTol( AstSkyFrame *, int * );
+static void SetSkyTol( AstSkyFrame *, double, int * );
+static void ClearSkyTol( AstSkyFrame *, int * );
+
+/* Member functions. */
+/* ================= */
+static double Angle( AstFrame *this_frame, const double a[],
+                     const double b[], const double c[], int *status ) {
+/*
+*  Name:
+*     Angle
+
+*  Purpose:
+*     Calculate the angle subtended by two points at a third point.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double Angle( AstFrame *this_frame, const double a[],
+*                   const double b[], const double c[], int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astAngle method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function finds the angle at point B between the line
+*     joining points A and B, and the line joining points C
+*     and B. These lines will in fact be geodesic curves (great circles).
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     a
+*        An array of double, with one element for each SkyFrame axis,
+*        containing the coordinates of the first point.
+*     b
+*        An array of double, with one element for each SkyFrame axis,
+*        containing the coordinates of the second point.
+*     c
+*        An array of double, with one element for each SkyFrame axis,
+*        containing the coordinates of the third point.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The angle in radians, from the line AB to the line CB, in
+*     the range $\pm \pi$ with positive rotation is in the same sense
+*     as rotation from axis 2 to axis 1.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input coordinates has this value.
+*     - A "bad" value will also be returned if points A and B are
+*     co-incident, or if points B and C are co-incident.
+*     - A "bad" value will also be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+   AstSkyFrame *this;            /* Pointer to SkyFrame structure */
+   const int *perm;              /* Axis permutation array */
+   double aa[ 2 ];               /* Permuted a coordinates */
+   double anga;                  /* Angle from north to the line BA */
+   double angc;                  /* Angle from north to the line BC */
+   double bb[ 2 ];               /* Permuted b coordinates */
+   double cc[ 2 ];               /* Permuted c coordinates */
+   double result;                /* Value to return */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+   perm = astGetPerm( this );
+   if ( astOK ) {
+
+/* Check that all supplied coordinates are OK. */
+      if ( ( a[ 0 ] != AST__BAD ) && ( a[ 1 ] != AST__BAD ) &&
+           ( b[ 0 ] != AST__BAD ) && ( b[ 1 ] != AST__BAD ) &&
+           ( c[ 0 ] != AST__BAD ) && ( c[ 1 ] != AST__BAD ) ) {
+
+/* Apply the axis permutation array to obtain the coordinates of the
+   three points in the required (longitude,latitude) order. */
+         aa[ perm[ 0 ] ] = a[ 0 ];
+         aa[ perm[ 1 ] ] = a[ 1 ];
+         bb[ perm[ 0 ] ] = b[ 0 ];
+         bb[ perm[ 1 ] ] = b[ 1 ];
+         cc[ perm[ 0 ] ] = c[ 0 ];
+         cc[ perm[ 1 ] ] = c[ 1 ];
+
+/* Check that A and B are not co-incident. */
+         if( aa[ 0 ] != bb[ 0 ] || aa[ 1 ] != bb[ 1 ] ) {
+
+/* Check that C and B are not co-incident. */
+            if( cc[ 0 ] != bb[ 0 ] || cc[ 1 ] != bb[ 1 ] ) {
+
+/* Find the angle from north to the line BA. */
+               anga = palDbear( bb[ 0 ], bb[ 1 ], aa[ 0 ], aa[ 1 ] );
+
+/* Find the angle from north to the line BC. */
+               angc = palDbear( bb[ 0 ], bb[ 1 ], cc[ 0 ], cc[ 1 ] );
+
+/* Find the difference. */
+               result = angc - anga;
+
+/* This value is the angle from north, but we want the angle from axis 2.
+   If the axes have been swapped so that axis 2 is actually the longitude
+   axis, then we need to correct this result. */
+               if( perm[ 0 ] != 0 ) result = piby2 - result;
+
+/* Fold the result into the range +/- PI. */
+               result = palDrange( result );
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double CalcLAST( AstSkyFrame *this, double epoch, double obslon,
+                        double obslat, double obsalt, double dut1,
+                        int *status ) {
+/*
+*  Name:
+*     CalcLAST
+
+*  Purpose:
+*     Calculate the Local Appearent Sidereal Time for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double CalcLAST( AstSkyFrame *this, double epoch, double obslon,
+*                      double obslat, double obsalt, double dut1,
+*                      int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function calculates and returns the Local Apparent Sidereal Time
+*     at the given epoch, etc.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     epoch
+*        The epoch (MJD).
+*     obslon
+*        Observatory geodetic longitude (radians)
+*     obslat
+*        Observatory geodetic latitude (radians)
+*     obsalt
+*        Observatory geodetic altitude (metres)
+*     dut1
+*        The UT1-UTC correction, in seconds.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    The Local Apparent Sidereal Time, in radians.
+
+*  Notes:
+*     -  A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS /* Declare the thread specific global data */
+   AstFrameSet *fs;   /* Mapping from TDB offset to LAST offset */
+   double epoch0;     /* Supplied epoch value */
+   double result;     /* Returned LAST value */
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* See if the required LAST value can be determined from the cached LAST
+   values in the SkyFrame virtual function table. */
+   result = GetCachedLAST( this, epoch, obslon, obslat, obsalt, dut1,
+                           status );
+
+/* If not, we do an exact calculation from scratch. */
+   if( result == AST__BAD ) {
+
+/* If not yet done, create two TimeFrames. Note, this is done here
+   rather than in astInitSkyFrameVtab in order to avoid infinite vtab
+   initialisation loops (caused by the TimeFrame class containing a
+   static SkyFrame). */
+      if( ! tdbframe ) {
+         astBeginPM;
+         tdbframe = astTimeFrame( "system=mjd,timescale=tdb", status );
+         lastframe = astTimeFrame( "system=mjd,timescale=last", status );
+         astEndPM;
+      }
+
+/* For better accuracy, use this integer part of the epoch as the origin of
+   the two TimeFrames. */
+      astSetTimeOrigin( tdbframe, (int) epoch );
+      astSetTimeOrigin( lastframe, (int) epoch );
+
+/* Convert the absolute Epoch value to an offset from the above origin. */
+      epoch0 = epoch;
+      epoch -= (int) epoch;
+
+/* Store the observers position in the two TimeFrames. */
+      astSetObsLon( tdbframe, obslon );
+      astSetObsLon( lastframe, obslon );
+
+      astSetObsLat( tdbframe, obslat );
+      astSetObsLat( lastframe, obslat );
+
+      astSetObsAlt( tdbframe, obsalt );
+      astSetObsAlt( lastframe, obsalt );
+
+/* Store the DUT1 value. */
+      astSetDut1( tdbframe, dut1 );
+      astSetDut1( lastframe, dut1 );
+
+/* Get the conversion from tdb mjd offset to last mjd offset. */
+      fs = astConvert( tdbframe, lastframe, "" );
+
+/* Use it to transform the SkyFrame Epoch from TDB offset to LAST offset. */
+      astTran1( fs, 1, &epoch, 1, &epoch );
+      fs = astAnnul( fs );
+
+/* Convert the LAST offset from days to radians. */
+      result = ( epoch - (int) epoch )*2*AST__DPI;
+
+/* Cache the new LAST value in the SkyFrame virtual function table. */
+      SetCachedLAST( this, result, epoch0, obslon, obslat, obsalt, dut1,
+                     status );
+   }
+
+/* Return the required LAST value. */
+   return result;
+}
+
+static void ClearAsTime( AstSkyFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     ClearAsTime
+
+*  Purpose:
+*     Clear the value of the AsTime attribute for a SkyFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void ClearAsTime( AstSkyFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function clears any value that has been set for the AsTime
+*     attribute for a specified axis of a SkyFrame. This attribute indicates
+*     whether axis values should be formatted as times (as opposed to angles)
+*     by default.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Index of the axis for which the value is to be cleared (zero based).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void.
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astClearAsTime" );
+
+/* Obtain a pointer to the Axis object. */
+   ax = astGetAxis( this, axis );
+
+/* If the Axis is a SkyAxis, clear the AsTime attribute (if it is not a
+   SkyAxis, it will not have this attribute anyway). */
+   if ( astIsASkyAxis( ax ) ) astClearAxisAsTime( ax );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astClearAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     SkyFrame, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   int axis;                     /* SkyFrame axis number */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* No. characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_object;
+
+/* Obtain the length of the "attrib" string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* AsTime(axis). */
+/* ------------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( attrib, "astime(%d)%n", &axis, &nc ) )
+        && ( nc >= len ) ) {
+      astClearAsTime( this, axis - 1 );
+
+/* Equinox. */
+/* -------- */
+   } else if ( !strcmp( attrib, "equinox" ) ) {
+      astClearEquinox( this );
+
+/* NegLon. */
+/* ------- */
+   } else if ( !strcmp( attrib, "neglon" ) ) {
+      astClearNegLon( this );
+
+/* Projection. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "projection" ) ) {
+      astClearProjection( this );
+
+/* SkyRef. */
+/* ------- */
+   } else if ( !strcmp( attrib, "skyref" ) ) {
+      astClearSkyRef( this, 0 );
+      astClearSkyRef( this, 1 );
+
+/* SkyTol. */
+/* ------- */
+   } else if ( !strcmp( attrib, "skytol" ) ) {
+      astClearSkyTol( this );
+
+/* SkyRef(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( attrib, "skyref(%d)%n", &axis, &nc ) )
+        && ( nc >= len ) ) {
+      astClearSkyRef( this, axis - 1 );
+
+/* SkyRefP. */
+/* -------- */
+   } else if ( !strcmp( attrib, "skyrefp" ) ) {
+      astClearSkyRefP( this, 0 );
+      astClearSkyRefP( this, 1 );
+
+/* SkyRefP(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( attrib, "skyrefp(%d)%n", &axis, &nc ) )
+        && ( nc >= len ) ) {
+      astClearSkyRefP( this, axis - 1 );
+
+/* SkyRefIs. */
+/* --------- */
+   } else if ( !strcmp( attrib, "skyrefis" ) ) {
+      astClearSkyRefIs( this );
+
+/* AlignOffset. */
+/* ------------ */
+   } else if ( !strcmp( attrib, "alignoffset" ) ) {
+      astClearAlignOffset( this );
+
+/* If the name was not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then report an
+   error. */
+   } else if ( !strncmp( attrib, "islataxis", 9 ) ||
+               !strncmp( attrib, "islonaxis", 9 ) ||
+               !strcmp( attrib, "lataxis" ) ||
+               !strcmp( attrib, "lonaxis" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearDut1( AstFrame *this, int *status ) {
+/*
+*  Name:
+*     ClearDut1
+
+*  Purpose:
+*     Clear the value of the Dut1 attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void ClearDut1( AstFrame *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astClearDut1 method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the Dut1 value and updates the LAST value
+*     stored in the SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double orig;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Note the original value */
+   orig = astGetDut1( this );
+
+/* Invoke the parent method to clear the Frame Dut1 */
+   (*parent_cleardut1)( this, status );
+
+/* If the DUT1 value has changed significantly, indicate that the LAST value
+   will need to be re-calculated when it is next needed. */
+   if( fabs( orig - astGetDut1( this ) ) > 1.0E-6 ) {
+      ( (AstSkyFrame *) this )->last = AST__BAD;
+      ( (AstSkyFrame *) this )->eplast = AST__BAD;
+      ( (AstSkyFrame *) this )->klast = AST__BAD;
+   }
+}
+
+static void ClearObsAlt( AstFrame *this, int *status ) {
+/*
+*  Name:
+*     ClearObsAlt
+
+*  Purpose:
+*     Clear the value of the ObsAlt attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void ClearObsAlt( AstFrame *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astClearObsAlt method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the ObsAlt value.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double orig;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Note the original value */
+   orig = astGetObsAlt( this );
+
+/* Invoke the parent method to clear the Frame ObsAlt. */
+   (*parent_clearobsalt)( this, status );
+
+/* If the altitude has changed significantly, indicate that the LAST value
+   and magnitude of the diurnal aberration vector will need to be
+   re-calculated when next needed. */
+   if( fabs( orig - astGetObsAlt( this ) ) > 0.001 ) {
+      ( (AstSkyFrame *) this )->last = AST__BAD;
+      ( (AstSkyFrame *) this )->eplast = AST__BAD;
+      ( (AstSkyFrame *) this )->klast = AST__BAD;
+      ( (AstSkyFrame *) this )->diurab = AST__BAD;
+   }
+}
+
+static void ClearObsLat( AstFrame *this, int *status ) {
+/*
+*  Name:
+*     ClearObsLat
+
+*  Purpose:
+*     Clear the value of the ObsLat attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void ClearObsLat( AstFrame *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astClearObsLat method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the ObsLat value.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double orig;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Note the original value */
+   orig = astGetObsLat( this );
+
+/* Invoke the parent method to clear the Frame ObsLat. */
+   (*parent_clearobslat)( this, status );
+
+/* If the altitude has changed significantly, indicate that the LAST value
+   and magnitude of the diurnal aberration vector will need to be
+   re-calculated when next needed. */
+   if( fabs( orig - astGetObsLat( this ) ) > 1.0E-8 ) {
+      ( (AstSkyFrame *) this )->last = AST__BAD;
+      ( (AstSkyFrame *) this )->eplast = AST__BAD;
+      ( (AstSkyFrame *) this )->klast = AST__BAD;
+      ( (AstSkyFrame *) this )->diurab = AST__BAD;
+   }
+}
+
+static void ClearObsLon( AstFrame *this, int *status ) {
+/*
+*  Name:
+*     ClearObsLon
+
+*  Purpose:
+*     Clear the value of the ObsLon attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void ClearObsLon( AstFrame *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astClearObsLon method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the ObsLon value.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double orig;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Note the original value */
+   orig = astGetObsLon( this );
+
+/* Invoke the parent method to clear the Frame ObsLon. */
+   (*parent_clearobslon)( this, status );
+
+/* If the longitude has changed significantly, indicate that the LAST value
+   will need to be re-calculated when it is next needed. */
+   if( fabs( orig - astGetObsLon( this ) ) > 1.0E-8 ) {
+      ( (AstSkyFrame *) this )->last = AST__BAD;
+      ( (AstSkyFrame *) this )->eplast = AST__BAD;
+      ( (AstSkyFrame *) this )->klast = AST__BAD;
+   }
+}
+
+static void ClearSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     ClearSystem
+
+*  Purpose:
+*     Clear the System attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void ClearSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astClearSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the System attribute for a SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;              /* FrameSet to be used as the Mapping */
+   AstSkyFrame *sfrm;            /* Copy of original SkyFrame */
+   AstSkyFrame *this;            /* Pointer to SkyFrame structure */
+   double xin[ 2 ];              /* Axis 0 values */
+   double yin[ 2 ];              /* Axis 1 values */
+   double xout[ 2 ];             /* Axis 0 values */
+   double yout[ 2 ];             /* Axis 1 values */
+   int skyref_set;               /* Is either SkyRef attribute set? */
+   int skyrefp_set;              /* Is either SkyRefP attribute set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* See if either the SkyRef or SkyRefP attribute is set. */
+   skyref_set = astTestSkyRef( this, 0 ) || astTestSkyRef( this, 1 );
+   skyrefp_set = astTestSkyRefP( this, 0 ) || astTestSkyRefP( this, 1 );
+
+/* If so, we will need to transform their values into the new coordinate
+   system. Save a copy of the SkyFrame with its original System value. */
+   sfrm = ( skyref_set || skyrefp_set )?astCopy( this ):NULL;
+
+/* Use the parent method to clear the System value. */
+   (*parent_clearsystem)( this_frame, status );
+
+/* Now modify the SkyRef and SkyRefP attributes if necessary. */
+   if( sfrm ) {
+
+/* Save the SkyRef and SkyRefP values. */
+      xin[ 0 ] = astGetSkyRef( sfrm, 0 );
+      xin[ 1 ] = astGetSkyRefP( sfrm, 0 );
+      yin[ 0 ] = astGetSkyRef( sfrm, 1 );
+      yin[ 1 ] = astGetSkyRefP( sfrm, 1 );
+
+/* Clear the SkyRef values to avoid infinite recursion in the following
+   call to astConvert. */
+      if( skyref_set ) {
+         astClearSkyRef( sfrm, 0 );
+         astClearSkyRef( sfrm, 1 );
+         astClearSkyRef( this, 0 );
+         astClearSkyRef( this, 1 );
+      }
+
+/* Get the Mapping from the original System to the default System. Invoking
+   astConvert will recursively invoke ClearSystem again. This is why we need
+   to be careful to ensure that SkyRef is cleared above - doing so ensure
+   we do not end up with infinite recursion. */
+      fs = astConvert( sfrm, this, "" );
+
+/* Check the Mapping was found. */
+      if( fs ) {
+
+/* Use the Mapping to find the SkyRef and SkyRefP positions in the default
+   coordinate system. */
+         astTran2( fs, 2, xin, yin, 1, xout, yout );
+
+/* Store the values as required. */
+         if( skyref_set ) {
+            astSetSkyRef( this, 0, xout[ 0 ] );
+            astSetSkyRef( this, 1, yout[ 0 ] );
+         }
+
+         if( skyrefp_set ) {
+            astSetSkyRefP( this, 0, xout[ 1 ] );
+            astSetSkyRefP( this, 1, yout[ 1 ] );
+         }
+
+/* Free resources. */
+         fs = astAnnul( fs );
+
+/* If the Mapping is not defined, we cannot convert the SkyRef or SkyRefP
+   positions in the new Frame so clear them. */
+      } else {
+         if( skyref_set ) {
+            astClearSkyRef( this, 0 );
+            astClearSkyRef( this, 1 );
+         }
+         if( skyrefp_set ) {
+            astClearSkyRefP( this, 0 );
+            astClearSkyRefP( this, 1 );
+         }
+      }
+
+/* Free resources. */
+      sfrm = astAnnul( sfrm );
+   }
+}
+
+static double Distance( AstFrame *this_frame,
+                        const double point1[], const double point2[], int *status ) {
+/*
+*  Name:
+*     Distance
+
+*  Purpose:
+*     Calculate the distance between two points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double Distance( AstFrame *this,
+*                      const double point1[], const double point2[], int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astDistance method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function finds the distance between two points whose
+*     SkyFrame coordinates are given. The distance calculated is that
+*     along the geodesic curve (i.e. great circle) that joins the two
+*     points.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     point1
+*        An array of double, with one element for each SkyFrame axis,
+*        containing the coordinates of the first point.
+*     point2
+*        An array of double, with one element for each SkyFrame axis,
+*        containing the coordinates of the second point.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The distance between the two points, in radians.
+
+*  Notes:
+*     - This function will return a "bad" result value (AST__BAD) if
+*     any of the input coordinates has this value.
+*     - A "bad" value will also be returned if this function is
+*     invoked with the AST error status set or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to SkyFrame structure */
+   const int *perm;              /* Axis permutation array */
+   double p1[ 2 ];               /* Permuted point1 coordinates */
+   double p2[ 2 ];               /* Permuted point2 coordinates */
+   double result;                /* Value to return */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+   perm = astGetPerm( this );
+   if ( astOK ) {
+
+/* Check that all supplied coordinates are OK. */
+      if ( ( point1[ 0 ] != AST__BAD ) && ( point1[ 1 ] != AST__BAD ) &&
+           ( point2[ 0 ] != AST__BAD ) && ( point2[ 1 ] != AST__BAD ) ) {
+
+/* Apply the axis permutation array to obtain the coordinates of the
+   two points in the required (longitude,latitude) order. */
+         p1[ perm[ 0 ] ] = point1[ 0 ];
+         p1[ perm[ 1 ] ] = point1[ 1 ];
+         p2[ perm[ 0 ] ] = point2[ 0 ];
+         p2[ perm[ 1 ] ] = point2[ 1 ];
+
+/* Calculate the great circle distance between the points in radians. */
+         result = palDsep( p1[ 0 ], p1[ 1 ], p2[ 0 ], p2[ 1 ] );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *Format( AstFrame *this_frame, int axis, double value, int *status ) {
+/*
+*  Name:
+*     Format
+
+*  Purpose:
+*     Format a coordinate value for a SkyFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     const char *Format( AstFrame *this, int axis, double value, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astFormat method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to a string containing the formatted
+*     (character) version of a coordinate value for a SkyFrame axis. The
+*     formatting applied is that specified by a previous invocation of the
+*     astSetFormat method. A suitable default format is applied if necessary,
+*     and this may depend on which sky coordinate system the SkyFrame
+*     describes.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        The number of the axis (zero-based) for which formatting is to be
+*        performed.
+*     value
+*        The coordinate value to be formatted, in radians.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the formatted value.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   const char *result;           /* Pointer value to return */
+   int format_set;               /* Format attribute set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astFormat" );
+
+/* Determine if a Format value has been set for the axis and set a temporary
+   value if it has not. Use the GetFormat member function for this class
+   together with member functions inherited from the parent class (rather than
+   using the object's methods directly) because if any of these methods have
+   been over-ridden by a derived class the Format string syntax may no longer
+   be compatible with this class. */
+   format_set = (*parent_testformat)( this_frame, axis, status );
+   if ( !format_set ) {
+      (*parent_setformat)( this_frame, axis, GetFormat( this_frame, axis, status ), status );
+   }
+
+/* Use the Format member function inherited from the parent class to format the
+   value and return a pointer to the resulting string. */
+   result = (*parent_format)( this_frame, axis, value, status );
+
+/* If necessary, clear any temporary Format value that was set above. */
+   if ( !format_set ) (*parent_clearformat)( this_frame, axis, status );
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *FrameGrid( AstFrame *this_object, int size, const double *lbnd,
+                               const double *ubnd, int *status ){
+/*
+*  Name:
+*     FrameGrid
+
+*  Purpose:
+*     Return a grid of points covering a rectangular area of a Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     AstPointSet *FrameGrid( AstFrame *this_frame, int size,
+*                             const double *lbnd, const double *ubnd,
+*                             int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astFrameGrid
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a PointSet containing positions spread
+*     approximately evenly throughtout a specified rectangular area of
+*     the Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     size
+*        The preferred number of points in the returned PointSet. The
+*        actual number of points in the returned PointSet may be
+*        different, but an attempt is made to stick reasonably closely to
+*        the supplied value.
+*     lbnd
+*        Pointer to an array holding the lower bound of the rectangular
+*        area on each Frame axis. The array should have one element for
+*        each Frame axis.
+*     ubnd
+*        Pointer to an array holding the upper bound of the rectangular
+*        area on each Frame axis. The array should have one element for
+*        each Frame axis.
+
+*  Returned Value:
+*     A pointer to a new PointSet holding the grid of points.
+
+*  Notes:
+*     - A NULL pointer is returned if an error occurs.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;
+   AstSkyFrame *this;
+   double **ptr;
+   double box_area;
+   double cl;
+   double dlon;
+   double hilat;
+   double hilon;
+   double inclon;
+   double lat_size;
+   double lat;
+   double lon;
+   double lolon;
+   double lon_size;
+   double lolat;
+   double totlen;
+   int ilat;
+   int ilon;
+   int imer;
+   int ip;
+   int ipar;
+   int ipmax;
+   int nmer;
+   int npar;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_object;
+
+/* Get the zero-based indices of the longitude and latitude axes. */
+   ilon = astGetLonAxis( this );
+   ilat = 1 - ilon;
+
+/* The latitude bounds may not be the right way round so check for it. */
+   if( lbnd[ ilat ] <= ubnd[ ilat ] ) {
+      lolat = lbnd[ ilat ];
+      hilat = ubnd[ ilat ];
+   } else {
+      lolat = ubnd[ ilat ];
+      hilat = lbnd[ ilat ];
+   }
+
+/* Check all bounds are good. Also check the size is positive. */
+   lolon = lbnd[ ilon ];
+   hilon = ubnd[ ilon ];
+   if( size > 0 && lolat != AST__BAD && hilat != AST__BAD &&
+       lolon != AST__BAD && hilon != AST__BAD ) {
+
+/* Ensure the longitude bounds are in the range 0-2PI. */
+      lolon = palDranrm( lolon );
+      hilon = palDranrm( hilon );
+
+/* If the upper longitude limit is less than the lower limit, add 2.PI */
+      if( hilon <= lolon &&
+          ubnd[ ilon ] != lbnd[ ilon ] ) hilon += 2*AST__DPI;
+
+/* Get the total area of the box in steradians. */
+      dlon = hilon - lolon;
+      box_area = fabs( dlon*( sin( hilat ) - sin( lolat ) ) );
+
+/* Get the nominal size of a square grid cell, in radians. */
+      lat_size = sqrt( box_area/size );
+
+/* How many parallels should we use to cover the box? Ensure we use at
+   least two. These parallels pass through the centre of the grid cells. */
+      npar = (int)( 0.5 + ( hilat - lolat )/lat_size );
+      if( npar < 2 ) npar = 2;
+
+/* Find the actual sample size implied by this number of parallels. */
+      lat_size = ( hilat - lolat )/npar;
+
+/* Find the total arc length of the parallels. */
+      totlen = 0.0;
+      lat = lolat + 0.5*lat_size;
+      for( ipar = 0; ipar < npar; ipar++ ) {
+         totlen += dlon*cos( lat );
+         lat += lat_size;
+      }
+
+/* If we space "size" samples evenly over this total arc-length, what is
+   the arc-distance between samples? */
+      lon_size = totlen/size;
+
+/* Create a PointSet in which to store the grid. Make it bigger than
+   necessary in order to leave room for extra samples caused by integer
+   truncation. */
+      ipmax = 2*size;
+      result = astPointSet( ipmax, 2, " ", status );
+      ptr = astGetPoints( result );
+      if( astOK ) {
+
+/* Loop over all the parallels. */
+         ip = 0;
+         lat = lolat + 0.5*lat_size;
+         for( ipar = 0; ipar < npar; ipar++ ) {
+
+/* Get the longitude increment between samples on this parallel. */
+            cl = cos( lat );
+            inclon = ( cl != 0.0 ) ? lon_size/cl : 0.0;
+
+/* Get the number of longitude samples for this parallel. Reduce it if
+   it would extend beyond the end of the PointSet. */
+            nmer = dlon/inclon;
+            if( ip + nmer >= ipmax ) nmer = ipmax - ip;
+
+/* Adjust the longitude increment to take up any slack caused by the
+   above integer division. */
+            inclon = dlon/nmer;
+
+/* Produce the samples for the current parallel. */
+            lon = lolon + 0.5*inclon;
+            for( imer = 0; imer < nmer; imer++ ) {
+               ptr[ ilon ][ ip ] = lon;
+               ptr[ ilat ][ ip ] = lat;
+
+               lon += inclon;
+               ip++;
+            }
+
+/* Get the latitude on the next parallel. */
+            lat += lat_size;
+         }
+
+/* Truncate the PointSet to exclude unused elements at the end. */
+         astSetNpoint( result, ip );
+      }
+
+/* Report error if supplied values were bad. */
+   } else if( astOK ) {
+      if( size < 1 ) {
+         astError( AST__ATTIN, "astFrameGrid(%s): The supplied grid "
+                   "size (%d) is invalid (programming error).",
+                   status, astGetClass( this ), size );
+      } else {
+         astError( AST__ATTIN, "astFrameGrid(%s): One of more of the "
+                   "supplied bounds is AST__BAD (programming error).",
+                   status, astGetClass( this ) );
+      }
+   }
+
+/* Annul the returned PointSet if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the PointSet holding the grid. */
+   return result;
+}
+
+static double Gap( AstFrame *this_frame, int axis, double gap, int *ntick, int *status ) {
+/*
+*  Name:
+*     Gap
+
+*  Purpose:
+*     Find a "nice" gap for tabulating SkyFrame axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double Gap( AstFrame *this, int axis, double gap, int *ntick, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astGap method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a gap size which produces a nicely spaced
+*     series of formatted values for a SkyFrame axis, the returned gap
+*     size being as close as possible to the supplied target gap
+*     size. It also returns a convenient number of divisions into
+*     which the gap can be divided.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        The number of the axis (zero-based) for which a gap is to be found.
+*     gap
+*        The target gap size.
+*     ntick
+*        Address of an int in which to return a convenient number of
+*        divisions into which the gap can be divided.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The nice gap size.
+
+*  Notes:
+*     - A value of zero is returned if the target gap size is zero.
+*     - A negative gap size is returned if the supplied gap size is negative.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   double result;                /* Gap value to return */
+   int format_set;               /* Format attribute set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astGap" );
+
+/* Determine if a Format value has been set for the axis and set a
+   temporary value if it has not. Use the GetFormat member function
+   for this class together with member functions inherited from the
+   parent class (rather than using the object's methods directly)
+   because if any of these methods have been over-ridden by a derived
+   class the Format string syntax may no longer be compatible with
+   this class. */
+   format_set = (*parent_testformat)( this_frame, axis, status );
+   if ( !format_set ) {
+      (*parent_setformat)( this_frame, axis, GetFormat( this_frame, axis, status ), status );
+   }
+
+/* Use the Gap member function inherited from the parent class to find
+   the gap size. */
+   result = (*parent_gap)( this_frame, axis, gap, ntick, status );
+
+/* If necessary, clear any temporary Format value that was set above. */
+   if ( !format_set ) (*parent_clearformat)( this_frame, axis, status );
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied SkyFrame,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;         /* Pointer to SkyFrame structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astTSizeOf( this->projection );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetActiveUnit( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetActiveUnit
+
+*  Purpose:
+*     Obtain the value of the ActiveUnit flag for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int GetActiveUnit( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetActiveUnit protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function returns the value of the ActiveUnit flag for a
+*    SkyFrame, which is always 0.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The value to use for the ActiveUnit flag (0).
+
+*/
+   return 0;
+}
+
+static int GetAsTime( AstSkyFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetAsTime
+
+*  Purpose:
+*     Obtain the value of the AsTime attribute for a SkyFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int GetAsTime( AstSkyFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function returns the boolean value of the AsTime attribute for a
+*     specified axis of a SkyFrame. This value indicates whether axis values
+*     should be formatted as times (as opposed to angles) by default.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Index of the axis for which information is required (zero based).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero or one, according to the setting of the AsTime attribute (if no
+*     value has previously been set, a suitable default is returned).
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   int axis_p;                   /* Permuted axis index */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise. */
+   result = 0;
+
+/* Validate and permute the axis index. */
+   axis_p = astValidateAxis( this, axis, 1, "astGetAsTime" );
+
+/* Obtain a pointer to the required Axis object. */
+   ax = astGetAxis( this, axis );
+
+/* Determine if the AsTime attribute has been set for the axis (this can only
+   be the case if the object is a SkyAxis). If the attribute is set, obtain its
+   value. */
+   if ( astIsASkyAxis( ax ) && astTestAxisAsTime( ax ) ) {
+      result = astGetAxisAsTime( ax );
+
+/* Otherwise, check which (permuted) axis is involved. Only the first
+   (longitude) axis may be displayed as a time by default. */
+   } else if ( axis_p == 0 ) {
+
+/* Test for those coordinate systems which normally have their longitude axes
+   displayed as times (basically, those that involve the Earth's equator) and
+   set the returned value appropriately. */
+      result = IsEquatorial( astGetSystem( this ), status );
+   }
+
+/* Annul the Axis object pointer. */
+   ax = astAnnul( ax );
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astGetAttrib
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a SkyFrame, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - The returned string pointer may point at memory allocated
+*     within the SkyFrame, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the SkyFrame. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   const char *cval;             /* Pointer to character attribute value */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Floating point attribute value */
+   double equinox;               /* Equinox attribute value (as MJD) */
+   int as_time;                  /* AsTime attribute value */
+   int axis;                     /* SkyFrame axis number */
+   int ival;                     /* Integer attribute value */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* No. characters read by astSscanf */
+   int neglon;                   /* Display long. values as [-pi,pi]? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* AsTime(axis). */
+/* ------------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( attrib, "astime(%d)%n", &axis, &nc ) )
+        && ( nc >= len ) ) {
+      as_time = astGetAsTime( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", as_time );
+         result = getattrib_buff;
+      }
+
+/* Equinox. */
+/* -------- */
+   } else if ( !strcmp( attrib, "equinox" ) ) {
+      equinox = astGetEquinox( this );
+      if ( astOK ) {
+
+/* Format the Equinox as decimal years. Use a Besselian epoch if it
+   will be less than 1984.0, otherwise use a Julian epoch. */
+         result = astFmtDecimalYr( ( equinox < palEpj2d( 1984.0 ) ) ?
+                                   palEpb( equinox ) : palEpj( equinox ),
+                                   DBL_DIG );
+      }
+
+/* IsLatAxis(axis) */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "islataxis(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = astGetIsLatAxis( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* IsLonAxis(axis) */
+/* --------------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( attrib, "islonaxis(%d)%n", &axis, &nc ) )
+               && ( nc >= len ) ) {
+      ival = astGetIsLonAxis( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* LatAxis */
+/* -------- */
+   } else if ( !strcmp( attrib, "lataxis" ) ) {
+      axis = astGetLatAxis( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", axis + 1 );
+         result = getattrib_buff;
+      }
+
+/* LonAxis */
+/* -------- */
+   } else if ( !strcmp( attrib, "lonaxis" ) ) {
+      axis = astGetLonAxis( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", axis + 1 );
+         result = getattrib_buff;
+      }
+
+/* NegLon */
+/* ------ */
+   } else if ( !strcmp( attrib, "neglon" ) ) {
+      neglon = astGetNegLon( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", neglon );
+         result = getattrib_buff;
+      }
+
+/* SkyTol */
+/* ------ */
+   } else if ( !strcmp( attrib, "skytol" ) ) {
+      dval = astGetSkyTol( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* Projection. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "projection" ) ) {
+      result = astGetProjection( this );
+
+/* SkyRef. */
+/* ------- */
+   } else if ( !strcmp( attrib, "skyref" ) ) {
+      cval = astFormat( this, 0, astGetSkyRef( this, 0 ) );
+      if ( astOK ) {
+         nc = sprintf( getattrib_buff, "%s, ", cval );
+         cval = astFormat( this, 1, astGetSkyRef( this, 1 ) );
+         if ( astOK ) {
+            (void) sprintf( getattrib_buff + nc, "%s", cval );
+            result = getattrib_buff;
+         }
+      }
+
+/* SkyRef(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( attrib, "skyref(%d)%n", &axis, &nc ) )
+        && ( nc >= len ) ) {
+      dval = astGetSkyRef( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* SkyRefP. */
+/* -------- */
+   } else if ( !strcmp( attrib, "skyrefp" ) ) {
+      cval = astFormat( this, 0, astGetSkyRefP( this, 0 ) );
+      if ( astOK ) {
+         nc = sprintf( getattrib_buff, "%s, ", cval );
+         cval = astFormat( this, 1, astGetSkyRefP( this, 1 ) );
+         if ( astOK ) {
+            (void) sprintf( getattrib_buff + nc, "%s", cval );
+            result = getattrib_buff;
+         }
+      }
+
+/* SkyRefP(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( attrib, "skyrefp(%d)%n", &axis, &nc ) )
+        && ( nc >= len ) ) {
+      dval = astGetSkyRefP( this, axis - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* SkyRefIs. */
+/* --------- */
+   } else if ( !strcmp( attrib, "skyrefis" ) ) {
+      ival = astGetSkyRefIs( this );
+      if ( astOK ) {
+         if( ival == AST__POLE_REF ){
+            result = POLE_STRING;
+         } else if( ival == AST__IGNORED_REF ){
+            result = IGNORED_STRING;
+         } else {
+            result = ORIGIN_STRING;
+         }
+      }
+
+/* AlignOffset */
+/* ----------- */
+   } else if ( !strcmp( attrib, "alignoffset" ) ) {
+      ival = astGetAlignOffset( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetDirection( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetDirection
+
+*  Purpose:
+*     Obtain the value of the Direction attribute for a SkyFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int GetDirection( AstFrame *this_frame, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetDirection method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns the value of the Direction attribute for a
+*     specified axis of a SkyFrame. A suitable default value is returned if no
+*     Direction value has previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero or one, depending on the Direction attribute value.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   int axis_p;                   /* Permuted axis index */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise. */
+   result = 0;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Validate and permute the axis index. */
+   axis_p = astValidateAxis( this, axis, 1, "astGetDirection" );
+
+/* Check if a value has been set for the axis Direction attribute. If so,
+   obtain its value. */
+   if ( astTestDirection( this, axis ) ) {
+      result = (*parent_getdirection)( this_frame, axis, status );
+
+/* Otherwise, we will generate a default Direction value. Currently all
+   systems supported by SkyFrame are left handed, so all longitude axes
+   are reversed and all latitude axes are not reversed. */
+   } else if( axis_p == 0 ) {
+      result = 0;
+   } else {
+      result = 1;
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static double GetBottom( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetBottom
+
+*  Purpose:
+*     Obtain the value of the Bottom attribute for a SkyFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double GetBottom( AstFrame *this_frame, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetBottom method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns the value of the Bottom attribute for a
+*     specified axis of a SkyFrame. A suitable default value is returned if no
+*     value has previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Bottom value to use.
+
+*  Notes:
+*     -  A value of -DBL_MAX will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   int axis_p;                   /* Permuted axis index */
+   double result;                /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return -DBL_MAX;
+
+/* Initialise. */
+   result = -DBL_MAX;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Validate and permute the axis index. */
+   axis_p = astValidateAxis( this, axis, 1, "astGetBottom" );
+
+/* Check if a value has been set for the axis Bottom attribute. If so,
+   obtain its value. */
+   if ( astTestBottom( this, axis ) ) {
+      result = (*parent_getbottom)( this_frame, axis, status );
+
+/* Otherwise, we will return a default Bottom value appropriate to the
+   SkyFrame class. */
+   } else {
+
+/* If it is a latitude axis return -pi/2. */
+      if( axis_p == 1 ) {
+         result = -piby2;
+
+/* If it is a longitude value return -DBL_MAX (i.e. no lower limit). */
+      } else {
+         result = -DBL_MAX;
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = -DBL_MAX;
+
+/* Return the result. */
+   return result;
+}
+
+static double GetCachedLAST( AstSkyFrame *this, double epoch, double obslon,
+                             double obslat, double obsalt, double dut1,
+                             int *status ) {
+/*
+*  Name:
+*     GetCachedLAST
+
+*  Purpose:
+*     Attempt to get a LAST value from the cache in the SkyFrame vtab.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double GetCachedLAST( AstSkyFrame *this, double epoch, double obslon,
+*                           double obslat, double obsalt, double dut1,
+*                           int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function searches the static cache of LAST values held in the
+*     SkyFrame virtual function table for a value that corresponds to the
+*     supplied parameter values. If one is found, it is returned.
+*     Otherwise AST__BAD is found.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     epoch
+*        The epoch (MJD).
+*     obslon
+*        Observatory geodetic longitude (radians)
+*     obslat
+*        Observatory geodetic latitude (radians)
+*     obsalt
+*        Observatory geodetic altitude (metres)
+*     dut1
+*        The UT1-UTC correction, in seconds.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    The Local Apparent Sidereal Time, in radians.
+
+*  Notes:
+*     -  A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   AstSkyLastTable *table;
+   double *ep;
+   double *lp;
+   double dep;
+   double result;
+   int ihi;
+   int ilo;
+   int itable;
+   int itest;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Wait until the table is not being written to by any thread. This also
+   prevents a thread from writing to the table whilst we are reading it. */
+   LOCK_RLOCK1
+
+/* Loop round every LAST table held in the vtab. Each table refers to a
+   different observatory position and/or DUT1 value. */
+   for( itable = 0; itable < nlast_tables; itable++ ) {
+      table = last_tables[ itable ];
+
+/* See if the table refers to the given position and dut1 value, allowing
+   some small tolerance. */
+      if( fabs( table->obslat - obslat ) < 2.0E-7 &&
+          fabs( table->obslon - obslon ) < 2.0E-7 &&
+          fabs( table->obsalt - obsalt ) < 1.0 &&
+          fabs( table->dut1 - dut1 ) < 1.0E-5 ) {
+
+/* Get pointers to the array of epoch and corresponding LAST values in
+   the table. */
+         ep = table->epoch;
+         lp = table->last;
+
+/* The values in the epoch array are monotonic increasing. Do a binary chop
+   within the table's epoch array to find the earliest entry that has a
+   value equal to or greater than the supplied epoch value. */
+         ilo = 0;
+         ihi = table->nentry - 1;
+         while( ihi > ilo ) {
+            itest = ( ilo + ihi )/2;
+            if( ep[ itest ] >= epoch ) {
+               ihi = itest;
+            } else {
+               ilo = itest + 1;
+            }
+         }
+
+/* Get the difference between the epoch at the entry selected above and
+   the requested epoch. */
+         dep = ep[ ilo ] - epoch;
+
+/* If the entry selected above is the first entry in the table, it can
+   only be used if it is within 0.001 second of the requested epoch. */
+         if( ilo == 0 ) {
+            if( fabs( dep ) < 0.001/86400.0 ) {
+               result = lp[ 0 ];
+            }
+
+/* If the list of epoch values contained no value that was greater than
+   the supplied epoch value, then we can use the last entry if
+   it is no more than 0.001 second away from the requested epoch. */
+         } else if( dep <= 0.0 ) {
+            if( fabs( dep ) < 0.001/86400.0 ) {
+                result = lp[ ilo ];
+            }
+
+
+/* Otherwise, see if the entry selected above is sufficiently close to
+   its lower neighbour (i.e. closer than 0.4 days) to allow a reasonably
+   accurate LAST value to be determined by interpolation. */
+         } else if( ep[ ilo ] - ep[ ilo - 1 ] < 0.4 ) {
+            ep += ilo - 1;
+            lp += ilo - 1;
+            result = *lp + ( epoch - *ep )*( lp[ 1 ] - *lp )/( ep[ 1 ] - *ep );
+
+/* If the neighbouring point is too far away for interpolation to be
+   reliable, then we can only use the point if it is within 0.001 seconds of
+   the requested epoch. */
+         } else if( fabs( dep ) < 0.001/86400.0 ) {
+            result = lp[ ilo ];
+         }
+
+/* If we have found the right table, we do not need to look at any other
+   tables, so leave the table loop. */
+         break;
+      }
+   }
+
+/* Indicate that threads may now write to the table. */
+   UNLOCK_RWLOCK1
+
+/* Ensure the returned value is within the range 0 - 2.PI. */
+   if( result != AST__BAD ) {
+      while( result > 2*AST__DPI ) result -= 2*AST__DPI;
+      while( result < 0.0 ) result += 2*AST__DPI;
+   }
+
+/* Return the required LAST value. */
+   return result;
+}
+
+static double GetEpoch( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetEpoch
+
+*  Purpose:
+*     Obtain the value of the Epoch attribute for a SkyFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double GetEpoch( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetEpoch method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns the value of the Epoch attribute for a
+*     SkyFrame. A suitable default value is returned if no value has
+*     previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Epoch value to use.
+
+*  Notes:
+*     -  A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   AstSystemType system;         /* System attribute */
+   double result;                /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Check if a value has been set for the Epoch attribute. If so, obtain its
+   value. */
+   if ( astTestEpoch( this ) ) {
+      result = (*parent_getepoch)( this_frame, status );
+
+/* Otherwise, we will return a default Epoch value appropriate to the
+   SkyFrame class. */
+   } else {
+
+/* Provide a default value of B1950.0 or J2000.0 depending on the System
+   setting. */
+      system = astGetSystem( this );
+      if( system  == AST__FK4 || system == AST__FK4_NO_E ) {
+         result = palEpb2d( 1950.0 );
+      } else {
+         result = palEpj2d( 2000.0 );
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static double GetTop( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetTop
+
+*  Purpose:
+*     Obtain the value of the Top attribute for a SkyFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double GetTop( AstFrame *this_frame, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetTop method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns the value of the Top attribute for a
+*     specified axis of a SkyFrame. A suitable default value is returned if no
+*     value has previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Top value to use.
+
+*  Notes:
+*     -  A value of DBL_MAX will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   int axis_p;                   /* Permuted axis index */
+   double result;                /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return DBL_MAX;
+
+/* Initialise. */
+   result = DBL_MAX;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Validate and permute the axis index. */
+   axis_p = astValidateAxis( this, axis, 1, "astGetTop" );
+
+/* Check if a value has been set for the axis Top attribute. If so,
+   obtain its value. */
+   if ( astTestTop( this, axis ) ) {
+      result = (*parent_gettop)( this_frame, axis, status );
+
+/* Otherwise, we will return a default Top value appropriate to the
+   SkyFrame class. */
+   } else {
+
+/* If this is a latitude axis return pi/2. */
+      if( axis_p == 1 ) {
+         result = piby2;
+
+/* If it is a longitude value return DBL_MAX (i.e. no upper limit). */
+      } else {
+         result = DBL_MAX;
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = DBL_MAX;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetDomain( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetDomain
+
+*  Purpose:
+*     Obtain a pointer to the Domain attribute string for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     const char *GetDomain( AstFrame *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetDomain protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function returns a pointer to the Domain attribute string
+*    for a SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a constant null-terminated string containing the
+*     Domain value.
+
+*  Notes:
+*     - The returned pointer or the string it refers to may become
+*     invalid following further invocation of this function or
+*     modification of the SkyFrame.
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to SkyFrame structure */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* If a Domain attribute string has been set, invoke the parent method
+   to obtain a pointer to it. */
+   if ( astTestDomain( this ) ) {
+      result = (*parent_getdomain)( this_frame, status );
+
+/* Otherwise, provide a pointer to a suitable default string. */
+   } else {
+      result = "SKY";
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetFormat( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetFormat
+
+*  Purpose:
+*     Access the Format string for a SkyFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     const char *GetFormat( AstFrame *this, int axis )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetFormat method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Format string for a specified axis
+*     of a SkyFrame. A pointer to a suitable default string is returned if no
+*     Format value has previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+
+*  Returned Value:
+*     Pointer to a null-terminated character string containing the requested
+*     information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   const char *result;           /* Pointer value to return */
+   int as_time;                  /* Value of AsTime attribute */
+   int as_time_set;              /* AsTime attribute set? */
+   int axis_p;                   /* Permuted axis index */
+   int digits;                   /* Number of digits of precision */
+   int is_latitude;              /* Value of IsLatitude attribute */
+   int is_latitude_set;          /* IsLatitude attribute set? */
+   int parent;                   /* Use parent method? */
+   int skyaxis;                  /* Is the Axis a SkyAxis? */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_frame);
+
+/* Initialise. */
+   result = NULL;
+   as_time_set = 0;
+   is_latitude = 0;
+   is_latitude_set = 0;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Validate and permute the axis index. */
+   axis_p = astValidateAxis( this, axis, 1, "astGetFormat" );
+
+/* Obtain a pointer to the Axis structure. */
+   ax = astGetAxis( this, axis );
+
+/* Decide whether the parent astGetFormat method is able to provide the format
+   string we require. We must use the parent method if the Axis is not a
+   SkyAxis, because the syntax of the Format string would become unsuitable
+   for use with the Axis astFormat method if it was over-ridden here. We also
+   use the parent method to return a Format pointer if an explicit Format
+   string has already been set. */
+   skyaxis = astIsASkyAxis( ax );
+   parent = ( !skyaxis || (*parent_testformat)( this_frame, axis, status ) );
+
+/* If neither of the above conditions apply, we may still be able to use the
+   parent method if the Axis (actually a SkyAxis) is required to behave as a
+   normal RA or DEC axis, as this is the standard behaviour provided by the
+   SkyAxis class. Examine the SkyFrame's System attribute to determine if its
+   axes should behave in this way. */
+   if ( !parent ) parent = IsEquatorial( astGetSystem( this ), status );
+
+/* If using the parent method and dealing with a SkyAxis, determine the
+   settings of any attributes that may affect the Format string. */
+   if ( astOK ) {
+      if ( parent ) {
+         if ( skyaxis ) {
+            as_time_set = astTestAsTime( this, axis );
+            is_latitude_set = astTestAxisIsLatitude( ax );
+            is_latitude = astGetAxisIsLatitude( ax );
+
+/* If no AsTime value is set for the axis, set a temporary value as determined
+   by the astGetAsTime method, which supplies suitable defaults for the axes of
+   a SkyFrame. */
+            if ( !as_time_set ) {
+               astSetAsTime( this, axis, astGetAsTime( this, axis ) );
+	    }
+
+/* Temporarly over-ride the SkyAxis IsLatitude attribute, regardless of its
+   setting, as the second axis of a SkyFrame is always the latitude axis. */
+            astSetAxisIsLatitude( ax, axis_p == 1 );
+         }
+
+/* Invoke the parent method to obtain a pointer to the Format string. */
+         result = (*parent_getformat)( this_frame, axis, status );
+
+/* Now restore the attributes that were temporarily over-ridden above to their
+   previous states. */
+         if ( skyaxis ) {
+            if ( !as_time_set ) astClearAsTime( this, axis );
+            if ( !is_latitude_set ) {
+               astClearAxisIsLatitude( ax );
+            } else {
+               astSetAxisIsLatitude( ax, is_latitude );
+            }
+         }
+
+/* If the parent method is unsuitable, we must construct a new Format string
+   here. This affects only those coordinate systems whose axes do not behave
+   like standard RA/DEC axes (e.g. typically ecliptic, galactic and
+   supergalactic coordinates). For these, we format values as decimal degrees
+   (or decimal hours if the AsTime attribute is set). Obtain the AsTime
+   value. */
+      } else {
+         as_time = astGetAsTime( this, axis );
+
+/* Determine how many digits of precision to use. This is obtained from the
+   SkyAxis Digits attribute (if set), otherwise from the Digits attribute of
+   the enclosing SkyFrame. */
+         if ( astTestAxisDigits( ax ) ) {
+            digits = astGetAxisDigits( ax );
+         } else {
+            digits = astGetDigits( this );
+         }
+
+/* If a time format is required, generate a Format string using decimal
+   hours. */
+         if ( astOK ) {
+            if ( as_time ) {
+               if ( digits <= 2 ) {
+                  result = "h";
+               } else {
+                  (void) sprintf( getformat_buff, "h.%d", digits - 2 );
+                  result = getformat_buff;
+               }
+
+/* Otherwise use decimal degrees. */
+            } else {
+               if ( digits <= 3 ) {
+                  result = "d";
+               } else {
+                  (void) sprintf( getformat_buff, "d.%d", digits - 3 );
+                  result = getformat_buff;
+               }
+	    }
+	 }
+      }
+   }
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetLabel( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetLabel
+
+*  Purpose:
+*     Access the Label string for a SkyFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     const char *GetLabel( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetLabel method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Label string for a specified axis
+*     of a SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated character string containing the
+*     requested information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstSystemType system;         /* Code identifying type of sky coordinates */
+   const char *result;           /* Pointer to label string */
+   int axis_p;                   /* Permuted axis index */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise. */
+   result = NULL;
+
+/* Validate and permute the axis index. */
+   axis_p = astValidateAxis( this, axis, 1, "astGetLabel" );
+
+/* Check if a value has been set for the required axis label string. If so,
+   invoke the parent astGetLabel method to obtain a pointer to it. */
+   if ( astTestLabel( this, axis ) ) {
+      result = (*parent_getlabel)( this, axis, status );
+
+/* Otherwise, identify the sky coordinate system described by the SkyFrame. */
+   } else {
+      system = astGetSystem( this );
+
+/* If OK, supply a pointer to a suitable default label string. */
+      if ( astOK ) {
+
+/* Equatorial coordinate systems. */
+         if ( IsEquatorial( system, status ) ) {
+	    result = ( axis_p == 0 ) ? "Right ascension" :
+	                               "Declination";
+
+/* Ecliptic coordinates. */
+         } else if ( system == AST__ECLIPTIC ) {
+	    result = ( axis_p == 0 ) ? "Ecliptic longitude" :
+                                       "Ecliptic latitude";
+
+/* Helio-ecliptic coordinates. */
+         } else if ( system == AST__HELIOECLIPTIC ) {
+	    result = ( axis_p == 0 ) ? "Helio-ecliptic longitude" :
+                                       "Helio-ecliptic latitude";
+
+/* AzEl coordinates. */
+         } else if ( system == AST__AZEL ) {
+	    result = ( axis_p == 0 ) ? "Azimuth" :
+                                       "Elevation";
+
+/* Galactic coordinates. */
+         } else if ( system == AST__GALACTIC ) {
+	    result = ( axis_p == 0 ) ? "Galactic longitude" :
+                                       "Galactic latitude";
+
+/* Supergalactic coordinates. */
+         } else if ( system == AST__SUPERGALACTIC ) {
+	    result = ( axis_p == 0 ) ? "Supergalactic longitude" :
+                                       "Supergalactic latitude";
+
+/* Unknown spherical coordinates. */
+         } else if ( system == AST__UNKNOWN ) {
+	    result = ( axis_p == 0 ) ? "Longitude" :
+                                       "Latitude";
+
+/* Report an error if the coordinate system was not recognised. */
+         } else {
+	    astError( AST__SCSIN, "astGetLabel(%s): Corrupt %s contains "
+		      "invalid sky coordinate system identification code "
+		      "(%d).", status, astGetClass( this ), astGetClass( this ),
+		      (int) system );
+         }
+
+/* If the SkyRef attribute has a set value, append " offset" to the label. */
+         if( astGetSkyRefIs( this ) != AST__IGNORED_REF &&
+             ( astTestSkyRef( this, 0 ) || astTestSkyRef( this, 1 ) ) ) {
+            sprintf( getlabel_buff, "%s offset", result );
+            result = getlabel_buff;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetDiurab( AstSkyFrame *this, int *status ) {
+/*
+*  Name:
+*     GetDiurab
+
+*  Purpose:
+*     Return the magnitude of the diurnal aberration vector.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double GetDiurab( AstSkyFrame *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function
+
+*  Description:
+*     This function returns the  magnitude of the diurnal aberration
+*     vector.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The magnitude of the diurnal aberration vector.
+
+*/
+
+/* Local Variables: */
+   double uau;
+   double vau;
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BAD;
+
+/* If the magnitude of the diurnal aberration vector has not yet been
+   found, find it now, and cache it in the SkyFrame structure. The cached
+   value will be reset to AST__BAD if the ObsLat attribute value is
+   changed. This code is transliterated from SLA_AOPPA. */
+   if( this->diurab == AST__BAD ) {
+      palGeoc( astGetObsLat( this ), astGetObsAlt( this ), &uau, &vau );
+      this->diurab = 2*AST__DPI*uau*SOLSID/C;
+   }
+
+/* Return the result, */
+   return this->diurab;
+}
+
+static double GetLAST( AstSkyFrame *this, int *status ) {
+/*
+*  Name:
+*     GetLAST
+
+*  Purpose:
+*     Return the Local Apparent Sidereal Time for the SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double GetLAST( AstSkyFrame *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function
+
+*  Description:
+*     This function returns the Local Apparent Sidereal Time (LAST)
+*     at the moment intime given by the Epoch attribute of the SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The LAST value.
+
+*/
+
+/* Local Variables: */
+   double dlast;                 /* Change in LAST */
+   double epoch;                 /* Epoch (TDB MJD) */
+   double last1;                 /* LAST at end of current interval */
+   double result;                /* Result value to return */
+   double delta_epoch;           /* Change in Epoch */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* The "last" component of the SkyFrame structure holds the accurate
+   LAST at the moment in time given by the "eplast" (a TDB MJD) component
+   of the SkyFrame structure. If the current value of the SkyFrame's
+   Epoch attribute is not much different to "eplast" (within 0.4 of a day),
+   then the returned LAST value is the "last" value plus the difference
+   between Epoch and "eplast", converted from solar to sidereal time,
+   then converted to radians. This approximation seems to be good to less
+   than a tenth of an arcsecond. If this approximation cannot be used,
+   invoke SetLast to recalculate the accurate LAST and update the "eplast"
+   and "last" values. */
+   if( this->eplast != AST__BAD ) {
+      epoch = astGetEpoch( this );
+      delta_epoch = epoch - this->eplast;
+
+/* Return the current LAST value if the epoch has not changed. */
+      if( delta_epoch == 0.0 ) {
+         result = this->last;
+
+/* If the previous full calculation of LAST was less than 0.4 days ago,
+   use a linear approximation to LAST. */
+      } else if( fabs( delta_epoch ) < 0.4 ) {
+
+/* If we do not know the ratio of sidereal to solar time at the current
+   epoch, calculate it now. This involves a full calculation of LAST at
+   the end of the current linear approximation period. */
+         if( this->klast == AST__BAD ) {
+            last1 = CalcLAST( this, this->eplast + 0.4, astGetObsLon( this ),
+                              astGetObsLat( this ), astGetObsAlt( this ),
+                              astGetDut1( this ), status );
+
+/* Ensure the change in LAST is positive so that we get a positive ratio. */
+            dlast = last1 - this->last;
+            if( dlast < 0.0 ) dlast += 2*AST__DPI;
+            this->klast = 2*AST__DPI*0.4/dlast;
+         }
+
+/* Now use the ratio of solar to sidereal time to calculate the linear
+   approximation to LAST. */
+         result = this->last + 2*AST__DPI*delta_epoch/this->klast;
+
+/* If the last accurate calculation of LAST was more than 0.4 days ago,
+   do a full accurate calculation. */
+      } else {
+         SetLast( this, status );
+         result = this->last;
+      }
+
+/* If we have not yet done an accurate calculation of LAST, do one now. */
+   } else {
+      SetLast( this, status );
+      result = this->last;
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static int GetIsLatAxis( AstSkyFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetIsLatAxis
+
+*  Purpose:
+*     Test an axis to see if it is a latitude axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int GetIsLatAxis( AstSkyFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function tests if a SkyFrame axis is a celestial latitude axis.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Zero based axis index.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the supplied axis is a celestial latitude axis, and zero
+*     otherwise.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get the index of the latitude axis and compare to the supplied axis
+   index. */
+   result = ( axis == astGetLatAxis( this ) );
+
+/* Return the result. */
+   return astOK ? result : 0;
+
+}
+
+static int GetIsLonAxis( AstSkyFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetIsLonAxis
+
+*  Purpose:
+*     Test an axis to see if it is a longitude axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int GetIsLonAxis( AstSkyFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function tests if a SkyFrame axis is a celestial longitude axis.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Zero based axis index.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the supplied axis is a celestial longitude axis, and zero
+*     otherwise.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get the index of the longitude axis and compare to the supplied axis
+   index. */
+   result = ( axis == astGetLonAxis( this ) );
+
+/* Return the result. */
+   return astOK ? result : 0;
+
+}
+
+static int GetLatAxis( AstSkyFrame *this, int *status ) {
+/*
+*  Name:
+*     GetLatAxis
+
+*  Purpose:
+*     Obtain the index of the latitude axis of a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int GetLatAxis( AstSkyFrame *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function returns the zero-based index of the latitude axis of
+*     a SkyFrame, taking into account any current axis permutation.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The zero based axis index (0 or 1) of the latitude axis.
+
+*  Notes:
+*     -  A value of one will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result to be returned */
+   const int *perm;              /* Axis permutation array */
+
+/* Check the global error status. */
+   if ( !astOK ) return 1;
+
+/* Initialise. */
+   result = 1;
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+   perm = astGetPerm( this );
+   if ( astOK ) {
+
+/* Identify the latitude axis. */
+      if( perm[ 0 ] == 1 ) {
+         result = 0;
+      } else {
+         result = 1;
+      }
+
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static int GetLonAxis( AstSkyFrame *this, int *status ) {
+/*
+*  Name:
+*     GetLonAxis
+
+*  Purpose:
+*     Obtain the index of the longitude axis of a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int GetLonAxis( AstSkyFrame *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function returns the zero-based index of the longitude axis of
+*     a SkyFrame, taking into account any current axis permutation.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The zero based axis index (0 or 1) of the longitude axis.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result to be returned */
+   const int *perm;              /* Axis permutation array */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise. */
+   result = 0;
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+   perm = astGetPerm( this );
+   if ( astOK ) {
+
+/* Identify the longitude axis. */
+      if( perm[ 0 ] == 0 ) {
+         result = 0;
+      } else {
+         result = 1;
+      }
+
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static double GetSkyRefP( AstSkyFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetSkyRefP
+
+*  Purpose:
+*     Obtain the value of the SkyRefP attribute for a SkyFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double GetSkyRefP( AstSkyFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function returns the value of the SkyRefP attribute for a
+*     SkyFrame axis, providing suitable defaults.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The SkyRefP value to be used.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   double result;                /* Returned value */
+   int axis_p;                   /* Permuted axis index */
+
+/* Initialise. */
+   result = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate and permute the axis index. */
+   axis_p = astValidateAxis( this, axis, 1, "astGetSkyRefP" );
+
+/* Check if a value has been set for the required axis. If so, return it. */
+   if( this->skyrefp[ axis_p ] != AST__BAD ) {
+      result = this->skyrefp[ axis_p ];
+
+/* Otherwise, return the default value */
+   } else {
+
+/* The default longitude value is always zero. */
+      if( axis_p == 0 ) {
+         result= 0.0;
+
+/* The default latitude value depends on SkyRef. The usual default is the
+   north pole. The exception to this is if the SkyRef attribute identifies
+   either the north or the south pole, in which case the origin is used as
+   the default. Allow some tolerance. */
+      } else if( fabs( cos( this->skyref[ 1 ] ) ) > 1.0E-10 ) {
+         result = pi/2;
+
+      } else {
+         result = 0.0;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetSymbol( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetSymbol
+
+*  Purpose:
+*     Obtain a pointer to the Symbol string for a SkyFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     const char *GetSymbol( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetSymbol method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Symbol string for a specified axis
+*     of a SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated character string containing the
+*     requested information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstSystemType system;         /* Code identifying type of sky coordinates */
+   const char *result;           /* Pointer to symbol string */
+   int axis_p;                   /* Permuted axis index */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise. */
+   result = NULL;
+
+/* Validate and permute the axis index. */
+   axis_p = astValidateAxis( this, axis, 1, "astGetSymbol" );
+
+/* Check if a value has been set for the required axis symbol string. If so,
+   invoke the parent astGetSymbol method to obtain a pointer to it. */
+   if ( astTestSymbol( this, axis ) ) {
+      result = (*parent_getsymbol)( this, axis, status );
+
+/* Otherwise, identify the sky coordinate system described by the SkyFrame. */
+   } else {
+      system = astGetSystem( this );
+
+/* If OK, supply a pointer to a suitable default Symbol string. */
+      if ( astOK ) {
+
+/* Equatorial coordinate systems. */
+         if ( IsEquatorial( system, status ) ) {
+	    result = ( axis_p == 0 ) ? "RA" : "Dec";
+
+/* Ecliptic coordinates. */
+         } else if ( system == AST__ECLIPTIC ) {
+	    result = ( axis_p == 0 ) ? "Lambda" : "Beta";
+
+/* Helio-ecliptic coordinates. */
+         } else if ( system == AST__HELIOECLIPTIC ) {
+	    result = ( axis_p == 0 ) ? "Lambda" : "Beta";
+
+/* AzEl coordinates. */
+         } else if ( system == AST__AZEL ) {
+	    result = ( axis_p == 0 ) ? "Az" : "El";
+
+/* Galactic coordinates. */
+         } else if ( system == AST__GALACTIC ) {
+	    result = ( axis_p == 0 ) ? "l" : "b";
+
+/* Supergalactic coordinates. */
+         } else if ( system == AST__SUPERGALACTIC ) {
+	    result = ( axis_p == 0 ) ? "SGL" : "SGB";
+
+/* Unknown spherical coordinates. */
+         } else if ( system == AST__UNKNOWN ) {
+	    result = ( axis_p == 0 ) ? "Lon" : "Lat";
+
+/* Report an error if the coordinate system was not recognised. */
+         } else {
+	    astError( AST__SCSIN, "astGetSymbol(%s): Corrupt %s contains "
+		      "invalid sky coordinate system identification code "
+		      "(%d).", status, astGetClass( this ), astGetClass( this ),
+		      (int) system );
+         }
+
+/* If the SkyRef attribute had a set value, prepend "D" (for "delta") to the
+   Symbol. */
+         if( astGetSkyRefIs( this ) != AST__IGNORED_REF &&
+             ( astTestSkyRef( this, 0 ) || astTestSkyRef( this, 1 ) ) ) {
+            sprintf( getsymbol_buff, "D%s", result );
+            result = getsymbol_buff;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType GetAlignSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetAlignSystem
+
+*  Purpose:
+*     Obtain the AlignSystem attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     AstSystemType GetAlignSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetAlignSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the AlignSystem attribute for a SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The AlignSystem value.
+
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to SkyFrame structure */
+   AstSystemType result;         /* Value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* If a AlignSystem attribute has been set, invoke the parent method to obtain
+   it. */
+   if ( astTestAlignSystem( this ) ) {
+      result = (*parent_getalignsystem)( this_frame, status );
+
+/* Otherwise, provide a suitable default. */
+   } else {
+      result = AST__ICRS;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType GetSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetSystem
+
+*  Purpose:
+*     Obtain the System attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     AstSystemType GetSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the System attribute for a SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System value.
+
+*  Notes:
+*     - AST__BADSYSTEM is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to SkyFrame structure */
+   AstSystemType result;         /* Value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* If a System attribute has been set, invoke the parent method to obtain
+   it. */
+   if ( astTestSystem( this ) ) {
+      result = (*parent_getsystem)( this_frame, status );
+
+/* Otherwise, provide a suitable default. */
+   } else {
+      result = AST__ICRS;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetTitle( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetTitle
+
+*  Purpose:
+*     Obtain a pointer to the Title string for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     const char *GetTitle( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetTitle method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Title string for a SkyFrame.
+*     A pointer to a suitable default string is returned if no Title value has
+*     previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a null-terminated character string containing the requested
+*     information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstSkyFrame *this;            /* Pointer to SkyFrame structure */
+   AstSystemType system;         /* Code identifying type of sky coordinates */
+   const char *extra;            /* Pointer to extra information */
+   const char *p;                /* Character pointer */
+   const char *projection;       /* Pointer to sky projection description */
+   const char *result;           /* Pointer to result string */
+   const char *word;             /* Pointer to critical word */
+   double epoch;                 /* Value of Epoch attribute */
+   double equinox;               /* Value of Equinox attribute */
+   int lextra;                   /* Length of extra information */
+   int offset;                   /* Using offset coordinate system? */
+   int pos;                      /* Buffer position to enter text */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_frame);
+
+/* Initialise. */
+   result = NULL;
+   pos = 0;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* See if a Title string has been set. If so, use the parent astGetTitle
+   method to obtain a pointer to it. */
+   if ( astTestTitle( this ) ) {
+      result = (*parent_gettitle)( this_frame, status );
+
+/* Otherwise, we will generate a default Title string. Obtain the values of the
+   SkyFrame's attributes that determine what this string will be. */
+   } else {
+      epoch = astGetEpoch( this );
+      equinox = astGetEquinox( this );
+      projection = astGetProjection( this );
+      system = astGetSystem( this );
+
+/* See if an offset coordinate system is being used.*/
+      offset = ( astTestSkyRef( this, 0 ) || astTestSkyRef( this, 1 ) )
+               && ( astGetSkyRefIs( this ) != AST__IGNORED_REF );
+
+/* Use this to determine if the word "coordinates" or "offsets" should be
+   used.*/
+      word = offset ? "offsets" : "coordinates";
+
+/* Classify the coordinate system type and create an appropriate Title
+   string.  (Note that when invoking the astFmtDecimalYr function we must
+   use a separate sprintf on each occasion so as not to over-write its
+   internal buffer before the result string has been used.) */
+      if ( astOK ) {
+         result = gettitle_buff;
+         switch ( system ) {
+
+/* FK4 equatorial coordinates. */
+/* --------------------------- */
+/* Display the Equinox and Epoch values. */
+	 case AST__FK4:
+	    pos = sprintf( gettitle_buff, "FK4 equatorial %s", word );
+            if( astTestEquinox( this ) || astGetUseDefs( this ) ) {
+   	       pos += sprintf( gettitle_buff + pos, "; mean equinox B%s",
+		               astFmtDecimalYr( palEpb( equinox ), 9 ) );
+            }
+            if( astTestEpoch( this ) || astGetUseDefs( this ) ) {
+               pos += sprintf( gettitle_buff + pos,
+                               "; epoch B%s", astFmtDecimalYr( palEpb( epoch ), 9 ) );
+            }
+	    break;
+
+/* FK4 coordinates with no E-terms of aberration. */
+/* ---------------------------------------------- */
+/* Display the Equinox and Epoch values. */
+	 case AST__FK4_NO_E:
+	    pos = sprintf( gettitle_buff, "FK4 equatorial %s; no E-terms", word );
+            if( astTestEquinox( this ) || astGetUseDefs( this ) ) {
+   	       pos += sprintf( gettitle_buff + pos, "; mean equinox B%s",
+		               astFmtDecimalYr( palEpb( equinox ), 9 ) );
+            }
+            if( astTestEpoch( this ) || astGetUseDefs( this ) ) {
+               pos += sprintf( gettitle_buff + pos,
+                               "; epoch B%s", astFmtDecimalYr( palEpb( epoch ), 9 ) );
+            }
+	    break;
+
+/* FK5 equatorial coordinates. */
+/* --------------------------- */
+/* Display only the Equinox value. */
+	 case AST__FK5:
+	    pos = sprintf( gettitle_buff, "FK5 equatorial %s", word );
+            if( astTestEquinox( this ) || astGetUseDefs( this ) ) {
+   	       pos += sprintf( gettitle_buff + pos, "; mean equinox J%s",
+		               astFmtDecimalYr( palEpj( equinox ), 9 ) );
+            }
+	    break;
+
+/* J2000 equatorial coordinates. */
+/* ----------------------------- */
+/* Based on the dynamically determined mean equator and equinox of J2000,
+   rather than on a model such as FK4 or FK5 */
+	 case AST__J2000:
+	    pos = sprintf( gettitle_buff, "J2000 equatorial %s", word );
+	    break;
+
+/* ICRS coordinates. */
+/* ----------------- */
+/* ICRS is only like RA/Dec by co-incidence, it is not really an
+   equatorial system by definition. */
+	 case AST__ICRS:
+	    pos = sprintf( gettitle_buff, "ICRS %s", word );
+	    break;
+
+/* AzEl coordinates. */
+/* ----------------- */
+	 case AST__AZEL:
+	    pos = sprintf( gettitle_buff, "Horizon (Azimuth/Elevation) %s", word );
+	    break;
+
+/* Geocentric apparent equatorial coordinates. */
+/* ------------------------------------------ */
+/* Display only the Epoch value. */
+	 case AST__GAPPT:
+	    pos = sprintf( gettitle_buff,
+                           "Geocentric apparent equatorial %s; "
+                           "; epoch J%s", word, astFmtDecimalYr( palEpj( epoch ), 9 ) );
+	    break;
+
+/* Ecliptic coordinates. */
+/* --------------------- */
+/* Display only the Equinox value. */
+	 case AST__ECLIPTIC:
+	    pos = sprintf( gettitle_buff, "Ecliptic %s", word );
+            if( astTestEquinox( this ) || astGetUseDefs( this ) ) {
+   	       pos += sprintf( gettitle_buff + pos, "; mean equinox J%s",
+		               astFmtDecimalYr( palEpj( equinox ), 9 ) );
+            }
+	    break;
+
+/* Helio-ecliptic coordinates. */
+/* --------------------------- */
+/* Display only the Epoch value (equinox is fixed). */
+	 case AST__HELIOECLIPTIC:
+	    pos = sprintf( gettitle_buff, "Helio-ecliptic %s; mean equinox J2000", word );
+            if( astTestEpoch( this ) || astGetUseDefs( this ) ) {
+   	       pos += sprintf( gettitle_buff + pos, "; epoch J%s",
+		               astFmtDecimalYr( palEpj( epoch ), 9 ) );
+            }
+	    break;
+
+/* Galactic coordinates. */
+/* --------------------- */
+/* Do not display an Equinox or Epoch value. */
+	 case AST__GALACTIC:
+	    pos = sprintf( gettitle_buff, "IAU (1958) galactic %s", word );
+	    break;
+
+/* Supergalactic coordinates. */
+/* -------------------------- */
+/* Do not display an Equinox or Epoch value. */
+	 case AST__SUPERGALACTIC:
+	    pos = sprintf( gettitle_buff,
+                           "De Vaucouleurs supergalactic %s", word );
+	    break;
+
+/* Unknown coordinates. */
+/* -------------------------- */
+	 case AST__UNKNOWN:
+	    pos = sprintf( gettitle_buff,
+                           "Spherical %s", word );
+	    break;
+
+/* Report an error if the coordinate system was not recognised. */
+	 default:
+	    astError( AST__SCSIN, "astGetTitle(%s): Corrupt %s contains "
+		      "invalid sky coordinate system identification code "
+		      "(%d).", status, astGetClass( this ), astGetClass( this ),
+		     (int) system );
+	    break;
+         }
+
+/* If OK, we add either a description of the sky projection, or (if used)
+   a description of the origin or pole of the offset coordinate system.
+   We include only one of these two strings in order to keep the length
+   of the title down to a reasonable value.*/
+         if ( astOK ) {
+
+/* If the SkyRef attribute has set values, create a description of the offset
+   coordinate system. */
+            if( offset ){
+               word = ( astGetSkyRefIs( this ) == AST__POLE_REF )?"pole":"origin";
+               lextra = sprintf( gettitle_buff2, "%s at %s ", word,
+                           astFormat( this, 0, astGetSkyRef( this, 0 ) ) );
+               lextra += sprintf( gettitle_buff2 + lextra, "%s",
+                           astFormat( this, 1, astGetSkyRef( this, 1 ) ) );
+               extra = gettitle_buff2;
+
+/* Otherwise, get the sky projection description. */
+            } else {
+               extra = projection;
+
+/* Determine the length of the extra information, after removing trailing
+   white space. */
+               for ( lextra = (int) strlen( extra ); lextra > 0; lextra-- ) {
+                  if ( !isspace( extra[ lextra - 1 ] ) ) break;
+               }
+            }
+
+/* If non-blank extra information is available, append it to the title string,
+   checking that the end of the buffer is not over-run. */
+            if ( lextra ) {
+               p = "; ";
+               while ( ( pos < AST__SKYFRAME_GETTITLE_BUFF_LEN ) && *p ) gettitle_buff[ pos++ ] = *p++;
+               p = extra;
+               while ( ( pos < AST__SKYFRAME_GETTITLE_BUFF_LEN ) &&
+                       ( p < ( extra + lextra ) ) ) gettitle_buff[ pos++ ] = *p++;
+               if( extra == projection ) {
+                  p = " projection";
+                  while ( ( pos < AST__SKYFRAME_GETTITLE_BUFF_LEN ) && *p ) gettitle_buff[ pos++ ] = *p++;
+               }
+               gettitle_buff[ pos ] = '\0';
+            }
+         }
+      }
+   }
+
+/* If an error occurred, clear the returned pointer value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetUnit( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetUnit
+
+*  Purpose:
+*     Obtain a pointer to the Unit string for a SkyFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     const char *GetUnit( AstFrame *this_frame, int axis )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astGetUnit method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Unit string for a specified axis
+*     of a SkyFrame. If the Unit attribute has not been set for the axis, a
+*     pointer to a suitable default string is returned instead. This string may
+*     depend on the value of the Format attribute for the axis and, in turn, on
+*     the type of sky coordinate system that the SkyFrame describes.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        The number of the axis (zero-based) for which information is required.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the Unit value.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   const char *result;           /* Pointer value to return */
+   int format_set;               /* Format attribute set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astGetUnit" );
+
+/* The Unit value may depend on the value of the Format attribute, so
+   determine if a Format value has been set for the axis and set a
+   temporary value if it has not. Use the GetFormat member function
+   for this class together with member functions inherited from the
+   parent class (rather than using the object's methods directly)
+   because if any of these methods have been over-ridden by a derived
+   class the Format string syntax may no longer be compatible with
+   this class. */
+   format_set = (*parent_testformat)( this_frame, axis, status );
+   if ( !format_set ) {
+      (*parent_setformat)( this_frame, axis, GetFormat( this_frame, axis, status ), status );
+   }
+
+/* Use the parent GetUnit method to return a pointer to the required Unit
+   string. */
+   result = (*parent_getunit)( this_frame, axis, status );
+
+/* If necessary, clear any temporary Format value that was set above. */
+   if ( !format_set ) (*parent_clearformat)( this_frame, axis, status );
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+void astInitSkyFrameVtab_(  AstSkyFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSkyFrameVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a SkyFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void astInitSkyFrameVtab( AstSkyFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SkyFrame vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the SkyFrame class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrameVtab *frame;          /* Pointer to Frame component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   int stat;                     /* SLALIB status */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitFrameVtab( (AstFrameVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsASkyFrame) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstFrameVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->ClearAsTime = ClearAsTime;
+   vtab->ClearEquinox = ClearEquinox;
+   vtab->ClearNegLon = ClearNegLon;
+   vtab->ClearProjection = ClearProjection;
+   vtab->GetAsTime = GetAsTime;
+   vtab->GetEquinox = GetEquinox;
+   vtab->GetNegLon = GetNegLon;
+   vtab->GetIsLatAxis = GetIsLatAxis;
+   vtab->GetIsLonAxis = GetIsLonAxis;
+   vtab->GetLatAxis = GetLatAxis;
+   vtab->GetLonAxis = GetLonAxis;
+   vtab->GetProjection = GetProjection;
+   vtab->SetAsTime = SetAsTime;
+   vtab->SetEquinox = SetEquinox;
+   vtab->SetNegLon = SetNegLon;
+   vtab->SetProjection = SetProjection;
+   vtab->SkyOffsetMap = SkyOffsetMap;
+   vtab->TestAsTime = TestAsTime;
+   vtab->TestEquinox = TestEquinox;
+   vtab->TestNegLon = TestNegLon;
+   vtab->TestProjection = TestProjection;
+
+   vtab->TestSkyTol = TestSkyTol;
+   vtab->SetSkyTol = SetSkyTol;
+   vtab->GetSkyTol = GetSkyTol;
+   vtab->ClearSkyTol = ClearSkyTol;
+
+   vtab->TestSkyRef = TestSkyRef;
+   vtab->SetSkyRef = SetSkyRef;
+   vtab->GetSkyRef = GetSkyRef;
+   vtab->ClearSkyRef = ClearSkyRef;
+
+   vtab->TestSkyRefP = TestSkyRefP;
+   vtab->SetSkyRefP = SetSkyRefP;
+   vtab->GetSkyRefP = GetSkyRefP;
+   vtab->ClearSkyRefP = ClearSkyRefP;
+
+   vtab->TestSkyRefIs = TestSkyRefIs;
+   vtab->SetSkyRefIs = SetSkyRefIs;
+   vtab->GetSkyRefIs = GetSkyRefIs;
+   vtab->ClearSkyRefIs = ClearSkyRefIs;
+
+   vtab->TestAlignOffset = TestAlignOffset;
+   vtab->SetAlignOffset = SetAlignOffset;
+   vtab->GetAlignOffset = GetAlignOffset;
+   vtab->ClearAlignOffset = ClearAlignOffset;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   frame = (AstFrameVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_gettop = frame->GetTop;
+   frame->GetTop = GetTop;
+
+   parent_setobsalt = frame->SetObsAlt;
+   frame->SetObsAlt = SetObsAlt;
+
+   parent_setobslat = frame->SetObsLat;
+   frame->SetObsLat = SetObsLat;
+
+   parent_setobslon = frame->SetObsLon;
+   frame->SetObsLon = SetObsLon;
+
+   parent_clearobslon = frame->ClearObsLon;
+   frame->ClearObsLon = ClearObsLon;
+
+   parent_clearobsalt = frame->ClearObsAlt;
+   frame->ClearObsAlt = ClearObsAlt;
+
+   parent_clearobslat = frame->ClearObsLat;
+   frame->ClearObsLat = ClearObsLat;
+
+   parent_getbottom = frame->GetBottom;
+   frame->GetBottom = GetBottom;
+
+   parent_getepoch = frame->GetEpoch;
+   frame->GetEpoch = GetEpoch;
+
+   parent_format = frame->Format;
+   frame->Format = Format;
+   parent_gap = frame->Gap;
+   frame->Gap = Gap;
+   parent_getdirection = frame->GetDirection;
+   frame->GetDirection = GetDirection;
+   parent_getdomain = frame->GetDomain;
+   frame->GetDomain = GetDomain;
+   parent_getsystem = frame->GetSystem;
+   frame->GetSystem = GetSystem;
+   parent_setsystem = frame->SetSystem;
+   frame->SetSystem = SetSystem;
+   parent_clearsystem = frame->ClearSystem;
+   frame->ClearSystem = ClearSystem;
+   parent_getalignsystem = frame->GetAlignSystem;
+   frame->GetAlignSystem = GetAlignSystem;
+   parent_getformat = frame->GetFormat;
+   frame->GetFormat = GetFormat;
+   parent_getlabel = frame->GetLabel;
+   frame->GetLabel = GetLabel;
+   parent_getsymbol = frame->GetSymbol;
+   frame->GetSymbol = GetSymbol;
+   parent_gettitle = frame->GetTitle;
+   frame->GetTitle = GetTitle;
+   parent_getunit = frame->GetUnit;
+   frame->GetUnit = GetUnit;
+   parent_match = frame->Match;
+   frame->Match = Match;
+   parent_overlay = frame->Overlay;
+   frame->Overlay = Overlay;
+   parent_subframe = frame->SubFrame;
+   frame->SubFrame = SubFrame;
+   parent_unformat = frame->Unformat;
+   frame->Unformat = Unformat;
+
+   parent_setdut1 = frame->SetDut1;
+   frame->SetDut1 = SetDut1;
+
+   parent_cleardut1 = frame->ClearDut1;
+   frame->ClearDut1 = ClearDut1;
+
+/* Store replacement pointers for methods which will be over-ridden by new
+   member functions implemented here. */
+   frame->Angle = Angle;
+   frame->Distance = Distance;
+   frame->FrameGrid = FrameGrid;
+   frame->Intersect = Intersect;
+   frame->Norm = Norm;
+   frame->NormBox = NormBox;
+   frame->Resolve = Resolve;
+   frame->ResolvePoints = ResolvePoints;
+   frame->Offset = Offset;
+   frame->Offset2 = Offset2;
+   frame->ValidateSystem = ValidateSystem;
+   frame->SystemString = SystemString;
+   frame->SystemCode = SystemCode;
+   frame->LineDef = LineDef;
+   frame->LineContains = LineContains;
+   frame->LineCrossing = LineCrossing;
+   frame->LineOffset = LineOffset;
+   frame->GetActiveUnit = GetActiveUnit;
+   frame->TestActiveUnit = TestActiveUnit;
+   frame->MatchAxesX = MatchAxesX;
+
+/* Store pointers to inherited methods that will be invoked explicitly
+   by this class. */
+   parent_clearformat = frame->ClearFormat;
+   parent_setformat = frame->SetFormat;
+   parent_testformat = frame->TestFormat;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "SkyFrame",
+               "Description of celestial coordinate system" );
+
+/* Initialize constants for converting between hours, degrees and
+   radians, etc.. */
+   LOCK_MUTEX2
+   palDtf2r( 1, 0, 0.0, &hr2rad, &stat );
+   palDaf2r( 1, 0, 0.0, &deg2rad, &stat );
+   palDaf2r( 180, 0, 0.0, &pi, &stat );
+   piby2 = 0.5*pi;
+   UNLOCK_MUTEX2
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void Intersect( AstFrame *this_frame, const double a1[2],
+                       const double a2[2], const double b1[2],
+                       const double b2[2], double cross[2],
+                       int *status ) {
+/*
+*  Name:
+*     Intersect
+
+*  Purpose:
+*     Find the point of intersection between two geodesic curves.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void Intersect( AstFrame *this_frame, const double a1[2],
+*                      const double a2[2], const double b1[2],
+*                      const double b2[2], double cross[2],
+*                      int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astIntersect method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function finds the coordinate values at the point of
+*     intersection between two geodesic curves. Each curve is specified
+*     by two points on the curve.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     a1
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a point on the first
+*        geodesic curve.
+*     a2
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a second point on the
+*        first geodesic curve.
+*     b1
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a point on the second
+*        geodesic curve.
+*     b2
+*        An array of double, with one element for each Frame axis.
+*        This should contain the coordinates of a second point on
+*        the second geodesic curve.
+*     cross
+*        An array of double, with one element for each Frame axis
+*        in which the coordinates of the required intersection
+*        point will be returned. These will be AST__BAD if the curves do
+*        not intersect.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*     - For SkyFrames each curve will be a great circle, and in general
+*     each pair of curves will intersect at two diametrically opposite
+*     points on the sky. The returned position is the one which is
+*     closest to point "a1".
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;          /* Pointer to the SkyFrame structure */
+   const int *perm;            /* Pointer to axis permutation array */
+   double aa1[ 2 ];            /* Permuted coordinates for a1 */
+   double aa2[ 2 ];            /* Permuted coordinates for a2 */
+   double bb1[ 2 ];            /* Permuted coordinates for b1 */
+   double bb2[ 2 ];            /* Permuted coordinates for b2 */
+   double cc[ 2 ];             /* Permuted coords at intersection */
+   double d1;                  /* Cos(distance from a1 to vp) */
+   double d2;                  /* Cos(distance from a1 to -vp) */
+   double na[ 3 ];             /* Normal to the a1/a2 great circle */
+   double nb[ 3 ];             /* Normal to the b1/b2 great circle */
+   double va1[ 3 ];            /* Vector pointing at a1 */
+   double va2[ 3 ];            /* Vector pointing at a2 */
+   double vb1[ 3 ];            /* Vector pointing at b1 */
+   double vb2[ 3 ];            /* Vector pointing at b2 */
+   double vmod;                /* Length of "vp" */
+   double vp[ 3 ];             /* Vector pointing at the intersection */
+   double vpn[ 3 ];            /* Normalised vp */
+   int iaxis;                  /* Axis index */
+
+/* Initialise. */
+   cross[ 0 ] = AST__BAD;
+   cross[ 1 ] = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Check that all supplied values are OK. */
+   if ( ( a1[ 0 ] != AST__BAD ) && ( a1[ 1 ] != AST__BAD ) &&
+        ( a2[ 0 ] != AST__BAD ) && ( a2[ 1 ] != AST__BAD ) &&
+        ( b1[ 0 ] != AST__BAD ) && ( b1[ 1 ] != AST__BAD ) &&
+        ( b2[ 0 ] != AST__BAD ) && ( b2[ 1 ] != AST__BAD ) ) {
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+      perm = astGetPerm( this );
+      if ( astOK ) {
+
+/* Apply the axis permutation array to obtain the coordinates of
+   the points in the required (longitude,latitude) order. */
+         for( iaxis = 0; iaxis < 2; iaxis++ ) {
+            aa1[ perm[ iaxis ] ] = a1[ iaxis ];
+            aa2[ perm[ iaxis ] ] = a2[ iaxis ];
+            bb1[ perm[ iaxis ] ] = b1[ iaxis ];
+            bb2[ perm[ iaxis ] ] = b2[ iaxis ];
+         }
+
+/* Convert each (lon,lat) pair into a unit length 3-vector. */
+         palDcs2c( aa1[ 0 ], aa1[ 1 ], va1 );
+         palDcs2c( aa2[ 0 ], aa2[ 1 ], va2 );
+         palDcs2c( bb1[ 0 ], bb1[ 1 ], vb1 );
+         palDcs2c( bb2[ 0 ], bb2[ 1 ], vb2 );
+
+/* Find the normal vectors to the two great cicles. */
+         palDvxv( va1, va2, na );
+         palDvxv( vb1, vb2, nb );
+
+/* The cross product of the two normal vectors points to one of the
+   two diametrically opposite intersections. */
+         palDvxv( na, nb, vp );
+
+/* Normalise the "vp" vector, also obtaining its original modulus. */
+         palDvn( vp, vpn, &vmod );
+         if( vmod != 0.0 ) {
+
+/* We want the intersection which is closest to "a1". The dot product
+   gives the cos(distance) between two positions. So find the dot
+   product between "a1" and "vpn", and then between "a1" and the point
+   diametrically opposite "vpn". */
+            d1 = palDvdv( vpn, va1 );
+            vpn[ 0 ] = -vpn[ 0 ];
+            vpn[ 1 ] = -vpn[ 1 ];
+            vpn[ 2 ] = -vpn[ 2 ];
+            d2 = palDvdv( vpn, va1 );
+
+/* Revert to "vpn" if it is closer to "a1". */
+            if( d1 > d2 ) {
+               vpn[ 0 ] = -vpn[ 0 ];
+               vpn[ 1 ] = -vpn[ 1 ];
+               vpn[ 2 ] = -vpn[ 2 ];
+            }
+
+/* Convert the vector back into a (lon,lat) pair, and put the longitude
+   into the range 0 to 2.pi. */
+            palDcc2s( vpn, cc, cc + 1 );
+            *cc = palDranrm( *cc );
+
+/* Permute the result coordinates to undo the effect of the SkyFrame
+   axis permutation array. */
+            cross[ 0 ] = cc[ perm[ 0 ] ];
+            cross[ 1 ] = cc[ perm[ 1 ] ];
+         }
+      }
+   }
+}
+
+static int IsEquatorial( AstSystemType system, int *status ) {
+/*
+*  Name:
+*     IsEquatorial
+
+*  Purpose:
+*     Test for an equatorial sky coordinate system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int IsEquatorial( AstSystemType system, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function returns a boolean value to indicate if a sky coordinate
+*     system is equatorial.
+
+*  Parameters:
+*     system
+*        Code to identify the sky coordinate system.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the sky coordinate system is equatorial, otherwise zero.
+
+*  Notes:
+*     -  A value of zero is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Determine if the sky coordinate system is an equatorial one. Note,
+   ICRS is not equatorial by definition, but is included here because it
+   is normally treated as an equatorial system in terms of the axis
+   labels, formats, etc. */
+   result = ( ( system == AST__FK4 ) ||
+              ( system == AST__FK4_NO_E ) ||
+              ( system == AST__ICRS ) ||
+              ( system == AST__FK5 ) ||
+              ( system == AST__J2000 ) ||
+              ( system == AST__GAPPT ) );
+
+/* Return the result. */
+   return result;
+}
+
+static int LineContains( AstFrame *this, AstLineDef *l, int def, double *point, int *status ) {
+/*
+*  Name:
+*     LineContains
+
+*  Purpose:
+*     Determine if a line contains a point.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int LineContains( AstFrame *this, AstLineDef *l, int def, double *point, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astLineContains
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function determines if the supplied point is on the supplied
+*     line within the supplied Frame. The start point of the line is
+*     considered to be within the line, but the end point is not. The tests
+*     are that the point of closest approach of the line to the point should
+*     be between the start and end, and that the distance from the point to
+*     the point of closest aproach should be less than 1.0E-7 of the length
+*     of the line.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     l
+*        Pointer to the structure defining the line.
+*     def
+*        Should be set non-zero if the "point" array was created by a
+*        call to astLineCrossing (in which case it may contain extra
+*        information following the axis values),and zero otherwise.
+*     point
+*        Point to an array containing the axis values of the point to be
+*        tested, possibly followed by extra cached information (see "def").
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the line contains the point.
+
+*  Notes:
+*     - The pointer supplied for "l" should have been created using the
+*     astLineDef method. These structures contained cached information about
+*     the lines which improve the efficiency of this method when many
+*     repeated calls are made. An error will be reported if the structure
+*     does not refer to the Frame specified by "this".
+*     - Zero will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   SkyLineDef *sl;               /* SkyLine information */
+   const int *perm;              /* Pointer to axis permutation array */
+   double *b;                    /* Pointer to Cartesian coords array */
+   double bb[3];                 /* Buffer for Cartesian coords */
+   double p1[2];                 /* Buffer for Spherical coords */
+   double t1, t2;
+   int result;                   /* Returned value */
+
+/* Initialise */
+   result =0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check that the line refers to the supplied Frame. */
+   if( l->frame != this ) {
+      astError( AST__INTER, "astLineContains(%s): The supplied line does "
+                "not relate to the supplied %s (AST internal programming "
+                "error).", status, astGetClass( this ), astGetClass( this ) );
+
+/* Check the axis values are good */
+   } else if( point[ 0 ] != AST__BAD && point[ 1 ] != AST__BAD ){
+
+/* Get a pointer to an array holding the corresponding Cartesian coords. */
+      if( def ) {
+         b = point + 2;
+
+      } else {
+         perm = astGetPerm( this );
+         if ( perm ) {
+            p1[ perm[ 0 ] ] = point[ 0 ];
+            p1[ perm[ 1 ] ] = point[ 1 ];
+            palDcs2c( p1[ 0 ], p1[ 1 ], bb );
+            b = bb;
+         } else {
+            b = NULL;
+         }
+      }
+
+/* Recast the supplied AstLineDef into a SkyLineDef to get the different
+   structure (we know from the above check on the Frame that it is safe to
+   do this). */
+      sl = (SkyLineDef *) l;
+
+/* Check that the point of closest approach of the line to the point is
+   within the limits of the line. */
+      if( LineIncludes( sl, b, status ) ){
+
+/* Check that the point is 90 degrees away from the pole of the great
+   circle containing the line. */
+        t1 = palDvdv( sl->q, b );
+        t2 = 1.0E-7*sl->length;
+        if( t2 < 1.0E-10 ) t2 = 1.0E-10;
+        if( fabs( t1 ) <= t2 ) result = 1;
+     }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int LineCrossing( AstFrame *this, AstLineDef *l1, AstLineDef *l2,
+                         double **cross, int *status ) {
+/*
+*  Name:
+*     LineCrossing
+
+*  Purpose:
+*     Determine if two lines cross.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int LineCrossing( AstFrame *this, AstLineDef *l1, AstLineDef *l2,
+*                       double **cross, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astLineCrossing
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function determines if the two suplied line segments cross,
+*     and if so returns the axis values at the point where they cross.
+*     A flag is also returned indicating if the crossing point occurs
+*     within the length of both line segments, or outside one or both of
+*     the line segments.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     l1
+*        Pointer to the structure defining the first line.
+*     l2
+*        Pointer to the structure defining the second line.
+*     cross
+*        Pointer to a location at which to put a pointer to a dynamically
+*        alocated array containing the axis values at the crossing. If
+*        NULL is supplied no such array is returned. Otherwise, the returned
+*        array should be freed using astFree when no longer needed. If the
+*        lines are parallel (i.e. do not cross) then AST__BAD is returned for
+*        all axis values. Note usable axis values are returned even if the
+*        lines cross outside the segment defined by the start and end points
+*        of the lines. The order of axes in the returned array will take
+*        account of the current axis permutation array if appropriate. Note,
+*        sub-classes such as SkyFrame may append extra values to the end
+*        of the basic frame axis values. A NULL pointer is returned if an
+*        error occurs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the lines cross at a point which is
+*     within the [start,end) segment of both lines. If the crossing point
+*     is outside this segment on either line, or if the lines are parallel,
+*     zero is returned. Note, the start point is considered to be inside
+*     the length of the segment, but the end point is outside.
+
+*  Notes:
+*     - The pointers supplied for "l1" and "l2" should have been created
+*     using the astLineDef method. These structures contained cached
+*     information about the lines which improve the efficiency of this method
+*     when many repeated calls are made. An error will be reported if
+*     either structure does not refer to the Frame specified by "this".
+*     - Zero will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   SkyLineDef *sl1;              /* SkyLine information for line 1 */
+   SkyLineDef *sl2;              /* SkyLine information for line 2 */
+   const int *perm;              /* Pointer to axis permutation array */
+   double *crossing;             /* Pointer to returned array */
+   double *b;                    /* Pointer to Cartesian coords */
+   double len;                   /* Vector length */
+   double p[ 2 ];                /* Temporary (lon,lat) pair */
+   double temp[ 3 ];             /* Temporary vector */
+   int result;                   /* Returned value */
+
+/* Initialise */
+   result = 0;
+   if( cross ) *cross = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Allocate returned array (2 elements for the lon and lat values, plus 3
+   for the corresponding (x,y,z) coords). */
+   crossing = astMalloc( sizeof(double)*5 );
+
+/* Check that both lines refer to the supplied Frame. */
+   if( l1->frame != this ) {
+      astError( AST__INTER, "astLineCrossing(%s): First supplied line does "
+                "not relate to the supplied %s (AST internal programming "
+                "error).", status, astGetClass( this ), astGetClass( this ) );
+
+   } else if( l2->frame != this ) {
+      astError( AST__INTER, "astLineCrossing(%s): Second supplied line does "
+                "not relate to the supplied %s (AST internal programming "
+                "error).", status, astGetClass( this ), astGetClass( this ) );
+
+/* Recast the supplied AstLineDefs into a SkyLineDefs to get the different
+   structure (we know from the above check on the Frame that it is safe to
+   do this). */
+   } else if( crossing ){
+      sl1 = (SkyLineDef *) l1;
+      sl2 = (SkyLineDef *) l2;
+
+/* Point of intersection of the two great circles is perpendicular to the
+   pole vectors of both great circles. Put the Cartesian coords in elements
+   2 to 4 of the returned array. */
+      palDvxv( sl1->q, sl2->q, temp );
+      b = crossing + 2;
+      palDvn( temp, b, &len );
+
+/* See if this point is within the length of both arcs. If so return it. */
+      if( LineIncludes( sl2, b, status ) && LineIncludes( sl1, b, status ) ) {
+         result = 1;
+
+/* If not, see if the negated b vector is within the length of both arcs.
+   If so return it. Otherwise, we return zero. */
+      } else {
+         b[ 0 ] *= -1.0;
+         b[ 1 ] *= -1.0;
+         b[ 2 ] *= -1.0;
+         if( LineIncludes( sl2, b, status ) && LineIncludes( sl1, b, status ) ) result = 1;
+      }
+
+/* Store the spherical coords in elements 0 and 1 of the returned array. */
+      palDcc2s( b, p, p + 1 );
+
+/* Permute the spherical axis value into the order used by the SkyFrame. */
+      perm = astGetPerm( this );
+      if( perm ){
+         crossing[ 0 ] = p[ perm[ 0 ] ];
+         crossing[ 1 ] = p[ perm[ 1 ] ];
+      }
+   }
+
+/* If an error occurred, return 0. */
+   if( !astOK ) {
+      result = 0;
+      crossing = astFree( crossing );
+   }
+
+/* Return the array */
+   if( cross ) {
+      *cross = crossing;
+   } else {
+      crossing = astFree( crossing );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstLineDef *LineDef( AstFrame *this, const double start[2],
+                            const double end[2], int *status ) {
+/*
+*  Name:
+*     LineDef
+
+*  Purpose:
+*     Creates a structure describing a line segment in a 2D Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     AstLineDef *LineDef( AstFrame *this, const double start[2],
+*                          const double end[2], int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astLineDef
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function creates a structure containing information describing a
+*     given line segment within the supplied 2D Frame. This may include
+*     information which allows other methods such as astLineCrossing to
+*     function more efficiently. Thus the returned structure acts as a
+*     cache to store intermediate values used by these other methods.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame. Must have 2 axes.
+*     start
+*        An array of 2 doubles marking the start of the line segment.
+*     end
+*        An array of 2 doubles marking the end of the line segment.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the memory structure containing the description of the
+*     line. This structure should be freed using astFree when no longer
+*     needed. A NULL pointer is returned (without error) if any of the
+*     supplied axis values are AST__BAD.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   SkyLineDef *result;           /* Returned value */
+   const int *perm;              /* Axis permutation array */
+   double le;                    /* Length of end vector */
+   double len;                   /* Permuted point1 coordinates */
+   double ls;                    /* Length of start vector */
+   double p1[ 2 ];               /* Permuted point1 coordinates */
+   double p2[ 2 ];               /* Permuted point2 coordinates */
+   double temp[3];               /* Cartesian coords at offset position */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Check the axis values are good */
+   if( start[ 0 ] != AST__BAD && start[ 1 ] != AST__BAD &&
+       end[ 0 ] != AST__BAD && end[ 1 ] != AST__BAD ) {
+
+/* Allocate memory for the returned structure. */
+      result = astMalloc( sizeof( SkyLineDef ) );
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+      perm = astGetPerm( this );
+      if ( perm ) {
+
+/* Apply the axis permutation array to obtain the coordinates of the two
+   input points in the required (longitude,latitude) order. */
+         p1[ perm[ 0 ] ] = start[ 0 ];
+         p1[ perm[ 1 ] ] = start[ 1 ];
+         p2[ perm[ 0 ] ] = end[ 0 ];
+         p2[ perm[ 1 ] ] = end[ 1 ];
+
+/* Convert each point into a 3-vector of unit length and store in the
+   returned structure. */
+         palDcs2c( p1[ 0 ], p1[ 1 ], result->start );
+         palDcs2c( p2[ 0 ], p2[ 1 ], result->end );
+
+/* Calculate the great circle distance between the points in radians and
+   store in the result structure. Correct for rounding errors in palDcs2c
+   that can result in the vectors not having exactly unit length. */
+         result->length = palDvdv( result->start, result->end );
+         ls = result->start[0]*result->start[0] +
+              result->start[1]*result->start[1] +
+              result->start[2]*result->start[2];
+         le = result->end[0]*result->end[0] +
+              result->end[1]*result->end[1] +
+              result->end[2]*result->end[2];
+         result->length = acos( result->length/sqrt( ls*le ) );
+
+/* Find a unit vector representing the pole of the system in which the
+   equator is given by the great circle. This is such that going the
+   short way from the start to the end, the pole is to the left of the
+   line as seen by the observer (i.e. from the centre of the sphere).
+   If the line has zero length, or 180 degrees length, the pole is
+   undefined, so we use an arbitrary value. */
+         if( result->length == 0.0 || result->length > pi - 5.0E-11 ) {
+            palDcs2c( p1[ 0 ] + 0.01, p1[ 1 ] + 0.01, temp );
+            palDvxv( temp, result->start, result->dir );
+         } else {
+            palDvxv( result->end, result->start, result->dir );
+         }
+         palDvn( result->dir, result->q, &len );
+
+/* Also store a point which is 90 degrees along the great circle from the
+   start. */
+         palDvxv( result->start, result->q, result->dir );
+
+/* Store a pointer to the defining SkyFrame. */
+         result->frame = this;
+
+/* Indicate that the line is considered to be terminated at the start and
+   end points. */
+         result->infinite = 0;
+
+/* Normalise the spherical start and end positions stored in the returned
+   structure. */
+         result->start_2d[ 0 ] = start[ 0 ];
+         result->start_2d[ 1 ] = start[ 1 ];
+         result->end_2d[ 0 ] = end[ 0 ];
+         result->end_2d[ 1 ] = end[ 1 ];
+
+         astNorm( this, result->start_2d );
+         astNorm( this, result->end_2d );
+      }
+   }
+
+/* Free the returned pointer if an error occurred. */
+   if( !astOK ) result = astFree( result );
+
+/* Return a pointer to the output structure. */
+   return (AstLineDef *) result;
+}
+
+static int LineIncludes( SkyLineDef *l, double point[3], int *status ) {
+/*
+*  Name:
+*     LineIncludes
+
+*  Purpose:
+*     Determine if a line includes a point which is known to be in the
+*     great circle.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int LineIncludes( SkyLineDef *l, double point[3], int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astLineIncludes
+*     method inherited from the Frame class).
+
+*  Description:
+*     The supplied point is assumed to be a point on the great circle of
+*     which the supplied line is a segment. This function returns true if
+*     "point" is within the bounds of the segment (the end point of the
+*     line is assumed * not to be part of the segment).
+
+*  Parameters:
+*     l
+*        Pointer to the structure defining the line.
+*     point
+*        An array holding the Cartesian coords of the point to be tested.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the line includes the point.
+
+*  Notes:
+*     - Zero will be returned if this function is invoked with the global
+*     error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   double t1, t2, t3;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* If the line is of infite length, it is assumed to include the supplied
+   point. */
+   if( l->infinite ) return 1;
+
+/* Otherwise, get the unsigned distance of the point from the start of the
+   line in the range 0 - 180 degs. Check it is less than the line length.
+   Then check that the point is not more than 90 degs away from the quarter
+   point. */
+   t1 = palDvdv( l->start, point );
+   t2 = acos( t1 );
+   t3 = palDvdv( l->dir, point );
+   return ( ((l->length > 0) ? t2 < l->length : t2 == 0.0 ) && t3 >= -1.0E-8 );
+}
+
+static void LineOffset( AstFrame *this, AstLineDef *line, double par,
+                        double prp, double point[2], int *status ){
+/*
+*  Name:
+*     LineOffset
+
+*  Purpose:
+*     Find a position close to a line.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void LineOffset( AstFrame *this, AstLineDef *line, double par,
+*                      double prp, double point[2], int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astLineOffset
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a position formed by moving a given distance along
+*     the supplied line, and then a given distance away from the supplied line.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     line
+*        Pointer to the structure defining the line.
+*     par
+*        The distance to move along the line from the start towards the end.
+*     prp
+*        The distance to move at right angles to the line. Positive
+*        values result in movement to the left of the line, as seen from
+*        the observer, when moving from start towards the end.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The pointer supplied for "line" should have been created using the
+*     astLineDef method. This structure contains cached information about the
+*     line which improves the efficiency of this method when many repeated
+*     calls are made. An error will be reported if the structure does not
+*     refer to the Frame specified by "this".
+*-
+*/
+
+/* Local Variables; */
+    SkyLineDef *sl;
+    const int *perm;
+    double c;
+    double nx;
+    double ny;
+    double nz;
+    double p[2];
+    double s;
+    double v[3];
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Check that the line refers to the supplied Frame. */
+   if( line->frame != this ) {
+      astError( AST__INTER, "astLineOffset(%s): The supplied line does "
+                "not relate to the supplied %s (AST internal programming "
+                "error).", status, astGetClass( this ), astGetClass( this ) );
+
+/* This implementation uses spherical geometry. */
+   } else {
+
+/* Get a pointer to the SkyLineDef structure. */
+      sl = (SkyLineDef *) line;
+
+/* Move a distance par from start to end. */
+      c = cos( par );
+      s = sin( par );
+      nx = c * sl->start[ 0 ] + s * sl->dir[ 0 ];
+      ny = c * sl->start[ 1 ] + s * sl->dir[ 1 ];
+      nz = c * sl->start[ 2 ] + s * sl->dir[ 2 ];
+
+/* Move a distance prp from this point towards the pole point. */
+      if( prp != 0.0 ) {
+         c = cos( prp );
+         s = sin( prp );
+         v[ 0 ] = c * nx + s * sl->q[ 0 ];
+         v[ 1 ] = c * ny + s * sl->q[ 1 ];
+         v[ 2 ] = c * nz + s * sl->q[ 2 ];
+      } else {
+         v[ 0 ] = nx;
+         v[ 1 ] = ny;
+         v[ 2 ] = nz;
+      }
+
+/* Convert to lon/lat */
+      palDcc2s( v, p, p + 1 );
+
+/* Permute the spherical axis value into the order used by the SkyFrame. */
+      perm = astGetPerm( this );
+      if( perm ){
+         point[ 0 ] = p[ perm[ 0 ] ];
+         point[ 1 ] = p[ perm[ 1 ] ];
+      }
+   }
+}
+
+static int MakeSkyMapping( AstSkyFrame *target, AstSkyFrame *result,
+                           AstSystemType align_sys, AstMapping **map, int *status ) {
+/*
+*  Name:
+*     MakeSkyMapping
+
+*  Purpose:
+*     Generate a Mapping between two SkyFrames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int MakeSkyMapping( AstSkyFrame *target, AstSkyFrame *result,
+*                         AstSystemType align_sys, AstMapping **map, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function takes two SkyFrames and generates a Mapping that
+*     converts between them, taking account of differences in their
+*     coordinate systems, equinox value, epoch, etc. (but not allowing
+*     for any axis permutations).
+
+*  Parameters:
+*     target
+*        Pointer to the first SkyFrame.
+*     result
+*        Pointer to the second SkyFrame.
+*     align_sys
+*        The system in which to align the two SkyFrames.
+*     map
+*        Pointer to a location which is to receive a pointer to the
+*        returned Mapping. The forward transformation of this Mapping
+*        will convert from "target" coordinates to "result"
+*        coordinates, and the inverse transformation will convert in
+*        the opposite direction (all coordinate values in radians).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the Mapping could be generated, or zero if the two
+*     SkyFrames are sufficiently un-related that no meaningful Mapping
+*     can be produced.
+
+*  Notes:
+*     A value of zero is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*/
+
+/* Local Constants: */
+#define MAX_ARGS 4               /* Max arguments for an SlaMap conversion */
+
+/* Local Variables: */
+   AstMapping *omap;             /* Mapping from coorinates to offsets */
+   AstMapping *tmap2;            /* Temporary Mapping */
+   AstMapping *tmap;             /* Temporary Mapping */
+   AstSlaMap *slamap;            /* Pointer to SlaMap */
+   AstSystemType result_system;  /* Code to identify result coordinate system */
+   AstSystemType system;         /* Code to identify coordinate system */
+   AstSystemType target_system;  /* Code to identify target coordinate system */
+   double args[ MAX_ARGS ];      /* Conversion argument array */
+   double epoch;                 /* Epoch as Modified Julian Date */
+   double epoch_B;               /* Besselian epoch as decimal years */
+   double epoch_J;               /* Julian epoch as decimal years */
+   double equinox;               /* Equinox as Modified Julian Date */
+   double equinox_B;             /* Besselian equinox as decimal years */
+   double equinox_J;             /* Julian equinox as decimal years */
+   double diurab;                /* Magnitude of diurnal aberration vector */
+   double last;                  /* Local Apparent Sidereal Time */
+   double lat;                   /* Observers latitude */
+   double result_epoch;          /* Result frame Epoch */
+   double result_equinox;        /* Result frame Epoch */
+   double target_epoch;          /* Target frame Epoch */
+   double target_equinox;        /* Target frame Epoch */
+   int isunit;                   /* Is the SlaMap effectively a unit mapping? */
+   int match;                    /* Mapping can be generated? */
+   int step1;                    /* Convert target to FK5 J2000? */
+   int step2;                    /* Convert FK5 J2000 to align sys? */
+   int step3;                    /* Convert align sys to FK5 J2000? */
+   int step4;                    /* Convert FK5 J2000 to result? */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise the returned values. */
+   match = 1;
+   *map = NULL;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   epoch_B = 0.0;
+   epoch_J = 0.0;
+   equinox_B = 0.0;
+   equinox_J = 0.0;
+
+/* Get the two epoch values. */
+   result_epoch = astGetEpoch( result );
+   target_epoch = astGetEpoch( target );
+
+/* Get the two equinox values. */
+   result_equinox = astGetEquinox( result );
+   target_equinox = astGetEquinox( target );
+
+/* Get the two system values. */
+   result_system = astGetSystem( result );
+   target_system = astGetSystem( target );
+
+/* If either system is not references to the equinox given by the Equinox
+   attribute, then use the equinox of the other system rather than
+   adopting the arbitrary default of J2000. */
+   if( !EQREF(result_system) ) result_equinox = target_equinox;
+   if( !EQREF(target_system) ) target_equinox = result_equinox;
+
+/* If both systems are unknown, assume they are the same. Return a UnitMap.
+   We need to do this, otherwise a simple change of Title (for instance)
+   will result in a FrameSet whose current Frame has System=AST__UNKNOWN
+   loosing its integrity. */
+   if( target_system == AST__UNKNOWN && result_system == AST__UNKNOWN ) {
+      *map = (AstMapping *) astUnitMap( 2, "", status );
+      return 1;
+   }
+
+/* The total Mapping is divided into two parts in series; the first part
+   converts from the target SkyFrame to the alignment system, using the
+   Epoch and Equinox of the target Frame, the second part converts from
+   the alignment system to the result SkyFrame, using the Epoch and Equinox
+   of the result Frame. Each of these parts has an arbitrary input and an
+   output system, and therefore could be implemented using a collection
+   of NxN conversions. To reduce the complexity, each part is implement
+   by converting from the input system to FK5 J2000, and then from FK5
+   J2000 to the output system. This scheme required only N conversions
+   rather than NxN. Thus overall the total Mapping is made up of 4 steps
+   in series. Some of these steps may be ommitted if they are effectively
+   a UnitMap. Determine which steps need to be included. Assume all need
+   to be done to begin with. */
+   step1 = 1;
+   step2 = 1;
+   step3 = 1;
+   step4 = 1;
+
+/* If the target system is the same as the alignment system, neither of the
+   first 2 steps need be done. */
+   if( target_system == align_sys ) {
+      step1 = 0;
+      step2 = 0;
+   }
+
+/* If the result system is the same as the alignment system, neither of the
+   last 2 steps need be done. */
+   if( result_system == align_sys ) {
+      step3 = 0;
+      step4 = 0;
+   }
+
+/* If the two epochs are the same, or if the alignment system is FK5 J2000,
+   steps 2 and 3 are not needed. */
+   if( step2 && step3 ) {
+      if( align_sys == AST__FK5 || result_epoch == target_epoch ) {
+      step2 = 0;
+      step3 = 0;
+      }
+   }
+
+/* None are needed if the target and result SkyFrames have the same
+   System, Epoch and Equinox. */
+   if(  result_system == target_system &&
+        result_epoch == target_epoch &&
+        result_equinox == target_equinox ) {
+      step1 = 0;
+      step2 = 0;
+      step3 = 0;
+      step4 = 0;
+   }
+
+/* Create an initial (null) SlaMap. */
+   slamap = astSlaMap( 0, "", status );
+
+/* Define local macros as shorthand for adding sky coordinate
+   conversions to this SlaMap.  Each macro simply stores details of
+   the additional arguments in the "args" array and then calls
+   astSlaAdd. The macros differ in the number of additional argument
+   values. */
+   #define TRANSFORM_0(cvt) \
+           astSlaAdd( slamap, cvt, 0, NULL );
+
+   #define TRANSFORM_1(cvt,arg0) \
+           args[ 0 ] = arg0; \
+           astSlaAdd( slamap, cvt, 1, args );
+
+   #define TRANSFORM_2(cvt,arg0,arg1) \
+           args[ 0 ] = arg0; \
+           args[ 1 ] = arg1; \
+           astSlaAdd( slamap, cvt, 2, args );
+
+   #define TRANSFORM_3(cvt,arg0,arg1,arg2) \
+           args[ 0 ] = arg0; \
+           args[ 1 ] = arg1; \
+           args[ 2 ] = arg2; \
+           astSlaAdd( slamap, cvt, 3, args );
+
+   #define TRANSFORM_4(cvt,arg0,arg1,arg2,arg3) \
+           args[ 0 ] = arg0; \
+           args[ 1 ] = arg1; \
+           args[ 2 ] = arg2; \
+           args[ 3 ] = arg3; \
+           astSlaAdd( slamap, cvt, 4, args );
+
+/* Convert _to_ FK5 J2000.0 coordinates. */
+/* ===================================== */
+/* The overall conversion is formulated in four phases. In this first
+   phase, we convert from the target coordinate system to intermediate sky
+   coordinates expressed using the FK5 system, mean equinox J2000.0. */
+
+/* Obtain the sky coordinate system, equinox, epoch, etc, of the target
+   SkyFrame. */
+   system = target_system;
+   equinox = target_equinox;
+   epoch = target_epoch;
+   last = GetLAST( target, status );
+   diurab = GetDiurab( target, status );
+   lat = astGetObsLat( target );
+   if( astOK && step1 ) {
+
+/* Convert the equinox and epoch values (stored as Modified Julian
+   Dates) into the equivalent Besselian and Julian epochs (as decimal
+   years). */
+      equinox_B = palEpb( equinox );
+      equinox_J = palEpj( equinox );
+      epoch_B = palEpb( epoch );
+      epoch_J = palEpj( epoch );
+
+/* Formulate the conversion... */
+
+/* From FK4. */
+/* --------- */
+/* If necessary, apply the old-style FK4 precession model to bring the
+   equinox to B1950.0, with rigorous handling of the E-terms of
+   aberration. Then convert directly to FK5 J2000.0 coordinates. */
+      if ( system == AST__FK4 ) {
+         VerifyMSMAttrs( target, result, 1, "Equinox Epoch", "astMatch", status );
+         if ( equinox_B != 1950.0 ) {
+            TRANSFORM_1( "SUBET", equinox_B )
+            TRANSFORM_2( "PREBN", equinox_B, 1950.0 )
+            TRANSFORM_1( "ADDET", 1950.0 )
+         }
+         TRANSFORM_1( "FK45Z", epoch_B )
+
+/* From FK4 with no E-terms. */
+/* ------------------------- */
+/* This is the same as above, except that we do not need to subtract
+      the E-terms initially as they are already absent. */
+      } else if ( system == AST__FK4_NO_E ) {
+         VerifyMSMAttrs( target, result, 1, "Equinox Epoch", "astMatch", status );
+         if ( equinox_B != 1950.0 ) {
+            TRANSFORM_2( "PREBN", equinox_B, 1950.0 )
+         }
+         TRANSFORM_1( "ADDET", 1950.0 )
+         TRANSFORM_1( "FK45Z", epoch_B )
+
+/* From FK5. */
+/* --------- */
+/* We simply need to apply a precession correction for the change of
+   equinox.  Omit even this if the equinox is already J2000.0. */
+      } else if ( system == AST__FK5 ) {
+         VerifyMSMAttrs( target, result, 1, "Equinox", "astMatch", status );
+         if ( equinox_J != 2000.0 ) {
+            TRANSFORM_2( "PREC", equinox_J, 2000.0 );
+         }
+
+/* From J2000. */
+/* ----------- */
+/* Convert from J2000 to ICRS, then from ICRS to FK5. */
+      } else if ( system == AST__J2000 ) {
+         VerifyMSMAttrs( target, result, 1, "Epoch", "astMatch", status );
+         TRANSFORM_0( "J2000H" )
+         TRANSFORM_1( "HFK5Z", epoch_J );
+
+/* From geocentric apparent. */
+/* ------------------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( system == AST__GAPPT ) {
+         VerifyMSMAttrs( target, result, 1, "Epoch", "astMatch", status );
+         TRANSFORM_2( "AMP", epoch, 2000.0 )
+
+/* From ecliptic coordinates. */
+/* -------------------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( system == AST__ECLIPTIC ) {
+         VerifyMSMAttrs( target, result, 1, "Equinox", "astMatch", status );
+         TRANSFORM_1( "ECLEQ", equinox )
+
+/* From helio-ecliptic coordinates. */
+/* -------------------------------- */
+      } else if ( system == AST__HELIOECLIPTIC ) {
+         VerifyMSMAttrs( target, result, 1, "Epoch", "astMatch", status );
+         TRANSFORM_1( "HEEQ", epoch )
+
+/* From galactic coordinates. */
+/* -------------------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( system == AST__GALACTIC ) {
+         TRANSFORM_0( "GALEQ" )
+
+/* From ICRS. */
+/* ---------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( system == AST__ICRS ) {
+         VerifyMSMAttrs( target, result, 1, "Epoch", "astMatch", status );
+         TRANSFORM_1( "HFK5Z", epoch_J );
+
+/* From supergalactic coordinates. */
+/* ------------------------------- */
+/* Convert to galactic coordinates and then to FK5 J2000.0
+      equatorial. */
+      } else if ( system == AST__SUPERGALACTIC ) {
+         TRANSFORM_0( "SUPGAL" )
+         TRANSFORM_0( "GALEQ" )
+
+/* From AzEl. */
+/* ---------- */
+/* Rotate from horizon to equator (H2E), shift hour angle into RA (H2R),
+      go from geocentric apparent to FK5 J2000. */
+      } else if ( system == AST__AZEL ) {
+         VerifyMSMAttrs( target, result, 1, "ObsLon ObsLat Epoch", "astMatch", status );
+         TRANSFORM_2( "H2E", lat, diurab )
+         TRANSFORM_1( "H2R", last )
+         TRANSFORM_2( "AMP", epoch, 2000.0 )
+
+/* From unknown coordinates. */
+/* ------------------------- */
+/* No conversion is possible. */
+      } else if ( system == AST__UNKNOWN ) {
+         match = 0;
+      }
+   }
+
+/* Convert _from_ FK5 J2000.0 coordinates _to_ the alignment system. */
+/* ============================================================ */
+/* In this second phase, we convert to the system given by the align_sys
+   argument (if required), still using the properties of the target Frame. */
+   if ( astOK && match && step2 ) {
+
+/* Align in FK4. */
+/* --------------- */
+/* Convert directly from FK5 J2000.0 to FK4 B1950.0 coordinates at the
+   appropriate epoch. Then, if necessary, apply the old-style FK4
+   precession model to bring the equinox to that required, with
+   rigorous handling of the E-terms of aberration. */
+      if ( align_sys == AST__FK4 ) {
+         VerifyMSMAttrs( target, result, 1, "Equinox Epoch", "astMatch", status );
+         TRANSFORM_1( "FK54Z", epoch_B )
+         if ( equinox_B != 1950.0 ) {
+            TRANSFORM_1( "SUBET", 1950.0 )
+            TRANSFORM_2( "PREBN", 1950.0, equinox_B )
+            TRANSFORM_1( "ADDET", equinox_B )
+         }
+
+/* Align in FK4 with no E-terms. */
+/* ------------------------------- */
+/* This is the same as above, except that we do not need to add the
+   E-terms at the end. */
+      } else if ( align_sys == AST__FK4_NO_E ) {
+         VerifyMSMAttrs( target, result, 1, "Equinox Epoch", "astMatch", status );
+         TRANSFORM_1( "FK54Z", epoch_B )
+         TRANSFORM_1( "SUBET", 1950.0 )
+         if ( equinox_B != 1950.0 ) {
+            TRANSFORM_2( "PREBN", 1950.0, equinox_B )
+         }
+
+/* Align in FK5. */
+/* ------------- */
+/* We simply need to apply a precession correction for the change of
+   equinox.  Omit even this if the required equinox is J2000.0. */
+      } else if ( align_sys == AST__FK5 ) {
+         VerifyMSMAttrs( target, result, 1, "Equinox", "astMatch", status );
+         if ( equinox_J != 2000.0 ) {
+            TRANSFORM_2( "PREC", 2000.0, equinox_J )
+         }
+
+/* Align in J2000. */
+/* --------------- */
+/* Mov from FK5 to ICRS, and from ICRS to J2000. */
+      } else if ( align_sys == AST__J2000 ) {
+         VerifyMSMAttrs( target, result, 1, "Epoch", "astMatch", status );
+         TRANSFORM_1( "FK5HZ", epoch_J )
+         TRANSFORM_0( "HJ2000" )
+
+/* Align in geocentric apparent. */
+/* ------------------------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( align_sys == AST__GAPPT ) {
+         VerifyMSMAttrs( target, result, 1, "Epoch", "astMatch", status );
+         TRANSFORM_2( "MAP", 2000.0, epoch )
+
+/* Align in ecliptic coordinates. */
+/* -------------------------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( align_sys == AST__ECLIPTIC ) {
+         VerifyMSMAttrs( target, result, 1, "Equinox", "astMatch", status );
+         TRANSFORM_1( "EQECL", equinox )
+
+/* Align in helio-ecliptic coordinates. */
+/* ------------------------------------ */
+      } else if ( align_sys == AST__HELIOECLIPTIC ) {
+         VerifyMSMAttrs( target, result, 1, "Epoch", "astMatch", status );
+         TRANSFORM_1( "EQHE", epoch )
+
+/* Align in galactic coordinates. */
+/* -------------------------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( align_sys == AST__GALACTIC ) {
+         TRANSFORM_0( "EQGAL" )
+
+/* Align in ICRS. */
+/* -------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( align_sys == AST__ICRS ) {
+         VerifyMSMAttrs( target, result, 1, "Epoch", "astMatch", status );
+         TRANSFORM_1( "FK5HZ", epoch_J )
+
+/* Align in supergalactic coordinates. */
+/* ------------------------------------- */
+/* Convert to galactic coordinates and then to supergalactic. */
+      } else if ( align_sys == AST__SUPERGALACTIC ) {
+         TRANSFORM_0( "EQGAL" )
+         TRANSFORM_0( "GALSUP" )
+
+/* Align in AzEl coordinates. */
+/* -------------------------- */
+/* Go from FK5 J2000 to geocentric apparent (MAP), shift RA into hour angle
+   (R2H), rotate from equator to horizon (E2H). */
+      } else if ( align_sys == AST__AZEL ) {
+         VerifyMSMAttrs( target, result, 1, "ObsLon ObsLat Epoch", "astMatch", status );
+         TRANSFORM_2( "MAP", 2000.0, epoch )
+         TRANSFORM_1( "R2H", last )
+         TRANSFORM_2( "E2H", lat, diurab )
+
+/* Align in unknown coordinates. */
+/* ------------------------------- */
+/* No conversion is possible. */
+      } else if ( align_sys == AST__UNKNOWN ) {
+         match = 0;
+      }
+   }
+
+/* Convert _from_ the alignment system _to_ FK5 J2000.0 coordinates */
+/* =========================================================== */
+/* In this third phase, we convert from the alignment system (if required)
+   to the intermediate FK5 J2000 system, using the properties of the
+   result SkyFrame. */
+
+/* Obtain the sky coordinate system, equinox, epoch, etc, of the result
+   SkyFrame. */
+   system = result_system;
+   equinox = result_equinox;
+   epoch = result_epoch;
+   diurab = GetDiurab( result, status );
+   last = GetLAST( result, status );
+   lat = astGetObsLat( result );
+
+/* Convert the equinox and epoch values (stored as Modified Julian
+   Dates) into the equivalent Besselian and Julian epochs (as decimal
+   years). */
+   if( astOK ) {
+      equinox_B = palEpb( equinox );
+      equinox_J = palEpj( equinox );
+      epoch_B = palEpb( epoch );
+      epoch_J = palEpj( epoch );
+   }
+
+/* Check we need to do the conversion. */
+   if ( astOK && match && step3 ) {
+
+/* Formulate the conversion... */
+
+/* From FK4. */
+/* --------- */
+/* If necessary, apply the old-style FK4 precession model to bring the
+   equinox to B1950.0, with rigorous handling of the E-terms of
+   aberration. Then convert directly to FK5 J2000.0 coordinates. */
+      if ( align_sys == AST__FK4 ) {
+         VerifyMSMAttrs( target, result, 3, "Equinox Epoch", "astMatch", status );
+         if ( equinox_B != 1950.0 ) {
+            TRANSFORM_1( "SUBET", equinox_B )
+            TRANSFORM_2( "PREBN", equinox_B, 1950.0 )
+            TRANSFORM_1( "ADDET", 1950.0 )
+         }
+         TRANSFORM_1( "FK45Z", epoch_B )
+
+/* From FK4 with no E-terms. */
+/* ------------------------- */
+/* This is the same as above, except that we do not need to subtract
+   the E-terms initially as they are already absent. */
+      } else if ( align_sys == AST__FK4_NO_E ) {
+         VerifyMSMAttrs( target, result, 3, "Equinox Epoch", "astMatch", status );
+         if ( equinox_B != 1950.0 ) {
+            TRANSFORM_2( "PREBN", equinox_B, 1950.0 )
+         }
+         TRANSFORM_1( "ADDET", 1950.0 )
+         TRANSFORM_1( "FK45Z", epoch_B )
+
+/* From FK5. */
+/* --------- */
+/* We simply need to apply a precession correction for the change of
+   equinox.  Omit even this if the equinox is already J2000.0. */
+      } else if ( align_sys == AST__FK5 ) {
+         VerifyMSMAttrs( target, result, 3, "Equinox", "astMatch", status );
+         if ( equinox_J != 2000.0 ) {
+            TRANSFORM_2( "PREC", equinox_J, 2000.0 );
+         }
+
+/* From geocentric apparent. */
+/* ------------------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( align_sys == AST__GAPPT ) {
+         VerifyMSMAttrs( target, result, 3, "Epoch", "astMatch", status );
+         TRANSFORM_2( "AMP", epoch, 2000.0 )
+
+/* From ecliptic coordinates. */
+/* -------------------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( align_sys == AST__ECLIPTIC ) {
+         VerifyMSMAttrs( target, result, 3, "Equinox", "astMatch", status );
+         TRANSFORM_1( "ECLEQ", equinox )
+
+/* From helio-ecliptic coordinates. */
+/* -------------------------------- */
+      } else if ( align_sys == AST__HELIOECLIPTIC ) {
+         VerifyMSMAttrs( target, result, 3, "Epoch", "astMatch", status );
+         TRANSFORM_1( "HEEQ", epoch )
+
+/* From galactic coordinates. */
+/* -------------------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( align_sys == AST__GALACTIC ) {
+         TRANSFORM_0( "GALEQ" )
+
+/* From ICRS. */
+/* ---------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( align_sys == AST__ICRS ) {
+         VerifyMSMAttrs( target, result, 3, "Epoch", "astMatch", status );
+         TRANSFORM_1( "HFK5Z", epoch_J )
+
+/* From J2000. */
+/* ----------- */
+/* From J2000 to ICRS, and from ICRS to FK5. */
+      } else if ( align_sys == AST__J2000 ) {
+         VerifyMSMAttrs( target, result, 3, "Epoch", "astMatch", status );
+         TRANSFORM_0( "J2000H" )
+         TRANSFORM_1( "HFK5Z", epoch_J )
+
+/* From supergalactic coordinates. */
+/* ------------------------------- */
+/* Convert to galactic coordinates and then to FK5 J2000.0
+      equatorial. */
+      } else if ( align_sys == AST__SUPERGALACTIC ) {
+         TRANSFORM_0( "SUPGAL" )
+         TRANSFORM_0( "GALEQ" )
+
+/* From AzEl. */
+/* ---------- */
+/* Rotate from horizon to equator (H2E), shift hour angle into RA (H2R),
+      go from geocentric apparent to FK5 J2000. */
+      } else if ( align_sys == AST__AZEL ) {
+         VerifyMSMAttrs( target, result, 3, "ObsLon ObsLat Epoch", "astMatch", status );
+         TRANSFORM_2( "H2E", lat, diurab )
+         TRANSFORM_1( "H2R", last )
+         TRANSFORM_2( "AMP", epoch, 2000.0 )
+
+/* From unknown coordinates. */
+/* ------------------------------- */
+/* No conversion is possible. */
+      } else if ( align_sys == AST__UNKNOWN ) {
+         match = 0;
+      }
+   }
+
+/* Convert _from_ FK5 J2000.0 coordinates. */
+/* ======================================= */
+/* In this fourth and final phase, we convert to the result coordinate
+   system from the intermediate FK5 J2000 sky coordinates generated above. */
+   if ( astOK && match && step4 ) {
+
+/* To FK4. */
+/* ------- */
+/* Convert directly from FK5 J2000.0 to FK4 B1950.0 coordinates at the
+   appropriate epoch. Then, if necessary, apply the old-style FK4
+   precession model to bring the equinox to that required, with
+   rigorous handling of the E-terms of aberration. */
+      if ( system == AST__FK4 ) {
+         VerifyMSMAttrs( target, result, 3, "Equinox Epoch", "astMatch", status );
+         TRANSFORM_1( "FK54Z", epoch_B )
+         if ( equinox_B != 1950.0 ) {
+            TRANSFORM_1( "SUBET", 1950.0 )
+            TRANSFORM_2( "PREBN", 1950.0, equinox_B )
+            TRANSFORM_1( "ADDET", equinox_B )
+         }
+
+/* To FK4 with no E-terms. */
+/* ----------------------- */
+/* This is the same as above, except that we do not need to add the
+   E-terms at the end. */
+      } else if ( system == AST__FK4_NO_E ) {
+         VerifyMSMAttrs( target, result, 3, "Equinox Epoch", "astMatch", status );
+         TRANSFORM_1( "FK54Z", epoch_B )
+         TRANSFORM_1( "SUBET", 1950.0 )
+         if ( equinox_B != 1950.0 ) {
+            TRANSFORM_2( "PREBN", 1950.0, equinox_B )
+         }
+
+/* To FK5. */
+/* ------- */
+/* We simply need to apply a precession correction for the change of
+   equinox.  Omit even this if the required equinox is J2000.0. */
+      } else if ( system == AST__FK5 ) {
+         VerifyMSMAttrs( target, result, 3, "Equinox", "astMatch", status );
+         if ( equinox_J != 2000.0 ) {
+            TRANSFORM_2( "PREC", 2000.0, equinox_J )
+         }
+
+/* To geocentric apparent. */
+/* ----------------------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( system == AST__GAPPT ) {
+         VerifyMSMAttrs( target, result, 3, "Epoch", "astMatch", status );
+         TRANSFORM_2( "MAP", 2000.0, epoch )
+
+/* To ecliptic coordinates. */
+/* ------------------------ */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( system == AST__ECLIPTIC ) {
+         VerifyMSMAttrs( target, result, 3, "Equinox", "astMatch", status );
+         TRANSFORM_1( "EQECL", equinox )
+
+/* To helio-ecliptic coordinates. */
+/* ------------------------------ */
+      } else if ( system == AST__HELIOECLIPTIC ) {
+         VerifyMSMAttrs( target, result, 3, "Epoch", "astMatch", status );
+         TRANSFORM_1( "EQHE", epoch )
+
+/* To galactic coordinates. */
+/* ------------------------ */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( system == AST__GALACTIC ) {
+         TRANSFORM_0( "EQGAL" )
+
+/* To ICRS. */
+/* -------- */
+/* This conversion is supported directly by SLALIB. */
+      } else if ( system == AST__ICRS ) {
+         VerifyMSMAttrs( target, result, 3, "Epoch", "astMatch", status );
+         TRANSFORM_1( "FK5HZ", epoch_J )
+
+/* To J2000. */
+/* --------- */
+/* From FK5 to ICRS, then from ICRS to J2000. */
+      } else if ( system == AST__J2000 ) {
+         VerifyMSMAttrs( target, result, 3, "Epoch", "astMatch", status );
+         TRANSFORM_1( "FK5HZ", epoch_J )
+         TRANSFORM_0( "HJ2000" )
+
+/* To supergalactic coordinates. */
+/* ----------------------------- */
+/* Convert to galactic coordinates and then to supergalactic. */
+      } else if ( system == AST__SUPERGALACTIC ) {
+         TRANSFORM_0( "EQGAL" )
+         TRANSFORM_0( "GALSUP" )
+
+/* To AzEl */
+/* ------- */
+/* Go from FK5 J2000 to geocentric apparent (MAP), shift RA into hour angle
+   (R2H), rotate from equator to horizon (E2H). */
+      } else if ( system == AST__AZEL ) {
+         VerifyMSMAttrs( target, result, 3, "ObsLon ObsLat Epoch", "astMatch", status );
+         TRANSFORM_2( "MAP", 2000.0, epoch )
+         TRANSFORM_1( "R2H", last )
+         TRANSFORM_2( "E2H", lat, diurab )
+
+/* To unknown coordinates. */
+/* ----------------------------- */
+/* No conversion is possible. */
+      } else if ( system == AST__UNKNOWN ) {
+         match = 0;
+      }
+   }
+
+/* See of the slamap created above is effectively a unit mapping to
+   within the tolerance of the more accurate SkyFrame (target or result). */
+   isunit = TestSlaUnit( target, result, slamap, status );
+
+/* Now need to take account of the possibility that the input or output
+   SkyFrame may represent an offset system rather than a coordinate system.
+   Form the Mapping from the target coordinate system to the associated
+   offset system. A UnitMap is returned if the target does not use an
+   offset system. */
+   omap = SkyOffsetMap( target, status );
+
+/* Invert it to get the Mapping from the actual used system (whther
+   offsets or coordinates) to the coordinate system. */
+   astInvert( omap );
+
+/* Combine it with the slamap created earlier, so that its coordinate
+   outputs feed the inputs of the slamap. We only do this if the slamap
+   is not effectively a unit mapping. Annul redundant pointers afterwards. */
+   if( ! isunit ) {
+      tmap = (AstMapping *) astCmpMap( omap, slamap, 1, "", status );
+   } else {
+      tmap = astClone( omap );
+   }
+   omap = astAnnul( omap );
+   slamap =astAnnul( slamap );
+
+/* Now form the Mapping from the result coordinate system to the associated
+   offset system. A UnitMap is returned if the result does not use an
+   offset system. */
+   omap = SkyOffsetMap( result, status );
+
+/* Combine it with the above CmpMap, so that the CmpMap outputs feed the
+   new Mapping inputs. Annul redundant pointers afterwards. */
+   tmap2 = (AstMapping *) astCmpMap( tmap, omap, 1, "", status );
+   omap =astAnnul( omap );
+   tmap =astAnnul( tmap );
+
+/* Simplify the Mapping produced above (this eliminates any redundant
+   conversions) and annul the original pointer. */
+   *map = astSimplify( tmap2 );
+   tmap2 = astAnnul( tmap2 );
+
+/* If an error occurred, annul the returned Mapping and clear the
+   returned values. */
+   if ( !astOK ) {
+      *map = astAnnul( *map );
+      match = -1;
+   }
+
+/* Return the result. */
+   return match;
+
+/* Undefine macros local to this function. */
+#undef MAX_ARGS
+#undef TRANSFORM_0
+#undef TRANSFORM_1
+#undef TRANSFORM_2
+#undef TRANSFORM_3
+}
+
+static int Match( AstFrame *template_frame, AstFrame *target, int matchsub,
+                  int **template_axes, int **target_axes, AstMapping **map,
+                  AstFrame **result, int *status ) {
+/*
+*  Name:
+*     Match
+
+*  Purpose:
+*     Determine if conversion is possible between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int Match( AstFrame *template, AstFrame *target, int matchsub,
+*                int **template_axes, int **target_axes,
+*                AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astMatch method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function matches a "template" SkyFrame to a "target" Frame and
+*     determines whether it is possible to convert coordinates between them.
+*     If it is, a mapping that performs the transformation is returned along
+*     with a new Frame that describes the coordinate system that results when
+*     this mapping is applied to the "target" coordinate system. In addition,
+*     information is returned to allow the axes in this "result" Frame to be
+*     associated with the corresponding axes in the "target" and "template"
+*     Frames from which they are derived.
+
+*  Parameters:
+*     template
+*        Pointer to the template SkyFrame. This describes the coordinate system
+*        (or set of possible coordinate systems) into which we wish to convert
+*        our coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate system in
+*        which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case.
+*     template_axes
+*        Address of a location where a pointer to int will be returned if the
+*        requested coordinate conversion is possible. This pointer will point
+*        at a dynamically allocated array of integers with one element for each
+*        axis of the "result" Frame (see below). It must be freed by the caller
+*        (using astFree) when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element of this
+*        array will return the index of the template SkyFrame axis from which
+*        it is derived. If it is not derived from any template SkyFrame axis,
+*        a value of -1 will be returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned if the
+*        requested coordinate conversion is possible. This pointer will point
+*        at a dynamically allocated array of integers with one element for each
+*        axis of the "result" Frame (see below). It must be freed by the caller
+*        (using astFree) when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element of this
+*        array will return the index of the target Frame axis from which it
+*        is derived. If it is not derived from any target Frame axis, a value
+*        of -1 will be returned instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will be
+*        returned if the requested coordinate conversion is possible. If
+*        returned, the forward transformation of this Mapping may be used to
+*        convert coordinates between the "target" Frame and the "result"
+*        Frame (see below) and the inverse transformation will convert in the
+*        opposite direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be returned
+*        if the requested coordinate conversion is possible. If returned, this
+*        Frame describes the coordinate system that results from applying the
+*        returned Mapping (above) to the "target" coordinate system. In
+*        general, this Frame will combine attributes from (and will therefore
+*        be more specific than) both the target and the template Frames. In
+*        particular, when the template allows the possibility of transformaing
+*        to any one of a set of alternative coordinate systems, the "result"
+*        Frame will indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate conversion is
+*     possible. Otherwise zero is returned (this will not in itself result in
+*     an error condition).
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+
+*  Implementation Notes:
+*     This implementation addresses the matching of a SkyFrame class object to
+*     any other class of Frame. A SkyFrame will match any class of SkyFrame
+*     (i.e. possibly from a derived class) but will not match a less
+*     specialised class of Frame.
+*/
+
+/* Local Variables: */
+   AstFrame *frame0;          /* Pointer to Frame underlying axis 0 */
+   AstFrame *frame1;          /* Pointer to Frame underlying axis 1 */
+   AstSkyFrame *template;     /* Pointer to template SkyFrame structure */
+   int iaxis;                 /* Axis index */
+   int iaxis0;                /* Axis index underlying axis 0 */
+   int iaxis1;                /* Axis index underlying axis 1 */
+   int match;                 /* Coordinate conversion possible? */
+   int swap1;                 /* Template axes swapped? */
+   int swap2;                 /* Target axes swapped? */
+   int swap;                  /* Additional axis swap needed? */
+   int target_axis0;          /* Index of 1st SkyFrame axis in the target */
+   int target_axis1;          /* Index of 2nd SkyFrame axis in the target */
+   int target_naxes;          /* Number of target axes */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   swap = 0;
+   target_axis0 = -1;
+   target_axis1 = -1;
+
+/* Obtain a pointer to the template SkyFrame structure. */
+   template = (AstSkyFrame *) template_frame;
+
+/* Obtain the number of axes in the target Frame. */
+   target_naxes = astGetNaxes( target );
+
+/* The first criterion for a match is that the template matches as a
+   Frame class object. This ensures that the number of axes (2) and
+   domain, etc. of the target Frame are suitable. Invoke the parent
+   "astMatch" method to verify this. */
+   match = (*parent_match)( template_frame, target, matchsub,
+                            template_axes, target_axes, map, result, status );
+
+/* If a match was found, annul the returned objects, which are not
+   needed, but keep the memory allocated for the axis association
+   arrays, which we will re-use. */
+   if ( astOK && match ) {
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+   }
+
+/* If OK so far, obtain pointers to the primary Frames which underlie
+   all target axes. Stop when a SkyFrame axis is found. */
+   if ( match && astOK ) {
+
+      match = 0;
+      for( iaxis = 0; iaxis < target_naxes; iaxis++ ) {
+         astPrimaryFrame( target, iaxis, &frame0, &iaxis0 );
+         if( astIsASkyFrame( frame0 ) ) {
+            target_axis0 = iaxis;
+            match = 1;
+            break;
+         } else {
+            frame0 = astAnnul( frame0 );
+         }
+      }
+
+/* Check at least one SkyFrame axis was found it the target. */
+      if( match ) {
+
+/* If so, search the remaining target axes for another axis that is
+   derived from the same SkyFrame. */
+         match = 0;
+         for( iaxis++ ; iaxis < target_naxes; iaxis++ ) {
+            astPrimaryFrame( target, iaxis, &frame1, &iaxis1 );
+            if( frame1 == frame0 ) {
+               target_axis1 = iaxis;
+               frame1 = astAnnul( frame1 );
+               match = 1;
+               break;
+            } else {
+               frame1 = astAnnul( frame1 );
+            }
+         }
+
+/* Annul the remaining Frame pointer used in the above tests. */
+         frame0 = astAnnul( frame0 );
+      }
+
+/* If this test is passed, we can now test that the underlying axis indices
+   are 0 and 1, in either order. This then ensures that we have a
+   single SkyFrame (not a compound Frame) with both axes present. */
+      if ( match && astOK ) {
+         match = ( ( ( iaxis0 == 0 ) && ( iaxis1 == 1 ) ) ||
+                   ( ( iaxis1 == 0 ) && ( iaxis0 == 1 ) ) );
+      }
+
+   }
+
+/* If a possible match has been detected, we must now decide how the
+   order of the axes in the result Frame relates to the order of axes
+   in the target Frame. There are two factors involved. The first
+   depends on whether the axis permutation array for the template
+   SkyFrame (whose method we are executing) causes an axis
+   reversal. Determine this by permuting axis index zero. */
+   if ( astOK && match ) {
+      swap1 = ( astValidateAxis( template, 0, 1, "astMatch" ) != 0 );
+
+/* The second factor depends on whether the axes of the underlying
+   primary SkyFrame are reversed when seen in the target Frame. */
+      swap2 = ( iaxis0 != 0 );
+
+/* Combine these to determine if an additional axis swap will be
+   needed. */
+      swap = ( swap1 != swap2 );
+
+/* Now check to see if this additional swap is permitted by the
+   template's Permute attribute. */
+      match = ( !swap || astGetPermute( template ) );
+   }
+
+/* If the Frames still match, we next set up the axis association
+   arrays. */
+   if ( astOK && match ) {
+
+/* If the target axis order is to be preserved, then the target axis
+   association involves no permutation but the template axis
+   association may involve an axis swap. */
+      if ( astGetPreserveAxes( template ) ) {
+         (*template_axes)[ 0 ] = swap;
+         (*template_axes)[ 1 ] = !swap;
+         (*target_axes)[ 0 ] = target_axis0;
+         (*target_axes)[ 1 ] = target_axis1;
+
+/* Otherwise, any swap applies to the target axis association
+   instead. */
+      } else {
+         (*template_axes)[ 0 ] = 0;
+         (*template_axes)[ 1 ] = 1;
+         (*target_axes)[ 0 ] = swap ? target_axis1 : target_axis0;
+         (*target_axes)[ 1 ] = swap ? target_axis0 : target_axis1;
+      }
+
+/* Use the target's "astSubFrame" method to create a new Frame (the
+   result Frame) with copies of the target axes in the required
+   order. This process also overlays the template attributes on to the
+   target Frame and returns a Mapping between the target and result
+   Frames which effects the required coordinate conversion. */
+      match = astSubFrame( target, template, 2, *target_axes, *template_axes,
+                           map, result );
+   }
+
+/* If an error occurred, or conversion to the result Frame's
+   coordinate system was not possible, then free all memory, annul the
+   returned objects, and reset the returned value. */
+   if ( !astOK || !match ) {
+      *template_axes = astFree( *template_axes );
+      *target_axes = astFree( *target_axes );
+      if( *map ) *map = astAnnul( *map );
+      if( *result ) *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static void MatchAxesX( AstFrame *frm2_frame, AstFrame *frm1, int *axes,
+                        int *status ) {
+/*
+*  Name:
+*     MatchAxesX
+
+*  Purpose:
+*     Find any corresponding axes in two Frames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void MatchAxesX( AstFrame *frm2, AstFrame *frm1, int *axes )
+*                      int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astMatchAxesX
+*     method inherited from the Frame class).
+
+*     This function looks for corresponding axes within two supplied
+*     Frames. An array of integers is returned that contains an element
+*     for each axis in the second supplied Frame. An element in this array
+*     will be set to zero if the associated axis within the second Frame
+*     has no corresponding axis within the first Frame. Otherwise, it
+*     will be set to the index (a non-zero positive integer) of the
+*     corresponding axis within the first supplied Frame.
+
+*  Parameters:
+*     frm2
+*        Pointer to the second Frame.
+*     frm1
+*        Pointer to the first Frame.
+*     axes
+*        Pointer to an integer array in which to return the indices of
+*        the axes (within the first Frame) that correspond to each axis
+*        within the second Frame. Axis indices start at 1. A value of zero
+*        will be stored in the returned array for each axis in the second
+*        Frame that has no corresponding axis in the first Frame.
+*
+*        The number of elements in this array must be greater than or
+*        equal to the number of axes in the second Frame.
+*     status
+*        Pointer to inherited status value.
+
+*  Notes:
+*     -  Corresponding axes are identified by the fact that a Mapping
+*     can be found between them using astFindFrame or astConvert. Thus,
+*     "corresponding axes" are not necessarily identical. For instance,
+*     SkyFrame axes in two Frames will match even if they describe
+*     different celestial coordinate systems
+*/
+
+/* Local Variables: */
+   AstFrame *resfrm;
+   AstMapping *resmap;
+   AstSkyFrame *frm2;
+   int *frm2_axes;
+   int *frm1_axes;
+   int max_axes;
+   int min_axes;
+   int preserve_axes;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the SkyFrame. */
+   frm2 = (AstSkyFrame *) frm2_frame;
+
+/* Temporarily ensure that the PreserveAxes attribute is non-zero in
+   the first supplied Frame. This means thte result Frame returned by
+   astMatch below will have the axis count and order of the target Frame
+   (i.e. "pfrm"). */
+   if( astTestPreserveAxes( frm1 ) ) {
+      preserve_axes = astGetPreserveAxes( frm1 ) ? 1 : 0;
+   } else {
+      preserve_axes = -1;
+   }
+   astSetPreserveAxes( frm1, 1 );
+
+/* Temporarily ensure that the MaxAxes and MinAxes attributes in the
+   first supplied Frame are set so the Frame can be used as a template
+   in astMatch for matching any number of axes. */
+   if( astTestMaxAxes( frm1 ) ) {
+      max_axes = astGetMaxAxes( frm1 );
+   } else {
+      max_axes = -1;
+   }
+   astSetMaxAxes( frm1, 10000 );
+
+   if( astTestMinAxes( frm1 ) ) {
+      min_axes = astGetMinAxes( frm1 );
+   } else {
+      min_axes = -1;
+   }
+   astSetMinAxes( frm1, 1 );
+
+/* Attempt to find a sub-frame within the first supplied Frame that
+   corresponds to the supplied SkyFrame. */
+   if( astMatch( frm1, frm2, 1, &frm1_axes, &frm2_axes, &resmap, &resfrm ) ) {
+
+/* If successfull, Store the one-based index within "frm1" of the
+   corresponding axes. */
+      axes[ 0 ] = frm1_axes[ 0 ] + 1;
+      axes[ 1 ] = frm1_axes[ 1 ] + 1;
+
+/* Free resources */
+      frm1_axes = astFree( frm1_axes );
+      frm2_axes = astFree( frm2_axes );
+      resmap = astAnnul( resmap );
+      resfrm = astAnnul( resfrm );
+
+/* If no corresponding SkyFrame was found store zeros in the returned array. */
+   } else {
+      axes[ 0 ] = 0;
+      axes[ 1 ] = 0;
+   }
+
+/* Re-instate the original attribute values in the first supplied Frame. */
+   if( preserve_axes == -1 ) {
+      astClearPreserveAxes( frm1 );
+   } else {
+      astSetPreserveAxes( frm1, preserve_axes );
+   }
+
+   if( max_axes == -1 ) {
+      astClearMaxAxes( frm1 );
+   } else {
+      astSetMaxAxes( frm1, max_axes );
+   }
+
+   if( min_axes == -1 ) {
+      astClearMinAxes( frm1 );
+   } else {
+      astSetMinAxes( frm1, min_axes );
+   }
+}
+
+static void Norm( AstFrame *this_frame, double value[], int *status ) {
+/*
+*  Name:
+*     Norm
+
+*  Purpose:
+*     Normalise a set of SkyFrame coordinates.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void Norm( AstAxis *this, double value[], int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astNorm method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function converts a set of SkyFrame coordinate values,
+*     which might potentially be unsuitable for display to a user (for
+*     instance, may lie outside the expected range of values) into a
+*     set of acceptable alternative values suitable for display.
+*
+*     This is done by wrapping coordinates so that the latitude lies
+*     in the range (-pi/2.0) <= latitude <= (pi/2.0). If the NegLon
+*     attribute is zero (the default), then the wrapped longitude value
+*     lies in the range 0.0 <= longitude < (2.0*pi). Otherwise, it lies
+*     in the range -pi <= longitude < pi.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     value
+*        An array of double, with one element for each SkyFrame axis.
+*        This should contain the initial set of coordinate values,
+*        which will be modified in place.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   const int *perm;              /* Axis permutation array */
+   double sky_lat;               /* Sky latitude value */
+   double sky_long;              /* Sky longitude value */
+   double v[ 2 ];                /* Permuted value coordinates */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+   perm = astGetPerm( this );
+   if ( astOK ) {
+
+/* Obtain the sky longitude and latitude values, allowing for any axis
+   permutation. */
+      v[ perm[ 0 ] ] = value[ 0 ];
+      v[ perm[ 1 ] ] = value[ 1 ];
+      sky_long = v[ 0 ];
+      sky_lat = v[ 1 ];
+
+/* Test if both values are OK (i.e. not "bad"). */
+      if ( ( sky_long != AST__BAD ) && ( sky_lat != AST__BAD ) ) {
+
+/* Fold the longitude value into the range 0 to 2*pi and the latitude into
+   the range -pi to +pi. */
+         sky_long = palDranrm( sky_long );
+         sky_lat = palDrange( sky_lat );
+
+/* If the latitude now exceeds pi/2, shift the longitude by pi in whichever
+   direction will keep it in the range 0 to 2*pi. */
+         if ( sky_lat > ( pi / 2.0 ) ) {
+            sky_long += ( sky_long < pi ) ? pi : -pi;
+
+/* Reflect the latitude value through the pole, so it lies in the range 0 to
+   pi/2. */
+            sky_lat = pi - sky_lat;
+
+/* If the latitude is less than -pi/2, shift the longitude in the same way
+   as above. */
+         } else if ( sky_lat < -( pi / 2.0 ) ) {
+            sky_long += ( sky_long < pi ) ? pi : -pi;
+
+/* But reflect the latitude through the other pole, so it lies in the range
+   -pi/2 to 0. */
+            sky_lat = -pi - sky_lat;
+         }
+
+/* If only the longitude value is valid, wrap it into the range 0 to 2*pi. */
+      } else if ( sky_long != AST__BAD ) {
+         sky_long = palDranrm( sky_long );
+
+/* If only the latitude value is valid, wrap it into the range -pi to +pi. */
+      } else if ( sky_lat != AST__BAD ) {
+         sky_lat = palDrange( sky_lat );
+
+/* Then refect through one of the poles (as above), if necessary, to move it
+   into the range -pi/2 to +pi/2. */
+         if ( sky_lat > ( pi / 2.0 ) ) {
+            sky_lat = pi - sky_lat;
+         } else if ( sky_lat < -( pi / 2.0 ) ) {
+            sky_lat = -pi - sky_lat;
+         }
+      }
+
+/* Convert 2*pi longitude into zero. Allow for a small error. */
+      if ( fabs( sky_long - ( 2.0 * pi ) ) <=
+          ( 2.0 * pi ) * ( DBL_EPSILON * (double) FLT_RADIX ) ) sky_long = 0.0;
+
+/* If the NegLon attribute is set, and the longitude value is good,
+   convert it into the range -pi to +pi. */
+      if( sky_long != AST__BAD && astGetNegLon( this ) ) {
+         sky_long = palDrange( sky_long );
+      }
+
+/* Return the new values, allowing for any axis permutation. */
+      v[ 0 ] = sky_long;
+      v[ 1 ] = sky_lat;
+      value[ 0 ] = v[ perm[ 0 ] ];
+      value[ 1 ] = v[ perm[ 1 ] ];
+   }
+}
+
+static void NormBox( AstFrame *this_frame, double lbnd[], double ubnd[],
+                     AstMapping *reg, int *status ) {
+/*
+*  Name:
+*     NormBox
+
+*  Purpose:
+*     Extend a box to include effect of any singularities in the Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void astNormBox( AstFrame *this, double lbnd[], double ubnd[],
+*                      AstMapping *reg, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astNormBox method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function modifies a supplied box to include the effect of any
+*     singularities in the co-ordinate system represented by the Frame.
+*     For a normal Cartesian coordinate system, the box will be returned
+*     unchanged. Other classes of Frame may do other things. For instance,
+*     a SkyFrame will check to see if the box contains either the north
+*     or south pole and extend the box appropriately.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     lbnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        lower axis bounds for the box. They will be modified on exit
+*        to include the effect of any singularities within the box.
+*     ubnd
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). Initially, this should contain a set of
+*        upper axis bounds for the box. They will be modified on exit
+*        to include the effect of any singularities within the box.
+*     reg
+*        A Mapping which should be used to test if any singular points are
+*        inside or outside the box. The Mapping should leave an input
+*        position unchanged if the point is inside the box, and should
+*        set all bad if the point is outside the box.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   const int *perm;              /* Axis permutation array */
+   double lb[ 2 ];               /* Permuted lower bounds */
+   double t;                     /* Temporary storage */
+   double t2;                    /* Temporary storage */
+   double ub[ 2 ];               /* Permuted upper bounds */
+   double x[2];                  /* 1st axis values at poles */
+   double xo[2];                 /* Tested 1st axis values at poles */
+   double y[2];                  /* 2nd axis values at poles */
+   double yo[2];                 /* Tested 2nd axis values at poles */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+   perm = astGetPerm( this );
+   if( perm ) {
+
+/* Obtain the sky longitude and latitude limits, allowing for any axis
+   permutation. */
+      lb[ perm[ 0 ] ] = lbnd[ 0 ];
+      lb[ perm[ 1 ] ] = lbnd[ 1 ];
+      ub[ perm[ 0 ] ] = ubnd[ 0 ];
+      ub[ perm[ 1 ] ] = ubnd[ 1 ];
+
+/* Use the supplied Mapping to test if box includes either pole. */
+      if( perm[ 0 ] == 0 ) {
+         x[ 0 ] = 0.0;
+         y[ 0 ] = AST__DPIBY2;
+         x[ 1 ] = 0.0;
+         y[ 1 ] = -AST__DPIBY2;
+      } else {
+         x[ 0 ] = AST__DPIBY2;
+         y[ 0 ] = 0.0;
+         x[ 1 ] = -AST__DPIBY2;
+         y[ 1 ] = 0.0;
+      }
+      astTran2( reg, 2, x, y, 1, xo, yo );
+
+/* If the box includes the north pole... */
+      if( xo[ 0 ] != AST__BAD ) {
+
+/* Find the lowest latitude after normalisation. */
+         if( ub[ 1 ] != AST__BAD &&  lb[ 1 ] != AST__BAD ){
+            t = palDrange( ub[ 1 ] );
+            t2 = palDrange( lb[ 1 ] );
+            if( t2 < t ) t = t2;
+         } else {
+            t = AST__BAD;
+         }
+
+/* Set the lower returned limit to this value and the upper returned limit
+   to +90 degs */
+         lb[ 1 ] = t;
+         ub[ 1 ] = AST__DPIBY2;
+
+/* Set the longitude range to 0 to 2PI */
+         lb[ 0 ] = 0;
+         ub[ 0 ] = 2*AST__DPI;
+
+      }
+
+/* If the box includes the south pole... */
+      if( xo[ 1 ] != AST__BAD ) {
+
+/* Find the highest latitude after normalisation. */
+         if( ub[ 1 ] != AST__BAD &&  lb[ 1 ] != AST__BAD ){
+            t = palDrange( ub[ 1 ] );
+            t2 = palDrange( lb[ 1 ] );
+            if( t2 > t ) t = t2;
+         } else {
+            t = AST__BAD;
+         }
+
+/* Set the upper returned limit to this value and the lower returned limit
+   to -90 degs */
+         lb[ 1 ] = -AST__DPIBY2;
+         ub[ 1 ] = t;
+
+/* Set the longitude range to 0 to 2PI */
+         lb[ 0 ] = 0;
+         ub[ 0 ] = 2*AST__DPI;
+      }
+
+/* Return the modified limits. */
+      lbnd[ 0 ] = lb[ perm[ 0 ] ];
+      lbnd[ 1 ] = lb[ perm[ 1 ] ];
+      ubnd[ 0 ] = ub[ perm[ 0 ] ];
+      ubnd[ 1 ] = ub[ perm[ 1 ] ];
+   }
+}
+
+static void Offset( AstFrame *this_frame, const double point1[],
+                    const double point2[], double offset, double point3[], int *status ) {
+/*
+*  Name:
+*     Offset
+
+*  Purpose:
+*     Calculate an offset along a geodesic curve.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void Offset( AstFrame *this,
+*                  const double point1[], const double point2[],
+*                  double offset, double point3[], int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astOffset method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function finds the SkyFrame coordinate values of a point
+*     which is offset a specified distance along the geodesic curve
+*     (i.e. great circle) between two other points.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     point1
+*        An array of double, with one element for each SkyFrame axis.
+*        This should contain the coordinates of the point marking the
+*        start of the geodesic curve.
+*     point2
+*        An array of double, with one element for each SkyFrame axis.
+*        This should contain the coordinates of the point marking the
+*        end of the geodesic curve.
+*     offset
+*        The required offset from the first point along the geodesic
+*        curve, in radians. If this is positive, it will be towards
+*        the second point. If it is negative, it will be in the
+*        opposite direction. This offset need not imply a position
+*        lying between the two points given, as the curve will be
+*        extrapolated if necessary.
+*     point3
+*        An array of double, with one element for each SkyFrame axis
+*        in which the coordinates of the required point will be
+*        returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*     - "Bad" coordinate values will also be returned if the two
+*     points supplied are coincident (or otherwise fail to uniquely
+*     specify a geodesic curve) but the requested offset is non-zero.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   const int *perm;              /* Pointer to axis permutation array */
+   double mrot[ 3 ][ 3 ];        /* Rotation matrix */
+   double p1[ 2 ];               /* Permuted coordinates for point1 */
+   double p2[ 2 ];               /* Permuted coordinates for point2 */
+   double p3[ 2 ];               /* Permuted coordinates for point3 */
+   double scale;                 /* Scale factor */
+   double v1[ 3 ];               /* 3-vector for p1 */
+   double v2[ 3 ];               /* 3-vector for p2 */
+   double v3[ 3 ];               /* 3-vector for p3 */
+   double vmod;                  /* Modulus of vector */
+   double vrot[ 3 ];             /* Vector along rotation axis */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+   perm = astGetPerm( this );
+   if ( astOK ) {
+
+/* Check that all supplied coordinates are OK. If not, generate "bad"
+   output coordinates. */
+      if ( ( point1[ 0 ] == AST__BAD ) || ( point1[ 1 ] == AST__BAD ) ||
+           ( point2[ 0 ] == AST__BAD ) || ( point2[ 1 ] == AST__BAD ) ) {
+         point3[ 0 ] = AST__BAD;
+         point3[ 1 ] = AST__BAD;
+
+/* Otherwise, apply the axis permutation array to obtain the
+   coordinates of the two input points in the required
+   (longitude,latitude) order. */
+      } else {
+         p1[ perm[ 0 ] ] = point1[ 0 ];
+         p1[ perm[ 1 ] ] = point1[ 1 ];
+         p2[ perm[ 0 ] ] = point2[ 0 ];
+         p2[ perm[ 1 ] ] = point2[ 1 ];
+
+/* Convert each point into a 3-vector of unit length. */
+         palDcs2c( p1[ 0 ], p1[ 1 ], v1 );
+         palDcs2c( p2[ 0 ], p2[ 1 ], v2 );
+
+/* Find the cross product between these two vectors (the vector order
+   is reversed here to compensate for the sense of rotation introduced
+   by palDav2m and palDmxv below). */
+         palDvxv( v2, v1, v3 );
+
+/* Normalise the cross product vector, also obtaining its original
+   modulus. */
+         palDvn( v3, vrot, &vmod );
+
+/* If the original modulus was zero, the input points are either
+   coincident or diametrically opposite, so do not uniquely define a
+   great circle. In either case, we can only generate output
+   coordinates if the offset required is an exact multiple of pi. If
+   it is, generate the 3-vector that results from rotating the first
+   input point through this angle. */
+         if ( vmod == 0.0 ) {
+            if ( sin( offset ) == 0.0 ) {
+               scale = cos( offset );
+               v3[ 0 ] = v1[ 0 ] * scale;
+               v3[ 1 ] = v1[ 1 ] * scale;
+               v3[ 2 ] = v1[ 2 ] * scale;
+
+/* Convert the 3-vector back into spherical cooordinates and then
+   constrain the longitude result to lie in the range 0 to 2*pi
+   (palDcc2s doesn't do this itself). */
+               palDcc2s( v3, &p3[ 0 ], &p3[ 1 ] );
+               p3[ 0 ] = palDranrm( p3[ 0 ] );
+
+/* If the offset was not a multiple of pi, generate "bad" output
+   coordinates. */
+            } else {
+               p3[ 0 ] = AST__BAD;
+               p3[ 1 ] = AST__BAD;
+            }
+
+/* If the two input points define a great circle, scale the normalised
+   cross product vector to make its length equal to the required
+   offset (angle) between the first input point and the result. */
+         } else {
+            vrot[ 0 ] *= offset;
+            vrot[ 1 ] *= offset;
+            vrot[ 2 ] *= offset;
+
+/* Generate the rotation matrix that implements this rotation and use
+   it to rotate the first input point (3-vector) to give the required
+   result (3-vector). */
+            palDav2m( vrot, mrot );
+            palDmxv( mrot, v1, v3 );
+
+/* Convert the 3-vector back into spherical cooordinates and then
+   constrain the longitude result to lie in the range 0 to 2*pi. */
+            palDcc2s( v3, &p3[ 0 ], &p3[ 1 ] );
+            p3[ 0 ] = palDranrm( p3[ 0 ] );
+         }
+
+/* Permute the result coordinates to undo the effect of the SkyFrame
+   axis permutation array. */
+         point3[ 0 ] = p3[ perm[ 0 ] ];
+         point3[ 1 ] = p3[ perm[ 1 ] ];
+      }
+   }
+}
+
+static AstMapping *SkyOffsetMap( AstSkyFrame *this, int *status ){
+/*
+*++
+*  Name:
+c     astSkyOffsetMap
+f     AST_SKYOFFSETMAP
+
+*  Purpose:
+*     Returns a Mapping which goes from absolute coordinates to offset
+*     coordinates.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "skyframe.h"
+c     AstMapping *astSkyOffsetMap( AstSkyFrame *this )
+f     RESULT = AST_SKYOFFSETMAP( THIS, STATUS )
+
+*  Class Membership:
+*     SkyFrame method.
+
+*  Description:
+*     This function returns a Mapping in which the forward transformation
+*     transforms a position in the coordinate system given by the System
+*     attribute of the supplied SkyFrame, into the offset coordinate system
+*     specified by the SkyRef, SkyRefP and SkyRefIs attributes of the
+*     supplied SkyFrame.
+*
+*     A UnitMap is returned if the SkyFrame does not define an offset
+*     coordinate system.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the SkyFrame.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSkyOffsetMap()
+f     AST_SKYOFFSETMAP = INTEGER
+*        Pointer to the returned Mapping.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstCmpMap *map3;            /* Partial Mapping. */
+   AstMapping *result;         /* The returned Mapping. */
+   AstMatrixMap *map1;         /* Spherical rotation in 3D cartesian space */
+   AstSphMap *map2;            /* 3D Cartesian to 2D spherical Mapping */
+   double *vx;                 /* Pointer to x unit vector. */
+   double *vy;                 /* Pointer to y unit vector. */
+   double *vz;                 /* Pointer to z unit vector. */
+   double mat[ 9 ];            /* Spherical rotation matrix */
+   double vmod;                /* Length of vector (+ve) */
+   double vp[ 3 ];             /* Unit vector representin SkyRefP position. */
+   int lataxis;                /* Index of the latitude axis */
+   int lonaxis;                /* Index of the longitude axis */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Return a UnitMap if the offset coordinate system is not defined. */
+   if( astGetSkyRefIs( this ) == AST__IGNORED_REF ||
+       ( !astTestSkyRef( this, 0 ) && !astTestSkyRef( this, 1 ) ) ) {
+      result = (AstMapping *) astUnitMap( 2, "", status );
+
+/* Otherwise... */
+   } else {
+
+/* Get the longitude and latitude at the reference point and at a point
+   on the primary meridian. */
+      lataxis = astGetLatAxis( this );
+      lonaxis = 1 - lataxis;
+
+/* Initialise pointers to the rows of the 3x3 matrix. Each row will be
+   used to store a unit vector. */
+      vx = mat;
+      vy = mat + 3;
+      vz = mat + 6;
+
+/* The following trig converts between (longitude,latitude) and (x,y,z)
+   on a unit sphere, in which (0,0) is at (1,0,0), (0,pi/2) is (0,0,1)
+   and (pi/2,0) is at (0,1,0). */
+
+/* First deal with cases where the SkyRef attribute holds the standard
+   coords at the origin of the offset coordinate system. */
+      if( astGetSkyRefIs( this ) == AST__ORIGIN_REF ) {
+
+/* Convert each point into a 3-vector of unit length. The SkyRef position
+   defines the X axis in the offset coord system. */
+         palDcs2c( astGetSkyRef( this, lonaxis ), astGetSkyRef( this, lataxis ), vx );
+         palDcs2c( astGetSkyRefP( this, lonaxis ), astGetSkyRefP( this, lataxis ), vp );
+
+/* The Y axis is perpendicular to both the X axis and the skyrefp
+   position. That is, it is parallel to the cross product of the 2 above
+   vectors.*/
+         palDvxv( vp, vx, vy );
+
+/* Normalize the y vector. */
+         palDvn( vy, vy, &vmod );
+
+/* Report an error if the modulus of the vector is zero.*/
+         if( vmod == 0.0 ) {
+            astError( AST__BADOC, "astConvert(%s): The position specified by the SkyRefP "
+                      "attribute is either coincident, with or opposite to, the "
+                      "position specified by the SkyRef attribute.", status, astGetClass( this ) );
+
+/* If OK, form the Z axis as the cross product of the x and y axes. */
+         } else {
+            palDvxv( vx, vy, vz );
+
+         }
+
+/* Now deal with cases where the SkyRef attribute holds the standard
+   coords at the north pole of the offset coordinate system. */
+      } else {
+
+/* Convert each point into a 3-vector of unit length. The SkyRef position
+   defines the Z axis in the offset coord system. */
+         palDcs2c( astGetSkyRef( this, lonaxis ), astGetSkyRef( this, lataxis ), vz );
+         palDcs2c( astGetSkyRefP( this, lonaxis ), astGetSkyRefP( this, lataxis ), vp );
+
+/* The Y axis is perpendicular to both the Z axis and the skyrefp
+   position. That is, it is parallel to the cross product of the 2 above
+   vectors.*/
+         palDvxv( vz, vp, vy );
+
+/* Normalize the y vector. */
+         palDvn( vy, vy, &vmod );
+
+/* Report an error if the modulus of the vector is zero.*/
+         if( vmod == 0.0 ) {
+            astError( AST__BADOC, "astConvert(%s): The position specified by the SkyRefP "
+                      "attribute is either coincident, with or opposite to, the "
+                      "position specified by the SkyRef attribute.", status, astGetClass( this ) );
+
+/* If OK, form the X axis as the cross product of the y and z axes. */
+         } else {
+            palDvxv( vy, vz, vx );
+         }
+      }
+
+/* Create a MatrixMap which implements the above spherical rotation. Each
+   row in this matrix represents one of the unit axis vectors found above. */
+      map1 = astMatrixMap( 3, 3, 0, mat, "", status );
+
+/* Create a 3D cartesian to 2D spherical Mapping. */
+      map2 = astSphMap( "UnitRadius=1", status );
+
+/* Form a series CmpMap which converts from 2D (long,lat) in the base
+   System to 2D (long,lat) in the offset coordinate system. */
+      map3 = astCmpMap( map1, map2, 1, "", status );
+      astInvert( map2 );
+      result = (AstMapping *) astCmpMap( map2, map3, 1, "", status );
+
+/* Free resources. */
+      map1 = astAnnul( map1 );
+      map2 = astAnnul( map2 );
+      map3 = astAnnul( map3 );
+   }
+
+/* Annul the returned Mapping if anything has gone wrong. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+
+}
+
+static double Offset2( AstFrame *this_frame, const double point1[2],
+                       double angle, double offset, double point2[2], int *status ) {
+/*
+*  Name:
+*     Offset2
+
+*  Purpose:
+*     Calculate an offset along a geodesic curve at a given bearing.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     double Offset2( AstFrame *this_frame, const double point1[2],
+*                     double angle, double offset, double point2[2], int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astOffset2 method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function finds the SkyFrame coordinate values of a point
+*     which is offset a specified distance along the geodesic curve
+*     (i.e. great circle) at a given angle from a given starting point.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     point1
+*        An array of double, with one element for each SkyFrame axis.
+*        This should contain the coordinates of the point marking the
+*        start of the geodesic curve.
+*     angle
+*        The angle (in radians) from the positive direction of the second
+*        axis, to the direction of the required position, as seen from
+*        the starting position. Positive rotation is in the sense of
+*        rotation from the positive direction of axis 2 to the positive
+*        direction of axis 1.
+*     offset
+*        The required offset from the first point along the geodesic
+*        curve, in radians. If this is positive, it will be towards
+*        the given angle. If it is negative, it will be in the
+*        opposite direction.
+*     point2
+*        An array of double, with one element for each SkyFrame axis
+*        in which the coordinates of the required point will be
+*        returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The direction of the geodesic curve at the end point. That is, the
+*     angle (in radians) between the positive direction of the second
+*     axis and the continuation of the geodesic curve at the requested
+*     end point. Positive rotation is in the sense of rotation from
+*     the positive direction of axis 2 to the positive direction of axis
+*     1.
+
+*  Notes:
+*     - The geodesic curve used by this function is the path of
+*     shortest distance between two points, as defined by the
+*     astDistance function.
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;          /* Pointer to the SkyFrame structure */
+   const int *perm;            /* Pointer to axis permutation array */
+   double p1[ 2 ];             /* Permuted coordinates for point1 */
+   double p2[ 2 ];             /* Permuted coordinates for point2 */
+   double result;              /* The returned answer */
+   double cosoff;              /* Cosine of offset */
+   double cosa1;               /* Cosine of longitude at start */
+   double cosb1;               /* Cosine of latitude at start */
+   double pa;                  /* A position angle measured from north */
+   double q1[ 3 ];             /* Vector PI/2 away from R4 in meridian of R4 */
+   double q2[ 3 ];             /* Vector PI/2 away from R4 on equator */
+   double q3[ 3 ];             /* Vector PI/2 away from R4 on great circle */
+   double r0[ 3 ];             /* Reference position vector */
+   double r3[ 3 ];             /* Vector PI/2 away from R0 on great circle */
+   double sinoff;              /* Sine of offset */
+   double sina1;               /* Sine of longitude at start */
+   double sinb1;               /* Sine of latitude at start */
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+   perm = astGetPerm( this );
+   if ( astOK ) {
+
+/* Check that all supplied values are OK. If not, generate "bad"
+   output coordinates. */
+      if ( ( point1[ 0 ] == AST__BAD ) || ( point1[ 1 ] == AST__BAD ) ||
+           ( angle == AST__BAD ) || ( offset == AST__BAD ) ) {
+         point2[ 0 ] = AST__BAD;
+         point2[ 1 ] = AST__BAD;
+
+/* Otherwise, apply the axis permutation array to obtain the
+   coordinates of the starting point in the required (longitude,latitude)
+   order. */
+      } else {
+         p1[ perm[ 0 ] ] = point1[ 0 ];
+         p1[ perm[ 1 ] ] = point1[ 1 ];
+
+/* If the axes are permuted, convert the supplied angle into a position
+   angle. */
+         pa = ( perm[ 0 ] == 0 )? angle: piby2 - angle;
+
+/* Use Shcal to calculate the required vectors R0 (representing
+   the reference point) and R3 (representing the point which is 90
+   degrees away from the reference point, along the required great
+   circle). The XY plane defines zero latitude, Z is in the direction
+   of increasing latitude, X is towards zero longitude, and Y is
+   towards longitude 90 degrees. */
+         Shcal( p1[ 0 ], p1[ 1 ], pa, r0, r3, status );
+
+/* Use Shapp to use R0 and R3 to calculate the new position. */
+         Shapp( offset, r0, r3,  p1[ 0 ], p2, status );
+
+/* Normalize the result. */
+         astNorm( this, p2 );
+
+/* Create the vector Q1 representing the point in the meridian of the
+   required point which has latitude 90 degrees greater than the
+   required point. */
+         sina1 = sin( p2[ 0 ] );
+         cosa1 = cos( p2[ 0 ] );
+         sinb1 = sin( p2[ 1 ] );
+         cosb1 = cos( p2[ 1 ] );
+
+         q1[ 0 ] = -sinb1*cosa1;
+         q1[ 1 ] = -sinb1*sina1;
+         q1[ 2 ] = cosb1;
+
+/* Create the vector Q2 representing the point on the equator (i.e. a
+   latitude of zero), which has a longitude 90 degrees to the west of
+   the required point. */
+         q2[ 0 ] = -sina1;
+         q2[ 1 ] =  cosa1;
+         q2[ 2 ] =  0.0;
+
+/* Create the vector Q3 representing the point which is 90 degrees away
+   from the required point, along the required great circle. */
+         cosoff = cos( offset );
+         sinoff = sin( offset );
+
+         q3[ 0 ] = -sinoff*r0[ 0 ] + cosoff*r3[ 0 ];
+         q3[ 1 ] = -sinoff*r0[ 1 ] + cosoff*r3[ 1 ];
+         q3[ 2 ] = -sinoff*r0[ 2 ] + cosoff*r3[ 2 ];
+
+/* Calculate the position angle of the great circle at the required
+   point. */
+         pa = atan2( palDvdv( q3, q2 ), palDvdv( q3, q1 ) );
+
+/* Convert this from a pa into the required angle. */
+         result = ( perm[ 0 ] == 0 )? pa: piby2 - pa;
+
+/* Ensure that the end angle is in the range 0 to 2*pi. */
+         result = palDranrm( result );
+
+/* Permute the result coordinates to undo the effect of the SkyFrame
+   axis permutation array. */
+         point2[ 0 ] = p2[ perm[ 0 ] ];
+         point2[ 1 ] = p2[ perm[ 1 ] ];
+      }
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static void Overlay( AstFrame *template, const int *template_axes,
+                     AstFrame *result, int *status ) {
+/*
+*  Name:
+*     Overlay
+
+*  Purpose:
+*     Overlay the attributes of a template SkyFrame on to another Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void Overlay( AstFrame *template, const int *template_axes,
+*                   AstFrame *result, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astOverlay method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function overlays attributes of a SkyFrame (the "template") on to
+*     another Frame, so as to over-ride selected attributes of that second
+*     Frame. Normally only those attributes which have been specifically set
+*     in the template will be transferred. This implements a form of
+*     defaulting, in which a Frame acquires attributes from the template, but
+*     retains its original attributes (as the default) if new values have not
+*     previously been explicitly set in the template.
+*
+*     Note that if the result Frame is a SkyFrame and a change of sky
+*     coordinate system occurs as a result of overlaying its System
+*     attribute, then some of its original attribute values may no
+*     longer be appropriate (e.g. the Title, or attributes describing
+*     its axes). In this case, these will be cleared before overlaying
+*     any new values.
+
+*  Parameters:
+*     template
+*        Pointer to the template SkyFrame, for which values should have been
+*        explicitly set for any attribute which is to be transferred.
+*     template_axes
+*        Pointer to an array of int, with one element for each axis of the
+*        "result" Frame (see below). For each axis in the result frame, the
+*        corresponding element of this array should contain the (zero-based)
+*        index of the template axis to which it corresponds. This array is used
+*        to establish from which template axis any axis-dependent attributes
+*        should be obtained.
+*
+*        If any axis in the result Frame is not associated with a template
+*        axis, the corresponding element of this array should be set to -1.
+*
+*        If a NULL pointer is supplied, the template and result axis
+*        indices are assumed to be identical.
+*     result
+*        Pointer to the Frame which is to receive the new attribute values.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  In general, if the result Frame is not from the same class as the
+*     template SkyFrame, or from a class derived from it, then attributes may
+*     exist in the template SkyFrame which do not exist in the result Frame. In
+*     this case, these attributes will not be transferred.
+*/
+
+
+/* Local Variables: */
+   AstSystemType new_alignsystem;/* Code identifying new alignment coords */
+   AstSystemType new_system;     /* Code identifying new sky cordinates */
+   AstSystemType old_system;     /* Code identifying old sky coordinates */
+   int axis;                     /* Loop counter for result SkyFrame axes */
+   int skyref_changed;           /* Has the SkyRef attribute changed? */
+   int reset_system;             /* Was the template System value cleared? */
+   int skyframe;                 /* Result Frame is a SkyFrame? */
+   int tax0;                     /* Template axis for result axis 0 */
+   int tax1;                     /* Template axis for result axis 1 */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Indicate that we do not need to reset the System attribute of the
+   template. */
+   reset_system = 0;
+   new_system = AST__UNKNOWN;
+
+/* If the result Frame is a SkyFrame, we must test to see if overlaying its
+   System attribute will change the type of sky coordinate system it
+   describes. Determine the value of this attribute for the result and template
+   SkyFrames. We also need to do this if either SkyRef attribute would
+   change. */
+   skyframe = astIsASkyFrame( result );
+   if ( skyframe ) {
+      old_system = astGetSystem( result );
+      new_system = astGetSystem( template );
+      skyref_changed = ( astGetSkyRef( result, 0 ) !=
+                         astGetSkyRef( template, 0 ) ) ||
+                       ( astGetSkyRef( result, 1 ) !=
+                         astGetSkyRef( template, 1 ) );
+
+/* If the coordinate system will change, any value already set for the result
+   SkyFrame's Title will no longer be appropriate, so clear it. */
+      if ( new_system != old_system || skyref_changed ) {
+         astClearTitle( result );
+
+/* Test if the old and new sky coordinate systems are similar enough to make
+   use of the same axis attribute values (e.g. if they are both equatorial
+   systems, then they can both use the same axis labels, etc.,so long as
+   the SKyRefIs value has not changed). */
+         if ( IsEquatorial( new_system, status ) != IsEquatorial( old_system, status ) ||
+              skyref_changed ) {
+
+/* If necessary, clear inappropriate values for all those axis attributes
+   whose access functions are over-ridden by this class (these access functions
+   will then provide suitable defaults appropriate to the new coordinate system
+   instead). */
+            for ( axis = 0; axis < 2; axis++ ) {
+               astClearAsTime( result, axis );
+               astClearDirection( result, axis );
+               astClearFormat( result, axis );
+               astClearLabel( result, axis );
+               astClearSymbol( result, axis );
+               astClearUnit( result, axis );
+            }
+         }
+      }
+
+/* If the result Frame is not a SkyFrame, we must temporarily clear the
+   System and AlignSystem values since the values used by this class are only
+   appropriate to this class. */
+   } else {
+      if( astTestSystem( template ) ) {
+         new_system = astGetSystem( template );
+         astClearSystem( template );
+         new_alignsystem = astGetAlignSystem( template );
+         astClearAlignSystem( template );
+         reset_system = 1;
+      }
+   }
+
+/* Invoke the parent class astOverlay method to transfer attributes inherited
+   from the parent class. */
+   (*parent_overlay)( template, template_axes, result, status );
+
+/* Reset the System and AlignSystem values if necessary */
+   if( reset_system ) {
+      astSetSystem( template, new_system );
+      astSetAlignSystem( template, new_alignsystem );
+   }
+
+/* Check if the result Frame is a SkyFrame or from a class derived from
+   SkyFrame. If not, we cannot transfer SkyFrame attributes to it as it is
+   insufficiently specialised. In this case simply omit these attributes. */
+   if ( skyframe && astOK ) {
+
+/* Define a macro that tests whether an attribute is set in the template and,
+   if so, transfers its value to the result. */
+#define OVERLAY(attr) \
+   if ( astTest##attr( template ) ) { \
+      astSet##attr( result, astGet##attr( template ) ); \
+   }
+
+/* Store template axis indices */
+   if( template_axes ) {
+      tax0 = template_axes[ 0 ];
+      tax1 = template_axes[ 1 ];
+   } else {
+      tax0 = 0;
+      tax1 = 1;
+   }
+
+/* Define a similar macro that does the same for SkyFrame specific axis
+   attributes. */
+#define OVERLAY2(attr) \
+   if( astTest##attr( template, tax0 ) ) { \
+      astSet##attr( result, 0, astGet##attr( template, tax0 ) ); \
+   } \
+   if( astTest##attr( template, tax1 ) ) { \
+      astSet##attr( result, 1, astGet##attr( template, tax1 ) ); \
+   }
+
+/* Use the macro to transfer each SkyFrame attribute in turn. */
+      OVERLAY(Equinox);
+      OVERLAY(Projection);
+      OVERLAY(NegLon);
+      OVERLAY(SkyTol);
+      OVERLAY(AlignOffset);
+      OVERLAY(SkyRefIs);
+      OVERLAY2(SkyRef);
+      OVERLAY2(SkyRefP);
+   }
+
+/* Undefine macros local to this function. */
+#undef OVERLAY
+#undef OVERLAY2
+}
+
+static void Resolve( AstFrame *this_frame, const double point1[],
+                     const double point2[], const double point3[],
+                     double point4[], double *d1, double *d2, int *status ){
+/*
+*  Name:
+*     Resolve
+
+*  Purpose:
+*     Resolve a vector into two orthogonal components
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void Resolve( AstFrame *this, const double point1[],
+*                   const double point2[], const double point3[],
+*                   double point4[], double *d1, double *d2, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astResolve method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function resolves a vector into two perpendicular components.
+*     The vector from point 1 to point 2 is used as the basis vector.
+*     The vector from point 1 to point 3 is resolved into components
+*     parallel and perpendicular to this basis vector. The lengths of the
+*     two components are returned, together with the position of closest
+*     aproach of the basis vector to point 3.
+*
+*     Each vector is a geodesic curve. For a SkyFrame, these are great
+*     circles on the celestial sphere.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the start of the basis vector,
+*        and of the vector to be resolved.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the basis vector.
+*     point3
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the vector to be
+*        resolved.
+*     point4
+*        An array of double, with one element for each Frame axis
+*        in which the coordinates of the point of closest approach of the
+*        basis vector to point 3 will be returned.
+*     d1
+*        The address of a location at which to return the distance from
+*        point 1 to point 4 (that is, the length of the component parallel
+*        to the basis vector). Positive values are in the same sense as
+*        movement from point 1 to point 2.
+*     d2
+*        The address of a location at which to return the distance from
+*        point 4 to point 3 (that is, the length of the component
+*        perpendicular to the basis vector). The returned value is always
+*        positive.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function will return "bad" coordinate values (AST__BAD)
+*     if any of the input coordinates has this value, or if the required
+*     output values are undefined.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   const int *perm;              /* Pointer to axis permutation array */
+   double n1[ 3 ];               /* Unit normal to grt crcl thru p1 and p2 */
+   double n2[ 3 ];               /* Unit normal to grt crcl thru p3 and p4 */
+   double p1[ 2 ];               /* Permuted coordinates for point1 */
+   double p2[ 2 ];               /* Permuted coordinates for point2 */
+   double p3[ 2 ];               /* Permuted coordinates for point3 */
+   double p4[ 2 ];               /* Permuted coordinates for point4 */
+   double v1[ 3 ];               /* 3-vector for p1 */
+   double v2[ 3 ];               /* 3-vector for p2 */
+   double v3[ 3 ];               /* 3-vector for p3 */
+   double v4[ 3 ];               /* 3-vector for p4 */
+   double v5[ 3 ];               /* 3-vector 90 degs away from p1 */
+   double vmod;                  /* Modulus of vector */
+   double vtemp[ 3 ];            /* Temporary vector workspace */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Store initial bad output values. */
+   point4[ 0 ] = AST__BAD;
+   point4[ 1 ] = AST__BAD;
+   *d1 = AST__BAD;
+   *d2 = AST__BAD;
+
+/* Check that all supplied values are OK. */
+   if ( ( point1[ 0 ] != AST__BAD ) && ( point1[ 1 ] != AST__BAD ) &&
+        ( point2[ 0 ] != AST__BAD ) && ( point2[ 1 ] != AST__BAD ) &&
+        ( point3[ 0 ] != AST__BAD ) && ( point3[ 1 ] != AST__BAD ) ) {
+
+/* If so, obtain a pointer to the SkyFrame's axis permutation array. */
+      perm = astGetPerm( this );
+      if ( astOK ) {
+
+/* Apply the axis permutation array to obtain the coordinates of the
+   three supplied point in the required (longitude,latitude) order. */
+         p1[ perm[ 0 ] ] = point1[ 0 ];
+         p1[ perm[ 1 ] ] = point1[ 1 ];
+         p2[ perm[ 0 ] ] = point2[ 0 ];
+         p2[ perm[ 1 ] ] = point2[ 1 ];
+         p3[ perm[ 0 ] ] = point3[ 0 ];
+         p3[ perm[ 1 ] ] = point3[ 1 ];
+
+/* Convert each point into a 3-vector of unit length. */
+         palDcs2c( p1[ 0 ], p1[ 1 ], v1 );
+         palDcs2c( p2[ 0 ], p2[ 1 ], v2 );
+         palDcs2c( p3[ 0 ], p3[ 1 ], v3 );
+
+/* Find the cross product between the first two vectors, and normalize is.
+   This is the unit normal to the great circle plane defining parallel
+   distance. */
+         palDvxv( v2, v1, vtemp );
+         palDvn( vtemp, n1, &vmod );
+
+/* Return with bad values if the normal is undefined (i.e. if the first two
+   vectors are identical or diametrically opposite). */
+         if( vmod > 0.0 ) {
+
+/* Now take the cross product of the normal vector and v1. This gives a
+   point, v5, on the great circle which is 90 degrees away from v1, in the
+   direction of v2. */
+            palDvxv( v1, n1, v5 );
+
+/* Find the cross product of the outlying point (point 3), and the vector
+   n1 found above, and normalize it. This is the unit normal to the great
+   circle plane defining perpendicular distance. */
+            palDvxv( v3, n1, vtemp );
+            palDvn( vtemp, n2, &vmod );
+
+/* Return with bad values if the normal is undefined (i.e. if the
+   outlying point is normal to the great circle defining the basis
+   vector). */
+            if( vmod > 0.0 ) {
+
+/* The point of closest approach, point 4, is the point which is normal
+   to both normal vectors (i.e. the intersection of the two great circles).
+   This is the cross product of n1 and n2. No need to normalize this time
+   since both n1 and n2 are unit vectors, and so v4 will already be a
+   unit vector. */
+               palDvxv( n1, n2, v4 );
+
+/* The dot product of v4 and v1 is the cos of the parallel distance,
+   d1, whilst the dot product of v4 and v5 is the sin of the parallel
+   distance. Use these to get the parallel distance with the correct
+   sign, in the range -PI to +PI. */
+               *d1 = atan2( palDvdv( v4, v5 ), palDvdv( v4, v1 ) );
+
+/* The dot product of v4 and v3 is the cos of the perpendicular distance,
+   d2, whilst the dot product of n1 and v3 is the sin of the perpendicular
+   distance. Use these to get the perpendicular distance. */
+               *d2 = fabs( atan2( palDvdv( v3, n1 ), palDvdv( v3, v4 ) ) );
+
+/* Convert the 3-vector representing the intersection of the two planes
+   back into spherical cooordinates and then constrain the longitude result
+   to lie in the range 0 to 2*pi. */
+               palDcc2s( v4, &p4[ 0 ], &p4[ 1 ] );
+               p4[ 0 ] = palDranrm( p4[ 0 ] );
+
+/* Permute the result coordinates to undo the effect of the SkyFrame
+   axis permutation array. */
+               point4[ 0 ] = p4[ perm[ 0 ] ];
+               point4[ 1 ] = p4[ perm[ 1 ] ];
+            }
+         }
+      }
+   }
+
+   return;
+
+}
+
+static AstPointSet *ResolvePoints( AstFrame *this_frame, const double point1[],
+                                   const double point2[], AstPointSet *in,
+                                   AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     ResolvePoints
+
+*  Purpose:
+*     Resolve a set of vectors into orthogonal components
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     AstPointSet *astResolvePoints( AstFrame *this, const double point1[],
+*                                    const double point2[], AstPointSet *in,
+*                                    AstPointSet *out )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astResolvePoints method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function takes a Frame and a set of vectors encapsulated
+*     in a PointSet, and resolves each one into two orthogonal components,
+*     returning these two components in another PointSet.
+*
+*     This is exactly the same as the public astResolve method, except
+*     that this method allows many vectors to be processed in a single call,
+*     thus reducing the computational cost of overheads of many
+*     individual calls to astResolve.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     point1
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the start of the basis vector,
+*        and of the vectors to be resolved.
+*     point2
+*        An array of double, with one element for each Frame axis
+*        (Naxes attribute). This marks the end of the basis vector.
+*     in
+*        Pointer to the PointSet holding the ends of the vectors to be
+*        resolved.
+*     out
+*        Pointer to a PointSet which will hold the length of the two
+*        resolved components. A NULL value may also be given, in which
+*        case a new PointSet will be created by this function.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet. The first axis will
+*     hold the lengths of the vector components parallel to the basis vector.
+*     These values will be signed (positive values are in the same sense as
+*     movement from point 1 to point 2. The second axis will hold the lengths
+*     of the vector components perpendicular to the basis vector. These
+*     values will be signed only if the Frame is 2-dimensional, in which
+*     case a positive value indicates that rotation from the basis vector
+*     to the tested vector is in the same sense as rotation from the first
+*     to the second axis of the Frame.
+
+*  Notes:
+*     - The number of coordinate values per point in the input
+*     PointSet must match the number of axes in the supplied Frame.
+*     - If an output PointSet is supplied, it must have space for
+*     sufficient number of points and 2 coordinate values per point.
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*     - We assume spherical geometry throughout this function.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstSkyFrame *this;            /* Pointer to SkyFrame structure */
+   const int *perm;              /* Pointer to axis permutation array */
+   double **ptr_in;              /* Pointers to input axis values */
+   double **ptr_out;             /* Pointers to returned axis values */
+   double *d1;                   /* Pointer to next parallel component value */
+   double *d2;                   /* Pointer to next perpendicular component value */
+   double *point3x;              /* Pointer to next first axis value */
+   double *point3y;              /* Pointer to next second axis value */
+   double n1[ 3 ];               /* Unit normal to grt crcl thru p1 and p2 */
+   double n2[ 3 ];               /* Unit normal to grt crcl thru p3 and p4 */
+   double p1[ 2 ];               /* Permuted coordinates for point1 */
+   double p2[ 2 ];               /* Permuted coordinates for point2 */
+   double p3[ 2 ];               /* Permuted coordinates for point3 */
+   double sign;                  /* Sign for perpendicular distances */
+   double v1[ 3 ];               /* 3-vector for p1 */
+   double v2[ 3 ];               /* 3-vector for p2 */
+   double v3[ 3 ];               /* 3-vector for p3 */
+   double v4[ 3 ];               /* 3-vector for p4 */
+   double v5[ 3 ];               /* 3-vector 90 degs away from p1 */
+   double vmod;                  /* Modulus of vector */
+   double vtemp[ 3 ];            /* Temporary vector workspace */
+   int ipoint;                   /* Index of next point */
+   int ncoord_in;                /* Number of input PointSet coordinates */
+   int ncoord_out;               /* Number of coordinates in output PointSet */
+   int npoint;                   /* Number of points to transform */
+   int npoint_out;               /* Number of points in output PointSet */
+   int ok;                       /* OK to proceed? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Obtain the number of input vectors to resolve and the number of coordinate
+   values per vector. */
+   npoint = astGetNpoint( in );
+   ncoord_in = astGetNcoord( in );
+
+/* If OK, check that the number of input coordinates matches the number
+   required by the Frame. Report an error if these numbers do not match. */
+   if ( astOK && ( ncoord_in != 2 ) ) {
+      astError( AST__NCPIN, "astResolvePoints(%s): Bad number of coordinate "
+                "values (%d) in input %s.", status, astGetClass( this ), ncoord_in,
+                astGetClass( in ) );
+      astError( AST__NCPIN, "The %s given requires 2 coordinate values for "
+                "each input point.", status, astGetClass( this ) );
+   }
+
+/* If still OK, and a non-NULL pointer has been given for the output PointSet,
+   then obtain the number of points and number of coordinates per point for
+   this PointSet. */
+   if ( astOK && out ) {
+      npoint_out = astGetNpoint( out );
+      ncoord_out = astGetNcoord( out );
+
+/* Check that the dimensions of this PointSet are adequate to accommodate the
+   output coordinate values and report an error if they are not. */
+      if ( astOK ) {
+         if ( npoint_out < npoint ) {
+            astError( AST__NOPTS, "astResolvePoints(%s): Too few points (%d) in "
+                      "output %s.", status, astGetClass( this ), npoint_out,
+                      astGetClass( out ) );
+            astError( AST__NOPTS, "The %s needs space to hold %d transformed "
+                      "point(s).", status, astGetClass( this ), npoint );
+         } else if ( ncoord_out < 2 ) {
+            astError( AST__NOCTS, "astResolvePoints(%s): Too few coordinate "
+                      "values per point (%d) in output %s.", status,
+                      astGetClass( this ), ncoord_out, astGetClass( out ) );
+            astError( AST__NOCTS, "The %s supplied needs space to store 2 "
+                      "coordinate value(s) per transformed point.", status,
+                      astGetClass( this ) );
+         }
+      }
+   }
+
+/* If all the validation stages are passed successfully, and a NULL output
+   pointer was given, then create a new PointSet to encapsulate the output
+   coordinate data. */
+   if ( astOK ) {
+      if ( !out ) {
+         result = astPointSet( npoint, 2, "", status );
+
+/* Otherwise, use the PointSet supplied. */
+      } else {
+         result = out;
+      }
+   }
+
+/* Get pointers to the input and output axis values */
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Obtain a pointer to the SkyFrame's axis permutation array. */
+   perm = astGetPerm( this );
+
+/* If the axes have been swapped we need to swap the sign of the returned
+   perpendicular distances. */
+   sign = ( perm[ 0 ] == 0 ) ? -1.0 : 1.0;
+
+/* Check pointers can be used safely */
+   if( astOK ) {
+
+/* Apply the axis permutation array to obtain the coordinates of the
+   two supplied points in the required (longitude,latitude) order. */
+      p1[ perm[ 0 ] ] = point1[ 0 ];
+      p1[ perm[ 1 ] ] = point1[ 1 ];
+      p2[ perm[ 0 ] ] = point2[ 0 ];
+      p2[ perm[ 1 ] ] = point2[ 1 ];
+
+/* Convert these points into 3-vectors of unit length. */
+      palDcs2c( p1[ 0 ], p1[ 1 ], v1 );
+      palDcs2c( p2[ 0 ], p2[ 1 ], v2 );
+
+/* Find the cross product between the vectors, and normalize it. This is the
+   unit normal to the great circle plane defining parallel distance. */
+      palDvxv( v2, v1, vtemp );
+      palDvn( vtemp, n1, &vmod );
+
+/* Return with bad values if the normal is undefined (i.e. if the first two
+   vectors are identical or diametrically opposite). */
+      ok = 0;
+      if( vmod > 0.0 ) {
+         ok = 1;
+
+/* Now take the cross product of the normal vector and v1. This gives a
+   point, v5, on the great circle which is 90 degrees away from v1, in the
+   direction of v2. */
+         palDvxv( v1, n1, v5 );
+      }
+
+/* Store pointers to the first two axis arrays in the returned PointSet. */
+      d1 = ptr_out[ 0 ];
+      d2 = ptr_out[ 1 ];
+
+/* Store pointers to the axis values in the supplied PointSet. */
+      point3x = ptr_in[ 0 ];
+      point3y = ptr_in[ 1 ];
+
+/* Check supplied values can be used */
+      if( ok ) {
+
+/* Loop round each supplied vector. */
+         for( ipoint = 0; ipoint < npoint; ipoint++, d1++, d2++,
+                                           point3x++, point3y++ ) {
+
+/* Store bad output values if either input axis value is bad. */
+            if( *point3x == AST__BAD || *point3y == AST__BAD ){
+               *d1 = AST__BAD;
+               *d2 = AST__BAD;
+
+/* If both are good... */
+            } else {
+
+/* Apply the axis permutation array to obtain the coordinates in the
+   required (longitude,latitude) order. */
+               p3[ perm[ 0 ] ] = *point3x;
+               p3[ perm[ 1 ] ] = *point3y;
+
+/* Convert into a 3-vector of unit length. */
+               palDcs2c( p3[ 0 ], p3[ 1 ], v3 );
+
+/* Find the cross product of the outlying point (point 3), and the vector
+   n1 found above, and normalize it. This is the unit normal to the great
+   circle plane defining perpendicular distance. */
+               palDvxv( v3, n1, vtemp );
+               palDvn( vtemp, n2, &vmod );
+
+/* Return with bad values if the normal is undefined (i.e. if the
+   outlying point is normal to the great circle defining the basis
+   vector). */
+               if( vmod <= 0.0 ) {
+                  *d1 = AST__BAD;
+                  *d2 = AST__BAD;
+               } else {
+
+/* The point of closest approach, point 4, is the point which is normal
+   to both normal vectors (i.e. the intersection of the two great circles).
+   This is the cross product of n1 and n2. No need to normalize this time
+   since both n1 and n2 are unit vectors, and so v4 will already be a
+   unit vector. */
+                  palDvxv( n1, n2, v4 );
+
+/* The dot product of v4 and v1 is the cos of the parallel distance,
+   d1, whilst the dot product of v4 and v5 is the sin of the parallel
+   distance. Use these to get the parallel distance with the correct
+   sign, in the range -PI to +PI. */
+                  *d1 = atan2( palDvdv( v4, v5 ), palDvdv( v4, v1 ) );
+
+/* The dot product of v4 and v3 is the cos of the perpendicular distance,
+   d2, whilst the dot product of n1 and v3 is the sin of the perpendicular
+   distance. Use these to get the perpendicular distance. */
+                  *d2 = sign*atan2( palDvdv( v3, n1 ), palDvdv( v3, v4 ) );
+               }
+            }
+         }
+
+/* If supplied values cannot be used, fill the returned PointSet with bad
+   values */
+      } else {
+         for( ipoint = 0; ipoint < npoint; ipoint++, d1++, d2++ ) {
+            *d1 = AST__BAD;
+            *d2 = AST__BAD;
+         }
+      }
+   }
+
+/* Annul the returned PointSet if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static void SetAsTime( AstSkyFrame *this, int axis, int value, int *status ) {
+/*
+*  Name:
+*     SetAsTime
+
+*  Purpose:
+*     Set a value for the AsTime attribute for a SkyFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void SetAsTime( AstSkyFrame *this, int axis, int value, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function sets the boolean value of the AsTime attribute for a
+*     specified axis of a SkyFrame. This value indicates whether axis values
+*     should be formatted as times (as opposed to angles) by default.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Index of the axis for which a value is to be set (zero based).
+*     value
+*        The boolean value to be set.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void.
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   AstSkyAxis *new_ax;           /* Pointer to new SkyAxis object */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astSetAsTime" );
+
+/* Obtain a pointer to the Axis object. */
+   ax = astGetAxis( this, axis );
+
+/* Check if the Axis object is a SkyAxis. If not, we will replace it with
+   one. */
+   if ( !astIsASkyAxis( ax ) ) {
+
+/* Create a new SkyAxis and overlay the attributes of the original Axis. */
+      new_ax = astSkyAxis( "", status );
+      astAxisOverlay( ax, new_ax );
+
+/* Modify the SkyFrame to use the new Skyaxis and annul the original Axis
+   pointer. Retain a pointer to the new SkyAxis. */
+      astSetAxis( this, axis, new_ax );
+      ax = astAnnul( ax );
+      ax = (AstAxis *) new_ax;
+   }
+
+/* Set a value for the Axis AsTime attribute. */
+   astSetAxisAsTime( ax, value );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (extends the astSetAttrib method inherited from
+*     the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a SkyFrame, the attribute
+*     and its value being specified by means of a string of the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in lower
+*     case with no white space present. The value to the right of the "="
+*     should be a suitable textual representation of the value to be assigned
+*     and this will be interpreted according to the attribute's data type.
+*     White space surrounding the value is only significant for string
+*     attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Attributes:
+*     As well as those attributes inherited from the parent class, this
+*     function also accepts values for the following additional attributes:
+*
+*        Equinox (double, read as a string)
+
+*  Notes:
+*     This protected method is intended to be invoked by the Object astSet
+*     method and makes additional attributes accessible to it.
+*/
+
+/* Local Vaiables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   double dval;                  /* Floating point attribute value */
+   double dval1;                 /* Floating point attribute value */
+   double dval2;                 /* Floating point attribute value */
+   double mjd;                   /* Modified Julian Date */
+   int astime;                   /* Value of AsTime attribute */
+   int axis;                     /* Axis index */
+   int equinox;                  /* Offset of Equinox attribute value */
+   int ival;                     /* Integer attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+   int neglon;                   /* Display -ve longitudes? */
+   int ok;                       /* Can string be used? */
+   int offset;                   /* Offset of start of attribute value */
+   int projection;               /* Offset of projection attribute value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* AsTime(axis). */
+/* ------------- */
+   if ( nc = 0,
+        ( 2 == astSscanf( setting, "astime(%d)= %d %n", &axis, &astime, &nc ) )
+        && ( nc >= len ) ) {
+      astSetAsTime( this, axis - 1, astime );
+
+/* Equinox. */
+/* -------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "equinox=%n%*[^\n]%n",
+                              &equinox, &nc ) ) && ( nc >= len ) ) {
+
+/* Convert the Equinox value to a Modified Julian Date before use. */
+      mjd = astReadDateTime( setting + equinox );
+      if ( astOK ) {
+         astSetEquinox( this, mjd );
+
+/* Report contextual information if the conversion failed. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid equinox value "
+                   "\"%s\" given for sky coordinate system.", status,
+                   astGetClass( this ), setting + equinox );
+      }
+
+/* NegLon. */
+/* ------- */
+   } else if ( nc = 0,
+             ( 1 == astSscanf( setting, "neglon= %d %n", &neglon, &nc ) )
+               && ( nc >= len ) ) {
+      astSetNegLon( this, neglon );
+
+/* SkyTol. */
+/* ------- */
+   } else if ( nc = 0,
+             ( 1 == astSscanf( setting, "skytol= %lg %n", &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetSkyTol( this, dval );
+
+/* Projection. */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "projection=%n%*[^\n]%n",
+                              &projection, &nc ) )
+               && ( nc >= len ) ) {
+      astSetProjection( this, setting + projection );
+
+/* SkyRef. */
+/* ------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "skyref=%n%*[^\n]%n",
+                                 &offset, &nc ) )
+               && ( nc >= len ) ) {
+      ok = 0;
+      nc = astUnformat( this, 0, setting + offset, &dval1 );
+      if( setting[ offset + nc ] == ',' ) {
+         nc++;
+         nc += astUnformat( this, 1, setting + offset + nc, &dval2 );
+         if( nc == strlen( setting + offset ) ) {
+            astSetSkyRef( this, 0, dval1 );
+            astSetSkyRef( this, 1, dval2 );
+            ok = 1;
+         }
+      }
+
+      if( !ok && astOK ) {
+         astError( AST__BADOC, "astSetAttrib(%s): Invalid axis values string "
+                   "\"%.*s\" given for SkyRef attribute.", status, astGetClass( this ),
+                   (int) astChrLen( setting + offset ), setting + offset );
+      }
+
+/* SkyRef(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "skyref(%d)= %lg %n",
+                                 &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetSkyRef( this, axis - 1, dval );
+
+/* SkyRefIs. */
+/* --------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "skyrefis=%n%*[^\n]%n",
+                              &offset, &nc ) )
+               && ( nc >= len ) ) {
+
+      if( astChrMatch( setting + offset, POLE_STRING ) ) {
+         astSetSkyRefIs( this, AST__POLE_REF );
+
+      } else if( astChrMatch( setting + offset, ORIGIN_STRING ) ) {
+         astSetSkyRefIs( this, AST__ORIGIN_REF );
+
+      } else if( astChrMatch( setting + offset, IGNORED_STRING ) ) {
+         astSetSkyRefIs( this, AST__IGNORED_REF );
+
+      } else if( astOK ) {
+         astError( AST__OPT, "astSet(%s): option '%s' is unknown in '%s'.", status,
+                   astGetClass( this ), setting+offset, setting );
+      }
+
+/* SkyRefP. */
+/* -------- */
+   } else if ( nc = 0,
+               ( 0 == astSscanf( setting, "skyrefp=%n%*[^\n]%n",
+                                 &offset, &nc ) )
+               && ( nc >= len ) ) {
+
+      ok = 0;
+      nc = astUnformat( this, 0, setting + offset, &dval1 );
+      if( setting[ offset + nc ] == ',' ) {
+         nc++;
+         nc += astUnformat( this, 1, setting + offset + nc, &dval2 );
+         if( nc == strlen( setting + offset ) ) {
+            astSetSkyRefP( this, 0, dval1 );
+            astSetSkyRefP( this, 1, dval2 );
+            ok = 1;
+         }
+      }
+
+      if( !ok && astOK ) {
+         astError( AST__BADOC, "astSetAttrib(%s): Invalid axis values string "
+                   "\"%.*s\" given for SkyRefP attribute.", status, astGetClass( this ),
+                   (int) astChrLen( setting + offset ), setting + offset );
+      }
+
+
+/* SkyRefP(axis). */
+/* -------------- */
+   } else if ( nc = 0,
+               ( 2 == astSscanf( setting, "skyrefp(%d)= %lg %n",
+                                 &axis, &dval, &nc ) )
+               && ( nc >= len ) ) {
+      astSetSkyRefP( this, axis - 1, dval );
+
+/* AlignOffset. */
+/* ------------ */
+   } else if ( nc = 0,
+             ( 1 == astSscanf( setting, "alignoffset= %d %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetAlignOffset( this, ival );
+
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* If the attribute was not recognised, use this macro to report an error
+   if a read-only attribute has been specified. */
+   } else if ( !strncmp( setting, "islataxis", 9 ) ||
+               !strncmp( setting, "islonaxis", 9 ) ||
+               MATCH( "lataxis" ) ||
+               MATCH( "lonaxis" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Pass any unrecognised setting to the parent method for further
+   interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SetCachedLAST( AstSkyFrame *this, double last, double epoch,
+                           double obslon, double obslat, double obsalt,
+                           double dut1, int *status ) {
+/*
+*  Name:
+*     SetCachedLAST
+
+*  Purpose:
+*     Store a LAST value in the cache in the SkyFrame vtab.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void SetCachedLAST( AstSkyFrame *this, double last, double epoch,
+*                         double obslon, double obslat, double obsalt,
+*                         double dut1, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function stores the supplied LAST value in a cache in the
+*     SkyFrame virtual function table for later use by GetCachedLAST.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     last
+*        The Local Apparent Sidereal Time (radians).
+*     epoch
+*        The epoch (MJD).
+*     obslon
+*        Observatory geodetic longitude (radians)
+*     obslat
+*        Observatory geodetic latitude (radians)
+*     obsalt
+*        Observatory geodetic altitude (metres)
+*     dut1
+*        The UT1-UTC correction, in seconds.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   AstSkyLastTable *table;
+   double *ep;
+   double *lp;
+   double lp_ref;
+   int i;
+   int itable;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise */
+   table = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Ensure no threads are allowed to read the table whilst we are writing
+   to it. */
+   LOCK_WLOCK1
+
+/* Loop round every LAST table held in the vtab. Each table refers to a
+   different observatory position and/or DUT1 value. */
+   for( itable = 0; itable < nlast_tables; itable++ ) {
+      table = last_tables[ itable ];
+
+/* See if the table refers to the given position and dut1 value, allowing
+   some small tolerance. If it does, leave the loop. */
+      if( fabs( table->obslat - obslat ) < 2.0E-7 &&
+          fabs( table->obslon - obslon ) < 2.0E-7 &&
+          fabs( table->obsalt - obsalt ) < 1.0 &&
+          fabs( table->dut1 - dut1 ) < 1.0E-5 ) break;
+
+/* Ensure "table" ends up NULL if no suitable table is found. */
+      table = NULL;
+   }
+
+/* If no table was found, create one now, and add it into the vtab cache. */
+   if( !table ) {
+
+      astBeginPM;
+      table = astMalloc( sizeof( AstSkyLastTable ) );
+      itable = nlast_tables++;
+      last_tables = astGrow( last_tables, nlast_tables,
+                                   sizeof( AstSkyLastTable * ) );
+      astEndPM;
+
+      if( astOK ) {
+         last_tables[ itable ] = table;
+         table->obslat = obslat;
+         table->obslon = obslon;
+         table->obsalt = obsalt;
+         table->dut1 = dut1;
+         table->nentry = 1;
+
+         astBeginPM;
+         table->epoch = astMalloc( sizeof( double ) );
+         table->last = astMalloc( sizeof( double ) );
+         astEndPM;
+
+         if( astOK ) {
+            table->epoch[ 0 ] = epoch;
+            table->last[ 0 ] = last;
+         }
+      }
+
+
+/* If we have a table, add the new point into it. */
+   } else {
+
+/* Extend the epoch and last arrays. */
+      astBeginPM;
+      table->epoch = astGrow( table->epoch, ++(table->nentry), sizeof( double ) );
+      table->last = astGrow( table->last, table->nentry, sizeof( double ) );
+      astEndPM;
+
+/* Check memory allocation was successful. */
+      if( astOK ) {
+
+/* Get pointers to the last original elements in the arrays of epoch and
+   corresponding LAST values in the table. */
+         ep = table->epoch + table->nentry - 2;
+         lp = table->last + table->nentry - 2;
+
+/* Starting from the end of the arrays, shuffle all entries up one
+   element until an element is found which is less than the supplied epoch
+   value. This maintains the epoch array in monotonic increasing order. */
+         for( i = table->nentry - 2; i >= 0; i--,ep--,lp-- ) {
+            if( *ep <= epoch ) break;
+            ep[ 1 ] = *ep;
+            lp[ 1 ] = *lp;
+         }
+
+/* Store the new epoch and LAST value. Add or subtract 2.PI as needed
+   from the new LAST value to ensure it is continuous with an adjacent
+   LAST value. This is needed for interpolation between the two values
+   to be meaningful.  */
+         ep[ 1 ] = epoch;
+
+/* For most cases, compare with the previous LAST value. If the new epoch
+   value is smaller than any epoch already in the table, there will be no
+   previous LAST value. So compare with the next value instead. */
+         if( i >= 0 ) {
+            lp_ref = lp[ 0 ];
+         } else {
+            lp_ref = lp[ 2 ];
+         }
+
+         if( last > lp_ref + AST__DPI ) {
+            lp[ 1 ] = last - 2*AST__DPI;
+
+         } else if( last < lp_ref - AST__DPI ) {
+            lp[ 1 ] = last + 2*AST__DPI;
+
+         } else {
+            lp[ 1 ] = last;
+         }
+      }
+   }
+
+/* Indicate other threads are now allowed to read the table. */
+   UNLOCK_RWLOCK1
+
+}
+
+static void SetDut1( AstFrame *this_frame, double val, int *status ) {
+/*
+*  Name:
+*     SetDut1
+
+*  Purpose:
+*     Set the value of the Dut1 attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void SetDut1( AstFrame *this, double val, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astSetDut1 method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function clears the Dut1 value and updates the LAST value
+*     stored in the SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     val
+*        New Dut1 value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;
+   double orig;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Note the original Dut1 value. */
+   orig = astGetDut1( this );
+
+/* Invoke the parent method to set the Frame Dut1 value. */
+   (*parent_setdut1)( this_frame, val, status );
+
+/* If the DUT1 value has changed significantly, indicate that the LAST value
+   will need to be re-calculated when it is next needed. */
+   if( fabs( orig - val ) > 1.0E-6 ) {
+      this->last = AST__BAD;
+      this->eplast = AST__BAD;
+      this->klast = AST__BAD;
+   }
+}
+
+static void SetLast( AstSkyFrame *this, int *status ) {
+/*
+*  Name:
+*     SetLast
+
+*  Purpose:
+*     Set the Local Appearent Sidereal Time for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void SetLast( AstSkyFrame *this, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function sets the Local Apparent Sidereal Time at the epoch
+*     and geographical longitude given by the current values of the Epoch
+*     and ObsLon attributes associated with the supplied SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   double epoch;      /* Epoch as a TDB MJD */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the SkyFrame Epoch as a TDB MJD. */
+   epoch = astGetEpoch( this );
+
+/* Calculate the LAST value (in rads) and store in the SkyFrame structure. */
+   this->last = CalcLAST( this, epoch, astGetObsLon( this ),
+                          astGetObsLat( this ), astGetObsAlt( this ),
+                          astGetDut1( this ), status );
+
+/* Save the TDB MJD to which this LAST corresponds. */
+   this->eplast = epoch;
+
+/* The ratio between solar and sidereal time is a slowly varying function
+   of epoch. The GetLAST function returns a fast approximation to LAST
+   by using the ratio between solar and sidereal time. Indicate that
+   GetLAST should re-calculate the ratio by setting the ratio value bad. */
+   this->klast = AST__BAD;
+}
+
+static void SetObsAlt( AstFrame *this, double val, int *status ) {
+/*
+*  Name:
+*     SetObsAlt
+
+*  Purpose:
+*     Set the value of the ObsAlt attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void SetObsAlt( AstFrame *this, double val, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astSetObsAlt method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function sets the ObsAlt value.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     val
+*        New ObsAlt value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double orig;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Note the original ObsAlt value. */
+   orig = astGetObsAlt( this );
+
+/* Invoke the parent method to set the Frame ObsAlt. */
+   (*parent_setobsalt)( this, val, status );
+
+/* If the altitude has changed significantly, indicate that the LAST value
+   and magnitude of the diurnal aberration vector will need to be
+   re-calculated when next needed. */
+   if( fabs( orig - val ) > 0.001 ) {
+      ( (AstSkyFrame *) this )->last = AST__BAD;
+      ( (AstSkyFrame *) this )->eplast = AST__BAD;
+      ( (AstSkyFrame *) this )->klast = AST__BAD;
+      ( (AstSkyFrame *) this )->diurab = AST__BAD;
+   }
+}
+
+static void SetObsLat( AstFrame *this, double val, int *status ) {
+/*
+*  Name:
+*     SetObsLat
+
+*  Purpose:
+*     Set the value of the ObsLat attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void SetObsLat( AstFrame *this, double val, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astSetObsLat method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function sets the ObsLat value.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     val
+*        New ObsLat value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double orig;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Note the original ObsLat value. */
+   orig = astGetObsLat( this );
+
+/* Invoke the parent method to set the Frame ObsLat. */
+   (*parent_setobslat)( this, val, status );
+
+/* If the altitude has changed significantly, indicate that the LAST value
+   and magnitude of the diurnal aberration vector will need to be
+   re-calculated when next needed. */
+   if( fabs( orig - val ) > 1.0E-8 ) {
+      ( (AstSkyFrame *) this )->last = AST__BAD;
+      ( (AstSkyFrame *) this )->eplast = AST__BAD;
+      ( (AstSkyFrame *) this )->klast = AST__BAD;
+      ( (AstSkyFrame *) this )->diurab = AST__BAD;
+   }
+}
+
+static void SetObsLon( AstFrame *this, double val, int *status ) {
+/*
+*  Name:
+*     SetObsLon
+
+*  Purpose:
+*     Set the value of the ObsLon attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void SetObsLon( AstFrame *this, double val, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astSetObsLon method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function sets the ObsLon value.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     val
+*        New ObsLon value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double orig;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Note the original ObsLon value. */
+   orig = astGetObsLon( this );
+
+/* Invoke the parent method to set the Frame ObsLon. */
+   (*parent_setobslon)( this, val, status );
+
+/* If the longitude has changed significantly, indicate that the LAST value
+   will need to be re-calculated when it is next needed. */
+   if( fabs( orig - val ) > 1.0E-8 ) {
+      ( (AstSkyFrame *) this )->last = AST__BAD;
+      ( (AstSkyFrame *) this )->eplast = AST__BAD;
+      ( (AstSkyFrame *) this )->klast = AST__BAD;
+   }
+}
+
+static void SetSystem( AstFrame *this_frame, AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SetSystem
+
+*  Purpose:
+*     Set the System attribute for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void SetSystem( AstFrame *this_frame, AstSystemType system, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astSetSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function assigns a new value to the System attribute for a SkyFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     system
+*        The new System value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;              /* FrameSet to be used as the Mapping */
+   AstSkyFrame *sfrm;            /* Copy of original SkyFrame */
+   AstSkyFrame *this;            /* Pointer to SkyFrame structure */
+   double xin[ 2 ];              /* Axis 0 values */
+   double xout[ 2 ];             /* Axis 0 values */
+   double yin[ 2 ];              /* Axis 1 values */
+   double yout[ 2 ];             /* Axis 1 values */
+   int aloff;                    /* The AlignOffset attribute value */
+   int aloff_set;                /* Is the AlignOffset attribute set? */
+   int skyref_set;               /* Is either SkyRef attribute set? */
+   int skyrefis;                 /* The SkyRefIs attribute value */
+   int skyrefis_set;             /* Is the SkyRefIs attribute set? */
+   int skyrefp_set;              /* Is either SkyRefP attribute set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* See if either the SkyRef or SkyRefP attribute is set. */
+   skyref_set = astTestSkyRef( this, 0 ) || astTestSkyRef( this, 1 );
+   skyrefp_set = astTestSkyRefP( this, 0 ) || astTestSkyRefP( this, 1 );
+
+/* If so, we will need to transform their values into the new coordinate
+   system. Save a copy of the SkyFrame with its original System value. */
+   sfrm = ( skyref_set || skyrefp_set )?astCopy( this ):NULL;
+
+/* Use the parent method to set the new System value. */
+   (*parent_setsystem)( this_frame, system, status );
+
+/* Now modify the SkyRef and SkyRefP attributes if necessary. */
+   if( sfrm ) {
+
+/* Save the AlignOffset, SkyRefIs, SkyRef and SkyRefP values. */
+      aloff_set = astTestAlignOffset( sfrm );
+      aloff = astGetAlignOffset( sfrm );
+      skyrefis_set = astTestSkyRefIs( sfrm );
+      skyrefis = astGetSkyRefIs( sfrm );
+
+      xin[ 0 ] = astGetSkyRef( sfrm, 0 );
+      xin[ 1 ] = astGetSkyRefP( sfrm, 0 );
+      yin[ 0 ] = astGetSkyRef( sfrm, 1 );
+      yin[ 1 ] = astGetSkyRefP( sfrm, 1 );
+
+/* Clear the SkyRef and SkyRefP values to avoid infinite recursion in the
+   following call to astConvert. */
+      if( skyref_set ) {
+         astClearSkyRef( sfrm, 0 );
+         astClearSkyRef( sfrm, 1 );
+         astClearSkyRef( this, 0 );
+         astClearSkyRef( this, 1 );
+      }
+
+      if( skyrefp_set ) {
+         astClearSkyRefP( sfrm, 0 );
+         astClearSkyRefP( sfrm, 1 );
+         astClearSkyRefP( this, 0 );
+         astClearSkyRefP( this, 1 );
+      }
+
+/* Also set AlignOffset and SkyRefIs so that the following call to
+   astConvert does not align in offset coords. */
+      astSetAlignOffset( sfrm, 0 );
+      astSetSkyRefIs( sfrm, AST__IGNORED_REF );
+
+/* Get the Mapping from the original System to the new System. Invoking
+   astConvert will recursively invoke SetSystem again. This is why we need
+   to be careful to ensure that SkyRef and SKyRefP are cleared above - doing
+   so ensure we do not end up with infinite recursion. */
+      fs = astConvert( sfrm, this, "" );
+
+/* If the conversion is not possible, clear the SkyRef and SkyRefP
+   values. */
+      if( !fs ) {
+         if( skyref_set ) {
+            astClearSkyRef( this, 0 );
+            astClearSkyRef( this, 1 );
+         }
+         if( skyrefp_set ) {
+            astClearSkyRefP( this, 0 );
+            astClearSkyRefP( this, 1 );
+         }
+
+/* Use the Mapping to find the SkyRef and SkyRefP positions in the new
+   coordinate system. */
+      } else {
+         astTran2( fs, 2, xin, yin, 1, xout, yout );
+
+/* Store the values as required. */
+         if( skyref_set ) {
+            astSetSkyRef( this, 0, xout[ 0 ] );
+            astSetSkyRef( this, 1, yout[ 0 ] );
+         }
+
+         if( skyrefp_set ) {
+            astSetSkyRefP( this, 0, xout[ 1 ] );
+            astSetSkyRefP( this, 1, yout[ 1 ] );
+         }
+
+/* Restore the original SkyRefIs and AlignOffset values. */
+         if( aloff_set ) {
+            astSetAlignOffset( this, aloff );
+         } else {
+            astClearAlignOffset( this );
+         }
+
+         if( skyrefis_set ) {
+            astSetSkyRefIs( this, skyrefis );
+         } else {
+            astClearSkyRefIs( this );
+         }
+
+/* Free resources. */
+         fs = astAnnul( fs );
+      }
+      sfrm = astAnnul( sfrm );
+   }
+}
+
+static void Shapp( double dist, double *r0, double *r3, double a0,
+                   double *p4, int *status ){
+/*
+*  Name:
+*     Shapp
+
+*  Purpose:
+*     Use the vectors calculated by Shcal to find a sky position
+*     which is offset along a given position angle.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void Shapp( double dist, double *r0, double *r3, double a0,
+*                 double *p4, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function uses the vectors R0 and R3 calculated previously by
+*     Shcal to find the sky position which is offset away from the
+*     "reference" position (see function Offset2) by a given arc
+*     distance, along a given great circle.
+*
+*     No checks are made for AST__BAD values.
+
+*  Parameters:
+*     dist
+*        The arc distance to move away from the reference position
+*        in the given direction, in radians.
+*     r0
+*        Pointer to an array holding the 3-vector representing the reference
+*        position.
+*     r3
+*        Pointer to an array holding the 3-vector representing the
+*        point which is 90 degrees away from the reference point, along
+*        the required great circle.
+*     a0
+*        The sky longitude of the reference position, in radians.
+*     p4
+*        Pointer to an array of 2 doubles in which to put the sky longitude
+*        and latitude of the required point, in radians.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double cosdst;            /* Cosine of DIST */
+   double r4[ 3 ];           /* Required position vector */
+   double sindst;            /* Sine of DIST */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store commonly used values. */
+   sindst = sin( dist );
+   cosdst = cos( dist );
+
+/* The vector R4 representing the required point is produced as a
+   linear sum of R0 and R3. */
+   r4[ 0 ] = cosdst*r0[ 0 ] + sindst*r3[ 0 ];
+   r4[ 1 ] = cosdst*r0[ 1 ] + sindst*r3[ 1 ];
+   r4[ 2 ] = cosdst*r0[ 2 ] + sindst*r3[ 2 ];
+
+/* Create the longitude of the required point. If this point is at
+   a pole it is assigned the same longitude as the reference point. */
+   if( r4[ 0 ] != 0.0 || r4[ 1 ] != 0.0 ) {
+      p4[ 0 ] = atan2( r4[ 1 ], r4[ 0 ] );
+   } else {
+      p4[ 0 ] = a0;
+   }
+
+/* Create the latitude of the required point. */
+   if( r4[ 2 ] > 1.0 ) {
+      r4[ 2 ] = 1.0;
+   } else if( r4[ 2 ] < -1.0 ) {
+      r4[ 2 ] = -1.0;
+   }
+   p4[ 1 ] = asin( r4[ 2 ] );
+
+}
+
+static void Shcal( double a0, double b0, double angle, double *r0,
+                   double *r3, int *status ) {
+/*
+*  Name:
+*     Shcal
+
+*  Purpose:
+*     Calculate vectors required by Offset2.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     void Shcal( double a0, double b0, double angle, double *r0,
+*                 double *r3, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function calculates the 3-vector R0, representing the given
+*     sky position (A0,B0), and the 3-vector R3, representing the sky
+*     position which is 90 degrees away from R0, along a great circle
+*     passing through R0 at a position angle given by ANGLE. Each
+*     3-vector holds Cartesian (X,Y,Z) values with origin at the centre
+*     of the celestial sphere. The XY plane is the "equator", the Z
+*     axis is in the direction of the "north pole", X is towards zero
+*     longitude (A=0), and Y is towards longitude 90 degrees.
+*
+*     No checks are made for AST__BAD input values.
+
+*  Parameters:
+*     a0
+*        The sky longitude of the given position, in radians.
+*     b0
+*        The sky latitude of the given position, in radians.
+*     angle
+*        The position angle of a great circle passing through the given
+*        position.  That is, the angle from north to the required
+*        direction, in radians. Positive angles are in the sense of
+*        rotation from north to east.
+*     r0
+*        A pointer to an array to receive 3-vector R0. See above.
+*     r3
+*        A pointer to an array to receive 3-vector R3. See above.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double cosa0;         /* Cosine of A0 */
+   double cosb0;         /* Cosine of B0 */
+   double cospa;         /* Cosine of ANGLE */
+   double r1[ 3 ];       /* Vector PI/2 away from R0 in meridian of R0 */
+   double r2[ 3 ];       /* Vector PI/2 away from R0 on equator */
+   double sinpa;         /* Sine of ANGLE */
+   double sina0;         /* Sine of A0 */
+   double sinb0;         /* Sine of B0 */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store commonly used values. */
+   sina0 = sin( a0 );
+   cosa0 = cos( a0 );
+   sinb0 = sin( b0 );
+   cosb0 = cos( b0 );
+   sinpa = sin( angle );
+   cospa = cos( angle );
+
+/* Create the vector R0 representing the given point. The XY plane
+   defines zero latitude, Z is in the direction of increasing latitude,
+   X is towards zero longitude, and Y is towards longitude 90 degrees. */
+   r0[ 0 ] =  cosb0*cosa0;
+   r0[ 1 ] =  cosb0*sina0;
+   r0[ 2 ] =  sinb0;
+
+/* Create the vector R1 representing the point in the meridian of the
+   given point which has latitude 90 degrees greater than the
+   given point. */
+   r1[ 0 ] = -sinb0*cosa0;
+   r1[ 1 ] = -sinb0*sina0;
+   r1[ 2 ] =  cosb0;
+
+/* Create the vector R2 representing the point on the equator (i.e. a
+   latitude of zero), which has a longitude 90 degrees to the west of
+   the given point. */
+   r2[ 0 ] = -sina0;
+   r2[ 1 ] =  cosa0;
+   r2[ 2 ] =  0.0;
+
+/* Create the vector R3 representing the point which is 90 degrees away
+   from the given point, along the required great circle. */
+   r3[ 0 ] =  cospa*r1[ 0 ] + sinpa*r2[ 0 ];
+   r3[ 1 ] =  cospa*r1[ 1 ] + sinpa*r2[ 1 ];
+   r3[ 2 ] =  cospa*r1[ 2 ] + sinpa*r2[ 2 ];
+
+/* Return */
+   return;
+}
+
+static int SubFrame( AstFrame *target_frame, AstFrame *template,
+                     int result_naxes, const int *target_axes,
+                     const int *template_axes, AstMapping **map,
+                     AstFrame **result, int *status ) {
+/*
+*  Name:
+*     SubFrame
+
+*  Purpose:
+*     Select axes from a SkyFrame and convert to the new coordinate system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int SubFrame( AstFrame *target, AstFrame *template,
+*                   int result_naxes, const int *target_axes,
+*                   const int *template_axes, AstMapping **map,
+*                   AstFrame **result, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the protected astSubFrame method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function selects a requested sub-set (or super-set) of the axes from
+*     a "target" SkyFrame and creates a new Frame with copies of the selected
+*     axes assembled in the requested order. It then optionally overlays the
+*     attributes of a "template" Frame on to the result. It returns both the
+*     resulting Frame and a Mapping that describes how to convert between the
+*     coordinate systems described by the target and result Frames. If
+*     necessary, this Mapping takes account of any differences in the Frames'
+*     attributes due to the influence of the template.
+
+*  Parameters:
+*     target
+*        Pointer to the target SkyFrame, from which axes are to be selected.
+*     template
+*        Pointer to the template Frame, from which new attributes for the
+*        result Frame are to be obtained. Optionally, this may be NULL, in
+*        which case no overlaying of template attributes will be performed.
+*     result_naxes
+*        Number of axes to be selected from the target Frame. This number may
+*        be greater than or less than the number of axes in this Frame (or
+*        equal).
+*     target_axes
+*        Pointer to an array of int with result_naxes elements, giving a list
+*        of the (zero-based) axis indices of the axes to be selected from the
+*        target SkyFrame. The order in which these are given determines the
+*        order in which the axes appear in the result Frame. If any of the
+*        values in this array is set to -1, the corresponding result axis will
+*        not be derived from the target Frame, but will be assigned default
+*        attributes instead.
+*     template_axes
+*        Pointer to an array of int with result_naxes elements. This should
+*        contain a list of the template axes (given as zero-based axis indices)
+*        with which the axes of the result Frame are to be associated. This
+*        array determines which axes are used when overlaying axis-dependent
+*        attributes of the template on to the result. If any element of this
+*        array is set to -1, the corresponding result axis will not receive any
+*        template attributes.
+*
+*        If the template argument is given as NULL, this array is not used and
+*        a NULL pointer may also be supplied here.
+*     map
+*        Address of a location to receive a pointer to the returned Mapping.
+*        The forward transformation of this Mapping will describe how to
+*        convert coordinates from the coordinate system described by the target
+*        SkyFrame to that described by the result Frame. The inverse
+*        transformation will convert in the opposite direction.
+*     result
+*        Address of a location to receive a pointer to the result Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if coordinate conversion is possible
+*     between the target and the result Frame. Otherwise zero is returned and
+*     *map and *result are returned as NULL (but this will not in itself
+*     result in an error condition). In general, coordinate conversion should
+*     always be possible if no template Frame is supplied but may not always
+*     be possible otherwise.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+
+*  Implementation Notes:
+*     -  This implementation addresses the selection of axes from a SkyFrame
+*     object. This results in another object of the same class only if both
+*     axes of the SkyFrame are selected, once each. Otherwise, the result is a
+*     Frame class object which inherits the SkyFrame's axis information (if
+*     appropriate) but none of the other properties of a SkyFrame.
+*     -  In the event that a SkyFrame results, the returned Mapping will take
+*     proper account of the relationship between the target and result sky
+*     coordinate systems.
+*     -  In the event that a Frame class object results, the returned Mapping
+*     will only represent a selection/permutation of axes.
+
+*  Implementation Deficiencies:
+*     -  Any axis selection is currently permitted. Probably this should be
+*     restricted so that each axis can only be selected once. The
+*     astValidateAxisSelection method will do this but currently there are bugs
+*     in the CmpFrame class that cause axis selections which will not pass this
+*     test. Install the validation when these are fixed.
+*/
+
+/* Local Variables: */
+   AstAxis *ax;                  /* Pointer to result Frame Axis object */
+   AstMapping *tmpmap;           /* Temporary Mapping pointer */
+   AstPermMap *permmap;          /* Pointer to PermMap */
+   AstSkyFrame *target;          /* Pointer to the SkyFrame structure */
+   AstSkyFrame *temp;            /* Pointer to copy of target SkyFrame */
+   AstSystemType align_sys;      /* System in which to align the SkyFrames */
+   int match;                    /* Coordinate conversion is possible? */
+   int perm[ 2 ];                /* Permutation array for axis swap */
+   int result_swap;              /* Swap result SkyFrame coordinates? */
+   int set_usedefs;              /* Set the returned UseDefs attribute zero?*/
+   int target_axis;              /* Target SkyFrame axis index */
+   int target_swap;              /* Swap target SkyFrame coordinates? */
+
+/* Initialise the returned values. */
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the target SkyFrame structure. */
+   target = (AstSkyFrame *) target_frame;
+
+/* Result is a SkyFrame. */
+/* --------------------- */
+/* Check if the result Frame is to have two axes obtained by selecting
+   both of the target SkyFrame axes, in either order. If so, the
+   result will also be a SkyFrame. */
+   if ( ( result_naxes == 2 ) &&
+        ( ( ( target_axes[ 0 ] == 0 ) && ( target_axes[ 1 ] == 1 ) ) ||
+          ( ( target_axes[ 0 ] == 1 ) && ( target_axes[ 1 ] == 0 ) ) ) ) {
+
+/* If a template has not been supplied, or is the same object as the
+   target, we are simply extracting axes from the supplied SkyFrame. In
+   this case we temporarily force the UseDefs attribute to 1 so that (for
+   instance) the astPickAxes method can function correctly. E.g. if you
+   have a SkyFrame with no set Epoch and UseDefs set zero,  and you try to
+   swap the axes, the attempt would fail because MakeSkyMapping would be
+   unable to determine the Mapping from original to swapped SkyFrame,
+   because of the lack of an Epoch value. */
+      set_usedefs = 0;
+      if( !template || template == target_frame ) {
+         if( !astGetUseDefs( target ) ) {
+            astClearUseDefs( target );
+            set_usedefs = 1;
+         }
+      }
+
+/* Form the result from a copy of the target and then permute its axes
+   into the order required. */
+      *result = astCopy( target );
+      astPermAxes( *result, target_axes );
+
+/* If required, overlay the template attributes on to the result SkyFrame.
+   Also get the system in which to align the two SkyFrames. This is the
+   value of the AlignSystem attribute from the template (if there is a
+   template). */
+      if ( template ) {
+         astOverlay( template, template_axes, *result );
+         align_sys = astGetAlignSystem( template );
+
+      } else {
+         align_sys = astGetAlignSystem( target );
+      }
+
+/* See whether alignment occurs in offset coordinates or absolute
+   coordinates. If the current call to this function is part of the
+   process of restoring a FrameSet's integrity following changes to
+   the FrameSet's current Frame, then we ignore the setting of the
+   AlignOffset attributes and use 0. This ensures that when the System
+   attribute (for instance) is changed via a FrameSet pointer, the
+   Mappings within the FrameSet are modified to produce offsets in the
+   new System. If we are not currently restoring a FrameSet's integrity,
+   then we align in offsets if the template is a SkyFrame and both template
+   and target want alignment to occur in the offset coordinate system. In
+   this case we use a UnitMap to connect them. */
+      if( ( astGetFrameFlags( target_frame ) & AST__INTFLAG ) == 0 ) {
+         if( astGetAlignOffset( target ) &&
+             astGetSkyRefIs( target ) != AST__IGNORED_REF &&
+             template && astIsASkyFrame( template ) ){
+            if( astGetAlignOffset( (AstSkyFrame *) template ) &&
+                astGetSkyRefIs( (AstSkyFrame *) template ) != AST__IGNORED_REF ) {
+               match = 1;
+               *map = (AstMapping *) astUnitMap( 2, "", status );
+            }
+         }
+      }
+
+/* Otherwise, generate a Mapping that takes account of changes in the sky
+   coordinate system (equinox, epoch, etc.) between the target SkyFrame and
+   the result SkyFrame. If this Mapping can be generated, set "match" to
+   indicate that coordinate conversion is possible. */
+      if( ! *map ) {
+         match = ( MakeSkyMapping( target, (AstSkyFrame *) *result,
+                                   align_sys, map, status ) != 0 );
+      }
+
+/* If required, re-instate the original zero value of UseDefs. */
+      if( set_usedefs ) {
+         astSetUseDefs( target, 0 );
+         astSetUseDefs( *result, 0 );
+      }
+
+/* If a Mapping has been obtained, it will expect coordinate values to be
+   supplied in (longitude,latitude) pairs. Test whether we need to swap the
+   order of the target SkyFrame coordinates to conform with this. */
+      if ( astOK && match ) {
+         target_swap = ( astValidateAxis( target, 0, 1, "astSubFrame" ) != 0 );
+
+/* Coordinates will also be delivered in (longitude,latitude) pairs, so check
+   to see whether the result SkyFrame coordinate order should be swapped. */
+         result_swap = ( target_swap != ( target_axes[ 0 ] != 0 ) );
+
+/* If either set of coordinates needs swapping, create a PermMap that
+   will swap a pair of coordinates. */
+         permmap = NULL;
+         if ( target_swap || result_swap ) {
+            perm[ 0 ] = 1;
+            perm[ 1 ] = 0;
+            permmap = astPermMap( 2, perm, 2, perm, NULL, "", status );
+         }
+
+/* If necessary, prefix this PermMap to the main Mapping. */
+         if ( target_swap ) {
+            tmpmap = (AstMapping *) astCmpMap( permmap, *map, 1, "", status );
+            *map = astAnnul( *map );
+            *map = tmpmap;
+         }
+
+/* Also, if necessary, append it to the main Mapping. */
+         if ( result_swap ) {
+            tmpmap = (AstMapping *) astCmpMap( *map, permmap, 1, "", status );
+            *map = astAnnul( *map );
+            *map = tmpmap;
+         }
+
+/* Annul the pointer to the PermMap (if created). */
+         if ( permmap ) permmap = astAnnul( permmap );
+      }
+
+/* Result is not a SkyFrame. */
+/* ------------------------- */
+/* In this case, we select axes as if the target were from the Frame
+   class.  However, since the resulting data will then be separated
+   from their enclosing SkyFrame, default attribute values may differ
+   if the methods for obtaining them were over-ridden by the SkyFrame
+   class. To overcome this, we ensure that these values are explicitly
+   set for the result Frame (rather than relying on their
+   defaults). */
+   } else {
+
+/* Make a temporary copy of the target SkyFrame. We will explicitly
+   set the attribute values in this copy so as not to modify the
+   original. */
+      temp = astCopy( target );
+
+/* Define a macro to test if an attribute is set. If not, set it
+   explicitly to its default value. */
+#define SET(attribute) \
+   if ( !astTest##attribute( temp ) ) { \
+      astSet##attribute( temp, astGet##attribute( temp ) ); \
+   }
+
+/* Set attribute values which apply to the Frame as a whole and which
+   we want to retain, but whose defaults are over-ridden by the
+   SkyFrame class. */
+      SET(Domain)
+      SET(Title)
+
+/* Now loop to set explicit attribute values for each axis. */
+      for ( target_axis = 0; target_axis < 2; target_axis++ ) {
+
+/* Define a macro to test if an axis attribute is set. If not, set it
+   explicitly to its default value. */
+#define SET_AXIS(attribute) \
+   if ( !astTest##attribute( temp, target_axis ) ) { \
+      astSet##attribute( temp, target_axis, \
+                         astGet##attribute( temp, target_axis ) ); \
+   }
+
+/* Use this macro to set explicit values for all the axis attributes
+   for which the SkyFrame class over-rides the default value. */
+         SET_AXIS(AsTime)
+         SET_AXIS(Format)
+         SET_AXIS(Label)
+         SET_AXIS(Symbol)
+         SET_AXIS(Unit)
+
+/* Now handle axis attributes for which there are no SkyFrame access
+   methods.  For these we require a pointer to the temporary
+   SkyFrame's Axis object. */
+         ax = astGetAxis( temp, target_axis );
+
+/* Set an explicit value for the IsLatitude and CentreZero attributes. */
+         if( astValidateAxis( temp, target_axis, 1, "astSubFrame" ) == 1 ) {
+            astSetAxisIsLatitude( ax, 1 );
+            astSetAxisCentreZero( ax, 1 );
+
+         } else {
+            astSetAxisIsLatitude( ax, 0 );
+            astSetAxisCentreZero( ax, astGetNegLon( temp ) );
+         }
+
+/* Annul the Axis object pointer. */
+         ax = astAnnul( ax );
+      }
+
+/* Clear attributes which have an extended range of values allowed by
+   this class. */
+      astClearSystem( temp );
+      astClearAlignSystem( temp );
+
+/* Invoke the astSubFrame method inherited from the Frame class to
+   produce the result Frame by selecting the required set of axes and
+   overlaying the template Frame's attributes. */
+      match = (*parent_subframe)( (AstFrame *) temp, template,
+                                  result_naxes, target_axes, template_axes,
+                                  map, result, status );
+
+/* Delete the temporary copy of the target SkyFrame. */
+      temp = astDelete( temp );
+   }
+
+/* Ensure the returned Frame does not have active units. */
+   astSetActiveUnit( *result, 0 );
+
+/* If an error occurred or no match was found, annul the returned
+   objects and reset the returned result. */
+   if ( !astOK || !match ) {
+      if( *map ) *map = astAnnul( *map );
+      if( *result ) *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+
+/* Undefine macros local to this function. */
+#undef SET
+#undef SET_AXIS
+}
+
+static AstSystemType SystemCode( AstFrame *this, const char *system, int *status ) {
+/*
+*  Name:
+*     SystemCode
+
+*  Purpose:
+*     Convert a string into a coordinate system type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     AstSystemType SystemCode( AstFrame *this, const char *system, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astSystemCode method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a string used for the external
+*     description of a sky coordinate system into a SkyFrame
+*     coordinate system type code (System attribute value). It is the
+*     inverse of the astSystemString function.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     system
+*        Pointer to a constant null-terminated string containing the
+*        external description of the sky coordinate system.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System type code.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM is returned if the sky coordinate
+*     system description was not recognised. This does not produce an
+*     error.
+*     - A value of AST__BADSYSTEM is also returned if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;      /* Result value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" string against each possibility and assign the
+   result. */
+   if ( astChrMatch( "FK4", system ) ) {
+      result = AST__FK4;
+
+   } else if ( astChrMatch( "FK4_NO_E", system ) ||
+               astChrMatch( "FK4-NO-E", system ) ) {
+      result = AST__FK4_NO_E;
+
+   } else if ( astChrMatch( "FK5", system ) ||
+               astChrMatch( "Equatorial", system ) ) {
+      result = AST__FK5;
+
+   } else if ( astChrMatch( "J2000", system ) ) {
+      result = AST__J2000;
+
+   } else if ( astChrMatch( "ICRS", system ) ) {
+      result = AST__ICRS;
+
+   } else if ( astChrMatch( "AZEL", system ) ) {
+      result = AST__AZEL;
+
+   } else if ( astChrMatch( "GAPPT", system ) ||
+               astChrMatch( "GEOCENTRIC", system ) ||
+               astChrMatch( "APPARENT", system ) ) {
+         result = AST__GAPPT;
+
+   } else if ( astChrMatch( "ECLIPTIC", system ) ) {
+      result = AST__ECLIPTIC;
+
+   } else if ( astChrMatch( "HELIOECLIPTIC", system ) ) {
+      result = AST__HELIOECLIPTIC;
+
+   } else if ( astChrMatch( "GALACTIC", system ) ) {
+      result = AST__GALACTIC;
+
+   } else if ( astChrMatch( "SUPERGALACTIC", system ) ) {
+      result = AST__SUPERGALACTIC;
+
+   } else if ( astChrMatch( "UNKNOWN", system ) ) {
+      result = AST__UNKNOWN;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *SystemString( AstFrame *this, AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SystemString
+
+*  Purpose:
+*     Convert a coordinate system type code into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     const char *SystemString( AstFrame *this, AstSystemType system, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astSystemString method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a SkyFrame coordinate system type code
+*     (System attribute value) into a string suitable for use as an
+*     external representation of the coordinate system type.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the sky coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" value against each possibility and convert to a
+   string pointer. (Where possible, return the same string as would be
+   used in the FITS WCS representation of the coordinate system). */
+   switch ( system ) {
+   case AST__FK4:
+      result = "FK4";
+      break;
+
+   case AST__FK4_NO_E:
+      result = "FK4-NO-E";
+      break;
+
+   case AST__FK5:
+      result = "FK5";
+      break;
+
+   case AST__J2000:
+      result = "J2000";
+      break;
+
+   case AST__ICRS:
+      result = "ICRS";
+      break;
+
+   case AST__GAPPT:
+      result = "GAPPT";
+      break;
+
+   case AST__AZEL:
+      result = "AZEL";
+      break;
+
+   case AST__ECLIPTIC:
+      result = "ECLIPTIC";
+      break;
+
+   case AST__HELIOECLIPTIC:
+      result = "HELIOECLIPTIC";
+      break;
+
+   case AST__GALACTIC:
+      result = "GALACTIC";
+      break;
+
+   case AST__SUPERGALACTIC:
+      result = "SUPERGALACTIC";
+      break;
+
+   case AST__UNKNOWN:
+      result = "Unknown";
+      break;
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static int TestActiveUnit( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     TestActiveUnit
+
+*  Purpose:
+*     Test the ActiveUnit flag for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int TestActiveUnit( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astTestActiveUnit protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function test the value of the ActiveUnit flag for a SkyFrame,
+*    which is always "unset".
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The result of the test (0).
+
+*/
+   return 0;
+}
+
+static int TestAsTime( AstSkyFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     TestAsTime
+
+*  Purpose:
+*     Determine if a value has been set for a SkyFrame's AsTime attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int TestAsTime( AstSkyFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function returns a boolean value to indicate if a value has
+*     previously been set for the AsTime attribute for a specified axis of a
+*     SkyFrame. This attribute indicates whether axis values should be
+*     formatted as times (as opposed to angles) by default.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        Index of the axis for which information is required (zero based).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero or one, according to whether the AsTime attribute has been set.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables. */
+   AstAxis *ax;                  /* Pointer to Axis object */
+   int result;                   /* Result to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astTestAsTime" );
+
+/* Obtain a pointer to the Axis object. */
+   ax = astGetAxis( this, axis );
+
+/* Determine if the AsTime attribute has been set for it (it cannot have been
+   set unless the object is a SkyAxis). */
+   result = ( astIsASkyAxis( ax ) && astTestAxisAsTime( ax ) );
+
+/* Annul the Axis pointer. */
+   ax = astAnnul( ax );
+
+/* Return the result. */
+   return result;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a SkyFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astTestAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a SkyFrame's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   int axis;                     /* SkyFrame axis number */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* No. characters read by astSscanf */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* AsTime(axis). */
+/* ------------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( attrib, "astime(%d)%n", &axis, &nc ) )
+        && ( nc >= len ) ) {
+      result = astTestAsTime( this, axis - 1 );
+
+/* Equinox. */
+/* -------- */
+   } else if ( !strcmp( attrib, "equinox" ) ) {
+      result = astTestEquinox( this );
+
+/* NegLon. */
+/* ------- */
+   } else if ( !strcmp( attrib, "neglon" ) ) {
+      result = astTestNegLon( this );
+
+/* SkyTol. */
+/* ------- */
+   } else if ( !strcmp( attrib, "skytol" ) ) {
+      result = astTestSkyTol( this );
+
+/* Projection. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "projection" ) ) {
+      result = astTestProjection( this );
+
+/* SkyRefIs. */
+/* --------- */
+   } else if ( !strcmp( attrib, "skyrefis" ) ) {
+      result = astTestSkyRefIs( this );
+
+/* SkyRef. */
+/* ------- */
+   } else if ( !strcmp( attrib, "skyref" ) ) {
+      result = astTestSkyRef( this, 0 ) || astTestSkyRef( this, 1 );
+
+/* SkyRef(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( attrib, "skyref(%d)%n", &axis, &nc ) )
+        && ( nc >= len ) ) {
+      result = astTestSkyRef( this, axis - 1 );
+
+/* SkyRefP. */
+/* -------- */
+   } else if ( !strcmp( attrib, "skyrefp" ) ) {
+      result = astTestSkyRefP( this, 0 ) || astTestSkyRefP( this, 1 );
+
+/* SkyRefP(axis). */
+/* ------------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( attrib, "skyrefp(%d)%n", &axis, &nc ) )
+        && ( nc >= len ) ) {
+      result = astTestSkyRefP( this, axis - 1 );
+
+/* AlignOffset */
+/* ----------- */
+   } else if ( !strcmp( attrib, "alignoffset" ) ) {
+      result = astTestAlignOffset( this );
+
+/* If the name is not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then return
+   zero. */
+   } else if ( !strncmp( attrib, "islataxis", 9 ) ||
+               !strncmp( attrib, "islonaxis", 9 ) ||
+               !strcmp( attrib, "lataxis" ) ||
+               !strcmp( attrib, "lonaxis" ) ) {
+      result = 0;
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static int TestSlaUnit( AstSkyFrame *sf1, AstSkyFrame *sf2, AstSlaMap *slamap,
+                        int *status ){
+/*
+*  Name:
+*     Unformat
+
+*  Purpose:
+*     See if a slamap is effectively a unit mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int TestSlaUnit( AstSkyFrame *sf1, AstSkyFrame *sf2, AstSlaMap *slamap,
+*                      int *status )
+
+*  Class Membership:
+*     SkyFrame member function.
+
+*  Description:
+*     This function tests a SlaMap to see if it is effectively a unit
+*     transformatuon to within a tolerance given by the smaller tolerance
+*     of the two supplied SkyFrames.
+
+*  Parameters:
+*     sf1
+*        Pointer to the first SkyFrame.
+*     sf2
+*        Pointer to the second SkyFrame (may be NULL)
+*     slamap
+*        Pointer to the SlaMap to test.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the SlaMap is effectively a unit mapping, and zero
+*     otherwise.
+
+*/
+
+/*  Number of test points. */
+#define NTEST 14
+
+/* Local Variables: */
+   double maxshift;            /* Max. shift produced by slamap (rads) */
+   double olat[NTEST];         /* Transformed latitudes */
+   double olon[NTEST];         /* Transformed longitudes */
+   double shift;               /* Shift produced by slamap (rads) */
+   double tol2;                /* Second tolerance (in radians) */
+   double tol;                 /* Used tolerance (in radians) */
+   int i;                      /* Loop count */
+   int result;                 /* Returned flag */
+
+/* A grid of lon/lat points covering the sphere. */
+   double lat[ NTEST ] = { 0.0,  0.0,  0.0,  0.0,
+                           0.8,  0.8,  0.8,  0.8,
+                          -0.8, -0.8, -0.8, -0.8,
+                           1.570796, -1.570796 };
+   double lon[ NTEST ] = { 0.0,  1.57,  3.14,  4.71,
+                           0.8,  2.37,  3.94,  5.51,
+                           0.8,  2.37,  3.94,  5.51,
+                           0.0, 0.0 };
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the SlaMap is empty (i.e. has no conversions in it), then is it a
+   UnitMap. So save time by not transforming the test values. */
+   if( astSlaIsEmpty( slamap ) ) {
+      result = 1;
+
+/* Otherwise, get the smaller of the tolerances associated with the
+   supplied SkyFrames, in radians. */
+   } else {
+      tol = astGetSkyTol( sf1 );
+      if( sf2 ) {
+         tol2 = astGetSkyTol( sf2 );
+         if( tol2 < tol ) tol = tol2;
+      }
+
+/* If the tolerance is zero, there is no need to do the test. */
+      if( tol > 0.0 ) {
+
+/* Transform the test point using the SlaMap. */
+         astTran2( slamap, NTEST, lon, lat, 1, olon, olat );
+
+/* Find the maximum shift produced by the SlaMap at any of the test
+   positions. Again, to avoid the slow-down produced by checking for
+   axis permutation, use palDsep rather than astDistance. */
+         maxshift = 0.0;
+         for( i = 0; i < NTEST; i++ ) {
+            shift = palDsep( lon[ i ], lat[ i ], olon[ i ], olat[ i ] );
+            if( shift > maxshift ) maxshift = shift;
+         }
+
+/* Convert the max shift to arc-seconds and do the check. */
+         result = ( maxshift*AST__DR2D*3600 < tol );
+      }
+   }
+
+   return result;
+}
+
+static int Unformat( AstFrame *this_frame, int axis, const char *string,
+                     double *value, int *status ) {
+/*
+*  Name:
+*     Unformat
+
+*  Purpose:
+*     Read a formatted coordinate value for a SkyFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     int Unformat( AstFrame *this, int axis, const char *string,
+*                   double *value, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the public astUnformat
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function reads a formatted coordinate value for a SkyFrame
+*     axis (supplied as a string) and returns the equivalent numerical
+*     value as a double. It also returns the number of characters read
+*     from the string.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame.
+*     axis
+*        The number of the SkyFrame axis for which the coordinate
+*        value is to be read (axis numbering starts at zero for the
+*        first axis).
+*     string
+*        Pointer to a constant null-terminated string containing the
+*        formatted coordinate value.
+*     value
+*        Pointer to a double in which the coordinate value read will
+*        be returned (in radians).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of characters read from the string to obtain the
+*     coordinate value.
+
+*  Notes:
+*     - Any white space at the beginning of the string will be
+*     skipped, as also will any trailing white space following the
+*     coordinate value read. The function's return value will reflect
+*     this.
+*     - A function value of zero (and no coordinate value) will be
+*     returned, without error, if the string supplied does not contain
+*     a suitably formatted value.
+*     - The string "<bad>" is recognised as a special case and will
+*     generate the value AST__BAD, without error. The test for this
+*     string is case-insensitive and permits embedded white space.
+*     - A function result of zero will be returned and no coordinate
+*     value will be returned via the "value" pointer if this function
+*     is invoked with the global error status set, or if it should
+*     fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   double coord;                 /* Coordinate value read */
+   int format_set;               /* Format attribute set? */
+   int nc;                       /* Number of characters read */
+
+/* Initialise. */
+   nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return nc;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astUnformat" );
+
+/* Determine if a Format value has been set for the axis and set a
+   temporary value if it has not. Use the GetFormat member function
+   for this class together with member functions inherited from the
+   parent class (rather than using the object's methods directly)
+   because if any of these methods have been over-ridden by a derived
+   class the Format string syntax may no longer be compatible with
+   this class. */
+   format_set = (*parent_testformat)( this_frame, axis, status );
+   if ( !format_set ) {
+      (*parent_setformat)( this_frame, axis, GetFormat( this_frame, axis, status ), status );
+   }
+
+/* Use the Unformat member function inherited from the parent class to
+   read the coordinate value. */
+   nc = (*parent_unformat)( this_frame, axis, string, &coord, status );
+
+/* If necessary, clear any temporary Format value that was set above. */
+   if ( !format_set ) (*parent_clearformat)( this_frame, axis, status );
+
+/* If an error occurred, clear the number of characters read. */
+   if ( !astOK ) {
+      nc = 0;
+
+/* Otherwise, if characters were read, return the coordinate value. */
+   } else if ( nc ) {
+      *value = coord;
+   }
+
+/* Return the number of characters read. */
+   return nc;
+}
+
+static int ValidateSystem( AstFrame *this, AstSystemType system, const char *method, int *status ) {
+/*
+*
+*  Name:
+*     ValidateSystem
+
+*  Purpose:
+*     Validate a value for a Frame's System attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int ValidateSystem( AstFrame *this, AstSystemType system,
+*                         const char *method, int *status )
+
+*  Class Membership:
+*     SkyFrame member function (over-rides the astValidateSystem method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of the supplied system value.
+*     If the value is valid, it is returned unchanged. Otherwise, an
+*     error is reported and a value of AST__BADSYSTEM is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The system value to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The validated system value.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;              /* Validated system value */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the value is out of bounds, report an error. */
+   if ( system < FIRST_SYSTEM || system > LAST_SYSTEM ) {
+         astError( AST__AXIIN, "%s(%s): Bad value (%d) given for the System "
+                   "or AlignSystem attribute of a %s.", status, method,
+                   astGetClass( this ), (int) system, astGetClass( this ) );
+
+/* Otherwise, return the supplied value. */
+   } else {
+      result = system;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void VerifyMSMAttrs( AstSkyFrame *target, AstSkyFrame *result,
+                            int which, const char *attrs, const char *method, int *status ) {
+/*
+*  Name:
+*     VerifyMSMAttrs
+
+*  Purpose:
+*     Verify that usable attribute values are available.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*      void VerifyMSMAttrs( AstSkyFrame *target, AstSkyFrame *result,
+*                           int which, const char *attrs, const char *method, int *status )
+
+*  Class Membership:
+*     SkyFrame member function
+
+*  Description:
+*     This function tests each attribute listed in "attrs". It returns
+*     without action if 1) an explicit value has been set for each attribute
+*     in the SkyFrame indicated by "which" or 2) the UseDefs attribute of the
+*     "which" SkyFrame is non-zero.
+*
+*     If UseDefs is zero (indicating that default values should not be
+*     used for attributes), and any of the named attributes does not have
+*     an explicitly set value, then an error is reported.
+*
+*     The displayed error message assumes that tjis function was called
+*     as part of the process of producing a Mapping from "target" to "result".
+
+*  Parameters:
+*     target
+*        Pointer to the target SkyFrame.
+*     result
+*        Pointer to the result SkyFrame.
+*     which
+*        If 2, both the target and result SkyFrames are checked for the
+*        supplied attributes. If less than 2, only the target SkyFrame is
+*        checked. If greater than 2, only the result SkyFrame is checked.
+*     attrs
+*        A string holding a space separated list of attribute names.
+*     method
+*        A string holding the name of the calling method for use in error
+*        messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   const char *a;
+   const char *p;
+   const char *desc;
+   int len;
+   int set1;
+   int set2;
+   int state;
+   int usedef1;
+   int usedef2;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Get the UseDefs attributes of the two SkyFrames. */
+   usedef1 = astGetUseDefs( target );
+   usedef2 = astGetUseDefs( result );
+
+/* If both SkyFrames have a non-zero value for its UseDefs attribute, then
+   all attributes are assumed to have usable values, since the defaults
+   will be used if no explicit value has been set. So we only need to do
+   any checks if UseDefs is zero for either SkyFrame. */
+   if( !usedef1 || !usedef2 ) {
+
+/* Stop compiler warnings about uninitialised variables */
+      a = NULL;
+      desc = NULL;
+      len = 0;
+      set1 = 0;
+      set2 = 0;
+
+/* Loop round the "attrs" string identifying the start and length of each
+   non-blank word in the string. */
+      state = 0;
+      p = attrs;
+      while( 1 ) {
+         if( state == 0 ) {
+            if( !isspace( *p ) ) {
+               a = p;
+               len = 1;
+               state = 1;
+            }
+         } else {
+            if( isspace( *p ) || !*p ) {
+
+/* The end of a word has just been reached. Compare it to each known
+   attribute value. Get a flag indicating if the attribute has a set
+   value, and a string describing the attribute.*/
+               if( len > 0 ) {
+
+                  if( !strncmp( "Equinox", a, len ) ) {
+                     set1 = astTestEquinox( target );
+                     set2 = astTestEquinox( result );
+                     desc = "reference equinox";
+
+                  } else if( !strncmp( "Dut1", a, len ) ) {
+                     set1 = astTestDut1( target );
+                     set2 = astTestDut1( result );
+                     desc = "UT1-UTC correction";
+
+                  } else if( !strncmp( "Epoch", a, len ) ) {
+                     set1 = astTestEpoch( target );
+                     set2 = astTestEpoch( result );
+                     desc = "epoch of observation";
+
+                  } else if( !strncmp( "ObsLon", a, len ) ) {
+                     set1 = astTestObsLon( target );
+                     set2 = astTestObsLon( result );
+                     desc = "longitude of observer";
+
+                  } else if( !strncmp( "ObsLat", a, len ) ) {
+                     set1 = astTestObsLat( target );
+                     set2 = astTestObsLat( result );
+                     desc = "latitude of observer";
+
+                  } else if( !strncmp( "ObsAlt", a, len ) ) {
+                     set1 = astTestObsAlt( target );
+                     set2 = astTestObsAlt( result );
+                     desc = "altitude of observer";
+
+                  } else {
+                     astError( AST__INTER, "VerifyMSMAttrs(SkyFrame): "
+                               "Unknown attribute name \"%.*s\" supplied (AST "
+                               "internal programming error).", status, len, a );
+                  }
+
+/* If the attribute is not set in the target but should be, report an
+   error. */
+                  if( !usedef1 && !set1 && which < 3 ) {
+                     astClearTitle( target );
+                     astClearTitle( result );
+                     astError( AST__NOVAL, "%s(%s): Cannot convert "
+                               "celestial coordinates from %s to %s.", status,
+                               method, astGetClass( target ),
+                               astGetC( target, "Title" ),
+                               astGetC( result, "Title" ) );
+                     astError( AST__NOVAL, "No value has been set for "
+                               "the \"%.*s\" attribute (%s) in the input %s.", status,
+                               len, a, desc, astGetClass( target ) );
+                     break;
+                  }
+
+/* If the attribute is not set in the result but should be, report an
+   error. */
+                  if( !usedef2 && !set2 && which > 1 ) {
+                     astClearTitle( target );
+                     astClearTitle( result );
+                     astError( AST__NOVAL, "%s(%s): Cannot convert "
+                               "celestial coordinates from %s to %s.", status,
+                               method, astGetClass( result ),
+                               astGetC( target, "Title" ),
+                               astGetC( result, "Title" ) );
+                     astError( AST__NOVAL, "No value has been set for "
+                               "the \"%.*s\" attribute (%s) in the output %s.", status,
+                               len, a, desc, astGetClass( result ) );
+                     break;
+                  }
+
+/* Continue the word search algorithm. */
+               }
+               len = 0;
+               state = 0;
+            } else {
+               len++;
+            }
+         }
+         if( !*(p++) ) break;
+      }
+   }
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/*
+*att++
+*  Name:
+*     AlignOffset
+
+*  Purpose:
+*     Align SkyFrames using the offset coordinate system?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which controls how a SkyFrame
+*     behaves when it is used (by
+c     astFindFrame or astConvert) as a template to match another (target)
+f     AST_FINDFRAME or AST_CONVERT) as a template to match another (target)
+*     SkyFrame. It determines the coordinate system in which the two
+*     SkyFrames are aligned if a match occurs.
+*
+*     If the template and target SkyFrames both have defined offset coordinate
+*     systems (i.e. the SkyRefIs attribute is set to either "Origin" or "
+*     Pole"), and they both have a non-zero value for AlignOffset, then
+*     alignment occurs within the offset coordinate systems (that is, a
+*     UnitMap will always be used to align the two SkyFrames). If either
+*     the template or target SkyFrame has zero (the default value) for
+*     AlignOffset, or if either SkyFrame has SkyRefIs set to "Ignored", then
+*     alignment occurring within the coordinate system specified by the
+*     AlignSystem attribute.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+*att--
+*/
+astMAKE_CLEAR(SkyFrame,AlignOffset,alignoffset,-INT_MAX)
+astMAKE_GET(SkyFrame,AlignOffset,int,0,( ( this->alignoffset != -INT_MAX ) ?
+                                           this->alignoffset : 0 ))
+astMAKE_SET(SkyFrame,AlignOffset,int,alignoffset,( value != 0 ))
+astMAKE_TEST(SkyFrame,AlignOffset,( this->alignoffset != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     AsTime(axis)
+
+*  Purpose:
+*     Format celestal coordinates as times?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute specifies the default style of formatting to be
+c     used (e.g. by astFormat) for the celestial coordinate values
+f     used (e.g. by AST_FORMAT) for the celestial coordinate values
+*     described by a SkyFrame. It takes a separate boolean value for
+*     each SkyFrame axis so that, for instance, the setting
+*     "AsTime(2)=0" specifies the default formatting style for
+*     celestial latitude values.
+*
+*     If the AsTime attribute for a SkyFrame axis is zero, then
+*     coordinates on that axis will be formatted as angles by default
+*     (using degrees, minutes and seconds), otherwise they will be
+*     formatted as times (using hours, minutes and seconds).
+*
+*     The default value of AsTime is chosen according to the sky
+*     coordinate system being represented, as determined by the
+*     SkyFrame's System attribute. This ensures, for example, that
+*     right ascension values will be formatted as times by default,
+*     following normal conventions.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+
+*  Notes:
+*     - The AsTime attribute operates by changing the default value of
+*     the corresponding Format(axis) attribute. This, in turn, may
+*     also affect the value of the Unit(axis) attribute.
+*     - Only the default style of formatting is affected by the AsTime
+*     value. If an explicit Format(axis) value is set, it will
+*     over-ride any effect from the AsTime attribute.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     Equinox
+
+*  Purpose:
+*     Epoch of the mean equinox.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute is used to qualify those celestial coordinate
+*     systems described by a SkyFrame which are notionally based on
+*     the ecliptic (the plane of the Earth's orbit around the Sun)
+*     and/or the Earth's equator.
+*
+*     Both of these planes are in motion and their positions are
+*     difficult to specify precisely. In practice, therefore, a model
+*     ecliptic and/or equator are used instead. These, together with
+*     the point on the sky that defines the coordinate origin (the
+*     intersection of the two planes termed the "mean equinox") move
+*     with time according to some model which removes the more rapid
+*     fluctuations. The SkyFrame class supports both the FK4 and
+*     FK5 models.
+*
+*     The position of a fixed source expressed in any of these
+*     coordinate systems will appear to change with time due to
+*     movement of the coordinate system itself (rather than motion of
+*     the source).  Such coordinate systems must therefore be
+*     qualified by a moment in time (the "epoch of the mean equinox"
+*     or "equinox" for short) which allows the position of the model
+*     coordinate system on the sky to be determined. This is the role
+*     of the Equinox attribute.
+*
+*     The Equinox attribute is stored as a Modified Julian Date, but
+*     when setting or getting its value you may use the same formats
+*     as for the Epoch attribute (q.v.).
+*
+*     The default Equinox value is B1950.0 (Besselian) for the old
+*     FK4-based coordinate systems (see the System attribute) and
+*     J2000.0 (Julian) for all others.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+
+*  Notes:
+*     - Care must be taken to distinguish the Equinox value, which
+*     relates to the definition of a time-dependent coordinate system
+*     (based on solar system reference planes which are in motion),
+*     from the superficially similar Epoch value. The latter is used
+*     to qualify coordinate systems where the positions of sources
+*     change with time (or appear to do so) for a variety of other
+*     reasons, such as aberration of light caused by the observer's
+*     motion, etc.
+*     - See the description of the System attribute for details of
+*     which qualifying attributes apply to each celestial coordinate
+*     system.
+*att--
+*/
+/* Clear the Equinox value by setting it to AST__BAD. */
+astMAKE_CLEAR(SkyFrame,Equinox,equinox,AST__BAD)
+
+/* Provide a default value of B1950.0 or J2000.0 depending on the System
+   setting. */
+astMAKE_GET(SkyFrame,Equinox,double,AST__BAD,(
+            ( this->equinox != AST__BAD ) ? this->equinox :
+               ( ( ( astGetSystem( this ) == AST__FK4 ) ||
+                   ( astGetSystem( this ) == AST__FK4_NO_E ) ) ?
+                    palEpb2d( 1950.0 ) : palEpj2d( 2000.0 ) ) ))
+
+/* Allow any Equinox value to be set, unless the System is Helio-ecliptic
+   (in which case clear the value so that J2000 is used). */
+astMAKE_SET(SkyFrame,Equinox,double,equinox,((astGetSystem(this)!=AST__HELIOECLIPTIC)?value:AST__BAD))
+
+/* An Equinox value is set if it is not equal to AST__BAD. */
+astMAKE_TEST(SkyFrame,Equinox,( this->equinox != AST__BAD ))
+
+
+/*
+*att++
+*  Name:
+*     IsLatAxis(axis)
+
+*  Purpose:
+*     Is the specified celestial axis a latitude axis?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean), read-only.
+
+*  Description:
+*     This is a read-only boolean attribute that indicates the nature of
+*     the specified axis. The attribute has a non-zero value if the
+*     specified axis is a celestial latitude axis (Declination, Galactic
+*     latitude, etc), and is zero otherwise.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+
+*  Notes:
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the SkyFrame axis to be tested.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     IsLonAxis(axis)
+
+*  Purpose:
+*     Is the specified celestial axis a longitude axis?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean), read-only.
+
+*  Description:
+*     This is a read-only boolean attribute that indicates the nature of
+*     the specified axis. The attribute has a non-zero value if the
+*     specified axis is a celestial longitude axis (Right Ascension, Galactic
+*     longitude, etc), and is zero otherwise.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+
+*  Notes:
+*     - When specifying this attribute by name, it should be
+*     subscripted with the number of the SkyFrame axis to be tested.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     LatAxis
+
+*  Purpose:
+*     Index of the latitude axis.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This read-only attribute gives the index (1 or 2) of the latitude
+*     axis within the SkyFrame (taking into account any current axis
+*     permutations).
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     LonAxis
+
+*  Purpose:
+*     Index of the longitude axis.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This read-only attribute gives the index (1 or 2) of the longitude
+*     axis within the SkyFrame (taking into account any current axis
+*     permutations).
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     NegLon
+
+*  Purpose:
+*     Display negative longitude values?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which controls how longitude values
+c     are normalized for display by astNorm.
+f     are normalized for display by AST_NORM.
+*
+*     If the NegLon attribute is zero, then normalized
+*     longitude values will be in the range zero to 2.pi. If NegLon is
+*     non-zero, then normalized longitude values will be in the range -pi
+*     to pi.
+*
+*     The default value depends on the current value of the SkyRefIs
+*     attribute, If SkyRefIs has a value of "Origin", then the default for
+*     NegLon is one, otherwise the default is zero.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+*att--
+*/
+/* Clear the NegLon value by setting it to -INT_MAX. */
+astMAKE_CLEAR(SkyFrame,NegLon,neglon,-INT_MAX)
+
+/* Supply a default of 0 for absolute coords and 1 for offset coords if
+   no NegLon value has been set. */
+astMAKE_GET(SkyFrame,NegLon,int,0,( ( this->neglon != -INT_MAX ) ?
+this->neglon : (( astGetSkyRefIs( this ) == AST__ORIGIN_REF )? 1 : 0)))
+
+/* Set a NegLon value of 1 if any non-zero value is supplied. */
+astMAKE_SET(SkyFrame,NegLon,int,neglon,( value != 0 ))
+
+/* The NegLon value is set if it is not -INT_MAX. */
+astMAKE_TEST(SkyFrame,NegLon,( this->neglon != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     SkyTol
+
+*  Purpose:
+*     The smallest significant shift in sky coordinates.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute indicates the accuracy of the axis values that will
+*     be represented by the SkyFrame. If the arc-distance between two
+*     positions within the SkyFrame is smaller than the value of SkyTol,
+*     then the two positions will (for the puposes indicated below) be
+*     considered to be co-incident.
+*
+*     This value is used only when constructing the Mapping between
+*     two different SkyFrames (for instance, when calling
+c     astConvert or astFindFrame).
+f     AST_CONVERT or AST_FINDFRAME).
+*     If the transformation between the two SkyFrames causes positions to
+*     shift by less than SkyTol arc-seconds, then the transformation is
+*     replaced by a UnitMap.  This could in certain circumatances allow
+*     major simplifications to be made to the transformation between
+*     any pixel grids associated with the two SkyFrames (for instance, if
+*     each SkyFrame is part of the WCS FrameSet associated with an image).
+*
+*     A common case is when two SkyFrames use the FK5 system, but have
+*     slightly different Epoch values. If the AlignSystem attribute has
+*     its default value of "ICRS", then the transformation between the
+*     two SkyFrames will include a very small rotation (FK5 rotates with
+*     respect to ICRS as a rate of about 0.0005 arc-seconds per year). In
+*     most circumstances such a small rotation is insignificant. Setting
+*     SkyTol to some suitably small non-zero value will cause this
+*     rotation to be ignored, allowing much simpler transformations to
+*     be used.
+*
+*     The test to determine the shift introduced by transforming between
+*     the two SkyFrames is performed by transforming a set of 14 position
+*     spread evenly over the whole sky. The largest shift produced at any
+*     of these 14 positions is compared to the value of SkyTol.
+*
+*     The SkyTol value is in units of arc-seconds, and the default value
+*     is 0.001.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+*att--
+*/
+astMAKE_CLEAR(SkyFrame,SkyTol,skytol,AST__BAD)
+astMAKE_GET(SkyFrame,SkyTol,double,0.001,((this->skytol!=AST__BAD)?this->skytol:0.001))
+astMAKE_SET(SkyFrame,SkyTol,double,skytol,fabs(value))
+astMAKE_TEST(SkyFrame,SkyTol,(this->skytol!=AST__BAD))
+
+/*
+*att++
+*  Name:
+*     Projection
+
+*  Purpose:
+*     Sky projection description.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute provides a place to store a description of the
+*     type of sky projection used when a SkyFrame is attached to a
+*     2-dimensional object, such as an image or plotting surface. For
+*     example, typical values might be "orthographic", "Hammer-Aitoff"
+*     or "cylindrical equal area".
+*
+*     The Projection value is purely descriptive and does not affect
+*     the celestial coordinate system represented by the SkyFrame in
+*     any way. If it is set to a non-blank string, the description
+*     provided may be used when forming the default value for the
+*     SkyFrame's Title attribute (so that typically it will appear in
+*     graphical output, for instance). The default value is an empty
+*     string.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+*att--
+*/
+/* Clear the Projection value by freeing the allocated memory and
+   assigning a NULL pointer. */
+astMAKE_CLEAR(SkyFrame,Projection,projection,astFree( this->projection ))
+
+/* If the Projection value is not set, return a pointer to an empty
+   string. */
+astMAKE_GET(SkyFrame,Projection,const char *,NULL,( this->projection ?
+                                                    this->projection : "" ))
+
+/* Set a Projection value by freeing any previously allocated memory,
+   allocating new memory, storing the string and saving the pointer to
+   the copy. */
+astMAKE_SET(SkyFrame,Projection,const char *,projection,astStore(
+                     this->projection, value, strlen( value ) + (size_t) 1 ))
+
+/* The Projection value is set if the pointer to it is not NULL. */
+astMAKE_TEST(SkyFrame,Projection,( this->projection != NULL ))
+
+/*
+*att++
+*  Name:
+*     SkyRefIs
+
+*  Purpose:
+*     Selects the nature of the offset coordinate system.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute controls how the values supplied for the SkyRef and
+*     SkyRefP attributes are used. These three attributes together allow
+*     a SkyFrame to represent offsets relative to some specified origin
+*     or pole within the coordinate system specified by the System attribute,
+*     rather than absolute axis values. SkyRefIs can take one of the
+*     case-insensitive values "Origin", "Pole" or "Ignored".
+*
+*     If SkyRefIs is set to "Origin", then the coordinate system
+*     represented by the SkyFrame is modified to put the origin of longitude
+*     and latitude at the position specified by the SkyRef attribute.
+*
+*     If SkyRefIs is set to "Pole", then the coordinate system represented
+*     by the SkyFrame is modified to put the north pole at the position
+*     specified by the SkyRef attribute.
+*
+*     If SkyRefIs is set to "Ignored" (the default), then any value set for the
+*     SkyRef attribute is ignored, and the SkyFrame represents the coordinate
+*     system specified by the System attribute directly without any rotation.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+
+*att--
+*/
+astMAKE_CLEAR(SkyFrame,SkyRefIs,skyrefis,AST__BAD_REF)
+astMAKE_SET(SkyFrame,SkyRefIs,int,skyrefis,value)
+astMAKE_TEST(SkyFrame,SkyRefIs,( this->skyrefis != AST__BAD_REF ))
+astMAKE_GET(SkyFrame,SkyRefIs,int,AST__IGNORED_REF,(this->skyrefis == AST__BAD_REF ? AST__IGNORED_REF : this->skyrefis))
+
+/*
+*att++
+*  Name:
+*     SkyRef(axis)
+
+*  Purpose:
+*     Position defining the offset coordinate system.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute allows a SkyFrame to represent offsets, rather than
+*     absolute axis values, within the coordinate system specified by the
+*     System attribute. If supplied, SkyRef should be set to hold the
+*     longitude and latitude of a point within the coordinate system
+*     specified by the System attribute. The coordinate system represented
+*     by the SkyFrame will then be rotated in order to put the specified
+*     position at either the pole or the origin of the new coordinate system
+*     (as indicated by the SkyRefIs attribute). The orientation of the
+*     modified coordinate system is then controlled using the SkyRefP
+*     attribute.
+*
+*     If an integer axis index is included in the attribute name (e.g.
+*     "SkyRef(1)") then the attribute value should be supplied as a single
+*     floating point axis value, in radians, when setting a value for the
+*     attribute, and will be returned in the same form when getting the value
+*     of the attribute. In this case the integer axis index should be "1"
+*     or "2" (the values to use for longitude and latitude axes are
+*     given by the LonAxis and LatAxis attributes).
+*
+*     If no axis index is included in the attribute name (e.g. "SkyRef") then
+*     the attribute value should be supplied as a character string
+*     containing two formatted axis values (an axis 1 value followed by a
+*     comma, followed by an axis 2 value). The same form
+*     will be used when getting the value of the attribute.
+*
+*     The default values for SkyRef are zero longitude and zero latitude.
+
+*  Aligning SkyFrames with Offset Coordinate Systems:
+*     The offset coordinate system within a SkyFrame should normally be
+*     considered as a superficial "re-badging" of the axes of the coordinate
+*     system specified by the System attribute - it merely provides an
+*     alternative numerical "label" for each position in the System coordinate
+*     system. The SkyFrame retains full knowledge of the celestial coordinate
+*     system on which the offset coordinate system is based (given by the
+*     System attribute). For instance, the SkyFrame retains knowledge of the
+*     way that one celestial coordinate system may "drift" with respect to
+*     another over time. Normally, if you attempt to align two SkyFrames (e.g.
+f     using the AST_CONVERT or AST_FINDFRAME routine),
+c     using the astConvert or astFindFrame routine),
+*     the effect of any offset coordinate system defined in either SkyFrame
+*     will be removed, resulting in alignment being performed in the
+*     celestial coordinate system given by the AlignSystem attribute.
+*     However, by setting the AlignOffset attribute to a non-zero value, it
+*     is possible to change this behaviour so that the effect of the offset
+*     coordinate system is not removed when aligning two SkyFrames.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+
+*  Notes:
+*     - If the System attribute of the SkyFrame is changed, any position
+*     given for SkyRef is transformed into the new System.
+*     - If a value has been assigned to SkyRef attribute, then
+*     the default values for certain attributes are changed as follows:
+*     the default axis Labels for the SkyFrame are modified by appending
+*     " offset" to the end, the default axis Symbols for the SkyFrame are
+*     modified by prepending the character "D" to the start, and the
+*     default title is modified by replacing the projection information by the
+*     origin information.
+
+*att--
+*/
+MAKE_CLEAR(SkyRef,skyref,AST__BAD,2)
+MAKE_SET(SkyRef,double,skyref,value,2)
+MAKE_TEST(SkyRef,( this->skyref[axis_p] != AST__BAD ),2)
+MAKE_GET(SkyRef,double,0.0,((this->skyref[axis_p]!=AST__BAD)?this->skyref[axis_p]:0.0),2)
+
+/*
+*att++
+*  Name:
+*     SkyRefP(axis)
+
+*  Purpose:
+*     Position on primary meridian of offset coordinate system.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute is used to control the orientation of the offset
+*     coordinate system defined by attributes SkyRef and SkyRefIs. If used,
+*     it should be set to hold the longitude and latitude of a point within
+*     the coordinate system specified by the System attribute. The offset
+*     coordinate system represented by the SkyFrame will then be rotated in
+*     order to put the position supplied for SkyRefP on the zero longitude
+*     meridian. This rotation is about an axis from the centre of the
+*     celestial sphere to the point specified by the SkyRef attribute.
+*     The default value for SkyRefP is usually the north pole (that is, a
+*     latitude of +90 degrees in the coordinate system specified by the System
+*     attribute). The exception to this is if the SkyRef attribute is
+*     itself set to either the north or south pole. In these cases the
+*     default for SkyRefP is the origin (that is, a (0,0) in the coordinate
+*     system specified by the System attribute).
+*
+*     If an integer axis index is included in the attribute name (e.g.
+*     "SkyRefP(1)") then the attribute value should be supplied as a single
+*     floating point axis value, in radians, when setting a value for the
+*     attribute, and will be returned in the same form when getting the value
+*     of the attribute. In this case the integer axis index should be "1"
+*     or "2" (the values to use for longitude and latitude axes are
+*     given by the LonAxis and LatAxis attributes).
+*
+*     If no axis index is included in the attribute name (e.g. "SkyRefP") then
+*     the attribute value should be supplied as a character string
+*     containing two formatted axis values (an axis 1 value followed by a
+*     comma, followed by an axis 2 value). The same form
+*     will be used when getting the value of the attribute.
+
+*  Applicability:
+*     SkyFrame
+*        All SkyFrames have this attribute.
+
+*  Notes:
+*     - If the position given by the SkyRef attribute defines the origin
+*     of the offset coordinate system (that is, if the SkyRefIs attribute
+*     is set to "origin"), then there will in general be two orientations
+*     which will put the supplied SkyRefP position on the zero longitude
+*     meridian. The orientation which is actually used is the one which
+*     gives the SkyRefP position a positive latitude in the offset coordinate
+*     system (the other possible orientation would give the SkyRefP position
+*     a negative latitude).
+*     - An error will be reported if an attempt is made to use a
+*     SkyRefP value which is co-incident with SkyRef or with the point
+*     diametrically opposite to SkyRef on the celestial sphere. The
+*     reporting of this error is deferred until the SkyRef and SkyRefP
+*     attribute values are used within a calculation.
+*     - If the System attribute of the SkyFrame is changed, any position
+*     given for SkyRefP is transformed into the new System.
+
+*att--
+*/
+MAKE_CLEAR(SkyRefP,skyrefp,AST__BAD,2)
+MAKE_SET(SkyRefP,double,skyrefp,value,2)
+MAKE_TEST(SkyRefP,( this->skyrefp[axis_p] != AST__BAD ),2)
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for SkyFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for SkyFrame objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *in;              /* Pointer to input SkyFrame */
+   AstSkyFrame *out;             /* Pointer to output SkyFrame */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output SkyFrames. */
+   in = (AstSkyFrame *) objin;
+   out = (AstSkyFrame *) objout;
+
+/* For safety, first clear any references to the input memory from
+   the output SkyFrame. */
+   out->projection = NULL;
+
+/* If necessary, allocate memory in the output SkyFrame and store a
+   copy of the input Projection string. */
+   if ( in->projection ) out->projection = astStore( NULL, in->projection,
+                                      strlen( in->projection ) + (size_t) 1 );
+
+/* If an error occurred, free any allocated memory. */
+   if ( !astOK ) {
+      out->projection = astFree( out->projection );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for SkyFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for SkyFrame objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to SkyFrame */
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) obj;
+
+/* Free the memory used for the Projection string if necessary. */
+   this->projection = astFree( this->projection );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for SkyFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the SkyFrame class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the SkyFrame whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSkyFrame *this;            /* Pointer to the SkyFrame structure */
+   AstSystemType system;         /* System attribute value */
+   char buf[ 100 ];              /* Comment buffer */
+   char key[ 10 ];               /* Buffer for keywords */
+   const char *sval;             /* Pointer to string value */
+   const int *perm;              /* Pointer to axis permutation array */
+   double dval;                  /* Double value */
+   int bessyr;                   /* Use a Besselian year value ?*/
+   int helpful;                  /* Helpful to display un-set value? */
+   int invperm[ 2 ];             /* Inverse permutation array */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+   int axis;                     /* External (i.e. permuted) zero-based axis index */
+   int axis_p;                   /* Internal zero-based axis index */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SkyFrame structure. */
+   this = (AstSkyFrame *) this_object;
+
+/* Get a pointer to the SkyFrame's axis permutation array (using a method,
+   to allow for any over-ride by a derived class). */
+   perm = astGetPerm( this );
+
+/* Generate an inverse axis permutation array from the forward permutation
+   values. This will be used to determine which axis should be enquired
+   about (using possibly over-ridden methods) to obtain data to
+   correspond with a particular internal value (i.e. instance variable)
+   relating to an axis. This step is needed so that the effect of any
+   axis permutation can be un-done before values are written out, as
+   output values are written by this function in un-permuted order. */
+   for ( axis = 0; axis < 2; axis++ ) invperm[ perm[ axis ] ] = axis;
+
+/* Write out values representing the instance variables for the
+   SkyFrame class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Projection. */
+/* ----------- */
+   set = TestProjection( this, status );
+   sval = set ? GetProjection( this, status ) : astGetProjection( this );
+   astWriteString( channel, "Proj", set, 0, sval,
+                   "Description of sky projection" );
+
+/* NegLon. */
+/* ------- */
+   set = TestNegLon( this, status );
+   ival = set ? GetNegLon( this, status ) : astGetNegLon( this );
+   astWriteInt( channel, "NegLon", set, 0, ival,
+                ival ? "Display negative longitude values" :
+                       "Display positive longitude values" );
+
+/* SkyTol. */
+/* ------- */
+   set = TestSkyTol( this, status );
+   dval = set ? GetSkyTol( this, status ) : astGetSkyTol( this );
+   astWriteDouble( channel, "SkyTol", set, 1, dval,
+                   "Smallest significant separation [arc-sec]");
+
+/* Equinox. */
+/* -------- */
+   set = TestEquinox( this, status );
+   dval = set ? GetEquinox( this, status ) : astGetEquinox( this );
+
+/* Decide whether the Equinox value is relevant to the current
+   coordinate system. */
+   system = astGetSystem( this );
+   helpful = ( ( system == AST__FK4 ) ||
+               ( system == AST__FK4_NO_E ) ||
+               ( system == AST__FK5 ) ||
+               ( system == AST__ECLIPTIC ) );
+
+/* Convert MJD to Besselian or Julian years, depending on the value. */
+   bessyr = ( dval < palEpj2d( 1984.0 ) );
+   dval = bessyr ? palEpb( dval ) : palEpj( dval );
+   astWriteDouble( channel, "Eqnox", set, helpful, dval,
+                   bessyr ? "Besselian epoch of mean equinox" :
+                            "Julian epoch of mean equinox" );
+
+/* SkyRefIs. */
+/* --------- */
+   set = TestSkyRefIs( this, status );
+   ival = set ? GetSkyRefIs( this, status ) : astGetSkyRefIs( this );
+   if( ival == AST__POLE_REF ) {
+      astWriteString( channel, "SRefIs", set, 0, POLE_STRING,
+                      "Rotated to put pole at ref. pos." );
+
+   } else if( ival == AST__IGNORED_REF ) {
+      astWriteString( channel, "SRefIs", set, 0, IGNORED_STRING,
+                      "Not rotated (ref. pos. is ignored)" );
+
+   } else {
+      astWriteString( channel, "SRefIs", set, 0, ORIGIN_STRING,
+                      "Rotated to put origin at ref. pos." );
+   }
+
+/* SkyRef. */
+/* ------- */
+/* The inverse axis permutation array is used to obtain the axis index
+   to use when accessing the SkyRef attribute. This reverses the effect
+   of the SkyFrame's axis permutation array and yields a value appropriate
+   to the axis with internal index "axis". */
+   for ( axis_p = 0; axis_p < 2; axis_p++ ) {
+      axis = invperm[ axis_p ];
+
+      set = TestSkyRef( this, axis, status );
+      dval = set ? GetSkyRef( this, axis, status ) : astGetSkyRef( this, axis );
+      sprintf( buf, "Ref. pos. %s %s", astGetSymbol( this, axis ),
+               astFormat( this, axis, dval ) );
+      sprintf( key, "SRef%d", axis_p + 1 );
+      astWriteDouble( channel, key, set, 0, dval, buf );
+   }
+
+/* SkyRefP. */
+/* -------- */
+   for ( axis_p = 0; axis_p < 2; axis_p++ ) {
+      axis = invperm[ axis_p ];
+
+      set = TestSkyRefP( this, axis, status );
+      dval = set ? GetSkyRefP( this, axis, status ) : astGetSkyRefP( this, axis );
+      sprintf( buf, "Ref. north %s %s", astGetSymbol( this, axis ),
+               astFormat( this, axis, dval ) );
+      sprintf( key, "SRefP%d", axis_p + 1 );
+      astWriteDouble( channel, key, set, 0, dval, buf );
+   }
+
+/* AlignOffset. */
+/* ------------ */
+   set = TestAlignOffset( this, status );
+   ival = set ? GetAlignOffset( this, status ) : astGetAlignOffset( this );
+   astWriteInt( channel, "AlOff", set, 0, ival,
+                ival ? "Align in offset coords" :
+                       "Align in system coords" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsASkyFrame and astCheckSkyFrame functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(SkyFrame,Frame)
+astMAKE_CHECK(SkyFrame)
+
+AstSkyFrame *astSkyFrame_( const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astSkyFrame
+
+*  Purpose:
+*     Create a SkyFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     AstSkyFrame *astSkyFrame( const char *options, int *status, ... )
+
+*  Class Membership:
+*     SkyFrame constructor.
+
+*  Description:
+*     This function creates a new SkyFrame and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new SkyFrame. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new SkyFrame.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic SkyFrame constructor which
+*     is available via the protected interface to the SkyFrame class.
+*     A public interface is provided by the astSkyFrameId_ function.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSkyFrame *new;             /* Pointer to new SkyFrame */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SkyFrame, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitSkyFrame( NULL, sizeof( AstSkyFrame ), !class_init, &class_vtab,
+                          "SkyFrame" );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new SkyFrame's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new SkyFrame. */
+   return new;
+}
+
+AstSkyFrame *astInitSkyFrame_( void *mem, size_t size, int init,
+                               AstSkyFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSkyFrame
+
+*  Purpose:
+*     Initialise a SkyFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     AstSkyFrame *astInitSkyFrame( void *mem, size_t size, int init,
+*                                   AstFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SkyFrame initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new SkyFrame object. It allocates memory (if necessary) to accommodate
+*     the SkyFrame plus any additional data associated with the derived class.
+*     It then initialises a SkyFrame structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a SkyFrame at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the SkyFrame is to be created. This
+*        must be of sufficient size to accommodate the SkyFrame data
+*        (sizeof(SkyFrame)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the SkyFrame (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the SkyFrame
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the SkyFrame's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new SkyFrame.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+
+*  Returned Value:
+*     A pointer to the new SkyFrame.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSkyAxis *ax;               /* Pointer to SkyAxis object */
+   AstSkyFrame *new;             /* Pointer to the new SkyFrame */
+   int axis;                     /* Loop counter for axes */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitSkyFrameVtab( vtab, name );
+
+/* Initialise a Frame structure (the parent class) as the first component
+   within the SkyFrame structure, allocating memory if necessary. */
+   new = (AstSkyFrame *) astInitFrame( mem, size, 0,
+                                       (AstFrameVtab *) vtab, name, 2 );
+
+      if ( astOK ) {
+
+/* Initialise the SkyFrame data. */
+/* ----------------------------- */
+/* Initialise all attributes to their "undefined" values. */
+      new->equinox = AST__BAD;
+      new->projection = NULL;
+      new->neglon = -INT_MAX;
+      new->skytol = AST__BAD;
+      new->alignoffset = -INT_MAX;
+      new->skyrefis = AST__BAD_REF;
+      new->skyref[ 0 ] = AST__BAD;
+      new->skyref[ 1 ] = AST__BAD;
+      new->skyrefp[ 0 ] = AST__BAD;
+      new->skyrefp[ 1 ] = AST__BAD;
+      new->last = AST__BAD;
+      new->eplast = AST__BAD;
+      new->klast = AST__BAD;
+      new->diurab = AST__BAD;
+
+/* Loop to replace the Axis object associated with each SkyFrame axis with
+   a SkyAxis object instead. */
+      for ( axis = 0; axis < 2; axis++ ) {
+
+/* Create the new SkyAxis, assign it to the required SkyFrame axis and then
+   annul the SkyAxis pointer. */
+         ax = astSkyAxis( "", status );
+         astSetAxis( new, axis, ax );
+         ax = astAnnul( ax );
+      }
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstSkyFrame *astLoadSkyFrame_( void *mem, size_t size,
+                               AstSkyFrameVtab *vtab, const char *name,
+                               AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadSkyFrame
+
+*  Purpose:
+*     Load a SkyFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "skyframe.h"
+*     AstSkyFrame *astLoadSkyFrame( void *mem, size_t size,
+*                                    AstSkyFrameVtab *vtab, const char *name,
+*                                    AstChannel *channel )
+
+*  Class Membership:
+*     SkyFrame loader.
+
+*  Description:
+*     This function is provided to load a new SkyFrame using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     SkyFrame structure in this memory, using data read from the
+*     input Channel.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the SkyFrame is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        SkyFrame data (sizeof(SkyFrame)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the SkyFrame (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the SkyFrame structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstSkyFrame) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new SkyFrame. If this is NULL, a pointer
+*        to the (static) virtual function table for the SkyFrame class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "SkyFrame" is used instead.
+
+*  Returned Value:
+*     A pointer to the new SkyFrame.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSkyFrame *new;             /* Pointer to the new SkyFrame */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   char *sval;                   /* Pointer to string value */
+   const int *perm;              /* Pointer to axis permutation array */
+   double dval;                  /* Floating point attribute value */
+   int axis;                     /* External axis index */
+   int invperm[ 2 ];             /* Inverse permutation array */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this SkyFrame. In this case the
+   SkyFrame belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstSkyFrame );
+      vtab = &class_vtab;
+      name = "SkyFrame";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitSkyFrameVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built SkyFrame. */
+   new = astLoadFrame( mem, size, (AstFrameVtab *) vtab, name,
+                       channel );
+
+   if ( astOK ) {
+
+/* Get a pointer to the SkyFrame's axis permutation array (using a method,
+   to allow for any over-ride by a derived class). */
+      perm = astGetPerm( new );
+
+/* Generate an inverse axis permutation array from the forward permutation
+   values. This will be used to determine which axis should be enquired
+   about (using possibly over-ridden methods) to obtain data to
+   correspond with a particular internal value (i.e. instance variable)
+   relating to an axis. This step is needed so that the effect of any
+   axis permutation can be un-done before values are written out, as
+   output values are written by this function in un-permuted order. */
+      for( axis = 0; axis < 2; axis++ ) invperm[ perm[ axis ] ] = axis;
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "SkyFrame" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* The attributes defining the offset coordinate system must be loaded
+   before the System attrivbute, since SetSystem uses them. */
+
+/* AlignOffset */
+/* ----------- */
+      new->alignoffset = astReadInt( channel, "aloff", -INT_MAX );
+      if ( TestAlignOffset( new, status ) ) SetAlignOffset( new, new->alignoffset, status );
+
+/* SkyRefIs. */
+/* --------- */
+      sval = astReadString( channel, "srefis", " " );
+      if( sval ){
+         new->skyrefis = AST__BAD_REF;
+         if( astChrMatch( sval, POLE_STRING ) ) {
+            new->skyrefis = AST__POLE_REF;
+         } else if( astChrMatch( sval, ORIGIN_STRING ) ) {
+            new->skyrefis = AST__ORIGIN_REF;
+         } else if( astChrMatch( sval, IGNORED_STRING ) ) {
+            new->skyrefis = AST__IGNORED_REF;
+         } else if( !astChrMatch( sval, " " ) && astOK ){
+	    astError( AST__INTER, "astRead(SkyFrame): Corrupt SkyFrame contains "
+		      "invalid SkyRefIs attribute value (%s).", status, sval );
+         }
+         if( TestSkyRefIs( new, status ) ) SetSkyRefIs( new, new->skyrefis, status );
+         sval = astFree( sval );
+      }
+
+/* SkyRef. */
+/* ------- */
+      new->skyref[ 0 ] = astReadDouble( channel, "sref1", AST__BAD );
+      axis = invperm[ 0 ];
+      if ( TestSkyRef( new, axis, status ) ) SetSkyRef( new, axis, new->skyref[ 0 ], status );
+
+      new->skyref[ 1 ] = astReadDouble( channel, "sref2", AST__BAD );
+      axis = invperm[ 1 ];
+      if ( TestSkyRef( new, axis, status ) ) SetSkyRef( new, axis, new->skyref[ 1 ], status );
+
+/* SkyRefP. */
+/* -------- */
+      new->skyrefp[ 0 ] = astReadDouble( channel, "srefp1", AST__BAD );
+      axis = invperm[ 0 ];
+      if ( TestSkyRefP( new, axis, status ) ) SetSkyRefP( new, axis, new->skyrefp[ 0 ], status );
+
+      new->skyrefp[ 1 ] = astReadDouble( channel, "srefp2", AST__BAD );
+      axis = invperm[ 1 ];
+      if ( TestSkyRefP( new, axis, status ) ) SetSkyRefP( new, axis, new->skyrefp[ 1 ], status );
+
+/* System. */
+/* ------- */
+/* The System attribute is now part of the Frame class, but this code is
+   retained to allow this version of AST to read SkyFrames dumped by
+   previous versions.  */
+
+/* Check a value has not already been assigned to the Frames System
+   attribute.  */
+      if( !astTestSystem( new ) ){
+
+/* Read the external representation as a string. */
+         sval = astReadString( channel, "system", NULL );
+
+/* If a value was read, use the SetAttrib method to validate and store the
+   new value in the correct place, then free the string. */
+         if ( sval ) {
+            astSet( new, "System=%s", status, sval);
+            sval = astFree( sval );
+         }
+      }
+
+/* Epoch. */
+/* ------ */
+/* The Epoch attribute is now part of the Frame class, but this code is
+   retained to allow this version of AST to read SkyFrames dumped by
+   previous versions.  */
+
+/* Check a value has not already been assigned to the Frames Epoch
+   attribute.  */
+      if( !astTestEpoch( new ) ){
+
+/* Get the value. */
+         dval = astReadDouble( channel, "epoch", AST__BAD );
+
+/* If a value was read, use the SetAttrib method to validate and store the
+   new value in the correct place. */
+         if( dval != AST__BAD ) {
+            if( dval < 1984.0 ) {
+               astSet( new, "Epoch=B%.*g", status, DBL_DIG, dval);
+            } else {
+               astSet( new, "Epoch=J%.*g", status, DBL_DIG, dval);
+            }
+         }
+      }
+
+/* Projection. */
+/* ----------- */
+      new->projection = astReadString( channel, "proj", NULL );
+
+/* Equinox. */
+/* -------- */
+/* Interpret this as Besselian or Julian depending on its value. */
+      new->equinox = astReadDouble( channel, "eqnox", AST__BAD );
+      if ( TestEquinox( new, status ) ) {
+         SetEquinox( new, ( new->equinox < 1984.0 ) ? palEpb2d( new->equinox ) :
+                                                      palEpj2d( new->equinox ), status );
+      }
+
+/* NegLon. */
+/* ------- */
+      new->neglon = astReadInt( channel, "neglon", -INT_MAX );
+      if ( TestNegLon( new, status ) ) SetNegLon( new, new->neglon, status );
+
+/* SkyTol. */
+/* ------- */
+      new->skytol = astReadDouble( channel, "skytol", AST__BAD );
+      if ( TestSkyTol( new, status ) ) SetSkyTol( new, new->skytol, status );
+
+/* Other values */
+/* ------------ */
+      new->last = AST__BAD;
+      new->eplast = AST__BAD;
+      new->klast = AST__BAD;
+      new->diurab = AST__BAD;
+
+/* If an error occurred, clean up by deleting the new SkyFrame. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new SkyFrame pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+void astClearAsTime_( AstSkyFrame *this, int axis, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,SkyFrame,ClearAsTime))( this, axis, status );
+}
+int astGetAsTime_( AstSkyFrame *this, int axis, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,SkyFrame,GetAsTime))( this, axis, status );
+}
+void astSetAsTime_( AstSkyFrame *this, int axis, int value, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,SkyFrame,SetAsTime))( this, axis, value, status );
+}
+int astTestAsTime_( AstSkyFrame *this, int axis, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,SkyFrame,TestAsTime))( this, axis, status );
+}
+int astGetIsLatAxis_( AstSkyFrame *this, int axis, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,SkyFrame,GetIsLatAxis))( this, axis, status );
+}
+int astGetIsLonAxis_( AstSkyFrame *this, int axis, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,SkyFrame,GetIsLonAxis))( this, axis, status );
+}
+int astGetLatAxis_( AstSkyFrame *this, int *status ) {
+   if ( !astOK ) return 1;
+   return (**astMEMBER(this,SkyFrame,GetLatAxis))( this, status );
+}
+int astGetLonAxis_( AstSkyFrame *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,SkyFrame,GetLonAxis))( this, status );
+}
+double astGetSkyRefP_( AstSkyFrame *this, int axis, int *status ) {
+   if ( !astOK ) return 0.0;
+   return (**astMEMBER(this,SkyFrame,GetSkyRefP))( this, axis, status );
+}
+AstMapping *astSkyOffsetMap_( AstSkyFrame *this, int *status ) {
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,SkyFrame,SkyOffsetMap))( this, status );
+}
+
+/* Special public interface functions. */
+/* =================================== */
+/* These provide the public interface to certain special functions
+   whose public interface cannot be handled using macros (such as
+   astINVOKE) alone. In general, they are named after the
+   corresponding protected version of the function, but with "Id"
+   appended to the name. */
+
+/* Public Interface Function Prototypes. */
+/* ------------------------------------- */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstSkyFrame *astSkyFrameId_( const char *, ... );
+
+/* Special interface function implementations. */
+/* ------------------------------------------- */
+AstSkyFrame *astSkyFrameId_( const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astSkyFrame
+f     AST_SKYFRAME
+
+*  Purpose:
+*     Create a SkyFrame.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "skyframe.h"
+c     AstSkyFrame *astSkyFrame( const char *options, ... )
+f     RESULT = AST_SKYFRAME( OPTIONS, STATUS )
+
+*  Class Membership:
+*     SkyFrame constructor.
+
+*  Description:
+*     This function creates a new SkyFrame and optionally initialises
+*     its attributes.
+*
+*     A SkyFrame is a specialised form of Frame which describes
+*     celestial longitude/latitude coordinate systems. The particular
+*     celestial coordinate system to be represented is specified by
+*     setting the SkyFrame's System attribute (currently, the default
+*     is ICRS) qualified, as necessary, by a mean Equinox value and/or
+*     an Epoch.
+*
+*     For each of the supported celestial coordinate systems, a SkyFrame
+*     can apply an optional shift of origin to create a coordinate system
+*     representing offsets within the celestial coordinate system from some
+*     specified point. This offset coordinate system can also be rotated to
+*     define new longitude and latitude axes. See attributes SkyRef, SkyRefIs
+*     and SkyRefP
+*
+*     All the coordinate values used by a SkyFrame are in
+*     radians. These may be formatted in more conventional ways for
+c     display by using astFormat.
+f     display by using AST_FORMAT.
+
+*  Parameters:
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new SkyFrame. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new SkyFrame. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSkyFrame()
+f     AST_SKYFRAME = INTEGER
+*        A pointer to the new SkyFrame.
+
+*  Examples:
+c     frame = astSkyFrame( "" );
+c        Creates a SkyFrame to describe the default ICRS celestial
+c        coordinate system.
+c     frame = astSkyFrame( "System = FK5, Equinox = J2005, Digits = 10" );
+c        Creates a SkyFrame to describe the FK5 celestial
+c        coordinate system, with a mean Equinox of J2005.0.
+c        Because especially accurate coordinates will be used,
+c        additional precision (10 digits) has been requested. This will
+c        be used when coordinate values are formatted for display.
+c     frame = astSkyFrame( "System = FK4, Equinox = 1955-sep-2" );
+c        Creates a SkyFrame to describe the old FK4 celestial
+c        coordinate system.  A default Epoch value (B1950.0) is used,
+c        but the mean Equinox value is given explicitly as "1955-sep-2".
+c     frame = astSkyFrame( "System = GAPPT, Epoch = %s", date );
+c        Creates a SkyFrame to describe the Geocentric Apparent
+c        celestial coordinate system. The Epoch value, which specifies
+c        the date of observation, is obtained from a date/time string
+c        supplied via the string pointer "date".
+f     FRAME = AST_SKYFRAME( ' ', STATUS )
+f        Creates a SkyFrame to describe the default ICRS celestial
+f        coordinate system.
+f     FRAME = AST_SKYFRAME( 'System = FK5, Equinox = J2005, Digits = 10', STATUS )
+f        Creates a SkyFrame to describe the FK5 celestial
+f        coordinate system, with a mean Equinox of J2005.0.
+f        Because especially accurate coordinates will be used,
+f        additional precision (10 digits) has been requested. This will
+f        be used when coordinate values are formatted for display.
+f     FRAME = AST_SKYFRAME( 'System = FK4, Equinox = 1955-SEP-2', STATUS )
+f        Creates a SkyFrame to describe the old FK4 celestial
+f        coordinate system.  A default Epoch value (B1950.0) is used,
+f        but the mean Equinox value is given explicitly as "1955-SEP-2".
+f     FRAME = AST_SKYFRAME( 'System = GAPPT, Epoch = ' // DATE, STATUS )
+f        Creates a SkyFrame to describe the Geocentric Apparent
+f        celestial coordinate system. The Epoch value, which specifies
+f        the date of observation, is obtained from a date/time string
+f        contained in the character variable DATE.
+
+*  Notes:
+*     - Currently, the default celestial coordinate system is
+*     ICRS. However, this default may change in future as new
+*     astrometric standards evolve. The intention is to track the most
+*     modern appropriate standard. For this reason, you should use the
+*     default only if this is what you intend (and can tolerate any
+*     associated slight change in behaviour with future versions of
+*     this function). If you intend to use the ICRS system
+*     indefinitely, then you should specify it explicitly using an
+c     "options" value of "System=ICRS".
+f     OPTIONS value of "System=ICRS".
+*     - Whichever celestial coordinate system is represented, it will
+*     have two axes.  The first of these will be the longitude axis
+*     and the second will be the latitude axis. This order can be
+c     changed using astPermAxes if required.
+f     changed using AST_PERMAXES if required.
+*     - When conversion between two SkyFrames is requested (as when
+c     supplying SkyFrames to astConvert),
+f     supplying SkyFrames AST_CONVERT),
+*     account will be taken of the nature of the celestial coordinate
+*     systems they represent, together with any qualifying mean Equinox or
+*     Epoch values, etc. The AlignSystem attribute will also be taken into
+*     account. The results will therefore fully reflect the
+*     relationship between positions on the sky measured in the two
+*     systems.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astSkyFrame constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astSkyFrame_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - The variable argument list also prevents this function from
+*     invoking astSkyFrame_ directly, so it must be a
+*     re-implementation of it in all respects, except for the final
+*     conversion of the result to an ID value.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSkyFrame *new;             /* Pointer to new SkyFrame */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SkyFrame, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitSkyFrame( NULL, sizeof( AstSkyFrame ), !class_init, &class_vtab,
+                          "SkyFrame" );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new SkyFrame's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new SkyFrame. */
+   return astMakeId( new );
+}
+
+
+
+
+
+
+
diff --git a/ast/skyframe.h b/ast/skyframe.h
new file mode 100644
index 0000000..41d8f22
--- /dev/null
+++ b/ast/skyframe.h
@@ -0,0 +1,505 @@
+#if !defined( SKYFRAME_INCLUDED ) /* Include this file only once */
+#define SKYFRAME_INCLUDED
+/*
+*+
+*  Name:
+*     skyframe.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the SkyFrame class.
+
+*  Invocation:
+*     #include "skyframe.h"
+
+*  Description:
+*     This include file defines the interface to the SkyFrame class
+*     and provides the type definitions, function prototypes and
+*     macros, etc. needed to use this class.
+
+*  Inheritance:
+*     The SkyFrame class inherits from the Frame class.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstSkyFrame
+*           SkyFrame object type.
+
+*     Protected:
+*        AstSkyFrameVtab
+*           SkyFrame virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     4-MAR-1996 (RFWS):
+*        Original version.
+*     24-MAY-1996 (RFWS):
+*        Tidied up, etc.
+*     24-SEP-1996 (RFWS):
+*        Added I/O facilities.
+*     16-JUL-1997 (RFWS):
+*        Added Projection attribute.
+*     26-FEB-1998 (RFWS):
+*        Over-ride the astUnformat method.
+*     3-APR-2001 (DSB):
+*        Added "Unknown" option for the System attribute. Added read-only
+*        attributes LatAxis and LonAxis.
+*     10-OCT-2002 (DSB):
+*        Moved definitions of macros for SkyFrame system values into
+*        this file from skyframe.c.
+*     15-NOV-2002 (DSB):
+*        Move the System attribute from this class to the parent (Frame)
+*        class.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitSkyFrameVtab method.
+*     19-APR-2004 (DSB):
+*        Added SkyRef, SkyRefIs, SkyRefP and AlignOffset attributes.
+*        Simplified prologue.
+*     2-DEC-2004 (DSB):
+*        Added System "J2000"
+*     22-FEB-2006 (DSB):
+*        Added Local Apparent Sidereal Time to the SkyFrame structure.
+*     3-OCT-2006 (DSB):
+*        Added Equation of Equinoxes to the SkyFrame structure.
+*     6-OCT-2006 (DSB):
+*        Removed Equation of Equinoxes from the SkyFrame structure.
+*        Added dut1 to the SkyFrame structure.
+*        Added Dut1 accessor methods.
+*     14-OCT-2006 (DSB):
+*        Moved dut1 to the Frame class.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+#include "frame.h"               /* Parent Frame class */
+
+/* Macros. */
+/* ======= */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Define values for the different values of the SkyRefIs attribute. */
+#define AST__BAD_REF 	 0
+#define AST__POLE_REF    1
+#define AST__ORIGIN_REF  2
+#define AST__IGNORED_REF 3
+
+/* Values used to represent different System attribute values. */
+#define AST__FK4           1
+#define AST__FK4_NO_E      2
+#define AST__FK5           3
+#define AST__GAPPT         4
+#define AST__ECLIPTIC      5
+#define AST__GALACTIC      6
+#define AST__SUPERGALACTIC 7
+#define AST__ICRS          8
+#define AST__HELIOECLIPTIC 9
+#define AST__J2000         10
+#define AST__UNKNOWN       11
+#define AST__AZEL          12
+
+/* Define constants used to size global arrays in this module. */
+/* Define other numerical constants for use in this module. */
+#define AST__SKYFRAME_GETATTRIB_BUFF_LEN 200
+#define AST__SKYFRAME_GETFORMAT_BUFF_LEN 50
+#define AST__SKYFRAME_GETLABEL_BUFF_LEN 40
+#define AST__SKYFRAME_GETSYMBOL_BUFF_LEN 20
+#define AST__SKYFRAME_GETTITLE_BUFF_LEN 200
+
+#endif
+
+/* Type Definitions. */
+/* ================= */
+
+/* Cached LAST look-up table. */
+/* -------------------------- */
+/* Holds a list of epoch values and the corresponding Local Apparent
+   Sidereal Time values. Also holds the observatory position and DUT1
+   value used when calculating the LAST values. */
+typedef struct AstSkyLastTable {
+   double obslat;         /* ObsLat at which LAST values were calculated */
+   double obslon;         /* ObsLon at which LAST values were calculated */
+   double obsalt;         /* ObsAlt at which LAST values were calculated */
+   double dut1;           /* Dut1 values at which LAST values were calculated */
+   int nentry;            /* Number of entries in the epoch and last arrays */
+   double *epoch;         /* Array of epoch values */
+   double *last;          /* Array of LAST values */
+} AstSkyLastTable;
+
+/* SkyFrame structure. */
+/* ------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstSkyFrame {
+
+/* Attributes inherited from the parent class. */
+   AstFrame frame;               /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   char *projection;             /* Description of sky projection */
+   double equinox;               /* Modified Julian Date of mean equinox */
+   int neglon;                   /* Display negative longitude values? */
+   double skytol;                /* Smallest significant distance */
+   int alignoffset;              /* Align SkyFrame in offset coords? */
+   int skyrefis;                 /* Nature of offset coord system */
+   double skyref[ 2 ];           /* Origin or pole of offset coord system */
+   double skyrefp[ 2 ];          /* Point on primary meridian of offset coord system */
+   double last;                  /* Local Apparent Sidereal Time */
+   double eplast;                /* Epoch used to calculate "last" */
+   double klast;                 /* Ratio of solar to sidereal time */
+   double diurab;                /* Magnitude of diurnal aberration vector */
+} AstSkyFrame;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all objects in the
+   class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstSkyFrameVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstFrameVtab frame_vtab;      /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstMapping *(* SkyOffsetMap)( AstSkyFrame *, int * );
+   const char *(* GetProjection)( AstSkyFrame *, int * );
+   double (* GetEquinox)( AstSkyFrame *, int * );
+   int (* GetNegLon)( AstSkyFrame *, int * );
+   int (* GetAsTime)( AstSkyFrame *, int, int * );
+   int (* GetIsLatAxis)( AstSkyFrame *, int, int * );
+   int (* GetIsLonAxis)( AstSkyFrame *, int, int * );
+   int (* GetLatAxis)( AstSkyFrame *, int * );
+   int (* GetLonAxis)( AstSkyFrame *, int * );
+   int (* TestAsTime)( AstSkyFrame *, int, int * );
+   int (* TestEquinox)( AstSkyFrame *, int * );
+   int (* TestNegLon)( AstSkyFrame *, int * );
+   int (* TestProjection)( AstSkyFrame *, int * );
+   void (* ClearAsTime)( AstSkyFrame *, int, int * );
+   void (* ClearEquinox)( AstSkyFrame *, int * );
+   void (* ClearNegLon)( AstSkyFrame *, int * );
+   void (* ClearProjection)( AstSkyFrame *, int * );
+   void (* SetAsTime)( AstSkyFrame *, int, int, int * );
+   void (* SetEquinox)( AstSkyFrame *, double, int * );
+   void (* SetNegLon)( AstSkyFrame *, int, int * );
+   void (* SetProjection)( AstSkyFrame *, const char *, int * );
+
+   int (* GetSkyRefIs)( AstSkyFrame *, int * );
+   int (* TestSkyRefIs)( AstSkyFrame *, int * );
+   void (* ClearSkyRefIs)( AstSkyFrame *, int * );
+   void (* SetSkyRefIs)( AstSkyFrame *, int, int * );
+
+   double (* GetSkyTol)( AstSkyFrame *, int * );
+   int (* TestSkyTol)( AstSkyFrame *, int * );
+   void (* ClearSkyTol)( AstSkyFrame *, int * );
+   void (* SetSkyTol)( AstSkyFrame *, double, int * );
+
+   double (* GetSkyRef)( AstSkyFrame *, int, int * );
+   int (* TestSkyRef)( AstSkyFrame *, int, int * );
+   void (* ClearSkyRef)( AstSkyFrame *, int, int * );
+   void (* SetSkyRef)( AstSkyFrame *, int, double, int * );
+
+   double (* GetSkyRefP)( AstSkyFrame *, int, int * );
+   int (* TestSkyRefP)( AstSkyFrame *, int, int * );
+   void (* ClearSkyRefP)( AstSkyFrame *, int, int * );
+   void (* SetSkyRefP)( AstSkyFrame *, int, double, int * );
+
+   int (* GetAlignOffset)( AstSkyFrame *, int * );
+   int (* TestAlignOffset)( AstSkyFrame *, int * );
+   void (* ClearAlignOffset)( AstSkyFrame *, int * );
+   void (* SetAlignOffset)( AstSkyFrame *, int, int * );
+
+} AstSkyFrameVtab;
+
+#if defined(THREAD_SAFE)
+
+/* The AstSkyFrameGlobals structure makes a forward reference to the
+   AstTimeFrame structure which is not defined here (since the
+   timeframe.h file includes skyframe.h). Hence make a preliminary
+   definition available now. */
+struct AstTimeFrame;
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstSkyFrameGlobals {
+   AstSkyFrameVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ AST__SKYFRAME_GETATTRIB_BUFF_LEN + 1 ];
+   char GetFormat_Buff[ AST__SKYFRAME_GETFORMAT_BUFF_LEN + 1 ];
+   char GetLabel_Buff[ AST__SKYFRAME_GETLABEL_BUFF_LEN + 1 ];
+   char GetSymbol_Buff[ AST__SKYFRAME_GETSYMBOL_BUFF_LEN + 1 ];
+   char GetTitle_Buff[ AST__SKYFRAME_GETTITLE_BUFF_LEN + 1 ];
+   char GetTitle_Buff2[ AST__SKYFRAME_GETTITLE_BUFF_LEN + 1 ];
+   struct AstTimeFrame *TDBFrame;
+   struct AstTimeFrame *LASTFrame;
+} AstSkyFrameGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(SkyFrame)         /* Check class membership */
+astPROTO_ISA(SkyFrame)           /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+AstSkyFrame *astSkyFrame_( const char *, int *, ...);
+#else
+AstSkyFrame *astSkyFrameId_( const char *, ... )__attribute__((format(printf,1,2)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstSkyFrame *astInitSkyFrame_( void *, size_t, int, AstSkyFrameVtab *,
+                               const char *, int * );
+
+/* Vtab initialiser. */
+void astInitSkyFrameVtab_( AstSkyFrameVtab *, const char *, int * );
+
+/* Loader. */
+AstSkyFrame *astLoadSkyFrame_( void *, size_t, AstSkyFrameVtab *,
+                               const char *, AstChannel *channel, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitSkyFrameGlobals_( AstSkyFrameGlobals * );
+#endif
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+AstMapping *astSkyOffsetMap_( AstSkyFrame *, int * );
+
+#if defined(astCLASS)            /* Protected */
+const char *astGetProjection_( AstSkyFrame *, int * );
+double astGetEquinox_( AstSkyFrame *, int * );
+int astGetNegLon_( AstSkyFrame *, int * );
+int astGetAsTime_( AstSkyFrame *, int, int * );
+int astGetIsLatAxis_( AstSkyFrame *, int, int * );
+int astGetIsLonAxis_( AstSkyFrame *, int, int * );
+int astGetLatAxis_( AstSkyFrame *, int * );
+int astGetLonAxis_( AstSkyFrame *, int * );
+int astTestAsTime_( AstSkyFrame *, int, int * );
+int astTestEquinox_( AstSkyFrame *, int * );
+int astTestNegLon_( AstSkyFrame *, int * );
+int astTestProjection_( AstSkyFrame *, int * );
+void astClearAsTime_( AstSkyFrame *, int, int * );
+void astClearEquinox_( AstSkyFrame *, int * );
+void astClearNegLon_( AstSkyFrame *, int * );
+void astClearProjection_( AstSkyFrame *, int * );
+void astSetAsTime_( AstSkyFrame *, int, int, int * );
+void astSetEquinox_( AstSkyFrame *, double, int * );
+void astSetNegLon_( AstSkyFrame *, int, int * );
+void astSetProjection_( AstSkyFrame *, const char *, int * );
+
+int astGetAlignOffset_( AstSkyFrame *, int * );
+int astTestAlignOffset_( AstSkyFrame *, int * );
+void astClearAlignOffset_( AstSkyFrame *, int * );
+void astSetAlignOffset_( AstSkyFrame *, int, int * );
+
+int astGetSkyRefIs_( AstSkyFrame *, int * );
+int astTestSkyRefIs_( AstSkyFrame *, int * );
+void astClearSkyRefIs_( AstSkyFrame *, int * );
+void astSetSkyRefIs_( AstSkyFrame *, int, int * );
+
+double astGetSkyRef_( AstSkyFrame *, int, int * );
+int astTestSkyRef_( AstSkyFrame *, int, int * );
+void astClearSkyRef_( AstSkyFrame *, int, int * );
+void astSetSkyRef_( AstSkyFrame *, int, double, int * );
+
+double astGetSkyRefP_( AstSkyFrame *, int, int * );
+int astTestSkyRefP_( AstSkyFrame *, int, int * );
+void astClearSkyRefP_( AstSkyFrame *, int, int * );
+void astSetSkyRefP_( AstSkyFrame *, int, double, int * );
+
+double astGetSkyTol_( AstSkyFrame *, int * );
+int astTestSkyTol_( AstSkyFrame *, int * );
+void astClearSkyTol_( AstSkyFrame *, int * );
+void astSetSkyTol_( AstSkyFrame *, double, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckSkyFrame(this) astINVOKE_CHECK(SkyFrame,this,0)
+#define astVerifySkyFrame(this) astINVOKE_CHECK(SkyFrame,this,1)
+
+/* Test class membership. */
+#define astIsASkyFrame(this) astINVOKE_ISA(SkyFrame,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+#define astSkyFrame astINVOKE(F,astSkyFrame_)
+#else
+#define astSkyFrame astINVOKE(F,astSkyFrameId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitSkyFrame(mem,size,init,vtab,name) \
+astINVOKE(O,astInitSkyFrame_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitSkyFrameVtab(vtab,name) astINVOKE(V,astInitSkyFrameVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadSkyFrame(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadSkyFrame_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+
+#define astSkyOffsetMap(this) \
+astINVOKE(O,astSkyOffsetMap_(astCheckSkyFrame(this),STATUS_PTR))
+
+/* Interfaces to protected member functions. */
+/* ----------------------------------------- */
+/* Here we make use of astCheckSkyFrame to validate SkyFrame pointers
+   before use. This provides a contextual error report if a pointer to
+   the wrong sort of object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astClearAsTime(this,axis) \
+astINVOKE(V,astClearAsTime_(astCheckSkyFrame(this),axis,STATUS_PTR))
+#define astClearEquinox(this) \
+astINVOKE(V,astClearEquinox_(astCheckSkyFrame(this),STATUS_PTR))
+#define astClearNegLon(this) \
+astINVOKE(V,astClearNegLon_(astCheckSkyFrame(this),STATUS_PTR))
+#define astClearProjection(this) \
+astINVOKE(V,astClearProjection_(astCheckSkyFrame(this),STATUS_PTR))
+#define astGetAsTime(this,axis) \
+astINVOKE(V,astGetAsTime_(astCheckSkyFrame(this),axis,STATUS_PTR))
+#define astGetEquinox(this) \
+astINVOKE(V,astGetEquinox_(astCheckSkyFrame(this),STATUS_PTR))
+#define astGetNegLon(this) \
+astINVOKE(V,astGetNegLon_(astCheckSkyFrame(this),STATUS_PTR))
+#define astGetIsLatAxis(this,axis) \
+astINVOKE(V,astGetIsLatAxis_(astCheckSkyFrame(this),axis,STATUS_PTR))
+#define astGetIsLonAxis(this,axis) \
+astINVOKE(V,astGetIsLonAxis_(astCheckSkyFrame(this),axis,STATUS_PTR))
+#define astGetLatAxis(this) \
+astINVOKE(V,astGetLatAxis_(astCheckSkyFrame(this),STATUS_PTR))
+#define astGetLonAxis(this) \
+astINVOKE(V,astGetLonAxis_(astCheckSkyFrame(this),STATUS_PTR))
+#define astGetProjection(this) \
+astINVOKE(V,astGetProjection_(astCheckSkyFrame(this),STATUS_PTR))
+#define astSetAsTime(this,axis,value) \
+astINVOKE(V,astSetAsTime_(astCheckSkyFrame(this),axis,value,STATUS_PTR))
+#define astSetEquinox(this,value) \
+astINVOKE(V,astSetEquinox_(astCheckSkyFrame(this),value,STATUS_PTR))
+#define astSetNegLon(this,value) \
+astINVOKE(V,astSetNegLon_(astCheckSkyFrame(this),value,STATUS_PTR))
+#define astSetProjection(this,value) \
+astINVOKE(V,astSetProjection_(astCheckSkyFrame(this),value,STATUS_PTR))
+#define astTestAsTime(this,axis) \
+astINVOKE(V,astTestAsTime_(astCheckSkyFrame(this),axis,STATUS_PTR))
+#define astTestEquinox(this) \
+astINVOKE(V,astTestEquinox_(astCheckSkyFrame(this),STATUS_PTR))
+#define astTestNegLon(this) \
+astINVOKE(V,astTestNegLon_(astCheckSkyFrame(this),STATUS_PTR))
+#define astTestProjection(this) \
+astINVOKE(V,astTestProjection_(astCheckSkyFrame(this),STATUS_PTR))
+
+#define astClearAlignOffset(this) astINVOKE(V,astClearAlignOffset_(astCheckSkyFrame(this),STATUS_PTR))
+#define astGetAlignOffset(this) astINVOKE(V,astGetAlignOffset_(astCheckSkyFrame(this),STATUS_PTR))
+#define astSetAlignOffset(this,value) astINVOKE(V,astSetAlignOffset_(astCheckSkyFrame(this),value,STATUS_PTR))
+#define astTestAlignOffset(this) astINVOKE(V,astTestAlignOffset_(astCheckSkyFrame(this),STATUS_PTR))
+
+#define astClearSkyRefIs(this) astINVOKE(V,astClearSkyRefIs_(astCheckSkyFrame(this),STATUS_PTR))
+#define astGetSkyRefIs(this) astINVOKE(V,astGetSkyRefIs_(astCheckSkyFrame(this),STATUS_PTR))
+#define astSetSkyRefIs(this,value) astINVOKE(V,astSetSkyRefIs_(astCheckSkyFrame(this),value,STATUS_PTR))
+#define astTestSkyRefIs(this) astINVOKE(V,astTestSkyRefIs_(astCheckSkyFrame(this),STATUS_PTR))
+
+#define astClearSkyRef(this,axis) astINVOKE(V,astClearSkyRef_(astCheckSkyFrame(this),axis,STATUS_PTR))
+#define astGetSkyRef(this,axis) astINVOKE(V,astGetSkyRef_(astCheckSkyFrame(this),axis,STATUS_PTR))
+#define astSetSkyRef(this,axis,value) astINVOKE(V,astSetSkyRef_(astCheckSkyFrame(this),axis,value,STATUS_PTR))
+#define astTestSkyRef(this,axis) astINVOKE(V,astTestSkyRef_(astCheckSkyFrame(this),axis,STATUS_PTR))
+
+#define astClearSkyRefP(this,axis) astINVOKE(V,astClearSkyRefP_(astCheckSkyFrame(this),axis,STATUS_PTR))
+#define astGetSkyRefP(this,axis) astINVOKE(V,astGetSkyRefP_(astCheckSkyFrame(this),axis,STATUS_PTR))
+#define astSetSkyRefP(this,axis,value) astINVOKE(V,astSetSkyRefP_(astCheckSkyFrame(this),axis,value,STATUS_PTR))
+#define astTestSkyRefP(this,axis) astINVOKE(V,astTestSkyRefP_(astCheckSkyFrame(this),axis,STATUS_PTR))
+
+#define astClearSkyTol(this) astINVOKE(V,astClearSkyTol_(astCheckSkyFrame(this),STATUS_PTR))
+#define astGetSkyTol(this) astINVOKE(V,astGetSkyTol_(astCheckSkyFrame(this),STATUS_PTR))
+#define astSetSkyTol(this,value) astINVOKE(V,astSetSkyTol_(astCheckSkyFrame(this),value,STATUS_PTR))
+#define astTestSkyTol(this) astINVOKE(V,astTestSkyTol_(astCheckSkyFrame(this),STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/slamap.c b/ast/slamap.c
new file mode 100644
index 0000000..345bbf0
--- /dev/null
+++ b/ast/slamap.c
@@ -0,0 +1,5027 @@
+/*
+*class++
+*  Name:
+*     SlaMap
+
+*  Purpose:
+*     Sequence of celestial coordinate conversions.
+
+*  Constructor Function:
+c     astSlaMap (also see astSlaAdd)
+f     AST_SLAMAP (also see AST_SLAADD)
+
+*  Description:
+*     An SlaMap is a specialised form of Mapping which can be used to
+*     represent a sequence of conversions between standard celestial
+*     (longitude, latitude) coordinate systems.
+*
+*     When an SlaMap is first created, it simply performs a unit
+c     (null) Mapping on a pair of coordinates. Using the astSlaAdd
+f     (null) Mapping on a pair of coordinates. Using the AST_SLAADD
+c     function, a series of coordinate conversion steps may then be
+f     routine, a series of coordinate conversion steps may then be
+*     added, selected from those provided by the SLALIB Positional
+*     Astronomy Library (Starlink User Note SUN/67). This allows
+*     multi-step conversions between a variety of celestial coordinate
+*     systems to be assembled out of the building blocks provided by
+*     SLALIB.
+*
+*     For details of the individual coordinate conversions available,
+c     see the description of the astSlaAdd function.
+f     see the description of the AST_SLAADD routine.
+
+*  Inheritance:
+*     The SlaMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The SlaMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     In addition to those functions applicable to all Mappings, the
+c     following function may also be applied to all SlaMaps:
+f     In addition to those routines applicable to all Mappings, the
+f     following routine may also be applied to all SlaMaps:
+*
+c     - astSlaAdd: Add a celestial coordinate conversion to an SlaMap
+f     - AST_SLAADD: Add a celestial coordinate conversion to an SlaMap
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2013 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     25-APR-1996 (RFWS):
+*        Original version.
+*     28-MAY-1996 (RFWS):
+*        Fixed bug in argument order to palMappa for AST__SLA_AMP case.
+*     26-SEP-1996 (RFWS):
+*        Added external interface and I/O facilities.
+*     23-MAY-1997 (RFWS):
+*        Over-ride the astMapMerge method.
+*     28-MAY-1997 (RFWS):
+*        Use strings to specify conversions for the public interface
+*        and convert to macros (from an enumerated type) for the
+*        internal representation. Tidy the public prologues.
+*     8-JAN-2003 (DSB):
+*        - Changed private InitVtab method to protected astInitSlaMapVtab
+*        method.
+*        - Included STP conversion functions.
+*     11-JUN-2003 (DSB):
+*        - Added HFK5Z and FK5HZ conversion functions.
+*     28-SEP-2003 (DSB):
+*        - Added HEEQ and EQHE conversion functions.
+*     2-DEC-2004 (DSB):
+*        - Added J2000H and HJ2000 conversion functions.
+*     15-AUG-2005 (DSB):
+*        - Added H2E and E2H conversion functions.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     22-FEB-2006 (DSB):
+*        Cache results returned by palMappa in order to increase speed.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*     31-AUG-2007 (DSB):
+*        - Modify H2E and E2H conversion functions so that they convert to
+*        and from apparent (HA,Dec) rather than topocentric (HA,Dec) (i.e.
+*        include a correction for diurnal aberration). This requires an
+*        extra conversion argument holding the magnitude of the diurnal
+*        aberration vector.
+*        - Correct bug in the simplification of adjacent AMP and MAP
+*        conversions.
+*     15-NOV-2013 (DSB):
+*        Fix bug in merging of adjacent AMP and MAP conversions (MapMerge
+*        did not take account of the fact that the arguments for these
+*        two conversions are stored in swapped order).
+*     6-JUL-2015 (DSB):
+*        Added method astSlaIsEmpty.
+*     30-NOV-2016 (DSB):
+*        Added a "narg" argumeent to astSlaAdd.
+
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS SlaMap
+
+/* Codes to identify SLALIB sky coordinate conversions. */
+#define AST__SLA_NULL    0       /* Null value */
+#define AST__SLA_ADDET   1       /* Add E-terms of aberration */
+#define AST__SLA_SUBET   2       /* Subtract E-terms of aberration */
+#define AST__SLA_PREBN   3       /* Bessel-Newcomb (FK4) precession */
+#define AST__SLA_PREC    4       /* Apply IAU 1975 (FK5) precession model */
+#define AST__SLA_FK45Z   5       /* FK4 to FK5, no proper motion or parallax */
+#define AST__SLA_FK54Z   6       /* FK5 to FK4, no proper motion or parallax */
+#define AST__SLA_AMP     7       /* Geocentric apparent to mean place */
+#define AST__SLA_MAP     8       /* Mean place to geocentric apparent */
+#define AST__SLA_ECLEQ   9       /* Ecliptic to J2000.0 equatorial */
+#define AST__SLA_EQECL  10       /* Equatorial J2000.0 to ecliptic */
+#define AST__SLA_GALEQ  11       /* Galactic to J2000.0 equatorial */
+#define AST__SLA_EQGAL  12       /* J2000.0 equatorial to galactic */
+#define AST__SLA_GALSUP 13       /* Galactic to supergalactic */
+#define AST__SLA_SUPGAL 14       /* Supergalactic to galactic */
+#define AST__HPCEQ      15       /* Helioprojective-Cartesian to J2000.0 equatorial */
+#define AST__EQHPC      16       /* J2000.0 equatorial to Helioprojective-Cartesian */
+#define AST__HPREQ      17       /* Helioprojective-Radial to J2000.0 equatorial */
+#define AST__EQHPR      18       /* J2000.0 equatorial to Helioprojective-Radial */
+#define AST__SLA_HFK5Z  19       /* ICRS to FK5 J2000.0, no pm or parallax */
+#define AST__SLA_FK5HZ  20       /* FK5 J2000.0 to ICRS, no pm or parallax */
+#define AST__HEEQ       21       /* Helio-ecliptic to equatorial */
+#define AST__EQHE       22       /* Equatorial to helio-ecliptic */
+#define AST__J2000H     23       /* Dynamical J2000 to ICRS */
+#define AST__HJ2000     24       /* ICRS to dynamical J2000 */
+#define AST__SLA_DH2E   25       /* Horizon to equatorial coordinates */
+#define AST__SLA_DE2H   26       /* Equatorial coordinates to horizon */
+#define AST__R2H        27       /* RA to hour angle */
+#define AST__H2R        28       /* Hour to RA angle */
+
+/* Maximum number of arguments required by an SLALIB conversion. */
+#define MAX_SLA_ARGS 4
+
+/* The alphabet (used for generating keywords for arguments). */
+#define ALPHABET "abcdefghijklmnopqrstuvwxyz"
+
+/* Angle conversion (PI is from the SLALIB slamac.h file) */
+#define PI 3.1415926535897932384626433832795028841971693993751
+#define PIBY2 (PI/2.0)
+#define D2R (PI/180.0)
+#define R2D (180.0/PI)
+#define AS2R (PI/648000.0)
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "pal.h"              /* SLALIB interface */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+#include "wcsmap.h"              /* Required for AST__DPI */
+#include "unitmap.h"             /* Unit (null) Mappings */
+#include "slamap.h"              /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->Eq_Cache = AST__BAD; \
+   globals->Ep_Cache = AST__BAD; \
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(SlaMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(SlaMap,Class_Init)
+#define class_vtab astGLOBAL(SlaMap,Class_Vtab)
+#define eq_cache astGLOBAL(SlaMap,Eq_Cache)
+#define ep_cache astGLOBAL(SlaMap,Ep_Cache)
+#define amprms_cache astGLOBAL(SlaMap,Amprms_Cache)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* A cache used to store the most recent results from palMappa in order
+   to avoid continuously recalculating the same values. */
+static double eq_cache = AST__BAD;
+static double ep_cache = AST__BAD;
+static double amprms_cache[ 21 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstSlaMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstSlaMap *astSlaMapId_( int, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *CvtString( int, const char **, int *, const char *[ MAX_SLA_ARGS ], int * );
+static int CvtCode( const char *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int SlaIsEmpty( AstSlaMap *, int * );
+static void AddSlaCvt( AstSlaMap *, int, int, const double *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void De2h( double, double, double, double, double *, double *, int * );
+static void Dh2e( double, double, double, double, double *, double *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void Earth( double, double[3], int * );
+static void SlaAdd( AstSlaMap *, const char *, int, const double[], int * );
+static void SolarPole( double, double[3], int * );
+static void Hpcc( double, double[3], double[3][3], double[3], int * );
+static void Hprc( double, double[3], double[3][3], double[3], int * );
+static void Hgc( double, double[3][3], double[3], int * );
+static void Haec( double, double[3][3], double[3], int * );
+static void Haqc( double, double[3][3], double[3], int * );
+static void Gsec( double, double[3][3], double[3], int * );
+static void STPConv( double, int, int, int, double[3], double *[3], int, double[3], double *[3], int * );
+static void J2000H( int, int, double *, double *, int * );
+
+static int GetObjSize( AstObject *, int * );
+
+/* Member functions. */
+/* ================= */
+
+static void De2h( double ha, double dec, double phi, double diurab,
+                  double *az, double *el, int *status ){
+
+/* Not quite like slaDe2h since it converts from apparent (ha,dec) to
+   topocentric (az,el). This includes a correction for diurnal
+   aberration. The magnitude of the diurnal aberration vector should be
+   supplied in parameter "diurab". The extra code is taken from the
+   Fortran routine SLA_AOPQK. */
+
+/* Local Variables: */
+   double a;
+   double cd;
+   double ch;
+   double cp;
+   double f;
+   double r;
+   double sd;
+   double sh;
+   double sp;
+   double x;
+   double xhd;
+   double xhdt;
+   double y;
+   double yhd;
+   double yhdt;
+   double z;
+   double zhd;
+   double zhdt;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Pre-compute common values */
+   sh = sin( ha );
+   ch = cos( ha );
+   sd = sin( dec );
+   cd = cos( dec );
+   sp = sin( phi );
+   cp = cos( phi );
+
+/* Components of cartesian (-ha,dec) vector. */
+   xhd = ch*cd;
+   yhd = -sh*cd;
+   zhd = sd;
+
+/* Modify the above vector to apply diurnal aberration. */
+   f = ( 1.0 - diurab*yhd );
+   xhdt = f*xhd;
+   yhdt = f*( yhd + diurab );
+   zhdt = f*zhd;
+
+/* Convert to cartesian (az,el). */
+   x = -xhdt*sp + zhdt*cp;
+   y = yhdt;
+   z = xhdt*cp + zhdt*sp;
+
+/* Convert to spherical (az,el). */
+   r = sqrt( x*x + y*y );
+   if( r == 0.0 ) {
+      a = 0.0;
+   } else {
+      a = atan2( y, x );
+   }
+
+   while( a < 0.0 ) a += 2*AST__DPI;
+
+   *az = a;
+   *el = atan2( z, r );
+}
+
+static void Dh2e( double az, double el, double phi, double diurab, double *ha,
+                  double *dec, int *status ){
+
+/* Not quite like slaDh2e since it converts from topocentric (az,el) to
+   apparent (ha,dec). This includes a correction for diurnal aberration.
+   The magnitude of the diurnal aberration vector should be supplied in
+   parameter "diurab". The extra code is taken from the Fortran routine
+   SLA_OAPQK. */
+
+/* Local Variables: */
+   double ca;
+   double ce;
+   double cp;
+   double f;
+   double r;
+   double sa;
+   double se;
+   double sp;
+   double x;
+   double xmhda;
+   double y;
+   double ymhda;
+   double z;
+   double zmhda;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Pre-compute common values. */
+   sa = sin( az );
+   ca = cos( az );
+   se = sin( el );
+   ce = cos( el );
+   sp = sin( phi );
+   cp = cos( phi );
+
+/* Cartesian (az,el) to Cartesian (ha,dec) - note, +ha, not -ha. */
+   xmhda = -ca*ce*sp + se*cp;
+   ymhda = -sa*ce;
+   zmhda = ca*ce*cp + se*sp;
+
+/* Correct this vector for diurnal aberration. Since the above
+   expressions produce +ha rather than -ha, we do not negate "diurab"
+   before using it. Compare this to SLA_AOPQK. */
+   f = ( 1 - diurab*ymhda );
+   x = f*xmhda;
+   y = f*( ymhda + diurab );
+   z = f*zmhda;
+
+/* Cartesian (ha,dec) to spherical (ha,dec). */
+   r = sqrt( x*x + y*y );
+   if( r == 0.0 ) {
+      *ha = 0.0;
+   } else {
+      *ha = atan2( y, x );
+   }
+   *dec = atan2( z, r );
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two SlaMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     SlaMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two SlaMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a SlaMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the SlaMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSlaMap *that;
+   AstSlaMap *this;
+   const char *argdesc[ MAX_SLA_ARGS ];
+   const char *comment;
+   int i, j;
+   int nargs;
+   int nin;
+   int nout;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two SlaMap structures. */
+   this = (AstSlaMap *) this_object;
+   that = (AstSlaMap *) that_object;
+
+/* Check the second object is a SlaMap. We know the first is a
+   SlaMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsASlaMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two SlaMaps differ, it may still be possible
+   for them to be equivalent. First compare the SlaMaps if their Invert
+   flags are the same. In this case all the attributes of the two SlaMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+            if( this->ncvt == that->ncvt ) {
+               result = 1;
+               for( i = 0; i < this->ncvt && result; i++ ) {
+                  if( this->cvttype[ i ] != that->cvttype[ i ] ) {
+                     result = 0;
+                  } else {
+                     CvtString( this->cvttype[ i ], &comment, &nargs,
+                                argdesc, status );
+                     for( j = 0; j < nargs; j++ ) {
+                        if( !astEQUAL( this->cvtargs[ i ][ j ],
+                                       that->cvtargs[ i ][ j ] ) ){
+                           result = 0;
+                           break;
+                        }
+                     }
+                  }
+               }
+            }
+
+/* If the Invert flags for the two SlaMaps differ, the attributes of the two
+   SlaMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a SlaMap, Invert flags must be equal. */
+            result = 0;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     SlaMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied SlaMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the SlaMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSlaMap *this;         /* Pointer to SlaMap structure */
+   int result;              /* Result value to return */
+   int cvt;                 /* Loop counter for coordinate conversions */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the SlaMap structure. */
+   this = (AstSlaMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   for ( cvt = 0; cvt < this->ncvt; cvt++ ) {
+      result += astTSizeOf( this->cvtargs[ cvt ] );
+      result += astTSizeOf( this->cvtextra[ cvt ] );
+   }
+
+   result += astTSizeOf( this->cvtargs );
+   result += astTSizeOf( this->cvtextra );
+   result += astTSizeOf( this->cvttype );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static void AddSlaCvt( AstSlaMap *this, int cvttype, int narg,
+                       const double *args, int *status ) {
+/*
+*  Name:
+*     AddSlaCvt
+
+*  Purpose:
+*     Add a coordinate conversion step to an SlaMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void AddSlaCvt( AstSlaMap *this, int cvttype, int narg,
+*                     const double *args )
+
+*  Class Membership:
+*     SlaMap member function.
+
+*  Description:
+*     This function allows one of the sky coordinate conversions
+*     supported by SLALIB to be appended to an SlaMap. When an SlaMap
+*     is first created (using astSlaMap), it simply performs a unit
+*     mapping. By using AddSlaCvt repeatedly, a series of sky
+*     coordinate conversions may then be specified which the SlaMap
+*     will subsequently perform in sequence. This allows a complex
+*     coordinate conversion to be assembled out of the basic building
+*     blocks provided by SLALIB. The SlaMap will also perform the
+*     inverse coordinate conversion (applying the individual
+*     conversion steps in reverse) if required.
+
+*  Parameters:
+*     this
+*        Pointer to the SlaMap.
+*     cvttype
+*        A code to identify which sky coordinate conversion is to be
+*        appended.  See the "SLALIB Coordinate Conversions" section
+*        for details of those available.
+*     narg
+*        The number of argument values supplied in "args".
+*     args
+*        Pointer to an array of double containing the argument values
+*        required to fully specify the required coordinate
+*        conversion. The number of arguments depends on the conversion
+*        (see the "SLALIB Coordinate Conversions" section for
+*        details). This value is ignored and may be NULL if no
+*        arguments are required.
+
+*  Returned Value:
+*     void.
+
+*  SLALIB Coordinate Conversions:
+*     The following values may be supplied for the "cvttype" parameter
+*     in order to specify the sky coordinate conversion to be
+*     performed. In each case the value is named after the SLALIB
+*     routine that performs the conversion, and the relevant SLALIB
+*     documentation should be consulted for full details.
+*
+*     The argument(s) required to fully specify each conversion are
+*     indicated in parentheses after each value. Values for these
+*     should be given in the array pointed at by "args". The argument
+*     names given match the corresponding SLALIB function arguments
+*     (in the Fortran 77 documentation - SUN/67) and their values
+*     should be given using the same units, time scale, calendar,
+*     etc. as in SLALIB.
+*
+*        AST__SLA_ADDET( EQ )
+*           Add E-terms of aberration.
+*        AST__SLA_SUBET( EQ )
+*           Subtract E-terms of aberration.
+*        AST__SLA_PREBN( BEP0, BEP1 )
+*           Apply Bessel-Newcomb pre-IAU 1976 (FK4) precession model.
+*        AST__SLA_PREC( EP0, EP1 )
+*           Apply IAU 1975 (FK5) precession model.
+*        AST__SLA_FK45Z( BEPOCH )
+*           Convert FK4 to FK5 (no proper motion or parallax).
+*        AST__SLA_FK54Z( BEPOCH )
+*           Convert FK5 to FK4 (no proper motion or parallax).
+*        AST__SLA_AMP( DATE, EQ )
+*           Convert geocentric apparent to mean place.
+*        AST__SLA_MAP( EQ, DATE )
+*           Convert mean place to geocentric apparent.
+*        AST__SLA_ECLEQ( DATE )
+*           Convert ecliptic coordinates to J2000.0 equatorial.
+*        AST__SLA_EQECL( DATE )
+*           Convert equatorial J2000.0 to ecliptic coordinates.
+*        AST__SLA_GALEQ( )
+*           Convert galactic coordinates to J2000.0 equatorial.
+*        AST__SLA_EQGAL( )
+*           Convert J2000.0 equatorial to galactic coordinates.
+*        AST__SLA_HFK5Z( JEPOCH )
+*           Convert ICRS coordinates to J2000.0 equatorial (no proper
+*           motion or parallax).
+*        AST__SLA_FK5HZ( JEPOCH )
+*           Convert J2000.0 equatorial to ICRS coordinates (no proper
+*           motion or parallax).
+*        AST__SLA_GALSUP( )
+*           Convert galactic to supergalactic coordinates.
+*        AST__SLA_SUPGAL( )
+*           Convert supergalactic coordinates to galactic.
+*        AST__HPCEQ( DATE, OBSX, OBSY, OBSZ )
+*           Convert Helioprojective-Cartesian coordinates to J2000.0
+*           equatorial. This is not a native SLALIB conversion, but is
+*           implemented by functions within this module. The DATE argument
+*           is the MJD defining the HPC coordinate system. The OBSX, OBSY
+*           and OBSZ arguments are the AST__HAEC coordinates of the observer.
+*        AST__EQHPC( DATE, OBSX, OBSY, OBSZ )
+*           Convert J2000.0 equatorial coordinates to Helioprojective-Cartesian.
+*        AST__HPREQ( DATE, OBSX, OBSY, OBSZ )
+*           Convert Helioprojective-Radial coordinates to J2000.0 equatorial.
+*        AST__EQHPR( DATE, OBSX, OBSY, OBSZ )
+*           Convert J2000.0 equatorial coordinates to Helioprojective-Radial.
+*        AST__HEEQ( DATE )
+*           Convert helio-ecliptic to ecliptic coordinates.
+*        AST__EQHE( DATE )
+*           Convert ecliptic to helio-ecliptic coordinates.
+*        AST__J2000H( )
+*           Convert dynamical J2000 to ICRS.
+*        AST__HJ2000( )
+*           Convert ICRS to dynamical J2000.
+*        AST__SLA_DH2E( LAT, DIURAB )
+*           Convert horizon to equatorial coordinates
+*        AST__SLA_DE2H( LAT, DIURAB )
+*           Convert equatorial to horizon coordinates
+*        AST__R2H( LAST )
+*           Convert RA to Hour Angle.
+*        AST__H2R( LAST )
+*           Convert Hour Angle to RA.
+
+*  Notes:
+*     - The specified conversion is appended only if the SlaMap's
+*     Invert attribute is zero. If it is non-zero, this function
+*     effectively prefixes the inverse of the conversion specified
+*     instead.
+*     - Sky coordinate values are in radians (as for SLALIB) and all
+*     conversions are performed using double arithmetic.
+*/
+
+/* Local Variables: */
+   const char *argdesc[ MAX_SLA_ARGS ]; /* Pointers to argument descriptions */
+   const char *comment;          /* Pointer to comment string */
+   const char *cvt_string;       /* Pointer to conversion type string */
+   int nargs;                    /* Number of arguments */
+   int ncvt;                     /* Number of coordinate conversions */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the coordinate conversion type and obtain the number of
+   required arguments. */
+   cvt_string = CvtString( cvttype, &comment, &nargs, argdesc, status );
+
+/* If the sky coordinate conversion type was not valid, then report an
+   error. */
+   if ( astOK && !cvt_string ) {
+      astError( AST__SLAIN, "AddSlaCvt(%s): Invalid SLALIB sky coordinate "
+                "conversion type (%d).", status, astGetClass( this ),
+                (int) cvttype );
+   }
+
+/* If the number of supplied arguments is incorrect, then report an error. */
+   if ( astOK && nargs != narg ) {
+      astError( AST__TIMIN, "AddSlaCvt(%s): Invalid no. of arguments for SLALIB "
+                "coordinate conversion type %d - %d supplied, %d required.",
+                status, astGetClass( this ), (int) cvttype, narg, nargs );
+   }
+
+/* Note the number of coordinate conversions already stored in the SlaMap. */
+   if ( astOK ) {
+      ncvt = this->ncvt;
+
+/* Extend the array of conversion types and the array of pointers to
+   their argument lists to accommodate the new one. */
+      this->cvttype = (int *) astGrow( this->cvttype, ncvt + 1,
+                                       sizeof( int ) );
+      this->cvtargs = (double **) astGrow( this->cvtargs, ncvt + 1,
+                                           sizeof( double * ) );
+      this->cvtextra = (double **) astGrow( this->cvtextra, ncvt + 1,
+                                           sizeof( double * ) );
+
+/* If OK, allocate memory and store a copy of the argument list,
+   putting a pointer to the copy into the SlaMap. */
+      if ( astOK ) {
+         this->cvtargs[ ncvt ] = astStore( NULL, args,
+                                           sizeof( double ) * (size_t) nargs );
+         this->cvtextra[ ncvt ] = NULL;
+      }
+
+/* Store the conversion type and increment the conversion count. */
+      if ( astOK ) {
+         this->cvttype[ ncvt ] = cvttype;
+         this->ncvt++;
+      }
+   }
+}
+
+static int CvtCode( const char *cvt_string, int *status ) {
+/*
+*  Name:
+*     CvtCode
+
+*  Purpose:
+*     Convert a conversion type from a string representation to a code value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     int CvtCode( const char *cvt_string, int *status )
+
+*  Class Membership:
+*     SlaMap member function.
+
+*  Description:
+*     This function accepts a string used to repersent one of the
+*     SLALIB sky coordinate conversions and converts it into a code
+*     value for internal use.
+
+*  Parameters:
+*     cvt_string
+*        Pointer to a constant null-terminated string representing a
+*        sky coordinate conversion. This is case sensitive and should
+*        contain no unnecessary white space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The equivalent conversion code. If the string was not
+*     recognised, the code AST__SLA_NULL is returned, without error.
+
+*  Notes:
+*     - A value of AST__SLA_NULL will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = AST__SLA_NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Test the string against each recognised value in turn and assign
+   the result. */
+   if ( astChrMatch( cvt_string, "ADDET" ) ) {
+      result = AST__SLA_ADDET;
+
+   } else if ( astChrMatch( cvt_string, "SUBET" ) ) {
+      result = AST__SLA_SUBET;
+
+   } else if ( astChrMatch( cvt_string, "PREBN" ) ) {
+      result = AST__SLA_PREBN;
+
+   } else if ( astChrMatch( cvt_string, "PREC" ) ) {
+      result = AST__SLA_PREC;
+
+   } else if ( astChrMatch( cvt_string, "FK45Z" ) ) {
+      result = AST__SLA_FK45Z;
+
+   } else if ( astChrMatch( cvt_string, "FK54Z" ) ) {
+      result = AST__SLA_FK54Z;
+
+   } else if ( astChrMatch( cvt_string, "AMP" ) ) {
+      result = AST__SLA_AMP;
+
+   } else if ( astChrMatch( cvt_string, "MAP" ) ) {
+      result = AST__SLA_MAP;
+
+   } else if ( astChrMatch( cvt_string, "ECLEQ" ) ) {
+      result = AST__SLA_ECLEQ;
+
+   } else if ( astChrMatch( cvt_string, "EQECL" ) ) {
+      result = AST__SLA_EQECL;
+
+   } else if ( astChrMatch( cvt_string, "GALEQ" ) ) {
+      result = AST__SLA_GALEQ;
+
+   } else if ( astChrMatch( cvt_string, "EQGAL" ) ) {
+      result = AST__SLA_EQGAL;
+
+   } else if ( astChrMatch( cvt_string, "FK5HZ" ) ) {
+      result = AST__SLA_FK5HZ;
+
+   } else if ( astChrMatch( cvt_string, "HFK5Z" ) ) {
+      result = AST__SLA_HFK5Z;
+
+   } else if ( astChrMatch( cvt_string, "GALSUP" ) ) {
+      result = AST__SLA_GALSUP;
+
+   } else if ( astChrMatch( cvt_string, "SUPGAL" ) ) {
+      result = AST__SLA_SUPGAL;
+
+   } else if ( astChrMatch( cvt_string, "HPCEQ" ) ) {
+      result = AST__HPCEQ;
+
+   } else if ( astChrMatch( cvt_string, "EQHPC" ) ) {
+      result = AST__EQHPC;
+
+   } else if ( astChrMatch( cvt_string, "HPREQ" ) ) {
+      result = AST__HPREQ;
+
+   } else if ( astChrMatch( cvt_string, "EQHPR" ) ) {
+      result = AST__EQHPR;
+
+   } else if ( astChrMatch( cvt_string, "HEEQ" ) ) {
+      result = AST__HEEQ;
+
+   } else if ( astChrMatch( cvt_string, "EQHE" ) ) {
+      result = AST__EQHE;
+
+   } else if ( astChrMatch( cvt_string, "J2000H" ) ) {
+      result = AST__J2000H;
+
+   } else if ( astChrMatch( cvt_string, "HJ2000" ) ) {
+      result = AST__HJ2000;
+
+   } else if ( astChrMatch( cvt_string, "H2E" ) ) {
+      result = AST__SLA_DH2E;
+
+   } else if ( astChrMatch( cvt_string, "E2H" ) ) {
+      result = AST__SLA_DE2H;
+
+   } else if ( astChrMatch( cvt_string, "R2H" ) ) {
+      result = AST__R2H;
+
+   } else if ( astChrMatch( cvt_string, "H2R" ) ) {
+      result = AST__H2R;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *CvtString( int cvt_code, const char **comment,
+                              int *nargs, const char *arg[ MAX_SLA_ARGS ], int *status ) {
+/*
+*  Name:
+*     CvtString
+
+*  Purpose:
+*     Convert a conversion type from a code value to a string representation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     const char *CvtString( int cvt_code, const char **comment,
+*                            int *nargs, const char *arg[ MAX_SLA_ARGS ], int *status )
+
+*  Class Membership:
+*     SlaMap member function.
+
+*  Description:
+*     This function accepts a code value used to represent one of the
+*     SLALIB sky coordinate conversions and converts it into an
+*     equivalent string representation. It also returns a descriptive
+*     comment and information about the arguments required in order to
+*     perform the conversion.
+
+*  Parameters:
+*     cvt_code
+*        The conversion code.
+*     comment
+*        Address of a location to return a pointer to a constant
+*        null-terminated string containing a description of the
+*        conversion.
+*     nargs
+*        Address of an int in which to return the number of arguments
+*        required in order to perform the conversion (may be zero).
+*     arg
+*        An array in which to return a pointer to a constant
+*        null-terminated string for each argument (above) containing a
+*        description of what each argument represents.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string representation of
+*     the conversion code value supplied. If the code supplied is not
+*     valid, a NULL pointer will be returned, without error.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Result pointer to return */
+
+/* Initialise the returned values. */
+   *comment = NULL;
+   *nargs = 0;
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Test for each valid code value in turn and assign the appropriate
+   return values. */
+   switch ( cvt_code ) {
+
+   case AST__SLA_ADDET:
+      result = "ADDET";
+      *comment = "Add E-terms of aberration";
+      *nargs = 1;
+      arg[ 0 ] = "Besselian epoch of mean equinox (FK4)";
+      break;
+
+   case AST__SLA_SUBET:
+      result = "SUBET";
+      *comment = "Subtract E-terms of aberration";
+      *nargs = 1;
+      arg[ 0 ] = "Besselian epoch of mean equinox (FK4)";
+      break;
+
+   case AST__SLA_PREBN:
+      result = "PREBN";
+      *comment = "Apply Bessel-Newcomb (FK4) precession";
+      *nargs = 2;
+      arg[ 0 ] = "From Besselian epoch";
+      arg[ 1 ] = "To Besselian epoch";
+      break;
+
+   case AST__SLA_PREC:
+      result = "PREC";
+      *comment = "Apply IAU 1975 (FK5) precession";
+      *nargs = 2;
+      arg[ 0 ] = "From Julian epoch";
+      arg[ 1 ] = "To Julian epoch";
+      break;
+
+   case AST__SLA_FK45Z:
+      result = "FK45Z";
+      *comment = "FK4 to FK5 J2000.0 (no PM or parallax)";
+      arg[ 0 ] = "Besselian epoch of FK4 coordinates";
+      *nargs = 1;
+      break;
+
+   case AST__SLA_FK54Z:
+      result = "FK54Z";
+      *comment = "FK5 J2000.0 to FK4 (no PM or parallax)";
+      *nargs = 1;
+      arg[ 0 ] = "Besselian epoch of FK4 system";
+      break;
+
+   case AST__SLA_AMP:
+      result = "AMP";
+      *comment = "Geocentric apparent to mean place (FK5)";
+      *nargs = 2;
+      arg[ 0 ] = "TDB of apparent place (as MJD)";
+      arg[ 1 ] = "Julian epoch of mean equinox (FK5)";
+      break;
+
+   case AST__SLA_MAP:
+      result = "MAP";
+      *comment = "Mean place (FK5) to geocentric apparent";
+      *nargs = 2;
+      arg[ 0 ] = "Julian epoch of mean equinox (FK5)";
+      arg[ 1 ] = "TDB of apparent place (as MJD)";
+      break;
+
+   case AST__SLA_ECLEQ:
+      result = "ECLEQ";
+      *comment = "Ecliptic (IAU 1980) to J2000.0 equatorial (FK5)";
+      *nargs = 1;
+      arg[ 0 ] = "TDB of mean ecliptic (as MJD)";
+      break;
+
+   case AST__SLA_EQECL:
+      result = "EQECL";
+      *comment = "Equatorial J2000.0 (FK5) to ecliptic (IAU 1980)";
+      *nargs = 1;
+      arg[ 0 ] = "TDB of mean ecliptic (as MJD)";
+      break;
+
+   case AST__SLA_GALEQ:
+      result = "GALEQ";
+      *comment = "Galactic (IAU 1958) to J2000.0 equatorial (FK5)";
+      *nargs = 0;
+      break;
+
+   case AST__SLA_EQGAL:
+      result = "EQGAL";
+      *comment = "J2000.0 equatorial (FK5) to galactic (IAU 1958)";
+      *nargs = 0;
+      break;
+
+   case AST__SLA_FK5HZ:
+      result = "FK5HZ";
+      *comment = "J2000.0 FK5 to ICRS (no PM or parallax)";
+      arg[ 0 ] = "Julian epoch of FK5 coordinates";
+      *nargs = 1;
+      break;
+
+   case AST__SLA_HFK5Z:
+      result = "HFK5Z";
+      *comment = "ICRS to J2000.0 FK5 (no PM or parallax)";
+      arg[ 0 ] = "Julian epoch of FK5 coordinates";
+      *nargs = 1;
+      break;
+
+   case AST__SLA_GALSUP:
+      result = "GALSUP";
+      *comment = "Galactic (IAU 1958) to supergalactic";
+      *nargs = 0;
+      break;
+
+   case AST__SLA_SUPGAL:
+      result = "SUPGAL";
+      *comment = "Supergalactic to galactic (IAU 1958)";
+      *nargs = 0;
+      break;
+
+   case AST__HPCEQ:
+      result = "HPCEQ";
+      *comment = "Helioprojective-Cartesian to J2000.0 equatorial (FK5)";
+      *nargs = 4;
+      arg[ 0 ] = "Modified Julian Date of observation";
+      arg[ 1 ] = "Heliocentric-Aries-Ecliptic X value at observer";
+      arg[ 2 ] = "Heliocentric-Aries-Ecliptic Y value at observer";
+      arg[ 3 ] = "Heliocentric-Aries-Ecliptic Z value at observer";
+      break;
+
+   case AST__EQHPC:
+      result = "EQHPC";
+      *comment = "J2000.0 equatorial (FK5) to Helioprojective-Cartesian";
+      *nargs = 4;
+      arg[ 0 ] = "Modified Julian Date of observation";
+      arg[ 1 ] = "Heliocentric-Aries-Ecliptic X value at observer";
+      arg[ 2 ] = "Heliocentric-Aries-Ecliptic Y value at observer";
+      arg[ 3 ] = "Heliocentric-Aries-Ecliptic Z value at observer";
+      break;
+
+   case AST__HPREQ:
+      result = "HPREQ";
+      *comment = "Helioprojective-Radial to J2000.0 equatorial (FK5)";
+      *nargs = 4;
+      arg[ 0 ] = "Modified Julian Date of observation";
+      arg[ 1 ] = "Heliocentric-Aries-Ecliptic X value at observer";
+      arg[ 2 ] = "Heliocentric-Aries-Ecliptic Y value at observer";
+      arg[ 3 ] = "Heliocentric-Aries-Ecliptic Z value at observer";
+      break;
+
+   case AST__EQHPR:
+      result = "EQHPR";
+      *comment = "J2000.0 equatorial (FK5) to Helioprojective-Radial";
+      *nargs = 4;
+      arg[ 0 ] = "Modified Julian Date of observation";
+      arg[ 1 ] = "Heliocentric-Aries-Ecliptic X value at observer";
+      arg[ 2 ] = "Heliocentric-Aries-Ecliptic Y value at observer";
+      arg[ 3 ] = "Heliocentric-Aries-Ecliptic Z value at observer";
+      break;
+
+   case AST__HEEQ:
+      result = "HEEQ";
+      *comment = "Helio-ecliptic to equatorial";
+      *nargs = 1;
+      arg[ 0 ] = "Modified Julian Date of observation";
+      break;
+
+   case AST__EQHE:
+      result = "EQHE";
+      *comment = "Equatorial to helio-ecliptic";
+      *nargs = 1;
+      arg[ 0 ] = "Modified Julian Date of observation";
+      break;
+
+   case AST__J2000H:
+      result = "J2000H";
+      *comment = "J2000 equatorial (dynamical) to ICRS";
+      *nargs = 0;
+      break;
+
+   case AST__HJ2000:
+      result = "HJ2000";
+      *comment = "ICRS to J2000 equatorial (dynamical)";
+      *nargs = 0;
+      break;
+
+   case AST__SLA_DH2E:
+      result = "H2E";
+      *comment = "Horizon to equatorial";
+      *nargs = 2;
+      arg[ 0 ] = "Geodetic latitude of observer";
+      arg[ 1 ] = "Magnitude of diurnal aberration vector";
+      break;
+
+   case AST__SLA_DE2H:
+      result = "E2H";
+      *comment = "Equatorial to horizon";
+      *nargs = 2;
+      arg[ 0 ] = "Geodetic latitude of observer";
+      arg[ 1 ] = "Magnitude of diurnal aberration vector";
+      break;
+
+   case AST__R2H:
+      result = "R2H";
+      *comment = "RA to Hour Angle";
+      *nargs = 1;
+      arg[ 0 ] = "Local apparent sidereal time (radians)";
+      break;
+
+   case AST__H2R:
+      result = "H2R";
+      *comment = "Hour Angle to RA";
+      *nargs = 1;
+      arg[ 0 ] = "Local apparent sidereal time (radians)";
+      break;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void Earth( double mjd, double earth[3], int *status ) {
+/*
+*+
+*  Name:
+*     Earth
+
+*  Purpose:
+*     Returns the AST__HAEC position of the earth at the specified time.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void Earth( double mjd, double earth[3], int *status )
+
+*  Class Membership:
+*     SlaMap method.
+
+*  Description:
+*     This function returns the AST__HAEC position of the earth at the
+*     specified time. See astSTPConv for a description of the AST__HAEC
+*     coordinate systems.
+
+*  Parameters:
+*     mjd
+*        Modified Julian date.
+*     earth
+*        The AST__HAEC position of the earth at the given date.
+*-
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   double dpb[3];     /* Earth position (barycentric) */
+   double dph[3];     /* Earth position (heliocentric) */
+   double dvb[3];     /* Earth velocity (barycentric) */
+   double dvh[3];     /* Earth velocity (heliocentric, AST__HAQC) */
+   double ecmat[3][3];/* Equatorial to ecliptic matrix */
+   int i;             /* Loop count */
+
+/* Initialize. */
+   for( i = 0; i < 3; i++ ) earth[ i ] = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the position of the earth at the given date in the AST__HAQC coord
+   system (dph). */
+   palEvp( mjd, 2000.0, dvb, dpb, dvh, dph );
+
+/* Now rotate the earths position vector into AST__HAEC coords. */
+   palEcmat( palEpj2d( 2000.0 ), ecmat );
+   palDmxv( ecmat, dph, earth );
+
+/* Convert from AU to metres. */
+   earth[0] *= AST__AU;
+   earth[1] *= AST__AU;
+   earth[2] *= AST__AU;
+
+}
+
+static void Hgc( double mjd, double mat[3][3], double offset[3], int *status ) {
+/*
+*+
+*  Name:
+*     Hgc
+
+*  Purpose:
+*     Returns matrix and offset for converting AST__HGC positions to AST__HAEC.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void Hgc( double mjd, double mat[3][3], double offset[3], int *status )
+
+*  Class Membership:
+*     SlaMap method.
+
+*  Description:
+*     This function returns a 3x3 matrix which rotates direction vectors
+*     given in the AST__HGC system to the AST__HAEC system at the
+*     specified date. It also returns the position of the origin of the
+*     AST__HGC system as an AST__HAEC position. See astSTPConv for a
+*     description of these coordinate systems.
+
+*  Parameters:
+*     mjd
+*        Modified Julian date defining the coordinate systems.
+*     mat
+*        Matrix which rotates from AST__HGC to AST__HAEC.
+*     offset
+*        The origin of the AST__HGC system within the AST__HAEC system.
+*-
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   double earth[3];   /* Earth position (heliocentric, AST__HAEC) */
+   double len;        /* Vector length */
+   double xhg[3];     /* Unix X vector of AST__HGC system in AST__HAEC */
+   double yhg[3];     /* Unix Y vector of AST__HGC system in AST__HAEC */
+   double ytemp[3];   /* Un-normalized Y vector */
+   double zhg[3];     /* Unix Z vector of AST__HGC system in AST__HAEC */
+   int i;             /* Loop count */
+   int j;             /* Loop count */
+
+/* Initialize. */
+   for( i = 0; i < 3; i++ ) {
+      for( j = 0; j < 3; j++ ) {
+         mat[i][j] = (i==j)?1.0:0.0;
+      }
+      offset[ i ] = 0.0;
+   }
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a unit vector parallel to the solar north pole at the given date.
+   This vector is expressed in AST__HAEC coords. This is the Z axis of the
+   AST__HGC system. */
+   SolarPole( mjd, zhg, status );
+
+/* Get the position of the earth at the given date in the AST__HAEC coord
+   system. */
+   Earth( mjd, earth, status );
+
+/* The HG Y axis is perpendicular to both the polar axis and the
+   sun-earth line. Obtain a Y vector by taking the cross product of the
+   two vectors, and then normalize it into a unit vector. */
+   palDvxv( zhg, earth, ytemp );
+   palDvn( ytemp, yhg, &len );
+
+/* The HG X axis is perpendicular to both Z and Y, */
+   palDvxv( yhg, zhg, xhg );
+
+/* The HG X, Y and Z unit vectors form the columns of the required matrix.
+   The origins of the two systems are co-incident, so return the zero offset
+   vector initialised earlier. */
+   for( i = 0; i < 3; i++ ) {
+      mat[ i ][ 0 ] = xhg[ i ];
+      mat[ i ][ 1 ] = yhg[ i ];
+      mat[ i ][ 2 ] = zhg[ i ];
+   }
+
+}
+
+static void Gsec( double mjd, double mat[3][3], double offset[3], int *status ) {
+/*
+*+
+*  Name:
+*     Gsec
+
+*  Purpose:
+*     Returns matrix and offset for converting AST__GSEC positions to AST__HAEC.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void Gsec( double mjd, double mat[3][3], double offset[3], int *status )
+
+*  Class Membership:
+*     SlaMap method.
+
+*  Description:
+*     This function returns a 3x3 matrix which rotates direction vectors
+*     given in the AST__GSEC system to the AST__HAEC system at the
+*     specified date. It also returns the position of the origin of the
+*     AST__GSEC system as an AST__HAEC position. See astSTPConv for a
+*     description of these coordinate systems.
+
+*  Parameters:
+*     mjd
+*        Modified Julian date defining the coordinate systems.
+*     mat
+*        Matrix which rotates from AST__GSEC to AST__HAEC.
+*     offset
+*        The origin of the AST__GSEC system within the AST__HAEC system.
+*-
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   double earth[3];   /* Earth position (heliocentric, AST__HAEC) */
+   double pole[3];    /* Solar pole (AST__HAEC) */
+   double len;        /* Vector length */
+   double xgs[3];     /* Unix X vector of AST__GSEC system in AST__HAEC */
+   double ygs[3];     /* Unix Y vector of AST__GSEC system in AST__HAEC */
+   double ytemp[3];   /* Un-normalized Y vector */
+   double zgs[3];     /* Unix Z vector of AST__GSEC system in AST__HAEC */
+   int i;             /* Loop count */
+   int j;             /* Loop count */
+
+/* Initialize. */
+   for( i = 0; i < 3; i++ ) {
+      for( j = 0; j < 3; j++ ) {
+         mat[i][j] = (i==j)?1.0:0.0;
+      }
+      offset[ i ] = 0.0;
+   }
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the position of the earth at the given date in the AST__HAEC coord
+   system. */
+   Earth( mjd, earth, status );
+
+/* We need to find unit vectors parallel to the GSEC (X,Y,Z) axes, expressed
+   in terms of the AST__HAEC (X,Y,Z) axes. The GSEC X axis starts at the
+   earth and passes through the centre of the sun. This is just the
+   normalized opposite of the earth's position vector. */
+   palDvn( earth, xgs, &len );
+   xgs[0] *= -1.0;
+   xgs[1] *= -1.0;
+   xgs[2] *= -1.0;
+
+/* The GSEC Y axis is perpendicular to both the X axis and the ecliptic north
+   pole vector. So find the ecliptic north pole vector in AST__HAEC coords. */
+   pole[ 0 ] = 0.0;
+   pole[ 1 ] = 0.0;
+   pole[ 2 ] = 1.0;
+
+/* Find the GSEC Y axis by taking the vector product of the X axis and
+   the ecliptic north pole vector, and then normalize it into a unit
+   vector. */
+   palDvxv( pole, xgs, ytemp );
+   palDvn( ytemp, ygs, &len );
+
+/* The GSEC Z axis is perpendicular to both X and Y axis, and forms a
+   right-handed system. The resulting vector will be of unit length
+   since the x and y vectors are both of unit length, and are
+   perpendicular to each other. It therefore does not need to be
+   normalized.*/
+   palDvxv( xgs, ygs, zgs );
+
+/* The GSEC X, Y and Z unit vectors form the columns of the required matrix. */
+   for( i = 0; i < 3; i++ ) {
+      mat[ i ][ 0 ] = xgs[ i ];
+      mat[ i ][ 1 ] = ygs[ i ];
+      mat[ i ][ 2 ] = zgs[ i ];
+      offset[i] = earth[ i ];
+   }
+
+}
+
+static void Haec( double mjd, double mat[3][3], double offset[3], int *status ) {
+/*
+*+
+*  Name:
+*     Haec
+
+*  Purpose:
+*     Returns matrix and offset for converting AST__HAEC positions to AST__HAEC.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void Haec( double mjd, double mat[3][3], double offset[3], int *status )
+
+*  Class Membership:
+*     SlaMap method.
+
+*  Description:
+*     This function returns a 3x3 matrix which rotates direction vectors
+*     given in the AST__HAEC system to the AST__HAEC system at the
+*     specified date. It also returns the position of the origin of the
+*     AST__HAEC system as an AST__HAEC position. See astSTPConv for a
+*     description of these coordinate systems.
+
+*  Parameters:
+*     mjd
+*        Modified Julian date defining the coordinate systems.
+*     mat
+*        Matrix which rotates from AST__HAEC to AST__HAEC.
+*     offset
+*        The origin of the AST__HAEC system within the AST__HAEC system.
+*-
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int i;             /* Loop count */
+   int j;             /* Loop count */
+
+/* Return an identity matrix and a zero offset vector. */
+   for( i = 0; i < 3; i++ ) {
+      for( j = 0; j < 3; j++ ) {
+         mat[i][j] = (i==j)?1.0:0.0;
+      }
+      offset[ i ] = 0.0;
+   }
+
+}
+
+static void Haqc( double mjd, double mat[3][3], double offset[3], int *status ) {
+/*
+*+
+*  Name:
+*     Haqc
+
+*  Purpose:
+*     Returns matrix and offset for converting AST__HAQC positions to AST__HAEC.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void Haqc( double mjd, double mat[3][3], double offset[3], int *status )
+
+*  Class Membership:
+*     SlaMap method.
+
+*  Description:
+*     This function returns a 3x3 matrix which rotates direction vectors
+*     given in the AST__HAQC system to the AST__HAEC system at the
+*     specified date. It also returns the position of the origin of the
+*     AST__HAQC system as an AST__HAEC position. See astSTPConv for a
+*     description of these coordinate systems.
+
+*  Parameters:
+*     mjd
+*        Modified Julian date defining the coordinate systems.
+*     mat
+*        Matrix which rotates from AST__HAQC to AST__HAEC.
+*     offset
+*        The origin of the AST__HAQC system within the AST__HAEC system.
+*-
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int i;             /* Loop count */
+   int j;             /* Loop count */
+
+/* Initialise an identity matrix and a zero offset vector. */
+   for( i = 0; i < 3; i++ ) {
+      for( j = 0; j < 3; j++ ) {
+         mat[i][j] = (i==j)?1.0:0.0;
+      }
+      offset[ i ] = 0.0;
+   }
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Return the required matrix. */
+   palEcmat( palEpj2d( 2000.0 ), mat );
+   return;
+}
+
+static void Hpcc( double mjd, double obs[3], double mat[3][3], double offset[3], int *status ) {
+/*
+*+
+*  Name:
+*     Hpcc
+
+*  Purpose:
+*     Returns matrix and offset for converting AST__HPCC positions to
+*     AST__HAEC.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void Hpcc( double mjd, double obs[3], double mat[3][3], double offset[3], int *status )
+
+*  Class Membership:
+*     SlaMap method.
+
+*  Description:
+*     This function returns a 3x3 matrix which rotates direction vectors
+*     given in the AST__HPCC system to the AST__HAEC system at the
+*     specified date. It also returns the position of the origin of the
+*     AST__HPCC system as an AST__HAEC position. See astSTPConv for a
+*     description of these coordinate systems.
+
+*  Parameters:
+*     mjd
+*        Modified Julian date defining the coordinate systems.
+*     obs
+*        The observers position, in AST__HAEC, or NULL if the observer is
+*        at the centre of the earth.
+*     mat
+*        Matrix which rotates from AST__HPCC to AST__HAEC.
+*     offset
+*        The origin of the AST__HPCC system within the AST__HAEC system.
+*-
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   double earth[3];   /* Earth position (heliocentric, AST__HAEC) */
+   double pole[3];    /* Solar pole vector (AST__HAEC) */
+   double len;        /* Vector length */
+   double xhpc[3];    /* Unix X vector of AST__HPCC system in AST__HAEC */
+   double yhpc[3];    /* Unix Y vector of AST__HPCC system in AST__HAEC */
+   double ytemp[3];   /* Un-normalized Y vector */
+   double zhpc[3];    /* Unix Z vector of AST__HPCC system in AST__HAEC */
+   int i;             /* Loop count */
+   int j;             /* Loop count */
+
+/* Initialize. */
+   for( i = 0; i < 3; i++ ) {
+      for( j = 0; j < 3; j++ ) {
+         mat[i][j] = (i==j)?1.0:0.0;
+      }
+      offset[i] = 0.0;
+   }
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If no observers position was supplied, use the position of the earth
+   at the specified date in AST__HAEC coords. */
+   if( !obs ) {
+      Earth( mjd, earth, status );
+      obs = earth;
+   }
+
+/* We need to find unit vectors parallel to the HPCC (X,Y,Z) axes, expressed
+   in terms of the AST__HAEC (X,Y,Z) axes. The HPCC X axis starts at the
+   observer and passes through the centre of the sun. This is just the
+   normalized opposite of the supplied observer's position vector. */
+   palDvn( obs, xhpc, &len );
+   xhpc[0] *= -1.0;
+   xhpc[1] *= -1.0;
+   xhpc[2] *= -1.0;
+
+/* The HPC Y axis is perpendicular to both the X axis and the solar north
+   pole vector. So find the solar north pole vector in AST__HAEC coords. */
+   SolarPole( mjd, pole, status );
+
+/* Find the HPC Y axis by taking the vector product of the X axis and
+   the solar north pole vector, and then normalize it into a unit vector.
+   Note, HPC (X,Y,Z) axes form a left-handed system! */
+   palDvxv( xhpc, pole, ytemp );
+   palDvn( ytemp, yhpc, &len );
+
+/* The HPC Z axis is perpendicular to both X and Y axis, and forms a
+   left-handed system. The resulting vector will be of unit length
+   since the x and y vectors are both of unit length, and are
+   perpendicular to each other. It therefore does not need to be
+   normalized.*/
+   palDvxv( yhpc, xhpc, zhpc );
+
+/* The HPC X, Y and Z unit vectors form the columns of the required matrix. */
+   for( i = 0; i < 3; i++ ) {
+      mat[ i ][ 0 ] = xhpc[ i ];
+      mat[ i ][ 1 ] = yhpc[ i ];
+      mat[ i ][ 2 ] = zhpc[ i ];
+      offset[i] = obs[ i ];
+   }
+
+}
+
+static void Hprc( double mjd, double obs[3], double mat[3][3], double offset[3], int *status ) {
+/*
+*+
+*  Name:
+*     Hprc
+
+*  Purpose:
+*     Returns matrix and offset for converting AST__HPRC positions to
+*     AST__HAEC.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void Hprc( double mjd, double obs[3], double mat[3][3], double offset[3], int *status )
+
+*  Class Membership:
+*     SlaMap method.
+
+*  Description:
+*     This function returns a 3x3 matrix which rotates direction vectors
+*     given in the AST__HPRC system to the AST__HAEC system at the
+*     specified date. It also returns the position of the origin of the
+*     AST__HPRC system as an AST__HAEC position. See astSTPConv for a
+*     description of these coordinate systems.
+
+*  Parameters:
+*     mjd
+*        Modified Julian date defining the coordinate systems.
+*     obs
+*        The observers position, in AST__HAEC, or NULL if the observer is
+*        at the centre of the earth.
+*     mat
+*        Matrix which rotates from AST__HPRC to AST__HAEC.
+*     offset
+*        The origin of the AST__HPRC system within the AST__HAEC system.
+*-
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   double pole[3];    /* Solar pole (AST__HAEC) */
+   double earth[3];   /* Earth position (heliocentric, AST__HAEC) */
+   double len;        /* Vector length */
+   double xhpr[3];    /* Unix X vector of AST__HPRC system in AST__HAEC */
+   double yhpr[3];    /* Unix Y vector of AST__HPRC system in AST__HAEC */
+   double ytemp[3];   /* Un-normalized Y vector */
+   double zhpr[3];    /* Unix Z vector of AST__HPRC system in AST__HAEC */
+   int i;             /* Loop count */
+   int j;             /* Loop count */
+
+/* Initialize. */
+   for( i = 0; i < 3; i++ ) {
+      for( j = 0; j < 3; j++ ) {
+         mat[i][j] = (i==j)?1.0:0.0;
+      }
+      offset[i] = 0.0;
+   }
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If no observers position was supplied, use the position of the earth
+   at the specified date in AST__HAEC coords. */
+   if( !obs ) {
+      Earth( mjd, earth, status );
+      obs = earth;
+   }
+
+/* We need to find unit vectors parallel to the HPRC (X,Y,Z) axes, expressed
+   in terms of the AST__HAEC (X,Y,Z) axes. The HPRC Z axis starts at the
+   observer and passes through the centre of the sun. This is just the
+   normalized opposite of the supplied observer's position vector. */
+   palDvn( obs, zhpr, &len );
+   zhpr[0] *= -1.0;
+   zhpr[1] *= -1.0;
+   zhpr[2] *= -1.0;
+
+/* The HPR Y axis is perpendicular to both the Z axis and the solar north
+   pole vector. So find the solar north pole vector in AST__HAEC coords. */
+   SolarPole( mjd, pole, status );
+
+/* Find the HPR Y axis by taking the vector product of the Z axis and
+   the solar north pole vector, and then normalize it into a unit vector.
+   Note, HPR (X,Y,Z) axes form a left-handed system! */
+   palDvxv( pole, zhpr, ytemp );
+   palDvn( ytemp, yhpr, &len );
+
+/* The HPRC X axis is perpendicular to both Y and Z axis, and forms a
+   left-handed system. The resulting vector will be of unit length
+   since the y and z vectors are both of unit length, and are
+   perpendicular to each other. It therefore does not need to be
+   normalized.*/
+   palDvxv( zhpr, yhpr, xhpr );
+
+/* The HPRC X, Y and Z unit vectors form the columns of the required matrix. */
+   for( i = 0; i < 3; i++ ) {
+      mat[ i ][ 0 ] = xhpr[ i ];
+      mat[ i ][ 1 ] = yhpr[ i ];
+      mat[ i ][ 2 ] = zhpr[ i ];
+      offset[ i ] = obs[ i ];
+   }
+}
+
+static void J2000H( int forward, int npoint, double *alpha, double *delta, int *status ){
+/*
+*  Name:
+*     J2000H
+
+*  Purpose:
+*     Convert dynamical J2000 equatorial coords to ICRS.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void J2000H( int forward, int npoint, double *alpha, double *delta, int *status )
+
+*  Class Membership:
+*     SlaMap method.
+
+*  Description:
+*     This function converts the supplied dynamical J2000 equatorial coords
+*     to ICRS (or vice-versa).
+
+*  Parameters:
+*     forward
+*        Do forward transformation?
+*     npoint
+*        Number of points to transform.
+*     alpha
+*        Pointer to longitude values.
+*     delta
+*        Pointer to latitude values.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   int i;                  /* Loop count */
+   double rmat[3][3];      /* J2000 -> ICRS rotation matrix */
+   double v1[3];           /* J2000 vector */
+   double v2[3];           /* ICRS vector */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the J2000 to ICRS rotation matrix (supplied by P.T. Wallace) */
+   palDeuler( "XYZ", -0.0068192*AS2R, 0.0166172*AS2R, 0.0146000*AS2R,
+              rmat );
+
+/* Loop round all points. */
+   for( i = 0; i < npoint; i++ ) {
+
+/* Convert from (alpha,delta) to 3-vector */
+      palDcs2c( alpha[ i ], delta[ i ], v1 );
+
+/* Rotate the 3-vector */
+      if( forward ) {
+         palDmxv( rmat, v1, v2 );
+      } else {
+         palDimxv( rmat, v1, v2 );
+      }
+
+/* Convert from 3-vector to (alpha,delta) */
+      palDcc2s( v2, alpha + i, delta + i );
+   }
+}
+
+void astSTPConv1_( double mjd, int in_sys, double in_obs[3], double in[3],
+                   int out_sys, double out_obs[3], double out[3], int *status ){
+/*
+*+
+*  Name:
+*     astSTPConv1
+
+*  Purpose:
+*     Converts a 3D solar system position between specified STP coordinate
+*     systems.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void astSTPConv1( double mjd, int in_sys, double in_obs[3],
+*                       double in[3], int out_sys, double out_obs[3],
+*                       double out[3] )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function converts a single 3D solar-system position from the
+*     specified input coordinate system to the specified output coordinate
+*     system. See astSTPConv for a list of supported coordinate systems.
+
+*  Parameters:
+*     mjd
+*        The Modified Julian Date to which the coordinate systems refer.
+*     in_sys
+*        The coordinate system in which the input positions are supplied.
+*     in_obs
+*        The position of the observer in AST__HAEC coordinates. This is only
+*        needed if the input system is an observer-centric system. If this
+*        is not the case, a NULL pointer can be supplied. A NULL pointer
+*        can also be supplied to indicate that he observer is at the centre of
+*        the earth at the specified date.
+*     in
+*        A 3-element array holding the input position.
+*     out_sys
+*        The coordinate system in which the input positions are supplied.
+*     out_obs
+*        The position of the observer in AST__HAEC coordinates. This is only
+*        needed if the output system is an observer-centric system. If this
+*        is not the case, a NULL pointer can be supplied. A NULL pointer
+*        can also be supplied to indicate that he observer is at the centre of
+*        the earth at the specified date.
+*     out
+*        A 3-element array holding the output position.
+
+*  Notes:
+*     - The "in" and "out" arrays may safely point to the same memory.
+*     - Output longitude values are always in the range 0 - 2.PI.
+
+*-
+*/
+
+/* Local Variables: */
+   double *ins[ 3 ];      /* The input position */
+   double *outs[ 3 ];     /* The output position */
+
+/* Store pointers to the supplied arrays suitable for passing to STPConv. */
+   ins[ 0 ] = in;
+   ins[ 1 ] = in + 1;
+   ins[ 2 ] = in + 2;
+   outs[ 0 ] = out;
+   outs[ 1 ] = out + 1;
+   outs[ 2 ] = out + 2;
+
+/* Convert the position. */
+   STPConv( mjd, 0, 1, in_sys, in_obs, ins, out_sys, out_obs, outs, status );
+
+}
+
+void astSTPConv_( double mjd, int n, int in_sys, double in_obs[3],
+                  double *in[3], int out_sys, double out_obs[3],
+                  double *out[3], int *status ){
+/*
+*+
+*  Name:
+*     astSTPConv
+
+*  Purpose:
+*     Converts a set of 3D solar system positions between specified STP
+*     coordinate systems.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void astSTPConv( double mjd, int n, int in_sys, double in_obs[3],
+*                      double *in[3], int out_sys, double out_obs[3],
+*                      double *out[3] )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function converts a set of 3D solar-system positions from
+*     the specified input coordinate system to the specified output
+*     coordinate system.
+
+*  Parameters:
+*     mjd
+*        The Modified Julian Date to which the coordinate systems refer.
+*     in_sys
+*        The coordinate system in which the input positions are supplied
+*        (see below).
+*     in_obs
+*        The position of the observer in AST__HAEC coordinates. This is only
+*        needed if the input system is an observer-centric system. If this
+*        is not the case, a NULL pointer can be supplied. A NULL pointer
+*        can also be supplied to indicate that he observer is at the centre of
+*        the earth at the specified date.
+*     in
+*        A 3-element array holding the input positions. Each of the 3
+*        elements should point to an array of "n" axis values. For spherical
+*        input systems, in[3] can be supplied as NULL, in which case a
+*        constant value of 1 AU will be used.
+*     out_sys
+*        The coordinate system in which the input positions are supplied
+*        (see below).
+*     out_obs
+*        The position of the observer in AST__HAEC coordinates. This is only
+*        needed if the output system is an observer-centric system. If this
+*        is not the case, a NULL pointer can be supplied. A NULL pointer
+*        can also be supplied to indicate that he observer is at the centre of
+*        the earth at the specified date.
+*     out
+*        A 3-element array holding the output positions. Each of the 3
+*        elements should point to an array of "n" axis values. If in[3] is
+*        NULL, no values will be assigned to out[3].
+
+*  Notes:
+*     - The "in" and "out" arrays may safely point to the same memory.
+*     - Output longitude values are always in the range 0 - 2.PI.
+
+*  Supported Coordinate Systems:
+*     Coordinate systems are either spherical or Cartesian, and are right
+*     handed (unless otherwise indicated). Spherical systems use axis 0 for
+*     longitude, axis 1 for latitude, and axis 2 for radius. Cartesian systems
+*     use 3 mutually perpendicular axes; X is axis 0 and points towards the
+*     intersection of the equator and the zero longitude meridian of the
+*     corresponding spherical system, Y is axis 1 and points towards longitude
+*     of +90 degrees, Z is axis 2 and points twowards the north pole. All
+*     angles are in radians and all distances are in metres. The following
+*     systems are supported:
+*
+*     - AST__HAE: Heliocentric-aries-ecliptic spherical coordinates. Centred
+*     at the centre of the sun. The north pole points towards the J2000
+*     ecliptic north pole, and meridian of zero longitude includes the
+*     J2000 equinox.
+*
+*     - AST__HAEC: Heliocentric-aries-ecliptic cartesian coordinates. Origin
+*     at the centre of the sun. The Z axis points towards the J2000 ecliptic
+*     north pole, and the X axis points towards the J2000 equinox.
+*
+*     - AST__HAQ: Heliocentric-aries-equatorial spherical coordinates. Centred
+*     at the centre of the sun. The north pole points towards the FK5 J2000
+*     equatorial north pole, and meridian of zero longitude includes the
+*     FK5 J2000 equinox.
+*
+*     - AST__HAQC: Heliocentric-aries-equatorial cartesian coordinates. Origin
+*     at the centre of the sun. The Z axis points towards the FK5 J2000
+*     equatorial north pole, and the X axis points towards the FK5 J2000
+*     equinox.
+*
+*     - AST__HG: Heliographic spherical coordinates. Centred at the centre of
+*     the sun. North pole points towards the solar north pole at the given
+*     date. The meridian of zero longitude includes the sun-earth line at
+*     the given date.
+*
+*     - AST__HGC: Heliographic cartesian coordinates. Origin at the centre of
+*     the sun. The Z axis points towards the solar north pole at the given
+*     date. The X axis is in the plane spanned by the Z axis, and the
+*     sun-earth line at the given date.
+*
+*     - AST__HPC: Helioprojective-cartesian spherical coordinates. A
+*     left-handed system (that is, longitude increases westwards), centred
+*     at the specified observer position. The intersection of the
+*     zero-longitude meridian and the equator coincides with the centre of
+*     the sun as seen from the observers position. The zero longitude
+*     meridian includes the solar north pole at the specified date.
+*
+*     - AST__HPCC: Helioprojective-cartesian cartesian coordinates. A
+*     left-handed system with origin at the specified observer position. The
+*     X axis points towards the centre of the sun as seen from the observers
+*     position. The X-Z plane includes the solar north pole at the specified
+*     date.
+*
+*     - AST__HPR: Helioprojective-radial spherical coordinates. A left-handed
+*     system (that is, longitude increases westwards), centred at the
+*     specified observer position. The north pole points towards the centre
+*     of the sun as seen from the observers position. The zero longitude
+*     meridian includes the solar north pole at the specified date.
+*
+*     - AST__HPRC: Helioprojective-radial cartesian coordinates. A left-handed
+*     system with origin at the specified observer position. The Z axis points
+*     towards the centre of the sun as seen from the observers position. The
+*     X-Z plane includes the solar north pole at the specified date.
+*
+*     - AST__GSE: Geocentric-solar-ecliptic spherical coordinates. Centred at
+*     the centre of the earth at the given date. The north pole points towards
+*     the  J2000 ecliptic north pole, and the meridian of zero longitude
+*     includes the Sun.
+*
+*     - AST__GSEC: Geocentric-solar-ecliptic cartesian coordinates. Origin at
+*     the centre of the earth at the given date. The X axis points towards the
+*     centre of sun, and the X-Z plane contains the J2000 ecliptic north
+*     pole. Since the earth may not be exactly in the mean ecliptic of
+*     J2000, the Z axis will not in general correspond exactly to the
+*     ecliptic north pole.
+*-
+*/
+   STPConv( mjd, 0, n, in_sys, in_obs, in, out_sys, out_obs, out, status );
+}
+
+static void STPConv( double mjd, int ignore_origins, int n, int in_sys,
+                     double in_obs[3], double *in[3], int out_sys,
+                     double out_obs[3], double *out[3], int *status ){
+/*
+*  Name:
+*     STPConv
+
+*  Purpose:
+*     Convert a set of 3D solar system positions between specified STP
+*     coordinate systems.
+
+*  Type:
+*     Private member function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void STPConv( double mjd, int ignore_origins, int n, int in_sys,
+*                   double in_obs[3], double *in[3], int out_sys,
+*                   double out_obs[3], double *out[3], int *status ){
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+*     This function converts a set of 3D solar-system positions from
+*     the specified input coordinate system to the specified output
+*     coordinate system. See astSTPConv for a list of the available
+*     coordinate systems.
+
+*  Parameters:
+*     mjd
+*        The Modified Julian Date to which the coordinate systems refer.
+*     ignore_origins
+*        If non-zero, then the coordinate system definitions are modified so
+*        that all cartesian systems have the origin at the centre of the
+*        Sun. If zero, the correct origins are used for each individual
+*        system.
+*     n
+*        The number of positions to transform.
+*     in_sys
+*        The coordinate system in which the input positions are supplied
+*     in_obs
+*        The position of the observer in AST__HAEC coordinates. This is only
+*        needed if the input system is an observer-centric system. If this
+*        is not the case, a NULL pointer can be supplied. A NULL pointer
+*        can also be supplied to indicate that he observer is at the centre of
+*        the earth at the specified date.
+*     in
+*        A 3-element array holding the input positions. Each of the 3
+*        elements should point to an array of "n" axis values. For spherical
+*        input systems, in[3] can be supplied as NULL, in which case a
+*        constant value of 1 AU will be used.
+*     out_sys
+*        The coordinate system in which the input positions are supplied
+*        (see "Supported Coordinate Systems" below).
+*     out_obs
+*        The position of the observer in AST__HAEC coordinates. This is only
+*        needed if the output system is an observer-centric system. If this
+*        is not the case, a NULL pointer can be supplied. A NULL pointer
+*        can also be supplied to indicate that he observer is at the centre of
+*        the earth at the specified date.
+*     out
+*        A 3-element array holding the input positions. Each of the 3
+*        elements should point to an array of "n" axis values. For spherical
+*        output coordinates, out[2] may be NULL, in which case the output
+*        radius values are thrown away.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Output longitude values are always in the range 0 - 2.PI.
+*     - The "in" and "out" arrays may safely point to the same memory.
+*     - The contents of the output array is left unchanged if an error
+*     has already occurred.
+*/
+
+/* Local Variables: */
+   double *out2;      /* Pointer to output third axis values */
+   double *px;        /* Pointer to next X axis value */
+   double *py;        /* Pointer to next Y axis value */
+   double *pz;        /* Pointer to next Z axis value */
+   double lat;        /* Latitude value */
+   double lng;        /* Longitude value */
+   double mat1[3][3]; /* Input->HAEC rotation matrix */
+   double mat2[3][3]; /* Output->HAEC rotation matrix */
+   double mat3[3][3]; /* HAEC->output rotation matrix */
+   double mat4[3][3]; /* Input->output rotation matrix */
+   double off1[3];    /* Origin of input system in HAEC coords */
+   double off2[3];    /* Origin of output system in HAEC coords */
+   double off3[3];    /* HAEC vector from output origin to input origin */
+   double off4[3];    /* Position of input origin within output system */
+   double p[3];       /* Current position */
+   double q[3];       /* New position */
+   double radius;     /* Radius value */
+   int cur_sys;       /* Current system for output values */
+   int i;             /* Loop count */
+   int j;             /* Loop count */
+   int inCsys;        /* Input cartesian system */
+   int outCsys;       /* Output cartesian system */
+   size_t nbyte;      /* Amount of memory to copy */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If out[2] was supplied as null, allocate memory to hold third axis
+   values. Otherwise, use the supplied array. */
+   nbyte = n*sizeof( double );
+   if( !out[2] ) {
+      out2 = (double *) astMalloc( nbyte );
+   } else {
+      out2 = out[2];
+   }
+
+/* Copy the input data to the output data and note that the output values
+   are currently in the same system as the input values. */
+   memcpy ( out[ 0 ], in[ 0 ], nbyte );
+   memcpy ( out[ 1 ], in[ 1 ], nbyte );
+   if( in[2] ) {
+      memcpy ( out2, in[ 2 ], nbyte );
+   } else {
+      for( i = 0; i < n; i++ ) out2[ i ] = AST__AU;
+   }
+   cur_sys = in_sys;
+
+/* Skip the next bit if the output values are now in the required system. */
+   if( cur_sys != out_sys ) {
+
+/* If the current system is spherical note the corresponding cartesian
+   system. If the current system is cartesian, use it. */
+      if( cur_sys == AST__HG ){
+         inCsys = AST__HGC;
+      } else if( cur_sys == AST__HAQ ){
+         inCsys = AST__HAQC;
+      } else if( cur_sys == AST__HAE ){
+         inCsys = AST__HAEC;
+      } else if( cur_sys == AST__GSE ){
+         inCsys = AST__GSEC;
+      } else if( cur_sys == AST__HPC ){
+         inCsys = AST__HPCC;
+      } else if( cur_sys == AST__HPR ){
+         inCsys = AST__HPRC;
+      } else {
+         inCsys = cur_sys;
+      }
+
+/* Convert input spherical positions into the corresponding cartesian system,
+   putting the results in the "out" arrays. Modify the input system
+   accordingly. */
+      if( cur_sys != inCsys ) {
+         px = out[ 0 ];
+         py = out[ 1 ];
+         pz = out2;
+         for( i = 0; i < n; i++ ) {
+            p[ 0 ] = *px;
+            p[ 1 ] = *py;
+            p[ 2 ] = *pz;
+            if( p[ 0 ] != AST__BAD &&
+                p[ 1 ] != AST__BAD &&
+                p[ 2 ] != AST__BAD ) {
+                palDcs2c( p[ 0 ], p[ 1 ], q );
+                *(px++) = q[ 0 ]*p[ 2 ];
+                *(py++) = q[ 1 ]*p[ 2 ];
+                *(pz++) = q[ 2 ]*p[ 2 ];
+            } else {
+               *(px++) = AST__BAD;
+               *(py++) = AST__BAD;
+               *(pz++) = AST__BAD;
+            }
+         }
+
+         cur_sys = inCsys;
+
+      }
+   }
+
+/* Skip the next bit if the output values are now in the required system. */
+   if( cur_sys != out_sys ) {
+
+/* If the required output system is spherical, note the corresponding
+   cartesian system. If the required output system is cartesian, use it.*/
+      if( out_sys == AST__HG ){
+         outCsys = AST__HGC;
+      } else if( out_sys == AST__HAQ ){
+         outCsys = AST__HAQC;
+      } else if( out_sys == AST__HAE ){
+         outCsys = AST__HAEC;
+      } else if( out_sys == AST__GSE ){
+         outCsys = AST__GSEC;
+      } else if( out_sys == AST__HPC ){
+         outCsys = AST__HPCC;
+      } else if( out_sys == AST__HPR ){
+         outCsys = AST__HPRC;
+      } else {
+         outCsys = out_sys;
+      }
+
+/* Skip the next bit if the output values are already in the required
+   output cartesian system. */
+      if( cur_sys != outCsys ) {
+
+/* Obtain an offset vector and a rotation matrix which moves positions from
+   the current (Cartesian) system to the AST__HAEC system. The offset vector
+   returned by these functions is the AST__HAEC coordinates of the origin of
+   the current system. The matrix rotates direction vectors from the current
+   system to the AST__HAEC system. */
+         if( cur_sys == AST__HGC ) {
+            Hgc( mjd, mat1, off1, status );
+
+         } else if( cur_sys == AST__HAEC ) {
+            Haec( mjd, mat1, off1, status );
+
+         } else if( cur_sys == AST__HAQC ) {
+            Haqc( mjd, mat1, off1, status );
+
+         } else if( cur_sys == AST__GSEC ) {
+            Gsec( mjd, mat1, off1, status );
+
+         } else if( cur_sys == AST__HPCC ) {
+            Hpcc( mjd, in_obs, mat1, off1, status );
+
+         } else if( cur_sys == AST__HPRC ) {
+            Hprc( mjd, in_obs, mat1, off1, status );
+
+         } else {
+            astError( AST__INTER, "astSTPConv(SlaMap): Unsupported input "
+                      "cartesian coordinate system type %d (internal AST "
+                      "programming error).", status, cur_sys );
+         }
+
+/* Obtain an offset vector and a rotation matrix which moves positions from
+   the required output Cartesian system to the AST__HAEC system. */
+         if( outCsys == AST__HGC ) {
+            Hgc( mjd, mat2, off2, status );
+
+         } else if( outCsys == AST__HAEC ) {
+            Haec( mjd, mat2, off2, status );
+
+         } else if( outCsys == AST__HAQC ) {
+            Haqc( mjd, mat2, off2, status );
+
+         } else if( outCsys == AST__GSEC ) {
+            Gsec( mjd, mat2, off2, status );
+
+         } else if( outCsys == AST__HPCC ) {
+            Hpcc( mjd, out_obs, mat2, off2, status );
+
+         } else if( outCsys == AST__HPRC ) {
+            Hprc( mjd, out_obs, mat2, off2, status );
+
+         } else {
+            astError( AST__INTER, "astSTPConv(SlaMap): Unsupported output "
+                      "cartesian coordinate system type %d (internal AST "
+                      "programming error).", status, outCsys );
+         }
+
+/* Invert the second matrix to get the matrix which rotates AST__HAEC coords
+   to the output cartesian system. This an be done simply by transposing it
+   since all the matrices are 3D rotations. */
+         for( i = 0; i < 3; i++ ) {
+            for( j = 0; j < 3; j++ ) mat3[ i ][ j ] = mat2[ j ][ i ];
+
+/* Find the offset in AST__HAEC coords from the origin of the output
+   cartesian system to the origin of the current system. */
+            off3[ i ] = off1[ i ] - off2[ i ];
+         }
+
+/* Unless the origins are being ignored, use the above matrix to rotate the
+   above AST__HAEC offset into the output cartesian system. If origins are
+   being ignored, use an offset of zero. */
+         if( ignore_origins ) {
+            off4[ 0 ] = 0.0;
+            off4[ 1 ] = 0.0;
+            off4[ 2 ] = 0.0;
+         } else {
+            palDmxv( mat3, off3, off4 );
+         }
+
+/* Concatentate the two matrices to get the matrix which rotates from the
+   current system to the output cartesian system. */
+         palDmxm( mat3, mat1, mat4 );
+
+/* Use the matrix and offset to convert current positions to output
+   cartesian positions. */
+         px = out[ 0 ];
+         py = out[ 1 ];
+         pz = out2;
+
+         for( i = 0; i < n; i++ ) {
+            p[ 0 ] = *px;
+            p[ 1 ] = *py;
+            p[ 2 ] = *pz;
+
+            if( p[ 0 ] != AST__BAD &&
+                p[ 1 ] != AST__BAD &&
+                p[ 2 ] != AST__BAD ) {
+               palDmxv( mat4, p, q );
+               *(px++) = q[ 0 ] + off4[ 0 ];
+               *(py++) = q[ 1 ] + off4[ 1 ];
+               *(pz++) = q[ 2 ] + off4[ 2 ];
+            } else {
+               *(px++) = AST__BAD;
+               *(py++) = AST__BAD;
+               *(pz++) = AST__BAD;
+            }
+         }
+
+/* Indicate that the output values are now in the required output
+   cartesian system. */
+         cur_sys = outCsys;
+
+      }
+   }
+
+/* Skip the next bit if the output values are now in the required system. */
+   if( cur_sys != out_sys ) {
+
+/* The only reason why the output values may not be in the required output
+   system is because the output system is spherical. Convert output Cartesian
+   positions to output spherical positions. */
+      px = out[ 0 ];
+      py = out[ 1 ];
+      pz = out2;
+      for( i = 0; i < n; i++ ) {
+         p[ 0 ] = *px;
+         p[ 1 ] = *py;
+         p[ 2 ] = *pz;
+         if( p[ 0 ] != AST__BAD &&
+             p[ 1 ] != AST__BAD &&
+             p[ 2 ] != AST__BAD ) {
+             palDvn( p, q, &radius );
+             palDcc2s( q, &lng, &lat );
+             *(px++) = palDranrm( lng );
+             *(py++) = lat;
+             *(pz++) = radius;
+         } else {
+            *(px++) = AST__BAD;
+            *(py++) = AST__BAD;
+            *(pz++) = AST__BAD;
+         }
+      }
+   }
+
+/* If out[2] was supplied as null, free the memory used to hold third axis
+   values. */
+   if( !out[2] ) out2 = (double *) astFree( (void *) out2 );
+}
+
+void astInitSlaMapVtab_(  AstSlaMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSlaMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a SlaMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void astInitSlaMapVtab( AstSlaMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SlaMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the SlaMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsASlaMap) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->SlaAdd = SlaAdd;
+   vtab->SlaIsEmpty = SlaIsEmpty;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "SlaMap",
+               "Conversion between sky coordinate systems" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing an SlaMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     SlaMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated SlaMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated SlaMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated SlaMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the SlaMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        SlaMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated SlaMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *new;              /* Pointer to replacement Mapping */
+   AstSlaMap *slamap;            /* Pointer to SlaMap */
+   const char *argdesc[ MAX_SLA_ARGS ]; /* Argument descriptions (junk) */
+   const char *class;            /* Pointer to Mapping class string */
+   const char *comment;          /* Pointer to comment string (junk) */
+   double (*cvtargs)[ MAX_SLA_ARGS ]; /* Pointer to argument arrays */
+   int *cvttype;                 /* Pointer to transformation type codes */
+   int *narg;                    /* Pointer to argument count */
+   int done;                     /* Finished (no further simplification)? */
+   int iarg;                     /* Loop counter for arguments */
+   int icvt1;                    /* Loop initial value */
+   int icvt2;                    /* Loop final value */
+   int icvt;                     /* Loop counter for transformation steps */
+   int ikeep;                    /* Index to store step being kept */
+   int imap1;                    /* Index of first SlaMap to merge */
+   int imap2;                    /* Index of last SlaMap to merge */
+   int imap;                     /* Loop counter for Mappings */
+   int inc;                      /* Increment for transformation step loop */
+   int invert;                   /* SlaMap applied in inverse direction? */
+   int istep;                    /* Loop counter for transformation steps */
+   int keep;                     /* Keep transformation step? */
+   int ngone;                    /* Number of Mappings eliminated */
+   int nstep0;                   /* Original number of transformation steps */
+   int nstep;                    /* Total number of transformation steps */
+   int result;                   /* Result value to return */
+   int simpler;                  /* Simplification possible? */
+   int unit;                     /* Replacement Mapping is a UnitMap? */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* SlaMaps can only be merged if they are in series (or if there is
+   only one Mapping present, in which case it makes no difference), so
+   do nothing if they are not. */
+   if ( series || ( *nmap == 1 ) ) {
+
+/* Initialise the number of transformation steps to be merged to equal
+   the number in the nominated SlaMap. */
+      nstep = ( (AstSlaMap *) ( *map_list )[ where ] )->ncvt;
+
+/* Search adjacent lower-numbered Mappings until one is found which is
+   not an SlaMap. Accumulate the number of transformation steps
+   involved in any SlaMaps found. */
+      imap1 = where;
+      while ( ( imap1 - 1 >= 0 ) && astOK ) {
+         class = astGetClass( ( *map_list )[ imap1 - 1 ] );
+         if ( !astOK || strcmp( class, "SlaMap" ) ) break;
+         nstep += ( (AstSlaMap *) ( *map_list )[ imap1 - 1 ] )->ncvt;
+         imap1--;
+      }
+
+/* Similarly search adjacent higher-numbered Mappings. */
+      imap2 = where;
+      while ( ( imap2 + 1 < *nmap ) && astOK ) {
+         class = astGetClass( ( *map_list )[ imap2 + 1 ] );
+         if ( !astOK || strcmp( class, "SlaMap" ) ) break;
+         nstep += ( (AstSlaMap *) ( *map_list )[ imap2 + 1 ] )->ncvt;
+         imap2++;
+      }
+
+/* Remember the initial number of transformation steps. */
+      nstep0 = nstep;
+
+/* Allocate memory for accumulating a list of all the transformation
+   steps involved in all the SlaMaps found. */
+      cvttype = astMalloc( sizeof( int ) * (size_t) nstep );
+      cvtargs = astMalloc( sizeof( double[ MAX_SLA_ARGS ] ) * (size_t) nstep );
+      narg = astMalloc( sizeof( int ) * (size_t) nstep );
+
+/* Loop to obtain the transformation data for each SlaMap being merged. */
+      nstep = 0;
+      for ( imap = imap1; astOK && ( imap <= imap2 ); imap++ ) {
+
+/* Obtain a pointer to the SlaMap and note if it is being applied in
+   its inverse direction. */
+         slamap = (AstSlaMap *) ( *map_list )[ imap ];
+         invert = ( *invert_list )[ imap ];
+
+/* Set up loop limits and an increment to scan the transformation
+   steps in each SlaMap in either the forward or reverse direction, as
+   dictated by the associated "invert" value. */
+         icvt1 = invert ? slamap->ncvt - 1 : 0;
+         icvt2 = invert ? -1 : slamap->ncvt;
+         inc = invert ? -1 : 1;
+
+/* Loop through each transformation step in the SlaMap. */
+         for ( icvt = icvt1; icvt != icvt2; icvt += inc ) {
+
+/* For simplicity, free any extra information stored with the conversion
+   step (it will be recreated as and when necessary). */
+            slamap->cvtextra[ icvt ] = astFree( slamap->cvtextra[ icvt ] );
+
+/* Store the transformation type code and use "CvtString" to determine
+   the associated number of arguments. Then store these arguments. */
+            cvttype[ nstep ] = slamap->cvttype[ icvt ];
+            (void) CvtString( cvttype[ nstep ], &comment, narg + nstep,
+                              argdesc, status );
+            if ( !astOK ) break;
+            for ( iarg = 0; iarg < narg[ nstep ]; iarg++ ) {
+               cvtargs[ nstep ][ iarg ] = slamap->cvtargs[ icvt ][ iarg ];
+            }
+
+/* If the SlaMap is inverted, we must not only accumulate its
+   transformation steps in reverse, but also apply them in
+   reverse. For some steps this means swapping arguments, for some it
+   means changing the transformation type code to a complementary
+   value, and for others it means both.  Define macros to perform each
+   of these changes. */
+
+/* Macro to swap the values of two nominated arguments if the
+   transformation type code matches "code". */
+#define SWAP_ARGS( code, arg1, arg2 ) \
+            if ( cvttype[ nstep ] == code ) { \
+               double tmp = cvtargs[ nstep ][ arg1 ]; \
+               cvtargs[ nstep ][ arg1 ] = cvtargs[ nstep ][ arg2 ]; \
+               cvtargs[ nstep ][ arg2 ] = tmp; \
+            }
+
+/* Macro to exchange a transformation type code for its inverse (and
+   vice versa). */
+#define SWAP_CODES( code1, code2 ) \
+            if ( cvttype[ nstep ] == code1 ) { \
+               cvttype[ nstep ] = code2; \
+            } else if ( cvttype[ nstep ] == code2 ) { \
+               cvttype[ nstep ] = code1; \
+            }
+
+/* Use these macros to apply the changes where needed. */
+            if ( invert ) {
+
+/* E-terms of aberration. */
+/* ---------------------- */
+/* Exchange addition and subtraction of E-terms. */
+               SWAP_CODES( AST__SLA_ADDET, AST__SLA_SUBET )
+
+/* Bessel-Newcomb pre-IAU 1976 (FK4) precession model. */
+/* --------------------------------------------------- */
+/* Exchange the starting and ending Besselian epochs. */
+               SWAP_ARGS( AST__SLA_PREBN, 0, 1 )
+
+/* IAU 1975 (FK5) precession model. */
+/* -------------------------------- */
+/* Exchange the starting and ending epochs. */
+               SWAP_ARGS( AST__SLA_PREC, 0, 1 )
+
+/* FK4 to FK5 (no proper motion or parallax). */
+/* ------------------------------------------ */
+/* Exchange FK5 to FK4 conversion for its inverse, and vice versa. */
+               SWAP_CODES( AST__SLA_FK54Z, AST__SLA_FK45Z )
+
+/* Geocentric apparent to mean place. */
+/* ---------------------------------- */
+/* Exchange the transformation code for its inverse and also exchange
+   the order of the date and equinox arguments. */
+               SWAP_CODES( AST__SLA_AMP, AST__SLA_MAP )
+               SWAP_ARGS( AST__SLA_AMP, 0, 1 )
+               SWAP_ARGS( AST__SLA_MAP, 0, 1 )
+
+/* Ecliptic coordinates to FK5 J2000.0 equatorial. */
+/* ------------------------------------------- */
+/* Exchange the transformation code for its inverse. */
+               SWAP_CODES( AST__SLA_ECLEQ, AST__SLA_EQECL )
+
+/* Horizon to equatorial. */
+/* ---------------------- */
+/* Exchange the transformation code for its inverse. */
+               SWAP_CODES( AST__SLA_DH2E, AST__SLA_DE2H )
+
+/* Galactic coordinates to FK5 J2000.0 equatorial. */
+/* ------------------------------------------- */
+/* Exchange the transformation code for its inverse. */
+               SWAP_CODES( AST__SLA_GALEQ, AST__SLA_EQGAL )
+
+/* ICRS coordinates to FK5 J2000.0 equatorial. */
+/* ------------------------------------------- */
+/* Exchange the transformation code for its inverse. */
+               SWAP_CODES( AST__SLA_HFK5Z, AST__SLA_FK5HZ )
+
+/* Galactic to supergalactic coordinates. */
+/* -------------------------------------- */
+/* Exchange the transformation code for its inverse. */
+               SWAP_CODES( AST__SLA_GALSUP, AST__SLA_SUPGAL )
+
+/* FK5 J2000 equatorial coordinates to Helioprojective-Cartesian. */
+/* -------------------------------------------------------------- */
+/* Exchange the transformation code for its inverse. */
+               SWAP_CODES( AST__EQHPC, AST__HPCEQ )
+
+/* FK5 J2000 equatorial coordinates to Helioprojective-Radial. */
+/* ----------------------------------------------------------- */
+/* Exchange the transformation code for its inverse. */
+               SWAP_CODES( AST__EQHPR, AST__HPREQ )
+
+/* FK5 J2000 equatorial coordinates to Helio-ecliptic. */
+/* --------------------------------------------------- */
+/* Exchange the transformation code for its inverse. */
+               SWAP_CODES( AST__EQHE, AST__HEEQ )
+
+/* Dynamical J2000.0 to ICRS. */
+/* -------------------------- */
+/* Exchange the transformation code for its inverse. */
+               SWAP_CODES( AST__J2000H, AST__HJ2000 )
+
+/* HA to RA */
+/* -------- */
+/* Exchange the transformation code for its inverse. */
+               SWAP_CODES( AST__H2R, AST__R2H )
+
+            }
+
+/* Undefine the local macros. */
+#undef SWAP_ARGS
+#undef SWAP_CODES
+
+/* Count the transformation steps. */
+            nstep++;
+         }
+      }
+
+/* Loop to simplify the sequence of transformation steps until no
+   further improvement is possible. */
+      done = 0;
+      while ( astOK && !done ) {
+
+/* Examine each remaining transformation step in turn.  */
+         ikeep = -1;
+         for ( istep = 0; istep < nstep; istep++ ) {
+
+/* Initially assume we will retain the current step. */
+            keep = 1;
+
+/* Eliminate redundant precession corrections. */
+/* ------------------------------------------- */
+/* First check if this is a redundant precession transformation
+   (i.e. the starting and ending epochs are the same). If so, then
+   note that it should not be kept. */
+            if ( ( ( cvttype[ istep ] == AST__SLA_PREBN ) ||
+                   ( cvttype[ istep ] == AST__SLA_PREC ) ) &&
+                 astEQUAL( cvtargs[ istep ][ 0 ], cvtargs[ istep ][ 1 ] ) ) {
+               keep = 0;
+
+/* The remaining simplifications act to combine adjacent
+   transformation steps, so only apply them while there are at least 2
+   steps left. */
+            } else if ( istep < ( nstep - 1 ) ) {
+
+/* Define a macro to test if two adjacent transformation type codes
+   have specified values. */
+#define PAIR_CVT( code1, code2 ) \
+               ( ( cvttype[ istep ] == code1 ) && \
+                 ( cvttype[ istep + 1 ] == code2 ) )
+
+/* Combine adjacent precession corrections. */
+/* ---------------------------------------- */
+/* If two precession corrections are adjacent, and have an equinox
+   value in common, then they may be combined into a single correction
+   by eliminating the common equinox. */
+               if ( ( PAIR_CVT( AST__SLA_PREBN, AST__SLA_PREBN ) ||
+                      PAIR_CVT( AST__SLA_PREC, AST__SLA_PREC ) ) &&
+                    astEQUAL( cvtargs[ istep ][ 1 ], cvtargs[ istep + 1 ][ 0 ] ) ) {
+
+/* Retain the second correction, changing its first argument, and
+   eliminate the first correction. */
+                  cvtargs[ istep + 1 ][ 0 ] = cvtargs[ istep ][ 0 ];
+                  istep++;
+
+/* Eliminate redundant E-term handling. */
+/* ------------------------------------ */
+/* Check if adjacent steps implement a matching pair of corrections
+   for the E-terms of aberration with the same argument value. If so,
+   they will cancel, so eliminate them both. */
+               } else if ( ( PAIR_CVT( AST__SLA_SUBET, AST__SLA_ADDET ) ||
+                             PAIR_CVT( AST__SLA_ADDET, AST__SLA_SUBET ) ) &&
+                           astEQUAL( cvtargs[ istep ][ 0 ],
+                                  cvtargs[ istep + 1 ][ 0 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant FK4/FK5 conversions. */
+/* ---------------------------------------- */
+/* Similarly, check for a matching pair of FK4/FK5 conversions with
+   the same argument value and eliminate them both if possible. */
+               } else if ( ( PAIR_CVT( AST__SLA_FK45Z, AST__SLA_FK54Z ) ||
+                             PAIR_CVT( AST__SLA_FK54Z, AST__SLA_FK45Z ) ) &&
+                           astEQUAL( cvtargs[ istep ][ 0 ],
+                                  cvtargs[ istep + 1 ][ 0 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant ICRS/FK5 conversions. */
+/* ----------------------------------------- */
+/* Similarly, check for a matching pair of ICRS/FK5 conversions with
+   the same argument value and eliminate them both if possible. */
+               } else if ( ( PAIR_CVT( AST__SLA_HFK5Z, AST__SLA_FK5HZ ) ||
+                             PAIR_CVT( AST__SLA_FK5HZ, AST__SLA_HFK5Z ) ) &&
+                           astEQUAL( cvtargs[ istep ][ 0 ],
+                                  cvtargs[ istep + 1 ][ 0 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant geocentric apparent conversions. */
+/* ---------------------------------------------------- */
+/* As above, check for a matching pair of conversions with matching
+   argument values (note the argument order reverses for the two
+   directions) and eliminate them if possible. */
+               } else if ( ( PAIR_CVT( AST__SLA_AMP, AST__SLA_MAP ) ||
+                             PAIR_CVT( AST__SLA_MAP, AST__SLA_AMP ) ) &&
+                           astEQUAL( cvtargs[ istep ][ 0 ],
+                                  cvtargs[ istep + 1 ][ 1 ] ) &&
+                           astEQUAL( cvtargs[ istep ][ 1 ],
+                                  cvtargs[ istep + 1 ][ 0 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant ecliptic coordinate conversions. */
+/* ---------------------------------------------------- */
+/* This is handled in the same way as the FK4/FK5 case. */
+               } else if ( ( PAIR_CVT( AST__SLA_ECLEQ, AST__SLA_EQECL ) ||
+                             PAIR_CVT( AST__SLA_EQECL, AST__SLA_ECLEQ ) ) &&
+                           astEQUAL( cvtargs[ istep ][ 0 ],
+                                  cvtargs[ istep + 1 ][ 0 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant AzEl coordinate conversions. */
+/* ------------------------------------------------ */
+               } else if ( ( PAIR_CVT( AST__SLA_DH2E, AST__SLA_DE2H ) ||
+                             PAIR_CVT( AST__SLA_DE2H, AST__SLA_DH2E ) ) &&
+                           astEQUAL( cvtargs[ istep ][ 0 ],
+                                  cvtargs[ istep + 1 ][ 0 ] ) &&
+                           astEQUAL( cvtargs[ istep ][ 1 ],
+                                  cvtargs[ istep + 1 ][ 1 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant galactic coordinate conversions. */
+/* ---------------------------------------------------- */
+/* This is handled as above, except that there are no arguments to
+   check. */
+               } else if ( PAIR_CVT( AST__SLA_GALEQ, AST__SLA_EQGAL ) ||
+                           PAIR_CVT( AST__SLA_EQGAL, AST__SLA_GALEQ ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant supergalactic coordinate conversions. */
+/* --------------------------------------------------------- */
+/* This is handled as above. */
+               } else if ( PAIR_CVT( AST__SLA_GALSUP, AST__SLA_SUPGAL ) ||
+                           PAIR_CVT( AST__SLA_SUPGAL, AST__SLA_GALSUP ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant helioprojective-Cartesian coordinate conversions. */
+/* --------------------------------------------------------------------- */
+               } else if ( ( PAIR_CVT( AST__HPCEQ, AST__EQHPC ) ||
+                             PAIR_CVT( AST__EQHPC, AST__HPCEQ ) ) &&
+                           astEQUAL( cvtargs[ istep ][ 0 ],
+                             cvtargs[ istep + 1 ][ 0 ] ) &&
+                           astEQUAL( cvtargs[ istep ][ 1 ],
+                             cvtargs[ istep + 1 ][ 1 ] ) &&
+                           astEQUAL( cvtargs[ istep ][ 2 ],
+                             cvtargs[ istep + 1 ][ 2 ] ) &&
+                           astEQUAL( cvtargs[ istep ][ 3 ],
+                             cvtargs[ istep + 1 ][ 3 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant helioprojective-Radial coordinate conversions. */
+/* --------------------------------------------------------------------- */
+               } else if ( ( PAIR_CVT( AST__HPREQ, AST__EQHPR ) ||
+                             PAIR_CVT( AST__EQHPR, AST__HPREQ ) ) &&
+                           astEQUAL( cvtargs[ istep ][ 0 ],
+                             cvtargs[ istep + 1 ][ 0 ] ) &&
+                           astEQUAL( cvtargs[ istep ][ 1 ],
+                             cvtargs[ istep + 1 ][ 1 ] ) &&
+                           astEQUAL( cvtargs[ istep ][ 2 ],
+                             cvtargs[ istep + 1 ][ 2 ] ) &&
+                           astEQUAL( cvtargs[ istep ][ 3 ],
+                             cvtargs[ istep + 1 ][ 3 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant helio-ecliptic coordinate conversions. */
+/* ---------------------------------------------------------- */
+               } else if ( ( PAIR_CVT( AST__EQHE, AST__HEEQ ) ||
+                             PAIR_CVT( AST__HEEQ, AST__EQHE ) ) &&
+                           astEQUAL( cvtargs[ istep ][ 0 ],
+                                  cvtargs[ istep + 1 ][ 0 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant dynamical J2000 coordinate conversions. */
+/* ----------------------------------------------------------- */
+               } else if ( PAIR_CVT( AST__J2000H, AST__HJ2000 ) ||
+                           PAIR_CVT( AST__HJ2000, AST__J2000H ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Eliminate redundant Hour Angle conversions. */
+/* ------------------------------------------- */
+               } else if ( ( PAIR_CVT( AST__R2H, AST__H2R ) ||
+                             PAIR_CVT( AST__H2R, AST__R2H ) ) &&
+                           astEQUAL( cvtargs[ istep ][ 0 ],
+                                  cvtargs[ istep + 1 ][ 0 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+               }
+
+/* Undefine the local macro. */
+#undef PAIR_CVT
+            }
+
+/* If the current transformation (possibly modified above) is being
+   kept, then increment the index that identifies its new location in
+   the list of transformation steps. */
+            if ( keep ) {
+               ikeep++;
+
+/* If the new location is different to its current location, copy the
+   transformation data into the new location. */
+               if ( ikeep != istep ) {
+                  cvttype[ ikeep ] = cvttype[ istep ];
+                  for ( iarg = 0; iarg < narg[ istep ]; iarg++ ) {
+                     cvtargs[ ikeep ][ iarg ] = cvtargs[ istep ][ iarg ];
+                  }
+                  narg[ ikeep ] = narg[ istep ];
+               }
+            }
+         }
+
+/* Note if no simplification was achieved on this iteration (i.e. the
+   number of transformation steps was not reduced). This is the signal
+   to quit. */
+         done = ( ( ikeep + 1 ) >= nstep );
+
+/* Note how many transformation steps now remain. */
+         nstep = ikeep + 1;
+      }
+
+/* Determine how many Mappings can be eliminated by condensing all
+   those considered above into a single Mapping. */
+      if ( astOK ) {
+         ngone = imap2 - imap1;
+
+/* Determine if the replacement Mapping can be a UnitMap (a null
+   Mapping). This will only be the case if all the transformation
+   steps were eliminated above. */
+         unit = ( nstep == 0 );
+
+/* Determine if simplification is possible. This will be the case if
+   (a) Mappings were eliminated ("ngone" is non-zero), or (b) the
+   number of transformation steps was reduced, or (c) the SlaMap(s)
+   can be replaced by a UnitMap, or (d) if there was initially only
+   one SlaMap present, its invert flag was set (this flag will always
+   be cleared in the replacement Mapping). */
+         simpler = ngone || ( nstep < nstep0 ) || unit ||
+                   ( *invert_list )[ where ];
+
+/* Do nothing more unless simplification is possible. */
+         if ( simpler ) {
+
+/* If the replacement Mapping is a UnitMap, then create it. */
+            if ( unit ) {
+               new = (AstMapping *)
+                        astUnitMap( astGetNin( ( *map_list )[ where ] ), "", status );
+
+/* Otherwise, create a replacement SlaMap and add each of the
+   remaining transformation steps to it. */
+            } else {
+               new = (AstMapping *) astSlaMap( 0, "", status );
+               for ( istep = 0; istep < nstep; istep++ ) {
+                  AddSlaCvt( (AstSlaMap *) new, cvttype[ istep ],
+                             narg[ istep ], cvtargs[ istep ], status );
+               }
+            }
+
+/* Annul the pointers to the Mappings being eliminated. */
+            if ( astOK ) {
+               for ( imap = imap1; imap <= imap2; imap++ ) {
+                  ( *map_list )[ imap ] = astAnnul( ( *map_list )[ imap ] );
+               }
+
+/* Insert the pointer and invert value for the new Mapping. */
+               ( *map_list )[ imap1 ] = new;
+               ( *invert_list )[ imap1 ] = 0;
+
+/* Move any subsequent Mapping information down to close the gap. */
+               for ( imap = imap2 + 1; imap < *nmap; imap++ ) {
+                  ( *map_list )[ imap - ngone ] = ( *map_list )[ imap ];
+                  ( *invert_list )[ imap - ngone ] = ( *invert_list )[ imap ];
+               }
+
+/* Blank out any information remaining at the end of the arrays. */
+               for ( imap = ( *nmap - ngone ); imap < *nmap; imap++ ) {
+                  ( *map_list )[ imap ] = NULL;
+                  ( *invert_list )[ imap ] = 0;
+               }
+
+/* Decrement the Mapping count and return the index of the first
+   Mapping which was eliminated. */
+               ( *nmap ) -= ngone;
+               result = imap1;
+
+/* If an error occurred, annul the new Mapping pointer. */
+            } else {
+               new = astAnnul( new );
+            }
+         }
+      }
+
+/* Free the memory used for the transformation steps. */
+      cvttype = astFree( cvttype );
+      cvtargs = astFree( cvtargs );
+      narg = astFree( narg );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static void SlaAdd( AstSlaMap *this, const char *cvt, int narg,
+                    const double args[], int *status ) {
+/*
+*++
+*  Name:
+c     astSlaAdd
+f     AST_SLAADD
+
+*  Purpose:
+*     Add a celestial coordinate conversion to an SlaMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "slamap.h"
+c     void astSlaAdd( AstSlaMap *this, const char *cvt, int narg,
+c                     const double args[] )
+f     CALL AST_SLAADD( THIS, CVT, NARG, ARGS, STATUS )
+
+*  Class Membership:
+*     SlaMap method.
+
+*  Description:
+c     This function adds one of the standard celestial coordinate
+f     This routine adds one of the standard celestial coordinate
+*     system conversions provided by the SLALIB Positional Astronomy
+*     Library (Starlink User Note SUN/67) to an existing SlaMap.
+*
+c     When an SlaMap is first created (using astSlaMap), it simply
+f     When an SlaMap is first created (using AST_SLAMAP), it simply
+c     performs a unit (null) Mapping. By using astSlaAdd (repeatedly
+f     performs a unit (null) Mapping. By using AST_SLAADD (repeatedly
+*     if necessary), one or more coordinate conversion steps may then
+*     be added, which the SlaMap will perform in sequence. This allows
+*     multi-step conversions between a variety of celestial coordinate
+*     systems to be assembled out of the building blocks provided by
+*     SLALIB.
+*
+*     Normally, if an SlaMap's Invert attribute is zero (the default),
+*     then its forward transformation is performed by carrying out
+*     each of the individual coordinate conversions specified by
+c     astSlaAdd in the order given (i.e. with the most recently added
+f     AST_SLAADD in the order given (i.e. with the most recently added
+*     conversion applied last).
+*
+*     This order is reversed if the SlaMap's Invert attribute is
+*     non-zero (or if the inverse transformation is requested by any
+*     other means) and each individual coordinate conversion is also
+*     replaced by its own inverse. This process inverts the overall
+*     effect of the SlaMap. In this case, the first conversion to be
+*     applied would be the inverse of the one most recently added.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the SlaMap.
+c     cvt
+f     CVT = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string which identifies the
+f        A character string which identifies the
+*        celestial coordinate conversion to be added to the
+*        SlaMap. See the "SLALIB Conversions" section for details of
+*        those available.
+c     narg
+f     NARG = INTEGER (Given)
+*        The number of argument values supplied in the
+c        "args" array.
+f        ARGS array.
+c     args
+f     ARGS( * ) = DOUBLE PRECISION (Given)
+*        An array containing argument values for the celestial
+*        coordinate conversion. The number of arguments required, and
+*        hence the number of array elements used, depends on the
+*        conversion specified (see the "SLALIB Conversions"
+*        section). This array is ignored
+c        and a NULL pointer may be supplied
+*        if no arguments are needed.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - All coordinate values processed by an SlaMap are in
+*     radians. The first coordinate is the celestial longitude and the
+*     second coordinate is the celestial latitude.
+*     - When assembling a multi-stage conversion, it can sometimes be
+*     difficult to determine the most economical conversion path. For
+*     example, converting to the standard FK5 coordinate system as an
+*     intermediate stage is often sensible in formulating the problem,
+*     but may introduce unnecessary extra conversion steps. A solution
+*     to this is to include all the steps which are (logically)
+c     necessary, but then to use astSimplify to simplify the resulting
+f     necessary, but then to use AST_SIMPLIFY to simplify the resulting
+*     SlaMap. The simplification process will eliminate any steps
+*     which turn out not to be needed.
+c     - This function does not check to ensure that the sequence of
+f     - This routine does not check to ensure that the sequence of
+*     coordinate conversions added to an SlaMap is physically
+*     meaningful.
+
+*  SLALIB Conversions:
+*     The following strings (which are case-insensitive) may be supplied
+c     via the "cvt" parameter to indicate which celestial coordinate
+f     via the CVT argument to indicate which celestial coordinate
+*     conversion is to be added to the SlaMap. Each string is derived
+*     from the name of the SLALIB routine that performs the
+*     conversion and the relevant documentation (SUN/67) should be
+*     consulted for details.  Where arguments are needed by
+*     the conversion, they are listed in parentheses. Values for
+c     these arguments should be given, via the "args" array, in the
+f     these arguments should be given, via the ARGS array, in the
+*     order indicated. The argument names match the corresponding
+*     SLALIB routine arguments and their values should be given using
+*     exactly the same units, time scale, calendar, etc. as described
+*     in SUN/67:
+*
+*     - "ADDET" (EQ): Add E-terms of aberration.
+*     - "SUBET" (EQ): Subtract E-terms of aberration.
+*     - "PREBN" (BEP0,BEP1): Apply Bessel-Newcomb pre-IAU 1976 (FK4)
+*     precession model.
+*     - "PREC" (EP0,EP1): Apply IAU 1975 (FK5) precession model.
+*     - "FK45Z" (BEPOCH): Convert FK4 to FK5 (no proper motion or parallax).
+*     - "FK54Z" (BEPOCH): Convert FK5 to FK4 (no proper motion or parallax).
+*     - "AMP" (DATE,EQ): Convert geocentric apparent to mean place.
+*     - "MAP" (EQ,DATE): Convert mean place to geocentric apparent.
+*     - "ECLEQ" (DATE): Convert ecliptic coordinates to FK5 J2000.0 equatorial.
+*     - "EQECL" (DATE): Convert equatorial FK5 J2000.0 to ecliptic coordinates.
+*     - "GALEQ": Convert galactic coordinates to FK5 J2000.0 equatorial.
+*     - "EQGAL": Convert FK5 J2000.0 equatorial to galactic coordinates.
+*     - "HFK5Z" (JEPOCH): Convert ICRS coordinates to FK5 J2000.0 equatorial.
+*     - "FK5HZ" (JEPOCH): Convert FK5 J2000.0 equatorial coordinates to ICRS.
+*     - "GALSUP": Convert galactic to supergalactic coordinates.
+*     - "SUPGAL": Convert supergalactic coordinates to galactic.
+*     - "J2000H": Convert dynamical J2000.0 to ICRS.
+*     - "HJ2000": Convert ICRS to dynamical J2000.0.
+*     - "R2H" (LAST): Convert RA to Hour Angle.
+*     - "H2R" (LAST): Convert Hour Angle to RA.
+*
+*     For example, to use the "ADDET" conversion, which takes a single
+*     argument EQ, you should consult the documentation for the SLALIB
+*     routine SLA_ADDET. This describes the conversion in detail and
+*     shows that EQ is the Besselian epoch of the mean equator and
+*     equinox.
+c     This value should then be supplied to astSlaAdd in args[0].
+f     This value should then be supplied to AST_SLAADD in ARGS(1).
+*
+*     In addition the following strings may be supplied for more complex
+*     conversions which do not correspond to any one single SLALIB routine
+*     (DIURAB is the magnitude of the diurnal aberration vector in units
+*     of "day/(2.PI)", DATE is the Modified Julian Date of the observation,
+*     and (OBSX,OBSY,OBZ) are the Heliocentric-Aries-Ecliptic cartesian
+*     coordinates, in metres, of the observer):
+*
+*     - "HPCEQ" (DATE,OBSX,OBSY,OBSZ): Convert Helioprojective-Cartesian coordinates to J2000.0 equatorial.
+*     - "EQHPC" (DATE,OBSX,OBSY,OBSZ): Convert J2000.0 equatorial coordinates to Helioprojective-Cartesian.
+*     - "HPREQ" (DATE,OBSX,OBSY,OBSZ): Convert Helioprojective-Radial coordinates to J2000.0 equatorial.
+*     - "EQHPR" (DATE,OBSX,OBSY,OBSZ): Convert J2000.0 equatorial coordinates to Helioprojective-Radial.
+*     - "HEEQ" (DATE): Convert helio-ecliptic coordinates to J2000.0 equatorial.
+*     - "EQHE" (DATE): Convert J2000.0 equatorial coordinates to helio-ecliptic.
+*     - "H2E" (LAT,DIRUAB): Convert horizon coordinates to equatorial.
+*     - "E2H" (LAT,DIURAB): Convert equatorial coordinates to horizon.
+*
+*     Note, the "H2E" and "E2H" conversions convert between topocentric
+*     horizon coordinates (azimuth,elevation), and apparent local equatorial
+*     coordinates (hour angle,declination). Thus, the effects of diurnal
+*     aberration are taken into account in the conversions but the effects
+*     of atmospheric refraction are not.
+
+*--
+*/
+
+/* Local Variables: */
+   int cvttype;                  /* Conversion type code */
+
+/* Check the inherited status. */
+   if ( !astOK ) return;
+
+/* Validate the type string supplied and obtain the equivalent
+   conversion type code. */
+   cvttype = CvtCode( cvt, status );
+
+/* If the string was not recognised, then report an error. */
+   if ( astOK && ( cvttype == AST__SLA_NULL ) ) {
+      astError( AST__SLAIN,
+                "astSlaAdd(%s): Invalid SLALIB sky coordinate conversion "
+                "type \"%s\".", status, astGetClass( this ), cvt );
+   }
+
+/* Add the new conversion to the SlaMap. */
+   AddSlaCvt( this, cvttype, narg, args, status );
+}
+
+static int SlaIsEmpty( AstSlaMap *this, int *status ){
+/*
+*+
+*  Name:
+*     astSlaIsEmpty
+
+*  Purpose:
+*     Indicates if a SlaMap is empty (i.e. has no conversions).
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     result = astSlaIsEmpty( AstSlaMap *this )
+
+*  Class Membership:
+*     SlaMap method.
+
+*  Description:
+*     This function returns a flag indicating if the SlaMap is empty
+*     (i.e. has not yet had any conversions added to it using astSlaAdd).
+
+*  Parameters:
+*     this
+*        The SlaMap.
+*-
+*/
+   if( !astOK ) return 1;
+   return ( this->ncvt == 0 );
+}
+
+
+static void SolarPole( double mjd, double pole[3], int *status ) {
+/*
+*  Name:
+*     SolarPole
+
+*  Purpose:
+*     Returns a unit vector along the solar north pole at the given date.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void SolarPole( double mjd, double pole[3], int *status )
+
+*  Class Membership:
+*     SlaMap member function.
+
+*  Description:
+*     This function returns a unit vector along the solar north pole at
+*     the given date, in the AST__HAEC coordinate system.
+
+*  Parameters:
+*     mjd
+*        The date at which the solar north pole vector is required.
+*     pole
+*        An array holding the (X,Y,Z) components of the vector, in the
+*        AST__HAEC system.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  AST__BAD will be returned for all components of the vector if this
+*     function is invoked with the global error status set, or if it should
+*     fail for any reason.
+*/
+
+/* Local Variables: */
+   double omega;
+   double sproj;
+   double inc;
+   double t1;
+
+/* Initialize. */
+   pole[0] = AST__BAD;
+   pole[1] = AST__BAD;
+   pole[2] = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* First, we find the ecliptic longitude of the ascending node of the solar
+   equator on the ecliptic at the required date. This is based on the
+   equation in the "Explanatory Supplement to the Astronomical Alamanac",
+   section "Physical Ephemeris of the Sun":
+
+   Omega = 75.76 + 0.01397*T degrees
+
+   Note, the text at the start of the chapter says that "T" is measured in
+   centuries since J2000, but the equivalent expression in Table 15.4 is
+   only consistent with the above equation if "T" is measured in days since
+   J2000. We assume T is in days. The text does not explicitly say so,
+   but we assume that this longitude value (Omega) is with respect to the
+   mean equinox of J2000.0. */
+   omega = 75.76 + 0.01397*( palEpj(mjd) - 2000.0 );
+
+/* Convert this to the ecliptic longitude of the projection of the sun's
+   north pole onto the ecliptic, in radians. */
+   sproj = ( omega - 90.0 )*D2R;
+
+/* Obtain a unit vector parallel to the sun's north pole, in terms of
+   the required ecliptic (X,Y,Z) axes, in which X points towards ecliptic
+   longitude/latitude ( 0, 0 ), Y axis points towards ecliptic
+   longitude/latitude ( 90, 0 ) degrees, and Z axis points towards the
+   ecliptic north pole. The inclination of the solar axis to the ecliptic
+   axis (7.25 degrees) is taken from the "Explanatory Supplement" section
+   "The Physical Ephemeris of the Sun". */
+   inc = 7.25*D2R;
+   t1 = sin( inc );
+   pole[ 0 ]= t1*cos( sproj );
+   pole[ 1 ] = t1*sin( sproj );
+   pole[ 2 ] = cos( inc );
+
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply an SlaMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     SlaMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes an SlaMap and a set of points encapsulated
+*     in a PointSet and transforms the points so as to perform the
+*     sequence of SLALIB sky coordinate conversions specified by
+*     previous invocations of astSlaAdd.
+
+*  Parameters:
+*     this
+*        Pointer to the SlaMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the SlaMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstSlaMap *map;               /* Pointer to SlaMap to be applied */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *alpha;                /* Pointer to longitude array */
+   double *args;                 /* Pointer to argument list for conversion */
+   double *extra;                /* Pointer to intermediate values */
+   double *delta;                /* Pointer to latitude array */
+   double *p[3];                 /* Pointers to arrays to be transformed */
+   double *obs;                  /* Pointer to array holding observers position */
+   int cvt;                      /* Loop counter for conversions */
+   int ct;                       /* Conversion type */
+   int end;                      /* Termination index for conversion loop */
+   int inc;                      /* Increment for conversion loop */
+   int npoint;                   /* Number of points */
+   int point;                    /* Loop counter for points */
+   int start;                    /* Starting index for conversion loop */
+   int sys;                      /* STP coordinate system code */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this);
+
+/* Obtain a pointer to the SlaMap. */
+   map = (AstSlaMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   coordinate conversions needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse transformation, according
+   to the direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( this ) ) forward = !forward;
+
+/* Transform the coordinate values. */
+/* -------------------------------- */
+/* Use "alpha" and "delta" as synonyms for the arrays of longitude and latitude
+   coordinate values stored in the output PointSet. */
+   if ( astOK ) {
+      alpha = ptr_out[ 0 ];
+      delta = ptr_out[ 1 ];
+
+/* Initialise the output coordinate values by copying the input ones. */
+      (void) memcpy( alpha, ptr_in[ 0 ], sizeof( double ) * (size_t) npoint );
+      (void) memcpy( delta, ptr_in[ 1 ], sizeof( double ) * (size_t) npoint );
+
+/* We will loop to apply each SLALIB sky coordinate conversion in turn to the
+   (alpha,delta) arrays. However, if the inverse transformation was requested,
+   we must loop through these transformations in reverse order, so set up
+   appropriate limits and an increment to control this loop. */
+      start = forward ? 0 : map->ncvt - 1;
+      end = forward ? map->ncvt : -1;
+      inc = forward ? 1 : -1;
+
+/* Loop through the coordinate conversions in the required order and obtain a
+   pointer to the argument list for the current conversion. */
+      for ( cvt = start; cvt != end; cvt += inc ) {
+         args = map->cvtargs[ cvt ];
+         extra = map->cvtextra[ cvt ];
+
+/* Define a local macro as a shorthand to apply the code given as "function"
+   (the macro argument) to each element of the (alpha,delta) arrays in turn.
+   Before applying this conversion function, each element is first checked for
+   "bad" coordinates (indicated by the value AST__BAD) and appropriate "bad"
+   result values are assigned if necessary. */
+#define TRAN_ARRAY(function) \
+        for ( point = 0; point < npoint; point++ ) { \
+           if ( ( alpha[ point ] == AST__BAD ) || \
+                ( delta[ point ] == AST__BAD ) ) { \
+              alpha[ point ] = AST__BAD; \
+              delta[ point ] = AST__BAD; \
+	   } else { \
+              function \
+	   } \
+        }
+
+/* Classify the SLALIB sky coordinate conversion to be applied. */
+         ct = map->cvttype[ cvt ];
+         switch ( ct ) {
+
+/* Add E-terms of aberration. */
+/* -------------------------- */
+/* Add or subtract (for the inverse) the E-terms from each coordinate pair
+   in turn, returning the results to the same arrays. */
+            case AST__SLA_ADDET:
+               if ( forward ) {
+                  TRAN_ARRAY(palAddet( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point );)
+	       } else {
+                  TRAN_ARRAY(palSubet( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point );)
+               }
+               break;
+
+/* Subtract E-terms of aberration. */
+/* ------------------------------- */
+/* This is the same as above, but with the forward and inverse cases
+   transposed. */
+            case AST__SLA_SUBET:
+               if ( forward ) {
+                  TRAN_ARRAY(palSubet( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point );)
+	       } else {
+                  TRAN_ARRAY(palAddet( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point );)
+	       }
+               break;
+
+/* Apply Bessel-Newcomb pre-IAU 1976 (FK4) precession model. */
+/* --------------------------------------------------------- */
+/* Since we are transforming a sequence of points, first set up the required
+   precession matrix, swapping the argument order to get the inverse matrix
+   if required. */
+            case AST__SLA_PREBN:
+               {
+                  double epoch1 = forward ? args[ 0 ] : args[ 1 ];
+                  double epoch2 = forward ? args[ 1 ] : args[ 0 ];
+                  double precess_matrix[ 3 ][ 3 ];
+                  double vec1[ 3 ];
+                  double vec2[ 3 ];
+                  palPrebn( epoch1, epoch2, precess_matrix );
+
+/* For each point in the (alpha,delta) arrays, convert to Cartesian
+   coordinates, apply the precession matrix, convert back to polar coordinates
+   and then constrain the longitude result to lie in the range 0 to 2*pi
+   (palDcc2s doesn't do this itself). */
+                  TRAN_ARRAY(palDcs2c( alpha[ point ], delta[ point ], vec1 );
+                             palDmxv( precess_matrix, vec1, vec2 );
+                             palDcc2s( vec2, alpha + point, delta + point );
+                             alpha[ point ] = palDranrm( alpha[ point ] );)
+	       }
+               break;
+
+/* Apply IAU 1975 (FK5) precession model. */
+/* -------------------------------------- */
+/* This is handled in the same way as above, but using the appropriate FK5
+   precession matrix. */
+            case AST__SLA_PREC:
+               {
+                  double epoch1 = forward ? args[ 0 ] : args[ 1 ];
+                  double epoch2 = forward ? args[ 1 ] : args[ 0 ];
+                  double precess_matrix[ 3 ][ 3 ];
+                  double vec1[ 3 ];
+                  double vec2[ 3 ];
+                  palPrec( epoch1, epoch2, precess_matrix );
+                  TRAN_ARRAY(palDcs2c( alpha[ point ], delta[ point ], vec1 );
+                             palDmxv( precess_matrix, vec1, vec2 );
+                             palDcc2s( vec2, alpha + point, delta + point );
+                             alpha[ point ] = palDranrm( alpha[ point ] );)
+	       }
+               break;
+
+/* Convert FK4 to FK5 (no proper motion or parallax). */
+/* -------------------------------------------------- */
+/* Apply the conversion to each point. */
+	    case AST__SLA_FK45Z:
+               if ( forward ) {
+                  TRAN_ARRAY(palFk45z( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point );)
+
+/* The inverse transformation is also straightforward, except that we need a
+   couple of dummy variables as function arguments. */
+	       } else {
+                  double dr1950;
+                  double dd1950;
+                  TRAN_ARRAY(palFk54z( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point,
+                                       &dr1950, &dd1950 );)
+	       }
+               break;
+
+/* Convert FK5 to FK4 (no proper motion or parallax). */
+/* -------------------------------------------------- */
+/* This is the same as above, but with the forward and inverse cases
+   transposed. */
+	    case AST__SLA_FK54Z:
+               if ( forward ) {
+                  double dr1950;
+                  double dd1950;
+                  TRAN_ARRAY(palFk54z( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point,
+                                       &dr1950, &dd1950 );)
+	       } else {
+                  TRAN_ARRAY(palFk45z( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point );)
+               }
+               break;
+
+/* Convert geocentric apparent to mean place. */
+/* ------------------------------------------ */
+/* Since we are transforming a sequence of points, first set up the required
+   parameter array. Than apply this to each point in turn. */
+	    case AST__SLA_AMP:
+               {
+
+                  if( !extra ) {
+
+                     if( args[ 1 ] != eq_cache ||
+                         args[ 0 ] != ep_cache ) {
+                        eq_cache = args[ 1 ];
+                        ep_cache = args[ 0 ];
+                        palMappa( eq_cache, ep_cache, amprms_cache );
+                     }
+
+                     extra = astStore( NULL, amprms_cache,
+                                       sizeof( double )*21 );
+                     map->cvtextra[ cvt ] = extra;
+                  }
+
+                  if ( forward ) {
+                     TRAN_ARRAY(palAmpqk( alpha[ point ], delta[ point ],
+                                          extra,
+                                          alpha + point, delta + point );)
+
+/* The inverse uses the same parameter array but converts from mean place
+   to geocentric apparent. */
+                  } else {
+                     TRAN_ARRAY(palMapqkz( alpha[ point ], delta[ point ],
+                                           extra,
+                                           alpha + point, delta + point );)
+		  }
+               }
+               break;
+
+/* Convert mean place to geocentric apparent. */
+/* ------------------------------------------ */
+/* This is the same as above, but with the forward and inverse cases
+   transposed. */
+	    case AST__SLA_MAP:
+               {
+                  if( !extra ) {
+
+                     if( args[ 0 ] != eq_cache ||
+                         args[ 1 ] != ep_cache ) {
+                        eq_cache = args[ 0 ];
+                        ep_cache = args[ 1 ];
+                        palMappa( eq_cache, ep_cache, amprms_cache );
+                     }
+
+                     extra = astStore( NULL, amprms_cache,
+                                       sizeof( double )*21 );
+                     map->cvtextra[ cvt ] = extra;
+                  }
+
+                  if ( forward ) {
+                     TRAN_ARRAY(palMapqkz( alpha[ point ], delta[ point ],
+                                           extra,
+                                           alpha + point, delta + point );)
+                  } else {
+                     TRAN_ARRAY(palAmpqk( alpha[ point ], delta[ point ],
+                                          extra,
+                                          alpha + point, delta + point );)
+		  }
+               }
+               break;
+
+/* Convert ecliptic coordinates to J2000.0 equatorial. */
+/* --------------------------------------------------- */
+/* Since we are transforming a sequence of points, first set up the required
+   conversion matrix (the conversion is a rotation). */
+	    case AST__SLA_ECLEQ:
+               {
+                  double convert_matrix[ 3 ][ 3 ];
+                  double precess_matrix[ 3 ][ 3 ];
+                  double rotate_matrix[ 3 ][ 3 ];
+                  double vec1[ 3 ];
+                  double vec2[ 3 ];
+
+/* Obtain the matrix that precesses equatorial coordinates from J2000.0 to the
+   required date. Also obtain the rotation matrix that converts from
+   equatorial to ecliptic coordinates.  */
+                  palPrec( 2000.0, palEpj( args[ 0 ] ), precess_matrix );
+                  palEcmat( args[ 0 ], rotate_matrix );
+
+/* Multiply these matrices to give the overall matrix that converts from
+   equatorial J2000.0 coordinates to ecliptic coordinates for the required
+   date. */
+                  palDmxm( rotate_matrix, precess_matrix, convert_matrix );
+
+/* Apply the conversion by transforming from polar to Cartesian coordinates,
+   multiplying by the inverse conversion matrix and converting back to polar
+   coordinates. Then constrain the longitude result to lie in the range
+   0 to 2*pi (palDcc2s doesn't do this itself). */
+                  if ( forward ) {
+                     TRAN_ARRAY(palDcs2c( alpha[ point ], delta[ point ],
+                                          vec1 );
+                                palDimxv( convert_matrix, vec1, vec2 );
+                                palDcc2s( vec2, alpha + point, delta + point );
+                                alpha[ point ] = palDranrm ( alpha[ point ] );)
+
+/* The inverse conversion is the same except that we multiply by the forward
+   conversion matrix (palDmxv instead of palDimxv). */
+                  } else {
+                     TRAN_ARRAY(palDcs2c( alpha[ point ], delta[ point ],
+                                          vec1 );
+                                palDmxv( convert_matrix, vec1, vec2 );
+                                palDcc2s( vec2, alpha + point, delta + point );
+                                alpha[ point ] = palDranrm ( alpha[ point ] );)
+                  }
+	       }
+               break;
+
+/* Convert equatorial J2000.0 to ecliptic coordinates. */
+/* --------------------------------------------------- */
+/* This is the same as above, but with the forward and inverse cases
+   transposed. */
+	    case AST__SLA_EQECL:
+               {
+                  double convert_matrix[ 3 ][ 3 ];
+                  double precess_matrix[ 3 ][ 3 ];
+                  double rotate_matrix[ 3 ][ 3 ];
+                  double vec1[ 3 ];
+                  double vec2[ 3 ];
+
+/* Create the conversion matrix. */
+                  palPrec( 2000.0, palEpj( args[ 0 ] ), precess_matrix );
+                  palEcmat( args[ 0 ], rotate_matrix );
+                  palDmxm( rotate_matrix, precess_matrix, convert_matrix );
+
+/* Apply it. */
+                  if ( forward ) {
+                     TRAN_ARRAY(palDcs2c( alpha[ point ], delta[ point ],
+                                          vec1 );
+                                palDmxv( convert_matrix, vec1, vec2 );
+                                palDcc2s( vec2, alpha + point, delta + point );
+                                alpha[ point ] = palDranrm ( alpha[ point ] );)
+                  } else {
+                     TRAN_ARRAY(palDcs2c( alpha[ point ], delta[ point ],
+                                          vec1 );
+                                palDimxv( convert_matrix, vec1, vec2 );
+                                palDcc2s( vec2, alpha + point, delta + point );
+                                alpha[ point ] = palDranrm ( alpha[ point ] );)
+                  }
+	       }
+               break;
+
+/* Convert ICRS to J2000.0 equatorial. */
+/* ----------------------------------- */
+/* Apply the conversion to each point. */
+	    case AST__SLA_HFK5Z:
+               if ( forward ) {
+                  double dr5;
+                  double dd5;
+                  TRAN_ARRAY(palHfk5z( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point,
+                                       &dr5, &dd5 );)
+
+/* The inverse simply uses the inverse SLALIB function. */
+	       } else {
+                  TRAN_ARRAY(palFk5hz( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point );)
+	       }
+               break;
+
+/* Convert J2000.0 to ICRS equatorial. */
+/* ----------------------------------- */
+/* This is the same as above, but with the forward and inverse cases
+   transposed. */
+	    case AST__SLA_FK5HZ:
+               if ( forward ) {
+                  TRAN_ARRAY(palFk5hz( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point );)
+
+/* The inverse simply uses the inverse SLALIB function. */
+	       } else {
+                  double dr5;
+                  double dd5;
+                  TRAN_ARRAY(palHfk5z( alpha[ point ], delta[ point ],
+                                       args[ 0 ],
+                                       alpha + point, delta + point,
+                                       &dr5, &dd5 );)
+	       }
+               break;
+
+/* Convert horizon to equatorial. */
+/* ------------------------------ */
+/* Apply the conversion to each point. */
+	    case AST__SLA_DH2E:
+               if ( forward ) {
+                  TRAN_ARRAY(Dh2e( alpha[ point ], delta[ point ],
+                                      args[ 0 ], args[ 1 ],
+                                      alpha + point, delta + point, status );)
+
+/* The inverse simply uses the inverse SLALIB function. */
+	       } else {
+                  TRAN_ARRAY(De2h( alpha[ point ], delta[ point ],
+                                      args[ 0 ], args[ 1 ],
+                                      alpha + point, delta + point, status );)
+	       }
+               break;
+
+/* Convert equatorial to horizon. */
+/* ------------------------------ */
+/* This is the same as above, but with the forward and inverse cases
+   transposed. */
+	    case AST__SLA_DE2H:
+               if ( forward ) {
+                  TRAN_ARRAY(De2h( alpha[ point ], delta[ point ],
+                                      args[ 0 ], args[ 1 ],
+                                      alpha + point, delta + point, status );)
+
+/* The inverse simply uses the inverse SLALIB function. */
+	       } else {
+                  TRAN_ARRAY(Dh2e( alpha[ point ], delta[ point ],
+                                      args[ 0 ], args[ 1 ],
+                                      alpha + point, delta + point, status );)
+	       }
+               break;
+
+/* Convert galactic coordinates to J2000.0 equatorial. */
+/* --------------------------------------------------- */
+/* Apply the conversion to each point. */
+	    case AST__SLA_GALEQ:
+               if ( forward ) {
+                  TRAN_ARRAY(palGaleq( alpha[ point ], delta[ point ],
+                                       alpha + point, delta + point );)
+
+/* The inverse simply uses the inverse SLALIB function. */
+	       } else {
+                  TRAN_ARRAY(palEqgal( alpha[ point ], delta[ point ],
+                                       alpha + point, delta + point );)
+	       }
+               break;
+
+/* Convert J2000.0 equatorial to galactic coordinates. */
+/* --------------------------------------------------- */
+/* This is the same as above, but with the forward and inverse cases
+   transposed. */
+	    case AST__SLA_EQGAL:
+               if ( forward ) {
+                  TRAN_ARRAY(palEqgal( alpha[ point ], delta[ point ],
+                                       alpha + point, delta + point );)
+	       } else {
+                  TRAN_ARRAY(palGaleq( alpha[ point ], delta[ point ],
+                                       alpha + point, delta + point );)
+               }
+               break;
+
+/* Convert galactic to supergalactic coordinates. */
+/* ---------------------------------------------- */
+/* Apply the conversion to each point. */
+	    case AST__SLA_GALSUP:
+               if ( forward ) {
+                  TRAN_ARRAY(palGalsup( alpha[ point ], delta[ point ],
+                                        alpha + point, delta + point );)
+
+/* The inverse simply uses the inverse SLALIB function. */
+               } else {
+                  TRAN_ARRAY(palSupgal( alpha[ point ], delta[ point ],
+                                        alpha + point, delta + point );)
+               }
+               break;
+
+/* Convert supergalactic coordinates to galactic. */
+/* ---------------------------------------------- */
+/* This is the same as above, but with the forward and inverse cases
+   transposed. */
+	    case AST__SLA_SUPGAL:
+               if ( forward ) {
+                  TRAN_ARRAY(palSupgal( alpha[ point ], delta[ point ],
+                                        alpha + point, delta + point );)
+               } else {
+                  TRAN_ARRAY(palGalsup( alpha[ point ], delta[ point ],
+                                        alpha + point, delta + point );)
+               }
+               break;
+
+/* If the conversion type was not recognised, then report an error
+   (this should not happen unless validation in astSlaAdd has failed
+   to detect a bad value previously). */
+            default:
+               astError( AST__SLAIN, "astTransform(%s): Corrupt %s contains "
+                         "invalid SLALIB sky coordinate conversion code (%d).", status,
+                         astGetClass( this ), astGetClass( this ),
+                         (int) ct );
+               break;
+
+/* Convert any STP coordinates to J2000 equatorial. */
+/* ------------------------------------------------ */
+            case AST__HPCEQ:
+            case AST__HPREQ:
+            case AST__HEEQ:
+               {
+
+/* Get the code for the appropriate 3D STP coordinate system to use.
+   Also, get a point to the observer position, if needed. */
+                  if( ct == AST__HPCEQ ) {
+                    sys = AST__HPC;
+                    obs = args + 1;
+
+                  } else if( ct == AST__HPREQ ) {
+                    sys = AST__HPR;
+                    obs = args + 1;
+
+                  } else {
+                    sys = AST__GSE;
+                    obs = NULL;
+
+                  }
+
+/* Store the 3D positions to be transformed. The supplied arrays are used
+   for the longitude and latitude values. No radius values are supplied.
+   (a value of 1AU will be used in the transformation). */
+                  p[0] = alpha;
+                  p[1] = delta;
+                  p[2] = NULL;
+
+/* Convert the supplied positions to (or from) AST__HEQ, ignoring the
+   distinction between the origin of the input and output systems (which
+   is appropriate since we are considering points at an infinite distance
+   from the observer). */
+                  if( forward ) {
+                     STPConv( args[ 0 ], 1, npoint, sys, obs, p,
+                              AST__HAQ, NULL, p, status );
+                  } else {
+                     STPConv( args[ 0 ], 1, npoint, AST__HAQ, NULL, p,
+                              sys, obs, p, status );
+                  }
+	       }
+               break;
+
+
+/* Convert J2000 equatorial to any STP coordinates. */
+/* ------------------------------------------------ */
+/* Same as above, but with forward and inverse cases transposed. */
+            case AST__EQHPC:
+            case AST__EQHPR:
+            case AST__EQHE:
+               {
+
+/* Get the code for the appropriate 3D STP coordinate system to use.
+   Also, get a point to the observer position, if needed. */
+                  if( ct == AST__EQHPC ) {
+                    sys = AST__HPC;
+                    obs = args + 1;
+
+                  } else if( ct == AST__EQHPR ) {
+                    sys = AST__HPR;
+                    obs = args + 1;
+
+                  } else {
+                    sys = AST__GSE;
+                    obs = NULL;
+
+                  }
+
+/* Store the 3D positions to be transformed. The supplied arrays are used
+   for the longitude and latitude values. No radius values are supplied.
+   (a value of 1AU will be used in the transformation). */
+                  p[0] = alpha;
+                  p[1] = delta;
+                  p[2] = NULL;
+
+/* Convert the supplied positions from (or to) AST__HEQ, ignoring the
+   distinction between the origin of the input and output systems (which
+   is appropriate since we are considering points at an infinite distance
+   from the observer). */
+                  if( forward ) {
+                     STPConv( args[ 0 ], 1, npoint, AST__HAQ, NULL, p,
+                              sys, obs, p, status );
+                  } else {
+                     STPConv( args[ 0 ], 1, npoint, sys, obs, p,
+                              AST__HAQ, NULL, p, status );
+                  }
+	       }
+               break;
+
+/* Convert dynamical J2000.0 to ICRS. */
+/* ---------------------------------- */
+/* Apply the conversion to each point. */
+	    case AST__J2000H:
+               J2000H( forward, npoint, alpha, delta, status );
+               break;
+
+/* Convert ICRS to dynamical J2000.0  */
+/* ---------------------------------- */
+	    case AST__HJ2000:
+               J2000H( !(forward), npoint, alpha, delta, status );
+               break;
+
+/* Convert HA to RA, or RA to HA */
+/* ----------------------------- */
+/* The forward and inverse transformations are the same. */
+	    case AST__H2R:
+	    case AST__R2H:
+               TRAN_ARRAY( alpha[ point ] = args[ 0 ] - alpha[ point ]; )
+               break;
+
+         }
+      }
+   }
+
+/* If an error has occurred and a new PointSet may have been created, then
+   clean up by annulling it. In any case, ensure that a NULL result is
+   returned.*/
+   if ( !astOK ) {
+      if ( !out ) result = astAnnul( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+
+/* Undefine macros local to this function. */
+#undef TRAN_ARRAY
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for SlaMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for SlaMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstSlaMap *in;                /* Pointer to input SlaMap */
+   AstSlaMap *out;               /* Pointer to output SlaMap */
+   int cvt;                      /* Loop counter for coordinate conversions */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output SlaMap structures. */
+   in = (AstSlaMap *) objin;
+   out = (AstSlaMap *) objout;
+
+/* For safety, first clear any references to the input memory from the output
+   SlaMap. */
+   out->cvtargs = NULL;
+   out->cvtextra = NULL;
+   out->cvttype = NULL;
+
+/* Allocate memory for the output array of argument list pointers. */
+   out->cvtargs = astMalloc( sizeof( double * ) * (size_t) in->ncvt );
+
+/* Allocate memory for the output array of extra (intermediate) values. */
+   out->cvtextra = astMalloc( sizeof( double * ) * (size_t) in->ncvt );
+
+/* If necessary, allocate memory and make a copy of the input array of sky
+   coordinate conversion codes. */
+   if ( in->cvttype ) out->cvttype = astStore( NULL, in->cvttype,
+                                               sizeof( int )
+                                               * (size_t) in->ncvt );
+
+/* If OK, loop through each conversion in the input SlaMap and make a copy of
+   its argument list, storing the new pointer in the output argument list
+   array. */
+   if ( astOK ) {
+      for ( cvt = 0; cvt < in->ncvt; cvt++ ) {
+         out->cvtargs[ cvt ] = astStore( NULL, in->cvtargs[ cvt ],
+                                         astSizeOf( in->cvtargs[ cvt ] ) );
+         out->cvtextra[ cvt ] = astStore( NULL, in->cvtextra[ cvt ],
+                                         astSizeOf( in->cvtextra[ cvt ] ) );
+      }
+
+/* If an error occurred while copying the argument lists, loop through the
+   conversions again and clean up by ensuring that the new memory allocated for
+   each argument list is freed. */
+      if ( !astOK ) {
+         for ( cvt = 0; cvt < in->ncvt; cvt++ ) {
+            out->cvtargs[ cvt ] = astFree( out->cvtargs[ cvt ] );
+	 }
+      }
+   }
+
+/* If an error occurred, free all other memory allocated above. */
+   if ( !astOK ) {
+      out->cvtargs = astFree( out->cvtargs );
+      out->cvtextra = astFree( out->cvtextra );
+      out->cvttype = astFree( out->cvttype );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for SlaMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for SlaMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstSlaMap *this;              /* Pointer to SlaMap */
+   int cvt;                      /* Loop counter for coordinate conversions */
+
+/* Obtain a pointer to the SlaMap structure. */
+   this = (AstSlaMap *) obj;
+
+/* Loop to free the memory containing the argument list for each sky coordinate
+   conversion. */
+   for ( cvt = 0; cvt < this->ncvt; cvt++ ) {
+      this->cvtargs[ cvt ] = astFree( this->cvtargs[ cvt ] );
+      this->cvtextra[ cvt ] = astFree( this->cvtextra[ cvt ] );
+   }
+
+/* Free the memory holding the array of conversion types and the array of
+   argument list pointers. */
+   this->cvtargs = astFree( this->cvtargs );
+   this->cvtextra = astFree( this->cvtextra );
+   this->cvttype = astFree( this->cvttype );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for SlaMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the SlaMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the SlaMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstSlaMap *this;              /* Pointer to the SlaMap structure */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword string */
+   const char *argdesc[ MAX_SLA_ARGS ]; /* Pointers to argument descriptions */
+   const char *comment;          /* Pointer to comment string */
+   const char *sval;             /* Pointer to string value */
+   int iarg;                     /* Loop counter for arguments */
+   int icvt;                     /* Loop counter for conversion steps */
+   int ival;                     /* Integer value */
+   int nargs;                    /* Number of conversion arguments */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SlaMap structure. */
+   this = (AstSlaMap *) this_object;
+
+/* Write out values representing the instance variables for the SlaMap
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Number of conversion steps. */
+/* --------------------------- */
+/* Regard this as "set" if it is non-zero. */
+   ival = this->ncvt;
+   set = ( ival != 0 );
+   astWriteInt( channel, "Nsla", set, 0, ival, "Number of conversion steps" );
+
+/* Write out data for each conversion step... */
+   for ( icvt = 0; icvt < this->ncvt; icvt++ ) {
+
+/* Conversion type. */
+/* ---------------- */
+/* Change each conversion type code into an equivalent string and
+   obtain associated descriptive information. If the conversion code
+   was not recognised, report an error and give up. */
+      if ( astOK ) {
+         sval = CvtString( this->cvttype[ icvt ], &comment, &nargs, argdesc, status );
+         if ( astOK && !sval ) {
+            astError( AST__SLAIN,
+                      "astWrite(%s): Corrupt %s contains invalid SLALIB "
+                      "sky coordinate conversion code (%d).", status,
+                      astGetClass( channel ), astGetClass( this ),
+                      (int) this->cvttype[ icvt ] );
+            break;
+         }
+
+/* Create an appropriate keyword and write out the conversion code
+   information. */
+         (void) sprintf( key, "Sla%d", icvt + 1 );
+         astWriteString( channel, key, 1, 1, sval, comment );
+
+/* Write out data for each conversion argument... */
+         for ( iarg = 0; iarg < nargs; iarg++ ) {
+
+/* Arguments. */
+/* ---------- */
+/* Create an appropriate keyword and write out the argument value,
+   accompanied by the descriptive comment obtained above. */
+            (void) sprintf( key, "Sla%d%c", icvt + 1, ALPHABET[ iarg ] );
+            astWriteDouble( channel, key, 1, 1, this->cvtargs[ icvt ][ iarg ],
+                            argdesc[ iarg ] );
+         }
+
+/* Quit looping if an error occurs. */
+         if ( !astOK ) break;
+      }
+   }
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsASlaMap and astCheckSlaMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(SlaMap,Mapping)
+astMAKE_CHECK(SlaMap)
+
+AstSlaMap *astSlaMap_( int flags, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astSlaMap
+f     AST_SLAMAP
+
+*  Purpose:
+*     Create an SlaMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "slamap.h"
+c     AstSlaMap *astSlaMap( int flags, const char *options, ... )
+f     RESULT = AST_SLAMAP( FLAGS, OPTIONS, STATUS )
+
+*  Class Membership:
+*     SlaMap constructor.
+
+*  Description:
+*     This function creates a new SlaMap and optionally initialises
+*     its attributes.
+*
+*     An SlaMap is a specialised form of Mapping which can be used to
+*     represent a sequence of conversions between standard celestial
+*     (longitude, latitude) coordinate systems.
+*
+*     When an SlaMap is first created, it simply performs a unit
+c     (null) Mapping on a pair of coordinates. Using the astSlaAdd
+f     (null) Mapping on a pair of coordinates. Using the AST_SLAADD
+c     function, a series of coordinate conversion steps may then be
+f     routine, a series of coordinate conversion steps may then be
+*     added, selected from those provided by the SLALIB Positional
+*     Astronomy Library (Starlink User Note SUN/67). This allows
+*     multi-step conversions between a variety of celestial coordinate
+*     systems to be assembled out of the building blocks provided by
+*     SLALIB.
+*
+*     For details of the individual coordinate conversions available,
+c     see the description of the astSlaAdd function.
+f     see the description of the AST_SLAADD routine.
+
+*  Parameters:
+c     flags
+f     FLAGS = INTEGER (Given)
+c        This parameter is reserved for future use and should currently
+f        This argument is reserved for future use and should currently
+*        always be set to zero.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new SlaMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new SlaMap. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSlaMap()
+f     AST_SLAMAP = INTEGER
+*        A pointer to the new SlaMap.
+
+*  Notes:
+*     - The Nin and Nout attributes (number of input and output
+*     coordinates) for an SlaMap are both equal to 2. The first
+*     coordinate is the celestial longitude and the second coordinate
+*     is the celestial latitude. All coordinate values are in radians.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSlaMap *new;               /* Pointer to the new SlaMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SlaMap, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitSlaMap( NULL, sizeof( AstSlaMap ), !class_init, &class_vtab,
+                        "SlaMap", flags );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new SlaMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new SlaMap. */
+   return new;
+}
+
+AstSlaMap *astSlaMapId_( int flags, const char *options, ... ) {
+/*
+*  Name:
+*     astSlaMapId_
+
+*  Purpose:
+*     Create an SlaMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     AstSlaMap *astSlaMapId_( int flags, const char *options, ... )
+
+*  Class Membership:
+*     SlaMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astSlaMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astSlaMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astSlaMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astSlaMap_.
+
+*  Returned Value:
+*     The ID value associated with the new SlaMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSlaMap *new;               /* Pointer to the new SlaMap */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SlaMap, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitSlaMap( NULL, sizeof( AstSlaMap ), !class_init, &class_vtab,
+                        "SlaMap", flags );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new SlaMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new SlaMap. */
+   return astMakeId( new );
+}
+
+AstSlaMap *astInitSlaMap_( void *mem, size_t size, int init,
+                           AstSlaMapVtab *vtab, const char *name,
+                           int flags, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSlaMap
+
+*  Purpose:
+*     Initialise an SlaMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     AstSlaMap *astInitSlaMap( void *mem, size_t size, int init,
+*                               AstSlaMapVtab *vtab, const char *name,
+*                               int flags )
+
+*  Class Membership:
+*     SlaMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new SlaMap object. It allocates memory (if necessary) to accommodate
+*     the SlaMap plus any additional data associated with the derived class.
+*     It then initialises an SlaMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for an SlaMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the SlaMap is to be initialised.
+*        This must be of sufficient size to accommodate the SlaMap data
+*        (sizeof(SlaMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the SlaMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the SlaMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the SlaMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new SlaMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astClass
+*        method).
+*     flags
+*        This parameter is reserved for future use. It is currently ignored.
+
+*  Returned Value:
+*     A pointer to the new SlaMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSlaMap *new;               /* Pointer to the new SlaMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitSlaMapVtab( vtab, name );
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the SlaMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstSlaMap *) astInitMapping( mem, size, 0,
+                                       (AstMappingVtab *) vtab, name,
+                                       2, 2, 1, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the SlaMap data. */
+/* --------------------------- */
+/* The initial state is with no SLALIB conversions set, in which condition the
+   SlaMap simply implements a unit mapping. */
+      new->ncvt = 0;
+      new->cvtargs = NULL;
+      new->cvtextra = NULL;
+      new->cvttype = NULL;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstSlaMap *astLoadSlaMap_( void *mem, size_t size,
+                           AstSlaMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadSlaMap
+
+*  Purpose:
+*     Load a SlaMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "slamap.h"
+*     AstSlaMap *astLoadSlaMap( void *mem, size_t size,
+*                               AstSlaMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     SlaMap loader.
+
+*  Description:
+*     This function is provided to load a new SlaMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     SlaMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a SlaMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the SlaMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        SlaMap data (sizeof(SlaMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the SlaMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the SlaMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstSlaMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new SlaMap. If this is NULL, a pointer to
+*        the (static) virtual function table for the SlaMap class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "SlaMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new SlaMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstSlaMap *new;               /* Pointer to the new SlaMap */
+   char *sval;                   /* Pointer to string value */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword string */
+   const char *argdesc[ MAX_SLA_ARGS ]; /* Pointers to argument descriptions */
+   const char *comment;          /* Pointer to comment string */
+   int iarg;                     /* Loop counter for arguments */
+   int icvt;                     /* Loop counter for conversion steps */
+   int nargs;                    /* Number of conversion arguments */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this SlaMap. In this case the
+   SlaMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstSlaMap );
+      vtab = &class_vtab;
+      name = "SlaMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitSlaMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built SlaMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "SlaMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Number of conversion steps. */
+/* --------------------------- */
+/* Read the number of conversion steps and allocate memory to hold
+   data for each step. */
+      new->ncvt = astReadInt( channel, "nsla", 0 );
+      if ( new->ncvt < 0 ) new->ncvt = 0;
+      new->cvttype = astMalloc( sizeof( int ) * (size_t) new->ncvt );
+      new->cvtargs = astMalloc( sizeof( double * ) * (size_t) new->ncvt );
+      new->cvtextra = astMalloc( sizeof( double * ) * (size_t) new->ncvt );
+
+/* If an error occurred, ensure that all allocated memory is freed. */
+      if ( !astOK ) {
+         new->cvttype = astFree( new->cvttype );
+         new->cvtargs = astFree( new->cvtargs );
+         new->cvtextra = astFree( new->cvtextra );
+
+/* Otherwise, initialise the argument pointer array. */
+      } else {
+         for ( icvt = 0; icvt < new->ncvt; icvt++ ) {
+            new->cvtargs[ icvt ] = NULL;
+            new->cvtextra[ icvt ] = NULL;
+         }
+
+/* Read in data for each conversion step... */
+         for ( icvt = 0; icvt < new->ncvt; icvt++ ) {
+
+/* Conversion type. */
+/* ---------------- */
+/* Create an appropriate keyword and read the string representation of
+   the conversion type. */
+            (void) sprintf( key, "sla%d", icvt + 1 );
+            sval = astReadString( channel, key, NULL );
+
+/* If no value was read, report an error. */
+            if ( astOK ) {
+               if ( !sval ) {
+                  astError( AST__BADIN,
+                            "astRead(%s): An SLALIB sky coordinate conversion "
+                            "type is missing from the input SlaMap data.", status,
+                            astGetClass( channel ) );
+
+/* Otherwise, convert the string representation into the required
+   conversion type code. */
+               } else {
+                  new->cvttype[ icvt ] = CvtCode( sval, status );
+
+/* If the string was not recognised, report an error. */
+                  if ( new->cvttype[ icvt ] == AST__SLA_NULL ) {
+                     astError( AST__BADIN,
+                              "astRead(%s): Invalid SLALIB sky conversion "
+                              "type \"%s\" in SlaMap data.", status,
+                              astGetClass( channel ), sval );
+                  }
+               }
+
+/* Free the memory holding the string value. */
+               sval = astFree( sval );
+            }
+
+/* Obtain the number of arguments associated with the conversion and
+   allocate memory to hold them. */
+            (void) CvtString( new->cvttype[ icvt ], &comment, &nargs,
+                              argdesc, status );
+            new->cvtargs[ icvt ] = astMalloc( sizeof( double ) *
+                                              (size_t) nargs );
+
+/* Read in data for each argument... */
+            if ( astOK ) {
+               for ( iarg = 0; iarg < nargs; iarg++ ) {
+
+/* Arguments. */
+/* ---------- */
+/* Create an appropriate keyword and read each argument value. */
+                  (void) sprintf( key, "sla%d%c", icvt + 1, ALPHABET[ iarg ] );
+                  new->cvtargs[ icvt ][ iarg ] = astReadDouble( channel, key,
+                                                                AST__BAD );
+               }
+            }
+
+/* Quit looping if an error occurs. */
+            if ( !astOK ) break;
+         }
+      }
+
+/* If an error occurred, clean up by deleting the new SlaMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new SlaMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+void astSlaAdd_( AstSlaMap *this, const char *cvt, int narg, const double args[], int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,SlaMap,SlaAdd))( this, cvt, narg, args, status );
+}
+
+int astSlaIsEmpty_( AstSlaMap *this, int *status ) {
+   if ( !astOK ) return 1;
+   return (**astMEMBER(this,SlaMap,SlaIsEmpty))( this, status );
+}
+
diff --git a/ast/slamap.h b/ast/slamap.h
new file mode 100644
index 0000000..e7280b8
--- /dev/null
+++ b/ast/slamap.h
@@ -0,0 +1,330 @@
+#if !defined( SLAMAP_INCLUDED )  /* Include this file only once */
+#define SLAMAP_INCLUDED
+/*
+*+
+*  Name:
+*     slamap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the SlaMap class.
+
+*  Invocation:
+*     #include "slamap.h"
+
+*  Description:
+*     This include file defines the interface to the SlaMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The SlaMap class encapsulates the conversions provided by the
+*     SLALIB library (SUN/67) for converting between different sky
+*     coordinate systems.  Since, typically, a sequence of these
+*     SLALIB conversions is required, an SlaMap can be used to
+*     accumulate a series of conversions which it then applies in
+*     sequence.
+
+*  Inheritance:
+*     The SlaMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        astTransform
+*           Use an SlaMap to transform a set of points.
+
+*     Protected:
+*        astMapMerge
+*           Simplify a sequence of Mappings containing an SlaMap.
+
+*  New Methods Defined:
+*     Public:
+*        astSlaAdd
+*           Add a coordinate conversion step to an SlaMap.
+
+*     Private:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsASlaMap
+*           Test class membership.
+*        astSlaMap
+*           Create an SlaMap.
+
+*     Protected:
+*        astCheckSlaMap
+*           Validate class membership.
+*        astInitSlaMap
+*           Initialise an SlaMap.
+*        astLoadSlaMap
+*           Load an SlaMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstSlaMap
+*           SlaMap object type.
+
+*     Protected:
+*        AstSlaMapVtab
+*           SlaMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     26-APR-1996 (RFWS):
+*        Original version.
+*     26-SEP-1996 (RFWS):
+*        Added external interface and I/O facilities.
+*     23-MAY-1997 (RFWS):
+*        Over-ride the astMapMerge method.
+*     15-OCT-2002 (DSB):
+*        Added astSTPConv, astSTPConv1, and STP coordinate system macros.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitSlaMapVtab method.
+*     22-FEB-2006 (DSB):
+*        Added cvtextra to the AstSlaMap structure.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+
+/* Macros */
+/* ====== */
+#if defined(astCLASS)            /* Protected */
+#define AST__NOSTP -1 /* An invalid value for an STP coordinate system */
+#define AST__HAE  0   /* Heliocentric-aries-ecliptic spherical coordinates */
+#define AST__HAEC 1   /* Heliocentric-aries-ecliptic cartesian coordinates */
+#define AST__HAQ  2   /* Heliocentric-aries-equatorial spherical coordinates */
+#define AST__HAQC 3   /* Heliocentric-aries-equatorial cartesian coordinates */
+#define AST__HG	  4   /* Heliographic spherical coordinates */
+#define AST__HGC  5   /* Heliographic cartesian coordinates */
+#define AST__HPC  6   /* Helioprojective-cartesian spherical coordinates */
+#define AST__HPCC 7   /* Helioprojective-cartesian cartesian coordinates */
+#define AST__HPR  8   /* Helioprojective-radial spherical coordinates */
+#define AST__HPRC 9   /* Helioprojective-radial cartesian coordinates */
+#define AST__GSE  10  /* Geocentric-solar-ecliptic spherical coordinates */
+#define AST__GSEC 11  /* Geocentric-solar-ecliptic cartesian coordinates */
+#endif
+
+/* One IAU astronomical unit, in metres. */
+#define AST__AU 1.49597870E11
+
+/* One solar radius (top of photosphere?), in metres (from "The Explanatory
+   Supplement to the Astronomical Almanac"). */
+#define AST__SOLRAD 6.96E8
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* SlaMap structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstSlaMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int *cvttype;                 /* Pointer to array of conversion types */
+   double **cvtargs;             /* Pointer to argument list pointer array */
+   double **cvtextra;            /* Pointer to intermediate values pointer array */
+   int ncvt;                     /* Number of conversions to perform */
+} AstSlaMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstSlaMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   void (* SlaAdd)( AstSlaMap *, const char *, int, const double[], int * );
+   int (* SlaIsEmpty)( AstSlaMap *, int * );
+} AstSlaMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstSlaMapGlobals {
+   AstSlaMapVtab Class_Vtab;
+   int Class_Init;
+   double Eq_Cache;
+   double Ep_Cache;
+   double Amprms_Cache[ 21 ];
+} AstSlaMapGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(SlaMap)           /* Check class membership */
+astPROTO_ISA(SlaMap)             /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstSlaMap *astSlaMap_( int, const char *, int *, ...);
+#else
+AstSlaMap *astSlaMapId_( int, const char *, ... )__attribute__((format(printf,2,3)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstSlaMap *astInitSlaMap_( void *, size_t, int, AstSlaMapVtab *,
+                           const char *, int, int * );
+
+/* Vtab initialiser. */
+void astInitSlaMapVtab_( AstSlaMapVtab *, const char *, int * );
+
+/* Loader. */
+AstSlaMap *astLoadSlaMap_( void *, size_t, AstSlaMapVtab *,
+                           const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitSlaMapGlobals_( AstSlaMapGlobals * );
+#endif
+
+/* Other functions. */
+void astSTPConv1_( double, int, double[3], double[3], int, double[3], double[3], int * );
+void astSTPConv_( double, int, int, double[3], double *[3], int, double[3], double *[3], int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+void astSlaAdd_( AstSlaMap *, const char *, int, const double[], int * );
+int astSlaIsEmpty_( AstSlaMap *, int * );
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckSlaMap(this) astINVOKE_CHECK(SlaMap,this,0)
+#define astVerifySlaMap(this) astINVOKE_CHECK(SlaMap,this,1)
+
+/* Test class membership. */
+#define astIsASlaMap(this) astINVOKE_ISA(SlaMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astSlaMap astINVOKE(F,astSlaMap_)
+#else
+#define astSlaMap astINVOKE(F,astSlaMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitSlaMap(mem,size,init,vtab,name,flags) \
+astINVOKE(O,astInitSlaMap_(mem,size,init,vtab,name,flags,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitSlaMapVtab(vtab,name) astINVOKE(V,astInitSlaMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadSlaMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadSlaMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckSlaMap to validate SlaMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+#define astSlaAdd(this,cvt,narg,args) \
+astINVOKE(V,astSlaAdd_(astCheckSlaMap(this),cvt,narg,args,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#define astSTPConv astSTPConv_
+#define astSTPConv1 astSTPConv1_
+#define astSlaIsEmpty(this) astINVOKE(V,astSlaIsEmpty_(astCheckSlaMap(this),STATUS_PTR))
+#endif
+
+#endif
+
+
+
+
+
diff --git a/ast/specfluxframe.c b/ast/specfluxframe.c
new file mode 100644
index 0000000..ea00d49
--- /dev/null
+++ b/ast/specfluxframe.c
@@ -0,0 +1,2189 @@
+/*
+*class++
+*  Name:
+*     SpecFluxFrame
+
+*  Purpose:
+*     Compound spectrum/flux Frame.
+
+*  Constructor Function:
+c     astSpecFluxFrame
+f     AST_SPECFLUXFRAME
+
+*  Description:
+*     A SpecFluxFrame combines a SpecFrame and a FluxFrame into a single
+*     2-dimensional compound Frame. Such a Frame can for instance be used
+*     to describe a Plot of a spectrum in which the first axis represents
+*     spectral position and the second axis represents flux.
+
+*  Inheritance:
+*     The SpecFluxFrame class inherits from the CmpFrame class.
+
+*  Attributes:
+*     The SpecFluxFrame class does not define any new attributes beyond
+*     those which are applicable to all CmpFrames. However, the attributes
+*     of the component Frames can be accessed as if they were attributes
+*     of the SpecFluxFrame. For instance, the SpecFluxFrame will recognise
+*     the "StdOfRest" attribute and forward access requests to the component
+*     SpecFrame. An axis index can optionally be appended to the end of any
+*     attribute name, in which case the request to access the attribute will
+*     be forwarded to the primary Frame defining the specified axis.
+
+*  Functions:
+c     The SpecFluxFrame class does not define any new functions beyond those
+f     The SpecFluxFrame class does not define any new routines beyond those
+*     which are applicable to all CmpFrames.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     8-DEC-2004 (DSB):
+*        Original version.
+*     29-APR-2011 (DSB):
+*        Prevent astFindFrame from matching a subclass template against a
+*        superclass target.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS SpecFluxFrame
+
+/* Define the first and last acceptable System values. */
+#define FIRST_SYSTEM AST__COMP
+#define LAST_SYSTEM AST__COMP
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "mapping.h"             /* Coordinate Mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "permmap.h"             /* Coordinate permutation Mappings */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "axis.h"                /* Coordinate axes */
+#include "cmpframe.h"            /* Parent CmpFrame class */
+#include "tranmap.h"             /* Separated transformation Mappings */
+#include "mathmap.h"             /* Algebraic Mappings */
+#include "ratemap.h"             /* Differential Mappings */
+#include "specframe.h"           /* SpecFrame class */
+#include "fluxframe.h"           /* FluxFrame class */
+#include "specfluxframe.h"       /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_match)( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int (* parent_subframe)( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static const char *(* parent_gettitle)( AstFrame *, int * );
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetTitle_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(SpecFluxFrame)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(SpecFluxFrame,Class_Init)
+#define class_vtab astGLOBAL(SpecFluxFrame,Class_Vtab)
+#define gettitle_buff astGLOBAL(SpecFluxFrame,GetTitle_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char gettitle_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstSpecFluxFrameVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstSpecFluxFrame *astSpecFluxFrameId_( void *, void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstFluxFrame *GetFluxFrame( AstSpecFluxFrame *, int, int * );
+static AstMapping *MakeMap2( AstSpecFluxFrame *, int * );
+static AstMapping *MakeMap3( AstSpecFluxFrame *, AstSpecFluxFrame *, int * );
+static AstMapping *MakeMapF( AstFluxFrame *, AstSpecFrame *, AstFluxFrame *, AstSpecFrame *, int * );
+static AstMapping *MakeMapI( AstFluxFrame *, AstSpecFrame *, AstFluxFrame *, AstSpecFrame *, int * );
+static AstSpecFrame *GetSpecFrame( AstSpecFluxFrame *, int, int * );
+static const char *GetTitle( AstFrame *, int * );
+static int MakeSFMapping( AstSpecFluxFrame *, AstSpecFluxFrame *, AstMapping **, int * );
+static int Match( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int SubFrame( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+
+/* Member functions. */
+/* ================= */
+
+static AstFluxFrame *GetFluxFrame( AstSpecFluxFrame *this, int std, int *status ){
+/*
+*  Name:
+*     GetFluxFrame
+
+*  Purpose:
+*     Return a pointer to the FluxFrame in a FluxSpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     AstFluxFrame *GetFluxFrame( AstSpecFluxFrame *this, int std, int *status )
+
+*  Class Membership:
+*     SpecFluxFrame member function.
+
+*  Description:
+*     Returns a pointer to the FluxFrame in a SpecFluxFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFluxFrame.
+*     std
+*        If non zero, then the returned FluxFrame is a standardised copy of
+*        the FluxFrame in the supplied SpecFluxFrame, in which the System has
+*        been set explicitly (rather than potentially being defaulted), and
+*        the Units have been cleared to use default units appropriate to
+*        the flux System.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the FluxFrame. Should be freed using astAnnul when no
+*     longer needed.
+
+*  Notes:
+*     NULL is returned if this function is invoked with the global error
+*     status set or if it should fail for any reason.
+*/
+
+/* Local Variables; */
+   AstFluxFrame *ff;
+   AstFluxFrame *ret;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* The FluxFrame is always the second Frame in the parent CmpFrame. */
+   ff = (AstFluxFrame *) ((AstCmpFrame *)this)->frame2;
+
+/* Produce a standardised copy of the FluxFrame if required, or clone the
+   above pointer otherwise. */
+   if( std ) {
+      ret = astCopy( ff );
+      astSetSystem( ret, astGetSystem( ff ) );
+      astClearUnit( ret, 0 );
+   } else {
+      ret = astClone( ff );
+   }
+
+/* Annul the returned pointer if anything went wrong. */
+   if( !astOK ) ret = astAnnul( ret );
+
+/* Return the result. */
+   return ret;
+}
+
+static AstSpecFrame *GetSpecFrame( AstSpecFluxFrame *this, int std, int *status ){
+/*
+*  Name:
+*     GetSpecFrame
+
+*  Purpose:
+*     Return a pointer to the SpecFrame in a FluxSpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     AstSpecFrame *GetSpecFrame( AstSpecFluxFrame *this, int std, int *status )
+
+*  Class Membership:
+*     SpecFluxFrame member function.
+
+*  Description:
+*     Returns a pointer to the SpecFrame in a SpecFluxFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFluxFrame.
+*     std
+*        If non zero, then the returned SpecFrame is a standardised copy of
+*        the SpecFrame in the supplied SpecFluxFrame, in which the System
+*        and Units have been set explicitly to the values appropriate to the
+*        flux system in use in the FluxFrame in the supplied SpecFluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the FluxFrame. Should be freed using astAnnul when no
+*     longer needed.
+
+*  Notes:
+*     NULL is returned if this function is invoked with the global error
+*     status set or if it should fail for any reason.
+*/
+
+/* Local Variables; */
+   AstFluxFrame *ff;
+   AstSpecFrame *ret;
+   AstSpecFrame *sf;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the SpecFrame (the first Frame in the parent CmpFrame). */
+   sf = (AstSpecFrame *) ((AstCmpFrame *)this)->frame1;
+
+/* If we want a standardised version of the SpecFrame... */
+   if( std ) {
+
+/* The FluxFrame is always the second Frame in the parent CmpFrame. */
+      ff = (AstFluxFrame *) ((AstCmpFrame *)this)->frame2;
+
+/* Produce a copy of the SpecFrame and set its System and Units
+   appropriate to the flux system (expressed in default units). */
+      ret = astCopy( sf );
+      astSetSystem( ret, astGetDensitySystem( ff ) );
+      astSetUnit( ret, 0, astGetDensityUnit( ff ) );
+
+/* If we are not standardising the SpecFrame, just return a clone of the
+   pointer in the parent CmpFrame. */
+   } else {
+      ret = astClone( sf );
+   }
+
+/* Annul the returned pointer if anything went wrong. */
+   if( !astOK ) ret = astAnnul( ret );
+
+/* Return the result. */
+   return ret;
+}
+
+static const char *GetTitle( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetTitle
+
+*  Purpose:
+*     Obtain a pointer to the Title string for a SpecFluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     const char *GetTitle( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SpecFluxFrame member function (over-rides the astGetTitle method
+*     inherited from the CmpFrame class).
+
+*  Description:
+*     This function returns a pointer to the Title string for a SpecFluxFrame.
+*     A pointer to a suitable default string is returned if no Title value has
+*     previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a null-terminated character string containing the requested
+*     information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   AstSpecFluxFrame *this;
+   AstSpecFrame *sf;
+   AstFluxFrame *ff;
+   const char *result;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_frame);
+
+/* Initialise. */
+   result = NULL;
+
+/* Obtain a pointer to the SpecFluxFrame structure. */
+   this = (AstSpecFluxFrame *) this_frame;
+
+/* See if a Title string has been set. If so, use the parent astGetTitle
+   method to obtain a pointer to it. */
+   if ( astTestTitle( this ) ) {
+      result = (*parent_gettitle)( this_frame, status );
+
+/* Otherwise, we will generate a default Title string. Obtain the values of the
+   SpecFrame's attributes that determine what this string will be. */
+   } else {
+      ff = GetFluxFrame( this, 0, status );
+      sf = GetSpecFrame( this, 0, status );
+
+      if( astOK ) {
+         sprintf( gettitle_buff, "%s versus %s", astGetLabel( ff, 0 ),
+                  astGetLabel( sf, 0 ) );
+         gettitle_buff[ 0 ] = toupper( gettitle_buff[ 0 ] );
+         result = gettitle_buff;
+      }
+
+      ff = astAnnul( ff );
+      sf = astAnnul( sf );
+
+   }
+
+/* If an error occurred, clear the returned pointer value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+void astInitSpecFluxFrameVtab_(  AstSpecFluxFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSpecFluxFrameVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a SpecFluxFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     void astInitSpecFluxFrameVtab( AstSpecFluxFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SpecFluxFrame vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the SpecFluxFrame class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstFrameVtab *frame;          /* Pointer to Frame component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitCmpFrameVtab( (AstCmpFrameVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsASpecFluxFrame) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstCmpFrameVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   frame = (AstFrameVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+
+   parent_match = frame->Match;
+   frame->Match = Match;
+
+   parent_subframe = frame->SubFrame;
+   frame->SubFrame = SubFrame;
+
+   parent_gettitle = frame->GetTitle;
+   frame->GetTitle = GetTitle;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetDump( vtab, Dump, "SpecFluxFrame",
+               "Compound spectral/flux coordinate system description" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static AstMapping *MakeMap2( AstSpecFluxFrame *this, int *status ){
+/*
+*  Name:
+*     MakeMap2
+
+*  Purpose:
+*     Generate the second Mapping required by MakeSFMapping
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     AstMapping *MakeMap2( AstSpecFluxFrame *this, int *status )
+
+*  Class Membership:
+*     SpecFluxFrame member function.
+
+*  Description:
+*     The second Mapping used by MakeSFMapping contains three Mappings in
+*     parallel which converts v1 (flux value) and x1 (spectral position) into
+*     default units, and passes the third axis (a copy of flux value)
+*     unchanged.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFluxFrame to use.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the required Mapping, or NULL if the Mapping cannot be
+*     created. The Mapping will have 3 inputs and 3 outputs.
+
+*  Notes:
+*     NULL is returned if this function is invoked with the global error
+*     status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *f1;
+   AstFrame *f2;
+   AstFrameSet *fs;
+   AstMapping *ax1_map;
+   AstMapping *ax2_map;
+   AstMapping *ax3_map;
+   AstMapping *ret;
+   AstMapping *tmap;
+
+/* Initialise. */
+   ret = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Input 0 is the supplied FluxFrame value and output 0 is the corresponding
+   value in the default units for the FluxFrame system. Take a copy of the
+   supplied FluxFrame, and fix its System value (which may be a default value
+   based on the Units string), and then clear the Units so that it represents
+   default units for the System. */
+   f1 = (AstFrame *) GetFluxFrame( this, 0, status );
+   f2 = (AstFrame *) GetFluxFrame( this, 1, status );
+
+/* Now, if conversion was possible, get the Mapping from the supplied
+   FluxFrame to the default units FluxFrame. */
+   fs = astConvert( f1, f2, "" );
+   f1 = astAnnul( f1 );
+   f2 = astAnnul( f2 );
+
+   if( fs ) {
+      ax1_map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+      fs = astAnnul( fs );
+
+/* Input 1 is the supplied SpecFrame value and output 1 is the corresponding
+   value in the spectral system used by the flux system (wavelength or
+   frequency). Take a copy of the supplied SpecFrame, and fix its System
+   value to wavelength or frequency (depending on the System value of the
+   FluxFrame), and set up units of Hz or Angstrom (these are the spectral
+   position units used within the default flux units for a FluxFrame). */
+      f1 = (AstFrame *) GetSpecFrame( this, 0, status );
+      f2 = (AstFrame *) GetSpecFrame( this, 1, status );
+
+/* Now, if conversion was possible, get the Mapping from the supplied
+   SpecFrame to the required SpecFrame. */
+      fs = astConvert( f1, f2, "" );
+      f1 = astAnnul( f1 );
+      f2 = astAnnul( f2 );
+
+      if( fs ) {
+         ax2_map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+         fs = astAnnul( fs );
+
+/* Create a UnitMap for the 3rd axis. */
+         ax3_map = (AstMapping *) astUnitMap( 1, "", status );
+
+/* Create a parallel CmpMap containing the three Mappings. */
+         tmap = (AstMapping *) astCmpMap( ax1_map, ax2_map, 0, "", status );
+         ret = (AstMapping *) astCmpMap( tmap, ax3_map, 0, "", status );
+
+/* Free remaining resources. */
+         tmap = astAnnul( tmap );
+         ax2_map = astAnnul( ax2_map );
+         ax3_map = astAnnul( ax3_map );
+
+      }
+      ax1_map = astAnnul( ax1_map );
+   }
+
+/* If an error has occurred, return NULL. */
+   if( !astOK ) ret = astAnnul( ret );
+
+/* Return the result */
+   return ret;
+}
+
+static AstMapping *MakeMap3( AstSpecFluxFrame *target, AstSpecFluxFrame *result, int *status ){
+/*
+*  Name:
+*     MakeMap3
+
+*  Purpose:
+*     Generate the third Mapping required by MakeSFMapping
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     AstMapping *MakeMap3( AstSpecFluxFrame *target, AstSpecFluxFrame *result, int *status )
+
+*  Class Membership:
+*     SpecFluxFrame member function.
+
+*  Description:
+*     The third Mapping used by MakeSFMapping converts input (v1,x1) in
+*     default units to output (v2,x2) in default units. The third axis (x1)
+*     in original units is converted to x2 in original units.
+
+*  Parameters:
+*     target
+*        Pointer to the first SpecFluxFrame.
+*     result
+*        Pointer to the second SpecFluxFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the required Mapping, or NULL if the Mapping cannot be
+*     created. The Mapping will have 3 inputs and 3 outputs.
+
+*  Notes:
+*     NULL is returned if this function is invoked with the global error
+*     status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstFluxFrame *ff2;
+   AstFluxFrame *ff1;
+   AstFrameSet *fs;
+   AstMapping *fmap;
+   AstMapping *imap;
+   AstMapping *mapa;
+   AstMapping *mapb;
+   AstMapping *ret;
+   AstSpecFrame *sf2;
+   AstSpecFrame *sf1;
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* The first two inputs and outputs are related by a TranMap which
+   converts between standardised (v1,x1) and standardised (v2,x2). Get
+   pointers to the standardised SpecFrames and FluxFrames in the two
+   supplied SpecFluxFrames. */
+   ff1 = GetFluxFrame( target, 1, status );
+   sf1 = GetSpecFrame( target, 1, status );
+   ff2 = GetFluxFrame( result, 1, status );
+   sf2 = GetSpecFrame( result, 1, status );
+
+/* Create the Mapping which defines the forward transformation of the
+   required TranMap. The forward transformation of this Mapping goes from
+   (v1,x1) to (v2,x2). */
+   fmap = MakeMapF( ff1, sf1, ff2, sf2, status );
+
+/* Create the Mapping which defines the inverse transformation of the
+   required TranMap. The inverse transformation of this Mapping goes from
+   (v2,x2) to (v1,x1). */
+   imap = MakeMapI( ff1, sf1, ff2, sf2, status );
+
+/* Combine these into a TranMap */
+   if( fmap && imap ) {
+      mapa = (AstMapping *) astTranMap( fmap, imap, "", status );
+   } else {
+      mapa = NULL;
+   }
+
+/* Free resources. */
+   ff1 = astAnnul( ff1 );
+   sf1 = astAnnul( sf1 );
+   ff2 = astAnnul( ff2 );
+   sf2 = astAnnul( sf2 );
+   if( fmap ) fmap = astAnnul( fmap );
+   if( imap ) imap = astAnnul( imap );
+
+/* The third input and output are related by a Mapping which converts
+   between supplied (x1) and supplied (x2). Get pointers to the original
+   unmodified SpecFrames in the two supplied SpecFluxFrames. */
+   sf1 = GetSpecFrame( target, 0, status );
+   sf2 = GetSpecFrame( result, 0, status );
+
+/* Find the Mapping from the first to the second. */
+   fs = astConvert( sf1, sf2, "" );
+   if( fs ) {
+      mapb = astGetMapping( fs, AST__BASE, AST__CURRENT );
+      fs = astAnnul( fs );
+   } else {
+      mapb = NULL;
+   }
+
+/* Free resources. */
+   sf1 = astAnnul( sf1 );
+   sf2 = astAnnul( sf2 );
+
+/* Combine the two Mappings in parallel. */
+   if( mapa && mapb ) ret = (AstMapping *) astCmpMap( mapa, mapb, 0, "", status );
+   if( mapa ) mapa = astAnnul( mapa );
+   if( mapb ) mapb = astAnnul( mapb );
+
+/* If an error has occurred, return NULL. */
+   if( !astOK ) ret = astAnnul( ret );
+
+/* Return the result */
+   return ret;
+}
+
+static AstMapping *MakeMapF( AstFluxFrame *v1, AstSpecFrame *x1,
+                             AstFluxFrame *v2, AstSpecFrame *x2, int *status ){
+/*
+*  Name:
+*     MakeMapF
+
+*  Purpose:
+*     Generate the forward part of the third Mapping required by MakeSFMapping
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     AstMapping *MakeMapF( AstFluxFrame *v1, AstSpecFrame *x1,
+*                           AstFluxFrame *v2, AstSpecFrame *x2, int *status )
+
+*  Class Membership:
+*     SpecFluxFrame member function.
+
+*  Description:
+*     Theis creates a 2-input 2-output Mapping which transforms
+*     input (v1,x1) in default units to output (v2,x2) in default units.
+
+*  Parameters:
+*     v1
+*        Pointer to the standardised input FluxFrame.
+*     x1
+*        Pointer to the standardised input SpecFrame.
+*     v2
+*        Pointer to the standardised output FluxFrame.
+*     x2
+*        Pointer to the standardised output SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the required Mapping, or NULL if the Mapping cannot be
+*     created.
+
+*  Notes:
+*     NULL is returned if this function is invoked with the global error
+*     status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpMap *cmap1;
+   AstCmpMap *cmap2;
+   AstCmpMap *cmap3;
+   AstFrameSet *fs;
+   AstMapping *m;
+   AstMapping *ret;
+   AstMathMap *div;
+   AstPermMap *perm;
+   AstRateMap *rate;
+   AstUnitMap *unit;
+   const char *fwd[1];
+   const char *inv[2];
+   int inperm[ 2 ];
+   int outperm[ 3 ];
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* First create the required component Mappings.
+   --------------------------------------------- */
+
+/* A Mapping which maps input spectral position (x1) into output spectral
+   position (x2). */
+   fs = astConvert( x1, x2, "" );
+   if( fs ) {
+      m = astGetMapping( fs, AST__BASE, AST__CURRENT );
+
+/* A 1-input 1-output Mapping in which the input is spectral position (x1)
+   and the output is the rate of change of output spectral position (x2)
+   with respect to input spectral position (x1). */
+      rate = astRateMap( m, 0, 0, "", status );
+
+/* A MathMap which is used to divide the flux value (v1) by the absolute rate
+   of change of x2 wrt x1 */
+      fwd[ 0 ] = "out=in0/abs(in1)";
+      inv[ 0 ] = "in0";
+      inv[ 1 ] = "in1";
+      div = astMathMap( 2, 1, 1, fwd, 2, inv, "", status );
+
+/* A 1D UnitMap used to copy v1. */
+      unit = astUnitMap( 1, "", status );
+
+/* A PermMap which is used to produce an extra output copy of x1. */
+      inperm[ 0 ] = 0;
+      inperm[ 1 ] = 2;
+      outperm[ 0 ] = 0;
+      outperm[ 1 ] = 1;
+      outperm[ 2 ] = 1;
+      perm = astPermMap( 2, inperm, 3, outperm, NULL, "", status );
+
+/* Now combine these component Mappings together.
+   --------------------------------------------- */
+
+/* First put the UnitMap and the RateMap in parallel. This produces a 2-in
+   2-out Mapping in which the inputs are (v1,x1) and the outputs are
+   (v1,dx2/dx1). */
+      cmap1 = astCmpMap( unit, rate, 0, "", status );
+
+/* Now put this in series with the dividing MathMap. This results in a
+   2-in, 1-out Mapping in which the inputs are v1 and x1 and the single
+   output is v2. */
+      cmap2 = astCmpMap( cmap1, div, 1, "", status );
+
+/* Now put this in parallel with the x1->x2 Mapping. This results in a
+   3-in, 2-out Mapping in which the inputs are (v1,x1,x1) and the outputs
+   are (v2,x2). */
+      cmap3 = astCmpMap( cmap2, m, 0, "", status );
+
+/* Finally put this in series with the PermMap. This results in a 2-in,
+   2-out Mapping in which the inputs are (v1,x1) and the outputs are
+   (v2,x2). */
+      ret = (AstMapping *) astCmpMap( perm, cmap3, 1, "", status );
+
+/* Free resources. */
+      fs = astAnnul( fs );
+      m = astAnnul( m );
+      rate = astAnnul( rate );
+      div= astAnnul( div );
+      unit = astAnnul( unit );
+      perm = astAnnul( perm );
+      cmap1 = astAnnul( cmap1 );
+      cmap2 = astAnnul( cmap2 );
+      cmap3 = astAnnul( cmap3 );
+   }
+
+/* If an error has occurred, return NULL. */
+   if( !astOK ) ret = astAnnul( ret );
+
+/* Return the result */
+   return ret;
+}
+
+static AstMapping *MakeMapI( AstFluxFrame *v1, AstSpecFrame *x1,
+                             AstFluxFrame *v2, AstSpecFrame *x2, int *status ){
+/*
+*  Name:
+*     MakeMapI
+
+*  Purpose:
+*     Generate the inverse part of the third Mapping required by MakeSFMapping
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     AstMapping *MakeMapI( AstFluxFrame *v1, AstSpecFrame *x1,
+*                           AstFluxFrame *v2, AstSpecFrame *x2 )
+
+*  Class Membership:
+*     SpecFluxFrame member function.
+
+*  Description:
+*     This creates a 2-input 2-output Mapping in which the inverse
+*     transformation transforms "outputs" representing (v2,x2) into
+*     "inputs" representing (v1,x1).
+
+*  Parameters:
+*     v1
+*        Pointer to the standardised input FluxFrame.
+*     x1
+*        Pointer to the standardised input SpecFrame.
+*     v2
+*        Pointer to the standardised output FluxFrame.
+*     x2
+*        Pointer to the standardised output SpecFrame.
+
+*  Returned Value:
+*     A pointer to the required Mapping, or NULL if the Mapping cannot be
+*     created.
+
+*  Notes:
+*     NULL is returned if this function is invoked with the global error
+*     status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstCmpMap *cmap1;
+   AstCmpMap *cmap2;
+   AstCmpMap *cmap3;
+   AstCmpMap *cmap4;
+   AstCmpMap *cmap5;
+   AstFrameSet *fs;
+   AstMapping *m;
+   AstMapping *ret;
+   AstMathMap *mult;
+   AstPermMap *perm;
+   AstRateMap *rate;
+   AstUnitMap *unit;
+   const char *fwd[1];
+   const char *inv[2];
+   int inperm[ 2 ];
+   int outperm[ 3 ];
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* We create a CmpMap in which the forward transformation foes from
+   (v2,x2) to (v1,x1) and we finally invert this Mapping to get the
+   required Mapping in which the *inverse* transformation goes from
+   (v2,x2) to (v1,x1).
+
+   First create the required component Mappings.
+   --------------------------------------------- */
+
+/* A Mapping which maps spectral position x1 into spectral position x2. */
+   fs = astConvert( x1, x2, "" );
+   if( fs ) {
+      m = astGetMapping( fs, AST__BASE, AST__CURRENT );
+
+/* A 1-input 1-output Mapping in which the input is spectral position x1
+   and the output is the rate of change of spectral position x2 with
+   respect to spectral position x1. */
+      rate = astRateMap( m, 0, 0, "", status );
+
+/* Now invert "m" so that its forward transformation goes from x2 to x1.
+   The RateMap created above retains a copy of the original Invert flag
+   for "m" and uses it in preference to the current value when transforming
+   points. */
+      astInvert( m );
+
+/* A MathMap which is used to multiple the flux value v2 by the
+   absolute rate of change of x2 wrt x1 */
+      fwd[ 0 ] = "out=in0*abs(in1)";
+      inv[ 0 ] = "in0";
+      inv[ 1 ] = "in1";
+      mult = astMathMap( 2, 1, 1, fwd, 2, inv, "", status );
+
+/* A 1D UnitMap used to copy various values. */
+      unit = astUnitMap( 1, "", status );
+
+/* A PermMap which is used to produce an extra copy of x1. */
+      inperm[ 0 ] = 0;
+      inperm[ 1 ] = 2;
+      outperm[ 0 ] = 0;
+      outperm[ 1 ] = 1;
+      outperm[ 2 ] = 1;
+      perm = astPermMap( 2, inperm, 3, outperm, NULL, "", status );
+
+/* Now combine these component Mappings together.
+   --------------------------------------------- */
+
+/* First put the UnitMap and the RateMap in parallel. This produces a 2-in
+   2-out Mapping in which the inputs are (v2,x1) and the outputs are
+   (v2,dx2/dx1). */
+      cmap1 = astCmpMap( unit, rate, 0, "", status );
+
+/* Now put this in series with the multiplying MathMap. This results in a
+   2-in, 1-out Mapping in which the inputs are (v2,x1) and the single
+   output is v1. */
+      cmap2 = astCmpMap( cmap1, mult, 1, "", status );
+
+/* Now put this in parallel with the UnitMap to get a 3-in, 2-out Mapping
+   in which the inputs are (v2,x1,x1) and the outputs are (v1,x1). */
+      cmap3 = astCmpMap( cmap2, unit, 0, "", status );
+
+/* Now put this in series with the PermMap to get a 2-in, 2-out Mapping
+   in which the inputs are (v2,x1) and the outputs are (v1,x1). */
+      cmap4 = astCmpMap( perm, cmap3, 1, "", status );
+
+/* Now put the UnitMap in parallel with the (x2->x1 Mapping to get a
+   2-in, 2-out Mapping in which the inputs are (v2,x2) and the outputs are
+   (v2,x1). */
+      cmap5 = astCmpMap( unit, m, 0, "", status );
+
+/* Finally put this in series with "cmap4" to get a 2-in 2-out Mapping
+   from (v2,x2) to (v1,x1). */
+      ret = (AstMapping *) astCmpMap( cmap5, cmap4, 1, "", status );
+
+/* Invert this so that the inverse transformation goes from (v2,x2) to
+   (v1,x1). */
+      astInvert( ret );
+
+/* Free resources. */
+      fs = astAnnul( fs );
+      m = astAnnul( m );
+      rate = astAnnul( rate );
+      mult = astAnnul( mult );
+      unit = astAnnul( unit );
+      perm = astAnnul( perm );
+      cmap1 = astAnnul( cmap1 );
+      cmap2 = astAnnul( cmap2 );
+      cmap3 = astAnnul( cmap3 );
+      cmap4 = astAnnul( cmap4 );
+      cmap5 = astAnnul( cmap5 );
+   }
+
+/* If an error has occurred, return NULL. */
+   if( !astOK ) ret = astAnnul( ret );
+
+/* Return the result */
+   return ret;
+}
+
+static int MakeSFMapping( AstSpecFluxFrame *target, AstSpecFluxFrame *result,
+                          AstMapping **map, int *status ){
+/*
+*  Name:
+*     MakeSFMapping
+
+*  Purpose:
+*     Generate a Mapping between two SpecFluxFrames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     int MakeSFMapping( AstSpecFluxFrame *target, AstSpecFluxFrame *result,
+*                        AstMapping **map, int *status )
+
+*  Class Membership:
+*     SpecFluxFrame member function.
+
+*  Description:
+*     This function takes two SpecFluxFrames and generates a Mapping that
+*     converts between them, taking account of differences in their
+*     coordinate systems, systems, units, etc. (but not allowing for any
+*     axis permutations).
+
+*  Parameters:
+*     target
+*        Pointer to the first SpecFluxFrame.
+*     result
+*        Pointer to the second SpecFluxFrame.
+*     map
+*        Pointer to a location which is to receive a pointer to the
+*        returned Mapping. The forward transformation of this Mapping
+*        will convert from "target" coordinates to "result"
+*        coordinates, and the inverse transformation will convert in
+*        the opposite direction (all coordinate values in radians).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the Mapping could be generated, or zero if the two
+*     SpecFluxFrames are sufficiently un-related that no meaningful Mapping
+*     can be produced.
+
+*  Notes:
+*     A value of zero is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map1;
+   AstMapping *map2;
+   AstMapping *map3;
+   AstMapping *map4;
+   AstMapping *map5;
+   AstMapping *tmap1;
+   AstMapping *tmap2;
+   AstMapping *tmap3;
+   AstMapping *tmap4;
+   int inperm[2];
+   int match;
+   int outperm[3];
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise the returned values. */
+   match = 0;
+   *map = NULL;
+
+/* Initialise other things. */
+   map1 = NULL;
+   map2 = NULL;
+   map3 = NULL;
+   map4 = NULL;
+   map5 = NULL;
+   tmap1 = NULL;
+   tmap2 = NULL;
+   tmap3 = NULL;
+   tmap4 = NULL;
+
+/* At the top level, the required Mapping consists of five Mappings in
+   series. Inputs 0 and 1 of the total Mapping correspond to the SpecFrame
+   and FluxFrame in the target SpecFluxFrame. These are referred to as X1
+   and V1. Outputs 0 and 1 of the total Mapping correspond to the SpecFrame
+   and FluxFrame in the result SpecFluxFrame. These are referred to as X2
+   and V2. */
+
+/* Map1 is a PermMap which copies v1 to its first output and x1 to its
+   second and third outputs. The inverse transformation copies v1 from
+   its first output and x1 from its third output. */
+   inperm[ 0 ] = 2;
+   inperm[ 1 ] = 0;
+   outperm[ 0 ] = 1;
+   outperm[ 1 ] = 0;
+   outperm[ 2 ] = 0;
+   map1 = (AstMapping *) astPermMap( 2, inperm, 3, outperm, NULL, "", status );
+
+/* Map2 contains three Mappings in parallel which converts v1 and x1 into
+   default units, and passes the third axis unchanged. */
+   map2 = MakeMap2( target, status );
+
+/* Map3 converts ( v1,x1) in default units to (v2,x2) in default units.
+   The third axis (x1) in original units is convert to x2 in original
+   units. */
+   map3 = map2 ? MakeMap3( target, result, status ) : NULL;
+
+/* Map4 converts (v2,x2) in default units to (v2,x2) in original units
+   and passes the third axis unchanged. This is similar to Map2 but based
+   on the result ratherthan the target, and in the opposite direction. */
+   if( map3 ) {
+      map4 = MakeMap2( result, status );
+      if( map4 ) astInvert( map4 );
+   } else {
+      map4 = NULL;
+   }
+
+/* Map5 is a PermMap which is the inverse of Map1. */
+   map5 = map4 ? astCopy( map1 ) : NULL;
+   if( map5 ) astInvert( map5 );
+
+/* Combine all 6 Mappings in series. */
+   if( map5 ) {
+      tmap1 = (AstMapping *) astCmpMap( map1, map2, 1, "", status );
+      tmap2 = (AstMapping *) astCmpMap( tmap1, map3, 1, "", status );
+      tmap3 = (AstMapping *) astCmpMap( tmap2, map4, 1, "", status );
+      tmap4 = (AstMapping *) astCmpMap( tmap3, map5, 1, "", status );
+
+/* Return the simplified total Mapping. */
+      *map = astSimplify( tmap4 );
+      match = 1;
+   }
+
+/* Free resources. */
+   if( map1 ) map1 = astAnnul( map1 );
+   if( map2 ) map2 = astAnnul( map2 );
+   if( map3 ) map3 = astAnnul( map3 );
+   if( map4 ) map4 = astAnnul( map4 );
+   if( map5 ) map5 = astAnnul( map5 );
+   if( tmap1 ) tmap1 = astAnnul( tmap1 );
+   if( tmap2 ) tmap2 = astAnnul( tmap2 );
+   if( tmap3 ) tmap3 = astAnnul( tmap3 );
+   if( tmap4 ) tmap4 = astAnnul( tmap4 );
+
+/* If an error occurred, annul the returned Mapping and clear the
+   returned values. */
+   if ( !astOK ) {
+      *map = astAnnul( *map );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static int Match( AstFrame *template_frame, AstFrame *target, int matchsub,
+                  int **template_axes, int **target_axes,
+                  AstMapping **map, AstFrame **result, int *status ) {
+/*
+*  Name:
+*     Match
+
+*  Purpose:
+*     Determine if conversion is possible between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     int Match( AstFrame *template, AstFrame *target, int matchsub,
+*                int **template_axes, int **target_axes,
+*                AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     SpecFluxFrame member function (over-rides the protected astMatch
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function matches a "template" SpecFluxFrame to a "target" Frame
+*     and determines whether it is possible to convert coordinates
+*     between them.  If it is, a Mapping that performs the
+*     transformation is returned along with a new Frame that describes
+*     the coordinate system that results when this Mapping is applied
+*     to the "target" coordinate system. In addition, information is
+*     returned to allow the axes in this "result" Frame to be
+*     associated with the corresponding axes in the "target" Frame and
+*     "template" SpecFluxFrame from which they are derived.
+
+*  Parameters:
+*     template
+*        Pointer to the template SpecFluxFrame. This describes the
+*        coordinate system (or set of possible coordinate systems)
+*        into which we wish to convert our coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate
+*        system in which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case.
+*     template_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the (zero-based) index of the
+*        template SpecFluxFrame axis from which it is derived. If it is not
+*        derived from any template axis, a value of -1 will be
+*        returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned
+*        if the requested coordinate conversion is possible. This
+*        pointer will point at a dynamically allocated array of
+*        integers with one element for each axis of the "result" Frame
+*        (see below). It must be freed by the caller (using astFree)
+*        when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element
+*        of this array will return the (zero-based) index of the
+*        target Frame axis from which it is derived. If it is not
+*        derived from any target axis, a value of -1 will be returned
+*        instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will
+*        be returned if the requested coordinate conversion is
+*        possible. If returned, the forward transformation of this
+*        Mapping may be used to convert coordinates between the
+*        "target" Frame and the "result" Frame (see below) and the
+*        inverse transformation will convert in the opposite
+*        direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be
+*        returned if the requested coordinate conversion is
+*        possible. If returned, this Frame describes the coordinate
+*        system that results from applying the returned Mapping
+*        (above) to the "target" coordinate system. In general, this
+*        Frame will combine attributes from (and will therefore be
+*        more specific than) both the target Frame and the template
+*        SpecFluxFrame. In particular, when the template allows the
+*        possibility of transformaing to any one of a set of
+*        alternative coordinate systems, the "result" Frame will
+*        indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate
+*     conversion is possible. Otherwise zero is returned (this will
+*     not in itself result in an error condition).
+
+*  Notes:
+*     - By default, the "result" Frame will have its number of axes
+*     and axis order determined by the "template" SpecFluxFrame. However,
+*     if the PreserveAxes attribute of the template SpecFluxFrame is
+*     non-zero, then the axis count and axis order of the "target"
+*     Frame will be used instead.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstSpecFluxFrame *template;   /* Pointer to template SpecFluxFrame structure */
+   int match;                    /* Coordinate conversion possible? */
+   int swap1;                    /* Template axes swapped? */
+   int swap2;                    /* Target axes swapped? */
+   int swap;                     /* Additional axis swap needed? */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the template SpecFluxFrame structure. */
+   template = (AstSpecFluxFrame *) template_frame;
+
+/* If the target is not a SpecFluxFrame, use the results returned by the
+   parent Match method inherited from the CmpFrame class. */
+   if( !astIsASpecFluxFrame( target ) ) {
+      match = (*parent_match)( template_frame, target, matchsub, template_axes,
+                               target_axes, map, result, status );
+
+
+/* If the target is a SpecFluxFrame, see if we can convert between target
+   and template */
+   } else {
+
+/* We must now decide how the order of the axes in the result Frame relates to
+   the order of axes in the target Frame. There are two factors involved. The
+   first depends on whether the axis permutation array for the template
+   SpecFluxFrame (whose method we are executing) causes an axis
+   reversal. Determine this by permuting axis index zero. */
+      swap1 = ( astValidateAxis( template, 0, 1, "astMatch" ) != 0 );
+
+/* The second factor depends on whether the axes of the target SpecFluxFrame
+   causes an axis reversal. Determine this by permuting axis index zero. */
+      swap2 = ( astValidateAxis( target, 0, 1, "astMatch" ) != 0 );
+
+/* Combine these to determine if an additional axis swap will be
+   needed. */
+      swap = ( swap1 != swap2 );
+
+/* Now check to see if this additional swap is permitted by the template's
+   Permute attribute. */
+      match = ( !swap || astGetPermute( template ) );
+
+/* Allocate the target and template axes arrays. */
+      *template_axes = astMalloc( sizeof(int)*2 );
+      *target_axes = astMalloc( sizeof(int)*2 );
+
+/* If the Frames still match, we next set up the axis association
+   arrays. */
+      if ( astOK && match ) {
+
+/* If the target axis order is to be preserved, then the target axis
+   association involves no permutation but the template axis
+   association may involve an axis swap. */
+         if ( astGetPreserveAxes( template ) ) {
+            (*template_axes)[ 0 ] = swap;
+            (*template_axes)[ 1 ] = !swap;
+            (*target_axes)[ 0 ] = 0;
+            (*target_axes)[ 1 ] = 1;
+
+/* Otherwise, any swap applies to the target axis association
+   instead. */
+         } else {
+            (*template_axes)[ 0 ] = 0;
+            (*template_axes)[ 1 ] = 1;
+            (*target_axes)[ 0 ] = swap;
+            (*target_axes)[ 1 ] = !swap;
+         }
+
+/* Use the target's "astSubFrame" method to create a new Frame (the
+   result Frame) with copies of the target axes in the required
+   order. This process also overlays the template attributes on to the
+   target Frame and returns a Mapping between the target and result
+   Frames which effects the required coordinate conversion. */
+         match = astSubFrame( target, template, 2, *target_axes, *template_axes,
+                              map, result );
+
+/* If an error occurred, or conversion to the result Frame's
+   coordinate system was not possible, then free all memory, annul the
+   returned objects, and reset the returned value. */
+         if ( !astOK || !match ) {
+            *template_axes = astFree( *template_axes );
+            *target_axes = astFree( *target_axes );
+            if( *map ) *map = astAnnul( *map );
+            if( *result ) *result = astAnnul( *result );
+            match = 0;
+         }
+      }
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static int SubFrame( AstFrame *target_frame, AstFrame *template,
+                     int result_naxes, const int *target_axes,
+                     const int *template_axes, AstMapping **map,
+                     AstFrame **result, int *status ) {
+/*
+*  Name:
+*     SubFrame
+
+*  Purpose:
+*     Select axes from a SpecFluxFrame and convert to the new coordinate system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     int SubFrame( AstFrame *target, AstFrame *template,
+*                   int result_naxes, const int *target_axes,
+*                   const int *template_axes, AstMapping **map,
+*                   AstFrame **result, int *status )
+
+*  Class Membership:
+*     SpecFluxFrame member function (over-rides the protected astSubFrame
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function selects a requested sub-set (or super-set) of the
+*     axes from a "target" SpecFluxFrame and creates a new Frame with
+*     copies of the selected axes assembled in the requested order. It
+*     then optionally overlays the attributes of a "template" Frame on
+*     to the result. It returns both the resulting Frame and a Mapping
+*     that describes how to convert between the coordinate systems
+*     described by the target and result Frames. If necessary, this
+*     Mapping takes account of any differences in the Frames'
+*     attributes due to the influence of the template.
+
+*  Parameters:
+*     target
+*        Pointer to the target SpecFluxFrame, from which axes are to be selected.
+*     template
+*        Pointer to the template Frame, from which new attributes for
+*        the result Frame are to be obtained. Optionally, this may be
+*        NULL, in which case no overlaying of template attributes will
+*        be performed.
+*     result_naxes
+*        Number of axes to be selected from the target Frame. This
+*        number may be greater than or less than the number of axes in
+*        this Frame (or equal).
+*     target_axes
+*        Pointer to an array of int with result_naxes elements, giving
+*        a list of the (zero-based) axis indices of the axes to be
+*        selected from the target SpecFluxFrame. The order in which these
+*        are given determines the order in which the axes appear in
+*        the result Frame. If any of the values in this array is set
+*        to -1, the corresponding result axis will not be derived from
+*        the target Frame, but will be assigned default attributes
+*        instead.
+*     template_axes
+*        Pointer to an array of int with result_naxes elements. This
+*        should contain a list of the template axes (given as
+*        zero-based axis indices) with which the axes of the result
+*        Frame are to be associated. This array determines which axes
+*        are used when overlaying axis-dependent attributes of the
+*        template on to the result. If any element of this array is
+*        set to -1, the corresponding result axis will not receive any
+*        template attributes.
+*
+*        If the template argument is given as NULL, this array is not
+*        used and a NULL pointer may also be supplied here.
+*     map
+*        Address of a location to receive a pointer to the returned
+*        Mapping.  The forward transformation of this Mapping will
+*        describe how to convert coordinates from the coordinate
+*        system described by the target SpecFluxFrame to that described by
+*        the result Frame. The inverse transformation will convert in
+*        the opposite direction.
+*     result
+*        Address of a location to receive a pointer to the result Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if coordinate conversion is
+*     possible between the target and the result Frame. Otherwise zero
+*     is returned and *map and *result are returned as NULL (but this
+*     will not in itself result in an error condition). In general,
+*     coordinate conversion should always be possible if no template
+*     Frame is supplied but may not always be possible otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+
+*  Implementation Deficiencies:
+*     - It is not clear that the method of handling "extra" axes is
+*     the best one, nor is the method of setting the "following" flag
+*     necessarily correct.  However, it is also not obvious that this
+*     feature will ever be needed, so improvements have been left
+*     until the requirement is clearer.
+*/
+
+/* Local Variables: */
+   AstMapping *tmpmap;           /* Temporary Mapping pointer */
+   AstPermMap *permmap;          /* Pointer to PermMap */
+   AstSpecFluxFrame *target;     /* Pointer to target SpecFluxFrame structure */
+   int match;                    /* Coordinate conversion is possible? */
+   int perm[ 2 ];                /* Permutation array for axis swap */
+   int result_swap;              /* Swap result SpecFluxFrame coordinates? */
+   int target_swap;              /* Swap target SpecFluxFrame coordinates? */
+
+/* Initialise the returned values. */
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* If the template is not a SpecFluxFrame we use the parent SubFrame
+   method inherited form the CmpFrame class. */
+   if( !template || !astIsASpecFluxFrame( template ) || result_naxes != 2 ) {
+      match = (*parent_subframe)( target_frame, template, result_naxes,
+                                  target_axes, template_axes, map, result, status );
+
+/* Otherwise... */
+   } else {
+
+/* Obtain a pointer to the target SpecFluxFrame structure. */
+      target = (AstSpecFluxFrame *) target_frame;
+
+/* Form the result from a copy of the target and then permute its axes
+   into the order required. */
+      *result = astCopy( target );
+      astPermAxes( *result, target_axes );
+
+/* Overlay the template attributes on to the result SpecFrame. */
+      astOverlay( template, template_axes, *result );
+
+/* Generate a Mapping that takes account of changes in the coordinate
+   system (system, units, etc.) between the target SpecFluxFrame and the
+   result SpecFluxFrame. If this Mapping can be generated, set "match" to
+   indicate that coordinate conversion is possible. */
+      match = MakeSFMapping( target, (AstSpecFluxFrame *) *result, map, status );
+
+/* If a Mapping has been obtained, it will expect coordinate values to be
+   supplied in (flux,spec) pairs. Test whether we need to swap the
+   order of the target SpecFluxFrame coordinates to conform with this. */
+      if ( astOK && match ) {
+         target_swap = ( astValidateAxis( target, 0, 1, "astSubFrame" ) != 0 );
+
+/* Coordinates will also be delivered in (flux,spec) pairs, so check
+   to see whether the result SpecFluxFrame coordinate order should be
+   swapped. */
+         result_swap = ( target_swap != ( target_axes[ 0 ] != 0 ) );
+
+/* If either set of coordinates needs swapping, create a PermMap that
+   will swap a pair of coordinates. */
+         permmap = NULL;
+         if ( target_swap || result_swap ) {
+            perm[ 0 ] = 1;
+            perm[ 1 ] = 0;
+            permmap = astPermMap( 2, perm, 2, perm, NULL, "", status );
+         }
+
+/* If necessary, prefix this PermMap to the main Mapping. */
+         if ( target_swap ) {
+            tmpmap = (AstMapping *) astCmpMap( permmap, *map, 1, "", status );
+            *map = astAnnul( *map );
+            *map = tmpmap;
+         }
+
+/* Also, if necessary, append it to the main Mapping. */
+         if ( result_swap ) {
+            tmpmap = (AstMapping *) astCmpMap( *map, permmap, 1, "", status );
+            *map = astAnnul( *map );
+            *map = tmpmap;
+         }
+
+/* Annul the pointer to the PermMap (if created). */
+         if ( permmap ) permmap = astAnnul( permmap );
+      }
+   }
+
+/* If an error occurred, clean up by annulling the result pointers and
+   returning appropriate null values. */
+   if ( !astOK ) {
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   the axes of a SpecFluxFrame using the private macros defined for this
+   purpose at the start of this file. */
+
+/* Copy constructor. */
+/* ----------------- */
+
+/* Destructor. */
+/* ----------- */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for SpecFluxFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the SpecFluxFrame class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFluxFrame whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSpecFluxFrame *this;            /* Pointer to the SpecFluxFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SpecFluxFrame structure. */
+   this = (AstSpecFluxFrame *) this_object;
+
+/* Write out values representing the instance variables for the
+   SpecFluxFrame class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsASpecFluxFrame and astCheckSpecFluxFrame functions using
+   the macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(SpecFluxFrame,CmpFrame)
+astMAKE_CHECK(SpecFluxFrame)
+
+AstSpecFluxFrame *astSpecFluxFrame_( void *frame1_void, void *frame2_void,
+                                     const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astSpecFluxFrame
+f     AST_SPECFLUXFRAME
+
+*  Purpose:
+*     Create a SpecFluxFrame.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "specfluxframe.h"
+c     AstSpecFluxFrame *astSpecFluxFrame( AstSpecFrame *frame1, AstFluxFrame *frame2,
+c                                         const char *options, ... )
+f     RESULT = AST_SPECFLUXFRAME( FRAME1, FRAME2, OPTIONS, STATUS )
+
+*  Class Membership:
+*     SpecFluxFrame constructor.
+
+*  Description:
+*     This function creates a new SpecFluxFrame and optionally initialises
+*     its attributes.
+*
+*     A SpecFluxFrame combines a SpecFrame and a FluxFrame into a single
+*     2-dimensional compound Frame. Such a Frame can for instance be used
+*     to describe a Plot of a spectrum in which the first axis represents
+*     spectral position and the second axis represents flux.
+
+*  Parameters:
+c     frame1
+f     FRAME1 = INTEGER (Given)
+*        Pointer to the SpecFrame. This will form the first axis in the
+*        new SpecFluxFrame.
+c     frame2
+f     FRAME2 = INTEGER (Given)
+*        Pointer to the FluxFrame. This will form the second axis in the
+*        new SpecFluxFrame. The "SpecVal" attribute of this FluxFrame is
+*        not used by the SpecFluxFrame class and so may be set to AST__BAD
+*        when the FluxFrame is created.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new SpecFluxFrame. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new SpecFluxFrame. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSpecFluxFrame()
+f     AST_SPECFLUXFRAME = INTEGER
+*        A pointer to the new SpecFluxFrame.
+
+*  Notes:
+*     - The supplied Frame pointers are stored directly, rather than
+*     being used to create deep copies of the supplied Frames. This means
+*     that any subsequent changes made to the Frames via the supplied
+*     pointers will result in equivalent changes being visible in the
+*     SpecFluxFrame.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+
+*  Implementation Notes:
+*     - This function implements the basic SpecFluxFrame constructor which
+*     is available via the protected interface to the SpecFluxFrame class.
+*     A public interface is provided by the astSpecFluxFrameId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "frame1" and "frame2" parameters are of type (void *) and are
+*     converted and validated within the function itself.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSpecFluxFrame *new;        /* Pointer to new SpecFluxFrame */
+   AstFluxFrame *frame2;         /* Pointer to FluxFrame structure */
+   AstSpecFrame *frame1;         /* Pointer to SpecFrame structure */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   new = NULL;
+   if ( !astOK ) return new;
+
+/* Obtain and validate pointers to the Frame structures provided. */
+   frame1 = astCheckSpecFrame( frame1_void );
+   frame2 = astCheckFluxFrame( frame2_void );
+   if ( astOK ) {
+
+/* Initialise the SpecFluxFrame, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitSpecFluxFrame( NULL, sizeof( AstSpecFluxFrame ), !class_init,
+                                  &class_vtab, "SpecFluxFrame", frame1, frame2 );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   SpecFluxFrame's attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new SpecFluxFrame. */
+   return new;
+}
+
+AstSpecFluxFrame *astSpecFluxFrameId_( void *frame1_void, void *frame2_void,
+                                       const char *options, ... ) {
+/*
+*  Name:
+*     astSpecFluxFrameId_
+
+*  Purpose:
+*     Create a SpecFluxFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     AstSpecFluxFrame *astSpecFluxFrameId_( void *frame1_void, void *frame2_void,
+*                                            const char *options, ... )
+
+*  Class Membership:
+*     SpecFluxFrame constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astSpecFluxFrame constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astSpecFluxFrame_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur). For the same reason, the "frame1" and "frame2"
+*     parameters are of type (void *) and are converted and validated
+*     within the function itself.
+*
+*     The variable argument list also prevents this function from
+*     invoking astSpecFluxFrame_ directly, so it must be a
+*     re-implementation of it in all respects, except for the final
+*     conversion of the result to an ID value.
+
+*  Parameters:
+*     As for astSpecFluxFrame_.
+
+*  Returned Value:
+*     The ID value associated with the new SpecFluxFrame.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSpecFluxFrame *new;        /* Pointer to new SpecFluxFrame */
+   AstSpecFrame *frame1;         /* Pointer to first Frame structure */
+   AstFluxFrame *frame2;         /* Pointer to second Frame structure */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   new = NULL;
+   if ( !astOK ) return new;
+
+/* Obtain the Frame pointers from the ID's supplied and validate the
+   pointers to ensure they identify valid Frames. */
+   frame1 = astVerifySpecFrame( astMakePointer( frame1_void ) );
+   frame2 = astVerifyFluxFrame( astMakePointer( frame2_void ) );
+   if ( astOK ) {
+
+/* Initialise the SpecFluxFrame, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitSpecFluxFrame( NULL, sizeof( AstSpecFluxFrame ), !class_init,
+                                  &class_vtab, "SpecFluxFrame", frame1, frame2 );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   SpecFluxFrame's attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return an ID value for the new SpecFluxFrame. */
+   return astMakeId( new );
+}
+
+AstSpecFluxFrame *astInitSpecFluxFrame_( void *mem, size_t size, int init,
+                               AstSpecFluxFrameVtab *vtab, const char *name,
+                               AstSpecFrame *frame1, AstFluxFrame *frame2, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSpecFluxFrame
+
+*  Purpose:
+*     Initialise a SpecFluxFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     AstSpecFluxFrame *astInitSpecFluxFrame( void *mem, size_t size, int init,
+*                                 AstSpecFluxFrameVtab *vtab, const char *name,
+*                                 AstSpecFrame *frame1, AstFluxFrame *frame2 )
+
+*  Class Membership:
+*     SpecFluxFrame initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new SpecFluxFrame object. It allocates memory (if
+*     necessary) to accommodate the SpecFluxFrame plus any additional data
+*     associated with the derived class.  It then initialises a
+*     SpecFluxFrame structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual
+*     function table for a SpecFluxFrame at the start of the memory passed
+*     via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the SpecFluxFrame is to be
+*        created. This must be of sufficient size to accommodate the
+*        SpecFluxFrame data (sizeof(SpecFluxFrame)) plus any data used by the
+*        derived class. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the SpecFluxFrame (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the SpecFluxFrame structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*     init
+*        A logical flag indicating if the SpecFluxFrame's virtual function
+*        table is to be initialised. If this value is non-zero, the
+*        virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new SpecFluxFrame.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the Object astClass function).
+*     frame1
+*        Pointer to the SpecFrame
+*     frame2
+*        Pointer to the FluxFrame
+
+*  Returned Value:
+*     A pointer to the new SpecFluxFrame.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSpecFluxFrame *new;        /* Pointer to new SpecFluxFrame */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitSpecFluxFrameVtab( vtab, name );
+
+/* Initialise a Frame structure (the parent class) as the first
+   component within the SpecFluxFrame structure, allocating memory if
+   necessary. Set the number of Frame axes to zero, since all axis
+   information is stored within the component Frames. */
+   new = astInitCmpFrame( mem, size, 0, (AstCmpFrameVtab *) vtab, name,
+                          frame1, frame2 );
+   if ( astOK ) {
+
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstSpecFluxFrame *astLoadSpecFluxFrame_( void *mem, size_t size,
+                                 AstSpecFluxFrameVtab *vtab, const char *name,
+                                 AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadSpecFluxFrame
+
+*  Purpose:
+*     Load a SpecFluxFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specfluxframe.h"
+*     AstSpecFluxFrame *astLoadSpecFluxFrame( void *mem, size_t size,
+*                                   AstSpecFluxFrameVtab *vtab, const char *name,
+*                                   AstChannel *channel )
+
+*  Class Membership:
+*     SpecFluxFrame loader.
+
+*  Description:
+*     This function is provided to load a new SpecFluxFrame using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     SpecFluxFrame structure in this memory, using data read from the
+*     input Channel.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the SpecFluxFrame is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        SpecFluxFrame data (sizeof(SpecFluxFrame)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the SpecFluxFrame (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the SpecFluxFrame structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstSpecFluxFrame) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new SpecFluxFrame. If this is NULL, a pointer
+*        to the (static) virtual function table for the SpecFluxFrame class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "SpecFluxFrame" is used instead.
+
+*  Returned Value:
+*     A pointer to the new SpecFluxFrame.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstSpecFluxFrame *new;        /* Pointer to the new SpecFluxFrame */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this SpecFluxFrame. In this case the
+   SpecFluxFrame belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstSpecFluxFrame );
+      vtab = &class_vtab;
+      name = "SpecFluxFrame";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitSpecFluxFrameVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built SpecFluxFrame. */
+   new = astLoadCmpFrame( mem, size, (AstCmpFrameVtab *) vtab, name,
+                          channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "SpecFluxFrame" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+/*  (none)  */
+
+/* If an error occurred, clean up by deleting the new SpecFluxFrame. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new SpecFluxFrame pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
diff --git a/ast/specfluxframe.h b/ast/specfluxframe.h
new file mode 100644
index 0000000..ff8a5e1
--- /dev/null
+++ b/ast/specfluxframe.h
@@ -0,0 +1,215 @@
+#if !defined( SPECFLUXFRAME_INCLUDED ) /* Include this file only once */
+#define SPECFLUXFRAME_INCLUDED
+/*
+*+
+*  Name:
+*     specfluxframe.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the SpecFluxFrame class.
+
+*  Invocation:
+*     #include "specfluxframe.h"
+
+*  Description:
+*     This include file defines the interface to the SpecFluxFrame class
+*     and provides the type definitions, function prototypes and
+*     macros, etc. needed to use this class.
+
+*  Inheritance:
+*     The SpecFluxFrame class inherits from the Frame class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     8-DEC-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+#include "cmpframe.h"            /* Parent Frame class */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros. */
+/* ------- */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* SpecFluxFrame structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstSpecFluxFrame {
+
+/* Attributes inherited from the parent class. */
+   AstCmpFrame cmpframe;         /* Parent class structure */
+
+} AstSpecFluxFrame;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstSpecFluxFrameVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstCmpFrameVtab frame_vtab;   /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+
+} AstSpecFluxFrameVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstSpecFluxFrameGlobals {
+   AstSpecFluxFrameVtab Class_Vtab;
+   int Class_Init;
+   char GetTitle_Buff[ 201 ];
+} AstSpecFluxFrameGlobals;
+
+#endif
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(SpecFluxFrame)         /* Check class membership */
+astPROTO_ISA(SpecFluxFrame)           /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstSpecFluxFrame *astSpecFluxFrame_( void *, void *, const char *, int *, ...);
+#else
+AstSpecFluxFrame *astSpecFluxFrameId_( void *, void *, const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstSpecFluxFrame *astInitSpecFluxFrame_( void *, size_t, int, AstSpecFluxFrameVtab *,
+                               const char *, AstSpecFrame *, AstFluxFrame *, int * );
+
+/* Vtab initialiser. */
+void astInitSpecFluxFrameVtab_( AstSpecFluxFrameVtab *, const char *, int * );
+
+/* Loader. */
+AstSpecFluxFrame *astLoadSpecFluxFrame_( void *, size_t, AstSpecFluxFrameVtab *,
+                               const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitSpecFluxFrameGlobals_( AstSpecFluxFrameGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckSpecFluxFrame(this) astINVOKE_CHECK(SpecFluxFrame,this,0)
+#define astVerifySpecFluxFrame(this) astINVOKE_CHECK(SpecFluxFrame,this,1)
+
+/* Test class membership. */
+#define astIsASpecFluxFrame(this) astINVOKE_ISA(SpecFluxFrame,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astSpecFluxFrame astINVOKE(F,astSpecFluxFrame_)
+#else
+#define astSpecFluxFrame astINVOKE(F,astSpecFluxFrameId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitSpecFluxFrame(mem,size,init,vtab,name,frame1,frame2) \
+astINVOKE(O,astInitSpecFluxFrame_(mem,size,init,vtab,name,astCheckSpecFrame(frame1),astCheckFluxFrame(frame2),STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitSpecFluxFrameVtab(vtab,name) astINVOKE(V,astInitSpecFluxFrameVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadSpecFluxFrame(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadSpecFluxFrame_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckSpecFluxFrame to validate SpecFluxFrame pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#endif
+
+
+
+
+
diff --git a/ast/specframe.c b/ast/specframe.c
new file mode 100644
index 0000000..eef06ba
--- /dev/null
+++ b/ast/specframe.c
@@ -0,0 +1,7437 @@
+/*
+*class++
+*  Name:
+*     SpecFrame
+
+*  Purpose:
+*     Spectral coordinate system description.
+
+*  Constructor Function:
+c     astSpecFrame
+f     AST_SPECFRAME
+
+*  Description:
+*     A SpecFrame is a specialised form of one-dimensional Frame which
+*     represents various coordinate systems used to describe positions within
+*     an electro-magnetic spectrum. The particular coordinate system to be
+*     used is specified by setting the SpecFrame's System attribute (the
+*     default is wavelength) qualified, as necessary, by other attributes
+*     such as the rest frequency, the standard of rest, the epoch of
+*     observation, units, etc (see the description of the System attribute
+*     for details).
+*
+*     By setting a value for thr SpecOrigin attribute, a SpecFrame can be made
+*     to represent offsets from a given spectral position, rather than absolute
+*     spectral values.
+
+*  Inheritance:
+*     The SpecFrame class inherits from the Frame class.
+
+*  Attributes:
+*     In addition to those attributes common to all Frames, every
+*     SpecFrame also has the following attributes:
+*
+*     - AlignSpecOffset: Align SpecFrames using the offset coordinate system?
+*     - AlignStdOfRest: Standard of rest in which to align SpecFrames
+*     - RefDec: Declination of the source (FK5 J2000)
+*     - RefRA: Right ascension of the source (FK5 J2000)
+*     - RestFreq: Rest frequency
+*     - SourceSys: Source velocity spectral system
+*     - SourceVel: Source velocity
+*     - SourceVRF: Source velocity rest frame
+*     - SpecOrigin: The zero point for SpecFrame axis values
+*     - StdOfRest: Standard of rest
+*
+*     Several of the Frame attributes inherited by the SpecFrame class
+*     refer to a specific axis of the Frame (for instance Unit(axis),
+*     Label(axis), etc). Since a SpecFrame is strictly one-dimensional,
+*     it allows these attributes to be specified without an axis index.
+*     So for instance, "Unit" is allowed in place of "Unit(1)".
+
+*  Functions:
+c     In addition to those functions applicable to all Frames, the
+c     following functions may also be applied to all SpecFrames:
+f     In addition to those routines applicable to all Frames, the
+f     following routines may also be applied to all SpecFrames:
+*
+c     - astSetRefPos: Set reference position in any celestial system
+f     - AST_SETREFPOS: Set reference position in any celestial system
+c     - astGetRefPos: Get reference position in any celestial system
+f     - AST_GETREFPOS: Get reference position in any celestial system
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     4-NOV-2002 (DSB):
+*        Original version.
+*     2-FEB-2005 (DSB):
+*        - Avoid using astStore to allocate more storage than is supplied
+*        in the "data" pointer. This can cause access violations since
+*        astStore will then read beyond the end of the "data" area.
+*     22-MAR-2005 (DSB):
+*        - Re-structure MakeSpecMapping in order to avoid unnecessary
+*        access to SpecFrame attributes which may not be set, and to
+*        check that all required attributes have been set if UseDefs is
+*        zero.
+*     23-MAR-2005 (DSB):
+*        - Added missing rest frames to SorEqual.
+*     12-AUG-2005 (DSB):
+*        - Remove GeoLon and GeoLat attributes. Use the new ObsLon and
+*        ObsLat attributes in the parent Frame class instead. Note, for
+*        backward compatibility the public attribute accessors and the
+*        astLoadSpecFrame functions still recogonise GeoLon and GeoLat,
+*        but use the ObsLat/ObsLon attributes internally.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     1-MAR-2006 (DSB):
+*        Replace astSetPermMap within DEBUG blocks by astBeginPM/astEndPM.
+*     6-OCT-2006 (DSB):
+*        Guard against annulling null pointers in subFrame.
+*     18-OCT-2006 (DSB):
+*        Added SpecOrigin and AlignSpecOffset attributes.
+*     23-OCT-2006 (DSB):
+*        Fix memory leak caused by addition of SpecOrigin and AlignSpecOffset
+*        attributes.
+*     15-NOV-2006 (DSB):
+*        Only write out SpecOrigin if it is not bad.
+*     8-JAN-2006 (DSB):
+*        - SubFrame: Copy the SourceSystem and SourceStdOfRest attributes
+*        to the System and StdOfRest attributes of the "align_frm"
+*        SpecFrame before calling MakeSpecMapping. Previously, the
+*        values assigned to  SourceSystem and SourceStdOfRest were
+*        ignored, and alignment was always performed in the templates System
+*        and StdOfRest.
+*        - MakeSpecMapping: Correct logic used to decide if steps 2 and 7
+*        can be cancelled.
+*        - OriginSystem: Clear the AlignSpecOffset attributes before
+*        finding the Mapping between the old and new Systems.
+*     16-JAN-2006 (DSB):
+*        Fix bug in Dump that caused SrcVRF not to be written out.
+*     31-JAN-2007 (DSB):
+*        Modified so that a SpecFrame can be used as a template to find a
+*        SpecFrame contained within a CmpFrame. This involves changes in
+*        Match and the removal of the local versions of SetMaxAxes and
+*        SetMinAxes.
+*     8-AUG-2007 (DSB):
+*        Changed Overlay to avoid the possibility of making permanent
+*        changes to the supplied template Frame.
+*     3-SEP-2007 (DSB):
+*        In SubFrame, since AlignSystem is extended by the SpecFrame class
+*        it needs to be cleared before invoking the parent SubFrame
+*        method in cases where the result Frame is not a SkyFrame.
+*     2-OCT-2007 (DSB):
+*        In Overlay, clear AlignSystem as well as System before calling
+*        the parent overlay method.
+*     4-SEP-2009 (DSB):
+*        In MakeSpecMapping, in order to produce alignment that is not
+*        affected by the epoch or reference position, make the alignment
+*        frame adapt to the epoch and reference position of the target
+*        and result Frames.
+*     14-SEP-2009 (DSB):
+*        In MakeSpecMapping, extend the 4-SEP-2009 fix to cover other
+*        attributes that define the available rest frames (e.g.
+*        SourceVRF, SourceVel, ObsLat, ObsLon, ObsAlt).
+*     16-SEP-2009 (DSB):
+*        In MakeSpecMapping, retain the original alignment frame attribute
+*        values if we are restoring the integrity of a FrameSet.
+*     29-APR-2011 (DSB):
+*        Prevent astFindFrame from matching a subclass template against a
+*        superclass target.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS SpecFrame
+
+/* Define the first and last acceptable System values. */
+#define FIRST_SYSTEM AST__FREQ
+#define LAST_SYSTEM AST__VREL
+
+/* Define the first and last acceptable StdOfRest values. */
+#define FIRST_SOR AST__TPSOR
+#define LAST_SOR AST__SCSOR
+
+/* The supported spectral coordinate systems fall into two groups;
+   "relative", and "absolute". The relative systems define each axis
+   value with respect to the rest frequency, whereas the absolute systems
+   have axis values which do not depend on the rest frequency. Define a
+   macro which returns one if the specified system is absolute, and zero
+   otherwise. */
+#define ABS_SYSTEM(sys) \
+      ( ( sys == AST__ENERGY || \
+          sys == AST__WAVENUM || \
+          sys == AST__WAVELEN || \
+          sys == AST__AIRWAVE || \
+          sys == AST__FREQ ) ? 1 : 0 )
+
+/* Define other numerical constants for use in this module. */
+#define GETATTRIB_BUFF_LEN 50
+#define GETLABEL_BUFF_LEN 200
+#define GETSYMBOL_BUFF_LEN 20
+#define GETTITLE_BUFF_LEN 200
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "unit.h"                /* Units management facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "specmap.h"             /* Spectral coordinate Mappings */
+#include "frame.h"               /* Parent Frame class */
+#include "skyframe.h"            /* Celestial coordinate frames */
+#include "specframe.h"           /* Interface definition for this class */
+#include "mapping.h"             /* Coordinate Mappings */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "pal.h"                 /* SlaLib interface */
+#include "shiftmap.h"            /* Change of origin */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <math.h>
+#include <limits.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are used or extended by this
+   class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstSystemType (* parent_getalignsystem)( AstFrame *, int * );
+static AstSystemType (* parent_getsystem)( AstFrame *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static const char *(* parent_getdomain)( AstFrame *, int * );
+static const char *(* parent_getlabel)( AstFrame *, int, int * );
+static const char *(* parent_getsymbol)( AstFrame *, int, int * );
+static const char *(* parent_gettitle)( AstFrame *, int * );
+static const char *(* parent_getunit)( AstFrame *, int, int * );
+static int (* parent_match)( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int (* parent_subframe)( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_setunit)( AstFrame *, int, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_overlay)( AstFrame *, const int *, AstFrame *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static void (* parent_setsystem)( AstFrame *, AstSystemType, int * );
+static void (* parent_clearsystem)( AstFrame *, int * );
+static void (* parent_clearunit)( AstFrame *, int, int * );
+
+/* Define a variable to hold a SkyFrame which will be used for formatting
+   and unformatting sky positions, etc. */
+static AstSkyFrame *skyframe;
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->GetLabel_Buff[ 0 ] = 0; \
+   globals->GetSymbol_Buff[ 0 ] = 0; \
+   globals->GetTitle_Buff[ 0 ] = 0; \
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(SpecFrame)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(SpecFrame,Class_Init)
+#define class_vtab astGLOBAL(SpecFrame,Class_Vtab)
+#define getattrib_buff astGLOBAL(SpecFrame,GetAttrib_Buff)
+#define getlabel_buff astGLOBAL(SpecFrame,GetLabel_Buff)
+#define getsymbol_buff astGLOBAL(SpecFrame,GetSymbol_Buff)
+#define gettitle_buff astGLOBAL(SpecFrame,GetTitle_Buff)
+
+
+
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Buffer returned by GetAttrib. */
+static char getattrib_buff[ 51 ];
+
+/* Default GetLabel string buffer */
+static char getlabel_buff[ 201 ];
+
+/* Default GetSymbol buffer */
+static char getsymbol_buff[ 21 ];
+
+/* Default Title string buffer */
+static char gettitle_buff[ 201 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstSpecFrameVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+
+#endif
+
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstStdOfRestType StdOfRestCode( const char *, int * );
+static int GetObjSize( AstObject *, int * );
+static AstSystemType GetAlignSystem( AstFrame *, int * );
+static AstSystemType SystemCode( AstFrame *, const char *, int * );
+static AstSystemType ValidateSystem( AstFrame *, AstSystemType, const char *, int * );
+static const char *DefUnit( AstSystemType, const char *, const char *, int * );
+static const char *GetDomain( AstFrame *, int * );
+static const char *GetLabel( AstFrame *, int, int * );
+static const char *GetSymbol( AstFrame *, int, int * );
+static const char *GetTitle( AstFrame *, int * );
+static const char *GetUnit( AstFrame *, int, int * );
+static const char *SpecMapUnit( AstSystemType, const char *, const char *, int * );
+static const char *StdOfRestString( AstStdOfRestType, int * );
+static const char *SystemLabel( AstSystemType, int * );
+static const char *SystemString( AstFrame *, AstSystemType, int * );
+static double ConvertSourceVel( AstSpecFrame *, AstStdOfRestType, AstSystemType, int * );
+static int EqualSor( AstSpecFrame *, AstSpecFrame *, int * );
+static int GetActiveUnit( AstFrame *, int * );
+static int MakeSpecMapping( AstSpecFrame *, AstSpecFrame *, AstSpecFrame *, int, AstMapping **, int * );
+static int Match( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int SorConvert( AstSpecFrame *, AstSpecFrame *, AstSpecMap *, int * );
+static int SubFrame( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int TestActiveUnit( AstFrame *, int * );
+static void ClearUnit( AstFrame *, int, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void GetRefPos( AstSpecFrame *, AstSkyFrame *, double *, double *, int * );
+static void Overlay( AstFrame *, const int *, AstFrame *, int * );
+static void SetRefPos( AstSpecFrame *, AstSkyFrame *, double, double, int * );
+static void SetUnit( AstFrame *, int, const char *, int * );
+static void VerifyAttrs( AstSpecFrame *, const char *, const char *, const char *, int * );
+static double ToUnits( AstSpecFrame *, const char *, double, const char *, int * );
+static void OriginStdOfRest( AstSpecFrame *, AstStdOfRestType, const char *, int * );
+static void OriginSystem( AstSpecFrame *, AstSystemType, const char *, int * );
+
+static AstSystemType GetSystem( AstFrame *, int * );
+static void SetSystem( AstFrame *, AstSystemType, int * );
+static void ClearSystem( AstFrame *, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+static AstStdOfRestType GetAlignStdOfRest( AstSpecFrame *, int * );
+static int TestAlignStdOfRest( AstSpecFrame *, int * );
+static void ClearAlignStdOfRest( AstSpecFrame *, int * );
+static void SetAlignStdOfRest( AstSpecFrame *, AstStdOfRestType, int * );
+
+static AstStdOfRestType GetStdOfRest( AstSpecFrame *, int * );
+static int TestStdOfRest( AstSpecFrame *, int * );
+static void ClearStdOfRest( AstSpecFrame *, int * );
+static void SetStdOfRest( AstSpecFrame *, AstStdOfRestType, int * );
+
+static double GetRestFreq( AstSpecFrame *, int * );
+static int TestRestFreq( AstSpecFrame *, int * );
+static void ClearRestFreq( AstSpecFrame *, int * );
+static void SetRestFreq( AstSpecFrame *, double, int * );
+
+static double GetSourceVel( AstSpecFrame *, int * );
+static int TestSourceVel( AstSpecFrame *, int * );
+static void ClearSourceVel( AstSpecFrame *, int * );
+static void SetSourceVel( AstSpecFrame *, double, int * );
+
+static double GetRefRA( AstSpecFrame *, int * );
+static int TestRefRA( AstSpecFrame *, int * );
+static void ClearRefRA( AstSpecFrame *, int * );
+static void SetRefRA( AstSpecFrame *, double, int * );
+
+static double GetRefDec( AstSpecFrame *, int * );
+static int TestRefDec( AstSpecFrame *, int * );
+static void ClearRefDec( AstSpecFrame *, int * );
+static void SetRefDec( AstSpecFrame *, double, int * );
+
+static AstStdOfRestType GetSourceVRF( AstSpecFrame *, int * );
+static int TestSourceVRF( AstSpecFrame *, int * );
+static void ClearSourceVRF( AstSpecFrame *, int * );
+static void SetSourceVRF( AstSpecFrame *, AstStdOfRestType, int * );
+
+static AstSystemType GetSourceSys( AstSpecFrame *, int * );
+static int TestSourceSys( AstSpecFrame *, int * );
+static void ClearSourceSys( AstSpecFrame *, int * );
+static void SetSourceSys( AstSpecFrame *, AstSystemType, int * );
+
+static double GetSpecOrigin( AstSpecFrame *, int * );
+static int TestSpecOrigin( AstSpecFrame *, int * );
+static void ClearSpecOrigin( AstSpecFrame *, int * );
+static void SetSpecOrigin( AstSpecFrame *, double, int * );
+static double GetSpecOriginCur( AstSpecFrame *, int * );
+
+static int GetAlignSpecOffset( AstSpecFrame *, int * );
+static int TestAlignSpecOffset( AstSpecFrame *, int * );
+static void SetAlignSpecOffset( AstSpecFrame *, int, int * );
+static void ClearAlignSpecOffset( AstSpecFrame *, int * );
+
+/* Member functions. */
+/* ================= */
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astClearAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     SpecFrame, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to the SpecFrame structure */
+   char *new_attrib;             /* Pointer value to new attribute name */
+   int len;                      /* Length of attrib string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_object;
+
+/* Obtain the length of the "attrib" string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* First look for axis attributes defined by the Frame class. Since a
+   SpecFrame has only 1 axis, we allow these attributes to be specified
+   without a trailing "(axis)" string. */
+   if ( !strcmp( attrib, "direction" ) ||
+        !strcmp( attrib, "bottom" ) ||
+        !strcmp( attrib, "top" ) ||
+        !strcmp( attrib, "format" ) ||
+        !strcmp( attrib, "label" ) ||
+        !strcmp( attrib, "symbol" ) ||
+        !strcmp( attrib, "unit" ) ) {
+
+/* Create a new attribute name from the original by appending the string
+   "(1)" and then use the parent ClearAttrib method. */
+      new_attrib = astMalloc( len + 4 );
+      if( new_attrib ) {
+         memcpy( new_attrib, attrib, len );
+         memcpy( new_attrib + len, "(1)", 4 );
+         (*parent_clearattrib)( this_object, new_attrib, status );
+         new_attrib = astFree( new_attrib );
+      }
+
+/* AlignStdOfRest. */
+/* --------------- */
+   } else if ( !strcmp( attrib, "alignstdofrest" ) ) {
+      astClearAlignStdOfRest( this );
+
+/* GeoLat. */
+/* ------- */
+/* Retained for backward compatibility with older versions of AST in which
+   SpecFrame had GeoLon/Lat attributes (now ObsLon/Lat are used instead). */
+   } else if ( !strcmp( attrib, "geolat" ) ) {
+      astClearAttrib( this, "obslat" );
+
+/* GeoLon. */
+/* ------- */
+   } else if ( !strcmp( attrib, "geolon" ) ) {
+      astClearAttrib( this, "obslon" );
+
+/* RefDec. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "refdec" ) ) {
+      astClearRefDec( this );
+
+/* RefRA. */
+/* --------- */
+   } else if ( !strcmp( attrib, "refra" ) ) {
+      astClearRefRA( this );
+
+/* RestFreq. */
+/* --------- */
+   } else if ( !strcmp( attrib, "restfreq" ) ) {
+      astClearRestFreq( this );
+
+/* SourceVel. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "sourcevel" ) ) {
+      astClearSourceVel( this );
+
+/* SpecOrigin. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "specorigin" ) ) {
+      astClearSpecOrigin( this );
+
+/* AlignSpecOffset. */
+/* ---------------- */
+   } else if ( !strcmp( attrib, "alignspecoffset" ) ) {
+      astClearAlignSpecOffset( this );
+
+/* SourceVRF */
+/* --------- */
+   } else if ( !strcmp( attrib, "sourcevrf" ) ) {
+      astClearSourceVRF( this );
+
+/* SourceSys */
+/* --------- */
+   } else if ( !strcmp( attrib, "sourcesys" ) ) {
+      astClearSourceSys( this );
+
+/* StdOfRest. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "stdofrest" ) ) {
+      astClearStdOfRest( this );
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     ClearSystem
+
+*  Purpose:
+*     Clear the System attribute for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void ClearSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astClearSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the System attribute for a SpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to SpecFrame structure */
+   AstSystemType newsys;         /* System after clearing */
+   AstSystemType oldsys;         /* System before clearing */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_frame;
+
+/* Save the original system */
+   oldsys = astGetSystem( this_frame );
+
+/* Use the parent ClearSystem method to clear the System value. */
+   (*parent_clearsystem)( this_frame, status );
+
+/* Get the default System. */
+   newsys = astGetSystem( this_frame );
+
+/* If the system has actually changed. */
+   if( newsys != oldsys ) {
+
+/* Changing the System value will in general require the Units to change
+   as well. If the used has previously specified the units to be used with
+   the new system, then re-instate them (they are stored in the "usedunits"
+   array in the SpecFrame structure). Otherwise, clear the units so that
+   the default units will eb used with the new System. */
+      if( (int) newsys < this->nuunits && this->usedunits &&
+          this->usedunits[ (int) newsys ] ) {
+         astSetUnit( this, 0, this->usedunits[ (int) newsys ] );
+      } else {
+         astClearUnit( this, 0 );
+      }
+
+/* Also, clear all attributes which have system-specific defaults. */
+      astClearLabel( this_frame, 0 );
+      astClearSymbol( this_frame, 0 );
+      astClearTitle( this_frame );
+
+/* Modify the SpecOrigin value to use the new System */
+      OriginSystem( this, oldsys, "astClearSystem", status );
+
+   }
+
+}
+
+static void ClearStdOfRest( AstSpecFrame *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearStdOfRest
+
+*  Purpose:
+*     Clear the StdOfRest attribute for a SpecFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void astClearStdOfRest( AstSpecFrame *this )
+
+*  Class Membership:
+*     SpecFrame virtual function
+
+*  Description:
+*     This function clears the StdOfRest attribute for a SpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Modify the SpecOrigin value stored in the SpecFrame structure to refer to the
+   default rest frame (heliocentric). */
+   OriginStdOfRest( this, AST__HLSOR, "astClearStdOfRest", status );
+
+/* Store a bad value for the standard of rest in the SpecFrame structure. */
+   this->stdofrest = AST__BADSOR;
+}
+
+
+static void ClearUnit( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     ClearUnit
+
+*  Purpose:
+*     Clear the value of the Unit string for a SpecFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void ClearUnit( AstFrame *this_frame, int axis )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astClearUnit method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function clears the Unit string for a specified axis of a
+*     SpecFrame. It also clears the UsedUnit item in the SpecFrame
+*     structure corresponding to the current System.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     axis
+*        The number of the axis (zero-based).
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to the SpecFrame structure */
+   int system;                   /* The SpecFrame's System value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_frame;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astClearUnit" );
+
+/* Clear the UsedUnit item for the current System, if current set. */
+   system = (int) astGetSystem( this );
+   if( system < this->nuunits && this->usedunits ) {
+      this->usedunits[ system ] = astFree( this->usedunits[ system ] );
+   }
+
+/* Use the parent method to clear the Unit attribute of the axis. */
+   (*parent_clearunit)( this_frame, axis, status );
+}
+
+static double ConvertSourceVel( AstSpecFrame *this, AstStdOfRestType newsor,
+                                AstSystemType newsys, int *status ) {
+/*
+*  Name:
+*     ConvertSourceVel
+
+*  Purpose:
+*     Convert the SourceVel value to a specified rest frame and spectral
+*     system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     double ConvertSourceVel( AstSpecFrame *this, AstStdOfRestType newsor,
+*                              AstSystemType newsys, int *status )
+
+*  Class Membership:
+*     SpecFrame member function
+
+*  Description:
+*     This function convert the SourceVel value to a specified rest frame
+*     and spectral system, and returns the new value.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     newsor
+*        The rest frame in which the source velocity is required.
+*     newsys
+*        The spectral system (AST__VREL or AST__REDSHIFT) in which the
+*        source velocity is required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The converted source velocity (m/s), or redshift.
+
+*  Notes:
+*     - This function returns zero if an error occurs.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *from;     /* Pointer to a source SpecFrame */
+   AstSpecFrame *to;       /* Pointer to a destination SpecFrame */
+   AstSpecMap *specmap;    /* Pointer to a SpecMap */
+   AstStdOfRestType sor;   /* Standard of rest in which SourceVel is defined */
+   AstSystemType sys;      /* Spectral system in which SourceVel is defined */
+   double ret;             /* The returned value */
+   double rf;              /* Rest frequency (Hz) */
+   double temp;            /* Temporary storage */
+
+/* Initialise */
+   ret = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Get the value of the SourceVel attribute. This will be a velocity in m/s
+   (relativistic, radio or optical), or unitless redshift or beta factor,
+   depending on the current value of SourceSys. */
+   ret = astGetSourceVel( this );
+
+/* Check it can be used (depends on whether a value has been set and
+   whether the UseDefs attribute is zero). */
+   VerifyAttrs( this, "convert source velocity to a new standard of rest",
+                "SourceVel", "astMatch", status );
+
+/* Get the rest frame and spectral system to which value refers. */
+   sor = astGetSourceVRF( this );
+   sys = astGetSourceSys( this );
+
+/* If necessary, convert to the requested rest frame and spectral system. */
+   if( sor != newsor || sys != newsys ) {
+
+/* Verify that usable value is available for the RestFreq attribute. An
+   error is reported if not. */
+      VerifyAttrs( this, "convert source velocity to a new standard of rest",
+                   "RestFreq", "astMatch", status );
+
+/* Take two copies of the supplied SpecFrame and set their StdOfRest
+   attributes to the required values. */
+      from = astCopy( this );
+      astSetStdOfRest( from, sor );
+
+      to = astCopy( this );
+      astSetStdOfRest( to, newsor );
+
+/* Initialise a new SpecMap to describe the conversion. The new SpecMap
+   initially represents a UnitMap. */
+      specmap = astSpecMap( 1, 0, "", status );
+
+/* Add a conversion from the spectral system in which the SourceVEl value
+   is stored, to relativistic velocity. */
+      if( sys == AST__VRADIO ) {
+         astSpecAdd( specmap, "VRTOVL", 0, NULL );
+
+      } else if( sys == AST__VOPTICAL ) {
+         astSpecAdd( specmap, "VOTOVL", 0, NULL );
+
+      } else if( sys == AST__REDSHIFT ) {
+         astSpecAdd( specmap, "ZOTOVL", 0, NULL );
+
+      } else if( sys == AST__BETA ) {
+         astSpecAdd( specmap, "BTTOVL", 0, NULL );
+      }
+
+/* Add a conversion from velocity to frequency since SorConvert converts
+   frequencies. */
+      rf = astGetRestFreq( this );
+      astSpecAdd( specmap, "VLTOFR", 1, &rf );
+
+/* Now add a conversion from frequency in the SourveVRF standard of rest to
+   frequency in the required rest frame. */
+      SorConvert( from, to, specmap, status );
+
+/* Add a conversion from frequency back to velocity. Note, the value of the
+   rest frequency does not affect the overall conversion. */
+      astSpecAdd( specmap, "FRTOVL", 1, &rf );
+
+/* Add a conversion from relativistic velocity to the required spectral
+   system, if needed. */
+      if( newsys == AST__VRADIO ) {
+         astSpecAdd( specmap, "VLTOVR", 0, NULL );
+
+      } else if( newsys == AST__VOPTICAL ) {
+         astSpecAdd( specmap, "VLTOVO",0, NULL );
+
+      } else if( newsys == AST__REDSHIFT ) {
+         astSpecAdd( specmap, "VLTOZO",0, NULL );
+
+      } else if( newsys == AST__BETA ) {
+         astSpecAdd( specmap, "VLTOBT",0, NULL );
+      }
+
+/* Use the SpecMap to convert the source velocity in the SourceVRF
+   standard of rest and SourceSys spectral system to the required rest
+   frame and spectral system. */
+      temp = ret;
+      astTran1( specmap, 1, &temp, 1, &ret );
+
+/* Free resources */
+      specmap = astAnnul( specmap );
+      to = astAnnul( to );
+      from = astAnnul( from );
+   }
+
+/* Return zero if an error has occurred. */
+   if( !astOK ) ret = 0.0;
+
+/* Return the answer. */
+   return ret;
+
+}
+
+static const char *DefUnit( AstSystemType system, const char *method,
+                            const char *class, int *status ){
+/*
+*  Name:
+*     DefUnit
+
+*  Purpose:
+*     Return the default units for a spectral coordinate system type.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *DefUnit( AstSystemType system, const char *method,
+*                          const char *class, int *status )
+
+*  Class Membership:
+*     SpecFrame member function.
+
+*  Description:
+*     This function returns a textual representation of the default
+*     units associated with the specified spectral coordinate system.
+
+*  Parameters:
+*     system
+*        The spectral coordinate system.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     As tring describing the default units. This string follows the
+*     units syntax described in FITS WCS paper I "Representations of world
+*     coordinates in FITS" (Greisen & Calabretta).
+
+*  Notes:
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Value to return */
+
+/* Initialize */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get an identifier for the default units. */
+   if( system == AST__FREQ ) {
+      result = "GHz";
+   } else if( system == AST__ENERGY ) {
+      result = "J";
+   } else if( system == AST__WAVENUM ) {
+      result = "1/m";
+   } else if( system == AST__WAVELEN ) {
+      result = "Angstrom";
+   } else if( system == AST__AIRWAVE ) {
+      result = "Angstrom";
+   } else if( system == AST__VRADIO ) {
+      result = "km/s";
+   } else if( system == AST__VOPTICAL ) {
+      result = "km/s";
+   } else if( system == AST__REDSHIFT ) {
+      result = "";
+   } else if( system == AST__BETA ) {
+      result = "";
+   } else if( system == AST__VREL ) {
+      result = "km/s";
+
+/* Report an error if the coordinate system was not recognised. */
+   } else {
+      astError( AST__SCSIN, "%s(%s): Corrupt %s contains illegal System "
+                "identification code (%d).", status, method, class, class,
+                (int) system );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int EqualSor( AstSpecFrame *this, AstSpecFrame *that, int *status ) {
+/*
+*  Name:
+*     EqualSor
+
+*  Purpose:
+*     Do two SpecFrames use the same standard of rest?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     int EqualSor( AstSpecFrame *this, AstSpecFrame *that, int *status )
+
+*  Class Membership:
+*     SpecFrame member function
+
+*  Description:
+*    This function returns non-zero if the two supplied SpecFrames use
+*    the same standard of rest.
+
+*  Parameters:
+*     this
+*        Pointer to the first SpecFrame.
+*     that
+*        Pointer to the second SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the two SpecFrames use the same standard of rest. Zero
+*     otherwise.
+
+*/
+
+/* Local Variables: */
+   AstStdOfRestType sor;             /* Standard of rest */
+   int result;                       /* Value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise. */
+   result = 1;
+
+/* Compare StdOfRest attributes. */
+   sor = astGetStdOfRest( this );
+   if( astGetStdOfRest( that ) != sor ) {
+      result = 0;
+
+/* If the standards of rest are equal we need to check the the attributes
+   which specify the precise rest frame. */
+   } else {
+
+/* The reference RA and Dec need to be equal */
+      if( !astEQUAL( astGetRefRA( this ), astGetRefRA( that ) ) ||
+          !astEQUAL( astGetRefDec( this ), astGetRefDec( that ) ) ) {
+         result = 0;
+
+/* For source rest frame, the source velocities, rest frames and systems must
+   be equal */
+      } else if( sor == AST__SCSOR ){
+         if( !astEQUAL( astGetSourceVel( this ), astGetSourceVel( that ) ) ||
+                     astGetSourceVRF( this ) != astGetSourceVRF( that ) ||
+                     astGetSourceSys( this ) != astGetSourceSys( that ) ) {
+            result = 0;
+         }
+
+/* For geocentric, barycentric and heliocentric rest frames, the epochs must
+   be the same */
+      } else if( sor == AST__GESOR || sor == AST__BYSOR || sor == AST__HLSOR ){
+         if( !astEQUAL( astGetEpoch( this ), astGetEpoch( that ) ) ) result = 0;
+
+/* For topocentric rest frame, the epoch and position of the observer must be
+   the same */
+      } else if( sor == AST__TPSOR ){
+         if( !astEQUAL( astGetEpoch( this ), astGetEpoch( that ) ) ||
+             !astEQUAL( astGetObsAlt( this ), astGetObsAlt( that ) ) ||
+             !astEQUAL( astGetObsLon( this ), astGetObsLon( that ) ) ||
+             !astEQUAL( astGetObsLat( this ), astGetObsLat( that ) ) ) result = 0;
+
+      } else if( sor != AST__LKSOR && sor != AST__LDSOR &&
+                 sor != AST__GLSOR && sor != AST__LGSOR && astOK ) {
+         astError( AST__INTER, "SorEqual(SpecFrame): Function SorEqual "
+                   "does not yet support rest frame %d (AST internal "
+                   "programming error)", status, sor );
+      }
+   }
+
+/* Return the result */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied SpecFrame,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;         /* Pointer to SpecFrame structure */
+   int result;                /* Result value to return */
+   int i;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   if( this->usedunits ) {
+      for( i = 0; i < this->nuunits; i++ ) {
+         result += astTSizeOf( this->usedunits[ i ] );
+      }
+      result += astTSizeOf( this->usedunits );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetActiveUnit( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetActiveUnit
+
+*  Purpose:
+*     Obtain the value of the ActiveUnit flag for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     int GetActiveUnit( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astGetActiveUnit protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function returns the value of the ActiveUnit flag for a
+*    SpecFrame, which is always 1.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The value to use for the ActiveUnit flag (1).
+
+*/
+   return 1;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the protected astGetAttrib
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a SpecFrame, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - The returned string pointer may point at memory allocated
+*     within the SpecFrame, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the SpecFrame. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstSpecFrame *this;           /* Pointer to the SpecFrame structure */
+   AstStdOfRestType sor;         /* Standard of rest */
+   AstSystemType sys;            /* Spectral system */
+   char *new_attrib;             /* Pointer value to new attribute name */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Attribute value */
+   int ival;                     /* Attribute value */
+   int len;                      /* Length of attrib string */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_object;
+
+/* Create an FK5 J2000 SkyFrame which will be used for formatting and
+   unformatting sky positions, etc. */
+   LOCK_MUTEX2
+   if( !skyframe ) {
+      astBeginPM;
+      skyframe = astSkyFrame( "system=FK5,equinox=J2000", status );
+      astEndPM;
+   }
+   UNLOCK_MUTEX2
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* First look for axis attributes defined by the Frame class. Since a
+   SpecFrame has only 1 axis, we allow these attributes to be specified
+   without a trailing "(axis)" string. */
+   if ( !strcmp( attrib, "direction" ) ||
+        !strcmp( attrib, "bottom" ) ||
+        !strcmp( attrib, "top" ) ||
+        !strcmp( attrib, "format" ) ||
+        !strcmp( attrib, "label" ) ||
+        !strcmp( attrib, "symbol" ) ||
+        !strcmp( attrib, "unit" ) ) {
+
+/* Create a new attribute name from the original by appending the string
+   "(1)" and then use the parent GetAttrib method. */
+      new_attrib = astMalloc( len + 4 );
+      if( new_attrib ) {
+         memcpy( new_attrib, attrib, len );
+         memcpy( new_attrib + len, "(1)", 4 );
+         result = (*parent_getattrib)( this_object, new_attrib, status );
+         new_attrib = astFree( new_attrib );
+      }
+
+/* AlignStdOfRest. */
+/* --------------- */
+/* Obtain the AlignStdOfRest code and convert to a string. */
+   } else if ( !strcmp( attrib, "alignstdofrest" ) ) {
+      sor = astGetAlignStdOfRest( this );
+      if ( astOK ) {
+         result = StdOfRestString( sor, status );
+
+/* Report an error if the value was not recognised. */
+         if ( !result ) {
+            astError( AST__SCSIN,
+                     "astGetAttrib(%s): Corrupt %s contains invalid AlignStdOfRest "
+                     "identification code (%d).", status, astGetClass( this ),
+                     astGetClass( this ), (int) sor );
+         }
+      }
+
+/* AlignSpecOffset */
+/* --------------- */
+   } else if ( !strcmp( attrib, "alignspecoffset" ) ) {
+      ival = astGetAlignSpecOffset( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* GeoLat. */
+/* ------- */
+/* Retained for backward compatibility with older versions of AST in which
+   SpecFrame had GeoLon/Lat attributes (now ObsLon/Lat are used instead). */
+   } else if ( !strcmp( attrib, "geolat" ) ) {
+      result = astGetAttrib( this, "obslat" );
+
+/* GeoLon. */
+/* ------- */
+   } else if ( !strcmp( attrib, "geolon" ) ) {
+      result = astGetAttrib( this, "obslon" );
+
+/* RefDec. */
+/* ------- */
+/* Convert to a string using the SkyFrame Format method. */
+   } else if ( !strcmp( attrib, "refdec" ) ) {
+      dval = astGetRefDec( this );
+      if ( astOK ) {
+         result = astFormat( skyframe, 1, dval );
+      }
+
+/* RefRA. */
+/* ------ */
+/* Convert to a string using the SkyFrame Format method. */
+   } else if ( !strcmp( attrib, "refra" ) ) {
+      dval = astGetRefRA( this );
+      if ( astOK ) {
+         result = astFormat( skyframe, 0, dval );
+      }
+
+/* RestFreq. */
+/* --------- */
+   } else if ( !strcmp( attrib, "restfreq" ) ) {
+      dval = astGetRestFreq( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval*1.0E-9 );
+         result = getattrib_buff;
+      }
+
+/* SourceVel */
+/* --------- */
+   } else if ( !strcmp( attrib, "sourcevel" ) ) {
+      dval = astGetSourceVel( this );
+      if ( astOK ) {
+
+/* Convert from m/s to km/s if the SourceVel value is a velocity. . */
+         if( astGetSourceSys( this ) == AST__VREL ||
+             astGetSourceSys( this ) == AST__VRADIO ||
+             astGetSourceSys( this ) == AST__VOPTICAL ) dval *= 1.0E-3;
+
+/* Format */
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+
+      }
+
+/* SpecOrigin. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "specorigin" ) ) {
+      dval = GetSpecOriginCur( this, status );
+      if( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+
+/* SourceVRF */
+/* ----------*/
+   } else if ( !strcmp( attrib, "sourcevrf" ) ) {
+      sor = astGetSourceVRF( this );
+      if ( astOK ) {
+         result = StdOfRestString( sor, status );
+
+/* Report an error if the value was not recognised. */
+         if ( !result ) {
+            astError( AST__SCSIN,
+                     "astGetAttrib(%s): Corrupt %s contains invalid SourceVRF "
+                     "identification code (%d).", status, astGetClass( this ),
+                     astGetClass( this ), (int) sor );
+         }
+      }
+
+/* SourceSys */
+/* ----------*/
+   } else if ( !strcmp( attrib, "sourcesys" ) ) {
+      sys = astGetSourceSys( this );
+      if ( astOK ) {
+         result = SystemString( (AstFrame *) this, sys, status );
+
+/* Report an error if the value was not recognised. */
+         if ( !result ) {
+            astError( AST__SCSIN,
+                     "astGetAttrib(%s): Corrupt %s contains invalid SourceSys "
+                     "identification code (%d).", status, astGetClass( this ),
+                     astGetClass( this ), (int) sys );
+         }
+      }
+
+/* StdOfRest. */
+/* ---------- */
+/* Obtain the StdOfRest code and convert to a string. */
+   } else if ( !strcmp( attrib, "stdofrest" ) ) {
+      sor = astGetStdOfRest( this );
+      if ( astOK ) {
+         result = StdOfRestString( sor, status );
+
+/* Report an error if the value was not recognised. */
+         if ( !result ) {
+            astError( AST__SCSIN,
+                     "astGetAttrib(%s): Corrupt %s contains invalid StdOfRest "
+                     "identification code (%d).", status, astGetClass( this ),
+                     astGetClass( this ), (int) sor );
+         }
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetDomain( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetDomain
+
+*  Purpose:
+*     Obtain a pointer to the Domain attribute string for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *GetDomain( AstFrame *this, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astGetDomain protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function returns a pointer to the Domain attribute string
+*    for a SpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a constant null-terminated string containing the
+*     Domain value.
+
+*  Notes:
+*     - The returned pointer or the string it refers to may become
+*     invalid following further invocation of this function or
+*     modification of the SpecFrame.
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to SpecFrame structure */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_frame;
+
+/* If a Domain attribute string has been set, invoke the parent method
+   to obtain a pointer to it. */
+   if ( astTestDomain( this ) ) {
+      result = (*parent_getdomain)( this_frame, status );
+
+/* Otherwise, provide a pointer to a suitable default string. */
+   } else {
+      result = "SPECTRUM";
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetLabel( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetLabel
+
+*  Purpose:
+*     Access the Label string for a SpecFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *GetLabel( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astGetLabel method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Label string for a specified axis
+*     of a SpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated character string containing the
+*     requested information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstMapping *map;              /* Mapping between units */
+   AstSystemType system;         /* Code identifying type of spectral coordinates */
+   char *new_lab;                /* Modified label string */
+   const char *result;           /* Pointer to label string */
+   double orig;                  /* Spec origin */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise. */
+   result = NULL;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astGetLabel" );
+
+/* Check if a value has been set for the required axis label string. If so,
+   invoke the parent astGetLabel method to obtain a pointer to it. */
+   if ( astTestLabel( this, axis ) ) {
+      result = (*parent_getlabel)( this, axis, status );
+
+/* Otherwise, identify the spectral coordinate system described by the
+   SpecFrame. */
+   } else {
+      system = astGetSystem( this );
+
+/* If OK, supply a pointer to a suitable default label string. */
+      if ( astOK ) {
+         result = strcpy( getlabel_buff, SystemLabel( system, status ) );
+         getlabel_buff[ 0 ] = toupper( getlabel_buff[ 0 ] );
+
+/* If a non-zero SpecOrigin has been specified, include the offset now. */
+         orig = GetSpecOriginCur( (AstSpecFrame *) this, status );
+         if( orig != 0.0 ) {
+            sprintf( getlabel_buff + strlen( getlabel_buff ), " offset from %s",
+                     astFormat( this, 0, orig ) );
+         }
+
+/* Modify this default to take account of the current value of the Unit
+   attribute, if set. */
+         if( astTestUnit( this, axis ) ) {
+
+/* Find a Mapping from the default Units for the current System, to the
+   units indicated by the Unit attribute. This Mapping is used to modify
+   the existing default label appropriately. For instance, if the default
+   units is "Hz" and the actual units is "log(Hz)", then the default label
+   of "Frequency" is changed to "log( frequency )". */
+            map = astUnitMapper( DefUnit( system, "astGetLabel",
+                                          astGetClass( this ), status ),
+                                 astGetUnit( this, axis ), result,
+                                 &new_lab );
+            if( new_lab ) {
+               result = strcpy( getlabel_buff, new_lab );
+               new_lab = astFree( new_lab );
+            }
+
+/* Annul the unused Mapping. */
+            if( map ) map = astAnnul( map );
+
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void GetRefPos( AstSpecFrame *this, AstSkyFrame *frm, double *lon,
+                       double *lat, int *status ){
+/*
+*++
+*  Name:
+c     astGetRefPos
+f     AST_GETREFPOS
+
+*  Purpose:
+*     Return the reference position in a specified celestial coordinate system.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "specframe.h"
+c     void astGetRefPos( AstSpecFrame *this, AstSkyFrame *frm, double *lon,
+c                        double *lat )
+f     CALL AST_GETREFPOS( THIS, FRM, LON, LAT, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns the reference position (specified by attributes RefRA and
+*     RefDec) converted to the celestial coordinate system represented by
+*     a supplied SkyFrame. The celestial longitude and latitude values
+*     are returned in radians.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the SpecFrame.
+c     frm
+f     FRM = INTEGER (Given)
+*        Pointer to the SkyFrame which defines the required celestial
+*        coordinate system.
+c        If NULL
+f        If AST__NULL
+*        is supplied, then the longitude and latitude values are returned
+*        as FK5 J2000 RA and Dec values.
+c     lon
+f     LON = DOUBLE PRECISION (Returned)
+c        A pointer to a double in which to store the
+f        The
+*        longitude of the reference point, in the coordinate system
+*        represented by the supplied SkyFrame (radians).
+c     lat
+f     LAT = DOUBLE PRECISION (Returned)
+c        A pointer to a double in which to store the
+f        The
+*        latitude of the reference point, in the coordinate system
+*        represented by the supplied SkyFrame (radians).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Values of AST__BAD will be returned if this function is
+c     invoked with the AST error status set, or if it should fail for
+f     invoked with STATUS set to an error value, or if it should fail for
+*     any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;            /* Conversion FrameSet */
+   AstFrame *fb;               /* Base Frame */
+   AstFrame *fc;               /* Current Frame */
+   double xin[ 1 ];            /* Axis 1 values */
+   double yin[ 1 ];            /* Axis 2 values */
+   double xout[ 1 ];           /* Axis 1 values */
+   double yout[ 1 ];           /* Axis 2 values */
+
+/* Initialise. */
+   if( lon ) *lon = AST__BAD;
+   if( lat ) *lat = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If no SkyFrame was supplied, just return the stored RefRA and RefDec
+   values. */
+   if( !frm ) {
+      if( lon ) *lon = astGetRefRA( this );
+      if( lat ) *lat = astGetRefDec( this );
+
+/* Otherwise, convert the stored values to the requested system. */
+   } else {
+
+/* Create an FK5 J2000 SkyFrame which will be used for formatting and
+   unformatting sky positions, etc. */
+      LOCK_MUTEX2
+      if( !skyframe ) {
+         astBeginPM;
+         skyframe = astSkyFrame( "system=FK5,equinox=J2000", status );
+         astEndPM;
+      }
+      UNLOCK_MUTEX2
+
+/* Find the Mapping from the SkyFrame which describes the internal format
+   in which the RefRA and RefDec attribute values are stored, to the
+   supplied Frame. */
+      fs = astFindFrame( skyframe, frm, "" );
+
+/* If alignment was possible, use the Mapping to transform the internal
+   RefRA and RefDec values. Check for axis permutatuion. */
+      if( fs ) {
+         fb = astGetFrame( fs, AST__BASE );
+         if( astGetLonAxis( fb ) == 0 ) {
+            xin[ 0 ] = astGetRefRA( this );
+            yin[ 0 ] = astGetRefDec( this );
+         } else {
+            yin[ 0 ] = astGetRefRA( this );
+            xin[ 0 ] = astGetRefDec( this );
+         }
+         astTran2( fs, 1, xin, yin, 1, xout, yout );
+
+/* Store the returned values, checking to see if the axes of the supplied
+   SkyFrame have been permuted. */
+         fc = astGetFrame( fs, AST__CURRENT );
+         if( astGetLonAxis( fc ) == 0 ) {
+            if( lon ) *lon = xout[ 0 ];
+            if( lat ) *lat = yout[ 0 ];
+         } else {
+            if( lon ) *lon = yout[ 0 ];
+            if( lat ) *lat = xout[ 0 ];
+         }
+
+/* Annul object references. */
+         fc = astAnnul( fc );
+         fb = astAnnul( fb );
+         fs = astAnnul( fs );
+      }
+   }
+}
+
+static const char *GetSymbol( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetSymbol
+
+*  Purpose:
+*     Obtain a pointer to the Symbol string for a SpecFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *GetSymbol( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astGetSymbol method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Symbol string for a specified axis
+*     of a SpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated character string containing the
+*     requested information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstMapping *map;              /* Mapping between units */
+   AstSystemType system;         /* Code identifying type of sky coordinates */
+   char *new_sym;                /* Modified symbol string */
+   const char *result;           /* Pointer to symbol string */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise. */
+   result = NULL;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astGetSymbol" );
+
+/* Check if a value has been set for the required axis symbol string. If so,
+   invoke the parent astGetSymbol method to obtain a pointer to it. */
+   if ( astTestSymbol( this, axis ) ) {
+      result = (*parent_getsymbol)( this, axis, status );
+
+/* Otherwise, identify the sky coordinate system described by the SpecFrame. */
+   } else {
+      system = astGetSystem( this );
+
+/* If OK, supply a pointer to a suitable default Symbol string. */
+      if ( astOK ) {
+
+         if( system == AST__FREQ ) {
+	    result = "FREQ";
+         } else if( system == AST__ENERGY ) {
+	    result = "ENER";
+         } else if( system == AST__WAVENUM ) {
+	    result = "WAVN";
+         } else if( system == AST__WAVELEN ) {
+	    result = "WAVE";
+         } else if( system == AST__AIRWAVE ) {
+	    result = "AWAV";
+         } else if( system == AST__VRADIO ) {
+	    result = "VRAD";
+         } else if( system == AST__VOPTICAL ) {
+	    result = "VOPT";
+         } else if( system == AST__REDSHIFT ) {
+	    result = "ZOPT";
+         } else if( system == AST__BETA ) {
+	    result = "BETA";
+         } else if( system == AST__VREL ) {
+	    result = "VELO";
+
+/* Report an error if the coordinate system was not recognised. */
+         } else {
+	    astError( AST__SCSIN, "astGetSymbol(%s): Corrupt %s contains "
+		      "invalid System identification code (%d).", status,
+                      astGetClass( this ), astGetClass( this ), (int) system );
+         }
+
+/* Modify this default to take account of the current value of the Unit
+   attribute, if set. */
+         if( astTestUnit( this, axis ) ) {
+
+/* Find a Mapping from the default Units for the current System, to the
+   units indicated by the Unit attribute. This Mapping is used to modify
+   the existing default symbol appropriately. For instance, if the default
+   units is "Hz" and the actual units is "log(Hz)", then the default symbol
+   of "nu" is changed to "log( nu )". */
+            map = astUnitMapper( DefUnit( system, "astGetSymbol",
+                                          astGetClass( this ), status ),
+                                 astGetUnit( this, axis ), result,
+                                 &new_sym );
+            if( new_sym ) {
+               result = strcpy( getsymbol_buff, new_sym );
+               new_sym = astFree( new_sym );
+            }
+
+/* Annul the unused Mapping. */
+            if( map ) map = astAnnul( map );
+
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType GetAlignSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetAlignSystem
+
+*  Purpose:
+*     Obtain the AlignSystem attribute for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "Specframe.h"
+*     AstSystemType GetAlignSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astGetAlignSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the AlignSystem attribute for a SpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The AlignSystem value.
+
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to SpecFrame structure */
+   AstSystemType result;         /* Value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_frame;
+
+/* If a AlignSystem attribute has been set, invoke the parent method to obtain
+   it. */
+   if ( astTestAlignSystem( this ) ) {
+      result = (*parent_getalignsystem)( this_frame, status );
+
+/* Otherwise, provide a suitable default. */
+   } else {
+      result = AST__WAVELEN;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType GetSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetSystem
+
+*  Purpose:
+*     Obtain the System attribute for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     AstSystemType GetSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astGetSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the System attribute for a SpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System value.
+
+*  Notes:
+*     - AST__BADSYSTEM is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to SpecFrame structure */
+   AstSystemType result;         /* Value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_frame;
+
+/* If a System attribute has been set, invoke the parent method to obtain
+   it. */
+   if ( astTestSystem( this ) ) {
+      result = (*parent_getsystem)( this_frame, status );
+
+/* Otherwise, provide a suitable default. */
+   } else {
+      result = AST__WAVELEN;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetTitle( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetTitle
+
+*  Purpose:
+*     Obtain a pointer to the Title string for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *GetTitle( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astGetTitle method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Title string for a SpecFrame.
+*     A pointer to a suitable default string is returned if no Title value has
+*     previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a null-terminated character string containing the requested
+*     information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstSpecFrame *this;           /* Pointer to SpecFrame structure */
+   AstStdOfRestType sor;         /* Code identifying standard of rest */
+   AstSystemType system;         /* Code identifying type of coordinates */
+   const char *sor_string;       /* Pointer to SOR description */
+   const char *result;           /* Pointer to result string */
+   double rf;                    /* Rest frequency */
+   int nc;                       /* No. of characters added */
+   int pos;                      /* Buffer position to enter text */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_frame);
+
+/* Initialise. */
+   result = NULL;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_frame;
+
+/* See if a Title string has been set. If so, use the parent astGetTitle
+   method to obtain a pointer to it. */
+   if ( astTestTitle( this ) ) {
+      result = (*parent_gettitle)( this_frame, status );
+
+/* Otherwise, we will generate a default Title string. Obtain the values of the
+   SpecFrame's attributes that determine what this string will be. */
+   } else {
+      system = astGetSystem( this );
+      sor = astGetStdOfRest( this );
+      sor_string = StdOfRestString( sor, status );
+      rf = astGetRestFreq( this );
+
+/* Classify the coordinate system type and create an appropriate Title
+   string.  (Note that when invoking the astFmtDecimalYr function we must
+   use a separate sprintf on each occasion so as not to over-write its
+   internal buffer before the result string has been used.) */
+      if ( astOK ) {
+         result = gettitle_buff;
+
+/* Begin with the system's default label. */
+         pos = sprintf( gettitle_buff, "%s", SystemLabel( system, status ) );
+         gettitle_buff[ 0 ] = toupper( gettitle_buff[ 0 ] );
+
+/* Append the standard of rest in parentheses, if set. */
+         if( astTestStdOfRest( this ) ) {
+            nc = sprintf( gettitle_buff+pos, " (%s)", sor_string );
+            pos += nc;
+         }
+
+/* Append the rest frequency if relevant. */
+         if( !ABS_SYSTEM(system) && ( astTestRestFreq( this ) ||
+                                      astGetUseDefs( this ) ) ) {
+            pos += sprintf( gettitle_buff+pos, ", rest frequency = %g GHz", rf*1.0E-9 );
+         }
+      }
+   }
+
+/* If an error occurred, clear the returned pointer value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static double GetSpecOriginCur( AstSpecFrame *this, int *status ) {
+/*
+*  Name:
+*     GetSpecOriginCur
+
+*  Purpose:
+*     Obtain the SpecOrigin attribute for a SpecFrame in current units.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     double GetSpecOriginCur( AstSpecFrame *this, int *status )
+
+*  Class Membership:
+*     SpecFrame virtual function
+
+*  Description:
+*     This function returns the SpecOrigin attribute for a SpecFrame, in
+*     the current units of the SpecFrame. The protected astGetSpecOrigin
+*     method can be used to obtain the time origin in the default units of
+*     the SpecFrame's System.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The SpecOrigin value, in the units, system and rest frame specified
+*     by the current values of the Unit, System and StdOfRest attributes
+*     within "this".
+
+*  Notes:
+*     - AST__BAD is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   const char *cur;
+   const char *def;
+   double result;
+   double defval;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the value in the default units */
+   result = astGetSpecOrigin( this );
+
+/* If SpecOrigin is non-zero and non-BAD we convert it to the current units.*/
+   if( result != 0.0 && result != AST__BAD ) {
+
+/* Get the default units for the SpecFrame's System. */
+      def = DefUnit( astGetSystem( this ), "astGetSpecOrigin", "SpecFrame", status );
+
+/* Get the current units from the SpecFrame. */
+      cur = astGetUnit( this, 0 );
+
+/* If the units differ, get a Mapping from default to current units. */
+      if( cur && def ){
+         if( strcmp( cur, def ) ) {
+            map = astUnitMapper( def, cur, NULL, NULL );
+
+/* Report an error if the units are incompatible. */
+            if( !map ) {
+               astError( AST__BADUN, "%s(%s): The current units (%s) are not suitable "
+                         "for a SpecFrame.", status, "astGetSpecOrigin", astGetClass( this ),
+                         cur );
+
+/* Otherwise, transform the stored origin value.*/
+            } else {
+               defval = result;
+               astTran1( map, 1, &defval, 1, &result );
+               map = astAnnul( map );
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+
+static const char *GetUnit( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetUnit
+
+*  Purpose:
+*     Obtain a pointer to the Unit string for a SpecFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *GetUnit( AstFrame *this_frame, int axis )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astGetUnit method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Unit string for a specified axis
+*     of a SpecFrame. If the Unit attribute has not been set for the axis, a
+*     pointer to a suitable default string is returned instead.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     axis
+*        The number of the axis (zero-based) for which information is required.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the Unit value.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to the SpecFrame structure */
+   AstSystemType system;         /* The SpecFrame's System value */
+   const char *result;           /* Pointer value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_frame;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astGetUnit" );
+
+/* If a value has been set for the Unit attribute, use the parent
+   GetUnit method to return a pointer to the required Unit string. */
+   if( astTestUnit( this, axis ) ){
+      result = (*parent_getunit)( this_frame, axis, status );
+
+/* Otherwise, identify the spectral coordinate system described by the
+   SpecFrame. */
+   } else {
+      system = astGetSystem( this );
+
+/* Return a string describing the default units. */
+      result = DefUnit( system, "astGetUnit", astGetClass( this ), status );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+void astInitSpecFrameVtab_(  AstSpecFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSpecFrameVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a SpecFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void astInitSpecFrameVtab( AstSpecFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SpecFrame vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the SpecFrame class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrameVtab *frame;          /* Pointer to Frame component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitFrameVtab( (AstFrameVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsASpecFrame) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstFrameVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->GetRefPos = GetRefPos;
+   vtab->SetRefPos = SetRefPos;
+
+   vtab->ClearAlignStdOfRest = ClearAlignStdOfRest;
+   vtab->TestAlignStdOfRest = TestAlignStdOfRest;
+   vtab->GetAlignStdOfRest = GetAlignStdOfRest;
+   vtab->SetAlignStdOfRest = SetAlignStdOfRest;
+
+   vtab->ClearSourceVRF = ClearSourceVRF;
+   vtab->TestSourceVRF = TestSourceVRF;
+   vtab->GetSourceVRF = GetSourceVRF;
+   vtab->SetSourceVRF = SetSourceVRF;
+
+   vtab->ClearSourceSys = ClearSourceSys;
+   vtab->TestSourceSys = TestSourceSys;
+   vtab->GetSourceSys = GetSourceSys;
+   vtab->SetSourceSys = SetSourceSys;
+
+   vtab->ClearRefDec = ClearRefDec;
+   vtab->TestRefDec = TestRefDec;
+   vtab->GetRefDec = GetRefDec;
+   vtab->SetRefDec = SetRefDec;
+
+   vtab->ClearRefRA = ClearRefRA;
+   vtab->TestRefRA = TestRefRA;
+   vtab->GetRefRA = GetRefRA;
+   vtab->SetRefRA = SetRefRA;
+
+   vtab->ClearRestFreq = ClearRestFreq;
+   vtab->TestRestFreq = TestRestFreq;
+   vtab->GetRestFreq = GetRestFreq;
+   vtab->SetRestFreq = SetRestFreq;
+
+   vtab->ClearStdOfRest = ClearStdOfRest;
+   vtab->TestStdOfRest = TestStdOfRest;
+   vtab->GetStdOfRest = GetStdOfRest;
+   vtab->SetStdOfRest = SetStdOfRest;
+
+   vtab->ClearSourceVel = ClearSourceVel;
+   vtab->TestSourceVel = TestSourceVel;
+   vtab->GetSourceVel = GetSourceVel;
+   vtab->SetSourceVel = SetSourceVel;
+
+   vtab->ClearSpecOrigin = ClearSpecOrigin;
+   vtab->TestSpecOrigin = TestSpecOrigin;
+   vtab->GetSpecOrigin = GetSpecOrigin;
+   vtab->SetSpecOrigin = SetSpecOrigin;
+
+   vtab->TestAlignSpecOffset = TestAlignSpecOffset;
+   vtab->SetAlignSpecOffset = SetAlignSpecOffset;
+   vtab->GetAlignSpecOffset = GetAlignSpecOffset;
+   vtab->ClearAlignSpecOffset = ClearAlignSpecOffset;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   frame = (AstFrameVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_getdomain = frame->GetDomain;
+   frame->GetDomain = GetDomain;
+
+   parent_getsystem = frame->GetSystem;
+   frame->GetSystem = GetSystem;
+   parent_setsystem = frame->SetSystem;
+   frame->SetSystem = SetSystem;
+   parent_clearsystem = frame->ClearSystem;
+   frame->ClearSystem = ClearSystem;
+
+   parent_getalignsystem = frame->GetAlignSystem;
+   frame->GetAlignSystem = GetAlignSystem;
+
+   parent_getlabel = frame->GetLabel;
+   frame->GetLabel = GetLabel;
+
+   parent_getsymbol = frame->GetSymbol;
+   frame->GetSymbol = GetSymbol;
+
+   parent_gettitle = frame->GetTitle;
+   frame->GetTitle = GetTitle;
+
+   parent_clearunit = frame->ClearUnit;
+   frame->ClearUnit = ClearUnit;
+
+   parent_getunit = frame->GetUnit;
+   frame->GetUnit = GetUnit;
+
+   parent_setunit = frame->SetUnit;
+   frame->SetUnit = SetUnit;
+
+   parent_match = frame->Match;
+   frame->Match = Match;
+
+   parent_overlay = frame->Overlay;
+   frame->Overlay = Overlay;
+
+   parent_subframe = frame->SubFrame;
+   frame->SubFrame = SubFrame;
+
+/* Store replacement pointers for methods which will be over-ridden by new
+   member functions implemented here. */
+   frame->GetActiveUnit = GetActiveUnit;
+   frame->TestActiveUnit = TestActiveUnit;
+   frame->ValidateSystem = ValidateSystem;
+   frame->SystemString = SystemString;
+   frame->SystemCode = SystemCode;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "SpecFrame",
+               "Description of spectral coordinate system" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MakeSpecMapping( AstSpecFrame *target, AstSpecFrame *result,
+                            AstSpecFrame *align_frm, int report,
+                            AstMapping **map, int *status ) {
+/*
+*  Name:
+*     MakeSpecMapping
+
+*  Purpose:
+*     Generate a Mapping between two SpecFrames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     int MakeSpecMapping( AstSpecFrame *target, AstSpecFrame *result,
+*                          AstSpecFrame *align_frm, int report,
+*                          AstMapping **map, int *status ) {
+
+*  Class Membership:
+*     SpecFrame member function.
+
+*  Description:
+*     This function takes two SpecFrames and generates a Mapping that
+*     converts between them, taking account of differences in their
+*     coordinate systems, rest frequency, standard of rest, etc.
+*
+*     In order to cut down the number of transformations to be considered,
+*     the scheme works by first converting from the target frame to an
+*     "alignment" Frame, using the attributes of the target to define the
+*     transformation. A transformation is then found from the alignment
+*     frame to the required result Frame,  using the attributes of the
+*     result to define the transformation. The alignment Frame is
+*     described by the AlignSystem and AlignStdOfRest attributes of the
+*     "align_frm" SpecFrame.
+*
+*     Thus, different forms of alignment can be obtained by suitable
+*     choice of the attributes of "align_frm". For instance, to compare the
+*     radio velocity dispersion of two lines at different rest frequencies,
+*     you would set "system=radio velocity" and (probably) "stdofrest=local
+*     group" in "align_frm". On the other hand if you wanted to re-calibrate
+*     an existing radio velocity Frame within a FrameSet to use a different
+*     rest frequency, you would make the SpecFrame the current Frame and then
+*     set the rest frequency attribute for the FrameSet. The "integrity
+*     checking" system in the FrameSet class would then get the Mapping
+*     between the original and the modified SpecFrames. In this case, the
+*     "alignment system" needs to be "frequency" since you want the original
+*     and modified SpecFrames to be aligned in frequency, not radio velocity.
+
+*  Parameters:
+*     target
+*        Pointer to the first SpecFrame.
+*     result
+*        Pointer to the second SpecFrame.
+*     align_frm
+*        A SpecFrame defining the system and standard of rest in which to
+*        align the target and result SpecFrames.
+*     report
+*        Should errors be reported if no match is possible? These reports
+*        will describe why no match was possible.
+*     map
+*        Pointer to a location which is to receive a pointer to the
+*        returned Mapping. The forward transformation of this Mapping
+*        will convert from "target" coordinates to "result"
+*        coordinates, and the inverse transformation will convert in
+*        the opposite direction (all coordinate values in radians).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the Mapping could be generated, or zero if the two
+*     SpecFrames are sufficiently un-related that no meaningful Mapping
+*     can be produced (albeit an "unmeaningful" Mapping will be returned
+*     in this case, which will need to be annulled).
+
+*  Notes:
+*     A value of zero is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*/
+
+/* Local Constants: */
+#define MAX_ARGS 1               /* Max arguments for an SpecMap conversion */
+
+/* Local Variables: */
+   AstMapping *map1;             /* Intermediate Mapping */
+   AstMapping *map2;             /* Intermediate Mapping */
+   AstMapping *umap1;            /* First Units Mapping */
+   AstMapping *umap2;            /* Second Units Mapping */
+   AstSpecMap *specmap;          /* Pointer to SpecMap */
+   AstShiftMap *sm;              /* ShiftMap pointer */
+   AstSpecFrame *align_target;   /* Alignment Frame with target properties */
+   AstSpecFrame *align_result;   /* Alignment Frame with result properties */
+   AstSystemType serr;           /* Erroneous system */
+   AstSystemType align_system;   /* Code to identify alignment system */
+   AstSystemType target_system;  /* Code to identify target system */
+   AstSystemType result_system;  /* Code to identify result system */
+   const char *uerr;             /* Erroneous units */
+   const char *ures;             /* Results units */
+   const char *utarg;            /* Target units */
+   const char *vmess;            /* Text for use in error messages */
+   double args[ MAX_ARGS ];      /* Conversion argument array */
+   double target_rf;             /* Target rest frequency (Hz) */
+   double result_rf;             /* Result rest frequency (Hz) */
+   double target_origin;         /* Target origin */
+   double result_origin;         /* Result origin */
+   int match;                    /* Mapping can be generated? */
+   int step2;                    /* Perform the 2nd step in the Mapping? */
+   int step3;                    /* Perform the 3rd step in the Mapping? */
+   int step4;                    /* Perform the 4th step in the Mapping? */
+   int step5;                    /* Perform the 5th step in the Mapping? */
+   int step6;                    /* Perform the 6th step in the Mapping? */
+   int step7;                    /* Perform the 7th step in the Mapping? */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise the returned values. */
+   match = 1;
+   *map = NULL;
+
+/* Create an initial (null) SpecMap. This is a 1D Mapping which converts
+   spectral axis values between different systems and standard of rest.
+   The axis units used by the SpecMap class match the default units used
+   by this class. Any discrepancy between units is taken into account at
+   the end of this function, once the total SpecMap has been created. */
+   specmap = astSpecMap( 1, 0, "", status );
+
+/* Define local macros as shorthand for adding spectral coordinate
+   conversions to this SpecMap.  Each macro simply stores details of
+   the additional arguments in the "args" array and then calls
+   astSpecAdd. The macros differ in the number of additional argument
+   values. */
+#define TRANSFORM_0(cvt) \
+        astSpecAdd( specmap, cvt, 0, NULL );
+
+#define TRANSFORM_1(cvt,arg0) \
+        args[ 0 ] = arg0; \
+        astSpecAdd( specmap, cvt, 1, args );
+
+/* Get all the necessary attributes from the result, target and alignment
+   Frames. */
+   target_rf = astGetRestFreq( target );
+   result_rf = astGetRestFreq( result );
+
+   target_system = astGetSystem( target );
+   result_system = astGetSystem( result );
+   align_system = astGetSystem( align_frm );
+
+/* Define text for error messages.*/
+   vmess = "convert between spectral systems";
+
+/* Verify that values for the standard of rest have been set if required
+   (i.e if the UseDefs attribute of either SpecFrame is false). */
+   VerifyAttrs( result, vmess, "StdOfRest", "astMatch", status );
+   VerifyAttrs( target, vmess, "StdOfRest", "astMatch", status );
+
+/* There are two different strategies for alignment. I'll use the Source
+   rest frame as an example, although the same argument applies to other
+   rest frames. In the first strategy, all "Source" rest frames are
+   considered equal. That is, if two SpecFrames respresent (for example)
+   frequencies in the source frame, then the SpecFrames are aligned using
+   a UnitMap even if the details of the two source rest frames differ.
+   This is usually what users want to see when (for instance) aligning
+   plots of two spectra which both represent source frequencies but where
+   the source frames details differ. In the second strategy, "Source"
+   rest frames are aligned using a SpecMap that takes into account any
+   differences in the properties of the source rest frames. This is what
+   should happen when changes are made to the properties of a SpecFrame
+   within a FrameSet. For instance, if the user changes the SourceVel
+   attribute of the current Frame (assumed here to be a SpecFrame) in a
+   FrameSet, then the process of restoring the integrity of the FrameSet
+   (see frameset.c for details of integrity restoration) should cause the
+   base->current Mapping in the FrameSet to be modified to reflect the
+   new SourceVel value.
+
+   So if the current call to this function is part of the process of
+   restoring a FrameSet's integrity following changes to the FrameSet's
+   current Frame, then we want to retain the properties of the supplied
+   alignment Frame. So we use clones of the supplied alignment Frame. */
+   if( astGetFrameFlags( target ) & AST__INTFLAG ) {
+      align_target = astClone( align_frm );
+      align_result = astClone( align_frm );
+
+/* Buf if we are not restoring the integrity of a FrameSet, we want
+   to ignore any differences in the properties that define the available
+   rest frames. So create copies of the alignment Frame in which the
+   properies defining the available rest frames are the same as in the
+   target and result Frames. */
+   } else {
+      align_target = astCopy( align_frm );
+      astSetEpoch( align_target, astGetEpoch( target ) );
+      astSetRefRA( align_target, astGetRefRA( target ) );
+      astSetRefDec( align_target, astGetRefDec( target ) );
+      astSetSourceVRF( align_target, astGetSourceVRF( target ) );
+      astSetSourceVel( align_target, astGetSourceVel( target ) );
+      astSetObsLat( align_target, astGetObsLat( target ) );
+      astSetObsLon( align_target, astGetObsLon( target ) );
+      astSetObsAlt( align_target, astGetObsAlt( target ) );
+
+      align_result = astCopy( align_frm );
+      astSetEpoch( align_result, astGetEpoch( result ) );
+      astSetRefRA( align_result, astGetRefRA( result ) );
+      astSetRefDec( align_result, astGetRefDec( result ) );
+      astSetSourceVRF( align_result, astGetSourceVRF( result ) );
+      astSetSourceVel( align_result, astGetSourceVel( result ) );
+      astSetObsLat( align_result, astGetObsLat( result ) );
+      astSetObsLon( align_result, astGetObsLon( result ) );
+      astSetObsAlt( align_result, astGetObsAlt( result ) );
+   }
+
+/* The supported spectral coordinate systems fall into two groups;
+   "relative", and "absolute". The relative systems define each axis
+   value with respect to the rest frequency, whereas the absolute systems
+   have axis values which do not depend on the rest frequency. In order
+   to convert an axis value from a system in one group to a system in the
+   other group, the rest frequency must be known. However, the rest
+   frequency is not necessary in order to convert axis values between two
+   systems belonging to the same group.  Determine if the alignment system
+   is absolute or relative. If absolute, we ignore the system of the supplied
+   "align_frm" and align in frequency, since aligning in any absolute system
+   will automatically ensure that all the other absolute systems are aligned.
+   Similarly, aligning in any relative system will automatically ensure that
+   all the other relative systems are aligned. Doing this cuts down the
+   complexity of the conversion process since we do not need to check every
+   possible alignment system. */
+   align_system = ( ABS_SYSTEM( align_system ) ) ? AST__FREQ : AST__VREL;
+
+/* The total Mapping is made up of the following steps in series:
+
+  0) Convert from an offset value to an absolute value (if SpecOrigin set)
+  1) Convert target units to default units for the targets system
+  2) Convert from target system in target SOR to frequency in target SOR
+  3) Convert from freq in target SOR to freq in alignment SOR
+  4) Convert from freq in alignment SOR to alignment system in alignment SOR
+  5) Convert from alignment system in alignment SOR to freq in alignment SOR
+  6) Convert from freq in alignment SOR to freq in result SOR
+  7) Convert from freq in result SOR to result system in result SOR
+  8) Convert default units for the result system to results unit
+  9) Convert from an absolute value to an offset value (if SpecOrigin set)
+
+   Steps 1,2,3,4 are performed using the attributes of the target (rest
+   frequency, reference farem, etc), whilst steps 5,6,7,8 are performed
+   using the attributes of the target (rest frequency, reference frame,
+   etc). It is necessary to go from target system to alignment system
+   via frequency because SOR conversion can only be performed in the
+   frequency domain.
+
+   Some of these steps may not be necessary. Initially assume all steps
+   are necessary (we leave steps 0, 1, 8 and 9 out of this process and
+   implement them once all other steps have been done). */
+   step2 = 1;
+   step3 = 1;
+   step4 = 1;
+   step5 = 1;
+   step6 = 1;
+   step7 = 1;
+
+/* Step 2 is not necessary if the target system is frequency. */
+   if( target_system == AST__FREQ ) step2 = 0;
+
+/* Step 3 is not necessary if the alignment SOR is the same as the target
+   SOR. */
+   if( EqualSor( target, align_target, status ) ) step3 = 0;
+
+/* Step 6 is not necessary if the alignment SOR is the same as the result
+   SOR. */
+   if( EqualSor( result, align_result, status ) ) step6 = 0;
+
+/* Step 7 is not necessary if the result system is frequency. */
+   if( result_system == AST__FREQ ) step7 = 0;
+
+/* Steps 4 and 5 are not necessary if the alignment system is frequency,
+   or if the target and result rest frequencies are equal. */
+   if( align_system == AST__FREQ || result_rf == target_rf ) step4 = step5 = 0;
+
+/* Steps 3 and 6 are not necessary if steps 4 and 5 are not necessary, and
+   the target sor equals the result sor. */
+   if( !step4 && !step5 && EqualSor( target, result, status ) ) step3 = step6 = 0;
+
+/* Steps 2 and 7 are not necessary if steps 3, 4, 5 and 6 are not necessary,
+   and the target sor equals the result sor, and the target and results
+   systems are equal (if the systems are relative they must also have equal
+   rest frequencies). */
+   if( !step3 && !step4 && !step5 && !step6 && EqualSor( target, result, status ) &&
+       target_system == result_system ) {
+      if( !ABS_SYSTEM( target_system ) && result_rf == target_rf ) step2 = step7 = 0;
+   }
+
+
+/* Now we know which steps are needed, let's do them (we delay unit
+   conversion to the end)... */
+
+/* Step 2: target system in target rest frame to frequency in target rest
+   frame. */
+   if( step2 ) {
+      if( target_system != AST__FREQ ) {
+
+/* If the target system is absolute, we can convert directly to frequency. */
+         if ( target_system == AST__ENERGY ) {
+            TRANSFORM_0( "ENTOFR" )
+
+         } else if ( target_system == AST__WAVENUM ) {
+            TRANSFORM_0( "WNTOFR" )
+
+         } else if ( target_system == AST__WAVELEN ) {
+            TRANSFORM_0( "WVTOFR" )
+
+         } else if ( target_system == AST__AIRWAVE ) {
+            TRANSFORM_0( "AWTOFR" )
+
+/* If the target target_system is relative, we first need to convert to
+   apparent radial velocity, and then to frequency using the rest frequency. */
+         } else {
+
+            if ( target_system == AST__VRADIO ) {
+               TRANSFORM_0( "VRTOVL" )
+
+            } else if ( target_system == AST__VOPTICAL ) {
+               TRANSFORM_0( "VOTOVL" )
+
+            } else if ( target_system == AST__REDSHIFT ) {
+               TRANSFORM_0( "ZOTOVL" )
+
+            } else if ( target_system == AST__BETA ) {
+               TRANSFORM_0( "BTTOVL" )
+            }
+
+            VerifyAttrs( target, vmess, "RestFreq", "astMatch", status );
+            TRANSFORM_1( "VLTOFR", target_rf )
+         }
+      }
+   }
+
+/* Step 3: frequency in target rest frame to frequency in alignment rest
+   frame. */
+   if( step3 ) match = SorConvert( target, align_target, specmap, status );
+
+/* Step 4: frequency in alignment rest frame to alignment system in alignment
+   rest frame. The alignment will be either relativistic velocity or
+   frequency. */
+   if( step4 ) {
+      if( align_system == AST__VREL ) {
+         VerifyAttrs( target, vmess, "RestFreq", "astMatch", status );
+         TRANSFORM_1( "FRTOVL", target_rf )
+      }
+   }
+
+/* Step 5: Alignment system in alignment rest frame to frequency in alignment
+   rest frame (from now on use the attributes of the result SpecFrame to
+   define the conversion parameters). */
+   if( step5 ) {
+      if( align_system == AST__VREL ) {
+         VerifyAttrs( result, vmess, "RestFreq", "astMatch", status );
+         TRANSFORM_1( "VLTOFR", result_rf )
+      }
+   }
+
+/* Step 6: frequency in alignment rest frame to frequency in result rest
+   frame. */
+   if( step6 ) match = SorConvert( align_result, result, specmap, status );
+
+/* Step 7: frequency in result rest frame to result system in result rest
+   frame. */
+   if( step7 ) {
+      if( result_system != AST__FREQ ) {
+
+/* If the results system is absolute, we can convert directly. */
+         if ( result_system == AST__ENERGY ) {
+            TRANSFORM_0( "FRTOEN" )
+
+         } else if ( result_system == AST__WAVENUM ) {
+            TRANSFORM_0( "FRTOWN" )
+
+         } else if ( result_system == AST__WAVELEN ) {
+            TRANSFORM_0( "FRTOWV" )
+
+         } else if ( result_system == AST__AIRWAVE ) {
+            TRANSFORM_0( "FRTOAW" )
+
+/* If the result system is relative, we first need to convert to apparent
+   radial velocity from frequency using the rest frequency. Report an error
+   if the rest frequency is undefined. */
+         } else {
+            VerifyAttrs( result, vmess, "RestFreq", "astMatch", status );
+            TRANSFORM_1( "FRTOVL", result_rf )
+
+/* Now convert from apparent radial velocity to the required result system. */
+            if ( result_system == AST__VRADIO ) {
+               TRANSFORM_0( "VLTOVR" )
+
+            } else if ( result_system == AST__VOPTICAL ) {
+               TRANSFORM_0( "VLTOVO" )
+
+            } else if ( result_system == AST__REDSHIFT ) {
+               TRANSFORM_0( "VLTOZO" )
+
+            } else if ( result_system == AST__BETA ) {
+               TRANSFORM_0( "VLTOBT" )
+            }
+         }
+      }
+   }
+
+/* The SpecMap created above class assumes that the axis values supplied to
+   its Transform method are in units which correspond to the default units
+   for its class (the returned values also use these units). However,
+   the Unit attributes of the supplied Frames may have been set to some
+   non-default value, and so we need to add Mappings before and after the
+   SpecMap which convert to and from the default units. Find the Mapping
+   from the target Frame Units to the default Units for the target's system. */
+   utarg = astGetUnit( target, 0 );
+   umap1 = astUnitMapper( utarg, SpecMapUnit( target_system, "MakeSpecMap",
+                                              "SpecFrame", status ), NULL, NULL );
+
+/* Find the Mapping from the default Units for the result's system to the
+   Units of the result Frame. */
+   ures = astGetUnit( result, 0 );
+   umap2 = astUnitMapper( SpecMapUnit( result_system, "MakeSpecMap",
+                                       "SpecFrame", status ), ures, NULL, NULL );
+
+/* If both units Mappings were created OK, sandwich the SpecMap between
+   them. */
+   if( umap1 && umap2 ) {
+      map1 = (AstMapping *) astCmpMap( umap1, specmap, 1, "", status );
+      map2 = (AstMapping *) astCmpMap( map1, umap2, 1, "", status );
+      map1 = astAnnul( map1 );
+
+/* If the simplified SpecMap is a UnitMap, and the target and result
+   units are the same, we do not need to know the mapping between units.
+   Otherwise, report an error and indicate that we cannot convert between
+   the Frames. */
+   } else {
+      map2 = astSimplify( specmap );
+      if( !astIsAUnitMap( map2 ) || strcmp( ures, utarg ) ) {
+         match = 0;
+         if( astOK && report ) {
+            if( !umap1 ) {
+               uerr = utarg;
+               serr = astGetSystem( target );
+            } else {
+               uerr = ures;
+               serr = astGetSystem( result );
+            }
+            astError( AST__BADUN, "astMatch(SpecFrame): Inappropriate units (%s) "
+                      "specified for a %s axis.", status, uerr, SystemLabel( serr, status ) );
+         }
+      }
+   }
+
+/* Step 0: offset to absolute value in target system. Prepend the Maping created
+   above with a ShiftMap that does the required shift of origin. */
+   target_origin = GetSpecOriginCur( target, status );
+   if( target_origin != 0.0 ) {
+      sm = astShiftMap( 1, &target_origin, "", status );
+      map1 = (AstMapping *) astCmpMap( sm, map2, 1, "", status );
+      sm = astAnnul( sm );
+   } else {
+      map1 = astClone( map2 );
+   }
+   map2 = astAnnul( map2 );
+
+/* Step 9: absolute value to offset in result system. If we are aligning in the
+   offset system, use the transformed target origin as the new zero point.
+   Otherwise use the origin from the result frame. First get the origin for the
+   result system. */
+   if( astGetAlignSpecOffset( target ) && astGetAlignSpecOffset( result ) ) {
+      result_origin = 0.0;
+      astTran1( map1, 1, &result_origin, 1, &result_origin );
+   } else {
+      result_origin = GetSpecOriginCur( result, status );
+   }
+
+/* Now create the ShiftMap and apend it to the end of the Maping. */
+   if( result_origin != 0.0 ) {
+      result_origin = -result_origin;
+      sm = astShiftMap( 1, &result_origin, "", status );
+      map2 = (AstMapping *) astCmpMap( map1, sm, 1, "", status );
+      sm = astAnnul( sm );
+   } else {
+      map2 = astClone( map1 );
+   }
+   map1 = astAnnul( map1 );
+
+/* Return the simplified Mapping. */
+   *map = astSimplify( map2 );
+
+/* Annul remaining resources. */
+   map2 = astAnnul( map2 );
+   specmap = astAnnul( specmap );
+   if( umap1 ) umap1 = astAnnul( umap1 );
+   if( umap2 ) umap2 = astAnnul( umap2 );
+   align_result = astAnnul( align_result );
+   align_target = astAnnul( align_target );
+
+/* If an error occurred, annul the returned Mapping and clear the returned
+   values. */
+   if ( !astOK ) {
+      *map = astAnnul( *map );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+
+/* Undefine macros local to this function. */
+#undef MAX_ARGS
+#undef TRANSFORM_0
+#undef TRANSFORM_1
+}
+
+static int Match( AstFrame *template_frame, AstFrame *target, int matchsub,
+                  int **template_axes, int **target_axes, AstMapping **map,
+                  AstFrame **result, int *status ) {
+/*
+*  Name:
+*     Match
+
+*  Purpose:
+*     Determine if conversion is possible between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     int Match( AstFrame *template, AstFrame *target, int matchsub,
+*                int **template_axes, int **target_axes,
+*                AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the protected astMatch method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function matches a "template" SpecFrame to a "target" Frame and
+*     determines whether it is possible to convert coordinates between them.
+*     If it is, a mapping that performs the transformation is returned along
+*     with a new Frame that describes the coordinate system that results when
+*     this mapping is applied to the "target" coordinate system. In addition,
+*     information is returned to allow the axes in this "result" Frame to be
+*     associated with the corresponding axes in the "target" and "template"
+*     Frames from which they are derived.
+
+*  Parameters:
+*     template
+*        Pointer to the template SpecFrame. This describes the coordinate
+*        system (or set of possible coordinate systems) into which we wish to
+*        convert our coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate system in
+*        which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case.
+*     template_axes
+*        Address of a location where a pointer to int will be returned if the
+*        requested coordinate conversion is possible. This pointer will point
+*        at a dynamically allocated array of integers with one element for each
+*        axis of the "result" Frame (see below). It must be freed by the caller
+*        (using astFree) when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element of this
+*        array will return the index of the template SpecFrame axis from
+*        which it is derived. If it is not derived from any template
+*        SpecFrame axis, a value of -1 will be returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned if the
+*        requested coordinate conversion is possible. This pointer will point
+*        at a dynamically allocated array of integers with one element for each
+*        axis of the "result" Frame (see below). It must be freed by the caller
+*        (using astFree) when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element of this
+*        array will return the index of the target Frame axis from which it
+*        is derived. If it is not derived from any target Frame axis, a value
+*        of -1 will be returned instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will be
+*        returned if the requested coordinate conversion is possible. If
+*        returned, the forward transformation of this Mapping may be used to
+*        convert coordinates between the "target" Frame and the "result"
+*        Frame (see below) and the inverse transformation will convert in the
+*        opposite direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be returned
+*        if the requested coordinate conversion is possible. If returned, this
+*        Frame describes the coordinate system that results from applying the
+*        returned Mapping (above) to the "target" coordinate system. In
+*        general, this Frame will combine attributes from (and will therefore
+*        be more specific than) both the target and the template Frames. In
+*        particular, when the template allows the possibility of transformaing
+*        to any one of a set of alternative coordinate systems, the "result"
+*        Frame will indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate conversion is
+*     possible. Otherwise zero is returned (this will not in itself result in
+*     an error condition).
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+
+*  Implementation Notes:
+*     This implementation addresses the matching of a SpecFrame class
+*     object to any other class of Frame. A SpecFrame will match any class
+*     of SpecFrame (i.e. possibly from a derived class) but will not match
+*     a less specialised class of Frame.
+*/
+
+/* Local Variables: */
+   AstFrame *frame0;             /* Pointer to Frame underlying axis 0 */
+   AstSpecFrame *template;       /* Pointer to template SpecFrame structure */
+   int iaxis0;                   /* Axis index underlying axis 0 */
+   int iaxis;                    /* Axis index */
+   int match;                    /* Coordinate conversion possible? */
+   int target_axis0;             /* Index of SpecFrame axis in the target */
+   int target_naxes;             /* Number of target axes */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the template SpecFrame structure. */
+   template = (AstSpecFrame *) template_frame;
+
+/* Obtain the number of axes in the target Frame. */
+   target_naxes = astGetNaxes( target );
+
+/* The first criterion for a match is that the template matches as a
+   Frame class object. This ensures that the number of axes (1) and
+   domain, etc. of the target Frame are suitable. Invoke the parent
+   "astMatch" method to verify this. */
+   match = (*parent_match)( template_frame, target, matchsub,
+                            template_axes, target_axes, map, result, status );
+
+/* If a match was found, annul the returned objects, which are not
+   needed, but keep the memory allocated for the axis association
+   arrays, which we will re-use. */
+   if ( astOK && match ) {
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+   }
+
+/* If OK so far, obtain pointers to the primary Frames which underlie
+   all target axes. Stop when a SpecFrame axis is found. */
+   if ( match && astOK ) {
+      match = 0;
+      for( iaxis = 0; iaxis < target_naxes; iaxis++ ) {
+         astPrimaryFrame( target, iaxis, &frame0, &iaxis0 );
+         if( astIsASpecFrame( frame0 ) ) {
+            frame0 = astAnnul( frame0 );
+            target_axis0 = iaxis;
+            match = 1;
+            break;
+         } else {
+            frame0 = astAnnul( frame0 );
+         }
+      }
+
+   }
+
+/* Check at least one SpecFrame axis was found it the target. Store the
+   axis associataions. */
+   if( match && astOK ) {
+      (*template_axes)[ 0 ] = 0;
+      (*target_axes)[ 0 ] = target_axis0;
+
+/* Use the target's "astSubFrame" method to create a new Frame (the
+   result Frame) with copies of the target axes in the required
+   order. This process also overlays the template attributes on to the
+   target Frame and returns a Mapping between the target and result
+   Frames which effects the required coordinate conversion. */
+      match = astSubFrame( target, template, 1, *target_axes, *template_axes,
+                           map, result );
+
+   }
+
+/* If an error occurred, or conversion to the result Frame's
+   coordinate system was not possible, then free all memory, annul the
+   returned objects, and reset the returned value. */
+   if ( !astOK || !match ) {
+      *template_axes = astFree( *template_axes );
+      *target_axes = astFree( *target_axes );
+      if( *map ) *map = astAnnul( *map );
+      if( *result ) *result = astAnnul( *result );
+      match = 0;
+   }
+
+
+/* Return the result. */
+   return match;
+}
+
+static void OriginStdOfRest( AstSpecFrame *this, AstStdOfRestType newsor,
+                             const char *method, int *status ){
+/*
+*  Name:
+*     OriginStdOfRest
+
+*  Purpose:
+*     Convert the SpecOrigin in a SpecFrame to a new rest frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void OriginStdOfRest( AstSpecFrame *this, AstStdOfRestType newsor,
+*                           const char *method, int *status )
+
+*  Class Membership:
+*     SpecFrame member function
+
+*  Description:
+*     This function converts the value of the SpecOrigin attribute stored
+*     within a supplied SpecFrame from the rest frame currently associated
+*     with the SpecFrame, to the new rest frame indicated by "newsor".
+
+*  Parameters:
+*     this
+*        Point to the SpecFrame. On entry, the SpecOrigin value is
+*        assumed to refer to the re st frame given by the astGetStdOfRest
+*        method. On exit, the SpecOrigin value refers to the rest frame
+*        supplied in "newsor". The StdOfRest attribute of the SpecFrame
+*        should then be modified in order to keep things consistent.
+*     newsor
+*        The rest frame to which the SpecOrigin value stored within "this"
+*        should refer on exit.
+*     method
+*        Pointer to a string holding the name of the method to be
+*        included in any error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+
+/* Local Variables: */
+   AstSpecFrame *sf;
+   AstFrameSet *fs;
+   double origin;
+   double neworigin;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Do nothing if the SpecOrigin attribute has not been assigned a value. */
+   if( astTestSpecOrigin( this ) ) {
+
+/* Do nothing if the rest frame will not change. */
+      if( newsor != astGetStdOfRest( this ) ) {
+
+/* Save the original SpecOrigin value (in the current SpecFrame units) and then
+   clear it. */
+         origin = GetSpecOriginCur( this, status );
+         astClearSpecOrigin( this );
+
+/* Take a copy of the SpecFrame and set the new StdOfRest. */
+         sf = astCopy( this );
+         astSetStdOfRest( sf, newsor );
+
+/* Create a Mapping to perform the rest frame change, then use it to convert
+   the value to the new rest frame. */
+         fs = astConvert( this, sf, "" );
+         neworigin = AST__BAD;
+         if( fs ) {
+            astTran1( fs, 1, &origin, 1, &neworigin );
+            fs = astAnnul( fs );
+         }
+
+/* If succesful, convert from the current units to the default units, and store
+   in "this". */
+         if( neworigin != AST__BAD ) {
+            astSetSpecOrigin( this, ToUnits( this, astGetUnit( this, 0 ), neworigin,
+                              method, status ) );
+
+         } else if( astOK ) {
+            astError( AST__ATSER, "%s(%s): Cannot convert the SpecOrigin "
+                      "value to a different rest frame.", status, method,
+                      astGetClass( this ) );
+         }
+      }
+   }
+}
+
+static void OriginSystem( AstSpecFrame *this, AstSystemType oldsys,
+                          const char *method, int *status ){
+/*
+*  Name:
+*     OriginSystem
+
+*  Purpose:
+*     Convert the SpecOrigin in a SpecFrame to a new System.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void OriginSystem( AstSpecFrame *this, AstSystemType oldsys,
+*                        const char *method, int *status )
+
+*  Class Membership:
+*     SpecFrame member function
+
+*  Description:
+*     This function converts the value of the SpecOrigin attribute stored
+*     within a supplied SpecFrame from its original System, etc, to the
+*     System, etc, currently associated with the SpecFrame.
+
+*  Parameters:
+*     this
+*        Point to the SpecFrame. On entry, the SpecOrigin value is
+*        assumed to refer to the System given by "oldsys", etc. On exit, the
+*        SpecOrigin value refers to the System returned by the astGetSystem
+*        method, etc.
+*     oldsys
+*        The System to which the SpecOrigin value stored within "this"
+*        refers on entry.
+*     method
+*        A string containing the method name for error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstSpecFrame *sf1;
+   AstSpecFrame *sf2;
+   AstFrameSet *fs;
+   double origin;
+   double neworigin;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Do nothing if the SpecOrigin attribute has not been assigned a value. */
+   if( astTestSpecOrigin( this ) ) {
+
+/* Do nothing if the System will not change. */
+      if( oldsys != astGetSystem( this ) ) {
+
+/* Note the original SpecOrigin value, in the SpecFrame's default units. */
+         origin = astGetSpecOrigin( this );
+
+/* Take a copy of the original SpecFrame and ensure the Units, SpecOrigin and
+   AlignSpecOffset attributes are cleared. */
+         sf1 = astCopy( this );
+         astClearUnit( sf1, 0 );
+         astClearSpecOrigin( sf1 );
+         astClearAlignSpecOffset( sf1 );
+
+/* Take another copy of the SpecFrame and set the old system. */
+         sf2 = astCopy( sf1 );
+         astSetSystem( sf2, oldsys );
+
+/* Create a Mapping to perform the rest frame change, then use it to convert
+   the value to the current system. */
+         fs = astConvert( sf2, sf1, "" );
+         neworigin = AST__BAD;
+         if( fs ) {
+            astTran1( fs, 1, &origin, 1, &neworigin );
+            fs = astAnnul( fs );
+         }
+
+/* Free resources */
+         sf1 = astAnnul( sf1 );
+         sf2 = astAnnul( sf2 );
+
+/* If succesful, store it in "this". */
+         if( neworigin != AST__BAD ) {
+            astSetSpecOrigin( this, neworigin );
+
+         } else if( astOK ) {
+            astError( AST__ATSER, "%s(%s): Cannot convert the SpecOrigin "
+                      "value to a different spectral system.", status, method,
+                      astGetClass( this ) );
+         }
+      }
+   }
+}
+
+
+static void Overlay( AstFrame *template, const int *template_axes,
+                     AstFrame *result, int *status ) {
+/*
+*  Name:
+*     Overlay
+
+*  Purpose:
+*     Overlay the attributes of a template SpecFrame on to another Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void Overlay( AstFrame *template, const int *template_axes,
+*                   AstFrame *result, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the protected astOverlay method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function overlays attributes of a SpecFrame (the "template") on to
+*     another Frame, so as to over-ride selected attributes of that second
+*     Frame. Normally only those attributes which have been specifically set
+*     in the template will be transferred. This implements a form of
+*     defaulting, in which a Frame acquires attributes from the template, but
+*     retains its original attributes (as the default) if new values have not
+*     previously been explicitly set in the template.
+*
+*     Note that if the result Frame is a SpecFrame and a change of spectral
+*     coordinate system occurs as a result of overlaying its System
+*     attribute, then some of its original attribute values may no
+*     longer be appropriate (e.g. the Title, or attributes describing
+*     its axes). In this case, these will be cleared before overlaying
+*     any new values.
+
+*  Parameters:
+*     template
+*        Pointer to the template SpecFrame, for which values should have been
+*        explicitly set for any attribute which is to be transferred.
+*     template_axes
+*        Pointer to an array of int, with one element for each axis of the
+*        "result" Frame (see below). For each axis in the result frame, the
+*        corresponding element of this array should contain the (zero-based)
+*        index of the template axis to which it corresponds. This array is used
+*        to establish from which template axis any axis-dependent attributes
+*        should be obtained.
+*
+*        If any axis in the result Frame is not associated with a template
+*        axis, the corresponding element of this array should be set to -1.
+*
+*        If a NULL pointer is supplied, the template and result axis
+*        indices are assumed to be identical.
+*     result
+*        Pointer to the Frame which is to receive the new attribute values.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  In general, if the result Frame is not from the same class as the
+*     template SpecFrame, or from a class derived from it, then attributes may
+*     exist in the template SpecFrame which do not exist in the result Frame.
+*     In this case, these attributes will not be transferred.
+*/
+
+/* Local Variables: */
+   AstFrame *templt;             /* Copy of supplied template Frame */
+   AstSystemType new_system;     /* Code identifying new cordinates */
+   AstSystemType old_system;     /* Code identifying old coordinates */
+   const char *method;           /* Pointer to method string */
+   const char *new_class;        /* Pointer to template class string */
+   const char *old_class;        /* Pointer to result class string */
+   int specframe;                /* Result Frame is a SpecFrame? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise strings used in error messages. */
+   new_class = astGetClass( template );
+   old_class = astGetClass( result );
+   method = "astOverlay";
+
+/* Get the old and new systems. */
+   old_system = astGetSystem( result );
+   new_system = astGetSystem( template );
+
+/* It may be necessary to make temporary changes to the template Frame
+   below. In order to ensure that we make no permanent changes to the
+   supplied frame, we will, if necessary, take a deep copy of the
+   supplied Frame, storing a pointer to the copy in "templt". If it is
+   not necessary to make any changes to the template, we still want
+   "templt" to hold a usable pointer, so we initialise it now to hold a
+   clone of the supplied pointer. This pointer will be replaced by a
+   pointer to a deep copy (if required) below. */
+   templt = astClone( template );
+
+/* If the result Frame is a SpecFrame, we must test to see if overlaying its
+   System attribute will change the type of coordinate system it describes.
+   Determine the value of this attribute for the result and template
+   SpecFrames. */
+   specframe = astIsASpecFrame( result );
+   if( specframe ) {
+
+/* If the coordinate system will change, any value already set for the result
+   SpecFrame's Title will no longer be appropriate, so clear it. */
+      if ( new_system != old_system ) {
+         astClearTitle( result );
+
+/* If the systems have the same default units, we can retain the current
+   Unit value. */
+         if( strcmp( DefUnit( new_system, method, new_class, status ),
+                     DefUnit( old_system, method, old_class, status ) ) ) {
+            astClearUnit( result, 0 );
+         }
+
+/* If necessary, clear inappropriate values for all those axis attributes
+   whose access functions are over-ridden by this class (these access functions
+   will then provide suitable defaults appropriate to the new coordinate system
+   instead). */
+         astClearLabel( result, 0 );
+         astClearSymbol( result, 0 );
+      }
+
+/* If the result Frame is not a SpecFrame, we must temporarily clear the
+   System and AlignSystem values since the values used by this class
+   are only appropriate to this class. Use a deep copy to avoid the danger
+   of making any permanent changes to the suppied Frame. */
+   } else {
+      if( astTestSystem( template ) ) {
+         templt = astAnnul( templt );
+         templt = astCopy( template );
+         astClearSystem( templt );
+         astClearAlignSystem( templt );
+      }
+   }
+
+/* Invoke the parent class astOverlay method to transfer attributes inherited
+   from the parent class. */
+   (*parent_overlay)( templt, template_axes, result, status );
+
+/* Check if the result Frame is a SpecFrame or from a class derived from
+   SpecFrame. If not, we cannot transfer SpecFrame attributes to it as it is
+   insufficiently specialised. In this case simply omit these attributes. */
+   if ( specframe && astOK ) {
+
+/* Define macros that test whether an attribute is set in the template and,
+   if so, transfers its value to the result. */
+#define OVERLAY(attribute) \
+   if ( astTest##attribute( template ) ) { \
+      astSet##attribute( result, astGet##attribute( template ) ); \
+   }
+
+/* Use the macro to transfer each SpecFrame attribute in turn. Note,
+   SourceVRF must be overlayed before SourceVel. Otherwise the stored value
+   for SourceVel would be changed from the default SourceVRF to the specified
+   SourceVRF when SourceVRF was overlayed. */
+      OVERLAY(AlignStdOfRest)
+      OVERLAY(AlignSpecOffset);
+      OVERLAY(RefDec)
+      OVERLAY(RefRA)
+      OVERLAY(RestFreq)
+      OVERLAY(SourceSys)
+      OVERLAY(SourceVRF)
+      OVERLAY(SourceVel)
+      OVERLAY(StdOfRest)
+      OVERLAY(SpecOrigin)
+   }
+
+/* Free resources */
+   templt = astAnnul( templt );
+
+/* Undefine macros local to this function. */
+#undef OVERLAY
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (extends the astSetAttrib method inherited from
+*     the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a SpecFrame, the attribute
+*     and its value being specified by means of a string of the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in lower
+*     case with no white space present. The value to the right of the "="
+*     should be a suitable textual representation of the value to be assigned
+*     and this will be interpreted according to the attribute's data type.
+*     White space surrounding the value is only significant for string
+*     attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This protected method is intended to be invoked by the Object astSet
+*     method and makes additional attributes accessible to it.
+*/
+
+/* Local Vaiables: */
+   AstMapping *umap;             /* Mapping between units */
+   AstSpecFrame *this;           /* Pointer to the SpecFrame structure */
+   AstStdOfRestType sor;         /* Standard of rest type code */
+   AstSystemType sys;            /* Spectral system type code */
+   char *a;                      /* Pointer to next character */
+   char *new_setting;            /* Pointer value to new attribute setting */
+   double dval;                  /* Double atribute value */
+   double dtemp;                 /* Temporary double atribute value */
+   int ival;                     /* Integer attribute value */
+   int len;                      /* Length of setting string */
+   int ulen;                     /* Used length of setting string */
+   int namelen;                  /* Length of attribute name in setting */
+   int nc;                       /* Number of characters read by astSscanf */
+   int off;                      /* Offset of attribute value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_object;
+
+/* Create an FK5 J2000 SkyFrame which will be used for formatting and
+   unformatting sky positions, etc. */
+   LOCK_MUTEX2
+   if( !skyframe ) {
+      astBeginPM;
+      skyframe = astSkyFrame( "system=FK5,equinox=J2000", status );
+      astEndPM;
+   }
+   UNLOCK_MUTEX2
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Obtain the used length of the setting string. */
+   ulen = astChrLen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* First look for axis attributes defined by the Frame class. Since a
+   SpecFrame has only 1 axis, we allow these attributes to be specified
+   without a trailing "(axis)" string. */
+   if ( !strncmp( setting, "direction=", 10 ) ||
+        !strncmp( setting, "bottom=", 7 ) ||
+        !strncmp( setting, "top=", 4 ) ||
+        !strncmp( setting, "format=", 7 ) ||
+        !strncmp( setting, "label=", 6 ) ||
+        !strncmp( setting, "symbol=", 7 ) ||
+        !strncmp( setting, "unit=", 5 ) ) {
+
+/* Create a new setting string from the original by appending the string
+   "(1)" to the end of the attribute name and then use the parent SetAttrib
+   method. */
+      new_setting = astMalloc( len + 4 );
+      if( new_setting ) {
+         memcpy( new_setting, setting, len + 1 );
+         a = strchr( new_setting, '=' );
+         namelen = a - new_setting;
+         memcpy( a, "(1)", 4 );
+         a += 3;
+         strcpy( a, setting + namelen );
+         (*parent_setattrib)( this_object, new_setting, status );
+         new_setting = astFree( new_setting );
+      }
+
+/* AlignStdOfRest. */
+/* --------------- */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "alignstdofrest=%n%*s %n", &off, &nc ) )
+        && ( nc >= len ) ) {
+
+/* Convert the string to a StdOfRest code before use. */
+      sor = StdOfRestCode( setting + off, status );
+      if ( sor != AST__BADSOR ) {
+         astSetAlignStdOfRest( this, sor );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid standard of rest "
+                   "description \"%s\".", status, astGetClass( this ), setting+off );
+      }
+
+/* GeoLat. */
+/* ------- */
+/* Retained for backward compatibility with older versions of AST in which
+   SpecFrame had GeoLon/Lat attributes (now ObsLon/Lat are used instead). */
+   } else if ( nc = 0,
+              ( 0 == astSscanf( setting, "geolat=%n%*s %n", &off, &nc ) )
+              && ( nc >= 7 ) ) {
+      new_setting = astStore( NULL, setting, len + 1 );
+      new_setting[ 0 ] = 'o';
+      new_setting[ 1 ] = 'b';
+      new_setting[ 2 ] = 's';
+      astSetAttrib( this, new_setting );
+      new_setting = astFree( new_setting );
+
+/* GeoLon. */
+/* ------- */
+   } else if ( nc = 0,
+              ( 0 == astSscanf( setting, "geolon=%n%*s %n", &off, &nc ) )
+              && ( nc >= 7 ) ) {
+      new_setting = astStore( NULL, setting, len + 1 );
+      new_setting[ 0 ] = 'o';
+      new_setting[ 1 ] = 'b';
+      new_setting[ 2 ] = 's';
+      astSetAttrib( this, new_setting );
+      new_setting = astFree( new_setting );
+
+/* RefDec. */
+/* ------- */
+   } else if ( nc = 0,
+              ( 0 == astSscanf( setting, "refdec=%n%*s %n", &off, &nc ) )
+              && ( nc >= 7 ) ) {
+
+/* Convert the string to a radians value before use. */
+      ival = astUnformat( skyframe, 1, setting + off, &dval );
+      if ( ival == ulen - off  ) {
+         astSetRefDec( this, dval );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid reference "
+                   "declination \"%s\".", status, astGetClass( this ), setting + off );
+      }
+
+/* RefRA. */
+/* ------ */
+   } else if ( nc = 0,
+              ( 0 == astSscanf( setting, "refra=%n%*s %n", &off, &nc ) )
+              && ( nc >= 6 ) ) {
+
+/* Convert the string to a radians value before use. */
+      ival = astUnformat( skyframe, 0, setting + off, &dval );
+      if ( ival == ulen - off  ) {
+         astSetRefRA( this, dval );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid reference right "
+                   "ascension \"%s\".", status, astGetClass( this ), setting + off );
+      }
+
+/* AlignSpecOffset. */
+/* ---------------- */
+   } else if ( nc = 0,
+             ( 1 == astSscanf( setting, "alignspecoffset= %d %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetAlignSpecOffset( this, ival );
+
+/* RestFreq. */
+/* --------- */
+/* Without any units indication - assume GHz. Convert to Hz for storage. */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "restfreq= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetRestFreq( this, dval*1.0E9 );
+
+/* With units indication. */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "restfreq= %lg %n%*s %n", &dval, &off, &nc ) )
+        && ( nc >= len ) ) {
+      dtemp = AST__BAD;
+
+/* Is there a Mapping from the supplied units to Hz? If so, use the
+   Mapping to convert the supplied value to Hz. */
+      if( ( umap = astUnitMapper( setting + off, "Hz", NULL, NULL ) ) ) {
+         astTran1( umap, 1, &dval, 1, &dtemp );
+         umap = astAnnul( umap );
+
+/* Otherwise, if there is a Mapping from the supplied units to metre,
+   assume the supplied unit is a vacuum wavelength. */
+      } else if( ( umap = astUnitMapper( setting + off, "m", NULL, NULL ) ) ) {
+
+/* Convert the supplied wavelength to metres. */
+         astTran1( umap, 1, &dval, 1, &dtemp );
+         umap = astAnnul( umap );
+
+/* Convert the wavelength (m) to frequency (Hz). */
+         if( dtemp != AST__BAD && dtemp != 0.0 ) {
+            dtemp = AST__C/dtemp;
+         } else if( astOK ) {
+            astError( AST__ATTIN, "astSetAttrib(%s): Invalid rest wavelength "
+                   "\"%g %s\" supplied.", status, astGetClass( this ), dval, setting + off );
+         }
+
+/* Otherwise, if there is a Mapping from the supplied units to Joule,
+   assume the supplied unit is an energy. */
+      } else if( ( umap = astUnitMapper( setting + off, "J", NULL, NULL ) ) ) {
+
+/* Convert the supplied energy to Joules. */
+         astTran1( umap, 1, &dval, 1, &dtemp );
+         umap = astAnnul( umap );
+
+/* Convert the energy (J) to frequency (Hz). */
+         if( dtemp != AST__BAD ) {
+            dtemp *= 1.0/AST__H;
+         } else if( astOK ) {
+            astError( AST__ATTIN, "astSetAttrib(%s): Invalid rest energy "
+                   "\"%g %s\" supplied.", status, astGetClass( this ), dval, setting + off );
+         }
+
+/* Otherwise report an error. */
+      } else if( astOK ) {
+         astError( AST__ATTIN, "astSetAttrib(%s): Rest frequency given in an "
+                   "unsupported system of units \"%g %s\".", status,
+                   astGetClass( this ), dval, setting + off );
+      }
+
+/* Set the rest frequency. */
+      astSetRestFreq( this, dtemp );
+
+/* SourceVel. */
+/* ---------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "sourcevel= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+
+/* Convert from km/s to m/s if the SourceVel value is a velocity. */
+         if( astGetSourceSys( this ) == AST__VREL ||
+             astGetSourceSys( this ) == AST__VRADIO ||
+             astGetSourceSys( this ) == AST__VOPTICAL ) dval *= 1.0E3;
+
+/* Store the value */
+      astSetSourceVel( this, dval );
+
+/* SourceVRF */
+/* --------- */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "sourcevrf=%n%*s %n", &off, &nc ) )
+        && ( nc >= len ) ) {
+
+/* Convert the string to a StdOfRest code before use. */
+      sor = StdOfRestCode( setting + off, status );
+      if ( sor != AST__BADSOR ) {
+         astSetSourceVRF( this, sor );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid standard of rest "
+                   "description \"%s\".", status, astGetClass( this ), setting+off );
+      }
+
+/* SourceSys */
+/* --------- */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "sourcesys=%n%*s %n", &off, &nc ) )
+        && ( nc >= len ) ) {
+
+/* Convert the string to a System code before use. */
+      sys = SystemCode( (AstFrame *) this, setting + off, status );
+      astSetSourceSys( this, sys );
+
+/* StdOfRest. */
+/* ---------- */
+   } else if ( nc = 0,
+              ( 0 == astSscanf( setting, "stdofrest=%n%*s %n", &off, &nc ) )
+              && ( nc >= len ) ) {
+
+/* Convert the string to a StdOfRest code before use. */
+      sor = StdOfRestCode( setting + off, status );
+      if ( sor != AST__BADSOR ) {
+         astSetStdOfRest( this, sor );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid standard of rest "
+                   "description \"%s\".", status, astGetClass( this ), setting + off );
+      }
+
+/* SpecOrigin */
+/* ---------- */
+
+/* Floating-point without any units indication - assume the current Unit
+   value. Convert from current units to default units for current system. */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "specorigin= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+
+      astSetSpecOrigin( this, ToUnits( this, astGetUnit( this, 0 ), dval,
+                                       "astSetSpecOrigin", status ) );
+
+/* Floating-point with units. Convert the supplied value to the default units
+   for the SpecFrame's System. */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "specorigin= %lg %n%*s %n", &dval, &off, &nc ) )
+        && ( nc >= len ) ) {
+      astSetSpecOrigin( this, ToUnits( this, setting + off, dval, "astSetSpecOrigin", status ) );
+
+/* Pass any unrecognised setting to the parent method for further
+   interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SetRefPos( AstSpecFrame *this, AstSkyFrame *frm, double lon,
+                       double lat, int *status ){
+/*
+*++
+*  Name:
+c     astSetRefPos
+f     AST_SETREFPOS
+
+*  Purpose:
+*     Set the reference position in a specified celestial coordinate system.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "specframe.h"
+c     void astSetRefPos( AstSpecFrame *this, AstSkyFrame *frm, double lon,
+c                        double lat )
+f     CALL AST_SETREFPOS( THIS, FRM, LON, LAT, STATUS )
+
+*  Class Membership:
+*     Frame method.
+
+*  Description:
+c     This function
+f     This routine
+*     sets the reference position (see attributes RefRA and RefDec) using
+*     axis values (in radians) supplied within the celestial coordinate
+*     system represented by a supplied SkyFrame.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the SpecFrame.
+c     frm
+f     FRM = INTEGER (Given)
+*        Pointer to the SkyFrame which defines the celestial coordinate
+*        system in which the longitude and latitude values are supplied.
+c        If NULL
+f        If AST__NULL
+*        is supplied, then the supplied longitude and latitude values are
+*        assumed to be FK5 J2000 RA and Dec values.
+c     lon
+f     LON = DOUBLE PRECISION (Given)
+*        The longitude of the reference point, in the coordinate system
+*        represented by the supplied SkyFrame (radians).
+c     lat
+f     LAT = DOUBLE PRECISION (Given)
+*        The latitude of the reference point, in the coordinate system
+*        represented by the supplied SkyFrame (radians).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;            /* Conversion FrameSet */
+   AstFrame *fb;               /* Base Frame */
+   AstFrame *fc;               /* Current Frame */
+   double xin[ 1 ];            /* Axis 1 values */
+   double yin[ 1 ];            /* Axis 2 values */
+   double xout[ 1 ];           /* Axis 1 values */
+   double yout[ 1 ];           /* Axis 2 values */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If no SkyFrame was supplied, just store the supplied RefRA and RefDec
+   values. */
+   if( !frm ) {
+      astSetRefRA( this, lon );
+      astSetRefDec( this, lat );
+
+/* Otherwise, convert the supplied values from the requested system. */
+   } else {
+
+/* Create an FK5 J2000 SkyFrame which will be used for formatting and
+   unformatting sky positions, etc. */
+      LOCK_MUTEX2
+      if( !skyframe ) {
+         astBeginPM;
+         skyframe = astSkyFrame( "system=FK5,equinox=J2000", status );
+         astEndPM;
+      }
+      UNLOCK_MUTEX2
+
+/* Find the Mapping from the supplied SkyFrame, to the SkyFrame which
+   describes the internal format in which the RefRA and RefDec attribute
+   values are stored. */
+      fs = astFindFrame( frm, skyframe, "" );
+
+/* If alignment was possible, use the Mapping to transform the supplied
+   axis values, checking to see if the axes of the supplied SkyFrame have
+   been permuted. */
+      if( fs ) {
+
+/* Find the longitude axis in the Base Frame, and store the supplied
+   longitude and latitude values. */
+         fb = astGetFrame( fs, AST__BASE );
+         if( astGetLonAxis( fb ) == 0 ) {
+            xin[ 0 ] = lon;
+            yin[ 0 ] = lat;
+         } else {
+            xin[ 0 ] = lat;
+            yin[ 0 ] = lon;
+         }
+         astTran2( fs, 1, xin, yin, 1, xout, yout );
+
+/* Store the corresponding RefRA and RefDec values. */
+         fc = astGetFrame( fs, AST__CURRENT );
+         if( astGetLonAxis( fc ) == 0 ) {
+            astSetRefRA( this, xout[ 0 ] );
+            astSetRefDec( this, yout[ 0 ] );
+         } else {
+            astSetRefRA( this, yout[ 0 ] );
+            astSetRefDec( this, xout[ 0 ] );
+         }
+
+/* Annul object references. */
+         fc = astAnnul( fc );
+         fb = astAnnul( fb );
+         fs = astAnnul( fs );
+      }
+   }
+}
+
+static void SetStdOfRest( AstSpecFrame *this, AstStdOfRestType value, int *status ) {
+/*
+*+
+*  Name:
+*     astSetStdOfRest
+
+*  Purpose:
+*     Set the StdOfRest attribute for a SpecFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void astSetStdOfRest( AstSpecFrame *this, AstStdOfRestType value )
+
+*  Class Membership:
+*     SpecFrame virtual function
+
+*  Description:
+*     This function set a new value for the StdOfRest attribute for a
+*     SpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     value
+*        The new value.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+
+/* Validate the StdOfRest value being set and report an error if necessary. */
+   if( value < FIRST_SOR || value > LAST_SOR ) {
+      astError( AST__ATTIN, "%s(%s): Bad value (%d) given for StdOfRest attribute.", status,
+                            "astSetStdOfRest", astGetClass( this ), (int) value );
+
+/* Otherwise set the new StdOfRest */
+   } else {
+
+/* Modify the SpecOrigin value stored in the SpecFrame structure to refer
+   to the new rest frame. */
+      OriginStdOfRest( this, value, "astSetStdOfRest", status );
+
+/* Store the new value for the rest frame in the SpecFrame structure. */
+      this->stdofrest = value;
+
+   }
+}
+
+static void SetSystem( AstFrame *this_frame, AstSystemType newsys, int *status ) {
+/*
+*  Name:
+*     SetSystem
+
+*  Purpose:
+*     Set the System attribute for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void SetSystem( AstFrame *this_frame, AstSystemType newsys, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astSetSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function sets the System attribute for a SpecFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     newsys
+*        The new System value to be stored.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to SpecFrame structure */
+   AstSystemType oldsys;         /* Original System value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_frame;
+
+/* Save the original System value */
+   oldsys = astGetSystem( this_frame );
+
+/* Use the parent SetSystem method to store the new System value. */
+   (*parent_setsystem)( this_frame, newsys, status );
+
+/* If the system has changed... */
+   if( oldsys != newsys ) {
+
+/* Changing the System value will in general require the Units to change
+   as well. If the user has previously specified the units to be used with
+   the new system, then re-instate them (they are stored in the "usedunits"
+   array in the SpecFrame structure). Otherwise, clear the units so that
+   the default units will eb used with the new System. */
+      if( (int) newsys < this->nuunits && this->usedunits &&
+          this->usedunits[ (int) newsys ] ) {
+         astSetUnit( this, 0, this->usedunits[ (int) newsys ] );
+      } else {
+         astClearUnit( this, 0 );
+      }
+
+/* Modify the stored SpecOrigin. */
+      OriginSystem( this, oldsys, "astSetSystem", status );
+
+/* Also, clear all attributes which have system-specific defaults. */
+      astClearLabel( this_frame, 0 );
+      astClearSymbol( this_frame, 0 );
+      astClearTitle( this_frame );
+   }
+}
+
+static void SetUnit( AstFrame *this_frame, int axis, const char *value, int *status ) {
+/*
+*  Name:
+*     SetUnit
+
+*  Purpose:
+*     Set a pointer to the Unit string for a SpecFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void SetUnit( AstFrame *this_frame, int axis, const char *value )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astSetUnit method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function stores a pointer to the Unit string for a specified axis
+*     of a SpecFrame. It also stores the string in the "usedunits" array
+*     in the SpecFrame structure, in the element associated with the
+*     current System.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     axis
+*        The number of the axis (zero-based) for which information is required.
+*     unit
+*        The new string to store.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to the SpecFrame structure */
+   int i;                        /* Loop counter */
+   int system;                   /* The SpecFrame's System value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_frame;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astSetUnit" );
+
+/* Store the supplied value as the UsedUnit for the current System. First
+   ensure the array is big enough. Free any previous value stored for the
+   current system. */
+   system = (int) astGetSystem( this );
+   if( system >= this->nuunits ) {
+      this->usedunits = astGrow( this->usedunits, system + 1,
+                                 sizeof(char *) );
+      if( astOK ) {
+         for( i = this->nuunits; i < system + 1; i++ ) this->usedunits[ i ] = NULL;
+         this->nuunits = system + 1;
+      }
+   }
+
+/* Now store a copy of the value, if it is different to the stored string. */
+   if( astOK && ( !this->usedunits[ system ] ||
+                  strcmp( this->usedunits[ system ], value ) ) ) {
+      this->usedunits[ system ] = astStore( this->usedunits[ system ],
+                                            value, strlen( value ) + 1 );
+   }
+
+/* Now use the parent SetUnit method to store the value in the Axis
+   structure */
+   (*parent_setunit)( this_frame, axis, value, status );
+
+}
+
+static int SorConvert( AstSpecFrame *this, AstSpecFrame *that,
+                       AstSpecMap *specmap, int *status ) {
+/*
+*  Name:
+*     SorConvert
+
+*  Purpose:
+*     Add a conversion to a SpecMap which transforms between two
+*     standards of rest.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     int SorConvert( AstSpecFrame *this, AstSpecFrame *that,
+*                     AstSpecMap *specmap, int *status )
+
+*  Class Membership:
+*     SpecFrame member function.
+
+*  Description:
+*     This function adds a conversion to a SpecMap which transforms
+*     frequencies from the standard of rest specified by "this" to
+*     the standard of rest specified by "that". Note the conversion is
+*     always between frequency in the two rest frames no matter what the
+*     System attributes of the two SpecFrames may be (which are ignored).
+
+*  Parameters:
+*     this
+*        The SpecFrame which defines the input rest frame.
+*     that
+*        The SpecFrame which defines the output rest frame.
+*     specmap
+*        The SpecMap to which the conversion is to be added.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero is returned if the conversion could not be performed. One is
+*     returned otherwise.
+
+*/
+
+/* Local Constants: */
+#define MAX_ARGS 7               /* Max arguments for an SpecMap conversion */
+
+/* Local Variables: */
+   AstStdOfRestType from;        /* Input standard of rest */
+   AstStdOfRestType to;          /* Output standard of rest */
+   const char *vmess;            /* Text for use in error messages */
+   double args[ MAX_ARGS ];      /* Conversion argument array */
+   double dec;                   /* DEC of source (radians, FK5 J2000) */
+   double epoch;                 /* Epoch of observation (MJD) */
+   double alt;                   /* Observers geodetic altitude (radians) */
+   double lat;                   /* Observers geodetic latitude (radians) */
+   double lon;                   /* Observers geodetic longitude (radians) */
+   double ra;                    /* RA of source (radians, FK5 J2000) */
+   int result;                   /* Returned value */
+
+/* Initialise */
+   result = 1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* No conversion is required if the rest frames are equal. */
+   if( !EqualSor( this, that, status ) ) {
+
+/* Define local macros as shorthand for adding spectral coordinate
+   conversions to the SpecMap.  Each macro simply stores details of
+   the additional arguments in the "args" array and then calls
+   astSpecAdd. The macros differ in the number of additional argument
+   values. */
+#define TRANSFORM_2(cvt,arg0,arg1) \
+        args[ 0 ] = arg0; \
+        args[ 1 ] = arg1; \
+        astSpecAdd( specmap, cvt, 2, args );
+
+#define TRANSFORM_3(cvt,arg0,arg1,arg2) \
+        args[ 0 ] = arg0; \
+        args[ 1 ] = arg1; \
+        args[ 2 ] = arg2; \
+        astSpecAdd( specmap, cvt, 3, args );
+
+#define TRANSFORM_6(cvt,arg0,arg1,arg2,arg3,arg4,arg5) \
+        args[ 0 ] = arg0; \
+        args[ 1 ] = arg1; \
+        args[ 2 ] = arg2; \
+        args[ 3 ] = arg3; \
+        args[ 4 ] = arg4; \
+        args[ 5 ] = arg5; \
+        astSpecAdd( specmap, cvt, 6, args );
+
+/* A string for use in error messages. */
+      vmess = "convert between different standards of rest";
+
+/* Get the required values from "this". */
+      from = astGetStdOfRest( this );
+      ra = astGetRefRA( this );
+      dec = astGetRefDec( this );
+      lon = astGetObsLon( this );
+      lat = astGetObsLat( this );
+      alt = astGetObsAlt( this );
+      epoch = astGetEpoch( this );
+
+/* Verify that the reference RA and DEC can be used (they are needed by all
+   the conversions used below). */
+      VerifyAttrs( this, vmess, "RefRA RefDec", "astMatch", status );
+
+/* Convert from the "this" rest frame to heliographic. */
+      if( from == AST__TPSOR ) {
+         VerifyAttrs( this, vmess, "ObsLon ObsLat ObsAlt Epoch", "astMatch", status );
+         TRANSFORM_6( "TPF2HL", lon, lat, alt, epoch, ra, dec )
+
+      } else if( from == AST__GESOR ) {
+         VerifyAttrs( this, vmess, "Epoch", "astMatch", status );
+         TRANSFORM_3( "GEF2HL", epoch, ra, dec )
+
+      } else if( from == AST__BYSOR ) {
+         VerifyAttrs( this, vmess, "Epoch", "astMatch", status );
+         TRANSFORM_3( "BYF2HL", epoch, ra, dec )
+
+      } else if( from == AST__LKSOR ) {
+         TRANSFORM_2( "LKF2HL", ra, dec )
+
+      } else if( from == AST__LDSOR ) {
+         TRANSFORM_2( "LDF2HL", ra, dec )
+
+      } else if( from == AST__LGSOR ) {
+         TRANSFORM_2( "LGF2HL", ra, dec )
+
+      } else if( from == AST__GLSOR ) {
+         TRANSFORM_2( "GLF2HL", ra, dec )
+
+      } else if( from == AST__SCSOR ) {
+         TRANSFORM_3( "USF2HL", ConvertSourceVel( this, AST__HLSOR, AST__VREL, status ),
+                      ra, dec )
+      }
+
+/* Now go from heliocentric to the "to" frame. */
+      to = astGetStdOfRest( that );
+      ra = astGetRefRA( that );
+      dec = astGetRefDec( that );
+      lon = astGetObsLon( that );
+      lat = astGetObsLat( that );
+      alt = astGetObsAlt( that );
+      epoch = astGetEpoch( that );
+      VerifyAttrs( that, vmess, "RefRA RefDec", "astMatch", status );
+
+      if( to == AST__TPSOR ) {
+         VerifyAttrs( that, vmess, "ObsLon ObsLat ObsAlt Epoch", "astMatch", status );
+         TRANSFORM_6( "HLF2TP", lon, lat, alt, epoch, ra, dec )
+
+      } else if( to == AST__GESOR ) {
+         VerifyAttrs( that, vmess, "Epoch", "astMatch", status );
+         TRANSFORM_3( "HLF2GE", epoch, ra, dec )
+
+      } else if( to == AST__BYSOR ) {
+         VerifyAttrs( that, vmess, "Epoch", "astMatch", status );
+         TRANSFORM_3( "HLF2BY", epoch, ra, dec )
+
+      } else if( to == AST__LKSOR ) {
+         TRANSFORM_2( "HLF2LK", ra, dec )
+
+      } else if( to == AST__LDSOR ) {
+         TRANSFORM_2( "HLF2LD", ra, dec )
+
+      } else if( to == AST__LGSOR ) {
+         TRANSFORM_2( "HLF2LG", ra, dec )
+
+      } else if( to == AST__GLSOR ) {
+         TRANSFORM_2( "HLF2GL", ra, dec )
+
+      } else if( to == AST__SCSOR ) {
+         TRANSFORM_3( "HLF2US", ConvertSourceVel( that, AST__HLSOR, AST__VREL, status ),
+                      ra, dec )
+      }
+   }
+
+/* Return the result. */
+   return result;
+
+/* Undefine macros local to this function. */
+#undef MAX_ARGS
+#undef TRANSFORM_2
+#undef TRANSFORM_3
+#undef TRANSFORM_6
+}
+
+static const char *SpecMapUnit( AstSystemType system, const char *method,
+                                const char *class, int *status ){
+/*
+*  Name:
+*     SpecMapUnit
+
+*  Purpose:
+*     Return the default units for a spectral coordinate system type used
+*     by the SpecMap class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *SpecMapUnit( AstSystemType system, const char *method,
+*                              const char *class, int *status )
+
+*  Class Membership:
+*     SpecFrame member function.
+
+*  Description:
+*     This function returns a textual representation of the
+*     units used by the SpecMap class for the specified spectral
+*     coordinate system. In general, the SpecMap class uses SI units
+*     (m/s, Hz, m, etc), but this class (SpecFrame) has default units
+*     more appropriate to astronomers (km/s, GHz, Angstroms, etc).
+
+*  Parameters:
+*     system
+*        The spectral coordinate system.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A string describing the default units. This string follows the
+*     units syntax described in FITS WCS paper I "Representations of world
+*     coordinates in FITS" (Greisen & Calabretta).
+
+*  Notes:
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Value to return */
+
+/* Initialize */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get an identifier for the default units. */
+   if( system == AST__FREQ ) {
+      result = "Hz";
+   } else if( system == AST__ENERGY ) {
+      result = "J";
+   } else if( system == AST__WAVENUM ) {
+      result = "1/m";
+   } else if( system == AST__WAVELEN ) {
+      result = "m";
+   } else if( system == AST__AIRWAVE ) {
+      result = "m";
+   } else if( system == AST__VRADIO ) {
+      result = "m/s";
+   } else if( system == AST__VOPTICAL ) {
+      result = "m/s";
+   } else if( system == AST__REDSHIFT ) {
+      result = "";
+   } else if( system == AST__BETA ) {
+      result = "";
+   } else if( system == AST__VREL ) {
+      result = "m/s";
+
+/* Report an error if the coordinate system was not recognised. */
+   } else {
+      astError( AST__SCSIN, "%s(%s): Corrupt %s contains illegal System "
+                "identification code (%d).", status, method, class, class,
+                (int) system );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstStdOfRestType StdOfRestCode( const char *sor, int *status ) {
+/*
+*  Name:
+*     StdOfRestCode
+
+*  Purpose:
+*     Convert a string into a standard of rest type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     AstStdOfRestType StdOfRestCode( const char *sor )
+
+*  Class Membership:
+*     SpecFrame member function.
+
+*  Description:
+*     This function converts a string used for the external description of
+*     a standard of rest into a SpecFrame standard of rest type code
+*     (StdOfRest attribute value). It is the inverse of the
+*     StdOfRestString function.
+
+*  Parameters:
+*     sor
+*        Pointer to a constant null-terminated string containing the
+*        external description of the standard of rest.
+
+*  Returned Value:
+*     The StdOfRest type code.
+
+*  Notes:
+*     - A value of AST__BADSOR is returned if the standard of rest
+*     description was not recognised. This does not produce an error.
+*     - A value of AST__BADSOR is also returned if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstStdOfRestType result;      /* Result value to return */
+
+/* Initialise. */
+   result = AST__BADSOR;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "sor" string against each possibility and assign the
+   result. */
+   if ( astChrMatch( "TOPO", sor ) || astChrMatch( "TOPOCENT", sor ) || astChrMatch( "TOPOCENTRIC", sor ) ) {
+      result = AST__TPSOR;
+
+   } else if ( astChrMatch( "GEO", sor ) || astChrMatch( "GEOCENTR", sor ) || astChrMatch( "GEOCENTRIC", sor ) ) {
+      result = AST__GESOR;
+
+   } else if ( astChrMatch( "BARY", sor ) || astChrMatch( "BARYCENT", sor ) || astChrMatch( "BARYCENTRIC", sor ) ) {
+      result = AST__BYSOR;
+
+   } else if ( astChrMatch( "HELIO", sor ) || astChrMatch( "HELIOCEN", sor ) || astChrMatch( "HELIOCENTRIC", sor ) ) {
+      result = AST__HLSOR;
+
+   } else if ( astChrMatch( "LSRK", sor ) || astChrMatch( "LSR", sor ) ) {
+      result = AST__LKSOR;
+
+   } else if ( astChrMatch( "LSRD", sor ) ) {
+      result = AST__LDSOR;
+
+   } else if ( astChrMatch( "GAL", sor ) || astChrMatch( "GALACTOC", sor ) || astChrMatch( "GALACTIC", sor ) ) {
+      result = AST__GLSOR;
+
+   } else if ( astChrMatch( "LG", sor ) || astChrMatch( "LOCALGRP", sor ) ||
+               astChrMatch( "LOCAL_GROUP", sor ) || astChrMatch( "LOCAL-GROUP", sor ) ) {
+      result = AST__LGSOR;
+
+   } else if ( astChrMatch( "SOURCE", sor ) || astChrMatch( "SRC", sor ) ) {
+      result = AST__SCSOR;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *StdOfRestString( AstStdOfRestType sor, int *status ) {
+/*
+*  Name:
+*     StdOfRestString
+
+*  Purpose:
+*     Convert a standard of rest type code into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *StdOfRestString( AstStdOfRestType sor, int *status )
+
+*  Class Membership:
+*     SpecFrame member function.
+
+*  Description:
+*     This function converts a SpecFrame standard of rest type code
+*     (StdOfRest attribute value) into a string suitable for use as an
+*     external representation of the standard of rest type.
+
+*  Parameters:
+*     sor
+*        The standard of rest type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the standard of rest
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "sor" value against each possibility and convert to a
+   string pointer. (Where possible, return the same string as would be
+   used in the FITS WCS representation of the standard of rest). */
+   switch ( sor ) {
+
+   case AST__TPSOR:
+      result = "Topocentric";
+      break;
+
+   case AST__GESOR:
+      result = "Geocentric";
+      break;
+
+   case AST__BYSOR:
+      result = "Barycentric";
+      break;
+
+   case AST__HLSOR:
+      result = "Heliocentric";
+      break;
+
+   case AST__LDSOR:
+      result = "LSRD";
+      break;
+
+   case AST__LKSOR:
+      result = "LSRK";
+      break;
+
+   case AST__LGSOR:
+      result = "Local_group";
+      break;
+
+   case AST__GLSOR:
+      result = "Galactic";
+      break;
+
+   case AST__SCSOR:
+      result = "Source";
+      break;
+
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static int SubFrame( AstFrame *target_frame, AstFrame *template,
+                     int result_naxes, const int *target_axes,
+                     const int *template_axes, AstMapping **map,
+                     AstFrame **result, int *status ) {
+/*
+*  Name:
+*     SubFrame
+
+*  Purpose:
+*     Select axes from a SpecFrame and convert to the new coordinate
+*     system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     int SubFrame( AstFrame *target, AstFrame *template,
+*                   int result_naxes, const int *target_axes,
+*                   const int *template_axes, AstMapping **map,
+*                   AstFrame **result, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the protected astSubFrame
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function selects a requested sub-set (or super-set) of the axes
+*     from a "target" SpecFrame and creates a new Frame with copies of
+*     the selected axes assembled in the requested order. It then
+*     optionally overlays the attributes of a "template" Frame on to the
+*     result. It returns both the resulting Frame and a Mapping that
+*     describes how to convert between the coordinate systems described by
+*     the target and result Frames. If necessary, this Mapping takes
+*     account of any differences in the Frames' attributes due to the
+*     influence of the template.
+
+*  Parameters:
+*     target
+*        Pointer to the target SpecFrame, from which axes are to be
+*        selected.
+*     template
+*        Pointer to the template Frame, from which new attributes for the
+*        result Frame are to be obtained. Optionally, this may be NULL, in
+*        which case no overlaying of template attributes will be performed.
+*     result_naxes
+*        Number of axes to be selected from the target Frame. This number may
+*        be greater than or less than the number of axes in this Frame (or
+*        equal).
+*     target_axes
+*        Pointer to an array of int with result_naxes elements, giving a list
+*        of the (zero-based) axis indices of the axes to be selected from the
+*        target SpecFrame. The order in which these are given determines
+*        the order in which the axes appear in the result Frame. If any of the
+*        values in this array is set to -1, the corresponding result axis will
+*        not be derived from the target Frame, but will be assigned default
+*        attributes instead.
+*     template_axes
+*        Pointer to an array of int with result_naxes elements. This should
+*        contain a list of the template axes (given as zero-based axis indices)
+*        with which the axes of the result Frame are to be associated. This
+*        array determines which axes are used when overlaying axis-dependent
+*        attributes of the template on to the result. If any element of this
+*        array is set to -1, the corresponding result axis will not receive any
+*        template attributes.
+*
+*        If the template argument is given as NULL, this array is not used and
+*        a NULL pointer may also be supplied here.
+*     map
+*        Address of a location to receive a pointer to the returned Mapping.
+*        The forward transformation of this Mapping will describe how to
+*        convert coordinates from the coordinate system described by the target
+*        SpecFrame to that described by the result Frame. The inverse
+*        transformation will convert in the opposite direction.
+*     result
+*        Address of a location to receive a pointer to the result Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if coordinate conversion is possible
+*     between the target and the result Frame. Otherwise zero is returned and
+*     *map and *result are returned as NULL (but this will not in itself
+*     result in an error condition). In general, coordinate conversion should
+*     always be possible if no template Frame is supplied but may not always
+*     be possible otherwise.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+
+*  Implementation Notes:
+*     -  This implementation addresses the selection of axes from a
+*     SpecFrame object. This results in another object of the same class
+*     only if the single SpecFrame axis is selected exactly once.
+*     Otherwise, the result is a Frame class object which inherits the
+*     SpecFrame's axis information (if appropriate) but none of the other
+*     properties of a SpecFrame.
+*     -  In the event that a SpecFrame results, the returned Mapping will
+*     take proper account of the relationship between the target and result
+*     coordinate systems.
+*     -  In the event that a Frame class object results, the returned Mapping
+*     will only represent a selection/permutation of axes.
+
+*  Implementation Deficiencies:
+*     -  Any axis selection is currently permitted. Probably this should be
+*     restricted so that each axis can only be selected once. The
+*     astValidateAxisSelection method will do this but currently there are bugs
+*     in the CmpFrame class that cause axis selections which will not pass this
+*     test. Install the validation when these are fixed.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *target;      /* Pointer to the SpecFrame structure */
+   AstSpecFrame *temp;        /* Pointer to copy of target SpecFrame */
+   AstSpecFrame *align_frm;   /* Frame in which to align the SpecFrames */
+   int match;                 /* Coordinate conversion is possible? */
+   int report;                /* Report errors if SpecFrames cannot be aligned? */
+
+/* Initialise the returned values. */
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the target SpecFrame structure. */
+   target = (AstSpecFrame *) target_frame;
+
+/* Result is a SpecFrame. */
+/* -------------------------- */
+/* Check if the result Frame is to have one axis obtained by selecting
+   the single target SpecFrame axis. If so, the result will also be
+   a SpecFrame. */
+   if ( ( result_naxes == 1 ) && ( target_axes[ 0 ] == 0 ) ) {
+
+/* Form the result from a copy of the target. */
+      *result = astCopy( target );
+
+/* Initialise a flag to indicate that MakeSpecMapping should not report
+   errors if no Mapping can be created. */
+      report = 0;
+
+/* If required, overlay the template attributes on to the result SpecFrame.
+   Also get the system and standard of rest in which to align the two
+   SpecFrames. These are the values from the template (if there is a
+   template). */
+      if ( template ) {
+         astOverlay( template, template_axes, *result );
+         if( astIsASpecFrame( template ) ) {
+            align_frm = astCopy( template );
+
+/* Since we now know that both the template and target are SpecFrames, it
+   should usually be possible to convert betwen them. If conversion is
+   *not* possible (fpr instance if no rest frequency is availalbe, etc)
+   then the user will probably be interested in knowing the reason why
+   conversion is not possible. Therefore, indicate that MakeSpecMapping
+   should report errors if no Mapping can be created. */
+            report = 1;
+
+         } else {
+            align_frm = astCopy( target );
+         }
+
+/* If no template was supplied, align in the System and StdOfRest of the
+   target. */
+      } else {
+         VerifyAttrs( target, "convert between different spectral systems",
+                   "StdOfRest", "astMatch", status );
+         align_frm = astCopy( target );
+      }
+
+/* The MakeSpecMapping function uses the System and StdOfRest attributes to
+   define the alignment frame. But the AlignSystem and AlignStdOfRest
+   attributes should be used for this purpose. Therefore, copy the values
+   of the AlignSystem and AlignStdOfRest attributes to the System and
+   StdOfRest attribute. */
+      astSetSystem( align_frm, astGetAlignSystem( align_frm ) );
+      astSetStdOfRest( align_frm, astGetAlignStdOfRest( align_frm ) );
+
+/* Generate a Mapping that takes account of changes in the sky coordinate
+   system (equinox, epoch, etc.) between the target SpecFrame and the result
+   SpecFrame. If this Mapping can be generated, set "match" to indicate that
+   coordinate conversion is possible. If the template is a specframe,
+   report errors if a match is not possible. */
+      match = ( MakeSpecMapping( target, (AstSpecFrame *) *result,
+                align_frm, report, map, status ) != 0 );
+
+/* Free resources. */
+      align_frm = astAnnul( align_frm );
+
+/* Result is not a SpecFrame. */
+/* ------------------------------ */
+/* In this case, we select axes as if the target were from the Frame
+   class.  However, since the resulting data will then be separated
+   from their enclosing SpecFrame, default attribute values may differ
+   if the methods for obtaining them were over-ridden by the SpecFrame
+   class. To overcome this, we ensure that these values are explicitly
+   set for the result Frame (rather than relying on their defaults). */
+   } else {
+
+/* Make a temporary copy of the target SpecFrame. We will explicitly
+   set the attribute values in this copy so as not to modify the original. */
+      temp = astCopy( target );
+
+/* Define a macro to test if an attribute is set. If not, set it
+   explicitly to its default value. */
+#define SET(attribute) \
+   if ( !astTest##attribute( temp ) ) { \
+      astSet##attribute( temp, astGet##attribute( temp ) ); \
+   }
+
+/* Set attribute values which apply to the Frame as a whole and which
+   we want to retain, but whose defaults are over-ridden by the
+   SpecFrame class. */
+      SET(Domain)
+      SET(Title)
+
+/* Define a macro to test if an attribute is set for axis zero (the only
+   axis of a SpecFrame). If not, set it explicitly to its default value. */
+#define SET_AXIS(attribute) \
+   if ( !astTest##attribute( temp, 0 ) ) { \
+      astSet##attribute( temp, 0, \
+                         astGet##attribute( temp, 0 ) ); \
+   }
+
+/* Use this macro to set explicit values for all the axis attributes
+   for which the SpecFrame class over-rides the default value. */
+      SET_AXIS(Label)
+      SET_AXIS(Symbol)
+      SET_AXIS(Unit)
+
+/* Clear attributes which have an extended range of values allowed by
+   this class. */
+      astClearSystem( temp );
+      astClearAlignSystem( temp );
+
+/* Invoke the astSubFrame method inherited from the Frame class to
+   produce the result Frame by selecting the required set of axes and
+   overlaying the template Frame's attributes. */
+      match = (*parent_subframe)( (AstFrame *) temp, template,
+                                  result_naxes, target_axes, template_axes,
+                                  map, result, status );
+
+/* Delete the temporary copy of the target SpecFrame. */
+      temp = astDelete( temp );
+   }
+
+/* If an error occurred or no match was found, annul the returned
+   objects and reset the returned result. */
+   if ( !astOK || !match ) {
+      if( *map ) *map = astAnnul( *map );
+      if( *result ) *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+
+/* Undefine macros local to this function. */
+#undef SET
+#undef SET_AXIS
+}
+
+static AstSystemType SystemCode( AstFrame *this, const char *system, int *status ) {
+/*
+*  Name:
+*     SystemCode
+
+*  Purpose:
+*     Convert a string into a coordinate system type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     AstSystemType SystemCode( AstFrame *this, const char *system, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astSystemCode method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a string used for the external
+*     description of a coordinate system into a SpecFrame
+*     coordinate system type code (System attribute value). It is the
+*     inverse of the astSystemString function.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     system
+*        Pointer to a constant null-terminated string containing the
+*        external description of the sky coordinate system.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System type code.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM is returned if the sky coordinate
+*     system description was not recognised. This does not produce an
+*     error.
+*     - A value of AST__BADSYSTEM is also returned if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;      /* Result value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" string against each possibility and assign the
+   result. */
+   if ( astChrMatch( "FREQ", system ) ) {
+      result = AST__FREQ;
+
+   } else if ( astChrMatch( "ENER", system ) || astChrMatch( "ENERGY", system ) ) {
+      result = AST__ENERGY;
+
+   } else if ( astChrMatch( "WAVN", system ) || astChrMatch( "WAVENUM", system ) ) {
+      result = AST__WAVENUM;
+
+   } else if ( astChrMatch( "WAVE", system ) || astChrMatch( "WAVELEN", system ) ) {
+      result = AST__WAVELEN;
+
+   } else if ( astChrMatch( "AWAV", system ) || astChrMatch( "AIRWAVE", system ) ) {
+      result = AST__AIRWAVE;
+
+   } else if ( astChrMatch( "VRAD", system ) || astChrMatch( "VRADIO", system ) ) {
+      result = AST__VRADIO;
+
+   } else if ( astChrMatch( "VOPT", system ) || astChrMatch( "VOPTICAL", system ) ) {
+      result = AST__VOPTICAL;
+
+   } else if ( astChrMatch( "ZOPT", system ) || astChrMatch( "REDSHIFT", system ) ) {
+      result = AST__REDSHIFT;
+
+   } else if ( astChrMatch( "BETA", system ) ) {
+      result = AST__BETA;
+
+   } else if ( astChrMatch( "VELO", system ) || astChrMatch( "VREL", system ) ) {
+      result = AST__VREL;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *SystemLabel( AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SystemLabel
+
+*  Purpose:
+*     Return a label for a coordinate system type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *SystemLabel( AstSystemType system, int *status )
+
+*  Class Membership:
+*     SpecFrame member function.
+
+*  Description:
+*     This function converts a SpecFrame coordinate system type code
+*     (System attribute value) into a descriptive string for human readers.
+
+*  Parameters:
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the sky coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" value against each possibility and convert to a
+   string pointer. */
+   switch ( system ) {
+
+   case AST__FREQ:
+      result = "frequency";
+      break;
+
+   case AST__ENERGY:
+      result = "energy";
+      break;
+
+   case AST__WAVENUM:
+      result = "wave-number";
+      break;
+
+   case AST__WAVELEN:
+      result = "wavelength";
+      break;
+
+   case AST__AIRWAVE:
+      result = "wavelength in air";
+      break;
+
+   case AST__VRADIO:
+      result = "radio velocity";
+      break;
+
+   case AST__VOPTICAL:
+      result = "optical velocity";
+      break;
+
+   case AST__REDSHIFT:
+      result = "redshift";
+      break;
+
+   case AST__BETA:
+      result = "beta factor";
+      break;
+
+   case AST__VREL:
+      result = "apparent radial velocity";
+      break;
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static const char *SystemString( AstFrame *this, AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SystemString
+
+*  Purpose:
+*     Convert a coordinate system type code into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     const char *SystemString( AstFrame *this, AstSystemType system, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astSystemString method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a SpecFrame coordinate system type code
+*     (System attribute value) into a string suitable for use as an
+*     external representation of the coordinate system type.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the sky coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" value against each possibility and convert to a
+   string pointer. (Where possible, return the same string as would be
+   used in the FITS WCS representation of the coordinate system). */
+   switch ( system ) {
+
+   case AST__FREQ:
+      result = "FREQ";
+      break;
+
+   case AST__ENERGY:
+      result = "ENER";
+      break;
+
+   case AST__WAVENUM:
+      result = "WAVN";
+      break;
+
+   case AST__WAVELEN:
+      result = "WAVE";
+      break;
+
+   case AST__AIRWAVE:
+      result = "AWAV";
+      break;
+
+   case AST__VRADIO:
+      result = "VRAD";
+      break;
+
+   case AST__VOPTICAL:
+      result = "VOPT";
+      break;
+
+   case AST__REDSHIFT:
+      result = "ZOPT";
+      break;
+
+   case AST__BETA:
+      result = "BETA";
+      break;
+
+   case AST__VREL:
+      result = "VELO";
+      break;
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static int TestActiveUnit( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     TestActiveUnit
+
+*  Purpose:
+*     Test the ActiveUnit flag for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     int TestActiveUnit( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astTestActiveUnit protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function test the value of the ActiveUnit flag for a SpecFrame,
+*    which is always "unset".
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The result of the test (0).
+
+*/
+   return 0;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astTestAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a SpecFrame's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to the SpecFrame structure */
+   char *new_attrib;             /* Pointer value to new attribute name */
+   int len;                      /* Length of attrib string */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* First look for axis attributes defined by the Frame class. Since a
+   SpecFrame has only 1 axis, we allow these attributes to be specified
+   without a trailing "(axis)" string. */
+   if ( !strcmp( attrib, "direction" ) ||
+        !strcmp( attrib, "bottom" ) ||
+        !strcmp( attrib, "top" ) ||
+        !strcmp( attrib, "format" ) ||
+        !strcmp( attrib, "label" ) ||
+        !strcmp( attrib, "symbol" ) ||
+        !strcmp( attrib, "unit" ) ) {
+
+/* Create a new attribute name from the original by appending the string
+   "(1)" and then use the parent TestAttrib method. */
+      new_attrib = astMalloc( len + 4 );
+      if( new_attrib ) {
+         memcpy( new_attrib, attrib, len );
+         memcpy( new_attrib + len, "(1)", 4 );
+         result = (*parent_testattrib)( this_object, new_attrib, status );
+         new_attrib = astFree( new_attrib );
+      }
+
+/* AlignStdOfRest. */
+/* --------------- */
+   } else if ( !strcmp( attrib, "alignstdofrest" ) ) {
+      result = astTestAlignStdOfRest( this );
+
+/* GeoLat. */
+/* ------- */
+/* Retained for backward compatibility with older versions of AST in which
+   SpecFrame had GeoLon/Lat attributes (now ObsLon/Lat are used instead). */
+   } else if ( !strcmp( attrib, "geolat" ) ) {
+      result = astTestAttrib( this, "obslat" );
+
+/* GeoLon. */
+/* ------- */
+   } else if ( !strcmp( attrib, "geolon" ) ) {
+      result = astTestAttrib( this, "obslon" );
+
+/* RefDec. */
+/* ------- */
+   } else if ( !strcmp( attrib, "refdec" ) ) {
+      result = astTestRefDec( this );
+
+/* RefRA. */
+/* ------ */
+   } else if ( !strcmp( attrib, "refra" ) ) {
+      result = astTestRefRA( this );
+
+/* RestFreq. */
+/* --------- */
+   } else if ( !strcmp( attrib, "restfreq" ) ) {
+      result = astTestRestFreq( this );
+
+/* SourceVel. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "sourcevel" ) ) {
+      result = astTestSourceVel( this );
+
+/* SourceVRF */
+/* --------- */
+   } else if ( !strcmp( attrib, "sourcevrf" ) ) {
+      result = astTestSourceVRF( this );
+
+/* SourceSys */
+/* --------- */
+   } else if ( !strcmp( attrib, "sourcesys" ) ) {
+      result = astTestSourceSys( this );
+
+/* StdOfRest. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "stdofrest" ) ) {
+      result = astTestStdOfRest( this );
+
+/* SpecOrigin. */
+/* --------- */
+   } else if ( !strcmp( attrib, "specorigin" ) ) {
+      result = astTestSpecOrigin( this );
+
+/* AlignSpecOffset */
+/* --------------- */
+   } else if ( !strcmp( attrib, "alignspecoffset" ) ) {
+      result = astTestAlignSpecOffset( this );
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static double ToUnits( AstSpecFrame *this, const char *oldunit, double oldval,
+                       const char *method, int *status ){
+/*
+*
+*  Name:
+*     ToUnits
+
+*  Purpose:
+*     Convert a supplied spectral value to the default units of the supplied
+*     SpecFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     double ToUnits( AstSpecFrame *this, const char *oldunit, double oldval,
+*                     const char *method, int *status )
+
+*  Class Membership:
+*     SpecFrame member function
+
+*  Description:
+*     This function converts the supplied value from the supplied units to
+*     the default units associated with the supplied SpecFrame's System.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     oldunit
+*        The units in which "oldval" is supplied.
+*     oldval
+*        The value to be converted.
+*     method
+*        Pointer to a string holding the name of the method to be
+*        included in any error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The converted value.
+
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   const char *defunit;
+   double result;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get default units associated with the System attribute of the supplied
+   SpecFrame, and find a Mapping from the old units to the default. */
+   defunit = DefUnit( astGetSystem( this ), method, "SpecFrame", status );
+   map = astUnitMapper( oldunit, defunit, NULL, NULL );
+   if( map ) {
+
+/* Use the Mapping to convert the supplied value. */
+      astTran1( map, 1, &oldval, 1, &result );
+
+/* Free resources. */
+      map = astAnnul( map );
+
+/* Report an error if no conversion is possible. */
+   } else if( astOK ){
+      astError( AST__BADUN, "%s(%s): Cannot convert the supplied attribute "
+                "value from units of %s to %s.", status, method, astGetClass( this ),
+                 oldunit, defunit );
+   }
+
+/* Return the result */
+   return result;
+}
+
+
+static int ValidateSystem( AstFrame *this, AstSystemType system, const char *method, int *status ) {
+/*
+*
+*  Name:
+*     ValidateSystem
+
+*  Purpose:
+*     Validate a value for a Frame's System attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     int ValidateSystem( AstFrame *this, AstSystemType system,
+*                         const char *method, int *status )
+
+*  Class Membership:
+*     SpecFrame member function (over-rides the astValidateSystem method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of the supplied system value.
+*     If the value is valid, it is returned unchanged. Otherwise, an
+*     error is reported and a value of AST__BADSYSTEM is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The system value to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The validated system value.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;              /* Validated system value */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the value is out of bounds, report an error. */
+   if ( system < FIRST_SYSTEM || system > LAST_SYSTEM ) {
+         astError( AST__AXIIN, "%s(%s): Bad value (%d) given for the System "
+                   "or AlignSystem attribute of a %s.", status, method,
+                   astGetClass( this ), (int) system, astGetClass( this ) );
+
+/* Otherwise, return the supplied value. */
+   } else {
+      result = system;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void VerifyAttrs( AstSpecFrame *this, const char *purp,
+                         const char *attrs, const char *method, int *status ) {
+/*
+*  Name:
+*     VerifyAttrs
+
+*  Purpose:
+*     Verify that usable attribute values are available.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     void VerifyAttrs( AstSpecFrame *this, const char *purp,
+*                       const char *attrs, const char *method, int *status  )
+
+*  Class Membership:
+*     SpecFrame member function
+
+*  Description:
+*     This function tests each attribute listed in "attrs". It returns
+*     without action if 1) an explicit value has been set for each attribute
+*     or 2) the UseDefs attribute of the supplied SpecFrame is non-zero.
+*
+*     If UseDefs is zero (indicating that default values should not be
+*     used for attributes), and any of the named attributes does not have
+*     an explicitly set value, then an error is reported.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame.
+*     purp
+*        Pointer to a text string containing a message which will be
+*        included in any error report. This shouldindicate the purpose
+*        for which the attribute value is required.
+*     attrs
+*        A string holding a space separated list of attribute names.
+*     method
+*        A string holding the name of the calling method for use in error
+*        messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   const char *a;
+   const char *desc;
+   const char *p;
+   int len;
+   int set;
+   int state;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* If the SpecFrame has a non-zero value for its UseDefs attribute, then
+   all attributes are assumed to have usable values, since the defaults
+   will be used if no explicit value has been set. So we only need to do
+   any checks if UseDefs is zero. */
+   if( !astGetUseDefs( this ) ) {
+
+/* Stop compiler warnings about uninitialised variables */
+      a = NULL;
+      desc = NULL;
+      len = 0;
+      set = 0;
+
+/* Loop round the "attrs" string identifying the start and length of each
+   non-blank word in the string. */
+      state = 0;
+      p = attrs;
+      while( 1 ) {
+         if( state == 0 ) {
+            if( !isspace( *p ) ) {
+               a = p;
+               len = 1;
+               state = 1;
+            }
+         } else {
+            if( isspace( *p ) || !*p ) {
+
+/* The end of a word has just been reached. Compare it to each known
+   attribute value. Get a flag indicating if the attribute has a set
+   value, and a string describing the attribute.*/
+               if( len > 0 ) {
+
+                  if( !strncmp( "ObsLat", a, len ) ) {
+                     set = astTestObsLat( this );
+                     desc = "observer's latitude";
+
+                  } else if( !strncmp( "ObsLon", a, len ) ) {
+                     set = astTestObsLon( this );
+                     desc = "observer's longitude";
+
+                  } else if( !strncmp( "ObsAlt", a, len ) ) {
+                     set = astTestObsAlt( this );
+                     desc = "observer's altitude";
+
+                  } else if( !strncmp( "RefRA", a, len ) ) {
+                     set = astTestRefRA( this );
+                     desc = "source RA";
+
+                  } else if( !strncmp( "RefDec", a, len ) ) {
+                     set = astTestRefDec( this );
+                     desc = "source Dec";
+
+                  } else if( !strncmp( "RestFreq", a, len ) ) {
+                     set = astTestRestFreq( this );
+                     desc = "rest frequency";
+
+                  } else if( !strncmp( "SourceVel", a, len ) ) {
+                     set = astTestSourceVel( this );
+                     desc = "source velocity";
+
+                  } else if( !strncmp( "StdOfRest", a, len ) ) {
+                     set = astTestStdOfRest( this );
+                     desc = "spectral standard of rest";
+
+                  } else if( !strncmp( "Epoch", a, len ) ) {
+                     set = astTestEpoch( this );
+                     desc = "epoch of observation";
+
+                  } else {
+                     astError( AST__INTER, "VerifyAttrs(SpecFrame): "
+                               "Unknown attribute name \"%.*s\" supplied (AST "
+                               "internal programming error).", status, len, a );
+                  }
+
+/* If the attribute does not have a set value, report an error. */
+                  if( !set && astOK ) {
+                     astError( AST__NOVAL, "%s(%s): Cannot %s.", status, method,
+                               astGetClass( this ), purp );
+                     astError( AST__NOVAL, "No value has been set for "
+                               "the AST \"%.*s\" attribute (%s).", status, len, a,
+                               desc );
+                  }
+
+/* Continue the word search algorithm. */
+               }
+               len = 0;
+               state = 0;
+            } else {
+               len++;
+            }
+         }
+         if( !*(p++) ) break;
+      }
+   }
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/*
+*att++
+*  Name:
+*     AlignSpecOffset
+
+*  Purpose:
+*     Align SpecFrames using the offset coordinate system?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute is a boolean value which controls how a SpecFrame
+*     behaves when it is used (by
+c     astFindFrame or astConvert) as a template to match another (target)
+f     AST_FINDFRAME or AST_CONVERT) as a template to match another (target)
+*     SpecFrame. It determines whether alignment occurs between the offset
+*     values defined by the current value of the SpecOffset attribute, or
+*     between the corresponding absolute spectral values.
+*
+*     The default value of zero results in the two SpecFrames being aligned
+*     so that a given absolute spectral value in one is mapped to the same
+*     absolute value in the other. A non-zero value results in the SpecFrames
+*     being aligned so that a given offset value in one is mapped to the same
+*     offset value in the other.
+
+*  Applicability:
+*     SpecFrame
+*        All SpecFrames have this attribute.
+*att--
+*/
+astMAKE_CLEAR(SpecFrame,AlignSpecOffset,alignspecoffset,-INT_MAX)
+astMAKE_GET(SpecFrame,AlignSpecOffset,int,0,( ( this->alignspecoffset != -INT_MAX ) ?
+                                   this->alignspecoffset : 0 ))
+astMAKE_SET(SpecFrame,AlignSpecOffset,int,alignspecoffset,( value != 0 ))
+astMAKE_TEST(SpecFrame,AlignSpecOffset,( this->alignspecoffset != -INT_MAX ))
+
+
+
+/*
+*att++
+*  Name:
+*     AlignStdOfRest
+
+*  Purpose:
+*     Standard of rest to use when aligning SpecFrames.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute controls how a SpecFrame behaves when it is used (by
+c     astFindFrame or astConvert) as a template to match another (target)
+f     AST_FINDFRAME or AST_CONVERT) as a template to match another (target)
+*     SpecFrame. It identifies the standard of rest in which alignment is
+*     to occur. See the StdOfRest attribute for a desription of the values
+*     which may be assigned to this attribute. The default AlignStdOfRest
+*     value is "Helio" (heliographic).
+*
+c     When astFindFrame or astConvert is used on two SpecFrames (potentially
+f     When AST_FindFrame or AST_CONVERT is used on two SpecFrames (potentially
+*     describing different spectral coordinate systems), it returns a Mapping
+*     which can be used to transform a position in one SpecFrame into the
+*     corresponding position in the other. The Mapping is made up of the
+*     following steps in the indicated order:
+*
+*     - Map values from the system used by the target (wavelength,
+*     apparent radial velocity, etc) to the system specified by the
+*     AlignSystem attribute, using the target's rest frequency if necessary.
+*
+*     - Map these values from the target's standard of rest to the standard of
+*     rest specified by the AlignStdOfRest attribute, using the Epoch, ObsLat,
+*     ObsLon, ObsAlt, RefDec and RefRA attributes of the target to define the
+*     two standards of rest.
+*
+*     - Map these values from the standard of rest specified by the
+*     AlignStdOfRest attribute, to the template's standard of rest, using the
+*     Epoch, ObsLat, ObsLon, ObsAlt, RefDec and RefRA attributes of the
+*     template to define the two standards of rest.
+*
+*     - Map these values from the system specified by the AlignSystem
+*     attribute, to the system used by the template, using the template's
+*     rest frequency if necessary.
+
+*  Applicability:
+*     SpecFrame
+*        All SpecFrames have this attribute.
+
+*att--
+*/
+/* The AlignStdOfRest value has a value of AST__BADSOR when not set yielding
+   a default of AST__HLSOR. */
+astMAKE_TEST(SpecFrame,AlignStdOfRest,( this->alignstdofrest != AST__BADSOR ))
+astMAKE_CLEAR(SpecFrame,AlignStdOfRest,alignstdofrest,AST__BADSOR)
+astMAKE_GET(SpecFrame,AlignStdOfRest,AstStdOfRestType,AST__BADSOR,(
+            ( this->alignstdofrest == AST__BADSOR ) ? AST__HLSOR : this->alignstdofrest ) )
+
+/* Validate the AlignStdOfRest value being set and report an error if necessary. */
+astMAKE_SET(SpecFrame,AlignStdOfRest,AstStdOfRestType,alignstdofrest,(
+            ( ( value >= FIRST_SOR ) && ( value <= LAST_SOR ) ) ?
+                 value :
+                 ( astError( AST__ATTIN, "%s(%s): Bad value (%d) "
+                             "given for AlignStdOfRest attribute.", status,
+                             "astSetAlignStdOfRest", astGetClass( this ), (int) value ),
+
+/* Leave the value unchanged on error. */
+                                            this->alignstdofrest ) ) )
+
+/*
+*att++
+*  Name:
+*     RefDec
+
+*  Purpose:
+*     The declination of the reference point
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies the FK5 J2000.0 declination of a reference
+*     point on the sky. See the description of attribute RefRA for details.
+*     The default RefDec is "0:0:0".
+
+*  Applicability:
+*     SpecFrame
+*        All SpecFrames have this attribute.
+
+*att--
+*/
+/* The reference declination (FK5 J2000, radians). Clear the RefDec value by
+   setting it to AST__BAD, which results in a default value of zero. Any
+   value is acceptable. */
+astMAKE_CLEAR(SpecFrame,RefDec,refdec,AST__BAD)
+astMAKE_GET(SpecFrame,RefDec,double,0.0,((this->refdec!=AST__BAD)?this->refdec:0.0))
+astMAKE_SET(SpecFrame,RefDec,double,refdec,value)
+astMAKE_TEST(SpecFrame,RefDec,( this->refdec != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     RefRA
+
+*  Purpose:
+*     The right ascension of the reference point
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute, together with the RefDec attribute, specifies the FK5
+*     J2000.0 coordinates of a reference point on the sky. For 1-dimensional
+*     spectra, this should normally be the position of the source. For
+*     spectral data with spatial coverage (spectral cubes, etc), this should
+*     be close to centre of the spatial coverage. It is used to define the
+*     correction for Doppler shift to be applied when using the
+c     astFindFrame or astConvert
+f     AST_FINDFRAME or AST_CONVERT
+*     method to convert between different standards of rest.
+*
+*     The SpecFrame class assumes this velocity correction is spatially
+*     invariant. If a single SpecFrame is used (for instance, as a
+*     component of a CmpFrame) to describe spectral values at different
+*     points on the sky, then it is assumes that the doppler shift at any
+*     spatial position is the same as at the reference position. The
+*     maximum velocity error introduced by this assumption is of the order
+*     of V*SIN(FOV), where FOV is the angular field of view, and V is the
+*     relative velocity of the two standards of rest. As an example, when
+*     correcting from the observers rest frame (i.e. the topocentric rest
+*     frame) to the kinematic local standard of rest the maximum value of V
+*     is about 20 km/s, so for 5 arc-minute field of view the maximum velocity
+*     error introduced by the correction will be about 0.03 km/s. As another
+*     example, the maximum error when correcting from the observers rest frame
+*     to the local group is about 5 km/s over a 1 degree field of view.
+*
+*     The RefRA and RefDec attributes are stored internally in radians, but
+*     are converted to and from a string for access. The format "hh:mm:ss.ss"
+*     is used for RefRA, and "dd:mm:ss.s" is used for RefDec. The methods
+c     astSetRefPos and astGetRefPos may be used to access the values of
+f     AST_SETREFPOS and AST_GETREFPOS may be used to access the value of
+*     these attributes directly as unformatted values in radians.
+*
+*     The default for RefRA is "0:0:0".
+
+*  Applicability:
+*     SpecFrame
+*        All SpecFrames have this attribute.
+
+*att--
+*/
+/* The reference right ascension (FK5 J2000, radians). Clear the RefRA value
+   by setting it to AST__BAD, which gives a default value of 0.0. Any
+   value is acceptable. */
+astMAKE_CLEAR(SpecFrame,RefRA,refra,AST__BAD)
+astMAKE_GET(SpecFrame,RefRA,double,0.0,((this->refra!=AST__BAD)?this->refra:0.0))
+astMAKE_SET(SpecFrame,RefRA,double,refra,value)
+astMAKE_TEST(SpecFrame,RefRA,( this->refra != AST__BAD ))
+
+
+/*
+*att++
+*  Name:
+*     RestFreq
+
+*  Purpose:
+*     The rest frequency.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute specifies the frequency corresponding to zero
+*     velocity. It is used when converting between between velocity-based
+*     coordinate systems and and other coordinate systems (such as frequency,
+*     wavelength, energy, etc). The default value is 1.0E5 GHz.
+*
+*     When setting a new value for this attribute, the new value can be
+*     supplied either directly as a frequency, or indirectly as a wavelength
+*     or energy, in which case the supplied value is converted to a frequency
+*     before being stored. The nature of the supplied value is indicated by
+*     appending text to the end of the numerical value indicating the units in
+*     which the value is supplied. If the units are not specified, then the
+*     supplied value is assumed to be a frequency in units of GHz. If the
+*     supplied unit is a unit of frequency, the supplied value is assumed to
+*     be a frequency in the given units. If the supplied unit is a unit of
+*     length, the supplied value is assumed to be a (vacuum) wavelength. If
+*     the supplied unit is a unit of energy, the supplied value is assumed to
+*     be an energy. For instance, the following strings all result in
+*     a rest frequency of around 1.4E14 Hz being used: "1.4E5", "1.4E14 Hz",
+*     "1.4E14 s**-1", "1.4E5 GHz", "2.14E-6 m", "21400 Angstrom", "9.28E-20 J",
+*     "9.28E-13 erg", "0.58 eV", etc.
+*
+*     When getting the value of this attribute, the returned value is
+*     always a frequency in units of GHz.
+
+*  Applicability:
+*     SpecFrame
+*        All SpecFrames have this attribute.
+
+*att--
+*/
+/* The rest frequency (Hz). Clear the RestFreq value by setting it to AST__BAD,
+   which gives 1.0E14 as the default value. Any value is acceptable. */
+astMAKE_CLEAR(SpecFrame,RestFreq,restfreq,AST__BAD)
+astMAKE_GET(SpecFrame,RestFreq,double,1.0E14,((this->restfreq!=AST__BAD)?this->restfreq:1.0E14))
+astMAKE_SET(SpecFrame,RestFreq,double,restfreq,value)
+astMAKE_TEST(SpecFrame,RestFreq,( this->restfreq != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     SourceVel
+
+*  Purpose:
+*     The source velocity.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute (together with SourceSys, SourceVRF, RefRA and RefDec)
+*     defines the "Source" standard of rest (see attribute StdOfRest). This is
+*     a rest frame which is moving towards the position given by RefRA and
+*     RefDec at a  velocity given by SourceVel. A positive value means
+*     the source is moving away from the observer. When a new value is
+*     assigned to this attribute, the supplied value is assumed to refer
+*     to the spectral system specified by the SourceSys attribute. For
+*     instance, the SourceVel value may be supplied as a radio velocity, a
+*     redshift, a beta factor, etc. Similarly, when the current value of
+*     the SourceVel attribute is obtained, the returned value will refer
+*     to the spectral system specified by the SourceSys value. If the
+*     SourceSys value is changed, any value previously stored for the SourceVel
+*     attribute will be changed automatically from the old spectral system
+*     to the new spectral system.
+*
+*     When setting a value for SourceVel, the value should be supplied in the
+*     rest frame specified by the SourceVRF attribute. Likewise, when getting
+*     the value of SourceVel, it will be returned in the rest frame specified
+*     by the SourceVRF attribute.
+*
+*     The default SourceVel value is zero.
+
+*  Applicability:
+*     SpecFrame
+*        All SpecFrames have this attribute.
+
+*  Notes:
+*     - It is important to set an appropriate value for SourceVRF and
+*     SourceSys before setting a value for SourceVel. If a new value is later
+*     set for SourceVRF or SourceSys, the value stored for SourceVel will
+*     simultaneously be changed to the new standard of rest or spectral
+*     system.
+
+*att--
+*/
+/* The source velocity (velocities are stored internally in m/s). Clear it
+   by setting it to AST__BAD, which returns a default value of zero. Any
+   value is acceptable. */
+astMAKE_CLEAR(SpecFrame,SourceVel,sourcevel,AST__BAD)
+astMAKE_SET(SpecFrame,SourceVel,double,sourcevel,value)
+astMAKE_TEST(SpecFrame,SourceVel,( this->sourcevel != AST__BAD ))
+astMAKE_GET(SpecFrame,SourceVel,double,0.0,((this->sourcevel!=AST__BAD)?this->sourcevel:0.0))
+
+/*
+*att++
+*  Name:
+*     SourceVRF
+
+*  Purpose:
+*     Rest frame in which the source velocity is stored.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute identifies the rest frame in which the source
+*     velocity or redshift is stored (the source velocity or redshift is
+*     accessed using attribute SourceVel). When setting a new value for the
+*     SourceVel attribute, the source velocity or redshift should be supplied
+*     in the rest frame indicated by this attribute. Likewise, when getting
+*     the value of the SourceVel attribute, the velocity or redshift will be
+*     returned in this rest frame.
+*
+*     If the value of SourceVRF is changed, the value stored for SourceVel
+*     will be converted from the old to the new rest frame.
+*
+*     The values which can be supplied are the same as for the StdOfRest
+*     attribute (except that SourceVRF cannot be set to "Source"). The
+*     default value is "Helio".
+
+*  Applicability:
+*     SpecFrame
+*        All SpecFrames have this attribute.
+
+*att--
+*/
+/* The SourceVRF value has a value of AST__BADSOR when not set yielding
+   a default of AST__HLSOR. */
+astMAKE_TEST(SpecFrame,SourceVRF,( this->sourcevrf != AST__BADSOR ))
+astMAKE_GET(SpecFrame,SourceVRF,AstStdOfRestType,AST__BADSOR,(
+            ( this->sourcevrf == AST__BADSOR ) ? AST__HLSOR : this->sourcevrf ) )
+
+/* When clearing SourceVRF, convert the SourceVel value to heliocentric
+  (but only if set)*/
+astMAKE_CLEAR(SpecFrame,SourceVRF,sourcevrf,((astTestSourceVel( this )?
+             (void)(astSetSourceVel( this, ConvertSourceVel( this, AST__HLSOR,
+                    astGetSourceSys( this ), status ) ),NULL):
+             NULL),AST__BADSOR))
+
+/* Validate the SourceVRF value being set and report an error if necessary.
+   If OK, convert the stored SourceVel value into the new rest frame (but
+only if set)*/
+astMAKE_SET(SpecFrame,SourceVRF,AstStdOfRestType,sourcevrf,(
+            ( ( value >= FIRST_SOR ) && ( value <= LAST_SOR ) && value != AST__SCSOR ) ?
+                 (astTestSourceVel( this )?
+                 (void)(astSetSourceVel( this,ConvertSourceVel(this,value,astGetSourceSys( this ), status )),NULL):
+                 NULL), value:( astError( AST__ATTIN, "%s(%s): Bad value (%d) "
+                             "given for SourceVRF attribute.", status,
+                             "astSetSourceVRF", astGetClass( this ), (int) value ),
+
+/* Leave the value unchanged on error. */
+                                            this->sourcevrf ) ) )
+
+/*
+*att++
+*  Name:
+*     SourceSys
+
+*  Purpose:
+*     Spectral system in which the source velocity is stored.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute identifies the spectral system in which the
+*     SourceVel attribute value (the source velocity) is supplied and
+*     returned. It can be one of the following:
+*
+*        - "VRAD" or "VRADIO": Radio velocity (km/s)
+*        - "VOPT" or "VOPTICAL": Optical velocity (km/s)
+*        - "ZOPT" or "REDSHIFT": Redshift (dimensionless)
+*        - "BETA": Beta factor (dimensionless)
+*        - "VELO" or "VREL": Apparent radial ("relativistic") velocity (km/s)
+*
+*     When setting a new value for the SourceVel attribute, the source
+*     velocity should be supplied in the spectral system indicated
+*     by this attribute. Likewise, when getting the value of the SourceVel
+*     attribute, the velocity will be returned in this spectral system.
+*
+*     If the value of SourceSys is changed, the value stored for SourceVel
+*     will be converted from the old to the new spectral systems.
+*
+*     The default value is "VELO" (apparent radial velocity).
+
+*  Applicability:
+*     SpecFrame
+*        All SpecFrames have this attribute.
+
+*att--
+*/
+/* The SourceSys value has a value of AST__BADSYS when not set yielding
+   a default of AST__VREL. */
+astMAKE_TEST(SpecFrame,SourceSys,( this->sourcesys != AST__BADSYSTEM ))
+astMAKE_GET(SpecFrame,SourceSys,AstSystemType,AST__BADSYSTEM,(
+            ( this->sourcesys == AST__BADSYSTEM ) ? AST__VREL : this->sourcesys ) )
+
+/* When clearing SourceSys, convert the SourceVel value to relativistic
+   velocity (but only if set) */
+astMAKE_CLEAR(SpecFrame,SourceSys,sourcesys,((astTestSourceVel( this )?
+(void)(astSetSourceVel( this, ConvertSourceVel( this, astGetSourceVRF( this ),
+                                         AST__VREL, status ) ),NULL):NULL),AST__BADSYSTEM))
+
+/* Validate the SourceSys value being set and report an error if necessary.
+   If OK, convert the stored SourceVel value into the new rest frame (but
+   only if set)*/
+astMAKE_SET(SpecFrame,SourceSys,AstSystemType,sourcesys,(
+            ( ( value == AST__VREL ) || ( value == AST__BETA ) ||
+              ( value == AST__VRADIO ) || ( value == AST__REDSHIFT ) ||
+              ( value == AST__VOPTICAL ) ) ?
+              (astTestSourceVel( this )?
+               (void)(astSetSourceVel( this, ConvertSourceVel( this, astGetSourceVRF( this ),
+                                                        value, status )),NULL):NULL),
+                                                        value:
+                 ( astError( AST__ATTIN, "%s(%s): Bad value (%d) "
+                             "given for SourceSys attribute.", status,
+                             "astSetSourceSys", astGetClass( this ), (int) value ),
+
+/* Leave the value unchanged on error. */
+                                            this->sourcesys ) ) )
+
+/*
+*att++
+*  Name:
+*     StdOfRest
+
+*  Purpose:
+*     Standard of rest.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute identifies the standard of rest to which the spectral
+*     axis values of a SpecFrame refer, and may take any of the values
+*     listed in the "Standards of Rest" section (below).
+*
+*     The default StdOfRest value is "Helio".
+
+*  Applicability:
+*     SpecFrame
+*        All SpecFrames have this attribute.
+
+*  Standards of Rest:
+*     The SpecFrame class supports the following StdOfRest values (all are
+*     case-insensitive):
+*
+*     - "Topocentric", "Topocent" or "Topo": The observers rest-frame (assumed
+*     to be on the surface of the earth). Spectra recorded in this standard of
+*     rest suffer a Doppler shift which varies over the course of a day
+*     because of the rotation of the observer around the axis of the earth.
+*     This standard of rest must be qualified using the ObsLat, ObsLon,
+*     ObsAlt, Epoch, RefRA and RefDec attributes.
+*
+*     - "Geocentric", "Geocentr" or "Geo": The rest-frame of the earth centre.
+*     Spectra recorded in this standard of rest suffer a Doppler shift which
+*     varies over the course of a year because of the rotation of the earth
+*     around the Sun. This standard of rest must be qualified using the Epoch,
+*     RefRA and RefDec attributes.
+*
+*     - "Barycentric", "Barycent" or "Bary": The rest-frame of the solar-system
+*     barycentre. Spectra recorded in this standard of rest suffer a Doppler
+*     shift which depends both on the velocity of the Sun through the Local
+*     Standard of Rest, and on the movement of the planets through the solar
+*     system. This standard of rest must be qualified using the Epoch, RefRA
+*     and RefDec attributes.
+*
+*     - "Heliocentric", "Heliocen" or "Helio": The rest-frame of the Sun.
+*     Spectra recorded in this standard of rest suffer a Doppler shift which
+*     depends on the velocity of the Sun through the Local Standard of Rest.
+*     This standard of rest must be qualified using the RefRA and RefDec
+*     attributes.
+*
+*     - "LSRK", "LSR": The rest-frame of the kinematical Local Standard of
+*     Rest. Spectra recorded in this standard of rest suffer a Doppler shift
+*     which depends on the velocity of the kinematical Local Standard of Rest
+*     through the galaxy. This standard of rest must be qualified using the
+*     RefRA and RefDec attributes.
+*
+*     - "LSRD": The rest-frame of the dynamical Local Standard of Rest. Spectra
+*     recorded in this standard of rest suffer a Doppler shift which depends
+*     on the velocity of the dynamical Local Standard of Rest through the
+*     galaxy.  This standard of rest must be qualified using the RefRA and
+*     RefDec attributes.
+*
+*     - "Galactic", "Galactoc" or "Gal": The rest-frame of the galactic centre.
+*     Spectra recorded in this standard of rest suffer a Doppler shift which
+*     depends on the velocity of the galactic centre through the local group.
+*     This standard of rest must be qualified using the RefRA and RefDec
+*     attributes.
+*
+*     - "Local_group", "Localgrp" or "LG": The rest-frame of the local group.
+*     This standard of rest must be qualified using the RefRA and RefDec
+*     attributes.
+*
+*     - "Source", or "src": The rest-frame of the source. This standard of
+*     rest must be qualified using the RefRA, RefDec and SourceVel attributes.
+*
+*     Where more than one alternative System value is shown above, the
+*     first of these will be returned when an enquiry is made.
+*att--
+*/
+/* The StdOfRest value has a value of AST__BADSOR when not set yielding
+   a default of AST__HLSOR. */
+astMAKE_TEST(SpecFrame,StdOfRest,( this->stdofrest != AST__BADSOR ))
+astMAKE_GET(SpecFrame,StdOfRest,AstStdOfRestType,AST__BADSOR,(
+            ( this->stdofrest == AST__BADSOR ) ? AST__HLSOR : this->stdofrest ) )
+
+/*
+*att++
+*  Name:
+*     SpecOrigin
+
+*  Purpose:
+*     The zero point for SpecFrame axis values
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This specifies the origin from which all spectral values are measured.
+*     The default value (zero) results in the SpecFrame describing
+*     absolute spectral values in the system given by the System attribute
+*     (e.g. frequency, velocity, etc). If a SpecFrame is to be used to
+*     describe offset from some origin, the SpecOrigin attribute
+*     should be set to hold the required origin value. The SpecOrigin value
+*     stored inside the SpecFrame structure is modified whenever SpecFrame
+*     attribute values are changed so that it refers to the original spectral
+*     position.
+*
+*     When setting a new value for this attribute, the supplied value is assumed
+*     to be in the system, units and standard of rest described by the SpecFrame.
+*     Likewise, when getting the value of this attribute, the value is returned
+*     in the system, units and standard of rest described by the SpecFrame. If
+*     any of these attributes are changed, then any previously stored SpecOrigin
+*     value will also be changed so that refers to the new system, units or
+*     standard of rest.
+
+*  Applicability:
+*     SpecFrame
+*        All SpecFrames have this attribute.
+
+*att--
+*/
+/* The spec origin, stored internally in the default units associated
+   with the current System value. Clear the SpecOrigin value  by setting it
+   to AST__BAD, which gives 0.0 as the default value. Any value is acceptable. */
+astMAKE_CLEAR(SpecFrame,SpecOrigin,specorigin,AST__BAD)
+astMAKE_GET(SpecFrame,SpecOrigin,double,0.0,((this->specorigin!=AST__BAD)?this->specorigin:0.0))
+astMAKE_SET(SpecFrame,SpecOrigin,double,specorigin,value)
+astMAKE_TEST(SpecFrame,SpecOrigin,( this->specorigin != AST__BAD ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for SpecFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for SpecFrame objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *in;             /* Pointer to input SpecFrame */
+   AstSpecFrame *out;            /* Pointer to output SpecFrame */
+   char *usedunit;               /* Pointer to an element of usedunits array */
+   int i;                        /* Loop count */
+   int nused;                    /* Size of "usedunits" array */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output SpecFrames. */
+   in = (AstSpecFrame *) objin;
+   out = (AstSpecFrame *) objout;
+
+/* Nullify the pointers stored in the output object since these will
+   currently be pointing at the input data (since the output is a simple
+   byte-for-byte copy of the input). Otherwise, the input data could be
+   freed by accidient if the output object is deleted due to an error
+   occuring in this function. */
+   out->usedunits = NULL;
+
+/* Store the last used units in the output SpecMap. */
+   if( in && in->usedunits ) {
+      nused = in->nuunits;
+      out->usedunits = astMalloc( nused*sizeof( char * ) );
+      if( out->usedunits ) {
+         for( i = 0; i < nused; i++ ) {
+            usedunit = in->usedunits[ i ];
+            if( usedunit ) {
+               out->usedunits[ i ] = astStore( NULL, usedunit,
+                                               strlen( usedunit ) + 1 );
+            } else {
+               out->usedunits[ i ] = NULL;
+            }
+         }
+      }
+   }
+
+/* If an error has occurred, free the output resources. */
+   if( !astOK ) Delete( (AstObject *) out, status );
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for SpecFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for SpecFrame objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;
+   int i;
+
+/* Release the memory referred to in the SpecFrame structure. */
+   this = (AstSpecFrame *) obj;
+   if( this && this->usedunits ) {
+      for( i = 0; i < this->nuunits; i++ ) {
+         this->usedunits[ i ] = astFree( this->usedunits[ i ] );
+      }
+      this->usedunits = astFree( this->usedunits );
+   }
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for SpecFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the SpecFrame class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecFrame whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSpecFrame *this;           /* Pointer to the SpecFrame structure */
+   AstStdOfRestType sor;         /* StdOfRest attribute value */
+   AstSystemType sys;            /* Spectral system value */
+   char buff[ 20 ];              /* Buffer for item name */
+   char comm[ 50 ];              /* Buffer for comment */
+   const char *sval;             /* Pointer to string value */
+   double dval;                  /* Double value */
+   int i;                        /* Loop count */
+   int ival;                     /* int value */
+   int j;                        /* Loop count */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SpecFrame structure. */
+   this = (AstSpecFrame *) this_object;
+
+/* Write out values representing the instance variables for the
+   SpecFrame class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* StdOfRest. */
+/* ---------- */
+   set = TestStdOfRest( this, status );
+   sor = set ? GetStdOfRest( this, status ) : astGetStdOfRest( this );
+
+/* If set, convert explicitly to a string for the external
+   representation. */
+   sval = "";
+   if ( set ) {
+      if ( astOK ) {
+         sval = StdOfRestString( sor, status );
+
+/* Report an error if the StdOfRest value was not recognised. */
+         if ( !sval ) {
+            astError( AST__SCSIN,
+                     "%s(%s): Corrupt %s contains invalid standard of rest "
+                     "identification code (%d).", status, "astWrite",
+                     astGetClass( channel ), astGetClass( this ), (int) sor );
+         }
+      }
+
+/* If not set, use astGetAttrib which returns a string value using
+   (possibly over-ridden) methods. */
+   } else {
+      sval = astGetAttrib( this_object, "stdofrest" );
+   }
+
+/* Write out the value. */
+   astWriteString( channel, "SoR", set, 1, sval, "Standard of rest" );
+
+/* AlignStdOfRest. */
+/* --------------- */
+   set = TestAlignStdOfRest( this, status );
+   sor = set ? GetAlignStdOfRest( this, status ) : astGetAlignStdOfRest( this );
+
+/* If set, convert explicitly to a string for the external representation. */
+   if ( set ) {
+      if ( astOK ) {
+         sval = StdOfRestString( sor, status );
+
+/* Report an error if the StdOfRest value was not recognised. */
+         if ( !sval ) {
+            astError( AST__SCSIN,
+                     "%s(%s): Corrupt %s contains invalid alignment standard "
+                     "of rest identification code (%d).", status, "astWrite",
+                     astGetClass( channel ), astGetClass( this ), (int) sor );
+         }
+      }
+
+/* If not set, use astGetAttrib which returns a string value using
+   (possibly over-ridden) methods. */
+   } else {
+      sval = astGetAttrib( this_object, "alignstdofrest" );
+   }
+
+/* Write out the value. */
+   astWriteString( channel, "AlSoR", set, 0, sval, "Alignment standard of rest" );
+
+/* RefRA. */
+/* ------ */
+   set = TestRefRA( this, status );
+   dval = set ? GetRefRA( this, status ) : astGetRefRA( this );
+   astWriteDouble( channel, "RefRA", set, 0, dval, "Reference RA (rads, FK5 J2000)" );
+
+/* RefDec. */
+/* ------- */
+   set = TestRefDec( this, status );
+   dval = set ? GetRefDec( this, status ) : astGetRefDec( this );
+   astWriteDouble( channel, "RefDec", set, 0, dval, "Reference Dec (rads, FK5 J2000)" );
+
+/* RestFreq. */
+/* --------- */
+   set = TestRestFreq( this, status );
+   dval = set ? GetRestFreq( this, status ) : astGetRestFreq( this );
+   astWriteDouble( channel, "RstFrq", set, 0, dval, "Rest frequency (Hz)" );
+
+/* SourceVel. */
+/* ---------- */
+   set = TestSourceVel( this, status );
+   dval = set ? GetSourceVel( this, status ) : astGetSourceVel( this );
+   astWriteDouble( channel, "SrcVel", set, 0, dval, "Source velocity (m/s)" );
+
+/* SourceVRF. */
+/* ---------- */
+   set = TestSourceVRF( this, status );
+   sor = set ? GetSourceVRF( this, status ) : astGetSourceVRF( this );
+
+/* If set, convert explicitly to a string for the external representation. */
+   if ( set ) {
+      if ( astOK ) {
+         sval = StdOfRestString( sor, status );
+
+/* Report an error if the value was not recognised. */
+         if ( !sval ) {
+            astError( AST__SCSIN,
+                     "%s(%s): Corrupt %s contains invalid source velocity "
+                     "rest frame identification code (%d).", status, "astWrite",
+                     astGetClass( channel ), astGetClass( this ), (int) sor );
+         }
+      }
+
+/* If not set, use astGetAttrib which returns a string value using
+   (possibly over-ridden) methods. */
+   } else {
+      sval = astGetAttrib( this_object, "sourcevrf" );
+   }
+
+/* Write out the value. */
+   astWriteString( channel, "SrcVRF", set, 0, sval, "Source velocity rest frame" );
+
+/* SourceSys. */
+/* ---------- */
+   set = TestSourceSys( this, status );
+   sys = set ? GetSourceSys( this, status ) : astGetSourceSys( this );
+
+/* If set, convert explicitly to a string for the external representation. */
+   if ( set ) {
+      if ( astOK ) {
+         sval = SystemString( (AstFrame *) this, sys, status );
+
+/* Report an error if the value was not recognised. */
+         if ( !sval ) {
+            astError( AST__SCSIN,
+                     "%s(%s): Corrupt %s contains invalid source velocity "
+                     "spectral system identification code (%d).", status, "astWrite",
+                     astGetClass( channel ), astGetClass( this ), (int) sys );
+         }
+      }
+
+/* If not set, use astGetAttrib which returns a string value using
+   (possibly over-ridden) methods. */
+   } else {
+      sval = astGetAttrib( this_object, "sourcesys" );
+   }
+
+/* Write out the value. */
+   astWriteString( channel, "SrcSys", set, 0, sval, "Source velocity spectral system" );
+
+/* AlignSpecOffset. */
+/* ---------------- */
+   set = TestAlignSpecOffset( this, status );
+   ival = set ? GetAlignSpecOffset( this, status ) : astGetAlignSpecOffset( this );
+   astWriteInt( channel, "AlSpOf", set, 0, ival,
+                ival ? "Align in offset coords" :
+                       "Align in system coords" );
+
+/* UsedUnits */
+/* --------- */
+   if( this->usedunits ) {
+      for( i = 0; i < this->nuunits; i++ ) {
+         if( this->usedunits[ i ] ) {
+            sprintf( buff, "U%s", astSystemString( this, (AstSystemType) i ));
+            for( j = 2; j < strlen( buff ); j++ ) buff[ j ] = tolower( buff[ j ] );
+            sprintf( comm, "Preferred units for %s", SystemLabel( (AstSystemType) i, status ) );
+            astWriteString( channel, buff, 1, 0, this->usedunits[ i ], comm );
+         }
+      }
+   }
+
+/* SpecOrigin. */
+/* ----------- */
+   set = TestSpecOrigin( this, status );
+   dval = set ? GetSpecOrigin( this, status ) : astGetSpecOrigin( this );
+   if( dval != AST__BAD ) {
+      astWriteDouble( channel, "SpOrg", set, 0, dval, "Spec offset" );
+   }
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsASpecFrame and astCheckSpecFrame functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(SpecFrame,Frame)
+astMAKE_CHECK(SpecFrame)
+
+AstSpecFrame *astSpecFrame_( const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astSpecFrame
+
+*  Purpose:
+*     Create a SpecFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     AstSpecFrame *astSpecFrame( const char *options, int *status, ... )
+
+*  Class Membership:
+*     SpecFrame constructor.
+
+*  Description:
+*     This function creates a new SpecFrame and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new SpecFrame. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new SpecFrame.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic SpecFrame constructor which
+*     is available via the protected interface to the SpecFrame class.
+*     A public interface is provided by the astSpecFrameId_ function.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMapping *um;               /* Mapping from default to actual units */
+   AstSpecFrame *new;            /* Pointer to new SpecFrame */
+   AstSystemType s;              /* System */
+   const char *u;                /* Units string */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SpecFrame, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitSpecFrame( NULL, sizeof( AstSpecFrame ), !class_init,
+                           &class_vtab, "SpecFrame" );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new SpecFrame's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* Check the Units are appropriate for the System. */
+      u = astGetUnit( new, 0 );
+      s = astGetSystem( new );
+      um = astUnitMapper( DefUnit( s, "astSpecFrame", "SpecFrame", status ),
+                          u, NULL, NULL );
+      if( um ) {
+         um = astAnnul( um );
+      } else {
+         astError( AST__BADUN, "astSpecFrame: Inappropriate units (%s) "
+                   "specified for a %s axis.", status, u, SystemLabel( s, status ) );
+      }
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new SpecFrame. */
+   return new;
+}
+
+AstSpecFrame *astInitSpecFrame_( void *mem, size_t size, int init,
+                                 AstSpecFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSpecFrame
+
+*  Purpose:
+*     Initialise a SpecFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     AstSpecFrame *astInitSpecFrame( void *mem, size_t size, int init,
+*                                     AstFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SpecFrame initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new SpecFrame object. It allocates memory (if
+*     necessary) to accommodate the SpecFrame plus any additional data
+*     associated with the derived class. It then initialises a
+*     SpecFrame structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual function
+*     table for a SpecFrame at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the SpecFrame is to be
+*	created. This must be of sufficient size to accommodate the
+*	SpecFrame data (sizeof(SpecFrame)) plus any data used by
+*	the derived class. If a value of NULL is given, this function
+*	will allocate the memory itself using the "size" parameter to
+*	determine its size.
+*     size
+*        The amount of memory used by the SpecFrame (plus derived
+*	class data). This will be used to allocate memory if a value of
+*	NULL is given for the "mem" parameter. This value is also stored
+*	in the SpecFrame structure, so a valid value must be supplied
+*	even if not required for allocating memory.
+*     init
+*        A logical flag indicating if the SpecFrame's virtual function
+*	table is to be initialised. If this value is non-zero, the
+*	virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*	associated with the new SpecFrame.
+*     name
+*        Pointer to a constant null-terminated character string which
+*	contains the name of the class to which the new object belongs
+*	(it is this pointer value that will subsequently be returned by
+*	the astGetClass method).
+
+*  Returned Value:
+*     A pointer to the new SpecFrame.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSpecFrame *new;        /* Pointer to the new SpecFrame */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitSpecFrameVtab( vtab, name );
+
+/* Initialise a 1D Frame structure (the parent class) as the first component
+   within the SpecFrame structure, allocating memory if necessary. */
+   new = (AstSpecFrame *) astInitFrame( mem, size, 0,
+                                        (AstFrameVtab *) vtab, name, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the SpecFrame data. */
+/* ----------------------------- */
+/* Initialise all attributes to their "undefined" values. */
+      new->alignstdofrest = AST__BADSOR;
+      new->refdec = AST__BAD;
+      new->refra = AST__BAD;
+      new->restfreq = AST__BAD;
+      new->sourcevel = AST__BAD;
+      new->sourcevrf = AST__BADSOR;
+      new->sourcesys = AST__BADSYSTEM;
+      new->stdofrest = AST__BADSOR;
+      new->nuunits = 0;
+      new->usedunits = NULL;
+      new->specorigin = AST__BAD;
+      new->alignspecoffset = -INT_MAX;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstSpecFrame *astLoadSpecFrame_( void *mem, size_t size,
+                                 AstSpecFrameVtab *vtab,
+                                 const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadSpecFrame
+
+*  Purpose:
+*     Load a SpecFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specframe.h"
+*     AstSpecFrame *astLoadSpecFrame( void *mem, size_t size,
+*                                      AstSpecFrameVtab *vtab,
+*                                      const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     SpecFrame loader.
+
+*  Description:
+*     This function is provided to load a new SpecFrame using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     SpecFrame structure in this memory, using data read from the
+*     input Channel.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the SpecFrame is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        SpecFrame data (sizeof(SpecFrame)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the SpecFrame (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the SpecFrame structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstSpecFrame) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new SpecFrame. If this is NULL, a pointer
+*        to the (static) virtual function table for the SpecFrame class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "SpecFrame" is used instead.
+
+*  Returned Value:
+*     A pointer to the new SpecFrame.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSpecFrame *new;            /* Pointer to the new SpecFrame */
+   char buff[ 20 ];              /* Buffer for item name */
+   char *sval;                   /* Pointer to string value */
+   double obslat;                /* Value for ObsLat attribute */
+   double obslon;                /* Get a pointer to the thread specific global data structure. */
+
+/* Value for ObsLon attribute */
+   int i;                        /* Loop count */
+   int j;                        /* Loop count */
+   int nc;                       /* String length */
+   int sys;                      /* System value */
+
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this SpecFrame. In this case the
+   SpecFrame belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstSpecFrame );
+      vtab = &class_vtab;
+      name = "SpecFrame";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitSpecFrameVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built SpecFrame. */
+   new = astLoadFrame( mem, size, (AstFrameVtab *) vtab, name,
+                       channel );
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+       astReadClassData( channel, "SpecFrame" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* StdOfRest. */
+/* ---------- */
+/* Set the default and read the external representation as a string. */
+       new->stdofrest = AST__BADSOR;
+       sval = astReadString( channel, "sor", NULL );
+
+/* If a value was read, convert from a string to a StdOfRest code. */
+       if ( sval ) {
+          if ( astOK ) {
+             new->stdofrest = StdOfRestCode( sval, status );
+
+/* Report an error if the value wasn't recognised. */
+             if ( new->stdofrest == AST__BADSOR ) {
+                astError( AST__ATTIN,
+                          "astRead(%s): Invalid standard of rest description "
+                          "\"%s\".", status, astGetClass( channel ), sval );
+             }
+          }
+
+/* Free the string value. */
+          sval = astFree( sval );
+       }
+
+/* AlignStdOfRest. */
+/* --------------- */
+/* Set the default and read the external representation as a string. */
+       new->alignstdofrest = AST__BADSOR;
+       sval = astReadString( channel, "alsor", NULL );
+
+/* If a value was read, convert from a string to a StdOfRest code. */
+       if ( sval ) {
+          if ( astOK ) {
+             new->alignstdofrest = StdOfRestCode( sval, status );
+
+/* Report an error if the value wasn't recognised. */
+             if ( new->alignstdofrest == AST__BADSOR ) {
+                astError( AST__ATTIN,
+                          "astRead(%s): Invalid alignment standard of rest "
+                          "description \"%s\".", status, astGetClass( channel ), sval );
+             }
+          }
+
+/* Free the string value. */
+          sval = astFree( sval );
+       }
+
+/* GeoLat. */
+/* ------- */
+/* Retained for backward compatibility with older versions of AST in
+   which SpecFrame had a GeoLat attribute (now ObsLat is used instead). */
+      if( !astTestObsLat( new ) ) {
+         obslat = astReadDouble( channel, "geolat", AST__BAD );
+         if ( obslat != AST__BAD ) astSetObsLat( new, obslat );
+      }
+
+/* GeoLon. */
+/* ------- */
+/* Retained for backward compatibility with older versions of AST in
+   which SpecFrame had a GeoLon attribute (now ObsLon is used instead). */
+      if( !astTestObsLon( new ) ) {
+         obslon = astReadDouble( channel, "geolon", AST__BAD );
+         if ( obslon != AST__BAD ) astSetObsLon( new, obslon );
+      }
+
+/* RefRA. */
+/* ------ */
+      new->refra = astReadDouble( channel, "refra", AST__BAD );
+      if ( TestRefRA( new, status ) ) SetRefRA( new, new->refra, status );
+
+/* RefDec. */
+/* ------- */
+      new->refdec = astReadDouble( channel, "refdec", AST__BAD );
+      if ( TestRefDec( new, status ) ) SetRefDec( new, new->refdec, status );
+
+/* RestFreq. */
+/* --------- */
+      new->restfreq = astReadDouble( channel, "rstfrq", AST__BAD );
+      if ( TestRestFreq( new, status ) ) SetRestFreq( new, new->restfreq, status );
+
+/* AlignSpecOffset */
+/* --------------- */
+      new->alignspecoffset = astReadInt( channel, "alspof", -INT_MAX );
+      if ( TestAlignSpecOffset( new, status ) ) SetAlignSpecOffset( new, new->alignspecoffset, status );
+
+/* SourceVel. */
+/* ---------- */
+      new->sourcevel = astReadDouble( channel, "srcvel", AST__BAD );
+      if ( TestSourceVel( new, status ) ) SetSourceVel( new, new->sourcevel, status );
+
+/* SourceVRF */
+/* --------- */
+/* Set the default and read the external representation as a string. */
+       new->sourcevrf = AST__BADSOR;
+       sval = astReadString( channel, "srcvrf", NULL );
+
+/* If a value was read, convert from a string to a StdOfRest code. */
+       if ( sval ) {
+          if ( astOK ) {
+             new->sourcevrf = StdOfRestCode( sval, status );
+
+/* Report an error if the value wasn't recognised. */
+             if ( new->sourcevrf == AST__BADSOR ) {
+                astError( AST__ATTIN,
+                          "astRead(%s): Invalid source velocity rest frame "
+                          "description \"%s\".", status, astGetClass( channel ), sval );
+             }
+          }
+
+/* Free the string value. */
+          sval = astFree( sval );
+       }
+
+/* SourceSys */
+/* --------- */
+/* Set the default and read the external representation as a string. */
+       new->sourcesys = AST__BADSYSTEM;
+       sval = astReadString( channel, "srcsys", NULL );
+
+/* If a value was read, convert from a string to a System code. */
+       if ( sval ) {
+          if ( astOK ) {
+             new->sourcesys = SystemCode( (AstFrame *) new, sval, status );
+
+/* Report an error if the value wasn't recognised. */
+             if ( new->sourcesys == AST__BADSYSTEM ) {
+                astError( AST__ATTIN,
+                          "astRead(%s): Invalid source velocity spectral system "
+                          "description \"%s\".", status, astGetClass( channel ), sval );
+             }
+          }
+
+/* Free the string value. */
+          sval = astFree( sval );
+       }
+
+/* UsedUnits */
+/* --------- */
+      new->nuunits = 0;
+      new->usedunits = NULL;
+      for( sys = FIRST_SYSTEM; sys <= LAST_SYSTEM; sys++ ) {
+         nc = sprintf( buff, "u%s", astSystemString( new, (AstSystemType) sys ));
+         for( j = 0; j < nc; j++ ) buff[ j ] = tolower( buff[ j ] );
+         sval = astReadString( channel, buff, NULL );
+         if( sval ) {
+            if( (int) sys >= new->nuunits ) {
+               new->usedunits = astGrow( new->usedunits, sys + 1,
+                                          sizeof(char *) );
+               if( astOK ) {
+                  for( i = new->nuunits; i < sys + 1; i++ ) new->usedunits[ i ] = NULL;
+                  new->nuunits = sys + 1;
+               }
+            } else {
+               new->usedunits[ sys ] = astFree( new->usedunits[ sys ] );
+            }
+            if( astOK ) {
+               new->usedunits[ sys ] = astStore( new->usedunits[ sys ],
+                                                 sval, strlen( sval ) + 1 );
+            }
+            sval = astFree( sval);
+         }
+      }
+
+/* SpecOrigin. */
+/* --------- */
+      new->specorigin = astReadDouble( channel, "sporg", AST__BAD );
+      if ( TestSpecOrigin( new, status ) ) SetSpecOrigin( new, new->specorigin, status );
+
+
+/* If an error occurred, clean up by deleting the new SpecFrame. */
+       if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new SpecFrame pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+void astGetRefPos_( AstSpecFrame *this, AstSkyFrame *frm, double *lon,
+                    double *lat, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,SpecFrame,GetRefPos))(this,frm,lon,lat, status );
+}
+void astSetRefPos_( AstSpecFrame *this, AstSkyFrame *frm, double lon,
+                    double lat, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,SpecFrame,SetRefPos))(this,frm,lon,lat, status );
+}
+
+void astSetStdOfRest_( AstSpecFrame *this, AstStdOfRestType value, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,SpecFrame,SetStdOfRest))(this,value, status );
+}
+
+void astClearStdOfRest_( AstSpecFrame *this, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,SpecFrame,ClearStdOfRest))(this, status );
+}
+
+
+
+/* Special public interface functions. */
+/* =================================== */
+/* These provide the public interface to certain special functions
+   whose public interface cannot be handled using macros (such as
+   astINVOKE) alone. In general, they are named after the
+   corresponding protected version of the function, but with "Id"
+   appended to the name. */
+
+/* Public Interface Function Prototypes. */
+/* ------------------------------------- */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstSpecFrame *astSpecFrameId_( const char *, ... );
+
+/* Special interface function implementations. */
+/* ------------------------------------------- */
+AstSpecFrame *astSpecFrameId_( const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astSpecFrame
+f     AST_SPECFRAME
+
+*  Purpose:
+*     Create a SpecFrame.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "specframe.h"
+c     AstSpecFrame *astSpecFrame( const char *options, ... )
+f     RESULT = AST_SPECFRAME( OPTIONS, STATUS )
+
+*  Class Membership:
+*     SpecFrame constructor.
+
+*  Description:
+*     This function creates a new SpecFrame and optionally initialises
+*     its attributes.
+*
+*     A SpecFrame is a specialised form of one-dimensional Frame which
+*     represents various coordinate systems used to describe positions within
+*     an electro-magnetic spectrum. The particular coordinate system to be
+*     used is specified by setting the SpecFrame's System attribute (the
+*     default is wavelength) qualified, as necessary, by other attributes
+*     such as the rest frequency, the standard of rest, the epoch of
+*     observation, etc (see the description of the System attribute for
+*     details).
+*
+*     By setting a value for thr SpecOrigin attribute, a SpecFrame can be made
+*     to represent offsets from a given spectral position, rather than absolute
+
+*  Parameters:
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new SpecFrame. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new SpecFrame. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSpecFrame()
+f     AST_SPECFRAME = INTEGER
+*        A pointer to the new SpecFrame.
+
+*  Examples:
+c     frame = astSpecFrame( "" );
+f     FRAME = AST_SPECFRAME( ' ', STATUS )
+*        Creates a SpecFrame to describe the default wavelength spectral
+*        coordinate system. The RestFreq attribute (rest frequency) is
+*        unspecified, so it will not be possible to align this SpecFrame
+*        with another SpecFrame on the basis of a velocity-based system. The
+*        standard of rest is also unspecified. This means that alignment
+*        will be possible with other SpecFrames, but no correction will be
+*        made for Doppler shift caused by change of rest frame during the
+*        alignment.
+c     frame = astSpecFrame( "System=VELO, RestFreq=1.0E15, StdOfRest=LSRK" );
+f     FRAME = AST_SPECFRAME( 'System=VELO, RestFreq=1.0E15, StdOfRest=LSRK', STATUS )
+*        Creates a SpecFrame describing a apparent radial velocity ("VELO") axis
+*        with rest frequency 1.0E15 Hz (about 3000 Angstroms), measured
+*        in the kinematic Local Standard of Rest ("LSRK"). Since the
+*        source position has not been specified (using attributes RefRA and
+*        RefDec), it will only be possible to align this SpecFrame with
+*        other SpecFrames which are also measured in the LSRK standard of
+*        rest.
+
+*  Notes:
+*     - When conversion between two SpecFrames is requested (as when
+c     supplying SpecFrames to astConvert),
+f     supplying SpecFrames AST_CONVERT),
+*     account will be taken of the nature of the spectral coordinate systems
+*     they represent, together with any qualifying rest frequency, standard
+*     of rest, epoch values, etc. The AlignSystem and AlignStdOfRest
+*     attributes will also be taken into account. The results will therefore
+*     fully reflect the relationship between positions measured in the two
+*     systems. In addition, any difference in the Unit attributes of the two
+*     systems will also be taken into account.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astSpecFrame constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astSpecFrame_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - The variable argument list also prevents this function from
+*     invoking astSpecFrame_ directly, so it must be a
+*     re-implementation of it in all respects, except for the final
+*     conversion of the result to an ID value.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMapping *um;               /* Mapping from default to actual units */
+   AstSpecFrame *new;            /* Pointer to new SpecFrame */
+   AstSystemType s;              /* System */
+   const char *u;                /* Units string */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+   astGET_GLOBALS(NULL);         /* Get a pointer to the thread specific global data structure. */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SpecFrame, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitSpecFrame( NULL, sizeof( AstSpecFrame ), !class_init,
+                           &class_vtab, "SpecFrame" );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new SpecFrame's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* Check the Units are appropriate for the System. */
+      u = astGetUnit( new, 0 );
+      s = astGetSystem( new );
+      um = astUnitMapper( DefUnit( s, "astSpecFrame", "SpecFrame", status ),
+                          u, NULL, NULL );
+      if( um ) {
+         um = astAnnul( um );
+      } else {
+         astError( AST__BADUN, "astSpecFrame: Inappropriate units (%s) "
+                   "specified for a %s axis.", status, u, SystemLabel( s, status ) );
+      }
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new SpecFrame. */
+   return astMakeId( new );
+}
+
diff --git a/ast/specframe.h b/ast/specframe.h
new file mode 100644
index 0000000..34d8eac
--- /dev/null
+++ b/ast/specframe.h
@@ -0,0 +1,430 @@
+#if !defined( SPECFRAME_INCLUDED ) /* Include this file only once */
+#define SPECFRAME_INCLUDED
+/*
+*+
+*  Name:
+*     specframe.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the SpecFrame class.
+
+*  Invocation:
+*     #include "specframe.h"
+
+*  Description:
+*     This include file defines the interface to the SpecFrame class
+*     and provides the type definitions, function prototypes and
+*     macros, etc. needed to use this class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     12-NOV-2002 (DSB):
+*        Original version.
+*     18-OCT-2006 (DSB):
+*        Added SpecOrigin.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+#include "frame.h"               /* Parent Frame class */
+#include "skyframe.h"            /* Celestial coordinate systems */
+
+/* Macros. */
+/* ======= */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+
+#if defined(astCLASS)            /* Protected */
+
+/* Values used to represent different System attribute values. */
+#define AST__FREQ          1
+#define AST__ENERGY        2
+#define AST__WAVENUM       3
+#define AST__WAVELEN       4
+#define AST__AIRWAVE       5
+#define AST__VRADIO        6
+#define AST__VOPTICAL      7
+#define AST__REDSHIFT      8
+#define AST__BETA          9
+#define AST__VREL          10
+
+/* Values used to represent different StdOfRest attribute values. */
+#define AST__BADSOR        0
+#define AST__TPSOR         1
+#define AST__GESOR         2
+#define AST__BYSOR         3
+#define AST__HLSOR         4
+#define AST__LDSOR         5
+#define AST__LKSOR         6
+#define AST__LGSOR         7
+#define AST__GLSOR         8
+#define AST__SCSOR         9
+#endif
+
+/* Type Definitions. */
+/* ================= */
+
+/* Integer type used to store the spectral StdOfRest attribute. */
+typedef int AstStdOfRestType;
+
+/* SpecFrame structure. */
+/* ------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstSpecFrame {
+
+/* Attributes inherited from the parent class. */
+   AstFrame frame;               /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstStdOfRestType alignstdofrest;/* Code identifying alignment StdOfRest */
+   AstStdOfRestType stdofrest;   /* Standard of rest */
+   double refdec;                /* Dec (FK5 J2000) of source */
+   double refra;                 /* RA (FK5 J2000) of source */
+   double restfreq;              /* Rest frequency (Hz)*/
+   double sourcevel;             /* Source velocity (heliocentric, m/s) */
+   AstStdOfRestType sourcevrf;   /* Code identifying source vel. StdOfRest */
+   AstSystemType sourcesys;      /* Code identifying source vel. system */
+   int nuunits;                  /* Size of usedunits array */
+   char **usedunits;             /* Last used units for each system */
+   double specorigin;            /* Origin for sectral values */
+   int alignspecoffset;          /* Align SpecFrame in offset coords? */
+} AstSpecFrame;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all objects in the
+   class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstSpecFrameVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstFrameVtab frame_vtab;      /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   void (* GetRefPos)( AstSpecFrame *, AstSkyFrame *, double *, double *, int * );
+   void (* SetRefPos)( AstSpecFrame *, AstSkyFrame *, double, double, int * );
+
+   AstStdOfRestType (* GetStdOfRest)( AstSpecFrame *, int * );
+   int (* TestStdOfRest)( AstSpecFrame *, int * );
+   void (* ClearStdOfRest)( AstSpecFrame *, int * );
+   void (* SetStdOfRest)( AstSpecFrame *, AstStdOfRestType, int * );
+
+   AstStdOfRestType (* GetAlignStdOfRest)( AstSpecFrame *, int * );
+   int (* TestAlignStdOfRest)( AstSpecFrame *, int * );
+   void (* ClearAlignStdOfRest)( AstSpecFrame *, int * );
+   void (* SetAlignStdOfRest)( AstSpecFrame *, AstStdOfRestType, int * );
+
+   AstStdOfRestType (* GetSourceVRF)( AstSpecFrame *, int * );
+   int (* TestSourceVRF)( AstSpecFrame *, int * );
+   void (* ClearSourceVRF)( AstSpecFrame *, int * );
+   void (* SetSourceVRF)( AstSpecFrame *, AstStdOfRestType, int * );
+
+   AstSystemType (* GetSourceSys)( AstSpecFrame *, int * );
+   int (* TestSourceSys)( AstSpecFrame *, int * );
+   void (* ClearSourceSys)( AstSpecFrame *, int * );
+   void (* SetSourceSys)( AstSpecFrame *, AstSystemType, int * );
+
+   double (* GetRestFreq)( AstSpecFrame *, int * );
+   int (* TestRestFreq)( AstSpecFrame *, int * );
+   void (* ClearRestFreq)( AstSpecFrame *, int * );
+   void (* SetRestFreq)( AstSpecFrame *, double, int * );
+
+   double (* GetRefRA)( AstSpecFrame *, int * );
+   int (* TestRefRA)( AstSpecFrame *, int * );
+   void (* ClearRefRA)( AstSpecFrame *, int * );
+   void (* SetRefRA)( AstSpecFrame *, double, int * );
+
+   double (* GetRefDec)( AstSpecFrame *, int * );
+   int (* TestRefDec)( AstSpecFrame *, int * );
+   void (* ClearRefDec)( AstSpecFrame *, int * );
+   void (* SetRefDec)( AstSpecFrame *, double, int * );
+
+   double (* GetSourceVel)( AstSpecFrame *, int * );
+   int (* TestSourceVel)( AstSpecFrame *, int * );
+   void (* ClearSourceVel)( AstSpecFrame *, int * );
+   void (* SetSourceVel)( AstSpecFrame *, double, int * );
+
+   double (* GetSpecOrigin)( AstSpecFrame *, int * );
+   int (* TestSpecOrigin)( AstSpecFrame *, int * );
+   void (* ClearSpecOrigin)( AstSpecFrame *, int * );
+   void (* SetSpecOrigin)( AstSpecFrame *, double, int * );
+
+   int (* GetAlignSpecOffset)( AstSpecFrame *, int * );
+   int (* TestAlignSpecOffset)( AstSpecFrame *, int * );
+   void (* ClearAlignSpecOffset)( AstSpecFrame *, int * );
+   void (* SetAlignSpecOffset)( AstSpecFrame *, int, int * );
+
+
+} AstSpecFrameVtab;
+
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstSpecFrameGlobals {
+   AstSpecFrameVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 51 ];
+   char GetLabel_Buff[ 201 ];
+   char GetSymbol_Buff[ 21 ];
+   char GetTitle_Buff[ 201 ];
+} AstSpecFrameGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(SpecFrame)         /* Check class membership */
+astPROTO_ISA(SpecFrame)           /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+AstSpecFrame *astSpecFrame_( const char *, int *, ...);
+#else
+AstSpecFrame *astSpecFrameId_( const char *, ... )__attribute__((format(printf,1,2)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstSpecFrame *astInitSpecFrame_( void *, size_t, int,
+                                         AstSpecFrameVtab *,
+                                         const char *, int * );
+
+/* Vtab initialiser. */
+void astInitSpecFrameVtab_( AstSpecFrameVtab *, const char *, int * );
+
+/* Loader. */
+AstSpecFrame *astLoadSpecFrame_( void *, size_t,
+                                         AstSpecFrameVtab *,
+                                         const char *, AstChannel *channel, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitSpecFrameGlobals_( AstSpecFrameGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+void astGetRefPos_( AstSpecFrame *, AstSkyFrame *, double *, double *, int * );
+void astSetRefPos_( AstSpecFrame *, AstSkyFrame *, double, double, int * );
+
+#if defined(astCLASS)            /* Protected */
+
+AstStdOfRestType astGetStdOfRest_( AstSpecFrame *, int * );
+int astTestStdOfRest_( AstSpecFrame *, int * );
+void astClearStdOfRest_( AstSpecFrame *, int * );
+void astSetStdOfRest_( AstSpecFrame *, AstStdOfRestType, int * );
+
+AstStdOfRestType astGetAlignStdOfRest_( AstSpecFrame *, int * );
+int astTestAlignStdOfRest_( AstSpecFrame *, int * );
+void astClearAlignStdOfRest_( AstSpecFrame *, int * );
+void astSetAlignStdOfRest_( AstSpecFrame *, AstStdOfRestType, int * );
+
+AstStdOfRestType astGetSourceVRF_( AstSpecFrame *, int * );
+int astTestSourceVRF_( AstSpecFrame *, int * );
+void astClearSourceVRF_( AstSpecFrame *, int * );
+void astSetSourceVRF_( AstSpecFrame *, AstStdOfRestType, int * );
+
+AstSystemType astGetSourceSys_( AstSpecFrame *, int * );
+int astTestSourceSys_( AstSpecFrame *, int * );
+void astClearSourceSys_( AstSpecFrame *, int * );
+void astSetSourceSys_( AstSpecFrame *, AstSystemType, int * );
+
+double astGetRestFreq_( AstSpecFrame *, int * );
+int astTestRestFreq_( AstSpecFrame *, int * );
+void astClearRestFreq_( AstSpecFrame *, int * );
+void astSetRestFreq_( AstSpecFrame *, double, int * );
+
+double astGetRefRA_( AstSpecFrame *, int * );
+int astTestRefRA_( AstSpecFrame *, int * );
+void astClearRefRA_( AstSpecFrame *, int * );
+void astSetRefRA_( AstSpecFrame *, double, int * );
+
+double astGetRefDec_( AstSpecFrame *, int * );
+int astTestRefDec_( AstSpecFrame *, int * );
+void astClearRefDec_( AstSpecFrame *, int * );
+void astSetRefDec_( AstSpecFrame *, double, int * );
+
+double astGetSourceVel_( AstSpecFrame *, int * );
+int astTestSourceVel_( AstSpecFrame *, int * );
+void astClearSourceVel_( AstSpecFrame *, int * );
+void astSetSourceVel_( AstSpecFrame *, double, int * );
+
+double astGetSpecOrigin_( AstSpecFrame *, int * );
+int astTestSpecOrigin_( AstSpecFrame *, int * );
+void astClearSpecOrigin_( AstSpecFrame *, int * );
+void astSetSpecOrigin_( AstSpecFrame *, double, int * );
+
+int astGetAlignSpecOffset_( AstSpecFrame *, int * );
+int astTestAlignSpecOffset_( AstSpecFrame *, int * );
+void astClearAlignSpecOffset_( AstSpecFrame *, int * );
+void astSetAlignSpecOffset_( AstSpecFrame *, int, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckSpecFrame(this) astINVOKE_CHECK(SpecFrame,this,0)
+#define astVerifySpecFrame(this) astINVOKE_CHECK(SpecFrame,this,1)
+
+/* Test class membership. */
+#define astIsASpecFrame(this) astINVOKE_ISA(SpecFrame,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+#define astSpecFrame astINVOKE(F,astSpecFrame_)
+#else
+#define astSpecFrame astINVOKE(F,astSpecFrameId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitSpecFrame(mem,size,init,vtab,name) \
+astINVOKE(O,astInitSpecFrame_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitSpecFrameVtab(vtab,name) astINVOKE(V,astInitSpecFrameVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadSpecFrame(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadSpecFrame_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+
+/* None. */
+
+/* Interfaces to protected member functions. */
+/* ----------------------------------------- */
+/* Here we make use of astCheckSpecFrame to validate SpecFrame pointers
+   before use. This provides a contextual error report if a pointer to
+   the wrong sort of object is supplied. */
+
+#define astGetRefPos(this,frm,lon,lat) astINVOKE(V,astGetRefPos_(astCheckSpecFrame(this),(frm==NULL?NULL:astCheckSkyFrame(frm)),lon,lat,STATUS_PTR))
+#define astSetRefPos(this,frm,lon,lat) astINVOKE(V,astSetRefPos_(astCheckSpecFrame(this),(frm==NULL?NULL:astCheckSkyFrame(frm)),lon,lat,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+
+#define astGetStdOfRest(this) astINVOKE(V,astGetStdOfRest_(astCheckSpecFrame(this),STATUS_PTR))
+#define astTestStdOfRest(this) astINVOKE(V,astTestStdOfRest_(astCheckSpecFrame(this),STATUS_PTR))
+#define astClearStdOfRest(this) astINVOKE(V,astClearStdOfRest_(astCheckSpecFrame(this),STATUS_PTR))
+#define astSetStdOfRest(this,value) astINVOKE(V,astSetStdOfRest_(astCheckSpecFrame(this),value,STATUS_PTR))
+
+#define astGetAlignStdOfRest(this) astINVOKE(V,astGetAlignStdOfRest_(astCheckSpecFrame(this),STATUS_PTR))
+#define astTestAlignStdOfRest(this) astINVOKE(V,astTestAlignStdOfRest_(astCheckSpecFrame(this),STATUS_PTR))
+#define astClearAlignStdOfRest(this) astINVOKE(V,astClearAlignStdOfRest_(astCheckSpecFrame(this),STATUS_PTR))
+#define astSetAlignStdOfRest(this,value) astINVOKE(V,astSetAlignStdOfRest_(astCheckSpecFrame(this),value,STATUS_PTR))
+
+#define astGetSourceVRF(this) astINVOKE(V,astGetSourceVRF_(astCheckSpecFrame(this),STATUS_PTR))
+#define astTestSourceVRF(this) astINVOKE(V,astTestSourceVRF_(astCheckSpecFrame(this),STATUS_PTR))
+#define astClearSourceVRF(this) astINVOKE(V,astClearSourceVRF_(astCheckSpecFrame(this),STATUS_PTR))
+#define astSetSourceVRF(this,value) astINVOKE(V,astSetSourceVRF_(astCheckSpecFrame(this),value,STATUS_PTR))
+
+#define astGetSourceSys(this) astINVOKE(V,astGetSourceSys_(astCheckSpecFrame(this),STATUS_PTR))
+#define astTestSourceSys(this) astINVOKE(V,astTestSourceSys_(astCheckSpecFrame(this),STATUS_PTR))
+#define astClearSourceSys(this) astINVOKE(V,astClearSourceSys_(astCheckSpecFrame(this),STATUS_PTR))
+#define astSetSourceSys(this,value) astINVOKE(V,astSetSourceSys_(astCheckSpecFrame(this),value,STATUS_PTR))
+
+#define astGetRestFreq(this) astINVOKE(V,astGetRestFreq_(astCheckSpecFrame(this),STATUS_PTR))
+#define astTestRestFreq(this) astINVOKE(V,astTestRestFreq_(astCheckSpecFrame(this),STATUS_PTR))
+#define astClearRestFreq(this) astINVOKE(V,astClearRestFreq_(astCheckSpecFrame(this),STATUS_PTR))
+#define astSetRestFreq(this,value) astINVOKE(V,astSetRestFreq_(astCheckSpecFrame(this),value,STATUS_PTR))
+
+#define astGetRefRA(this) astINVOKE(V,astGetRefRA_(astCheckSpecFrame(this),STATUS_PTR))
+#define astTestRefRA(this) astINVOKE(V,astTestRefRA_(astCheckSpecFrame(this),STATUS_PTR))
+#define astClearRefRA(this) astINVOKE(V,astClearRefRA_(astCheckSpecFrame(this),STATUS_PTR))
+#define astSetRefRA(this,value) astINVOKE(V,astSetRefRA_(astCheckSpecFrame(this),value,STATUS_PTR))
+
+#define astGetRefDec(this) astINVOKE(V,astGetRefDec_(astCheckSpecFrame(this),STATUS_PTR))
+#define astTestRefDec(this) astINVOKE(V,astTestRefDec_(astCheckSpecFrame(this),STATUS_PTR))
+#define astClearRefDec(this) astINVOKE(V,astClearRefDec_(astCheckSpecFrame(this),STATUS_PTR))
+#define astSetRefDec(this,value) astINVOKE(V,astSetRefDec_(astCheckSpecFrame(this),value,STATUS_PTR))
+
+#define astGetSourceVel(this) astINVOKE(V,astGetSourceVel_(astCheckSpecFrame(this),STATUS_PTR))
+#define astTestSourceVel(this) astINVOKE(V,astTestSourceVel_(astCheckSpecFrame(this),STATUS_PTR))
+#define astClearSourceVel(this) astINVOKE(V,astClearSourceVel_(astCheckSpecFrame(this),STATUS_PTR))
+#define astSetSourceVel(this,value) astINVOKE(V,astSetSourceVel_(astCheckSpecFrame(this),value,STATUS_PTR))
+
+#define astGetSpecOrigin(this) astINVOKE(V,astGetSpecOrigin_(astCheckSpecFrame(this),STATUS_PTR))
+#define astTestSpecOrigin(this) astINVOKE(V,astTestSpecOrigin_(astCheckSpecFrame(this),STATUS_PTR))
+#define astClearSpecOrigin(this) astINVOKE(V,astClearSpecOrigin_(astCheckSpecFrame(this),STATUS_PTR))
+#define astSetSpecOrigin(this,value) astINVOKE(V,astSetSpecOrigin_(astCheckSpecFrame(this),value,STATUS_PTR))
+
+#define astClearAlignSpecOffset(this) astINVOKE(V,astClearAlignSpecOffset_(astCheckSpecFrame(this),STATUS_PTR))
+#define astGetAlignSpecOffset(this) astINVOKE(V,astGetAlignSpecOffset_(astCheckSpecFrame(this),STATUS_PTR))
+#define astSetAlignSpecOffset(this,value) astINVOKE(V,astSetAlignSpecOffset_(astCheckSpecFrame(this),value,STATUS_PTR))
+#define astTestAlignSpecOffset(this) astINVOKE(V,astTestAlignSpecOffset_(astCheckSpecFrame(this),STATUS_PTR))
+
+
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/specmap.c b/ast/specmap.c
new file mode 100644
index 0000000..48ada4e
--- /dev/null
+++ b/ast/specmap.c
@@ -0,0 +1,4696 @@
+/*
+*class++
+*  Name:
+*     SpecMap
+
+*  Purpose:
+*     Sequence of spectral coordinate conversions.
+
+*  Constructor Function:
+c     astSpecMap (also see astSpecAdd)
+f     AST_SPECMAP (also see AST_SPECADD)
+
+*  Description:
+*     A SpecMap is a specialised form of Mapping which can be used to
+*     represent a sequence of conversions between standard spectral
+*     coordinate systems.
+*
+*     When an SpecMap is first created, it simply performs a unit
+c     (null) Mapping. Using the astSpecAdd
+f     (null) Mapping. Using the AST_SPECADD
+c     function, a series of coordinate conversion steps may then be
+f     routine, a series of coordinate conversion steps may then be
+*     added. This allows multi-step conversions between a variety of
+*     spectral coordinate systems to be assembled out of a set of building
+*     blocks.
+*
+*     Conversions are available to transform between standards of rest.
+*     Such conversions need to know the source position as an RA and DEC.
+*     This information can be supplied in the form of parameters for
+*     the relevant conversions, in which case the SpecMap is 1-dimensional,
+*     simply transforming the spectral axis values. This means that the
+*     same source position will always be used by the SpecMap. However, this
+*     may not be appropriate for an accurate description of a 3-D spectral
+*     cube, where changes of spatial position can produce significant
+*     changes in the Doppler shift introduced when transforming between
+*     standards of rest. For this situation, a 3-dimensional SpecMap can
+*     be created in which axes 2 and 3 correspond to the source RA and DEC
+*     The SpecMap simply copies values for axes 2 and 3 from input to
+*     output), but modifies axis 1 values (the spectral axis) appropriately.
+*
+*     For details of the individual coordinate conversions available,
+c     see the description of the astSpecAdd function.
+f     see the description of the AST_SPECADD routine.
+
+*  Inheritance:
+*     The SpecMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The SpecMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     In addition to those functions applicable to all Mappings, the
+c     following function may also be applied to all SpecMaps:
+f     In addition to those routines applicable to all Mappings, the
+f     following routine may also be applied to all SpecMaps:
+*
+c     - astSpecAdd: Add a spectral coordinate conversion to an SpecMap
+f     - AST_SPECADD: Add a spectral coordinate conversion to an SpecMap
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     6-NOV-2002 (DSB):
+*        Original version.
+*     14-JUL-2003 (DSB):
+*        Added checks for NAN values produced by transformation functions.
+*     17-SEP-2003 (DSB):
+*        - Improve FRTOAW accuracy by iterating.
+*        - Changed Refrac to use algorithm given in FITS-WCS paper 3
+*        version dated 21/9/03.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*     15-NOV-2006 (DSB):
+*        Guard against division by zero when converting freq to wave in
+*        SystemChange.
+*     18-JUN-2009 (DSB):
+*        Add OBSALT argument to TPF2HL and HLF2TP conversions.
+*        Change GEOLON/LAT to OBSLON/LAT for consistency with other
+*        classes.
+*     2-OCT-2012 (DSB):
+*        Check for Infs as well as NaNs.
+*     1-DEC-2016 (DSB):
+*        Added a "narg" argumeent to astSpecAdd.
+
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS SpecMap
+
+/* Codes to identify spectral coordinate conversions. */
+#define AST__SPEC_NULL   0       /* Null value */
+#define AST__FRTOVL      1       /* Frequency to relativistic velocity */
+#define AST__VLTOFR      2       /* Relativistic velocity to Frequency */
+#define AST__ENTOFR      3       /* Energy to frequency */
+#define AST__FRTOEN      4       /* Frequency to energy */
+#define AST__WNTOFR      5       /* Wave number to frequency */
+#define AST__FRTOWN      6       /* Frequency to wave number */
+#define AST__WVTOFR      7       /* Wavelength (vacuum) to frequency */
+#define AST__FRTOWV      8       /* Frequency to wavelength (vacuum) */
+#define AST__AWTOFR      9       /* Wavelength (air) to frequency */
+#define AST__FRTOAW     10       /* Frequency to wavelength (air) */
+#define AST__VRTOVL     11       /* Radio to relativistic velocity */
+#define AST__VLTOVR     12       /* Relativistic to radio velocity */
+#define AST__VOTOVL     13       /* Optical to relativistic velocity */
+#define AST__VLTOVO     14       /* Relativistic to optical velocity */
+#define AST__ZOTOVL     15       /* Redshift to relativistic velocity */
+#define AST__VLTOZO     16       /* Relativistic velocity to redshift */
+#define AST__BTTOVL     17       /* Beta factor to relativistic velocity */
+#define AST__VLTOBT     18       /* Relativistic velocity to beta factor */
+#define AST__USF2HL     19       /* User-defined to heliocentric frequency  */
+#define AST__HLF2US     20       /* Heliocentric to user-defined frequency */
+#define AST__TPF2HL     21       /* Topocentric to heliocentric frequency  */
+#define AST__HLF2TP     22       /* Heliocentric to topocentric frequency */
+#define AST__GEF2HL     23       /* Geocentric to heliocentric frequency */
+#define AST__HLF2GE     24       /* Heliocentric to geocentric frequency */
+#define AST__BYF2HL     25       /* Barycentric to heliocentric frequency */
+#define AST__HLF2BY     26       /* Heliocentric to barycentric frequency */
+#define AST__LKF2HL     27       /* LSRK to heliocentric frequency */
+#define AST__HLF2LK     28       /* Heliocentric to LSRK frequency */
+#define AST__LDF2HL     29       /* LSRD to heliocentric frequency */
+#define AST__HLF2LD     30       /* Heliocentric to LSRD frequency */
+#define AST__LGF2HL     31       /* Local group to heliocentric frequency */
+#define AST__HLF2LG     32       /* Heliocentric to local group frequency */
+#define AST__GLF2HL     33       /* Galactic to heliocentric frequency */
+#define AST__HLF2GL     34       /* Heliocentric to galactic frequency */
+
+/* Maximum number of arguments required by a conversion. */
+#define MAX_ARGS 7
+
+/* The alphabet (used for generating keywords for arguments). */
+#define ALPHABET "abcdefghijklmnopqrstuvwxyz"
+
+/* Angle conversion */
+#define PI 3.141592653589793238462643
+#define PIBY2 (PI/2.0)
+#define D2R (PI/180.0)
+#define R2D (180.0/PI)
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "pal.h"              /* SLALIB interface */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+#include "unitmap.h"             /* Unit (null) Mappings */
+#include "specmap.h"             /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double (* parent_rate)( AstMapping *, double *, int, int, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(SpecMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(SpecMap,Class_Init)
+#define class_vtab astGLOBAL(SpecMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstSpecMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* Structure to hold parameters and intermediate values describing a
+   reference frame */
+typedef struct FrameDef {
+   double obsalt;     /* Observers geodetic altitude (m) */
+   double obslat;     /* Observers geodetic latitude (rads) */
+   double obslon;     /* Observers geodetic longitude (rads, +ve east) */
+   double epoch;      /* Julian epoch of observation */
+   double refdec;     /* RA of reference point (FK5 J2000) */
+   double refra;      /* DEC of reference point (FK5 J2000) */
+   double veluser;    /* Heliocentric velocity of user-defined system (m/s) */
+   double last;       /* Local apparent sideral time */
+   double amprms[21]; /* Mean to apparent parameters */
+   double vuser[3];   /* Used-defined velocity as a FK5 J2000 vector */
+   double dvh[3];     /* Earth-sun velocity */
+   double dvb[3];     /* Barycentre-sun velocity */
+} FrameDef;
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstSpecMap *astSpecMapId_( int, int, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *CvtString( int, const char **, int *, int *, int *, int *, const char *[ MAX_ARGS ], int * );
+static double BaryVel( double, double, FrameDef *, int * );
+static double GalVel( double, double, FrameDef *, int * );
+static double GeoVel( double, double, FrameDef *, int * );
+static double LgVel( double, double, FrameDef *, int * );
+static double LsrdVel( double, double, FrameDef *, int * );
+static double LsrkVel( double, double, FrameDef *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static double Refrac( double, int * );
+static double Rverot( double, double, double, double, double, int * );
+static double TopoVel( double, double, FrameDef *, int * );
+static double UserVel( double, double, FrameDef *, int * );
+static int CvtCode( const char *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int FrameChange( int, int, double *, double *, double *, double *, int, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int SystemChange( int, int, double *, double *, int, int * );
+static void AddSpecCvt( AstSpecMap *, int, int, const double *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void SpecAdd( AstSpecMap *, const char *, int, const double[], int * );
+
+static int GetObjSize( AstObject *, int * );
+/* Member functions. */
+/* ================= */
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two SpecMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     SpecMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two SpecMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a SpecMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the SpecMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSpecMap *that;
+   AstSpecMap *this;
+   const char *argdesc[ MAX_ARGS ];
+   const char *comment;
+   int argdec;
+   int argra;
+   int i, j;
+   int nargs;
+   int nin;
+   int nout;
+   int result;
+   int szargs;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two SpecMap structures. */
+   this = (AstSpecMap *) this_object;
+   that = (AstSpecMap *) that_object;
+
+/* Check the second object is a SpecMap. We know the first is a
+   SpecMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsASpecMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two SpecMaps differ, it may still be possible
+   for them to be equivalent. First compare the SpecMaps if their Invert
+   flags are the same. In this case all the attributes of the two SpecMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+            if( this->ncvt == that->ncvt ) {
+               result = 1;
+               for( i = 0; i < this->ncvt && result; i++ ) {
+                  if( this->cvttype[ i ] != that->cvttype[ i ] ) {
+                     result = 0;
+                  } else {
+                     CvtString( this->cvttype[ i ], &comment, &argra,
+                                &argdec, &nargs, &szargs, argdesc, status );
+                     for( j = 0; j < nargs; j++ ) {
+                        if( !astEQUAL( this->cvtargs[ i ][ j ],
+                                       that->cvtargs[ i ][ j ] ) ){
+                           result = 0;
+                           break;
+                        }
+                     }
+                  }
+               }
+            }
+
+/* If the Invert flags for the two SpecMaps differ, the attributes of the two
+   SpecMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a SpecMap, Invert flags must be equal. */
+            result = 0;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     SpecMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied SpecMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSpecMap *this;         /* Pointer to SpecMap structure */
+   int result;               /* Result value to return */
+   int cvt;                  /* Loop counter for coordinate conversions */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the SpecMap structure. */
+   this = (AstSpecMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   for ( cvt = 0; cvt < this->ncvt; cvt++ ) {
+      result += astTSizeOf( this->cvtargs[ cvt ] );
+   }
+
+   result += astTSizeOf( this->cvtargs );
+   result += astTSizeOf( this->cvttype );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static void AddSpecCvt( AstSpecMap *this, int cvttype, int narg,
+                        const double *args, int *status ) {
+/*
+*  Name:
+*     AddSpecCvt
+
+*  Purpose:
+*     Add a coordinate conversion step to an SpecMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     void AddSpecCvt( AstSpecMap *this, int cvttype, int narg,
+*                      const double *args )
+
+*  Class Membership:
+*     SpecMap member function.
+
+*  Description:
+*     This function allows one of the supported spectral coordinate
+*     conversions to be appended to a SpecMap. When a SpecMap is first
+*     created (using astSpecMap), it simply performs a unit mapping. By
+*     using AddSpecCvt repeatedly, a series of coordinate conversions may
+*     then be specified which the SpecMap will subsequently perform in
+*     sequence. This allows a complex coordinate conversion to be
+*     assembled out of the basic building blocks. The SpecMap will also
+*     perform the inverse coordinate conversion (applying the individual
+*     conversion steps in reverse) if required.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecMap.
+*     cvttype
+*        A code to identify which spectral coordinate conversion is to be
+*        appended.  See the "Coordinate Conversions" section for details
+*        of those available.
+*     narg
+*        The number of argument values supplied in "args".
+*     args
+*        Pointer to an array of double containing the argument values
+*        required to fully specify the required coordinate
+*        conversion. The number of arguments depends on the conversion
+*        (see the "Coordinate Conversions" section for details). This
+*        value is ignored and may be NULL if no arguments are required.
+
+*  Returned Value:
+*     void.
+
+*  Coordinate Conversions:
+*     The following values may be supplied for the "cvttype" parameter
+*     in order to specify the coordinate conversion to be performed.
+*     The argument(s) required to fully specify each conversion are
+*     indicated in parentheses after each value, and described at the end
+*     of the list. Values for these should be given in the array pointed
+*     at by "args".
+*
+*        AST__FRTOVL( RF )
+*           Convert frequency to relativistic velocity.
+*        AST__VLTOFR( RF )
+*           Convert relativistic velocity to Frequency.
+*        AST__ENTOFR
+*           Convert energy to frequency.
+*        AST__FRTOEN
+*           Convert frequency to energy.
+*        AST__WNTOFR
+*           Convert wave number to frequency.
+*        AST__FRTOWN
+*           Convert frequency to wave number.
+*        AST__WVTOFR
+*           Convert wavelength (vacuum) to frequency.
+*        AST__FRTOWV
+*           Convert frequency to wavelength (vacuum).
+*        AST__AWTOFR
+*           Convert wavelength (air) to frequency.
+*        AST__FRTOAW
+*           Convert frequency to wavelength (air).
+*        AST__VRTOVL
+*           Convert radio to relativistic velocity.
+*        AST__VLTOVR
+*           Convert relativistic to radio velocity.
+*        AST__VOTOVL
+*           Convert optical to relativistic velocity.
+*        AST__VLTOVO
+*           Convert relativistic to optical velocity.
+*        AST__ZOTOVL
+*           Convert redshift to relativistic velocity.
+*        AST__VLTOZO
+*           Convert relativistic velocity to redshift.
+*        AST__BTTOVL
+*           Convert beta factor to relativistic velocity.
+*        AST__VLTOBT
+*           Convert relativistic velocity to beta factor.
+*        AST_USF2HL( VOFF, RA, DEC )
+*           Convert frequency from a user-defined reference frame to
+*           heliocentric.
+*        AST__HLF2US( VOFF, RA, DEC )
+*           Convert frequency from heliocentric reference frame to
+*           user-defined.
+*        AST__TPF2HL( OBSLON, OBSLAT, OBSALT, EPOCH, RA, DEC )
+*           Convert from Topocentric to heliocentric frequency
+*        AST__HLF2TP( OBSLON, OBSLAT, OBSALT, EPOCH, RA, DEC )
+*           Convert from Heliocentric to topocentric frequency.
+*        AST__GEF2HL( EPOCH, RA, DEC )
+*           Convert from Geocentric to heliocentric frequency.
+*        AST__HLF2GE( EPOCH, RA, DEC )
+*           Convert from Heliocentric to geocentric frequency.
+*        AST__BYF2HL( EPOCH, RA, DEC )
+*           Convert from Barycentric to heliocentric frequency.
+*        AST__HLF2BY( EPOCH, RA, DEC )
+*           Convert from Heliocentric to barycentric frequency.
+*        AST__LKF2HL( RA, DEC )
+*           Convert from LSRK to heliocentric frequency.
+*        AST__HLF2LK( RA, DEC )
+*           Convert from Heliocentric to LSRK frequency.
+*        AST__LDF2HL( RA, DEC )
+*           Convert from LSRD to heliocentric frequency.
+*        AST__HLF2LD( RA, DEC )
+*           Convert from Heliocentric to LSRD frequency.
+*        AST__LGF2HL( RA, DEC )
+*           Convert from Local group to heliocentric frequency.
+*        AST__HLF2LG( RA, DEC )
+*           Convert from Heliocentric to local group frequency.
+*        AST__GLF2HL( RA, DEC )
+*           Convert from Galactic to heliocentric frequency.
+*        AST__HLF2GL( RA, DEC )
+*           Convert from Heliocentric to galactic frequency.
+*
+*     The units for the values processed by the above conversions are as
+*     follows:
+*
+*     - all velocities: metres per second.
+*     - frequency: Hertz.
+*     - all wavelengths: metres.
+*     - energy: Joules.
+*     - wave number: cycles per metre.
+*
+*     The arguments used in the above conversions are as follows:
+*
+*     - RF: Rest frequency (Hz).
+*     - OBSALT: Geodetic altitude of observer (IAU 1975, metres).
+*     - OBSLAT: Geodetic latitude of observer (IAU 1975, radians).
+*     - OBSLON: Longitude of observer (radians, positive eastwards).
+*     - EPOCH: Epoch of observation (UT1 expressed as a Modified Julian Date).
+*     - RA: Right Ascension of source (radians, FK5 J2000).
+*     - DEC: Declination of source (radians, FK5 J2000).
+*     - VOFF: Velocity of the user-defined reference frame, towards the
+*     position given by RA and DEC, measured in the heliocentric
+*     reference frame.
+*
+*     If the SpecMap is 3-dimensional, source positions are provided by the
+*     values supplied to inputs 2 and 3 of the SpecMap (which are simply
+*     copied to outputs 2 and 3). Note, usable values are still required
+*     for the RA and DEC arguments in order to define the "user-defined"
+*     reference frame used by USF2HL and HLF2US. However, AST__BAD can be
+*     supplied for RA and DEC if the user-defined reference frame is not
+*     required.
+
+*  Notes:
+*     - The specified conversion is appended only if the SpecMap's
+*     Invert attribute is zero. If it is non-zero, this function
+*     effectively prefixes the inverse of the conversion specified
+*     instead.
+*/
+
+/* Local Variables: */
+   const char *argdesc[ MAX_ARGS ]; /* Pointers to argument descriptions */
+   const char *comment;          /* Pointer to comment string */
+   const char *cvt_string;       /* Pointer to conversion type string */
+   int argdec;                   /* Index of DEC argument */
+   int argra;                    /* Index of RA argument */
+   int i;                        /* Argument index */
+   int nargs;                    /* Number of user-supplied arguments */
+   int ncvt;                     /* Number of coordinate conversions */
+   int szargs;                   /* Size of arguments array */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the coordinate conversion type and obtain the number of
+   required user-supplied arguments, and the size of the array in which
+   to put the user-supplied arguments (the array meay leave room after
+   the user-supplied arguments for various useful pre-calculated values). */
+   cvt_string = CvtString( cvttype, &comment, &argra, &argdec, &nargs,
+                           &szargs, argdesc, status );
+
+/* If the coordinate conversion type was not valid, then report an
+   error. */
+   if ( astOK && !cvt_string ) {
+      astError( AST__SPCIN, "AddSpecCvt(%s): Invalid spectral coordinate "
+                "conversion type (%d).", status, astGetClass( this ),
+                (int) cvttype );
+   }
+
+/* If the number of supplied arguments is incorrect, then report an error. */
+   if ( astOK && nargs != narg ) {
+      astError( AST__TIMIN, "AddSpecCvt(%s): Invalid no. of arguments for spectral "
+                "coordinate conversion type %d - %d supplied, %d required.",
+                status, astGetClass( this ), (int) cvttype, narg, nargs );
+   }
+
+/* Note the number of coordinate conversions already stored in the SpecMap. */
+   if ( astOK ) {
+      ncvt = this->ncvt;
+
+/* Extend the array of conversion types and the array of pointers to
+   their argument lists to accommodate the new one. */
+      this->cvttype = (int *) astGrow( this->cvttype, ncvt + 1,
+                                       sizeof( int ) );
+      this->cvtargs = (double **) astGrow( this->cvtargs, ncvt + 1,
+                                           sizeof( double * ) );
+
+/* If OK, allocate memory and store a copy of the argument list,
+   putting a pointer to the copy into the SpecMap. */
+      if ( astOK ) {
+         this->cvtargs[ ncvt ] = astStore( NULL, args,
+                                           sizeof( double ) * (size_t) szargs );
+      }
+
+/* Store the conversion type and increment the conversion count. Also put
+   AST__BAD in any elements of the argument array which are beyond the
+   end of the user-supplied arguments. These will be used to hold
+   intermediate values calculated on the basis of the user-supplied
+   arguments. */
+      if ( astOK ) {
+         this->cvttype[ ncvt ] = cvttype;
+         this->ncvt++;
+         for( i = nargs; i < szargs; i++ ) this->cvtargs[ ncvt ][ i ] = AST__BAD;
+      }
+   }
+}
+
+static double BaryVel( double ra, double dec, FrameDef *def, int *status ) {
+/*
+*  Name:
+*     BaryVel
+
+*  Purpose:
+*     Find the velocity of the earth-sun barycentre away from the source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     double BaryVel( double ra, double dec, FrameDef *def, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function finds the component of the velocity of the earth-sun
+*     barycentre away from a specified source position, at a given epoch, in
+*     the frame of rest of the centre of the Sun.
+
+*  Parameters:
+*     ra
+*        The RA (rads, FK5 J2000) of the source.
+*     dec
+*        The Dec (rads, FK5 J2000) of the source.
+*     def
+*        Pointer to a FrameDef structure which holds the parameters which
+*        define the frame, together with cached intermediate results.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     The component of the frame's velocity away from the position given by
+*     "ra" and "dec", in m/s, measured within the Heliographic frame of
+*     rest. Zero is returned if an error has already occurred.
+
+*/
+
+/* Local Variables: */
+   double dpb[ 3 ];          /* Barycentric earth position vector */
+   double dph[ 3 ];          /* Heliocentric earth position vector */
+   double dvh[ 3 ];          /* Heliocentric earth velocity vector */
+   double v[ 3 ];            /* Source direction vector */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Get the Cartesian vector towards the source, in the Cartesian FK5
+   J2000 system. */
+   palDcs2c( ra, dec, v );
+
+/* If not already done so, get the Earth/Sun velocity and position vectors in
+   the same system. Speed is returned in units of AU/s. Store in the supplied
+   frame definition structure. */
+   if( def->dvb[ 0 ] == AST__BAD ) {
+      palEvp( def->epoch, 2000.0, def->dvb, dpb, dvh, dph );
+
+/* Change the barycentric velocity of the earth into the heliocentric
+   velocity of the barycentre. */
+      def->dvb[ 0 ] = dvh[ 0 ] - def->dvb[ 0 ];
+      def->dvb[ 1 ] = dvh[ 1 ] - def->dvb[ 1 ];
+      def->dvb[ 2 ] = dvh[ 2 ] - def->dvb[ 2 ];
+   }
+
+/* Return the component away from the source, of the velocity of the
+   barycentre relative to the sun (in m/s). */
+   return -palDvdv( v, def->dvb )*149.597870E9;
+
+}
+
+static int CvtCode( const char *cvt_string, int *status ) {
+/*
+*  Name:
+*     CvtCode
+
+*  Purpose:
+*     Convert a conversion type from a string representation to a code value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     int CvtCode( const char *cvt_string, int *status )
+
+*  Class Membership:
+*     SpecMap member function.
+
+*  Description:
+*     This function accepts a string used to repersent one of the
+*     SpecMap coordinate conversions and converts it into a code
+*     value for internal use.
+
+*  Parameters:
+*     cvt_string
+*        Pointer to a constant null-terminated string representing a
+*        spectral coordinate conversion. This is case sensitive and should
+*        contain no unnecessary white space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The equivalent conversion code. If the string was not
+*     recognised, the code AST__SPEC_NULL is returned, without error.
+
+*  Notes:
+*     - A value of AST__SPEC_NULL will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = AST__SPEC_NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Test the string against each recognised value in turn and assign
+   the result. */
+   if ( astChrMatch( cvt_string, "FRTOVL" ) ) {
+      result = AST__FRTOVL;
+
+   } else if ( astChrMatch( cvt_string, "VLTOFR" ) ) {
+      result = AST__VLTOFR;
+
+   } else if ( astChrMatch( cvt_string, "VLTOFR" ) ) {
+      result = AST__VLTOFR;
+
+   } else if ( astChrMatch( cvt_string, "ENTOFR" ) ) {
+      result = AST__ENTOFR;
+
+   } else if ( astChrMatch( cvt_string, "FRTOEN" ) ) {
+      result = AST__FRTOEN;
+
+   } else if ( astChrMatch( cvt_string, "WNTOFR" ) ) {
+      result = AST__WNTOFR;
+
+   } else if ( astChrMatch( cvt_string, "FRTOWN" ) ) {
+      result = AST__FRTOWN;
+
+   } else if ( astChrMatch( cvt_string, "WVTOFR" ) ) {
+      result = AST__WVTOFR;
+
+   } else if ( astChrMatch( cvt_string, "FRTOWV" ) ) {
+      result = AST__FRTOWV;
+
+   } else if ( astChrMatch( cvt_string, "AWTOFR" ) ) {
+      result = AST__AWTOFR;
+
+   } else if ( astChrMatch( cvt_string, "FRTOAW" ) ) {
+      result = AST__FRTOAW;
+
+   } else if ( astChrMatch( cvt_string, "VRTOVL" ) ) {
+      result = AST__VRTOVL;
+
+   } else if ( astChrMatch( cvt_string, "VLTOVR" ) ) {
+      result = AST__VLTOVR;
+
+   } else if ( astChrMatch( cvt_string, "VOTOVL" ) ) {
+      result = AST__VOTOVL;
+
+   } else if ( astChrMatch( cvt_string, "VLTOVO" ) ) {
+      result = AST__VLTOVO;
+
+   } else if ( astChrMatch( cvt_string, "ZOTOVL" ) ) {
+      result = AST__ZOTOVL;
+
+   } else if ( astChrMatch( cvt_string, "VLTOZO" ) ) {
+      result = AST__VLTOZO;
+
+   } else if ( astChrMatch( cvt_string, "BTTOVL" ) ) {
+      result = AST__BTTOVL;
+
+   } else if ( astChrMatch( cvt_string, "VLTOBT" ) ) {
+      result = AST__VLTOBT;
+
+   } else if ( astChrMatch( cvt_string, "USF2HL" ) ) {
+      result = AST__USF2HL;
+
+   } else if ( astChrMatch( cvt_string, "HLF2US" ) ) {
+      result = AST__HLF2US;
+
+   } else if ( astChrMatch( cvt_string, "TPF2HL" ) ) {
+      result = AST__TPF2HL;
+
+   } else if ( astChrMatch( cvt_string, "HLF2TP" ) ) {
+      result = AST__HLF2TP;
+
+   } else if ( astChrMatch( cvt_string, "GEF2HL" ) ) {
+      result = AST__GEF2HL;
+
+   } else if ( astChrMatch( cvt_string, "HLF2GE" ) ) {
+      result = AST__HLF2GE;
+
+   } else if ( astChrMatch( cvt_string, "BYF2HL" ) ) {
+      result = AST__BYF2HL;
+
+   } else if ( astChrMatch( cvt_string, "HLF2BY" ) ) {
+      result = AST__HLF2BY;
+
+   } else if ( astChrMatch( cvt_string, "LKF2HL" ) ) {
+      result = AST__LKF2HL;
+
+   } else if ( astChrMatch( cvt_string, "HLF2LK" ) ) {
+      result = AST__HLF2LK;
+
+   } else if ( astChrMatch( cvt_string, "LDF2HL" ) ) {
+      result = AST__LDF2HL;
+
+   } else if ( astChrMatch( cvt_string, "HLF2LD" ) ) {
+      result = AST__HLF2LD;
+
+   } else if ( astChrMatch( cvt_string, "LGF2HL" ) ) {
+      result = AST__LGF2HL;
+
+   } else if ( astChrMatch( cvt_string, "HLF2LG" ) ) {
+      result = AST__HLF2LG;
+
+   } else if ( astChrMatch( cvt_string, "GLF2HL" ) ) {
+      result = AST__GLF2HL;
+
+   } else if ( astChrMatch( cvt_string, "HLF2GL" ) ) {
+      result = AST__HLF2GL;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *CvtString( int cvt_code, const char **comment,
+                              int *argra, int *argdec, int *nargs, int *szargs,
+                              const char *arg[ MAX_ARGS ], int *status ) {
+/*
+*  Name:
+*     CvtString
+
+*  Purpose:
+*     Convert a conversion type from a code value to a string representation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     const char *CvtString( int cvt_code, const char **comment,
+*                            int *argra, int *argdec, int *nargs,
+*                            int *szargs, const char *arg[ MAX_ARGS ], int *status )
+
+*  Class Membership:
+*     SpecMap member function.
+
+*  Description:
+*     This function accepts a code value used to represent one of the
+*     SpecMap coordinate conversions and converts it into an
+*     equivalent string representation. It also returns a descriptive
+*     comment and information about the arguments required in order to
+*     perform the conversion.
+
+*  Parameters:
+*     cvt_code
+*        The conversion code.
+*     comment
+*        Address of a location to return a pointer to a constant
+*        null-terminated string containing a description of the
+*        conversion.
+*     argra
+*        Address of an int in which to return the index of the argument
+*        corresponding to the source RA. Returned equal to -1 if the
+*        conversion does not have a source RA argument.
+*     argdec
+*        Address of an int in which to return the index of the argument
+*        corresponding to the source DEC. Returned equal to -1 if the
+*        conversion does not have a source DEC argument.
+*     nargs
+*        Address of an int in which to return the number of arguments
+*        required from the user in order to perform the conversion (may
+*        be zero).
+*     szargs
+*        Address of an int in which to return the number of arguments
+*        associated with the conversion. This may be bigger than "nargs"
+*        if the conversion can pre-calculate useful values on the basis
+*        of the user-supplied values. Such precalculated values are
+*        stored after the last user-supplied argument.
+*     arg
+*        An array in which to return a pointer to a constant
+*        null-terminated string for each argument (above) containing a
+*        description of what each argument represents. This includes both
+*        user-supplied arguments and pre-calculated values.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string representation of
+*     the conversion code value supplied. If the code supplied is not
+*     valid, a NULL pointer will be returned, without error.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;          /* Result pointer to return */
+
+/* Initialise the returned values. */
+   *comment = NULL;
+   *nargs = 0;
+   *argra = -1;
+   *argdec = -1;
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Test for each valid code value in turn and assign the appropriate
+   return values. */
+   switch ( cvt_code ) {
+
+   case AST__FRTOVL:
+      *comment = "Convert frequency to rel. velocity";
+      result = "FRTOVL";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "Rest frequency (Hz)";
+      break;
+
+   case AST__VLTOFR:
+      *comment = "Convert rel. velocity to frequency";
+      result = "VLTOFR";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "Rest frequency (Hz)";
+      break;
+
+   case AST__ENTOFR:
+      *comment = "Convert energy to frequency";
+      result = "ENTOFR";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__FRTOEN:
+      *comment = "Convert frequency to energy";
+      result = "FRTOEN";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__WNTOFR:
+      *comment = "Convert wave number to frequency";
+      result = "WNTOFR";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__FRTOWN:
+      *comment = "Convert frequency to wave number";
+      result = "FRTOWN";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__WVTOFR:
+      *comment = "Convert wavelength (vacuum) to frequency";
+      result = "WVTOFR";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__FRTOWV:
+      *comment = "Convert frequency to wavelength (vacuum)";
+      result = "FRTOWV";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__AWTOFR:
+      *comment = "Convert wavelength (air) to frequency";
+      result = "AWTOFR";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__FRTOAW:
+      *comment = "Convert frequency to wavelength (air)";
+      result = "FRTOAW";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__VRTOVL:
+      *comment = "Convert radio to rel. velocity";
+      result = "VRTOVL";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__VLTOVR:
+      *comment = "Convert relativistic to radio velocity";
+      result = "VLTOVR";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__VOTOVL:
+      *comment = "Convert optical to rel. velocity";
+      result = "VOTOVL";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__VLTOVO:
+      *comment = "Convert relativistic to optical velocity";
+      result = "VLTOVO";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__ZOTOVL:
+      *comment = "Convert redshift to rel. velocity";
+      result = "ZOTOVL";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__VLTOZO:
+      *comment = "Convert rel. velocity to redshift";
+      result = "VLTOZO";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__BTTOVL:
+      *comment = "Convert beta factor to rel. velocity";
+      result = "BTTOVL";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__VLTOBT:
+      *comment = "Convert rel. velocity to beta factor";
+      result = "VLTOBT";
+      *nargs = 0;
+      *szargs = 0;
+      break;
+
+   case AST__USF2HL:
+      *comment = "Convert from user-defined to heliocentric frequency";
+      result = "USF2HL";
+      *argra = 1;
+      *argdec = 2;
+      *nargs = 3;
+      *szargs = 4;
+      arg[ 0 ] = "Velocity offset (m/s)";
+      arg[ 1 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 2 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 3 ] = "Frequency correction factor";
+      break;
+
+   case AST__HLF2US:
+      *comment = "Convert from heliocentric to user-defined frequency";
+      result = "HLF2US";
+      *argra = 1;
+      *argdec = 2;
+      *nargs = 3;
+      *szargs = 4;
+      arg[ 0 ] = "Velocity offset (m/s)";
+      arg[ 1 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 2 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 3 ] = "Frequency correction factor";
+      break;
+
+   case AST__TPF2HL:
+      *comment = "Convert from Topocentric to heliocentric frequency";
+      result = "TPF2HL";
+      *argra = 4;
+      *argdec = 5;
+      *nargs = 6;
+      *szargs = 7;
+      arg[ 0 ] = "Longitude (positive eastwards, radians)";
+      arg[ 1 ] = "Latitude (geodetic, radians)";
+      arg[ 2 ] = "Altitude (geodetic, metres)";
+      arg[ 3 ] = "UT1 epoch of observaton (Modified Julian Date)";
+      arg[ 4 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 5 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 6 ] = "Frequency correction factor";
+      break;
+
+   case AST__HLF2TP:
+      *comment = "Convert from Heliocentric to topocentric frequency";
+      result = "HLF2TP";
+      *argra = 4;
+      *argdec = 5;
+      *nargs = 6;
+      *szargs = 7;
+      arg[ 0 ] = "Longitude (positive eastwards, radians)";
+      arg[ 1 ] = "Latitude (geodetic, radians)";
+      arg[ 2 ] = "Altitude (geodetic, metres)";
+      arg[ 3 ] = "UT1 epoch of observaton (Modified Julian Date)";
+      arg[ 4 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 5 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 6 ] = "Frequency correction factor";
+      break;
+
+   case AST__GEF2HL:
+      *comment = "Convert from Geocentric to heliocentric frequency";
+      result = "GEF2HL";
+      *argra = 1;
+      *argdec = 2;
+      *nargs = 3;
+      *szargs = 4;
+      arg[ 0 ] = "UT1 epoch of observaton (Modified Julian Date)";
+      arg[ 1 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 2 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 3 ] = "Frequency correction factor";
+      break;
+
+   case AST__HLF2GE:
+      *comment = "Convert from Heliocentric to geocentric frequency";
+      result = "HLF2GE";
+      *argra = 1;
+      *argdec = 2;
+      *nargs = 3;
+      *szargs = 4;
+      arg[ 0 ] = "UT1 epoch of observaton (Modified Julian Date)";
+      arg[ 1 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 2 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 3 ] = "Frequency correction factor";
+      break;
+
+   case AST__BYF2HL:
+      *comment = "Convert from Barycentric to heliocentric frequency";
+      result = "BYF2HL";
+      *argra = 1;
+      *argdec = 2;
+      *nargs = 3;
+      *szargs = 4;
+      arg[ 0 ] = "UT1 epoch of observaton (Modified Julian Date)";
+      arg[ 1 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 2 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 3 ] = "Frequency correction factor";
+      break;
+
+   case AST__HLF2BY:
+      *comment = "Convert from Heliocentric to barycentric frequency";
+      result = "HLF2BY";
+      *argra = 1;
+      *argdec = 2;
+      *nargs = 3;
+      *szargs = 4;
+      arg[ 0 ] = "UT1 epoch of observaton (Modified Julian Date)";
+      arg[ 1 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 2 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 3 ] = "Frequency correction factor";
+      break;
+
+   case AST__LKF2HL:
+      *comment = "Convert from LSRK to heliocentric frequency";
+      result = "LKF2HL";
+      *argra = 0;
+      *argdec = 1;
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 1 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 2 ] = "Frequency correction factor";
+      break;
+
+   case AST__HLF2LK:
+      *comment = "Convert from Heliocentric to LSRK frequency";
+      result = "HLF2LK";
+      *argra = 0;
+      *argdec = 1;
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 1 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 2 ] = "Frequency correction factor";
+      break;
+
+   case AST__LDF2HL:
+      *comment = "Convert from LSRD to heliocentric frequency";
+      result = "LDF2HL";
+      *argra = 0;
+      *argdec = 1;
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 1 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 2 ] = "Frequency correction factor";
+      break;
+
+   case AST__HLF2LD:
+      *comment = "Convert from Heliocentric to LSRD frequency";
+      result = "HLF2LD";
+      *argra = 0;
+      *argdec = 1;
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 1 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 2 ] = "Frequency correction factor";
+      break;
+
+   case AST__LGF2HL:
+      *comment = "Convert from Local group to heliocentric frequency";
+      result = "LGF2HL";
+      *argra = 0;
+      *argdec = 1;
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 1 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 2 ] = "Frequency correction factor";
+      break;
+
+   case AST__HLF2LG:
+      *comment = "Convert from Heliocentric to local group frequency";
+      result = "HLF2LG";
+      *argra = 0;
+      *argdec = 1;
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 1 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 2 ] = "Frequency correction factor";
+      break;
+
+   case AST__GLF2HL:
+      *comment = "Convert from Galactic to heliocentric frequency";
+      result = "GLF2HL";
+      *argra = 0;
+      *argdec = 1;
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 1 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 2 ] = "Frequency correction factor";
+      break;
+
+   case AST__HLF2GL:
+      *comment = "Convert from Heliocentric to galactic frequency";
+      *argra = 0;
+      *argdec = 1;
+      result = "HLF2GL";
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "RA of source (FK5 J2000, radians)";
+      arg[ 1 ] = "DEC of source (FK5 J2000, radians)";
+      arg[ 2 ] = "Frequency correction factor";
+      break;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int FrameChange( int cvt_code, int np, double *ra, double *dec, double *freq,
+                        double *args, int forward, int *status ){
+/*
+*  Name:
+*     FrameChange
+
+*  Purpose:
+*     Apply a doppler shift caused by a change of reference frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     int FrameChange( int cvt_code, int np, double *ra, double *dec,
+*                      double *freq, double *args, int forward, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function modifies the supplied frequency values in order to
+*     apply a doppler shift caused by a change of the observers rest-frame.
+
+*  Parameters:
+*     cvt_code
+*        A code indicating the conversion to be applied. If the code does
+*        not correspond to a change of rest-frame, then the supplied
+*        frequencies are left unchanged and zero is returned as the
+*        function value.
+*     np
+*        The number of frequency values to transform.
+*     ra
+*        Pointer to an array of "np" RA (J2000 FK5) values at which the
+*        "np" frequencies are observed. These are unchanged on exit. If a
+*        NULL pointer is supplied, then all frequencies are assumed to be
+*        observed at the single RA value given by "refra"
+*     dec
+*        Pointer to an array of "np" Dec (J2000 FK5) values at which the
+*        "np" frequencies are observed. These are unchanged on exit. If a
+*        NULL pointer is supplied, then all frequencies are assumed to be
+*        observed at the single Dec value given by "refdec"
+*     freq
+*        Pointer to an array of "np" frequency values, measured in the
+*        input rest-frame. These are modified on return to hold the
+*        corresponding values measured in the output rest-frame.
+*     args
+*        Pointer to an array holding the conversion arguments. The number
+*        of arguments expected depends on the particular conversion being
+*        used.
+*     forward
+*        Should the conversion be applied in the forward or inverse
+*        direction? Non-zero for forward, zero for inverse.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the supplied conversion code corresponds to a change of
+*     reference frame. Zoer otherwise  (in which case the upplied values
+*     will not have been changed).
+
+*  Notes:
+*     - The "args" array contains RA and DEC values which give the "source"
+*     position (FK5 J2000). If a NULL value is supplied for the "ra"
+*     parameter, then these args define the position of all the frequency
+*     values. In addition they also define the direction of motion of
+*     the "user-defined" rest-frame (see "veluser"). Thus they should still
+*     be supplied even if "ra" is NULL.
+
+*/
+
+/* Local Variables: */
+   FrameDef def;      /* Structure holding frame parameters */
+   double (* cvtFunc)( double, double, FrameDef *, int * ); /* Pointer to conversion function */
+   double *fcorr;     /* Pointer to frequency correction factor */
+   double *pdec;      /* Pointer to next Dec value */
+   double *pf;        /* Pointer to next frequency value */
+   double *pra;       /* Pointer to next RA value */
+   double factor;     /* Frequency correction factor */
+   double s;          /* Velocity correction (m/s) */
+   int i;             /* Loop index */
+   int result;        /* Returned value */
+   int sign;          /* Sign for velocity correction */
+
+/* Check inherited status. */
+   if( !astOK ) return 0;
+
+/* Initialise */
+   cvtFunc = NULL;
+   fcorr = NULL;
+   sign = 0;
+
+/* Set the return value to indicate that the supplied conversion code
+   represents a change of rest-frame. */
+   result = 1;
+
+/* Initialise a structure which stores parameters which define the
+   transformation. */
+   def.obsalt = AST__BAD;
+   def.obslat = AST__BAD;
+   def.obslon = AST__BAD;
+   def.epoch = AST__BAD;
+   def.refdec = AST__BAD;
+   def.refra = AST__BAD;
+   def.veluser = AST__BAD;
+   def.last = AST__BAD;
+   def.amprms[ 0 ] = AST__BAD;
+   def.vuser[ 0 ] = AST__BAD;
+   def.dvh[ 0 ] = AST__BAD;
+   def.dvb[ 0 ] = AST__BAD;
+
+/* Test for each rest-frame code value in turn and assign the appropriate
+   values. */
+   switch ( cvt_code ) {
+
+   case AST__USF2HL:
+      cvtFunc = UserVel;
+      def.veluser = args[ 0 ];
+      def.refra = args[ 1 ];
+      def.refdec = args[ 2 ];
+      fcorr = args + 3;
+      sign = -1;
+      break;
+
+   case AST__HLF2US:
+      cvtFunc = UserVel;
+      def.veluser = args[ 0 ];
+      def.refra = args[ 1 ];
+      def.refdec = args[ 2 ];
+      fcorr = args + 3;
+      sign = +1;
+      break;
+
+   case AST__TPF2HL:
+      cvtFunc = TopoVel;
+      def.obslon = args[ 0 ];
+      def.obslat = args[ 1 ];
+      def.obsalt = args[ 2 ];
+      def.epoch = args[ 3 ];
+      def.refra = args[ 4 ];
+      def.refdec = args[ 5 ];
+      fcorr = args + 6;
+      sign = -1;
+      break;
+
+   case AST__HLF2TP:
+      cvtFunc = TopoVel;
+      def.obslon = args[ 0 ];
+      def.obslat = args[ 1 ];
+      def.obsalt = args[ 2 ];
+      def.epoch = args[ 3 ];
+      def.refra = args[ 4 ];
+      def.refdec = args[ 5 ];
+      fcorr = args + 6;
+      sign = +1;
+      break;
+
+   case AST__GEF2HL:
+      cvtFunc = GeoVel;
+      def.epoch = args[ 0 ];
+      def.refra = args[ 1 ];
+      def.refdec = args[ 2 ];
+      fcorr = args + 3;
+      sign = -1;
+      break;
+
+   case AST__HLF2GE:
+      cvtFunc = GeoVel;
+      def.epoch = args[ 0 ];
+      def.refra = args[ 1 ];
+      def.refdec = args[ 2 ];
+      fcorr = args + 3;
+      sign = +1;
+      break;
+
+   case AST__BYF2HL:
+      cvtFunc = BaryVel;
+      def.epoch = args[ 0 ];
+      def.refra = args[ 1 ];
+      def.refdec = args[ 2 ];
+      fcorr = args + 3;
+      sign = -1;
+      break;
+
+   case AST__HLF2BY:
+      cvtFunc = BaryVel;
+      def.epoch = args[ 0 ];
+      def.refra = args[ 1 ];
+      def.refdec = args[ 2 ];
+      fcorr = args + 3;
+      sign = +1;
+      break;
+
+   case AST__LKF2HL:
+      cvtFunc = LsrkVel;
+      def.refra = args[ 0 ];
+      def.refdec = args[ 1 ];
+      fcorr = args + 2;
+      sign = -1;
+      break;
+
+   case AST__HLF2LK:
+      cvtFunc = LsrkVel;
+      def.refra = args[ 0 ];
+      def.refdec = args[ 1 ];
+      fcorr = args + 2;
+      sign = +1;
+      break;
+
+   case AST__LDF2HL:
+      cvtFunc = LsrdVel;
+      def.refra = args[ 0 ];
+      def.refdec = args[ 1 ];
+      fcorr = args + 2;
+      sign = -1;
+      break;
+
+   case AST__HLF2LD:
+      cvtFunc = LsrdVel;
+      def.refra = args[ 0 ];
+      def.refdec = args[ 1 ];
+      fcorr = args + 2;
+      sign = +1;
+      break;
+
+   case AST__LGF2HL:
+      cvtFunc = LgVel;
+      def.refra = args[ 0 ];
+      def.refdec = args[ 1 ];
+      fcorr = args + 2;
+      sign = -1;
+      break;
+
+   case AST__HLF2LG:
+      cvtFunc = LgVel;
+      def.refra = args[ 0 ];
+      def.refdec = args[ 1 ];
+      fcorr = args + 2;
+      sign = +1;
+      break;
+
+   case AST__GLF2HL:
+      cvtFunc = GalVel;
+      def.refra = args[ 0 ];
+      def.refdec = args[ 1 ];
+      fcorr = args + 2;
+      sign = -1;
+      break;
+
+   case AST__HLF2GL:
+      cvtFunc = GalVel;
+      def.refra = args[ 0 ];
+      def.refdec = args[ 1 ];
+      fcorr = args + 2;
+      sign = +1;
+      break;
+
+/* If the supplied code does not represent a change of rest-frame, clear
+   the returned flag. */
+   default:
+      result = 0;
+   }
+
+/* Check we have a rest-frame code. */
+   if( result ) {
+
+/* First deal with cases where we have a single source position (given by
+   refra and refdec). */
+      if( !ra ) {
+
+/* If the frequency correction factor has not been found, find it now. */
+         if( *fcorr == AST__BAD ) {
+
+/* Get the velocity correction. This is the component of the velocity of the
+   output system, away from the source, as measured in the input system. */
+            s = sign*cvtFunc( def.refra, def.refdec, &def, status );
+
+/* Find the factor by which to correct supplied frequencies. If the
+   velocity correction is positive, the output frequency wil be lower than
+   the input frequency. */
+            if( s < AST__C && s > -AST__C ) {
+               *fcorr = sqrt( ( AST__C - s )/( AST__C + s ) );
+            }
+         }
+
+/* Correct each supplied frequency. */
+         if( *fcorr != AST__BAD && *fcorr != 0.0 ) {
+            factor = forward ? *fcorr : 1.0 / ( *fcorr );
+            pf = freq;
+            for( i = 0; i < np; i++, pf++ ) {
+               if( *pf != AST__BAD ) *pf *= factor;
+            }
+
+/* Set returned values bad if the velocity correction is un-physical. */
+         } else {
+            pf = freq;
+            for( i = 0; i < np; i++ ) *(pf++) = AST__BAD;
+         }
+
+/* Now deal with cases where each frequency value has its own source
+   position. */
+      } else {
+
+/* Invert the sign if we are doing a inverse transformation. */
+         if( !forward ) sign = -sign;
+
+/* Loop round each value. */
+         pf = freq;
+         pra = ra;
+         pdec = dec;
+         for( i = 0; i < np; i++ ) {
+
+/* If the ra or dec is bad, store a bad frequency. */
+            if( *pra == AST__BAD || *pdec == AST__BAD || *pf == AST__BAD ) {
+               *pf = AST__BAD;
+
+/* Otherwise, produce a corrected frequency. */
+            } else {
+
+/* Get the velocity correction. */
+               s = sign*cvtFunc( *pra, *pdec, &def, status );
+
+/* Correct this frequency, if possible. Otherwise set bad. */
+               if( s < AST__C && s > -AST__C ) {
+                  *pf *= sqrt( ( AST__C - s )/( AST__C + s ) );
+               } else {
+                  *pf = AST__BAD;
+               }
+            }
+
+/* Move on to the next position. */
+            pf++;
+            pra++;
+            pdec++;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GalVel( double ra, double dec, FrameDef *def, int *status ) {
+/*
+*  Name:
+*     GalVel
+
+*  Purpose:
+*     Find the velocity of the galactic centre away from the source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     double GalVel( double ra, double dec, FrameDef *def, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function finds the component of the velocity of the galactic
+*     centre away from a specified source position, in the frame of rest
+*     of the Sun.
+
+*  Parameters:
+*     ra
+*        The RA (rads, FK5 J2000) of the source.
+*     dec
+*        The Dec (rads, FK5 J2000) of the source.
+*     def
+*        Pointer to a FrameDef structure which holds the parameters which
+*        define the frame, together with cached intermediate results.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     The component of the frame's velocity away from the position given by
+*     "ra" and "dec", in m/s, measured within the Heliographic frame of
+*     rest. Zero is returned if an error has already occurred.
+
+*/
+
+/* Local Variables: */
+   double s1, s2;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Get the component away from the source, of the velocity of the sun
+   relative to the dynamic LSR (in km/s). */
+   s1 = (double) palRvlsrd( (float) ra, (float) dec );
+
+/* Get the component away from the source, of the velocity of the
+   dynamic LSR relative to the galactic centre (in km/s). */
+   s2 = (double) palRvgalc( (float) ra, (float) dec );
+
+/* Return the total velocity of the galactic centre away from the source,
+   relative to the sun, in m/s. */
+   return -1000.0*( s1 + s2 );
+}
+
+static double GeoVel( double ra, double dec, FrameDef *def, int *status ) {
+/*
+*  Name:
+*     GeoVel
+
+*  Purpose:
+*     Find the velocity of the earth away from the source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     double GeoVel( double ra, double dec, FrameDef *def, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function finds the component of the velocity of the earth away
+*     from a specified source position, at a given epoch, in the frame of
+*     rest of the Sun.
+
+*  Parameters:
+*     ra
+*        The RA (rads, FK5 J2000) of the source.
+*     dec
+*        The Dec (rads, FK5 J2000) of the source.
+*     def
+*        Pointer to a FrameDef structure which holds the parameters which
+*        define the frame, together with cached intermediate results.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     The component of the frame's velocity away from the position given by
+*     "ra" and "dec", in m/s, measured within the Heliographic frame of
+*     rest. Zero is returned if an error has already occurred.
+
+*/
+
+/* Local Variables: */
+   double dpb[ 3 ];          /* Barycentric earth position vector */
+   double dph[ 3 ];          /* Heliocentric earth position vector */
+   double dvb[ 3 ];          /* Barycentric earth velocity vector */
+   double v[ 3 ];            /* Source direction vector */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Get the Cartesian vector towards the source, in the Cartesian FK5
+   J2000 system. */
+   palDcs2c( ra, dec, v );
+
+/* If not already done so, get the Earth/Sun velocity and position vectors in
+   the same system. Speed is returned in units of AU/s. Store in the supplied
+   frame definition structure. */
+   if( def->dvh[ 0 ] == AST__BAD ) palEvp( def->epoch, 2000.0, dvb, dpb,
+                                           def->dvh, dph );
+
+/* Return the component away from the source, of the velocity of the earths
+   centre relative to the sun (in m/s). */
+   return -palDvdv( v, def->dvh )*149.597870E9;
+}
+
+void astInitSpecMapVtab_(  AstSpecMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSpecMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a SpecMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     void astInitSpecMapVtab( AstSpecMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SpecMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the SpecMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsASpecMap) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->SpecAdd = SpecAdd;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_rate = mapping->Rate;
+   mapping->Rate = Rate;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "SpecMap",
+               "Conversion between spectral coordinate systems" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static double LgVel( double ra, double dec, FrameDef *def, int *status ) {
+/*
+*  Name:
+*     LgVel
+
+*  Purpose:
+*     Find the velocity of the Local Group away from the source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     double LgVel( double ra, double dec, FrameDef *def, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function finds the component of the Local Group velocity away
+*     from a specified source position, in the frame of rest of the Sun.
+
+*  Parameters:
+*     ra
+*        The RA (rads, FK5 J2000) of the source.
+*     dec
+*        The Dec (rads, FK5 J2000) of the source.
+*     def
+*        Pointer to a FrameDef structure which holds the parameters which
+*        define the frame, together with cached intermediate results.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     The component of the frame's velocity away from the position given by
+*     "ra" and "dec", in m/s, measured within the Heliographic frame of
+*     rest. Zero is returned if an error has already occurred.
+
+*/
+
+/* Return the component away from the source, of the velocity of the
+   local group relative to the sun (in m/s). */
+   return -1000.0*palRvlg( (float) ra, (float) dec );
+}
+
+static double LsrdVel( double ra, double dec, FrameDef *def, int *status ) {
+/*
+*  Name:
+*     LsrdVel
+
+*  Purpose:
+*     Find the velocity of the Dynamical LSR away from the source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     double LsrdVel( double ra, double dec, FrameDef *def, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function finds the component of the velocity of the Dynamical
+*     LSR away from a specified source position, in the frame of rest of
+*     the Sun.
+
+*  Parameters:
+*     ra
+*        The RA (rads, FK5 J2000) of the source.
+*     dec
+*        The Dec (rads, FK5 J2000) of the source.
+*     def
+*        Pointer to a FrameDef structure which holds the parameters which
+*        define the frame, together with cached intermediate results.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     The component of the frame's velocity away from the position given by
+*     "ra" and "dec", in m/s, measured within the Heliographic frame of
+*     rest. Zero is returned if an error has already occurred.
+
+*/
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Get the component away from the source, of the velocity of the sun
+   relative to the dynamical LSR (in m/s). This can also be thought of as the
+   velocity of the LSR towards the source relative to the sun. Return the
+   negated value (i.e. velocity of lsrd *away from* the source. */
+   return -1000.0*palRvlsrd( (float) ra, (float) dec );
+}
+
+static double LsrkVel( double ra, double dec, FrameDef *def, int *status ) {
+/*
+*  Name:
+*     LsrkVel
+
+*  Purpose:
+*     Find the velocity of the Kinematic LSR away from the source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     double LsrkVel( double ra, double dec, FrameDef *def, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function finds the component of the velocity of the Kinematic
+*     LSR away from a specified source position, in the frame of rest of
+*     the Sun.
+
+*  Parameters:
+*     ra
+*        The RA (rads, FK5 J2000) of the source.
+*     dec
+*        The Dec (rads, FK5 J2000) of the source.
+*     def
+*        Pointer to a FrameDef structure which holds the parameters which
+*        define the frame, together with cached intermediate results.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     The component of the frame's velocity away from the position given by
+*     "ra" and "dec", in m/s, measured within the Heliographic frame of
+*     rest. Zero is returned if an error has already occurred.
+
+*/
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Get the component away from the source, of the velocity of the sun
+   relative to the kinematic LSR (in m/s). This can also be thought of as the
+   velocity of the LSR towards the source relative to the sun. Return the
+   negated value (i.e. velocity of lsrk *away from* the source. */
+   return -1000.0*palRvlsrk( (float) ra, (float) dec );
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a SpecMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     SpecMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated SpecMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated SpecMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated SpecMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the SpecMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        SpecMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated SpecMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *new;              /* Pointer to replacement Mapping */
+   AstSpecMap *specmap;            /* Pointer to SpecMap */
+   const char *argdesc[ MAX_ARGS ]; /* Argument descriptions (junk) */
+   const char *class;            /* Pointer to Mapping class string */
+   const char *comment;          /* Pointer to comment string (junk) */
+   double (*cvtargs)[ MAX_ARGS ]; /* Pointer to argument arrays */
+   double tmp;                   /* Temporary storage */
+   int *cvttype;                 /* Pointer to transformation type codes */
+   int *narg;                    /* Pointer to argument count */
+   int *szarg;                   /* Pointer to argument array size */
+   int argdec;                   /* Index of DEC argument */
+   int argra;                    /* Index of RA argument */
+   int done;                     /* Finished (no further simplification)? */
+   int iarg;                     /* Loop counter for arguments */
+   int icvt1;                    /* Loop initial value */
+   int icvt2;                    /* Loop final value */
+   int icvt;                     /* Loop counter for transformation steps */
+   int ikeep;                    /* Index to store step being kept */
+   int imap1;                    /* Index of first SpecMap to merge */
+   int imap2;                    /* Index of last SpecMap to merge */
+   int imap;                     /* Loop counter for Mappings */
+   int inc;                      /* Increment for transformation step loop */
+   int invert;                   /* SpecMap applied in inverse direction? */
+   int istep;                    /* Loop counter for transformation steps */
+   int keep;                     /* Keep transformation step? */
+   int ngone;                    /* Number of Mappings eliminated */
+   int nin;                      /* Numbr of axes for SpecMaps being merged */
+   int nstep0;                   /* Original number of transformation steps */
+   int nstep;                    /* Total number of transformation steps */
+   int result;                   /* Result value to return */
+   int simpler;                  /* Simplification possible? */
+   int unit;                     /* Replacement Mapping is a UnitMap? */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* SpecMaps can only be merged if they are in series (or if there is
+   only one Mapping present, in which case it makes no difference), so
+   do nothing if they are not. */
+   if ( series || ( *nmap == 1 ) ) {
+
+/* Save the number of inputs for the SpecMap. */
+      nin = astGetNin( this );
+
+/* Initialise the number of transformation steps to be merged to equal
+   the number in the nominated SpecMap. */
+      nstep = ( (AstSpecMap *) ( *map_list )[ where ] )->ncvt;
+
+/* Search adjacent lower-numbered Mappings until one is found which is
+   not a SpecMap, or is a SpecMap with a different number of axes. Accumulate
+   the number of transformation steps involved in any SpecMaps found. */
+      imap1 = where;
+      while ( ( imap1 - 1 >= 0 ) && astOK ) {
+         class = astGetClass( ( *map_list )[ imap1 - 1 ] );
+         if ( !astOK || strcmp( class, "SpecMap" ) ||
+              astGetNin( ( *map_list )[ imap1 - 1 ] ) != nin ) break;
+         nstep += ( (AstSpecMap *) ( *map_list )[ imap1 - 1 ] )->ncvt;
+         imap1--;
+      }
+
+/* Similarly search adjacent higher-numbered Mappings. */
+      imap2 = where;
+      while ( ( imap2 + 1 < *nmap ) && astOK ) {
+         class = astGetClass( ( *map_list )[ imap2 + 1 ] );
+         if ( !astOK || strcmp( class, "SpecMap" ) ||
+              astGetNin( ( *map_list )[ imap2 + 1 ] ) != nin ) break;
+         nstep += ( (AstSpecMap *) ( *map_list )[ imap2 + 1 ] )->ncvt;
+         imap2++;
+      }
+
+/* Remember the initial number of transformation steps. */
+      nstep0 = nstep;
+
+/* Allocate memory for accumulating a list of all the transformation
+   steps involved in all the SpecMaps found. */
+      cvttype = astMalloc( sizeof( int ) * (size_t) nstep );
+      cvtargs = astMalloc( sizeof( double[ MAX_ARGS ] ) * (size_t) nstep );
+      szarg = astMalloc( sizeof( int ) * (size_t) nstep );
+      narg = astMalloc( sizeof( int ) * (size_t) nstep );
+
+/* Loop to obtain the transformation data for each SpecMap being merged. */
+      nstep = 0;
+      for ( imap = imap1; astOK && ( imap <= imap2 ); imap++ ) {
+
+/* Obtain a pointer to the SpecMap and note if it is being applied in
+   its inverse direction. */
+         specmap = (AstSpecMap *) ( *map_list )[ imap ];
+         invert = ( *invert_list )[ imap ];
+
+/* Set up loop limits and an increment to scan the transformation
+   steps in each SpecMap in either the forward or reverse direction, as
+   dictated by the associated "invert" value. */
+         icvt1 = invert ? specmap->ncvt - 1 : 0;
+         icvt2 = invert ? -1 : specmap->ncvt;
+         inc = invert ? -1 : 1;
+
+/* Loop through each transformation step in the SpecMap. */
+         for ( icvt = icvt1; icvt != icvt2; icvt += inc ) {
+
+/* Store the transformation type code and use "CvtString" to determine
+   the associated number of arguments. Then store these arguments. */
+            cvttype[ nstep ] = specmap->cvttype[ icvt ];
+            (void) CvtString( cvttype[ nstep ], &comment, &argra, &argdec,
+                              narg + nstep, szarg + nstep, argdesc, status );
+            if ( !astOK ) break;
+            for ( iarg = 0; iarg < szarg[ nstep ]; iarg++ ) {
+               cvtargs[ nstep ][ iarg ] = specmap->cvtargs[ icvt ][ iarg ];
+            }
+
+/* If the SpecMap is inverted, we must not only accumulate its
+   transformation steps in reverse, but also apply them in
+   reverse. For some steps this means changing arguments, for some it
+   means changing the transformation type code to a complementary
+   value, and for others it means both.  Define macros to perform each
+   of the required changes. */
+
+/* Macro to exchange a transformation type code for its inverse (and
+   vice versa). */
+#define SWAP_CODES( code1, code2 ) \
+            if ( cvttype[ nstep ] == code1 ) { \
+               cvttype[ nstep ] = code2; \
+            } else if ( cvttype[ nstep ] == code2 ) { \
+               cvttype[ nstep ] = code1; \
+            }
+
+/* Macro to exchange a transformation type code for its inverse (and
+   vice versa), and reciprocate a specified argument. */
+#define SWAP_CODES2( code1, code2, jarg ) \
+            if ( cvttype[ nstep ] == code1 ) { \
+               cvttype[ nstep ] = code2; \
+               tmp = cvtargs[ nstep ][ jarg ]; \
+               if( tmp != AST__BAD && tmp != 0.0 ) { \
+                  cvtargs[ nstep ][ jarg ] = 1.0/tmp; \
+               } else { \
+                  cvtargs[ nstep ][ jarg ] = AST__BAD; \
+               } \
+            } else if ( cvttype[ nstep ] == code2 ) { \
+               cvttype[ nstep ] = code1; \
+               tmp = cvtargs[ nstep ][ jarg ]; \
+               if( tmp != AST__BAD && tmp != 0.0 ) { \
+                  cvtargs[ nstep ][ jarg ] = 1.0/tmp; \
+               } else { \
+                  cvtargs[ nstep ][ jarg ] = AST__BAD; \
+               } \
+            }
+
+/* Macro to exchange a transformation type code for its inverse (and
+   vice versa), and negate a specified argument. */
+#define SWAP_CODES3( code1, code2, jarg ) \
+            if ( cvttype[ nstep ] == code1 ) { \
+               cvttype[ nstep ] = code2; \
+               tmp = cvtargs[ nstep ][ jarg ]; \
+               if( tmp != AST__BAD ) { \
+                  cvtargs[ nstep ][ jarg ] = -tmp; \
+               } \
+            } else if ( cvttype[ nstep ] == code2 ) { \
+               cvttype[ nstep ] = code1; \
+               tmp = cvtargs[ nstep ][ jarg ]; \
+               if( tmp != AST__BAD ) { \
+                  cvtargs[ nstep ][ jarg ] = -tmp; \
+               } \
+            }
+
+/* Use these macros to apply the changes where needed. */
+            if ( invert ) {
+
+/* Exchange transformation codes for their inverses. */
+               SWAP_CODES( AST__FRTOVL, AST__VLTOFR )
+               SWAP_CODES( AST__ENTOFR, AST__FRTOEN )
+               SWAP_CODES( AST__WNTOFR, AST__FRTOWN )
+               SWAP_CODES( AST__WVTOFR, AST__FRTOWV )
+               SWAP_CODES( AST__AWTOFR, AST__FRTOAW )
+               SWAP_CODES( AST__VRTOVL, AST__VLTOVR )
+               SWAP_CODES( AST__VOTOVL, AST__VLTOVO )
+               SWAP_CODES( AST__ZOTOVL, AST__VLTOZO )
+               SWAP_CODES( AST__BTTOVL, AST__VLTOBT )
+
+/* Exchange transformation codes for their inverses, and reciprocate the
+   frequency correction factor. */
+               SWAP_CODES2( AST__TPF2HL, AST__HLF2TP, 6 )
+               SWAP_CODES2( AST__USF2HL, AST__HLF2US, 3 )
+               SWAP_CODES2( AST__GEF2HL, AST__HLF2GE, 3 )
+               SWAP_CODES2( AST__BYF2HL, AST__HLF2BY, 3 )
+               SWAP_CODES2( AST__LKF2HL, AST__HLF2LK, 2 )
+               SWAP_CODES2( AST__LDF2HL, AST__HLF2LD, 2 )
+               SWAP_CODES2( AST__LGF2HL, AST__HLF2LG, 2 )
+               SWAP_CODES2( AST__GLF2HL, AST__HLF2GL, 2 )
+
+            }
+
+/* Undefine the local macros. */
+#undef SWAP_CODES
+#undef SWAP_CODES2
+#undef SWAP_CODES3
+
+/* Count the transformation steps. */
+            nstep++;
+         }
+      }
+
+/* Loop to simplify the sequence of transformation steps until no
+   further improvement is possible. */
+      done = 0;
+      while ( astOK && !done ) {
+
+/* Examine each remaining transformation step in turn.  */
+         ikeep = -1;
+         for ( istep = 0; istep < nstep; istep++ ) {
+
+/* Initially assume we will retain the current step. */
+            keep = 1;
+
+/* The only simplifications for the conversions currently in this class act
+   to combine adjacent transformation steps, so only apply them while there
+   are at least 2 steps left. */
+            if ( istep < ( nstep - 1 ) ) {
+
+/* Define a macro to test if two adjacent transformation type codes
+   have specified values. */
+#define PAIR_CVT( code1, code2 ) \
+               ( ( cvttype[ istep ] == code1 ) && \
+                 ( cvttype[ istep + 1 ] == code2 ) )
+
+/* Define a macro to test if two adjacent transformation type codes
+   have specified values, either way round. */
+#define PAIR_CVT2( code1, code2 ) \
+               ( ( PAIR_CVT( code1, code2 ) ) || \
+                 ( PAIR_CVT( code2, code1 ) ) )
+
+/* If a correction is followed by its inverse, and the user-supplied argument
+   values are unchanged (we do not need to test values stored in the
+   argument array which were not supplied by the user), we can eliminate them.
+   First check for conversions which have no user-supplied arguments. */
+               if ( PAIR_CVT2( AST__ENTOFR, AST__FRTOEN ) ||
+                    PAIR_CVT2( AST__WNTOFR, AST__FRTOWN ) ||
+                    PAIR_CVT2( AST__WVTOFR, AST__FRTOWV ) ||
+                    PAIR_CVT2( AST__AWTOFR, AST__FRTOAW ) ||
+                    PAIR_CVT2( AST__VRTOVL, AST__VLTOVR ) ||
+                    PAIR_CVT2( AST__VOTOVL, AST__VLTOVO ) ||
+                    PAIR_CVT2( AST__ZOTOVL, AST__VLTOZO ) ||
+                    PAIR_CVT2( AST__BTTOVL, AST__VLTOBT ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Now check for conversions which have a single user-supplied argument. */
+               } else if( PAIR_CVT2( AST__FRTOVL, AST__VLTOFR ) &&
+                          astEQUAL( cvtargs[ istep ][ 0 ],
+                                 cvtargs[ istep + 1 ][ 0 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Now check for conversions which have two user-supplied arguments. */
+               } else if( ( PAIR_CVT2( AST__LKF2HL, AST__HLF2LK ) ||
+                            PAIR_CVT2( AST__LDF2HL, AST__HLF2LD ) ||
+                            PAIR_CVT2( AST__LGF2HL, AST__HLF2LG ) ||
+                            PAIR_CVT2( AST__GLF2HL, AST__HLF2GL ) ) &&
+                          astEQUAL( cvtargs[ istep ][ 0 ],
+                                 cvtargs[ istep + 1 ][ 0 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 1 ],
+                                 cvtargs[ istep + 1 ][ 1 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Now check for conversions which have three user-supplied arguments. */
+               } else if( ( PAIR_CVT2( AST__GEF2HL, AST__HLF2GE ) ||
+                            PAIR_CVT2( AST__BYF2HL, AST__HLF2BY ) ||
+                            PAIR_CVT2( AST__USF2HL, AST__HLF2US ) ) &&
+                          astEQUAL( cvtargs[ istep ][ 0 ],
+                                 cvtargs[ istep + 1 ][ 0 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 1 ],
+                                 cvtargs[ istep + 1 ][ 1 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 2 ],
+                                 cvtargs[ istep + 1 ][ 2 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Now check for conversions which have six user-supplied arguments (currently
+   no conversions have four or five user-supplied arguments). */
+               } else if( ( PAIR_CVT2( AST__TPF2HL, AST__HLF2TP ) ) &&
+                          astEQUAL( cvtargs[ istep ][ 0 ],
+                                 cvtargs[ istep + 1 ][ 0 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 1 ],
+                                 cvtargs[ istep + 1 ][ 1 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 2 ],
+                                 cvtargs[ istep + 1 ][ 2 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 3 ],
+                                 cvtargs[ istep + 1 ][ 3 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 4 ],
+                                 cvtargs[ istep + 1 ][ 4 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 5 ],
+                                 cvtargs[ istep + 1 ][ 5 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+               }
+
+/* Undefine the local macros. */
+#undef PAIR_CVT
+#undef PAIR_CVT2
+            }
+
+/* If the current transformation (possibly modified above) is being
+   kept, then increment the index that identifies its new location in
+   the list of transformation steps. */
+            if ( keep ) {
+               ikeep++;
+
+/* If the new location is different to its current location, copy the
+   transformation data into the new location. */
+               if ( ikeep != istep ) {
+                  cvttype[ ikeep ] = cvttype[ istep ];
+                  for ( iarg = 0; iarg < szarg[ istep ]; iarg++ ) {
+                     cvtargs[ ikeep ][ iarg ] = cvtargs[ istep ][ iarg ];
+                  }
+                  szarg[ ikeep ] = szarg[ istep ];
+                  narg[ ikeep ] = narg[ istep ];
+               }
+            }
+         }
+
+/* Note if no simplification was achieved on this iteration (i.e. the
+   number of transformation steps was not reduced). This is the signal
+   to quit. */
+         done = ( ( ikeep + 1 ) >= nstep );
+
+/* Note how many transformation steps now remain. */
+         nstep = ikeep + 1;
+      }
+
+/* Determine how many Mappings can be eliminated by condensing all
+   those considered above into a single Mapping. */
+      if ( astOK ) {
+         ngone = imap2 - imap1;
+
+/* Determine if the replacement Mapping can be a UnitMap (a null
+   Mapping). This will only be the case if all the transformation
+   steps were eliminated above. */
+         unit = ( nstep == 0 );
+
+/* Determine if simplification is possible. This will be the case if
+   (a) Mappings were eliminated ("ngone" is non-zero), or (b) the
+   number of transformation steps was reduced, or (c) the SpecMap(s)
+   can be replaced by a UnitMap, or (d) if there was initially only
+   one SpecMap present, its invert flag was set (this flag will always
+   be cleared in the replacement Mapping). */
+         simpler = ngone || ( nstep < nstep0 ) || unit ||
+                   ( *invert_list )[ where ];
+
+/* Do nothing more unless simplification is possible. */
+         if ( simpler ) {
+
+/* If the replacement Mapping is a UnitMap, then create it. */
+            if ( unit ) {
+               new = (AstMapping *)
+                        astUnitMap( astGetNin( ( *map_list )[ where ] ), "", status );
+
+/* Otherwise, create a replacement SpecMap and add each of the
+   remaining transformation steps to it. */
+            } else {
+               new = (AstMapping *) astSpecMap( nin, 0, "", status );
+               for ( istep = 0; istep < nstep; istep++ ) {
+                  AddSpecCvt( (AstSpecMap *) new, cvttype[ istep ],
+                             narg[ istep ], cvtargs[ istep ], status );
+               }
+            }
+
+/* Annul the pointers to the Mappings being eliminated. */
+            if ( astOK ) {
+               for ( imap = imap1; imap <= imap2; imap++ ) {
+                  ( *map_list )[ imap ] = astAnnul( ( *map_list )[ imap ] );
+               }
+
+/* Insert the pointer and invert value for the new Mapping. */
+               ( *map_list )[ imap1 ] = new;
+               ( *invert_list )[ imap1 ] = 0;
+
+/* Move any subsequent Mapping information down to close the gap. */
+               for ( imap = imap2 + 1; imap < *nmap; imap++ ) {
+                  ( *map_list )[ imap - ngone ] = ( *map_list )[ imap ];
+                  ( *invert_list )[ imap - ngone ] = ( *invert_list )[ imap ];
+               }
+
+/* Blank out any information remaining at the end of the arrays. */
+               for ( imap = ( *nmap - ngone ); imap < *nmap; imap++ ) {
+                  ( *map_list )[ imap ] = NULL;
+                  ( *invert_list )[ imap ] = 0;
+               }
+
+/* Decrement the Mapping count and return the index of the first
+   Mapping which was eliminated. */
+               ( *nmap ) -= ngone;
+               result = imap1;
+
+/* If an error occurred, annul the new Mapping pointer. */
+            } else {
+               new = astAnnul( new );
+            }
+         }
+      }
+
+/* Free the memory used for the transformation steps. */
+      cvttype = astFree( cvttype );
+      cvtargs = astFree( cvtargs );
+      szarg = astFree( szarg );
+      narg = astFree( narg );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     SpecMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*  Implementation Deficiencies:
+*     The initial version of this implementation only deals with
+*     frequency->wavelength conversions. This is because the slowness of
+*     the numerical differentiation implemented by the astRate method in
+*     the parent Mapping class is cripples conversion between SpecFluxFrames.
+*     Such conversions only rely on rate of change of wavelength with
+*     respect to frequency. This implementation should be extended when
+*     needed.
+
+*/
+
+/* Local Variables: */
+   AstSpecMap *map;
+   double result;
+   int cvt;
+
+/* Check inherited status */
+   if( !astOK ) return AST__BAD;
+
+/* Get a pointer to the SpecMap structure. */
+   map = (AstSpecMap *) this;
+
+/* Return 1.0 if the SpecMap has no conversions. */
+   if( map->ncvt == 0 ) return 1.0;
+
+/* Store the type of the first conversion.*/
+   cvt = map->cvttype[ 0 ];
+
+/* If this is a 3D SpecMap or if it has more than one component, or if
+   that conversion is not between frequency and wavelength, use the
+   astRate method inherited form the parent Mapping class. */
+   if( astGetNin( map ) != 1 || map->ncvt != 1 ||
+       ( cvt != AST__WVTOFR && cvt != AST__FRTOWV ) ) {
+      result = (*parent_rate)( this, at, ax1, ax2, status );
+
+/* Otherwise, evaluate the known analytical expressions for the rate of
+   change of frequency with respect to wavelength or wavelength with
+   respect to frequency. */
+   } else {
+      result = ( *at != AST__BAD ) ? -AST__C/((*at)*(*at)) : AST__BAD;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double Refrac( double wavelen, int *status ){
+/*
+*  Name:
+*     Refrac
+
+*  Purpose:
+*     Returns the refractive index of dry air at a given wavelength.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     double Refrac( double wavelen, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function returns the refractive index of dry air at standard
+*     temperature and pressure, at a given wavelength. The formula is
+*     taken from the paper "Representation of Spectral Coordinates in FITS"
+*     (Greisen et al).
+
+*  Parameters:
+*     wavelen
+*        The wavelength, in metres. This should be the air wavelength,
+*        but supplying the vacuum wavelength will make no significant
+*        difference.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The refractive index. A value of 1.0 is returned if an error
+*     occurs, or has already occurred.
+
+*/
+
+/* Local Variables: */
+   double w2;                /* Wavenumber squared */
+
+/* Check the global error status. */
+   if ( !astOK || wavelen == 0.0 ) return 1.0;
+
+/* Find the squared wave number in units of "(per um)**2". */
+   w2 = 1.0E-12/( wavelen * wavelen );
+
+/* Apply the rest of the algorithm as described in the FITS WCS
+   paper III. */
+   return 1.0 + 1.0E-6*( 287.6155 + 1.62887*w2 + 0.01360*w2*w2 );
+}
+
+static double Rverot( double phi, double h, double ra, double da,
+                      double st, int *status ) {
+/*
+*  Name:
+*     Rverot
+
+*  Purpose:
+*     Find the velocity component in a given direction due to Earth rotation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     double Rverot( double phi, double h, double ra, double da,
+*                    double st, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function is like slaRverot, except that it takes account of the
+*     observers height (h), and does all calculations in double precision.
+
+*  Parameters:
+*     phi
+*        The geodetic latitude of the observer (radians, IAU 1976).
+*     h
+*        The geodetic height above the reference spheroid of the observer
+*        (metres, IAU 1976).
+*     ra
+*        The geocentric apparent RA (rads) of the source.
+*     da
+*        The geocentric apparent Dec (rads) of the source.
+*     st
+*        The local apparent sidereal time (radians).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     The component of the Earth rotation in direction [RA,DA] (km/s).
+*     The result is positive when the observer is receding from the
+*     given point on the sky. Zero is returned if an error has already
+*     occurred.
+
+*/
+
+/* Local Variables: */
+   double pv[ 6 ];           /* Observer position and velocity */
+   double v[ 3 ];            /* Source direction vector */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Get the Cartesian coordinates of the unit vector pointing towards the
+   given sky position. */
+   palDcs2c( ra, da, v );
+
+/* Get velocity and position of the observer. */
+   palPvobs( phi, h, st, pv );
+
+/* Return the component of the observer's velocity away from the sky
+   position, and convert from AU/s to km/s. */
+   return -palDvdv( v, pv + 3 )*149.597870E6;
+}
+
+static void SpecAdd( AstSpecMap *this, const char *cvt, int narg,
+                     const double args[], int *status ) {
+/*
+*++
+*  Name:
+c     astSpecAdd
+f     AST_SPECADD
+
+*  Purpose:
+*     Add a spectral coordinate conversion to a SpecMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "specmap.h"
+c     void astSpecAdd( AstSpecMap *this, const char *cvt, int narg,
+c                      const double args[] )
+f     CALL AST_SPECADD( THIS, CVT, NARG, ARGS, STATUS )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+c     This function adds one of the standard spectral coordinate
+f     This routine adds one of the standard spectral coordinate
+*     system conversions listed below to an existing SpecMap.
+*
+c     When a SpecMap is first created (using astSpecMap), it simply
+f     When a SpecMap is first created (using AST_SPECMAP), it simply
+c     performs a unit (null) Mapping. By using astSpecAdd (repeatedly
+f     performs a unit (null) Mapping. By using AST_SPECADD (repeatedly
+*     if necessary), one or more coordinate conversion steps may then
+*     be added, which the SpecMap will perform in sequence. This allows
+*     multi-step conversions between a variety of spectral coordinate
+*     systems to be assembled out of the building blocks provided by
+*     this class.
+*
+*     Normally, if a SpecMap's Invert attribute is zero (the default),
+*     then its forward transformation is performed by carrying out
+*     each of the individual coordinate conversions specified by
+c     astSpecAdd in the order given (i.e. with the most recently added
+f     AST_SPECADD in the order given (i.e. with the most recently added
+*     conversion applied last).
+*
+*     This order is reversed if the SpecMap's Invert attribute is
+*     non-zero (or if the inverse transformation is requested by any
+*     other means) and each individual coordinate conversion is also
+*     replaced by its own inverse. This process inverts the overall
+*     effect of the SpecMap. In this case, the first conversion to be
+*     applied would be the inverse of the one most recently added.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the SpecMap.
+c     cvt
+f     CVT = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string which identifies the
+f        A character string which identifies the
+*        spectral coordinate conversion to be added to the
+*        SpecMap. See the "Available Conversions" section for details of
+*        those available.
+c     narg
+f     NARG = INTEGER (Given)
+*        The number of argument values supplied in the
+c        "args" array.
+f        ARGS array.
+c     args
+f     ARGS( * ) = DOUBLE PRECISION (Given)
+*        An array containing argument values for the spectral
+*        coordinate conversion. The number of arguments required, and
+*        hence the number of array elements used, depends on the
+*        conversion specified (see the "Available Conversions"
+*        section). This array is ignored
+c        and a NULL pointer may be supplied
+*        if no arguments are needed.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - When assembling a multi-stage conversion, it can sometimes be
+*     difficult to determine the most economical conversion path. For
+*     example, when converting between reference frames, converting first
+*     to the heliographic reference frame as an intermediate stage is often
+*     sensible in formulating the problem, but may introduce unnecessary
+*     extra conversion steps. A solution to this is to include all the steps
+*     which are (logically) necessary, but then to use
+c     astSimplify to simplify the resulting
+f     AST_SIMPLIFY to simplify the resulting
+*     SpecMap. The simplification process will eliminate any steps
+*     which turn out not to be needed.
+c     - This function does not check to ensure that the sequence of
+f     - This routine does not check to ensure that the sequence of
+*     coordinate conversions added to a SpecMap is physically
+*     meaningful.
+
+*  Available Conversions:
+*     The following strings (which are case-insensitive) may be supplied
+c     via the "cvt" parameter to indicate which spectral coordinate
+f     via the CVT argument to indicate which spectral coordinate
+*     conversion is to be added to the SpecMap. Where arguments are needed by
+*     the conversion, they are listed in parentheses. Values for
+c     these arguments should be given, via the "args" array, in the
+f     these arguments should be given, via the ARGS array, in the
+*     order indicated. Units and argument names are described at the end of
+*     the list of conversions.
+
+*     - "FRTOVL" (RF): Convert frequency to relativistic velocity.
+*     - "VLTOFR" (RF): Convert relativistic velocity to Frequency.
+*     - "ENTOFR": Convert energy to frequency.
+*     - "FRTOEN": Convert frequency to energy.
+*     - "WNTOFR": Convert wave number to frequency.
+*     - "FRTOWN": Convert frequency to wave number.
+*     - "WVTOFR": Convert wavelength (vacuum) to frequency.
+*     - "FRTOWV": Convert frequency to wavelength (vacuum).
+*     - "AWTOFR": Convert wavelength (air) to frequency.
+*     - "FRTOAW": Convert frequency to wavelength (air).
+*     - "VRTOVL": Convert radio to relativistic velocity.
+*     - "VLTOVR": Convert relativistic to radio velocity.
+*     - "VOTOVL": Convert optical to relativistic velocity.
+*     - "VLTOVO": Convert relativistic to optical velocity.
+*     - "ZOTOVL": Convert redshift to relativistic velocity.
+*     - "VLTOZO": Convert relativistic velocity to redshift.
+*     - "BTTOVL": Convert beta factor to relativistic velocity.
+*     - "VLTOBT": Convert relativistic velocity to beta factor.
+*     - "USF2HL" (VOFF,RA,DEC): Convert frequency from a user-defined
+*     reference frame to heliocentric.
+*     - "HLF2US" (VOFF,RA,DEC): Convert frequency from heliocentric
+*     reference frame to user-defined.
+*     - "TPF2HL" (OBSLON,OBSLAT,OBSALT,EPOCH,RA,DEC): Convert frequency from
+*     topocentric reference frame to heliocentric.
+*     - "HLF2TP" (OBSLON,OBSLAT,OBSALT,EPOCH,RA,DEC): Convert frequency from
+*     heliocentric reference frame to topocentric.
+*     - "GEF2HL" (EPOCH,RA,DEC): Convert frequency from geocentric
+*     reference frame to heliocentric.
+*     - "HLF2GE" (EPOCH,RA,DEC): Convert frequency from
+*     heliocentric reference frame to geocentric.
+*     - "BYF2HL" (EPOCH,RA,DEC): Convert frequency from
+*     barycentric reference frame to heliocentric.
+*     - "HLF2BY" (EPOCH,RA,DEC): Convert frequency from
+*     heliocentric reference frame to barycentric.
+*     - "LKF2HL" (RA,DEC): Convert frequency from kinematic LSR
+*     reference frame to heliocentric.
+*     - "HLF2LK" (RA,DEC): Convert frequency from heliocentric
+*     reference frame to kinematic LSR.
+*     - "LDF2HL" (RA,DEC): Convert frequency from dynamical LSR
+*     reference frame to heliocentric.
+*     - "HLF2LD" (RA,DEC): Convert frequency from heliocentric
+*     reference frame to dynamical LSR.
+*     - "LGF2HL" (RA,DEC): Convert frequency from local group
+*     reference frame to heliocentric.
+*     - "HLF2LG" (RA,DEC): Convert frequency from heliocentric
+*     reference frame to local group.
+*     - "GLF2HL" (RA,DEC): Convert frequency from galactic
+*     reference frame to heliocentric.
+*     - "HLF2GL" (RA,DEC): Convert frequency from heliocentric
+*     reference frame to galactic.
+
+*     The units for the values processed by the above conversions are as
+*     follows:
+*
+*     - all velocities: metres per second (positive if the source receeds from
+*       the observer).
+*     - frequency: Hertz.
+*     - all wavelengths: metres.
+*     - energy: Joules.
+*     - wave number: cycles per metre.
+*
+*     The arguments used in the above conversions are as follows:
+*
+*     - RF: Rest frequency (Hz).
+*     - OBSALT: Geodetic altitude of observer (IAU 1975, metres).
+*     - OBSLAT: Geodetic latitude of observer (IAU 1975, radians).
+*     - OBSLON: Longitude of observer (radians - positive eastwards).
+*     - EPOCH: Epoch of observation (UT1 expressed as a Modified Julian Date).
+*     - RA: Right Ascension of source (radians, FK5 J2000).
+*     - DEC: Declination of source (radians, FK5 J2000).
+*     - VOFF: Velocity of the user-defined reference frame, towards the
+*     position given by RA and DEC, measured in the heliocentric
+*     reference frame.
+*
+*     If the SpecMap is 3-dimensional, source positions are provided by the
+*     values supplied to inputs 2 and 3 of the SpecMap (which are simply
+*     copied to outputs 2 and 3). Note, usable values are still required
+*     for the RA and DEC arguments in order to define the "user-defined"
+*     reference frame used by USF2HL and HLF2US. However, AST__BAD can be
+*     supplied for RA and DEC if the user-defined reference frame is not
+*     required.
+*
+*--
+*/
+
+/* Local Variables: */
+   int cvttype;                  /* Conversion type code */
+
+/* Check the inherited status. */
+   if ( !astOK ) return;
+
+/* Validate the type string supplied and obtain the equivalent
+   conversion type code. */
+   cvttype = CvtCode( cvt, status );
+
+/* If the string was not recognised, then report an error. */
+   if ( astOK && ( cvttype == AST__SPEC_NULL ) ) {
+      astError( AST__SPCIN,
+                "%s(%s): Invalid SpecMap spectral coordinate "
+                "conversion type \"%s\".", status, "astAddSpec", astGetClass( this ), cvt );
+   }
+
+/* Add the new conversion to the SpecMap. */
+   AddSpecCvt( this, cvttype, narg, args, status );
+}
+
+static int SystemChange( int cvt_code, int np, double *values, double *args,
+                         int forward, int *status ){
+/*
+*  Name:
+*     SystemChange
+
+*  Purpose:
+*     Change values between two spectral systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     int SystemChange( int cvt_code, int np, double *values, double *args,
+*                       int forward, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function modifies the supplied values in order to change the
+*     spectral co-ordinate system (frequency, wavelength, etc) to which
+*     they refer.
+
+*  Parameters:
+*     cvt_code
+*        A code indicating the conversion to be applied. If the code does
+*        not correspond to a change of system, then the supplied values
+*        are left unchanged and zero is returned as the function value.
+*     np
+*        The number of frequency values to transform.
+*     values
+*        Pointer to an array of "np" spectral values. These are modified on
+*        return to hold the corresponding values measured in the output
+*        system.
+*     args
+*        Pointer to an array holding the conversion arguments. The number
+*        of arguments expected depends on the particular conversion being
+*        used.
+*     forward
+*        Should the conversion be applied in the forward or inverse
+*        direction? Non-zero for forward, zero for inverse.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the supplied conversion code corresponds to a change of
+*     system. Zero otherwise  (in which case the upplied values will not
+*     have been changed).
+
+*/
+
+/* Local Variables: */
+   double *pv;        /* Pointer to next value */
+   double d;          /* Intermediate value */
+   double f2;         /* Squared frequency */
+   double temp;       /* Intermediate value */
+   int i;             /* Loop index */
+   int iter;          /* Iteration count */
+   int result;        /* Returned value */
+
+/* Check inherited status. */
+   if( !astOK ) return 0;
+
+/* Set the return value to indicate that the supplied conversion code
+   represents a change of system. */
+   result = 1;
+
+/* Test for each code value in turn and assign the appropriate values. */
+   switch ( cvt_code ) {
+
+/* Frequency to relativistic velocity. */
+   case AST__FRTOVL:
+      if( forward ) {
+         if( args[ 0 ] != AST__BAD ) {
+            temp = args[ 0 ] * args[ 0 ];
+            pv = values - 1;
+            for( i = 0; i < np; i++ ){
+               pv++;
+               if( *pv != AST__BAD ) {
+                  f2 = ( *pv ) * ( *pv );
+                  d = temp + f2;
+                  if( d > 0.0 ) {
+                     *pv = AST__C*( ( temp - f2 )/d );
+                     if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+                  } else {
+                     *pv = AST__BAD;
+                  }
+               }
+            }
+         } else {
+            pv = values;
+            for( i = 0; i < np; i++ ) *( pv++ ) = AST__BAD;
+         }
+      } else {
+         SystemChange( AST__VLTOFR, np, values, args, 1, status );
+      }
+      break;
+
+/* Relativistic velocity to frequency. */
+   case AST__VLTOFR:
+      if( forward ) {
+         if( args[ 0 ] != AST__BAD ) {
+            temp = args[ 0 ];
+            pv = values - 1;
+            for( i = 0; i < np; i++ ){
+               pv++;
+               if( *pv != AST__BAD ) {
+                  d = AST__C + ( *pv );
+                  if( d != 0.0 ) {
+                     d = ( AST__C - ( *pv ) )/d;
+                     if( d >= 0.0 ) {
+                        *pv = temp*sqrt( d );
+                        if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+                     } else {
+                        *pv = AST__BAD;
+                     }
+                  } else {
+                     *pv = AST__BAD;
+                  }
+               }
+            }
+         } else {
+            pv = values;
+            for( i = 0; i < np; i++ ) *( pv++ ) = AST__BAD;
+         }
+      } else {
+         SystemChange( AST__FRTOVL, np, values, args, 1, status );
+      }
+      break;
+
+/* Energy to frequency */
+   case AST__ENTOFR:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               *pv /= AST__H;
+            }
+         }
+      } else {
+         SystemChange( AST__FRTOEN, np, values, args, 1, status );
+      }
+      break;
+
+/* Frequency to energy */
+   case AST__FRTOEN:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               *pv *= AST__H;
+            }
+         }
+      } else {
+         SystemChange( AST__ENTOFR, np, values, args, 1, status );
+      }
+      break;
+
+/* Wave number to frequency */
+   case AST__WNTOFR:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               *pv *= AST__C;
+            }
+         }
+      } else {
+         SystemChange( AST__FRTOWN, np, values, args, 1, status );
+      }
+      break;
+
+/* Wave number to frequency */
+   case AST__FRTOWN:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               *pv /= AST__C;
+            }
+         }
+      } else {
+         SystemChange( AST__WNTOFR, np, values, args, 1, status );
+      }
+      break;
+
+/* Wavelength to frequency */
+   case AST__WVTOFR:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD && *pv != 0.0 ) {
+               *pv = AST__C/( *pv );
+               if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+            } else {
+               *pv = AST__BAD;
+            }
+         }
+      } else {
+         SystemChange( AST__FRTOWV, np, values, args, 1, status );
+      }
+      break;
+
+/* Frequency to wavelength. */
+   case AST__FRTOWV:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD && *pv != 0.0 ) {
+               *pv = AST__C/( *pv );
+               if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+            } else {
+               *pv = AST__BAD;
+            }
+         }
+      } else {
+         SystemChange( AST__WVTOFR, np, values, args, 1, status );
+      }
+      break;
+
+/* Wavelength in air to frequency. */
+   case AST__AWTOFR:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD && *pv != 0.0 ) {
+               *pv = AST__C/( ( *pv )*Refrac( *pv, status ) );
+               if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+            } else {
+               *pv = AST__BAD;
+            }
+         }
+      } else {
+         SystemChange( AST__FRTOAW, np, values, args, 1, status );
+      }
+      break;
+
+/* Frequency to wavelength in air. */
+   case AST__FRTOAW:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD && *pv != 0.0 ) {
+
+/* Form the vacuum wavelength. */
+               temp = AST__C/( *pv );
+
+/* The refractive index function "Refrac" requires the wavelength in air
+   as its parameter. Initially assume that the wavelength in air is equal
+   to the vacuum wavelength to get he first estimate of the wavelength in
+   air. Then use this estimate to get a better refractive index in order to
+   form a better estimate of the air wavelength, etc. Iterate in this way a
+   few times. */
+               *pv = temp;
+               for( iter = 0; iter < 3; iter++ ) {
+                  *pv = temp/Refrac( *pv, status );
+                  if( !astISFINITE( *pv ) ) {
+                     *pv = AST__BAD;
+                     break;
+                  }
+               }
+
+            } else {
+               *pv = AST__BAD;
+            }
+         }
+      } else {
+         SystemChange( AST__AWTOFR, np, values, args, 1, status );
+      }
+      break;
+
+/* Radio velocity to relativistic velocity */
+   case AST__VRTOVL:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               temp = 1.0 - ( *pv )/AST__C;
+               temp *= temp;
+               *pv = AST__C*( 1.0 - temp )/( 1.0 + temp );
+               if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+            }
+         }
+      } else {
+         SystemChange( AST__VLTOVR, np, values, args, 1, status );
+      }
+      break;
+
+/* Relativistic velocity to radio velocity. */
+   case AST__VLTOVR:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               temp = AST__C + ( *pv );
+               if( temp != 0.0 ) {
+                  temp = (AST__C - *pv )/temp;
+                  if( temp >= 0.0 ) {
+                     *pv = AST__C*( 1.0 - sqrt( temp ) );
+                     if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+                  } else {
+                     *pv = AST__BAD;
+                  }
+               } else {
+                  *pv = AST__BAD;
+               }
+            }
+         }
+      } else {
+         SystemChange( AST__VRTOVL, np, values, args, 1, status );
+      }
+      break;
+
+/* Optical velocity to relativistic velocity */
+   case AST__VOTOVL:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               temp = 1.0 + ( *pv )/AST__C;
+               temp *= temp;
+               *pv = AST__C*( temp - 1.0 )/( temp + 1.0 );
+               if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+            }
+         }
+      } else {
+         SystemChange( AST__VLTOVO, np, values, args, 1, status );
+      }
+      break;
+
+/* Relativistic velocity to optical velocity. */
+   case AST__VLTOVO:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               temp = AST__C - *pv;
+               if( temp != 0.0 ) {
+                  temp = (AST__C + *pv )/temp;
+                  if( temp >= 0.0 ) {
+                     *pv = AST__C*( sqrt( temp ) - 1.0 );
+                     if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+                  } else {
+                     *pv = AST__BAD;
+                  }
+               } else {
+                  *pv = AST__BAD;
+               }
+            }
+         }
+      } else {
+         SystemChange( AST__VOTOVL, np, values, args, 1, status );
+      }
+      break;
+
+/* Redshift to relativistic velocity */
+   case AST__ZOTOVL:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               temp = 1.0 + ( *pv );
+               temp *= temp;
+               *pv = AST__C*( temp - 1.0 )/( temp + 1.0 );
+               if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+            }
+         }
+      } else {
+         SystemChange( AST__VLTOZO, np, values, args, 1, status );
+      }
+      break;
+
+/* Relativistic velocity to redshift. */
+   case AST__VLTOZO:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               temp = AST__C - *pv;
+               if( temp != 0.0 ) {
+                  temp = (AST__C + *pv )/temp;
+                  if( temp >= 0.0 ) {
+                     *pv = sqrt( temp ) - 1.0;
+                     if( !astISFINITE( *pv ) ) *pv = AST__BAD;
+                  } else {
+                     *pv = AST__BAD;
+                  }
+               } else {
+                  *pv = AST__BAD;
+               }
+            }
+         }
+      } else {
+         SystemChange( AST__ZOTOVL, np, values, args, 1, status );
+      }
+      break;
+
+/* Beta factor to relativistic velocity */
+   case AST__BTTOVL:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               *pv *= AST__C;
+            }
+         }
+      } else {
+         SystemChange( AST__VLTOBT, np, values, args, 1, status );
+      }
+      break;
+
+/* Relativistic velocity to beta factor. */
+   case AST__VLTOBT:
+      if( forward ) {
+         pv = values - 1;
+         for( i = 0; i < np; i++ ) {
+            pv++;
+            if( *pv != AST__BAD ) {
+               *pv /= AST__C;
+            }
+         }
+      } else {
+         SystemChange( AST__BTTOVL, np, values, args, 1, status );
+      }
+      break;
+
+/* If the supplied code does not represent a change of system, clear
+   the returned flag. */
+   default:
+      result = 0;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double TopoVel( double ra, double dec, FrameDef *def, int *status ) {
+/*
+*  Name:
+*     TopoVel
+
+*  Purpose:
+*     Find the velocity of the observer away from the source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     double TopoVel( double ra, double dec, FrameDef *def, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function finds the component of the velocity of the observer away
+*     from a specified source position, at a given epoch, in the frame of
+*     rest of the Sun.
+
+*  Parameters:
+*     ra
+*        The RA (rads, FK5 J2000) of the source.
+*     dec
+*        The Dec (rads, FK5 J2000) of the source.
+*     def
+*        Pointer to a FrameDef structure which holds the parameters which
+*        define the frame, together with cached intermediate results.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     The component of the frame's velocity away from the position given by
+*     "ra" and "dec", in m/s, measured within the Heliographic frame of
+*     rest. Zero is returned if an error has already occurred.
+
+*/
+
+/* Local Variables: */
+   double deca;              /* Apparent DEC */
+   double raa;               /* Apparent RA */
+   double vobs;              /* Velocity of observer relative to earth */
+   double vearth;            /* Velocity of earth realtive to sun */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* If not already done so, get the parameters defining the transformation
+   of mean ra and dec to apparent ra and dec, and store in the supplied frame
+   definition structure. */
+   if( def->amprms[ 0 ] == AST__BAD ) palMappa( 2000.0, def->epoch,
+                                                def->amprms );
+
+/* Convert the source position from mean ra and dec to apparent ra and dec. */
+   palMapqkz( ra, dec, def->amprms, &raa, &deca );
+
+/* If not already done so, get the local apparent siderial time (in radians)
+   and store in the supplied frame definition structure. */
+   if( def->last == AST__BAD ) def->last = palGmst( def->epoch ) +
+                                           palEqeqx( def->epoch ) +
+                                           def->obslon;
+
+/* Get the component away from the source, of the velocity of the observer
+   relative to the centre of the earth (in m/s). */
+   vobs = 1000.0*Rverot( def->obslat, def->obsalt, raa, deca, def->last,
+                         status );
+
+/* Get the component away from the source, of the velocity of the earth's
+   centre relative to the Sun, in m/s. */
+   vearth = GeoVel( ra, dec, def, status );
+
+/* Return the total velocity of the observer away from the source in the
+   frame of the sun. */
+   return vobs + vearth;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a SpecMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     SpecMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a SpecMap and a set of points encapsulated
+*     in a PointSet and transforms the points so as to perform the
+*     sequence of spectral coordinate conversions specified by
+*     previous invocations of astSpecAdd.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the SpecMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstSpecMap *map;              /* Pointer to SpecMap to be applied */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *spec;                 /* Pointer to output spectral axis value array */
+   double *alpha;                /* Pointer to output RA axis value array */
+   double *beta;                 /* Pointer to output DEC axis value array */
+   int cvt;                      /* Loop counter for conversions */
+   int end;                      /* Termination index for conversion loop */
+   int inc;                      /* Increment for conversion loop */
+   int map3d;                    /* Is the SpecMap 3-dimensional? */
+   int ncoord_in;                /* Number of coordinates per input point */
+   int npoint;                   /* Number of points */
+   int start;                    /* Starting index for conversion loop */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the SpecMap. */
+   map = (AstSpecMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   coordinate conversions needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   ncoord_in = astGetNcoord( in );
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse transformation, according
+   to the direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( this ) ) forward = !forward;
+
+/* Transform the coordinate values. */
+/* -------------------------------- */
+/* Use "spec" as a synonym for the array of spectral axis values stored in
+   the output PointSet. */
+   if ( astOK ) {
+      spec = ptr_out[ 0 ];
+
+/* If this is a 3D SpecMap use "alpha" as a synonym for the array of RA axis
+   values and "beta" as a synonym for the array of DEC axis values stored
+   in the output PointSet. */
+      map3d = ( ncoord_in == 3 );
+      if( map3d ) {
+         alpha = ptr_out[ 1 ];
+         beta = ptr_out[ 2 ];
+      } else {
+         alpha = NULL;
+         beta = NULL;
+      }
+
+/* Initialise the output coordinate values by copying the input ones. */
+      (void) memcpy( spec, ptr_in[ 0 ], sizeof( double ) * (size_t) npoint );
+      if( map3d ) {
+         (void) memcpy( alpha, ptr_in[ 1 ], sizeof( double ) * (size_t) npoint );
+         (void) memcpy( beta, ptr_in[ 2 ], sizeof( double ) * (size_t) npoint );
+      }
+
+/* We will loop to apply each spectral coordinate conversion in turn to the
+   (spec) array. However, if the inverse transformation was requested,
+   we must loop through these transformations in reverse order, so set up
+   appropriate limits and an increment to control this loop. */
+      start = forward ? 0 : map->ncvt - 1;
+      end = forward ? map->ncvt : -1;
+      inc = forward ? 1 : -1;
+
+/* Loop through the coordinate conversions in the required order. */
+      for ( cvt = start; cvt != end; cvt += inc ) {
+
+/* Process conversions which correspond to changes of reference frames. */
+         if( !FrameChange( map->cvttype[ cvt ], npoint, alpha, beta, spec,
+                          map->cvtargs[ cvt ], forward, status ) ) {
+
+/* If this conversion was not a change of reference frame, it must be a
+   change of system. */
+            SystemChange( map->cvttype[ cvt ], npoint, spec,
+                          map->cvtargs[ cvt ], forward, status );
+         }
+      }
+   }
+
+/* If an error has occurred and a new PointSet may have been created, then
+   clean up by annulling it. In any case, ensure that a NULL result is
+   returned.*/
+   if ( !astOK ) {
+      if ( !out ) result = astAnnul( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+
+}
+
+static double UserVel( double ra, double dec, FrameDef *def, int *status ) {
+/*
+*  Name:
+*     UserVel
+
+*  Purpose:
+*     Find the component of the velocity of the user-defined rest-frame
+*     away from the source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     double UserVel( double ra, double dec, FrameDef *def, int *status )
+
+*  Class Membership:
+*     SpecMap method.
+
+*  Description:
+*     This function finds the component of the velocity of the user-defined
+*     rest-frame away from a specified position. The magnitude and direction
+*     of the rest-frames velocity are defined within the supplied "def"
+*     structure. The user-defined rest-frame is typically used to represent
+*     the velocity of the source within the heliocentric rest-frame.
+
+*  Parameters:
+*     ra
+*        The RA (rads, FK5 J2000) of the source.
+*     dec
+*        The Dec (rads, FK5 J2000) of the source.
+*     def
+*        Pointer to a FrameDef structure which holds the parameters which
+*        define the frame, together with cached intermediate results.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     The component of the frame's velocity away from the position given by
+*     "ra" and "dec", in m/s, measured within the Heliographic frame of
+*     rest. Zero is returned if an error has already occurred.
+
+*  Notes:
+*     - The direction of the user velocity is given by def->refra and
+*     def->refdec (an FK5 J2000 position). The maginitude of the velocity
+*     is given by def->veluser, in m/s, positive when the source is moving
+*     away from the observer towards def->refra, def->refdec, and given
+*     with respect to the heliocentric rest-frame.
+
+*/
+
+/* Local Variables: */
+   double vb[ 3 ];          /* Source position vector */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* If not already done so, express the user velocity in the form of a
+   J2000.0 x,y,z vector. */
+   if( def->vuser[ 0 ] == AST__BAD ) {
+      def->vuser[ 0 ] = def->veluser*cos( def->refra )*cos( def->refdec );
+      def->vuser[ 1 ] = def->veluser*sin( def->refra )*cos( def->refdec );
+      def->vuser[ 2 ] = def->veluser*sin( def->refdec );
+   }
+
+/* Convert given J2000 RA,Dec to x,y,z. */
+   palDcs2c( ra, dec, vb );
+
+/* Return the dot product with the user velocity. Invert it to get the
+   velocity towards the observer (the def->veluser value is supposed to be
+   positive if the source is moving away from the observer). */
+   return -palDvdv( def->vuser, vb );
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for SpecMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for SpecMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstSpecMap *in;                /* Pointer to input SpecMap */
+   AstSpecMap *out;               /* Pointer to output SpecMap */
+   int cvt;                       /* Loop counter for coordinate conversions */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output SpecMap structures. */
+   in = (AstSpecMap *) objin;
+   out = (AstSpecMap *) objout;
+
+/* For safety, first clear any references to the input memory from the output
+   SpecMap. */
+   out->cvtargs = NULL;
+   out->cvttype = NULL;
+
+/* Allocate memory for the output array of argument list pointers. */
+   out->cvtargs = astMalloc( sizeof( double * ) * (size_t) in->ncvt );
+
+/* If necessary, allocate memory and make a copy of the input array of
+   coordinate conversion codes. */
+   if ( in->cvttype ) out->cvttype = astStore( NULL, in->cvttype,
+                                               sizeof( int )
+                                               * (size_t) in->ncvt );
+
+/* If OK, loop through each conversion in the input SpecMap and make a copy of
+   its argument list, storing the new pointer in the output argument list
+   array. */
+   if ( astOK ) {
+      for ( cvt = 0; cvt < in->ncvt; cvt++ ) {
+         out->cvtargs[ cvt ] = astStore( NULL, in->cvtargs[ cvt ],
+                                         astSizeOf( in->cvtargs[ cvt ] ) );
+      }
+
+/* If an error occurred while copying the argument lists, loop through the
+   conversions again and clean up by ensuring that the new memory allocated for
+   each argument list is freed. */
+      if ( !astOK ) {
+         for ( cvt = 0; cvt < in->ncvt; cvt++ ) {
+            out->cvtargs[ cvt ] = astFree( out->cvtargs[ cvt ] );
+	 }
+      }
+   }
+
+/* If an error occurred, free all other memory allocated above. */
+   if ( !astOK ) {
+      out->cvtargs = astFree( out->cvtargs );
+      out->cvttype = astFree( out->cvttype );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for SpecMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for SpecMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstSpecMap *this;              /* Pointer to SpecMap */
+   int cvt;                       /* Loop counter for coordinate conversions */
+
+/* Obtain a pointer to the SpecMap structure. */
+   this = (AstSpecMap *) obj;
+
+/* Loop to free the memory containing the argument list for each coordinate
+   conversion. */
+   for ( cvt = 0; cvt < this->ncvt; cvt++ ) {
+      this->cvtargs[ cvt ] = astFree( this->cvtargs[ cvt ] );
+   }
+
+/* Free the memory holding the array of conversion types and the array of
+   argument list pointers. */
+   this->cvtargs = astFree( this->cvtargs );
+   this->cvttype = astFree( this->cvttype );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for SpecMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the SpecMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the SpecMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstSpecMap *this;             /* Pointer to the SpecMap structure */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword string */
+   const char *argdesc[ MAX_ARGS ]; /* Pointers to argument descriptions */
+   const char *comment;          /* Pointer to comment string */
+   const char *sval;             /* Pointer to string value */
+   int argdec;                   /* Index of DEC argument */
+   int argra;                    /* Index of RA argument */
+   int iarg;                     /* Loop counter for arguments */
+   int icvt;                     /* Loop counter for conversion steps */
+   int ival;                     /* Integer value */
+   int nargs;                    /* Number of user-supplied arguments */
+   int szargs;                   /* Number of stored arguments */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SpecMap structure. */
+   this = (AstSpecMap *) this_object;
+
+/* Write out values representing the instance variables for the SpecMap
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Number of conversion steps. */
+/* --------------------------- */
+/* Regard this as "set" if it is non-zero. */
+   ival = this->ncvt;
+   set = ( ival != 0 );
+   astWriteInt( channel, "Nspec", set, 0, ival, "Number of conversion steps" );
+
+/* Write out data for each conversion step... */
+   for ( icvt = 0; icvt < this->ncvt; icvt++ ) {
+
+/* Conversion type. */
+/* ---------------- */
+/* Change each conversion type code into an equivalent string and
+   obtain associated descriptive information. If the conversion code
+   was not recognised, report an error and give up. */
+      if ( astOK ) {
+         sval = CvtString( this->cvttype[ icvt ], &comment, &argra, &argdec,
+                           &nargs, &szargs, argdesc, status );
+         if ( astOK && !sval ) {
+            astError( AST__SPCIN,
+                      "astWrite(%s): Corrupt %s contains invalid SpecMap "
+                      "spectral coordinate conversion code (%d).", status,
+                      astGetClass( channel ), astGetClass( this ),
+                      (int) this->cvttype[ icvt ] );
+            break;
+         }
+
+/* Create an appropriate keyword and write out the conversion code
+   information. */
+         (void) sprintf( key, "Spec%d", icvt + 1 );
+         astWriteString( channel, key, 1, 1, sval, comment );
+
+/* Write out data for each conversion argument... */
+         for ( iarg = 0; iarg < szargs; iarg++ ) {
+
+/* Arguments. */
+/* ---------- */
+/* Create an appropriate keyword and write out the argument value,
+   accompanied by the descriptive comment obtained above. */
+            if( this->cvtargs[ icvt ][ iarg ] != AST__BAD ) {
+               (void) sprintf( key, "Spec%d%c", icvt + 1, ALPHABET[ iarg ] );
+               astWriteDouble( channel, key, 1, 1, this->cvtargs[ icvt ][ iarg ],
+                               argdesc[ iarg ] );
+            }
+         }
+
+/* Quit looping if an error occurs. */
+         if ( !astOK ) break;
+      }
+   }
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsASpecMap and astCheckSpecMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(SpecMap,Mapping)
+astMAKE_CHECK(SpecMap)
+
+AstSpecMap *astSpecMap_( int nin, int flags, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astSpecMap
+f     AST_SPECMAP
+
+*  Purpose:
+*     Create a SpecMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "specmap.h"
+c     AstSpecMap *astSpecMap( int nin, int flags, const char *options, ... )
+f     RESULT = AST_SPECMAP( NIN, FLAGS, OPTIONS, STATUS )
+
+*  Class Membership:
+*     SpecMap constructor.
+
+*  Description:
+*     This function creates a new SpecMap and optionally initialises
+*     its attributes.
+*
+*     An SpecMap is a specialised form of Mapping which can be used to
+*     represent a sequence of conversions between standard spectral
+*     coordinate systems. This includes conversions between frequency,
+*     wavelength, and various forms of velocity, as well as conversions
+*     between different standards of rest.
+*
+*     When a SpecMap is first created, it simply performs a unit
+c     (null) Mapping. Using the astSpecAdd function,
+f     (null) Mapping. Using the AST_SPECADD routine,
+*     a series of coordinate conversion steps may then be added, selected
+*     from the list of supported conversions. This allows multi-step
+*     conversions between a variety of spectral coordinate systems to
+*     be assembled out of the building blocks provided by this class.
+*
+*     For details of the individual coordinate conversions available,
+c     see the description of the astSpecAdd function.
+f     see the description of the AST_SPECADD routine.
+*
+*     Conversions are available to transform between standards of rest.
+*     Such conversions need to know the source position as an RA and DEC.
+*     This information can be supplied in the form of parameters for
+*     the relevant conversions, in which case the SpecMap is 1-dimensional,
+*     simply transforming the spectral axis values. This means that the
+*     same source position will always be used by the SpecMap. However, this
+*     may not be appropriate for an accurate description of a 3-D spectral
+*     cube, where changes of spatial position can produce significant
+*     changes in the Doppler shift introduced when transforming between
+*     standards of rest. For this situation, a 3-dimensional SpecMap can
+*     be created in which axes 2 and 3 correspond to the source RA and DEC
+*     The SpecMap simply copies values for axes 2 and 3 from input to
+*     output).
+
+*  Parameters:
+c     nin
+f     NIN = INTEGER (Given)
+*        The number of inputs to the Mapping (this will also equal the
+*        number of outputs). This value must be either 1 or 3. In either
+*        case, the first input and output correspoindis the spectral axis.
+*        For a 3-axis SpecMap, the second and third axes give the RA and
+*        DEC (J2000 FK5) of the source. This positional information is
+*        used by conversions which transform between standards of rest,
+*        and replaces the "RA" and "DEC" arguments for the individual
+*        conversions listed in description of the "SpecAdd"
+c        function.
+f        routine.
+c     flags
+f     FLAGS = INTEGER (Given)
+c        This parameter is reserved for future use and should currently
+f        This argument is reserved for future use and should currently
+*        always be set to zero.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new SpecMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new SpecMap. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSpecMap()
+f     AST_SPECMAP = INTEGER
+*        A pointer to the new SpecMap.
+
+*  Notes:
+*     - The nature and units of the coordinate values supplied for the
+*     first input (i.e. the spectral input) of a SpecMap must be appropriate
+*     to the first conversion step applied by the SpecMap. For instance, if
+*     the first conversion step is "FRTOVL" (frequency to relativistic
+*     velocity), then the coordinate values for the first input should
+*     be frequency in units of Hz. Similarly, the nature and units of the
+*     coordinate values returned by a SpecMap will be determined by the
+*     last conversion step applied by the SpecMap. For instance, if the
+*     last conversion step is "VLTOVO" (relativistic velocity to optical
+*     velocity), then the coordinate values for the first output will be optical
+*     velocity in units of metres per second. See the description of the
+c     astSpecAdd function for the units expected and returned by each
+f     AST_SPECADD routine for the units expected and returned by each
+*     conversion.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSpecMap *new;              /* Pointer to the new SpecMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SpecMap, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitSpecMap( NULL, sizeof( AstSpecMap ), !class_init, &class_vtab,
+                        "SpecMap", nin, flags );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new SpecMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new SpecMap. */
+   return new;
+}
+
+AstSpecMap *astSpecMapId_( int nin, int flags, const char *options, ... ) {
+/*
+*  Name:
+*     astSpecMapId_
+
+*  Purpose:
+*     Create a SpecMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     AstSpecMap *astSpecMapId_( int nin, int flags, const char *options, ... )
+
+*  Class Membership:
+*     SpecMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astSpecMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astSpecMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astSpecMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astSpecMap_.
+
+*  Returned Value:
+*     The ID value associated with the new SpecMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSpecMap *new;              /* Pointer to the new SpecMap */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SpecMap, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitSpecMap( NULL, sizeof( AstSpecMap ), !class_init, &class_vtab,
+                        "SpecMap", nin, flags );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new SpecMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new SpecMap. */
+   return astMakeId( new );
+}
+
+AstSpecMap *astInitSpecMap_( void *mem, size_t size, int init,
+                             AstSpecMapVtab *vtab, const char *name,
+                             int nin, int flags, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSpecMap
+
+*  Purpose:
+*     Initialise a SpecMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     AstSpecMap *astInitSpecMap( void *mem, size_t size, int init,
+*                               AstSpecMapVtab *vtab, const char *name,
+*                               int nin, int flags )
+
+*  Class Membership:
+*     SpecMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new SpecMap object. It allocates memory (if necessary) to accommodate
+*     the SpecMap plus any additional data associated with the derived class.
+*     It then initialises a SpecMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a SpecMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the SpecMap is to be initialised.
+*        This must be of sufficient size to accommodate the SpecMap data
+*        (sizeof(SpecMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the SpecMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the SpecMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the SpecMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new SpecMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astClass
+*        method).
+*     nin
+*        The number of inputs and outputs for the SpecMap (either 1 or 3).
+*     flags
+*        This parameter is reserved for future use. It is currently ignored.
+
+*  Returned Value:
+*     A pointer to the new SpecMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSpecMap *new;               /* Pointer to the new SpecMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Check nin is OK (1 or 3). */
+   if( nin != 1 && nin != 3 ) {
+      astError( AST__BADNI, "astInitSpecMap(SpecMap): Supplied number of "
+                "SpecMap axes (%d) is illegal; it should be 1 or 2. ", status,
+                nin );
+   }
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitSpecMapVtab( vtab, name );
+
+/* Initialise a 1D Mapping structure (the parent class) as the first component
+   within the SpecMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstSpecMap *) astInitMapping( mem, size, 0,
+                                       (AstMappingVtab *) vtab, name,
+                                       nin, nin, 1, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the SpecMap data. */
+/* --------------------------- */
+/* The initial state is with no conversions set, in which condition the
+   SpecMap simply implements a unit mapping. */
+      new->ncvt = 0;
+      new->cvtargs = NULL;
+      new->cvttype = NULL;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstSpecMap *astLoadSpecMap_( void *mem, size_t size,
+                           AstSpecMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadSpecMap
+
+*  Purpose:
+*     Load a SpecMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "specmap.h"
+*     AstSpecMap *astLoadSpecMap( void *mem, size_t size,
+*                               AstSpecMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     SpecMap loader.
+
+*  Description:
+*     This function is provided to load a new SpecMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     SpecMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a SpecMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the SpecMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        SpecMap data (sizeof(SpecMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the SpecMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the SpecMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstSpecMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new SpecMap. If this is NULL, a pointer to
+*        the (static) virtual function table for the SpecMap class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "SpecMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new SpecMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstSpecMap *new;              /* Pointer to the new SpecMap */
+   char *sval;                   /* Pointer to string value */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword string */
+   const char *argdesc[ MAX_ARGS ]; /* Pointers to argument descriptions */
+   const char *comment;          /* Pointer to comment string */
+   int argdec;                   /* Index of DEC argument */
+   int argra;                    /* Index of RA argument */
+   int iarg;                     /* Loop counter for arguments */
+   int icvt;                     /* Loop counter for conversion steps */
+   int nargs;                    /* Number of user-supplied arguments */
+   int szargs;                   /* Number of stored arguments */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this SpecMap. In this case the
+   SpecMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstSpecMap );
+      vtab = &class_vtab;
+      name = "SpecMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitSpecMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built SpecMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "SpecMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Number of conversion steps. */
+/* --------------------------- */
+/* Read the number of conversion steps and allocate memory to hold
+   data for each step. */
+      new->ncvt = astReadInt( channel, "nspec", 0 );
+      if ( new->ncvt < 0 ) new->ncvt = 0;
+      new->cvttype = astMalloc( sizeof( int ) * (size_t) new->ncvt );
+      new->cvtargs = astMalloc( sizeof( double * ) * (size_t) new->ncvt );
+
+/* If an error occurred, ensure that all allocated memory is freed. */
+      if ( !astOK ) {
+         new->cvttype = astFree( new->cvttype );
+         new->cvtargs = astFree( new->cvtargs );
+
+/* Otherwise, initialise the argument pointer array. */
+      } else {
+         for ( icvt = 0; icvt < new->ncvt; icvt++ ) {
+            new->cvtargs[ icvt ] = NULL;
+         }
+
+/* Read in data for each conversion step... */
+         for ( icvt = 0; icvt < new->ncvt; icvt++ ) {
+
+/* Conversion type. */
+/* ---------------- */
+/* Create an appropriate keyword and read the string representation of
+   the conversion type. */
+            (void) sprintf( key, "spec%d", icvt + 1 );
+            sval = astReadString( channel, key, NULL );
+
+/* If no value was read, report an error. */
+            if ( astOK ) {
+               if ( !sval ) {
+                  astError( AST__BADIN,
+                            "astRead(%s): A spectral coordinate conversion "
+                            "type is missing from the input SpecMap data.", status,
+                            astGetClass( channel ) );
+
+/* Otherwise, convert the string representation into the required
+   conversion type code. */
+               } else {
+                  new->cvttype[ icvt ] = CvtCode( sval, status );
+
+/* If the string was not recognised, report an error. */
+                  if ( new->cvttype[ icvt ] == AST__SPEC_NULL ) {
+                     astError( AST__BADIN,
+                              "astRead(%s): Invalid spectral conversion "
+                              "type \"%s\" in SpecMap data.", status,
+                              astGetClass( channel ), sval );
+                  }
+               }
+
+/* Free the memory holding the string value. */
+               sval = astFree( sval );
+            }
+
+/* Obtain the number of arguments associated with the conversion and
+   allocate memory to hold them. */
+            (void) CvtString( new->cvttype[ icvt ], &comment, &argra,
+                              &argdec, &nargs, &szargs, argdesc, status );
+            new->cvtargs[ icvt ] = astMalloc( sizeof( double ) *
+                                              (size_t) szargs );
+
+/* Read in data for each argument... */
+            if ( astOK ) {
+               for ( iarg = 0; iarg < szargs; iarg++ ) {
+
+/* Arguments. */
+/* ---------- */
+/* Create an appropriate keyword and read each argument value. */
+                  (void) sprintf( key, "spec%d%c", icvt + 1, ALPHABET[ iarg ] );
+                  new->cvtargs[ icvt ][ iarg ] = astReadDouble( channel, key,
+                                                                AST__BAD );
+               }
+            }
+
+/* Quit looping if an error occurs. */
+            if ( !astOK ) break;
+         }
+      }
+
+/* If an error occurred, clean up by deleting the new SpecMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new SpecMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+void astSpecAdd_( AstSpecMap *this, const char *cvt, int narg,
+                  const double args[], int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,SpecMap,SpecAdd))( this, cvt, narg, args, status );
+}
+
+
+
+
diff --git a/ast/specmap.h b/ast/specmap.h
new file mode 100644
index 0000000..d266ba1
--- /dev/null
+++ b/ast/specmap.h
@@ -0,0 +1,282 @@
+#if !defined( SPECMAP_INCLUDED )  /* Include this file only once */
+#define SPECMAP_INCLUDED
+/*
+*+
+*  Name:
+*     specmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the SpecMap class.
+
+*  Invocation:
+*     #include "specmap.h"
+
+*  Description:
+*     This include file defines the interface to the SpecMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The SpecMap class encapsulates various ecptral coordinate
+*     conversions. Since, typically, a sequence of these conversions is
+*     required, a SpecMap can be used to accumulate a series of conversions
+*     which it then applies in sequence.
+
+*  Inheritance:
+*     The SpecMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        astTransform
+*           Use an SpecMap to transform a set of points.
+
+*     Protected:
+*        astMapMerge
+*           Simplify a sequence of Mappings containing an SpecMap.
+
+*  New Methods Defined:
+*     Public:
+*        astSpecAdd
+*           Add a coordinate conversion step to an SpecMap.
+
+*     Private:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsASpecMap
+*           Test class membership.
+*        astSpecMap
+*           Create an SpecMap.
+
+*     Protected:
+*        astCheckSpecMap
+*           Validate class membership.
+*        astInitSpecMap
+*           Initialise an SpecMap.
+*        astLoadSpecMap
+*           Load an SpecMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstSpecMap
+*           SpecMap object type.
+
+*     Protected:
+*        AstSpecMapVtab
+*           SpecMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     8-NOV-2002 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ------ */
+/* Physical constants taken from Chapter 15 of the "Explanatory Supplement
+   to the Astronomical Ephemeris". */
+#define AST__C 2.99792458E8      /* Speed of light (metres per second) */
+#define AST__H 6.6260755E-34     /* Plank constant (Joule.seconds) */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* SpecMap structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstSpecMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int *cvttype;                 /* Pointer to array of conversion types */
+   double **cvtargs;             /* Pointer to argument list pointer array */
+   int ncvt;                     /* Number of conversions to perform */
+} AstSpecMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstSpecMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   void (* SpecAdd)( AstSpecMap *, const char *, int, const double[], int * );
+} AstSpecMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstSpecMapGlobals {
+   AstSpecMapVtab Class_Vtab;
+   int Class_Init;
+} AstSpecMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitSpecMapGlobals_( AstSpecMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(SpecMap)           /* Check class membership */
+astPROTO_ISA(SpecMap)             /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstSpecMap *astSpecMap_( int, int, const char *, int *, ...);
+#else
+AstSpecMap *astSpecMapId_( int, int, const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstSpecMap *astInitSpecMap_( void *, size_t, int, AstSpecMapVtab *,
+                             const char *, int, int, int * );
+
+/* Vtab initialiser. */
+void astInitSpecMapVtab_( AstSpecMapVtab *, const char *, int * );
+
+/* Loader. */
+AstSpecMap *astLoadSpecMap_( void *, size_t, AstSpecMapVtab *,
+                           const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+void astSpecAdd_( AstSpecMap *, const char *, int, const double[], int * );
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckSpecMap(this) astINVOKE_CHECK(SpecMap,this,0)
+#define astVerifySpecMap(this) astINVOKE_CHECK(SpecMap,this,1)
+
+/* Test class membership. */
+#define astIsASpecMap(this) astINVOKE_ISA(SpecMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astSpecMap astINVOKE(F,astSpecMap_)
+#else
+#define astSpecMap astINVOKE(F,astSpecMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitSpecMap(mem,size,init,vtab,name,nin,flags) \
+astINVOKE(O,astInitSpecMap_(mem,size,init,vtab,name,nin,flags,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitSpecMapVtab(vtab,name) astINVOKE(V,astInitSpecMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadSpecMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadSpecMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckSpecMap to validate SpecMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+#define astSpecAdd(this,cvt,narg,args) \
+astINVOKE(V,astSpecAdd_(astCheckSpecMap(this),cvt,narg,args,STATUS_PTR))
+
+#endif
+
+
+
+
+
diff --git a/ast/sphmap.c b/ast/sphmap.c
new file mode 100644
index 0000000..c50df91
--- /dev/null
+++ b/ast/sphmap.c
@@ -0,0 +1,2061 @@
+/*
+*class++
+*  Name:
+*     SphMap
+
+*  Purpose:
+*     Map 3-d Cartesian to 2-d spherical coordinates
+
+*  Constructor Function:
+c     astSphMap
+f     AST_SPHMAP
+
+*  Description:
+*     A SphMap is a Mapping which transforms points from a
+*     3-dimensional Cartesian coordinate system into a 2-dimensional
+*     spherical coordinate system (longitude and latitude on a unit
+*     sphere centred at the origin). It works by regarding the input
+*     coordinates as position vectors and finding their intersection
+*     with the sphere surface. The inverse transformation always
+*     produces points which are a unit distance from the origin
+*     (i.e. unit vectors).
+
+*  Inheritance:
+*     The SphMap class inherits from the Mapping class.
+
+*  Attributes:
+*     In addition to those attributes common to all Mappings, every
+*     SphMap also has the following attributes:
+*
+*     - UnitRadius: SphMap input vectors lie on a unit sphere?
+*     - PolarLong: The longitude value to assign to either pole
+
+*  Functions:
+c     The SphMap class does not define any new functions beyond those
+f     The SphMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David Berry (Starlink)
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     24-OCT-1996 (DSB):
+*        Original version.
+*     5-MAR-1997 (RFWS):
+*        Tidied public prologues.
+*     24-MAR-1998 (RFWS):
+*        Override the astMapMerge method.
+*     4-SEP-1998 (DSB):
+*        Added UnitRadius attribute.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitSphMapVtab
+*        method.
+*     11-JUN-2003 (DSB):
+*        Added PolarLong attribute.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*     5-NOV-2013 (DSB):
+*        Modify MapMerge so that it can spot and simplify an
+*        (inverted SphMap,MatrixMap,SphMap) sequence in which the
+*        MatrixMap just magnifies or reflects the radius vector.
+*     25-MAR-2014 (DSB):
+*        Correct 5-NOV-2013 MapMerge change.
+*     28-APR-2016 (DSB):
+*        Avoid modifying the attributes of the existing SphMap in
+*        MapMerge, since it may be in use in other contexts. Modify a
+*        copy instead.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS SphMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory management facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "unitmap.h"             /* Unit (identity) Mappings */
+#include "sphmap.h"              /* Interface definition for this class */
+#include "pal.h"                 /* SLA transformations */
+#include "wcsmap.h"              /* For the AST__DPIBY2 (etc) constants */
+#include "matrixmap.h"           /* Matrix mappings */
+#include "winmap.h"              /* Shift and scale mappings */
+#include "zoommap.h"             /* Scale mappings */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(SphMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(SphMap,Class_Init)
+#define class_vtab astGLOBAL(SphMap,Class_Vtab)
+#define getattrib_buff astGLOBAL(SphMap,GetAttrib_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstSphMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstSphMap *astSphMapId_( const char *, ...);
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static int GetUnitRadius( AstSphMap *, int * );
+static int TestUnitRadius( AstSphMap *, int * );
+static void ClearUnitRadius( AstSphMap *, int * );
+static void SetUnitRadius( AstSphMap *, int, int * );
+
+static double GetPolarLong( AstSphMap *, int * );
+static int TestPolarLong( AstSphMap *, int * );
+static void ClearPolarLong( AstSphMap *, int * );
+static void SetPolarLong( AstSphMap *, double, int * );
+
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+/* Member functions. */
+/* ================= */
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a SphMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status, int *status )
+
+*  Class Membership:
+*     SphMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     SphMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the SphMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSphMap *this;             /* Pointer to the SphMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SphMap structure. */
+   this = (AstSphMap *) this_object;
+
+/* UnitRadius */
+/* ---------- */
+   if ( !strcmp( attrib, "unitradius" ) ) {
+      astClearUnitRadius( this );
+
+/* PolarLong */
+/* --------- */
+   } else if ( !strcmp( attrib, "polarlong" ) ) {
+      astClearPolarLong( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two SphMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status, int *status )
+
+*  Class Membership:
+*     SphMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two SphMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a SphMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the SphMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSphMap *that;
+   AstSphMap *this;
+   int nin;
+   int nout;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two SphMap structures. */
+   this = (AstSphMap *) this_object;
+   that = (AstSphMap *) that_object;
+
+/* Check the second object is a SphMap. We know the first is a
+   SphMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsASphMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two SphMaps differ, it may still be possible
+   for them to be equivalent. First compare the SphMaps if their Invert
+   flags are the same. In this case all the attributes of the two SphMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+
+            if( astEQUAL( this->polarlong, that->polarlong ) &&
+                          this->unitradius == that->unitradius ){
+               result = 1;
+            }
+
+/* If the Invert flags for the two SphMaps differ, the attributes of the two
+   SphMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a SphMap, Invert flags must be equal. */
+            result = 0;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a SphMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status, int *status )
+
+*  Class Membership:
+*     SphMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a SphMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the SphMap.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the SphMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the SphMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
+   AstSphMap *this;              /* Pointer to the SphMap structure */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Double precision attribute value */
+   int ival;                     /* Int attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the SphMap structure. */
+   this = (AstSphMap *) this_object;
+
+/* UnitRadius. */
+/* ----------- */
+   if ( !strcmp( attrib, "unitradius" ) ) {
+      ival = astGetUnitRadius( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* PolarLong */
+/* --------- */
+   } else if ( !strcmp( attrib, "polarlong" ) ) {
+      dval = astGetPolarLong( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astInitSphMapVtab_(  AstSphMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSphMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a SphMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     void astInitSphMapVtab( AstSphMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SphMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the SphMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsASphMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->ClearUnitRadius = ClearUnitRadius;
+   vtab->SetUnitRadius = SetUnitRadius;
+   vtab->GetUnitRadius = GetUnitRadius;
+   vtab->TestUnitRadius = TestUnitRadius;
+
+   vtab->ClearPolarLong = ClearPolarLong;
+   vtab->SetPolarLong = SetPolarLong;
+   vtab->GetPolarLong = GetPolarLong;
+   vtab->TestPolarLong = TestPolarLong;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the class dump, copy and delete functions.*/
+   astSetDump( vtab, Dump, "SphMap", "Cartesian to Spherical mapping" );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a SphMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status, int *status )
+
+*  Class Membership:
+*     SphMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated SphMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated SphMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated SphMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the SphMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        SphMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated SphMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *new;              /* Pointer to replacement Mapping */
+   AstMatrixMap *mm;             /* Pointer to MatrixMap */
+   AstSphMap *sm;                /* The new SphMap */
+   AstWinMap *wm;                /* The new WinMap */
+   const char *class;            /* Pointer to Mapping class string */
+   double absval;                /* Absolute value fo each diagonal element */
+   double diag[ 3 ];             /* The diagonal matrix elements */
+   double polarlong;             /* Value of PolarLong attribute */
+   int imap1;                    /* Index of first SphMap */
+   int imap2;                    /* Index of second SphMap */
+   int imap;                     /* Loop counter for Mappings */
+   int result;                   /* Result value to return */
+   int simpler;                  /* Mappings simplified? */
+
+/* Initialise the returned result. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialisation. */
+   new = NULL;
+   simpler = 0;
+
+/* We will only handle the case of SphMaps in series and will consider
+   merging the nominated SphMap with the Mapping which follows
+   it. Check that there is such a Mapping. */
+   if ( series && ( ( where + 1 ) < *nmap ) ) {
+
+/* Obtain the indices of the two potential SphMaps to be merged. */
+      imap1 = where;
+      imap2 = where + 1;
+
+/* Obtain the Class string of the second Mapping and determine if it
+   is a SphMap. */
+      class = astGetClass( ( *map_list )[ imap2 ] );
+      if ( astOK && !strcmp( class, "SphMap" ) ) {
+
+/* Check if the first SphMap is applied in the inverse direction and
+   the second in the forward direction. This combination can be
+   simplified if the PolarLongitude attributes are equal.. */
+         if( ( *invert_list )[ imap1 ] && !( *invert_list )[ imap2 ] ) {
+            simpler = astEQUAL( astGetPolarLong( ( *map_list )[ imap1 ] ),
+                                astGetPolarLong( ( *map_list )[ imap2 ] ) );
+
+/* If the first SphMap is applied in the forward direction and the second in
+   the inverse direction, the combination can only be simplified if the
+   input vectors to the first SphMap all have unit length (as indicated by
+   the UnitRadius attribute). */
+         } else if( !( *invert_list )[ imap1 ] && ( *invert_list )[ imap2 ] ) {
+            simpler = astGetUnitRadius( ( *map_list )[ imap1 ] );
+         }
+      }
+
+/* If the two SphMaps can be simplified, create a UnitMap to replace
+   them. */
+      if ( simpler ) {
+         new = (AstMapping *) astUnitMap( 2, "", status );
+
+/* Annul the pointers to the SphMaps. */
+         if ( astOK ) {
+            ( *map_list )[ imap1 ] = astAnnul( ( *map_list )[ imap1 ] );
+            ( *map_list )[ imap2 ] = astAnnul( ( *map_list )[ imap2 ] );
+
+/* Insert the pointer to the replacement Mapping and initialise its
+   invert flag. */
+            ( *map_list )[ imap1 ] = new;
+            ( *invert_list )[ imap1 ] = 0;
+
+/* Loop to close the resulting gap by moving subsequent elements down
+   in the arrays. */
+            for ( imap = imap2 + 1; imap < *nmap; imap++ ) {
+               ( *map_list )[ imap - 1 ] = ( *map_list )[ imap ];
+               ( *invert_list )[ imap - 1 ] = ( *invert_list )[ imap ];
+            }
+
+/* Clear the vacated elements at the end. */
+            ( *map_list )[ *nmap - 1 ] = NULL;
+            ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+            ( *nmap )--;
+            result = imap1;
+         }
+      }
+   }
+
+/* Another possible simplification is if the nominated Mapping is an inverted
+   SphMap followed in series by a ZoomMap or diagonal MatrixMap that has
+   diagonal elements of equal magnitude, which is then followed by a
+   non-inverted SphMap. This is equivalent to a 3D rotation of a pair of
+   (longitude,latitude) angles. The MatrixMap/ZoomMap may magnify the
+   radius vector, but this will not alter the angles. Any difference in
+   signs amongst the diagonal elements will cause a reflection or reversal
+   of the corresponbding angles, which can be represented by a WinMap. We
+   do not need to consider the other possibility (that the nominated
+   SphMap is the *last* Mapping in such a sequence of three), since we
+   will already have discovered such a sequence on an earlier invocation
+   of this function. */
+   if( series && !simpler && ( *invert_list )[ where ] &&
+       where + 2 < *nmap  ) {
+
+/* Check the third Mapping is a non-inverted SphMap. */
+      class = astGetClass( ( *map_list )[ where + 2 ] );
+      if( astOK && !strcmp( class, "SphMap" ) &&
+          !( *invert_list )[ where + 2 ] ) {
+
+/* Check the second Mapping is a ZoomMap, or a diagonal MatrixMap that
+   has diagonal elements of equal magnitude. Since the Mapping is
+   sandwiched between the two SphMaps, we know it must have 3 inputs and
+   3 outputs. Record the corresponding diagonal values. The state of the
+   Invert flag does not matter since it will only affect the degree to
+   which the radius vector is magnified - it will not change the signs of
+   any diagonal elements. */
+         class = astGetClass( ( *map_list )[ where + 1 ] );
+         if( astOK && !strcmp( class, "ZoomMap" ) ) {
+            diag[ 0 ] = astGetZoom( ( *map_list )[ where + 1 ] );
+            if( diag[ 0 ] != 0.0 ) {
+               diag[ 1 ] = diag[ 0 ];
+               diag[ 2 ] = diag[ 0 ];
+            } else {
+               class = NULL;
+            }
+
+         } else if( astOK && !strcmp( class, "MatrixMap" ) ) {
+            mm = (AstMatrixMap *)  ( *map_list )[ where + 1 ];
+            if( mm->form == 1 && mm->f_matrix ) {
+               diag[ 0 ] = mm->f_matrix[ 0 ];
+               if( diag[ 0 ] != 0.0 ) {
+                  diag[ 1 ] = mm->f_matrix[ 1 ];
+                  diag[ 2 ] = mm->f_matrix[ 2 ];
+
+                  absval = fabs( diag[ 0 ] );
+                  if( !astEQUAL( fabs( diag[ 1 ] ), absval ) ||
+                      !astEQUAL( fabs( diag[ 2 ] ), absval ) ) {
+                     class = NULL;
+                  }
+
+               } else {
+                  class = NULL;
+               }
+
+            } else {
+               class = NULL;
+            }
+
+         } else {
+            class = NULL;
+         }
+
+      } else {
+         class = NULL;
+      }
+
+/* We can only make changes if above conditions were met. */
+      if( class ) {
+
+/* Create a WinMap that modifies the (longitude,latitude) values, initially
+   with undefined corners. */
+         wm = astWinMap( 2, NULL, NULL, NULL, NULL, "", status );
+
+/* Store appropriate scales and offsets in the WinMap. These just depend on
+   the signs of the matrix diagonal elements since we know the magnitudes of
+   these elements are all equal. */
+         if( diag[ 0 ] < 0.0 ) {
+            if( diag[ 1 ] < 0.0 ) {
+               wm->a[ 0 ] = AST__DPI;
+               wm->b[ 0 ] = 1.0;
+            } else {
+               wm->a[ 0 ] = AST__DPI;
+               wm->b[ 0 ] = -1.0;
+            }
+
+         } else {
+            if( diag[ 1 ] < 0.0 ) {
+               wm->a[ 0 ] = 0.0;
+               wm->b[ 0 ] = -1.0;
+            } else {
+               wm->a[ 0 ] = 0.0;
+               wm->b[ 0 ] = 1.0;
+            }
+         }
+
+         if( diag[ 2 ] < 0.0 ) {
+            wm->a[ 1 ] = 0.0;
+            wm->b[ 1 ] = -1.0;
+         } else {
+            wm->a[ 1 ] = 0.0;
+            wm->b[ 1 ] = 1.0;
+         }
+
+/* We are aiming to replace the supplied (SphMap,MatrixMap,SphMap)
+   combination with (WinMap,SphMap,SphMap), leaving us with an inverted
+   and non-inverted SphMap side by side. This is on the understanding
+   that a subsequent call to this function will combine these two
+   adjacent SphMaps into a UnitMap. But this will only happen if the
+   adjacent SphMaps have equal values for their PolarLong attributes. The
+   change of (SphMap,MatrixMap) to (WinMap,SphMap) will change the value
+   of the PolarLong attribute in the first SphMap, so we need to work out
+   this changed value and check that it is the same as the PolarLong
+   value of the second SphMap. If they are different, there is no point
+   making any changes since the two SphMaps cannot be merged into a
+   UnitMap. So get the PolarLong value from the supplied first SphMap. */
+         polarlong = astGetPolarLong( ( *map_list )[ where ] );
+
+/* Modified the PolarLong value to take account of the change from
+   (SphMap,MatrixMap) to (WinMap,SphMap). */
+         polarlong =  wm->a[ 0 ] + wm->b[ 0 ]*polarlong;
+
+/* Check this is the same as the PolarLong value in the second SphMap. */
+         if( astEQUAL( polarlong, astGetPolarLong( ( *map_list )[ where + 2 ] ) ) ) {
+
+/* All is good, so we can now change the supplied Mappings list. First
+   get a copy of the first SphMap and change its PolarLong value. We use
+   a copy since the original may be in use in other contexts that could be
+   badly affected by the change. */
+            sm = astCopy( ( *map_list )[ where ] );
+            astSetPolarLong( sm, polarlong );
+
+/* Annul the SphMap and the MatrixMap or ZoomMap. */
+            (void) astAnnul( ( *map_list )[ where ] );
+            (void) astAnnul( ( *map_list )[ where + 1 ] );
+
+/* Store the modified SphMap in the slot left vacant by the annulled
+   MatrixMap or ZoomMap. */
+            ( *map_list )[ where + 1 ] = (AstMapping *) sm;
+            ( *invert_list )[ where + 1 ] = ( *invert_list )[ where ];
+
+/* Store the new WinMap in the place of the SphMap. */
+            ( *map_list )[ where ] = astClone( wm );
+            ( *invert_list )[ where ] = 0;
+
+/* Return the index of the first modified element. */
+            result = where;
+         }
+
+/* Free resources. */
+         wm = astAnnul( wm );
+      }
+   }
+
+/* If an error occurred, clear the returned result. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a SphMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     SphMap member function (over-rides the astSetAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a SphMap, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the SphMap.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstSphMap *this;              /* Pointer to the SphMap structure */
+   double dval;                  /* Double precision attribute value */
+   int len;                      /* Length of setting string */
+   int ival;                     /* Int attribute value */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SphMap structure. */
+   this = (AstSphMap *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* UnitRadius */
+/* ---------- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "unitradius= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetUnitRadius( this, ival );
+
+/* PolarLong */
+/* --------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "polarlong= %lf %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetPolarLong( this, dval );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a SphMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status, int *status )
+
+*  Class Membership:
+*     SphMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a SphMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the SphMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSphMap *this;             /* Pointer to the SphMap structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the SphMap structure. */
+   this = (AstSphMap *) this_object;
+
+/* UnitRadius */
+/* ---------- */
+   if ( !strcmp( attrib, "unitradius" ) ) {
+      result = astTestUnitRadius( this );
+
+/* PolarLong */
+/* --------- */
+   } else if ( !strcmp( attrib, "polarlong" ) ) {
+      result = astTestPolarLong( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a SphMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status, int *status )
+
+*  Class Membership:
+*     SphMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a SphMap and a set of points encapsulated in a
+*     PointSet and transforms the points from Cartesian coordinates to
+*     spherical coordinates.
+
+*  Parameters:
+*     this
+*        Pointer to the SphMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the SphMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstSphMap *map;               /* Pointer to SphMap to be applied */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   int npoint;                   /* Number of points */
+   int point;                    /* Loop counter for points */
+   double *p0;                   /* Pointer to x axis value */
+   double *p1;                   /* Pointer to y axis value */
+   double *p2;                   /* Pointer to z axis value */
+   double *q0;                   /* Pointer to longitude value */
+   double *q1;                   /* Pointer to latitude value */
+   double mxerr;                 /* Largest value which is effectively zero */
+   double polarlong;             /* Longitude at either pole */
+   double v[3];                  /* Vector for a single point */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the SphMap. */
+   map = (AstSphMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse mapping, according to the
+   direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if( astOK ){
+
+/* First deal with forward mappings from Cartesian to Spherical. */
+      if( forward ){
+
+/* Get the longitude to return at either pole. */
+         polarlong = astGetPolarLong( this );
+
+/* Store pointers to the input Cartesian axes. */
+         p0 = ptr_in[ 0 ];
+         p1 = ptr_in[ 1 ];
+         p2 = ptr_in[ 2 ];
+
+/* Store pointers to the output Spherical axes. */
+         q0 = ptr_out[ 0 ];
+         q1 = ptr_out[ 1 ];
+
+/* Apply the mapping to every point. */
+         for( point = 0; point < npoint; point++ ){
+            if( *p0 != AST__BAD && *p1 != AST__BAD && *p2 != AST__BAD ){
+               v[0] = *p0;
+               v[1] = *p1;
+               v[2] = *p2;
+
+/* At either pole, return the longitude equal to PolarLong attribute. */
+               mxerr = fabs( 1000.0*v[ 2 ] )*DBL_EPSILON;
+               if( fabs( v[ 0 ] ) < mxerr && fabs( v[ 1 ] ) < mxerr ) {
+                  if( v[ 2 ] < 0.0 ) {
+                     *(q0++) = polarlong;
+                     *(q1++) = -AST__DPIBY2;
+                  } else if( v[ 2 ] > 0.0 ) {
+                     *(q0++) = polarlong;
+                     *(q1++) = AST__DPIBY2;
+                  } else {
+                     *(q0++) = AST__BAD;
+                     *(q1++) = AST__BAD;
+                  }
+
+/* Otherwise use a SLALIB function to do the conversion (SLALIB always
+   returns zero at either pole which is why we make the above check). */
+               } else {
+                  palDcc2s( v, q0++, q1++ );
+               }
+
+            } else {
+               *(q0++) = AST__BAD;
+               *(q1++) = AST__BAD;
+            }
+            p0++;
+            p1++;
+            p2++;
+         }
+
+/* Now deal with inverse mappings from Spherical to Cartesian. */
+      } else {
+
+/* Store pointers to the input Spherical axes. */
+         q0 = ptr_in[ 0 ];
+         q1 = ptr_in[ 1 ];
+
+/* Store pointers to the output Cartesian axes. */
+         p0 = ptr_out[ 0 ];
+         p1 = ptr_out[ 1 ];
+         p2 = ptr_out[ 2 ];
+
+/* Apply the mapping to every point. */
+         for( point = 0; point < npoint; point++ ){
+            if( *q0 != AST__BAD && *q1 != AST__BAD ){
+               palDcs2c( *q0, *q1, v );
+               *(p0++) = v[ 0 ];
+               *(p1++) = v[ 1 ];
+               *(p2++) = v[ 2 ];
+            } else {
+               *(p0++) = AST__BAD;
+               *(p1++) = AST__BAD;
+               *(p2++) = AST__BAD;
+
+            }
+            q0++;
+            q1++;
+         }
+
+      }
+
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* UnitRadius */
+/* ---------- */
+/*
+*att++
+*  Name:
+*     UnitRadius
+
+*  Purpose:
+*     SphMap input vectors lie on a unit sphere?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This is a boolean attribute which indicates whether the
+*     3-dimensional vectors which are supplied as input to a SphMap
+*     are known to always have unit length, so that they lie on a unit
+*     sphere centred on the origin.
+*
+c     If this condition is true (indicated by setting UnitRadius
+c     non-zero), it implies that a CmpMap which is composed of a
+c     SphMap applied in the forward direction followed by a similar
+c     SphMap applied in the inverse direction may be simplified
+c     (e.g. by astSimplify) to become a UnitMap. This is because the
+c     input and output vectors will both have unit length and will
+c     therefore have the same coordinate values.
+f     If this condition is true (indicated by setting UnitRadius
+f     non-zero), it implies that a CmpMap which is composed of a
+f     SphMap applied in the forward direction followed by a similar
+f     SphMap applied in the inverse direction may be simplified
+f     (e.g. by AST_SIMPLIFY) to become a UnitMap. This is because the
+f     input and output vectors will both have unit length and will
+f     therefore have the same coordinate values.
+*
+*     If UnitRadius is zero (the default), then although the output
+*     vector produced by the CmpMap (above) will still have unit
+*     length, the input vector may not have. This will, in general,
+*     change the coordinate values, so it prevents the pair of SphMaps
+*     being simplified.
+
+*  Notes:
+*     - This attribute is intended mainly for use when SphMaps are
+*     involved in a sequence of Mappings which project (e.g.) a
+*     dataset on to the celestial sphere. By regarding the celestial
+*     sphere as a unit sphere (and setting UnitRadius to be non-zero)
+*     it becomes possible to cancel the SphMaps present, along with
+*     associated sky projections, when two datasets are aligned using
+*     celestial coordinates. This often considerably improves
+*     performance.
+*     - Such a situations often arises when interpreting FITS data and
+*     is handled automatically by the FitsChan class.
+*     - The value of the UnitRadius attribute is used only to control
+*     the simplification of Mappings and has no effect on the value of
+*     the coordinates transformed by a SphMap. The lengths of the
+*     input 3-dimensional Cartesian vectors supplied are always
+*     ignored, even if UnitRadius is non-zero.
+*     - The value of this attribute may changed only if the SphMap
+*     has no more than one reference. That is, an error is reported if the
+*     SphMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     SphMap
+*        All SphMaps have this attribute.
+*att--
+*/
+astMAKE_CLEAR1(SphMap,UnitRadius,unitradius,-1)
+astMAKE_GET(SphMap,UnitRadius,int,0,(this->unitradius == -1 ? 0 : this->unitradius))
+astMAKE_SET1(SphMap,UnitRadius,int,unitradius,( value ? 1 : 0 ))
+astMAKE_TEST(SphMap,UnitRadius,( this->unitradius != -1 ))
+
+/* PolarLong */
+/* --------- */
+/*
+*att++
+*  Name:
+*     PolarLong
+
+*  Purpose:
+*     The longitude value to assign to either pole
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute holds the longitude value, in radians, to be
+*     returned when a Cartesian position corresponding to either the north
+*     or south pole is transformed into spherical coordinates. The
+*     default value is zero.
+*
+*     Note, the value of this attribute may changed only if the SphMap
+*     has no more than one reference. That is, an error is reported if the
+*     SphMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     SphMap
+*        All SphMaps have this attribute.
+*att--
+*/
+astMAKE_CLEAR1(SphMap,PolarLong,polarlong,AST__BAD)
+astMAKE_GET(SphMap,PolarLong,double,0.0,(this->polarlong == AST__BAD ? 0.0 : this->polarlong))
+astMAKE_SET1(SphMap,PolarLong,double,polarlong,value)
+astMAKE_TEST(SphMap,PolarLong,( this->polarlong != AST__BAD ))
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for SphMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status, int *status, int *status )
+
+*  Description:
+*     This function implements the copy constructor for SphMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the SphMap to be copied.
+*     objout
+*        Pointer to the SphMap being constructed.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for SphMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status, int *status )
+
+*  Description:
+*     This function implements the destructor for SphMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the SphMap to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This destructor does nothing and exists only to maintain a
+*     one-to-one correspondence between destructors and copy
+*     constructors.
+*/
+
+
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for SphMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status, int *status, int *status, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the SphMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the SphMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSphMap *this;              /* Pointer to the SphMap structure */
+   double dval;                  /* Double precision attribute value */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SphMap structure. */
+   this = (AstSphMap *) this_object;
+
+/* Write out values representing the instance variables for the
+   SphMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* UnitRadius. */
+/* ------- */
+   set = TestUnitRadius( this, status );
+   ival = set ? GetUnitRadius( this, status ) : astGetUnitRadius( this );
+   if( ival ) {
+      astWriteInt( channel, "UntRd", set, 0, ival, "All input vectors have unit length" );
+   } else {
+      astWriteInt( channel, "UntRd", set, 0, ival, "Input vectors do not all have unit length" );
+   }
+
+/* PolarLong. */
+/* ---------- */
+   set = TestPolarLong( this, status );
+   dval = set ? GetPolarLong( this, status ) : astGetPolarLong( this );
+   astWriteDouble( channel, "PlrLg", set, 1, dval, "Polar longitude (rad.s)" );
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsASphMap and astCheckSphMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(SphMap,Mapping)
+astMAKE_CHECK(SphMap)
+
+AstSphMap *astSphMap_( const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astSphMap
+f     AST_SPHMAP
+
+*  Purpose:
+*     Create a SphMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "sphmap.h"
+c     AstSphMap *astSphMap( const char *options, ... )
+f     RESULT = AST_SPHMAP( OPTIONS, STATUS )
+
+*  Class Membership:
+*     SphMap constructor.
+
+*  Description:
+*     This function creates a new SphMap and optionally initialises
+*     its attributes.
+*
+*     A SphMap is a Mapping which transforms points from a
+*     3-dimensional Cartesian coordinate system into a 2-dimensional
+*     spherical coordinate system (longitude and latitude on a unit
+*     sphere centred at the origin). It works by regarding the input
+*     coordinates as position vectors and finding their intersection
+*     with the sphere surface. The inverse transformation always
+*     produces points which are a unit distance from the origin
+*     (i.e. unit vectors).
+
+*  Parameters:
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new SphMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new SphMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSphMap()
+f     AST_SPHMAP = INTEGER
+*        A pointer to the new SphMap.
+
+*  Notes:
+*     - The spherical coordinates are longitude (positive
+*     anti-clockwise looking from the positive latitude pole) and
+*     latitude. The Cartesian coordinates are right-handed, with the x
+*     axis (axis 1) at zero longitude and latitude, and the z axis
+*     (axis 3) at the positive latitude pole.
+*     - At either pole, the longitude is set to the value of the
+*     PolarLong attribute.
+*     - If the Cartesian coordinates are all zero, then the longitude
+*     and latitude are set to the value AST__BAD.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSphMap *new;              /* Pointer to new SphMap */
+   va_list args;                /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SphMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitSphMap( NULL, sizeof( AstSphMap ), !class_init, &class_vtab,
+                        "SphMap" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new SphMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new SphMap. */
+   return new;
+}
+
+AstSphMap *astSphMapId_( const char *options, ...) {
+/*
+*  Name:
+*     astSphMapId_
+
+*  Purpose:
+*     Create a SphMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     AstSphMap *astSphMapId_( const char *options, ... )
+
+*  Class Membership:
+*     SphMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astSphMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astSphMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astSphMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astSphMap_.
+
+*  Returned Value:
+*     The ID value associated with the new SphMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSphMap *new;              /* Pointer to new SphMap */
+   va_list args;                /* Variable argument list */
+   int *status;                 /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the SphMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitSphMap( NULL, sizeof( AstSphMap ), !class_init, &class_vtab,
+                        "SphMap" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new SphMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new SphMap. */
+   return astMakeId( new );
+}
+
+AstSphMap *astInitSphMap_( void *mem, size_t size, int init,
+                           AstSphMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSphMap
+
+*  Purpose:
+*     Initialise a SphMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     AstSphMap *astInitSphMap( void *mem, size_t size, int init,
+*                               AstSphMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SphMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new SphMap object. It allocates memory (if necessary) to accommodate
+*     the SphMap plus any additional data associated with the derived class.
+*     It then initialises a SphMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a SphMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the SphMap is to be initialised.
+*        This must be of sufficient size to accommodate the SphMap data
+*        (sizeof(SphMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the SphMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the SphMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the SphMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new SphMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+
+*  Returned Value:
+*     A pointer to the new SphMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSphMap *new;              /* Pointer to new SphMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitSphMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the SphMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstSphMap *) astInitMapping( mem, size, 0,
+                                       (AstMappingVtab *) vtab, name,
+                                       3, 2, 1, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the SphMap data. */
+/* --------------------------- */
+/* Are all input vectors of unit length? Store a value of -1 to indicate that
+   no value has yet been set. This will cause a default value of 0 (no, i.e.
+   input vectors are not all of unit length) to be used. */
+      new->unitradius = -1;
+      new->polarlong = AST__BAD;
+
+   }
+
+/* Return a pointer to the new SphMap. */
+   return new;
+}
+
+AstSphMap *astLoadSphMap_( void *mem, size_t size,
+                           AstSphMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadSphMap
+
+*  Purpose:
+*     Load a SphMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "sphmap.h"
+*     AstSphMap *astLoadSphMap( void *mem, size_t size,
+*                               AstSphMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     SphMap loader.
+
+*  Description:
+*     This function is provided to load a new SphMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     SphMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a SphMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the SphMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        SphMap data (sizeof(SphMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the SphMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the SphMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstSphMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new SphMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the SphMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "SphMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new SphMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSphMap *new;               /* Pointer to the new SphMap */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this SphMap. In this case the
+   SphMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstSphMap );
+      vtab = &class_vtab;
+      name = "SphMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitSphMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built SphMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "SphMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* UnitRadius. */
+/* ----------- */
+      new->unitradius = astReadInt( channel, "untrd", -1 );
+      if ( TestUnitRadius( new, status ) ) SetUnitRadius( new, new->unitradius, status );
+
+/* PolarLong. */
+/* ---------- */
+      new->polarlong = astReadDouble( channel, "plrlg", AST__BAD );
+      if ( TestPolarLong( new, status ) ) SetPolarLong( new, new->polarlong, status );
+
+   }
+
+/* If an error occurred, clean up by deleting the new SphMap. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the new SphMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
diff --git a/ast/sphmap.h b/ast/sphmap.h
new file mode 100644
index 0000000..905703b
--- /dev/null
+++ b/ast/sphmap.h
@@ -0,0 +1,374 @@
+#if !defined( SPHMAP_INCLUDED ) /* Include this file only once */
+#define SPHMAP_INCLUDED
+/*
+*+
+*  Name:
+*     sphmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the SphMap class.
+
+*  Invocation:
+*     #include "sphmap.h"
+
+*  Description:
+*     This include file defines the interface to the SphMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The SphMap class implements Mappings which maps positions from
+*     3-dimensional Cartesian coordinates into 2-dimensional spherical
+*     coordinates (i.e. longitude and latitude on a unit sphere). The
+*     inverse Mapping always produces vectors of unit length.
+*
+*     The spherical coordinates are longitude (positive anti-clockwise
+*     looking from the positive latitude pole) and latitude. The
+*     Cartesian coordinates are right-handed, with the x-axis (axis 1)
+*     at zero longitude and latitude, and the z-axis (axis 3) at the
+*     positive latitude pole.
+*
+*     At either pole, the longitude is set to the value of the PolarLong
+*     attribute. If the Cartesian coordinates are all zero, then the
+*     longitude and latitude values are set to AST__BAD.
+
+*  Inheritance:
+*     The SphMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     PolarLong (double)
+*        This attribute holds the longitude value, in radians, to be
+*        returned when a Cartesian position corresponding to either the north
+*        or south pole is transformed into spherical coordinates. The
+*        default value is zero.
+*     UnitRadius (integer)
+*        This is a boolean attribute which indicates whether the
+*        3-dimensional vectors which are supplied as input to a SphMap
+*        are known to always have unit length, so that they lie on a
+*        unit sphere centred on the origin.
+*
+*        If this condition is true (indicated by setting UnitRadius
+*        non-zero), it implies that a CmpMap which is composed of a
+*        SphMap applied in the forward direction followed by a similar
+*        SphMap applied in the inverse direction may be simplified
+*        (e.g. by astSimplify) to become a UnitMap. This is because
+*        the input and output vectors will both have unit length and
+*        will therefore have the same coordinate values.
+*
+*        If UnitRadius is zero (the default), then although the output
+*        vector produced by the CmpMap (above) will still have unit
+*        length, the input vector may not have. This will, in general,
+*        change the coordinate values, so it prevents the pair of
+*        SphMaps being simplified.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearAttrib
+*           Clear an attribute value for a SphMap.
+*        astGetAttrib
+*           Get an attribute value for a SphMap.
+*        astMapMerge
+*           Simplify a sequence of Mappings containing a SphMap.
+*        astSetAttrib
+*           Set an attribute value for a SphMap.
+*        astTestAttrib
+*           Test if an attribute value has been set for a SphMap.
+*        astTransform
+*           Apply a SphMap to transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearUnitRadius
+*           Clear the UnitRadius attribute value for a SphMap.
+*        astGetUnitRadius
+*           Get the UnitRadius attribute value for a SphMap.
+*        astSetUnitRadius
+*           Set the UnitRadius attribute value for a SphMap.
+*        astTestUnitRadius
+*           Test if a UnitRadius attribute value has been set for a SphMap.
+*        astClearPolarLong
+*           Clear the PolarLong attribute value for a SphMap.
+*        astGetPolarLong
+*           Get the PolarLong attribute value for a SphMap.
+*        astSetPolarLong
+*           Set the PolarLong attribute value for a SphMap.
+*        astTestPolarLong
+*           Test if a PolarLong attribute value has been set for a SphMap.
+
+*  Other Class Functions:
+*     Public:
+*        astIsASphMap
+*           Test class membership.
+*        astSphMap
+*           Create a SphMap.
+*
+*     Protected:
+*        astCheckSphMap
+*           Validate class membership.
+*        astInitSphMap
+*           Initialise a SphMap.
+*        astInitSphMapVtab
+*           Initialise the virtual function table for the SphMap class.
+*        astLoadSphMap
+*           Load a SphMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstSphMap
+*           SphMap object type.
+*
+*     Protected:
+*        AstSphMapVtab
+*           SphMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     25-OCT-1996 (DSB):
+*        Original version.
+*     24-MAR-1998 (RFWS):
+*        Override the astMapMerge method.
+*     4-SEP-1998 (DSB):
+*        Added UnitRadius attribute.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitSphMapVtab method.
+*     11-JUN-2003 (DSB):
+*        Added PolarLong attribute.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* SphMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstSphMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double polarlong;             /* Longitude to assign to either pole */
+   int unitradius;               /* Are input vectors always of unit length? */
+} AstSphMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstSphMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   int (* GetUnitRadius)( AstSphMap *, int * );
+   int (* TestUnitRadius)( AstSphMap *, int * );
+   void (* ClearUnitRadius)( AstSphMap *, int * );
+   void (* SetUnitRadius)( AstSphMap *, int, int * );
+
+   double (* GetPolarLong)( AstSphMap *, int * );
+   int (* TestPolarLong)( AstSphMap *, int * );
+   void (* ClearPolarLong)( AstSphMap *, int * );
+   void (* SetPolarLong)( AstSphMap *, double, int * );
+} AstSphMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstSphMapGlobals {
+   AstSphMapVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 51 ];
+} AstSphMapGlobals;
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(SphMap)          /* Check class membership */
+astPROTO_ISA(SphMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstSphMap *astSphMap_( const char *, int *, ...);
+#else
+AstSphMap *astSphMapId_( const char *, ...)__attribute__((format(printf,1,2)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstSphMap *astInitSphMap_( void *, size_t, int, AstSphMapVtab *,
+                           const char *, int * );
+
+/* Vtab initialiser. */
+void astInitSphMapVtab_( AstSphMapVtab *, const char *, int * );
+
+/* Loader. */
+AstSphMap *astLoadSphMap_( void *, size_t, AstSphMapVtab *,
+                           const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitSphMapGlobals_( AstSphMapGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+int astGetUnitRadius_( AstSphMap *, int * );
+int astTestUnitRadius_( AstSphMap *, int * );
+void astClearUnitRadius_( AstSphMap *, int * );
+void astSetUnitRadius_( AstSphMap *, int, int * );
+
+double astGetPolarLong_( AstSphMap *, int * );
+int astTestPolarLong_( AstSphMap *, int * );
+void astClearPolarLong_( AstSphMap *, int * );
+void astSetPolarLong_( AstSphMap *, double, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckSphMap(this) astINVOKE_CHECK(SphMap,this,0)
+#define astVerifySphMap(this) astINVOKE_CHECK(SphMap,this,1)
+
+/* Test class membership. */
+#define astIsASphMap(this) astINVOKE_ISA(SphMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astSphMap astINVOKE(F,astSphMap_)
+#else
+#define astSphMap astINVOKE(F,astSphMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define \
+astInitSphMap(mem,size,init,vtab,name) \
+astINVOKE(O,astInitSphMap_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitSphMapVtab(vtab,name) astINVOKE(V,astInitSphMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadSphMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadSphMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckSphMap to validate SphMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astClearUnitRadius(this)     astINVOKE(V,astClearUnitRadius_(astCheckSphMap(this),STATUS_PTR))
+#define astGetUnitRadius(this)       astINVOKE(V,astGetUnitRadius_(astCheckSphMap(this),STATUS_PTR))
+#define astSetUnitRadius(this,value) astINVOKE(V,astSetUnitRadius_(astCheckSphMap(this),value,STATUS_PTR))
+#define astTestUnitRadius(this)      astINVOKE(V,astTestUnitRadius_(astCheckSphMap(this),STATUS_PTR))
+
+#define astClearPolarLong(this)     astINVOKE(V,astClearPolarLong_(astCheckSphMap(this),STATUS_PTR))
+#define astGetPolarLong(this)       astINVOKE(V,astGetPolarLong_(astCheckSphMap(this),STATUS_PTR))
+#define astSetPolarLong(this,value) astINVOKE(V,astSetPolarLong_(astCheckSphMap(this),value,STATUS_PTR))
+#define astTestPolarLong(this)      astINVOKE(V,astTestPolarLong_(astCheckSphMap(this),STATUS_PTR))
+#endif
+
+#endif
+
+
+
+
+
diff --git a/ast/sst.sty b/ast/sst.sty
new file mode 100644
index 0000000..55f98dc
--- /dev/null
+++ b/ast/sst.sty
@@ -0,0 +1,234 @@
+\ProvidesPackage{sst}
+
+%%%%
+% Packet for formatting latex code output from prolat
+%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% .. Set up the sectioning and appearance for the sstroutine.
+
+\RequirePackage{titlesec}
+
+%...  New subsection sstrefsection
+\titleclass{\sstrefsection}{straight}[\subsection]
+\newcounter{sstrefsection}
+\renewcommand{\thesstrefsection}{\arabic{sstrefsection}}
+
+%...  Formatting the title for sstrefsection
+\titleformat{name=\sstrefsection}%
+{\normalfont\Large\bfseries\centering}{}{0pt}{}
+\titlespacing*{\sstrefsection}{0pt}{3.5ex plus 1ex minus .2ex}{2.3ex plus .2ex}
+
+%...  Command to turn name and short description into single variable for title.
+\newcommand{\sstsectitle}[2]{\strut \vphantom{#1}#1\newline \ignorespaces#2\strut}
+
+% ..  Command to completely remove section from contents so it can be
+% added in manually at the correct section.
+\newcommand{\nocontentsline}[3]{}
+\newcommand{\tocless}[2]{\bgroup\let\addcontentsline=\nocontentsline#1{#2}\egroup}
+%% Add a toc entry which will be used in  the main toc
+\newcommand{\sstmaintocline}[2]{\addcontentsline{toc}{subsection}{\protect\numberline{}#1}}
+
+%% commands to temporarily stop sstroutines from writing anything into the main toc
+\newcommand{\sstnomaintoc}{\renewcommand{\sstmaintocline}[2]{}}
+%% Command to restart sstroutines appearing in the main html toc
+%% (does nothing in pdf)
+\newcommand{\sstmaintoc}{\renewcommand{\sstmaintocline}[2]{\addcontentsline{toc}{subsection}{\protect\numberline{}##1}}}
+
+
+
+%% .. command to format the sst title
+\newcommand{\ssttitle}[2]{
+  \tocless\sstrefsection{\centering\rule{\textwidth}{0.5mm}\\%
+    #1\\#2\\\rule{\textwidth}{0.5mm}}
+  \sstmaintocline{\RemoveSpaces{#1}}{#2}
+}
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%...  Basic  SSTROUTINE command
+\newcommand{\sstroutine}[3]{
+  \clearpage
+  % Create the section.
+  \ssttitle{#1}{#2}
+
+  % Set the first parameter as the label; need to remove spaces first.
+  \label{\RemoveSpaces{#1}}
+
+  %..  change the mark on the left hand side to include the name of the chapter.
+  \markright{\textit{\RemoveSpaces{#1}}}
+  \iftwoside
+  \fancyhead[RE,LO]{\thepage\hspace{1cm}\rightmark}
+  \else
+  \lhead{\thepage\hspace{1cm}\rightmark}
+  \fi
+
+  %..  Nest all the material within a description
+  \begin{description}[style=nextline]
+    #3
+  \end{description}
+
+  % End the page
+  \newpage
+}
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% .. Various sst macros that are used within an \sstroutine
+
+% .. diytopic -- basis for many of th commands.
+\newcommand{\sstdiytopic}[2]{\item[#1:]
+\begin{description}[style=nextline]\item[]\end{description} #2}
+
+\newcommand{\sstdescription}[1]{\item[Description:] #1 }
+
+\newcommand{\sstusage}[1]{\item[Usage:]{\raggedright \tt #1}}
+
+\newcommand{\sstparameters}[1]{\item[Parameters:]
+\begin{description}[style=nextline]\item[] #1 \end{description}}
+
+\newcommand{\sstkeywords}[1]{\item[Keywords:]
+\begin{description}[style=nextline]\item[] #1 \end{description}}
+
+\newcommand{\sstsubsection}[2]{\item[{#1}] #2}
+
+\newcommand{\sstnotes}[1]{\sstdiytopic{Notes}{#1}}
+
+\newcommand{\sstdiylist}[2]{\item[#1] #2}
+
+\newcommand{\sstimplementationstatus}[1]{\item[Implementation Status:] #1}
+
+\newcommand{\sstbugs}[1]{\item[Bugs:] #1}
+
+%  Format a list of items while in paragraph mode.
+\newcommand{\sstitemlist}[1]{
+  \mbox{}%\vspace{-1\baselineskip}
+  \begin{itemize}
+     #1
+  \end{itemize}
+}
+
+\newcommand{\ssthitemlist}[1]{\mbox{}\begin{itemize}#1\end{itemize}}
+\newcommand{\sstapplicability}[1]{\sstdiytopic{Applicability}{#1}}
+\newcommand{\sstresparameters}[1]{\sstdiytopic{Results Parameters}{#1}}
+\newcommand{\sstarguments}[1]{\sstdiytopic{Arguments}{#1}}
+\newcommand{\sstinvocation}[1]{\sstdiytopic{Invocation}{{\tt #1}}}
+\newcommand{\sstreturnedvalue}[1]{\sstdiytopic{Returned Value}{#1}}
+\newcommand{\sstimplementation}[1]{\sstdiytopic{Implementation}{#1}}
+
+
+\newcommand{\sstexamplesubsection}[2]{\sloppy
+\item[{\texttt{\textcolor{MidnightBlue}{#1}}}]
+#2 }
+
+% Set the font of the label of the sstexample items:
+% (extra \\ spacing is required to not have weird effects. I think the
+% important point is to explicitly force the label into vertical mode,
+% so it doesn't try and continue the previous paragraph).
+\newcommand{\sstexamplefont}[1]{\\#1\\\\}
+
+\newcommand{\sstexamples}[1]{
+  \setlength{\parindent}{0mm}
+   \item[Examples:]
+   \begin{description}[style=unboxed,font=\sstexamplefont,
+     leftmargin=0pt,]
+      #1
+   \end{description}
+}
+
+%  Define the format of an item.
+\newcommand{\sstitem}{\item\mbox{}}
+
+%  Format the attribute data type section.
+\providecommand{\sstattributetype}[1]{}%\item[Type:]#1}
+
+% an environment for references (for the SST sstdiytopic command).
+\newenvironment{refs}{\vspace{-4ex} % normally 3ex
+                      \begin{list}{}{\setlength{\topsep}{0mm}
+                                     \setlength{\partopsep}{0mm}
+                                     \setlength{\itemsep}{0mm}
+                                     \setlength{\parsep}{0mm}
+                                     \setlength{\leftmargin}{1.5em}
+                                     \setlength{\itemindent}{-\leftmargin}
+                                     \setlength{\labelsep}{0mm}
+                                     \setlength{\labelwidth}{0mm}}
+                    }{\end{list}}
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% .. Additional SST-routine like commands.
+
+
+% .. SST attribute command: similar to an sstroutine, but slightly
+% different formatting and doesn't force a new page. See SUN/95 for
+% example of usage. Also doesn't appear in the table of contents.
+
+\newcommand{\sstattribute}[3]{
+  % Create the section.
+  \subsubsection*{\centering \rule{\textwidth}{0.5mm}\\\Large\ignorespaces#1
+    \\#2\\\rule{\textwidth}{0.5mm}}
+
+  % Set the first parameter as the label; need to remove spaces first.
+  \label{\RemoveSpaces{#1}}
+
+  %..  change the mark on the left hand side to include the name of the section.
+  \markright{\textit{\RemoveSpaces{#1}}}
+
+  \iftwoside
+  \fancyhead[RE,LO]{\thepage\hspace{1cm}\rightmark}
+  \else
+  \lhead{\thepage\hspace{1cm}\rightmark}
+  \fi
+
+  %..  Nest all the material within a description
+  \begin{description}[style=nextline]
+    #3
+  \end{description}%
+}
+
+
+%% SSTroutinenolabel should be same as regular sstroutine, but not
+%% define a label (see sun209 -- this is because sun209 uses \textit
+%% in some of its macro titles, so thesse cannot be automatically
+%% changed to a label. The label must be manually created when
+%% using these.
+\newcommand{\sstroutinenolabel}[3]{
+  \clearpage
+  % Create the section.
+  \ssttitle{#1}{#2}
+
+  %..  change the mark on the left hand side to include the name of the chapter.
+  \markright{\textit{\RemoveSpaces{#1}}}
+  \iftwoside
+  \fancyhead[RE,LO]{\thepage\hspace{1cm}\rightmark}
+  \else
+  \lhead{\thepage\hspace{1cm}\rightmark}
+  \fi
+
+  %..  Nest all the material within a description
+  \begin{description}[style=nextline]
+    #3
+  \end{description}
+
+  % End the page
+  \newpage
+}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% .. Miscellaneous
+
+%% Change the \wedge command to something that looks better.
+\renewcommand{\wedge}{\mbox{\textasciicircum}}
+
+
+%% command to prevent sstroutines from appearing in the extra html toc
+%% called by \sstminitoc{Title of Toc} (does nothing in pdf)
+\newcommand{\sstnoextratoc}{}
+%% Command to restart sstroutines appearing in the extra html toc
+%% called by \sstminitoc{Title of Toc} (does nothing in pdf)
+\newcommand{\sstextratoc}{}
+
+
+% Command that in html typesets a listing of all
+% sstroutines/sstroutinenolables that occur before the next sectioning
+% command. Does nothing in pdf at the moment.
+\newcommand{\sstminitoc}[1]{}
diff --git a/ast/starabbrev.sty b/ast/starabbrev.sty
new file mode 100644
index 0000000..7396b9f
--- /dev/null
+++ b/ast/starabbrev.sty
@@ -0,0 +1,284 @@
+%  Shortcuts
+%  ---------
+
+
+%% Handling angles
+\ifpdf
+\typeout{... Using standard SIunitx degrees}
+\else
+\sisetup{
+  math-degree=\HCode{<mo>&deg;</mo>},
+  text-degree=\HCode{&deg;},
+  text-arcminute=\HCode{&prime;},
+  text-arcsecond=\HCode{&prime;&prime;}
+}
+\fi
+
+%% Symbol for degrees;
+\ifpdf
+  \newcommand{\dgs}{\si{\degree}}
+\else
+  \newcommand{\dgs}{\ifmmode \HCode{<mo>&deg;</mo>} \else \HCode{&deg;} \fi}
+\fi
+
+%%
+% arcminute symbol
+\newcommand{\arcm}{\si{\arcminute}}
+
+% arcsec symbol
+\newcommand{\arcsec}{\si{\arcsecond}}
+
+% hours symbol
+\newcommand{\hr}{\textsuperscript{h}}
+
+% minutes symbol
+\newcommand{\mn}{\textsuperscript{m}}
+
+% seconds symbol
+\newcommand{\rsec}{\textsuperscript{s}}
+
+
+% commands for ra and dec (These do not put the angle symbol over the
+% decimal.
+\newcommand{\dec}[1]{\ang[retain-explicit-plus]{#1}}
+\ifpdf
+\newcommand{\ra}[1]{%
+  \ang[%
+    math-degree=\textsuperscript{h},
+    math-arcminute=\textsuperscript{m},
+    math-arcsecond=\textsuperscript{s},
+    text-degree=\textsuperscript{h},
+    text-arcminute=\textsuperscript{m},
+    text-arcsecond=\textsuperscript{s}]
+      {#1}
+      }
+\else
+\newcommand{\ra}[1]{%
+  \ifmmode
+  \ang[%
+    text-degree=\HCode{<sup>h</sup>},
+    math-degree=\HCode{<sup>h</sup>},
+    math-arcminute=\HCode{<sup>m</sup>},
+    math-arcsecond=\HCode{<sup>s</sup>},
+    text-arcminute=\HCode{<sup>m</sup>},
+    text-arcsecond=\HCode{<sup>s</sup>},
+  ]%
+  {#1}%
+  \else
+  \ang[%
+    text-degree=\textsuperscript{h},
+    text-arcminute=\textsuperscript{m},
+    text-arcsecond=\textsuperscript{s}]
+      {#1}
+  \fi
+}
+\fi
+
+
+%% Note that due to limitations in tex4ht, you need to use \sb for _
+%% and \sp for ^ if the macro is defined before the \begin{document}
+%% command.
+
+% Typographical shortcuts
+\newcommand{\fcfbe}{\ensuremath{\mathrm{FCF\sb{beamequiv}}}}
+\newcommand{\fcfb}{\ensuremath{\mathrm{FCF\sb{beam}}}}
+\newcommand{\fcfa}{$\mathrm{FCF\sb{arcsec}}$}
+\newcommand{\fcfm}{$\mathrm{FCF\sb{match}}$}
+
+% Starlink Package name
+\newcommand{\starlink}{\href{http://www.starlink.ac.uk}{Starlink}}
+
+% Set up some common package names.
+\newcommand{\ccdpack}{\xref{\textsc{Ccdpack}}{sun139}{}}
+\newcommand{\convert}{\xref{\textsc{Convert}}{sun55}{}}
+\newcommand{\cupid}{\xref{\textsc{Cupid}}{sun255}{}}
+\newcommand{\datacube}{\xref{\textsc{Datacube}}{sun237}{}}
+\newcommand{\Figaro}{\xref{\textsc{Figaro}}{sun86}{}}
+\newcommand{\fluxes}{\xref{\textsc{Fluxes}}{sun213}{}}
+\newcommand{\gaia}{\xref{\textsc{Gaia}}{sun214}{}}
+\newcommand{\Kappa}{\xref{\textsc{Kappa}}{sun95}{}}
+\newcommand{\agi}{\xref{AGI}{sun48}{}}
+\newcommand{\ndf}{\xref{NDF}{sun33}{}}
+\newcommand{\surf}{\xref{\textsc{Surf}}{sun216}{}}
+\newcommand{\jcmtdr}{\xref{\textsc{JCMTdr}}{sun132}{}}
+\newcommand{\oracdr}{\href{http://www.oracdr.org/oracdr}{ORAC-DR}}
+\newcommand{\photom}{\xref{\textsc{Photom}}{sun45}{}}
+\newcommand{\picard}{\xref{\textsc{Picard}}{sun265}{}}
+\newcommand{\smurf}{\xref{\textsc{Smurf}}{sun258}{}}
+\newcommand{\splat}{\xref{\textsc{Splat}}{sun243}{}}
+\newcommand{\ssds}{\xref{\textsc{Starlink Standard Data Structures}}{sgp38}{}}
+\newcommand{\topcat}{\href{http://www.starlink.ac.uk/topcat}{\textsc{Topcat}}}
+
+% DR recipe names
+\newcommand{\drrecipe}[1]{\texttt{#1}}
+
+% Application tasks
+\newcommand{\task}[1]{\textsf{#1}}
+
+
+
+% ADAM parameters
+\newcommand{\param}[1]{\texttt{#1}}
+
+% Environment variables, filenames, URLs, and model names
+% These are the same at the moment but could be adjusted in one place.
+\newcommand{\envvar}[1]{\texttt{#1}}
+\newcommand{\file}[1]{\texttt{#1}}
+\newcommand{\model}[1]{\texttt{#1}}
+%\providecommand{\url}[1]{\texttt{#1}}
+
+% GAIA menu functions and buttons.  Would like a bold texttt to mimic
+% their appearance in GAIA, but the founts are not in regular LaTeX.
+\newcommand{\gaiathing}[1]{\textbf{\textsf{#1}}}
+
+% SMURF tasks
+\newcommand{\calcnoise}{\xref{\task{calcnoise}}{sun258}{CALCNOISE}}
+\newcommand{\clean}{\xref{\task{sc2clean}}{sun258}{SC2CLEAN}}
+\newcommand{\concat}{\xref{\task{sc2concat}}{sun258}{SC2CONCAT}}
+\newcommand{\configmeld}{\xref{\task{configmeld}}{sun258}{CONFIGMELD}}
+\newcommand{\flatfield}{\xref{\task{flatfield}}{sun258}{FLATFIELD}}
+\newcommand{\jcmtstate}{\xref{\task{jcmtstate2cat}}{sun258}{JCMTSTATE2CAT}}
+\newcommand{\makemap}{\xref{\task{makemap}}{sun258}{MAKEMAP}}
+\newcommand{\skyloop}{\xref{\task{skyloop}}{sun258}{SKYLOOP}}
+\newcommand{\stackframes}{\xref{\task{stackframes}}{sun258}{STACKFRAMES}}
+
+% KAPPA
+\newcommand{\beamfit}{\xref{\task{beamfit}}{sun95}{BEAMFIT}}
+\newcommand{\block}{\xref{\task{block}}{sun95}{BLOCK}}
+\newcommand{\chpix}{\xref{\task{chpix}}{sun95}{CHPIX}}
+\newcommand{\cdiv}{\xref{\task{cdiv}}{sun95}{CDIV}}
+\newcommand{\cmult}{\xref{\task{cmult}}{sun95}{CMULT}}
+\newcommand{\compave}{\xref{\task{compave}}{sun95}{COMPAVE}}
+\newcommand{\configecho}{\xref{\task{configecho}}{sun95}{CONFIGECHO}}
+\newcommand{\fitslist}{\xref{\task{fitslist}}{sun95}{FITSLIST}}
+\newcommand{\fitsval}{\xref{\task{fitsval}}{sun95}{FITSVAL}}
+\newcommand{\gausmooth}{\xref{\task{gausmooth}}{sun95}{GAUSMOOTH}}
+\newcommand{\hislist}{\xref{\task{hislist}}{sun95}{HISLIST}}
+\newcommand{\histat}{\xref{\task{histat}}{sun95}{HISTAT}}
+\newcommand{\histogram}{\xref{\task{histogram}}{sun95}{HISTOGRAM}}
+\newcommand{\linplot}{\xref{\task{linplot}}{sun95}{LINPLOT}}
+\newcommand{\makesnr}{\xref{\task{makesnr}}{sun95}{MAKESNR}}
+\newcommand{\ndfcopy}{\xref{\task{ndfcopy}}{sun95}{NDFCOPY}}
+\newcommand{\ndftrace}{\xref{\task{ndftrace}}{sun95}{NDFTRACE}}
+\newcommand{\paste}{\xref{\task{paste}}{sun95}{PASTE}}
+\newcommand{\provshow}{\xref{\task{provshow}}{sun95}{PROVSHOW}}
+\newcommand{\showqual}{\xref{\task{showqual}}{sun95}{SHOWQUAL}}
+\newcommand{\setvar}{\xref{\task{setvar}}{sun95}{SETVAR}}
+\newcommand{\stats}{\xref{\task{stats}}{sun95}{STATS}}
+\newcommand{\sub}{\xref{\task{sub}}{sun95}{SUB}}
+\newcommand{\wcsattrib}{\xref{\task{wcsattrib}}{sun95}{WCSATTRIB}}
+\newcommand{\wcsframe}{\xref{\task{wcsframe}}{sun95}{WCSFRAME}}
+\newcommand{\wcsmosaic}{\xref{\task{wcsmosaic}}{sun95}{WCSMOSAIC}}
+
+% CCDPACK
+\newcommand{\makemos}{\xref{\task{makemos}}{sun139}{MAKEMOS}}
+
+% CUPID
+\newcommand{\findback}{\xref{\task{findback}}{sun255}{FINDBACK}}
+\newcommand{\findclumps}{\xref{\task{findclumps}}{sun255}{FINDCLUMPS}}
+
+% Misc
+\newcommand{\autophotom}{\xref{\task{autophotom}}{sun45}{AUTOPHOTOM}}
+\newcommand{\fitstondf}{\xref{\task{fits2ndf}}{sun55}{FITS2NDF}}
+
+\newcommand{\stardocs}[2]{\href{http://www.starlink.ac.uk/docs/#1#2.htx/#1#2.html}{\textbf{\uppercase{#1}/#2}}}
+% Documents
+\newcommand{\convertsun}{\xref{\textbf{SUN/55}}{sun55}{}}
+\newcommand{\cupidsun}{\xref{\textbf{SUN/255}}{sun255}{}}
+\newcommand{\gaiasun}{\xref{\textbf{SUN/214}}{sun214}{}}
+\newcommand{\hdstracesun}{\xref{\textbf{SUN/102}}{sun102}{}}
+\newcommand{\kappasun}{\xref{\textbf{SUN/95}}{sun95}{}}
+\newcommand{\oracdrsun}{\xref{\textbf{SUN/230}}{sun230}{}}
+\newcommand{\picardsun}{\stardocs{sun}{265}}
+\newcommand{\pipelinesun}{\xref{\textbf{SUN/264}}{sun264}{}}
+\newcommand{\smurfsun}{\xref{\textbf{SUN/258}}{sun258}{}}
+
+
+% Shorthand and HTML references for other Starlink tasks
+\providecommand{\CCDPACK}{\textsc{ccdpack}}
+\providecommand{\CCDPACKref}{\xref{\CCDPACK}{sun139}{}}
+\providecommand{\GAIA}{\textsc{gaia}}
+\providecommand{\GAIAref}{\xref{\GAIA}{sun214}{}}
+\providecommand{\HDSTRACE}{\textsc{hdstrace}}
+\providecommand{\HDSTRACEref}{\xref{\HDSTRACE}{sun102}{}}
+\providecommand{\KAPPA}{\textsc{kappa}}
+\providecommand{\CURSA}{\xref{\textsc{cursa}}{sun190}{}}
+\providecommand{\KAPPAref}{\xref{(SUN/95)}{sun95}{}}
+\providecommand{\SMURF}{\textsc{smurf}}
+\providecommand{\SMURFcook}{\xref{SC/21}{sc21}{}}
+\providecommand{\ADAMsgref}{\xref{SG/4}{sg4}{}}
+\providecommand{\ADAMsunref}{\xref{SUN/101}{sun101}{}}
+\providecommand{\astref}{\xref{SUN/211}{sun211}{}}
+\providecommand{\ndfref}{\xref{SUN/33}{sun33}{}}
+
+% Application tasks
+\providecommand{\task}[1]{\textsf{#1}}
+
+% SMURF tasks
+\providecommand{\badbolos}{\xref{\task{badbolos}}{sun258}{BADBOLOS}}
+\providecommand{\calcdark}{\xref{\task{calcdark}}{sun258}{CALCDARK}}
+\providecommand{\calcflat}{\xref{\task{calcflat}}{sun258}{CALCFLAT}}
+\providecommand{\calcnoise}{\xref{\task{calcnoise}}{sun258}{CALCNOISE}}
+\providecommand{\calcresp}{\xref{\task{calcresp}}{sun258}{CALCRESP}}
+\providecommand{\copyflat}{\xref{\task{copyflat}}{sun258}{COPYFLAT}}
+\providecommand{\dreamsolve}{\xref{\task{dreamsolve}}{sun258}{DREAMSOLVE}}
+\providecommand{\dreamweights}{\xref{\task{dreamweights}}{sun258}{DREAMWEIGHTS}}
+%  ...use fitdd instead of fit1d because the 1 breaks the macro
+\providecommand{\fitdd}{\xref{\task{fit1d}}{sun258}{FIT1D}}
+\providecommand{\gsdtoacsis}{\xref{\task{gsd2acsis}}{sun258}{GSD2ACSIS}}
+\providecommand{\gsdshow}{\xref{\task{gsdshow}}{sun258}{GSDSHOW}}
+\providecommand{\smurfhelp}{\xref{\task{smurfhelp}}{sun258}{SMURFHELP}}
+\providecommand{\impaztec}{\xref{\task{impaztec}}{sun258}{IMPAZTEC}}
+\providecommand{\makecube}{\xref{\task{makecube}}{sun258}{MAKECUBE}}
+\providecommand{\rawunpress}{\xref{\task{rawunpress}}{sun258}{RAWUNPRESS}}
+\providecommand{\rawfixmeta}{\xref{\task{rawfixmeta}}{sun258}{RAWFIXMETA}}
+\providecommand{\sctwosim}{\xref{\task{sc2sim}}{sun258}{SC2SIM}}
+\providecommand{\sctwothreadtest}{\xref{\task{sc2threadtest}}{sun258}{SC2THREADTEST}}
+\providecommand{\scanfit}{\xref{\task{scanfit}}{sun258}{SCANFIT}}
+\providecommand{\skynoise}{\xref{\task{skynoise}}{sun258}{SKYNOISE}}
+\providecommand{\smurfcopy}{\xref{\task{smurfcopy}}{sun258}{SMURFCOPY}}
+\providecommand{\stackframes}{\xref{\task{stackframes}}{sun258}{STACKFRAMES}}
+\providecommand{\starecalc}{\xref{\task{starecalc}}{sun258}{STARECALC}}
+\providecommand{\timesort}{\xref{\task{timesort}}{sun258}{TIMESORT}}
+\providecommand{\unmakecube}{\xref{\task{unmakecube}}{sun258}{UNMAKECUBE}}
+
+\providecommand{\extinction}{\xref{\task{extinction}}{sun258}{EXTINCTION}}
+\providecommand{\flatfield}{\xref{\task{flatfield}}{sun258}{FLATFIELD}}
+\providecommand{\jcmtstate}{\xref{\task{jcmtstate2cat}}{sun258}{JCMTSTATE2CAT}}
+\providecommand{\dumpocscfg}{\xref{\task{dumpocscfg}}{sun258}{DUMPOCSCFG}}
+\providecommand{\makemap}{\xref{\task{makemap}}{sun258}{MAKEMAP}}
+\providecommand{\gettsys}{\xref{\task{gettsys}}{sun258}{GETTSYS}}
+
+\providecommand{\remsky}{\xref{\task{remsky}}{sun258}{REMSKY}}
+\providecommand{\clean}{\xref{\task{sc2clean}}{sun258}{SC2CLEAN}}
+\providecommand{\concat}{\xref{\task{sc2concat}}{sun258}{SC2CONCAT}}
+\providecommand{\fft}{\xref{\task{sc2fft}}{sun258}{SC2FFT}}
+\providecommand{\fts}{\xref{\task{sc2fts}}{sun258}{SC2FTS}}
+
+\providecommand{\rebin}{\texttt{rebin}}
+\providecommand{\iterate}{\texttt{iterate}}
+
+% Other tasks
+\providecommand{\makemos}{\xref{\task{makemos}}{sun139}{MAKEMOS}}
+\providecommand{\csub}{\xref{\task{csub}}{sun95}{CSUB}}
+\providecommand{\clinplot}{\xref{\task{clinplot}}{sun95}{CLINPLOT}}
+\providecommand{\mlinplot}{\xref{\task{mlinplot}}{sun95}{MLINPLOT}}
+\providecommand{\collapse}{\xref{\task{collapse}}{sun95}{COLLAPSE}}
+\providecommand{\fillbad}{\xref{\task{fillbad}}{sun95}{FILLBAD}}
+\providecommand{\fitsedit}{\xref{\task{fitsedit}}{sun95}{FITSEDIT}}
+\providecommand{\kapdiv}{\xref{\task{div}}{sun95}{DIV}}
+\providecommand{\ndfcopy}{\xref{\task{ndfcopy}}{sun95}{NDFCOPY}}
+\providecommand{\parget}{\xref{\task{parget}}{sun95}{PARGET}}
+\providecommand{\provshow}{\xref{\task{provshow}}{sun95}{PROVSHOW}}
+\providecommand{\thresh}{\xref{\task{thresh}}{sun95}{THRESH}}
+\providecommand{\wcsmosaic}{\xref{\task{wcsmosaic}}{sun95}{WCSMOSAIC}}
+\providecommand{\wcsalign}{\xref{\task{wcsalign}}{sun95}{WCSALIGN}}
+\providecommand{\wcsattrib}{\xref{\task{wcsattrib}}{sun95}{WCSATTRIB}}
+\providecommand{\fitslist}{\xref{\task{fitslist}}{sun95}{FITSLIST}}
+\providecommand{\display}{\xref{\task{display}}{sun95}{DISPLAY}}
+\providecommand{\ndfcompress}{\xref{\task{ndfcompress}}{sun95}{NDFCOMPRESS}}
+\providecommand{\topcat}{\xref{\textsc{Topcat}}{sun253}{}}
+
+% prevent issues if a pdf .aux file is still around
+\providecommand{\pgfsyspdfmark}[3]{}
+
diff --git a/ast/starlink.cls b/ast/starlink.cls
new file mode 100644
index 0000000..5907136
--- /dev/null
+++ b/ast/starlink.cls
@@ -0,0 +1,510 @@
+% Latex2E class for writing starlink documents.
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesClass{starlink}
+%-----------------------------------------------------%
+% .. Class options.
+
+% With chapters...
+\newif\ifwithchapters
+\withchaptersfalse
+
+% If twoside...
+\newif\iftwoside
+\twosidefalse
+
+% If list of figures (lof)
+\newif\ifwithlof
+\withloftrue
+
+% If no abstract
+\newif\ifwithabs
+\withabstrue
+
+% If all one page (affects html output only)
+\newif\ifmultipage
+\multipagetrue
+
+% Declare the options.
+\DeclareOption{chapters}{\withchapterstrue}
+\DeclareOption{twoside}{\twosidetrue}
+\DeclareOption{nolof}{\withloffalse}
+\DeclareOption{noabs}{\withabsfalse}
+\DeclareOption{onepage}{\multipagefalse}
+
+% Pass all options not defined above to the classes.
+% (Must be done before process options)
+\ifwithchapters
+   \typeout{..... passing options to report .....}
+   \DeclareOption*{\PassOptionsToClass{\CurrentOption}{report}}
+\else
+   \typeout{........passing options to article......}
+   \DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}
+\fi
+
+% Process custom options.
+\ProcessOptions\relax
+
+
+\ifwithchapters
+   \LoadClass{report}
+   \typeout{........Report!...........}
+\else
+   \LoadClass{article}
+   \typeout{..........Article!..........}
+\fi
+
+
+%--------------------------------------
+% Packages required for all reports
+% chek if in a pdf or not
+\RequirePackage{ifpdf}
+
+
+% Font types and encoding.
+\RequirePackage[T1]{fontenc}
+\RequirePackage[utf8]{inputenc}
+
+% microtype For improved pdf typography (must come after loading class)
+\RequirePackage{microtype}
+
+%maths
+\RequirePackage{amsmath}
+\RequirePackage{mathpazo}
+
+% units
+\RequirePackage{siunitx}
+
+% Titlesec.
+\RequirePackage{titlesec}
+
+% Package to allow graphics to be loaded (\includegraphics) and the
+% default extensions it will look for (and their order).
+\RequirePackage{graphicx}
+\DeclareGraphicsExtensions{.pdf,.png,.jpg,.jpeg}
+
+%.. Probably needed for something?
+\RequirePackage{multirow}
+
+% formatting of list enivornments
+\RequirePackage{enumitem}
+
+%.. Using color
+\RequirePackage[usenames,dvipsnames,svgnames,table]{xcolor}
+
+%.. Allow boxes with frames and backgrounds, over multiple pages
+\RequirePackage[framemethod=TikZ]{mdframed}
+
+%.. Allow tables on multiple pages
+\RequirePackage{longtable}
+
+%.. Allow sideways tables
+\RequirePackage{rotating}
+
+%.. Allow landscape pdf pages
+\RequirePackage{pdflscape}
+
+%.. Set up the page
+\RequirePackage[text={160mm,230mm},centering]{geometry}
+
+%.. title page formatting
+\RequirePackage{titling}
+
+%... Set up the headers.
+\RequirePackage{fancyhdr}
+
+%.... table of contents formatting
+\ifpdf
+\RequirePackage{tocloft}
+\fi
+
+%.. hyperref
+\RequirePackage[pdfusetitle=true,backref,
+                breaklinks=True,pdfdisplaydoctitle=true]{hyperref}
+
+%... allow environments using verbatim
+\RequirePackage{fancyvrb}
+
+%... allow starlink docs to use indexes
+\RequirePackage{makeidx}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% Starlink document identification commands
+\newcommand{\stardoccategory}[1]{\def \@stardoccategory {#1}}
+\newcommand{\stardocinitials}[1]{\def \@stardocinitials {#1}}
+\newcommand{\stardoccopyright}[1]{\def \@stardoccopyright {#1}}
+\newcommand{\stardocnumber}[1]{\def \@stardocnumber{#1}}
+
+% Define a stardoctitle that takes an optional 'short' argument; this
+% can then be used in various places. E.g. if you have a multi line
+% full title, please ensure you also have a single line short title.
+\def\stardoctitle{\@ifnextchar [{\@stardoctitletwo}{\@stardoctitleone}}
+\def\@stardoctitletwo[#1]#2{\gdef\@stardoctitle{#2}\gdef\@shorttitle{#1}}
+\def\@stardoctitleone#1{\gdef\@stardoctitle{#1}\gdef\@shorttitle{#1}}
+
+\newcommand{\stardocversion}[1]{\def \@stardocversion{#1}}
+\newcommand{\stardocmanual}[1]{\def \@stardocmanual{#1}}
+\newcommand{\stardocabstract}[1]{\def \@stardocabstract{#1}}
+\newcommand{\stardocauthors}[1]{\def\@stardocauthors{#1}}
+\newcommand{\stardocdate}[1]{\def\@stardocdate{#1}}
+\newcommand{\startitlepic}[1]{\def\@startitlepic{#1}}
+\newcommand{\starfunders}[1]{\def\@starfunders{#1}}
+\newcommand{\starproject}[1]{\def\@starproject{#1}}
+\newcommand{\stardocsource}[1]{\def\@stardocsource{#1}}
+\newcommand{\stardocname}[1]{\def\@stardocname{#1}}
+
+% Defaults for current data?
+\starfunders{}
+\starproject{Starlink Project}
+
+% initalise to nothing
+\stardoccategory{}
+\stardocinitials{}
+\stardoccopyright{}
+\stardocnumber{}
+\stardoctitle{}
+\stardocversion{}
+\stardocmanual{}
+\stardocabstract{}
+\stardocauthors{}
+\startitlepic{}
+\stardocname{}
+\stardocauthors{}
+\stardocdate{}
+
+
+% Provide \the... versions of these commands so you don't need to use @
+% in latex.
+\newcommand{\thestardoccategory}{\@stardoccategory}
+\newcommand{\thestardocinitials}{\@stardocinitials}
+\newcommand{\thestardoccopyright}{\@stardoccopyright}
+\newcommand{\thestardocnumber}{\@stardocnumber}
+\newcommand{\thestardoctitle}{\@stardoctitle}
+\newcommand{\theshorttitle}{\@shorttitle}
+\newcommand{\thestardocversion}{\@stardocversion}
+\newcommand{\thestardocmanual}{\@stardocmanual}
+\newcommand{\thestardocabstract}{\@stardocabstract}
+\newcommand{\thestardocauthors}{\@stardocauthors}
+\newcommand{\thestarfunders}{\@starfunders}
+\newcommand{\thestarproject}{\@starproject}
+\newcommand{\thestartitlepic}{\@startitlepic}
+\newcommand{\thestardocsource}{\@stardocsource}
+\newcommand{\thestardocdate}{\@stardocdate}
+
+%  Ensure the stardoctitle etc is available as \thetitle.
+\newcommand{\thetitle}{\@stardoctitle}
+\title{\@stardoctitle}
+
+% Ensure the author list is available as \theauthor.
+\newcommand{\theauthor}{\@stardocauthors}
+\author{\@stardocauthors}
+
+
+%%--------------------------------------------------------
+
+%% Various commands to setup the frontmatter of starlink docs.
+
+%% This should consist of 1) the title, 2) the abstract, 3) the table
+%% of contents, 4) the list of figures (unless class option nolof is given).
+
+%.. Format the initial header.
+\newcommand{\titleheader}{%
+  \begin{flushright}
+  \textbf{\thestardocinitials /\thestardocnumber}
+  \end{flushright}
+  \thestarproject\\
+  \thestardoccategory\ \thestardocnumber\\
+}
+
+%... Format the main ttile
+\newcommand{\titlemain}{%
+\begin{center}
+{\Huge\textbf{\thestardoctitle}}
+
+{\Huge\textbf{\thestardocversion}}
+
+{\Huge\textbf{\thestardocmanual}}
+\end{center}
+}
+
+
+% Graphics for front page
+\newcommand{\thestargraphics}{%
+\centering
+\thestartitlepic
+\vspace{7.5mm}
+\rule{\textwidth}{0.5mm}%
+}
+
+% .. Provide a command \startitle page that will produce a consistent
+% starlink title page
+\newcommand{\startitlepage}{%
+  \null
+  \vskip 2em%
+\vspace*{-1\baselineskip}
+\thispagestyle{empty}
+\titleheader
+%\vspace{-7mm}
+\begin{flushright}
+\par\thestardocauthors
+\par\thestardocdate
+\par\thestardoccopyright
+\end{flushright}
+%\vspace{-2mm}
+\rule{\textwidth}{0.5mm}
+\vspace{7.5mm}
+\titlemain%\par
+%\vspace{5mm}
+\thestargraphics\\
+}
+
+
+%%..  command to print the abstract (with copyright at bottom of page)
+\newcommand{\Abstract}{%
+  \ifwithabs
+    \ifwithchapters
+        \chapter*{}
+    \fi
+    \section*{Abstract}
+    \thispagestyle{fancy}
+    \markboth{Abstract}{}
+    \thestardocabstract{}
+    \\
+    \vspace*{\fill}
+    \\
+    {\small\thestardoccopyright{}}
+    \clearpage
+  \fi
+}
+
+%% General Front matter command -- title page, abstract, toc, lof This
+%% command \scfrontmatter is what should be called
+%% after \begin{document} in any starlink tex file.
+
+\newcommand*{\scfrontmatter}{
+% Use roman page numbers
+\renewcommand{\thepage}{\roman{page}}
+\setcounter{page}{1}
+
+% Create the titlepage
+\begin{titlepage}%
+\startitlepage
+\end{titlepage}
+
+% Show the abstract (defined to do nothing if noabs is set)
+\Abstract{}
+
+\clearpage
+
+% Table of contents (catcode stuff to prevent errors with _)
+\begingroup \catcode`\_=12 \tableofcontents \endgroup
+\clearpage
+
+% unless the class option 'nolof' has been given, create a list of
+% figures.
+\ifwithlof
+  \begingroup \catcode`\_=12 \listoffigures \endgroup
+\fi
+\clearpage
+
+% Reset the page counting to arabic and start from 1.
+\renewcommand{\thepage}{\arabic{page}}
+\setcounter{page}{1}
+}
+%%%-------------------------------------
+%% Back matter commands (references and index)
+% Ensure index shows up in toc.
+\let\oldprintindex\printindex
+\renewcommand{\printindex}{%
+  \phantomsection \addcontentsline{toc}{section}{Index}
+  \oldprintindex}
+
+%%------------------------------------
+
+%% Various class specific macros
+
+%% Starlink list enivornments
+
+%% enumdesc: An enumerated description list
+\newcounter{enumdescc}
+\newcounter{enumdescci}
+\newlist{enumdesc}{description}{2}
+\setlist[enumdesc,1]{%
+  before={\stepcounter{enumdescc}\setcounter{enumdescci}{0}},%
+  style=nextline,leftmargin=0.5cm,labelindent=0.5cm,rightmargin=0.5cm,
+  topsep=0.5\baselineskip, font={%
+    \phantomsection\normalfont\normalsize\bfseries\refstepcounter{enumdescci}\theenumdescci~}
+}
+
+
+
+%% A description list which has the labels in a box on the left with
+%% the length of the widest label, and the definitions aligned past
+%% it. In HTML output, starstyle.4ht will format this as a table.
+
+%% This uses the package eqparbox to get the box of width of the
+%% widest label (takes 2 runs of pdflatex).
+\usepackage{eqparbox}
+
+\newcounter{desc}
+\newcommand{\descriptionmakelabel}[1]{\eqparbox{descnb\romannumeral\value{desc}}{#1\hfill}}
+
+\newlist{aligndesc}{description}{2}
+\setlist[aligndesc]{before={\refstepcounter{desc}\renewcommand{\makelabel}{\descriptionmakelabel}},
+  leftmargin=\dimexpr\eqboxwidth{descnb\romannumeral\numexpr\value{desc}+1\relax}+3em\relax,
+  labelsep=1em, labelindent=2em, rightmargin=2em}
+
+%%%--------------------------------------------------------------
+%% Linking and referencing commands.
+
+%%..  Starlink xref command
+%% By default use the starlink.ac.uk; this will be fixed up at the
+%% end of make world by a different program.
+\newcommand{\xref}[3]{%
+\href{http://www.starlink.ac.uk/cgi-bin/htxserver/#2.htx/#2.html?xref_#3}{#1}}
+\newcommand{\xlabel}[1]{\label{\protect{xref_#1}}}
+\newcommand{\cref}[3]{#1~\ref{#2}}
+%problems with _ in labels (e.g. in xrefs)
+\let\oldunderscore\_
+\renewcommand{\_}{\ifmmode \oldunderscore \else \string_\fi}
+
+
+%%---------------------------------------------------------------
+%% Deprecated commands (for compatability only)
+
+% % Graphics commands
+\newcommand{\starfig}[6]{
+  \begin{figure}#2
+    \centering\includegraphics[#3]{#1}
+    \typeout{#1 inserted on page \arabic{page}}
+    \caption[#5]{\label{#4} #6}
+  \end{figure}
+}
+
+% A starlink Hyperref (defined a bit differently to regular hyperref,
+% and with a first argument that doesn't do anything. Deprecated; only
+% provided for consistency with old documents.  Include the string, not
+% just the cross reference number or letter in the hyperlink.
+\newcommand{\slhyperref}[4]{\hyperref[#4]{#2\ref*{#4}#3}}
+
+\newcommand{\latexhtml}[2]{#1}
+
+% %.. Empty environment latex only.
+\newenvironment{latexonly}{}{}
+
+%%%% Command that doesn't do anything in latex
+\newcommand{\html}[1]{}
+
+%.. environments that don't do anything
+\def\makeinnocent#1{\catcode`#1=12 }
+ \def\csarg#1#2{\expandafter#1\csname#2\endcsname}
+
+ \def\ThrowAwayComment#1{\begingroup
+     \def\CurrentComment{#1}%
+     \let\do\makeinnocent \dospecials
+     \makeinnocent\^^L% and whatever other special cases
+     \endlinechar`\^^M \catcode`\^^M=12 \xComment}
+ {\catcode`\^^M=12 \endlinechar=-1 %
+  \gdef\xComment#1^^M{\def\test{#1}
+       \csarg\ifx{PlainEnd\CurrentComment Test}\test
+           \let\html@next\endgroup
+       \else \csarg\ifx{LaLaEnd\CurrentComment Test}\test
+             \edef\html@next{\endgroup\noexpand\end{\CurrentComment}}
+       \else \let\html@next\xComment
+       \fi \fi \html@next}
+ }
+ \def\excludecomment
+   #1{\expandafter\def\csname#1\endcsname{\ThrowAwayComment{#1}}%
+      {\escapechar=-1\relax
+       \csarg\xdef{PlainEnd#1Test}{\string\\end#1}%
+       \csarg\xdef{LaLaEnd#1Test}{\string\\end\string\{#1\string\}}%
+      }}
+
+\excludecomment{htmlonly}
+
+\newcommand{\latex}[1]{#1}
+
+%------------------------------------------------------------------------
+%..  Define additional colours.
+\definecolor{mygray}{gray}{0.7}
+\definecolor{MidnightBlue}{RGB}{25, 25, 112}
+\definecolor{bblue}{RGB}{172,207,230}
+
+
+%--------------------------------------------------
+%.. Miscellanous commands
+%.. Create a command to remove all space from input
+\def\RemoveSpaces#1{\zap@space#1 \@empty}
+
+% % Command for text that should be pushed to the right of the line (eg
+% % following an hfill, on a single line of text
+\newcommand*{\scpushright}[1]{\hfill #1}
+
+
+
+% %.. verbatim environment for quoting terminal.
+\DefineVerbatimEnvironment{terminalv}{Verbatim}{%
+xleftmargin=.5in,formatcom=\color{MidnightBlue},fontsize=\small}
+
+
+
+% command for a text box that floats around and pops out from the text (framed)
+\mdfsetup{%
+ backgroundcolor=white,%
+    middlelinewidth=4pt,%
+    middlelinecolor=bblue,%
+    userdefinedwidth=0.8\textwidth,%
+    roundcorner=10pt, %
+    innertopmargin=\topskip}%
+
+\newenvironment{sltextbox}[1]{%
+\begin{figure*}[h]\begin{mdframed}[userdefinedwidth=0.8\textwidth,
+                            align=center,%
+                       frametitle=#1,%
+        frametitlebackgroundcolor=bblue]%
+\setlength{\parskip}{\medskipamount}%
+}%
+{\end{mdframed}\end{figure*}}
+
+
+%Framed  boxes (obsolete).
+\newsavebox{\fmbox}
+\newenvironment{fmpage}[1]{\begin{lrbox}{\fmbox}\begin{minipage}{#1}}{\end{minipage}\end{lrbox}\fbox{\usebox{\fmbox}}}
+
+
+
+%.. Tip box
+\newenvironment{tip}%
+{\begin{figure*}[h]\begin{mdframed}[userdefinedwidth=0.8\textwidth,align=center,frametitle=Tip,frametitlebackgroundcolor=bblue]%
+\setlength{\parskip}{\medskipamount}%
+}%
+{\end{mdframed}\end{figure*}}
+
+%.. starlink long table (used so that its easier to fix it up for html output)
+\setlength{\LTcapwidth}{\textwidth}
+\newenvironment{sllongtable}[2]{%
+\begin{longtable}{#1}\caption{#2}\\}
+{\end{longtable}}
+
+
+%.........................................
+
+\newcommand{\Acronyms}{%
+  \ifwithchapters
+  \chapter*{}
+  \fi
+  \section*{Acronyms}
+  \markboth{Acronyms}{}
+  \addcontentsline{toc}{section}{\protect\numberline{}Acronyms}
+}
+
+
+%--------------------------------------------------------------------
+%% Load the reamining starlink specific classes.
+
+%.. The remaining starlink specific definitions.
+\RequirePackage{starabbrev}
+\RequirePackage{starstyle}
+\RequirePackage{sst}
+
+%--------------------------------------------------------------------
diff --git a/ast/starstyle.sty b/ast/starstyle.sty
new file mode 100644
index 0000000..c2cea2e
--- /dev/null
+++ b/ast/starstyle.sty
@@ -0,0 +1,162 @@
+\ProvidesPackage{starstyle}
+%%%%
+%% Package for styling the output (primarily in pdf)
+%%%%
+
+% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Number sections down to subsubsection level
+\setcounter{secnumdepth}{4}
+
+% %%% Formatting of the section headging
+\newcommand{\colorsection}[1]{%
+\colorbox{bblue}{\strut\parbox{\dimexpr\textwidth-2\fboxsep}{\thesection\ \quad #1}{\huge\strut}}}
+
+\newcommand{\colorsectionnumberless}[1]{%
+\colorbox{bblue}{\strut\parbox{\dimexpr\textwidth-2\fboxsep}{#1}\strut}}
+
+
+% % Section headings (including numberless)
+
+\ifpdf
+\titleformat{name=\section}%
+{\normalfont\Large\bfseries}{}{0pt}{\colorsection}
+\titleformat{name=\section,numberless}%
+{\normalfont\Large\bfseries}{}{0pt}{\colorsectionnumberless}
+\fi
+
+%... Chapter headings
+\ifwithchapters
+  \titleformat{name=\chapter}[display]%
+  {\normalfont\huge\bfseries\thispagestyle{plain}}{%
+    \chaptertitlename\ \thechapter}{0pt}{}
+
+  \titleformat{name=\chapter,numberless}[display]%
+  {\normalfont\huge\bfseries\thispagestyle{plain}}{}{0pt}{\Huge}
+
+  %... spacing after chapter headings
+  \titlespacing*{\chapter}{0pt}{0pt}{20pt}
+
+\fi
+
+
+%.. section spacing
+\titlespacing*{\section}{0pt}{3.5ex plus 1ex minus .2ex}{2.3ex plus .2ex}
+\titlespacing*{\subsection}{0pt}{2ex plus 1ex minus .2ex}{1.5ex plus .2ex}
+\titlespacing*{\subsubsection}{0pt}{2ex plus 1ex minus .2ex}{1ex plus .2ex}
+\titlespacing*{\paragraph}{0pt}{2ex plus 1ex minus .2ex}{1em}
+\titlespacing*{\subparagraph}{\parindent}{2ex plus 1ex minus
+  .2ex}{1em}
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%% Page styles (e.g. headers and footers)
+
+\pagestyle{fancy}
+\fancyhf{}
+\rhead{\slshape \thestardocinitials{}/\thestardocnumber{} \leftmark}
+\lhead{\thepage}
+\renewcommand{\headrulewidth}{0.0pt}
+
+
+\ifwithchapters
+\renewcommand{\chaptermark}[1]{
+  \markboth{#1}{}}
+\else
+\renewcommand{\sectionmark}[1]{
+  \markboth{#1}{}}
+\fi
+
+% .. Reset the plain pagestyle (used on chapter pages) to only have
+% the page number and no rules. 
+\fancypagestyle{plain}{%
+\fancyhf{}%
+
+\iftwoside
+\fancyhead[RE,LO]{\thepage}%
+\fancyhead[LE,RO]{\slshape
+  \thestardocinitials{}/\thestardocnumber{}\nouppercase{---\leftmark}}%
+\else
+\fancyhead[LO]{\thepage}
+\fancyhead[RO]{\slshape
+\thestardocinitials{}/\thestardocnumber{}\nouppercase{---\leftmark}}%
+\fi
+
+\renewcommand{\headrulewidth}{0pt}%
+\renewcommand{\footrulewidth}{0pt}%
+}
+
+% ensure correct chaptermakr is used when in pagestyle plain
+\pagestyle{plain}
+\fancyhf{}
+
+\iftwoside
+\fancyhead[LE,RO]{\slshape \thestardocinitials{}/\thestardocnumber{} \nouppercase{---\leftmark}}
+\fancyhead[RE,LO]{\thepage}
+\else
+\lhead{\slshape \thestardocinitials{}/\thestardocnumber{} \nouppercase{---\leftmark}}
+\rhead{\thepage}
+\fi
+
+\renewcommand{\headrulewidth}{0.0pt}
+
+\ifwithchapters
+\renewcommand{\chaptermark}[1]{
+  \markboth{#1}{}}
+\else
+\renewcommand{\sectionmark}[1]{
+  \markboth{#1}{}}
+\fi
+
+
+\renewcommand{\headrulewidth}{0pt}%
+\renewcommand{\footrulewidth}{0pt}%
+
+\ifwithchapters
+\renewcommand{\chaptermark}[1]{\markboth{{#1}}{}}%
+\else
+\renewcommand{\sectionmark}[1]{\markboth{{#1}}{}}%
+\fi
+
+%% Select the fancy pagestyle as default
+\pagestyle{fancy}
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% General spacing
+\setlength{\headheight}{1.5cm}
+\setlength{\parskip}{\medskipamount}
+\setlength{\parindent}{0pt}
+\renewcommand{\arraystretch}{1.5}
+
+
+%%%% Set up defaults for floats
+% maximum size of 'top area'
+\renewcommand{\topfraction}{0.9}
+% maximum size of 'bottom area'
+\renewcommand{\bottomfraction}{0.9}
+% minimum amount of text on a non-float page
+\renewcommand{\textfraction}{0.1}
+
+
+
+%Enumerate list appearance
+\setlist[enumerate,1]{label=(\arabic*)}
+
+
+% macros for typesetting parameters
+\providecommand{\aparam}[1]{\texttt{#1}}     % ADAM parameter
+\providecommand{\cparam}[1]{\texttt{#1}}     % CONFIG parameter
+\providecommand{\ndfcomp}[1]{\texttt{#1}}    % NDF component
+
+
+
+\newcommand{\menuitem}[2]{\item[\protect{\hyperref[#1]{#1}}] #2}
+\newcommand{\classitem}[1]{\item [\protect{\hyperref[#1]{#1}}]}
+
+
+
diff --git a/ast/stc.c b/ast/stc.c
new file mode 100644
index 0000000..08e4392
--- /dev/null
+++ b/ast/stc.c
@@ -0,0 +1,3703 @@
+/*
+*class++
+*  Name:
+*     Stc
+
+*  Purpose:
+*     Represents an instance of the IVOA STC class.
+
+*  Constructor Function:
+c     astStc
+f     AST_STC
+
+*  Description:
+*     The Stc class is an implementation of the IVOA STC class which forms
+*     part of the IVOA Space-Time Coordinate Metadata system. See:
+*
+*     http://hea-www.harvard.edu/~arots/nvometa/STC.html
+*
+*     The Stc class does not have a constructor function of its own, as it
+*     is simply a container class for a family of specialised sub-classes
+*     including StcCatalogEntryLocation, StcResourceProfile, StcSearchLocation
+*     and StcObsDataLocation.
+
+*  Inheritance:
+*     The Stc class inherits from the Region class.
+
+*  Attributes:
+*     In addition to those attributes common to all Regions, every
+*     Stc also has the following attributes:
+*
+*     - RegionClass: The class name of the encapsulated Region.
+
+*  Functions:
+c     In addition to those functions applicable to all Regions, the
+c     following functions may also be applied to all Stc's:
+f     In addition to those routines applicable to all Regions, the
+f     following routines may also be applied to all Stc's:
+*
+c     - astGetStcRegion: Get a pointer to the encapsulated Region
+f     - AST_GETSTCREGION: Get a pointer to the encapsulated Region
+c     - astGetStcCoord: Get information about an AstroCoords element
+f     - AST_GETSTCCOORD: Get information about an AstroCoords element
+c     - astGetStcNCoord: Returns the number of AstroCoords elements in an Stc
+f     - AST_GETSTCNCOORD: Returns the number of AstroCoords elements in an Stc
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2008 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     23-NOV-2004 (DSB):
+*        Original version.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     13-MAR-2009 (DSB):
+*        Over-ride astRegBasePick.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Stc
+
+/* The number of components in an AstroCoords element which are described
+   using a Region within a KeyMap. */
+#define NREG 5
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "unitmap.h"             /* Unit Mappings */
+#include "region.h"              /* Regions (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "stc.h"                 /* Interface definition for this class */
+#include "keymap.h"              /* Lists of value/key pairs */
+#include "pointlist.h"           /* Individual points in a Frame */
+#include "ellipse.h"             /* Ellipses within a Frame */
+#include "interval.h"            /* Axis intervals within a Frame */
+#include "prism.h"               /* Extrusions into higher dimensions */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstMapping *(* parent_simplify)( AstMapping *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstRegion *(* parent_getdefunc)( AstRegion *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_equal)( AstObject *, AstObject *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+static int (* parent_getusedefs)( AstObject *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static void (* parent_setregfs)( AstRegion *, AstFrame *, int * );
+static void (*parent_regclearattrib)( AstRegion *, const char *, char **, int * );
+static void (*parent_regsetattrib)( AstRegion *, const char *, char **, int * );
+
+static void (* parent_clearnegated)( AstRegion *, int * );
+static void (* parent_clearclosed)( AstRegion *, int * );
+static void (* parent_clearfillfactor)( AstRegion *, int * );
+static void (* parent_clearmeshsize)( AstRegion *, int * );
+
+static void (* parent_setclosed)( AstRegion *, int, int * );
+static void (* parent_setfillfactor)( AstRegion *, double, int * );
+static void (* parent_setmeshsize)( AstRegion *, int, int * );
+static void (* parent_setnegated)( AstRegion *, int, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* The keys associated with each component of an AstroCoords element
+   within KeyMap */
+static const char *regkey[ NREG ] = { AST__STCERROR,
+                                      AST__STCRES,
+                                      AST__STCSIZE,
+                                      AST__STCPIXSZ,
+                                      AST__STCVALUE };
+
+/* The comments associated with each component of an AstroCoords element
+   within KeyMap */
+static const char *regcom[ NREG ] = { "AstroCoords error region",
+                                      "AstroCoords resolution region",
+                                      "AstroCoords size region",
+                                      "AstroCoords pixel size region",
+                                      "AstroCoords value region" };
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Stc)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Stc,Class_Init)
+#define class_vtab astGLOBAL(Stc,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstStcVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstKeyMap *GetStcCoord( AstStc *, int, int * );
+static AstKeyMap *MakeAstroCoordsKeyMap( AstRegion *, AstKeyMap *, const char *, int * );
+static AstMapping *Simplify( AstMapping *, int * );
+static AstPointSet *RegBaseMesh( AstRegion *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstRegion *GetDefUnc( AstRegion *, int * );
+static AstRegion *GetStcRegion( AstStc *, int * );
+static AstRegion *RegBasePick( AstRegion *this, int, const int *, int * );
+static const char *GetRegionClass( AstStc *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetBounded( AstRegion *, int * );
+static int GetObjSize( AstObject *, int * );
+static int GetStcNCoord( AstStc *, int * );
+static int GetUseDefs( AstObject *, int * );
+static int Overlap( AstRegion *, AstRegion *, int * );
+static int OverlapX( AstRegion *, AstRegion *, int * );
+static int RegPins( AstRegion *, AstPointSet *, AstRegion *, int **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void GetRegion( AstStc *, AstRegion **, int *, int * );
+static void RegBaseBox( AstRegion *, double *, double *, int * );
+static void RegClearAttrib( AstRegion *, const char *, char **, int * );
+static void RegSetAttrib( AstRegion *, const char *, char **, int * );
+static void SetRegFS( AstRegion *, AstFrame *, int * );
+
+static void ClearAttrib( AstObject *, const char *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+
+static void ClearClosed( AstRegion *, int * );
+static int GetClosed( AstRegion *, int * );
+static void SetClosed( AstRegion *, int, int * );
+static int TestClosed( AstRegion *, int * );
+
+static void ClearMeshSize( AstRegion *, int * );
+static int GetMeshSize( AstRegion *, int * );
+static void SetMeshSize( AstRegion *, int, int * );
+static int TestMeshSize( AstRegion *, int * );
+
+static void ClearFillFactor( AstRegion *, int * );
+static double GetFillFactor( AstRegion *, int * );
+static void SetFillFactor( AstRegion *, double, int * );
+static int TestFillFactor( AstRegion *, int * );
+
+static void ClearNegated( AstRegion *, int * );
+static int GetNegated( AstRegion *, int * );
+static void SetNegated( AstRegion *, int, int * );
+static int TestNegated( AstRegion *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Stc.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astClearAttrib protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     Stc, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstStc *this;                 /* Pointer to the Stc structure */
+   int len;                      /* Length of attrib string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Stc structure. */
+   this = (AstStc *) this_object;
+
+/* Obtain the length of the "attrib" string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* (none as yet) */
+/* ------------- */
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Test if the attribute name matches any of the read-only attributes
+   of this class. If it does, then report an error. */
+   if ( !strcmp( attrib, "regionclass" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two Objects are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     int Equal( AstObject *this_object, AstObject *that_object, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astEqual protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two Stcs are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Stc.
+*     that
+*        Pointer to the second Stc.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the Stcs are equivalent, zero otherwise.
+
+*  Notes:
+*     - The Stcs are equivalent if their encapsulated Region are
+*     equivalent, and if they have the same boolean operation, negation
+*     and closed flags.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstStc *that;
+   AstStc *this;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the Equal method inherited from the parent Region class. This checks
+   that the Objects are both of the same class, and have the same Negated
+   and Closed flags (amongst other things). */
+   if( (*parent_equal)( this_object, that_object, status ) ) {
+
+/* Obtain pointers to the two Stc structures. */
+      this = (AstStc *) this_object;
+      that = (AstStc *) that_object;
+
+/* Test their encapsulated Region for equality. */
+      result = astEqual( this->region, that->region );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+/*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Define a function to set an attribute value for a Stc.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "stc.h"
+*     MAKE_SET(attribute,lattribute,type)
+
+*  Class Membership:
+*     Defined by the Stc class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Set<Attribute>( AstRegion *this, <Type> value )
+*
+*     that sets the value of a specified Region attribute in the parent
+*     Region structure and also in the encapsulated Region.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     lattribute
+*        Name of the attribute, all in lower case.
+*     type
+*        The C type of the attribute.
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attribute,lattribute,type) \
+static void Set##attribute( AstRegion *this_region, type value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstStc *this;         /* Pointer to the Stc structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Use the parent method to set the value in the parent Region structure. */ \
+   (*parent_set##lattribute)( this_region, value, status ); \
+\
+/* Also set the value in the encapsulated Region. */ \
+   this = (AstStc *) this_region; \
+   astSet##attribute( this->region, value ); \
+}
+
+/* Use the above macro to create accessors for the MeshSize, Closed and
+   FillFactor attributes. */
+MAKE_SET(FillFactor,fillfactor,double)
+MAKE_SET(MeshSize,meshsize,int)
+MAKE_SET(Closed,closed,int)
+MAKE_SET(Negated,negated,int)
+
+/* Undefine the macro. */
+#undef MAKE_SET
+
+/*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Define a function to clear an attribute value for a Stc.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "stc.h"
+*     MAKE_CLEAR(attribute,lattribute)
+
+*  Class Membership:
+*     Defined by the Stc class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static void Clear<Attribute>( AstRegion *this )
+*
+*     that sets the value of a specified Region attribute in the parent
+*     Region structure and also in the encapsulated Region.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     lattribute
+*        Name of the attribute, all in lower case.
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attribute,lattribute) \
+static void Clear##attribute( AstRegion *this_region, int *status ) { \
+\
+/* Local Variables: */ \
+   AstStc *this;         /* Pointer to the Stc structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Use the parent method to clear the value in the parent Region structure. */ \
+   (*parent_clear##lattribute)( this_region, status ); \
+\
+/* Also clear the value in the encapsulated Region. */ \
+   this = (AstStc *) this_region; \
+   astClear##attribute( this->region ); \
+}
+
+/* Use the above macro to create accessors for the MeshSize, Closed and
+   FillFactor attributes. */
+MAKE_CLEAR(FillFactor,fillfactor)
+MAKE_CLEAR(MeshSize,meshsize)
+MAKE_CLEAR(Closed,closed)
+MAKE_CLEAR(Negated,negated)
+
+/* Undefine the macro. */
+#undef MAKE_CLEAR
+
+
+/*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Define a function to get an attribute value for a Stc.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "stc.h"
+*     MAKE_GET(attribute,type,bad)
+
+*  Class Membership:
+*     Defined by the Stc class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static <Type> Get<Attribute>( AstRegion *this )
+*
+*     that gets the value of a specified Region attribute from the encapsulated
+*     Region.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     type
+*        The C type of the attribute.
+*     bad
+*        Value to return in caseof error.
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attribute,type,bad) \
+static type Get##attribute( AstRegion *this_region, int *status ) { \
+\
+/* Local Variables: */ \
+   AstStc *this;         /* Pointer to the Stc structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (bad); \
+\
+/* Get the value from the encapsulated Region. */ \
+   this = (AstStc *) this_region; \
+   return astGet##attribute( this->region ); \
+}
+
+/* Use the above macro to create accessors for the MeshSize, Closed and
+   FillFactor attributes. */
+MAKE_GET(FillFactor,double,AST__BAD)
+MAKE_GET(MeshSize,int,100)
+MAKE_GET(Closed,int,1)
+MAKE_GET(Negated,int,0)
+
+/* Undefine the macro. */
+#undef MAKE_GET
+
+/*
+*  Name:
+*     MAKE_TEST
+
+*  Purpose:
+*     Define a function to test an attribute value for a Stc.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "stc.h"
+*     MAKE_TEST(attribute)
+
+*  Class Membership:
+*     Defined by the Stc class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function
+*     of the form:
+*
+*        static int Test<Attribute>( AstRegion *this )
+*
+*     that test the value of a specified Region attribute from the encapsulated
+*     Region.
+
+*  Parameters:
+*     attribute
+*        Name of the attribute, as it appears in the function name.
+*     type
+*        The C type of the attribute.
+*/
+
+/* Define the macro. */
+#define MAKE_TEST(attribute) \
+static int Test##attribute( AstRegion *this_region, int *status ) { \
+\
+/* Local Variables: */ \
+   AstStc *this;         /* Pointer to the Stc structure */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Test the value from the encapsulated Region. */ \
+   this = (AstStc *) this_region; \
+   return astTest##attribute( this->region ); \
+}
+
+/* Use the above macro to create accessors for the MeshSize, Closed and
+   FillFactor attributes. */
+MAKE_TEST(FillFactor)
+MAKE_TEST(MeshSize)
+MAKE_TEST(Closed)
+MAKE_TEST(Negated)
+
+/* Undefine the macro. */
+#undef MAKE_TEST
+
+
+
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied Stc,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstStc *this;         /* Pointer to Stc structure */
+   int result;           /* Result value to return */
+   int i;                /* AstroCoords index */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the Stc structure. */
+   this = (AstStc *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astGetObjSize( this->region );
+
+   if( this->coord ) {
+      for( i = 0; i < this->ncoord; i++ ) {
+         result += astGetObjSize( this->coord[ i ] );
+      }
+      result += astTSizeOf( this->coord );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a Stc.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the protected astGetAttrib
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a Stc, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Stc, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Stc. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstStc *this;                 /* Pointer to the Stc structure */
+   const char *result;           /* Pointer value to return */
+   int len;                      /* Length of attrib string */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Stc structure. */
+   this = (AstStc *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* RegionClass. */
+/* ------------ */
+   if ( !strcmp( attrib, "regionclass" ) ) {
+      result = astGetClass( this->region );
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetBounded( AstRegion *this_region, int *status ) {
+/*
+*  Name:
+*     GetBounded
+
+*  Purpose:
+*     Is the Region bounded?
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     int GetBounded( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Stc method (over-rides the astGetBounded method inherited from
+*     the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the Region is bounded.
+*     The implementation provided by the base Region class is suitable
+*     for Region sub-classes representing the inside of a single closed
+*     curve (e.g. Circle, Ellipse, Box, etc). Other sub-classes (such as
+*     Stc, PointList, etc ) may need to provide their own implementations.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the Region is bounded. Zero otherwise.
+
+*/
+
+/* Local Variables: */
+   AstStc *this;              /* Pointer to Stc structure */
+   AstRegion *reg;            /* Pointer to the encapsulated Region */
+   int neg;                   /* Negated flag to use */
+   int neg_old;               /* Original Negated flag */
+   int result;                /* Returned result */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Stc structure. */
+   this = (AstStc *) this_region;
+
+/* Get the encapsulated Region, and the Negated value which should be used
+   with it. The returned values take account of whether the supplied Stc has
+   itself been Negated or not. The returned Region represent a region within
+   the base Frame of the FrameSet encapsulated by the parent Region
+   structure. */
+   GetRegion( this, &reg, &neg, status );
+
+/* Temporarily set the Negated attribute to the required value.*/
+   neg_old = astGetNegated( reg );
+   astSetNegated( reg, neg );
+
+/* See if the encapsulated Region is bounded. */
+   result = astGetBounded( reg );
+
+/* Re-instate the original value for the Negated attribute of the
+   encapsulated Region. */
+   if( reg ) astSetNegated( reg, neg_old );
+
+/* Free resources. */
+   reg = astAnnul( reg );
+
+/* Return zero if an error occurred. */
+   if( !astOK ) result = 0;
+
+/* Return the required pointer. */
+   return result;
+}
+
+static AstRegion *GetDefUnc( AstRegion *this_region, int *status ) {
+/*
+*  Name:
+*     GetDefUnc
+
+*  Purpose:
+*     Obtain a pointer to the default uncertainty Region for a given Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     AstRegion *GetDefUnc( AstRegion *this )
+
+*  Class Membership:
+*     Stc method (over-rides the astGetDefUnc method inherited from
+*     the Region class).
+
+*     This function returns a pointer to a Region which represents the
+*     default uncertainty associated with a position on the boundary of the
+*     given  Region. The returned Region refers to the base Frame within the
+*     FrameSet encapsulated by the supplied Region.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+
+*  Returned Value:
+*     A pointer to the Region. This should be annulled (using astAnnul)
+*     when no longer needed.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstStc *this;              /* Pointer to the Stc structure */
+   AstRegion *result;         /* Returned pointer */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Stc structure. */
+   this = (AstStc *) this_region;
+
+/* If the encapsulated region has non-default uncertainty, use it as
+   the default uncertainty for the Cmpregion. Note, the current Frame of
+   an uncertainty Region is assumed to be the same as the base Frame in the
+   Stc. */
+   if( astTestUnc( this->region ) ) {
+      result = astGetUncFrm( this->region, AST__CURRENT );
+
+/* Otherwise, use the parent method to determine the default uncertainty. */
+   } else {
+      result = (* parent_getdefunc)( this_region, status );
+   }
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the required pointer. */
+   return result;
+}
+
+static void GetRegion( AstStc *this, AstRegion **reg, int *neg, int *status ) {
+/*
+*
+*  Name:
+*     GetRegion
+
+*  Purpose:
+*     Get the encapsulated Region of a Stc.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void GetRegion( AstStc *this, AstRegion **reg, int *neg, int *status )
+
+*  Class Membership:
+*     Stc member function
+
+*  Description:
+*     This function returns a pointer to a Region which is equivalent to
+*     the supplied Stc. If the Stc has been negated, then the returned
+*     "negated" flag will be set such that it represents the negated Stc.
+*
+*     The current Frames in returned encapsulated Region will be equivalent
+*     to the base Frame in the FrameSet encapsulated by the parent Region
+*     structure.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc.
+*     reg
+*        Address of a location to receive a pointer to the encapsulated
+*        Region. The current Frame in this region will be equivalent to
+*        the base Frame in the FrameSet
+*     neg
+*        The value of the Negated attribute to be used with reg.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Any changes made to the encapsulated Region using the returned
+*     pointer will be reflected in the supplied Stc.
+
+*/
+
+/* Initialise */
+   if( reg ) *reg = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Return the component Region pointers. */
+   if( reg ) *reg = astClone( this->region );
+
+/* Initialise the other returned items. Note, the Stc initialiser stored a
+   deep copy of the supplied encapsulated Region, and so we do not
+   need to worry about attributes of the Region having been changed
+   after the creation of the Stc. This is different to the CmpMap
+   class which merely clones its supplied component pointers and so has
+   to save copies of the original Invert settings within the CmpMap
+   structure. */
+   if( neg ) *neg = astGetNegated( this->region );
+
+/* If the Stc has been inverted, we modify the boolean operator and
+   negation flags so that they reflect the inverted Stc. */
+   if( astGetNegated( this ) && neg ) *neg = *neg ? 0 : 1;
+}
+
+static const char *GetRegionClass( AstStc *this, int *status ){
+/*
+*+
+*  Name:
+*     astGetRegionClass
+
+*  Purpose:
+*     Get the value of a RegionClass attribute for a Stc.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     const char *astGetRegionClass( AstStc *this )
+
+*  Class Membership:
+*     Stc virtual function
+
+*  Description:
+*     This function returns a pointer to the value of the RegionClass
+*     attribute for a Stc.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Stc, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Stc. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and return the class of the encapsulated Region. */
+   return astGetClass( ((AstStc *) this)->region );
+}
+
+
+static AstKeyMap *GetStcCoord( AstStc *this, int icoord, int *status ){
+/*
+*++
+*  Name:
+c     astGetStcCoord
+f     AST_GETSTCCOORD
+
+*  Purpose:
+*     Return information about an AstroCoords element stored in an Stc.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "specframe.h"
+c     AstKeyMap *astGetStcCoord( AstStc *this, int icoord )
+f     RESULT = AST_GETSTCCOORD( THIS, ICOORD, STATUS )
+
+*  Class Membership:
+*     Stc method.
+
+*  Description:
+*     When any sub-class of Stc is created, the constructor function
+*     allows one or more AstroCoords elements to be stored within the Stc.
+*     This function allows any one of these AstroCoords elements to be
+*     retrieved. The format of the returned information is the same as
+*     that used to pass the original information to the Stc constructor.
+*     That is, the information is returned in a KeyMap structure
+*     containing elements with one or more of the keys given by symbolic
+*     constants AST__STCNAME, AST__STCVALUE, AST__STCERROR, AST__STCRES,
+*     AST__STCSIZE and AST__STCPIXSZ.
+*
+*     If the coordinate system represented by the Stc has been changed
+*     since it was created (for instance, by changing its System
+*     attribute), then the sizes and positions in the returned KeyMap
+*     will reflect the change in coordinate system.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Stc.
+c     icoord
+f     ICOORD = INTEGER (Given)
+*        The index of the AstroCoords element required. The first has index
+*        one. The number of AstroCoords elements in the Stc can be found using
+c        function astGetStcNcoord.
+f        function AST_GETSTCNCOORD.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetStcCoord()
+f     AST_GETSTCCOORD = INTEGER
+*        A pointer to a new KeyMap containing the required information.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstKeyMap *result;
+   AstMapping *map;
+   AstMapping *smap;
+   AstObject *obj;
+   AstRegion *reg;
+   AstRegion *rereg;
+   AstRegion *srereg;
+   int ikey;
+   int nc;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate the supplied index. */
+   nc = astGetStcNCoord( this );
+   if( icoord < 1 || icoord > nc ) {
+      astError( AST__STCIND, "astGetStcCoord(%s): Supplied AstroCoords "
+                "index (%d) is invalid.", status, astGetClass( this ), icoord );
+
+      if( icoord < 1 ) {
+         astError( AST__STCIND, "The index of the first AstroCoord "
+                   "element is one, not zero." , status);
+      } else if( nc == 0 ) {
+         astError( AST__STCIND, "There are no AstroCoords elements in "
+                   "the supplied %s.", status, astGetClass( this ) );
+      } else if( nc == 1 ) {
+         astError( AST__STCIND, "There is 1 AstroCoords element in "
+                   "the supplied %s.", status, astGetClass( this ) );
+      } else {
+         astError( AST__STCIND, "There are %d AstroCoords elements in "
+                   "the supplied %s.", status, nc, astGetClass( this ) );
+      }
+
+/* If the index is OK, initialise the returned KeyMap to be a copy of the
+   KeyMap holding information about the required AstroCoords element.*/
+   } else {
+      result = astCopy( this->coord[ icoord - 1 ] );
+
+/* The Regions stored within this KeyMap describe regions within the base
+   Frame of the parent Region structure. If the Mapping from base to current
+   Frame in the parent Region structure is not a UnitMap, we need to
+   change these to represent regions within the current Frame of the
+   parent Region structure. */
+      map = astGetMapping( ((AstRegion *)this)->frameset,
+                           AST__BASE, AST__CURRENT );
+      smap = astSimplify( map );
+      frm = astGetFrame( ((AstRegion *)this)->frameset, AST__CURRENT );
+
+/* If the Frame represented by the Region has changed, erase the Names
+   element since they may no longer be correct. */
+      if( !astIsAUnitMap( smap ) ) astMapRemove( result, AST__STCNAME );
+
+/* Loop round keys for which a Region may be stored in the KeyMap. */
+      for( ikey = 0; ikey < NREG; ikey++ ) {
+
+/* If the KeyMap contains a Region for this key, get a pointer to it. */
+         if( astMapGet0A( result, regkey[ ikey ], &obj ) ){
+            reg = (AstRegion *) obj;
+
+/* Sets its RegionFS attribute so that the encapsulated FrameSet will be
+   included in any dump of the Region. This is needed since the returned
+   Region pointer will have no parent Region from which the FrameSet can
+   be determined. */
+            astSetRegionFS( reg, 1 );
+
+/* If necessary, remap the Region into the current Frame, and simplify. */
+            if( !astIsAUnitMap( smap ) ) {
+               rereg = astMapRegion( reg, smap, frm );
+               srereg = astSimplify( rereg );
+               rereg = astAnnul( rereg );
+            } else {
+               srereg = astClone( reg );
+            }
+
+/* Replace the Region in the KeyMap with the remapped Region. */
+            astMapPut0A( result, regkey[ ikey ], srereg, NULL );
+
+/* Free resources */
+            reg = astAnnul( reg );
+            srereg = astAnnul( srereg );
+         }
+      }
+
+      frm = astAnnul( frm );
+      map = astAnnul( map );
+      smap = astAnnul( smap );
+
+/* Annul the returned KeyMap if an error has occurred. */
+      if( !astOK ) result = astAnnul( result );
+   }
+
+/* Return the pointer */
+   return result;
+
+}
+
+static int GetStcNCoord( AstStc *this, int *status ){
+/*
+*++
+*  Name:
+c     astGetStcNCoord
+f     AST_GETSTCNCOORD
+
+*  Purpose:
+*     Return the number of AstroCoords elements stored in an Stc.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "stc.h"
+c     int astGetStcNCoord( AstStc *this )
+f     RESULT = AST_GETSTCNCOORD( THIS, STATUS )
+
+*  Class Membership:
+*     Stc method.
+
+*  Description:
+*     This function returns the number of AstroCoords elements stored in
+*     an Stc.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Stc.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetStcNCoord()
+f     AST_GETSTCNCOORD = INTEGER
+*        The number of  AstroCoords elements stored in the Stc.
+
+*  Notes:
+*     - Zero will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Return the required value. */
+   return astOK ? this->ncoord : 0;
+
+}
+
+static AstRegion *GetStcRegion( AstStc *this, int *status ) {
+/*
+*++
+*  Name:
+c     astGetStcRegion
+f     AST_GETSTCREGION
+
+*  Purpose:
+*     Obtain a copy of the encapsulated Region within a Stc.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "stc.h"
+c     AstRegion *astGetStcRegion( AstStc *this )
+f     RESULT = AST_GETSTCREGION( THIS, STATUS )
+
+*  Class Membership:
+*     Region method.
+
+*  Description:
+*     This function returns a pointer to a deep copy of the Region
+*     supplied when the Stc was created.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Stc.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astGetStcRegion()
+f     AST_GETSTCREGION = INTEGER
+*        A pointer to a deep copy of the Region encapsulated within the
+*        supplied Stc.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Return a pointer to a copy of the encapsulated Region. */
+   return astCopy( this->region );
+}
+
+static int GetUseDefs( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetUseDefs
+
+*  Purpose:
+*     Get the value of the UseDefs attribute for a Stc.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     int GetUseDefs( AstObject *this_object, int *status ) {
+
+*  Class Membership:
+*     Stc member function (over-rides the protected astGetUseDefs
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the value of the UseDefs attribute for a
+*     Stc, supplying a suitable default.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - The USeDefs value.
+*/
+
+/* Local Variables: */
+   AstStc *this;                 /* Pointer to the Stc structure */
+   int result;                   /* Value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Stc structure. */
+   this = (AstStc *) this_object;
+
+/* If the UseDefs value for the Stc has been set explicitly, use the
+   Get method inherited from the parent Region class to get its value. */
+   if( astTestUseDefs( this ) ) {
+      result = (*parent_getusedefs)( this_object, status );
+
+/* Otherwise, supply a default value equal to the UseDefs value of the
+   encapsulated Region. */
+   } else {
+      result = astGetUseDefs( this->region );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astInitStcVtab_(  AstStcVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Stc.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     void astInitStcVtab( AstStcVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Stc vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Stc class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstRegionVtab *region;        /* Pointer to Region component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitRegionVtab( (AstRegionVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAStc) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstRegionVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+   vtab->GetRegionClass = GetRegionClass;
+   vtab->GetStcRegion = GetStcRegion;
+   vtab->GetStcCoord = GetStcCoord;
+   vtab->GetStcNCoord = GetStcNCoord;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   region = (AstRegionVtab *) vtab;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_simplify = mapping->Simplify;
+   mapping->Simplify = Simplify;
+
+   parent_setregfs = region->SetRegFS;
+   region->SetRegFS = SetRegFS;
+
+   parent_equal = object->Equal;
+   object->Equal = Equal;
+
+   parent_clearclosed = region->ClearClosed;
+   region->ClearClosed = ClearClosed;
+
+   parent_setclosed = region->SetClosed;
+   region->SetClosed = SetClosed;
+
+   region->TestClosed = TestClosed;
+   region->GetClosed = GetClosed;
+
+   parent_regsetattrib = region->RegSetAttrib;
+   region->RegSetAttrib = RegSetAttrib;
+
+   parent_regclearattrib = region->RegClearAttrib;
+   region->RegClearAttrib = RegClearAttrib;
+
+   parent_clearnegated = region->ClearNegated;
+   region->ClearNegated = ClearNegated;
+
+   parent_setnegated = region->SetNegated;
+   region->SetNegated = SetNegated;
+
+   region->TestNegated = TestNegated;
+   region->GetNegated = GetNegated;
+
+   parent_setmeshsize = region->SetMeshSize;
+   region->SetMeshSize = SetMeshSize;
+
+   parent_clearmeshsize = region->ClearMeshSize;
+   region->ClearMeshSize = ClearMeshSize;
+
+   region->TestMeshSize = TestMeshSize;
+   region->GetMeshSize = GetMeshSize;
+
+   parent_setfillfactor = region->SetFillFactor;
+   region->SetFillFactor = SetFillFactor;
+
+   parent_clearfillfactor = region->ClearFillFactor;
+   region->ClearFillFactor = ClearFillFactor;
+
+   region->TestFillFactor = TestFillFactor;
+   region->GetFillFactor = GetFillFactor;
+
+   parent_getusedefs = object->GetUseDefs;
+   object->GetUseDefs = GetUseDefs;
+
+   parent_getdefunc = region->GetDefUnc;
+   region->GetDefUnc = GetDefUnc;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   region->Overlap = Overlap;
+   region->OverlapX = OverlapX;
+   region->RegBaseBox = RegBaseBox;
+   region->RegBaseMesh = RegBaseMesh;
+   region->RegBasePick = RegBasePick;
+   region->RegPins = RegPins;
+   region->GetBounded = GetBounded;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "Stc", "An IVOA Space-Time-Coords object" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static AstKeyMap *MakeAstroCoordsKeyMap( AstRegion *reg, AstKeyMap *coord,
+                                         const char *class, int *status ){
+/*
+*  Name:
+*     MakeAstroCoordsKeyMap
+
+*  Purpose:
+*     Create a new KeyMap holding Regions describing a supplied
+*     AstroCoords element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     AstKeyMap *MakeAstroCoordsKeyMap( AstRegion *reg, AstKeyMap *coord,
+*                                       const char *class, int *status )
+
+*  Class Membership:
+*     Stc member function
+
+*  Description:
+*     This function returns a pointer to a new KeyMap containing elements
+*     which correspond to the components of an STC AstroCoords element.
+*     The element with key AST__STCNAME holds a vector of character
+*     strings containing the names associated with each of the axies.
+*     The other elements of the returned KeyMap such as AST__STCERROR,
+*     AST__STCRES, etc, hold pointers to Regions describing the error
+*     box, resolution, etc, in the Frame of the supplied Region "reg".
+
+*  Parameters:
+*     reg
+*        Pointer to the Region in which the AstroCoords is defined.
+*     coordId
+*        An ID (not a pointer) to a KeyMap defining a single <AstroCoords>
+*        element, having elements with keys given by constants AST__STCNAME,
+*        AST__STCVALUE, AST__STCERROR, AST__STCRES, AST__STCSIZE,
+*        AST__STCPIXSZ. Any of these elements may be omitted, but no other
+*        elements should be included. If supplied, the AST__STCNAME element
+*        should be a vector of character string pointers holding the "Name"
+*        item for each axis. Any other supplied elements should be scalar
+*        elements, each  holding a pointer to a Region describing the
+*        associated item of ancillary information (error, resolution, size,
+*        pixel size or value). These Regions should refer to the coordinate
+*        system represented by "region".
+*     class
+*        Pointer to a string holding the STC class name.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to the new KeyMap.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;                /* Pointer to current Frame */
+   AstFrameSet *fs;              /* Pointer to conversion FrameSet */
+   AstKeyMap *result;            /* Pointer value to return */
+   AstMapping *map;              /* Pointer to conversion Mapping */
+   AstObject *obj;               /* Pointer to Object stored in supplied KeyMap */
+   AstRegion *areg;              /* Pointer to remapped Region */
+   AstRegion *sareg;             /* Pointer to simplified remapped Region */
+   const char *key;              /* Current key */
+   int j;                        /* Index of key within KeyMap */
+   int naxes;                    /* Number of axes in region */
+   int nkey;                     /* Number of keys in supplied KeyMap */
+   int nv;                       /* Number of values in KeyMap element */
+   int type;                     /* Data type of entry */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* Confirm it is a genuine KeyMap pointer. */
+   if( !astIsAKeyMap( coord ) && astOK ) {
+      astError( AST__STCKEY, "astInitStc(%s): Supplied pointer is for "
+                "a %s, not a KeyMap.", status, class, astGetClass( coord ) );
+   }
+
+/* Initialise the new KeyMap to be a copy of the supplied KeyMap. */
+   result = astCopy( coord );
+
+/* Check the supplied KeyMap is usable. */
+   naxes = astGetNaxes( reg );
+   nkey = astMapSize( result );
+   for( j = 0; j < nkey; j++ ) {
+      key = astMapKey( result, j );
+      if( key ) {
+         nv = astMapLength( result, key );
+         type = astMapType( result, key );
+
+/* Check no unknown keys are present in the KeyMap. */
+         if( strcmp( key, AST__STCNAME ) &&
+             strcmp( key, AST__STCVALUE ) &&
+             strcmp( key, AST__STCERROR ) &&
+             strcmp( key, AST__STCRES ) &&
+             strcmp( key, AST__STCSIZE ) &&
+             strcmp( key, AST__STCPIXSZ ) ) {
+            astError( AST__STCKEY, "astInitStc(%s): Unknown key "
+                      "\"%s\" supplied in an AstroCoords list.", status,
+                      class, key );
+            break;
+
+/* Check that the "Name" element is a vector of "naxes" strings. */
+         } else if( !strcmp( key, AST__STCNAME ) ) {
+            if( nv != naxes ) {
+               astError( AST__STCKEY, "astInitStc(%s): %d \"%s\" "
+                         "values supplied in an AstroCoords list, but "
+                         "the Stc has %d axes. ", status, class, nv, key,
+                         naxes );
+               break;
+
+            } else if( type != AST__STRINGTYPE ) {
+               astError( AST__STCKEY, "astInitStc(%s): The \"%s\" "
+                         "values supplied in an AstroCoords list are "
+                         "not character strings. ", status, class, key );
+               break;
+            }
+
+/* Check that all other elements are scalar. */
+         } else if( nv != 1 ) {
+            astError( AST__STCKEY, "astInitStc(%s): %d \"%s\" "
+                      "values supplied in an AstroCoords list, but "
+                      "only one is allowed. ", status, class, nv, key );
+            break;
+
+/* Check that all other elements are AST Object pointers. */
+         } else if( type != AST__OBJECTTYPE ) {
+            astError( AST__STCKEY, "astInitStc(%s): The \"%s\" "
+                      "value supplied in an AstroCoords list is "
+                      "not an AST Object pointer. ", status, class, key );
+            break;
+
+/* Check that the Object pointers are not NULL. */
+         } else {
+            astMapGet0A( result, key, &obj );
+            if( astOK ) {
+               if( !obj ) {
+                  astError( AST__STCKEY, "astInitStc(%s): The \"%s\" "
+                            "value supplied in an AstroCoords list is "
+                            "a NULL pointer. ", status, class, key );
+                  break;
+
+/* Check that the Object pointers are Region pointers. */
+               } else if( !astIsARegion( obj ) ){
+                  astError( AST__STCKEY, "astInitStc(%s): The \"%s\" "
+                            "value supplied in an AstroCoords list is "
+                            "a %s, not a Region. ", status, class, key,
+                            astGetClass(obj) );
+                  obj = astAnnul( obj );
+                  break;
+
+/* Check that the Region pointers can be converted to the coordinate
+   system represented by the supplied Region. */
+               } else {
+                  fs = astConvert( obj, reg, "" );
+                  if( !fs ) {
+                     obj = astAnnul( obj );
+                     astError( AST__STCKEY, "astInitStc(%s): The \"%s\" "
+                               "value supplied in an AstroCoords list "
+                               "cannot be converted to the coordinate "
+                               "system of its parent Stc object.", status, class,
+                               key );
+                     break;
+
+/* If necessary, map the Region into the same frame as the supplied
+   Region, and replace the Region in the returned KeyMap with the
+   remapped Region. Also set the RegionFS attribute to indicate that the
+   FrameSet in the Region does not need to be dumped if it contains a
+   UnitMap. */
+                  } else {
+                     map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+                     if( !astIsAUnitMap( map ) ) {
+                        frm = astGetFrame( fs, AST__CURRENT );
+                        areg = astMapRegion( (AstRegion *) obj, map, frm );
+                        sareg = astSimplify( areg );
+                        astSetRegionFS( sareg, 0 );
+                        astMapPut0A( result, key, sareg, NULL );
+                        areg = astAnnul( areg );
+                        sareg = astAnnul( sareg );
+                        frm = astAnnul( frm );
+                     } else {
+                        astSetRegionFS( (AstRegion *) obj, 0 );
+                     }
+                     map = astAnnul( map );
+                     fs = astAnnul( fs );
+
+                  }
+                  obj = astAnnul( obj );
+               }
+            }
+         }
+      }
+   }
+
+/* Free the returned KeyMap if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result */
+   return result;
+
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstStc *this;       /* Pointer to STC structure */
+   int i;              /* Loop count */
+   int result;         /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the STC structure. */
+   this = (AstStc *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->region, mode, extra, fail );
+   for( i = 0; i < this->ncoord; i++ ) {
+      if( !result ) result = astManageLock( this->coord[ i ], mode,
+                                            extra, fail );
+   }
+
+   return result;
+
+}
+#endif
+
+static int Overlap( AstRegion *this, AstRegion *that, int *status ){
+/*
+*  Name:
+*     Overlap
+
+*  Purpose:
+*     Test if two regions overlap each other.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     int Overlap( AstRegion *this, AstRegion *that, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astOverlap method inherited
+*     from the Region class).
+
+*  Description:
+*     This function returns an integer value indicating if the two
+*     supplied Regions overlap. The two Regions are converted to a commnon
+*     coordinate system before performing the check. If this conversion is
+*     not possible (for instance because the two Regions represent areas in
+*     different domains), then the check cannot be performed and a zero value
+*     is returned to indicate this.
+
+*  Parameters:
+*     this
+*        Pointer to the first Region.
+*     that
+*        Pointer to the second Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     astOverlap()
+*        A value indicating if there is any overlap between the two Regions.
+*        Possible values are:
+*
+*        0 - The check could not be performed because the second Region
+*            could not be mapped into the coordinate system of the first
+*            Region.
+*
+*        1 - There is no overlap between the two Regions.
+*
+*        2 - The first Region is completely inside the second Region.
+*
+*        3 - The second Region is completely inside the first Region.
+*
+*        4 - There is partial overlap between the two Regions.
+*
+*        5 - The Regions are identical.
+*
+*        6 - The second Region is the negation of the first Region.
+
+*  Notes:
+*     - The returned values 5 and 6 do not check the value of the Closed
+*     attribute in the two Regions.
+*     - A value of zero will be returned if this function is invoked with the
+*     AST error status set, or if it should fail for any reason.
+
+*/
+
+/* Check the inherited status. */
+   if ( !astOK ) return 0;
+
+/* Invoke the "astOverlap" method on the encapsulated Region. */
+   return astOverlap( ((AstStc *)this)->region, that );
+}
+
+static int OverlapX( AstRegion *that, AstRegion *this, int *status ){
+/*
+*  Name:
+*     OverlapX
+
+*  Purpose:
+*     Test if two regions overlap each other.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     int OverlapX( AstRegion *that, AstRegion *this )
+
+*  Class Membership:
+*     Stc member function (over-rides the astOverlapX method inherited
+*     from the Region class).
+
+*  Description:
+*     This function performs the processing for the public astOverlap
+*     method and has exactly the same interface except that the order
+*     of the two arguments is swapped. This is a trick to allow
+*     the astOverlap method to be over-ridden by derived classes on
+*     the basis of the class of either of its two arguments.
+*
+*     See the astOverlap method for details of the interface.
+
+*/
+
+/* Local Variables: */
+   int result;
+
+/* Check the inherited status. */
+   if ( !astOK ) return 0;
+
+/* Invoke the "astOverlapX" method on the encapsulated Region. */
+   result = astOverlap( ((AstStc *)that)->region, this );
+
+/* Swap the returned values 2 and 3 to take account of the swapping of
+   the regions.*/
+   if( result == 2 ) {
+      result = 3;
+   } else if( result == 3 ) {
+      result = 2;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status ){
+/*
+*  Name:
+*     RegBaseBox
+
+*  Purpose:
+*     Returns the bounding box of an un-negated Region in the base Frame of
+*     the encapsulated FrameSet.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     void RegBaseBox( AstRegion *this, double *lbnd, double *ubnd, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astRegBaseBox protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns the upper and lower axis bounds of a Region in
+*     the base Frame of the encapsulated FrameSet, assuming the Region
+*     has not been negated. That is, the value of the Negated attribute
+*     is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     lbnd
+*        Pointer to an array in which to return the lower axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     ubnd
+*        Pointer to an array in which to return the upper axis bounds
+*        covered by the Region in the base Frame of the encapsulated
+*        FrameSet. It should have at least as many elements as there are
+*        axes in the base Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Invoke the method on the encapsulated Region. */
+   astRegBaseBox( ((AstStc *)this)->region, lbnd, ubnd );
+}
+
+static AstPointSet *RegBaseMesh( AstRegion *this, int *status ){
+/*
+*  Name:
+*     RegBaseMesh
+
+*  Purpose:
+*     Create a new PointSet containing a mesh of points on the boundary of a
+*     Region in its base Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     AstPointSet *astRegBaseMesh( AstRegion *this, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astRegBaseMesh protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function creates a new PointSet containing a mesh of points on the
+*     boundary of the Region. The points refer to the base Frame of
+*     the encapsulated FrameSet.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the PointSet. Annul the pointer using astAnnul when it
+*     is no longer needed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return NULL;
+
+/* Invoke the astRegMesh method on the encapsulated Region. This returns
+   a mesh in the current Frame of the encapsulated Region which is the same
+   as the base Frame of the Stc Region. */
+   return astRegMesh( ((AstStc *)this)->region );
+}
+
+static AstRegion *RegBasePick( AstRegion *this_region, int naxes,
+                               const int *axes, int *status ){
+/*
+*  Name:
+*     RegBasePick
+
+*  Purpose:
+*     Return a Region formed by picking selected base Frame axes from the
+*     supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     AstRegion *RegBasePick( AstRegion *this, int naxes, const int *axes,
+*                             int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astRegBasePick protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function attempts to return a Region that is spanned by selected
+*     axes from the base Frame of the encapsulated FrameSet of the supplied
+*     Region. This may or may not be possible, depending on the class of
+*     Region. If it is not possible a NULL pointer is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     naxes
+*        The number of base Frame axes to select.
+*     axes
+*        An array holding the zero-based indices of the base Frame axes
+*        that are to be selected.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the Region, or NULL if no region can be formed.
+
+*  Notes:
+*    - A NULL pointer is returned if an error has already occurred, or if
+*    this function should fail for any reason.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Invoke the astRegBaePick method on the encapsulated Region. */
+   return astRegBasePick( ((AstStc *)this_region)->region, naxes, axes );
+}
+
+static void RegClearAttrib( AstRegion *this_region, const char *attrib,
+                            char **base_attrib, int *status ) {
+/*
+*  Name:
+*     RegClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Region.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     void RegClearAttrib( AstRegion *this, const char *attrib,
+*                          char **base_attrib, int *status )
+
+*  Class Membership:
+*     Stc method (over-rides the astRegClearAttrib method inherited from
+*     the Region class).
+
+*  Description:
+*     This function clears the value of an attribute in both the base and
+*     current Frame in the FrameSet encapsulated within a Region, without
+*     remapping either Frame.
+*
+*     No error is reported if the attribute is not recognised by the base
+*     Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     attrib
+*        Pointer to a null terminated string containing an attribute name.
+*        NOTE, IT SHOULD BE ENTIRELY LOWER CASE.
+*     base_attrib
+*        Address of a location at which to return a pointer to the null
+*        terminated string holding the name of the attribute which was
+*        cleared in the base Frame of the encapsulated FrameSet. This may
+*        differ from the supplied name if the supplied name contains an axis
+*        index and the current->base Mapping in the FrameSet produces an
+*        axis permutation. The returned pointer should be freed using
+*        astFree when no longer needed. A NULL pointer may be supplied in
+*        which case no pointer is returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstStc *this;
+   char *batt;
+   int rep;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Stc  structure. */
+   this = (AstStc *) this_region;
+
+/* Use the RegClearAttrib method inherited from the parent class to clear
+   the attribute in the current and base Frames in the FrameSet encapsulated
+   by the parent Region structure. */
+   (*parent_regclearattrib)( this_region, attrib, &batt, status );
+
+/* Now clear the base Frame attribute in the encapsulated Region (the current
+   Frame within the encapsulated Region is equivalent to the base Frame in the
+   parent Region structure). Annul any "attribute unknown" error that results
+   from attempting to do this. */
+   if( astOK ) {
+      rep = astReporting( 0 );
+      astRegClearAttrib( this->region, batt, NULL );
+      if( astStatus == AST__BADAT ) astClearStatus;
+      astReporting( rep );
+   }
+
+/* If required, return the base Frame attribute name, otherwise free it. */
+   if( base_attrib ) {
+      *base_attrib = batt;
+   } else {
+      batt = astFree( batt );
+   }
+}
+
+static int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+                    int **mask, int *status ){
+/*
+*  Name:
+*     RegPins
+
+*  Purpose:
+*     Check if a set of points fall on the boundary of a given Stc.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     int RegPins( AstRegion *this, AstPointSet *pset, AstRegion *unc,
+*                  int **mask, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astRegPins protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a flag indicating if the supplied set of
+*     points all fall on the boundary of the given Stc.
+*
+*     Some tolerance is allowed, as specified by the uncertainty Region
+*     stored in the supplied Stc "this", and the supplied uncertainty
+*     Region "unc" which describes the uncertainty of the supplied points.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc.
+*     pset
+*        Pointer to the PointSet. The points are assumed to refer to the
+*        base Frame of the FrameSet encapsulated by "this".
+*     unc
+*        Pointer to a Region representing the uncertainties in the points
+*        given by "pset". The Region is assumed to represent the base Frame
+*        of the FrameSet encapsulated by "this". Zero uncertainity is assumed
+*        if NULL is supplied.
+*     mask
+*        Pointer to location at which to return a pointer to a newly
+*        allocated dynamic array of ints. The number of elements in this
+*        array is equal to the value of the Npoint attribute of "pset".
+*        Each element in the returned array is set to 1 if the
+*        corresponding position in "pset" is on the boundary of the Region
+*        and is set to zero otherwise. A NULL value may be supplied
+*        in which case no array is created. If created, the array should
+*        be freed using astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the points all fall on the boundary of the given
+*     Region, to within the tolerance specified. Zero otherwise.
+
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return 0;
+
+/* Invoke the method on the encapsulated Region. */
+   return astRegPins( ((AstStc *)this)->region, pset, unc, mask );
+}
+
+static void RegSetAttrib( AstRegion *this_region, const char *setting,
+                          char **base_setting, int *status ) {
+/*
+*  Name:
+*     RegSetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     void RegSetAttrib( AstRegion *this, const char *setting,
+*                        char **base_setting, int *status )
+
+*  Class Membership:
+*     Stc method (over-rides the astRegSetAttrib method inherited from
+*     the Region class).
+
+*  Description:
+*     This function assigns an attribute value to both the base and
+*     current Frame in the FrameSet encapsulated within a Region, without
+*     remapping either Frame.
+*
+*     No error is reported if the attribute is not recognised by the base
+*     Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     setting
+*        Pointer to a null terminated attribute setting string. NOTE, IT
+*        SHOULD BE ENTIRELY LOWER CASE. The supplied string will be
+*        interpreted using the public interpretation implemented by
+*        astSetAttrib. This can be different to the interpretation of the
+*        protected accessor functions. For instance, the public
+*        interpretation of an unqualified floating point value for the
+*        Epoch attribute is to interpet the value as a gregorian year,
+*        but the protected interpretation is to interpret the value as an
+*        MJD.
+*     base_setting
+*        Address of a location at which to return a pointer to the null
+*        terminated attribute setting string which was applied to the
+*        base Frame of the encapsulated FrameSet. This may differ from
+*        the supplied setting if the supplied setting contains an axis
+*        index and the current->base Mapping in the FrameSet produces an
+*        axis permutation. The returned pointer should be freed using
+*        astFree when no longer needed. A NULL pointer may be supplied in
+*        which case no pointer is returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *keymap;
+   AstObject *obj;
+   AstRegion *reg;
+   AstStc *this;
+   char *bset;
+   int i;
+   int ikey;
+   int rep;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Stc structure. */
+   this = (AstStc *) this_region;
+
+/* Use the RegSetAttrib method inherited from the parent class to apply the
+   setting to the current and base Frames in the FrameSet encapsulated by the
+   parent Region structure. */
+   (*parent_regsetattrib)( this_region, setting, &bset, status );
+
+/* Now apply the base Frame setting to the encapsulated Region (the current
+   Frame within the encapsulated Region is equivalent to the base Frame in the
+   parent Region structure). Annul any "attribute unknown" error that results
+   from attempting to do this. Also do any AstroCoords in the Stc. */
+   if( astOK ) {
+      rep = astReporting( 0 );
+      astRegSetAttrib( this->region, bset, NULL );
+      if( astStatus == AST__BADAT ) astClearStatus;
+
+/* Loop round all AstroCoords elements. */
+      for( i = 0; i < this->ncoord; i++ ) {
+
+/* Get a pointer to the KeyMap holding a description of the current
+   AstroCoords element. */
+         keymap = this->coord[ i ];
+
+/* Loop round all the elements of this KeyMap which may hold a Region
+   pointer. */
+         for( ikey = 0; ikey < NREG; ikey++ ) {
+
+/* If the KeyMap contains a Region for this key, get a pointer to it. */
+            if( astMapGet0A( keymap, regkey[ ikey ], &obj ) ){
+               reg = (AstRegion *) obj;
+
+/* Modify it by applying the attribute setting. */
+               astRegSetAttrib( reg, bset, NULL );
+               if( astStatus == AST__BADAT ) astClearStatus;
+
+/* Annul the pointer. */
+               reg = astAnnul( reg );
+            }
+         }
+      }
+
+      astReporting( rep );
+   }
+
+/* If required, return the base Frame setting string, otherwise free it. */
+   if( base_setting ) {
+      *base_setting = bset;
+   } else {
+      bset = astFree( bset );
+   }
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Stc.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astSetAttrib method inherited
+*     from the Region class).
+
+*  Description:
+*     This function assigns an attribute value for a Stc, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*/
+
+/* Local Vaiables: */
+   AstStc *this;                 /* Pointer to the Stc structure */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Stc structure. */
+   this = (AstStc *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* (none as yet) */
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* Use this macro to report an error if a read-only attribute has been
+   specified. */
+   if ( MATCH( "regionclass" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+}
+
+static void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status ) {
+/*
+*  Name:
+*     SetRegFS
+
+*  Purpose:
+*     Stores a new FrameSet in a Region
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     void SetRegFS( AstRegion *this_region, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     Stc method (over-rides the astSetRegFS method inherited from
+*     the Region class).
+
+*  Description:
+*     This function creates a new FrameSet and stores it in the supplied
+*     Region. The new FrameSet contains two copies of the supplied
+*     Frame, connected by a UnitMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Region.
+*     frm
+*        The Frame to use.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstRegion *creg;        /* Pointer to encapsulated Region structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the parent method to store the FrameSet in the parent Region
+   structure. */
+   (* parent_setregfs)( this_region, frm, status );
+
+/* If the encapsulated Region has a dummy FrameSet use this method
+   recursively to give it the same FrameSet. */
+   creg = ((AstStc *) this_region )->region;
+   if( creg && !astGetRegionFS( creg ) ) astSetRegFS( creg, frm );
+
+}
+
+static AstMapping *Simplify( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     Simplify
+
+*  Purpose:
+*     Simplify a Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "region.h"
+*     AstMapping *Simplify( AstMapping *this, int *status )
+
+*  Class Membership:
+*     Stc method (over-rides the astSimplify method inherited from
+*     the Region class).
+
+*  Description:
+*     This function simplifies a Stc to eliminate redundant
+*     computational steps, or to merge separate steps which can be
+*     performed more efficiently in a single operation.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A new pointer to the (possibly simplified) Region.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;                /* Current Frame */
+   AstKeyMap *keymap;            /* KeyMap holding stroCoords element */
+   AstMapping *map;              /* Base->current Mapping */
+   AstObject *obj;               /* Pointer to object retrieved from keymap */
+   AstRegion *newreg;            /* New encapsulated Region */
+   AstRegion *reg;               /* AstroCoords Region pointer */
+   AstRegion *treg;              /* Temporary Region pointer */
+   AstStc *stc;                  /* Returned Stc Structure. */
+   AstStc *temp;                 /* Temporary Stc pointer */
+   int i;                        /* Index of current AstroCoords element */
+   int ikey;                     /* Index of key to be tested */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Invoke the Simplify method of the parent Region class. This simplifies
+   the FrameSet and uncertainty Region in the parent Region structure. */
+   stc = (AstStc *) (AstRegion *) (* parent_simplify)( this_mapping, status );
+
+/* If the Stc is negated, we can perform a simplication by transferring
+   the negated state from the Stc itself to the encapsulated Region. */
+   if( astGetNegated( stc ) ) {
+
+/* Ensure that modifying "stc" will not modify the supplied Stc, by
+   creating a copy of the supplied Stc, if this has not already been done. */
+      if( stc == (AstStc *) this_mapping ) {
+         temp = (AstStc *) astCopy( stc );
+         (void) astAnnul( stc );
+         stc = temp;
+      }
+
+/* Modify "temp" by negating both the Stc structure and its encapsulated
+   Region. */
+      astNegate( stc );
+      astNegate( stc->region );
+
+   }
+
+/* Get the base->current Mapping from the parent Region structure, and
+   the current Frame. */
+   map = astGetMapping( ((AstRegion *) stc)->frameset, AST__BASE, AST__CURRENT );
+   frm = astGetFrame( ((AstRegion *) stc)->frameset, AST__CURRENT );
+
+/* We may be able to perform some more simplication on the encapsulated
+   Region itself. If the above mapping is not a unit map, remap the
+   encapsulated Region into the current Frame of the parent Region structure
+   and simplify it. This transfers complication from the Mapping in the
+   parent Region structure to the encapsulated Region. */
+   if( !astIsAUnitMap( map ) ) {
+      treg = astMapRegion( stc->region, map, frm );
+      newreg = astSimplify( treg );
+      treg = astAnnul( treg );
+
+/* If the base->current Mapping in the parent Region structure is a unit
+   map, simplification of the whole Stc is possible if the encapsulated
+   Region (without any remapping) can be simplied. */
+   } else {
+      newreg = astSimplify( stc->region );
+   }
+
+/* If the encapsulated Region has been changed, store it in the returned
+   Stc. */
+   if( newreg != stc->region ) {
+
+/* Ensure that modifying "stc" will not modify the supplied Stc, by
+   creating a copy of the supplied Stc, if this has not already been done. */
+      if( stc == (AstStc *) this_mapping ) {
+         temp = (AstStc *) astCopy( stc );
+         (void) astAnnul( stc );
+         stc = temp;
+      }
+
+/* Store the new region in "stc", annulling the existing Region. */
+      if( stc ) {
+         (void) astAnnul( stc->region );
+         stc->region = astClone( newreg );
+      }
+
+/* The encapsulated Region now represents an area in the current Frame
+   represented by the supplied Stc. Since the encapsulated Region is
+   defined as being in the base Frame of the FrameSet in the parent
+   Region structure, the parent FrameSet should just be a UnitMap. Modify
+   it appropriately (if it not already a UnitMap). */
+      if( !astIsAUnitMap( map ) ) astSetRegFS( stc, frm );
+   }
+
+/* Free resources */
+   newreg = astAnnul( newreg );
+
+/* Now we do a similar process on any Regions held within an AstroCoords
+   elements. Loop round all AstroCoords elements. */
+   if( stc ) {
+      for( i = 0; i < stc->ncoord; i++ ) {
+
+/* Get a pointewr to the KeyMap holding a description of the current
+   AstroCoords element. */
+         keymap = stc->coord[ i ];
+
+/* Loop round all the elements of this KeyMap which may hold a Region
+   pointer. */
+         for( ikey = 0; ikey < NREG; ikey++ ) {
+
+/* If the KeyMap contains a Region for this key, get a pointer to it. */
+            if( astMapGet0A( keymap, regkey[ ikey ], &obj ) ){
+               reg = (AstRegion *) obj;
+
+/* We have two tasks now, firstly to ensure that this AstroCoords Region
+   describes an area in the base Frame of the FrameSet in the parent
+   Region structure (which may have been changed by the earlier
+   simplications performed by this function), and secondly, to attempt to
+   simplify the Region.
+
+   The Stc structure addressed by the "stc" pointer will have a current
+   Frame given by "frm". This will also be its base Frame, and the
+   base->current Mapping will consequently be a UnitMap. The Mapping from
+   the original base Frame to the new base Frame is given by "map". Unless
+   this is a UnitMap, we need to remap the Region.*/
+               if( !astIsAUnitMap( map ) ) {
+                  treg = astMapRegion( reg, map, frm );
+               } else {
+                  treg = astClone( reg );
+               }
+
+/* Now attempt to simplify the Region.*/
+               newreg = astSimplify( treg );
+
+/* If the Region has been changed by either of these steps, we need to
+   store the modified Region back in the "stc" structure which is being
+   returned. But we need to be careful we do not modify the supplied Stc
+   structure. */
+               if( newreg != reg ) {
+
+                  if( stc == (AstStc *) this_mapping ) {
+                     temp = astCopy( stc );
+                     (void) astAnnul( stc );
+                     stc = temp;
+                     keymap = temp->coord[ i ];
+                  }
+
+                  astMapPut0A( keymap, regkey[ ikey ], newreg, regcom[ ikey ] );
+
+               }
+
+/* Free resources */
+               reg = astAnnul( reg );
+               treg = astAnnul( treg );
+               newreg = astAnnul( newreg );
+
+            }
+         }
+      }
+   }
+
+/* Free resources */
+   map = astAnnul( map );
+   frm = astAnnul( frm );
+
+/* If an error occurred, annul the returned Mapping. */
+   if ( !astOK ) stc = astAnnul( stc );
+
+/* Return the result. */
+   return (AstMapping *) stc;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a Stc.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astTestAttrib protected
+*     method inherited from the Region class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a Stc's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstStc *this;                 /* Pointer to the Stc structure */
+   int len;                      /* Length of attrib string */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the Stc structure. */
+   this = (AstStc *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Read-only attributes. */
+/* --------------------- */
+/* Test if the attribute name matches any of the read-only attributes
+   of this class. If it does, then return zero. */
+   if ( !strcmp( attrib, "regionclass" ) ) {
+      result = 0;
+
+/* Not recognised. */
+/* --------------- */
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this_mapping, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a Stc to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     Stc member function (over-rides the astTransform method inherited
+*     from the Region class).
+
+*  Description:
+*     This function takes a Stc and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required Region.
+*     This implies applying each of the Stc's encapsulated Region in turn,
+*     either in series or in parallel.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc.
+*     in
+*        Pointer to the PointSet associated with the input coordinate values.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the Stc being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *ps;              /* Pointer to PointSet */
+   AstPointSet *pset_tmp;        /* Pointer to PointSet holding base Frame positions*/
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstRegion *reg;               /* Pointer to encapsulated Region */
+   AstStc *this;                 /* Pointer to the Stc structure */
+   double **ptr;                 /* Pointer to axis values */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   int coord;                    /* Zero-based index for coordinates */
+   int good;                     /* Is the point inside the Stc? */
+   int ncoord_out;               /* No. of coordinates per output point */
+   int ncoord_tmp;               /* No. of coordinates per base Frame point */
+   int neg;                      /* Negated value for encapsulated Region */
+   int neg_old;                  /* Original Negated flag */
+   int npoint;                   /* No. of points */
+   int point;                    /* Loop counter for points */
+   int rep;                      /* Original error reporting status */
+   int status_value;                   /* AST status value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a Pointer to the Stc structure */
+   this = (AstStc *) this_mapping;
+
+/* Get the encapsulated Region, and the Negated value which should be used
+   with it. The returned values take account of whether the supplied Stc has
+   itself been Negated or not. The returned Region represent a region within
+   the base Frame of the FrameSet encapsulated by the parent Region
+   structure. */
+   GetRegion( this, &reg, &neg, status );
+
+/* Temporarily set the Negated attribute to the required value.*/
+   neg_old = astGetNegated( reg );
+   astSetNegated( reg, neg );
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Region class. This function validates
+   all arguments and generates an output PointSet if necessary, containing
+   a copy of the input PointSet. */
+   result = (*parent_transform)( this_mapping, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* First use the encapsulated FrameSet in the parent Region structure to
+   transform the supplied positions from the current Frame in the
+   encapsulated FrameSet (the Frame represented by the Stc), to the
+   base Frame (the Frame in which the encapsulated Region are defined). Note,
+   the returned pointer may be a clone of the "in" pointer, and so we
+   must be carefull not to modify the contents of the returned PointSet. */
+   pset_tmp = astRegTransform( this, in, 0, NULL, NULL );
+
+/* Now transform this PointSet using the encapsulated Region. */
+   ps = astTransform( reg, pset_tmp, 0, NULL );
+
+/* Determine the numbers of points and coordinates per point for these base
+   Frame PointSets and obtain pointers for accessing the base Frame and output
+   coordinate values. */
+   npoint = astGetNpoint( pset_tmp );
+   ncoord_tmp = astGetNcoord( pset_tmp );
+   ptr = astGetPoints( ps );
+   ncoord_out = astGetNcoord( result );
+   ptr_out = astGetPoints( result );
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if ( astOK ) {
+
+      for ( point = 0; point < npoint; point++ ) {
+         good = 0;
+
+         for ( coord = 0; coord < ncoord_tmp; coord++ ) {
+            if( ptr[ coord ][ point ] != AST__BAD ) {
+               good = 1;
+               break;
+            }
+         }
+
+         if( !good ) {
+            for ( coord = 0; coord < ncoord_out; coord++ ) {
+               ptr_out[ coord ][ point ] = AST__BAD;
+            }
+         }
+      }
+   }
+
+/* Re-instate the original value for the Negated attribute of the
+   encapsulated Region. Do this even if an error has occurred. */
+   status_value = astStatus;
+   astClearStatus;
+   rep = astReporting( 0 );
+   if( reg ) astSetNegated( reg, neg_old );
+   astReporting( rep );
+   astSetStatus( status_value );
+
+/* Free resources. */
+   reg = astAnnul( reg );
+   ps = astAnnul( ps );
+   pset_tmp = astAnnul( pset_tmp );
+
+/* If an error occurred, clean up by deleting the output PointSet (if
+   allocated by this function) and setting a NULL result pointer. */
+   if ( !astOK ) {
+      if ( !out ) result = astDelete( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+
+/* Stc Attributes: */
+/* =============== */
+
+/*
+*att++
+*  Name:
+*     RegionClass
+
+*  Purpose:
+*     The AST class name of the Region encapsulated within an Stc
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String, read-only.
+
+*  Description:
+*     This is a read-only attribute giving the AST class name of the
+*     Region encapsulated within an Stc (that is, the class of the Region
+*     which was supplied when the Stc was created).
+
+*  Applicability:
+*     Stc
+*        All Stc objects this attribute.
+*att--
+*/
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Stc objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Stc objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the component
+*     Regions within the Stc.
+*/
+
+/* Local Variables: */
+   AstStc *in;                /* Pointer to input Stc */
+   AstStc *out;               /* Pointer to output Stc */
+   int i;                     /* AstroCoords index */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Stcs. */
+   in = (AstStc *) objin;
+   out = (AstStc *) objout;
+
+/* For safety, start by clearing any references to the input component
+   Regions, etc,  from the output Stc. */
+   out->region = NULL;
+   out->coord = NULL;
+   out->ncoord = 0;
+
+/* Make a copy of the Region and store a pointer to it in the output Stc
+   structure. */
+   out->region = astCopy( in->region );
+
+/* Copy any memory holding AstroCoords values */
+   if( in->coord && in->ncoord ) {
+      out->ncoord = in->ncoord;
+      out->coord = astMalloc( sizeof(AstKeyMap *) * (size_t)in->ncoord );
+      if( out->coord ) {
+         for( i = 0; i < in->ncoord; i++ ) {
+            out->coord[ i ] = astCopy( in->coord[ i ] );
+         }
+      }
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Stc objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Stc objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstStc *this;              /* Pointer to Stc */
+   int i;                     /* AstroCoords index */
+
+/* Obtain a pointer to the Stc structure. */
+   this = (AstStc *) obj;
+
+/* Annul the pointer to the encapsulated Region. */
+   this->region = astAnnul( this->region );
+
+/* Free any memory holding AstroCoords values */
+   if( this->coord ) {
+      for( i = 0; i < this->ncoord; i++ ) {
+         this->coord[ i ] = astAnnul( this->coord[ i ] );
+      }
+      this->coord = astFree( this->coord );
+   }
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Stc objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Stc class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Stc whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define COMMENT_LEN 150          /* Maximum length of a comment string */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstStc *this;                 /* Pointer to the Stc structure */
+   char comment[ COMMENT_LEN + 1 ]; /* Buffer for comment string */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword string */
+   int ico;                      /* Loop counter for KeyMaps */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Stc structure. */
+   this = (AstStc *) this_object;
+
+/* Write out values representing the instance variables for the Stc
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Encapsulated Region. */
+/* -------------------- */
+   astWriteObject( channel, "Region", 1, 1, this->region,
+                   "STC Region" );
+
+/* AstroCoords info */
+/* ---------------- */
+   astWriteInt( channel, "Ncoord", ( this->ncoord != 0 ), 0, this->ncoord,
+                "Number of AstroCoords elements" );
+
+   for ( ico = 1; ico <= this->ncoord; ico++ ) {
+      (void) sprintf( key, "Coord%d", ico );
+      (void) sprintf( comment, "AstroCoords number %d", ico );
+      astWriteObject( channel, key, 1, 1, this->coord[ ico - 1 ],
+                      comment );
+   }
+
+/* Undefine macros local to this function. */
+#undef COMMENT_LEN
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAStc and astCheckStc functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Stc,Region)
+astMAKE_CHECK(Stc)
+
+AstStc *astInitStc_( void *mem, size_t size, int init, AstStcVtab *vtab,
+                     const char *name, AstRegion *region, int ncoords,
+                     AstKeyMap **coords, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStc
+
+*  Purpose:
+*     Initialise a Stc.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stc.h"
+*      AstStc *astInitStc( void *mem, size_t size, int init, AstStcVtab *vtab,
+*                          const char *name, AstRegion *region, int ncoords,
+*                          AstKeyMap **coords )
+
+*  Class Membership:
+*     Stc initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Stc object. It allocates memory (if necessary) to
+*     accommodate the Stc plus any additional data associated with the
+*     derived class. It then initialises a Stc structure at the start
+*     of this memory. If the "init" flag is set, it also initialises the
+*     contents of a virtual function table for a Stc at the start of
+*     the memory passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Stc is to be initialised.
+*        This must be of sufficient size to accommodate the Stc data
+*        (sizeof(Stc)) plus any data used by the derived class. If a
+*        value of NULL is given, this function will allocate the memory itself
+*        using the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Stc (plus derived class
+*        data). This will be used to allocate memory if a value of NULL is
+*        given for the "mem" parameter. This value is also stored in the
+*        Stc structure, so a valid value must be supplied even if not
+*        required for allocating memory.
+*     init
+*        A logical flag indicating if the Stc's virtual function table
+*        is to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Stc.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     region
+*        Pointer to the Region represented by the Stc.
+*     ncoords
+*        Number of KeyMap pointers supplied in "coords". Can be zero.
+*        Ignored if "coords" is NULL.
+*     coords
+*        Pointer to an array of "ncoords" KeyMap pointers, or NULL if
+*        "ncoords" is zero. Each KeyMap defines defines a single <AstroCoords>
+*        element, and should have elements with keys given by constants
+*        AST__STCNAME, AST__STCVALUE, AST__STCERROR, AST__STCRES, AST__STCSIZE,
+*        AST__STCPIXSZ. Any of these elements may be omitted, but no other
+*        elements should be included. If supplied, the AST__STCNAME element
+*        should be a vector of character string pointers holding the "Name"
+*        item for each axis. Any other supplied elements should be scalar
+*        elements, each  holding a pointer to a Region describing the
+*        associated item of ancillary information (error, resolution, size,
+*        pixel size or value). These Regions should describe a volume within
+*        the coordinate system represented by "region".
+
+*  Returned Value:
+*     A pointer to the new Stc.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstMapping *frm;              /* Current Frame in supplied Stc */
+   AstMapping *map;              /* Base -> Current Mapping in supplied Stc */
+   AstRegion *reg;               /* Copy of supplied Region */
+   AstStc *new;                  /* Pointer to new Stc */
+   int i;                        /* AstroCoords index */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitStcVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* If the supplied Region is an Stc, create a new Region by mapping the
+   encapsulated Region within the supplied Stc into the current Frame of the
+   Stc. */
+   if( astIsAStc( region ) ) {
+      map = astGetMapping( region->frameset, AST__BASE, AST__CURRENT );
+      frm = astGetFrame( region->frameset, AST__CURRENT );
+      reg = astMapRegion( ((AstStc *) region)->region, map, frm );
+      frm = astAnnul( frm );
+      map = astAnnul( map );
+
+/* Otherwise, just take a copy of the supplied Region. */
+   } else {
+      reg = astCopy( region );
+   }
+
+/* Initialise a Region structure (the parent class) as the first component
+   within the Stc structure, allocating memory if necessary. A NULL
+   PointSet is suppled as the encapsulated Region will perform the function
+   of defining the Region shape. The base Frame of the FrameSet in the
+   parent Region structure will be the same as the current Frames of the
+   FrameSets in the two encapsulated Region. */
+   if ( astOK ) {
+      new = (AstStc *) astInitRegion( mem, size, 0, (AstRegionVtab *) vtab,
+                                      name, reg, NULL, NULL );
+
+/* Initialise the Stc data. */
+/* --------------------------- */
+/* Store a pointer to the encapsulated Region. */
+      new->region = astClone( reg );
+
+/* No AstroCoords info as yet. */
+      new->ncoord = 0;
+      new->coord = NULL;
+
+/* Transfer attributes from the encapsulated region to the parent region. */
+     astRegOverlay( new, reg, 1 );
+     if( astTestIdent( reg ) ) astSetIdent( new, astGetIdent( reg ) );
+
+/* If the base->current Mapping in the FrameSet within the encapsulated Region
+   is a UnitMap, then the FrameSet does not need to be included in the
+   Dump of the new Stc. Set the RegionFS attribute of the encapsulated
+   Region to zero to flag this. Note, we do this after the previous class
+   to astRegOverlay because we do not want this zero value for RegionFS to
+   be copied into the new Stc object. */
+      astSetRegionFS( reg, 0 );
+
+/* For each supplied AstroCoords, create a new KeyMap holding Regions
+   representing the various elements of the AstroCoords, and store the
+   new KeyMap in the Stc structure. */
+      if( coords && ncoords > 0 ) {
+         new->ncoord = ncoords;
+         new->coord = astMalloc( sizeof( AstKeyMap *)*(size_t) ncoords );
+         if( new->coord ) {
+            for( i = 0; i < ncoords; i++ ) {
+               new->coord[ i ] = MakeAstroCoordsKeyMap( reg, coords[ i ],
+                                                        name, status );
+            }
+         }
+      }
+
+/* If an error occurred, clean up deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Free resources */
+   reg = astAnnul( reg );
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstStc *astLoadStc_( void *mem, size_t size, AstStcVtab *vtab,
+                     const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadStc
+
+*  Purpose:
+*     Load a Stc.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stc.h"
+*     AstStc *astLoadStc( void *mem, size_t size, AstStcVtab *vtab,
+*                         const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     Stc loader.
+
+*  Description:
+*     This function is provided to load a new Stc using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Stc structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Stc at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Stc is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Stc data (sizeof(Stc)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Stc (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Stc structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstStc) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Stc. If this is NULL, a pointer to
+*        the (static) virtual function table for the Stc class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Stc" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Stc.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstFrame *f1;                 /* Base Frame in parent Region */
+   AstObject *obj;               /* Pointer to Object retrieved from KeyMap */
+   AstRegion *creg;              /* Pointer to encapsulated Region */
+   AstStc *new;                  /* Pointer to the new Stc */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword string */
+   int ico;                      /* Loop counter for AstroCoords */
+   int ikey;                     /* Index of KeyMap */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Stc. In this case the
+   Stc belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstStc );
+      vtab = &class_vtab;
+      name = "Stc";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitStcVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Stc. */
+   new = astLoadRegion( mem, size, (AstRegionVtab *) vtab, name,
+                        channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Stc" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Encapsulated Region. */
+/* -------------------- */
+      new->region = astReadObject( channel, "region", NULL );
+
+/* Get a pointer to the base Frame in the FrameSet encapsulated by the
+   parent Region structure. */
+      f1 = astGetFrame( ((AstRegion *) new)->frameset, AST__BASE );
+
+/* If the encapsulated Region has a dummy FrameSet rather than the correct
+   FrameSet, the correct FrameSet will have copies of the base Frame of the
+   new Stc as both its current and base Frames, connected by a UnitMap (this
+   is equivalent to a FrameSet containing a single Frame). However if the new
+   Stc being loaded has itself got a dummy FrameSet, then we do not do this
+   since we do not yet know what the correct FrameSet is. In this case we
+   wait until the parent Region invokes the astSetRegFS method on the new
+   Stc. */
+      if( !astRegDummyFS( new ) ) {
+         creg = new->region;
+         if( astRegDummyFS( creg ) ) astSetRegFS( creg, f1 );
+      }
+
+/* AstroCoords info */
+/* ---------------- */
+/* The number of AstroCoords described in the new Stc. */
+      new->ncoord = astReadInt( channel, "ncoord", 0 );
+      if( new->ncoord < 0 ) new->ncoord = 0;
+
+/* Read back each KeyMap describing these AstroCoords. */
+      new->coord = astMalloc( sizeof( AstKeyMap *) * (size_t) new->ncoord );
+      for( ico = 1; ico <= new->ncoord; ico++ ) {
+         (void) sprintf( key, "coord%d", ico );
+         new->coord[ ico - 1 ] = astReadObject( channel, key, NULL );
+
+/* Ensure the Regions within the KeyMap do not have dummy FrameSets. */
+         if( new->coord[ ico - 1 ] && !astRegDummyFS( new ) ) {
+            for( ikey = 0; ikey < NREG; ikey++ ) {
+               if( astMapGet0A( new->coord[ ico - 1 ], regkey[ ikey ], &obj ) ){
+                  creg = (AstRegion *) obj;
+                  if( astRegDummyFS( creg ) ) {
+                     astSetRegFS( creg, f1 );
+                     astMapPut0A( new->coord[ ico - 1 ], regkey[ ikey ], creg,
+                                  regcom[ ikey ] );
+                  }
+                  creg = astAnnul( creg );
+               }
+            }
+         }
+      }
+
+/* Free resources */
+      f1 = astAnnul( f1 );
+
+/* If an error occurred, clean up by deleting the new Stc. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Stc pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+const char *astGetRegionClass_( AstStc *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Stc,GetRegionClass))( this, status );
+}
+
+AstRegion *astGetStcRegion_( AstStc *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Stc,GetStcRegion))( this, status );
+}
+
+AstKeyMap *astGetStcCoord_( AstStc *this, int icoord, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Stc,GetStcCoord))( this, icoord, status );
+}
+
+int astGetStcNCoord_( AstStc *this, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Stc,GetStcNCoord))( this, status );
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ast/stc.h b/ast/stc.h
new file mode 100644
index 0000000..1fe0b1b
--- /dev/null
+++ b/ast/stc.h
@@ -0,0 +1,240 @@
+#if !defined( STC_INCLUDED ) /* Include this file only once */
+#define STC_INCLUDED
+/*
+*+
+*  Name:
+*     stc.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Stc class.
+
+*  Invocation:
+*     #include "stc.h"
+
+*  Description:
+*     This include file defines the interface to the Stc class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The Stc class is an implementation of the IVOA STC class which forms
+*     part of the IVOA Space-Time Coordinate Metadata system. See:
+*
+*     http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Inheritance:
+*     The Stc class inherits from the Region class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     23-NOV-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "region.h"              /* Coordinate regions (parent class) */
+#include "keymap.h"              /* Lists of value/key pairs */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros. */
+/* ======= */
+
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+#define AST__STCNAME  "Name"
+#define AST__STCVALUE "Value"
+#define AST__STCERROR "Error"
+#define AST__STCRES   "Resolution"
+#define AST__STCSIZE  "Size"
+#define AST__STCPIXSZ "PixSize"
+
+/* Type Definitions. */
+/* ================= */
+/* Stc structure. */
+/* ------------------ */
+
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstStc {
+
+/* Attributes inherited from the parent class. */
+   AstRegion parent_region;      /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstRegion *region;            /* Encapsulated Region */
+   AstKeyMap **coord;            /* STC AstroCoords info */
+   int ncoord;                   /* Number of AstroCoords in "coords" */
+} AstStc;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstStcVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstRegionVtab region_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   const char *(* GetRegionClass)( AstStc *, int * );
+   AstRegion *(* GetStcRegion)( AstStc *, int * );
+   AstKeyMap *(* GetStcCoord)( AstStc *, int, int * );
+   int (* GetStcNCoord)( AstStc *, int * );
+
+} AstStcVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstStcGlobals {
+   AstStcVtab Class_Vtab;
+   int Class_Init;
+} AstStcGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitStcGlobals_( AstStcGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Stc)          /* Check class membership */
+astPROTO_ISA(Stc)            /* Test class membership */
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstStc *astInitStc_( void *, size_t, int, AstStcVtab *, const char *,
+                     AstRegion *, int, AstKeyMap **, int * );
+
+/* Vtab initialiser. */
+void astInitStcVtab_( AstStcVtab *, const char *, int * );
+
+/* Loader. */
+AstStc *astLoadStc_( void *, size_t, AstStcVtab *,
+                     const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+AstRegion *astGetStcRegion_( AstStc *, int * );
+AstKeyMap *astGetStcCoord_( AstStc *, int, int * );
+int astGetStcNCoord_( AstStc *, int * );
+
+# if defined(astCLASS)           /* Protected */
+const char *astGetRegionClass_( AstStc *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckStc(this) astINVOKE_CHECK(Stc,this,0)
+#define astVerifyStc(this) astINVOKE_CHECK(Stc,this,1)
+
+/* Test class membership. */
+#define astIsAStc(this) astINVOKE_ISA(Stc,this)
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitStc(mem,size,init,vtab,name,reg,ncoords,coords) \
+astINVOKE(O,astInitStc_(mem,size,init,vtab,name,reg,ncoords,coords,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitStcVtab(vtab,name) astINVOKE(V,astInitStcVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadStc(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadStc_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckStc to validate Stc pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#define astGetStcRegion(this) astINVOKE(O,astGetStcRegion_(astCheckStc(this),STATUS_PTR))
+#define astGetStcCoord(this,icoord) astINVOKE(O,astGetStcCoord_(astCheckStc(this),icoord,STATUS_PTR))
+#define astGetStcNCoord(this) astINVOKE(V,astGetStcNCoord_(astCheckStc(this),STATUS_PTR))
+#if defined(astCLASS)            /* Protected */
+#define astGetRegionClass(this) astINVOKE(V,astGetRegionClass_(astCheckStc(this),STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/stccatalogentrylocation.c b/ast/stccatalogentrylocation.c
new file mode 100644
index 0000000..a1ea16f
--- /dev/null
+++ b/ast/stccatalogentrylocation.c
@@ -0,0 +1,804 @@
+/*
+*class++
+*  Name:
+*     StcCatalogEntryLocation
+
+*  Purpose:
+*     Correspond to the IVOA STCCatalogEntryLocation class.
+
+*  Constructor Function:
+c     astStcCatalogEntryLocation
+f     AST_STCCATALOGENTRYLOCATION
+
+*  Description:
+*     The StcCatalogEntryLocation class is a sub-class of Stc used to describe
+*     the coverage of the datasets contained in some VO resource.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Inheritance:
+*     The StcCatalogEntryLocation class inherits from the Stc class.
+
+*  Attributes:
+*     The StcCatalogEntryLocation class does not define any new attributes beyond
+*     those which are applicable to all Stcs.
+
+*  Functions:
+c     The StcCatalogEntryLocation class does not define any new functions beyond those
+f     The StcCatalogEntryLocation class does not define any new routines beyond those
+*     which are applicable to all Stcs.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     26-NOV-2004 (DSB):
+*        Original version.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS StcCatalogEntryLocation
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "stc.h"                 /* Coordinate stcs (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "region.h"              /* Regions within coordinate systems */
+#include "stccatalogentrylocation.h"  /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(StcCatalogEntryLocation)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(StcCatalogEntryLocation,Class_Init)
+#define class_vtab astGLOBAL(StcCatalogEntryLocation,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstStcCatalogEntryLocationVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstStcCatalogEntryLocation *astStcCatalogEntryLocationId_( void *, int, AstKeyMap **, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static void Dump( AstObject *, AstChannel *, int * );
+
+/* Member functions. */
+/* ================= */
+
+void astInitStcCatalogEntryLocationVtab_(  AstStcCatalogEntryLocationVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcCatalogEntryLocationVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a StcCatalogEntryLocation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stccatalogentrylocation.h"
+*     void astInitStcCatalogEntryLocationVtab( AstStcCatalogEntryLocationVtab *vtab, const char *name )
+
+*  Class Membership:
+*     StcCatalogEntryLocation vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the StcCatalogEntryLocation class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstStcVtab *stc;        /* Pointer to Stc component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitStcVtab( (AstStcVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAStcCatalogEntryLocation) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstStcVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   mapping = (AstMappingVtab *) vtab;
+   stc = (AstStcVtab *) vtab;
+
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDump( vtab, Dump, "StcCatalogEntryLocation", "Resource coverage" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+/* None */
+
+/* Destructor. */
+/* ----------- */
+/* None */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for StcCatalogEntryLocation objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the StcCatalogEntryLocation class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the StcCatalogEntryLocation whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstStcCatalogEntryLocation *this;                 /* Pointer to the StcCatalogEntryLocation structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the StcCatalogEntryLocation structure. */
+   this = (AstStcCatalogEntryLocation *) this_object;
+
+/* Write out values representing the instance variables for the
+   StcCatalogEntryLocation class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* There are no values to write, so return without further action. */
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAStcCatalogEntryLocation and astCheckStcCatalogEntryLocation functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(StcCatalogEntryLocation,Stc)
+astMAKE_CHECK(StcCatalogEntryLocation)
+
+
+AstStcCatalogEntryLocation *astStcCatalogEntryLocation_( void *region_void, int ncoords,
+                               AstKeyMap **coords, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astStcCatalogEntryLocation
+f     AST_STCCATALOGENTRYLOCATION
+
+*  Purpose:
+*     Create a StcCatalogEntryLocation.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "stccatalogentrylocation.h"
+c     AstStcCatalogEntryLocation *astStcCatalogEntryLocation( AstRegion *region,
+c                  int ncoords, AstKeyMap *coords[], const char *options, ... )
+f     RESULT = AST_STCCATALOGENTRYLOCATION( REGION, NCOORDS, COORDS, OPTIONS, STATUS )
+
+*  Class Membership:
+*     StcCatalogEntryLocation constructor.
+
+*  Description:
+*     This function creates a new StcCatalogEntryLocation and optionally initialises its
+*     attributes.
+*
+*     The StcCatalogEntryLocation class is a sub-class of Stc used to describe
+*     the coverage of the datasets contained in some VO resource.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Parameters:
+c     region
+f     REGION = INTEGER (Given)
+*        Pointer to the encapsulated Region.
+c     ncoords
+f     NCOORDS = INTEGER (Given)
+c        The length of the "coords" array. Supply zero if "coords" is NULL.
+f        The length of the COORDS array. Supply zero if COORDS should be
+f        ignored.
+c     coords
+f     COORDS( NCOORDS ) = INTEGER (Given)
+c        Pointer to an array holding "ncoords" AstKeyMap pointers (if "ncoords"
+f        An array holding NCOORDS AstKeyMap pointers (if NCOORDS
+*        is zero, the supplied value is ignored). Each supplied KeyMap
+*        describes the contents of a single STC <AstroCoords> element, and
+*        should have elements with keys given by constants AST__STCNAME,
+*        AST__STCVALUE, AST__STCERROR, AST__STCRES, AST__STCSIZE,
+*        AST__STCPIXSZ. Any of these elements may be omitted, but no other
+*        elements should be included. If supplied, the AST__STCNAME element
+*        should be a vector of character string pointers holding the "Name"
+*        item for each axis in the coordinate system represented by
+c        "region".
+f        REGION.
+*        Any other supplied elements should be scalar elements, each  holding
+*        a pointer to a Region describing the associated item of ancillary
+*        information (error, resolution, size, pixel size or value). These
+*        Regions should describe a volume within the coordinate system
+c        represented by "region".
+f        represented by REGION.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new StcCatalogEntryLocation. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new StcCatalogEntryLocation. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astStcCatalogEntryLocation()
+f     AST_STCCATALOGENTRYLOCATION = INTEGER
+*        A pointer to the new StcCatalogEntryLocation.
+
+*  Notes:
+*     - A deep copy is taken of the supplied Region. This means that
+*     any subsequent changes made to the encapsulated Region using the
+*     supplied pointer will have no effect on the Stc.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstRegion *region;            /* Pointer to Region structure */
+   AstStcCatalogEntryLocation *new;   /* Pointer to new StcCatalogEntryLocation */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the Region structure provided. */
+   region = astCheckRegion( region_void );
+
+/* Initialise the StcCatalogEntryLocation, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitStcCatalogEntryLocation( NULL, sizeof( AstStcCatalogEntryLocation ), !class_init,
+                                    &class_vtab, "StcCatalogEntryLocation", region,
+                                    ncoords, coords );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new StcCatalogEntryLocation's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new StcCatalogEntryLocation. */
+   return new;
+}
+
+AstStcCatalogEntryLocation *astStcCatalogEntryLocationId_( void *region_void, int ncoords,
+                               AstKeyMap **coords, const char *options, ... ) {
+/*
+*  Name:
+*     astStcCatalogEntryLocationId_
+
+*  Purpose:
+*     Create a StcCatalogEntryLocation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stccatalogentrylocation.h"
+*     AstStcCatalogEntryLocation *astStcCatalogEntryLocationId( AstRegion *region,
+*                  int ncoords, AstKeyMap *coords[], const char *options, ..., int *status )
+
+*  Class Membership:
+*     StcCatalogEntryLocation constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astStcCatalogEntryLocation constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astStcCatalogEntryLocation_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astStcCatalogEntryLocation_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astStcCatalogEntryLocation_.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The ID value associated with the new StcCatalogEntryLocation.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstKeyMap **keymaps;            /* Pointer to array of KeyMap pointers */
+   AstRegion *region;              /* Pointer to Region structure */
+   AstStcCatalogEntryLocation *new;/* Pointer to new StcCatalogEntryLocation */
+   int icoord;                     /* Keymap index */
+   va_list args;                   /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Region pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Region. */
+   region = astVerifyRegion( astMakePointer( region_void ) );
+
+/* Obtain pointer from the supplied KeyMap ID's and validate the
+   pointers to ensure it identifies a valid KeyMap. */
+   keymaps = astMalloc( sizeof( AstKeyMap * )*(size_t) ncoords );
+   if( keymaps ) {
+      for( icoord = 0; icoord < ncoords; icoord++ ) {
+         keymaps[ icoord ] = astVerifyKeyMap( astMakePointer( coords[ icoord ] ) );
+      }
+   }
+
+/* Initialise the StcCatalogEntryLocation, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitStcCatalogEntryLocation( NULL, sizeof( AstStcCatalogEntryLocation ), !class_init,
+                                    &class_vtab, "StcCatalogEntryLocation", region,
+                                    ncoords, keymaps );
+
+/* Free resources. */
+   keymaps = astFree( keymaps );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new StcCatalogEntryLocation's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new StcCatalogEntryLocation. */
+   return astMakeId( new );
+}
+
+AstStcCatalogEntryLocation *astInitStcCatalogEntryLocation_( void *mem, size_t size,
+                                    int init, AstStcCatalogEntryLocationVtab *vtab,
+                                    const char *name, AstRegion *region,
+                                    int ncoords, AstKeyMap **coords, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcCatalogEntryLocation
+
+*  Purpose:
+*     Initialise a StcCatalogEntryLocation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stccatalogentrylocation.h"
+*     AstStcCatalogEntryLocation *astInitStcCatalogEntryLocation_( void *mem, size_t size,
+*                                    int init, AstStcCatalogEntryLocationVtab *vtab,
+*                                    const char *name, AstRegion *region,
+*                                    int ncoords, AstKeyMap **coords )
+
+*  Class Membership:
+*     StcCatalogEntryLocation initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new StcCatalogEntryLocation object. It allocates memory (if necessary) to accommodate
+*     the StcCatalogEntryLocation plus any additional data associated with the derived class.
+*     It then initialises a StcCatalogEntryLocation structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a StcCatalogEntryLocation at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the StcCatalogEntryLocation is to be initialised.
+*        This must be of sufficient size to accommodate the StcCatalogEntryLocation data
+*        (sizeof(StcCatalogEntryLocation)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the StcCatalogEntryLocation (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the StcCatalogEntryLocation
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the StcCatalogEntryLocation's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new StcCatalogEntryLocation.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     region
+*        A pointer to the Region encapsulated by the StcCatalogEntryLocation.
+*     ncoords
+*        Number of KeyMap pointers supplied in "coords". Can be zero.
+*        Ignored if "coords" is NULL.
+*     coords
+*        Pointer to an array of "ncoords" KeyMap pointers, or NULL if
+*        "ncoords" is zero. Each KeyMap defines defines a single <AstroCoords>
+*        element, and should have elements with keys given by constants
+*        AST__STCNAME, AST__STCVALUE, AST__STCERROR, AST__STCRES, AST__STCSIZE,
+*        AST__STCPIXSZ. These elements hold values for the corresponding
+*        components of the STC AstroCoords element. Any of these elements may
+*        be omitted, but no other elements should be included. All supplied
+*        elements should be vector elements, with vector length less than or
+*        equal to the number of axes in the supplied Region. The data type of
+*        all elements should be "double", except for AST__STCNAME which should
+*        be "character string". If no value is available for a given axis, then
+*        AST__BAD (or NULL for the AST__STCNAME element) should be stored in
+*        the vector at the index corresponding to the axis (trailing axes
+*        can be omitted completely from the KeyMap).
+
+*  Returned Value:
+*     A pointer to the new StcCatalogEntryLocation.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstStcCatalogEntryLocation *new;       /* Pointer to new StcCatalogEntryLocation */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitStcCatalogEntryLocationVtab( vtab, name );
+
+/* Initialise a Stc structure (the parent class) as the first component
+   within the StcCatalogEntryLocation structure, allocating memory if necessary. */
+   new = (AstStcCatalogEntryLocation *) astInitStc( mem, size, 0, (AstStcVtab *) vtab,
+                                               name, region, ncoords, coords );
+
+/* If an error occurred, clean up by deleting the new StcCatalogEntryLocation. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return a pointer to the new StcCatalogEntryLocation. */
+   return new;
+}
+
+AstStcCatalogEntryLocation *astLoadStcCatalogEntryLocation_( void *mem, size_t size, AstStcCatalogEntryLocationVtab *vtab,
+                                                   const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadStcCatalogEntryLocation
+
+*  Purpose:
+*     Load a StcCatalogEntryLocation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stccatalogentrylocation.h"
+*     AstStcCatalogEntryLocation *astLoadStcCatalogEntryLocation( void *mem, size_t size, AstStcCatalogEntryLocationVtab *vtab,
+*                                       const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     StcCatalogEntryLocation loader.
+
+*  Description:
+*     This function is provided to load a new StcCatalogEntryLocation using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     StcCatalogEntryLocation structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a StcCatalogEntryLocation at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the StcCatalogEntryLocation is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        StcCatalogEntryLocation data (sizeof(StcCatalogEntryLocation)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the StcCatalogEntryLocation (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the StcCatalogEntryLocation structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstStcCatalogEntryLocation) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new StcCatalogEntryLocation. If this is NULL, a pointer
+*        to the (static) virtual function table for the StcCatalogEntryLocation class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "StcCatalogEntryLocation" is used instead.
+
+*  Returned Value:
+*     A pointer to the new StcCatalogEntryLocation.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstStcCatalogEntryLocation *new;              /* Pointer to the new StcCatalogEntryLocation */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this StcCatalogEntryLocation. In this case the
+   StcCatalogEntryLocation belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstStcCatalogEntryLocation );
+      vtab = &class_vtab;
+      name = "StcCatalogEntryLocation";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitStcCatalogEntryLocationVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built StcCatalogEntryLocation. */
+   new = astLoadStc( mem, size, (AstStcVtab *) vtab, name, channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "StcCatalogEntryLocation" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* There are no values to read. */
+/* ---------------------------- */
+
+/* If an error occurred, clean up by deleting the new StcCatalogEntryLocation. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new StcCatalogEntryLocation pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
+
diff --git a/ast/stccatalogentrylocation.h b/ast/stccatalogentrylocation.h
new file mode 100644
index 0000000..f999e6a
--- /dev/null
+++ b/ast/stccatalogentrylocation.h
@@ -0,0 +1,223 @@
+#if !defined( STCCATALOGENTRYLOCATION_INCLUDED ) /* Include this file only once */
+#define STCCATALOGENTRYLOCATION_INCLUDED
+/*
+*+
+*  Name:
+*     stccatalogentrylocation.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the StcCatalogEntryLocation class.
+
+*  Invocation:
+*     #include "stccatalogentrylocation.h"
+
+*  Description:
+*     This include file defines the interface to the StcCatalogEntryLocation class
+*     and provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The StcCatalogEntryLocation class is a sub-class of Stc used to describe
+*     the coverage of the datasets contained in some VO resource.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Inheritance:
+*     The StcCatalogEntryLocation class inherits from the Stc class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     26-NOV-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "stc.h"                 /* Coordinate stcs (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* StcCatalogEntryLocation structure. */
+/* ----------------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstStcCatalogEntryLocation {
+
+/* Attributes inherited from the parent class. */
+   AstStc stc;             /* Parent class structure */
+
+} AstStcCatalogEntryLocation;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstStcCatalogEntryLocationVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstStcVtab stc_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+} AstStcCatalogEntryLocationVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstStcCatalogEntryLocationGlobals {
+   AstStcCatalogEntryLocationVtab Class_Vtab;
+   int Class_Init;
+} AstStcCatalogEntryLocationGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitStcCatalogEntryLocationGlobals_( AstStcCatalogEntryLocationGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(StcCatalogEntryLocation)          /* Check class membership */
+astPROTO_ISA(StcCatalogEntryLocation)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstStcCatalogEntryLocation *astStcCatalogEntryLocation_( void *, int, AstKeyMap **, const char *, int *, ...);
+#else
+AstStcCatalogEntryLocation *astStcCatalogEntryLocationId_( void *, int, AstKeyMap **, const char *, ... )__attribute__((format(printf,4,5)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstStcCatalogEntryLocation *astInitStcCatalogEntryLocation_( void *, size_t, int, AstStcCatalogEntryLocationVtab *, const char *, AstRegion *, int, AstKeyMap **, int * );
+
+/* Vtab initialiser. */
+void astInitStcCatalogEntryLocationVtab_( AstStcCatalogEntryLocationVtab *, const char *, int * );
+
+/* Loader. */
+AstStcCatalogEntryLocation *astLoadStcCatalogEntryLocation_( void *, size_t, AstStcCatalogEntryLocationVtab *,
+                                   const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckStcCatalogEntryLocation(this) astINVOKE_CHECK(StcCatalogEntryLocation,this,0)
+#define astVerifyStcCatalogEntryLocation(this) astINVOKE_CHECK(StcCatalogEntryLocation,this,1)
+
+/* Test class membership. */
+#define astIsAStcCatalogEntryLocation(this) astINVOKE_ISA(StcCatalogEntryLocation,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astStcCatalogEntryLocation astINVOKE(F,astStcCatalogEntryLocation_)
+#else
+#define astStcCatalogEntryLocation astINVOKE(F,astStcCatalogEntryLocationId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitStcCatalogEntryLocation(mem,size,init,vtab,name,region,ncoords,coords) \
+astINVOKE(O,astInitStcCatalogEntryLocation_(mem,size,init,vtab,name,region,ncoords,coords,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitStcCatalogEntryLocationVtab(vtab,name) astINVOKE(V,astInitStcCatalogEntryLocationVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadStcCatalogEntryLocation(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadStcCatalogEntryLocation_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckStcCatalogEntryLocation to validate StcCatalogEntryLocation pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/stcobsdatalocation.c b/ast/stcobsdatalocation.c
new file mode 100644
index 0000000..777d29e
--- /dev/null
+++ b/ast/stcobsdatalocation.c
@@ -0,0 +1,1051 @@
+/*
+*class++
+*  Name:
+*     StcObsDataLocation
+
+*  Purpose:
+*     Correspond to the IVOA ObsDataLocation class.
+
+*  Constructor Function:
+c     astStcObsDataLocation
+f     AST_STCOBSDATALOCATION
+
+*  Description:
+*     The StcObsDataLocation class is a sub-class of Stc used to describe
+*     the coordinate space occupied by a particular observational dataset.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+*
+*     An STC ObsDataLocation element specifies the extent of the
+*     observation within a specified coordinate system, and also specifies
+*     the observatory location within a second coordinate system.
+*
+*     The AST StcObsDataLocation class inherits from Stc, and therefore
+*     an StcObsDataLocation can be used directly as an Stc. When used
+*     in this way, the StcObsDataLocation describes the location of the
+*     observation (not the observatory).
+*
+*     Eventually, this class will have a method for returning an Stc
+*     describing the observatory location. However, AST currently does not
+*     include any classes of Frame for describing terrestrial or solar
+*     system positions. Therefore, the provision for returning observatory
+*     location as an Stc is not yet available. However, for terrestrial
+*     observations, the position of the observatory can still be recorded
+*     using the ObsLon and ObsLat attributes of the Frame encapsulated
+*     within the Stc representing the observation location (this assumes
+*     the observatory is located at sea level).
+
+*  Inheritance:
+*     The StcObsDataLocation class inherits from the Stc class.
+
+*  Attributes:
+*     The StcObsDataLocation class does not define any new attributes beyond
+*     those which are applicable to all Stcs.
+
+*  Functions:
+c     The StcObsDataLocation class does not define any new functions beyond those
+f     The StcObsDataLocation class does not define any new routines beyond those
+*     which are applicable to all Stcs.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     25-APR-2005 (DSB):
+*        Original version.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS StcObsDataLocation
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "stc.h"                 /* Coordinate stcs (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "region.h"              /* Regions within coordinate systems */
+#include "pointlist.h"           /* Points within coordinate systems */
+#include "stcobsdatalocation.h"  /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(StcObsDataLocation)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(StcObsDataLocation,Class_Init)
+#define class_vtab astGLOBAL(StcObsDataLocation,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstStcObsDataLocationVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstStcObsDataLocation *astStcObsDataLocationId_( void *, int, AstKeyMap **, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void StcSetObs( AstStcObsDataLocation *, AstPointList *, int * );
+
+static int GetObjSize( AstObject *, int * );
+/* Member functions. */
+/* ================= */
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcobsdatalocation.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     StcObsDataLocation member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied StcObsDataLocation,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the StcObsDataLocation.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstStcObsDataLocation *this;         /* Pointer to StcObsDataLocation structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the StcObsDataLocation structure. */
+   this = (AstStcObsDataLocation *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astGetObjSize( this->obs );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+
+void astInitStcObsDataLocationVtab_(  AstStcObsDataLocationVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcObsDataLocationVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a StcObsDataLocation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcobsdatalocation.h"
+*     void astInitStcObsDataLocationVtab( AstStcObsDataLocationVtab *vtab, const char *name )
+
+*  Class Membership:
+*     StcObsDataLocation vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the StcObsDataLocation class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstStcVtab *stc;        /* Pointer to Stc component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitStcVtab( (AstStcVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAStcObsDataLocation) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstStcVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+   stc = (AstStcVtab *) vtab;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   vtab->StcSetObs = StcSetObs;
+
+/* Declare the copy constructor, destructor and class dump functions. */
+   astSetDump( vtab, Dump, "StcObsDataLocation", "Observation coverage" );
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static void StcSetObs( AstStcObsDataLocation *this, AstPointList *obs, int *status ) {
+/*
+*+
+*  Name:
+*     astStcSetObs
+
+*  Purpose:
+*     Set the observatory position within an StcObsDataLocation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "region.h"
+*     void astStcSetObs( AstStcObsDataLocation *this, AstPointList *obs )
+
+*  Class Membership:
+*     StcObsDataLocation virtual function
+
+*  Description:
+*     This function stores a clone of the supplied PointList pointer
+*     within the supplied StcObsDataLocation, first annulling any
+*     pointer already stored in the StcObsDataLocation.
+
+*  Parameters:
+*     this
+*        Pointer to the StcObsDataLocation.
+*     obs
+*        Pointer to a PointList defining the observatory position. NULL
+*        may be supplied in which case any existing observatory position
+*        is removed.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Free any existing obseravtory position PointList. */
+   if( this->obs ) this->obs = astAnnul( this->obs );
+
+/* Store any supplied pointer. */
+   if( obs ) this->obs = astClone( obs );
+
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for StcObsDataLocation objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for StcObsDataLocation
+*     objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the component
+*     Regions within the StcObsDataLocation.
+*/
+
+/* Local Variables: */
+   AstStcObsDataLocation *in;   /* Pointer to input StcObsDataLocation */
+   AstStcObsDataLocation *out;  /* Pointer to output StcObsDataLocation */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output StcObsDataLocations. */
+   in = (AstStcObsDataLocation *) objin;
+   out = (AstStcObsDataLocation *) objout;
+
+/* For safety, start by clearing any references to the input component
+   Regions, etc,  from the output StcObsDataLocation. */
+   out->obs = NULL;
+
+/* Make a copy of the Observatory location */
+   if( in->obs ) out->obs = astCopy( in->obs );
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for StcObsDataLocation objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for StcObsDataLocation objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstStcObsDataLocation *this;   /* Pointer to StcObsDataLocation */
+
+/* Obtain a pointer to the StcObsDataLocation structure. */
+   this = (AstStcObsDataLocation *) obj;
+
+/* Annul the pointer to the observatory location Region. */
+   if( this->obs ) this->obs = astAnnul( this->obs );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for StcObsDataLocation objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the StcObsDataLocation class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the StcObsDataLocation whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstStcObsDataLocation *this;                 /* Pointer to the StcObsDataLocation structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the StcObsDataLocation structure. */
+   this = (AstStcObsDataLocation *) this_object;
+
+/* Write out values representing the instance variables for the
+   StcObsDataLocation class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Observatory position. */
+/* --------------------- */
+   astWriteObject( channel, "ObsLoc", 1, 1, this->obs, "Observatory position" );
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAStcObsDataLocation and astCheckStcObsDataLocation functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(StcObsDataLocation,Stc)
+astMAKE_CHECK(StcObsDataLocation)
+
+
+AstStcObsDataLocation *astStcObsDataLocation_( void *region_void, int ncoords,
+                               AstKeyMap **coords, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astStcObsDataLocation
+f     AST_STCOBSDATALOCATION
+
+*  Purpose:
+*     Create a StcObsDataLocation.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "stcobsdatalocation.h"
+c     AstStcObsDataLocation *astStcObsDataLocation( AstRegion *region,
+c                  int ncoords, AstKeyMap *coords[], const char *options, ... )
+f     RESULT = AST_STCOBSDATALOCATION( REGION, NCOORDS, COORDS, OPTIONS, STATUS )
+
+*  Class Membership:
+*     StcObsDataLocation constructor.
+
+*  Description:
+*     This function creates a new StcObsDataLocation and optionally initialises its
+*     attributes.
+*
+*     The StcObsDataLocation class is a sub-class of Stc used to describe
+*     the coverage of the datasets contained in some VO resource.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Parameters:
+c     region
+f     REGION = INTEGER (Given)
+*        Pointer to the encapsulated Region.
+c     ncoords
+f     NCOORDS = INTEGER (Given)
+c        The length of the "coords" array. Supply zero if "coords" is NULL.
+f        The length of the COORDS array. Supply zero if COORDS should be
+f        ignored.
+c     coords
+f     COORDS( NCOORDS ) = INTEGER (Given)
+c        Pointer to an array holding "ncoords" AstKeyMap pointers (if "ncoords"
+f        An array holding NCOORDS AstKeyMap pointers (if NCOORDS
+*        is zero, the supplied value is ignored). Each supplied KeyMap
+*        describes the contents of a single STC <AstroCoords> element, and
+*        should have elements with keys given by constants AST__STCNAME,
+*        AST__STCVALUE, AST__STCERROR, AST__STCRES, AST__STCSIZE,
+*        AST__STCPIXSZ. Any of these elements may be omitted, but no other
+*        elements should be included. If supplied, the AST__STCNAME element
+*        should be a vector of character string pointers holding the "Name"
+*        item for each axis in the coordinate system represented by
+c        "region".
+f        REGION.
+*        Any other supplied elements should be scalar elements, each  holding
+*        a pointer to a Region describing the associated item of ancillary
+*        information (error, resolution, size, pixel size or value). These
+*        Regions should describe a volume within the coordinate system
+c        represented by "region".
+f        represented by REGION.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new StcObsDataLocation. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new StcObsDataLocation. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astStcObsDataLocation()
+f     AST_STCOBSDATALOCATION = INTEGER
+*        A pointer to the new StcObsDataLocation.
+
+*  Notes:
+*     - A deep copy is taken of the supplied Region. This means that
+*     any subsequent changes made to the encapsulated Region using the
+*     supplied pointer will have no effect on the Stc.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstRegion *region;            /* Pointer to Region structure */
+   AstStcObsDataLocation *new;   /* Pointer to new StcObsDataLocation */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the Region structure provided. */
+   region = astCheckRegion( region_void );
+
+/* Initialise the StcObsDataLocation, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitStcObsDataLocation( NULL, sizeof( AstStcObsDataLocation ), !class_init,
+                                    &class_vtab, "StcObsDataLocation", region,
+                                    ncoords, coords );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new StcObsDataLocation's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new StcObsDataLocation. */
+   return new;
+}
+
+AstStcObsDataLocation *astStcObsDataLocationId_( void *region_void, int ncoords,
+                               AstKeyMap **coords, const char *options, ... ) {
+/*
+*  Name:
+*     astStcObsDataLocationId_
+
+*  Purpose:
+*     Create a StcObsDataLocation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcobsdatalocation.h"
+*     AstStcObsDataLocation *astStcObsDataLocationId( AstRegion *region,
+*                  int ncoords, AstKeyMap *coords[], const char *options, ..., int *status )
+
+*  Class Membership:
+*     StcObsDataLocation constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astStcObsDataLocation constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astStcObsDataLocation_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astStcObsDataLocation_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astStcObsDataLocation_.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The ID value associated with the new StcObsDataLocation.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstKeyMap **keymaps;            /* Pointer to array of KeyMap pointers */
+   AstRegion *region;              /* Pointer to Region structure */
+   AstStcObsDataLocation *new;     /* Pointer to new StcObsDataLocation */
+   int icoord;                     /* Keymap index */
+   va_list args;                   /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Region pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Region. */
+   region = astVerifyRegion( astMakePointer( region_void ) );
+
+/* Obtain pointer from the supplied KeyMap ID's and validate the
+   pointers to ensure it identifies a valid KeyMap. */
+   keymaps = astMalloc( sizeof( AstKeyMap * )*(size_t) ncoords );
+   if( keymaps ) {
+      for( icoord = 0; icoord < ncoords; icoord++ ) {
+         keymaps[ icoord ] = astVerifyKeyMap( astMakePointer( coords[ icoord ] ) );
+      }
+   }
+
+/* Initialise the StcObsDataLocation, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitStcObsDataLocation( NULL, sizeof( AstStcObsDataLocation ), !class_init,
+                                    &class_vtab, "StcObsDataLocation", region,
+                                    ncoords, keymaps );
+
+/* Free resources. */
+   keymaps = astFree( keymaps );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new StcObsDataLocation's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new StcObsDataLocation. */
+   return astMakeId( new );
+}
+
+AstStcObsDataLocation *astInitStcObsDataLocation_( void *mem, size_t size,
+                                    int init, AstStcObsDataLocationVtab *vtab,
+                                    const char *name, AstRegion *region,
+                                    int ncoords, AstKeyMap **coords, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcObsDataLocation
+
+*  Purpose:
+*     Initialise a StcObsDataLocation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcobsdatalocation.h"
+*     AstStcObsDataLocation *astInitStcObsDataLocation_( void *mem, size_t size,
+*                                    int init, AstStcObsDataLocationVtab *vtab,
+*                                    const char *name, AstRegion *region,
+*                                    int ncoords, AstKeyMap **coords )
+
+*  Class Membership:
+*     StcObsDataLocation initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new StcObsDataLocation object. It allocates memory (if necessary) to accommodate
+*     the StcObsDataLocation plus any additional data associated with the derived class.
+*     It then initialises a StcObsDataLocation structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a StcObsDataLocation at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the StcObsDataLocation is to be initialised.
+*        This must be of sufficient size to accommodate the StcObsDataLocation data
+*        (sizeof(StcObsDataLocation)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the StcObsDataLocation (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the StcObsDataLocation
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the StcObsDataLocation's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new StcObsDataLocation.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     region
+*        A pointer to the Region encapsulated by the StcObsDataLocation.
+*     ncoords
+*        Number of KeyMap pointers supplied in "coords". Can be zero.
+*        Ignored if "coords" is NULL.
+*     coords
+*        Pointer to an array of "ncoords" KeyMap pointers, or NULL if
+*        "ncoords" is zero. Each KeyMap defines defines a single <AstroCoords>
+*        element, and should have elements with keys given by constants
+*        AST__STCNAME, AST__STCVALUE, AST__STCERROR, AST__STCRES, AST__STCSIZE,
+*        AST__STCPIXSZ. These elements hold values for the corresponding
+*        components of the STC AstroCoords element. Any of these elements may
+*        be omitted, but no other elements should be included. All supplied
+*        elements should be vector elements, with vector length less than or
+*        equal to the number of axes in the supplied Region. The data type of
+*        all elements should be "double", except for AST__STCNAME which should
+*        be "character string". If no value is available for a given axis, then
+*        AST__BAD (or NULL for the AST__STCNAME element) should be stored in
+*        the vector at the index corresponding to the axis (trailing axes
+*        can be omitted completely from the KeyMap).
+
+*  Returned Value:
+*     A pointer to the new StcObsDataLocation.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstStcObsDataLocation *new;       /* Pointer to new StcObsDataLocation */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitStcObsDataLocationVtab( vtab, name );
+
+/* Initialise a Stc structure (the parent class) as the first component
+   within the StcObsDataLocation structure, allocating memory if necessary. */
+   new = (AstStcObsDataLocation *) astInitStc( mem, size, 0, (AstStcVtab *) vtab,
+                                               name, region, ncoords, coords );
+
+/* If succesful, initialise properties of the StcObsDataLocation. */
+   if( new ) {
+      new->obs = NULL;
+   }
+
+/* If an error occurred, clean up by deleting the new StcObsDataLocation. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return a pointer to the new StcObsDataLocation. */
+   return new;
+}
+
+AstStcObsDataLocation *astLoadStcObsDataLocation_( void *mem, size_t size, AstStcObsDataLocationVtab *vtab,
+                                                   const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadStcObsDataLocation
+
+*  Purpose:
+*     Load a StcObsDataLocation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcobsdatalocation.h"
+*     AstStcObsDataLocation *astLoadStcObsDataLocation( void *mem, size_t size, AstStcObsDataLocationVtab *vtab,
+*                                       const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     StcObsDataLocation loader.
+
+*  Description:
+*     This function is provided to load a new StcObsDataLocation using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     StcObsDataLocation structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a StcObsDataLocation at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the StcObsDataLocation is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        StcObsDataLocation data (sizeof(StcObsDataLocation)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the StcObsDataLocation (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the StcObsDataLocation structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstStcObsDataLocation) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new StcObsDataLocation. If this is NULL, a pointer
+*        to the (static) virtual function table for the StcObsDataLocation class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "StcObsDataLocation" is used instead.
+
+*  Returned Value:
+*     A pointer to the new StcObsDataLocation.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstStcObsDataLocation *new;              /* Pointer to the new StcObsDataLocation */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this StcObsDataLocation. In this case the
+   StcObsDataLocation belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstStcObsDataLocation );
+      vtab = &class_vtab;
+      name = "StcObsDataLocation";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitStcObsDataLocationVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built StcObsDataLocation. */
+   new = astLoadStc( mem, size, (AstStcVtab *) vtab, name, channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "StcObsDataLocation" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Observatory position. */
+/* --------------------- */
+      new->obs = astReadObject( channel, "obsloc", NULL );
+
+/* If an error occurred, clean up by deleting the new StcObsDataLocation. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new StcObsDataLocation pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+void astStcSetObs_( AstStcObsDataLocation *this, AstPointList *obs, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,StcObsDataLocation,StcSetObs))( this, obs, status );
+}
+
+
+
+
diff --git a/ast/stcobsdatalocation.h b/ast/stcobsdatalocation.h
new file mode 100644
index 0000000..ad3b793
--- /dev/null
+++ b/ast/stcobsdatalocation.h
@@ -0,0 +1,236 @@
+#if !defined( STCOBSDATALOCATION_INCLUDED ) /* Include this file only once */
+#define STCOBSDATALOCATION_INCLUDED
+/*
+*+
+*  Name:
+*     stcobsdatalocation.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the StcObsDataLocation class.
+
+*  Invocation:
+*     #include "stcobsdatalocation.h"
+
+*  Description:
+*     This include file defines the interface to the StcObsDataLocation class
+*     and provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The StcObsDataLocation class is a sub-class of Stc used to describe
+*     the an observation contained in some VO resource.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Inheritance:
+*     The StcObsDataLocation class inherits from the Stc class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     25-APR-2005 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "stc.h"                 /* Coordinate stcs (parent class) */
+#include "pointlist.h"           /* Points within coordinate systems */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* StcObsDataLocation structure. */
+/* ----------------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstStcObsDataLocation {
+
+/* Attributes inherited from the parent class. */
+   AstStc stc;             /* Parent class structure */
+
+/* Attributes specific to the StcObsDataLOcation class. */
+   AstPointList *obs;      /* Observatory position */
+
+} AstStcObsDataLocation;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstStcObsDataLocationVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstStcVtab stc_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   void (* StcSetObs)( AstStcObsDataLocation *, AstPointList *, int * );
+
+} AstStcObsDataLocationVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstStcObsDataLocationGlobals {
+   AstStcObsDataLocationVtab Class_Vtab;
+   int Class_Init;
+} AstStcObsDataLocationGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitStcObsDataLocationGlobals_( AstStcObsDataLocationGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(StcObsDataLocation)          /* Check class membership */
+astPROTO_ISA(StcObsDataLocation)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstStcObsDataLocation *astStcObsDataLocation_( void *, int, AstKeyMap **, const char *, int *, ...);
+#else
+AstStcObsDataLocation *astStcObsDataLocationId_( void *, int, AstKeyMap **, const char *, ... )__attribute__((format(printf,4,5)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstStcObsDataLocation *astInitStcObsDataLocation_( void *, size_t, int, AstStcObsDataLocationVtab *, const char *, AstRegion *, int, AstKeyMap **, int * );
+
+/* Vtab initialiser. */
+void astInitStcObsDataLocationVtab_( AstStcObsDataLocationVtab *, const char *, int * );
+
+/* Loader. */
+AstStcObsDataLocation *astLoadStcObsDataLocation_( void *, size_t, AstStcObsDataLocationVtab *,
+                                   const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+
+#if defined(astCLASS)            /* Protected */
+void astStcSetObs_( AstStcObsDataLocation *, AstPointList *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckStcObsDataLocation(this) astINVOKE_CHECK(StcObsDataLocation,this,0)
+#define astVerifyStcObsDataLocation(this) astINVOKE_CHECK(StcObsDataLocation,this,1)
+
+/* Test class membership. */
+#define astIsAStcObsDataLocation(this) astINVOKE_ISA(StcObsDataLocation,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astStcObsDataLocation astINVOKE(F,astStcObsDataLocation_)
+#else
+#define astStcObsDataLocation astINVOKE(F,astStcObsDataLocationId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitStcObsDataLocation(mem,size,init,vtab,name,region,ncoords,coords) \
+astINVOKE(O,astInitStcObsDataLocation_(mem,size,init,vtab,name,region,ncoords,coords,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitStcObsDataLocationVtab(vtab,name) astINVOKE(V,astInitStcObsDataLocationVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadStcObsDataLocation(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadStcObsDataLocation_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckStcObsDataLocation to validate StcObsDataLocation pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astStcSetObs(this,obs) \
+astINVOKE(V,astStcSetObs_(astCheckStcObsDataLocation(this),obs?astCheckPointList(obs):NULL,STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/stcresourceprofile.c b/ast/stcresourceprofile.c
new file mode 100644
index 0000000..4ae6cc0
--- /dev/null
+++ b/ast/stcresourceprofile.c
@@ -0,0 +1,807 @@
+/*
+*class++
+*  Name:
+*     StcResourceProfile
+
+*  Purpose:
+*     Correspond to the IVOA STCResourceProfile class.
+
+*  Constructor Function:
+c     astStcResourceProfile
+f     AST_STCRESOURCEPROFILE
+
+*  Description:
+*     The StcResourceProfile class is a sub-class of Stc used to describe
+*     the coverage of the datasets contained in some VO resource.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Inheritance:
+*     The StcResourceProfile class inherits from the Stc class.
+
+*  Attributes:
+*     The StcResourceProfile class does not define any new attributes beyond
+*     those which are applicable to all Stcs.
+
+*  Functions:
+c     The StcResourceProfile class does not define any new functions beyond those
+f     The StcResourceProfile class does not define any new routines beyond those
+*     which are applicable to all Stcs.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     26-NOV-2004 (DSB):
+*        Original version.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS StcResourceProfile
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "stc.h"                 /* Coordinate stcs (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "region.h"              /* Regions within coordinate systems */
+#include "stcresourceprofile.h"  /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(StcResourceProfile)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(StcResourceProfile,Class_Init)
+#define class_vtab astGLOBAL(StcResourceProfile,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstStcResourceProfileVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstStcResourceProfile *astStcResourceProfileId_( void *, int, AstKeyMap **, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static void Dump( AstObject *, AstChannel *, int * );
+
+/* Member functions. */
+/* ================= */
+
+void astInitStcResourceProfileVtab_(  AstStcResourceProfileVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcResourceProfileVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a StcResourceProfile.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcresourceprofile.h"
+*     void astInitStcResourceProfileVtab( AstStcResourceProfileVtab *vtab, const char *name )
+
+*  Class Membership:
+*     StcResourceProfile vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the StcResourceProfile class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstStcVtab *stc;        /* Pointer to Stc component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitStcVtab( (AstStcVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAStcResourceProfile) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstStcVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   mapping = (AstMappingVtab *) vtab;
+   stc = (AstStcVtab *) vtab;
+
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDump( vtab, Dump, "StcResourceProfile", "Resource coverage" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+/* None */
+
+/* Destructor. */
+/* ----------- */
+/* None */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for StcResourceProfile objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the StcResourceProfile class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the StcResourceProfile whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstStcResourceProfile *this;                 /* Pointer to the StcResourceProfile structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the StcResourceProfile structure. */
+   this = (AstStcResourceProfile *) this_object;
+
+/* Write out values representing the instance variables for the
+   StcResourceProfile class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* There are no values to write, so return without further action. */
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAStcResourceProfile and astCheckStcResourceProfile functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(StcResourceProfile,Stc)
+astMAKE_CHECK(StcResourceProfile)
+
+
+AstStcResourceProfile *astStcResourceProfile_( void *region_void, int ncoords,
+                               AstKeyMap **coords, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astStcResourceProfile
+f     AST_STCRESOURCEPROFILE
+
+*  Purpose:
+*     Create a StcResourceProfile.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "stcresourceprofile.h"
+c     AstStcResourceProfile *astStcResourceProfile( AstRegion *region,
+c                  int ncoords, AstKeyMap *coords[], const char *options, ... )
+f     RESULT = AST_STCRESOURCEPROFILE( REGION, NCOORDS, COORDS, OPTIONS, STATUS )
+
+*  Class Membership:
+*     StcResourceProfile constructor.
+
+*  Description:
+*     This function creates a new StcResourceProfile and optionally initialises its
+*     attributes.
+*
+*     The StcResourceProfile class is a sub-class of Stc used to describe
+*     the coverage of the datasets contained in some VO resource.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Parameters:
+c     region
+f     REGION = INTEGER (Given)
+*        Pointer to the encapsulated Region.
+c     ncoords
+f     NCOORDS = INTEGER (Given)
+c        The length of the "coords" array. Supply zero if "coords" is NULL.
+f        The length of the COORDS array. Supply zero if COORDS should be
+f        ignored.
+c     coords
+f     COORDS( NCOORDS ) = INTEGER (Given)
+c        Pointer to an array holding "ncoords" AstKeyMap pointers (if "ncoords"
+f        An array holding NCOORDS AstKeyMap pointers (if NCOORDS
+*        is zero, the supplied value is ignored). Each supplied KeyMap
+*        describes the contents of a single STC <AstroCoords> element, and
+*        should have elements with keys given by constants AST__STCNAME,
+*        AST__STCVALUE, AST__STCERROR, AST__STCRES, AST__STCSIZE,
+*        AST__STCPIXSZ. Any of these elements may be omitted, but no other
+*        elements should be included. If supplied, the AST__STCNAME element
+*        should be a vector of character string pointers holding the "Name"
+*        item for each axis in the coordinate system represented by
+c        "region".
+f        REGION.
+*        Any other supplied elements should be scalar elements, each  holding
+*        a pointer to a Region describing the associated item of ancillary
+*        information (error, resolution, size, pixel size or value). These
+*        Regions should describe a volume within the coordinate system
+c        represented by "region".
+f        represented by REGION.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new StcResourceProfile. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new StcResourceProfile. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astStcResourceProfile()
+f     AST_STCRESOURCEPROFILE = INTEGER
+*        A pointer to the new StcResourceProfile.
+
+*  Notes:
+*     - A deep copy is taken of the supplied Region. This means that
+*     any subsequent changes made to the encapsulated Region using the
+*     supplied pointer will have no effect on the Stc.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstRegion *region;            /* Pointer to Region structure */
+   AstStcResourceProfile *new;   /* Pointer to new StcResourceProfile */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the Region structure provided. */
+   region = astCheckRegion( region_void );
+
+/* Initialise the StcResourceProfile, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitStcResourceProfile( NULL, sizeof( AstStcResourceProfile ), !class_init,
+                                    &class_vtab, "StcResourceProfile", region,
+                                    ncoords, coords );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new StcResourceProfile's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new StcResourceProfile. */
+   return new;
+}
+
+AstStcResourceProfile *astStcResourceProfileId_( void *region_void, int ncoords,
+                               AstKeyMap **coords, const char *options, ... ) {
+/*
+*  Name:
+*     astStcResourceProfileId_
+
+*  Purpose:
+*     Create a StcResourceProfile.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcresourceprofile.h"
+*     AstStcResourceProfile *astStcResourceProfileId( AstRegion *region,
+*                  int ncoords, AstKeyMap *coords[], const char *options, ... )
+
+*  Class Membership:
+*     StcResourceProfile constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astStcResourceProfile constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astStcResourceProfile_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astStcResourceProfile_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astStcResourceProfile_.
+
+*  Returned Value:
+*     The ID value associated with the new StcResourceProfile.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstKeyMap **keymaps;            /* Pointer to array of KeyMap pointers */
+   AstRegion *region;              /* Pointer to Region structure */
+   AstStcResourceProfile *new;     /* Pointer to new StcResourceProfile */
+   int icoord;                     /* Keymap index */
+   va_list args;                   /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+   astGET_GLOBALS(NULL);         /* Get a pointer to the thread specific global data structure. */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Region pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Region. */
+   region = astVerifyRegion( astMakePointer( region_void ) );
+
+/* Obtain pointer from the supplied KeyMap ID's and validate the
+   pointers to ensure it identifies a valid KeyMap. */
+   keymaps = astMalloc( sizeof( AstKeyMap * )*(size_t) ncoords );
+   if( keymaps ) {
+      for( icoord = 0; icoord < ncoords; icoord++ ) {
+         keymaps[ icoord ] = astVerifyKeyMap( astMakePointer( coords[ icoord ] ) );
+      }
+   }
+
+/* Initialise the StcResourceProfile, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitStcResourceProfile( NULL, sizeof( AstStcResourceProfile ), !class_init,
+                                    &class_vtab, "StcResourceProfile", region,
+                                    ncoords, keymaps );
+
+/* Free resources. */
+   keymaps = astFree( keymaps );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new StcResourceProfile's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new StcResourceProfile. */
+   return astMakeId( new );
+}
+
+AstStcResourceProfile *astInitStcResourceProfile_( void *mem, size_t size,
+                                    int init, AstStcResourceProfileVtab *vtab,
+                                    const char *name, AstRegion *region,
+                                    int ncoords, AstKeyMap **coords, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcResourceProfile
+
+*  Purpose:
+*     Initialise a StcResourceProfile.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcresourceprofile.h"
+*     AstStcResourceProfile *astInitStcResourceProfile_( void *mem, size_t size,
+*                                    int init, AstStcResourceProfileVtab *vtab,
+*                                    const char *name, AstRegion *region,
+*                                    int ncoords, AstKeyMap **coords )
+
+*  Class Membership:
+*     StcResourceProfile initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new StcResourceProfile object. It allocates memory (if necessary) to accommodate
+*     the StcResourceProfile plus any additional data associated with the derived class.
+*     It then initialises a StcResourceProfile structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a StcResourceProfile at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the StcResourceProfile is to be initialised.
+*        This must be of sufficient size to accommodate the StcResourceProfile data
+*        (sizeof(StcResourceProfile)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the StcResourceProfile (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the StcResourceProfile
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the StcResourceProfile's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new StcResourceProfile.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     region
+*        A pointer to the Region encapsulated by the StcResourceProfile.
+*     ncoords
+*        Number of KeyMap pointers supplied in "coords". Can be zero.
+*        Ignored if "coords" is NULL.
+*     coords
+*        Pointer to an array of "ncoords" KeyMap pointers, or NULL if
+*        "ncoords" is zero. Each KeyMap defines defines a single <AstroCoords>
+*        element, and should have elements with keys given by constants
+*        AST__STCNAME, AST__STCVALUE, AST__STCERROR, AST__STCRES, AST__STCSIZE,
+*        AST__STCPIXSZ. These elements hold values for the corresponding
+*        components of the STC AstroCoords element. Any of these elements may
+*        be omitted, but no other elements should be included. All supplied
+*        elements should be vector elements, with vector length less than or
+*        equal to the number of axes in the supplied Region. The data type of
+*        all elements should be "double", except for AST__STCNAME which should
+*        be "character string". If no value is available for a given axis, then
+*        AST__BAD (or NULL for the AST__STCNAME element) should be stored in
+*        the vector at the index corresponding to the axis (trailing axes
+*        can be omitted completely from the KeyMap).
+
+*  Returned Value:
+*     A pointer to the new StcResourceProfile.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstStcResourceProfile *new;       /* Pointer to new StcResourceProfile */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitStcResourceProfileVtab( vtab, name );
+
+/* Initialise a Stc structure (the parent class) as the first component
+   within the StcResourceProfile structure, allocating memory if necessary. */
+   new = (AstStcResourceProfile *) astInitStc( mem, size, 0, (AstStcVtab *) vtab,
+                                               name, region, ncoords, coords );
+
+/* If an error occurred, clean up by deleting the new StcResourceProfile. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return a pointer to the new StcResourceProfile. */
+   return new;
+}
+
+AstStcResourceProfile *astLoadStcResourceProfile_( void *mem, size_t size, AstStcResourceProfileVtab *vtab,
+                                                   const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadStcResourceProfile
+
+*  Purpose:
+*     Load a StcResourceProfile.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcresourceprofile.h"
+*     AstStcResourceProfile *astLoadStcResourceProfile( void *mem, size_t size, AstStcResourceProfileVtab *vtab,
+*                                       const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     StcResourceProfile loader.
+
+*  Description:
+*     This function is provided to load a new StcResourceProfile using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     StcResourceProfile structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a StcResourceProfile at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the StcResourceProfile is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        StcResourceProfile data (sizeof(StcResourceProfile)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the StcResourceProfile (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the StcResourceProfile structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstStcResourceProfile) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new StcResourceProfile. If this is NULL, a pointer
+*        to the (static) virtual function table for the StcResourceProfile class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "StcResourceProfile" is used instead.
+
+*  Returned Value:
+*     A pointer to the new StcResourceProfile.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstStcResourceProfile *new;              /* Pointer to the new StcResourceProfile */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this StcResourceProfile. In this case the
+   StcResourceProfile belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstStcResourceProfile );
+      vtab = &class_vtab;
+      name = "StcResourceProfile";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitStcResourceProfileVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built StcResourceProfile. */
+   new = astLoadStc( mem, size, (AstStcVtab *) vtab, name, channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "StcResourceProfile" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* There are no values to read. */
+/* ---------------------------- */
+
+/* If an error occurred, clean up by deleting the new StcResourceProfile. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new StcResourceProfile pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
+
diff --git a/ast/stcresourceprofile.h b/ast/stcresourceprofile.h
new file mode 100644
index 0000000..8157dbb
--- /dev/null
+++ b/ast/stcresourceprofile.h
@@ -0,0 +1,223 @@
+#if !defined( STCRESOURCEPROFILE_INCLUDED ) /* Include this file only once */
+#define STCRESOURCEPROFILE_INCLUDED
+/*
+*+
+*  Name:
+*     stcresourceprofile.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the StcResourceProfile class.
+
+*  Invocation:
+*     #include "stcresourceprofile.h"
+
+*  Description:
+*     This include file defines the interface to the StcResourceProfile class
+*     and provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The StcResourceProfile class is a sub-class of Stc used to describe
+*     the coverage of the datasets contained in some VO resource.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Inheritance:
+*     The StcResourceProfile class inherits from the Stc class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     26-NOV-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "stc.h"                 /* Coordinate stcs (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* StcResourceProfile structure. */
+/* ----------------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstStcResourceProfile {
+
+/* Attributes inherited from the parent class. */
+   AstStc stc;             /* Parent class structure */
+
+} AstStcResourceProfile;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstStcResourceProfileVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstStcVtab stc_vtab;    /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+} AstStcResourceProfileVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstStcResourceProfileGlobals {
+   AstStcResourceProfileVtab Class_Vtab;
+   int Class_Init;
+} AstStcResourceProfileGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitStcResourceProfileGlobals_( AstStcResourceProfileGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(StcResourceProfile)          /* Check class membership */
+astPROTO_ISA(StcResourceProfile)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstStcResourceProfile *astStcResourceProfile_( void *, int, AstKeyMap **, const char *, int *, ...);
+#else
+AstStcResourceProfile *astStcResourceProfileId_( void *, int, AstKeyMap **, const char *, ... )__attribute__((format(printf,4,5)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstStcResourceProfile *astInitStcResourceProfile_( void *, size_t, int, AstStcResourceProfileVtab *, const char *, AstRegion *, int, AstKeyMap **, int * );
+
+/* Vtab initialiser. */
+void astInitStcResourceProfileVtab_( AstStcResourceProfileVtab *, const char *, int * );
+
+/* Loader. */
+AstStcResourceProfile *astLoadStcResourceProfile_( void *, size_t, AstStcResourceProfileVtab *,
+                                   const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckStcResourceProfile(this) astINVOKE_CHECK(StcResourceProfile,this,0)
+#define astVerifyStcResourceProfile(this) astINVOKE_CHECK(StcResourceProfile,this,1)
+
+/* Test class membership. */
+#define astIsAStcResourceProfile(this) astINVOKE_ISA(StcResourceProfile,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astStcResourceProfile astINVOKE(F,astStcResourceProfile_)
+#else
+#define astStcResourceProfile astINVOKE(F,astStcResourceProfileId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitStcResourceProfile(mem,size,init,vtab,name,region,ncoords,coords) \
+astINVOKE(O,astInitStcResourceProfile_(mem,size,init,vtab,name,region,ncoords,coords,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitStcResourceProfileVtab(vtab,name) astINVOKE(V,astInitStcResourceProfileVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadStcResourceProfile(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadStcResourceProfile_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckStcResourceProfile to validate StcResourceProfile pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/stcschan.c b/ast/stcschan.c
new file mode 100644
index 0000000..4b43524
--- /dev/null
+++ b/ast/stcschan.c
@@ -0,0 +1,8732 @@
+/*
+*class++
+*  Name:
+*     StcsChan
+
+*  Purpose:
+*     I/O Channel using STC-S to represent Objects.
+
+*  Constructor Function:
+c     astStcsChan
+f     AST_STCSCHAN
+
+*  Description:
+*     A StcsChan is a specialised form of Channel which supports STC-S
+*     I/O operations. Writing an Object to an StcsChan (using
+c     astWrite) will, if the Object is suitable, generate an
+f     AST_WRITE) will, if the Object is suitable, generate an
+*     STC-S description of that Object, and reading from an StcsChan will
+*     create a new Object from its STC-S description.
+*
+*     When an STC-S description is read using
+c     astRead,
+f     AST_READ,
+*     the returned AST Object may be 1) a PointList describing the STC
+*     AstroCoords (i.e. a single point of interest within the coordinate frame
+*     described by the STC-S description), or 2) a Region describing the STC
+*     AstrCoordsArea (i.e. an area or volume of interest within the coordinate
+*     frame described by the STC-S description), or 3) a KeyMap
+*     containing the uninterpreted property values read form the STC-S
+*     description, or 4) a KeyMap containing any combination of the first
+*     3 options. The attributes StcsArea, StcsCoords and StcsProps
+*     control which of the above is returned by
+c     astRead.
+f     AST_READ.
+*
+*     When an STC-S description is created from an AST Object using
+c     astWrite,
+f     AST_WRITE,
+*     the AST Object must be either a Region or a KeyMap. If it is a
+*     Region, it is assumed to define the AstroCoordsArea or (if the
+*     Region is a single point) the AstroCoords to write to the STC-S
+*     description. If the Object is a KeyMap, it may contain an entry
+*     with the key "AREA", holding a Region to be used to define the
+*     AstroCoordsArea. It may also contain an entry with the key "COORDS",
+*     holding a Region (a PointList) to be used to create the
+*     AstroCoords. It may also contain an entry with key "PROPS", holding
+*     a KeyMap that contains uninterpreted property values to be used as
+*     defaults for any STC-S properties that are not determined by the
+*     other supplied Regions. In addition, a KeyMap supplied to
+c     astWrite
+f     AST_WRITE
+*     may itself hold the default STC-S properties (rather than defaults
+*     being held in a secondary KeyMap, stored as the "PROPS" entry in the
+*     supplied KeyMap).
+*
+*     The
+c     astRead and astWrite
+f     AST_READ and AST_WRITE
+*     functions work together so that any Object returned by
+c     astRead can immediately be re-written using astWrite.
+f     AST_READ can immediately be re-written using AST_WRITE.
+*
+*     Normally, when you use an StcsChan, you should provide "source"
+c     and "sink" functions which connect it to an external data store
+c     by reading and writing the resulting text. These functions
+f     and "sink" routines which connect it to an external data store
+f     by reading and writing the resulting text. These routines
+*     should perform any conversions needed between external character
+c     encodings and the internal ASCII encoding. If no such functions
+f     encodings and the internal ASCII encoding. If no such routines
+*     are supplied, a Channel will read from standard input and write
+*     to standard output.
+*
+*     Alternatively, an XmlChan can be told to read or write from
+*     specific text files using the SinkFile and SourceFile attributes,
+*     in which case no sink or source function need be supplied.
+*
+*     Support for STC-S is currently based on the IVOA document "STC-S:
+*     Space-Time Coordinate (STC) Metadata Linear String Implementation",
+*     version 1.30 (dated 5th December 2007), available at
+*     http://www.ivoa.net/Documents/latest/STC-S.html. Note, this
+*     document is a recommednation only and does not constitute an accepted
+*     IVOA standard.
+*
+*     The full text of version 1.30 is supported by the StcsChan class,
+*     with the following exceptions and provisos:
+*
+*     - When reading an STC-S phrase, case is ignored except when reading
+*     units strings.
+*     - There is no support for multiple intervals specified within a
+*     TimeInterval, PositionInterval, SpectralInterval or RedshiftInterval.
+*     - If the ET timescale is specified, TT is used instead.
+*     - If the TEB timescale is specified, TDB is used instead.
+*     - The LOCAL timescale is not supported.
+*     - The AST TimeFrame and SkyFrame classes do not currently allow a
+*     reference position to be specified. Consequently, any <refpos>
+*     specified within the Time or Space sub-phrase of an STC-S document
+*     is ignored.
+*     - The Convex identifier for the space sub-phrase is not supported.
+*     - The GEO_C and GEO_D space frames are not supported.
+*     - The UNITSPHERE and SPHER3 space flavours are not supported.
+*     - If any Error values are supplied in a space sub-phrase, then the
+*     number of values supplied should equal the number of spatial axes,
+*     and the values are assumed to specify an error box (i.e. error
+*     circles, ellipses, etc, are not supported).
+*     - The spectral and redshift sub-phrases do not support the
+*     following <refpos> values: LOCAL_GROUP_CENTER, UNKNOWNRefPos,
+*     EMBARYCENTER, MOON, MERCURY, VENUS, MARS, JUPITER, SATURN, URANUS,
+*     NEPTUNE, PLUTO.
+*     - Error values are supported but error ranges are not.
+*     - Resolution, PixSize and Size values are ignored.
+*     - Space velocity sub-phrases are ignored.
+
+*  Inheritance:
+*     The StcsChan class inherits from the Channel class.
+
+*  Attributes:
+*     In addition to those attributes common to all Channels, every
+*     StcsChan also has the following attributes:
+*
+*     - StcsArea: Return the CoordinateArea component after reading an STC-S?
+*     - StcsCoords: Return the Coordinates component after reading an STC-S?
+*     - StcsLength: Controls output buffer length
+*     - StcsProps: Return the STC-S properties after reading an STC-S?
+
+*  Functions:
+c     The StcsChan class does not define any new functions beyond those
+f     The StcsChan class does not define any new routines beyond those
+*     which are applicable to all Channels.
+
+*  Copyright:
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David Berry (Starlink)
+
+*  History:
+*     18-DEC-2008 (DSB):
+*        Original version.
+*     22-MAY-2008 (DSB):
+*        Retain default Equinox values in SkyFrame when reading an STC-S.
+*     30-OCT-2009 (DSB):
+*        Make case insensitive (except for units strings).
+*     21-FEB-2014 (DSB):
+*        Split long properties up into words when writing out an STC-S
+*        description.
+*     26-MAR-2015 (DSB):
+*        Guard against seg faults if an error has already occured.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS StcsChan
+
+/* Values identifying particular forms of CoordArea */
+#define NULL_ID                1
+#define TIME_INTERVAL_ID       2
+#define START_TIME_ID          3
+#define STOP_TIME_ID           4
+#define POSITION_INTERVAL_ID   5
+#define ALLSKY_ID             6
+#define CIRCLE_ID              7
+#define ELLIPSE_ID             8
+#define BOX_ID                 9
+#define POLYGON_ID            10
+#define CONVEX_ID             11
+#define POSITION_ID           12
+#define TIME_ID               13
+#define SPECTRAL_INTERVAL_ID  14
+#define SPECTRAL_ID           15
+#define REDSHIFT_INTERVAL_ID  16
+#define REDSHIFT_ID           17
+#define VELOCITY_INTERVAL_ID  18
+#define UNION_ID  	      19
+#define INTERSECTION_ID       20
+#define DIFFERENCE_ID         21
+#define NOT_ID                22
+#define VELOCITY_ID           23
+
+/* The number of words used to form an extract from an STC-S description
+   for use in an error message. */
+#define NEWORD 10
+
+/* Max length of string returned by GetAttrib */
+#define GETATTRIB_BUFF_LEN 50
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "frame.h"               /* Generic cartesian coordinate systems */
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "channel.h"             /* Interface for parent class */
+#include "stcschan.h"            /* Interface definition for this class */
+#include "loader.h"              /* Interface to the global loader */
+#include "skyframe.h"            /* Celestial coordinate systems */
+#include "timeframe.h"           /* Time coordinate systems */
+#include "specframe.h"           /* Spectral coordinate systems */
+#include "wcsmap.h"              /* PI-related constants */
+#include "region.h"              /* Abstract regions */
+#include "interval.h"            /* Axis intervals */
+#include "unitmap.h"             /* Unit mappings */
+#include "nullregion.h"          /* Boundless regions */
+#include "cmpregion.h"           /* Compound regions */
+#include "box.h"                 /* Box regions */
+#include "prism.h"               /* Prism regions */
+#include "circle.h"              /* Circle regions */
+#include "ellipse.h"             /* Ellipse regions */
+#include "polygon.h"             /* Polygon regions */
+#include "pointlist.h"           /* Lists of points */
+#include "keymap.h"              /* KeyMap interface */
+
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Module Types. */
+/* ============= */
+typedef struct WordContext {
+   char *line;
+   char *wnext;
+   char *e;
+   char f;
+   int done;
+   char *words[ NEWORD ];
+   int next;
+   int close;
+   int open;
+} WordContext;
+
+/* Module Variables. */
+/* ================= */
+
+/* Pointers to parent class methods which are extended by this class. */
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static int (* parent_getindent)( AstChannel *, int * );
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(StcsChan)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define getattrib_buff astGLOBAL(StcsChan,GetAttrib_Buff)
+#define class_init astGLOBAL(StcsChan,Class_Init)
+#define class_vtab astGLOBAL(StcsChan,Class_Vtab)
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstStcsChanVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+/* Buffer returned by GetAttrib. */
+static char getattrib_buff[ GETATTRIB_BUFF_LEN + 1 ];
+
+#endif
+
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstStcsChan *astStcsChanForId_( const char *(*)( void ),
+                                char *(*)( const char *(*)( void ), int * ),
+                                void (*)( const char * ),
+                                void (*)( void (*)( const char * ), const char *, int * ),
+                                const char *, ... );
+AstStcsChan *astStcsChanId_( const char *(* source)( void ),
+                             void (* sink)( const char * ),
+                             const char *options, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstKeyMap *ReadProps( AstStcsChan *, int * );
+static AstObject *Read( AstChannel *, int * );
+static AstPointList *SinglePointList( AstFrame *, double *, AstRegion *, int *);
+static AstRegion *MakeSpaceRegion( AstKeyMap *, AstFrame *, double, int * );
+static char *AddItem( AstStcsChan *, AstKeyMap *, const char *, const char *, char *, int *, int *, int, int * );
+static char *ContextFragment( WordContext *, char **, int * );
+static char *PutRegionProps( AstStcsChan *, AstKeyMap *, const char *, int, char *, int *, int *, int, int * );
+static char *SourceWrap( const char *(*)( void ), int * );
+static const char *GetNextWord( AstStcsChan *, WordContext *, int * );
+static const char *ReadSpaceArgs( AstStcsChan *, const char *, int, int, WordContext *, AstKeyMap *, int * );
+static double *BoxCorners( AstFrame *, const double[2], const double[2], int * );
+static int GetIndent( AstChannel *, int * );
+static int GetRegionProps( AstStcsChan *, AstRegion *, AstKeyMap *, int, int, double, int, int * );
+static int SpaceId( const char *, int * );
+static int Write( AstChannel *, AstObject *, int * );
+static int WriteRegion( AstStcsChan *, AstRegion *, AstKeyMap *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void FreeContext( WordContext *, int * );
+static void GetFmt( const char *, AstKeyMap *, int, int, char *, int * );
+static void MapPut0C( AstKeyMap *, const char *, const char *, const char *, int, int * );
+static void MapPut0D( AstKeyMap *, const char *, double, double, int, int * );
+static void SetUnc( AstRegion *, AstRegion *, AstFrame *, int, double, double *, int, int * );
+static void SinkWrap( void (*)( const char * ), const char *, int * );
+static void WriteProps( AstStcsChan *, AstKeyMap *, int * );
+
+static int GetStcsArea( AstStcsChan *, int * );
+static int TestStcsArea( AstStcsChan *, int * );
+static void ClearStcsArea( AstStcsChan *, int * );
+static void SetStcsArea( AstStcsChan *, int, int * );
+
+static int GetStcsCoords( AstStcsChan *, int * );
+static int TestStcsCoords( AstStcsChan *, int * );
+static void ClearStcsCoords( AstStcsChan *, int * );
+static void SetStcsCoords( AstStcsChan *, int, int * );
+
+static int GetStcsProps( AstStcsChan *, int * );
+static int TestStcsProps( AstStcsChan *, int * );
+static void ClearStcsProps( AstStcsChan *, int * );
+static void SetStcsProps( AstStcsChan *, int, int * );
+
+static void ClearAttrib( AstObject *, const char *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+
+static int TestStcsLength( AstStcsChan *, int * );
+static void ClearStcsLength( AstStcsChan *, int * );
+static void SetStcsLength( AstStcsChan *, int, int * );
+static int GetStcsLength( AstStcsChan *, int * );
+
+/* Member functions. */
+/* ================= */
+
+static char *AddItem( AstStcsChan *this, AstKeyMap *km, const char *key,
+                      const char *prefix, char *line, int *nc, int *crem,
+                      int linelen, int *status ){
+/*
+*  Name:
+*     AddItem
+
+*  Purpose:
+*     Add an STC-S property item to a buffer.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     char *AddItem( AstStcsChan *this, AstKeyMap *km, const char *key,
+*                    const char *prefix, char *line, int *nc, int *crem,
+*                    int linelen, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function appends text describing a singlke STC-S property to
+*     a supplied text buffer, handling the splitting of text into lines.
+
+*  Parameters:
+*     this
+*        The StcsChan.
+*     km
+*        Pointer to a KeyMap containing the STC-S properties.
+*     key
+*        The key name associated with the property to be checked.
+*     prefix
+*        if not NULL, this is a string that is to be written out before
+*        the property value. It should usually include a trailing space.
+*     line
+*        Pointer to the buffer to recieve the prefix and property value.
+*     nc
+*        Pointer to an int in which to store the number of characters in
+*        the buffer. Updated on exit.
+*     crem
+*        Pointer to an int in which to store the maximum number of
+*        characters before a new line. Ignored if linelen is zero. Updated
+*        on exit.
+*     linelen
+*        The maximum number of character per line, or zero if all text is
+*        to be included in a single line.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the buffer. This will usually be "line", but may be
+*     different to "line" if it was necessary to expand the memory to make
+*     room for the new property.
+
+*/
+
+/* Local Variables: */
+   char *result;          /* Returned pointer */
+   char **words;          /* All words */
+   const char *text;      /* Property value */
+   const char *word;      /* Single word */
+   int iw;                /* Word index */
+   int len;               /* Length of new text */
+   int nw;                /* Number of words in property */
+
+/* Initialise */
+   result = line;
+   len = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the KeyMap contains the required property... */
+   if( astMapGet0C( km, key, &text ) ) {
+
+/* Add any supplied prefix to the returned buffer. */
+      if( prefix ) {
+         len = strlen( prefix );
+         if( len > *crem && len < linelen ) {
+            astPutNextText( this, result );
+            *nc = 0;
+            result = astAppendString( result, nc, "   " );
+            *crem = linelen - 3;
+         }
+         result = astAppendString( result, nc, prefix );
+         *crem -= len;
+      }
+
+/* Split the property into words. */
+      words = astChrSplit( text, &nw );
+
+/* Append each word to the buffer. */
+      for( iw = 0; iw < nw; iw++ ) {
+         word = words[ iw ];
+
+/* If required, get the number of characters to be added to the buffer. */
+         if( linelen ) {
+            len = strlen( word );
+
+/* If there is insufficient room left, write out the text through the
+   Channel sink function, and start a new line with three spaces. Then
+   reset the number of character remaining in the line. */
+            if( len > *crem && len < linelen ) {
+               astPutNextText( this, result );
+               *nc = 0;
+               result = astAppendString( result, nc, "   " );
+               *crem = linelen - 3;
+            }
+
+/* Reduce crem to account for the text that is about to be added to the
+   line. */
+            *crem -= len;
+         }
+
+/* Add the property value to the returned buffer. */
+         result = astAppendString( result, nc, word );
+
+/* Add a traling space to the returned buffer, if there is room. */
+         if( !linelen || *crem > 0 ) {
+            result = astAppendString( result, nc, " " );
+            (*crem)--;
+         }
+      }
+
+/* Free the words buffer. */
+      if( words ) {
+         for( iw = 0; iw < nw; iw++ ) words[ iw ] = astFree( words[ iw ] );
+         words = astFree( words );
+      }
+   }
+
+/* Return the buffer pointer. */
+   return result;
+}
+
+static double *BoxCorners( AstFrame *frm, const double centre[2],
+                           const double bsize[2], int *status ) {
+/*
+*  Name:
+*     BoxCorners
+
+*  Purpose:
+*     Determine the positions of the corners of an STC Box.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     double *BoxCorners( AstFrame *frm, const double centre[2],
+*                         const double bsize[2], int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function returns a pointer to a dynamically allocated array
+*     holding the positions of the corners of the STC Box defined by the
+*     supplied "centre" and "bsize" arrays.
+
+*  Parameters:
+*     frm
+*        Pointer to the Frame in which the Box is defined. Must be 2-D.
+*     centre
+*        Two element array holding the Frame co-ordinates at the centre
+*        of the Box.
+*     bsize
+*        Two element array holding the full width and height of the Box.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array holding the axis values
+*     at the four corners, in a form suitable for passing to the
+*     astPolygon constructor function. NULL is returned if an error has
+*     already occurred, of if this function fails for any reason.
+*/
+
+/* Local Variables: */
+   double *result;         /* Returned pointer. */
+   double bh1[ 2 ];        /* A first point on the bottom horizontal edge */
+   double bh2[ 2 ];        /* A second point on the bottom horizontal edge */
+   double blc[ 2 ];        /* Position of bottom left corner */
+   double brc[ 2 ];        /* Position of bottom right corner */
+   double lv1[ 2 ];        /* A first point on the left vertical edge */
+   double lv2[ 2 ];        /* A second point on the left vertical edge */
+   double pa;              /* Position angle of great circle/straight line */
+   double rv1[ 2 ];        /* A first point on the right vertical edge */
+   double rv2[ 2 ];        /* A second point on the right vertical edge */
+   double th1[ 2 ];        /* A first point on the top horizontal edge */
+   double th2[ 2 ];        /* A second point on the top horizontal edge */
+   double tlc[ 2 ];        /* Position of top left corner */
+   double trc[ 2 ];        /* Position of top right corner */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check the Frame is 2-dimensional. */
+   if( astGetNaxes( frm ) != 2 ) {
+      astError( AST__BADIN, "astRead(StcsChan): Supplied space frame has "
+                "%d axes.", status, astGetNaxes( frm ) );
+      astError( AST__BADIN, "astRead(StcsChan): Can only use STC Box regions "
+                "with 2-dimensional space frames.", status );
+   }
+
+/* Offset away from the centre by half the Box width along a great circle
+   initially parallel to the positive first frame axis (i.e. position
+   angle +pi/2). The end position goes in "rv1" and the position angle of
+   the great circle (or straight line) at that point is returned as the
+   function value. NOTE, the use of the words "left" and "right" below is
+   vague because it depends on whether we are using a SkyFrame (which has
+   a reversed first axis) or a basic Frame. In general, the choice of "left"
+   and "right" below is appropriate for a basic Frame. */
+   pa = astOffset2( frm, centre, AST__DPIBY2, bsize[ 0 ]/2, rv1 );
+
+/* Turn by 90 degrees and offset away by half the box height. This is done
+   so that we have a second point (rv2) to define the great circle (or
+   straight line) that forms the first vertical edge of the Box (i.e. the
+   great circle or straight line through rv1 and rv2). Note, for spherical
+   Frames (i.e. SkyFrames) "rv2" is not necessarily a corner of the box. */
+   (void) astOffset2( frm, rv1, pa + AST__DPIBY2, bsize[ 1 ]/2, rv2 );
+
+/* In the same way, get two points on the second vertical Box edge. */
+   pa = astOffset2( frm, centre, -AST__DPIBY2, bsize[ 0 ]/2, lv1 );
+   (void) astOffset2( frm, lv1, pa + AST__DPIBY2, bsize[ 1 ]/2, lv2 );
+
+/* In the same way, get two points on the top horizontal Box edge. */
+   pa = astOffset2( frm, centre, 0.0, bsize[ 1 ]/2, th1 );
+   (void) astOffset2( frm, th1, pa + AST__DPIBY2, bsize[ 0 ]/2, th2 );
+
+/* In the same way, get two points on the bottom horizontal Box edge. */
+   pa = astOffset2( frm, centre, AST__DPI, bsize[ 1 ]/2, bh1 );
+   (void) astOffset2( frm, bh1, pa + AST__DPIBY2, bsize[ 0 ]/2, bh2 );
+
+/* The first corner of the Box is at the intersection of the first
+   vertical and top horizontal edges. */
+   astIntersect( frm, lv1, lv2, th1, th2, tlc );
+
+/* The top right corner of the Box is at the intersection of the right
+   vertical and top horizontal edges. */
+   astIntersect( frm, rv1, rv2, th1, th2, trc );
+
+/* The bottom left corner of the Box is at the intersection of the left
+   vertical and bottom horizontal edges. */
+   astIntersect( frm, lv1, lv2, bh1, bh2, blc );
+
+/* The bottom right corner of the Box is at the intersection of the right
+   vertical and bottom horizontal edges. */
+   astIntersect( frm, rv1, rv2, bh1, bh2, brc );
+
+/* Gather the corners together into an array suitable for use with
+   astPolygon. Make sure the vertices are traversed in an ant-clockwise
+   sense whether in a SkyFrame or a basic Frame. */
+   result = astMalloc( 8*sizeof( *result ) );
+   if( result ) {
+      if( astIsASkyFrame( frm ) ) {
+         result[ 0 ] = tlc[ 0 ];
+         result[ 1 ] = trc[ 0 ];
+         result[ 2 ] = brc[ 0 ];
+         result[ 3 ] = blc[ 0 ];
+         result[ 4 ] = tlc[ 1 ];
+         result[ 5 ] = trc[ 1 ];
+         result[ 6 ] = brc[ 1 ];
+         result[ 7 ] = blc[ 1 ];
+      } else {
+         result[ 3 ] = tlc[ 0 ];
+         result[ 2 ] = trc[ 0 ];
+         result[ 1 ] = brc[ 0 ];
+         result[ 0 ] = blc[ 0 ];
+         result[ 7 ] = tlc[ 1 ];
+         result[ 6 ] = trc[ 1 ];
+         result[ 5 ] = brc[ 1 ];
+         result[ 4 ] = blc[ 1 ];
+      }
+
+   }
+
+/* Return the pointer. */
+   return result;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a StcsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     StcsChan member function (over-rides the astClearAttrib protected
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     StcsChan, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstStcsChan *this;              /* Pointer to the StcsChan structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the StcsChan structure. */
+   this = (AstStcsChan *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+   if ( !strcmp( attrib, "stcsarea" ) ) {
+      astClearStcsArea( this );
+
+   } else if ( !strcmp( attrib, "stcscoords" ) ) {
+      astClearStcsCoords( this );
+
+   } else if ( !strcmp( attrib, "stcsprop" ) ) {
+      astClearStcsProps( this );
+
+   } else if ( !strcmp( attrib, "stcslength" ) ) {
+      astClearStcsLength( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static char *ContextFragment( WordContext *con, char **buf, int *status ){
+/*
+*  Name:
+*     ContextFragment
+
+*  Purpose:
+*     Returns a string holding a fragment of the document being read.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     char *ContextFragment( WordContext *con, char **buf, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function returns a pointer to a string that holds a fragment
+*     of the STC-S document currently being read. The fragment ends at
+*     the last word read by function GetNextWord, and starts a certain
+*     number of words earlier in the document, as specified by the NEWORD
+*     macro.
+
+*  Parameters:
+*     con
+*        Pointer to the context structure, managed by GetNextWord.
+*     buf
+*        Address of a pointer to a dynamically allocated buffer. This
+*        pointer should be NULL on the first call to this function, and
+*        will be updated by this function. The pointer should be freed
+*        using astFree when no longer needed.
+*     status
+*        Address of the inherited status value.
+
+*  Returned Value:
+*     A pointer to the buffer.
+*/
+
+/* Local Variables: */
+   int i;                     /* Word count */
+   int j;                     /* Word index */
+   int nc;                    /* Text length */
+
+/* Initialise the number of characters written to the buffer. */
+   nc = 0;
+
+/* Get the index of the first word to add to the buffer. The "next"
+   component of the context structure holds the index at which the word
+   returned by the next call to GetNextWord will be stored. So at the
+   moment, this is the index of the oldest word in the cyclic list. */
+   j = con->next;
+
+/* Loop round all non-NULL words in the cyclic list. */
+   for( i = 0; i < NEWORD; i++ ) {
+      if( con->words[ j ] ) {
+
+/* Append this word to the buffer, extending the buffer size as
+   necessary. */
+         *buf = astAppendString( *buf, &nc, con->words[ j ] );
+
+/* Append a trailingh space. */
+         *buf = astAppendString( *buf, &nc, " " );
+      }
+
+/* Increment the index of the next word to use in the cyclic list. Wrap
+   back to zerp when the end of the list is reached. */
+      if( ++j == NEWORD ) j = 0;
+   }
+
+/* Remove the final trailing space. */
+   if( nc ) (*buf)[ nc - 1 ] = 0;
+
+/* Return a pointer to the supplied buffer. */
+   return *buf;
+}
+
+static void FreeContext( WordContext *con, int *status ){
+/*
+*  Name:
+*     FreeContext
+
+*  Purpose:
+*     Free the resources used by a word-reading context structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     voidFreeContext( WordContext *con, int *status );
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function frees the resources used by the supplied WordContext
+*     structure. This structure is used by GetNextWord to keep track of
+*     which word to return next.
+*
+*     This function frees the dynamic memory pointers stored within the
+*     WordContext structure, but does not free the memory holding the
+*     WordContext structure itself.
+
+*  Parameters:
+*     con
+*        Pointer to a structure holding the context.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int i;                         /* Word index */
+
+/* Check the supplied pointer. */
+   if ( !con ) return;
+
+/* Free the resources. */
+   con->line = astFree( con->line );
+
+   for( i = 0; i < NEWORD; i++ ) {
+      con->words[ i ] = astFree( con->words[ i ] );
+   }
+
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a StcsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     StcsChan member function (over-rides the protected astGetAttrib
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a StcsChan, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan.
+*     attrib
+*        Pointer to a null terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the StcsChan, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the StcsChan. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstStcsChan *this;            /* Pointer to the StcsChan structure */
+   const char *result;           /* Pointer value to return */
+   int ival;                     /* Integer attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the StcsChan structure. */
+   this = (AstStcsChan *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* StcsArea. */
+/* --------- */
+   if ( !strcmp( attrib, "stcsarea" ) ) {
+      ival = astGetStcsArea( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* StcsCoords. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "stcscoords" ) ) {
+      ival = astGetStcsCoords( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+
+/* StcsProps. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "stcsprops" ) ) {
+      ival = astGetStcsProps( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* StcsLength */
+/* --------- */
+   } else if ( !strcmp( attrib, "stcslength" ) ) {
+      ival = astGetStcsLength( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static void GetFmt( const char *key, AstKeyMap *props, int i, int defdigs,
+                    char *fmt, int *status ){
+/*
+*  Name:
+*     GetFmt
+
+*  Purpose:
+*     Decide how many digits to use when formatting a numerical STC-S
+*     property value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     void GetFmt( const char *key, AstKeyMap *props, int i,
+*                  int defdigs, char *fmt, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function locates the named property in the supplied KeyMap. If
+*     it is found, a printf format specifier is generated that matches
+*     the value is determined and returned. Otherwise, a default format
+*     specified based on the supplied default number of digits is returned.
+
+*  Parameters:
+*     key
+*        The key name associated with the property.
+*     km
+*        Pointer to a KeyMap containing the STC-S properties.
+*     i
+*        For vector values, this is the index of the vector element to be
+*        checked. Should be zero for scalar values. If "i" is greater
+*        than the number of values in the vector, then the number of digits
+*        in the first element is found and returned.
+*     defdigs
+*        The value to return if the KeyMap does not contain an entry with
+*        the supplied key.
+*     fmt
+*        Pointer to a string in which to return the format specifier.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   const char *dot;       /* Pointer to decimal point */
+   const char *p;         /* Pointer to next character */
+   const char *word;      /* Property value */
+   int after0;            /* Digits after the decimal point in first word */
+   int after;             /* Digits after the decimal point in current word */
+   int before0;           /* Digits before the decimal point in first word */
+   int before;            /* Digits before the decimal point in current word */
+   int exp0;              /* Was an exponent found in first word? */
+   int exp;               /* Was an exponent found in current word? */
+   int j;                 /* Index of current word */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise. */
+   exp = 1;
+   before = defdigs;
+   after = 0;
+   exp0 = 0;
+   before0 = 0;
+   after0 = 0;
+
+/* If the KeyMap contains the required property... */
+   if( astMapGet0C( props, key, &word ) ) {
+
+/* Skip over the words in the string. */
+      p = word;
+      for( j = 0; j <= i; j++ ) {
+
+/* Find the next space or terminating null at the end of the current word.
+   Also count the number of digits before and after the decimal point and
+   see if the word includes an exponent. */
+         exp = 0;
+         before = 0;
+         after = 0;
+         dot = NULL;
+
+         while( *p != 0 && *p != ' ' ) {
+            if( ! exp ) {
+               if( isdigit( *p ) ) {
+                  if( dot ) {
+                     after++;
+                  } else {
+                     before++;
+                  }
+
+               } else if( *p == '.' ) {
+                  dot = p;
+
+               } else if( *p == 'e' || *p == 'E' ) {
+                  exp = 1;
+               }
+            }
+            p++;
+         }
+
+/* Note the values for the first word. */
+         if( j == 0 ) {
+            exp0 = exp;
+            before0 = before;
+            after0 = after;
+         }
+
+/* Find the following non-space marking the start of the next word,
+   or the terminating null. */
+         while( *p != 0 && *p == ' ' ) p++;
+
+/* If we find the terminating null before we have found the i'th word,
+   break out of the loop using the first word instead of the i'th word. */
+         if( *p == 0 ) {
+            exp = exp0;
+            before = before0;
+            after = after0;
+            break;
+         }
+      }
+   }
+
+   if( exp ) {
+      sprintf( fmt, "%%.%dg", before + after );
+   } else {
+      sprintf( fmt, "%%.%df", after );
+   }
+}
+
+static int GetIndent( AstChannel *this, int *status ) {
+/*
+*  Name:
+*     GetIndent
+
+*  Purpose:
+*     Get the value of the Indent attribute for a StcsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     int GetIndent( AstChannel *this, int *status )
+
+*  Class Membership:
+*     StcsChan member function (over-rides the protected astGetIndent
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function returns the value of the Indent attribute, supplying
+*     a default value appropriate to an StcsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - The Indent value to use.
+
+*/
+
+/* If the attribute is set, return its value. Otherwise return a value of
+   zero. */
+   return astTestIndent( this ) ? (*parent_getindent)( this, status ) : 0;
+}
+
+static const char *GetNextWord( AstStcsChan *this, WordContext *con,
+                                int *status ){
+/*
+*  Name:
+*     GetNextWord
+
+*  Purpose:
+*     Get a pointer to the next input word read from an STC-S source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     const char *GetNextWord( AstStcsChan *this, WordContext *con,
+*                              int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function returns a pointer to the next word of an STC-S
+*     description.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan, or NULL (to initialise "con").
+*     con
+*        Pointer to a structure holding context. The structure should be
+*        initialised by calling this function with a NULL "this" pointer
+*        before making further use of this function. When finished, it
+*        should be released using FreeContext.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new word. NULL is returned if an error has already
+*     occurred, of if "this" is NULL.
+*/
+
+/* Local Variables: */
+   const char *result;            /* Returned pointer. */
+   int i;                         /* Word index */
+   size_t len;                    /* Word length */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If no StcChan was supplied, initialise the supplied WordContext. */
+   if( !this ) {
+      con->e = NULL;
+      con->line = NULL;
+      con->done = 0;
+      con->next = 0;
+      con->wnext = NULL;
+      con->close = 0;
+      con->open = 0;
+      for( i = 0; i < NEWORD; i++ ) con->words[ i ] = NULL;
+
+/* Words that end with an opening parenthesis are treated as two words. If the
+   previous word ended in an opening parenthesis, it will have been removed by
+   the previous call to this function and the "con->open" flag set. In
+   this case, we just return a pointer to the second of the two words - a
+   single "(" character - and clear the "con->open" flag. */
+   } else if( con->open && ! con->done ) {
+      con->open = 0;
+      result = "(";
+
+/* Likewise deal with words that end with a closing parenthesis. */
+   } else if( con->close && ! con->done ) {
+      con->close = 0;
+      result = ")";
+
+/* Words that begin with an opening parenthesis are treated as two words. If
+   the previous word was such an opening parenthesis, the rest of the word
+   will have been removed by the previous call to this function and the
+   "con->wnext" pointer set to the start of the remaining word. In
+   this case, re-instate the original character that was replaced by a
+   terminating null when the previous word was returned, return the
+   "con->wnext" pointer, and then clear the pointer. */
+   } else if( con->wnext && ! con->done ) {
+      *(con->wnext) = con->f;
+      result = con->wnext;
+      con->wnext = NULL;
+
+/* Otherwise... */
+   } else {
+
+/* If the previous invocation of this function converted a space
+   character into a null character, change it back again. */
+      if( con->e ) *(con->e) = ' ';
+
+/* Get a pointer to the next non-white character in the current line of
+   input text. */
+      result = con->e;
+      if( result ) {
+         while( *result && isspace( *result ) ) result++;
+      }
+
+/* If we have exhausted the current line, get the next line by invoking
+   the source function. We loop until we read a line that is not entirely
+   blank. */
+      while( ( !result || ! *result ) && astOK ) {
+
+/* First free the memory holding the previous line. */
+         if( con->line ) con->line = astFree( con->line );
+         con->e = NULL;
+
+/* Get the next line of text from the source function. */
+         con->line = astGetNextText( this );
+         result = con->line;
+
+/* Break when we reach the end of the input text. */
+         if( !result ) break;
+
+/* Get a pointer to the first non-white character in the new line. */
+         while( *result && isspace( *result ) ) result++;
+      }
+
+/* Find the end of the word. */
+      if( result && *result ) {
+         con->e = (char *) result + 1;
+         while( *(con->e) && !isspace( *(con->e) ) ) (con->e)++;
+
+/* If the word is already null-terminated, nullify the "e" pointer to
+   indicate this. Otherwise, change the white-space character into a
+   null. */
+         if( *(con->e) ) {
+            *(con->e) = 0;
+            len = con->e - result;
+         } else {
+            con->e = NULL;
+            len = strlen( result );
+         }
+
+/* Add the word into the cyclic list of words used to form a document
+   fragment to include in error and warning messages. */
+         con->words[ con->next ] = astStore( con->words[ con->next ],
+                                             result, len + 1 );
+         if( ++(con->next) == NEWORD ) con->next = 0;
+
+/* Deal with words that include an opening or closing parenthesis at
+   start or end. These words must have 2 or more characters. */
+         if( len > 1 ) {
+
+/* If the word ends with an opening parenthesis, replace the parenthesis
+   with a null character and set a flag indicating that the next word
+   returned should consist of just an opening parenthesis. */
+            if( result[ len - 1 ] == '(' ) {
+               ((char *) result)[ len - 1 ] = 0;
+               con->open = 1;
+
+/* If the word ends with a closing parenthesis, replace the parenthesis
+   with a null character and set a flag indicating that the next word
+   returned should consist of just a closing parenthesis. */
+            } else if( result[ len - 1 ] == ')' ) {
+               ((char *) result)[ len - 1 ] = 0;
+               con->close = 1;
+
+/* If the word starts with an opening parenthesis, replace the parenthesis
+   with a null character and set a flag indicating that the next word
+   returned should consist of just a closing parenthesis. */
+            } else if( result[ 0 ] == '(' ) {
+               con->wnext = ( (char *) result ) + 1;
+               con->f = *(con->wnext);
+               *(con->wnext) = 0;
+            }
+         }
+
+/* If we have run out of input words, but we have not yet finished
+   interpreting the previous word returned, return a null string, rather
+   than a null pointer in order to allow further interpretation of the
+   previous word. */
+      } else if( ! con->done ) {
+         result = "";
+      }
+   }
+
+/* Return the pointer to the next word. */
+   return result;
+}
+
+static int GetRegionProps( AstStcsChan *this, AstRegion *spreg,
+                           AstKeyMap *spprops, int nspace, int defdigs,
+                           double scale, int issky, int *status ) {
+/*
+*  Name:
+*     GetRegionProps
+
+*  Purpose:
+*     Create STC-S properties to describe a given Region and store in a
+*     KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     int GetRegionProps( AstStcsChan *this, AstRegion *spreg,
+*                         AstKeyMap *spprops, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function creates a set of STC-S properties to describe the
+*     supplied spatial (2D) Region, and stores them in the supplied KeyMap.
+
+*  Parameters:
+*     this
+*        The StcsChan being used.
+*     spreg
+*        The 2-D spatial Region to be described.
+*     spprops
+*        A KeyMap in which to store the created properties.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Returns the integer code for the spatial region, or NULL_ID if the
+*     properties could not be created for any reason.
+
+*/
+
+
+/* Local Variables: */
+   AstKeyMap *new_props;   /* KeyMap holding component Region properties */
+   AstMapping *sreg;       /* Simplified Region */
+   AstRegion **reg_list;   /* Array of component Regioon pointers */
+   char *prop;             /* Formatted property string */
+   char buf[ 100 ];        /* Buffer for formatted values */
+   char fmt[ 10 ];         /* Buffer for format specifier */
+   double *p;              /* Pointer to next axis value */
+   double *points;         /* Pointer to array of Region axis values */
+   double a;               /* Circle or ellipse radius */
+   double angle;           /* Ellipse position angle */
+   double b;               /* Ellipse radius */
+   double centre[ 3 ];     /* Circle or ellipse centre */
+   double lbnd[ 3 ];       /* Region lower bounds */
+   double ubnd[ 3 ];       /* Region upper bounds */
+   int i;                  /* Loop index */
+   int j;                  /* Loop index */
+   int nc;                 /* Number of characters in "prop" string */
+   int np;                 /* Number of points defining the Region */
+   int nreg;               /* Number of component Regions */
+   int ok;                 /* Can the Region be written out? */
+   int oper;               /* Code for CmpRegion boolean operator */
+   int spaceid;            /* Identifier for STC-S spatial region type */
+
+/* Check inherited status */
+   if( !astOK ) return NULL_ID;
+
+/* Initialise */
+   spaceid = NULL_ID;
+   ok = 1;
+   prop = NULL;
+
+/* If the Region has been negated, temporarily negate the Region, and
+   write its properties into a new KeyMap by calling this function
+   recursively. Then store the new KeyMap in the supplied KeyMap. */
+   if( astGetNegated( spreg ) ) {
+      spaceid = NOT_ID;
+      astNegate( spreg );
+      new_props = astKeyMap( " ", status );
+
+      if( GetRegionProps( this, spreg, new_props, nspace, defdigs,
+                          scale, issky, status ) == NULL_ID ) ok = 0;
+
+      astMapPut0C( spprops, "ID", "Not", NULL );
+      astMapPut0A( spprops, "REGION1", new_props, NULL );
+      astMapPut0I( spprops, "NREG", 1, NULL );
+      astNegate( spreg );
+
+/* Store properties that are specific to AllSky sub-phrases (i.e. none)... */
+   } else if( astIsANullRegion( spreg ) && astGetNegated( spreg ) ) {
+      spaceid = ALLSKY_ID;
+      astMapPut0C( spprops, "ID", "AllSky", NULL );
+
+/* Store properties that are specific to Circle sub-phrases... */
+   } else if( astIsACircle( spreg ) ) {
+      spaceid = CIRCLE_ID;
+      astMapPut0C( spprops, "ID", "Circle", NULL );
+
+/* Get the geometric parameters of the Circle. */
+      astCirclePars( spreg, centre, &a, NULL );
+
+/* Create a string holding the formatted centre axis values, scaling
+   to the required units. Use the Frame's Digits attribute to specify
+   how many digits to use when formatting the axis values. */
+      nc = 0;
+      for( i = 0; i < nspace; i++ ) {
+         if( centre[ i ] != AST__BAD ) {
+            GetFmt( "CENTRE", spprops, i, defdigs, fmt, status );
+            (void) sprintf( buf, fmt, scale*centre[ i ] );
+            prop = astAppendString( prop, &nc, buf );
+            prop = astAppendString( prop, &nc, " " );
+
+         } else {
+            ok = 0;
+            astAddWarning( this, 1, "The supplied Circle contains "
+                           "one or more bad centre axis values.",
+                           "astWrite", status );
+            break;
+         }
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      prop[ nc - 1 ] = 0;
+      astMapPut0C( spprops, "CENTRE", prop, NULL );
+
+/* Scale, format and store the radius. */
+      if( a != AST__BAD ) {
+         GetFmt( "RADIUS", spprops, 0, defdigs, fmt, status );
+         (void) sprintf( buf, fmt, scale*a );
+         astMapPut0C( spprops, "RADIUS", buf, NULL );
+      } else {
+         ok = 0;
+         astAddWarning( this, 1, "The supplied Circle has an "
+                        "undefined radius.", "astWrite", status );
+      }
+
+/* Store properties that are specific to PositionInterval sub-phrases... */
+   } else if( astIsAInterval( spreg ) || astIsABox( spreg ) ) {
+      spaceid = POSITION_INTERVAL_ID;
+      astMapPut0C( spprops, "ID", "PositionInterval", NULL );
+
+/* Get the bounds of the Region. */
+      astGetRegionBounds( spreg, lbnd, ubnd );
+
+/* Create a string holding the formatted low limits, scaling to the
+   required units. Use the Frame's Digits attribute to specify how
+   many digits to use when formatting the axis values. */
+      nc = 0;
+      for( i = 0; i < nspace; i++ ) {
+         if( lbnd[ i ] == AST__BAD || lbnd[ i ] == DBL_MAX ||
+             lbnd[ i ] == -DBL_MAX ) {
+            astAddWarning( this, 1, "Spatial axis %d has an undefined "
+                           "lower limit.", "astWrite", status, i + 1 );
+            ok = 0;
+            break;
+         } else {
+            GetFmt( "LOLIMIT", spprops, i, defdigs, fmt, status );
+            (void) sprintf( buf, fmt, scale*lbnd[ i ] );
+            prop = astAppendString( prop, &nc, buf );
+            prop = astAppendString( prop, &nc, " " );
+         }
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      prop[ nc - 1 ] = 0;
+      astMapPut0C( spprops, "LOLIMIT", prop, NULL );
+
+/* Do the same for the upper limits. */
+      nc = 0;
+      for( i = 0; i < nspace; i++ ) {
+         if( ubnd[ i ] == AST__BAD || ubnd[ i ] == DBL_MAX ||
+             ubnd[ i ] == -DBL_MAX ) {
+            astAddWarning( this, 1, "Spatial axis %d has an undefined "
+                           "upper limit.", "astWrite", status, i + 1 );
+            ok = 0;
+            break;
+         } else {
+            GetFmt( "HILIMIT", spprops, i, defdigs, fmt, status );
+            (void) sprintf( buf, fmt, scale*ubnd[ i ] );
+            prop = astAppendString( prop, &nc, buf );
+            prop = astAppendString( prop, &nc, " " );
+         }
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      prop[ nc - 1 ] = 0;
+      astMapPut0C( spprops, "HILIMIT", prop, NULL );
+
+/* Store properties that are specific to Ellipse sub-phrases... */
+   } else if( astIsAEllipse( spreg ) ) {
+      spaceid = ELLIPSE_ID;
+      astMapPut0C( spprops, "ID", "Ellipse", NULL );
+
+/* Get the geometric parameters of the Ellipse. */
+      astEllipsePars( spreg, centre, &a, &b, &angle, NULL, NULL );
+
+/* Create a string holding the formatted centre axis values, scaling
+   to the required units. Use the Frame's Digits attribute to specify
+   how many digits to use when formatting the axis values. */
+      nc = 0;
+      for( i = 0; i < nspace; i++ ) {
+         if( centre[ i ] != AST__BAD ) {
+            GetFmt( "CENTRE", spprops, i, defdigs, fmt, status );
+            (void) sprintf( buf, fmt, scale*centre[ i ] );
+            prop = astAppendString( prop, &nc, buf );
+            prop = astAppendString( prop, &nc, " " );
+
+         } else {
+            ok = 0;
+            astAddWarning( this, 1, "The supplied Ellipse contains "
+                           "one or more bad centre axis values.",
+                           "astWrite", status );
+            break;
+         }
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      prop[ nc - 1 ] = 0;
+      astMapPut0C( spprops, "CENTRE", prop, NULL );
+
+/* Scale, format and store the two radii. */
+      if( a != AST__BAD && b != AST__BAD && angle != AST__BAD ) {
+         GetFmt( "RADIUS1", spprops, 0, defdigs, fmt, status );
+         (void) sprintf( buf, fmt, scale*a );
+         astMapPut0C( spprops, "RADIUS1", buf, NULL );
+
+         GetFmt( "RADIUS2", spprops, 0, defdigs, fmt, status );
+         (void) sprintf( buf, fmt, scale*b );
+         astMapPut0C( spprops, "RADIUS2", buf, NULL );
+
+/* Convert the angle to degrees in the direction required by STC-S,
+   format and store. */
+         angle *= AST__DR2D;
+         if( !issky )  angle = 90 - angle;
+         while( angle < 0.0 ) angle += 360.0;
+         while( angle >= 360.0 ) angle -= 360.0;
+
+         GetFmt( "POSANGLE", spprops, 0, defdigs, fmt, status );
+         (void) sprintf( buf, fmt, angle );
+         astMapPut0C( spprops, "POSANGLE", buf, NULL );
+
+      } else {
+         astAddWarning( this, 1, "The gemeotric parameters of the "
+                        "supplied Ellipse are undefined.",
+                        "astWrite", status );
+         ok = 0;
+      }
+
+/* Store properties that are specific to Polygon sub-phrases... */
+   } else if( astIsAPolygon( spreg ) ) {
+      spaceid = POLYGON_ID;
+      astMapPut0C( spprops, "ID", "Polygon", NULL );
+
+/* Get an array holding the axis values at the polygon vertices. */
+      astGetRegionPoints( spreg, 0, 0, &np, NULL );
+      points = astMalloc( sizeof( double )*np*nspace );
+      astGetRegionPoints( spreg, np, nspace, &np, points );
+
+/* Create a string holding the formatted vertex axis values, scaling
+   to the required units. Use the Frame's Digits attribute to specify
+   how many digits to use when formatting the axis values. */
+      GetFmt( "VERTICES", spprops, 0, defdigs, fmt, status );
+      nc = 0;
+      for( j = 0; j < np; j++ ) {
+         p = points + j;
+         for( i = 0; i < nspace; i++ ) {
+            if( *p != AST__BAD ) {
+               (void) sprintf( buf, fmt, scale*(*p) );
+               prop = astAppendString( prop, &nc, buf );
+               prop = astAppendString( prop, &nc, " " );
+               p += np;
+            } else {
+               astAddWarning( this, 1, "The supplied Polygon contains "
+                              "one or more bad axis values.", "astWrite",
+                              status );
+               ok = 0;
+               break;
+            }
+         }
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      prop[ nc - 1 ] = 0;
+      astMapPut0C( spprops, "VERTICES", prop, NULL );
+
+/* Free resources. */
+      points = astFree( points );
+
+/* Store properties that are specific to Position sub-phrases... */
+   } else if( astIsAPointList( spreg ) ) {
+      spaceid = POSITION_ID;
+      astMapPut0C( spprops, "ID", "Position", NULL );
+
+/* Check the PointList contains only a single point. */
+      astGetRegionPoints( spreg, 0, 0, &np, NULL );
+      if( np > 1 ) {
+         astAddWarning( this, 1, "The supplied PointList contains "
+                        "more than one position.", "astWrite", status );
+         ok = 0;
+
+/* If so, get the axis values at the point. */
+      } else {
+         astGetRegionPoints( spreg, 1, nspace, &np, centre );
+
+/* Create a string holding the formatted axis values, scaling to the
+   required units. Use the Frame's Digits attribute to specify how many
+   digits to use when formatting the axis values. */
+         nc = 0;
+         for( i = 0; i < nspace; i++ ) {
+            if( centre[ i ] != AST__BAD ) {
+               GetFmt( "POSITION", spprops, i, defdigs, fmt, status );
+               (void) sprintf( buf, fmt, scale*centre[ i ] );
+               prop = astAppendString( prop, &nc, buf );
+               prop = astAppendString( prop, &nc, " " );
+
+            } else {
+               astAddWarning( this, 1, "The supplied PointList contains "
+                              "one or more bad axis values.", "astWrite",
+                              status );
+               ok = 0;
+               break;
+            }
+         }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+         prop[ nc - 1 ] = 0;
+         astMapPut0C( spprops, "POSITION", prop, NULL );
+      }
+
+/* Store properties that are specific to compound Position sub-phrases... */
+   } else {
+
+/* If the Region is not a CmpRegion (e.g. a Prism?) see if simplifying it
+   produces a CmpRegion. */
+      if( !astIsACmpRegion( spreg ) ) {
+         sreg = astSimplify( spreg );
+      } else {
+         sreg = astClone( spreg );
+      }
+
+/* If we now have a CmpRegion, write its properties into a new KeyMap by
+   calling this function recursively. Then store the new KeyMap in the
+   supplied KeyMap. */
+      if( astIsACmpRegion( sreg ) ) {
+
+/* Get the list of Regions that the CmpRegion combines together. This
+   also returns the boolean operator with which they are combined. */
+         nreg = 0;
+         reg_list = NULL;
+         oper = astCmpRegionList( (AstCmpRegion *) sreg, &nreg, &reg_list );
+
+/* Store compound region type in the supplied KeyMap. */
+         if( oper == AST__AND ) {
+            spaceid = INTERSECTION_ID;
+            astMapPut0C( spprops, "ID", "Intersection", NULL );
+         } else if( oper == AST__OR ) {
+            spaceid = UNION_ID;
+            astMapPut0C( spprops, "ID", "Union", NULL );
+         } else {
+            spaceid = DIFFERENCE_ID;
+            astMapPut0C( spprops, "ID", "Difference", NULL );
+         }
+
+/* Loop round each of the combined Regions. */
+         for( i = 0; i < nreg; i++ ) {
+
+/* Create a new KeyMap, and then call this function recursively to store
+   the properties of the i'th component Region in the new KeyMap. */
+            if( ok ) {
+               new_props = astKeyMap( " ", status );
+               if( GetRegionProps( this, reg_list[ i ], new_props, nspace,
+                                   defdigs, scale, issky, status )
+                   == NULL_ID ) ok = 0;
+
+/* Store the new KeyMap in the supplied KeyMap. */
+               sprintf( buf, "REGION%d", i + 1 );
+               astMapPut0A( spprops, buf, new_props, NULL );
+
+/* Free resources. */
+               new_props = astAnnul( new_props );
+            }
+            reg_list[ i ] = astAnnul( reg_list[ i ] );
+         }
+         reg_list = astFree( reg_list );
+         astMapPut0I( spprops, "NREG", nreg, NULL );
+
+/* All other classes of Region are unsupported. */
+      } else {
+         astAddWarning( this, 1, "The supplied %s cannot be written "
+                        "out since STC-S does not support %s regions.",
+                        "astWrite", status, astGetClass( spreg ),
+                        astGetClass( spreg ) );
+         ok = 0;
+      }
+
+/* Free resources. */
+      sreg = astAnnul( sreg );
+   }
+
+   if( prop ) prop = astFree( prop );
+
+/* If an error has occurred, return NULL_ID. */
+   if( !ok || !astOK ) spaceid = NULL_ID;
+
+/* Return the identifier for the STC-S spatial region type. */
+   return spaceid;
+}
+
+void astInitStcsChanVtab_(  AstStcsChanVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcsChanVtab
+
+*  Purpose:
+*     Initialise a virtual function table for an StcsChan.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     void astInitStcsChanVtab( AstStcsChanVtab *vtab, const char *name )
+
+*  Class Membership:
+*     StcsChan vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the StcsChan class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstChannelVtab *channel;      /* Pointer to Channel component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitChannelVtab( (AstChannelVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAStcsChan) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstChannelVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+
+   vtab->ClearStcsArea = ClearStcsArea;
+   vtab->GetStcsArea = GetStcsArea;
+   vtab->SetStcsArea = SetStcsArea;
+   vtab->TestStcsArea = TestStcsArea;
+
+   vtab->ClearStcsCoords = ClearStcsCoords;
+   vtab->GetStcsCoords = GetStcsCoords;
+   vtab->SetStcsCoords = SetStcsCoords;
+   vtab->TestStcsCoords = TestStcsCoords;
+
+   vtab->ClearStcsProps = ClearStcsProps;
+   vtab->GetStcsProps = GetStcsProps;
+   vtab->SetStcsProps = SetStcsProps;
+   vtab->TestStcsProps = TestStcsProps;
+
+   vtab->SetStcsLength = SetStcsLength;
+   vtab->ClearStcsLength = ClearStcsLength;
+   vtab->TestStcsLength = TestStcsLength;
+   vtab->GetStcsLength = GetStcsLength;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   channel = (AstChannelVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   channel->Write = Write;
+   channel->Read = Read;
+
+   parent_getindent = channel->GetIndent;
+   channel->GetIndent = GetIndent;
+
+/* Declare the Dump function for this class. There is no destructor or
+   copy constructor. */
+   astSetDump( vtab, Dump, "StcsChan", "STC-S I/O Channel" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static AstRegion *MakeSpaceRegion( AstKeyMap *props, AstFrame *frm,
+                                   double scale, int *status ){
+/*
+*  Name:
+*     MakeSpaceRegion
+
+*  Purpose:
+*     Create a Region to describe the space coverage of the STC-S
+*     description being read.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     AstRegion *MakeSpaceRegion( AstKeyMap *props, AstFrame *frm,
+*                                 double scale, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function returns a pointer to a new Region that describes the
+*     spatial coverage of an STC-S description.
+
+*  Parameters:
+*     props
+*        A KeyMap holding properties read from the STC-S space sub-phrase.
+*     frm
+*        The Frame in which the Region is to be defined.
+*     scale
+*        A factor that must be applied to the raw axis values read from the
+*        STC-S description in order to convert them into the units used by
+*        the supplied Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Region pointer.
+
+*/
+
+
+/* Local Variables: */
+   AstKeyMap *reg_props;       /* KeyMap holding argument properties */
+   AstRegion *reg;             /* Current argument Region */
+   AstRegion *result;          /* Returned Region */
+   AstRegion *tmp;             /* Temporary Region pointer */
+   char key[ 20 ];             /* Key for argument region */
+   const char *id;             /* Sub-phrase identifier */
+   double *p;                  /* Pointer to next axis value */
+   double *temp;               /* Pointer to array of reordered polygon vertex axis values */
+   double *vertices;           /* Pointer to array of polygon vertex axis values */
+   double val1;                /* Scalar value read from KeyMap */
+   double val2;                /* Scalar value read from KeyMap */
+   double val3;                /* Scalar value read from KeyMap */
+   double vec1[ 10 ];          /* Vector read from KeyMap */
+   double vec2[ 10 ];          /* Vector read from KeyMap */
+   int iaxis;                  /* Axis index */
+   int ireg;                   /* Index of argument regions */
+   int ivert;                  /* Vertex index */
+   int naxes;                  /* Number of spatial axes */
+   int nreg;                   /* Number of argument regions */
+   int nval;                   /* Number of values read from KeyMap */
+   int nvert;                  /* Number of vertices */
+   int spaceid;                /* Integer identifier for spatial shape */
+   int oper;                   /* Boolean operator code for CmpRegion */
+
+/* Initialise */
+   result = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Temporarily ensure that an error is reported if an attempt is made to
+   access a non-existent KeyMap entry. */
+   astSetKeyError( props, 1 );
+
+/* Get the space sub-phrase identifier from the properties KeyMap, and
+   find the corresponding integer identifier. */
+
+   astMapGet0C( props, "ID", &id );
+   spaceid = SpaceId( id, status );
+
+/* Get the number of axes in the Frame. */
+   naxes = astGetNaxes( frm );
+
+/* Create a suitable Region to enclose the space positions. This
+   includes scaling the supplied axis values to the units used by
+   the Frame. */
+   if( spaceid == POSITION_INTERVAL_ID ) {
+      astMapGet1D( props, "DLOLIMIT", naxes, &nval, vec1 );
+      astMapGet1D( props, "DHILIMIT", naxes, &nval, vec2 );
+
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+         vec1[ iaxis ] *= scale;
+         vec2[ iaxis ] *= scale;
+      }
+
+      result = (AstRegion *) astBox( frm, 1, vec1, vec2, NULL, " ", status );
+
+   } else if( spaceid == ALLSKY_ID ) {
+      result = (AstRegion *) astNullRegion( frm, NULL, "Negated=1", status );
+
+   } else if( spaceid == CIRCLE_ID ) {
+      astMapGet1D( props, "DCENTRE", naxes, &nval, vec1 );
+      astMapGet0D( props, "RADIUS", &val1 );
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) vec1[ iaxis ] *= scale;
+      val1 *= scale;
+      result = (AstRegion *) astCircle( frm, 1, vec1, &val1, NULL, " ",
+                                        status );
+
+   } else if( spaceid == ELLIPSE_ID ) {
+      astMapGet1D( props, "DCENTRE", naxes, &nval, vec1 );
+      astMapGet0D( props, "RADIUS1", &val1 );
+      astMapGet0D( props, "RADIUS2", &val2 );
+      astMapGet0D( props, "POSANGLE", &val3 );
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) vec1[ iaxis ] *= scale;
+      vec2[ 0 ] = val1*scale;
+      vec2[ 1 ] = val2*scale;
+      if( !astIsASkyFrame( frm ) ) val3 = 90.0 - val3;
+      val3 *= AST__DD2R;
+      result = (AstRegion *) astEllipse( frm, 1, vec1, vec2, &val3, NULL, " ",
+                                         status );
+
+   } else if( spaceid == BOX_ID ) {
+      astMapGet1D( props, "DCENTRE", naxes, &nval, vec1 );
+      astMapGet1D( props, "DBSIZE", naxes, &nval, vec2 );
+
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+         vec1[ iaxis ] *= scale;
+         vec2[ iaxis ] *= scale;
+      }
+
+      vertices = BoxCorners( frm, vec1, vec2, status );
+      result = (AstRegion *) astPolygon( frm, 4, 4, vertices, NULL, " ",
+                                         status );
+      vertices = astFree( vertices );
+
+   } else if( spaceid == POLYGON_ID ) {
+      nval = astMapLength( props, "DVERTICES" );
+      temp = astMalloc( sizeof( double )*nval );
+      astMapGet1D( props, "DVERTICES", nval, &nval, temp );
+
+/* An STC-S polygon description holds the vertex axis values in the wrong
+   order for the AstPolygon constructor. Therefore, transpose the temp
+   array (scale them at the same time). */
+      vertices = astMalloc( sizeof( double )*nval );
+      if( astOK ) {
+         nvert = nval/naxes;
+         p = temp;
+         for( ivert = 0; ivert < nvert; ivert++ ) {
+            for( iaxis = 0; iaxis < naxes; iaxis++,p++ ) {
+               vertices[ iaxis*nvert + ivert ] = *p*scale;
+            }
+         }
+
+         result = (AstRegion *) astPolygon( frm, nvert, nvert, vertices, NULL,
+                                            " ", status );
+      }
+
+      vertices = astFree( vertices );
+      temp = astFree( temp );
+
+   } else if( spaceid == POSITION_ID ) {
+      astMapGet1D( props, "DPOSITION", naxes, &nval, vec1 );
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) vec1[ iaxis ] *= scale;
+      result = (AstRegion *) SinglePointList( frm, vec1, NULL, status );
+
+   } else if( spaceid == CONVEX_ID ) {
+      astError( AST__INTER, "astRead(StcsChan): No support for Convex in "
+                "MakeSpaceRegion (internal AST programming error).", status );
+
+/* All remaining valid space id values are compound - their arguments are held
+   within separate KeyMaps nested inside the supplied KeyMap. */
+   } else if( spaceid != NULL_ID ) {
+
+/* The number of arguments is defined in the NREG entry. */
+      astMapGet0I( props, "NREG", &nreg );
+
+/* Get the CmpRegion operator code. */
+      if( spaceid == UNION_ID ) {
+         oper = AST__OR;
+      } else if( spaceid == INTERSECTION_ID ) {
+         oper = AST__AND;
+      } else if( spaceid == DIFFERENCE_ID ) {
+         oper = AST__XOR;
+      } else {
+         oper = 0;  /* To avoid compiler warnings */
+      }
+
+/* Loop over all argument Regions. */
+      for( ireg = 0; ireg < nreg; ireg++ ) {
+
+/* Get the KeyMap holding the STC-S properties of the current argument
+   region. */
+         sprintf( key, "REGION%d", ireg + 1 );
+         astMapGet0A( props, key, &reg_props );
+
+/* Construct an AST Region from this list of STC-S properties. */
+         reg = MakeSpaceRegion( reg_props, frm, scale, status );
+
+/* If we are creating a "Not" element, just negate the argument region
+   and return it. */
+         if( spaceid == NOT_ID ) {
+            astNegate( reg );
+            result = astClone( reg );
+
+/* If we are creating a "Union", "Difference" or "Intersection" element,
+   combine the first two arguments into a CmpRegion, and then add in each
+   subsequent argument. */
+         } else {
+            if( ireg == 0 ) {
+               result = astClone( reg );
+            } else {
+               tmp = (AstRegion *) astCmpRegion( result, reg, oper, " ",
+                                                 status );
+               (void) astAnnul( result );
+               result = tmp;
+            }
+         }
+
+/* Free resources */
+         reg = astAnnul( reg );
+         reg_props = astAnnul( reg_props );
+      }
+   }
+
+/* Ensure that no error is reported if an attempt is made to access a
+   non-existent KeyMap entry. */
+   astSetKeyError( props, 0 );
+
+/* Return the Region. */
+   return result;
+}
+
+static void MapPut0C( AstKeyMap *km, const char *key, const char *value,
+                      const char *def, int defs, int *status ){
+/*
+*  Name:
+*     MapPut0C
+
+*  Purpose:
+*     Store a text STC-S property in the supplied keymap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     void MapPut0C( AstKeyMap *km, const char *key, const char *value,
+*                    const char *def, int defs, int *status )
+
+*  Class Membership:
+*     StcsChan member function.
+
+*  Description:
+*     This function stors the supplied value in the given KeyMap,
+*     handling default values.
+
+*  Parameters:
+*     km
+*        Pointer to the KeyMap in which to store the value.
+*     key
+*        Pointer to a string holding the property name associated with
+*        the value.
+*     value
+*        The property value. If this is NULL then the function
+*        returns without action.
+*     def
+*        The default property value.
+*     defs
+*        If zero, then the value is not stored in the KeyMap if the value
+*        is equal to the default value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Check the inherited status */
+   if( !astOK ) return;
+
+/* If the value is NULL, ignore the entry. */
+   if( value ) {
+
+/* If the value is equal to the default value, and we are NOT storing
+   default values, ensure the KeyMap has no entry for the given key. */
+      if( astChrMatch( value, def ) && !defs ) {
+         astMapRemove( km, key );
+
+/* Otherwise, store the value. */
+      } else {
+         astMapPut0C( km, key, value, NULL );
+      }
+   }
+}
+
+static void MapPut0D( AstKeyMap *km, const char *key, double value, double def,
+                      int defs, int *status ){
+/*
+*  Name:
+*     MapPut0D
+
+*  Purpose:
+*     Store a floating point STC-S property in the supplied keymap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     void MapPut0D( AstKeyMap *km, const char *key, double value, double def,
+*                    int defs, int *status )
+
+*  Class Membership:
+*     StcsChan member function.
+
+*  Description:
+*     This function stors the supplied value in the given KeyMap,
+*     handling default values.
+
+*  Parameters:
+*     km
+*        Pointer to the KeyMap in which to store the value.
+*     key
+*        Pointer to a string holding the property name associated with
+*        the value.
+*     value
+*        The property value. If this is AST__BAD then the function
+*        returns without action.
+*     def
+*        The default property value.
+*     defs
+*        If zero, then the value is not stored in the KeyMap if the value
+*        is equal to the default value.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Check the inherited status */
+   if( !astOK ) return;
+
+/* If the value is bad, ignore the entry. */
+   if( value != AST__BAD ) {
+
+/* If the value is equal to the default value, and we are NOT storing
+   default values, ensure the KeyMap has no entry for the given key. */
+      if( value == def && !defs ) {
+         astMapRemove( km, key );
+
+/* Otherwise, store the value. */
+      } else {
+         astMapPut0D( km, key, value, NULL );
+      }
+   }
+}
+
+static char *PutRegionProps( AstStcsChan *this, AstKeyMap *km, const char *id,
+                             int indent, char *line, int *nc, int *crem,
+                             int linelen, int *status ){
+/*
+*  Name:
+*     PutRegionProps
+
+*  Purpose:
+*     Append STC-S space sub-phrase properties to the end of a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     char *PutRegionProps( AstStcsChan *this, AstKeyMap *km, const char *id,
+*                           int indent, char *line, int *nc, int *crem,
+*                           int linelen, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function converts the STC-S properties for the space sub-phrase
+*     supplied in a KeyMap into text, and appends them to the supplied
+*     line of text in the order required by STC-S.
+*
+*     It is assumed that the sub-phrase identifier has already been put
+*     into the string.
+
+*  Parameters:
+*     this
+*        The StcsChan.
+*     km
+*        Pointer to a KeyMap containing the STC-S properties.
+*     id
+*        Pointer to the sub-phrase identifier.
+*     indent
+*        If greater than or equal to zero, then it gives the number of
+*        spaces indentation to place before the first word (also indicates
+*        that a new-line should follow the last word of the argument). If
+*        negative, never use indentation.
+*     line
+*        Pointer to the buffer to receive the property values.
+*     nc
+*        Pointer to an int in which to store the number of characaters in
+*        the buffer. Updated on exit.
+*     crem
+*        Pointer to an int in which to store the maximum number of
+*        characters before a new line. Ignored if zero. Updated on exit.
+*     linelen
+*        The maximum number of character per line, or zero if all text is
+*        to be included in a single line.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the buffer. This will usually be "line", but may be
+*     different to "line" if it was necessary to expand the memory to make
+*     room for new properties.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *reg_props;
+   char *result;
+   char key[ 20 ];
+   int i;
+   int ireg;
+   int nreg;
+   int spaceid;
+
+/* Initialise */
+   result = line;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Temporarily ensure that an error is reported if an attempt is made to
+   access a non-existent KeyMap entry. */
+   astSetKeyError( km, 1 );
+
+/* Get the integer code for the space sub-phrase identifier. */
+   spaceid = SpaceId( id, status );
+
+/* Do each type of space sub-phrase. */
+   if( spaceid == NULL_ID ) {
+      astError( AST__INTER, "astWrite(StcsChan): Illegal 'spaceid' value "
+                "in function PutRegionProps (internal AST programming "
+                "error).", status );
+
+   } else if( spaceid == POSITION_INTERVAL_ID ) {
+      result = AddItem( this, km, "LOLIMIT", NULL, result, nc, crem, linelen, status );
+      result = AddItem( this, km, "HILIMIT", NULL, result, nc, crem, linelen, status );
+
+   } else if( spaceid == ALLSKY_ID ) {
+
+   } else if( spaceid == CIRCLE_ID ) {
+      result = AddItem( this, km, "CENTRE", NULL, result, nc, crem, linelen, status );
+      result = AddItem( this, km, "RADIUS", NULL, result, nc, crem, linelen, status );
+
+   } else if( spaceid == ELLIPSE_ID ) {
+      result = AddItem( this, km, "CENTRE", NULL, result, nc, crem, linelen, status );
+      result = AddItem( this, km, "RADIUS1", NULL, result, nc, crem, linelen, status );
+      result = AddItem( this, km, "RADIUS2", NULL, result, nc, crem, linelen, status );
+      result = AddItem( this, km, "POSANGLE", NULL, result, nc, crem, linelen, status );
+
+   } else if( spaceid == BOX_ID ) {
+      result = AddItem( this, km, "CENTRE", NULL, result, nc, crem, linelen, status );
+      result = AddItem( this, km, "BSIZE", NULL, result, nc, crem, linelen, status );
+
+   } else if( spaceid == POLYGON_ID ) {
+      result = AddItem( this, km, "VERTICES", NULL, result, nc, crem, linelen, status );
+
+   } else if( spaceid == CONVEX_ID ) {
+      astError( AST__INTER, "astWrite(StcsChan): No Convex support yet "
+                "(internal AST programming error).", status );
+
+   } else if( spaceid == POSITION_ID ) {
+      result = AddItem( this, km, "POSITION", NULL, result, nc, crem, linelen, status );
+
+/* All remaining space id values are compound regions. */
+   } else {
+
+/* Append an opening parenthesis. */
+      result = astAppendString( result, nc, "( " );
+
+/* If required, write out the text through the Channel sink function,
+   and start a new line. */
+      if( indent >= 0 ) {
+         astPutNextText( this, result );
+         *nc = 0;
+         *crem = linelen;
+      }
+
+/* Set the indentation for the next level down. */
+      if( indent == 0 ) {
+         indent = 6;
+      } else if( indent > 0 ){
+         indent += 3;
+      }
+
+/* Loop round all argument Regions. */
+      astMapGet0I( km, "NREG", &nreg );
+      for( ireg = 0; ireg < nreg; ireg++ ) {
+         sprintf( key, "REGION%d", ireg + 1 );
+         astMapGet0A( km, key, &reg_props );
+
+/* Put any required indentation at the start of the line. */
+         if( indent > 0 ) {
+            for( i = 0; i < indent; i++ ) {
+               result = astAppendString( result, nc, " " );
+            }
+            *crem -= indent;
+         }
+
+/* Append the identifier for the next argument to the string. */
+         result = AddItem( this, reg_props, "ID", NULL, result, nc, crem,
+                           linelen, status );
+
+/* Append the arguments to the string. */
+         astMapGet0C( reg_props, "ID", &id );
+         result = PutRegionProps( this, reg_props, id, indent, result, nc,
+                                  crem, linelen, status );
+
+/* Write the text out to the sink function, and start a new line. */
+         if( indent > 0 ) {
+            astPutNextText( this, result );
+            *nc = 0;
+            *crem = linelen;
+         }
+
+/* Free resources. */
+         reg_props = astAnnul( reg_props );
+      }
+
+/* Decrease any indentation, and then append a closing parenthesis. */
+      if( indent > 2 ) {
+         indent -= 3;
+         for( i = 0; i < indent; i++ ) {
+            result = astAppendString( result, nc, " " );
+         }
+      }
+      result = astAppendString( result, nc, ") " );
+
+/* If we are about to return fomr the top-level, start a new line. */
+      if( indent > 0 && indent < 6 ) {
+         astPutNextText( this, result );
+         *nc = 0;
+         for( i = 0; i < indent; i++ ) {
+            result = astAppendString( result, nc, " " );
+         }
+         *crem = linelen - indent;
+      }
+   }
+
+/* Ensure that no error is reported if an attempt is made to access a
+   non-existent KeyMap entry. */
+   astSetKeyError( km, 0 );
+
+/* Return the buffer pointer. */
+   return result;
+}
+
+static AstObject *Read( AstChannel *this_channel, int *status ) {
+/*
+*  Name:
+*     Read
+
+*  Purpose:
+*     Read an Object from a Channel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     AstObject *Read( AstChannel *this_channel, int *status )
+
+*  Class Membership:
+*     StcsChan member function (over-rides the astRead method
+*     inherited from the Channel class).
+
+*  Description:
+*     This function reads an Object from an StcsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object.
+*/
+
+/* Local Variables: */
+   AstFrame *spacefrm;       /* Pointer to SpaceFrame for space sub-phrase */
+   AstFrameSet *fs;          /* Temporary FrameSet */
+   AstKeyMap *full_props;    /* KeyMap holding all sub-phrase properties */
+   AstKeyMap *props;         /* KeyMap holding current sub-phrase properties */
+   AstObject *new;           /* Pointer to returned Object */
+   AstObject *obj;           /* Pointer to Object extracted from a KeyMap */
+   AstPrism *tr;             /* Temporary Region pointer */
+   AstRegion *full_co;       /* Region describing full coord position */
+   AstRegion *full_enc;      /* Region describing full enclosure */
+   AstRegion *red_co;        /* Region describing red-shift coord */
+   AstRegion *red_enc;       /* Region describing red-shift enclosure */
+   AstRegion *space_co;      /* Region describing space coord */
+   AstRegion *space_enc;     /* Region describing space enclosure */
+   char **words;             /* Array of pointers to individual words */
+   int nword;                /* Number of words returned */
+   AstRegion *spec_co;       /* Region describing spectral coord */
+   AstRegion *spec_enc;      /* Region describing spectral enclosure */
+   AstRegion *time_co;       /* Region describing time coord */
+   AstRegion *time_enc;      /* Region describing time enclosure */
+   AstSpecFrame *redfrm;     /* Pointer to SpecFrame for redshift sub-phrase */
+   AstSpecFrame *specfrm;    /* Pointer to SpecFrame for spectral sub-phrase */
+   AstStcsChan *this;        /* Pointer to the StcsChan structure */
+   AstStdOfRestType sor;     /* Standard of rest */
+   AstSystemType sys;        /* Frame System attribute value */
+   AstTimeFrame *tf1;        /* Temporary TimeFrame */
+   AstTimeFrame *timefrm;    /* Pointer to TimeFrame for time sub-phrase */
+   AstTimeScaleType ts;      /* TimeFrame TimeScale attribute value */
+   WordContext con;          /* Context for finding next source word */
+   char *fbuf;               /* Pointer to buffer holding document fragment */
+   const char *new_ts;       /* Time scale string */
+   double epoch;             /* Value to use for the Epoch attribue */
+   double fill;              /* Filling factor */
+   double hilim;             /* Axis upper limit */
+   double lolim;             /* Axis lower limit */
+   double scale;             /* Units scaling factor */
+   int nval;                 /* No. of values read from KeyMap */
+   double vals[ 10 ];        /* Values read from KeyMap */
+   double start;             /* Start time */
+   double stop;              /* Stop time */
+   double time;              /* Time value */
+   double time_origin;       /* Value to use as TimeFrame TimeOrigin*/
+   double value;             /* Axis value */
+   int iaxis;                /* Axis index */
+   int is_skyframe;          /* Is the space frame a SkyFrame? */
+   int level;                /* Warning reporting level */
+   int naxes;                /* No. of space Frame axes */
+   int nwant;                /* Number of objects to return */
+   int use_co;               /* Do we have a full coordinate position? */
+   int use_enc;              /* Do we have a full enclosure? */
+   int want_co;              /* Is the Coordinates component wanted? */
+   int want_enc;             /* Is the enclosure region wanted? */
+   int want_props;           /* Are the STC-S properties wanted? */
+   const char *cval;         /* Pointer to property value */
+   const char *type;         /* Type of redshift axis */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Obtain a pointer to the StcsChan structure. */
+   this = (AstStcsChan *) this_channel;
+
+/* Initialise. */
+   epoch = AST__BAD;
+   start = AST__BAD;
+   stop = AST__BAD;
+   time = AST__BAD;
+   time_co = NULL;
+   time_enc = NULL;
+   space_co = NULL;
+   space_enc = NULL;
+   spacefrm = NULL;
+   spec_co = NULL;
+   spec_enc = NULL;
+   red_co = NULL;
+   red_enc = NULL;
+   use_co = 1;
+   use_enc = 0;
+   scale = 1.0;
+
+/* Read the STC-S description from the external source, parse it, and
+   create a KeyMap containing the parsed property values. */
+   full_props = ReadProps( this, status );
+
+/* If the STC-S description contained a time sub-phrase, get the KeyMap
+   containing the proprties of the time sub-phrase, and then create AST
+   Regions describing the time coordinate value and its enclosing Region. */
+   if( astMapGet0A( full_props, "TIME_PROPS", &obj ) ) {
+      props = (AstKeyMap *) obj;
+
+/* Create the default TimeFrame */
+      timefrm = astTimeFrame( " ", status );
+
+/* Get the TIMESCALE property from the KeyMap, and identify the corresponding
+   AST TimeScale. */
+      ts = AST__BADTS;
+      new_ts = NULL;
+      level = 3;
+
+      if( astMapGet0C( props, "TIMESCALE", &cval ) ) {
+
+         if( astChrMatch( cval, "TT" ) ) {
+            ts = AST__TT;
+
+         } else if( astChrMatch( cval, "TDT" ) ) {
+            ts = AST__TT;
+            new_ts = "TT";
+
+         } else if( astChrMatch( cval, "ET" ) ) {
+            ts = AST__TT;
+            new_ts = "TT";
+
+         } else if( astChrMatch( cval, "TAI" ) ) {
+            ts = AST__TAI;
+
+         } else if( astChrMatch( cval, "IAT" ) ) {
+            ts = AST__TAI;
+            new_ts = "TAI";
+
+         } else if( astChrMatch( cval, "UTC" ) ) {
+            ts = AST__UTC;
+
+         } else if( astChrMatch( cval, "TEB" ) ) {
+            ts = AST__TDB;
+            new_ts = "TDB";
+            level = 1;
+
+         } else if( astChrMatch( cval, "TDB" ) ) {
+            ts = AST__TDB;
+
+         } else if( astChrMatch( cval, "TCG" ) ) {
+            ts = AST__TCG;
+
+         } else if( astChrMatch( cval, "TCB" ) ) {
+            ts = AST__TCB;
+
+         } else if( astChrMatch( cval, "LST" ) ) {
+            ts = AST__LMST;
+
+         } else if( astChrMatch( cval, "nil" ) ) {
+            astAddWarning( this, 2, "Time scale defaulting to 'TAI'.",
+                           "astRead", status );
+
+         } else if( astOK ){
+            astError( AST__BADIN, "astRead(StcsChan): Unknown time scale '%s'.",
+                      status, cval );
+         }
+
+      } else {
+         astAddWarning( this, 2, "Time scale defaulting to 'TAI'.",
+                        "astRead", status );
+      }
+
+/* Issue a warning if a different time-scale was substituted for the supplied
+   time-scale. */
+      if( new_ts ) {
+         astAddWarning( this, level, "AST does not support the '%s' time "
+                        "scale. The '%s' timescale is being used instead.",
+                        "astRead", status, cval, new_ts );
+      }
+
+/* If we got a time scale, set the TimeScale attribute in the TimeFrame
+   to the same value. */
+      if( ts != AST__BADTS ) astSetTimeScale( timefrm, ts );
+
+/* The AST TimeFrame class has no reference position, so allow any reference
+   position but issue a warning for anything other than "TOPOCENTER" and
+   "UNKNOWNRefPos". */
+      if( !astMapGet0C( props, "REFPOS", &cval ) ) cval = "UNKNOWNRefPos";
+      if( !astChrMatch( cval, "TOPOCENTER" ) ) {
+         astAddWarning( this, 1, "AST only supports topocentric time frames, "
+                        "so 'TOPOCENTER' will be used in place of '%s'.",
+                        "astRead", status, cval );
+      }
+
+/* Get the times describes by the time sub-phrase as MJD values. */
+      astMapGet0D( props, "MJDSTART", &start );
+      astMapGet0D( props, "MJDTIME", &time );
+      astMapGet0D( props, "MJDSTOP", &stop );
+
+/* Get the earliest time represented by the time sub-phrase. We use this
+   as the TimeOrigin for the TimeFrame, and also as the Epoch for all
+   frames. */
+      time_origin = start;
+      if( time_origin == AST__BAD ) time_origin = time;
+      if( time_origin == AST__BAD ) time_origin = stop;
+      epoch = time_origin;
+
+/* Store the TimeOrigin value in the TimeFrame, modifying the time values
+   accordingly. */
+      if( time_origin != AST__BAD ) {
+         astSetTimeOrigin( timefrm, time_origin );
+         if( start != AST__BAD ) start -= time_origin;
+         if( stop != AST__BAD ) stop -= time_origin;
+         if( time != AST__BAD ) time -= time_origin;
+      }
+
+/* Convert the epoch to TDB. */
+      if( epoch != AST__BAD && ts != AST__TDB ) {
+         tf1 = astCopy( timefrm );
+         astSetTimeScale( tf1, AST__TDB );
+         fs = astConvert( timefrm, tf1, "" );
+         astTran1( fs, 1, &epoch, 1, &epoch );
+         fs = astAnnul( fs );
+         tf1 = astAnnul( tf1 );
+      }
+
+/* Store the epoch value in the TimeFrame. */
+      if( epoch != AST__BAD ) astSetEpoch( timefrm, epoch );
+
+/* Create a suitable Region to describe the enclosure for the time coords */
+      if( start != AST__BAD || stop != AST__BAD ) {
+         time_enc = (AstRegion *) astInterval( timefrm, &start, &stop,
+                                               NULL, "", status );
+         use_enc = 1;
+      }
+
+/* Create a suitable Region to describe the time coords contained within
+   the above enclosure. */
+      if( time != AST__BAD ) {
+         time_co = (AstRegion *) SinglePointList( (AstFrame *) timefrm,
+                                                   &time, NULL, status);
+      } else {
+         use_co = 0;
+      }
+
+/* If no enclosure Region was created for the time sub-phrase, use a
+   copy of any coordinate region. This is because each sub-phrase needs
+   to have an enclosure of some sort if they are to be combined in parallel
+   into an enclose for the whole CmpFrame. */
+      if( ! time_enc && time_co ) time_enc = astCopy( time_co );
+
+/* Set the filling factor. */
+      if( time_enc && astMapGet0D( props, "FILLFACTOR", &fill ) ) {
+         astSetFillFactor( time_enc, fill );
+      }
+
+/* Get the units in which the time error values are given, and get the
+   scaling factor that converts them into days. */
+      if( astMapGet0C( props, "UNIT", &cval ) ) {
+         if( !strcmp( cval, "s" ) ) {
+            scale = 1.0/86400.0;
+
+         } else if( !strcmp( cval, "d" ) ) {
+            scale = 1.0;
+
+         } else if( !strcmp( cval, "a" ) ) {
+            scale = 365.25;
+
+         } else if( !strcmp( cval, "yr" ) ) {
+            scale = 365.25;
+
+         } else if( !strcmp( cval, "cy" ) ) {
+            scale = 36525.0;
+
+         } else if( astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Unsupported "
+                      "units (%s) for the time axis within an "
+                      "STC-S description.", status, cval );
+         }
+
+      } else {
+         scale = 1.0/86400.0;
+      }
+
+/* Associate an uncertainty with the two Regions. */
+      if( astMapGet1D( props, "DERROR", 2, &nval, vals ) ) {
+         if( nval > 1 ) {
+            astAddWarning( this, 1, "An STC-S time sub-phrase contains an "
+                           "Error range. AST does not support error ranges "
+                           "so the mid value will be used as the error.",
+                           "astRead", status );
+            vals[ 0 ] = 0.5*( vals[ 0 ] + vals[ 1 ] );
+         }
+
+         SetUnc( time_enc, time_co, (AstFrame *) timefrm, 0, scale, vals, 1,
+                 status );
+      }
+
+/* Free resources */
+      props = astAnnul( props );
+      timefrm = astAnnul( timefrm );
+   }
+
+/* If the STC-S description contained a space sub-phrase, get the KeyMap
+   containing the proprties of the space sub-phrase, and then create AST
+   Regions describing the spatial position and its enclosing Region. */
+   if( astMapGet0A( full_props, "SPACE_PROPS", &obj ) ) {
+      props = (AstKeyMap *) obj;
+
+/* The class of Frame (SkyFrame or basic Frame) is determined by the
+   "FLAVOR". */
+      is_skyframe = 0;
+      if( astMapGet0C( props, "FLAVOUR", &cval ) ) {
+
+         if( astChrMatch( cval, "SPHER2" ) ) {
+            spacefrm = (AstFrame *) astSkyFrame( "", status );
+            is_skyframe = 1;
+
+         } else if( astChrMatch( cval, "CART1" ) ) {
+            spacefrm = astFrame( 1, "", status );
+
+         } else if( astChrMatch( cval, "CART2" ) ) {
+            spacefrm = astFrame( 2, "", status );
+
+         } else if( astChrMatch( cval, "CART3" ) ) {
+            spacefrm = astFrame( 3, "", status );
+
+         } else {
+            astError( AST__BADIN, "astRead(StcsChan): Unsupported "
+                      "space 'Flavor' (%s) found in STC-S description.",
+                      status, cval );
+         }
+
+      } else {
+         spacefrm = (AstFrame *) astSkyFrame( "", status );
+         is_skyframe = 1;
+      }
+
+/* Consider each supported space frame. Report an error for frames
+   not supported by AST. */
+      if( astMapGet0C( props, "FRAME", &cval ) ) {
+         if( astChrMatch( cval, "ICRS" ) ) {
+            sys = AST__ICRS;
+
+         } else if( astChrMatch( cval, "FK5" ) ) {
+            sys = AST__FK5;
+
+         } else if( astChrMatch( cval, "FK4" ) ) {
+            sys = AST__FK4;
+
+         } else if( astChrMatch( cval, "J2000" ) ) {
+            sys = AST__FK5;
+
+         } else if( astChrMatch( cval, "B1950" ) ) {
+            sys = AST__FK4;
+
+         } else if( astChrMatch( cval, "ECLIPTIC" ) ) {
+            sys = AST__ECLIPTIC;
+
+         } else if( astChrMatch( cval, "GALACTIC" ) ) {
+            sys = AST__GALACTIC;
+
+         } else if( astChrMatch( cval, "GALACTIC_II" ) ) {
+            sys = AST__GALACTIC;
+
+         } else if( astChrMatch( cval, "SUPER_GALACTIC" ) ) {
+            sys = AST__SUPERGALACTIC;
+
+         } else if( astChrMatch( cval, "UNKNOWNFrame" ) ) {
+            sys = AST__UNKNOWN;
+
+         } else {
+            sys = AST__UNKNOWN;
+            astAddWarning( this, 1, "'UNKNOWNFrame' being used in place of "
+                           "unsupported frame '%s' in an STC-S description.",
+                           "astRead", status, cval );
+         }
+
+      } else {
+         cval = "UNKNOWNFrame";
+         sys = AST__UNKNOWN;
+         astAddWarning( this, 1, "Space frame defaulting to 'UNKNOWNFrame' "
+                        "in an STC-S description.", "astRead", status );
+      }
+
+/* We can set the System (only needed for SkyFrames). */
+      if( is_skyframe ) {
+         astSetSystem( spacefrm, sys );
+
+/* If we have a basic Frame, set the Domain equal to the STC-S frame value. */
+      } else {
+         astSetDomain( spacefrm, cval );
+      }
+
+/* Set the epoch of the space frame. */
+      if( epoch != AST__BAD ) astSetEpoch( spacefrm, epoch );
+
+/* The AST Frame and SkyFrame class has no reference position, so for
+   SkyFrames we consider "TOPOCENTER" and "UNKNOWN" acceptable and all
+   other unsupported. For other Frames we allow any reference position. */
+      if( !astMapGet0C( props, "REFPOS", &cval ) ) cval = "UNKNOWNRefPos";
+      if( is_skyframe && !astChrMatch( cval, "TOPOCENTER" ) ) {
+         astAddWarning( this, 1, "AST only supports topocentric sky frames, "
+                        "so 'TOPOCENTER' will be used in place of '%s'.",
+                        "astRead", status, cval );
+      }
+
+/* Get the number of spatial axes. */
+      naxes = astGetNaxes( spacefrm );
+
+/* Get the units strings. */
+      if( !astMapGet0C( props, "UNIT", &cval ) ) {
+         if( is_skyframe ) {
+            cval = "deg";
+         } else {
+            cval = "m";
+         }
+      }
+
+/* In AST, SkyFrames always use radians, so set up a scaling factor to
+   convert supplied axis values into radians. */
+      if( is_skyframe ) {
+
+         if( !strcmp( cval, "deg" ) || !strcmp( cval, "deg deg" ) ) {
+            scale = AST__DD2R;
+
+         } else if( !strcmp( cval, "arcmin" ) || !strcmp( cval, "arcmin arcmin" ) ) {
+            scale = AST__DD2R/60.0;
+
+         } else if( !strcmp( cval, "arcsec" ) || !strcmp( cval, "arcsec arcsec" ) ) {
+            scale = AST__DD2R/3600.0;
+
+         } else if( astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Unsupported "
+                      "units (%s) for a spherical co-ordinate system "
+                      "within an STC-S description: '%s'.", status,
+                      cval, ContextFragment( &con, &fbuf, status ) );
+         }
+
+/* Basic Frames can use any of the allowed units, so use a scale factor of
+   1.0. Also set the active unit flag in the space frame to enable intelligent
+   units conversion by astConvert etc. */
+      } else {
+         scale = 1.0;
+         astSetActiveUnit( spacefrm, 1 );
+
+/* Basic Frames can have different units on different axes. So split the
+   units property up into separate words. */
+         words = astChrSplit( cval, &nword );
+
+/* Set values for the Unit attributes of the Frame. Replicate the last
+   supplied unit string for any extra axes. */
+         for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+            if( iaxis < nword ) {
+               astSetUnit( spacefrm, iaxis, words[ iaxis ] );
+            } else {
+               astSetUnit( spacefrm, iaxis, words[ nword - 1 ] );
+            }
+         }
+
+/* Free resources. */
+         for( iaxis = 0; iaxis < nword; iaxis++ ) {
+            words[ iaxis ] = astFree( words[ iaxis ] );
+         }
+         words = astFree( words );
+      }
+
+/* Create a suitable Region to enclose the space positions. This
+   includes scaling the supplied axis values to the units used by
+   the Frame. */
+      space_enc = MakeSpaceRegion( props, spacefrm, scale, status );
+      if( space_enc ) use_enc = 1;
+
+/* Create a suitable Region to describe the space coords contained within
+   the above enclosure. If any sub-phrase has no coordinate value, then
+   we cannot produce a PointList describing the complete coordinate set. */
+      if( astMapGet1D( props, "DPOSITION", naxes, &nval, vals ) ) {
+         for( iaxis = 0; iaxis < nval; iaxis++ ) vals[ iaxis ] *= scale;
+         space_co = (AstRegion *) SinglePointList( spacefrm, vals, NULL,
+                                                   status);
+      } else {
+         use_co = 0;
+      }
+
+/* If no enclosure Region was created for the space sub-phrase, use a
+   copy of any coordinate region. This is because each sub-phrase needs
+   to have an enclosure of some sort if they are to be combined in parallel
+   into an enclose for the whole CmpFrame. */
+      if( ! space_enc && space_co ) space_enc = astCopy( space_co );
+
+/* Set the filling factor. */
+      if( space_enc && astMapGet0D( props, "FILLFACTOR", &fill ) ) {
+         astSetFillFactor( space_enc, fill );
+      }
+
+/* Associate an uncertainty with the two Regions. */
+      if( astMapGet1D( props, "DERROR", 2*naxes, &nval, vals ) ) {
+         if( nval > naxes ) {
+            astAddWarning( this, 1, "An STC-S space sub-phrase contains an "
+                           "Error range. AST does not support error ranges "
+                           "so the mid value will be used as the error.",
+                           "astRead", status );
+            for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+               vals[ iaxis ] = 0.5*( vals[ iaxis ] + vals[ iaxis + naxes ] );
+            }
+
+/* If insufficient error values have been supplied, replicate the last
+   one. */
+         } else {
+            for( iaxis = nval; iaxis < naxes; iaxis++ ) {
+               vals[ iaxis ] = vals[ nval - 1 ];
+            }
+         }
+
+/* Set the uncertainty in the two space regions. */
+         SetUnc( space_enc, space_co, (AstFrame *) spacefrm, is_skyframe,
+                 scale, vals, naxes, status );
+      }
+
+/* Free resources */
+      props = astAnnul( props );
+      spacefrm = astAnnul( spacefrm );
+   }
+
+
+
+/* If the STC-S description contained a velocity sub-phrase, issue a
+   warning. */
+   if( astMapGet0A( full_props, "VELOCITY_PROPS", &obj ) ) {
+      astAddWarning( this, 1, "Ignoring a velocity sub-phrase found in "
+                           "an STC-S description.", "astRead", status );
+      obj = astAnnul( obj );
+   }
+
+
+/* If the STC-S description contained a spectral sub-phrase, get the KeyMap
+   containing the proprties of the spectral sub-phrase, and then create AST
+   Regions describing the spectral coordinate value and its enclosing Region. */
+   if( astMapGet0A( full_props, "SPECTRAL_PROPS", &obj ) ) {
+      props = (AstKeyMap *) obj;
+
+/* Create the default SpecFrame */
+      specfrm = astSpecFrame( " ", status );
+
+/* Get the REFPOS property from the KeyMap, and identify the corresponding
+   AST StdOfRest. */
+      sor = AST__BADSOR;
+      if( astMapGet0C( props, "REFPOS", &cval ) ) {
+
+         if( astChrMatch( cval, "GEOCENTER" ) ) {
+            sor = AST__GESOR;
+
+         } else if( astChrMatch( cval, "BARYCENTER" ) ) {
+            sor = AST__BYSOR;
+
+         } else if( astChrMatch( cval, "HELIOCENTER" ) ) {
+            sor = AST__HLSOR;
+
+         } else if( astChrMatch( cval, "TOPOCENTER" ) ) {
+            sor = AST__TPSOR;
+
+         } else if( astChrMatch( cval, "LSR" ) ||
+                    astChrMatch( cval, "LSRK" ) ) {
+            sor = AST__LKSOR;
+
+         } else if( astChrMatch( cval, "LSRD" ) ) {
+            sor = AST__LDSOR;
+
+         } else if( astChrMatch( cval, "GALACTIC_CENTER" ) ) {
+            sor = AST__GLSOR;
+
+         } else {
+            astAddWarning( this, 1, "Using 'HELIOCENTER' in place of "
+                           "unsupported spectral reference position '%s' "
+                           "found in an STC-S description.", "astRead",
+                           status, cval );
+         }
+
+      } else {
+         astAddWarning( this, 2, "Spectral reference position defaulting to "
+                        "'HELIOCENTER' in an STC-S description.", "astRead",
+                        status );
+      }
+
+/* If we got a ref pos, set the StdOfRest attribute in the SpecFrame. */
+      if( sor != AST__BADSOR ) astSetStdOfRest( specfrm, sor );
+
+/* Get the units. */
+      if( !astMapGet0C( props, "UNIT", &cval ) ) cval = "Hz";
+
+
+/* Set the spectral system implied by the unit string. */
+      if( !cval || !strcmp( cval, "Hz" ) || !strcmp( cval, "MHz" ) ||
+          !strcmp( cval, "GHz" ) ) {
+         astSetSystem( specfrm, AST__FREQ );
+
+      } else if( !strcmp( cval, "m" ) || !strcmp( cval, "mm" ) ||
+                 !strcmp( cval, "um" ) || !strcmp( cval, "nm" ) ||
+                 !strcmp( cval, "Angstrom" ) ) {
+         astSetSystem( specfrm, AST__WAVELEN );
+
+      } else if( !strcmp( cval, "eV" ) || !strcmp( cval, "keV" ) ||
+                 !strcmp( cval, "MeV" ) ) {
+         astSetSystem( specfrm, AST__ENERGY );
+
+      } else if( astOK ) {
+         astError( AST__BADIN, "astRead(StcsChan): Unsupported spectral "
+                   "units (%s) found within an STC-S description.",
+                   status, cval );
+      }
+
+/* Set the units. */
+      astSetUnit( specfrm, 0, cval );
+
+/* Set the epoch */
+      if( epoch != AST__BAD ) astSetEpoch( specfrm, epoch );
+
+/* Create a suitable Region to describe the enclosure for the spectral
+   coords */
+      if( astMapGet0D( props, "LOLIMIT", &lolim ) ) {
+         astMapGet0D( props, "HILIMIT", &hilim );
+         spec_enc = (AstRegion *) astInterval( specfrm, &lolim, &hilim,
+                                               NULL, "", status );
+         use_enc = 1;
+      }
+
+/* Create a suitable Region to describe the spectral coords contained within
+   the above enclosure. If any sub-phrase has no coordinate value, then
+   we cannot produce a PointList describing the complete coordinate set. */
+      if( astMapGet0D( props, "SPECTRAL", &value ) ) {
+         spec_co = (AstRegion *) SinglePointList( (AstFrame *) specfrm,
+                                                   &value, NULL, status);
+      } else {
+         use_co = 0;
+      }
+
+/* If no enclosure Region was created for the spectral sub-phrase, use a
+   copy of any coordinate region. This is because each sub-phrase needs
+   to have an enclosure of some sort if they are to be combined in parallel
+   into an enclose for the whole CmpFrame. */
+      if( ! spec_enc && spec_co ) spec_enc = astCopy( spec_co );
+
+/* Set the filling factor. */
+      if( spec_enc && astMapGet0D( props, "FILLFACTOR", &fill ) ) {
+         astSetFillFactor( spec_enc, fill );
+      }
+
+
+/* Associate an uncertainty with the two Regions. */
+      if( astMapGet1D( props, "DERROR", 2, &nval, vals ) ) {
+         if( nval > 1 ) {
+            astAddWarning( this, 1, "An STC-S spectral sub-phrase contains an "
+                           "Error range. AST does not support error ranges "
+                           "so the mid value will be used as the error.",
+                           "astRead", status );
+            vals[ 0 ] = 0.5*( vals[ 0 ] + vals[ 1 ] );
+         }
+
+         SetUnc( spec_enc, spec_co, (AstFrame *) specfrm, 0, 1.0, vals, 1,
+                 status );
+      }
+
+/* Free resources */
+      props = astAnnul( props );
+      specfrm = astAnnul( specfrm );
+   }
+
+
+
+
+/* If the STC-S description contained a redshift sub-phrase, get the KeyMap
+   containing the properties of the redshift sub-phrase, and then create AST
+   Regions describing the redshift coordinate value and its enclosing Region. */
+   if( astMapGet0A( full_props, "REDSHIFT_PROPS", &obj ) ) {
+      props = (AstKeyMap *) obj;
+
+/* Create the default SpecFrame */
+      redfrm = astSpecFrame( "Domain=REDSHIFT", status );
+
+/* Get the REFPOS property from the KeyMap, and identify the corresponding
+   AST StdOfRest. */
+      sor = AST__BADSOR;
+      if( astMapGet0C( props, "REFPOS", &cval ) ) {
+
+         if( astChrMatch( cval, "GEOCENTER" ) ) {
+            sor = AST__GESOR;
+
+         } else if( astChrMatch( cval, "BARYCENTER" ) ) {
+            sor = AST__BYSOR;
+
+         } else if( astChrMatch( cval, "HELIOCENTER" ) ) {
+            sor = AST__HLSOR;
+
+         } else if( astChrMatch( cval, "TOPOCENTER" ) ) {
+            sor = AST__TPSOR;
+
+         } else if( astChrMatch( cval, "LSR" ) ||
+                    astChrMatch( cval, "LSRK" ) ) {
+            sor = AST__LKSOR;
+
+         } else if( astChrMatch( cval, "LSRD" ) ) {
+            sor = AST__LDSOR;
+
+         } else if( astChrMatch( cval, "GALACTIC_CENTER" ) ) {
+            sor = AST__GLSOR;
+
+         } else {
+            astAddWarning( this, 1, "Using 'HELIOCENTER' in place of "
+                           "unsupported redshift reference position '%s' "
+                           "found in an STC-S description.", "astRead",
+                           status, cval );
+         }
+
+      } else {
+         astAddWarning( this, 2, "Redshift reference position defaulting to "
+                        "'HELIOCENTER' in an STC-S description.", "astRead",
+                        status );
+      }
+
+/* If we got a ref pos, set the StdOfRest attribute in the SpecFrame. */
+      if( sor != AST__BADSOR ) astSetStdOfRest( redfrm, sor );
+
+/* Get the redshift type. */
+      if( !astMapGet0C( props, "TYPE", &type ) ) type = "REDSHIFT";
+
+/* Now get the velocity definition, and set the equivalent SpecFrame
+   System value. AST only supports optical redshift, so report an error
+   or a warning for unsupported combinations. */
+      if( astMapGet0C( props, "DOPPLERDEF", &cval ) ){
+
+         if( astChrMatch( cval, "OPTICAL" ) ) {
+            if( astChrMatch( type, "VELOCITY" ) ){
+               astSetSystem( redfrm, AST__VOPTICAL );
+            } else {
+               astSetSystem( redfrm, AST__REDSHIFT );
+            }
+
+         } else if( astChrMatch( cval, "RADIO" ) ) {
+            if( astChrMatch( type, "VELOCITY" ) ){
+               astSetSystem( redfrm, AST__VRADIO );
+            } else {
+               astSetSystem( redfrm, AST__REDSHIFT );
+               astAddWarning( this, 1, "STC-S RADIO redshift not supported. "
+                              "Assuming OPTICAL redshift instead.", "astRead",
+                              status );
+            }
+
+         } else if( astChrMatch( cval, "RELATIVISTIC" ) ) {
+            if( astChrMatch( type, "VELOCITY" ) ){
+               astSetSystem( redfrm, AST__VREL );
+            } else {
+               astSetSystem( redfrm, AST__REDSHIFT );
+               astAddWarning( this, 1, "STC-S RELATIVISTIC redshift not supported. "
+                              "Assuming OPTICAL redshift instead.", "astRead",
+                              status );
+            }
+
+         } else {
+            if( astChrMatch( type, "VELOCITY" ) ){
+               astSetSystem( redfrm, AST__VOPTICAL );
+               astAddWarning( this, 1, "Doppler velocity definition defaulting"
+                              " to 'OPTICAL' in an STC-S description.",
+                              "astRead", status );
+
+            } else {
+               astSetSystem( redfrm, AST__REDSHIFT );
+            }
+         }
+      }
+
+/* Set the units. */
+      if( astChrMatch( type, "VELOCITY" ) ){
+         if( astMapGet0C( props, "UNIT", &cval ) ) {
+            astSetUnit( redfrm, 0, cval );
+         } else {
+            astSetUnit( redfrm, 0, "km/s" );
+         }
+
+      } else if( astMapGet0C( props, "UNIT", &cval ) ) {
+         astAddWarning( this, 1, "Ignoring units (%s) specified for REDSHIFT "
+                        "in an STC-S description.", "astRead", status, cval );
+      }
+
+/* Set the epoch */
+      if( epoch != AST__BAD ) astSetEpoch( redfrm, epoch );
+
+/* Create a suitable Region to describe the enclosure for the redshift
+   coords */
+      if( astMapGet0D( props, "LOLIMIT", &lolim ) ) {
+         astMapGet0D( props, "HILIMIT", &hilim );
+         red_enc = (AstRegion *) astInterval( redfrm, &lolim, &hilim,
+                                               NULL, "", status );
+         use_enc = 1;
+      }
+
+/* Create a suitable Region to describe the redshift coords contained within
+   the above enclosure. If any sub-phrase has no coordinate value, then
+   we cannot produce a PointList describing the complete coordinate set. */
+      if( astMapGet0D( props, "REDSHIFT", &value ) ) {
+         red_co = (AstRegion *) SinglePointList( (AstFrame *) redfrm,
+                                                   &value, NULL, status);
+      } else {
+         use_co = 0;
+      }
+
+/* If no enclosure Region was created for the redshift sub-phrase, use a
+   copy of any coordinate region. This is because each sub-phrase needs
+   to have an enclosure of some sort if they are to be combined in parallel
+   into an enclose for the whole CmpFrame. */
+      if( ! red_enc && red_co ) red_enc = astCopy( red_co );
+
+/* Set the filling factor. */
+      if( red_enc && astMapGet0D( props, "FILLFACTOR", &fill ) ) {
+         astSetFillFactor( red_enc, fill );
+      }
+
+/* Associate an uncertainty with the two Regions. */
+      if( astMapGet1D( props, "DERROR", 2, &nval, vals ) ) {
+         if( nval > 1 ) {
+            astAddWarning( this, 1, "An STC-S redshift sub-phrase contains an "
+                           "Error range. AST does not support error ranges "
+                           "so the mid value will be used as the error.",
+                           "astRead", status );
+            vals[ 0 ] = 0.5*( vals[ 0 ] + vals[ 1 ] );
+         }
+
+         SetUnc( red_enc, red_co, (AstFrame *) redfrm, 0, 1.0, vals, 1,
+                 status );
+      }
+
+/* Free resources */
+      props = astAnnul( props );
+      redfrm = astAnnul( redfrm );
+   }
+
+/* If a particular position was specified by the STC_S document, create the
+   full position from the individual sub-phrase position */
+   if( use_co ) {
+      new = time_co ? astClone( time_co ) : NULL;
+
+      if( space_co ) {
+         if( new ) {
+            tr = astPrism( new, space_co, "", status );
+            (void) astAnnul( new );
+            new = (AstObject *) tr;
+         } else {
+            new = astClone( space_co );
+         }
+      }
+
+      if( spec_co ) {
+         if( new ) {
+            tr = astPrism( new, spec_co, "", status );
+            (void) astAnnul( new );
+            new = (AstObject *) tr;
+         } else {
+            new = astClone( spec_co );
+         }
+      }
+
+      if( red_co ) {
+         if( new ) {
+            tr = astPrism( new, red_co, "", status );
+            (void) astAnnul( new );
+            new = (AstObject *) tr;
+         } else {
+            new = astClone( red_co );
+         }
+      }
+
+      if( new ) {
+         full_co = astSimplify( new );
+         new = astAnnul( new );
+      } else {
+         full_co = NULL;
+      }
+
+   } else {
+      full_co = NULL;
+   }
+
+/* If an enclosing volume was specified by the STC_S document, create the
+   full enclosure Region from the individual sub-phrase enclosure Regions. */
+   if( use_enc ) {
+      new = time_enc ? astClone( time_enc ) : NULL;
+
+      if( space_enc ) {
+         if( new ) {
+            tr = astPrism( new, space_enc, "", status );
+            (void) astAnnul( new );
+            new = (AstObject *) tr;
+         } else {
+            new = astClone( space_enc );
+         }
+      }
+
+      if( spec_enc ) {
+         if( new ) {
+            tr = astPrism( new, spec_enc, "", status );
+            (void) astAnnul( new );
+            new = (AstObject *) tr;
+         } else {
+            new = astClone( spec_enc );
+         }
+      }
+
+      if( red_enc ) {
+         if( new ) {
+            tr = astPrism( new, red_enc, "", status );
+            (void) astAnnul( new );
+            new = (AstObject *) tr;
+         } else {
+            new = astClone( red_enc );
+         }
+      }
+      full_enc = astSimplify( new );
+      new = astAnnul( new );
+
+   } else {
+      full_enc = NULL;
+   }
+
+/* See which, and how many, items are to be returned. */
+   nwant = 0;
+   if( ( want_enc = astGetStcsArea( this ) ) ) nwant++;
+   if( ( want_co = astGetStcsCoords( this ) ) ) nwant++;
+   if( ( want_props = astGetStcsProps( this ) ) ) nwant++;
+
+/* If one, and only one, of the three items is to be returned, return it. */
+   new = NULL;
+   if( nwant == 1 ) {
+      if( want_enc && full_enc ) {
+         new = astClone( full_enc );
+      } else if( want_co && full_co ) {
+         new = astClone( full_co );
+      } else if( want_props && full_props ){
+         new = astClone( full_props );
+      }
+
+/* If more than one item is to be returned, put them into a KeyMap and
+   return the KeyMap. */
+   } else if( nwant > 1 ) {
+      new = (AstObject *) astKeyMap( " ", status );
+      if( want_enc && full_enc ) astMapPut0A( new, "AREA", full_enc, NULL );
+      if( want_co && full_co ) astMapPut0A( new, "COORDS", full_co, NULL );
+      if( want_props && full_props ) astMapPut0A( new, "PROPS", full_props, NULL );
+
+/* Report an error if nothing is to be returned. */
+   } else if( astOK ){
+      astError( AST__ATTIN, "astRead(StcsChan): The StcsArea, StcsCoords "
+                "and StcsProps attributes indicate that nothing is to be "
+                "returned (possible programming error).", status );
+   }
+
+/* Free resources */
+   if( space_enc ) space_enc = astAnnul( space_enc );
+   if( spec_enc ) spec_enc = astAnnul( spec_enc );
+   if( time_enc ) time_enc = astAnnul( time_enc );
+   if( red_enc ) red_enc = astAnnul( red_enc );
+   if( space_co ) space_co = astAnnul( space_co );
+   if( spec_co ) spec_co = astAnnul( spec_co );
+   if( time_co ) time_co = astAnnul( time_co );
+   if( red_co ) red_co = astAnnul( red_co );
+   if( full_enc ) full_enc = astAnnul( full_enc );
+   if( full_co ) full_co = astAnnul( full_co );
+   if( full_props ) full_props = astAnnul( full_props );
+
+/* If an error occurred, clean up by deleting the new Object and
+   return a NULL pointer. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the pointer to the new Object. */
+   return new;
+}
+
+static AstKeyMap *ReadProps( AstStcsChan *this, int *status ) {
+/*
+*  Name:
+*     ReadProps
+
+*  Purpose:
+*     Read STC-S properties from the source and store in a KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     AstKeyMap *ReadProps( AstStcsChan *this, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function parses the list of space-separated words read from the
+*     source function, identifies the purpose of each word within the STC-S
+*     description, and stores the words in a returned KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new KeyMap. This will contain up to five entries
+*     with any or all of the following keys: TIME_PROPS, SPACE_PROPS,
+*     VELOCITY_PROPS, SPECTRAL_PROPS, REDSHIFT_PROPS. If an entry is absent,
+*     it means the STC-S description did not contain the corresponding
+*     sub-phrase. The value associated with each of these entries will be a
+*     KeyMap. These will contain values for the sub-phrase proprties read
+*     from the STC-S description. Properties that are not specified in
+*     the STC-S description will not be present in the KeyMap. The values
+*     stored in the KeyMap are the words read form the STC-S description
+*     without any conversion or other processing.
+*/
+
+/* Local Constants: */
+#define MAXVAL 6
+
+/* Local Variables: */
+   AstKeyMap *props;         /* KeyMap holding current sub-phrase properties */
+   AstKeyMap *result;        /* Returned KeyMap holding all properties */
+   AstTimeFrame *timefrm;    /* Used for unformatting ISO date-times */
+   WordContext con;          /* Context for finding next source word */
+   char *fbuf;               /* Pointer to buffer holding document fragment */
+   char *prop;               /* String holding complete property value */
+   const char *subphrase;    /* Name of current sub phrase */
+   const char *t;            /* Temporary character string pointer */
+   const char *word;         /* Pointer to next source word */
+   double val[ MAXVAL ];     /* Array of numerical property values */
+   double start;             /* Start time (MJD) */
+   double stop;              /* Stop time (MJD) */
+   double time;              /* Time value (MJD) */
+   double value;             /* Axis value */
+   int iaxis;                /* Axis index */
+   int is_jd;                /* Is time value a JD rather than an MJD? */
+   int nunit;                /* Number of units strings supplied */
+   int nval;                 /* Number of numerical values read */
+   int naxes;                /* No. of space Frame axes */
+   int nc;                   /* Number of characters written to string */
+   int new_word;             /* Get a new word at the end of the pass? */
+   int redid;                /* Redshift sub-phrase component identifier */
+   int spaceid;              /* Space sub-phrase component identifier */
+   int specid;               /* Spectral sub-phrase component identifier */
+   int timeid;               /* Time sub-phrase component identifier */
+   int velid;                /* Velocity sub-phrase component identifier */
+
+/* The stage reached in the parsing of the STC-S description is indicated
+   by the "look_for" variable. This variable is allowed the following
+   values, indicating the item that is to be checked for next. */
+   enum look_for_type {
+      ERROR,
+      FILL_FACTOR,
+      FLAVOUR,
+      FRAME,
+      LIMITS,
+      PIX_SIZE,
+      POSITION,
+      POSITION_INTERVAL,
+      REDSHIFT_IDENTIFIER,
+      RED_SPEC_LABEL,
+      RED_SPEC_VALUE,
+      REFPOS,
+      RESOLUTION,
+      SIZE,
+      SPACE_IDENTIFIER,
+      SPECTRAL_IDENTIFIER,
+      START,
+      STOP,
+      TIME,
+      TIME_IDENTIFIER,
+      TIME_LABEL,
+      TIME_SCALE,
+      TYPE_DOPPLER,
+      UNIT,
+      VELOCITY_IDENTIFIER,
+      VELOCITY
+   } look_for;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Create the returned KeyMap. */
+   result = astKeyMap( " ", status );
+
+/* Initialise the word search context. */
+   (void) GetNextWord( NULL, &con, status );
+
+/* Get a pointer to the first word in the STC-S description. */
+   word = GetNextWord( this, &con, status );
+
+/* Indicate we are currently looking for the time sub-phrase (the first
+   item in an STC-S description). */
+   look_for = TIME_IDENTIFIER;
+
+/* Initialise everything else. */
+   fbuf = NULL;
+   naxes = 0;
+   prop = NULL;
+   props = NULL;
+   redid = NULL_ID;
+   spaceid = NULL_ID;
+   specid = NULL_ID;
+   subphrase = NULL;
+   t = NULL;
+   timeid = NULL_ID;
+   velid = NULL_ID;
+   timefrm = NULL;
+
+/* Loop until all words in the STC-S description have been interpreted or
+   an error has occurred. */
+   while( word && astOK ) {
+
+/* Initialise a flag to indicate that we have interpreted the current word
+   sucesfully and so will need to get a new word before the next pass through
+   this loop. If it turns out that we cannot interpret the current word
+   in this pass, then this flag will be set to zero at some point, thus
+   preventing a new word from being acquired and causing another attempt to
+   re-interpret the current word in a different context. */
+      new_word = 1;
+
+/* If we are currently looking for the time sub-phrase, see if the current
+   word is any of the known time sub-phrase identifiers. Is so, move on
+   to read the associated sub-phrase component. */
+      if( look_for == TIME_IDENTIFIER ) {
+/* ------------------------------------------------------------------ */
+
+/* Assume that we will be moving on to read the fill factor (most time
+   sub-phrases start with the fill factor ). */
+         look_for = FILL_FACTOR;
+
+/* Now check the word to see if it a known time sub-phrase identifier. */
+         if( astChrMatch( word, "TimeInterval" ) ) {
+            timeid = TIME_INTERVAL_ID;
+
+         } else if( astChrMatch( word, "StartTime" ) ) {
+            timeid = START_TIME_ID;
+
+         } else if( astChrMatch( word, "StopTime" ) ) {
+            timeid = STOP_TIME_ID;
+
+         } else if( astChrMatch( word, "Time" ) ) {
+            look_for = TIME_SCALE;  /* After "Time", we move on to find the
+            timeid = TIME_ID;          time-scale, not the fill factor */
+
+/* If the word is not a known time sub-phrase identifier, indicate that we
+   should attempt to re-interpret the current word as a space sub-phrase
+   identifier, rather than getting a new word. */
+         } else {
+            look_for = SPACE_IDENTIFIER;
+            new_word = 0;
+         }
+
+/* If we have found a time sub-phrase identifier, create a KeyMap to hold
+   the properties of the time sub-phrase, and store the time sub-phrase
+   identifier in the new KeyMap. */
+         if( timeid != NULL_ID ) {
+            subphrase = "time";
+            props = astKeyMap( " ", status );
+            astMapPut0A( result, "TIME_PROPS", props, NULL );
+            astMapPut0C( props, "ID", word, NULL );
+            naxes = 1;
+         }
+
+
+
+/* If we are currently looking for the space sub-phrase, see if the current
+   word is any of the known space sub-phrase identifiers. Is so, move on
+   to read the associated sub-phrase component. */
+      } else if( look_for == SPACE_IDENTIFIER ) {
+/* ------------------------------------------------------------------ */
+
+/* Indicate we have finished any preceding time sub-phrase. */
+         timeid = NULL_ID;
+
+/* Now check the word to see if it a known space sub-phrase identifier. */
+         spaceid = SpaceId( word, status );
+
+/* Decide what to look for next. */
+         if( spaceid == POSITION_ID ) {
+            look_for = FRAME;
+
+         } else if( spaceid != NULL_ID ) {
+            look_for = FILL_FACTOR;
+
+/* If the word is not a known space sub-phrase identifier, move on to
+   re-interpret it as a Spectral sub-phrase identifier. */
+         } else {
+            look_for = SPECTRAL_IDENTIFIER;
+            new_word = 0;
+         }
+
+/* If we have found a space sub-phrase identifier, create a KeyMap to hold
+   the properties of the space sub-phrase, and store the space sub-phrase
+   identifier in the new KeyMap. */
+         if( spaceid != NULL_ID ) {
+            subphrase = "space";
+            if( props ) props = astAnnul( props );
+            props = astKeyMap( " ", status );
+            astMapPut0A( result, "SPACE_PROPS", props, NULL );
+            astMapPut0C( props, "ID", word, NULL );
+         }
+
+
+
+/* If we are currently looking for the velocity sub-phrase, see if the current
+   word is any of the known velocity sub-phrase identifiers. Is so, move on
+   to read the associated sub-phrase component.  */
+      } else if( look_for == VELOCITY_IDENTIFIER ) {
+/* ------------------------------------------------------------------ */
+
+/* Indicate we have finished any preceding space sub-phrase. */
+         spaceid = NULL_ID;
+
+/* Now check the word to see if it a known velocity sub-phrase identifier. */
+         if( astChrMatch( word, "VelocityInterval" ) ) {
+            velid = VELOCITY_INTERVAL_ID;
+            look_for = FILL_FACTOR;
+
+         } else if( astChrMatch( word, "Velocity" ) ) {
+            velid = VELOCITY_ID;
+            look_for = VELOCITY;
+
+/* If the word is not a known velocity sub-phrase identifier, move on to
+   re-interpret it as a Spectral sub-phrase identifier. */
+         } else {
+            look_for = SPECTRAL_IDENTIFIER;
+            new_word = 0;
+         }
+
+/* If we have found a velocity sub-phrase identifier, create a KeyMap to
+   hold the properties of the velocity sub-phrase, and store the velocity
+   sub-phrase identifier in the new KeyMap. */
+         if( velid != NULL_ID ) {
+            subphrase = "velocity";
+            if( props ) props = astAnnul( props );
+            props = astKeyMap( " ", status );
+            astMapPut0A( result, "VELOCITY_PROPS", props, NULL );
+            astMapPut0C( props, "ID", word, NULL );
+         }
+
+
+
+/* If we are currently looking for the spectral sub-phrase, see if the
+   word is any of the known spectral sub-phrase identifiers. Is so, move
+   on to read the associated sub-phrase component. */
+      } else if( look_for == SPECTRAL_IDENTIFIER ) {
+/* ------------------------------------------------------------------ */
+
+/* Indicate we have finished any preceding velocity sub-phrase. */
+         velid = NULL_ID;
+
+/* Now check the word to see if it a known spectral sub-phrase identifier. */
+         if( astChrMatch( word, "SpectralInterval" ) ) {
+            look_for = FILL_FACTOR;         /* Move on to find the fill factor */
+            specid = SPECTRAL_INTERVAL_ID;
+
+         } else if( astChrMatch( word, "Spectral" ) ) {
+            look_for = REFPOS;              /* Move on to find the refpos */
+            specid = SPECTRAL_ID;
+
+/* If the word is not a known spectral sub-phrase identifier, move on to
+   look for the Redshift sub-phrase. */
+         } else {
+            look_for = REDSHIFT_IDENTIFIER;
+            new_word = 0;
+         }
+
+/* If we have found a spectral sub-phrase identifier, create a KeyMap to
+   hold the properties of the spectral sub-phrase, and store the spectral
+   sub-phrase identifier in the new KeyMap. */
+         if( specid != NULL_ID ) {
+            subphrase = "spectral";
+            if( props ) props = astAnnul( props );
+            props = astKeyMap( " ", status );
+            astMapPut0A( result, "SPECTRAL_PROPS", props, NULL );
+            astMapPut0C( props, "ID", word, NULL );
+            naxes = 1;
+         }
+
+
+
+/* If we are currently looking for the redshift sub-phrase, see if the
+   word is any of the known redshift sub-phrase identifiers. Is so, move
+   on to read the associated sub-phrase component. */
+      } else if( look_for == REDSHIFT_IDENTIFIER ) {
+/* ------------------------------------------------------------------ */
+
+/* Indicate we have finished any preceding spectral sub-phrase. */
+         specid = NULL_ID;
+
+/* Now check the word to see if it a known spectral sub-phrase identifier. */
+         if( astChrMatch( word, "RedshiftInterval" ) ) {
+            look_for = FILL_FACTOR;       /* Move on to find the fill factor */
+            redid = REDSHIFT_INTERVAL_ID;
+
+         } else if( astChrMatch( word, "Redshift" ) ) {
+            look_for = REFPOS;            /* Move on to find the refpos */
+            redid = REDSHIFT_ID;
+
+/* If the word is not a known redshift sub-phrase identifier, report a
+   warning. */
+         } else if( word[ 0 ] && astOK ) {
+            astError( AST__BADIN, "astRead(%s): Unsupported or irrelevant "
+                      "word '%s' found in STC-S %s sub-phrase: '%s'.", status,
+                      astGetClass( this ), word, subphrase,
+                      ContextFragment( &con, &fbuf, status ) );
+            new_word = 0;
+         }
+
+/* If we have found a redshift sub-phrase identifier, create a KeyMap to
+   hold the properties of the redshift sub-phrase, and store the redshift
+   sub-phrase identifier in the new KeyMap. */
+         if( redid != NULL_ID ) {
+            subphrase = "redshift";
+            if( props ) props = astAnnul( props );
+            props = astKeyMap( " ", status );
+            astMapPut0A( result, "REDSHIFT_PROPS", props, NULL );
+            astMapPut0C( props, "ID", word, NULL );
+            naxes = 1;
+         }
+
+/* Indicate we can now end when we run out of input words. */
+         con.done = 1;
+
+
+
+/* If we are currently looking for a fill factor... */
+      } else if( look_for == FILL_FACTOR ) {
+/* ------------------------------------------------------------------ */
+
+/* If the current word is "fillfactor" attempt to read the numerical filling
+   factor from the next word. If this fails, or if the current word is
+   not "fillfactor", indicate that we will be re-interpreting the current
+   word in a new context and so do not need a new word. */
+         if( astChrMatch( word, "fillfactor" ) ) {
+            word = GetNextWord( this, &con, status );
+            if( astChr2Double( word ) == AST__BAD ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected a numerical "
+                         "filling factor, but found '%s' in the %s "
+                         "sub-phrase of STC-S description: '%s'.", status,
+                          word, subphrase, ContextFragment( &con, &fbuf,
+                                                            status ) );
+               new_word = 0;
+            }
+         } else {
+            new_word = 0;
+         }
+
+/* If we are reading a time sub-phrase, move on to read the timescale. */
+         if( timeid != NULL_ID ) {
+            look_for = TIME_SCALE;
+
+/* If we are reading a space sub-phrase, move on to read the frame. */
+         } else if( spaceid != NULL_ID ) {
+            look_for = FRAME;
+
+/* If we are reading a velocity sub-phrase, move on to read the limits. */
+         } else if( velid != NULL_ID ) {
+            look_for = LIMITS;
+
+/* Otherwise (i.e. for spectral and redshift sub-phrases) move on to read
+   the refpos. */
+         } else {
+            look_for = REFPOS;
+         }
+
+/* If the word was usable, record it as the fillfactor property. */
+         if( new_word ) astMapPut0C( props, "FILLFACTOR", word, NULL );
+
+
+
+/* If we are currently looking for a time scale... */
+      } else if( look_for == TIME_SCALE ) {
+/* ------------------------------------------------------------------ */
+
+/* If the current word is a recognised STC-S timescale, store it in the
+   props KeyMap. Otherwise, indicate that the word can be re-used in the
+   next context. */
+         if( astChrMatch( word, "TT" )  ||
+             astChrMatch( word, "TDT" ) ||
+             astChrMatch( word, "ET" )  ||
+             astChrMatch( word, "TAI" ) ||
+             astChrMatch( word, "IAT" ) ||
+             astChrMatch( word, "UTC" ) ||
+             astChrMatch( word, "TEB" ) ||
+             astChrMatch( word, "TDB" ) ||
+             astChrMatch( word, "TCG" ) ||
+             astChrMatch( word, "TCB" ) ||
+             astChrMatch( word, "LST" ) ||
+             astChrMatch( word, "nil" ) ) {
+
+            astMapPut0C( props, "TIMESCALE", word, NULL );
+
+         } else {
+            new_word = 0;
+         }
+
+/* Move on to look for a refpos */
+         look_for = REFPOS;
+
+
+
+/* If we are currently looking for a space frame... */
+      } else if( look_for == FRAME ) {
+/* ------------------------------------------------------------------ */
+
+/* If the current word is a recognised STC-S spatial frame, store it in
+   the props KeyMap. Otherwise, indicate that the word can be re-used. */
+         if( astChrMatch( word, "ICRS" ) ||
+             astChrMatch( word, "FK5" ) ||
+             astChrMatch( word, "FK4" ) ||
+             astChrMatch( word, "J2000" ) ||
+             astChrMatch( word, "B1950" ) ||
+             astChrMatch( word, "ECLIPTIC" ) ||
+             astChrMatch( word, "GALACTIC" ) ||
+             astChrMatch( word, "GALACTIC_II" ) ||
+             astChrMatch( word, "SUPER_GALACTIC" ) ||
+             astChrMatch( word, "GEO_C" ) ||
+             astChrMatch( word, "GEO_D" ) ||
+             astChrMatch( word, "UNKNOWNFrame" ) ) {
+
+            astMapPut0C( props, "FRAME", word, NULL );
+
+         } else {
+            new_word = 0;
+         }
+
+/* Move on to look for a refpos */
+         look_for = REFPOS;
+
+
+
+/* If we are currently looking for a refpos... */
+      } else if( look_for == REFPOS ) {
+/* ------------------------------------------------------------------ */
+
+/* If the current word is a recognised STC-S reference position, store it in
+   the props KeyMap. Otherwise, indicate that the word can be re-used. The
+   first group of reference positions apply to all sub-phrases. */
+         if( astChrMatch( word, "GEOCENTER" ) ||
+             astChrMatch( word, "BARYCENTER" ) ||
+             astChrMatch( word, "HELIOCENTER" ) ||
+             astChrMatch( word, "TOPOCENTER" ) ||
+             astChrMatch( word, "GALACTIC_CENTER" ) ||
+             astChrMatch( word, "EMBARYCENTER" ) ||
+             astChrMatch( word, "MOON" ) ||
+             astChrMatch( word, "MERCURY" ) ||
+             astChrMatch( word, "VENUS" ) ||
+             astChrMatch( word, "MARS" ) ||
+             astChrMatch( word, "JUPITER" ) ||
+             astChrMatch( word, "SATURN" ) ||
+             astChrMatch( word, "URANUS" ) ||
+             astChrMatch( word, "NEPTUNE" ) ||
+             astChrMatch( word, "PLUTO" ) ||
+             astChrMatch( word, "UNKNOWNRefPos" ) ) {
+
+            astMapPut0C( props, "REFPOS", word, NULL );
+
+/* This group of reference positions apply only to spectral and redshift
+   sub-phrases. */
+         } else if( astChrMatch( word, "LSR" ) ||
+                    astChrMatch( word, "LSRK" ) ||
+                    astChrMatch( word, "LSRD" ) ||
+                    astChrMatch( word, "LOCAL_GROUP_CENTER" ) ) {
+
+            if( specid != NULL_ID || redid != NULL_ID ) {
+               astMapPut0C( props, "REFPOS", word, NULL );
+
+            } else if( astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Illegal reference "
+                         "position '%s' found in the %s sub-phrase of "
+                         "STC-S description: '%s'.", status, word,
+                         subphrase, ContextFragment( &con, &fbuf, status ) );
+               new_word = 0;
+            }
+
+         } else {
+            new_word = 0;
+         }
+
+/* Choose what to look for next on the basis of the type of sub-phrase
+   currently being interpreted. */
+         if( timeid == TIME_INTERVAL_ID ){
+            look_for = START;   /* Move on to find the start time */
+
+         } else if( timeid == START_TIME_ID ){
+            look_for = START;   /* Move on to find the start time */
+
+         } else if( timeid == STOP_TIME_ID ){
+            look_for = STOP;    /* Move on to find the stop time */
+
+         } else if( timeid == TIME_ID ){
+            look_for = TIME;    /* Move on to find the time */
+
+         } else if( spaceid != NULL_ID ){
+            look_for = FLAVOUR; /* Move on to find the spatial flavour */
+
+         } else if( specid == SPECTRAL_INTERVAL_ID ) {
+            look_for = LIMITS;  /* Move on to find the spectral limits */
+
+         } else if( specid == SPECTRAL_ID ) {
+            look_for = RED_SPEC_VALUE; /* Move on to find the spectral value */
+
+         } else if( redid == REDSHIFT_INTERVAL_ID ) {
+            look_for = TYPE_DOPPLER;   /* Move on to find the redshift type */
+
+         } else if( redid == REDSHIFT_ID ) {
+            look_for = TYPE_DOPPLER;   /* Move on to find the redshift type */
+
+         } else if( astOK ) {  /* Should never happen */
+             astError( AST__INTER, "astRead(StcsChan): Sanity check 1 fails in "
+                       "function ReadProps (AST internal programming error).",
+                       status );
+            new_word = 0;
+         }
+
+
+
+
+
+/* If we are currently looking for a start time... */
+      } else if( look_for == START ) {
+/* ------------------------------------------------------------------ */
+
+/* Save the current word as the start of the START value. */
+         nc = 0;
+         prop = astAppendString( prop, &nc, word );
+
+/* If the current word is "JD" or "MJD", the following word should be
+   numerical. */
+         is_jd = astChrMatch( word, "JD" );
+         if( is_jd || astChrMatch( word, "MJD" ) ) {
+            word = GetNextWord( this, &con, status );
+            value = astChr2Double( word );
+            if( value == AST__BAD && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected numerical "
+                         "value in Start time, but found '%s %s' in STC-S "
+                         "description: '%s'.", status, prop, word,
+                         ContextFragment( &con, &fbuf, status ) );
+
+/* Append the second word to the first word. */
+            } else {
+               prop = astAppendString( prop, &nc, " " );
+               prop = astAppendString( prop, &nc, word );
+            }
+
+/* Convert JD to MJD if required. */
+            start = is_jd ? value - 2400000.5 : value;
+
+/* Otherwise, the current word should be an ISO date. Use a TimeFrame
+   to check the string. */
+         } else {
+            if( !timefrm ) timefrm = astTimeFrame( " ", status );
+            if( !astUnformat( timefrm, 0, word, &start ) && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected ISO date "
+                         "string Start time, but found '%s' in an STC-S "
+                         "description: '%s'.", status, word,
+                         ContextFragment( &con, &fbuf, status ) );
+            }
+         }
+
+/* Record the START property. */
+         astMapPut0C( props, "START", prop, NULL );
+         astMapPut0D( props, "MJDSTART", start, NULL );
+
+/* Decide what to do next. */
+         if( timeid == TIME_INTERVAL_ID ){
+            look_for = STOP;        /* Move on to find the stop time */
+
+         } else if( timeid == START_TIME_ID ){
+            look_for = TIME_LABEL;  /* Move on to find the "coord" time */
+
+         }
+
+
+
+/* If we are currently looking for a stop time... */
+      } else if( look_for == STOP ) {
+/* ------------------------------------------------------------------ */
+
+/* Save the current word as the start of the STOP value. */
+         nc = 0;
+         prop = astAppendString( prop, &nc, word );
+
+/* If the current word is "JD" or "MJD", the following word should be
+   numerical. */
+         is_jd = astChrMatch( word, "JD" );
+         if( is_jd || astChrMatch( word, "MJD" ) ) {
+            word = GetNextWord( this, &con, status );
+            value = astChr2Double( word );
+            if( value == AST__BAD && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected numerical "
+                         "value in Stop time, but found '%s %s' in STC-S "
+                         "description: '%s'.", status, prop, word,
+                         ContextFragment( &con, &fbuf, status ) );
+
+/* Append the second word to the first word. */
+            } else {
+               prop = astAppendString( prop, &nc, " " );
+               prop = astAppendString( prop, &nc, word );
+            }
+
+/* Convert JD to MJD if required. */
+            stop = is_jd ? value - 2400000.5 : value;
+
+/* Otherwise, the current word should be an ISO date. Use a TimeFrame
+   to check the string. */
+         } else {
+            if( !timefrm ) timefrm = astTimeFrame( " ", status );
+            if( !astUnformat( timefrm, 0, word, &stop ) && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected ISO date "
+                         "string Stop time, but found '%s' in an STC-S "
+                         "description: '%s'.", status, word,
+                         ContextFragment( &con, &fbuf, status ) );
+            }
+         }
+
+/* Record the STOP property. */
+         astMapPut0C( props, "STOP", prop, NULL );
+         astMapPut0D( props, "MJDSTOP", stop, NULL );
+
+/* Move on to find the "coord" time. */
+         look_for = TIME_LABEL;
+
+
+
+/* If we are currently looking for the label before a time coord value... */
+      } else if( look_for == TIME_LABEL ) {
+/* ------------------------------------------------------------------ */
+         if( astChrMatch( word, "Time" ) ) {
+            look_for = TIME;
+         } else {
+            new_word = 0;
+            look_for = UNIT;
+         }
+
+
+
+/* If we are currently looking for a time... */
+      } else if( look_for == TIME ) {
+/* ------------------------------------------------------------------ */
+
+/* Save the current word as the start of the TIME value. */
+         nc = 0;
+         prop = astAppendString( prop, &nc, word );
+
+/* If the current word is "JD" or "MJD", the following word should be
+   numerical. */
+         is_jd = astChrMatch( word, "JD" );
+         if( is_jd || astChrMatch( word, "MJD" ) ) {
+            word = GetNextWord( this, &con, status );
+            value = astChr2Double( word );
+            if( value == AST__BAD && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected numerical "
+                         "value in Time value, but found '%s %s' in STC-S "
+                         "description: '%s'.", status, prop, word,
+                         ContextFragment( &con, &fbuf, status ) );
+
+/* Append the second word to the first word. */
+            } else {
+               prop = astAppendString( prop, &nc, " " );
+               prop = astAppendString( prop, &nc, word );
+            }
+
+/* Convert JD to MJD if required. */
+            time = is_jd ? value - 2400000.5 : value;
+
+/* Otherwise, the current word should be an ISO date. Use a TimeFrame
+   to check the string. */
+         } else {
+            if( !timefrm ) timefrm = astTimeFrame( " ", status );
+            if( !astUnformat( timefrm, 0, word, &time ) && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected ISO date "
+                         "string Time value, but found '%s' in an STC-S "
+                         "description: '%s'.", status, word,
+                         ContextFragment( &con, &fbuf, status ) );
+            }
+         }
+
+/* Record the TIME property. */
+         astMapPut0C( props, "TIME", prop, NULL );
+         astMapPut0D( props, "MJDTIME", time, NULL );
+
+/* Move on to look for the units. */
+         look_for = UNIT;
+
+
+
+/* If we are currently looking for a space "flavor"... */
+      } else if( look_for == FLAVOUR ) {
+/* ------------------------------------------------------------------ */
+
+/* If the current word is a recognised flavour value, note how many axis
+   values are required to specify a position. Otherwise, indicate that
+   the word can be re-used. */
+         if( astChrMatch( word, "SPHER2" ) ) {
+            naxes = 2;
+
+         } else if( astChrMatch( word, "UNITSPHER" ) ) {
+            naxes = 2;
+
+         } else if( astChrMatch( word, "CART1" ) ) {
+            naxes = 1;
+
+         } else if( astChrMatch( word, "CART2" ) ) {
+            naxes = 2;
+
+         } else if( astChrMatch( word, "CART3" ) ) {
+            naxes = 3;
+
+         } else if( astChrMatch( word, "SPHER3" ) ) {
+            naxes = 3;
+
+         } else {
+            naxes = 2;
+            new_word = 0;
+         }
+
+/* If the word was recognised as a flavour, store it in the porperties
+   KeyMap. */
+         if( new_word ) {
+            astMapPut0C( props, "FLAVOR", word, NULL );
+            astMapPut0C( props, "FLAVOUR", word, NULL );
+         }
+
+/* The next set of words to be read from the source function will specify
+   the arguments of the region enclosing the spatial positions. This may
+   contain nested regions, so use a recursive function to read the
+   arguments and store them in the properties KeyMap. */
+         if( new_word ) word = GetNextWord( this, &con, status );
+         word = ReadSpaceArgs( this, word, spaceid, naxes, &con, props,
+                               status );
+         new_word = 0;
+
+/* Move on to the next look_for (following the region argument list read
+   by ReadSpaceArgs). */
+         if( spaceid == POSITION_ID ) {
+            look_for = UNIT;
+         } else {
+            look_for = POSITION;
+         }
+
+
+
+/* If we are currently looking for interval "lolimit"and "hilimit" ... */
+      } else if( look_for == LIMITS ) {
+/* ------------------------------------------------------------------ */
+         if( velid != NULL_ID ) {
+            t = "velocity";
+            look_for = VELOCITY;
+
+         } else if( specid != NULL_ID ) {
+            t = "spectral";
+            look_for = RED_SPEC_LABEL;
+
+         } else {
+            t = "redshift";
+            look_for = RED_SPEC_LABEL;
+         }
+
+/* The current word should be a numerical value (the low limit ). */
+         if( astChr2Double( word ) == AST__BAD && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected a numerical "
+                      "value for a %s lolimit, but found '%s' in an STC-S "
+                      "description: '%s'.", status, t, word,
+                      ContextFragment( &con, &fbuf, status ) );
+         } else {
+            astMapPut0C( props, "LOLIMIT", word, NULL );
+         }
+
+/* The next word should be a numerical value (the high limit ). */
+         word = GetNextWord( this, &con, status );
+         if( astChr2Double( word ) == AST__BAD && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected a numerical "
+                      "value for a %s hilimit, but found '%s' in an STC-S "
+                      "description: '%s'.", status, t, word,
+                      ContextFragment( &con, &fbuf, status ) );
+         } else {
+            astMapPut0C( props, "HILIMIT", word, NULL );
+         }
+
+
+
+/* If we are currently looking for the label before a spectral or redshift
+   value... */
+      } else if( look_for == RED_SPEC_LABEL ) {
+/* ------------------------------------------------------------------ */
+         if( specid != NULL_ID && astChrMatch( word, "Spectral" ) ) {
+            look_for = RED_SPEC_VALUE;
+
+         } else if( redid != NULL_ID && astChrMatch( word, "Redshift" ) ) {
+            look_for = RED_SPEC_VALUE;
+
+         } else {
+            new_word = 0;
+            look_for = UNIT;
+         }
+
+
+
+/* If we are currently looking for an spectral or redshift value. */
+      } else if( look_for == RED_SPEC_VALUE ) {
+/* ------------------------------------------------------------------ */
+
+         t = ( specid != NULL_ID ) ? "spectral" : "redshift";
+         if( astChr2Double( word ) == AST__BAD && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected a numerical "
+                      "%s value, but found '%s' in an STC-S "
+                      "description: '%s'.", status, t, word,
+                      ContextFragment( &con, &fbuf, status ) );
+         } else {
+            astMapPut0C( props, ( specid != NULL_ID ) ? "SPECTRAL" : "REDSHIFT",
+                         word, NULL );
+         }
+
+/* Decide what to do next. */
+         look_for = UNIT;
+
+
+
+/* If we are currently looking for information needed to create a spatial
+   Position ... */
+      } else if( look_for == POSITION ) {
+/* ------------------------------------------------------------------ */
+
+/* Check the current word is "Position". If so, get the next word. */
+         if( astChrMatch( word, "Position" ) ) {
+            word = GetNextWord( this, &con, status );
+
+/* Get a value for every space axis. */
+            nc = 0;
+            for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+               val[ iaxis ] = astChr2Double( word );
+               if( val[ iaxis ] == AST__BAD && astOK ) {
+                  astError( AST__BADIN, "astRead(StcsChan): Expected another "
+                            "axis value for a space Position, but found "
+                            "'%s' in an STC-S description: '%s'.", status,
+                            word, ContextFragment( &con, &fbuf, status ) );
+               }
+               prop = astAppendString( prop, &nc, word );
+               prop = astAppendString( prop, &nc, " " );
+               word = GetNextWord( this, &con, status );
+            }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+            prop[ nc - 1 ] = 0;
+            astMapPut0C( props, "POSITION", prop, NULL );
+            astMapPut1D( props, "DPOSITION", naxes, val, NULL );
+         }
+
+/* Move on to read the "unit" item. */
+         new_word = 0;
+         look_for = UNIT;
+
+
+
+/* If we are currently looking for the redshift type and doppler
+   definition ... */
+      } else if( look_for == TYPE_DOPPLER ) {
+/* ------------------------------------------------------------------ */
+
+         if( astChrMatch( word, "VELOCITY" ) ||
+             astChrMatch( word, "REDSHIFT" ) ) {
+            astMapPut0C( props, "TYPE", word, NULL );
+            word = GetNextWord( this, &con, status );
+         }
+
+         if( astChrMatch( word, "OPTICAL" ) ||
+             astChrMatch( word, "RADIO" ) ||
+             astChrMatch( word, "RELATIVISTIC" ) ) {
+            astMapPut0C( props, "DOPPLERDEF", word, NULL );
+         } else {
+            new_word = 0;
+         }
+
+/* Decide what to do next. */
+         look_for = ( redid == REDSHIFT_INTERVAL_ID ) ? LIMITS : RED_SPEC_VALUE;
+
+
+
+/* If we are currently looking for a velocity label and value... */
+      } else if( look_for == VELOCITY ) {
+/* ------------------------------------------------------------------ */
+
+         if( astChrMatch( word, "Velocity" ) ) {
+            word = GetNextWord( this, &con, status );
+            if( astChr2Double( word ) == AST__BAD && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected a "
+                         "numerical Velocity value but found 'Velocity %s' "
+                         "in an STC-S description: '%s'.", status, word,
+                         ContextFragment( &con, &fbuf, status ) );
+            }
+
+         } else {
+            new_word = 0;
+         }
+
+         look_for = UNIT;
+
+
+
+/* If we are currently looking for a "unit" string... */
+      } else if( look_for == UNIT ) {
+/* ------------------------------------------------------------------ */
+
+/* See if the current word is "unit". If so, read the next word (which
+   will be the unit string itself). Otherwise, indicate the current word
+   can be re-used. */
+         if( astChrMatch( word, "unit" ) ) {
+            word = GetNextWord( this, &con, status );
+         } else {
+            new_word = 0;
+         }
+
+/* If we have a unit string... */
+         if( new_word ) {
+
+/* Check that the unit string is one of the allowed values (different
+   values are allowed for different sub-phrases). Space frames can have
+   multiple units strings (one for each axis) so loop round until a string
+   is found which is not a valid unit string. */
+            nc = 0;
+            nunit = 0;
+            while( ( timeid != NULL_ID && (  !strcmp( word, "s" ) ||
+                                          !strcmp( word, "d" ) ||
+                                          !strcmp( word, "a" ) ||
+                                          !strcmp( word, "yr" ) ||
+                                          !strcmp( word, "cy" ) ) ) ||
+
+                ( spaceid != NULL_ID && ( !strcmp( word, "deg" ) ||
+                                          !strcmp( word, "arcmin" ) ||
+                                          !strcmp( word, "arcsec" ) ||
+                                          !strcmp( word, "m" ) ||
+                                          !strcmp( word, "mm" ) ||
+                                          !strcmp( word, "m" ) ||
+                                          !strcmp( word, "km" ) ||
+                                          !strcmp( word, "AU" ) ||
+                                          !strcmp( word, "pc" ) ||
+                                          !strcmp( word, "kpc" ) ||
+                                          !strcmp( word, "Mpc" ) ) ) ||
+
+                ( velid != NULL_ID && (   !strcmp( word, "deg" ) ||
+                                          !strcmp( word, "arcmin" ) ||
+                                          !strcmp( word, "arcsec" ) ||
+                                          !strcmp( word, "m" ) ||
+                                          !strcmp( word, "mm" ) ||
+                                          !strcmp( word, "km" ) ||
+                                          !strcmp( word, "AU" ) ||
+                                          !strcmp( word, "pc" ) ||
+                                          !strcmp( word, "kpc" ) ||
+                                          !strcmp( word, "Mpc" ) ) ) ||
+
+                (                         !strcmp( word, "Hz" ) ||
+                                          !strcmp( word, "MHz" ) ||
+                                          !strcmp( word, "GHz" ) ||
+                                          !strcmp( word, "m" ) ||
+                                          !strcmp( word, "mm" ) ||
+                                          !strcmp( word, "um" ) ||
+                                          !strcmp( word, "nm" ) ||
+                                          !strcmp( word, "Angstrom" ) ||
+                                          !strcmp( word, "eV" ) ||
+                                          !strcmp( word, "keV" ) ||
+                                          !strcmp( word, "MeV" ) ) ) {
+
+               prop = astAppendString( prop, &nc, word );
+               prop = astAppendString( prop, &nc, " " );
+               nunit++;
+               word = GetNextWord( this, &con, status );
+            }
+
+/* Report an error if an inappropriate number of valid unit strings was
+   found. */
+            if( nunit == 0 && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Unsupported "
+                         "units (%s) for the %s sub-phrase within an "
+                         "STC-S description: '%s'.", status, word, subphrase,
+                         ContextFragment( &con, &fbuf, status ) );
+
+            } else if( nunit != 1 && nunit != naxes && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Incorrect number of "
+                         "units string (%d) supplied for the %s sub-phrase within an "
+                         "STC-S description: '%s'.", status, nunit, subphrase,
+                         ContextFragment( &con, &fbuf, status ) );
+
+/* Otherwise, remove the trailing space, and store the property value in the
+   KeyMap. */
+            } else {
+               prop[ nc - 1 ] = 0;
+               astMapPut0C( props, "UNIT", prop, NULL );
+            }
+
+/* The current word is the first word that was not a valid unit string,
+   and so can be re-used. */
+            new_word = 0;
+         }
+
+/* Move on to find the errors. */
+         look_for = ERROR;
+
+
+
+/* If we are currently looking for an "Error" string... */
+      } else if( look_for == ERROR ) {
+/* ------------------------------------------------------------------ */
+
+/* If the current word is "Error" read all subsequent words until the first
+   non-numerical value is encountered. */
+         if( astChrMatch( word, "Error" ) ) {
+            word = GetNextWord( this, &con, status );
+            value = astChr2Double( word );
+
+            nc = 0;
+            nval = 0;
+            while( value != AST__BAD ) {
+               if( nval < MAXVAL ) {
+                  val[ nval++ ] = value;
+                  prop = astAppendString( prop, &nc, word );
+                  prop = astAppendString( prop, &nc, " " );
+                  word = GetNextWord( this, &con, status );
+                  value = astChr2Double( word );
+               } else {
+                  astError( AST__BADIN, "astRead(StcsChan): Too many (more "
+                            "than %d) numerical values found for the Error "
+                            "property of the %s sub-phrase within an STC-S "
+                            "description: '%s'.", status, MAXVAL, subphrase,
+                            ContextFragment( &con, &fbuf, status ) );
+                  break;
+               }
+            }
+
+/* Report an error if no numerical error values were found. */
+            if( nval == 0 && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected a "
+                         "numerical error value but found 'Error %s' "
+                         "for the %s sub-phrase within an "
+                         "STC-S description: '%s'.", status, word, subphrase,
+                         ContextFragment( &con, &fbuf, status ) );
+
+/* Otherwise, remove the trailing space and store the concatenated
+   string of formatted values in the properties KeyMap. Also store a
+   corresponding vector of floating point values in the KeyMap. */
+            } else {
+               prop[ nc - 1 ] = 0;
+               astMapPut0C( props, "ERROR", prop, NULL );
+               astMapPut1D( props, "DERROR", nval, val, NULL );
+            }
+         }
+
+/* Indicate that we do not need to get a new word (we can re-use the last
+   one that turned out not to be a numerical value above). */
+         new_word = 0;
+
+/* Next look for Resolution */
+         look_for = RESOLUTION;
+
+
+
+/* If we are currently looking for a "Resolution" string... */
+      } else if( look_for == RESOLUTION ) {
+/* ------------------------------------------------------------------ */
+
+/* If the current word is "Resolution" read all subsequent words until the
+   first non-numerical value is encountered. */
+         if( astChrMatch( word, "Resolution" ) ) {
+            word = GetNextWord( this, &con, status );
+            value = astChr2Double( word );
+
+            nc = 0;
+            nval = 0;
+            while( value != AST__BAD ) {
+               if( nval < MAXVAL ) {
+                  val[ nval++ ] = value;
+                  prop = astAppendString( prop, &nc, word );
+                  prop = astAppendString( prop, &nc, " " );
+                  word = GetNextWord( this, &con, status );
+                  value = astChr2Double( word );
+               } else {
+                  astError( AST__BADIN, "astRead(StcsChan): Too many (more "
+                            "than %d) numerical values found for the Resolution "
+                            "property of the %s sub-phrase within an STC-S "
+                            "description: '%s'.", status, MAXVAL, subphrase,
+                            ContextFragment( &con, &fbuf, status ) );
+                  break;
+               }
+            }
+
+/* Report an error if no numerical values were found. */
+            if( nval == 0 && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected a "
+                         "numerical resolution value but found 'Resolution %s' "
+                         "for the %s sub-phrase within an STC-S description:"
+                         " '%s'.", status, word, subphrase,
+                         ContextFragment( &con, &fbuf, status ) );
+
+/* Otherwise, remove the trailing space and store the concatenated
+   string of formatted values in the properties KeyMap. Also store a
+   corresponding vector of floating point values in the KeyMap. */
+            } else {
+               prop[ nc - 1 ] = 0;
+               astMapPut0C( props, "RESOLUTION", prop, NULL );
+               astMapPut1D( props, "DRESOLUTION", nval, val, NULL );
+            }
+         }
+
+/* Indicate that we do not need to get a new word (we can re-use the last
+   one that turned out not to be a numerical value above). */
+         new_word = 0;
+
+/* Next look for Size. */
+         look_for = SIZE;
+
+
+
+/* If we are currently looking for a spatial "Size" string... */
+      } else if( look_for == SIZE ) {
+/* ------------------------------------------------------------------ */
+
+/* If the current word is "Size" read all subsequent words until the
+   first non-numerical value is encountered. */
+         if( astChrMatch( word, "Size" ) ) {
+            word = GetNextWord( this, &con, status );
+            value = astChr2Double( word );
+
+            nc = 0;
+            nval = 0;
+            while( value != AST__BAD ) {
+               if( nval < MAXVAL ) {
+                  val[ nval++ ] = value;
+                  prop = astAppendString( prop, &nc, word );
+                  prop = astAppendString( prop, &nc, " " );
+                  word = GetNextWord( this, &con, status );
+                  value = astChr2Double( word );
+               } else {
+                  astError( AST__BADIN, "astRead(StcsChan): Too many (more "
+                            "than %d) numerical values found for the Size "
+                            "property of the %s sub-phrase within an STC-S "
+                            "description: '%s'.", status, MAXVAL, subphrase,
+                            ContextFragment( &con, &fbuf, status ) );
+                  break;
+               }
+            }
+
+/* Report an error if no numerical values were found. */
+            if( nval == 0 && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected a "
+                         "numerical size value but found 'Size %s' "
+                         "for the %s sub-phrase within an STC-S description:"
+                         " '%s'.", status, word, subphrase,
+                         ContextFragment( &con, &fbuf, status ) );
+
+/* Otherwise, remove the trailing space and store the concatenated
+   string of formatted values in the properties KeyMap. Also store a
+   corresponding vector of floating point values in the KeyMap. */
+            } else {
+               prop[ nc - 1 ] = 0;
+               astMapPut0C( props, "SIZE", prop, NULL );
+               astMapPut1D( props, "DSIZE", nval, val, NULL );
+            }
+         }
+
+/* Indicate that we do not need to get a new word (we can re-use the last
+   one that turned out not to be a numerical value above). */
+         new_word = 0;
+
+/* Next look for PixSize. */
+         look_for = PIX_SIZE;
+
+
+
+/* If we are currently looking for a "PixSize" string... */
+      } else if( look_for == PIX_SIZE ) {
+/* ------------------------------------------------------------------ */
+
+/* If the current word is "PixSize" read all subsequent words until the
+   first non-numerical value is encountered. */
+         if( astChrMatch( word, "PixSize" ) ) {
+            word = GetNextWord( this, &con, status );
+            value = astChr2Double( word );
+
+            nc = 0;
+            nval = 0;
+            while( value != AST__BAD ) {
+               if( nval < MAXVAL ) {
+                  val[ nval++ ] = value;
+                  prop = astAppendString( prop, &nc, word );
+                  prop = astAppendString( prop, &nc, " " );
+                  word = GetNextWord( this, &con, status );
+                  value = astChr2Double( word );
+               } else {
+                  astError( AST__BADIN, "astRead(StcsChan): Too many (more "
+                            "than %d) numerical values found for the PixSize "
+                            "property of the %s sub-phrase within an STC-S "
+                            "description: '%s'.", status, MAXVAL, subphrase,
+                            ContextFragment( &con, &fbuf, status ) );
+                  break;
+               }
+            }
+
+/* Report an error if no numerical values were found. */
+            if( nval == 0 && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected a "
+                         "numerical pixel size but found 'PixSize %s' "
+                         "for the %s sub-phrase within an STC-S description:"
+                         " '%s'.", status, word, subphrase,
+                         ContextFragment( &con, &fbuf, status ) );
+
+/* Otherwise, remove the trailing space and store the concatenated
+   string of formatted values in the properties KeyMap. Also store a
+   corresponding vector of floating point values in the KeyMap. */
+            } else {
+               prop[ nc - 1 ] = 0;
+               astMapPut0C( props, "PIXSIZE", prop, NULL );
+               astMapPut1D( props, "DPIXSIZE", nval, val, NULL );
+            }
+         }
+
+/* Indicate that we do not need to get a new word (we can re-use the last
+   one that turned out not to be a numerical value above). */
+         new_word = 0;
+
+/* Next look for the next sub-phrase. */
+         if( timeid != NULL_ID ) {
+            look_for = SPACE_IDENTIFIER;
+
+         } else if( spaceid != NULL_ID ) {
+            look_for = VELOCITY_IDENTIFIER;
+
+         } else if( velid != NULL_ID ) {
+            look_for = SPECTRAL_IDENTIFIER;
+
+         } else if( specid != NULL_ID ) {
+            look_for = REDSHIFT_IDENTIFIER;
+
+         } else {
+            break;
+         }
+
+
+
+
+/* Report an error for any unknown look_for. */
+/* ------------------------------------------------------------------ */
+      } else if( astOK ) {
+         astError( AST__INTER, "astRead(StcsChan): Illegal look_for value "
+                   "(%d) encountered (internal AST programming error).",
+                   status, look_for );
+      }
+
+/* If required, get the next word in the STC-S description. */
+      if( new_word ) word = GetNextWord( this, &con, status );
+   }
+
+/* Free resources stored in the GetNextWord context structure. */
+   con.done = 1;
+   (void) GetNextWord( this, &con, status );
+   FreeContext( &con, status );
+
+/* Free other resources */
+   if( fbuf ) fbuf = astFree( fbuf );
+   if( prop ) prop = astFree( prop );
+   if( props ) props = astAnnul( props );
+   if( timefrm ) timefrm = astAnnul( timefrm );
+
+/* If an error occurred, clean up by deleting the new Object and
+   return a NULL pointer. */
+   if ( !astOK ) result = astDelete( result );
+
+/* Return the pointer to the properties KeyMap. */
+   return result;
+
+/* Undefine Local Constants: */
+#undef MAXVAL
+}
+
+static const char *ReadSpaceArgs( AstStcsChan *this, const char *word,
+                                  int spaceid, int naxes, WordContext *con,
+                                  AstKeyMap *props, int *status ){
+/*
+*  Name:
+*     ReadSpaceArgs
+
+*  Purpose:
+*     Read space region arguments from an STC-S description.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     const char *ReadSpaceArgs( AstStcsChan *this, const char *word,
+*                                int spaceid, int naxes, WordContext *con,
+*                                AstKeyMap *props, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function parses the list of space-separated words that form
+*     the argument list of a spatial region. These words are read from the
+*     source function, and stored in the supplied KeyMap using keys that
+*     identify their purpose.
+*
+*     This function calls itself recursively to handle compound regions.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan.
+*     word
+*        The first word of the argument list.
+*     spaceid
+*        An integer identifier for the type of spatial region for which
+*        arguments are being read.
+*     naxes
+*        Number of axes in the space frame.
+*     con
+*        Pointer to a structure holding context for use with the
+*        GetNextWord function. On exit, the next word returned by the
+*        GetNextWord function will be the first word following the
+*        argument list.
+*     props
+*        Pointer to the KeyMap in which the argument values should be
+*        stored.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the next wpord to be interpreted.
+
+*/
+
+
+/* Local Variables: */
+   AstKeyMap *new_props;   /* KeyMap holding properties of an argument region */
+   char *fbuf;             /* Pointer to buffer holding document fragment */
+   char *prop;             /* String property value */
+   char key[ 20 ];         /* Key for argument region */
+   double *p;              /* Pointer to next polygon vertex axis value */
+   double *temp;           /* Array of polygon vertex axis values */
+   double val;             /* Single numerical value */
+   double vals[ 6 ];       /* List of numerical values */
+   int iaxis;              /* Axis index */
+   int nc;                 /* Used length of string */
+   int new_spaceid;        /* Type of next argument region */
+   int nreg;               /* Number of argument regions found */
+   int nvert;              /* Number of vertices in polygon */
+
+/* Check inherited status */
+   if( !astOK ) return word;
+
+/* Initialise. */
+   fbuf = NULL;
+   prop = NULL;
+   nc = 0;
+
+/* If we are looking for information needed to create a spatial
+   Interval... */
+   if( spaceid == POSITION_INTERVAL_ID ) {
+
+/* Get a lolimit value for every space axis. */
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+         vals[ iaxis ] = astChr2Double( word );
+         if( vals[ iaxis ] == AST__BAD && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected another "
+                   "'lolimit' value for a PositionInterval, but found "
+                   "'%s' in an STC-S description: '%s'.", status, word,
+                   ContextFragment( con, &fbuf, status ) );
+         }
+         prop = astAppendString( prop, &nc, word );
+         prop = astAppendString( prop, &nc, " " );
+         word = GetNextWord( this, con, status );
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      if( prop && nc > 0 ) {
+         prop[ nc - 1 ] = 0;
+         astMapPut0C( props, "LOLIMIT", prop, NULL );
+         astMapPut1D( props, "DLOLIMIT", naxes, vals, NULL );
+      }
+
+/* Get a hilimit value for every space axis. */
+      nc = 0;
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+         vals[ iaxis ] = astChr2Double( word );
+         if( vals[ iaxis ] == AST__BAD && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected another "
+                   "'hilimit' value for a PositionInterval, but found "
+                   "'%s' in an STC-S description: '%s'.", status, word,
+                   ContextFragment( con, &fbuf, status ) );
+         }
+         prop = astAppendString( prop, &nc, word );
+         prop = astAppendString( prop, &nc, " " );
+         word = GetNextWord( this, con, status );
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      if( prop && nc > 0 ) {
+         prop[ nc - 1 ] = 0;
+         astMapPut0C( props, "HILIMIT", prop, NULL );
+         astMapPut1D( props, "DLOLIMIT", naxes, vals, NULL );
+      }
+
+/* If we are currently looking for information needed to create a spatial
+   AllSky ... */
+   } else if( spaceid == ALLSKY_ID ) {
+
+
+
+/* If we are currently looking for information needed to create a spatial
+   Circle ... */
+   } else if( spaceid == CIRCLE_ID ) {
+
+/* Get a centre value for every space axis. */
+      nc = 0;
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+         vals[ iaxis ] = astChr2Double( word );
+         if( vals[ iaxis ] == AST__BAD && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected another "
+                   "'centre' value for a Circle, but found "
+                   "'%s' in an STC-S description: '%s'.", status, word,
+                   ContextFragment( con, &fbuf, status ) );
+         }
+         prop = astAppendString( prop, &nc, word );
+         prop = astAppendString( prop, &nc, " " );
+         word = GetNextWord( this, con, status );
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      if( prop && nc > 0 ) {
+         prop[ nc - 1 ] = 0;
+         astMapPut0C( props, "CENTRE", prop, NULL );
+         astMapPut1D( props, "DCENTRE", naxes, vals, NULL );
+      }
+
+/* Get the radius value. */
+      val = astChr2Double( word );
+      if( val == AST__BAD && astOK ) {
+         astError( AST__BADIN, "astRead(StcsChan): Expected a radius "
+                   "value for a Circle, but found '%s' in an STC-S "
+                   "description: '%s'.", status, word,
+                   ContextFragment( con, &fbuf, status ) );
+      }
+
+/* Store the property value in the KeyMap. */
+      astMapPut0C( props, "RADIUS", word, NULL );
+
+/* Get the next word. */
+      word = GetNextWord( this, con, status );
+
+
+
+/* If we are currently looking for information needed to create a spatial
+   Ellipse ... */
+   } else if( spaceid == ELLIPSE_ID ) {
+
+/* Get a centre value for every space axis. */
+      nc = 0;
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+         vals[ iaxis ] = astChr2Double( word );
+         if( vals[ iaxis ] == AST__BAD && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected another "
+                   "centre value for an Ellipse, but found "
+                   "'%s' in an STC-S description: '%s'.", status, word,
+                   ContextFragment( con, &fbuf, status ) );
+         }
+         prop = astAppendString( prop, &nc, word );
+         prop = astAppendString( prop, &nc, " " );
+         word = GetNextWord( this, con, status );
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      if( prop && nc > 0 ) {
+         prop[ nc - 1 ] = 0;
+         astMapPut0C( props, "CENTRE", prop, NULL );
+         astMapPut1D( props, "DCENTRE", naxes, vals, NULL );
+      }
+
+/* Get the first radius value . */
+      val = astChr2Double( word );
+      if( val == AST__BAD && astOK ) {
+         astError( AST__BADIN, "astRead(StcsChan): Expected the first "
+                   "radius value for an Ellipse, but found "
+                   "'%s' in an STC-S description: '%s'.", status, word,
+                   ContextFragment( con, &fbuf, status ) );
+      }
+
+/* Store the property value in the KeyMap. */
+      astMapPut0C( props, "RADIUS1", word, NULL );
+
+/* Get the second radius value . */
+      word = GetNextWord( this, con, status );
+      val = astChr2Double( word );
+      if( val == AST__BAD && astOK ) {
+         astError( AST__BADIN, "astRead(StcsChan): Expected the second "
+                   "radius value for an Ellipse, but found "
+                   "'%s' in an STC-S description: '%s'.", status, word,
+                   ContextFragment( con, &fbuf, status ) );
+      }
+
+/* Store the property value in the KeyMap. */
+      astMapPut0C( props, "RADIUS2", word, NULL );
+
+/* Get the position angle value. */
+      word = GetNextWord( this, con, status );
+      val = astChr2Double( word );
+      if( val == AST__BAD && astOK ) {
+         astError( AST__BADIN, "astRead(StcsChan): Expected the position "
+                   "angle value for an Ellipse, but found "
+                   "'%s' in an STC-S description: '%s'.", status, word,
+                   ContextFragment( con, &fbuf, status ) );
+      }
+
+/* Store the property value in the KeyMap. */
+      astMapPut0C( props, "POSANGLE", word, NULL );
+
+/* Get the next word. */
+      word = GetNextWord( this, con, status );
+
+
+
+/* If we are currently looking for information needed to create a spatial
+   Box ... */
+   } else if( spaceid == BOX_ID ) {
+
+/* Get a centre value for every space axis. */
+      nc = 0;
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+         vals[ iaxis ] = astChr2Double( word );
+         if( vals[ iaxis ] == AST__BAD && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected another "
+                      "centre value for a Box, but found "
+                      "'%s' in an STC-S description: '%s'.", status,
+                      word, ContextFragment( con, &fbuf, status ) );
+         }
+         prop = astAppendString( prop, &nc, word );
+         prop = astAppendString( prop, &nc, " " );
+         word = GetNextWord( this, con, status );
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      if( prop && nc > 0 ) {
+         prop[ nc - 1 ] = 0;
+         astMapPut0C( props, "CENTRE", prop, NULL );
+         astMapPut1D( props, "DCENTRE", naxes, vals, NULL );
+      }
+
+/* Get bsize value for every space axis. */
+      nc = 0;
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+         vals[ iaxis ] = astChr2Double( word );
+         if( vals[ iaxis ] == AST__BAD && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected another "
+                      "'bsize' value for a Box, but found "
+                      "'%s' in an STC-S description: '%s'.", status,
+                      word, ContextFragment( con, &fbuf, status ) );
+         }
+         prop = astAppendString( prop, &nc, word );
+         prop = astAppendString( prop, &nc, " " );
+         word = GetNextWord( this, con, status );
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      if( prop && nc > 0 ) {
+         prop[ nc - 1 ] = 0;
+         astMapPut0C( props, "BSIZE", prop, NULL );
+         astMapPut1D( props, "DBSIZE", naxes, vals, NULL );
+      }
+
+
+/* If we are currently looking for information needed to create a spatial
+   Polygon ... */
+   } else if( spaceid == POLYGON_ID ) {
+
+/* Read the first vertex into a dynamically allocated array. */
+      temp = astMalloc( sizeof( *temp )*naxes );
+      if( temp ) {
+         nc = 0;
+         p = temp;
+         for( iaxis = 0; iaxis < naxes; iaxis++,p++ ) {
+            val = astChr2Double( word );
+            if( val == AST__BAD && astOK ) {
+               astError( AST__BADIN, "astRead(StcsChan): Expected another "
+                      "vertex value for a Polygon, but found "
+                      "'%s' in an STC-S description: '%s'.", status,
+                      word, ContextFragment( con, &fbuf, status ) );
+            } else {
+               *p = val;
+            }
+            prop = astAppendString( prop, &nc, word );
+            prop = astAppendString( prop, &nc, " " );
+            word = GetNextWord( this, con, status );
+         }
+
+/* Loop round reading remaining vertices, expanding the array as needed. */
+         nvert = 1;
+         val = astChr2Double( word );
+         while( val != AST__BAD && astOK ) {
+
+            temp = astGrow( temp, naxes*( nvert + 1 ), sizeof( *temp ) );
+            if( astOK ) {
+               p = temp + naxes*nvert;
+
+               for( iaxis = 0; iaxis < naxes; iaxis++, p++ ) {
+                  if( val == AST__BAD && astOK ) {
+                     astError( AST__BADIN, "astRead(StcsChan): Expected "
+                               "another vertex value for a Polygon, but "
+                               "found '%s' in an STC-S description: '%s'.",
+                               status, word, ContextFragment( con, &fbuf,
+                                                              status ) );
+                  } else {
+                     *p = val;
+                  }
+                  prop = astAppendString( prop, &nc, word );
+                  prop = astAppendString( prop, &nc, " " );
+                  word = GetNextWord( this, con, status );
+                  val = astChr2Double( word );
+               }
+               nvert++;
+            }
+         }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+         if( prop && nc > 0 ) {
+            prop[ nc - 1 ] = 0;
+            astMapPut0C( props, "VERTICES", prop, NULL );
+            astMapPut1D( props, "DVERTICES", naxes*nvert, temp, NULL );
+         }
+         temp = astFree( temp );
+      }
+
+
+
+/* If we are currently looking for information needed to create a spatial
+   Convex ... */
+   } else if( spaceid == CONVEX_ID ) {
+      astError( AST__BADIN, "astRead(StcsChan): A Convex was found "
+                "within an STC-S description ('Convex' regions "
+                "are not yet supported by AST): %s", status,
+                ContextFragment( con, &fbuf, status ) );
+
+
+
+/* If we are currently looking for information needed to create a spatial
+   Position ... */
+   } else if( spaceid == POSITION_ID ) {
+
+/* Get a value for every space axis. */
+      nc = 0;
+      for( iaxis = 0; iaxis < naxes; iaxis++ ) {
+         vals[ iaxis ] = astChr2Double( word );
+         if( vals[ iaxis ] == AST__BAD && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected another "
+                      "axis value for a space Position, but found "
+                      "'%s' in an STC-S description: '%s'.", status,
+                      word, ContextFragment( con, &fbuf, status ) );
+         }
+         prop = astAppendString( prop, &nc, word );
+         prop = astAppendString( prop, &nc, " " );
+         word = GetNextWord( this, con, status );
+      }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+      if( prop && nc > 0 ) {
+         prop[ nc - 1 ] = 0;
+         astMapPut0C( props, "POSITION", prop, NULL );
+         astMapPut1D( props, "DPOSITION", naxes, vals, NULL );
+      }
+
+
+/* All remaining space id values require the argument list to be enclosed
+   in parentheses. Report an error if the current word does not start
+   with an opening parenthesis. */
+   } else if( *word != '(' && astOK ) {
+      astError( AST__BADIN, "astRead(StcsChan): Expected an opening "
+                "parenthesis but found '%s' in an STC-S description: '%s'.",
+                status, word, ContextFragment( con, &fbuf, status ) );
+
+/* Skip over the opening parenthesis. If the first word consists of just the
+   opening parenthesis, get the next word.  */
+   } else {
+      if( *(++word) == 0 ) word = GetNextWord( this, con, status );
+
+/* Loop round all regions included in the compound region. */
+      nreg = 0;
+      while( astOK ) {
+
+/* If the next word starts with a closing parenthesis, we have reached
+   the end of the argument list. */
+         if( *word == ')' ) {
+
+/* Skip over the closing parenthesis. If the word consists of just the
+   closing parenthesis, get the next word.  */
+            if( *(++word) == 0 ) word = GetNextWord( this, con, status );
+
+/* Leave the loop. */
+            break;
+         }
+
+/* Identify the region type from the current word. */
+         new_spaceid = SpaceId( word, status );
+         if( new_spaceid == NULL_ID && astOK ) {
+            astError( AST__BADIN, "astRead(StcsChan): Expected a "
+                      "CoordinateArea or a closing parenthesis but found "
+                      "'%s' in an STC-S description: '%s'.", status, word,
+                      ContextFragment( con, &fbuf, status ) );
+         }
+
+/* Create a new KeyMap to store the properties of the new region. Store
+   this new KeyMap in the supplied KeyMap using a key of the form
+   "REGION<n>". */
+         new_props = astKeyMap( " ", status );
+         astMapPut0C( new_props, "ID", word, NULL );
+         sprintf( key, "REGION%d", ++nreg );
+         astMapPut0A( props, key, new_props, NULL );
+
+/* Get the next word (i.e. the first word of the argument list for the
+   region). */
+         word = GetNextWord( this, con, status );
+
+/* Call this function recursively to read the argument list. */
+         word = ReadSpaceArgs( this, word, new_spaceid, naxes, con,
+                               new_props, status );
+
+/* Free resources. */
+         new_props = astAnnul( new_props );
+      }
+
+/* Store the number of regions in the supplied KeyMap. */
+      astMapPut0I( props, "NREG", nreg, NULL );
+
+/* Report an error if an in appropriate number of argument Regions were
+   supplied. */
+      if( spaceid == UNION_ID ) {
+         if( nreg < 2 && astOK ){
+            astError( AST__BADIN, "astRead(StcsChan): Less than two "
+                      "CoordinateAreas found within a 'Union' element in an "
+                      "STC-S description: '%s'.", status,
+                      ContextFragment( con, &fbuf, status ) );
+         }
+
+      } else if( spaceid == INTERSECTION_ID ) {
+         if( nreg < 2 && astOK ){
+            astError( AST__BADIN, "astRead(StcsChan): Less than two "
+                      "CoordinateAreas found within an 'Intersection' element "
+                      "in an STC-S description: '%s'.", status,
+                      ContextFragment( con, &fbuf, status ) );
+         }
+
+      } else if( spaceid == DIFFERENCE_ID ) {
+         if( nreg != 2 && astOK ){
+            astError( AST__BADIN, "astRead(StcsChan): %d CoordinateArea(s) "
+                      "found within a 'Difference' element in an STC-S "
+                      "description: '%s'.", status, nreg,
+                      ContextFragment( con, &fbuf, status ) );
+         }
+
+
+      } else if( spaceid == NOT_ID ) {
+         if( nreg != 1 && astOK ){
+            astError( AST__BADIN, "astRead(StcsChan): %d CoordinateAreas "
+                      "found within a 'Not' element in an STC-S description: "
+                      "'%s'.", status, nreg,
+                      ContextFragment( con, &fbuf, status ) );
+         }
+
+/* Report an error for unknown spaceid values */
+      } else if( astOK ) {
+         astError( AST__INTER, "astRead(StcsChan): Illegal 'spaceid' value "
+                   "passed to function ReadSpaceArgs (internal AST "
+                   "programming error).", status );
+      }
+   }
+
+/* Free resources */
+   if( prop ) prop = astFree( prop );
+
+/* Return a pointer to the next word to be interpreted. */
+   return word;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a StcsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     StcsChan member function (over-rides the astSetAttrib protected
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function assigns an attribute value for a StcsChan, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstStcsChan *this;          /* Pointer to the StcsChan structure */
+   int ival;                   /* Integer attribute value */
+   int len;                    /* Length of setting string */
+   int nc;                     /* Number of characters read by "astSscanf" */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the StcsChan structure. */
+   this = (AstStcsChan *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* StcsArea. */
+/* --------- */
+   if ( nc = 0,
+               ( 1 == astSscanf( setting, "stcsarea= %d %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetStcsArea( this, ival );
+
+/* StcsCoords. */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "stcscoords= %d %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetStcsCoords( this, ival );
+
+/* StcsProps. */
+/* ----------- */
+   } else if ( nc = 0,
+               ( 1 == astSscanf( setting, "stcsprops= %d %n", &ival, &nc ) )
+               && ( nc >= len ) ) {
+      astSetStcsProps( this, ival );
+
+/* StcsLength */
+/* ----------*/
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "stcslength= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetStcsLength( this, ival );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SetUnc( AstRegion *reg1, AstRegion *reg2, AstFrame *frm,
+                    int is_skyframe, double scale, double *error, int nax,
+                    int *status ){
+/*
+*  Name:
+*     SetUnc
+
+*  Purpose:
+*     Store an uncertainty Box with a supplied Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     void SetUnc( AstRegion *reg1, AstRegion *reg2, AstFrame *frm,
+*                  int is_skyframe, double scale, double *error, int nax,
+*                  int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function creates a new Box with dimensions specified by the
+*     values in the "error" array, centred on a representative position
+*     within one of the supplied Regions, and then stores the Box as the
+*     uncertainty Region within both the supplied Regions.
+
+*  Parameters:
+*     reg1
+*        Pointer to a Region to which the error values relate.
+*     reg2
+*        Pointer to another Region to which the error values relate.
+*     frm
+*        Pointer to the Frame encapsulated by both Regions.
+*     is_skyframe
+*        Should be non-zero if "frm" is a SkyFrame.
+*     scale
+*        A scale factor to apply to the error values before using them.
+*     error
+*        Pointer to an array of RMS error values, one for each axis in
+*        "frm". These are modified on exit. For a SkyFrame, both values
+*        (including the longitude axis value) should be given as an
+*        arc-distance. This function will convert the arc-distance to
+*        a longitude increment using a representative latitude for the
+*        region.
+*     nax
+*        The numner of axes in "frm".
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstBox *unc;           /* Uncertainty box */
+   double dist;           /* Diagonal length of Region bounding box */
+   double lbnd[ 6 ];      /* Lower bounds of Region bounding box */
+   double spos1[ 6 ];     /* A representative position in the Region */
+   double spos2[ 6 ];     /* A second position in the Region */
+   double ubnd[ 6 ];      /* Upper bounds of Region bounding box */
+   int i;                 /* Axis index */
+
+/* Check the global error status. Also check an error value was supplied,
+   and at least one of the Region pointers is not NULL. */
+   if ( !astOK || error[ 0 ] == AST__BAD || ( !reg1 && !reg2 ) ) return;
+
+/* We need a representative position within the region. First get the
+   coordinates at opposite corners of the region bounding box. */
+   astRegBaseBox( reg1 ? reg1 : reg2, lbnd, ubnd );
+
+/* Find the diagonal length of the bounding box. */
+   dist = astDistance( frm, lbnd, ubnd );
+
+/* Offset away from one corner towards the other by half the diagonal
+   length. The resulting position returned in spos1 is our representative
+   position for the region. */
+   astOffset( frm, lbnd, ubnd, dist/2, spos1 );
+
+/* Scale the error values */
+   for( i = 0; i < nax; i++ ) error[ i ] *= scale;
+
+/* If the region is defined within a SkyFrame, the supplied longitude
+   error value will be an arc-distance value. But we need a longitude
+   increment to create an uncertainty Region, so do the conversion. */
+   if( is_skyframe ) {
+
+/* Offset away from the representative position found above along the
+   first (i.e. longitude) axis by an arc-distance given by the Error
+   value. */
+      (void) astOffset2( frm, spos1, AST__DPIBY2, error[ 0 ], spos2 );
+
+/* Find the positive axis increment along the first axis. */
+      error[ 0 ] = astAxDistance( frm, 1, spos1[ 0 ], spos2[ 0 ] );
+      if( error[ 0 ] != AST__BAD ) error[ 0 ] = fabs( error[ 0 ] );
+   }
+
+/* The uncertainty Region will be a Box centred at the representative
+   position found above. Modify the "error" array to hold the corner
+   axis values. */
+   for( i = 0; i < nax; i++ ) error[ i ] += spos1[ i ];
+
+/* Create the box, and store it as the uncertainty Region in the supplied
+   Region. */
+   unc = astBox( frm, 0, spos1, error, NULL, " ", status );
+   if( reg1 ) astSetUnc( reg1, unc );
+   if( reg2 ) astSetUnc( reg2, unc );
+
+/* Free resources. */
+   unc = astAnnul( unc );
+}
+
+static AstPointList *SinglePointList( AstFrame *frm, double *pos,
+                                      AstRegion *unc, int *status){
+/*
+*  Name:
+*     SinglePointList
+
+*  Purpose:
+*     Create a PointList holding a single point.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     AstPointList *SinglePointList( AstFrame *frm, double *pos,
+*                                    AstRegion *unc, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function creates a new PointList holding a single supplied
+*     position.
+
+*  Parameters:
+*     frm
+*        Pointer to the Frame in which the PointList is defined.
+*     pos
+*        Array holding the position. The length of this array must equal
+*        the number of axes in "frm".
+*     unc
+*        Pointer to an uncertainty Region to associate with the new
+*        PointList, or NULL.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new PointList. NULL is returned if an error has
+*     already occurred, of if this function fails for any reason.
+*/
+
+/* Local Variables: */
+   AstPointList *result;   /* Returned pointer. */
+   AstPointSet *pset;      /* PointSet holding axis values */
+   double **ptr;           /* Pointer to PointSet data arrays */
+   int i;                  /* Axis index */
+   int nax;                /* Number of axes */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get he number of axes. */
+   nax = astGetNaxes( frm );
+
+/* Create a PointSet to hold the supplied point, and get a pointer to its
+   data arrays. */
+   pset = astPointSet( 1, nax, "", status );
+   ptr = astGetPoints( pset );
+   if( astOK ) {
+
+/* Copy the supplied axis values into the PointSet data arrays. */
+      for( i = 0; i < nax; i++ ) ptr[ i ][ 0 ] = pos[ i ];
+
+/* Create the PointList. */
+      result = astPointList( frm, pset, unc, "", status );
+   }
+
+/* Free resources */
+   pset = astAnnul( pset );
+
+/* Return the result. */
+   return result;
+}
+
+static void SinkWrap( void (* sink)( const char * ), const char *line, int *status ) {
+/*
+*  Name:
+*     SinkWrap
+
+*  Purpose:
+*     Wrapper function to invoke a C StcsChan sink function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     void SinkWrap( void (* sink)( const char * ), const char *line, int *status )
+
+*  Class Membership:
+*     StcsChan member function.
+
+*  Description:
+*     This function invokes the sink function whose pointer is
+*     supplied in order to write an output line to an external data
+*     store.
+
+*  Parameters:
+*     sink
+*        Pointer to a sink function, whose single parameter is a
+*        pointer to a const, null-terminated string containing the
+*        text to be written, and which returns void. This is the form
+*        of StcsChan sink function employed by the C language interface
+*        to the AST library.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the sink function. */
+   ( *sink )( line );
+}
+
+static char *SourceWrap( const char *(* source)( void ), int *status ) {
+/*
+*  Name:
+*     SourceWrap
+
+*  Purpose:
+*     Wrapper function to invoke a C StcsChan source function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     char *SourceWrap( const char *(* source)( void ), int *status )
+
+*  Class Membership:
+*     StcsChan member function.
+
+*  Description:
+*     This function invokes the source function whose pointer is
+*     supplied in order to read the next input line from an external
+*     data store. It then returns a pointer to a dynamic string
+*     containing a copy of the text that was read.
+
+*  Parameters:
+*     source
+*        Pointer to a source function, with no parameters, that
+*        returns a pointer to a const, null-terminated string
+*        containing the text that it read. This is the form of StcsChan
+*        source function employed by the C language interface to the
+*        AST library.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated, null terminated string
+*     containing a copy of the text that was read. This string must be
+*     freed by the caller (using astFree) when no longer required.
+*
+*     A NULL pointer will be returned if there is no more input text
+*     to read.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   char *result;                 /* Pointer value to return */
+   const char *line;             /* Pointer to input line */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the source function to read the next input line and return a
+   pointer to the resulting string. */
+   line = ( *source )();
+
+/* If a string was obtained, make a dynamic copy of it and save the
+   resulting pointer. */
+   if ( line ) result = astString( line, (int) strlen( line ) );
+
+/* Return the result. */
+   return result;
+}
+
+static int SpaceId( const char *word, int *status ){
+/*
+*  Name:
+*     SpaceId
+
+*  Purpose:
+*     Return the integer identifier for a given textual space identifier.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     int SpaceId( const char *word, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function returns an integer identifier for the given space
+*     identifier.
+
+*  Parameters:
+*     word
+*        The word holding the textual space identifier.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The integer space identifier, or NULL_ID if the supplied word was
+*     not a known space identifier.
+
+*/
+
+
+/* Local Variables: */
+   int spaceid;            /* Returned identifier */
+
+/* Check inherited status */
+   if( !astOK ) return NULL_ID;
+
+   if( astChrMatch( word, "PositionInterval" ) ) {
+      spaceid = POSITION_INTERVAL_ID;
+
+   } else if( astChrMatch( word, "AllSky" ) ) {
+      spaceid = ALLSKY_ID;
+
+   } else if( astChrMatch( word, "Circle" ) ) {
+      spaceid = CIRCLE_ID;
+
+   } else if( astChrMatch( word, "Ellipse" ) ) {
+      spaceid = ELLIPSE_ID;
+
+   } else if( astChrMatch( word, "Box" ) ) {
+      spaceid = BOX_ID;
+
+   } else if( astChrMatch( word, "Polygon" ) ) {
+      spaceid = POLYGON_ID;
+
+   } else if( astChrMatch( word, "Convex" ) ) {
+      spaceid = CONVEX_ID;
+
+   } else if( astChrMatch( word, "Union" ) ) {
+      spaceid = UNION_ID;
+
+   } else if( astChrMatch( word, "Intersection" ) ) {
+      spaceid = INTERSECTION_ID;
+
+   } else if( astChrMatch( word, "Difference" ) ) {
+      spaceid = DIFFERENCE_ID;
+
+   } else if( astChrMatch( word, "Not" ) ) {
+      spaceid = NOT_ID;
+
+   } else if( astChrMatch( word, "Position" ) ) {
+      spaceid = POSITION_ID;
+
+   } else {
+      spaceid = NULL_ID;
+   }
+
+/* Return the integer space identifier. */
+   return spaceid;
+}
+
+static void StoreTimeProp( AstKeyMap *props, AstTimeFrame *frm,
+                           const char *key, double value, int *status ){
+/*
+*  Name:
+*     StoreTimeProp
+
+*  Purpose:
+*     Store a time value as an STC-S property, using the existing format.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     void StoreTimeProp( AstKeyMap *props, AstTimeFrame *frm,
+*                         const char *key, double value, int *status )
+
+*  Class Membership:
+*     StcsChan member function.
+
+*  Description:
+*     This function formats the supplied time value and stores it in
+*     the "props" KeyMap, using the supplied key name. If the KeyMap
+*     already contains an entry for the given key, the new value is
+*     written using the same format. Otherwise, the new value is written
+*     as an ISO date and time string.
+
+*  Parameters:
+*     props
+*        Pointer to the KeyMap in which to store the time value.
+*     frm
+*        Pointer to a TimeFrame that can be used to format the time value.
+*     key
+*        Pointer to a string holding the property name associated with
+*        the time value.
+*     value
+*        The time value, in the system described by "frm".
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstFrame *fmtfrm;    /* Frame defining Format/System for formatted value */
+   AstFrame *fs;        /* FrameSet connecting Frames */
+   const char *fmttxt;  /* Formatted text */
+   const char *oldval;  /* Pointer to old formatted time value */
+   const char *p;       /* Pointer to next character in formatted value */
+   double fmtval;       /* The time value in the formatting system */
+   int ndp;             /* Number of decimal places in formatted value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* We want a TimeFrame (fmtfrm) that describes how to format the time
+   value. If the Format attribute of the supplied TimeFrame has been
+   set, use it (and the current System). So just take a clone of the
+   supplied frame pointer. */
+   if( astTestFormat( frm, 0 ) ) {
+      fmtfrm = astClone( frm );
+
+/* If the Format attribute has not been set, we create a copy of the
+   supplied TimeFrame, and set its System and Format attributes to
+   produce the required format. */
+   } else {
+      fmtfrm = astCopy( frm );
+
+/* If the KeyMap contains an entry for the specified key, determine the
+   format of the time string it contains. */
+      if( astMapGet0C( props, key, &oldval ) && oldval ) {
+
+/* See how many digits there are after the decimal place */
+         p = strchr( oldval, '.' );
+         ndp = 0;
+         if( p ) {
+            while( *(++p) ) {
+               if( isdigit( *p ) ) {
+                  ndp++;
+               } else {
+                  break;
+               }
+            }
+         }
+
+/* If the string starts with "JD", the time is formatted as a numerical
+   Julian date. */
+         if( !strncmp( oldval, "JD", 2 ) ) {
+            astSetSystem( fmtfrm, AST__JD );
+            if( ndp > 0 ) {
+               astSet( fmtfrm, "Format=JD %%.%df", status, ndp );
+            } else {
+               astSetFormat( fmtfrm, 0, "JD %d" );
+            }
+
+/* If the string starts with "MJD", the time is formatted as a numerical
+   Modified Julian date. */
+         } else if( !strncmp( oldval, "MJD", 3 ) ) {
+            astSetSystem( fmtfrm, AST__MJD );
+            if( ndp > 0 ) {
+               astSet( fmtfrm, "Format=MJD %%.%df", status, ndp );
+            } else {
+               astSetFormat( fmtfrm, 0, "MJD %d" );
+            }
+
+/* Otherwise, the current word should be an ISO date. See how many
+   decimal paces in the seconds field there are (if any). */
+         } else {
+            astSet( fmtfrm, "Format=iso.%dT", status, ndp );
+         }
+
+/* If the KeyMap does not contain an entry for the specified key, an
+   ISO date/time string with 1 decimal place in the seconds field
+   is used. */
+      } else {
+         astSetFormat( fmtfrm, 0, "iso.1T" );
+      }
+   }
+
+/* Ensure the displayed value is an abolute value. */
+   astClearTimeOrigin( fmtfrm );
+
+/* Convert the supplied time value into the required system. */
+   fs = astConvert( frm, fmtfrm, "" );
+   astTran1( fs, 1, &value, 1, &fmtval );
+
+/* Format the value. */
+   fmttxt = astFormat( fmtfrm, 0, fmtval );
+
+/* Store it in the KeyMap. */
+   astMapPut0C( props, key, fmttxt, NULL );
+
+/* Free resources. */
+   fs = astAnnul( fs );
+   fmtfrm = astAnnul( fmtfrm );
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a StcsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     StcsChan member function (over-rides the astTestAttrib protected
+*     method inherited from the Object class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a StcsChan's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstStcsChan *this;            /* Pointer to the StcsChan structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the StcsChan structure. */
+   this = (AstStcsChan *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+   if ( !strcmp( attrib, "stcsarea" ) ) {
+      result = astTestStcsArea( this );
+
+   } else if ( !strcmp( attrib, "stcscoords" ) ) {
+      result = astTestStcsCoords( this );
+
+   } else if ( !strcmp( attrib, "stcsprops" ) ) {
+      result = astTestStcsProps( this );
+
+   } else if ( !strcmp( attrib, "stcslength" ) ) {
+      result = astTestStcsLength( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static int Write( AstChannel *this_channel, AstObject *object, int *status ) {
+/*
+*  Name:
+*     Write
+
+*  Purpose:
+*     Write an Object to a StcsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     int Write( AstChannel *this, AstObject *object, int *status )
+
+*  Class Membership:
+*     StcsChan member function (over-rides the astWrite method
+*     inherited from the Channel class).
+
+*  Description:
+*     This function writes an Object to a StcsChan.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan.
+*     object
+*        Pointer to the Object which is to be written.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of Objects written to the StcsChan by this invocation of
+*     astWrite.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the AST error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;            /* AREA Frame */
+   AstFrameSet *fs;          /* FrameSet connecting AREA and COORDS */
+   AstKeyMap *props;         /* A KeyMap holding the STC-S properties list */
+   AstMapping *map;          /* Mapping connecting AREA and COORDS */
+   AstObject *obj;           /* A temporary Object pointer */
+   AstRegion *area;          /* The Region representing the STC CoordArea */
+   AstRegion *coords;        /* The Region representing the STC Coords */
+   AstRegion *new_coords;    /* COORDS Region mapped into frame of AREA */
+   AstStcsChan *this;        /* Pointer to the StcsChan structure */
+   astDECLARE_GLOBALS        /* Declare the thread specific global data */
+   const char *class;        /* Pointer to string holding object class */
+   const char *errclass;     /* Type of the failed entry */
+   const char *errname;      /* Name of the failed entry */
+   const char *method;       /* Pointer to string holding calling method */
+   const char *wantclass;    /* The expected type */
+   int ret;                  /* Number of objects read */
+
+/* Initialise. */
+   ret = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_channel);
+
+/* Obtain a pointer to the StcsChan structure. */
+   this = (AstStcsChan *) this_channel;
+
+/* Store the calling method, and object class. */
+   method = "astWrite";
+   class = astGetClass( this );
+
+/* Initialise */
+   area = NULL;
+   coords = NULL;
+   props = NULL;
+
+/* If the supplied Object is a Region, we will use it to define the AREA
+   properties. */
+   if( astIsARegion( object ) ) {
+      area = (AstRegion *) astClone( object );
+
+/* If the supplied Object is a KeyMap... */
+   } else if( astIsAKeyMap( object ) ) {
+      errname = NULL;
+      wantclass = NULL;
+      errclass = NULL;
+
+/* If the supplied KeyMap contains an entry with key "AREA", and if it is
+   a Region, use it to define the AREA properties. */
+      if( astMapGet0A( (AstKeyMap *) object, "AREA", &obj ) ) {
+         if( astIsARegion( obj ) ) {
+            area = (AstRegion *) obj;
+         } else {
+            wantclass = "Region";
+            errclass = astGetClass( obj );
+            errname = "AREA";
+            obj = astAnnul( obj );
+         }
+      }
+
+/* If the supplied KeyMap contains an entry with key "COORDS", and if it is
+   a Region, use it to define the COORDS properties. */
+      if( astMapGet0A( (AstKeyMap *) object, "COORDS", &obj ) ) {
+         if( astIsARegion( obj ) ) {
+            coords = (AstRegion *) obj;
+         } else {
+            wantclass = "Region";
+            errclass = astGetClass( obj );
+            errname = "COORDS";
+            obj = astAnnul( obj );
+         }
+      }
+
+/* If the supplied KeyMap contains an entry with key "PROPS", and if it is
+   a KeyMap, use it to define values for the properties that cannot be
+   determined from the supplied Regions (Resolution, PixSize, etc). */
+      if( astMapGet0A( (AstKeyMap *) object, "PROPS", &obj ) ) {
+         if( astIsAKeyMap( obj ) ) {
+            props = (AstKeyMap *) obj;
+         } else {
+            wantclass = "KeyMap";
+            errclass = astGetClass( obj );
+            errname = "PROPS";
+            obj = astAnnul( obj );
+         }
+      }
+
+/* If the supplied KeyMap contains an entry with any of the keys
+   "TIME_PROPS", "SPACE_PROPS", "SPECTRAL_PROPS" or "REDSHIFT_PROPS",
+   use the supplied KeyMap to define values for all properties. */
+      if( astMapGet0A( (AstKeyMap *) object, "TIME_PROPS", &obj ) ||
+          astMapGet0A( (AstKeyMap *) object, "SPACE_PROPS", &obj ) ||
+          astMapGet0A( (AstKeyMap *) object, "SPECTRAL_PROPS", &obj ) ||
+          astMapGet0A( (AstKeyMap *) object, "REDSHIFT_PROPS", &obj ) ) {
+         props = astClone( object );
+      }
+
+/* Report an error if the Object in the keymap has the wrong type. */
+      if( errname && astOK ) {
+         astAddWarning( this, 1, "The supplied KeyMap contains a %s "
+                        "called '%s'. But '%s' should be a %s "
+                        "(programming error).", method, status,
+                        errclass, errname, errname, wantclass );
+      }
+
+/* Report an error if the keymap contains none of the above. */
+      if( !area && !coords && !props && astOK ) {
+         astAddWarning( this, 1, "The supplied KeyMap does not "
+                        "contains anything that can be written out "
+                        "through a %s.", method, status, class );
+      }
+
+/* If both COORDS and AREA were supplied, ensure they are in the same
+   Frame by mapping the COORDS Region into the Frame of the AREA Region. */
+      if( area && coords ) {
+         fs = astConvert( coords, area, " " );
+         if( fs ) {
+            map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+            frm = astGetFrame( fs, AST__CURRENT );
+
+            new_coords = astMapRegion( coords, map, frm );
+
+            map = astAnnul( map );
+            frm = astAnnul( frm );
+            coords = astAnnul( coords );
+            fs = astAnnul( fs );
+
+            coords = new_coords;
+
+         } else if( astOK ){
+            astAddWarning( this, 1, "Cannot convert between the co-ordinate "
+                           "frame of the COORDS Region and the co-ordinate "
+                           "frame of the AREA Region.", method, status );
+         }
+      }
+
+/* Report an error if the supplied object is neither a KeyMap nor a
+   Region. */
+   } else if( astOK ) {
+      astAddWarning( this, 1, "Failed to write out a %s through a %s. "
+                     "The %s class cannot be used to write out a %s.",
+                     method, status, astGetClass( object ), class, class,
+                     astGetClass( object ) );
+   }
+
+
+/* If we do not have a KeyMap in which to store the STC-S properties,
+   create one now. */
+   if( astOK ) {
+      if( ! props ) props = astKeyMap ( " ", status );
+
+/* Determine the set of STC-S properties that describe the COORDS Region,
+   and add them into the properties keymap, over-writing any values for the
+   same properties that are already in the props keymap. */
+      ret = coords ? WriteRegion( this, coords, props, status ) : 1;
+
+/* Determine the set of STC-S properties that describe the AREA Region,
+   and add them into the properties keymap, over-writing any values for the
+   same properties that are already in the props keymap. NB, we need to
+   do AREA after COORDS so that the sub-phrase identifier implied by the
+   AREA is used in preference to that implied by the COORDS. */
+      if( area && ret ) ret = WriteRegion( this, area, props, status );
+
+/* Convert the properties list into text and write it out through the
+   parent Channel's sink function. */
+      if( ret ) WriteProps( this, props, status );
+   }
+
+/* Free resources. */
+   if( area ) area = astAnnul( area );
+   if( coords ) coords = astAnnul( coords );
+   if( props ) props = astAnnul( props );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) ret = 0;
+
+/* Return the answer. */
+   return ret;
+}
+
+static void WriteProps( AstStcsChan *this, AstKeyMap *props, int *status ){
+/*
+*  Name:
+*     WriteProps
+
+*  Purpose:
+*     Write out a set of STC-S properties to the sink function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     void WriteProps( AstStcsChan *this, AstKeyMap *props, int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function converts the STC-S properties supplied in a KeyMap
+*     into text, and writes the text out through the sink function associated
+*     with the parent Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan.
+*     props
+*        Pointer to the KeyMap holding the STC-S properties.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *spprops;     /* Sub-phrase properties */
+   AstObject *obj;         /* Generic Object pointer */
+   char *line;             /* Dynamically allocated buffer for output text */
+   const char *id;         /* Sub-phrase identifier */
+   const char *prefix;     /* Prefix for property value */
+   int nc;                 /* Number of characters in "line" */
+   int pretty;             /* Include new-lines and indentation in returned text? */
+   int crem;               /* Character remaining on current output line */
+   int linelen;            /* Line length */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise things. */
+   nc = 0;
+   line = NULL;
+
+/* See if indentation and new-lines are to be added to the output text to
+   make it look pretty. */
+   pretty = astGetIndent( this );
+
+/* If so, get the line length to use, and initialise the number of
+   remaining characters in the current output line. */
+   if( pretty ) {
+      linelen = astGetStcsLength( this );
+   } else {
+      linelen = 0;
+   }
+   crem = linelen;
+
+/* Add each word in the time sub-phrase into the output buffer, in the
+   order defined by the STC-S standard. */
+   if( astMapGet0A( props, "TIME_PROPS", &obj ) ) {
+      spprops = (AstKeyMap *) obj;
+
+      line = AddItem( this, spprops, "ID", NULL, line, &nc, &crem, linelen, status );
+      astMapGet0C( spprops, "ID", &id );
+
+      line = AddItem( this, spprops, "FILLFACTOR", "fillfactor ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "TIMESCALE", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "REFPOS", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "START", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "STOP", NULL, line, &nc, &crem, linelen, status );
+
+      prefix = !astChrMatch( id, "Time" ) ? "Time " : NULL;
+      line = AddItem( this, spprops, "TIME", prefix, line, &nc, &crem, linelen, status );
+
+      line = AddItem( this, spprops, "UNIT", "unit ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "ERROR", "Error ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "RESOLUTION", "Resolution ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "PIXSIZE", "PixSize ", line, &nc, &crem, linelen, status );
+
+      spprops = astAnnul( spprops );
+
+/* Write out the time sub-phrase text through the Channel sink function. */
+      if( pretty && astChrLen( line ) ) {
+         astPutNextText( this, line );
+         nc = 0;
+         crem = linelen;
+      }
+   }
+
+/* Add each word in the space sub-phrase into the output buffer, in the
+   order defined by the STC-S standard. */
+   if( astMapGet0A( props, "SPACE_PROPS", &obj ) ) {
+      spprops = (AstKeyMap *) obj;
+
+      line = AddItem( this, spprops, "ID", NULL, line, &nc, &crem, linelen, status );
+      astMapGet0C( spprops, "ID", &id );
+
+      line = AddItem( this, spprops, "FILLFACTOR", "fillfactor ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "FRAME", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "REFPOS", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "FLAVOUR", NULL, line, &nc, &crem, linelen, status );
+
+      line = PutRegionProps( this, spprops, id, (pretty ? 0 : -1), line, &nc,
+                             &crem, linelen, status );
+
+      prefix = !astChrMatch( id, "Position" ) ? "Position " : NULL;
+      line = AddItem( this, spprops, "POSITION", prefix, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "UNIT", "unit ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "ERROR", "Error ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "RESOLUTION", "Resolution ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "SIZE", "Size ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "PIXSIZE", "PixSize ", line, &nc, &crem, linelen, status );
+
+      spprops = astAnnul( spprops );
+
+/* Write out the spatial sub-phrase text through the Channel sink function. */
+      if( pretty && astChrLen( line ) ) {
+         astPutNextText( this, line );
+         nc = 0;
+         crem = linelen;
+      }
+   }
+
+/* Add each word in the spectral sub-phrase into the output buffer, in the
+   order defined by the STC-S standard. */
+   if( astMapGet0A( props, "SPECTRAL_PROPS", &obj ) ) {
+      spprops = (AstKeyMap *) obj;
+
+      line = AddItem( this, spprops, "ID", NULL, line, &nc, &crem, linelen, status );
+      astMapGet0C( spprops, "ID", &id );
+
+      line = AddItem( this, spprops, "FILLFACTOR", "fillfactor ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "REFPOS", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "LOLIMIT", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "HILIMIT", NULL, line, &nc, &crem, linelen, status );
+
+      prefix = !astChrMatch( id, "Spectral" ) ? "Spectral " : NULL;
+      line = AddItem( this, spprops, "SPECTRAL", prefix, line, &nc, &crem, linelen, status );
+
+      line = AddItem( this, spprops, "UNIT", "unit ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "ERROR", "Error ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "RESOLUTION", "Resolution ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "PIXSIZE", "PixSize ", line, &nc, &crem, linelen, status );
+
+      spprops = astAnnul( spprops );
+
+/* Write out the spectral sub-phrase text through the Channel sink function. */
+      if( pretty && astChrLen( line ) ) {
+         astPutNextText( this, line );
+         nc = 0;
+         crem = linelen;
+      }
+   }
+
+/* Add each word in the redshift sub-phrase into the output buffer, in the
+   order defined by the STC-S standard. */
+   if( astMapGet0A( props, "REDSHIFT_PROPS", &obj ) ) {
+      spprops = (AstKeyMap *) obj;
+
+      line = AddItem( this, spprops, "ID", NULL, line, &nc, &crem, linelen, status );
+      astMapGet0C( spprops, "ID", &id );
+
+      line = AddItem( this, spprops, "FILLFACTOR", "fillfactor ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "REFPOS", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "TYPE", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "DOPPLERDEF", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "LOLIMIT", NULL, line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "HILIMIT", NULL, line, &nc, &crem, linelen, status );
+
+      prefix = !astChrMatch( id, "Redshift" ) ? "Redshift " : NULL;
+      line = AddItem( this, spprops, "REDSHIFT", prefix, line, &nc, &crem, linelen, status );
+
+      line = AddItem( this, spprops, "UNIT", "unit ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "ERROR", "Error ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "RESOLUTION", "Resolution ", line, &nc, &crem, linelen, status );
+      line = AddItem( this, spprops, "PIXSIZE", "PixSize ", line, &nc, &crem, linelen, status );
+
+      spprops = astAnnul( spprops );
+
+/* Write out the redshift sub-phrase text through the Channel sink function. */
+      if( pretty && astChrLen( line ) ) {
+         astPutNextText( this, line );
+         nc = 0;
+         crem = linelen;
+      }
+   }
+
+/* Write out any remaining text through the Channel sink function. */
+   if( nc && astChrLen( line ) ) astPutNextText( this, line );
+
+/* Free resources. */
+   line = astFree( line );
+
+}
+
+static int WriteRegion( AstStcsChan *this, AstRegion *reg, AstKeyMap *props,
+                        int *status ){
+/*
+*  Name:
+*     WriteRegion
+
+*  Purpose:
+*     Convert a Region into a set of STC-S properties and store them in a
+*     KeyMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     int WriteRegion( AstStcsChan *this, AstRegion *reg, AstKeyMap *props,
+*                      int *status )
+
+*  Class Membership:
+*     StcsChan member function
+
+*  Description:
+*     This function attempts to convert the supplied Region nto a set of
+*     STC-S properties, and stores them in the supplied KeyMap.
+
+*  Parameters:
+*     this
+*        Pointer to the StcsChan being used.
+*     reg
+*        Pointer to the region to be converted.
+*     props
+*        Pointer to the KeyMap in which to store the STC-S properties.
+*        On exit, each STC-S sub-phrase has an entry in this KeyMap,
+*        and each of these entries has a value that is another KeyMap
+*        holding the properties for the sub-phrase.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the conversion was succesful, and
+*     zero is returned otherwise.
+*/
+
+/* Local Variables: */
+   AstFrame *efrm;         /* Pointer to encapsulated Frame */
+   AstFrame *pfrm;         /* Pointer to primary Frame cntaining an axis */
+   AstFrame *spfrm;        /* The sub-phrase Frame */
+   AstKeyMap *spprops;     /* Sub-phrase properties */
+   AstMapping *map;        /* Base->current Region Mapping */
+   AstMapping *sreg;       /* Simplified Region */
+   AstObject *obj;         /* Generic object pointer */
+   AstRegion *spreg;       /* The sub-phrase Region */
+   AstRegion *treg;        /* Temporary Region pointer */
+   AstRegion *unc;         /* Uncertainty region */
+   AstRegion *unca;        /* Adaptive uncertainty region */
+   AstStdOfRestType sor;   /* StdOfRest attribute value */
+   AstSystemType sys;      /* System attribute value */
+   char *prop;             /* Formatted property string */
+   char *unit1;            /* Pointer to string holding first axis unit */
+   char buf[ 100 ];        /* Buffer for formatted values */
+   char fmt[ 10 ];         /* Buffer for format specifier */
+   const char *class;      /* Class name */
+   const char *dom;        /* Domain name */
+   const char *dopdef;     /* DopplerDef value */
+   const char *flavour;    /* The STC-S flavour for the space frame */
+   const char *q;          /* Pointer to next character */
+   const char *tfrm;       /* STC-S string for Frame */
+   const char *tsor;       /* STC-S string for RefPos */
+   const char *tts;        /* Time scale label */
+   const char *type;       /* Redshift Type value */
+   const char *unit;       /* Unit string */
+   double *pcen;           /* Pointer to Circle or ellipse centre */
+   double equinox;         /* The required equinox value */
+   double error;           /* Axis error value */
+   double fill;            /* Fill factor */
+   double lbnd[ 3 ];       /* Region lower bounds */
+   double lim;             /* Unlimited bounds value */
+   double p1[ 2 ];         /* End point of error line */
+   double scale;           /* Factor for scaling Region values into required units */
+   double ubnd[ 3 ];       /* Region upper bounds */
+   int allthesame;         /* Do all axes have the same units? */
+   int defdigs;            /* Default number of digits */
+   int defs;               /* Include default values in output STC-S? */
+   int i;                  /* Loop index */
+   int issky;              /* Do the space axes form a SkyFrame? */
+   int nax;                /* The number of axes */
+   int nc;                 /* Number of characters in "prop" string */
+   int nspace;             /* Number of space axes */
+   int ok;                 /* Can the Region be written out? */
+   int pax;                /* Index of axis in primary Frame */
+   int redax;              /* The index of the redshift axis */
+   int retain_units;       /* Retain the units/system in properties KeyMap? */
+   int spaceax[ 3 ];       /* Indicies of the space axes */
+   int spaceid;            /* Code for space sub-phrase identifier */
+   int specax;             /* The index of the spectral axis */
+   int timeax;             /* Index of time axis */
+   int ts;                 /* Time scale identifier */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise things to avoid comiler warnings. */
+   sys = AST__BADSYSTEM;
+
+/* Assume we can do the conversion. */
+   ok = 1;
+
+/* See if default values are to be included in the output. */
+   defs = ( astGetFull( this ) > 0 );
+
+/* STC-S requires that the spatial shape (circle, box. etc) refers to
+   the coordinate system described by the STC-S. This is not quite like
+   AST, in that the AST class type (Circle, Box, etc) defines the
+   shape of the region in the base Frame, rather than the current Frame.
+   So we can only write the Region out using STC-S if the shape in the
+   current Frame is the same as the shape in the base Frame. This is the
+   case if the simplified Mapping connecting base and current Frames is
+   a UnitMap. Get the base->current Mapping from the Region. */
+   map = astRegMapping( reg );
+
+/* If it is not UnitMap, see if simplifying the whole Region results in
+   the base->current Mapping in the simplified Region being a UnitMap. */
+   if( !astIsAUnitMap( map ) ) {
+      map = astAnnul( map );
+      sreg = astSimplify( reg );
+      map = astRegMapping( sreg );
+
+/* If it is still not UnitMap, we cannot write out the region. */
+      if( !astIsAUnitMap( map ) ) {
+         astAddWarning( this, 1, "The supplied Region does not have a "
+                        "supported shape within its current coordinate "
+                        "system.", "astWrite", status );
+         ok = 0;
+      }
+
+   } else {
+      sreg = astClone( reg );
+   }
+   map = astAnnul( map );
+
+/* Store a safe value that can be used to test unbounded axes. */
+   lim = sqrt( DBL_MAX );
+
+/* First job is to identify the Time, Space, Spectral and Redshift axes
+   in the supplied Region.
+   ------------------------------------------------------------------- */
+
+/* Initialise things. */
+   timeax = -1;
+   nspace = 0;
+   issky = 0;
+   specax = -1;
+   redax = -1;
+   prop = NULL;
+
+/* Get a pointer to the Frame encapsulated by the Region. */
+   efrm = astRegFrame( sreg );
+
+/* Loop round all axes. */
+   nax = astGetNaxes( sreg );
+   for( i = 0; i < nax; i++ ) {
+
+/* Get the primary Frame that defines the current axis of the Region. */
+      astPrimaryFrame( efrm, i, &pfrm, &pax );
+
+/* Get its class and domain. */
+      class = astGetClass( pfrm );
+      dom = astGetDomain( pfrm );
+      if( astOK ) {
+
+/* The time axis is described by a TimeFrame with any domain. */
+         if( !strcmp( class, "TimeFrame" ) ) {
+            if( timeax == -1 ) {
+               timeax = i;
+            } else {
+               astAddWarning( this, 1, "More than one time axis found. "
+                              "Extra axis (axis %d) will be ignored.",
+                              "astWrite", status, i + 1 );
+            }
+
+/* The space axes are described by a SkyFrame or a basic Frame. If a
+   mixture of both types are found, report a warning and ignore the later
+   axes. */
+         } else if( !strcmp( class, "SkyFrame" ) ) {
+            if( issky || nspace == 0 ) {
+               if( nspace < 2 ) {
+                  spaceax[ nspace++ ] = i;
+                  issky = 1;
+               } else {
+                  astAddWarning( this, 1, "More than two sky frame axes "
+                                 "found. Extra axis (axis %d) will be ignored.",
+                                 "astWrite", status, i + 1 );
+               }
+
+            } else {
+               astAddWarning( this, 1, "Mixture of basic and sky frame "
+                              "axes found. Sky frame axis %d will be "
+                              "ignored.", "astWrite", status, i + 1 );
+            }
+
+         } else if( !strcmp( class, "Frame" ) ) {
+            if( !issky ) {
+               if( nspace < 3 ) {
+                  spaceax[ nspace++ ] = i;
+               } else {
+                  astAddWarning( this, 1, "More than three basic space frame axes "
+                                 "found. Extra axis (axis %d) will be ignored.",
+                                 "astWrite", status, i + 1 );
+               }
+
+            } else {
+               astAddWarning( this, 1, "Mixture of basic and sky frame "
+                              "axes found. Basic frame axis %d will be "
+                              "ignored.", "astWrite", status, i + 1 );
+            }
+
+/* The spectral axis is described by a SpecFrame with domain SPECTRUM. */
+         } else if( !strcmp( class, "SpecFrame" ) &&
+                    !strcmp( dom, "SPECTRUM" ) ) {
+            if( specax == -1 ) {
+               specax = i;
+            } else {
+               astAddWarning( this, 1, "More than one spectral axis found. "
+                              "Extra axis (axis %d) will be ignored.",
+                              "astWrite", status, i + 1 );
+            }
+
+/* The redshift axis is described by a SpecFrame with domain REDSHIFT. */
+         } else if( !strcmp( class, "SpecFrame" ) &&
+                    !strcmp( dom, "REDSHIFT" ) ) {
+            if( redax == -1 ) {
+               redax = i;
+            } else {
+               astAddWarning( this, 1, "More than one redshift axis found. "
+                              "Extra axis (axis %d) will be ignored.",
+                              "astWrite", status, i + 1 );
+            }
+
+/* Warn about unused axes. */
+         } else {
+            astAddWarning( this, 1, "Could not classify axis %d (class=%s "
+                           "domain=%s). It will be ignored.", "astWrite", status,
+                           i + 1, class, dom );
+         }
+      }
+
+/* Free resources. */
+      pfrm = astAnnul( pfrm );
+   }
+   efrm = astAnnul( efrm );
+
+/* Set a flag indicating if there is anything to convert. */
+   ok = ok && ( timeax != -1 || nspace > 0 || specax != -1 || redax != -1 );
+
+
+/* Now we have identified the axes, we convert each available STC-S
+   sub-phrase, starting with the time sub-phrase.
+   ---------------------------------------------------------------- */
+   if( timeax != -1 ) {
+
+/* Create a Region by picking the time axis from the supplied Region. */
+      spreg = astPickAxes( sreg, 1, &timeax, NULL );
+
+/* Check it is a Region. If not, we cannot convert anything. */
+      if( !astIsARegion( spreg ) ) {
+         astAddWarning( this, 1, "Cannot determine the region covered by "
+                        "the time axis.", "astWrite", status );
+         ok = 0;
+
+/* Otherwise we add a description of the time sub-phrase to the
+   properties keymap. */
+      } else {
+
+/* Get a pointer to the Region's time phrase property KeyMap, creating
+   one if necessary. */
+         if( astMapGet0A( props, "TIME_PROPS", &obj ) ) {
+            spprops = (AstKeyMap *) obj;
+         } else {
+            spprops = astKeyMap( " ", status );
+            astMapPut0A( props, "TIME_PROPS", spprops, NULL );
+         }
+
+/* Get the Region's fill factor. */
+         fill = astGetFillFactor( spreg );
+
+/* Ensure the TimeFrame represents MJD. If not, take a deep copy (to
+   avoid changing the supplied Region), and set its system to MJD. */
+         if( astGetSystem( spreg ) != AST__MJD ) {
+            treg = astCopy( spreg );
+            (void) astAnnul( spreg );
+            spreg = treg;
+            astSetAdaptive( spreg, 1 );
+            astSetSystem( spreg, AST__MJD );
+         }
+
+/* Get the bounds of the Region (i.e. the time axis coverage). */
+         astGetRegionBounds( spreg, lbnd, ubnd );
+
+/* Get a pointer to the time Region's encapsulated Frame. */
+         spfrm = astRegFrame( spreg );
+
+/* Report a warning if the sub-phrase Frame is not a TimeFrame */
+         if( !astIsATimeFrame( spfrm ) ) {
+            ok = 0;
+            astAddWarning( this, 1, "The time sub-phrase in the supplied "
+                           "KeyMap is not described using an AST TimeFrame.",
+                           "astWrite", status );
+
+/* Store properties that are specific to Time moments... */
+         } else if( lbnd[ 0 ] == ubnd[ 0 ] ) {
+            astMapPut0C( spprops, "ID", "Time", NULL );
+            StoreTimeProp( spprops,  (AstTimeFrame *) spfrm, "TIME", lbnd[ 0 ], status );
+            fill = AST__BAD;
+
+/* Store properties that are specific to Time intervals... */
+         } else if( lbnd[ 0 ] > -lim && ubnd[ 0 ] < lim ) {
+            astMapPut0C( spprops, "ID", "TimeInterval", NULL );
+            StoreTimeProp( spprops,  (AstTimeFrame *) spfrm, "START", lbnd[ 0 ], status );
+            StoreTimeProp( spprops,  (AstTimeFrame *) spfrm, "STOP", ubnd[ 0 ], status );
+
+/* Store properties that are specific to Start times... */
+         } else if( lbnd[ 0 ] > -lim ) {
+            astMapPut0C( spprops, "ID", "StartTime", NULL );
+            StoreTimeProp( spprops,  (AstTimeFrame *) spfrm, "START", lbnd[ 0 ], status );
+
+/* Store properties that are specific to Stop times... */
+         } else {
+            astMapPut0C( spprops, "ID", "StopTime", NULL );
+            StoreTimeProp( spprops,  (AstTimeFrame *) spfrm, "STOP", ubnd[ 0 ], status );
+
+         }
+
+/* Store properties that are common to all time sub-phrase types. First the
+   fill factor. */
+         MapPut0D( spprops, "FILLFACTOR", fill, 1.0, defs, status );
+
+/* Now the time scale. */
+         ts = astGetTimeScale( spfrm );
+         if( ts == AST__TT ) {
+            tts = "TT";
+
+         } else if( ts == AST__TAI ) {
+            tts = "TAI";
+
+         } else if( ts == AST__UTC ) {
+            tts = "UTC";
+
+         } else if( ts == AST__TDB ) {
+            tts = "TDB";
+
+         } else if( ts == AST__TCG ) {
+            tts = "TCG";
+
+         } else if( ts == AST__TCB ) {
+            tts = "TCB";
+
+         } else if( ts == AST__LMST ) {
+            tts = "LST";
+
+         } else {
+            tts = "nil";
+            astAddWarning( this, 1, "Timescale '%s' is unsupported by "
+                           "STC-S.", "astWrite", status,
+                           astGetC( spfrm, "TimeScale" ) );
+            ok = 0;
+         }
+
+         MapPut0C( spprops, "TIMESCALE", tts, "nil", defs, status );
+
+/* RefPos. The AST TimeFrame class has no reference position, we leave
+   unchanged any refpos already in the keymap. If there is no refpos in the
+   keymap, we use "TOPOCENTER". */
+         if( !astMapHasKey( spprops, "REFPOS" ) ) {
+	    astMapPut0C( spprops, "REFPOS", "TOPOCENTER", NULL );
+         }
+
+/* That's it for the time sub-phrase, unless the supplied Region has an
+   explicit (non-default) uncertainty. */
+         unc = astGetUnc( spreg, 0 );
+         if( unc ) {
+
+/* See if the supplied properties KeyMap contains any item that refers to
+   the Unit included in the STC-S description, but which is not updated by
+   this function. If it does, we need to retain any units specified
+   within the KeyMap. */
+            retain_units = ( astMapHasKey( spprops, "RESOLUTION" ) ||
+                             astMapHasKey( spprops, "PIXSIZE" ) ||
+                             astMapHasKey( spprops, "SIZE" ) );
+
+            if( retain_units ) {
+               if( !astMapGet0C( spprops, "UNIT", &unit ) ) unit = "s";
+            } else {
+               unit = "s";
+            }
+
+/* Store the units string */
+            MapPut0C( spprops, "UNIT", unit, "s", defs, status );
+
+/* If necessary, map the uncertainty region into the requied units. Take
+   a deep copy to avoid changing the supplied Region. */
+            if( strcmp( unit, astGetUnit( unc, 0 ) ) ) {
+               unca = astCopy( unc );
+               astSetAdaptive( unca, 0 );
+               astSetUnit( unca, 0, unit );
+            } else {
+               unca = astClone( unc );
+            }
+
+/* Get the bounds of the uncertainty. */
+            astGetRegionBounds( unca, lbnd, ubnd );
+
+/* The error is half the width of the bounding box. */
+            astMapPut0D( spprops, "ERROR", 0.5*( ubnd[ 0 ] - lbnd[ 0 ] ), NULL );
+
+/* Free resources. */
+            unca = astAnnul( unca );
+            unc = astAnnul( unc );
+         }
+
+/* Free resources. */
+         spfrm = astAnnul( spfrm );
+         spprops = astAnnul( spprops );
+      }
+
+/* Free resources. */
+      spreg = astAnnul( spreg );
+
+   }
+
+
+/* Now convert the space sub-phrase.
+   ---------------------------------------------------------------- */
+   if( nspace > 0 && ok ) {
+
+/* Create a Region by picking the space axes from the supplied Region. */
+      spreg = astPickAxes( sreg, nspace, spaceax, NULL );
+
+/* Check it is a Region. If not, we cannot convert anything. */
+      if( ! astIsARegion( spreg ) ) {
+         astAddWarning( this, 1, "Cannot determine the region covered by "
+                        "the space axes.", "astWrite", status );
+         ok = 0;
+
+/* Otherwise we add a description of the space sub-phrase to the
+   properties keymap. */
+      } else {
+
+/* Get a pointer to the Region's space phrase property KeyMap, creating
+   one if necessary. */
+         if( astMapGet0A( props, "SPACE_PROPS", &obj ) ) {
+            spprops = (AstKeyMap *) obj;
+         } else {
+            spprops = astKeyMap( " ", status );
+            astMapPut0A( props, "SPACE_PROPS", spprops, NULL );
+         }
+
+/* If the space frame is a SkyFrame, ensure it refers to a coodinate
+   system that is supported by STC-S. Take a deep copy before changing
+   anything. */
+         if( issky ) {
+            sys = astGetSystem( spreg );
+            if( sys != AST__FK4 &&
+                sys != AST__FK5 &&
+                sys != AST__ICRS &&
+                sys != AST__ECLIPTIC &&
+                sys != AST__GALACTIC &&
+                sys != AST__SUPERGALACTIC &&
+                sys != AST__UNKNOWN ) {
+               treg = astCopy( spreg );
+               (void) astAnnul( spreg );
+               spreg = treg;
+               astSetAdaptive( spreg, 1 );
+               astSetSystem( spreg, AST__ICRS );
+            }
+         }
+
+/* Get a pointer to the Region's encapsulated Frame. */
+         spfrm = astRegFrame( spreg );
+
+/* If the supplied Region is defined in a SkyFrame, choose the units to
+   use when storing radius, error, etc in the KeyMap. If the props KeyMap
+   already contains a unit specification, we use it. Otherwise we use the
+   default (degrees). AST uses radians internally, so find the scaling
+   factor. */
+         if( issky ) {
+            if( astMapGet0C( spprops, "UNIT", &unit ) ) {
+               if( !strcmp( unit, "arcmin" ) ) {
+                  scale = AST__DR2D*60.0;
+               } else if( !strcmp( unit, "arcsec" ) ) {
+                  scale = AST__DR2D*3600.0;
+               } else {
+                  unit = "deg";
+                  scale = AST__DR2D;
+               }
+            } else {
+               unit = "deg";
+               scale = AST__DR2D;
+            }
+
+/* Store the units string */
+            MapPut0C( spprops, "UNIT", unit, "deg", defs, status );
+
+/* If the supplied Region is not defined in a SkyFrame, we will arrange
+   that the Region and the KeyMap use the same units, so set a scale
+   factor of 1.0. */
+         } else {
+            scale = 1.0;
+
+/* See if the supplied properties KeyMap contains any item that refers to
+   the Unit included in the STC-S description, but which is not updated by
+   this function. If it does, we need to retain any units specified
+   within the KeyMap. */
+            retain_units = ( astMapHasKey( spprops, "RESOLUTION" ) ||
+                             astMapHasKey( spprops, "PIXSIZE" ) ||
+                             astMapHasKey( spprops, "SIZE" ) );
+
+/* If so, and if the properties KeyMap already contains a Unit
+   specification, we convert the Region to the same units. Take a deep
+   copy of the Region first to avoid modifying the supplied Region. */
+            if( retain_units ) {
+               if( !astMapGet0C( spprops, "UNIT", &unit ) ) unit = "deg";
+
+               treg = astCopy( spreg );
+               (void) astAnnul( spreg );
+               spreg = treg;
+
+               for( i = 0; i < nspace; i++ ) {
+                  astSetUnit( spreg, i, unit );
+
+/* Space frames can have different units on different axes. So look for
+   the start of the next word in the Unit propert. This will be the unit
+   for the next axis. If there are no more words in the Unit property,
+   re-use the last unit value. */
+                  q = unit;
+                  while( *q && !isspace( *q ) ) q++;
+                  while( *q && isspace( *q ) ) q++;
+                  if( *q ) unit = q;
+               }
+
+/* If we are not retaining the units specified in the properties KeyMap, we
+   retain the existing Region units instead, and store these units in the
+   properties KeyMap. We also check that these units are supported by
+   STC-S. */
+            } else {
+
+               nc = 0;
+               allthesame = 1;
+               unit1 = NULL;
+
+               for( i = 0; i < nspace; i++ ) {
+                  unit = astGetUnit( spreg, i );
+
+                  if( !unit1 ) {
+                     unit1 = astStore( NULL, unit, strlen( unit ) + 1 );
+                  } else {
+                     if( strcmp( unit, unit1 ) ) allthesame = 0;
+                  }
+
+                  if( strcmp( unit, "deg" ) &&
+                      strcmp( unit, "arcmin" ) &&
+                      strcmp( unit, "arcsec" ) &&
+                      strcmp( unit, "m" ) &&
+                      strcmp( unit, "mm" ) &&
+                      strcmp( unit, "km" ) &&
+                      strcmp( unit, "AU" ) &&
+                      strcmp( unit, "pc" ) &&
+                      strcmp( unit, "kpc" ) &&
+                      strcmp( unit, "Mpc" ) ) {
+                     astAddWarning( this, 1, "Cannot use spatial units '%s'.",
+                                    "astWrite", status, unit );
+                     ok = 0;
+                     break;
+                  }
+                  prop = astAppendString( prop, &nc, unit );
+                  prop = astAppendString( prop, &nc, " " );
+               }
+
+/* Remove the trailing space, and store the property value in the KeyMap. */
+               if( ! allthesame ) {
+                  if( prop && nc > 0 ) {
+                     prop[ nc - 1 ] = 0;
+                     astMapPut0C( spprops, "UNIT", prop, NULL );
+                  }
+               } else {
+                  astMapPut0C( spprops, "UNIT", unit1, NULL );
+               }
+
+               unit1 = astFree( unit1 );
+
+            }
+         }
+
+/* Get the fill factor. */
+         fill = astGetFillFactor( spreg );
+
+/* Get the default number of digits. This is only used if the supplied
+   properties KeyMap does not have a value for the item being stored. If
+   it does, the number of digits is inherited form the value int he KeyMap. */
+         defdigs = astGetDigits( spfrm );
+
+/* Store properties that are specific to the particular type of Region. */
+         spaceid = GetRegionProps( this, spreg, spprops, nspace, defdigs,
+                                   scale, issky, status );
+         if( spaceid == NULL_ID ) ok = 0;
+
+/* If the above went OK, store values for the properties that are common
+   to all types of space sub-phrase. */
+         if( ok ) {
+
+/* First the fill factor. */
+            if( spaceid != POSITION_ID ) {
+               MapPut0D( spprops, "FILLFACTOR", fill, 1.0, defs, status );
+            }
+
+/* Now the coordinate frame. */
+            tfrm = NULL;
+            sys = astGetSystem( spfrm );
+            if( issky ) {
+               if( sys == AST__FK4 ){
+                  tfrm = "B1950";
+                  equinox = 1950.0;
+
+               } else if( sys == AST__FK5 ){
+                  tfrm = "J2000";
+                  equinox = 2000.0;
+
+               } else if( sys == AST__ICRS ){
+                  tfrm = "ICRS";
+                  equinox = AST__BAD;
+
+               } else if( sys == AST__ECLIPTIC ){
+                  tfrm = "ECLIPTIC";
+                  equinox = 2000.0;
+
+               } else if( sys == AST__GALACTIC ){
+                  tfrm = "GALACTIC";
+                  equinox = AST__BAD;
+
+               } else if( sys == AST__SUPERGALACTIC ){
+                  tfrm = "SUPER_GALACTIC";
+                  equinox = AST__BAD;
+
+               } else if( sys == AST__UNKNOWN ){
+                  tfrm = NULL;
+                  equinox = AST__BAD;
+
+               } else {
+                  tfrm = NULL;
+                  astAddWarning( this, 1, "Sky system '%s' is "
+                                 "unsupported by STC-S.", "astWrite",
+                                 status, astGetC( spfrm, "System" ) );
+                  ok = 0;
+               }
+
+               if( tfrm && equinox != AST__BAD ) {
+                  if( astGetD( spfrm, "Equinox" ) != equinox ) {
+                     astAddWarning( this, 1, "STC-S requires an equinox "
+                                    "of %g for the %s frame, but the "
+                                    "supplied %s equinox is %g.", "astWrite",
+                                    status, equinox, tfrm,
+                                    astGetClass( spfrm ),
+                                    astGetD( spfrm, "Equinox" ) );
+                     ok = 0;
+                     tfrm = NULL;
+                  }
+               }
+            }
+
+/* If we do not yet have a Frame, use the Domain value if it is set (and
+   is a legal STC-S Frame). */
+            if( ! tfrm ) {
+               if( astTestDomain( spfrm ) ) {
+                  tfrm = astGetDomain( spfrm );
+                  if( strcmp( tfrm, "ICRS" ) &&
+                      strcmp( tfrm, "FK5" ) &&
+                      strcmp( tfrm, "FK4" ) &&
+                      strcmp( tfrm, "J2000" ) &&
+                      strcmp( tfrm, "B1950" ) &&
+                      strcmp( tfrm, "ECLIPTIC" ) &&
+                      strcmp( tfrm, "GALACTIC" ) &&
+                      strcmp( tfrm, "GALACTIC_II" ) &&
+                      strcmp( tfrm, "SUPER_GALACTIC" ) &&
+                      strcmp( tfrm, "GEO_C" ) &&
+                      strcmp( tfrm, "GEO_D" ) ){
+                     astAddWarning( this, 1, "'UNKNOWNFrame' being used in "
+                                    "place of unsupported frame '%s'.",
+                                     "astWrite", status, tfrm );
+                     tfrm = NULL;
+                  }
+               }
+            }
+
+/* Store the Frame name in the props keymap. */
+            if( !tfrm ) tfrm = "UNKNOWNFrame";
+            astMapPut0C( spprops, "FRAME", tfrm, NULL );
+
+/* RefPos. The AST SkyFrame and Frame classes have no reference position, so
+   we leave unchanged any refpos already in the props keymap. If there is
+   no refpos in the keymap, we use "TOPOCENTER". */
+            if( !astMapHasKey( spprops, "REFPOS" ) ) {
+               astMapPut0C( spprops, "REFPOS", "TOPOCENTER", NULL );
+            }
+
+/* Flavour. */
+            if( issky ) {
+               flavour = "SPHER2";
+            } else if( nspace == 1 ){
+               flavour = "CART1";
+            } else if( nspace == 2 ){
+               flavour = "CART2";
+            } else {
+               flavour = "CART3";
+            }
+            MapPut0C( spprops, "FLAVOUR", flavour, "SPHER2", defs, status );
+
+/* That's it for the space sub-phrase, unless the supplied Region has an
+   explicit (non-default) uncertainty. */
+            unc = astGetUnc( spreg, 0 );
+            if( unc ) {
+
+/* Get the bounds of the uncertainty. */
+               astGetRegionBounds( unc, lbnd, ubnd );
+
+/* If its a sky frame, find the position of the centre of the uncertainty
+   region. */
+               pcen = issky ? astRegCentre( unc, NULL, NULL, 0,
+                                            AST__CURRENT ) : NULL;
+
+/* Find the half-width of the bounding box for each space axis, and
+   concatenate their formatted values into a string. If any bound is
+   undefined, quit the axis loop with nc=0. We need to convert longitude
+   axis values from lingitude increments to arc-distance.  */
+               nc = 0;
+               defdigs = astGetDigits( unc );
+
+               for( i = 0; i < nspace; i++ ) {
+                  if( ubnd[ i ] != AST__BAD && lbnd[ i ] != AST__BAD ){
+
+                     if( ! issky ) {
+                        error = 0.5*( ubnd[ i ] - lbnd[ i ] );
+                     } else {
+                        if( i == 0 ) {
+                           p1[ 0 ] = ubnd[ 0 ];
+                           p1[ 1 ] = pcen[ 1 ];
+                        } else {
+                           p1[ 0 ] = pcen[ 0 ];
+                           p1[ 1 ] = ubnd[ 1 ];
+                        }
+                        error = astDistance( spfrm, pcen, p1 );
+                     }
+
+                     GetFmt( "ERROR", spprops, i, defdigs, fmt, status );
+                     (void) sprintf( buf, fmt, scale*error );
+                     prop = astAppendString( prop, &nc, buf );
+                     prop = astAppendString( prop, &nc, " " );
+
+                  } else {
+                     nc = 0;
+                     break;
+                  }
+               }
+
+/* If the bounds were all good, store the string holding the formatted
+   error values in the properties KeyMap. */
+               if( prop && nc > 0 ) {
+                  prop[ nc - 1 ] = 0;
+                  astMapPut0C( spprops, "ERROR", prop, NULL );
+               }
+
+/* Free resources. */
+               pcen = astFree( pcen );
+               unc = astAnnul( unc );
+            }
+         }
+
+/* Free resources. */
+         spfrm = astAnnul( spfrm );
+         spprops = astAnnul( spprops );
+      }
+
+/* Free resources. */
+      spreg = astAnnul( spreg );
+
+   }
+
+
+
+/* Convert the spectral sub-phrase.
+   ---------------------------------------------------------------- */
+   if( specax != -1 ) {
+
+/* Create a Region by picking the spectral axis from the supplied Region. */
+      spreg = astPickAxes( sreg, 1, &specax, NULL );
+
+/* Check it is a Region. If not, we cannot convert anything. */
+      if( !astIsARegion( spreg ) ) {
+         astAddWarning( this, 1, "Cannot determine the region covered by "
+                        "the spectral axis.", "astWrite", status );
+         ok = 0;
+
+/* Otherwise we add a description of the spectral sub-phrase to the
+   properties keymap. */
+      } else {
+
+/* Get a pointer to the Region's spectral phrase property KeyMap, creating
+   one if necessary. */
+         if( astMapGet0A( props, "SPECTRAL_PROPS", &obj ) ) {
+            spprops = (AstKeyMap *) obj;
+         } else {
+            spprops = astKeyMap( " ", status );
+            astMapPut0A( props, "SPECTRAL_PROPS", spprops, NULL );
+         }
+
+/* See if the supplied properties KeyMap contains any item that refers to
+   the Unit included in the STC-S description, but which is not updated by
+   this function. If it does, we need to retain any units specified
+   within the KeyMap. */
+         retain_units = ( astMapHasKey( spprops, "RESOLUTION" ) ||
+                          astMapHasKey( spprops, "PIXSIZE" ) ||
+                          astMapHasKey( spprops, "SIZE" ) );
+
+/* If so, and if the properties KeyMap already contains a Unit specification,
+   we convert the Region to the same units and system. Determine the
+   required system and units. */
+         if( retain_units ) {
+            if( !astMapGet0C( spprops, "UNIT", &unit ) ) unit = "Hz";
+
+            if( !strcmp( unit, "Hz" ) ||
+                !strcmp( unit, "MHz" ) ||
+                !strcmp( unit, "GHz" ) ) {
+               sys = AST__FREQ;
+
+            } else if( !strcmp( unit, "m" ) ||
+                       !strcmp( unit, "mm" ) ||
+                       !strcmp( unit, "um" ) ||
+                       !strcmp( unit, "nm" ) ||
+                       !strcmp( unit, "Angstrom" ) ) {
+               sys = AST__WAVELEN;
+
+            } else if( !strcmp( unit, "eV" ) ||
+                       !strcmp( unit, "keV" ) ||
+                       !strcmp( unit, "MeV" ) ) {
+               sys = AST__ENERGY;
+
+            } else {
+               astAddWarning( this, 1, "Illegal STC-S units '%s' found in "
+                              "supplied KeyMap", "astWrite", status, unit );
+               ok = 0;
+            }
+
+/* If we do not need to retain the units implied by the supplied KeyMap,
+   use the Units and system in the supplied Region so long as they are
+   supported by STC-S. If not, use a related supported system instead. */
+         } else {
+            sys = astGetSystem( spreg );
+            unit = astGetUnit( spreg, 0 );
+
+            if( sys == AST__ENERGY ) {
+               sys = AST__ENERGY;
+               if( strcmp( unit, "eV" ) &&
+                   strcmp( unit, "keV" ) &&
+                   strcmp( unit, "MeV" ) ) unit = "eV";
+
+            } else if( sys == AST__WAVELEN || sys == AST__AIRWAVE ||
+                       sys == AST__VOPTICAL || sys == AST__REDSHIFT ){
+               sys = AST__WAVELEN;
+               if( strcmp( unit, "m" ) &&
+                   strcmp( unit, "mm" ) &&
+                   strcmp( unit, "um" ) &&
+                   strcmp( unit, "nm" ) &&
+                   strcmp( unit, "Angstrom" ) ) unit = "m";
+
+            } else {
+               sys = AST__FREQ;
+               if( strcmp( unit, "Hz" ) &&
+                   strcmp( unit, "MHz" ) &&
+                   strcmp( unit, "GHz" ) ) unit = "Hz";
+
+            }
+         }
+
+/* Store the units string */
+         MapPut0C( spprops, "UNIT", unit, "Hz", defs, status );
+
+/* If either the System or Unit needs to be changed in the Region, take a
+   deep copy first in order to avoid changing the supplied Region. */
+         if( sys != astGetSystem( spreg ) ||
+             ( unit && strcmp( unit, astGetUnit( spreg, 0 ) ) ) ) {
+            treg = astCopy( spreg );
+            (void) astAnnul( spreg );
+            spreg = treg;
+            astSetAdaptive( spreg, 1 );
+            astSetSystem( spreg, sys );
+            astSetUnit( spreg, 0, unit );
+         }
+
+/* Get the Region's fill factor. */
+         fill = astGetFillFactor( spreg );
+
+/* Get the bounds of the Region (i.e. the spectral axis coverage). */
+         astGetRegionBounds( spreg, lbnd, ubnd );
+
+/* Get a pointer to the spectral Region's encapsulated Frame. */
+         spfrm = astRegFrame( spreg );
+
+/* Report a warning if the sub-phrase Frame is not a SpecFrame */
+         if( !astIsASpecFrame( spfrm ) ) {
+            ok = 0;
+            astAddWarning( this, 1, "The spectral sub-phrase in the supplied "
+                           "KeyMap is not described using an AST SpecFrame.",
+                           "astWrite", status );
+
+/* Store properties that are specific to spectral positions... */
+         } else if( lbnd[ 0 ] == ubnd[ 0 ] ) {
+            astMapPut0C( spprops, "ID", "Spectral", NULL );
+            astMapPut0D( spprops, "SPECTRAL", lbnd[ 0 ], NULL );
+            fill = AST__BAD;
+
+/* Store properties that are specific to Spectral intervals... */
+         } else if( lbnd[ 0 ] > -lim && ubnd[ 0 ] < lim ) {
+            astMapPut0C( spprops, "ID", "SpectralInterval", NULL );
+            astMapPut0D( spprops, "LOLIMIT", lbnd[ 0 ], NULL );
+            astMapPut0D( spprops, "HILIMIT", ubnd[ 0 ], NULL );
+
+         } else {
+            ok = 0;
+            astAddWarning( this, 1, "Cannot write out an unbounded "
+                           "spectral interval.", "astWrite", status );
+         }
+
+/* Store properties that are common to all spectral sub-phrase types. First the
+   fill factor. */
+         MapPut0D( spprops, "FILLFACTOR", fill, 1.0, defs, status );
+
+/* Now the reference position. */
+         sor = astGetStdOfRest( spfrm );
+         if( sor == AST__GESOR ) {
+            tsor = "GEOCENTER";
+
+         } else if( sor == AST__BYSOR ) {
+            tsor = "BARYCENTER";
+
+         } else if( sor == AST__HLSOR ) {
+            tsor = "HELIOCENTER";
+
+         } else if( sor == AST__TPSOR ) {
+            tsor = "TOPOCENTER";
+
+         } else if( sor == AST__LKSOR ) {
+            tsor = "LSRK";
+
+         } else if( sor == AST__LDSOR ) {
+            tsor = "LSRD";
+
+         } else if( sor == AST__GLSOR ) {
+            tsor = "GALACTIC_CENTER";
+
+         } else {
+            tsor = NULL;
+         }
+
+         if( !tsor ) tsor = "UNKNOWNRefPos";
+         MapPut0C( spprops, "REFPOS", tsor, "UNKNOWNRefPos", defs,
+                   status );
+
+/* Now the unit string. */
+         MapPut0C( spprops, "UNIT", unit, "Hz", defs, status );
+
+/* That's it for the spectral sub-phrase, unless the supplied Region has an
+   explicit (non-default) uncertainty. */
+         unc = astGetUnc( spreg, 0 );
+         if( unc ) {
+
+/* Get the bounds of the uncertainty. */
+            astGetRegionBounds( unc, lbnd, ubnd );
+
+/* The error is half the width of the bounding box. */
+            astMapPut0D( spprops, "ERROR", 0.5*( ubnd[ 0 ] - lbnd[ 0 ] ), NULL );
+
+/* Free resources. */
+            unc = astAnnul( unc );
+         }
+
+/* Free resources. */
+         spfrm = astAnnul( spfrm );
+         spprops = astAnnul( spprops );
+      }
+
+/* Free resources. */
+      spreg = astAnnul( spreg );
+
+   }
+
+
+
+/* Convert the redshift sub-phrase.
+   ---------------------------------------------------------------- */
+   if( redax != -1 ) {
+
+/* Create a Region by picking the redshift axis from the supplied Region. */
+      spreg = astPickAxes( sreg, 1, &redax, NULL );
+
+/* Check it is a Region. If not, we cannot convert anything. */
+      if( !astIsARegion( spreg ) ) {
+         astAddWarning( this, 1, "Cannot determine the region covered by "
+                        "the redshift axis.", "astWrite", status );
+         ok = 0;
+
+/* Otherwise we add a description of the redshift sub-phrase to the
+   properties keymap. */
+      } else {
+
+/* Get a pointer to the Region's redshift phrase property KeyMap, creating
+   one if necessary. */
+         if( astMapGet0A( props, "REDSHIFT_PROPS", &obj ) ) {
+            spprops = (AstKeyMap *) obj;
+         } else {
+            spprops = astKeyMap( " ", status );
+            astMapPut0A( props, "REDSHIFT_PROPS", spprops, NULL );
+         }
+
+/* See if the supplied properties KeyMap contains any item that refers to
+   the system included in the STC-S description, but which is not updated by
+   this function. If it does, we need to retain any system specified
+   within the KeyMap. */
+         retain_units = ( astMapHasKey( spprops, "RESOLUTION" ) ||
+                          astMapHasKey( spprops, "PIXSIZE" ) ||
+                          astMapHasKey( spprops, "SIZE" ) );
+
+/* If so, and if the properties KeyMap already contains a DopplerDef or
+   Type specification, we convert the Region to the same system. */
+         if( retain_units ){
+            if( !astMapGet0C( spprops, "DOPPLERDEF", &dopdef ) ) dopdef = "OPTICAL";
+            if( !astMapGet0C( spprops, "TYPE", &type ) ) type = "VELOCITY";
+
+            if( astChrMatch( type, "VELOCITY" ) ) {
+               if( astChrMatch( dopdef, "OPTICAL" ) ) {
+                  sys = AST__VOPTICAL;
+               } else if( astChrMatch( dopdef, "RADIO" ) ) {
+                  sys = AST__VRADIO;
+               } else if( astChrMatch( dopdef, "RELATIVISTIC" ) ) {
+                  sys = AST__VREL;
+               } else {
+                  astAddWarning( this, 1, "Illegal STC-S DopplerDef '%s' "
+                                 "found in supplied KeyMap", "astWrite", status,
+                                 dopdef );
+                  ok = 0;
+               }
+
+            } else if( astChrMatch( type, "REDSHIFT" ) ) {
+               if( astChrMatch( dopdef, "OPTICAL" ) ) {
+                  sys = AST__REDSHIFT;
+               } else {
+                  astAddWarning( this, 1, "Unsupported combination of "
+                                 "DopplerDef='%s' and Type='%s' found in "
+                                 "supplied KeyMap", "astWrite", status, dopdef,
+                                 type );
+                  ok = 0;
+               }
+
+            } else {
+               astAddWarning( this, 1, "Illegal STC-S Redshift Type '%s' "
+                              "found in supplied KeyMap", "astWrite", status,
+                              type );
+               ok = 0;
+            }
+
+/* If the supplied KeyMap does not imply the required system, use the
+   system in the supplied Region. */
+         } else {
+            sys = astGetSystem( spreg );
+         }
+
+/* Choose the requied units. */
+         unit = ( sys == AST__REDSHIFT ) ? "": "km/s";
+
+/* Store the units string */
+         MapPut0C( spprops, "UNIT", unit, unit, defs, status );
+
+/* If either the System or Unit needs to be changed in the Region, take a
+   deep copy first in order to avoid changing the supplied Region. */
+         if( sys != astGetSystem( spreg ) ||
+             ( unit && strcmp( unit, astGetUnit( spreg, 0 ) ) ) ) {
+            treg = astCopy( spreg );
+            (void) astAnnul( spreg );
+            spreg = treg;
+            astSetAdaptive( spreg, 1 );
+            astSetSystem( spreg, sys );
+            astSetUnit( spreg, 0, unit );
+         }
+
+/* Get the Region's fill factor. */
+         fill = astGetFillFactor( spreg );
+
+/* Get the bounds of the Region (i.e. the redshift axis coverage). */
+         astGetRegionBounds( spreg, lbnd, ubnd );
+
+/* Get a pointer to the spectral Region's encapsulated Frame. */
+         spfrm = astRegFrame( spreg );
+
+/* Report a warning if the sub-phrase Frame is not a SpecFrame */
+         if( !astIsASpecFrame( spfrm ) ) {
+            ok = 0;
+            astAddWarning( this, 1, "The redshift sub-phrase in the supplied "
+                           "KeyMap is not described using an AST SpecFrame.",
+                           "astWrite", status );
+
+/* Store properties that are specific to redshift positions... */
+         } else if( lbnd[ 0 ] == ubnd[ 0 ] ) {
+            astMapPut0C( spprops, "ID", "Redshift", NULL );
+            astMapPut0D( spprops, "REDSHIFT", lbnd[ 0 ], NULL );
+            fill = AST__BAD;
+
+/* Store properties that are specific to Redshift intervals... */
+         } else if( lbnd[ 0 ] > -lim && ubnd[ 0 ] < lim ) {
+            astMapPut0C( spprops, "ID", "RedshiftInterval", NULL );
+            astMapPut0D( spprops, "LOLIMIT", lbnd[ 0 ], NULL );
+            astMapPut0D( spprops, "HILIMIT", ubnd[ 0 ], NULL );
+
+         } else {
+            ok = 0;
+            astAddWarning( this, 1, "Cannot write out an unbounded "
+                           "redshift interval.", "astWrite", status );
+         }
+
+/* Store properties that are common to all redshift sub-phrase types. First the
+   fill factor. */
+         MapPut0D( spprops, "FILLFACTOR", fill, 1.0, defs, status );
+
+/* Now the reference position. */
+         sor = astGetStdOfRest( spfrm );
+
+         if( sor == AST__GESOR ) {
+            tsor = "GEOCENTER";
+
+         } else if( sor == AST__BYSOR ) {
+            tsor = "BARYCENTER";
+
+         } else if( sor == AST__HLSOR ) {
+            tsor = "HELIOCENTER";
+
+         } else if( sor == AST__TPSOR ) {
+            tsor = "TOPOCENTER";
+
+         } else if( sor == AST__LKSOR ) {
+            tsor = "LSRK";
+
+         } else if( sor == AST__LDSOR ) {
+            tsor = "LSRD";
+
+         } else if( sor == AST__GLSOR ) {
+            tsor = "GALACTIC_CENTER";
+
+         } else {
+            tsor = NULL;
+         }
+
+         if( !tsor ) tsor = "UNKNOWNRefPos";
+         MapPut0C( spprops, "REFPOS", tsor, "UNKNOWNRefPos", defs,
+                   status );
+
+/* Type and DopplerDef. */
+         if( sys == AST__VOPTICAL ) {
+            type = "VELOCITY";
+            dopdef = "OPTICAL";
+
+         } else if( sys == AST__VRADIO ) {
+            type = "VELOCITY";
+            dopdef = "RADIO";
+
+         } else if( sys == AST__VREL ) {
+            type = "VELOCITY";
+            dopdef = "RELATIVISTIC";
+
+         } else {
+            type = "REDSHIFT";
+            dopdef = "OPTICAL";
+         }
+         astMapPut0C( spprops, "DOPPLERDEF", dopdef, NULL );
+         MapPut0C( spprops, "TYPE", type, "REDSHIFT", defs, status );
+
+/* Now the unit string. */
+         MapPut0C( spprops, "UNIT", unit, unit, defs, status );
+
+/* That's it for the redshift sub-phrase, unless the supplied Region has an
+   explicit (non-default) uncertainty. */
+         unc = astGetUnc( spreg, 0 );
+         if( unc ) {
+
+/* Get the bounds of the uncertainty. */
+            astGetRegionBounds( unc, lbnd, ubnd );
+
+/* The error is half the width of the bounding box. */
+            astMapPut0D( spprops, "ERROR", 0.5*( ubnd[ 0 ] - lbnd[ 0 ] ), NULL );
+
+/* Free resources. */
+            unc = astAnnul( unc );
+         }
+
+/* Free resources. */
+         spfrm = astAnnul( spfrm );
+         spprops = astAnnul( spprops );
+      }
+
+/* Free resources. */
+      spreg = astAnnul( spreg );
+
+   }
+
+/* Free resources */
+   if( sreg ) sreg = astAnnul( sreg );
+   if( prop ) prop = astFree( prop );
+
+/* Return the result. */
+   return ok;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     StcsArea
+
+*  Purpose:
+*     Return the CoordinateArea component when reading an STC-S document?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This is a boolean attribute which controls what is returned
+*     by the
+c     astRead
+f     AST_READ
+*     function when it is used to read from an StcsChan.
+*     If StcsArea is set non-zero (the default), then a Region
+*     representing the STC CoordinateArea will be returned by
+c     astRead.
+f     AST_READ.
+*     If StcsArea is set to zero, then the STC CoordinateArea
+*     will not be returned.
+
+*  Notes:
+*     - Other attributes such as StcsCoords and StcsProps can be used to
+*     specify other Objects to be returned by
+c     astRead.
+f     AST_READ.
+*     If more than one of these attributes is set non-zero, then the
+*     actual Object returned by
+c     astRead
+f     AST_READ
+*     will be a KeyMap, containing the requested Objects. In this
+*     case, the Region representing the STC CoordinateArea will be
+*     stored in the returned KeyMap using the key "AREA". If StcsArea
+*     is the only attribute to be set non-zero, then the Object returned by
+c     astRead
+f     AST_READ
+*     will be the CoordinateArea Region itself.
+*     - The class of Region used to represent the CoordinateArea for each
+*     STC-S sub-phrase is determined by the first word in the
+*     sub-phrase (the "sub-phrase identifier"). The individual sub-phrase
+*     Regions are combined into a single Prism, which is then simplified
+c     using astSimplify
+f     using AST_SIMPLIFY
+*     to form the returned region.
+*     - Sub-phrases that represent a single value ( that is, have
+*     identifiers "Time", "Position", "Spectral" or "Redshift" ) are
+*     considered to be be part of the STC CoordinateArea component.
+*     - The TimeFrame used to represent a time STC-S sub-phrase will have
+*     its TimeOrigin attribute set to the sub-phrase start time. If no
+*     start time is specified by the sub-phrase, then the stop time will be
+*     used instead. If no stop time is specified by the sub-phrase, then
+*     the single time value specified in the sub-phrase will be used
+*     instead. Subsequently clearing the TimeOrigin attribute (or setting
+*     its value to zero) will cause the TimeFrame to reprsent absolute times.
+*     - The Epoch attribute for the returned Region is set in the same
+*     way as the TimeOrigin attribute (see above).
+
+*  Applicability:
+*     StcsChan
+*        All StcsChans have this attribute.
+*att--
+*/
+
+/* This ia a boolean value (0 or 1) with a value of -INT_MAX when
+   undefined but yielding a default of 1. */
+astMAKE_CLEAR(StcsChan,StcsArea,stcsarea,-INT_MAX)
+astMAKE_GET(StcsChan,StcsArea,int,1,( this->stcsarea != -INT_MAX ? this->stcsarea : 1 ))
+astMAKE_SET(StcsChan,StcsArea,int,stcsarea,( value != 0 ))
+astMAKE_TEST(StcsChan,StcsArea,( this->stcsarea != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     StcsCoords
+
+*  Purpose:
+*     Return the Coordinates component when reading an STC-S document?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This is a boolean attribute which controls what is returned
+*     by the
+c     astRead
+f     AST_READ
+*     function when it is used to read from an StcsChan.
+*     If StcsCoords is set non-zero, then a PointList
+*     representing the STC Coordinates will be returned by
+c     astRead.
+f     AST_READ.
+*     If StcsCoords is set to zero (the default), then the STC
+*     Coordinates will not be returned.
+
+*  Notes:
+*     - Other attributes such as StcsArea and StcsProps can be used to
+*     specify other Objects to be returned by
+c     astRead.
+f     AST_READ.
+*     If more than one of these attributes is set non-zero, then the
+*     actual Object returned by
+c     astRead
+f     AST_READ
+*     will be a KeyMap, containing the requested Objects. In this
+*     case, the PointList representing the STC Coordinates will be
+*     stored in the returned KeyMap using the key "COORDS". If StcsCoords
+*     is the only attribute to be set non-zero, then the Object returned by
+c     astRead
+f     AST_READ
+*     will be the Coordinates PointList itself.
+*     - The Coordinates component is specified by the additional axis
+*     values embedded within the body of each STC-S sub-phrase that
+*     represents an extended area. Sub-phrases that represent a single
+*     value ( that is, have identifiers "Time", "Position", "Spectral"
+*     or "Redshift" ) are not considered to be be part of the STC
+*     Coordinates component.
+*     - If the STC-S documents does not contain a Coordinates component,
+*     then a NULL object pointer
+f     (AST__NULL)
+*     will be returned by
+c     astRead
+f     AST_READ
+*     if the Coordinates component is the only object being returned. If
+*     other objects are also being returned (see attributes StcsProps and
+*     StcsArea), then the returned KeyMap will contain a "COORDS" key
+*     only if the Coordinates component is read succesfully.
+*     - The TimeFrame used to represent a time STC-S sub-phrase will have
+*     its TimeOrigin attribute set to the sub-phrase start time. If no
+*     start time is specified by the sub-phrase, then the stop time will be
+*     used instead. If no stop time is specified by the sub-phrase, then
+*     the single time value specified in the sub-phrase will be used
+*     instead. Subsequently clearing the TimeOrigin attribute (or setting
+*     its value to zero) will cause the TimeFrame to reprsent absolute times.
+*     - The Epoch attribute for the returned Region is set in the same
+*     way as the TimeOrigin attribute (see above).
+
+*  Applicability:
+*     StcsChan
+*        All StcsChans have this attribute.
+*att--
+*/
+
+/* This ia a boolean value (0 or 1) with a value of -INT_MAX when
+   undefined but yielding a default of zero. */
+astMAKE_CLEAR(StcsChan,StcsCoords,stcscoords,-INT_MAX)
+astMAKE_GET(StcsChan,StcsCoords,int,0,( this->stcscoords != -INT_MAX ? this->stcscoords : 0 ))
+astMAKE_SET(StcsChan,StcsCoords,int,stcscoords,( value != 0 ))
+astMAKE_TEST(StcsChan,StcsCoords,( this->stcscoords != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     StcsProps
+
+*  Purpose:
+*     Return all properties when reading an STC-S document?
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This is a boolean attribute which controls what is returned
+*     by the
+c     astRead
+f     AST_READ
+*     function when it is used to read from an StcsChan.
+*     If StcsProps is set non-zero, then a KeyMap containing all the
+*     properties read from the STC-S document will be returned by
+c     astRead.
+f     AST_READ.
+*     If StcsProps is set to zero (the default), then the properties
+*     will not be returned.
+
+*  Notes:
+*     - Other attributes such as StcsCoords and StcsArea can be used to
+*     specify other Objects to be returned by
+c     astRead.
+f     AST_READ.
+*     If more than one of these attributes is set non-zero, then the
+*     actual Object returned by
+c     astRead
+f     AST_READ
+*     will be a KeyMap containing the requested Objects. In this
+*     case, the properties KeyMap will be stored in the returned KeyMap
+*     using the key "PROPS". If StcsProps is the only attribute to be
+*     set non-zero, then the Object returned by
+c     astRead
+f     AST_READ
+*     will be the properties KeyMap itself.
+*     - The KeyMap containing the properties will have entries for one or
+*     more of the following keys: "TIME_PROPS", "SPACE_PROPS", "SPECTRAL_PROPS"
+*     and "REDSHIFT_PROPS". Each of these entries will be another KeyMap
+*     containing the properties of the corresponding STC-S sub-phrase.
+
+*  Applicability:
+*     StcsChan
+*        All StcsChans have this attribute.
+*att--
+*/
+
+/* This ia a boolean value (0 or 1) with a value of -INT_MAX when
+   undefined but yielding a default of zero. */
+astMAKE_CLEAR(StcsChan,StcsProps,stcsprops,-INT_MAX)
+astMAKE_GET(StcsChan,StcsProps,int,0,( this->stcsprops != -INT_MAX ? this->stcsprops : 0 ))
+astMAKE_SET(StcsChan,StcsProps,int,stcsprops,( value != 0 ))
+astMAKE_TEST(StcsChan,StcsProps,( this->stcsprops != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     StcsLength
+
+*  Purpose:
+*     Controls output line length.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute specifies the maximum length to use when writing out
+*     text through the sink function supplied when the StcsChan was created.
+*     It is ignored if the Indent attribute is zero (in which case the text
+*     supplied to the sink function can be of any length). The default value
+*     is 70.
+*
+*     The number of characters in each string written out through the sink
+*     function will not usually be greater than the value of this attribute
+*     (but may be less). However, if any single word in the STC-S
+*     description exceeds the specified length, then the word will be
+*     written out as a single line.
+*
+f     Note, the default value of zero is unlikely to be appropriate when
+f     an StcsChan is used within Fortran code. In this case, StcsLength
+f     should usually be set to the size of the CHARACTER variable used to
+f     receive the text returned by AST_GETLINE within the sink function.
+f     In addition, the Indent attribute should be set non-zero. This
+f     avoids the possibility of long lines being truncated invisibly
+f     within AST_GETLINE.
+
+*  Applicability:
+*     StcsChan
+*        All StcsChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(StcsChan,StcsLength,stcslength,-INT_MAX)
+astMAKE_GET(StcsChan,StcsLength,int,70,( ( this->stcslength != -INT_MAX ) ? this->stcslength : 70 ))
+astMAKE_SET(StcsChan,StcsLength,int,stcslength,(value<0?0:value))
+astMAKE_TEST(StcsChan,StcsLength,( this->stcslength != -INT_MAX ))
+
+/* Copy constructor. */
+/* ----------------- */
+
+/* Destructor. */
+/* ----------- */
+
+/* Dump function. */
+/* -------------- */
+
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for StcsChan objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the StcsChan class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Object (an StcsChan) whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstStcsChan *this;            /* Pointer to the StcsChan structure */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the StcsChan structure. */
+   this = (AstStcsChan *) this_object;
+
+/* Write out values representing the instance variables for the
+   StcsChan class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* StcsArea. */
+/* --------- */
+   set = TestStcsArea( this, status );
+   ival = set ? GetStcsArea( this, status ) : astGetStcsArea( this );
+   astWriteInt( channel, "StcsArea", set, 0, ival,
+                ival ? "Read the STC CoordinatesArea component" :
+                       "Do not read the STC CoordinatesArea component" );
+
+/* StcsCoords. */
+/* ----------- */
+   set = TestStcsCoords( this, status );
+   ival = set ? GetStcsCoords( this, status ) : astGetStcsCoords( this );
+   astWriteInt( channel, "StcsCoords", set, 0, ival,
+                ival ? "Read the STC Coordinates component" :
+                       "Do not read the STC Coordinates component" );
+
+/* StcsProps. */
+/* ---------- */
+   set = TestStcsProps( this, status );
+   ival = set ? GetStcsProps( this, status ) : astGetStcsProps( this );
+   astWriteInt( channel, "StcsProps", set, 0, ival,
+                ival ? "Read the STC-S properties" :
+                       "Do not read the STC-S properties" );
+
+/* StcsLength */
+/* ---------- */
+      set = TestStcsLength( this, status );
+      ival = set ? GetStcsLength( this, status ) : astGetStcsLength( this );
+      astWriteInt( channel, "StcsLen", set, 0, ival, "STC-S buffer length" );
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAStcsChan and astCheckStcsChan functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(StcsChan,Channel)
+astMAKE_CHECK(StcsChan)
+
+AstStcsChan *astStcsChan_( const char *(* source)( void ),
+                           void (* sink)( const char * ),
+                           const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astStcsChan
+f     AST_STCSCHAN
+
+*  Purpose:
+*     Create an StcsChan.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "stcschan.h"
+c     AstStcsChan *astStcsChan( const char *(* source)( void ),
+c                               void (* sink)( const char * ),
+c                               const char *options, ... )
+f     RESULT = AST_STCSCHAN( SOURCE, SINK, OPTIONS, STATUS )
+
+*  Class Membership:
+*     StcsChan constructor.
+
+*  Description:
+*     This function creates a new StcsChan and optionally initialises
+*     its attributes.
+*
+*     A StcsChan is a specialised form of Channel which supports STC-S
+*     I/O operations. Writing an Object to an StcsChan (using
+c     astWrite) will, if the Object is suitable, generate an
+f     AST_WRITE) will, if the Object is suitable, generate an
+*     STC-S description of that Object, and reading from an StcsChan will
+*     create a new Object from its STC-S description.
+*
+*     Normally, when you use an StcsChan, you should provide "source"
+c     and "sink" functions which connect it to an external data store
+c     by reading and writing the resulting text. These functions
+f     and "sink" routines which connect it to an external data store
+f     by reading and writing the resulting text. These routines
+*     should perform any conversions needed between external character
+c     encodings and the internal ASCII encoding. If no such functions
+f     encodings and the internal ASCII encoding. If no such routines
+*     are supplied, a Channel will read from standard input and write
+*     to standard output.
+*
+*     Alternatively, an XmlChan can be told to read or write from
+*     specific text files using the SinkFile and SourceFile attributes,
+*     in which case no sink or source function need be supplied.
+
+*  Parameters:
+c     source
+f     SOURCE = SUBROUTINE (Given)
+c        Pointer to a source function that takes no arguments and
+c        returns a pointer to a null-terminated string.  If no value
+c        has been set for the SourceFile attribute, this function
+c        will be used by the StcsChan to obtain lines of input text. On
+c        each invocation, it should return a pointer to the next input
+c        line read from some external data store, and a NULL pointer
+c        when there are no more lines to read.
+c
+c        If "source" is NULL and no value has been set for the SourceFile
+c        attribute, the StcsChan will read from standard input instead.
+f        A source routine, which is a subroutine which takes a single
+f        integer error status argument.   If no value has been set
+f        for the SourceFile attribute, this routine will be used by
+f        the StcsChan to obtain lines of input text. On each
+f        invocation, it should read the next input line from some
+f        external data store, and then return the resulting text to
+f        the AST library by calling AST_PUTLINE. It should supply a
+f        negative line length when there are no more lines to read.
+f        If an error occurs, it should set its own error status
+f        argument to an error value before returning.
+f
+f        If the null routine AST_NULL is suppied as the SOURCE value,
+f        and no value has been set for the SourceFile attribute,
+f        the StcsChan will read from standard input instead.
+c     sink
+f     SINK = SUBROUTINE (Given)
+c        Pointer to a sink function that takes a pointer to a
+c        null-terminated string as an argument and returns void.
+c        If no value has been set for the SinkFile attribute, this
+c        function will be used by the StcsChan to deliver lines of
+c        output text. On each invocation, it should deliver the
+c        contents of the string supplied to some external data store.
+c
+c        If "sink" is NULL, and no value has been set for the SinkFile
+c        attribute, the StcsChan will write to standard output instead.
+f        A sink routine, which is a subroutine which takes a single
+f        integer error status argument.  If no value has been set
+f        for the SinkFile attribute, this routine will be used by
+f        the StcsChan to deliver lines of output text. On each
+f        invocation, it should obtain the next output line from the
+f        AST library by calling AST_GETLINE, and then deliver the
+f        resulting text to some external data store.  If an error
+f        occurs, it should set its own error status argument to an
+f        error value before returning.
+f
+f        If the null routine AST_NULL is suppied as the SINK value,
+f        and no value has been set for the SinkFile attribute,
+f        the StcsChan will write to standard output instead.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new StcsChan. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new StcsChan. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astStcsChan()
+f     AST_STCSCHAN = INTEGER
+*        A pointer to the new StcsChan.
+
+*  Notes:
+f     - The names of the routines supplied for the SOURCE and SINK
+f     arguments should appear in EXTERNAL statements in the Fortran
+f     routine which invokes AST_STCSCHAN. However, this is not generally
+f     necessary for the null routine AST_NULL (so long as the AST_PAR
+f     include file has been used).
+*     - If the external data source or sink uses a character encoding
+*     other than ASCII, the supplied source and sink functions should
+*     translate between the external character encoding and the internal
+*     ASCII encoding used by AST.
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+f     - Note that the null routine AST_NULL (one underscore) is
+f     different to AST__NULL (two underscores), which is the null Object
+f     pointer.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstStcsChan *new;             /* Pointer to new StcsChan */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the StcsChan, allocating memory and initialising the
+   virtual function table as well if necessary. This interface is for
+   use by other C functions within AST, and uses the standard "wrapper"
+   functions included in this class. */
+   new = astInitStcsChan( NULL, sizeof( AstStcsChan ), !class_init,
+                          &class_vtab, "StcsChan", source, SourceWrap,
+                          sink, SinkWrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   StcsChan's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new StcsChan. */
+   return new;
+}
+
+AstStcsChan *astStcsChanId_( const char *(* source)( void ),
+                             void (* sink)( const char * ),
+                             const char *options, ... ) {
+/*
+*  Name:
+*     astStcsChanId_
+
+*  Purpose:
+*     Create an StcsChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     AstStcsChan *astStcsChanId_( const char *(* source)( void ),
+*                                  void (* sink)( const char * ),
+*                                  const char *options, ... )
+
+*  Class Membership:
+*     StcsChan constructor.
+
+*  Description:
+*     This function implements the external (public) C interface to the
+*     astStcsChan constructor function. Another function (astStcsChanForId)
+*     should be called to create an StcsChan for use within other languages.
+*     Both functions return an ID value (instead of a true C pointer) to
+*     external users, and must be provided because astStcsChan_ has a variable
+*     argument list which cannot be encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astStcsChan_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astStcsChan_.
+
+*  Returned Value:
+*     The ID value associated with the new StcsChan.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstStcsChan *new;             /* Pointer to new StcsChan */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the StcsChan, allocating memory and initialising the
+   virtual function table as well if necessary. This interface is for
+   use by external C functions and uses the standard "wrapper"
+   functions included in this class. */
+   new = astInitStcsChan( NULL, sizeof( AstStcsChan ), !class_init,
+                          &class_vtab, "StcsChan", source, SourceWrap,
+                          sink, SinkWrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   StcsChan's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new StcsChan. */
+   return astMakeId( new );
+}
+
+AstStcsChan *astStcsChanForId_( const char *(* source)( void ),
+                                char *(* source_wrap)( const char *(*)( void ), int * ),
+                                void (* sink)( const char * ),
+                                void (* sink_wrap)( void (*)( const char * ),
+                                                    const char *, int * ),
+                                const char *options, ... ) {
+/*
+*+
+*  Name:
+*     astStcsChanFor
+
+*  Purpose:
+*     Initialise an StcsChan from a foreign language interface.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     AstStcsChan *astStcsChanFor( const char *(* source)( void ),
+*                                  char *(* source_wrap)( const char *(*)
+*                                                         ( void ), int * ),
+*                                  void (* sink)( const char * ),
+*                                  void (* sink_wrap)( void (*)( const char * ),
+*                                                      const char *, int * ),
+*                                  const char *options, ... )
+
+*  Class Membership:
+*     StcsChan constructor.
+
+*  Description:
+*     This function creates a new StcsChan from a foreign language
+*     interface and optionally initialises its attributes.
+*
+*     A StcsChan is a specialised form of Channel which supports STC-S
+*     I/O operations. Writing an Object to an StcsChan (using
+c     astWrite) will, if the Object is suitable, generate an
+f     AST_WRITE) will, if the Object is suitable, generate an
+*     STC-S description of that Object, and reading from an StcsChan will
+*     create a new Object from its STC-S description.
+*
+*     Normally, when you use an StcsChan, you should provide "source"
+c     and "sink" functions which connect it to an external data store
+c     by reading and writing the resulting text. These functions
+f     and "sink" routines which connect it to an external data store
+f     by reading and writing the resulting text. These routines
+*     should perform any conversions needed between external character
+c     encodings and the internal ASCII encoding. If no such functions
+f     encodings and the internal ASCII encoding. If no such routines
+*     are supplied, a Channel will read from standard input and write
+*     to standard output.
+
+*  Parameters:
+*     source
+*        Pointer to a "source" function which will be used to obtain
+*        lines of input text. Generally, this will be obtained by
+*        casting a pointer to a source function which is compatible
+*        with the "source_wrap" wrapper function (below). The pointer
+*        should later be cast back to its original type by the
+*        "source_wrap" function before the function is invoked.
+*
+*        If "source" is NULL, the StcsChan will read from standard
+*        input instead.
+*     source_wrap
+*        Pointer to a function which can be used to invoke the
+*        "source" function supplied (above). This wrapper function is
+*        necessary in order to hide variations in the nature of the
+*        source function, such as may arise when it is supplied by a
+*        foreign (non-C) language interface.
+*
+*        The single parameter of the "source_wrap" function is a
+*        pointer to the "source" function, and it should cast this
+*        function pointer (as necessary) and invoke the function with
+*        appropriate arguments to obtain the next line of input
+*        text. The "source_wrap" function should then return a pointer
+*        to a dynamically allocated, null terminated string containing
+*        the text that was read. The string will be freed (using
+*        astFree) when no longer required and the "source_wrap"
+*        function need not concern itself with this. A NULL pointer
+*        should be returned if there is no more input to read.
+*
+*        If "source_wrap" is NULL, the StcsChan will read from standard
+*        input instead.
+*     sink
+*        Pointer to a "sink" function which will be used to deliver
+*        lines of output text. Generally, this will be obtained by
+*        casting a pointer to a sink function which is compatible with
+*        the "sink_wrap" wrapper function (below). The pointer should
+*        later be cast back to its original type by the "sink_wrap"
+*        function before the function is invoked.
+*
+*        If "sink" is NULL, the StcsChan will write to standard output
+*        instead.
+*     sink_wrap
+*        Pointer to a function which can be used to invoke the "sink"
+*        function supplied (above). This wrapper function is necessary
+*        in order to hide variations in the nature of the sink
+*        function, such as may arise when it is supplied by a foreign
+*        (non-C) language interface.
+*
+*        The first parameter of the "sink_wrap" function is a pointer
+*        to the "sink" function, and the second parameter is a pointer
+*        to a const, null-terminated character string containing the
+*        text to be written.  The "sink_wrap" function should cast the
+*        "sink" function pointer (as necessary) and invoke the
+*        function with appropriate arguments to deliver the line of
+*        output text. The "sink_wrap" function then returns void.
+*
+*        If "sink_wrap" is NULL, the Channel will write to standard
+*        output instead.
+*     options
+*        Pointer to a null-terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new StcsChan. The syntax used is identical to
+*        that for the astSet function and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of additional arguments may follow it in
+*        order to supply values to be substituted for these
+*        specifiers. The rules for supplying these are identical to
+*        those for the astSet function (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     astStcsChanFor()
+*        A pointer to the new StcsChan.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*     - This function is only available through the public interface
+*     to the StcsChan class (not the protected interface) and is
+*     intended solely for use in implementing foreign language
+*     interfaces to this class.
+*-
+
+*  Implememtation Notes:
+*     - This function behaves exactly like astStcsChanId_, in that it
+*     returns ID values and not true C pointers, but it has two
+*     additional arguments. These are pointers to the "wrapper
+*     functions" which are needed to accommodate foreign language
+*     interfaces.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstStcsChan *new;             /* Pointer to new StcsChan */
+   va_list args;                 /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise the StcsChan, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitStcsChan( NULL, sizeof( AstStcsChan ), !class_init,
+                          &class_vtab, "StcsChan", source, source_wrap,
+                          sink, sink_wrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   StcsChan's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new StcsChan. */
+   return astMakeId( new );
+}
+
+AstStcsChan *astInitStcsChan_( void *mem, size_t size, int init,
+                               AstStcsChanVtab *vtab, const char *name,
+                               const char *(* source)( void ),
+                               char *(* source_wrap)( const char *(*)( void ), int * ),
+                               void (* sink)( const char * ),
+                               void (* sink_wrap)( void (*)( const char * ),
+                                                   const char *, int * ), int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcsChan
+
+*  Purpose:
+*     Initialise an StcsChan.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     AstStcsChan *astInitStcsChan( void *mem, size_t size, int init,
+*                                   AstStcsChanVtab *vtab, const char *name,
+*                                   const char *(* source)( void ),
+*                                   char *(* source_wrap)( const char *(*)( void ), int * ),
+*                                   void (* sink)( const char * ),
+*                                   void (* sink_wrap)( void (*)( const char * ),
+*                                                     const char *, int * ) )
+
+*  Class Membership:
+*     StcsChan initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new StcsChan object. It allocates memory (if
+*     necessary) to accommodate the StcsChan plus any additional data
+*     associated with the derived class.  It then initialises a
+*     StcsChan structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual
+*     function table for an StcsChan at the start of the memory passed
+*     via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the StcsChan is to be
+*        initialised.  This must be of sufficient size to accommodate
+*        the StcsChan data (sizeof(StcsChan)) plus any data used by the
+*        derived class. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the StcsChan (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the StcsChan structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*     init
+*        A boolean flag indicating if the StcsChan's virtual function
+*        table is to be initialised. If this value is non-zero, the
+*        virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new StcsChan.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*     source
+*        Pointer to a "source" function which will be used to obtain
+*        lines of text. Generally, this will be obtained by
+*        casting a pointer to a source function which is compatible
+*        with the "source_wrap" wrapper function (below). The pointer
+*        should later be cast back to its original type by the
+*        "source_wrap" function before the function is invoked.
+*
+*        If "source" is NULL, the Channel will read from standard
+*        input instead.
+*     source_wrap
+*        Pointer to a function which can be used to invoke the
+*        "source" function supplied (above). This wrapper function is
+*        necessary in order to hide variations in the nature of the
+*        source function, such as may arise when it is supplied by a
+*        foreign (non-C) language interface.
+*
+*        The single parameter of the "source_wrap" function is a
+*        pointer to the "source" function, and it should cast this
+*        function pointer (as necessary) and invoke the function with
+*        appropriate arguments to obtain the next line of input
+*        text. The "source_wrap" function should then return a pointer
+*        to a dynamically allocated, null terminated string containing
+*        the text that was read. The string will be freed (using
+*        astFree) when no longer required and the "source_wrap"
+*        function need not concern itself with this. A NULL pointer
+*        should be returned if there is no more input to read.
+*
+*        If "source_wrap" is NULL, the Channel will read from standard
+*        input instead.
+*     sink
+*        Pointer to a "sink" function which will be used to deliver
+*        lines of text. Generally, this will be obtained by
+*        casting a pointer to a sink function which is compatible with
+*        the "sink_wrap" wrapper function (below). The pointer should
+*        later be cast back to its original type by the "sink_wrap"
+*        function before the function is invoked.
+*
+*        If "sink" is NULL, the contents of the StcsChan will not be
+*        written out before being deleted.
+*     sink_wrap
+*        Pointer to a function which can be used to invoke the "sink"
+*        function supplied (above). This wrapper function is necessary
+*        in order to hide variations in the nature of the sink
+*        function, such as may arise when it is supplied by a foreign
+*        (non-C) language interface.
+*
+*        The first parameter of the "sink_wrap" function is a pointer
+*        to the "sink" function, and the second parameter is a pointer
+*        to a const, null-terminated character string containing the
+*        text to be written.  The "sink_wrap" function should cast the
+*        "sink" function pointer (as necessary) and invoke the
+*        function with appropriate arguments to deliver the line of
+*        output text. The "sink_wrap" function then returns void.
+*
+*        If "sink_wrap" is NULL, the Channel will write to standard
+*        output instead.
+
+*  Returned Value:
+*     A pointer to the new StcsChan.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstStcsChan *new;              /* Pointer to new StcsChan */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitStcsChanVtab( vtab, name );
+
+/* Initialise a Channel structure (the parent class) as the first
+   component within the StcsChan structure, allocating memory if
+   necessary. */
+   new = (AstStcsChan *) astInitChannel( mem, size, 0,
+                                         (AstChannelVtab *) vtab, name,
+                                         source, source_wrap, sink,
+                                         sink_wrap );
+
+   if ( astOK ) {
+
+/* Initialise the StcsChan data. */
+/* ---------------------------- */
+      new->stcsarea = -INT_MAX;
+      new->stcscoords = -INT_MAX;
+      new->stcsprops = -INT_MAX;
+      new->stcslength = -INT_MAX;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstStcsChan *astLoadStcsChan_( void *mem, size_t size,
+                               AstStcsChanVtab *vtab, const char *name,
+                               AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadStcsChan
+
+*  Purpose:
+*     Load an StcsChan.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcschan.h"
+*     AstStcsChan *astLoadStcsChan( void *mem, size_t size,
+*                                   AstStcsChanVtab *vtab, const char *name,
+*                                   AstChannel *channel )
+
+*  Class Membership:
+*     StcsChan loader.
+
+*  Description:
+*     This function is provided to load a new StcsChan using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     StcsChan structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for an StcsChan at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the StcsChan is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        StcsChan data (sizeof(StcsChan)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the StcsChan (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the StcsChan structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstStcsChan) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new StcsChan. If this is NULL, a pointer
+*        to the (static) virtual function table for the StcsChan class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "StcsChan" is used instead.
+
+*  Returned Value:
+*     A pointer to the new StcsChan.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
+   AstStcsChan *new;            /* Pointer to the new StcsChan */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this StcsChan. In this case the
+   StcsChan belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstStcsChan );
+      vtab = &class_vtab;
+      name = "StcsChan";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitStcsChanVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built StcsChan. */
+   new = astLoadChannel( mem, size, (AstChannelVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "StcsChan" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* StcsArea. */
+/* --------- */
+      new->stcsarea = astReadInt( channel, "stcsarea", -INT_MAX );
+      if ( TestStcsArea( new, status ) ) SetStcsArea( new, new->stcsarea, status );
+
+/* StcsCoords. */
+/* ----------- */
+      new->stcscoords = astReadInt( channel, "stcscoords", -INT_MAX );
+      if ( TestStcsCoords( new, status ) ) SetStcsCoords( new, new->stcscoords, status );
+
+/* StcsProps. */
+/* ---------- */
+      new->stcsprops = astReadInt( channel, "stcsprops", -INT_MAX );
+      if ( TestStcsProps( new, status ) ) SetStcsProps( new, new->stcsprops, status );
+
+/* StcsLength */
+/* ---------- */
+      new->stcslength = astReadInt( channel, "stcslen", -INT_MAX );
+
+   }
+
+/* If an error occurred, clean up by deleting the new StcsChan. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the new StcsChan pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
+
+
+
diff --git a/ast/stcschan.h b/ast/stcschan.h
new file mode 100644
index 0000000..11de827
--- /dev/null
+++ b/ast/stcschan.h
@@ -0,0 +1,308 @@
+#if !defined( STCSCHAN_INCLUDED ) /* Include this file only once */
+#define STCSCHAN_INCLUDED
+/*
+*+
+*  Name:
+*     stcschan.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the StcsChan class.
+
+*  Invocation:
+*     #include "stcschan.h"
+
+*  Description:
+*     This include file defines the interface to the StcsChan class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The StcsChan class provides facilities for reading and writing AST
+*     Objects in the form of STC-S text.
+
+*  Inheritance:
+*     The StcsChan class inherits from the Channel class.
+
+*  Copyright:
+*     Copyright (C) 2008 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (JAC, UCLan)
+
+*  History:
+*     18-DEC-2008 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "channel.h"             /* I/O channels (parent class) */
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros. */
+/* ------- */
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* Define constants used to size global arrays in this module. */
+/* Define other numerical constants for use in this module. */
+#define AST__STCSCHAN_GETATTRIB_BUFF_LEN 200
+
+/* Type Definitions. */
+/* ================= */
+
+/* StcsChan structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstStcsChan {
+
+/* Attributes inherited from the parent class. */
+   AstChannel channel;          /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int stcsarea;                /* Read the STC CoordinatesArea? */
+   int stcscoords;              /* Read the STC Coordinates? */
+   int stcsprops;               /* Read the STC-S properties? */
+   int stcslength;              /* Line length */
+} AstStcsChan;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstStcsChanVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstChannelVtab channel_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   int (* GetStcsArea)( AstStcsChan *, int * );
+   int (* TestStcsArea)( AstStcsChan *, int * );
+   void (* ClearStcsArea)( AstStcsChan *, int * );
+   void (* SetStcsArea)( AstStcsChan *, int, int * );
+
+   int (* GetStcsCoords)( AstStcsChan *, int * );
+   int (* TestStcsCoords)( AstStcsChan *, int * );
+   void (* ClearStcsCoords)( AstStcsChan *, int * );
+   void (* SetStcsCoords)( AstStcsChan *, int, int * );
+
+   int (* GetStcsProps)( AstStcsChan *, int * );
+   int (* TestStcsProps)( AstStcsChan *, int * );
+   void (* ClearStcsProps)( AstStcsChan *, int * );
+   void (* SetStcsProps)( AstStcsChan *, int, int * );
+
+   int (* GetStcsLength)( AstStcsChan *, int * );
+   int (* TestStcsLength)( AstStcsChan *, int * );
+   void (* ClearStcsLength)( AstStcsChan *, int * );
+   void (* SetStcsLength)( AstStcsChan *, int, int * );
+
+} AstStcsChanVtab;
+
+#if defined(THREAD_SAFE)
+typedef struct AstStcsChanGlobals {
+   AstStcsChanVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ AST__STCSCHAN_GETATTRIB_BUFF_LEN + 1 ];
+} AstStcsChanGlobals;
+
+#endif
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(StcsChan)          /* Check class membership */
+astPROTO_ISA(StcsChan)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstStcsChan *astStcsChan_( const char *(*)( void ), void (*)( const char * ),
+                          const char *, int *, ...);
+#else
+AstStcsChan *astStcsChanId_( const char *(*)( void ), void (*)( const char * ),
+                            const char *, ... );
+AstStcsChan *astStcsChanForId_( const char *(*)( void ),
+                              char *(*)( const char *(*)( void ), int * ),
+                              void (*)( const char * ),
+                              void (*)( void (*)( const char * ),
+                                        const char *, int * ),
+                              const char *, ... );
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstStcsChan *astInitStcsChan_( void *, size_t, int, AstStcsChanVtab *,
+                             const char *, const char *(*)( void ),
+                             char *(*)( const char *(*)( void ), int * ),
+                             void (*)( const char * ),
+                             void (*)( void (*)( const char * ),
+                             const char *, int * ), int * );
+
+/* Vtab initialiser. */
+void astInitStcsChanVtab_( AstStcsChanVtab *, const char *, int * );
+
+
+
+/* Loader. */
+AstStcsChan *astLoadStcsChan_( void *, size_t, AstStcsChanVtab *,
+                               const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitStcsChanGlobals_( AstStcsChanGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+# if defined(astCLASS)           /* Protected */
+int astGetStcsArea_( AstStcsChan *, int * );
+int astTestStcsArea_( AstStcsChan *, int * );
+void astClearStcsArea_( AstStcsChan *, int * );
+void astSetStcsArea_( AstStcsChan *, int, int * );
+
+int astGetStcsCoords_( AstStcsChan *, int * );
+int astTestStcsCoords_( AstStcsChan *, int * );
+void astClearStcsCoords_( AstStcsChan *, int * );
+void astSetStcsCoords_( AstStcsChan *, int, int * );
+
+int astGetStcsProps_( AstStcsChan *, int * );
+int astTestStcsProps_( AstStcsChan *, int * );
+void astClearStcsProps_( AstStcsChan *, int * );
+void astSetStcsProps_( AstStcsChan *, int, int * );
+
+int astGetStcsLength_( AstStcsChan *, int * );
+int astTestStcsLength_( AstStcsChan *, int * );
+void astClearStcsLength_( AstStcsChan *, int * );
+void astSetStcsLength_( AstStcsChan *, int, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckStcsChan(this) astINVOKE_CHECK(StcsChan,this,0)
+#define astVerifyStcsChan(this) astINVOKE_CHECK(StcsChan,this,1)
+
+/* Test class membership. */
+#define astIsAStcsChan(this) astINVOKE_ISA(StcsChan,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astStcsChan astINVOKE(F,astStcsChan_)
+#else
+#define astStcsChan astINVOKE(F,astStcsChanId_)
+#define astStcsChanFor astINVOKE(F,astStcsChanForId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitStcsChan(mem,size,init,vtab,name,source,source_wrap,sink,sink_wrap) \
+astINVOKE(O,astInitStcsChan_(mem,size,init,vtab,name,source,source_wrap,sink,sink_wrap,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitStcsChanVtab(vtab,name) astINVOKE(V,astInitStcsChanVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadStcsChan(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadStcsChan_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to member functions. */
+/* ------------------------------- */
+/* Here we make use of astCheckStcsChan to validate StcsChan pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+
+#if defined(astCLASS)            /* Protected */
+
+#define astClearStcsArea(this) \
+astINVOKE(V,astClearStcsArea_(astCheckStcsChan(this),STATUS_PTR))
+#define astGetStcsArea(this) \
+astINVOKE(V,astGetStcsArea_(astCheckStcsChan(this),STATUS_PTR))
+#define astSetStcsArea(this,value) \
+astINVOKE(V,astSetStcsArea_(astCheckStcsChan(this),value,STATUS_PTR))
+#define astTestStcsArea(this) \
+astINVOKE(V,astTestStcsArea_(astCheckStcsChan(this),STATUS_PTR))
+
+#define astClearStcsCoords(this) \
+astINVOKE(V,astClearStcsCoords_(astCheckStcsChan(this),STATUS_PTR))
+#define astGetStcsCoords(this) \
+astINVOKE(V,astGetStcsCoords_(astCheckStcsChan(this),STATUS_PTR))
+#define astSetStcsCoords(this,value) \
+astINVOKE(V,astSetStcsCoords_(astCheckStcsChan(this),value,STATUS_PTR))
+#define astTestStcsCoords(this) \
+astINVOKE(V,astTestStcsCoords_(astCheckStcsChan(this),STATUS_PTR))
+
+#define astClearStcsProps(this) \
+astINVOKE(V,astClearStcsProps_(astCheckStcsChan(this),STATUS_PTR))
+#define astGetStcsProps(this) \
+astINVOKE(V,astGetStcsProps_(astCheckStcsChan(this),STATUS_PTR))
+#define astSetStcsProps(this,value) \
+astINVOKE(V,astSetStcsProps_(astCheckStcsChan(this),value,STATUS_PTR))
+#define astTestStcsProps(this) \
+astINVOKE(V,astTestStcsProps_(astCheckStcsChan(this),STATUS_PTR))
+
+#define astClearStcsLength(this) astINVOKE(V,astClearStcsLength_(astCheckStcsChan(this),STATUS_PTR))
+#define astGetStcsLength(this) astINVOKE(V,astGetStcsLength_(astCheckStcsChan(this),STATUS_PTR))
+#define astSetStcsLength(this,stcslength) astINVOKE(V,astSetStcsLength_(astCheckStcsChan(this),stcslength,STATUS_PTR))
+#define astTestStcsLength(this) astINVOKE(V,astTestStcsLength_(astCheckStcsChan(this),STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
diff --git a/ast/stcsearchlocation.c b/ast/stcsearchlocation.c
new file mode 100644
index 0000000..aa6bd4e
--- /dev/null
+++ b/ast/stcsearchlocation.c
@@ -0,0 +1,806 @@
+/*
+*class++
+*  Name:
+*     StcSearchLocation
+
+*  Purpose:
+*     Correspond to the IVOA SearchLocation class.
+
+*  Constructor Function:
+c     astStcSearchLocation
+f     AST_STCSEARCHLOCATION
+
+*  Description:
+*     The StcSearchLocation class is a sub-class of Stc used to describe
+*     the coverage of a query.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Inheritance:
+*     The StcSearchLocation class inherits from the Stc class.
+
+*  Attributes:
+*     The StcSearchLocation class does not define any new attributes beyond
+*     those which are applicable to all Stcs.
+
+*  Functions:
+c     The StcSearchLocation class does not define any new functions beyond those
+f     The StcSearchLocation class does not define any new routines beyond those
+*     which are applicable to all Stcs.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     26-NOV-2004 (DSB):
+*        Original version.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS StcSearchLocation
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "stc.h"                 /* Coordinate stcs (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "region.h"              /* Regions within coordinate systems */
+#include "stcsearchlocation.h"   /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(StcSearchLocation)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(StcSearchLocation,Class_Init)
+#define class_vtab astGLOBAL(StcSearchLocation,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstStcSearchLocationVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstStcSearchLocation *astStcSearchLocationId_( void *, int, AstKeyMap **, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static void Dump( AstObject *, AstChannel *, int * );
+
+/* Member functions. */
+/* ================= */
+
+void astInitStcSearchLocationVtab_( AstStcSearchLocationVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcSearchLocationVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a StcSearchLocation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcsearchlocation.h"
+*     void astInitStcSearchLocationVtab( AstStcSearchLocationVtab *vtab, const char *name )
+
+*  Class Membership:
+*     StcSearchLocation vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the StcSearchLocation class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;  /* Pointer to Mapping component of Vtab */
+   AstStcVtab *stc;          /* Pointer to Stc component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitStcVtab( (AstStcVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAStcSearchLocation) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstStcVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   mapping = (AstMappingVtab *) vtab;
+   stc = (AstStcVtab *) vtab;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+
+/* Declare the copy constructor, destructor and class dump
+   functions. */
+   astSetDump( vtab, Dump, "StcSearchLocation", "Query coverage" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+/* None */
+
+/* Destructor. */
+/* ----------- */
+/* None */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for StcSearchLocation objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the StcSearchLocation class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the StcSearchLocation whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstStcSearchLocation *this;                 /* Pointer to the StcSearchLocation structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the StcSearchLocation structure. */
+   this = (AstStcSearchLocation *) this_object;
+
+/* Write out values representing the instance variables for the
+   StcSearchLocation class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* There are no values to write, so return without further action. */
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAStcSearchLocation and astCheckStcSearchLocation functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(StcSearchLocation,Stc)
+astMAKE_CHECK(StcSearchLocation)
+
+AstStcSearchLocation *astStcSearchLocation_( void *region_void, int ncoords,
+                               AstKeyMap **coords, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astStcSearchLocation
+f     AST_STCSEARCHLOCATION
+
+*  Purpose:
+*     Create a StcSearchLocation.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "stcsearchlocation.h"
+c     AstStcResourceProfile *astStcSearchLocation( AstRegion *region,
+c                   int ncoords, AstKeyMap *coords[], const char *options, ... )
+f     RESULT = AST_STCSEARCHLOCATION( REGION, NCOORDS, COORDS, OPTIONS, STATUS )
+
+*  Class Membership:
+*     StcSearchLocation constructor.
+
+*  Description:
+*     This function creates a new StcSearchLocation and optionally initialises its
+*     attributes.
+*
+*     The StcSearchLocation class is a sub-class of Stc used to describe
+*     the coverage of a VO query.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Parameters:
+c     region
+f     REGION = INTEGER (Given)
+*        Pointer to the encapsulated Region.
+c     ncoords
+f     NCOORDS = INTEGER (Given)
+c        The length of the "coords" array. Supply zero if "coords" is NULL.
+f        The length of the COORDS array. Supply zero if COORDS should be
+f        ignored.
+c     coords
+f     COORDS( NCOORDS ) = INTEGER (Given)
+c        Pointer to an array holding "ncoords" AstKeyMap pointers (if "ncoords"
+f        An array holding NCOORDS AstKeyMap pointers (if NCOORDS
+*        is zero, the supplied value is ignored). Each supplied KeyMap
+*        describes the contents of a single STC <AstroCoords> element, and
+*        should have elements with keys given by constants AST__STCNAME,
+*        AST__STCVALUE, AST__STCERROR, AST__STCRES, AST__STCSIZE,
+*        AST__STCPIXSZ. Any of these elements may be omitted, but no other
+*        elements should be included. If supplied, the AST__STCNAME element
+*        should be a vector of character string pointers holding the "Name"
+*        item for each axis in the coordinate system represented by
+c        "region".
+f        REGION.
+*        Any other supplied elements should be scalar elements, each  holding
+*        a pointer to a Region describing the associated item of ancillary
+*        information (error, resolution, size, pixel size or value). These
+*        Regions should describe a volume within the coordinate system
+c        represented by "region".
+f        represented by REGION.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new StcSearchLocation. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new StcSearchLocation. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astStcSearchLocation()
+f     AST_STCSEARCHLOCATION = INTEGER
+*        A pointer to the new StcSearchLocation.
+
+*  Notes:
+*     - A deep copy is taken of the supplied Region. This means that
+*     any subsequent changes made to the encapsulated Region using the
+*     supplied pointer will have no effect on the Stc.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstRegion *region;            /* Pointer to Region structure */
+   AstStcSearchLocation *new;    /* Pointer to new StcSearchLocation */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain and validate a pointer to the Region structure provided. */
+   region = astCheckRegion( region_void );
+
+/* Initialise the StcSearchLocation, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitStcSearchLocation( NULL, sizeof( AstStcSearchLocation ), !class_init,
+                                    &class_vtab, "StcSearchLocation", region,
+                                    ncoords, coords );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new StcSearchLocation's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new StcSearchLocation. */
+   return new;
+}
+
+AstStcSearchLocation *astStcSearchLocationId_( void *region_void, int ncoords,
+                               AstKeyMap **coords, const char *options, ... ) {
+/*
+*  Name:
+*     astStcSearchLocationId_
+
+*  Purpose:
+*     Create a StcSearchLocation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "stcsearchlocation.h"
+*     AstStcSearchLocation *astStcSearchLocationId_( AstRegion *region,
+*                  int ncoords, AstKeyMap *coords[], const char *options, ... )
+
+*  Class Membership:
+*     StcSearchLocation constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astStcSearchLocation constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astStcSearchLocation_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astStcSearchLocation_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astStcSearchLocation_.
+
+*  Returned Value:
+*     The ID value associated with the new StcSearchLocation.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstKeyMap **keymaps;            /* Pointer to array of KeyMap pointers */
+   AstRegion *region;              /* Pointer to Region structure */
+   AstStcSearchLocation *new;      /* Pointer to new StcSearchLocation */
+   int icoord;                     /* Keymap index */
+   va_list args;                   /* Variable argument list */
+   int *status;                    /* Pointer to inherited status value */
+
+   /* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a Region pointer from the supplied ID and validate the
+   pointer to ensure it identifies a valid Region. */
+   region = astVerifyRegion( astMakePointer( region_void ) );
+
+/* Obtain pointer from the supplied KeyMap ID's and validate the
+   pointers to ensure it identifies a valid KeyMap. */
+   keymaps = astMalloc( sizeof( AstKeyMap * )*(size_t) ncoords );
+   if( keymaps ) {
+      for( icoord = 0; icoord < ncoords; icoord++ ) {
+         keymaps[ icoord ] = astVerifyKeyMap( astMakePointer( coords[ icoord ] ) );
+      }
+   }
+
+/* Initialise the StcSearchLocation, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitStcSearchLocation( NULL, sizeof( AstStcSearchLocation ), !class_init,
+                                   &class_vtab, "StcSearchLocation", region,
+                                   ncoords, keymaps );
+
+/* Free resources. */
+   keymaps = astFree( keymaps );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new StcSearchLocation's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new StcSearchLocation. */
+   return astMakeId( new );
+}
+
+AstStcSearchLocation *astInitStcSearchLocation_( void *mem, size_t size,
+                                    int init, AstStcSearchLocationVtab *vtab,
+                                    const char *name, AstRegion *region,
+                                    int ncoords, AstKeyMap **coords, int *status ) {
+/*
+*+
+*  Name:
+*     astInitStcSearchLocation
+
+*  Purpose:
+*     Initialise a StcSearchLocation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcsearchlocation.h"
+*     AstStcSearchLocation *astInitStcSearchLocation_( void *mem, size_t size,
+*                                    int init, AstStcSearchLocationVtab *vtab,
+*                                    const char *name, AstRegion *region,
+*                                    int ncoords, AstKeyMap **coords )
+
+*  Class Membership:
+*     StcSearchLocation initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new StcSearchLocation object. It allocates memory (if necessary) to accommodate
+*     the StcSearchLocation plus any additional data associated with the derived class.
+*     It then initialises a StcSearchLocation structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a StcSearchLocation at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the StcSearchLocation is to be initialised.
+*        This must be of sufficient size to accommodate the StcSearchLocation data
+*        (sizeof(StcSearchLocation)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the StcSearchLocation (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the StcSearchLocation
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the StcSearchLocation's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new StcSearchLocation.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     region
+*        A pointer to the Region encapsulated by the StcSearchLocation.
+*     ncoords
+*        Number of KeyMap pointers supplied in "coords". Can be zero.
+*        Ignored if "coords" is NULL.
+*     coords
+*        Pointer to an array of "ncoords" KeyMap pointers, or NULL if
+*        "ncoords" is zero. Each KeyMap defines defines a single <AstroCoords>
+*        element, and should have elements with keys given by constants
+*        AST__STCNAME, AST__STCVALUE, AST__STCERROR, AST__STCRES, AST__STCSIZE,
+*        AST__STCPIXSZ. These elements hold values for the corresponding
+*        components of the STC AstroCoords element. Any of these elements may
+*        be omitted, but no other elements should be included. All supplied
+*        elements should be vector elements, with vector length less than or
+*        equal to the number of axes in the supplied Region. The data type of
+*        all elements should be "double", except for AST__STCNAME which should
+*        be "character string". If no value is available for a given axis, then
+*        AST__BAD (or NULL for the AST__STCNAME element) should be stored in
+*        the vector at the index corresponding to the axis (trailing axes
+*        can be omitted completely from the KeyMap).
+
+*  Returned Value:
+*     A pointer to the new StcSearchLocation.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstStcSearchLocation *new;       /* Pointer to new StcSearchLocation */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitStcSearchLocationVtab( vtab, name );
+
+/* Initialise a Stc structure (the parent class) as the first component
+   within the StcSearchLocation structure, allocating memory if necessary. */
+   new = (AstStcSearchLocation *) astInitStc( mem, size, 0, (AstStcVtab *) vtab,
+                                              name, region, ncoords, coords );
+
+/* If an error occurred, clean up by deleting the new StcSearchLocation. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return a pointer to the new StcSearchLocation. */
+   return new;
+}
+
+AstStcSearchLocation *astLoadStcSearchLocation_( void *mem, size_t size, AstStcSearchLocationVtab *vtab,
+                                                 const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadStcSearchLocation
+
+*  Purpose:
+*     Load a StcSearchLocation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "stcsearchlocation.h"
+*     AstStcSearchLocation *astLoadStcSearchLocation( void *mem, size_t size, AstStcSearchLocationVtab *vtab,
+*                                       const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     StcSearchLocation loader.
+
+*  Description:
+*     This function is provided to load a new StcSearchLocation using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     StcSearchLocation structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a StcSearchLocation at the start of the memory
+*     passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the StcSearchLocation is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        StcSearchLocation data (sizeof(StcSearchLocation)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the StcSearchLocation (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the StcSearchLocation structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstStcSearchLocation) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new StcSearchLocation. If this is NULL, a pointer
+*        to the (static) virtual function table for the StcSearchLocation class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "StcSearchLocation" is used instead.
+
+*  Returned Value:
+*     A pointer to the new StcSearchLocation.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstStcSearchLocation *new;              /* Pointer to the new StcSearchLocation */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this StcSearchLocation. In this case the
+   StcSearchLocation belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstStcSearchLocation );
+      vtab = &class_vtab;
+      name = "StcSearchLocation";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitStcSearchLocationVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built StcSearchLocation. */
+   new = astLoadStc( mem, size, (AstStcVtab *) vtab, name, channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "StcSearchLocation" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* There are no values to read. */
+/* ---------------------------- */
+
+/* If an error occurred, clean up by deleting the new StcSearchLocation. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new StcSearchLocation pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
+
diff --git a/ast/stcsearchlocation.h b/ast/stcsearchlocation.h
new file mode 100644
index 0000000..c359711
--- /dev/null
+++ b/ast/stcsearchlocation.h
@@ -0,0 +1,222 @@
+#if !defined( STCSEARCHLOCATION_INCLUDED ) /* Include this file only once */
+#define STCSEARCHLOCATION_INCLUDED
+/*
+*+
+*  Name:
+*     stcsearchlocation.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the StcSearchLocation class.
+
+*  Invocation:
+*     #include "stcsearchlocation.h"
+
+*  Description:
+*     This include file defines the interface to the StcSearchLocation class
+*     and provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The StcSearchLocation class is a sub-class of Stc used to describe
+*     the coverage of a VO query.
+*
+*     See http://hea-www.harvard.edu/~arots/nvometa/STC.html
+
+*  Inheritance:
+*     The StcSearchLocation class inherits from the Stc class.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     26-NOV-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "stc.h"                 /* Coordinate stcs (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* StcSearchLocation structure. */
+/* ----------------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstStcSearchLocation {
+
+/* Attributes inherited from the parent class. */
+   AstStc stc;             /* Parent class structure */
+
+} AstStcSearchLocation;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstStcSearchLocationVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstStcVtab stc_vtab;          /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+} AstStcSearchLocationVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstStcSearchLocationGlobals {
+   AstStcSearchLocationVtab Class_Vtab;
+   int Class_Init;
+} AstStcSearchLocationGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitStcSearchLocationGlobals_( AstStcSearchLocationGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(StcSearchLocation)          /* Check class membership */
+astPROTO_ISA(StcSearchLocation)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstStcSearchLocation *astStcSearchLocation_(  void *, int, AstKeyMap **, const char *, int *, ...);
+#else
+AstStcSearchLocation *astStcSearchLocationId_(  void *, int, AstKeyMap **, const char *, ... )__attribute__((format(printf,4,5)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstStcSearchLocation *astInitStcSearchLocation_( void *, size_t, int, AstStcSearchLocationVtab *, const char *, AstRegion *, int, AstKeyMap **, int * );
+
+/* Vtab initialiser. */
+void astInitStcSearchLocationVtab_( AstStcSearchLocationVtab *, const char *, int * );
+
+/* Loader. */
+AstStcSearchLocation *astLoadStcSearchLocation_( void *, size_t, AstStcSearchLocationVtab *, const char *, AstChannel *, int * );
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckStcSearchLocation(this) astINVOKE_CHECK(StcSearchLocation,this,0)
+#define astVerifyStcSearchLocation(this) astINVOKE_CHECK(StcSearchLocation,this,1)
+
+/* Test class membership. */
+#define astIsAStcSearchLocation(this) astINVOKE_ISA(StcSearchLocation,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astStcSearchLocation astINVOKE(F,astStcSearchLocation_)
+#else
+#define astStcSearchLocation astINVOKE(F,astStcSearchLocationId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitStcSearchLocation(mem,size,init,vtab,name,region,ncoords,coords) \
+astINVOKE(O,astInitStcSearchLocation_(mem,size,init,vtab,name,region,ncoords,coords,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitStcSearchLocationVtab(vtab,name) astINVOKE(V,astInitStcSearchLocationVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadStcSearchLocation(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadStcSearchLocation_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckStcSearchLocation to validate StcSearchLocation pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/sun210.htx_tar b/ast/sun210.htx_tar
new file mode 100644
index 0000000..d3acd36
Binary files /dev/null and b/ast/sun210.htx_tar differ
diff --git a/ast/sun210.pdf b/ast/sun210.pdf
new file mode 100644
index 0000000..ea4d49c
Binary files /dev/null and b/ast/sun210.pdf differ
diff --git a/ast/sun210.tex b/ast/sun210.tex
new file mode 100644
index 0000000..6c62c09
--- /dev/null
+++ b/ast/sun210.tex
@@ -0,0 +1,50985 @@
+\documentclass[twoside,11pt]{starlink}
+
+% ? Specify used packages
+% ? End of specify used packages
+
+% -----------------------------------------------------------------------------
+% ? Document identification
+% Fixed part
+\stardoccategory    {Starlink User Note}
+\stardocinitials    {SUN}
+\stardocsource      {sun\stardocnumber}
+
+% Variable part - replace [xxx] as appropriate.
+\stardocnumber      {210.27}
+\stardocauthors     {R.F. Warren-Smith \& D.S. Berry}
+\stardocdate        {26th October 2016}
+\stardoctitle     {AST\linebreak%
+                                A Library for Handling\linebreak%
+                                World Coordinate Systems\linebreak%
+                                in Astronomy}
+\stardoccopyright   {Copyright (C) 2014 Science \& Technology Facilities Council}
+\stardocversion     {V8.4}
+\stardocmanual      {Programmer's Guide\\(Fortran Version)}
+\startitlepic{
+   \includegraphics[width=0.25\textwidth]{sun210_figures/fronta}~~~~~\hfill
+   \includegraphics[width=0.25\textwidth]{sun210_figures/frontb}~~~~~\hfill
+   \includegraphics[width=0.25\textwidth]{sun210_figures/frontc}
+}
+\stardocabstract  {
+The AST library provides a comprehensive range of facilities for
+attaching world coordinate systems to astronomical data, for
+retrieving and interpreting that information in a variety of formats,
+including FITS-WCS, and for generating graphical output based on it.
+
+This programmer's manual should be of interest to anyone writing
+astronomical applications which need to manipulate coordinate system
+data, especially celestial or spectral coordinate systems. AST is portable and
+environment-independent.
+}
+% ? End of document identification
+% -----------------------------------------------------------------------------
+% ? Document specific \providecommand or \newenvironment commands.
+
+\providecommand{\appref}[1]{Appendix~\ref{#1}}
+\providecommand{\secref}[1]{\S\ref{#1}}
+
+\providecommand{\fitskey}[3]{{#1}&{#2}&{#3}\\}
+
+% Use {\tt ... } as \texttt{...} does not work if there are new lines in #1
+\providecommand{\sstsynopsis}[1]{\sstdiytopic{Synopsis}{\tt #1}}
+
+%  Format the constructor section.
+\providecommand{\sstconstructor}[1]{\sstdiytopic{Constructor Function}{#1}}
+
+% ? End of document specific commands
+% -----------------------------------------------------------------------------
+%  \htmlref{Title}{Title} Page.
+%  ===========
+\begin{document}
+\scfrontmatter
+
+\begin{center}
+\emph{This is the Fortran version of this document.\\
+        For the C version, please see \xref{SUN/211}{sun211}{}.}
+\end{center}
+
+% Main text of document.
+\vspace{7mm}
+\section{Introduction}
+
+Welcome to the AST library. If you are writing software for astronomy
+and need to use celestial coordinates (\emph{e.g.}\ RA and Dec), spectral
+coordinates (\emph{e.g.}\ wavelength, frequency, \emph{etc.}), or
+other coordinate system information, then this library should be of
+interest. It provides solutions for most of the problems you will meet
+and allows you to write robust and flexible software. It is able to read
+and write WCS information in a variety of formats, including
+\htmladdnormallink{FITS-WCS}{http://fits.gsfc.nasa.gov/fits_wcs.html}.
+
+%\subsection{TBW---What is a World Coordinate \htmlref{System}{System}?}
+
+\subsection{What Problems Does AST Tackle?}
+
+Here are some of the main problems you may face when handling world
+coordinate system (WCS) information and the solutions that AST
+provides:
+
+\begin{description}
+\item[1. The Variety of Coordinate Systems]\mbox{}\\
+Astronomers use a wide range of differing coordinate systems to describe
+positions within a variety of physical domains. For instance, there are a
+large number of celestial coordinate systems in use within astronomy to
+describe positions on the sky. Understanding these, and knowing how to
+convert coordinates between them, can require considerable expertise. It
+can also be difficult to decide which of them your software should support.
+The same applies to coordinate systems describing other domains, such as
+position within an electro-magnetic spectrum.
+
+\textbf{Solution.} AST has built-in knowledge of many coordinate systems
+and allows you to convert freely between them without specialist
+knowledge. This avoids the need to embed details of specific
+coordinate systems in your software. You also benefit automatically
+when new coordinate systems are added to AST.
+
+\item[2. Storing and Retrieving WCS Information]\mbox{}\\
+Storing coordinate system information in astronomical datasets and
+retrieving it later can present a considerable challenge. Typically,
+it requires knowledge of rather complex conventions
+(\emph{e.g.}\ FITS) which are low-level, often mis-interpreted and may
+be subject to change. Exchanging information with other software
+systems is further complicated by the number of different conventions
+in use.
+
+\textbf{Solution.} AST combines a unifying high-level description of WCS
+information with the ability to save and restore this using a variety
+of formats. Details of the formats, which include FITS, are handled
+internally by AST. This frees you from the need to understand them or
+embed the details in your software. Again, you benefit automatically
+when new formats are added to AST.
+
+\item[3. Generating Graphical Output]\mbox{}\\
+Producing graphical displays involving curvilinear coordinate systems,
+such as celestial coordinate grids, can be complicated. Particular
+difficulties arise when handling large areas of sky, the polar regions
+and discontinuous (\emph{e.g.}\ segmented) sky projections.  Even just
+numbering and labelling curvilinear axes is rarely straightforward.
+
+\textbf{Solution.} AST provides plotting facilities especially designed
+for use with curvilinear coordinate systems. These include the
+plotting of axes and complete labelled coordinate grids.  A large
+number of options are provided for tailoring the output to your
+specific needs. Three dimensional coordinate grids can also be produced.
+
+\item[4. Aligning Data from Different Sources]\mbox{}\\
+One of the main uses of coordinate systems is to facilitate the
+inter-comparison of data from different sources. A typical use might
+be to plot (say) radio contours over an optical image.  In practice,
+however, different celestial coordinate systems may have been used,
+making accurate alignment far from simple.
+
+\textbf{Solution} AST provides a one-step method of aligning datasets,
+searching for all possible intermediate coordinate systems.  This
+makes it simple to directly inter-relate the pixel coordinates of
+different datasets.
+
+\item[5. Handling Different Types of Coordinate \htmlref{System}{System}]\mbox{}\\
+Not all coordinate systems used in astronomy are celestial ones, so if
+you are writing general-purpose software such as (say) a display tool,
+you may also need to handle axes representing wavelength, distance,
+time or whatever else comes along. Obviously, you would prefer not to
+handle each one as a special case.
+
+\textbf{Solution} AST uses the same flexible high-level model to
+describe all types of coordinate system. This allows you to write
+software that handles different kinds of coordinate axis without
+introducing special cases.
+\end{description}
+
+\subsection{Other Design Objectives}
+
+As well as its scientific objectives, the AST library's design
+includes a number of technical criteria intended to make it applicable
+to as wide a range of projects as possible. The main considerations
+are described here:
+
+\begin{enumerate}
+\item {\bf{Minimum Software Dependencies.}}
+The AST library depends on no other other software\footnote{It comes with
+bundled copies of the ERFA and
+\xref{Starlink PAL libraries}{sun268}{} which are built
+at the same time as the other AST internal libraries. Alternatively, external
+PAL and ERFA libraries may be used by specifying the ``\texttt{--with-external\_pal}'' option when configuring AST}.
+
+\item {\bf{Environment Independence.}}
+AST is designed so that it can operate in a variety of ``programming
+environments'' and is not tied to any particular one. To allow this,
+it uses simple, flexible interfaces to obtain the following services:
+
+\begin{itemize}
+\item {\bf{Data Storage.}} Data I/O operations are based on text
+and/or FITS headers. This makes it easy to interface to a wide variety
+of astronomical data formats in a machine-independent way.
+
+\item {\bf{Graphics.}} Graphical output is produced \emph{via} a
+simple generic graphics interface, which may easily be re-implemented
+over different graphics systems. AST provides a default implementation
+based on the widely-used PGPLOT graphics system
+(\xref{SUN/15}{sun15}{}).
+
+\item {\bf{Error Handling.}} Error messages are written to standard
+error by default, but go through a simple generic interface similar to
+that used for graphics (above). This permits error message delivery
+\emph{via} other routes when necessary (\emph{e.g.} in a graphical
+interface).
+\end{itemize}
+
+\item {\bf{Multiple Language Support.}}
+AST has been designed to be called from more than one language.
+Both Fortran and C interfaces are available (see
+\xref{SUN/211}{sun211}{} for the C version)
+and use from C$++$ is also straightforward if the C interface is
+included using:
+
+\begin{small}
+\begin{terminalv}
+extern "C" {
+#include "ast.h"
+}
+\end{terminalv}
+\end{small}
+
+A JNI interface (known as ``JNIAST'' - see
+\url{http://www.starlink.ac.uk/jniast/}) has also been developed by Starlink
+which allows AST to be used from Java.
+
+\item {\bf{\htmlref{Object}{Object} Oriented Design.}}
+AST uses ``object oriented'' techniques internally in order to provide
+a flexible and easily-extended programming model.  A fairly
+traditional calling interface is provided, however, so that the
+library's facilities are easily accessible to programmers using
+Fortran and C.
+
+\item {\bf{Portability.}}
+AST is implemented entirely in ANSI standard C and, when called
+\emph{via} its C interface, makes no explicit use of any
+machine-dependent facilities.
+
+The Fortran interface is, unavoidably, machine dependent. However, the
+potential for problems has been minimised by encapsulating the
+interface layer in a compact set of C macros which facilitate its
+transfer to other platforms. No Fortran compiler is needed to build
+the library.
+
+Currently, AST is supported by Starlink on PC~Linux, Sun~Solaris and
+Tru64~Unix (formerly DEC~UNIX) platforms.
+\end{enumerate}
+
+\subsection{What Does ``AST'' Stand For?}
+
+The library name ``AST'' stands for ``ASTrometry Library''. The name
+arose when it was thought that knowledge of ``astrometry''
+(\emph{i.e.}\ celestial coordinate systems) would form the bulk of the
+library.  In fact, it turns out that astrometry forms only a minor
+component, but the name AST has stuck.
+
+\cleardoublepage
+\section{Overview of AST Concepts}
+
+This section presents a brief overview of AST concepts. It is intended
+as a basic orientation course before you move on to the more technical
+considerations in subsequent sections.
+
+\subsection{\label{ss:mappingoverview}Relationships Between Coordinate Systems}
+
+The relationships between coordinate systems are represented in AST by
+Objects called Mappings. A \htmlref{Mapping}{Mapping} does not represent a coordinate
+system itself, but merely the process by which you move from one
+coordinate system to another related one.
+
+   A convenient picture of a Mapping is as a ``black box''
+   (Figure~\ref{fig:mapping}) into which you can feed sets of
+   coordinates.
+   \begin{figure}[bhtp]
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun210_figures/mapping}
+   \caption{A Mapping viewed as a ``black box'' for transforming coordinates.}
+   \label{fig:mapping}
+   \end{center}
+   \end{figure}
+
+For each set you feed in, the Mapping returns a corresponding set of
+transformed coordinates. Since each set of coordinates represents a
+point in a coordinate space, the Mapping acts to inter-relate
+corresponding positions in the two spaces, although what these spaces
+represent is unspecified.  Notice that a Mapping need not have the
+same number of input and output coordinates. That is, the two
+coordinate spaces which it inter-relates need not have the same number
+of dimensions.
+
+In many cases, the transformation can, in principle, be performed in
+either direction: either from the \emph{input} coordinate space to the
+\emph{output}, or \emph{vice versa}. The first of these is termed the
+\emph{forward} transformation and the other the \emph{inverse}
+transformation.
+
+
+\textbf{Further reading:} For a more complete discussion of Mappings,
+see~\secref{ss:mappings}.
+
+\subsection{\label{ss:mappingselection}Mappings Available}
+
+The basic concept of a \htmlref{Mapping}{Mapping} (\secref{ss:mappingoverview}) is rather
+generic and obviously it is necessary to have specific Mappings that
+implement specific relationships between coordinate systems. AST
+provides a range of these, to perform transformations such as the
+following and, where appropriate, their inverses:
+
+\begin{itemize}
+\item Conversions between various celestial coordinate systems (the
+\htmlref{SlaMap}{SlaMap}).
+
+\item Conversions between various spectral coordinate systems (the
+\htmlref{SpecMap}{SpecMap} and \htmlref{GrismMap}{GrismMap}).
+
+\item Conversions between various time systems (the \htmlref{TimeMap}{TimeMap}).
+
+\item Conversion between 2-dimensional spherical celestial coordinates
+(longitude and latitude) and a 3-dimensional vectorial positions (the \htmlref{SphMap}{SphMap}).
+
+\item Various projections of the celestial sphere on to 2-dimensional
+coordinate spaces---\emph{i.e.}\ map projections (the \htmlref{DssMap}{DssMap} and \htmlref{WcsMap}{WcsMap}).
+
+\item Permutation, introduction and elimination of coordinates (the
+\htmlref{PermMap}{PermMap}).
+
+\item Various linear coordinate transformations (the \htmlref{MatrixMap}{MatrixMap}, \htmlref{WinMap}{WinMap},
+\htmlref{ShiftMap}{ShiftMap} and \htmlref{ZoomMap}{ZoomMap}).
+
+\item General N-dimensional polynomial transformations (the \htmlref{PolyMap}{PolyMap}).
+
+\item Lookup tables (the \htmlref{LutMap}{LutMap}).
+
+\item General-purpose transformations expressed using arithmetic
+operations and functions similar to those available in Fortran (the
+\htmlref{MathMap}{MathMap}).
+
+\item Transformations for internal use within a program, based on
+private transformation routines which you write yourself in Fortran
+(the \htmlref{IntraMap}{IntraMap}).
+\end{itemize}
+
+\textbf{Further reading:} For a more complete description of each of the
+Mappings mentioned above, see its entry in
+\appref{ss:classdescriptions}. In addition, see the discussion of the
+PermMap in \secref{ss:permmapexample}, the \htmlref{UnitMap}{UnitMap} in
+\secref{ss:unitmapexample} and the IntraMap in
+\secref{ss:intramaps}. The ZoomMap is used as an example throughout
+\secref{ss:primer}.
+
+\subsection{\label{ss:cmpmapoverview}Compound Mappings}
+
+The Mappings described in \secref{ss:mappingselection} provide a set
+of basic building blocks from which more complex Mappings may be
+constructed. The key to doing this is a type of \htmlref{Mapping}{Mapping} called a
+\htmlref{CmpMap}{CmpMap}, or compound Mapping.  A CmpMap's role is, in principle, very
+simple: it allows any other pair of Mappings to be joined together
+into a single entity which behaves as if it were a single Mapping. A
+CmpMap is therefore a container for another pair of Mappings.
+
+   A pair of Mappings may be combined using a CmpMap in either of two
+   ways. The first of these, \emph{in series}, is illustrated in
+   Figure~\ref{fig:seriescmpmap}.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun210_figures/series}
+   \caption[A CmpMap composed of two component Mappings joined in series]{A CmpMap (compound Mapping) composed of two component
+   Mappings joined in series. The output coordinates of the first Mapping
+   feed into the input coordinates of the second one, so that the whole
+   entity behaves like a single Mapping.}
+   \label{fig:seriescmpmap}
+   \end{center}
+   \end{figure}
+
+
+   Here, the transformations implemented by each component Mapping are
+   performed one after the other, with the output from the first Mapping
+   feeding into the second.  The second way, \emph{in parallel}, is shown in
+   Figure~\ref{fig:parallelcmpmap}.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.5\textwidth]{sun210_figures/parallel}
+   \caption[A CmpMap composed of two Mappings joined in parallel.]{A CmpMap composed of two Mappings joined in parallel. Each
+   component Mapping acts on a complementary subset of the input and
+   output coordinates.}
+   \label{fig:parallelcmpmap}
+   \end{center}
+   \end{figure}
+
+In this case, each Mapping acts on a complementary subset of the
+input and output coordinates.\footnote{A pair of Mappings can be combined
+in a third way using a \htmlref{TranMap}{TranMap}. A TranMap allows the forward
+transformation of one Mapping to be combined with the inverse
+transformation of another to produce a single Mapping.}
+
+   The CmpMap forms the key to building arbitrarily complex Mappings
+   because it is itself a form of Mapping. This means that a CmpMap may
+   contain other CmpMaps as components
+   (\emph{e.g.}\ Figure~\ref{fig:complexcmpmap}). This nesting of CmpMaps
+   can be repeated indefinitely, so that complex Mappings may be built in
+   a hierarchical manner out of simper ones.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.65\textwidth]{sun210_figures/complex}
+   \caption[CmpMaps may be nested in order to
+   construct complex Mappings out of simpler building blocks.]{CmpMaps
+   (compound Mappings) may be nested in order to
+   construct complex Mappings out of simpler building blocks.}
+   \label{fig:complexcmpmap}
+   \end{center}
+   \end{figure}
+   This gives AST great flexibility in the coordinate transformations it
+   can describe.
+
+\textbf{Further reading:} For a more complete description of CmpMaps,
+see \secref{ss:cmpmaps}. Also see the CmpMap entry in
+\appref{ss:classdescriptions}.
+
+\subsection{Representing Coordinate Systems}
+
+   While Mappings (\secref{ss:mappingoverview}) represent the
+   relationships between coordinate systems in AST, the coordinate
+   systems themselves are represented by Objects called Frames
+   (Figure~\ref{fig:frames}).
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.55\textwidth]{sun210_figures/frames}
+   \caption[Representing coordinate systems as Frames.]{(a) A basic Frame is used to represent a Cartesian coordinate
+   system, here 2-dimensional. (b) A \htmlref{SkyFrame}{SkyFrame} represents a (spherical)
+   celestial coordinate system. (c) The axis order of any \htmlref{Frame}{Frame} may be
+   permuted to match the coordinate space it describes.}
+   \label{fig:frames}
+   \end{center}
+   \end{figure}
+
+A Frame is similar in concept to the frame you might draw around a
+graph.  It contains information about the labels which appear on the
+axes, the axis units, a title, knowledge of how to format the
+coordinate values on each axis, \emph{etc.}  An AST Frame is not,
+however, restricted to two dimensions and may have any number of axes.
+
+A basic Frame may be used to represent a Cartesian coordinate system
+by setting values for its \emph{attributes} (all AST Objects have
+values associated with them called attributes, which may be set and
+enquired).  Usually, this would involve setting appropriate axis
+labels and units, for example.  Routines are provided for use with
+Frames to perform operations such as formatting coordinate values as
+text, calculating distances between points, interchanging axes,
+\emph{etc.}
+
+There are several more specialised forms of Frame, which provide the
+additional functionality required when handling coordinates within some
+specific physical domain. This ranges from tasks such as formatting axis
+values, to complex tasks such as determining the transformation between
+any pair of related coordinate systems. For instance, the SkyFrame
+(Figure~\ref{fig:frames}b,c), represents celestial coordinate systems,
+the \htmlref{SpecFrame}{SpecFrame} represents spectral coordinate systems, and the \htmlref{TimeFrame}{TimeFrame}
+represents time coordinate systems. All these provide a wide range of
+different systems for describing positions within their associated physical
+domain, and these may be selected by setting appropriate attributes.
+
+   As with compound Mappings (\secref{ss:cmpmapoverview}), it is possible
+   to merge two Frames together to form a compound Frame, or \htmlref{CmpFrame}{CmpFrame}, in
+   which both sets of axes are combined.  One could, for example, have
+   celestial coordinates on two axes and an unrelated coordinate
+   (wavelength, perhaps) on a third (Figure~\ref{fig:cmpframe}).
+   Knowledge of the relationships between the axes is preserved
+   internally by the process of constructing the CmpFrame which
+   represents them.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.4\textwidth]{sun210_figures/cmpframe}
+   \caption[A CmpFrame (compound Frame) formed by combining two simpler
+   Frames.]{A CmpFrame (compound Frame) formed by combining two simpler
+   Frames. Note how the special relationship which exists between the RA
+   and Dec axes is preserved within this data structure. As with compound
+   Mappings (Figure~\ref{fig:complexcmpmap}), CmpFrames may be nested in
+   order to build more complex Frames.}
+   \label{fig:cmpframe}
+   \end{center}
+   \end{figure}
+
+\textbf{Further reading:} For a more complete description of Frames see
+\secref{ss:frames}, for SkyFrames see \secref{ss:skyframes} and for
+SpecFrames see \secref{ss:specframes}.  Also see the Frame, SkyFrame,
+SpecFrame, TimeFrame and CmpFrame entries in \appref{ss:classdescriptions}.
+
+\subsection{Networks of Coordinate Systems}
+
+   Mappings and Frames may be connected together to form networks called
+   FrameSets, which are used to represent sets of inter-related
+   coordinate systems (Figure~\ref{fig:frameset}).
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.65\textwidth]{sun210_figures/frameset}
+   \caption[A FrameSet is a network of Frames.]{A FrameSet is a network of Frames inter-connected by Mappings
+   such that there is exactly one conversion path, \emph{via} Mappings,
+   between any pair of Frames.}
+   \label{fig:frameset}
+   \end{center}
+   \end{figure}
+
+
+A \htmlref{FrameSet}{FrameSet} may be extended by adding a new \htmlref{Frame}{Frame} to it, together with
+an associated \htmlref{Mapping}{Mapping} which relates the new coordinate system to one
+which is already present.  This process ensures that there is always
+exactly one path, \emph{via} Mappings, between any pair of Frames.  A
+function is provided for identifying this path and returning the
+complete Mapping.
+
+One of the Frames in a FrameSet is termed its \emph{base} Frame.  This
+underlies the FrameSet's purpose, which is to calibrate datasets and
+other entities by attaching coordinate systems to them.  In this
+context, the base Frame represents the ``native'' coordinate system
+(for example, the pixel coordinates of an image).  Similarly, one
+Frame is termed the \emph{current} Frame and represents the
+``currently-selected'' coordinates.  It might, typically, be a
+celestial or spectral coordinate system and would be used during
+interactions with
+a user, as when plotting axes on a graph or producing a table of
+results.  Other Frames within the FrameSet represent a library of
+alternative coordinate systems which a software user can select by
+making them current.
+
+\textbf{Further reading:} For a more complete description of
+FrameSets, see \secref{ss:framesets} and \secref{ss:fshigher}. Also
+see the FrameSet entry in \appref{ss:classdescriptions}.
+
+\subsection{Input/Output Facilities}
+
+AST allows you to convert any kind of \htmlref{Object}{Object} into a stream of text
+which contains a full description of that Object. This text may be
+written out by one program and read back in by another, thus allowing
+the original Object to be reconstructed.
+
+The filter which converts Objects into text and back again is itself a
+kind of Object, called a \htmlref{Channel}{Channel}. A Channel provides a number of
+options for controlling the information content of the text, such as
+the addition of comments for human interpretation.  It is also
+possible to intercept the text being processed by a Channel so that it
+may be redirected to/from any chosen external data store, such as a
+text file, an astronomical dataset, or a network connection.
+
+The text format used by the basic Channel class is peculiar to the AST
+library - no other software will understand it. However, more specialised
+forms of Channel are provided which use text formats more widely
+understood.
+
+To further facilitate the storage of coordinate system information in
+astronomical datasets, a more specialised form of Channel called a
+\htmlref{FitsChan}{FitsChan} is provided. Instead of using free-format text, a FitsChan
+converts AST Objects to and from FITS header cards. It also allows the
+information to be encoded in the FITS cards in a number of ways
+(called \emph{encodings}), so that WCS information from a variety of
+sources can be handled.
+
+Another sub-class of Channel, called \htmlref{XmlChan}{XmlChan}, is a specialised form of
+Channel that stores the text in the form of XML markup. Currently, two
+markup formats are provided by the XmlChan class, one is closely related
+to the text format produced by the basic Channel class (currently, no
+schema or DTD is available describing this format). The other is a subset
+of an early draft of the IVOA Space-Time-Coordinates XML (STC-X) schema
+(V1.20) described at
+\url{http://www.ivoa.net/Documents/WD/STC/STC-20050225.html
+}\footnote{XML documents which use only the subset of the STC schema
+supported by AST can be read by the XmlChan class to produce
+corresponding AST objects (subclasses of the \htmlref{Stc}{Stc} class). However, the
+reverse is not possible. That is, AST objects can not currently be
+written out in the form of STC documents.}. The version of STC-X that has
+been adopted by the IVOA differs in several significant respects from
+V1.20, and therefore this XmlChan format is of historical interest only.
+
+Finally, the \htmlref{StcsChan}{StcsChan} class provides facilities for reading and writing
+IVOA STC-S region descriptions. STC-S (see
+\url{http://www.ivoa.net/Documents/latest/STC-S.html}) is a linear string
+syntax that allows simple specification of STC metadata. AST supports a
+subset of the STC-S specification, allowing an STC-S description of a
+region within an AST-supported astronomical coordinate system to be converted
+into an equivalent AST \htmlref{Region}{Region} object, and vice-versa.
+
+\textbf{Further reading:} For a more complete description of Channels
+see \secref{ss:channels} and for FitsChans see \secref{ss:nativefits}
+and \secref{ss:foreignfits}. Also see the Channel and FitsChan entries
+in \appref{ss:classdescriptions} and the \htmlref{Encoding}{Encoding} entry in
+\appref{ss:attributedescriptions}.
+
+\subsection{Producing Graphical Output}
+
+Two dimensional graphical output is supported by a specialised form of
+\htmlref{FrameSet}{FrameSet} called
+a \htmlref{Plot}{Plot}, whose base \htmlref{Frame}{Frame} corresponds with the native coordinates of
+the underlying graphics system.  Plotting operations are specified in
+\emph{physical coordinates} which correspond with the Plot's current
+Frame. Typically, this might be a celestial coordinate system.
+
+Three dimensional plotting is also supported, via the \htmlref{Plot3D}{Plot3D} class -
+sub-class of Plot.
+
+Operations, such as drawing lines, are automatically transformed from
+physical to graphical coordinates before plotting, using an adaptive
+algorithm which ensures smooth curves (because the transformation is
+usually non-linear).  ``Missing'' coordinates (\emph{e.g.}\ graphical
+coordinates which do not project on to the celestial sphere),
+discontinuities and generalised clipping are all consistently handled.
+It is possible, for example, to plot in equatorial coordinates and
+clip in galactic coordinates.  The usual plotting operations are
+provided (text, markers), but a geodesic curve replaces the primitive
+straight line element.  There is also a separate function for drawing
+axis lines, since these are normally not geodesics.
+
+In addition to drawing coordinate grids over an area of the sky, another
+common use of the Plot class is to produce line plots such as flux
+against wavelength, displacement again time, \emph{etc}. For these
+situations the current Frame of the Plot would be a compound Frame
+(\htmlref{CmpFrame}{CmpFrame}) containing a pair of 1-dimensional Frames - the first
+representing the X axis quantity (wavelength, time, etc), and the second
+representing the Y axis quantity (flux, displacement, etc). The Plot
+class includes an option for axes to be plotted logarithmically.
+
+   Perhaps the most useful graphics function available is for drawing
+   fully annotated coordinate grids (\emph{e.g.}\ Figure~\ref{fig:gridplot}).
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.6\textwidth]{sun210_figures/gridplot_bw}
+   \caption[A labelled coordinate grid for an all-sky zenithal equal area
+   projection in ecliptic coordinates.]{A labelled coordinate grid for an all-sky zenithal equal area
+   projection in ecliptic coordinates. This was composed and drawn
+   \emph{via} a Plot using a
+   single subroutine call.}
+   \label{fig:gridplot}
+   \end{center}
+   \end{figure}
+
+This uses a general algorithm which does not depend on knowledge of
+the coordinates being represented, so can also handle
+programmer-defined coordinate systems.  Grids for all-sky projections,
+including polar regions, can be drawn and most aspects of the output
+(colour, line style, \emph{etc.}) can be adjusted by setting
+appropriate Plot attributes.
+
+\textbf{Further reading:} For a more complete description of
+Plots and how to produce graphical output, see \secref{ss:plots}. Also
+see the Plot entry in \appref{ss:classdescriptions}.
+
+\cleardoublepage
+\section{\label{ss:howto}How To\ldots}
+
+For those of you with a plane to catch, this section provides some
+instant templates and recipes for performing the most
+commonly-required operations using AST, but without going into
+detail. The examples given (sort of) follow on from each other, so you
+should be able to construct a variety of programs by piecing them
+together.  Note that some of them appear longer than they actually
+are, because we have included plenty of comments and a few options
+that you probably won't need.
+
+If any of this material has you completely baffled, then you may want
+to read the introduction to AST programming concepts in
+\secref{ss:primer} first. Otherwise, references to more detailed
+reading are given after each example, just in case they don't quite do
+what you want.
+
+\subsection{\ldots Obtain and Install AST}
+The AST library is available both as a stand-alone package and also as
+part of the Starlink Software Collection\footnote{The Starlink Software
+Collection can be downloaded from
+\url{http://www.starlink.ac.uk/Download/}.}. If your site has the Starlink
+Software Collection installed then AST should already be available.
+
+If not, you can download the AST library by itself from
+\url{http://www.starlink.ac.uk/ast/}.
+
+\subsection{\ldots Structure an AST Program}
+
+An AST program normally has the following structure:
+
+\small
+\begin{terminalv}
+*  Include the interface to the AST library.
+      INCLUDE 'AST_PAR'
+
+*  Declare an integer status variable.
+      INTEGER STATUS
+      <maybe other declarations>
+
+*  Initialise the status to zero.
+      STATUS = 0
+      <maybe some Fortran statements>
+
+*  Enclose the parts which use AST between AST_BEGIN and AST_END calls.
+      CALL AST_BEGIN( STATUS )
+      <Fortran statements which use AST>
+      CALL AST_END( STATUS )
+
+      <maybe more Fortran statements>
+      END
+\end{terminalv}
+\normalsize
+
+The use of \htmlref{AST\_BEGIN}{AST\_BEGIN} and \htmlref{AST\_END}{AST\_END} is optional, but has the effect of
+tidying up after you have finished using AST, so is normally
+recommended. For more details of this, see \secref{ss:contexts}. For
+details of how to access the AST\_PAR include file, see
+\secref{ss:accessingheaderfile}.
+
+\subsection{\label{ss:howtobuild}\ldots Build an AST Program}
+
+To build a simple AST program that doesn't use graphics, use:
+
+\begin{small}
+\begin{terminalv}
+f77 program.f -L/star/lib -I/star/include `ast_link` -o program
+\end{terminalv}
+\end{small}
+
+On Linux systems you should usually use \verb+g77 -fno-second-underscore+ in
+place of \verb+f77+ - see \xref{``Software development on Linux''}{sun212}
+{software_development_on_linux} in \xref{SUN/212}{sun212}{}.
+
+To build a program which uses PGPLOT for graphics, use:
+
+\begin{small}
+\begin{terminalv}
+f77 program.f -L/star/lib `ast_link -pgplot` -o program
+\end{terminalv}
+\end{small}
+
+again using \verb+g77 -fno-second-underscore+ in place of \verb+f77+
+on Linux systems.
+
+For more details about accessing AST include files, see
+\secref{ss:accessingheaderfile}. For more
+details about linking programs, see \secref{ss:linking} and the
+description of the ``\htmlref{ast\_link}{ast\_link}'' command in
+\appref{ss:commanddescriptions}.
+
+\subsection{\label{ss:howtoreadwcs}\ldots Read a WCS Calibration from a Dataset}
+
+
+Precisely how you extract world coordinate system (WCS) information
+from a dataset obviously depends on what type of dataset it
+is. Usually, however, you should be able to obtain a set of FITS
+header cards which contain the WCS information (and probably much more
+besides). Suppose that CARDS is an array of character strings
+containing a complete set of FITS header cards and NCARD is the number
+of cards. Then proceed as follows:
+
+\small
+\begin{terminalv}
+      INTEGER FITSCHAN, ICARD, NCARD, WCSINFO
+      CHARACTER * ( 80 ) CARDS( NCARD )
+
+      ...
+
+*  Create a FitsChan and fill it with FITS header cards.
+      FITSCHAN = AST_FITSCHAN( AST_NULL, AST_NULL, ' ', STATUS )
+      DO 1 ICARD = 1, NCARD
+         CALL AST_PUTFITS( FITSCHAN, CARDS( ICARD ), .FALSE., STATUS )
+ 1    CONTINUE
+
+*  Rewind the FitsChan and read WCS information from it.
+      CALL AST_CLEAR( FITSCHAN, 'Card', STATUS )
+      WCSINFO = AST_READ( FITSCHAN, STATUS )
+\end{terminalv}
+\normalsize
+
+The result should be a pointer, WCSINFO, to a \htmlref{FrameSet}{FrameSet} which contains
+the WCS information. This pointer can now be used to perform many
+useful tasks, some of which are illustrated in the following recipes.
+
+Some datasets which do not easily yield FITS header cards may require
+a different approach, possibly involving use of a \htmlref{Channel}{Channel} or \htmlref{XmlChan}{XmlChan}
+(\secref{ss:channels}) rather than a \htmlref{FitsChan}{FitsChan}. In the case of the
+Starlink NDF data format, for example, all the above may be replaced
+by a single call to the routine
+\xref{NDF\_GTWCS}{sun33}{NDF_GTWCS}---see \xref{SUN/33}{sun33}{}.  The
+whole process can probably be encapsulated in a similar way for most
+other data systems, whether they use FITS header cards or not.
+
+For more details about reading WCS information from datasets, see
+\secref{ss:identifyingfitsencoding} and
+\secref{ss:readingforeignfits}. For a more general description of
+FitsChans and their use with FITS header cards, see
+\secref{ss:nativefits} and \secref{ss:foreignfits}. For more details
+about FrameSets, see \secref{ss:framesets} and \secref{ss:fshigher}.
+
+\subsection{\ldots Validate WCS Information}
+
+Once you have read WCS information from a dataset, as in
+\secref{ss:howtoreadwcs}, you may wish to check that you have been
+successful. The following will detect and classify the things that
+might possibly go wrong:
+
+\small
+\begin{terminalv}
+      IF ( STATUS .NE. 0 ) THEN
+         <an error occurred (a message will have been issued)>
+      ELSE IF ( WCSINFO .EQ. AST__NULL ) THEN
+         <there was no WCS information present>
+      ELSE IF ( AST_GETC( WCSINFO, 'Class', STATUS ) .NE. 'FrameSet' ) THEN
+         <something unexpected was read (i.e. not a FrameSet)>
+      ELSE
+         <WCS information was read OK>
+      END IF
+\end{terminalv}
+\normalsize
+
+For more information about detecting errors in AST routines, see
+\secref{ss:errordetection}. For details of how to validate input data
+read by AST, see \secref{ss:validatinginput} and
+\secref{ss:readingforeignfits}.
+
+\subsection{\ldots Display AST Data}
+
+If you have a pointer to any AST \htmlref{Object}{Object}, you can display the data
+stored in that Object in textual form as follows:
+
+\begin{small}
+\begin{terminalv}
+      CALL AST_SHOW( WCSINFO, STATUS )
+\end{terminalv}
+\end{small}
+
+Here, we have used a pointer to the \htmlref{FrameSet}{FrameSet} which we read earlier
+(\secref{ss:howtoreadwcs}).  The result is written to the program's
+standard output stream. This can be very useful during debugging.
+
+For more details about using \htmlref{AST\_SHOW}{AST\_SHOW}, see
+\secref{ss:displayingobjects}. For information about interpreting the
+output, also see \secref{ss:textualoutputformat}.
+
+\subsection{\label{ss:howtotransform}\ldots Convert Between Pixel and World Coordinates}
+
+You may use a pointer to a \htmlref{FrameSet}{FrameSet}, such as we read in
+\secref{ss:howtoreadwcs}, to transform a set of points between the
+pixel coordinates of an image and the associated world coordinates. If
+you are working in two dimensions, proceed as follows:
+
+\small
+\begin{terminalv}
+      INTEGER N
+      DOUBLE PRECISION XPIXEL( N ), YPIXEL( N )
+      DOUBLE PRECISION XWORLD( N ), YWORLD( N )
+
+      ...
+
+      CALL AST_TRAN2( WCSINFO, N, XPIXEL, YPIXEL, .TRUE.,
+     :                            XWORLD, YWORLD, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, N is the number of points to be transformed, XPIXEL and YPIXEL
+hold the pixel coordinates, and XWORLD and YWORLD receive the returned
+world coordinates.\footnote{By pixel coordinates, we mean a coordinate
+system in which the first pixel in the image is centred on (1,1) and
+each pixel is a unit square.  Note that the world coordinates will not
+necessarily be celestial coordinates, but if they are, then they will
+be in radians.}  To transform in the opposite direction, interchange
+the two pairs of arrays (so that the world coordinates are given as
+input) and change the fifth argument of \htmlref{AST\_TRAN2}{AST\_TRAN2} to .FALSE..
+
+To transform points in one dimension, use \htmlref{AST\_TRAN1}{AST\_TRAN1}. In any other
+number of dimensions (or if the number of dimensions is initially
+unknown), use \htmlref{AST\_TRANN}{AST\_TRANN}. These routines are described in
+\appref{ss:functiondescriptions}.
+
+For more information about transforming coordinates, see
+\secref{ss:transforming} and \secref{ss:framesetasmapping}. For
+details of how to handle missing coordinates, see
+\secref{ss:badcoordinates}.
+
+\subsection{\label{ss:howtotestforcelestial}\ldots Test if a WCS is a Celestial Coordinate System}
+
+The world coordinate system (WCS) currently associated with an image
+may often be a celestial coordinate system, but this need not
+necessarily be the case. For instance, instead of right ascension and
+declination, an image might have a WCS with axes representing
+wavelength and slit position, or maybe just plain old pixels.
+
+If you have obtained a WCS calibration for an image, as in
+\secref{ss:howtoreadwcs}, in the form of a pointer WCSINFO to a
+\htmlref{FrameSet}{FrameSet}, then you may determine if the current coordinate system is a
+celestial one or not, as follows:
+
+\begin{small}
+\begin{terminalv}
+      INTEGER FRAME
+      LOGICAL ISSKY
+
+      ...
+
+*  Obtain a pointer to the current Frame and determine if it is a
+*  SkyFrame.
+      FRAME = AST_GETFRAME( WCSINFO, AST__CURRENT, STATUS )
+      ISSKY = AST_ISASKYFRAME( FRAME, STATUS )
+      CALL AST_ANNUL( FRAME, STATUS )
+\end{terminalv}
+\end{small}
+
+This will set ISSKY to .TRUE.\ if the WCS is a celestial coordinate
+system, and to .FALSE.\ otherwise.
+
+\subsection{\label{ss:howtotestforspectral}\ldots Test if a WCS is a Spectral Coordinate System}
+Testing for a spectral coordinate system is basically the same as testing
+for a celestial coordinate system (see the previous section). The one
+difference is that you use the
+AST\_ISASPECFRAME routine
+in place of the
+AST\_ISASKYFRAME routine.
+
+\subsection{\label{ss:howtoformatcoordinates}\ldots Format Coordinates for Display}
+
+Once you have converted pixel coordinates into world coordinates
+(\secref{ss:howtotransform}), you may want to format them as text
+before displaying them. Typically, this would convert from (say)
+radians into something more comprehensible. Using the \htmlref{FrameSet}{FrameSet} pointer
+WCSINFO obtained in \secref{ss:howtoreadwcs} and a pair of world
+coordinates XW and YW (\emph{e.g.}\ see \secref{ss:howtotransform}),
+you could proceed as follows:
+
+\begin{small}
+\begin{terminalv}
+      CHARACTER * ( 20 ) XTEXT, YTEXT
+      DOUBLE PRECISION XW, YW
+
+      ...
+
+      XTEXT = AST_FORMAT( WCSINFO, 1, XW, STATUS )
+      YTEXT = AST_FORMAT( WCSINFO, 2, YW, STATUS )
+
+      WRITE ( *, 199 ) XTEXT, YTEXT
+ 199  FORMAT( 'Position = ', A, ', ', A )
+\end{terminalv}
+\end{small}
+
+Here, the second argument to \htmlref{AST\_FORMAT}{AST\_FORMAT} is the axis number.
+
+With celestial coordinates, this will usually result in sexagesimal
+notation, such as ``12:34:56.7''. However, the same method may be
+applied to any type of coordinates and appropriate formatting will be
+employed.
+
+For more information about formatting coordinate values and how to
+control the style of formatting used, see
+\secref{ss:formattingaxisvalues} and
+\secref{ss:formattingskyaxisvalues}. If necessary, also see
+\secref{ss:normalising} for details of how to ``normalise'' a set of
+coordinates so that they lie within the standard range (\emph{e.g.}\ 0
+to 24 hours for right ascension and $\pm 90^\circ$ for
+declination).
+
+\subsection{\ldots Display Coordinates as they are Transformed}
+
+In addition to formatting coordinates as part of a program's output,
+you may also want to examine coordinate values while debugging your
+program. To save time, you can ``eavesdrop'' on the coordinate values
+being processed every time they are transformed. For example, when
+using the \htmlref{FrameSet}{FrameSet} pointer WCSINFO obtained in
+\secref{ss:howtoreadwcs} to transform coordinates
+(\secref{ss:howtotransform}), you could inspect the coordinate values
+as follows:
+
+\begin{small}
+\begin{terminalv}
+      CALL AST_SET( WCSINFO, 'Report=1', STATUS )
+      CALL AST_TRAN2( WCSINFO, N, XPIXEL, YPIXEL, .TRUE.,
+     :                            XWORLD, YWORLD, STATUS )
+\end{terminalv}
+\end{small}
+
+By setting the FrameSet's \htmlref{Report}{Report} attribute to 1, coordinate
+transformations are automatically displayed on the program's standard
+output stream, appropriately formatted, for example:
+
+\begin{terminalv}
+(42.1087, 20.2717) --> (2:06:03.0, 34:22:39)
+(43.0197, 21.1705) --> (2:08:20.6, 35:31:24)
+(43.9295, 22.0716) --> (2:10:38.1, 36:40:09)
+(44.8382, 22.9753) --> (2:12:55.6, 37:48:55)
+(45.7459, 23.8814) --> (2:15:13.1, 38:57:40)
+(46.6528, 24.7901) --> (2:17:30.6, 40:06:25)
+(47.5589, 25.7013) --> (2:19:48.1, 41:15:11)
+(48.4644, 26.6149) --> (2:22:05.6, 42:23:56)
+(49.3695, 27.5311) --> (2:24:23.1, 43:32:41)
+(50.2742, 28.4499) --> (2:26:40.6, 44:41:27)
+\end{terminalv}
+
+For a complete description of the Report attribute, see its entry in
+\appref{ss:attributedescriptions}.  For further details of how to set
+and enquire attribute values, see \secref{ss:settingattributes} and
+\secref{ss:gettingattributes}.
+
+\subsection{\ldots Read Coordinates Entered by a User}
+
+In addition to writing out coordinate values generated by your program
+(\secref{ss:howtoformatcoordinates}), you may also need to accept
+coordinates entered by a user, or perhaps read from a file. In this
+case, you will probably want to allow ``free-format'' input, so that
+the user has some flexibility in the format that can be used. You will
+probably also want to detect any typing errors.
+
+Let's assume that you want to read a number of lines of text, each
+containing the world coordinates of a single point, and to split each
+line into individual numerical coordinate values. Using the \htmlref{FrameSet}{FrameSet}
+pointer WCSINFO obtained earlier (\secref{ss:howtoreadwcs}), you could
+proceed as follows:
+
+\begin{small}
+\begin{terminalv}
+      CHARACTER TEXT * ( 80 )
+      DOUBLE PRECISION COORD( 10 )
+      INTEGER IAXIS, N, NAXES, T
+
+      ...
+
+*  Obtain the number of coordinate axes (if not already known).
+      NAXES = AST_GETI( WCSINFO, 'Naxes', STATUS )
+
+*  Loop to read each line of input text, in this case from the
+*  standard input channel (your programming environment will probably
+*  provide a better way of reading text than this). Set the index T to
+*  the start of each line read.
+ 2    CONTINUE
+      READ( *, '(A)', END=99 ) TEXT
+      T = 1
+
+*  Attempt to read a coordinate for each axis.
+      DO 3 IAXIS = 1, NAXES
+         N = AST_UNFORMAT( WCSINFO, IAXIS, TEXT( T : ), COORD( IAXIS ),
+     :                     STATUS )
+
+*  If nothing was read and this is not the first axis and the end of
+*  the text has not been reached, try stepping over a separator and
+*  reading again.
+         IF ( ( N .EQ. 0 ) .AND. ( IAXIS .GT. 1 ) .AND.
+     :        ( T .LT. LEN( STRING ) ) ) THEN
+            T = T + 1
+            N = AST_UNFORMAT( WCSINFO, IAXIS, TEXT( T : ),
+                              COORD( IAXIS ), STATUS )
+         END IF
+
+*  Quit if nothing was read, otherwise move on to the next coordinate.
+         IF ( N .EQ. 0 ) GO TO 4
+         T = T + N
+ 3    CONTINUE
+ 4    CONTINUE
+
+*  Test for the possible errors that may occur...
+
+*  Error detected by AST (a message will have been issued).
+      IF ( STATUS .NE. 0 ) THEN
+         GO TO 99
+
+*  Error in input data at character TEXT( T + N : T + N ).
+      ELSE IF ( ( T .LT. LEN( STRING ) ) .OR. ( N .EQ. 0 ) ) THEN
+         <handle the error, or report your own message here>
+         GO TO 99
+
+      ELSE
+         <coordinates were read OK>
+      END IF
+
+*  Return to read the next input line.
+      GO TO 2
+ 99   CONTINUE
+\end{terminalv}
+\end{small}
+
+This algorithm has the advantage of accepting free-format input in
+whatever style is appropriate for the world coordinates in use (under
+the control of the FrameSet whose pointer you provide). For example,
+wavelength values might be read as floating point numbers
+(\emph{e.g.}\ ``1.047'' or ``4787''), whereas celestial positions
+could be given in sexagesimal format (\emph{e.g.}\ ``12:34:56'' or
+``12~34.5'') and would be converted into radians. Individual
+coordinate values may be separated by white space and/or any
+non-ambiguous separator character, such as a comma.
+
+For more information on reading coordinate values using the
+\htmlref{AST\_UNFORMAT}{AST\_UNFORMAT} function, see \secref{ss:unformattingaxisvalues}. For
+details of how sexagesimal formats are handled, and the forms of input
+that may be used for for celestial coordinates, see
+\secref{ss:unformattingskyaxisvalues}.
+
+\subsection{\label{ss:howtocreatenewwcs}\ldots Create a New WCS Calibration}
+
+This section describes how to add a WCS calibration to a data set which you
+are creating from scratch, rather than modifying an existing data set.
+
+In most common cases, the simplest way to create a new WCS calibration
+from scratch is probably to create a set of strings describing the
+required calibration in terms of the keywords used by the FITS WCS
+standard, and then convert these strings into an AST \htmlref{FrameSet}{FrameSet} describing
+the calibration. This FrameSet can then be used for many other purposes, or
+simply stored in the data set.
+
+The full FITS-WCS standard is quite involved, currently running to four
+separate papers, but the basic kernel is quite simple, involving the
+following keywords (all of which end with an integer axis index,
+indicated below by $<i>$):
+
+\begin{description}
+\item[CRPIX<i>]\mbox{}\\
+hold the pixel coordinates at a reference point
+\item[CRVAL<i>]\mbox{}\\
+hold the corresponding WCS coordinates at the reference point
+\item[CTYPE<i>]\mbox{}\\
+name the quantity represented by the WCS axes, together with the
+projection algorithm used to convert the scaled and rotated pixel coordinates
+to WCS coordinates.
+\item[CD<i>\_<j>]\mbox{}\\
+a set of keywords which specify the elements of a matrix. This matrix scales
+pixel offsets from the reference point into the offsets required as input
+by the projection algorithm specified by the CTYPE keywords. This matrix
+specifies the scale and rotation of the image. If there is no rotation
+the off-diagonal elements of the matrix (\emph{e.g.} CD1\_2 and
+CD2\_1) can be omitted.
+\end{description}
+
+As an example consider the common case of a simple 2D image of the sky in
+which north is parallel to the second pixel axis and east parallel to the
+(negative) first pixel axis. The image scale is 1.2 arc-seconds per pixel
+on both axes, and the image is presumed to have been obtained with a
+tangent plane projection. Furthermore, it is known that pixel coordinates
+(100.5,98.4) correspond to an RA of 11:00:10 and a Dec. of  -23:26:02.
+A suitable set of FITS-WCS header cards could be:
+
+\begin{small}
+\begin{terminalv}
+CTYPE1  = 'RA---TAN'       / Axis 1 represents RA with a tan projection
+CTYPE2  = 'DEC--TAN'       / Axis 2 represents Dec with a tan projection
+CRPIX1  = 100.5            / Pixel coordinates of reference point
+CRPIX2  = 98.4             / Pixel coordinates of reference point
+CRVAL1  = 165.04167        / Degrees equivalent of "11:00:10" hours
+CRVAL2  = -23.433889       / Decimal equivalent of "-23:26:02" degrees
+CD1_1   = -0.0003333333    / Decimal degrees equivalent of -1.2 arc-seconds
+CD2_2   = 0.0003333333     / Decimal degrees equivalent of 1.2 arc-seconds
+\end{terminalv}
+\end{small}
+
+Notes:
+\begin{itemize}
+\item a FITS header card begins with the keyword name starting at column 1,
+has an equals sign in column 9, and the keyword value in columns 11 to 80.
+\item string values must be enclosed in single quotes.
+\item celestial longitude and latitude must both be specified in decimal degrees.
+\item the CD1\_1 value is negative to indicate that RA increases as the
+first pixel axis decreases.
+\item the (RA,Dec) coordinates will be taken as ICRS coordinates. For FK5
+you should add:
+
+\begin{small}
+\begin{terminalv}
+RADESYS = 'FK5'
+EQUINOX = 2005.6
+\end{terminalv}
+\end{small}
+
+The EQUINOX value defaults to J2000.0 if omitted. FK4 can also be used in
+place of FK5, in which case EQUINOX defaults to B1950.0.
+
+\end{itemize}
+
+Once you have created these FITS-WCS header card strings, you should
+store them in a \htmlref{FitsChan}{FitsChan} and then read the corresponding FrameSet from the
+FitsChan. How to do this is described in \secref{ss:howtoreadwcs}.
+
+Having created the WCS calibration, you may want to store it in a data
+file. How to do this is described in \secref{ss:howtowritewcs}).\footnote{If
+you are writing the WCS calibration to a FITS file you obviously
+have the choice of storing the FITS-WCS cards directly.}
+
+If the required WCS calibration cannot be described as a set of FITS-WCS
+headers, then a different approach is necessary. In this case, you should
+first create a \htmlref{Frame}{Frame} describing pixel coordinates, and store this Frame
+in a new FrameSet. You should then create a new Frame describing the
+world coordinate system. This Frame may be a specific subclass of Frame such
+as a \htmlref{SkyFrame}{SkyFrame} for celestial coordinates, a \htmlref{SpecFrame}{SpecFrame} for spectral
+coordinates, a Timeframe for time coordinates, or a \htmlref{CmpFrame}{CmpFrame} for a combination
+of different coordinates.
+You also need to create a suitable \htmlref{Mapping}{Mapping} which transforms pixel
+coordinates into world coordinates. AST provides many different types of
+Mappings, all of which can be combined together in arbitrary fashions to
+create more complicated Mappings. The WCS Frame should then be added into
+the FrameSet, using the Mapping to connect the WCS Frame with the pixel
+Frame.
+
+\subsection{\label{ss:howtomodifywcs}\ldots Modify a WCS Calibration}
+
+The usual reason for wishing to modify the WCS calibration associated
+with a dataset is that the data have been geometrically transformed in
+some way (here, we will assume a 2-dimensional image dataset). This
+causes the image features (stars, galaxies, \emph{etc.}) to move with
+respect to the grid of pixels which they occupy, so that any
+coordinate systems previously associated with the image become
+invalid.
+
+To correct for this, it is necessary to set up a \htmlref{Mapping}{Mapping} which
+expresses the positions of image features in the new data grid in
+terms of their positions in the old grid. In both cases, the grid
+coordinates we use will have the first pixel centred at (1,1) with
+each pixel being a unit square.
+
+AST allows you to correct for any type of geometrical transformation
+in this way, so long as a suitable Mapping to describe it can be
+constructed. For purposes of illustration, we will assume here that
+the new image coordinates XNEW and YNEW can be expressed in terms of
+the old coordinates XOLD and YOLD as follows:
+
+\begin{small}
+\begin{terminalv}
+      DOUBLE PRECISION XNEW, XOLD, YNEW, YOLD
+      DOUBLE PRECISION M( 4 ), Z( 2 )
+
+      ...
+
+      XNEW = XOLD * M( 1 ) + YOLD * M( 2 ) + Z( 1 )
+      YNEW = XOLD * M( 3 ) + YOLD * M( 4 ) + Z( 2 )
+\end{terminalv}
+\end{small}
+
+where M is a 2$\times$2 transformation matrix and Z represents a shift
+of origin. This is therefore a general linear coordinate
+transformation which can represent displacement, rotation,
+magnification and shear.
+
+In AST, it can be represented by concatenating two Mappings. The first
+is a \htmlref{MatrixMap}{MatrixMap}, which implements the matrix multiplication. The second
+is a \htmlref{WinMap}{WinMap}, which linearly transforms one coordinate window on to
+another, but will be used here simply to implement the shift of
+origin (alternatively, a \htmlref{ShiftMap}{ShiftMap} could have been used in place of a
+WinMap). These Mappings may be constructed and concatenated as follows:
+
+\begin{small}
+\begin{terminalv}
+      DOUBLE PRECISION INA( 2 ), INB( 2 ), OUTA( 2 ), OUTB( 2 )
+      INTEGER MATRIXMAP, WINMAP
+
+      ...
+
+*  Set up the corners of a unit square.
+      DATA INA / 2 * 0.0D0 /
+      DATA INB / 2 * 1.0D0 /
+
+*  The MatrixMap may be constructed directly from the matrix M.
+      MATRIXMAP = AST_MATRIXMAP( 2, 2, 0, M, ' ', STATUS )
+
+*  For the WinMap, we take the coordinates of the corners of a unit
+*  square (window) and then shift them by the required amounts.
+      OUTA( 1 ) = INA( 1 ) + Z( 1 )
+      OUTA( 2 ) = INA( 2 ) + Z( 2 )
+      OUTB( 1 ) = INB( 1 ) + Z( 1 )
+      OUTB( 2 ) = INB( 2 ) + Z( 2 )
+
+*  The WinMap will then implement this shift.
+      WINMAP = AST_WINMAP( 2, INA, INB, OUTA, OUTB, ' ', STATUS )
+
+*  Join the two Mappings together, so that they are applied one after
+*  the other.
+      NEWMAP = AST_CMPMAP( MATRIXMAP, WINMAP, 1, ' ', STATUS )
+\end{terminalv}
+\end{small}
+
+You might, of course, create any other form of Mapping depending on
+the type of geometrical transformation involved. For an overview of
+the Mappings provided by AST, see \secref{ss:mappingselection}, and
+for a description of the capabilities of each class of Mapping, see
+its entry in \appref{ss:classdescriptions}. For an overview of how
+individual Mappings may be combined, see \secref{ss:cmpmapoverview}
+(\secref{ss:cmpmaps} gives more details).
+
+Assuming you have obtained a WCS calibration for your original image
+in the form of a pointer to a \htmlref{FrameSet}{FrameSet}, WCSINFO1
+(\secref{ss:howtoreadwcs}), the Mapping created above may be used to
+produce a calibration for the new image as follows:
+
+\begin{small}
+\begin{terminalv}
+      INTEGER WCSINFO1, WCSINFO2
+
+      ...
+
+*  If necessary, make a copy of the WCS calibration, since we are
+*  about to alter it.
+      WCSINFO2 = AST_COPY( WCSINFO1, STATUS )
+
+*  Re-map the base Frame so that it refers to the new data grid
+*  instead of the old one.
+      CALL AST_REMAPFRAME( WCSINFO2, AST__BASE, NEWMAP, STATUS )
+\end{terminalv}
+\end{small}
+
+This will produce a pointer, WCSINFO2, to a new FrameSet in which all
+the coordinate systems associated with the original image are modified
+so that they are correctly registered with your new image instead.
+
+For more information about re-mapping the Frames within a FrameSet,
+see \secref{ss:remapframe}. Also see \secref{ss:wcsprocessingexample}
+for a similar example to the above, applicable to the case of reducing
+the size of an image by binning.
+
+\subsection{\label{ss:howtowritewcs}\ldots Write a Modified WCS Calibration to a Dataset}
+
+If you have modified the WCS calibration associated with a dataset,
+such as in the example above (\secref{ss:howtomodifywcs}), then you
+will need to write the modified version out along with any new data.
+
+In the same way as when reading a WCS calibration
+(\secref{ss:howtoreadwcs}), how you do this will depend on your data
+system, but we will assume that you wish to generate a set of FITS
+header cards that can be stored with the data. You should usually make
+preparations for doing this when you first read the WCS calibration
+from your input dataset by modifying the example given in
+\secref{ss:howtoreadwcs} as follows:
+
+\begin{small}
+\begin{terminalv}
+      INTEGER FITSCHAN1, WCSINFO1
+      CHARACTER * ( 20 ) ENCODE
+
+      ...
+
+*  Create an input FitsChan and fill it with FITS header cards. Note,
+*  if you have all the header cards in a single string, use AST_PUTCARDS in
+*  place of AST_PUTFITS.
+      FITSCHAN1 = AST_FITSCHAN( AST_NULL, AST_NULL, ' ', STATUS )
+      DO 1 ICARD = 1, NCARD
+         CALL AST_PUTFITS( FITSCHAN1, CARDS( ICARD ), .FALSE., STATUS )
+ 1    CONTINUE
+
+*  Note which encoding has been used for the WCS information.
+      ENCODE = AST_GETC( FITSCHAN1, 'Encoding', STATUS );
+
+*  Rewind the input FitsChan and read the WCS information from it.
+      CALL AST_CLEAR( FITSCHAN1, 'Card', STATUS )
+      WCSINFO1 = AST_READ( FITSCHAN1, STATUS )
+\end{terminalv}
+\end{small}
+
+Note how we have added an enquiry to determine how the WCS information
+is encoded in the input FITS cards, storing the resulting string in
+the ENCODE variable. This must be done \textbf{before} actually reading
+the WCS calibration.
+
+
+Once you have produced a modified WCS calibration for the output
+dataset (\emph{e.g.}\ \secref{ss:howtomodifywcs}), in the form of a
+\htmlref{FrameSet}{FrameSet} identified by the pointer WCSINFO2, you can produce a new
+\htmlref{FitsChan}{FitsChan} containing the output FITS header cards as follows:
+
+\small
+\begin{terminalv}
+      INTEGER FITSCHAN2, JUNK, WCSINFO2
+
+      ...
+
+*  Make a copy of the input FitsChan, AFTER the WCS information has
+*  been read from it. This will propagate all the input FITS header
+*  cards, apart from those describing the WCS calibration.
+      FITSCHAN2 = AST_COPY( FITSCHAN1, STATUS )
+
+*  If necessary, make modifications to the cards in FITSCHAN2
+*  (e.g. you might need to change NAXIS1, NAXIS2, etc., to account for
+*  a change in image size). You probably only need to do this if your
+*  data system does not provide these facilities itself.
+      <details not shown - see below>
+
+*  Alternatively, if your data system handles the propagation of FITS
+*  header cards to the output dataset for you, then simply create an
+*  empty FitsChan to contain the output WCS information alone.
+*     FITSCHAN2 = AST_FITSCHAN( AST_NULL, AST_NULL, ' ', STATUS )
+
+*  Rewind the new FitsChan (if necessary) and attempt to write the
+*  output WCS information to it using the same encoding method as the
+*  input dataset.
+      CALL AST_SET( FITSCHAN2, 'Card=1, Encoding=' // ENCODE, STATUS )
+      IF ( AST_WRITE( FITSCHAN2, WCSINFO2, STATUS ) .EQ. 0 ) THEN
+
+*  If this didn't work (the WCS FrameSet has become too complex), then
+*  use the native AST encoding instead.
+         CALL AST_SETC( FITSCHAN2, 'Encoding', 'NATIVE', STATUS );
+         JUNK = AST_WRITE( FITSCHAN2, WCSINFO2, STATUS );
+      END IF
+\end{terminalv}
+\normalsize
+
+For details of how to modify the contents of the output FitsChan in
+other ways, such as by adding, over-writing or deleting header cards,
+see \secref{ss:addressingfitscards}, \secref{ss:addingmulticards}, \secref{ss:addingfitscards} and
+\secref{ss:findingandchangingfits}.
+
+Once you have assembled the output FITS cards, you may retrieve them
+from the FitsChan that contains them as follows:
+
+\small
+\begin{terminalv}
+      CHARACTER * ( 80 ) CARD
+
+      ...
+
+      CALL AST_CLEAR( FITSCHAN2, 'Card', STATUS )
+ 5    CONTINUE
+      IF ( AST_FINDFITS( FITSCHAN2, '%f', CARD, .TRUE., STATUS ) ) THEN
+         WRITE ( *, '(A)' ) CARD
+         GO TO 5
+      END IF
+\end{terminalv}
+\normalsize
+
+Here, we have simply written each card to the standard output unit,
+but you would obviously replace this with a subroutine call to store
+the cards in your output dataset.
+
+For data systems that do not use FITS header cards, a different
+approach may be needed, possibly involving use of a \htmlref{Channel}{Channel} or \htmlref{XmlChan}{XmlChan}
+(\secref{ss:channels}) rather than a FitsChan.  In the case of the
+Starlink NDF data format, for example, all of the above may be
+replaced by a single call to the routine
+\xref{NDF\_PTWCS}{sun33}{NDF_PTWCS}---see \xref{SUN/33}{sun33}{}. The
+whole process can probably be encapsulated in a similar way for most
+other data systems, whether they use FITS header cards or not.
+
+For an overview of how to propagate WCS information through data
+processing steps, see \secref{ss:propagatingwcsinformation}.  For more
+information about writing WCS information to FitsChans, see
+\secref{ss:writingnativefits} and \secref{ss:writingforeignfits}.  For
+information about the options for encoding WCS information in FITS
+header cards, see \secref{ss:nativeencoding},
+\secref{ss:foreignencodings}, and the description of the \htmlref{Encoding}{Encoding}
+attribute in \appref{ss:attributedescriptions}.  For a complete
+understanding of FitsChans and their use with FITS header cards, you
+should read \secref{ss:nativefits} and \secref{ss:foreignfits}.
+
+\subsection{\label{ss:howtoplotgrid}\ldots Display a Graphical Coordinate Grid}
+
+   A common requirement when displaying image data is to plot an
+   associated coordinate grid (\emph{e.g.}\ Figure~\ref{fig:overgrid})
+   over the displayed image.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun210_figures/overgrid_bw}
+   \caption[An example of a displayed image with a coordinate grid
+   plotted over it.]{An example of a displayed image with a coordinate grid
+   plotted over it.}
+   \label{fig:overgrid}
+   \end{center}
+   \end{figure}
+
+The use of AST in such circumstances is independent of the underlying
+graphics system, so starting up the graphics system, setting up a
+coordinate system, displaying the image, and closing down afterwards
+can all be done using the graphics routines you would normally use.
+
+However, displaying an image at a precise location can be a little
+fiddly with some graphics systems, and obviously the grid drawn by AST
+will not be accurately registered with the image unless this is done
+correctly. In the following template, we therefore illustrate both
+steps, basing the image display on the PGPLOT graphics
+package.\footnote{An interface is provided with AST that allows it to
+use PGPLOT (\xref{SUN/15}{sun15}{}) for its graphics, although
+interfaces to other graphics systems may also be written.}  Plotting a
+coordinate grid with AST then becomes a relatively minor part of what
+is almost a complete graphics program.
+
+Once again, we assume that a pointer, WCSINFO, to a suitable \htmlref{FrameSet}{FrameSet}
+associated with the image has already been obtained
+(\secref{ss:howtoreadwcs}).
+
+\small
+\begin{terminalv}
+      DOUBLE PRECISION BBOX( 4 )
+      INTEGER NX, NY, PGBEG, PLOT
+      REAL DATA( NX, NY ), GBOX( 4 ), HI, LO, SCALE, TR( 6 )
+      REAL X1, X2, XLEFT, XRIGHT, Y1, Y2, YBOTTOM, YTOP
+
+      ...
+
+*  Access the image data, which we assume will be stored in the real
+*  2-dimensional array DATA with dimension sizes NX and NY. Also
+*  derive limits for scaling it, which we assign to the variables HI
+*  and LO.
+      <this stage depends on your data system, so is not shown>
+
+*  Open PGPLOT using the device given by environment variable
+*  PGPLOT_DEV and check for success.
+      IF ( PGBEG( 0, ' ', 1, 1 ) .EQ. 1 ) THEN
+
+*  Clear the screen and ensure equal scales on both axes.
+         CALL PGPAGE
+         CALL PGWNAD( 0.0, 1.0, 0.0, 1.0 )
+
+*  Obtain the extent of the plotting area (not strictly necessary for
+*  PGPLOT, but possibly for other graphics systems). From this, derive
+*  the display scale in graphics units per pixel so that the image
+*  will fit within the display area.
+         CALL PGQWIN( X1, X2, Y1, Y2 )
+         SCALE = MIN( ( X2 - X1 ) / NX, ( Y2 - Y1 ) / NY )
+
+*  Calculate the extent of the area in graphics units that the image
+*  will occupy, so as to centre it within the display area.
+         XLEFT   = 0.5 * ( X1 + X2 - NX * SCALE )
+         XRIGHT  = 0.5 * ( X1 + X2 + NX * SCALE )
+         YBOTTOM = 0.5 * ( Y1 + Y2 - NY * SCALE )
+         YTOP    = 0.5 * ( Y1 + Y2 + NY * SCALE )
+
+*  Set up a PGPLOT coordinate transformation matrix and display the
+*  image data as a grey scale map (these details are specific to
+*  PGPLOT).
+         TR( 1 ) = XLEFT - 0.5 * SCALE
+         TR( 2 ) = SCALE
+         TR( 3 ) = 0.0
+         TR( 4 ) = YBOTTOM - 0.5 * SCALE
+         TR( 5 ) = 0.0
+         TR( 6 ) = SCALE
+         CALL PGGRAY( DATA, NX, NY, 1, NX, 1, NY, HI, LO, TR )
+
+*  BEGINNING OF AST BIT
+*  ====================
+*  Store the locations of the bottom left and top right corners of the
+*  region used to display the image, in graphics coordinates.
+         GBOX( 1 ) = XLEFT
+         GBOX( 2 ) = YBOTTOM
+         GBOX( 3 ) = XRIGHT
+         GBOX( 4 ) = YTOP
+
+*  Similarly, store the locations of the image's bottom left and top
+*  right corners, in pixel coordinates -- with the first pixel centred
+*  at (1,1).
+         BBOX( 1 ) = 0.5D0
+         BBOX( 2 ) = 0.5D0
+         BBOX( 3 ) = NX + 0.5D0
+         BBOX( 4 ) = NY + 0.5D0
+
+*  Create a Plot, based on the FrameSet associated with the
+*  image. This attaches the Plot to the graphics surface so that it
+*  matches the displayed image. Specify that a complete set of grid
+*  lines should be drawn (rather than just coordinate axes).
+         PLOT = AST_PLOT( WCSINFO, GBOX, BBOX, 'Grid=1', STATUS )
+
+*  Optionally, we can now set other Plot attributes to control the
+*  appearance of the grid. The values assigned here use the
+*  colour/font indices defined by the underlying graphics system.
+         CALL AST_SET( PLOT, 'Colour(grid)=2, Font(textlab)=3', STATUS )
+
+*  Use the Plot to draw the coordinate grid.
+         CALL AST_GRID( PLOT, STATUS )
+
+         <maybe some more AST graphics here>
+
+*  Annul the Plot when finished (or use the AST_BEGIN/AST_END
+*  technique shown earlier).
+         CALL AST_ANNUL( PLOT, STATUS )
+
+*  END OF AST BIT
+*  ==============
+
+*  Close down the graphics system.
+         CALL PGEND
+      END IF
+\end{terminalv}
+\normalsize
+
+Note that once you have set up a \htmlref{Plot}{Plot} which is aligned with a
+displayed image, you may also use it to generate further graphical
+output of your own, specified in the image's world coordinate system
+(such as markers to represent astronomical objects, annotation,
+\emph{etc.}). There is also a range of Plot attributes which gives
+control over most aspects of the output's appearance.  For details of
+the facilities available, see \secref{ss:plots} and the description of
+the Plot class in \appref{ss:classdescriptions}.
+
+For details of how to build a graphics program which uses PGPLOT, see
+\secref{ss:howtobuild} and the description of the \htmlref{ast\_link}{ast\_link} command in
+\appref{ss:commanddescriptions}.
+
+\subsection{\label{ss:howtoswitchgrid}\ldots Switch to Plot a Different Celestial Coordinate Grid}
+
+Once you have set up a \htmlref{Plot}{Plot} to draw a coordinate grid
+(\secref{ss:howtoplotgrid}), it is a simple matter to change things so
+that the grid represents a different celestial coordinate system. For
+example, after creating the Plot with \htmlref{AST\_PLOT}{AST\_PLOT}, you could use:
+
+\small
+\begin{terminalv}
+      CALL AST_SET( PLOT, 'System=Galactic', STATUS )
+\end{terminalv}
+\normalsize
+or:
+\small
+\begin{terminalv}
+      CALL AST_SET( PLOT, 'System=FK5, Equinox=J2010', STATUS )
+\end{terminalv}
+\normalsize
+
+and any axes and/or grid drawn subsequently would represent the new
+celestial coordinate system you specified.  Note, however, that this
+will only work if the original grid represented celestial coordinates
+of some kind (see \secref{ss:howtotestforcelestial} for how to
+determine if this is the case\footnote{Note that the methods applied
+to a \htmlref{FrameSet}{FrameSet} may be used equally well with a Plot.}). If it did not,
+you will get an error message.
+
+For more information about the celestial coordinate systems available,
+see the descriptions of the \htmlref{System}{System}, \htmlref{Equinox}{Equinox} and \htmlref{Epoch}{Epoch} attributes in
+\appref{ss:attributedescriptions}.
+
+\subsection{\ldots Give a User Control Over the Appearance of a Plot}
+
+The idea of using a \htmlref{Plot}{Plot}'s attributes to control the appearance of the
+graphical output it produces (\secref{ss:howtoplotgrid} and
+\secref{ss:howtoswitchgrid}) can easily be extended to allow the user
+of a program complete control over such matters.
+
+For instance, if the file ``plot.config'' contains a series of
+plotting options in the form of Plot attribute assignments (see below
+for an example), then we could create a Plot and implement these
+assignments before producing the graphical output as follows:
+
+\small
+\begin{terminalv}
+      CHARACTER LINE( 120 )
+      INTEGER BASE
+
+      ...
+
+*  Create a Plot and define the default appearance of the graphical
+*  output it will produce.
+      PLOT = AST_PLOT( WCSINFO, GBOX, PBOX,
+     :                 'Grid=1, Colour(grid)=2, Font(textlab)=3',
+     :                 STATUS )
+
+*  Obtain the value of any Plot attributes we want to preserve.
+      BASE = AST_GETI( PLOT, 'Base', STATUS )
+
+*  Open the plot configuration file, if it exists.
+      OPEN ( 1, FILE = 'plot.config', STATUS = 'OLD', ERR = 8 )
+
+*  Read each line of text and use it to set new Plot attribute
+*  values. Close the file when done.
+ 6    CONTINUE
+         READ ( 1, '(A)', END = 7 ) LINE
+         CALL AST_SET( PLOT, LINE, STATUS )
+      GO TO 6
+ 7    CLOSE ( 1 )
+ 8    CONTINUE
+
+*  Restore any attribute values we are preserving.
+      CALL AST_SETI( PLOT, 'Base', BASE, STATUS )
+
+*  Produce the graphical output (e.g.).
+      CALL AST_GRID( PLOT, STATUS )
+\end{terminalv}
+\normalsize
+
+Notice that we take care that the Plot's \htmlref{Base}{Base} attribute is preserved
+so that the user cannot change it. This is because graphical output
+will not be produced successfully if the base \htmlref{Frame}{Frame} does not describe
+the plotting surface to which we attached the Plot when we created it.
+
+The arrangement shown above allows the contents of the ``plot.config''
+file to control most aspects of the graphical output produced
+(including the coordinate system used; the colour, line style,
+thickness and font used for each component; the positioning of axes
+and tick marks; the precision, format and positioning of labels;
+\emph{etc.}) \emph{via} assignments of the form:
+
+\small
+\begin{terminalv}
+System=Galactic, Equinox = 2001
+Border = 1, Colour( border ) = 1
+Colour( grid ) = 2
+DrawAxes = 1
+Colour( axes ) = 3
+Digits = 8
+Labelling = Interior
+\end{terminalv}
+\normalsize
+
+For a more sophisticated interface, you could obviously perform
+pre-processing on this input---for example, to translate words like
+``red'', ``green'' and ``blue'' into colour indices, to permit
+comments and blank lines, \emph{etc.}
+
+For a full list of the attributes that may be used to control the
+appearance of graphical output, see the description of the Plot class
+in \appref{ss:classdescriptions}. For a complete description of each
+individual attribute (\emph{e.g.}\ those above), see the attribute's
+entry in \appref{ss:attributedescriptions}.
+
+\cleardoublepage
+\section{\label{ss:primer}An AST Object Primer}
+
+The AST library deals throughout with entities called Objects and a
+basic understanding of how to handle these is needed before you can
+use the library effectively.  If you are already familiar with an
+object-oriented language, such as C$++$, few of the concepts should
+seem new to you.  Be aware, however, that AST is designed to be used
+\emph{via} fairly conventional Fortran and C interfaces, so some
+things have to be done a little differently.
+
+If you are not already familiar with object-oriented programming, then
+don't worry---we will not emphasise this aspect more than is necessary
+and will not assume any background knowledge.  Instead, this section
+concentrates on presenting all the fundamental information you will
+need, explaining how AST Objects behave and how to manipulate them
+from conventional Fortran programs.
+
+If you like to read documents from cover to cover, then you can
+consider this section as an introduction to the programming techniques
+used in the rest of the document. Otherwise, you may prefer to skim
+through it on a first reading and return to it later as reference
+material.
+
+\subsection{AST Objects}
+
+An AST \htmlref{Object}{Object} is an entity which is used to store information and
+Objects come in various kinds, called \emph{classes}, according to the
+sort of information they hold. Throughout this section, we will make
+use of a simple Object belonging to the ``\htmlref{ZoomMap}{ZoomMap}'' class to
+illustrate many of the basic concepts.
+
+A ZoomMap is an Object that contains a recipe for converting
+coordinates between two hypothetical coordinate systems.  It does this
+by multiplying all the coordinate values by a constant called the
+\emph{\htmlref{Zoom}{Zoom} factor}.  A ZoomMap is a very simple Object which exists
+mainly for use in examples. It allows us to illustrate the ways in
+which Objects are manipulated and to introduce the concept of a
+\htmlref{Mapping}{Mapping}---a recipe for converting coordinates---which is fundamental
+to the way the AST library works.
+
+\subsection{\label{ss:objectcreation}Object Creation and Pointers}
+
+Let us first consider how to create a \htmlref{ZoomMap}{ZoomMap}. This is done very
+simply as follows:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER STATUS, ZOOMMAP
+
+      STATUS = 0
+
+      ...
+
+      ZOOMMAP = AST_ZOOMMAP( 2, 5.0D0, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+The first step is to include the file AST\_PAR which defines the
+interface to the AST library and, amongst other things, declares
+\htmlref{AST\_ZOOMMAP}{AST\_ZOOMMAP} to be an integer function.  We then declare an integer
+variable ZOOMMAP to receive the result and an integer STATUS variable
+to hold the error status, which we initialise to zero. Next, we invoke
+AST\_ZOOMMAP to create the ZoomMap. The pattern is the same for all
+other classes of AST \htmlref{Object}{Object}---you simply prefix ``AST\_'' to the class
+name to obtain the function that creates the Object.
+
+These functions are called \emph{constructor functions}, or simply
+\emph{constructors} (you can find an individual description of all AST
+functions in \appref{ss:functiondescriptions}) and the arguments
+passed to the constructor are used to initialise the new Object. In
+this case, we specify 2 as the number of coordinates (\emph{i.e.}\ we
+are going to work in a 2-dimensional
+space) and 5.0D0 as the \htmlref{Zoom}{Zoom} factor to be applied. Note that this is a
+Fortran double precision value. We will return to the final two
+arguments, a blank string and the error status, shortly
+(\secref{ss:attributeinitialisation} and \secref{ss:errordetection}).
+
+The integer value returned by the constructor is termed an \emph{Object
+pointer} or, in this case, a \emph{ZoomMap pointer}. This pointer is not
+an Object itself, but is a value used to refer to the Object. You
+should be careful not to modify any Object pointer yourself, as this
+may render it invalid. Instead, you perform all subsequent operations
+on the Object by passing this pointer to other AST routines.
+
+\subsection{\label{ss:objecthierarchy}The Object Hierarchy}
+
+Now that we have created our first \htmlref{ZoomMap}{ZoomMap}, let us examine how it
+relates to other kinds of \htmlref{Object}{Object} before investigating what we can do
+with it.
+
+We have so far indicated that a ZoomMap is a kind of Object and have
+also mentioned that it is a kind of \htmlref{Mapping}{Mapping} as well. These statements
+can be represented very simply using the following hierarchy:
+
+\small
+\begin{terminalv}
+Object
+   Mapping
+      ZoomMap
+\end{terminalv}
+\normalsize
+
+which is a way of stating that a ZoomMap is a special class of
+Mapping, while a Mapping, in turn, is a special class of Object.  This
+is exactly like saying that an Oak is a special form of Tree, while a
+Tree, in turn, is a special form of Plant. This may seem almost
+trivial, but before you turn to read something less dull, be assured
+that it is a very important idea to keep in mind in what follows.
+
+If we look at some of the other Objects used by the AST library, we
+can see how these are all related in a similar way (don't worry about
+what they do at this stage):
+\label{ss:mappinghierarchy}
+
+\small
+\begin{terminalv}
+Object
+   Mapping
+      Frame
+         FrameSet
+            Plot
+      UnitMap
+      ZoomMap
+   Channel
+      FitsChan
+      XmlChan
+\end{terminalv}
+\normalsize
+
+Notice that there are several different types of Mapping available
+(\emph{i.e.}\ there are classes of Object indented beneath the
+``Mapping'' heading) and, in addition, other types of Object which are
+not Mappings---Channels for instance (which are at the same
+hierarchical level as Mappings).
+
+The most specialised Object we have shown here is the \htmlref{Plot}{Plot} (which we
+will not discuss in detail until \secref{ss:plots}). As you can see, a
+Plot is a \htmlref{FrameSet}{FrameSet}\ldots\ and a \htmlref{Frame}{Frame}\ldots\ and a Mapping\ldots\ and,
+like everything else, ultimately an Object.
+
+What this means is that you can use a Plot not only for its own
+specialised behaviour, but also whenever any of these other
+less-specialised classes of Object is called for. The general rule is
+that an Object of a particular class may substitute for any of the
+classes appearing above it in this hierarchy. The Object is then said
+to \emph{inherit} the behaviour of these higher classes. We can
+therefore use our ZoomMap whenever a ZoomMap, a Mapping or an Object
+is called for.
+
+Sometimes, this can lead to some spectacular short-cuts by avoiding
+the need to break large Objects down in order to access their
+components. With some practice and a little lateral thinking you
+should soon be able to spot opportunities for this.
+
+You can find the full \emph{class hierarchy}, as this is called, for
+the AST library in \appref{ss:classhierarchy} and you may need to
+refer to it occasionally until you are familiar with the classes you
+need to use.
+
+\subsection{\label{ss:displayingobjects}Displaying Objects}
+
+Let us now return to the \htmlref{ZoomMap}{ZoomMap} that we created earlier
+(\secref{ss:objectcreation}) and examine what it's made of.
+There is a routine for doing this, called \htmlref{AST\_SHOW}{AST\_SHOW}, which is provided
+mainly for looking at Objects while you are debugging programs.
+
+If you consult the description of AST\_SHOW in
+\appref{ss:functiondescriptions}, you will find that it takes a
+pointer to an \htmlref{Object}{Object} as its argument (in addition to the usual STATUS
+argument). Although we have only a ZoomMap pointer available,
+fortunately this is not a problem. If you refer to the brief class
+hierarchy described above (\secref{ss:mappinghierarchy}), you will see
+that a ZoomMap is an Object, albeit a specialised one, so it inherits
+the properties of all Objects and can be substituted wherever an
+Object is required.  We can therefore pass our ZoomMap pointer
+directly to AST\_SHOW, as follows:
+
+\small
+\begin{terminalv}
+      CALL AST_SHOW( ZOOMMAP, STATUS )
+\end{terminalv}
+\normalsize
+
+The output from this will appear on the standard output stream and
+should look like the following:
+
+\small
+\begin{terminalv}
+Begin ZoomMap
+   Nin = 2
+IsA Mapping
+   Zoom = 5
+End ZoomMap
+\end{terminalv}
+\normalsize
+
+Here, the ``Begin'' and ``End'' lines mark the beginning and end of
+the ZoomMap, while the values 2 and 5 are simply the values we
+supplied to initialise it (\secref{ss:objectcreation}). These have
+been given simple names to make them easy to refer to.
+
+The line in the middle which says ``IsA~\htmlref{Mapping}{Mapping}'' is a dividing line
+between the two values. It indicates that the ``\htmlref{Nin}{Nin}'' value is a
+property shared by all Mappings, so the ZoomMap has inherited this
+from its \emph{parent class} (Mapping). The ``\htmlref{Zoom}{Zoom}'' value, however,
+is specific to a ZoomMap and isn't shared by other kinds of Mappings.
+
+\subsection{\label{ss:gettingattributes}Getting Attribute Values}
+
+We saw above (\secref{ss:displayingobjects}) how to display the
+internal values of an \htmlref{Object}{Object}, but what about accessing these values
+from a program?  Not all internal Object values are accessible in this
+way, but many are. Those that are, are called \emph{attributes}. A
+description of all the attributes used by the AST library can be found
+in \appref{ss:attributedescriptions}.
+
+Attributes come in several data types (character string, integer,
+boolean and floating point) and there is a standard way of obtaining
+their values. As an example, consider obtaining the value of the \htmlref{Nin}{Nin}
+attribute for the \htmlref{ZoomMap}{ZoomMap} created earlier. This could be done as
+follows:
+
+\small
+\begin{terminalv}
+      INTEGER NIN
+
+      ...
+
+      NIN = AST_GETI( ZOOMMAP, 'Nin', STATUS )
+\end{terminalv}
+\normalsize
+
+Here, the integer function AST\_GETI is used to extract the attribute
+value by giving it the ZoomMap pointer and the attribute name
+(attribute names are not case sensitive, but we have used consistent
+capitalisation in this document in order to identify them). Remember
+to use the AST\_PAR include file to save having to declare AST\_GETI
+as integer yourself.
+
+If we had wanted the value of the \htmlref{Zoom}{Zoom} attribute, we would probably
+have used AST\_GETD instead, this being a double precision version of
+the same function, for example:
+
+\small
+\begin{terminalv}
+      DOUBLE PRECISION ZOOM
+
+      ...
+
+      ZOOM = AST_GETD( ZOOMMAP, 'Zoom', STATUS )
+\end{terminalv}
+\normalsize
+
+However, we could equally well have read the Nin value as double
+precision, or the Zoom value as an integer, or whatever we wanted.
+
+The data type you want returned is specified simply by replacing the
+final character of the AST\_GETx function name with C~(character),
+D~(double precision), I~(integer), L~(logical) or R~(real). If
+possible, the value is converted to the type you want. If not, an
+error message will result. In converting from integer to logical, zero
+is regarded as .FALSE.\ and non-zero as .TRUE.. Note that all floating
+point values are stored internally as double precision. Boolean values
+are stored as integers, but only take the values 1 and 0 (for
+true/false).
+
+\subsection{\label{ss:settingattributes}Setting Attribute Values}
+
+Some attribute values are read-only and cannot be altered after an
+\htmlref{Object}{Object} has been created. The \htmlref{Nin}{Nin} attribute of a \htmlref{ZoomMap}{ZoomMap} (describing
+the number of coordinates) is like this. It is defined when the
+ZoomMap is created, but cannot then be altered.
+
+Other attributes, however, can be modified whenever you want. A
+ZoomMap's \htmlref{Zoom}{Zoom} attribute is like this. If we wanted to change it, this
+could be done simply as follows:
+
+\small
+\begin{terminalv}
+      CALL AST_SETD( ZOOMMAP, 'Zoom', 99.6D0, STATUS )
+\end{terminalv}
+\normalsize
+
+which sets the value to 99.6 (double precision). As when getting an
+attribute value (\secref{ss:gettingattributes}), you have a choice of
+which data type you will use to supply the new value. For instance,
+you could use an integer value, as in:
+
+\small
+\begin{terminalv}
+      CALL AST_SETI( ZOOMMAP, 'Zoom', 99, STATUS )
+\end{terminalv}
+\normalsize
+
+and the necessary data conversion would occur.  You specify the data
+type you want to supply simply by replacing the final character of the
+AST\_SETx routine name with C~(character), D~(double precision),
+I~(integer), L~(logical) or R~(real).  Setting a boolean attribute to
+any non-zero integer causes it to take the value 1.
+
+An alternative way of setting attribute values for Objects is to use
+the \htmlref{AST\_SET}{AST\_SET} routine (\emph{i.e.}\ with no final character specifying
+a data type). In this case, you supply the attribute values in a
+character string. The big advantage of this method is that you can
+assign values to several attributes at once, separating them with
+commas. This also reads more naturally in programs. For example:
+
+\small
+\begin{terminalv}
+      CALL AST_SET( ZOOMMAP, 'Zoom=99.6, Report=1', STATUS )
+\end{terminalv}
+\normalsize
+
+would set values for both the Zoom attribute and the \htmlref{Report}{Report} attribute
+(about which more shortly---\secref{ss:transforming}). You don't really
+have to worry about data types with this method, as any character
+representation will do (although you must use 0/1 instead of
+.TRUE./.FALSE., which are not supported). Note, when using AST\_SET, a
+literal comma may be included in an attribute value by enclosed the value in
+quotation marks:
+\small
+\begin{terminalv}
+      CALL AST_SET( SKYFRAME, 'SkyRef="12:13:32,-23:12:44"', STATUS )
+\end{terminalv}
+\normalsize
+
+\label{ss:attributeinitialisation}
+
+Finally, a very convenient way of setting attribute values is to do so
+at the same time as you create an Object. Every Object constructor
+function has a penultimate character argument which allows you to do
+this. Although you can simply leave this blank, it is an ideal
+opportunity to initialise the Object to have just the attributes you
+want. For example, we might have created our original ZoomMap with:
+
+\small
+\begin{terminalv}
+      ZOOMMAP = AST_ZOOMMAP( 2, 5.0D0, 'Report=1', STATUS )
+\end{terminalv}
+\normalsize
+
+and it would then start life with its Report attribute set to 1.
+
+\subsection{\label{ss:defaultingattributes}Testing, Clearing and Defaulting Attributes}
+
+You can use the AST\_GETx family of routines
+(\secref{ss:gettingattributes}) to get a value for any \htmlref{Object}{Object} attribute
+at any time, regardless of whether a value has previously been set for
+it. If no value has been set, the AST library will generate a suitable
+default value.
+
+Often, the default value of an attribute will not simply be trivial
+(zero or blank) but may involve considerable processing to
+calculate. Wherever possible, defaults are designed to be real-life,
+sensible values that convey information about the state of the
+Object. In particular, they may often be based on the values of other
+attributes, so their values may change in response to changes in these
+other attributes. The \htmlref{ZoomMap}{ZoomMap} class that we have studied so far is a
+little too simple to show this behaviour, but we will meet it later
+on.
+
+An attribute that returns a default value in this way is said to be
+\emph{un-set}. Conversely, once an explicit value has been assigned to
+an attribute, it becomes \emph{set} and will always return precisely
+that value, never a default.
+
+The distinction between set and un-set attributes is important and
+affects the behaviour of several key routines in the AST library. You
+can test if an attribute is set using the logical function \htmlref{AST\_TEST}{AST\_TEST},
+as in:
+
+\small
+\begin{terminalv}
+      IF ( AST_TEST( ZOOMMAP, 'Report', STATUS ) ) THEN
+         <the Report attribute is set>
+      END IF
+\end{terminalv}
+\normalsize
+
+(as usual, remember to include the AST\_PAR file to declare the
+function as LOGICAL, or make this declaration yourself).
+
+Once an attribute is set, you can return it to its un-set state using
+\htmlref{AST\_CLEAR}{AST\_CLEAR}. The effect is as if it had never been set in the first
+place. For example:
+
+\small
+\begin{terminalv}
+      CALL AST_CLEAR( ZOOMMAP, 'Report', STATUS )
+\end{terminalv}
+\normalsize
+
+would ensure that the default value of the \htmlref{Report}{Report} attribute is used
+subsequently.
+
+%\subsection{TBW--Handling Character Attributes}
+
+\subsection{\label{ss:transforming}Transforming Coordinates}
+
+We now have the necessary apparatus to start using our \htmlref{ZoomMap}{ZoomMap} to show
+what it is really for. Here, we will also encounter a routine that is
+a little more fussy about the type of pointer it will accept.
+
+The purpose of a ZoomMap is to multiply coordinates by a constant zoom
+factor. To witness this in action, we will first set the \htmlref{Report}{Report}
+attribute for our ZoomMap to a non-zero value:
+
+\small
+\begin{terminalv}
+      CALL AST_SET( ZOOMMAP, 'Report=1', STATUS )
+\end{terminalv}
+\normalsize
+
+This boolean (integer) attribute, which is present in all Mappings
+(and a ZoomMap is a \htmlref{Mapping}{Mapping}), causes the automatic display of all
+coordinate values that the Mapping converts. It is not a good idea to
+leave this feature turned on in a finished program, but it can save a
+lot of work during debugging.
+
+Our next step is to set up some coordinates for the ZoomMap to work
+on, using two arrays XIN and YIN, and two arrays to receive the
+transformed coordinates, XOUT and YOUT.  Note that these arrays are
+double precision, as are all coordinate data processed by the AST
+library:
+
+\small
+\begin{terminalv}
+      DOUBLE PRECISION XIN( 10 ), YIN( 10 ), XOUT( 10 ), YOUT( 10 )
+      DATA XIN / 0D0, 1D0, 2D0, 3D0, 4D0, 5D0, 6D0, 7D0, 8D0, 9D0 /
+      DATA YIN / 0D0, 2D0, 4D0, 6D0, 8D0, 10D0, 12D0, 14D0, 16D0, 18D0 /
+\end{terminalv}
+\normalsize
+
+We will now use the routine \htmlref{AST\_TRAN2}{AST\_TRAN2} to transform the input
+coordinates. This is the most commonly-used (2-dimensional) coordinate
+transformation routine. If you look at its description in
+\appref{ss:functiondescriptions}, you will see that it requires a
+pointer to a Mapping, so we cannot supply just any old \htmlref{Object}{Object} pointer,
+as we could with the routines discussed previously. If we passed it a
+pointer to an inappropriate Object, an error message would result.
+
+Fortunately, a ZoomMap is a Mapping (\appref{ss:classhierarchy}), so we
+can use it with AST\_TRAN2 to transform our coordinates, as follows:
+
+\small
+\begin{terminalv}
+      CALL AST_TRAN2( ZOOMMAP, 10, XIN, YIN, .TRUE., XOUT, YOUT, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, 10 is the number of points we want to transform and the fifth
+argument value of .TRUE.\ indicates that we want to transform in the
+\emph{forward} direction (from input to output).
+
+Because our ZoomMap's Report attribute is set to 1, this will cause
+the effects of the ZoomMap on the coordinates to be displayed on the
+standard output stream:
+
+\small
+\begin{terminalv}
+(0, 0) --> (0, 0)
+(1, 2) --> (5, 10)
+(2, 4) --> (10, 20)
+(3, 6) --> (15, 30)
+(4, 8) --> (20, 40)
+(5, 10) --> (25, 50)
+(6, 12) --> (30, 60)
+(7, 14) --> (35, 70)
+(8, 16) --> (40, 80)
+(9, 18) --> (45, 90)
+\end{terminalv}
+\normalsize
+
+This shows the coordinate values of each point both before and after
+the ZoomMap is applied. You can see that each coordinate value has
+been multiplied by the factor 5 determined by the \htmlref{Zoom}{Zoom} attribute
+value. The transformed coordinates are now stored in the XOUT and YOUT
+arrays.
+
+If we wanted to transform in the opposite direction, we need simply
+change the fifth argument of AST\_TRAN2 from .TRUE. to .FALSE.. We can
+also feed the output coordinates from the above back into the routine:
+
+\small
+\begin{terminalv}
+      CALL AST_TRAN2( ZOOMMAP, 10, XOUT, YOUT, .FALSE., XIN, YIN, STATUS )
+\end{terminalv}
+\normalsize
+
+The output would then look like:
+
+\small
+\begin{terminalv}
+(0, 0) --> (0, 0)
+(5, 10) --> (1, 2)
+(10, 20) --> (2, 4)
+(15, 30) --> (3, 6)
+(20, 40) --> (4, 8)
+(25, 50) --> (5, 10)
+(30, 60) --> (6, 12)
+(35, 70) --> (7, 14)
+(40, 80) --> (8, 16)
+(45, 90) --> (9, 18)
+\end{terminalv}
+\normalsize
+
+This is termed the \emph{inverse} transformation (we have converted
+from output to input) and you can see that the original coordinates
+have been recovered by dividing by the Zoom factor.
+
+\subsection{\label{ss:annullingpointers}Managing Object Pointers}
+
+So far, we have looked at creating Objects and using them in various
+simple ways but have not yet considered how to get rid of them again.
+
+Every \htmlref{Object}{Object} consumes various computer resources (principally memory)
+and should be disposed of when it is no longer required, so as to free
+up these resources. One way of doing this (not necessarily the
+best---\secref{ss:contexts}) is to \emph{annul} each Object pointer once
+you have finished with it, using \htmlref{AST\_ANNUL}{AST\_ANNUL}. For example:
+
+\small
+\begin{terminalv}
+      CALL AST_ANNUL( ZOOMMAP, STATUS )
+\end{terminalv}
+\normalsize
+
+This indicates that you have finished with the pointer and sets it to
+the null value AST\_\_NULL (as defined in the AST\_PAR include file),
+so that any attempt to use it again will generate an error message.
+
+In general, this process may not delete the Object, because there may
+still be other pointers associated with it. However, each Object
+maintains a count of the number of pointers associated with it and
+will be deleted if you annul the final pointer. Using AST\_ANNUL
+consistently will therefore ensure that all Objects are disposed of at
+the correct time. You can determine how many pointers are associated
+with an Object by examining its (read-only) \htmlref{RefCount}{RefCount} attribute.
+
+\subsection{\label{ss:contexts}AST Pointer Contexts---Begin and End}
+
+The use of \htmlref{AST\_ANNUL}{AST\_ANNUL} (\secref{ss:annullingpointers}) is not completely
+foolproof, however. Consider the following:
+
+\small
+\begin{terminalv}
+      CALL AST_SHOW( AST_ZOOMMAP( 2, 5.ODO, ' ', STATUS ), STATUS )
+\end{terminalv}
+\normalsize
+
+This creates a \htmlref{ZoomMap}{ZoomMap} and displays it on standard output
+(\secref{ss:displayingobjects}). Using function invocations as
+arguments to other routines in this way is very convenient because it
+avoids the need for intermediate pointer variables. However, the
+pointer generated by \htmlref{AST\_ZOOMMAP}{AST\_ZOOMMAP} is still active, and since we have
+not stored its value, we cannot use AST\_ANNUL to annul it. The
+ZoomMap will therefore stay around until the end of the program.
+
+A simple way to avoid this problem is to enclose all use of AST
+routines between calls to \htmlref{AST\_BEGIN}{AST\_BEGIN} and \htmlref{AST\_END}{AST\_END}, for example:
+
+\small
+\begin{terminalv}
+      CALL AST_BEGIN( STATUS )
+      CALL AST_SHOW( AST_ZOOMMAP( 2, 5.ODO, ' ', STATUS ), STATUS )
+      CALL AST_END( STATUS )
+\end{terminalv}
+\normalsize
+
+When the AST\_END call executes, every \htmlref{Object}{Object} pointer created since
+the previous AST\_BEGIN call is automatically annulled and any Objects
+left without pointers are deleted. This provides a simple solution to
+managing Objects and their pointers, and allows you to create Objects
+very freely without needing to keep detailed track of each one.
+Because this is so convenient, we implicitly assume that AST\_BEGIN
+and AST\_END are used in most of the examples given in this document.
+Pointer management is not generally shown explicitly unless it is
+particularly relevant to the point being illustrated.
+
+If necessary, calls to AST\_BEGIN and AST\_END may be nested, like
+\htmlref{IF}{IF}\ldots ENDIF blocks in Fortran, to define a series of AST pointer
+contexts. Each call to AST\_END will then annul only those Object
+pointers created since the matching call to AST\_BEGIN.
+
+\subsection{Exporting, Importing and Exempting AST Pointers}
+The \htmlref{AST\_EXPORT}{AST\_EXPORT} routine allows you to export particular pointers from
+one AST context (\secref{ss:contexts}) to the next outer one, as
+follows:
+
+\small
+\begin{terminalv}
+      CALL AST_EXPORT( ZOOMMAP, STATUS )
+\end{terminalv}
+\normalsize
+
+This would identify the pointer stored in ZOOMMAP as being required after
+the end of the current AST context. It causes any pointers nominated
+in this way to survive the next call to \htmlref{AST\_END}{AST\_END} (but only one such
+call) unscathed, so that they are available to the next outer context.
+This facility is not needed often, but is invaluable when the purpose
+of your \htmlref{AST\_BEGIN}{AST\_BEGIN}\ldots AST\_END block is basically to generate an
+\htmlref{Object}{Object} pointer. Without this, there is no way of getting that pointer
+out.
+
+The \htmlref{AST\_IMPORT}{AST\_IMPORT} routine can be used in a similar manner to import a
+pointer into the current context, so that it is deleted when the current
+context is closed using AST\_END.
+
+
+Sometimes, you may also want to exempt a pointer from all the effects
+of AST contexts. You should not need to do this often, but it will
+prove essential if you ever need to write a library of routines that
+stores AST pointers as part of its own internal data. Without some
+form of exemption, the caller of your routines could cause the
+pointers you have stored to be annulled---thus corrupting your
+internal data---simply by using AST\_END. To avoid this, you should
+use \htmlref{AST\_EXEMPT}{AST\_EXEMPT} on each pointer that you store, for example:
+
+\small
+\begin{terminalv}
+      CALL AST_EXEMPT( ZOOMMAP, STATUS )
+\end{terminalv}
+\normalsize
+
+This will prevent the pointer being affected by any subsequent use of
+AST\_END. Of course, it then becomes your responsibility to annul this
+pointer (using \htmlref{AST\_ANNUL}{AST\_ANNUL}) when it is no longer required.
+
+
+
+
+\subsection{\label{ss:copyingobjects}Copying Objects}
+
+The AST library makes extensive use of pointers, not only for
+accessing Objects directly, but also as a means of storing Objects
+inside other Objects (a number of classes of \htmlref{Object}{Object} are designed to
+hold collections of other Objects). Rather than copy an Object in its
+entirety, a pointer to the interior Object is simply stored in the
+enclosing Object.
+
+This means that Objects may frequently not be completely independent
+of each other because, for instance, they both contain pointers to the
+same sub-Object. In this situation, changing one Object (say assigning
+an attribute value) may affect the other one \emph{via} the common
+Object.
+
+It is difficult to describe all cases where this may happen, so you
+should always be alert to the possibility. Fortunately, there is a
+simple solution. If you require two Objects to be independent, then
+simply use \htmlref{AST\_COPY}{AST\_COPY} to make a copy of one, \emph{e.g.}:
+
+\small
+\begin{terminalv}
+      INTEGER ZOOMMAP1, ZOOMMAP2
+
+      ...
+
+      ZOOMMAP2 = AST_COPY( ZOOMMAP1, STATUS )
+\end{terminalv}
+\normalsize
+
+This process will create a true copy of any Object and return a
+pointer to the copy. This copy will not contain any pointers to any
+component of the original Object (everything is duplicated), so you
+can then modify it safely, without fear of affecting either the
+original or any other Object.
+
+%\subsection{TBW - Inheritance}
+
+
+\subsection{\label{ss:errordetection}Error Detection}
+
+If an error occurs in an AST routine (for example, if you supply an
+invalid argument, such as a pointer to the wrong class of \htmlref{Object}{Object}), an
+error message will be written to the standard error stream and the
+function will immediately return.
+
+To indicate that an error has occurred, each AST routine that can
+potentially fail has a final integer \emph{error status} argument
+called STATUS.  This is both an input and an output argument.
+Normally, you should declare a single error status variable and pass
+it as the STATUS argument to every AST routine you invoke.  This
+variable must initially be cleared (\emph{i.e.}\ set to
+zero\footnote{We will assume throughout that the ``OK'' value is zero,
+as it currently is. However, a different value could, in principle, be
+used if the environment in which AST is running requires it. To allow
+for this possibility, you might prefer to use a parameter constant to
+represent the value zero when testing for errors.} to indicate no
+error).  If an error occurs, the STATUS argument is returned set to a
+different \emph{error value}, which allows you to detect the error, as
+follows:
+
+\small
+\begin{terminalv}
+      STATUS = 0
+
+      ...
+
+      ZOOMMAP = AST_ZOOMMAP( 2, 5.0D0, 'Title=My ZoomMap', STATUS )
+      IF ( STATUS .NE. 0 ) THEN
+         <an error has occurred>
+      END IF
+\end{terminalv}
+\normalsize
+
+In this example, an error would be detected because we have attempted
+to set a value for the \htmlref{Title}{Title} attribute of a \htmlref{ZoomMap}{ZoomMap} and a ZoomMap does
+not have such an attribute.
+
+A consequence of the error status variable STATUS being set to an
+error value is that almost all AST routines will subsequently cease to
+function and will instead simply return without action.  This means
+that you do not need to check for errors very frequently. Instead, you
+can usually simply invoke a succession of AST routines. If an error
+occurs in any of them, the following ones will do nothing and you can
+check for the error at the end, for example:
+
+\small
+\begin{terminalv}
+      STATUS = 0
+
+      ...
+
+      CALL AST_ROUTINEA( ... , STATUS )
+      CALL AST_ROUTINEB( ... , STATUS )
+      CALL AST_ROUTINEC( ... , STATUS )
+      IF ( STATUS .NE. 0 ) THEN
+         <an error has occurred>
+      END IF
+\end{terminalv}
+\normalsize
+
+There are, however, a few routines which do not adhere to this general
+rule and which will attempt to execute if their STATUS argument is
+initially set.  These routines, such as \htmlref{AST\_ANNUL}{AST\_ANNUL}, are concerned with
+cleaning up and recovering resources. For example, in the following:
+
+\small
+\begin{terminalv}
+      STATUS = 0
+
+      ...
+
+      ZOOMMAP = AST_ZOOMMAP( 2, 5.0D0, ' ', STATUS )
+
+      CALL AST_ROUTINEX( ... , STATUS )
+      CALL AST_ROUTINEY( ... , STATUS )
+      CALL AST_ROUTINEZ( ... , STATUS )
+
+      CALL AST_ANNUL( ZOOMMAP, STATUS )
+      IF ( STATUS .NE. 0 ) THEN
+         <an error has occurred>
+      END IF
+\end{terminalv}
+\normalsize
+
+AST\_ANNUL will execute normally in order to recover the resources
+associated with the ZoomMap that was created earlier, regardless of
+whether an error has occurred in any of the intermediate routines.
+Routines which behave in this way are noted in the relevant
+descriptions in \appref{ss:functiondescriptions}.
+
+If a serious error occurs, you will probably want to abort your
+program, but sometimes you may want to recover and carry on.  This is
+simply done by resetting your error status variable to zero, whereupon
+the AST routines you pass it to will execute normally again.
+
+
+
+
+
+
+
+\cleardoublepage
+\section{\label{ss:mappings}Inter-Relating Coordinate Systems (Mappings)}
+
+In \secref{ss:primer} we used the \htmlref{ZoomMap}{ZoomMap} as an example of a
+\htmlref{Mapping}{Mapping}. We saw how it could be used to transform coordinates from its
+input to its output and back again (\secref{ss:transforming}). We also
+saw how its behaviour could be controlled by setting various
+attributes, such as the \htmlref{Zoom}{Zoom} factor and the \htmlref{Report}{Report} attribute that made
+it display coordinate values as it transformed them.
+
+In this section, we will look at Mappings a bit more thoroughly and
+explore the behaviour which is common to all the Mappings provided by
+AST.  This is good background for what follows, because many of the
+Objects we discuss later will also turn out to be Mappings in various
+disguises.
+
+\subsection{\label{ss:mappingclass}The Mapping Class}
+
+Before we start, it is worth taking a quick look at the \htmlref{Mapping}{Mapping} class
+as a whole and some of the sub-classes it contains:
+
+\begin{terminalv}
+   Mapping
+      CmpMap
+      DssMap
+      GrismMap
+      IntraMap
+      LutMap
+      MathMap
+      MatrixMap
+      PermMap
+      PolyMap
+      SlaMap
+      SpecMap
+      TimeMap
+      UnitMap
+      WcsMap
+      ZoomMap
+
+      Frame
+         <various types of Frame>
+\end{terminalv}
+
+The \htmlref{Frame}{Frame} sub-class has been separated out here because it is covered
+in detail in \secref{ss:frames}. We start by looking at the parent
+class, Mapping.
+
+AST does not provide a function to create a basic Mapping
+(\emph{i.e.}\ the AST\_MAPPING constructor does not exist). This is
+because the Mapping class itself is ``virtual'' and basic Mappings are
+of no use in themselves. The Mapping class serves simply to contain
+the various specialised Mappings that exist.
+However, it provides more than just a convenient heading for them
+because it bestows all classes of Mapping with common properties
+(\emph{e.g.}\ attributes) and behaviour.  By examining the Mapping
+class, we are therefore examining the things that all other Mappings
+have in common.
+
+\subsection{The Mapping Model}
+
+The concept of a \htmlref{Mapping}{Mapping} was illustrated in Figure~\ref{fig:mapping}.
+It is a black box which you can supply with a set of coordinate values
+in return for a set of transformed coordinates. The two sets are
+termed \emph{input} and \emph{output} coordinates. You can also go
+back the other way and transform output coordinates back into input
+coordinates, as we saw in \secref{ss:transforming}.
+
+\subsection{Changing Attributes of a Mapping}
+
+Many classes of \htmlref{Mapping}{Mapping} have attributes that provide values for parameter
+used within the transformation. For instance, the \htmlref{ZoomMap}{ZoomMap} class has an
+attribute called ``\htmlref{Zoom}{Zoom}'' that gives the scalar value by which each
+coordinate is to be multiplied. These attribute values should be set when
+the Mapping is created and should not be changed afterwards. Indeed, the
+AST library will report an error if an attempt is made to change the
+value of a Mapping attribute. This is because, once created, Mappings are
+often later included within other objects such as FrameSets and CmpMaps.
+This means that in general there could be many active references to a single
+Mapping object within a program. Changing an attribute of the Mapping
+via one particular reference (i.e pointer) would cause all the other
+references to change too, with often undesirable or unpredictable
+consequences. To avoid this, Mappings are considered \emph{immutable} in
+most situations. The one exception is if the Mapping has not yet been
+cloned or included in another \htmlref{Object}{Object} (\emph{i.e.} it has a reference
+couint of one) - changing the attributes of such a Mapping is allowed,
+and will not generate an error.
+
+Note, the \htmlref{Invert}{Invert} attribute of a Mapping is not subject to this rule and
+can be changed at any time.
+
+\subsection{Input and Output Coordinate Numbers}
+
+In general, the number of coordinates you feed into a \htmlref{Mapping}{Mapping} to
+represent a single point need not be the same as the number that comes
+out. Often these numbers will be the same, and often they will both
+equal 2 (because 2-dimensional coordinate systems are common), but
+this needn't necessarily be the case.
+
+The number of coordinates required to specify an input point is
+represented by the integer attribute \htmlref{Nin}{Nin} and the number required to
+specify an output point is represented by \htmlref{Nout}{Nout}. These are read-only
+attributes common to all Mappings. Generally, their values are fixed
+when a Mapping is created.
+
+In \secref{ss:objectcreation}, we saw how the Nin attribute for a
+\htmlref{ZoomMap}{ZoomMap} was initialised by the call to the constructor function
+\htmlref{AST\_ZOOMMAP}{AST\_ZOOMMAP} which created it. In this case, the Nout attribute was
+not needed and it implicitly took the same value as Nout, but we could
+have enquired about its value had we wanted, as follows:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER NOUT, STATUS, ZOOMMAP
+
+      STATUS = 0
+
+      ...
+
+      NOUT = AST_GETI( ZOOMMAP, 'Nout', STATUS )
+\end{terminalv}
+\normalsize
+
+\subsection{Forward and Inverse Transformations}
+
+We stated earlier that a \htmlref{Mapping}{Mapping} may be used to transform coordinates
+either from input to output, or \emph{vice versa}. These are termed
+its \emph{forward} and \emph{inverse} transformations.
+
+This statement was not quite accurate, however, because in general
+Mappings are only \textbf{potentially} capable of working in both
+directions. In practice, coordinate transformation may only be
+feasible in one direction or the other because some functions are not
+easily inverted (they may be multi-valued, for instance). Allowance
+must be made for this, so each Mapping has two read-only boolean
+(integer) attributes, \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse}, which indicate
+whether each transformation is available.
+
+A transformation is available if the corresponding attribute is
+non-zero, otherwise it is not.\footnote{Most of the Mappings provided
+by the AST library work in both directions, although the \htmlref{LutMap}{LutMap} can
+behave otherwise.} If you enquire about the value of these attributes,
+a value of 0 or 1 is returned.  Attempting to use a Mapping to apply a
+transformation which is not available will result in an error.
+
+\subsection{\label{ss:invertingmappings}Inverting Mappings}
+
+An important attribute, common to all Mappings, is the \htmlref{Invert}{Invert}
+flag. This is a boolean (integer) attribute that can be assigned a new
+value at any time. If it is non-zero, it has the effect of
+interchanging the \htmlref{Mapping}{Mapping}'s input and output coordinates and the
+Mapping is then said to be \emph{inverted}. By default, the Invert
+attribute is zero.
+
+There is no magic in this. There is no fancy arithmetic involved in
+inverting mathematical functions, for instance. The Invert flag is
+simply a switch that interchanges a Mapping's input and output
+ports. If it is non-zero, the Mapping's \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes are
+swapped, its \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse} attributes are swapped, and
+when you ask for what was once the forward transformation you get the
+inverse transformation instead (and \emph{vice versa}). When you
+return the Invert attribute to zero, or clear it, the Mapping returns
+to its original behaviour.
+
+Often, the actual value of the Invert attribute is unimportant and you
+simply wish to invert its boolean sense, so that what was the
+Mapping's input becomes its output and \emph{vice versa}. This is most
+easily accomplished using \htmlref{AST\_INVERT}{AST\_INVERT}, as follows:
+
+\small
+\begin{terminalv}
+      INTEGER MAPPING
+
+      ...
+
+      CALL AST_INVERT( MAPPING, STATUS )
+\end{terminalv}
+\normalsize
+
+If the Mapping you have happens to be the wrong way around,
+AST\_INVERT allows you to correct the problem.
+
+\subsection{Finding the Rate of Change of a Mapping Output}
+The
+\htmlref{AST\_RATE}{AST\_RATE}
+function can be used to find the rate of change of any \htmlref{Mapping}{Mapping} output
+with respect to any Mapping input, at a given input position. The method
+used produces good accuracy (typically a relative error of 10E-10 or
+less) but may require the Mapping to be evaluated 100 or more times.
+An estimate of the second derivative is also produced by this function.
+
+
+\subsection{Reporting Coordinate Transformations}
+
+We have already seen (\secref{ss:transforming}) how the boolean
+(integer) \htmlref{Report}{Report} attribute of a \htmlref{Mapping}{Mapping} works. If it is non-zero, the
+operation of transforming a set of coordinates will result in a report
+being written to standard output. This will display the coordinate
+values before and after transformation. It can save considerable time
+during program development by eliminating the need to add loops and
+output statements to your program.
+
+In a finished program, however, you should be careful that the Report
+attribute is not set to a non-zero value unless you want to see the
+output (there may often be rather a lot of this!). To help prevent
+unwanted output being produced by accident, the Report attribute is
+unusual in that its value is not preserved when a Mapping is copied
+using \htmlref{AST\_COPY}{AST\_COPY} (\secref{ss:copyingobjects}). Instead, it reverts to
+its default of zero (\emph{i.e.}\ un-set) in the copy. It also reverts
+to zero when a Mapping is written out, \emph{e.g.}\ to a file using a
+\htmlref{Channel}{Channel} (\secref{ss:channels}).
+
+%\subsection{TBW---More on Transforming Coordinates}
+
+\subsection{\label{ss:badcoordinates}Handling Missing (Bad) Coordinate Values}
+
+Even when coordinates can, in principle, be transformed in either
+direction by a \htmlref{Mapping}{Mapping}, there may still be instances where specific
+coordinate values cannot be handled. For example, the Mapping may be
+mathematically intractable (\emph{e.g.}\ singular) in certain places,
+or it may map a subset of one space on to another, so that some points
+in one space are not represented in the other.  Sky projections often
+show this behaviour, since it is quite common to project only half of
+the celestial sphere on to two dimensions, omitting points on the
+opposite side of the sky. There are many other examples.
+
+To indicate when coordinates cannot be transformed, for whatever
+reason, AST substitutes a special output coordinate value given by the
+parameter constant AST\_\_BAD (as defined in the AST\_PAR include
+file).  Before making use of coordinates generated by any of the AST
+transformation routines, therefore, you may need to check for the
+presence of this value.
+
+Because coordinates with the value AST\_\_BAD can be generated in this
+way, all other AST routines are also capable of recognising this value
+and handling it appropriately. The coordinate transformation routines
+do this by propagating any missing input coordinate information
+through to their output.  This means that if you supply coordinates
+with the value AST\_\_BAD, the returned coordinates are also likely to
+contain this value. Here, for example, is what happens if you use a
+\htmlref{ZoomMap}{ZoomMap} (with \htmlref{Zoom}{Zoom} factor 5) to transform such a set of coordinates:
+
+\small
+\begin{terminalv}
+(0, 0) --> (0, 0)
+(<bad>, 2) --> (<bad>, 10)
+(2, 4) --> (10, 20)
+(3, 6) --> (15, 30)
+(4, <bad>) --> (20, <bad>)
+(5, 10) --> (25, 50)
+(<bad>, <bad>) --> (<bad>, <bad>)
+(7, 14) --> (35, 70)
+(8, 16) --> (40, 80)
+(9, 18) --> (45, 90)
+\end{terminalv}
+\normalsize
+
+The AST\_\_BAD value is represented by the string ``$<$bad$>$''. This
+is a case of ``garbage in, garbage out'' but at least it's consistent
+garbage that you can recognise!
+
+Note how the presence of the AST\_\_BAD value in one input dimension
+does not necessarily result in the loss of information for all output
+dimensions. Sometimes, such loss will be unavoidable, but in general
+an attempt is made to preserve information as far as possible. The
+exact behaviour will depend on the Mapping involved.
+
+\subsection{\label{ss:unitmapexample}Example---the UnitMap}
+
+The \htmlref{UnitMap}{UnitMap} is the simplest of Mappings. It is a null \htmlref{Mapping}{Mapping}. Its
+purpose is simply to copy coordinate values, unaltered, from its input
+to its output and \emph{vice versa}.
+
+A UnitMap has no additional attributes beyond those of a basic
+Mapping. Its \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes are always equal and are
+specified by the first argument supplied to its constructor. For
+example:
+
+\small
+\begin{terminalv}
+      INTEGER UNITMAP
+
+      ...
+
+      UNITMAP = AST_UNITMAP( 2, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+will create a UnitMap that copies 2-dimensional coordinates. Inverting
+a UnitMap has no effect beyond changing the value of its \htmlref{Invert}{Invert}
+attribute.
+
+The main use of a UnitMap is to allow a Mapping to be supplied when one
+is required (as an argument to a routine, for example) but you wish
+it to leave coordinate values unchanged.
+
+\subsection{\label{ss:permmapexample}Example---the PermMap}
+
+The \htmlref{PermMap}{PermMap} is a rather more complicated \htmlref{Mapping}{Mapping} than we have met
+previously.  Its purpose is to change the order, or number, of
+coordinates. It is also able to substitute fixed values for
+coordinates.
+
+To illustrate its action, suppose our input coordinates are denoted by
+($x_1,x_2,x_3,x_4$) in a 4-dimensional space and suppose our output
+coordinates are to be ($x_4,x_1,x_2,x_3$). Our PermMap, therefore,
+should rotate the coordinate values by one position.
+
+To create such a PermMap, we first set up two integer arrays. One of
+these, OUTPERM, controls the selection of input coordinates for use in
+the output and the other, INPERM, controls selection of output
+coordinates for use in the input:
+
+\small
+\begin{terminalv}
+      INTEGER OUTPERM( 4 ), INPERM( 4 )
+      DATA OUTPERM / 4, 1, 2, 3 /
+      DATA INPERM / 2, 3, 4, 1 /
+\end{terminalv}
+\normalsize
+
+Note that the numbers we store in these arrays are the indices of the
+coordinates that we want to select. We have chosen these so that the
+forward and inverse transformations will perform complementary
+permutations on the coordinates.
+
+The PermMap is then created by passing these arrays to its
+constructor, as follows:
+
+\small
+\begin{terminalv}
+      INTEGER PERMMAP
+      DOUBLE PRECISION DUMMY( 1 )
+
+      ...
+
+      PERMMAP = AST_PERMMAP( 4, INPERM, 4, OUTPERM, DUMMY, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+(the fifth argument is not being used, so a dummy array has been supplied).
+Note that we specify the number of input and output coordinates
+separately, but set both to 4 in this example. The resulting PermMap
+would have the following effect when used to transform coordinates:
+
+\begin{terminalv}
+Forward:
+   (1, 2, 3, 4) --> (4, 1, 2, 3)
+   (2, 4, 6, 8) --> (8, 2, 4, 6)
+   (3, 6, 9, 12) --> (12, 3, 6, 9)
+   (4, 8, 12, 16) --> (16, 4, 8, 12)
+   (5, 10, 15, 20) --> (20, 5, 10, 15)
+
+Inverse:
+   (4, 1, 2, 3) --> (1, 2, 3, 4)
+   (8, 2, 4, 6) --> (2, 4, 6, 8)
+   (12, 3, 6, 9) --> (3, 6, 9, 12)
+   (16, 4, 8, 12) --> (4, 8, 12, 16)
+   (20, 5, 10, 15) --> (5, 10, 15, 20)
+\end{terminalv}
+
+If the number of input and output coordinates are unequal so, also,
+will be the size of the OUTPERM and INPERM arrays. This means,
+however, that we cannot fill them with coordinate indices so that they
+perform complementary permutations, because one transformation will
+lose information (discard a coordinate) that the other cannot recover.
+To give an example, consider the following:
+
+\small
+\begin{terminalv}
+      INTEGER OUTPERM( 3 ), INPERM( 4 )
+      DOUBLE PRECISION CONST( 1 )
+      DATA OUTPERM / 4, 3, 2 /
+      DATA INPERM / -1, 3, 2, 1 /
+      DATA CONST / 99.004D0 /
+\end{terminalv}
+\normalsize
+
+In this case, the forward transformation will change
+($x_1,x_2,x_3,x_4$) into ($x_4,x_3,x_2$) and will discard $x_1$. The
+inverse transformation restores the original coordinate order, but has
+no value to assign to the first coordinate. In this case, the number
+entered in the INPERM array is $-$1.
+
+This negative value indicates that the coordinate value should be
+obtained by addressing the CONST array using an index of 1 (the
+absolute value). This array, ignored in the previous example, may then
+be used to supply a value for the missing coordinate.
+
+The constructor function:
+
+\small
+\begin{terminalv}
+      PERMMAP = AST_PERMMAP( 4, INPERM, 3, OUTPERM, CONST, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+will then create a PermMap with the following effect when used to
+transform coordinates:
+
+\begin{terminalv}
+Forward:
+   (1, 2, 3, 4) --> (4, 3, 2)
+   (2, 4, 6, 8) --> (8, 6, 4)
+   (3, 6, 9, 12) --> (12, 9, 6)
+   (4, 8, 12, 16) --> (16, 12, 8)
+   (5, 10, 15, 20) --> (20, 15, 10)
+
+Inverse:
+   (4, 3, 2) --> (99.004, 2, 3, 4)
+   (8, 6, 4) --> (99.004, 4, 6, 8)
+   (12, 9, 6) --> (99.004, 6, 9, 12)
+   (16, 12, 8) --> (99.004, 8, 12, 16)
+   (20, 15, 10) --> (99.004, 10, 15, 20)
+\end{terminalv}
+
+The CONST array may contain more than one value if necessary and may
+be addressed by both the INPERM and OUTPERM arrays using coordinate
+indices $-$1, $-$2, $-$3,~\emph{etc.}\ to refer to the first, second,
+third,~\emph{etc.}\ elements.
+
+If there is no suitable replacement value that can be supplied
+\emph{via} the CONST array, a value of zero may be entered into the
+OUTPERM and/or INPERM arrays. This causes the value AST\_\_BAD to be
+used for the affected coordinate (as defined in the AST\_PAR include
+file), thus indicating a missing coordinate value
+(\secref{ss:badcoordinates}).
+
+The principle use for a PermMap lies in matching a coordinate system
+to a data array where there is a choice of storage order for the data.
+PermMaps are also useful for discarding unwanted coordinates so as to
+reduce the number of dimensions, such as when selecting a ``slice''
+from a multi-dimensional array.
+
+\cleardoublepage
+\section{\label{ss:cmpmaps}Compound Mappings (CmpMaps)}
+
+We now turn to a rather special form of \htmlref{Mapping}{Mapping}, the \htmlref{CmpMap}{CmpMap}. The
+Mappings we have considered so far have been atomic, in the sense that
+they perform pre-defined elementary transformations. A CmpMap,
+however, is a compound Mapping. In essence, it is a framework for
+containing other Mappings and its purpose is to allow those Mappings
+to work together in various combinations while appearing as a single
+\htmlref{Object}{Object}. A CmpMap's behaviour is therefore not pre-defined, but is
+determined by the other Mappings it contains.
+
+\subsection{\label{ss:seriescmpmap}Combining Mappings in Series}
+
+Consider a simple example based on two 2-dimensional coordinate
+systems. Suppose that to convert from one to the other we must swap
+the coordinate order and multiply both coordinates by 5, so that the
+coordinates ($x_1,x_2$) transform into ($5x_2,5x_1$). This can be done
+in two stages:
+
+\begin{enumerate}
+\item Apply a \htmlref{PermMap}{PermMap} (\secref{ss:permmapexample}) to swap the
+coordinate order.
+
+\item Apply a \htmlref{ZoomMap}{ZoomMap} (\secref{ss:transforming}) to multiply both
+coordinate values by the constant 5.
+\end{enumerate}
+
+The PermMap and ZoomMap are then said to operate \emph{in series},
+because they are applied sequentially
+(\emph{c.f.}\ Figure~\ref{fig:seriescmpmap}).  We can create a \htmlref{CmpMap}{CmpMap}
+that applies these Mappings in series as follows:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER CMPMAP, PERMMAP, STATUS, ZOOMMAP
+      INTEGER INPERM( 2 ), OUTPERM( 2 ), CONST( 1 )
+      DATA INPERM / 1, 2 /
+      DATA OUTPERM / 1, 2 /
+
+      STATUS = 0
+
+      ...
+
+*  Create the individual Mappings.
+      PERMMAP = AST_PERMMAP( 2, INPERM, 2, OUTPERM, CONST, ' ', STATUS )
+      ZOOMMAP = AST_ZOOMMAP( 2, 5.0D0, ' ', STATUS )
+
+*  Combine them in series.
+      CMPMAP = AST_CMPMAP( PERMMAP, ZOOMMAP, .TRUE., ' ', STATUS )
+
+*  Annul the individual Mapping pointers.
+      CALL AST_ANNUL( PERMMAP, STATUS )
+      CALL AST_ANNUL( ZOOMMAP, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, the third argument (.TRUE.) of the constructor function
+\htmlref{AST\_CMPMAP}{AST\_CMPMAP} indicates ``in series''.
+
+When used to transform coordinates in the forward direction, the
+resulting CmpMap will apply the first component \htmlref{Mapping}{Mapping} (the PermMap)
+and then the second one (the ZoomMap). When transforming in the
+inverse direction, it will apply the second one (in the inverse
+direction) and then the first one (also in the inverse direction).  In
+general, although not in this particular example, the order in which
+the two component Mappings are supplied is significant. Clearly, also,
+the \htmlref{Nout}{Nout} attribute (number of output coordinates) for the first
+Mapping must equal the \htmlref{Nin}{Nin} attribute (number of input coordinates) for
+the second one.
+
+\subsection{Combining Mappings in Parallel}
+
+Connecting two Mappings in series (\secref{ss:seriescmpmap}) is not the
+only way of combining them. The alternative, \emph{in parallel},
+involves applying the two Mappings at once but on different subsets of
+the coordinate values.
+
+Consider, for example, a set of 3-dimensional coordinates and suppose
+we wish to transform them by swapping the first two coordinate values
+and multiplying the final one by 5, so that ($x_1,x_2,x_3$) transforms
+into ($x_2,x_1,5x_3$). Again, we can perform each of these steps
+individually using Mappings similar to the \htmlref{PermMap}{PermMap} and \htmlref{ZoomMap}{ZoomMap} used
+earlier (\secref{ss:seriescmpmap}). In this case, however, the ZoomMap is
+1-dimensional and the individual Mappings are applied in parallel
+(\emph{c.f.}\ Figure~\ref{fig:parallelcmpmap}).
+
+Creating a \htmlref{CmpMap}{CmpMap} for this purpose is also very simple:
+
+\small
+\begin{terminalv}
+      CMPMAP = AST_CMPMAP( PERMMAP, ZOOMMAP, .FALSE., ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+The only difference is that the third argument of \htmlref{AST\_CMPMAP}{AST\_CMPMAP} is now
+.FALSE., meaning ``in parallel''.
+
+As before, the order in which the two component Mappings are supplied
+is significant. The first one acts on the lower-numbered input
+coordinate values (however many it needs) and produces the
+lower-numbered output coordinates, while the second \htmlref{Mapping}{Mapping} acts on
+the higher-numbered input coordinates (however many remain) and
+generates the remaining higher-numbered output coordinates.  When the
+CmpMap transforms coordinates in the inverse direction, both component
+Mappings are applied to the same coordinates, but in the inverse
+direction.
+
+Note that the \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes of the component Mappings
+(\emph{i.e.}\ the numbers of input and output coordinates) will sum to
+give the Nin and Nout attributes of the overall CmpMap.
+
+\subsection{\label{ss:cmpmapcomponents}The Component Mappings}
+
+A \htmlref{CmpMap}{CmpMap} does not store copies of its component Mappings, but simply
+holds pointers to them. In th example above (\secref{ss:seriescmpmap}),
+we were free to annul the individual \htmlref{Mapping}{Mapping} pointers after creating
+the CmpMap because the pointers held internally by the CmpMap
+increased the reference count (\htmlref{RefCount}{RefCount} attribute) of each component
+Mapping by one. The individual components are therefore not deleted by
+\htmlref{AST\_ANNUL}{AST\_ANNUL}, but retained until the CmpMap itself is deleted and annuls
+the pointers it holds. Consistent use of AST\_ANNUL
+(\secref{ss:annullingpointers}) and/or pointer contexts
+(\secref{ss:contexts}) will therefore ensure that all Objects are
+deleted at the appropriate time.
+
+Note that access to a CmpMap's component Mappings is not generally
+available unless pointers to them are retained when the CmpMap is
+created. If such pointers are retained, then subsequent modifications
+to the individual components can be used to indirectly modify the
+behaviour of the overall CmpMap.
+
+There is an important exception to this, however, because a CmpMap
+retains a copy of the initial \htmlref{Invert}{Invert} flag settings of each of its
+components and uses these in order to ignore any subsequent external
+changes. This means that you may invert either component Mapping
+before inserting it into a CmpMap and need not worry if you un-invert
+it again later. The CmpMap's behaviour will not be affected by the
+later action.
+
+\subsection{\label{ss:complexcmpmap}Creating More Complex Mappings}
+
+Because a \htmlref{CmpMap}{CmpMap} is itself a \htmlref{Mapping}{Mapping}, any existing CmpMap can
+substitute (\secref{ss:objecthierarchy}) as a component Mapping when
+constructing a new CmpMap using \htmlref{AST\_CMPMAP}{AST\_CMPMAP}. This has the effect of
+nesting one CmpMap inside another and opens up many new possibilities.
+For example, combining three Mappings in series can be accomplished as
+follows:
+
+\small
+\begin{terminalv}
+      INTEGER MAP1, MAP2, MAP3
+
+      ...
+
+      CMPMAP = AST_CMPMAP( MAP1, AST_CMPMAP( MAP2, MAP3, .TRUE., ' ', STATUS ),
+     :                     .TRUE., ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+The way in which the individual component Mappings are grouped within
+the nested CmpMaps is not usually important.
+
+A similar technique can be used to combine multiple Mappings in
+parallel and, of course, mixed series and parallel combinations are
+also possible (Figure~\ref{fig:complexcmpmap}).  There is no built-in
+limit to how many CmpMaps may be nested in this way, so this mechanism
+provides an indefinitely extensible method of building complex
+Mappings out of the elemental building blocks provided by AST.
+
+In practice, you might not need to construct such complex CmpMaps
+yourself very frequently, but they will often be returned by AST
+routines.  Nested CmpMaps underlie the library's entire ability to
+represent a wide range of different coordinate transformations.
+
+\subsection{\label{ss:cmpmapexample}Example---Transforming Between Two Calibrated Images}
+
+Consider, as a practical example of CmpMaps, two images of the
+sky. Suppose that for each image we have a \htmlref{Mapping}{Mapping} which converts from
+pixel coordinates to a standard celestial coordinate system, say
+FK5~(J2000.0). If we wish to inter-compare these images, we can do so
+by using this celestial coordinate system to align them. That is, we
+first convert from pixel coordinates in the first image into FK5
+coordinates and we then convert from FK5 coordinates into pixel
+coordinates in the second image.
+
+If MAPA and MAPB are pointers to our two original Mappings, we could
+form a \htmlref{CmpMap}{CmpMap} which transforms directly between the pixel coordinates
+of the first and second images by combining these Mappings, as
+follows:
+
+\small
+\begin{terminalv}
+      INTEGER ALIGNMAP, MAPA, MAPB
+
+      ...
+
+      CALL AST_INVERT( MAPB, STATUS )
+      ALIGNMAP = AST_CMPMAP( MAPA, MAPB, .TRUE., ' ', STATUS )
+      CALL AST_INVERT( MAPB, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, we have used \htmlref{AST\_INVERT}{AST\_INVERT} (\secref{ss:invertingmappings}) to
+invert MAPB before inserting it into the CmpMap because, as supplied,
+it converted in the wrong direction. Afterwards, we invert it again to
+return it to its original state. The CmpMap, however, will ignore this
+subsequent change (\secref{ss:cmpmapcomponents}).
+
+The forward transformation of the resulting CmpMap will now transform
+from pixel coordinates in the first image to pixel coordinates in the
+second image, while its inverse transformation will convert in the
+opposite direction.
+
+\subsection{\label{ss:overcomplexcmpmaps}Over-Complex Compound Mappings}
+
+While a \htmlref{CmpMap}{CmpMap} provides a very flexible way of constructing
+arbitrarily complex Mappings (\secref{ss:complexcmpmap}), it
+unfortunately also provides an opportunity for representing simple
+Mappings in complex ways. Sometimes, unnecessary complexity can be
+difficult to avoid but can obscure important simplifications.
+
+Consider the example above (\secref{ss:cmpmapexample}), in which we
+inter-related two images of the sky \emph{via} a CmpMap.  If the two
+images turned out to be simply offset from each other by a shift along
+each pixel axis, then this approach would align them correctly, but it
+would be inefficient. This is because it would introduce unnecessary
+and expensive transformations to and from an intermediate celestial
+coordinate system, whereas a simple shift of pixel origin would
+suffice.
+
+Recognising that a simpler and more efficient solution exists
+obviously requires a little more than simply joining two Mappings
+end-to-end. We must also determine whether the resulting CmpMap is
+more complex than it needs to be, \emph{i.e.}\ contains redundant
+information. If it is, we then need a way to simplify it.
+
+The problem is not always just one of efficiency, however. Sometimes
+we may also need to know something about the actual form a \htmlref{Mapping}{Mapping}
+takes---\emph{i.e.}\ the nature of the operations it performs.
+Unnecessary complexity can obscure this, but such complexity can
+easily accumulate during normal data processing.
+
+For example, a Mapping that transforms pixel coordinates into
+positions on the sky might be repeatedly modified as changes are made
+to the shape and size of the image. Typically, on each occasion,
+another Mapping will be concatenated to reflect what has happened to
+the image. This could soon make it difficult to discern the overall
+nature of the transformation from the complex CmpMap that
+accumulates. If only shifts of origin were involved on each occasion,
+however, they could be combined into a single shift which could be
+represented much more simply.
+
+Suppose we now wanted to represent our image's celestial coordinate
+calibration using FITS conventions (\secref{ss:foreignfits}). This
+requires AST to determine whether the Mapping which relates pixel
+coordinate to sky positions conforms to the FITS model (for example,
+whether it is equivalent to applying a single set of shifts and scale
+factors followed by a map projection). Clearly, there is an important
+use here for some means of simplifying the internal structure of a
+CmpMap.
+
+\subsection{\label{ss:simplifyingcmpmaps}Simplifying Compound Mappings}
+
+The ability to simplify compound Mappings is provided by the
+\htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY} function. This function encapsulates a number of
+heuristics for converting Mappings, or combinations of Mappings within
+a \htmlref{CmpMap}{CmpMap}, into simpler, equivalent ones. When applied to a CmpMap,
+AST\_SIMPLIFY tries to reduce the number of individual Mappings within
+it by merging neighbouring component Mappings together. It will do
+this with both series and parallel combinations of Mappings, or both,
+and will handle CmpMaps nested to any depth
+(\secref{ss:complexcmpmap}).
+
+   To illustrate how AST\_SIMPLIFY works, consider the combination of
+   Mappings shown in Figure~\ref{fig:simplifyexample}.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun210_figures/simpexamp}
+   \caption[An over-complex compound Mapping.]{An over-complex compound Mapping, consisting of PermMaps,
+   ZoomMaps and a \htmlref{UnitMap}{UnitMap}, which can be simplified to become a single
+   UnitMap.  The enclosing nested CmpMaps have been omitted for clarity.}
+   \label{fig:simplifyexample}
+   \end{center}
+   \end{figure}
+
+If this were contained in a CmpMap, it could be simplified as follows:
+
+\small
+\begin{terminalv}
+      INTEGER SIMPLER
+
+      ...
+
+      SIMPLER = AST_SIMPLIFY( CMPMAP, STATUS );
+\end{terminalv}
+\normalsize
+
+In this case, the result would be a simple 3-dimensional UnitMap (the
+identity \htmlref{Mapping}{Mapping}).  To reach this conclusion, AST\_SIMPLIFY will have
+made a number of deductions, roughly as follows:
+
+\begin{enumerate}
+\item The two 2-dimensional ZoomMaps in series are equivalent to a
+single \htmlref{ZoomMap}{ZoomMap} with a combined \htmlref{Zoom}{Zoom} factor of unity. This, in turn, is
+equivalent to a 2-dimensional UnitMap.
+
+\item This UnitMap in parallel with the other 1-dimensional UnitMap is
+equivalent to a single 3-dimensional UnitMap. This UnitMap, sandwiched
+between any other pair of Mappings, can then be eliminated.
+
+\item The remaining two PermMaps in series are equivalent to a single
+3-dimensional \htmlref{PermMap}{PermMap}. When these are combined, the resulting PermMap
+is found to be equivalent to a 3-dimensional UnitMap.
+\end{enumerate}
+
+This example is a little contrived, but illustrates how AST\_SIMPLIFY
+can deal with even quite complicated compound Mappings through a
+series of incremental simplifications. Where possible, this will
+result in either a simpler compound Mapping or, if feasible, an atomic
+(non-compound) Mapping, as here. If no simplification is possible,
+AST\_SIMPLIFY will just return a pointer to the original Mapping.
+
+Although AST\_SIMPLIFY cannot identify every simplification that is
+theoretically possible, sufficient rules are included to deal with the
+most common and important cases.
+
+\cleardoublepage
+\section{\label{ss:frames}Representing Coordinate Systems (Frames)}
+
+An AST \htmlref{Frame}{Frame} is an \htmlref{Object}{Object} that is used to represent a coordinate
+system. Contrast this with a \htmlref{Mapping}{Mapping} (\secref{ss:mappings}), which is
+used to describe how to convert between coordinate systems. The two
+concepts are complementary and we will see how they work together in
+\secref{ss:framesets}.
+
+In this section we will discuss only basic Frames, which represent
+Cartesian coordinate systems. More specialised types of Frame
+(\emph{e.g.}\ the \htmlref{SkyFrame}{SkyFrame}, which represents celestial coordinate
+systems, and the \htmlref{SpecFrame}{SpecFrame}, which represents spectral coordinate
+systems) are covered later (\secref{ss:skyframes} and \secref{ss:specframes})
+and, naturally, inherit the properties and behaviour of the simple Frames
+discussed here.
+
+\subsection{The Frame Model}
+
+The best way to think about a \htmlref{Frame}{Frame} is like the frame that you would
+plot around a graph. In two dimensions, you would have an ``$x$'' and
+a ``$y$'' axis, a title on the graph and labels on the axes, together
+with an indication of the physical units being plotted. The values
+marked along each axis would be formatted in a human-readable way. The
+frame around a graph therefore defines a coordinate space within which
+you can locate points, draw lines, calculate distances, \emph{etc.}
+
+An AST Frame works in much the same way, embodying all of these
+concepts and a few more. It also allows any number of axes, which
+means that a Frame can represent coordinate systems with any number of
+dimensions. You specify how many when you create it.
+
+Remember that the basic Frame we are considering here is completely
+general.  It knows nothing of celestial coordinates, for example, and
+all its axes are equivalent. It can be adapted to describe any general
+purpose Cartesian coordinate system by setting its attributes, such as
+its \htmlref{Title}{Title} and axis Labels, \emph{etc.}\ to appropriate values.
+
+\subsection{\label{ss:creatingframes}Creating a Frame}
+
+Creating a \htmlref{Frame}{Frame} is straightforward and follows the usual pattern:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER FRAME, STATUS
+
+      STATUS = 0
+
+      ...
+
+      FRAME = AST_FRAME( 2, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+The first argument of the \htmlref{AST\_FRAME}{AST\_FRAME} constructor function specifies
+the number of axes which the Frame should have.
+
+\subsection{\label{ss:frameasmapping}Using a Frame as a Mapping}
+
+We should briefly point out that the \htmlref{Frame}{Frame} we created above
+(\secref{ss:creatingframes}) is also a \htmlref{Mapping}{Mapping}
+(\secref{ss:mappingclass}) and therefore inherits the properties and
+behaviour common to other Mappings.
+
+One way to see this is to set the Frame's \htmlref{Report}{Report} attribute (inherited
+from the Mapping class) to a non-zero value and pass the Frame pointer
+to a coordinate transformation routine, such as \htmlref{AST\_TRAN2}{AST\_TRAN2}.
+
+\small
+\begin{terminalv}
+      DOUBLE PRECISION XIN( 5 ), YIN( 5 ), XOUT( 5 ), YOUT( 5 )
+      DATA XIN / 0D0, 1D0, 2D0, 3D0, 4D0, 5D0 /
+      DATA YIN / 0D0, 2D0, 4D0, 6D0, 8D0, 10D0 /
+
+      CALL AST_SET( FRAME, 'Report=1', STATUS )
+      CALL AST_TRAN2( FRAME, 5, XIN, YIN, .TRUE., XOUT, YOUT, STATUS )
+\end{terminalv}
+\normalsize
+
+The resulting output might then look like this:
+
+\begin{terminalv}
+(1, 2) --> (1, 2)
+(2, 4) --> (2, 4)
+(3, 6) --> (3, 6)
+(4, 8) --> (4, 8)
+(5, 10) --> (5, 10)
+\end{terminalv}
+
+This is not very exciting because a Frame implements an identity
+transformation just like a \htmlref{UnitMap}{UnitMap}
+(\secref{ss:unitmapexample}). However, it illustrates that a Frame can
+be used as a Mapping and that its \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes are both
+equal to the number of Frame axes.
+
+When we consider more specialised Frames
+(\emph{e.g.}~\secref{ss:framesets}), we will see that using them as
+Mappings can be very useful indeed.
+
+\subsection{\label{ss:frameaxisattributes}Frame Axis Attributes}
+
+Frames have a number of attributes which can take multiple values, one
+for each axis. These separate values are identified by appending the
+axis number in parentheses to the attribute name. For example, the
+Label(1) attribute is a character string containing the label which
+appears on the first axis.
+
+\htmlref{Axis}{Axis} attributes are accessed in the same way as all other attributes
+(\secref{ss:gettingattributes}, \secref{ss:settingattributes} and
+\secref{ss:defaultingattributes}). For example, the Label on the second
+axis might be obtained as follows:
+
+\small
+\begin{terminalv}
+      CHARACTER * ( 70 ) LABEL
+
+      ...
+
+      LABEL = AST_GETC( FRAME, 'Label(2)', STATUS )
+\end{terminalv}
+\normalsize
+
+Other attribute access routines (AST\_SETx, \htmlref{AST\_TEST}{AST\_TEST} and \htmlref{AST\_CLEAR}{AST\_CLEAR})
+may also be applied to axis attributes in the same way.
+
+If the axis number is stored in a program variable, then its value
+must be formatted to generate a suitable attribute name before using
+this to access the attribute itself. For example, the following will
+print out the Label value for each axis of a \htmlref{Frame}{Frame}:
+
+\small
+\begin{terminalv}
+      CHARACTER * ( 10 ) AXIS
+      INTEGER IAXIS
+
+      ...
+
+      DO 1 IAXIS = 1, AST_GETI( FRAME, 'Naxes', STATUS )
+         WRITE ( AXIS, '( I10 )' ) IAXIS
+         LABEL = AST_GETC( FRAME, 'Label(' // AXIS // ')', STATUS )
+         WRITE ( *, 199 ) IAXIS, LABEL
+ 199     FORMAT ( 'Label ', I2, ': ', A )
+ 1    CONTINUE
+\end{terminalv}
+\normalsize
+
+Note the use of the \htmlref{Naxes}{Naxes} attribute to determine the number of Frame
+axes.
+
+The output from this might look like the following:
+
+\begin{terminalv}
+Label  1: Axis 1
+Label  2: Axis 2
+\end{terminalv}
+
+In this case, the Frame's default axis Labels have been revealed as
+rather un-exciting. Normally, you would set much more useful values,
+typically when you create the Frame---perhaps something like:
+
+\small
+\begin{terminalv}
+      FRAME = AST_FRAME( 2, 'Label(1)=Offset from centre of field,' //
+                            'Unit(1) =mm,' //
+                            'Label(2)=Transmission coefficient,' //
+                            'Unit(2) =%', STATUS )
+\end{terminalv}
+\normalsize
+
+Here, we have also set the (character string) Unit attribute for each
+axis to describe the physical units represented on that axis. All the
+attribute assignments have been combined into a single string,
+separated by commas.
+
+\subsection{\label{ss:frameattributes}Frame Attributes}
+
+We will now briefly outline the various attributes associated with a
+\htmlref{Frame}{Frame} (this is, of course, in addition to those inherited from the
+\htmlref{Mapping}{Mapping} class). We will not delve too deeply into the details of each
+attribute, for which you should consult the appropriate description in
+\appref{ss:attributedescriptions}. Instead, we aim simply to sketch
+the range of facilities available:
+
+\begin{quote}
+\begin{description}
+\item[\htmlref{Naxes}{Naxes}]\mbox{}\\
+A read-only integer giving the number of Frame axes.
+
+\item[\htmlref{Title}{Title}]\mbox{}\\
+A string describing the coordinate system which the Frame represents.
+
+\item[\htmlref{Label(axis)}{Label(axis)}]\mbox{}\\
+A label string for each axis.
+
+\item[\htmlref{Unit(axis)}{Unit(axis)}]\mbox{}\\
+A string describing the physical units on each axis. You can choose
+whether to make this attribute ``active'' or ``passive'' (using
+\htmlref{AST\_SETACTIVEUNIT}{AST\_SETACTIVEUNIT}
+). If active, its value will be taken into account when finding the
+Mapping between two Frames (\emph{e.g.} a scaling of 0.001 would be used
+to connect two axis with units of ``km'' and ``m''). If passive, its value
+is ignored. Its use is described in more detail in \secref{ss:frameunits}.
+
+\item[\htmlref{Symbol(axis)}{Symbol(axis)}]\mbox{}\\
+A string containing a ``short form'' symbol (\emph{e.g.}\ like ``X''
+or ``Y'') used to represent the quantity plotted on each axis.
+
+\item[\htmlref{Digits/Digits(axis)}{Digits/Digits(axis)}]\mbox{}\\
+The preferred number of digits of precision to be used when formatting
+values for display on each axis.
+
+\item[\htmlref{Format(axis)}{Format(axis)}]\mbox{}\\
+A string containing a \emph{format specifier} which determines exactly
+how values should be formatted for display on each axis
+(\secref{ss:formattingaxisvalues}). If this attribute is un-set, the
+formatting is based on the Digits value, otherwise the Format string
+over-rides the Digits value.
+
+\item[\htmlref{Direction(axis)}{Direction(axis)}]\mbox{}\\
+A boolean (integer) value which indicates in which direction each axis
+should be plotted. If it is non-zero (the default), the axis should be
+plotted in the conventional direction---\emph{i.e.}\ increasing to the
+right for the abscissa and increasing upwards for the ordinate. If it
+is zero, the axis should be plotted in reverse.  This attribute is
+provided as a hint only and programs are free to ignore it if they
+wish.
+
+\item[\htmlref{Domain}{Domain}]\mbox{}\\
+A character string which identifies the \emph{physical domain} to
+which the Frame's coordinate system applies. The primary purpose of
+this attribute is to prevent unwanted conversions from occurring
+between coordinate systems which are not related. Its use is described
+in more detail in \secref{ss:framedomains}.
+
+\item[\htmlref{System}{System}]\mbox{}\\
+A character string which identifies the specific coordinate system used
+to describe positions within the physical domain represented by the Frame.
+For a simple Frame, this attribute currently has a fixed value of
+``Cartesian'', but could in principle be extended to include options such
+as ``Polar'', ``Cylindrical'', \emph{etc}. More specialised Frames such
+as the \htmlref{SkyFrame}{SkyFrame}, \htmlref{TimeFrame}{TimeFrame}  and \htmlref{SpecFrame}{SpecFrame}, re-define the allowed values to be
+appropriate to the domain which they describe. For instance, the SkyFrame
+allows values such as ``FK4'' and ``Galactic'', and the SpecFrame allows
+values such as ``frequency'' and ``wavelength''.
+
+\item[\htmlref{Epoch}{Epoch}]\mbox{}\\
+This value is used to qualify a coordinate system by giving the moment in
+time when the coordinates are correct. Usually, this will be the date of
+observation. The Epoch value is important in cases where coordinates
+systems move with respect to each other over time. An example of two such
+coordinate systems are the FK4 and FK5 celestial coordinate systems.
+
+\item[\htmlref{ObsLon}{ObsLon}]\mbox{}\\
+Specifies the longitude of the observer (assumed to be on the surface of
+the earth). The basic Frame class does not use this value, but
+specialised sub-classes may. For instance, the SpecFrame class uses it to
+calculate the relative velocity of the observer and the centre of the
+earth for use in converting between standards of rest.
+
+\item[\htmlref{ObsLat}{ObsLat}]\mbox{}\\
+Specifies the latitude of the observer. Use in conjunction with ObsLon.
+
+\end{description}
+\end{quote}
+
+There are also some further Frame attributes, not described above,
+which are important when Frames are used as templates to search for
+other Frames. Their use goes beyond the present discussion.
+%TBW---Add reference here.
+
+\subsection{\label{ss:formattingaxisvalues}Formatting Axis Values}
+
+The coordinate values associated with each axis of a \htmlref{Frame}{Frame} are stored
+(\emph{e.g.}\ within your program) as double precision values. The
+Frame class therefore provides a function, \htmlref{AST\_FORMAT}{AST\_FORMAT}, to convert
+these values into formatted strings for display:
+
+\small
+\begin{terminalv}
+      CHARACTER * ( 50 ) STRING
+      DOUBLE PRECISION VALUE
+
+      ...
+
+      STRING = AST_FORMAT( FRAME, IAXIS, VALUE, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, the AST\_FORMAT character function is passed a Frame pointer,
+the number of an axis (IAXIS) and a double precision value to format
+(VALUE). It returns a character string containing the formatted value.
+\label{ss:formattingwithdigits}
+
+By default, the formatting applied will be determined by the Frame's
+Digits attribute and will normally display results with seven digits
+of precision (corresponding approximately to the Fortran REAL data
+type on many machines). Setting a different Digits value, however,
+allows you to adjust the precision as necessary to suit the accuracy
+of the coordinate data you are processing.  If finer control is
+needed, it is also possible to set a Digits value for each individual
+axis by appending an axis number to the attribute name
+(\emph{e.g.}\ ``Digits(2)''). If this is done, it over-rides the
+effect of the Frame's main Digits value for that axis.
+
+Even finer control is possible by setting the (character string)
+Format attribute for a Frame axis. The string given should contain a
+\emph{format specifier} which explicitly determines how the values on
+that axis should be formatted. This will over-ride the effects of any
+Digits value\footnote{The exception to this rule is that if the Format value
+includes a precision of ``$.*$'', then Digits will be used to determine
+the actual precision used.}.  Unfortunately for Fortran programmers, this must
+be a C language format specifier,\footnote{This is a consequence of
+implementing the AST library in C.} so you might find the Digits
+approach preferable.
+
+The simplest type of format specifier takes the form ``\%m.nG'', where
+``m'' and ``n'' are integers giving the minimum field width in characters
+and the number of significant digits to display (\emph{e.g.}\
+``\%10.5G''). The ''n'' value may be replaced by an asterisk, in which
+case the value of the Digits attribute is used to determine the number of
+significant digits to display. Other formatting options are also possible
+and if you need to use them you may wish to consult a book on C (see the
+``printf'' function), remembering that you want to format a double
+precision (C double) value.
+
+It is recommended that you use AST\_FORMAT whenever you display
+formatted coordinate values, even although you could format them
+yourself using a WRITE statement. This is because it puts the Frame in
+control of formatting. When you start to handle more elaborate Frames
+(representing, say, celestial coordinates), you will need different
+formatting methods. This approach delivers them without any change to
+your software.
+
+You should also consider regularly using the \htmlref{AST\_NORM}{AST\_NORM} routine,
+described below (\secref{ss:normalising}), for any values that will be
+made visible to the user of your software.
+
+\subsection{\label{ss:normalising}Normalising Frame Coordinates}
+
+The routine \htmlref{AST\_NORM}{AST\_NORM} is provided to cope with the fact that some
+coordinate systems do not extend indefinitely in all directions. Some
+may have boundaries, outside which coordinates are meaningless, while
+others wrap around on themselves, so that after a certain distance you
+return to the beginning again (coordinate systems based on circles and
+spheres, for instance). A basic \htmlref{Frame}{Frame} has no such complications, but
+other more specialised Frames (such as SkyFrames, representing the
+celestial sphere---\secref{ss:skyframes}) do.
+
+The role played by AST\_NORM is to \emph{normalise} any arbitrary set
+of coordinates by converting them into a set which is ``within
+bounds'', interpreted according to the particular Frame in
+question. For example, on the celestial sphere, a right ascension
+value of 24~hours or more can have a suitable multiple of 24~hours
+subtracted without affecting its meaning and AST\_NORM would perform
+this task. Similarly, negative values of right ascension would have a
+multiple of 24~hours added, so that the result lies in the range zero
+to 24~hours. The coordinates in question are modified in place by
+AST\_NORM, as follows:
+
+\small
+\begin{terminalv}
+      DOUBLE PRECISION POINT( 2 )
+
+      ...
+
+      CALL AST_NORM( FRAME, POINT, STATUS )
+\end{terminalv}
+\normalsize
+
+If the coordinates supplied are initially OK, as they would always be
+with a basic Frame, then they are returned unchanged.
+
+Because the main purpose of AST\_NORM is to convert coordinates into
+the preferred range for human consumption, its use is almost always
+appropriate immediately before formatting coordinate values for
+display using \htmlref{AST\_FORMAT}{AST\_FORMAT} (\secref{ss:formattingaxisvalues}). Even if
+the Frame in question does not restrict the range of coordinates, so
+that AST\_NORM does nothing, using it will allow you to process other
+more specialised Frames, where normalisation is important, without
+changing your software.
+
+\subsection{\label{ss:unformattingaxisvalues}Reading Formatted Axis Values}
+
+The process of converting a formatted coordinate value for a \htmlref{Frame}{Frame}
+axis, such as might be produced by \htmlref{AST\_FORMAT}{AST\_FORMAT}
+(\secref{ss:formattingaxisvalues}), back into a numerical (double
+precision) value ready for processing is performed by \htmlref{AST\_UNFORMAT}{AST\_UNFORMAT}.
+However, although this process is essentially the inverse of that
+performed by AST\_FORMAT, there are a number of additional difficulties
+that must be addressed in practice.
+
+The main use for AST\_UNFORMAT is in reading formatted coordinate
+values which have been entered by the user of a program, or read from
+a file. As such, we can rarely assume that the values are neatly
+formatted in the way that AST\_FORMAT would produce. Instead, it is
+usually desirable to allow considerable flexibility in the form of
+input that can be accommodated, so as to permit ``free-format'' data
+input by the user. In addition, we may need to extract individual
+coordinate values embedded in other textual data.
+
+Underlying these requirements is the root difficulty that the textual
+format used to represent a coordinate value will depend on the class
+of Frame we are considering. For example, for a basic Frame,
+AST\_UNFORMAT may have to read a value like ``1.25E-6'', whereas a
+more specialised Frame representing celestial coordinates may have to
+handle a value like ``-07d~49m~13s''. Of course, the format might also
+depend on which axis is being considered.
+
+Ideally, we would like to write software that can handle any kind of
+Frame. However, this makes it a little more difficult to analyse
+textual input data to extract individual coordinate values, since we
+cannot make assumptions about how the values are formatted. It would
+not be safe, for example, simply to assume that the values being read
+are separated by white space. This is not just because they might be
+separated by some other character, but also because celestial
+coordinate values might themselves contain spaces. In fact, to be
+completely safe, we cannot make any assumptions about how a formatted
+coordinate value is separated from the surrounding text, except that
+it should be separated in some way which is not ambiguous.
+
+This is the very basic assumption upon which AST\_UNFORMAT works. It is
+invoked as follows:
+
+\small
+\begin{terminalv}
+      INTEGER N
+
+      ...
+
+      N = AST_UNFORMAT( FRAME, IAXIS, STRING, VALUE, STATUS )
+\end{terminalv}
+\normalsize
+
+It is supplied with a Frame pointer (FRAME), the number of an axis
+(IAXIS) and a character string to be read (STRING). If it succeeds in
+reading a value, AST\_UNFORMAT returns the resulting coordinate
+\emph{via} its penultimate argument (VALUE). The returned function
+value indicates how many characters were read from the string in order
+to obtain this result.
+
+The string is read as follows:
+
+\begin{enumerate}
+\item Any white space at the start is skipped over.
+
+\item Further characters are considered, one at a time, until the next
+character no longer matches any of the acceptable forms of input
+(given the characters that precede it). The longest sequence of
+characters which matches is then considered ``read''.
+
+\item If a suitable sequence of characters was read successfully, it
+is converted into a coordinate value which is returned. Any white
+space following this sequence is then skipped over and the total
+number of characters consumed is returned as the function value.
+
+\item If the sequence of characters read is empty, or insufficient to
+define a coordinate value, then the string does not contain a value to
+read. In this case, the read is aborted and AST\_UNFORMAT returns a
+function value of zero and no coordinate value. However, it returns
+without error.
+\end{enumerate}
+
+Note that failing to read a coordinate value does not constitute an
+error, at least so far as AST\_UNFORMAT is concerned. However, an
+error can occur if the sequence of characters read appears to have the
+correct form but cannot be converted into a valid coordinate
+value. Typically, this will be because it violates some constraint,
+such as a limit on the value of one of its fields. The resulting error
+message will give details.
+
+For any given Frame axis, AST\_UNFORMAT does not necessarily always
+use the same algorithm for converting the sequence of characters it
+reads into a coordinate value. This is because some forms of input
+(particularly free-format input) can be ambiguous and might be
+interpreted in several ways depending on the context. For example, the
+celestial longitude ``12:34:56.7'' could represent an angle in degrees
+or a right ascension in hours. To decide which to use, AST\_UNFORMAT
+may examine the Frame's attributes and, in particular, the appropriate
+\htmlref{Format(axis)}{Format(axis)} string which is used by AST\_FORMAT when formatting
+coordinate values (\secref{ss:formattingaxisvalues}). This is done in
+order that AST\_FORMAT and AST\_UNFORMAT should complement each
+other---so that formatting a value and then un-formatting it will
+yield the original value, subject to any rounding error.
+
+To give a simple (but crucially incomplete!) example, consider reading
+a value for the axis of a basic Frame, as follows:
+
+\small
+\begin{terminalv}
+      N = AST_UNFORMAT( FRAME, IAXIS, ' 1.5E6   -99.0', VALUE, STATUS )
+\end{terminalv}
+\normalsize
+
+AST\_UNFORMAT will skip over the initial space in the string supplied
+and then examine each successive character. It will accept the
+sequence ``1.5E6'' as input, but reject the space which follows
+because it does not form part of the format of a floating point
+number. It will then convert the characters ``1.5E6'' into a
+coordinate value and skip over the three spaces which follow them. The
+returned function value will therefore be 9, equal to the total number
+of characters consumed. This result may be used to address the string
+during a subsequent read, so as to commence reading at the start of
+``-99.0''.
+
+Most importantly, however, note that if the user of a program
+mistakenly enters the string ``~1.5R6\ldots'' instead of
+``~1.5E6\ldots'', a coordinate value of 1.5 and a function result of 4
+will be returned, because the ``R'' would prematurely terminate the
+attempt to read the value. Because this sort of mistake does not
+automatically result in an error but can produce incorrect results, it
+is \textbf{vital} to check the returned function value to ensure that
+the expected number of characters have been read. For example, if the
+string is expected to contain exactly one value, and nothing else,
+then the following would suffice:
+
+\small
+\begin{terminalv}
+      N = AST_UNFORMAT( FRAME, IAXIS, STRING, VALUE, STATUS )
+      IF ( STATUS .EQ. 0 ) THEN
+         IF ( N .LT. LEN( STRING ) ) THEN
+            <error in input data>
+         ELSE
+            <value read correctly>
+         END IF
+      END IF
+\end{terminalv}
+\normalsize
+
+If AST\_UNFORMAT does not detect an error itself, we check that it has
+read to the end of the string. If this reveals an error, the value of
+N indicates where it occurred.
+
+Another common requirement is to obtain a position by reading a list
+of coordinates from a string which contains one value for each axis of
+a Frame. We assume that the values are separated in some unambiguous
+manner, perhaps using white space and/or some unspecified
+single-character separator. The choice of separator is up to the data
+supplier, who must choose it so as not to conflict with the format of
+the coordinate values, but our software does not need to know what it
+is. The following is a template algorithm for reading data in this
+form:
+
+\small
+\begin{terminalv}
+      INTEGER I
+      DOUBLE PRECISION VALUES( 10 )
+
+      ...
+
+*  Initialise the string index.
+      I = 1
+
+*  Obtain the number of Frame axes and loop through them.
+      DO 1 IAXIS = 1, AST_GETI( FRAME, 'Naxes', STATUS )
+
+*  Attempt to read a value for this axis.
+         N = AST_UNFORMAT( FRAME, IAXIS, STRING( I : ),
+     :                     VALUES( IAXIS ), STATUS )
+
+*  If nothing was read and this is not the first axis and the end of
+*  the string has not been reached, try stepping over a separator and
+*  reading again.
+         IF ( ( N .EQ. 0 ) .AND. ( IAXIS .GT. 1 ) .AND.
+     :        ( I .LT. LEN( STRING ) ) ) THEN
+            I = I + 1
+            N = AST_UNFORMAT( FRAME, IAXIS, STRING( I : ),
+     :                        VALUES( IAXIS ), STATUS )
+         END IF
+
+*  Quit if nothing was read, otherwise move on to the next value.
+         IF ( N .EQ. 0 ) GO TO 2
+         I = I + N
+ 1    CONTINUE
+ 2    CONTINUE
+
+*  Check for possible errors.
+      IF ( STATUS .EQ. 0 ) THEN
+         IF ( ( I .LT. LEN( STRING ) ) .OR. ( N .EQ. 0 ) ) THEN
+            <error in input data>
+         ELSE
+            <values read correctly>
+         END IF
+      END IF
+\end{terminalv}
+\normalsize
+
+In this case, the value of I will indicate the location of any input error.
+
+Note that this algorithm is insensitive to the precise format of the
+data and will therefore work with any class of Frame and any
+reasonably unambiguous input data. For example, here is a range of
+suitable input data for a 3-dimensional basic Frame:
+
+\small
+\begin{terminalv}
+1 2.5 3
+3.1,3.2,3.3
+1.5, 2.6, -9.9e2
+-1.1+0.4-1.8
+    .1/.2/.3
+ 44.0 ; 55.1 -14
+\end{terminalv}
+\normalsize
+
+\subsection{\label{ss:permutingaxes}Permuting Frame Axes}
+
+Once a \htmlref{Frame}{Frame} has been created, it is not possible to change the number
+of axes it contains, but it is possible to change the order in which
+these axes occur. To do so, an integer \emph{permutation array} is
+filled with the numbers of the axes so as to specify the new order,
+\emph{e.g.:}
+
+\small
+\begin{terminalv}
+      INTEGER PERM( 2 )
+      DATA PERM / 2, 1 /
+\end{terminalv}
+\normalsize
+
+In this case, the axes of a 2-dimensional Frame could be interchanged
+by passing this permutation array to the \htmlref{AST\_PERMAXES}{AST\_PERMAXES} function. That
+is, an ($x_1,x_2$) coordinate system would be changed into an
+($x_2,x_1$) coordinate system by:
+
+\small
+\begin{terminalv}
+      CALL AST_PERMAXES( FRAME, PERM, STATUS )
+\end{terminalv}
+\normalsize
+
+If the axes are permuted more than once, the effects are cumulative.
+You are, of course, not restricted to Frames with only two axes.
+
+\subsection{Selecting Frame Axes}
+
+An alternative to changing the number of \htmlref{Frame}{Frame} axes, which is not
+allowed, is to create a new Frame by selecting axes from an existing
+one. The method of doing this is very similar to the way \htmlref{AST\_PERMAXES}{AST\_PERMAXES}
+is used (\secref{ss:permutingaxes}), in that we supply an integer
+array filled with the numbers of the axes we want, in their new
+order. In this case, however, the number of array elements need not
+equal the number of Frame axes.
+
+For example, we could select axes 3 and 2 (in that order) from a
+3-dimensional Frame as follows:
+
+\small
+\begin{terminalv}
+      INTEGER FRAME1, FRAME2, MAPPING, PICK( 2 )
+      DATA PICK / 3, 2 /
+
+      ...
+
+      FRAME2 = AST_PICKAXES( FRAME1, 2, PICK, MAPPING, STATUS )
+\end{terminalv}
+\normalsize
+
+This would return a pointer to a 2-dimensional Frame (FRAME2) which
+contains the information associated with axes 3 and 2, in that order,
+from the original Frame (FRAME1). The original Frame is not altered by
+this process. Beware, however, that the axis information may still be
+shared by both Frames, so if you wish to alter either of them
+independently you may first need to use \htmlref{AST\_COPY}{AST\_COPY}
+(\secref{ss:copyingobjects}) to make an independent copy.
+
+In addition to the new Frame pointer, \htmlref{AST\_PICKAXES}{AST\_PICKAXES} will also return a
+pointer to a new \htmlref{Mapping}{Mapping} \emph{via} its fourth argument. This Mapping will
+inter-relate the two Frames. By this we mean that its forward
+transformation will convert coordinates originally in the coordinate
+system represented by FRAME1 into that represented by FRAME2, while
+its inverse transformation will convert in the opposite direction. In
+this particular case, the Mapping would be a \htmlref{PermMap}{PermMap}
+(\secref{ss:permmapexample}) and would implement the following
+transformations:
+
+\begin{terminalv}
+Forward:
+   (1, 2, 3) --> (3, 2)
+   (2, 4, 6) --> (6, 4)
+   (3, 6, 9) --> (9, 6)
+   (4, 8, 12) --> (12, 8)
+   (5, 10, 15) --> (15, 10)
+
+Inverse:
+   (3, 2) --> (<bad>, 2, 3)
+   (6, 4) --> (<bad>, 4, 6)
+   (9, 6) --> (<bad>, 6, 9)
+   (12, 8) --> (<bad>, 8, 12)
+   (15, 10) --> (<bad>, 10, 15)
+\end{terminalv}
+
+This is our first introduction to the idea of inter-relating pairs of
+Frames \emph{via} a Mapping, but this will assume a central role later on.
+
+Note that when using AST\_PICKAXES, it is also possible to request
+more axes than there were in the original Frame. This will involve
+selecting axes from the original Frame that do not exist. To do this,
+the corresponding axis number (in the PICK array) should be set to
+zero and the effect is to introduce an additional new axis which is
+not derived from the original Frame. This axis will have default
+values for all its attributes. You will need to do this because
+AST\_PICKAXES does not allow you to select any of the original axes
+more than once.\footnote{It will probably not be obvious why this
+restriction is necessary, but consider creating a Frame with one
+longitude axis and two latitude axes. Which latitude axis should be
+associated with the longitude axis?}
+
+\subsection{\label{ss:distanceandoffset}Calculating Distances, Angles and Offsets}
+Some complementary
+routines
+are provided for use with Frames to allow you to perform geometric
+operations without needing to know the nature of the coordinate system
+represented by the \htmlref{Frame}{Frame}.
+
+Routines
+can be used to find the distance between two points, and to offset a
+specified distance along a line joining two points, \emph{etc.} In essence,
+these define the metric of the coordinate space which the Frame represents. In
+the case of a basic Frame, this is a Cartesian metric.
+
+The first of these routines, \htmlref{AST\_DISTANCE}{AST\_DISTANCE}, returns a double precision
+distance value when supplied with the Frame coordinates of two
+points. For example:
+
+\small
+\begin{terminalv}
+      DOUBLE PRECISION DIST, POINT1( 2 ), POINT2( 2 )
+      DATA POINT1 / 0D0, 0D0 /
+      DATA POINT2 / 1D0, 1D0 /
+
+      ...
+
+      DIST = AST_DISTANCE( FRAME, POINT1, POINT2, STATUS )
+\end{terminalv}
+\normalsize
+
+This calculates the distance between the origin (0,0) and a point at
+position (1,1). In this case, the result, as you would expect, is
+$\surd{2}$. However, this is only true for the Cartesian coordinate
+system which a basic Frame represents. In general, AST\_DISTANCE will
+calculate the geodesic distance between the two points, so that with a
+more specialised Frame (such as a \htmlref{SkyFrame}{SkyFrame}, representing the celestial
+sphere) a great-circle distance might be returned.
+
+The \htmlref{AST\_OFFSET}{AST\_OFFSET} routine is really the inverse of AST\_DISTANCE. Given
+two points in a Frame, it calculates the coordinates of a third point
+which is offset a specified distance away from the first point along
+the geodesic joining it to the second one. For example:
+
+\small
+\begin{terminalv}
+      DOUBLE PRECISION POINT1( 2 ), POINT2( 2 ), POINT3( 2 )
+      DATA POINT1 / 0D0, 0D0 /
+      DATA POINT2 / 1D0, 1D0 /
+
+      ...
+
+      CALL AST_OFFSET( FRAME, POINT1, POINT2, 0.5D0, POINT3, STATUS )
+\end{terminalv}
+\normalsize
+
+This would fill the POINT3 array with the coordinates of a point which
+is offset 0.5 units away from the origin (0,0) in the direction of the
+position (1,1). Again, this is a simple result in a Cartesian Frame,
+as varying the offset will trace out a straight line. On the celestial
+sphere, however (\emph{e.g.}\ using a SkyFrame), it would trace out a
+great circle.
+
+The routines \htmlref{AST\_AXDISTANCE}{AST\_AXDISTANCE} and \htmlref{AST\_AXOFFSET}{AST\_AXOFFSET} are similar to AST\_DISTANCE
+and AST\_OFFSET, except that the curves which they use as ``straight
+lines'' are not geodesics, but curves parallel to a specified axis\footnote
+{For instance, a line of constant Declination is not a geodesic}. One
+reason for using these routines is to deal with the cyclic ambiguity of
+longitude and latitude axes.
+
+The \htmlref{AST\_OFFSET2}{AST\_OFFSET2} routine is similar to AST\_OFFSET, but instead of using the
+geodesic which passes through two positions, it uses the geodesic which
+passes at a given position angle through the starting position.
+
+Position angles are always measured from the positive direction of the
+second Frame axis to the required line, with positive angles being in the
+same sense as rotation from the positive direction of the second axis to
+the positive direction of the first Frame axis. This definition applies
+to all classes of Frame, including SkyFrame. The default ordering of axes
+in a SkyFrame makes the second axis equivalent to north, and so the
+definition of position angle given above corresponds to the normal
+astronomical usage, ``from north, through east''. However, it should be
+remembered that it is possible to permute the axes of a SkyFrame (or
+indeed any Frame), so that north becomes axis 1. In this case, an AST
+``position angle'' would be the angle ``from east, through north''.
+Always take the axis ordering into account when deriving an astronomical
+position angle from an AST position angle.
+
+Within a Cartesian coordinate system, the position angle of a geodesic
+(\emph{i.e.}\ a straight line) is constant along its entire length, but
+this is not necessarily true of other coordinate systems. Within a
+spherical coordinate system, for instance, the position angle of a geodesic
+will vary along its length (except for the special cases of a meridian and
+the equator). In addition to returning the required offset position, the
+AST\_OFFSET2 routine
+returns the position angle of the geodesic at the
+offset position. This is useful if you want to trace out a path which
+involves turning through specified angles. For instance, tracing out a
+rectangle in which each side is a geodesic involves turning through 90
+degrees at the corners. To do this, use AST\_OFFSET2 to calculate the
+position of each corner, and then add (or subtract) 90 degrees from the
+position angle returned by AST\_OFFSET2.
+
+The \htmlref{AST\_ANGLE}{AST\_ANGLE} routine
+calculates the angle subtended by two points, at a third point.
+If used with a 2-dimensional Frame the returned angle
+is signed to indicate the sense of rotation (clockwise or anti-clockwise)
+in taking the ``shortest route'' from the first point to the second.
+If the Frame has more than 2 axes, the result is un-signed and is always
+in the range zero to $\pi$.
+
+The \htmlref{AST\_AXANGLE}{AST\_AXANGLE} routine is similar to AST\_AXANGLE,
+but the ``reference direction'', from which angles are measured, is
+a specified axis.
+
+The \htmlref{AST\_RESOLVE}{AST\_RESOLVE} routine
+resolves a given displacement within a Frame into two components, parallel and
+perpendicular to a given reference direction.
+
+The displacement is specified by two positions within the Frame; the
+starting and ending positions. The reference direction is defined by the
+geodesic curve passing through the starting position and a third specified
+position. The lengths of the two components are returned, together with
+the position on the reference geodesic which is closest to the third
+supplied point.
+
+\subsection{\label{ss:framedomains}The Domain Attribute}
+
+The \htmlref{Domain}{Domain} attribute is one of the most important properties of a
+\htmlref{Frame}{Frame}, although the concept it expresses can sometimes seem a little
+subtle.  We will introduce it here, but its true value will probably
+not become apparent until later (\secref{ss:framesetconverting}).
+
+To understand the need for the Domain attribute, consider using
+different Frames to represent the following different coordinate
+systems associated with a CCD image:
+
+\begin{enumerate}
+\item A coordinate system based on pixel numbers.
+
+\item Positions on the CCD chip, measured in $\mu$m.
+
+\item Positions in the focal plane of the telescope, measured in mm.
+
+\item A celestial coordinate system, measured in radians.
+\end{enumerate}
+
+If we had two such CCD images, we might legitimately want to align
+them pixel-for-pixel (\emph{i.e.}\ using the coordinate system based
+on pixel numbers) in order to, say, divide by a flat-field exposure.
+We might similarly consider aligning them using any of the other
+coordinate systems so as to achieve different results. For example, we
+might consider merging separate images from a CCD mosaic by using
+focal plane positions.
+
+It would obviously not be legitimate, however, to directly compare
+positions in one image measured in pixels with positions in the other
+measured in mm, nor to equate chip positions in $\mu$m with sky
+coordinates in radians. If we wanted to inter-compare these
+coordinates, we would need to do it indirectly, using other
+information based on the experimental set-up. For instance, we might
+need to know the size of the pixels expressed in mm and the
+orientation of the CCD chip in the focal plane.
+
+Note that it is not simply the difference in physical units which
+prevents certain coordinates from being directly inter-compared
+(because the appropriate unit scaling factors could be included
+without any additional information). Neither is it the fact that
+different coordinate systems are in use (because we could legitimately
+inter-compare two different celestial coordinate systems without any
+extra information).  Instead, it is the different nature of the
+coordinate spaces to which these coordinate systems have been applied.
+
+We normally express this by saying that the coordinate systems apply
+to different \emph{physical domains}. Although we may establish
+\emph{ad hoc} relationships between coordinates in different physical
+domains, they are not intrinsically related to each other and we need
+to supply extra information before we can convert coordinates between
+them.
+
+In AST, the role of the (character string) Domain attribute is to
+assign Frames to their respective physical domains. The way it
+operates is as follows:
+
+\begin{itemize}
+\item Coordinate systems which apply to the same physical domain
+(\emph{i.e.}\ whose Frames have the same Domain value) can be directly
+inter-compared.
+
+If the domain has several coordinate systems associated with it
+(\emph{e.g.}\ the celestial sphere), then a coordinate conversion may
+be involved. Otherwise, coordinate values may simply be equated.
+
+\item Coordinate systems which apply to different physical domains
+(\emph{i.e.}\ whose Frames have different Domain values) cannot be
+directly inter-compared.
+
+If any relationship does exist between such coordinate systems---and
+it need not---then additional information must be supplied in order to
+establish the relationship between them in any particular case. We
+will see later (\secref{ss:framesets}) how to establish such
+relationships between Frames in different domains.
+\end{itemize}
+
+With the basic Frames we are considering here, each physical domain only
+has a single (Cartesian) coordinate system associated with it, so that if
+two such Frames have the same Domain value, their coordinate systems will
+be identical and may simply be equated. With more specialised Frames,
+however, more than one coordinate system may apply to each domain. In
+such cases, a coordinate conversion may need to be performed.
+
+When a basic Frame is created, its Domain attribute defaults to a
+blank string. This means that all such Frames belong to the same
+(null) domain by default and therefore describe the same unspecified
+physical coordinate space. In order to assign a Frame to a different
+domain, you simply need to set its Domain value. This is normally most
+conveniently done when it is created, as follows:
+
+\small
+\begin{terminalv}
+      FRAME1 = AST_FRAME( 2, 'Domain=CCD_CHIP,' //
+                             'Unit(1)=micron,' //
+                             'Unit(2)=micron', STATUS )
+      FRAME2 = AST_FRAME( 2, 'Domain=FOCAL_PLANE,' //
+                             'Unit(1)=mm,' //
+                             'Unit(2)=mm', STATUS )
+\end{terminalv}
+\normalsize
+
+Here, we have created two Frames in different physical
+domains. Although their coordinate values all have units of length,
+they cannot be directly inter-compared (because their axes may be
+rotated with respect to each other, for instance).
+
+All Domain values are automatically converted to upper case and white
+space is removed, but there are no other restrictions on the names you
+may use to label different physical domains. From a practical point of
+view, however, it is worth following a few conventions
+(\secref{ss:domainconventions}).
+
+\subsection{\label{ss:domainconventions}Conventions for Domain Names}
+
+When choosing a value for the \htmlref{Domain}{Domain} attribute of a \htmlref{Frame}{Frame}, it
+obviously makes sense to avoid generic names which might clash with
+those used for similar (but subtly different!) purposes by other
+programmers. If you are developing software for an instrument, for
+example, and want to identify an instrumental coordinate system, then
+it is sensible to add a distinguishing prefix. For instance, you might
+use $<$INST$>$\_FOCAL\_PLANE, where $<$INST$>$ (\emph{e.g.}\ an
+acronym) identifies your instrument.
+
+For some purposes, however, a standard choice of Domain name is
+desirable so that different items of software can communicate. For
+this purpose, the following Domain names are reserved by AST and the
+use recommended below should be carefully observed:
+
+\begin{quote}
+\begin{description}
+\item[GRAPHICS]\mbox{}\\
+Identifies the coordinate space used by an underlying computer
+graphics system to specify plotting operations. Typically, when
+performing graphical operations, AST is used to define additional
+coordinate systems which are related to these ``native'' graphical
+coordinates.  Plotting may be carried out in any of these coordinate
+systems, but the GRAPHICS domain identifies the native coordinates
+through which AST communicates with the underlying graphics system.
+
+\item[GRID]\mbox{}\\
+Identifies the instantaneous \emph{data grid} used to store and handle
+data, together with an associated coordinate system. In this
+coordinate system, the first element stored in an array of data always
+has a coordinate value of unity at its centre and all elements have
+unit extent. This applies to all dimensions.
+
+If data are copied or transformed to a new data grid (by whatever
+means), or a subset of the original grid is extracted, then the same
+rules apply to the copy or subset. Its first element therefore has
+GRID coordinate values of unity at its centre. Note that this means
+that GRID coordinates remain attached to the first element of the data
+grid and not to its data content (\emph{e.g.}\ the features in an
+image).
+
+\item[PIXEL]\mbox{}\\
+Identifies an array of pixels and an associated \emph{pixel-based}
+coordinate system which is related to the GRID coordinate system
+(above) simply by a shift of origin along each axis. This shift may be
+integral, fractional, positive, negative or zero. The data elements
+retain their unit extent along each axis.
+
+Because the amount of shift is unspecified, the PIXEL domain is
+distinct from the GRID domain. The relationship between them contains
+a degree of uncertainty, such as typically arises from the different
+conventions used by different software systems. For instance, in some
+software the first pixel is regarded as being centred at (1,1), while
+in other software it is at (0.5,0.5). In addition, some software
+packages implement a ``pixel origin'' which allows pixel coordinates
+to start at an arbitrary value.
+
+The GRID domain (which corresponds with the pixel-numbering convention
+used by FITS) is a special case of the PIXEL domain and avoids this
+uncertainty. In general, additional information is required in order
+to convert from one to the other.
+
+\item[SKY]\mbox{}\\
+Identifies the domain which contains all equivalent celestial
+coordinate systems. Because these are represented in AST by SkyFrames
+(\secref{ss:skyframes}), it should be no surprise that the default
+Domain value for a \htmlref{SkyFrame}{SkyFrame} is SKY. Since there is only one sky, you
+probably won't need to change this very often.
+
+\item[SPECTRUM]\mbox{}\\
+Identifies the domain used to describe positions within an
+electro-magnetic spectrum. The AST \htmlref{SpecFrame}{SpecFrame} (\secref{ss:specframes})
+class describes positions within this domain, allowing a wide range of
+different coordinate systems to be used (frequency, wavelength,
+\emph{etc}). The default Domain value for a SpecFrame is SPECTRUM.
+
+\item[TIME]\mbox{}\\
+Identifies the domain used to describe moments in time. The AST \htmlref{TimeFrame}{TimeFrame}
+class describes positions within this domain, allowing a wide range of
+different coordinate systems and timescales to be used. The default Domain
+value for a TimeFrame is TIME.
+
+\end{description}
+\end{quote}
+
+Although we have drawn a necessary distinction here between the GRID
+and PIXEL domains, we will continue to refer in general terms to image
+``pixels'' and ``pixel coordinates'' whenever this distinction is not
+important. This should not be taken to imply that the GRID convention
+for numbering pixels is excluded---in fact, it is usually to be
+preferred (at the level of data handling being discussed in this
+document) and we recommend it.
+
+\subsection{\label{ss:frameunits}The Unit Attribute}
+Each axis of a \htmlref{Frame}{Frame} has a Unit attribute which holds the physical units used
+to describe positions on the axis. The index of the axis to which the
+attribute refers should normally be placed in parentheses following the
+attribute name (``Unit(2)'' for instance). However, if the Frame has only
+a single axis, then the axis index can be omitted.
+
+In versions of AST prior to version 2.0, the Unit attribute was nothing
+more than a descriptive string intended purely for human readers---no
+part of the AST system used the Unit string for any purpose (other than
+inclusion in axis labels produced by the \htmlref{Plot}{Plot} class). In particular, no
+account was taken of the Unit attribute when finding the \htmlref{Mapping}{Mapping} between
+two Frames. Thus if the conversion between a pair of 1-dimensional Frames
+representing velocity was found (using
+\htmlref{AST\_CONVERT}{AST\_CONVERT}
+) the returned Mapping would always be a \htmlref{UnitMap}{UnitMap}, even if the Unit
+attributes of the two Frames were ``km/h'' and ``m/s''. This behaviour is
+referred to below as a \emph{passive} Unit attribute.
+
+As of AST version 2.0, a facility exists which allows the Unit attribute
+to be \emph{active}; that is, differences in the
+Unit attribute may be taken into account when finding the Mapping between
+two Frames. In order to minimise the risk of breaking older software, the
+\emph{default} behaviour of simple Frames and SkyFrames is unchanged from
+previous versions (\emph{i.e.} they have passive Unit attributes). However,
+the new
+routines \htmlref{AST\_SETACTIVEUNIT}{AST\_SETACTIVEUNIT} and \htmlref{AST\_GETACTIVEUNIT}{AST\_GETACTIVEUNIT}
+allow this default behaviour to be changed. The \htmlref{SpecFrame}{SpecFrame} and \htmlref{TimeFrame}{TimeFrame}
+classes \emph{always} have an active Unit attribute (attempts to change this
+are ignored).
+
+For instance, consider the above example of two 1-dimensional Frames
+describing velocity. These Frames can be created as follows:
+
+\small
+\begin{terminalv}
+      INTEGER FRAME1, FRAME2
+
+      FRAME1 = AST_FRAME( 1, 'Domain=VELOCITY,Unit=km/h' )
+      FRAME2 = AST_FRAME( 1, 'Domain=VELOCITY,Unit=m/s' )
+
+\end{terminalv}
+\normalsize
+
+By default, these Frames have passive Unit attributes, and so an attempt
+to find a Mapping between them would ignore the difference in their Unit
+attributes and return a unit Mapping. To avoid this, we indicate that we
+want these Frames to have \emph{active} Unit attributes, as follows:
+
+\small
+\begin{terminalv}
+      CALL AST_SETACTIVEUNIT( FRAME1, .TRUE., STATUS )
+      CALL AST_SETACTIVEUNIT( FRAME2, .TRUE., STATUS )
+\end{terminalv}
+\normalsize
+
+If we then find the Mapping between them as follows:
+
+\small
+\begin{terminalv}
+      INTEGER CVT
+      ...
+      CVT = AST_CONVERT( FRAME1, FRAME2, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+the Mapping contained within the \htmlref{FrameSet}{FrameSet} returned by
+AST\_CONVERT
+will be a one-dimensional \htmlref{ZoomMap}{ZoomMap} which simply scales its input (a
+velocity in $km/h$) by a factor of 0.278 to create its output (a velocity
+in $m/s$).
+
+In fact we need not have set the Unit attribute active in FRAME1
+since the behaviour of AST\_CONVERT is determined by its TO Frame
+(the second Frame argument).
+
+\subsubsection{\label{ss:unitsyntax}The Syntax for Unit Strings}
+Conversion between units systems relies on the use of a specific syntax
+for the Unit attribute. If the value of the Unit attribute does not
+conform to this syntax, then an error will be reported if an attempt is
+made to use it to determine an inter-unit \htmlref{Mapping}{Mapping} (this will never happen
+if the Unit attribute is \emph{passive}).
+
+The adopted syntax is that described in FITS-WCS paper I "Representation
+of World Coordinate in FITS" by Greisen \& Calabretta. We distinguish
+here between ``basic'' units and ``derived'' units: derived units are
+defined in terms of other units (either derived or basic), whereas basic
+units have no such definitions. Derived units may be represented by their
+own \emph{symbol} (\emph{e.g.} ``Jy''---the Jansky) or by a
+\emph{mathematical expression} which combines other symbols and constants
+to form a definition of the unit (\emph{e.g.} ``km/s''---kilometres per
+second). Unit symbols may be prefixed by a string representing a standard
+multiple or sub-multiple.
+
+In addition to the unit symbols listed in FITS-WCS Paper I, any other
+arbitrary unit symbol may be used, with the proviso that it will not be
+possible to convert between Frames using such units. The exception to
+this is if both Frames refer to the same unknown unit string. For instance,
+an axis with unknown unit symbol "flop" \emph{could} be converted to an axis
+with  unit "Mflop" (Mega-flop).
+
+Unit symbols (optionally prefixed with a multiple or sub-multiple) can be
+combined together using a limited range of mathematical operators and
+functions, to produce new units. Such expressions may also contain
+parentheses and numerical constants (these may optionally use
+``scientific'' notation including an ``E'' character to represent the
+power of 10).
+
+The following tables list the symbols for the basic and derived units which
+may be included in a units string, the standard prefixes for multiples
+and sub-multiples, and the strings which may be used to represent
+mathematical operators and functions.
+
+\begin{table}[htbp]
+\begin{center}
+\begin{tabular}{|l|l|l|}
+\hline
+\multicolumn{3}{|c|}{{\large Basic units}} \\ \hline
+\multicolumn{1}{|c|}{Quantity} & \multicolumn{1}{|c|}{Symbol} &
+\multicolumn{1}{c|}{\htmlref{Full}{Full} Name} \\ \hline
+length              & m   & metre \\
+mass                & g   & gram \\
+time                & s   & second \\
+plane angle         & rad & radian \\
+solid angle         & sr  & steradian \\
+temperature         & K   & Kelvin \\
+electric current    & A   & Ampere \\
+amount of substance & mol & mole \\
+luminous intensity  & cd  & candela \\
+\hline
+\end{tabular}
+\end{center}
+\end{table}
+
+\begin{table}[htbp]
+\begin{center}
+\begin{small}
+\begin{tabular}{|l|l|l|l|}
+\hline
+\multicolumn{4}{|c|}{{\large Derived units}} \\ \hline
+\multicolumn{1}{|c|}{Quantity} & \multicolumn{1}{|c|}{Symbol} &
+\multicolumn{1}{c|}{Full Name} & \multicolumn{1}{c|}{Definition} \\ \hline
+area & barn & barn & 1.0E-28 m**2 \\
+area & pix & pixel & \\
+area & pixel & pixel & \\
+electric capacitance & F & Farad & C/V \\
+electric charge & C & Coulomb & A s \\
+electric conductance & S & Siemens & A/V \\
+electric potential & V & Volt & J/C \\
+electric resistance & Ohm & Ohm & V/A \\
+energy & J & Joule & N m \\
+energy & Ry & Rydberg & 13.605692 eV \\
+energy & eV & electron-Volt & 1.60217733E-19 J \\
+energy & erg & erg & 1.0E-7 J \\
+events & count & count & \\
+events & ct & count & \\
+events & ph & photon & \\
+events & photon & photon & \\
+flux density & Jy & Jansky & 1.0E-26 W /m**2 /Hz \\
+flux density & R & Rayleigh & 1.0E10/(4*PI) photon.m**-2 /s/sr \\
+flux density & mag & magnitude & \\
+force & N & Newton & kg m/s**2 \\
+frequency & Hz & Hertz & 1/s \\
+illuminance & lx & lux & lm/m**2 \\
+inductance & H & Henry & Wb/A \\
+length & AU & astronomical unit & 1.49598E11 m \\
+length & Angstrom & Angstrom & 1.0E-10 m \\
+length & lyr & light year & 9.460730E15 m \\
+length & pc & parsec & 3.0867E16 m \\
+length & solRad & solar radius & 6.9599E8 m \\
+luminosity & solLum & solar luminosity & 3.8268E26 W \\
+luminous flux & lm & lumen & cd sr \\
+magnetic field & G & Gauss & 1.0E-4 T \\
+magnetic flux & Wb & Weber & V s \\
+mass & solMass & solar mass & 1.9891E30 kg \\
+mass & u & unified atomic mass unit & 1.6605387E-27 kg \\
+magnetic flux density & T & Tesla & Wb/m**2 \\
+plane angle  & arcmin & arc-minute & 1/60 deg \\
+plane angle  & arcsec & arc-second & 1/3600 deg \\
+plane angle  & mas & milli-arcsecond & 1/3600000 deg \\
+plane angle & deg & degree & pi/180 rad \\
+power & W & Watt & J/s \\
+pressure, stress & Pa & Pascal & N/m**2 \\
+time  & a & year & 31557600 s \\
+time  & d & day & 86400 s \\
+time  & h & hour & 3600 s \\
+time  & yr & year & 31557600 s \\
+time  & min & minute & 60 s \\
+      & D & Debye & 1.0E-29/3 C.m \\
+\hline
+\end{tabular}
+\end{small}
+\end{center}
+\end{table}
+
+\begin{table}[htbp]
+\begin{center}
+\begin{tabular}{|lll|lll|}
+\hline
+\multicolumn{6}{|c|}{{\large Prefixes for multiples \&
+sub-multiples}} \\ \hline
+\multicolumn{1}{|c}{Sub-multiple} & \multicolumn{1}{c}{Name} &
+\multicolumn{1}{c|}{Prefix} &
+\multicolumn{1}{|c}{Sub-multiple} & \multicolumn{1}{c}{Name} &
+\multicolumn{1}{c|}{Prefix} \\ \hline
+$10^{-1}$ & deci & d & $10$ & deca & da \\
+$10^{-2}$ & centi & c & $10^{2}$ & hecto & h \\
+$10^{-3}$ & milli & m & $10^{3}$ & kilo & k \\
+$10^{-6}$ & micro & u & $10^{6}$ & mega & M \\
+$10^{-9}$ & nano & n & $10^{9}$ & giga & G \\
+$10^{-12}$ & pico & p & $10^{12}$ & tera & T \\
+$10^{-15}$ & femto & f & $10^{15}$ & peta & P \\
+$10^{-18}$ & atto & a & $10^{18}$ & exa & E \\
+$10^{-21}$ & zepto & z & $10^{21}$ & zetta & Z \\
+$10^{-24}$ & yocto & y & $10^{24}$ & yotta & Y \\
+\hline
+\end{tabular}
+\end{center}
+\end{table}
+
+\begin{table}[htbp]
+\begin{center}
+\begin{tabular}{|l|l|}
+\hline
+\multicolumn{2}{|c|}{{\large Mathematical operators \& functions}} \\
+\hline
+\multicolumn{1}{|c|}{String} & \multicolumn{1}{|c|}{Meaning} \\ \hline
+sym1 sym2           & multiplication (a space) \\
+sym1*sym2           & multiplication (an asterisk) \\
+sym1.sym2           & multiplication (a dot) \\
+sym1/sym2           & division \\
+sym1**y             & exponentiation ($y$ must be a numerical constant)\\
+sym1\verb+^+y       & exponentiation ($y$ must be a numerical constant)\\
+log(sym1)           & common logarithm \\
+ln(sym1)            & natural logarithm \\
+exp(sym1)           & exponential \\
+sqrt(sym1)          & square root \\
+\hline
+\end{tabular}
+\end{center}
+\end{table}
+
+\subsubsection{Side-effects of Changing the Unit attribute}
+If an \htmlref{Axis}{Axis} has an active Unit attribute, changing its value (either by
+setting a new value or by clearing it so that the default value is
+re-instated) may cause the Label and Symbol attributes to be changed
+accordingly. For instance, if an Axis has Unit, Label and Symbol of ``Hz'',
+``Frequency'' and ``nu'', then changing its Unit attribute to ``log(Hz)''
+will cause AST to change its Label and Symbol to ``log(Frequency)'' and
+``Log(nu)''. These changes are only made if the Unit attribute is active,
+and a \htmlref{Mapping}{Mapping} can be found from the old units to the new units. On the other
+ hand, changing the Unit from ``Hz'' to ``MHz'' would not cause any change
+to the Label or Symbol attributes.
+
+\cleardoublepage
+\section{\label{ss:skyframes}Celestial Coordinate Systems (SkyFrames)}
+
+A \htmlref{Frame}{Frame} which is specialised for representing coordinate systems on
+the celestial sphere is obviously of great importance in
+astronomy. The \htmlref{SkyFrame}{SkyFrame} is such a Frame. In this section we examine
+the additional properties and behaviour of a SkyFrame that distinguish
+it from a basic Frame (\secref{ss:frames}).
+
+\subsection{The SkyFrame Model}
+
+A \htmlref{SkyFrame}{SkyFrame} is, of course, a \htmlref{Frame}{Frame} (\secref{ss:frames}) and also a
+\htmlref{Mapping}{Mapping} (\secref{ss:mappings}), so it inherits all the properties and
+behaviour of these two ancestral classes.  When used as a Mapping, a
+SkyFrame implements a unit transformation, exactly like a basic Frame
+(\secref{ss:frameasmapping}) or a \htmlref{UnitMap}{UnitMap}, so this aspect of its
+behaviour is not of great importance.
+
+When used as a Frame, however, a SkyFrame represents a 2-dimensional
+\emph{spherical} coordinate system, in which the shortest distance
+between two points is a great circle.  A SkyFrame therefore always has
+exactly two axes which represent the longitude and latitude of a
+coordinate system residing on the celestial sphere. Many such
+coordinate systems can be represented by a SkyFrame, as we will see
+shortly.
+
+A SkyFrame can represent any of the commonly used celestial coordinate
+systems. Optionally, the origin of the longitude/latitude system can be
+moved to any specified point in the standard celestial system, allowing
+a SkyFrame to represent offsets from a specified sky position.
+
+When it is first created, a SkyFrame's axes are always in the order
+(longitude,~latitude) but this can be changed, if required, by using the
+\htmlref{AST\_PERMAXES}{AST\_PERMAXES} routine (\secref{ss:permutingaxes}). The order of the axes
+can be determined at any time using the \htmlref{LatAxis}{LatAxis} and \htmlref{LonAxis}{LonAxis} attributes. A
+SkyFrame's coordinate values are always stored as angles in (double
+precision) radians, regardless of the setting of the Unit attribute
+\footnote{The units used for the internal floating-point representation of an
+axis value can be determined by examining the InternalUnit attribute of
+the Frame. For most Frames, the Unit and InternalUnit attributes will be
+equal, but InternalUnit is always set to ``\texttt{rad}'' for SkyFrames.}.
+
+\subsection{Creating a SkyFrame}
+
+The \htmlref{SkyFrame}{SkyFrame} constructor function is particularly simple and a
+SkyFrame with default attributes is created as follows:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER SKYFRAME, STATUS
+
+      STATUS = 0
+
+      ...
+
+      SKYFRAME = AST_SKYFRAME( ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+Such a SkyFrame would represent the default celestial coordinate
+system which, at present, is the ICRS system (the default was "FK5(J2000)"
+in versions of AST prior to 3.0).
+
+\subsection{Specifying a Particular Celestial Coordinate System}
+
+For many purposes, the ICRS coordinate system is perfectly
+adequate. In order to support conversion between a variety of
+celestial coordinate systems, however, you can create SkyFrames that
+represent any of these.
+
+Selection of a particular coordinate system is performed simply by
+setting a value for the \htmlref{SkyFrame}{SkyFrame}'s (character string) \htmlref{System}{System}
+attribute. This setting is most conveniently done when the SkyFrame is
+created. For example, a SkyFrame representing the old FK4~(B1950.0)
+coordinate system would be created by:
+
+\small
+\begin{terminalv}
+      SKYFRAME = AST_SKYFRAME( 'System=FK4', STATUS )
+\end{terminalv}
+\normalsize
+
+Note that specifying ``System$=$FK4'' also changes the associated
+equinox (from J2000.0 to B1950.0). This is because the default value
+of the SkyFrame's \htmlref{Equinox}{Equinox} attribute (\secref{ss:equinoxitem}) depends
+on the System attribute setting.
+
+You may change the System value at any time, although this is not
+usually needed.  The values supported are set out in the attribute's
+description in \appref{ss:attributedescriptions} and include a variety
+of equatorial coordinate systems, together with ecliptic and galactic
+coordinates.
+
+General spherical coordinates are supported by specifying
+``System$=$unknown''. You should note, though, that no \htmlref{Mapping}{Mapping} can be
+created to convert between ``unknown'' coordinates and any of the other
+celestial coordinate systems (see \secref{ss:introducingconversion} ).
+
+\subsection{Attributes which Qualify Celestial Coordinate Systems}
+
+Many celestial coordinate systems have some additional free parameters
+which serve to identify a particular coordinate system from amongst a
+broader class of related coordinate systems. For example, the
+FK5~(J2010.0) system is distinguished from the FK5~(J2000.0)
+system by a different equinox---and the coordinates of a fixed
+astronomical source would have different values when expressed in
+these two systems.
+
+In AST, these free parameters are represented by additional \htmlref{SkyFrame}{SkyFrame}
+attributes, each of which has a default appropriate to
+(\emph{i.e.}\ defined by) the setting of the main \htmlref{System}{System}
+attribute. Each of these \emph{qualifying attributes} may, however, be
+assigned an explicit value so as to select a particular coordinate
+system. Note, it is usually best to assign explicit
+values whenever possible rather than relying on defaults. Attribute
+should only be left at their default value if you ``don't care'' what
+value is used. In certain circumstances (particularly, when aligning two
+Frames), a default value for an attribute may be replaced by the value
+from another similar \htmlref{Frame}{Frame}. Such value replacement can be prevented by
+assigning an explicit value to the attribute, rather than simply relying on
+the default.
+
+
+The main SkyFrame attributes which qualify the System attribute are:
+
+\begin{quote}
+\begin{description}
+
+\item[\label{ss:epochitem}\htmlref{Epoch}{Epoch}]\mbox{}\\
+This attribute is inherited from the Frame class. It gives the moment in
+time when the coordinates are correct for the astronomical source
+under study (usually the date of observation).
+
+\item[\label{ss:equinoxitem}\htmlref{Equinox}{Equinox}]\mbox{}\\
+This value is used to qualify celestial coordinate systems that are
+notionally based on the Earth's equator and/or the ecliptic (the plane
+of the Earth's orbit around the Sun). The position of either of these
+planes is difficult to specify precisely, so in practice a model
+\emph{mean} equator and/or ecliptic are used instead. These, together
+with the point on the sky that defines the coordinate origin (termed
+the \emph{mean equinox}) move with time according to some model which
+smoothes out the more rapid fluctuations. The SkyFrame class supports
+both the old FK4 model and the newer FK5 one.
+
+Coordinates expressed in any of these systems vary with time due to
+movement (by definition) of the coordinate system itself, and must
+therefore be qualified by a moment in time (the \emph{epoch of the mean
+equinox}, or ``equinox'' for short) which specifies the position of
+the model coordinate system on the sky. This is the role of the
+Equinox attribute.
+
+Note that it is quite valid and common to relate the position of a
+source to an equinox other than the date of observation. Usually a
+standard equinox such as J2000.0 is used, meaning that the coordinates
+are referred to axes defined by where the model mean equator and
+ecliptic would lie on the sky at the Julian epoch J2000.0.
+\end{description}
+\end{quote}
+
+For further details of these attributes you should consult their
+descriptions in \appref{ss:attributedescriptions} and for details of
+the System settings for which they are relevant, see the description
+of the System attribute (also in \appref{ss:attributedescriptions}).
+For the interested reader, an excellent overview of celestial
+coordinate systems can also be found in the documentation for the
+SLALIB library (\xref{SUN/67}{sun67}{}).
+
+The value of these qualifying attributes is most conveniently set at
+the same time as the System value, \emph{e.g.}\ when a SkyFrame is
+created. For instance:
+
+\small
+\begin{terminalv}
+      SKYFRAME = AST_SKYFRAME( 'System=Ecliptic, Equinox=J2005.5', STATUS )
+\end{terminalv}
+\normalsize
+
+would create a SkyFrame representing an ecliptic coordinate system
+referred to the mean equinox and ecliptic of Julian epoch J2005.5.
+
+Note that it does no harm to assign values to qualifying attributes
+which are not relevant to the main System value. Any such values are
+stored, but are not used unless the System value is later set so that
+they become relevant.
+
+\subsection{Using Default SkyFrame Attributes}
+
+The default values supplied for many \htmlref{SkyFrame}{SkyFrame} attributes will depend
+on the value of the SkyFrame's \htmlref{System}{System} attribute. In practice, this
+means that there is usually little need to specify many of these
+attributes explicitly unless you have some special requirement. This
+can be illustrated by using \htmlref{AST\_SHOW}{AST\_SHOW} to examine a SkyFrame, as
+follows:
+
+\small
+\begin{terminalv}
+      CALL AST_SHOW( AST_SKYFRAME( 'System=FK4-NO-E, Epoch=1958', STATUS ), STATUS )
+\end{terminalv}
+\normalsize
+
+The output from this might look like the following:
+
+\begin{terminalv}
+ Begin SkyFrame         # Description of celestial coordinate system
+#   Title = "FK4 equatorial coordinates; no E-terms; mean equinox B1950.0;
+epoch B1958.0"   # Title of coordinate system
+    Naxes = 2   # Number of coordinate axes
+#   Domain = "SKY"      # Coordinate system domain
+    Epoch = 1958        # Besselian epoch of observation
+#   Lbl1 = "Right ascension"    # Label for axis 1
+#   Lbl2 = "Declination"        # Label for axis 2
+    System = "FK4-NO-E"         # Coordinate system type
+#   Uni1 = "hh:mm:ss.s"         # Units for axis 1
+#   Uni2 = "ddd:mm:ss"  # Units for axis 2
+#   Dir1 = 0    # Plot axis 1 in reverse direction
+#   Bot2 = -1.5707963267949     # Lowest legal axis value
+#   Top2 = 1.5707963267949      # Highest legal axis value
+    Ax1 =       # Axis number 1
+       Begin SkyAxis    # Celestial coordinate axis
+       End SkyAxis
+    Ax2 =       # Axis number 2
+       Begin SkyAxis    # Celestial coordinate axis
+       End SkyAxis
+ IsA Frame      # Coordinate system description
+#   Eqnox = 1950        # Besselian epoch of mean equinox
+ End SkyFrame
+\end{terminalv}
+
+Note that the defaults (indicated by the ``\verb?#?'' comment
+character at the start of the line) for attributes such as the \htmlref{Title}{Title},
+axis Labels and Format specifiers are all set to values appropriate
+for the particular equatorial coordinate system that the SkyFrame
+represents.
+
+This means, for example, that if we were to use this SkyFrame to
+format a right ascension value stored in radians using \htmlref{AST\_FORMAT}{AST\_FORMAT}
+(\secref{ss:formattingaxisvalues}), it would automatically result in a
+string in sexagesimal notation (such as ``12:14:35.7'') suitable for
+display.  If we changed the value of the SkyFrame's Digits attribute
+(which is inherited from the \htmlref{Frame}{Frame} class), the number of digits
+appearing would also change accordingly.
+
+These choices would be appropriate for a System value of ``FK4-NO-E'',
+but if a different System value were set, the defaults would be
+correspondingly different. For example, ecliptic longitude is
+traditionally expressed in degrees, so setting ``System=ecliptic''
+would result in coordinate values being formatted as degrees by
+default.
+
+Of course, if you do not like any of these defaults, you may always
+over-ride them by setting explicit attribute values yourself.
+
+\subsection{\label{ss:formattingskyaxisvalues}Formatting Celestial Coordinates}
+
+SkyFrames use \htmlref{AST\_FORMAT}{AST\_FORMAT} for formatting coordinate values in the same
+way as other Frames (\secref{ss:formattingaxisvalues}). However, they
+offer a different set of formatting options more appropriate to
+celestial coordinates.
+
+The Digits attribute of a \htmlref{SkyFrame}{SkyFrame} behaves in essentially the same way
+as for a basic \htmlref{Frame}{Frame} (\secref{ss:formattingwithdigits}), so the
+precision with which celestial coordinates are displayed can also be
+adjusted in this way. However, the range of format specifiers that can
+be given for the \htmlref{Format(axis)}{Format(axis)} attribute, and the default format
+resulting from any particular Digits value, is different.
+
+The syntax of SkyFrame format specifiers is detailed under the
+description of the Format(axis) attribute in
+\appref{ss:attributedescriptions}.  Briefly, however, it allows
+celestial coordinates to be expressed either as angles or times and to
+include one or more of the fields:
+
+\begin{quote}
+\begin{itemize}
+\item degrees or hours
+\item arc-minutes or minutes
+\item arc-seconds or seconds
+\end{itemize}
+\end{quote}
+
+with a specified number of decimal places for the final field. A range
+of field separators is also available, as the following examples show:
+
+\begin{quote}
+\begin{center}
+\begin{tabular}{|l|l|}
+\hline
+\textbf{Format Specifier} & \textbf{Example Formatted Value}\\
+\hline \hline
+{\tt{d}} & {\tt{219}}\\
+{\tt{d.3}} & {\tt{219.123}}\\
+{\tt{dm}} & {\tt{219:05}}\\
+{\tt{dm.2}} & {\tt{219:05.44}}\\
+{\tt{dms}} & {\tt{219:05:42}}\\
+{\tt{hms.1}} & {\tt{15:44:13.8}}\\
+{\tt{bdms.2}} & {\tt{219 05 42.81}}\\
+{\tt{lhms.3}} & {\tt{15h44m13.88s}}\\
+{\tt{+zlhms}} & {\tt{+06h10m44s}}\\
+{\tt{ms.1}} & {\tt{13145:42.8}}\\
+{\tt{lmst.3}} & {\tt{876m22.854s}}\\
+{\tt{s.2}} & {\tt{788742.81}}\\
+\hline
+\end{tabular}
+\end{center}
+\end{quote}
+
+Note the following key points:
+
+\begin{itemize}
+\item The required fields are specified using characters chosen from
+either ``dms'' or ``hms'' according to whether the value is to be
+formatted as an angle (in degrees) or a time (in hours).
+
+\item If no degrees or hours field is required, the distinction
+between angle and time may be made by including ``t'' to request time.
+
+\item The number of decimal places (for the final field) is indicated
+using ``\texttt{.}'' followed by an integer. An asterisk can be used in
+place of an integer, in which case the number of decimal places is
+chosen so that the total number of digits in the formatted value is equal
+to the value of the Digits attribute.
+
+\item ``b'' causes fields to be separated by blanks, while ``l''
+causes them to be separated by the appropriate letters (the default
+being a colon).
+
+\item ``z'' causes padding with leading zeros.
+
+\item ``+'' cause a plus sign to be prefixed to positive values
+(negative values always have a minus sign).
+\end{itemize}
+
+The formatting performed by a SkyFrame is also influenced by the
+\htmlref{AsTime(axis)}{AsTime(axis)} attribute, which has a boolean (integer) value for each
+SkyFrame axis.  It determines whether the default format specifier for
+an axis will present values as angles (\emph{e.g.}\ in degrees) if it
+is zero, or as times (\emph{e.g.}\ in hours) if it is non-zero.
+
+The default AsTime value depends on the celestial coordinate system
+which the SkyFrame represents which, in turn, depends on its \htmlref{System}{System}
+attribute value. For example, equatorial longitude values (right
+ascension) are normally expressed in hours, whereas ecliptic
+longitudes are normally expressed in degrees, so their default AsTime
+values will reflect this difference.
+
+The value of the AsTime attribute may be set explicitly to over-ride
+these defaults if required, with the formatting precision being
+determined by the \htmlref{Digits/Digits(axis)}{Digits/Digits(axis)} value. Alternatively, the
+Format(axis) attribute may be set explicitly to specify both the
+format and precision required. Setting an explicit Format value always
+over-rides the effects of both the Digits and AsTime attributes (unless
+the Format value does not specify the required number of decimal places,
+in which case Digits is used to determine the default number of decimal
+places)
+
+\subsection{\label{ss:unformattingskyaxisvalues}Reading Formatted Celestial Coordinates}
+
+The process of converting formatted celestial coordinates, such as
+might be produced by the \htmlref{AST\_FORMAT}{AST\_FORMAT} function
+(\secref{ss:formattingskyaxisvalues}), into numerical (double
+precision) coordinate values is performed by using \htmlref{AST\_UNFORMAT}{AST\_UNFORMAT}
+(\secref{ss:unformattingaxisvalues}) and passing it a pointer to a
+\htmlref{SkyFrame}{SkyFrame}. The use of a SkyFrame means that the range of input formats
+accepted is appropriate to positions on the sky expressed as angles
+and/or times, while the returned value is in radians.
+
+The following describes the forms of celestial coordinate which are
+supported:
+
+\begin{itemize}
+\item You may supply an optional sign, followed by between one and
+three fields representing either degrees, arc-minutes, arc-seconds or
+hours, minutes, seconds (\emph{e.g.}\ ``$-$12~42~03'').
+
+\item Each field should consist of a sequence of one or more digits,
+which may include leading zeros. At most one field may contain a
+decimal point, in which case it is taken to be the final field
+(\emph{e.g.}\ decimal degrees might be given as ``124.707'', while
+degrees and decimal arc-minutes might be given as ``$-$13~33.8'').
+
+\item The first field given may take any value, allowing angles and
+times outside the conventional ranges to be represented. However,
+subsequent fields must have values of less than 60 (\emph{e.g.}
+``720~45~31'' is valid, whereas ``11~45~61'' is not).
+
+\item Fields may be separated by white space or by ``:'' (colon), but
+the choice of separator must be used consistently throughout the
+value. Additional white space may be present around fields and
+separators (\emph{e.g.}\ ``$-$~2:~04~:~7.1'').
+
+\item The following field identification characters may be used as
+separators to replace those above (or may be appended to the final
+field), in order to identify the field to which they are appended:
+
+\begin{quote}
+\begin{tabular}{lll}
+d & -- & degrees \\
+h & -- & hours \\
+m & -- & minutes (of arc or time) \\
+s & -- & seconds (of arc or time) \\
+\texttt{'} & -- & arc-minutes \\
+\texttt{"} & -- & arc-seconds
+\end{tabular}
+\end{quote}
+
+Either lower or upper case may be used.  Fields must be given in order
+of decreasing significance
+(\emph{e.g.}\ ``$-$11D~3\texttt{'}~14.4\texttt{"}'' or ``22h14m11.2s'').
+
+\item The presence of certain field identification characters
+indicates whether the value is to be interpreted as an angle or a time
+(with 24 hours corresponding to 360 degrees), as follows:
+
+\begin{quote}
+\begin{tabular}{lll}
+d & -- & angle \\
+\texttt{'} & -- & angle \\
+\texttt{"} & -- & angle \\
+h & -- & time
+\end{tabular}
+\end{quote}
+
+Incompatible angle/time identification characters may not be mixed
+(\emph{e.g.}\ ``10h14\texttt{'}3\texttt{"}'' is not valid).  The remaining
+field identification characters and separators do not specify a
+preference for an angle or a time and may be used with either.
+
+\item If no preference for an angle or a time is expressed anywhere
+within the value, then it is interpreted as an angle if the Format
+attribute string associated with the SkyFrame axis generates an angle
+and as a time otherwise.  This ensures that values produced by
+AST\_FORMAT (\secref{ss:formattingskyaxisvalues}) are correctly
+interpreted by AST\_UNFORMAT.
+
+\item Fields may be omitted, in which case they default to zero. The
+remaining fields may be identified by using appropriate field
+identification characters (see above) and/or by adding extra colon
+separators (e.g. ``$-$05m13s'' is equivalent to ``$-$:05:13''). If a field
+is not identified explicitly, it is assumed that adjacent fields have
+been given, after taking account of any extra separator
+characters. For example:
+
+\begin{quote}
+\begin{tabular}{lll}
+10d & -- & degrees \\
+10d12 & -- & degrees and arc-minutes \\
+11:14\texttt{"} & -- & arc-minutes and arc-seconds \\
+9h13s & -- & hours and seconds of time \\
+:45:33 & -- & minutes and seconds (of arc or time) \\
+:55: & -- & minutes (of arc or time) \\
+::13 & -- & seconds (of arc or time) \\
+$-$6::2.5 & -- & degrees/hours and seconds (of arc or time) \\
+07m14 & -- & minutes and seconds (of arc or time) \\
+$-$8:14\texttt{'} & -- & degrees and arc-minutes \\
+$-$h3:14 & -- & minutes and seconds of time \\
+h:2.1 & -- & seconds of time
+\end{tabular}
+\end{quote}
+
+\item If fields are omitted in such a way that the remaining ones
+cannot be identified uniquely (e.g. ``01:02''), then the first field
+(either given explicitly or implied by an extra leading colon
+separator) is taken to be the most significant field that AST\_FORMAT
+would produce when formatting a value (using the Format attribute
+associated with the SkyFrame axis). By default, this means that the
+first field will normally be interpreted as degrees or hours. However,
+if this does not result in consistent field identification, then the
+last field (either given explicitly or implied by an extra trailing
+colon separator) is taken to to be the least significant field that
+AST\_FORMAT would produce.
+
+\end{itemize}
+
+This final convention is intended to ensure that values formatted by
+AST\_FORMAT which contain less than three fields will be correctly
+interpreted if read back using AST\_UNFORMAT, even if they do not
+contain field identification characters.  However, it also affects
+other forms of input. For example, if the \htmlref{Format(axis)}{Format(axis)} string were set
+to ``mst.1'' (producing two fields representing minutes and seconds of
+time), then formatted input would be interpreted by AST\_UNFORMAT as
+follows:
+
+\begin{quote}
+\begin{tabular}{lll}
+12 13 & -- & minutes and seconds \\
+12 & -- & minutes \\
+:13 & -- & seconds \\
+$-$18: & -- & minutes \\
+12.8 & -- & minutes \\
+1 2 3 & -- & hours, minutes and seconds \\
+& & \\
+4\texttt{'} & -- & arc-minutes \\
+60::\texttt{"} & -- & degrees \\
+$-$23:\texttt{"} & -- & arc-minutes \\
+$-$33h & -- & hours
+\end{tabular}
+\end{quote}
+
+(in the last four cases, explicit field identification has been given
+which overrides the implicit identification).
+
+Alternatively, if the Format(axis) string were set to ``s.3''
+(producing only an arc-seconds field), then formatted input would be
+interpreted by AST\_UNFORMAT as follows:
+
+\begin{quote}
+\begin{tabular}{lll}
+12.8 & -- & arc-seconds \\
+12 13 & -- & arc-minutes and arc-seconds \\
+:12 & -- & arc-seconds \\
+13: & -- & arc-minutes \\
+1 2 3 & -- & degrees, arc-minutes and arc-seconds
+\end{tabular}
+\end{quote}
+
+In general, if you are preparing formatted input data containing
+celestial coordinates and wish to omit certain fields, then you are
+advised to identify clearly those that you do provide by using the
+appropriate field identification characters and/or extra colon
+separators. This prevents you depending on the implicit field
+identification described above which, in turn, depends on an
+appropriate Format(axis) string having been set.
+
+When writing software, it is also a good idea to set the Format(axis)
+string so that data input will be as simple as possible for the
+user. Unless some special effect is desired, this normally means that
+it should contain ``d'' or ``h'' to ensure that the first field
+entered by the user will be interpreted as degrees or hours, unless
+otherwise identified. This is the normal behaviour unless an explicit
+Format(axis) value has been set to override the default.
+
+\subsection{Representing Offsets from a Specified Sky Position}
+A \htmlref{SkyFrame}{SkyFrame} can be modified so that its longitude and latitude axes are
+referred to an origin at any specified sky position. Such a coordinate
+system is referred to as an ``offset'' coordinate system. First, the \htmlref{System}{System}
+attribute should be set to represent the celestial coordinate system in
+which the origin is to be specified. Then the SkyRef attribute should be
+set to hold the coordinates of the origin within the selected celestial
+coordinate system.
+
+By default, ``north'' in the new offset coordinate system is parallel to
+north in the original celestial coordinate system. However, the direction
+of north in the offset system can be controlled by assigning a value to
+the SkyRefP attribute. This attribute should be assigned the celestial
+coordinates of a point which is on the zero longitude meridian and which
+has non-zero latitude.
+
+By default, the position given by the SkyRef attribute is used as the
+origin of the new longitude/latitude system, but an option exists to use
+it as the north pole of the system instead. This option is controlled by
+the \htmlref{SkyRefIs}{SkyRefIs} attribute. The choice of value for SkyRefIs depends on what
+sort of offset coordinate system you want. Setting SkyRefIs to
+``Origin'' (the default) produces an offset coordinate system which is
+approximately Cartesian close to the specified position. Setting SkyRefIs
+to
+``Pole'' produces an offset coordinate system which is approximately Polar
+close to the specified position.
+
+\cleardoublepage
+\section{\xlabel{ss_specframes}\label{ss:specframes}Spectral Coordinate Systems (SpecFrames)}
+
+The \htmlref{SpecFrame}{SpecFrame} is a \htmlref{Frame}{Frame} which is specialised for representing coordinate
+systems which describe a position within an electro-magnetic spectrum.
+In this section we examine the additional properties and behaviour of a
+SpecFrame that distinguish it from a basic Frame (\secref{ss:frames}).
+
+\subsection{The SpecFrame Model}
+
+As for a \htmlref{SkyFrame}{SkyFrame}, a \htmlref{SpecFrame}{SpecFrame} is a \htmlref{Frame}{Frame} (\secref{ss:frames}) and also a
+\htmlref{Mapping}{Mapping} (\secref{ss:mappings}), so it inherits all the properties and
+behaviour of these two ancestral classes.  When used as a Mapping, a
+SpecFrame implements a unit transformation, exactly like a basic Frame
+(\secref{ss:frameasmapping}) or a \htmlref{UnitMap}{UnitMap}, so this aspect of its
+behaviour is not of great importance.
+
+When used as a Frame, however, a SpecFrame represents a wide range of
+different 1-dimensional coordinate system which can be used to describe
+positions within a spectrum. The options available largely mirror those
+described in the FITS-WCS paper III \emph{Representations of spectral
+coordinates in FITS} (Greisen, Valdes, Calabretta \& Allen).
+
+\subsection{Creating a SpecFrame}
+
+The \htmlref{SpecFrame}{SpecFrame} constructor function is particularly simple and a
+SpecFrame with default attributes is created as follows:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER SPECFRAME, STATUS
+
+      STATUS = 0
+
+      ...
+
+      SPECFRAME = AST_SPECFRAME( ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+Such a SpecFrame would represent the default coordinate system which is
+heliocentric wavelength in metres (i.e. wavelength corrected to take into
+account the Doppler shift caused by the velocity of the observer around the
+sun).
+
+\subsection{Specifying a Particular Spectral Coordinate System}
+
+Selection of a particular coordinate system is performed simply by
+setting a value for the \htmlref{SpecFrame}{SpecFrame}'s (character string) \htmlref{System}{System}
+attribute. This setting is most conveniently done when the SpecFrame is
+created. For example, a SpecFrame representing Energy would be created by:
+
+\small
+\begin{terminalv}
+      SPECFRAME = AST_SPECFRAME( 'System=Energy', STATUS )
+\end{terminalv}
+\normalsize
+
+Note that specifying ``System$=$Energy'' also changes the associated
+Unit (from metres to Joules). This is because the default value
+of the SpecFrame's Unit attribute depends on the System attribute setting.
+
+You may change the System value at any time, although this is not
+usually needed.  The values supported are set out in the attribute's
+description in \appref{ss:attributedescriptions} and include a variety
+of velocity systems, together with frequency, wavelength, energy,
+wave-number, \emph{etc}.
+
+\subsection{Attributes which Qualify Spectral Coordinate Systems}
+
+Many spectral coordinate systems have some additional free parameters
+which serve to identify a particular coordinate system from amongst a
+broader class of related coordinate systems. For example, the
+velocity systems are all parameterised by a rest frequency---the
+frequency which defines zero velocity, and all coordinate systems
+are qualified by a `standard of rest'' which indicates the rest frame to
+which the values refer.
+
+In AST, these free parameters are represented by additional \htmlref{SpecFrame}{SpecFrame}
+attributes, each of which has a default appropriate to
+(\emph{i.e.}\ defined by) the setting of the main \htmlref{System}{System}
+attribute. Each of these \emph{qualifying attributes} may, however, be
+assigned an explicit value so as to select a particular coordinate
+system. Note, it is usually best to assign explicit
+values whenever possible rather than relying on defaults. Attribute
+should only be left at their default value if you ``don't care'' what
+value is used. In certain circumstances (particularly, when aligning two
+Frames), a default value for an attribute may be replaced by the value
+from another similar \htmlref{Frame}{Frame}. Such value replacement can be prevented by
+assigning an explicit value to the attribute, rather than simply relying on
+the default.
+
+
+The main SpecFrame attributes which qualify the System attribute are:
+
+\begin{quote}
+\begin{description}
+
+\item[\htmlref{Epoch}{Epoch}]\mbox{}\\
+This attribute is inherited from the Frame class. It gives the moment in
+time when the coordinates are correct for the astronomical source
+under study (usually the date of observation). It is needed in order to
+calculate the Doppler shift produced by the velocity of the observer
+relative to the centre of the earth, and of the earth relative to the sun.
+
+\item[\htmlref{StdOfRest}{StdOfRest}]\mbox{}\\
+This specifies the rest frame in which the coordinates are correct.
+Transforming between different standards of rest involves taking account
+of the Doppler shift introduced by the relative motion of the two
+standards of rest.
+
+\item[\htmlref{RestFreq}{RestFreq}]\mbox{}\\
+Specifies the frequency which correspond to zero velocity. When setting a
+value for this attribute, the value may be supplied as a wavelength
+(including an indication of the units being used, ``nm'' ``Angstrom'',
+\emph{etc.}), which will be automatically be converted to a frequency.
+
+\item[\htmlref{RefRA}{RefRA}]\mbox{}\\
+Specifies the RA (FK5 J2000) of the source. This is used when converting
+between standards of rest. It specifies the direction along which the
+component of the relative velocity of the two standards of rest is taken.
+
+\item[\htmlref{RefDec}{RefDec}]\mbox{}\\
+Specifies the Dec (FK5 J2000) of the source. Used in conjunction with
+REFRA.
+
+\item[\htmlref{SourceVel}{SourceVel}]\mbox{}\\
+This defines the ``source'' standard of rest. This is a rest frame which
+is moving towards the position given by RefRA and RefDec, at a velocity
+given by SourceVel. The velocity is stored internally as a heliocentric
+velocity, but can be given in any of the other supported standards of rest.
+
+\end{description}
+\end{quote}
+
+For further details of these attributes you should consult their
+descriptions in \appref{ss:attributedescriptions} and for details of
+the System settings for which they are relevant, see the description
+of the System attribute (also in \appref{ss:attributedescriptions}).
+
+Note that it does no harm to assign values to qualifying attributes
+which are not relevant to the main System value. Any such values are
+stored, but are not used unless the System value is later set so that
+they become relevant.
+
+\subsection{Using Default SpecFrame Attributes}
+
+The default values supplied for many \htmlref{SpecFrame}{SpecFrame} attributes will depend
+on the value of the SpecFrame's \htmlref{System}{System} attribute. In practice, this
+means that there is usually little need to specify many of these
+attributes explicitly unless you have some special requirement. This
+can be illustrated by using \htmlref{AST\_SHOW}{AST\_SHOW} to examine a SpecFrame, as
+follows:
+
+\small
+\begin{terminalv}
+      CALL AST_SHOW( AST_SPECFRAME( 'System=Vopt, RestFreq=250 GHz', STATUS ),
+     :               STATUS )
+\end{terminalv}
+\normalsize
+
+The output from this might look like the following:
+
+\begin{terminalv}
+ Begin SpecFrame        # Description of spectral coordinate system
+#   Title = "Optical velocity, rest frequency = 250 GHz"       # Title
+of coordinate system
+    Naxes = 1   # Number of coordinate axes
+#   Domain = "SPECTRUM"         # Coordinate system domain
+#   Epoch = 2000        # Julian epoch of observation
+#   Lbl1 = "Optical velocity"  # Label for axis 1
+    System = "VOPT"     # Coordinate system type
+#   Uni1 = "km/s"       # Units for axis 1
+    Ax1 =       # Axis number 1
+       Begin Axis       # Coordinate axis
+       End Axis
+ IsA Frame      # Coordinate system description
+#   SoR = "Heliocentric"        # Standard of rest
+    RstFrq = 250000000000       # Rest frequency (Hz)
+ End SpecFrame
+\end{terminalv}
+
+Note that the defaults (indicated by the ``\verb?#?'' comment
+character at the start of the line) for attributes such as the \htmlref{Title}{Title},
+axis Labels and Unit specifiers are all set to values appropriate
+for the particular velocity system that the SpecFrame represents.
+
+These choices would be appropriate for a System value of ``Vopt'',
+but if a different System value were set, the defaults would be
+correspondingly different. For example, by default frequency is measured in
+units of GHz, not $km/s$, so setting ``System=freq''
+would change the appropriate line above from:
+
+\begin{terminalv}
+#   Uni1 = "km/s"       # Units for axis 1
+\end{terminalv}
+
+to
+
+\begin{terminalv}
+#   Uni1 = "GHz"        # Units for axis 1
+\end{terminalv}
+
+Of course, if you do not like any of these defaults, you may always
+over-ride them by setting explicit attribute values yourself. For
+instance, you may choose to have your frequency axis expressed in ``kHz''
+rather than ``GHz''. To do this simply set the attribute value as follows:
+
+\small
+\begin{terminalv}
+      CALL AST_SETC( SPECFRAME, 'Unit', 'kHz', STATUS )
+\end{terminalv}
+\normalsize
+
+No error will be reported if you accidentally set an inappropriate Unit value
+(say "J" - Joules)---after all, AST cannot tell what you are about to do,
+and you \emph{may} be about to change the System value to ``Energy''.
+However, an error \emph{will} be reported if you attempt to find a
+conversion between two SpecFrames (for instance using
+\htmlref{AST\_CONVERT}{AST\_CONVERT}
+) if either SpecFrame has a Unit value which is inappropriate for its
+System value.
+
+SpecFrame attributes, like all other attributes, all have default
+value. However, be aware that for some attributes these default values
+can never be more than ``a legal numerical value'' and have no
+astronomical significance. For instance, the \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec} attributes
+(which give the source position) both have a default value of zero. So
+unless your source happens to be at that point (highly unlikely!) you will
+need to set new values. Likewise, the \htmlref{RestFreq}{RestFreq} (rest frequency) attribute
+has an arbitrary default value of 1.0E5 GHz. Some operations are not
+affected by inappropriate values for these attributes (for instance,
+converting from frequency to wavelength, changing axis units, \emph{etc}),
+but some are. For instance, converting from frequency to velocity
+requires a correct rest frequency, moving between different standards of
+rest requires a correct source position. The moral is, always set explicit
+values for as many attributes as possible.
+
+\subsection{\label{ss:creatingspectralcubes}Creating Spectral Cubes}
+You can use a \htmlref{SpecFrame}{SpecFrame} to describe the spectral axis in a data cube
+containing two spatial axes and a spectral axis. To do this you would
+create an appropriate SpecFrame, together with a 2-dimensional \htmlref{Frame}{Frame}
+(often a \htmlref{SkyFrame}{SkyFrame}) to describe the spatial axes. You would then combine
+these two Frames together into a single \htmlref{CmpFrame}{CmpFrame}.
+
+\small
+\begin{terminalv}
+      INTEGER SKYFRAME
+      INTEGER SPECFRAME
+      INTEGER CMPFRAME
+      ...
+      SKYFRAME = AST_SKYFRAME( 'Epoch=J2002', STATUS )
+      SPECFRAME = AST_SPECFRAME( 'System=Freq,StdOfRest=LSRK',
+     :                           STATUS )
+      CMPFRAME = AST_CMPFRAME( SKYFRAME, SPECFRAME, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+In the resulting CmpFrame, axis 1 will be RA, axis 2 will be Dec and axis
+3 will be Frequency. If this is not the order you want, you can permute
+the axes using
+\htmlref{AST\_PERMAXES}{AST\_PERMAXES}.
+
+There is one potential problem with this approach if you are interested in
+unusually high accuracy. Conversion between different standards of rest
+involves taking account of the Doppler shift caused by the relative
+motion of the two standards of rest. At some point this involves finding
+the component of  the relative velocity in the direction of interest.
+For a SpecFrame, this direction is always given by the \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec}
+attributes, even if the SpecFrame is embedded within a CmpFrame as above.
+It would be more appropriate if this ``direction of interest'' was
+specified by the values passed into the CmpFrame on the RA and DEC axes,
+allowing each pixel within a data cube to have a slightly different
+correction for Doppler shift.
+
+Unfortunately, the SpecFrame class cannot do this (since it is purely a
+1-dimensional Frame), and so some small degree of error will be
+introduced when converting between standards of rest, the size of the
+error varying from pixel to pixel. It is hoped that at some point in the
+future a sub-class of CmpFrame (a SpecCubeFrame) will be added to AST which
+allows for this spatial variation in Doppler shift.
+
+The maximum velocity error introduced by this problem is of the order of
+$V*SIN(FOV)$, where $FOV$ is the angular field of view, and $V$ is the
+relative velocity of the two standards of rest. As an example, when
+correcting from the observers rest frame (i.e. the topocentric rest
+frame) to the kinematic local standard of rest the maximum value of $V$
+is about 20 $km/s$, so for 5 arc-minute field of view the maximum
+velocity error introduced by the correction will be about 0.03 $km/s$. As
+another example, the maximum error when correcting from the observers
+rest frame to the local group is about 5 $km/s$ over a 1 degree field of
+view.
+
+\subsection{\label{ss:handlingdualsidebandspectra}Handling Dual-Sideband Spectra}
+Dual sideband super-heterodyne receivers produce spectra in which each channel
+contains contributions from two different frequencies, referred to as the
+``upper sideband frequency'' and the ``lower sideband frequency''. In the
+rest frame of the observer (topocentric), these are related to each other as
+follows:
+
+\begin{quote}
+\begin{small}
+\begin{equation}
+\label{eqn:dsb}
+   f_{lsb} = 2.f_{LO} - f_{usb}
+\end{equation}
+\end{small}
+\end{quote}
+
+where $f_{LO}$ is a fixed frequency known as the ``local oscillator
+frequency''. In other words, the local oscillator frequency is always
+mid-way between any pair of corresponding upper and lower sideband
+frequencies\footnote{Note, this simple relationship only applies if all
+frequencies are topocentric.}. If you want to describe the spectral axis
+of such a spectrum using a \htmlref{SpecFrame}{SpecFrame} you must choose whether you want the
+SpecFrame to describe $f_{lsb}$ or $f_{usb}$ - a basic SpecFrame cannot
+describe both sidebands simultaneously. However, there is a sub-class of
+SpecFrame, called \htmlref{DSBSpecFrame}{DSBSpecFrame}, which overcomes this difficulty.
+
+A DSBSpecFrame has a \htmlref{SideBand}{SideBand} attribute which indicates if the
+DSBSpecFrame is currently being used to describe the upper or lower
+sideband spectral axis. The value of this attribute can be changed at any
+time. If you use the
+\htmlref{AST\_CONVERT}{AST\_CONVERT}
+function to find the \htmlref{Mapping}{Mapping} between two DSBSpecFrames, the setting for
+the two SideBand attributes will be taken into account. Thus, if you take
+a copy of a DSBSpecFrame, toggle its SideBand attribute, and then use
+AST\_CONVERT
+to find a Mapping from the original to the modified copy, the resulting
+Mapping will be of the form of equation \ref{eqn:dsb} (if the
+DSBSpecFrame has its \htmlref{StdOfRest}{StdOfRest} attribute set to ``Topocentric'').
+
+In general, when finding a Mapping between two arbitrary DSBSpecFrames,
+the total Mapping is made of of three parts in series:
+
+\begin{enumerate}
+\item A Mapping which converts the first DSBSpecFrame into its upper
+sideband representation. If the DSBSpecFrame already represents its upper
+sideband, this Mapping will be a \htmlref{UnitMap}{UnitMap}.
+\item A Mapping which converts from the first to the second DSBSpecFrame,
+treating them as if they were both basic SpecFrames. This takes account of
+any difference in units, standard of rest, system, \emph{etc} between the
+two DSBSpecFrames.
+\item A Mapping which converts the second DSBSpecFrame from its upper
+sideband representation to its current sideband. If the DSBSpecFrame
+currently represents its upper sideband, this Mapping will be a UnitMap.
+\end{enumerate}
+
+If an attempt is made to find the Mapping between a DSBSpecFrame and a
+basic SpecFrame, then the DSBSpecFrame will be treated like a basic
+SpecFrame. In other words, the returned Mapping will not be affected by
+the setting of the SideBand attribute (or any of the other attributes
+specific to the DSBSpecFrame class).
+
+In practice, the local oscillator frequency for a dual sideband
+instrument may not be easily available to an observer. Instead, it is
+common practice to specify the spectral position of some central feature
+in the observation (commonly the centre of the instrument passband),
+together with an ``intermediate frequency''. Together, these two values
+allow the local oscillator frequency to be determined. The intermediate
+frequency is the difference between the topocentric frequency at the
+central spectral position and the topocentric frequency of the local
+oscillator. So:
+
+\begin{quote}
+\begin{small}
+\begin{equation}
+\label{eqn:dsb2}
+   f_{LO} = f_{central} + f_{if}
+\end{equation}
+\end{small}
+\end{quote}
+
+The DSBSpecFrame class uses the \htmlref{DSBCentre}{DSBCentre} attribute to specify the central
+spectral position ($f_{central}$), and the \htmlref{IF}{IF} attribute to specify the
+intermediate frequency ($f_{if}$). The DSBCentre value is given and returned
+in the spectral system described by the DSBSpecFrame (thus you do not need to
+calculate the corresponding topocentric frequency yourself - this will be
+done automatically by the DSBSpecFrame when you assign a new value to the
+DSBCentre attribute). The value assigned to the IF attribute should
+always be a topocentric frequency in units of Hz, however a negative
+value may be given to indicate that the DSBCentre value is in the upper
+sideband (that is, if $IF < 0$  then $f_{central} > f_{LO}$). A positive
+value for IF indicates that the DSBCentre value is in the lower sideband
+(that is, if $IF > 0$  then $f_{central} < f_{LO}$).
+
+
+\cleardoublepage
+\section{\xlabel{ss_timeframes}\label{ss:timeframes}Time Systems (TimeFrames)}
+
+The \htmlref{TimeFrame}{TimeFrame} is a \htmlref{Frame}{Frame} which is specialised for representing moments in
+time. In this section we examine the additional properties and behaviour of a
+TimeFrame that distinguish it from a basic Frame (\secref{ss:frames}).
+
+\subsection{The TimeFrame Model}
+
+As for a \htmlref{SkyFrame}{SkyFrame}, a \htmlref{TimeFrame}{TimeFrame} is a \htmlref{Frame}{Frame} (\secref{ss:frames}) and also a
+\htmlref{Mapping}{Mapping} (\secref{ss:mappings}), so it inherits all the properties and
+behaviour of these two ancestral classes.  When used as a Mapping, a
+TimeFrame implements a unit transformation, exactly like a basic Frame
+(\secref{ss:frameasmapping}) or a \htmlref{UnitMap}{UnitMap}, so this aspect of its
+behaviour is not of great importance.
+
+When used as a Frame, however, a TimeFrame represents a wide range of
+different 1-dimensional coordinate system which can be used to describe
+moments in time. Absolute times and relative (i.e. elapsed) times are
+supported (attribute \htmlref{TimeOrigin}{TimeOrigin}), as are a range of different time scales
+(attribute \htmlref{TimeScale}{TimeScale}). An absolute or relative value in any time scale can
+be represented in different forms such as Modified Julian Date, Julian \htmlref{Epoch}{Epoch},
+\emph{etc} (attribute \htmlref{System}{System}). AST extends the definition of these systems to
+allow them to be used with any unit of time (attribute Unit). The TimeFrame
+class also allows times to formatted as either a simple floating point value
+or as a Gregorian date and time of day (attribute Format).
+
+\subsection{Creating a TimeFrame}
+
+The \htmlref{TimeFrame}{TimeFrame} constructor function is particularly simple and a
+TimeFrame with default attributes is created as follows:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER TIMEFRAME, STATUS
+
+      STATUS = 0
+
+      ...
+
+      TIMEFRAME = AST_TIMEFRAME( ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+Such a TimeFrame would represent the default coordinate system which is
+Modified Julian Date (with the usual units of days) in the International
+Atomic Time (TAI) time scale.
+
+\subsection{Specifying a Particular Time System}
+By setting the \htmlref{System}{System} attribute appropriately, the \htmlref{TimeFrame}{TimeFrame} can represent
+Julian Date, Modified Julian Date, Julian \htmlref{Epoch}{Epoch} or Besselian Epoch (the
+time scale is specified by a separate attribute called \htmlref{TimeScale}{TimeScale}).
+
+Selection of a particular coordinate system is performed simply by
+setting a value for the TimeFrame's (character string) System
+attribute. This setting is most conveniently done when the TimeFrame is
+created. For example, a TimeFrame representing Julian Epoch would be created
+by:
+
+\small
+\begin{terminalv}
+      TIMEFRAME = AST_TIMEFRAME( 'System=JEPOCH', STATUS )
+\end{terminalv}
+\normalsize
+
+Note that specifying ``System$=$JEPOCH'' also changes the associated
+default Unit (from days to years). This is because the default value
+of the TimeFrame's Unit attribute depends on the System attribute setting.
+
+You may change the System value at any time, although this is not
+usually needed.  The values supported are set out in the attribute's
+description in \appref{ss:attributedescriptions}.
+
+\subsection{Attributes which Qualify Time Coordinate Systems}
+
+Time coordinate systems require some additional free parameters to identify
+a particular coordinate system from amongst a broader class of related
+coordinate systems. For example, all TimeFrames are qualified by the time
+scale (that is, the physical process used to define the flow of time),
+and some require the position of the observer's clock.
+
+In AST, these free parameters are represented by additional \htmlref{TimeFrame}{TimeFrame}
+attributes, each of which has a default appropriate to (\emph{i.e.}\ defined
+by) the setting of the main \htmlref{System}{System} attribute. Each of these \emph{qualifying
+attributes} may, however, be assigned an explicit value so as to select a
+particular coordinate system. Note, it is usually best to assign explicit
+values whenever possible rather than relying on defaults. Attribute
+should only be left at their default value if you ``don't care'' what
+value is used. In certain circumstances (particularly, when aligning two
+Frames), a default value for an attribute may be replaced by the value
+from another similar \htmlref{Frame}{Frame}. Such value replacement can be prevented by
+assigning an explicit value to the attribute, rather than simply relying on
+the default.
+
+The main TimeFrame attributes which qualify the System attribute are:
+
+\begin{quote}
+\begin{description}
+
+\item[\htmlref{TimeScale}{TimeScale}]\mbox{}\\
+This specifies the time scale.
+
+\item[\htmlref{LTOffset}{LTOffset}]\mbox{}\\
+This specifies the offset from Local Time to UTC in hours (time zones
+east of Greenwich have positive values). Note, AST uses the value as
+supplied without making any correction for daylight saving.
+
+\item[\htmlref{TimeOrigin}{TimeOrigin}]\mbox{}\\
+This specifies the zero point from which time values are measured, within
+the system specified by the System attribute. Thus, a value of zero (the
+default) indicates that time values represent absolute times. Non-zero
+values may be used to indicate that the TimeFrame represents elapsed time
+since the specified origin.
+
+\end{description}
+\end{quote}
+
+For further details of these attributes you should consult their
+descriptions in \appref{ss:attributedescriptions} and for details of
+the System settings for which they are relevant, see the description
+of the System attribute (also in \appref{ss:attributedescriptions}).
+
+Note that it does no harm to assign values to qualifying attributes
+which are not relevant to the main System or TimeScale value. Any such
+values are stored, but are not used unless the System and/or TimeScale
+value is later set so that they become relevant.
+
+\cleardoublepage
+\section{\label{ss:cmpframes}Compound Frames (CmpFrames)}
+
+We now turn to a rather special form of \htmlref{Mapping}{Mapping}, the \htmlref{CmpFrame}{CmpFrame}. The
+Frames we have considered so far have been atomic, in the sense that
+they represent pre-defined elementary physical domains. A CmpFrame,
+however, is a compound \htmlref{Frame}{Frame}. In essence, it is a structure for
+containing other Frames and its purpose is to allow those Frames
+to work together in various combinations while appearing as a single
+\htmlref{Object}{Object}. A CmpFrame's behaviour is therefore not pre-defined, but is
+determined by the other Frames it contains (its ``component'' Frames).
+
+As with compound Mappings, compound Frames can be nested within each
+other, forming arbitrarily complex Frames.
+
+\subsection{Creating a CmpFrame}
+A very common use for a \htmlref{CmpFrame}{CmpFrame} within astronomy is to represent a
+``spectral cube''. This is a 3-dimensional \htmlref{Frame}{Frame} in which one of the axes
+represents position within a spectrum, and the other two axes represent
+position on the sky (or some other spatial domain such as the focal plane
+of a telescope). As an example, we create such a CmpFrame in which axes
+1 and 2 represent Right Ascension and Declination (ICRS), and axis 3
+represents wavelength (these are the default coordinate Systems
+represented by a \htmlref{SkyFrame}{SkyFrame} and a \htmlref{SpecFrame}{SpecFrame} respectively):
+
+\small
+\begin{terminalv}
+      INTEGER SKYFRAME
+      INTEGER SPECFRAME
+      INTEGER CMPFRAME
+      ...
+      SKYFRAME = AST_SKYFRAME( ' ', STATUS )
+      SPECFRAME = AST_SPECFRAME( ' ', STATUS )
+      CMPFRAME = AST_CMPFRAME( SKYFRAME, SPECFRAME, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+If it was desired to make RA and Dec correspond to axes 1 and 3, with
+axis 2 being the spectral axis, then the axes of the CmpFrame created
+above would need to be permuted as follows:
+
+\small
+\begin{terminalv}
+      INTEGER PERM(3)
+      ...
+
+      PERM( 1 ) = 1
+      PERM( 2 ) = 3
+      PERM( 3 ) = 2
+      CALL AST_PERMAXES( CMPFRAME, PERM, STATUS )
+\end{terminalv}
+\normalsize
+
+\subsection{The Attributes of a CmpFrame}
+
+A \htmlref{CmpFrame}{CmpFrame} \emph{is a} \htmlref{Frame}{Frame} and so has all the attributes of a Frame.
+The default value for the \htmlref{Domain}{Domain} attribute for a CmpFrame is formed by
+concatenating the Domains of the two component Frames, separated by a
+minus sign (``-'').\footnote{If both component Frames have blank Domains,
+then the default Domain for the CmpFrame is the string ``CMP''.} The (fixed)
+value for its \htmlref{System}{System} attribute is ``Compound''.\footnote{Any attempt to
+change the System value of a CmpFrame is ignored.} A CmpFrame has no
+further attributes over and above those common to all Frames. However,
+attributes of the two component Frames can be accessed as if they were
+attributes of the CmpFrame, as described below.
+
+Frame attributes which are specific to individual axes (such as Label(2),
+Format(1), \emph{etc}) simply mirror the corresponding axes of the
+relevant component Frame. That is, if the ``Label(2)'' attribute of a
+CmpFrame is accessed, the CmpFrame will forward the access request to the
+component Frame which contains axis 2. Thus, default values for axis
+attributes will be the same as those provided by the component Frames.
+
+An axis index can optionally be appended to the name of Frames attributes
+which do not normally have such an index (System, Domain, \htmlref{Epoch}{Epoch}, \htmlref{Title}{Title},
+\emph{etc}). If this is done, the access request is forwarded to the
+component Frame containing the indicated axis. For instance, if a
+CmpFrame contains a \htmlref{SpecFrame}{SpecFrame} and a \htmlref{SkyFrame}{SkyFrame} in that order, and the axes
+have not been permuted, then getting the value of attribute ``System'' will
+return  ``Compound'' as mentioned above (that is, the System value of the
+CmpFrame as a whole), whereas getting the value of attribute
+``System(1)'' will return ``Spectral''(that is, the System value of the
+component Frame containing axis 1 --- the SpecFrame).
+
+This technique is not limited to attributes common to all Frames. For
+instance, the SkyFrame class defines an attribute called \htmlref{Equinox}{Equinox} which is
+not held by other classes of Frames. To set a value for the Equinox
+attribute of the SkyFrame contained within the above CmpFrame, assign the
+value to the ``Equinox(2)'' attribute of the CmpFrame. Since the SkyFrame
+defines both axes 2 and 3 of the CmpFrame, we could equivalently have set
+a value for ``Equinox(3)'' since this would also result in the attribute
+access being forwarded to the SkyFrame.
+
+Finally, if an attribute is not qualified by a axis index, attempts will
+be made to access it using each of the CmpFrame axes in turn. Using the
+above example of the spectral cube, if an attempt was made to get the
+value of attribute ``Equinox'' (with no axis index), each axis in turn
+would be used. Since axis 1 is contained within a SpecFrame, the first
+attempt would fail since the SpecFrame class does not have an Equinox
+attribute. However, the second attempt would succeed because axis 2 is
+contained within a SkyFrame which \emph{does} have an Equinox attribute. Thus
+the returned attribute value would be that obtained from the SkyFrame
+containing axis 2. When getting or testing an attribute value, the
+returned value is determined by the \emph{first} axis which recognises
+the attribute. When setting an attribute value, \emph{all} axes
+which recognises the attribute have the attribute value set to the given
+value. Likewise, when clearing an attribute value, all axes
+which recognises the attribute have the attribute value cleared.
+
+\cleardoublepage
+\section{\label{ss:introducingconversion}An Introduction to Coordinate System Conversions}
+
+In this section, we start to look at techniques for converting between
+different coordinate systems.  At this stage, the tools we have available
+are Frames (\secref{ss:frames}), SkyFrames (\secref{ss:skyframes}),
+SpecFrames (\secref{ss:specframes}), TimeFrames (\secref{ss:timeframes}) and
+various Mappings (\secref{ss:mappings}). These are sufficient to allow us to
+begin examining the problem, but more sophisticated approaches will also emerge
+later (\secref{ss:framesetconverting}).
+
+\subsection{\label{ss:convertingskyframes}Converting between Celestial Coordinate Systems}
+
+We begin by examining how to convert between two celestial coordinate
+systems represented by SkyFrames, as this is both an illuminating and
+practical example.  Consider the problem of converting celestial
+coordinates between:
+
+\begin{enumerate}
+\item The old FK4 system, with no E terms, a Besselian epoch of
+1958.0 and a Besselian equinox of 1960.0.
+
+\item An ecliptic coordinate system based on the mean equinox and
+ecliptic of Julian epoch 2010.5.
+\end{enumerate}
+
+This example is arbitrary but not completely unrealistic. Unless you
+already have expertise with such conversions, you are unlikely to find
+it straightforward.
+
+Using AST, we begin by creating two SkyFrames to represent these
+coordinate systems, as follows:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER SKYFRAME1, SKYFRAME2, STATUS
+
+      STATUS = 0
+
+      ...
+
+      SKYFRAME1 = AST_SKYFRAME( 'System=FK4-NO-E, Epoch=B1958, Equinox=B1960', STATUS )
+      SKYFRAME2 = AST_SKYFRAME( 'System=Ecliptic, Equinox=J2010.5', STATUS )
+\end{terminalv}
+\normalsize
+
+Note how specifying the coordinate systems consists simply of
+initialising the attributes of each \htmlref{SkyFrame}{SkyFrame} appropriately.  The next
+step is to find a way of converting between these SkyFrames. This is
+done using \htmlref{AST\_CONVERT}{AST\_CONVERT}, as follows:
+
+\small
+\begin{terminalv}
+      INTEGER CVT
+
+      ...
+
+      CVT = AST_CONVERT( SKYFRAME1, SKYFRAME2, ' ', STATUS )
+      IF ( CVT .EQ. AST__NULL ) THEN
+         <conversion is not possible>
+      ELSE
+         <conversion is possible>
+      END IF
+\end{terminalv}
+\normalsize
+
+The third argument of AST\_CONVERT is not used here and should be a
+blank string.
+
+AST\_CONVERT will return a null result, AST\_\_NULL (as defined in the
+AST\_PAR include file), if conversion is not possible. In this
+example, conversion is possible, so it will return a pointer to a new
+\htmlref{Object}{Object} that describes the conversion.
+
+The Object returned is called a \htmlref{FrameSet}{FrameSet}. We have not discussed
+FrameSets yet (\secref{ss:framesets}), but for the present purposes we
+can consider them simply as Objects that can behave both as Mappings
+and as Frames. It is the FrameSet's behaviour as a \htmlref{Mapping}{Mapping} in which we
+are mainly interested here, because the Mapping it implements is the
+one we require---\emph{i.e.}\ it converts between the two celestial
+coordinate systems (\secref{ss:framesetsfromconvert}).
+
+For example, if ALPHA1 and DELTA1 are two arrays containing the
+longitude and latitude, in radians, of N points on the sky in the
+original coordinate system (corresponding to SKYFRAME1), then they
+could be converted into the new coordinate system (represented by
+SKYFRAME2) as follows:
+
+\small
+\begin{terminalv}
+      INTEGER N
+      DOUBLE PRECISION ALPHA1( N ), DELTA1( N )
+      DOUBLE PRECISION ALPHA2( N ), DELTA2( N )
+
+      ...
+
+      CALL AST_TRAN2( CVT, N, ALPHA1, DELTA1, .TRUE., ALPHA2, DELTA2, STATUS )
+\end{terminalv}
+\normalsize
+
+The new coordinates are returned \emph{via} the ALPHA2 and DELTA2
+arrays.  To transform coordinates in the opposite direction, we simply
+invert the 5th (logical) argument to \htmlref{AST\_TRAN2}{AST\_TRAN2}, as follows:
+
+\small
+\begin{terminalv}
+      CALL AST_TRAN2( CVT, N, ALPHA2, DELTA2, .FALSE., ALPHA1, DELTA1, STATUS )
+\end{terminalv}
+\normalsize
+
+The FrameSet returned by AST\_CONVERT also contains information about
+the SkyFrames used in the conversion
+(\secref{ss:framesetsfromconvert}). As we mentioned above, a FrameSet
+may be used as a \htmlref{Frame}{Frame} and in this case it behaves like the
+``destination'' Frame used in the conversion (\emph{i.e.}\ like
+SKYFRAME2). We could therefore use the CVT FrameSet to calculate the
+distance between two points (with coordinates in radians) in the
+destination coordinate system, using \htmlref{AST\_DISTANCE}{AST\_DISTANCE}:
+
+\small
+\begin{terminalv}
+      DOUBLE PRECISION DISTANCE, POINT1( 2 ), POINT2( 2 )
+
+      ...
+
+      DISTANCE = AST_DISTANCE( CVT, POINT1, POINT2, STATUS )
+\end{terminalv}
+\normalsize
+
+and the result would be the same as if the SKYFRAME2 SkyFrame had been
+used.
+
+Another way to see how the FrameSet produced by astConvert retains
+information about the coordinate systems involved is to set its \htmlref{Report}{Report}
+attribute (inherited from the Mapping class) so that it displays the
+coordinates before and after conversion (\secref{ss:transforming}):
+
+\small
+\begin{terminalv}
+      CALL AST_SET( CVT, 'Report=1', STATUS )
+      CALL AST_TRAN2( CVT, N, ALPHA1, DELTA1, .TRUE., ALPHA2, DELTA2, STATUS )
+\end{terminalv}
+\normalsize
+
+The output from this might look like the following:
+
+\begin{terminalv}
+(2:06:03.0, 34:22:39) --> (42.1087, 20.2717)
+(2:08:20.6, 35:31:24) --> (43.0197, 21.1705)
+(2:10:38.1, 36:40:09) --> (43.9295, 22.0716)
+(2:12:55.6, 37:48:55) --> (44.8382, 22.9753)
+(2:15:13.1, 38:57:40) --> (45.7459, 23.8814)
+(2:17:30.6, 40:06:25) --> (46.6528, 24.7901)
+(2:19:48.1, 41:15:11) --> (47.5589, 25.7013)
+(2:22:05.6, 42:23:56) --> (48.4644, 26.6149)
+(2:24:23.1, 43:32:41) --> (49.3695, 27.5311)
+(2:26:40.6, 44:41:27) --> (50.2742, 28.4499)
+\end{terminalv}
+
+Here, we see that the input FK4 equatorial coordinate values (given in
+radians) have been formatted automatically in sexagesimal notation
+using the conventional hours for right ascension and degrees for
+declination. Conversely, the output ecliptic coordinates are shown in
+decimal degrees, as is conventional for ecliptic coordinates. Both are
+displayed using the default precision of 7 digits.\footnote{The
+leading digit is zero and is therefore not seen in this particular
+example.}
+
+In fact, the CVT FrameSet has access to all the information in the
+original SkyFrames which were passed to AST\_CONVERT. If you had set a
+new Digits attribute value for either of these, the formatting above
+would reflect the different precision you requested by displaying a
+greater or smaller number of digits.
+
+
+\subsection{\label{ss:convertingspecframes}Converting between Spectral Coordinate Systems}
+The principles described in the previous section for converting between
+celestial coordinate systems also apply to the task of converting between
+spectral coordinate systems. As an example, let's look at how we might
+convert between frequency measured in $GHz$ as measured in the rest frame
+of the telescope, and radio velocity measured in $km/s$ measured with
+respect the kinematic Local Standard of Rest.
+
+First we create a default \htmlref{SpecFrame}{SpecFrame}, and then set its attributes to
+describe the required radio velocity system (this is slightly more
+convenient, given the relatively large number of attributes, than
+specifying the attribute values in a single string such as would be
+passed to the SpecFrame constructor). We then take a copy of this
+SpecFrame, and change the attribute values so that the copy describes the
+original frequency system (modifying a copy, rather than creating a new
+SpecFrame from scratch, avoids the need to specify the epoch, reference
+position, \emph{etc} a second time since they are all inherited by the copy):
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER SPECFRAME1, SPECFRAME2, STATUS
+
+      STATUS = 0
+
+      ...
+
+      SPECFRAME1 = AST_SPECFRAME( ' ', STATUS )
+      CALL AST_SETC( SPECFRAME1, 'System=vradio', STATUS )
+      CALL AST_SETC( SPECFRAME1, 'Unit=km/s', STATUS )
+      CALL AST_SETC( SPECFRAME1, 'Epoch=1996-Oct-2 12:13:56.985',
+     :               STATUS )
+      CALL AST_SETC( SPECFRAME1, 'ObsLon=W155:28:18', STATUS )
+      CALL AST_SETC( SPECFRAME1, 'ObsLat=N19:49:34', STATUS )
+      CALL AST_SETC( SPECFRAME1, 'RefRA=18:14:50.6', STATUS )
+      CALL AST_SETC( SPECFRAME1, 'RefDec=-4:40:49', STATUS )
+      CALL AST_SETC( SPECFRAME1, 'RestFreq=230.538 GHz', STATUS )
+      CALL AST_SETC( SPECFRAME1, 'StdOfRest=LSRK', STATUS )
+
+      SPECFRAME2 = AST_COPY( SPECFRAME1, STATUS )
+      CALL AST_SETC( SPECFRAME1, 'System=freq', STATUS )
+      CALL AST_SETC( SPECFRAME1, 'Unit=GHz', STATUS )
+      CALL AST_SETC( SPECFRAME1, 'StdOfRest=Topocentric', STATUS )
+
+\end{terminalv}
+\normalsize
+
+Note, the fact that a SpecFrame has only a single axis means that we were
+able to refer to the Unit attribute without an axis index. The other
+attributes are: the time of of observation (\htmlref{Epoch}{Epoch}), the geographical
+position of the telescope (\htmlref{ObsLat}{ObsLat} \& \htmlref{ObsLon}{ObsLon}), the position of the source
+on the sky (\htmlref{RefRA}{RefRA} \& \htmlref{RefDec}{RefDec}), the rest frequency (\htmlref{RestFreq}{RestFreq}) and the
+standard of rest (\htmlref{StdOfRest}{StdOfRest}).
+
+The next step is to find a way of converting between these SpecFrames. We
+use exactly the same code that we did in the previous section where we were
+converting between celestial coordinate systems:
+
+\small
+\begin{terminalv}
+      INTEGER CVT
+
+      ...
+
+      CVT = AST_CONVERT( SPECFRAME1, SPECFRAME2, ' ', STATUS )
+      IF ( CVT .EQ. AST__NULL ) THEN
+         <conversion is not possible>
+      ELSE
+         <conversion is possible>
+      END IF
+\end{terminalv}
+\normalsize
+
+A before, this will give us a \htmlref{FrameSet}{FrameSet} (assuming conversion is possible,
+which should always be the case for our example), and we can use the
+FrameSet to convert between the two spectral coordinate systems. We use
+\htmlref{AST\_TRAN1}{AST\_TRAN1} in place of \htmlref{AST\_TRAN2}{AST\_TRAN2}
+since a SpecFrame has only one axis (unlike a \htmlref{SkyFrame}{SkyFrame} which has two).
+
+For example, if FRQ is an array containing the observed frequency, in
+GHz, of N spectral channels (describe by SPECFRAME1), then they
+could be converted into the new coordinate system (represented by
+SPECFRAME2) as follows:
+
+\small
+\begin{terminalv}
+      INTEGER N
+      DOUBLE PRECISION FRQ( N )
+      DOUBLE PRECISION VEL( N )
+
+      ...
+
+      CALL AST_TRAN1( CVT, N, FRQ, .TRUE., VEL, STATUS )
+\end{terminalv}
+\normalsize
+
+The radio velocity values are returned in the VEL array.
+
+\subsection{Converting between Time Coordinate Systems}
+All the principles outlined in the previous section about aligning
+spectral cocordinate systems (SpecFrames) can be applied directly to the
+problem of aligning time coordinate systems (TimeFrames).
+
+\subsection{\label{ss:convertingpermutedaxes}Handling SkyFrame Axis Permutations}
+
+We can illustrate an important point if we swap the axis order of
+either \htmlref{SkyFrame}{SkyFrame} in the example above (\secref{ss:convertingskyframes})
+before identifying the conversion. Let's assume we use \htmlref{AST\_PERMAXES}{AST\_PERMAXES}
+(\secref{ss:permutingaxes}) to do this to the second SkyFrame, before
+applying \htmlref{AST\_CONVERT}{AST\_CONVERT}, as follows:
+
+\small
+\begin{terminalv}
+      INTEGER PERM( 2 )
+      DATA PERM / 2, 1 /
+
+      ...
+
+      CALL AST_PERMAXES( SKYFRAME2, PERM, STATUS )
+      CVT = AST_CONVERT( SKYFRAME1, SKYFRAME2, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+Now, the destination SkyFrame system no longer represents the
+coordinate system:
+
+\begin{quote}
+(ecliptic~longitude, ecliptic~latitude)
+\end{quote}
+
+but instead represents the transposed system:
+
+\begin{quote}
+(ecliptic~latitude, ecliptic~longitude)
+\end{quote}
+
+As a consequence, when we use the \htmlref{FrameSet}{FrameSet} returned by AST\_CONVERT to
+apply a coordinate transformation, we obtain something like the
+following:
+
+\begin{terminalv}
+(2:06:03.0, 34:22:39) --> (20.2717, 42.1087)
+(2:08:20.6, 35:31:24) --> (21.1705, 43.0197)
+(2:10:38.1, 36:40:09) --> (22.0716, 43.9295)
+(2:12:55.6, 37:48:55) --> (22.9753, 44.8382)
+(2:15:13.1, 38:57:40) --> (23.8814, 45.7459)
+(2:17:30.6, 40:06:25) --> (24.7901, 46.6528)
+(2:19:48.1, 41:15:11) --> (25.7013, 47.5589)
+(2:22:05.6, 42:23:56) --> (26.6149, 48.4644)
+(2:24:23.1, 43:32:41) --> (27.5311, 49.3695)
+(2:26:40.6, 44:41:27) --> (28.4499, 50.2742)
+\end{terminalv}
+
+When compared to the original (\secref{ss:convertingskyframes}), the
+output coordinate order has been swapped to compensate for the
+different destination SkyFrame axis order.
+
+In all, there are four possible axis combinations, corresponding to two
+possible axis orders for each of the source and destination SkyFrames,
+and AST\_CONVERT will convert correctly between any of these.
+The point to note is that a SkyFrame contains knowledge about how to
+convert to and from other SkyFrames. Since its two axes (longitude and
+latitude) are distinguishable, the conversion is able to take account
+of the axis order.
+
+If you need to identify the axes of a SkyFrame explicitly, taking into
+account any axis permutations, the \htmlref{LatAxis}{LatAxis} and \htmlref{LonAxis}{LonAxis} attributes can be
+used. These are read-only attributes which give the indices of the
+latitude and longitude axes respectively.
+
+\subsection{\label{ss:convertingframes}Converting Between Frames}
+
+Having seen how clever SkyFrames are (\secref{ss:convertingskyframes}
+and \secref{ss:convertingpermutedaxes}), we will next examine how dumb
+a basic \htmlref{Frame}{Frame} can be in comparison. For example, if we create two
+2-dimensional Frames and use \htmlref{AST\_CONVERT}{AST\_CONVERT} to derive a conversion
+between them, as follows:
+
+\small
+\begin{terminalv}
+      INTEGER FRAME1, FRAME2
+
+      ...
+
+      FRAME1 = AST_FRAME( 2, ' ', STATUS )
+      FRAME2 = AST_FRAME( 2, ' ', STATUS )
+      CVT = AST_CONVERT( FRAME1, FRAME2, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+then the coordinate transformation which the ``cvt'' \htmlref{FrameSet}{FrameSet} performs
+will be as follows:
+
+\begin{terminalv}
+(1, 2) --> (1, 2)
+(2, 4) --> (2, 4)
+(3, 6) --> (3, 6)
+(4, 8) --> (4, 8)
+(5, 10) --> (5, 10)
+\end{terminalv}
+
+This is an identity transformation, exactly the same as a \htmlref{UnitMap}{UnitMap}
+(\secref{ss:unitmapexample}). Even if we permute the axis order of our
+Frames, as we did above (\secref{ss:convertingpermutedaxes}), we will
+fare no better. The conversion between our two basic Frames will
+always be an identity transformation.
+
+The reason for this is that, unlike a \htmlref{SkyFrame}{SkyFrame}, all basic Frames start
+life the same and have axes that are indistinguishable. Therefore,
+permuting their axes doesn't make them look any different---they still
+represent the same coordinate system.
+%Actually, this behaviour isn't as dumb as it seems and can actually be
+%very useful, as the following example illustrates.
+%
+%\subsection{Distinguishable and Indistinguishable Axes}
+%
+%c+
+%Imagine you have two Frames which represent the pixel coordinates of
+%two 2-dimensional images. Let's call their axes ``X'' and ``Y''.
+%Suppose you now transpose the second image and swap its Frame axes
+%(with astPermAxes) to take account of this.
+%c-
+%f+
+%Imagine you have two Frames which represent the pixel coordinates of
+%two 2-dimensional images. Let's call their axes ``X'' and ``Y''.
+%Suppose you now transpose the second image and swap its Frame axes
+%(with astPermAxes) to take account of this.
+%f-
+%
+%Next, consider what happens if you want to subtract one image from the
+%other. If you have a ``subtract'' program that is intelligent and
+%tries to align the two images for you, one of two things could happen:
+%
+%\begin{enumerate}
+%c+
+%\item If the axes are distinguishable, when your program invokes
+%astConvert it will derive a transformation between the two images
+%which swaps the X and Y coordinates (corresponding to the transposition
+%you applied to the second image). However, in aligning X-with-X and
+%Y-with-Y, this will completely undo the effects of your transposition!
+%c-
+%f+
+%\item If the axes are distinguishable, when your program invokes
+%AST\_CONVERT it will derive a transformation between the two images
+%which swaps the X and Y coordinates (corresponding to the transposition
+%you applied to the second image). However, in aligning X-with-X and
+%Y-with-Y, this will completely undo the effects of your transposition!
+%f-
+%
+%\item If the axes are indistinguishable, the transformation between
+%the two images will always be an identity
+%(\secref{ss:convertingframes}). Therefore, your program will align
+%X-with-Y and Y-with-X, so that you see the effects of your earlier
+%transposition of the second image.
+%\end{enumerate}
+%
+%Clearly, if we are considering pixel coordinates, the latter behaviour
+%is preferable, since there would be no point in implementing an image
+%transposition program if we could never see the effects of it. This
+%indicates that a basic Frame, with is indistinguishable axes, is the
+%correct type of \htmlref{Object}{Object} to represent a pixel coordinate system, where
+%this behaviour is necessary.
+%
+%Conversely, the former behaviour would be more useful if the axes we
+%were considering were, say, wavelength (in nm) and slit position (in
+%mm). In this case, we would expect our ``subtract'' program to
+%subtract data at corresponding wavelengths and slit positions, not
+%just at corresponding pixels. This case requires distinguishable axes,
+%so that corresponding axes in the two images can be matched up, just
+%as happens with a SkyFrame (\secref{ss:convertingpermutedaxes}).
+%
+%Of course, there may also be intermediate cases, where some axes are
+%distinguishable and others aren't.
+
+\subsection{\label{ss:alignmentsystem}The Choice of Alignment System}
+
+In practice, when AST is asked to find a conversion between two Frames
+describing two different coordinate systems on a given physical domain,
+it uses an intermediate ``alignment'' system. Thus, when finding a
+conversion from system A to system B, AST first finds the \htmlref{Mapping}{Mapping} from
+system A to some alignment system, system C, and then finds the Mapping
+from this system C to the required system B. It finally concatenates
+these two Mappings to get the Mapping from system A to system B.
+
+One advantage of this is that it cuts down the number of conversion
+algorithms required. If there are $N$ different Systems which may be used
+to describe positions within the \htmlref{Domain}{Domain}, then this approach requires
+about $2*N$ conversion algorithms to be written. The alternative approach
+of going directly from system A to system B would require about $N*N$
+conversion algorithms.
+
+In addition, the use of an intermediate alignment system highlights the
+nature of the conversion process. What do we mean by saying that a
+Mapping ``converts a position in one coordinate system into the
+corresponding position in another''? In practice, it means that the input
+and output coordinates correspond to the same coordinates \emph{in some
+third coordinate system}. The choice of this third coordinate system, the
+``alignment'' system, can completely alter the nature of the Mapping. The
+\htmlref{Frame}{Frame} class has an attribute called \htmlref{AlignSystem}{AlignSystem} which can be used to
+specify the alignment system.
+
+As an example, consider the case of aligning two spectra calibrated in
+radio velocity, but each with a different rest frequency (each spectrum
+will be described by a \htmlref{SpecFrame}{SpecFrame}). Since the rest frequencies differ, a
+given velocity will correspond to different frequencies in the two
+spectra. So when we come to ``align'' these two spectra (that is, find a
+Mapping which converts positions in one SpecFrame to the corresponding
+positions in the other), we have the choice of aligning the frequencies
+or aligning the velocities. Different Mappings will be required to
+describe these two forms of alignment. If we set AlignSystem to ``Freq''
+then the returned Mapping will align the frequencies described by the two
+SpecFrames. On the other hand, if we set AlignSystem to ``Vradio''
+then the returned Mapping will align the velocities.
+
+Some choices of alignment system are redundant. For instance, in the
+above example, changing the alignment system from frequency to wavelength
+has no effect on the returned Mapping: if two spectra are aligned in
+frequency they will also be aligned in wavelength (assuming the speed of
+light doesn't change).
+
+The default value for AlignSystem depends on the class of Frame. For a
+SpecFrame, the default is wavelength (or equivalently, frequency)
+since this is the system in which observations are usually made. The
+SpecFrame class also has an attribute called \htmlref{AlignStdOfRest}{AlignStdOfRest} which
+allows the standard of rest of the alignment system to be specified.
+Similarly, the \htmlref{TimeFrame}{TimeFrame} class has an attribute called \htmlref{AlignTimeScale}{AlignTimeScale}
+which allows the time scale of the alignment system to be specified.
+Currently, the \htmlref{SkyFrame}{SkyFrame} uses ICRS as the default for AlignSystem, since
+this is a close approximation to an inertial frame of rest.
+
+\cleardoublepage
+\section{\label{ss:framesets}Coordinate System Networks (FrameSets)}
+
+We saw in \secref{ss:introducingconversion} how \htmlref{AST\_CONVERT}{AST\_CONVERT} could be
+used to find a \htmlref{Mapping}{Mapping} that inter-relates a pair of coordinate systems
+represented by Frames. There is a limitation to this, however, in that
+it can only be applied to coordinate systems that are inter-related by
+suitable conventions. In the case of celestial coordinates, the
+relevant conventions are standards set out by the International
+Astronomical Union, and others, that define what these coordinate
+systems mean. In practice, however, the relationships between many
+other coordinate systems are also of practical importance.
+
+Consider, for example, the focal plane of a telescope upon which an
+image of the sky is falling. We could measure positions in this focal
+plane in millimetres or, if there were a detector system such as a CCD
+present, we could count pixels. We could also use celestial
+coordinates of many different kinds. All of these systems are
+equivalent in their effectiveness at specifying positions in the focal
+plane, but some are more convenient than others for particular
+purposes.
+
+Although we could, in principle, convert between all of these focal
+plane coordinate systems, there is no pre-defined convention for doing
+so. This is because the conversions required depend on where the
+telescope is pointing and how the CCD is mounted in the focal
+plane. Clearly, knowledge about this cannot be built into the AST
+library and must be supplied in some other way. Note that this is
+exactly the same problem as we met in \secref{ss:framedomains} when
+discussing the \htmlref{Domain}{Domain} attribute---\emph{i.e.}\ coordinate systems that
+apply to different physical domains require that extra information be
+supplied before we can convert between them.
+
+What we need, therefore, is a general way to describe how coordinate
+systems are inter-related, so that when there is no convention already
+in place, we can define our own. We can then look forward to
+converting, say, from pixels into galactic coordinates and {\emph{vice
+versa.}  In AST, the \htmlref{FrameSet}{FrameSet} class provides this capability.
+
+\subsection{The FrameSet Model}
+
+Consider a coordinate system (call it number 1) which is represented
+by a \htmlref{Frame}{Frame} of some kind. Now consider a \htmlref{Mapping}{Mapping} which, when applied to
+the coordinates in system 1 yields coordinates in another system,
+number 2. The Mapping therefore inter-relates coordinate systems 1 and
+2.
+
+Now consider a second Mapping which inter-relates system 1 and a
+further coordinate system, number 3. If we wanted to convert
+coordinates between systems 2 and 3, we could do so by:
+
+\begin{enumerate}
+\item Applying our first Mapping in reverse, so as to convert between
+systems 2 and 1.
+
+\item Applying the second Mapping, as given, to convert between
+systems 1 and 3.
+\end{enumerate}
+
+We are not limited to three coordinate systems, of course. In fact, we
+could continue to introduce any number of further coordinate systems,
+so long as we have a suitable Mapping for each one which relates it to
+one of the Frames already present. Continuing in this way, we can
+build up a network in which Frames are inter-related by Mappings in
+such a way that there is always a way of converting between any pair
+of coordinate systems.
+
+The \htmlref{FrameSet}{FrameSet} (Figure~\ref{fig:frameset}) encapsulates these ideas.  It
+is a network composed of Frames and associated Mappings, in which
+there is always exactly one path, \emph{via} Mappings, between any
+pair of Frames.  Since we assemble FrameSets ourselves, they can be
+used to represent any coordinate systems we choose and to set up the
+particular relationships between them that we want.
+
+\subsection{\label{ss:creatingaframeset}Creating a FrameSet}
+
+Before we can create a \htmlref{FrameSet}{FrameSet}, we must have a \htmlref{Frame}{Frame} of some kind to
+put into it, so let's create a simple one:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER FRAME1, STATUS
+
+      STATUS = 0
+
+      ...
+
+      FRAME1 = AST_FRAME( 2, 'Domain=A', STATUS )
+\end{terminalv}
+\normalsize
+
+We have set this Frame's \htmlref{Domain}{Domain} attribute (\secref{ss:framedomains}) to
+A so that it will be distinct from the others we will be using. We can
+now create a new FrameSet containing just this Frame, as follows:
+
+\small
+\begin{terminalv}
+      INTEGER FRAMESET
+
+      ...
+
+      FRAMESET = AST_FRAMESET( FRAME1, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+So far, however, this Frame isn't related to any others.
+
+\subsection{\label{ss:addingframes}Adding New Frames to a FrameSet}
+
+We can now add further Frames to the \htmlref{FrameSet}{FrameSet} created above
+(\secref{ss:creatingaframeset}). To do so, we must supply a new \htmlref{Frame}{Frame}
+and an associated \htmlref{Mapping}{Mapping} that relates it to any of the Frames that
+are already present (there is only one present so far).  To keep the
+example simple, we will just use a \htmlref{ZoomMap}{ZoomMap} that multiplies coordinates
+by 10. The required Objects are created as follows:
+
+\small
+\begin{terminalv}
+      INTEGER FRAME2, MAPPING12
+
+      ...
+
+      FRAME2 = AST_FRAME( 2, 'Domain=B', STATUS )
+      MAPPING12 = AST_ZOOMMAP( 2, 10.0D0, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+To add the new Frame into our FrameSet, we use the \htmlref{AST\_ADDFRAME}{AST\_ADDFRAME}
+routine:
+
+\small
+\begin{terminalv}
+      CALL AST_ADDFRAME( FRAMESET, 1, MAPPING12, FRAME2, STATUS )
+\end{terminalv}
+\normalsize
+
+Whenever a Frame is added to a FrameSet, it is assigned an integer
+index. This index starts with 1 for the initial Frame used to create
+the FrameSet (\secref{ss:creatingaframeset}) and increments by one
+every time a new Frame is added. This index is the primary way of
+identifying the Frames within a FrameSet.
+
+When a Frame is added, we also have to specify which of the existing
+ones the new Frame is related to. Here, we chose number 1, the only
+one present so far, and the new one we added became number 2.
+
+Note that a FrameSet does not make copies of the Frames and Mappings
+that you insert into it. Instead, it holds pointers to them. This
+means that if you retain the original pointers to these Objects and
+alter them, you will indirectly be altering the FrameSet's
+contents. You can, of course, always use \htmlref{AST\_COPY}{AST\_COPY}
+(\secref{ss:copyingobjects}) to make a separate copy of any \htmlref{Object}{Object} if
+you need to ensure its independence.
+
+We could also add a third Frame into our FrameSet, this time defining
+a coordinate system which is reached by multiplying the original
+coordinates (of FRAME1) by 5:
+
+\small
+\begin{terminalv}
+      CALL AST_ADDFRAME( FRAMESET, 1,
+     :                   AST_ZOOMMAP( 2, 5.0D0, ' ', STATUS ),
+     :                   AST_FRAME( 2, 'Domain=C', STATUS ),
+     :                   STATUS )
+\end{terminalv}
+\normalsize
+
+Here, we have avoided storing unnecessary pointer values by using
+function invocations directly as arguments for AST\_ADDFRAME. This
+assumes that we are using \htmlref{AST\_BEGIN}{AST\_BEGIN} and \htmlref{AST\_END}{AST\_END}
+(\secref{ss:contexts}) to ensure that Objects are correctly deleted
+when no longer required.
+
+   Our example FrameSet now contains three Frames and two Mappings with
+   the arrangement shown in Figure~\ref{fig:fsexample}.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun210_figures/fsexample}
+   \caption[An example FrameSet.]{An example FrameSet, in which Frames~2 and 3 are related to
+   Frame~1 by multiplying its coordinates by factors of 10 and 5
+   respectively. The FrameSet's \htmlref{Base}{Base} attribute has the value 1 and its
+   \htmlref{Current}{Current} attribute has the value 3. The transformation performed when
+   the FrameSet is used as a Mapping (\emph{i.e.}\ from its base to
+   its current Frame) is shown in bold.}
+   \label{fig:fsexample}
+   \end{center}
+   \end{figure}
+   The total number of Frames is given by its read-only \htmlref{Nframe}{Nframe} attribute.
+
+\subsection{\label{ss:baseandcurrent}The Base and Current Frames}
+
+At all times, one of the Frames in a \htmlref{FrameSet}{FrameSet} is designated to be its
+\emph{base} \htmlref{Frame}{Frame} and one to be its \emph{current} Frame
+(Figure~\ref{fig:fsexample}). These Frames are identified by two
+integer FrameSet attributes, \htmlref{Base}{Base} and \htmlref{Current}{Current}, which hold the indices
+of the nominated Frames within the FrameSet.
+
+The existence of the base and current Frames reflects an important
+application of FrameSets, which is to attach coordinate systems to
+entities such as data arrays, data files, plotting surfaces (for
+graphics), \emph{etc.}  In this context, the base Frame represents the
+``native'' coordinate system of the attached entity---for example, the
+pixel coordinates of an image or the intrinsic coordinates of a
+plotting surface. The other Frames within the FrameSet represent
+alternative coordinate systems which may also be used to refer to
+positions within that entity.  The current Frame represents the
+particular coordinate system which is currently selected for use. For
+instance, if an image were being displayed, you would aim to label it
+with coordinates corresponding to the current Frame. In order to see a
+different coordinate system, a software user would arrange for a
+different Frame to be made current.
+
+The choice of base and current Frames may be changed at any time,
+simply by assigning new values to the FrameSet's Base and Current
+attributes. For example, to make the Frame with index 3 become the
+current Frame, you could use:
+
+\small
+\begin{terminalv}
+      CALL AST_SETI( FRAMESET, 'Current', 3, STATUS )
+\end{terminalv}
+\normalsize
+
+You can nominate the same Frame to be both the base and current Frame
+if you wish.
+\label{ss:baseandcurrentdefault}
+
+By default (\emph{i.e.}\ if the Base or Current attribute is un-set),
+the first Frame added to a FrameSet becomes its base Frame and the
+last one added becomes its current Frame.\footnote{Although this is
+reversed if the FrameSet's \htmlref{Invert}{Invert} attribute is non-zero.} Whenever a
+new Frame is added to a FrameSet, the Current attribute is modified so
+that the new Frame becomes the current one. This behaviour is
+reflected in the state of the example FrameSet in
+Figure~\ref{fig:fsexample}.
+
+\subsection{\label{ss:astbaseandastcurrent}Referring to the Base and Current Frames}
+
+It is often necessary to refer to the base and current Frames
+(\secref{ss:baseandcurrent}) within a \htmlref{FrameSet}{FrameSet}, but it can be
+cumbersome having to obtain their indices from the \htmlref{Base}{Base} and \htmlref{Current}{Current}
+attributes on each occasion. To make this easier, two parameter
+constants, AST\_\_BASE and AST\_\_CURRENT, are defined in the AST\_PAR
+include file and may be used to represent the indices of the base and
+current Frames respectively. They may be used whenever a \htmlref{Frame}{Frame} index
+is required.
+
+For example, when adding a new Frame to a FrameSet
+(\secref{ss:addingframes}), you could use the following to indicate
+that the new Frame is related to the existing current Frame, whatever
+its index happens to be:
+
+\small
+\begin{terminalv}
+      INTEGER FRAME, MAPPING
+
+      ...
+
+      CALL AST_ADDFRAME( FRAMESET, AST__CURRENT, MAPPING, FRAME, STATUS )
+\end{terminalv}
+\normalsize
+
+Of course, the Frame you added would then become the new current
+Frame.
+
+\subsection{\label{ss:framesetasmapping}Using a FrameSet as a Mapping}
+
+The \htmlref{FrameSet}{FrameSet} class inherits properties and behaviour from the \htmlref{Frame}{Frame}
+class (\secref{ss:frames}) and, in turn, from the \htmlref{Mapping}{Mapping} class
+(\secref{ss:mappings}). Its behaviour when used as a Mapping is
+particularly important.
+
+Consider, for instance, passing a FrameSet pointer to a coordinate
+transformation routine such as \htmlref{AST\_TRAN2}{AST\_TRAN2}:
+
+\small
+\begin{terminalv}
+      INTEGER N
+      DOUBLE PRECISION XIN( N ), YIN( N )
+      DOUBLE PRECISION XOUT( N ), YOUT( N )
+
+      ...
+
+      CALL AST_TRAN2( FRAMESET, N, XIN, YIN, .TRUE., XOUT, YOUT, STATUS )
+\end{terminalv}
+\normalsize
+
+The coordinate transformation applied by this FrameSet would be the
+one which converts between its base and current Frames. Using the
+FrameSet in Figure~\ref{fig:fsexample}, for example, the coordinates
+would be multiplied by a factor of 5.  If we instead requested the
+FrameSet's inverse transformation, we would be transforming from its
+current Frame to its base Frame, so our example FrameSet would then
+multiply by a factor of 0.2.
+
+Whenever the choice of base and current Frames changes, the
+transformations which a FrameSet performs when used as a Mapping also
+change to reflect this. The \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes may also change in
+consequence, because they are determined by the numbers of axes in the
+FrameSet's base and current Frames respectively. These numbers need
+not necessarily be equal, of course.
+
+Like any Mapping, a FrameSet may also be inverted by changing the
+boolean sense of its \htmlref{Invert}{Invert} attribute, \emph{e.g.}\ using \htmlref{AST\_INVERT}{AST\_INVERT}
+(\secref{ss:invertingmappings}). If this is happens, the values of the
+FrameSet's \htmlref{Base}{Base} and \htmlref{Current}{Current} attributes are interchanged, along with
+its Nin and Nout attributes, so that its base and current Frames swap
+places. When used as a Mapping, the FrameSet will therefore perform
+the inverse transformation to that which it performed previously.
+
+To summarise, a FrameSet may be used exactly like any other Mapping
+which inter-relates the coordinate systems described by its base and
+current Frames.
+
+\subsection{\label{ss:extractingamapping}Extracting a Mapping from a FrameSet}
+
+Although it is very convenient to use a \htmlref{FrameSet}{FrameSet} when a \htmlref{Mapping}{Mapping} is
+required (\secref{ss:framesetasmapping}), a FrameSet necessarily
+contains additional information and sometimes this might cause
+inefficiency or confusion.  For example, if you wanted to use a
+Mapping contained in one FrameSet and insert it into another, it would
+probably not be efficient to insert the whole of the first FrameSet
+into the second one, although it would work.
+
+In such a situation, the \htmlref{AST\_GETMAPPING}{AST\_GETMAPPING} function allows you to
+extract a Mapping from a FrameSet. You do this by specifying the two
+Frames which the Mapping should inter-relate using their indices
+within the FrameSet. For example:
+
+\small
+\begin{terminalv}
+      MAP = AST_GETMAPPING( FRAMESET, 2, 3, STATUS )
+\end{terminalv}
+\normalsize
+
+would return a pointer to a Mapping that converted between Frames~2
+and 3 in the FrameSet. Its inverse transformation would then convert
+in the opposite direction, \emph{i.e.}\ between Frames~3 and 2.  Note
+that this Mapping might not be independent of the Mappings contained
+within the FrameSet---\emph{i.e.}\ they may share sub-Objects---so
+\htmlref{AST\_COPY}{AST\_COPY} should be used to make a copy if you need to guarantee
+independence (\secref{ss:copyingobjects}).
+
+Very often, the Mapping returned by AST\_GETMAPPING will be a compound
+Mapping, or \htmlref{CmpMap}{CmpMap} (\secref{ss:cmpmaps}). This reflects the fact that
+conversion between the two Frames may need to be done \emph{via} an
+intermediate coordinate system so that several stages may be involved.
+You can, however, easily simplify this Mapping (where this is possible)
+by using the \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY} function (\secref{ss:simplifyingcmpmaps})
+and this is recommended if you plan to use it for transforming a large
+amount of data.
+
+\subsection{\label{ss:framesetasframe}Using a FrameSet as a Frame}
+
+A \htmlref{FrameSet}{FrameSet} can also be used as a \htmlref{Frame}{Frame}, in which capacity it almost
+always behaves as if its current Frame had been used instead. For
+example, if you request the \htmlref{Title}{Title} attribute of a FrameSet using:
+
+\small
+\begin{terminalv}
+      CHARACTER * ( 80 ) TITLE
+
+      ...
+
+      TITLE = AST_GETC( FRAMESET, 'Title', STATUS )
+\end{terminalv}
+\normalsize
+
+the result will be the Title of the current Frame, or a suitable
+default if the current Frame's Title attribute is un-set. The same
+also applies to other attribute operations---\emph{i.e.}\ setting,
+clearing and testing attributes.  Most attributes shared by both
+Frames and FrameSets behave in this way, such as \htmlref{Naxes}{Naxes}, \htmlref{Label(axis)}{Label(axis)},
+\htmlref{Format(axis)}{Format(axis)}, \emph{etc.} There are, however, a few exceptions:
+
+\begin{quote}
+\begin{description}
+\item[\htmlref{Class}{Class}]\mbox{}\\
+Has the value ``FrameSet''.
+
+\item[\htmlref{ID}{ID}]\mbox{}\\
+Identifies the particular FrameSet (not its current Frame).
+
+\item[\htmlref{Nin}{Nin}]\mbox{}\\
+Equals the number of axes in the FrameSet's base Frame.
+
+\item[\htmlref{Invert}{Invert}]\mbox{}\\
+Is independent of any of the Objects within the FrameSet.
+
+\item[\htmlref{Nobject}{Nobject}]\mbox{}\\
+Counts the number of active FrameSets.
+
+\item[\htmlref{RefCount}{RefCount}]\mbox{}\\
+Counts the number of active pointers to the FrameSet (not to its
+current Frame).
+\end{description}
+\end{quote}
+
+Note that the set of attributes possessed by a FrameSet can vary,
+depending on the nature of its current Frame. For example, if the
+current Frame is a \htmlref{SkyFrame}{SkyFrame} (\secref{ss:skyframes}), then the FrameSet
+will acquire an \htmlref{Equinox}{Equinox} attribute from it which can be set, enquired,
+\emph{etc.}  However, if the current Frame is changed to be a basic
+Frame, which does not have an Equinox attribute, then this attribute
+will be absent from the FrameSet as well. Any attempt to reference it
+will then result in an error.
+
+\subsection{Extracting a Frame from a FrameSet}
+
+Although a \htmlref{FrameSet}{FrameSet} may be used in place of its current \htmlref{Frame}{Frame} in most
+situations, it is sometimes convenient to have direct access to a
+specified Frame within it. This may be obtained using the
+\htmlref{AST\_GETFRAME}{AST\_GETFRAME} function, as follows:
+
+\small
+\begin{terminalv}
+      FRAME = AST_GETFRAME( FRAMESET, AST__BASE, STATUS )
+\end{terminalv}
+\normalsize
+
+This would return a pointer (not a copy) to the base Frame within the
+FrameSet. Note the use of AST\_\_BASE
+(\secref{ss:astbaseandastcurrent}) as shorthand for the value of the
+FrameSet's \htmlref{Base}{Base} attribute, which gives the base Frame's index.
+
+\subsection{Removing a Frame from a FrameSet}
+
+Removing a \htmlref{Frame}{Frame} from a \htmlref{FrameSet}{FrameSet} is straightforward and is performed
+using the \htmlref{AST\_REMOVEFRAME}{AST\_REMOVEFRAME} routine. You identify the Frame you wish to
+remove in the usual way, by giving its index within the FrameSet. For
+example, the following would remove the Frame with index 1:
+
+\small
+\begin{terminalv}
+      CALL AST_REMOVEFRAME( FRAMESET, 1, STATUS );
+\end{terminalv}
+\normalsize
+
+The only restriction is that you cannot remove the last remaining
+Frame because a FrameSet must always contain at least one Frame.  When
+a Frame is removed, the Frames which follow it are re-numbered
+(\emph{i.e.}\ their indices are reduced by one) so as to preserve the
+sequence of consecutive Frame indices.  The FrameSet's \htmlref{Nframe}{Nframe}
+attribute is also decremented.
+
+If appropriate, AST\_REMOVEFRAME will modify the FrameSet's \htmlref{Base}{Base}
+and/or \htmlref{Current}{Current} attributes so that they continue to identify the same
+Frames as previously. If either the base or current Frame is removed,
+however, the corresponding attribute will become un-set, so that it
+reverts to its default value (\secref{ss:baseandcurrentdefault}) and
+therefore identifies an alternative Frame.
+
+Note that it is quite permissible to remove any Frame from a FrameSet,
+even although other Frames may appear to depend on it. For example, in
+Figure~\ref{fig:fsexample}, if Frame~1 were removed, the correct
+relationship between Frames~2 and 3 would still be preserved, although
+they would be re-numbered as Frames~1 and 2.
+
+\cleardoublepage
+\section{\label{ss:fshigher}Higher Level Operations on FrameSets}
+
+\subsection{\label{ss:framesetsfromconvert}Creating FrameSets with AST\_CONVERT}
+
+Before considering the important subject of using FrameSets to convert
+between coordinate systems (\secref{ss:framesetconverting}), let us
+return briefly to reconsider the output generated by \htmlref{AST\_CONVERT}{AST\_CONVERT}. We
+used this function earlier (\secref{ss:introducingconversion}), when
+converting between the coordinate systems represented by various kinds
+of \htmlref{Frame}{Frame}, and indicated that it returns a \htmlref{FrameSet}{FrameSet} to represent the
+coordinate conversion it identifies. We are now in a position to
+examine the structure of this FrameSet.
+
+Take our earlier example (\secref{ss:convertingskyframes}) of
+converting between the celestial coordinate systems represented by two
+SkyFrames:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER SKYFRAME1, SKYFRAME2, STATUS
+
+      STATUS = 0
+
+      ...
+
+      SKYFRAME1 = AST_SKYFRAME( 'System=FK4-NO-E, Epoch=B1958, Equinox=B1960', STATUS )
+      SKYFRAME2 = AST_SKYFRAME( 'System=Ecliptic, Equinox=J2010.5', STATUS )
+
+      CVT = AST_CONVERT( SKYFRAME1, SKYFRAME2, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+   This will produce a pointer, CVT, to the FrameSet shown in
+   Figure~\ref{fig:fsconvert}.
+   \begin{figure}[bhtp]
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun210_figures/fsconvert}
+   \caption[FrameSet produced when converting between two SkyFrames.]{The FrameSet produced when AST\_CONVERT is used to convert
+   between the coordinate systems represented by two SkyFrames. The
+   source \htmlref{SkyFrame}{SkyFrame} becomes the base Frame, while the destination SkyFrame
+   becomes the current Frame. The \htmlref{Mapping}{Mapping} between them implements the
+   required conversion.}
+   \label{fig:fsconvert}
+   \end{center}
+   \end{figure}
+
+As can be seen, this FrameSet contains just two Frames.  The source
+Frame supplied to AST\_CONVERT becomes its base Frame, while the
+destination Frame becomes its current Frame. (The FrameSet, of course,
+simply holds pointers to these Frames, rather than making copies.) The
+Mapping which relates the base Frame to the current Frame is the one
+which implements the required conversion.
+
+As we noted earlier (\secref{ss:convertingskyframes}), the FrameSet
+returned by AST\_CONVERT may be used both as a Mapping and as a Frame
+to perform most of the functions you are likely to need. However, the
+Mapping may be extracted for use on its own if necessary, using
+\htmlref{AST\_GETMAPPING}{AST\_GETMAPPING} (\secref{ss:extractingamapping}), for example:
+
+\small
+\begin{terminalv}
+      INTEGER MAPPING
+
+      ...
+
+      MAPPING = AST_GETMAPPING( CVT, AST__BASE, AST__CURRENT, STATUS )
+\end{terminalv}
+\normalsize
+
+\subsection{\label{ss:framesetconverting}Converting between FrameSet Coordinate Systems}
+
+   We now consider the process of converting between the coordinate
+   systems represented by two FrameSets. This is a most important
+   operation, as a subsequent example (\secref{ss:registeringimages})
+   will show, and is illustrated in Figure~\ref{fig:fsalign}.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun210_figures/fsalign}
+   \caption[Conversion between two FrameSets is performed by establishin a link between a pair of Frames, one from each FrameSet.]{Conversion
+   between two FrameSets is performed by establishing
+   a link between a pair of Frames, one from each \htmlref{FrameSet}{FrameSet}. If conversion
+   between these two Frames is possible, then a route for converting
+   between the current Frames of both FrameSets can also be found. In
+   practice, there may be many ways of pairing Frames to find the
+   ``missing link'', so the Frames' \htmlref{Domain}{Domain} attribute may be used to
+   narrow the choice.}
+   \label{fig:fsalign}
+   \end{center}
+   \end{figure}
+
+Recalling (\secref{ss:framesetasframe}) that a FrameSet will behave
+like its current \htmlref{Frame}{Frame} when necessary, conversion between two
+FrameSets is performed using \htmlref{AST\_CONVERT}{AST\_CONVERT}
+(\secref{ss:convertingskyframes}), but supplying pointers to FrameSets
+instead of Frames. The effect of this is to convert between the
+coordinate systems represented by the current Frames of each FrameSet:
+
+\small
+\begin{terminalv}
+      INTEGER FRAMESETA, FRAMESETB
+
+      ...
+
+      CVT = AST_CONVERT( FRAMESETA, FRAMESETB, 'SKY', STATUS )
+\end{terminalv}
+\normalsize
+
+When using FrameSets, we are presented with considerably more
+conversion options than when using Frames alone. This is because each
+current Frame is related to all the other Frames in its respective
+FrameSet. Therefore, if we can establish a link between any pair of
+Frames, one from each FrameSet, we can form a complete conversion path
+between the two current Frames (Figure~\ref{fig:fsalign}).
+
+This expanded range of options is, of course, precisely the
+intention. By connecting Frames together within a FrameSet, we have
+extended the range of coordinate systems that can be reached from any
+one of them.  We are therefore no longer restricted to converting
+between Frames with the same Domain value (\secref{ss:framedomains}),
+but can go \emph{via} a range of intermediate coordinate systems in
+order to make the connection we require. Transformation between
+different domains has therefore become possible because, in assembling
+the FrameSets, we provided the additional information needed to
+inter-relate them.
+
+It is important to appreciate, however, that the choice of ``missing
+link'' is crucial in determining the conversion that results.
+Although each FrameSet may be perfectly self-consistent internally,
+this does not mean that all conversion paths through the combined
+network of Mappings are equivalent. Quite the contrary in fact:
+everything depends on where the inter-connecting link between the two
+FrameSets is made.  In practice, there may be a large number of
+possible pairings of Frames and hence of possible links. Other factors
+must therefore be used to restrict the choice. These are:
+
+\begin{enumerate}
+\item Not every possible pairing of Frames is legitimate. For example,
+you cannot convert directly between a basic Frame and a \htmlref{SkyFrame}{SkyFrame} which
+belong to different classes, so such pairings will be ignored.
+
+\item In a similar way, you cannot convert directly between Frames
+with different Domain values (\secref{ss:framedomains}). If the Domain
+attribute is used consistently (typically only one Frame in each
+FrameSet will have a particular Domain value), then this further
+restricts the choice.
+
+\item The third argument of AST\_CONVERT may then be used to specify
+explicitly which Domain value the paired Frames should have. You may
+also supply a comma-separated list of preferences here (see below).
+
+\item If the above steps fail to uniquely identify the link, then the
+first suitable pairing of Frames is used, so that any ambiguity is
+resolved by the order in which Frames are considered for pairing (see
+the description of the AST\_CONVERT function in
+\appref{ss:functiondescriptions} for details of the search
+order).\footnote{If you find that how this ambiguity is resolved
+actually makes a difference to the conversion that results, then you
+have probably constructed a FrameSet which lacks internal
+self-consistency. For example, you might have two Frames representing
+indistinguishable coordinate systems but inter-related by a non-null
+\htmlref{Mapping}{Mapping}.}
+\end{enumerate}
+
+In the example above we supplied the string ``SKY'' as the third
+argument of AST\_CONVERT. This constitutes a request that a pair of
+Frames with
+the Domain value SKY (\emph{i.e.}\ representing celestial coordinate
+systems) should be used to inter-relate the two FrameSets. Note that
+this does not specify which celestial coordinate system to use, but is
+a general request that the two FrameSets be inter-related using
+coordinates on the celestial sphere.
+
+Of course, it may be that this request cannot be met because there may
+not be a celestial coordinate system in both FrameSets. If this is
+likely to happen, we can supply a list of preferences, or a
+\emph{domain search path},
+as the third argument to AST\_CONVERT, such as
+the following:
+
+\small
+\begin{terminalv}
+      CVT = AST_CONVERT( FRAMESETA, FRAMESETB, 'SKY,PIXEL,GRID,', STATUS )
+\end{terminalv}
+\normalsize
+
+Now, if the two FrameSets cannot be inter-related using the SKY domain,
+AST\_CONVERT will attempt to use the PIXEL domain instead. If this
+also fails, it will try the GRID domain. A blank field in the domain
+search path (here indicated by the final comma) allows any Domain
+value to be used. This can be employed as a last resort when all else
+has failed.
+
+If astConvert succeeds in identifying a conversion, it will return a
+pointer to a FrameSet (\secref{ss:framesetsfromconvert}) in which the
+source and destination Frames are inter-connected by the required
+Mapping. In this case, of course, these Frames will be the current
+Frames of the two FrameSets, but in all other respects the returned
+FrameSet is the same as when converting between Frames.
+
+Very importantly, however, AST\_CONVERT may modify the FrameSets you
+are converting between. It does this, in order to indicate which
+pairing of Frames was used to inter-relate them, by changing the \htmlref{Base}{Base}
+attribute for each FrameSet so that the Frame used in the pairing
+becomes its base Frame (\secref{ss:baseandcurrent}).
+
+Finally, note that AST\_CONVERT may also be used to convert between a
+FrameSet and a Frame, or \emph{vice versa}. If a pointer to a Frame is
+supplied for either the first or second argument, it will behave like
+a FrameSet containing only a single Frame.
+
+\subsection{\label{ss:registeringimages}Example---Registering Two Images}
+
+Consider two images which have been calibrated by attaching FrameSets
+to them, such that the base \htmlref{Frame}{Frame} of each \htmlref{FrameSet}{FrameSet} corresponds to the
+raw data grid coordinates of each image (the GRID domain of
+\secref{ss:domainconventions}). Suppose, also, that these FrameSets
+contain an unknown number of other Frames, representing alternative
+world coordinate systems.  What we wish to do is register these two
+images, such that we can transform from a position in the data grid of
+one into the corresponding position in the data grid of the other.
+This is a very practical example because images will typically be
+calibrated using FrameSets in precisely this way.
+
+The first step will probably involve making a copy of both FrameSets
+(using \htmlref{AST\_COPY}{AST\_COPY}---\secref{ss:copyingobjects}), since we will be
+modifying them. Let ``frameseta'' and ``framesetb'' be pointers to
+these copies. Since we want to convert between the base Frames of
+these FrameSets (\emph{i.e.}\ their data grid coordinates), the next
+step is to make these Frames current. This is simply done by inverting
+both FrameSets, which interchanges their base and current
+Frames. astInvert will perform this task:
+
+\small
+\begin{terminalv}
+      CALL AST_INVERT( FRAMESETA, STATUS )
+      CALL AST_INVERT( FRAMESETB, STATUS )
+\end{terminalv}
+\normalsize
+
+To identify the required conversion, we now use \htmlref{AST\_CONVERT}{AST\_CONVERT},
+supplying a suitable domain search path with which we would like our
+two images to be registered:
+
+\small
+\begin{terminalv}
+      CVT = AST_CONVERT( FRAMESETA, FRAMESETB, 'SKY,PIXEL,GRID', STATUS )
+      IF ( CVT .EQ. AST__NULL ) THEN
+         <no conversion was possible>
+      ELSE
+         <conversion was possible>
+      END IF
+\end{terminalv}
+\normalsize
+
+The effects of this are:
+
+\begin{enumerate}
+\item AST\_CONVERT first attempts to register the two images on the
+celestial sphere (\emph{i.e.}\ using the SKY domain). To do this, it
+searches for a celestial coordinate system, although not necessarily
+the same one, attached to each image.  If it finds a suitable pair of
+coordinate systems, it then registers the images by matching
+corresponding positions on the sky.
+
+\item If this fails, AST\_CONVERT next tries to match positions in the
+PIXEL domain (\secref{ss:framedomains}). If it succeeds, the two
+images will then be registered so that their corresponding pixel
+positions correspond. If the PIXEL domain is offset from the data grid
+(as typically happens in data reduction systems which implement a
+``pixel origin''), then this will be correctly accounted for.
+
+\item If this also fails, the GRID domain is finally used. This will
+result in image registration by matching corresponding points in the
+data grids used by both images. This means they will be
+aligned so that the first element their data arrays correspond.
+
+\item If all of the above fail, AST\_CONVERT will return the value
+AST\_\_NULL. Otherwise a pointer to a FrameSet will be returned.
+\end{enumerate}
+
+The resulting CVT FrameSet may then be used directly
+(\secref{ss:convertingskyframes}) to convert between positions in the
+data grid of the first image and corresponding positions in the data
+grid of the second image.
+
+To determine which domain was used to achieve registration,
+we can use the fact that the \htmlref{Base}{Base} attribute of each FrameSet is set by
+AST\_CONVERT to indicate which intermediate Frames were used. We
+can therefore simply invert either FrameSet (to make its base Frame
+become the current one) and then enquire the \htmlref{Domain}{Domain} value:
+
+\small
+\begin{terminalv}
+      CHARACTER * ( 20 ) DOMAIN
+
+      ...
+
+
+      CALL AST_INVERT( FRAMESETA, STATUS )
+      DOMAIN = AST_GETC( FRAMESETA, 'Domain', STATUS )
+\end{terminalv}
+\normalsize
+
+If conversion was successful, the result will be one of the strings
+``SKY'', ``PIXEL'' or ``GRID''.
+
+\subsection{\label{ss:remapframe}Re-Defining a FrameSet Coordinate System}
+
+As discussed earlier (\secref{ss:baseandcurrent}), an important
+application of a \htmlref{FrameSet}{FrameSet} is to allow coordinate system information to
+be attached to entities such as images in order to calibrate them. In
+addition, one of the main objectives of AST is to simplify the
+propagation of such information through successive stages of data
+processing, so that it remains consistent with the associated image
+data.
+
+In such a situation, the FrameSet's base \htmlref{Frame}{Frame} would correspond with
+the image's data grid coordinates and its other Frames (if any) with
+the various alternative world coordinate systems associated with the
+image.  If the data processing being performed does not change the
+relationship between the image's data grid coordinates and any of the
+associated world coordinate systems, then propagation of the WCS
+information is straightforward and simply involves copying the
+FrameSet associated with the image.
+
+If any of these relationships change, however, then corresponding
+changes must be made to the way Frames within the FrameSet are
+inter-related. By far the most common case occurs when the image
+undergoes some geometrical transformation resulting in ``re-gridding''
+on to another data grid, but the same principles can be applied to any
+re-definition of a coordinate system.
+
+To pursue the re-gridding example, we would need to modify our
+FrameSet to account for the fact that the image's data grid coordinate
+system (corresponding to the FrameSet's base Frame) has
+changed. Looking at the steps needed in detail, we might proceed as
+follows:
+
+\begin{enumerate}
+\item Create a \htmlref{Mapping}{Mapping} which represents the relationship between the
+original data grid coordinate system and the new one.
+
+\item Obtain a Frame to represent the new data grid coordinate system
+(we could re-use the original base Frame here, using \htmlref{AST\_GETFRAME}{AST\_GETFRAME} to
+obtain a pointer to it).
+
+\item Add the new Frame to the FrameSet, related to the original base
+Frame by the new Mapping. This Frame now represents the new data grid
+coordinate system and is correctly related to all the other Frames
+present.\footnote{This is because any transformation to or from this
+new Frame must go \emph{via} the base Frame representing the original
+data grid coordinate system, which we assume was correctly related to
+all the other Frames present.}
+
+\item Remove the original base Frame (representing the old data grid
+coordinate system).
+
+\item Make the new Frame the base Frame and restore the original
+current Frame.
+\end{enumerate}
+
+   The effect of these steps is to change the relationship between the
+   base Frame and all the other Frames present. It is as if a new Mapping
+   has been interposed between the Frame we want to alter and all the
+   other Frames within the FrameSet (Figure~\ref{fig:fsremap}).
+   \begin{figure}[hbtp]
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun210_figures/fsremap}
+\caption[Interposing a Mapping into a FrameSet]{The effect
+    of \htmlref{AST\_REMAPFRAME}{AST\_REMAPFRAME} is to interpose a Mapping between
+   a nominated Frame within a FrameSet and the remaining contents of the
+   FrameSet. This effectively ``re-defines'' the coordinate system
+   represented by the affected Frame. It may be used to compensate (say)
+   for geometrical changes made to an associated image. The
+   inter-relationships between all the other Frames within the FrameSet
+   remain unchanged.}
+   \label{fig:fsremap}
+   \end{center}
+   \end{figure}
+
+Performing the steps above is rather lengthy, however, so the
+AST\_REMAPFRAME function is provided to perform all of these
+operations in one go.  A practical example of its use is given below
+(\secref{ss:wcsprocessingexample}).
+
+\subsection{\label{ss:wcsprocessingexample}Example---Binning an Image}
+
+As an example of using \htmlref{AST\_REMAPFRAME}{AST\_REMAPFRAME}, consider a case where the
+pixels of a 2-dimensional image have been binned 2$\times$2, so as to
+reduce the image size by a factor of two in each dimension.  We must
+now modify the associated \htmlref{FrameSet}{FrameSet} to reflect this change to the
+image. Much the same process would be needed for any other geometrical
+change the image might undergo.
+
+We first set up a \htmlref{Mapping}{Mapping} (a \htmlref{WinMap}{WinMap} in this case) which relates the
+data grid coordinates in the original image to those in the new one:
+
+\small
+\begin{terminalv}
+      INTEGER WINMAP
+      DOUBLE PRECISION INA( 2 ), INB( 2 ), OUTA( 2 ), OUTB( 2 )
+      DATA INA / 0.5D0, 0.5D0 /
+      DATA INB / 2.5D0, 2.5D0 /
+      DATA OUTA / 0.5D0, 0.5D0 /
+      DATA OUTB / 1.5DO, 1.5DO /
+
+      ...
+
+      WINMAP = AST_WINMAP( 2, INA, INB, OUTA, OUTB, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+Here, we have simply set up arrays containing the data grid
+coordinates of the bottom left and top right corners of the first
+element in the output image (OUTA and OUTB) and the corresponding
+coordinates in the input image (INA and INB). \htmlref{AST\_WINMAP}{AST\_WINMAP} then creates
+a WinMap which performs the required transformation. We do not need to
+know the size of the image.
+
+We can then pass this WinMap to AST\_REMAPFRAME. This modifies the
+relationship between our FrameSet's base \htmlref{Frame}{Frame} and the other Frames in
+the FrameSet, so that the base Frame represents the data grid
+coordinate system of the new image rather than the old one:
+
+\small
+\begin{terminalv}
+      INTEGER FRAMESET
+
+      ...
+
+      CALL AST_REMAPFRAME( FRAMESET, AST__BASE, WINMAP, STATUS )
+\end{terminalv}
+\normalsize
+
+Any other coordinate systems described by the FrameSet, no matter how
+many of these there might be, are now correctly associated with the
+new image.
+
+\subsection{\label{ss:framesetintegrity}Maintaining the Integrity of FrameSets}
+
+When constructing a \htmlref{FrameSet}{FrameSet}, you are provided with a framework into
+which you can place any combination of Frames and Mappings that you
+wish. There are relatively few constraints on this process and no
+checks are performed to see whether the FrameSet you construct makes
+physical sense.  It is quite possible, for example, to construct a
+FrameSet containing two identical SkyFrames which are inter-related by
+a non-unit \htmlref{Mapping}{Mapping}. AST will not object if you do this, but it makes
+no sense, because applying a non-unit Mapping to any set of celestial
+coordinates cannot yield positions that are still in the original
+coordinate system.  If you use such a FrameSet to perform coordinate
+conversions, you are likely to get unpredictable results because the
+information in the FrameSet is corrupt.
+
+It is, of course, your responsibility as a programmer to ensure the
+validity of any information which you insert into a
+FrameSet. Normally, this is straightforward and simply consists of
+formulating your problem correctly (a diagram can often help to
+clarify how coordinate systems are inter-related) and writing the
+appropriate bug-free code to construct the FrameSet. However, once you
+start to modify an existing FrameSet, there are new opportunities for
+corrupting it!
+
+Consider, for example, a FrameSet whose current \htmlref{Frame}{Frame} is a
+\htmlref{SkyFrame}{SkyFrame}. We can set a new value for this SkyFrame's \htmlref{Equinox}{Equinox} attribute
+simply by using \htmlref{AST\_SET}{AST\_SET} on the FrameSet, as follows:
+
+\small
+\begin{terminalv}
+      CALL AST_SET( FRAMESET, 'Equinox=J2010', STATUS )
+\end{terminalv}
+\normalsize
+
+The effect of this will be to change the celestial coordinate system
+which the current Frame represents. You can see, however, that this
+has the potential to make the FrameSet corrupt unless corresponding
+changes are also made to the Mapping which relates this SkyFrame to
+the other Frames within the FrameSet. In fact, it is a general rule
+that any change to a FrameSet which affects its current Frame can
+potentially require corresponding changes to the FrameSet's Mappings
+in order to maintain its overall integrity.
+
+Fortunately, once you have stored valid information in a FrameSet, AST
+will look after these details for you automatically, so that the
+FrameSet's integrity is maintained. In the example above, it would do
+this by appropriately re-mapping the current Frame (as if
+\htmlref{AST\_REMAPFRAME}{AST\_REMAPFRAME} had been used---\secref{ss:remapframe}) in response to
+the use of AST\_SET. One way of illustrating this process is as
+follows:
+
+\small
+\begin{terminalv}
+      INTEGER SKYFRAME
+
+      ...
+
+      SKYFRAME = AST_SKYFRAME( ' ', STATUS )
+      FRAMESET = AST_FRAMESET( SKYFRAME, STATUS )
+      CALL AST_ADDFRAME( FRAMESET, 1, AST_UNITMAP( 2, ' ', STATUS )
+     :                   SKYFRAME, STATUS )
+\end{terminalv}
+\normalsize
+
+This constructs a trivial FrameSet whose base and current Frames are
+both the same SkyFrame connected by a \htmlref{UnitMap}{UnitMap}. You can think of this
+as a ``pipe'' connecting two coordinate systems. At present, these two
+systems represent identical ICRS coordinates, so the FrameSet
+implements a unit Mapping. We can change the coordinate system on the
+current end of this pipe as follows:
+
+\small
+\begin{terminalv}
+      CALL AST_SET( FRAMESET, 'System=Ecliptic, Equinox=J2010', STATUS )
+\end{terminalv}
+\normalsize
+
+and the Mapping which the FrameSet implements would change
+accordingly. To change the coordinate system on the base end of the
+pipe, we might use:
+
+\small
+\begin{terminalv}
+      CALL AST_INVERT( FRAMESET )
+      CALL AST_SET( FRAMESET, 'System=Galactic', STATUS )
+      CALL AST_INVERT( FRAMESET )
+\end{terminalv}
+\normalsize
+
+The FrameSet would then convert between galactic and ecliptic
+coordinates.
+
+Note that AST\_SET is not the only function which has this effect:
+\htmlref{AST\_CLEAR}{AST\_CLEAR} behaves similarly, as also does \htmlref{AST\_PERMAXES}{AST\_PERMAXES}
+(\secref{ss:permutingaxes}). If you need to circumvent this mechanism
+for any reason, this can be done by going behind the scenes and
+obtaining a pointer directly to the Frame you wish to modify. Consider
+the following, for example:
+
+\small
+\begin{terminalv}
+      SKYFRAME = AST_GETFRAME( FRAMESET, AST__CURRENT, STATUS )
+      CALL AST_SET( SKYFRAME, 'Equinox=J2010', STATUS )
+      CALL AST_ANNUL( SKYFRAME, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, AST\_SET is applied to the SkyFrame pointer rather than the
+FrameSet pointer, so the usual checks on FrameSet integrity do not
+occur. The SkyFrame's Equinox attribute will therefore be modified
+without any corresponding change to the FrameSet's Mappings.  In this
+case you must take responsibility yourself for maintaining the
+FrameSet's integrity, perhaps through appropriate use of
+AST\_REMAPFRAME.
+
+\subsection{Merging FrameSets}
+
+   As well as adding individual Frames to a \htmlref{FrameSet}{FrameSet}
+   (\secref{ss:addingframes}), it is also possible to add complete sets of
+   inter-related Frames which are contained within another
+   FrameSet. This, of course, corresponds to the process of merging two
+   FrameSets (Figure~\ref{fig:fsmerge}).
+   \begin{figure}[hbtp]
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun210_figures/fsmerge}
+   \caption[Two FrameSets in the process of being merged.]{Two FrameSets in the process of being merged using
+   \htmlref{AST\_ADDFRAME}{AST\_ADDFRAME}. FrameSet~B is being added to FrameSet~A by supplying a
+   new \htmlref{Mapping}{Mapping} which inter-relates a nominated \htmlref{Frame}{Frame} in A (here number~1)
+   and the current Frame of B. In the merged FrameSet, the Frames
+   contributed by B will be re-numbered to become Frames~4, 5 and 6. The
+   base Frame will remain unchanged, but the current Frame of B becomes
+   the new current Frame. Note that FrameSet~B itself is not
+   altered by this process.}
+   \label{fig:fsmerge}
+   \end{center}
+   \end{figure}
+
+
+
+This process is performed by adding one FrameSet to another using
+AST\_ADDFRAME, in much the same manner as when adding a new Frame to
+an existing FrameSet (\secref{ss:addingframes}). It is simply a matter
+of providing a FrameSet pointer, instead of a Frame pointer, for the
+4th argument. In performing the merger you must, as usual, supply a
+Mapping, but in this case the Mapping should relate the current Frame
+of the FrameSet being added to one of the Frames already present. For
+example, you might perform the merger shown in
+Figure~\ref{fig:fsmerge} as follows:
+
+\small
+\begin{terminalv}
+      INTEGER MAPPING
+
+      ...
+
+      CALL AST_ADDFRAME( FRAMESETA, 1, MAPPING, FRAMESETB, STATUS )
+\end{terminalv}
+\normalsize
+
+The Frames acquired by FRAMESETA from the FrameSet being added
+(FRAMESETB) are re-numbered so that they retain their original order
+and follow on consecutively after the Frames that were already
+present, whose indices remain unchanged. The base Frame of FRAMESETA
+remains unchanged, but the current Frame of FRAMESETB becomes its new
+current Frame. All the inter-relationships between Frames in both
+FrameSets remain in place and are preserved in the merged FrameSet.
+
+Note that while this process modifies the first FrameSet (FRAMESETA),
+it leaves the original contents of the one being added (FRAMESETB)
+unchanged.
+
+%\cleardoublepage
+%\section{\label{ss:searching}TBW - Searching for Coordinate Systems}
+
+\cleardoublepage
+\section{\label{ss:channels}Saving and Restoring Objects (Channels)}
+
+Facilities are provided by the AST library for performing input and
+output (I/O) with any kind of \htmlref{Object}{Object}. This means it is possible
+to write any Object into various external representations for
+storage, and then to read these representations back in, so as to
+restore the original Object. Typically, an Object would be written by
+one program and read back in by another.
+
+We refer to ``external representations'' in the plural because AST is
+designed to function independently of any particular data storage
+system. This means that Objects may need converting into a number of
+different external representations in order to be compatible with
+(say) the astronomical data storage system in which they will reside.
+
+In this section, we discuss the basic I/O facilities which support
+external representations based on a textual format referred to as the AST
+``native format''. These are implemented using a new kind of Object---a
+\htmlref{Channel}{Channel}. We will examine later how to use other representations, based on
+an XML format or on the use of FITS headers, for storing Objects. These
+are implemented using more specialised forms of Channel called \htmlref{XmlChan}{XmlChan}
+(\secref{ss:xmlchan}) and \htmlref{FitsChan}{FitsChan} (\secref{ss:nativefits}).
+
+\subsection{The Channel Model}
+
+The best way to start thinking about a \htmlref{Channel}{Channel} is like a Fortran I/O
+unit (also represented by an integer, as it happens) and to think of
+the process of creating a Channel as the combined process of
+allocating a unit number and attaching it to a file by opening the
+file on that unit. Subsequently, you can read and write Objects
+\emph{via} the Channel.
+
+This analogy is not quite perfect, however, because a Channel has, in
+principle, two ``files'' attached to it. One is used when reading, and
+the other when writing. These are termed the Channel's \emph{source}
+and \emph{sink} respectively. In practice, the source and sink may
+both be the same, in which case the analogy with the Fortran I/O unit
+is correct, but this need not always be so. It is not necessarily so
+with the basic Channel, as we will now see
+(\secref{ss:creatingachannel}).
+
+\subsection{\label{ss:creatingachannel}Creating a Channel}
+
+The process of creating a \htmlref{Channel}{Channel} is straightforward. As you
+might expect, it uses the constructor function \htmlref{AST\_CHANNEL}{AST\_CHANNEL}:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER CHANNEL, STATUS
+
+      STATUS = 0
+
+      ...
+
+      CHANNEL = AST_CHANNEL( AST_NULL, AST_NULL, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+The first two arguments to AST\_CHANNEL specify the external source
+and sink that the Channel is to use. There arguments are the names of
+Fortran subroutines and we will examine their use in more detail later
+(\secref{ss:channelsource} and \secref{ss:channelsink}).
+
+In this very simple example we have supplied the name of the null
+routine AST\_NULL\footnote{Note that AST\_NULL (one underscore) is a
+routine name and is distinct from AST\_\_NULL (two underscores) which
+is a null \htmlref{Object}{Object} pointer.  Since we are passing the name of one
+routine to another routine, AST\_NULL would normally have to appear in
+a Fortran EXTERNAL statement. In this example, however, a suitable
+statement is already present in the AST\_PAR include file.} for both
+the source and sink routines.  This requests the default behaviour,
+which means that textual input will be read from the program's
+standard input stream (typically, this means your keyboard) while
+textual output will go to the standard output stream (typically
+appearing on your screen). On UNIX systems, of course, either of these
+streams can easily be redirected to files.
+
+\subsection{\label{ss:writingtoachannel}Writing Objects to a Channel}
+
+The process of saving Objects is very straightforward. You can
+simply write any \htmlref{Object}{Object} to a \htmlref{Channel}{Channel} using the \htmlref{AST\_WRITE}{AST\_WRITE}
+function, as follows:
+
+\small
+\begin{terminalv}
+      INTEGER NOBJ, OBJECT
+
+      ...
+
+      NOBJ = AST_WRITE( CHANNEL, OBJECT, STATUS )
+\end{terminalv}
+\normalsize
+
+The effect of this will be to produce a textual description of the
+Object which will appear, by default, on your program's standard
+output stream. Any class of Object may be converted into text in this
+way.
+
+AST\_WRITE returns a count of the number of Objects written. Usually,
+this will be one, unless the Object supplied cannot be
+represented. With a basic Channel all Objects can be represented, so a
+value of one will always be returned unless there has been an
+error. We will see later, however, that more specialised forms of
+Channel may impose restrictions on the kind of Object you can write
+(\secref{ss:foreignfitslimitations}). In such cases, AST\_WRITE may
+return zero to indicate that the Object was not acceptable.
+
+\subsection{\label{ss:readingfromachannel}Reading Objects from a Channel}
+
+Before discussing the format of the output produced above
+(\secref{ss:writingtoachannel}), let us consider how to read it back,
+so as to reconstruct the original \htmlref{Object}{Object}. Naturally, we would first
+need to save the output in a file. We can do that either by using the
+\htmlref{SinkFile}{SinkFile} attribute, or (on UNIX systems), by redirecting standard output
+to a file using a shell command like:
+
+\small
+\begin{terminalv}
+program1 >file
+\end{terminalv}
+\normalsize
+
+Within a subsequent program, we can read this Object back in by
+using the \htmlref{AST\_READ}{AST\_READ} function, having first created a suitable
+\htmlref{Channel}{Channel}:
+
+\small
+\begin{terminalv}
+      OBJECT = AST_READ( CHANNEL, STATUS )
+\end{terminalv}
+\normalsize
+
+By default, this function will read from the standard input stream
+(the default source for a basic Channel), so we would need to ensure
+that our second program reads its input from the file in which the
+Object description is stored. On UNIX systems, we could again use a
+shell redirection command such as:
+
+\small
+\begin{terminalv}
+program2 <file
+\end{terminalv}
+\normalsize
+
+Alternatively, we could have assigned a value to the SinkFile attribute
+before invoking
+AST\_READ.
+
+\subsection{Saving and Restoring Multiple Objects}
+
+I/O operations performed on a basic \htmlref{Channel}{Channel} are sequential. This
+means that if you write more than one \htmlref{Object}{Object} to a Channel,
+each new Object's textual description is simply appended to the
+previous one. You can store any number of Objects in this way,
+subject only to the storage space you have available.
+
+After you read an Object back from a basic Channel, the
+Channel is ``positioned'' at the end of that Object's
+textual description. If you then perform another read, you will
+read the next Object's textual description and therefore
+retrieve the next Object.  This process may be repeated to read
+each Object in turn. When there are no more Objects to be
+read, \htmlref{AST\_READ}{AST\_READ} will return the value AST\_\_NULL to indicate an
+\emph{end-of-file}.
+
+\subsection{\label{ss:validatinginput}Validating Input}
+
+The pointer returned by \htmlref{AST\_READ}{AST\_READ} (\secref{ss:readingfromachannel})
+could identify any class of \htmlref{Object}{Object}---this is determined entirely by
+the external data being read. If it is necessary to test for a
+particular class (say a \htmlref{Frame}{Frame}), this may be done as follows using the
+appropriate member of the \htmlref{AST\_ISA$<$CLASS$>$}{AST\_ISA$<$CLASS$>$} family of functions:
+
+\small
+\begin{terminalv}
+      LOGICAL OK
+
+      ...
+
+      OK = AST_ISAFRAME( OBJECT, STATUS )
+\end{terminalv}
+\normalsize
+
+Note, however, that this will accept any Frame, so would be equally
+happy with a basic Frame or a \htmlref{SkyFrame}{SkyFrame}.  An alternative validation
+strategy would be to obtain the value of the Object's \htmlref{Class}{Class} attribute
+and then test this character string, as follows:
+
+\small
+\begin{terminalv}
+      OK = AST_GETC( OBJECT, 'Class', STATUS ) .EQ. 'Frame'
+\end{terminalv}
+\normalsize
+
+This would only accept a basic Frame and would reject a SkyFrame.
+
+\subsection{Storing an ID String with an Object}
+
+Occasionally, you may want to store a number of Objects and later
+retrieve them and use each for a different purpose. If the Objects are
+of the same class, you cannot use the \htmlref{Class}{Class} attribute to distinguish
+them when you read them back
+(\emph{c.f.}~\secref{ss:validatinginput}). Although relying on the
+order in which they are stored is a possible solution, this becomes
+complicated if some of the Objects are optional and may not always be
+present. It also makes extending your data format in future more
+difficult.
+
+To help with this, every AST \htmlref{Object}{Object} has an \htmlref{ID}{ID} attribute and an \htmlref{Ident}{Ident}
+attribute, both of which allows you, in effect, to attach a textual
+identification label to it. You simply set the ID or Ident attribute before
+writing the Object:
+
+\small
+\begin{terminalv}
+      CALL AST_SET( OBJECT, 'ID=Calibration', STATUS )
+      NOBJ = AST_WRITE( CHANNEL, OBJECT, STATUS )
+\end{terminalv}
+\normalsize
+
+You can then test its value after you read the Object back:
+
+\small
+\begin{terminalv}
+      OBJECT = AST_READ( CHANNEL, STATUS )
+      IF ( AST_GETC( OBJECT, 'ID', STATUS ) .EQ. 'Calibration' ) THEN
+         <the Calibration Object has been read>
+      ELSE
+         <some other Object has been read>
+      END IF
+\end{terminalv}
+\normalsize
+
+The only difference between the ID and Ident attributes is that the ID
+attribute is unique to a particular Object and is lost if, for example,
+you make a copy of the Object. The Ident attrubute, on the other hand, is
+transferred to the new Object when a copy is made. Consequently, it is
+safest to set the value of the ID attribute immediately before you
+perform the write.
+
+\subsection{\label{ss:textualoutputformat}The Textual Output Format}
+
+Let us now examine the format of the textual output produced by
+writing an \htmlref{Object}{Object} to a basic \htmlref{Channel}{Channel}
+(\secref{ss:writingtoachannel}). To give a concrete example, suppose
+the Object in question is a \htmlref{SkyFrame}{SkyFrame}, written out as follows:
+
+\small
+\begin{terminalv}
+      INTEGER SKYFRAME
+
+      ...
+
+      NOBJ = AST_WRITE( CHANNEL, SKYFRAME, STATUS )
+\end{terminalv}
+\normalsize
+
+The output should then look like the following:
+
+\small
+\begin{terminalv}
+ Begin SkyFrame 	# Description of celestial coordinate system
+#   Title = "FK4 Equatorial Coordinates, no E-terms, Mean Equinox B1950.0, Epoch B1958.0" 	# Title of coordinate system
+    Naxes = 2 	# Number of coordinate axes
+#   Domain = "SKY" 	# Coordinate system domain
+#   Lbl1 = "Right Ascension" 	# Label for axis 1
+#   Lbl2 = "Declination" 	# Label for axis 2
+#   Uni1 = "hh:mm:ss.s" 	# Units for axis 1
+#   Uni2 = "ddd:mm:ss" 	# Units for axis 2
+#   Dir1 = 0 	# Plot axis 1 in reverse direction (hint)
+    Ax1 = 	# Axis number 1
+       Begin SkyAxis 	# Celestial coordinate axis
+       End SkyAxis
+    Ax2 = 	# Axis number 2
+       Begin SkyAxis 	# Celestial coordinate axis
+       End SkyAxis
+ IsA Frame 	# Coordinate system description
+    System = "FK4-NO-E" 	# Celestial coordinate system type
+    Epoch = 1958 	# Besselian epoch of observation
+#   Eqnox = 1950 	# Besselian epoch of mean equinox
+ End SkyFrame
+\end{terminalv}
+\normalsize
+
+You will notice that this output is designed both for a human reader,
+in that it is formatted, and also to be read back by a computer in
+order to reconstruct the SkyFrame. In fact, this is precisely the way
+that \htmlref{AST\_SHOW}{AST\_SHOW} works (\secref{ss:displayingobjects}), this routine
+being roughly equivalent to the following use of a Channel:
+
+\small
+\begin{terminalv}
+      CHANNEL = AST_CHANNEL( AST_NULL, AST_NULL, ' ', STATUS )
+      NOBJ = AST_WRITE( CHANNEL, OBJECT, STATUS )
+      CALL AST_ANNUL( CHANNEL, STATUS )
+\end{terminalv}
+\normalsize
+
+Some lines of the output start with a ``\verb?#?'' comment character,
+which turns the rest of the line into a comment. These lines will be
+ignored when read back in by \htmlref{AST\_READ}{AST\_READ}.  They typically contain
+default values, or values that can be derived in some way from the
+other data present, so that they do not actually need to be stored in
+order to reconstruct the original Object. They are provided purely for
+human information. The same comment character is also used to append
+explanatory comments to most output lines.
+
+It is not sensible to attempt a complete description of this output
+format because every class of Object is potentially different and each
+can define how its own data should be represented. However, there are
+some basic rules, which mean that the following common features will
+usually be present:
+
+\begin{enumerate}
+\item Each Object is delimited by matching ``Begin'' and ``End''
+lines, which also identify the class of Object involved.
+
+\item Within each Object description, data values are represented
+by a simple ``keyword~$=$~value'' syntax, with one value to a line.
+
+\item Lines beginning ``IsA'' are used to mark the divisions between
+data belonging to different levels in the class hierarchy
+(\appref{ss:classhierarchy}). Thus, ``IsA~\htmlref{Frame}{Frame}'' marks the end of data
+associated with the Frame class and the start of data associated with
+some derived class (a SkyFrame in the above example). ``IsA'' lines
+may be omitted if associated data values are absent and no confusion
+arises.
+
+\item Objects may contain other Objects as data. This is
+indicated by an absent value, with the description of the data
+Object following on subsequent lines.
+
+\item Indentation is used to clarify the overall structure.
+\end{enumerate}
+
+Beyond these general principles, the best guide to what a particular
+line of output represents will generally be the comment which
+accompanies it together with a general knowledge of the class of
+Object being described.
+
+\subsection{\label{ss:controllingchanneloutput}Controlling the Amount of Output}
+
+It is not always necessary for the output from \htmlref{AST\_WRITE}{AST\_WRITE}
+(\secref{ss:writingtoachannel}) to be human-readable, so a \htmlref{Channel}{Channel} has
+attributes that allow the amount of detail in the output to be
+controlled.
+
+The first of these is the integer attribute \htmlref{Full}{Full}, which controls the
+extent to which optional, commented out, output lines are produced. By
+default, Full is zero, and this results in the standard style of
+output (\secref{ss:textualoutputformat}) where default values that may
+be helpful to humans are included. To suppress these optional lines,
+Full should be set to $-$1. This is most conveniently done when the
+Channel is created, so that:
+
+\small
+\begin{terminalv}
+      CHANNEL = AST_CHANNEL( AST_NULL, AST_NULL, 'Full=-1', STATUS )
+      NOBJ = AST_WRITE( CHANNEL, SKYFRAME, STATUS )
+      CALL AST_ANNUL( CHANNEL, STATUS )
+\end{terminalv}
+\normalsize
+
+would result in output containing only the essential information, such
+as:
+
+\small
+\begin{terminalv}
+ Begin SkyFrame 	# Description of celestial coordinate system
+    Naxes = 2 	# Number of coordinate axes
+    Ax1 = 	# Axis number 1
+       Begin SkyAxis 	# Celestial coordinate axis
+       End SkyAxis
+    Ax2 = 	# Axis number 2
+       Begin SkyAxis 	# Celestial coordinate axis
+       End SkyAxis
+ IsA Frame 	# Coordinate system description
+    System = "FK4-NO-E" 	# Celestial coordinate system type
+    Epoch = 1958 	# Besselian epoch of observation
+ End SkyFrame
+\end{terminalv}
+\normalsize
+
+In contrast, setting Full to $+$1 will result in additional output
+lines which will reveal every last detail of the \htmlref{Object}{Object}'s
+construction. Often this will be rather more than you want, especially
+for more complex Objects, but it can sometimes help when debugging
+programs. This is how a \htmlref{SkyFrame}{SkyFrame} appears at this level of detail:
+
+\small
+\begin{terminalv}
+ Begin SkyFrame 	# Description of celestial coordinate system
+#   RefCnt = 1 	# Count of active Object pointers
+#   Nobj = 1 	# Count of active Objects in same class
+ IsA Object 	# Astrometry Object
+#   Nin = 2 	# Number of input coordinates
+#   Nout = 2 	# Number of output coordinates
+#   Invert = 0 	# Mapping not inverted
+#   Fwd = 1 	# Forward transformation defined
+#   Inv = 1 	# Inverse transformation defined
+#   Report = 0 	# Don't report coordinate transformations
+ IsA Mapping 	# Mapping between coordinate systems
+#   Title = "FK4 Equatorial Coordinates, no E-terms, Mean Equinox B1950.0, Epoch B1958.0" 	# Title of coordinate system
+    Naxes = 2 	# Number of coordinate axes
+#   Domain = "SKY" 	# Coordinate system domain
+#   Lbl1 = "Right Ascension" 	# Label for axis 1
+#   Lbl2 = "Declination" 	# Label for axis 2
+#   Sym1 = "RA" 	# Symbol for axis 1
+#   Sym2 = "Dec" 	# Symbol for axis 2
+#   Uni1 = "hh:mm:ss.s" 	# Units for axis 1
+#   Uni2 = "ddd:mm:ss" 	# Units for axis 2
+#   Dig1 = 7 	# Individual precision for axis 1
+#   Dig2 = 7 	# Individual precision for axis 2
+#   Digits = 7 	# Default formatting precision
+#   Fmt1 = "hms.1" 	# Format specifier for axis 1
+#   Fmt2 = "dms" 	# Format specifier for axis 2
+#   Dir1 = 0 	# Plot axis 1 in reverse direction (hint)
+#   Dir2 = 1 	# Plot axis 2 in conventional direction (hint)
+#   Presrv = 0 	# Don't preserve target axes
+#   Permut = 1 	# Axes may be permuted to match
+#   MinAx = 2 	# Minimum number of axes to match
+#   MaxAx = 2 	# Maximum number of axes to match
+#   MchEnd = 0 	# Match initial target axes
+#   Prm1 = 1 	# Axis 1 not permuted
+#   Prm2 = 2 	# Axis 2 not permuted
+    Ax1 = 	# Axis number 1
+       Begin SkyAxis 	# Celestial coordinate axis
+#         RefCnt = 1 	# Count of active Object pointers
+#         Nobj = 2 	# Count of active Objects in same class
+       IsA Object 	# Astrometry Object
+#         Label = "Angle on Sky" 	# Axis Label
+#         Symbol = "delta" 	# Axis symbol
+#         Unit = "ddd:mm:ss" 	# Axis units
+#         Digits = 7 	# Default formatting precision
+#         Format = "dms" 	# Format specifier
+#         Dirn = 1 	# Plot in conventional direction
+       IsA Axis 	# Coordinate axis
+#         Format = "dms" 	# Format specifier
+#         IsLat = 0 	# Longitude axis (not latitude)
+#         AsTime = 0 	# Display values as angles (not times)
+       End SkyAxis
+    Ax2 = 	# Axis number 2
+       Begin SkyAxis 	# Celestial coordinate axis
+#         RefCnt = 1 	# Count of active Object pointers
+#         Nobj = 2 	# Count of active Objects in same class
+       IsA Object 	# Astrometry Object
+#         Label = "Angle on Sky" 	# Axis Label
+#         Symbol = "delta" 	# Axis symbol
+#         Unit = "ddd:mm:ss" 	# Axis units
+#         Digits = 7 	# Default formatting precision
+#         Format = "dms" 	# Format specifier
+#         Dirn = 1 	# Plot in conventional direction
+       IsA Axis 	# Coordinate axis
+#         Format = "dms" 	# Format specifier
+#         IsLat = 0 	# Longitude axis (not latitude)
+#         AsTime = 0 	# Display values as angles (not times)
+       End SkyAxis
+ IsA Frame 	# Coordinate system description
+    System = "FK4-NO-E" 	# Celestial coordinate system type
+    Epoch = 1958 	# Besselian epoch of observation
+#   Eqnox = 1950 	# Besselian epoch of mean equinox
+ End SkyFrame
+\end{terminalv}
+\normalsize
+
+\subsection{\label{ss:channelcommenting}Controlling Commenting}
+
+Another way of controlling output from a \htmlref{Channel}{Channel} is \emph{via} the
+boolean (integer) \htmlref{Comment}{Comment} attribute, which controls whether comments
+are appended to describe the purpose of each value. Comment has the
+value 1 by default but, if set to zero, will suppress these
+comments. This is normally appropriate only if you wish to minimise
+the amount of output, for example:
+
+\small
+\begin{terminalv}
+      CALL AST_SET( CHANNEL, 'Full=-1, Comment=0', STATUS )
+      NOBJ = AST_WRITE( CHANNEL, SKYFRAME, STATUS )
+\end{terminalv}
+\normalsize
+
+might result in the following more compact output:
+
+\small
+\begin{terminalv}
+ Begin SkyFrame
+    Naxes = 2
+    Ax1 =
+       Begin SkyAxis
+       End SkyAxis
+    Ax2 =
+       Begin SkyAxis
+       End SkyAxis
+ IsA Frame
+    System = "FK4-NO-E"
+    Epoch = 1958
+ End SkyFrame
+\end{terminalv}
+\normalsize
+
+\subsection{Editing Textual Output}
+
+The safest advice about editing the textual output from \htmlref{AST\_WRITE}{AST\_WRITE} (or
+\htmlref{AST\_SHOW}{AST\_SHOW}) is ``don't!''---unless you know what you are doing.
+
+Having given that warning, however, it is sometimes possible to make
+changes to the text, or even to write entire \htmlref{Object}{Object} descriptions from
+scratch, and to read the results back in to construct new
+Objects. Normally, simple changes to numerical values are safest, but
+be aware that this is a back door method of creating Objects, so
+you are on your own! There are a number of potential pitfalls. In
+particular:
+
+\begin{itemize}
+\item \htmlref{AST\_READ}{AST\_READ} is intended for retrieving data written by AST\_WRITE
+and not for reading data input by humans. As such, the data validation
+provided is very limited and is certainly not foolproof. This makes it
+quite easy to construct Objects that are internally inconsistent by
+this means. In contrast, the normal programming interface incorporates
+numerous checks designed to make it impossible to construct invalid
+Objects. You should not necessarily think you have found a bug if your
+changes to an Object's textual description fail to produce the results
+you expected!
+
+\item In many instances the names associated with values in textual
+output will correspond with Object attributes. Sometimes, however,
+these names may differ from the attribute name. This is mainly because
+of length restrictions imposed by other common external formats, such
+as FITS headers. Some of the names used do not correspond with
+attributes at all.
+
+\item It is safest to change single numerical or string values.
+Beware of changing the size or shape of Objects (\emph{e.g.}\ the
+number of axes in a \htmlref{Frame}{Frame}). Often, these values must match others
+stored elsewhere within the Object and changing them in a haphazard
+fashion will not produce useful results.
+
+\item Be wary about un-commenting default values. Sometimes this will
+work, but often these values are derived from other Objects stored
+more deeply in the structure and the proper place to insert a new
+value is not where the default itself appears.
+\end{itemize}
+
+\subsection{\label{ss:mixingchanneltext}Mixing Objects with other Text}
+
+By default, when you use \htmlref{AST\_READ}{AST\_READ} to read from a basic \htmlref{Channel}{Channel}
+(\secref{ss:readingfromachannel}), it is assumed that you are reading a
+stream of text containing only AST Objects, which follow each other
+end-to-end. If any extraneous input data are encountered which do not
+appear to form part of the textual description of an \htmlref{Object}{Object}, then an
+error will result. In particular, the first input line must identify
+the start of an Object description, so you cannot start reading half
+way through an Object.
+
+Sometimes, however, you may want to store AST Object descriptions
+intermixed with other textual data. You can do this by setting the
+Channel's boolean (integer) \htmlref{Skip}{Skip} attribute to 1. This will cause every
+read to skip over extraneous data until the start of a new AST Object
+description, if any, is found. So long as your other data do not mimic
+the appearance of an AST Object description, the two sets of data can
+co-exist.
+
+For example, by setting Skip to 1, the following complete Fortran
+program will read all the AST Objects whose descriptions appear in the
+source of this document, ignoring the other text. \htmlref{AST\_SHOW}{AST\_SHOW} is used to
+display those found:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER CHANNEL, OBJECT, STATUS
+
+      STATUS = 0
+      CHANNEL = AST_CHANNEL( AST_NULL, AST_NULL, 'Skip=1', STATUS )
+ 1    OBJECT = AST_READ( CHANNEL, STATUS )
+      IF ( OBJECT .NE. AST__NULL ) THEN
+         CALL AST_SHOW( OBJECT, STATUS )
+         CALL AST_ANNUL( OBJECT, STATUS )
+         GO TO 1
+      END IF
+      CALL AST_ANNUL( CHANNEL, STATUS )
+      END
+\end{terminalv}
+\normalsize
+
+\subsection{\label{ss:channelsource}Reading Objects from Files}
+
+Thus far, we have only considered the default behaviour of a \htmlref{Channel}{Channel}
+in reading and writing Objects through a program's standard input and
+output streams. We will now consider how to access Objects stored in
+files more directly.
+
+The simple approach is to use the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes of
+the Channel. For instance, the following will read a pair of Objects from
+a text file called ``fred.txt'':
+
+\small
+\begin{terminalv}
+   CALL AST_SET( CHANNEL, 'SourceFile=fred.txt', STATUS )
+   OBJ1 = AST_READ( CHANNEL, STATUS )
+   OBJ2 = AST_READ( CHANNEL, STATUS )
+   CALL AST_CLEAR( CHANNEL, 'SourceFile', STATUS )
+\end{terminalv}
+\normalsize
+
+Note, the act of clearing the attribute tells AST that no more Objects
+are to be read from the file and so the file is then closed. If the
+attribute is not cleared, the file will remain open and further Objects
+can be read from it. The file will always be closed when the Channel is
+deleted.
+
+This simple approach will normally be sufficient. However, because the
+AST library is designed to be used from more than one language, it has
+to be a little careful about reading and writing to files. This is due
+to incompatibilities that may exist between the file I/O facilities
+provided by different languages. If such incompatibilities prevent the
+above simple system being used, we need to adopt a system that off-loads
+all file I/O to external code.
+
+What this means in practice is that if the above simple approach cannot
+be used, you must instead provide some simple
+Fortran routines that perform the actual transfer of data to and from
+files and similar external data stores. The routines you provide are
+supplied as the source and/or sink routine arguments to \htmlref{AST\_CHANNEL}{AST\_CHANNEL}
+when you create a Channel (\secref{ss:creatingachannel}). An example is
+the best way to illustrate this.
+
+Consider the following simple subroutine called SOURCE. It reads a
+single line of text from a Fortran I/O unit and then calls
+\htmlref{AST\_PUTLINE}{AST\_PUTLINE} to pass it to the AST library, together with its
+length. It sets this length to be negative if there is no more input:
+
+\small
+\begin{terminalv}
+      SUBROUTINE SOURCE( STATUS )
+      INTEGER STATUS
+      CHARACTER * ( 200 ) BUFFER
+
+      READ( 1, '(A)', END = 99 ) BUFFER
+      CALL AST_PUTLINE( BUFFER, LEN( BUFFER ), STATUS )
+      RETURN
+
+ 99   CALL AST_PUTLINE( BUFFER, -1, STATUS )
+      END
+\end{terminalv}
+\normalsize
+
+Our main program might then look something like this (omitting error
+checking for brevity):
+
+\small
+\begin{terminalv}
+      EXTERNAL SOURCE
+
+      ...
+
+*  Open the input file.
+      OPEN( UNIT = 1, FILE = 'infile.ast', STATUS = 'OLD' )
+
+*  Create the Channel and read an Object from it.
+      CHANNEL = AST_CHANNEL( SOURCE, AST_NULL, ' ', STATUS )
+      OBJECT = AST_READ( CHANNEL, STATUS )
+
+      ...
+
+*  Annul the Channel and close the file when done.
+      CALL AST_ANNUL( CHANNEL, STATUS )
+      CLOSE( 1 )
+\end{terminalv}
+\normalsize
+
+Here, we first open the required input file.  We then pass the name of
+our SOURCE routine as the first argument to AST\_CHANNEL when creating
+a new Channel (ensuring that SOURCE also appears in an EXTERNAL
+statement). When we read an \htmlref{Object}{Object} from this Channel using
+\htmlref{AST\_READ}{AST\_READ}, the SOURCE routine will be called to obtain the textual
+data from the file, the end-of-file being detected when it yields a
+negative line length.
+
+Note, if a value is set for the SourceFile attribute,
+the AST\_READ function will ignore any source routine
+specified when the Channel was created.
+
+\subsection{\label{ss:channelsink}Writing Objects to Files}
+
+As for reading, writing Objects to files can be done in two different ways.
+Again, the simple approach is to use the \htmlref{SinkFile}{SinkFile} attribute of the \htmlref{Channel}{Channel}.
+For instance, the following will write a pair of Objects to a text file
+called ``fred.txt'':
+
+\small
+\begin{terminalv}
+   CALL AST_SET( CHANNEL, 'SinkFile=fred.txt', STATUS )
+   NOBJ = AST_WRITE( CHANNEL, OBJECT1, STATUS )
+   NOBJ = AST_WRITE( CHANNEL, OBJECT2, STATUS )
+   CALL AST_CLEAR( CHANNEL, 'SinkFile', STATUS )
+\end{terminalv}
+\normalsize
+
+Note, the act of clearing the attribute tells AST that no more output
+will be written to the file and so the file is then closed. If the
+attribute is not cleared, the file will remain open and further Objects
+can be written to it. The file will always be closed when the Channel is
+deleted.
+
+If the details of the language's I/O system on the computer you are using
+means that the above approach cannot be used, then we can write a SINK routine,
+that obtains a line of output text from the AST library by calling \htmlref{AST\_GETLINE}{AST\_GETLINE}
+and then writes it to a file. We can use this in basically the same way as
+the SOURCE routine in the previous section (\secref{ss:channelsource}):
+
+\small
+\begin{terminalv}
+      SUBROUTINE SINK( STATUS )
+      INTEGER L, STATUS
+      CHARACTER * ( 200 ) BUFFER
+
+      CALL AST_GETLINE( BUFFER, L, STATUS )
+      IF ( L .GT. 0 ) WRITE( 2, '(A)' ) BUFFER( : L )
+
+      END
+\end{terminalv}
+\normalsize
+
+In this case, our main program would supply the name of this SINK
+routine as the second argument to \htmlref{AST\_CHANNEL}{AST\_CHANNEL} (ensuring that it also
+appears in an EXTERNAL statement), as follows:
+
+\small
+\begin{terminalv}
+      EXTERNAL SINK
+
+      ...
+
+*  Open the output file.
+      OPEN( UNIT = 2, FILE = 'outfile.ast', STATUS = 'NEW' )
+
+*  Create a Channel and write an Object to it.
+      CHANNEL = AST_CHANNEL( SOURCE, SINK, ' ', STATUS )
+      NOBJ = AST_WRITE( CHANNEL, OBJECT, STATUS )
+
+      ...
+
+*  Annul the Channel and close the file when done.
+      CALL AST_ANNUL( CHANNEL, STATUS )
+      CLOSE( 2 )
+\end{terminalv}
+\normalsize
+
+Note that we can specify a source and/or a sink routine for the
+Channel, and that these may use either the same file, or different
+files according to whether we are reading or writing. AST has no
+knowledge of the underlying file system, nor of file positioning. It
+just reads and writes sequentially. If you wish, for example, to
+reposition a file at the beginning in between reads and writes, then
+this can be done directly (and completely independently of AST) using
+standard Fortran statements.
+
+If an error occurs in your source or sink routine, you can communicate
+this to the AST library by setting the STATUS argument to any error
+value. This will immediately terminate the read or write operation.
+
+Note, if a value is set for the SinkFile attribute,
+the \htmlref{AST\_WRITE}{AST\_WRITE} function will ignore any sink routine
+specified when the Channel was created.
+
+\subsection{\label{ss:otherplaces}Reading and Writing Objects to other Places}
+
+It should be obvious from the above (\secref{ss:channelsource} and
+\secref{ss:channelsink}) that a \htmlref{Channel}{Channel}'s source and sink routines
+provide a flexible means of intercepting textual data that describes
+AST Objects as it flows in and out of your program. In fact, you might
+like to regard a Channel simply as a filter for converting AST Objects
+to and from a stream of text which is then handled by your source and
+sink routines, where the real I/O occurs.
+
+This gives you the ability to store AST Objects in virtually any data
+system, so long as you can convert a stream of text into something
+that can be stored (it need no longer be text) and retrieve it
+again. There is generally no need to retain comments.  Other
+possibilities, such as inter-process and network communication, could
+also be implemented \emph{via} source and sink functions in basically
+the same way.
+
+\cleardoublepage
+\section{\label{ss:nativefits}Storing AST Objects in FITS Headers (FitsChans)}
+
+A FITS header is a sequence of 80-character strings, formatted
+according to particular rules defined by the Flexible Image Transport
+\htmlref{System}{System}
+(FITS). \htmladdnormallinkfoot{FITS}{http://fits.gsfc.nasa.gov/}
+is a widely-used standard for data interchange in astronomy and has
+also been adopted as a data processing format in some astronomical
+data reduction systems.  The individual 80-character strings in a FITS
+header are usually called \emph{cards} or \emph{header cards} (for
+entirely anachronistic reasons).
+
+A sequence of FITS cards appears as a header at the start of every
+FITS data file, and sometimes also at other points within it, and is
+used to provide ancillary information which qualifies or describes the
+main array of data stored in the file. As such, FITS headers are prime
+territory for storing information about the coordinate systems
+associated with data held in FITS files.
+
+In this section, we will examine how to store information in FITS
+headers directly in the form of AST Objects---a process which is
+supported by a specialised class of \htmlref{Channel}{Channel} called a \htmlref{FitsChan}{FitsChan}. Our
+discussion here will turn out to be a transitional step that
+emphasises the similarities between a FitsChan and a Channel
+(\secref{ss:channels}). At the same time, it will prepare us for the
+next section (\secref{ss:foreignfits}), where we will examine how to
+use a FitsChan to tackle some of the more difficult problems that FITS
+headers can present.
+
+\subsection{\label{ss:nativeencoding}The Native FITS Encoding}
+
+As it turns out, we are not the first to have thought of storing WCS
+information in FITS headers. In fact, the original FITS standard (1981
+vintage) defined a set of header keywords for this purpose which have
+been widely used, although they have proved too limited for many
+practical purposes.
+
+At the time of writing, a number of different ways of using FITS
+headers for storing WCS information are in use, most (although not
+all) based on the original standard. We will refer to these
+alternative ways of storing the information as FITS \emph{encodings}
+but will defer a discussion of their advantages and limitations until
+the next section (\secref{ss:foreignfits}).
+
+Here, we will examine how to store AST Objects directly in FITS
+headers. In effect, this defines a new encoding, which we will term
+the \emph{native encoding}. This is a special kind of encoding,
+because not only does it allow us to associate conventional
+WCS calibration information with FITS data, but it also allows any other
+information that can be expressed in terms of AST Objects to be stored
+as well.  In fact, the native encoding provides us with facilities
+roughly analogous to those of the \htmlref{Channel}{Channel}
+(\secref{ss:channels})---\emph{i.e.}\ a lossless way of
+transferring AST Objects from program to program---but based on FITS
+headers instead of free-format text.
+
+\subsection{The FitsChan Model}
+
+I/O between AST Objects and FITS headers is supported by a specialised
+form of \htmlref{Channel}{Channel} called a \htmlref{FitsChan}{FitsChan}. A FitsChan contains a buffer which
+may hold any number, including zero, of FITS header cards. This buffer
+forms a workspace in which you can assemble FITS cards and manipulate
+them before writing them out to a file.
+
+By default, when a FitsChan is first created, it contains no cards and
+there are five ways of inserting cards into it:
+
+\begin{enumerate}
+\item You may add cards yourself, one at a time, using \htmlref{AST\_PUTFITS}{AST\_PUTFITS}
+(\secref{ss:addingfitscards}).
+
+\item You may add cards yourself, supplying all cards concatenated into a
+single string, using \htmlref{AST\_PUTCARDS}{AST\_PUTCARDS}.
+(\secref{ss:addingmulticards}).
+
+\item You may write an AST \htmlref{Object}{Object} to the FitsChan (using \htmlref{AST\_WRITE}{AST\_WRITE}),
+which will have the effect of creating new cards within the FitsChan
+which describe the Object (\secref{ss:writingnativefits}).
+
+\item You may assign a value to the \htmlref{SourceFile}{SourceFile} attribute of the FitsChan.
+The value should be the path to a text file holding a set of FITS header
+cards, one per line. When the SourceFile value is set (using
+AST\_SETC or \htmlref{AST\_SET}{AST\_SET}).
+the file is opened and the headers copied from it into the FitsChan.
+The file is then immediately closed.
+
+\item You may specify a source routine which reads data from some
+external store of FITS cards, just like the source associated with a
+basic Channel (\secref{ss:channelsource}). If you supply a source
+routine, it will be called when the FitsChan is created in order to
+fill it with an initial set of cards (\secref{ss:fitssourceandsink}).
+\end{enumerate}
+
+There are also four ways of removing cards from a FitsChan:
+
+\begin{enumerate}
+\item You may delete cards yourself, one at a time, using \htmlref{AST\_DELFITS}{AST\_DELFITS}
+(\secref{ss:findingandchangingfits}).
+
+\item You may read an AST Object from the FitsChan (using \htmlref{AST\_READ}{AST\_READ}),
+which will have the effect of removing those cards from the FitsChan
+which describe the Object (\secref{ss:readingnativefits}).
+
+\item You may assign a value to the FitsChan's \htmlref{SinkFile}{SinkFile} attribute. When
+the FitsChan is deleted, any remaining headers are written out to a text
+file with path equal to the value of the SinkFile attribute.
+
+\item Alternatively, You may specify a sink routine which writes data to some
+external store of FITS cards, just like the sink associated with a
+basic Channel (\secref{ss:channelsink}). If you supply a sink routine,
+it will be called when the FitsChan is deleted in order to write out
+any FITS cards that remain in it (\secref{ss:fitssourceandsink}). Note,
+the sink routine is not called if the SinkFile attribute has been set.
+\end{enumerate}
+
+Note, in particular, that reading an AST Object from a FitsChan is
+\emph{destructive}. That is, it deletes the FITS cards that describe the
+Object. The reason for this is explained in
+\secref{ss:destructiveread}.
+
+In addition to the above, you may also read individual cards from a
+FitsChan using the function \htmlref{AST\_FINDFITS}{AST\_FINDFITS} (which is not
+destructive). This is the main means of writing out FITS cards if you
+have not supplied a sink routine.  AST\_FINDFITS also provides a means
+of searching for particular FITS cards (by keyword, for example) and
+there are other facilities for overwriting cards when required
+(\secref{ss:findingandchangingfits}).
+
+\subsection{\label{ss:creatingafitschan}Creating a FitsChan}
+
+The \htmlref{FitsChan}{FitsChan} constructor function, \htmlref{AST\_FITSCHAN}{AST\_FITSCHAN}, is straightforward
+to use:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER FITSCHAN, STATUS
+
+      STATUS = 0
+
+      ...
+
+      FITSCHAN = AST_FITSCHAN( AST_NULL, AST_NULL, 'Encoding=NATIVE', STATUS )
+\end{terminalv}
+\normalsize
+
+Here, we have omitted any source or sink functions by supplying the
+AST\_NULL routine for the first two arguments (remember to include the
+AST\_PAR include file which contains the required EXTERNAL statement
+for this routine).
+We have also initialised the FitsChan's \htmlref{Encoding}{Encoding} attribute to
+NATIVE. This indicates that we will be using the native encoding
+(\secref{ss:nativeencoding}) to store and retrieve Objects. If this
+was left unspecified, the default would depend on the FitsChan's
+contents. An attempt is made to use whatever encoding appears to have
+been used previously. For an empty FitsChan, the default is NATIVE,
+but it does no harm to be sure.
+
+\subsection{\label{ss:addressingfitscards}Addressing Cards in a FitsChan}
+
+Because a \htmlref{FitsChan}{FitsChan} contains an ordered sequence of header cards, a
+mechanism is needed for addressing them. This allows you to specify
+where new cards are to be added, for example, or which card is to be
+deleted.
+
+This role is filled by the FitsChan's integer \htmlref{Card}{Card} attribute, which
+gives the index of the \emph{current card} in the FitsChan.  You can
+nominate any card you like to be current, simply by setting a new
+value for the Card attribute, for example:
+
+\small
+\begin{terminalv}
+      INTEGER ICARD
+
+      ...
+
+      CALL AST_SETI( FITSCHAN, 'Card', ICARD, STATUS )
+\end{terminalv}
+\normalsize
+
+where ICARD contains the index of the card on which you wish to
+operate next.  Some functions will update the Card attribute as a
+means of advancing through the sequence of cards, when reading them
+for example, or to indicate which card matches a search criterion.
+
+The default value for Card is one, which is the index of the first
+card. This means that you can ``rewind'' a FitsChan to access its
+first card by clearing the Card attribute:
+
+\small
+\begin{terminalv}
+      CALL AST_CLEAR( FITSCHAN, 'Card', STATUS )
+\end{terminalv}
+\normalsize
+
+The total number of cards in a FitsChan is given by the integer \htmlref{Ncard}{Ncard}
+attribute. This is a read-only attribute whose value is automatically
+updated as you add or remove cards. It means you can address all the
+cards in sequence using a loop such as the following:
+
+\small
+\begin{terminalv}
+      DO 1 ICARD = 1, AST_GETI( FITSCHAN, 'Ncard', STATUS )
+         CALL AST_SETI( FITSCHAN, 'Card', ICARD, STATUS )
+         <access the current card>
+ 1    CONTINUE
+\end{terminalv}
+\normalsize
+
+However, it is usually possible to write slightly tidier loops based
+on the \htmlref{AST\_FINDFITS}{AST\_FINDFITS} function described later
+(\secref{ss:extractingfitscards} and
+\secref{ss:findingandchangingfits}).
+
+If you set the Card attribute to a value larger than Ncard, the
+FitsChan is regarded as being positioned at its \emph{end-of-file}. In
+this case there is no current card and an attempt to obtain a value
+for the Card attribute will always return the value Ncard~$+$~1. When
+a FitsChan is empty, it is always at the end-of-file.
+
+\subsection{\label{ss:writingnativefits}Writing Native Objects to a FitsChan}
+
+Having created an empty \htmlref{FitsChan}{FitsChan} (\secref{ss:creatingafitschan}), you
+can write any AST \htmlref{Object}{Object} to it in the native encoding using the
+\htmlref{AST\_WRITE}{AST\_WRITE} function. Let us assume we are writing a
+\htmlref{SkyFrame}{SkyFrame},\footnote{More probably, you would want to write a \htmlref{FrameSet}{FrameSet},
+but for purposes of illustration a SkyFrame contains a more manageable
+amount of data.} as follows:
+
+\small
+\begin{terminalv}
+      INTEGER NOBJ, SKYFRAME
+
+      ...
+
+      NOBJ = AST_WRITE( FITSCHAN, SKYFRAME, STATUS )
+\end{terminalv}
+\normalsize
+
+Since we have selected the native encoding
+(\secref{ss:nativeencoding}), there are no restrictions on the class
+of Object we may write, so AST\_WRITE should always return a value of
+one, unless an error occurs. Unlike a basic \htmlref{Channel}{Channel}
+(\secref{ss:writingtoachannel}), this write operation will not produce
+any output from our program. The FITS headers produced are simply
+stored inside the FitsChan.
+
+After this write operation, the \htmlref{Ncard}{Ncard} attribute will be updated to
+reflect the number of new cards added to the FitsChan and the \htmlref{Card}{Card}
+attribute will point at the card immediately after the last one
+written. Since our FitsChan was initially empty, the Card attribute
+will, in this example, point at the end-of-file
+(\secref{ss:addressingfitscards}).
+
+The FITS standard imposes a limit of 68 characters on the length of
+strings which may be stored in a single header card. Sometimes, a
+description of an AST Object involves the use of strings which exceed
+this limit (\emph{e.g.}\ a \htmlref{Frame}{Frame} title can be of arbitrary length). If
+this occurs, the long string will be split over two or more header cards.
+Each ``continuation'' card will have the keyword \texttt{CONTINUE} in
+columns 1 to 8, and will contain a space in column 9 (instead of the
+usual equals sign). An ampersand (``\texttt{\&}'') is appended to the end of
+each of the strings (except the last one) to indicate that the string is
+continued on the next card.
+
+
+Note, this splitting of long strings over several cards only occurs when
+writing AST Objects to a FitsChan using the AST\_WRITE routine and the
+\emph{native} encoding. If a long string is stored in a FitsChan using
+(for instance) the \htmlref{AST\_PUTFITS}{AST\_PUTFITS} or \htmlref{AST\_PUTCARDS}{AST\_PUTCARDS} routine, it will simply be truncated.
+
+\subsection{\label{ss:extractingfitscards}Extracting Individual Cards from a FitsChan}
+
+To examine the contents of the \htmlref{FitsChan}{FitsChan} after writing the \htmlref{SkyFrame}{SkyFrame}
+above (\secref{ss:writingnativefits}), we must write a simple loop to
+extract each card in turn and print it out. We must also remember to
+rewind the FitsChan first, \emph{e.g.}\ using \htmlref{AST\_CLEAR}{AST\_CLEAR}. The
+following loop would do:
+
+\small
+\begin{terminalv}
+      CHARACTER * ( 80 ) CARD
+
+      ...
+
+      CALL AST_CLEAR( FITSCHAN, 'Card', STATUS )
+
+ 2    CONTINUE
+      IF ( AST_FINDFITS( FITSCHAN, '%f', CARD, .TRUE., STATUS ) ) THEN
+         WRITE ( *, '(A)' ) CARD
+         GO TO 2
+      END IF
+\end{terminalv}
+\normalsize
+
+Here, we have used the \htmlref{AST\_FINDFITS}{AST\_FINDFITS} function to find a FITS card by
+keyword. It is given a keyword template of ``\%f'', which matches any
+FITS keyword, so it always finds the current card, which it
+returns. Its fourth argument is set to .TRUE., to indicate that the
+\htmlref{Card}{Card} attribute should be incremented afterwards so that the following
+card will be found the next time around the loop. AST\_FINDFITS
+returns .FALSE.\ when it reaches the end-of-file and this terminates
+the loop.
+
+If we were storing the FITS headers in an output FITS file instead of
+printing them out, we might use a loop like this but replace the WRITE
+statement with a call to a suitable data access routine to store the
+header card. This would only be necessary if we had not provided a
+sink routine for the FitsChan (\secref{ss:fitssourceandsink}).
+
+\subsection{The Native FitsChan Output Format}
+
+If we print out the FITS header cards describing the \htmlref{SkyFrame}{SkyFrame} we wrote
+earlier (\secref{ss:writingnativefits}), we should obtain something
+like the following:
+
+\small
+\begin{terminalv}
+COMMENT AST ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AST
+COMMENT AST            Beginning of AST data for SkyFrame object             AST
+COMMENT AST ................................................................ AST
+BEGAST_A= 'SkyFrame'           / Description of celestial coordinate system
+NAXES_A =                    2 / Number of coordinate axes
+AX1_A   = '        '           / Axis number 1
+BEGAST_B= 'SkyAxis '           / Celestial coordinate axis
+ENDAST_A= 'SkyAxis '           / End of object definition
+AX2_A   = '        '           / Axis number 2
+BEGAST_C= 'SkyAxis '           / Celestial coordinate axis
+ENDAST_B= 'SkyAxis '           / End of object definition
+ISA_A   = 'Frame   '           / Coordinate system description
+SYSTEM_A= 'FK4-NO-E'           / Celestial coordinate system type
+EPOCH_A =               1958.0 / Besselian epoch of observation
+ENDAST_C= 'SkyFrame'           / End of object definition
+COMMENT AST ................................................................ AST
+COMMENT AST               End of AST data for SkyFrame object                AST
+COMMENT AST ---------------------------------------------------------------- AST
+\end{terminalv}
+\normalsize
+
+As you can see, this resembles the information that would be written
+to a basic \htmlref{Channel}{Channel} to describe the same SkyFrame
+(\secref{ss:textualoutputformat}), except that it has been formatted
+into 80-character header cards according to FITS conventions.
+
+There are also a number of other differences worth noting:
+
+\begin{enumerate}
+\item There is no unnecessary information about default values
+provided for the benefit of the human reader. This is because the \htmlref{Full}{Full}
+attribute for a \htmlref{FitsChan}{FitsChan} defaults to $-$1, thus suppressing this
+information (\emph{c.f.}~\secref{ss:controllingchanneloutput}). You
+can restore the information if you wish by setting Full to 0 or $+$1,
+in which case additional COMMENT cards will be generated to hold it.
+
+\item The information is not indented, because FITS does not allow
+this. However, if you change the Full attribute to 0 or $+$1, comments
+will be included that are intended to help break up the sequence of
+headers and highlight its structure. This will probably only be of use
+if you are attempting to track down a problem by examining the FITS
+cards produced in detail.
+
+\item The FITS keywords which appear to the left of the ``$=$'' signs
+have additional characters (``\_A'', ``\_B'', \emph{etc.}) appended to
+them. This is done in order to make each keyword unique.
+\end{enumerate}
+
+This last point is worth further comment and is necessary because the
+FITS standard only allows for certain keywords (such as COMMENT and
+HISTORY) to appear more than once. \htmlref{AST\_WRITE}{AST\_WRITE} therefore appends an
+arbitrary sequence of two characters to each new keyword it generates
+in order to ensure that it does not duplicate any already present in
+the FitsChan.
+
+The main risk from not following this convention is that some software
+might ignore (say) all but the last occurrence of a keyword before
+passing the FITS headers on. Such an event is unlikely, but would
+obviously destroy the information present, so AST\_WRITE enforces the
+uniqueness of the keywords it uses. The extra characters added are
+ignored when the information is read back.
+
+As with a basic Channel, you can also suppress the comments produced
+in a FitsChan by setting the boolean (integer) \htmlref{Comment}{Comment} attribute to
+zero (\secref{ss:channelcommenting}). However, FITS headers are
+traditionally generously commented, so this is not recommended.
+
+\subsection{\label{ss:addingfitscards}Adding Individual Cards to a FitsChan}
+
+To insert individual cards into a \htmlref{FitsChan}{FitsChan}, prior to reading them back
+as Objects for example, you should use the \htmlref{AST\_PUTFITS}{AST\_PUTFITS} routine. You
+can insert a card in front of the current one as follows:
+
+\small
+\begin{terminalv}
+      CALL AST_PUTFITS( FITSCHAN, CARD, .FALSE., STATUS )
+\end{terminalv}
+\normalsize
+
+where the third argument of .FALSE.\ indicates that the current card
+should not be overwritten. Note that facilities are not provided by
+AST for formatting the card contents.
+
+After inserting a card, the FitsChan's \htmlref{Card}{Card} attribute points at the
+original Card, or at the end-of-file if the FitsChan was originally
+empty. Entering a sequence of cards is therefore straightforward. If
+CARDS is an array of character strings containing FITS header cards
+and NCARDS is the number of cards, then a loop such as the following
+will insert the cards in sequence into a FitsChan:
+
+\small
+\begin{terminalv}
+      INTEGER NCARD
+      CHARACTER * ( 80 ) CARDS( NCARD )
+
+      ...
+
+      DO 3 ICARD = 1, NCARD
+         CALL AST_PUTFITS( FITSCHAN, CARDS( ICARD ), .FALSE., STATUS )
+ 3    CONTINUE
+\end{terminalv}
+\normalsize
+
+
+Note that AST\_PUTFITS enforces the validity of a FitsChan by
+rejecting any cards which do not adhere to the FITS standard. If any
+such cards are detected, an error will result.
+
+\subsection{\label{ss:addingmulticards}Adding Concatenated Cards to a FitsChan}
+
+If you have all your cards concatenated together into a single long string,
+each occupying 80 characters (with no delimiters), you can insert them
+into a \htmlref{FitsChan}{FitsChan} in a single call using
+\htmlref{AST\_PUTCARDS}{AST\_PUTCARDS}.
+This call first empties the supplied FitsChan of any existing cards, then
+inserts the new cards, and finally rewinds the FitsChan so that a
+subsequent call to
+\htmlref{AST\_READ}{AST\_READ}
+will start reading from the first supplied card. The
+AST\_PUTCARDS routine uses \htmlref{AST\_PUTFITS}{AST\_PUTFITS}
+internally to interpret and store each individual card, and so the
+caveats in \secref{ss:addingfitscards} should be read.
+
+
+\subsection{\label{ss:readingnativefits}Reading Native Objects From a FitsChan}
+
+Once you have stored a FITS header description of an \htmlref{Object}{Object} in a
+\htmlref{FitsChan}{FitsChan} using the native encoding (\secref{ss:writingnativefits}),
+you can read it back using \htmlref{AST\_READ}{AST\_READ} in much the same way as with a
+basic \htmlref{Channel}{Channel} (\secref{ss:readingfromachannel}). Similar comments
+about validating the Object you read also apply
+(\secref{ss:validatinginput}).  If you have just written to the
+FitsChan, you must remember to rewind it first:
+
+\small
+\begin{terminalv}
+      INTEGER OBJECT
+
+      ...
+
+      CALL AST_CLEAR( FITSCHAN, 'Card', STATUS )
+      OBJECT = AST_READ( FITSCHAN, STATUS )
+\end{terminalv}
+\normalsize
+
+An important feature of a FitsChan is that read operations are
+destructive. This means that if an Object description is found, it
+will be consumed by AST\_READ which will remove all the cards
+involved, including associated COMMENT cards, from the FitsChan. Thus,
+if you write an Object to a FitsChan, rewind, and read the same Object
+back, you should end up with the original FitsChan contents.  If you
+need to circumvent this behaviour for any reason, it is a simple
+matter to make a copy of a FitsChan using \htmlref{AST\_COPY}{AST\_COPY}
+(\secref{ss:copyingobjects}). If you then read from the copy, the
+original FitsChan will remain untouched.
+
+After a read completes, the FitsChan's \htmlref{Card}{Card} attribute identifies the
+card immediately following the last card read, or the end-of-file of
+there are no more cards.
+
+
+Since the \emph{native} encoding is being used, any long strings involved
+in the object description will have been split into two or more adjacent
+contuation cards when the Object was stored in the header using routine
+\htmlref{AST\_WRITE}{AST\_WRITE}. The AST\_READ routine reverses this process by concatenating
+any such adjacent continuation cards to re-create the original long
+string.
+
+\subsection{Saving and Restoring Multiple Objects in a FitsChan}
+
+When using the native FITS encoding, multiple Objects may be stored
+and all I/O operations are sequential.  This means that you can simply
+write a sequence of Objects to a \htmlref{FitsChan}{FitsChan}. After each write operation,
+the \htmlref{Card}{Card} attribute will be updated so that the next write appends the
+next \htmlref{Object}{Object} description to the previous one.
+
+If you then rewind the FitsChan, you can read the Objects back in the
+original order. Reading them back will, of course, remove their
+descriptions from the FitsChan (\secref{ss:readingnativefits}) but the
+behaviour of the Card attribute is such that successive reads will
+simply return each Object in sequence.
+
+The only thing that may require care, given that a FitsChan can always
+be addressed randomly by setting its Card attribute, is to avoid
+writing one Object on top of another. For obvious reasons, the Object
+descriptions in a FitsChan must remain separate if they are to make
+sense when read back.
+
+\subsection{Mixing Native Objects with Other FITS Cards}
+
+Of course, any real FITS header will contain other information besides
+AST Objects, if only the mandatory FITS cards that must accompany all
+FITS data. When FITS headers are read in from a real dataset,
+therefore, any native AST \htmlref{Object}{Object} descriptions will be inter-mixed with
+many other cards.
+
+Because this is the normal state of affairs, the boolean (integer)
+\htmlref{Skip}{Skip} attribute for a \htmlref{FitsChan}{FitsChan} defaults to one. This means that when
+you read an Object From a FitsChan, any irrelevant cards will simply
+be skipped over until the start of the next Object description, if
+any, is found. If you start reading part way through an Object
+description, no error will result. The remainder of the description
+will simply be skipped.
+
+Setting Skip to zero will change this behaviour to resemble that of a
+basic \htmlref{Channel}{Channel} (\secref{ss:mixingchanneltext}), where extraneous data
+are not permitted by default, but this will probably rarely be useful.
+
+\subsection{\label{ss:findingandchangingfits}Finding and Changing Cards in a FitsChan}
+
+You can search for, and retrieve, particular cards in a \htmlref{FitsChan}{FitsChan} by
+keyword, using the function \htmlref{AST\_FINDFITS}{AST\_FINDFITS}. This performs a search,
+starting at the current card, until it finds a card whose keyword
+matches the template you supply, or the end-of-file is reached.
+
+If a suitable card is found, AST\_FINDFITS returns the card's contents
+and then sets the FitsChan's \htmlref{Card}{Card} attribute either to identify the
+card found, or the one following it. The way you want the Card
+attribute to be set is indicated by the fourth (logical) argument to
+AST\_FINDFITS. A value of .TRUE.\ is returned to indicate success.  If
+a suitable card cannot be found, AST\_FINDFITS returns a value of
+.FALSE.\ to indicate failure and sets the FitsChan's Card attribute to
+the end-of-file.
+
+Requesting that the Card attribute be set to indicate the card that
+AST\_FINDFITS finds is useful if you want to replace that card with a
+new one, as in this example:
+
+\small
+\begin{terminalv}
+      CHARACTER * ( 80 ) NEWCARD
+      LOGICAL JUNK
+
+      ...
+
+      JUNK = AST_FINDFITS( FITSCHAN, 'AIRMASS', CARD, .FALSE., STATUS )
+      CALL AST_PUTFITS( FITSCHAN, NEWCARD, .TRUE., STATUS )
+\end{terminalv}
+\normalsize
+
+Here, AST\_FINDFITS is used to search for a card with the keyword
+AIRMASS. If the card is found, \htmlref{AST\_PUTFITS}{AST\_PUTFITS} then overwrites it with a
+new card.  Otherwise, the Card attribute ends up pointing at the
+end-of-file and the new card is simply appended to the end of the
+FitsChan.
+
+A similar approach can be used to delete selected cards from a
+FitsChan using \htmlref{AST\_DELFITS}{AST\_DELFITS}, which deletes the current card:
+
+\small
+\begin{terminalv}
+      IF ( AST_FINDFITS( FITSCHAN, 'BSCALE', CARD, .FALSE., STATUS ) ) THEN
+         CALL AST_DELFITS( FITSCHAN, STATUS )
+      END IF
+\end{terminalv}
+\normalsize
+
+This deletes the first card, if any, with the BSCALE keyword.
+
+Requesting that AST\_FINDFITS increments the Card attribute to
+identify the card following the one found is more useful when writing
+loops.  For example, the following loop extracts each card whose
+keyword matches the template ``CD\%6d'' (that is, ``CD'' followed by
+six decimal digits):
+
+\small
+\begin{terminalv}
+ 4    CONTINUE
+      IF ( AST_FINDFITS( FITSCHAN, 'CD%6d', CARD, .TRUE., STATUS ) ) THEN
+         <process the card's contents>
+         GO TO 4
+      END IF
+\end{terminalv}
+\normalsize
+
+For further details of keyword templates, see the description of
+AST\_FINDFITS in \appref{ss:functiondescriptions}.
+
+\subsection{\label{ss:fitssourceandsink}Source and Sink Routines for FitsChans}
+
+The use of source and sink routines with a \htmlref{FitsChan}{FitsChan} is optional. This
+is because you can always arrange to explicitly fill a FitsChan with
+FITS cards (\secref{ss:addingfitscards} and \secref{ss:addingmulticards})
+and you can also extract any
+cards that remain and write them out yourself
+(\secref{ss:extractingfitscards}) before you delete the FitsChan.
+
+If you choose to use these routines, however, they behave in a very
+similar manner to those used by a \htmlref{Channel}{Channel} (\secref{ss:channelsource}
+and \secref{ss:channelsink}). You supply these routines, as arguments
+to the constructor function \htmlref{AST\_FITSCHAN}{AST\_FITSCHAN} when you create the FitsChan
+(\secref{ss:creatingafitschan}). The source routine is invoked
+implicitly at this point to fill the FitsChan with FITS cards and the
+FitsChan is then rewound, so that the first card becomes current. The
+sink routine is automatically invoked later, when the FitsChan is
+deleted, in order to write out any cards that remain in it.
+
+
+The only real difference between the source and sink routines for a
+FitsChan and a basic Channel is that FITS cards are limited in length
+to 80~characters, so the choice of buffer size is simplified.  This
+affects the way the card contents are passed, so the routines
+themselves are slightly different.  The following is therefore the
+FitsChan equivalent of the Channel SOURCE routine given in
+\secref{ss:channelsource}:
+
+\small
+\begin{terminalv}
+      INTEGER FUNCTION FITSSOURCE( CARD, STATUS )
+      CHARACTER * ( 80 ) CARD
+      INTEGER STATUS
+
+      READ( 1, '(A)', END = 99 ) CARD
+      FITSSOURCE = 1
+      RETURN
+
+ 99   FITSSOURCE = 0
+      END
+\end{terminalv}
+\normalsize
+
+Here, the FITS card contents are returned \emph{via} the CARD argument
+(the \htmlref{AST\_PUTLINE}{AST\_PUTLINE} routine should not be used) and the function returns
+1 to indicate that a card has been read. A value of zero is returned
+if there are no more cards to read.
+
+The sink routine for a FitsChan is also a little different
+(\emph{c.f.}\ the SINK routine in~\secref{ss:channelsink}), as
+follows:
+
+\small
+\begin{terminalv}
+      SUBROUTINE FITSSINK( CARD, STATUS )
+      CHARACTER * ( 80 ) CARD
+      INTEGER STATUS
+
+      WRITE( 2, '(A)' ) CARD
+
+      END
+\end{terminalv}
+\normalsize
+
+The contents of the FITS card being written are passed \emph{via}\ the
+CARD argument (the \htmlref{AST\_GETLINE}{AST\_GETLINE} routine should not be used).
+
+Of course, both of these examples assume that you are accessing text
+files. If this is not the case, then appropriate changes to the I/O
+statements would be needed.  The details obviously depend on the
+format of the file you are handling, which need not necessarily be a
+true FITS file.
+
+\cleardoublepage
+\section{\label{ss:foreignfits}Using Foreign FITS Encodings}
+
+We saw in the previous section (\secref{ss:nativefits}) how to store
+and retrieve any kind of AST \htmlref{Object}{Object} in a FITS header by using a
+\htmlref{FitsChan}{FitsChan}. To achieve this, we set the FitsChan's \htmlref{Encoding}{Encoding} attribute to
+NATIVE. However, the Objects we wrote could then only be read back by
+other programs that use AST.
+
+In practice, we will also encounter FITS headers containing WCS
+information written by other software systems.  We will probably also
+need to write FITS headers in a format that can be understood by these
+systems. Indeed, this interchange of data is one of the main reasons
+for the existence of FITS, so in this section we will examine how to
+accommodate these requirements.
+
+\subsection{\label{ss:foreignencodings}The Foreign FITS Encodings}
+
+As mentioned previously (\secref{ss:nativeencoding}), there are a
+number of conventions currently in use for storing WCS information in
+FITS headers, which we call \emph{encodings}. Here, we are concerned
+with those encodings defined by software systems other than AST, which
+we term \emph{foreign encodings}.
+
+Currently, AST supports six foreign encodings, which may be selected
+by setting the \htmlref{Encoding}{Encoding} attribute of a \htmlref{FitsChan}{FitsChan} to one of the
+following (character string) values:
+
+\begin{quote}
+\begin{description}
+\item[DSS]\mbox{}\\
+This encoding stores WCS information using the convention developed at
+the Space Telescope Science Institute for the Digitised Sky Survey
+(DSS) astrometric plate calibrations.  DSS images which use this
+convention are widely available and it is understood by a number of
+important and well-established astronomy applications.
+
+However, the calibration model used (based on a polynomial fit) is not
+easily applicable to other types of data and creating the polynomial
+coefficients needed to calibrate your own images can prove
+difficult. For this reason, the DSS encoding is probably best viewed
+as a ``read-only'' format. It is possible, however, to read in WCS
+information using this encoding and then to write it back out again,
+so long as only minor changes have been made.
+
+\item[FITS-WCS]\mbox{}\\
+This encoding is very important because it is based on a new FITS standard
+which should, for the first time, address the problem of celestial coordinate
+systems in a proper manner, by considerably extending the original FITS
+standard.
+
+The conventions used are described in a series of papers by
+E.W.\,Greisen, M.\,Calabretta, \emph{et. al.}, often referred to as the
+``FITS-WCS papers''. They are described at
+\url{http://fits.gsfc.nasa.gov/fits_wcs.html}. Now that the first two papers
+in this series have been agreed, this encoding should be understood by any
+FITS-WCS compliant software and it is likely to be adopted widely for FITS
+data in future.  For details of the coverage of these conventions provided
+by the FitsChan class, see \appref{ss:fitswcscoverage}.
+
+\item[FITS-IRAF]\mbox{}\\
+This encoding is based on the conventions described in the document
+``World Coordinate Systems Representations Within the FITS Format'' by R.J.
+Hanisch and D.G. Wells, 1988.\footnote{Available by ftp from
+fits.cv.nrao.edu /fits/documents/wcs/wcs88.ps.Z} It is employed
+by the IRAF data analysis facility, so its use will facilitate data
+exchange with IRAF. This encoding is in effect a sub-set of the current
+FITS-WCS encoding.
+
+\item[FITS-PC]\mbox{}\\
+This encoding is based on a previous version of the proposed new FITS WCS
+standard which used \texttt{PCjjjjiii} and \texttt{CDELTj} keywords to describe
+axis rotation and scaling. Versions of AST prior to V1.5 used this scheme
+for the FITS-WCS encoding. As of V1.5, FITS-WCS uses \texttt{CDi\_j}
+keywords instead.\footnote{There are many other differences between the
+previous and the current FITS-WCS encodings. The keywords to describe
+axis rotation and scaling is used purely as a label to identify the
+scheme.} The FITS-PC encoding is included in AST V1.5 only to allow
+FITS-WCS data created with previous versions to be read. It should not,
+in general, be used to create new data sets.
+
+\item[FITS-AIPS]\mbox{}\\
+This encoding is based on the conventions described in the document
+``Non-linear Coordinate Systems in AIPS'' by Eric W. Greisen (revised 9th
+September, 1994).\footnote{Available by ftp from fits.cv.nrao.edu
+/fits/documents/wcs/aips27.ps.Z} It is currently employed by the AIPS
+data analysis facility, so its use will facilitate data exchange with
+AIPS. This encoding uses \texttt{CROTAi} and \texttt{CDELTi} keywords to
+describe axis rotation and scaling.
+
+\item[FITS-AIPS++]\mbox{}\\
+Encodes coordinate system information in FITS
+header cards using the conventions used by the AIPS++ project.
+This is an extension of FITS-AIPS which includes some of the
+features of FITS-PC and FITS-IRAF.
+\end{description}
+\end{quote}
+
+For more detail about the above encodings, see the description of the
+Encoding attribute in \appref{ss:attributedescriptions}.
+
+\subsection{\label{ss:foreignfitslimitations}Limitations of Foreign Encodings}
+
+The foreign encodings available for storing WCS information in FITS
+headers have a number of limitations when compared with the native
+encoding of AST Objects (\secref{ss:nativefits}). The main ones are:
+
+\begin{enumerate}
+\item Only one class of AST \htmlref{Object}{Object}, the \htmlref{FrameSet}{FrameSet}, may be represented
+using a foreign FITS encoding. This should not come as a surprise,
+because the purpose of storing WCS information in FITS headers is to
+attach coordinate systems to an associated array of data. Since the
+FrameSet is the AST Object designed for the same purpose
+(\secref{ss:baseandcurrent}), there is a natural correspondence.
+
+The way in which a FrameSet is translated to and from the foreign
+encoding also follows from this correspondence. The FrameSet's base
+\htmlref{Frame}{Frame} identifies the data grid coordinates of the associated FITS
+data. These are the same as FITS pixel coordinates, in which the first
+pixel (in 2 dimensions) has coordinates (1,1) at its
+centre. Similarly, the current Frame of the FrameSet identifies the
+FITS world coordinate system associated with the data.
+
+\item You may store a representation of only a single FrameSet in any
+individual set of FITS header cards (\emph{i.e.}\ in a single
+\htmlref{FitsChan}{FitsChan}) at one time. If you attempt to store more than one, you may
+over-write the previous one or generate an invalid representation of
+your WCS information.
+
+This is mainly a consequence of the use of fixed FITS keywords by
+foreign encodings and the fact that you cannot, in general, have
+multiple FITS cards with the same keyword.
+
+\item In general, it will not be possible to store every possible
+FrameSet that you might construct. Depending on the encoding, only
+certain FrameSets that conform to particular restrictions can be
+represented and, even then, some of their information may be lost. See
+the description of the \htmlref{Encoding}{Encoding} attribute in
+\appref{ss:attributedescriptions} for more details of these
+limitations.
+\end{enumerate}
+
+It should be understood that using foreign encodings to read and write
+information held in AST Objects is essentially a process of converting
+the data format. As such, it potentially suffers from the same
+problems faced by all such processes, \emph{i.e.}\ differences between
+the AST data model and that of the foreign encoding may cause some
+information to be lost.  Because the AST model is extremely flexible,
+however, any data loss can largely be eliminated when reading.
+Instead, this effect manifests itself in the form of the above
+encoding-dependent restrictions on the kind of AST Objects which may
+be written.
+
+One of the aims of the AST library, of course, is to insulate you from
+the details of these foreign encodings and the restrictions they
+impose. We will see shortly, therefore, how AST provides a mechanism
+for determining whether your WCS information satisfies the necessary
+conditions and allows you to make an automatic choice of which
+encoding to use.
+
+\subsection{\label{ss:identifyingfitsencoding}Identifying Foreign Encodings on Input}
+
+Let us now examine the practicalities of extracting WCS information
+from a set of FITS header cards which have been written by some other
+software system. We will pretend that our program does not know which
+encoding has been used for the WCS information and must discover this
+for itself. In order to have a concrete example, however, we will use
+the following set of cards. These use the FITS-AIPS encoding and
+contain a typical mix of other FITS cards which are irrelevant to the
+WCS information in which we are interested:
+
+\small
+\begin{terminalv}
+SIMPLE  =                    T / Written by IDL:  30-Jul-1997 05:35:42.00
+BITPIX  =                  -32 / Bits per pixel.
+NAXIS   =                    2 / Number of dimensions
+NAXIS1  =                  300 / Length of x axis.
+NAXIS2  =                  300 / Length of y axis.
+CTYPE1  = 'GLON-ZEA'           / X-axis type
+CTYPE2  = 'GLAT-ZEA'           / Y-axis type
+CRVAL1  =           -149.56866 / Reference pixel value
+CRVAL2  =           -19.758201 / Reference pixel value
+CRPIX1  =              150.500 / Reference pixel
+CRPIX2  =              150.500 / Reference pixel
+CDELT1  =             -1.20000 / Degrees/pixel
+CDELT2  =              1.20000 / Degrees/pixel
+CROTA1  =              0.00000 / Rotation in degrees.
+SURVEY  = 'COBE DIRBE'
+BUNITS  = 'MJy/sr  '           /
+ORIGIN  = 'CDAC    '           / Cosmology Data Analysis Center
+TELESCOP= 'COBE    '           / COsmic Background Explorer satellite
+INSTRUME= 'DIRBE   '           / COBE instrument [DIRBE, DMR, FIRAS]
+PIXRESOL=                    9 / Quad tree pixel resolution [6, 9]
+DATE    = '27/09/94'           / FITS file creation date (dd/mm/yy)
+DATE-MAP= '16/09/94'           / Date of original file creation (dd/mm/yy)
+COMMENT     COBE specific keywords
+DATE-BEG= '08/12/89'           / date of initial data represented (dd/mm/yy)
+DATE-END= '25/09/90'           / date of final data represented   (dd/mm/yy)
+\end{terminalv}
+\normalsize
+
+The first step is to create a \htmlref{FitsChan}{FitsChan} and insert these cards into
+it. If CARDS is an array of character strings holding the header cards
+and NCARDS is the number of cards, this could be done as follows:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER FITSCHAN, ICARD, NCARD, STATUS
+      CHARACTER * ( 80 ) CARDS( NCARD )
+
+      STATUS = 0
+
+      ...
+
+      FITSCHAN = AST_FITSCHAN( AST_NULL, AST_NULL, ' ', STATUS )
+      DO 1 ICARD = 1, NCARD
+         CALL AST_PUTFITS( FITSCHAN, CARDS( ICARD ), .FALSE., STATUS )
+ 1    CONTINUE
+\end{terminalv}
+\normalsize
+
+Note that we have not initialised the \htmlref{Encoding}{Encoding} attribute of the
+FitsChan as we did in \secref{ss:creatingafitschan} when we wanted to
+use the native encoding. This is because we are pretending not to know
+which encoding to use and want AST to determine this for us. By
+leaving the Encoding attribute un-set, its default value will adjust
+to whichever encoding AST considers to be most appropriate, according
+to the FITS header cards present. For details of how this choice is
+made, see the description of the Encoding attribute in
+\appref{ss:attributedescriptions}.
+
+This approach has the obvious advantages of making our program simpler
+and more flexible and of freeing us from having to know about the
+different encodings available. As a bonus, it also means that the
+program will be able to read any new encodings that AST may support in
+future, without needing to be changed.
+
+At this point, we could enquire the default value of the Encoding
+attribute, which indicates which encoding AST intends to use, as
+follows:
+
+\small
+\begin{terminalv}
+      CHARACTER * ( 20 ) ENCODE
+
+      ...
+
+      ENCODE = AST_GETC( FITSCHAN, 'Encoding', STATUS )
+\end{terminalv}
+\normalsize
+
+The result of this enquiry would be the string ``FITS-AIPS''.  Note
+that we could also have set the FitsChan's Encoding attribute
+explicitly, such as when creating it:
+
+\small
+\begin{terminalv}
+      FITSCHAN = AST_FITSCHAN( AST_NULL, AST_NULL, 'Encoding=FITS-AIPS', STATUS )
+\end{terminalv}
+\normalsize
+
+If we tried to read information using this encoding
+(\secref{ss:readingforeignfits}), but failed, we could then change the
+encoding and try again.  This would allow our program to take control
+of how the optimum choice of encoding is arrived at. However, it would
+also involve using explicit knowledge of the encodings available and
+this is best avoided if possible.
+
+\subsection{\label{ss:readingforeignfits}Reading Foreign WCS Information from a FITS Header}
+
+Having stored a set of FITS header cards in a \htmlref{FitsChan}{FitsChan} and determined
+how the WCS information is encoded
+(\secref{ss:identifyingfitsencoding}), the next step is to read an AST
+\htmlref{Object}{Object} from the FitsChan using \htmlref{AST\_READ}{AST\_READ}. We must also remember to
+rewind the FitsChan first, if necessary, such as by clearing its \htmlref{Card}{Card}
+attribute, which defaults to 1:
+
+\small
+\begin{terminalv}
+      INTEGER WCSINFO
+
+      ...
+
+      CALL AST_CLEAR( FITSCHAN, 'Card', STATUS )
+      WCSINFO = AST_READ( FITSCHAN, STATUS )
+\end{terminalv}
+\normalsize
+
+If the pointer returned by AST\_READ is not equal to AST\_\_NULL, then
+an Object has been read successfully. Otherwise, there was either no
+information to read or the choice of FITS encoding
+(\secref{ss:identifyingfitsencoding}) was inappropriate.
+
+At this point you might like to indulge in a little data validation
+along the lines described in \secref{ss:validatinginput}, for example:
+
+\small
+\begin{terminalv}
+      IF ( AST_GETC( WCSINFO, 'Class', STATUS ) .EQ. 'FrameSet' ) THEN
+         <the Object is a FrameSet, so use it>
+      ELSE
+         <something unexpected was read>
+      END IF
+\end{terminalv}
+\normalsize
+
+If a foreign encoding has definitely been used, then the Object will
+automatically be a \htmlref{FrameSet}{FrameSet} (\secref{ss:foreignfitslimitations}), so
+this stage can be omitted. However, if the native encoding
+(\secref{ss:nativeencoding}) might have been employed, which is a
+possibility if you accept the FitsChan's default \htmlref{Encoding}{Encoding} value, then
+any class of Object might have been read and a quick check would be
+worthwhile.
+
+If you used \htmlref{AST\_SHOW}{AST\_SHOW} (\secref{ss:displayingobjects}) to examine the
+FrameSet which results from reading our example FITS header
+(\secref{ss:identifyingfitsencoding}), you would find that its base
+\htmlref{Frame}{Frame} describes the image's pixel coordinate system and that its
+current Frame is a \htmlref{SkyFrame}{SkyFrame} representing galactic coordinates. These
+two Frames are inter-related by a \htmlref{Mapping}{Mapping} (actually a \htmlref{CmpMap}{CmpMap}) which
+incorporates the effects of various rotations, scalings and a
+``zenithal equal area'' sky projection, so that each pixel of the FITS
+image is mapped on to a corresponding sky position in galactic
+coordinates.
+
+Because this FrameSet may be used both as a Mapping
+(\secref{ss:framesetasmapping}) and as a Frame
+(\secref{ss:framesetasframe}), it may be employed directly to perform
+many useful operations without any need to decompose it into its
+component parts. These include:
+
+\begin{itemize}
+\item Transforming data grid (FITS pixel) coordinates into galactic
+coordinates and \emph{vice versa} (\secref{ss:framesetasmapping}).
+
+\item Formatting coordinate values (either pixel or galactic
+coordinates) ready for display to a user
+(\secref{ss:formattingaxisvalues} and \secref{ss:normalising}).
+
+\item Enquiring about axis labels (or other axis
+information---\secref{ss:frameattributes}) which might be used, for
+example, to label columns of coordinates in a table
+(\secref{ss:frameaxisattributes}).
+
+\item Aligning the image with another image from which a similar
+FrameSet has been obtained (\secref{ss:registeringimages}).
+
+\item Creating a \htmlref{Plot}{Plot} (\secref{ss:plots}), which can be used to overlay
+a variety of graphical information (including a coordinate
+grid---Figure~\ref{fig:gridplot}) on the displayed image.
+
+\item Generating a new FrameSet which reflects any geometrical
+processing you perform on the associated image data
+(\secref{ss:wcsprocessingexample}). This new FrameSet could then be
+written out as FITS headers to describe the modified image
+(\secref{ss:writingforeignfits}).
+\end{itemize}
+
+If the FrameSet contains other Frames (apart from the base and current
+Frames), then you would also have access to information about other
+coordinate systems associated with the image.
+
+\subsection{\label{ss:destructiveread}Removing WCS Information from FITS Headers---the Destructive Read}
+
+It is instructive at this point to examine the contents of a \htmlref{FitsChan}{FitsChan}
+after we have read a \htmlref{FrameSet}{FrameSet} from it
+(\secref{ss:readingforeignfits}). The following would rewind our
+FitsChan and display its contents:
+
+\small
+\begin{terminalv}
+      CHARACTER CARD * ( 80 )
+
+      ...
+
+      CALL AST_CLEAR( FITSCHAN, 'Card', STATUS )
+ 2    CONTINUE
+      IF ( AST_FINDFITS( FITSCHAN, '%f', CARD, .TRUE., STATUS ) ) THEN
+         WRITE ( *, '(A)' ) CARD
+         GO TO 2
+      END IF
+\end{terminalv}
+\normalsize
+
+The output, if we started with the example FITS header in
+\secref{ss:identifyingfitsencoding}, might look like this:
+
+\small
+\begin{terminalv}
+SIMPLE  =                    T /  Written by IDL:  30-Jul-1997 05:35:42.00
+BITPIX  =                  -32 /  Bits per pixel.
+NAXIS   =                    2 /  Number of dimensions
+NAXIS1  =                  300 /  Length of x axis.
+NAXIS2  =                  300 /  Length of y axis.
+SURVEY  = 'COBE DIRBE'
+BUNITS  = 'MJy/sr  '
+ORIGIN  = 'CDAC    '           /  Cosmology Data Analysis Center
+TELESCOP= 'COBE    '           /  COsmic Background Explorer satellite
+INSTRUME= 'DIRBE   '           /  COBE instrument [DIRBE, DMR, FIRAS]
+PIXRESOL=                    9 /  Quad tree pixel resolution [6, 9]
+DATE    = '27/09/94'           /  FITS file creation date (dd/mm/yy)
+DATE-MAP= '16/09/94'           /  Date of original file creation (dd/mm/yy)
+COMMENT     COBE specific keywords
+DATE-BEG= '08/12/89'           /  date of initial data represented (dd/mm/yy)
+DATE-END= '25/09/90'           /  date of final data represented   (dd/mm/yy)
+\end{terminalv}
+\normalsize
+
+Comparing this with the original, you can see that all the FITS cards
+that represent WCS information have been removed. They have
+effectively been ``sucked out'' of the FitsChan by the destructive
+read that \htmlref{AST\_READ}{AST\_READ} performs and converted into an equivalent
+FrameSet. AST remembers where they were stored, however, so that if we
+later write WCS information back into the FitsChan
+(\secref{ss:writingforeignfits}) they will, as far as possible, go
+back into their original locations.  This helps to preserve the
+overall layout of the FITS header.
+
+You can now see why AST\_READ performs destructive reads. It is a
+mechanism for removing WCS information from a FITS header while
+insulating you, as a programmer, from the details of the encoding
+being used. It means you can ensure that all relevant header cards
+have been removed, giving you a clean slate, without having to know
+which FITS keywords any particular encoding uses.
+
+Clearing this WCS information out of a FITS header is particularly
+important when considering how to write new WCS information back after
+processing (\secref{ss:writingforeignfits}). If any relevant FITS
+cards are left over from the input dataset and find their way into the
+new processed header, they could interfere with the new information
+being written.\footnote{This can happen if a particular keyword is
+present in the input header but is not used in the output header
+(whether particular keywords are used can depend on the WCS
+information being stored). In such a case, the original value would
+not be over-written by a new output value, so would remain erroneously
+present.} The destructive read mechanism ensures that this doesn't
+happen.
+
+\subsection{\label{ss:propagatingwcsinformation}Propagating WCS Information through Data Processing Steps}
+
+One of the purposes of AST is to make it feasible to propagate WCS
+information through successive stages of data processing, so that it
+remains consistent with the associated image data. As far as possible,
+this should happen regardless of the FITS encoding used to store the
+original WCS information.
+
+If the data processing being performed does not change the
+relationship between image pixel and world coordinates (whatever these
+may be), then propagation of the WCS information is
+straightforward. You can simply copy the FITS header from input to
+output.
+
+If this relationship changes, however, then the WCS information must
+be processed alongside the image data and a new FITS header generated
+to represent it. In this case, the sequence of operations within your
+program would probably be as follows:
+
+\begin{enumerate}
+\item Read the image data and associated FITS header from the input
+dataset, putting the header cards into a \htmlref{FitsChan}{FitsChan}
+(\secref{ss:identifyingfitsencoding}).
+
+\item Read an AST \htmlref{Object}{Object}, a \htmlref{FrameSet}{FrameSet}, from the FitsChan (typically
+using a foreign FITS encoding---\secref{ss:readingforeignfits}).
+
+\item Process the image data and modify the FrameSet accordingly
+(\emph{e.g.}~\secref{ss:wcsprocessingexample}).
+
+\item Write the FrameSet back into the FitsChan
+(\secref{ss:writingforeignfits}).
+
+\item Perform any other modification of FITS header cards your program
+may require.
+
+\item Write the FitsChan contents (\emph{i.e.}\ processed header
+cards) and image data to the output dataset.
+\end{enumerate}
+
+In stage (2), the original WCS information will be removed from the
+FitsChan by a destructive read. Later, in stage (4), new WCS
+information is written to replace it. This is the process which we
+consider next (\secref{ss:writingforeignfits}).
+
+\subsection{\label{ss:writingforeignfits}Writing Foreign WCS Information to a FITS Header}
+
+Before we can write processed WCS information held in a \htmlref{FrameSet}{FrameSet} back
+into a \htmlref{FitsChan}{FitsChan} in preparation for output, we must select the FITS
+encoding to use.  Unfortunately, we cannot simply depend on the
+default value of the \htmlref{Encoding}{Encoding} attribute, as we did when reading the
+input information (\secref{ss:identifyingfitsencoding}), because the
+destructive action of reading the WCS data
+(\secref{ss:destructiveread}) will have altered the FitsChan's
+contents. This, in turn, will have changed the choice of default
+encoding, probably causing it to revert to NATIVE.
+
+We will return to the question of the optimum choice of encoding
+below.  For now, let's assume we want to use the same encoding for
+output as we used for input. Since we enquired what that was before we
+read the input WCS data from the FitsChan
+(\secref{ss:identifyingfitsencoding}), we can now set that value
+explicitly. We can also set the FitsChan's \htmlref{Card}{Card} attribute back to 1 at
+the same time (because the write will fail if the FitsChan is not
+rewound). \htmlref{AST\_WRITE}{AST\_WRITE} can then be used to write the output WCS
+information into the FitsChan:
+
+\small
+\begin{terminalv}
+      INTEGER NOBJ
+
+      ...
+
+
+      CALL AST_SET( FITSCHAN, 'Card=1, Encoding=' // ENCODE, STATUS )
+      NOBJ = AST_WRITE( FITSCHAN, WCSINFO, STATUS )
+\end{terminalv}
+\normalsize
+
+The value returned by AST\_WRITE (assigned to NOBJ) indicates how many
+Objects were written. This will either be 1 or zero. A value of zero
+is used to indicate that the information could not be encoded in the
+form you requested. If this happens, nothing will have been written.
+
+If your choice of encoding proves inadequate, the probable reason is
+that the changes you have made to the FrameSet have caused it to
+depart from the data model which the encoding assumes.  AST knows
+about the data model used by each encoding and will attempt to
+simplify the FrameSet you provide so as to fit into that model, thus
+relieving you of the need to understand the details and limitations of
+each encoding yourself.\footnote{Storing values in the FitsChan for
+FITS headers NAXIS1, NAXIS2, \emph{etc.} (the grid dimensions in pixels),
+before invoking
+AST\_WRITE
+can sometimes help to produce a successful write.} When this attempt fails,
+however, you must consider what alternative encoding to use.
+
+Ideally, you would probably want to try a sequence of alternative
+encodings, using an approach such as the following:
+
+\small
+\begin{terminalv}
+*  1.
+      CALL AST_SET( FITSCHAN, 'Card=1, Encoding=FITS-WCS', STATUS )
+      IF ( AST_WRITE( FITSCHAN, WCSINFO, STATUS ) .EQ. 0 ) THEN
+
+*  2.
+         CALL AST_SETC( FITSCHAN, 'Encoding', ENCODE, STATUS )
+         IF ( AST_WRITE( FITSCHAN, WCSINFO, STATUS ) .EQ. 0 ) THEN
+
+*  3.
+            CALL AST_SET( FITSCHAN, 'Encoding=NATIVE', STATUS )
+            NOBJ = AST_WRITE( FITSCHAN, WCSINFO, STATUS )
+         END IF
+      END IF
+\end{terminalv}
+\normalsize
+
+That is:
+
+\begin{enumerate}
+\item Start by trying the FITS-WCS encoding, on the grounds that FITS
+should provide a universal interchange standard in which all WCS
+information should be expressed if possible.
+
+\item If that fails, then try the original encoding used for the input
+WCS information, on the grounds that you are at least not making the
+information any harder for others to read than it originally was.
+
+\item If that also fails, then you are probably trying to store fairly
+complex information for which you need the native encoding. Only other
+AST programs will then be able to read this information, but these are
+probably the only programs that will be able to do anything sensible
+with it anyway.
+\end{enumerate}
+
+An alternative approach might be to encode the WCS information in several
+ways, since this gives the maximum chance that other software will be
+able to read it. This approach is only possible if there is no
+significant conflict between the FITS keywords used by the different
+encodings\footnote{In practice, this means you should avoid mixing
+FITS-IRAF, FITS-WCS, FITS-AIPS, FITS-AIPS++ and FITS-PC encodings since they share
+many keywords.}.  Adopting this approach would simply require multiple
+calls to AST\_WRITE, rewinding the FitsChan and changing its Encoding value
+before each one.
+
+Unfortunately, however, there is a drawback to duplicating WCS
+information in the FITS header in this way, because any program which
+modifies one version of this information and simply copies the
+remainder of the header will risk producing two inconsistent sets of
+information. This could obviously be confusing to subsequent
+software. Whether you consider this a worthwhile risk probably depends
+on the use to which you expect your data to be put.
+
+\cleardoublepage
+\section{\label{ss:xmlchan}Storing AST Objects as XML (XmlChan)}
+
+\htmladdnormallinkfoot{XML}{http://www.w3.org/XML/}
+is fast becoming the standard format for passing structured data around
+the internet, and much general purpose software has been written for
+tasks such as the parsing, editing, display and transformation of XML
+data. The \htmlref{XmlChan}{XmlChan} class (a specialised form of \htmlref{Channel}{Channel}) provides
+facilities for storing AST objects externally in the form of XML documents,
+thus allowing such software to be used.
+
+The primary XML format used by the XmlChan class is a fairly close
+transliteration of the AST native format produced by the basic Channel
+class. Currently, there is no DTD or schema defining the structure of data
+produced in this format by an XmlChan. The following is a native AST
+representation of a simple 1-D \htmlref{Frame}{Frame} (including comments and with the \htmlref{Full}{Full}
+attribute set to zero so that some default attribute values are included
+as extra comments):
+
+\small
+\begin{terminalv}
+ Begin Frame    # Coordinate system description
+#   Title = "1-d coordinate system"     # Title of coordinate system
+    Naxes = 1   # Number of coordinate axes
+    Domain = "SCREEN"   # Coordinate system domain
+#   Lbl1 = "Axis 1"     # Label for axis 1
+#   Uni1 = "cm"         # Units for axis 1
+    Ax1 =       # Axis number 1
+       Begin Axis       # Coordinate axis
+          Unit = "cm"   # Axis units
+       End Axis
+ End Frame
+\end{terminalv}
+\normalsize
+
+The corresponding XmlChan output would look like:
+
+\small
+\begin{terminalv}
+ <Frame xmlns="http://www.starlink.ac.uk/ast/xml/"
+        desc="Coordinate system description">
+    <_attribute name="Title" quoted="true" value="1-d coordinate system"
+                desc="Title of coordinate system" default="true"/>
+    <_attribute name="Naxes" value="1" desc="Number of coordinate axes"/>
+    <_attribute name="Domain" quoted="true" value="SCREEN"
+                desc="Coordinate system domain"/>
+    <_attribute name="Lbl1" quoted="true" value="Axis 1"
+                desc="Label for axis 1" default="true"/>
+    <_attribute name="Uni1" quoted="true" value="cm"
+                desc="Units for axis 1" default="true"/>
+    <Axis label="Ax1" desc="Coordinate axis">
+       <!--Axis number 1-->
+       <_attribute name="Unit" quoted="true" value="cm" desc="Axis units"/>
+    </Axis>
+ </Frame>
+\end{terminalv}
+\normalsize
+
+
+Notes:
+
+\begin{enumerate}
+\item The AST class name is used as the name for an XML element which contain
+a description of an AST object.
+
+\item AST attributes are described by XML elements with the name
+``\_attribute''. Unfortunately, the word ``attribute'' is also used by XML
+to refer to a ``name=value'' pair within an element start tag. So for
+instance, the ``\htmlref{Title}{Title}'' attribute of the AST Frame object is described
+within an XML element with name ``\_attribute'' in which the XML attribute
+``name'' has the value ``Title'', and the XML attribute ``value'' has the
+value ``1-d coordinate system''. The moral is always to be clear clear
+about the context (AST or XML) in which the word \emph{attribute} is being
+used!
+
+\item The XML includes comments both as XML attributes with the name ``desc'',
+and as separate comment tags.
+
+\item Elements which describe default values are identified by the fact
+that they have an XML attribute called ``default'' set to the value
+``true''. These elements are ignored when being read back into an XmlChan.
+
+\item The outer-most XML element of an AST object will set the default
+namespace to \verb+http://www.starlink.ac.uk/ast/xml/+ which will be
+inherited by all nested elements.
+
+\end{enumerate}
+
+
+The XmlChan class changes the default value for the \htmlref{Comment}{Comment} and Full
+attributes (inherited from the base Channel class) to zero and -1,
+resulting in terse output by default. With the default values for these
+attributes, the above XML is reduced to the following:
+
+\small
+\begin{terminalv}
+ <Frame xmlns="http://www.starlink.ac.uk/ast/xml/">
+    <_attribute name="Naxes" value="1"/>
+    <_attribute name="Domain" quoted="true" value="SCREEN"/>
+    <Axis label="Ax1">
+       <_attribute name="Unit" quoted="true" value="cm"/>
+    </Axis>
+ </Frame>
+\end{terminalv}
+\normalsize
+
+
+The XmlChan class uses the \htmlref{Skip}{Skip} attributes very similarly to the Channel
+class. If Skip is zero (the default) then an error will be reported if the text
+supplied by the source function does not begin with an AST \htmlref{Object}{Object}. If
+Skip is non-zero, then initial text is skipped over without error until
+the start of an AST object is found. this allows an AST object to be
+located within a larger XML document.
+
+\subsection{Reading IVOA Space-Time-Coordinates XML (STC-X) Descriptions}
+The \htmlref{XmlChan}{XmlChan} class also provides support for reading (but not writing) XML
+documents which use a restricted subset of an early draft (V1.20) of the
+IVOA Space-Time-Coordinates XML (STC-X) system. The version of STC-X
+finally adopted by the IVOA differs in several significant respects from
+V1.20, and so the STC-X support currently provided by AST is mainly of
+historical interest. Note, AST also supports the alternative ``STC-S''
+linear string description of the STC model (see \secref{ss:stcschans}).
+
+STC-X V1.20 is documented at
+\url{http://www.ivoa.net/Documents/WD/STC/STC-20050225.html}, and the current
+version is documented at
+\url{http://www.ivoa.net/Documents/latest/STC-X.html}.
+
+When an STC-X document is read using an XmlChan, the read operation
+produces an AST \htmlref{Object}{Object} of the \htmlref{Stc}{Stc} class, which is itself a subclass of
+\htmlref{Region}{Region}. Specifically, each such Object will be an instance of
+\htmlref{StcSearchLocation}{StcSearchLocation}, \htmlref{StcResourceProfile}{StcResourceProfile}, \htmlref{StcCatalogEntryLocation}{StcCatalogEntryLocation} or
+\htmlref{StcObsDataLocation}{StcObsDataLocation}. See the description of the XmlChan class and the
+\htmlref{XmlFormat}{XmlFormat} attribute for further details.
+
+\cleardoublepage
+\section{\label{ss:stcschans}Reading and writing STC-S descriptions (StcsChans)}
+
+The \htmlref{StcsChan}{StcsChan} class provides facilities for reading and writing
+IVOA ``STC-S'' descriptions. STC-S (see
+\url{http://www.ivoa.net/Documents/latest/STC-S.html}) is a linear string
+syntax that allows simple specification of the STC metadata describing a
+region in an astronomical coordinate system. AST supports a
+subset of the STC-S specification, allowing an STC-S description of a
+region within an AST-supported astronomical coordinate system to be converted
+into an equivalent AST \htmlref{Region}{Region} object, and vice-versa. For further
+details, see the full description of the StcsChan class in
+\appref{ss:classdescriptions}.
+
+
+\cleardoublepage
+\section{\label{ss:intramaps}Creating Your Own Private Mappings (IntraMaps)}
+
+\subsection{The Need for Extensibility}
+
+However many \htmlref{Mapping}{Mapping} classes are provided by AST, sooner or later you
+will want to transform coordinates in some way that has not been
+foreseen. You might want to plot a graph in some novel curvilinear
+coordinate system (perhaps you already have a WCS system in your
+software and just want to use AST for its graphical capabilities).
+Alternatively, you might need to calibrate a complex dataset (like an
+objective prism plate) where each position must be converted to world
+coordinates with reference to calibration data under the control of an
+elaborate algorithm.
+
+In such cases, it is clear that the basic pre-formed components
+provided by AST for building Mappings are just not enough. What you
+need is access to a programming language. However, if you write your
+own software to transform coordinate values, then it must be made
+available in the form of an AST class (from which you can create
+Objects) before it can be used in conjunction with other AST
+facilities.
+
+At this point you might consider writing your own AST class, but this
+is not recommended. Not only would the internal conventions used by
+AST take some time to master, but you might also find yourself having
+to change your software whenever a new version of AST was
+released. Fortunately, there is a much easier route provided by the
+\htmlref{IntraMap}{IntraMap} class.
+
+\subsection{The IntraMap Model}
+
+To allow you to write your own Mappings, AST provides a special kind
+of \htmlref{Mapping}{Mapping} called an \htmlref{IntraMap}{IntraMap}. An IntraMap is a sort of ``wrapper''
+for a coordinate transformation routine written in Fortran. You write
+this routine yourself and then register it with AST. This, in effect,
+creates a new class from which you can create Mappings
+(\emph{i.e.}\ IntraMaps) which will transform coordinates in whatever
+way your transformation routine specifies.
+
+Because IntraMaps are Mappings, they may be used in the same way as
+any other Mapping. For instance, they may be combined in series or
+parallel with other Mappings using a \htmlref{CmpMap}{CmpMap} (\secref{ss:cmpmaps}),
+they may be inverted (\secref{ss:invertingmappings}), you may enquire
+about their attributes (\secref{ss:gettingattributes}), they may be
+inserted into FrameSets (\secref{ss:framesets}), \emph{etc.} They do,
+however, have some important limitations of which you should be aware
+before we go on to consider how to create them.
+
+\subsection{\label{ss:intramaplimitations}Limitations of IntraMaps}
+
+By now, you might be wondering why any other kind of \htmlref{Mapping}{Mapping} is
+required at all. After all, why not simply write your own coordinate
+transformation routines in Fortran, wrap them up in IntraMaps and do
+away with all the other Mapping classes in AST?
+
+The reason is not too hard to find. Any transformation routine you
+write is created solely by you, so it is a private extension which
+does not form a permanent part of AST. If you use it to calibrate some
+data and then pass that data to someone else, who has only the
+standard version of AST, then they will not be able to interpret it.
+
+Thus, while an \htmlref{IntraMap}{IntraMap} is fine for use by you and your collaborators
+(who we assume have access to the same transformation routines), it
+does not address the need for universal data exchange like other AST
+Mappings do. This is where the ``Intra'' in the class name
+``IntraMap'' comes from, implying private or internal usage.
+
+For this reason, it is unwise to store IntraMaps in datasets, unless
+they will be used solely for communication between collaborating items
+of software which share conventions about their use.  A private
+database describing coordinate systems on a graphics device might be
+an example where IntraMaps would be suitable, because the data would
+probably never be accessed by anyone else's software. Restricting
+IntraMap usage to within a single program (\emph{i.e.} never writing
+it out) is, of course, completely safe.
+
+If, by accident, an IntraMap should happen to escape as part of a
+dataset, then the unsuspecting recipient is likely to receive an error
+message when they attempt to read the data. However, AST will
+associate details of the IntraMap's transformation routine and its
+author (if provided) with the data, so that the recipient can make an
+intelligent enquiry to obtain the necessary software if this proves
+essential.
+
+\subsection{\label{ss:transformationfunctions}Writing a Transformation Routine}
+
+The first stage in creating an \htmlref{IntraMap}{IntraMap} is to write the coordinate
+transformation routine. This should have a calling interface like the
+\htmlref{AST\_TRANN}{AST\_TRANN} function provided by AST (\emph{q.v.}). Here is a simple
+example of a suitable transformation routine which transforms
+coordinates by squaring them:
+\xlabel{SqrTran}
+
+\small
+\begin{terminalv}
+      SUBROUTINE SQRTRAN( THIS, NPOINT, NCOORD_IN, INDIM, IN, FORWARD,
+    :                     NCOORD_OUT, OUTDIM, OUT, STATUS )
+      INTEGER THIS, NPOINT, NCOORD_IN, INDIM, NCOORD_OUT, OUTDIM, STATUS
+      DOUBLE PRECISION IN( INDIM, NCOORD_IN ), OUT( OUTDIM, NCOORD_OUT )
+      LOGICAL FORWARD
+
+      INCLUDE 'AST_PAR'
+      DOUBLE PRECISION X
+      INTEGER COORD, POINT
+
+*  Forward transformation.
+      IF ( FORWARD ) THEN
+         DO 2 POINT = 1, NPOINT
+            DO 1 COORD = 1, NCOORD_IN
+               X = IN( POINT, COORD )
+               IF ( X .EQ. AST__BAD ) THEN
+                  OUT( POINT, COORD ) = AST__BAD
+               ELSE
+                  OUT( POINT, COORD ) = X * X
+               ENDIF
+ 1          CONTINUE
+ 2       CONTINUE
+
+*  Inverse transformation.
+      ELSE
+         DO 4 POINT = 1, NPOINT
+            DO 3 COORD = 1, NCOORD_IN
+               X = IN( POINT, COORD )
+               IF ( X .LT. 0.0D0 .OR. X .EQ. AST__BAD ) THEN
+                  OUT( POINT, COORD ) = AST__BAD
+               ELSE
+                  OUT( POINT, COORD ) = SQRT( X )
+               ENDIF
+ 3          CONTINUE
+ 4       CONTINUE
+      ENDIF
+      END
+\end{terminalv}
+\normalsize
+
+As you can see, the routine comes in two halves which implement the
+forward and inverse coordinate transformations. The number of points
+to be transformed (NPOINT) and the numbers of input and output
+coordinates per point (NCOORD\_IN and NCOORD\_OUT---in this case both
+are assumed equal) are passed to the routine. A pair of loops then
+accesses all the coordinate values.  Note that it is legitimate to
+omit one or other of the forward/inverse transformations and simply
+not to implement it, if it will not be required. It is also
+permissible to require that the numbers of input and output
+coordinates be fixed (\emph{e.g.}\ at 2), or to write the routine so
+that it can handle arbitrary dimensionality, as here.
+
+Before using an incoming coordinate, the routine must first check that
+it is not set to the value AST\_\_BAD, which indicates missing data
+(\secref{ss:badcoordinates}). If it is, the same value is also
+assigned to any affected output coordinates. The value AST\_\_BAD is
+also generated if any coordinates cannot be transformed. In this
+example, this can happen with the inverse transformation if negative
+values are encountered, so that the square root cannot be taken.
+
+There are very few restrictions on what a coordinate transformation
+routine may do. For example, it may freely perform I/O to access any
+external data needed, it may invoke other AST facilities (but beware
+of unwanted recursion), \emph{etc.} Typically, you may also want to
+pass information to it \emph{via}\ global variables held in common
+blocks.  Remember, however, that whatever facilities the
+transformation routine requires must be available in every program
+which uses it.
+
+Generally, it is not a good idea to retain context information within
+a transformation routine. That is, it should transform each set of
+coordinates as a single point and retain no memory of the points it
+has transformed before. This is in order to conform with the AST model
+of a \htmlref{Mapping}{Mapping}.
+
+If an error occurs within a transformation routine, it should set its
+STATUS argument to an error value before returning. This will alert
+AST to the error, causing it to abort the current operation. The error
+value AST\_\_ITFER is available for this purpose, but other values may
+also be used (\emph{e.g.}\ if you wish to distinguish different types
+of error). The AST\_\_ITFER error value is defined in the AST\_ERR
+include file.
+
+\subsection{\label{ss:registeringintramaps}Registering a Transformation Routine}
+
+Having written your coordinate transformation routine, the next step
+is to register it with AST. Registration is performed using
+\htmlref{AST\_INTRAREG}{AST\_INTRAREG}, as follows:
+
+\small
+\begin{terminalv}
+      EXTERNAL SQRTRAN
+
+      CHARACTER * ( 80 ) AUTHOR, CONTACT, PURPOSE
+
+      ...
+
+      PURPOSE = 'Square each coordinate value'
+      AUTHOR  = 'R.F. Warren-Smith & D.S. Berry'
+      CONTACT = 'http://www.starlink.ac.uk/cgi-bin/htxserver/' //
+                'sun210.htx/?xref_SqrTran'
+
+      CALL AST_INTRAREG( 'SqrTran', 2, 2, SQRTRAN, 0,
+     :                   PURPOSE, AUTHOR, CONTACT, STATUS )
+\end{terminalv}
+\normalsize
+
+Note that the transformation routine must also appear in a Fortran
+EXTERNAL statement.
+
+The first argument to AST\_INTRAREG is a name by which the
+transformation routine will be known. This will be used when we come
+to create an \htmlref{IntraMap}{IntraMap} and is case sensitive. We recommend that you
+base this on the actual routine name and make this sufficiently
+unusual that it is unlikely to clash with any other routines in most
+people's software.
+
+The next two arguments specify the number of input and output
+coordinates which the transformation routine will handle. These
+correspond with the \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes of the IntraMap we will
+create. Here, we have set them both to 2, which means that we will
+only be able to create IntraMaps with 2 input and 2 output coordinates
+(despite the fact that the transformation routine can actually handle
+other dimensionalities). We will see later
+(\secref{ss:variableintramapcoordinates}) how to remove this
+restriction.
+
+The fourth argument should contain a set of flags which describe the
+transformation routine in a little more detail. We will return to this
+shortly (\secref{ss:restrictedintramaps} \&
+\secref{ss:simplifyingintramaps}). For now, we supply a value of zero.
+
+The remaining arguments are character strings which document the
+transformation routine, mainly for the benefit of anyone who is
+unfortunate enough to encounter a reference to it in their data which
+they cannot interpret. As explained above
+(\secref{ss:intramaplimitations}), you should try and avoid this, but
+accidents will happen, so you should always provide strings containing
+the following:
+
+\begin{enumerate}
+\item A short description of what the transformation routine is for.
+\item The name of the author.
+\item Contact details, such as an e-mail or WWW address.
+\end{enumerate}
+
+The idea is that anyone finding an IntraMap in their data, but lacking
+the necessary transformation routine, should be able to contact the
+author and make a sensible enquiry in order to obtain it. If you
+expect many enquiries, you may like to set up a World Wide Web page
+and use that instead (in the example above, we use the WWW address of
+the relevant part of this document).
+
+\subsection{Creating an IntraMap}
+
+Once a transformation routine been registered, creating an \htmlref{IntraMap}{IntraMap}
+from it is simple:
+
+\small
+\begin{terminalv}
+      INTEGER INTRAMAP
+
+      ...
+
+      INTRAMAP = AST_INTRAMAP( 'SqrTran', 2, 2, ' ', STATUS );
+\end{terminalv}
+\normalsize
+
+We simply use the \htmlref{AST\_INTRAMAP}{AST\_INTRAMAP} constructor function and pass it the
+name of the transformation routine to use. This name is the same (case
+sensitive) one that we associated with the routine when we registered
+it using \htmlref{AST\_INTRAREG}{AST\_INTRAREG} (\secref{ss:registeringintramaps}).
+
+You can, of course, register any number of transformation routines and
+select which one to use whenever you create an IntraMap. You can also
+create any number of independent IntraMaps using each transformation
+routine. In this sense, each transformation routine you register
+effectively creates a new ``sub-class'' of IntraMap, from which you
+can create Objects just like any other class. However, an error will
+occur if you attempt to use a transformation routine that has not yet
+been registered.
+
+The second and third arguments to AST\_INTRAMAP are the numbers of
+input and output coordinates. These define the \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes
+for the IntraMap that is created and they must match the corresponding
+numbers given when the transformation routine was registered.
+
+The penultimate argument is the usual attribute initialisation
+string. You may set attribute values for an IntraMap in exactly the
+same way as for any other \htmlref{Mapping}{Mapping} (\secref{ss:settingattributes}, and
+also see \secref{ss:intraflag}).
+
+\subsection{\label{ss:restrictedintramaps}Restricted Implementations of Transformation Routines}
+
+You may not always want to use both the forward and inverse
+transformations when you create an \htmlref{IntraMap}{IntraMap}, so it is possible to omit
+either from the underlying coordinate transformation routine. Consider
+the following, for example:
+
+\small
+\begin{terminalv}
+      SUBROUTINE POLY3TRAN( THIS, NPOINT, NCOORD_IN, INDIM, IN, FORWARD,
+     :                      NCOORD_OUT, OUTDIM, OUT, STATUS )
+      INTEGER THIS, NPOINT, NCOORD_IN, INDIM, NCOORD_OUT, OUTDIM, STATUS
+      DOUBLE PRECISION IN( INDIM, NCOORD_IN ), OUT( OUTDIM, NCOORD_OUT )
+      LOGICAL FORWARD
+
+      INCLUDE 'AST_PAR'
+      DOUBLE PRECISION X
+      INTEGER POINT
+
+*  Forward transformation.
+      DO 1 POINT = 1, NPOINT
+         X = IN( POINT, 1 )
+         IF ( X .EQ. AST__BAD ) THEN
+            OUT( POINT, 1 ) = AST__BAD
+         ELSE
+            OUT( POINT, 1 ) =
+     :      6.18D0 + X * ( 0.12D0 + X * ( -0.003D0 + X * 0.0000101D0 ) )
+         END IF
+ 1    CONTINUE
+      END
+\end{terminalv}
+\normalsize
+
+This implements a 1-dimensional cubic polynomial transformation. Since
+this is somewhat awkward to invert, however, we have only implemented
+the forward transformation.  When registering the routine, this is
+indicated via the FLAGS argument to \htmlref{AST\_INTRAREG}{AST\_INTRAREG}, as follows:
+
+\small
+\begin{terminalv}
+      EXTERNAL POLY3TRAN
+
+      ...
+
+      CALL AST_INTRAREG( 'Poly3Tran', 1, 1, POLY3TRAN, AST__NOINV,
+     :                   PURPOSE, AUTHOR, CONTACT, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, the fifth argument has been set to the flag value AST\_\_NOINV
+to indicate the lack of an inverse. If the forward transformation were
+absent, we would use AST\_\_NOFOR instead. Flag values for this
+argument may be combined by summing them if necessary.
+
+\subsection{\label{ss:variableintramapcoordinates}Variable Numbers of Coordinates}
+
+In our earlier examples, we have used a fixed number of input and
+output coordinates when registering a coordinate transformation
+routine. It is not necessary to impose this restriction, however, if
+the transformation routine can cope with a variable number of
+coordinates (as with the example in
+\secref{ss:transformationfunctions}). We indicate the acceptability of
+a variable number when registering the transformation routine by
+supplying the value AST\_\_ANY for the number of input and/or output
+coordinates, as follows:
+
+\small
+\begin{terminalv}
+      CALL AST_INTRAREG( 'SqrTran', AST__ANY, AST__ANY, SQRTRAN, 0,
+     :                   PURPOSE, AUTHOR, CONTACT, STATUS )
+\end{terminalv}
+\normalsize
+
+The result is that an \htmlref{IntraMap}{IntraMap} may now be created with any number of
+input and output coordinates. For example:
+
+\small
+\begin{terminalv}
+      INTEGER INTRAMAP1, INTRAMAP2
+
+      ...
+
+      INTRAMAP1 = AST_INTRAMAP( 'SqrTran', 1, 1, ' ', STATUS )
+      INTRAMAP2 = AST_INTRAMAP( 'SqrTran', 3, 3, 'Invert=1', STATUS )
+\end{terminalv}
+\normalsize
+
+It is possible to fix either the number of input or output coordinates
+(by supplying an explicit number to \htmlref{AST\_INTRAREG}{AST\_INTRAREG}), but more subtle
+restrictions on the number of coordinates, such as requiring that \htmlref{Nin}{Nin}
+and \htmlref{Nout}{Nout} be equal, are not supported. This means that:
+
+\small
+\begin{terminalv}
+      INTRAMAP = AST_INTRAMAP( 'SqrTran', 1, 2, ' ', STATUS )
+\end{terminalv}
+\normalsize
+
+will be accepted without error, although the transformation routine
+cannot actually handle such a combination sensibly. If this is
+important, it would be worth adding a check within the transformation
+routine itself, so that the error would be detected when it came to be
+used.
+
+\subsection{\label{ss:intraflag}Adapting a Transformation Routine to Individual IntraMaps}
+
+In the examples given so far, our coordinate transformation routines
+have not made use of the THIS pointer passed to them (which identifies
+the \htmlref{IntraMap}{IntraMap} whose transformation we are implementing). In practice,
+this will often be the case. However, the presence of the THIS pointer
+allows the transformation routine to invoke any other AST routine on
+the IntraMap, and this permits enquiries about its attributes. The
+transformation routine's behaviour can therefore be modified according
+to any attribute values which are set. This turns out to be a useful
+thing to do, so each IntraMap has a special \htmlref{IntraFlag}{IntraFlag} attribute reserved
+for exactly this purpose.
+
+Consider, for instance, the case where the transformation routine has
+access to several alternative sets of internally-stored data which it
+may apply to perform its transformation. Rather than implement many
+different versions of the transformation routine, you may switch
+between them by setting a value for the IntraFlag attribute when you
+create an instance of an IntraMap, for example:
+
+\small
+\begin{terminalv}
+      INTRAMAP1 = AST_INTRAMAP( 'MyTran', 2, 2, 'IntraFlag=A', STATUS )
+      INTRAMAP2 = AST_INTRAMAP( 'MyTran', 2, 2, 'IntraFlag=B', STATUS )
+\end{terminalv}
+\normalsize
+
+The transformation routine may then enquire the value of the IntraFlag
+attribute (\emph{e.g.}\ using AST\_GETC and passing it the THIS
+pointer) and use whichever dataset is required for that particular
+IntraMap.
+
+This approach is particularly useful when the number of possible
+transformations is unbounded or not known in advance, in which case
+the IntraFlag attribute may be used to hold numerical values encoded
+as part of a character string (effectively using them as data for the
+IntraMap). It is also superior to the use of a global switch for
+communication (\emph{e.g.}\ setting an index to select the ``current''
+data before using the IntraMap), because it continues to work when
+several IntraMaps are embedded within a more complex compound \htmlref{Mapping}{Mapping},
+when you may have no control over the order in which they are used.
+
+\subsection{\xlabel{MaxTran}\label{ss:simplifyingintramaps}Simplifying IntraMaps}
+
+A notable disadvantage of IntraMaps is that they are ``black boxes''
+as far as AST is concerned. This means that they have limited ability
+to participate in the simplification of compound Mappings performed,
+\emph{e.g.}, by \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY} (\secref{ss:simplifyingcmpmaps}),
+because AST cannot know how they interact with other Mappings. In
+reality, of course, they will often implement such specialised
+coordinate transformations that the simplification possibilities will
+be rather limited anyway.
+
+One important simplification, however, is the ability of a \htmlref{Mapping}{Mapping} to
+cancel with its own inverse to yield a unit Mapping (a \htmlref{UnitMap}{UnitMap}). This
+is important because Mappings are frequently used to relate a dataset
+to some external standard (a celestial coordinate system, for
+example). When inter-relating two similar datasets calibrated using
+the same standard, part of the Mapping often cancels, because it is
+applied first in one direction and then the other, effectively
+eliminating the reference to the standard. This is often a useful
+simplification and can lead to greater efficiency.
+
+Many transformations have this property of cancelling with their own
+inverse, but not necessarily all. Consider the following
+transformation routine, for example:
+
+\small
+\begin{terminalv}
+      SUBROUTINE MAXTRAN( THIS, NPOINT, NCOORD_IN, INDIM, IN, FORWARD,
+     :                    NCOORD_OUT, OUTDIM, OUT, STATUS )
+      INTEGER THIS, NPOINT, NCOORD_IN, INDIM, NCOORD_OUT, OUTDIM, STATUS
+      DOUBLE PRECISION IN( INDIM, NCOORD_IN ), OUT( OUTDIM, NCOORD_OUT )
+      LOGICAL FORWARD
+
+      INCLUDE 'AST_PAR'
+      DOUBLE PRECISION HI, X
+      INTEGER COORD, POINT
+
+*  Forward transformation.
+      IF ( FORWARD ) THEN
+         DO 2 POINT = 1, NPOINT
+            HI = AST__BAD
+            DO 1 COORD = 1, NCOORD_IN
+               X = IN( POINT, COORD )
+               IF ( X .NE. AST__BAD ) THEN
+                  IF ( X .GT. HI .OR. HI .EQ. AST__BAD ) HI = X
+               END IF
+ 1          CONTINUE
+ 2       CONTINUE
+
+*  Inverse transformation.
+      ELSE
+         DO 4 COORD = 1, NCOORD_OUT
+            DO 3 POINT = 1, NPOINT
+               OUT( POINT, COORD ) = IN( POINT, 1 )
+ 3          CONTINUE
+ 4       CONTINUE
+      END IF
+      END
+\end{terminalv}
+\normalsize
+
+This routine takes any number of input coordinates and returns a
+single output coordinate which is the maximum value of the input
+coordinates. Its inverse (actually a ``pseudo-inverse'') sets all the
+input coordinates to the value of the output
+coordinate.\footnote{Remember that IN holds the original ``output''
+coordinates when applying the inverse transformation and OUT holds the
+original ``input'' coordinates.}
+
+If this routine is applied in the forward direction and then in the
+inverse direction, it does \textbf{not} in general restore the original
+coordinate values. However, if applied in the inverse direction and
+then the forward direction, it does. Hence, replacing the sequence of
+operations with an equivalent UnitMap is possible in the latter case,
+but not in the former.
+
+To distinguish these possibilities, two flag values are provided for
+use with \htmlref{AST\_INTRAREG}{AST\_INTRAREG} to indicate what simplification (if any) is
+possible. For example, to register the above transformation routine,
+we might use:
+
+\small
+\begin{terminalv}
+      EXTERNAL MAXTRAN
+
+      ...
+
+      CALL AST_INTRAREG( 'MaxTran', AST__ANY, 1, MAXTRAN, AST__SIMPIF,
+     :                   PURPOSE, AUTHOR, CONTACT, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, the flag value AST\_\_SIMPIF supplied for the fifth argument
+indicates that simplification is possible if the transformation is
+applied in the inverse direction followed by the forward direction. To
+indicate the complementary case, the flag AST\_\_SIMPFI would be used
+instead. If both simplifications are possible (as with the SQRTRAN
+function in \secref{ss:transformationfunctions}), then we would use
+the sum of both values.
+
+In practice, some judgement is usually necessary when deciding whether
+to allow simplification. For example, seen in one light our SQRTRAN
+routine (\secref{ss:transformationfunctions}) does not cancel with its
+own inverse, because squaring a coordinate value and then taking its
+square root can change the original value, if this was
+negative. Therefore, replacing this combination with a UnitMap will
+change the behaviour of a compound Mapping and should not be
+allowed. Seen in another light, however, where the coordinates being
+processed are intrinsically all positive, it is a permissible and
+probably useful simplification.
+
+If such distinctions are ever important in practice, it is simple to
+register the same transformation routine twice with different flag
+values (use a separate name for each) and then use whichever is
+appropriate when creating an \htmlref{IntraMap}{IntraMap}.
+
+\subsection{\label{ss:readingandwritingintramaps}Writing and Reading IntraMaps}
+
+It is most important to realise that when you write an \htmlref{IntraMap}{IntraMap} to a
+\htmlref{Channel}{Channel} (\secref{ss:writingtoachannel}), the transformation routine
+which it uses is not stored with it. To do so is impossible, because
+the routine has been compiled and loaded into memory ready for
+execution before AST gets to see it. However, AST does store the name
+associated with the transformation routine and various details about
+the IntraMap itself.
+
+This means that any program attempting to read the IntraMap
+(\secref{ss:readingfromachannel}) cannot make use of it unless it also
+has independent access to the original transformation routine. If it
+does not have access to this routine, an error will occur at the point
+where the IntraMap is read and the associated error message will
+direct the user to the author of the transformation routine for more
+information.
+
+However, if the necessary transformation routine is available, and
+has been registered before the read operation takes place, then AST is
+able to re-create the original IntraMap and will do so. Registration
+of the transformation routine must, of course, use the same name
+(and, in fact, be identical in most particulars) as was used in the
+original program which wrote the data.
+
+This means that a set of co-operating programs which all have access
+to the same set of transformation routines and register them in
+identical fashion (see \secref{ss:intramaplibrary} for how this can
+best be achieved) can freely exchange data that contain IntraMaps. The
+need to avoid exporting such data to unsuspecting third parties
+(\secref{ss:intramaplimitations}) must, however, be re-iterated.
+
+\subsection{\label{ss:intramaplibrary}Managing Transformation Routines in Libraries}
+
+If you are developing a large suite of data reduction software, you
+may have a need to use IntraMaps at various points within it. Very
+probably this will occur in unrelated modules which are compiled
+separately and then stored in a library. Since the transformation
+routines required must be registered before they can be used, this
+makes it difficult to decide where to perform this registration,
+especially since any particular data reduction program may use an
+arbitrary subset of the modules in your library.
+
+To assist with this problem, AST allows you to perform the same
+registration of a transformation routine any number of times, so long
+as it is performed using an identical invocation of \htmlref{AST\_INTRAREG}{AST\_INTRAREG} on
+each occasion (\emph{i.e.}\ all of its arguments must be
+identical). This means you do not have to keep track of whether a
+particular routine has already been registered but could, in fact,
+register it on each occasion immediately before it is required
+(wherever that may be). In order that all registrations are identical,
+however, it is recommended that you group them all together into a
+single routine, perhaps as follows:
+
+\small
+\begin{terminalv}
+      SUBROUTINE MYTRANS( STATUS )
+      INTEGER STATUS
+
+      INCLUDE 'AST_PAR'
+      EXTERNAL MAXTRAN, POLY3TRAN, SQRTRAN
+
+      ...
+
+      CALL AST_INTRAREG( 'MaxTran', AST__ANY, 1, MAXTRAN, AST__SIMPIF,
+     :                   PURPOSE, AUTHOR, CONTACT, STATUS )
+
+      ...
+
+      CALL AST_INTRAREG( 'Poly3Tran', 1, 1, POLY3TRAN, AST__NOINV,
+     :                   PURPOSE, AUTHOR, CONTACT, STATUS )
+
+      ...
+
+      CALL AST_INTRAREG( 'SqrTran, 2, 2, SQRTRAN, 0,
+     :                   PURPOSE, AUTHOR, CONTACT, STATUS )
+      END
+\end{terminalv}
+\normalsize
+
+You can then simply invoke this routine wherever necessary. It is, in
+fact, particularly important to register all relevant transformation
+routines in this way before you attempt to read an \htmlref{Object}{Object} that might
+be (or contain) an \htmlref{IntraMap}{IntraMap}
+(\secref{ss:readingandwritingintramaps}). This is because you may not
+know in advance which of these transformation routines the IntraMap
+will use, so they must all be available in order to avoid an error.
+
+\cleardoublepage
+\section{\label{ss:plots}Producing Graphical Output (Plots)}
+
+Graphical output from AST is performed though an \htmlref{Object}{Object} called a \htmlref{Plot}{Plot},
+which is a specialised form of \htmlref{FrameSet}{FrameSet}. A Plot does not represent the
+graphical content itself, but is a route through which plotting
+operations, such as drawing lines and curves, are conveyed on to a
+plotting surface to appear as visible graphics.
+
+\subsection{The Plot Model}
+
+When a \htmlref{Plot}{Plot} is created, it is initialised by providing a \htmlref{FrameSet}{FrameSet} whose
+base \htmlref{Frame}{Frame} (as specified by its \htmlref{Base}{Base} attribute) is mapped linearly or
+logarithmically (as specified by the LogPlot attribues) on to a
+\emph{plotting area}. This is a rectangular region in the graphical
+coordinate space of the underlying graphics system and becomes the new
+base Frame of the Plot. In effect, the Plot becomes attached to the
+plotting surface, in rather the same way that a basic FrameSet might be
+attached to (say) an image.
+
+The current Frame of the Plot (derived from the current Frame of the
+FrameSet supplied) is used to represent a \emph{physical coordinate
+system}. This is the system in which plotting operations are
+performed by your program.  Every plotting operation is then
+transformed through the \htmlref{Mapping}{Mapping} which inter-relates the Plot's current
+and base Frames in order to appear on the plotting surface.
+
+An example may help here. Suppose we start with a FrameSet whose base
+Frame describes the pixel coordinates of an image and whose current
+Frame describes a celestial (equatorial) coordinate system. Let us
+assume that these two Frames are inter-related by a Mapping within the
+FrameSet which represents a particular sky projection.
+
+When a Plot is created from this FrameSet, we specify how the pixel
+coordinates (the base Frame) maps on to the plotting surface. This
+simply corresponds to telling the Plot where we have previously
+plotted the image data. If we now use the Plot to plot a line with
+latitude zero in our physical coordinate system, as given by the
+current Frame, this line would appear as a curve (the equator) on the
+plotting surface, correctly registered with the image.
+
+There are a number of plotting functions provided, which all work in a
+similar way. Plotting operations are transformed through the Mapping
+which the Plot represents before they appear on the plotting
+surface.\footnote{Like any FrameSet, a Plot can be used as a
+Mapping. In this case it is the inverse transformation which is used
+when plotting (\emph{i.e.}\ that which transforms between the current
+and base Frames).}  It is possible to draw symbols, lines, axes,
+entire grids and more in this way.
+
+%\subsection{TBW---Creating a Plot}
+
+\subsection{Plotting Symbols}
+
+The simplest form of plotting is to draw symbols (termed
+\emph{markers}) at a set of points. This is performed by \htmlref{AST\_MARK}{AST\_MARK},
+which is supplied with a set of physical coordinates at which to place
+the markers:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+      INTEGER NCOORD, NMARK, TYPE, STATUS
+      DOUBLE PRECISION IN( NMARK, NCOORD )
+
+      STATUS = 0
+
+      ...
+
+      CALL AST_MARK( PLOT, NMARK, NCOORD, NMARK, IN, TYPE, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, NMARK specifies how many markers to plot and NCOORD specifies
+how many coordinates are being supplied for each
+point.\footnote{Remember, the physical coordinate space need not
+necessarily be 2-dimensional, even if the plotting surface is.} The
+array IN supplies the coordinates and the integer TYPE specifies which
+type of marker to plot.
+
+\subsection{\label{ss:plottinggeodesics}Plotting Geodesic Curves}
+
+There is no \htmlref{Plot}{Plot} routine to draw a straight line, because any straight
+line in physical coordinates can potentially turn into a curve in
+graphical coordinates. We therefore start by considering how to draw
+geodesic curves.  These are curves which trace the path of shortest
+distance between two points in physical coordinates
+ and are the basic drawing element in a Plot.
+
+In many instances, the geodesic will, in fact, be a straight line, but
+this depends on the Plot's current \htmlref{Frame}{Frame}. If this represents a
+celestial coordinate system, for instance, it will be a great circle
+(corresponding with the behaviour of the \htmlref{AST\_DISTANCE}{AST\_DISTANCE} function which
+defines the metric of the physical coordinate space).  The geodesic
+will, of course, be transformed into graphics coordinates before being
+plotted. A geodesic curve is plotted using \htmlref{AST\_CURVE}{AST\_CURVE} as follows:
+
+\small
+\begin{terminalv}
+      DOUBLE PRECISION START( NCOORD ), FINISH( NCOORD )
+
+      ...
+
+      CALL AST_CURVE( PLOT, START, FINISH, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, START and FINISH are arrays containing the starting and
+finishing coordinates of the curve. The \htmlref{AST\_OFFSET}{AST\_OFFSET} and AST\_DISTANCE
+routines can often be useful for computing these
+(\secref{ss:distanceandoffset}).
+
+If you need to draw a series of curves end-to-end (when drawing a
+contour line, for example), then a more efficient alternative is to
+use \htmlref{AST\_POLYCURVE}{AST\_POLYCURVE}. This has the same effect as a sequence of calls to
+AST\_CURVE, but allows you to supply a whole set of points at the same
+time. AST\_POLYLINE then joins them, in sequence, using geodesic
+curves:
+
+\small
+\begin{terminalv}
+      INTEGER NPOINT
+      DOUBLE PRECISION COORDS( NPOINT, NCOORD )
+
+      ...
+
+      CALL AST_POLYCURVE( PLOT, NPOINT, NCOORD, NPOINT, COORDS, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, NPOINT specifies how many points are to be joined and NCOORD
+specifies how many coordinates are being supplied for each point.  The
+array COORDS supplies the coordinates of the points in the Plot's
+physical coordinate system.
+
+\subsection{Plotting Curves Parallel to Axes}
+
+As there is no \htmlref{Plot}{Plot} routine to draw a ``straight line'', drawing axes
+and grid lines to represent coordinate systems requires a slightly
+different approach. The problem is that for some coordinate systems,
+these grid lines will not be geodesics, so \htmlref{AST\_CURVE}{AST\_CURVE} and
+\htmlref{AST\_POLYCURVE}{AST\_POLYCURVE} (\secref{ss:plottinggeodesics}) cannot easily be used
+(you would have to resort to approximating grid lines by many small
+elements). Lines of constant celestial latitude provide an example of
+this, with the exception of the equator which is a geodesic.
+
+The \htmlref{AST\_GRIDLINE}{AST\_GRIDLINE} routine allows these curves to be drawn, as follows:
+
+\small
+\begin{terminalv}
+      INTEGER AXIS
+      DOUBLE PRECISION LENGTH
+
+      ...
+
+      CALL AST_GRIDLINE( PLOT, AXIS, START, LENGTH, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, AXIS specifies which physical coordinate axis we wish to draw
+parallel to. The START array contains the coordinates of the start of
+the curve and LENGTH specifies the distance to draw along the axis in
+physical coordinate space.
+
+\subsection{\label{ss:plottinggeneralizedcurves}Plotting Generalized Curves}
+We have seen how geodesic curves and grid lines can be drawn. The \htmlref{Plot}{Plot}
+class includes another method,
+\htmlref{AST\_GENCURVE}{AST\_GENCURVE},
+which allows curves of \emph{any} form to be drawn. The caller supplies a
+\htmlref{Mapping}{Mapping} which maps offset along the curve\footnote{normalized so that the
+start of the curve is at offset 0.0 and the end of the curve is at offset
+1.0 - offset need not be linearly related to distance.} into the
+corresponding position in the current \htmlref{Frame}{Frame} of the Plot.
+AST\_GENCURVE,
+then takes care of Mapping these positions into graphics coordinates. The
+choice of exactly which positions along the curve are to be used to
+define the curve is also made by
+AST\_GENCURVE,
+using an adaptive algorithm which concentrates points around areas where
+the curve is bending sharply or is discontinuous in graphics coordinates.
+
+The \htmlref{IntraMap}{IntraMap} class may be of particular use in this context since it allows
+you to code your own Mappings to do any transformation you choose.
+
+
+\subsection{\label{ss:clipping}Clipping}
+
+Like many graphics systems, a \htmlref{Plot}{Plot} allows you to \emph{clip} the graphics
+you produce. This means that plotting is restricted to certain regions
+of the plotting surface so that anything drawn outside these regions
+will not appear.  All Plots automatically clip at the edges of the
+plotting area specified when the Plot is created. This means that
+graphics are ultimately restricted to the rectangular region of
+plotting space to which you have attached the Plot.
+
+In addition to this, you may also specify lower and upper limits on
+each axis at which clipping should occur. This permits you to further
+restrict the plotting region. Moreover, you may attach these clipping
+limits to \emph{any} of the Frames in the Plot. This allows you to
+place restrictions on where plotting will take place in either the
+physical coordinate system, the graphical coordinate system, or in any
+other coordinate system which is described by a \htmlref{Frame}{Frame} within the Plot.
+
+For example, you could plot using equatorial coordinates and set up
+clipping limits in galactic coordinates. In general, you could set up
+arbitrary clipping regions by adding a new Frame to a Plot (in which
+clipping will be performed) and inter-relating this to the other
+Frames in a suitable way.
+
+Clipping limits are defined using the \htmlref{AST\_CLIP}{AST\_CLIP} routine, as follows:
+
+\small
+\begin{terminalv}
+      INTEGER IFRAME, NAXES
+      DOUBLE PRECISION LBND( NAXES ), UBND( NAXES )
+
+      ...
+
+      CALL AST_CLIP( PLOT, IFRAME, LBND, UBND, STATUS )
+\end{terminalv}
+\normalsize
+
+Here, the IFRAME value gives the index of the Frame within the Plot to
+which clipping is to be applied, while LBND and UBND give the limits
+on each axis of the selected Frame (NAXES is the number of axes in
+this Frame).
+
+You can remove clipping by giving a value of AST\_\_NOFRAME for IFRAME.
+
+\subsection{Using a Plot as a Mapping}
+
+All Plots are also Mappings (just like the FrameSets from which they
+are derived), so can be used to transform coordinates.
+
+Like FrameSets, the forward transformation of a \htmlref{Plot}{Plot} will convert
+coordinates between the base and current Frames (\emph{i.e.}\ between
+graphical and physical coordinates). This would be useful if you were
+(say) reading a cursor position in graphical coordinates and needed to
+convert this into physical coordinates for display.
+
+Conversely, a Plot's inverse transformation converts between its
+current and base Frames (\emph{i.e.}\ from physical coordinates to
+graphical coordinates). This transformation is applied automatically
+whenever plotting operations are carried out by AST routines. It may
+also be useful to apply it directly, however, if you wish to perform
+additional plotting operations (\emph{e.g.}\ those provided by the
+native graphics system) at positions specified in physical
+coordinates.
+
+There is, however. one important difference between using a \htmlref{FrameSet}{FrameSet}
+and a Plot to transform coordinates, and this is that clipping may be
+applied by a Plot (if it has been enabled using
+\htmlref{AST\_CLIP}{AST\_CLIP}---\secref{ss:clipping}). Any point which lies within the
+clipped region of a Plot will, when transformed, yield coordinates
+with the value AST\_\_BAD. If you wish to avoid this clipping, you
+should extract the relevant \htmlref{Mapping}{Mapping} from the Plot (using
+\htmlref{AST\_GETMAPPING}{AST\_GETMAPPING}) and use this, instead of the Plot, to transform the
+coordinates.
+
+\subsection{Using a Plot as a Frame}
+
+Every \htmlref{Plot}{Plot} is also a \htmlref{Frame}{Frame}, so can be used to obtain the values of
+Frame attributes such as a \htmlref{Title}{Title}, axis Labels, axis Units,
+\emph{etc.}, which are typically used when displaying data and/or
+coordinates. These attributes are, as for any \htmlref{FrameSet}{FrameSet}, derived from
+the current Frame of the Plot (\secref{ss:framesetasframe}). They are
+also used automatically when using the Plot to plot coordinate axes
+and coordinate grids (\emph{e.g.}\ for labelling
+them---\secref{ss:plottingagrid}).
+
+Because the current Frame of a Plot represents physical coordinates,
+any Frame operation applied to the Plot will effectively be working in
+this coordinate system. For example, the \htmlref{AST\_DISTANCE}{AST\_DISTANCE} and \htmlref{AST\_OFFSET}{AST\_OFFSET}
+routines will compute distances and offsets in physical coordinate
+space, and \htmlref{AST\_FORMAT}{AST\_FORMAT} will format physical coordinates in an
+appropriate way for display.
+
+\subsection{\label{ss:validphysicalcoordinates}Regions of Valid Physical Coordinates}
+
+When points in physical coordinate space are transformed by a \htmlref{Plot}{Plot}
+into graphics coordinates for plotting, they may not always yield
+valid coordinates, irrespective of any clipping being applied
+(\secref{ss:clipping}). To indicate this, the resulting coordinate
+values will be set to the value AST\_\_BAD
+(\secref{ss:badcoordinates}).
+
+There are a number of reasons why this may occur, but typically it
+will be because physical coordinates only map on to a subset of the
+graphics coordinate space. This situation is commonly encountered with
+all-sky projections where, typically, the celestial sphere appears,
+when plotted, as a distorted shape (\emph{e.g.}\ an ellipse) which
+does not entirely fill the graphics space. In some cases, there may
+even be multiple regions of valid and invalid physical coordinates.
+
+When plotting is performed \emph{via} a Plot, graphical output will
+only appear in the regions of valid physical coordinates. Nothing will
+appear where invalid coordinates occur. Such output is effectively
+clipped. If you wish to plot in these areas, you must change
+coordinate system and use, say, graphical coordinates to address the
+plotting surface directly.
+
+\subsection{Plotting Borders}
+
+The \htmlref{AST\_BORDER}{AST\_BORDER} routine is provided to draw a (line) border around
+your graphical output. With most graphics systems, this would simply
+be a rectangular box around the plotting area. With a \htmlref{Plot}{Plot}, however,
+this boundary follows the edge of each region containing valid,
+unclipped physical coordinates (\secref{ss:validphysicalcoordinates}).
+
+This means, for example, that if you were plotting an all-sky
+projection, this boundary would outline the perimeter of the celestial
+sphere when projected on to your plotting surface. Of course, if there
+is no clipping and all physical coordinates are valid, then you will
+get the traditional rectangular box. AST\_BORDER requires only a
+pointer to the Plot and the usual STATUS argument:
+
+\small
+\begin{terminalv}
+      LOGICAL HOLES
+
+      ...
+
+      HOLES = AST_BORDER( PLOT, STATUS )
+\end{terminalv}
+\normalsize
+
+It returns a logical value to indicate if any invalid or clipped
+physical coordinates were found within the plotting area. If they
+were, it will draw around the valid unclipped regions and return
+.TRUE.. Otherwise, it will draw a simple rectangular border and return
+.FALSE..
+
+\subsection{Plotting Text}
+
+Using a \htmlref{Plot}{Plot} to draw text involves supplying a string of text to be
+displayed and a position in physical coordinates where the text is to
+appear. The position is transformed into graphical coordinates to
+determine where the text should appear on the plotting surface. You
+must also provide a 2-element UP vector which gives the upward
+direction of the text in graphical coordinates. This allows text to be
+drawn at any angle.
+
+Plotting is performed by \htmlref{AST\_TEXT}{AST\_TEXT}, for example:
+
+\small
+\begin{terminalv}
+      CHARACTER * ( 20 ) TEXT
+      DOUBLE PRECISION POS( NCOORD )
+      REAL UP( 2 )
+      DATA UP / 0.0, 1.0 /
+
+      ...
+
+      CALL AST_TEXT( PLOT, TEXT, POS, UP, 'TL', STATUS )
+\end{terminalv}
+\normalsize
+
+Here, TEXT contains the string to be drawn, POS is an array of
+physical coordinates and UP specifies the upward vector. In this case,
+the text will be drawn horizontally. The penultimate argument
+specifies the text justification, here indicating that the top left
+corner of the text should appear at the position given.
+
+Further control over the appearance of the text is possible by setting
+values for various Plot attributes, for example Colour, Font and Size.
+Sub-strings within the displayed text can be given different appearances,
+or turned into super-scripts or sub-scripts, by the inclusion of escape
+sequences (see section~\secref{ss:escapes}) within the supplied text string.
+
+\subsection{\label{ss:plottingagrid}Plotting a Grid}
+
+The most comprehensive plotting routine available is \htmlref{AST\_GRID}{AST\_GRID}, which
+can be used to draw labelled coordinate axes and, optionally, to
+overlay coordinate grids on the plotting area
+(Figure~\ref{fig:gridplot}). The routine is straightforward to use,
+simply requiring a pointer to the \htmlref{Plot}{Plot} and a STATUS argument:
+
+\small
+\begin{terminalv}
+      CALL AST_GRID( PLOT, STATUS )
+\end{terminalv}
+\normalsize
+
+It will draw both linear and curvilinear axes and grids, as required
+by the particular Plot. The appearance of the output can be modified
+in a wide variety of ways by setting various Plot attributes.
+The Label attributes of the current \htmlref{Frame}{Frame} are displayed as the axis
+labels in the grid, and the \htmlref{Title}{Title} attribute as the plot title. Sub-strings
+within these strings can be given different appearances, or turned into
+super-scripts or sub-scripts, by the inclusion of escape sequences (see
+section~\secref{ss:escapes}) within the Label attributes.
+
+\subsection{\label{ss:escapes}Controlling the Appearance of Sub-strings}
+Normally, each string of characters displayed using a \htmlref{Plot}{Plot} will be
+plotted so that all characters in the string have the same font size,
+colour, \emph{etc.}, specified by the appropriate attributes of the
+Plot. However, it is possible to include \emph{escape sequences} within
+the text to modify the appearance of sub-strings. \htmlref{Escape}{Escape} sequences can be
+used to change, colour, font, size, width, to introduce extra horizontal
+space between characters, and to change the base line of characters (thus
+allowing super-scripts and sub-scripts to be created). See the entry for
+the Escape attribute in \appref{ss:attributedescriptions} for details.
+
+As an example, if the character string ``\verb+10\%^50+\%s70+0.5+'' is
+plotted, it will be displayed as ``$10^{0.5}$'' - that is, with a
+super-scripted exponent. The exponent text will be 70\% of the size of
+normal text (as determined by the Size attribute), and its baseline will
+be raised by 50\% of the height of a normal character.
+
+Such escape sequences can be used in the strings assigned to textual
+attributes of the Plot (such as the axis Labels), and may also be
+included in strings plotted using
+\htmlref{AST\_TEXT}{AST\_TEXT}.
+
+The Format attribute for the \htmlref{SkyAxis}{SkyAxis} class includes the ``g'' option
+which will cause escape sequences to be included when formatting
+celestial positions so that super-script characters are used as
+delimiters for the various fields (a super-script ``h'' for hours, ``m''
+for minutes, \emph{etc}).
+
+Note, the facility for interpreting escape sequences is only available if
+the graphics wrapper functions which provide the interface to the
+underlying graphics system support all the functions included in the
+\verb+grf.h+ file as of AST V3.2. Older grf interfaces may need to be
+extended by the addition of new functions before escape sequences can be
+interpretted.
+
+\subsection{\label{ss:logaxes}Producing Logarithmic Axes}
+In certain situations you may wish for one or both of the plotted axes to
+be displayed logarithmically rather than linearly. For instance, you may
+wish to do this when using a \htmlref{Plot}{Plot} to represent a spectrum of, say, flux
+against frequency. In this case, you can cause the frequency axis to be drawn
+logarithmically simply by setting the boolean LogPlot attribute for the
+frequency axis to a non-zero value. This causes several things to happen:
+
+\begin{enumerate}
+
+\item The \htmlref{Mapping}{Mapping} between the base \htmlref{Frame}{Frame} of the Plot (which represents
+the underlying graphics world coordinate system) and the base Frame of
+the \htmlref{FrameSet}{FrameSet} supplied when the Plot was created, is modified. By
+default, this mapping is linear on both axes, but setting LogPlot non-zero
+for an axis causes the Mapping to be modified so that it is logarithmic
+on the specified axis. This is only possible if the displayed section of
+the axis does not include the value zero (otherwise the attempt to set
+a new value for LogPlot is ignored,and it retains its default value of
+zero).
+
+\item The major tick marks drawn as part of the annotated coordinate grid
+are spaced logarithmically rather than linearly. That is, major axis
+values are chosen so that there is a constant ratio between adjacent
+tick mark values. This ratio is constrained to be a power of ten. The
+minor tick marks are drawn at linearly distributed points between the
+adjoining major tick values. Thus if a pair of adjacent major tick values
+are drawn at axis values 10.0 and 100.0, minor ticks will be placed at
+20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0 and 90.0 (note only 8 minor tick
+marks are drawn).
+
+\item If possible, numerical axis labels are shown as powers of ten.
+This depends on the facilities implemented by the graphics wrapper
+functions (see the next section). Extra functions were introduced to this
+set of wrapper functions at AST V3.2 which enable super-scripts and
+sub-scripts to be produced. Some older wrappers may not yet have
+implemented these functiosn and this will result in axis labels being
+drawn in usual scientific or decimal notation.
+
+\end{enumerate}
+
+Whilst the LogPlot attribute can be used to control all three of the above
+facilities, it is possible to control them individually as well. The
+LogTicks and LogLabel attributes control the behaviour specified in items
+2 and 3 above, but the default values for these attributes depend on the
+setting of the LogPlot attribute. This means that setting LogPlot
+non-zero will swicth all three facilites on, so long as zero values have
+not been assigned explicitly to LogTicks or LogLabel.
+
+
+\subsection{\label{ss:choosingagraphicspackage}Choosing a Graphics Package}
+The \htmlref{Plot}{Plot} class itself does not include any code for actually drawing on a
+graphics device. Instead, it requires a set of functions to be provided
+which it uses to draw the required graphics. These include functions
+to draw a straight line, draw a text string, \emph{etc}. You may choose
+to provide functions from your favorite graphics package, or you can even
+write your own! To accomodate variations in the calling interfaces of
+different graphics packages, AST defines a standard interface for these
+routines. If this interface differs from the interface provided by your
+graphics package (which in general it will), then you must write a set of
+\emph{wrapper functions}, which provide the interface expected by AST but
+which then call functions from your graphics package to provide the
+required functionality. AST comes with wrapper functions suitable for
+the PGPLOT graphics package (see \xref{SUN/15}{sun15}{}).
+
+There are two ways of indicating which wrapper functions are to be used by
+the Plot class:
+\begin{enumerate}
+
+\item A file containing C functions with pre-defined names can be written
+and linked with the application using options of the \htmlref{ast\_link}{ast\_link} command.
+(see \secref{ss:howtobuild} and \appref{ss:commanddescriptions}). AST is
+distributed with such a file (called \texttt{grf\_pgplot.c}) which calls PGPLOT
+functions to implement the required functionality. This file can be used
+as a template for writing your own.
+Currently, it is not possible to write such ``grf modules'' in Fortran.
+If you want to use wrapper functions written in Fortran, then you must
+use the \htmlref{AST\_GRFSET}{AST\_GRFSET} method as described below.
+
+\item The
+AST\_GRFSET
+method of the Plot class can be used to ``register''
+wrapper functions at run-time. This allows an application to switch
+between graphics systems if required. Graphics functions registered in
+this way do not need to have the pre-defined names used in the link-time
+method described above.
+
+\end{enumerate}
+
+For details of the interfaces of the wrapper routines, see
+the reference documentation for the AST\_GRFSET method.
+
+\cleardoublepage
+\section{Compiling and Linking Software that Uses AST}
+
+A small number of UNIX commands are provided by AST to assist with the
+process of building software. A description of these can be found in
+\appref{ss:commanddescriptions} and their use is discussed here.  Note
+that in order to access these commands, the appropriate directory
+(normally ``/star/bin'') should be on your PATH.\footnote{If you have
+not installed AST in the usual location, then substitute the
+appropriate directory in place of ``/star'' wherever it occurs.}
+
+\subsection{\label{ss:accessingheaderfile}Accessing AST Include Files}
+
+
+The include files provided for use with Fortran are:
+
+\begin{quote}
+\begin{description}
+\item[AST\_PAR]\mbox{}\\
+Declares the types of all AST functions and defines parameter
+constants, except those that identify error values.
+
+\item[AST\_ERR]\mbox{}\\
+Defines parameter constants to represent the various error values to
+which the AST error status may be set when an error occurs
+(\secref{ss:errordetection}).
+\end{description}
+\end{quote}
+
+References to AST include files should be in upper case. Most modern
+Fortran compilers allow the directory to be specified as a command line
+option:
+
+\small
+\begin{terminalv}
+f77 prog.f -I/star/include -o prog
+\end{terminalv}
+\normalsize
+
+If you are using such a compiler then your Fortran source code should,
+for instance, include:
+
+\small
+\begin{terminalv}
+      INCLUDE 'AST_PAR'
+\end{terminalv}
+\normalsize
+
+(that is, there is no need to include the directory within the INCLUDE
+statement). If your compiler does not provide such an option then your
+source code must contain an absolute file name identifying the directory
+where the include files reside, for instance:
+
+\small
+\begin{terminalv}
+      INCLUDE '/star/include/AST_PAR'
+\end{terminalv}
+\normalsize
+
+
+\subsection{\label{ss:linking}Linking with AST Facilities}
+
+Fortran programs may be linked with AST by including execution of the
+command ``\htmlref{ast\_link}{ast\_link}'' on the compiler command line. Thus, to compile
+and link a program called ``prog'', the following might be used:
+
+\small
+\begin{terminalv}
+f77 prog.f -L/star/lib `ast_link` -o prog
+\end{terminalv}
+\normalsize
+
+On Linux systems you should usually use \verb+g77 -fno-second-underscore+ in
+place of \verb+f77+ - see \xref{``Software development on Linux''}{sun212}
+{software_development_on_linux} in \xref{SUN/212}{sun212}{}.
+
+
+Note the use of backward quote characters, which cause the
+``ast\_link'' command to be executed and its result substituted into
+the compiler command. An alternative is to save the output from
+``ast\_link'' in (say) a shell variable and use this instead. You may
+find this a little faster if you are building software repeatedly
+during development.
+
+Programs which use AST can also be linked in a number of other ways,
+depending on the facilities they require. In the example above, we
+have used the default method which assumes that the program will not
+be generating graphical output, so that no graphics libraries need be
+linked. If you need other facilities, then various switches can be
+applied to the ``ast\_link'' command in order to control the linking
+process.
+
+For example, if you were producing graphical output using the PGPLOT
+graphics package, you could link with the AST/PGPLOT interface by
+using the ``$-$pgplot'' switch with ``ast\_link'', as
+follows:\footnote{Use the ``$-$pgp'' option instead if you wish to use
+the Starlink version of PGPLOT which uses GKS to generate its output.}
+
+\begin{small}
+\begin{terminalv}
+f77 prog.f -L/star/lib `ast_link -pgplot` -o prog
+\end{terminalv}
+\end{small}
+
+again using \verb+g77 -fno-second-underscore+ in place of \verb+f77+
+on Linux systems.
+
+
+See the ``ast\_link'' command description in
+\appref{ss:commanddescriptions} for details of the options available.
+
+\subsection{Building ADAM Applications that Use AST}
+
+Users of Starlink's \xref{ADAM}{sg4}{} programming environment
+\latex{(SG/4)} on UNIX should use the
+``\xref{alink}{sun144}{ADAM_link_scripts}'' command
+(\xref{SUN/144}{sun144}{}) to compile and link applications and can
+access the AST library by including execution of the command
+``\htmlref{ast\_link\_adam}{ast\_link\_adam}'' on the command line, as follows:
+
+\begin{small}
+\begin{terminalv}
+alink adamprog.f `ast_link_adam`
+\end{terminalv}
+\end{small}
+
+Note the use of backward quote characters.
+
+By default, AST error messages produced by applications built in this
+way will be delivered \emph{via} the Starlink EMS Error Message
+Service (\xref{SSN/4}{ssn4}{}) so that error handling by AST is
+consistent with the \xref{\emph{inherited
+status}}{sun104}{inherited_status} error handling normally used in
+Starlink software.
+
+Switches may be given to the ``ast\_link\_adam'' command (in a similar
+way to ``\htmlref{ast\_link}{ast\_link}''---\secref{ss:linking}) in order to link with
+additional AST-related facilities, such as a graphics interface. See
+the ``ast\_link\_adam'' command description in
+\appref{ss:commanddescriptions} for details of the options available.
+
+\appendix
+\cleardoublepage
+\section{\label{ss:classhierarchy}The AST Class Hierarchy}
+The following table shows the hierarchy of classes in the AST library.
+For a description of each class, you should consult
+\appref{ss:classdescriptions}.
+
+\small
+\begin{terminalv}
+Object             - Base class for all AST Objects
+   Axis            - Store axis information
+      SkyAxis      - Store celestial axis information
+   Channel         - Basic (textual) I/O channel
+      FitsChan     - I/O Channel using FITS header cards
+      XmlChan      - I/O Channel using XML
+      StcsChan     - I/O Channel using IVOA STC-S descriptions
+   KeyMap          - Store a set of key/value pairs
+      Table        - Store a 2-dimensional table of values
+   Mapping         - Inter-relate two coordinate systems
+      CmpMap       - Compound Mapping
+      DssMap       - Map points using Digitised Sky Survey plate solution
+      Frame        - Coordinate system description
+         CmpFrame  - Compound Frame
+            SpecFluxFrame - Observed value versus spectral position
+         FluxFrame - Observed value at a given fixed spectral position
+         FrameSet  - Set of inter-related coordinate systems
+            Plot   - Provide facilities for 2D graphical output
+               Plot3D - Provide facilities for 3D graphical output
+         Region    - Specify areas within a coordinate system
+            Box    - A box region with sides parallel to the axes of a Frame
+            Circle - A circular or spherical region within a Frame
+            CmpRegion  - A combination of two regions within a single Frame
+            Ellipse    - An elliptical region within a 2-dimensional Frame
+            Interval   - Intervals on one or more axes of a Frame.
+            NullRegion - A boundless region within a Frame
+            PointList  - A collection of points in a Frame
+            Polygon    - A polygonal region within a 2-dimensional Frame
+            Prism  - An extrusion of a Region into orthogonal dimensions
+            Stc    - Represents an generic instance of an IVOA STC-X description
+               StcResourceProfile - Represents an an IVOA STC-X ResourceProfile
+               StcSearchLocation  - Represents an an IVOA STC-X SearchLocation
+               StcCatalogEntryLocation - Represents an an IVOA STC-X CatalogEntryLocation
+               StcObsDataLocation - Represents an an IVOA STC-X ObsDataLocation
+         SkyFrame  - Celestial coordinate system description
+         SpecFrame - Spectral coordinate system description
+            DSBSpecFrame - Dual sideband spectral coordinate system description
+         TimeFrame - Time coordinate system description
+      GrismMap     - Models the spectral dispersion produced by a grism
+      IntraMap     - Map points using a private transformation function
+      LutMap       - Transform 1-dimensional coordinates using a lookup table
+      MathMap      - Transform coordinates using mathematical expressions
+      MatrixMap    - Map positions by multiplying them by a matrix
+      NormMap      - Normalise coordinates using a supplied Frame
+      PcdMap       - Apply 2-dimensional pincushion/barrel distortion
+      PermMap      - Coordinate permutation Mapping
+      PolyMap      - General N-dimensional polynomial Mapping
+      RateMap      - Calculates an element of a Mapping's Jacobian matrix
+      SelectorMap  - Locates positions within a set of Regions
+      ShiftMap     - Shifts each axis by a constant amount
+      SlaMap       - Sequence of celestial coordinate conversions
+      SpecMap      - Sequence of spectral coordinate conversions
+      SphMap       - Map 3-d Cartesian to 2-d spherical coordinates
+      SwitchMap    - Encapuslates a set of alternate Mappings
+      TimeMap      - Sequence of time coordinate conversions
+      TranMap      - Combine fwd. and inv. transformations from two Mappings
+      UnitMap      - Unit (null) Mapping
+      UnitNormMap  - Converts a vector to a unit vector plus length
+      WcsMap       - Implement a FITS-WCS sky projection
+      WinMap       - Match windows by scaling and shifting each axis
+      ZoomMap      - Zoom coordinates about the origin
+\end{terminalv}
+\normalsize
+
+\cleardoublepage
+\section{\label{ss:functiondescriptions}AST Routine Descriptions}
+\small
+\sstroutine{
+   AST\_SET
+}{
+   Set attribute values for an Object
+}{
+   \sstdescription{
+      This routine assigns a set of attribute values to an \htmlref{Object}{Object},
+      over-riding any previous values. The attributes and their new
+      values are specified via a character string, which should
+      contain a comma-separated list of the form:
+
+         \texttt{"} attribute\_1 = value\_1, attribute\_2 = value\_2, ... \texttt{"}
+
+      where \texttt{"} attribute\_n\texttt{"}  specifies an attribute name, and the value
+      to the right of each \texttt{"} =\texttt{"}  sign should be a suitable textual
+      representation of the value to be assigned. This value will be
+      interpreted according to the attribute\texttt{'} s data type.
+   }
+   \sstinvocation{
+      CALL AST\_SET( THIS, SETTINGS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         SETTINGS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing a comma-separated list of
+         attribute settings in the form described above.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         CALL AST\_SET( MAP, \texttt{'} \htmlref{Report}{Report} = 1, \htmlref{Zoom}{Zoom} = 25.0\texttt{'} , STATUS )
+      }{
+         Sets the Report attribute for Object MAP to the value 1 and
+         the Zoom attribute to 25.0.
+      }
+      \sstexamplesubsection{
+         CALL AST\_SET( FRAME, \texttt{'} Label( 1 ) =Offset from cluster axis\texttt{'} , STATUS )
+      }{
+         Sets the Label(1) attribute for Object FRAME to a suitable
+         string.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         White space may also surround attribute values, where it will
+         generally be ignored (except for string-valued attributes where
+         it is significant and forms part of the value to be assigned).
+
+         \sstitem
+         To include a literal comma in the value assigned to an attribute,
+         the whole attribute value should be enclosed in quotation markes.
+
+         \sstitem
+         An error will result if an attempt is made to set a value for
+         a read-only attribute.
+      }
+   }
+}
+\sstroutine{
+   AST\_ADDCOLUMN
+}{
+   Add a new column definition to a table
+}{
+   \sstdescription{
+      Adds the definition of a new column to the supplied table. Initially,
+      the column is empty. Values may be added subsequently using the
+      methods of the \htmlref{KeyMap}{KeyMap} class.
+   }
+   \sstinvocation{
+      CALL AST\_ADDCOLUMN( THIS, NAME, TYPE, NDIM, DIMS, UNIT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Table}{Table}.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The column name. Trailing spaces are ignored (all other spaces
+         are significant). The supplied string is converted to upper case.
+      }
+      \sstsubsection{
+         TYPE = INTEGER (Given)
+      }{
+         The data type associated with the column. See \texttt{"} Applicability:\texttt{"}
+         below.
+      }
+      \sstsubsection{
+         NDIM = INTEGER (Given)
+      }{
+         The number of dimensions spanned by the values stored in a single
+         cell of the column. Zero if the column holds scalar values.
+      }
+      \sstsubsection{
+         DIMS( NDIM ) = INTEGER (Given)
+      }{
+         An array holding the the lengths of each of the axes spanned by
+         the values stored in a single cell of the column. Ignored if the
+         column holds scalara values.
+      }
+      \sstsubsection{
+         UNIT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A string specifying the units of the column. Supply a blank
+         string if the column is unitless.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Table
+      }{
+         Tables can hold columns with any of the following data types -
+         AST\_\_INTTYPE (for integer), AST\_\_SINTTYPE (for
+         INTEGER$*$2),
+         AST\_\_BYTETYPE (for
+         bytes),
+         AST\_\_DOUBLETYPE (for double
+         precision floating point), AST\_\_FLOATTYPE (for single
+         precision floating point), AST\_\_STRINGTYPE (for character string),
+         AST\_\_OBJECTTYPE (for AST \htmlref{Object}{Object} pointer), AST\_\_POINTERTYPE (for
+         arbitrary C pointer) or AST\_\_UNDEFTYPE (for undefined values
+         created by
+         \htmlref{AST\_MAPPUTU}{AST\_MAPPUTU}).
+      }
+      \sstsubsection{
+         \htmlref{FitsTable}{FitsTable}
+      }{
+         FitsTables can hold columns with any of the following data types -
+         AST\_\_INTTYPE (for integer), AST\_\_SINTTYPE (for
+         INTEGER$*$2),
+         AST\_\_BYTETYPE (for
+         bytes),
+         AST\_\_DOUBLETYPE (for double
+         precision floating point), AST\_\_FLOATTYPE (for single
+         precision floating point), AST\_\_STRINGTYPE (for character string).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This
+         routine
+         returns without action if a column already exists in the Table
+         with the supplied name and properties. However an error is
+         reported if any of the properties differ.
+      }
+   }
+}
+\sstroutine{
+   AST\_ADDFRAME
+}{
+   Add a Frame to a FrameSet to define a new coordinate system
+}{
+   \sstdescription{
+      This routine adds a new \htmlref{Frame}{Frame} and an associated \htmlref{Mapping}{Mapping} to a
+      \htmlref{FrameSet}{FrameSet} so as to define a new coordinate system, derived from
+      one which already exists within the FrameSet. The new Frame then
+      becomes the FrameSet\texttt{'} s current Frame.
+
+      This routine
+      may also be used to merge two FrameSets, or to append extra axes
+      to every Frame in a FrameSet.
+   }
+   \sstinvocation{
+      CALL AST\_ADDFRAME( THIS, IFRAME, MAP, FRAME, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         IFRAME = INTEGER (Given)
+      }{
+         The index of the Frame within the FrameSet which describes
+         the coordinate system upon which the new one is to be based.
+         This value should lie in the range from 1 to the number of
+         Frames already in the FrameSet (as given by its \htmlref{Nframe}{Nframe}
+         attribute). As a special case, AST\_\_ALLFRAMES may be supplied,
+         in which case the axes defined by the supplied Frame are appended
+         to every Frame in the FrameSet (see the Notes section for details).
+      }
+      \sstsubsection{
+         MAP = INTEGER (Given)
+      }{
+         Pointer to a Mapping which describes how to convert
+         coordinates from the old coordinate system (described by the
+         Frame with index IFRAME) into coordinates in the new
+         system. The Mapping\texttt{'} s forward transformation should perform
+         this conversion, and its inverse transformation should
+         convert in the opposite direction. The supplied Mapping is ignored
+         if parameter IFRAME is equal to AST\_\_ALLFRAMES.
+      }
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         Pointer to a Frame that describes the new coordinate system.
+         Any class of Frame may be supplied (including Regions and
+         FrameSets).
+
+         This routine may also be used to merge two FrameSets by
+         supplying a pointer to a second FrameSet for this argument
+         (see the Notes section for details).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Deep copies of the supplied
+         MAPPING and FRAME
+         objects are stored within the modified FrameSet. So any changes made
+         to the FrameSet after calling this method will have no effect on the
+         supplied Mapping and Frame objects.
+
+         \sstitem
+         A value of AST\_\_BASE or AST\_\_CURRENT may be given for the
+         IFRAME argument to specify the base Frame or the current
+         Frame respectively.
+
+         \sstitem
+         This routine sets the value of the \htmlref{Current}{Current} attribute for the
+         FrameSet so that the new Frame subsequently becomes the current
+         Frame.
+
+         \sstitem
+         The number of input coordinate values accepted by the supplied
+         Mapping (its \htmlref{Nin}{Nin} attribute) must match the number of axes in the
+         Frame identified by the IFRAME argument. Similarly, the
+         number of output coordinate values generated by this Mapping
+         (its \htmlref{Nout}{Nout} attribute) must match the number of axes in the new
+         Frame.
+
+         \sstitem
+         As a special case, if a pointer to a FrameSet is given for the
+         FRAME argument, this is treated as a request to merge a pair of
+         FrameSets.  This is done by appending all the new Frames (in the
+         FRAME FrameSet) to the original FrameSet, while preserving
+         their order and retaining all the inter-relationships
+         (i.e. Mappings) between them. The two sets of Frames are
+         inter-related within the merged FrameSet by using the Mapping
+         supplied. This should convert between the Frame identified by
+         the IFRAME argument (in the original FrameSet) and the current
+         Frame of the FRAME FrameSet. This latter Frame becomes the
+         current Frame in the merged FrameSet.
+
+         \sstitem
+         As another special case, if a value of AST\_\_ALLFRAMES is supplied
+         for parameter
+         IFRAME,
+         then the supplied Mapping is ignored, and the axes defined by the
+         supplied Frame are appended to each Frame in the FrameSet. In detail,
+         each Frame in the FrameSet is replaced by a \htmlref{CmpFrame}{CmpFrame} containing the
+         original Frame and the Frame specified by parameter
+         FRAME.
+         In addition, each Mapping in the FrameSet is replaced by a \htmlref{CmpMap}{CmpMap}
+         containing the original Mapping and a \htmlref{UnitMap}{UnitMap} in parallel. The Nin and
+         Nout attributes of the UnitMap are set equal to the number of axes
+         in the supplied Frame. Each new CmpMap is simplified using
+         \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}
+         before being stored in the FrameSet.
+      }
+   }
+}
+\sstroutine{
+   AST\_ADDPARAMETER
+}{
+   Add a new global parameter definition to a table
+}{
+   \sstdescription{
+      Adds the definition of a new global parameter to the supplied
+      table. Note, this does not store a value for the parameter. To get
+      or set the parameter value, the methods of the paremt \htmlref{KeyMap}{KeyMap} class
+      should be used, using the name of the parameter as the key.
+   }
+   \sstinvocation{
+      CALL AST\_ADDPARAMETER( THIS, NAME, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Table}{Table}.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The parameter name. Trailing spaces are ignored (all other spaces
+         are significant). The supplied string is converted to upper case.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Unlike columns, the definition of a parameter does not specify its type,
+         size or dimensionality.
+      }
+   }
+}
+\sstroutine{
+   AST\_ADDVARIANT
+}{
+   Store a new variant Mapping for the current Frame in a FrameSet
+}{
+   \sstdescription{
+      This routine
+      allows a new variant \htmlref{Mapping}{Mapping} to be stored with the current \htmlref{Frame}{Frame}
+      in a \htmlref{FrameSet}{FrameSet}. See the \texttt{"} \htmlref{Variant}{Variant}\texttt{"}  attribute for more details. It can
+      also be used to rename the currently selected variant Mapping.
+   }
+   \sstinvocation{
+      CALL AST\_ADDVARIANT( THIS, MAP, NAME, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         MAP = INTEGER (Given)
+      }{
+         Pointer to a Mapping which describes how to convert
+         coordinates from the current Frame to the new variant of the
+         current Frame. If
+         AST\_\_NULL
+         is supplied, then the name associated with the currently selected
+         variant of the current Frame is set to the value supplied for
+         NAME, but no new variant is added.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The name to associate with the new variant Mapping (or the currently
+         selected variant Mapping if
+         MAP is AST\_\_NULL).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The newly added Variant becomes the current variant on exit (this is
+         equivalent to setting the Variant attribute to the value supplied for
+         NAME).
+
+         \sstitem
+         An error is reported if a variant with the supplied name already
+         exists in the current Frame.
+
+         \sstitem
+         An error is reported if the current Frame is a mirror for the
+         variant Mappings in another Frame. This is only the case if the
+         \htmlref{AST\_MIRRORVARIANTS}{AST\_MIRRORVARIANTS} routine
+         has been called to make the current Frame act as a mirror.
+      }
+   }
+}
+\sstroutine{
+   AST\_ANGLE
+}{
+   Calculate the angle subtended by two points at a third point
+}{
+   \sstdescription{
+      This routine
+      finds the angle at point B between the line joining points A and B,
+      and the line joining points C and B. These lines will in fact be
+      geodesic curves appropriate to the \htmlref{Frame}{Frame} in use. For instance, in
+      \htmlref{SkyFrame}{SkyFrame}, they will be great circles.
+   }
+   \sstinvocation{
+      RESULT = AST\_ANGLE( THIS, A, B, C, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         A( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute) containing the coordinates of the first point.
+      }
+      \sstsubsection{
+         B( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (Naxes attribute) containing the coordinates of the second point.
+      }
+      \sstsubsection{
+         C( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (Naxes attribute) containing the coordinates of the third point.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_ANGLE = DOUBLE PRECISION
+      }{
+         The angle in radians, from the line AB to the line CB. If the
+         Frame is 2-dimensional, it will be in the range \$$\backslash$pm $\backslash$pi\$,
+         and positive rotation is in the same sense as rotation from
+         the positive direction of axis 2 to the positive direction of
+         axis 1. If the Frame has more than 2 axes, a positive value will
+         always be returned in the range zero to \$$\backslash$pi\$.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of AST\_\_BAD will also be returned if points A and B are
+         co-incident, or if points B and C are co-incident.
+
+         \sstitem
+         A value of AST\_\_BAD will also be returned if this function is
+         invoked with STATUS set to an error value, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_ANNUL
+}{
+   Annul a pointer to an Object
+}{
+   \sstdescription{
+      This routine annuls a pointer to an \htmlref{Object}{Object} so that it is no
+      longer recognised as a valid pointer by the AST library. Any
+      resources associated with the pointer are released and made
+      available for re-use.
+
+      This routine also decrements the Object\texttt{'} s \htmlref{RefCount}{RefCount} attribute by
+      one. If this attribute reaches zero (which happens when the last
+      pointer to the Object is annulled), then the Object is deleted.
+   }
+   \sstinvocation{
+      CALL AST\_ANNUL( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given and Returned)
+      }{
+         The Object pointer to be annulled. A null pointer value (AST\_\_NULL)
+         is always returned.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine attempts to execute even if STATUS is set to an
+         error value
+         on entry, although no further error report will be
+         made if it subsequently fails under these circumstances. In
+         particular, it will fail if the pointer suppled is not valid,
+         but this will only be reported if the error status is clear on
+         entry.
+      }
+   }
+}
+\sstroutine{
+   AST\_AXANGLE
+}{
+   Returns the angle from an axis, to a line through two points
+}{
+   \sstdescription{
+      This routine
+      finds the angle, as seen from point A, between the positive
+      direction of a specified axis, and the geodesic curve joining point
+      A to point B.
+   }
+   \sstinvocation{
+      RESULT = AST\_AXANGLE( THIS, A, B, AXIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Frame}{Frame}.
+      }
+      \sstsubsection{
+         A( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute) containing the coordinates of the first point.
+      }
+      \sstsubsection{
+         B( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (Naxes attribute) containing the coordinates of the second point.
+      }
+      \sstsubsection{
+         AXIS = INTEGER (Given)
+      }{
+         The number of the Frame axis from which the angle is to be
+         measured (axis numbering starts at 1 for the first axis).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_AXANGLE = DOUBLE PRECISION
+      }{
+         The angle in radians, from the positive direction of the
+         specified axis, to the line AB. If the Frame is 2-dimensional,
+         it will be in the range [-PI/2,$+$PI/2], and positive rotation is in
+         the same sense as rotation from the positive direction of axis 2
+         to the positive direction of axis 1. If the Frame has more than 2
+         axes, a positive value will always be returned in the range zero
+         to PI.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The geodesic curve used by this routine is the path of
+         shortest distance between two points, as defined by the
+         \htmlref{AST\_DISTANCE}{AST\_DISTANCE} function.
+
+         \sstitem
+         This function will return \texttt{"} bad\texttt{"}  coordinate values (AST\_\_BAD)
+         if any of the input coordinates has this value, or if the require
+         position angle is undefined.
+      }
+   }
+}
+\sstroutine{
+   AST\_AXDISTANCE
+}{
+   Find the distance between two axis values
+}{
+   \sstdescription{
+      This routine returns a signed value representing the axis increment
+      from axis value v1 to axis value v2.
+
+      For a simple \htmlref{Frame}{Frame}, this is a trivial operation returning the
+      difference between the two axis values. But for other derived classes
+      of Frame (such as a \htmlref{SkyFrame}{SkyFrame}) this is not the case.
+   }
+   \sstinvocation{
+      RESULT = AST\_AXDISTANCE( THIS, AXIS, V1, V2, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         AXIS = INTEGER (Given)
+      }{
+         The index of the axis to which the supplied values refer. The
+         first axis has index 1.
+      }
+      \sstsubsection{
+         V1 = DOUBLE PRECISION (Given)
+      }{
+         The first axis value.
+      }
+      \sstsubsection{
+         V2 = DOUBLE PRECISION (Given)
+      }{
+         The second axis value.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_AXDISTANCE = DOUBLE PRECISION
+      }{
+         The distance from the first to the second axis value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function will return a \texttt{"} bad\texttt{"}  result value (AST\_\_BAD) if
+         any of the input values has this value.
+
+         \sstitem
+         A \texttt{"} bad\texttt{"}  value will also be returned if this function is
+         invoked with STATUS set to an error value, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_AXNORM
+}{
+   Normalise an array of axis values
+}{
+   \sstdescription{
+      This routine
+      modifies a supplied array of axis values so that they are normalised
+      in the manner indicated by
+      argument OPER.
+
+      No normalisation is possible for a simple \htmlref{Frame}{Frame} and so the supplied
+      values are returned unchanged. However, this may not be the case for
+      specialised sub-classes of Frame. For instance, a \htmlref{SkyFrame}{SkyFrame} has a
+      discontinuity at zero longitude and so a longitude value can be
+      expressed in the range [-Pi,$+$PI] or the range [0,2$*$PI]. See the
+      \texttt{"} Applicability:\texttt{"}  section below for details.
+   }
+   \sstinvocation{
+      CALL AST\_AXNORM( THIS, AXIS, OPER, NVAL, VALUES, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         AXIS = INTEGER (Given)
+      }{
+         The index of the axis to which the supplied values refer. The
+         first axis has index 1.
+      }
+      \sstsubsection{
+         OPER = INTEGER (Given)
+      }{
+         Indicates the type of normalisation to be applied. If zero is
+         supplied, the normalisation will be the same as that performed by
+         routine \htmlref{AST\_NORM}{AST\_NORM}.
+         If 1 is supplied, the normalisation will be chosen automatically
+         so that the resulting list has the smallest range.
+      }
+      \sstsubsection{
+         NVAL = INTEGER (Given)
+      }{
+         The number of points in the values array.
+      }
+      \sstsubsection{
+         VALUES( NVAL ) = DOUBLE PRECISION (Given and Returned)
+      }{
+         On entry, the axis values to be normalised. Modified on exit to
+         hold the normalised values.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         If OPER
+         is 0, longitude values are returned in the range [0,2$*$PI].
+         If OPER
+         is 1, longitude values are returned in either the range
+         [0,2$*$PI] or [-PI,PI]. The choice is made so that that the
+         resulting list has the smallest range. Latitude values are
+         always returned in the range [-PI,PI].
+      }
+      \sstsubsection{
+         All other classes of Frame
+      }{
+         The supplied axis values are returned unchanged.
+      }
+   }
+}
+\sstroutine{
+   AST\_AXOFFSET
+}{
+   Add an increment onto a supplied axis value
+}{
+   \sstdescription{
+      This routine returns an axis value formed by adding a signed axis
+      increment onto a supplied axis value.
+
+      For a simple \htmlref{Frame}{Frame}, this is a trivial operation returning the
+      sum of the two supplied values. But for other derived classes
+      of Frame (such as a \htmlref{SkyFrame}{SkyFrame}) this is not the case.
+   }
+   \sstinvocation{
+      RESULT = AST\_AXOFFSET( THIS, AXIS, V1, DIST, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         AXIS = INTEGER (Given)
+      }{
+         The index of the axis to which the supplied values refer. The
+         first axis has index 1.
+      }
+      \sstsubsection{
+         V1 = DOUBLE PRECISION (Given)
+      }{
+         The original axis value.
+      }
+      \sstsubsection{
+         DIST = DOUBLE PRECISION (Given)
+      }{
+         The axis increment to add to the original axis value.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_AXOFFSET = DOUBLE PRECISION
+      }{
+         The incremented axis value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function will return a \texttt{"} bad\texttt{"}  result value (AST\_\_BAD) if
+         any of the input values has this value.
+
+         \sstitem
+         A \texttt{"} bad\texttt{"}  value will also be returned if this function is
+         invoked with STATUS set to an error value, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_BBUF
+}{
+   Begin a new graphical buffering context
+}{
+   \sstdescription{
+      This routine
+      starts a new graphics buffering context. A matching call to the
+      routine \htmlref{AST\_EBUF}{AST\_EBUF}
+      should be used to end the context.
+   }
+   \sstinvocation{
+      CALL AST\_BBUF( THIS STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Plot}{Plot}.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The nature of the buffering is determined by the underlying
+         graphics system (as defined by the current grf module). Each call
+         to this routine
+         to this routine
+         simply invokes the astGBBuf function in the grf module.
+      }
+   }
+}
+\sstroutine{
+   AST\_BEGIN
+}{
+   Begin a new AST context
+}{
+   \sstdescription{
+      This routine begins a new AST context. Any \htmlref{Object}{Object} pointers
+      created within this context will be annulled when it is later
+      ended using \htmlref{AST\_END}{AST\_END} (just as if \htmlref{AST\_ANNUL}{AST\_ANNUL} had been invoked),
+      unless they have first been exported using \htmlref{AST\_EXPORT}{AST\_EXPORT} or rendered
+      exempt using \htmlref{AST\_EXEMPT}{AST\_EXEMPT}. If
+      annulling a pointer causes an Object\texttt{'} s \htmlref{RefCount}{RefCount} attribute to
+      fall to zero (which happens when the last pointer to it is
+      annulled), then the Object will be deleted.
+   }
+   \sstinvocation{
+      CALL AST\_BEGIN( STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine attempts to execute even if STATUS is set to an
+         error value.
+
+         \sstitem
+         Contexts delimited by AST\_BEGIN and AST\_END may be nested to any
+         depth.
+      }
+   }
+}
+\sstroutine{
+   AST\_BORDER
+}{
+   Draw a border around valid regions of a Plot
+}{
+   \sstdescription{
+      This function draws a (line) border around regions of the
+      plotting area of a \htmlref{Plot}{Plot} which correspond to valid, unclipped
+      physical coordinates. For example, when plotting using an
+      all-sky map projection, this function could be used to draw the
+      boundary of the celestial sphere when it is projected on to the
+      plotting surface.
+
+      If the entire plotting area contains valid, unclipped physical
+      coordinates, then the boundary will just be a rectangular box
+      around the edges of the plotting area.
+
+      If the Plot is a \htmlref{Plot3D}{Plot3D}, this method is applied individually to
+      each of the three 2D Plots encapsulated within the Plot3D (each of
+      these Plots corresponds to a single 2D plane in the 3D graphics
+      system). In addition, if the entire plotting volume has valid
+      coordinates in the 3D current \htmlref{Frame}{Frame} of the Plot3D, then additional
+      lines are drawn along the edges of the 3D plotting volume so that
+      the entire plotting volume is enclosed within a cuboid grid.
+   }
+   \sstinvocation{
+      RESULT = AST\_BORDER( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_BORDER = LOGICAL
+      }{
+         .FALSE. is returned if the plotting space is completely filled by
+         valid, unclipped physical coordinates (so that only a
+         rectangular box was drawn around the edge). Otherwise, .TRUE. is
+         returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of .FALSE. will be returned if this function is invoked
+         with STATUS set to an error value, or if it should fail for any
+         reason.
+
+         \sstitem
+         An error results if either the current Frame or the base Frame
+         of the Plot is not 2-dimensional or (for a Plot3D) 3-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the base
+         and current Frames of the Plot is not defined (i.e. the Plot\texttt{'} s
+         \htmlref{TranForward}{TranForward} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   AST\_BOUNDINGBOX
+}{
+   Return a bounding box for previously drawn graphics
+}{
+   \sstdescription{
+      This routine returns the bounds of a box which just encompasess the
+      graphics produced by the previous call to any of the \htmlref{Plot}{Plot} methods
+      which produce graphical output. If no such previous call has yet
+      been made, or if the call failed for any reason, then the bounding box
+      returned by this routine is undefined.
+   }
+   \sstinvocation{
+      CALL AST\_BOUNDINGBOX( THIS, LBND, UBND, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         LBND( 2 ) = REAL (Returned)
+      }{
+         A two element array in which is returned the lower limits of the
+         bounding box on each of the two axes of the graphics coordinate
+         system (the base \htmlref{Frame}{Frame} of the Plot).
+      }
+      \sstsubsection{
+         UBND( 2 ) = REAL (Returned)
+      }{
+         A two element array in which is returned the upper limits of the
+         bounding box on each of the two axes of the graphics coordinate
+         system (the base Frame of the Plot).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error results if the base Frame of the Plot is not
+         2-dimensional.
+      }
+   }
+}
+\sstroutine{
+   AST\_BOX
+}{
+   Create a Box
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Box}{Box} and optionally initialises its
+      attributes.
+
+      The Box class implements a \htmlref{Region}{Region} which represents a box with sides
+      parallel to the axes of a \htmlref{Frame}{Frame} (i.e. an area which encloses a given
+      range of values on each axis). A Box is similar to an \htmlref{Interval}{Interval}, the
+      only real difference being that the Interval class allows some axis
+      limits to be unspecified. Note, a Box will only look like a box if
+      the Frame geometry is approximately flat. For instance, a Box centred
+      close to a pole in a \htmlref{SkyFrame}{SkyFrame} will look more like a fan than a box
+      (the \htmlref{Polygon}{Polygon} class can be used to create a box-like region close to a
+      pole).
+   }
+   \sstinvocation{
+      RESULT = AST\_BOX( FRAME, FORM, POINT1, POINT2, UNC, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         A pointer to the Frame in which the region is defined. A deep
+         copy is taken of the supplied Frame. This means that any
+         subsequent changes made to the Frame using the supplied pointer
+         will have no effect the Region.
+      }
+      \sstsubsection{
+         FORM = INTEGER (Given)
+      }{
+         Indicates how the box is described by the remaining parameters.
+         A value of zero indicates that the box is specified by a centre
+         position and a corner position. A value of one indicates that the
+         box is specified by a two opposite corner positions.
+      }
+      \sstsubsection{
+         POINT1( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). If
+         FORM
+         is zero, this array should contain the coordinates at the centre of
+         the box.
+         If FORM
+         is one, it should contain the coordinates at the corner of the box
+         which is diagonally opposite the corner specified by
+         POINT2.
+      }
+      \sstsubsection{
+         POINT2( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (Naxes attribute) containing the coordinates at any corner of the
+         box.
+      }
+      \sstsubsection{
+         UNC = INTEGER (Given)
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with the boundary of the Box being created.
+         The uncertainty in any point on the boundary of the Box is found by
+         shifting the supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at
+         the boundary point being considered. The area covered by the
+         shifted uncertainty Region then represents the uncertainty in the
+         boundary position. The uncertainty is assumed to be the same for
+         all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. Box, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Box. Alternatively,
+         a null \htmlref{Object}{Object} pointer (AST\_\_NULL)
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the Box being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{AST\_OVERLAP}{AST\_OVERLAP}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Box. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_BOX = INTEGER
+      }{
+         A pointer to the new Box.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_CHANNEL
+}{
+   Create a Channel
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Channel}{Channel} and optionally initialises
+      its attributes.
+
+      A Channel implements low-level input/output for the AST library.
+      Writing an \htmlref{Object}{Object} to a Channel (using \htmlref{AST\_WRITE}{AST\_WRITE}) will generate a
+      textual representation of that Object, and reading from a
+      Channel (using \htmlref{AST\_READ}{AST\_READ}) will create a new Object from its
+      textual representation.
+
+      Normally, when you use a Channel, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  routines which connect it to an external data store
+      by reading and writing the resulting text. By default, however,
+      a Channel will read from standard input and write to standard
+      output. Alternatively, a Channel can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+   }
+   \sstinvocation{
+      RESULT = AST\_CHANNEL( SOURCE, SINK, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         SOURCE = SUBROUTINE (Given)
+      }{
+         A source routine, which is a subroutine which takes a single
+         integer error status argument.   If no value has been set
+         for the SourceFile attribute, this routine will be used by
+         the Channel to obtain lines of input text. On each
+         invocation, it should read the next input line from some
+         external data store, and then return the resulting text to
+         the AST library by calling \htmlref{AST\_PUTLINE}{AST\_PUTLINE}. It should supply a
+         negative line length when there are no more lines to read.
+         If an error occurs, it should set its own error status
+         argument to an error value before returning.
+
+         If the null routine AST\_NULL is suppied as the SOURCE value,
+         and no value has been set for the SourceFile attribute,
+         the Channel will read from standard input instead.
+      }
+      \sstsubsection{
+         SINK = SUBROUTINE (Given)
+      }{
+         A sink routine, which is a subroutine which takes a single
+         integer error status argument.  If no value has been set
+         for the SinkFile attribute, this routine will be used by
+         the Channel to deliver lines of output text. On each
+         invocation, it should obtain the next output line from the
+         AST library by calling \htmlref{AST\_GETLINE}{AST\_GETLINE}, and then deliver the
+         resulting text to some external data store.  If an error
+         occurs, it should set its own error status argument to an
+         error value before returning.
+
+         If the null routine AST\_NULL is suppied as the SINK value,
+         and no value has been set for the SinkFile attribute,
+         the Channel will write to standard output instead.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Channel. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_CHANNEL = INTEGER
+      }{
+         A pointer to the new Channel.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The names of the routines supplied for the SOURCE and SINK
+         arguments should appear in EXTERNAL statements in the Fortran
+         routine which invokes AST\_CHANNEL. However, this is not generally
+         necessary for the null routine AST\_NULL (so long as the AST\_PAR
+         include file has been used).
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+
+         \sstitem
+         Note that the null routine AST\_NULL (one underscore) is
+         different to AST\_\_NULL (two underscores), which is the null Object
+         pointer.
+      }
+   }
+}
+\sstroutine{
+   AST\_CIRCLE
+}{
+   Create a Circle
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Circle}{Circle} and optionally initialises its
+      attributes.
+
+      A Circle is a \htmlref{Region}{Region} which represents a circle or sphere within the
+      supplied \htmlref{Frame}{Frame}.
+   }
+   \sstinvocation{
+      RESULT = AST\_CIRCLE( FRAME, FORM, CENTRE, POINT, UNC, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         A pointer to the Frame in which the region is defined. A deep
+         copy is taken of the supplied Frame. This means that any
+         subsequent changes made to the Frame using the supplied pointer
+         will have no effect the Region.
+      }
+      \sstsubsection{
+         FORM = INTEGER (Given)
+      }{
+         Indicates how the circle is described by the remaining parameters.
+         A value of zero indicates that the circle is specified by a
+         centre position and a position on the circumference. A value of one
+         indicates that the circle is specified by a centre position and a
+         scalar radius.
+      }
+      \sstsubsection{
+         CENTRE( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute) containing the coordinates at the centre of
+         the circle or sphere.
+      }
+      \sstsubsection{
+         POINT( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         If FORM
+         is zero, then this array should have one element for each Frame
+         axis (Naxes attribute), and should be supplied holding the
+         coordinates at a point on the circumference of the circle or sphere.
+         If FORM
+         is one, then this array should have one element only which should
+         be supplied holding the scalar radius of the circle or sphere,
+         as a geodesic distance within the Frame.
+      }
+      \sstsubsection{
+         UNC = INTEGER (Given)
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with the boundary of the Circle being created.
+         The uncertainty in any point on the boundary of the Circle is found by
+         shifting the supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at
+         the boundary point being considered. The area covered by the
+         shifted uncertainty Region then represents the uncertainty in the
+         boundary position. The uncertainty is assumed to be the same for
+         all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. \htmlref{Box}{Box}, Circle, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Circle. Alternatively,
+         a null \htmlref{Object}{Object} pointer (AST\_\_NULL)
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the Circle being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{AST\_OVERLAP}{AST\_OVERLAP}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Circle. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_CIRCLE = INTEGER
+      }{
+         A pointer to the new Circle.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_CIRCLEPARS
+}{
+   Returns the geometric parameters of an Circle
+}{
+   \sstdescription{
+      This routine
+      returns the geometric parameters describing the supplied \htmlref{Circle}{Circle}.
+   }
+   \sstinvocation{
+      CALL AST\_CIRCLEPARS( THIS, CENTRE, RADIUS, P1, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         CENTRE( $*$ ) = DOUBLE PRECISION (Returned)
+      }{
+         An array
+         in which to return the coordinates of the Circle centre.
+         The length of this array should be no less than the number of
+         axes in the associated coordinate system.
+      }
+      \sstsubsection{
+         RADIUS = DOUBLE PRECISION (Returned)
+      }{
+         Returned holding the radius of the Circle, as an geodesic
+         distance in the associated coordinate system.
+      }
+      \sstsubsection{
+         P1( $*$ ) = DOUBLE PRECISION (Returned)
+      }{
+         An array
+         in which to return the coordinates of a point on the
+         circumference of the Circle. The length of this array should be
+         no less than the number of axes in the associated coordinate system.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the coordinate system represented by the Circle has been
+         changed since it was first created, the returned parameters refer
+         to the new (changed) coordinate system, rather than the original
+         coordinate system. Note however that if the transformation from
+         original to new coordinate system is non-linear, the shape
+         represented by the supplied Circle object may not be an accurate
+         circle.
+      }
+   }
+}
+\sstroutine{
+   AST\_CLEAR
+}{
+   Clear attribute values for an Object
+}{
+   \sstdescription{
+      This routine clears the values of a specified set of attributes
+      for an \htmlref{Object}{Object}. Clearing an attribute cancels any value that has
+      previously been explicitly set for it, so that the standard
+      default attribute value will subsequently be used instead. This
+      also causes the \htmlref{AST\_TEST}{AST\_TEST} function to return the value .FALSE. for
+      the attribute, indicating that no value has been set.
+   }
+   \sstinvocation{
+      CALL AST\_CLEAR( THIS, ATTRIB, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         ATTRIB = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing a comma-separated list of the
+         names of the attributes to be cleared.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         It does no harm to clear an attribute whose value has not been
+         set.
+
+         \sstitem
+         An error will result if an attempt is made to clear the value
+         of a read-only attribute.
+      }
+   }
+}
+\sstroutine{
+   AST\_CLIP
+}{
+   Set up or remove clipping for a Plot
+}{
+   \sstdescription{
+      This routine defines regions of a \htmlref{Plot}{Plot} which are to be clipped.
+      Any subsequent graphical output created using the Plot will then
+      be visible only within the unclipped regions of the plotting
+      area. See also the \htmlref{Clip}{Clip} attribute.
+   }
+   \sstinvocation{
+      CALL AST\_CLIP( THIS, IFRAME, LBND, UBND, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         IFRAME = INTEGER (Given)
+      }{
+         The index of the \htmlref{Frame}{Frame} within the Plot to which the clipping
+         limits supplied in LBND and UBND (below) refer. Clipping
+         may be applied to any of the coordinate systems associated
+         with a Plot (as defined by the Frames it contains), so this
+         index may take any value from 1 to the number of Frames in
+         the Plot (\htmlref{Nframe}{Nframe} attribute). In addition, the values
+         AST\_\_BASE and AST\_\_CURRENT may be used to specify the base
+         and current Frames respectively.
+
+         For example, a value of AST\_\_CURRENT causes clipping to be
+         performed in physical coordinates, while a value of AST\_\_BASE
+         would clip in graphical coordinates. Clipping may also be
+         removed completely by giving a value of AST\_\_NOFRAME. In this
+         case any clipping bounds supplied (below) are ignored.
+      }
+      \sstsubsection{
+         LBND( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each axis of the clipping Frame
+         (identified by the index IFRAME). This should contain the
+         lower bound, on each axis, of the region which is to remain
+         visible (unclipped).
+      }
+      \sstsubsection{
+         UBND( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each axis of the clipping Frame
+         (identified by the index IFRAME). This should contain the
+         upper bound, on each axis, of the region which is to remain
+         visible (unclipped).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Only one clipping Frame may be active at a time. This routine
+         will deactivate any previously-established clipping Frame before
+         setting up new clipping limits.
+
+         \sstitem
+         The clipping produced by this routine is in addition to that
+         specified by the Clip attribute which occurs at the edges of the
+         plotting area
+         established when the Plot is created (see \htmlref{AST\_PLOT}{AST\_PLOT}). The
+         underlying graphics system may also impose further clipping.
+
+         \sstitem
+         When testing a graphical position for clipping, it is first
+         transformed into the clipping Frame. The resulting coordinate on
+         each axis is then checked against the clipping limits (given by
+         LBND and UBND). By default, a position is clipped if any
+         coordinate lies outside these limits. However, if a non-zero
+         value is assigned to the Plot\texttt{'} s \htmlref{ClipOp}{ClipOp} attribute, then a
+         position is only clipped if the coordinates on all axes lie
+         outside their clipping limits.
+
+         \sstitem
+         If the lower clipping limit exceeds the upper limit for any
+         axis, then the sense of clipping for that axis is reversed (so
+         that coordinate values lying between the limits are clipped
+         instead of those lying outside the limits). To produce a \texttt{"} hole\texttt{"}
+         in a coordinate space (that is, an internal region where nothing
+         is plotted), you should supply all the bounds in reversed order,
+         and set the ClipOp attribute for the Plot to a non-zero value.
+
+         \sstitem
+         Either clipping limit may be set to the value AST\_\_BAD, which
+         is equivalent to setting it to infinity (or minus infinity for a
+         lower bound) so that it is not used.
+
+         \sstitem
+         If a graphical position results in any bad coordinate values
+         (AST\_\_BAD) when transformed into the clipping Frame, then it is
+         treated (for the purposes of producing graphical output) as if
+         it were clipped.
+
+         \sstitem
+         When a Plot is used as a \htmlref{Mapping}{Mapping} to transform points
+         (e.g. using \htmlref{AST\_TRAN2}{AST\_TRAN2}), any clipped output points are assigned
+         coordinate values of AST\_\_BAD.
+
+         \sstitem
+         An error results if the base Frame of the Plot is not
+         2-dimensional.
+      }
+   }
+}
+\sstroutine{
+   AST\_CLONE
+}{
+   Clone (duplicate) an Object pointer
+}{
+   \sstdescription{
+      This function returns a duplicate pointer to an existing
+      \htmlref{Object}{Object}. It also increments the Object\texttt{'} s \htmlref{RefCount}{RefCount} attribute to
+      keep track of how many pointers have been issued.
+
+      Note that this function is NOT equivalent to an assignment
+      statement, as in general the two pointers will not have the same
+      value.
+   }
+   \sstinvocation{
+      RESULT = AST\_CLONE( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Original pointer to the Object.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_CLONE = INTEGER
+      }{
+         A duplicate pointer to the same Object.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_CMPFRAME
+}{
+   Create a CmpFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{CmpFrame}{CmpFrame} and optionally initialises
+      its attributes.
+
+      A CmpFrame is a compound \htmlref{Frame}{Frame} which allows two component Frames
+      (of any class) to be merged together to form a more complex
+      Frame. The axes of the two component Frames then appear together
+      in the resulting CmpFrame (those of the first Frame, followed by
+      those of the second Frame).
+
+      Since a CmpFrame is itself a Frame, it can be used as a
+      component in forming further CmpFrames. Frames of arbitrary
+      complexity may be built from simple individual Frames in this
+      way.
+
+      Also since a Frame is a \htmlref{Mapping}{Mapping}, a CmpFrame can also be used as a
+      Mapping. Normally, a CmpFrame is simply equivalent to a \htmlref{UnitMap}{UnitMap},
+      but if either of the component Frames within a CmpFrame is a \htmlref{Region}{Region}
+      (a sub-class of Frame), then the CmpFrame will use the Region as a
+      Mapping when transforming values for axes described by the Region.
+      Thus input axis values corresponding to positions which are outside the
+      Region will result in bad output axis values.
+   }
+   \sstinvocation{
+      RESULT = AST\_CMPFRAME( FRAME1, FRAME2, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME1 = INTEGER (Given)
+      }{
+         Pointer to the first component Frame.
+      }
+      \sstsubsection{
+         FRAME2 = INTEGER (Given)
+      }{
+         Pointer to the second component Frame.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new CmpFrame. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_CMPFRAME = INTEGER
+      }{
+         A pointer to the new CmpFrame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_CMPMAP
+}{
+   Create a CmpMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{CmpMap}{CmpMap} and optionally initialises
+      its attributes.
+
+      A CmpMap is a compound \htmlref{Mapping}{Mapping} which allows two component
+      Mappings (of any class) to be connected together to form a more
+      complex Mapping. This connection may either be \texttt{"} in series\texttt{"}
+      (where the first Mapping is used to transform the coordinates of
+      each point and the second mapping is then applied to the
+      result), or \texttt{"} in parallel\texttt{"}  (where one Mapping transforms the
+      earlier coordinates for each point and the second Mapping
+      simultaneously transforms the later coordinates).
+
+      Since a CmpMap is itself a Mapping, it can be used as a
+      component in forming further CmpMaps. Mappings of arbitrary
+      complexity may be built from simple individual Mappings in this
+      way.
+   }
+   \sstinvocation{
+      RESULT = AST\_CMPMAP( MAP1, MAP2, SERIES, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         MAP1 = INTEGER (Given)
+      }{
+         Pointer to the first component Mapping.
+      }
+      \sstsubsection{
+         MAP2 = INTEGER (Given)
+      }{
+         Pointer to the second component Mapping.
+      }
+      \sstsubsection{
+         SERIES = LOGICAL (Given)
+      }{
+         If a .TRUE. value is given for this argument, the two
+         component Mappings will be connected in series. A
+         .FALSE. value requests that they are connected in parallel.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new CmpMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_CMPMAP = INTEGER
+      }{
+         A pointer to the new CmpMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the component Mappings are connected in series, then using
+         the resulting CmpMap to transform coordinates will cause the
+         first Mapping to be applied, followed by the second Mapping. If
+         the inverse CmpMap transformation is requested, the two
+         component Mappings will be applied in both the reverse order and
+         the reverse direction.
+
+         \sstitem
+         When connecting two component Mappings in series, the number
+         of output coordinates generated by the first Mapping (its \htmlref{Nout}{Nout}
+         attribute) must equal the number of input coordinates accepted
+         by the second Mapping (its \htmlref{Nin}{Nin} attribute).
+
+         \sstitem
+         If the component Mappings of a CmpMap are connected in
+         parallel, then the first Mapping will be used to transform the
+         earlier input coordinates for each point (and to produce the
+         earlier output coordinates) and the second Mapping will be used
+         simultaneously to transform the remaining input coordinates (to
+         produce the remaining output coordinates for each point). If the
+         inverse transformation is requested, each Mapping will still be
+         applied to the same coordinates, but in the reverse direction.
+
+         \sstitem
+         When connecting two component Mappings in parallel, there is
+         no restriction on the number of input and output coordinates for
+         each Mapping.
+
+         \sstitem
+         Note that the component Mappings supplied are not copied by
+         AST\_CMPMAP (the new CmpMap simply retains a reference to
+         them). They may continue to be used for other purposes, but
+         should not be deleted. If a CmpMap containing a copy of its
+         component Mappings is required, then a copy of the CmpMap should
+         be made using \htmlref{AST\_COPY}{AST\_COPY}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_CMPREGION
+}{
+   Create a CmpRegion
+}{
+   \sstdescription{
+      This function creates a new \htmlref{CmpRegion}{CmpRegion} and optionally initialises
+      its attributes.
+
+      A CmpRegion is a \htmlref{Region}{Region} which allows two component
+      Regions (of any class) to be combined to form a more complex
+      Region. This combination may be performed a boolean AND, OR
+      or XOR (exclusive OR) operator. If the AND operator is
+      used, then a position is inside the CmpRegion only if it is
+      inside both of its two component Regions. If the OR operator is
+      used, then a position is inside the CmpRegion if it is inside
+      either (or both) of its two component Regions. If the XOR operator
+      is used, then a position is inside the CmpRegion if it is inside
+      one but not both of its two component Regions. Other operators can
+      be formed by negating one or both component Regions before using
+      them to construct a new CmpRegion.
+
+      The two component Region need not refer to the same coordinate
+      \htmlref{Frame}{Frame}, but it must be possible for the
+      \htmlref{AST\_CONVERT}{AST\_CONVERT}
+      function to determine a \htmlref{Mapping}{Mapping} between them (an error will be
+      reported otherwise when the CmpRegion is created). For instance,
+      a CmpRegion may combine a Region defined within an ICRS \htmlref{SkyFrame}{SkyFrame}
+      with a Region defined within a Galactic SkyFrame. This is
+      acceptable because the SkyFrame class knows how to convert between
+      these two systems, and consequently the
+      AST\_CONVERT
+      function will also be able to convert between them. In such cases,
+      the second component Region will be mapped into the coordinate Frame
+      of the first component Region, and the Frame represented by the
+      CmpRegion as a whole will be the Frame of the first component Region.
+
+      Since a CmpRegion is itself a Region, it can be used as a
+      component in forming further CmpRegions. Regions of arbitrary
+      complexity may be built from simple individual Regions in this
+      way.
+   }
+   \sstinvocation{
+      RESULT = AST\_CMPREGION( REGION1, REGION2, OPER, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         REGION1 = INTEGER (Given)
+      }{
+         Pointer to the first component Region.
+      }
+      \sstsubsection{
+         REGION2 = INTEGER (Given)
+      }{
+         Pointer to the second component Region. This Region will be
+         transformed into the coordinate Frame of the first region before
+         use. An error will be reported if this is not possible.
+      }
+      \sstsubsection{
+         OPER = INTEGER (Given)
+      }{
+         The boolean operator with which to combine the two Regions. This
+         must be one of the symbolic constants AST\_\_AND, AST\_\_OR or AST\_\_XOR.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new CmpRegion. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_CMPREGION = INTEGER
+      }{
+         A pointer to the new CmpRegion.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If one of the supplied Regions has an associated uncertainty,
+         that uncertainty will also be used for the returned CmpRegion.
+         If both supplied Regions have associated uncertainties, the
+         uncertainty associated with the first Region will be used for the
+         returned CmpRegion.
+
+         \sstitem
+         Deep copies are taken of the supplied Regions. This means that
+         any subsequent changes made to the component Regions using the
+         supplied pointers will have no effect on the CmpRegion.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_COLUMNNAME
+}{
+   Get the name of the column at a given index within the Table
+}{
+   \sstdescription{
+      This function returns a string holding the name of the column with
+      the given index within the \htmlref{Table}{Table}.
+
+      This function is intended primarily as a means of iterating round all
+      the columns in a Table. For this purpose, the number of columns in
+      the Table is given by the \htmlref{Ncolumn}{Ncolumn} attribute of the Table. This function
+      could then be called in a loop, with the index value going from
+      one to Ncolumn.
+
+      Note, the index associated with a column decreases monotonically with
+      the age of the column: the oldest Column in the Table will have index
+      one, and the Column added most recently to the Table will have the
+      largest index.
+   }
+   \sstinvocation{
+      RESULT = AST\_COLUMNNAME( THIS, INDEX, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         INDEX = INTEGER (Given)
+      }{
+         The index into the list of columns. The first column has index
+         one, and the last has index \texttt{"} Ncolumn\texttt{"} .
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_COLUMNNAME = CHARACTER $*$ ( AST\_\_SZCHR )
+      }{
+         The
+         upper case column name.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A blank string will be returned if this function is invoked
+         with STATUS set to an error value, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_COLUMNNULL
+}{
+   Get or set the null value for an integer column of a FITS table
+}{
+   \sstdescription{
+      This function allows a null value to be stored with a named
+      integer-valued column in a \htmlref{FitsTable}{FitsTable}. The supplied null value is
+      assigned to the TNULLn keyword in the FITS header associated with
+      the FitsTable. A value in the named column is then considered to be
+      null if 1) it equals the null value supplied to this function, or
+      2) no value has yet been stored in the cell.
+
+      As well as setting a new null value, this function also returns the
+      previous null value. If no null value has been set previously, a
+      default value will be returned. This default will be an integer
+      value that does not currently occur anywhere within the named column.
+      If no such value can be found, what happens depends on whether the
+      column contains any cells in which no values have yet been stored.
+      If so, an error will be reported. Otherwise (i.e. if there are no
+      null values in the column), an arbitrary value of zero will be
+      returned as the function value, and no TNULLn keyword will be
+      stored in the FITS header.
+
+      A flag is returned indicating if the returned null value was set
+      explicitly by a previous call to this function, or is a default
+      value.
+
+      A second flag is returned indicating if the named column contains
+      any null values (i.e. values equal to the supplied null value, or
+      cells to which no value has yet been assigned).
+   }
+   \sstinvocation{
+      RESULT = AST\_COLUMNNULL( THIS, COLUMN, SET, NEWVAL, WASSET, HASNULL,
+                               STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Table}{Table}.
+      }
+      \sstsubsection{
+         COLUMN = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string holding the name of the column. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         SET = LOGICAL (Given)
+      }{
+         If .TRUE., the value supplied for argument NEWVAL
+         will be stored as the current null value, replacing any value
+         set by a previous call to this function.
+         If .FALSE., the value supplied for argument NEWVAL
+         is ignored and the current null value is left unchanged.
+      }
+      \sstsubsection{
+         NEWVAL = INTEGER (Given)
+      }{
+         The new null value to use. Ignored if
+         SET is .FALSE.
+         An error will be reported if the supplied value is outside the
+         range of values that can be stored in the integer data type
+         associated with the column.
+      }
+      \sstsubsection{
+         WASSET = LOGICAL (Returned)
+      }{
+         .TRUE. will be returned
+         if the returned null value was set previously via an
+         earlier invocation of this function.
+         .FALSE.
+         is returned otherwise. If the named column does not exist, or an
+         error occurs, a value of
+         .FALSE. is returned.
+      }
+      \sstsubsection{
+         HASNULL = LOGICAL (Returned)
+      }{
+         .TRUE. will be returned
+         if and only if the named column currently contains any values
+         equal to the null value on exit (i.e.
+         NEWVAL if SET is .TRUE.
+         or the returned function value otherwise), or contains any empty
+         cells. If the named column does not exist, or an error occurs, a
+         value of
+         .FALSE. is returned.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_COLUMNNULL = INTEGER
+      }{
+         The null value that was in use on entry to this function. If a
+         null value has been set by a previous invocation of this
+         function, it will be returned. Otherwise, if
+         SET is .TRUE., the supplied NEWVAL
+         value is returned. Otherwise, a default value is chosen (if
+         possible) that does not currently occur in the named column. If
+         all available values are in use in the column, an error is
+         reported if and only if the column contains any empty cells.
+         Otherwise, a value of zero is returned. A value of zero is also
+         returned if the named column does not exist, or an error occurs.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The FITS binary table definition allows only integer-valued
+         columns to have an associated null value. This routine will return
+         without action if the column is not integer-valued.
+      }
+   }
+}
+\sstroutine{
+   AST\_COLUMNSHAPE
+}{
+   Returns the shape of the values in a named column
+}{
+   \sstdescription{
+      This routine
+      returns the number of dimensions spaned by each value in a named
+      column of a \htmlref{Table}{Table}, together with the length of each dimension.
+      These are the values supplied when the column was created using
+      \htmlref{AST\_ADDCOLUMN}{AST\_ADDCOLUMN}.
+   }
+   \sstinvocation{
+      CALL AST\_COLUMNSHAPE( THIS, COLUMN, MXDIM, NDIM, DIMS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         COLUMN = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string holding the upper case name of the column. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         MXDIM = INTEGER (Given)
+      }{
+         The length of the
+         DIMS array.
+      }
+      \sstsubsection{
+         NDIM = INTEGER (Returned)
+      }{
+         The
+         number of dimensions spanned by values in the named column.
+         This will be zero if the column contains scalar values.
+      }
+      \sstsubsection{
+         DIMS( MXDIM ) = INTEGER (Returned)
+      }{
+         An
+         array in which to return the length of each dimension. Any
+         excess trailing elements will be filled with the value 1.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No error is reported if the requested column cannot be found in the
+         given Table. A value of zero is returned for
+         NDIM and the supplied values in DIMS
+         are left unchanged.
+
+         \sstitem
+         A value of zero is returned for
+         NDIM
+         if an error occurs.
+      }
+   }
+}
+\sstroutine{
+   AST\_COLUMNSIZE
+}{
+   Get the number of bytes needed to hold a full column of data
+}{
+   \sstdescription{
+      This function returns the number of bytes of memory that must be
+      allocated prior to retrieving the data from a column using
+      \htmlref{AST\_GETCOLUMNDATA}{AST\_GETCOLUMNDATA}.
+   }
+   \sstinvocation{
+      RESULT = AST\_COLUMNSIZE( THIS, COLUMN, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Table}{Table}.
+      }
+      \sstsubsection{
+         COLUMN = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string holding the name of the column. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         \htmlref{AST\_COLUMNNULL}{AST\_COLUMNNULL} = INTEGER
+      }{
+         The number of bytes required to store the column data.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error will be reported if the named column does not exist in
+         the \htmlref{FitsTable}{FitsTable}.
+
+         \sstitem
+         Zero will be returned as the function value in an error occurs.
+      }
+   }
+}
+\sstroutine{
+   AST\_CONVERT
+}{
+   Determine how to convert between two coordinate systems
+}{
+   \sstdescription{
+      This function compares two Frames and determines whether it is
+      possible to convert between the coordinate systems which they
+      represent. If conversion is possible, it returns a \htmlref{FrameSet}{FrameSet}
+      which describes the conversion and which may be used (as a
+      \htmlref{Mapping}{Mapping}) to transform coordinate values in either direction.
+
+      The same function may also be used to determine how to convert
+      between two FrameSets (or between a \htmlref{Frame}{Frame} and a FrameSet, or
+      vice versa). This mode is intended for use when (for example)
+      two images have been calibrated by attaching a FrameSet to each.
+      AST\_CONVERT might then be used to search for a
+      celestial coordinate system that both images have in common, and
+      the result could then be used to convert between the pixel
+      coordinates of both images -- having effectively used their
+      celestial coordinate systems to align them.
+
+      When using FrameSets, there may be more than one possible
+      intermediate coordinate system in which to perform the
+      conversion (for instance, two FrameSets might both have
+      celestial coordinates, detector coordinates, pixel coordinates,
+      etc.). A comma-separated list of coordinate system domains may
+      therefore be given which defines a priority order to use when
+      selecting the intermediate coordinate system.  The path used for
+      conversion must go via an intermediate coordinate system whose
+      \htmlref{Domain}{Domain} attribute matches one of the domains given. If conversion
+      cannot be achieved using the first domain, the next one is
+      considered, and so on, until success is achieved.
+   }
+   \sstinvocation{
+      RESULT = AST\_CONVERT( FROM, TO, DOMAINLIST, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FROM = INTEGER (Given)
+      }{
+         Pointer to a Frame which represents the \texttt{"} source\texttt{"}  coordinate
+         system.  This is the coordinate system in which you already
+         have coordinates available.
+
+         If a FrameSet is given, its current Frame (as determined by
+         its \htmlref{Current}{Current} attribute) is taken to describe the source
+         coordinate system. Note that the \htmlref{Base}{Base} attribute of this
+         FrameSet may be modified by this function to indicate which
+         intermediate coordinate system was used (see under
+         \texttt{"} FrameSets\texttt{"}  in the \texttt{"} Applicability\texttt{"}  section for details).
+      }
+      \sstsubsection{
+         TO = INTEGER (Given)
+      }{
+         Pointer to a Frame which represents the \texttt{"} destination\texttt{"}
+         coordinate system. This is the coordinate system into which
+         you wish to convert your coordinates.
+
+         If a FrameSet is given, its current Frame (as determined by
+         its Current attribute) is taken to describe the destination
+         coordinate system. Note that the Base attribute of this
+         FrameSet may be modified by this function to indicate which
+         intermediate coordinate system was used (see under
+         \texttt{"} FrameSets\texttt{"}  in the \texttt{"} Applicability\texttt{"}  section for details).
+      }
+      \sstsubsection{
+         DOMAINLIST = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing a
+         comma-separated list of Frame domains. This may be used to
+         define a priority order for the different intermediate
+         coordinate systems that might be used to perform the
+         conversion.
+
+         The function will first try to obtain a conversion by making
+         use only of an intermediate coordinate system whose Domain
+         attribute matches the first domain in this list. If this
+         fails, the second domain in the list will be used, and so on,
+         until conversion is achieved. A blank domain (e.g. two
+         consecutive commas) indicates that all coordinate systems
+         should be considered, regardless of their domains.
+
+         This list is case-insensitive and all white space is ignored.
+         If you do not wish to restrict the domain in this way,
+         you should supply a blank string. This is normally
+         appropriate if either of the source or destination coordinate
+         systems are described by Frames (rather than FrameSets),
+         since there is then usually only one possible choice of
+         intermediate coordinate system.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{DSBSpecFrame}{DSBSpecFrame}
+      }{
+         If the \htmlref{AlignSideBand}{AlignSideBand} attribute is non-zero, alignment occurs in the
+         upper sideband expressed within the spectral system and standard of
+         rest given by attributes \htmlref{AlignSystem}{AlignSystem} and \htmlref{AlignStdOfRest}{AlignStdOfRest}. If
+         AlignSideBand is zero, the two DSBSpecFrames are aligned as if
+         they were simple SpecFrames (i.e. the \htmlref{SideBand}{SideBand} is ignored).
+      }
+      \sstsubsection{
+         Frame
+      }{
+         This function applies to all Frames. Alignment occurs within the
+         coordinate system given by attribute AlignSystem.
+      }
+      \sstsubsection{
+         FrameSet
+      }{
+         If either of the FROM or TO arguments is a pointer to a
+         FrameSet, then AST\_CONVERT will attempt to convert from the
+         coordinate system described by the current Frame of the FROM
+         FrameSet to that described by the current Frame of the TO
+         FrameSet.
+
+         To achieve this, it will consider all of the Frames within
+         each FrameSet as a possible way of reaching an intermediate
+         coordinate system that can be used for the conversion. There
+         is then the possibility that more than one conversion path
+         may exist and, unless the choice is sufficiently restricted
+         by the DOMAINLIST string, the sequence in which the Frames
+         are considered can be important. In this case, the search
+         for a conversion path proceeds as follows:
+         \sstitemlist{
+
+            \sstitem
+            Each field in the DOMAINLIST string is considered in turn.
+
+            \sstitem
+            The Frames within each FrameSet are considered in a
+            specific order: (1) the base Frame is always considered
+            first, (2) after this come all the other Frames in
+            Frame-index order (but omitting the base and current Frames),
+            (3) the current Frame is always considered last.  However, if
+            either FrameSet\texttt{'} s \htmlref{Invert}{Invert} attribute is set to a non-zero value
+            (so that the FrameSet is inverted), then its Frames are
+            considered in reverse order. (Note that this still means that
+            the base Frame is considered first and the current Frame
+            last, because the Invert value will also cause these Frames
+            to swap places.)
+
+            \sstitem
+            All source Frames are first considered (in the appropriate
+            order) for conversion to the first destination Frame. If no
+            suitable intermediate coordinate system emerges, they are
+            then considered again for conversion to the second
+            destination Frame (in the appropriate order), and so on.
+
+            \sstitem
+            Generally, the first suitable intermediate coordinate
+            system found is used. However, the overall Mapping between
+            the source and destination coordinate systems is also
+            examined.  Preference is given to cases where both the
+            forward and inverse transformations are defined (as indicated
+            by the \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse} attributes). If only one
+            transformation is defined, the forward one is preferred.
+
+            \sstitem
+            If the domain of the intermediate coordinate system matches
+            the current DOMAINLIST field, the conversion path is
+            accepted. Otherwise, the next DOMAINLIST field is considered
+            and the process repeated.
+
+         }
+         If conversion is possible, the Base attributes of the two
+         FrameSets will be modified on exit to identify the Frames
+         used to access the intermediate coordinate system which was
+         finally accepted.
+
+         Note that it is possible to force a particular Frame within a
+         FrameSet to be used as the basis for the intermediate
+         coordinate system, if it is suitable, by (a) focussing
+         attention on
+         it by specifying its domain in the DOMAINLIST string, or (b)
+         making it the base Frame, since this is always considered
+         first.
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         Alignment occurs within the spectral system and standard of rest
+         given by attributes AlignSystem and AlignStdOfRest.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         Alignment occurs within the time system and time scale given by
+         attributes AlignSystem and \htmlref{AlignTimeScale}{AlignTimeScale}.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_CONVERT = INTEGER
+      }{
+         If the requested coordinate conversion is possible, the
+         function returns a pointer to a FrameSet which describes the
+         conversion. Otherwise, a null \htmlref{Object}{Object} pointer (AST\_\_NULL) is
+         returned without error.
+
+         If a FrameSet is returned, it will contain two Frames. Frame
+         number 1 (its base Frame) will describe the source coordinate
+         system, corresponding to the FROM argument. Frame number 2
+         (its current Frame) will describe the destination coordinate
+         system, corresponding to the TO argument. The Mapping
+         which inter-relates these two Frames will perform the
+         required conversion between their respective coordinate
+         systems.
+
+         Note that a FrameSet may be used both as a Mapping and as a
+         Frame.  If the result is used as a Mapping (e.g. with
+         \htmlref{AST\_TRAN2}{AST\_TRAN2}), then it provides a means of converting coordinates
+         from the source to the destination coordinate system (or
+         vice versa if its inverse transformation is selected). If it
+         is used as a Frame, its attributes will describe the
+         destination coordinate system.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         CVT = AST\_CONVERT( A, B, \texttt{'}  \texttt{'} , STATUS )
+      }{
+         Attempts to convert between the coordinate systems represented
+         by A and B (assumed to be Frames). If successful, a FrameSet
+         is returned via the CVT pointer which may be used to apply the
+         conversion to sets of coordinates (e.g. using AST\_TRAN2).
+      }
+      \sstexamplesubsection{
+         CVT = AST\_CONVERT( \htmlref{AST\_SKYFRAME}{AST\_SKYFRAME}( \texttt{'}  \texttt{'} , STATUS ), AST\_SKYFRAME( \texttt{'} \htmlref{Equinox}{Equinox}=2005\texttt{'} , STATUS ), \texttt{'}  \texttt{'} , STATUS )
+      }{
+         Creates a FrameSet which describes precession in the default
+         FK5 celestial coordinate system between equinoxes J2000 (also
+         the default) and J2005. The returned CVT pointer may then
+         be passed to AST\_TRAN2 to apply this precession correction to
+         any number of coordinate values given in radians.
+
+         Note that the returned FrameSet also contains information
+         about how to format coordinate values. This means that
+         setting its \htmlref{Report}{Report} attribute to 1 is a simple way to obtain
+         printed output (formatted in sexagesimal notation) to show
+         the coordinate values before and after conversion.
+      }
+      \sstexamplesubsection{
+         CVT = AST\_CONVERT( A, B, \texttt{'} SKY,DETECTOR,\texttt{'} , STATUS )
+      }{
+         Attempts to convert between the coordinate systems
+         represented by the current Frames of A and B
+         (now assumed to be FrameSets), via the intermediate \texttt{"} SKY\texttt{"}
+         coordinate system.  This, by default, is the Domain
+         associated with a celestial coordinate system represented by
+         a \htmlref{SkyFrame}{SkyFrame}.
+
+         If this fails (for example, because either FrameSet lacks
+         celestial coordinate information), then the user-defined
+         \texttt{"} DETECTOR\texttt{"}  coordinate system is used instead. If this also
+         fails, then all other possible ways of achieving conversion
+         are considered before giving up.
+
+         The returned pointer CVT indicates whether conversion was
+         possible and will have the value AST\_\_NULL if it was not. If
+         conversion was possible, CVT will point at a new FrameSet
+         describing the conversion.
+
+         The Base attributes of the two FrameSets
+         will be set by AST\_CONVERT to indicate which of their Frames was
+         used for the intermediate coordinate system. This means that
+         you can subsequently determine which coordinate system was
+         used by enquiring the Domain attribute of either base Frame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Mapping represented by the returned FrameSet results in
+         alignment taking place in the coordinate system specified by the
+         AlignSystem attribute of the TO Frame. See the description of the
+         AlignSystem attribute for further details.
+
+         \sstitem
+         When aligning (say) two images, which have been calibrated by
+         attaching FrameSets to them, it is usually necessary to convert
+         between the base Frames (representing \texttt{"} native\texttt{"}  pixel
+         coordinates) of both FrameSets. This may be achieved by
+         inverting the FrameSets (e.g. using astInvert) so as to
+         interchange their base and current Frames before using
+         astConvert.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_CONVEX$<$X$>$
+}{
+   Create a new Polygon representing the convex hull of a 2D data grid
+}{
+   \sstdescription{
+      This is a set of functions that create the shortest \htmlref{Polygon}{Polygon} that
+      encloses all pixels with a specified value within a gridded
+      2-dimensional data array (e.g. an image).
+
+      A basic 2-dimensional \htmlref{Frame}{Frame} is used to represent the pixel coordinate
+      system in the returned Polygon. The \htmlref{Domain}{Domain} attribute is set to
+      \texttt{"} PIXEL\texttt{"} , the \htmlref{Title}{Title} attribute is set to \texttt{"} Pixel coordinates\texttt{"} , and the
+      Unit attribute for each axis is set to \texttt{"} pixel\texttt{"} . All other
+      attributes are left unset. The nature of the pixel coordinate system
+      is determined by parameter
+      STARPIX.
+
+      You should use a function which matches the numerical type of the
+      data you are processing by replacing $<$X$>$ in the generic function
+      name
+      AST\_CONVEX$<$X$>$
+      are procesing data with type
+      REAL, you should use the function AST\_CONVEXR
+      (see the \texttt{"} Data Type Codes\texttt{"}  section below for the codes appropriate to
+      other numerical types).
+   }
+   \sstinvocation{
+      RESULT = AST\_CONVEX$<$X$>$( VALUE, OPER, ARRAY, LBND, UBND, STARPIX, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         VALUE = $<$Xtype$>$ (Given)
+      }{
+         A data value that specifies the pixels to be included within the
+         convex hull.
+      }
+      \sstsubsection{
+         OPER = INTEGER (Given)
+      }{
+         Indicates how the
+         VALUE
+         parameter is used to select the required pixels. It can
+         have any of the following values:
+         \sstitemlist{
+
+            \sstitem
+            AST\_\_LT: include pixels with value less than VALUE.
+
+            \sstitem
+            AST\_\_LE: include pixels with value less than or equal to VALUE.
+
+            \sstitem
+            AST\_\_EQ: include pixels with value equal to VALUE.
+
+            \sstitem
+            AST\_\_NE: include pixels with value not equal to VALUE.
+
+            \sstitem
+            AST\_\_GE: include pixels with value greater than or equal to VALUE.
+
+            \sstitem
+            AST\_\_GT: include pixels with value greater than VALUE.
+         }
+      }
+      \sstsubsection{
+         ARRAY( $*$ ) = $<$Xtype$>$ (Given)
+      }{
+         A
+         2-dimensional array containing the data to be processed.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using AST\_CONVEXR, the type of each array element
+         should be REAL).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the second dimension least rapidly
+         (i.e. normal Fortran array storage order).
+      }
+      \sstsubsection{
+         LBND( 2 ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         UBND( 2) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that LBND and UBND together define the shape
+         and size of the input grid, its extent along a particular
+         (J\texttt{'} th) dimension being UBND(J)-LBND(J)$+$1. They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre or upper corner, as selected by parameter
+         STARPIX.
+      }
+      \sstsubsection{
+         STARPIX = LOGICAL (Given)
+      }{
+         A flag indicating the nature of the pixel coordinate system used
+         to describe the vertex positions in the returned Polygon. If
+         .TRUE.,
+         the standard Starlink definition of pixel coordinate is used in
+         which a pixel with integer index I spans a range of pixel coordinate
+         from (I-1) to I (i.e. pixel corners have integral pixel coordinates).
+         If .FALSE.,
+         the definition of pixel coordinate used by other AST functions
+         such as AST\_RESAMPLE, AST\_MASK,
+         etc., is used. In this definition, a pixel with integer index I
+         spans a range of pixel coordinate from (I-0.5) to (I$+$0.5) (i.e.
+         pixel centres have integral pixel coordinates).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_CONVEX$<$X$>$ = INTEGER
+      }{
+         A pointer to the required Polygon.
+         AST\_\_NULL
+         is returned without error if the array contains no pixels that
+         satisfy the criterion specified by
+         VALUE and OPER.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_NULL
+         will be returned if this function is invoked with the global
+         error status set, or if it should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate masking function, you should
+      replace $<$X$>$ in the generic function name AST\_CONVEX$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         UI: INTEGER (treated as unsigned)
+
+         \sstitem
+         S: INTEGER$*$2 (short integer)
+
+         \sstitem
+         US: INTEGER$*$2 (short integer, treated as unsigned)
+
+         \sstitem
+         B: BYTE (treated as signed)
+
+         \sstitem
+         UB: BYTE (treated as unsigned)
+
+      }
+      For example, AST\_CONVEXD would be used to process DOUBLE
+      PRECISION data, while AST\_CONVEXS would be used to process
+      short integer data (stored in an INTEGER$*$2 array), etc.
+
+      For compatibility with other Starlink facilities, the codes W
+      and UW are provided as synonyms for S and US respectively (but
+      only in the Fortran interface to AST).
+   }
+}
+\sstroutine{
+   AST\_COPY
+}{
+   Copy an Object
+}{
+   \sstdescription{
+      This function creates a copy of an \htmlref{Object}{Object} and returns a pointer
+      to the resulting new Object. It makes a \texttt{"} deep\texttt{"}  copy, which
+      contains no references to any other Object (i.e. if the original
+      Object contains references to other Objects, then the actual
+      data are copied, not simply the references). This means that
+      modifications may safely be made to the copy without indirectly
+      affecting any other Object.
+   }
+   \sstinvocation{
+      RESULT = AST\_COPY( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Object to be copied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_COPY = INTEGER
+      }{
+         Pointer to the new Object.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_CURRENTTIME
+}{
+   Return the current system time
+}{
+   \sstdescription{
+      This routine
+      returns the current system time, represented in the form specified
+      by the supplied \htmlref{TimeFrame}{TimeFrame}. That is, the returned floating point
+      value should be interpreted using the attribute values of the
+      TimeFrame. This includes \htmlref{System}{System}, \htmlref{TimeOrigin}{TimeOrigin}, \htmlref{LTOffset}{LTOffset}, \htmlref{TimeScale}{TimeScale},
+      and Unit.
+   }
+   \sstinvocation{
+      RESULT = AST\_CURRENTTIME( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the TimeFrame.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_CURRENTTIME = DOUBLE
+      }{
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Values of AST\_\_BAD will be returned if this function is
+         invoked with STATUS set to an error value, or if it should fail for
+         any reason.
+
+         \sstitem
+         It is assumes that the system time (returned by the C time()
+         function) follows the POSIX standard, representing a continuous
+         monotonic increasing count of SI seconds since the epoch 00:00:00
+         UTC 1 January 1970 AD (equivalent to TAI with a constant offset).
+         Resolution is one second.
+
+         \sstitem
+         An error will be reported if the TimeFrame has a TimeScale value
+         which cannot be converted to TAI (e.g. \texttt{"} angular\texttt{"}  systems such as
+         UT1, GMST, LMST and LAST).
+
+         \sstitem
+         Any inaccuracy in the system clock will be reflected in the value
+         returned by this function.
+      }
+   }
+}
+\sstroutine{
+   AST\_CURVE
+}{
+   Draw a geodesic curve
+}{
+   \sstdescription{
+      This routine draws a geodesic curve between two points in the
+      physical coordinate system of a \htmlref{Plot}{Plot}.  The curve drawn is the
+      path of shortest distance joining the two points (as defined by
+      the \htmlref{AST\_DISTANCE}{AST\_DISTANCE} function for the current \htmlref{Frame}{Frame} of the Plot).
+      For example, if the current Frame is a basic Frame, then the
+      curve joining the two points will be a straight line in physical
+      coordinate space.  If the current Frame is more specialised and
+      describes, for instance, a sky coordinate system, then the
+      geodesic curve would be a great circle in physical coordinate
+      space passing through the two sky positions given.
+
+      Note that the geodesic curve is transformed into graphical
+      coordinate space for plotting, so that a straight line in
+      physical coordinates may result in a curved line being drawn if
+      the \htmlref{Mapping}{Mapping} involved is non-linear. Any discontinuities in the
+      Mapping between physical and graphical coordinates are
+      catered for, as is any clipping established using \htmlref{AST\_CLIP}{AST\_CLIP}.
+
+      If you need to draw many geodesic curves end-to-end, then the
+      \htmlref{AST\_POLYCURVE}{AST\_POLYCURVE} routine is equivalent to repeatedly calling
+      AST\_CURVE, but will usually be more efficient.
+
+      If you need to draw curves which are not geodesics, see \htmlref{AST\_GENCURVE}{AST\_GENCURVE}
+      or \htmlref{AST\_GRIDLINE}{AST\_GRIDLINE}.
+   }
+   \sstinvocation{
+      CALL AST\_CURVE( THIS, START, FINISH, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         START( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array, with one element for each axis of the Plot, giving
+         the physical coordinates of the first point on the geodesic
+         curve.
+      }
+      \sstsubsection{
+         FINISH( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array, with one element for each axis of the Plot, giving
+         the physical coordinates of the second point on the geodesic
+         curve.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No curve is drawn if either of the START or FINISH arrays
+         contains any coordinates with the value AST\_\_BAD.
+
+         \sstitem
+         An error results if the base Frame of the Plot is not 2-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   AST\_DECOMPOSE
+}{
+   Decompose a Mapping into two component Mappings
+}{
+   \sstdescription{
+      This routine returns pointers to two Mappings which, when applied
+      either in series or parallel, are equivalent to the supplied \htmlref{Mapping}{Mapping}.
+
+      Since the \htmlref{Frame}{Frame} class inherits from the Mapping class, Frames can
+      be considered as special types of Mappings and so this method can
+      be used to decompose either CmpMaps or CmpFrames.
+   }
+   \sstinvocation{
+      CALL AST\_DECOMPOSE( THIS, MAP1, MAP2, SERIES, INVERT1, INVERT2, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping.
+      }
+      \sstsubsection{
+         MAP1 = INTEGER (Returned)
+      }{
+         A pointer to first component
+         Mapping.
+      }
+      \sstsubsection{
+         MAP2 = INTEGER (Returned)
+      }{
+         A pointer to second component
+         Mapping.
+      }
+      \sstsubsection{
+         SERIES = LOGICAL (Returned)
+      }{
+         Indicates if the
+         component Mappings are applied in series or parallel. A .TRUE.
+         value means that the supplied Mapping is equivalent to applying MAP1
+         followed by MAP2 in series. A zero value means that the supplied
+         Mapping is equivalent to applying MAP1 to the lower numbered axes
+         and MAP2 to the higher numbered axes, in parallel.
+      }
+      \sstsubsection{
+         INVERT1 = INTEGER (Returned)
+      }{
+         The value of the \htmlref{Invert}{Invert} attribute to be used with MAP1.
+      }
+      \sstsubsection{
+         INVERT2 = INTEGER (Returned)
+      }{
+         The value of the Invert attribute to be used with MAP2.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         If the supplied Mapping is a CmpMap, then MAP1 and MAP2 will be
+         returned holding pointers to the component Mappings used to
+         create the CmpMap, either in series or parallel. Note, changing
+         the Invert attribute of either of the component Mappings using
+         the returned pointers will have no effect on the supplied CmpMap.
+         This is because the CmpMap remembers and uses the original settings
+         of the Invert attributes (that is, the values of the Invert
+         attributes when the CmpMap was first created). These are the
+         Invert values which are returned in INVERT1 and INVERT2.
+      }
+      \sstsubsection{
+         \htmlref{TranMap}{TranMap}
+      }{
+         If the supplied Mapping is a TranMap, then MAP1 and MAP2 will be
+         returned holding pointers to the forward and inverse Mappings
+         represented by the TranMap (zero will be returned for
+         SERIES).
+         Note, changing the Invert attribute of
+         either of the component Mappings using the returned pointers will
+         have no effect on the supplied TranMap. This is because the TranMap
+         remembers and uses the original settings of the Invert attributes
+         (that is, the values of the Invert attributes when the TranMap was
+         first created). These are the
+         Invert values which are returned in INVERT1 and INVERT2.
+      }
+      \sstsubsection{
+         Mapping
+      }{
+         For any class of Mapping other than a CmpMap, MAP1 will be
+         returned holding a clone of the supplied Mapping pointer, and MAP2
+         will be returned holding AST\_\_NULL. INVERT1 will be returned
+         holding the current value of the Invert attribute for the supplied
+         Mapping, and INVERT2 will be returned holding zero.
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         If the supplied Mapping is a CmpFrame, then MAP1 and MAP2 will be
+         returned holding pointers to the component Frames used to
+         create the CmpFrame. The component Frames are considered to be in
+         applied in parallel.
+      }
+      \sstsubsection{
+         Frame
+      }{
+         For any class of Frame other than a CmpFrame, MAP1 will be
+         returned holding a clone of the supplied Frame pointer, and MAP2
+         will be returned holding AST\_\_NULL.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned Invert values should be used in preference to the
+         current values of the Invert attribute in map1 and map2. This is
+         because the attributes may have changed value since the Mappings
+         were combined.
+
+         \sstitem
+         Any changes made to the component Mappings using the returned
+         pointers will be reflected in the supplied Mapping.
+      }
+   }
+}
+\sstroutine{
+   AST\_DELETE
+}{
+   Delete an Object
+}{
+   \sstdescription{
+      This routine deletes an \htmlref{Object}{Object}, freeing all resources
+      associated with it and rendering any remaining pointers to the
+      Object invalid.
+
+      Note that deletion is unconditional, regardless of whether other
+      pointers to the Object are still in use (possibly within other
+      Objects). A safer approach is to defer deletion, until all
+      references to an Object have expired, by using \htmlref{AST\_BEGIN}{AST\_BEGIN}/\htmlref{AST\_END}{AST\_END}
+      (together with \htmlref{AST\_CLONE}{AST\_CLONE} and \htmlref{AST\_ANNUL}{AST\_ANNUL} if necessary).
+   }
+   \sstinvocation{
+      CALL AST\_DELETE( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given and Returned)
+      }{
+         Pointer to the Object to be deleted. A null pointer value
+         (AST\_\_NULL) is always returned.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine attempts to execute even if STATUS is set to an error
+         value
+         on entry, although no further error report will be
+         made if it subsequently fails under these circumstances.
+      }
+   }
+}
+\sstroutine{
+   AST\_DELFITS
+}{
+   Delete the current FITS card in a FitsChan
+}{
+   \sstdescription{
+      This routine deletes the current FITS card from a \htmlref{FitsChan}{FitsChan}. The
+      current card may be selected using the \htmlref{Card}{Card} attribute (if its index
+      is known) or by using \htmlref{AST\_FINDFITS}{AST\_FINDFITS} (if only the FITS keyword is
+      known).
+
+      After deletion, the following card becomes the current card.
+   }
+   \sstinvocation{
+      CALL AST\_DELFITS( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function returns without action if the FitsChan is
+         initially positioned at the \texttt{"} end-of-file\texttt{"}  (i.e. if the Card
+         attribute exceeds the number of cards in the FitsChan).
+
+         \sstitem
+         If there are no subsequent cards in the FitsChan, then the
+         Card attribute is left pointing at the \texttt{"} end-of-file\texttt{"}  after
+         deletion (i.e. is set to one more than the number of cards in
+         the FitsChan).
+      }
+   }
+}
+\sstroutine{
+   AST\_DISTANCE
+}{
+   Calculate the distance between two points in a Frame
+}{
+   \sstdescription{
+      This function finds the distance between two points whose \htmlref{Frame}{Frame}
+      coordinates are given. The distance calculated is that along
+      the geodesic curve that joins the two points.
+
+      For example, in a basic Frame, the distance calculated will be
+      the Cartesian distance along the straight line joining the two
+      points. For a more specialised Frame describing a sky coordinate
+      system, however, it would be the distance along the great circle
+      passing through two sky positions.
+   }
+   \sstinvocation{
+      RESULT = AST\_DISTANCE( THIS, POINT1, POINT2, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         POINT1( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute) containing the coordinates of the first point.
+      }
+      \sstsubsection{
+         POINT2( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         containing the coordinates of the second point.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_DISTANCE = DOUBLE PRECISION
+      }{
+         The distance between the two points.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function will return a \texttt{"} bad\texttt{"}  result value (AST\_\_BAD) if
+         any of the input coordinates has this value.
+
+         \sstitem
+         A \texttt{"} bad\texttt{"}  value will also be returned if this function is
+         invoked with STATUS set to an error value, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_DOWNSIZE
+}{
+   Reduce the number of vertices in a Polygon
+}{
+   \sstdescription{
+      This function returns a pointer to a new \htmlref{Polygon}{Polygon} that contains a
+      subset of the vertices in the supplied Polygon. The subset is
+      chosen so that the returned Polygon is a good approximation to
+      the supplied Polygon, within the limits specified by the supplied
+      parameter values. That is, the density of points in the returned
+      Polygon is greater at points where the curvature of the boundary of
+      the supplied Polygon is greater.
+   }
+   \sstinvocation{
+      RESULT = AST\_DOWNSIZE( THIS, MAXERR, MAXVERT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Polygon.
+      }
+      \sstsubsection{
+         MAXERR = DOUBLE PRECISION (Given)
+      }{
+         The maximum allowed discrepancy between the supplied and
+         returned Polygons, expressed as a geodesic distance within the
+         Polygon\texttt{'} s coordinate frame. If this is zero or less, the
+         returned Polygon will have the number of vertices specified by
+         MAXVERT.
+      }
+      \sstsubsection{
+         MAXVERT = INTEGER (Given)
+      }{
+         The maximum allowed number of vertices in the returned Polygon.
+         If this is less than 3, the number of vertices in the returned
+         Polygon will be the minimum needed to achieve the maximum
+         discrepancy specified by
+         MAXERR.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_DOWNSIZE = INTEGER
+      }{
+         Pointer to the new Polygon.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_DSBSPECFRAME
+}{
+   Create a DSBSpecFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{DSBSpecFrame}{DSBSpecFrame} and optionally initialises its
+      attributes.
+
+      A DSBSpecFrame is a specialised form of \htmlref{SpecFrame}{SpecFrame} which represents
+      positions in a spectrum obtained using a dual sideband instrument.
+      Such an instrument produces a spectrum in which each point contains
+      contributions from two distinctly different frequencies, one from
+      the \texttt{"} lower side band\texttt{"}  (LSB) and one from the \texttt{"} upper side band\texttt{"}  (USB).
+      Corresponding LSB and USB frequencies are connected by the fact
+      that they are an equal distance on either side of a fixed central
+      frequency known as the \texttt{"} Local Oscillator\texttt{"}  (LO) frequency.
+
+      When quoting a position within such a spectrum, it is necessary to
+      indicate whether the quoted position is the USB position or the
+      corresponding LSB position. The \htmlref{SideBand}{SideBand} attribute provides this
+      indication. Another option that the SideBand attribute provides is
+      to represent a spectral position by its topocentric offset from the
+      LO frequency.
+
+      In practice, the LO frequency is specified by giving the distance
+      from the LO frequency to some \texttt{"} central\texttt{"}  spectral position. Typically
+      this central position is that of some interesting spectral feature.
+      The distance from this central position to the LO frequency is known
+      as the \texttt{"} intermediate frequency\texttt{"}  (\htmlref{IF}{IF}). The value supplied for IF can
+      be a signed value in order to indicate whether the LO frequency is
+      above or below the central position.
+   }
+   \sstinvocation{
+      RESULT = AST\_DSBSPECFRAME( OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new DSBSpecFrame. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_DSBSPECFRAME = INTEGER
+      }{
+         A pointer to the new DSBSpecFrame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_EBUF
+}{
+   End the current graphical buffering context
+}{
+   \sstdescription{
+      This routine
+      ends the current graphics buffering context. It should match a
+      corresponding call to the
+      AST\_EBUF routine.
+   }
+   \sstinvocation{
+      CALL AST\_EBUF( THIS STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Plot}{Plot}.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The nature of the buffering is determined by the underlying
+         graphics system (as defined by the current grf module). Each call
+         to this routine
+         simply invokes the astGEBuf function in the grf module.
+      }
+   }
+}
+\sstroutine{
+   AST\_ELLIPSE
+}{
+   Create a Ellipse
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Ellipse}{Ellipse} and optionally initialises its
+      attributes.
+
+      A Ellipse is a \htmlref{Region}{Region} which represents a elliptical area within the
+      supplied 2-dimensional \htmlref{Frame}{Frame}.
+   }
+   \sstinvocation{
+      RESULT = AST\_ELLIPSE( FRAME, FORM, CENTRE, POINT1, POINT2, UNC, OPTIONS,
+                            STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         A pointer to the Frame in which the region is defined. It must
+         have exactly 2 axes. A deep copy is taken of the supplied Frame.
+         This means that any subsequent changes made to the Frame using the
+         supplied pointer will have no effect the Region.
+      }
+      \sstsubsection{
+         FORM = INTEGER (Given)
+      }{
+         Indicates how the ellipse is described by the remaining parameters.
+         A value of zero indicates that the ellipse is specified by a
+         centre position and two positions on the circumference. A value of
+         one indicates that the ellipse is specified by its centre position,
+         the half-lengths of its two axes, and the orientation of its first
+         axis.
+      }
+      \sstsubsection{
+         CENTRE( 2 ) = DOUBLE PRECISION (Given)
+      }{
+         An array
+         containing the coordinates at the centre of
+         the ellipse.
+      }
+      \sstsubsection{
+         POINT1( 2 ) = DOUBLE PRECISION (Given)
+      }{
+         If FORM
+         is zero, this array should contain the coordinates of one of the four
+         points where an axis of the ellipse crosses the circumference of the
+         ellipse.
+         If FORM
+         is one, it should contain the lengths of semi-major and
+         semi-minor axes of the ellipse, given as geodesic distances
+         within the Frame.
+      }
+      \sstsubsection{
+         POINT2( 2 ) = DOUBLE PRECISION (Given)
+      }{
+         If FORM
+         is zero, this array should containing the coordinates at some other
+         point on the circumference of the ellipse, distinct from
+         POINT1. If FORM
+         is one, the first element of this array should hold the angle
+         between the second axis of the Frame and the first ellipse axis
+         (i.e. the ellipse axis which is specified first in the
+         POINT1
+         array), and the second element will be ignored. The angle should be
+         given in radians, measured positive in the same sense as rotation
+         from the positive direction of the second Frame axis to the positive
+         direction of the first Frame axis.
+      }
+      \sstsubsection{
+         UNC = INTEGER (Given)
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with the boundary of the Ellipse being created.
+         The uncertainty in any point on the boundary of the Ellipse is found by
+         shifting the supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at
+         the boundary point being considered. The area covered by the
+         shifted uncertainty Region then represents the uncertainty in the
+         boundary position. The uncertainty is assumed to be the same for
+         all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. \htmlref{Box}{Box}, \htmlref{Circle}{Circle}, Ellipse, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Ellipse. Alternatively,
+         a null \htmlref{Object}{Object} pointer (AST\_\_NULL)
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the Ellipse being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{AST\_OVERLAP}{AST\_OVERLAP}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Ellipse. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_ELLIPSE = INTEGER
+      }{
+         A pointer to the new Ellipse.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_ELLIPSEPARS
+}{
+   Returns the geometric parameters of an Ellipse
+}{
+   \sstdescription{
+      This routine
+      returns the geometric parameters describing the supplied ellipse.
+   }
+   \sstinvocation{
+      CALL AST\_ELLIPSEPARS( THIS, CENTRE, A, B, ANGLE, P1, P2, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         CENTRE( 2 ) = DOUBLE PRECISION (Returned)
+      }{
+         The coordinates of the \htmlref{Ellipse}{Ellipse} centre are returned in this arrays.
+      }
+      \sstsubsection{
+         A = DOUBLE PRECISION (Returned)
+      }{
+         Returned holding the half-length of the first axis of the
+         ellipse.
+      }
+      \sstsubsection{
+         B = DOUBLE PRECISION (Returned)
+      }{
+         Returned holding the half-length of the second axis of the
+         ellipse.
+      }
+      \sstsubsection{
+         ANGLE = DOUBLE PRECISION (Returned)
+      }{
+         If the coordinate system in which the Ellipse is defined has
+         axes (X,Y), then
+         ANGLE
+         is returned holding the angle from the positive direction of
+         the Y axis to the first axis of the ellipse, in radians.
+         Positive rotation is in the same sense as rotation from the
+         positive direction of Y to the positive direction of X.
+      }
+      \sstsubsection{
+         P1( 2 ) = DOUBLE PRECISION (Returned)
+      }{
+         An array in which to return the coordinates at one of the two ends
+         of the first axis  of the ellipse.
+      }
+      \sstsubsection{
+         P2( 2 ) = DOUBLE PRECISION (Returned)
+      }{
+         An array in which to return the coordinates at one of the two ends
+         of the second axis  of the ellipse.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the coordinate system represented by the Ellipse has been
+         changed since it was first created, the returned parameters refer
+         to the new (changed) coordinate system, rather than the original
+         coordinate system. Note however that if the transformation from
+         original to new coordinate system is non-linear, the shape
+         represented by the supplied Ellipse object may not be an accurate
+         ellipse.
+
+         \sstitem
+         Values of AST\_\_BAD are returned for the parameters without error
+         if the ellipse is degenerate or undefined.
+      }
+   }
+}
+\sstroutine{
+   AST\_EMPTYFITS
+}{
+   Delete all cards in a FitsChan
+}{
+   \sstdescription{
+      This routine
+      deletes all cards and associated information from a \htmlref{FitsChan}{FitsChan}.
+   }
+   \sstinvocation{
+      CALL AST\_EMPTYFITS( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This method simply deletes the cards currently in the FitsChan.
+         Unlike \htmlref{AST\_WRITEFITS}{AST\_WRITEFITS},
+         they are not first written out to the sink function or sink file.
+
+         \sstitem
+         Any Tables or warnings stored in the FitsChan are also deleted.
+
+         \sstitem
+         This method attempt to execute even if an error has occurred
+         previously.
+      }
+   }
+}
+\sstroutine{
+   AST\_END
+}{
+   End an AST context
+}{
+   \sstdescription{
+      This routine ends an AST context which was
+      begun with a matching invocation of \htmlref{AST\_BEGIN}{AST\_BEGIN}. Any \htmlref{Object}{Object}
+      pointers created within this context will be annulled (just as
+      if \htmlref{AST\_ANNUL}{AST\_ANNUL} had been invoked) and will cease to be valid
+      afterwards, unless they have previously been exported using
+      \htmlref{AST\_EXPORT}{AST\_EXPORT} or rendered exempt using \htmlref{AST\_EXEMPT}{AST\_EXEMPT}.
+      If annulling a pointer causes an Object\texttt{'} s \htmlref{RefCount}{RefCount} attribute to
+      fall to zero (which happens when the last pointer to it is
+      annulled), then the Object will be deleted.
+   }
+   \sstinvocation{
+      CALL AST\_END( STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine attempts to execute even if STATUS is set to an
+         error value.
+
+         \sstitem
+         Contexts delimited by AST\_BEGIN and AST\_END may be nested to any
+         depth.
+      }
+   }
+}
+\sstroutine{
+   AST\_ESCAPES
+}{
+   Control whether graphical escape sequences are included in strings
+}{
+   \sstdescription{
+      The \htmlref{Plot}{Plot} class defines a set of escape sequences which can be
+      included within a text string in order to control the appearance of
+      sub-strings within the text. See the \htmlref{Escape}{Escape} attribute for a
+      description of these escape sequences. It is usually inappropriate
+      for AST to return strings containing such escape sequences when
+      called by application code. For instance, an application which
+      displays the value of the \htmlref{Title}{Title} attribute of a \htmlref{Frame}{Frame} usually does
+      not want the displayed string to include potentially long escape
+      sequences which a human read would have difficuly interpreting.
+      Therefore the default behaviour is for AST to strip out such escape
+      sequences when called by application code. This default behaviour
+      can be changed using this function.
+   }
+   \sstinvocation{
+      RESULT = AST\_ESCAPES( NEWVAL, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NEWVAL = INTEGER (Given)
+      }{
+         A flag which indicates if escapes sequences should be included
+         in returned strings. If zero is supplied, escape sequences will
+         be stripped out of all strings returned by any AST function. If
+         a positive value is supplied, then any escape sequences will be
+         retained in the value returned to the caller. If a negative
+         value is supplied, the current value of the flag will be left
+         unchanged.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Object}{Object}
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_ESCAPES = INTEGER
+      }{
+         The value of the flag on entry to this function.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function also controls whether the
+         \htmlref{AST\_STRIPESCAPES}{AST\_STRIPESCAPES}
+         function removes escape sequences from the supplied string, or
+         returns the supplied string without change.
+
+         \sstitem
+         This function attempts to execute even if an error has already
+         occurred.
+      }
+   }
+}
+\sstroutine{
+   AST\_EXEMPT
+}{
+   Exempt an Object pointer from AST context handling
+}{
+   \sstdescription{
+      This routine exempts an \htmlref{Object}{Object} pointer from AST context
+      handling, as implemented by \htmlref{AST\_BEGIN}{AST\_BEGIN} and \htmlref{AST\_END}{AST\_END}. This means that
+      the pointer will not be affected when AST\_END is called and will
+      remain active until the end of the program, or until explicitly
+      annulled using \htmlref{AST\_ANNUL}{AST\_ANNUL}.
+
+      If possible, you should avoid using this routine when writing
+      applications. It is provided mainly for developers of other
+      libraries, who may wish to retain references to AST Objects in
+      internal data structures, and who therefore need to avoid the
+      effects of AST\_BEGIN and AST\_END.
+   }
+   \sstinvocation{
+      CALL AST\_EXEMPT( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Object pointer to be exempted from context handling.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+}
+\sstroutine{
+   AST\_EXPORT
+}{
+   Export an Object pointer to an outer context
+}{
+   \sstdescription{
+      This routine exports an \htmlref{Object}{Object} pointer from the current AST context
+      into the context that encloses the current one. This means that
+      the pointer will no longer be annulled when the current context
+      is ended (with \htmlref{AST\_END}{AST\_END}), but only when the next outer context (if
+      any) ends.
+   }
+   \sstinvocation{
+      CALL AST\_EXPORT( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Object pointer to be exported.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         It is only sensible to apply this routine to pointers that
+         have been created within (or exported to) the current context
+         and have not been rendered exempt using \htmlref{AST\_EXEMPT}{AST\_EXEMPT}.
+         Applying it to an unsuitable Object pointer has no effect.
+      }
+   }
+}
+\sstroutine{
+   AST\_FINDFITS
+}{
+   Find a FITS card in a FitsChan by keyword
+}{
+   \sstdescription{
+      This function searches for a card in a \htmlref{FitsChan}{FitsChan} by keyword. The
+      search commences at the current card (identified by the \htmlref{Card}{Card}
+      attribute) and ends when a card is found whose FITS keyword
+      matches the template supplied, or when the last card in the
+      FitsChan has been searched.
+
+      If the search is successful (i.e. a card is found which matches
+      the template), the contents of the card are
+      returned and the Card attribute is adjusted to identify the card
+      found or, if required, the one following it. If the search is
+      not successful, the function returns .FALSE. and the Card attribute
+      is set to the \texttt{"} end-of-file\texttt{"} .
+   }
+   \sstinvocation{
+      RESULT = AST\_FINDFITS( THIS, NAME, CARD, INC, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing a
+         template for the keyword to be found. In the simplest case,
+         this should simply be the keyword name (the search is case
+         insensitive and trailing spaces are ignored). However, this
+         template may also contain \texttt{"} field specifiers\texttt{"}  which are
+         capable of matching a range of characters (see the \texttt{"} Keyword
+         Templates\texttt{"}  section for details). In this case, the first card
+         with a keyword which matches the template will be found. To
+         find the next FITS card regardless of its keyword, you should
+         use the template \texttt{"} \%f\texttt{"} .
+      }
+      \sstsubsection{
+         CARD = CHARACTER $*$ ( 80 ) (Returned)
+      }{
+         A character variable with at least 80 characters
+         in which the FITS card which is found will be returned.  If
+         the search is not successful, a
+         card will not be returned.
+      }
+      \sstsubsection{
+         INC = LOGICAL (Given)
+      }{
+         If this value is .FALSE. (and the search is successful), the
+         FitsChan\texttt{'} s Card attribute will be set to the index of the card
+         that was found. If it is .TRUE., however, the Card
+         attribute will be incremented to identify the card which
+         follows the one found.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_FINDFITS = LOGICAL
+      }{
+         .TRUE. if the search was successful, otherwise .FALSE..
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFITS( FITSCHAN, \texttt{'} \%f\texttt{'} , CARD, .TRUE., STATUS )
+      }{
+         Returns the current card in a FitsChan and advances the Card
+         attribute to identify the card that follows (the \texttt{"} \%f\texttt{"}
+         template matches any keyword).
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFITS( FITSCHAN, \texttt{'} BITPIX\texttt{'} , CARD, .TRUE., STATUS )
+      }{
+         Searches a FitsChan for a FITS card with the \texttt{"} BITPIX\texttt{"}  keyword
+         and returns that card. The Card attribute is then incremented
+         to identify the card that follows it.
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFITS( FITSCHAN, \texttt{'} COMMENT\texttt{'} , CARD, .FALSE., STATUS )
+      }{
+         Sets the Card attribute of a FitsChan to identify the next
+         COMMENT card (if any) and returns that card.
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFITS( FITSCHAN, \texttt{'} CRVAL\%1d\texttt{'} , CARD, .TRUE., STATUS )
+      }{
+         Searches a FitsChan for the next card with a keyword of the
+         form \texttt{"} CRVALi\texttt{"}  (for example, any of the keywords \texttt{"} CRVAL1\texttt{"} ,
+         \texttt{"} CRVAL2\texttt{"}  or \texttt{"} CRVAL3\texttt{"}  would be matched). The card found (if
+         any) is returned, and the Card attribute is then incremented
+         to identify the following card (ready to search for another
+         keyword with the same form, perhaps).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The search always starts with the current card, as identified
+         by the Card attribute. To ensure you search the entire contents
+         of a FitsChan, you should first clear the Card attribute (using
+         \htmlref{AST\_CLEAR}{AST\_CLEAR}). This effectively \texttt{"} rewinds\texttt{"}  the FitsChan.
+
+         \sstitem
+         If a search is unsuccessful, the Card attribute is set to the
+         \texttt{"} end-of-file\texttt{"}  (i.e. to one more than the number of cards in the
+         FitsChan). No error occurs.
+
+         \sstitem
+         A value of .FALSE. will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any
+         reason.
+      }
+   }
+   \sstdiytopic{
+      Keyword Templates
+   }{
+      The templates used to match FITS keywords are normally composed
+      of literal characters, which must match the keyword exactly
+      (apart from case). However, a template may also contain \texttt{"} field
+      specifiers\texttt{"}  which can match a range of possible characters. This
+      allows you to search for keywords that contain (for example)
+      numbers, where the digits comprising the number are not known in
+      advance.
+
+      A field specifier starts with a \texttt{"} \%\texttt{"}  character. This is followed
+      by an optional single digit (0 to 9) specifying a field
+      width. Finally, there is a single character which specifies the
+
+      type of character to be matched, as follows:
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} c\texttt{"} : matches all upper case letters,
+
+         \sstitem
+         \texttt{"} d\texttt{"} : matches all decimal digits,
+
+         \sstitem
+         \texttt{"} f\texttt{"} : matches all characters which are permitted within a FITS
+         keyword (upper case letters, digits, underscores and hyphens).
+
+      }
+      If the field width is omitted, the field specifier matches one
+      or more characters. If the field width is zero, it matches zero
+      or more characters. Otherwise, it matches exactly the number of
+
+      characters specified. In addition to this:
+
+      \sstitemlist{
+
+         \sstitem
+         The template \texttt{"} \%f\texttt{"}  will match a blank FITS keyword consisting
+         of 8 spaces (as well as matching all other keywords).
+
+         \sstitem
+         A template consisting of 8 spaces will match a blank keyword
+         (only).
+
+      }
+      For example:
+
+      \sstitemlist{
+
+         \sstitem
+         The template \texttt{"} BitPix\texttt{"}  will match the keyword \texttt{"} BITPIX\texttt{"}  only.
+
+         \sstitem
+         The template \texttt{"} crpix\%1d\texttt{"}  will match keywords consisting of
+         \texttt{"} CRPIX\texttt{"}  followed by one decimal digit.
+
+         \sstitem
+         The template \texttt{"} P\%c\texttt{"}  will match any keyword starting with \texttt{"} P\texttt{"}
+         and followed by one or more letters.
+
+         \sstitem
+         The template \texttt{"} E\%0f\texttt{"}  will match any keyword beginning with \texttt{"} E\texttt{"} .
+
+         \sstitem
+         The template \texttt{"} \%f\texttt{"}  will match any keyword at all (including a
+         blank one).
+      }
+   }
+}
+\sstroutine{
+   AST\_FINDFRAME
+}{
+   Find a coordinate system with specified characteristics
+}{
+   \sstdescription{
+      This function uses a \texttt{"} template\texttt{"}  \htmlref{Frame}{Frame} to search another Frame
+      (or \htmlref{FrameSet}{FrameSet}) to identify a coordinate system which has a
+      specified set of characteristics. If a suitable coordinate
+      system can be found, the function returns a pointer to a
+      FrameSet which describes the required coordinate system and how
+      to convert coordinates to and from it.
+
+      This function is provided to help answer general questions about
+      coordinate systems, such as typically arise when coordinate
+      information is imported into a program as part of an initially
+      unknown dataset. For example:
+      \sstitemlist{
+
+         \sstitem
+         Is there a wavelength scale?
+
+         \sstitem
+         Is there a 2-dimensional coordinate system?
+
+         \sstitem
+         Is there a celestial coordinate system?
+
+         \sstitem
+         Can I plot the data in ecliptic coordinates?
+
+      }
+      You can also use this function as a means of reconciling a
+      user\texttt{'} s preference for a particular coordinate system (for
+      example, what type of axes to draw) with what is actually
+      possible given the coordinate information available.
+
+      To perform a search, you supply a \texttt{"} target\texttt{"}  Frame (or FrameSet)
+      which represents the set of coordinate systems to be searched.
+      If a basic Frame is given as the target, this set of coordinate
+      systems consists of the one described by this Frame, plus all
+      other \texttt{"} virtual\texttt{"}  coordinate systems which can potentially be
+      reached from it by applying built-in conversions (for example,
+      any of the celestial coordinate conversions known to the AST
+      library would constitute a \texttt{"} built-in\texttt{"}  conversion). If a FrameSet
+      is given as the target, the set of coordinate systems to be
+      searched consists of the union of those represented by all the
+      individual Frames within it.
+
+      To select from this large set of possible coordinate systems,
+      you supply a \texttt{"} template\texttt{"}  Frame which is an instance of the type
+      of Frame you are looking for. Effectively, you then ask the
+      function to \texttt{"} find a coordinate system that looks like this\texttt{"} .
+
+      You can make your request more or less specific by setting
+      attribute values for the template Frame. If a particular
+      attribute is set in the template, then the function will only
+      find coordinate systems which have exactly the same value for
+      that attribute.  If you leave a template attribute un-set,
+      however, then the function has discretion about the value the
+      attribute should have in any coordinate system it finds. The
+      attribute will then take its value from one of the actual
+      (rather than virtual) coordinate systems in the target. If the
+      target is a FrameSet, its \htmlref{Current}{Current} attribute will be modified to
+      indicate which of its Frames was used for this purpose.
+
+      The result of this process is a coordinate system represented by
+      a hybrid Frame which acquires some attributes from the template
+      (but only if they were set) and the remainder from the
+      target. This represents the \texttt{"} best compromise\texttt{"}  between what you
+      asked for and what was available. A \htmlref{Mapping}{Mapping} is then generated
+      which converts from the target coordinate system to this hybrid
+      one, and the returned FrameSet encapsulates all of this
+      information.
+   }
+   \sstinvocation{
+      RESULT = AST\_FINDFRAME( TARGET, TEMPLATE, DOMAINLIST, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         TARGET = INTEGER (Given)
+      }{
+         Pointer to the target Frame (or FrameSet).
+
+         Note that if a FrameSet is supplied (and a suitable
+         coordinate system is found), then its Current attribute will
+         be modified to indicate which Frame was used to obtain
+         attribute values which were not specified by the template.
+         This Frame will, in some sense, represent the \texttt{"} closest\texttt{"}
+         non-virtual coordinate system to the one you requested.
+      }
+      \sstsubsection{
+         TEMPLATE = INTEGER (Given)
+      }{
+         Pointer to the template Frame, which should be an instance of
+         the type of Frame you wish to find. If you wanted to find a
+         Frame describing a celestial coordinate system, for example,
+         then you might use a \htmlref{SkyFrame}{SkyFrame} here. See the \texttt{"} Examples\texttt{"}
+         section for more ideas.
+      }
+      \sstsubsection{
+         DOMAINLIST = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing a
+         comma-separated list of Frame domains. This may be used to
+         establish a priority order for the different types of
+         coordinate system that might be found.
+
+         The function will first try to find a suitable coordinate
+         system whose \htmlref{Domain}{Domain} attribute equals the first domain in this
+         list. If this fails, the second domain in the list will be
+         used, and so on, until a result is obtained. A blank domain
+         (e.g. two consecutive commas) indicates that any coordinate
+         system is acceptable (subject to the template) regardless of
+         its domain.
+
+         This list is case-insensitive and all white space is ignored.
+         If you do not wish to restrict the domain in this way,
+         you should supply a blank string.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         This function applies to all Frames.
+      }
+      \sstsubsection{
+         FrameSet
+      }{
+         If the target is a FrameSet, the possibility exists that
+         several of the Frames within it might be matched by the
+         template.  Unless the choice is sufficiently restricted by
+         the DOMAINLIST string, the sequence in which Frames are
+         searched can then become important. In this case, the search
+         proceeds as follows:
+         \sstitemlist{
+
+            \sstitem
+            Each field in the DOMAINLIST string is considered in turn.
+
+            \sstitem
+            An attempt is made to match the template to each of the
+            target\texttt{'} s Frames in the order: (1) the current Frame, (2) the
+            base Frame, (3) each remaining Frame in the order of being
+            added to the target FrameSet.
+
+            \sstitem
+            Generally, the first match found is used. However, the
+            Mapping between the target coordinate system and the
+            resulting Frame is also examined. Preference is given to
+            cases where both the forward and inverse transformations are
+            defined (as indicated by the \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse}
+            attributes). If only one transformation is defined, the
+            forward one is preferred.
+
+            \sstitem
+            If a match is found and the domain of the resulting Frame also
+            matches the current DOMAINLIST field, it is
+            accepted. Otherwise, the next DOMAINLIST field is considered
+            and the process repeated.
+
+         }
+         If a suitable coordinate system is found, the Current
+         attribute of the target FrameSet will be modified on exit to
+         identify the Frame whose match with the target was eventually
+         accepted.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_FINDFRAME = INTEGER
+      }{
+         If the search is successful, the function returns a pointer
+         to a FrameSet which contains the Frame found and a
+         description of how to convert to (and from) the coordinate
+         system it represents. Otherwise, a null \htmlref{Object}{Object} pointer
+         (AST\_\_NULL) is returned without error.
+
+         If a FrameSet is returned, it will contain two Frames. Frame
+         number 1 (its base Frame) represents the target coordinate
+         system and will be the same as the (base Frame of the)
+         target. Frame number 2 (its current Frame) will be a Frame
+         representing the coordinate system which the function
+         found. The Mapping which inter-relates these two Frames will
+         describe how to convert between their respective coordinate
+         systems.
+
+         Note that a FrameSet may be used both as a Mapping and as a
+         Frame. If the result is used as a Mapping (e.g. with
+         astTran2), then it provides a means of converting coordinates
+         from the target coordinate system into the new coordinate
+         system that was found (and vice versa if its inverse
+         transformation is selected). If it is used as a Frame, its
+         attributes will describe the new coordinate system.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFRAME( TARGET, \htmlref{AST\_FRAME}{AST\_FRAME}( 3, \texttt{'}  \texttt{'} , STATUS ), \texttt{'}  \texttt{'} , STATUS )
+      }{
+         Searches for a 3-dimensional coordinate system in the target
+         Frame (or FrameSet). No attributes have been set in the
+         template Frame (created by AST\_FRAME), so no restriction has
+         been placed on the required coordinate system, other than
+         that it should have 3 dimensions. The first suitable Frame
+         found will be returned as part of the RESULT FrameSet.
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFRAME( TARGET, \htmlref{AST\_SKYFRAME}{AST\_SKYFRAME}( \texttt{'}  \texttt{'} , STATUS ), \texttt{'}  \texttt{'} , STATUS )
+      }{
+         Searches for a celestial coordinate system in the target
+         Frame (or FrameSet). The type of celestial coordinate system
+         is unspecified, so AST\_FINDFRAME will return the first one
+         found as part of the RESULT FrameSet. If the target is
+         a FrameSet, then its Current attribute will be updated to
+         identify the Frame that was used.
+
+         If no celestial coordinate system can be found, a value of
+         AST\_\_NULL will be returned without error.
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFRAME( TARGET, AST\_SKYFRAME( \texttt{'} \htmlref{MaxAxes}{MaxAxes}=100\texttt{'} , STATUS ), \texttt{'}  \texttt{'} , STATUS )
+      }{
+         This is like the last example, except that in the event of the
+         target being a \htmlref{CmpFrame}{CmpFrame}, the component Frames encapsulated by the
+         CmpFrame will be searched for a SkyFrame. If found, the returned
+         Mapping will included a \htmlref{PermMap}{PermMap} which selects the required axes
+         from the target CmpFrame.
+
+         This is acomplished by setting the MaxAxes attribute of the
+         template SkyFrame to a large number (larger than or equal to the
+         number of axes in the target CmpFrame). This allows the SkyFrame
+         to be used as a match for Frames containing from 2 to 100 axes.
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFRAME( TARGET, AST\_SKYFRAME( \texttt{'} \htmlref{System}{System}=FK5\texttt{'} , STATUS ), \texttt{'}  \texttt{'} , STATUS )
+      }{
+         Searches for an equatorial (FK5) coordinate system in the
+         target. The \htmlref{Equinox}{Equinox} value for the coordinate system has not
+         been specified, so will be obtained from the target. If the
+         target is a FrameSet, its Current attribute will be updated
+         to indicate which SkyFrame was used to obtain this value.
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFRAME( TARGET, AST\_FRAME( 2, \texttt{'}  \texttt{'} , STATUS ), \texttt{'} SKY,PIXEL,\texttt{'} , STATUS )
+      }{
+         Searches for a 2-dimensional coordinate system in the
+         target. Initially, a search is made for a suitable coordinate
+         system whose Domain attribute has the value \texttt{"} SKY\texttt{"} . If this
+         search fails, a search is then made for one with the domain
+         \texttt{"} PIXEL\texttt{"} . If this also fails, then any 2-dimensional
+         coordinate system is returned as part of the RESULT
+         FrameSet.
+
+         Only if no 2-dimensional coordinate systems can be reached by
+         applying built-in conversions to any of the Frames in the
+         target will a value of AST\_\_NULL be returned.
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFRAME( TARGET, AST\_FRAME( 1, \texttt{'} Domain=WAVELENGTH\texttt{'} , STATUS ), \texttt{'}  \texttt{'} , STATUS )
+      }{
+         Searches for any 1-dimensional coordinate system in the
+         target which has the domain \texttt{"} WAVELENGTH\texttt{"} .
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFRAME( TARGET, AST\_FRAME( 1, \texttt{'}  \texttt{'} , STATUS ), \texttt{'} WAVELENGTH\texttt{'} , STATUS )
+      }{
+         This example has exactly the same effect as that above. It
+         illustrates the equivalence of the template\texttt{'} s Domain attribute
+         and the fields in the DOMAINLIST string.
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFRAME( TARGET, AST\_FRAME( 1, \texttt{'} MaxAxes=3\texttt{'} , STATUS ), \texttt{'}  \texttt{'} , STATUS )
+      }{
+         This is a more advanced example which will search for any
+         coordinate system in the target having 1, 2 or 3
+         dimensions. The Frame returned (as part of the RESULT
+         FrameSet) will always be 1-dimensional, but will be related
+         to the coordinate system that was found by a suitable Mapping
+         (e.g. a PermMap) which simply extracts the first axis.
+
+         If we had wanted a Frame representing the actual (1, 2 or
+         3-dimensional) coordinate system found, we could set the
+         \htmlref{PreserveAxes}{PreserveAxes} attribute to a non-zero value in the template.
+      }
+      \sstexamplesubsection{
+         RESULT = AST\_FINDFRAME( TARGET, AST\_SKYFRAME( \texttt{'} \htmlref{Permute}{Permute}=0\texttt{'} , STATUS ), \texttt{'}  \texttt{'} , STATUS )
+      }{
+         Searches for any celestial coordinate system in the target,
+         but only finds one if its axes are in the conventional
+         (longitude,latitude) order and have not been permuted
+         (e.g. with \htmlref{AST\_PERMAXES}{AST\_PERMAXES}).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Mapping represented by the returned FrameSet results in
+         alignment taking place in the coordinate system specified by the
+         \htmlref{AlignSystem}{AlignSystem} attribute of the TEMPLATE Frame. See the description
+         of the AlignSystem attribute for further details.
+
+         \sstitem
+         Beware of setting the Domain attribute of the template and then
+         using a DOMAINLIST string which does not include the template\texttt{'} s domain
+         (or a blank field). If you do so, no coordinate system will be
+         found.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      More on Using Templates
+   }{
+      A Frame (describing a coordinate system) will be found by this
+      function if (a) it is \texttt{"} matched\texttt{"}  by the template you supply, and
+      (b) the value of its Domain attribute appears in the DOMAINLIST
+      string (except that a blank field in this string permits any
+      domain). A successful match by the template depends on a number
+      of criteria, as outlined below:
+      \sstitemlist{
+
+         \sstitem
+         In general, a template will only match another Frame which
+         belongs to the same class as the template, or to a derived (more
+         specialised) class. For example, a SkyFrame template will match
+         any other SkyFrame, but will not match a basic
+         Frame. Conversely, a basic Frame template will match any class
+         of Frame.
+
+         \sstitem
+         The exception to this is that a Frame of any class can be used to
+         match a CmpFrame, if that CmpFrame contains a Frame of the same
+         class as the template. Note however, the MaxAxes and \htmlref{MinAxes}{MinAxes}
+         attributes of the template must be set to suitable values to allow
+         it to match the CmpFrame. That is, the MinAxes attribute must be
+         less than or equal to the number of axes in the target, and the MaxAxes
+         attribute must be greater than or equal to the number of axes in
+         the target.
+
+         \sstitem
+         If using a CmpFrame as a template frame, the MinAxes and MaxAxes
+         for the template are determined by the MinAxes and MaxAxes values of
+         the component Frames within the template. So if you want a template
+         CmpFrame to be able to match Frames with different numbers of axes,
+         then you must set the MaxAxes and/or MinAxes attributes in the component
+         template Frames, before combining them together into the template
+         CmpFrame.
+
+         \sstitem
+         If a template has a value set for any of its main attributes, then
+         it will only match Frames which have an identical value for that
+         attribute (or which can be transformed, using a built-in
+         conversion, so that they have the required value for that
+         attribute). If any attribute in the template is un-set, however,
+         then Frames are matched regardless of the value they may have
+         for that attribute. You may therefore make a template more or
+         less specific by choosing the attributes for which you set
+         values. This requirement does not apply to \texttt{'} descriptive\texttt{'}  attributes
+         such as titles, labels, symbols, etc.
+
+         \sstitem
+         An important application of this principle involves the Domain
+         attribute. Setting the Domain attribute of the template has the
+         effect of restricting the search to a particular type of Frame
+         (with the domain you specify).  Conversely, if the Domain
+         attribute is not set in the template, then the domain of the
+         Frame found is not relevant, so all Frames are searched.  Note
+         that the
+         DOMAINLIST string provides an alternative way of restricting the
+         search in the same manner, but is a more convenient interface if
+         you wish to search automatically for another domain if the first
+         search fails.
+
+         \sstitem
+         Normally, a template will only match a Frame which has the
+         same number of axes as itself. However, for some classes of
+         template, this default behaviour may be changed by means of the
+         MinAxes, MaxAxes and \htmlref{MatchEnd}{MatchEnd} attributes. In addition, the
+         behaviour of a template may be influenced by its Permute and
+         PreserveAxes attributes, which control whether it matches Frames
+         whose axes have been permuted, and whether this permutation is
+         retained in the Frame which is returned (as opposed to returning
+         the axes in the order specified in the template, which is the
+         default behaviour). You should consult the descriptions of these
+         attributes for details of this more advanced use of templates.
+      }
+   }
+}
+\sstroutine{
+   AST\_FITSCHAN
+}{
+   Create a FitsChan
+}{
+   \sstdescription{
+      This function creates a new \htmlref{FitsChan}{FitsChan} and optionally initialises
+      its attributes.
+
+      A FitsChan is a specialised form of \htmlref{Channel}{Channel} which supports I/O
+      operations involving the use of FITS (Flexible Image Transport
+      \htmlref{System}{System}) header cards. Writing an \htmlref{Object}{Object} to a FitsChan (using
+      \htmlref{AST\_WRITE}{AST\_WRITE}) will, if the Object is suitable, generate a
+      description of that Object composed of FITS header cards, and
+      reading from a FitsChan will create a new Object from its FITS
+      header card description.
+
+      While a FitsChan is active, it represents a buffer which may
+      contain zero or more 80-character \texttt{"} header cards\texttt{"}  conforming to
+      FITS conventions. Any sequence of FITS-conforming header cards
+      may be stored, apart from the \texttt{"} END\texttt{"}  card whose existence is
+      merely implied.  The cards may be accessed in any order by using
+      the FitsChan\texttt{'} s integer \htmlref{Card}{Card} attribute, which identifies a \texttt{"} current\texttt{"}
+      card, to which subsequent operations apply. Searches
+      based on keyword may be performed (using \htmlref{AST\_FINDFITS}{AST\_FINDFITS}), new
+      cards may be inserted (\htmlref{AST\_PUTFITS}{AST\_PUTFITS}, \htmlref{AST\_PUTCARDS}{AST\_PUTCARDS}, \htmlref{AST\_SETFITS$<$X$>$}{AST\_SETFITS$<$X$>$}) and
+      existing ones may be deleted (\htmlref{AST\_DELFITS}{AST\_DELFITS}) or changed (AST\_SETFITS$<$X$>$).
+
+      When you create a FitsChan, you have the option of specifying
+      \texttt{"} source\texttt{"}  and \texttt{"} sink\texttt{"}  functions which connect it to external data
+      stores by reading and writing FITS header cards. If you provide
+      a source function, it is used to fill the FitsChan with header cards
+      when it is accessed for the first time. If you do not provide a
+      source function, the FitsChan remains empty until you explicitly enter
+      data into it (e.g. using AST\_PUTFITS, AST\_PUTCARDS, AST\_WRITE
+      or by using the \htmlref{SourceFile}{SourceFile} attribute to specifying a text file from
+      which headers should be read). When the FitsChan is deleted, any
+      remaining header cards in the FitsChan can be saved in either of
+      two ways: 1) by specifying a value for the \htmlref{SinkFile}{SinkFile} attribute (the
+      name of a text file to which header cards should be written), or 2)
+      by providing a sink function (used to to deliver header cards to an
+      external data store). If you do not provide a sink function or a
+      value for SinkFile, any header cards remaining when the FitsChan
+      is deleted will be lost, so you should arrange to extract them
+      first if necessary
+      (e.g. using AST\_FINDFITS or \htmlref{AST\_READ}{AST\_READ}).
+
+      Coordinate system information may be described using FITS header
+      cards using several different conventions, termed
+      \texttt{"} encodings\texttt{"} . When an AST Object is written to (or read from) a
+      FitsChan, the value of the FitsChan\texttt{'} s \htmlref{Encoding}{Encoding} attribute
+      determines how the Object is converted to (or from) a
+      description involving FITS header cards. In general, different
+      encodings will result in different sets of header cards to
+      describe the same Object. Examples of encodings include the DSS
+      encoding (based on conventions used by the STScI Digitised Sky
+      Survey data), the FITS-WCS encoding (based on a proposed FITS
+      standard) and the NATIVE encoding (a near loss-less way of
+      storing AST Objects in FITS headers).
+
+      The available encodings differ in the range of Objects they can
+      represent, in the number of Object descriptions that can coexist
+      in the same FitsChan, and in their accessibility to other
+      (external) astronomy applications (see the Encoding attribute
+      for details). Encodings are not necessarily mutually exclusive
+      and it may sometimes be possible to describe the same Object in
+      several ways within a particular set of FITS header cards by
+      using several different encodings.
+
+      The detailed behaviour of AST\_READ and AST\_WRITE, when used with
+      a FitsChan, depends on the encoding in use. In general, however,
+      all use of AST\_READ is destructive, so that FITS header cards
+      are consumed in the process of reading an Object, and are
+      removed from the FitsChan (this deletion can be prevented for
+      specific cards by calling the
+      \htmlref{AST\_RETAINFITS}{AST\_RETAINFITS} routine).
+
+      If the encoding in use allows only a single Object description
+      to be stored in a FitsChan (e.g. the DSS, FITS-WCS and FITS-IRAF
+      encodings), then write operations using AST\_WRITE will
+      over-write any existing Object description using that
+      encoding. Otherwise (e.g. the NATIVE encoding), multiple Object
+      descriptions are written sequentially and may later be read
+      back in the same sequence.
+   }
+   \sstinvocation{
+      RESULT = AST\_FITSCHAN( SOURCE, SINK, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         SOURCE = FUNCTION (Given)
+      }{
+         A source routine, which is a function taking two arguments: a
+         character argument of length 80 to contain a FITS card, and an
+         integer error status argument. It should return an integer value.
+         This function will be used by the FitsChan to obtain input
+         FITS header cards. On each invocation, it should read the
+         next input card from some external source (such as a FITS
+         file), and return the contents of the card via its character
+         argument. It should return a function result of one unless
+         there are no more cards to be read, in which case it should
+         return zero. If an error occurs, it should set its error
+         status argument to an error value before returning.
+
+         If the null routine AST\_NULL is supplied as the SOURCE value,
+         the FitsChan will remain empty until cards are explicitly
+         stored in it (e.g. using AST\_PUTCARDS, AST\_PUTFITS or via the
+         SourceFile attribute).
+      }
+      \sstsubsection{
+         SINK = SUBROUTINE (Given)
+      }{
+         A sink routine, which is a subroutine which takes two
+         arguments: a character argument of length 80 to contain a
+         FITS card, and an integer error status argument. If no
+         value has been set for the SinkFile attribute, this routine
+         will be used by the FitsChan to deliver any FITS header cards
+         it contains when it is finally deleted. On each invocation,
+         it should deliver the contents of the character string passed
+         to it as a FITS header card to some external data store (such
+         as a FITS file).  If an error occurs, it should set its error
+         status argument to an error value before returning.
+
+         If the null routine AST\_NULL is supplied as the SINK value,
+         and no value has been set for the SinkFile attribute, the
+         contents of the FitsChan will be lost when it is deleted.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new FitsChan. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+
+         Note, the FITSCHAN\_OPTIONS environment variable may be used
+         to specify default options for all newly created FitsChans.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_FITSCHAN = INTEGER
+      }{
+         A pointer to the new FitsChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The names of the routines supplied for the SOURCE and SINK
+         arguments should appear in EXTERNAL statements in the Fortran
+         routine which invokes AST\_FITSCHAN. However, this is not generally
+         necessary for the null routine AST\_NULL (so long as the AST\_PAR
+         include file has been used).
+
+         \sstitem
+         No FITS \texttt{"} END\texttt{"}  card will be written via the sink routine. You
+         should add this card yourself after the FitsChan has been
+         deleted.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+
+         \sstitem
+         Note that the null routine AST\_NULL (one underscore) is
+         different to AST\_\_NULL (two underscores), which is the null Object
+         pointer.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_FITSTABLE
+}{
+   Create a FitsTable
+}{
+   \sstdescription{
+      This function creates a new \htmlref{FitsTable}{FitsTable} and optionally initialises
+      its attributes.
+
+      The FitsTable class is a representation of a FITS binary table. It
+      inherits from the \htmlref{Table}{Table} class. The parent Table is used to hold the
+      binary data of the main table, and a \htmlref{FitsChan}{FitsChan} is used to hold the FITS
+      header. Note, there is no provision for binary data following the main
+      table (such data is referred to as a \texttt{"} heap\texttt{"}  in the FITS standard).
+
+      Note - it is not recommended to use the FitsTable class to store
+      very large tables.
+   }
+   \sstinvocation{
+      RESULT = AST\_FITSTABLE( HEADER, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         HEADER = INTEGER (Given)
+      }{
+         Pointer to an optional FitsChan containing headers to be stored
+         in the FitsTable.
+         AST\_\_NULL
+         may be supplied if the new FitsTable is to be left empty. If
+         supplied, and if the headers describe columns of a FITS binary
+         table, then equivalent (empty) columns are added to the FitsTable.
+         Each column has the same index in the FitsTable that it has in
+         the supplied header.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new FitsTable. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_FITSTABLE = INTEGER
+      }{
+         A pointer to the new FitsTable.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list described above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_FLUXFRAME
+}{
+   Create a FluxFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{FluxFrame}{FluxFrame} and optionally initialises
+      its attributes.
+
+      A FluxFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various systems used to represent the signal level in an
+      observation. The particular coordinate system to be used is specified
+      by setting the FluxFrame\texttt{'} s \htmlref{System}{System} attribute qualified, as necessary, by
+      other attributes such as the units, etc (see the description of the
+      System attribute for details).
+
+      All flux values are assumed to be measured at the same frequency or
+      wavelength (as given by the \htmlref{SpecVal}{SpecVal} attribute). Thus this class is
+      more appropriate for use with images rather than spectra.
+   }
+   \sstinvocation{
+      RESULT = AST\_FLUXFRAME( SPECVAL, SPECFRM, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         SPECVAL = DOUBLE PRECISION (Given)
+      }{
+         The spectral value to which the flux values refer, given in the
+         spectral coordinate system specified by
+         SPECFRM. The value supplied for the SPECVAL
+         parameter becomes the default value for the SpecVal attribute.
+         A value of AST\_\_BAD may be supplied if the spectral position is
+         unknown, but this may result in it not being possible for the
+         \htmlref{AST\_CONVERT}{AST\_CONVERT}
+         function to determine a \htmlref{Mapping}{Mapping} between the new FluxFrame and
+         some other FluxFrame.
+      }
+      \sstsubsection{
+         SPECFRM = INTEGER (Given)
+      }{
+         A pointer to a \htmlref{SpecFrame}{SpecFrame} describing the spectral coordinate system
+         in which the
+         SPECVAL
+         parameter is given. A deep copy of this object is taken, so any
+         subsequent changes to the SpecFrame using the supplied pointer will
+         have no effect on the new FluxFrame.
+         AST\_\_NULL can be supplied if AST\_\_BAD is supplied for SPECVAL.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new FluxFrame. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_FLUXFRAME = INTEGER
+      }{
+         A pointer to the new FluxFrame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When conversion between two FluxFrames is requested (as when
+         supplying FluxFrames AST\_CONVERT),
+         account will be taken of the nature of the flux coordinate systems
+         they represent, together with any qualifying attribute values, including
+         the \htmlref{AlignSystem}{AlignSystem} attribute. The results will therefore fully reflect the
+         relationship between positions measured in the two systems. In addition,
+         any difference in the Unit attributes of the two systems will also be
+         taken into account.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_FORMAT
+}{
+   Format a coordinate value for a Frame axis
+}{
+   \sstdescription{
+      This function returns a character string containing the
+      formatted (character) version of a coordinate value for a \htmlref{Frame}{Frame}
+      axis. The formatting applied is determined by the Frame\texttt{'} s
+      attributes and, in particular, by any Format attribute string
+      that has been set for the axis. A suitable default format (based
+      on the Digits attribute value) will be applied if necessary.
+   }
+   \sstinvocation{
+      RESULT = AST\_FORMAT( THIS, AXIS, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         AXIS = INTEGER (Given)
+      }{
+         The number of the Frame axis for which formatting is to be
+         performed (axis numbering starts at 1 for the first axis).
+      }
+      \sstsubsection{
+         VALUE = DOUBLE PRECISION (Given)
+      }{
+         The coordinate value to be formatted.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_FORMAT = CHARACTER $*$ ( AST\_\_SZCHR )
+      }{
+         The formatted value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A formatted value may be converted back into a numerical
+         (double precision) value using \htmlref{AST\_UNFORMAT}{AST\_UNFORMAT}.
+
+         \sstitem
+         A blank string will be returned if this function is invoked
+         with STATUS set to an error value, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_FRAME
+}{
+   Create a Frame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Frame}{Frame} and optionally initialises its
+      attributes.
+
+      A Frame is used to represent a coordinate system. It does this
+      in rather the same way that a frame around a graph describes the
+      coordinate space in which data are plotted. Consequently, a
+      Frame has a \htmlref{Title}{Title} (string) attribute, which describes the
+      coordinate space, and contains axes which in turn hold
+      information such as Label and Units strings which are used for
+      labelling (e.g.) graphical output. In general, however, the
+      number of axes is not restricted to two.
+
+      Functions are available for converting Frame coordinate values
+      into a form suitable for display, and also for calculating
+      distances and offsets between positions within the Frame.
+
+      Frames may also contain knowledge of how to transform to and
+      from related coordinate systems.
+   }
+   \sstinvocation{
+      RESULT = AST\_FRAME( NAXES, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NAXES = INTEGER (Given)
+      }{
+         The number of Frame axes (i.e. the number of dimensions of
+         the coordinate space which the Frame describes).
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Frame. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_FRAME = INTEGER
+      }{
+         A pointer to the new Frame.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         FRAME = AST\_FRAME( 2, \texttt{'} Title=Energy Spectrum\texttt{'} , STATUS );
+      }{
+         Creates a new 2-dimensional Frame and initialises its Title
+         attribute to the string \texttt{"} Energy Spectrum\texttt{"} .
+      }
+      \sstexamplesubsection{
+         FRAME = AST\_FRAME( 2, \texttt{'} Label(1)=Energy, Label(2)=Response\texttt{'} , STATUS );
+      }{
+         Creates a new 2-dimensional Frame and initialises its axis
+         Label attributes to suitable string values.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_FRAMESET
+}{
+   Create a FrameSet
+}{
+   \sstdescription{
+      This function creates a new \htmlref{FrameSet}{FrameSet} and optionally initialises
+      its attributes.
+
+      A FrameSet consists of a set of one or more Frames (which
+      describe coordinate systems), connected together by Mappings
+      (which describe how the coordinate systems are inter-related). A
+      FrameSet makes it possible to obtain a \htmlref{Mapping}{Mapping} between any pair
+      of these Frames (i.e. to convert between any of the coordinate
+      systems which it describes).  The individual Frames are
+      identified within the FrameSet by an integer index, with Frames
+      being numbered consecutively from one as they are added to the
+      FrameSet.
+
+      Every FrameSet has a \texttt{"} base\texttt{"}  \htmlref{Frame}{Frame} and a \texttt{"} current\texttt{"}  Frame (which
+      are allowed to be the same). Any of the Frames may be nominated
+      to hold these positions, and the choice is determined by the
+      values of the FrameSet\texttt{'} s \htmlref{Base}{Base} and \htmlref{Current}{Current} attributes, which hold
+      the indices of the relevant Frames.  By default, the first Frame
+      added to a FrameSet is its base Frame, and the last one added is
+      its current Frame.
+
+      The base Frame describes the \texttt{"} native\texttt{"}  coordinate system of
+      whatever the FrameSet is used to calibrate (e.g. the pixel
+      coordinates of an image) and the current Frame describes the
+      \texttt{"} apparent\texttt{"}  coordinate system in which it should be viewed
+      (e.g. displayed, etc.). Any further Frames represent a library
+      of alternative coordinate systems, which may be selected by
+      making them current.
+
+      When a FrameSet is used in a context that requires a Frame,
+      (e.g. obtaining its \htmlref{Title}{Title} value, or number of axes), the current
+      Frame is used. A FrameSet may therefore be used in place of its
+      current Frame in most situations.
+
+      When a FrameSet is used in a context that requires a Mapping,
+      the Mapping used is the one between its base Frame and its
+      current Frame. Thus, a FrameSet may be used to convert \texttt{"} native\texttt{"}
+      coordinates into \texttt{"} apparent\texttt{"}  ones, and vice versa. Like any
+      Mapping, a FrameSet may also be inverted (see \htmlref{AST\_INVERT}{AST\_INVERT}), which
+      has the effect of interchanging its base and current Frames and
+      hence of reversing the Mapping between them.
+
+      Regions may be added into a FrameSet (since a \htmlref{Region}{Region} is a type of
+      Frame), either explicitly or as components within CmpFrames. In this
+      case the Mapping between a pair of Frames within a FrameSet will
+      include the effects of the clipping produced by any Regions included
+      in the path between the Frames.
+   }
+   \sstinvocation{
+      RESULT = AST\_FRAMESET( FRAME, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         Pointer to the first Frame to be inserted into the
+         FrameSet. This initially becomes both the base and the
+         current Frame. (Further Frames may be added using the
+         \htmlref{AST\_ADDFRAME}{AST\_ADDFRAME} routine.)
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new FrameSet. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_FRAMESET
+      }{
+         A pointer to the new FrameSet.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If a pointer to an existing FrameSet is given for the FRAME
+         argument, then the new FrameSet will (as a special case) be
+         initialised to contain the same Frames and Mappings, and to have
+         the same attribute values, as the one supplied. This process is
+         similar to making a copy of a FrameSet (see \htmlref{AST\_COPY}{AST\_COPY}), except
+         that the Frames and Mappings contained in the original are not
+         themselves copied, but are shared by both FrameSets.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GENCURVE
+}{
+   Draw a generalized curve
+}{
+   \sstdescription{
+      This routine draws a general user-defined curve defined by the
+      supplied \htmlref{Mapping}{Mapping}. Note that the curve is transformed into graphical
+      coordinate space for plotting, so that a straight line in
+      physical coordinates may result in a curved line being drawn if
+      the Mapping involved is non-linear. Any discontinuities in the
+      Mapping between physical and graphical coordinates are
+      catered for, as is any clipping established using \htmlref{AST\_CLIP}{AST\_CLIP}.
+
+      If you need to draw simple straight lines (geodesics), \htmlref{AST\_CURVE}{AST\_CURVE}
+      or \htmlref{AST\_POLYCURVE}{AST\_POLYCURVE} will usually be easier to use and faster.
+   }
+   \sstinvocation{
+      CALL AST\_GENCURVE( THIS, MAP )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Plot}{Plot}.
+      }
+      \sstsubsection{
+         MAP = INTEGER (Given)
+      }{
+         Pointer to a Mapping. This Mapping should have 1 input
+         coordinate representing offset along the required curve,
+         normalized so that the start of the curve is at offset 0.0,
+         and the end of the curve is at offset 1.0. Note, this offset
+         does not need to be linearly related to distance along the curve.
+         The number of output coordinates should equal the number of axes
+         in the current \htmlref{Frame}{Frame} of the Plot. The Mapping should map a
+         specified offset along the curve, into the corresponding
+         coordinates in the current Frame of the Plot. The inverse
+         transformation need not be defined.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error results if the base Frame of the Plot is not 2-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   AST\_GET$<$X$>$
+}{
+   Get an attribute value for an Object
+}{
+   \sstdescription{
+      This is a family of functions which return a specified attribute
+      value for an \htmlref{Object}{Object} using one of several different data
+      types. The type is selected by replacing $<$X$>$ in the function name
+      by C, D, I, L or R, to obtain a result in Character, Double
+      precision, Integer, Logical or Real format, respectively.
+
+      If possible, the attribute value is converted to the type you
+      request. If conversion is not possible, an error will result.
+   }
+   \sstinvocation{
+      RESULT = AST\_GET$<$X$>$( THIS, ATTRIB, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         ATTRIB = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing the name of the attribute whose
+         value is required.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         These functions apply to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GET$<$X$>$ = $<$X$>$type
+      }{
+         The attribute value, in the data type corresponding to $<$X$>$.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         WRITE( $*$, \texttt{'} (\texttt{'} \texttt{'}  \htmlref{RefCount}{RefCount} = \texttt{'} \texttt{'} , A10 )\texttt{'}  ) AST\_GETC( Z, \texttt{'} RefCount\texttt{'} , STATUS )
+      }{
+         Prints the RefCount attribute value for Object Z as a character
+         string.
+      }
+      \sstexamplesubsection{
+         NAXES = AST\_GETI( FRAME, \texttt{'} \htmlref{Naxes}{Naxes}\texttt{'} , STATUS )
+      }{
+         Obtains the value of the Naxes attribute for Object FRAME as an
+         integer.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         An appropriate \texttt{"} null\texttt{"}  value will be returned if this function
+         is invoked with STATUS set to an error value, or if it should
+         fail for any reason. This null value is zero for numeric
+         values, .FALSE. for logical values, and blank for character values.
+
+         \sstitem
+         Numerical attribute values of zero translate to logical value
+         .FALSE. and all other numerical values translate to .TRUE..
+      }
+   }
+}
+\sstroutine{
+   AST\_GETACTIVEUNIT
+}{
+   Determines how the Unit attribute will be used
+}{
+   \sstdescription{
+      This routine
+      returns the current value of the ActiveUnit flag for a \htmlref{Frame}{Frame}. See
+      the description of the \htmlref{AST\_SETACTIVEUNIT}{AST\_SETACTIVEUNIT} routine
+      for a description of the ActiveUnit flag.
+   }
+   \sstinvocation{
+      RESULT = AST\_GETACTIVEUNIT( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETACTIVEUNIT = LOGICAL
+      }{
+         The current value of the ActiveUnit flag.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of .FALSE. will be returned if this function is
+         invoked with STATUS set to an error value, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETCOLUMNDATA
+}{
+   Retrieve all the data values stored in a column
+}{
+   \sstdescription{
+      This routine
+      copies all data values from a named column into a supplied buffer
+   }
+   \sstinvocation{
+      CALL AST\_GETCOLUMNDATA( THIS, COLUMN, RNULL, DNULL, MXSIZE,
+                              COLDATA, NELEM, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{FitsTable}{FitsTable}.
+      }
+      \sstsubsection{
+         COLUMN = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string holding the name of the column. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         RNULL = REAL (Given)
+      }{
+         The value to return in
+         COLDATA
+         for any cells for which no value has been stored in the
+         FitsTable. Ignored if the column\texttt{'} s data type is not
+         AST\_\_FLOATTYPE. Supplying
+         AST\_\_NANR
+         will cause a single precision IEEE NaN value to be used.
+      }
+      \sstsubsection{
+         DNULL = REAL (Given)
+      }{
+         The value to return in
+         COLDATA
+         for any cells for which no value has been stored in the
+         FitsTable. Ignored if the column\texttt{'} s data type is not
+         AST\_\_DOUBLETYPE. Supplying AST\_\_NAN will cause a double precision
+         IEEE NaN value to be used.
+      }
+      \sstsubsection{
+         MXSIZE = INTEGER (Given)
+      }{
+         The size of the
+         COLDATA
+         array, in bytes. The amount of memory needed to hold the data
+         from a column may be determined using
+         \htmlref{AST\_COLUMNSIZE}{AST\_COLUMNSIZE}.
+         If the supplied array is too small to hold all the column data,
+         trailing column values will be omitted from the returned array,
+         but no error will be reported.
+      }
+      \sstsubsection{
+         COLDATA( $*$ ) = BYTE (Given)
+      }{
+         An
+         area of memory in which to return the data
+         values currently stored in the column. The values are stored in
+         row order. If the column holds non-scalar values, the elements
+         of each value are stored in \texttt{"} Fortran\texttt{"}  order. No data type
+         conversion is performed - the data type of each returned value
+         is the data type associated with the column when the column was
+         added to the table. If the column holds strings, the returned
+         strings will be null terminated. Any excess room at the end of
+         the array will be left unchanged.
+      }
+      \sstsubsection{
+         NELEM = INTEGER (Return)
+      }{
+         The number of elements returned in the
+         COLDATA
+         array. This is the product of the number of rows returned and
+         the number of elements in each column value.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The RNULL and DNULL arguments
+         specify the value to be returned for any empty cells within columns
+         holding floating point values. For columns holding integer values,
+         the value returned for empty cells is the value returned by the
+         \htmlref{AST\_COLUMNNULL}{AST\_COLUMNNULL} functiom.
+         For columns holding string values, the ASCII NULL character is returned
+         for empty cells.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETFITS$<$X$>$
+}{
+   Get a named keyword value from a FitsChan
+}{
+   \sstdescription{
+      This is a family of functions which gets a value for a named keyword,
+      or the value of the current card, from a \htmlref{FitsChan}{FitsChan} using one of several
+      different data types. The data type of the returned value is selected
+      by replacing $<$X$>$ in the function name by one of the following strings
+      representing the recognised FITS data types:
+
+      \sstitemlist{
+
+         \sstitem
+         CF - Complex floating point values.
+
+         \sstitem
+         CI - Complex integer values.
+
+         \sstitem
+         F  - Floating point values.
+
+         \sstitem
+         I  - Integer values.
+
+         \sstitem
+         L  - Logical (i.e. boolean) values.
+
+         \sstitem
+         S  - String values.
+
+         \sstitem
+         CN - A \texttt{"} CONTINUE\texttt{"}  value, these are treated like string values, but
+                are encoded without an equals sign.
+
+      }
+      The data type of the \texttt{"} value\texttt{"}
+      argument
+
+      depends on $<$X$>$ as follows:
+
+      \sstitemlist{
+
+         \sstitem
+         CF - DOUBLE PRECISION(2) (a 2 element array to hold the real and
+                imaginary parts of the complex value).
+
+         \sstitem
+         CI - INTEGER(2) (a 2 element array to hold the real and imaginary
+                parts of the complex value).
+
+         \sstitem
+         F  - DOUBLE PRECISION.
+
+         \sstitem
+         I  - INTEGER
+
+         \sstitem
+         L  - LOGICAL
+
+         \sstitem
+         S  - CHARACTER
+
+         \sstitem
+         CN - CHARACTER
+      }
+   }
+   \sstinvocation{
+      RESULT = AST\_GETFITS$<$X$>$( THIS, NAME, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string
+         containing the FITS keyword name. This may be a complete FITS
+         header card, in which case the keyword to use is extracted from
+         it. No more than 80 characters are read from this string. If
+         a single dot \texttt{'} .\texttt{'}
+         is supplied, the value of the current card is returned.
+      }
+      \sstsubsection{
+         VALUE = $<$X$>$type (Returned)
+      }{
+         A
+         buffer to receive the keyword value. The data type depends on $<$X$>$
+         as described above. The conents of the buffer on entry are left
+         unchanged if the keyword is not found.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETFITS$<$X$>$ = LOGICAL
+      }{
+         .FALSE.
+         is returned if the keyword was not found in the FitsChan (no error
+         is reported). Otherwise, a value of
+         .TRUE.
+         is returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If a name is supplied, the card following the current card is
+         checked first. If this is not the required card, then the rest of the
+         FitsChan is searched, starting with the first card added to the
+         FitsChan. Therefore cards should be accessed in the order they are
+         stored in the FitsChan (if possible) as this will minimise the time
+         spent searching for cards.
+
+         \sstitem
+         If the requested card is found, it becomes the current card,
+         otherwise the current card is left pointing at the \texttt{"} end-of-file\texttt{"} .
+
+         \sstitem
+         If the stored keyword value is not of the requested type, it is
+         converted into the requested type.
+
+         \sstitem
+         If the keyword is found in the FitsChan, but has no associated
+         value, an error is reported. If necessary, the
+         \htmlref{AST\_TESTFITS}{AST\_TESTFITS}
+         function can be used to determine if the keyword has a defined
+         value in the FitsChan prior to calling this function.
+
+         \sstitem
+         An error will be reported if the keyword name does not conform
+         to FITS requirements.
+
+         \sstitem
+         .FALSE.
+         is returned as the function value if an error has already occurred,
+         or if this function should fail for any reason.
+
+         \sstitem
+         The FITS standard says that string keyword values should be
+         padded with trailing spaces if they are shorter than 8 characters.
+         For this reason, trailing spaces are removed from the string
+         returned by
+         AST\_GETFITSS
+         if the original string (including any trailing spaces) contains 8
+         or fewer characters. Trailing spaces are not removed from longer
+         strings.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETFRAME
+}{
+   Obtain a pointer to a specified Frame in a FrameSet
+}{
+   \sstdescription{
+      This function returns a pointer to a specified \htmlref{Frame}{Frame} in a
+      \htmlref{FrameSet}{FrameSet}.
+   }
+   \sstinvocation{
+      RESULT = AST\_GETFRAME( THIS, IFRAME, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         IFRAME = INTEGER (Given)
+      }{
+         The index of the required Frame within the FrameSet.  This
+         value should lie in the range from 1 to the number of Frames
+         in the FrameSet (as given by its \htmlref{Nframe}{Nframe} attribute).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETFRAME = INTEGER
+      }{
+         A pointer to the requested Frame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of AST\_\_BASE or AST\_\_CURRENT may be given for the
+         IFRAME argument to specify the base Frame or the current
+         Frame respectively.
+
+         \sstitem
+         This function increments the \htmlref{RefCount}{RefCount} attribute of the
+         selected Frame by one.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETGRFCONTEXT
+}{
+   Return the KeyMap that describes a Plot\texttt{'} s graphics context
+}{
+   \sstdescription{
+      This routine
+      returns a reference to a \htmlref{KeyMap}{KeyMap} that will be passed to any drawing
+      routines registered using \htmlref{AST\_GRFSET}{AST\_GRFSET}.
+      This KeyMap can be used by an application to pass information to
+      the drawing routines
+      about the context in which they are being called. The contents of
+      the KeyMap are never accessed byt the \htmlref{Plot}{Plot} class itself.
+   }
+   \sstinvocation{
+      RESULT = AST\_GETGRFCONTEXT( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETGRFCONTEXT = INTEGER
+      }{
+         A pointer to the graphics context KeyMap. The returned pointer
+         should be annulled when it is no longer needed.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETLINE
+}{
+   Obtain text to be written by a Channel sink routine
+}{
+   \sstdescription{
+      This routine should only be used when implementing a routine
+      which will be passed as the SINK argument to \htmlref{AST\_CHANNEL}{AST\_CHANNEL}. It
+      should be used to obtain (from the AST library) each line of
+      text which is to be written to the external data sink. One such
+      line should be obtained in this way for each invocation of the
+      sink routine.
+   }
+   \sstinvocation{
+      CALL AST\_GETLINE( LINE, L, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         LINE = CHARACTER $*$ ( $*$ ) (Returned)
+      }{
+         The line of text to be written. Depending on the length of
+         character variable supplied, the returned text may be
+         truncated if necessary. Note, however, that it will not be
+         padded with blanks in order to fill this variable.
+      }
+      \sstsubsection{
+         L = INTEGER (Returned)
+      }{
+         The number of characters returned, which may be zero. Note
+         that characters beyond the L\texttt{'} th character in the LINE
+         variable are not modified and may therefore contain junk.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine is only available in the Fortran interface to the
+         AST library.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETMAPPING
+}{
+   Obtain a Mapping that converts between two Frames in a FrameSet
+}{
+   \sstdescription{
+      This function returns a pointer to a \htmlref{Mapping}{Mapping} that will convert
+      coordinates between the coordinate systems represented by two
+      Frames in a \htmlref{FrameSet}{FrameSet}.
+   }
+   \sstinvocation{
+      RESULT = AST\_GETMAPPING( THIS, IFRAME1, IFRAME2, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         IFRAME1 = INTEGER (Given)
+      }{
+         The index of the first \htmlref{Frame}{Frame} in the FrameSet. This Frame describes
+         the coordinate system for the \texttt{"} input\texttt{"}  end of the Mapping.
+      }
+      \sstsubsection{
+         IFRAME2 = INTEGER (Given)
+      }{
+         The index of the second Frame in the FrameSet. This Frame
+         describes the coordinate system for the \texttt{"} output\texttt{"}  end of the
+         Mapping.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETMAPPING = INTEGER
+      }{
+         Pointer to a Mapping whose forward transformation converts
+         coordinates from the first coordinate system to the second
+         one, and whose inverse transformation converts coordinates in
+         the opposite direction.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned Mapping will include the clipping effect of any
+         Regions which occur on the path between the two supplied Frames
+         (this includes the two supplied Frames themselves).
+
+         \sstitem
+         The values given for the IFRAME1 and IFRAME2 arguments
+         should lie in the range from 1 to the number of Frames in the
+         FrameSet (as given by its \htmlref{Nframe}{Nframe} attribute). A value of
+         AST\_\_BASE or AST\_\_CURRENT may also be given to identify the
+         FrameSet\texttt{'} s base Frame or current Frame respectively.  It is
+         permissible for both these arguments to have the same value, in
+         which case a unit Mapping (\htmlref{UnitMap}{UnitMap}) is returned.
+
+         \sstitem
+         It should always be possible to generate the Mapping
+         requested, but this does necessarily guarantee that it will be
+         able to perform the required coordinate conversion. If
+         necessary, the \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse} attributes of the
+         returned Mapping should be inspected to determine if the
+         required transformation is available.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETREFPOS
+}{
+   Return the reference position in a specified celestial coordinate system
+}{
+   \sstdescription{
+      This routine
+      returns the reference position (specified by attributes \htmlref{RefRA}{RefRA} and
+      \htmlref{RefDec}{RefDec}) converted to the celestial coordinate system represented by
+      a supplied \htmlref{SkyFrame}{SkyFrame}. The celestial longitude and latitude values
+      are returned in radians.
+   }
+   \sstinvocation{
+      CALL AST\_GETREFPOS( THIS, FRM, LON, LAT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{SpecFrame}{SpecFrame}.
+      }
+      \sstsubsection{
+         FRM = INTEGER (Given)
+      }{
+         Pointer to the SkyFrame which defines the required celestial
+         coordinate system.
+         If AST\_\_NULL
+         is supplied, then the longitude and latitude values are returned
+         as FK5 J2000 RA and Dec values.
+      }
+      \sstsubsection{
+         LON = DOUBLE PRECISION (Returned)
+      }{
+         The
+         longitude of the reference point, in the coordinate system
+         represented by the supplied SkyFrame (radians).
+      }
+      \sstsubsection{
+         LAT = DOUBLE PRECISION (Returned)
+      }{
+         The
+         latitude of the reference point, in the coordinate system
+         represented by the supplied SkyFrame (radians).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Values of AST\_\_BAD will be returned if this function is
+         invoked with STATUS set to an error value, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETREGIONBOUNDS
+}{
+   Returns the bounding box of Region
+}{
+   \sstdescription{
+      This routine
+      returns the upper and lower limits of a box which just encompasses
+      the supplied \htmlref{Region}{Region}. The limits are returned as axis values within
+      the \htmlref{Frame}{Frame} represented by the Region. The value of the \htmlref{Negated}{Negated}
+      attribute is ignored (i.e. it is assumed that the Region has not
+      been negated).
+   }
+   \sstinvocation{
+      CALL AST\_GETREGIONBOUNDS( THIS, LBND, UBND, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         LBND() = DOUBLE PRECISION (Returned)
+      }{
+         An
+         array in which to return the lower axis bounds covered by the Region.
+         It should have at least as many elements as there are axes in the
+         Region. If an axis has no lower limit, the returned value will
+         be the largest possible negative value.
+      }
+      \sstsubsection{
+         UBND() = DOUBLE PRECISION (Returned)
+      }{
+         An
+         array in which to return the upper axis bounds covered by the Region.
+         It should have at least as many elements as there are axes in the
+         Region. If an axis has no upper limit, the returned value will
+         be the largest possible positive value.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The value of the Negated attribute is ignored (i.e. it is assumed that
+         the Region has not been negated).
+
+         \sstitem
+         If an axis has no extent on an axis then the lower limit will be
+         returned larger than the upper limit. Note, this is different to an
+         axis which has a constant value (in which case both lower and upper
+         limit will be returned set to the constant value).
+
+         \sstitem
+         If the bounds on an axis cannot be determined, AST\_\_BAD is returned for
+         both upper and lower bounds
+      }
+   }
+}
+\sstroutine{
+   AST\_GETREGIONFRAME
+}{
+   Obtain a pointer to the encapsulated Frame within a Region
+}{
+   \sstdescription{
+      This function returns a pointer to the \htmlref{Frame}{Frame} represented by a
+      \htmlref{Region}{Region}.
+   }
+   \sstinvocation{
+      RESULT = AST\_GETREGIONFRAME( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETREGIONFRAME = INTEGER
+      }{
+         A pointer to a deep copy of the Frame represented by the Region.
+         Using this pointer to modify the Frame will have no effect on
+         the Region. To modify the Region, use the Region pointer directly.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETREGIONFRAMESET
+}{
+   Obtain a pointer to the encapsulated FrameSet within a Region
+}{
+   \sstdescription{
+      This function returns a pointer to the \htmlref{FrameSet}{FrameSet} encapsulated by a
+      \htmlref{Region}{Region}. The base \htmlref{Frame}{Frame} is the Frame in which the box was originally
+      defined, and the current Frame is the Frame into which the Region
+      is currently mapped (i.e. it will be the same as the Frame returned
+      by \htmlref{AST\_GETREGIONFRAME}{AST\_GETREGIONFRAME}).
+   }
+   \sstinvocation{
+      RESULT = AST\_GETREGIONFRAMESET( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETREGIONFRAMESET = INTEGER
+      }{
+         A pointer to a deep copy of the FrameSet represented by the Region.
+         Using this pointer to modify the FrameSet will have no effect on
+         the Region.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETREGIONMESH
+}{
+   Return a mesh of points covering the surface or volume of a Region
+}{
+   \sstdescription{
+      This routine
+      returns the axis values at a mesh of points either covering the
+      surface (i.e. boundary) of the supplied \htmlref{Region}{Region}, or filling the
+      interior (i.e. volume) of the Region. The number of points in
+      the mesh is approximately equal to the \htmlref{MeshSize}{MeshSize} attribute.
+   }
+   \sstinvocation{
+      CALL AST\_GETREGIONMESH( THIS, SURFACE, MAXPOINT, MAXCOORD, NPOINT,
+                              POINTS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         SURFACE = LOGICAL (Given)
+      }{
+         If .TRUE.,
+         the returned points will cover the surface or the Region.
+         Otherwise, they will fill the interior of the Region.
+      }
+      \sstsubsection{
+         MAXPOINT = INTEGER (Given)
+      }{
+         If zero, the number of points in the mesh is returned in
+         NPOINT,
+         but no axis values are returned and all other parameters are ignored.
+         If not zero, the supplied value should be the length of the
+         first dimension of the POINTS
+         array. An error is reported if the number of points in the mesh
+         exceeds this number.
+      }
+      \sstsubsection{
+         MAXCOORD = INTEGER (Given)
+      }{
+         The length of the
+         second dimension of the POINTS array.
+         An error is reported if the number of axes in the supplied Region
+         exceeds this number.
+      }
+      \sstsubsection{
+         NPOINT = INTEGER (Returned)
+      }{
+         The
+         number of points in the returned mesh.
+      }
+      \sstsubsection{
+         POINTS( MAXPOINT, MAXCOORD ) = DOUBLE PRECISION (Returned)
+      }{
+         An array in which to return the coordinates values at the mesh
+         positions. These are stored such that the value of coordinate
+         number COORD for point number POINT is found in element
+         POINTS(POINT,COORD).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error is reported if the Region is unbounded.
+
+         \sstitem
+         If the coordinate system represented by the Region has been
+         changed since it was first created, the returned axis values refer
+         to the new (changed) coordinate system, rather than the original
+         coordinate system. Note however that if the transformation from
+         original to new coordinate system is non-linear, the shape within
+         the new coordinate system may be distorted, and so may not match
+         that implied by the name of the Region subclass (\htmlref{Circle}{Circle}, \htmlref{Box}{Box}, etc).
+      }
+   }
+}
+\sstroutine{
+   AST\_GETREGIONPOINTS
+}{
+   Returns the positions that define the given Region
+}{
+   \sstdescription{
+      This routine
+      returns the axis values at the points that define the supplied
+      \htmlref{Region}{Region}. The particular meaning of these points will depend on the
+      type of class supplied, as listed below under \texttt{"} Applicability:\texttt{"} .
+   }
+   \sstinvocation{
+      CALL AST\_GETREGIONPOINTS( THIS, MAXPOINT, MAXCOORD, NPOINT, POINTS,
+                                STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         MAXPOINT = INTEGER (Given)
+      }{
+         If zero, the number of points needed to define the Region is
+         returned in
+         NPOINT,
+         but no axis values are returned and all other parameters are ignored.
+         If not zero, the supplied value should be the length of the
+         first dimension of the POINTS
+         array. An error is reported if the number of points needed to define
+         the Region exceeds this number.
+      }
+      \sstsubsection{
+         MAXCOORD = INTEGER (Given)
+      }{
+         The length of the
+         second dimension of the POINTS array.
+         An error is reported if the number of axes in the supplied Region
+         exceeds this number.
+      }
+      \sstsubsection{
+         NPOINT = INTEGER (Returned)
+      }{
+         The
+         number of points defining the Region.
+      }
+      \sstsubsection{
+         POINTS( MAXPOINT, MAXCOORD ) = DOUBLE PRECISION (Returned)
+      }{
+         An array in which to return the coordinates values at the
+         positions that define the Region. These are stored such that the
+         value of coordinate number COORD for point number POINT
+         is found in element POINTS(POINT,COORD).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Box}{Box}
+      }{
+         The first returned position is the Box centre, and the second is
+         a Box corner.
+      }
+      \sstsubsection{
+         \htmlref{Circle}{Circle}
+      }{
+         The first returned position is the Circle centre, and the second is
+         a point on the circumference.
+      }
+      \sstsubsection{
+         \htmlref{CmpRegion}{CmpRegion}
+      }{
+         Returns a value of zero for
+         NPOINT
+         and leaves the supplied array contents unchanged. To find the
+         points defining a CmpRegion, use this method on the component
+         Regions, which can be accessed by invoking
+         \htmlref{AST\_DECOMPOSE}{AST\_DECOMPOSE}
+         on the CmpRegion.
+      }
+      \sstsubsection{
+         \htmlref{Ellipse}{Ellipse}
+      }{
+         The first returned position is the Ellipse centre. The second is
+         the end of one of the axes of the ellipse. The third is some
+         other point on the circumference of the ellipse, distinct from
+         the second point.
+      }
+      \sstsubsection{
+         \htmlref{Interval}{Interval}
+      }{
+         The first point corresponds to the lower bounds position, and
+         the second point corresponds to the upper bounds position. These
+         are reversed to indicate an extcluded interval rather than an
+         included interval. See the Interval constructor for more
+         information.
+      }
+      \sstsubsection{
+         \htmlref{NullRegion}{NullRegion}
+      }{
+         Returns a value of zero for
+         NPOINT
+         and leaves the supplied array contents unchanged.
+      }
+      \sstsubsection{
+         \htmlref{PointList}{PointList}
+      }{
+         The positions returned are those that were supplied when the
+         PointList was constructed.
+      }
+      \sstsubsection{
+         \htmlref{Polygon}{Polygon}
+      }{
+         The positions returned are the vertex positions that were supplied
+         when the Polygon was constructed.
+      }
+      \sstsubsection{
+         \htmlref{Prism}{Prism}
+      }{
+         Returns a value of zero for
+         NPOINT
+         and leaves the supplied array contents unchanged. To find the
+         points defining a Prism, use this method on the component
+         Regions, which can be accessed by invoking
+         AST\_DECOMPOSE
+         on the CmpRegion.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the coordinate system represented by the Region has been
+         changed since it was first created, the returned axis values refer
+         to the new (changed) coordinate system, rather than the original
+         coordinate system. Note however that if the transformation from
+         original to new coordinate system is non-linear, the shape within
+         the new coordinate system may be distorted, and so may not match
+         that implied by the name of the Region subclass (Circle, Box, etc).
+      }
+   }
+}
+\sstroutine{
+   AST\_GETSTCCOORD
+}{
+   Return information about an AstroCoords element stored in an Stc
+}{
+   \sstdescription{
+      When any sub-class of \htmlref{Stc}{Stc} is created, the constructor function
+      allows one or more AstroCoords elements to be stored within the Stc.
+      This function allows any one of these AstroCoords elements to be
+      retrieved. The format of the returned information is the same as
+      that used to pass the original information to the Stc constructor.
+      That is, the information is returned in a \htmlref{KeyMap}{KeyMap} structure
+      containing elements with one or more of the keys given by symbolic
+      constants AST\_\_STCNAME, AST\_\_STCVALUE, AST\_\_STCERROR, AST\_\_STCRES,
+      AST\_\_STCSIZE and AST\_\_STCPIXSZ.
+
+      If the coordinate system represented by the Stc has been changed
+      since it was created (for instance, by changing its \htmlref{System}{System}
+      attribute), then the sizes and positions in the returned KeyMap
+      will reflect the change in coordinate system.
+   }
+   \sstinvocation{
+      RESULT = AST\_GETSTCCOORD( THIS, ICOORD, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Stc.
+      }
+      \sstsubsection{
+         ICOORD = INTEGER (Given)
+      }{
+         The index of the AstroCoords element required. The first has index
+         one. The number of AstroCoords elements in the Stc can be found using
+         function \htmlref{AST\_GETSTCNCOORD}{AST\_GETSTCNCOORD}.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETSTCCOORD = INTEGER
+      }{
+         A pointer to a new KeyMap containing the required information.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETSTCNCOORD
+}{
+   Return the number of AstroCoords elements stored in an Stc
+}{
+   \sstdescription{
+      This function returns the number of AstroCoords elements stored in
+      an \htmlref{Stc}{Stc}.
+   }
+   \sstinvocation{
+      RESULT = AST\_GETSTCNCOORD( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Stc.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETSTCNCOORD = INTEGER
+      }{
+         The number of  AstroCoords elements stored in the Stc.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Zero will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETSTCREGION
+}{
+   Obtain a copy of the encapsulated Region within a Stc
+}{
+   \sstdescription{
+      This function returns a pointer to a deep copy of the \htmlref{Region}{Region}
+      supplied when the \htmlref{Stc}{Stc} was created.
+   }
+   \sstinvocation{
+      RESULT = AST\_GETSTCREGION( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Stc.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETSTCREGION = INTEGER
+      }{
+         A pointer to a deep copy of the Region encapsulated within the
+         supplied Stc.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETTABLES
+}{
+   Retrieve any FitsTables currently in a FitsChan
+}{
+   \sstdescription{
+      If the supplied \htmlref{FitsChan}{FitsChan} currently contains any tables, then this
+      function returns a pointer to a \htmlref{KeyMap}{KeyMap}. Each entry in the KeyMap
+      is a pointer to a \htmlref{FitsTable}{FitsTable} holding the data for a FITS binary
+      table. The key used to access each entry is the FITS extension
+      name in which the table should be stored.
+
+      Tables can be present in a FitsChan as a result either of using the
+      \htmlref{AST\_PUTTABLE}{AST\_PUTTABLE} (or \htmlref{AST\_PUTTABLES}{AST\_PUTTABLES})
+      method to store existing tables in the FitsChan, or of using the
+      \htmlref{AST\_WRITE}{AST\_WRITE}
+      method to write a \htmlref{FrameSet}{FrameSet} to the FitsChan. For the later case, if
+      the FitsChan \texttt{"} \htmlref{TabOK}{TabOK}\texttt{"}  attribute is positive and the FrameSet requires
+      a look-up table to describe one or more axes, then the \texttt{"} -TAB\texttt{"}
+      algorithm code described in FITS-WCS paper III is used and the table
+      values are stored in the FitsChan in the form of a FitsTable object
+      (see the documentation for the \texttt{"} TabOK\texttt{"}  attribute).
+   }
+   \sstinvocation{
+      RESULT = AST\_GETTABLES( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETTABLES = INTEGER
+      }{
+         A pointer to a deep copy of the KeyMap holding the tables currently
+         in the FitsChan, or
+         AST\_\_NULL
+         if the FitsChan does not contain any tables. The returned
+         pointer should be annulled using
+         \htmlref{AST\_ANNUL}{AST\_ANNUL}
+         when no longer needed.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GETUNC
+}{
+   Obtain uncertainty information from a Region
+}{
+   \sstdescription{
+      This function returns a \htmlref{Region}{Region} which represents the uncertainty
+      associated with positions within the supplied Region. See
+      \htmlref{AST\_SETUNC}{AST\_SETUNC}
+      for more information about Region uncertainties and their use.
+   }
+   \sstinvocation{
+      RESULT = AST\_GETUNC( THIS, DEF, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         DEF = LOGICAL (Given)
+      }{
+         Controls what is returned if no uncertainty information has been
+         associated explicitly with the supplied Region. If
+         .TRUE.
+         is supplied, then the default uncertainty Region used internally
+         within AST is returned (see \texttt{"} Applicability\texttt{"}  below). If
+         .FALSE. is supplied, then AST\_\_NULL
+         will be returned (without error).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{CmpRegion}{CmpRegion}
+      }{
+         The default uncertainty for a CmpRegion is taken from one of the
+         two component Regions. If the first component Region has a
+         non-default uncertainty, then it is used as the default uncertainty
+         for the parent CmpRegion. Otherwise, if the second component Region
+         has a non-default uncertainty, then it is used as the default
+         uncertainty for the parent CmpRegion. If neither of the
+         component Regions has non-default uncertainty, then the default
+         uncertainty for the CmpRegion is 1.0E-6 of the bounding box of
+         the CmpRegion.
+      }
+      \sstsubsection{
+         \htmlref{Prism}{Prism}
+      }{
+         The default uncertainty for a Prism is formed by combining the
+         uncertainties from the two component Regions. If a component
+         Region does not have a non-default uncertainty, then its default
+         uncertainty will be used to form the default uncertainty of the
+         parent Prism.
+      }
+      \sstsubsection{
+         Region
+      }{
+         For other classes of Region, the default uncertainty is 1.0E-6
+         of the bounding box of the Region. If the bounding box has zero
+         width on any axis, then the uncertainty will be 1.0E-6 of the
+         axis value.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GETUNC = INTEGER
+      }{
+         A pointer to a Region describing the uncertainty in the supplied
+         Region.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If uncertainty information is associated with a Region, and the
+         coordinate system described by the Region is subsequently changed
+         (e.g. by changing the value of its \htmlref{System}{System} attribute, or using the
+         \htmlref{AST\_MAPREGION}{AST\_MAPREGION}
+         function), then the uncertainty information returned by this function
+         will be modified so that it refers to the coordinate system currently
+         described by the supplied Region.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GRFPOP
+}{
+   Restore previously saved graphics functions used by a Plot
+}{
+   \sstdescription{
+      The \htmlref{AST\_GRFPUSH}{AST\_GRFPUSH} and AST\_GRFPOP functions are intended for situations
+      where it is necessary to make temporary changes to the graphics
+      functions used by the \htmlref{Plot}{Plot}. The current functions should first be
+      saved by calling AST\_GRFPUSH. New functions should then be registered
+      using \htmlref{AST\_GRFSET}{AST\_GRFSET}. The required graphics should then be produced.
+      Finally, AST\_GRFPOP should be called to restore the original graphics
+      functions.
+   }
+   \sstinvocation{
+      CALL AST\_GRFPOP( THIS STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine returns without action if there are no snapshots to
+         restore. No error is reported in this case.
+      }
+   }
+}
+\sstroutine{
+   AST\_GRFPUSH
+}{
+   Save the current graphics functions used by a Plot
+}{
+   \sstdescription{
+      This routine takes a snapshot of the graphics functions which are
+      currently registered with the supplied \htmlref{Plot}{Plot}, and saves the snapshot
+      on a first-in-last-out stack within the Plot. The snapshot can be
+      restored later using function
+      \htmlref{AST\_GRFPOP}{AST\_GRFPOP}.
+
+      The AST\_GRFPUSH and AST\_GRFPOP functions are intended for situations
+      where it is necessary to make temporary changes to the graphics
+      functions used by the Plot. The current functions should first be
+      saved by calling AST\_GRFPUSH. New functions should then be registered
+      using \htmlref{AST\_GRFSET}{AST\_GRFSET}. The required graphics should then be produced.
+      Finally, AST\_GRFPOP should be called to restore the original graphics
+      functions.
+   }
+   \sstinvocation{
+      CALL AST\_GRFPUSH( THIS STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_GRFSET
+}{
+   Register a graphics routine for use by a Plot
+}{
+   \sstdescription{
+      This routine can be used to select the underlying graphics
+      routines to be used when the supplied \htmlref{Plot}{Plot} produces graphical output.
+      If this routine is not called prior to producing graphical
+      output, then the underlying graphics routines selected at
+      link-time (using the \htmlref{ast\_link}{ast\_link} command) will be used. To use
+      alternative graphics routines, call this routine before
+      the graphical output is created, specifying the graphics
+      routines to be used. This will register the routine for future
+      use, but the routine will not actually be used until the \htmlref{Grf}{Grf}
+      attribute is given a non-zero value.
+   }
+   \sstinvocation{
+      CALL AST\_GRFSET( THIS, NAME, FUN, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A name indicating the graphics routine to be replaced.
+         Various graphics routines are used by the
+         Plot class, and any combination of them may be supplied by calling
+         this routine once for each routine to be replaced. If any of the
+         graphics routines are not replaced in this way, the
+         corresponding routines in the graphics interface selected at
+         link-time (using the ast\_link command) are used. The allowed
+         function names are:
+
+         \sstitemlist{
+
+            \sstitem
+            Attr -  Enquire or set a graphics attribute value
+
+            \sstitem
+            BBuf - Start a new graphics buffering context
+
+            \sstitem
+            Cap -  Inquire a capability
+
+            \sstitem
+            EBuf - End the current graphics buffering context
+
+            \sstitem
+            Flush - Flush all pending graphics to the output device
+
+            \sstitem
+            Line - Draw a polyline (i.e. a set of connected lines)
+
+            \sstitem
+            Mark -  Draw a set of markers
+
+            \sstitem
+            Qch -  Return the character height in world coordinates
+
+            \sstitem
+            Scales -  Get the axis scales
+
+            \sstitem
+            Text - Draw a character string
+
+            \sstitem
+            TxExt -  Get the extent of a character string
+
+         }
+         The string is case insensitive. For details of the interface
+         required for each, see the sections below.
+      }
+      \sstsubsection{
+         FUN = INTEGER FUNCTION (Given)
+      }{
+         The name of the routine to be used to provide the
+         functionality indicated by parameter NAME (the name
+         should also appear in a Fortran EXTERNAL statement in the
+         routine which invokes AST\_GRFSET).
+
+         Once a routine has been provided, the \texttt{"} null\texttt{"}  routine AST\_NULL can
+         be supplied in a subsequent call to astGrfSet to reset the routine
+         to the corresponding routine in the graphics interface selected at
+         link-time. AST\_NULL is defined in the AST\_PAR include file.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstdiytopic{
+      Function Interfaces
+   }{
+      All the functions listed below (except for \texttt{"} Cap\texttt{"} ) should return an
+      integer value of 0 if an error occurs, and 1 otherwise. All x and y
+      values refer
+      to \texttt{"} graphics cordinates\texttt{"}  as defined by the GRAPHBOX parameter of
+      the \htmlref{AST\_PLOT}{AST\_PLOT} call which created the Plot.
+
+      The first argument (GRFCON)
+      for each function is an AST \htmlref{KeyMap}{KeyMap} pointer that can be used by the
+      called function to establish the context in which it is being called.
+      The contents of the KeyMap are determined by the calling
+      application, which should obtain a pointer to the KeyMap using the
+      \htmlref{AST\_GETGRFCONTEXT}{AST\_GETGRFCONTEXT} routine,
+      and then store any necessary information in the KeyMap using the
+      methods of the KeyMap class. Note, the functions listed below
+      should never annul or delete the supplied KeyMap pointer.
+   }
+   \sstdiytopic{
+      Attr
+   }{
+      The \texttt{"} Attr\texttt{"}  function returns the current value of a specified graphics
+      attribute, and optionally establishes a new value. The supplied
+      value is converted to an integer value if necessary before use.
+      It requires the following interface:
+
+      INTEGER FUNCTION ATTR( GRFCON, ATT, VAL, OLDVAL, PRIM )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         ATT = INTEGER (Given) - An integer identifying the required attribute.
+           The following symbolic values are defined in GRF\_PAR:
+           GRF\_\_STYLE (Line style),
+           GRF\_\_WIDTH (Line width),
+           GRF\_\_SIZE (Character and marker size scale factor),
+           GRF\_\_FONT (Character font),
+           GRF\_\_COLOUR (Colour index).
+
+         \sstitem
+         VAL = DOUBLE PRECISION (Given) -
+           no value is stored.
+
+         \sstitem
+         OLDVAL = DOUBLE PRECISION (Returned) - Returned holding
+           the attribute value.
+
+         \sstitem
+         PRIM = INTEGER (Given) -
+           The sort of graphics primitive to be drawn with the new attribute.
+           Identified by the following values defined in GRF\_PAR:
+           GRF\_\_LINE,
+           GRF\_\_MARK,
+           GRF\_\_TEXT.
+      }
+   }
+   \sstdiytopic{
+      BBuf
+   }{
+      The \texttt{"} BBuf\texttt{"}  function should start a new graphics buffering context.
+      A matching call to the function \texttt{"} EBuf\texttt{"}  should be used to end the
+      context. The nature of the buffering is determined by the underlying
+      graphics system.
+
+      INTEGER FUNCTION BBUF( GRFCON )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+      }
+   }
+   \sstdiytopic{
+      Cap
+   }{
+      The \texttt{"} Cap\texttt{"}  function is called to determine if the grf module has a
+      given capability, as indicated by the \texttt{"} cap\texttt{"}  argument:
+
+      INTEGER FUNCTION CAP( GRFCON, CAP, VALUE )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         CAP = INTEGER (Given)
+            The capability being inquired about. This will be one of the
+            following constants defined in GRF\_PAR:
+
+      }
+         GRF\_\_SCALES: This function should return a non-zero value if the
+         \texttt{"} Scales\texttt{"}  function is implemented, and zero otherwise. The supplied
+         VALUE argument should be ignored.
+
+         GRF\_\_MJUST: This function should return a non-zero value if
+         the \texttt{"} Text\texttt{"}  and \texttt{"} TxExt\texttt{"}  functions recognise \texttt{"} M\texttt{"}  as a
+         character in the justification string. If the first character of
+         a justification string is \texttt{"} M\texttt{"} , then the text should be justified
+         with the given reference point at the bottom of the bounding box.
+         This is different to \texttt{"} B\texttt{"}  justification, which requests that the
+         reference point be put on the baseline of the text, since some
+         characters hang down below the baseline. If the \texttt{"} Text\texttt{"}  or
+         \texttt{"} TxExt\texttt{"}  function cannot differentiate between \texttt{"} M\texttt{"}  and \texttt{"} B\texttt{"} ,
+         then this function should return zero, in which case \texttt{"} M\texttt{"}
+         justification will never be requested by Plot. The supplied
+         VALUE argument should be ignored.
+
+         GRF\_\_ESC: This function should return a non-zero value if the
+         \texttt{"} Text\texttt{"}  and \texttt{"} TxExt\texttt{"}  functions can recognise and interpret
+         graphics escape sequences within the supplied string (see
+         attribute \htmlref{Escape}{Escape}). Zero should be returned if escape sequences
+         cannot be interpreted (in which case the Plot class will interpret
+         them itself if needed). The supplied VALUE argument should be
+         ignored only if escape sequences cannot be interpreted by \texttt{"} Text\texttt{"}  and
+         \texttt{"} TxExt\texttt{"} . Otherwise, VALUE indicates whether \texttt{"} Text\texttt{"}  and \texttt{"} TxExt\texttt{"}
+         should interpret escape sequences in subsequent calls. If VALUE is
+         non-zero then escape sequences should be interpreted by \texttt{"} Text\texttt{"}  and
+         \texttt{"} TxExt\texttt{"} . Otherwise, they should be drawn as literal text.
+
+      \sstitemlist{
+
+         \sstitem
+         VALUE = INTEGER (Given)
+            The use of this parameter depends on the value of CAP as
+            described above.
+
+         \sstitem
+         Returned Function Value:
+            The value returned by the function depends on the value of CAP
+            as described above. Zero should be returned if the supplied
+            capability is not recognised.
+      }
+   }
+   \sstdiytopic{
+      EBuf
+   }{
+      The \texttt{"} EBuf\texttt{"}  function should end the current graphics buffering
+      context. See the description of \texttt{"} BBuf\texttt{"}  above for further details.
+      It requires the following interface:
+
+      INTEGER FUNCTION EBUF( GRFCON )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+      }
+   }
+   \sstdiytopic{
+      Flush
+   }{
+      The \texttt{"} Flush\texttt{"}  function ensures that the display device is up-to-date,
+      by flushing any pending graphics to the output device. It
+      requires the following interface:
+
+      INTEGER FUNCTION FLUSH( GRFCON )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+      }
+   }
+   \sstdiytopic{
+      Line
+   }{
+      The \texttt{"} Line\texttt{"}  function displays lines joining the given positions and
+      requires the following interface:
+
+      INTEGER FUNCTION LINE( GRFCON, N, X, Y )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         N = INTEGER (Given) - The number of positions to be joined together.
+
+         \sstitem
+         X( N ) = REAL (Given) - An array holding the \texttt{"} n\texttt{"}  x values.
+
+         \sstitem
+         Y( N ) = REAL (Given) - An array holding the \texttt{"} n\texttt{"}  y values.
+      }
+   }
+   \sstdiytopic{
+      Mark
+   }{
+      The \texttt{"} Mark\texttt{"}  function displays markers at the given positions. It
+      requires the following interface:
+
+      INTEGER FUNCTION MARK( GRFCON, N, X, Y, TYPE )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         N = INTEGER (Given) - The number of positions to be marked.
+
+         \sstitem
+         X( N ) = REAL (Given) - An array holding the \texttt{"} n\texttt{"}  x values.
+
+         \sstitem
+         Y( N ) = REAL (Given) - An array holding the \texttt{"} n\texttt{"}  y values.
+
+         \sstitem
+         TYPE = INTEGER (Given) - An integer which can be used to indicate
+           the type of marker symbol required.
+      }
+   }
+   \sstdiytopic{
+      Qch
+   }{
+      The \texttt{"} Qch\texttt{"}  function returns the heights of characters drawn vertically
+      and horizontally in graphics coordinates. It requires the following
+      interface:
+
+      INTEGER FUNCTION QCH( GRFCON, CHV, CHH )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         CHV = REAL (Returned) The height of
+         characters drawn with a vertical baseline. This will be an
+         increment in the X axis.
+
+         \sstitem
+         CHH = REAL (Returned) The height of
+         characters drawn with a horizontal baseline. This will be an
+         increment in the Y axis.
+      }
+   }
+   \sstdiytopic{
+      Scales
+   }{
+      The \texttt{"} Scales\texttt{"}  function returns two values (one for each axis) which
+      scale increments on the corresponding axis into a \texttt{"} normal\texttt{"}  coordinate
+      system in which: 1) the axes have equal scale in terms of (for instance)
+      millimetres per unit distance, 2) X values increase from left to
+      right, and 3) Y values increase from bottom to top. It requires the
+      following interface:
+
+      INTEGER FUNCTION SCALES( GRFCON, ALPHA, BETA )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         ALPHA = REAL (Returned) The
+         scale for the X axis (i.e. Xnorm = alpha$*$Xworld).
+
+         \sstitem
+         BETA = REAL (Returned) The
+         scale for the Y axis (i.e. Ynorm = beta$*$Yworld).
+      }
+   }
+   \sstdiytopic{
+      Text
+   }{
+      The \texttt{"} Text\texttt{"}  function displays a character string at a given
+      position using a specified justification and up-vector. It
+      requires the following interface:
+
+      INTEGER FUNCTION TEXT( GRFCON, TEXT, X, Y, JUST, UPX, UPY )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         TEXT = CHARACTER $*$ ( $*$ ) (Given) - The string to be displayed.
+
+         \sstitem
+         X = REAL (Given) - The reference x coordinate.
+
+         \sstitem
+         Y = REAL (Given) - The reference y coordinate.
+
+         \sstitem
+         JUST = CHARACTER $*$ ( $*$ ) (Given ) - A string which specifies the
+            location within the
+            text string which is to be placed at the reference position
+            given by x and y. The first character may be \texttt{'} T\texttt{'}  for \texttt{"} top\texttt{"} ,
+            \texttt{'} C\texttt{'}  for \texttt{"} centre\texttt{"} , or \texttt{'} B\texttt{'}  for \texttt{"} bottom\texttt{"} , and specifies the
+            vertical location of the reference position. Note, \texttt{"} bottom\texttt{"}
+            corresponds to the base-line of normal text. Some characters
+            (eg \texttt{"} y\texttt{"} , \texttt{"} g\texttt{"} , \texttt{"} p\texttt{"} , etc) descend below the base-line. The second
+            character may be \texttt{'} L\texttt{'}  for \texttt{"} left\texttt{"} , \texttt{'} C\texttt{'}  for \texttt{"} centre\texttt{"} , or \texttt{'} R\texttt{'}
+            for \texttt{"} right\texttt{"} , and specifies the horizontal location of the
+            reference position. If the string has less than 2 characters
+            then \texttt{'} C\texttt{'}  is used for the missing characters.
+
+         \sstitem
+         UPX = REAL (Given) - The x component of the up-vector for the text.
+            If necessary the supplied value should be negated
+            to ensure that positive values always refer to displacements from
+            left to right on the screen.
+
+         \sstitem
+         UPX = REAL (Given) - The y component of the up-vector for the text.
+            If necessary the supplied value should be negated
+            to ensure that positive values always refer to displacements from
+            bottom to top on the screen.
+      }
+   }
+   \sstdiytopic{
+      TxExt
+   }{
+      The \texttt{"} TxExt\texttt{"}  function returns the corners of a box which would enclose
+      the supplied character string if it were displayed using the
+      Text function described above. The returned box includes any leading
+      or trailing spaces. It requires the following interface:
+
+      INTEGER FUNCTION TXEXT( GRFCON, TEXT, X, Y, JUST, UPX, UPY, XB, YB )
+
+      \sstitemlist{
+
+         \sstitem
+         GRFCON = INTEGER (Given) -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         TEXT = CHARACTER $*$ ( $*$ ) (Given) - The string to be displayed.
+
+         \sstitem
+         X = REAL (Given) - The reference x coordinate.
+
+         \sstitem
+         Y = REAL (Given) - The reference y coordinate.
+
+         \sstitem
+         JUST = CHARACTER $*$ ( $*$ ) (Given ) - A string which specifies the
+            location within the
+            text string which is to be placed at the reference position
+            given by x and y. See \texttt{"} Text\texttt{"}  above.
+
+         \sstitem
+         UPX = REAL (Given) - The x component of the up-vector for the text.
+            See \texttt{"} Text\texttt{"}  above.
+
+         \sstitem
+         UPX = REAL (Given) - The y component of the up-vector for the text.
+            See \texttt{"} Text\texttt{"}  above.
+
+         \sstitem
+         XB( 4 ) = REAL (Returned) - Returned holding the x coordinate of
+            each corner of the bounding box.
+
+         \sstitem
+         YB( 4 ) = REAL (Returned) - Returned holding the y coordinate of
+            each corner of the bounding box.
+      }
+   }
+}
+\sstroutine{
+   AST\_GRID
+}{
+   Draw a set of labelled coordinate axes
+}{
+   \sstdescription{
+      This routine draws a complete annotated set of
+      coordinate axes for a \htmlref{Plot}{Plot} with (optionally) a coordinate grid
+      superimposed. Details of the axes and grid can be controlled by
+      setting values for the various attributes defined by the Plot
+      class (q.v.).
+   }
+   \sstinvocation{
+      CALL AST\_GRID( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the supplied Plot is a \htmlref{Plot3D}{Plot3D}, the axes will be annotated
+         using three 2-dimensional Plots, one for each 2D plane in the 3D
+         current coordinate system. The plots will be \texttt{"} pasted\texttt{"}  onto 3 faces
+         of the cuboid graphics volume specified when the Plot3D was
+         constructed. The faces to be used can be controlled by the \texttt{"} \htmlref{RootCorner}{RootCorner}\texttt{"}
+         attribute.
+
+         \sstitem
+         An error results if either the current \htmlref{Frame}{Frame} or the base Frame
+         of the Plot is not 2-dimensional or (for a Plot3D) 3-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the base
+         and current Frames of the Plot is not defined in either
+         direction (i.e. the Plot\texttt{'} s \htmlref{TranForward}{TranForward} or \htmlref{TranInverse}{TranInverse} attribute
+         is zero).
+      }
+   }
+}
+\sstroutine{
+   AST\_GRIDLINE
+}{
+   Draw a grid line (or axis) for a Plot
+}{
+   \sstdescription{
+      This routine draws a curve in the physical coordinate system of
+      a \htmlref{Plot}{Plot} by varying only one of the coordinates along the length
+      of the curve. It is intended for drawing coordinate axes,
+      coordinate grids, and tick marks on axes (but note that these
+      are also available via the more comprehensive \htmlref{AST\_GRID}{AST\_GRID} routine).
+
+      The curve is transformed into graphical coordinate space for
+      plotting, so that a straight line in physical coordinates may
+      result in a curved line being drawn if the \htmlref{Mapping}{Mapping} involved is
+      non-linear. Any discontinuities in the Mapping between physical
+      and graphical coordinates are catered for, as is any
+      clipping established using \htmlref{AST\_CLIP}{AST\_CLIP}.
+   }
+   \sstinvocation{
+      CALL AST\_GRIDLINE( THIS, AXIS, START, LENGTH, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         AXIS = INTEGER (Given)
+      }{
+         The index of the Plot axis whose physical coordinate value is
+         to be varied along the length of the curve (all other
+         coordinates will remain fixed). This value should lie in the
+         range from 1 to the number of Plot axes (\htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         START( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array, with one element for each axis of the Plot, giving
+         the physical coordinates of the start of the curve.
+      }
+      \sstsubsection{
+         LENGTH = DOUBLE PRECISION (Given)
+      }{
+         The length of curve to be drawn, given as an increment along
+         the selected physical axis. This may be positive or negative.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No curve is drawn if the START array contains any
+         coordinates with the value AST\_\_BAD, nor if LENGTH has this value.
+
+         \sstitem
+         An error results if the base \htmlref{Frame}{Frame} of the Plot is not 2-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   AST\_GRISMMAP
+}{
+   Create a GrismMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{GrismMap}{GrismMap} and optionally initialises
+      its attributes.
+
+      A GrismMap is a specialised form of \htmlref{Mapping}{Mapping} which transforms
+      1-dimensional coordinates using the spectral dispersion equation
+      described in FITS-WCS paper III \texttt{"} Representation of spectral
+      coordinates in FITS\texttt{"} . This describes the dispersion produced by
+      gratings, prisms and grisms.
+
+      When initially created, the forward transformation of a GrismMap
+      transforms input \texttt{"} grism parameter\texttt{"}  values into output wavelength
+      values. The \texttt{"} grism parameter\texttt{"}  is a dimensionless value which is
+      linearly related to position on the detector. It is defined in FITS-WCS
+      paper III as \texttt{"} the offset on the detector from the point of intersection
+      of the camera axis, measured in units of the effective local length\texttt{"} .
+      The units in which wavelength values are expected or returned is
+      determined by the values supplied for the \htmlref{GrismWaveR}{GrismWaveR}, \htmlref{GrismNRP}{GrismNRP} and
+      \htmlref{GrismG}{GrismG} attribute: whatever units are used for these attributes will
+      also be used for the wavelength values.
+   }
+   \sstinvocation{
+      RESULT = AST\_GRISMMAP( OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new GrismMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GRISMMAP = INTEGER
+      }{
+         A pointer to the new GrismMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_GetTableHeader
+}{
+   Get the FITS headers from a FitsTable
+}{
+   \sstdescription{
+      This function returns a pointer to a \htmlref{FitsChan}{FitsChan} holding copies of
+      the FITS headers associated with a \htmlref{FitsTable}{FitsTable}.
+   }
+   \sstinvocation{
+      RESULT = AST\_GETTABLEHEADER( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsTable.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_GetTableHeader = INTEGER
+      }{
+         A pointer to a deep copy of the FitsChan stored within the
+         FitsTable.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned pointer should be annulled using
+         \htmlref{AST\_ANNUL}{AST\_ANNUL}
+         when it is no longer needed.
+
+         \sstitem
+         Changing the contents of the returned FitsChan will have no effect
+         on the FitsTable. To modify the FitsTable, the modified FitsChan must
+         be stored in the FitsTable using
+         \htmlref{AST\_PUTTABLEHEADER}{AST\_PUTTABLEHEADER}.
+      }
+   }
+}
+\sstroutine{
+   AST\_HASATTRIBUTE
+}{
+   Test if an Object has a named attribute
+}{
+   \sstdescription{
+      This function returns a logical result to indicate
+      whether the supplied \htmlref{Object}{Object} has an attribute with the supplied name.
+   }
+   \sstinvocation{
+      RESULT = AST\_HASATTRIBUTE( THIS, ATTRIB, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the first Object.
+      }
+      \sstsubsection{
+         ATTRIB = INTEGER (Given)
+      }{
+         The
+         name of the attribute to be tested.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         \htmlref{AST\_SAME}{AST\_SAME} = LOGICAL
+      }{
+         .TRUE. if the Object has the named attribute, otherwise
+         .FALSE.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of .FALSE. will be returned if this function is invoked
+         with STATUS set to an error value, or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_HASCOLUMN
+}{
+   Returns a flag indicating if a column is present in a Table
+}{
+   \sstdescription{
+      This routine
+      returns a flag indicating if a named column exists in a \htmlref{Table}{Table}, for
+      instance, by having been added to to the Table using
+      \htmlref{AST\_ADDCOLUMN}{AST\_ADDCOLUMN}.
+   }
+   \sstinvocation{
+      RESULT = AST\_HASCOLUMN( THIS, COLUMN, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         COLUMN = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string holding the upper case name of the column. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of
+         .FALSE.
+         is returned for if an error occurs.
+      }
+   }
+}
+\sstroutine{
+   AST\_HASPARAMETER
+}{
+   Returns a flag indicating if a named global parameter is present in a Table
+}{
+   \sstdescription{
+      This routine
+      returns a flag indicating if a named parameter exists in a \htmlref{Table}{Table}, for
+      instance, by having been added to to the Table using
+      \htmlref{AST\_ADDPARAMETER}{AST\_ADDPARAMETER}.
+   }
+   \sstinvocation{
+      RESULT = AST\_HASPARAMETER( THIS, PARAMETER, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         PARAMETER = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string holding the upper case name of the parameter. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of
+         .FALSE.
+         is returned for if an error occurs.
+      }
+   }
+}
+\sstroutine{
+   AST\_IMPORT
+}{
+   Import an Object pointer to the current context
+}{
+   \sstdescription{
+      This routine
+      imports an \htmlref{Object}{Object} pointer that was created in a higher or lower
+      level context, into the current AST context.
+      This means that the pointer will be annulled when the current context
+      is ended (with \htmlref{AST\_END}{AST\_END}).
+   }
+   \sstinvocation{
+      CALL AST\_IMPORT( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Object pointer to be imported.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+}
+\sstroutine{
+   AST\_INTERSECT
+}{
+   Find the point of intersection between two geodesic curves
+}{
+   \sstdescription{
+      This routine
+      finds the coordinate values at the point of intersection between
+      two geodesic curves. Each curve is specified by two points on
+      the curve.  It can only be used with 2-dimensional Frames.
+
+      For example, in a basic \htmlref{Frame}{Frame}, it will find the point of
+      intersection between two straight lines. But for a \htmlref{SkyFrame}{SkyFrame} it
+      will find an intersection of two great circles.
+   }
+   \sstinvocation{
+      CALL AST\_INTERSECT( THIS, A1, A2, B1, B2, CROSS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         A1( 2 ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). This should contain the coordinates of the
+         first point on the first geodesic curve.
+      }
+      \sstsubsection{
+         A2( 2 ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (Naxes attribute). This should contain the coordinates of a
+         second point on the first geodesic curve. It should not be
+         co-incident with the first point.
+      }
+      \sstsubsection{
+         B1( 2 ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (Naxes attribute). This should contain the coordinates of the
+         first point on the second geodesic curve.
+      }
+      \sstsubsection{
+         B2( 2 ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (Naxes attribute). This should contain the coordinates of a
+         second point on the second geodesic curve. It should not be
+         co-incident with the first point.
+      }
+      \sstsubsection{
+         CROSS( 2 ) = DOUBLE PRECISION (Returned)
+      }{
+         An array with one element for each Frame axis
+         in which the coordinates of the required intersection will
+         be returned.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For SkyFrames each curve will be a great circle, and in general
+         each pair of curves will intersect at two diametrically opposite
+         points on the sky. The returned position is the one which is
+         closest to point
+         A1.
+
+         \sstitem
+         This function will return \texttt{"} bad\texttt{"}  coordinate values (AST\_\_BAD)
+         if any of the input coordinates has this value, or if the two
+         points defining either geodesic are co-incident, or if the two
+         curves do not intersect.
+
+         \sstitem
+         The geodesic curve used by this routine is the path of
+         shortest distance between two points, as defined by the
+         \htmlref{AST\_DISTANCE}{AST\_DISTANCE} function.
+
+         \sstitem
+         An error will be reported if the Frame is not 2-dimensional.
+      }
+   }
+}
+\sstroutine{
+   AST\_INTERVAL
+}{
+   Create a Interval
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Interval}{Interval} and optionally initialises its
+      attributes.
+
+      A Interval is a \htmlref{Region}{Region} which represents upper and/or lower limits on
+      one or more axes of a \htmlref{Frame}{Frame}. For a point to be within the region
+      represented by the Interval, the point must satisfy all the
+      restrictions placed on all the axes. The point is outside the region
+      if it fails to satisfy any one of the restrictions. Each axis may have
+      either an upper limit, a lower limit, both or neither. If both limits
+      are supplied but are in reverse order (so that the lower limit is
+      greater than the upper limit), then the interval is an excluded
+      interval, rather than an included interval.
+
+      At least one axis limit must be supplied.
+
+      Note, The Interval class makes no allowances for cyclic nature of
+      some coordinate systems (such as \htmlref{SkyFrame}{SkyFrame} coordinates). A \htmlref{Box}{Box}
+      should usually be used in these cases since this requires the user
+      to think about suitable upper and lower limits,
+   }
+   \sstinvocation{
+      RESULT = AST\_INTERVAL( FRAME, LBND, UBND, UNC, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         A pointer to the Frame in which the region is defined. A deep
+         copy is taken of the supplied Frame. This means that any
+         subsequent changes made to the Frame using the supplied pointer
+         will have no effect the Region.
+      }
+      \sstsubsection{
+         LBND( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute) containing the lower limits on each axis.
+         Set a value to AST\_\_BAD to indicate that the axis has no lower
+         limit.
+      }
+      \sstsubsection{
+         UBND( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (Naxes attribute) containing the upper limits on each axis.
+         Set a value to AST\_\_BAD to indicate that the axis has no upper
+         limit.
+      }
+      \sstsubsection{
+         UNC = INTEGER (Given)
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with the boundary of the Interval being created.
+         The uncertainty in any point on the boundary of the Interval is found by
+         shifting the supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at
+         the boundary point being considered. The area covered by the
+         shifted uncertainty Region then represents the uncertainty in the
+         boundary position. The uncertainty is assumed to be the same for
+         all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. Box, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Interval. Alternatively,
+         a null \htmlref{Object}{Object} pointer (AST\_\_NULL)
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the Interval being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{AST\_OVERLAP}{AST\_OVERLAP}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Interval. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_INTERVAL = INTEGER
+      }{
+         A pointer to the new Interval.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_INTRAMAP
+}{
+   Create an IntraMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{IntraMap}{IntraMap} and optionally initialises
+      its attributes.
+
+      An IntraMap is a specialised form of \htmlref{Mapping}{Mapping} which encapsulates
+      a privately-defined coordinate transformation routine
+      (e.g. written in Fortran) so that it may be used like any other
+      AST Mapping. This allows you to create Mappings that perform any
+      conceivable coordinate transformation.
+
+      However, an IntraMap is intended for use within a single program
+      or a private suite of software, where all programs have access
+      to the same coordinate transformation functions (i.e. can be
+      linked against them). IntraMaps should not normally be stored in
+      datasets which may be exported for processing by other software,
+      since that software will not have the necessary transformation
+      functions available, resulting in an error.
+
+      You must register any coordinate transformation functions to be
+      used using \htmlref{AST\_INTRAREG}{AST\_INTRAREG} before creating an IntraMap.
+   }
+   \sstinvocation{
+      RESULT = AST\_INTRAMAP( NAME, NIN, NOUT, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing the name of the transformation
+         routine to use (which should previously have been registered
+         using AST\_INTRAREG). This name is case sensitive. All white
+         space will be removed before use.
+      }
+      \sstsubsection{
+         NIN = INTEGER (Given)
+      }{
+         The number of input coordinates. This must be compatible with
+         the number of input coordinates accepted by the
+         transformation routine (as specified when this routine was
+         registered using AST\_INTRAREG).
+      }
+      \sstsubsection{
+         NOUT = INTEGER (Given)
+      }{
+         The number of output coordinates. This must be compatible
+         with the number of output coordinates produced by the
+         transformation routine (as specified when this routine was
+         registered using AST\_INTRAREG).
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new IntraMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_INTRAMAP = INTEGER
+      }{
+         A pointer to the new IntraMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_INTRAREG
+}{
+   Register a transformation routine for use by an IntraMap
+}{
+   \sstdescription{
+      This function registers a privately-defined coordinate
+      transformation routine written in Fortran so that it may be used
+      to create an \htmlref{IntraMap}{IntraMap}. An IntraMap is a specialised form of
+      \htmlref{Mapping}{Mapping} which encapsulates the Fortran routine so that it may be
+      used like any other AST Mapping. This allows you to create
+      Mappings that perform any conceivable coordinate transformation.
+
+      Registration of relevant transformation routines is required
+      before using the \htmlref{AST\_INTRAMAP}{AST\_INTRAMAP} constructor function to create an
+      IntraMap or reading an external representation of an IntraMap
+      from a \htmlref{Channel}{Channel}.
+   }
+   \sstinvocation{
+      CALL AST\_INTRAREG( NAME, NIN, NOUT, TRAN, FLAGS, PURPOSE, AUTHOR,
+                         CONTACT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing a unique name to be associated
+         with the transformation routine in order to identify it. This
+         name is case sensitive. All white space will be removed
+         before use.
+      }
+      \sstsubsection{
+         NIN = INTEGER (Given)
+      }{
+         The number of input coordinates accepted by the
+         transformation routine (i.e. the number of dimensions of the
+         space in which the input points reside). A value of AST\_\_ANY
+         may be given if the routine is able to accommodate a variable
+         number of input coordinates.
+      }
+      \sstsubsection{
+         NOUT = INTEGER (Given)
+      }{
+         The number of output coordinates produced by the
+         transformation routine (i.e. the number of dimensions of the
+         space in which the output points reside). A value of AST\_\_ANY
+         may be given if the routine is able to produce a variable
+         number of output coordinates.
+      }
+      \sstsubsection{
+         TRAN = SUBROUTINE (Given)
+      }{
+         The transformation routine to be registered.  This routine
+         should perform whatever coordinate transformations are
+         required and should have an interface like \htmlref{AST\_TRANN}{AST\_TRANN} (q.v.).
+
+         This transformation routine must also appear in an EXTERNAL
+         statement in the routine which calls AST\_INTRAREG.
+      }
+      \sstsubsection{
+         FLAGS = INTEGER (Given)
+      }{
+         This value may be used to supply a set of flags which
+         describe the transformation routine and which may affect the
+         behaviour of any IntraMap which uses it.  Often, a value of
+         zero will be given here, but you may also supply the sum of a
+         set of flags as described in the \texttt{"} Transformation Flags\texttt{"}
+         section (below).
+      }
+      \sstsubsection{
+         PURPOSE = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing a short (one line) textual
+         comment to describe the purpose of the transformation
+         routine.
+      }
+      \sstsubsection{
+         AUTHOR = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing the name of the author of the
+         transformation routine.
+      }
+      \sstsubsection{
+         CONTACT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing contact details for the author
+         of the transformation routine (e.g. an e-mail or WWW
+         address). If any IntraMap which uses this transformation
+         routine is exported as part of a dataset to an external user
+         who does not have access to the routine, then these contact
+         details should allow them to obtain the necessary code.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Beware that an external representation of an IntraMap (created
+         by writing it to a Channel) will not include the coordinate
+         transformation routine which it uses, so will only refer to the
+         routine by its name (as assigned using AST\_INTRAREG).
+         Consequently, the external representation cannot be utilised by
+         another program unless that program has also registered the same
+         transformation routine with the same name using an identical
+         invocation of AST\_INTRAREG. If no such registration has been
+         performed, then attempting to read the external representation
+         will result in an error.
+
+         \sstitem
+         You may use AST\_INTRAREG to register a transformation routine
+         with the same name more than once, but only if the arguments
+         supplied are identical on each occasion (i.e there is no way of
+         changing things once a routine has been successfully registered
+         under a given name, and attempting to do so will result in an
+         error). This feature simply allows registration to be performed
+         independently, but consistently, at several places within your
+         program, without having to check whether it has already been
+         done.
+
+         \sstitem
+         If an error occurs in the transformation routine, this may be
+         indicated by setting its STATUS argument to an error value
+         before it returns.  This will immediately terminate the current
+         AST operation.  The error value AST\_\_ITFER is available for this
+         purpose, but other values may also be used (e.g. if you wish to
+         distinguish different types of error). The AST\_\_ITFER error
+         value is defined in the AST\_ERR include file.
+      }
+   }
+   \sstdiytopic{
+      Transformation Flags
+   }{
+      The following flags are defined in the AST\_PAR include file and
+      allow you to provide further information about the nature of the
+      transformation routine. Having selected the set of flags which
+      apply, you should supply the sum of their values as the FLAGS
+      argument to AST\_INTRAREG.
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_NOFWD: If this flag is set, it indicates that the
+         transformation routine does not implement a forward coordinate
+         transformation. In this case, any IntraMap which uses it will
+         have a \htmlref{TranForward}{TranForward} attribute value of zero and the
+         transformation routine itself will not be called with its
+         FORWARD argument set to .TRUE.. By default, it is assumed that a
+         forward transformation is provided.
+
+         \sstitem
+         AST\_\_NOINV: If this flag is set, it indicates that the
+         transformation routine does not implement an inverse coordinate
+         transformation. In this case, any IntraMap which uses it will
+         have a \htmlref{TranInverse}{TranInverse} attribute value of zero and the
+         transformation routine itself will not be called with its
+         FORWARD argument set to .FALSE.. By default, it is assumed that
+         an inverse transformation is provided.
+
+         \sstitem
+         AST\_\_SIMPFI: You may set this flag if applying the
+         transformation routine\texttt{'} s forward coordinate transformation,
+         followed immediately by the matching inverse transformation,
+         should always restore the original set of coordinates. It
+         indicates that AST may replace such a sequence of operations by
+         an identity Mapping (a \htmlref{UnitMap}{UnitMap}) if it is encountered while
+         simplifying a compound Mapping (e.g. using \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}).  It is
+         not necessary that both transformations have actually been
+         implemented.
+
+         \sstitem
+         AST\_\_SIMPIF: You may set this flag if applying the
+         transformation routine\texttt{'} s inverse coordinate transformation,
+         followed immediately by the matching forward transformation,
+         should always restore the original set of coordinates. It
+         indicates that AST may replace such a sequence of operations by
+         an identity Mapping (a UnitMap) if it is encountered while
+         simplifying a compound Mapping (e.g. using AST\_SIMPLIFY).  It is
+         not necessary that both transformations have actually been
+         implemented.
+      }
+   }
+}
+\sstroutine{
+   AST\_INVERT
+}{
+   Invert a Mapping
+}{
+   \sstdescription{
+      This routine inverts a \htmlref{Mapping}{Mapping} by reversing the boolean sense
+      of its \htmlref{Invert}{Invert} attribute. If this attribute is zero (the
+      default), the Mapping will transform coordinates in the way
+      specified when it was created. If it is non-zero, the input and
+      output coordinates will be inter-changed so that the direction
+      of the Mapping is reversed. This will cause it to display the
+      inverse of its original behaviour.
+   }
+   \sstinvocation{
+      CALL AST\_INVERT( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_ISA$<$CLASS$>$
+}{
+   Test membership of a class by an Object
+}{
+   \sstdescription{
+      This is a family of functions which test whether an \htmlref{Object}{Object} is a
+      member of the class called $<$CLASS$>$, or of any class derived from
+      it.
+   }
+   \sstinvocation{
+      RESULT = AST\_ISA$<$CLASS$>$( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         These functions apply to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_ISA$<$CLASS$>$ = LOGICAL
+      }{
+         .TRUE. if the Object belongs to the class called $<$CLASS$>$ (or to
+         a class derived from it), otherwise .FALSE..
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         MEMBER = AST\_ISAFRAME( OBJ, STATUS );
+      }{
+         Tests whether Object OBJ is a member of the \htmlref{Frame}{Frame} class, or
+         of any class derived from a Frame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Every AST class provides a function (AST\_ISA$<$CLASS$>$) of this
+         form, where $<$CLASS$>$ should be replaced by the class name.
+
+         \sstitem
+         This function attempts to execute even if STATUS is set to an
+         error value
+         on entry, although no further error report will be made
+         if it subsequently fails under these circumstances.
+
+         \sstitem
+         A value of .FALSE. will be returned if this function should fail
+         for any reason. In particular, it will fail if the pointer
+         supplied does not identify an Object of any sort.
+      }
+   }
+}
+\sstroutine{
+   AST\_KEYMAP
+}{
+   Create a KeyMap
+}{
+   \sstdescription{
+      This function creates a new empty \htmlref{KeyMap}{KeyMap} and optionally initialises its
+      attributes. Entries can then be added to the KeyMap using the
+      \htmlref{AST\_MAPPUT0$<$X$>$}{AST\_MAPPUT0$<$X$>$} and \htmlref{AST\_MAPPUT1$<$X$>$}{AST\_MAPPUT1$<$X$>$} functions.
+
+      The KeyMap class is used to store a set of values with associated keys
+      which identify the values. The keys are strings. These may be case
+      sensitive or insensitive as selected by the \htmlref{KeyCase}{KeyCase} attribute, and
+      trailing spaces are ignored. The value associated with a key can be
+      integer (signed 4 and 2 byte, or unsigned 1 byte), floating point
+      (single or double precision),
+      character string or AST \htmlref{Object}{Object} pointer. Each
+      value can be a scalar or a one-dimensional vector. A KeyMap is
+      conceptually similar to a \htmlref{Mapping}{Mapping} in that a KeyMap transforms an
+      input into an output - the input is the key, and the output is the
+      value associated with the key. However, this is only a conceptual
+      similarity, and it should be noted that the KeyMap class inherits from
+      the Object class rather than the Mapping class. The methods of the
+      Mapping class cannot be used with a KeyMap.
+   }
+   \sstinvocation{
+      RESULT = AST\_KEYMAP( OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new KeyMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAP = INTEGER
+      }{
+         A pointer to the new KeyMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_LINEARAPPROX
+}{
+   Obtain a linear approximation to a Mapping, if appropriate
+}{
+   \sstdescription{
+      This function tests the forward coordinate transformation
+      implemented by a \htmlref{Mapping}{Mapping} over a given range of input coordinates. If
+      the transformation is found to be linear to a specified level of
+      accuracy, then an array of fit coefficients is returned. These
+      may be used to implement a linear approximation to the Mapping\texttt{'} s
+      forward transformation within the specified range of output coordinates.
+      If the transformation is not sufficiently linear, no coefficients
+      are returned.
+   }
+   \sstinvocation{
+      RESULT = AST\_LINEARAPPROX( THIS, LBND, UBND, TOL, FIT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping.
+      }
+      \sstsubsection{
+         LBND( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array
+         containing the lower bounds of a box defined within the input
+         coordinate system of the Mapping. The number of elements in this
+         array should equal the value of the Mapping\texttt{'} s \htmlref{Nin}{Nin} attribute. This
+         box should specify the region over which linearity is required.
+      }
+      \sstsubsection{
+         UBND( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array
+         containing the upper bounds of the box specifying the region over
+         which linearity is required.
+      }
+      \sstsubsection{
+         TOL = DOUBLE PRECISION (Given)
+      }{
+         The maximum permitted deviation from linearity, expressed as
+         a positive Cartesian displacement in the output coordinate
+         space of the Mapping. If a linear fit to the forward
+         transformation of the Mapping deviates from the true transformation
+         by more than this amount at any point which is tested, then no fit
+         coefficients will be returned.
+      }
+      \sstsubsection{
+         FIT( $*$ ) = DOUBLE PRECISION (Returned)
+      }{
+         An array
+         in which to return the co-efficients of the linear
+         approximation to the specified transformation. This array should
+         have at least \texttt{"} ( Nin $+$ 1 ) $*$ \htmlref{Nout}{Nout}\texttt{"} , elements. The first Nout elements
+         hold the constant offsets for the transformation outputs. The
+         remaining elements hold the gradients. So if the Mapping has 2 inputs
+         and 3 outputs the linear approximation to the forward transformation
+         is:
+
+            X\_out = fit(1) $+$ fit(4)$*$X\_in $+$ fit(5)$*$Y\_in
+
+            Y\_out = fit(2) $+$ fit(6)$*$X\_in $+$ fit(7)$*$Y\_in
+
+            Z\_out = fit(3) $+$ fit(8)$*$X\_in $+$ fit(9)$*$Y\_in
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_LINEARAPPROX = LOGICAL
+      }{
+         If the forward transformation is sufficiently linear,
+         .TRUE is returned. Otherwise .FALSE. is returned
+         and the fit co-efficients are set to AST\_\_BAD.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function fits the Mapping\texttt{'} s forward transformation. To fit
+         the inverse transformation, the Mapping should be inverted using
+         \htmlref{AST\_INVERT}{AST\_INVERT}
+         before invoking this function.
+
+         \sstitem
+         A value of .FALSE.
+         will be returned if this function is invoked
+         with the global error status set, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_LUTMAP
+}{
+   Create a LutMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{LutMap}{LutMap} and optionally initialises
+      its attributes.
+
+      A LutMap is a specialised form of \htmlref{Mapping}{Mapping} which transforms
+      1-dimensional coordinates by using linear interpolation in a
+      lookup table.  Each input coordinate value is first scaled to
+      give the index of an entry in the table by subtracting a
+      starting value (the input coordinate corresponding to the first
+      table entry) and dividing by an increment (the difference in
+      input coordinate value between adjacent table entries).
+
+      The resulting index will usually contain a fractional part, so
+      the output coordinate value is then generated by interpolating
+      linearly between the appropriate entries in the table. If the
+      index lies outside the range of the table, linear extrapolation
+      is used based on the two nearest entries (i.e. the two entries
+      at the start or end of the table, as appropriate).
+
+      If the lookup table entries increase or decrease monotonically,
+      then the inverse transformation may also be performed.
+   }
+   \sstinvocation{
+      RESULT = AST\_LUTMAP( NLUT, LUT, START, INC, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NLUT = INTEGER (Given)
+      }{
+         The number of entries in the lookup table. This value must be
+         at least 2.
+      }
+      \sstsubsection{
+         LUT( NLUT ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing the
+         lookup table entries.
+      }
+      \sstsubsection{
+         START = DOUBLE PRECISION (Given)
+      }{
+         The input coordinate value which corresponds to the first lookup
+         table entry.
+      }
+      \sstsubsection{
+         INC = DOUBLE PRECISION (Given)
+      }{
+         The lookup table spacing (the increment in input coordinate
+         value between successive lookup table entries). This value
+         may be positive or negative, but must not be zero.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new LutMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_LUTMAP = INTEGER
+      }{
+         A pointer to the new LutMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the entries in the lookup table either increase or decrease
+         monotonically, then the new LutMap\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute will
+         have a value of one, indicating that the inverse transformation
+         can be performed. Otherwise, it will have a value of zero, so
+         that any attempt to use the inverse transformation will result
+         in an error.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_MAPBOX
+}{
+   Find a bounding box for a Mapping
+}{
+   \sstdescription{
+      This routine allows you to find the \texttt{"} bounding box\texttt{"}  which just
+      encloses another box after it has been transformed by a \htmlref{Mapping}{Mapping}
+      (using either its forward or inverse transformation). A typical
+      use might be to calculate the size of an image after being
+      transformed by a Mapping.
+
+      The routine works on one dimension at a time. When supplied with
+      the lower and upper bounds of a rectangular region (box) of
+      input coordinate space, it finds the lowest and highest values
+      taken by a nominated output coordinate within that region. It
+      also returns the input coordinates where these bounding values
+      are attained. It should be used repeatedly to obtain the extent
+      of the bounding box in more than one dimension.
+   }
+   \sstinvocation{
+      CALL AST\_MAPBOX( THIS, LBND\_IN, UBND\_IN, FORWARD, COORD\_OUT,
+                       LBND\_OUT, UBND\_OUT, XL, XU, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping.
+      }
+      \sstsubsection{
+         LBND\_IN( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Mapping input
+         coordinate. This should contain the lower bound of the input
+         box in each input dimension.
+      }
+      \sstsubsection{
+         UBND\_IN( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Mapping input
+         coordinate. This should contain the upper bound of the input
+         box in each input dimension.
+
+         Note that it is permissible for the upper bound to be less
+         than the corresponding lower bound, as the values will simply
+         be swapped before use.
+      }
+      \sstsubsection{
+         FORWARD = LOGICAL (Given)
+      }{
+         If this value is .TRUE., then the Mapping\texttt{'} s forward
+         transformation will be used to transform the input
+         box. Otherwise, its inverse transformation will be used.
+
+         (If the inverse transformation is selected, then references
+         to \texttt{"} input\texttt{"}  and \texttt{"} output\texttt{"}  coordinates in this description
+         should be transposed. For example, the size of the LBND\_IN
+         and UBND\_IN arrays should match the number of output
+         coordinates, as given by the Mapping\texttt{'} s \htmlref{Nout}{Nout} attribute.
+         Similarly, the COORD\_OUT argument, below, should nominate one
+         of the Mapping\texttt{'} s input coordinates.)
+      }
+      \sstsubsection{
+         COORD\_OUT = INTEGER (Given)
+      }{
+         The index of the output coordinate for which the lower and
+         upper bounds are required. This value should be at least one,
+         and no larger than the number of Mapping output coordinates.
+      }
+      \sstsubsection{
+         LBND\_OUT = DOUBLE PRECISION (Returned)
+      }{
+         The lowest value taken by the nominated output coordinate
+         within the specified region of input coordinate space.
+      }
+      \sstsubsection{
+         UBND\_OUT = DOUBLE PRECISION (Returned)
+      }{
+         The highest value taken by the nominated output coordinate
+         within the specified region of input coordinate space.
+      }
+      \sstsubsection{
+         XL( $*$ ) = DOUBLE PRECISION (Returned)
+      }{
+         An array with one element for each Mapping input
+         coordinate. This will return the coordinates of an input
+         point (although not necessarily a unique one) for which the
+         nominated output coordinate attains the lower bound value
+         returned in LBND\_OUT.
+      }
+      \sstsubsection{
+         XU( $*$ ) = DOUBLE PRECISION (Returned)
+      }{
+         An array with one element for each Mapping input
+         coordinate. This will return the coordinates of an input
+         point (although not necessarily a unique one) for which the
+         nominated output coordinate attains the upper bound value
+         returned in UBND\_OUT.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Any input points which are transformed by the Mapping to give
+         output coordinates containing the value AST\_\_BAD are regarded as
+         invalid and are ignored. They will make no contribution to
+         determining the output bounds, even although the nominated
+         output coordinate might still have a valid value at such points.
+
+         \sstitem
+         An error will occur if the required output bounds cannot be
+         found. Typically, this might happen if all the input points
+         which the routine considers turn out to be invalid (see
+         above). The number of points considered before generating such
+         an error is quite large, so this is unlikely to occur by
+         accident unless valid points are restricted to a very small
+         subset of the input coordinate space.
+
+         \sstitem
+         The values returned via LBND\_OUT, UBND\_OUT, XL and XU will be
+         set to the value AST\_\_BAD if this routine should fail for any
+         reason. Their initial values on entry will not be altered if the
+         routine is invoked with STATUS set to an error value.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPCOPY
+}{
+   Copy entries from one KeyMap into another
+}{
+   \sstdescription{
+      This routine
+      copies all entries from one \htmlref{KeyMap}{KeyMap} into another.
+   }
+   \sstinvocation{
+      CALL AST\_MAPCOPY( THIS, THAT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the destination KeyMap.
+      }
+      \sstsubsection{
+         THAT = INTEGER (Given)
+      }{
+         Pointer to the source KeyMap.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Entries from the source KeyMap will replace any existing entries in
+         the destination KeyMap that have the same key.
+
+         \sstitem
+         The one exception to the above rule is that if a source entry
+         contains a scalar KeyMap entry, and the destination contains a
+         scalar KeyMap entry with the same key, then the source KeyMap entry
+         will be copied into the destination KeyMap entry using this function,
+         rather than simply replacing the destination KeyMap entry.
+
+         \sstitem
+         If the destination entry has a non-zero value for its \htmlref{MapLocked}{MapLocked}
+         attribute, then an error will be reported if the source KeyMap
+         contains any keys that do not already exist within the destination
+         KeyMap.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPDEFINED
+}{
+   Check if a KeyMap contains a defined value for a key
+}{
+   \sstdescription{
+      This function checks to see if a \htmlref{KeyMap}{KeyMap} contains a defined value for
+      a given key. If the key is present in the KeyMap but has an
+      undefined value it returns
+      .FALSE. (unlike \htmlref{AST\_MAPHASKEY}{AST\_MAPHASKEY} which would return .TRUE.).
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPDEFINED( THIS, KEY, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored. The supplied string is converted to upper
+         case before use if the \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPDEFINED = LOGICAL
+      }{
+         .TRUE.
+         is returned if the requested key name is present in the KeyMap
+         and has a defined value.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPGET0$<$X$>$
+}{
+   Get a scalar value from a KeyMap
+}{
+   \sstdescription{
+      This is a set of functions for retrieving a scalar value from a \htmlref{KeyMap}{KeyMap}.
+      You should replace $<$X$>$ in the generic function name
+      AST\_MAPGET0$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+      The stored value is converted to the data type indiced by $<$X$>$
+      before being returned (an error is reported if it is not possible to
+      convert the stored value to the requested data type).
+      Note, the version of this function which returns character strings,
+      AST\_MAPGET0C, has an extra parameter in which is returned the number
+      of characters written into the supplied CHARACTER variable.
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPGET0$<$X$>$( THIS, KEY, VALUE, STATUS )
+
+      RESULT = AST\_MAPGET0C( THIS, KEY, VALUE, L, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored. The supplied string is converted to upper
+         case before use if the \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         VALUE = $<$X$>$type (Returned)
+      }{
+         The requested value.
+         If the requested key is not found, or if it is found but has an
+         undefined value (see
+         \htmlref{AST\_MAPPUTU}{AST\_MAPPUTU}),
+         then the contents of the
+         buffer on entry to this function will be unchanged on exit.
+      }
+      \sstsubsection{
+         L = INTEGER (Returned)
+      }{
+         This parameter is only present in the interface for the AST\_MAPGET0C
+         function. It is returned holding the number of characters
+         written into the CHARACTER variable supplied for parameter VALUE.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPGET0$<$X$>$ = LOGICAL
+      }{
+         .TRUE.
+         is returned if the requested key name was found, and does not have
+         an undefined value (see
+         AST\_MAPPUTU). .FALSE.
+         is returned otherwise.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No error is reported if the requested key cannot be found in the
+         given KeyMap, but a
+         .FALSE.
+         value will be returned as the function value. The supplied buffer
+         will be returned unchanged.
+
+         \sstitem
+         If the stored value is a vector value, then the first value in
+         the vector will be returned.
+
+         \sstitem
+         If the returned value is an AST \htmlref{Object}{Object} pointer, the Object\texttt{'} s reference
+         count is incremented by this call. Any subsequent changes made to
+         the Object using the returned pointer will be reflected in any
+         any other active pointers for the Object. The returned pointer
+         should be annulled using
+         \htmlref{AST\_ANNUL}{AST\_ANNUL}
+         when it is no longer needed.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      routine, you should replace $<$X$>$ in the generic routine name AST\_MAPGET0$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         C: CHARACTER
+
+         \sstitem
+         A: INTEGER used to identify an AstObject
+
+         \sstitem
+         S: INTEGER$*$2 (short integer)
+
+         \sstitem
+         B: Unsigned byte
+
+      }
+      For example, AST\_MAPGET0D would be used to get a DOUBLE PRECISION value,
+      while AST\_MAPGET0I would be used to get an INTEGER, etc.
+   }
+}
+\sstroutine{
+   AST\_MAPGET1$<$X$>$
+}{
+   Get a vector value from a KeyMap
+}{
+   \sstdescription{
+      This is a set of functions for retrieving a vector value from a \htmlref{KeyMap}{KeyMap}.
+      You should replace $<$X$>$ in the generic function name
+      AST\_MAPGET1$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+      The stored value is converted to the data type indiced by $<$X$>$
+      before being returned (an error is reported if it is not possible to
+      convert the stored value to the requested data type).
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPGET1$<$X$>$( THIS, KEY, MXVAL, NVAL, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         MXVAL = INTEGER (Given)
+      }{
+         The number of elements in the
+         VALUE array.
+      }
+      \sstsubsection{
+         NVAL = INTEGER (Returned)
+      }{
+         The
+         number of elements stored in the
+         Any unused elements of the array are left unchanged.
+      }
+      \sstsubsection{
+         VALUE( MXVAL ) = $<$X$>$type (Returned)
+      }{
+         The requested values.
+         If the requested key is not found, or if it is found but has an
+         undefined value (see
+         \htmlref{AST\_MAPPUTU}{AST\_MAPPUTU}),
+         then the contents of the
+         buffer on entry to this function will be unchanged on exit.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPGET1$<$X$>$ = LOGICAL
+      }{
+         .TRUE.
+         is returned if the requested key name was found, and does not have
+         an undefined value (see
+         AST\_MAPPUTU). .FALSE.
+         is returned otherwise.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No error is reported if the requested key cannot be found in the
+         given KeyMap, but a
+         .FALSE.
+         value will be returned as the function value. The supplied array
+         will be returned unchanged.
+
+         \sstitem
+         If the stored value is a scalar value, then the value will be
+         returned in the first element of the supplied array, and
+         NVAL
+         will be returned set to 1.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      routine, you should replace $<$X$>$ in the generic routine name AST\_MAPGET1$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         C: CHARACTER
+
+         \sstitem
+         A: INTEGER used to identify an AstObject
+
+         \sstitem
+         S: INTEGER$*$2 (short integer)
+
+         \sstitem
+         B: Unsigned byte
+
+      }
+      For example, AST\_MAPGET1D would be used to get DOUBLE PRECISION values,
+      while AST\_MAPGET1I would be used to get INTEGER values, etc.
+   }
+}
+\sstroutine{
+   AST\_MAPGETELEM$<$X$>$
+}{
+   Get a single element of a vector value from a KeyMap
+}{
+   \sstdescription{
+      This is a set of functions for retrieving a single element of a vector
+      value from a \htmlref{KeyMap}{KeyMap}. You should replace $<$X$>$ in the generic function name
+      AST\_MAPGETELEM$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+      The stored value is converted to the data type indiced by $<$X$>$
+      before being returned (an error is reported if it is not possible to
+      convert the stored value to the requested data type).
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPGETELEM$<$X$>$( THIS, KEY, ELEM, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         ELEM = INTEGER (Given)
+      }{
+         The index of the required vector element, starting at
+         one.
+         An error will be reported if the value is outside the range of
+         the vector.
+      }
+      \sstsubsection{
+         VALUE = $<$X$>$type (Returned)
+      }{
+         The requested value.
+         If the requested key is not found, or if it is found but has an
+         undefined value (see
+         \htmlref{AST\_MAPPUTU}{AST\_MAPPUTU}),
+         then the contents of the
+         buffer on entry to this function will be unchanged on exit.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPGETELEM$<$X$>$ = LOGICAL
+      }{
+         .TRUE.
+         is returned if the requested key name was found, and does not have
+         an undefined value (see
+         AST\_MAPPUTU). .FALSE.
+         is returned otherwise.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No error is reported if the requested key cannot be found in the
+         given KeyMap, or if it has an undefined value, but a
+         .FALSE.
+         value will be returned as the function value.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      routine, you should replace $<$X$>$ in the generic routine name
+      AST\_MAPGETELEM$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         C: CHARACTER
+
+         \sstitem
+         A: INTEGER used to identify an AstObject
+
+         \sstitem
+         S: INTEGER$*$2 (short integer)
+
+         \sstitem
+         B: Unsigned byte
+
+      }
+      For example, AST\_MAPGETELEMD would be used to get a DOUBLE PRECISION
+      value, while AST\_MAPGETELEMI would be used to get an INTEGER value, etc.
+   }
+}
+\sstroutine{
+   AST\_MAPHASKEY
+}{
+   Check if an entry with a given key exists in a KeyMap
+}{
+   \sstdescription{
+      This function returns a flag indicating if the \htmlref{KeyMap}{KeyMap} contains an
+      entry with the given key.
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPHASKEY( THIS, KEY, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the KeyMap entry. Trailing spaces are
+         ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPHASKEY = LOGICAL
+      }{
+         .TRUE. if the key was found, and .FALSE. otherwise.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         .TRUE.
+         is returned if the key exists but has an undefined value (that is,
+         the returned value does not depend on whether the entry has a
+         defined value or not). See also
+         \htmlref{AST\_MAPDEFINED}{AST\_MAPDEFINED}, which returns zero in such a case.
+
+         \sstitem
+         A function value of
+         .FALSE.
+         will be returned if an error has already occurred, or if this
+         function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPKEY
+}{
+   Get the key at a given index within the KeyMap
+}{
+   \sstdescription{
+      This function returns a string holding the key for the entry with
+      the given index within the \htmlref{KeyMap}{KeyMap}.
+
+      This function is intended primarily as a means of iterating round all
+      the elements in a KeyMap. For this purpose, the number of entries in
+      the KeyMap should first be found using
+      \htmlref{AST\_MAPSIZE}{AST\_MAPSIZE}
+      and this function should then be called in a loop, with the index
+      value going from
+      one to the size of the KeyMap.
+      The index associated with a given entry is determined by the \htmlref{SortBy}{SortBy}
+      attribute.
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPKEY( THIS, INDEX, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         INDEX = INTEGER (Given)
+      }{
+         The index into the KeyMap. The first entry has index
+         one, and the last has index SIZE, the value returned by the
+         AST\_MAPSIZE function.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPKEY = CHARACTER $*$ ( AST\_\_SZCHR )
+      }{
+         The key value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A blank string will be returned if this function is invoked
+         with STATUS set to an error value, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPLENC
+}{
+   Get the number of characters in a character entry in a KeyMap
+}{
+   \sstdescription{
+      This function returns the minimum length which a character variable
+      which must have in order to be able to store a specified entry in
+      the supplied \htmlref{KeyMap}{KeyMap}. If the named entry is a vector entry, then the
+      returned value is the length of the longest element of the vector
+      value.
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPLENC( THIS, KEY, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the KeyMap entry. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPLENC = INTEGER
+      }{
+         The length (i.e. number of characters) of the longest formatted
+         value associated with the named entry.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A function value of zero will be returned without error if the
+         named entry cannot be formatted as a character string.
+
+         \sstitem
+         A function value of zero will be returned if an error has already
+         occurred, or if this function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPLENGTH
+}{
+   Get the vector length of an entry in a KeyMap
+}{
+   \sstdescription{
+      This function returns the vector length of a named entry in a \htmlref{KeyMap}{KeyMap},
+      (that is, how many values are associated with the entry).
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPLENGTH( THIS, KEY, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the KeyMap entry. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPLENGTH = INTEGER
+      }{
+         The length of the entry. One for a scalar, greater than one for
+         a vector. A value of zero is returned if the KeyMap does not
+         contain the named entry.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A function value of zero will be returned if an error has already
+         occurred, or if this function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPPUT0$<$X$>$
+}{
+   Add a scalar value to a KeyMap
+}{
+   \sstdescription{
+      This is a set of routine
+      for adding scalar values to a \htmlref{KeyMap}{KeyMap}. You should use a
+      routine
+      which matches the data type of the data you wish to add to the KeyMap
+      by replacing $<$X$>$ in the generic
+      routine name AST\_MAPPUT0$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+   }
+   \sstinvocation{
+      CALL AST\_MAPPUT0$<$X$>$( THIS, KEY, VALUE, COMMENT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap in which to store the supplied value.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string to be stored with the value, which can later
+         be used to identify the value. Trailing spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         VALUE = $<$X$>$type (Given)
+      }{
+         The value to be stored. The data type of this value should match the
+         1-character type code appended to the
+         routine name (e.g. if you are using AST\_MAPPUT0A, the type of this
+         value should be \texttt{"} integer pointer for an AstObject\texttt{"} ).
+      }
+      \sstsubsection{
+         COMMENT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A comment string to be stored with the value.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the supplied key is already in use in the KeyMap, the new value
+         will replace the old value.
+
+         \sstitem
+         If the stored value is an AST \htmlref{Object}{Object} pointer, the Object\texttt{'} s reference
+         count is incremented by this call. Any subsequent changes made to
+         the Object using the returned pointer will be reflected in any
+         any other active pointers for the Object, including any obtained
+         later using
+         AST\_MAPGET0A.
+         The reference count for the Object will be decremented when the
+         KeyMap is destroyed, or the entry is removed or over-written with a
+         different pointer.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      routine, you should replace $<$X$>$ in the generic routine name AST\_MAPPUT0$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         C: CHARACTER
+
+         \sstitem
+         A: INTEGER used to identify an AstObject
+
+         \sstitem
+         S: INTEGER$*$2 (short integer)
+
+         \sstitem
+         B: Unsigned byte
+
+      }
+      For example, AST\_MAPPUT0D would be used to store a DOUBLE PRECISION value,
+      while AST\_MAPPUT0I would be used to store an INTEGER, etc.
+   }
+}
+\sstroutine{
+   AST\_MAPPUT1$<$X$>$
+}{
+   Add a vector value to a KeyMap
+}{
+   \sstdescription{
+      This is a set of routine
+      for adding vector values to a \htmlref{KeyMap}{KeyMap}. You should use a
+      routine
+      which matches the data type of the data you wish to add to the KeyMap
+      by replacing $<$X$>$ in the generic
+      routine name AST\_MAPPUT1$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+   }
+   \sstinvocation{
+      CALL AST\_MAPPUT1$<$X$>$( THIS, KEY, SIZE, VALUE, COMMENT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap in which to store the supplied values.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string to be stored with the values, which can later
+         be used to identify the values. Trailing spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         SIZE = INTEGER (Given)
+      }{
+         The number of elements in the supplied array of values.
+      }
+      \sstsubsection{
+         VALUE( $*$ ) = $<$X$>$type (Given)
+      }{
+         The array of values to be stored. The data type of this value should
+         match the 1-character type code appended to the
+         routine name (e.g. if you are using AST\_MAPPUT1A, the type of this
+         value should be \texttt{"} integer pointer for an AstObject)\texttt{"} .
+      }
+      \sstsubsection{
+         COMMENT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A comment string to be stored with the values.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the supplied key is already in use in the KeyMap, the new values
+         will replace the old values.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      routine, you should replace $<$X$>$ in the generic routine name AST\_MAPPUT1$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         C: CHARACTER
+
+         \sstitem
+         A: INTEGER used to identify an AstObject
+
+         \sstitem
+         S: INTEGER$*$2 (short integer)
+
+         \sstitem
+         B: Unsigned byte
+
+      }
+      For example, AST\_MAPPUT1D would be used to store DOUBLE PRECISION values,
+      while AST\_MAPPUT1I would be used to store INTEGER, etc.
+   }
+}
+\sstroutine{
+   AST\_MAPPUTELEM$<$X$>$
+}{
+   Put a value into an element of a vector value in a KeyMap
+}{
+   \sstdescription{
+      This is a set of functions for storing a value in a single element of
+      a vector value in a \htmlref{KeyMap}{KeyMap}. You should replace $<$X$>$ in the generic
+      function name
+      AST\_MAPPUTELEM$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+      The supplied value is converted from the data type indicated by $<$X$>$
+      to the data type of the KeyMap entry before being stored (an error
+      is reported if it is not possible to convert the value to the
+      required data type).
+   }
+   \sstinvocation{
+      CALL AST\_MAPPUTELEM$<$X$>$( THIS, KEY, ELEM, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         ELEM = INTEGER (Given)
+      }{
+         The index of the vector element to modify, starting at
+         one.
+      }
+      \sstsubsection{
+         VALUE = $<$X$>$type (Given)
+      }{
+         The value to store.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         If the
+         ELEM
+         index is outside the range of the vector, the length of
+         the vector will be increased by one element and the supplied
+         value will be stored at the end of the vector in the new element.
+      }
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         If the
+         ELEM
+         index is outside the range of the vector, an error will be
+         reported. The number of elements in each cell of a column is
+         specified when the column is created using
+         \htmlref{AST\_ADDCOLUMN}{AST\_ADDCOLUMN}.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the entry originally holds a scalar value, it will be treated
+         like a vector entry of length 1.
+
+         \sstitem
+         If the specified key cannot be found in the given KeyMap, or is
+         found but has an undefined value, a new
+         vector entry with the given name, and data type implied by $<$X$>$, is
+         created and the supplied value is stored in its first entry.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      routine, you should replace $<$X$>$ in the generic routine name
+      AST\_MAPPUTELEM$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         C: CHARACTER
+
+         \sstitem
+         A: INTEGER used to identify an AstObject
+
+         \sstitem
+         S: INTEGER$*$2 (short integer)
+
+         \sstitem
+         B: BYTE (unsigned)
+
+      }
+      For example, AST\_MAPPUTELEMD would be used to put a DOUBLE PRECISION
+      value, while AST\_MAPPUTELEMI would be used to put an INTEGER value, etc.
+   }
+}
+\sstroutine{
+   AST\_MAPPUTU
+}{
+   Add an entry to a KeyMap with an undefined value
+}{
+   \sstdescription{
+      This routine
+      adds a new entry to a \htmlref{KeyMap}{KeyMap}, but no value is stored with the
+      entry. The entry therefore has a special data type represented by
+      symbolic constant AST\_\_UNDEFTYPE.
+
+      An example use is to add entries with undefined values to a KeyMap
+      prior to locking them with the \htmlref{MapLocked}{MapLocked} attribute. Such entries
+      can act as placeholders for values that can be added to the KeyMap
+      later.
+   }
+   \sstinvocation{
+      CALL AST\_MAPPUTU( THIS, KEY, COMMENT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap in which to store the supplied value.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string to be stored with the value, which can later
+         be used to identify the value. Trailing spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         COMMENT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A comment string to be stored with the value.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the supplied key is already in use in the KeyMap, the value
+         associated with the key will be removed.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPREGION
+}{
+   Transform a Region into a new Frame using a given Mapping
+}{
+   \sstdescription{
+      This function returns a pointer to a new \htmlref{Region}{Region} which corresponds to
+      supplied Region described by some other specified coordinate system. A
+      \htmlref{Mapping}{Mapping} is supplied which transforms positions between the old and new
+      coordinate systems. The new Region may not be of the same class as
+      the original region.
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPREGION( THIS, MAP, FRAME, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         MAP = INTEGER (Given)
+      }{
+         Pointer to a Mapping which transforms positions from the
+         coordinate system represented by the supplied Region to the
+         coordinate system specified by
+         FRAME.
+         The supplied Mapping should define both forward and inverse
+         transformations, and these transformations should form a genuine
+         inverse pair. That is, transforming a position using the forward
+         transformation and then using the inverse transformation should
+         produce the original input position. Some Mapping classes (such
+         as \htmlref{PermMap}{PermMap}, \htmlref{MathMap}{MathMap}, \htmlref{SphMap}{SphMap}) can result in Mappings for which this
+         is not true.
+      }
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         Pointer to a \htmlref{Frame}{Frame} describing the coordinate system in which
+         the new Region is required.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPREGION = INTEGER
+      }{
+         A pointer to a new Region. This Region will represent the area
+         within the coordinate system specified by
+         FRAME
+         which corresponds to the supplied Region.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The uncertainty associated with the supplied Region is modified
+         using the supplied Mapping.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPREMOVE
+}{
+   Removed a named entry from a KeyMap
+}{
+   \sstdescription{
+      This routine
+      removes a named entry from a \htmlref{KeyMap}{KeyMap}. It returns without action if the
+      KeyMap does not contain the specified key.
+   }
+   \sstinvocation{
+      CALL AST\_MAPREMOVE( THIS, KEY, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPRENAME
+}{
+   Rename an existing KeyMap entry
+}{
+   \sstdescription{
+      This routine
+      associated a new key with an existing entry in a \htmlref{KeyMap}{KeyMap}. It returns
+      without action if the oldkey does not exist in the KeyMap.
+   }
+   \sstinvocation{
+      CALL AST\_MAPRENAME( THIS, OLDKEY, NEWKEY, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         OLDKEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the entry to be renamed. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         NEKEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The new character string to associated with the renamed entry.
+         Trailing spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         KeyCase attribute is currently set to zero.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPSIZE
+}{
+   Get the number of entries in a KeyMap
+}{
+   \sstdescription{
+      This function returns the number of entries in a \htmlref{KeyMap}{KeyMap}.
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPSIZE( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPSIZE = INTEGER
+      }{
+         The number of entries in the KeyMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A function value of zero will be returned if an error has already
+         occurred, or if this function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPSPLIT
+}{
+   Split a Mapping up into parallel component Mappings
+}{
+   \sstdescription{
+      This routine
+      creates a new \htmlref{Mapping}{Mapping} which connects specified inputs within a
+      supplied Mapping to the corresponding outputs of the supplied Mapping.
+      This is only possible if the specified inputs correspond to some
+      subset of the Mapping outputs. That is, there must exist a subset of
+      the Mapping outputs for which each output depends only on the selected
+      Mapping inputs, and not on any of the inputs which have not been
+      selected. Also, any output which is not in this subset must not depend
+      on any of the selected inputs. If these conditions are not met by the
+      supplied Mapping, then
+      an AST\_\_NULL
+      Mapping pointer is returned.
+   }
+   \sstinvocation{
+      CALL AST\_MAPSPLIT( THIS, NIN, IN, OUT, MAP, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping to be split.
+      }
+      \sstsubsection{
+         NIN = INTEGER (Given)
+      }{
+         The number of inputs to pick from THIS.
+      }
+      \sstsubsection{
+         IN( NIN ) = INTEGER (Given)
+      }{
+         An
+         array holding the indices within the supplied Mapping of the inputs
+         which are to be picked from the Mapping.
+         If \texttt{"} \htmlref{Nin}{Nin}\texttt{"}  is the number of inputs of the supplied Mapping, then each
+         element should have a value in the range 1 to Nin.
+      }
+      \sstsubsection{
+         OUT( $*$ ) = INTEGER (Returned)
+      }{
+         An
+         array in which to return the indices of the outputs of the supplied
+         Mapping which are fed by the picked inputs. A value of one is
+         used to refer to the first Mapping output. The supplied array should
+         have a length at least equal to the number of outputs in the
+         supplied Mapping. The number of values stored in the array on
+         exit will equal the number of outputs in the returned Mapping.
+         The i\texttt{'} th element in the returned array holds the index within
+         the supplied Mapping which corresponds to the i\texttt{'} th output of
+         the returned Mapping.
+      }
+      \sstsubsection{
+         MAP = INTEGER (Returned)
+      }{
+         The
+         returned Mapping. This Mapping will have
+         NIN inputs (the number of outputs may be different to NIN). AST\_\_NULL
+         is returned if the supplied Mapping has no subset of outputs which
+         depend only on the selected inputs. The returned Mapping is a
+         deep copy of the required parts of the supplied Mapping.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If this
+         routine
+         is invoked with the global error status set, or if it should fail for
+         any reason, then
+         AST\_\_NULL
+         will be returned for
+         MAP.
+      }
+   }
+}
+\sstroutine{
+   AST\_MAPTYPE
+}{
+   Get the data type of an entry in a KeyMap
+}{
+   \sstdescription{
+      This function returns a value indicating the data type of a
+      named entry in a \htmlref{KeyMap}{KeyMap}. This is the data type which was used when the
+      entry was added to the KeyMap.
+   }
+   \sstinvocation{
+      RESULT = AST\_MAPTYPE( THIS, KEY, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string identifying the KeyMap entry. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MAPTYPE = INTEGER
+      }{
+         One of AST\_\_INTTYPE (for integer), AST\_\_SINTTYPE (for
+         INTEGER$*$2),
+         AST\_\_BYTETYPE (for unsigned bytes
+         ) AST\_\_DOUBLETYPE (for double
+         precision floating point), AST\_\_FLOATTYPE (for single
+         precision floating point), AST\_\_STRINGTYPE (for character string),
+         AST\_\_OBJECTTYPE (for AST \htmlref{Object}{Object} pointer), AST\_\_POINTERTYPE (for
+         arbitrary C pointer) or AST\_\_UNDEFTYPE (for undefined values
+         created by
+         \htmlref{AST\_MAPPUTU}{AST\_MAPPUTU}).
+         AST\_\_BADTYPE is returned if the supplied key is not found in the KeyMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A function value of AST\_\_BADTYPE will be returned if an error has
+         already occurred, or if this function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_MARK
+}{
+   Draw a set of markers for a Plot
+}{
+   \sstdescription{
+      This routine draws a set of markers (symbols) at positions
+      specified in the physical coordinate system of a \htmlref{Plot}{Plot}. The
+      positions are transformed into graphical coordinates to
+      determine where the markers should appear within the plotting
+      area.
+   }
+   \sstinvocation{
+      CALL AST\_MARK( THIS, NMARK, NCOORD, INDIM, IN, TYPE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         NMARK = INTEGER (Given)
+      }{
+         The number of markers to draw. This may be zero, in which
+         case nothing will be drawn.
+      }
+      \sstsubsection{
+         NCOORD = INTEGER (Given)
+      }{
+         The number of coordinates being supplied for each mark
+         (i.e. the number of axes in the current \htmlref{Frame}{Frame} of the Plot, as
+         given by its \htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         INDIM = INTEGER (Given)
+      }{
+         The number of elements along the first dimension of the IN
+         array (which contains the marker coordinates). This value is
+         required so that the coordinate values can be correctly
+         located if they do not entirely fill this array. The value
+         given should not be less than NMARK.
+      }
+      \sstsubsection{
+         IN( INDIM, NCOORD ) = DOUBLE PRECISION (Given)
+      }{
+         A 2-dimensional array giving the physical coordinates of the
+         points where markers are to be drawn. These should be
+         stored such that the value of coordinate number COORD for
+         input mark number MARK is found in element IN(MARK,COORD).
+      }
+      \sstsubsection{
+         TYPE = INTEGER (Given)
+      }{
+         A value specifying the type (e.g. shape) of marker to be
+         drawn. The set of values which may be used (and the shapes
+         that will result) is determined by the underlying graphics
+         system.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Markers are not drawn at positions which have any coordinate
+         equal to the value AST\_\_BAD (or where the transformation into
+         graphical coordinates yields coordinates containing the value
+         AST\_\_BAD).
+
+         \sstitem
+         If any marker position is clipped (see \htmlref{AST\_CLIP}{AST\_CLIP}), then the
+         entire marker is not drawn.
+
+         \sstitem
+         An error results if the base Frame of the Plot is not 2-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   AST\_MASK$<$X$>$
+}{
+   Mask a region of a data grid
+}{
+   \sstdescription{
+      This is a set of functions for masking out regions within gridded data
+      (e.g. an image). The functions modifies a given data grid by
+      assigning a specified value to all samples which are inside (or outside
+      if INSIDE is .FALSE.)
+      the specified \htmlref{Region}{Region}.
+
+      You should use a masking function which matches the numerical
+      type of the data you are processing by replacing $<$X$>$ in
+      the generic function name AST\_MASK$<$X$>$ by an appropriate 1- or
+      2-character type code. For example, if you are masking data
+      with type REAL, you should use the function AST\_MASKR (see
+      the \texttt{"} Data Type Codes\texttt{"}  section below for the codes appropriate to
+      other numerical types).
+   }
+   \sstinvocation{
+      RESULT = AST\_MASK$<$X$>$( THIS, MAP, INSIDE, NDIM, LBND, UBND, IN, VAL,
+                            STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to a Region.
+      }
+      \sstsubsection{
+         MAP = INTEGER (Given)
+      }{
+         Pointer to a \htmlref{Mapping}{Mapping}. The forward transformation should map
+         positions in the coordinate system of the supplied Region
+         into pixel coordinates as defined by the
+         LBND and UBND arguments. A value of AST\_\_NULL
+         can be supplied if the coordinate system of the supplied Region
+         corresponds to pixel coordinates. This is equivalent to
+         supplying a \htmlref{UnitMap}{UnitMap}.
+
+         The number of inputs for this Mapping (as given by its \htmlref{Nin}{Nin} attribute)
+         should match the number of axes in the supplied Region (as given
+         by the \htmlref{Naxes}{Naxes} attribute of the Region).
+         The number of outputs for the Mapping (as given by its \htmlref{Nout}{Nout} attribute)
+         should match the number of
+         grid dimensions given by the value of NDIM
+         below.
+      }
+      \sstsubsection{
+         INSIDE = INTEGER (Given)
+      }{
+         A boolean value which indicates which pixel are to be masked. If
+         .TRUE.
+         is supplied, then all grid pixels with centres inside the supplied
+         Region are assigned the value given by
+         VAL,
+         and all other pixels are left unchanged. If
+         .FALSE.
+         is supplied, then all grid pixels with centres not inside the supplied
+         Region are assigned the value given by
+         VAL,
+         and all other pixels are left unchanged. Note, the \htmlref{Negated}{Negated}
+         attribute of the Region is used to determine which pixel are
+         inside the Region and which are outside. So the inside of a Region
+         which has not been negated is the same as the outside of the
+         corresponding negated Region.
+
+         For types of Region such as \htmlref{PointList}{PointList} which have zero volume,
+         pixel centres will rarely fall exactly within the Region. For
+         this reason, the inclusion criterion is changed for zero-volume
+         Regions so that pixels are included (or excluded) if any part of
+         the Region passes through the pixel. For a PointList, this means
+         that pixels are included (or excluded) if they contain at least
+         one of the points listed in the PointList.
+      }
+      \sstsubsection{
+         NDIM = INTEGER (Given)
+      }{
+         The number of dimensions in the input grid. This should be at
+         least one.
+      }
+      \sstsubsection{
+         LBND( NDIM ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         UBND( NDIM ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that LBND and UBND together define the shape
+         and size of the input grid, its extent along a particular
+         (J\texttt{'} th) dimension being UBND(J)-LBND(J)$+$1. They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre.
+      }
+      \sstsubsection{
+         IN( $*$ ) = $<$Xtype$>$ (Given and Returned)
+      }{
+         An array, with one element for each pixel in the
+         input grid, containing the data to be masked.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using AST\_MASKR, the type of each array element
+         should be REAL).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. normal Fortran array storage order).
+
+         On exit, the samples specified by
+         INSIDE are set to the value of VAL.
+         All other samples are left unchanged.
+      }
+      \sstsubsection{
+         VAL = $<$Xtype$>$ (Given)
+      }{
+         This argument should have the same type as the elements of
+         the IN array. It specifies the value used to flag the
+         masked data (see
+         INSIDE).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MASK$<$X$>$ = INTEGER
+      }{
+         The number of pixels to which a value of
+         BADVAL
+         has been assigned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with the global error status set, or if it should fail for any
+         reason.
+
+         \sstitem
+         An error will be reported if the overlap of the Region and
+         the array cannot be determined.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate masking function, you should
+      replace $<$X$>$ in the generic function name AST\_MASK$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         UI: INTEGER (treated as unsigned)
+
+         \sstitem
+         S: INTEGER$*$2 (short integer)
+
+         \sstitem
+         US: INTEGER$*$2 (short integer, treated as unsigned)
+
+         \sstitem
+         B: BYTE (treated as signed)
+
+         \sstitem
+         UB: BYTE (treated as unsigned)
+
+      }
+      For example, AST\_MASKD would be used to process DOUBLE
+      PRECISION data, while AST\_MASKS would be used to process
+      short integer data (stored in an INTEGER$*$2 array), etc.
+
+      For compatibility with other Starlink facilities, the codes W
+      and UW are provided as synonyms for S and US respectively (but
+      only in the Fortran interface to AST).
+   }
+}
+\sstroutine{
+   AST\_MATCHAXES
+}{
+   Find any corresponding axes in two Frames
+}{
+   \sstdescription{
+      This function looks for corresponding axes within two supplied
+      Frames. An array of integers is returned that contains an element
+      for each axis in the second supplied \htmlref{Frame}{Frame}. An element in this array
+      will be set to zero if the associated axis within the second Frame
+      has no corresponding axis within the first Frame. Otherwise, it
+      will be set to the index (a non-zero positive integer) of the
+      corresponding axis within the first supplied Frame.
+   }
+   \sstinvocation{
+      CALL AST\_MATCHAXES( FRM1, FRM2, AXES, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRM1 = INTEGER (Given)
+      }{
+         Pointer to the first Frame.
+      }
+      \sstsubsection{
+         FRM2 = INTEGER (Given)
+      }{
+         Pointer to the second Frame.
+      }
+      \sstsubsection{
+         AXES = INTEGER( $*$ ) (Returned)
+      }{
+         An
+         integer array in which to return the indices of the axes (within
+         the first Frame) that correspond to each axis within the second
+         Frame. \htmlref{Axis}{Axis} indices start at 1. A value of zero will be stored
+         in the returned array for each axis in the second Frame that has
+         no corresponding axis in the first Frame.
+
+         The number of elements in this array must be greater than or
+         equal to the number of axes in the second Frame.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         This function applies to all Frames.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Corresponding axes are identified by the fact that a \htmlref{Mapping}{Mapping} can
+         be found between them using
+         \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} or \htmlref{AST\_CONVERT}{AST\_CONVERT}.
+         Thus, \texttt{"} corresponding axes\texttt{"}  are not necessarily identical. For
+         instance, \htmlref{SkyFrame}{SkyFrame} axes in two Frames will match even if they
+         describe different celestial coordinate systems
+      }
+   }
+}
+\sstroutine{
+   AST\_MATHMAP
+}{
+   Create a MathMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{MathMap}{MathMap} and optionally initialises its
+      attributes.
+
+      A MathMap is a \htmlref{Mapping}{Mapping} which allows you to specify a set of forward
+      and/or inverse transformation functions using arithmetic operations
+      and mathematical functions similar to those available in Fortran. The
+      MathMap interprets these functions at run-time, whenever its forward
+      or inverse transformation is required. Because the functions are not
+      compiled in the normal sense (unlike an \htmlref{IntraMap}{IntraMap}), they may be used to
+      describe coordinate transformations in a transportable manner. A
+      MathMap therefore provides a flexible way of defining new types of
+      Mapping whose descriptions may be stored as part of a dataset and
+      interpreted by other programs.
+   }
+   \sstinvocation{
+      RESULT = AST\_MATHMAP( NIN, NOUT, NFWD, FWD, NINV, INV, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NIN = INTEGER
+      }{
+         Number of input variables for the MathMap. This determines the
+         value of its \htmlref{Nin}{Nin} attribute.
+      }
+      \sstsubsection{
+         NOUT = INTEGER
+      }{
+         Number of output variables for the MathMap. This determines the
+         value of its \htmlref{Nout}{Nout} attribute.
+      }
+      \sstsubsection{
+         NFWD = INTEGER
+      }{
+         The number of forward transformation functions being supplied.
+         This must be at least equal to NOUT, but may be increased to
+         accommodate any additional expressions which define intermediate
+         variables for the forward transformation (see the \texttt{"} Calculating
+         Intermediate Values\texttt{"}  section below).
+      }
+      \sstsubsection{
+         FWD = CHARACTER $*$ ( $*$ )( NFWD )
+      }{
+         An array which contains the expressions defining the forward
+         transformation.
+         The syntax of these expressions is described below.
+      }
+      \sstsubsection{
+         NINV = INTEGER
+      }{
+         The number of inverse transformation functions being supplied.
+         This must be at least equal to NIN, but may be increased to
+         accommodate any additional expressions which define intermediate
+         variables for the inverse transformation (see the \texttt{"} Calculating
+         Intermediate Values\texttt{"}  section below).
+      }
+      \sstsubsection{
+         INV = CHARACTER $*$ ( $*$ )( NINV )
+      }{
+         An array which contains the expressions defining the inverse
+         transformation.
+         The syntax of these expressions is described below.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new MathMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MATHMAP = INTEGER
+      }{
+         A pointer to the new MathMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The sequence of numbers produced by the random number functions
+         available within a MathMap is normally unpredictable and different for
+         each MathMap. However, this behaviour may be controlled by means of
+         the MathMap\texttt{'} s \htmlref{Seed}{Seed} attribute.
+
+         \sstitem
+         Normally, compound Mappings (CmpMaps) which involve MathMaps will
+         not be subject to simplification (e.g. using \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}) because AST
+         cannot know how different MathMaps will interact. However, in the
+         special case where a MathMap occurs in series with its own inverse,
+         then simplification may be possible. Whether simplification does, in
+         fact, occur under these circumstances is controlled by the MathMap\texttt{'} s
+         \htmlref{SimpFI}{SimpFI} and \htmlref{SimpIF}{SimpIF} attributes.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Defining Transformation Functions
+   }{
+      A MathMap\texttt{'} s transformation functions are supplied as a set of
+      expressions in an array of character strings. Normally you would
+      supply the same number of expressions for the forward transformation,
+      via the FWD argument, as there are output variables (given by the
+      MathMap\texttt{'} s Nout attribute). For instance, if Nout is 2 you might use:
+      \sstitemlist{
+
+         \sstitem
+         \texttt{'} R = SQRT( X $*$ X $+$ Y $*$ Y )\texttt{'}
+
+         \sstitem
+         \texttt{'} THETA = ATAN2( Y, X )\texttt{'}
+
+      }
+      which defines a transformation from Cartesian to polar
+      coordinates. Here, the variables that appear on the left of each
+      expression (R and THETA) provide names for the output variables and
+      those that appear on the right (X and Y) are references to input
+      variables.
+
+      To complement this, you must also supply expressions for the inverse
+      transformation via the INV argument.  In this case, the number of
+      expressions given would normally match the number of MathMap input
+      coordinates (given by the Nin attribute).  If Nin is 2, you might use:
+      \sstitemlist{
+
+         \sstitem
+         \texttt{'} X = R $*$ COS( THETA )\texttt{'}
+
+         \sstitem
+         \texttt{'} Y = R $*$ SIN( THETA )\texttt{'}
+
+      }
+      which expresses the transformation from polar to Cartesian
+      coordinates. Note that here the input variables (X and Y) are named on
+      the left of each expression, and the output variables (R and THETA)
+      are referenced on the right.
+
+      Normally, you cannot refer to a variable on the right of an expression
+      unless it is named on the left of an expression in the complementary
+      set of functions. Therefore both sets of functions (forward and
+      inverse) must be formulated using the same consistent set of variable
+      names. This means that if you wish to leave one of the transformations
+      undefined, you must supply dummy expressions which simply name each of
+      the output (or input) variables.  For example, you might use:
+      \sstitemlist{
+
+         \sstitem
+         \texttt{'} X\texttt{'}
+
+         \sstitem
+         \texttt{'} Y\texttt{'}
+
+      }
+      for the inverse transformation above, which serves to name the input
+      variables but without defining an inverse transformation.
+   }
+   \sstdiytopic{
+      Calculating Intermediate Values
+   }{
+      It is sometimes useful to calculate intermediate values and then to
+      use these in the final expressions for the output (or input)
+      variables. This may be done by supplying additional expressions for
+      the forward (or inverse) transformation functions. For instance, the
+      following array of five expressions describes 2-dimensional pin-cushion
+      distortion:
+      \sstitemlist{
+
+         \sstitem
+         \texttt{'} R = SQRT( XIN $*$ XIN $+$ YIN $*$ YIN )\texttt{'}
+
+         \sstitem
+         \texttt{'} ROUT = R $*$ ( 1 $+$ 0.1 $*$ R $*$ R )\texttt{'}
+
+         \sstitem
+         \texttt{'} THETA = ATAN2( YIN, XIN )\texttt{'} ,
+
+         \sstitem
+         \texttt{'} XOUT = ROUT $*$ COS( THETA )\texttt{'}
+
+         \sstitem
+         \texttt{'} YOUT = ROUT $*$ SIN( THETA )\texttt{'}
+
+      }
+      Here, we first calculate three intermediate results (R, ROUT
+      and THETA) and then use these to calculate the final results (XOUT
+      and YOUT). The MathMap knows that only the final two results
+      constitute values for the output variables because its Nout attribute
+      is set to 2. You may define as many intermediate variables in this
+      way as you choose. Having defined a variable, you may then refer to it
+      on the right of any subsequent expressions.
+
+      Note that when defining the inverse transformation you may only refer
+      to the output variables XOUT and YOUT. The intermediate variables R,
+      ROUT and THETA (above) are private to the forward transformation and
+      may not be referenced by the inverse transformation. The inverse
+      transformation may, however, define its own private intermediate
+      variables.
+   }
+   \sstdiytopic{
+      Expression Syntax
+   }{
+      The expressions given for the forward and inverse transformations
+      closely follow the syntax of Fortran (with some extensions for
+      compatibility with the C language). They may contain references to
+      variables and literal constants, together with arithmetic, logical,
+      relational and bitwise operators, and function invocations. A set of
+      symbolic constants is also available. Each of these is described in
+      detail below. Parentheses may be used to over-ride the normal order of
+      evaluation. There is no built-in limit to the length of expressions
+      and they are insensitive to case or the presence of additional white
+      space.
+   }
+   \sstdiytopic{
+      Variables
+   }{
+      Variable names must begin with an alphabetic character and may contain
+      only alphabetic characters, digits, and the underscore character
+      \texttt{"} \_\texttt{"} . There is no built-in limit to the length of variable names.
+   }
+   \sstdiytopic{
+      Literal Constants
+   }{
+      Literal constants, such as \texttt{"} 0\texttt{"} , \texttt{"} 1\texttt{"} , \texttt{"} 0.007\texttt{"}  or \texttt{"} 2.505E-16\texttt{"}  may appear
+      in expressions, with the decimal point and exponent being optional (a
+      \texttt{"} D\texttt{"}  may also be used as an exponent character). A unary minus \texttt{"} -\texttt{"}  may
+      be used as a prefix.
+   }
+   \sstdiytopic{
+      Arithmetic Precision
+   }{
+      All arithmetic is floating point, performed in double precision.
+   }
+   \sstdiytopic{
+      Propagation of Missing Data
+   }{
+      Unless indicated otherwise, if any argument of a function or operator
+      has the value AST\_\_BAD (indicating missing data), then the result of
+      that function or operation is also AST\_\_BAD, so that such values are
+      propagated automatically through all operations performed by MathMap
+      transformations.  The special value AST\_\_BAD can be represented in
+      expressions by the symbolic constant \texttt{"} $<$bad$>$\texttt{"} .
+
+      A $<$bad$>$ result (i.e. equal to AST\_\_BAD) is also produced in response
+      to any numerical error (such as division by zero or numerical
+      overflow), or if an invalid argument value is provided to a function
+      or operator.
+   }
+   \sstdiytopic{
+      Arithmetic Operators
+   }{
+      The following arithmetic operators are available:
+      \sstitemlist{
+
+         \sstitem
+         X1 $+$ X2: Sum of X1 and X2.
+
+         \sstitem
+         X1 - X2: Difference of X1 and X2.
+
+         \sstitem
+         X1 $*$ X2: Product of X1 and X2.
+
+         \sstitem
+         X1 / X2: Ratio of X1 and X2.
+
+         \sstitem
+         X1 $*$$*$ X2: X1 raised to the power of X2.
+
+         \sstitem
+         $+$ X: Unary plus, has no effect on its argument.
+
+         \sstitem
+         - X: Unary minus, negates its argument.
+      }
+   }
+   \sstdiytopic{
+      Logical Operators
+   }{
+      Logical values are represented using zero to indicate .FALSE. and
+      non-zero to indicate .TRUE.. In addition, the value AST\_\_BAD is taken to
+      mean \texttt{"} unknown\texttt{"} . The values returned by logical operators may therefore
+      be 0, 1 or AST\_\_BAD. Where appropriate, \texttt{"} tri-state\texttt{"}  logic is
+      implemented. For example, A.OR.B may evaluate to 1 if A is non-zero,
+      even if B has the value AST\_\_BAD. This is because the result of the
+      operation would not be affected by the value of B, so long as A is
+      non-zero.
+
+      The following logical operators are available:
+      \sstitemlist{
+
+         \sstitem
+         X1 .AND. X2: Logical AND between X1 and X2, returning 1 if both X1
+         and X2 are non-zero, and 0 otherwise. This operator implements
+         tri-state logic. (The synonym \texttt{"} \&\&\texttt{"}  is also provided for compatibility
+         with C.)
+
+         \sstitem
+         X1 .OR. X2: Logical OR between X1 and X2, returning 1 if either X1
+         or X2 are non-zero, and 0 otherwise. This operator implements
+         tri-state logic. (The synonym \texttt{"} $|$$|$\texttt{"}  is also provided for compatibility
+         with C.)
+
+         \sstitem
+         X1 .NEQV. X2: Logical exclusive OR (XOR) between X1 and X2,
+         returning 1 if exactly one of X1 and X2 is non-zero, and 0
+         otherwise. Tri-state logic is not used with this operator. (The
+         synonym \texttt{"} .XOR.\texttt{"}  is also provided, although this is not standard
+         Fortran. In addition, the C-like synonym \texttt{"} $\wedge$$\wedge$\texttt{"}  may be used, although
+         this is also not standard.)
+
+         \sstitem
+         X1 .EQV. X2: Tests whether the logical states of X1 and X2
+         (i.e. .TRUE./.FALSE.) are equal. It is the negative of the exclusive OR
+         (XOR) function.  Tri-state logic is not used with this operator.
+
+         \sstitem
+         .NOT. X: Logical unary NOT operation, returning 1 if X is zero, and
+         0 otherwise. (The synonym \texttt{"} !\texttt{"}  is also provided for compatibility with
+         C.)
+      }
+   }
+   \sstdiytopic{
+      Relational Operators
+   }{
+      Relational operators return the logical result (0 or 1) of comparing
+      the values of two floating point values for equality or inequality. The
+      value AST\_\_BAD may also be returned if either argument is $<$bad$>$.
+
+      The following relational operators are available:
+      \sstitemlist{
+
+         \sstitem
+         X1 .EQ. X2: Tests whether X1 equals X2. (The synonym \texttt{"} ==\texttt{"}  is also
+         provided for compatibility with C.)
+
+         \sstitem
+         X1 .NE. X2: Tests whether X1 is unequal to X2. (The synonym \texttt{"} !=\texttt{"}  is
+         also provided for compatibility with C.)
+
+         \sstitem
+         X1 .GT. X2: Tests whether X1 is greater than X2. (The synonym \texttt{"} $>$\texttt{"}  is
+         also provided for compatibility with C.)
+
+         \sstitem
+         X1 .GE. X2: Tests whether X1 is greater than or equal to X2. (The
+         synonym \texttt{"} $>$=\texttt{"}  is also provided for compatibility with C.)
+
+         \sstitem
+         X1 .LT. X2: Tests whether X1 is less than X2. (The synonym \texttt{"} $<$\texttt{"}  is also
+         provided for compatibility with C.)
+
+         \sstitem
+         X1 .LE. X2: Tests whether X1 is less than or equal to X2. (The synonym
+         \texttt{"} $<$=\texttt{"}  is also provided for compatibility with C.)
+
+      }
+      Note that relational operators cannot usefully be used to compare
+      values with the $<$bad$>$ value (representing missing data), because the
+      result is always $<$bad$>$. The ISBAD() function should be used instead.
+
+      Note, also, that because logical operators can operate on floating
+      point values, care must be taken to use parentheses in some cases
+      where they would not normally be required in Fortran. For example,
+      the expresssion:
+      \sstitemlist{
+
+         \sstitem
+         .NOT. A .EQ. B
+
+      }
+      must be written:
+      \sstitemlist{
+
+         \sstitem
+         .NOT. ( A .EQ. B )
+
+      }
+      to prevent the .NOT. operator from associating with the variable A.
+   }
+   \sstdiytopic{
+      Bitwise Operators
+   }{
+      Bitwise operators are often useful when operating on raw data
+      (e.g. from instruments), so they are provided for use in MathMap
+      expressions. In this case, however, the values on which they operate
+      are floating point values rather than the more usual pure integers. In
+      order to produce results which match the pure integer case, the
+      operands are regarded as fixed point binary numbers (i.e. with the
+      binary equivalent of a decimal point) with negative numbers
+      represented using twos-complement notation. For integer values, the
+      resulting bit pattern corresponds to that of the equivalent signed
+      integer (digits to the right of the point being zero). Operations on
+      the bits representing the fractional part are also possible, however.
+
+      The following bitwise operators are available:
+      \sstitemlist{
+
+         \sstitem
+         X1 $>$$>$ X2: Rightward bit shift. The integer value of X2 is taken
+         (rounding towards zero) and the bits representing X1 are then
+         shifted this number of places to the right (or to the left if the
+         number of places is negative). This is equivalent to dividing X1 by
+         the corresponding power of 2.
+
+         \sstitem
+         X1 $<$$<$ X2: Leftward bit shift. The integer value of X2 is taken
+         (rounding towards zero), and the bits representing X1 are then
+         shifted this number of places to the left (or to the right if the
+         number of places is negative). This is equivalent to multiplying X1
+         by the corresponding power of 2.
+
+         \sstitem
+         X1 \& X2: Bitwise AND between the bits of X1 and those of X2
+         (equivalent to a logical AND applied at each bit position in turn).
+
+         \sstitem
+         X1 $|$ X2: Bitwise OR between the bits of X1 and those of X2
+         (equivalent to a logical OR applied at each bit position in turn).
+
+         \sstitem
+         X1 $\wedge$ X2: Bitwise exclusive OR (XOR) between the bits of X1 and
+         those of X2 (equivalent to a logical XOR applied at each bit
+         position in turn).
+
+      }
+      Note that no bit inversion operator is provided. This is
+      because inverting the bits of a twos-complement fixed point binary
+      number is equivalent to simply negating it. This differs from the
+      pure integer case because bits to the right of the binary point are
+      also inverted. To invert only those bits to the left of the binary
+      point, use a bitwise exclusive OR with the value -1 (i.e. X$\wedge$-1).
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The following functions are available:
+      \sstitemlist{
+
+         \sstitem
+         ABS(X): Absolute value of X (sign removal), same as FABS(X).
+
+         \sstitem
+         ACOS(X): Inverse cosine of X, in radians.
+
+         \sstitem
+         ACOSD(X): Inverse cosine of X, in degrees.
+
+         \sstitem
+         ACOSH(X): Inverse hyperbolic cosine of X.
+
+         \sstitem
+         ACOTH(X): Inverse hyperbolic cotangent of X.
+
+         \sstitem
+         ACSCH(X): Inverse hyperbolic cosecant of X.
+
+         \sstitem
+         AINT(X): Integer part of X (round towards zero), same as INT(X).
+
+         \sstitem
+         ASECH(X): Inverse hyperbolic secant of X.
+
+         \sstitem
+         ASIN(X): Inverse sine of X, in radians.
+
+         \sstitem
+         ASIND(X): Inverse sine of X, in degrees.
+
+         \sstitem
+         ASINH(X): Inverse hyperbolic sine of X.
+
+         \sstitem
+         ATAN(X): Inverse tangent of X, in radians.
+
+         \sstitem
+         ATAND(X): Inverse tangent of X, in degrees.
+
+         \sstitem
+         ATANH(X): Inverse hyperbolic tangent of X.
+
+         \sstitem
+         ATAN2(X1, X2): Inverse tangent of X1/X2, in radians.
+
+         \sstitem
+         ATAN2D(X1, X2): Inverse tangent of X1/X2, in degrees.
+
+         \sstitem
+         CEIL(X): Smallest integer value not less then X (round towards
+           plus infinity).
+
+         \sstitem
+         COS(X): Cosine of X in radians.
+
+         \sstitem
+         COSD(X): Cosine of X in degrees.
+
+         \sstitem
+         COSH(X): Hyperbolic cosine of X.
+
+         \sstitem
+         COTH(X): Hyperbolic cotangent of X.
+
+         \sstitem
+         CSCH(X): Hyperbolic cosecant of X.
+
+         \sstitem
+         DIM(X1, X2): Returns X1-X2 if X1 is greater than X2, otherwise 0.
+
+         \sstitem
+         EXP(X): Exponential function of X.
+
+         \sstitem
+         FABS(X): Absolute value of X (sign removal), same as ABS(X).
+
+         \sstitem
+         FLOOR(X): Largest integer not greater than X (round towards
+           minus infinity).
+
+         \sstitem
+         FMOD(X1, X2): Remainder when X1 is divided by X2, same as
+           MOD(X1, X2).
+
+         \sstitem
+         GAUSS(X1, X2): Random sample from a Gaussian distribution with mean
+           X1 and standard deviation X2.
+
+         \sstitem
+         INT(X): Integer part of X (round towards zero), same as AINT(X).
+
+         \sstitem
+         ISBAD(X): Returns 1 if X has the $<$bad$>$ value (AST\_\_BAD), otherwise 0.
+
+         \sstitem
+         LOG(X): Natural logarithm of X.
+
+         \sstitem
+         LOG10(X): Logarithm of X to base 10.
+
+         \sstitem
+         MAX(X1, X2, ...): Maximum of two or more values.
+
+         \sstitem
+         MIN(X1, X2, ...): Minimum of two or more values.
+
+         \sstitem
+         MOD(X1, X2): Remainder when X1 is divided by X2, same as
+           FMOD(X1, X2).
+
+         \sstitem
+         NINT(X): Nearest integer to X (round to nearest).
+
+         \sstitem
+         POISSON(X): Random integer-valued sample from a Poisson
+           distribution with mean X.
+
+         \sstitem
+         POW(X1, X2): X1 raised to the power of X2.
+
+         \sstitem
+         QIF(x1, x2, x3): Returns X2 if X1 is true, and X3 otherwise.
+
+         \sstitem
+         RAND(X1, X2): Random sample from a uniform distribution in the
+           range X1 to X2 inclusive.
+
+         \sstitem
+         SECH(X): Hyperbolic secant of X.
+
+         \sstitem
+         SIGN(X1, X2): Absolute value of X1 with the sign of X2
+           (transfer of sign).
+
+         \sstitem
+         SIN(X): Sine of X in radians.
+
+         \sstitem
+         SINC(X): Sinc function of X [= SIN(X)/X].
+
+         \sstitem
+         SIND(X): Sine of X in degrees.
+
+         \sstitem
+         SINH(X): Hyperbolic sine of X.
+
+         \sstitem
+         SQR(X): Square of X (= X$*$X).
+
+         \sstitem
+         SQRT(X): Square root of X.
+
+         \sstitem
+         TAN(X): Tangent of X in radians.
+
+         \sstitem
+         TAND(X): Tangent of X in degrees.
+
+         \sstitem
+         TANH(X): Hyperbolic tangent of X.
+      }
+   }
+   \sstdiytopic{
+      Symbolic Constants
+   }{
+      The following symbolic constants are available (the enclosing \texttt{"} $<$$>$\texttt{"}
+      brackets must be included):
+      \sstitemlist{
+
+         \sstitem
+         $<$bad$>$: The \texttt{"} bad\texttt{"}  value (AST\_\_BAD) used to flag missing data. Note
+         that you cannot usefully compare values with this constant because the
+         result is always $<$bad$>$. The ISBAD() function should be used instead.
+
+         \sstitem
+         $<$dig$>$: Number of decimal digits of precision available in a
+         floating point (double precision) value.
+
+         \sstitem
+         $<$e$>$: \htmlref{Base}{Base} of natural logarithms.
+
+         \sstitem
+         $<$epsilon$>$: Smallest positive number such that 1.0$+$$<$epsilon$>$ is
+         distinguishable from unity.
+
+         \sstitem
+         $<$mant\_dig$>$: The number of base $<$radix$>$ digits stored in the
+         mantissa of a floating point (double precision) value.
+
+         \sstitem
+         $<$max$>$: Maximum representable floating point (double precision) value.
+
+         \sstitem
+         $<$max\_10\_exp$>$: Maximum integer such that 10 raised to that power
+         can be represented as a floating point (double precision) value.
+
+         \sstitem
+         $<$max\_exp$>$: Maximum integer such that $<$radix$>$ raised to that
+         power minus 1 can be represented as a floating point (double precision)
+         value.
+
+         \sstitem
+         $<$min$>$: Smallest positive number which can be represented as a
+         normalised floating point (double precision) value.
+
+         \sstitem
+         $<$min\_10\_exp$>$: Minimum negative integer such that 10 raised to that
+         power can be represented as a normalised floating point (double
+         precision) value.
+
+         \sstitem
+         $<$min\_exp$>$: Minimum negative integer such that $<$radix$>$ raised to
+         that power minus 1 can be represented as a normalised floating point
+         (double precision) value.
+
+         \sstitem
+         $<$pi$>$: Ratio of the circumference of a circle to its diameter.
+
+         \sstitem
+         $<$radix$>$: The radix (number base) used to represent the mantissa of
+         floating point (double precision) values.
+
+         \sstitem
+         $<$rounds$>$: The mode used for rounding floating point results after
+         addition. Possible values include: -1 (indeterminate), 0 (toward
+         zero), 1 (to nearest), 2 (toward plus infinity) and 3 (toward minus
+         infinity). Other values indicate machine-dependent behaviour.
+      }
+   }
+   \sstdiytopic{
+      Evaluation Precedence and Associativity
+   }{
+      Items appearing in expressions are evaluated in the following order
+      (highest precedence first):
+      \sstitemlist{
+
+         \sstitem
+         Constants and variables
+
+         \sstitem
+         Function arguments and parenthesised expressions
+
+         \sstitem
+         Function invocations
+
+         \sstitem
+         Unary $+$ - ! .not.
+
+         \sstitem
+         $*$$*$
+
+         \sstitem
+         $*$ /
+
+         \sstitem
+         $+$ -
+
+         \sstitem
+         $<$$<$ $>$$>$
+
+         \sstitem
+         $<$ .lt. $<$= .le. $>$ .gt. $>$= .ge.
+
+         \sstitem
+         == .eq. != .ne.
+
+         \sstitem
+         \&
+
+         \sstitem
+         $\wedge$
+
+         \sstitem
+         $|$
+
+         \sstitem
+         \&\& .and.
+
+         \sstitem
+         $\wedge$$\wedge$
+
+         \sstitem
+         $|$$|$ .or
+
+         \sstitem
+         .eqv. .neqv. .xor.
+
+      }
+      All operators associate from left-to-right, except for unary $+$,
+      unary -, !, .not. and $*$$*$ which associate from right-to-left.
+   }
+}
+\sstroutine{
+   AST\_MATRIXMAP
+}{
+   Create a MatrixMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{MatrixMap}{MatrixMap} and optionally initialises
+      its attributes.
+
+      A MatrixMap is a form of \htmlref{Mapping}{Mapping} which performs a general linear
+      transformation.  Each set of input coordinates, regarded as a
+      column-vector, are pre-multiplied by a matrix (whose elements
+      are specified when the MatrixMap is created) to give a new
+      column-vector containing the output coordinates. If appropriate,
+      the inverse transformation may also be performed.
+   }
+   \sstinvocation{
+      RESULT = AST\_MATRIXMAP( NIN, NOUT, FORM, MATRIX, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NIN = INTEGER (Given)
+      }{
+         The number of input coordinates, which determines the number
+         of columns in the matrix.
+      }
+      \sstsubsection{
+         NOUT = INTEGER (Given)
+      }{
+         The number of output coordinates, which determines the number
+         of rows in the matrix.
+      }
+      \sstsubsection{
+         FORM = INTEGER (Given)
+      }{
+         An integer which indicates the form in which the matrix
+         elements will be supplied.
+
+         A value of zero indicates that a full NOUT x NIN  matrix
+         of values will be supplied via the MATRIX argument
+         (below). In this case, the elements should be given in row
+         order (the elements of the first row, followed by the
+         elements of the second row, etc.).
+
+         A value of 1 indicates that only the diagonal elements of the
+         matrix will be supplied, and that all others should be
+         zero. In this case, the elements of MATRIX should contain
+         only the diagonal elements, stored consecutively.
+
+         A value of 2 indicates that a \texttt{"} unit\texttt{"}  matrix is required,
+         whose diagonal elements are set to unity (with all other
+         elements zero).  In this case, the MATRIX argument is not used.
+      }
+      \sstsubsection{
+         MATRIX( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         The array of matrix elements to be used, stored according to
+         the value of FORM.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new MatrixMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_MATRIXMAP = INTEGER
+      }{
+         A pointer to the new MatrixMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         In general, a MatrixMap\texttt{'} s forward transformation will always
+         be available (as indicated by its \htmlref{TranForward}{TranForward} attribute), but
+         its inverse transformation (\htmlref{TranInverse}{TranInverse} attribute) will only be
+         available if the associated matrix is square and non-singular.
+
+         \sstitem
+         As an exception to this, the inverse transformation is always
+         available if a unit or diagonal matrix is specified. In this
+         case, if the matrix is not square, one or more of the input
+         coordinate values may not be recoverable from a set of output
+         coordinates. Any coordinates affected in this way will simply be
+         set to the value zero.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_MIRRORVARIANTS
+}{
+   Make the current Frame mirror the variant Mappings in another Frame
+}{
+   \sstdescription{
+      This routine
+      indicates that all access to the \htmlref{Variant}{Variant} attribute of the current
+      \htmlref{Frame}{Frame} should should be forwarded to some other nominated Frame in
+      the \htmlref{FrameSet}{FrameSet}. For instance, if a value is set subsequently for the
+      Variant attribute of the current Frame, the current Frame will be left
+      unchanged and the setting is instead applied to the nominated Frame.
+      Likewise, if the value of the Variant attribute is requested, the
+      value returned is the value stored for the nominated Frame rather
+      than the current Frame itself.
+
+      This provides a mechanism for propagating the effects of variant
+      Mappings around a FrameSet. If a new Frame is added to a FrameSet
+      by connecting it to an pre-existing Frame that has two or more variant
+      Mappings, then it may be appropriate to set the new Frame so that it
+      mirrors the variants Mappings of the pre-existing Frame. If this is
+      done, then it will be possible to select a specific variant \htmlref{Mapping}{Mapping}
+      using either the pre-existing Frame or the new Frame.
+   }
+   \sstinvocation{
+      CALL AST\_MIRRORVARIANTS( THIS, IFRAME, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         IFRAME = INTEGER (Given)
+      }{
+         The index of the Frame within the FrameSet which is to be
+         mirrored by the current Frame. This value should lie in the range
+         from 1 to the number of Frames in the FrameSet (as given by its
+         \htmlref{Nframe}{Nframe} attribute). If AST\_\_NOFRAME is supplied (or the current
+         Frame is specified), then any mirroring established by a previous
+         call to this
+         routine
+         is disabled.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Mirrors can be chained. That is, if Frame B is set to be a mirror
+         of Frame A, and Frame C is set to be a mirror of Frame B, then
+         Frame C will act as a mirror of Frame A.
+
+         \sstitem
+         Variant Mappings cannot be added to the current Frame if it is
+         mirroring another Frame. So calls to the
+         \htmlref{AST\_ADDVARIANT}{AST\_ADDVARIANT} routine
+         will cause an error to be reported if the current Frame is
+         mirroring another Frame.
+
+         \sstitem
+         A value of AST\_\_BASE may be given for the
+         IFRAME argument
+         to specify the base Frame.
+
+         \sstitem
+         Any variant Mappings explicitly added to the current Frame using
+         AST\_ADDVARIANT
+         will be ignored if the current Frame is mirroring another Frame.
+      }
+   }
+}
+\sstroutine{
+   AST\_NEGATE
+}{
+   Negate the area represented by a Region
+}{
+   \sstdescription{
+      This function negates the area represented by a \htmlref{Region}{Region}. That is,
+      points which were previously inside the region will then be
+      outside, and points which were outside will be inside. This is
+      acomplished by toggling the state of the \htmlref{Negated}{Negated} attribute for
+      the supplied region.
+   }
+   \sstinvocation{
+      CALL AST\_NEGATE( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_NORM
+}{
+   Normalise a set of Frame coordinates
+}{
+   \sstdescription{
+      This routine normalises a set of \htmlref{Frame}{Frame} coordinate values which
+      might be unsuitable for display (e.g. may lie outside the
+      expected range) into a set of acceptable values suitable for
+      display.
+   }
+   \sstinvocation{
+      CALL AST\_NORM( THIS, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         VALUE( $*$ ) = DOUBLE PRECISION (Given and Returned)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). Initially, this should contain a set of
+         coordinate values representing a point in the space which the
+         Frame describes. If these values lie outside the expected
+         range for the Frame, they will be replaced with more
+         acceptable (normalised) values. Otherwise, they will be
+         returned unchanged.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For some classes of Frame, whose coordinate values are not
+         constrained, this function will never modify the values
+         supplied. However, for Frames whose axes represent cyclic
+         quantities (such as angles or positions on the sky), coordinates
+         will typically be wrapped into an appropriate standard range,
+         such as zero to 2$*$pi.
+
+         \sstitem
+         The \htmlref{NormMap}{NormMap} class is a \htmlref{Mapping}{Mapping} which can be used to normalise a
+         set of points using the
+         AST\_NORM routine
+         of a specified Frame.
+
+         \sstitem
+         It is intended to be possible to put any set of coordinates
+         into a form suitable for display by using this function to
+         normalise them, followed by appropriate formatting
+         (using \htmlref{AST\_FORMAT}{AST\_FORMAT}).
+      }
+   }
+}
+\sstroutine{
+   AST\_NORMMAP
+}{
+   Create a NormMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{NormMap}{NormMap} and optionally initialises its
+      attributes.
+
+      A NormMap is a \htmlref{Mapping}{Mapping} which normalises coordinate values using the
+      \htmlref{AST\_NORM}{AST\_NORM} routine
+      of the supplied \htmlref{Frame}{Frame}. The number of inputs and outputs of a NormMap
+      are both equal to the number of axes in the supplied Frame.
+
+      The forward and inverse transformation of a NormMap are both
+      defined but are identical (that is, they do not form a real inverse
+      pair in that the inverse transformation does not undo the
+      normalisation, instead it reapplies it). However, the
+      \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}
+      function will replace neighbouring pairs of forward and inverse
+      NormMaps by a single \htmlref{UnitMap}{UnitMap} (so long as the Frames encapsulated by
+      the two NormMaps are equal - i.e. have the same class and the same
+      attribute values). This means, for instance, that if a \htmlref{CmpMap}{CmpMap} contains
+      a NormMap, the CmpMap will still cancel with its own inverse.
+   }
+   \sstinvocation{
+      RESULT = AST\_NORMMAP( FRAME, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         A pointer to the Frame which is to be used to normalise the
+         supplied axis values.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new NormMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_NORMMAP = INTEGER
+      }{
+         A pointer to the new NormMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_NULLREGION
+}{
+   Create a NullRegion
+}{
+   \sstdescription{
+      This function creates a new \htmlref{NullRegion}{NullRegion} and optionally initialises its
+      attributes.
+
+      A NullRegion is a \htmlref{Region}{Region} with no bounds. If the \htmlref{Negated}{Negated} attribute of a
+      NullRegion is false, the NullRegion represents a Region containing no
+      points. If the Negated attribute of a NullRegion is true, the NullRegion
+      represents an infinite Region containing all points within the
+      coordinate system.
+   }
+   \sstinvocation{
+      RESULT = AST\_NULLREGION( FRAME, UNC, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         A pointer to the \htmlref{Frame}{Frame} in which the region is defined. A deep
+         copy is taken of the supplied Frame. This means that any
+         subsequent changes made to the Frame using the supplied pointer
+         will have no effect the Region.
+      }
+      \sstsubsection{
+         UNC = INTEGER (Given)
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with positions in the supplied Frame.
+         The uncertainty in any point in the Frame is found by shifting the
+         supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at the point
+         being considered. The area covered by the shifted uncertainty
+         Region then represents the uncertainty in the position. The
+         uncertainty is assumed to be the same for all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. \htmlref{Box}{Box}, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created NullRegion. Alternatively,
+         a null \htmlref{Object}{Object} pointer (AST\_\_NULL)
+         may be supplied, in which case a default uncertainty of zero is
+         used.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new NullRegion. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_NULLREGION = INTEGER
+      }{
+         A pointer to the new NullRegion.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_OFFSET
+}{
+   Calculate an offset along a geodesic curve
+}{
+   \sstdescription{
+      This routine finds the \htmlref{Frame}{Frame} coordinate values of a point which
+      is offset a specified distance along the geodesic curve between
+      two other points.
+
+      For example, in a basic Frame, this offset will be along the
+      straight line joining two points. For a more specialised Frame
+      describing a sky coordinate system, however, it would be along
+      the great circle passing through two sky positions.
+   }
+   \sstinvocation{
+      CALL AST\_OFFSET( THIS, POINT1, POINT2, OFFSET, POINT3, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         POINT1( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). This should contain the coordinates of the
+         point marking the start of the geodesic curve.
+      }
+      \sstsubsection{
+         POINT2( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis.
+         This should contain the coordinates of the point marking the
+         end of the geodesic curve.
+      }
+      \sstsubsection{
+         OFFSET = DOUBLE PRECISION
+      }{
+         The required offset from the first point along the geodesic
+         curve. If this is positive, it will be towards the second
+         point. If it is negative, it will be in the opposite
+         direction. This offset need not imply a position lying
+         between the two points given, as the curve will be
+         extrapolated if necessary.
+      }
+      \sstsubsection{
+         POINT3( $*$ ) = DOUBLE PRECISION (Returned)
+      }{
+         An array with one element for each Frame axis
+         in which the coordinates of the required point will be returned.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The geodesic curve used by this routine is the path of
+         shortest distance between two points, as defined by the
+         \htmlref{AST\_DISTANCE}{AST\_DISTANCE} function.
+
+         \sstitem
+         This function will return \texttt{"} bad\texttt{"}  coordinate values (AST\_\_BAD)
+         if any of the input coordinates has this value.
+
+         \sstitem
+         \texttt{"} Bad\texttt{"}  coordinate values will also be returned if the two
+         points supplied are coincident (or otherwise fail to uniquely
+         specify a geodesic curve) but the requested offset is non-zero.
+      }
+   }
+}
+\sstroutine{
+   AST\_OFFSET2
+}{
+   Calculate an offset along a geodesic curve in a 2D Frame
+}{
+   \sstdescription{
+      This routine finds the \htmlref{Frame}{Frame} coordinate values of a point which
+      is offset a specified distance along the geodesic curve at a
+      given angle from a specified starting point. It can only be
+      used with 2-dimensional Frames.
+
+      For example, in a basic Frame, this offset will be along the
+      straight line joining two points. For a more specialised Frame
+      describing a sky coordinate system, however, it would be along
+      the great circle passing through two sky positions.
+   }
+   \sstinvocation{
+      RESULT = AST\_OFFSET2( THIS, POINT1, ANGLE, OFFSET, POINT2, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         POINT1( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). This should contain the coordinates of the
+         point marking the start of the geodesic curve.
+      }
+      \sstsubsection{
+         ANGLE = DOUBLE PRECISION (Given)
+      }{
+         The angle (in radians) from the positive direction of the second
+         axis, to the direction of the required position, as seen from
+         the starting position. Positive rotation is in the sense of
+         rotation from the positive direction of axis 2 to the positive
+         direction of axis 1.
+      }
+      \sstsubsection{
+         OFFSET = DOUBLE PRECISION
+      }{
+         The required offset from the first point along the geodesic
+         curve. If this is positive, it will be in the direction of the
+         given angle. If it is negative, it will be in the opposite
+         direction.
+      }
+      \sstsubsection{
+         POINT2( $*$ ) = DOUBLE PRECISION (Returned)
+      }{
+         An array with one element for each Frame axis
+         in which the coordinates of the required point will be returned.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_OFFSET2 = DOUBLE PRECISION
+      }{
+         The direction of the geodesic curve at the end point. That is, the
+         angle (in radians) between the positive direction of the second
+         axis and the continuation of the geodesic curve at the requested
+         end point. Positive rotation is in the sense of rotation from
+         the positive direction of axis 2 to the positive direction of axis
+         1.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The geodesic curve used by this routine is the path of
+         shortest distance between two points, as defined by the
+         \htmlref{AST\_DISTANCE}{AST\_DISTANCE} function.
+
+         \sstitem
+         An error will be reported if the Frame is not 2-dimensional.
+
+         \sstitem
+         This function will return \texttt{"} bad\texttt{"}  coordinate values (AST\_\_BAD)
+         if any of the input coordinates has this value.
+      }
+   }
+}
+\sstroutine{
+   AST\_OUTLINE$<$X$>$
+}{
+   Create a new Polygon outling values in a 2D data grid
+}{
+   \sstdescription{
+      This is a set of functions that create a \htmlref{Polygon}{Polygon} enclosing a single
+      contiguous set of pixels that have a specified value within a gridded
+      2-dimensional data array (e.g. an image).
+
+      A basic 2-dimensional \htmlref{Frame}{Frame} is used to represent the pixel coordinate
+      system in the returned Polygon. The \htmlref{Domain}{Domain} attribute is set to
+      \texttt{"} PIXEL\texttt{"} , the \htmlref{Title}{Title} attribute is set to \texttt{"} Pixel coordinates\texttt{"} , and the
+      Unit attribute for each axis is set to \texttt{"} pixel\texttt{"} . All other
+      attributes are left unset. The nature of the pixel coordinate system
+      is determined by parameter
+      STARPIX.
+
+      The
+      MAXERR and MAXVERT
+      parameters can be used to control how accurately the returned
+      Polygon represents the required region in the data array. The
+      number of vertices in the returned Polygon will be the minimum
+      needed to achieve the required accuracy.
+
+      You should use a function which matches the numerical type of the
+      data you are processing by replacing $<$X$>$ in the generic function
+      name
+      AST\_OUTLINE$<$X$>$
+      are procesing data with type
+      REAL, you should use the function AST\_OUTLINER
+      (see the \texttt{"} Data Type Codes\texttt{"}  section below for the codes appropriate to
+      other numerical types).
+   }
+   \sstinvocation{
+      RESULT = AST\_OUTLINE$<$X$>$( VALUE, OPER, ARRAY, LBND, UBND, MAXERR,
+                               MAXVERT, INSIDE, STARPIX, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         VALUE = $<$Xtype$>$ (Given)
+      }{
+         A data value that specifies the pixels to be outlined.
+      }
+      \sstsubsection{
+         OPER = INTEGER (Given)
+      }{
+         Indicates how the
+         VALUE
+         parameter is used to select the outlined pixels. It can
+         have any of the following values:
+         \sstitemlist{
+
+            \sstitem
+            AST\_\_LT: outline pixels with value less than VALUE.
+
+            \sstitem
+            AST\_\_LE: outline pixels with value less than or equal to VALUE.
+
+            \sstitem
+            AST\_\_EQ: outline pixels with value equal to VALUE.
+
+            \sstitem
+            AST\_\_NE: outline pixels with value not equal to VALUE.
+
+            \sstitem
+            AST\_\_GE: outline pixels with value greater than or equal to VALUE.
+
+            \sstitem
+            AST\_\_GT: outline pixels with value greater than VALUE.
+         }
+      }
+      \sstsubsection{
+         ARRAY( $*$ ) = $<$Xtype$>$ (Given)
+      }{
+         A
+         2-dimensional array containing the data to be processed.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using AST\_OUTLINER, the type of each array element
+         should be REAL).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the second dimension least rapidly
+         (i.e. normal Fortran array storage order).
+      }
+      \sstsubsection{
+         LBND( 2 ) = INTEGER (Given)
+      }{
+         An array
+         containing the pixel index of the first pixel in the input grid
+         along each dimension.
+      }
+      \sstsubsection{
+         UBND( 2) = INTEGER (Given)
+      }{
+         An array
+         containing the pixel index of the last pixel in the input grid
+         along each dimension.
+
+         Note that LBND and UBND together define the shape
+         and size of the input pixel grid, its extent along a particular
+         (J\texttt{'} th) dimension being UBND(J)-LBND(J)$+$1 pixels.
+         For FITS images,
+         the LBND values will be 1 and the UBND
+         values will be equal to the NAXISi header values. Other
+         data systems, such as the Starlink NDF system, allow an
+         arbitrary pixel origin to be used (i.e. LBND
+         is not necessarily 1).
+
+         These bounds also define the input grid\texttt{'} s floating point coordinate
+         system, each pixel having unit extent along each dimension with
+         integral coordinate values at its centre or upper corner, as selected
+         by parameter
+         STARPIX.
+      }
+      \sstsubsection{
+         MAXERR = DOUBLE PRECISION (Given)
+      }{
+         Together with
+         MAXVERT,
+         this determines how accurately the returned Polygon represents
+         the required region of the data array. It gives the target
+         discrepancy between the returned Polygon and the accurate outline
+         in the data array, expressed as a number of pixels. Insignificant
+         vertices are removed from the accurate outline, one by one, until
+         the number of vertices remaining in the returned Polygon equals
+         MAXVERT,
+         or the largest discrepancy between the accurate outline and the
+         returned Polygon is greater than
+         MAXERR. If MAXERR
+         is zero or less, its value is ignored and the returned Polygon will
+         have the number of vertices specified by
+         MAXVERT.
+      }
+      \sstsubsection{
+         MAXVERT = INTEGER (Given)
+      }{
+         Together with
+         MAXERR,
+         this determines how accurately the returned Polygon represents
+         the required region of the data array. It gives the maximum
+         allowed number of vertices in the returned Polygon. Insignificant
+         vertices are removed from the accurate outline, one by one, until
+         the number of vertices remaining in the returned Polygon equals
+         MAXVERT,
+         or the largest discrepancy between the accurate outline and the
+         returned Polygon is greater than
+         MAXERR. If MAXVERT
+         is less than 3, its value is ignored and the number of vertices in
+         the returned Polygon will be the minimum needed to ensure that the
+         discrepancy between the accurate outline and the returned
+         Polygon is less than
+         MAXERR.
+      }
+      \sstsubsection{
+         INSIDE( 2 ) = INTEGER (Given)
+      }{
+         An array
+         containing the pixel indices of a pixel known to be inside the
+         required region. This is needed because the supplied data
+         array may contain several disjoint areas of pixels that satisfy
+         the criterion specified by
+         VALUE and OPER.
+         In such cases, the area described by the returned Polygon will
+         be the one that contains the pixel specified by
+         INSIDE.
+         If the specified pixel is outside the bounds given by
+         LBND and UBND,
+         or has a value that does not meet the criterion specified by
+         VALUE and OPER,
+         then this function will search for a suitable pixel. The search
+         starts at the central pixel and proceeds in a spiral manner until
+         a pixel is found that meets the specified crierion.
+      }
+      \sstsubsection{
+         STARPIX = LOGICAL (Given)
+      }{
+         A flag indicating the nature of the pixel coordinate system used
+         to describe the vertex positions in the returned Polygon. If
+         .TRUE.,
+         the standard Starlink definition of pixel coordinate is used in
+         which a pixel with integer index I spans a range of pixel coordinate
+         from (I-1) to I (i.e. pixel corners have integral pixel coordinates).
+         If .FALSE.,
+         the definition of pixel coordinate used by other AST functions
+         such as AST\_RESAMPLE, AST\_MASK,
+         etc., is used. In this definition, a pixel with integer index I
+         spans a range of pixel coordinate from (I-0.5) to (I$+$0.5) (i.e.
+         pixel centres have integral pixel coordinates).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_OUTLINE$<$X$>$ = INTEGER
+      }{
+         A pointer to the required Polygon.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function proceeds by first finding a very accurate polygon,
+         and then removing insignificant vertices from this fine polygon
+         using
+         \htmlref{AST\_DOWNSIZE}{AST\_DOWNSIZE}.
+
+         \sstitem
+         The returned Polygon is the outer boundary of the contiguous set
+         of pixels that includes ths specified \texttt{"} inside\texttt{"}  point, and satisfy
+         the specified value requirement. This set of pixels may potentially
+         include \texttt{"} holes\texttt{"}  where the pixel values fail to meet the specified
+         value requirement. Such holes will be ignored by this function.
+
+         \sstitem
+         AST\_\_NULL
+         will be returned if this function is invoked with the global
+         error status set, or if it should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate masking function, you should
+      replace $<$X$>$ in the generic function name AST\_OUTLINE$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         UI: INTEGER (treated as unsigned)
+
+         \sstitem
+         S: INTEGER$*$2 (short integer)
+
+         \sstitem
+         US: INTEGER$*$2 (short integer, treated as unsigned)
+
+         \sstitem
+         B: BYTE (treated as signed)
+
+         \sstitem
+         UB: BYTE (treated as unsigned)
+
+      }
+      For example, AST\_OUTLINED would be used to process DOUBLE
+      PRECISION data, while AST\_OUTLINES would be used to process
+      short integer data (stored in an INTEGER$*$2 array), etc.
+
+      For compatibility with other Starlink facilities, the codes W
+      and UW are provided as synonyms for S and US respectively (but
+      only in the Fortran interface to AST).
+   }
+}
+\sstroutine{
+   AST\_OVERLAP
+}{
+   Test if two regions overlap each other
+}{
+   \sstdescription{
+      This function returns an integer value indicating if the two
+      supplied Regions overlap. The two Regions are converted to a commnon
+      coordinate system before performing the check. If this conversion is
+      not possible (for instance because the two Regions represent areas in
+      different domains), then the check cannot be performed and a zero value
+      is returned to indicate this.
+   }
+   \sstinvocation{
+      RESULT = AST\_OVERLAP( THIS, THAT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the first \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         THAT = INTEGER (Given)
+      }{
+         Pointer to the second Region.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_OVERLAP = INTEGER
+      }{
+         A value indicating if there is any overlap between the two Regions.
+         Possible values are:
+
+         0 - The check could not be performed because the second Region
+             could not be mapped into the coordinate system of the first
+             Region.
+
+         1 - There is no overlap between the two Regions.
+
+         2 - The first Region is completely inside the second Region.
+
+         3 - The second Region is completely inside the first Region.
+
+         4 - There is partial overlap between the two Regions.
+
+         5 - The Regions are identical to within their uncertainties.
+
+         6 - The second Region is the exact negation of the first Region
+             to within their uncertainties.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned values 5 and 6 do not check the value of the \htmlref{Closed}{Closed}
+         attribute in the two Regions.
+
+         \sstitem
+         A value of zero will be returned if this function is invoked with the
+         AST error status set, or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_PARAMETERNAME
+}{
+   Get the name of the global parameter at a given index within the Table
+}{
+   \sstdescription{
+      This function returns a string holding the name of the global parameter with
+      the given index within the \htmlref{Table}{Table}.
+
+      This function is intended primarily as a means of iterating round all
+      the parameters in a Table. For this purpose, the number of parameters in
+      the Table is given by the \htmlref{Nparameter}{Nparameter} attribute of the Table. This function
+      could then be called in a loop, with the index value going from
+      one to Nparameter.
+
+      Note, the index associated with a parameter decreases monotonically with
+      the age of the parameter: the oldest Parameter in the Table will have index
+      one, and the Parameter added most recently to the Table will have the
+      largest index.
+   }
+   \sstinvocation{
+      RESULT = AST\_PARAMETERNAME( THIS, INDEX, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         INDEX = INTEGER (Given)
+      }{
+         The index into the list of parameters. The first parameter has index
+         one, and the last has index \texttt{"} Nparameter\texttt{"} .
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_PARAMETERNAME = CHARACTER $*$ ( AST\_\_SZCHR )
+      }{
+         The
+         upper case parameter name.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A blank string will be returned if this function is invoked
+         with STATUS set to an error value, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_PCDMAP
+}{
+   Create a PcdMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{PcdMap}{PcdMap} and optionally initialises its
+      attributes.
+
+      A PcdMap is a non-linear \htmlref{Mapping}{Mapping} which transforms 2-dimensional
+      positions to correct for the radial distortion introduced by some
+      cameras and telescopes. This can take the form either of pincushion
+      or barrel distortion, and is characterized by a single distortion
+      coefficient.
+
+      A PcdMap is specified by giving this distortion coefficient and the
+      coordinates of the centre of the radial distortion. The forward
+      transformation of a PcdMap applies the distortion:
+
+         RD = R $*$ ( 1 $+$ C $*$ R $*$ R )
+
+      where R is the undistorted radial distance from the distortion
+      centre (specified by attribute PcdCen), RD is the radial distance
+      from the same centre in the presence of distortion, and C is the
+      distortion coefficient (given by attribute \htmlref{Disco}{Disco}).
+
+      The inverse transformation of a PcdMap removes the distortion
+      produced by the forward transformation. The expression used to derive
+      R from RD is an approximate inverse of the expression above, obtained
+      from two iterations of the Newton-Raphson method. The mismatch between
+      the forward and inverse expressions is negligible for astrometric
+      applications (to reach 1 milliarcsec at the edge of the Anglo-Australian
+      Telescope triplet or a Schmidt field would require field diameters of
+      2.4 and 42 degrees respectively).
+
+      If a PcdMap is inverted (e.g. using \htmlref{AST\_INVERT}{AST\_INVERT}) then the roles of the
+      forward and inverse transformations are reversed; the forward
+      transformation will remove the distortion, and the inverse
+      transformation will apply it.
+   }
+   \sstinvocation{
+      RESULT = AST\_PCDMAP( DISCO, PCDCEN, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         DISCO = DOUBLE PRECISION (Given)
+      }{
+         The distortion coefficient. Negative values give barrel
+         distortion, positive values give pincushion distortion, and
+         zero gives no distortion.
+      }
+      \sstsubsection{
+         PCDCEN( 2 ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing the coordinates of the centre of the
+         distortion.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new PcdMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_PCDMAP = INTEGER
+      }{
+         A pointer to the new PcdMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_PERMAXES
+}{
+   Permute the axis order in a Frame
+}{
+   \sstdescription{
+      This routine permutes the order in which a \htmlref{Frame}{Frame}\texttt{'} s axes occur.
+   }
+   \sstinvocation{
+      CALL AST\_PERMAXES( THIS, PERM, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         PERM( $*$ ) = INTEGER (Given)
+      }{
+         An array with one element for each axis of the Frame (\htmlref{Naxes}{Naxes}
+         attribute). This should list the axes in their new order,
+         using the original axis numbering (which starts at 1 for the
+         first axis).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Only genuine permutations of the axis order are permitted, so
+         each axis must be referenced exactly once in the PERM array.
+
+         \sstitem
+         If successive axis permutations are applied to a Frame, then
+         the effects are cumulative.
+      }
+   }
+}
+\sstroutine{
+   AST\_PERMMAP
+}{
+   Create a PermMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{PermMap}{PermMap} and optionally initialises its
+      attributes.
+
+      A PermMap is a \htmlref{Mapping}{Mapping} which permutes the order of coordinates,
+      and possibly also changes the number of coordinates, between its
+      input and output.
+
+      In addition to permuting the coordinate order, a PermMap may
+      also assign constant values to coordinates. This is useful when
+      the number of coordinates is being increased as it allows fixed
+      values to be assigned to any new ones.
+   }
+   \sstinvocation{
+      RESULT = AST\_PERMMAP( NIN, INPERM, NOUT, OUTPERM, CONSTANT, OPTIONS,
+                            STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NIN = INTEGER (Given)
+      }{
+         The number of input coordinates.
+      }
+      \sstsubsection{
+         INPERM = INTEGER( NIN ) (Given)
+      }{
+         An array which, for each input
+         coordinate, should contain the number of the output
+         coordinate whose value is to be used (note that this array
+         therefore defines the inverse coordinate transformation).
+         Coordinates are numbered starting from 1.
+
+         For details of additional special values that may be used in
+         this array, see the description of the CONSTANT argument.
+      }
+      \sstsubsection{
+         NOUT = INTEGER (Given)
+      }{
+         The number of output coordinates.
+      }
+      \sstsubsection{
+         OUTPERM = INTEGER( NOUT ) (Given)
+      }{
+         An array which, for each output
+         coordinate, should contain the number of the input coordinate
+         whose value is to be used (note that this array therefore
+         defines the forward coordinate transformation).  Coordinates
+         are numbered starting from 1.
+
+         For details of additional special values that may be used in
+         this array, see the description of the CONSTANT argument.
+      }
+      \sstsubsection{
+         CONSTANT = DOUBLE PRECISION( $*$ ) (Given)
+      }{
+         An array containing values which may be assigned to
+         input and/or output coordinates instead of deriving them
+         from other coordinate values. If either of the INPERM or
+         OUTPERM arrays contains a negative value, it is used to
+         address this CONSTANT array (such that -1 addresses the
+         first element, -2 addresses the second element, etc.) and the
+         value obtained is used as the corresponding coordinate value.
+
+         Care should be taken to ensure that locations lying outside
+         the extent of this array are not accidentally addressed. The
+         array is not used if the INPERM and OUTPERM arrays do not
+         contain negative values.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new PermMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_PERMMAP = INTEGER
+      }{
+         A pointer to the new PermMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If either of the INPERM or OUTPERM arrays contains a
+         zero value (or a positive value which does not identify a valid
+         output/input coordinate, as appropriate), then the value
+         AST\_\_BAD is assigned as the new coordinate value.
+
+         \sstitem
+         This function does not attempt to ensure that the forward and
+         inverse transformations performed by the PermMap are
+         self-consistent in any way. You are therefore free to supply
+         coordinate permutation arrays that achieve whatever effect is
+         desired.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_PICKAXES
+}{
+   Create a new Frame by picking axes from an existing one
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Frame}{Frame} whose axes are copied from an
+      existing Frame along with other Frame attributes, such as its
+      \htmlref{Title}{Title}. Any number (zero or more) of the original Frame\texttt{'} s axes
+      may be copied, in any order, and additional axes with default
+      attributes may also be included in the new Frame.
+
+      A \htmlref{Mapping}{Mapping} that converts between the coordinate
+      systems described by the two Frames will also be returned.
+   }
+   \sstinvocation{
+      RESULT = AST\_PICKAXES( THIS, NAXES, AXES, MAP, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the original Frame.
+      }
+      \sstsubsection{
+         NAXES = INTEGER (Given)
+      }{
+         The number of axes required in the new Frame.
+      }
+      \sstsubsection{
+         AXES( NAXES ) = INTEGER (Given)
+      }{
+         An array which lists the axes to be
+         copied. These should be given in the order required in the
+         new Frame, using the axis numbering in the original Frame
+         (which starts at 1 for the first axis). Axes may be selected
+         in any order, but each may only be used once.  If additional
+         (default) axes are also to be included, the corresponding
+         elements of this array should be set to zero.
+      }
+      \sstsubsection{
+         MAP = INTEGER (Returned)
+      }{
+         A pointer to a new
+         Mapping. This will be a \htmlref{PermMap}{PermMap} (or a \htmlref{UnitMap}{UnitMap} as a special
+         case) that describes the axis permutation that has taken
+         place between the original and new Frames. The Mapping\texttt{'} s
+         forward transformation will convert coordinates from the
+         original Frame into the new one, and vice versa.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         This function applies to all Frames. The class of Frame returned
+         may differ from that of the original Frame, depending on which
+         axes are selected. For example, if a single axis is picked from a
+         \htmlref{SkyFrame}{SkyFrame} (which must always have two axes) then the resulting
+         Frame cannot be a valid SkyFrame, so will revert to the parent
+         class (Frame) instead.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         Using this function on a FrameSet is identical to using it on
+         the current Frame in the FrameSet. The returned Frame will not
+         be a FrameSet.
+      }
+      \sstsubsection{
+         \htmlref{Region}{Region}
+      }{
+         If this function is used on a Region, an attempt is made to
+         retain the bounds information on the selected axes. If
+         succesful, the returned Frame will be a Region of some class.
+         Otherwise, the returned Frame is obtained by calling this
+         function on the Frame represented by the supplied Region (the
+         returned Frame will then not be a Region). In order to be
+         succesful, the selected axes in the Region must be independent
+         of the others. For instance, a \htmlref{Box}{Box} can be split in this way but
+         a \htmlref{Circle}{Circle} cannot. Another requirement for success is that no
+         default axes are added (that is, the
+         AXES
+         array must not contain any zero values.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_PICKAXES = INTEGER
+      }{
+         A pointer to the new Frame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The new Frame will contain a \texttt{"} deep\texttt{"}  copy (c.f. \htmlref{AST\_COPY}{AST\_COPY}) of all
+         the data selected from the original Frame. Modifying any aspect
+         of the new Frame will therefore not affect the original one.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_PLOT
+}{
+   Create a Plot
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Plot}{Plot} and optionally initialises its
+      attributes.
+
+      A Plot is a specialised form of \htmlref{FrameSet}{FrameSet}, in which the base
+      \htmlref{Frame}{Frame} describes a \texttt{"} graphical\texttt{"}  coordinate system and is
+      associated with a rectangular plotting area in the underlying
+      graphics system. This plotting area is where graphical output
+      appears. It is defined when the Plot is created.
+
+      The current Frame of a Plot describes a \texttt{"} physical\texttt{"}  coordinate
+      system, which is the coordinate system in which plotting
+      operations are specified. The results of each plotting operation
+      are automatically transformed into graphical coordinates so as
+      to appear in the plotting area (subject to any clipping which
+      may be in effect).
+
+      Because the \htmlref{Mapping}{Mapping} between physical and graphical coordinates
+      may often be non-linear, or even discontinuous, most plotting
+      does not result in simple straight lines. The basic plotting
+      element is therefore not a straight line, but a geodesic curve
+      (see \htmlref{AST\_CURVE}{AST\_CURVE}). A Plot also provides facilities for drawing
+      markers or symbols (\htmlref{AST\_MARK}{AST\_MARK}), text (\htmlref{AST\_TEXT}{AST\_TEXT}) and grid lines
+      (\htmlref{AST\_GRIDLINE}{AST\_GRIDLINE}). It is also possible to draw curvilinear axes
+      with optional coordinate grids (\htmlref{AST\_GRID}{AST\_GRID}).
+      A range of Plot attributes is available to allow precise control
+      over the appearance of graphical output produced by these
+      routines.
+
+      You may select different physical coordinate systems in which to
+      plot (including the native graphical coordinate system itself)
+      by selecting different Frames as the current Frame of a Plot,
+      using its \htmlref{Current}{Current} attribute.  You may also set up clipping (see
+      \htmlref{AST\_CLIP}{AST\_CLIP}) to limit the extent of any plotting you perform, and
+      this may be done in any of the coordinate systems associated
+      with the Plot, not necessarily the one you are plotting in.
+
+      Like any FrameSet, a Plot may also be used as a Frame. In this
+      case, it behaves like its current Frame, which describes the
+      physical coordinate system.
+
+      When used as a Mapping, a Plot describes the inter-relation
+      between graphical coordinates (its base Frame) and physical
+      coordinates (its current Frame).  It differs from a normal
+      FrameSet, however, in that an attempt to transform points which
+      lie in clipped areas of the Plot will result in bad coordinate
+      values (AST\_\_BAD).
+   }
+   \sstinvocation{
+      RESULT = AST\_PLOT( FRAME, GRAPHBOX, BASEBOX, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         Pointer to a Frame describing the physical coordinate system
+         in which to plot. A pointer to a FrameSet may also be given,
+         in which case its current Frame will be used to define the
+         physical coordinate system and its base Frame will be mapped
+         on to graphical coordinates (see below).
+
+         If a null \htmlref{Object}{Object} pointer (AST\_\_NULL) is given, a default
+         2-dimensional Frame will be used to describe the physical
+         coordinate system. Labels, etc. may then be attached to this
+         by setting the appropriate Frame attributes
+         (e.g. \htmlref{Label(axis)}{Label(axis)}) for the Plot.
+      }
+      \sstsubsection{
+         GRAPHBOX( 4 ) = REAL (Given)
+      }{
+         An array giving the position and extent of the plotting area
+         (on the plotting surface of the underlying graphics system)
+         in which graphical output is to appear. This must be
+         specified using graphical coordinates appropriate to the
+         underlying graphics system.
+
+         The first pair of values should give the coordinates of the
+         bottom left corner of the plotting area and the second pair
+         should give the coordinates of the top right corner. The
+         coordinate on the horizontal axis should be given first in
+         each pair. Note that the order in which these points are
+         given is important because it defines up, down, left and
+         right for subsequent graphical operations.
+      }
+      \sstsubsection{
+         BASEBOX( 4 ) = DOUBLE PRECISION (Given)
+      }{
+         An array giving the coordinates of two points in the supplied
+         Frame (or in the base Frame if a FrameSet was supplied) which
+         correspond to the bottom left and top right corners of the
+         plotting area, as specified above. This range of coordinates
+         will be mapped linearly on to the plotting area. The
+         coordinates should be given in the same order as above.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Plot. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_PLOT
+      }{
+         A pointer to the new Plot.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The base Frame of the returned Plot will be a new Frame which
+         is created by this function to represent the coordinate system
+         of the underlying graphics system (graphical coordinates). It is
+         given a Frame index of 1 within the Plot. The choice of base
+         Frame (\htmlref{Base}{Base} attribute) should not, in general, be changed once a
+         Plot has been created (although you could use this as a way of
+         moving the plotting area around on the plotting surface).
+
+         \sstitem
+         If a Frame is supplied (via the FRAME pointer), then it
+         becomes the current Frame of the new Plot and is given a Frame
+         index of 2.
+
+         \sstitem
+         If a FrameSet is supplied (via the FRAME pointer), then
+         all the Frames within this FrameSet become part of the new Plot
+         (where their Frame indices are increased by 1), with the
+         FrameSet\texttt{'} s current Frame becoming the current Frame of the Plot.
+
+         \sstitem
+         If a null Object pointer (AST\_\_NULL) is supplied (via the
+         FRAME pointer), then the returned Plot will contain two
+         Frames, both created by this function. The base Frame will
+         describe graphics coordinates (as above) and the current Frame
+         will be a basic Frame with no attributes set (this will
+         therefore give default values for such things as the Plot \htmlref{Title}{Title}
+         and the Label on each axis). Physical coordinates will be mapped
+         linearly on to graphical coordinates.
+
+         \sstitem
+         An error will result if the Frame supplied (or the base Frame
+         if a FrameSet was supplied) is not 2-dimensional.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_PLOT3D
+}{
+   Create a Plot3D
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Plot3D}{Plot3D} and optionally initialises
+      its attributes.
+
+      A Plot3D is a specialised form of \htmlref{Plot}{Plot} that provides facilities
+      for producing 3D graphical output.
+   }
+   \sstinvocation{
+      RESULT = AST\_PLOT3D( FRAME, GRAPHBOX, BASEBOX, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         Pointer to a \htmlref{Frame}{Frame} describing the physical coordinate system
+         in which to plot. A pointer to a \htmlref{FrameSet}{FrameSet} may also be given,
+         in which case its current Frame will be used to define the
+         physical coordinate system and its base Frame will be mapped
+         on to graphical coordinates (see below).
+
+         If a null \htmlref{Object}{Object} pointer (AST\_\_NULL) is given, a default
+         3-dimensional Frame will be used to describe the physical
+         coordinate system. Labels, etc. may then be attached to this
+         by setting the appropriate Frame attributes
+         (e.g. \htmlref{Label(axis)}{Label(axis)}) for the Plot.
+      }
+      \sstsubsection{
+         GRAPHBOX( 6 ) = REAL (Given)
+      }{
+         An array giving the position and extent of the plotting volume
+         (within the plotting space of the underlying graphics system)
+         in which graphical output is to appear. This must be
+         specified using graphical coordinates appropriate to the
+         underlying graphics system.
+
+         The first triple of values should give the coordinates of the
+         bottom left corner of the plotting volume and the second triple
+         should give the coordinates of the top right corner. The
+         coordinate on the horizontal axis should be given first in
+         each pair. Note that the order in which these points are
+         given is important because it defines up, down, left and
+         right for subsequent graphical operations.
+      }
+      \sstsubsection{
+         BASEBOX( 6 ) = DOUBLE PRECISION (Given)
+      }{
+         An array giving the coordinates of two points in the supplied
+         Frame (or in the base Frame if a FrameSet was supplied) which
+         correspond to the bottom left and top right corners of the
+         plotting volume, as specified above. This range of coordinates
+         will be mapped linearly on to the plotting area. The
+         coordinates should be given in the same order as above.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Plot3D. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_PLOT3D = INTEGER
+      }{
+         A pointer to the new Plot3D.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The base Frame of the returned Plot3D will be a new Frame which
+         is created by this function to represent the coordinate system
+         of the underlying graphics system (graphical coordinates). It is
+         given a Frame index of 1 within the Plot3D. The choice of base
+         Frame (\htmlref{Base}{Base} attribute) should not, in general, be changed once a
+         Plot3D has been created (although you could use this as a way of
+         moving the plotting area around on the plotting surface).
+
+         \sstitem
+         If a Frame is supplied (via the FRAME pointer), then it
+         becomes the current Frame of the new Plot3D and is given a Frame
+         index of 2.
+
+         \sstitem
+         If a FrameSet is supplied (via the FRAME pointer), then
+         all the Frames within this FrameSet become part of the new Plot3D
+         (where their Frame indices are increased by 1), with the
+         FrameSet\texttt{'} s current Frame becoming the current Frame of the Plot3D.
+
+         \sstitem
+         At least one of the three axes of the current Frame must be
+         independent of the other two current Frame axes.
+
+         \sstitem
+         If a null Object pointer (AST\_\_NULL) is supplied (via the
+         FRAME pointer), then the returned Plot3D will contain two
+         Frames, both created by this function. The base Frame will
+         describe graphics coordinates (as above) and the current Frame
+         will be a basic Frame with no attributes set (this will
+         therefore give default values for such things as the Plot3D \htmlref{Title}{Title}
+         and the Label on each axis). Physical coordinates will be mapped
+         linearly on to graphical coordinates.
+
+         \sstitem
+         An error will result if the Frame supplied (or the base Frame
+         if a FrameSet was supplied) is not 3-dimensional.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_POINTLIST
+}{
+   Create a PointList
+}{
+   \sstdescription{
+      This function creates a new \htmlref{PointList}{PointList} object and optionally initialises
+      its attributes.
+
+      A PointList object is a specialised type of \htmlref{Region}{Region} which represents a
+      collection of points in a coordinate \htmlref{Frame}{Frame}.
+   }
+   \sstinvocation{
+      RESULT = AST\_POINTLIST( FRAME, NPNT, COORD, DIM, POINTS, UNC, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         A pointer to the Frame in which the region is defined. A deep
+         copy is taken of the supplied Frame. This means that any
+         subsequent changes made to the Frame using the supplied pointer
+         will have no effect the Region.
+      }
+      \sstsubsection{
+         NPNT = INTEGER (Given)
+      }{
+         The number of points in the Region.
+      }
+      \sstsubsection{
+         NCOORD = INTEGER (Given)
+      }{
+         The number of coordinates being supplied for each point. This
+         must equal the number of axes in the supplied Frame, given by
+         its \htmlref{Naxes}{Naxes} attribute.
+      }
+      \sstsubsection{
+         DIM = INTEGER (Given)
+      }{
+         The number of elements along the first dimension of the POINTS
+         array (which contains the point coordinates). This value is
+         required so that the coordinate values can be correctly
+         located if they do not entirely fill this array. The value
+         given should not be less than NPNT.
+      }
+      \sstsubsection{
+         POINTS( DIM, NCOORD ) = DOUBLE PRECISION (Given)
+      }{
+         A 2-dimensional array giving the physical coordinates of the
+         points. These should be stored such that the value of coordinate
+         number COORD for point number PNT is found in element IN(PNT,COORD).
+      }
+      \sstsubsection{
+         UNC = INTEGER (Given)
+      }{
+         An optional pointer to an existing Region which specifies the uncertainties
+         associated with each point in the PointList being created. The
+         uncertainty at any point in the PointList is found by shifting the
+         supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at the point
+         being considered. The area covered by the shifted uncertainty Region
+         then represents the uncertainty in the position. The uncertainty is
+         assumed to be the same for all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. \htmlref{Box}{Box}, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Box. Alternatively,
+         a null \htmlref{Object}{Object} pointer (AST\_\_NULL)
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the bounding box of the
+         PointList being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{AST\_OVERLAP}{AST\_OVERLAP}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new PointList. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_POINTLIST = INTEGER
+      }{
+         A pointer to the new PointList.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_POLYCURVE
+}{
+   Draw a series of connected geodesic curves
+}{
+   \sstdescription{
+      This routine joins a series of points specified in the physical
+      coordinate system of a \htmlref{Plot}{Plot} by drawing a sequence of geodesic
+      curves.  It is equivalent to making repeated calls to the
+      \htmlref{AST\_CURVE}{AST\_CURVE} routine (q.v.), except that AST\_POLYCURVE will
+      generally be more efficient when drawing many geodesic curves
+      end-to-end. A typical application of this might be in drawing
+      contour lines.
+
+      As with AST\_CURVE, full account is taken of the \htmlref{Mapping}{Mapping} between
+      physical and graphical coordinate systems. This includes any
+      discontinuities and clipping established using \htmlref{AST\_CLIP}{AST\_CLIP}.
+   }
+   \sstinvocation{
+      CALL AST\_POLYCURVE( THIS, NPOINT, NCOORD, INDIM, IN, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         NPOINT = INTEGER (Given)
+      }{
+         The number of points between which geodesic curves are to be drawn.
+      }
+      \sstsubsection{
+         NCOORD = INTEGER (Given)
+      }{
+         The number of coordinates being supplied for each point (i.e.
+         the number of axes in the current \htmlref{Frame}{Frame} of the Plot, as given
+         by its \htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         INDIM = INTEGER (Given)
+      }{
+         The number of elements along the first dimension of the IN
+         array (which contains the input coordinates). This value is
+         required so that the coordinate values can be correctly
+         located if they do not entirely fill this array. The value
+         given should not be less than NPOINT.
+      }
+      \sstsubsection{
+         IN( INDIM, NCOORD ) = DOUBLE PRECISION (Given)
+      }{
+         A 2-dimensional array giving the physical coordinates of the
+         points which are to be joined in sequence by geodesic
+         curves. These should be stored such that the value of
+         coordinate number COORD for input point number POINT is found
+         in element IN(POINT,COORD).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No curve is drawn on either side of any point which has any
+         coordinate equal to the value AST\_\_BAD.
+
+         \sstitem
+         An error results if the base Frame of the Plot is not
+         2-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   AST\_POLYGON
+}{
+   Create a Polygon
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Polygon}{Polygon} object and optionally initialises
+      its attributes.
+
+      The Polygon class implements a polygonal area, defined by a
+      collection of vertices, within a 2-dimensional \htmlref{Frame}{Frame}. The vertices
+      are connected together by geodesic curves within the encapsulated Frame.
+      For instance, if the encapsulated Frame is a simple Frame then the
+      geodesics will be straight lines, but if the Frame is a \htmlref{SkyFrame}{SkyFrame} then
+      the geodesics will be great circles. Note, the vertices must be
+      supplied in an order such that the inside of the polygon is to the
+      left of the boundary as the vertices are traversed. Supplying them
+      in the reverse order will effectively negate the polygon.
+
+      Within a SkyFrame, neighbouring vertices are always joined using the
+      shortest path. Thus if an edge of 180 degrees or more in length is
+      required, it should be split into section each of which is less
+      than 180 degrees. The closed path joining all the vertices in order
+      will divide the celestial sphere into two disjoint regions. The
+      inside of the polygon is the region which is circled in an
+      anti-clockwise manner (when viewed from the inside of the celestial
+      sphere) when moving through the list of vertices in the order in
+      which they were supplied when the Polygon was created (i.e. the
+      inside is to the left of the boundary when moving through the
+      vertices in the order supplied).
+   }
+   \sstinvocation{
+      RESULT = AST\_POLYGON( FRAME, NPNT, DIM, POINTS, UNC, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME = INTEGER (Given)
+      }{
+         A pointer to the Frame in which the region is defined. It must
+         have exactly 2 axes. A deep copy is taken of the supplied Frame.
+         This means that any subsequent changes made to the Frame using the
+         supplied pointer will have no effect the \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         NPNT = INTEGER (Given)
+      }{
+         The number of points in the Region.
+      }
+      \sstsubsection{
+         DIM = INTEGER (Given)
+      }{
+         The number of elements along the first dimension of the POINTS
+         array (which contains the point coordinates). This value is
+         required so that the coordinate values can be correctly
+         located if they do not entirely fill this array. The value
+         given should not be less than NPNT.
+      }
+      \sstsubsection{
+         POINTS( DIM, 2 ) = DOUBLE PRECISION (Given)
+      }{
+         A 2-dimensional array giving the physical coordinates of the
+         vertices. These should be stored such that the value of coordinate
+         number COORD for point number PNT is found in element IN(PNT,COORD).
+      }
+      \sstsubsection{
+         UNC = INTEGER (Given)
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with the boundary of the Polygon being created.
+         The uncertainty in any point on the boundary of the Polygon is found by
+         shifting the supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at
+         the boundary point being considered. The area covered by the
+         shifted uncertainty Region then represents the uncertainty in the
+         boundary position. The uncertainty is assumed to be the same for
+         all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. \htmlref{Box}{Box}, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Polygon. Alternatively,
+         a null \htmlref{Object}{Object} pointer (AST\_\_NULL)
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the Polygon being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{AST\_OVERLAP}{AST\_OVERLAP}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Polygon. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_POLYGON = INTEGER
+      }{
+         A pointer to the new Polygon.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_POLYMAP
+}{
+   Create a PolyMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{PolyMap}{PolyMap} and optionally initialises
+      its attributes.
+
+      A PolyMap is a form of \htmlref{Mapping}{Mapping} which performs a general polynomial
+      transformation.  Each output coordinate is a polynomial function of
+      all the input coordinates. The coefficients are specified separately
+      for each output coordinate. The forward and inverse transformations
+      are defined independantly by separate sets of coefficients. If no
+      inverse transformation is supplied, an iterative method can be used
+      to evaluate the inverse based only on the forward transformation.
+   }
+   \sstinvocation{
+      RESULT = AST\_POLYMAP( NIN, NOUT, NCOEFF\_F, COEFF\_F, NCOEFF\_I, COEFF\_I,
+                            OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NIN = INTEGER (Given)
+      }{
+         The number of input coordinates.
+      }
+      \sstsubsection{
+         NOUT = INTEGER (Given)
+      }{
+         The number of output coordinates.
+      }
+      \sstsubsection{
+         NCOEFF\_F = INTEGER (Given)
+      }{
+         The number of non-zero coefficients necessary to define the
+         forward transformation of the PolyMap. If zero is supplied, the
+         forward transformation will be undefined.
+      }
+      \sstsubsection{
+         COEFF\_F( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing
+         \texttt{"} NCOEFF\_F$*$( 2 $+$ NIN )\texttt{"}  elements. Each group of \texttt{"} 2 $+$ NIN\texttt{"}
+         adjacent elements describe a single coefficient of the forward
+         transformation. Within each such group, the first element is the
+         coefficient value; the next element is the integer index of the
+         PolyMap output which uses the coefficient within its defining
+         polynomial (the first output has index 1); the remaining elements
+         of the group give the integer powers to use with each input
+         coordinate value (powers must not be negative, and floating
+         point values are rounded to the nearest integer).
+
+         For instance, if the PolyMap has 3 inputs and 2 outputs, each group
+         consisting of 5 elements, A groups such as \texttt{"} (1.2, 2.0, 1.0, 3.0, 0.0)\texttt{"}
+         describes a coefficient with value 1.2 which is used within the
+         definition of output 2. The output value is incremented by the
+         product of the coefficient value, the value of input coordinate
+         1 raised to the power 1, and the value of input coordinate 2 raised
+         to the power 3. Input coordinate 3 is not used since its power is
+         specified as zero. As another example, the group \texttt{"} (-1.0, 1.0,
+         0.0, 0.0, 0.0 )\texttt{"}  describes adds a constant value -1.0 onto
+         output 1 (it is a constant value since the power for every input
+         axis is given as zero).
+
+         Each final output coordinate value is the sum of the \texttt{"} NCOEFF\_F\texttt{"}  terms
+         described by the \texttt{"} NCOEFF\_F\texttt{"}  groups within the supplied array.
+      }
+      \sstsubsection{
+         NCOEFF\_I = INTEGER (Given)
+      }{
+         The number of non-zero coefficients necessary to define the
+         inverse transformation of the PolyMap. If zero is supplied, the
+         inverse transformation will be undefined.
+      }
+      \sstsubsection{
+         COEFF\_I( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing
+         \texttt{"} NCOEFF\_I$*$( 2 $+$ NOUT )\texttt{"}  elements. Each group of \texttt{"} 2 $+$ NOUT\texttt{"}
+         adjacent elements describe a single coefficient of the inverse
+         transformation, using the same schame as \texttt{"} COEFF\_F\texttt{"} ,
+         except that \texttt{"} inputs\texttt{"}  and \texttt{"} outputs\texttt{"}  are transposed.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new PolyMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_POLYMAP = INTEGER
+      }{
+         A pointer to the new PolyMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_POLYTRAN
+}{
+   Fit a PolyMap inverse or forward transformation
+}{
+   \sstdescription{
+      This function creates a new \htmlref{PolyMap}{PolyMap} which is a copy of the supplied
+      PolyMap, in which a specified transformation (forward or inverse)
+      has been replaced by a new polynomial transformation. The
+      coefficients of the new transformation are estimated by sampling
+      the other transformation and performing a least squares polynomial
+      fit in the opposite direction to the sampled positions and values.
+
+      This method can only be used on (1-input,1-output) or (2-input,2-output)
+      PolyMaps.
+
+      The transformation to create is specified by the
+      FORWARD parameter.
+      In what follows \texttt{"} X\texttt{"}  refers to the inputs of the PolyMap, and \texttt{"} Y\texttt{"}  to
+      the outputs of the PolyMap. The forward transformation transforms
+      input values (X) into output values (Y), and the inverse transformation
+      transforms output values (Y) into input values (X). Within a PolyMap,
+      each transformation is represented by an independent set of
+      polynomials, P\_f or P\_i: Y=P\_f(X) for the forward transformation and
+      X=P\_i(Y) for the inverse transformation.
+
+      The FORWARD
+      parameter specifies the transformation to be replaced. If it is
+      is .TRUE.,
+      a new forward transformation is created
+      by first finding the input values (X) using the inverse transformation
+      (which must be available) at a regular grid of points (Y) covering a
+      rectangular region of the PolyMap\texttt{'} s output space. The coefficients of
+      the required forward polynomial, Y=P\_f(X), are chosen in order to
+      minimise the sum of the squared residuals between the sampled values
+      of Y and P\_f(X).
+
+      If FORWARD is .FALSE. (probably the most likely case),
+      a new inverse transformation is created by
+      first finding the output values (Y) using the forward transformation
+      (which must be available) at a regular grid of points (X) covering a
+      rectangular region of the PolyMap\texttt{'} s input space. The coefficients of
+      the required inverse polynomial, X=P\_i(Y), are chosen in order to
+      minimise the sum of the squared residuals between the sampled values
+      of X and P\_i(Y).
+
+      This fitting process is performed repeatedly with increasing
+      polynomial orders (starting with linear) until the target
+      accuracy is achieved, or a specified maximum order is reached. If
+      the target accuracy cannot be achieved even with this maximum-order
+      polynomial, the best fitting maximum-order polynomial is returned so
+      long as its accuracy is better than
+      MAXACC.
+      If it is not, an error is reported.
+   }
+   \sstinvocation{
+      RESULT = AST\_POLYTRAN( THIS, FORWARD, ACC, MAXACC, MAXORDER, LBND,
+                             UBND, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the original \htmlref{Mapping}{Mapping}.
+      }
+      \sstsubsection{
+         FORWARD = LOGICAL (Given)
+      }{
+         If .TRUE.,
+         the forward PolyMap transformation is replaced. Otherwise the
+         inverse transformation is replaced.
+      }
+      \sstsubsection{
+         ACC = DOUBLE (Given)
+      }{
+         The target accuracy, expressed as a geodesic distance within
+         the PolyMap\texttt{'} s input space (if
+         FORWARD is .FALSE.) or output space (if FORWARD is .TRUE.).
+      }
+      \sstsubsection{
+         MAXACC = DOUBLE (Given)
+      }{
+         The maximum allowed accuracy for an acceptable polynomial,
+         expressed as a geodesic distance within the PolyMap\texttt{'} s input
+         space (if
+         FORWARD is .FALSE.) or output space (if FORWARD is .TRUE.).
+      }
+      \sstsubsection{
+         MAXORDER = INTEGER (Given)
+      }{
+         The maximum allowed polynomial order. This is one more than the
+         maximum power of either input axis. So for instance, a value of
+         3 refers to a quadratic polynomial. Note, cross terms with total
+         powers greater than or equal to
+         MAXORDER
+         are not inlcuded in the fit. So the maximum number of terms in
+         each of the fitted polynomials is
+         MAXORDER$*$(MAXORDER$+$1)/2.
+      }
+      \sstsubsection{
+         LBND( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An
+         array holding the lower bounds of a rectangular region within
+         the PolyMap\texttt{'} s input space (if
+         FORWARD is .FALSE.) or output space (if FORWARD is .TRUE.).
+         The new polynomial will be evaluated over this rectangle. The
+         length of this array should equal the value of the PolyMap\texttt{'} s \htmlref{Nin}{Nin}
+         or \htmlref{Nout}{Nout} attribute, depending on
+         FORWARD.
+      }
+      \sstsubsection{
+         UBND( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An
+         array holding the upper bounds of a rectangular region within
+         the PolyMap\texttt{'} s input space (if
+         FORWARD is .FALSE.) or output space (if FORWARD is .TRUE.).
+         The new polynomial will be evaluated over this rectangle.  The
+         length of this array should equal the value of the PolyMap\texttt{'} s Nin
+         or Nout attribute, depending on
+         FORWARD.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_POLYTRAN = INTEGER
+      }{
+         A pointer to the new PolyMap.
+         AST\_\_NULL
+         will be returned if the fit fails to achieve the accuracy
+         specified by
+         MAXACC,
+         but no error will be reported.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function can only be used on 1D or 2D PolyMaps which have
+         the same number of inputs and outputs.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_PRISM
+}{
+   Create a Prism
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Prism}{Prism} and optionally initialises
+      its attributes.
+
+      A Prism is a \htmlref{Region}{Region} which represents an extrusion of an existing Region
+      into one or more orthogonal dimensions (specified by another Region).
+      If the Region to be extruded has N axes, and the Region defining the
+      extrusion has M axes, then the resulting Prism will have (M$+$N) axes.
+      A point is inside the Prism if the first N axis values correspond to
+      a point inside the Region being extruded, and the remaining M axis
+      values correspond to a point inside the Region defining the extrusion.
+
+      As an example, a cylinder can be represented by extruding an existing
+      \htmlref{Circle}{Circle}, using an \htmlref{Interval}{Interval} to define the extrusion. Ih this case, the
+      Interval would have a single axis and would specify the upper and
+      lower limits of the cylinder along its length.
+   }
+   \sstinvocation{
+      RESULT = AST\_PRISM( REGION1, REGION2, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         REGION1 = INTEGER (Given)
+      }{
+         Pointer to the Region to be extruded.
+      }
+      \sstsubsection{
+         REGION2 = INTEGER (Given)
+      }{
+         Pointer to the Region defining the extent of the extrusion.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Prism. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_PRISM = INTEGER
+      }{
+         A pointer to the new Prism.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Deep copies are taken of the supplied Regions. This means that
+         any subsequent changes made to the component Regions using the
+         supplied pointers will have no effect on the Prism.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_PURGEROWS
+}{
+   Remove all empty rows from a table
+}{
+   \sstdescription{
+      This function removes all empty rows from the \htmlref{Table}{Table}, renaming
+      the key associated with each table cell accordingly.
+   }
+   \sstinvocation{
+      CALL AST\_PURGEROWS( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_PURGEWCS
+}{
+   Delete all cards in the FitsChan describing WCS information
+}{
+   \sstdescription{
+      This routine
+      deletes all cards in a \htmlref{FitsChan}{FitsChan} that relate to any of the recognised
+      WCS encodings. On exit, the current card is the first remaining card
+      in the FitsChan.
+   }
+   \sstinvocation{
+      CALL AST\_PURGEWCS( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_PUTCARDS
+}{
+   Store a set of FITS header cards in a FitsChan
+}{
+   \sstdescription{
+      This routine
+      stores a set of FITS header cards in a \htmlref{FitsChan}{FitsChan}. The cards are
+      supplied concatenated together into a single character string.
+      Any existing cards in the FitsChan are removed before the new cards
+      are added. The FitsChan is \texttt{"} re-wound\texttt{"}  on exit by clearing its \htmlref{Card}{Card}
+      attribute. This means that a subsequent invocation of
+      \htmlref{AST\_READ}{AST\_READ}
+      can be made immediately without the need to re-wind the FitsChan
+      first.
+   }
+   \sstinvocation{
+      CALL AST\_PUTCARDS( THIS, CARDS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         CARDS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string
+         containing the FITS cards to be stored. Each individual card
+         should occupy 80 characters in this string, and there should be
+         no delimiters, new lines, etc, between adjacent cards. The final
+         card may be less than 80 characters long.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error will result if the supplied string contains any cards
+         which cannot be interpreted.
+      }
+   }
+}
+\sstroutine{
+   AST\_PUTCOLUMNDATA
+}{
+   Store new data values for all rows of a column
+}{
+   \sstdescription{
+      This routine
+      copies data values from a supplied buffer into a named column. The
+      first element in the buffer becomes the first element in the first
+      row of the column. If the buffer does not completely fill the
+      column, then any trailing rows are filled with null values.
+   }
+   \sstinvocation{
+      CALL AST\_PUTCOLUMNDATA( THIS, COLUMN, CLEN, SIZE, COLDATA, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{FitsTable}{FitsTable}.
+      }
+      \sstsubsection{
+         COLUMN = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The character string holding the name of the column. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         CLEN = INTEGER (Given)
+      }{
+         If the column holds character strings, then this must be set to
+         the length of each fixed length string in the supplied array.
+         This is often determined by the appropriate TFORMn keyword in
+         the binary table header. The supplied value is ignored if the
+         column does not hold character data.
+      }
+      \sstsubsection{
+         SIZE = INTEGER (Given)
+      }{
+         The size of the
+         COLDATA
+         array, in bytes. This should be an integer multiple of the
+         number of bytes needed to hold the full vector value stored in a
+         single cell of the column. An error is reported if this is not
+         the case.
+      }
+      \sstsubsection{
+         COLDATA( $*$ ) = BYTE (Given)
+      }{
+         An
+         area of memory holding the data to copy into the column. The values
+         should be stored in row order. If the column holds non-scalar values,
+         the elements of each value should be stored in \texttt{"} Fortran\texttt{"}  order. No
+         data type conversion is performed.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_PUTFITS
+}{
+   Store a FITS header card in a FitsChan
+}{
+   \sstdescription{
+      This routine stores a FITS header card in a \htmlref{FitsChan}{FitsChan}. The card
+      is either inserted before the current card (identified by the
+      \htmlref{Card}{Card} attribute), or over-writes the current card, as required.
+   }
+   \sstinvocation{
+      CALL AST\_PUTFITS( THIS, CARD, OVERWRITE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         CARD = CHARACTER $*$ ( 80 ) (Given)
+      }{
+         A character string string containing the FITS card to be
+         stored. No more than 80 characters will be used from this
+         string.
+      }
+      \sstsubsection{
+         OVERWRITE = LOGICAL (Given)
+      }{
+         If this value is .FALSE., the new card is inserted in front of
+         the current card in the FitsChan (as identified by the
+         initial value of the Card attribute). If it is .TRUE., the
+         new card replaces the current card. In either case, the Card
+         attribute is then incremented by one so that it subsequently
+         identifies the card following the one stored.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the Card attribute initially points at the \texttt{"} end-of-file\texttt{"}
+         (i.e. exceeds the number of cards in the FitsChan), then the new
+         card is appended as the last card in the FitsChan.
+
+         \sstitem
+         An error will result if the supplied string cannot be interpreted
+         as a FITS header card.
+      }
+   }
+}
+\sstroutine{
+   AST\_PUTLINE
+}{
+   Store a text line read by a Channel source routine
+}{
+   \sstdescription{
+      This routine should only be used when implementing a routine
+      which will be passed as the SOURCE argument to \htmlref{AST\_CHANNEL}{AST\_CHANNEL}. It
+      should be used to pass back (to the AST library) each line of
+      text read from the external data source. One such line should be
+      passed back in this way for each invocation of the source
+      routine.
+   }
+   \sstinvocation{
+      CALL AST\_PUTLINE( LINE, L, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         LINE = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing the line of input text which
+         has been read.
+      }
+      \sstsubsection{
+         L = INTEGER (Given)
+      }{
+         The number of characters in the input line, which may be
+         zero. If there is no more input available (e.g. an end of
+         file has been reached), this value should be set negative and
+         this will terminate the read operation on the \htmlref{Channel}{Channel}.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine is only available in the Fortran interface to the
+         AST library.
+      }
+   }
+}
+\sstroutine{
+   AST\_PUTTABLE
+}{
+   Store a single FitsTable in a FitsChan
+}{
+   \sstdescription{
+      This routine
+      allows a representation of a single FITS binary table to be
+      stored in a \htmlref{FitsChan}{FitsChan}. For instance, this may provide the coordinate
+      look-up tables needed subequently when reading FITS-WCS headers
+      for axes described using the \texttt{"} -TAB\texttt{"}  algorithm. Since, in general,
+      the calling application may not know which tables will be needed -
+      if any - prior to calling
+      \htmlref{AST\_READ}{AST\_READ}, the \htmlref{AST\_TABLESOURCE}{AST\_TABLESOURCE} routine
+      provides an alternative mechanism in which a caller-supplied
+      function is invoked to store a named table in the FitsChan.
+   }
+   \sstinvocation{
+      CALL AST\_PUTTABLE( THIS, TABLE, EXTNAM, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         TABLE = INTEGER (Given)
+      }{
+         Pointer to a \htmlref{FitsTable}{FitsTable} to be added to the FitsChan. If a FitsTable
+         with the associated extension name already exists in the FitsChan,
+         it is replaced with the new one. A deep copy of the FitsTable is
+         stored in the FitsChan, so any subsequent changes made to the
+         FitsTable will have no effect on the behaviour of the FitsChan.
+      }
+      \sstsubsection{
+         EXTNAM = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The name of the FITS extension associated with the table.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Tables stored in the FitsChan may be retrieved using
+         \htmlref{AST\_GETTABLES}{AST\_GETTABLES}.
+
+         \sstitem
+         The \htmlref{AST\_PUTTABLES}{AST\_PUTTABLES} method can add multiple FitsTables in a single call.
+      }
+   }
+}
+\sstroutine{
+   AST\_PUTTABLEHEADER
+}{
+   Store new FITS headers in a FitsTable
+}{
+   \sstdescription{
+      This routine
+      stores new FITS headers in the supplied \htmlref{FitsTable}{FitsTable}. Any existing
+      headers are first deleted.
+   }
+   \sstinvocation{
+      CALL AST\_PUTTABLEHEADER( THIS, HEADER, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsTable.
+      }
+      \sstsubsection{
+         HEADER = INTEGER (Given)
+      }{
+         Pointer to a \htmlref{FitsChan}{FitsChan} holding the headers for the FitsTable.
+         A deep copy of the supplied FitsChan is stored in the FitsTable,
+         replacing the current FitsChan in the Fitstable. Keywords that
+         are fixed either by the properties of the \htmlref{Table}{Table}, or by the FITS
+         standard, are removed from the copy (see \texttt{"} Notes:\texttt{"}  below).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The attributes of the supplied FitsChan, together with any source
+         and sink functions associated with the FitsChan, are copied to the
+         FitsTable.
+
+         \sstitem
+         Values for the following keywords are generated automatically by
+         the FitsTable (any values for these keywords in the supplied
+         FitsChan will be ignored): \texttt{"} XTENSION\texttt{"} , \texttt{"} BITPIX\texttt{"} , \texttt{"} NAXIS\texttt{"} , \texttt{"} NAXIS1\texttt{"} ,
+         \texttt{"} NAXIS2\texttt{"} , \texttt{"} PCOUNT\texttt{"} , \texttt{"} GCOUNT\texttt{"} , \texttt{"} TFIELDS\texttt{"} , \texttt{"} TFORM\%d\texttt{"} , \texttt{"} TTYPE\%d\texttt{"} ,
+         \texttt{"} TNULL\%d\texttt{"} , \texttt{"} THEAP\texttt{"} , \texttt{"} TDIM\%d\texttt{"} .
+      }
+   }
+}
+\sstroutine{
+   AST\_PUTTABLES
+}{
+   Store one or more FitsTables in a FitsChan
+}{
+   \sstdescription{
+      This routine
+      allows representations of one or more FITS binary tables to be
+      stored in a \htmlref{FitsChan}{FitsChan}. For instance, these may provide the coordinate
+      look-up tables needed subequently when reading FITS-WCS headers
+      for axes described using the \texttt{"} -TAB\texttt{"}  algorithm. Since, in general,
+      the calling application may not know which tables will be needed -
+      if any - prior to calling
+      \htmlref{AST\_READ}{AST\_READ}, the \htmlref{AST\_TABLESOURCE}{AST\_TABLESOURCE} routine
+      provides an alternative mechanism in which a caller-supplied
+      function is invoked to store a named table in the FitsChan.
+   }
+   \sstinvocation{
+      CALL AST\_PUTTABLES( THIS, TABLES, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         TABLES = INTEGER (Given)
+      }{
+         Pointer to a \htmlref{KeyMap}{KeyMap} holding the tables that are to be added
+         to the FitsChan. Each entry should hold a scalar value which is a
+         pointer to a \htmlref{FitsTable}{FitsTable} to be added to the FitsChan. Any unusable
+         entries are ignored. The key associated with each entry should be
+         the name of the FITS binary extension from which the table was
+         read. If a FitsTable with the associated key already exists in the
+         FitsChan, it is replaced with the new one. A deep copy of each
+         usable FitsTable is stored in the FitsChan, so any subsequent
+         changes made to the FitsTables will have no effect on the
+         behaviour of the FitsChan.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Tables stored in the FitsChan may be retrieved using
+         \htmlref{AST\_GETTABLES}{AST\_GETTABLES}.
+
+         \sstitem
+         The tables in the supplied KeyMap are added to any tables already
+         in the FitsChan.
+
+         \sstitem
+         The \htmlref{AST\_PUTTABLE}{AST\_PUTTABLE}
+         method provides a simpler means of adding a single table to a FitsChan.
+      }
+   }
+}
+\sstroutine{
+   AST\_QUADAPPROX
+}{
+   Obtain a quadratic approximation to a 2D Mapping
+}{
+   \sstdescription{
+      This function returns the co-efficients of a quadratic fit to the
+      supplied \htmlref{Mapping}{Mapping} over the input area specified by
+      LBND and UBND.
+      The Mapping must have 2 inputs, but may have any number of outputs.
+      The i\texttt{'} th Mapping output is modelled as a quadratic function of the
+      2 inputs (x,y):
+
+      output\_i = a\_i\_0 $+$ a\_i\_1$*$x $+$ a\_i\_2$*$y $+$ a\_i\_3$*$x$*$y $+$ a\_i\_4$*$x$*$x $+$
+                 a\_i\_5$*$y$*$y
+
+      The FIT
+      array is returned holding the values of the co-efficients a\_0\_0,
+      a\_0\_1, etc.
+   }
+   \sstinvocation{
+      RESULT = AST\_QUADAPPROX( THIS, LBND, UBND, NX, NY, FIT, RMS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping.
+      }
+      \sstsubsection{
+         LBND( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array
+         containing the lower bounds of a box defined within the input
+         coordinate system of the Mapping. The number of elements in this
+         array should equal the value of the Mapping\texttt{'} s \htmlref{Nin}{Nin} attribute. This
+         box should specify the region over which the fit is to be
+         performed.
+      }
+      \sstsubsection{
+         UBND( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array
+         containing the upper bounds of the box specifying the region over
+         which the fit is to be performed.
+      }
+      \sstsubsection{
+         NX = INTEGER (Given)
+      }{
+         The number of points to place along the first Mapping input. The
+         first point is at
+         LBND( 1 ) and the last is at UBND( 1 ).
+         If a value less than three is supplied a value of three will be used.
+      }
+      \sstsubsection{
+         NY = INTEGER (Given)
+      }{
+         The number of points to place along the second Mapping input. The
+         first point is at
+         LBND( 2 ) and the last is at UBND( 2 ).
+         If a value less than three is supplied a value of three will be used.
+      }
+      \sstsubsection{
+         FIT( $*$ ) = DOUBLE PRECISION (Returned)
+      }{
+         An array
+         in which to return the co-efficients of the quadratic
+         approximation to the specified transformation. This array should
+         have at least \texttt{"} 6$*$\htmlref{Nout}{Nout}\texttt{"} , elements. The first 6 elements hold the
+         fit to the first Mapping output. The next 6 elements hold the
+         fit to the second Mapping output, etc. So if the Mapping has 2
+         inputs and 2 outputs the quadratic approximation to the forward
+         transformation is:
+
+            X\_out = fit(1) $+$ fit(2)$*$X\_in $+$ fit(3)$*$Y\_in $+$ fit(4)$*$X\_in$*$Y\_in $+$
+                    fit(5)$*$X\_in$*$X\_in $+$ fit(6)$*$Y\_in$*$Y\_in
+            Y\_out = fit(7) $+$ fit(8)$*$X\_in $+$ fit(9)$*$Y\_in $+$ fit(10)$*$X\_in$*$Y\_in $+$
+                    fit(11)$*$X\_in$*$X\_in $+$ fit(12)$*$Y\_in$*$Y\_in
+      }
+      \sstsubsection{
+         RMS = DOUBLE PRECISION (Returned)
+      }{
+         The
+         RMS residual between the fit and the Mapping, summed over all
+         Mapping outputs.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_QUADAPPROX = LOGICAL
+      }{
+         If a quadratic approximation was created,
+         .TRUE is returned. Otherwise .FALSE. is returned
+         and the fit co-efficients are set to AST\_\_BAD.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function fits the Mapping\texttt{'} s forward transformation. To fit
+         the inverse transformation, the Mapping should be inverted using
+         \htmlref{AST\_INVERT}{AST\_INVERT}
+         before invoking this function.
+
+         \sstitem
+         A value of .FALSE.
+         will be returned if this function is invoked
+         with the global error status set, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_RATE
+}{
+   Calculate the rate of change of a Mapping output
+}{
+   \sstdescription{
+      This routine
+      evaluates the rate of change of a specified output of the supplied
+      \htmlref{Mapping}{Mapping} with respect to a specified input, at a specified input
+      position.
+
+      The result is estimated by interpolating the function using a
+      fourth order polynomial in the neighbourhood of the specified
+      position. The size of the neighbourhood used is chosen to minimise
+      the RMS residual per unit length between the interpolating
+      polynomial and the supplied Mapping function. This method produces
+      good accuracy but can involve evaluating the Mapping 100 or more
+      times.
+   }
+   \sstinvocation{
+      RESULT = AST\_RATE( THIS, AT, AX1, AX2, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         AT( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An
+         array holding the axis values at the position at which the rate
+         of change is to be evaluated. The number of elements in this
+         array should equal the number of inputs to the Mapping.
+      }
+      \sstsubsection{
+         AX1 = INTEGER (Given)
+      }{
+         The index of the Mapping output for which the rate of change is to
+         be found (output numbering starts at 1 for the first output).
+      }
+      \sstsubsection{
+         AX2 = INTEGER (Given)
+      }{
+         The index of the Mapping input which is to be varied in order to
+         find the rate of change (input numbering starts at 1 for the first
+         input).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_RATE = DOUBLE PRECISION
+      }{
+         The rate of change of Mapping output AX1 with respect to input
+         AX2, evaluated at AT, or AST\_\_BAD if the value cannot be
+         calculated.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of AST\_\_BAD will be returned if this function is invoked
+         with the global error status set, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_RATEMAP
+}{
+   Create a RateMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{RateMap}{RateMap} and optionally initialises
+      its attributes.
+
+      A RateMap is a \htmlref{Mapping}{Mapping} which represents a single element of the
+      Jacobian matrix of another Mapping. The Mapping for which the
+      Jacobian is required is specified when the new RateMap is created,
+      and is referred to as the \texttt{"} encapsulated Mapping\texttt{"}  below.
+
+      The number of inputs to a RateMap is the same as the number of inputs
+      to its encapsulated Mapping. The number of outputs from a RateMap
+      is always one. This one output equals the rate of change of a
+      specified output of the encapsulated Mapping with respect to a
+      specified input of the encapsulated Mapping (the input and output
+      to use are specified when the RateMap is created).
+
+      A RateMap which has not been inverted does not define an inverse
+      transformation. If a RateMap has been inverted then it will define
+      an inverse transformation but not a forward transformation.
+   }
+   \sstinvocation{
+      RESULT = AST\_RATEMAP( MAP, AX1, AX2, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         MAP = INTEGER (Given)
+      }{
+         Pointer to the encapsulated Mapping.
+      }
+      \sstsubsection{
+         AX1 = INTEGER (Given)
+      }{
+         Index of the output from the encapsulated Mapping for which the
+         rate of change is required. This corresponds to the delta
+         quantity forming the numerator of the required element of the
+         Jacobian matrix. The first axis has index 1.
+      }
+      \sstsubsection{
+         AX2 = INTEGER (Given)
+      }{
+         Index of the input to the encapsulated Mapping which is to be
+         varied. This corresponds to the delta quantity forming the
+         denominator of the required element of the Jacobian matrix.
+         The first axis has index 1.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new RateMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_RATEMAP = INTEGER
+      }{
+         A pointer to the new RateMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The forward transformation of the encapsulated Mapping must be
+         defined.
+
+         \sstitem
+         Note that the component Mappings supplied are not copied by
+         AST\_RATEMAP (the new RateMap simply retains a reference to
+         them). They may continue to be used for other purposes, but
+         should not be deleted. If a RateMap containing a copy of its
+         component Mappings is required, then a copy of the RateMap should
+         be made using \htmlref{AST\_COPY}{AST\_COPY}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_READ
+}{
+   Read an Object from a Channel
+}{
+   \sstdescription{
+      This function reads the next \htmlref{Object}{Object} from a \htmlref{Channel}{Channel} and returns a
+      pointer to the new Object.
+   }
+   \sstinvocation{
+      RESULT = AST\_READ( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Channel.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         All successful use of AST\_READ on a FitsChan is destructive, so that
+         FITS header cards are consumed in the process of reading an Object,
+         and are removed from the FitsChan (this deletion can be prevented
+         for specific cards by calling the FitsChan
+         \htmlref{AST\_RETAINFITS}{AST\_RETAINFITS} routine).
+         An unsuccessful call of
+         AST\_READ
+         (for instance, caused by the FitsChan not containing the necessary
+         FITS headers cards needed to create an Object) results in the
+         contents of the FitsChan being left unchanged.
+      }
+      \sstsubsection{
+         \htmlref{StcsChan}{StcsChan}
+      }{
+         The AST Object returned by a successful use of
+         AST\_READ
+         on an StcsChan, will be either a \htmlref{Region}{Region} or a \htmlref{KeyMap}{KeyMap}, depending
+         on the values of the \htmlref{StcsArea}{StcsArea}, \htmlref{StcsCoords}{StcsCoords} and \htmlref{StcsProps}{StcsProps}
+         attributes. See the documentation for these attributes for further
+         information.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_READ = INTEGER
+      }{
+         A pointer to the new Object. The class to which this will
+         belong is determined by the input data, so is not known in
+         advance.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned, without
+         error, if the Channel contains no further Objects to be read.
+
+         \sstitem
+         A null Object pointer will also be returned if this function
+         is invoked with STATUS set to an error value, or if it should fail
+         for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_READFITS
+}{
+   Read cards into a FitsChan from the source function
+}{
+   \sstdescription{
+      This routine
+      reads cards from the source function that was specified when the
+      \htmlref{FitsChan}{FitsChan} was created, and stores them in the FitsChan. This
+      normally happens once-only, when the FitsChan is accessed for the
+      first time.
+      This routine
+      provides a means of forcing a re-read of the external source, and
+      may be useful if (say) new cards have been deposited into the
+      external source. Any newcards read from the source are appended to
+      the end of the current contents of the FitsChan.
+   }
+   \sstinvocation{
+      CALL AST\_READFITS( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function returns without action if no source function was
+         specified when the FitsChan was created.
+
+         \sstitem
+         The \htmlref{SourceFile}{SourceFile} attribute is ignored by this
+         routine.
+         New cards are read from the source file whenever a new value is
+         assigned to the SourceFile attribute.
+      }
+   }
+}
+\sstroutine{
+   AST\_REBIN$<$X$>$
+}{
+   Rebin a region of a data grid
+}{
+   \sstdescription{
+      This is a set of functions for rebinning gridded data (e.g. an
+      image) under the control of a geometrical transformation, which
+      is specified by a \htmlref{Mapping}{Mapping}.  The functions operate on a pair of
+      data grids (input and output), each of which may have any number
+      of dimensions. Rebinning may be restricted to a specified
+      region of the input grid. An associated grid of error estimates
+      associated with the input data may also be supplied (in the form
+      of variance values), so as to produce error estimates for the
+      rebined output data. Propagation of missing data (bad pixels)
+      is supported.
+
+      Note, if you will be rebining a sequence of input arrays and then
+      co-adding them into a single array, the alternative
+      \htmlref{AST\_REBINSEQ$<$X$>$}{AST\_REBINSEQ$<$X$>$} routines
+      will in general be more efficient.
+
+      You should use a rebinning function which matches the numerical
+      type of the data you are processing by replacing $<$X$>$ in
+      the generic function name AST\_REBIN$<$X$>$ by an appropriate 1- or
+      2-character type code. For example, if you are rebinning data
+      with type REAL, you should use the function AST\_REBINR (see
+      the \texttt{"} Data Type Codes\texttt{"}  section below for the codes appropriate to
+      other numerical types).
+
+      Rebinning of the grid of input data is performed by transforming
+      the coordinates of the centre of each input grid element (or pixel)
+      into the coordinate system of the output grid. The input pixel
+      value is then divided up and assigned to the output pixels in the
+      neighbourhood of the central output coordinates. A choice of
+      schemes are provided for determining how each input pixel value is
+      divided up between the output pixels. In general, each output pixel
+      may be assigned values from more than one input pixel. All
+      contributions to a given output pixel are summed to produce the
+      final output pixel value. Output pixels can be set to the supplied
+      bad value if they receive contributions from an insufficient number
+      of input pixels. This is controlled by the
+      WLIM argument.
+
+      Input pixel coordinates are transformed into the coordinate
+      system of the output grid using the forward transformation of the
+      Mapping which is supplied. This means that geometrical features
+      in the input data are subjected to the Mapping\texttt{'} s forward
+      transformation as they are transferred from the input to the
+      output grid.
+
+      In practice, transforming the coordinates of every pixel of a
+      large data grid can be time-consuming, especially if the Mapping
+      involves complicated functions, such as sky projections. To
+      improve performance, it is therefore possible to approximate
+      non-linear Mappings by a set of linear transformations which are
+      applied piece-wise to separate sub-regions of the data. This
+      approximation process is applied automatically by an adaptive
+      algorithm, under control of an accuracy criterion which
+      expresses the maximum tolerable geometrical distortion which may
+      be introduced, as a fraction of a pixel.
+
+      This algorithm first attempts to approximate the Mapping with a
+      linear transformation applied over the whole region of the
+      input grid which is being used. If this proves to be
+      insufficiently accurate, the input region is sub-divided into
+      two along its largest dimension and the process is repeated
+      within each of the resulting sub-regions. This process of
+      sub-division continues until a sufficiently good linear
+      approximation is found, or the region to which it is being
+      applied becomes too small (in which case the original Mapping is
+      used directly).
+   }
+   \sstinvocation{
+      CALL AST\_REBIN$<$X$>$( THIS, WLIM, NDIM\_IN, LBND\_IN, UBND\_IN, IN, IN\_VAR,
+                         SPREAD, PARAMS, FLAGS,
+                         TOL, MAXPIX, BADVAL,
+                         NDIM\_OUT, LBND\_OUT, UBND\_OUT,
+                         LBND, UBND, OUT, OUT\_VAR, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to a Mapping, whose forward transformation will be
+         used to transform the coordinates of pixels in the input
+         grid into the coordinate system of the output grid.
+
+         The number of input coordinates used by this Mapping (as
+         given by its \htmlref{Nin}{Nin} attribute) should match the number of input
+         grid dimensions given by the value of NDIM\_IN
+         below. Similarly, the number of output coordinates (\htmlref{Nout}{Nout}
+         attribute) should match the number of output grid dimensions
+         given by NDIM\_OUT.
+      }
+      \sstsubsection{
+         WLIM = DOUBLE PRECISION (Given)
+      }{
+         Gives the required number of input pixel values which must contribute
+         to an output pixel in order for the output pixel value to be
+         considered valid. If the sum of the input pixel weights contributing
+         to an output pixel is less than the supplied
+         WLIM
+         value, then the output pixel value is returned set to the
+         supplied bad value.
+      }
+      \sstsubsection{
+         NDIM\_IN = INTEGER (Given)
+      }{
+         The number of dimensions in the input grid. This should be at
+         least one.
+      }
+      \sstsubsection{
+         LBND\_IN( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         UBND\_IN( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that LBND\_IN and UBND\_IN together define the shape
+         and size of the input grid, its extent along a particular
+         (J\texttt{'} th) dimension being UBND\_IN(J)-LBND\_IN(J)$+$1. They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre.
+      }
+      \sstsubsection{
+         IN( $*$ ) = $<$Xtype$>$ (Given)
+      }{
+         An array, with one element for each pixel in the
+         input grid, containing the input data to be rebined.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using AST\_REBINR, the type of each array element
+         should be REAL).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. normal Fortran array storage order).
+      }
+      \sstsubsection{
+         IN\_VAR( $*$ ) = $<$Xtype$>$ (Given)
+      }{
+         An optional second array with the same size and type as the
+         IN array. If the AST\_\_USEVAR flag is set via the FLAGS
+         argument (below), this array should contain a set of
+         non-negative values which represent estimates of the
+         statistical variance associated with each element of the IN
+         array. Estimates of the variance of the rebined output data
+         will then be calculated.
+
+         If the AST\_\_USEVAR flag is not set, no input variance
+         estimates are required and this array will not be used. A
+         dummy (e.g. one-element) array may then be supplied.
+      }
+      \sstsubsection{
+         SPREAD = INTEGER (Given)
+      }{
+         This argument specifies the scheme to be used for dividing
+         each input data value up amongst the corresponding output pixels.
+         It may be used to select
+         from a set of pre-defined schemes by supplying one of the
+         values described in the \texttt{"} Pixel Spreading Schemes\texttt{"}
+         section below.  If a value of zero is supplied, then the
+         default linear spreading scheme is used (equivalent to
+         supplying the value AST\_\_LINEAR).
+      }
+      \sstsubsection{
+         PARAMS( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An optional array which should contain
+         any additional parameter values required by the pixel
+         spreading scheme. If such parameters are required, this
+         will be noted in the \texttt{"} Pixel Spreading Schemes\texttt{"}
+         section below.
+
+         If no additional parameters are required, this array is not
+         used. A dummy (e.g. one-element) array may then be supplied.
+      }
+      \sstsubsection{
+         FLAGS = INTEGER (Given)
+      }{
+         The sum of a set of flag values which may be used to
+         provide additional control over the rebinning operation. See
+         the \texttt{"} Control Flags\texttt{"}  section below for a description of the
+         options available.  If no flag values are to be set, a value
+         of zero should be given.
+      }
+      \sstsubsection{
+         TOL = DOUBLE PRECISION (Given)
+      }{
+         The maximum tolerable geometrical distortion which may be
+         introduced as a result of approximating non-linear Mappings
+         by a set of piece-wise linear transformations. This should be
+         expressed as a displacement in pixels in the output grid\texttt{'} s
+         coordinate system.
+
+         If piece-wise linear approximation is not required, a value
+         of zero may be given. This will ensure that the Mapping is
+         used without any approximation, but may increase execution
+         time.
+
+         If the value is too high, discontinuities between the linear
+         approximations used in adjacent panel will be higher, and may
+         cause the edges of the panel to be visible when viewing the output
+         image at high contrast. If this is a problem, reduce the
+         tolerance value used.
+      }
+      \sstsubsection{
+         MAXPIX = INTEGER (Given)
+      }{
+         A value which specifies an initial scale size (in pixels) for
+         the adaptive algorithm which approximates non-linear Mappings
+         with piece-wise linear transformations. Normally, this should
+         be a large value (larger than any dimension of the region of
+         the input grid being used). In this case, a first attempt to
+         approximate the Mapping by a linear transformation will be
+         made over the entire input region.
+
+         If a smaller value is used, the input region will first be
+         divided into sub-regions whose size does not exceed MAXPIX
+         pixels in any dimension. Only at this point will attempts at
+         approximation commence.
+
+         This value may occasionally be useful in preventing false
+         convergence of the adaptive algorithm in cases where the
+         Mapping appears approximately linear on large scales, but has
+         irregularities (e.g. holes) on smaller scales. A value of,
+         say, 50 to 100 pixels can also be employed as a safeguard in
+         general-purpose software, since the effect on performance is
+         minimal.
+
+         If too small a value is given, it will have the effect of
+         inhibiting linear approximation altogether (equivalent to
+         setting TOL to zero). Although this may degrade
+         performance, accurate results will still be obtained.
+      }
+      \sstsubsection{
+         BADVAL = $<$Xtype$>$ (Given)
+      }{
+         This argument should have the same type as the elements of
+         the IN array. It specifies the value used to flag missing
+         data (bad pixels) in the input and output arrays.
+
+         If the AST\_\_USEBAD flag is set via the FLAGS argument,
+         then this value is used to test for bad pixels in the IN
+         (and IN\_VAR) array(s).
+
+         In all cases, this value is also used to flag any output
+         elements in the OUT (and OUT\_VAR) array(s) for which
+         rebined values could not be obtained (see the \texttt{"} Propagation
+         of Missing Data\texttt{"}  section below for details of the
+         circumstances under which this may occur).
+      }
+      \sstsubsection{
+         NDIM\_OUT = INTEGER (Given)
+      }{
+         The number of dimensions in the output grid. This should be
+         at least one. It need not necessarily be equal to the number
+         of dimensions in the input grid.
+      }
+      \sstsubsection{
+         LBND\_OUT( NDIM\_OUT ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the first pixel
+         in the output grid along each dimension.
+      }
+      \sstsubsection{
+         UBND\_OUT( NDIM\_OUT ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the last pixel in
+         the output grid along each dimension.
+
+         Note that LBND\_OUT and UBND\_OUT together define the
+         shape, size and coordinate system of the output grid in the
+         same way as LBND\_IN and UBND\_IN define the shape, size
+         and coordinate system of the input grid.
+      }
+      \sstsubsection{
+         LBND( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the first pixel in the region
+         of the input grid which is to be included in the rebined output
+         array.
+      }
+      \sstsubsection{
+         UBND( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the last pixel in the region of
+         the input grid which is to be included in the rebined output
+         array.
+
+         Note that LBND and UBND together define the shape and
+         position of a (hyper-)rectangular region of the input grid
+         which is to be included in the rebined output array. This region
+         should lie wholly within the extent of the input grid (as
+         defined by the LBND\_IN and UBND\_IN arrays). Regions of
+         the input grid lying outside this region will not be used.
+      }
+      \sstsubsection{
+         OUT( $*$ ) = $<$Xtype$>$ (Returned)
+      }{
+         An array, with one element for each pixel in the
+         output grid, in which the rebined data values will be
+         returned. The numerical type of this array should match that
+         of the IN array, and the data storage order should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. normal Fortran array storage order).
+      }
+      \sstsubsection{
+         OUT\_VAR( $*$ ) = $<$Xtype$>$ (Returned)
+      }{
+         An optional array with the same type and size as the OUT
+         array. If the AST\_\_USEVAR flag is set via the FLAGS argument,
+         this array will be used to return variance estimates for the
+         rebined data values.
+
+         The output variance values will be calculated on the
+         assumption that errors on the input data values are
+         statistically independent and that their variance estimates
+         may simply be summed (with appropriate weighting factors)
+         when several input pixels contribute to an output data
+         value. If this assumption is not valid, then the output error
+         estimates may be biased. In addition, note that the
+         statistical errors on neighbouring output data values (as
+         well as the estimates of those errors) may often be
+         correlated, even if the above assumption about the input data
+         is correct, because of the pixel spreading schemes
+         employed.
+
+         If the AST\_\_USEVAR flag is not set, no output variance
+         estimates will be calculated and this array will not be
+         used. A dummy (e.g. one-element) array may then be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate rebinning function, you should
+      replace $<$X$>$ in the generic function name AST\_REBIN$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         B: BYTE (treated as signed)
+
+         \sstitem
+         UB: BYTE (treated as unsigned)
+
+      }
+      For example, AST\_REBIND would be used to process DOUBLE
+      PRECISION data, while AST\_REBINI would be used to process
+      integer data (stored in an INTEGER array), etc.
+
+      Note that, unlike
+      \htmlref{AST\_RESAMPLE$<$X$>$}{AST\_RESAMPLE$<$X$>$}, the AST\_REBIN$<$X$>$
+      set of functions does not yet support unsigned integer data types
+      or integers of different sizes.
+   }
+   \sstdiytopic{
+      Pixel Spreading Schemes
+   }{
+      The pixel spreading scheme specifies the Point Spread Function (PSF)
+      applied to each input pixel value as it is copied into the output
+      array. It can be thought of as the inverse of the sub-pixel
+      interpolation schemes used by the
+      AST\_RESAMPLE$<$X$>$
+      group of functions. That is, in a sub-pixel interpolation scheme the
+      kernel specifies the weight to assign to each input pixel when
+      forming the weighted mean of the input pixels, whereas the kernel in a
+      pixel spreading scheme specifies the fraction of the input data value
+      which is to be assigned to each output pixel. As for interpolation, the
+      choice of suitable pixel spreading scheme involves stricking a balance
+      between schemes which tend to degrade sharp features in the data by
+      smoothing them, and those which attempt to preserve sharp features but
+      which often tend to introduce unwanted artifacts. See the
+      AST\_RESAMPLE$<$X$>$
+      documentation for further discussion.
+
+      The binning algorithm used has the ability to introduce artifacts
+      not seen when using a resampling algorithm. Particularly, when
+      viewing the output image at high contrast, systems of curves lines
+      covering the entire image may be visible. These are caused by a
+      beating effect between the input pixel positions and the output pixels
+      position, and their nature and strength depend critically upon the
+      nature of the Mapping and the spreading function being used. In
+      general, the nearest neighbour spreading function demonstrates this
+      effect more clearly than the other functions, and for this reason
+      should be used with caution.
+
+      The following values (defined in the
+      AST\_PAR include file)
+      may be assigned to the
+      SPREAD
+      parameter. See the
+      AST\_RESAMPLE$<$X$>$
+      documentation for details of these schemes including the use of the
+      FSPREAD and PARAMS arguments:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_NEAREST
+
+         \sstitem
+         AST\_\_LINEAR
+
+         \sstitem
+         AST\_\_SINC
+
+         \sstitem
+         AST\_\_SINCSINC
+
+         \sstitem
+         AST\_\_SINCCOS
+
+         \sstitem
+         AST\_\_SINCGAUSS
+
+         \sstitem
+         AST\_\_SOMBCOS
+
+      }
+      In addition, the following schemes can be used with
+      AST\_REBIN$<$X$>$ but not with AST\_RESAMPLE$<$X$>$:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_GAUSS: This scheme uses a kernel of the form exp(-k$*$x$*$x), with k
+         a positive constant determined by the full-width at half-maximum (FWHM).
+         The FWHM should be supplied in units of output pixels by means of the
+         PARAMS(2)
+         value and should be at least 0.1. The
+         PARAMS(1)
+         value should be used to specify at what point the Gaussian is truncated
+         to zero. This should be given as a number of output pixels on either
+         side of the central output point in each dimension (the nearest integer
+         value is used).
+      }
+   }
+   \sstdiytopic{
+      Control Flags
+   }{
+      The following flags are defined in the AST\_PAR include file and
+      may be used to provide additional control over the rebinning
+      process. Having selected a set of flags, you should supply the
+      sum of their values via the FLAGS argument:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_USEBAD: Indicates that there may be bad pixels in the
+         input array(s) which must be recognised by comparing with the
+         value given for BADVAL and propagated to the output array(s).
+         If this flag is not set, all input values are treated literally
+         and the BADVAL value is only used for flagging output array
+         values.
+
+         \sstitem
+         AST\_\_USEVAR: Indicates that variance information should be
+         processed in order to provide estimates of the statistical error
+         associated with the rebined values. If this flag is not set,
+         no variance processing will occur and the IN\_VAR and OUT\_VAR
+         arrays will not be used. (Note that this flag is only available
+         in the Fortran interface to AST.)
+      }
+   }
+   \sstdiytopic{
+      Propagation of Missing Data
+   }{
+      Instances of missing data (bad pixels) in the output grid are
+      identified by occurrences of the BADVAL value in the OUT
+      array. These are produced if the sum of the weights of the
+      contributing input pixels is less than
+      WLIM.
+
+      An input pixel is considered bad (and is consequently ignored) if
+      its
+      data value is equal to BADVAL and the AST\_\_USEBAD flag is
+      set via the FLAGS argument.
+
+      In addition, associated output variance estimates (if
+      calculated) may be declared bad and flagged with the BADVAL
+      value in the OUT\_VAR array for similar reasons.
+   }
+}
+\sstroutine{
+   AST\_REBINSEQ$<$X$>$
+}{
+   Rebin a region of a sequence of data grids
+}{
+   \sstdescription{
+      This set of
+      routines is identical to \htmlref{AST\_REBIN$<$X$>$}{AST\_REBIN$<$X$>$}
+      except that the rebinned input data is added into the supplied
+      output arrays, rather than simply over-writing the contents of the
+      output arrays. Thus, by calling this
+      routine
+      repeatedly, a sequence of input arrays can be rebinned and accumulated
+      into a single output array, effectively forming a mosaic of the
+      input data arrays.
+
+      In addition, the weights associated with each output pixel are
+      returned. The weight of an output pixel indicates the number of input
+      pixels which have been accumulated in that output pixel. If the entire
+      value of an input pixel is assigned to a single output pixel, then the
+      weight of that output pixel is incremented by one. If some fraction of
+      the value of an input pixel is assigned to an output pixel, then the
+      weight of that output pixel is incremented by the fraction used.
+
+      The start of a new sequence is indicated by specifying the
+      AST\_\_REBININIT flag via the
+      FLAGS argument.
+      This causes the supplied arrays to be filled with zeros before the
+      rebinned input data is added into them. Subsequenct invocations
+      within the same sequence should omit the AST\_\_REBININIT flag.
+
+      The last call in a sequence is indicated by specifying the
+      AST\_\_REBINEND flag. Depending on which flags are supplied, this may
+      cause the output data and variance arrays to be normalised before
+      being returned. This normalisation consists of dividing the data
+      array by the weights array, and can eliminate artifacts which may be
+      introduced into the rebinned data as a consequence of aliasing
+      between the input and output grids. This results in each output
+      pixel value being the weighted mean of the input pixel values that
+      fall in the neighbourhood of the output pixel (rather like
+      \htmlref{AST\_RESAMPLE$<$X$>$}{AST\_RESAMPLE$<$X$>$}).
+      Optionally, these normalised
+      values can then be multiplied by a scaling factor to ensure that the
+      total data sum in any small area is unchanged. This scaling factor
+      is equivalent to the number of input pixel values that fall into each
+      output pixel. In addition to
+      normalisation of the output data values, any output variances are
+      also appropriately normalised, and any output data values with
+      weight less than
+      WLIM are set to BADVAL.
+
+      Output variances can be generated in two ways; by rebinning the supplied
+      input variances with appropriate weights, or by finding the spread of
+      input data values contributing to each output pixel (see the AST\_\_GENVAR
+      and AST\_\_USEVAR flags).
+   }
+   \sstinvocation{
+      CALL AST\_REBINSEQ$<$X$>$( THIS, WLIM, NDIM\_IN, LBND\_IN, UBND\_IN, IN, IN\_VAR,
+                            SPREAD, PARAMS, FLAGS, TOL, MAXPIX, BADVAL,
+                            NDIM\_OUT, LBND\_OUT, UBND\_OUT, LBND, UBND, OUT,
+                            OUT\_VAR, WEIGHTS, NUSED, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to a \htmlref{Mapping}{Mapping}, whose forward transformation will be
+         used to transform the coordinates of pixels in the input
+         grid into the coordinate system of the output grid.
+
+         The number of input coordinates used by this Mapping (as
+         given by its \htmlref{Nin}{Nin} attribute) should match the number of input
+         grid dimensions given by the value of NDIM\_IN
+         below. Similarly, the number of output coordinates (\htmlref{Nout}{Nout}
+         attribute) should match the number of output grid dimensions
+         given by NDIM\_OUT.
+      }
+      \sstsubsection{
+         WLIM = DOUBLE PRECISION (Given)
+      }{
+         This value is only used if the AST\_\_REBINEND flag is specified
+         via the
+         FLAGS argument.
+         It gives the required number of input pixel values which must
+         contribute to an output pixel (i.e. the output pixel weight) in
+         order for the output pixel value to be considered valid. If the sum
+         of the input pixel weights contributing to an output pixel is less
+         than the supplied
+         WLIM
+         value, then the output pixel value is returned set to the
+         supplied bad value. If the supplied value is less than 1.0E-10
+         then 1.0E-10 is used instead.
+      }
+      \sstsubsection{
+         NDIM\_IN = INTEGER (Given)
+      }{
+         The number of dimensions in the input grid. This should be at
+         least one.
+      }
+      \sstsubsection{
+         LBND\_IN( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         UBND\_IN( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that LBND\_IN and UBND\_IN together define the shape
+         and size of the input grid, its extent along a particular
+         (J\texttt{'} th) dimension being UBND\_IN(J)-LBND\_IN(J)$+$1. They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre.
+      }
+      \sstsubsection{
+         IN( $*$ ) = $<$Xtype$>$ (Given)
+      }{
+         An array, with one element for each pixel in the
+         input grid, containing the input data to be rebined.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using AST\_REBINSEQR, the type of each array element
+         should be REAL).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. normal Fortran array storage order).
+      }
+      \sstsubsection{
+         IN\_VAR( $*$ ) = $<$Xtype$>$ (Given)
+      }{
+         An optional
+         second array with the same size and type as the
+         IN
+         array. If given, this should contain a set of non-negative values
+         which represent estimates of the statistical variance associated
+         with each element of the
+         IN
+         array.
+         If neither the AST\_\_USEVAR nor the AST\_\_VARWGT flag is set, no
+         input variance estimates are required and this
+         array
+         will not be used.
+         A dummy (e.g. one-element) array
+         may then be supplied.
+      }
+      \sstsubsection{
+         SPREAD = INTEGER (Given)
+      }{
+         This argument specifies the scheme to be used for dividing
+         each input data value up amongst the corresponding output pixels.
+         It may be used to select
+         from a set of pre-defined schemes by supplying one of the
+         values described in the \texttt{"} Pixel Spreading Schemes\texttt{"}
+         section in the description of the
+         AST\_REBIN$<$X$>$ routines.
+         If a value of zero is supplied, then the default linear spreading
+         scheme is used (equivalent to supplying the value AST\_\_LINEAR).
+      }
+      \sstsubsection{
+         PARAMS( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An optional array which should contain
+         any additional parameter values required by the pixel
+         spreading scheme. If such parameters are required, this
+         will be noted in the \texttt{"} Pixel Spreading Schemes\texttt{"}  section in the
+         description of the
+         AST\_REBIN$<$X$>$ routines.
+
+         If no additional parameters are required, this array is not
+         used. A dummy (e.g. one-element) array may then be supplied.
+      }
+      \sstsubsection{
+         FLAGS = INTEGER (Given)
+      }{
+         The sum of a set of flag values which may be used to
+         provide additional control over the rebinning operation. See
+         the \texttt{"} Control Flags\texttt{"}  section below for a description of the
+         options available.  If no flag values are to be set, a value
+         of zero should be given.
+      }
+      \sstsubsection{
+         TOL = DOUBLE PRECISION (Given)
+      }{
+         The maximum tolerable geometrical distortion which may be
+         introduced as a result of approximating non-linear Mappings
+         by a set of piece-wise linear transformations. This should be
+         expressed as a displacement in pixels in the output grid\texttt{'} s
+         coordinate system.
+
+         If piece-wise linear approximation is not required, a value
+         of zero may be given. This will ensure that the Mapping is
+         used without any approximation, but may increase execution
+         time.
+
+         If the value is too high, discontinuities between the linear
+         approximations used in adjacent panel will be higher, and may
+         cause the edges of the panel to be visible when viewing the output
+         image at high contrast. If this is a problem, reduce the
+         tolerance value used.
+      }
+      \sstsubsection{
+         MAXPIX = INTEGER (Given)
+      }{
+         A value which specifies an initial scale size (in pixels) for
+         the adaptive algorithm which approximates non-linear Mappings
+         with piece-wise linear transformations. Normally, this should
+         be a large value (larger than any dimension of the region of
+         the input grid being used). In this case, a first attempt to
+         approximate the Mapping by a linear transformation will be
+         made over the entire input region.
+
+         If a smaller value is used, the input region will first be
+         divided into sub-regions whose size does not exceed MAXPIX
+         pixels in any dimension. Only at this point will attempts at
+         approximation commence.
+
+         This value may occasionally be useful in preventing false
+         convergence of the adaptive algorithm in cases where the
+         Mapping appears approximately linear on large scales, but has
+         irregularities (e.g. holes) on smaller scales. A value of,
+         say, 50 to 100 pixels can also be employed as a safeguard in
+         general-purpose software, since the effect on performance is
+         minimal.
+
+         If too small a value is given, it will have the effect of
+         inhibiting linear approximation altogether (equivalent to
+         setting TOL to zero). Although this may degrade
+         performance, accurate results will still be obtained.
+      }
+      \sstsubsection{
+         BADVAL = $<$Xtype$>$ (Given)
+      }{
+         This argument should have the same type as the elements of
+         the IN array. It specifies the value used to flag missing
+         data (bad pixels) in the input and output arrays.
+
+         If the AST\_\_USEBAD flag is set via the FLAGS argument,
+         then this value is used to test for bad pixels in the IN
+         (and IN\_VAR) array(s).
+
+         In all cases, this value is also used to flag any output
+         elements in the OUT (and OUT\_VAR) array(s) for which
+         rebined values could not be obtained (see the \texttt{"} Propagation
+         of Missing Data\texttt{"}  section below for details of the
+         circumstances under which this may occur).
+      }
+      \sstsubsection{
+         NDIM\_OUT = INTEGER (Given)
+      }{
+         The number of dimensions in the output grid. This should be
+         at least one. It need not necessarily be equal to the number
+         of dimensions in the input grid.
+      }
+      \sstsubsection{
+         LBND\_OUT( NDIM\_OUT ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the first pixel
+         in the output grid along each dimension.
+      }
+      \sstsubsection{
+         UBND\_OUT( NDIM\_OUT ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the last pixel in
+         the output grid along each dimension.
+
+         Note that LBND\_OUT and UBND\_OUT together define the
+         shape, size and coordinate system of the output grid in the
+         same way as LBND\_IN and UBND\_IN define the shape, size
+         and coordinate system of the input grid.
+      }
+      \sstsubsection{
+         LBND( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the first pixel in the region
+         of the input grid which is to be included in the rebined output
+         array.
+      }
+      \sstsubsection{
+         UBND( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the last pixel in the region of
+         the input grid which is to be included in the rebined output
+         array.
+
+         Note that LBND and UBND together define the shape and
+         position of a (hyper-)rectangular region of the input grid
+         which is to be included in the rebined output array. This region
+         should lie wholly within the extent of the input grid (as
+         defined by the LBND\_IN and UBND\_IN arrays). Regions of
+         the input grid lying outside this region will not be used.
+      }
+      \sstsubsection{
+         OUT( $*$ ) = $<$Xtype$>$ (Given and Returned)
+      }{
+         An array, with one element for each pixel in the
+         output grid. The rebined data values will be added into the
+         original contents of this array. The numerical type of this array
+         should match that of the
+         IN array, and the data storage order should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. normal Fortran array storage order).
+      }
+      \sstsubsection{
+         OUT\_VAR( $*$ ) = $<$Xtype$>$ (Given and Returned)
+      }{
+         A
+         array with the same type and size as the
+         OUT
+         array. This
+         array
+         will only be used if the AST\_\_USEVAR or AST\_\_GENVAR flag is set
+         via the FLAGS argument,
+         via the \texttt{"} flags\texttt{"}  parameter,
+         in which case variance estimates for the rebined data values will
+         be added into the array. If neither the AST\_\_USEVAR flag nor the
+         AST\_\_GENVAR flag is set, no output variance estimates will be
+         calculated and this
+         array
+         will not be used. A
+         dummy (e.g. one-element) array
+         may then be supplied.
+      }
+      \sstsubsection{
+         WEIGHTS( $*$ ) = DOUBLE PRECISION (Given and Returned)
+      }{
+         An array
+         with one or two elements for each pixel in the output grid,
+         depending on whether or not the AST\_\_GENVAR flag has been supplied
+         via the
+         FLAGS parameter.
+         If AST\_\_GENVAR has not been specified then the array should have
+         one element for each output pixel, and it will be used to
+         accumulate the weight associated with each output pixel.
+         If AST\_\_GENVAR has been specified then the array should have
+         two elements for each output pixel. The first half of the array
+         is again used to accumulate the weight associated with each output
+         pixel, and the second half is used to accumulate the square of
+         the weights. In each half, the data storage order should be such that
+         the index of the first grid dimension varies most rapidly and that of
+         the final dimension least rapidly
+         (i.e. normal Fortran array storage order).
+      }
+      \sstsubsection{
+         NUSED = INTEGER$*$8 (Given and Returned)
+      }{
+         The
+         number of input data values that have been added into the output
+         array so far. The supplied value is incremented on exit by the
+         number of input values used. The value is initially set to zero
+         if the AST\_\_REBININIT flag is set in
+         FLAGS.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate rebinning function, you should
+      replace $<$X$>$ in the generic function name AST\_REBINSEQ$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         B: BYTE (treated as signed)
+
+         \sstitem
+         UB: BYTE (treated as unsigned)
+
+      }
+      For example, AST\_REBIND would be used to process DOUBLE
+      PRECISION data, while AST\_REBINI would be used to process
+      integer data (stored in an INTEGER array), etc.
+
+      Note that, unlike
+      AST\_RESAMPLE$<$X$>$, the AST\_REBINSEQ$<$X$>$
+      set of functions does not yet support unsigned integer data types
+      or integers of different sizes.
+   }
+   \sstdiytopic{
+      Control Flags
+   }{
+      The following flags are defined in the AST\_PAR include file and
+      may be used to provide additional control over the rebinning
+      process. Having selected a set of flags, you should supply the
+      sum of their values via the FLAGS argument:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_REBININIT: Used to mark the first call in a sequence. It indicates
+         that the supplied
+         OUT, OUT\_VAR and WEIGHTS
+         arrays should be filled with zeros (thus over-writing any supplied
+         values) before adding the rebinned input data into them. This flag
+         should be used when rebinning the first input array in a sequence.
+
+         \sstitem
+         AST\_\_REBINEND: Used to mark the last call in a sequence. It causes
+         each value in the
+         OUT and OUT\_VAR
+         arrays to be divided by a normalisation factor before being
+         returned. The normalisation factor for each output data value is just
+         the corresponding value from the weights array. The normalisation
+         factor for each output variance value is the square of the data value
+         normalisation factor (see also AST\_\_CONSERVEFLUX). It also causes
+         output data values to be set bad if the corresponding weight is less
+         than the value supplied for
+         argument WLIM.
+         It also causes any temporary values stored in the output variance array
+         (see flag AST\_\_GENVAR below) to be converted into usable variance values.
+         Note, this flag is ignored if the AST\_\_NONORM flag is set.
+
+         \sstitem
+         AST\_\_USEBAD: Indicates that there may be bad pixels in the
+         input array(s) which must be recognised by comparing with the
+         value given for BADVAL and propagated to the output array(s).
+         If this flag is not set, all input values are treated literally
+         and the BADVAL value is only used for flagging output array
+         values.
+
+         \sstitem
+         AST\_\_USEVAR: Indicates that output variance estimates should be
+         created by rebinning the supplied input variance estimates. An
+         error will be reported if both this flag and the AST\_\_GENVAR flag
+         are supplied.
+
+         \sstitem
+         AST\_\_GENVAR: Indicates that output variance estimates should be
+         created based on the spread of input data values contributing to each
+         output pixel. An error will be reported if both this flag and the
+         AST\_\_USEVAR flag are supplied. If the AST\_\_GENVAR flag is specified,
+         the supplied output variance array is first used as a work array to
+         accumulate the temporary values needed to generate the output
+         variances. When the sequence ends (as indicated by the
+         AST\_\_REBINEND flag), the contents of the output variance array are
+         converted into the required variance estimates. If the generation of
+         such output variances is required, this flag should be used on every
+         invocation of this
+         routine
+         within a sequence, and any supplied input variances will have no effect
+         on the output variances (although input variances will still be used
+         to weight the input data if the AST\_\_VARWGT flag is also supplied).
+         The statistical meaning of these output varianes is determined by
+         the presence or absence of the AST\_\_DISVAR flag (see below).
+
+         \sstitem
+         AST\_\_DISVAR: This flag is ignored unless the AST\_\_GENVAR flag
+         has also been specified. It determines the statistical meaning of
+         the generated output variances. If AST\_\_DISVAR is not specified,
+         generated variances represent variances on the output mean  values. If
+         AST\_\_DISVAR is specified, the generated variances represent the variance
+         of the distribution from which the input values were taken. Each output
+         variance created with AST\_\_DISVAR will be larger than that created
+         without AST\_\_DISVAR by a factor equal to the number of input samples
+         that contribute to the output sample.
+
+         \sstitem
+         AST\_\_VARWGT: Indicates that the input data should be weighted by
+         the reciprocal of the input variances. Otherwise, all input data are
+         given equal weight. If this flag is specified, the calculation of the
+         output variances (if any) is modified to take account of the
+         varying weights assigned to the input data values.
+
+         \sstitem
+         AST\_\_NONORM: If the simple unnormalised sum of all input data falling
+         in each output pixel is required, then this flag should be set on
+         each call in the sequence and the AST\_\_REBINEND should not be used
+         on the last call. In this case
+         WEIGHTS and NUSED are ignored.
+         This flag cannot be used with the AST\_\_CONSERVEFLUX, AST\_\_GENVAR
+         or AST\_\_VARWGT flag.
+
+         \sstitem
+         AST\_\_CONSERVEFLUX: Indicates that the normalized output pixel values
+         generated by the AST\_\_REBINEND flag should be scaled in such a way as
+         to preserve the total data value in a feature on the sky. Without this
+         flag, each normalised output pixel value represents a weighted mean
+         of the input data values around the corresponding input position.
+         (i.e. AST\_REBINSEQ$<$F$>$ behaves similarly to AST\_RESAMPLE$<$X$>$). This
+         (i.e. AST\_REBINSEQ$<$F$>$ behaves similarly to AST\_RESAMPLE$<$X$>$). This
+         is appropriate if the input data represents the spatial density of
+         some quantity (e.g. surface brightness in Janskys per square
+         arc-second) because the output pixel values will have the same
+         normalisation and units as the input pixel values. However, if the
+         input data values represent flux (or some other physical quantity)
+         per pixel, then the AST\_\_CONSERVEFLUX flag could be of use. It causes
+         each output pixel value to be scaled by the ratio of the output pixel
+         size to the input pixel size.
+
+      }
+      This flag can only be used if the Mapping is successfully approximated
+      by one or more linear transformations. Thus an error will be reported
+      if it used when the
+      TOL argument
+      is set to zero (which stops the use of linear approximations), or
+      if the Mapping is too non-linear to be approximated by a piece-wise
+      linear transformation. The ratio of output to input pixel size is
+      evaluated once for each panel of the piece-wise linear approximation to
+      the Mapping, and is assumed to be constant for all output pixels in the
+      panel. The scaling factors for adjacent panels will in general
+      differ slightly, and so the joints between panels may be visible when
+      viewing the output image at high contrast. If this is a problem,
+      reduce the value of the
+      TOL argument
+      until the difference between adjacent panels is sufficiently small
+      to be insignificant.
+
+      This flag should normally be supplied on each invocation of
+      AST\_REBINSEQ$<$X$>$
+      within a given sequence.
+
+      Note, this flag cannot be used in conjunction with the AST\_\_NOSCALE
+      flag (an error will be reported if both flags are specified).
+   }
+   \sstdiytopic{
+      Propagation of Missing Data
+   }{
+      Instances of missing data (bad pixels) in the output grid are
+      identified by occurrences of the BADVAL value in the OUT
+      array. These are only produced if the AST\_\_REBINEND flag is
+      specified and a pixel has zero weight.
+
+      An input pixel is considered bad (and is consequently ignored) if
+      its
+      data value is equal to BADVAL and the AST\_\_USEBAD flag is
+      set via the FLAGS argument.
+
+      In addition, associated output variance estimates (if
+      calculated) may be declared bad and flagged with the BADVAL
+      value in the OUT\_VAR array for similar reasons.
+   }
+}
+\sstroutine{
+   AST\_REMAPFRAME
+}{
+   Modify a Frame\texttt{'} s relationship to other Frames in a FrameSet
+}{
+   \sstdescription{
+      This routine modifies the relationship (i.e. \htmlref{Mapping}{Mapping}) between a
+      specified \htmlref{Frame}{Frame} in a \htmlref{FrameSet}{FrameSet} and the other Frames in that
+      FrameSet.
+
+      Typically, this might be required if the FrameSet has been used
+      to calibrate (say) an image, and that image is re-binned. The
+      Frame describing the image will then have undergone a coordinate
+      transformation, and this should be communicated to the associated
+      FrameSet using this routine.
+   }
+   \sstinvocation{
+      CALL AST\_REMAPFRAME( THIS, IFRAME, MAP, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         IFRAME = INTEGER (Given)
+      }{
+         The index within the FrameSet of the Frame to be modified.
+         This value should lie in the range from 1 to the number of
+         Frames in the FrameSet (as given by its \htmlref{Nframe}{Nframe} attribute).
+      }
+      \sstsubsection{
+         MAP = INTEGER (Given)
+      }{
+         Pointer to a Mapping whose forward transformation converts
+         coordinate values from the original coordinate system
+         described by the Frame to the new one, and whose inverse
+         transformation converts in the opposite direction.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of AST\_\_BASE or AST\_\_CURRENT may be given for the
+         IFRAME argument to specify the base Frame or the current
+         Frame respectively.
+
+         \sstitem
+         The relationship between the selected Frame and any other
+         Frame within the FrameSet will be modified by this routine,
+         but the relationship between all other Frames in the FrameSet
+         remains unchanged.
+
+         \sstitem
+         The number of input coordinate values accepted by the Mapping
+         (its \htmlref{Nin}{Nin} attribute) and the number of output coordinate values
+         generated (its \htmlref{Nout}{Nout} attribute) must be equal and must match the
+         number of axes in the Frame being modified.
+
+         \sstitem
+         If a simple change of axis order is required, then the
+         \htmlref{AST\_PERMAXES}{AST\_PERMAXES} routine may provide a more straightforward method
+         of making the required changes to the FrameSet.
+
+         \sstitem
+         This routine cannot be used to change the number of Frame
+         axes. To achieve this, a new Frame must be added to the FrameSet
+         (\htmlref{AST\_ADDFRAME}{AST\_ADDFRAME}) and the original one removed if necessary
+         (\htmlref{AST\_REMOVEFRAME}{AST\_REMOVEFRAME}).
+
+         \sstitem
+         Any variant Mappings associated with the remapped Frame (except
+         for the current variant) will be lost as a consequence of calling this
+         method (see attribute \texttt{"} \htmlref{Variant}{Variant}\texttt{"} ).
+      }
+   }
+}
+\sstroutine{
+   AST\_REMOVECOLUMN
+}{
+   Remove a column from a table
+}{
+   \sstdescription{
+      This function removes a specified column from the supplied table.
+      The
+      routine
+      returns without action if the named column does not exist in the
+      \htmlref{Table}{Table} (no error is reported).
+   }
+   \sstinvocation{
+      CALL AST\_REMOVECOLUMN( THIS, NAME, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The column name. Trailing spaces are ignored (all other spaces
+         are significant). Case is significant.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_REMOVEFRAME
+}{
+   Remove a Frame from a FrameSet
+}{
+   \sstdescription{
+      This routine removes a \htmlref{Frame}{Frame} from a \htmlref{FrameSet}{FrameSet}. All other Frames
+      in the FrameSet have their indices re-numbered from one (if
+      necessary), but are otherwise unchanged.
+   }
+   \sstinvocation{
+      CALL AST\_REMOVEFRAME( THIS, IFRAME, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         IFRAME = INTEGER (Given)
+      }{
+         The index within the FrameSet of the Frame to be removed.
+         This value should lie in the range from 1 to the number of
+         Frames in the FrameSet (as given by its \htmlref{Nframe}{Nframe} attribute).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Removing a Frame from a FrameSet does not affect the
+         relationship between other Frames in the FrameSet, even if they
+         originally depended on the Frame being removed.
+
+         \sstitem
+         The number of Frames in a FrameSet cannot be reduced to zero.
+         An error will result if an attempt is made to remove the only
+         remaining Frame.
+
+         \sstitem
+         A value of AST\_\_BASE or AST\_\_CURRENT may be given for the
+         IFRAME argument to specify the base Frame or the current
+         Frame respectively.
+
+         \sstitem
+         If a FrameSet\texttt{'} s base or current Frame is removed, the \htmlref{Base}{Base} or
+         \htmlref{Current}{Current} attribute (respectively) of the FrameSet will have its
+         value cleared, so that another Frame will then assume its role
+         by default.
+
+         \sstitem
+         If any other Frame is removed, the base and current Frames
+         will remain the same. To ensure this, the Base and/or Current
+         attributes of the FrameSet will be changed, if necessary, to
+         reflect any change in the indices of these Frames.
+      }
+   }
+}
+\sstroutine{
+   AST\_REMOVEPARAMETER
+}{
+   Remove a global parameter from a table
+}{
+   \sstdescription{
+      This function removes a specified global parameter from the supplied table.
+      The
+      routine
+      returns without action if the named parameter does not exist in the
+      \htmlref{Table}{Table} (no error is reported).
+   }
+   \sstinvocation{
+      CALL AST\_REMOVEPARAMETER( THIS, NAME, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The parameter name. Trailing spaces are ignored (all other spaces
+         are significant). Case is significant.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_REMOVEREGIONS
+}{
+   Remove any Regions from a Mapping
+}{
+   \sstdescription{
+      This function searches the suppliedMapping (which may be a
+      compound \htmlref{Mapping}{Mapping} such as a \htmlref{CmpMap}{CmpMap}) for any component Mappings
+      that are instances of the AST \htmlref{Region}{Region} class. It then creates a new
+      Mapping from which all Regions have been removed. If a Region
+      cannot simply be removed (for instance, if it is a component of a
+      parallel CmpMap), then it is replaced with an equivalent \htmlref{UnitMap}{UnitMap}
+      in the returned Mapping.
+   }
+   \sstinvocation{
+      RESULT = AST\_REMOVEREGIONS( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the original Mapping.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         If the supplied Mapping is a CmpFrame, any component Frames that
+         are instances of the Region class are replaced by the equivalent
+         \htmlref{Frame}{Frame}.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         If the supplied Mapping is a FrameSet, the returned Mapping
+         will be a copy of the supplied FrameSet in which Regions have
+         been removed from all the inter-Frame Mappings, and any Frames
+         which are instances of the Region class are repalced by the
+         equivalent Frame.
+      }
+      \sstsubsection{
+         Mapping
+      }{
+         This function applies to all Mappings.
+      }
+      \sstsubsection{
+         Region
+      }{
+         If the supplied Mapping is a Region, the returned Mapping will
+         be the equivalent Frame.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_REMOVEREGIONS = INTEGER
+      }{
+         A new pointer to the (possibly modified) Mapping.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function can safely be applied even to Mappings which
+         contain no Regions. If no Regions are found, it
+         behaves exactly like \htmlref{AST\_CLONE}{AST\_CLONE} and returns a pointer to the
+         original Mapping.
+
+         \sstitem
+         The Mapping returned by this function may not be independent
+         of the original (even if some Regions were removed), and
+         modifying it may therefore result in indirect modification of
+         the original. If a completely independent result is required, a
+         copy should be made using \htmlref{AST\_COPY}{AST\_COPY}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_REMOVEROW
+}{
+   Remove a row from a table
+}{
+   \sstdescription{
+      This function removes a specified row from the supplied table.
+      The
+      routine
+      returns without action if the row does not exist in the
+      \htmlref{Table}{Table} (no error is reported).
+   }
+   \sstinvocation{
+      CALL AST\_REMOVEROW( THIS, INDEX, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         INDEX = INTEGER (Given)
+      }{
+         The index of the row to be removed. The first row has index 1.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_REMOVETABLES
+}{
+   Remove one or more tables from a FitsChan
+}{
+   \sstdescription{
+      This routine
+      removes the named tables from the \htmlref{FitsChan}{FitsChan}, it they exist (no error
+      is reported if any the tables do not exist).
+   }
+   \sstinvocation{
+      CALL AST\_REMOVETABLES( THIS, KEY, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         KEY = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The key indicating which tables to exist. A single key or a
+         comma-separated list of keys can be supplied. If a blank string
+         is supplied, all tables are removed.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_RESAMPLE$<$X$>$
+}{
+   Resample a region of a data grid
+}{
+   \sstdescription{
+      This is a set of functions for resampling gridded data (e.g. an
+      image) under the control of a geometrical transformation, which
+      is specified by a \htmlref{Mapping}{Mapping}.  The functions operate on a pair of
+      data grids (input and output), each of which may have any number
+      of dimensions. Resampling may be restricted to a specified
+      region of the output grid. An associated grid of error estimates
+      associated with the input data may also be supplied (in the form
+      of variance values), so as to produce error estimates for the
+      resampled output data. Propagation of missing data (bad pixels)
+      is supported.
+
+      You should use a resampling function which matches the numerical
+      type of the data you are processing by replacing $<$X$>$ in
+      the generic function name AST\_RESAMPLE$<$X$>$ by an appropriate 1- or
+      2-character type code. For example, if you are resampling data
+      with type REAL, you should use the function AST\_RESAMPLER (see
+      the \texttt{"} Data Type Codes\texttt{"}  section below for the codes appropriate to
+      other numerical types).
+
+      Resampling of the grid of input data is performed by
+      transforming the coordinates of the centre of each output grid
+      element (or pixel) into the coordinate system of the input grid.
+      Since the resulting coordinates will not, in general, coincide
+      with the centre of an input pixel, sub-pixel interpolation is
+      performed between the neighbouring input pixels. This produces a
+      resampled value which is then assigned to the output pixel. A
+      choice of sub-pixel interpolation schemes is provided, but you
+      may also implement your own.
+
+      This algorithm samples the input data value, it does not integrate
+      it. Thus total data value in the input image will not, in general,
+      be conserved. However, an option is provided (see the \texttt{"} Control Flags\texttt{"}
+      section below) which can produce approximate flux conservation by
+      scaling the output values using the ratio of the output pixel size
+      to the input pixel size. However, if accurate flux conservation is
+      important to you, consder using the
+      \htmlref{AST\_REBIN$<$X$>$}{AST\_REBIN$<$X$>$} or \htmlref{AST\_REBINSEQ$<$X$>$}{AST\_REBINSEQ$<$X$>$} family of routines
+      instead.
+
+      Output pixel coordinates are transformed into the coordinate
+      system of the input grid using the inverse transformation of the
+      Mapping which is supplied. This means that geometrical features
+      in the input data are subjected to the Mapping\texttt{'} s forward
+      transformation as they are transferred from the input to the
+      output grid (although the Mapping\texttt{'} s forward transformation is
+      not explicitly used).
+
+      In practice, transforming the coordinates of every pixel of a
+      large data grid can be time-consuming, especially if the Mapping
+      involves complicated functions, such as sky projections. To
+      improve performance, it is therefore possible to approximate
+      non-linear Mappings by a set of linear transformations which are
+      applied piece-wise to separate sub-regions of the data. This
+      approximation process is applied automatically by an adaptive
+      algorithm, under control of an accuracy criterion which
+      expresses the maximum tolerable geometrical distortion which may
+      be introduced, as a fraction of a pixel.
+
+      This algorithm first attempts to approximate the Mapping with a
+      linear transformation applied over the whole region of the
+      output grid which is being used. If this proves to be
+      insufficiently accurate, the output region is sub-divided into
+      two along its largest dimension and the process is repeated
+      within each of the resulting sub-regions. This process of
+      sub-division continues until a sufficiently good linear
+      approximation is found, or the region to which it is being
+      applied becomes too small (in which case the original Mapping is
+      used directly).
+   }
+   \sstinvocation{
+      RESULT = AST\_RESAMPLE$<$X$>$( THIS, NDIM\_IN, LBND\_IN, UBND\_IN, IN, IN\_VAR,
+                                INTERP, FINTERP, PARAMS, FLAGS,
+                                TOL, MAXPIX, BADVAL,
+                                NDIM\_OUT, LBND\_OUT, UBND\_OUT,
+                                LBND, UBND, OUT, OUT\_VAR, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to a Mapping, whose inverse transformation will be
+         used to transform the coordinates of pixels in the output
+         grid into the coordinate system of the input grid. This
+         yields the positions which are used to obtain resampled
+         values by sub-pixel interpolation within the input grid.
+
+         The number of input coordinates used by this Mapping (as
+         given by its \htmlref{Nin}{Nin} attribute) should match the number of input
+         grid dimensions given by the value of NDIM\_IN
+         below. Similarly, the number of output coordinates (\htmlref{Nout}{Nout}
+         attribute) should match the number of output grid dimensions
+         given by NDIM\_OUT.
+      }
+      \sstsubsection{
+         NDIM\_IN = INTEGER (Given)
+      }{
+         The number of dimensions in the input grid. This should be at
+         least one.
+      }
+      \sstsubsection{
+         LBND\_IN( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         UBND\_IN( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that LBND\_IN and UBND\_IN together define the shape
+         and size of the input grid, its extent along a particular
+         (J\texttt{'} th) dimension being UBND\_IN(J)-LBND\_IN(J)$+$1. They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre.
+      }
+      \sstsubsection{
+         IN( $*$ ) = $<$Xtype$>$ (Given)
+      }{
+         An array, with one element for each pixel in the
+         input grid, containing the input data to be resampled.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using AST\_RESAMPLER, the type of each array element
+         should be REAL).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. normal Fortran array storage order).
+      }
+      \sstsubsection{
+         IN\_VAR( $*$ ) = $<$Xtype$>$ (Given)
+      }{
+         An optional second array with the same size and type as the
+         IN array. If the AST\_\_USEVAR flag is set via the FLAGS
+         argument (below), this array should contain a set of
+         non-negative values which represent estimates of the
+         statistical variance associated with each element of the IN
+         array. Estimates of the variance of the resampled output data
+         will then be calculated.
+
+         If the AST\_\_USEVAR flag is not set, no input variance
+         estimates are required and this array will not be used. A
+         dummy (e.g. one-element) array may then be supplied.
+      }
+      \sstsubsection{
+         INTERP = INTEGER (Given)
+      }{
+         This argument specifies the scheme to be used for sub-pixel
+         interpolation within the input grid. It may be used to select
+         from a set of pre-defined schemes by supplying one of the
+         values described in the \texttt{"} Sub-Pixel Interpolation Schemes\texttt{"}
+         section below.  If a value of zero is supplied, then the
+         default linear interpolation scheme is used (equivalent to
+         supplying the value AST\_\_LINEAR).
+
+         Alternatively, you may supply a value which indicates that
+         you will provide your own routine to perform sub-pixel
+         interpolation by means of the FINTERP argument. Again, see
+         the \texttt{"} Sub-Pixel Interpolation Schemes\texttt{"}  section below for
+         details.
+      }
+      \sstsubsection{
+         FINTERP = SUBROUTINE (Given)
+      }{
+         If the value given for the INTERP argument indicates that you
+         will provide your own routine for sub-pixel interpolation,
+         then the name of that routine should be given here (the name
+         should also appear in a Fortran EXTERNAL statement in the
+         routine which invokes AST\_RESAMPLE$<$X$>$). For details of the
+         interface which the routine should have (several are
+         possible, depending on the value of INTERP), see the
+         \texttt{"} Sub-Pixel Interpolation Schemes\texttt{"}  section below.
+
+         If the INTERP argument has any other value, corresponding to
+         one of the pre-defined interpolation schemes, then this
+         routine will not be used and you may supply the null routine
+         AST\_NULL here (note only one underscore).  No EXTERNAL
+         statement is required for this routine, so long as the AST\_PAR
+         include file has been used.
+      }
+      \sstsubsection{
+         PARAMS( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An optional array which should contain
+         any additional parameter values required by the sub-pixel
+         interpolation scheme. If such parameters are required, this
+         will be noted in the \texttt{"} Sub-Pixel Interpolation Schemes\texttt{"}
+         section below (you may also use this array to pass values
+         to your own interpolation routine).
+
+         If no additional parameters are required, this array is not
+         used. A dummy (e.g. one-element) array may then be supplied.
+      }
+      \sstsubsection{
+         FLAGS = INTEGER (Given)
+      }{
+         The sum of a set of flag values which may be used to
+         provide additional control over the resampling operation. See
+         the \texttt{"} Control Flags\texttt{"}  section below for a description of the
+         options available.  If no flag values are to be set, a value
+         of zero should be given.
+      }
+      \sstsubsection{
+         TOL = DOUBLE PRECISION (Given)
+      }{
+         The maximum tolerable geometrical distortion which may be
+         introduced as a result of approximating non-linear Mappings
+         by a set of piece-wise linear transformations. This should be
+         expressed as a displacement in pixels in the input grid\texttt{'} s
+         coordinate system.
+
+         If piece-wise linear approximation is not required, a value
+         of zero may be given. This will ensure that the Mapping is
+         used without any approximation, but may increase execution
+         time.
+      }
+      \sstsubsection{
+         MAXPIX = INTEGER (Given)
+      }{
+         A value which specifies an initial scale size (in pixels) for
+         the adaptive algorithm which approximates non-linear Mappings
+         with piece-wise linear transformations. Normally, this should
+         be a large value (larger than any dimension of the region of
+         the output grid being used). In this case, a first attempt to
+         approximate the Mapping by a linear transformation will be
+         made over the entire output region.
+
+         If a smaller value is used, the output region will first be
+         divided into sub-regions whose size does not exceed MAXPIX
+         pixels in any dimension. Only at this point will attempts at
+         approximation commence.
+
+         This value may occasionally be useful in preventing false
+         convergence of the adaptive algorithm in cases where the
+         Mapping appears approximately linear on large scales, but has
+         irregularities (e.g. holes) on smaller scales. A value of,
+         say, 50 to 100 pixels can also be employed as a safeguard in
+         general-purpose software, since the effect on performance is
+         minimal.
+
+         If too small a value is given, it will have the effect of
+         inhibiting linear approximation altogether (equivalent to
+         setting TOL to zero). Although this may degrade
+         performance, accurate results will still be obtained.
+      }
+      \sstsubsection{
+         BADVAL = $<$Xtype$>$ (Given)
+      }{
+         This argument should have the same type as the elements of
+         the IN array. It specifies the value used to flag missing
+         data (bad pixels) in the input and output arrays.
+
+         If the AST\_\_USEBAD flag is set via the FLAGS argument,
+         then this value is used to test for bad pixels in the IN
+         (and IN\_VAR) array(s).
+
+         Unless the AST\_\_NOBAD flag is set via the FLAGS argument,
+         this value is also used to flag any output
+         elements in the OUT (and OUT\_VAR) array(s) for which
+         resampled values could not be obtained (see the \texttt{"} Propagation
+         of Missing Data\texttt{"}  section below for details of the
+         circumstances under which this may occur). The AST\_RESAMPLE$<$X$>$
+         function return value indicates whether any such values have
+         been produced. If the AST\_\_NOBAD flag is set. then output array
+         elements for which no resampled value could be obtained are
+         left set to the value they had on entry to this function.
+      }
+      \sstsubsection{
+         NDIM\_OUT = INTEGER (Given)
+      }{
+         The number of dimensions in the output grid. This should be
+         at least one. It need not necessarily be equal to the number
+         of dimensions in the input grid.
+      }
+      \sstsubsection{
+         LBND\_OUT( NDIM\_OUT ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the first pixel
+         in the output grid along each dimension.
+      }
+      \sstsubsection{
+         UBND\_OUT( NDIM\_OUT ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the last pixel in
+         the output grid along each dimension.
+
+         Note that LBND\_OUT and UBND\_OUT together define the
+         shape, size and coordinate system of the output grid in the
+         same way as LBND\_IN and UBND\_IN define the shape, size
+         and coordinate system of the input grid.
+      }
+      \sstsubsection{
+         LBND( NDIM\_OUT ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the first pixel in the region
+         of the output grid for which a resampled value is to be
+         calculated.
+      }
+      \sstsubsection{
+         UBND( NDIM\_OUT ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the last pixel in the region of
+         the output grid for which a resampled value is to be
+         calculated.
+
+         Note that LBND and UBND together define the shape and
+         position of a (hyper-)rectangular region of the output grid
+         for which resampled values should be produced. This region
+         should lie wholly within the extent of the output grid (as
+         defined by the LBND\_OUT and UBND\_OUT arrays). Regions of
+         the output grid lying outside this region will not be
+         modified.
+      }
+      \sstsubsection{
+         OUT( $*$ ) = $<$Xtype$>$ (Returned)
+      }{
+         An array, with one element for each pixel in the
+         output grid, into which the resampled data values will be
+         returned. The numerical type of this array should match that
+         of the IN array, and the data storage order should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. normal Fortran array storage order).
+      }
+      \sstsubsection{
+         OUT\_VAR( $*$ ) = $<$Xtype$>$ (Returned)
+      }{
+         An optional array with the same type and size as the OUT
+         array. If the AST\_\_USEVAR flag is set via the FLAGS argument,
+         this array will be used to return variance estimates for the
+         resampled data values.
+
+         The output variance values will be calculated on the
+         assumption that errors on the input data values are
+         statistically independent and that their variance estimates
+         may simply be summed (with appropriate weighting factors)
+         when several input pixels contribute to an output data
+         value. If this assumption is not valid, then the output error
+         estimates may be biased. In addition, note that the
+         statistical errors on neighbouring output data values (as
+         well as the estimates of those errors) may often be
+         correlated, even if the above assumption about the input data
+         is correct, because of the sub-pixel interpolation schemes
+         employed.
+
+         If the AST\_\_USEVAR flag is not set, no output variance
+         estimates will be calculated and this array will not be
+         used. A dummy (e.g. one-element) array may then be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_RESAMPLE$<$X$>$ = INTEGER
+      }{
+         The number of output pixels for which no valid resampled value
+         could be obtained. Thus, in the absence of any error, a returned
+         value of zero indicates that all the required output pixels
+         received valid resampled data values (and variances). See the
+         BADVAL and FLAGS arguments.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with the global error status set, or if it should fail for any
+         reason.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate resampling function, you should
+      replace $<$X$>$ in the generic function name AST\_RESAMPLE$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: DOUBLE PRECISION
+
+         \sstitem
+         R: REAL
+
+         \sstitem
+         I: INTEGER
+
+         \sstitem
+         UI: INTEGER (treated as unsigned)
+
+         \sstitem
+         S: INTEGER$*$2 (short integer)
+
+         \sstitem
+         US: INTEGER$*$2 (short integer, treated as unsigned)
+
+         \sstitem
+         B: BYTE (treated as signed)
+
+         \sstitem
+         UB: BYTE (treated as unsigned)
+
+      }
+      For example, AST\_RESAMPLED would be used to process DOUBLE
+      PRECISION data, while AST\_RESAMPLES would be used to process
+      short integer data (stored in an INTEGER$*$2 array), etc.
+
+      For compatibility with other Starlink facilities, the codes W
+      and UW are provided as synonyms for S and US respectively (but
+      only in the Fortran interface to AST).
+   }
+   \sstdiytopic{
+      Sub-Pixel Interpolation Schemes
+   }{
+      There is no such thing as a perfect sub-pixel interpolation
+      scheme and, in practice, all resampling will result in some
+      degradation of gridded data.  A range of schemes is therefore
+      provided, from which you can choose the one which best suits
+      your needs.
+
+      In general, a balance must be struck between schemes which tend
+      to degrade sharp features in the data by smoothing them, and
+      those which attempt to preserve sharp features. The latter will
+      often tend to introduce unwanted oscillations, typically visible
+      as \texttt{"} ringing\texttt{"}  around sharp features and edges, especially if the
+      data are under-sampled (i.e. if the sharpest features are less
+      than about two pixels across). In practice, a good interpolation
+      scheme is likely to be a compromise and may exhibit some aspects
+      of both these features.
+
+      For under-sampled data, some interpolation schemes may appear to
+      preserve data resolution because they transform single input
+      pixels into single output pixels, rather than spreading their
+      data between several output pixels. While this may look
+      better cosmetically, it can result in a geometrical shift of
+      sharp features in the data. You should beware of this if you
+      plan to use such features (e.g.) for image alignment.
+
+      The following are two easy-to-use sub-pixel interpolation
+      schemes which are generally applicable. They are selected by
+      supplying the appropriate value (defined in the AST\_PAR include
+      file) via the INTERP argument. In these cases, the FINTERP
+      and PARAMS arguments are not used:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_NEAREST: This is the simplest possible scheme, in which
+         the value of the input pixel with the nearest centre to the
+         interpolation point is used. This is very quick to execute and
+         will preserve single-pixel features in the data, but may
+         displace them by up to half their width along each dimension. It
+         often gives a good cosmetic result, so is useful for quick-look
+         processing, but is unsuitable if accurate geometrical
+         transformation is required.
+
+         \sstitem
+         AST\_\_LINEAR: This is the default scheme, which uses linear
+         interpolation between the nearest neighbouring pixels in the
+         input grid (there are two neighbours in one dimension, four
+         neighbours in two dimensions, eight in three dimensions,
+         etc.). It is superior to the nearest-pixel scheme (above) in not
+         displacing features in the data, yet it still executes fairly
+         rapidly. It is generally a safe choice if you do not have any
+         particular reason to favour another scheme, since it cannot
+         introduce oscillations. However, it does introduce some spatial
+         smoothing which varies according to the distance of the
+         interpolation point from the neighbouring pixels. This can
+         degrade the shape of sharp features in the data in a
+         position-dependent way. It may also show in the output variance
+         grid (if used) as a pattern of stripes or fringes.
+
+      }
+      An alternative set of interpolation schemes is based on forming
+      the interpolated value from the weighted sum of a set of
+      surrounding pixel values (not necessarily just the nearest
+      neighbours). This approach has its origins in the theory of
+      digital filtering, in which interpolated values are obtained by
+      conceptually passing the sampled data (represented by a grid of
+      delta functions) through a linear filter which implements a
+      convolution. Because the convolution kernel is continuous, the
+      convolution yields a continuous function which may then be
+      evaluated at fractional pixel positions. The (possibly
+      multi-dimensional) kernel is usually regarded as \texttt{"} separable\texttt{"}  and
+      formed from the product of a set of identical 1-dimensional
+      kernel functions, evaluated along each dimension. Different
+      interpolation schemes are then distinguished by the choice of
+      this 1-dimensional interpolation kernel. The number of
+      surrounding pixels which contribute to the result may also be
+      varied.
+
+      From a practical standpoint, it is useful to divide the weighted
+      sum of pixel values by the sum of the weights when determining
+      the interpolated value.  Strictly, this means that a true
+      convolution is no longer being performed. However, the
+      distinction is rarely important in practice because (for
+      slightly subtle reasons) the sum of weights is always
+      approximately constant for good interpolation kernels. The
+      advantage of this technique, which is used here, is that it can
+      easily accommodate missing data and tends to minimise unwanted
+      oscillations at the edges of the data grid.
+
+      In the following schemes, which are based on a 1-dimensional
+      interpolation kernel, the first element of the PARAMS array
+      should be used to specify how many pixels are to contribute to the
+      interpolated result on either side of the interpolation point in
+      each dimension (the nearest integer value is used). Execution time
+      increases rapidly with this number. Typically, a value of 2 is
+      appropriate and the minimum value used will be 1 (i.e. two pixels
+      altogether, one on either side of the interpolation point).
+      A value of zero or less may be given for PARAMS(1)
+      to indicate that a suitable number of pixels should be calculated
+      automatically.
+
+      In each of these cases, the FINTERP argument is not used:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_GAUSS: This scheme uses a kernel of the form exp(-k$*$x$*$x), with
+         k a positive constant. The full-width at half-maximum (FWHM) is
+         given by
+         PARAMS(2)
+         value, which should be at least 0.1 (in addition, setting PARAMS(1)
+         to zero will select the number of contributing pixels so as to utilise
+         the width of the kernel out to where the envelope declines to 1\% of its
+         maximum value). This kernel suppresses noise at the expense of
+         smoothing the output array.
+
+         \sstitem
+         AST\_\_SINC: This scheme uses a sinc(pi$*$x) kernel, where x is the
+         pixel offset from the interpolation point and sinc(z)=sin(z)/z. This
+         sometimes features as an \texttt{"} optimal\texttt{"}  interpolation kernel in books on
+         image processing. Its supposed optimality depends on the assumption
+         that the data are band-limited (i.e. have no spatial frequencies above
+         a certain value) and are adequately sampled. In practice, astronomical
+         data rarely meet these requirements. In addition, high spatial
+         frequencies are often present due (e.g.) to image defects and cosmic
+         ray events. Consequently, substantial ringing can be experienced with
+         this kernel. The kernel also decays slowly with distance, so that
+         many surrounding pixels are required, leading to poor performance.
+         Abruptly truncating it, by using only a few neighbouring pixels,
+         improves performance and may reduce ringing (if PARAMS(1) is set to
+         zero, then only two pixels will be used on either side). However, a
+         more gradual truncation, as implemented by other kernels, is generally
+         to be preferred. This kernel is provided mainly so that you can
+         convince yourself not to use it!
+
+         \sstitem
+         AST\_\_SINCSINC: This scheme uses an improved kernel, of the form
+         sinc(pi$*$x).sinc(k$*$pi$*$x), with k a constant, out to the point where
+         sinc(k$*$pi$*$x) goes to zero, and zero beyond. The second sinc() factor
+         provides an \texttt{"} envelope\texttt{"}  which gradually rolls off the normal sinc(pi$*$x)
+         kernel at large offsets. The width of this envelope is specified by
+         giving the number of pixels offset at which it goes to zero by means
+         of the PARAMS(2) value, which should be at least 1.0 (in addition,
+         setting PARAMS(1) to zero will select the number of contributing
+         pixels so as to utilise the full width of the kernel, out to where it
+         reaches zero). The case given by PARAMS(1)=2, PARAMS(2)=2 is typically
+         a good choice and is sometimes known as the Lanczos kernel. This is a
+         valuable general-purpose interpolation scheme, intermediate in its
+         visual effect on images between the AST\_\_NEAREST and AST\_\_LINEAR
+         schemes. Although the kernel is slightly oscillatory, ringing is
+         adequately suppressed if the data are well sampled.
+
+         \sstitem
+         AST\_\_SINCCOS: This scheme uses a kernel of the form
+         sinc(pi$*$x).cos(k$*$pi$*$x), with k a constant, out to the point where
+         cos(k$*$pi$*$x) goes to zero, and zero beyond. As above, the cos() factor
+         provides an envelope which gradually rolls off the sinc() kernel
+         at large offsets. The width of this envelope is specified by giving
+         the number of pixels offset at which it goes to zero by means
+         of the PARAMS(2) value, which should be at least 1.0 (in addition,
+         setting PARAMS(1) to zero will select the number of contributing
+         pixels so as to utilise the full width of the kernel, out to where it
+         reaches zero). This scheme gives similar results to the
+         AST\_\_SINCSINC scheme, which it resembles.
+
+         \sstitem
+         AST\_\_SINCGAUSS: This scheme uses a kernel of the form
+         sinc(pi$*$x).exp(-k$*$x$*$x), with k a positive constant. Here, the sinc()
+         kernel is rolled off using a Gaussian envelope which is specified by
+         giving its full-width at half-maximum (FWHM) by means of the PARAMS(2)
+         value, which should be at least 0.1 (in addition, setting PARAMS(1)
+         to zero will select the number of contributing pixels so as to utilise
+         the width of the kernel out to where the envelope declines to 1\% of its
+         maximum value). On astronomical images and spectra, good results are
+         often obtained by approximately matching the FWHM of the
+         envelope function, given by PARAMS(2), to the point spread function
+         of the input data. However, there does not seem to be any theoretical
+         reason for this.
+
+         \sstitem
+         AST\_\_SOMB: This scheme uses a somb(pi$*$x) kernel (a \texttt{"} sombrero\texttt{"}
+         function), where x is the pixel offset from the interpolation point
+         and somb(z)=2$*$J1(z)/z  (J1 is a Bessel function of the first kind of
+         order 1). It is similar to the AST\_\_SINC kernel, and has the same
+         parameter usage.
+
+         \sstitem
+         AST\_\_SOMBCOS: This scheme uses a kernel of the form
+         somb(pi$*$x).cos(k$*$pi$*$x), with k a constant, out to the point where
+         cos(k$*$pi$*$x) goes to zero, and zero beyond. It is similar to the
+         AST\_\_SINCCOS kernel, and has the same parameter usage.
+
+      }
+      In addition, the following schemes are provided which are not based
+      on a 1-dimensional kernel:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_BLOCKAVE: This scheme simply takes an average of all the
+         pixels on the input grid in a cube centred on the interpolation
+         point.  The number of pixels in the cube is determined by the
+         value of the first element of the PARAMS array, which gives
+         the number of pixels in each dimension on either side of the
+         central point.  Hence a block of (2 $*$ PARAMS(1))$*$$*$NDIM\_IN
+         pixels in the input grid will be examined to determine the
+         value of the output pixel.  If the variance is not being used
+         (USEVAR = .FALSE.) then all valid pixels in this cube
+         will be averaged in to the result with equal weight.
+         If variances are being used, then each input pixel will be
+         weighted proportionally to the reciprocal of its variance; any
+         pixel without a valid variance will be discarded.  This scheme
+         is suitable where the output grid is much coarser than the
+         input grid; if the ratio of pixel sizes is R then a suitable
+         value of PARAMS(1) may be R/2.
+
+      }
+      Finally, supplying the following values for INTERP allows you to
+      implement your own sub-pixel interpolation scheme by means of
+      your own routine. You should supply the name of this routine via
+      the FINTERP argument:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_UKERN1: In this scheme, you supply a routine to evaluate
+         your own 1-dimensional interpolation kernel, which is then used
+         to perform sub-pixel interpolation (as described above). The
+         routine you supply should have the same interface as the
+         fictitious \htmlref{AST\_UKERN1}{AST\_UKERN1} routine (q.v.).  In addition, a value
+         should be given via PARAMS(1) to specify the number of
+         neighbouring pixels which are to contribute to each interpolated
+         value (in the same way as for the pre-defined interpolation
+         schemes described above). Other elements of the PARAMS array
+         are available to pass values to your interpolation routine.
+
+         \sstitem
+         AST\_\_UINTERP: This is a completely general scheme, in which
+         your interpolation routine has access to all of the input
+         data. This allows you to implement any interpolation algorithm
+         you choose, which could (for example) be non-linear, or
+         adaptive. In this case, the AST\_RESAMPLE$<$X$>$ functions play no
+         role in the sub-pixel interpolation process and simply handle
+         the geometrical transformation of coordinates and other
+         housekeeping. The routine you supply should have the same
+         interface as the fictitious \htmlref{AST\_UINTERP}{AST\_UINTERP} routine (q.v.). In this
+         case, the PARAMS argument is not used by AST\_RESAMPLE$<$X$>$, but
+         is available to pass values to your interpolation routine.
+      }
+   }
+   \sstdiytopic{
+      Control Flags
+   }{
+      The following flags are defined in the AST\_PAR include file and
+      may be used to provide additional control over the resampling
+      process. Having selected a set of flags, you should supply the
+      sum of their values via the FLAGS argument:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_NOBAD: Indicates that any output array elements for which no
+         resampled value could be obtained should be left set to the value
+         they had on entry to this function. If this flag is not supplied,
+         such output array elements are set to the value supplied for
+         argument BADVAL. Note, this flag cannot be used in conjunction
+         with the AST\_\_CONSERVEFLUX flag (an error will be reported if both
+         flags are specified).
+
+         \sstitem
+         AST\_\_URESAMP1, 2, 3 \& 4: A set of four flags which are
+         reserved for your own use. They may be used to pass private
+         information to any sub-pixel interpolation routine which you
+         implement yourself. They are ignored by all the pre-defined
+         interpolation schemes.
+
+         \sstitem
+         AST\_\_USEBAD: Indicates that there may be bad pixels in the
+         input array(s) which must be recognised by comparing with the
+         value given for BADVAL and propagated to the output array(s).
+         If this flag is not set, all input values are treated literally
+         and the BADVAL value is only used for flagging output array
+         values.
+
+         \sstitem
+         AST\_\_USEVAR: Indicates that variance information should be
+         processed in order to provide estimates of the statistical error
+         associated with the resampled values. If this flag is not set,
+         no variance processing will occur and the IN\_VAR and OUT\_VAR
+         arrays will not be used. (Note that this flag is only available
+         in the Fortran interface to AST.)
+
+         \sstitem
+         AST\_\_CONSERVEFLUX: Indicates that the output pixel values should
+         be scaled in such a way as to preserve (approximately) the total data
+         value in a feature on the sky. Without this flag, each output pixel
+         value represents an instantaneous sample of the input data values at
+         the corresponding input position. This is appropriate if the input
+         data represents the spatial density of some quantity (e.g. surface
+         brightness in Janskys per square arc-second) because the output
+         pixel values will have the same normalisation and units as the
+         input pixel values. However, if the input data values represent
+         flux (or some other physical quantity) per pixel, then the
+         AST\_\_CONSERVEFLUX flag could be used. This causes each output
+         pixel value to be scaled by the ratio of the output pixel size to
+         the input pixel size.
+
+      }
+      This flag can only be used if the Mapping is successfully approximated
+      by one or more linear transformations. Thus an error will be reported
+      if it used when the
+      TOL argument
+      is set to zero (which stops the use of linear approximations), or
+      if the Mapping is too non-linear to be approximated by a piece-wise
+      linear transformation. The ratio of output to input pixel size is
+      evaluated once for each panel of the piece-wise linear approximation to
+      the Mapping, and is assumed to be constant for all output pixels in the
+      panel. The scaling factors for adjacent panels will in general
+      differ slightly, and so the joints between panels may be visible when
+      viewing the output image at high contrast. If this is a problem,
+      reduce the value of the
+      TOL argument
+      until the difference between adjacent panels is sufficiently small
+      to be insignificant.
+
+      Note, this flag cannot be used in conjunction with the AST\_\_NOBAD
+      flag (an error will be reported if both flags are specified).
+   }
+   \sstdiytopic{
+      Propagation of Missing Data
+   }{
+      Unless the AST\_\_NOBAD flag is specified, instances of missing data
+      (bad pixels) in the output grid are
+      identified by occurrences of the BADVAL value in the OUT
+      array. These may be produced if any of the following happen:
+
+      \sstitemlist{
+
+         \sstitem
+         The input position (the transformed position of the output
+         pixel\texttt{'} s centre) lies outside the boundary of the grid of input
+         pixels.
+
+         \sstitem
+         The input position lies inside the boundary of a bad input
+         pixel. In this context, an input pixel is considered bad if its
+         data value is equal to BADVAL and the AST\_\_USEBAD flag is
+         set via the FLAGS argument.
+         (Positions which have half-integral coordinate values, and
+         therefore lie on a pixel boundary, are regarded as lying within
+         the pixel with the larger, i.e. more positive, index.)
+
+         \sstitem
+         The set of neighbouring input pixels (excluding those which
+         are bad) is unsuitable for calculating an interpolated
+         value. Whether this is true may depend on the sub-pixel
+         interpolation scheme in use.
+
+         \sstitem
+         The interpolated value lies outside the range which can be
+         represented using the data type of the OUT array.
+
+      }
+      In addition, associated output variance estimates (if
+      calculated) may be declared bad and flagged with the BADVAL
+      value in the OUT\_VAR array under any of the following
+      circumstances:
+
+      \sstitemlist{
+
+         \sstitem
+         The associated resampled data value (in the OUT array) is bad.
+
+         \sstitem
+         The set of neighbouring input pixels which contributed to the
+         output data value do not all have valid variance estimates
+         associated with them. In this context, an input variance
+         estimate may be regarded as bad either because it has the value
+         BADVAL (and the AST\_\_USEBAD flag is set), or because it is
+         negative.
+
+         \sstitem
+         The set of neighbouring input pixels for which valid variance
+         values are available is unsuitable for calculating an overall
+         variance value. Whether this is true may depend on the sub-pixel
+         interpolation scheme in use.
+
+         \sstitem
+         The variance value lies outside the range which can be
+         represented using the data type of the OUT\_VAR array.
+
+      }
+      If the AST\_\_NOBAD flag is specified via
+      argument FLAGS,
+      then output array elements that would otherwise be set to
+      BADVAL
+      are instead left holding the value they had on entry to this
+      function. The number of such array elements is returned as
+      the function value.
+   }
+}
+\sstroutine{
+   AST\_RESOLVE
+}{
+   Resolve a vector into two orthogonal components
+}{
+   \sstdescription{
+      This routine resolves a vector into two perpendicular components.
+      The vector from point 1 to point 2 is used as the basis vector.
+      The vector from point 1 to point 3 is resolved into components
+      parallel and perpendicular to this basis vector. The lengths of the
+      two components are returned, together with the position of closest
+      aproach of the basis vector to point 3.
+   }
+   \sstinvocation{
+      CALL AST\_RESOLVE( THIS, POINT1, POINT2, POINT3, POINT4, D1, D2,
+                        STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Frame}{Frame}.
+      }
+      \sstsubsection{
+         POINT1( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). This marks the start of the basis vector,
+         and of the vector to be resolved.
+      }
+      \sstsubsection{
+         POINT2( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (Naxes attribute). This marks the end of the basis vector.
+      }
+      \sstsubsection{
+         POINT3( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array with one element for each Frame axis
+         (Naxes attribute). This marks the end of the vector to be
+         resolved.
+      }
+      \sstsubsection{
+         POINT4( $*$ ) = DOUBLE PRECISION (Returned)
+      }{
+         An array with one element for each Frame axis
+         in which the coordinates of the point of closest approach of the
+         basis vector to point 3 will be returned.
+      }
+      \sstsubsection{
+         D1 = DOUBLE PRECISION (Returned)
+      }{
+         The distance from
+         point 1 to point 4 (that is, the length of the component parallel
+         to the basis vector). Positive values are in the same sense as
+         movement from point 1 to point 2.
+      }
+      \sstsubsection{
+         D2 = DOUBLE PRECISION (Returned)
+      }{
+         The distance from
+         point 4 to point 3 (that is, the length of the component
+         perpendicular to the basis vector). The value is always positive.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Each vector used in this routine is the path of
+         shortest distance between two points, as defined by the
+         \htmlref{AST\_DISTANCE}{AST\_DISTANCE} function.
+
+         \sstitem
+         This function will return \texttt{"} bad\texttt{"}  coordinate values (AST\_\_BAD)
+         if any of the input coordinates has this value, or if the required
+         output values are undefined.
+      }
+   }
+}
+\sstroutine{
+   AST\_RETAINFITS
+}{
+   Indicate that the current card in a FitsChan should be retained
+}{
+   \sstdescription{
+      This routine
+      stores a flag with the current card in the \htmlref{FitsChan}{FitsChan} indicating that
+      the card should not be removed from the FitsChan when an \htmlref{Object}{Object} is
+      read from the FitsChan using
+      \htmlref{AST\_READ}{AST\_READ}.
+
+      Cards that have not been flagged in this way are removed when a
+      read operation completes succesfully, but only if the card was used
+      in the process of creating the returned AST Object. Any cards that
+      are irrelevant to the creation of the AST Object are retained whether
+      or not they are flagged.
+   }
+   \sstinvocation{
+      CALL AST\_RETAINFITS( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function returns without action if the FitsChan is
+         initially positioned at the \texttt{"} end-of-file\texttt{"}  (i.e. if the \htmlref{Card}{Card}
+         attribute exceeds the number of cards in the FitsChan).
+
+         \sstitem
+         The current card is not changed by this function.
+      }
+   }
+}
+\sstroutine{
+   AST\_RegionOutline
+}{
+   Draw the outline of an AST Region
+}{
+   \sstdescription{
+      This function draws an outline around the supplied AST \htmlref{Region}{Region} object.
+   }
+   \sstinvocation{
+      CALL AST\_REGIONOUTLINE( THIS, REGION, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{Plot}{Plot}.
+      }
+      \sstsubsection{
+         REGION = INTEGER (Given)
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_SAME
+}{
+   Test if two AST pointers refer to the same Object
+}{
+   \sstdescription{
+      This function returns a logical result to indicate
+      whether two pointers refer to the same \htmlref{Object}{Object}.
+   }
+   \sstinvocation{
+      RESULT = AST\_SAME( THIS, THAT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the first Object.
+      }
+      \sstsubsection{
+         THAT = INTEGER (Given)
+      }{
+         Pointer to the second Object.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SAME = LOGICAL
+      }{
+         .TRUE. if the two pointers refer to the same Object, otherwise
+         .FALSE.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Two independent Objects that happen to be identical are not
+         considered to be the same Object by this function.
+
+         \sstitem
+         A value of .FALSE. will be returned if this function is invoked
+         with STATUS set to an error value, or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_SELECTORMAP
+}{
+   Create a SelectorMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SelectorMap}{SelectorMap} and optionally initialises
+      its attributes.
+
+      A SelectorMap is a \htmlref{Mapping}{Mapping} that identifies which \htmlref{Region}{Region} contains
+      a given input position.
+
+      A SelectorMap encapsulates a number of Regions that all have the same
+      number of axes and represent the same coordinate \htmlref{Frame}{Frame}. The number of
+      inputs (\htmlref{Nin}{Nin} attribute) of the SelectorMap equals the number of axes
+      spanned by one of the encapsulated Region. All SelectorMaps have only
+      a single output. SelectorMaps do not define an inverse transformation.
+
+      For each input position, the forward transformation of a SelectorMap
+      searches through the encapsulated Regions (in the order supplied when
+      the SelectorMap was created) until a Region is found which contains
+      the input position. The index associated with this Region is
+      returned as the SelectorMap output value (the index value is the
+      position of the Region within the list of Regions supplied when the
+      SelectorMap was created, starting at 1 for the first Region). If an
+      input position is not contained within any Region, a value of zero is
+      returned by the forward transformation.
+
+      If a compound Mapping contains a SelectorMap in series with its own
+      inverse, the combination of the two adjacent SelectorMaps will be
+      replaced by a \htmlref{UnitMap}{UnitMap} when the compound Mapping is simplified using
+      \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}.
+
+      In practice, SelectorMaps are often used in conjunction with SwitchMaps.
+   }
+   \sstinvocation{
+      RESULT = AST\_SELECTORMAP( NREG, REGS, BADVAL, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NREG = INTEGER (Given)
+      }{
+         The number of supplied Regions.
+      }
+      \sstsubsection{
+         REGS( NREG ) = INTEGER (Given)
+      }{
+         An array of pointers to the Regions. All the supplied Regions must
+         relate to the same coordinate Frame. The number of axes in this
+         coordinate Frame defines the number of inputs for the SelectorMap.
+      }
+      \sstsubsection{
+         BADVAL = DOUBLE PRECISION (Given)
+      }{
+         The value to be returned by the forward transformation of the
+         SelectorMap for any input positions that have a bad (AST\_\_BAD)
+         value on any axis.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new SelectorMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SELECTORMAP = INTEGER
+      }{
+         A pointer to the new SelectorMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Deep copies are taken of the supplied Regions. This means that
+         any subsequent changes made to the component Regions using the
+         supplied pointers will have no effect on the SelectorMap.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_SET
+}{
+   Set attribute values for an Object
+}{
+   \sstdescription{
+      This routine assigns a set of attribute values to an \htmlref{Object}{Object},
+      over-riding any previous values. The attributes and their new
+      values are specified via a character string, which should
+      contain a comma-separated list of the form:
+
+         \texttt{"} attribute\_1 = value\_1, attribute\_2 = value\_2, ... \texttt{"}
+
+      where \texttt{"} attribute\_n\texttt{"}  specifies an attribute name, and the value
+      to the right of each \texttt{"} =\texttt{"}  sign should be a suitable textual
+      representation of the value to be assigned. This value will be
+      interpreted according to the attribute\texttt{'} s data type.
+   }
+   \sstinvocation{
+      CALL AST\_SET( THIS, SETTINGS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         SETTINGS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing a comma-separated list of
+         attribute settings in the form described above.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         CALL AST\_SET( MAP, \texttt{'} \htmlref{Report}{Report} = 1, \htmlref{Zoom}{Zoom} = 25.0\texttt{'} , STATUS )
+      }{
+         Sets the Report attribute for Object MAP to the value 1 and
+         the Zoom attribute to 25.0.
+      }
+      \sstexamplesubsection{
+         CALL AST\_SET( FRAME, \texttt{'} Label( 1 ) =Offset from cluster axis\texttt{'} , STATUS )
+      }{
+         Sets the Label(1) attribute for Object FRAME to a suitable
+         string.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         White space may also surround attribute values, where it will
+         generally be ignored (except for string-valued attributes where
+         it is significant and forms part of the value to be assigned).
+
+         \sstitem
+         To include a literal comma in the value assigned to an attribute,
+         the whole attribute value should be enclosed in quotation markes.
+
+         \sstitem
+         An error will result if an attempt is made to set a value for
+         a read-only attribute.
+      }
+   }
+}
+\sstroutine{
+   AST\_SET$<$X$>$
+}{
+   Set an attribute value for an Object
+}{
+   \sstdescription{
+      This is a family of routines which set a specified attribute
+      value for an \htmlref{Object}{Object} using one of several different data
+      types. The type is selected by replacing $<$X$>$ in the routine name
+      by C, D, I, L or R, to supply a value in Character, Double
+      precision, Integer, Logical or Real format, respectively.
+
+      If possible, the value you supply is converted to the type of
+      the attribute. If conversion is not possible, an error will
+      result.
+   }
+   \sstinvocation{
+      CALL AST\_SET$<$X$>$( THIS, ATTRIB, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         ATTRIB = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing the name of the attribute whose
+         value is to be set.
+      }
+      \sstsubsection{
+         VALUE = $<$X$>$type (Given)
+      }{
+         The value to be set for the attribute, in the data type corresponding
+         to $<$X$>$.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         These routines apply to all Objects.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         CALL AST\_SETC( PLOT, \texttt{'} \htmlref{Title}{Title}\texttt{'} , CVALUE, STATUS )
+      }{
+         Sets the Title attribute value for Object PLOT to the contents
+         of the character variable CVALUE.
+      }
+      \sstexamplesubsection{
+         CALL AST\_SETL( FRAME, \texttt{'} Preserve\texttt{'} , .TRUE., STATUS );
+      }{
+         Sets the Preserve attribute value for Object FRAME to 1 (true).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         The logical value .FALSE. will translate to a numerical attribute
+         value of zero and logical .TRUE. will translate to one.
+
+         \sstitem
+         An error will result if an attempt is made to set a value for
+         a read-only attribute.
+      }
+   }
+}
+\sstroutine{
+   AST\_SETACTIVEUNIT
+}{
+   Specify how the Unit attribute should be used
+}{
+   \sstdescription{
+      This routine
+      sets the current value of the ActiveUnit flag for a \htmlref{Frame}{Frame}, which
+      controls how the Frame behaves when it is used (by
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} or \htmlref{AST\_CONVERT}{AST\_CONVERT})
+      to match another Frame. If the ActiveUnit flag is set in both
+      template and target Frames then the returned \htmlref{Mapping}{Mapping} takes into account
+      any differences in axis units. The default value for simple Frames is
+      zero, which preserves the behaviour of versions of AST prior to
+      version 2.0.
+
+      If the ActiveUnit flag of either Frame is
+      .FALSE.,
+      then the Mapping will ignore any difference in the Unit attributes of
+      corresponding template and target axes. In this mode, the Unit
+      attributes are purely descriptive commentary for the benefit of
+      human readers and do not influence the Mappings between Frames.
+      This is the behaviour which all Frames had in older version of AST,
+      prior to the introduction of this attribute.
+
+      If the ActiveUnit flag of both Frames is
+      .TRUE.,
+      then the Mapping from template to target will take account of any
+      difference in the axis Unit attributes, where-ever possible. For
+      instance, if corresponding target and template axes have Unit strings of
+      \texttt{"} km\texttt{"}  and \texttt{"} m\texttt{"} , then the \htmlref{FrameSet}{FrameSet} class will use a \htmlref{ZoomMap}{ZoomMap} to connect
+      them which introduces a scaling of 1000. If no Mapping can be found
+      between the corresponding units string, then an error is reported.
+      In this mode, it is assumed that values of the Unit attribute conform
+      to the syntax for units strings described in the FITS WCS Paper I
+      \texttt{"} Representations of world coordinates in FITS\texttt{"}  (Greisen \& Calabretta).
+      Particularly, any of the named unit symbols, functions, operators or
+      standard multiplier prefixes listed within that paper can be used within
+      a units string. A units string may contain symbols for unit which are
+      not listed in the FITS paper, but transformation to any other units
+      will then not be possible (except to units which depend only on the
+      same unknown units - thus \texttt{"} flops\texttt{"}  can be transformed to \texttt{"} Mflops\texttt{"}
+      even though \texttt{"} flops\texttt{"}  is not a standard FITS unit symbol).
+
+      A range of common non-standard variations of unit names and multiplier
+      prefixes are also allowed, such as adding an \texttt{"} s\texttt{"}  to the end of Angstrom,
+      using a lower case \texttt{"} a\texttt{"}  at the start of \texttt{"} angstrom\texttt{"} , \texttt{"} micron\texttt{"}  instead of
+      \texttt{"} um\texttt{"} , \texttt{"} sec\texttt{"}  instead of \texttt{"} s\texttt{"} , etc.
+
+      If the ActiveUnit flag is .TRUE., setting a new Unit value for an
+      axis may also change its Label and Symbol attributes. For instance, if
+      an axis has Unit \texttt{"} Hz\texttt{"}  and Label \texttt{"} frequency\texttt{"} , then changing its Unit to
+      \texttt{"} log(Hz)\texttt{"}  will change its Label to \texttt{"} log( frequency )\texttt{"} . In addition,
+      the \htmlref{Axis}{Axis} Format attribute will be cleared when-ever a new value
+      is assigned to the Unit attribute.
+
+      Note, if a .TRUE. value is set for the ActiveUnit flag, then changing a
+      Unit value for the current Frame within a FrameSet will result in the
+      Frame being re-mapped (that is, the Mappings which define the
+      relationships between Frames within the FrameSet will be modified to
+      take into account the change in Units).
+   }
+   \sstinvocation{
+      CALL AST\_SETACTIVEUNIT( THIS, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         VALUE = LOGICAL (Given)
+      }{
+         The new value to use.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The ActiveUnit flag for a SkyFrame is always .FALSE. (any value
+         supplied using this routine is ignored).
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         The ActiveUnit flag for a SpecFrame is always .TRUE. (any value
+         supplied using this routine is ignored).
+      }
+      \sstsubsection{
+         \htmlref{FluxFrame}{FluxFrame}
+      }{
+         The ActiveUnit flag for a FluxFrame is always .TRUE. (any value
+         supplied using this routine is ignored).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The default ActiveUnit flag for a CmpFrame is .TRUE. if both of the
+         component Frames are using active units, and .FALSE. otherwise. When
+         a new value is set for the ActiveUnit flag, the flag value
+         is propagated to the component Frames. This change will be
+         reflected through all references to the component Frames, not
+         just those encapsulated within the CmpFrame.
+      }
+      \sstsubsection{
+         \htmlref{Region}{Region}:
+      }{
+         Regions always use active units if possible.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The ActiveUnit flag resembles a Frame attribute, except that it
+         cannot be tested or cleared, and it cannot be accessed using the
+         generic \htmlref{AST\_GET$<$X$>$}{AST\_GET$<$X$>$} and \htmlref{AST\_SET$<$X$>$}{AST\_SET$<$X$>$} routines.
+
+         \sstitem
+         The \htmlref{AST\_GETACTIVEUNIT}{AST\_GETACTIVEUNIT} routine can be used to retrieve the current
+         value of the ActiveUnit flag.
+      }
+   }
+}
+\sstroutine{
+   AST\_SETFITS$<$X$>$
+}{
+   Store a keyword value in a FitsChan
+}{
+   \sstdescription{
+      This is a family of routines which store values for named keywords
+      within a \htmlref{FitsChan}{FitsChan} at the current card position. The supplied keyword
+      value can either over-write an existing keyword value, or can be
+      inserted as a new header card into the FitsChan.
+
+      The keyword data type is selected by replacing $<$X$>$ in the routine name
+      by one of the following strings representing the recognised FITS data
+
+      types:
+
+      \sstitemlist{
+
+         \sstitem
+         CF - Complex floating point values.
+
+         \sstitem
+         CI - Complex integer values.
+
+         \sstitem
+         F  - Floating point values.
+
+         \sstitem
+         I  - Integer values.
+
+         \sstitem
+         L  - Logical (i.e. boolean) values.
+
+         \sstitem
+         S  - String values.
+
+         \sstitem
+         CN - A \texttt{"} CONTINUE\texttt{"}  value, these are treated like string values, but
+                are encoded without an equals sign.
+
+      }
+      The data type of the \texttt{"} value\texttt{"}  parameter depends on $<$X$>$ as follows:
+
+      \sstitemlist{
+
+         \sstitem
+         CF - DOUBLE PRECISION(2) (a 2 element array holding the real and
+                imaginary parts of the complex value).
+
+         \sstitem
+         CI - INTEGER(2) (a 2 element array holding the real and imaginary
+                parts of the complex value).
+
+         \sstitem
+         F  - DOUBLE PRECISION.
+
+         \sstitem
+         I  - INTEGER
+
+         \sstitem
+         L  - LOGICAL
+
+         \sstitem
+         S  - CHARACTER
+
+         \sstitem
+         CN - CHARACTER
+      }
+   }
+   \sstinvocation{
+      CALL AST\_SETFITS$<$X$>$( THIS, NAME, VALUE, COMMENT, OVERWRITE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string
+         containing the FITS keyword name. This may be a complete FITS
+         header card, in which case the keyword to use is extracted from
+         it. No more than 80 characters are read from this string.
+      }
+      \sstsubsection{
+         VALUE = $<$X$>$type (Given)
+      }{
+         The keyword value to store with the named keyword. The data type
+         of this parameter depends on $<$X$>$ as described above.
+      }
+      \sstsubsection{
+         COMMENT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A string
+         holding a comment to associated with the keyword.
+         If
+         a blank string is supplied, then any comment included in the string
+         supplied for the
+         NAME parameter is used instead. If NAME
+         contains no comment, then any existing comment in the card being
+         over-written is retained. Otherwise, no comment is stored with
+         the card.
+      }
+      \sstsubsection{
+         OVERWRITE = LOGICAL (Given)
+      }{
+         If .TRUE.,
+         the new card formed from the supplied keyword name, value and comment
+         string over-writes the current card, and the current card is
+         incremented to refer to the next card (see the \texttt{"} \htmlref{Card}{Card}\texttt{"}  attribute). If
+         .FALSE.,
+         the new card is inserted in front of the current card and the current
+         card is left unchanged. In either case, if the current card on entry
+         points to the \texttt{"} end-of-file\texttt{"} , the new card is appended to the end of
+         the list.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The
+         routine \htmlref{AST\_SETFITSU}{AST\_SETFITSU}
+         can be used to indicate that no value is associated with a keyword.
+
+         \sstitem
+         The
+         routine \htmlref{AST\_SETFITSCM}{AST\_SETFITSCM}
+         can be used to store a pure comment card (i.e. a card with a blank
+         keyword).
+
+         \sstitem
+         To assign a new value for an existing keyword within a FitsChan,
+         first find the card describing the keyword using \htmlref{AST\_FINDFITS}{AST\_FINDFITS}, and
+         then use one of the AST\_SETFITS$<$X$>$ family to over-write the old value.
+
+         \sstitem
+         If, on exit, there are no cards following the card written by
+         this routine, then the current card is left pointing at the
+         \texttt{"} end-of-file\texttt{"} .
+
+         \sstitem
+         An error will be reported if the keyword name does not conform
+         to FITS requirements.
+      }
+   }
+}
+\sstroutine{
+   AST\_SETFITSCM
+}{
+   Store a comment card in a FitsChan
+}{
+   \sstdescription{
+      This
+      routine
+      stores a comment card ( i.e. a card with no keyword name or equals
+      sign) within a \htmlref{FitsChan}{FitsChan} at the current card position. The new card
+      can either over-write an existing card, or can be inserted as a new
+      card into the FitsChan.
+   }
+   \sstinvocation{
+      CALL AST\_SETFITSCM( THIS, COMMENT, OVERWRITE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         COMMENT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A string
+         holding the text of the comment card.
+         If
+         a blank string is supplied, then a totally blank card is produced.
+      }
+      \sstsubsection{
+         OVERWRITE = LOGICAL (Given)
+      }{
+         If .TRUE.,
+         the new card over-writes the current card, and the current card is
+         incremented to refer to the next card (see the \texttt{"} \htmlref{Card}{Card}\texttt{"}  attribute). If
+         .FALSE.,
+         the new card is inserted in front of the current card and the current
+         card is left unchanged. In either case, if the current card on entry
+         points to the \texttt{"} end-of-file\texttt{"} , the new card is appended to the end of
+         the list.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If, on exit, there are no cards following the card written by
+         this function, then the current card is left pointing at the
+         \texttt{"} end-of-file\texttt{"} .
+      }
+   }
+}
+\sstroutine{
+   AST\_SETFITSU
+}{
+   Store an undefined keyword value in a FitsChan
+}{
+   \sstdescription{
+      This
+      routine
+      stores an undefined value for a named keyword within
+      a \htmlref{FitsChan}{FitsChan} at the current card position. The new undefined value
+      can either over-write an existing keyword value, or can be inserted
+      as a new header card into the FitsChan.
+   }
+   \sstinvocation{
+      CALL AST\_SETFITSU( THIS, NAME, COMMENT, OVERWRITE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string
+         containing the FITS keyword name. This may be a complete FITS
+         header card, in which case the keyword to use is extracted from
+         it. No more than 80 characters are read from this string.
+      }
+      \sstsubsection{
+         COMMENT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A string
+         holding a comment to associated with the keyword.
+         If
+         a blank string is supplied, then any comment included in the string
+         supplied for the
+         NAME parameter is used instead. If NAME
+         contains no comment, then any existing comment in the card being
+         over-written is retained. Otherwise, no comment is stored with
+         the card.
+      }
+      \sstsubsection{
+         OVERWRITE = LOGICAL (Given)
+      }{
+         If .TRUE.,
+         the new card formed from the supplied keyword name and comment
+         string over-writes the current card, and the current card is
+         incremented to refer to the next card (see the \texttt{"} \htmlref{Card}{Card}\texttt{"}  attribute). If
+         .FALSE.,
+         the new card is inserted in front of the current card and the current
+         card is left unchanged. In either case, if the current card on entry
+         points to the \texttt{"} end-of-file\texttt{"} , the new card is appended to the end of
+         the list.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If, on exit, there are no cards following the card written by
+         this function, then the current card is left pointing at the
+         \texttt{"} end-of-file\texttt{"} .
+
+         \sstitem
+         An error will be reported if the keyword name does not conform
+         to FITS requirements.
+      }
+   }
+}
+\sstroutine{
+   AST\_SETREFPOS
+}{
+   Set the reference position in a specified celestial coordinate system
+}{
+   \sstdescription{
+      This routine
+      sets the reference position (see attributes \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec}) using
+      axis values (in radians) supplied within the celestial coordinate
+      system represented by a supplied \htmlref{SkyFrame}{SkyFrame}.
+   }
+   \sstinvocation{
+      CALL AST\_SETREFPOS( THIS, FRM, LON, LAT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the \htmlref{SpecFrame}{SpecFrame}.
+      }
+      \sstsubsection{
+         FRM = INTEGER (Given)
+      }{
+         Pointer to the SkyFrame which defines the celestial coordinate
+         system in which the longitude and latitude values are supplied.
+         If AST\_\_NULL
+         is supplied, then the supplied longitude and latitude values are
+         assumed to be FK5 J2000 RA and Dec values.
+      }
+      \sstsubsection{
+         LON = DOUBLE PRECISION (Given)
+      }{
+         The longitude of the reference point, in the coordinate system
+         represented by the supplied SkyFrame (radians).
+      }
+      \sstsubsection{
+         LAT = DOUBLE PRECISION (Given)
+      }{
+         The latitude of the reference point, in the coordinate system
+         represented by the supplied SkyFrame (radians).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_SETUNC
+}{
+   Store uncertainty information in a Region
+}{
+   \sstdescription{
+      Each \htmlref{Region}{Region} (of any class) can have an \texttt{"} uncertainty\texttt{"}  which specifies
+      the uncertainties associated with the boundary of the Region. This
+      information is supplied in the form of a second Region. The uncertainty
+      in any point on the boundary of a Region is found by shifting the
+      associated \texttt{"} uncertainty\texttt{"}  Region so that it is centred at the boundary
+      point being considered. The area covered by the shifted uncertainty
+      Region then represents the uncertainty in the boundary position.
+      The uncertainty is assumed to be the same for all points.
+
+      The uncertainty is usually specified when the Region is created, but
+      this
+      routine
+      allows it to be changed at any time.
+   }
+   \sstinvocation{
+      CALL AST\_SETUNC( THIS, UNC, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Region which is to be assigned a new uncertainty.
+      }
+      \sstsubsection{
+         UNC = INTEGER (Given)
+      }{
+         Pointer to the new uncertainty Region. This must be of a class for
+         which all instances are centro-symetric (e.g. \htmlref{Box}{Box}, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse},
+         etc.) or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions.
+         A deep copy of the supplied Region will be taken, so subsequent
+         changes to the uncertainty Region using the supplied pointer will
+         have no effect on the Region
+         THIS.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_SHIFTMAP
+}{
+   Create a ShiftMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{ShiftMap}{ShiftMap} and optionally initialises its
+      attributes.
+
+      A ShiftMap is a linear \htmlref{Mapping}{Mapping} which shifts each axis by a
+      specified constant value.
+   }
+   \sstinvocation{
+      RESULT = AST\_SHIFTMAP( NCOORD, SHIFT, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NCOORD = INTEGER (Given)
+      }{
+         The number of coordinate values for each point to be
+         transformed (i.e. the number of dimensions of the space in
+         which the points will reside). The same number is applicable
+         to both input and output points.
+      }
+      \sstsubsection{
+         SHIFT( NCOORD ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing the values to be added on to the input
+         coordinates in order to create the output coordinates. A separate
+         value should be supplied for each coordinate.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new ShiftMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SHIFTMAP = INTEGER
+      }{
+         A pointer to the new ShiftMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_SHOW
+}{
+   Display a textual representation of an Object on standard output
+}{
+   \sstdescription{
+      This routine displays a textual description of any AST \htmlref{Object}{Object}
+      on standard output. It is provided primarily as an aid to
+      debugging.
+   }
+   \sstinvocation{
+      CALL AST\_SHOW( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Object to be displayed.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+}
+\sstroutine{
+   AST\_SHOWFITS
+}{
+   Display the contents of a FitsChan on standard output
+}{
+   \sstdescription{
+      This routine
+      formats and displays all the cards in a \htmlref{FitsChan}{FitsChan} on standard output.
+   }
+   \sstinvocation{
+      CALL AST\_SHOWFITS( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_SHOWMESH
+}{
+   Display a mesh of points covering the surface of a Region
+}{
+   \sstdescription{
+      This routine
+      writes a table to standard output containing the axis values at a
+      mesh of points covering the surface of the supplied \htmlref{Region}{Region}. Each row
+      of output contains a tab-separated list of axis values, one for
+      each axis in the \htmlref{Frame}{Frame} encapsulated by the Region. The number of
+      points in the mesh is determined by the \htmlref{MeshSize}{MeshSize} attribute.
+
+      The table is preceded by a given title string, and followed by a
+      single line containing the word \texttt{"} ENDMESH\texttt{"} .
+   }
+   \sstinvocation{
+      CALL AST\_SHOWMESH( THIS, FORMAT, TTL, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         FORMAT = LOGICAL (Given)
+      }{
+         A boolean value indicating if the displayed axis values should
+         be formatted according to the Format attribute associated with
+         the Frame\texttt{'} s axis. Otherwise, they are displayed as simple
+         floating point values.
+      }
+      \sstsubsection{
+         TTL = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A title to display before displaying the first position.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+}
+\sstroutine{
+   AST\_SIMPLIFY
+}{
+   Simplify a Mapping
+}{
+   \sstdescription{
+      This function simplifies a \htmlref{Mapping}{Mapping} (which may be a compound
+      Mapping such as a \htmlref{CmpMap}{CmpMap}) to eliminate redundant computational
+      steps, or to merge separate steps which can be performed more
+      efficiently in a single operation.
+
+      As a simple example, a Mapping which multiplied coordinates by
+      5, and then multiplied the result by 10, could be simplified to
+      a single step which multiplied by 50. Similarly, a Mapping which
+      multiplied by 5, and then divided by 5, could be reduced to a
+      simple copying operation.
+
+      This function should typically be applied to Mappings which have
+      undergone substantial processing or have been formed by merging
+      other Mappings. It is of potential benefit, for example, in
+      reducing execution time if applied before using a Mapping to
+      transform a large number of coordinates.
+   }
+   \sstinvocation{
+      RESULT = AST\_SIMPLIFY( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the original Mapping.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         This function applies to all Mappings.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         If the supplied Mapping is a FrameSet, the returned Mapping
+         will be a copy of the supplied FrameSet in which all the
+         inter-\htmlref{Frame}{Frame} Mappings have been simplified.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SIMPLIFY = INTEGER
+      }{
+         A new pointer to the (possibly simplified) Mapping.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Mappings that have a set value for their \htmlref{Ident}{Ident} attribute are
+         left unchanged after simplification. This is so that their
+         individual identity is preserved. This restriction does not
+         apply to the simplification of Frames.
+
+         \sstitem
+         This function can safely be applied even to Mappings which
+         cannot be simplified. If no simplification is possible, it
+         behaves exactly like \htmlref{AST\_CLONE}{AST\_CLONE} and returns a pointer to the
+         original Mapping.
+
+         \sstitem
+         The Mapping returned by this function may not be independent
+         of the original (even if simplification was possible), and
+         modifying it may therefore result in indirect modification of
+         the original. If a completely independent result is required, a
+         copy should be made using \htmlref{AST\_COPY}{AST\_COPY}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_SKYFRAME
+}{
+   Create a SkyFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SkyFrame}{SkyFrame} and optionally initialises
+      its attributes.
+
+      A SkyFrame is a specialised form of \htmlref{Frame}{Frame} which describes
+      celestial longitude/latitude coordinate systems. The particular
+      celestial coordinate system to be represented is specified by
+      setting the SkyFrame\texttt{'} s \htmlref{System}{System} attribute (currently, the default
+      is ICRS) qualified, as necessary, by a mean \htmlref{Equinox}{Equinox} value and/or
+      an \htmlref{Epoch}{Epoch}.
+
+      For each of the supported celestial coordinate systems, a SkyFrame
+      can apply an optional shift of origin to create a coordinate system
+      representing offsets within the celestial coordinate system from some
+      specified point. This offset coordinate system can also be rotated to
+      define new longitude and latitude axes. See attributes SkyRef, \htmlref{SkyRefIs}{SkyRefIs}
+      and SkyRefP
+
+      All the coordinate values used by a SkyFrame are in
+      radians. These may be formatted in more conventional ways for
+      display by using \htmlref{AST\_FORMAT}{AST\_FORMAT}.
+   }
+   \sstinvocation{
+      RESULT = AST\_SKYFRAME( OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new SkyFrame. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SKYFRAME = INTEGER
+      }{
+         A pointer to the new SkyFrame.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         FRAME = AST\_SKYFRAME( \texttt{'}  \texttt{'} , STATUS )
+      }{
+         Creates a SkyFrame to describe the default ICRS celestial
+         coordinate system.
+      }
+      \sstexamplesubsection{
+         FRAME = AST\_SKYFRAME( \texttt{'} System = FK5, Equinox = J2005, Digits = 10\texttt{'} , STATUS )
+      }{
+         Creates a SkyFrame to describe the FK5 celestial
+         coordinate system, with a mean Equinox of J2005.0.
+         Because especially accurate coordinates will be used,
+         additional precision (10 digits) has been requested. This will
+         be used when coordinate values are formatted for display.
+      }
+      \sstexamplesubsection{
+         FRAME = AST\_SKYFRAME( \texttt{'} System = FK4, Equinox = 1955-SEP-2\texttt{'} , STATUS )
+      }{
+         Creates a SkyFrame to describe the old FK4 celestial
+         coordinate system.  A default Epoch value (B1950.0) is used,
+         but the mean Equinox value is given explicitly as \texttt{"} 1955-SEP-2\texttt{"} .
+      }
+      \sstexamplesubsection{
+         FRAME = AST\_SKYFRAME( \texttt{'} System = GAPPT, Epoch = \texttt{'}  // DATE, STATUS )
+      }{
+         Creates a SkyFrame to describe the Geocentric Apparent
+         celestial coordinate system. The Epoch value, which specifies
+         the date of observation, is obtained from a date/time string
+         contained in the character variable DATE.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Currently, the default celestial coordinate system is
+         ICRS. However, this default may change in future as new
+         astrometric standards evolve. The intention is to track the most
+         modern appropriate standard. For this reason, you should use the
+         default only if this is what you intend (and can tolerate any
+         associated slight change in behaviour with future versions of
+         this function). If you intend to use the ICRS system
+         indefinitely, then you should specify it explicitly using an
+         OPTIONS value of \texttt{"} System=ICRS\texttt{"} .
+
+         \sstitem
+         Whichever celestial coordinate system is represented, it will
+         have two axes.  The first of these will be the longitude axis
+         and the second will be the latitude axis. This order can be
+         changed using \htmlref{AST\_PERMAXES}{AST\_PERMAXES} if required.
+
+         \sstitem
+         When conversion between two SkyFrames is requested (as when
+         supplying SkyFrames \htmlref{AST\_CONVERT}{AST\_CONVERT}),
+         account will be taken of the nature of the celestial coordinate
+         systems they represent, together with any qualifying mean Equinox or
+         Epoch values, etc. The \htmlref{AlignSystem}{AlignSystem} attribute will also be taken into
+         account. The results will therefore fully reflect the
+         relationship between positions on the sky measured in the two
+         systems.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_SKYOFFSETMAP
+}{
+   Returns a Mapping which goes from absolute coordinates to offset
+   coordinates
+}{
+   \sstdescription{
+      This function returns a \htmlref{Mapping}{Mapping} in which the forward transformation
+      transforms a position in the coordinate system given by the \htmlref{System}{System}
+      attribute of the supplied \htmlref{SkyFrame}{SkyFrame}, into the offset coordinate system
+      specified by the SkyRef, SkyRefP and \htmlref{SkyRefIs}{SkyRefIs} attributes of the
+      supplied SkyFrame.
+
+      A \htmlref{UnitMap}{UnitMap} is returned if the SkyFrame does not define an offset
+      coordinate system.
+   }
+   \sstinvocation{
+      RESULT = AST\_SKYOFFSETMAP( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the SkyFrame.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SKYOFFSETMAP = INTEGER
+      }{
+         Pointer to the returned Mapping.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_SLAADD
+}{
+   Add a celestial coordinate conversion to an SlaMap
+}{
+   \sstdescription{
+      This routine adds one of the standard celestial coordinate
+      system conversions provided by the SLALIB Positional Astronomy
+      Library (Starlink User Note SUN/67) to an existing \htmlref{SlaMap}{SlaMap}.
+
+      When an SlaMap is first created (using \htmlref{AST\_SLAMAP}{AST\_SLAMAP}), it simply
+      performs a unit (null) \htmlref{Mapping}{Mapping}. By using AST\_SLAADD (repeatedly
+      if necessary), one or more coordinate conversion steps may then
+      be added, which the SlaMap will perform in sequence. This allows
+      multi-step conversions between a variety of celestial coordinate
+      systems to be assembled out of the building blocks provided by
+      SLALIB.
+
+      Normally, if an SlaMap\texttt{'} s \htmlref{Invert}{Invert} attribute is zero (the default),
+      then its forward transformation is performed by carrying out
+      each of the individual coordinate conversions specified by
+      AST\_SLAADD in the order given (i.e. with the most recently added
+      conversion applied last).
+
+      This order is reversed if the SlaMap\texttt{'} s Invert attribute is
+      non-zero (or if the inverse transformation is requested by any
+      other means) and each individual coordinate conversion is also
+      replaced by its own inverse. This process inverts the overall
+      effect of the SlaMap. In this case, the first conversion to be
+      applied would be the inverse of the one most recently added.
+   }
+   \sstinvocation{
+      CALL AST\_SLAADD( THIS, CVT, NARG, ARGS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the SlaMap.
+      }
+      \sstsubsection{
+         CVT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string which identifies the
+         celestial coordinate conversion to be added to the
+         SlaMap. See the \texttt{"} SLALIB Conversions\texttt{"}  section for details of
+         those available.
+      }
+      \sstsubsection{
+         NARG = INTEGER (Given)
+      }{
+         The number of argument values supplied in the
+         ARGS array.
+      }
+      \sstsubsection{
+         ARGS( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing argument values for the celestial
+         coordinate conversion. The number of arguments required, and
+         hence the number of array elements used, depends on the
+         conversion specified (see the \texttt{"} SLALIB Conversions\texttt{"}
+         section). This array is ignored
+         if no arguments are needed.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         All coordinate values processed by an SlaMap are in
+         radians. The first coordinate is the celestial longitude and the
+         second coordinate is the celestial latitude.
+
+         \sstitem
+         When assembling a multi-stage conversion, it can sometimes be
+         difficult to determine the most economical conversion path. For
+         example, converting to the standard FK5 coordinate system as an
+         intermediate stage is often sensible in formulating the problem,
+         but may introduce unnecessary extra conversion steps. A solution
+         to this is to include all the steps which are (logically)
+         necessary, but then to use \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY} to simplify the resulting
+         SlaMap. The simplification process will eliminate any steps
+         which turn out not to be needed.
+
+         \sstitem
+         This routine does not check to ensure that the sequence of
+         coordinate conversions added to an SlaMap is physically
+         meaningful.
+      }
+   }
+   \sstdiytopic{
+      SLALIB Conversions
+   }{
+      The following strings (which are case-insensitive) may be supplied
+      via the CVT argument to indicate which celestial coordinate
+      conversion is to be added to the SlaMap. Each string is derived
+      from the name of the SLALIB routine that performs the
+      conversion and the relevant documentation (SUN/67) should be
+      consulted for details.  Where arguments are needed by
+      the conversion, they are listed in parentheses. Values for
+      these arguments should be given, via the ARGS array, in the
+      order indicated. The argument names match the corresponding
+      SLALIB routine arguments and their values should be given using
+      exactly the same units, time scale, calendar, etc. as described
+      in SUN/67:
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} ADDET\texttt{"}  (EQ): Add E-terms of aberration.
+
+         \sstitem
+         \texttt{"} SUBET\texttt{"}  (EQ): Subtract E-terms of aberration.
+
+         \sstitem
+         \texttt{"} PREBN\texttt{"}  (BEP0,BEP1): Apply Bessel-Newcomb pre-IAU 1976 (FK4)
+         precession model.
+
+         \sstitem
+         \texttt{"} PREC\texttt{"}  (EP0,EP1): Apply IAU 1975 (FK5) precession model.
+
+         \sstitem
+         \texttt{"} FK45Z\texttt{"}  (BEPOCH): Convert FK4 to FK5 (no proper motion or parallax).
+
+         \sstitem
+         \texttt{"} FK54Z\texttt{"}  (BEPOCH): Convert FK5 to FK4 (no proper motion or parallax).
+
+         \sstitem
+         \texttt{"} AMP\texttt{"}  (DATE,EQ): Convert geocentric apparent to mean place.
+
+         \sstitem
+         \texttt{"} MAP\texttt{"}  (EQ,DATE): Convert mean place to geocentric apparent.
+
+         \sstitem
+         \texttt{"} ECLEQ\texttt{"}  (DATE): Convert ecliptic coordinates to FK5 J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} EQECL\texttt{"}  (DATE): Convert equatorial FK5 J2000.0 to ecliptic coordinates.
+
+         \sstitem
+         \texttt{"} GALEQ\texttt{"} : Convert galactic coordinates to FK5 J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} EQGAL\texttt{"} : Convert FK5 J2000.0 equatorial to galactic coordinates.
+
+         \sstitem
+         \texttt{"} HFK5Z\texttt{"}  (JEPOCH): Convert ICRS coordinates to FK5 J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} FK5HZ\texttt{"}  (JEPOCH): Convert FK5 J2000.0 equatorial coordinates to ICRS.
+
+         \sstitem
+         \texttt{"} GALSUP\texttt{"} : Convert galactic to supergalactic coordinates.
+
+         \sstitem
+         \texttt{"} SUPGAL\texttt{"} : Convert supergalactic coordinates to galactic.
+
+         \sstitem
+         \texttt{"} J2000H\texttt{"} : Convert dynamical J2000.0 to ICRS.
+
+         \sstitem
+         \texttt{"} HJ2000\texttt{"} : Convert ICRS to dynamical J2000.0.
+
+         \sstitem
+         \texttt{"} R2H\texttt{"}  (LAST): Convert RA to Hour Angle.
+
+         \sstitem
+         \texttt{"} H2R\texttt{"}  (LAST): Convert Hour Angle to RA.
+
+      }
+      For example, to use the \texttt{"} ADDET\texttt{"}  conversion, which takes a single
+      argument EQ, you should consult the documentation for the SLALIB
+      routine SLA\_ADDET. This describes the conversion in detail and
+      shows that EQ is the Besselian epoch of the mean equator and
+      equinox.
+      This value should then be supplied to AST\_SLAADD in ARGS(1).
+
+      In addition the following strings may be supplied for more complex
+      conversions which do not correspond to any one single SLALIB routine
+      (DIURAB is the magnitude of the diurnal aberration vector in units
+      of \texttt{"} day/(2.PI)\texttt{"} , DATE is the Modified Julian Date of the observation,
+      and (OBSX,OBSY,OBZ) are the Heliocentric-Aries-Ecliptic cartesian
+      coordinates, in metres, of the observer):
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} HPCEQ\texttt{"}  (DATE,OBSX,OBSY,OBSZ): Convert Helioprojective-Cartesian coordinates to J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} EQHPC\texttt{"}  (DATE,OBSX,OBSY,OBSZ): Convert J2000.0 equatorial coordinates to Helioprojective-Cartesian.
+
+         \sstitem
+         \texttt{"} HPREQ\texttt{"}  (DATE,OBSX,OBSY,OBSZ): Convert Helioprojective-Radial coordinates to J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} EQHPR\texttt{"}  (DATE,OBSX,OBSY,OBSZ): Convert J2000.0 equatorial coordinates to Helioprojective-Radial.
+
+         \sstitem
+         \texttt{"} HEEQ\texttt{"}  (DATE): Convert helio-ecliptic coordinates to J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} EQHE\texttt{"}  (DATE): Convert J2000.0 equatorial coordinates to helio-ecliptic.
+
+         \sstitem
+         \texttt{"} H2E\texttt{"}  (LAT,DIRUAB): Convert horizon coordinates to equatorial.
+
+         \sstitem
+         \texttt{"} E2H\texttt{"}  (LAT,DIURAB): Convert equatorial coordinates to horizon.
+
+      }
+      Note, the \texttt{"} H2E\texttt{"}  and \texttt{"} E2H\texttt{"}  conversions convert between topocentric
+      horizon coordinates (azimuth,elevation), and apparent local equatorial
+      coordinates (hour angle,declination). Thus, the effects of diurnal
+      aberration are taken into account in the conversions but the effects
+      of atmospheric refraction are not.
+   }
+}
+\sstroutine{
+   AST\_SLAMAP
+}{
+   Create an SlaMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SlaMap}{SlaMap} and optionally initialises
+      its attributes.
+
+      An SlaMap is a specialised form of \htmlref{Mapping}{Mapping} which can be used to
+      represent a sequence of conversions between standard celestial
+      (longitude, latitude) coordinate systems.
+
+      When an SlaMap is first created, it simply performs a unit
+      (null) Mapping on a pair of coordinates. Using the \htmlref{AST\_SLAADD}{AST\_SLAADD}
+      routine, a series of coordinate conversion steps may then be
+      added, selected from those provided by the SLALIB Positional
+      Astronomy Library (Starlink User Note SUN/67). This allows
+      multi-step conversions between a variety of celestial coordinate
+      systems to be assembled out of the building blocks provided by
+      SLALIB.
+
+      For details of the individual coordinate conversions available,
+      see the description of the AST\_SLAADD routine.
+   }
+   \sstinvocation{
+      RESULT = AST\_SLAMAP( FLAGS, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FLAGS = INTEGER (Given)
+      }{
+         This argument is reserved for future use and should currently
+         always be set to zero.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new SlaMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SLAMAP = INTEGER
+      }{
+         A pointer to the new SlaMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes (number of input and output
+         coordinates) for an SlaMap are both equal to 2. The first
+         coordinate is the celestial longitude and the second coordinate
+         is the celestial latitude. All coordinate values are in radians.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_SPECADD
+}{
+   Add a spectral coordinate conversion to a SpecMap
+}{
+   \sstdescription{
+      This routine adds one of the standard spectral coordinate
+      system conversions listed below to an existing \htmlref{SpecMap}{SpecMap}.
+
+      When a SpecMap is first created (using \htmlref{AST\_SPECMAP}{AST\_SPECMAP}), it simply
+      performs a unit (null) \htmlref{Mapping}{Mapping}. By using AST\_SPECADD (repeatedly
+      if necessary), one or more coordinate conversion steps may then
+      be added, which the SpecMap will perform in sequence. This allows
+      multi-step conversions between a variety of spectral coordinate
+      systems to be assembled out of the building blocks provided by
+      this class.
+
+      Normally, if a SpecMap\texttt{'} s \htmlref{Invert}{Invert} attribute is zero (the default),
+      then its forward transformation is performed by carrying out
+      each of the individual coordinate conversions specified by
+      AST\_SPECADD in the order given (i.e. with the most recently added
+      conversion applied last).
+
+      This order is reversed if the SpecMap\texttt{'} s Invert attribute is
+      non-zero (or if the inverse transformation is requested by any
+      other means) and each individual coordinate conversion is also
+      replaced by its own inverse. This process inverts the overall
+      effect of the SpecMap. In this case, the first conversion to be
+      applied would be the inverse of the one most recently added.
+   }
+   \sstinvocation{
+      CALL AST\_SPECADD( THIS, CVT, NARG, ARGS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the SpecMap.
+      }
+      \sstsubsection{
+         CVT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string which identifies the
+         spectral coordinate conversion to be added to the
+         SpecMap. See the \texttt{"} Available Conversions\texttt{"}  section for details of
+         those available.
+      }
+      \sstsubsection{
+         NARG = INTEGER (Given)
+      }{
+         The number of argument values supplied in the
+         ARGS array.
+      }
+      \sstsubsection{
+         ARGS( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing argument values for the spectral
+         coordinate conversion. The number of arguments required, and
+         hence the number of array elements used, depends on the
+         conversion specified (see the \texttt{"} Available Conversions\texttt{"}
+         section). This array is ignored
+         if no arguments are needed.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When assembling a multi-stage conversion, it can sometimes be
+         difficult to determine the most economical conversion path. For
+         example, when converting between reference frames, converting first
+         to the heliographic reference frame as an intermediate stage is often
+         sensible in formulating the problem, but may introduce unnecessary
+         extra conversion steps. A solution to this is to include all the steps
+         which are (logically) necessary, but then to use
+         \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY} to simplify the resulting
+         SpecMap. The simplification process will eliminate any steps
+         which turn out not to be needed.
+
+         \sstitem
+         This routine does not check to ensure that the sequence of
+         coordinate conversions added to a SpecMap is physically
+         meaningful.
+      }
+   }
+   \sstdiytopic{
+      Available Conversions
+   }{
+      The following strings (which are case-insensitive) may be supplied
+      via the CVT argument to indicate which spectral coordinate
+      conversion is to be added to the SpecMap. Where arguments are needed by
+      the conversion, they are listed in parentheses. Values for
+      these arguments should be given, via the ARGS array, in the
+      order indicated. Units and argument names are described at the end of
+      the list of conversions.
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} FRTOVL\texttt{"}  (RF): Convert frequency to relativistic velocity.
+
+         \sstitem
+         \texttt{"} VLTOFR\texttt{"}  (RF): Convert relativistic velocity to Frequency.
+
+         \sstitem
+         \texttt{"} ENTOFR\texttt{"} : Convert energy to frequency.
+
+         \sstitem
+         \texttt{"} FRTOEN\texttt{"} : Convert frequency to energy.
+
+         \sstitem
+         \texttt{"} WNTOFR\texttt{"} : Convert wave number to frequency.
+
+         \sstitem
+         \texttt{"} FRTOWN\texttt{"} : Convert frequency to wave number.
+
+         \sstitem
+         \texttt{"} WVTOFR\texttt{"} : Convert wavelength (vacuum) to frequency.
+
+         \sstitem
+         \texttt{"} FRTOWV\texttt{"} : Convert frequency to wavelength (vacuum).
+
+         \sstitem
+         \texttt{"} AWTOFR\texttt{"} : Convert wavelength (air) to frequency.
+
+         \sstitem
+         \texttt{"} FRTOAW\texttt{"} : Convert frequency to wavelength (air).
+
+         \sstitem
+         \texttt{"} VRTOVL\texttt{"} : Convert radio to relativistic velocity.
+
+         \sstitem
+         \texttt{"} VLTOVR\texttt{"} : Convert relativistic to radio velocity.
+
+         \sstitem
+         \texttt{"} VOTOVL\texttt{"} : Convert optical to relativistic velocity.
+
+         \sstitem
+         \texttt{"} VLTOVO\texttt{"} : Convert relativistic to optical velocity.
+
+         \sstitem
+         \texttt{"} ZOTOVL\texttt{"} : Convert redshift to relativistic velocity.
+
+         \sstitem
+         \texttt{"} VLTOZO\texttt{"} : Convert relativistic velocity to redshift.
+
+         \sstitem
+         \texttt{"} BTTOVL\texttt{"} : Convert beta factor to relativistic velocity.
+
+         \sstitem
+         \texttt{"} VLTOBT\texttt{"} : Convert relativistic velocity to beta factor.
+
+         \sstitem
+         \texttt{"} USF2HL\texttt{"}  (VOFF,RA,DEC): Convert frequency from a user-defined
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2US\texttt{"}  (VOFF,RA,DEC): Convert frequency from heliocentric
+         reference frame to user-defined.
+
+         \sstitem
+         \texttt{"} TPF2HL\texttt{"}  (OBSLON,OBSLAT,OBSALT,EPOCH,RA,DEC): Convert frequency from
+         topocentric reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2TP\texttt{"}  (OBSLON,OBSLAT,OBSALT,EPOCH,RA,DEC): Convert frequency from
+         heliocentric reference frame to topocentric.
+
+         \sstitem
+         \texttt{"} GEF2HL\texttt{"}  (EPOCH,RA,DEC): Convert frequency from geocentric
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2GE\texttt{"}  (EPOCH,RA,DEC): Convert frequency from
+         heliocentric reference frame to geocentric.
+
+         \sstitem
+         \texttt{"} BYF2HL\texttt{"}  (EPOCH,RA,DEC): Convert frequency from
+         barycentric reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2BY\texttt{"}  (EPOCH,RA,DEC): Convert frequency from
+         heliocentric reference frame to barycentric.
+
+         \sstitem
+         \texttt{"} LKF2HL\texttt{"}  (RA,DEC): Convert frequency from kinematic LSR
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2LK\texttt{"}  (RA,DEC): Convert frequency from heliocentric
+         reference frame to kinematic LSR.
+
+         \sstitem
+         \texttt{"} LDF2HL\texttt{"}  (RA,DEC): Convert frequency from dynamical LSR
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2LD\texttt{"}  (RA,DEC): Convert frequency from heliocentric
+         reference frame to dynamical LSR.
+
+         \sstitem
+         \texttt{"} LGF2HL\texttt{"}  (RA,DEC): Convert frequency from local group
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2LG\texttt{"}  (RA,DEC): Convert frequency from heliocentric
+         reference frame to local group.
+
+         \sstitem
+         \texttt{"} GLF2HL\texttt{"}  (RA,DEC): Convert frequency from galactic
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2GL\texttt{"}  (RA,DEC): Convert frequency from heliocentric
+         reference frame to galactic.
+
+      }
+      The units for the values processed by the above conversions are as
+      follows:
+
+      \sstitemlist{
+
+         \sstitem
+         all velocities: metres per second (positive if the source receeds from
+           the observer).
+
+         \sstitem
+         frequency: Hertz.
+
+         \sstitem
+         all wavelengths: metres.
+
+         \sstitem
+         energy: Joules.
+
+         \sstitem
+         wave number: cycles per metre.
+
+      }
+      The arguments used in the above conversions are as follows:
+
+      \sstitemlist{
+
+         \sstitem
+         RF: Rest frequency (Hz).
+
+         \sstitem
+         OBSALT: Geodetic altitude of observer (IAU 1975, metres).
+
+         \sstitem
+         OBSLAT: Geodetic latitude of observer (IAU 1975, radians).
+
+         \sstitem
+         OBSLON: Longitude of observer (radians - positive eastwards).
+
+         \sstitem
+         EPOCH: \htmlref{Epoch}{Epoch} of observation (UT1 expressed as a Modified Julian Date).
+
+         \sstitem
+         RA: Right Ascension of source (radians, FK5 J2000).
+
+         \sstitem
+         DEC: Declination of source (radians, FK5 J2000).
+
+         \sstitem
+         VOFF: Velocity of the user-defined reference frame, towards the
+         position given by RA and DEC, measured in the heliocentric
+         reference frame.
+
+      }
+      If the SpecMap is 3-dimensional, source positions are provided by the
+      values supplied to inputs 2 and 3 of the SpecMap (which are simply
+      copied to outputs 2 and 3). Note, usable values are still required
+      for the RA and DEC arguments in order to define the \texttt{"} user-defined\texttt{"}
+      reference frame used by USF2HL and HLF2US. However, AST\_\_BAD can be
+      supplied for RA and DEC if the user-defined reference frame is not
+      required.
+   }
+}
+\sstroutine{
+   AST\_SPECFLUXFRAME
+}{
+   Create a SpecFluxFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SpecFluxFrame}{SpecFluxFrame} and optionally initialises
+      its attributes.
+
+      A SpecFluxFrame combines a \htmlref{SpecFrame}{SpecFrame} and a \htmlref{FluxFrame}{FluxFrame} into a single
+      2-dimensional compound \htmlref{Frame}{Frame}. Such a Frame can for instance be used
+      to describe a \htmlref{Plot}{Plot} of a spectrum in which the first axis represents
+      spectral position and the second axis represents flux.
+   }
+   \sstinvocation{
+      RESULT = AST\_SPECFLUXFRAME( FRAME1, FRAME2, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FRAME1 = INTEGER (Given)
+      }{
+         Pointer to the SpecFrame. This will form the first axis in the
+         new SpecFluxFrame.
+      }
+      \sstsubsection{
+         FRAME2 = INTEGER (Given)
+      }{
+         Pointer to the FluxFrame. This will form the second axis in the
+         new SpecFluxFrame. The \texttt{"} \htmlref{SpecVal}{SpecVal}\texttt{"}  attribute of this FluxFrame is
+         not used by the SpecFluxFrame class and so may be set to AST\_\_BAD
+         when the FluxFrame is created.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new SpecFluxFrame. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SPECFLUXFRAME = INTEGER
+      }{
+         A pointer to the new SpecFluxFrame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The supplied Frame pointers are stored directly, rather than
+         being used to create deep copies of the supplied Frames. This means
+         that any subsequent changes made to the Frames via the supplied
+         pointers will result in equivalent changes being visible in the
+         SpecFluxFrame.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_SPECFRAME
+}{
+   Create a SpecFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SpecFrame}{SpecFrame} and optionally initialises
+      its attributes.
+
+      A SpecFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various coordinate systems used to describe positions within
+      an electro-magnetic spectrum. The particular coordinate system to be
+      used is specified by setting the SpecFrame\texttt{'} s \htmlref{System}{System} attribute (the
+      default is wavelength) qualified, as necessary, by other attributes
+      such as the rest frequency, the standard of rest, the epoch of
+      observation, etc (see the description of the System attribute for
+      details).
+
+      By setting a value for thr \htmlref{SpecOrigin}{SpecOrigin} attribute, a SpecFrame can be made
+      to represent offsets from a given spectral position, rather than absolute
+   }
+   \sstinvocation{
+      RESULT = AST\_SPECFRAME( OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new SpecFrame. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SPECFRAME = INTEGER
+      }{
+         A pointer to the new SpecFrame.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         FRAME = AST\_SPECFRAME( \texttt{'}  \texttt{'} , STATUS )
+      }{
+         Creates a SpecFrame to describe the default wavelength spectral
+         coordinate system. The \htmlref{RestFreq}{RestFreq} attribute (rest frequency) is
+         unspecified, so it will not be possible to align this SpecFrame
+         with another SpecFrame on the basis of a velocity-based system. The
+         standard of rest is also unspecified. This means that alignment
+         will be possible with other SpecFrames, but no correction will be
+         made for Doppler shift caused by change of rest frame during the
+         alignment.
+      }
+      \sstexamplesubsection{
+         FRAME = AST\_SPECFRAME( \texttt{'} System=VELO, RestFreq=1.0E15, \htmlref{StdOfRest}{StdOfRest}=LSRK\texttt{'} , STATUS )
+      }{
+         Creates a SpecFrame describing a apparent radial velocity (\texttt{"} VELO\texttt{"} ) axis
+         with rest frequency 1.0E15 Hz (about 3000 Angstroms), measured
+         in the kinematic Local Standard of Rest (\texttt{"} LSRK\texttt{"} ). Since the
+         source position has not been specified (using attributes \htmlref{RefRA}{RefRA} and
+         \htmlref{RefDec}{RefDec}), it will only be possible to align this SpecFrame with
+         other SpecFrames which are also measured in the LSRK standard of
+         rest.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When conversion between two SpecFrames is requested (as when
+         supplying SpecFrames \htmlref{AST\_CONVERT}{AST\_CONVERT}),
+         account will be taken of the nature of the spectral coordinate systems
+         they represent, together with any qualifying rest frequency, standard
+         of rest, epoch values, etc. The \htmlref{AlignSystem}{AlignSystem} and \htmlref{AlignStdOfRest}{AlignStdOfRest}
+         attributes will also be taken into account. The results will therefore
+         fully reflect the relationship between positions measured in the two
+         systems. In addition, any difference in the Unit attributes of the two
+         systems will also be taken into account.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_SPECMAP
+}{
+   Create a SpecMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SpecMap}{SpecMap} and optionally initialises
+      its attributes.
+
+      An SpecMap is a specialised form of \htmlref{Mapping}{Mapping} which can be used to
+      represent a sequence of conversions between standard spectral
+      coordinate systems. This includes conversions between frequency,
+      wavelength, and various forms of velocity, as well as conversions
+      between different standards of rest.
+
+      When a SpecMap is first created, it simply performs a unit
+      (null) Mapping. Using the \htmlref{AST\_SPECADD}{AST\_SPECADD} routine,
+      a series of coordinate conversion steps may then be added, selected
+      from the list of supported conversions. This allows multi-step
+      conversions between a variety of spectral coordinate systems to
+      be assembled out of the building blocks provided by this class.
+
+      For details of the individual coordinate conversions available,
+      see the description of the AST\_SPECADD routine.
+
+      Conversions are available to transform between standards of rest.
+      Such conversions need to know the source position as an RA and DEC.
+      This information can be supplied in the form of parameters for
+      the relevant conversions, in which case the SpecMap is 1-dimensional,
+      simply transforming the spectral axis values. This means that the
+      same source position will always be used by the SpecMap. However, this
+      may not be appropriate for an accurate description of a 3-D spectral
+      cube, where changes of spatial position can produce significant
+      changes in the Doppler shift introduced when transforming between
+      standards of rest. For this situation, a 3-dimensional SpecMap can
+      be created in which axes 2 and 3 correspond to the source RA and DEC
+      The SpecMap simply copies values for axes 2 and 3 from input to
+      output).
+   }
+   \sstinvocation{
+      RESULT = AST\_SPECMAP( NIN, FLAGS, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NIN = INTEGER (Given)
+      }{
+         The number of inputs to the Mapping (this will also equal the
+         number of outputs). This value must be either 1 or 3. In either
+         case, the first input and output correspoindis the spectral axis.
+         For a 3-axis SpecMap, the second and third axes give the RA and
+         DEC (J2000 FK5) of the source. This positional information is
+         used by conversions which transform between standards of rest,
+         and replaces the \texttt{"} RA\texttt{"}  and \texttt{"} DEC\texttt{"}  arguments for the individual
+         conversions listed in description of the \texttt{"} SpecAdd\texttt{"}
+         routine.
+      }
+      \sstsubsection{
+         FLAGS = INTEGER (Given)
+      }{
+         This argument is reserved for future use and should currently
+         always be set to zero.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new SpecMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SPECMAP = INTEGER
+      }{
+         A pointer to the new SpecMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The nature and units of the coordinate values supplied for the
+         first input (i.e. the spectral input) of a SpecMap must be appropriate
+         to the first conversion step applied by the SpecMap. For instance, if
+         the first conversion step is \texttt{"} FRTOVL\texttt{"}  (frequency to relativistic
+         velocity), then the coordinate values for the first input should
+         be frequency in units of Hz. Similarly, the nature and units of the
+         coordinate values returned by a SpecMap will be determined by the
+         last conversion step applied by the SpecMap. For instance, if the
+         last conversion step is \texttt{"} VLTOVO\texttt{"}  (relativistic velocity to optical
+         velocity), then the coordinate values for the first output will be optical
+         velocity in units of metres per second. See the description of the
+         AST\_SPECADD routine for the units expected and returned by each
+         conversion.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_SPHMAP
+}{
+   Create a SphMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SphMap}{SphMap} and optionally initialises
+      its attributes.
+
+      A SphMap is a \htmlref{Mapping}{Mapping} which transforms points from a
+      3-dimensional Cartesian coordinate system into a 2-dimensional
+      spherical coordinate system (longitude and latitude on a unit
+      sphere centred at the origin). It works by regarding the input
+      coordinates as position vectors and finding their intersection
+      with the sphere surface. The inverse transformation always
+      produces points which are a unit distance from the origin
+      (i.e. unit vectors).
+   }
+   \sstinvocation{
+      RESULT = AST\_SPHMAP( OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new SphMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SPHMAP = INTEGER
+      }{
+         A pointer to the new SphMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The spherical coordinates are longitude (positive
+         anti-clockwise looking from the positive latitude pole) and
+         latitude. The Cartesian coordinates are right-handed, with the x
+         axis (axis 1) at zero longitude and latitude, and the z axis
+         (axis 3) at the positive latitude pole.
+
+         \sstitem
+         At either pole, the longitude is set to the value of the
+         \htmlref{PolarLong}{PolarLong} attribute.
+
+         \sstitem
+         If the Cartesian coordinates are all zero, then the longitude
+         and latitude are set to the value AST\_\_BAD.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_STCCATALOGENTRYLOCATION
+}{
+   Create a StcCatalogEntryLocation
+}{
+   \sstdescription{
+      This function creates a new \htmlref{StcCatalogEntryLocation}{StcCatalogEntryLocation} and optionally initialises its
+      attributes.
+
+      The StcCatalogEntryLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of the datasets contained in some VO resource.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstinvocation{
+      RESULT = AST\_STCCATALOGENTRYLOCATION( REGION, NCOORDS, COORDS, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         REGION = INTEGER (Given)
+      }{
+         Pointer to the encapsulated \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         NCOORDS = INTEGER (Given)
+      }{
+         The length of the COORDS array. Supply zero if COORDS should be
+         ignored.
+      }
+      \sstsubsection{
+         COORDS( NCOORDS ) = INTEGER (Given)
+      }{
+         An array holding NCOORDS AstKeyMap pointers (if NCOORDS
+         is zero, the supplied value is ignored). Each supplied \htmlref{KeyMap}{KeyMap}
+         describes the contents of a single STC $<$AstroCoords$>$ element, and
+         should have elements with keys given by constants AST\_\_STCNAME,
+         AST\_\_STCVALUE, AST\_\_STCERROR, AST\_\_STCRES, AST\_\_STCSIZE,
+         AST\_\_STCPIXSZ. Any of these elements may be omitted, but no other
+         elements should be included. If supplied, the AST\_\_STCNAME element
+         should be a vector of character string pointers holding the \texttt{"} Name\texttt{"}
+         item for each axis in the coordinate system represented by
+         REGION.
+         Any other supplied elements should be scalar elements, each  holding
+         a pointer to a Region describing the associated item of ancillary
+         information (error, resolution, size, pixel size or value). These
+         Regions should describe a volume within the coordinate system
+         represented by REGION.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new StcCatalogEntryLocation. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_STCCATALOGENTRYLOCATION = INTEGER
+      }{
+         A pointer to the new StcCatalogEntryLocation.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A deep copy is taken of the supplied Region. This means that
+         any subsequent changes made to the encapsulated Region using the
+         supplied pointer will have no effect on the Stc.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_STCOBSDATALOCATION
+}{
+   Create a StcObsDataLocation
+}{
+   \sstdescription{
+      This function creates a new \htmlref{StcObsDataLocation}{StcObsDataLocation} and optionally initialises its
+      attributes.
+
+      The StcObsDataLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of the datasets contained in some VO resource.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstinvocation{
+      RESULT = AST\_STCOBSDATALOCATION( REGION, NCOORDS, COORDS, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         REGION = INTEGER (Given)
+      }{
+         Pointer to the encapsulated \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         NCOORDS = INTEGER (Given)
+      }{
+         The length of the COORDS array. Supply zero if COORDS should be
+         ignored.
+      }
+      \sstsubsection{
+         COORDS( NCOORDS ) = INTEGER (Given)
+      }{
+         An array holding NCOORDS AstKeyMap pointers (if NCOORDS
+         is zero, the supplied value is ignored). Each supplied \htmlref{KeyMap}{KeyMap}
+         describes the contents of a single STC $<$AstroCoords$>$ element, and
+         should have elements with keys given by constants AST\_\_STCNAME,
+         AST\_\_STCVALUE, AST\_\_STCERROR, AST\_\_STCRES, AST\_\_STCSIZE,
+         AST\_\_STCPIXSZ. Any of these elements may be omitted, but no other
+         elements should be included. If supplied, the AST\_\_STCNAME element
+         should be a vector of character string pointers holding the \texttt{"} Name\texttt{"}
+         item for each axis in the coordinate system represented by
+         REGION.
+         Any other supplied elements should be scalar elements, each  holding
+         a pointer to a Region describing the associated item of ancillary
+         information (error, resolution, size, pixel size or value). These
+         Regions should describe a volume within the coordinate system
+         represented by REGION.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new StcObsDataLocation. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_STCOBSDATALOCATION = INTEGER
+      }{
+         A pointer to the new StcObsDataLocation.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A deep copy is taken of the supplied Region. This means that
+         any subsequent changes made to the encapsulated Region using the
+         supplied pointer will have no effect on the Stc.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_STCRESOURCEPROFILE
+}{
+   Create a StcResourceProfile
+}{
+   \sstdescription{
+      This function creates a new \htmlref{StcResourceProfile}{StcResourceProfile} and optionally initialises its
+      attributes.
+
+      The StcResourceProfile class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of the datasets contained in some VO resource.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstinvocation{
+      RESULT = AST\_STCRESOURCEPROFILE( REGION, NCOORDS, COORDS, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         REGION = INTEGER (Given)
+      }{
+         Pointer to the encapsulated \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         NCOORDS = INTEGER (Given)
+      }{
+         The length of the COORDS array. Supply zero if COORDS should be
+         ignored.
+      }
+      \sstsubsection{
+         COORDS( NCOORDS ) = INTEGER (Given)
+      }{
+         An array holding NCOORDS AstKeyMap pointers (if NCOORDS
+         is zero, the supplied value is ignored). Each supplied \htmlref{KeyMap}{KeyMap}
+         describes the contents of a single STC $<$AstroCoords$>$ element, and
+         should have elements with keys given by constants AST\_\_STCNAME,
+         AST\_\_STCVALUE, AST\_\_STCERROR, AST\_\_STCRES, AST\_\_STCSIZE,
+         AST\_\_STCPIXSZ. Any of these elements may be omitted, but no other
+         elements should be included. If supplied, the AST\_\_STCNAME element
+         should be a vector of character string pointers holding the \texttt{"} Name\texttt{"}
+         item for each axis in the coordinate system represented by
+         REGION.
+         Any other supplied elements should be scalar elements, each  holding
+         a pointer to a Region describing the associated item of ancillary
+         information (error, resolution, size, pixel size or value). These
+         Regions should describe a volume within the coordinate system
+         represented by REGION.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new StcResourceProfile. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_STCRESOURCEPROFILE = INTEGER
+      }{
+         A pointer to the new StcResourceProfile.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A deep copy is taken of the supplied Region. This means that
+         any subsequent changes made to the encapsulated Region using the
+         supplied pointer will have no effect on the Stc.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_STCSCHAN
+}{
+   Create an StcsChan
+}{
+   \sstdescription{
+      This function creates a new \htmlref{StcsChan}{StcsChan} and optionally initialises
+      its attributes.
+
+      A StcsChan is a specialised form of \htmlref{Channel}{Channel} which supports STC-S
+      I/O operations. Writing an \htmlref{Object}{Object} to an StcsChan (using
+      \htmlref{AST\_WRITE}{AST\_WRITE}) will, if the Object is suitable, generate an
+      STC-S description of that Object, and reading from an StcsChan will
+      create a new Object from its STC-S description.
+
+      Normally, when you use an StcsChan, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  routines which connect it to an external data store
+      by reading and writing the resulting text. These routines
+      should perform any conversions needed between external character
+      encodings and the internal ASCII encoding. If no such routines
+      are supplied, a Channel will read from standard input and write
+      to standard output.
+
+      Alternatively, an \htmlref{XmlChan}{XmlChan} can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+   }
+   \sstinvocation{
+      RESULT = AST\_STCSCHAN( SOURCE, SINK, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         SOURCE = SUBROUTINE (Given)
+      }{
+         A source routine, which is a subroutine which takes a single
+         integer error status argument.   If no value has been set
+         for the SourceFile attribute, this routine will be used by
+         the StcsChan to obtain lines of input text. On each
+         invocation, it should read the next input line from some
+         external data store, and then return the resulting text to
+         the AST library by calling \htmlref{AST\_PUTLINE}{AST\_PUTLINE}. It should supply a
+         negative line length when there are no more lines to read.
+         If an error occurs, it should set its own error status
+         argument to an error value before returning.
+
+         If the null routine AST\_NULL is suppied as the SOURCE value,
+         and no value has been set for the SourceFile attribute,
+         the StcsChan will read from standard input instead.
+      }
+      \sstsubsection{
+         SINK = SUBROUTINE (Given)
+      }{
+         A sink routine, which is a subroutine which takes a single
+         integer error status argument.  If no value has been set
+         for the SinkFile attribute, this routine will be used by
+         the StcsChan to deliver lines of output text. On each
+         invocation, it should obtain the next output line from the
+         AST library by calling \htmlref{AST\_GETLINE}{AST\_GETLINE}, and then deliver the
+         resulting text to some external data store.  If an error
+         occurs, it should set its own error status argument to an
+         error value before returning.
+
+         If the null routine AST\_NULL is suppied as the SINK value,
+         and no value has been set for the SinkFile attribute,
+         the StcsChan will write to standard output instead.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new StcsChan. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_STCSCHAN = INTEGER
+      }{
+         A pointer to the new StcsChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The names of the routines supplied for the SOURCE and SINK
+         arguments should appear in EXTERNAL statements in the Fortran
+         routine which invokes AST\_STCSCHAN. However, this is not generally
+         necessary for the null routine AST\_NULL (so long as the AST\_PAR
+         include file has been used).
+
+         \sstitem
+         If the external data source or sink uses a character encoding
+         other than ASCII, the supplied source and sink functions should
+         translate between the external character encoding and the internal
+         ASCII encoding used by AST.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+
+         \sstitem
+         Note that the null routine AST\_NULL (one underscore) is
+         different to AST\_\_NULL (two underscores), which is the null Object
+         pointer.
+      }
+   }
+}
+\sstroutine{
+   AST\_STCSEARCHLOCATION
+}{
+   Create a StcSearchLocation
+}{
+   \sstdescription{
+      This function creates a new \htmlref{StcSearchLocation}{StcSearchLocation} and optionally initialises its
+      attributes.
+
+      The StcSearchLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of a VO query.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstinvocation{
+      RESULT = AST\_STCSEARCHLOCATION( REGION, NCOORDS, COORDS, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         REGION = INTEGER (Given)
+      }{
+         Pointer to the encapsulated \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         NCOORDS = INTEGER (Given)
+      }{
+         The length of the COORDS array. Supply zero if COORDS should be
+         ignored.
+      }
+      \sstsubsection{
+         COORDS( NCOORDS ) = INTEGER (Given)
+      }{
+         An array holding NCOORDS AstKeyMap pointers (if NCOORDS
+         is zero, the supplied value is ignored). Each supplied \htmlref{KeyMap}{KeyMap}
+         describes the contents of a single STC $<$AstroCoords$>$ element, and
+         should have elements with keys given by constants AST\_\_STCNAME,
+         AST\_\_STCVALUE, AST\_\_STCERROR, AST\_\_STCRES, AST\_\_STCSIZE,
+         AST\_\_STCPIXSZ. Any of these elements may be omitted, but no other
+         elements should be included. If supplied, the AST\_\_STCNAME element
+         should be a vector of character string pointers holding the \texttt{"} Name\texttt{"}
+         item for each axis in the coordinate system represented by
+         REGION.
+         Any other supplied elements should be scalar elements, each  holding
+         a pointer to a Region describing the associated item of ancillary
+         information (error, resolution, size, pixel size or value). These
+         Regions should describe a volume within the coordinate system
+         represented by REGION.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new StcSearchLocation. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_STCSEARCHLOCATION = INTEGER
+      }{
+         A pointer to the new StcSearchLocation.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A deep copy is taken of the supplied Region. This means that
+         any subsequent changes made to the encapsulated Region using the
+         supplied pointer will have no effect on the Stc.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_STRIPESCAPES
+}{
+   Remove AST escape sequences from a string
+}{
+   \sstdescription{
+      This function removes AST escape sequences from a supplied string,
+      returning the resulting text as the function value. The behaviour
+      of this function can be controlled by invoking the
+      \htmlref{AST\_ESCAPES}{AST\_ESCAPES} routine,
+      which can be used to supress or enable the removal of escape
+      sequences by this function.
+
+      AST escape sequences are used by the \htmlref{Plot}{Plot} class to modify the
+      appearance and position of sub-strings within a plotted text string.
+      See the \texttt{"} \htmlref{Escape}{Escape}\texttt{"}  attribute for further information.
+   }
+   \sstinvocation{
+      RESULT = AST\_STRIPESCAPES( TEXT )
+   }
+   \sstarguments{
+      \sstsubsection{
+         TEXT
+      }{
+         The string to be checked.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_STRIPESCAPES = CHARACTER
+      }{
+         The modified string. If the AST\_ESCAPES routine
+         has been called indicating that escape sequences should not be
+         stripped, then the supplied string is returned without change.
+      }
+   }
+}
+\sstroutine{
+   AST\_SWITCHMAP
+}{
+   Create a SwitchMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SwitchMap}{SwitchMap} and optionally initialises
+      its attributes.
+
+      A SwitchMap is a \htmlref{Mapping}{Mapping} which represents a set of alternate
+      Mappings, each of which is used to transform positions within a
+      particular region of the input or output coordinate system of the
+      SwitchMap.
+
+      A SwitchMap can encapsulate any number of Mappings, but they must
+      all have the same number of inputs (\htmlref{Nin}{Nin} attribute value) and the
+      same number of outputs (\htmlref{Nout}{Nout} attribute value). The SwitchMap itself
+      inherits these same values for its Nin and Nout attributes. Each of
+      these Mappings represents a \texttt{"} route\texttt{"}  through the switch, and are
+      referred to as \texttt{"} route\texttt{"}  Mappings below. Each route Mapping transforms
+      positions between the input and output coordinate space of the entire
+      SwitchMap, but only one Mapping will be used to transform any given
+      position. The selection of the appropriate route Mapping to use with
+      any given input position is made by another Mapping, called the
+      \texttt{"} selector\texttt{"}  Mapping. Each SwitchMap encapsulates two selector
+      Mappings in addition to its route Mappings; one for use with the
+      SwitchMap\texttt{'} s forward transformation (called the \texttt{"} forward selector
+      Mapping\texttt{"} ), and one for use with the SwitchMap\texttt{'} s inverse transformation
+      (called the \texttt{"} inverse selector Mapping\texttt{"} ). The forward selector Mapping
+      must have the same number of inputs as the route Mappings, but
+      should have only one output. Likewise, the inverse selector Mapping
+      must have the same number of outputs as the route Mappings, but
+      should have only one input.
+
+      When the SwitchMap is used to transform a position in the forward
+      direction (from input to output), each supplied input position is
+      first transformed by the forward transformation of the forward selector
+      Mapping. This produces a single output value for each input position
+      referred to as the selector value. The nearest integer to the selector
+      value is found, and is used to index the array of route Mappings (the
+      first supplied route Mapping has index 1, the second route Mapping has
+      index 2, etc). If the nearest integer to the selector value is less
+      than 1 or greater than the number of route Mappings, then the SwitchMap
+      output position is set to a value of AST\_\_BAD on every axis. Otherwise,
+      the forward transformation of the selected route Mapping is used to
+      transform the supplied input position to produce the SwitchMap output
+      position.
+
+      When the SwitchMap is used to transform a position in the inverse
+      direction (from \texttt{"} output\texttt{"}  to \texttt{"} input\texttt{"} ), each supplied \texttt{"} output\texttt{"}  position
+      is first transformed by the inverse transformation of the inverse
+      selector Mapping. This produces a selector value for each \texttt{"} output\texttt{"}
+      position. Again, the nearest integer to the selector value is found,
+      and is used to index the array of route Mappings. If this selector
+      index value is within the bounds of the array of route Mappings, then
+      the inverse transformation of the selected route Mapping is used to
+      transform the supplied \texttt{"} output\texttt{"}  position to produce the SwitchMap
+      \texttt{"} input\texttt{"}  position. If the selector index value is outside the bounds
+      of the array of route Mappings, then the SwitchMap \texttt{"} input\texttt{"}  position is
+      set to a value of AST\_\_BAD on every axis.
+
+      In practice, appropriate selector Mappings should be chosen to
+      associate a different route Mapping with each region of coordinate
+      space. Note that the \htmlref{SelectorMap}{SelectorMap} class of Mapping is particularly
+      appropriate for this purpose.
+
+      If a compound Mapping contains a SwitchMap in series with its own
+      inverse, the combination of the two adjacent SwitchMaps will be
+      replaced by a \htmlref{UnitMap}{UnitMap} when the compound Mapping is simplified using
+      \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}.
+   }
+   \sstinvocation{
+      RESULT = AST\_SWITCHMAP( FSMAP, ISMAP, NROUTE, ROUTEMAPS, OPTIONS,
+                              STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FSMAP = INTEGER (Given)
+      }{
+         Pointer to the forward selector Mapping. This must have a
+         defined forward transformation, but need not have a defined
+         inverse transformation. It must have one output, and the number of
+         inputs must match the number of inputs of each of the supplied
+         route Mappings.
+         AST\_\_NULL
+         may be supplied, in which case the SwitchMap will have an undefined
+         forward Mapping.
+      }
+      \sstsubsection{
+         ISMAP = INTEGER (Given)
+      }{
+         Pointer to the inverse selector Mapping. This must have a
+         defined inverse transformation, but need not have a defined
+         forward transformation. It must have one input, and the number of
+         outputs must match the number of outputs of each of the supplied
+         route Mappings.
+         AST\_\_NULL
+         may be supplied, in which case the SwitchMap will have an undefined
+         inverse Mapping.
+      }
+      \sstsubsection{
+         NROUTE = INTEGER (Given)
+      }{
+         The number of supplied route Mappings.
+      }
+      \sstsubsection{
+         ROUTEMAPS( NROUTE ) = INTEGER (Given)
+      }{
+         An array of pointers to the route Mappings. All the supplied
+         route Mappings must have common values for the Nin and Nout
+         attributes, and these values define the number of inputs and
+         outputs of the SwitchMap.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new SwitchMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_SWITCHMAP = INTEGER
+      }{
+         A pointer to the new SwitchMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Note that the component Mappings supplied are not copied by
+         AST\_SWITCHMAP (the new SwitchMap simply retains a reference to
+         them). They may continue to be used for other purposes, but
+         should not be deleted. If a SwitchMap containing a copy of its
+         component Mappings is required, then a copy of the SwitchMap should
+         be made using \htmlref{AST\_COPY}{AST\_COPY}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_TABLE
+}{
+   Create a Table
+}{
+   \sstdescription{
+      This function creates a new empty \htmlref{Table}{Table} and optionally initialises
+      its attributes.
+
+      The Table class is a type of \htmlref{KeyMap}{KeyMap} that represents a two-dimensional
+      table of values. The
+      AST\_MAPGET... and AST\_MAPPUT...
+      methods provided by the KeyMap class should be used for storing and
+      retrieving values from individual cells within a Table. Each entry
+      in the KeyMap represents a single cell of the table and has an
+      associated key of the form \texttt{"} $<$COL$>$(i)\texttt{"}  where \texttt{"} $<$COL$>$\texttt{"}  is the name of a
+      table column and \texttt{"} i\texttt{"}  is the row index (the first row is row 1). Keys
+      of this form should always be used when using KeyMap methods to access
+      entries within a Table.
+
+      Columns must be declared using the
+      \htmlref{AST\_ADDCOLUMN}{AST\_ADDCOLUMN}
+      method before values can be stored within them. This also fixes the
+      type and shape of the values that may be stored in any cell of the
+      column. Cells may contain scalar or vector values of any data type
+      supported by the KeyMap class. Multi-dimensional arrays may also be
+      stored, but these must be vectorised when storing and retrieving
+      them within a table cell. All cells within a single column must
+      have the same type and shape (specified when the column is declared).
+
+      Tables may have parameters that describe global properties of the
+      entire table. These are stored as entries in the parent KeyMap and
+      can be access using the get and set method of the KeyMap class.
+      However, parameters must be declared using the
+      \htmlref{AST\_ADDPARAMETER}{AST\_ADDPARAMETER}
+      method before being accessed.
+
+      Note - since accessing entries within a KeyMap is a relatively slow
+      process, it is not recommended to use the Table class to store
+      very large tables.
+   }
+   \sstinvocation{
+      RESULT = AST\_TABLE( OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new Table. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_TABLE = INTEGER
+      }{
+         A pointer to the new Table.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list described above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_TABLESOURCE
+}{
+   Register a source routine for accessing tables in FITS files
+}{
+   \sstdescription{
+      This routine can be used to register a call-back routine
+      with a \htmlref{FitsChan}{FitsChan}. The registered
+      routine
+      is called when-ever the FitsChan needs to read information from a
+      binary table contained within a FITS file. This occurs if the
+      \htmlref{AST\_READ}{AST\_READ}
+      function is invoked to read a \htmlref{FrameSet}{FrameSet} from a set of FITS headers
+      that use the \texttt{"} -TAB\texttt{"}  algorithm to describe one or more axes. Such
+      axes use a FITS binary table to store a look-up table of axis values.
+      The FitsChan will fail to read such axes unless the \texttt{"} \htmlref{TabOK}{TabOK}\texttt{"}  attribute
+      is set to a non-zero positive integer value. The table containing the
+      axis values must be made available to the FitsChan either by storing
+      the table contents in the FitsChan (using
+      \htmlref{AST\_PUTTABLES}{AST\_PUTTABLES} or \htmlref{AST\_PUTTABLE}{AST\_PUTTABLE}) prior to invoking AST\_READ
+      or by registering a call-back
+      routine using AST\_TABLESOURCE.
+      The first method is possibly simpler, but requires that the name of
+      the extension containing the table be known in advance. Since the
+      table name is embedded in the FITS headers, the name is often not
+      known in advance. If a call-back is registered, the FitsChan will
+      determine the name of the required table and invoke the call-back
+      routine
+      to supply the table at the point where it is needed (i.e. within
+      the AST\_READ method).
+   }
+   \sstinvocation{
+      CALL AST\_TABLESOURCE( THIS, TABSOURCE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         TABSOURCE = SUBROUTINE (Given)
+      }{
+         The table source routine to use.
+         It takes five arguments - the first is a pointer to the
+         FitsChan, the second is a string holding the name of the
+         FITS extension containing the required binary table (\texttt{"} EXTNAME\texttt{"} ),
+         the third is the integer FITS \texttt{"} EXTVER\texttt{"}  header value for the
+         required extension, the fourth is the integer FITS \texttt{"} EXTLEVEL\texttt{"}
+         header value for the required extension, and the fifth is
+         the inherited integer status value.
+
+         The call-back should read the entire contents (header and data)
+         of the binary table in the named extension of the external FITS
+         file, storing the contents in a newly created \htmlref{FitsTable}{FitsTable} object. It
+         should then store this FitsTable in the FitsChan using the
+         AST\_PUTTABLES or AST\_PUTTABLE
+         method, and finally annull its local copy of the FitsTable pointer.
+         If the table cannot be read for any reason, or if any other
+         error occurs, it should return a non-zero integer for the final
+         (third) argument.
+
+         If TABSOURCE is AST\_NULL,
+         any registered call-back function will be removed.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The name  of the routine supplied for the TABSOURCE
+         argument should appear in an EXTERNAL statement in the Fortran
+         routine which invokes AST\_TABLESOURCE. However, this is not generally
+         necessary for the null routine AST\_NULL (so long as the AST\_PAR
+         include file has been used).
+
+         \sstitem
+         Note that the null routine AST\_NULL (one underscore) is
+         different to AST\_\_NULL (two underscores), which is the null \htmlref{Object}{Object}
+         pointer.
+      }
+   }
+}
+\sstroutine{
+   AST\_TEST
+}{
+   Test if an Object attribute value is set
+}{
+   \sstdescription{
+      This function returns a logical result to indicate
+      whether a value has been explicitly set for one of an \htmlref{Object}{Object}\texttt{'} s
+      attributes.
+   }
+   \sstinvocation{
+      RESULT = AST\_TEST( THIS, ATTRIB, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         ATTRIB = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing the name of the attribute to be
+         tested.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_TEST = LOGICAL
+      }{
+         .TRUE. if a value has previously been explicitly set for the
+         attribute (and hasn\texttt{'} t been cleared), otherwise .FALSE..
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         A value of .FALSE. will be returned if this function is invoked
+         with STATUS set to an error value, or if it should fail for any reason.
+
+         \sstitem
+         A value of .FALSE. will also be returned if this function is used
+         to test a read-only attribute, although no error will result.
+      }
+   }
+}
+\sstroutine{
+   AST\_TESTFITS
+}{
+   See if a named keyword has a defined value in a FitsChan
+}{
+   \sstdescription{
+      This function serches for a named keyword in a \htmlref{FitsChan}{FitsChan}. If found,
+      and if the keyword has a value associated with it, a
+      .TRUE.
+      value is returned. If the keyword is not found, or if it does not
+      have an associated value, a
+      .FALSE.
+      value is returned.
+   }
+   \sstinvocation{
+      RESULT = AST\_TESTFITS( THIS, NAME, THERE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string
+         containing the FITS keyword name. This may be a complete FITS
+         header card, in which case the keyword to use is extracted from
+         it. No more than 80 characters are read from this string.
+      }
+      \sstsubsection{
+         THERE = LOGICAL (Returned)
+      }{
+         A value of .TRUE. will be returned if the keyword was found in the
+         header, and .FALSE. otherwise.
+         This parameter allows a distinction to be made between the case
+         where a keyword is not present, and the case where a keyword is
+         present but has no associated value.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_TESTFITS = LOGICAL
+      }{
+         A value of zero
+         .FALSE.
+         is returned if the keyword was not found in the FitsChan or has
+         no associated value. Otherwise, a value of
+         .TRUE.
+         is returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The current card is left unchanged by this function.
+
+         \sstitem
+         The card following the current card is checked first. If this is
+         not the required card, then the rest of the FitsChan is searched,
+         starting with the first card added to the FitsChan. Therefore cards
+         should be accessed in the order they are stored in the FitsChan (if
+         possible) as this will minimise the time spent searching for cards.
+
+         \sstitem
+         An error will be reported if the keyword name does not conform
+         to FITS requirements.
+
+         \sstitem
+         .FALSE.
+         is returned as the function value if an error has already occurred,
+         or if this function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_TEXT
+}{
+   Draw a text string for a Plot
+}{
+   \sstdescription{
+      This function draws a string of text at a position specified in
+      the physical coordinate system of a \htmlref{Plot}{Plot}. The physical position
+      is transformed into graphical coordinates to determine where the
+      text should appear within the plotting area.
+   }
+   \sstinvocation{
+      CALL AST\_TEXT( THIS, TEXT, POS, UP, JUST, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         TEXT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing the
+         text to be drawn. Trailing white space is ignored.
+      }
+      \sstsubsection{
+         POS( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array, with one element for each axis of the Plot, giving
+         the physical coordinates of the point where the reference
+         position of the text string is to be placed.
+      }
+      \sstsubsection{
+         UP( $*$ ) = REAL (Given)
+      }{
+         An array holding the components of a vector in the \texttt{"} up\texttt{"}
+         direction of the text (in graphical coordinates). For
+         example, to get horizontal text, the vector [0.0,1.0] should
+         be supplied. For a basic Plot, 2 values should be supplied. For
+         a \htmlref{Plot3D}{Plot3D}, 3 values should be supplied, and the actual up vector
+         used is the projection of the supplied up vector onto the text plane
+         specified by the current value of the Plot3D\texttt{'} s Norm attribute.
+      }
+      \sstsubsection{
+         JUST = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string identifying the
+         reference point for the text being drawn. The first character in
+         this string identifies the reference position in the \texttt{"} up\texttt{"}  direction
+         and may be \texttt{"} B\texttt{"}  (baseline), \texttt{"} C\texttt{"}  (centre), \texttt{"} T\texttt{"}  (top) or \texttt{"} M\texttt{"}  (bottom).
+         The second character identifies the side-to-side reference position
+         and may be \texttt{"} L\texttt{"}  (left), \texttt{"} C\texttt{"}  (centre) or \texttt{"} R\texttt{"}  (right ). The string is
+         case-insensitive, and only the first two characters are significant.
+
+         For example, a value of \texttt{"} BL\texttt{"}  means that the left end of the
+         baseline of the original (un-rotated) text is to be drawn at the
+         position given by POS.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Plot3D class currently does not interpret graphical escape
+         sequences contained within text displayed using this method.
+
+         \sstitem
+         Text is not drawn at positions which have any coordinate equal
+         to the value AST\_\_BAD (or where the transformation into
+         graphical coordinates yields coordinates containing the value
+         AST\_\_BAD).
+
+         \sstitem
+         If the plotting position is clipped (see \htmlref{AST\_CLIP}{AST\_CLIP}), then no
+         text is drawn.
+
+         \sstitem
+         An error results if the base \htmlref{Frame}{Frame} of the Plot is not
+         2-dimensional or (for a Plot3D) 3-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   AST\_TIMEADD
+}{
+   Add a time coordinate conversion to a TimeMap
+}{
+   \sstdescription{
+      This routine adds one of the standard time coordinate
+      system conversions listed below to an existing \htmlref{TimeMap}{TimeMap}.
+
+      When a TimeMap is first created (using \htmlref{AST\_TIMEMAP}{AST\_TIMEMAP}), it simply
+      performs a unit (null) \htmlref{Mapping}{Mapping}. By using AST\_TIMEADD (repeatedly
+      if necessary), one or more coordinate conversion steps may then
+      be added, which the TimeMap will perform in sequence. This allows
+      multi-step conversions between a variety of time coordinate
+      systems to be assembled out of the building blocks provided by
+      this class.
+
+      Normally, if a TimeMap\texttt{'} s \htmlref{Invert}{Invert} attribute is zero (the default),
+      then its forward transformation is performed by carrying out
+      each of the individual coordinate conversions specified by
+      AST\_TIMEADD in the order given (i.e. with the most recently added
+      conversion applied last).
+
+      This order is reversed if the TimeMap\texttt{'} s Invert attribute is
+      non-zero (or if the inverse transformation is requested by any
+      other means) and each individual coordinate conversion is also
+      replaced by its own inverse. This process inverts the overall
+      effect of the TimeMap. In this case, the first conversion to be
+      applied would be the inverse of the one most recently added.
+   }
+   \sstinvocation{
+      CALL AST\_TIMEADD( THIS, CVT, NARG, ARGS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the TimeMap.
+      }
+      \sstsubsection{
+         CVT = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string which identifies the
+         time coordinate conversion to be added to the
+         TimeMap. See the \texttt{"} Available Conversions\texttt{"}  section for details of
+         those available.
+      }
+      \sstsubsection{
+         NARG = INTEGER (Given)
+      }{
+         The number of argument values supplied in the
+         ARGS array.
+      }
+      \sstsubsection{
+         ARGS( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing argument values for the time
+         coordinate conversion. The number of arguments required, and
+         hence the number of array elements used, depends on the
+         conversion specified (see the \texttt{"} Available Conversions\texttt{"}
+         section). This array is ignored
+         if no arguments are needed.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When assembling a multi-stage conversion, it can sometimes be
+         difficult to determine the most economical conversion path. A solution
+         to this is to include all the steps which are (logically) necessary,
+         but then to use
+         \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY} to simplify the resulting
+         TimeMap. The simplification process will eliminate any steps
+         which turn out not to be needed.
+
+         \sstitem
+         This routine does not check to ensure that the sequence of
+         coordinate conversions added to a TimeMap is physically
+         meaningful.
+      }
+   }
+   \sstdiytopic{
+      Available Conversions
+   }{
+      The following strings (which are case-insensitive) may be supplied
+      via the CVT argument to indicate which time coordinate
+      conversion is to be added to the TimeMap. Where arguments are needed by
+      the conversion, they are listed in parentheses. Values for
+      these arguments should be given, via the ARGS array, in the
+      order indicated. Units and argument names are described at the end of
+      the list of conversions, and \texttt{"} MJD\texttt{"}  means Modified Julian Date.
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} MJDTOMJD\texttt{"}   (MJDOFF1,MJDOFF2): Convert MJD from one offset to another.
+
+         \sstitem
+         \texttt{"} MJDTOJD\texttt{"}   (MJDOFF,JDOFF): Convert MJD to Julian Date.
+
+         \sstitem
+         \texttt{"} JDTOMJD\texttt{"}   (JDOFF,MJDOFF): Convert Julian Date to MJD.
+
+         \sstitem
+         \texttt{"} MJDTOBEP\texttt{"}  (MJDOFF,BEPOFF): Convert MJD to Besselian epoch.
+
+         \sstitem
+         \texttt{"} BEPTOMJD\texttt{"}  (BEPOFF,MJDOFF): Convert Besselian epoch to MJD.
+
+         \sstitem
+         \texttt{"} MJDTOJEP\texttt{"}  (MJDOFF,JEPOFF): Convert MJD to Julian epoch.
+
+         \sstitem
+         \texttt{"} JEPTOMJD\texttt{"}  (JEPOFF,MJDOFF): Convert Julian epoch to MJD.
+
+         \sstitem
+         \texttt{"} TAITOUTC\texttt{"}  (MJDOFF): Convert a TAI MJD to a UTC MJD.
+
+         \sstitem
+         \texttt{"} UTCTOTAI\texttt{"}  (MJDOFF): Convert a UTC MJD to a TAI MJD.
+
+         \sstitem
+         \texttt{"} TAITOTT\texttt{"}   (MJDOFF): Convert a TAI MJD to a TT MJD.
+
+         \sstitem
+         \texttt{"} TTTOTAI\texttt{"}   (MJDOFF): Convert a TT MJD to a TAI MJD.
+
+         \sstitem
+         \texttt{"} TTTOTDB\texttt{"}   (MJDOFF, OBSLON, OBSLAT, OBSALT): Convert a TT MJD to a TDB MJD.
+
+         \sstitem
+         \texttt{"} TDBTOTT\texttt{"}   (MJDOFF, OBSLON, OBSLAT, OBSALT): Convert a TDB MJD to a TT MJD.
+
+         \sstitem
+         \texttt{"} TTTOTCG\texttt{"}   (MJDOFF): Convert a TT MJD to a TCG MJD.
+
+         \sstitem
+         \texttt{"} TCGTOTT\texttt{"}   (MJDOFF): Convert a TCG MJD to a TT MJD.
+
+         \sstitem
+         \texttt{"} TDBTOTCB\texttt{"}  (MJDOFF): Convert a TDB MJD to a TCB MJD.
+
+         \sstitem
+         \texttt{"} TCBTOTDB\texttt{"}  (MJDOFF): Convert a TCB MJD to a TDB MJD.
+
+         \sstitem
+         \texttt{"} UTTOGMST\texttt{"}  (MJDOFF): Convert a UT MJD to a GMST MJD.
+
+         \sstitem
+         \texttt{"} GMSTTOUT\texttt{"}  (MJDOFF): Convert a GMST MJD to a UT MJD.
+
+         \sstitem
+         \texttt{"} GMSTTOLMST\texttt{"}  (MJDOFF, OBSLON, OBSLAT): Convert a GMST MJD to a LMST MJD.
+
+         \sstitem
+         \texttt{"} LMSTTOGMST\texttt{"}  (MJDOFF, OBSLON, OBSLAT): Convert a LMST MJD to a GMST MJD.
+
+         \sstitem
+         \texttt{"} LASTTOLMST\texttt{"}  (MJDOFF, OBSLON, OBSLAT): Convert a GMST MJD to a LMST MJD.
+
+         \sstitem
+         \texttt{"} LMSTTOLAST\texttt{"}  (MJDOFF, OBSLON, OBSLAT): Convert a LMST MJD to a GMST MJD.
+
+         \sstitem
+         \texttt{"} UTTOUTC\texttt{"}  (DUT1): Convert a UT1 MJD to a UTC MJD.
+
+         \sstitem
+         \texttt{"} UTCTOUT\texttt{"}  (DUT1): Convert a UTC MJD to a UT1 MJD.
+
+         \sstitem
+         \texttt{"} LTTOUTC\texttt{"}  (LTOFF): Convert a Local Time MJD to a UTC MJD.
+
+         \sstitem
+         \texttt{"} UTCTOLT\texttt{"}  (LTOFF): Convert a UTC MJD to a Local Time MJD.
+
+      }
+      The units for the values processed by the above conversions are as
+      follows:
+
+      \sstitemlist{
+
+         \sstitem
+         Julian epochs and offsets: Julian years
+
+         \sstitem
+         Besselian epochs and offsets: Tropical years
+
+         \sstitem
+         Modified Julian Dates and offsets: days
+
+         \sstitem
+         Julian Dates and offsets: days
+
+      }
+      The arguments used in the above conversions are the zero-points
+      used by the
+      AST\_TRANSFORM routine.
+      The axis values supplied and returned by
+      AST\_TRANSFORM
+      are offsets away from these zero-points:
+
+      \sstitemlist{
+
+         \sstitem
+         MJDOFF: The zero-point being used with MJD values.
+
+         \sstitem
+         JDOFF: The zero-point being used with Julian Date values.
+
+         \sstitem
+         BEPOFF: The zero-point being used with Besselian epoch values.
+
+         \sstitem
+         JEPOFF: The zero-point being used with Julian epoch values.
+
+         \sstitem
+         OBSLON: Observer longitude in radians ($+$ve westwards).
+
+         \sstitem
+         OBSLAT: Observer geodetic latitude (IAU 1975) in radians ($+$ve northwards).
+
+         \sstitem
+         OBSALT: Observer geodetic altitude (IAU 1975) in metres.
+
+         \sstitem
+         DUT1: The UT1-UTC value to use.
+
+         \sstitem
+         LTOFF: The offset between Local Time and UTC (in hours, positive
+         for time zones east of Greenwich).
+      }
+   }
+}
+\sstroutine{
+   AST\_TIMEFRAME
+}{
+   Create a TimeFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{TimeFrame}{TimeFrame} and optionally initialises
+      its attributes.
+
+      A TimeFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various coordinate systems used to describe positions in
+      time.
+
+      A TimeFrame represents a moment in time as either an Modified Julian
+      Date (MJD), a Julian Date (JD), a Besselian epoch or a Julian epoch,
+      as determined by the \htmlref{System}{System} attribute. Optionally, a zero point can be
+      specified (using attribute \htmlref{TimeOrigin}{TimeOrigin}) which results in the TimeFrame
+      representing time offsets from the specified zero point.
+
+      Even though JD and MJD are defined as being in units of days, the
+      TimeFrame class allows other units to be used (via the Unit attribute)
+      on the basis of simple scalings (60 seconds = 1 minute, 60 minutes = 1
+      hour, 24 hours = 1 day, 365.25 days = 1 year). Likewise, Julian epochs
+      can be described in units other than the usual years. Besselian epoch
+      are always represented in units of (tropical) years.
+
+      The \htmlref{TimeScale}{TimeScale} attribute allows the time scale to be specified (that
+      is, the physical proces used to define the rate of flow of time).
+      MJD, JD and Julian epoch can be used to represent a time in any
+      supported time scale. However, Besselian epoch may only be used with the
+      \texttt{"} TT\texttt{"}  (Terrestrial Time) time scale. The list of supported time scales
+      includes universal time and siderial time. Strictly, these represent
+      angles rather than time scales, but are included in the list since
+      they are in common use and are often thought of as time scales.
+
+      When a time value is formatted it can be formated either as a simple
+      floating point value, or as a Gregorian date (see the Format
+      attribute).
+   }
+   \sstinvocation{
+      RESULT = AST\_TIMEFRAME( OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new TimeFrame. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_TIMEFRAME = INTEGER
+      }{
+         A pointer to the new TimeFrame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When conversion between two TimeFrames is requested (as when
+         supplying TimeFrames \htmlref{AST\_CONVERT}{AST\_CONVERT}),
+         account will be taken of the nature of the time coordinate systems
+         they represent, together with any qualifying time scale, offset,
+         unit, etc. The \htmlref{AlignSystem}{AlignSystem} and \htmlref{AlignTimeScale}{AlignTimeScale} attributes will also be
+         taken into account.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_TIMEMAP
+}{
+   Create a TimeMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{TimeMap}{TimeMap} and optionally initialises
+      its attributes.
+
+      A TimeMap is a specialised form of 1-dimensional \htmlref{Mapping}{Mapping} which can be
+      used to represent a sequence of conversions between standard time
+      coordinate systems.
+
+      When a TimeMap is first created, it simply performs a unit
+      (null) Mapping. Using the \htmlref{AST\_TIMEADD}{AST\_TIMEADD}
+      routine, a series of coordinate conversion steps may then be
+      added. This allows multi-step conversions between a variety of
+      time coordinate systems to be assembled out of a set of building
+      blocks.
+
+      For details of the individual coordinate conversions available,
+      see the description of the AST\_TIMEADD routine.
+   }
+   \sstinvocation{
+      RESULT = AST\_TIMEMAP( FLAGS, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         FLAGS = INTEGER (Given)
+      }{
+         This argument is reserved for future use and should currently
+         always be set to zero.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new TimeMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine. If no initialisation is required, a blank
+         value may be supplied.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_TIMEMAP = INTEGER
+      }{
+         A pointer to the new TimeMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The nature and units of the coordinate values supplied for the
+         first input (i.e. the time input) of a TimeMap must be appropriate
+         to the first conversion step applied by the TimeMap. For instance, if
+         the first conversion step is \texttt{"} MJDTOBEP\texttt{"}  (Modified Julian Date to
+         Besselian epoch) then the coordinate values for the first input should
+         be date in units of days. Similarly, the nature and units of the
+         coordinate values returned by a TimeMap will be determined by the
+         last conversion step applied by the TimeMap.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_TRAN1
+}{
+   Transform 1-dimensional coordinates
+}{
+   \sstdescription{
+      This routine applies a \htmlref{Mapping}{Mapping} to transform the coordinates of
+      a set of points in one dimension.
+   }
+   \sstinvocation{
+      CALL AST\_TRAN1( THIS, NPOINT, XIN, FORWARD, XOUT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         NPOINT = INTEGER (Given)
+      }{
+         The number of points to be transformed.
+      }
+      \sstsubsection{
+         XIN( NPOINT ) = DOUBLE PRECISION (Given)
+      }{
+         An array of coordinate values for the input
+         (untransformed) points.
+      }
+      \sstsubsection{
+         FORWARD = LOGICAL (Given)
+      }{
+         A .TRUE. value indicates that the Mapping\texttt{'} s forward
+         coordinate transformation is to be applied, while a .FALSE.
+         value indicates that the inverse transformation should be
+         used.
+      }
+      \sstsubsection{
+         XOUT( NPOINT ) = DOUBLE PRECISION (Returned)
+      }{
+         An array into which the
+         coordinates of the output (transformed) points will be written.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Mapping supplied must have the value 1 for both its \htmlref{Nin}{Nin}
+         and \htmlref{Nout}{Nout} attributes.
+      }
+   }
+}
+\sstroutine{
+   AST\_TRAN2
+}{
+   Transform 2-dimensional coordinates
+}{
+   \sstdescription{
+      This routine applies a \htmlref{Mapping}{Mapping} to transform the coordinates of
+      a set of points in two dimensions.
+   }
+   \sstinvocation{
+      CALL AST\_TRAN2( THIS, NPOINT, XIN, YIN, FORWARD, XOUT, YOUT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         NPOINT = INTEGER (Given)
+      }{
+         The number of points to be transformed.
+      }
+      \sstsubsection{
+         XIN( NPOINT ) = DOUBLE PRECISION (Given)
+      }{
+         An array of X-coordinate values for the input
+         (untransformed) points.
+      }
+      \sstsubsection{
+         YIN( NPOINT ) = DOUBLE PRECISION (Given)
+      }{
+         An array of Y-coordinate values for the input
+         (untransformed) points.
+      }
+      \sstsubsection{
+         FORWARD = LOGICAL (Given)
+      }{
+         A .TRUE. value indicates that the Mapping\texttt{'} s forward
+         coordinate transformation is to be applied, while a .FALSE.
+         value indicates that the inverse transformation should be
+         used.
+      }
+      \sstsubsection{
+         XOUT( NPOINT ) = DOUBLE PRECISION (Returned)
+      }{
+         An array into which the
+         X-coordinates of the output (transformed) points will be written.
+      }
+      \sstsubsection{
+         YOUT( NPOINT ) = DOUBLE PRECISION (Returned)
+      }{
+         An array into which the
+         Y-coordinates of the output (transformed) points will be written.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Mapping supplied must have the value 2 for both its \htmlref{Nin}{Nin}
+         and \htmlref{Nout}{Nout} attributes.
+      }
+   }
+}
+\sstroutine{
+   AST\_TRANGRID
+}{
+   Transform a grid of positions
+}{
+   \sstdescription{
+      This function uses the supplied \htmlref{Mapping}{Mapping} to transforms a regular square
+      grid of points covering a specified box. It attempts to do this
+      quickly by first approximating the Mapping with a linear transformation
+      applied over the whole region of the input grid which is being used.
+      If this proves to be insufficiently accurate, the input region is
+      sub-divided into two along its largest dimension and the process is
+      repeated within each of the resulting sub-regions. This process of
+      sub-division continues until a sufficiently good linear approximation
+      is found, or the region to which it is being applied becomes too small
+      (in which case the original Mapping is used directly).
+   }
+   \sstinvocation{
+      CALL AST\_TRANGRID( THIS, NCOORD\_IN, LBND, UBND, TOL, MAXPIX,
+                         FORWARD, NCOORD\_OUT, OUTDIM, OUT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         NCOORD\_IN = INTEGER (Given)
+      }{
+         The number of coordinates being supplied for each box corner
+         (i.e. the number of dimensions of the space in which the
+         input points reside).
+      }
+      \sstsubsection{
+         LBND( NCOORD\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         UBND( NCOORD\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that LBND and UBND together define the shape
+         and size of the input grid, its extent along a particular
+         (J\texttt{'} th) dimension being UBND(J)-LBND(J)$+$1. They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre.
+      }
+      \sstsubsection{
+         TOL = DOUBLE PRECISION (Given)
+      }{
+         The maximum tolerable geometrical distortion which may be
+         introduced as a result of approximating non-linear Mappings
+         by a set of piece-wise linear transformations. This should be
+         expressed as a displacement within the output coordinate system
+         of the Mapping.
+
+         If piece-wise linear approximation is not required, a value
+         of zero may be given. This will ensure that the Mapping is
+         used without any approximation, but may increase execution
+         time.
+
+         If the value is too high, discontinuities between the linear
+         approximations used in adjacent panel will be higher. If this
+         is a problem, reduce the tolerance value used.
+      }
+      \sstsubsection{
+         MAXPIX = INTEGER (Given)
+      }{
+         A value which specifies an initial scale size (in input grid points)
+         for the adaptive algorithm which approximates non-linear Mappings
+         with piece-wise linear transformations. Normally, this should
+         be a large value (larger than any dimension of the region of
+         the input grid being used). In this case, a first attempt to
+         approximate the Mapping by a linear transformation will be
+         made over the entire input region.
+
+         If a smaller value is used, the input region will first be
+         divided into sub-regions whose size does not exceed MAXPIX
+         grid points in any dimension. Only at this point will attempts
+         at approximation commence.
+
+         This value may occasionally be useful in preventing false
+         convergence of the adaptive algorithm in cases where the
+         Mapping appears approximately linear on large scales, but has
+         irregularities (e.g. holes) on smaller scales. A value of,
+         say, 50 to 100 grid points can also be employed as a safeguard
+         in general-purpose software, since the effect on performance is
+         minimal.
+
+         If too small a value is given, it will have the effect of
+         inhibiting linear approximation altogether (equivalent to
+         setting TOL to zero). Although this may degrade
+         performance, accurate results will still be obtained.
+      }
+      \sstsubsection{
+         FORWARD = LOGICAL (Given)
+      }{
+         A .TRUE. value indicates that the Mapping\texttt{'} s forward
+         coordinate transformation is to be applied, while a .FALSE.
+         value indicates that the inverse transformation should be
+         used.
+      }
+      \sstsubsection{
+         NCOORD\_OUT = INTEGER (Given)
+      }{
+         The number of coordinates being generated by the Mapping for
+         each output point (i.e. the number of dimensions of the
+         space in which the output points reside). This need not be
+         the same as NCOORD\_IN.
+      }
+      \sstsubsection{
+         OUTDIM = INTEGER (Given)
+      }{
+         The number of elements along the first dimension of the OUT
+         array (which will contain the output coordinates). The value
+         given should not be less than the number of points in the grid.
+      }
+      \sstsubsection{
+         OUT( OUTDIM, NCOORD\_OUT ) = DOUBLE PRECISION (Returned)
+      }{
+         An array into which the coordinates of the output
+         (transformed) points will be written. These will be stored
+         such that the value of coordinate number COORD for output
+         point number POINT will be found in element OUT(POINT,COORD).
+         The points are ordered such that the first axis of the input
+         grid changes most rapidly. For example, if the input grid is
+         2-dimensional and extends from (2,-1) to (3,1), the output
+         points will be stored in the order (2,-1), (3, -1), (2,0), (3,0),
+         (2,1), (3,1).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the forward coordinate transformation is being applied, the
+         Mapping supplied must have the value of NCOORD\_IN for its \htmlref{Nin}{Nin}
+         attribute and the value of NCOORD\_OUT for its \htmlref{Nout}{Nout} attribute. If
+         the inverse transformation is being applied, these values should
+         be reversed.
+      }
+   }
+}
+\sstroutine{
+   AST\_TRANMAP
+}{
+   Create a TranMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{TranMap}{TranMap} and optionally initialises
+      its attributes.
+
+      A TranMap is a \htmlref{Mapping}{Mapping} which combines the forward transformation of
+      a supplied Mapping with the inverse transformation of another
+      supplied Mapping, ignoring the un-used transformation in each
+      Mapping (indeed the un-used transformation need not exist).
+
+      When the forward transformation of the TranMap is referred to, the
+      transformation actually used is the forward transformation of the
+      first Mapping supplied when the TranMap was constructed. Likewise,
+      when the inverse transformation of the TranMap is referred to, the
+      transformation actually used is the inverse transformation of the
+      second Mapping supplied when the TranMap was constructed.
+   }
+   \sstinvocation{
+      RESULT = AST\_TRANMAP( MAP1, MAP2, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         MAP1 = INTEGER (Given)
+      }{
+         Pointer to the first component Mapping, which defines the
+         forward transformation.
+      }
+      \sstsubsection{
+         MAP2 = INTEGER (Given)
+      }{
+         Pointer to the second component Mapping, which defines the
+         inverse transformation.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new TranMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_TRANMAP = INTEGER
+      }{
+         A pointer to the new TranMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The number of output coordinates generated by the two Mappings
+         (their \htmlref{Nout}{Nout} attribute) must be equal, as must the number of input
+         coordinates accepted by each Mapping (their \htmlref{Nin}{Nin} attribute).
+
+         \sstitem
+         The forward transformation of the first Mapping must exist.
+
+         \sstitem
+         The inverse transformation of the second Mapping must exist.
+
+         \sstitem
+         Note that the component Mappings supplied are not copied by
+         AST\_TRANMAP (the new TranMap simply retains a reference to
+         them). They may continue to be used for other purposes, but
+         should not be deleted. If a TranMap containing a copy of its
+         component Mappings is required, then a copy of the TranMap should
+         be made using \htmlref{AST\_COPY}{AST\_COPY}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_TRANN
+}{
+   Transform N-dimensional coordinates
+}{
+   \sstdescription{
+      This routine applies a \htmlref{Mapping}{Mapping} to transform the coordinates of
+      a set of points in an arbitrary number of dimensions. It is the
+      appropriate routine to use if the coordinates are not purely 1-
+      or 2-dimensional and are stored in a single array (which they
+      need not fill completely).
+   }
+   \sstinvocation{
+      CALL AST\_TRANN( THIS, NPOINT,
+                      NCOORD\_IN, INDIM, IN,
+                      FORWARD, NCOORD\_OUT, OUTDIM, OUT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         NPOINT = INTEGER (Given)
+      }{
+         The number of points to be transformed.
+      }
+      \sstsubsection{
+         NCOORD\_IN = INTEGER (Given)
+      }{
+         The number of coordinates being supplied for each input point
+         (i.e. the number of dimensions of the space in which the
+         input points reside).
+      }
+      \sstsubsection{
+         INDIM = INTEGER (Given)
+      }{
+         The number of elements along the first dimension of the IN
+         array (which contains the input coordinates). This value is
+         required so that the coordinate values can be correctly
+         located if they do not entirely fill this array. The value
+         given should not be less than NPOINT.
+      }
+      \sstsubsection{
+         IN( INDIM, NCOORD\_IN ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing the coordinates of the input
+         (untransformed) points. These should be stored such that the
+         value of coordinate number COORD for input point number POINT
+         is found in element IN(POINT,COORD).
+      }
+      \sstsubsection{
+         FORWARD = LOGICAL (Given)
+      }{
+         A .TRUE. value indicates that the Mapping\texttt{'} s forward
+         coordinate transformation is to be applied, while a .FALSE.
+         value indicates that the inverse transformation should be
+         used.
+      }
+      \sstsubsection{
+         NCOORD\_OUT = INTEGER (Given)
+      }{
+         The number of coordinates being generated by the Mapping for
+         each output point (i.e. the number of dimensions of the
+         space in which the output points reside). This need not be
+         the same as NCOORD\_IN.
+      }
+      \sstsubsection{
+         OUTDIM = INTEGER (Given)
+      }{
+         The number of elements along the first dimension of the OUT
+         array (which will contain the output coordinates). This value
+         is required so that the coordinate values can be correctly
+         located if they will not entirely fill this array. The value
+         given should not be less than NPOINT.
+      }
+      \sstsubsection{
+         OUT( OUTDIM, NCOORD\_OUT ) = DOUBLE PRECISION (Returned)
+      }{
+         An array into which the coordinates of the output
+         (transformed) points will be written. These will be stored
+         such that the value of coordinate number COORD for output
+         point number POINT will be found in element OUT(POINT,COORD).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the forward coordinate transformation is being applied, the
+         Mapping supplied must have the value of NCOORD\_IN for its \htmlref{Nin}{Nin}
+         attribute and the value of NCOORD\_OUT for its \htmlref{Nout}{Nout} attribute. If
+         the inverse transformation is being applied, these values should
+         be reversed.
+      }
+   }
+}
+\sstroutine{
+   AST\_TUNE
+}{
+   Set or get an integer-valued AST global tuning parameter
+}{
+   \sstdescription{
+      This function returns the current value of an integer-valued AST
+      global tuning parameter, optionally storing a new value for the
+      parameter. For character-valued tuning parameters, see
+      \htmlref{AST\_TUNEC}{AST\_TUNEC}.
+   }
+   \sstinvocation{
+      RESULT = AST\_TUNE( NAME, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The name of the tuning parameter (case-insensitive).
+      }
+      \sstsubsection{
+         VALUE = INTEGER (Given)
+      }{
+         The new value for the tuning parameter. If this is AST\_\_TUNULL,
+         the existing current value will be retained.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_TUNE = INTEGER
+      }{
+         be returned if no value has been set for the parameter.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine attempts to execute even if STATUS is set to an
+         error value
+         on entry, although no further error report will be
+         made if it subsequently fails under these circumstances.
+
+         \sstitem
+         All threads in a process share the same AST tuning parameters
+         values.
+      }
+   }
+   \sstdiylist{
+      Tuning Parameters
+   }{
+      \sstsubsection{
+         ObjectCaching
+      }{
+         A boolean flag which indicates what should happen
+         to the memory occupied by an AST \htmlref{Object}{Object} when the Object is deleted
+         (i.e. when its reference count falls to zero or it is deleted using
+         \htmlref{AST\_DELETE}{AST\_DELETE}).
+         If this is zero, the memory is simply freed using the systems \texttt{"} free\texttt{"}
+         function. If it is non-zero, the memory is not freed. Instead a
+         pointer to it is stored in a pool of such pointers, all of which
+         refer to allocated but currently unused blocks of memory. This allows
+         AST to speed up subsequent Object creation by re-using previously
+         allocated memory blocks rather than allocating new memory using the
+         systems malloc function. The default value for this parameter is
+         zero. Setting it to a non-zero value will result in Object memory
+         being cached in future. Setting it back to zero causes any memory
+         blocks currently in the pool to be freed. Note, this tuning parameter
+         only controls the caching of memory used to store AST Objects. To
+         cache other memory blocks allocated by AST, use MemoryCaching.
+      }
+      \sstsubsection{
+         MemoryCaching
+      }{
+         A boolean flag similar to ObjectCaching except
+         that it controls caching of all memory blocks of less than 300 bytes
+         allocated by AST (whether for internal or external use), not just
+         memory used to store AST Objects.
+      }
+   }
+}
+\sstroutine{
+   AST\_TUNEC
+}{
+   Set or get a character-valued AST global tuning parameter
+}{
+   \sstdescription{
+      This function returns the current value of a character-valued
+      AST global tuning parameter, optionally storing a new value
+      for the parameter. For integer-valued tuning parameters, see
+      \htmlref{AST\_TUNE}{AST\_TUNE}.
+   }
+   \sstinvocation{
+      CALL AST\_TUNEC( NAME, VALUE, BUFF, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NAME = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         The name of the tuning parameter (case-insensitive).
+      }
+      \sstsubsection{
+         VALUE = CHARACTER $*$ ( ) (Given)
+      }{
+         The new value for the tuning parameter. If this is
+         AST\_\_TUNULLC,
+         the existing current value will be retained.
+      }
+      \sstsubsection{
+         BUFF = CHARACTER $*$ ( ) (Given)
+      }{
+         A character string in which to return the original value of
+         the tuning parameter. An error will be reported if the buffer
+         is too small to hold the value.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine attempts to execute even if STATUS is set to an
+         error value
+         on entry, although no further error report will be
+         made if it subsequently fails under these circumstances.
+
+         \sstitem
+         All threads in a process share the same AST tuning parameters
+         values.
+      }
+   }
+   \sstdiylist{
+      Tuning Parameters
+   }{
+      \sstsubsection{
+         HRDel
+      }{
+         A string to be drawn following the hours field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use (see the Format
+         attribute). This string may include escape sequences to produce
+         super-scripts, etc. (see the Escapes attribute for details
+         of the escape sequences allowed). The default value is
+         \texttt{"} \%-\%$\wedge$50$+$\%s70$+$h\%$+$\texttt{"}  which produces a super-script \texttt{"} h\texttt{"} .
+      }
+      \sstsubsection{
+         MNDel
+      }{
+         A string to be drawn following the minutes field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use. The default value is
+         \texttt{"} \%-\%$\wedge$50$+$\%s70$+$m\%$+$\texttt{"}  which produces a super-script \texttt{"} m\texttt{"} .
+      }
+      \sstsubsection{
+         SCDel
+      }{
+         A string to be drawn following the seconds field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use. The default value is
+         \texttt{"} \%-\%$\wedge$50$+$\%s70$+$s\%$+$\texttt{"}  which produces a super-script \texttt{"} s\texttt{"} .
+      }
+      \sstsubsection{
+         DGDel
+      }{
+         A string to be drawn following the degrees field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use. The default value is
+         \texttt{"} \%-\%$\wedge$53$+$\%s60$+$o\%$+$\texttt{"}  which produces a super-script \texttt{"} o\texttt{"} .
+      }
+      \sstsubsection{
+         AMDel
+      }{
+         A string to be drawn following the arc-minutes field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use. The default value is
+         \texttt{"} \%-\%$\wedge$20$+$\%s85$+$\texttt{'} \%$+$\texttt{"}  which produces a super-script \texttt{"} \texttt{'} \texttt{"}  (single quote).
+      }
+      \sstsubsection{
+         ASDel
+      }{
+         A string to be drawn following the arc-seconds field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use. The default value is
+         \texttt{"} \%-\%$\wedge$20$+$\%s85$+$$\backslash$\texttt{"} \%$+$\texttt{"}  which produces a super-script \texttt{"} \texttt{"} \texttt{"}  (double quote).
+      }
+      \sstsubsection{
+         EXDel
+      }{
+         A string to be drawn to introduce the exponent in a value when \texttt{"} g\texttt{"}
+         format is in use. The default value is \texttt{"} 10\%-\%$\wedge$50$+$\%s70$+$\texttt{"}  which
+         produces \texttt{"} 10\texttt{"}  followed by the exponent as a super-script.
+      }
+   }
+}
+\sstroutine{
+   AST\_UINTERP
+}{
+   Perform sub-pixel interpolation on a grid of data
+}{
+   \sstdescription{
+      This is a fictitious routine which does not actually
+      exist. Instead, this description constitutes a template so that
+      you may implement a routine with this interface for yourself
+      (and give it any name you wish). Such a routine
+      may be passed via the FINTERP argument of the \htmlref{AST\_RESAMPLE$<$X$>$}{AST\_RESAMPLE$<$X$>$}
+      functions (q.v.) in order to perform sub-pixel interpolation
+      during resampling of gridded data (you must also set the
+      INTERP argument of AST\_RESAMPLE$<$X$>$ to the value
+      AST\_\_UINTERP). This allows you to use your own interpolation
+      algorithm in addition to those which are pre-defined.
+
+      The routine interpolates an input grid of data (and,
+      optionally, processes associated statistical variance estimates)
+      at a specified set of points.
+   }
+   \sstinvocation{
+      CALL AST\_UINTERP( NDIM\_IN, LBND\_IN, UBND\_IN, IN, IN\_VAR,
+                        NPOINT, OFFSET, COORDS, PARAMS, FLAGS, BADVAL,
+                        OUT, OUT\_VAR, NBAD, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NDIM\_IN = INTEGER (Given)
+      }{
+         The number of dimensions in the input grid. This will be at
+         least one.
+      }
+      \sstsubsection{
+         LBND\_IN( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         UBND\_IN( NDIM\_IN ) = INTEGER (Given)
+      }{
+         An array
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that LBND\_IN and UBND\_IN together define the shape,
+         size and coordinate system of the input grid in the same
+         way as they do in AST\_RESAMPLE$<$X$>$.
+      }
+      \sstsubsection{
+         IN( $*$ ) = $<$Xtype$>$ (Given)
+      }{
+         An array, with one element for each pixel in the
+         input grid, containing the input data. This will be the same
+         array as was passed to AST\_RESAMPLE$<$X$>$ via the IN argument.
+         The numerical type of this array should match that of the
+         data being processed.
+      }
+      \sstsubsection{
+         IN\_VAR( $*$ ) = $<$Xtype$>$ (Given)
+      }{
+         An optional second array with the same size and type as the
+         IN array. This will only be given if the AST\_\_USEVAR flag is
+         set via the FLAGS argument (below). If given, it will contain
+         the set of variance values associated with the input data and
+         will be the same array as was passed to AST\_RESAMPLE$<$X$>$ via
+         the IN\_VAR argument.
+
+         If the AST\_\_USEVAR flag is not set, then no variance values
+         are being processed. In this case, this array of variance
+         values may be a dummy (e.g. one-element) array and should not
+         be used.
+      }
+      \sstsubsection{
+         NPOINT = INTEGER (Given)
+      }{
+         The number of points at which the input grid is to be
+         interpolated. This will be at least one.
+      }
+      \sstsubsection{
+         OFFSET( NPOINT ) = INTEGER (Given)
+      }{
+         For each interpolation point, this array will contain the
+         offset from the start of the OUT (and OUT\_VAR) array(s) at
+         which the interpolated value (and its variance, if required)
+         should be stored. For example, the interpolated value for
+         point number POINT should be stored in OUT(1$+$OFFSET(POINT)).
+      }
+      \sstsubsection{
+         COORDS( NPOINT, NDIM\_IN ) = DOUBLE PRECISION (Given)
+      }{
+         A 2-dimensional array containing the coordinates of the
+         points at which interpolation should be performed. These will
+         be stored so that coordinate number COORD for interpolation
+         point number POINT is found in element COORDS(POINT,COORD).
+
+         If any interpolation point has any of its coordinates equal
+         to the value AST\_\_BAD (as defined in the AST\_PAR include
+         file), then the corresponding output data (and variance)
+         should either be set to the value given by BADVAL,
+         or left unchanged, depending on whether the AST\_\_NOBAD flag is
+         specified by FLAGS.
+      }
+      \sstsubsection{
+         PARAMS( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         This will be the same array as was given via the
+         PARAMS argument of AST\_RESAMPLE$<$X$>$. You may use this to
+         pass any additional parameter values required by your
+         interpolation algorithm.
+      }
+      \sstsubsection{
+         FLAGS = INTEGER (Given)
+      }{
+         This will be the same value as was given via the FLAGS
+         argument of AST\_RESAMPLE$<$X$>$. You may test this value to
+         provide additional control over the operation of your
+         resampling algorithm. Note that the special flag values
+         AST\_\_URESAMP1, 2, 3 \& 4 are reserved for you to use for your
+         own purposes and will not clash with other pre-defined flag
+         values (see AST\_RESAMPLE$<$X$>$).
+      }
+      \sstsubsection{
+         BADVAL = $<$Xtype$>$ (Given)
+      }{
+         This will be the same value as was given for the BADVAL
+         argument of AST\_RESAMPLE$<$X$>$, and will have the same numerical
+         type as the data being processed (i.e. as elements of the IN
+         array).  It should be used to test for bad pixels in the
+         input grid (but only if the AST\_\_USEBAD flag is set via the
+         FLAGS argument) and (unless the AST\_\_NOBAD flag is set in
+         FLAGS) for identifying bad output values in the OUT (and
+         OUT\_VAR) array(s).
+      }
+      \sstsubsection{
+         OUT( $*$ ) = $<$Xtype$>$ (Returned)
+      }{
+         An array with the same numerical type as the IN
+         array, into which the interpolated data values should be
+         returned.  Note that details of the storage order and number
+         of dimensions of this array are not required, since the
+         OFFSET array contains all necessary information about where
+         each returned value should be stored.
+
+         In general, not all elements of this array (or the OUT\_VAR
+         array below) may be used in any particular invocation of the
+         routine. Those which are not used should be returned
+         unchanged.
+      }
+      \sstsubsection{
+         OUT\_VAR( $*$ ) = $<$Xtype$>$ (Returned)
+      }{
+         An optional array with the same type and size as the OUT
+         array, into which variance estimates for the resampled values
+         should be returned. This array will only be given if the
+         AST\_\_USEVAR flag is set via the FLAGS argument.
+
+         If given, it is addressed in exactly the same way (via the
+         OFFSET array) as the OUT array. The values returned should be
+         estimates of the statistical variance of the corresponding
+         values in the OUT array, on the assumption that all errors in
+         input data values are statistically independent and that
+         their variance estimates may simply be summed (with
+         appropriate weighting factors).
+
+         If the AST\_\_USEVAR flag is not set, then variance values are
+         not being processed.  In this case, this array may be a dummy
+         (e.g. one-element) array and should not be used.
+      }
+      \sstsubsection{
+         NBAD = INTEGER (Returned)
+      }{
+         This should return the number of interpolation points at
+         which no valid interpolated value could be obtained.  The maximum
+         value that should be returned is NPOINT, and the minimum is
+         zero (indicating that all output values were successfully
+         obtained).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The data type $<$Xtype$>$ indicates the numerical type of the data
+         being processed, as for AST\_RESAMPLE$<$X$>$.
+
+         \sstitem
+         This routine will typically be invoked more than once for each
+         invocation of AST\_RESAMPLE$<$X$>$.
+
+         \sstitem
+         If an error occurs within this routine, it should set the
+         STATUS argument to an error value before returning. This will
+         cause an immediate return from AST\_RESAMPLE$<$X$>$. The error value
+         AST\_\_UINER is available for this purpose, but other values may also
+         be used (e.g. if you wish to distinguish different types of error).
+         The AST\_\_UINER error value is defined in the AST\_ERR include file.
+      }
+   }
+}
+\sstroutine{
+   AST\_UKERN1
+}{
+   1-dimensional sub-pixel interpolation kernel
+}{
+   \sstdescription{
+      This is a fictitious routine which does not actually
+      exist. Instead, this description constitutes a template so that
+      you may implement a routine with this interface for yourself
+      (and give it any name you wish). Such a routine
+      may be passed via the FINTERP argument of the \htmlref{AST\_RESAMPLE$<$X$>$}{AST\_RESAMPLE$<$X$>$}
+      functions (q.v.) in order to supply a 1-dimensional
+      interpolation kernel to the algorithm which performs sub-pixel
+      interpolation during resampling of gridded data (you must also
+      set the INTERP argument of AST\_RESAMPLE$<$X$>$ to the value
+      AST\_\_UKERN1). This allows you to use your own interpolation
+      kernel in addition to those which are pre-defined.
+
+      The routine calculates the value of a 1-dimensional sub-pixel
+      interpolation kernel. This determines how the weight given to
+      neighbouring pixels in calculating an interpolated value depends
+      on the pixel\texttt{'} s offset from the interpolation point. In more than
+      one dimension, the weight assigned to a pixel is formed by
+      evaluating this 1-dimensional kernel using the offset along each
+      dimension in turn. The product of the returned values is then
+      used as the pixel weight.
+   }
+   \sstinvocation{
+      CALL AST\_UKERN1( OFFSET, PARAMS, FLAGS, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         OFFSET = DOUBLE PRECISION (Given)
+      }{
+         This will be the offset of the pixel from the interpolation
+         point, measured in pixels. This value may be positive or
+         negative, but for most practical interpolation schemes its
+         sign should be ignored.
+      }
+      \sstsubsection{
+         PARAMS( $*$ ) = DOUBLE PRECISION (Given)
+      }{
+         This will be the same array as was given via the
+         PARAMS argument of AST\_RESAMPLE$<$X$>$. You may use this to
+         pass any additional parameter values required by your kernel,
+         but note that PARAMS(1) will already have been used to specify
+         the number of neighbouring pixels which contribute to the
+         interpolated value.
+      }
+      \sstsubsection{
+         FLAGS = INTEGER (Given)
+      }{
+         This will be the same value as was given via the FLAGS
+         argument of AST\_RESAMPLE$<$X$>$. You may test this value to
+         provide additional control over the operation of your
+         routine. Note that the special flag values AST\_\_URESAMP1, 2,
+         3 \& 4 are reserved for you to use for your own purposes and
+         will not clash with other pre-defined flag
+         values (see AST\_RESAMPLE$<$X$>$).
+      }
+      \sstsubsection{
+         VALUE = DOUBLE PRECISION (Returned)
+      }{
+         The calculated kernel value,
+         which may be positive or negative.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Not all functions make good interpolation kernels. In general,
+         acceptable kernels tend to be symmetrical about zero, to have a
+         positive peak (usually unity) at zero, and to evaluate to zero
+         whenever the pixel offset has any other integral value (this
+         ensures that the interpolated values pass through the original
+         data). An interpolation kernel may or may not have regions with
+         negative values. You should consult a good book on image
+         processing for more details.
+
+         \sstitem
+         If an error occurs within this routine, it should set the
+         STATUS argument to an error value before returning. This will
+         cause an immediate return from AST\_RESAMPLE$<$X$>$. The error value
+         AST\_\_UK1ER is available for this purpose, but other values may also
+         be used (e.g. if you wish to distinguish different types of error).
+         The AST\_\_UK1ER error value is defined in the AST\_ERR include file.
+      }
+   }
+}
+\sstroutine{
+   AST\_UNFORMAT
+}{
+   Read a formatted coordinate value for a Frame axis
+}{
+   \sstdescription{
+      This function reads a formatted coordinate value (given as a
+      character string) for a \htmlref{Frame}{Frame} axis and returns the equivalent
+      numerical (double precision) value. It also returns the number
+      of characters read from the string.
+
+      The principle use of this function is in decoding user-supplied
+      input which contains formatted coordinate values. Free-format
+      input is supported as far as possible. If input is ambiguous, it
+      is interpreted with reference to the Frame\texttt{'} s attributes (in
+      particular, the Format string associated with the Frame\texttt{'} s
+      axis). This function is, in essence, the inverse of \htmlref{AST\_FORMAT}{AST\_FORMAT}.
+   }
+   \sstinvocation{
+      RESULT = AST\_UNFORMAT( THIS, AXIS, STRING, VALUE, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         AXIS = INTEGER (Given)
+      }{
+         The number of the Frame axis for which a coordinate value is to
+         be read (axis numbering starts at 1 for the first axis).
+      }
+      \sstsubsection{
+         STRING = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing the formatted coordinate value.
+         This string may contain additional information following the
+         value to be read, in which case reading stops at the first
+         character which cannot be interpreted as part of the value.
+         Any white space before or after the value is discarded.
+      }
+      \sstsubsection{
+         VALUE = DOUBLE PRECISION (Returned)
+      }{
+         The coordinate value read.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         This function applies to all Frames. See the \texttt{"} Frame Input
+         Format\texttt{"}  section below for details of the input formats
+         accepted by a basic Frame.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the input format to be suitable
+         for representing angles and times, with the resulting
+         coordinate value returned in radians.  See the \texttt{"} SkyFrame
+         Input Format\texttt{"}  section below for details of the formats
+         accepted.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The input formats accepted by a FrameSet are determined by
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_UNFORMAT = INTEGER
+      }{
+         The number of characters read from the string in order to
+         obtain the coordinate value. This will include any white
+         space which occurs before or after the value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A function value of zero (and no coordinate value) will be
+         returned, without error, if the string supplied does not contain
+         a suitably formatted value.
+
+         \sstitem
+         Beware that it is possible for a formatting error part-way
+         through an input string to terminate input before it has been
+         completely read, but to yield a coordinate value that appears
+         valid. For example, if a user types \texttt{"} 1.5R6\texttt{"}  instead of \texttt{"} 1.5E6\texttt{"} ,
+         the \texttt{"} R\texttt{"}  will terminate input, giving an incorrect coordinate
+         value of 1.5. It is therefore most important to check the return
+         value of this function to ensure that the correct number of
+         characters have been read.
+
+         \sstitem
+         An error will result if a value is read which appears to have
+         the correct format, but which cannot be converted into a valid
+         coordinate value (for instance, because the value of one or more
+         of its fields is invalid).
+
+         \sstitem
+         The string \texttt{"} $<$bad$>$\texttt{"}  is recognised as a special case and will
+         yield the coordinate value AST\_\_BAD without error. The test for
+         this string is case-insensitive and also permits embedded white
+         space.
+
+         \sstitem
+         A function result of zero will be returned and no coordinate
+         value will be returned via the VALUE argument if this function
+         is invoked with the AST error status set, or if it should fail
+         for any reason.
+      }
+   }
+   \sstdiytopic{
+      Frame Input Format
+   }{
+      The input format accepted for a basic Frame axis is as follows:
+      \sstitemlist{
+
+         \sstitem
+         An optional sign, followed by:
+
+         \sstitem
+         A sequence of one or more digits possibly containing a decimal point,
+         followed by:
+
+         \sstitem
+         An optional exponent field.
+
+         \sstitem
+         The exponent field, if present, consists of \texttt{"} E\texttt{"}  or \texttt{"} e\texttt{"}
+         followed by a possibly signed integer.
+
+      }
+      Examples of acceptable Frame input formats include:
+      \sstitemlist{
+
+         \sstitem
+         99
+
+         \sstitem
+         1.25
+
+         \sstitem
+         -1.6
+
+         \sstitem
+         1E8
+
+         \sstitem
+         -.99e-17
+
+         \sstitem
+         $<$bad$>$
+      }
+   }
+   \sstdiytopic{
+      SkyFrame Input Format
+   }{
+      The input format accepted for a SkyFrame axis is as follows:
+      \sstitemlist{
+
+         \sstitem
+         An optional sign, followed by between one and three fields
+         representing either degrees, arc-minutes, arc-seconds or hours,
+         minutes, seconds (e.g. \texttt{"} -12 42 03\texttt{"} ).
+
+         \sstitem
+         Each field should consist of a sequence of one or more digits,
+         which may include leading zeros. At most one field may contain a
+         decimal point, in which case it is taken to be the final field
+         (e.g. decimal degrees might be given as \texttt{"} 124.707\texttt{"} , while degrees
+         and decimal arc-minutes might be given as \texttt{"} -13 33.8\texttt{"} ).
+
+         \sstitem
+         The first field given may take any value, allowing angles and
+         times outside the conventional ranges to be
+         represented. However, subsequent fields must have values of less
+         than 60 (e.g. \texttt{"} 720 45 31\texttt{"}  is valid, whereas \texttt{"} 11 45 61\texttt{"}  is not).
+
+         \sstitem
+         Fields may be separated by white space or by \texttt{"} :\texttt{"}  (colon), but
+         the choice of separator must be used consistently throughout the
+         value. Additional white space may be present around fields and
+         separators (e.g. \texttt{"} - 2: 04 : 7.1\texttt{"} ).
+
+         \sstitem
+         The following field identification characters may be used as
+         separators to replace either of those above (or may be appended
+         to the final field), in order to identify the field to which
+         they are appended: \texttt{"} d\texttt{"} ---degrees; \texttt{"} h\texttt{"} ---hours; \texttt{"} m\texttt{"} ---minutes of
+         arc or time; \texttt{"} s\texttt{"} ---seconds of arc or time; \texttt{"} \texttt{'} \texttt{"}  (single
+         quote)---minutes of arc; \texttt{"} \texttt{"} \texttt{"}  (double quote)---seconds of arc.
+         Either lower or upper case may be used.  Fields must be given in
+         order of decreasing significance (e.g. \texttt{"} -11D 3\texttt{'}  14.4\texttt{"} \texttt{"}  or
+         \texttt{"} 22h14m11.2s\texttt{"} ).
+
+         \sstitem
+         The presence of any of the field identification characters
+         \texttt{"} d\texttt{"} , \texttt{"} \texttt{'} \texttt{"}  (single quote) or \texttt{"} \texttt{"} \texttt{"}  (double quote) indicates that the
+         value is to be interpreted as an angle. Conversely, the presence
+         of \texttt{"} h\texttt{"}  indicates that it is to be interpreted as a time (with 24
+         hours corresponding to 360 degrees). Incompatible angle/time
+         identification characters may not be mixed (e.g. \texttt{"} 10h14\texttt{'} 3\texttt{"} \texttt{"}  is
+         not valid).  The remaining field identification characters and
+         separators do not specify a preference for an angle or a time
+         and may be used with either.
+
+         \sstitem
+         If no preference for an angle or a time is expressed anywhere
+         within the value, it is interpreted as an angle if the Format
+         attribute string associated with the SkyFrame axis generates an
+         angle and as a time otherwise. This ensures that values produced
+         by AST\_FORMAT are correctly interpreted by AST\_UNFORMAT.
+
+         \sstitem
+         Fields may be omitted, in which case they default to zero. The
+         remaining fields may be identified by using appropriate field
+         identification characters (see above) and/or by adding extra
+         colon separators (e.g. \texttt{"} -05m13s\texttt{"}  is equivalent to \texttt{"} -:05:13\texttt{"} ). If
+         a field is not identified explicitly, it is assumed that
+         adjacent fields have been given, after taking account of any
+         extra separator characters (e.g. \texttt{"} 14:25.4s\texttt{"}  specifies minutes
+         and seconds, while \texttt{"} 14::25.4s\texttt{"}  specifies degrees and seconds).
+
+         \sstitem
+         If fields are omitted in such a way that the remaining ones
+         cannot be identified uniquely (e.g. \texttt{"} 01:02\texttt{"} ), then the first
+         field (either given explicitly or implied by an extra leading
+         colon separator) is taken to be the most significant field that
+         AST\_FORMAT would produce when formatting a value (using the
+         Format attribute associated with the SkyFrame axis).  By
+         default, this means that the first field will normally be
+         interpreted as degrees or hours. However, if this does not
+         result in consistent field identification, then the last field
+         (either given explicitly or implied by an extra trailing colon
+         separator) is taken to to be the least significant field that
+         AST\_FORMAT would produce.
+
+      }
+      This final convention is intended to ensure that values formatted
+      by AST\_FORMAT which contain less than three fields will be
+      correctly interpreted if read back using AST\_UNFORMAT, even if
+      they do not contain field identification characters.
+
+      Examples of acceptable SkyFrame input formats (with
+      interpretation in parentheses) include:
+      \sstitemlist{
+
+         \sstitem
+         -14d 13m 22.2s (-14d 13\texttt{'}  22.2\texttt{"} )
+
+         \sstitem
+         $+$ 12:34:56.7 (12d 34\texttt{'}  56.7\texttt{"}  or 12h 34m 56.7s)
+
+         \sstitem
+         001 : 02 : 03.4 (1d 02\texttt{'}  03.4\texttt{"}  or 1h 02m 03.4s)
+
+         \sstitem
+         22h 30 (22h 30m 00s)
+
+         \sstitem
+         136::10\texttt{"}  (136d 00\texttt{'}  10\texttt{"}  or 136h 00m 10s)
+
+         \sstitem
+         -14M 27S (-0d 14\texttt{'}  27\texttt{"}  or -0h 14m 27s)
+
+         \sstitem
+         -:14: (-0d 14\texttt{'}  00\texttt{"}  or -0h 14m 00s)
+
+         \sstitem
+         -::4.1 (-0d 00\texttt{'}  04.1\texttt{"}  or -0h 00m 04.1s)
+
+         \sstitem
+         .9\texttt{"}  (0d 00\texttt{'}  00.9\texttt{"} )
+
+         \sstitem
+         d12m (0d 12\texttt{'}  00\texttt{"} )
+
+         \sstitem
+         H 12:22.3s (0h 12m 22.3s)
+
+         \sstitem
+         $<$bad$>$ (AST\_\_BAD)
+
+      }
+      Where alternative interpretations are shown, the choice of angle or
+      time depends on the associated \htmlref{Format(axis)}{Format(axis)} attribute.
+   }
+}
+\sstroutine{
+   AST\_UNITMAP
+}{
+   Create a UnitMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{UnitMap}{UnitMap} and optionally initialises
+      its attributes.
+
+      A UnitMap is a unit (null) \htmlref{Mapping}{Mapping} that has no effect on the
+      coordinates supplied to it. They are simply copied. This can be
+      useful if a Mapping is required (e.g. to pass to another
+      routine) but you do not want it to have any effect.
+   }
+   \sstinvocation{
+      RESULT = AST\_UNITMAP( NCOORD, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NCOORD = INTEGER (Given)
+      }{
+         The number of input and output coordinates (these numbers are
+         necessarily the same).
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new UnitMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_UNITMAP = INTEGER
+      }{
+         A pointer to the new UnitMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_UNITNORMMAP
+}{
+   Create a UnitNormMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{UnitNormMap}{UnitNormMap} and optionally initialises its
+      attributes.
+
+      The forward transformation of a UnitNormMap subtracts the specified centre
+      and then transforms the resulting vector to a unit vector and the vector norm.
+      The output contains one more coordinate than the input: the initial \htmlref{Nin}{Nin} outputs
+      are in the same order as the input; the final output is the norm.
+
+      The inverse transformation of a UnitNormMap multiplies each component
+      of the provided vector by the provided norm and adds the specified centre.
+      The output contains one fewer coordinate than the input: the initial Nin inputs
+      are in the same order as the output; the final input is the norm.
+
+      UnitNormMap enables radially symmetric transformations, as follows:
+      \sstitemlist{
+
+         \sstitem
+         apply a UnitNormMap to produce a unit vector and norm (radius)
+
+         \sstitem
+         apply a one-dimensional mapping to the norm (radius), while passing the unit vector unchanged
+
+         \sstitem
+         apply the same UnitNormMap in the inverse direction to produce the result
+      }
+   }
+   \sstinvocation{
+      RESULT = AST\_UNITNORMMAP( NCOORD, CENTRE, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NCOORD = INTEGER (Given)
+      }{
+         The number of coordinate values for each point to be
+         transformed (i.e. the number of dimensions of the space in
+         which the points will reside). Output will include one additional coordinate.
+      }
+      \sstsubsection{
+         CENTRE( NCOORD ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing the values to be subtracted from the input
+         coordinates before computing unit vector and norm. A separate
+         value must be supplied for each coordinate.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new UnitNormMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_UNITNORMMAP = INTEGER
+      }{
+         A pointer to the new UnitNormMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_VERSION
+}{
+   Return the version of the AST library being used
+}{
+   \sstdescription{
+      This function
+      returns an integer representing the version of the AST library
+      being used. The library version is formatted as a string such as
+      \texttt{"} 2.0-7\texttt{"}  which contains integers representing the \texttt{"} major version\texttt{"}  (2),
+      the \texttt{"} minor version\texttt{"}  (0) and the \texttt{"} release\texttt{"}  (7). The integer returned
+      by this function combines all three integers together into a single
+      integer using the expresion:
+
+      (major version)$*$1E6 $+$ (minor version)$*$1E3 $+$ (release)
+   }
+   \sstinvocation{
+      RESULT = AST\_VERSION()
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Object}{Object}
+      }{
+         This routine applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_VERSION = INTEGER
+      }{
+         The major version, minor version and release numbers for the AST
+         library, encoded as a single integer.
+      }
+   }
+}
+\sstroutine{
+   AST\_WARNINGS
+}{
+   Returns any warnings issued by the previous read or write operation
+}{
+   \sstdescription{
+      This function returns an AST \htmlref{KeyMap}{KeyMap} object holding the text of any
+      warnings issued as a result of the previous invocation of the
+      \htmlref{AST\_READ}{AST\_READ} or \htmlref{AST\_WRITE}{AST\_WRITE}
+      function on the \htmlref{Channel}{Channel}. If no warnings were issued, a
+      AST\_\_NULL
+      will be returned.
+
+      Such warnings are non-fatal and will not prevent the
+      read or write operation succeeding. However, the converted object
+      may not be identical to the original object in all respects.
+      Differences which would usually be deemed as insignificant in most
+      usual cases will generate a warning, whereas more significant
+      differences will generate an error.
+
+      The \texttt{"} \htmlref{Strict}{Strict}\texttt{"}  attribute allows this warning facility to be switched
+      off, so that a fatal error is always reported for any conversion
+      error.
+   }
+   \sstinvocation{
+      RESULT = AST\_WARNINGS( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Channel.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         The basic Channel class generates a warning when ever an
+         un-recognised item is encountered whilst reading an \htmlref{Object}{Object} from
+         an external data source. If Strict is zero (the default), then
+         unexpected items in the Object description are simply ignored,
+         and any remaining items are used to construct the returned
+         Object. If Strict is non-zero, an error will be reported and a
+         NULL Object pointer returned if any unexpected items are
+         encountered.
+
+         As AST continues to be developed, new attributes are added
+         occasionally to selected classes. If an older version of AST is
+         used to read external Object descriptions created by a more
+         recent version of AST, then the Channel class will, by default,
+         ignore the new attributes, using the remaining attributes to
+         construct the Object. This is usually a good thing. However,
+         since external Object descriptions are often stored in plain
+         text, it is possible to edit them using a text editor. This
+         gives rise to the possibility of genuine errors in the
+         description due to finger-slips, typos, or simple
+         mis-understanding. Such inappropriate attributes will be ignored
+         if Strict is left at its default zero value. This will cause the
+         mis-spelled attribute to revert to its default value,
+         potentially causing subtle changes in the behaviour of
+         application software. If such an effect is suspected, the Strict
+         attribute can be set non-zero, resulting in the erroneous
+         attribute being identified in an error message.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         The returned KeyMap will contain warnings for all conditions
+         listed in the \htmlref{Warnings}{Warnings} attribute.
+      }
+      \sstsubsection{
+         \htmlref{XmlChan}{XmlChan}
+      }{
+         Reports conversion errors that result in what are usally
+         insignificant  changes.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_WARNINGS = INTEGER
+      }{
+         A pointer to the KeyMap holding the warning messages, or
+         AST\_\_NULL
+         if no warnings were issued during the previous read operation.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned KeyMap uses keys of the form \texttt{"} Warning\_1\texttt{"} ,
+         \texttt{"} Warning\_2\texttt{"} , etc.
+
+         \sstitem
+         A value of
+         AST\_\_NULL will be returned if this function is invoked with STATUS
+         set to an error value,
+         or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   AST\_WCSMAP
+}{
+   Create a WcsMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{WcsMap}{WcsMap} and optionally initialises its
+      attributes.
+
+      A WcsMap is used to represent sky coordinate projections as
+      described in the (draft) FITS world coordinate system (FITS-WCS)
+      paper by E.W. Griesen and M. Calabretta (A \& A, in preparation).
+      This paper defines a set of functions, or sky projections, which
+      transform longitude-latitude pairs representing spherical
+      celestial coordinates into corresponding pairs of Cartesian
+      coordinates (and vice versa).
+
+      A WcsMap is a specialised form of \htmlref{Mapping}{Mapping} which implements these
+      sky projections and applies them to a specified pair of coordinates.
+      All the projections in the FITS-WCS paper are supported, plus the now
+      deprecated \texttt{"} TAN with polynomial correction terms\texttt{"}  projection which
+      is refered to here by the code \texttt{"} TPN\texttt{"} . Using the FITS-WCS terminology,
+      the transformation is between \texttt{"} native spherical\texttt{"}  and \texttt{"} projection
+      plane\texttt{"}  coordinates.  These coordinates may, optionally, be embedded in
+      a space with more than two dimensions, the remaining coordinates being
+      copied unchanged. Note, however, that for consistency with other AST
+      facilities, a WcsMap handles coordinates that represent angles
+      in radians (rather than the degrees used by FITS-WCS).
+
+      The type of FITS-WCS projection to be used and the coordinates
+      (axes) to which it applies are specified when a WcsMap is first
+      created. The projection type may subsequently be determined
+      using the \htmlref{WcsType}{WcsType} attribute and the coordinates on which it acts
+      may be determined using the \htmlref{WcsAxis(lonlat)}{WcsAxis(lonlat)} attribute.
+
+      Each WcsMap also allows up to 100 \texttt{"} projection parameters\texttt{"}  to be
+      associated with each axis. These specify the precise form of the
+      projection, and are accessed using \htmlref{PVi\_m}{PVi\_m} attribute, where \texttt{"} i\texttt{"}  is
+      the integer axis index (starting at 1), and m is an integer
+      \texttt{"} parameter index\texttt{"}  in the range 0 to 99. The number of projection
+      parameters required by each projection, and their meanings, are
+      dependent upon the projection type (most projections either do not
+      use any projection parameters, or use parameters 1 and 2 associated
+      with the latitude axis). Before creating a WcsMap you should consult
+      the FITS-WCS paper for details of which projection parameters are
+      required, and which have defaults. When creating the WcsMap, you must
+      explicitly set values for all those required projection parameters
+      which do not have defaults defined in this paper.
+   }
+   \sstinvocation{
+      RESULT = AST\_WCSMAP( NCOORD, TYPE, LONAX, LATAX, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NCOORD = INTEGER (Given)
+      }{
+         The number of coordinate values for each point to be
+         transformed (i.e. the number of dimensions of the space in
+         which the points will reside). This must be at least 2. The
+         same number is applicable to both input and output points.
+      }
+      \sstsubsection{
+         TYPE = INTEGER (Given)
+      }{
+         The type of FITS-WCS projection to apply. This should be
+         given as a symbolic value such as AST\_\_TAN (for a tangent
+         plane projection), where the characters following the double
+         underscore give the projection type code (in upper case) as
+         used in the FITS-WCS \texttt{"} CTYPEi\texttt{"}  keyword. You should consult the
+         FITS-WCS paper for a list of the available projections. The
+         additional code of AST\_\_TPN can be supplied which represents a
+         TAN projection with polynomial correction terms as defined in an
+         early draft of the FITS-WCS paper.
+      }
+      \sstsubsection{
+         LONAX = INTEGER (Given)
+      }{
+         The index of the longitude axis. This should lie in the range
+         1 to NCOORD.
+      }
+      \sstsubsection{
+         LATAX = INTEGER (Given)
+      }{
+         The index of the latitude axis. This should lie in the range
+         1 to NCOORD and be distinct from LONAX.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new WcsMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+
+         If the sky projection to be implemented requires projection
+         parameter values to be set, then this should normally be done
+         here via the PVi\_m attribute (see the \texttt{"} Examples\texttt{"}
+         section). Setting values for these parameters is mandatory if
+         they do not have default values (as defined in the FITS-WCS
+         paper).
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_WCSMAP = INTEGER
+      }{
+         A pointer to the new WcsMap.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         WCSMAP = AST\_WCSMAP( 2, AST\_\_MER, 1, 2, \texttt{'}  \texttt{'} , STATUS )
+      }{
+         Creates a WcsMap that implements a FITS-WCS Mercator
+         projection on pairs of coordinates, with coordinates 1 and 2
+         representing the longitude and latitude respectively. Note
+         that the FITS-WCS Mercator projection does not require any
+         projection parameters.
+      }
+      \sstexamplesubsection{
+         WCSMAP = AST\_WCSMAP( 3, AST\_\_COE, 2, 3, \texttt{'} PV3\_1=40.0\texttt{'} , STATUS )
+      }{
+         Creates a WcsMap that implements a FITS-WCS conical equal
+         area projection. The WcsMap acts on points in a 3-dimensional
+         space; coordinates 2 and 3 represent longitude and latitude
+         respectively, while the values of coordinate 1 are copied
+         unchanged.  \htmlref{Projection}{Projection} parameter 1 associatyed with the latitude
+         axis (corresponding to FITS keyword \texttt{"} PV3\_1\texttt{"} ) is required and has
+         no default, so is set explicitly to 40.0 degrees. Projection
+         parameter 2 (corresponding to FITS keyword \texttt{"} PV3\_2\texttt{"} ) is required
+         but has a default of zero, so need not be specified.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The forward transformation of a WcsMap converts between
+         FITS-WCS \texttt{"} native spherical\texttt{"}  and \texttt{"} relative physical\texttt{"}  coordinates,
+         while the inverse transformation converts in the opposite
+         direction. This arrangement may be reversed, if required, by
+         using \htmlref{AST\_INVERT}{AST\_INVERT} or by setting the \htmlref{Invert}{Invert} attribute to a non-zero
+         value.
+
+         \sstitem
+         If any set of coordinates cannot be transformed (for example,
+         many projections do not cover the entire celestial sphere), then
+         a WcsMap will yield coordinate values of AST\_\_BAD.
+
+         \sstitem
+         The validity of any projection parameters given via the PVi\_m
+         parameter in the OPTIONS string is not checked by this
+         function. However, their validity is checked when the resulting
+         WcsMap is used to transform coordinates, and an error will
+         result if the projection parameters do not satisfy all the
+         required constraints (as defined in the FITS-WCS paper).
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_WINMAP
+}{
+   Create a WinMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{WinMap}{WinMap} and optionally initialises its
+      attributes.
+
+      A Winmap is a linear \htmlref{Mapping}{Mapping} which transforms a rectangular
+      window in one coordinate system into a similar window in another
+      coordinate system by scaling and shifting each axis (the window
+      edges being parallel to the coordinate axes).
+
+      A WinMap is specified by giving the coordinates of two opposite
+      corners (A and B) of the window in both the input and output
+      coordinate systems.
+   }
+   \sstinvocation{
+      RESULT = AST\_WINMAP( NCOORD, INA, INB, OUTA, OUTB, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NCOORD = INTEGER (Given)
+      }{
+         The number of coordinate values for each point to be
+         transformed (i.e. the number of dimensions of the space in
+         which the points will reside). The same number is applicable
+         to both input and output points.
+      }
+      \sstsubsection{
+         INA( NCOORD ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing the
+         coordinates of corner A of the window in the input coordinate
+         system.
+      }
+      \sstsubsection{
+         INB( NCOORD ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing the
+         coordinates of corner B of the window in the input coordinate
+         system.
+      }
+      \sstsubsection{
+         OUTA( NCOORD ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing the
+         coordinates of corner A of the window in the output coordinate
+         system.
+      }
+      \sstsubsection{
+         OUTB( NCOORD ) = DOUBLE PRECISION (Given)
+      }{
+         An array containing the
+         coordinates of corner B of the window in the output coordinate
+         system.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new WinMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_WINMAP = INTEGER
+      }{
+         A pointer to the new WinMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   AST\_WRITE
+}{
+   Write an Object to a Channel
+}{
+   \sstdescription{
+      This function writes an \htmlref{Object}{Object} to a \htmlref{Channel}{Channel}, appending it to any
+      previous Objects written to that Channel.
+   }
+   \sstinvocation{
+      RESULT = AST\_WRITE( THIS, OBJECT, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the Channel.
+      }
+      \sstsubsection{
+         OBJECT = INTEGER (Given)
+      }{
+         Pointer to the Object which is to be written.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         If the FitsChan uses a foreign encoding (e.g. FITS-WCS) rather
+         than the native AST encoding, then storing values in the
+         FitsChan for keywords NAXIS1, NAXIS2, etc., before invoking
+         AST\_WRITE
+         can help to produce a successful write.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_WRITE = INTEGER
+      }{
+         The number of Objects written to the Channel by this
+         invocation of AST\_WRITE (normally, this will be one).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with STATUS set to an error value, or if it should fail for any
+         reason.
+
+         \sstitem
+         Invoking this function will usually cause the sink function
+         associated with the channel to be called in order to transfer a
+         textual description of the supplied object to some external data
+         store. However, the FitsChan class behaves differently. Invoking
+         this function on a FitsChan causes new FITS header cards to be
+         added to an internal buffer (the sink function is not invoked).
+         This buffer is written out through the sink function only when the
+         FitsChan is deleted.
+      }
+   }
+}
+\sstroutine{
+   AST\_WRITEFITS
+}{
+   Write out all cards in a FitsChan to the sink function
+}{
+   \sstdescription{
+      This routine
+      writes out all cards currently in the \htmlref{FitsChan}{FitsChan}. If the \htmlref{SinkFile}{SinkFile}
+      attribute is set, they will be written out to the specified sink file.
+      Otherwise, they will be written out using the sink function specified
+      when the FitsChan was created. All cards are then deleted from the
+      FitsChan.
+   }
+   \sstinvocation{
+      CALL AST\_WRITEFITS( THIS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         THIS = INTEGER (Given)
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the SinkFile is unset, and no sink function is available, this
+         method simply empties the FitsChan, and is then equivalent to
+         \htmlref{AST\_EMPTYFITS}{AST\_EMPTYFITS}.
+
+         \sstitem
+         This method attempt to execute even if an error has occurred
+         previously.
+      }
+   }
+}
+\sstroutine{
+   AST\_XMLCHAN
+}{
+   Create an XmlChan
+}{
+   \sstdescription{
+      This function creates a new \htmlref{XmlChan}{XmlChan} and optionally initialises
+      its attributes.
+
+      A XmlChan is a specialised form of \htmlref{Channel}{Channel} which supports XML I/O
+      operations. Writing an \htmlref{Object}{Object} to an XmlChan (using
+      \htmlref{AST\_WRITE}{AST\_WRITE}) will, if the Object is suitable, generate an
+      XML description of that Object, and reading from an XmlChan will
+      create a new Object from its XML description.
+
+      Normally, when you use an XmlChan, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  routines which connect it to an external data store
+      by reading and writing the resulting XML text. By default, however,
+      an XmlChan will read from standard input and write to standard
+      output.
+
+      Alternatively, an XmlChan can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+   }
+   \sstinvocation{
+      RESULT = AST\_XMLCHAN( SOURCE, SINK, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         SOURCE = SUBROUTINE (Given)
+      }{
+         A source routine, which is a subroutine which takes a single
+         integer error status argument.   If no value has been set
+         for the SourceFile attribute, this routine will be used by
+         the XmlChan to obtain lines of input text. On each
+         invocation, it should read the next input line from some
+         external data store, and then return the resulting text to
+         the AST library by calling \htmlref{AST\_PUTLINE}{AST\_PUTLINE}. It should supply a
+         negative line length when there are no more lines to read.
+         If an error occurs, it should set its own error status
+         argument to an error value before returning.
+
+         If the null routine AST\_NULL is suppied as the SOURCE value,
+         and no value has been set for the SourceFile attribute,
+         the XmlChan will read from standard input instead.
+      }
+      \sstsubsection{
+         SINK = SUBROUTINE (Given)
+      }{
+         A sink routine, which is a subroutine which takes a single
+         integer error status argument.  If no value has been set
+         for the SinkFile attribute, this routine will be used by
+         the XmlChan to deliver lines of output text. On each
+         invocation, it should obtain the next output line from the
+         AST library by calling \htmlref{AST\_GETLINE}{AST\_GETLINE}, and then deliver the
+         resulting text to some external data store.  If an error
+         occurs, it should set its own error status argument to an
+         error value before returning.
+
+         If the null routine AST\_NULL is suppied as the SINK value,
+         and no value has been set for the SinkFile attribute,
+         the XmlChan will write to standard output instead.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new XmlChan. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_XMLCHAN = INTEGER
+      }{
+         A pointer to the new XmlChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The names of the routines supplied for the SOURCE and SINK
+         arguments should appear in EXTERNAL statements in the Fortran
+         routine which invokes AST\_XMLCHAN. However, this is not generally
+         necessary for the null routine AST\_NULL (so long as the AST\_PAR
+         include file has been used).
+
+         \sstitem
+         If the external data source or sink uses a character encoding
+         other than ASCII, the supplied source and sink functions should
+         translate between the external character encoding and the internal
+         ASCII encoding used by AST.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+
+         \sstitem
+         Note that the null routine AST\_NULL (one underscore) is
+         different to AST\_\_NULL (two underscores), which is the null Object
+         pointer.
+      }
+   }
+}
+\sstroutine{
+   AST\_ZOOMMAP
+}{
+   Create a ZoomMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{ZoomMap}{ZoomMap} and optionally initialises its
+      attributes.
+
+      A ZoomMap is a \htmlref{Mapping}{Mapping} which \texttt{"} zooms\texttt{"}  a set of points about the
+      origin by multiplying all coordinate values by the same scale
+      factor (the inverse transformation is performed by dividing by
+      this scale factor).
+   }
+   \sstinvocation{
+      RESULT = AST\_ZOOMMAP( NCOORD, ZOOM, OPTIONS, STATUS )
+   }
+   \sstarguments{
+      \sstsubsection{
+         NCOORD = INTEGER (Given)
+      }{
+         The number of coordinate values for each point to be
+         transformed (i.e. the number of dimensions of the space in
+         which the points will reside). The same number is applicable
+         to both input and output points.
+      }
+      \sstsubsection{
+         ZOOM = DOUBLE PRECISION (Given)
+      }{
+         Initial scale factor by which coordinate values should be
+         multiplied (by the forward transformation) or divided (by the
+         inverse transformation). This factor may subsequently be
+         changed via the ZoomMap\texttt{'} s \htmlref{Zoom}{Zoom} attribute. It may be positive
+         or negative, but should not be zero.
+      }
+      \sstsubsection{
+         OPTIONS = CHARACTER $*$ ( $*$ ) (Given)
+      }{
+         A character string containing an optional comma-separated
+         list of attribute assignments to be used for initialising the
+         new ZoomMap. The syntax used is identical to that for the
+         \htmlref{AST\_SET}{AST\_SET} routine.
+      }
+      \sstsubsection{
+         STATUS = INTEGER (Given and Returned)
+      }{
+         The global status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         AST\_ZOOMMAP = INTEGER
+      }{
+         A pointer to the new ZoomMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with STATUS set to an error value, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\normalsize
+
+\cleardoublepage
+\section{\label{ss:attributedescriptions}AST Attribute Descriptions}
+\small
+\sstroutine{
+   Abbrev(axis)
+}{
+   Abbreviate leading fields within numerical axis labels?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether matching leading fields should be removed from adjacent
+      numerical axis labels. It takes a separate value for each physical
+      axis of a \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} Abbrev(2)=0\texttt{"}
+      specifies that matching leading fields should not be removed on
+      the second axis.
+
+      If the Abbrev value of a Plot is non-zero (the default), then
+      leading fields will be removed from adjacent axis labels if they
+      are equal.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} Abbrev\texttt{"}  instead of
+         \texttt{"} Abbrev(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the Abbrev(1) value.
+      }
+   }
+}
+\sstroutine{
+   Adaptive
+}{
+   Should the area adapt to changes in the coordinate system?
+}{
+   \sstdescription{
+      The coordinate system represented by a \htmlref{Region}{Region} may be changed by
+      assigning new values to attributes such as \htmlref{System}{System}, Unit, etc.
+      For instance, a Region representing an area on the sky in ICRS
+      coordinates may have its System attribute changed so that it
+      represents (say) Galactic coordinates instead of ICRS. This
+      attribute controls what happens when the coordinate system
+      represented by a Region is changed in this way.
+
+      If Adaptive is non-zero (the default), then area represented by the
+      Region adapts to the new coordinate system. That is, the numerical
+      values which define the area represented by the Region are changed
+      by mapping them from the old coordinate system into the new coordinate
+      system. Thus the Region continues to represent the same physical
+      area.
+
+      If Adaptive is zero, then area represented by the Region does not adapt
+      to the new coordinate system. That is, the numerical values which
+      define the area represented by the Region are left unchanged. Thus
+      the physical area represented by the Region will usually change.
+
+      As an example, consider a Region describe a range of wavelength from
+      2000 Angstrom to 4000 Angstrom. If the Unit attribute for the Region
+      is changed from Angstrom to \texttt{"} nm\texttt{"}  (nanometre), what happens depends
+      on the setting of Adaptive. If Adaptive is non-zero, the \htmlref{Mapping}{Mapping}
+      from the old to the new coordinate system is found. In this case it
+      is a simple scaling by a factor of 0.1 (since 1 Angstrom is 0.1 nm).
+      This Mapping is then used to modify the numerical values within the
+      Region, changing 2000 to 200 and 4000 to 400. Thus the modified
+      region represents 200 nm to 400 nm, the same physical space as
+      the original 2000 Angstrom to 4000 Angstrom. However, if Adaptive
+      had been zero, then the numerical values would not have been changed,
+      resulting in the final Region representing 2000 nm to 4000 nm.
+
+      Setting Adaptive to zero can be necessary if you want correct
+      inaccurate attribute settings in an existing Region. For instance,
+      when creating a Region you may not know what \htmlref{Epoch}{Epoch} value to use, so
+      you would leave Epoch unset resulting in some default value being used.
+      If at some later point in the application, the correct Epoch value
+      is determined, you could assign the correct value to the Epoch
+      attribute. However, you would first need to set Adaptive temporarily
+      to zero, because otherwise the area represented by the Region would
+      be Mapped from the spurious default Epoch to the new correct Epoch,
+      which is not what is required.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AlignOffset
+}{
+   Align SkyFrames using the offset coordinate system?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how a \htmlref{SkyFrame}{SkyFrame}
+      behaves when it is used (by
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} or \htmlref{AST\_CONVERT}{AST\_CONVERT}) as a template to match another (target)
+      SkyFrame. It determines the coordinate system in which the two
+      SkyFrames are aligned if a match occurs.
+
+      If the template and target SkyFrames both have defined offset coordinate
+      systems (i.e. the \htmlref{SkyRefIs}{SkyRefIs} attribute is set to either \texttt{"} Origin\texttt{"}  or \texttt{"}
+      Pole\texttt{"} ), and they both have a non-zero value for AlignOffset, then
+      alignment occurs within the offset coordinate systems (that is, a
+      \htmlref{UnitMap}{UnitMap} will always be used to align the two SkyFrames). If either
+      the template or target SkyFrame has zero (the default value) for
+      AlignOffset, or if either SkyFrame has SkyRefIs set to \texttt{"} Ignored\texttt{"} , then
+      alignment occurring within the coordinate system specified by the
+      \htmlref{AlignSystem}{AlignSystem} attribute.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AlignSideBand
+}{
+   Should the SideBand attribute be taken into account when aligning
+   this \htmlref{DSBSpecFrame}{DSBSpecFrame} with another DSBSpecFrame?
+}{
+   \sstdescription{
+      This attribute controls how a DSBSpecFrame behaves when an attempt
+      is made to align it with another DSBSpecFrame using
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} or \htmlref{AST\_CONVERT}{AST\_CONVERT}.
+      If both DSBSpecFrames have a non-zero value for AlignSideBand, the
+      value of the \htmlref{SideBand}{SideBand} attribute in each DSBSpecFrame is used so that
+      alignment occurs between sidebands. That is, if one DSBSpecFrame
+      represents USB and the other represents LSB then
+      AST\_FINDFRAME and AST\_CONVERT
+      will recognise that the DSBSpecFrames represent different sidebands
+      and will take this into account when constructing the \htmlref{Mapping}{Mapping} that
+      maps positions in one DSBSpecFrame into the other. If AlignSideBand
+      in either DSBSpecFrame is set to zero, then the values of the SideBand
+      attributes are ignored. In the above example, this would result in a
+      frequency in the first DSBSpecFrame being mapped onto the same
+      frequency in the second DSBSpecFrame, even though those frequencies
+      refer to different sidebands. In other words, if either AlignSideBand
+      attribute is zero, then the two DSBSpecFrames aligns like basic
+      SpecFrames. The default value for AlignSideBand is zero.
+
+      When AST\_FINDFRAME or AST\_CONVERT
+      is used on two DSBSpecFrames (potentially describing different spectral
+      coordinate systems and/or sidebands), it returns a Mapping which can be
+      used to transform a position in one DSBSpecFrame into the corresponding
+      position in the other. The Mapping is made up of the following steps in
+      the indicated order:
+
+      \sstitemlist{
+
+         \sstitem
+         If both DSBSpecFrames have a value of 1 for the AlignSideBand
+         attribute, map values from the target\texttt{'} s current sideband (given by its
+         SideBand attribute) to the observed sideband (whether USB or LSB). If
+         the target already represents the observed sideband, this step will
+         leave the values unchanged. If either of the two DSBSpecFrames have a
+         value of zero for its AlignSideBand attribute, then this step is omitted.
+
+         \sstitem
+         Map the values from the spectral system of the target to the spectral
+         system of the template. This Mapping takes into account all the
+         inherited \htmlref{SpecFrame}{SpecFrame} attributes such as \htmlref{System}{System}, \htmlref{StdOfRest}{StdOfRest}, Unit, etc.
+
+         \sstitem
+         If both DSBSpecFrames have a value of 1 for the AlignSideBand
+         attribute, map values from the result\texttt{'} s observed sideband to the
+         result\texttt{'} s current sideband (given by its SideBand attribute). If the
+         result already represents the observed sideband, this step will leave
+         the values unchanged. If either of the two DSBSpecFrames have a value
+         of zero for its AlignSideBand attribute, then this step is omitted.
+      }
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         DSBSpecFrame
+      }{
+         All DSBSpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AlignSpecOffset
+}{
+   Align SpecFrames using the offset coordinate system?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how a \htmlref{SpecFrame}{SpecFrame}
+      behaves when it is used (by
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} or \htmlref{AST\_CONVERT}{AST\_CONVERT}) as a template to match another (target)
+      SpecFrame. It determines whether alignment occurs between the offset
+      values defined by the current value of the SpecOffset attribute, or
+      between the corresponding absolute spectral values.
+
+      The default value of zero results in the two SpecFrames being aligned
+      so that a given absolute spectral value in one is mapped to the same
+      absolute value in the other. A non-zero value results in the SpecFrames
+      being aligned so that a given offset value in one is mapped to the same
+      offset value in the other.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SpecFrame
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AlignStdOfRest
+}{
+   Standard of rest to use when aligning SpecFrames
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{SpecFrame}{SpecFrame} behaves when it is used (by
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} or \htmlref{AST\_CONVERT}{AST\_CONVERT}) as a template to match another (target)
+      SpecFrame. It identifies the standard of rest in which alignment is
+      to occur. See the \htmlref{StdOfRest}{StdOfRest} attribute for a desription of the values
+      which may be assigned to this attribute. The default AlignStdOfRest
+      value is \texttt{"} Helio\texttt{"}  (heliographic).
+
+      When AST\_FindFrame or AST\_CONVERT is used on two SpecFrames (potentially
+      describing different spectral coordinate systems), it returns a \htmlref{Mapping}{Mapping}
+      which can be used to transform a position in one SpecFrame into the
+      corresponding position in the other. The Mapping is made up of the
+      following steps in the indicated order:
+
+      \sstitemlist{
+
+         \sstitem
+         Map values from the system used by the target (wavelength,
+         apparent radial velocity, etc) to the system specified by the
+         \htmlref{AlignSystem}{AlignSystem} attribute, using the target\texttt{'} s rest frequency if necessary.
+
+         \sstitem
+         Map these values from the target\texttt{'} s standard of rest to the standard of
+         rest specified by the AlignStdOfRest attribute, using the \htmlref{Epoch}{Epoch}, \htmlref{ObsLat}{ObsLat},
+         \htmlref{ObsLon}{ObsLon}, \htmlref{ObsAlt}{ObsAlt}, \htmlref{RefDec}{RefDec} and \htmlref{RefRA}{RefRA} attributes of the target to define the
+         two standards of rest.
+
+         \sstitem
+         Map these values from the standard of rest specified by the
+         AlignStdOfRest attribute, to the template\texttt{'} s standard of rest, using the
+         Epoch, ObsLat, ObsLon, ObsAlt, RefDec and RefRA attributes of the
+         template to define the two standards of rest.
+
+         \sstitem
+         Map these values from the system specified by the AlignSystem
+         attribute, to the system used by the template, using the template\texttt{'} s
+         rest frequency if necessary.
+      }
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SpecFrame
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AlignSystem
+}{
+   Coordinate system in which to align the Frame
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{Frame}{Frame} behaves when it is used (by
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} or \htmlref{AST\_CONVERT}{AST\_CONVERT}) as a template to match another (target)
+      Frame. It identifies the coordinate system in which the two Frames
+      will be aligned by the match.
+
+      The values which may be assigned to this attribute, and its default
+      value, depend on the class of Frame and are described in the
+      \texttt{"} Applicability\texttt{"}  section below. In general, the AlignSystem attribute
+      will accept any of the values which may be assigned to the \htmlref{System}{System}
+      attribute.
+
+      The \htmlref{Mapping}{Mapping} returned by astFindFrame or astConvert will use the
+      coordinate system specified by the AlignSystem attribute as an
+      intermediate coordinate system. The total returned Mapping will first
+      map positions from the first Frame into this intermediate coordinate
+      system, using the attributes of the first Frame. It will then map
+      these positions from the intermediate coordinate system into the
+      second Frame, using the attributes of the second Frame.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The AlignSystem attribute for a basic Frame always equals \texttt{"} Cartesian\texttt{"} ,
+         and may not be altered.
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The AlignSystem attribute for a CmpFrame always equals \texttt{"} Compound\texttt{"} ,
+         and may not be altered.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The AlignSystem attribute of a FrameSet is the same as that of its
+         current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The default AlignSystem attribute for a SkyFrame is \texttt{"} ICRS\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         The default AlignSystem attribute for a SpecFrame is \texttt{"} Wave\texttt{"}
+         (wavelength).
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The default AlignSystem attribute for a TimeFrame is \texttt{"} MJD\texttt{"} .
+      }
+   }
+}
+\sstroutine{
+   AlignTimeScale
+}{
+   Time scale to use when aligning TimeFrames
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{TimeFrame}{TimeFrame} behaves when it is used (by
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} or \htmlref{AST\_CONVERT}{AST\_CONVERT}) as a template to match another (target)
+      TimeFrame. It identifies the time scale in which alignment is
+      to occur. See the \htmlref{TimeScale}{TimeScale} attribute for a desription of the values
+      which may be assigned to this attribute. The default AlignTimeScale
+      value depends on the current value of TimeScale: if TimeScale is
+      UT1, GMST, LMST or LAST, the default for AlignTimeScale is UT1, for all
+      other TimeScales the default is TAI.
+
+      When AST\_FindFrame or AST\_CONVERT is used on two TimeFrames (potentially
+      describing different time coordinate systems), it returns a \htmlref{Mapping}{Mapping}
+      which can be used to transform a position in one TimeFrame into the
+      corresponding position in the other. The Mapping is made up of the
+      following steps in the indicated order:
+
+      \sstitemlist{
+
+         \sstitem
+         Map values from the system used by the target (MJD, JD, etc) to the
+         system specified by the \htmlref{AlignSystem}{AlignSystem} attribute.
+
+         \sstitem
+         Map these values from the target\texttt{'} s time scale to the time scale
+         specified by the AlignTimeScale attribute.
+
+         \sstitem
+         Map these values from the time scale specified by the AlignTimeScale
+         attribute, to the template\texttt{'} s time scale.
+
+         \sstitem
+         Map these values from the system specified by the AlignSystem
+         attribute, to the system used by the template.
+      }
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         TimeFrame
+      }{
+         All TimeFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AllVariants
+}{
+   A list of the variant Mappings associated with the current Frame
+}{
+   \sstdescription{
+      This attribute gives a space separated list of the names of all the
+      variant Mappings associated with the current \htmlref{Frame}{Frame} (see attribute
+      \texttt{"} \htmlref{Variant}{Variant}\texttt{"} ). If the current Frame has no variant Mappings, then the
+      list will hold a single entry equal to the \htmlref{Domain}{Domain} name of the
+      current Frame.
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         All FrameSets have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AllWarnings
+}{
+   A list of all currently available condition names
+}{
+   \sstdescription{
+      This read-only attribute is a space separated list of all the conditions
+      names recognized by the \htmlref{Warnings}{Warnings} attribute. The names are listed
+      below.
+   }
+   \sstattributetype{
+      String, read-only
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Conditions
+   }{
+      The following conditions are currently recognised (all are
+      case-insensitive):
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} BadCel\texttt{"} : This condition arises when reading a \htmlref{FrameSet}{FrameSet} from a
+         non-Native encoded FitsChan if an unknown celestial co-ordinate
+         system is specified by the CTYPE keywords.
+
+         \sstitem
+         \texttt{"} BadCTYPE\texttt{"} : This condition arises when reading a FrameSet from a
+         non-Native encoded FitsChan if an illegal algorithm code is specified
+         by a CTYPE keyword, and the illegal code can be converted to an
+         equivalent legal code.
+
+         \sstitem
+         \texttt{"} BadKeyName\texttt{"} : This condition arises if a FITS keyword name is
+         encountered that contains an illegal character (i.e. one not allowed
+         by the FITS standard).
+
+         \sstitem
+         \texttt{"} BadKeyValue\texttt{"} : This condition arises if the value of a FITS keyword
+         cannot be determined from the content of the header card.
+
+         \sstitem
+         \texttt{"} BadLat\texttt{"} : This condition arises when reading a FrameSet from a
+         non-Native encoded FitsChan if the latitude of the reference point
+         has an absolute value greater than 90 degrees. The actual absolute
+         value used is set to exactly 90 degrees in these cases.
+
+         \sstitem
+         \texttt{"} BadMat\texttt{"} : This condition arises if the matrix describing the
+         transformation from pixel offsets to intermediate world coordinates
+         cannot be inverted. This matrix describes the scaling, rotation, shear,
+         etc., applied to the pixel axes, and is specified by keywords such as
+         PCi\_j, CDi\_j, CROTA, etc. For example, the matrix will not be invertable
+         if any rows or columns consist entirely of zeros. The FITS-WCS Paper I
+         \texttt{"} Representation of World Coordinates in FITS\texttt{"}  by Greisen \& Calabretta
+         requires that this matrix be invertable. Many operations (such as
+         grid plotting) will not be possible if the matrix cannot be inverted.
+
+         \sstitem
+         \texttt{"} BadPV\texttt{"} : This condition arises when reading a FrameSet from a
+         non-Native encoded FitsChan. It is issued if a \htmlref{PVi\_m}{PVi\_m} header is found
+         that refers to a projection parameter that is not used by the
+         projection type specified by CTYPE, or the PV values are otherwise
+         inappropriate for the projection type.
+
+         \sstitem
+         \texttt{"} BadVal\texttt{"} : This condition arises when reading a FrameSet from a
+         non-Native encoded FitsChan if it is not possible to convert the
+         value of a FITS keywords to the expected type. For instance, this
+         can occur if the FITS header contains a string value for a keyword
+         which should have a floating point value, or if the keyword has no
+         value at all (i.e. is a comment card).
+
+         \sstitem
+         \texttt{"} Distortion\texttt{"} : This condition arises when reading a FrameSet from a
+         non-Native encoded FitsChan if any of the CTYPE keywords specify an
+         unsupported distortion code using the \texttt{"} 4-3-3\texttt{"}  format specified in
+         FITS-WCS paper IV. Such distortion codes are ignored.
+
+         \sstitem
+         \texttt{"} NoCTYPE\texttt{"} : This condition arises if a default CTYPE value is used
+         within \htmlref{AST\_READ}{AST\_READ}, due to no value being present in the supplied FitsChan.
+         This condition is only tested for when using non-Native encodings.
+
+         \sstitem
+         \texttt{"} NoEquinox\texttt{"} : This condition arises if a default equinox value is used
+         within AST\_READ, due to no value being present in the supplied FitsChan.
+         This condition is only tested for when using non-Native encodings.
+
+         \sstitem
+         \texttt{"} NoRadesys\texttt{"} : This condition arises if a default reference frame is
+         used for an equatorial co-ordinate system within AST\_READ, due to no
+         value being present in the supplied FitsChan. This condition is only
+         tested for when using non-Native encodings.
+
+         \sstitem
+         \texttt{"} NoLonpole\texttt{"} : This condition arises if a default value is used for
+         the LONPOLE keyword within AST\_READ, due to no value being present
+         in the supplied FitsChan. This condition is only tested for when
+         using non-Native encodings.
+
+         \sstitem
+         \texttt{"} NoLatpole\texttt{"} : This condition arises if a default value is used for
+         the LATPOLE keyword within AST\_READ, due to no value being present
+         in the supplied FitsChan. This condition is only tested for when
+         using non-Native encodings.
+
+         \sstitem
+         \texttt{"} NoMjd-obs\texttt{"} : This condition arises if a default value is used for
+         the date of observation within AST\_READ, due to no value being present
+         in the supplied FitsChan. This condition is only tested for when using
+         non-Native encodings.
+
+         \sstitem
+         \texttt{"} Tnx\texttt{"} : This condition arises if a FrameSet is read from a FITS
+         header containing an IRAF \texttt{"} TNX\texttt{"}  projection which includes terms
+         not supproted by AST. Such terms are ignored and so the resulting
+         FrameSet may be inaccurate.
+
+         \sstitem
+         \texttt{"} Zpx\texttt{"} : This condition arises if a FrameSet is read from a FITS
+         header containing an IRAF \texttt{"} ZPX\texttt{"}  projection which includes \texttt{"} lngcor\texttt{"}
+         or \texttt{"} latcor\texttt{"}  correction terms. These terms are not supported by AST
+         and are ignored. The resulting FrameSet may therefore be inaccurate.
+      }
+   }
+}
+\sstroutine{
+   AsTime(axis)
+}{
+   Format celestal coordinates as times?
+}{
+   \sstdescription{
+      This attribute specifies the default style of formatting to be
+      used (e.g. by \htmlref{AST\_FORMAT}{AST\_FORMAT}) for the celestial coordinate values
+      described by a \htmlref{SkyFrame}{SkyFrame}. It takes a separate boolean value for
+      each SkyFrame axis so that, for instance, the setting
+      \texttt{"} AsTime(2)=0\texttt{"}  specifies the default formatting style for
+      celestial latitude values.
+
+      If the AsTime attribute for a SkyFrame axis is zero, then
+      coordinates on that axis will be formatted as angles by default
+      (using degrees, minutes and seconds), otherwise they will be
+      formatted as times (using hours, minutes and seconds).
+
+      The default value of AsTime is chosen according to the sky
+      coordinate system being represented, as determined by the
+      SkyFrame\texttt{'} s \htmlref{System}{System} attribute. This ensures, for example, that
+      right ascension values will be formatted as times by default,
+      following normal conventions.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The AsTime attribute operates by changing the default value of
+         the corresponding \htmlref{Format(axis)}{Format(axis)} attribute. This, in turn, may
+         also affect the value of the \htmlref{Unit(axis)}{Unit(axis)} attribute.
+
+         \sstitem
+         Only the default style of formatting is affected by the AsTime
+         value. If an explicit Format(axis) value is set, it will
+         over-ride any effect from the AsTime attribute.
+      }
+   }
+}
+\sstroutine{
+   Base
+}{
+   FrameSet base Frame index
+}{
+   \sstdescription{
+      This attribute gives the index of the \htmlref{Frame}{Frame} which is to be
+      regarded as the \texttt{"} base\texttt{"}  Frame within a \htmlref{FrameSet}{FrameSet}. The default is
+      the first Frame added to the FrameSet when it is created (this
+      Frame always has an index of 1).
+
+      When setting a new value for this attribute, a string may be
+      supplied instead of an integer index. In this case a search
+      is made within the FrameSet for a Frame that has its \htmlref{Domain}{Domain}
+      attribute value equal to the supplied string (the comparison is
+      case-insensitive). If found, the Frame is made the base Frame.
+      Otherwise an error is reported.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FrameSet
+      }{
+         All FrameSets have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Inverting a FrameSet (inverting the boolean sense of its
+         \htmlref{Invert}{Invert} attribute, with the \htmlref{AST\_INVERT}{AST\_INVERT} routine for example) will
+         interchange the values of its Base and \htmlref{Current}{Current} attributes.
+      }
+   }
+}
+\sstroutine{
+   Border
+}{
+   Draw a border around valid regions of a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether a border is drawn around regions corresponding to the
+      valid physical coordinates of a \htmlref{Plot}{Plot} (c.f. \htmlref{AST\_BORDER}{AST\_BORDER}).
+
+      If the Border value of a Plot is non-zero, then this border will
+      be drawn as part of the grid. Otherwise, the border is not drawn
+      (although axis labels and tick marks will still appear, unless
+      other relevant Plot attributes indicate that they should
+      not). The default behaviour is to draw the border if tick marks
+      and numerical labels will be drawn around the edges of the
+      plotting area (see the \htmlref{Labelling}{Labelling} attribute), but to omit it
+      otherwise.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Bottom(axis)
+}{
+   Lowest axis value to display
+}{
+   \sstdescription{
+      This attribute gives the lowest axis value to be displayed (for
+      instance, by the \htmlref{AST\_GRID}{AST\_GRID} method).
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         The default supplied by the Frame class is to display all axis
+         values, without any limit.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Bottom value to -90 degrees
+         for latitude axes, and 0 degrees for co-latitude axes. The
+         default for longitude axes is to display all axis values.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   Bounded
+}{
+   Is the Region bounded?
+}{
+   \sstdescription{
+      This is a read-only attribute indicating if the \htmlref{Region}{Region} is bounded.
+      A Region is bounded if it is contained entirely within some
+      finite-size bounding box.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+   }
+}
+\sstroutine{
+   CDMatrix
+}{
+   Use CDi\_j keywords to represent pixel scaling, rotation, etc?
+}{
+   \sstdescription{
+      This attribute is a boolean value which specifies how the linear
+      transformation from pixel coordinates to intermediate world
+      coordinates should be represented within a \htmlref{FitsChan}{FitsChan} when using
+      FITS-WCS encoding. This transformation describes the scaling,
+      rotation, shear, etc., of the pixel axes.
+
+      If the attribute has a non-zero value then the transformation is
+      represented by a set of CDi\_j keywords representing a square matrix
+      (where \texttt{"} i\texttt{"}  is the index of an intermediate world coordinate axis
+      and \texttt{"} j\texttt{"}  is the index of a pixel axis). If the attribute has a zero
+      value the transformation is represented by a set of PCi\_j keywords
+      (which also represent a square matrix) together with a corresponding
+      set of CDELTi keywords representing the axis scalings. See FITS-WCS
+      paper II \texttt{"} Representation of Celestial Coordinates in FITS\texttt{"}  by
+      M. Calabretta \& E.W. Greisen, for a complete description of these two
+      schemes.
+
+      The default value of the CDMatrix attribute is determined by the
+      contents of the FitsChan at the time the attribute is accessed. If
+      the FitsChan contains any CDi\_j keywords then the default value is
+      non-zero. Otherwise it is zero. Note, reading a \htmlref{FrameSet}{FrameSet} from a
+      FitsChan will in general consume any CDi\_j keywords present in the
+      FitsChan. Thus the default value for CDMatrix following a read will
+      usually be zero, even if the FitsChan originally contained some
+      CDi\_j keywords. This behaviour is similar to that of the \htmlref{Encoding}{Encoding}
+      attribute, the default value for which is determined by the contents
+      of the FitsChan at the time the attribute is accessed. If you wish
+      to retain the original value of the CDMatrix attribute (that is,
+      the value before reading the FrameSet) then you should enquire the
+      default value before doing the read, and then set that value
+      explicitly.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   CarLin
+}{
+   Ignore spherical rotations on CAR projections?
+}{
+   \sstdescription{
+      This attribute is a boolean value which specifies how FITS \texttt{"} CAR\texttt{"}
+      (plate carree, or \texttt{"} Cartesian\texttt{"} ) projections should be treated when
+      reading a \htmlref{FrameSet}{FrameSet} from a foreign encoded FITS header. If zero (the
+      default), it is assumed that the CAR projection conforms to the
+      conventions described in the FITS world coordinate system (FITS-WCS)
+      paper II \texttt{"} Representation of Celestial Coordinates in FITS\texttt{"}  by
+      M. Calabretta \& E.W. Greisen. If CarLin is non-zero, then these
+      conventions are ignored, and it is assumed that the mapping from pixel
+      coordinates to celestial coordinates is a simple linear transformation
+      (hence the attribute name \texttt{"} CarLin\texttt{"} ). This is appropriate for some older
+      FITS data which claims to have a \texttt{"} CAR\texttt{"}  projection, but which in fact do
+      not conform to the conventions of the FITS-WCS paper.
+
+      The FITS-WCS paper specifies that headers which include a CAR projection
+      represent a linear mapping from pixel coordinates to \texttt{"} native spherical
+      coordinates\texttt{"} , NOT celestial coordinates. An extra mapping is then
+      required from native spherical to celestial. This mapping is a 3D
+      rotation and so the overall \htmlref{Mapping}{Mapping} from pixel to celestial coordinates
+      is NOT linear. See the FITS-WCS papers for further details.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Card
+}{
+   Index of current FITS card in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the index of the \texttt{"} current\texttt{"}  FITS header card
+      within a \htmlref{FitsChan}{FitsChan}, the first card having an index of 1. The
+      choice of current card affects the behaviour of routines that
+      access the contents of the FitsChan, such as \htmlref{AST\_DELFITS}{AST\_DELFITS},
+      \htmlref{AST\_FINDFITS}{AST\_FINDFITS} and \htmlref{AST\_PUTFITS}{AST\_PUTFITS}.
+
+      A value assigned to Card will position the FitsChan at any
+      desired point, so that a particular card within it can be
+      accessed. Alternatively, the value of Card may be enquired in
+      order to determine the current position of a FitsChan.
+
+      The default value of Card is 1. This means that clearing
+      this attribute (using \htmlref{AST\_CLEAR}{AST\_CLEAR}) effectively \texttt{"} rewinds\texttt{"}  the
+      FitsChan, so that the first card is accessed next.  If Card is
+      set to a value which exceeds the total number of cards in the
+      FitsChan (as given by its \htmlref{Ncard}{Ncard} attribute), it is regarded as
+      pointing at the \texttt{"} end-of-file\texttt{"} . In this case, the value returned
+      in response to an enquiry is always one more than the number of
+      cards in the FitsChan.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   CardComm
+}{
+   The comment for the current card in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the comment for the current card of the
+      \htmlref{FitsChan}{FitsChan}. A zero-length string is returned if the card has no comment.
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   CardName
+}{
+   The keyword name of the current card in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the name of the keyword for the
+      current card of the \htmlref{FitsChan}{FitsChan}.
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   CardType
+}{
+   The data type of the current card in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the data type of the keyword value for the
+      current card of the \htmlref{FitsChan}{FitsChan}. It will be one of the following
+      integer constants: AST\_\_NOTYPE, AST\_\_COMMENT, AST\_\_INT, AST\_\_FLOAT,
+      AST\_\_STRING, AST\_\_COMPLEXF, AST\_\_COMPLEXI, AST\_\_LOGICAL,
+      AST\_\_CONTINUE, AST\_\_UNDEF.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Class
+}{
+   Object class name
+}{
+   \sstdescription{
+      This attribute gives the name of the class to which an \htmlref{Object}{Object}
+      belongs.
+   }
+   \sstattributetype{
+      Character string, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Clean
+}{
+   Remove cards used whilst reading even if an error occurs?
+}{
+   \sstdescription{
+      This attribute indicates whether or not cards should be removed from
+      the \htmlref{FitsChan}{FitsChan} if an error occurs within
+      \htmlref{AST\_READ}{AST\_READ}.
+      A succesful read on a FitsChan always results in the removal of
+      the cards which were involved in the description of the returned
+      \htmlref{Object}{Object}. However, in the event of an error during the read (for instance
+      if the cards in the FitsChan have illegal values, or if some required
+      cards are missing) no cards will be removed from the FitsChan if
+      the Clean attribute is zero (the default). If Clean is non-zero then
+      any cards which were used in the aborted attempt to read an object
+      will be removed.
+
+      This provides a means of \texttt{"} cleaning\texttt{"}  a FitsChan of WCS related cards
+      which works even in the event of the cards not forming a legal WCS
+      description.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Clip
+}{
+   Clip lines and/or markers at the Plot boundary?
+}{
+   \sstdescription{
+      This attribute controls whether curves and markers are clipped at the
+      boundary of the graphics box specified when the \htmlref{Plot}{Plot} was created. A
+      value of 3 implies both markers and curves are clipped at the Plot
+      boundary. A value of 2 implies markers are clipped, but not curves. A
+      value of 1 implies curves are clipped, but not markers. A value of
+      zero implies neither curves nor markers are clipped. The default
+      value is 1. Note, this attributes controls only the clipping
+      performed internally within AST. The underlying graphics system may
+      also apply clipping. In such cases, removing clipping using this
+      attribute does not guarantee that no clipping will be visible in the
+      final plot.
+
+      The \htmlref{AST\_CLIP}{AST\_CLIP} routine
+      can be used to establish generalised clipping within arbitrary
+      regions of the Plot.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ClipOp
+}{
+   Combine Plot clipping limits using a boolean OR?
+}{
+   \sstdescription{
+      This attribute controls how the clipping limits specified for
+      each axis of a \htmlref{Plot}{Plot} (using the \htmlref{AST\_CLIP}{AST\_CLIP} routine) are
+      combined. This, in turn, determines which parts of the graphical
+      output will be visible.
+
+      If the ClipOp attribute of a Plot is zero (the default),
+      graphical output is visible only if it satisfies the clipping
+      limits on all the axes of the clipping \htmlref{Frame}{Frame} (a boolean
+      AND). Otherwise, if ClipOp is non-zero, output is visible if it
+      satisfies the clipping limits on one or more axes (a boolean
+      OR).
+
+      An important use of this attribute is to allow areas of a Plot
+      to be left clear (e.g. as a background for some text). To
+      achieve this, the lower and upper clipping bounds supplied to
+      AST\_CLIP should be reversed, and the ClipOp attribute of the
+      Plot should be set to a non-zero value.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Closed
+}{
+   Should the boundary be considered to be inside the region?
+}{
+   \sstdescription{
+      This attribute controls whether points on the boundary of a \htmlref{Region}{Region}
+      are considered to be inside or outside the region. If the attribute
+      value is non-zero (the default), points on the boundary are considered
+      to be inside the region (that is, the Region is \texttt{"} closed\texttt{"} ). However,
+      if the attribute value is zero, points on the bounary are considered
+      to be outside the region.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{PointList}{PointList}
+      }{
+         The value of the Closed attribute is ignored by PointList regions.
+         If the PointList region has not been negated, then it is always
+         assumed to be closed. If the PointList region has been negated, then
+         it is always assumed to be open. This is required since points
+         have zero volume and therefore consist entirely of boundary.
+      }
+      \sstsubsection{
+         \htmlref{CmpRegion}{CmpRegion}
+      }{
+         The default Closed value for a CmpRegion is the Closed value of its
+         first component Region.
+      }
+      \sstsubsection{
+         \htmlref{Stc}{Stc}
+      }{
+         The default Closed value for an Stc is the Closed value of its
+         encapsulated Region.
+      }
+   }
+}
+\sstroutine{
+   Colour(element)
+}{
+   Colour index for a Plot element
+}{
+   \sstdescription{
+      This attribute determines the colour index used when drawing
+      each element of graphical output produced by a \htmlref{Plot}{Plot}. It takes a
+      separate value for each graphical element so that, for instance,
+      the setting \texttt{"} Colour(title)=2\texttt{"}  causes the Plot title to be drawn
+      using colour index 2. The synonym \texttt{"} Color\texttt{"}  may also be used.
+
+      The range of integer colour indices available and their
+      appearance is determined by the underlying graphics system. The
+      default behaviour is for all graphical elements to be drawn
+      using the default colour index supplied by this graphics system
+      (normally, this is likely to result in white plotting on a black
+      background, or vice versa).
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of the graphical elements available, see the
+         description of the Plot class.
+
+         \sstitem
+         If no graphical element is specified, (e.g. \texttt{"} Colour\texttt{"}  instead
+         of \texttt{"} Colour(title)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will
+         affect the attribute value of all graphical elements, while a
+         \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}  operation will use just the Colour(TextLab)
+         value.
+      }
+   }
+}
+\sstroutine{
+   ColumnLenC(column)
+}{
+   The largest string length of any value in a column
+}{
+   \sstdescription{
+      This attribute holds the minimum length which a character variable
+      must have in order to be able to store the longest value currently
+      present (at any row) in a specified column of the supplied \htmlref{Table}{Table}.
+      The required column name should be placed inside the parentheses in
+      the attribute name. If the named column holds vector values, then
+      the attribute value is the length of the longest element of the
+      vector value.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Table
+      }{
+         All Tables have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the named column holds numerical values, the length returned
+         is the length of the largest string that would be generated if the
+         column values were accessed as strings.
+      }
+   }
+}
+\sstroutine{
+   ColumnLength(column)
+}{
+   The number of elements in each value in a column
+}{
+   \sstdescription{
+      This attribute holds the number of elements in each value stored
+      in a named column. Each value can be a scalar (in which case the
+      ColumnLength attribute has a value of 1), or a multi-dimensional
+      array ( in which case the ColumnLength value is equal to the
+      product of the array dimensions).
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ColumnNdim(column)
+}{
+   The number of axes spanned by each value in a column
+}{
+   \sstdescription{
+      This attribute holds the number of axes spanned by each value in a
+      column. If each cell in the column is a scalar, ColumnNdim will be
+      zero. If each cell in the column is a 1D spectrum, ColumnNdim will
+      be one. If each cell in the column is a 2D image, ColumnNdim will be
+      two, etc. The required column name should be placed inside the
+      parentheses in the attribute name.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ColumnType(column)
+}{
+   The data type of each value in a column
+}{
+   \sstdescription{
+      This attribute holds a integer value indicating the data type of
+      a named column in a \htmlref{Table}{Table}. This is the data type which was used
+      when the column was added to the Table using astAddColumn. The
+      required column name should be placed inside the parentheses in
+      the attribute name.
+
+      The attribute value will be one of AST\_\_INTTYPE (for integer),
+      AST\_\_SINTTYPE (for
+      INTEGER$*$2),
+      AST\_\_BYTETYPE (for
+      bytes),
+      AST\_\_DOUBLETYPE (for double
+      precision floating point), AST\_\_FLOATTYPE (for single
+      precision floating point), AST\_\_STRINGTYPE (for character string),
+      AST\_\_OBJECTTYPE (for AST \htmlref{Object}{Object} pointer), AST\_\_POINTERTYPE (for
+      arbitrary C pointer) or AST\_\_UNDEFTYPE (for undefined values
+      created by
+      \htmlref{AST\_MAPPUTU}{AST\_MAPPUTU}).
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Table
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Comment
+}{
+   Include textual comments in output?
+}{
+   \sstdescription{
+      This is a boolean attribute which controls whether textual
+      comments are to be included in the output generated by a
+      \htmlref{Channel}{Channel}. If included, they will describe what each item of
+      output represents.
+
+      If Comment is non-zero, then comments will be included. If
+      it is zero, comments will be omitted.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         The default value is non-zero for a normal Channel.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         The default value is non-zero for a FitsChan.
+      }
+      \sstsubsection{
+         \htmlref{XmlChan}{XmlChan}
+      }{
+         The default value is zero for an XmlChan.
+      }
+   }
+}
+\sstroutine{
+   Current
+}{
+   FrameSet current Frame index
+}{
+   \sstdescription{
+      This attribute gives the index of the \htmlref{Frame}{Frame} which is to be
+      regarded as the \texttt{"} current\texttt{"}  Frame within a \htmlref{FrameSet}{FrameSet}. The default
+      is the most recent Frame added to the FrameSet (this Frame
+      always has an index equal to the FrameSet\texttt{'} s \htmlref{Nframe}{Nframe} attribute).
+
+      When setting a new value for this attribute, a string may be
+      supplied instead of an integer index. In this case a search
+      is made within the FrameSet for a Frame that has its \htmlref{Domain}{Domain}
+      attribute value equal to the supplied string (the comparison is
+      case-insensitive). If found, the Frame is made the current Frame.
+      Otherwise an error is reported.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FrameSet
+      }{
+         All FrameSets have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Inverting a FrameSet (inverting the boolean sense of its
+         \htmlref{Invert}{Invert} attribute, with the \htmlref{AST\_INVERT}{AST\_INVERT} routine for example) will
+         interchange the values of its \htmlref{Base}{Base} and Current attributes.
+      }
+   }
+}
+\sstroutine{
+   DSBCentre
+}{
+   The central position of interest in a dual sideband spectrum
+}{
+   \sstdescription{
+      This attribute specifies the central position of interest in a dual
+      sideband spectrum. Its sole use is to determine the local oscillator
+      frequency (the frequency which marks the boundary between the lower
+      and upper sidebands). See the description of the \htmlref{IF}{IF} (intermediate
+      frequency) attribute for details of how the local oscillator frequency
+      is calculated. The sideband containing this central position is
+      referred to as the \texttt{"} observed\texttt{"}  sideband, and the other sideband as
+      the \texttt{"} image\texttt{"}  sideband.
+
+      The value is accessed as a position in the spectral system
+      represented by the \htmlref{SpecFrame}{SpecFrame} attributes inherited by this class, but
+      is stored internally as topocentric frequency. Thus, if the \htmlref{System}{System}
+      attribute of the \htmlref{DSBSpecFrame}{DSBSpecFrame} is set to \texttt{"} VRAD\texttt{"} , the Unit attribute
+      set to \texttt{"} m/s\texttt{"}  and the \htmlref{StdOfRest}{StdOfRest} attribute set to \texttt{"} LSRK\texttt{"} , then values
+      for the DSBCentre attribute should be supplied as radio velocity in
+      units of \texttt{"} m/s\texttt{"}  relative to the kinematic LSR (alternative units may
+      be used by appending a suitable units string to the end of the value).
+      This value is then converted to topocentric frequency and stored. If
+      (say) the Unit attribute is subsequently changed to \texttt{"} km/s\texttt{"}  before
+      retrieving the current value of the DSBCentre attribute, the stored
+      topocentric frequency will be converted back to LSRK radio velocity,
+      this time in units of \texttt{"} km/s\texttt{"} , before being returned.
+
+      The default value for this attribute is 30 GHz.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         DSBSpecFrame
+      }{
+         All DSBSpecFrames have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Note
+   }{
+      \sstitemlist{
+
+         \sstitem
+         The attributes which define the transformation to or from topocentric
+         frequency should be assigned their correct values before accessing
+         this attribute. These potentially include System, Unit, StdOfRest,
+         \htmlref{ObsLon}{ObsLon}, \htmlref{ObsLat}{ObsLat}, \htmlref{ObsAlt}{ObsAlt}, \htmlref{Epoch}{Epoch}, \htmlref{RefRA}{RefRA}, \htmlref{RefDec}{RefDec} and \htmlref{RestFreq}{RestFreq}.
+      }
+   }
+}
+\sstroutine{
+   DefB1950
+}{
+   Use FK4 B1950 as defaults?
+}{
+   \sstdescription{
+      This attribute is a boolean value which specifies a default equinox
+      and reference frame to use when reading a \htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan}
+      with a foreign (i.e. non-native) encoding. It is only used if the FITS
+      header contains RA and DEC axes but contains no information about the
+      reference frame or equinox. If this is the case, then values of FK4 and
+      B1950 are assumed if the DefB1950 attribute has a non-zero value and
+      ICRS is assumed if DefB1950 is zero. The default value for DefB1950
+      depends on the value of the \htmlref{Encoding}{Encoding} attribute: for FITS-WCS encoding
+      the default is zero, and for all other encodings it is one.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Digits/Digits(axis)
+}{
+   Number of digits of precision
+}{
+   \sstdescription{
+      This attribute specifies how many digits of precision are
+      required by default when a coordinate value is formatted for a
+      \htmlref{Frame}{Frame} axis (e.g. using \htmlref{AST\_FORMAT}{AST\_FORMAT}). Its value may be set either
+      for a Frame as a whole, or (by subscripting the attribute name
+      with the number of an axis) for each axis individually. Any
+      value set for an individual axis will over-ride the value for
+      the Frame as a whole.
+
+      Note that the Digits value acts only as a means of determining a
+      default Format string. Its effects are over-ridden if a Format
+      string is set explicitly for an axis. However, if the Format
+      attribute specifies the precision using the string \texttt{"} .$*$\texttt{"} , then
+      the Digits attribute is used to determine the number of decimal
+      places to produce.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default Digits value supplied by the Frame class is 7. If
+         a value less than 1 is supplied, then 1 is used instead.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Digits attribute of a FrameSet (or one of its axes) is
+         the same as that of its current Frame (as specified by the
+         \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         \htmlref{Plot}{Plot}
+      }{
+         The default Digits value used by the Plot class when drawing
+         annotated axis labels is the smallest value which results in all
+         adjacent labels being distinct.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The Digits attribute is ignored when a TimeFrame formats a value
+         as a date and time string (see the Format attribute).
+      }
+   }
+}
+\sstroutine{
+   Direction(axis)
+}{
+   Display axis in conventional direction?
+}{
+   \sstdescription{
+      This attribute is a boolean value which suggests how the axes of
+      a \htmlref{Frame}{Frame} should be displayed (e.g.) in graphical output. By
+      default, it has the value one, indicating that they should be
+      shown in the conventional sense (increasing left to right for an
+      abscissa, and bottom to top for an ordinate). If set to zero,
+      this attribute indicates that the direction should be reversed,
+      as would often be done for an astronomical magnitude or a right
+      ascension axis.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default Direction value supplied by the Frame class is 1,
+         indicating that all axes should be displayed in the
+         conventional direction.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Direction value to
+         suggest that certain axes (e.g. right ascension) should be
+         plotted in reverse when appropriate.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Direction attribute of a FrameSet axis is the same as
+         that of its current Frame (as specified by the \htmlref{Current}{Current}
+         attribute).
+      }
+      \sstsubsection{
+         \htmlref{Plot}{Plot}
+      }{
+         The Direction attribute of the base Frame in a Plot is set to
+         indicate the sense of the two graphics axes, as implied by the
+         graphics bounding box supplied when the Plot was created.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+
+         \sstitem
+         The Direction attribute does not directly affect the behaviour
+         of the AST library. Instead, it serves as a hint to applications
+         programs about the orientation in which they may wish to display
+         any data associated with the Frame. Applications are free to
+         ignore this hint if they wish.
+      }
+   }
+}
+\sstroutine{
+   Disco
+}{
+   PcdMap pincushion/barrel distortion coefficient
+}{
+   \sstdescription{
+      This attribute specifies the pincushion/barrel distortion coefficient
+      used by a \htmlref{PcdMap}{PcdMap}. This coefficient is set when the PcdMap is created,
+      but may later be modified. If the attribute is cleared, its default
+      value is zero, which gives no distortion. For pincushion distortion,
+      the value should be positive. For barrel distortion, it should be
+      negative.
+
+      Note that the forward transformation of a PcdMap applies the
+      distortion specified by this attribute and the inverse
+      transformation removes this distortion. If the PcdMap is inverted
+      (e.g. using \htmlref{AST\_INVERT}{AST\_INVERT}), then the forward transformation will
+      remove the distortion and the inverse transformation will apply
+      it. The distortion itself will still be given by the same value of
+      Disco.
+
+      Note, the value of this attribute may changed only if the PcdMap
+      has no more than one reference. That is, an error is reported if the
+      PcdMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         PcdMap
+      }{
+         All PcdMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Domain
+}{
+   Coordinate system domain
+}{
+   \sstdescription{
+      This attribute contains a string which identifies the physical
+      domain of the coordinate system that a \htmlref{Frame}{Frame} describes.
+
+      The Domain attribute also controls how a Frame behaves when it is
+      used (by \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME}) as a template to match another (target)
+      Frame. It does this by specifying the Domain that the target
+      Frame should have in order to match the template. If the Domain
+      value in the template Frame is set, then only targets with the
+      same Domain value will be matched. If the template\texttt{'} s Domain
+      value is not set, however, then the target\texttt{'} s Domain will be
+      ignored.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default Domain value supplied by the Frame class is an
+         empty string.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Domain value to be
+         \texttt{"} SKY\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The CmpFrame class re-defines the default Domain value to be
+         of the form \texttt{"} $<$dom1$>$-$<$dom2$>$\texttt{"} , where $<$dom1$>$ and $<$dom2$>$ are the
+         Domains of the two component Frames. If both these Domains are
+         blank, then the string \texttt{"} CMP\texttt{"}  is used as the default Domain name.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Domain attribute of a FrameSet is the same as that of its
+         current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         The SpecFrame class re-defines the default Domain value to be
+         \texttt{"} SPECTRUM\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{DSBSpecFrame}{DSBSpecFrame}
+      }{
+         The DSBSpecFrame class re-defines the default Domain value to be
+         \texttt{"} DSBSPECTRUM\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{FluxFrame}{FluxFrame}
+      }{
+         The FluxFrame class re-defines the default Domain value to be
+         \texttt{"} FLUX\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{SpecFluxFrame}{SpecFluxFrame}
+      }{
+         The FluxFrame class re-defines the default Domain value to be
+         \texttt{"} SPECTRUM-FLUX\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class re-defines the default Domain value to be
+         \texttt{"} TIME\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         All Domain values are converted to upper case and white space
+         is removed before use.
+      }
+   }
+}
+\sstroutine{
+   DrawAxes(axis)
+}{
+   Draw axes for a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether curves representing coordinate axes should be drawn.
+      It takes a separate value for each physical axis of a
+      \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} DrawAxes(2)=0\texttt{"}
+      specifies that no axis should be drawn for the second axis.
+
+      If drawn, these axis lines will pass through any tick marks
+      associated with numerical labels drawn to mark values on the
+      axes. The location of these tick marks and labels (and hence the
+      axis lines) is determined by the Plot\texttt{'} s \htmlref{LabelAt(axis)}{LabelAt(axis)} attribute.
+
+      If the DrawAxes value of a Plot is non-zero (the default), then
+      axis lines will be drawn, otherwise they will be omitted.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Axis}{Axis} lines are drawn independently of any coordinate grid
+         lines (see the \htmlref{Grid}{Grid} attribute) so grid lines may be used to
+         substitute for axis lines if required.
+
+         \sstitem
+         In some circumstances, numerical labels and tick marks are
+         drawn around the edges of the plotting area (see the \htmlref{Labelling}{Labelling}
+         attribute).  In this case, the value of the DrawAxes attribute
+         is ignored.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} DrawAxes\texttt{"}  instead of
+         \texttt{"} DrawAxes(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the DrawAxes(1) value.
+      }
+   }
+}
+\sstroutine{
+   DrawTitle
+}{
+   Draw a title for a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether a title is drawn.
+
+      If the DrawTitle value of a \htmlref{Plot}{Plot} is non-zero (the default), then
+      the title will be drawn, otherwise it will be omitted.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Plot3D}{Plot3D}
+      }{
+         The Plot3D class ignores this attributes, assuming a value of
+         zero.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The text used for the title is obtained from the Plot\texttt{'} s \htmlref{Title}{Title}
+         attribute.
+
+         \sstitem
+         The vertical placement of the title can be controlled using
+         the \htmlref{TitleGap}{TitleGap} attribute.
+      }
+   }
+}
+\sstroutine{
+   Dut1
+}{
+   The UT1-UTC correction
+}{
+   \sstdescription{
+      This attribute is used when calculating the Local Apparent Sidereal
+      Time corresponding to \htmlref{SkyFrame}{SkyFrame}\texttt{'} s \htmlref{Epoch}{Epoch} value (used when converting
+      positions to or from the \texttt{"} AzEl\texttt{"}  system). It should be set to the
+      difference, in seconds, between the UT1 and UTC timescales at the
+      moment in time represented by the SkyFrame\texttt{'} s Epoch attribute. The
+      value to use is unpredictable and depends on changes in the earth\texttt{'} s
+      rotation speed. Values for UT1-UTC can be obtained from the
+      International Earth Rotation and Reference Systems Service
+      (IERS) at http://www.iers.org/.
+
+      Currently, the correction is always less than 1 second. This is
+      ensured by the occasional introduction of leap seconds into the UTC
+      timescale. Therefore no great error will usually result if no value
+      is assigned to this attribute (in which case a default value of
+      zero is used). However, it is possible that a decision may be taken
+      at some time in the future to abandon the introduction of leap
+      seconds, in which case the DUT correction could grow to significant
+      sizes.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         All Frames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Edge(axis)
+}{
+   Which edges to label in a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      which edges of a \htmlref{Plot}{Plot} are used for displaying numerical and
+      descriptive axis labels. It takes a separate value for each
+      physical axis of the Plot so that, for instance, the setting
+      \texttt{"} Edge(2)=left\texttt{"}  specifies which edge to use to display labels for
+      the second axis.
+
+      The values \texttt{"} left\texttt{"} , \texttt{"} top\texttt{"} , \texttt{"} right\texttt{"}  and \texttt{"} bottom\texttt{"}  (or any
+      abbreviation) can be supplied for this attribute. The default is
+      usually \texttt{"} bottom\texttt{"}  for the first axis and \texttt{"} left\texttt{"}  for the second
+      axis. However, if exterior labelling was requested (see the
+      \htmlref{Labelling}{Labelling} attribute) but cannot be produced using these default
+      Edge values, then the default values will be swapped if this
+      enables exterior labelling to be produced.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Plot3D}{Plot3D}
+      }{
+         The Plot3D class ignores this attributes. Instead it uses its
+         own \htmlref{RootCorner}{RootCorner} attribute to determine which edges of the 3D plot
+         to label.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         In some circumstances, numerical labels will be drawn along
+         internal grid lines instead of at the edges of the plotting area
+         (see the Labelling attribute). In this case, the Edge attribute
+         only affects the placement of the descriptive labels (these are
+         drawn at the edges of the plotting area, rather than along the
+         axis lines).
+      }
+   }
+}
+\sstroutine{
+   Encoding
+}{
+   System for encoding Objects as FITS headers
+}{
+   \sstdescription{
+      This attribute specifies the encoding system to use when AST
+      Objects are stored as FITS header cards in a \htmlref{FitsChan}{FitsChan}. It
+      affects the behaviour of the \htmlref{AST\_WRITE}{AST\_WRITE} and \htmlref{AST\_READ}{AST\_READ} routines when
+      they are used to transfer any AST \htmlref{Object}{Object} to or from an external
+      representation consisting of FITS header cards (i.e. whenever a
+      write or read operation is performed using a FitsChan as the I/O
+      \htmlref{Channel}{Channel}).
+
+      There are several ways (conventions) by which coordinate system
+      information may be represented in the form of FITS headers and
+      the Encoding attribute is used to specify which of these should
+      be used. The encoding options available are outlined in the
+      \texttt{"} Encodings Available\texttt{"}  section below, and in more detail in the
+      sections which follow.
+
+      Encoding systems differ in the range of possible Objects
+      (e.g. classes) they can represent, in the restrictions they
+      place on these Objects (e.g. compatibility with some
+      externally-defined coordinate system model) and in the number of
+      Objects that can be stored together in any particular set of
+      FITS header cards (e.g. multiple Objects, or only a single
+      Object). The choice of encoding also affects the range of
+      external applications which can potentially read and interpret
+      the FITS header cards produced.
+
+      The encoding options available are not necessarily mutually
+      exclusive, and it may sometimes be possible to store multiple
+      Objects (or the same Object several times) using different
+      encodings within the same set of FITS header cards. This
+      possibility increases the likelihood of other applications being
+      able to read and interpret the information.
+
+      By default, a FitsChan will attempt to determine which encoding
+      system is already in use, and will set the default Encoding
+      value accordingly (so that subsequent I/O operations adopt the
+      same conventions). It does this by looking for certain critical
+      FITS keywords which only occur in particular encodings. For
+      details of how this works, see the \texttt{"} Choice of Default Encoding\texttt{"}
+      section below. If you wish to ensure that a particular encoding
+      system is used, independently of any FITS cards already present,
+      you should set an explicit Encoding value yourself.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Encodings Available
+   }{
+      The Encoding attribute can take any of the following (case
+      insensitive) string values to select the corresponding encoding
+
+      system:
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} DSS\texttt{"} : Encodes coordinate system information in FITS header
+         cards using the convention developed at the Space Telescope
+         Science Institute (STScI) for the Digitised Sky Survey (DSS)
+         astrometric plate calibrations. The main advantages of this
+         encoding are that FITS images which use it are widely available
+         and it is understood by a number of important and
+         well-established astronomy applications. For further details,
+         see the section \texttt{"} The DSS Encoding\texttt{"}  below.
+
+         \sstitem
+         \texttt{"} FITS-WCS\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions described in the FITS
+         world coordinate system (FITS-WCS) papers by E.W. Greisen,
+         M. Calabretta, et al. The main advantages of this encoding are that
+         it should be understood by any FITS-WCS compliant application and
+         is likely to be adopted widely for FITS data in future. For further
+         details, see the section \texttt{"} The FITS-WCS Encoding\texttt{"}  below.
+
+         \sstitem
+         \texttt{"} FITS-PC\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions described in an earlier draft
+         of the FITS world coordinate system papers by E.W. Greisen and
+         M. Calabretta. This encoding uses a combination of CDELTi and
+         PCiiijjj keywords to describe the scale and rotation of the pixel
+         axes. This encoding is included to support existing data and
+         software which uses these now superceded conventions. In general,
+         the \texttt{"} FITS-WCS\texttt{"}  encoding (which uses CDi\_j or PCi\_j keywords to
+         describe the scale and rotation) should be used in preference to
+         \texttt{"} FITS-PC\texttt{"} .
+
+         \sstitem
+         \texttt{"} FITS-IRAF\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions described in the document
+         \texttt{"} World Coordinate Systems Representations Within the FITS
+         Format\texttt{"}  by R.J. Hanisch and D.G. Wells, 1988.  This encoding is
+         currently employed by the IRAF data analysis facility, so its
+         use will facilitate data exchange with IRAF. Its main advantages
+         are that it is a stable convention which approximates to a
+         subset of the propsed FITS-WCS encoding (above). This makes it
+         suitable as an interim method for storing coordinate system
+         information in FITS headers until the FITS-WCS encoding becomes
+         stable. Since many datasets currently use the FITS-IRAF
+         encoding, conversion of data from FITS-IRAF to the final form of
+         FITS-WCS is likely to be well supported.
+
+         \sstitem
+         \texttt{"} FITS-AIPS\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions originally introduced by the
+         AIPS data analysis facility. This is base on the use of CDELTi and
+         CROTAi keuwords to desribe the scale and rotation of each axis.
+         These conventions have been superceded but are still widely used.
+
+         \sstitem
+         \texttt{"} FITS-AIPS$+$$+$\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions used by the AIPS$+$$+$ project.
+         This is an extension of FITS-AIPS which includes some of the
+         features of FITS-IRAF and FITS-PC.
+
+         \sstitem
+         \texttt{"} FITS-CLASS\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions used by the CLASS project.
+         CLASS is a software package for reducing single-dish radio and
+         sub-mm spectroscopic data. See the section \texttt{"} CLASS FITS format\texttt{"}  at
+         http://www.iram.fr/IRAMFR/GILDAS/doc/html/class-html/.
+
+         \sstitem
+         \texttt{"} NATIVE\texttt{"} : Encodes AST Objects in FITS header cards using a
+         convention which is private to the AST library (but adheres to
+         the general FITS standard) and which uses FITS keywords that
+         will not clash with other encoding systems. The main advantages
+         of this are that any class of AST Object may be encoded, and any
+         (reasonable) number of Objects may be stored sequentially in the
+         same FITS header. This makes FITS headers an almost loss-less
+         communication path for passing AST Objects between applications
+         (although all such applications must, of course, make use of the
+         AST library to interpret the information). For further details,
+         see the section \texttt{"} The NATIVE Encoding\texttt{"}  below.
+      }
+   }
+   \sstdiytopic{
+      Choice of Default Encoding
+   }{
+      If the Encoding attribute of a FitsChan is not set, the default
+      value it takes is determined by the presence of certain critical
+      FITS keywords within the FitsChan. The sequence of decisions
+
+      used to arrive at the default value is as follows:
+
+      \sstitemlist{
+
+         \sstitem
+         If the FitsChan contains any keywords beginning with the
+         string \texttt{"} BEGAST\texttt{"} , then NATIVE encoding is used,
+
+         \sstitem
+         Otherwise, FITS-CLASS is used if the FitsChan contains a DELTAV
+         keyword and a keyword of the form VELO-xxx, where xxx indicates one
+         of the rest frames used by class (e.g. \texttt{"} VELO-LSR\texttt{"} ), or \texttt{"} VLSR\texttt{"} .
+
+         \sstitem
+         Otherwise, if the FitsChan contains a CTYPE keyword which
+         represents a spectral axis using the conventions of the AIPS and
+         AIPS$+$$+$ projects (e.g. \texttt{"} FELO-LSR\texttt{"} , etc), then one of FITS-AIPS or
+         FITS-AIPS$+$$+$ encoding is used. FITS-AIPS$+$$+$ is used if any of the
+         keywords CDi\_j, PROJP, LONPOLE or LATPOLE are
+         found in the FitsChan. Otherwise FITS-AIPS is used.
+
+         \sstitem
+         Otherwise, if the FitsChan contains a keyword of the form
+         \texttt{"} PCiiijjj\texttt{"} , where \texttt{"} i\texttt{"}  and \texttt{"} j\texttt{"}  are single digits, then
+         FITS-PC encoding is used,
+
+         \sstitem
+         Otherwise, if the FitsChan contains a keyword of the form
+         \texttt{"} CDiiijjj\texttt{"} , where \texttt{"} i\texttt{"}  and \texttt{"} j\texttt{"}  are single digits, then
+         FITS-IRAF encoding is used,
+
+         \sstitem
+         Otherwise, if the FitsChan contains a keyword of the form
+         \texttt{"} CDi\_j\texttt{"} , and at least one of RADECSYS, PROJPi, or CjVALi
+         where \texttt{"} i\texttt{"}  and \texttt{"} j\texttt{"}  are single digits, then FITS-IRAF encoding is
+         used.
+
+         \sstitem
+         Otherwise, if the FitsChan contains any keywords of the form
+         PROJPi, CjVALi or RADECSYS, where \texttt{"} i\texttt{"}  and \texttt{"} j\texttt{"}  are single digits,
+         then FITS-PC encoding is used.
+
+         \sstitem
+         Otherwise, if the FitsChan contains a keyword of the form
+         CROTAi, where \texttt{"} i\texttt{"}  is a single digit, then FITS-AIPS encoding is
+         used.
+
+         \sstitem
+         Otherwise, if the FitsChan contains a keyword of the form
+         CRVALi, where \texttt{"} i\texttt{"}  is a single digit, then FITS-WCS encoding is
+         used.
+
+         \sstitem
+         Otherwise, if the FitsChan contains the \texttt{"} PLTRAH\texttt{"}  keyword, then
+         DSS encoding is used,
+
+         \sstitem
+         Otherwise, if none of these conditions is met (as would be the
+         case when using an empty FitsChan), then NATIVE encoding is
+         used.
+
+      }
+      Except for the NATIVE and DSS encodings, all the above checks
+      also require that the header contains at least one CTYPE, CRPIX and
+      CRVAL keyword (otherwise the checking process continues to the next
+      case).
+
+      Setting an explicit value for the Encoding attribute always
+      over-rides this default behaviour.
+
+      Note that when writing information to a FitsChan, the choice of
+      encoding will depend greatly on the type of application you
+      expect to be reading the information in future. If you do not
+      know this, there may sometimes be an advantage in writing the
+      information several times, using a different encoding on each
+      occasion.
+   }
+   \sstdiytopic{
+      The DSS Encoding
+   }{
+      The DSS encoding uses FITS header cards to store a multi-term
+      polynomial which relates pixel positions on a digitised
+      photographic plate to celestial coordinates (right ascension and
+      declination). This encoding may only be used to store a single
+      AST Object in any set of FITS header cards, and that Object must
+      be a \htmlref{FrameSet}{FrameSet} which conforms to the STScI/DSS coordinate system
+      model (this means the \htmlref{Mapping}{Mapping} which relates its base and current
+      Frames must include either a \htmlref{DssMap}{DssMap} or a \htmlref{WcsMap}{WcsMap} with type
+      AST\_\_TAN or AST\_\_TPN).
+
+      When reading a DSS encoded Object (using AST\_READ), the FitsChan
+      concerned must initially be positioned at the first card (its
+      \htmlref{Card}{Card} attribute must equal 1) and the result of the read, if
+      successful, will always be a pointer to a FrameSet. The base
+      \htmlref{Frame}{Frame} of this FrameSet represents DSS pixel coordinates, and the
+      current Frame represents DSS celestial coordinates. Such a read
+      is always destructive and causes the FITS header cards required
+      for the construction of the FrameSet to be removed from the
+      FitsChan, which is then left positioned at the \texttt{"} end-of-file\texttt{"} . A
+      subsequent read using the same encoding will therefore not
+      return another FrameSet, even if the FitsChan is rewound.
+
+      When AST\_WRITE is used to store a FrameSet using DSS encoding,
+      an attempt is first made to simplify the FrameSet to see if it
+      conforms to the DSS model.  Specifically, the current Frame must
+      be a FK5 \htmlref{SkyFrame}{SkyFrame}; the projection must be a tangent plane
+      (gnomonic) projection with polynomial corrections conforming to
+      DSS requirements, and north must be parallel to the second base
+      Frame axis.
+
+      If the simplification process succeeds, a description of the
+      FrameSet is written to the FitsChan using appropriate DSS FITS
+      header cards. The base Frame of the FrameSet is used to form the
+      DSS pixel coordinate system and the current Frame gives the DSS
+      celestial coordinate system.  A successful write operation will
+      over-write any existing DSS encoded data in the FitsChan, but
+      will not affect other (non-DSS) header cards. If a destructive
+      read of a DSS encoded Object has previously occurred, then an
+      attempt will be made to store the FITS header cards back in
+      their original locations.
+
+      If an attempt to simplify a FrameSet to conform to the DSS model
+      fails (or if the Object supplied is not a FrameSet), then no
+      data will be written to the FitsChan and AST\_WRITE will return
+      zero. No error will result.
+   }
+   \sstdiytopic{
+      The FITS-WCS Encoding
+   }{
+      The FITS-WCS convention uses FITS header cards to describe the
+      relationship between pixels in an image (not necessarily
+      2-dimensional) and one or more related \texttt{"} world coordinate systems\texttt{"} .
+      The FITS-WCS encoding may only be used to store a single AST Object
+      in any set of FITS header cards, and that Object must be a FrameSet
+      which conforms to the FITS-WCS model (the FrameSet may, however,
+      contain multiple Frames which will be result in multiple FITS
+      \texttt{"} alternate axis descriptions\texttt{"} ). Details of the use made by this
+      Encoding of the conventions described in the FITS-WCS papers are
+      given in the appendix \texttt{"} FITS-WCS Coverage\texttt{"}  of this document. A few
+      main points are  described below.
+
+      The rotation and scaling of the intermediate world coordinate system
+      can be specified using either \texttt{"} CDi\_j\texttt{"}  keywords, or \texttt{"} PCi\_j\texttt{"}  together
+      with \texttt{"} CDELTi\texttt{"}  keywords. When writing a FrameSet to a FitsChan, the
+      the value of the \htmlref{CDMatrix}{CDMatrix} attribute of the FitsChan determines
+      which system is used.
+
+      In addition, this encoding supports the \texttt{"} TAN with polynomial correction
+      terms\texttt{"}  projection which was included in a draft of the FITS-WCS paper,
+      but was not present in the final version. A \texttt{"} TAN with polynomial
+      correction terms\texttt{"}  projection is represented using a WcsMap with type
+      AST\_\_TPN (rather than AST\_\_TAN which is used to represent simple
+      TAN projections). When reading a FITS header, a CTYPE keyword value
+      including a \texttt{"} -TAN\texttt{"}  code results in an AST\_\_TPN projection if there are
+      any projection parameters (given by the \htmlref{PVi\_m}{PVi\_m} keywords) associated with
+      the latitude axis, or if there are projection parameters associated
+      with the longitude axis for m greater than 4. When writing a
+      FrameSet to a FITS header, an AST\_\_TPN projection gives rise to a
+      CTYPE value including the normal \texttt{"} -TAN\texttt{"}  code, but the projection
+      parameters are stored in keywords with names \texttt{"} QVi\_m\texttt{"} , instead of the
+      usual \texttt{"} PVi\_m\texttt{"} . Since these QV parameters are not part of the
+      FITS-WCS standard they will be ignored by other non-AST software,
+      resulting in the WCS being interpreted as a simple TAN projection
+      without any corrections. This should be seen as an interim solution
+      until such time as an agreed method for describing projection
+      distortions within FITS-WCS has been published.
+
+      AST extends the range of celestial coordinate systems which may be
+      described using this encoding by allowing the inclusion of
+      \texttt{"} AZ--\texttt{"}  and \texttt{"} EL--\texttt{"}  as the coordinate specification within CTYPE
+      values. These form a longitude/latitude pair of axes which describe
+      azimuth and elevation. The geographic position of the observer
+      should be supplied using the OBSGEO-X/Y/Z keywords described in FITS-WCS
+      paper III. Currently, a simple model is used which includes diurnal
+      aberration, but ignores atmospheric refraction, polar motion, etc.
+      These may be added in a later release.
+
+      If an AST SkyFrame that represents offset rather than absolute
+      coordinates (see attribute \htmlref{SkyRefIs}{SkyRefIs}) is written to a FitsChan using
+      FITS-WCS encoding, two alternate axis descriptions will be created.
+      One will describe the offset coordinates, and will use \texttt{"} OFLN\texttt{"}  and
+      \texttt{"} OFLT\texttt{"}  as the axis codes in the CTYPE keywords. The other will
+      describe absolute coordinates as specified by the \htmlref{System}{System} attribute
+      of the SkyFrame, using the usual CTYPE codes (\texttt{"} RA--\texttt{"} /\texttt{"} DEC-\texttt{"} , etc).
+      In addition, the absolute coordinates description will contain
+      AST-specific keywords (SREF1/2, SREFP1/2 and SREFIS) that allow the
+      header to be read back into AST in order to reconstruct the original
+      SkyFrame.
+
+      When reading a FITS-WCS encoded Object (using AST\_READ), the FitsChan
+      concerned must initially be positioned at the first card (its
+      Card attribute must equal 1) and the result of the read, if
+      successful, will always be a pointer to a FrameSet. The base
+      Frame of this FrameSet represents FITS-WCS pixel coordinates,
+      and the current Frame represents the physical coordinate system
+      described by the FITS-WCS primary axis descriptions. If
+      secondary axis descriptions are also present, then the FrameSet
+      may contain additional (non-current) Frames which represent
+      these.  Such a read is always destructive and causes the FITS
+      header cards required for the construction of the FrameSet to be
+      removed from the FitsChan, which is then left positioned at the
+      \texttt{"} end-of-file\texttt{"} . A subsequent read using the same encoding will
+      therefore not return another FrameSet, even if the FitsChan is
+      rewound.
+
+      When AST\_WRITE is used to store a FrameSet using FITS-WCS
+      encoding, an attempt is first made to simplify the FrameSet to
+      see if it conforms to the FITS-WCS model. If this simplification
+      process succeeds (as it often should, as the model is reasonably
+      flexible), a description of the FrameSet is written to the
+      FitsChan using appropriate FITS header cards. The base Frame of
+      the FrameSet is used to form the FITS-WCS pixel coordinate
+      system and the current Frame gives the physical coordinate
+      system to be described by the FITS-WCS primary axis
+      descriptions.  Any additional Frames in the FrameSet may be used
+      to construct secondary axis descriptions, where appropriate.
+
+      A successful write operation will over-write any existing
+      FITS-WCS encoded data in the FitsChan, but will not affect other
+      (non-FITS-WCS) header cards. If a destructive read of a FITS-WCS
+      encoded Object has previously occurred, then an attempt will be
+      made to store the FITS header cards back in their original
+      locations. Otherwise, the new cards will be inserted following
+      any other FITS-WCS related header cards present or, failing
+      that, in front of the current card (as given by the Card
+      attribute).
+
+      If an attempt to simplify a FrameSet to conform to the FITS-WCS
+      model fails (or if the Object supplied is not a FrameSet), then
+      no data will be written to the FitsChan and AST\_WRITE will
+      return zero. No error will result.
+   }
+   \sstdiytopic{
+      The FITS-IRAF Encoding
+   }{
+      The FITS-IRAF encoding can, for most purposes, be considered as
+      a subset of the FITS-WCS encoding (above), although it differs
+      in the details of the FITS keywords used. It is used in exactly
+      the same way and has the same restrictions, but with the
+
+      addition of the following:
+
+      \sstitemlist{
+
+         \sstitem
+         The only celestial coordinate systems that may be represented
+         are equatorial, galactic and ecliptic,
+
+         \sstitem
+         Sky projections can be represented only if any associated
+         projection parameters are set to their default values.
+
+         \sstitem
+         Secondary axis descriptions are not supported, so when writing
+         a FrameSet to a FitsChan, only information from the base and
+         current Frames will be stored.
+
+      }
+      Note that this encoding is provided mainly as an interim measure to
+      provide a more stable alternative to the FITS-WCS encoding until the
+      FITS standard for encoding WCS information is finalised.  The name
+      \texttt{"} FITS-IRAF\texttt{"}  indicates the general keyword conventions used and does
+      not imply that this encoding will necessarily support all features of
+      the WCS scheme used by IRAF software. Nevertheless, an attempt has
+      been made to support a few such features where they are known to be
+      used by important sources of data.
+
+      When writing a FrameSet using the FITS-IRAF encoding, axis rotations
+      are specified by a matrix of FITS keywords of the form \texttt{"} CDi\_j\texttt{"} , where
+      \texttt{"} i\texttt{"}  and \texttt{"} j\texttt{"}  are single digits. The alternative form \texttt{"} CDiiijjj\texttt{"} , which
+      is also in use, is recognised when reading an Object, but is never
+      written.
+
+      In addition, the experimental IRAF \texttt{"} ZPX\texttt{"}  and \texttt{"} TNX\texttt{"}  sky projections will
+      be accepted when reading, but will never be written (the corresponding
+      FITS \texttt{"} ZPN\texttt{"}  or \texttt{"} distorted TAN\texttt{"}  projection being used instead). However,
+      there are restrictions on the use of these experimental projections.
+      For \texttt{"} ZPX\texttt{"} , longitude and latitude correction surfaces (appearing as
+      \texttt{"} lngcor\texttt{"}  or \texttt{"} latcor\texttt{"}  terms in the IRAF-specific \texttt{"} WAT\texttt{"}  keywords) are
+      not supported. For \texttt{"} TNX\texttt{"}  projections, only cubic surfaces encoded as
+      simple polynomials with \texttt{"} half cross-terms\texttt{"}  are supported. If an
+      un-usable \texttt{"} TNX\texttt{"}  or \texttt{"} ZPX\texttt{"}  projection is encountered while reading
+      from a FitsChan, a simpler form of TAN or ZPN projection is used
+      which ignores the unsupported features and may therefore be
+      inaccurate. If this happens, a warning message is added to the
+      contents of the FitsChan as a set of cards using the keyword \texttt{"} ASTWARN\texttt{"} .
+
+      You should not normally attempt to mix the foreign FITS encodings within
+      the same FitsChan, since there is a risk that keyword clashes may occur.
+   }
+   \sstdiytopic{
+      The FITS-PC Encoding
+   }{
+      The FITS-PC encoding can, for most purposes, be considered as
+      equivalent to the FITS-WCS encoding (above), although it differs
+      in the details of the FITS keywords used. It is used in exactly
+      the same way and has the same restrictions.
+   }
+   \sstdiytopic{
+      The FITS-AIPS Encoding
+   }{
+      The FITS-AIPS encoding can, for most purposes, be considered as
+      equivalent to the FITS-WCS encoding (above), although it differs
+      in the details of the FITS keywords used. It is used in exactly
+      the same way and has the same restrictions, but with the
+
+      addition of the following:
+
+      \sstitemlist{
+
+         \sstitem
+         The only celestial coordinate systems that may be represented
+         are equatorial, galactic and ecliptic,
+
+         \sstitem
+         Spectral axes can only be represented if they represent
+         frequency, radio velocity or optical velocity, and are linearly
+         sampled in frequency. In addition, the standard of rest
+         must be LSRK, LSRD, barycentric or geocentric.
+
+         \sstitem
+         Sky projections can be represented only if any associated
+         projection parameters are set to their default values.
+
+         \sstitem
+         The AIT, SFL and MER projections can only be written if the CRVAL
+         keywords are zero for both longitude and latitude axes.
+
+         \sstitem
+         Secondary axis descriptions are not supported, so when writing
+         a FrameSet to a FitsChan, only information from the base and
+         current Frames will be stored.
+
+         \sstitem
+         If there are more than 2 axes in the base and current Frames, any
+         rotation must be restricted to the celestial plane, and must involve
+         no shear.
+      }
+   }
+   \sstdiytopic{
+      The FITS-AIPS$+$$+$ Encoding
+   }{
+      The FITS-AIPS$+$$+$ encoding is based on the FITS-AIPS encoding, but
+      includes some features of the FITS-IRAF and FITS-PC encodings.
+      Specifically, any celestial projections supported by FITS-PC may be
+      used, including those which require parameterisation, and the axis
+      rotation and scaling may be specified using CDi\_j keywords. When
+      writing a FITS header, rotation will be specified using CROTA/CDELT
+      keywords if possible, otherwise CDi\_j keywords will be used instead.
+   }
+   \sstdiytopic{
+      The FITS-CLASS Encoding
+   }{
+      The FITS-CLASS encoding uses the conventions of the CLASS project.
+      These are described in the section \texttt{"} Developer Manual\texttt{"} /\texttt{"} CLASS FITS
+
+      Format\texttt{"}  contained in the CLASS documentation at:
+
+      http://www.iram.fr/IRAMFR/GILDAS/doc/html/class-html/class.html.
+
+      This encoding is similar to FITS-AIPS with the following restrictions:
+
+      \sstitemlist{
+
+         \sstitem
+         When a \htmlref{SpecFrame}{SpecFrame} is created by reading a FITS-CLASS header, the
+           attributes describing the observer\texttt{'} s position (\htmlref{ObsLat}{ObsLat}, \htmlref{ObsLon}{ObsLon} and
+           \htmlref{ObsAlt}{ObsAlt}) are left unset because the CLASS encoding does not specify
+           these values. Conversions to or from the topocentric standard of rest
+           will therefore be inaccurate (typically by up to about 0.5 km/s)
+           unless suitable values are assigned to these attributes after the
+           FrameSet has been created.
+
+         \sstitem
+         When writing a FrameSet to a FITS-CLASS header, the current Frame
+           in the FrameSet must have at least 3 WCS axes, of which one must be
+           a linear spectral axis. The spectral axis in the created header will
+           always describe frequency. If the spectral axis in the supplied
+           FrameSet refers to some other system (e.g. radio velocity, etc),
+           then it will be converted to frequency.
+
+         \sstitem
+         There must be a pair of celestial axes - either (RA,Dec) or
+           (GLON,GLAT). RA and Dec must be either FK4/B1950 or FK5/J2000.
+
+         \sstitem
+         A limited range of projection codes (TAN, ARC, STG, AIT, SFL, SIN)
+           can be used. For AIT and SFL, the reference point must be at the
+           origin of longitude and latitude. For SIN, the associated projection
+           parameters must both be zero.
+
+         \sstitem
+         No rotation of the celestial axes is allowed, unless the spatial
+           axes are degenerate (i.e. cover only a single pixel).
+
+         \sstitem
+         The frequency axis in the created header will always describe
+           frequency in the source rest frame. If the supplied FrameSet uses
+           some other standard of rest then suitable conversion will be applied.
+
+         \sstitem
+         The source velocity must be defined. In other words, the SpecFrame
+           attributes \htmlref{SourceVel}{SourceVel} and \htmlref{SourceVRF}{SourceVRF} must have been assigned values.
+
+         \sstitem
+         The frequency axis in a FITS-CLASS header does not represent
+           absolute frequency, but instead represents offsets from the rest
+           frequency in the standard of rest of the source.
+
+      }
+      When writing a FrameSet out using FITS-CLASS encoding, the current
+      Frame may be temporarily modified if this will allow the header
+      to be produced. If this is done, the associated pixel-$>$WCS Mapping
+      will also be modified to take account of the changes to the Frame.
+      The modifications performed include re-ordering axes (WCS axes, not
+      pixel axes), changing spectral coordinate system and standard of
+      rest, changing the celestial coordinate system and reference equinox,
+      and changing axis units.
+   }
+   \sstdiytopic{
+      The NATIVE Encoding
+   }{
+      The NATIVE encoding may be used to store a description of any
+      class of AST Object in the form of FITS header cards, and (for
+      most practical purposes) any number of these Object descriptions
+      may be stored within a single set of FITS cards. If multiple
+      Object descriptions are stored, they are written and read
+      sequentially. The NATIVE encoding makes use of unique FITS
+      keywords which are designed not to clash with keywords that have
+      already been used for other purposes (if a potential clash is
+      detected, an alternative keyword is constructed to avoid the
+      clash).
+
+      When reading a NATIVE encoded object from a FitsChan (using
+      AST\_READ), FITS header cards are read, starting at the current
+      card (as determined by the Card attribute), until the start of
+      the next Object description is found. This description is then
+      read and converted into an AST Object, for which a pointer is
+      returned. Such a read is always destructive and causes all the
+      FITS header cards involved in the Object description to be
+      removed from the FitsChan, which is left positioned at the
+      following card.
+
+      The Object returned may be of any class, depending on the
+      description that was read, and other AST routines may be used to
+      validate it (for example, by examining its \htmlref{Class}{Class} or \htmlref{ID}{ID} attribute
+      using AST\_GETC). If further NATIVE encoded Object descriptions
+      exist in the FitsChan, subsequent calls to AST\_READ will return
+      the Objects they describe in sequence (and destroy their
+      descriptions) until no more remain between the current card and
+      the \texttt{"} end-of-file\texttt{"} .
+
+      When AST\_WRITE is used to write an Object using NATIVE encoding,
+      a description of the Object is inserted immediately before the
+      current card (as determined by the Card attribute).  Multiple
+      Object descriptions may be written in this way and are stored
+      separately (and sequentially if the Card attribute is not
+      modified between the writes). A write operation using the NATIVE
+      encoding does not over-write previously written Object
+      descriptions. Note, however, that subsequent behaviour is
+      undefined if an Object description is written inside a
+      previously-written description, so this should be avoided.
+
+      When an Object is written to a FitsChan using NATIVE encoding,
+      AST\_WRITE should (barring errors) always transfer data and
+      return a value of 1.
+   }
+}
+\sstroutine{
+   Epoch
+}{
+   Epoch of observation
+}{
+   \sstdescription{
+      This attribute is used to qualify the coordinate systems described by
+      a \htmlref{Frame}{Frame}, by giving the moment in time when the coordinates are known
+      to be correct. Often, this will be the date of observation, and is
+      important in cases where coordinates systems move with respect to each
+      other over the course of time.
+
+      The Epoch attribute is stored as a Modified Julian Date, but
+      when setting its value it may be given in a variety of
+      formats. See the \texttt{"} Input Formats\texttt{"}  section (below) for details.
+      Strictly, the Epoch value should be supplied in the TDB timescale,
+      but for some purposes (for instance, for converting sky positions
+      between different types of equatorial system) the timescale is not
+      significant, and UTC may be used.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute. The basic Frame class provides
+         a default of J2000.0 (Julian) but makes no use of the Epoch value.
+         This is because the Frame class does not distinguish between
+         different Cartesian coordinate systems (see the \htmlref{System}{System} attribute).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The default Epoch value for a CmpFrame is selected as follows;
+         if the Epoch attribute has been set in the first component Frame
+         then the Epoch value from the first component Frame is used as
+         the default for the CmpFrame. Otherwise, if the Epoch attribute has
+         been set in the second component Frame then the Epoch value from the
+         second component Frame is used as the default for the CmpFrame.
+         Otherwise, the default Epoch value from the first component
+         Frame is used as the default for the CmpFrame. When the Epoch
+         attribute of a CmpFrame is set or cleared, it is also set or
+         cleared in the two component Frames.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Epoch attribute of a FrameSet is the same as that of its current
+         Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The coordinates of sources within a SkyFrame can change with time
+         for various reasons, including: (i) changing aberration of light
+         caused by the observer\texttt{'} s velocity (e.g. due to the Earth\texttt{'} s motion
+         around the Sun), (ii) changing gravitational deflection by the Sun
+         due to changes in the observer\texttt{'} s position with time, (iii) fictitious
+         motion due to rotation of non-inertial coordinate systems (e.g. the
+         old FK4 system), and (iv) proper motion of the source itself (although
+         this last effect is not handled by the SkyFrame class because it
+         affects individual sources rather than the coordinate system as
+         a whole).
+
+         The default Epoch value in a SkyFrame is B1950.0 (Besselian) for the
+         old FK4-based coordinate systems (see the System attribute) and
+         J2000.0 (Julian) for all others.
+
+         Care must be taken to distinguish the Epoch value, which relates to
+         motion (or apparent motion) of the source, from the superficially
+         similar \htmlref{Equinox}{Equinox} value. The latter is used to qualify a coordinate
+         system which is itself in motion in a (notionally) predictable way
+         as a result of being referred to a slowly moving reference plane
+         (e.g. the equator).
+
+         See the description of the System attribute for details of which
+         qualifying attributes apply to each celestial coordinate system.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         A TimeFrame describes a general time axis and so cannot be completely
+         characterised by a single Epoch value. For this reason the TimeFrame
+         class makes no use of the Epoch attribute. However, user code can
+         still make use of the attribute if necessary to represent a \texttt{"} typical\texttt{"}
+         time spanned by the TimeFrame. The default Epoch value for a TimeFrame
+         will be the TDB equivalent of the current value of the TimeFrame\texttt{'} s
+         \htmlref{TimeOrigin}{TimeOrigin} attribute. If no value has been set for TimeOrigin,
+         then the default Epoch value is J2000.0.
+      }
+   }
+   \sstdiytopic{
+      Input Formats
+   }{
+      The formats accepted when setting an Epoch value are listed
+      below. They are all case-insensitive and are generally tolerant
+      of extra white space and alternative field delimiters:
+
+      \sstitemlist{
+
+         \sstitem
+         Besselian Epoch: Expressed in decimal years, with or without
+         decimal places (\texttt{"} B1950\texttt{"}  or \texttt{"} B1976.13\texttt{"}  for example).
+
+         \sstitem
+         Julian Epoch: Expressed in decimal years, with or without
+         decimal places (\texttt{"} J2000\texttt{"}  or \texttt{"} J2100.9\texttt{"}  for example).
+
+         \sstitem
+         Year: Decimal years, with or without decimal places (\texttt{"} 1996.8\texttt{"}
+         for example).  Such values are interpreted as a Besselian epoch
+         (see above) if less than 1984.0 and as a Julian epoch otherwise.
+
+         \sstitem
+         Julian Date: With or without decimal places (\texttt{"} JD 2454321.9\texttt{"}  for
+         example).
+
+         \sstitem
+         Modified Julian Date: With or without decimal places
+         (\texttt{"} MJD 54321.4\texttt{"}  for example).
+
+         \sstitem
+         Gregorian Calendar Date: With the month expressed either as an
+         integer or a 3-character abbreviation, and with optional decimal
+         places to represent a fraction of a day (\texttt{"} 1996-10-2\texttt{"}  or
+         \texttt{"} 1996-Oct-2.6\texttt{"}  for example). If no fractional part of a day is
+         given, the time refers to the start of the day (zero hours).
+
+         \sstitem
+         Gregorian Date and Time: Any calendar date (as above) but with
+         a fraction of a day expressed as hours, minutes and seconds
+         (\texttt{"} 1996-Oct-2 12:13:56.985\texttt{"}  for example). The date and time can be
+         separated by a space or by a \texttt{"} T\texttt{"}  (as used by ISO8601 format).
+      }
+   }
+   \sstdiytopic{
+      Output Format
+   }{
+      When enquiring Epoch values, the format used is the \texttt{"} Year\texttt{"}
+      format described under \texttt{"} Input Formats\texttt{"} . This is a value in
+      decimal years which will be a Besselian epoch if less than
+      1984.0 and a Julian epoch otherwise.  By omitting any character
+      prefix, this format allows the Epoch value to be obtained as
+      either a character string or a floating point value.
+   }
+}
+\sstroutine{
+   Equinox
+}{
+   Epoch of the mean equinox
+}{
+   \sstdescription{
+      This attribute is used to qualify those celestial coordinate
+      systems described by a \htmlref{SkyFrame}{SkyFrame} which are notionally based on
+      the ecliptic (the plane of the Earth\texttt{'} s orbit around the Sun)
+      and/or the Earth\texttt{'} s equator.
+
+      Both of these planes are in motion and their positions are
+      difficult to specify precisely. In practice, therefore, a model
+      ecliptic and/or equator are used instead. These, together with
+      the point on the sky that defines the coordinate origin (the
+      intersection of the two planes termed the \texttt{"} mean equinox\texttt{"} ) move
+      with time according to some model which removes the more rapid
+      fluctuations. The SkyFrame class supports both the FK4 and
+      FK5 models.
+
+      The position of a fixed source expressed in any of these
+      coordinate systems will appear to change with time due to
+      movement of the coordinate system itself (rather than motion of
+      the source).  Such coordinate systems must therefore be
+      qualified by a moment in time (the \texttt{"} epoch of the mean equinox\texttt{"}
+      or \texttt{"} equinox\texttt{"}  for short) which allows the position of the model
+      coordinate system on the sky to be determined. This is the role
+      of the Equinox attribute.
+
+      The Equinox attribute is stored as a Modified Julian Date, but
+      when setting or getting its value you may use the same formats
+      as for the \htmlref{Epoch}{Epoch} attribute (q.v.).
+
+      The default Equinox value is B1950.0 (Besselian) for the old
+      FK4-based coordinate systems (see the \htmlref{System}{System} attribute) and
+      J2000.0 (Julian) for all others.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Care must be taken to distinguish the Equinox value, which
+         relates to the definition of a time-dependent coordinate system
+         (based on solar system reference planes which are in motion),
+         from the superficially similar Epoch value. The latter is used
+         to qualify coordinate systems where the positions of sources
+         change with time (or appear to do so) for a variety of other
+         reasons, such as aberration of light caused by the observer\texttt{'} s
+         motion, etc.
+
+         \sstitem
+         See the description of the System attribute for details of
+         which qualifying attributes apply to each celestial coordinate
+         system.
+      }
+   }
+}
+\sstroutine{
+   Escape
+}{
+   Allow changes of character attributes within strings?
+}{
+   \sstdescription{
+      This attribute controls the appearance of text strings and numerical
+      labels drawn by the \htmlref{AST\_GRID}{AST\_GRID} and (for the \htmlref{Plot}{Plot} class) \htmlref{AST\_TEXT}{AST\_TEXT} routines,
+      by determining if any escape sequences contained within the strings
+      should be used to control the appearance of the text, or should
+      be printed literally. Note, the \htmlref{Plot3D}{Plot3D} class only interprets escape
+      sequences within the
+      AST\_GRID routine.
+
+      If the Escape value of a Plot is one (the default), then any
+      escape sequences in text strings produce the effects described
+      below when printed. Otherwise, they are printed literally.
+
+      See also the \htmlref{AST\_ESCAPES}{AST\_ESCAPES} function.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Escape Sequences
+   }{
+      Escape sequences are introduced into the text string by a percent
+      \texttt{"} \%\texttt{"}  character. Any unrecognised, illegal or incomplete escape sequences
+      are printed literally. The following escape sequences are
+      currently recognised (\texttt{"} ...\texttt{"}  represents a string of one or more
+      decimal digits):
+
+        \%\%      - Print a literal \texttt{"} \%\texttt{"}  character.
+
+        \%$\wedge$...$+$  - Draw subsequent characters as super-scripts. The digits
+                  \texttt{"} ...\texttt{"}  give the distance from the base-line of \texttt{"} normal\texttt{"}
+                  text to the base-line of the super-script text, scaled
+                  so that a value of \texttt{"} 100\texttt{"}  corresponds to the height of
+                  \texttt{"} normal\texttt{"}  text.
+        \%$\wedge$$+$     - Draw subsequent characters with the normal base-line.
+
+        \%v...$+$  - Draw subsequent characters as sub-scripts. The digits
+                  \texttt{"} ...\texttt{"}  give the distance from the base-line of \texttt{"} normal\texttt{"}
+                  text to the base-line of the sub-script text, scaled
+                  so that a value of \texttt{"} 100\texttt{"}  corresponds to the height of
+                  \texttt{"} normal\texttt{"}  text.
+
+        \%v$+$     - Draw subsequent characters with the normal base-line
+                  (equivalent to \%$\wedge$$+$).
+
+        \%$>$...$+$  - Leave a gap before drawing subsequent characters.
+                  The digits \texttt{"} ...\texttt{"}  give the size of the gap, scaled
+                  so that a value of \texttt{"} 100\texttt{"}  corresponds to the height of
+                  \texttt{"} normal\texttt{"}  text.
+
+        \%$<$...$+$  - Move backwards before drawing subsequent characters.
+                  The digits \texttt{"} ...\texttt{"}  give the size of the movement, scaled
+                  so that a value of \texttt{"} 100\texttt{"}  corresponds to the height of
+                  \texttt{"} normal\texttt{"}  text.
+
+        \%s...$+$  - Change the Size attribute for subsequent characters. The
+                  digits \texttt{"} ...\texttt{"}  give the new Size as a fraction of the
+                  \texttt{"} normal\texttt{"}  Size, scaled so that a value of \texttt{"} 100\texttt{"}  corresponds
+                  to 1.0;
+
+        \%s$+$     - Reset the Size attribute to its \texttt{"} normal\texttt{"}  value.
+
+        \%w...$+$  - Change the Width attribute for subsequent characters. The
+                  digits \texttt{"} ...\texttt{"}  give the new width as a fraction of the
+                  \texttt{"} normal\texttt{"}  Width, scaled so that a value of \texttt{"} 100\texttt{"}  corresponds
+                  to 1.0;
+
+        \%w$+$     - Reset the Size attribute to its \texttt{"} normal\texttt{"}  value.
+
+        \%f...$+$  - Change the Font attribute for subsequent characters. The
+                  digits \texttt{"} ...\texttt{"}  give the new Font value.
+
+        \%f$+$     - Reset the Font attribute to its \texttt{"} normal\texttt{"}  value.
+
+        \%c...$+$  - Change the Colour attribute for subsequent characters. The
+                  digits \texttt{"} ...\texttt{"}  give the new Colour value.
+
+        \%c$+$     - Reset the Colour attribute to its \texttt{"} normal\texttt{"}  value.
+
+        \%t...$+$  - Change the Style attribute for subsequent characters. The
+                  digits \texttt{"} ...\texttt{"}  give the new Style value.
+
+        \%t$+$     - Reset the Style attribute to its \texttt{"} normal\texttt{"}  value.
+
+        \%h$+$     - Remember the current horizontal position (see \texttt{"} \%g$+$\texttt{"} )
+
+        \%g$+$     - Go to the horizontal position of the previous \texttt{"} \%h$+$\texttt{"}  (if any).
+
+        \%-      - Push the current graphics attribute values onto the top of
+                  the stack (see \texttt{"} \%$+$\texttt{"} ).
+
+        \%$+$      - Pop attributes values of the top the stack (see \texttt{"} \%-\texttt{"} ). If
+                  the stack is empty, \texttt{"} normal\texttt{"}  attribute values are restored.
+   }
+}
+\sstroutine{
+   FillFactor
+}{
+   Fraction of the Region which is of interest
+}{
+   \sstdescription{
+      This attribute indicates the fraction of the \htmlref{Region}{Region} which is of
+      interest. AST does not use this attribute internally for any purpose.
+      Typically, it could be used to indicate the fraction of the Region for
+      which data is available.
+
+      The supplied value must be in the range 0.0 to 1.0, and the default
+      value is 1.0 (except as noted below).
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpRegion}{CmpRegion}
+      }{
+         The default FillFactor for a CmpRegion is the FillFactor of its
+         first component Region.
+      }
+      \sstsubsection{
+         \htmlref{Prism}{Prism}
+      }{
+         The default FillFactor for a Prism is the product of the
+         FillFactors of its two component Regions.
+      }
+      \sstsubsection{
+         \htmlref{Stc}{Stc}
+      }{
+         The default FillFactor for an Stc is the FillFactor of its
+         encapsulated Region.
+      }
+   }
+}
+\sstroutine{
+   FitsAxisOrder
+}{
+   Frame title
+}{
+   \sstdescription{
+      This attribute specifies the order for the WCS axes in any new
+      FITS-WCS headers created using the
+      \htmlref{AST\_WRITE}{AST\_WRITE}
+      method.
+
+      The value of the FitsAxisOrder attribute can be either \texttt{"} $<$auto$>$\texttt{"}
+      (the default value), \texttt{"} $<$copy$>$\texttt{"}  or a space-separated list of axis
+      symbols:
+
+      \texttt{"} $<$auto$>$\texttt{"} : causes the WCS axis order to be chosen automatically so that
+      the i\texttt{'} th WCS axis in the new FITS header is the WCS axis which is
+      more nearly parallel to the i\texttt{'} th pixel axis.
+
+      \texttt{"} $<$copy$>$\texttt{"} : causes the WCS axis order to be set so that the i\texttt{'} th WCS
+      axis in the new FITS header is the i\texttt{'} th WCS axis in the current
+      \htmlref{Frame}{Frame} of the \htmlref{FrameSet}{FrameSet} being written out to the header.
+
+      \texttt{"} Sym1 Sym2...\texttt{"} : the space-separated list is seached in turn for
+      the Symbol attribute of each axis in the current Frame of the
+      FrameSet. The order in which these Symbols occur within the
+      space-separated list defines the order of the WCS axes in the
+      new FITS header. An error is reported if Symbol for a current
+      Frame axis is not present in the supplied list. However, no error
+      is reported if the list contains extra words that do not correspond
+      to the Symbol of any current Frame axis.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   FitsDigits
+}{
+   Digits of precision for floating point FITS values
+}{
+   \sstdescription{
+      This attribute gives the number of significant decimal digits to
+      use when formatting floating point values for inclusion in the
+      FITS header cards within a \htmlref{FitsChan}{FitsChan}.
+
+      By default, a positive value is used which results in no loss of
+      information, assuming that the value is double precision.
+      Usually, this causes no problems.
+
+      However, to adhere strictly to the recommendations of the FITS
+      standard, the width of the formatted value (including sign,
+      decimal point and exponent) ought not to be more than 20
+      characters. If you are concerned about this, you should set
+      FitsDigits to a negative value, such as -15. In this case, the
+      absolute value ($+$15) indicates the maximum number of significant
+      digits to use, but the actual number used may be fewer than this
+      to ensure that the FITS recommendations are satisfied. When
+      using this approach, the resulting number of significant digits
+      may depend on the value being formatted and on the presence of
+      any sign, decimal point or exponent.
+
+      The value of this attribute is effective when FITS header cards
+      are output, either using
+      \htmlref{AST\_FINDFITS}{AST\_FINDFITS} or by the action of the FitsChan\texttt{'} s sink routine
+      when it is finally deleted.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Font(element)
+}{
+   Character font for a Plot element
+}{
+   \sstdescription{
+      This attribute determines the character font index used when
+      drawing each element of graphical output produced by a \htmlref{Plot}{Plot}. It
+      takes a separate value for each graphical element so that, for
+      instance, the setting \texttt{"} Font(title)=2\texttt{"}  causes the Plot title to
+      be drawn using font number 2.
+
+      The range of integer font indices available and the appearance
+      of the resulting text is determined by the underlying graphics
+      system.  The default behaviour is for all graphical elements to
+      be drawn using the default font supplied by this graphics
+      system.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of the graphical elements available, see the
+         description of the Plot class.
+
+         \sstitem
+         If no graphical element is specified, (e.g. \texttt{"} Font\texttt{"}  instead
+         of \texttt{"} Font(title)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will
+         affect the attribute value of all graphical elements, while a
+         \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}  operation will use just the Font(TextLab)
+         value.
+      }
+   }
+}
+\sstroutine{
+   Format(axis)
+}{
+   Format specification for axis values
+}{
+   \sstdescription{
+      This attribute specifies the format to be used when displaying
+      coordinate values associated with a particular \htmlref{Frame}{Frame} axis
+      (i.e. to convert values from binary to character form). It is
+      interpreted by the \htmlref{AST\_FORMAT}{AST\_FORMAT} function and determines the
+      formatting which it applies.
+
+      If no Format value is set for a Frame axis, a default value is
+      supplied instead. This is based on the value of the Digits, or
+      Digits(axis), attribute and is chosen so that it displays the
+      requested number of digits of precision.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The Frame class interprets this attribute as a format
+         specification string to be passed to the C \texttt{"} printf\texttt{"}  function
+         (e.g. \texttt{"} \%1.7G\texttt{"} ) in order to format a single coordinate value
+         (supplied as a double precision number).
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the syntax and default value of
+         the Format string to allow the formatting of sexagesimal
+         values as appropriate for the particular celestial coordinate
+         system being represented. The syntax of SkyFrame Format
+         strings is described (below) in the \texttt{"} SkyFrame Formats\texttt{"}
+         section.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Format attribute of a FrameSet axis is the same as that
+         of its current Frame (as specified by the \htmlref{Current}{Current}
+         attribute). Note that the syntax of the Format string is also
+         determined by the current Frame.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class extends the syntax of the Format string to
+         allow the formatting of TimeFrame axis values as Gregorian calendar
+         dates and times. The syntax of TimeFrame Format strings is described
+         (below) in the \texttt{"} TimeFrame Formats\texttt{"}  section.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+   \sstdiytopic{
+      SkyFrame Formats
+   }{
+      The Format string supplied for a SkyFrame should contain zero or
+      more of the following characters. These may occur in any order,
+      but the following is recommended for clarity:
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} $+$\texttt{"} : Indicates that a plus sign should be prefixed to positive
+         values. By default, no plus sign is used.
+
+         \sstitem
+         \texttt{"} z\texttt{"} : Indicates that leading zeros should be prefixed to the
+         value so that the first field is of constant width, as would be
+         required in a fixed-width table (leading zeros are always
+         prefixed to any fields that follow). By default, no leading
+         zeros are added.
+
+         \sstitem
+         \texttt{"} i\texttt{"} : Use the standard ISO field separator (a colon) between
+         fields. This is the default behaviour.
+
+         \sstitem
+         \texttt{"} b\texttt{"} : Use a blank to separate fields.
+
+         \sstitem
+         \texttt{"} l\texttt{"} : Use a letter (\texttt{"} h\texttt{"} /\texttt{"} d\texttt{"} , \texttt{"} m\texttt{"}  or \texttt{"} s\texttt{"}  as appropriate) to
+         separate fields.
+
+         \sstitem
+         \texttt{"} g\texttt{"} : Use a letter and symbols to separate fields (\texttt{"} h\texttt{"} /\texttt{"} d\texttt{"} , \texttt{"} m\texttt{"}  or \texttt{"} s\texttt{"} ,
+         etc, as appropriate), but include escape sequences in the formatted
+         value so that the \htmlref{Plot}{Plot} class will draw the separators as small
+         super-scripts.
+
+         \sstitem
+         \texttt{"} d\texttt{"} : Include a degrees field. Expressing the angle purely in
+         degrees is also the default if none of \texttt{"} h\texttt{"} , \texttt{"} m\texttt{"} , \texttt{"} s\texttt{"}  or \texttt{"} t\texttt{"}  are
+         given.
+
+         \sstitem
+         \texttt{"} h\texttt{"} : Express the angle as a time and include an hours field
+         (where 24 hours correspond to 360 degrees). Expressing the angle
+         purely in hours is also the default if \texttt{"} t\texttt{"}  is given without
+         either \texttt{"} m\texttt{"}  or \texttt{"} s\texttt{"} .
+
+         \sstitem
+         \texttt{"} m\texttt{"} : Include a minutes field. By default this is not included.
+
+         \sstitem
+         \texttt{"} s\texttt{"} : Include a seconds field. By default this is not included.
+         This request is ignored if \texttt{"} d\texttt{"}  or \texttt{"} h\texttt{"}  is given, unless a minutes
+         field is also included.
+
+         \sstitem
+         \texttt{"} t\texttt{"} : Express the angle as a time (where 24 hours correspond to
+         360 degrees). This option is ignored if either \texttt{"} d\texttt{"}  or \texttt{"} h\texttt{"}  is
+         given and is intended for use where the value is to be expressed
+         purely in minutes and/or seconds of time (with no hours
+         field). If \texttt{"} t\texttt{"}  is given without \texttt{"} d\texttt{"} , \texttt{"} h\texttt{"} , \texttt{"} m\texttt{"}  or \texttt{"} s\texttt{"}  being
+         present, then it is equivalent to \texttt{"} h\texttt{"} .
+
+         \sstitem
+         \texttt{"} .\texttt{"} : Indicates that decimal places are to be given for the
+         final field in the formatted string (whichever field this
+         is). The \texttt{"} .\texttt{"}  should be followed immediately by an unsigned
+         integer which gives the number of decimal places required, or by an
+         asterisk. If an asterisk is supplied, a default number of decimal
+         places is used which is based on the value of the Digits
+         attribute.
+
+      }
+      All of the above format specifiers are case-insensitive. If
+      several characters make conflicting requests (e.g. if both \texttt{"} i\texttt{"}
+      and \texttt{"} b\texttt{"}  appear), then the character occurring last takes
+      precedence, except that \texttt{"} d\texttt{"}  and \texttt{"} h\texttt{"}  always override \texttt{"} t\texttt{"} .
+
+      If the format string starts with a percentage sign (\%), then the
+      whole format string is assumed to conform to the syntax defined by
+      the Frame class, and the axis values is formated as a decimal
+      radians value.
+   }
+   \sstdiytopic{
+      TimeFrame Formats
+   }{
+      The Format string supplied for a TimeFrame should either use the
+      syntax defined by the base Frame class (i.e. a C \texttt{"} printf\texttt{"}  format
+      string), or the extended \texttt{"} iso\texttt{"}  syntax described below (the default
+      value is inherited from the Frame class):
+
+      \sstitemlist{
+
+         \sstitem
+         C \texttt{"} printf\texttt{"}  syntax: If the Format string is a C \texttt{"} printf\texttt{"}  format
+         description such as \texttt{"} \%1.7G\texttt{"} , the TimeFrame axis value will be
+         formatted without change as a floating point value using this format.
+         The formatted string will thus represent an offset from the zero point
+         specified by the TimeFrame\texttt{'} s \htmlref{TimeOrigin}{TimeOrigin} attribute, measured in
+         units given by the TimeFrame\texttt{'} s Unit attribute.
+
+         \sstitem
+         \texttt{"} iso\texttt{"}  syntax: This is used to format a TimeFrame axis value as a
+         Gregorian date followed by an optional time of day. If the Format
+         value commences with the string \texttt{"} iso\texttt{"}  then the TimeFrame axis value
+         will be converted to an absolute MJD, including the addition of the
+         current TimeOrigin value, and then formatted as a Gregorian date
+         using the format \texttt{"} yyyy-mm-dd\texttt{"} . Optionally, the Format value may
+         include an integer precision following the \texttt{"} iso\texttt{"}  specification (e.g.
+         \texttt{"} iso.2\texttt{"} ), in which case the time of day will be appended to the
+         formatted date (if no time of day is included, the date field is
+         rounded to the nearest day). The integer value in the Format string
+         indicates the number of decimal places to use in the seconds field. For
+         instance, a Format value of \texttt{"} iso.0\texttt{"}  produces a time of day of the form
+         \texttt{"} hh:mm:ss\texttt{"} , and a Format value of \texttt{"} iso.2\texttt{"}  produces a time of day of the
+         form \texttt{"} hh:mm:ss.ss\texttt{"} . The date and time fields will be separated by a
+         space unless \texttt{'} T\texttt{'}  is appended to the end of string, in which case
+         the letter T (upper case) will be used as the separator. The value of
+         the Digits attribute is ignored when using this \texttt{"} iso\texttt{"}  format.
+      }
+   }
+}
+\sstroutine{
+   Full
+}{
+   Set level of output detail
+}{
+   \sstdescription{
+      This attribute is a three-state flag and takes values of -1, 0
+      or $+$1.  It controls the amount of information included in the
+      output generated by a \htmlref{Channel}{Channel}.
+
+      If Full is zero, then a modest amount of
+      non-essential but useful information will be included in the
+      output. If Full is negative, all non-essential information will
+      be suppressed to minimise the amount of output, while if it is
+      positive, the output will include the maximum amount of detailed
+      information about the \htmlref{Object}{Object} being written.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         The default value is zero for a normal Channel.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         The default value is zero for a FitsChan.
+      }
+      \sstsubsection{
+         \htmlref{XmlChan}{XmlChan}
+      }{
+         The default value is -1 for an XmlChan.
+      }
+      \sstsubsection{
+         \htmlref{StcsChan}{StcsChan}
+      }{
+         The default value is zero for an StcsChan. Set a positive value
+         to cause default values to be included in STC-S descriptions.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         All positive values supplied for this attribute are converted
+         to $+$1 and all negative values are converted to -1.
+      }
+   }
+}
+\sstroutine{
+   Gap(axis)
+}{
+   Interval between linearly spaced major axis values of a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      the linear interval between the \texttt{"} major\texttt{"}  axis values of a \htmlref{Plot}{Plot}, at
+      which (for example) major tick marks are drawn. It takes a separate
+      value for each physical axis of the Plot so that, for instance,
+      the setting \texttt{"} Gap(2)=3.0\texttt{"}  specifies the difference between adjacent major
+      values along the second axis. The Gap attribute is only used when
+      the LogTicks attribute indicates that the spacing between major axis
+      values is to be linear. If major axis values are logarithmically spaced
+      then the gap is specified using attribute LogGap.
+
+      The Gap value supplied will usually be rounded to the nearest
+      \texttt{"} nice\texttt{"}  value, suitable (e.g.) for generating axis labels, before
+      use. To avoid this \texttt{"} nicing\texttt{"}  you should set an explicit format
+      for the axis using the \htmlref{Format(axis)}{Format(axis)} or \htmlref{Digits/Digits(axis)}{Digits/Digits(axis)}
+      attribute. The default behaviour is for the Plot to generate its
+      own Gap value when required, based on the range of axis values
+      to be represented.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Gap value should use the same units as are used internally
+         for storing coordinate values on the corresponding axis. For
+         example, with a celestial coordinate system, the Gap value
+         should be in radians, not hours or degrees.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} Gap\texttt{"}  instead of \texttt{"} Gap(2)\texttt{"} ),
+         then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the attribute
+         value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}  operation
+         will use just the Gap(1) value.
+      }
+   }
+}
+\sstroutine{
+   Grf
+}{
+   Use Grf routines registered through AST\_GRFSET?
+}{
+   \sstdescription{
+      This attribute selects the routines which are used to draw graphics by
+      the \htmlref{Plot}{Plot} class. If it is zero, then the routines in the graphics
+      interface selected at link-time are used (see the \htmlref{ast\_link}{ast\_link} script).
+      Otherwise, routines registered using \htmlref{AST\_GRFSET}{AST\_GRFSET} are used. In this
+      case, if a routine is needed which has not been registered,
+      then the routine in the graphics interface selected at link-time is
+      used.
+
+      The default is to use the graphics interface
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Plot3D}{Plot3D}
+      }{
+         The Plot3D class ignores this attributes, assuming a value of
+         zero.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The value of this attribute is not saved when the Plot is written
+         out through a \htmlref{Channel}{Channel} to an external data store. On re-loading such
+         a Plot using \htmlref{AST\_READ}{AST\_READ}, the attribute will be cleared, resulting in the
+         graphics interface selected at link-time being used.
+      }
+   }
+}
+\sstroutine{
+   Grid
+}{
+   Draw grid lines for a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether grid lines (a grid of curves marking the \texttt{"} major\texttt{"}  values
+      on each axis) are drawn across the plotting area.
+
+      If the Grid value of a \htmlref{Plot}{Plot} is non-zero, then grid lines will be
+      drawn. Otherwise, short tick marks on the axes are used to mark
+      the major axis values.  The default behaviour is to use tick
+      marks if the entire plotting area is filled by valid physical
+      coordinates, but to draw grid lines otherwise.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The spacing between major axis values, which determines the
+         spacing of grid lines, may be set using the \htmlref{Gap(axis)}{Gap(axis)} attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismAlpha
+}{
+   The angle of incidence of the incoming light on the grating surface
+}{
+   \sstdescription{
+      This attribute holds the angle between the incoming light and the
+      normal to the grating surface, in radians. The default value is 0.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismEps
+}{
+   The angle between the normal and the dispersion plane
+}{
+   \sstdescription{
+      This attribute holds the angle (in radians) between the normal to
+      the grating or exit prism face, and the dispersion plane. The
+      dispersion plane is the plane spanned by the incoming and outgoing
+      ray. The default value is 0.0.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismG
+}{
+   The grating ruling density
+}{
+   \sstdescription{
+      This attribute holds the number of grating rulings per unit length.
+      The unit of length used should be consistent with the units used
+      for attributes \htmlref{GrismWaveR}{GrismWaveR} and \htmlref{GrismNRP}{GrismNRP}. The default value is 0.0.
+      (the appropriate value for a pure prism disperser with no grating).
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismM
+}{
+   The interference order
+}{
+   \sstdescription{
+      This attribute holds the interference order being considered.
+      The default value is 0.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismNR
+}{
+   The refractive index at the reference wavelength
+}{
+   \sstdescription{
+      This attribute holds refractive index of the grism material at the
+      reference wavelength (given by attribute \htmlref{GrismWaveR}{GrismWaveR}). The default
+      value is 1.0.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismNRP
+}{
+   The rate of change of refractive index with wavelength
+}{
+   \sstdescription{
+      This attribute holds the rate of change of the refractive index of the
+      grism material with respect to wavelength at the reference wavelength
+      (given by attribute \htmlref{GrismWaveR}{GrismWaveR}). The default value is 0.0 (the
+      appropriate value for a pure grating disperser with no prism). The
+      units of this attribute should be consistent with those of attributes
+      GrismWaveR and \htmlref{GrismG}{GrismG}.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismTheta
+}{
+   Angle between normal to detector plane and reference ray
+}{
+   \sstdescription{
+      This attribute gives the angle of incidence of light of the
+      reference wavelength (given by attribute \htmlref{GrismWaveR}{GrismWaveR}) onto the
+      detector. Specifically, it holds the angle (in radians) between
+      the normal to the detector plane and an incident ray at the reference
+      wavelength. The default value is 0.0.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismWaveR
+}{
+   The reference wavelength
+}{
+   \sstdescription{
+      This attribute holds reference wavelength. The default value is
+      5000 (Angstrom). The units of this attribute should be consistent with
+      those of attributes \htmlref{GrismNRP}{GrismNRP} and \htmlref{GrismG}{GrismG}.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ID
+}{
+   Object identification string
+}{
+   \sstdescription{
+      This attribute contains a string which may be used to identify
+      the \htmlref{Object}{Object} to which it is attached. There is no restriction on
+      the contents of this string, which is not used internally by the
+      AST library, and is simply returned without change when
+      required. The default value is an empty string.
+
+      An identification string can be valuable when, for example,
+      several Objects have been stored in a file (using \htmlref{AST\_WRITE}{AST\_WRITE}) and
+      are later retrieved (using \htmlref{AST\_READ}{AST\_READ}). Consistent use of the ID
+      attribute allows the retrieved Objects to be identified without
+      depending simply on the order in which they were stored.
+
+      This attribute may also be useful during debugging, to
+      distinguish similar Objects when using \htmlref{AST\_SHOW}{AST\_SHOW} to display them.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Unlike most other attributes, the value of the ID attribute is
+         not transferred when an Object is copied. Instead, its value is
+         undefined (and therefore defaults to an empty string) in any
+         copy. However, it is retained in any external representation of
+         an Object produced by the AST\_WRITE routine.
+      }
+   }
+}
+\sstroutine{
+   IF
+}{
+   The intermediate frequency in a dual sideband spectrum
+}{
+   \sstdescription{
+      This attribute specifies the (topocentric) intermediate frequency in
+      a dual sideband spectrum. Its sole use is to determine the local
+      oscillator (LO) frequency (the frequency which marks the boundary
+      between the lower and upper sidebands). The LO frequency is
+      equal to the sum of the centre frequency and the intermediate
+      frequency. Here, the \texttt{"} centre frequency\texttt{"}  is the topocentric
+      frequency in Hz corresponding to the current value of the \htmlref{DSBCentre}{DSBCentre}
+      attribute. The value of the IF attribute may be positive or
+      negative: a positive value results in the LO frequency being above
+      the central frequency, whilst a negative IF value results in the LO
+      frequency being below the central frequency. The sign of the IF
+      attribute value determines the default value for the \htmlref{SideBand}{SideBand}
+      attribute.
+
+      When setting a new value for this attribute, the units in which the
+      frequency value is supplied may be indicated by appending a suitable
+      string to the end of the formatted value. If the units are not
+      specified, then the supplied value is assumed to be in units of GHz.
+      For instance, the following strings all result in an IF of 4 GHz being
+      used: \texttt{"} 4.0\texttt{"} , \texttt{"} 4.0 GHz\texttt{"} , \texttt{"} 4.0E9 Hz\texttt{"} , etc.
+
+      When getting the value of this attribute, the returned value is
+      always in units of GHz. The default value for this attribute is 4 GHz.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{DSBSpecFrame}{DSBSpecFrame}
+      }{
+         All DSBSpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Ident
+}{
+   Permanent Object identification string
+}{
+   \sstdescription{
+      This attribute is like the \htmlref{ID}{ID} attribute, in that it contains a
+      string which may be used to identify the \htmlref{Object}{Object} to which it is
+      attached. The only difference between ID and Ident is that Ident
+      is transferred when an Object is copied, but ID is not.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ImagFreq
+}{
+   The image sideband equivalent of the rest frequency
+}{
+   \sstdescription{
+      This is a read-only attribute giving the frequency which
+      corresponds to the rest frequency but is in the opposite sideband.
+
+      The value is calculated by first transforming the rest frequency
+      (given by the \htmlref{RestFreq}{RestFreq} attribute) from the standard of rest of the
+      source (given by the \htmlref{SourceVel}{SourceVel} and \htmlref{SourceVRF}{SourceVRF} attributes) to the
+      standard of rest of the observer (i.e. the topocentric standard of
+      rest). The resulting topocentric frequency is assumed to be in the
+      same sideband as the value given for the \htmlref{DSBCentre}{DSBCentre} attribute (the
+      \texttt{"} observed\texttt{"}  sideband), and is transformed to the other sideband (the
+      \texttt{"} image\texttt{"}  sideband). The new frequency is converted back to the standard
+      of rest of the source, and the resulting value is returned as the
+      attribute value, in units of GHz.
+   }
+   \sstattributetype{
+      Floating point, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{DSBSpecFrame}{DSBSpecFrame}
+      }{
+         All DSBSpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Indent
+}{
+   Specifies the indentation to use in text produced by a Channel
+}{
+   \sstdescription{
+      This attribute controls the indentation within the output text produced by
+      the \htmlref{AST\_WRITE}{AST\_WRITE} function.
+      It gives the increase in the indentation for each level in the object
+      heirarchy. If it is set to zero, no indentation will be used. [3]
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Channel}{Channel}
+      }{
+         The default value is zero for a basic Channel.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         The FitsChan class ignores this attribute.
+      }
+      \sstsubsection{
+         \htmlref{StcsChan}{StcsChan}
+      }{
+         The default value for an StcsChan is zero, which causes the entire
+         STC-S description is written out by a single invocation of the sink
+         function. The text supplied to the sink function will not contain
+         any linefeed characters, and each pair of adjacent words will be
+         separated by a single space. The text may thus be arbitrarily large
+         and the \htmlref{StcsLength}{StcsLength} attribute is ignored.
+
+         If Indent is non-zero, then the text is written out via multiple
+         calls to the sink function, each call corresponding to a single
+         \texttt{"} line\texttt{"}  of text (although no line feed characters will be inserted
+         by AST). The complete STC-S description is broken into lines so that:
+
+         \sstitemlist{
+
+            \sstitem
+            the line length specified by attribute StcsLength is not exceeded
+
+            \sstitem
+            each sub-phrase (time, space, etc.) starts on a new line
+
+            \sstitem
+            each argument in a compound spatial region starts on a new line
+
+         }
+         If this causes a sub-phrase to extend to two or more lines, then the
+         second and subsequent lines will be indented by three spaces compared
+         to the first line. In addition, lines within a compound spatial region
+         will have extra indentation to highlight the nesting produced by the
+         parentheses. Each new level of nesting will be indented by a further
+         three spaces.
+
+         Note, the default value of zero is unlikely to be appropriate when
+         an StcsChan is used within Fortran code. In this case, Indent
+         should usually be set non-zero, and the StcsLength attribute set to
+         the size of the CHARACTER variable used to
+         receive the text returned by \htmlref{AST\_GETLINE}{AST\_GETLINE} within the sink function.
+         This avoids the possibility of long lines being truncated invisibly
+         within AST\_GETLINE.
+      }
+      \sstsubsection{
+         \htmlref{XmlChan}{XmlChan}
+      }{
+         The default value for an XmlChan is zero, which results in no
+         linefeeds or indentation strings being added to output text.
+         If any non-zero value is assigned to Indent, then extra linefeed and
+         space characters will be inserted as necessary to ensure that each
+         XML tag starts on a new line, and each tag will be indented by
+         a further 3 spaces to show its depth in the containment hierarchy.
+      }
+   }
+}
+\sstroutine{
+   InternalUnit(axis)
+}{
+   Physical units for unformated axis values
+}{
+   \sstdescription{
+      This read-only attribute contains a textual representation of the
+      physical units used to represent unformatted (i.e. floating point)
+      values on a particular axis of a \htmlref{Frame}{Frame}, typically handled internally
+      within application code. In most cases, the value of the InternalUnit
+      attribute will be the same as Unit attribute (i.e. formatted and
+      unformatted axis values will normally use the same system of units).
+      The main exception to this is the \htmlref{SkyFrame}{SkyFrame} class, which represents
+      unformatted axis values in radians, regardless of the current
+      setting of the Unit attribute.
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   IntraFlag
+}{
+   IntraMap identification string
+}{
+   \sstdescription{
+      This attribute allows an \htmlref{IntraMap}{IntraMap} to be flagged so that it is
+      distinguishable from other IntraMaps. The transformation routine
+      associated with the IntraMap may then enquire the value of this
+      attribute and adapt the transformation it provides according to the
+      particular IntraMap involved.
+
+      Although this is a string attribute, it may often be useful to store
+      numerical values here, encoded as a character string, and to use these
+      as data within the transformation routine. Note, however, that this
+      mechanism is not suitable for transferring large amounts of data (more
+      than about 1000 characters) to an IntraMap. For that purpose, global
+      variables are recommended, although the IntraFlag value can be used to
+      supplement this approach. The default IntraFlag value is an empty
+      string.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         IntraMap
+      }{
+         All IntraMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A pair of IntraMaps whose transformations may potentially cancel
+         cannot be simplified to produce a \htmlref{UnitMap}{UnitMap} (e.g. using \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY})
+         unless they have the same IntraFlag values. The test for equality is
+         case-sensitive.
+      }
+   }
+}
+\sstroutine{
+   Invert
+}{
+   Mapping inversion flag
+}{
+   \sstdescription{
+      This attribute controls which one of a \htmlref{Mapping}{Mapping}\texttt{'} s two possible
+      coordinate transformations is considered the \texttt{"} forward\texttt{"}
+      transformation (the other being the \texttt{"} inverse\texttt{"}
+      transformation). If the attribute value is zero (the default),
+      the Mapping\texttt{'} s behaviour will be the same as when it was first
+      created. However, if it is non-zero, its two transformations
+      will be inter-changed, so that the Mapping displays the inverse
+      of its original behaviour.
+
+      Inverting the boolean sense of the Invert attribute will cause
+      the values of a Mapping\texttt{'} s \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes to be
+      interchanged. The values of its \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse}
+      attributes will also be interchanged. This operation may be
+      performed with the \htmlref{AST\_INVERT}{AST\_INVERT} routine.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{UnitMap}{UnitMap}
+      }{
+         The value of the Invert attribute has no effect on the
+         behaviour of a UnitMap.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         Inverting the boolean sense of the Invert attribute for a
+         FrameSet will cause its base and current Frames (and its \htmlref{Base}{Base}
+         and \htmlref{Current}{Current} attributes) to be interchanged. This, in turn,
+         may affect other properties and attributes of the FrameSet
+         (such as Nin, Nout, \htmlref{Naxes}{Naxes}, TranForward, TranInverse,
+         etc.). The Invert attribute of a FrameSet is not itself
+         affected by selecting a new base or current \htmlref{Frame}{Frame}.
+      }
+   }
+}
+\sstroutine{
+   Invisible
+}{
+   Draw graphics using invisible ink?
+}{
+   \sstdescription{
+      This attribute controls the appearance of all graphics produced by
+      \htmlref{Plot}{Plot} methods by determining whether graphics should be visible or
+      invisible.
+
+      If the Invisible value of a Plot is non-zero, then all the Plot
+      methods which normally generate graphical output do not do so (you
+      can think of them drawing with \texttt{"} invisible ink\texttt{"} ). Such methods do,
+      however, continue to do all the calculations which would be needed to
+      produce the graphics. In particular, the bounding box enclosing the
+      graphics is still calculated and can be retrieved as normal using
+      \htmlref{AST\_BOUNDINGBOX}{AST\_BOUNDINGBOX}. The default value is zero, resulting in all methods
+      drawing graphics as normal, using visible ink.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+}
+\sstroutine{
+   IsLatAxis(axis)
+}{
+   Is the specified celestial axis a latitude axis?
+}{
+   \sstdescription{
+      This is a read-only boolean attribute that indicates the nature of
+      the specified axis. The attribute has a non-zero value if the
+      specified axis is a celestial latitude axis (Declination, Galactic
+      latitude, etc), and is zero otherwise.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the SkyFrame axis to be tested.
+      }
+   }
+}
+\sstroutine{
+   IsLinear
+}{
+   Is the Mapping linear?
+}{
+   \sstdescription{
+      This attribute indicates whether a \htmlref{Mapping}{Mapping} is an instance of a
+      class that always represents a linear transformation. Note, some
+      Mapping classes can represent linear or non-linear transformations
+      (the \htmlref{MathMap}{MathMap} class for instance). Such classes have a zero value for
+      the IsLinear attribute. Specific instances of such classes can be
+      tested for linearity using the
+      astLinearApprox function.
+      \htmlref{AST\_LINEARAPPROX}{AST\_LINEARAPPROX} routine.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         The IsLinear value for a CmpMap is determined by the classes
+         of the encapsulated Mappings. For instance, a CmpMap that combines
+         a \htmlref{ZoomMap}{ZoomMap} and a \htmlref{ShiftMap}{ShiftMap} will have a non-zero value for its IsLinear
+         attribute, but a CmpMap that contains a MathMap will have a
+         value of zero for its IsLinear attribute.
+      }
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         The IsLinear value for a Frame is 1 (since a Frame is equivalent
+         to a \htmlref{UnitMap}{UnitMap}).
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The IsLinear value for a FrameSet is obtained from the Mapping
+         from the base Frame to the current Frame.
+      }
+   }
+}
+\sstroutine{
+   IsLonAxis(axis)
+}{
+   Is the specified celestial axis a longitude axis?
+}{
+   \sstdescription{
+      This is a read-only boolean attribute that indicates the nature of
+      the specified axis. The attribute has a non-zero value if the
+      specified axis is a celestial longitude axis (Right Ascension, Galactic
+      longitude, etc), and is zero otherwise.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the SkyFrame axis to be tested.
+      }
+   }
+}
+\sstroutine{
+   IsSimple
+}{
+   Has the Mapping been simplified?
+}{
+   \sstdescription{
+      This attribute indicates whether a \htmlref{Mapping}{Mapping} has been simplified
+      by the
+      \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}
+      method. If the IsSimple value is non-zero, then the Mapping has
+      been simplified and so there is nothing to be gained by simplifying
+      it again. Indeed, the
+      AST\_SIMPLIFY
+      method will immediately return the Mapping unchanged if the IsSimple
+      attribute indicates that the Mapping has already been simplified.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         All classes of Frame return zero for the IsSimple attribute.
+         This is because changes can be made to a Frame which affect the
+         Mapping represented by the Frame, and so there can be no
+         guarantee that the Mapping may not need re-simplifying. Most
+         non-Frame Mappings, on the other hand, are immutable and so when
+         they are simplified it is certain that they weill remain in a
+         simple state.
+      }
+   }
+}
+\sstroutine{
+   IterInverse
+}{
+   Provide an iterative inverse transformation?
+}{
+   \sstdescription{
+      This attribute indicates whether the inverse transformation of
+      the \htmlref{PolyMap}{PolyMap} should be implemented via an iterative Newton-Raphson
+      approximation that uses the forward transformation to transform
+      candidate input positions until an output position is found which
+      is close to the required output position. By default, an iterative
+      inverse is provided if, and only if, no inverse polynomial was supplied
+      when the PolyMap was constructed.
+
+      The \htmlref{NiterInverse}{NiterInverse} and \htmlref{TolInverse}{TolInverse} attributes provide parameters that
+      control the behaviour of the inverse approximation method.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         PolyMap
+      }{
+         All PolyMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An iterative inverse can only be used if the PolyMap has equal
+         numbers of inputs and outputs, as given by the \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout}
+         attributes. An error will be reported if IterInverse is set non-zero
+         for a PolyMap that does not meet this requirement.
+      }
+   }
+}
+\sstroutine{
+   Iwc
+}{
+   Include a Frame representing FITS-WCS intermediate world coordinates?
+}{
+   \sstdescription{
+      This attribute is a boolean value which is used when a \htmlref{FrameSet}{FrameSet} is
+      read from a \htmlref{FitsChan}{FitsChan} with a foreign FITS encoding (e.g. FITS-WCS) using
+      \htmlref{AST\_READ}{AST\_READ}.
+      If it has a non-zero value then the returned FrameSet will include
+      Frames representing \texttt{"} intermediate world coordinates\texttt{"}  (IWC). These
+      Frames will have \htmlref{Domain}{Domain} name \texttt{"} IWC\texttt{"}  for primary axis descriptions, and
+      \texttt{"} IWCa\texttt{"}  for secondary axis descriptions, where \texttt{"} a\texttt{"}  is replaced by
+      the single alternate axis description character, as used in the
+      FITS-WCS header. The default value for \texttt{"} Iwc\texttt{"}  is zero.
+
+      FITS-WCS paper 1 defines IWC as a Cartesian coordinate system with one
+      axis for each WCS axis, and is the coordinate system produced by the
+      rotation matrix (represented by FITS keyword PCi\_j, CDi\_j, etc).
+      For instance, for a 2-D FITS-WCS header describing projected
+      celestial longitude and latitude, the intermediate world
+      coordinates represent offsets in degrees from the reference point
+      within the plane of projection.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   KeyCase
+}{
+   Are keys case sensitive?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how keys are
+      used. If KeyCase is zero, then key strings supplied to any method
+      are automatically converted to upper case before being used. If
+      KeyCase is non-zero (the default), then supplied key strings are
+      used without modification.
+
+      The value of this attribute can only be changed if the \htmlref{KeyMap}{KeyMap} is
+      empty. Its value can be set conveniently when creating the KeyMap.
+      An error will be reported if an attempt is made to change the
+      attribute value when the KeyMap contains any entries.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         All KeyMaps have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         The Table class over-rides this attribute by forcing it to zero.
+         That is, keys within a Table are always case insensitive.
+      }
+   }
+}
+\sstroutine{
+   KeyError
+}{
+   Report an error when getting the value of a non-existant KeyMap entry?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how the
+      AST\_MAPGET...
+      functions behave if the requested key is not found in the \htmlref{KeyMap}{KeyMap}.
+      If KeyError is zero (the default), then these functions will return
+      .FALSE.
+      but no error will be reported. If KeyError is non-zero, then the
+      same values are returned but an error is also reported.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         All KeyMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When setting a new value for KeyError, the supplied value is
+         propagated to any KeyMaps contained within the supplied KeyMap.
+
+         \sstitem
+         When clearing the KeyError attribute, the attribute is also
+         cleared in any KeyMaps contained within the supplied KeyMap.
+      }
+   }
+}
+\sstroutine{
+   LTOffset
+}{
+   The offset from UTC to Local Time, in hours
+}{
+   \sstdescription{
+      This specifies the offset from UTC to Local Time, in hours (fractional
+      hours can be supplied). It is positive for time zones east of Greenwich.
+      AST uses the figure as given, without making any attempt to correct for
+      daylight saving. The default value is zero.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         All TimeFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Label(axis)
+}{
+   Axis label
+}{
+   \sstdescription{
+      This attribute specifies a label to be attached to each axis of
+      a \htmlref{Frame}{Frame} when it is represented (e.g.) in graphical output.
+
+      If a Label value has not been set for a Frame axis, then a
+      suitable default is supplied.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default supplied by the Frame class is the string \texttt{"} \htmlref{Axis}{Axis}
+         $<$n$>$\texttt{"} , where $<$n$>$ is 1, 2, etc. for each successive axis.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Label value
+         (e.g. to \texttt{"} Right ascension\texttt{"}  or \texttt{"} Galactic latitude\texttt{"} ) as
+         appropriate for the particular celestial coordinate system
+         being represented.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class re-defines the default Label value as
+         appropriate for the particular time system being represented.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Label attribute of a FrameSet axis is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Axis labels are intended purely for interpretation by human
+         readers and not by software.
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   LabelAt(axis)
+}{
+   Where to place numerical labels for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      where numerical axis labels and associated tick marks are
+      placed.  It takes a separate value for each physical axis of a
+      \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} LabelAt(2)=10.0\texttt{"}
+      specifies where the numerical labels and tick marks for the
+      second axis should be drawn.
+
+      For each axis, the LabelAt value gives the value on the other
+      axis at which numerical labels and tick marks should be placed
+      (remember that Plots suitable for use with AST\_GRID may only
+      have two axes).  For example, in a celestial (RA,Dec) coordinate
+      system, LabelAt(1) gives a Dec value which defines a line (of
+      constant Dec) along which the numerical RA labels and their
+      associated tick marks will be drawn. Similarly, LabelAt(2) gives
+      the RA value at which the Dec labels and ticks will be drawn.
+
+      The default bahaviour is for the Plot to generate its own
+      position for numerical labels and tick marks.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The LabelAt value should use the same units as are used
+         internally for storing coordinate values on the appropriate
+         axis. For example, with a celestial coordinate system, the
+         LabelAt value should be in radians, not hours or degrees.
+
+         \sstitem
+         Normally, the LabelAt value also determines where the lines
+         representing coordinate axes will be drawn, so that the tick
+         marks will lie on these lines (but also see the DrawAxes
+         attribute).
+
+         \sstitem
+         In some circumstances, numerical labels and tick marks are
+         drawn around the edges of the plotting area (see the \htmlref{Labelling}{Labelling}
+         attribute).  In this case, the value of the LabelAt attribute is
+         ignored.
+      }
+   }
+}
+\sstroutine{
+   LabelUnits(axis)
+}{
+   Use axis unit descriptions in a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether the descriptive labels drawn for each axis of a \htmlref{Plot}{Plot}
+      should include a description of the units being used on the
+      axis.  It takes a separate value for each physical axis of a
+      Plot so that, for instance, the setting \texttt{"} LabelUnits(2)=1\texttt{"}
+      specifies that a unit description should be included in the
+      label for the second axis.
+
+      If the LabelUnits value of a Plot axis is non-zero, a unit
+      description will be included in the descriptive label for that
+      axis, otherwise it will be omitted.  The default behaviour is to
+      include a unit description unless the current \htmlref{Frame}{Frame} of the Plot
+      is a \htmlref{SkyFrame}{SkyFrame} representing equatorial, ecliptic, galactic or
+      supergalactic coordinates, in which case it is omitted.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The text used for the unit description is obtained from the
+         Plot\texttt{'} s \htmlref{Unit(axis)}{Unit(axis)} attribute.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LabelUnits\texttt{"}  instead of
+         \texttt{"} LabelUnits(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the LabelUnits(1) value.
+
+         \sstitem
+         If the current Frame of the Plot is not a SkyFrame, but includes
+         axes which were extracted from a SkyFrame, then the default behaviour
+         is to include a unit description only for those axes which were not
+         extracted from a SkyFrame.
+      }
+   }
+}
+\sstroutine{
+   LabelUp(axis)
+}{
+   Draw numerical Plot labels upright?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether the numerical labels for each axis of a \htmlref{Plot}{Plot} should be
+      drawn upright or not. It takes a separate value for each
+      physical axis of a Plot so that, for instance, the setting
+      \texttt{"} LabelUp(2)=1\texttt{"}  specifies that numerical labels for the second
+      axis should be drawn upright.
+
+      If the LabelUp value of a Plot axis is non-zero, it causes
+      numerical labels for that axis to be plotted upright (i.e. as
+      normal, horizontal text), otherwise labels are drawn parallel to
+      the axis to which they apply.
+
+      The default is to produce upright labels if the labels are placed
+      around the edge of the plot, and to produce labels that follow the
+      axes if the labels are placed within the interior of the plot (see
+      attribute \htmlref{Labelling}{Labelling}).
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         In some circumstances, numerical labels and tick marks are
+         drawn around the edges of the plotting area (see the Labelling
+         attribute).  In this case, the value of the LabelUp attribute is
+         ignored.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LabelUp\texttt{"}  instead of
+         \texttt{"} LabelUp(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the
+         attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the LabelUp(1) value.
+      }
+   }
+}
+\sstroutine{
+   Labelling
+}{
+   Label and tick placement option for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      the strategy for placing numerical labels and tick marks for a \htmlref{Plot}{Plot}.
+
+      If the Labelling value of a Plot is \texttt{"} exterior\texttt{"}  (the default), then
+      numerical labels and their associated tick marks are placed
+      around the edges of the plotting area, if possible. If this is
+      not possible, or if the Labelling value is \texttt{"} interior\texttt{"} , then they
+      are placed along grid lines inside the plotting area.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The \htmlref{LabelAt(axis)}{LabelAt(axis)} attribute may be used to determine the exact
+         placement of labels and tick marks that are drawn inside the
+         plotting area.
+      }
+   }
+}
+\sstroutine{
+   LatAxis
+}{
+   Index of the latitude axis
+}{
+   \sstdescription{
+      This read-only attribute gives the index (1 or 2) of the latitude
+      axis within the \htmlref{SkyFrame}{SkyFrame} (taking into account any current axis
+      permutations).
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ListSize
+}{
+   Number of points in a PointList
+}{
+   \sstdescription{
+      This is a read-only attribute giving the number of points in a
+      \htmlref{PointList}{PointList}. This value is determined when the PointList is created.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         PointList
+      }{
+         All PointLists have this attribute.
+      }
+   }
+}
+\sstroutine{
+   LogGap(axis)
+}{
+   Interval between major axis values of a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      the logarithmic interval between the \texttt{"} major\texttt{"}  axis values of a \htmlref{Plot}{Plot}, at
+      which (for example) major tick marks are drawn. It takes a separate
+      value for each physical axis of the Plot so that, for instance,
+      the setting \texttt{"} LogGap(2)=100.0\texttt{"}  specifies the ratio between adjacent major
+      values along the second axis. The LogGap attribute is only used when
+      the LogTicks attribute indicates that the spacing between major axis
+      values is to be logarithmic. If major axis values are linearly spaced
+      then the gap is specified using attribute Gap.
+
+      The LogGap value supplied will be rounded to the nearest power of 10.
+      The reciprocal of the supplied value may be used if this is necessary
+      to produce usable major axis values. If a zero or negative value is
+      supplied, an error will be reported when the grid is drawn. The default
+      behaviour is for the Plot to generate its own LogGap value when
+      required, based on the range of axis values to be represented.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The LogGap value is a ratio between axis values and is therefore
+         dimensionless.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LogGap\texttt{"}  instead of \texttt{"} LogGap(2)\texttt{"} ),
+         then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the attribute
+         value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}  operation
+         will use just the LogGap(1) value.
+      }
+   }
+}
+\sstroutine{
+   LogLabel(axis)
+}{
+   Use exponential format for numerical axis labels?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether the numerical axis labels should be in normal decimal form
+      or should be represented as 10 raised to the appropriate power.
+      That is, an axis value of 1000.0 will be drawn as \texttt{"} 1000.0\texttt{"}  if
+      LogLabel is zero, but as \texttt{"} 10$\wedge$3\texttt{"}  if LogLabel is non-zero. If
+      graphical escape sequences are supported (see attribute \htmlref{Escape}{Escape}),
+      the power in such exponential labels will be drawn as a small
+      superscript instead of using a \texttt{"} $\wedge$\texttt{"}  character to represent
+      exponentiation.
+
+      The default is to produce exponential labels if the major tick
+      marks are logarithmically spaced (see the LogTicks attribute).
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Plot}{Plot}
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LogLabel\texttt{"}  instead of
+         \texttt{"} LogLabel(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the
+         attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the LogLabel(1) value.
+      }
+   }
+}
+\sstroutine{
+   LogPlot(axis)
+}{
+   Map the plot logarithmically onto the screen?
+}{
+   \sstdescription{
+      This attribute controls the appearance of all graphics produced by
+      the \htmlref{Plot}{Plot}, by determining whether the axes of the plotting surface
+      are mapped logarithmically or linearly onto the base \htmlref{Frame}{Frame} of the
+      \htmlref{FrameSet}{FrameSet} supplied when the Plot was constructed. It takes a separate
+      value for each axis of the graphics coordinate system (i.e. the
+      base Frame in the Plot) so that, for instance, the setting
+      \texttt{"} LogPlot(2)=1\texttt{"}  specifies that the second axis of the graphics
+      coordinate system (usually the vertical axis) should be mapped
+      logarithmically onto the second axis of the base Frame of the
+      FrameSet supplied when the Plot was constructed.
+
+      If the LogPlot value of a Plot axis is non-zero, it causes that
+      axis to be mapped logarithmically, otherwise (the default) the axis
+      is mapped linearly.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The setting of the LogPlot attribute provides the default value
+         for the related LogTicks attribute. By selecting suitable values for
+         LogPlot and LogTicks, it is possible to have tick marks which are evenly
+         spaced in value but which are mapped logarithmically onto the screen
+         (and vice-versa).
+
+         \sstitem
+         An axis may only be mapped logarithmically if the visible part of
+         the axis does not include the value zero. The visible part of the
+         axis is that part which is mapped onto the plotting area, and is
+         measured within the base Frame of the FrameSet which was supplied when
+         the Plot was constructed. Any attempt to set LogPlot to a non-zero value
+         will be ignored (without error) if the visible part of the axis
+         includes the value zero
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LogPlot\texttt{"}  instead of
+         \texttt{"} LogPlot(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the
+         attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the LogPlot(1) value.
+      }
+   }
+}
+\sstroutine{
+   LogTicks(axis)
+}{
+   Space the major tick marks logarithmically?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether the major tick marks should be spaced logarithmically or
+      linearly in axis value. It takes a separate value for each physical
+      axis of the \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} LogTicks(2)=1\texttt{"}
+      specifies that the major tick marks on the second axis should be
+      spaced logarithmically.
+
+      If the LogTicks value for a physical axis is non-zero, the major
+      tick marks on that axis will be spaced logarithmically (that is,
+      there will be a constant ratio between the axis values at adjacent
+      major tick marks). An error will be reported if the dynamic range of
+      the axis (the ratio of the largest to smallest displayed axis value)
+      is less than 10.0. If the LogTicks value is zero, the major tick marks
+      will be evenly spaced (that is, there will be a constant difference
+      between the axis values at adjacent major tick marks). The default is
+      to produce logarithmically spaced tick marks if the corresponding
+      LogPlot attribute is non-zero and the ratio of maximum axis value
+      to minimum axis value is 100 or more. If either of these conditions
+      is not met, the default is to produce linearly spaced tick marks.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The setting of the LogTicks attribute does not affect the mapping
+         of the plot onto the screen, which is controlled by attribute LogPlot.
+         By selecting suitable values for LogPlot and LogTicks, it is possible to
+         have tick marks which are evenly spaced in value but which are mapped
+         logarithmically onto the screen (and vica-versa).
+
+         \sstitem
+         An error will be reported when drawing an annotated axis grid if
+         the visible part of the physical axis includes the value zero.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LogTicks\texttt{"}  instead of
+         \texttt{"} LogTicks(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the
+         attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the LogTicks(1) value.
+      }
+   }
+}
+\sstroutine{
+   LonAxis
+}{
+   Index of the longitude axis
+}{
+   \sstdescription{
+      This read-only attribute gives the index (1 or 2) of the longitude
+      axis within the \htmlref{SkyFrame}{SkyFrame} (taking into account any current axis
+      permutations).
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   LutEpsilon
+}{
+   The relative error of the values held in the took-up table
+}{
+   \sstdescription{
+      This attribute holds the relative error of the values held in the
+      took-up table. It is used when simplifying a \htmlref{LutMap}{LutMap}, to determine
+      if the LutMap should be considered linear. Setting a larger value
+      makes it more likely that a LutMap will be replaced by a \htmlref{WinMap}{WinMap}
+      (i.e. a linear \htmlref{Mapping}{Mapping}) when simplified.
+
+      The default value is the value of the system constant DBL\_EPSILON
+      (typically around 1e-16 or 2E-16). If the values in the look-up
+      table were derived from single precision data, it may be appropriate
+      to set this attribute to a value around 1E-7.
+
+      Note, the value of this attribute may changed only if the LutMap
+      has no more than one reference. That is, an error is reported if the
+      LutMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         LutMap
+      }{
+         All LutMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   LutInterp
+}{
+   Look-up table interpolation method
+}{
+   \sstdescription{
+      This attribute indicates the method to be used when finding the
+      output value of a \htmlref{LutMap}{LutMap} for an input value part way between two
+      table entries. If it is set to 0 (the default) then linear
+      interpolation is used. Otherwise, nearest neighbour interpolation
+      is used.
+
+      Using nearest neighbour interpolation causes AST\_\_BAD to be returned
+      for any point which falls outside the bounds of the table. Linear
+      interpolation results in an extrapolated value being returned based
+      on the two end entries in the table.
+
+      Note, the value of this attribute may changed only if the LutMap
+      has no more than one reference. That is, an error is reported if the
+      LutMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         LutMap
+      }{
+         All LutMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   MajTickLen(axis)
+}{
+   Length of major tick marks for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      the length of the major tick marks drawn on the axes of a \htmlref{Plot}{Plot}.
+      It takes a separate value for each physical axis of the Plot so
+      that, for instance, the setting \texttt{"} MajTickLen(2)=0\texttt{"}  specifies the
+      length of the major tick marks drawn on the second axis.
+
+      The MajTickLen value should be given as a fraction of the
+      minimum dimension of the plotting area. Negative values cause
+      major tick marks to be placed on the outside of the
+      corresponding grid line or axis (but subject to any clipping
+      imposed by the underlying graphics system), while positive
+      values cause them to be placed on the inside.
+
+      The default behaviour depends on whether a coordinate grid is
+      drawn inside the plotting area (see the \htmlref{Grid}{Grid} attribute). If so,
+      the default MajTickLen value is zero (so that major ticks are
+      not drawn), otherwise the default is $+$0.015.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} MajTickLen\texttt{"}  instead of
+         \texttt{"} MajTickLen(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the MajTickLen(1) value.
+      }
+   }
+}
+\sstroutine{
+   MapLocked
+}{
+   Prevent new entries being added to a KeyMap?
+}{
+   \sstdescription{
+      If this boolean attribute is set to
+      .TRUE.,
+      an error will be reported if an attempt is made to add a new entry
+      to the \htmlref{KeyMap}{KeyMap}. Note, the value associated with any existing entries
+      can still be changed, but no new entries can be stored in the KeyMap.
+      The default value
+      (.FALSE.)
+      allows new entries to be added to the KeyMap.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         All KeyMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When setting a new value for MapLocked, the supplied value is
+         propagated to any KeyMaps contained within the supplied KeyMap.
+
+         \sstitem
+         When clearing the MapLocked attribute, the attribute is also
+         cleared in any KeyMaps contained within the supplied KeyMap.
+      }
+   }
+}
+\sstroutine{
+   MatchEnd
+}{
+   Match trailing axes?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how a \htmlref{Frame}{Frame}
+      behaves when it is used (by \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME}) as a template to match
+      another (target) Frame. It applies only in the case where a
+      match occurs between template and target Frames with different
+      numbers of axes.
+
+      If the MatchEnd value of the template Frame is zero, then the
+      axes which occur first in the target Frame will be matched and
+      any trailing axes (in either the target or template) will be
+      disregarded. If it is non-zero, the final axes in each Frame
+      will be matched and any un-matched leading axes will be
+      disregarded instead.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default MatchEnd value for a Frame is zero, so that
+         trailing axes are disregarded.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The MatchEnd attribute of a FrameSet is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+}
+\sstroutine{
+   MaxAxes
+}{
+   Maximum number of Frame axes to match
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{Frame}{Frame} behaves when it is used (by
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME}) as a template to match another (target) Frame. It
+      specifies the maximum number of axes that the target Frame may
+      have in order to match the template.
+
+      Normally, this value will equal the number of Frame axes, so
+      that a template Frame will only match another Frame with the
+      same number of axes as itself. By setting a different value,
+      however, the matching process may be used to identify Frames
+      with specified numbers of axes.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default MaxAxes value for a Frame is equal to the number
+         of Frame axes (\htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The MaxAxes attribute of a CmpFrame defaults to a large number
+         (1000000) which is much larger than any likely number of axes in
+         a Frame. Combined with the \htmlref{MinAxes}{MinAxes} default of zero (for a
+         CmpFrame), this means that the default behaviour for a CmpFrame
+         is to match any target Frame that consists of a subset of the
+         axes in the template CmpFrame. To change this so that a CmpFrame
+         will only match Frames that have the same number of axes, you
+         should set the CmpFrame MaxAxes and MinAxes attributes to the
+         number of axes in the CmpFrame.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The MaxAxes attribute of a FrameSet is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When setting a MaxAxes value, the value of the MinAxes
+         attribute may also be silently changed so that it remains
+         consistent with (i.e. does not exceed) the new value. The
+         default MaxAxes value may also be reduced to remain consistent
+         with the MinAxes value.
+
+         \sstitem
+         If a template Frame is used to match a target with a different
+         number of axes, the \htmlref{MatchEnd}{MatchEnd} attribute of the template is used
+         to determine how the individual axes of each Frame should match.
+      }
+   }
+}
+\sstroutine{
+   MeshSize
+}{
+   Number of points used to represent the boundary of a Region
+}{
+   \sstdescription{
+      This attribute controls how many points are used when creating a
+      mesh of points covering the boundary or volume of a \htmlref{Region}{Region}. Such a
+      mesh is returned by the
+      \htmlref{AST\_GETREGIONMESH}{AST\_GETREGIONMESH}
+      method. The boundary mesh is also used when testing for overlap
+      between two Regions: each point in the bomdary mesh of the first
+      Region is checked to see if it is inside or outside the second Region.
+      Thus, the reliability of the overlap check depends on the value assigned
+      to this attribute. If the value used is very low, it is possible for
+      overlaps to go unnoticed. High values produce more reliable results, but
+      can result in the overlap test being very slow. The default value is 200
+      for two dimensional Regions and 2000 for three or more dimensional
+      Regions (this attribute is not used for 1-dimensional regions since the
+      boundary of a simple 1-d Region can only ever have two points). A
+      value of five is used if the supplied value is less than five.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpRegion}{CmpRegion}
+      }{
+         The default MeshSize for a CmpRegion is the MeshSize of its
+         first component Region.
+      }
+      \sstsubsection{
+         \htmlref{Stc}{Stc}
+      }{
+         The default MeshSize for an Stc is the MeshSize of its
+         encapsulated Region.
+      }
+   }
+}
+\sstroutine{
+   MinAxes
+}{
+   Minimum number of Frame axes to match
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{Frame}{Frame} behaves when it is used (by
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME}) as a template to match another (target) Frame. It
+      specifies the minimum number of axes that the target Frame may
+      have in order to match the template.
+
+      Normally, this value will equal the number of Frame axes, so
+      that a template Frame will only match another Frame with the
+      same number of axes as itself. By setting a different value,
+      however, the matching process may be used to identify Frames
+      with specified numbers of axes.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default MinAxes value for a Frame is equal to the number
+         of Frame axes (\htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The MinAxes attribute of a CmpFrame defaults to zero. Combined
+         with the \htmlref{MaxAxes}{MaxAxes} default of 1000000 (for a CmpFrame), this means
+         that the default behaviour for a CmpFrame is to match any target
+         Frame that consists of a subset of the axes in the template
+         CmpFrame. To change this so that a CmpFrame will only match Frames
+         that have the same number of axes, you should set the CmpFrame
+         MinAxes and MaxAxes attributes to the number of axes in the CmpFrame.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The MinAxes attribute of a FrameSet is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When setting a MinAxes value, the value of the MaxAxes
+         attribute may also be silently changed so that it remains
+         consistent with (i.e. is not less than) the new value. The
+         default MinAxes value may also be reduced to remain consistent
+         with the MaxAxes value.
+
+         \sstitem
+         If a template Frame is used to match a target with a different
+         number of axes, the \htmlref{MatchEnd}{MatchEnd} attribute of the template is used
+         to determine how the individual axes of each Frame should match.
+      }
+   }
+}
+\sstroutine{
+   MinTick(axis)
+}{
+   Density of minor tick marks for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      the density of minor tick marks which appear between the major
+      axis values of a \htmlref{Plot}{Plot}. It takes a separate value for each
+      physical axis of a Plot so that, for instance, the setting
+      \texttt{"} MinTick(2)=2\texttt{"}  specifies the density of minor tick marks along
+      the second axis.
+
+      The value supplied should be the number of minor divisions
+      required between each pair of major axis values, this being one
+      more than the number of minor tick marks to be drawn.  By
+      default, a value is chosen that depends on the gap between major
+      axis values and the nature of the axis.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} MinTick\texttt{"}  instead of
+         \texttt{"} MinTick(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the MinTick(1) value.
+      }
+   }
+}
+\sstroutine{
+   MinTickLen(axis)
+}{
+   Length of minor tick marks for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      the length of the minor tick marks drawn on the axes of a \htmlref{Plot}{Plot}.
+      It takes a separate value for each physical axis of the Plot so
+      that, for instance, the setting \texttt{"} MinTickLen(2)=0\texttt{"}  specifies the
+      length of the minor tick marks drawn on the second axis.
+
+      The MinTickLen value should be given as a fraction of the
+      minimum dimension of the plotting area. Negative values cause
+      minor tick marks to be placed on the outside of the
+      corresponding grid line or axis (but subject to any clipping
+      imposed by the underlying graphics system), while positive
+      values cause them to be placed on the inside.
+
+      The default value is $+$0.007.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The number of minor tick marks drawn is determined by the
+         Plot\texttt{'} s \htmlref{MinTick(axis)}{MinTick(axis)} attribute.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} MinTickLen\texttt{"}  instead of
+         \texttt{"} MinTickLen(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the MinTickLen(1) value.
+      }
+   }
+}
+\sstroutine{
+   NatLat
+}{
+   Native latitude of the reference point of a FITS-WCS projection
+}{
+   \sstdescription{
+      This attribute gives the latitude of the reference point of the
+      FITS-WCS projection implemented by a \htmlref{WcsMap}{WcsMap}. The value is in
+      radians in the \texttt{"} native spherical\texttt{"}  coordinate system. This value is
+      fixed for most projections, for instance it is PI/2 (90 degrees)
+      for all zenithal projections. For some projections (e.g. the conics)
+      the value is not fixed, but is specified by parameter one on the
+      latitude axis.
+
+      FITS-WCS paper II introduces the concept of a \texttt{"} fiducial point\texttt{"}
+      which is logical distinct from the projection reference point.
+      It is easy to confuse the use of these two points. The fiducial
+      point is the point which has celestial coordinates given by the
+      CRVAL FITS keywords. The native spherical coordinates for this point
+      default to the values of the NatLat and \htmlref{NatLon}{NatLon}, but these defaults
+      mey be over-ridden by values stored in the PVi\_j keywords. Put
+      another way, the CRVAL keywords will by default give the celestial
+      coordinates of the projection reference point, but may refer to
+      some other point if alternative native longitude and latitude values
+      are provided through the PVi\_j keywords.
+
+      The NatLat attribute is read-only.
+   }
+   \sstattributetype{
+      Floating point, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A default value of AST\_\_BAD is used if no latitude value is available.
+      }
+   }
+}
+\sstroutine{
+   NatLon
+}{
+   Native longitude of the reference point of a FITS-WCS projection
+}{
+   \sstdescription{
+      This attribute gives the longitude of the reference point of the
+      FITS-WCS projection implemented by a \htmlref{WcsMap}{WcsMap}. The value is in
+      radians in the \texttt{"} native spherical\texttt{"}  coordinate system, and will
+      usually be zero. See the description of attribute \htmlref{NatLat}{NatLat} for further
+      information.
+
+      The NatLon attribute is read-only.
+   }
+   \sstattributetype{
+      Floating point, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Naxes
+}{
+   Number of Frame axes
+}{
+   \sstdescription{
+      This is a read-only attribute giving the number of axes in a
+      \htmlref{Frame}{Frame} (i.e. the number of dimensions of the coordinate space
+      which the Frame describes). This value is determined when the
+      Frame is created.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Naxes attribute of a FrameSet is the same as that of its
+         current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The Naxes attribute of a CmpFrame is equal to the sum of the
+         Naxes values of its two component Frames.
+      }
+   }
+}
+\sstroutine{
+   Ncard
+}{
+   Number of FITS header cards in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the total number of FITS header cards
+      stored in a \htmlref{FitsChan}{FitsChan}. It is updated as cards are added or
+      deleted.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Ncolumn
+}{
+   The number of columns in the table
+}{
+   \sstdescription{
+      This attribute holds the number of columns currently in the table. Columns
+      are added and removed using the
+      \htmlref{AST\_ADDCOLUMN}{AST\_ADDCOLUMN} and \htmlref{AST\_REMOVECOLUMN}{AST\_REMOVECOLUMN}
+      functions.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   NegLon
+}{
+   Display negative longitude values?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how longitude values
+      are normalized for display by \htmlref{AST\_NORM}{AST\_NORM}.
+
+      If the NegLon attribute is zero, then normalized
+      longitude values will be in the range zero to 2.pi. If NegLon is
+      non-zero, then normalized longitude values will be in the range -pi
+      to pi.
+
+      The default value depends on the current value of the \htmlref{SkyRefIs}{SkyRefIs}
+      attribute, If SkyRefIs has a value of \texttt{"} Origin\texttt{"} , then the default for
+      NegLon is one, otherwise the default is zero.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Negated
+}{
+   Region negation flag
+}{
+   \sstdescription{
+      This attribute controls whether a \htmlref{Region}{Region} represents the \texttt{"} inside\texttt{"}  or
+      the \texttt{"} outside\texttt{"}  of the area which was supplied when the Region was
+      created. If the attribute value is zero (the default), the Region
+      represents the inside of the original area. However, if it is non-zero,
+      it represents the outside of the original area. The value of this
+      attribute may be toggled using the
+      \htmlref{AST\_NEGATE}{AST\_NEGATE} routine.
+
+      Note, whether the boundary is considered to be inside the Region or
+      not is controlled by the \htmlref{Closed}{Closed} attribute. Changing the value of
+      the Negated attribute does not change the value of the Closed attribute.
+      Thus, if Region is closed, then the boundary of the Region will be
+      inside the Region, whatever the setting of the Negated attribute.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Nframe
+}{
+   Number of Frames in a FrameSet
+}{
+   \sstdescription{
+      This attribute gives the number of Frames in a \htmlref{FrameSet}{FrameSet}. This
+      value will change as Frames are added or removed, but will
+      always be at least one.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FrameSet
+      }{
+         All FrameSets have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Nin
+}{
+   Number of input coordinates for a Mapping
+}{
+   \sstdescription{
+      This attribute gives the number of coordinate values required to
+      specify an input point for a \htmlref{Mapping}{Mapping} (i.e. the number of
+      dimensions of the space in which the Mapping\texttt{'} s input points
+      reside).
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         If a CmpMap\texttt{'} s component Mappings are joined in series, then
+         its Nin attribute is equal to the Nin attribute of the first
+         component (or to the \htmlref{Nout}{Nout} attribute of the second component
+         if the the CmpMap\texttt{'} s \htmlref{Invert}{Invert} attribute is non-zero).
+
+         If a CmpMap\texttt{'} s component Mappings are joined in parallel, then
+         its Nin attribute is given by the sum of the Nin attributes
+         of each component (or to the sum of their Nout attributes if
+         the CmpMap\texttt{'} s Invert attribute is non-zero).
+      }
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         The Nin attribute for a Frame is always equal to the number
+         of Frame axes (\htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Nin attribute of a FrameSet is equal to the number of
+         axes (Naxes attribute) of its base Frame (as specified by the
+         FrameSet\texttt{'} s \htmlref{Base}{Base} attribute). The Nin attribute value may
+         therefore change if a new base Frame is selected.
+      }
+   }
+}
+\sstroutine{
+   NiterInverse
+}{
+   Maximum number of iterations for the iterative inverse transformation
+}{
+   \sstdescription{
+      This attribute controls the iterative inverse transformation
+      used if the \htmlref{IterInverse}{IterInverse} attribute is non-zero.
+
+      Its value gives the maximum number of iterations of the
+      Newton-Raphson algorithm to be used for each transformed position.
+      The default value is 4. See also attribute \htmlref{TolInverse}{TolInverse}.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{PolyMap}{PolyMap}
+      }{
+         All PolyMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Nkey
+}{
+   Number of unique FITS keywords in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the total number of unique FITS keywords
+      stored in a \htmlref{FitsChan}{FitsChan}. It is updated as cards are added or
+      deleted. If no keyword occurrs more than once in the FitsChan, the
+      \htmlref{Ncard}{Ncard} and Nkey attributes will be equal. If any keyword occurrs
+      more than once, the Nkey attribute value will be smaller than
+      the Ncard attribute value.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Nobject
+}{
+   Number of Objects in class
+}{
+   \sstdescription{
+      This attribute gives the total number of Objects currently in
+      existence in the same class as the \htmlref{Object}{Object} whose attribute value
+      is requested. This count does not include Objects which belong
+      to derived (more specialised) classes.
+
+      This attribute is mainly intended for debugging. It can be used
+      to detect whether Objects which should have been deleted have,
+      in fact, been deleted.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Norm(axis)
+}{
+   Specifies the plane upon which a Plot3D draws text and markers
+}{
+   \sstdescription{
+      This attribute controls the appearance of text and markers drawn
+      by a \htmlref{Plot3D}{Plot3D}. It specifies the orientation of the plane upon which
+      text and markers will be drawn by all subsequent invocations of the
+      \htmlref{AST\_TEXT}{AST\_TEXT} and \htmlref{AST\_MARK}{AST\_MARK} functions.
+
+      When setting or getting the Norm attribute, the attribute name must
+      be qualified by an axis index in the range 1 to 3. The 3 elements of
+      the Norm attribute are together interpreted as a vector in 3D graphics
+      coordinates that is normal to the plane upon which text and marks
+      should be drawn. When testing or clearing the attribute, the axis
+      index is optional. If no index is supplied, then clearing the Norm
+      attribute will clear all three elements, and testing the Norm attribute
+      will return a non-zero value if any of the three elements are set.
+
+      The default value is 1.0 for each of the 3 elements. The length of
+      the vector is insignificant, but an error will be reported when
+      attempting to draw text or markers if the vector has zero length.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Plot}{Plot}
+      }{
+         All Plot3Ds have this attribute.
+      }
+   }
+}
+\sstroutine{
+   NormUnit(axis)
+}{
+   Normalised physical units for formatted axis values
+}{
+   \sstdescription{
+      The value of this read-only attribute is derived from the current
+      value of the Unit attribute. It will represent an equivalent system
+      of units to the Unit attribute, but will potentially be simplified.
+      For instance, if Unit is set to \texttt{"} s$*$(m/s)\texttt{"} , the NormUnit value will
+      be \texttt{"} m\texttt{"} . If no simplification can be performed, the value of the
+      NormUnit attribute will equal that of the Unit attribute.
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         All Frames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   Nout
+}{
+   Number of output coordinates for a Mapping
+}{
+   \sstdescription{
+      This attribute gives the number of coordinate values generated
+      by a \htmlref{Mapping}{Mapping} to specify each output point (i.e. the number of
+      dimensions of the space in which the Mapping\texttt{'} s output points
+      reside).
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         If a CmpMap\texttt{'} s component Mappings are joined in series, then
+         its Nout attribute is equal to the Nout attribute of the
+         second component (or to the \htmlref{Nin}{Nin} attribute of the first
+         component if the the CmpMap\texttt{'} s \htmlref{Invert}{Invert} attribute is non-zero).
+
+         If a CmpMap\texttt{'} s component Mappings are joined in parallel, then
+         its Nout attribute is given by the sum of the Nout attributes
+         of each component (or to the sum of their Nin attributes if
+         the CmpMap\texttt{'} s Invert attribute is non-zero).
+      }
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         The Nout attribute for a Frame is always equal to the number
+         of Frame axes (\htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Nout attribute of a FrameSet is equal to the number of
+         FrameSet axes (Naxes attribute) which, in turn, is equal to
+         the Naxes attribute of the FrameSet\texttt{'} s current Frame (as
+         specified by the \htmlref{Current}{Current} attribute). The Nout attribute value
+         may therefore change if a new current Frame is selected.
+      }
+   }
+}
+\sstroutine{
+   Nparameter
+}{
+   The number of global parameters in the table
+}{
+   \sstdescription{
+      This attribute holds the number of global parameters currently in the table.
+      Parameters are added and removed using the
+      \htmlref{AST\_ADDPARAMETER}{AST\_ADDPARAMETER} and \htmlref{AST\_REMOVEPARAMETER}{AST\_REMOVEPARAMETER}
+      functions.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Nrow
+}{
+   The number of rows in the table
+}{
+   \sstdescription{
+      This attribute holds the index of the last row to which any
+      contents have been added using any of the
+      astMapPut...
+      AST\_MAPPUT...
+      functions. The first row has index 1.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   NumLab(axis)
+}{
+   Draw numerical axis labels for a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether labels should be drawn to represent the numerical values
+      along each axis of a \htmlref{Plot}{Plot}. It takes a separate value for each
+      physical axis of a Plot so that, for instance, the setting
+      \texttt{"} NumLab(2)=1\texttt{"}  specifies that numerical labels should be drawn
+      for the second axis.
+
+      If the NumLab value of a Plot axis is non-zero (the default),
+      then numerical labels will be drawn for that axis, otherwise
+      they will be omitted.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The drawing of associated descriptive axis labels for a Plot
+         (describing the quantity being plotted along each axis) is
+         controlled by the \htmlref{TextLab(axis)}{TextLab(axis)} attribute.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} NumLab\texttt{"}  instead of
+         \texttt{"} NumLab(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the
+         attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the NumLab(1) value.
+      }
+   }
+}
+\sstroutine{
+   NumLabGap(axis)
+}{
+   Spacing of numerical labels for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      where numerical axis labels are placed relative to the axes they
+      describe.  It takes a separate value for each physical axis of a
+      \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} NumLabGap(2)=-0.01\texttt{"}
+      specifies where the numerical label for the second axis should
+      be drawn.
+
+      For each axis, the NumLabGap value gives the spacing between the
+      axis line (or edge of the plotting area, if appropriate) and the
+      nearest edge of the corresponding numerical axis
+      labels. Positive values cause the descriptive label to be placed
+      on the opposite side of the line to the default tick marks,
+      while negative values cause it to be placed on the same side.
+
+      The NumLabGap value should be given as a fraction of the minimum
+      dimension of the plotting area, the default value being $+$0.01.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} NumLabGap\texttt{"}  instead of
+         \texttt{"} NumLabGap(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the NumLabGap(1) value.
+      }
+   }
+}
+\sstroutine{
+   ObjSize
+}{
+   The in-memory size of the Object
+}{
+   \sstdescription{
+      This attribute gives the total number of bytes of memory used by
+      the \htmlref{Object}{Object}. This includes any Objects which are encapsulated within
+      the supplied Object.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ObsAlt
+}{
+   The geodetic altitude of the observer
+}{
+   \sstdescription{
+      This attribute specifies the geodetic altitude of the observer, in
+      metres, relative to the IAU 1976 reference ellipsoid. The basic \htmlref{Frame}{Frame}
+      class makes no use of this attribute, but specialised subclasses of
+      Frame may use it. For instance, the \htmlref{SpecFrame}{SpecFrame}, \htmlref{SkyFrame}{SkyFrame} and \htmlref{TimeFrame}{TimeFrame}
+      classes use it. The default value is zero.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+      \sstsubsection{
+         SpecFrame
+      }{
+         Together with the \htmlref{ObsLon}{ObsLon}, \htmlref{Epoch}{Epoch}, \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec} attributes,
+         it defines the Doppler shift introduced by the observers diurnal
+         motion around the earths axis, which is needed when converting to
+         or from the topocentric standard of rest. The maximum velocity
+         error which can be caused by an incorrect value is 0.5 km/s. The
+         default value for the attribute is zero.
+      }
+      \sstsubsection{
+         TimeFrame
+      }{
+         Together with the ObsLon attribute, it is used when converting
+         between certain time scales (TDB, TCB, LMST, LAST)
+      }
+   }
+}
+\sstroutine{
+   ObsLat
+}{
+   The geodetic latitude of the observer
+}{
+   \sstdescription{
+      This attribute specifies the geodetic latitude of the observer, in
+      degrees, relative to the IAU 1976 reference ellipsoid. The basic \htmlref{Frame}{Frame}
+      class makes no use of this attribute, but specialised subclasses of
+      Frame may use it. For instance, the \htmlref{SpecFrame}{SpecFrame}, \htmlref{SkyFrame}{SkyFrame} and \htmlref{TimeFrame}{TimeFrame}
+      classes use it. The default value is zero.
+
+      The value is stored internally in radians, but is converted to and
+      from a degrees string for access. Some example input formats are:
+      \texttt{"} 22:19:23.2\texttt{"} , \texttt{"} 22 19 23.2\texttt{"} , \texttt{"} 22:19.387\texttt{"} , \texttt{"} 22.32311\texttt{"} , \texttt{"} N22.32311\texttt{"} ,
+      \texttt{"} -45.6\texttt{"} , \texttt{"} S45.6\texttt{"} . As indicated, the sign of the latitude can
+      optionally be indicated using characters \texttt{"} N\texttt{"}  and \texttt{"} S\texttt{"}  in place of the
+      usual \texttt{"} $+$\texttt{"}  and \texttt{"} -\texttt{"} . When converting the stored value to a string, the
+      format \texttt{"} [s]dd:mm:ss.ss\texttt{"}  is used, when \texttt{"} [s]\texttt{"}  is \texttt{"} N\texttt{"}  or \texttt{"} S\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+      \sstsubsection{
+         SpecFrame
+      }{
+         Together with the \htmlref{ObsLon}{ObsLon}, \htmlref{Epoch}{Epoch}, \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec} attributes,
+         it defines the Doppler shift introduced by the observers diurnal
+         motion around the earths axis, which is needed when converting to
+         or from the topocentric standard of rest. The maximum velocity
+         error which can be caused by an incorrect value is 0.5 km/s. The
+         default value for the attribute is zero.
+      }
+      \sstsubsection{
+         TimeFrame
+      }{
+         Together with the ObsLon attribute, it is used when converting
+         between certain time scales (TDB, TCB, LMST, LAST)
+      }
+   }
+}
+\sstroutine{
+   ObsLon
+}{
+   The geodetic longitude of the observer
+}{
+   \sstdescription{
+      This attribute specifies the geodetic (or equivalently, geocentric)
+      longitude of the observer, in degrees, measured positive eastwards.
+      See also attribute \htmlref{ObsLat}{ObsLat}. The basic \htmlref{Frame}{Frame} class makes no use of this
+      attribute, but specialised subclasses of Frame may use it. For instance,
+      the \htmlref{SpecFrame}{SpecFrame}, \htmlref{SkyFrame}{SkyFrame} and \htmlref{TimeFrame}{TimeFrame} classes use it. The default value
+      is zero.
+
+      The value is stored internally in radians, but is converted to and
+      from a degrees string for access. Some example input formats are:
+      \texttt{"} 155:19:23.2\texttt{"} , \texttt{"} 155 19 23.2\texttt{"} , \texttt{"} 155:19.387\texttt{"} , \texttt{"} 155.32311\texttt{"} , \texttt{"} E155.32311\texttt{"} ,
+      \texttt{"} -204.67689\texttt{"} , \texttt{"} W204.67689\texttt{"} . As indicated, the sign of the longitude can
+      optionally be indicated using characters \texttt{"} E\texttt{"}  and \texttt{"} W\texttt{"}  in place of the
+      usual \texttt{"} $+$\texttt{"}  and \texttt{"} -\texttt{"} . When converting the stored value to a string, the
+      format \texttt{"} [s]ddd:mm:ss.ss\texttt{"}  is used, when \texttt{"} [s]\texttt{"}  is \texttt{"} E\texttt{"}  or \texttt{"} W\texttt{"}  and the
+      numerical value is chosen to be less than 180 degrees.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+      \sstsubsection{
+         SpecFrame
+      }{
+         Together with the ObsLon, \htmlref{Epoch}{Epoch}, \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec} attributes,
+         it defines the Doppler shift introduced by the observers diurnal
+         motion around the earths axis, which is needed when converting to
+         or from the topocentric standard of rest. The maximum velocity
+         error which can be caused by an incorrect value is 0.5 km/s. The
+         default value for the attribute is zero.
+      }
+      \sstsubsection{
+         TimeFrame
+      }{
+         Together with the ObsLon attribute, it is used when converting
+         between certain time scales (TDB, TCB, LMST, LAST)
+      }
+   }
+}
+\sstroutine{
+   PVMax(i)
+}{
+   Maximum number of FITS-WCS projection parameters
+}{
+   \sstdescription{
+      This attribute specifies the largest legal index for a PV projection
+      parameter attached to a specified axis of the \htmlref{WcsMap}{WcsMap} (i.e. the
+      largest legal value for \texttt{"} m\texttt{"}  when accessing the \texttt{"} \htmlref{PVi\_m}{PVi\_m}\texttt{"}  attribute).
+      The axis index is specified by i, and should be in the range 1 to 99.
+      The value for each axis is determined by the projection type specified
+      when the WcsMap
+      is first created using \htmlref{AST\_WCSMAP}{AST\_WCSMAP} and cannot subsequently be
+      changed.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   PVi\_m
+}{
+   FITS-WCS projection parameters
+}{
+   \sstdescription{
+      This attribute specifies the projection parameter values to be
+      used by a \htmlref{WcsMap}{WcsMap} when implementing a FITS-WCS sky projection.
+      Each PV attribute name should include two integers, i and m,
+      separated by an underscore. The axis index is specified
+      by i, and should be in the range 1 to 99. The parameter number
+      is specified by m, and should be in the range 0 to 99. For
+      example, \texttt{"} PV2\_1=45.0\texttt{"}  would specify a value for projection
+      parameter 1 of axis 2 in a WcsMap.
+
+      These projection parameters correspond exactly to the values
+      stored using the FITS-WCS keywords \texttt{"} PV1\_1\texttt{"} , \texttt{"} PV1\_2\texttt{"} , etc. This
+      means that projection parameters which correspond to angles must
+      be given in degrees (despite the fact that the angular
+      coordinates and other attributes used by a WcsMap are in
+      radians).
+
+      The set of projection parameters used by a WcsMap depends on the
+      type of projection, which is determined by its \htmlref{WcsType}{WcsType}
+      parameter.  Most projections either do not require projection
+      parameters, or use parameters 1 and 2 associated with the latitude
+      axis. You should consult the FITS-WCS paper for details.
+
+      Some projection parameters have default values (as defined in
+      the FITS-WCS paper) which apply if no explicit value is given.
+      You may omit setting a value for these \texttt{"} optional\texttt{"}  parameters and the
+      default will apply. Some projection parameters, however, have no
+      default and a value must be explicitly supplied.  This is most
+      conveniently
+      done using the OPTIONS argument of \htmlref{AST\_WCSMAP}{AST\_WCSMAP} (q.v.) when a WcsMap
+      is first created. An error will result when a WcsMap is used to
+      transform coordinates if any of its required projection
+      parameters has not been set and lacks a default value.
+
+      A \texttt{"} get\texttt{"}  operation for a parameter which has not been assigned a value
+      will return the default value defined in the FITS-WCS paper, or
+      AST\_\_BAD if the paper indicates that the parameter has no default.
+      A default value of zero is returned for parameters which are not
+      accessed by the projection.
+
+      Note, the FITS-WCS paper reserves parameters 1 and 2 on the longitude
+      axis to hold the native longitude and latitude of the fiducial
+      point of the projection, in degrees. The default values for these
+      parameters are determined by the projection type. The AST-specific
+      TPN projection does not use this convention - all projection
+      parameters for both axes are used to represent polynomical correction
+      terms, and the native longitude and latitude at the fiducial point may
+      not be changed from the default values of zero and 90 degrees.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The value of this attribute may changed only if the WcsMap
+         has no more than one reference. That is, an error is reported if the
+         WcsMap has been cloned, either by including it within another object
+         such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+         \htmlref{AST\_CLONE}{AST\_CLONE}
+         function.
+
+         \sstitem
+         If the projection parameter values given for a WcsMap do not
+         satisfy all the required constraints (as defined in the FITS-WCS
+         paper), then an error will result when the WcsMap is used to
+         transform coordinates.
+      }
+   }
+}
+\sstroutine{
+   PcdCen(axis)
+}{
+   Centre coordinates of pincushion/barrel distortion
+}{
+   \sstdescription{
+      This attribute specifies the centre of the pincushion/barrel
+      distortion implemented by a \htmlref{PcdMap}{PcdMap}. It takes a separate value for
+      each axis of the PcdMap so that, for instance, the settings
+      \texttt{"} PcdCen(1)=345.0,PcdCen(2)=-104.4\texttt{"}  specify that the pincushion
+      distortion is centred at positions of 345.0 and -104.4 on axes 1 and 2
+      respectively. This attribute is set when a PcdMap is created, but may
+      later be modified. If the attribute is cleared, the default value for
+      both axes is zero.
+
+      Note, the value of this attribute may changed only if the PcdMap
+      has no more than one reference. That is, an error is reported if the
+      PcdMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         PcdMap
+      }{
+         All PcdMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} PcdCen\texttt{"}  instead of
+         \texttt{"} PcdCen(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of both axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the PcdCen(1) value.
+      }
+   }
+}
+\sstroutine{
+   Permute
+}{
+   Permute axis order?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how a \htmlref{Frame}{Frame}
+      behaves when it is used (by \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME}) as a template to match
+      another (target) Frame. It specifies whether the axis order of
+      the target Frame may be permuted in order to obtain a match.
+
+      If the template\texttt{'} s Permute value is zero, it will match a target
+      only if it can do so without changing the order of its
+      axes. Otherwise, it will attempt to permute the target\texttt{'} s axes as
+      necessary.
+
+      The default value is 1, so that axis permutation will be attempted.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute. However, the Frame class
+         effectively ignores this attribute and behaves as if it has
+         the value 1. This is because the axes of a basic Frame are
+         not distinguishable and will always match any other Frame
+         whatever their order.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         Unlike a basic Frame, the SkyFrame class makes use of this
+         attribute.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Permute attribute of a FrameSet is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+}
+\sstroutine{
+   PolarLong
+}{
+   The longitude value to assign to either pole
+}{
+   \sstdescription{
+      This attribute holds the longitude value, in radians, to be
+      returned when a Cartesian position corresponding to either the north
+      or south pole is transformed into spherical coordinates. The
+      default value is zero.
+
+      Note, the value of this attribute may changed only if the \htmlref{SphMap}{SphMap}
+      has no more than one reference. That is, an error is reported if the
+      SphMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SphMap
+      }{
+         All SphMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   PolyTan
+}{
+   Use PVi\_m keywords to define distorted TAN projection?
+}{
+   \sstdescription{
+      This attribute is a boolean value which specifies how FITS \texttt{"} TAN\texttt{"}
+      projections should be treated when reading a \htmlref{FrameSet}{FrameSet} from a foreign
+      encoded FITS header. If zero, the projection is assumed to conform
+      to the published FITS-WCS standard. If positive, the convention
+      for a distorted TAN projection included in an early draft version
+      of FITS-WCS paper II are assumed. In this convention the
+      coefficients of a polynomial distortion to be applied to
+      intermediate world coordinates are specified by the \htmlref{PVi\_m}{PVi\_m} keywords.
+      This convention was removed from the paper before publication and so
+      does not form part of the standard. Indeed, it is incompatible with
+      the published standard because it re-defines the meaning of the
+      first five PVi\_m keywords on the longitude axis, which are reserved
+      by the published standard for other purposes. However, headers that
+      use this convention are still to be found, for instance the SCAMP
+      utility (http://www.astromatic.net/software/scamp) creates them.
+
+      The default value for the PolyTan attribute is -1. A negative
+      values causes the used convention to depend on the contents
+      of the \htmlref{FitsChan}{FitsChan}. If the FitsChan contains any PVi\_m keywords for
+      the latitude axis, or if it contains PVi\_m keywords for the
+      longitude axis with \texttt{"} m\texttt{"}  greater than 4, then the distorted TAN
+      convention is used. Otherwise, the standard convention is used.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   PreserveAxes
+}{
+   Preserve axes?
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{Frame}{Frame} behaves when it is used (by
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME}) as a template to match another (target) Frame. It
+      determines which axes appear (and in what order) in the \texttt{"} result\texttt{"}
+      Frame produced.
+
+      If PreserveAxes is zero in the template Frame, then the result
+      Frame will have the same number (and order) of axes as the
+      template. If it is non-zero, however, the axes of the target
+      Frame will be preserved, so that the result Frame will have the
+      same number (and order) of axes as the target.
+
+      The default value is zero, so that target axes are not preserved
+      and the result Frame resembles the template.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The PreserveAxes attribute of a FrameSet is the same as that
+         of its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+}
+\sstroutine{
+   ProjP(m)
+}{
+   FITS-WCS projection parameters
+}{
+   \sstdescription{
+      This attribute provides aliases for the PV attributes, which
+      specifies the projection parameter values to be used by a \htmlref{WcsMap}{WcsMap}
+      when implementing a FITS-WCS sky projection. ProjP is retained for
+      compatibility with previous versions of FITS-WCS and AST. New
+      applications should use the PV attibute instead.
+
+      Attributes ProjP(0) to ProjP(9) correspond to attributes PV$<$axlat$>$\_0
+      to PV$<$axlat$>$\_9, where $<$axlat$>$ is replaced by the index of the
+      latitude axis (given by attribute WcsAxis(2)). See PV for further
+      details.
+
+      Note, the value of this attribute may changed only if the WcsMap
+      has no more than one reference. That is, an error is reported if the
+      WcsMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Projection
+}{
+   Sky projection description
+}{
+   \sstdescription{
+      This attribute provides a place to store a description of the
+      type of sky projection used when a \htmlref{SkyFrame}{SkyFrame} is attached to a
+      2-dimensional object, such as an image or plotting surface. For
+      example, typical values might be \texttt{"} orthographic\texttt{"} , \texttt{"} Hammer-Aitoff\texttt{"}
+      or \texttt{"} cylindrical equal area\texttt{"} .
+
+      The Projection value is purely descriptive and does not affect
+      the celestial coordinate system represented by the SkyFrame in
+      any way. If it is set to a non-blank string, the description
+      provided may be used when forming the default value for the
+      SkyFrame\texttt{'} s \htmlref{Title}{Title} attribute (so that typically it will appear in
+      graphical output, for instance). The default value is an empty
+      string.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   RefCount
+}{
+   Count of active Object pointers
+}{
+   \sstdescription{
+      This attribute gives the number of active pointers associated
+      with an \htmlref{Object}{Object}. It is modified whenever pointers are created or
+      annulled (by \htmlref{AST\_CLONE}{AST\_CLONE}, \htmlref{AST\_ANNUL}{AST\_ANNUL} or \htmlref{AST\_END}{AST\_END} for example). The count
+      includes the initial pointer issued when the Object was created.
+
+      If the reference count for an Object falls to zero as the result
+      of annulling a pointer to it, then the Object will be deleted.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   RefDec
+}{
+   The declination of the reference point
+}{
+   \sstdescription{
+      This attribute specifies the FK5 J2000.0 declination of a reference
+      point on the sky. See the description of attribute \htmlref{RefRA}{RefRA} for details.
+      The default RefDec is \texttt{"} 0:0:0\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   RefRA
+}{
+   The right ascension of the reference point
+}{
+   \sstdescription{
+      This attribute, together with the \htmlref{RefDec}{RefDec} attribute, specifies the FK5
+      J2000.0 coordinates of a reference point on the sky. For 1-dimensional
+      spectra, this should normally be the position of the source. For
+      spectral data with spatial coverage (spectral cubes, etc), this should
+      be close to centre of the spatial coverage. It is used to define the
+      correction for Doppler shift to be applied when using the
+      \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} or \htmlref{AST\_CONVERT}{AST\_CONVERT}
+      method to convert between different standards of rest.
+
+      The \htmlref{SpecFrame}{SpecFrame} class assumes this velocity correction is spatially
+      invariant. If a single SpecFrame is used (for instance, as a
+      component of a \htmlref{CmpFrame}{CmpFrame}) to describe spectral values at different
+      points on the sky, then it is assumes that the doppler shift at any
+      spatial position is the same as at the reference position. The
+      maximum velocity error introduced by this assumption is of the order
+      of V$*$SIN(FOV), where FOV is the angular field of view, and V is the
+      relative velocity of the two standards of rest. As an example, when
+      correcting from the observers rest frame (i.e. the topocentric rest
+      frame) to the kinematic local standard of rest the maximum value of V
+      is about 20 km/s, so for 5 arc-minute field of view the maximum velocity
+      error introduced by the correction will be about 0.03 km/s. As another
+      example, the maximum error when correcting from the observers rest frame
+      to the local group is about 5 km/s over a 1 degree field of view.
+
+      The RefRA and RefDec attributes are stored internally in radians, but
+      are converted to and from a string for access. The format \texttt{"} hh:mm:ss.ss\texttt{"}
+      is used for RefRA, and \texttt{"} dd:mm:ss.s\texttt{"}  is used for RefDec. The methods
+      \htmlref{AST\_SETREFPOS}{AST\_SETREFPOS} and \htmlref{AST\_GETREFPOS}{AST\_GETREFPOS} may be used to access the value of
+      these attributes directly as unformatted values in radians.
+
+      The default for RefRA is \texttt{"} 0:0:0\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SpecFrame
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   RegionClass
+}{
+   The AST class name of the Region encapsulated within an Stc
+}{
+   \sstdescription{
+      This is a read-only attribute giving the AST class name of the
+      \htmlref{Region}{Region} encapsulated within an \htmlref{Stc}{Stc} (that is, the class of the Region
+      which was supplied when the Stc was created).
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Stc
+      }{
+         All Stc objects this attribute.
+      }
+   }
+}
+\sstroutine{
+   Report
+}{
+   Report transformed coordinates?
+}{
+   \sstdescription{
+      This attribute controls whether coordinate values are reported
+      whenever a \htmlref{Mapping}{Mapping} is used to transform a set of points. If its
+      value is zero (the default), no report is made. However, if it
+      is non-zero, the coordinates of each point are reported (both
+      before and after transformation) by writing them to standard
+      output.
+
+      This attribute is provided as an aid to debugging, and to avoid
+      having to report values explicitly in simple programs.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         When applied to a compound Mapping (CmpMap), only the Report
+         attribute of the CmpMap, and not those of its component
+         Mappings, is used.  Coordinate information is never reported
+         for the component Mappings individually, only for the
+         complete CmpMap.
+      }
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         When applied to any Frame, the formatting capabilities of the
+         Frame (as provided by the \htmlref{AST\_FORMAT}{AST\_FORMAT} function) will be used to
+         format the reported coordinates.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         When applied to any FrameSet, the formatting capabilities of
+         the base and current Frames will be used (as above) to
+         individually format the input and output coordinates, as
+         appropriate. The Report attribute of a FrameSet is not itself
+         affected by selecting a new base or current Frame, but the
+         resulting formatting capabilities may be.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Unlike most other attributes, the value of the Report
+         attribute is not transferred when a Mapping is copied. Instead,
+         its value is undefined (and therefore defaults to zero) in any
+         copy. Similarly, it becomes undefined in any external
+         representation of a Mapping produced by the \htmlref{AST\_WRITE}{AST\_WRITE} routine.
+      }
+   }
+}
+\sstroutine{
+   ReportLevel
+}{
+   Determines which read/write conditions are reported
+}{
+   \sstdescription{
+      This attribute determines which, if any, of the conditions that occur
+      whilst reading or writing an \htmlref{Object}{Object} should be reported. These
+      conditions will generate either a fatal error or a warning, as
+      determined by attribute \htmlref{Strict}{Strict}. ReportLevel can take any of the
+      following values:
+
+      0 - Do not report any conditions.
+
+      1 - \htmlref{Report}{Report} only conditions where significant information content has been
+      changed. For instance, an unsupported time-scale has been replaced by a
+      supported near-equivalent time-scale. Another example is if a basic
+      \htmlref{Channel}{Channel} unexpected encounters data items that may have been introduced
+      by later versions of AST.
+
+      2 - Report the above, and in addition report significant default
+      values. For instance, if no time-scale was specified when reading an
+      Object from an external data source, report the default time-scale
+      that is being used.
+
+      3 - Report the above, and in addition report any other potentially
+      interesting conditions that have no significant effect on the
+      conversion. For instance, report if a time-scale of \texttt{"} TT\texttt{"}
+      (terrestrial time) is used in place of \texttt{"} ET\texttt{"}  (ephemeris time). This
+      change has no signficiant effect because ET is the predecessor of,
+      and is continuous with, TT. Synonyms such as \texttt{"} IAT\texttt{"}  and \texttt{"} TAI\texttt{"}  are
+      another example.
+
+      The default value is 1. Note, there are many other conditions that
+      can occur whilst reading or writing an Object that completely
+      prevent the conversion taking place. Such conditions will always
+      generate errors, irrespective of the ReportLevel and Strict attributes.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         All Channels have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         All the conditions selected by the FitsChan \htmlref{Warnings}{Warnings} attribute are
+         reported at level 1.
+      }
+   }
+}
+\sstroutine{
+   RestFreq
+}{
+   The rest frequency
+}{
+   \sstdescription{
+      This attribute specifies the frequency corresponding to zero
+      velocity. It is used when converting between between velocity-based
+      coordinate systems and and other coordinate systems (such as frequency,
+      wavelength, energy, etc). The default value is 1.0E5 GHz.
+
+      When setting a new value for this attribute, the new value can be
+      supplied either directly as a frequency, or indirectly as a wavelength
+      or energy, in which case the supplied value is converted to a frequency
+      before being stored. The nature of the supplied value is indicated by
+      appending text to the end of the numerical value indicating the units in
+      which the value is supplied. If the units are not specified, then the
+      supplied value is assumed to be a frequency in units of GHz. If the
+      supplied unit is a unit of frequency, the supplied value is assumed to
+      be a frequency in the given units. If the supplied unit is a unit of
+      length, the supplied value is assumed to be a (vacuum) wavelength. If
+      the supplied unit is a unit of energy, the supplied value is assumed to
+      be an energy. For instance, the following strings all result in
+      a rest frequency of around 1.4E14 Hz being used: \texttt{"} 1.4E5\texttt{"} , \texttt{"} 1.4E14 Hz\texttt{"} ,
+      \texttt{"} 1.4E14 s$*$$*$-1\texttt{"} , \texttt{"} 1.4E5 GHz\texttt{"} , \texttt{"} 2.14E-6 m\texttt{"} , \texttt{"} 21400 Angstrom\texttt{"} , \texttt{"} 9.28E-20 J\texttt{"} ,
+      \texttt{"} 9.28E-13 erg\texttt{"} , \texttt{"} 0.58 eV\texttt{"} , etc.
+
+      When getting the value of this attribute, the returned value is
+      always a frequency in units of GHz.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   RootCorner
+}{
+   Specifies which edges of the 3D box should be annotated
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      which edges of the cube enclosing the 3D graphics space are used
+      for displaying numerical and descriptive axis labels. The attribute
+      value identifies one of the eight corners of the cube within
+      which graphics are being drawn (i.e. the cube specified by the
+      GRAPHBOX argument when \htmlref{AST\_PLOT3D}{AST\_PLOT3D}
+      was called tp create the \htmlref{Plot3D}{Plot3D}). \htmlref{Axis}{Axis} labels and tick marks will
+      be placed on the three cube edges that meet at the given corner.
+
+      The attribute value should consist of three character, each of
+      which must be either \texttt{"} U\texttt{"}  or \texttt{"} L\texttt{"} . The first character in the string
+      specifies the position of the corner on the first graphics axis.
+      If the character is \texttt{"} U\texttt{"}  then the corner is at the upper bound on the
+      first graphics axis. If it is \texttt{"} L\texttt{"} , then the corner is at the lower
+      bound on the first axis. Likewise, the second and third characters
+      in the string specify the location of the corner on the second and
+      third graphics axes.
+
+      For instance, corner \texttt{"} LLL\texttt{"}  is the corner that is at the lower bound
+      on all three graphics axes, and corner \texttt{"} ULU\texttt{"}  is at the upper bound
+      on axes 1 and 3 but at the lower bound on axis 2.
+
+      The default value is \texttt{"} LLL\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot3D
+      }{
+         All Plot3Ds have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Seed
+}{
+   Random number seed for a MathMap
+}{
+   \sstdescription{
+      This attribute, which may take any integer value, determines the
+      sequence of random numbers produced by the random number functions in
+      \htmlref{MathMap}{MathMap} expressions. It is set to an unpredictable default value when
+      a MathMap is created, so that by default each MathMap uses a different
+      set of random numbers.
+
+      If required, you may set this Seed attribute to a value of your
+      choosing in order to produce repeatable behaviour from the random
+      number functions. You may also enquire the Seed value (e.g. if an
+      initially unpredictable value has been used) and then use it to
+      reproduce the resulting sequence of random numbers, either from the
+      same MathMap or from another one.
+
+      Clearing the Seed attribute gives it a new unpredictable default
+      value.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         MathMap
+      }{
+         All MathMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SideBand
+}{
+   Indicates which sideband a dual sideband spectrum represents
+}{
+   \sstdescription{
+      This attribute indicates whether the \htmlref{DSBSpecFrame}{DSBSpecFrame} currently
+      represents its lower or upper sideband, or an offset from the local
+      oscillator frequency. When querying the current value, the returned
+      string is always one of \texttt{"} usb\texttt{"}  (for upper sideband), \texttt{"} lsb\texttt{"}  (for lower
+      sideband), or \texttt{"} lo\texttt{"}  (for offset from the local oscillator frequency).
+      When setting a new value, any of the strings \texttt{"} lsb\texttt{"} , \texttt{"} usb\texttt{"} , \texttt{"} observed\texttt{"} ,
+      \texttt{"} image\texttt{"}  or \texttt{"} lo\texttt{"}  may be supplied (case insensitive). The \texttt{"} observed\texttt{"}
+      sideband is which ever sideband (upper or lower) contains the central
+      spectral position given by attribute \htmlref{DSBCentre}{DSBCentre}, and the \texttt{"} image\texttt{"}
+      sideband is the other sideband. It is the sign of the \htmlref{IF}{IF} attribute
+      which determines if the observed sideband is the upper or lower
+      sideband. The default value for SideBand is the observed sideband.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         DSBSpecFrame
+      }{
+         All DSBSpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SimpFI
+}{
+   Forward-inverse MathMap pairs simplify?
+}{
+   \sstdescription{
+      This attribute should be set to a non-zero value if applying a
+      \htmlref{MathMap}{MathMap}\texttt{'} s forward transformation, followed immediately by the matching
+      inverse transformation will always restore the original set of
+      coordinates. It indicates that AST may replace such a sequence of
+      operations by an identity \htmlref{Mapping}{Mapping} (a \htmlref{UnitMap}{UnitMap}) if it is encountered
+      while simplifying a compound Mapping (e.g. using \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}).
+
+      By default, the SimpFI attribute is zero, so that AST will not perform
+      this simplification unless you have set SimpFI to indicate that it is
+      safe to do so.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         MathMap
+      }{
+         All MathMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For simplification to occur, the two MathMaps must be in series and
+         be identical (with textually identical transformation
+         functions). Functional equivalence is not sufficient.
+
+         \sstitem
+         The consent of both MathMaps is required before simplification can
+         take place. If either has a SimpFI value of zero, then simplification
+         will not occur.
+
+         \sstitem
+         The SimpFI attribute controls simplification only in the case where
+         a MathMap\texttt{'} s forward transformation is followed by the matching inverse
+         transformation. It does not apply if an inverse transformation is
+         followed by a forward transformation. This latter case is controlled
+         by the \htmlref{SimpIF}{SimpIF} attribute.
+
+         \sstitem
+         The \texttt{"} forward\texttt{"}  and \texttt{"} inverse\texttt{"}  transformations referred to are those
+         defined when the MathMap is created (corresponding to the FWD and
+         INV arguments of its constructor function). If the MathMap is
+         inverted (i.e. its \htmlref{Invert}{Invert} attribute is non-zero), then the role of the
+         SimpFI and SimpIF attributes will be interchanged.
+      }
+   }
+}
+\sstroutine{
+   SimpIF
+}{
+   Inverse-forward MathMap pairs simplify?
+}{
+   \sstdescription{
+      This attribute should be set to a non-zero value if applying a
+      \htmlref{MathMap}{MathMap}\texttt{'} s inverse transformation, followed immediately by the matching
+      forward transformation will always restore the original set of
+      coordinates. It indicates that AST may replace such a sequence of
+      operations by an identity \htmlref{Mapping}{Mapping} (a \htmlref{UnitMap}{UnitMap}) if it is encountered
+      while simplifying a compound Mapping (e.g. using \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}).
+
+      By default, the SimpIF attribute is zero, so that AST will not perform
+      this simplification unless you have set SimpIF to indicate that it is
+      safe to do so.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         MathMap
+      }{
+         All MathMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For simplification to occur, the two MathMaps must be in series and
+         be identical (with textually identical transformation
+         functions). Functional equivalence is not sufficient.
+
+         \sstitem
+         The consent of both MathMaps is required before simplification can
+         take place. If either has a SimpIF value of zero, then simplification
+         will not occur.
+
+         \sstitem
+         The SimpIF attribute controls simplification only in the case where
+         a MathMap\texttt{'} s inverse transformation is followed by the matching forward
+         transformation. It does not apply if a forward transformation is
+         followed by an inverse transformation. This latter case is controlled
+         by the \htmlref{SimpFI}{SimpFI} attribute.
+
+         \sstitem
+         The \texttt{"} forward\texttt{"}  and \texttt{"} inverse\texttt{"}  transformations referred to are those
+         defined when the MathMap is created (corresponding to the FWD and
+         INV arguments of its constructor function). If the MathMap is
+         inverted (i.e. its \htmlref{Invert}{Invert} attribute is non-zero), then the role of the
+         SimpFI and SimpIF attributes will be interchanged.
+      }
+   }
+}
+\sstroutine{
+   SimpVertices
+}{
+   Simplify a Polygon by transforming its vertices?
+}{
+   \sstdescription{
+      This attribute controls the behaviour of the
+      \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}
+      method when applied to a \htmlref{Polygon}{Polygon}. The simplified Polygon is created
+      by transforming the vertices from the \htmlref{Frame}{Frame} in which the Polygon
+      was originally defined into the Frame currently represented by the
+      Polygon. If SimpVertices is non-zero (the default) then this
+      simplified Polygon is returned without further checks. If SimpVertices
+      is zero, a check is made that the edges of the new Polygon do not
+      depart significantly from the edges of the original Polygon (as
+      determined by the uncertainty associated with the Polygon). This
+      could occur, for instance, if the \htmlref{Mapping}{Mapping} frrm the original to the
+      current Frame is highly non-linear. If this check fails, the
+      original unsimplified Polygon is returned without change.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Polygon
+      }{
+         All Polygons have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SinkFile
+}{
+   Output file to which to data should be written
+}{
+   \sstdescription{
+      This attribute specifies the name of a file to which the \htmlref{Channel}{Channel}
+      should write data. If specified it is used in preference to any sink
+      function specified when the Channel was created.
+
+      Assigning a new value to this attribute will cause any previously
+      opened SinkFile to be closed. The first subsequent call to
+      \htmlref{AST\_WRITE}{AST\_WRITE}
+      will attempt to open the new file (an error will be reported if the
+      file cannot be opened), and write data to it. All subsequent call to
+      AST\_WRITE
+      will write data to the new file, until the SinkFile attribute is
+      cleared or changed.
+
+      Clearing the attribute causes any open SinkFile to be closed. All
+      subsequent data writes will use the sink function specified when the
+      Channel was created, or will write to standard output if no sink
+      function was specified.
+
+      If no value has been assigned to SinkFile, a null string will be
+      returned if an attempt is made to get the attribute value.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         When the FitsChan is destroyed, any headers in the FitsChan will be
+         written out to the sink file, if one is specified (if not, the
+         sink function used when the FitsChan was created is used). The
+         sink file is a text file (not a FITS file) containing one header
+         per line.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A new SinkFile will over-write any existing file with the same
+         name unless the existing file is write protected, in which case an
+         error will be reported.
+
+         \sstitem
+         Any open SinkFile is closed when the Channel is deleted.
+
+         \sstitem
+         If the Channel is copied or dumped
+         (using \htmlref{AST\_COPY}{AST\_COPY} or \htmlref{AST\_SHOW}{AST\_SHOW})
+         the SinkFile attribute is left in a cleared state in the output
+         Channel (i.e. the value of the SinkFile attribute is not copied).
+      }
+   }
+}
+\sstroutine{
+   Size(element)
+}{
+   Character size for a Plot element
+}{
+   \sstdescription{
+      This attribute determines the character size used when drawing
+      each element of graphical output produced by a \htmlref{Plot}{Plot}. It takes a
+      separate value for each graphical element so that, for instance,
+      the setting \texttt{"} Size(title)=2.0\texttt{"}  causes the Plot title to be drawn
+      using twice the default character size.
+
+      The range of character sizes available and the appearance of the
+      resulting text is determined by the underlying graphics system.
+      The default behaviour is for all graphical elements to be drawn
+      using the default character size supplied by this graphics
+      system.
+   }
+   \sstattributetype{
+      Floating Point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of the graphical elements available, see the
+         description of the Plot class.
+
+         \sstitem
+         If no graphical element is specified, (e.g. \texttt{"} Size\texttt{"}  instead
+         of \texttt{"} Size(title)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will
+         affect the attribute value of all graphical elements, while a
+         \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}  operation will use just the Size(TextLab)
+         value.
+      }
+   }
+}
+\sstroutine{
+   SizeGuess
+}{
+   The expected size of the KeyMap
+}{
+   \sstdescription{
+      This is attribute gives an estimate of the number of entries that
+      will be stored in the \htmlref{KeyMap}{KeyMap}. It is used to tune the internal
+      properties of the KeyMap for speed and efficiency. A larger value
+      will result in faster access at the expense of increased memory
+      requirements. However if the SizeGuess value is much larger than
+      the actual size of the KeyMap, then there will be little, if any,
+      speed gained by making the SizeGuess even larger. The default value
+      is 300.
+
+      The value of this attribute can only be changed if the KeyMap is
+      empty. Its value can be set conveniently when creating the KeyMap.
+      An error will be reported if an attempt is made to set or clear the
+      attribute when the KeyMap contains any entries.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         All KeyMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Skip
+}{
+   Skip irrelevant data?
+}{
+   \sstdescription{
+      This is a boolean attribute which indicates whether the \htmlref{Object}{Object}
+      data being read through a \htmlref{Channel}{Channel} are inter-mixed with other,
+      irrelevant, external data.
+
+      If Skip is zero (the default), then the source of input data is
+      expected to contain descriptions of AST Objects and comments and
+      nothing else (if anything else is read, an error will
+      result). If Skip is non-zero, then any non-Object data
+      encountered between Objects will be ignored and simply skipped
+      over in order to reach the next Object.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         All Channels have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         The FitsChan class sets the default value of this attribute
+         to 1, so that all irrelevant FITS headers will normally be
+         ignored.
+      }
+   }
+}
+\sstroutine{
+   SkyRef(axis)
+}{
+   Position defining the offset coordinate system
+}{
+   \sstdescription{
+      This attribute allows a \htmlref{SkyFrame}{SkyFrame} to represent offsets, rather than
+      absolute axis values, within the coordinate system specified by the
+      \htmlref{System}{System} attribute. If supplied, SkyRef should be set to hold the
+      longitude and latitude of a point within the coordinate system
+      specified by the System attribute. The coordinate system represented
+      by the SkyFrame will then be rotated in order to put the specified
+      position at either the pole or the origin of the new coordinate system
+      (as indicated by the \htmlref{SkyRefIs}{SkyRefIs} attribute). The orientation of the
+      modified coordinate system is then controlled using the SkyRefP
+      attribute.
+
+      If an integer axis index is included in the attribute name (e.g.
+      \texttt{"} SkyRef(1)\texttt{"} ) then the attribute value should be supplied as a single
+      floating point axis value, in radians, when setting a value for the
+      attribute, and will be returned in the same form when getting the value
+      of the attribute. In this case the integer axis index should be \texttt{"} 1\texttt{"}
+      or \texttt{"} 2\texttt{"}  (the values to use for longitude and latitude axes are
+      given by the \htmlref{LonAxis}{LonAxis} and \htmlref{LatAxis}{LatAxis} attributes).
+
+      If no axis index is included in the attribute name (e.g. \texttt{"} SkyRef\texttt{"} ) then
+      the attribute value should be supplied as a character string
+      containing two formatted axis values (an axis 1 value followed by a
+      comma, followed by an axis 2 value). The same form
+      will be used when getting the value of the attribute.
+
+      The default values for SkyRef are zero longitude and zero latitude.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the System attribute of the SkyFrame is changed, any position
+         given for SkyRef is transformed into the new System.
+
+         \sstitem
+         If a value has been assigned to SkyRef attribute, then
+         the default values for certain attributes are changed as follows:
+         the default axis Labels for the SkyFrame are modified by appending
+         \texttt{"}  offset\texttt{"}  to the end, the default axis Symbols for the SkyFrame are
+         modified by prepending the character \texttt{"} D\texttt{"}  to the start, and the
+         default title is modified by replacing the projection information by the
+         origin information.
+      }
+   }
+   \sstdiytopic{
+      Aligning SkyFrames with Offset Coordinate Systems
+   }{
+      The offset coordinate system within a SkyFrame should normally be
+      considered as a superficial \texttt{"} re-badging\texttt{"}  of the axes of the coordinate
+      system specified by the System attribute - it merely provides an
+      alternative numerical \texttt{"} label\texttt{"}  for each position in the System coordinate
+      system. The SkyFrame retains full knowledge of the celestial coordinate
+      system on which the offset coordinate system is based (given by the
+      System attribute). For instance, the SkyFrame retains knowledge of the
+      way that one celestial coordinate system may \texttt{"} drift\texttt{"}  with respect to
+      another over time. Normally, if you attempt to align two SkyFrames (e.g.
+      using the \htmlref{AST\_CONVERT}{AST\_CONVERT} or \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} routine),
+      the effect of any offset coordinate system defined in either SkyFrame
+      will be removed, resulting in alignment being performed in the
+      celestial coordinate system given by the \htmlref{AlignSystem}{AlignSystem} attribute.
+      However, by setting the \htmlref{AlignOffset}{AlignOffset} attribute to a non-zero value, it
+      is possible to change this behaviour so that the effect of the offset
+      coordinate system is not removed when aligning two SkyFrames.
+   }
+}
+\sstroutine{
+   SkyRefIs
+}{
+   Selects the nature of the offset coordinate system
+}{
+   \sstdescription{
+      This attribute controls how the values supplied for the SkyRef and
+      SkyRefP attributes are used. These three attributes together allow
+      a \htmlref{SkyFrame}{SkyFrame} to represent offsets relative to some specified origin
+      or pole within the coordinate system specified by the \htmlref{System}{System} attribute,
+      rather than absolute axis values. SkyRefIs can take one of the
+      case-insensitive values \texttt{"} Origin\texttt{"} , \texttt{"} Pole\texttt{"}  or \texttt{"} Ignored\texttt{"} .
+
+      If SkyRefIs is set to \texttt{"} Origin\texttt{"} , then the coordinate system
+      represented by the SkyFrame is modified to put the origin of longitude
+      and latitude at the position specified by the SkyRef attribute.
+
+      If SkyRefIs is set to \texttt{"} Pole\texttt{"} , then the coordinate system represented
+      by the SkyFrame is modified to put the north pole at the position
+      specified by the SkyRef attribute.
+
+      If SkyRefIs is set to \texttt{"} Ignored\texttt{"}  (the default), then any value set for the
+      SkyRef attribute is ignored, and the SkyFrame represents the coordinate
+      system specified by the System attribute directly without any rotation.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SkyRefP(axis)
+}{
+   Position on primary meridian of offset coordinate system
+}{
+   \sstdescription{
+      This attribute is used to control the orientation of the offset
+      coordinate system defined by attributes SkyRef and \htmlref{SkyRefIs}{SkyRefIs}. If used,
+      it should be set to hold the longitude and latitude of a point within
+      the coordinate system specified by the \htmlref{System}{System} attribute. The offset
+      coordinate system represented by the \htmlref{SkyFrame}{SkyFrame} will then be rotated in
+      order to put the position supplied for SkyRefP on the zero longitude
+      meridian. This rotation is about an axis from the centre of the
+      celestial sphere to the point specified by the SkyRef attribute.
+      The default value for SkyRefP is usually the north pole (that is, a
+      latitude of $+$90 degrees in the coordinate system specified by the System
+      attribute). The exception to this is if the SkyRef attribute is
+      itself set to either the north or south pole. In these cases the
+      default for SkyRefP is the origin (that is, a (0,0) in the coordinate
+      system specified by the System attribute).
+
+      If an integer axis index is included in the attribute name (e.g.
+      \texttt{"} SkyRefP(1)\texttt{"} ) then the attribute value should be supplied as a single
+      floating point axis value, in radians, when setting a value for the
+      attribute, and will be returned in the same form when getting the value
+      of the attribute. In this case the integer axis index should be \texttt{"} 1\texttt{"}
+      or \texttt{"} 2\texttt{"}  (the values to use for longitude and latitude axes are
+      given by the \htmlref{LonAxis}{LonAxis} and \htmlref{LatAxis}{LatAxis} attributes).
+
+      If no axis index is included in the attribute name (e.g. \texttt{"} SkyRefP\texttt{"} ) then
+      the attribute value should be supplied as a character string
+      containing two formatted axis values (an axis 1 value followed by a
+      comma, followed by an axis 2 value). The same form
+      will be used when getting the value of the attribute.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the position given by the SkyRef attribute defines the origin
+         of the offset coordinate system (that is, if the SkyRefIs attribute
+         is set to \texttt{"} origin\texttt{"} ), then there will in general be two orientations
+         which will put the supplied SkyRefP position on the zero longitude
+         meridian. The orientation which is actually used is the one which
+         gives the SkyRefP position a positive latitude in the offset coordinate
+         system (the other possible orientation would give the SkyRefP position
+         a negative latitude).
+
+         \sstitem
+         An error will be reported if an attempt is made to use a
+         SkyRefP value which is co-incident with SkyRef or with the point
+         diametrically opposite to SkyRef on the celestial sphere. The
+         reporting of this error is deferred until the SkyRef and SkyRefP
+         attribute values are used within a calculation.
+
+         \sstitem
+         If the System attribute of the SkyFrame is changed, any position
+         given for SkyRefP is transformed into the new System.
+      }
+   }
+}
+\sstroutine{
+   SkyTol
+}{
+   The smallest significant shift in sky coordinates
+}{
+   \sstdescription{
+      This attribute indicates the accuracy of the axis values that will
+      be represented by the \htmlref{SkyFrame}{SkyFrame}. If the arc-distance between two
+      positions within the SkyFrame is smaller than the value of SkyTol,
+      then the two positions will (for the puposes indicated below) be
+      considered to be co-incident.
+
+      This value is used only when constructing the \htmlref{Mapping}{Mapping} between
+      two different SkyFrames (for instance, when calling
+      \htmlref{AST\_CONVERT}{AST\_CONVERT} or \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME}).
+      If the transformation between the two SkyFrames causes positions to
+      shift by less than SkyTol arc-seconds, then the transformation is
+      replaced by a \htmlref{UnitMap}{UnitMap}.  This could in certain circumatances allow
+      major simplifications to be made to the transformation between
+      any pixel grids associated with the two SkyFrames (for instance, if
+      each SkyFrame is part of the WCS \htmlref{FrameSet}{FrameSet} associated with an image).
+
+      A common case is when two SkyFrames use the FK5 system, but have
+      slightly different \htmlref{Epoch}{Epoch} values. If the \htmlref{AlignSystem}{AlignSystem} attribute has
+      its default value of \texttt{"} ICRS\texttt{"} , then the transformation between the
+      two SkyFrames will include a very small rotation (FK5 rotates with
+      respect to ICRS as a rate of about 0.0005 arc-seconds per year). In
+      most circumstances such a small rotation is insignificant. Setting
+      SkyTol to some suitably small non-zero value will cause this
+      rotation to be ignored, allowing much simpler transformations to
+      be used.
+
+      The test to determine the shift introduced by transforming between
+      the two SkyFrames is performed by transforming a set of 14 position
+      spread evenly over the whole sky. The largest shift produced at any
+      of these 14 positions is compared to the value of SkyTol.
+
+      The SkyTol value is in units of arc-seconds, and the default value
+      is 0.001.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SortBy
+}{
+   Determines how keys are sorted in a KeyMap
+}{
+   \sstdescription{
+      This attribute determines the order in which keys are returned by the
+      \htmlref{AST\_MAPKEY}{AST\_MAPKEY}
+      function. It may take the following values (the default is \texttt{"} None\texttt{"} ):
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} None\texttt{"} : The keys are returned in an arbitrary order. This is the
+         fastest method as it avoids the need for a sorted list of keys to
+         be maintained and used.
+
+         \sstitem
+         \texttt{"} AgeDown\texttt{"} : The keys are returned in the order in which values were
+         stored in the \htmlref{KeyMap}{KeyMap}, with the key for the most recent value being
+         returned last. If the value of an existing entry is changed, it goes
+         to the end of the list.
+
+         \sstitem
+         \texttt{"} AgeUp\texttt{"} : The keys are returned in the order in which values were
+         stored in the KeyMap, with the key for the most recent value being
+         returned first. If the value of an existing entry is changed, it goes
+         to the top of the list.
+
+         \sstitem
+         \texttt{"} KeyAgeDown\texttt{"} : The keys are returned in the order in which they
+         were originally stored in the KeyMap, with the most recent key being
+         returned last. If the value of an existing entry is changed, its
+         position in the list does not change.
+
+         \sstitem
+         \texttt{"} KeyAgeUp\texttt{"} : The keys are returned in the order in which they
+         were originally stored in the KeyMap, with the most recent key being
+         returned first. If the value of an existing entry is changed, its
+         position in the list does not change.
+
+         \sstitem
+         \texttt{"} KeyDown\texttt{"} : The keys are returned in alphabetical order, with \texttt{"} A...\texttt{"}
+         being returned last.
+
+         \sstitem
+         \texttt{"} KeyUp\texttt{"} : The keys are returned in alphabetical order, with \texttt{"} A...\texttt{"}
+         being returned first.
+      }
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         All KeyMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If a new value is assigned to SortBy (or if SortBy is cleared),
+         all entries currently in the KeyMap are re-sorted according to the
+         new SortBy value.
+      }
+   }
+}
+\sstroutine{
+   SourceFile
+}{
+   Input file from which to read data
+}{
+   \sstdescription{
+      This attribute specifies the name of a file from which the \htmlref{Channel}{Channel}
+      should read data. If specified it is used in preference to any source
+      function specified when the Channel was created.
+
+      Assigning a new value to this attribute will cause any previously
+      opened SourceFile to be closed. The first subsequent call to
+      \htmlref{AST\_READ}{AST\_READ}
+      will attempt to open the new file (an error will be reported if the
+      file cannot be opened), and read data from it. All subsequent call to
+      AST\_READ
+      will read data from the new file, until the SourceFile attribute is
+      cleared or changed.
+
+      Clearing the attribute causes any open SourceFile to be closed. All
+      subsequent data reads will use the source function specified when the
+      Channel was created, or will read from standard input if no source
+      function was specified.
+
+      If no value has been assigned to SourceFile, a null string will be
+      returned if an attempt is made to get the attribute value.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         In the case of a FitsChan, the specified SourceFile supplements
+         the source function specified when the FitsChan was created,
+         rather than replacing the source function. The source file
+         should be a text file (not a FITS file) containing one header per
+         line. When a value is assigned to SourceFile, the file is opened
+         and read immediately, and all headers read from the file are
+         appended to the end of any header already in the FitsChan. The file
+         is then closed. Clearing the SourceFile attribute has no further
+         effect, other than nullifying the string (i.e. the file name)
+         associated with the attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Any open SourceFile is closed when the Channel is deleted.
+
+         \sstitem
+         If the Channel is copied or dumped
+         (using \htmlref{AST\_COPY}{AST\_COPY} or \htmlref{AST\_SHOW}{AST\_SHOW})
+         the SourceFile attribute is left in a cleared state in the output
+         Channel (i.e. the value of the SourceFile attribute is not copied).
+      }
+   }
+}
+\sstroutine{
+   SourceSys
+}{
+   Spectral system in which the source velocity is stored
+}{
+   \sstdescription{
+      This attribute identifies the spectral system in which the
+      \htmlref{SourceVel}{SourceVel} attribute value (the source velocity) is supplied and
+      returned. It can be one of the following:
+
+      \sstitemlist{
+
+         \sstitem
+            \texttt{"} VRAD\texttt{"}  or \texttt{"} VRADIO\texttt{"} : Radio velocity (km/s)
+
+         \sstitem
+            \texttt{"} VOPT\texttt{"}  or \texttt{"} VOPTICAL\texttt{"} : Optical velocity (km/s)
+
+         \sstitem
+            \texttt{"} ZOPT\texttt{"}  or \texttt{"} REDSHIFT\texttt{"} : Redshift (dimensionless)
+
+         \sstitem
+            \texttt{"} BETA\texttt{"} : Beta factor (dimensionless)
+
+         \sstitem
+            \texttt{"} VELO\texttt{"}  or \texttt{"} VREL\texttt{"} : Apparent radial (\texttt{"} relativistic\texttt{"} ) velocity (km/s)
+
+      }
+      When setting a new value for the SourceVel attribute, the source
+      velocity should be supplied in the spectral system indicated
+      by this attribute. Likewise, when getting the value of the SourceVel
+      attribute, the velocity will be returned in this spectral system.
+
+      If the value of SourceSys is changed, the value stored for SourceVel
+      will be converted from the old to the new spectral systems.
+
+      The default value is \texttt{"} VELO\texttt{"}  (apparent radial velocity).
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SourceVRF
+}{
+   Rest frame in which the source velocity is stored
+}{
+   \sstdescription{
+      This attribute identifies the rest frame in which the source
+      velocity or redshift is stored (the source velocity or redshift is
+      accessed using attribute \htmlref{SourceVel}{SourceVel}). When setting a new value for the
+      SourceVel attribute, the source velocity or redshift should be supplied
+      in the rest frame indicated by this attribute. Likewise, when getting
+      the value of the SourceVel attribute, the velocity or redshift will be
+      returned in this rest frame.
+
+      If the value of SourceVRF is changed, the value stored for SourceVel
+      will be converted from the old to the new rest frame.
+
+      The values which can be supplied are the same as for the \htmlref{StdOfRest}{StdOfRest}
+      attribute (except that SourceVRF cannot be set to \texttt{"} Source\texttt{"} ). The
+      default value is \texttt{"} Helio\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SourceVel
+}{
+   The source velocity
+}{
+   \sstdescription{
+      This attribute (together with \htmlref{SourceSys}{SourceSys}, \htmlref{SourceVRF}{SourceVRF}, \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec})
+      defines the \texttt{"} Source\texttt{"}  standard of rest (see attribute \htmlref{StdOfRest}{StdOfRest}). This is
+      a rest frame which is moving towards the position given by RefRA and
+      RefDec at a  velocity given by SourceVel. A positive value means
+      the source is moving away from the observer. When a new value is
+      assigned to this attribute, the supplied value is assumed to refer
+      to the spectral system specified by the SourceSys attribute. For
+      instance, the SourceVel value may be supplied as a radio velocity, a
+      redshift, a beta factor, etc. Similarly, when the current value of
+      the SourceVel attribute is obtained, the returned value will refer
+      to the spectral system specified by the SourceSys value. If the
+      SourceSys value is changed, any value previously stored for the SourceVel
+      attribute will be changed automatically from the old spectral system
+      to the new spectral system.
+
+      When setting a value for SourceVel, the value should be supplied in the
+      rest frame specified by the SourceVRF attribute. Likewise, when getting
+      the value of SourceVel, it will be returned in the rest frame specified
+      by the SourceVRF attribute.
+
+      The default SourceVel value is zero.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         It is important to set an appropriate value for SourceVRF and
+         SourceSys before setting a value for SourceVel. If a new value is later
+         set for SourceVRF or SourceSys, the value stored for SourceVel will
+         simultaneously be changed to the new standard of rest or spectral
+         system.
+      }
+   }
+}
+\sstroutine{
+   SpecOrigin
+}{
+   The zero point for SpecFrame axis values
+}{
+   \sstdescription{
+      This specifies the origin from which all spectral values are measured.
+      The default value (zero) results in the \htmlref{SpecFrame}{SpecFrame} describing
+      absolute spectral values in the system given by the \htmlref{System}{System} attribute
+      (e.g. frequency, velocity, etc). If a SpecFrame is to be used to
+      describe offset from some origin, the SpecOrigin attribute
+      should be set to hold the required origin value. The SpecOrigin value
+      stored inside the SpecFrame structure is modified whenever SpecFrame
+      attribute values are changed so that it refers to the original spectral
+      position.
+
+      When setting a new value for this attribute, the supplied value is assumed
+      to be in the system, units and standard of rest described by the SpecFrame.
+      Likewise, when getting the value of this attribute, the value is returned
+      in the system, units and standard of rest described by the SpecFrame. If
+      any of these attributes are changed, then any previously stored SpecOrigin
+      value will also be changed so that refers to the new system, units or
+      standard of rest.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SpecFrame
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SpecVal
+}{
+   The spectral position at which flux values are measured
+}{
+   \sstdescription{
+      This attribute specifies the spectral position (frequency, wavelength,
+      etc.), at which the values described by the \htmlref{FluxFrame}{FluxFrame} are measured.
+      It is used when determining the \htmlref{Mapping}{Mapping} between between FluxFrames.
+
+      The default value and spectral system used for this attribute are
+      both specified when the FluxFrame is created.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FluxFrame
+      }{
+         All FluxFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   StcsArea
+}{
+   Return the CoordinateArea component when reading an STC-S document?
+}{
+   \sstdescription{
+      This is a boolean attribute which controls what is returned
+      by the
+      \htmlref{AST\_READ}{AST\_READ}
+      function when it is used to read from an \htmlref{StcsChan}{StcsChan}.
+      If StcsArea is set non-zero (the default), then a \htmlref{Region}{Region}
+      representing the STC CoordinateArea will be returned by
+      AST\_READ.
+      If StcsArea is set to zero, then the STC CoordinateArea
+      will not be returned.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         StcsChan
+      }{
+         All StcsChans have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Other attributes such as \htmlref{StcsCoords}{StcsCoords} and \htmlref{StcsProps}{StcsProps} can be used to
+         specify other Objects to be returned by
+         AST\_READ.
+         If more than one of these attributes is set non-zero, then the
+         actual \htmlref{Object}{Object} returned by
+         AST\_READ
+         will be a \htmlref{KeyMap}{KeyMap}, containing the requested Objects. In this
+         case, the Region representing the STC CoordinateArea will be
+         stored in the returned KeyMap using the key \texttt{"} AREA\texttt{"} . If StcsArea
+         is the only attribute to be set non-zero, then the Object returned by
+         AST\_READ
+         will be the CoordinateArea Region itself.
+
+         \sstitem
+         The class of Region used to represent the CoordinateArea for each
+         STC-S sub-phrase is determined by the first word in the
+         sub-phrase (the \texttt{"} sub-phrase identifier\texttt{"} ). The individual sub-phrase
+         Regions are combined into a single \htmlref{Prism}{Prism}, which is then simplified
+         using \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}
+         to form the returned region.
+
+         \sstitem
+         Sub-phrases that represent a single value ( that is, have
+         identifiers \texttt{"} Time\texttt{"} , \texttt{"} Position\texttt{"} , \texttt{"} Spectral\texttt{"}  or \texttt{"} Redshift\texttt{"}  ) are
+         considered to be be part of the STC CoordinateArea component.
+
+         \sstitem
+         The \htmlref{TimeFrame}{TimeFrame} used to represent a time STC-S sub-phrase will have
+         its \htmlref{TimeOrigin}{TimeOrigin} attribute set to the sub-phrase start time. If no
+         start time is specified by the sub-phrase, then the stop time will be
+         used instead. If no stop time is specified by the sub-phrase, then
+         the single time value specified in the sub-phrase will be used
+         instead. Subsequently clearing the TimeOrigin attribute (or setting
+         its value to zero) will cause the TimeFrame to reprsent absolute times.
+
+         \sstitem
+         The \htmlref{Epoch}{Epoch} attribute for the returned Region is set in the same
+         way as the TimeOrigin attribute (see above).
+      }
+   }
+}
+\sstroutine{
+   StcsCoords
+}{
+   Return the Coordinates component when reading an STC-S document?
+}{
+   \sstdescription{
+      This is a boolean attribute which controls what is returned
+      by the
+      \htmlref{AST\_READ}{AST\_READ}
+      function when it is used to read from an \htmlref{StcsChan}{StcsChan}.
+      If StcsCoords is set non-zero, then a \htmlref{PointList}{PointList}
+      representing the STC Coordinates will be returned by
+      AST\_READ.
+      If StcsCoords is set to zero (the default), then the STC
+      Coordinates will not be returned.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         StcsChan
+      }{
+         All StcsChans have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Other attributes such as \htmlref{StcsArea}{StcsArea} and \htmlref{StcsProps}{StcsProps} can be used to
+         specify other Objects to be returned by
+         AST\_READ.
+         If more than one of these attributes is set non-zero, then the
+         actual \htmlref{Object}{Object} returned by
+         AST\_READ
+         will be a \htmlref{KeyMap}{KeyMap}, containing the requested Objects. In this
+         case, the PointList representing the STC Coordinates will be
+         stored in the returned KeyMap using the key \texttt{"} COORDS\texttt{"} . If StcsCoords
+         is the only attribute to be set non-zero, then the Object returned by
+         AST\_READ
+         will be the Coordinates PointList itself.
+
+         \sstitem
+         The Coordinates component is specified by the additional axis
+         values embedded within the body of each STC-S sub-phrase that
+         represents an extended area. Sub-phrases that represent a single
+         value ( that is, have identifiers \texttt{"} Time\texttt{"} , \texttt{"} Position\texttt{"} , \texttt{"} Spectral\texttt{"}
+         or \texttt{"} Redshift\texttt{"}  ) are not considered to be be part of the STC
+         Coordinates component.
+
+         \sstitem
+         If the STC-S documents does not contain a Coordinates component,
+         then a NULL object pointer
+         (AST\_\_NULL)
+         will be returned by
+         AST\_READ
+         if the Coordinates component is the only object being returned. If
+         other objects are also being returned (see attributes StcsProps and
+         StcsArea), then the returned KeyMap will contain a \texttt{"} COORDS\texttt{"}  key
+         only if the Coordinates component is read succesfully.
+
+         \sstitem
+         The \htmlref{TimeFrame}{TimeFrame} used to represent a time STC-S sub-phrase will have
+         its \htmlref{TimeOrigin}{TimeOrigin} attribute set to the sub-phrase start time. If no
+         start time is specified by the sub-phrase, then the stop time will be
+         used instead. If no stop time is specified by the sub-phrase, then
+         the single time value specified in the sub-phrase will be used
+         instead. Subsequently clearing the TimeOrigin attribute (or setting
+         its value to zero) will cause the TimeFrame to reprsent absolute times.
+
+         \sstitem
+         The \htmlref{Epoch}{Epoch} attribute for the returned \htmlref{Region}{Region} is set in the same
+         way as the TimeOrigin attribute (see above).
+      }
+   }
+}
+\sstroutine{
+   StcsLength
+}{
+   Controls output line length
+}{
+   \sstdescription{
+      This attribute specifies the maximum length to use when writing out
+      text through the sink function supplied when the \htmlref{StcsChan}{StcsChan} was created.
+      It is ignored if the \htmlref{Indent}{Indent} attribute is zero (in which case the text
+      supplied to the sink function can be of any length). The default value
+      is 70.
+
+      The number of characters in each string written out through the sink
+      function will not usually be greater than the value of this attribute
+      (but may be less). However, if any single word in the STC-S
+      description exceeds the specified length, then the word will be
+      written out as a single line.
+
+      Note, the default value of zero is unlikely to be appropriate when
+      an StcsChan is used within Fortran code. In this case, StcsLength
+      should usually be set to the size of the CHARACTER variable used to
+      receive the text returned by \htmlref{AST\_GETLINE}{AST\_GETLINE} within the sink function.
+      In addition, the Indent attribute should be set non-zero. This
+      avoids the possibility of long lines being truncated invisibly
+      within AST\_GETLINE.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         StcsChan
+      }{
+         All StcsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   StcsProps
+}{
+   Return all properties when reading an STC-S document?
+}{
+   \sstdescription{
+      This is a boolean attribute which controls what is returned
+      by the
+      \htmlref{AST\_READ}{AST\_READ}
+      function when it is used to read from an \htmlref{StcsChan}{StcsChan}.
+      If StcsProps is set non-zero, then a \htmlref{KeyMap}{KeyMap} containing all the
+      properties read from the STC-S document will be returned by
+      AST\_READ.
+      If StcsProps is set to zero (the default), then the properties
+      will not be returned.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         StcsChan
+      }{
+         All StcsChans have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Other attributes such as \htmlref{StcsCoords}{StcsCoords} and \htmlref{StcsArea}{StcsArea} can be used to
+         specify other Objects to be returned by
+         AST\_READ.
+         If more than one of these attributes is set non-zero, then the
+         actual \htmlref{Object}{Object} returned by
+         AST\_READ
+         will be a KeyMap containing the requested Objects. In this
+         case, the properties KeyMap will be stored in the returned KeyMap
+         using the key \texttt{"} PROPS\texttt{"} . If StcsProps is the only attribute to be
+         set non-zero, then the Object returned by
+         AST\_READ
+         will be the properties KeyMap itself.
+
+         \sstitem
+         The KeyMap containing the properties will have entries for one or
+         more of the following keys: \texttt{"} TIME\_PROPS\texttt{"} , \texttt{"} SPACE\_PROPS\texttt{"} , \texttt{"} SPECTRAL\_PROPS\texttt{"}
+         and \texttt{"} REDSHIFT\_PROPS\texttt{"} . Each of these entries will be another KeyMap
+         containing the properties of the corresponding STC-S sub-phrase.
+      }
+   }
+}
+\sstroutine{
+   StdOfRest
+}{
+   Standard of rest
+}{
+   \sstdescription{
+      This attribute identifies the standard of rest to which the spectral
+      axis values of a \htmlref{SpecFrame}{SpecFrame} refer, and may take any of the values
+      listed in the \texttt{"} Standards of Rest\texttt{"}  section (below).
+
+      The default StdOfRest value is \texttt{"} Helio\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SpecFrame
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Standards of Rest
+   }{
+      The SpecFrame class supports the following StdOfRest values (all are
+      case-insensitive):
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} Topocentric\texttt{"} , \texttt{"} Topocent\texttt{"}  or \texttt{"} Topo\texttt{"} : The observers rest-frame (assumed
+         to be on the surface of the earth). Spectra recorded in this standard of
+         rest suffer a Doppler shift which varies over the course of a day
+         because of the rotation of the observer around the axis of the earth.
+         This standard of rest must be qualified using the \htmlref{ObsLat}{ObsLat}, \htmlref{ObsLon}{ObsLon},
+         \htmlref{ObsAlt}{ObsAlt}, \htmlref{Epoch}{Epoch}, \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec} attributes.
+
+         \sstitem
+         \texttt{"} Geocentric\texttt{"} , \texttt{"} Geocentr\texttt{"}  or \texttt{"} Geo\texttt{"} : The rest-frame of the earth centre.
+         Spectra recorded in this standard of rest suffer a Doppler shift which
+         varies over the course of a year because of the rotation of the earth
+         around the Sun. This standard of rest must be qualified using the Epoch,
+         RefRA and RefDec attributes.
+
+         \sstitem
+         \texttt{"} Barycentric\texttt{"} , \texttt{"} Barycent\texttt{"}  or \texttt{"} Bary\texttt{"} : The rest-frame of the solar-system
+         barycentre. Spectra recorded in this standard of rest suffer a Doppler
+         shift which depends both on the velocity of the Sun through the Local
+         Standard of Rest, and on the movement of the planets through the solar
+         system. This standard of rest must be qualified using the Epoch, RefRA
+         and RefDec attributes.
+
+         \sstitem
+         \texttt{"} Heliocentric\texttt{"} , \texttt{"} Heliocen\texttt{"}  or \texttt{"} Helio\texttt{"} : The rest-frame of the Sun.
+         Spectra recorded in this standard of rest suffer a Doppler shift which
+         depends on the velocity of the Sun through the Local Standard of Rest.
+         This standard of rest must be qualified using the RefRA and RefDec
+         attributes.
+
+         \sstitem
+         \texttt{"} LSRK\texttt{"} , \texttt{"} LSR\texttt{"} : The rest-frame of the kinematical Local Standard of
+         Rest. Spectra recorded in this standard of rest suffer a Doppler shift
+         which depends on the velocity of the kinematical Local Standard of Rest
+         through the galaxy. This standard of rest must be qualified using the
+         RefRA and RefDec attributes.
+
+         \sstitem
+         \texttt{"} LSRD\texttt{"} : The rest-frame of the dynamical Local Standard of Rest. Spectra
+         recorded in this standard of rest suffer a Doppler shift which depends
+         on the velocity of the dynamical Local Standard of Rest through the
+         galaxy.  This standard of rest must be qualified using the RefRA and
+         RefDec attributes.
+
+         \sstitem
+         \texttt{"} Galactic\texttt{"} , \texttt{"} Galactoc\texttt{"}  or \texttt{"} Gal\texttt{"} : The rest-frame of the galactic centre.
+         Spectra recorded in this standard of rest suffer a Doppler shift which
+         depends on the velocity of the galactic centre through the local group.
+         This standard of rest must be qualified using the RefRA and RefDec
+         attributes.
+
+         \sstitem
+         \texttt{"} Local\_group\texttt{"} , \texttt{"} Localgrp\texttt{"}  or \texttt{"} LG\texttt{"} : The rest-frame of the local group.
+         This standard of rest must be qualified using the RefRA and RefDec
+         attributes.
+
+         \sstitem
+         \texttt{"} Source\texttt{"} , or \texttt{"} src\texttt{"} : The rest-frame of the source. This standard of
+         rest must be qualified using the RefRA, RefDec and \htmlref{SourceVel}{SourceVel} attributes.
+
+      }
+      Where more than one alternative \htmlref{System}{System} value is shown above, the
+      first of these will be returned when an enquiry is made.
+   }
+}
+\sstroutine{
+   Strict
+}{
+   Report an error if any unexpeted data items are found?
+}{
+   \sstdescription{
+      This is a boolean attribute which indicates whether a warning
+      rather than an error should be issed for insignificant conversion
+      problems. If it is set non-zero, then fatal errors are issued
+      instead of warnings, resulting in the
+      inherited STATUS variable being set to an error value.
+      If Strict is zero (the default), then execution continues after minor
+      conversion problems, and a warning message is added to the \htmlref{Channel}{Channel}
+      structure. Such messages can be retrieved using the
+      \htmlref{AST\_WARNINGS}{AST\_WARNINGS}
+      function.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         All Channels have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This attribute was introduced in AST version 5.0. Prior to this
+         version of AST unexpected data items read by a basic Channel always
+         caused an error to be reported. So applications linked against
+         versions of AST prior to version 5.0 may not be able to read \htmlref{Object}{Object}
+         descriptions created by later versions of AST, if the Object\texttt{'} s class
+         description has changed.
+      }
+   }
+}
+\sstroutine{
+   Style(element)
+}{
+   Line style for a Plot element
+}{
+   \sstdescription{
+      This attribute determines the line style used when drawing each
+      element of graphical output produced by a \htmlref{Plot}{Plot}. It takes a
+      separate value for each graphical element so that, for instance,
+      the setting \texttt{"} Style(border)=2\texttt{"}  causes the Plot border to be drawn
+      using line style 2 (which might result in, say, a dashed line).
+
+      The range of integer line styles available and their appearance
+      is determined by the underlying graphics system.  The default
+      behaviour is for all graphical elements to be drawn using the
+      default line style supplied by this graphics system (normally,
+      this is likely to give a solid line).
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of the graphical elements available, see the
+         description of the Plot class.
+
+         \sstitem
+         If no graphical element is specified, (e.g. \texttt{"} Style\texttt{"}  instead of
+         \texttt{"} Style(border)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all graphical elements, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the Style(\htmlref{Border}{Border}) value.
+      }
+   }
+}
+\sstroutine{
+   Symbol(axis)
+}{
+   Axis symbol
+}{
+   \sstdescription{
+      This attribute specifies a short-form symbol to be used to
+      represent coordinate values for a particular axis of a
+      \htmlref{Frame}{Frame}. This might be used (e.g.) in algebraic expressions where
+      a full description of the axis would be inappropriate. Examples
+      include \texttt{"} RA\texttt{"}  and \texttt{"} Dec\texttt{"}  (for Right Ascension and Declination).
+
+      If a Symbol value has not been set for a Frame axis, then a
+      suitable default is supplied.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default Symbol value supplied by the Frame class is the
+         string \texttt{"} $<$\htmlref{Domain}{Domain}$>$$<$n$>$\texttt{"} , where $<$n$>$ is 1, 2, etc. for successive
+         axes, and $<$Domain$>$ is the value of the Frame\texttt{'} s Domain
+         attribute (truncated if necessary so that the final string
+         does not exceed 15 characters). If no Domain value has been
+         set, \texttt{"} x\texttt{"}  is used as the $<$Domain$>$ value in constructing this
+         default string.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Symbol value
+         (e.g. to \texttt{"} RA\texttt{"}  or \texttt{"} Dec\texttt{"} ) as appropriate for the particular
+         celestial coordinate system being represented.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class re-defines the default Symbol value as
+         appropriate for the particular time system being represented.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Symbol attribute of a FrameSet axis is the same as that
+         of its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   System
+}{
+   Coordinate system used to describe positions within the domain
+}{
+   \sstdescription{
+      In general it is possible for positions within a given physical
+      domain to be described using one of several different coordinate
+      systems. For instance, the \htmlref{SkyFrame}{SkyFrame} class can use galactic
+      coordinates, equatorial coordinates, etc, to describe positions on
+      the sky. As another example, the \htmlref{SpecFrame}{SpecFrame} class can use frequency,
+      wavelength, velocity, etc, to describe a position within an
+      electromagnetic spectrum. The System attribute identifies the particular
+      coordinate system represented by a \htmlref{Frame}{Frame}. Each class of Frame
+      defines a set of acceptable values for this attribute, as listed
+      below (all are case insensitive). Where more than one alternative
+      System value is shown, the first of will be returned when an
+      enquiry is made.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The System attribute for a basic Frame always equals \texttt{"} Cartesian\texttt{"} ,
+         and may not be altered.
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The System attribute for a CmpFrame always equals \texttt{"} Compound\texttt{"} ,
+         and may not be altered. In addition, the CmpFrame class allows
+         the System attribute to be referenced for a component Frame by
+         including the index of an axis within the required component
+         Frame. For instance, \texttt{"} System(3)\texttt{"}  refers to the System attribute
+         of the component Frame which includes axis 3 of the CmpFrame.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The System attribute of a FrameSet is the same as that of its
+         current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         SkyFrame
+      }{
+         The SkyFrame class supports the following System values and
+         associated celestial coordinate systems:
+
+         \sstitemlist{
+
+            \sstitem
+            \texttt{"} AZEL\texttt{"} : Horizon coordinates. The longitude axis is azimuth
+            such that geographic north has an azimuth of zero and geographic
+            east has an azimuth of $+$PI/2 radians. The zenith has elevation
+            $+$PI/2. When converting to and from other celestial coordinate
+            systems, no corrections are applied for atmospheric refraction
+            or polar motion (however, a correction for diurnal aberattion is
+            applied). Note, unlike most other
+            celestial coordinate systems, this system is right handed. Also,
+            unlike other SkyFrame systems, the AzEl system is sensitive to
+            the timescale in which the \htmlref{Epoch}{Epoch} value is supplied. This is
+            because of the gross diurnal rotation which this system undergoes,
+            causing a small change in time to translate to a large rotation.
+            When converting to or from an AzEl system, the Epoch value for
+            both source and destination SkyFrames should be supplied in the
+            TDB timescale. The difference between TDB and TT is between 1
+            and 2 milliseconds, and so a TT value can usually be supplied in
+            place of a TDB value. The TT timescale is related to TAI via
+            TT = TAI $+$ 32.184 seconds.
+
+            \sstitem
+            \texttt{"} ECLIPTIC\texttt{"} : Ecliptic coordinates (IAU 1980), referred to the
+            ecliptic and mean equinox specified by the qualifying \htmlref{Equinox}{Equinox}
+            value.
+
+            \sstitem
+            \texttt{"} FK4\texttt{"} : The old FK4 (barycentric) equatorial coordinate system,
+            which should be qualified by an Equinox value. The underlying
+            model on which this is based is non-inertial and rotates slowly
+            with time, so for accurate work FK4 coordinate systems should
+            also be qualified by an Epoch value.
+
+            \sstitem
+            \texttt{"} FK4-NO-E\texttt{"}  or \texttt{"} FK4\_NO\_E\texttt{"} : The old FK4 (barycentric) equatorial
+            system but without the \texttt{"} E-terms of aberration\texttt{"}  (e.g. some radio
+            catalogues). This coordinate system should also be qualified by
+            both an Equinox and an Epoch value.
+
+            \sstitem
+            \texttt{"} FK5\texttt{"}  or \texttt{"} EQUATORIAL\texttt{"} : The modern FK5 (barycentric) equatorial
+            coordinate system. This should be qualified by an Equinox value.
+
+            \sstitem
+            \texttt{"} GALACTIC\texttt{"} : Galactic coordinates (IAU 1958).
+
+            \sstitem
+            \texttt{"} GAPPT\texttt{"} , \texttt{"} GEOCENTRIC\texttt{"}  or \texttt{"} APPARENT\texttt{"} : The geocentric apparent
+            equatorial coordinate system, which gives the apparent positions
+            of sources relative to the true plane of the Earth\texttt{'} s equator and
+            the equinox (the coordinate origin) at a time specified by the
+            qualifying Epoch value. (Note that no Equinox is needed to
+            qualify this coordinate system because no model \texttt{"} mean equinox\texttt{"}
+            is involved.)  These coordinates give the apparent right
+            ascension and declination of a source for a specified date of
+            observation, and therefore form an approximate basis for
+            pointing a telescope. Note, however, that they are applicable to
+            a fictitious observer at the Earth\texttt{'} s centre, and therefore
+            ignore such effects as atmospheric refraction and the (normally
+            much smaller) aberration of light due to the rotational velocity
+            of the Earth\texttt{'} s surface.  Geocentric apparent coordinates are
+            derived from the standard FK5 (J2000.0) barycentric coordinates
+            by taking account of the gravitational deflection of light by
+            the Sun (usually small), the aberration of light caused by the
+            motion of the Earth\texttt{'} s centre with respect to the barycentre
+            (larger), and the precession and nutation of the Earth\texttt{'} s spin
+            axis (normally larger still).
+
+            \sstitem
+            \texttt{"} HELIOECLIPTIC\texttt{"} : Ecliptic coordinates (IAU 1980), referred to the
+            ecliptic and mean equinox of J2000.0, in which an offset is added to
+            the longitude value which results in the centre of the sun being at
+            zero longitude at the date given by the Epoch attribute. Attempts to
+            set a value for the Equinox attribute will be ignored, since this
+            system is always referred to J2000.0.
+
+            \sstitem
+            \texttt{"} ICRS\texttt{"} : The Internation Celestial Reference System, realised
+            through the Hipparcos catalogue. Whilst not an equatorial system
+            by definition, the ICRS is very close to the FK5 (J2000) system
+            and is usually treated as an equatorial system. The distinction
+            between ICRS and FK5 (J2000) only becomes important when accuracies
+            of 50 milli-arcseconds or better are required. ICRS need not be
+            qualified by an Equinox value.
+
+            \sstitem
+            \texttt{"} J2000\texttt{"} : An equatorial coordinate system based on the mean
+            dynamical equator and equinox of the J2000 epoch. The dynamical
+            equator and equinox differ slightly from those used by the FK5
+            model, and so a \texttt{"} J2000\texttt{"}  SkyFrame will differ slightly from an
+            \texttt{"} FK5(Equinox=J2000)\texttt{"}  SkyFrame. The J2000 System need not be
+            qualified by an Equinox value
+
+            \sstitem
+            \texttt{"} SUPERGALACTIC\texttt{"} : De Vaucouleurs Supergalactic coordinates.
+
+            \sstitem
+            \texttt{"} UNKNOWN\texttt{"} : Any other general spherical coordinate system. No
+            \htmlref{Mapping}{Mapping} can be created between a pair of SkyFrames if either of the
+            SkyFrames has System set to \texttt{"} Unknown\texttt{"} .
+
+         }
+         Currently, the default System value is \texttt{"} ICRS\texttt{"} . However, this
+         default may change in future as new astrometric standards
+         evolve. The intention is to track the most modern appropriate
+         standard. For this reason, you should use the default only if
+         this is what you intend (and can tolerate any associated slight
+         change in future). If you intend to use the ICRS system
+         indefinitely, then you should specify it explicitly.
+      }
+      \sstsubsection{
+         SpecFrame
+      }{
+         The SpecFrame class supports the following System values and
+         associated spectral coordinate systems (the default is \texttt{"} WAVE\texttt{"}  -
+         wavelength). They are all defined in FITS-WCS paper III:
+
+         \sstitemlist{
+
+            \sstitem
+            \texttt{"} FREQ\texttt{"} : Frequency (GHz)
+
+            \sstitem
+            \texttt{"} ENER\texttt{"}  or \texttt{"} ENERGY\texttt{"} : Energy (J)
+
+            \sstitem
+            \texttt{"} WAVN\texttt{"}  or \texttt{"} WAVENUM\texttt{"} : Wave-number (1/m)
+
+            \sstitem
+            \texttt{"} WAVE\texttt{"}  or \texttt{"} WAVELEN\texttt{"} : Vacuum wave-length (Angstrom)
+
+            \sstitem
+            \texttt{"} AWAV\texttt{"}  or \texttt{"} AIRWAVE\texttt{"} : Wave-length in air (Angstrom)
+
+            \sstitem
+            \texttt{"} VRAD\texttt{"}  or \texttt{"} VRADIO\texttt{"} : Radio velocity (km/s)
+
+            \sstitem
+            \texttt{"} VOPT\texttt{"}  or \texttt{"} VOPTICAL\texttt{"} : Optical velocity (km/s)
+
+            \sstitem
+            \texttt{"} ZOPT\texttt{"}  or \texttt{"} REDSHIFT\texttt{"} : Redshift (dimensionless)
+
+            \sstitem
+            \texttt{"} BETA\texttt{"} : Beta factor (dimensionless)
+
+            \sstitem
+            \texttt{"} VELO\texttt{"}  or \texttt{"} VREL\texttt{"} : Apparent radial (\texttt{"} relativistic\texttt{"} ) velocity (km/s)
+
+         }
+         The default value for the Unit attribute for each system is shown
+         in parentheses. Note that the default value for the ActiveUnit flag
+         is .TRUE.
+         for a SpecFrame, meaning that changes to the Unit attribute for
+         a SpecFrame will result in the SpecFrame being re-mapped within
+         its enclosing FrameSet in order to reflect the change in units
+         (see \htmlref{AST\_SETACTIVEUNIT}{AST\_SETACTIVEUNIT} routine for further information).
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class supports the following System values and
+         associated coordinate systems (the default is \texttt{"} MJD\texttt{"} ):
+
+         \sstitemlist{
+
+            \sstitem
+            \texttt{"} MJD\texttt{"} : Modified Julian Date (d)
+
+            \sstitem
+            \texttt{"} JD\texttt{"} : Julian Date (d)
+
+            \sstitem
+            \texttt{"} JEPOCH\texttt{"} : Julian epoch (yr)
+
+            \sstitem
+            \texttt{"} BEPOCH\texttt{"} : Besselian (yr)
+
+         }
+         The default value for the Unit attribute for each system is shown
+         in parentheses. Strictly, these systems should not allow changes
+         to be made to the units. For instance, the usual definition of
+         \texttt{"} MJD\texttt{"}  and \texttt{"} JD\texttt{"}  include the statement that the values will be in
+         units of days. However, AST does allow the use of other units
+         with all the above supported systems (except BEPOCH), on the
+         understanding that conversion to the \texttt{"} correct\texttt{"}  units involves
+         nothing more than a simple scaling (1 yr = 365.25 d, 1 d = 24 h,
+         1 h = 60 min, 1 min = 60 s). Besselian epoch values are defined
+         in terms of tropical years of 365.2422 days, rather than the
+         usual Julian year of 365.25 days. Therefore, to avoid any
+         confusion, the Unit attribute is automatically cleared to \texttt{"} yr\texttt{"}  when
+         a System value of BEPOCH System is selected, and an error is
+         reported if any attempt is subsequently made to change the Unit
+         attribute.
+
+         Note that the default value for the ActiveUnit flag
+         is .TRUE.
+         for a TimeFrame, meaning that changes to the Unit attribute for
+         a TimeFrame will result in the TimeFrame being re-mapped within
+         its enclosing FrameSet in order to reflect the change in units
+         (see AST\_SETACTIVEUNIT routine for further information).
+      }
+      \sstsubsection{
+         \htmlref{FluxFrame}{FluxFrame}
+      }{
+         The FluxFrame class supports the following System values and
+         associated systems for measuring observed value:
+
+         \sstitemlist{
+
+            \sstitem
+            \texttt{"} FLXDN\texttt{"} : Flux per unit frequency (W/m$\wedge$2/Hz)
+
+            \sstitem
+            \texttt{"} FLXDNW\texttt{"} : Flux per unit wavelength (W/m$\wedge$2/Angstrom)
+
+            \sstitem
+            \texttt{"} SFCBR\texttt{"} : Surface brightness in frequency units (W/m$\wedge$2/Hz/arcmin$*$$*$2)
+
+            \sstitem
+            \texttt{"} SFCBRW\texttt{"} : Surface brightness in wavelength units (W/m$\wedge$2/Angstrom/arcmin$*$$*$2)
+
+         }
+         The above lists specified the default units for each System. If an
+         explicit value is set for the Unit attribute but no value is set
+         for System, then the default System value is determined by the Unit
+         string (if the units are not appropriate for describing any of the
+         supported Systems then an error will be reported when an attempt is
+         made to access the System value). If no value has been specified for
+         either Unit or System, then System=FLXDN and Unit=W/m$\wedge$2/Hz are
+         used.
+      }
+   }
+}
+\sstroutine{
+   TabOK
+}{
+   Should the FITS-WCS -TAB algorithm be recognised?
+}{
+   \sstdescription{
+      This attribute is an integer value which indicates if the \texttt{"} -TAB\texttt{"}
+      algorithm, defined in FITS-WCS paper III, should be supported by
+      the \htmlref{FitsChan}{FitsChan}. The default value is zero. A zero or negative value
+      results in no support for -TAB axes (i.e. axes that have \texttt{"} -TAB\texttt{"}
+      in their CTYPE keyword value). In this case, the
+      \htmlref{AST\_WRITE}{AST\_WRITE}
+      method will return zero if the write operation would required the
+      use of the -TAB algorithm, and the
+      \htmlref{AST\_READ}{AST\_READ}
+      method will return
+      AST\_\_NULL
+      if any axis in the supplied header uses the -TAB algorithm.
+
+      If TabOK is set to a non-zero positive integer, these methods will
+      recognise and convert axes described by the -TAB algorithm, as
+      follows:
+
+      The AST\_WRITE
+      method will generate headers that use the -TAB algorithm (if
+      possible) if no other known FITS-WCS algorithm can be used to
+      describe the supplied \htmlref{FrameSet}{FrameSet}. This will result in a table of
+      coordinate values and index vectors being stored in the FitsChan.
+      After the write operation, the calling application should check to
+      see if such a table has been stored in the FitsChan. If so, the
+      table should be retrived from the FitsChan using the
+      \htmlref{AST\_GETTABLES}{AST\_GETTABLES}
+      method, and the data (and headers) within it copied into a new
+      FITS binary table extension. See
+      AST\_GETTABLES
+      for more information. The FitsChan uses a \htmlref{FitsTable}{FitsTable} object to store
+      the table data and headers. This FitsTable will contain the required
+      columns and headers as described by FITS-WCS paper III - the
+      coordinates array will be in a column named \texttt{"} COORDS\texttt{"} , and the index
+      vector(s) will be in columns named \texttt{"} INDEX$<$i$>$\texttt{"}  (where $<$i$>$ is the index
+      of the corresponding FITS WCS  axis). Note, index vectors are only
+      created if required. The EXTNAME value will be set to the value of the
+      AST\_\_TABEXTNAME constant (currently \texttt{"} WCS-TAB\texttt{"} ). The EXTVER header
+      will be set to the positive integer value assigned to the TabOK
+      attribute. No value will be stored for the EXTLEVEL header, and should
+      therefore be considered to default to 1.
+
+      The AST\_READ
+      method will generate a FrameSet from headers that use the -TAB
+      algorithm so long as the necessary FITS binary tables are made
+      available. There are two ways to do this: firstly, if the application
+      knows which FITS binary tables will be needed, then it can create a
+      Fitstable describing each such table and store it in the FitsChan
+      (using method
+      \htmlref{AST\_PUTTABLES}{AST\_PUTTABLES} or \htmlref{AST\_PUTTABLE}{AST\_PUTTABLE}) before invoking the AST\_READ method.
+      Secondly, if the application does not know which FITS binary tables
+      will be needed by
+      AST\_READ,
+      then it can register a call-back function with the FitsChan using
+      method
+      \htmlref{AST\_TABLESOURCE}{AST\_TABLESOURCE}.
+      This call-back function will be called from within
+      AST\_READ
+      if and when a -TAB header is encountered. When called, its arguments
+      will give the name, version and level of the FITS extension containing
+      a required table. The call-back function should read this table from
+      an external FITS file, and create a corresponding FitsTable which
+      it should then return to
+      AST\_READ. Note, currently AST\_READ
+      can only handle -TAB headers that describe 1-dimensional (i.e.
+      separable) axes.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   TextLab(axis)
+}{
+   Draw descriptive axis labels for a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether textual labels should be drawn to describe the quantity
+      being represented on each axis of a \htmlref{Plot}{Plot}. It takes a separate
+      value for each physical axis of a Plot so that, for instance,
+      the setting \texttt{"} TextLab(2)=1\texttt{"}  specifies that descriptive labels
+      should be drawn for the second axis.
+
+      If the TextLab value of a Plot axis is non-zero, then
+      descriptive labels will be drawn for that axis, otherwise they
+      will be omitted. The default behaviour is to draw descriptive
+      labels if tick marks and numerical labels are being drawn around
+      the edges of the plotting area (see the \htmlref{Labelling}{Labelling} attribute),
+      but to omit them otherwise.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The text used for the descriptive labels is derived from the
+         Plot\texttt{'} s \htmlref{Label(axis)}{Label(axis)} attribute, together with its \htmlref{Unit(axis)}{Unit(axis)}
+         attribute if appropriate (see the \htmlref{LabelUnits(axis)}{LabelUnits(axis)} attribute).
+
+         \sstitem
+         The drawing of numerical axis labels for a Plot (which
+         indicate values on the axis) is controlled by the \htmlref{NumLab(axis)}{NumLab(axis)}
+         attribute.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} TextLab\texttt{"}  instead of
+         \texttt{"} TextLab(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the TextLab(1) value.
+      }
+   }
+}
+\sstroutine{
+   TextLabGap(axis)
+}{
+   Spacing of descriptive axis labels for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      where descriptive axis labels are placed relative to the axes they
+      describe.  It takes a separate value for each physical axis of a
+      \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} TextLabGap(2)=0.01\texttt{"}
+      specifies where the descriptive label for the second axis should
+      be drawn.
+
+      For each axis, the TextLabGap value gives the spacing between the
+      descriptive label and the edge of a box enclosing all other parts
+      of the annotated grid (excluding other descriptive labels). The gap
+      is measured to the nearest edge of the label (i.e. the top or the
+      bottom). Positive values cause the descriptive label to be placed
+      outside the bounding box, while negative values cause it to be placed
+      inside.
+
+      The TextLabGap value should be given as a fraction of the minimum
+      dimension of the plotting area, the default value being $+$0.01.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If drawn, descriptive labels are always placed at the edges of
+         the plotting area, even although the corresponding numerical
+         labels may be drawn along axis lines in the interior of the
+         plotting area (see the \htmlref{Labelling}{Labelling} attribute).
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} TextLabGap\texttt{"}  instead of
+         \texttt{"} TextLabGap(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the TextLabGap(1) value.
+      }
+   }
+}
+\sstroutine{
+   TickAll
+}{
+   Draw tick marks on all edges of a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      whether tick marks should be drawn on all edges of a \htmlref{Plot}{Plot}.
+
+      If the TickAll value of a Plot is non-zero (the default), then
+      tick marks will be drawn on all edges of the Plot. Otherwise,
+      they will be drawn only on those edges where the numerical and
+      descriptive axis labels are drawn (see the \htmlref{Edge(axis)}{Edge(axis)}
+      attribute).
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         In some circumstances, numerical labels and tick marks are
+         drawn along grid lines inside the plotting area, rather than
+         around its edges (see the \htmlref{Labelling}{Labelling} attribute).  In this case,
+         the value of the TickAll attribute is ignored.
+      }
+   }
+}
+\sstroutine{
+   TimeOrigin
+}{
+   The zero point for TimeFrame axis values
+}{
+   \sstdescription{
+      This specifies the origin from which all time values are measured.
+      The default value (zero) results in the \htmlref{TimeFrame}{TimeFrame} describing
+      absolute time values in the system given by the \htmlref{System}{System} attribute
+      (e.g. MJD, Julian epoch, etc). If a TimeFrame is to be used to
+      describe elapsed time since some origin, the TimeOrigin attribute
+      should be set to hold the required origin value. The TimeOrigin value
+      stored inside the TimeFrame structure is modified whenever TimeFrame
+      attribute values are changed so that it refers to the original moment
+      in time.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         TimeFrame
+      }{
+         All TimeFrames have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Input Formats
+   }{
+      The formats accepted when setting a TimeOrigin value are listed
+      below. They are all case-insensitive and are generally tolerant
+      of extra white space and alternative field delimiters:
+
+      \sstitemlist{
+
+         \sstitem
+         Besselian \htmlref{Epoch}{Epoch}: Expressed in decimal years, with or without
+         decimal places (\texttt{"} B1950\texttt{"}  or \texttt{"} B1976.13\texttt{"}  for example).
+
+         \sstitem
+         Julian Epoch: Expressed in decimal years, with or without
+         decimal places (\texttt{"} J2000\texttt{"}  or \texttt{"} J2100.9\texttt{"}  for example).
+
+         \sstitem
+         Units: An unqualified decimal value is interpreted as a value in
+         the system specified by the TimeFrame\texttt{'} s System attribute, in the
+         units given by the TimeFrame\texttt{'} s Unit attribute. Alternatively, an
+         appropriate unit string can be appended to the end of the floating
+         point value (\texttt{"} 123.4 d\texttt{"}  for example), in which case the supplied value
+         is scaled into the units specified by the Unit attribute.
+
+         \sstitem
+         Julian Date: With or without decimal places (\texttt{"} JD 2454321.9\texttt{"}  for
+         example).
+
+         \sstitem
+         Modified Julian Date: With or without decimal places
+         (\texttt{"} MJD 54321.4\texttt{"}  for example).
+
+         \sstitem
+         Gregorian Calendar Date: With the month expressed either as an
+         integer or a 3-character abbreviation, and with optional decimal
+         places to represent a fraction of a day (\texttt{"} 1996-10-2\texttt{"}  or
+         \texttt{"} 1996-Oct-2.6\texttt{"}  for example). If no fractional part of a day is
+         given, the time refers to the start of the day (zero hours).
+
+         \sstitem
+         Gregorian Date and Time: Any calendar date (as above) but with
+         a fraction of a day expressed as hours, minutes and seconds
+         (\texttt{"} 1996-Oct-2 12:13:56.985\texttt{"}  for example). The date and time can be
+         separated by a space or by a \texttt{"} T\texttt{"}  (as used by ISO8601 format).
+      }
+   }
+   \sstdiytopic{
+      Output Format
+   }{
+      When enquiring TimeOrigin values, the returned formatted floating
+      point value represents a value in the TimeFrame\texttt{'} s System, in the unit
+      specified by the TimeFrame\texttt{'} s Unit attribute.
+   }
+}
+\sstroutine{
+   TimeScale
+}{
+   Time scale
+}{
+   \sstdescription{
+      This attribute identifies the time scale to which the time axis values
+      of a \htmlref{TimeFrame}{TimeFrame} refer, and may take any of the values listed in the
+      \texttt{"} Time Scales\texttt{"}  section (below).
+
+      The default TimeScale value depends on the current \htmlref{System}{System} value; if
+      the current TimeFrame system is \texttt{"} Besselian epoch\texttt{"}  the default is
+      \texttt{"} TT\texttt{"} , otherwise it is \texttt{"} TAI\texttt{"} . Note, if the System attribute is set
+      so that the TimeFrame represents Besselian \htmlref{Epoch}{Epoch}, then an error
+      will be reported if an attempt is made to set the TimeScale to
+      anything other than TT.
+
+      Note, the supported time scales fall into two groups. The first group
+      containing  UT1, GMST, LAST and LMST define time in terms of the
+      orientation of the earth. The second group (containing all the remaining
+      time scales) define time in terms of an atomic process. Since the rate of
+      rotation of the earth varies in an unpredictable way, conversion between
+      two timescales in different groups relies on a value being supplied for
+      the \htmlref{Dut1}{Dut1} attribute (defined by the parent \htmlref{Frame}{Frame} class). This attribute
+      specifies the difference between the UT1 and UTC time scales, in seconds,
+      and defaults to zero. See the documentation for the Dut1 attribute for
+      further details.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         TimeFrame
+      }{
+         All TimeFrames have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Time Scales
+   }{
+      The TimeFrame class supports the following TimeScale values (all are
+      case-insensitive):
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} TAI\texttt{"}  - International Atomic Time
+
+         \sstitem
+         \texttt{"} UTC\texttt{"}  - Coordinated Universal Time
+
+         \sstitem
+         \texttt{"} UT1\texttt{"}  - Universal Time
+
+         \sstitem
+         \texttt{"} GMST\texttt{"}  - Greenwich Mean Sidereal Time
+
+         \sstitem
+         \texttt{"} LAST\texttt{"}  - Local Apparent Sidereal Time
+
+         \sstitem
+         \texttt{"} LMST\texttt{"}  - Local Mean Sidereal Time
+
+         \sstitem
+         \texttt{"} TT\texttt{"}  - Terrestrial Time
+
+         \sstitem
+         \texttt{"} TDB\texttt{"}  - Barycentric Dynamical Time
+
+         \sstitem
+         \texttt{"} TCB\texttt{"}  - Barycentric Coordinate Time
+
+         \sstitem
+         \texttt{"} TCG\texttt{"}  - Geocentric Coordinate Time
+
+         \sstitem
+         \texttt{"} LT\texttt{"}  - Local Time (the offset from UTC is given by attribute \htmlref{LTOffset}{LTOffset})
+
+      }
+      An very informative description of these and other time scales is
+      available at http://www.ucolick.org/$\sim$sla/leapsecs/timescales.html.
+   }
+   \sstdiytopic{
+      UTC \htmlref{Warnings}{Warnings}
+   }{
+      UTC should ideally be expressed using separate hours, minutes and
+      seconds fields (or at least in seconds for a given date) if leap seconds
+      are to be taken into account. Since the TimeFrame class represents
+      each moment in time using a single floating point number (the axis value)
+      there will be an ambiguity during a leap second. Thus an error of up to
+      1 second can result when using AST to convert a UTC time to another
+      time scale if the time occurs within a leap second. Leap seconds
+      occur at most twice a year, and are introduced to take account of
+      variation in the rotation of the earth. The most recent leap second
+      occurred on 1st January 1999. Although in the vast majority of cases
+      leap second ambiguities won\texttt{'} t matter, there are potential problems in
+      on-line data acquisition systems and in critical applications involving
+      taking the difference between two times.
+   }
+}
+\sstroutine{
+   Title
+}{
+   Frame title
+}{
+   \sstdescription{
+      This attribute holds a string which is used as a title in (e.g.)
+      graphical output to describe the coordinate system which a \htmlref{Frame}{Frame}
+      represents. Examples might be \texttt{"} Detector Coordinates\texttt{"}  or
+      \texttt{"} Galactic Coordinates\texttt{"} .
+
+      If a Title value has not been set for a Frame, then a suitable
+      default is supplied, depending on the class of the Frame.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default supplied by the Frame class is \texttt{"} $<$n$>$-d coordinate
+         system\texttt{"} , where $<$n$>$ is the number of Frame axes (\htmlref{Naxes}{Naxes}
+         attribute).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The CmpFrame class re-defines the default Title value to be
+         \texttt{"} $<$n$>$-d compound coordinate system\texttt{"} , where $<$n$>$ is the number
+         of CmpFrame axes (Naxes attribute).
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Title attribute of a FrameSet is the same as that of its
+         current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A Frame\texttt{'} s Title is intended purely for interpretation by human
+         readers and not by software.
+      }
+   }
+}
+\sstroutine{
+   TitleGap
+}{
+   Vertical spacing for a Plot title
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{AST\_GRID}{AST\_GRID} routine) by determining
+      where the title of a \htmlref{Plot}{Plot} is drawn.
+
+      Its value gives the spacing between the bottom edge of the title
+      and the top edge of a bounding box containing all the other parts
+      of the annotated grid. Positive values cause the title to be
+      drawn outside the box, while negative values cause it to be drawn
+      inside.
+
+      The TitleGap value should be given as a fraction of the minimum
+      dimension of the plotting area, the default value being $+$0.05.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Plot3D}{Plot3D}
+      }{
+         The Plot3D class ignores this attributes since it does not draw
+         a \htmlref{Title}{Title}.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The text used for the title is obtained from the Plot\texttt{'} s Title
+         attribute.
+      }
+   }
+}
+\sstroutine{
+   Tol
+}{
+   Plotting tolerance
+}{
+   \sstdescription{
+      This attribute specifies the plotting tolerance (or resolution)
+      to be used for the graphical output produced by a \htmlref{Plot}{Plot}.  Smaller
+      values will result in smoother and more accurate curves being
+      drawn, but may slow down the plotting process. Conversely,
+      larger values may speed up the plotting process in cases where
+      high resolution is not required.
+
+      The Tol value should be given as a fraction of the minimum
+      dimension of the plotting area, and should lie in the range
+      from 1.0E-7 to 1.0. By default, a value of 0.01 is used.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+}
+\sstroutine{
+   TolInverse
+}{
+   Target relative error for the iterative inverse transformation
+}{
+   \sstdescription{
+      This attribute controls the iterative inverse transformation
+      used if the \htmlref{IterInverse}{IterInverse} attribute is non-zero.
+
+      Its value gives the target relative error in teh axis values of
+      each transformed position. Further iterations will be performed
+      until the target relative error is reached, or the maximum number
+      of iterations given by attribute \htmlref{NiterInverse}{NiterInverse} is reached.
+
+      The default value is 1.0E-6.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{PolyMap}{PolyMap}
+      }{
+         All PolyMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Top(axis)
+}{
+   Highest axis value to display
+}{
+   \sstdescription{
+      This attribute gives the highest axis value to be displayed (for
+      instance, by the \htmlref{AST\_GRID}{AST\_GRID} method).
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         The default supplied by the Frame class is to display all axis
+         values, without any limit.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Top value to $+$90 degrees
+         for latitude axes, and 180 degrees for co-latitude axes. The
+         default for longitude axes is to display all axis values.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   TranForward
+}{
+   Forward transformation defined?
+}{
+   \sstdescription{
+      This attribute indicates whether a \htmlref{Mapping}{Mapping} is able to transform
+      coordinates in the \texttt{"} forward\texttt{"}  direction (i.e. converting input
+      coordinates into output coordinates). If this attribute is
+      non-zero, the forward transformation is available. Otherwise, it
+      is not.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         The TranForward attribute value for a CmpMap is given by the
+         boolean AND of the value for each component Mapping.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The TranForward attribute of a FrameSet applies to the
+         transformation which converts between the FrameSet\texttt{'} s base
+         \htmlref{Frame}{Frame} and its current Frame (as specified by the \htmlref{Base}{Base} and
+         \htmlref{Current}{Current} attributes). This value is given by the boolean AND
+         of the TranForward values which apply to each of the
+         individual sub-Mappings required to perform this conversion.
+         The TranForward attribute value for a FrameSet may therefore
+         change if a new Base or Current Frame is selected.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error will result if a Mapping with a TranForward value of
+         zero is used to transform coordinates in the forward direction.
+      }
+   }
+}
+\sstroutine{
+   TranInverse
+}{
+   Inverse transformation defined?
+}{
+   \sstdescription{
+      This attribute indicates whether a \htmlref{Mapping}{Mapping} is able to transform
+      coordinates in the \texttt{"} inverse\texttt{"}  direction (i.e. converting output
+      coordinates back into input coordinates). If this attribute is
+      non-zero, the inverse transformation is available. Otherwise, it
+      is not.
+   }
+   \sstattributetype{
+      Integer (boolean), readonly.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         The TranInverse attribute value for a CmpMap is given by the
+         boolean AND of the value for each component Mapping.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The TranInverse attribute of a FrameSet applies to the
+         transformation which converts between the FrameSet\texttt{'} s current
+         \htmlref{Frame}{Frame} and its base Frame (as specified by the \htmlref{Current}{Current} and
+         \htmlref{Base}{Base} attributes). This value is given by the boolean AND of
+         the TranInverse values which apply to each of the individual
+         sub-Mappings required to perform this conversion.
+         The TranInverse attribute value for a FrameSet may therefore
+         change if a new Base or Current Frame is selected.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error will result if a Mapping with a TranInverse value of
+         zero is used to transform coordinates in the inverse direction.
+      }
+   }
+}
+\sstroutine{
+   Unit(axis)
+}{
+   Physical units for formatted axis values
+}{
+   \sstdescription{
+      This attribute contains a textual representation of the physical
+      units used to represent formatted coordinate values on a particular
+      axis of a \htmlref{Frame}{Frame}.
+      The \htmlref{AST\_SETACTIVEUNIT}{AST\_SETACTIVEUNIT} routine controls how the Unit values
+      are used.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default supplied by the Frame class is an empty string.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Unit value (e.g. to
+         \texttt{"} hh:mm:ss.sss\texttt{"} ) to describe the character string returned by
+         the \htmlref{AST\_FORMAT}{AST\_FORMAT} function when formatting coordinate values.
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         The SpecFrame class re-defines the default Unit value so that it
+         is appropriate for the current \htmlref{System}{System} value. See the System
+         attribute for details. An error will be reported if an attempt
+         is made to use an inappropriate Unit.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class re-defines the default Unit value so that it
+         is appropriate for the current System value. See the System
+         attribute for details. An error will be reported if an attempt
+         is made to use an inappropriate Unit (e.g. \texttt{"} km\texttt{"} ).
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Unit attribute of a FrameSet axis is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This attribute described the units used when an axis value is
+         formatted into a string using
+         AST\_FORMAT.
+         In some cases these units may be different to those used to represent
+         floating point axis values within application code (for instance a
+         SkyFrame always uses radians to represent floating point axis values).
+         The InternalUnit attribute described the units used for floating
+         point values.
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   UnitRadius
+}{
+   SphMap input vectors lie on a unit sphere?
+}{
+   \sstdescription{
+      This is a boolean attribute which indicates whether the
+      3-dimensional vectors which are supplied as input to a \htmlref{SphMap}{SphMap}
+      are known to always have unit length, so that they lie on a unit
+      sphere centred on the origin.
+
+      If this condition is true (indicated by setting UnitRadius
+      non-zero), it implies that a \htmlref{CmpMap}{CmpMap} which is composed of a
+      SphMap applied in the forward direction followed by a similar
+      SphMap applied in the inverse direction may be simplified
+      (e.g. by \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}) to become a \htmlref{UnitMap}{UnitMap}. This is because the
+      input and output vectors will both have unit length and will
+      therefore have the same coordinate values.
+
+      If UnitRadius is zero (the default), then although the output
+      vector produced by the CmpMap (above) will still have unit
+      length, the input vector may not have. This will, in general,
+      change the coordinate values, so it prevents the pair of SphMaps
+      being simplified.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SphMap
+      }{
+         All SphMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This attribute is intended mainly for use when SphMaps are
+         involved in a sequence of Mappings which project (e.g.) a
+         dataset on to the celestial sphere. By regarding the celestial
+         sphere as a unit sphere (and setting UnitRadius to be non-zero)
+         it becomes possible to cancel the SphMaps present, along with
+         associated sky projections, when two datasets are aligned using
+         celestial coordinates. This often considerably improves
+         performance.
+
+         \sstitem
+         Such a situations often arises when interpreting FITS data and
+         is handled automatically by the \htmlref{FitsChan}{FitsChan} class.
+
+         \sstitem
+         The value of the UnitRadius attribute is used only to control
+         the simplification of Mappings and has no effect on the value of
+         the coordinates transformed by a SphMap. The lengths of the
+         input 3-dimensional Cartesian vectors supplied are always
+         ignored, even if UnitRadius is non-zero.
+
+         \sstitem
+         The value of this attribute may changed only if the SphMap
+         has no more than one reference. That is, an error is reported if the
+         SphMap has been cloned, either by including it within another object
+         such as a CmpMap or \htmlref{FrameSet}{FrameSet} or by calling the
+         \htmlref{AST\_CLONE}{AST\_CLONE}
+         function.
+      }
+   }
+}
+\sstroutine{
+   UseDefs
+}{
+   Use default values for unspecified attributes?
+}{
+   \sstdescription{
+      This attribute specifies whether default values should be used
+      internally for object attributes which have not been assigned a
+      value explicitly. If a non-zero value (the default) is supplied for
+      UseDefs, then default values will be used for attributes which have
+      not explicitly been assigned a value. If zero is supplied for UseDefs,
+      then an error will be reported if an attribute for which no explicit
+      value has been supplied is needed internally within AST.
+
+      Many attributes (including the UseDefs attribute itself) are unaffected
+      by the setting of the UseDefs attribute, and default values will always
+      be used without error for such attributes. The \texttt{"} Applicability:\texttt{"}  section
+      below lists the attributes which are affected by the setting of UseDefs.
+
+      Note, UseDefs only affects access to attributes internally within
+      AST. The public accessor functions such as
+      AST\_GETC
+      is unaffected by the UseDefs attribute - default values will always
+      be returned if no value has been set. Application code should use the
+      \htmlref{AST\_TEST}{AST\_TEST}
+      function if required to determine if a value has been set for an
+      attribute.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Object}{Object}
+      }{
+         All Objects have this attribute, but ignore its setting except
+         as described below for individual classes.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The default value of UseDefs for a FrameSet is redefined to be
+         the UseDefs value of its current \htmlref{Frame}{Frame}.
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The default value of UseDefs for a CmpFrame is redefined to be
+         the UseDefs value of its first component Frame.
+      }
+      \sstsubsection{
+         \htmlref{Region}{Region}
+      }{
+         The default value of UseDefs for a Region is redefined to be
+         the UseDefs value of its encapsulated Frame.
+      }
+      \sstsubsection{
+         Frame
+      }{
+         If UseDefs is zero, an error is reported when aligning Frames if the
+         \htmlref{Epoch}{Epoch}, \htmlref{ObsLat}{ObsLat} or \htmlref{ObsLon}{ObsLon} attribute is required but has not been
+         assigned a value explicitly.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         If UseDefs is zero, an error is reported when aligning SkyFrames
+         if any of the following attributes are required but have not been
+         assigned a value explicitly: Epoch, \htmlref{Equinox}{Equinox}.
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         If UseDefs is zero, an error is reported when aligning SpecFrames
+         if any of the following attributes are required but have not been
+         assigned a value explicitly: Epoch, \htmlref{RefRA}{RefRA}, \htmlref{RefDec}{RefDec}, \htmlref{RestFreq}{RestFreq},
+         \htmlref{SourceVel}{SourceVel}, \htmlref{StdOfRest}{StdOfRest}.
+      }
+      \sstsubsection{
+         \htmlref{DSBSpecFrame}{DSBSpecFrame}
+      }{
+         If UseDefs is zero, an error is reported when aligning DSBSpecFrames
+         or when accessing the \htmlref{ImagFreq}{ImagFreq} attribute if any of the following
+         attributes are required but have not been assigned a value explicitly:
+         Epoch, \htmlref{DSBCentre}{DSBCentre}, \htmlref{IF}{IF}.
+      }
+   }
+}
+\sstroutine{
+   Variant
+}{
+   Indicates which variant of the current Frame is to be used
+}{
+   \sstdescription{
+      This attribute can be used to change the \htmlref{Mapping}{Mapping} that connects the
+      current \htmlref{Frame}{Frame} to the other Frames in the \htmlref{FrameSet}{FrameSet}. By default, each
+      Frame in a FrameSet is connected to the other Frames by a single
+      Mapping that can only be changed by using the
+      \htmlref{AST\_REMAPFRAME}{AST\_REMAPFRAME}
+      method. However, it is also possible to associate multiple Mappings
+      with a Frame, each Mapping having an identifying name. If this is
+      done, the \texttt{"} Variant\texttt{"}  attribute can be set to indicate the name of
+      the Mapping that is to be used with the current Frame.
+
+      A possible (if unlikely) use-case is to create a FrameSet that can
+      be used to describe the WCS of an image formed by co-adding images
+      of two different parts of the sky. In such an image, each pixel contains
+      flux from two points on the sky.and so the WCS for the image should
+      ideally contain one pixel Frame and two SkyFrames - one describing
+      each of the two co-added images. There is nothing to prevent a
+      FrameSet containing two explicit SkyFrames, but the problem then arises
+      of how to distinguish between them. The two primary characteristics of
+      a Frame that distinguishes it from other Frames  are its class and its
+      \htmlref{Domain}{Domain} attribute value. The class of a Frame cannot be changed, but we
+      could in principle use two different Domain values to distinguish the
+      two SkyFrames. However, in practice it is not uncommon for application
+      software to assume that SkyFrames will have the default Domain value
+      of \texttt{"} SKY\texttt{"} . That is, instead of searching for Frames that have a class
+      of \texttt{"} \htmlref{SkyFrame}{SkyFrame}\texttt{"} , such software searches for Frames that have a Domain
+      of \texttt{"} SKY\texttt{"} . To alleviate this problem, it is possible to add a single
+      SkyFrame to the FrameSet, but specifying two alternate Mappings to
+      use with the SkyFrame. Setting the \texttt{"} Variant\texttt{"}  attribute to the name
+      of one or the other of these alternate Mappings will cause the
+      SkyFrame to be remapped within the FrameSet so that it uses the
+      specified Mapping. The same facility can be used with any class of
+      Frame, not just SkyFrames.
+
+      To use this facility, the Frame should first be added to the
+      FrameSet in the usual manner using the
+      \htmlref{AST\_ADDFRAME}{AST\_ADDFRAME} method. By default, the Mapping supplied to \htmlref{AST\_ADDVARIANT}{AST\_ADDVARIANT}
+      is assigned a name equal to the Domain name of the Frame. To assign a
+      different name to it, the
+      AST\_ADDVARIANT
+      method should then be called specifying the required name and a NULL
+      Mapping. The
+      AST\_ADDVARIANT
+      method should then be called repeatedly to add each required extra
+      Mapping to the current Frame, supplying a unique name for each one.
+
+      Each Frame in a FrameSet can have its own set of variant Mappings.
+      To control the Mappings in use with a specific Frame, you need first
+      to make it the current Frame in the FrameSet.
+
+      The
+      \htmlref{AST\_MIRRORVARIANTS}{AST\_MIRRORVARIANTS} routine
+      allows the effects of variant Mappings associated with a nominated
+      Frame to be propagated to other Frames in the FrameSet.
+
+      Once this has been done, setting a new value for the \texttt{"} Variant\texttt{"}
+      attribute of a FrameSet will cause the current Frame in the
+      FrameSet to be remapped to use the specified variant Mapping. An
+      error will be reported if the current Frame has no variant Mapping
+      with the supplied name.
+
+      Getting the value of the \texttt{"} Variant\texttt{"}  attribute will return the name
+      of the variant Mapping currently in use with the current Frame. If
+      the Frame has no variant Mappings, the value will default to the
+      Domain name of the current Frame.
+
+      Clearing the \texttt{"} Variant\texttt{"}  attribute will have the effect of removing
+      all variant Mappings (except for the currently selected Mapping) from
+      the current Frame.
+
+      Testing the \texttt{"} Variant\texttt{"}  attribute will return
+      .TRUE.
+      if the current Frame contains any variant Mappings, and
+      .FALSE.
+      otherwise.
+
+      A complete list of the names associated with all the available
+      variant Mappings in the current Frame can be obtained from the
+      \htmlref{AllVariants}{AllVariants} attribute.
+
+      If a Frame with variant Mappings is remapped using the
+      AST\_REMAPFRAME
+      method, the currently selected variant Mapping is used by
+      AST\_REMAPFRAME
+      and the other variant Mappings are removed from the Frame.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FrameSet
+      }{
+         All FrameSets have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Warnings
+}{
+   Controls the issuing of warnings about various conditions
+}{
+   \sstdescription{
+      This attribute controls the issuing of warnings about selected
+      conditions when an \htmlref{Object}{Object} or keyword is read from or written to a
+      \htmlref{FitsChan}{FitsChan}. The value supplied for the Warnings attribute should
+      consist of a space separated list of condition names (see the
+      \htmlref{AllWarnings}{AllWarnings} attribute for a list of the currently defined names).
+      Each name indicates a condition which should be reported. The default
+      value for Warnings is the string \texttt{"} BadKeyName BadKeyValue Tnx Zpx
+      BadCel BadMat BadPV BadCTYPE\texttt{"} .
+
+      The text of any warning will be stored within the FitsChan in the
+      form of one or more new header cards with keyword ASTWARN. If
+      required, applications can check the FitsChan for ASTWARN cards
+      (using \htmlref{AST\_FINDFITS}{AST\_FINDFITS}) after the call to \htmlref{AST\_READ}{AST\_READ} or \htmlref{AST\_WRITE}{AST\_WRITE} has been
+      performed, and report the text of any such cards to the user. ASTWARN
+      cards will be propagated to any output header unless they are
+      deleted from the FitsChan using astDelFits.
+   }
+   \sstattributetype{
+      String
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+   \sstnotes{
+      This attribute only controls the warnings that are to be stored as
+      a set of header cards in the FitsChan as described above. It has no
+      effect on the storage of warnings in the parent \htmlref{Channel}{Channel} structure.
+      All warnings are stored in the parent Channel structure, from where
+      they can be retrieved using the
+      \htmlref{AST\_WARNINGS}{AST\_WARNINGS}
+      function.
+   }
+}
+\sstroutine{
+   WcsAxis(lonlat)
+}{
+   FITS-WCS projection axes
+}{
+   \sstdescription{
+      This attribute gives the indices of the longitude and latitude
+      coordinates of the FITS-WCS projection within the coordinate
+      space used by a \htmlref{WcsMap}{WcsMap}. These indices are defined when the
+      WcsMap is first created using \htmlref{AST\_WCSMAP}{AST\_WCSMAP} and cannot
+      subsequently be altered.
+
+      If \texttt{"} lonlat\texttt{"}  is 1, the index of the longitude axis is
+      returned. Otherwise, if it is 2, the index of the latitude axis
+      is returned.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   WcsType
+}{
+   FITS-WCS projection type
+}{
+   \sstdescription{
+      This attribute specifies which type of FITS-WCS projection will
+      be performed by a \htmlref{WcsMap}{WcsMap}. The value is specified when a WcsMap
+      is first created using \htmlref{AST\_WCSMAP}{AST\_WCSMAP} and cannot subsequently be
+      changed.
+
+      The values used are represented by symbolic constants with names of
+      the form \texttt{"} AST\_\_XXX\texttt{"} , where \texttt{"} XXX\texttt{"}  is the (upper case) 3-character
+      code used by the FITS-WCS \texttt{"} CTYPEi\texttt{"}  keyword to identify the
+      projection. For example, possible values are AST\_\_TAN (for the
+      tangent plane or gnomonic projection) and AST\_\_AIT (for the
+      Hammer-Aitoff projection). AST\_\_TPN is an exception in that it
+      is not part of the FITS-WCS standard (it represents a TAN
+      projection with polynomial correction terms as defined in an early
+      draft of the FITS-WCS paper).
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of available projections, see the FITS-WCS paper.
+      }
+   }
+}
+\sstroutine{
+   Width(element)
+}{
+   Line width for a Plot element
+}{
+   \sstdescription{
+      This attribute determines the line width used when drawing each
+      element of graphical output produced by a \htmlref{Plot}{Plot}.  It takes a
+      separate value for each graphical element so that, for instance,
+      the setting \texttt{"} Width(border)=2.0\texttt{"}  causes the Plot border to be
+      drawn using a line width of 2.0. A value of 1.0 results in a
+      line thickness which is approximately 0.0005 times the length of
+      the diagonal of the entire display surface.
+
+      The actual appearance of lines drawn with any particular width,
+      and the range of available widths, is determined by the
+      underlying graphics system.  The default behaviour is for all
+      graphical elements to be drawn using the default line width
+      supplied by this graphics system. This will not necessarily
+      correspond to a Width value of 1.0.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of the graphical elements available, see the
+         description of the Plot class.
+
+         \sstitem
+         If no graphical element is specified, (e.g. \texttt{"} Width\texttt{"}  instead of
+         \texttt{"} Width(border)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all graphical elements, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the Width(\htmlref{Border}{Border}) value.
+      }
+   }
+}
+\sstroutine{
+   XmlFormat
+}{
+   System for formatting Objects as XML
+}{
+   \sstdescription{
+      This attribute specifies the formatting system to use when AST
+      Objects are written out as XML through an \htmlref{XmlChan}{XmlChan}. It
+      affects the behaviour of the \htmlref{AST\_WRITE}{AST\_WRITE} routine  when
+      they are used to transfer any AST \htmlref{Object}{Object} to or from an external
+      XML representation.
+
+      The XmlChan class allows AST objects to be represented in the form
+      of XML in several ways (conventions) and the XmlFormat attribute is
+      used to specify which of these should be used. The formatting options
+      available are outlined in the \texttt{"} Formats Available\texttt{"}  section below.
+
+      By default, an XmlChan will attempt to determine which format system
+      is already in use, and will set the default XmlFormat value
+      accordingly (so that subsequent I/O operations adopt the same
+      conventions). It does this by looking for certain critical items
+      which only occur in particular formats. For details of how this
+      works, see the \texttt{"} Choice of Default Format\texttt{"}  section below. If you wish
+      to ensure that a particular format system is used, independently of
+      any XML already read, you should set an explicit XmlFormat value
+      yourself.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         XmlChan
+      }{
+         All XmlChans have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Formats Available
+   }{
+      The XmlFormat attribute can take any of the following (case
+      insensitive) string values to select the corresponding formatting
+      system:
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} NATIVE\texttt{"} : This is a direct conversion to XML of the heirarchical
+         format used by a standard XML channel (and also by the NATIVE
+         encoding of a \htmlref{FitsChan}{FitsChan}).
+
+         \sstitem
+         \texttt{"} QUOTED\texttt{"} : This is the same as NATIVE format except that extra
+         information is included which allows client code to convert the
+         XML into a form which can be read by a standard AST \htmlref{Channel}{Channel}. This
+         extra information indicates which AST attribute values should be
+         enclosed in quotes before being passed to a Channel.
+
+         \sstitem
+         \texttt{"} IVOA\texttt{"} : This is a format that uses an early draft of the STC-X schema
+         developed by the International Virtual Observatory Alliance (IVOA -
+         see \texttt{"} http://www.ivoa.net/\texttt{"} ) to describe coordinate systems, regions,
+         mappings, etc. Support is limited to V1.20 described at
+         \texttt{"} http://www.ivoa.net/Documents/WD/STC/STC-20050225.html\texttt{"} . Since the
+         version of STC-X finally adopted by the IVOA differs in several
+         significant respects from V1.20, this format is now mainly of
+         historical interest. Note, the alternative \texttt{"} STC-S\texttt{"}  format (a
+         simpler non-XML encoding of the STC metadata) is supported by the
+         \htmlref{StcsChan}{StcsChan} class.
+      }
+   }
+   \sstdiytopic{
+      Choice of Default Format;
+   }{
+      If the XmlFormat attribute of an XmlChan is not set, the default
+      value it takes is determined by the presence of certain critical
+      items within the document most recently read using
+      \htmlref{AST\_READ}{AST\_READ}.
+      The sequence of decision used to arrive at the default value is as
+      follows:
+
+      \sstitemlist{
+
+         \sstitem
+         If the previous document read contained any elements in any of the STC
+         namespaces (\texttt{"} urn:nvo-stc\texttt{"} , \texttt{"} urn:nvo-coords\texttt{"}  or \texttt{"} urn:nvo-region\texttt{"} ), then
+         the default value is IVOA.
+
+         \sstitem
+         If the previous document read contained any elements in the AST
+         namespace which had an associated XML attribute called \texttt{"} quoted\texttt{"} , then
+         the default value is QUOTED.
+
+         \sstitem
+         Otherwise, if none of these conditions is met (as would be the
+         case if no document had yet been read), then NATIVE format is
+         used.
+
+      }
+      Setting an explicit value for the XmlFormat attribute always
+      over-rides this default behaviour.
+   }
+   \sstdiytopic{
+      The IVOA Format
+   }{
+      The IVOA support caters only for certain parts of V1.20 of the
+      draft Space-Time Coordinate (STC) schema (see
+      http://www.ivoa.net/Documents/WD/STC/STC-20050225.html). Note, this
+      draft has now been superceded by an officially adopted version that
+      differs in several significant respects from V1.20. Consequently,
+      the \texttt{"} IVOA\texttt{"}  XmlChan format is of historical interest only.
+
+      The following points should be noted when using an XmlChan to read
+      or write STC information (note, this list is currently incomplete):
+
+      \sstitemlist{
+
+         \sstitem
+         Objects can currently only be read using this format, not written.
+
+         \sstitem
+         The AST object generated by reading an $<$STCMetadata$>$ element will
+         be an instance of one of the AST \texttt{"} \htmlref{Stc}{Stc}\texttt{"}  classes: \htmlref{StcResourceProfile}{StcResourceProfile},
+         \htmlref{StcSearchLocation}{StcSearchLocation}, \htmlref{StcCatalogEntryLocation}{StcCatalogEntryLocation}, \htmlref{StcObsDataLocation}{StcObsDataLocation}.
+
+         \sstitem
+         When reading an $<$STCMetadata$>$ element, the axes in the returned
+         AST Object will be in the order space, time, spectral, redshift,
+         irrespective of the order in which the axes occur in the $<$STCMetadata$>$
+         element. If the supplied $<$STCMetadata$>$ element does not contain all of
+         these axes, the returned AST Object will also omit them, but the
+         ordering of those axes which are present will be as stated above. If
+         the spatial frame represents a celestial coordinate system the
+         spatial axes will be in the order (longitude, latitude).
+
+         \sstitem
+         Until such time as the AST \htmlref{TimeFrame}{TimeFrame} is complete, a simple
+         1-dimensional \htmlref{Frame}{Frame} (with \htmlref{Domain}{Domain} set to TIME) will be used to
+         represent the STC $<$TimeFrame$>$ element. Consequently, most of the
+         information within a $<$TimeFrame$>$ element is currently ignored.
+
+         \sstitem
+         $<$SpaceFrame$>$ elements can only be read if they describe a celestial
+         longitude and latitude axes supported by the AST \htmlref{SkyFrame}{SkyFrame} class. The
+         space axes will be returned in the order (longitude, latitude).
+
+         \sstitem
+         Velocities associated with SpaceFrames cannot be read.
+
+         \sstitem
+         Any $<$GenericCoordFrame$>$ elements within an $<$AstroCoordSystem$>$ element
+         are currently ignored.
+
+         \sstitem
+         Any second or subsequent $<$AstroCoordSystem$>$ found within an
+         STCMetaData element is ignored.
+
+         \sstitem
+         Any second or subsequent $<$AstroCoordArea$>$ found within an
+         STCMetaData element is ignored.
+
+         \sstitem
+         Any $<$OffsetCenter$>$ found within a $<$SpaceFrame$>$ is ignored.
+
+         \sstitem
+         Any CoordFlavor element found within a $<$SpaceFrame$>$ is ignored.
+
+         \sstitem
+         $<$SpaceFrame$>$ elements can only be read if they refer to
+         one of the following space reference frames: ICRS, GALACTIC\_II,
+         SUPER\_GALACTIC, HEE, FK4, FK5, ECLIPTIC.
+
+         \sstitem
+         $<$SpaceFrame$>$ elements can only be read if the reference
+         position is TOPOCENTER. Also, any planetary ephemeris is ignored.
+
+         \sstitem
+         Regions: there is currently no support for STC regions of type
+         Sector, ConvexHull or SkyIndex.
+
+         \sstitem
+         The AST \htmlref{Region}{Region} read from a CoordInterval element is considered to
+         be open if either the lo\_include or the hi\_include attribute is
+         set to false.
+
+         \sstitem
+         $<$RegionFile$>$ elements are not supported.
+
+         \sstitem
+         Vertices within $<$\htmlref{Polygon}{Polygon}$>$ elements are always considered to be
+         joined using great circles (that is, $<$SmallCircle$>$ elements are
+         ignored).
+      }
+   }
+}
+\sstroutine{
+   XmlLength
+}{
+   Controls output buffer length
+}{
+   \sstdescription{
+      This attribute specifies the maximum length to use when writing out
+      text through the sink function supplied when the \htmlref{XmlChan}{XmlChan} was created.
+
+      The number of characters in each string written out through the sink
+      function will not be greater than the value of this attribute (but
+      may be less). A value of zero (the default) means there is no limit -
+      each string can be of any length.
+
+      Note, the default value of zero is unlikely to be appropriate when
+      an XmlChan is used within Fortran code. In this case, XmlLength
+      should usually be set to the size of the CHARACTER variable used to
+      receive the text returned by \htmlref{AST\_GETLINE}{AST\_GETLINE} within the sink function.
+      This avoids the possibility of long lines being truncated invisibly
+      within AST\_GETLINE.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         XmlChan
+      }{
+         All XmlChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   XmlPrefix
+}{
+   The namespace prefix to use when writing
+}{
+   \sstdescription{
+      This attribute is a string which is to be used as the namespace
+      prefix for all XML elements created as a result of writing an AST
+      \htmlref{Object}{Object} out through an \htmlref{XmlChan}{XmlChan}. The URI associated with the namespace
+      prefix is given by the symbolic constant AST\_\_XMLNS defined in
+      AST\_PAR.
+      A definition of the namespace prefix is included in each top-level
+      element produced by the XmlChan.
+
+      The default value is a blank string which causes no prefix to be
+      used. In this case each top-level element will set the default
+      namespace to be the value of AST\_\_XMLNS.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Zoom
+}{
+   ZoomMap scale factor
+}{
+   \sstdescription{
+      This attribute holds the \htmlref{ZoomMap}{ZoomMap} scale factor, by which
+      coordinate values are multiplied (by the forward transformation)
+      or divided (by the inverse transformation). The default value
+      is unity.
+
+      Note that if a ZoomMap is inverted (e.g. by using \htmlref{AST\_INVERT}{AST\_INVERT}),
+      then the reciprocal of this zoom factor will, in effect, be
+      used.
+
+      In general, \htmlref{Mapping}{Mapping} attributes cannot be changed after the Mapping
+      has been created (the exception to this is the \htmlref{Invert}{Invert} attribute,
+      which can be changed at any time). However, several of the oldest
+      Mapping classes - including the ZoomMap class - were introduced
+      into the AST library before this restriction was enforced. To
+      reduce the chances of breaking existing software, the attributes of
+      such Mappings may still be changed, but only for Mapping instances
+      that have exactly one active reference. In other words, an error will
+      be reported if an attempt is made to set or clear an attribute of a
+      Mapping (other than the Invert attribute) if that Mapping has been
+      cloned. Mappings are cloned when they are incorporated into another
+      object such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet}, or when the
+      \htmlref{AST\_CLONE}{AST\_CLONE}
+      function is used.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         ZoomMap
+      }{
+         All ZoomMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Zoom attribute may not be set to zero.
+      }
+   }
+}
+\normalsize
+
+\cleardoublepage
+\section{\label{ss:classdescriptions}AST Class Descriptions}
+\small
+\sstroutine{
+   Axis
+}{
+   Store axis information
+}{
+   \sstdescription{
+      The Axis class is used to store information associated with a
+      particular axis of a \htmlref{Frame}{Frame}. It is used internally by the AST
+      library and has no constructor function. You should encounter it
+      only within textual output (e.g. from \htmlref{AST\_WRITE}{AST\_WRITE}).
+   }
+   \sstconstructor{
+      None.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Axis class inherits from the \htmlref{Object}{Object} class.
+   }
+}
+\sstroutine{
+   Box
+}{
+   A box region with sides parallel to the axes of a Frame
+}{
+   \sstdescription{
+      The Box class implements a \htmlref{Region}{Region} which represents a box with sides
+      parallel to the axes of a \htmlref{Frame}{Frame} (i.e. an area which encloses a given
+      range of values on each axis). A Box is similar to an \htmlref{Interval}{Interval}, the
+      only real difference being that the Interval class allows some axis
+      limits to be unspecified. Note, a Box will only look like a box if
+      the Frame geometry is approximately flat. For instance, a Box centred
+      close to a pole in a \htmlref{SkyFrame}{SkyFrame} will look more like a fan than a box
+      (the \htmlref{Polygon}{Polygon} class can be used to create a box-like region close to a
+      pole).
+   }
+   \sstconstructor{
+      \htmlref{AST\_BOX}{AST\_BOX}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Box class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The Box class does not define any new attributes beyond
+      those which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The Box class does not define any new routines beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   Channel
+}{
+   Basic (textual) I/O channel
+}{
+   \sstdescription{
+      The Channel class implements low-level input/output for the AST
+      library.  Writing an \htmlref{Object}{Object} to a Channel will generate a textual
+      representation of that Object, and reading from a Channel will
+      create a new Object from its textual representation.
+
+      Normally, when you use a Channel, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  routines which connect it to an external data store
+      by reading and writing the resulting text. By default, however,
+      a Channel will read from standard input and write to standard
+      output. Alternatively, a Channel can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+   }
+   \sstconstructor{
+      \htmlref{AST\_CHANNEL}{AST\_CHANNEL}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Channel class inherits from the Object class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Objects, every
+      Channel also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Comment}{Comment}: Include textual comments in output?
+
+         \sstitem
+         \htmlref{Full}{Full}: Set level of output detail
+
+         \sstitem
+         \htmlref{Indent}{Indent}: Indentation increment between objects
+
+         \sstitem
+         \htmlref{ReportLevel}{ReportLevel}: Selects the level of error reporting
+
+         \sstitem
+         \htmlref{SinkFile}{SinkFile}: The path to a file to which the Channel should write
+
+         \sstitem
+         \htmlref{Skip}{Skip}: Skip irrelevant data?
+
+         \sstitem
+         \htmlref{SourceFile}{SourceFile}: The path to a file from which the Channel should read
+
+         \sstitem
+         \htmlref{Strict}{Strict}: Generate errors instead of warnings?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Objects, the
+      following routines may also be applied to all Channels:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_WARNINGS}{AST\_WARNINGS}: Return warnings from the previous read or write
+
+         \sstitem
+         \htmlref{AST\_READ}{AST\_READ}: Read an Object from a Channel
+
+         \sstitem
+         \htmlref{AST\_WRITE}{AST\_WRITE}: Write an Object to a Channel
+      }
+   }
+}
+\sstroutine{
+   Circle
+}{
+   A circular or spherical region within a Frame
+}{
+   \sstdescription{
+      The Circle class implements a \htmlref{Region}{Region} which represents a circle or
+      sphere within a \htmlref{Frame}{Frame}.
+   }
+   \sstconstructor{
+      \htmlref{AST\_CIRCLE}{AST\_CIRCLE}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Circle class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The Circle class does not define any new attributes beyond
+      those which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Regions, the
+      following routines may also be applied to all Circles:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_CIRCLEPARS}{AST\_CIRCLEPARS}: Get the geometric parameters of the Circle
+      }
+   }
+}
+\sstroutine{
+   CmpFrame
+}{
+   Compound Frame
+}{
+   \sstdescription{
+      A CmpFrame is a compound \htmlref{Frame}{Frame} which allows two component Frames
+      (of any class) to be merged together to form a more complex
+      Frame. The axes of the two component Frames then appear together
+      in the resulting CmpFrame (those of the first Frame, followed by
+      those of the second Frame).
+
+      Since a CmpFrame is itself a Frame, it can be used as a
+      component in forming further CmpFrames. Frames of arbitrary
+      complexity may be built from simple individual Frames in this
+      way.
+
+      Also since a Frame is a \htmlref{Mapping}{Mapping}, a CmpFrame can also be used as a
+      Mapping. Normally, a CmpFrame is simply equivalent to a \htmlref{UnitMap}{UnitMap},
+      but if either of the component Frames within a CmpFrame is a \htmlref{Region}{Region}
+      (a sub-class of Frame), then the CmpFrame will use the Region as a
+      Mapping when transforming values for axes described by the Region.
+      Thus input axis values corresponding to positions which are outside the
+      Region will result in bad output axis values.
+   }
+   \sstconstructor{
+      \htmlref{AST\_CMPFRAME}{AST\_CMPFRAME}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The CmpFrame class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The CmpFrame class does not define any new attributes beyond
+      those which are applicable to all Frames. However, the attributes
+      of the component Frames can be accessed as if they were attributes
+      of the CmpFrame. For instance, if a CmpFrame contains a \htmlref{SpecFrame}{SpecFrame}
+      and a \htmlref{SkyFrame}{SkyFrame}, then the CmpFrame will recognise the \texttt{"} \htmlref{Equinox}{Equinox}\texttt{"}
+      attribute and forward access requests to the component SkyFrame.
+      Likewise, it will recognise the \texttt{"} \htmlref{RestFreq}{RestFreq}\texttt{"}  attribute and forward
+      access requests to the component SpecFrame. An axis index can
+      optionally be appended to the end of any attribute name, in which
+      case the request to access the attribute will be forwarded to the
+      primary Frame defining the specified axis.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The CmpFrame class does not define any new routines beyond those
+      which are applicable to all Frames.
+   }
+}
+\sstroutine{
+   CmpMap
+}{
+   Compound Mapping
+}{
+   \sstdescription{
+      A CmpMap is a compound \htmlref{Mapping}{Mapping} which allows two component
+      Mappings (of any class) to be connected together to form a more
+      complex Mapping. This connection may either be \texttt{"} in series\texttt{"}
+      (where the first Mapping is used to transform the coordinates of
+      each point and the second mapping is then applied to the
+      result), or \texttt{"} in parallel\texttt{"}  (where one Mapping transforms the
+      earlier coordinates for each point and the second Mapping
+      simultaneously transforms the later coordinates).
+
+      Since a CmpMap is itself a Mapping, it can be used as a
+      component in forming further CmpMaps. Mappings of arbitrary
+      complexity may be built from simple individual Mappings in this
+      way.
+   }
+   \sstconstructor{
+      \htmlref{AST\_CMPMAP}{AST\_CMPMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The CmpMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The CmpMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The CmpMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   CmpRegion
+}{
+   A combination of two regions within a single Frame
+}{
+   \sstdescription{
+      A CmpRegion is a \htmlref{Region}{Region} which allows two component
+      Regions (of any class) to be combined to form a more complex
+      Region. This combination may be performed a boolean AND, OR
+      or XOR (exclusive OR) operator. If the AND operator is
+      used, then a position is inside the CmpRegion only if it is
+      inside both of its two component Regions. If the OR operator is
+      used, then a position is inside the CmpRegion if it is inside
+      either (or both) of its two component Regions. If the XOR operator
+      is used, then a position is inside the CmpRegion if it is inside
+      one but not both of its two component Regions. Other operators can
+      be formed by negating one or both component Regions before using
+      them to construct a new CmpRegion.
+
+      The two component Region need not refer to the same coordinate
+      \htmlref{Frame}{Frame}, but it must be possible for the
+      \htmlref{AST\_CONVERT}{AST\_CONVERT}
+      function to determine a \htmlref{Mapping}{Mapping} between them (an error will be
+      reported otherwise when the CmpRegion is created). For instance,
+      a CmpRegion may combine a Region defined within an ICRS \htmlref{SkyFrame}{SkyFrame}
+      with a Region defined within a Galactic SkyFrame. This is
+      acceptable because the SkyFrame class knows how to convert between
+      these two systems, and consequently the
+      AST\_CONVERT
+      function will also be able to convert between them. In such cases,
+      the second component Region will be mapped into the coordinate Frame
+      of the first component Region, and the Frame represented by the
+      CmpRegion as a whole will be the Frame of the first component Region.
+
+      Since a CmpRegion is itself a Region, it can be used as a
+      component in forming further CmpRegions. Regions of arbitrary
+      complexity may be built from simple individual Regions in this
+      way.
+   }
+   \sstconstructor{
+      \htmlref{AST\_CMPREGION}{AST\_CMPREGION}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The CmpRegion class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The CmpRegion class does not define any new attributes beyond those
+      which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The CmpRegion class does not define any new routines beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   DSBSpecFrame
+}{
+   Dual sideband spectral coordinate system description
+}{
+   \sstdescription{
+      A DSBSpecFrame is a specialised form of \htmlref{SpecFrame}{SpecFrame} which represents
+      positions in a spectrum obtained using a dual sideband instrument.
+      Such an instrument produces a spectrum in which each point contains
+      contributions from two distinctly different frequencies, one from
+      the \texttt{"} lower side band\texttt{"}  (LSB) and one from the \texttt{"} upper side band\texttt{"}  (USB).
+      Corresponding LSB and USB frequencies are connected by the fact
+      that they are an equal distance on either side of a fixed central
+      frequency known as the \texttt{"} Local Oscillator\texttt{"}  (LO) frequency.
+
+      When quoting a position within such a spectrum, it is necessary to
+      indicate whether the quoted position is the USB position or the
+      corresponding LSB position. The \htmlref{SideBand}{SideBand} attribute provides this
+      indication. Another option that the SideBand attribute provides is
+      to represent a spectral position by its topocentric offset from the
+      LO frequency.
+
+      In practice, the LO frequency is specified by giving the distance
+      from the LO frequency to some \texttt{"} central\texttt{"}  spectral position. Typically
+      this central position is that of some interesting spectral feature.
+      The distance from this central position to the LO frequency is known
+      as the \texttt{"} intermediate frequency\texttt{"}  (\htmlref{IF}{IF}). The value supplied for IF can
+      be a signed value in order to indicate whether the LO frequency is
+      above or below the central position.
+   }
+   \sstconstructor{
+      \htmlref{AST\_DSBSPECFRAME}{AST\_DSBSPECFRAME}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The DSBSpecFrame class inherits from the SpecFrame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all SpecFrames, every
+      DSBSpecFrame also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AlignSideBand}{AlignSideBand}: Should alignment occur between sidebands?
+
+         \sstitem
+         \htmlref{DSBCentre}{DSBCentre}: The central position of interest.
+
+         \sstitem
+         \htmlref{IF}{IF}: The intermediate frequency used to define the LO frequency.
+
+         \sstitem
+         \htmlref{ImagFreq}{ImagFreq}: The image sideband equivalent of the rest frequency.
+
+         \sstitem
+         \htmlref{SideBand}{SideBand}: Indicates which sideband the DSBSpecFrame represents.
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The DSBSpecFrame class does not define any new routines beyond those
+      which are applicable to all SpecFrames.
+   }
+}
+\sstroutine{
+   DssMap
+}{
+   Map points using a Digitised Sky Survey plate solution
+}{
+   \sstdescription{
+      The DssMap class implements a \htmlref{Mapping}{Mapping} which transforms between
+      2-dimensional pixel coordinates and an equatorial sky coordinate
+      system (right ascension and declination) using a Digitised Sky
+      Survey (DSS) astrometric plate solution.
+
+      The input coordinates are pixel numbers along the first and
+      second dimensions of an image, where the centre of the first
+      pixel is located at (1,1) and the spacing between pixel centres
+      is unity.
+
+      The output coordinates are right ascension and declination in
+      radians. The celestial coordinate system used (FK4, FK5, etc.)
+      is unspecified, and will usually be indicated by appropriate
+      keywords in a FITS header.
+   }
+   \sstconstructor{
+      The DssMap class does not have a constructor function.  A DssMap
+      is created only as a by-product of reading a \htmlref{FrameSet}{FrameSet} (using
+      \htmlref{AST\_READ}{AST\_READ}) from a \htmlref{FitsChan}{FitsChan} which contains FITS header cards
+      describing a DSS plate solution, and whose \htmlref{Encoding}{Encoding} attribute is
+      set to \texttt{"} DSS\texttt{"} . The result of such a read, if successful, is a
+      FrameSet whose base and current Frames are related by a DssMap.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The DssMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The DssMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The DssMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Ellipse
+}{
+   An elliptical region within a 2-dimensional Frame
+}{
+   \sstdescription{
+      The Ellipse class implements a \htmlref{Region}{Region} which represents a ellipse
+      within a 2-dimensional \htmlref{Frame}{Frame}.
+   }
+   \sstconstructor{
+      \htmlref{AST\_ELLIPSE}{AST\_ELLIPSE}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Ellipse class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The Ellipse class does not define any new attributes beyond
+      those which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Regions, the
+      following routines may also be applied to all Ellipses:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_ELLIPSEPARS}{AST\_ELLIPSEPARS}: Get the geometric parameters of the Ellipse
+      }
+   }
+}
+\sstroutine{
+   FitsChan
+}{
+   I/O Channel using FITS header cards to represent Objects
+}{
+   \sstdescription{
+      A FitsChan is a specialised form of \htmlref{Channel}{Channel} which supports I/O
+      operations involving the use of FITS (Flexible Image Transport
+      \htmlref{System}{System}) header cards. Writing an \htmlref{Object}{Object} to a FitsChan (using
+      \htmlref{AST\_WRITE}{AST\_WRITE}) will, if the Object is suitable, generate a
+      description of that Object composed of FITS header cards, and
+      reading from a FitsChan will create a new Object from its FITS
+      header card description.
+
+      While a FitsChan is active, it represents a buffer which may
+      contain zero or more 80-character \texttt{"} header cards\texttt{"}  conforming to
+      FITS conventions. Any sequence of FITS-conforming header cards
+      may be stored, apart from the \texttt{"} END\texttt{"}  card whose existence is
+      merely implied.  The cards may be accessed in any order by using
+      the FitsChan\texttt{'} s integer \htmlref{Card}{Card} attribute, which identifies a \texttt{"} current\texttt{"}
+      card, to which subsequent operations apply. Searches
+      based on keyword may be performed (using \htmlref{AST\_FINDFITS}{AST\_FINDFITS}), new
+      cards may be inserted (\htmlref{AST\_PUTFITS}{AST\_PUTFITS}, \htmlref{AST\_PUTCARDS}{AST\_PUTCARDS}, \htmlref{AST\_SETFITS$<$X$>$}{AST\_SETFITS$<$X$>$}) and
+      existing ones may be deleted (\htmlref{AST\_DELFITS}{AST\_DELFITS}), extracted
+      (\htmlref{AST\_GETFITS$<$X$>$}{AST\_GETFITS$<$X$>$}) or changed (AST\_SETFITS$<$X$>$).
+
+      When you create a FitsChan, you have the option of specifying
+      \texttt{"} source\texttt{"}  and \texttt{"} sink\texttt{"}  functions which connect it to external data
+      stores by reading and writing FITS header cards. If you provide
+      a source function, it is used to fill the FitsChan with header cards
+      when it is accessed for the first time. If you do not provide a
+      source function, the FitsChan remains empty until you explicitly enter
+      data into it (e.g. using AST\_PUTFITS, AST\_PUTCARDS, AST\_WRITE
+      or by using the \htmlref{SourceFile}{SourceFile} attribute to specifying a text file from
+      which headers should be read). When the FitsChan is deleted, any
+      remaining header cards in the FitsChan can be saved in either of
+      two ways: 1) by specifying a value for the \htmlref{SinkFile}{SinkFile} attribute (the
+      name of a text file to which header cards should be written), or 2)
+      by providing a sink function (used to to deliver header cards to an
+      external data store). If you do not provide a sink function or a
+      value for SinkFile, any header cards remaining when the FitsChan
+      is deleted will be lost, so you should arrange to extract them
+      first if necessary
+      (e.g. using AST\_FINDFITS or \htmlref{AST\_READ}{AST\_READ}).
+
+      Coordinate system information may be described using FITS header
+      cards using several different conventions, termed
+      \texttt{"} encodings\texttt{"} . When an AST Object is written to (or read from) a
+      FitsChan, the value of the FitsChan\texttt{'} s \htmlref{Encoding}{Encoding} attribute
+      determines how the Object is converted to (or from) a
+      description involving FITS header cards. In general, different
+      encodings will result in different sets of header cards to
+      describe the same Object. Examples of encodings include the DSS
+      encoding (based on conventions used by the STScI Digitised Sky
+      Survey data), the FITS-WCS encoding (based on a proposed FITS
+      standard) and the NATIVE encoding (a near loss-less way of
+      storing AST Objects in FITS headers).
+
+      The available encodings differ in the range of Objects they can
+      represent, in the number of Object descriptions that can coexist
+      in the same FitsChan, and in their accessibility to other
+      (external) astronomy applications (see the Encoding attribute
+      for details). Encodings are not necessarily mutually exclusive
+      and it may sometimes be possible to describe the same Object in
+      several ways within a particular set of FITS header cards by
+      using several different encodings.
+
+      The detailed behaviour of AST\_READ and AST\_WRITE, when used with
+      a FitsChan, depends on the encoding in use. In general, however,
+      all successful use of AST\_READ is destructive, so that FITS header cards
+      are consumed in the process of reading an Object, and are
+      removed from the FitsChan (this deletion can be prevented for
+      specific cards by calling the
+      \htmlref{AST\_RETAINFITS}{AST\_RETAINFITS} routine).
+      An unsuccessful call of
+      AST\_READ
+      (for instance, caused by the FitsChan not containing the necessary
+      FITS headers cards needed to create an Object) results in the
+      contents of the FitsChan being left unchanged.
+
+      If the encoding in use allows only a single Object description
+      to be stored in a FitsChan (e.g. the DSS, FITS-WCS and FITS-IRAF
+      encodings), then write operations using AST\_WRITE will
+      over-write any existing Object description using that
+      encoding. Otherwise (e.g. the NATIVE encoding), multiple Object
+      descriptions are written sequentially and may later be read
+      back in the same sequence.
+   }
+   \sstconstructor{
+      \htmlref{AST\_FITSCHAN}{AST\_FITSCHAN}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The FitsChan class inherits from the Channel class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Channels, every
+
+      FitsChan also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AllWarnings}{AllWarnings}: A list of the available conditions
+
+         \sstitem
+         \htmlref{Card}{Card}: Index of current FITS card in a FitsChan
+
+         \sstitem
+         \htmlref{CardComm}{CardComm}: The comment of the current FITS card in a FitsChan
+
+         \sstitem
+         \htmlref{CardName}{CardName}: The keyword name of the current FITS card in a FitsChan
+
+         \sstitem
+         \htmlref{CardType}{CardType}: The data type of the current FITS card in a FitsChan
+
+         \sstitem
+         \htmlref{CarLin}{CarLin}: Ignore spherical rotations on CAR projections?
+
+         \sstitem
+         \htmlref{CDMatrix}{CDMatrix}: Use a CD matrix instead of a PC matrix?
+
+         \sstitem
+         \htmlref{Clean}{Clean}: Remove cards used whilst reading even if an error occurs?
+
+         \sstitem
+         \htmlref{DefB1950}{DefB1950}: Use FK4 B1950 as default equatorial coordinates?
+
+         \sstitem
+         \htmlref{Encoding}{Encoding}: System for encoding Objects as FITS headers
+
+         \sstitem
+         \htmlref{FitsAxisOrder}{FitsAxisOrder}: Sets the order of WCS axes within new FITS-WCS headers
+
+         \sstitem
+         \htmlref{FitsDigits}{FitsDigits}: Digits of precision for floating-point FITS values
+
+         \sstitem
+         \htmlref{Iwc}{Iwc}: Add a \htmlref{Frame}{Frame} describing Intermediate World Coords?
+
+         \sstitem
+         \htmlref{Ncard}{Ncard}: Number of FITS header cards in a FitsChan
+
+         \sstitem
+         \htmlref{Nkey}{Nkey}: Number of unique keywords in a FitsChan
+
+         \sstitem
+         \htmlref{TabOK}{TabOK}: Should the FITS \texttt{"} -TAB\texttt{"}  algorithm be recognised?
+
+         \sstitem
+         \htmlref{PolyTan}{PolyTan}: Use \htmlref{PVi\_m}{PVi\_m} keywords to define distorted TAN projection?
+
+         \sstitem
+         \htmlref{Warnings}{Warnings}: Produces warnings about selected conditions
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Channels, the
+      following routines may also be applied to all FitsChans:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_DELFITS}{AST\_DELFITS}: Delete the current FITS card in a FitsChan
+
+         \sstitem
+         \htmlref{AST\_EMPTYFITS}{AST\_EMPTYFITS}: Delete all cards in a FitsChan
+
+         \sstitem
+         \htmlref{AST\_FINDFITS}{AST\_FINDFITS}: Find a FITS card in a FitsChan by keyword
+
+         \sstitem
+         \htmlref{AST\_GETFITS$<$X$>$}{AST\_GETFITS$<$X$>$}: Get a keyword value from a FitsChan
+
+         \sstitem
+         \htmlref{AST\_GETTABLES}{AST\_GETTABLES}: Retrieve any FitsTables from a FitsChan
+
+         \sstitem
+         \htmlref{AST\_PURGEWCS}{AST\_PURGEWCS}: Delete all WCS-related cards in a FitsChan
+
+         \sstitem
+         \htmlref{AST\_PUTCARDS}{AST\_PUTCARDS}: Stores a set of FITS header card in a FitsChan
+
+         \sstitem
+         \htmlref{AST\_PUTFITS}{AST\_PUTFITS}: Store a FITS header card in a FitsChan
+
+         \sstitem
+         \htmlref{AST\_PUTTABLE}{AST\_PUTTABLE}: Store a single FitsTables in a FitsChan
+
+         \sstitem
+         \htmlref{AST\_PUTTABLES}{AST\_PUTTABLES}: Store multiple FitsTables in a FitsChan
+
+         \sstitem
+         \htmlref{AST\_READFITS}{AST\_READFITS}: Read cards in through the source function
+
+         \sstitem
+         \htmlref{AST\_REMOVETABLES}{AST\_REMOVETABLES}: Remove one or more FitsTables from a FitsChan
+
+         \sstitem
+         \htmlref{AST\_RETAINFITS}{AST\_RETAINFITS}: Ensure current card is retained in a FitsChan
+
+         \sstitem
+         \htmlref{AST\_SETFITS$<$X$>$}{AST\_SETFITS$<$X$>$}: Store a new keyword value in a FitsChan
+
+         \sstitem
+         \htmlref{AST\_TABLESOURCE}{AST\_TABLESOURCE}: Register a source function for FITS table access
+
+         \sstitem
+         \htmlref{AST\_TESTFITS}{AST\_TESTFITS}: Test if a keyword has a defined value in a FitsChan
+
+         \sstitem
+         \htmlref{AST\_WRITEFITS}{AST\_WRITEFITS}: Write all cards out to the sink function
+      }
+   }
+}
+\sstroutine{
+   FitsTable
+}{
+   A representation of a FITS binary table
+}{
+   \sstdescription{
+      The FitsTable class is a representation of a FITS binary table. It
+      inherits from the \htmlref{Table}{Table} class. The parent Table is used to hold the
+      binary data of the main table, and a \htmlref{FitsChan}{FitsChan} (encapsulated within
+      the FitsTable) is used to hold the FITS header.
+
+      Note - it is not recommended to use the FitsTable class to store
+      very large tables.
+
+      FitsTables are primarily geared towards the needs of the \texttt{"} -TAB\texttt{"}
+      algorithm defined in FITS-WCS paper 2, and so do not support all
+      features of FITS binary tables. In particularly, they do not
+      provide any equivalent to the following features of FITS binary
+      tables: \texttt{"} heap\texttt{"}  data (i.e. binary data following the main table),
+      columns holding complex values, columns holding variable length
+      arrays, scaled columns, column formats, columns holding bit values,
+      8-byte integer values or logical values.
+   }
+   \sstconstructor{
+      \htmlref{AST\_FITSTABLE}{AST\_FITSTABLE}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The FitsTable class inherits from the Table class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The FitsTable class does not define any new attributes beyond
+      those which are applicable to all Tables.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Tables, the
+      following routines may also be applied to all FitsTables:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_COLUMNNULL}{AST\_COLUMNNULL}: Get/set the null value for a column of a FitsTable
+
+         \sstitem
+         \htmlref{AST\_COLUMNSIZE}{AST\_COLUMNSIZE}: Get number of bytes needed to hold a full column of data
+
+         \sstitem
+         \htmlref{AST\_GETCOLUMNDATA}{AST\_GETCOLUMNDATA}: Retrieve all the data values stored in a column
+
+         \sstitem
+         AST\_GETTABLEHEADER: Get the FITS headers from a FitsTable
+
+         \sstitem
+         \htmlref{AST\_PUTCOLUMNDATA}{AST\_PUTCOLUMNDATA}: Store data values in a column
+
+         \sstitem
+         \htmlref{AST\_PUTTABLEHEADER}{AST\_PUTTABLEHEADER}: Store FITS headers within a FitsTable
+      }
+   }
+}
+\sstroutine{
+   FluxFrame
+}{
+   Measured flux description
+}{
+   \sstdescription{
+      A FluxFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various systems used to represent the signal level in an
+      observation. The particular coordinate system to be used is specified
+      by setting the FluxFrame\texttt{'} s \htmlref{System}{System} attribute qualified, as necessary, by
+      other attributes such as the units, etc (see the description of the
+      System attribute for details).
+
+      All flux values are assumed to be measured at the same frequency or
+      wavelength (as given by the \htmlref{SpecVal}{SpecVal} attribute). Thus this class is
+      more appropriate for use with images rather than spectra.
+   }
+   \sstconstructor{
+      \htmlref{AST\_FLUXFRAME}{AST\_FLUXFRAME}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The FluxFrame class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      FluxFrame also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{SpecVal}{SpecVal}: The spectral position at which the flux values are measured.
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The FluxFrame class does not define any new routines beyond those
+      which are applicable to all Frames.
+   }
+}
+\sstroutine{
+   Frame
+}{
+   Coordinate system description
+}{
+   \sstdescription{
+      This class is used to represent coordinate systems. It does this
+      in rather the same way that a frame around a graph describes the
+      coordinate space in which data are plotted. Consequently, a
+      Frame has a \htmlref{Title}{Title} (string) attribute, which describes the
+      coordinate space, and contains axes which in turn hold
+      information such as Label and Units strings which are used for
+      labelling (e.g.) graphical output. In general, however, the
+      number of axes is not restricted to two.
+
+      Functions are available for converting Frame coordinate values
+      into a form suitable for display, and also for calculating
+      distances and offsets between positions within the Frame.
+
+      Frames may also contain knowledge of how to transform to and
+      from related coordinate systems.
+   }
+   \sstconstructor{
+      \htmlref{AST\_FRAME}{AST\_FRAME}
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When used as a \htmlref{Mapping}{Mapping}, a Frame implements a unit (null)
+         transformation in both the forward and inverse directions
+         (equivalent to a \htmlref{UnitMap}{UnitMap}). The \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attribute values are
+         both equal to the number of Frame axes.
+      }
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Frame class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      Frame also has the following attributes (if the Frame has only one
+      axis, the axis specifier can be omited from the following attribute
+      names):
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AlignSystem}{AlignSystem}: Coordinate system used to align Frames
+
+         \sstitem
+         \htmlref{Bottom(axis)}{Bottom(axis)}: Lowest axis value to display
+
+         \sstitem
+         \htmlref{Digits/Digits(axis)}{Digits/Digits(axis)}: Number of digits of precision
+
+         \sstitem
+         \htmlref{Direction(axis)}{Direction(axis)}: Display axis in conventional direction?
+
+         \sstitem
+         \htmlref{Domain}{Domain}: Coordinate system domain
+
+         \sstitem
+         \htmlref{Dut1}{Dut1}: Difference between the UT1 and UTC timescale
+
+         \sstitem
+         \htmlref{Epoch}{Epoch}: Epoch of observation
+
+         \sstitem
+         \htmlref{Format(axis)}{Format(axis)}: Format specification for axis values
+
+         \sstitem
+         \htmlref{InternalUnit(axis)}{InternalUnit(axis)}: Physical units for unformated axis values
+
+         \sstitem
+         \htmlref{Label(axis)}{Label(axis)}: \htmlref{Axis}{Axis} label
+
+         \sstitem
+         \htmlref{MatchEnd}{MatchEnd}: Match trailing axes?
+
+         \sstitem
+         \htmlref{MaxAxes}{MaxAxes}: Maximum number of Frame axes to match
+
+         \sstitem
+         \htmlref{MinAxes}{MinAxes}: Minimum number of Frame axes to match
+
+         \sstitem
+         \htmlref{Naxes}{Naxes}: Number of Frame axes
+
+         \sstitem
+         \htmlref{NormUnit(axis)}{NormUnit(axis)}: Normalised physical units for formatted axis values
+
+         \sstitem
+         \htmlref{ObsAlt}{ObsAlt}: Geodetic altitude of observer
+
+         \sstitem
+         \htmlref{ObsLat}{ObsLat}: Geodetic latitude of observer
+
+         \sstitem
+         \htmlref{ObsLon}{ObsLon}: Geodetic longitude of observer
+
+         \sstitem
+         \htmlref{Permute}{Permute}: Permute axis order?
+
+         \sstitem
+         \htmlref{PreserveAxes}{PreserveAxes}: Preserve axes?
+
+         \sstitem
+         \htmlref{Symbol(axis)}{Symbol(axis)}: Axis symbol
+
+         \sstitem
+         \htmlref{System}{System}: Coordinate system used to describe the domain
+
+         \sstitem
+         \htmlref{Title}{Title}: Frame title
+
+         \sstitem
+         \htmlref{Top(axis)}{Top(axis)}: Highest axis value to display
+
+         \sstitem
+         \htmlref{Unit(axis)}{Unit(axis)}: Physical units for formatted axis values
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Mappings, the
+      following routines may also be applied to all Frames:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_ANGLE}{AST\_ANGLE}: Find the angle subtended by two points at a third point
+
+         \sstitem
+         \htmlref{AST\_AXANGLE}{AST\_AXANGLE}: Find the angle from an axis, to a line through two points
+
+         \sstitem
+         \htmlref{AST\_AXDISTANCE}{AST\_AXDISTANCE}: Calculate the distance between two axis values
+
+         \sstitem
+         \htmlref{AST\_AXNORM}{AST\_AXNORM}: Normalises an array of axis values
+
+         \sstitem
+         \htmlref{AST\_AXOFFSET}{AST\_AXOFFSET}: Calculate an offset along an axis
+
+         \sstitem
+         \htmlref{AST\_CONVERT}{AST\_CONVERT}: Determine how to convert between two coordinate systems
+
+         \sstitem
+         \htmlref{AST\_DISTANCE}{AST\_DISTANCE}: Calculate the distance between two points in a Frame
+
+         \sstitem
+         \htmlref{AST\_FINDFRAME}{AST\_FINDFRAME}: Find a coordinate system with specified characteristics
+
+         \sstitem
+         \htmlref{AST\_FORMAT}{AST\_FORMAT}: Format a coordinate value for a Frame axis
+
+         \sstitem
+         \htmlref{AST\_GETACTIVEUNIT}{AST\_GETACTIVEUNIT}: Determines how the Unit attribute will be used
+
+         \sstitem
+         \htmlref{AST\_INTERSECT}{AST\_INTERSECT}: Find the intersection between two geodesic curves
+
+         \sstitem
+         \htmlref{AST\_MATCHAXES}{AST\_MATCHAXES}: Find any corresponding axes in two Frames
+
+         \sstitem
+         \htmlref{AST\_NORM}{AST\_NORM}: Normalise a set of Frame coordinates
+
+         \sstitem
+         \htmlref{AST\_OFFSET}{AST\_OFFSET}: Calculate an offset along a geodesic curve
+
+         \sstitem
+         \htmlref{AST\_OFFSET2}{AST\_OFFSET2}: Calculate an offset along a geodesic curve in a 2D Frame
+
+         \sstitem
+         \htmlref{AST\_PERMAXES}{AST\_PERMAXES}: Permute the order of a Frame\texttt{'} s axes
+
+         \sstitem
+         \htmlref{AST\_PICKAXES}{AST\_PICKAXES}: Create a new Frame by picking axes from an existing one
+
+         \sstitem
+         \htmlref{AST\_RESOLVE}{AST\_RESOLVE}: Resolve a vector into two orthogonal components
+
+         \sstitem
+         \htmlref{AST\_SETACTIVEUNIT}{AST\_SETACTIVEUNIT}: Specify how the Unit attribute should be used
+
+         \sstitem
+         \htmlref{AST\_UNFORMAT}{AST\_UNFORMAT}: Read a formatted coordinate value for a Frame axis
+      }
+   }
+}
+\sstroutine{
+   FrameSet
+}{
+   Set of inter-related coordinate systems
+}{
+   \sstdescription{
+      A FrameSet consists of a set of one or more Frames (which
+      describe coordinate systems), connected together by Mappings
+      (which describe how the coordinate systems are inter-related). A
+      FrameSet makes it possible to obtain a \htmlref{Mapping}{Mapping} between any pair
+      of these Frames (i.e. to convert between any of the coordinate
+      systems which it describes).  The individual Frames are
+      identified within the FrameSet by an integer index, with Frames
+      being numbered consecutively from one as they are added to the
+      FrameSet.
+
+      Every FrameSet has a \texttt{"} base\texttt{"}  \htmlref{Frame}{Frame} and a \texttt{"} current\texttt{"}  Frame (which
+      are allowed to be the same). Any of the Frames may be nominated
+      to hold these positions, and the choice is determined by the
+      values of the FrameSet\texttt{'} s \htmlref{Base}{Base} and \htmlref{Current}{Current} attributes, which hold
+      the indices of the relevant Frames.  By default, the first Frame
+      added to a FrameSet is its base Frame, and the last one added is
+      its current Frame.
+
+      The base Frame describes the \texttt{"} native\texttt{"}  coordinate system of
+      whatever the FrameSet is used to calibrate (e.g. the pixel
+      coordinates of an image) and the current Frame describes the
+      \texttt{"} apparent\texttt{"}  coordinate system in which it should be viewed
+      (e.g. displayed, etc.). Any further Frames represent a library
+      of alternative coordinate systems, which may be selected by
+      making them current.
+
+      When a FrameSet is used in a context that requires a Frame,
+      (e.g. obtaining its \htmlref{Title}{Title} value, or number of axes), the current
+      Frame is used. A FrameSet may therefore be used in place of its
+      current Frame in most situations.
+
+      When a FrameSet is used in a context that requires a Mapping,
+      the Mapping used is the one between its base Frame and its
+      current Frame. Thus, a FrameSet may be used to convert \texttt{"} native\texttt{"}
+      coordinates into \texttt{"} apparent\texttt{"}  ones, and vice versa. Like any
+      Mapping, a FrameSet may also be inverted (see \htmlref{AST\_INVERT}{AST\_INVERT}), which
+      has the effect of interchanging its base and current Frames and
+      hence of reversing the Mapping between them.
+
+      Regions may be added into a FrameSet (since a \htmlref{Region}{Region} is a type of
+      Frame), either explicitly or as components within CmpFrames. In this
+      case the Mapping between a pair of Frames within a FrameSet will
+      include the effects of the clipping produced by any Regions included
+      in the path between the Frames.
+   }
+   \sstconstructor{
+      \htmlref{AST\_FRAMESET}{AST\_FRAMESET}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The FrameSet class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      FrameSet also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AllVariants}{AllVariants}: List of all variant mappings stored with current Frame
+
+         \sstitem
+         \htmlref{Base}{Base}: FrameSet base Frame index
+
+         \sstitem
+         \htmlref{Current}{Current}: FrameSet current Frame index
+
+         \sstitem
+         \htmlref{Nframe}{Nframe}: Number of Frames in a FrameSet
+
+         \sstitem
+         \htmlref{Variant}{Variant}: Name of variant mapping in use by current Frame
+
+      }
+      Every FrameSet also inherits any further attributes that belong
+      to its current Frame, regardless of that Frame\texttt{'} s class. (For
+      example, the \htmlref{Equinox}{Equinox} attribute, defined by the \htmlref{SkyFrame}{SkyFrame} class, is
+      inherited by any FrameSet which has a SkyFrame as its current
+      Frame.) The set of attributes belonging to a FrameSet may therefore
+      change when a new current Frame is selected.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Frames, the
+      following routines may also be applied to all FrameSets:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_ADDFRAME}{AST\_ADDFRAME}: Add a Frame to a FrameSet to define a new coordinate
+         system
+
+         \sstitem
+         \htmlref{AST\_ADDVARIANT}{AST\_ADDVARIANT}: Add a variant Mapping to the current Frame
+
+         \sstitem
+         \htmlref{AST\_GETFRAME}{AST\_GETFRAME}: Obtain a pointer to a specified Frame in a FrameSet
+
+         \sstitem
+         \htmlref{AST\_GETMAPPING}{AST\_GETMAPPING}: Obtain a Mapping between two Frames in a FrameSet
+
+         \sstitem
+         \htmlref{AST\_MIRRORVARIANTS}{AST\_MIRRORVARIANTS}: Make the current Frame mirror variant Mappings in another Frame
+
+         \sstitem
+         \htmlref{AST\_REMAPFRAME}{AST\_REMAPFRAME}: Modify a Frame\texttt{'} s relationship to the other Frames in a
+         FrameSet
+
+         \sstitem
+         \htmlref{AST\_REMOVEFRAME}{AST\_REMOVEFRAME}: Remove a Frame from a FrameSet
+      }
+   }
+}
+\sstroutine{
+   GrismMap
+}{
+   Transform 1-dimensional coordinates using a grism dispersion equation
+}{
+   \sstdescription{
+      A GrismMap is a specialised form of \htmlref{Mapping}{Mapping} which transforms
+      1-dimensional coordinates using the spectral dispersion equation
+      described in FITS-WCS paper III \texttt{"} Representation of spectral
+      coordinates in FITS\texttt{"} . This describes the dispersion produced by
+      gratings, prisms and grisms.
+
+      When initially created, the forward transformation of a GrismMap
+      transforms input \texttt{"} grism parameter\texttt{"}  values into output wavelength
+      values. The \texttt{"} grism parameter\texttt{"}  is a dimensionless value which is
+      linearly related to position on the detector. It is defined in FITS-WCS
+      paper III as \texttt{"} the offset on the detector from the point of intersection
+      of the camera axis, measured in units of the effective local length\texttt{"} .
+      The units in which wavelength values are expected or returned is
+      determined by the values supplied for the \htmlref{GrismWaveR}{GrismWaveR}, \htmlref{GrismNRP}{GrismNRP} and
+      \htmlref{GrismG}{GrismG} attribute: whatever units are used for these attributes will
+      also be used for the wavelength values.
+   }
+   \sstconstructor{
+      \htmlref{AST\_GRISMMAP}{AST\_GRISMMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The GrismMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      GrismMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{GrismNR}{GrismNR}: The refractive index at the reference wavelength
+
+         \sstitem
+         \htmlref{GrismNRP}{GrismNRP}: Rate of change of refractive index with wavelength
+
+         \sstitem
+         \htmlref{GrismWaveR}{GrismWaveR}: The reference wavelength
+
+         \sstitem
+         \htmlref{GrismAlpha}{GrismAlpha}: The angle of incidence of the incoming light
+
+         \sstitem
+         \htmlref{GrismG}{GrismG}: The grating ruling density
+
+         \sstitem
+         \htmlref{GrismM}{GrismM}: The interference order
+
+         \sstitem
+         \htmlref{GrismEps}{GrismEps}: The angle between the normal and the dispersion plane
+
+         \sstitem
+         \htmlref{GrismTheta}{GrismTheta}: Angle between normal to detector plane and reference ray
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The GrismMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Interval
+}{
+   A region representing an interval on one or more axes of a Frame
+}{
+   \sstdescription{
+      The Interval class implements a \htmlref{Region}{Region} which represents upper
+      and/or lower limits on one or more axes of a \htmlref{Frame}{Frame}. For a point to
+      be within the region represented by the Interval, the point must
+      satisfy all the restrictions placed on all the axes. The point is
+      outside the region if it fails to satisfy any one of the restrictions.
+      Each axis may have either an upper limit, a lower limit, both or
+      neither. If both limits are supplied but are in reverse order (so
+      that the lower limit is greater than the upper limit), then the
+      interval is an excluded interval, rather than an included interval.
+
+      Note, The Interval class makes no allowances for cyclic nature of
+      some coordinate systems (such as \htmlref{SkyFrame}{SkyFrame} coordinates). A \htmlref{Box}{Box}
+      should usually be used in these cases since this requires the user
+      to think about suitable upper and lower limits,
+   }
+   \sstconstructor{
+      \htmlref{AST\_INTERVAL}{AST\_INTERVAL}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Interval class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The Interval class does not define any new attributes beyond
+      those which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The Interval class does not define any new routines beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   IntraMap
+}{
+   Map points using a private transformation routine
+}{
+   \sstdescription{
+      The IntraMap class provides a specialised form of \htmlref{Mapping}{Mapping} which
+      encapsulates a privately-defined coordinate transformation
+      routine (e.g. written in Fortran) so that it may be used like
+      any other AST Mapping. This allows you to create Mappings that
+      perform any conceivable coordinate transformation.
+
+      However, an IntraMap is intended for use within a single program
+      or a private suite of software, where all programs have access
+      to the same coordinate transformation functions (i.e. can be
+      linked against them). IntraMaps should not normally be stored in
+      datasets which may be exported for processing by other software,
+      since that software will not have the necessary transformation
+      functions available, resulting in an error.
+
+      You must register any coordinate transformation functions to be
+      used using \htmlref{AST\_INTRAREG}{AST\_INTRAREG} before creating an IntraMap.
+   }
+   \sstconstructor{
+      \htmlref{AST\_INTRAMAP}{AST\_INTRAMAP} (also see AST\_INTRAREG)
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The IntraMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      IntraMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{IntraFlag}{IntraFlag}: IntraMap identification string
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The IntraMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   KeyMap
+}{
+   Store a set of key/value pairs
+}{
+   \sstdescription{
+      The KeyMap class is used to store a set of values with associated keys
+      which identify the values. The keys are strings. These may be case
+      sensitive or insensitive as selected by the \htmlref{KeyCase}{KeyCase} attribute, and
+      trailing spaces are ignored. The value associated with a key can be
+      integer (signed 4 and 2 byte, or unsigned 1 byte), floating point
+      (single or double precision),
+      character string or AST \htmlref{Object}{Object} pointer. Each
+      value can be a scalar or a one-dimensional vector. A KeyMap is
+      conceptually similar to a \htmlref{Mapping}{Mapping} in that a KeyMap transforms an
+      input into an output - the input is the key, and the output is the
+      value associated with the key. However, this is only a conceptual
+      similarity, and it should be noted that the KeyMap class inherits from
+      the Object class rather than the Mapping class. The methods of the
+      Mapping class cannot be used with a KeyMap.
+   }
+   \sstconstructor{
+      \htmlref{AST\_KEYMAP}{AST\_KEYMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The KeyMap class inherits from the Object class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Objects, every
+      KeyMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{KeyCase}{KeyCase}: Sets the case in which keys are stored
+
+         \sstitem
+         \htmlref{KeyError}{KeyError}: \htmlref{Report}{Report} an error if the requested key does not exist?
+
+         \sstitem
+         \htmlref{SizeGuess}{SizeGuess}: The expected size of the KeyMap.
+
+         \sstitem
+         \htmlref{SortBy}{SortBy}: Determines how keys are sorted in a KeyMap.
+
+         \sstitem
+         \htmlref{MapLocked}{MapLocked}: Prevent new entries being added to the KeyMap?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Objects, the
+      following routines may also be applied to all KeyMaps:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_MAPDEFINED}{AST\_MAPDEFINED}: Does a KeyMap contain a defined value for a key?
+
+         \sstitem
+         \htmlref{AST\_MAPGET0$<$X$>$}{AST\_MAPGET0$<$X$>$}: Get a named scalar entry from a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPGET1$<$X$>$}{AST\_MAPGET1$<$X$>$}: Get a named vector entry from a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPGETELEM$<$X$>$}{AST\_MAPGETELEM$<$X$>$}: Get an element of a named vector entry from a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPHASKEY}{AST\_MAPHASKEY}: Does the KeyMap contain a named entry?
+
+         \sstitem
+         \htmlref{AST\_MAPKEY}{AST\_MAPKEY}: Return the key name at a given index in the KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPLENC}{AST\_MAPLENC}: Get the length of a named character entry in a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPLENGTH}{AST\_MAPLENGTH}: Get the length of a named entry in a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPCOPY}{AST\_MAPCOPY}: Copy entries from one KeyMap into another
+
+         \sstitem
+         \htmlref{AST\_MAPPUT0$<$X$>$}{AST\_MAPPUT0$<$X$>$}: Add a new scalar entry to a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPPUT1$<$X$>$}{AST\_MAPPUT1$<$X$>$}: Add a new vector entry to a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPPUTELEM$<$X$>$}{AST\_MAPPUTELEM$<$X$>$}: Puts a value into a vector entry in a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPPUTU}{AST\_MAPPUTU}: Add a new entry to a KeyMap with an undefined value
+
+         \sstitem
+         \htmlref{AST\_MAPREMOVE}{AST\_MAPREMOVE}: Removed a named entry from a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPRENAME}{AST\_MAPRENAME}: Rename an existing entry in a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPSIZE}{AST\_MAPSIZE}: Get the number of entries in a KeyMap
+
+         \sstitem
+         \htmlref{AST\_MAPTYPE}{AST\_MAPTYPE}: Return the data type of a named entry in a map
+      }
+   }
+}
+\sstroutine{
+   LutMap
+}{
+   Transform 1-dimensional coordinates using a lookup table
+}{
+   \sstdescription{
+      A LutMap is a specialised form of \htmlref{Mapping}{Mapping} which transforms
+      1-dimensional coordinates by using linear interpolation in a
+      lookup table.
+
+      Each input coordinate value is first scaled to give the index of
+      an entry in the table by subtracting a starting value (the input
+      coordinate corresponding to the first table entry) and dividing
+      by an increment (the difference in input coordinate value
+      between adjacent table entries).
+
+      The resulting index will usually contain a fractional part, so
+      the output coordinate value is then generated by interpolating
+      linearly between the appropriate entries in the table. If the
+      index lies outside the range of the table, linear extrapolation
+      is used based on the two nearest entries (i.e. the two entries
+      at the start or end of the table, as appropriate). If either of the
+      entries used for the interplation has a value of AST\_\_BAD, then the
+      interpolated value is returned as AST\_\_BAD.
+
+      If the lookup table entries increase or decrease monotonically
+      (ignoring any flat sections), then the inverse transformation may
+      also be performed.
+   }
+   \sstconstructor{
+      \htmlref{AST\_LUTMAP}{AST\_LUTMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The LutMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      LutMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{LutEpsilon}{LutEpsilon}: The relative error of the values in the table.
+
+         \sstitem
+         \htmlref{LutInterp}{LutInterp}: The interpolation method to use between table entries.
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The LutMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Mapping
+}{
+   Inter-relate two coordinate systems
+}{
+   \sstdescription{
+      This class provides the basic facilities for transforming a set
+      of coordinates (representing \texttt{"} input\texttt{"}  points) to give a new set
+      of coordinates (representing \texttt{"} output\texttt{"}  points).  It is used to
+      describe the relationship which exists between two different
+      coordinate systems and to implement operations which make use of
+      this (such as transforming coordinates and resampling grids of
+      data).  However, the Mapping class does not have a constructor
+      function of its own, as it is simply a container class for a
+      family of specialised Mappings which implement particular types
+      of coordinate transformation.
+   }
+   \sstconstructor{
+      None.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Mapping class inherits from the \htmlref{Object}{Object} class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Objects, every
+      Mapping also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Invert}{Invert}: Mapping inversion flag
+
+         \sstitem
+         \htmlref{IsLinear}{IsLinear}: Is the Mapping linear?
+
+         \sstitem
+         \htmlref{IsSimple}{IsSimple}: Has the Mapping been simplified?
+
+         \sstitem
+         \htmlref{Nin}{Nin}: Number of input coordinates for a Mapping
+
+         \sstitem
+         \htmlref{Nout}{Nout}: Number of output coordinates for a Mapping
+
+         \sstitem
+         \htmlref{Report}{Report}: Report transformed coordinates?
+
+         \sstitem
+         \htmlref{TranForward}{TranForward}: Forward transformation defined?
+
+         \sstitem
+         \htmlref{TranInverse}{TranInverse}: Inverse transformation defined?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Objects, the
+      following routines may also be applied to all Mappings:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_DECOMPOSE}{AST\_DECOMPOSE}: Decompose a Mapping into two component Mappings
+
+         \sstitem
+         \htmlref{AST\_TRANGRID}{AST\_TRANGRID}: Transform a grid of positions
+
+         \sstitem
+         \htmlref{AST\_INVERT}{AST\_INVERT}: Invert a Mapping
+
+         \sstitem
+         \htmlref{AST\_LINEARAPPROX}{AST\_LINEARAPPROX}: Calculate a linear approximation to a Mapping
+
+         \sstitem
+         \htmlref{AST\_QUADAPPROX}{AST\_QUADAPPROX}: Calculate a quadratic approximation to a 2D Mapping
+
+         \sstitem
+         \htmlref{AST\_MAPBOX}{AST\_MAPBOX}: Find a bounding box for a Mapping
+
+         \sstitem
+         \htmlref{AST\_MAPSPLIT}{AST\_MAPSPLIT}: Split a Mapping up into parallel component Mappings
+
+         \sstitem
+         \htmlref{AST\_RATE}{AST\_RATE}: Calculate the rate of change of a Mapping output
+
+         \sstitem
+         \htmlref{AST\_REBIN$<$X$>$}{AST\_REBIN$<$X$>$}: Rebin a region of a data grid
+
+         \sstitem
+         \htmlref{AST\_REBINSEQ$<$X$>$}{AST\_REBINSEQ$<$X$>$}: Rebin a region of a sequence of data grids
+
+         \sstitem
+         \htmlref{AST\_REMOVEREGIONS}{AST\_REMOVEREGIONS}: Remove any Regions from a Mapping
+
+         \sstitem
+         \htmlref{AST\_RESAMPLE$<$X$>$}{AST\_RESAMPLE$<$X$>$}: Resample a region of a data grid
+
+         \sstitem
+         \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}: Simplify a Mapping
+
+         \sstitem
+         \htmlref{AST\_TRAN1}{AST\_TRAN1}: Transform 1-dimensional coordinates
+
+         \sstitem
+         \htmlref{AST\_TRAN2}{AST\_TRAN2}: Transform 2-dimensional coordinates
+
+         \sstitem
+         \htmlref{AST\_TRANN}{AST\_TRANN}: Transform N-dimensional coordinates
+      }
+   }
+}
+\sstroutine{
+   MathMap
+}{
+   Transform coordinates using mathematical expressions
+}{
+   \sstdescription{
+      A MathMap is a \htmlref{Mapping}{Mapping} which allows you to specify a set of forward
+      and/or inverse transformation functions using arithmetic operations
+      and mathematical functions similar to those available in Fortran. The
+      MathMap interprets these functions at run-time, whenever its forward
+      or inverse transformation is required. Because the functions are not
+      compiled in the normal sense (unlike an \htmlref{IntraMap}{IntraMap}), they may be used to
+      describe coordinate transformations in a transportable manner. A
+      MathMap therefore provides a flexible way of defining new types of
+      Mapping whose descriptions may be stored as part of a dataset and
+      interpreted by other programs.
+   }
+   \sstconstructor{
+      \htmlref{AST\_MATHMAP}{AST\_MATHMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The MathMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      MathMap also has the following attributes:
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Seed}{Seed}: Random number seed
+
+         \sstitem
+         \htmlref{SimpFI}{SimpFI}: Forward-inverse MathMap pairs simplify?
+
+         \sstitem
+         \htmlref{SimpIF}{SimpIF}: Inverse-forward MathMap pairs simplify?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The MathMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   MatrixMap
+}{
+   Map coordinates by multiplying by a matrix
+}{
+   \sstdescription{
+      A MatrixMap is form of \htmlref{Mapping}{Mapping} which performs a general linear
+      transformation. Each set of input coordinates, regarded as a
+      column-vector, are pre-multiplied by a matrix (whose elements
+      are specified when the MatrixMap is created) to give a new
+      column-vector containing the output coordinates. If appropriate,
+      the inverse transformation may also be performed.
+   }
+   \sstconstructor{
+      \htmlref{AST\_MATRIXMAP}{AST\_MATRIXMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The MatrixMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The MatrixMap class does not define any new attributes beyond
+      those which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The MatrixMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   NormMap
+}{
+   Normalise coordinates using a supplied Frame
+}{
+   \sstdescription{
+      The NormMap class implements a \htmlref{Mapping}{Mapping} which normalises coordinate
+      values using the
+      \htmlref{AST\_NORM}{AST\_NORM} routine
+      of a supplied \htmlref{Frame}{Frame}. The number of inputs and outputs of a NormMap
+      are both equal to the number of axes in the supplied Frame.
+
+      The forward and inverse transformation of a NormMap are both
+      defined but are identical (that is, they do not form a real inverse
+      pair in that the inverse transformation does not undo the
+      normalisation, instead it reapplies it). However, the
+      \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}
+      function will replace neighbouring pairs of forward and inverse
+      NormMaps by a single \htmlref{UnitMap}{UnitMap} (so long as the Frames encapsulated by
+      the two NormMaps are equal - i.e. have the same class and the same
+      attribute values). This means, for instance, that if a \htmlref{CmpMap}{CmpMap} contains
+      a NormMap, the CmpMap will still cancel with its own inverse.
+   }
+   \sstconstructor{
+      \htmlref{AST\_NORMMAP}{AST\_NORMMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The NormMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The NormMap class does not define any new attributes beyond
+      those which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The NormMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   NullRegion
+}{
+   A boundless region within a Frame
+}{
+   \sstdescription{
+      The NullRegion class implements a \htmlref{Region}{Region} with no bounds within a \htmlref{Frame}{Frame}.
+      If the \htmlref{Negated}{Negated} attribute of a NullRegion is false, the NullRegion
+      represents a Region containing no points. If the Negated attribute of
+      a NullRegion is true, the NullRegion represents an infinite Region
+      (that is, all points in the coordinate system are inside the NullRegion).
+   }
+   \sstconstructor{
+      \htmlref{AST\_NULLREGION}{AST\_NULLREGION}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The NullRegion class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The NullRegion class does not define any new attributes beyond
+      those which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The NullRegion class does not define any new routines beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   Object
+}{
+   Base class for all AST Objects
+}{
+   \sstdescription{
+      This class is the base class from which all other classes in the
+      AST library are derived. It provides all the basic Object
+      behaviour and Object manipulation facilities required throughout
+      the library. There is no Object constructor, however, as Objects
+      on their own are not useful.
+   }
+   \sstconstructor{
+      None.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Object base class does not inherit from any other class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      All Objects have the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Class}{Class}: Object class name
+
+         \sstitem
+         \htmlref{ID}{ID}: Object identification string
+
+         \sstitem
+         \htmlref{Ident}{Ident}: Permanent Object identification string
+
+         \sstitem
+         \htmlref{Nobject}{Nobject}: Number of Objects in class
+
+         \sstitem
+         \htmlref{ObjSize}{ObjSize}: The in-memory size of the Object in bytes
+
+         \sstitem
+         \htmlref{RefCount}{RefCount}: Count of active Object pointers
+
+         \sstitem
+         \htmlref{UseDefs}{UseDefs}: Allow use of default values for Object attributes?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The following routines may be applied to all Objects:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_ANNUL}{AST\_ANNUL}: Annul a pointer to an Object
+
+         \sstitem
+         \htmlref{AST\_BEGIN}{AST\_BEGIN}: Begin a new AST context
+
+         \sstitem
+         \htmlref{AST\_CLEAR}{AST\_CLEAR}: Clear attribute values for an Object
+
+         \sstitem
+         \htmlref{AST\_CLONE}{AST\_CLONE}: Clone a pointer to an Object
+
+         \sstitem
+         \htmlref{AST\_COPY}{AST\_COPY}: Copy an Object
+
+         \sstitem
+         \htmlref{AST\_DELETE}{AST\_DELETE}: Delete an Object
+
+         \sstitem
+         \htmlref{AST\_END}{AST\_END}: End an AST context
+
+         \sstitem
+         \htmlref{AST\_ESCAPES}{AST\_ESCAPES}: Control whether graphical escape sequences are removed
+
+         \sstitem
+         \htmlref{AST\_EXEMPT}{AST\_EXEMPT}: Exempt an Object pointer from AST context handling
+
+         \sstitem
+         \htmlref{AST\_EXPORT}{AST\_EXPORT}: Export an Object pointer to an outer context
+
+         \sstitem
+         \htmlref{AST\_GET$<$X$>$}{AST\_GET$<$X$>$}: Get an attribute value for an Object
+
+         \sstitem
+         \htmlref{AST\_HASATTRIBUTE}{AST\_HASATTRIBUTE}: Test if an Object has a named attribute
+
+         \sstitem
+         \htmlref{AST\_IMPORT}{AST\_IMPORT}: Import an Object pointer to the current context
+
+         \sstitem
+         \htmlref{AST\_ISA$<$CLASS$>$}{AST\_ISA$<$CLASS$>$}: Test class membership
+
+         \sstitem
+         \htmlref{AST\_SAME}{AST\_SAME}: Do two AST pointers refer to the same Object?
+
+         \sstitem
+         \htmlref{AST\_SET}{AST\_SET}: Set attribute values for an Object
+
+         \sstitem
+         \htmlref{AST\_SET$<$X$>$}{AST\_SET$<$X$>$}: Set an attribute value for an Object
+
+         \sstitem
+         \htmlref{AST\_SHOW}{AST\_SHOW}: Display a textual representation of an Object on standard
+         output
+
+         \sstitem
+         \htmlref{AST\_TEST}{AST\_TEST}: Test if an attribute value is set for an Object
+
+         \sstitem
+         \htmlref{AST\_TUNE}{AST\_TUNE}: Set or get an integer AST tuning parameter
+
+         \sstitem
+         \htmlref{AST\_TUNEC}{AST\_TUNEC}: Set or get a character AST tuning parameter
+
+         \sstitem
+         \htmlref{AST\_VERSION}{AST\_VERSION}: Return the verson of the AST library being used.
+      }
+   }
+}
+\sstroutine{
+   PcdMap
+}{
+   Apply 2-dimensional pincushion/barrel distortion
+}{
+   \sstdescription{
+      A PcdMap is a non-linear \htmlref{Mapping}{Mapping} which transforms 2-dimensional
+      positions to correct for the radial distortion introduced by some
+      cameras and telescopes. This can take the form either of pincushion
+      or barrel distortion, and is characterized by a single distortion
+      coefficient.
+
+      A PcdMap is specified by giving this distortion coefficient and the
+      coordinates of the centre of the radial distortion. The forward
+      transformation of a PcdMap applies the distortion:
+
+         RD = R $*$ ( 1 $+$ C $*$ R $*$ R )
+
+      where R is the undistorted radial distance from the distortion
+      centre (specified by attribute PcdCen), RD is the radial distance
+      from the same centre in the presence of distortion, and C is the
+      distortion coefficient (given by attribute \htmlref{Disco}{Disco}).
+
+      The inverse transformation of a PcdMap removes the distortion
+      produced by the forward transformation. The expression used to derive
+      R from RD is an approximate inverse of the expression above.
+   }
+   \sstconstructor{
+      \htmlref{AST\_PCDMAP}{AST\_PCDMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The PcdMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      PcdMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Disco}{Disco}: PcdMap pincushion/barrel distortion coefficient
+
+         \sstitem
+         \htmlref{PcdCen(axis)}{PcdCen(axis)}: Centre coordinates of pincushion/barrel distortion
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The PcdMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   PermMap
+}{
+   Coordinate permutation Mapping
+}{
+   \sstdescription{
+      A PermMap is a \htmlref{Mapping}{Mapping} which permutes the order of coordinates,
+      and possibly also changes the number of coordinates, between its
+      input and output.
+
+      In addition to permuting the coordinate order, a PermMap may
+      also assign constant values to coordinates. This is useful when
+      the number of coordinates is being increased as it allows fixed
+      values to be assigned to any new ones.
+   }
+   \sstconstructor{
+      \htmlref{AST\_PERMMAP}{AST\_PERMMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The PermMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The PermMap class does not define any new attributes beyond
+      those which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The PermMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Plot
+}{
+   Provide facilities for 2D graphical output
+}{
+   \sstdescription{
+      This class provides facilities for producing 2D graphical output.
+      A Plot is a specialised form of \htmlref{FrameSet}{FrameSet}, in which the base
+      \htmlref{Frame}{Frame} describes a \texttt{"} graphical\texttt{"}  coordinate system and is
+      associated with a rectangular plotting area in the underlying
+      graphics system. This plotting area is where graphical output
+      appears. It is defined when the Plot is created.
+
+      The current Frame of a Plot describes a \texttt{"} physical\texttt{"}  coordinate
+      system, which is the coordinate system in which plotting
+      operations are specified. The results of each plotting operation
+      are automatically transformed into graphical coordinates so as
+      to appear in the plotting area (subject to any clipping which
+      may be in effect).
+
+      Because the \htmlref{Mapping}{Mapping} between physical and graphical coordinates
+      may often be non-linear, or even discontinuous, most plotting
+      does not result in simple straight lines. The basic plotting
+      element is therefore not a straight line, but a geodesic curve
+      (see \htmlref{AST\_CURVE}{AST\_CURVE}, \htmlref{AST\_GENCURVE}{AST\_GENCURVE} and \htmlref{AST\_POLYCURVE}{AST\_POLYCURVE}). A Plot also provides facilities
+      for drawing markers or symbols (\htmlref{AST\_MARK}{AST\_MARK}), text (\htmlref{AST\_TEXT}{AST\_TEXT}) and grid
+      lines (\htmlref{AST\_GRIDLINE}{AST\_GRIDLINE}). It is also possible to draw curvilinear axes
+      with optional coordinate grids (\htmlref{AST\_GRID}{AST\_GRID}).
+      A range of Plot attributes is available to allow precise control
+      over the appearance of graphical output produced by these
+      routines.
+
+      You may select different physical coordinate systems in which to
+      plot (including the native graphical coordinate system itself)
+      by selecting different Frames as the current Frame of a Plot,
+      using its \htmlref{Current}{Current} attribute.  You may also set up clipping (see
+      \htmlref{AST\_CLIP}{AST\_CLIP}) to limit the extent of any plotting you perform, and
+      this may be done in any of the coordinate systems associated
+      with the Plot, not necessarily the one you are plotting in.
+
+      Like any FrameSet, a Plot may also be used as a Frame. In this
+      case, it behaves like its current Frame, which describes the
+      physical coordinate system.
+
+      When used as a Mapping, a Plot describes the inter-relation
+      between graphical coordinates (its base Frame) and physical
+      coordinates (its current Frame).  It differs from a normal
+      FrameSet, however, in that an attempt to transform points which
+      lie in clipped areas of the Plot will result in bad coordinate
+      values (AST\_\_BAD).
+   }
+   \sstconstructor{
+      \htmlref{AST\_PLOT}{AST\_PLOT}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Plot class inherits from the FrameSet class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all FrameSets, every
+      Plot also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         Abbrev: Abbreviate leading fields?
+
+         \sstitem
+         \htmlref{Border}{Border}: Draw a border around valid regions of a Plot?
+
+         \sstitem
+         \htmlref{Clip}{Clip}: Clip lines and/or markers at the Plot boundary?
+
+         \sstitem
+         \htmlref{ClipOp}{ClipOp}: Combine Plot clipping limits using a boolean OR?
+
+         \sstitem
+         \htmlref{Colour(element)}{Colour(element)}: Colour index for a Plot element
+
+         \sstitem
+         \htmlref{DrawAxes(axis)}{DrawAxes(axis)}: Draw axes for a Plot?
+
+         \sstitem
+         \htmlref{DrawTitle}{DrawTitle}: Draw a title for a Plot?
+
+         \sstitem
+         \htmlref{Escape}{Escape}: Allow changes of character attributes within strings?
+
+         \sstitem
+         \htmlref{Edge(axis)}{Edge(axis)}: Which edges to label in a Plot
+
+         \sstitem
+         \htmlref{Font(element)}{Font(element)}: Character font for a Plot element
+
+         \sstitem
+         \htmlref{Gap(axis)}{Gap(axis)}: \htmlref{Interval}{Interval} between linearly spaced major axis values
+
+         \sstitem
+         \htmlref{Grf}{Grf}: Select the graphics interface to use.
+
+         \sstitem
+         \htmlref{Grid}{Grid}: Draw grid lines for a Plot?
+
+         \sstitem
+         \htmlref{Invisible}{Invisible}: Draw graphics in invisible ink?
+
+         \sstitem
+         \htmlref{LabelAt(axis)}{LabelAt(axis)}: Where to place numerical labels for a Plot
+
+         \sstitem
+         \htmlref{LabelUnits(axis)}{LabelUnits(axis)}: Use axis unit descriptions in a Plot?
+
+         \sstitem
+         \htmlref{LabelUp(axis)}{LabelUp(axis)}: Draw numerical Plot labels upright?
+
+         \sstitem
+         \htmlref{Labelling}{Labelling}: Label and tick placement option for a Plot
+
+         \sstitem
+         \htmlref{LogGap(axis)}{LogGap(axis)}: Interval between logarithmically spaced major axis values
+
+         \sstitem
+         \htmlref{LogPlot(axis)}{LogPlot(axis)}: Map the plot onto the screen logarithmically?
+
+         \sstitem
+         \htmlref{LogTicks(axis)}{LogTicks(axis)}: Space the major tick marks logarithmically?
+
+         \sstitem
+         \htmlref{MajTickLen(axis)}{MajTickLen(axis)}: Length of major tick marks for a Plot
+
+         \sstitem
+         \htmlref{MinTickLen(axis)}{MinTickLen(axis)}: Length of minor tick marks for a Plot
+
+         \sstitem
+         \htmlref{MinTick(axis)}{MinTick(axis)}: Density of minor tick marks for a Plot
+
+         \sstitem
+         \htmlref{NumLab(axis)}{NumLab(axis)}: Draw numerical axis labels for a Plot?
+
+         \sstitem
+         \htmlref{NumLabGap(axis)}{NumLabGap(axis)}: Spacing of numerical axis labels for a Plot
+
+         \sstitem
+         \htmlref{Size(element)}{Size(element)}: Character size for a Plot element
+
+         \sstitem
+         \htmlref{Style(element)}{Style(element)}: Line style for a Plot element
+
+         \sstitem
+         \htmlref{TextLab(axis)}{TextLab(axis)}: Draw descriptive axis labels for a Plot?
+
+         \sstitem
+         \htmlref{TextLabGap(axis)}{TextLabGap(axis)}: Spacing of descriptive axis labels for a Plot
+
+         \sstitem
+         \htmlref{TickAll}{TickAll}: Draw tick marks on all edges of a Plot?
+
+         \sstitem
+         \htmlref{TitleGap}{TitleGap}: Vertical spacing for a Plot title
+
+         \sstitem
+         \htmlref{Tol}{Tol}: Plotting tolerance
+
+         \sstitem
+         \htmlref{Width(element)}{Width(element)}: Line width for a Plot element
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all FrameSets, the
+      following routines may also be applied to all Plots:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_BBUF}{AST\_BBUF}: Begin a new graphical buffering context
+
+         \sstitem
+         \htmlref{AST\_BORDER}{AST\_BORDER}: Draw a border around valid regions of a Plot
+
+         \sstitem
+         \htmlref{AST\_BOUNDINGBOX}{AST\_BOUNDINGBOX}: Returns a bounding box for previously drawn graphics
+
+         \sstitem
+         \htmlref{AST\_CLIP}{AST\_CLIP}: Set up or remove clipping for a Plot
+
+         \sstitem
+         \htmlref{AST\_CURVE}{AST\_CURVE}: Draw a geodesic curve
+
+         \sstitem
+         \htmlref{AST\_EBUF}{AST\_EBUF}: End the current graphical buffering context
+
+         \sstitem
+         \htmlref{AST\_GENCURVE}{AST\_GENCURVE}: Draw a generalized curve
+
+         \sstitem
+         \htmlref{AST\_GETGRFCONTEXT}{AST\_GETGRFCONTEXT}: Get the graphics context for a Plot
+
+         \sstitem
+         \htmlref{AST\_GRFPOP}{AST\_GRFPOP}: Retrieve previously saved graphics functions
+
+         \sstitem
+         \htmlref{AST\_GRFPUSH}{AST\_GRFPUSH}: Save the current graphics functions
+
+         \sstitem
+         \htmlref{AST\_GRFSET}{AST\_GRFSET}: Register a graphics routine for use by the Plot class
+
+         \sstitem
+         \htmlref{AST\_GRID}{AST\_GRID}: Draw a set of labelled coordinate axes
+
+         \sstitem
+         \htmlref{AST\_GRIDLINE}{AST\_GRIDLINE}: Draw a grid line (or axis) for a Plot
+
+         \sstitem
+         \htmlref{AST\_MARK}{AST\_MARK}: Draw a set of markers for a Plot
+
+         \sstitem
+         \htmlref{AST\_POLYCURVE}{AST\_POLYCURVE}: Draw a series of connected geodesic curves
+
+         \sstitem
+         AST\_REGIONOUTLINE: Draw the outline of an AST \htmlref{Region}{Region}
+
+         \sstitem
+         \htmlref{AST\_TEXT}{AST\_TEXT}: Draw a text string for a Plot
+      }
+   }
+   \sstdiytopic{
+      Graphical Elements
+   }{
+      The colour index, character font, character size, line style and
+      line width used for plotting can be set independently for
+      various elements of the graphical output produced by a Plot.
+      The different graphical elements are identified by appending the
+      strings listed below as subscripts to the Plot attributes
+      Colour(element), Font(element), Size(element), Style(element)
+      and Width(element). These strings are case-insensitive and
+      unambiguous abbreviations may be used. Elements of the graphical
+      output which relate to individual axes can be referred to either
+      independently (e.g. \texttt{"} (Grid1)\texttt{"}  and \texttt{"} (Grid2)\texttt{"}  ) or together (e.g.
+      \texttt{"} (Grid)\texttt{"} ):
+
+      \sstitemlist{
+
+         \sstitem
+         Axes: \htmlref{Axis}{Axis} lines drawn through tick marks using AST\_GRID
+
+         \sstitem
+         Axis1: Axis line drawn through tick marks on axis 1 using AST\_GRID
+
+         \sstitem
+         Axis2: Axis line drawn through tick marks on axis 2 using AST\_GRID
+
+         \sstitem
+         Border: The Plot border drawn using AST\_BORDER, AST\_GRID or AST\_REGIONOUTLINE
+
+         \sstitem
+         Curves: Geodesic curves drawn using AST\_CURVE, AST\_GENCURVE or AST\_POLYCURVE
+
+         \sstitem
+         Grid: Grid lines drawn using AST\_GRIDLINE or AST\_GRID
+
+         \sstitem
+         Grid1: Grid lines which cross axis 1, drawn using AST\_GRIDLINE or AST\_GRID
+
+         \sstitem
+         Grid2: Grid lines which cross axis 2, drawn using AST\_GRIDLINE or AST\_GRID
+
+         \sstitem
+         Markers: Graphical markers (symbols) drawn using AST\_MARK
+
+         \sstitem
+         NumLab: Numerical axis labels drawn using AST\_GRID
+
+         \sstitem
+         NumLab1: Numerical labels for axis 1 drawn using AST\_GRID
+
+         \sstitem
+         NumLab2: Numerical labels for axis 2 drawn using AST\_GRID
+
+         \sstitem
+         Strings: Text strings drawn using AST\_TEXT
+
+         \sstitem
+         TextLab: Descriptive axis labels drawn using AST\_GRID
+
+         \sstitem
+         TextLab1: Descriptive label for axis 1 drawn using AST\_GRID
+
+         \sstitem
+         TextLab2: Descriptive label for axis 2 drawn using AST\_GRID
+
+         \sstitem
+         Ticks: Tick marks (both major and minor) drawn using AST\_GRID
+
+         \sstitem
+         Ticks1: Tick marks (both major and minor) for axis 1 drawn using AST\_GRID
+
+         \sstitem
+         Ticks2: Tick marks (both major and minor) for axis 2 drawn using AST\_GRID
+
+         \sstitem
+         \htmlref{Title}{Title}: The Plot title drawn using AST\_GRID
+      }
+   }
+}
+\sstroutine{
+   Plot3D
+}{
+   Provide facilities for 3D graphical output
+}{
+   \sstdescription{
+      A Plot3D is a specialised form of \htmlref{Plot}{Plot} that provides facilities
+      for producing 3D graphical output, including fully annotated 3D
+      coordinate grids. The base \htmlref{Frame}{Frame} in a Plot3D describes a 3-dimensional
+      \texttt{"} graphical\texttt{"}  coordinate system. The axes of this coordinate system are
+      assumed to be right-handed (that is, if X appears horizontally to the
+      right and Y vertically upwards, then Z is out of the screen towards
+      the viewer), and are assumed to be equally scaled (that is, the same
+      units are used to measure positions on each of the 3 axes). The upper
+      and lower bounds of a volume within this graphical coordinate system
+      is specified when the Plot3D is created, and all subsequent graphics
+      are \texttt{"} drawn\texttt{"}  in this volume.
+
+      The Plot3D class does not itself include any ability to draw on a
+      graphics device. Instead it calls upon function in an externally
+      supplied module (the \texttt{"} grf3d\texttt{"}  module) to do the required drawing.
+      A module should be written that implements the functions of the
+      grf3d interface using the facilities of a specific graphics system
+      This module should then be linked into the application so that the
+      Plot3D class can use its functions (see the description of the
+      \htmlref{ast\_link}{ast\_link} commands for details of how to do this). The grf3d interface
+      defines a few simple functions for drawing primitives such as straight
+      lines, markers and character strings. These functions all accept
+      positions in the 3D graphics coordinate system (the base Frame of the
+      Plot3D), and so the grf3d module must also manage the projection of
+      these 3D coordinates onto the 2D viewing surface, including the choice
+      of \texttt{"} eye\texttt{"} /\texttt{"} camera\texttt{"}  position, direction of viewing, etc. The AST
+      library includes a sample implementation of the grf3d interface
+      based on the PGPLOT graphics system (see file grf3d\_pgplot.c). This
+      implementation also serves to document the grf3d interface itself and
+      should be consulted for details before writing a new implementation.
+
+      The current Frame of a Plot3D describes a \texttt{"} physical\texttt{"}  3-dimensional
+      coordinate system, which is the coordinate system in which plotting
+      operations are specified when invoking the methods of the Plot3D
+      class. The results of each plotting operation are automatically
+      transformed into 3D graphical coordinates before being plotted
+      using the facilities of the grf3d module linked into the application.
+      Note, at least one of the three axes of the current Frame must be
+      independent of the other two current Frame axes.
+
+      You may select different physical coordinate systems in which to
+      plot (including the native graphical coordinate system itself)
+      by selecting different Frames as the current Frame of a Plot3D,
+      using its \htmlref{Current}{Current} attribute.
+
+      Like any \htmlref{FrameSet}{FrameSet}, a Plot3D may also be used as a Frame. In this
+      case, it behaves like its current Frame, which describes the
+      physical coordinate system.
+
+      When used as a \htmlref{Mapping}{Mapping}, a Plot3D describes the inter-relation
+      between 3D graphical coordinates (its base Frame) and 3D physical
+      coordinates (its current Frame).
+
+      Although the Plot3D class inherits from the Plot class, several of
+      the facilities of the Plot class are not available in the Plot3D
+      class, and an error will be reported if any attempt is made to use
+      them. Specifically, the Plot3D class does not support clipping
+      using the
+      astClip function.
+      \htmlref{AST\_CLIP}{AST\_CLIP} routine.
+      Nor does it support the specification of graphics primitive functions
+      at run-time using the
+      \htmlref{AST\_GRFSET}{AST\_GRFSET}, \htmlref{AST\_GRFPOP}{AST\_GRFPOP}, \htmlref{AST\_GRFPUSH}{AST\_GRFPUSH}, and \htmlref{AST\_GETGRFCONTEXT}{AST\_GETGRFCONTEXT} routines.
+   }
+   \sstconstructor{
+      \htmlref{AST\_PLOT3D}{AST\_PLOT3D}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Plot3D class inherits from the Plot class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Plots, every
+      Plot3D also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         Norm: Normal vector defining the 2D plane used for text and markers
+
+         \sstitem
+         \htmlref{RootCorner}{RootCorner}: Specifies which edges of the 3D box should be annotated.
+
+      }
+      Some attributes of the Plot class refer to specific physical
+      coordinate axes (e.g. Gap, LabelUp, DrawAxes, etc). For a basic
+      Plot, the axis index must be 1 or 2, but for a Plot3D the axis index
+      can be 1, 2 or 3.
+
+      Certain Plot attributes are ignored by the Plot3D class (e.g. Edge,
+      \htmlref{DrawTitle}{DrawTitle}, \htmlref{TitleGap}{TitleGap}, etc). Consult the Plot attribute documentation
+      for details. All other Plot attributes can be set for a specific
+      plane of the 3-d plot by appending one of the strings \texttt{"} \_XY\texttt{"} , \texttt{"} \_XZ\texttt{"}
+      or \texttt{"} \_YZ\texttt{"}  to the end of the Plot attribute name. For instance,
+      \texttt{"} \htmlref{Grid}{Grid}\_YZ\texttt{"}  refers to the \texttt{"} Grid\texttt{"}  attribute for the plane spanning
+      the second (Y) and third (Z) axes of the 3-d plot.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+       The Plot3D class does not define any new routines beyond those
+       which are applicable to all Plots. Note, however, that the
+       following methods inherited from the Plot class cannot be used with
+       a Plot3D and will report an error if called:
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_BOUNDINGBOX}{AST\_BOUNDINGBOX}, AST\_CLIP, \htmlref{AST\_CURVE}{AST\_CURVE}, \htmlref{AST\_GENCURVE}{AST\_GENCURVE},
+         AST\_GETGRFCONTEXT, AST\_GRFPOP, AST\_GRFPUSH, AST\_GRFSET,
+         \htmlref{AST\_GRIDLINE}{AST\_GRIDLINE}, \htmlref{AST\_POLYCURVE}{AST\_POLYCURVE}.
+      }
+   }
+}
+\sstroutine{
+   PointList
+}{
+   A collection of points in a Frame
+}{
+   \sstdescription{
+      The PointList class implements a \htmlref{Region}{Region} which represents a collection
+      of points in a \htmlref{Frame}{Frame}.
+   }
+   \sstconstructor{
+      \htmlref{AST\_POINTLIST}{AST\_POINTLIST}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The PointList class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Regions, every
+      PointList also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{ListSize}{ListSize}: The number of positions stored in the PointList
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The PointList class does not define any new routines beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   PolyMap
+}{
+   Map coordinates using polynomial functions
+}{
+   \sstdescription{
+      A PolyMap is a form of \htmlref{Mapping}{Mapping} which performs a general polynomial
+      transformation.  Each output coordinate is a polynomial function of
+      all the input coordinates. The coefficients are specified separately
+      for each output coordinate. The forward and inverse transformations
+      are defined independantly by separate sets of coefficients. If no
+      inverse transformation is supplied, an iterative method can be used
+      to evaluate the inverse based only on the forward transformation.
+   }
+   \sstconstructor{
+      \htmlref{AST\_POLYMAP}{AST\_POLYMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The PolyMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      PolyMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{IterInverse}{IterInverse}: Provide an iterative inverse transformation?
+
+         \sstitem
+         \htmlref{NiterInverse}{NiterInverse}: Maximum number of iterations for iterative inverse
+
+         \sstitem
+         \htmlref{TolInverse}{TolInverse}: Target relative error for iterative inverse
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Objects, the
+      following routines may also be applied to all Mappings:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_POLYTRAN}{AST\_POLYTRAN}: Fit a PolyMap inverse or forward transformation
+      }
+   }
+}
+\sstroutine{
+   Polygon
+}{
+   A polygonal region within a 2-dimensional Frame
+}{
+   \sstdescription{
+      The Polygon class implements a polygonal area, defined by a
+      collection of vertices, within a 2-dimensional \htmlref{Frame}{Frame}. The vertices
+      are connected together by geodesic curves within the encapsulated Frame.
+      For instance, if the encapsulated Frame is a simple Frame then the
+      geodesics will be straight lines, but if the Frame is a \htmlref{SkyFrame}{SkyFrame} then
+      the geodesics will be great circles. Note, the vertices must be
+      supplied in an order such that the inside of the polygon is to the
+      left of the boundary as the vertices are traversed. Supplying them
+      in the reverse order will effectively negate the polygon.
+
+      Within a SkyFrame, neighbouring vertices are always joined using the
+      shortest path. Thus if an edge of 180 degrees or more in length is
+      required, it should be split into section each of which is less
+      than 180 degrees. The closed path joining all the vertices in order
+      will divide the celestial sphere into two disjoint regions. The
+      inside of the polygon is the region which is circled in an
+      anti-clockwise manner (when viewed from the inside of the celestial
+      sphere) when moving through the list of vertices in the order in
+      which they were supplied when the Polygon was created (i.e. the
+      inside is to the left of the boundary when moving through the
+      vertices in the order supplied).
+   }
+   \sstconstructor{
+      \htmlref{AST\_POLYGON}{AST\_POLYGON}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Polygon class inherits from the \htmlref{Region}{Region} class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Regions, every
+      Polygon also has the following attributes:
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{SimpVertices}{SimpVertices}: Simplify by transforming the vertices?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Regions, the
+      following routines may also be applied to all Polygons:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_DOWNSIZE}{AST\_DOWNSIZE}: Reduce the number of vertices in a Polygon.
+
+         \sstitem
+         \htmlref{AST\_CONVEX$<$X$>$}{AST\_CONVEX$<$X$>$}: Create a Polygon giving the convex hull of a pixel array
+
+         \sstitem
+         \htmlref{AST\_OUTLINE$<$X$>$}{AST\_OUTLINE$<$X$>$}: Create a Polygon outlining values in a pixel array
+      }
+   }
+}
+\sstroutine{
+   Prism
+}{
+   An extrusion of a region into higher dimensions
+}{
+   \sstdescription{
+      A Prism is a \htmlref{Region}{Region} which represents an extrusion of an existing Region
+      into one or more orthogonal dimensions (specified by another Region).
+      If the Region to be extruded has N axes, and the Region defining the
+      extrusion has M axes, then the resulting Prism will have (M$+$N) axes.
+      A point is inside the Prism if the first N axis values correspond to
+      a point inside the Region being extruded, and the remaining M axis
+      values correspond to a point inside the Region defining the extrusion.
+
+      As an example, a cylinder can be represented by extruding an existing
+      \htmlref{Circle}{Circle}, using an \htmlref{Interval}{Interval} to define the extrusion. Ih this case, the
+      Interval would have a single axis and would specify the upper and
+      lower limits of the cylinder along its length.
+   }
+   \sstconstructor{
+      \htmlref{AST\_PRISM}{AST\_PRISM}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Prism class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The Prism class does not define any new attributes beyond those
+      which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The Prism class does not define any new routines beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   RateMap
+}{
+   Mapping which represents differentiation
+}{
+   \sstdescription{
+      A RateMap is a \htmlref{Mapping}{Mapping} which represents a single element of the
+      Jacobian matrix of another Mapping. The Mapping for which the
+      Jacobian is required is specified when the new RateMap is created,
+      and is referred to as the \texttt{"} encapsulated Mapping\texttt{"}  below.
+
+      The number of inputs to a RateMap is the same as the number of inputs
+      to its encapsulated Mapping. The number of outputs from a RateMap
+      is always one. This one output equals the rate of change of a
+      specified output of the encapsulated Mapping with respect to a
+      specified input of the encapsulated Mapping (the input and output
+      to use are specified when the RateMap is created).
+
+      A RateMap which has not been inverted does not define an inverse
+      transformation. If a RateMap has been inverted then it will define
+      an inverse transformation but not a forward transformation.
+   }
+   \sstconstructor{
+      \htmlref{AST\_RATEMAP}{AST\_RATEMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The RateMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The RateMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The RateMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Region
+}{
+   Represents a region within a coordinate system
+}{
+   \sstdescription{
+      This class provides the basic facilities for describing a region within
+      a specified coordinate system. However, the Region class does not
+      have a constructor function of its own, as it is simply a container
+      class for a family of specialised sub-classes such as \htmlref{Circle}{Circle}, \htmlref{Box}{Box}, etc,
+      which implement Regions with particular shapes.
+
+      All sub-classes of Region require a \htmlref{Frame}{Frame} to be supplied when the Region
+      is created. This Frame describes the coordinate system in which the
+      Region is defined, and is referred to as the \texttt{"} encapsulated Frame\texttt{"}  below.
+      Constructors will also typically required one or more positions to be
+      supplied which define the location and extent of the region. These
+      positions must be supplied within the encapsulated Frame.
+
+      The Region class inherits from the Frame class, and so a Region can be
+      supplied where-ever a Frame is expected. In these cases, supplying a
+      Region is equivalent to supplying a reference to its encapsulated Frame.
+      Thus all the methods of the Frame class can be used on the Region class.
+      For instance, the
+      \htmlref{AST\_FORMAT}{AST\_FORMAT} routine
+      may be used on a Region to format an axis value.
+
+      In addition, since Frame inherits from \htmlref{Mapping}{Mapping}, a Region is also a sort
+      of Mapping. Transforming positions by supplying a Region to one of the
+      AST\_TRAN$<$X$>$ routines
+      is the way to determine if a given position is inside or outside the
+      Region. When used as a Mapping, most classes of Frame are equivalent to
+      a \htmlref{UnitMap}{UnitMap}. However, the Region class modifies this behaviour so that a
+      Region acts like a UnitMap only for input positions which are within the
+      area represented by the Region. Input positions which are outside the
+      area produce bad output values (i.e. the output values are equal to
+      AST\_\_BAD). This behaviour is the same for both the forward and the
+      inverse transformation. In this sense the \texttt{"} inverse transformation\texttt{"}
+      is not a true inverse of the forward transformation, since applying
+      the forward transformation to a point outside the Region, and then
+      applying the inverse transformation results, in a set of AST\_\_BAD axis
+      values rather than the original axis values. If required, the
+      \htmlref{AST\_REMOVEREGIONS}{AST\_REMOVEREGIONS}
+      function can be used to remove the \texttt{"} masking\texttt{"}  effect of any Regions
+      contained within a compound Mapping or \htmlref{FrameSet}{FrameSet}. It does this by
+      replacing each Region with a UnitMap or equivalent Frame (depending
+      on the context in which the Region is used).
+
+      If the coordinate system represented by the Region is changed (by
+      changing the values of one or more of the attribute which the Region
+      inherits from its encapsulated Frame), the area represented by
+      the Region is mapped into the new coordinate system. For instance, let\texttt{'} s
+      say a Circle (a subclass of Region) is created, a \htmlref{SkyFrame}{SkyFrame} being
+      supplied to the constructor so that the Circle describes a circular
+      area on the sky in FK4 equatorial coordinates. Since Region inherits
+      from Frame, the Circle will have a \htmlref{System}{System} attribute and this attribute
+      will be set to \texttt{"} FK4\texttt{"} . If the System attribute of the Region is then
+      changed from FK4 to FK5, the circular area represented by the Region
+      will automatically be mapped from the FK4 system into the FK5 system.
+      In general, changing the coordinate system in this way may result in the
+      region changing shape - for instance, a circle may change into an
+      ellipse if the transformation from the old to the new coordinate system
+      is linear but with different scales on each axis. Thus the specific
+      class of a Region cannot be used as a guarantee of the shape in any
+      particular coordinate system. If the
+      \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY} routine
+      is used on a Region, it will endeavour to return a new Region of
+      a sub-class which accurately describes the shape in the current
+      coordinate system of the Region (but this may not always be possible).
+
+      It is possible to negate an existing Region so that it represents all
+      areas of the encapsulated Frame except for the area specified when
+      the Region was created.
+   }
+   \sstconstructor{
+      None.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Region class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      Region also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Adaptive}{Adaptive}: Should the area adapt to changes in the coordinate system?
+
+         \sstitem
+         \htmlref{Negated}{Negated}: Has the original region been negated?
+
+         \sstitem
+         \htmlref{Closed}{Closed}: Should the boundary be considered to be inside the region?
+
+         \sstitem
+         \htmlref{MeshSize}{MeshSize}: Number of points used to create a mesh covering the Region
+
+         \sstitem
+         \htmlref{FillFactor}{FillFactor}: Fraction of the Region which is of interest
+
+         \sstitem
+         \htmlref{Bounded}{Bounded}: Is the Region bounded?
+
+      }
+      Every Region also inherits any further attributes that belong
+      to the encapsulated Frame, regardless of that Frame\texttt{'} s class. (For
+      example, the \htmlref{Equinox}{Equinox} attribute, defined by the SkyFrame class, is
+      inherited by any Region which represents a SkyFrame.)
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Frames, the
+      following routines may also be applied to all Regions:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_GETREGIONBOUNDS}{AST\_GETREGIONBOUNDS}: Get the bounds of a Region
+
+         \sstitem
+         \htmlref{AST\_GETREGIONFRAME}{AST\_GETREGIONFRAME}: Get a copy of the Frame represent by a Region
+
+         \sstitem
+         \htmlref{AST\_GETREGIONFRAMESET}{AST\_GETREGIONFRAMESET}: Get a copy of the Frameset encapsulated by a Region
+
+         \sstitem
+         \htmlref{AST\_GETREGIONMESH}{AST\_GETREGIONMESH}: Get a mesh of points covering a Region
+
+         \sstitem
+         \htmlref{AST\_GETREGIONPOINTS}{AST\_GETREGIONPOINTS}: Get the positions that define a Region
+
+         \sstitem
+         \htmlref{AST\_GETUNC}{AST\_GETUNC}: Obtain uncertainty information from a Region
+
+         \sstitem
+         \htmlref{AST\_MAPREGION}{AST\_MAPREGION}: Transform a Region into a new coordinate system
+
+         \sstitem
+         \htmlref{AST\_NEGATE}{AST\_NEGATE}: Toggle the value of the Negated attribute
+
+         \sstitem
+         \htmlref{AST\_OVERLAP}{AST\_OVERLAP}: Determines the nature of the overlap between two Regions
+
+         \sstitem
+         \htmlref{AST\_MASK$<$X$>$}{AST\_MASK$<$X$>$}: Mask a region of a data grid
+
+         \sstitem
+         \htmlref{AST\_SETUNC}{AST\_SETUNC}: Associate a new uncertainty with a Region
+
+         \sstitem
+         \htmlref{AST\_SHOWMESH}{AST\_SHOWMESH}: Display a mesh of points on the surface of a Region
+      }
+   }
+}
+\sstroutine{
+   SelectorMap
+}{
+   A Mapping that locates positions within one of a set of alternate
+   Regions
+}{
+   \sstdescription{
+      A SelectorMap is a \htmlref{Mapping}{Mapping} that identifies which \htmlref{Region}{Region} contains
+      a given input position.
+
+      A SelectorMap encapsulates a number of Regions that all have the same
+      number of axes and represent the same coordinate \htmlref{Frame}{Frame}. The number of
+      inputs (\htmlref{Nin}{Nin} attribute) of the SelectorMap equals the number of axes
+      spanned by one of the encapsulated Region. All SelectorMaps have only
+      a single output. SelectorMaps do not define an inverse transformation.
+
+      For each input position, the forward transformation of a SelectorMap
+      searches through the encapsulated Regions (in the order supplied when
+      the SelectorMap was created) until a Region is found which contains
+      the input position. The index associated with this Region is
+      returned as the SelectorMap output value (the index value is the
+      position of the Region within the list of Regions supplied when the
+      SelectorMap was created, starting at 1 for the first Region). If an
+      input position is not contained within any Region, a value of zero is
+      returned by the forward transformation.
+
+      If a compound Mapping contains a SelectorMap in series with its own
+      inverse, the combination of the two adjacent SelectorMaps will be
+      replaced by a \htmlref{UnitMap}{UnitMap} when the compound Mapping is simplified using
+      \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}.
+
+      In practice, SelectorMaps are often used in conjunction with SwitchMaps.
+   }
+   \sstconstructor{
+      \htmlref{AST\_SELECTORMAP}{AST\_SELECTORMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SelectorMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The SelectorMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The SelectorMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   ShiftMap
+}{
+   Add a constant value to each coordinate
+}{
+   \sstdescription{
+      A ShiftMap is a linear \htmlref{Mapping}{Mapping} which shifts each axis by a
+      specified constant value.
+   }
+   \sstconstructor{
+      \htmlref{AST\_SHIFTMAP}{AST\_SHIFTMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The ShiftMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The ShiftMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The ShiftMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   SkyAxis
+}{
+   Store celestial axis information
+}{
+   \sstdescription{
+      The SkyAxis class is used to store information associated with a
+      particular axis of a \htmlref{SkyFrame}{SkyFrame}. It is used internally by the AST
+      library and has no constructor function. You should encounter it
+      only within textual output (e.g. from \htmlref{AST\_WRITE}{AST\_WRITE}).
+   }
+   \sstconstructor{
+      None.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SkyAxis class inherits from the \htmlref{Axis}{Axis} class.
+   }
+}
+\sstroutine{
+   SkyFrame
+}{
+   Celestial coordinate system description
+}{
+   \sstdescription{
+      A SkyFrame is a specialised form of \htmlref{Frame}{Frame} which describes
+      celestial longitude/latitude coordinate systems. The particular
+      celestial coordinate system to be represented is specified by
+      setting the SkyFrame\texttt{'} s \htmlref{System}{System} attribute (currently, the default
+      is ICRS) qualified, as necessary, by a mean \htmlref{Equinox}{Equinox} value and/or
+      an \htmlref{Epoch}{Epoch}.
+
+      For each of the supported celestial coordinate systems, a SkyFrame
+      can apply an optional shift of origin to create a coordinate system
+      representing offsets within the celestial coordinate system from some
+      specified reference point. This offset coordinate system can also be
+      rotated to define new longitude and latitude axes. See attributes
+      SkyRef, \htmlref{SkyRefIs}{SkyRefIs}, SkyRefP and \htmlref{AlignOffset}{AlignOffset}.
+
+      All the coordinate values used by a SkyFrame are in
+      radians. These may be formatted in more conventional ways for
+      display by using \htmlref{AST\_FORMAT}{AST\_FORMAT}.
+      For a SkyFrame, the Unit attribute describes the formatted value of
+      a SkyFrame axis, and may for instance be \texttt{"} h:m:s\texttt{"} , indicating that a
+      formatted axis value contains colon-separated fields for hours, minutes
+      and seconds. On the other hand, the InternalUnit attribute for a
+      SkyFrame is always set to \texttt{"} rad\texttt{"}  (i.e. radians), indicating that the
+      unformatted (i.e. floating point) axis values used by application code
+      are always in units of radians
+   }
+   \sstconstructor{
+      \htmlref{AST\_SKYFRAME}{AST\_SKYFRAME}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SkyFrame class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      SkyFrame also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AlignOffset}{AlignOffset}: Align SkyFrames using the offset coordinate system?
+
+         \sstitem
+         \htmlref{AsTime(axis)}{AsTime(axis)}: Format celestial coordinates as times?
+
+         \sstitem
+         \htmlref{Equinox}{Equinox}: Epoch of the mean equinox
+
+         \sstitem
+         IsLatAxis: Is the specified axis the latitude axis?
+
+         \sstitem
+         IsLonAxis: Is the specified axis the longitude axis?
+
+         \sstitem
+         \htmlref{LatAxis}{LatAxis}: Index of the latitude axis
+
+         \sstitem
+         \htmlref{LonAxis}{LonAxis}: Index of the longitude axis
+
+         \sstitem
+         \htmlref{NegLon}{NegLon}: Display longitude values in the range [-pi,pi]?
+
+         \sstitem
+         \htmlref{Projection}{Projection}: Sky projection description.
+
+         \sstitem
+         SkyRef: Position defining location of the offset coordinate system
+
+         \sstitem
+         \htmlref{SkyRefIs}{SkyRefIs}: Selects the nature of the offset coordinate system
+
+         \sstitem
+         SkyRefP: Position defining orientation of the offset coordinate system
+
+         \sstitem
+         \htmlref{SkyTol}{SkyTol}: Smallest significant shift in sky coordinates
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those
+      routines
+      applicable to all Frames, the following
+      routines
+      may also be applied to all SkyFrames:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_SKYOFFSETMAP}{AST\_SKYOFFSETMAP}: Obtain a \htmlref{Mapping}{Mapping} from absolute to offset coordinates
+      }
+   }
+}
+\sstroutine{
+   SlaMap
+}{
+   Sequence of celestial coordinate conversions
+}{
+   \sstdescription{
+      An SlaMap is a specialised form of \htmlref{Mapping}{Mapping} which can be used to
+      represent a sequence of conversions between standard celestial
+      (longitude, latitude) coordinate systems.
+
+      When an SlaMap is first created, it simply performs a unit
+      (null) Mapping on a pair of coordinates. Using the \htmlref{AST\_SLAADD}{AST\_SLAADD}
+      routine, a series of coordinate conversion steps may then be
+      added, selected from those provided by the SLALIB Positional
+      Astronomy Library (Starlink User Note SUN/67). This allows
+      multi-step conversions between a variety of celestial coordinate
+      systems to be assembled out of the building blocks provided by
+      SLALIB.
+
+      For details of the individual coordinate conversions available,
+      see the description of the AST\_SLAADD routine.
+   }
+   \sstconstructor{
+      \htmlref{AST\_SLAMAP}{AST\_SLAMAP} (also see AST\_SLAADD)
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SlaMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The SlaMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Mappings, the
+      following routine may also be applied to all SlaMaps:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_SLAADD}{AST\_SLAADD}: Add a celestial coordinate conversion to an SlaMap
+      }
+   }
+}
+\sstroutine{
+   SpecFluxFrame
+}{
+   Compound spectrum/flux Frame
+}{
+   \sstdescription{
+      A SpecFluxFrame combines a \htmlref{SpecFrame}{SpecFrame} and a \htmlref{FluxFrame}{FluxFrame} into a single
+      2-dimensional compound \htmlref{Frame}{Frame}. Such a Frame can for instance be used
+      to describe a \htmlref{Plot}{Plot} of a spectrum in which the first axis represents
+      spectral position and the second axis represents flux.
+   }
+   \sstconstructor{
+      \htmlref{AST\_SPECFLUXFRAME}{AST\_SPECFLUXFRAME}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SpecFluxFrame class inherits from the \htmlref{CmpFrame}{CmpFrame} class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The SpecFluxFrame class does not define any new attributes beyond
+      those which are applicable to all CmpFrames. However, the attributes
+      of the component Frames can be accessed as if they were attributes
+      of the SpecFluxFrame. For instance, the SpecFluxFrame will recognise
+      the \texttt{"} \htmlref{StdOfRest}{StdOfRest}\texttt{"}  attribute and forward access requests to the component
+      SpecFrame. An axis index can optionally be appended to the end of any
+      attribute name, in which case the request to access the attribute will
+      be forwarded to the primary Frame defining the specified axis.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The SpecFluxFrame class does not define any new routines beyond those
+      which are applicable to all CmpFrames.
+   }
+}
+\sstroutine{
+   SpecFrame
+}{
+   Spectral coordinate system description
+}{
+   \sstdescription{
+      A SpecFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various coordinate systems used to describe positions within
+      an electro-magnetic spectrum. The particular coordinate system to be
+      used is specified by setting the SpecFrame\texttt{'} s \htmlref{System}{System} attribute (the
+      default is wavelength) qualified, as necessary, by other attributes
+      such as the rest frequency, the standard of rest, the epoch of
+      observation, units, etc (see the description of the System attribute
+      for details).
+
+      By setting a value for thr \htmlref{SpecOrigin}{SpecOrigin} attribute, a SpecFrame can be made
+      to represent offsets from a given spectral position, rather than absolute
+      spectral values.
+   }
+   \sstconstructor{
+      \htmlref{AST\_SPECFRAME}{AST\_SPECFRAME}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SpecFrame class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      SpecFrame also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AlignSpecOffset}{AlignSpecOffset}: Align SpecFrames using the offset coordinate system?
+
+         \sstitem
+         \htmlref{AlignStdOfRest}{AlignStdOfRest}: Standard of rest in which to align SpecFrames
+
+         \sstitem
+         \htmlref{RefDec}{RefDec}: Declination of the source (FK5 J2000)
+
+         \sstitem
+         \htmlref{RefRA}{RefRA}: Right ascension of the source (FK5 J2000)
+
+         \sstitem
+         \htmlref{RestFreq}{RestFreq}: Rest frequency
+
+         \sstitem
+         \htmlref{SourceSys}{SourceSys}: Source velocity spectral system
+
+         \sstitem
+         \htmlref{SourceVel}{SourceVel}: Source velocity
+
+         \sstitem
+         \htmlref{SourceVRF}{SourceVRF}: Source velocity rest frame
+
+         \sstitem
+         \htmlref{SpecOrigin}{SpecOrigin}: The zero point for SpecFrame axis values
+
+         \sstitem
+         \htmlref{StdOfRest}{StdOfRest}: Standard of rest
+
+      }
+      Several of the Frame attributes inherited by the SpecFrame class
+      refer to a specific axis of the Frame (for instance \htmlref{Unit(axis)}{Unit(axis)},
+      \htmlref{Label(axis)}{Label(axis)}, etc). Since a SpecFrame is strictly one-dimensional,
+      it allows these attributes to be specified without an axis index.
+      So for instance, \texttt{"} Unit\texttt{"}  is allowed in place of \texttt{"} Unit(1)\texttt{"} .
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Frames, the
+      following routines may also be applied to all SpecFrames:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_SETREFPOS}{AST\_SETREFPOS}: Set reference position in any celestial system
+
+         \sstitem
+         \htmlref{AST\_GETREFPOS}{AST\_GETREFPOS}: Get reference position in any celestial system
+      }
+   }
+}
+\sstroutine{
+   SpecMap
+}{
+   Sequence of spectral coordinate conversions
+}{
+   \sstdescription{
+      A SpecMap is a specialised form of \htmlref{Mapping}{Mapping} which can be used to
+      represent a sequence of conversions between standard spectral
+      coordinate systems.
+
+      When an SpecMap is first created, it simply performs a unit
+      (null) Mapping. Using the \htmlref{AST\_SPECADD}{AST\_SPECADD}
+      routine, a series of coordinate conversion steps may then be
+      added. This allows multi-step conversions between a variety of
+      spectral coordinate systems to be assembled out of a set of building
+      blocks.
+
+      Conversions are available to transform between standards of rest.
+      Such conversions need to know the source position as an RA and DEC.
+      This information can be supplied in the form of parameters for
+      the relevant conversions, in which case the SpecMap is 1-dimensional,
+      simply transforming the spectral axis values. This means that the
+      same source position will always be used by the SpecMap. However, this
+      may not be appropriate for an accurate description of a 3-D spectral
+      cube, where changes of spatial position can produce significant
+      changes in the Doppler shift introduced when transforming between
+      standards of rest. For this situation, a 3-dimensional SpecMap can
+      be created in which axes 2 and 3 correspond to the source RA and DEC
+      The SpecMap simply copies values for axes 2 and 3 from input to
+      output), but modifies axis 1 values (the spectral axis) appropriately.
+
+      For details of the individual coordinate conversions available,
+      see the description of the AST\_SPECADD routine.
+   }
+   \sstconstructor{
+      \htmlref{AST\_SPECMAP}{AST\_SPECMAP} (also see AST\_SPECADD)
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SpecMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The SpecMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Mappings, the
+      following routine may also be applied to all SpecMaps:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_SPECADD}{AST\_SPECADD}: Add a spectral coordinate conversion to an SpecMap
+      }
+   }
+}
+\sstroutine{
+   SphMap
+}{
+   Map 3-d Cartesian to 2-d spherical coordinates
+}{
+   \sstdescription{
+      A SphMap is a \htmlref{Mapping}{Mapping} which transforms points from a
+      3-dimensional Cartesian coordinate system into a 2-dimensional
+      spherical coordinate system (longitude and latitude on a unit
+      sphere centred at the origin). It works by regarding the input
+      coordinates as position vectors and finding their intersection
+      with the sphere surface. The inverse transformation always
+      produces points which are a unit distance from the origin
+      (i.e. unit vectors).
+   }
+   \sstconstructor{
+      \htmlref{AST\_SPHMAP}{AST\_SPHMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SphMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      SphMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{UnitRadius}{UnitRadius}: SphMap input vectors lie on a unit sphere?
+
+         \sstitem
+         \htmlref{PolarLong}{PolarLong}: The longitude value to assign to either pole
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The SphMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Stc
+}{
+   Represents an instance of the IVOA STC class
+}{
+   \sstdescription{
+      The Stc class is an implementation of the IVOA STC class which forms
+      part of the IVOA Space-Time Coordinate Metadata system. See:
+
+      http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+
+      The Stc class does not have a constructor function of its own, as it
+      is simply a container class for a family of specialised sub-classes
+      including \htmlref{StcCatalogEntryLocation}{StcCatalogEntryLocation}, \htmlref{StcResourceProfile}{StcResourceProfile}, \htmlref{StcSearchLocation}{StcSearchLocation}
+      and \htmlref{StcObsDataLocation}{StcObsDataLocation}.
+   }
+   \sstconstructor{
+      AST\_STC
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Stc class inherits from the \htmlref{Region}{Region} class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Regions, every
+      Stc also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{RegionClass}{RegionClass}: The class name of the encapsulated Region.
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Regions, the
+      following routines may also be applied to all Stc\texttt{'} s:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_GETSTCREGION}{AST\_GETSTCREGION}: Get a pointer to the encapsulated Region
+
+         \sstitem
+         \htmlref{AST\_GETSTCCOORD}{AST\_GETSTCCOORD}: Get information about an AstroCoords element
+
+         \sstitem
+         \htmlref{AST\_GETSTCNCOORD}{AST\_GETSTCNCOORD}: Returns the number of AstroCoords elements in an Stc
+      }
+   }
+}
+\sstroutine{
+   StcCatalogEntryLocation
+}{
+   Correspond to the IVOA STCCatalogEntryLocation class
+}{
+   \sstdescription{
+      The StcCatalogEntryLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of the datasets contained in some VO resource.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstconstructor{
+      \htmlref{AST\_STCCATALOGENTRYLOCATION}{AST\_STCCATALOGENTRYLOCATION}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The StcCatalogEntryLocation class inherits from the Stc class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The StcCatalogEntryLocation class does not define any new attributes beyond
+      those which are applicable to all Stcs.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The StcCatalogEntryLocation class does not define any new routines beyond those
+      which are applicable to all Stcs.
+   }
+}
+\sstroutine{
+   StcObsDataLocation
+}{
+   Correspond to the IVOA ObsDataLocation class
+}{
+   \sstdescription{
+      The StcObsDataLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coordinate space occupied by a particular observational dataset.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+
+      An STC ObsDataLocation element specifies the extent of the
+      observation within a specified coordinate system, and also specifies
+      the observatory location within a second coordinate system.
+
+      The AST StcObsDataLocation class inherits from Stc, and therefore
+      an StcObsDataLocation can be used directly as an Stc. When used
+      in this way, the StcObsDataLocation describes the location of the
+      observation (not the observatory).
+
+      Eventually, this class will have a method for returning an Stc
+      describing the observatory location. However, AST currently does not
+      include any classes of \htmlref{Frame}{Frame} for describing terrestrial or solar
+      system positions. Therefore, the provision for returning observatory
+      location as an Stc is not yet available. However, for terrestrial
+      observations, the position of the observatory can still be recorded
+      using the \htmlref{ObsLon}{ObsLon} and \htmlref{ObsLat}{ObsLat} attributes of the Frame encapsulated
+      within the Stc representing the observation location (this assumes
+      the observatory is located at sea level).
+   }
+   \sstconstructor{
+      \htmlref{AST\_STCOBSDATALOCATION}{AST\_STCOBSDATALOCATION}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The StcObsDataLocation class inherits from the Stc class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The StcObsDataLocation class does not define any new attributes beyond
+      those which are applicable to all Stcs.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The StcObsDataLocation class does not define any new routines beyond those
+      which are applicable to all Stcs.
+   }
+}
+\sstroutine{
+   StcResourceProfile
+}{
+   Correspond to the IVOA STCResourceProfile class
+}{
+   \sstdescription{
+      The StcResourceProfile class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of the datasets contained in some VO resource.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstconstructor{
+      \htmlref{AST\_STCRESOURCEPROFILE}{AST\_STCRESOURCEPROFILE}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The StcResourceProfile class inherits from the Stc class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The StcResourceProfile class does not define any new attributes beyond
+      those which are applicable to all Stcs.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The StcResourceProfile class does not define any new routines beyond those
+      which are applicable to all Stcs.
+   }
+}
+\sstroutine{
+   StcSearchLocation
+}{
+   Correspond to the IVOA SearchLocation class
+}{
+   \sstdescription{
+      The StcSearchLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of a query.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstconstructor{
+      \htmlref{AST\_STCSEARCHLOCATION}{AST\_STCSEARCHLOCATION}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The StcSearchLocation class inherits from the Stc class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The StcSearchLocation class does not define any new attributes beyond
+      those which are applicable to all Stcs.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The StcSearchLocation class does not define any new routines beyond those
+      which are applicable to all Stcs.
+   }
+}
+\sstroutine{
+   StcsChan
+}{
+   I/O Channel using STC-S to represent Objects
+}{
+   \sstdescription{
+      A StcsChan is a specialised form of \htmlref{Channel}{Channel} which supports STC-S
+      I/O operations. Writing an \htmlref{Object}{Object} to an StcsChan (using
+      \htmlref{AST\_WRITE}{AST\_WRITE}) will, if the Object is suitable, generate an
+      STC-S description of that Object, and reading from an StcsChan will
+      create a new Object from its STC-S description.
+
+      When an STC-S description is read using
+      \htmlref{AST\_READ}{AST\_READ},
+      the returned AST Object may be 1) a \htmlref{PointList}{PointList} describing the STC
+      AstroCoords (i.e. a single point of interest within the coordinate frame
+      described by the STC-S description), or 2) a \htmlref{Region}{Region} describing the STC
+      AstrCoordsArea (i.e. an area or volume of interest within the coordinate
+      frame described by the STC-S description), or 3) a \htmlref{KeyMap}{KeyMap}
+      containing the uninterpreted property values read form the STC-S
+      description, or 4) a KeyMap containing any combination of the first
+      3 options. The attributes \htmlref{StcsArea}{StcsArea}, \htmlref{StcsCoords}{StcsCoords} and \htmlref{StcsProps}{StcsProps}
+      control which of the above is returned by
+      AST\_READ.
+
+      When an STC-S description is created from an AST Object using
+      AST\_WRITE,
+      the AST Object must be either a Region or a KeyMap. If it is a
+      Region, it is assumed to define the AstroCoordsArea or (if the
+      Region is a single point) the AstroCoords to write to the STC-S
+      description. If the Object is a KeyMap, it may contain an entry
+      with the key \texttt{"} AREA\texttt{"} , holding a Region to be used to define the
+      AstroCoordsArea. It may also contain an entry with the key \texttt{"} COORDS\texttt{"} ,
+      holding a Region (a PointList) to be used to create the
+      AstroCoords. It may also contain an entry with key \texttt{"} PROPS\texttt{"} , holding
+      a KeyMap that contains uninterpreted property values to be used as
+      defaults for any STC-S properties that are not determined by the
+      other supplied Regions. In addition, a KeyMap supplied to
+      AST\_WRITE
+      may itself hold the default STC-S properties (rather than defaults
+      being held in a secondary KeyMap, stored as the \texttt{"} PROPS\texttt{"}  entry in the
+      supplied KeyMap).
+
+      The
+      AST\_READ and AST\_WRITE
+      functions work together so that any Object returned by
+      AST\_READ can immediately be re-written using AST\_WRITE.
+
+      Normally, when you use an StcsChan, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  routines which connect it to an external data store
+      by reading and writing the resulting text. These routines
+      should perform any conversions needed between external character
+      encodings and the internal ASCII encoding. If no such routines
+      are supplied, a Channel will read from standard input and write
+      to standard output.
+
+      Alternatively, an \htmlref{XmlChan}{XmlChan} can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+
+      Support for STC-S is currently based on the IVOA document \texttt{"} STC-S:
+      Space-Time Coordinate (STC) Metadata Linear String Implementation\texttt{"} ,
+      version 1.30 (dated 5th December 2007), available at
+      http://www.ivoa.net/Documents/latest/STC-S.html. Note, this
+      document is a recommednation only and does not constitute an accepted
+      IVOA standard.
+
+      The full text of version 1.30 is supported by the StcsChan class,
+      with the following exceptions and provisos:
+
+      \sstitemlist{
+
+         \sstitem
+         When reading an STC-S phrase, case is ignored except when reading
+         units strings.
+
+         \sstitem
+         There is no support for multiple intervals specified within a
+         TimeInterval, PositionInterval, SpectralInterval or RedshiftInterval.
+
+         \sstitem
+         If the ET timescale is specified, TT is used instead.
+
+         \sstitem
+         If the TEB timescale is specified, TDB is used instead.
+
+         \sstitem
+         The LOCAL timescale is not supported.
+
+         \sstitem
+         The AST \htmlref{TimeFrame}{TimeFrame} and \htmlref{SkyFrame}{SkyFrame} classes do not currently allow a
+         reference position to be specified. Consequently, any $<$refpos$>$
+         specified within the Time or Space sub-phrase of an STC-S document
+         is ignored.
+
+         \sstitem
+         The Convex identifier for the space sub-phrase is not supported.
+
+         \sstitem
+         The GEO\_C and GEO\_D space frames are not supported.
+
+         \sstitem
+         The UNITSPHERE and SPHER3 space flavours are not supported.
+
+         \sstitem
+         If any Error values are supplied in a space sub-phrase, then the
+         number of values supplied should equal the number of spatial axes,
+         and the values are assumed to specify an error box (i.e. error
+         circles, ellipses, etc, are not supported).
+
+         \sstitem
+         The spectral and redshift sub-phrases do not support the
+         following $<$refpos$>$ values: LOCAL\_GROUP\_CENTER, UNKNOWNRefPos,
+         EMBARYCENTER, MOON, MERCURY, VENUS, MARS, JUPITER, SATURN, URANUS,
+         NEPTUNE, PLUTO.
+
+         \sstitem
+         Error values are supported but error ranges are not.
+
+         \sstitem
+         Resolution, PixSize and Size values are ignored.
+
+         \sstitem
+         Space velocity sub-phrases are ignored.
+      }
+   }
+   \sstconstructor{
+      \htmlref{AST\_STCSCHAN}{AST\_STCSCHAN}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The StcsChan class inherits from the Channel class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Channels, every
+      StcsChan also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{StcsArea}{StcsArea}: Return the CoordinateArea component after reading an STC-S?
+
+         \sstitem
+         \htmlref{StcsCoords}{StcsCoords}: Return the Coordinates component after reading an STC-S?
+
+         \sstitem
+         \htmlref{StcsLength}{StcsLength}: Controls output buffer length
+
+         \sstitem
+         \htmlref{StcsProps}{StcsProps}: Return the STC-S properties after reading an STC-S?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The StcsChan class does not define any new routines beyond those
+      which are applicable to all Channels.
+   }
+}
+\sstroutine{
+   SwitchMap
+}{
+   A Mapping that encapsulates a set of alternate Mappings
+}{
+   \sstdescription{
+      A SwitchMap is a \htmlref{Mapping}{Mapping} which represents a set of alternate
+      Mappings, each of which is used to transform positions within a
+      particular region of the input or output coordinate system of the
+      SwitchMap.
+
+      A SwitchMap can encapsulate any number of Mappings, but they must
+      all have the same number of inputs (\htmlref{Nin}{Nin} attribute value) and the
+      same number of outputs (\htmlref{Nout}{Nout} attribute value). The SwitchMap itself
+      inherits these same values for its Nin and Nout attributes. Each of
+      these Mappings represents a \texttt{"} route\texttt{"}  through the switch, and are
+      referred to as \texttt{"} route\texttt{"}  Mappings below. Each route Mapping transforms
+      positions between the input and output coordinate space of the entire
+      SwitchMap, but only one Mapping will be used to transform any given
+      position. The selection of the appropriate route Mapping to use with
+      any given input position is made by another Mapping, called the
+      \texttt{"} selector\texttt{"}  Mapping. Each SwitchMap encapsulates two selector
+      Mappings in addition to its route Mappings; one for use with the
+      SwitchMap\texttt{'} s forward transformation (called the \texttt{"} forward selector
+      Mapping\texttt{"} ), and one for use with the SwitchMap\texttt{'} s inverse transformation
+      (called the \texttt{"} inverse selector Mapping\texttt{"} ). The forward selector Mapping
+      must have the same number of inputs as the route Mappings, but
+      should have only one output. Likewise, the inverse selector Mapping
+      must have the same number of outputs as the route Mappings, but
+      should have only one input.
+
+      When the SwitchMap is used to transform a position in the forward
+      direction (from input to output), each supplied input position is
+      first transformed by the forward transformation of the forward selector
+      Mapping. This produces a single output value for each input position
+      referred to as the selector value. The nearest integer to the selector
+      value is found, and is used to index the array of route Mappings (the
+      first supplied route Mapping has index 1, the second route Mapping has
+      index 2, etc). If the nearest integer to the selector value is less
+      than 1 or greater than the number of route Mappings, then the SwitchMap
+      output position is set to a value of AST\_\_BAD on every axis. Otherwise,
+      the forward transformation of the selected route Mapping is used to
+      transform the supplied input position to produce the SwitchMap output
+      position.
+
+      When the SwitchMap is used to transform a position in the inverse
+      direction (from \texttt{"} output\texttt{"}  to \texttt{"} input\texttt{"} ), each supplied \texttt{"} output\texttt{"}  position
+      is first transformed by the inverse transformation of the inverse
+      selector Mapping. This produces a selector value for each \texttt{"} output\texttt{"}
+      position. Again, the nearest integer to the selector value is found,
+      and is used to index the array of route Mappings. If this selector
+      index value is within the bounds of the array of route Mappings, then
+      the inverse transformation of the selected route Mapping is used to
+      transform the supplied \texttt{"} output\texttt{"}  position to produce the SwitchMap
+      \texttt{"} input\texttt{"}  position. If the selector index value is outside the bounds
+      of the array of route Mappings, then the SwitchMap \texttt{"} input\texttt{"}  position is
+      set to a value of AST\_\_BAD on every axis.
+
+      In practice, appropriate selector Mappings should be chosen to
+      associate a different route Mapping with each region of coordinate
+      space. Note that the \htmlref{SelectorMap}{SelectorMap} class of Mapping is particularly
+      appropriate for this purpose.
+
+      If a compound Mapping contains a SwitchMap in series with its own
+      inverse, the combination of the two adjacent SwitchMaps will be
+      replaced by a \htmlref{UnitMap}{UnitMap} when the compound Mapping is simplified using
+      \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}.
+   }
+   \sstconstructor{
+      \htmlref{AST\_SWITCHMAP}{AST\_SWITCHMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SwitchMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The SwitchMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The SwitchMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Table
+}{
+   A 2-dimensional table of values
+}{
+   \sstdescription{
+      The Table class is a type of \htmlref{KeyMap}{KeyMap} that represents a two-dimensional
+      table of values. The
+      AST\_MAPGET... and AST\_MAPPUT...
+      methods provided by the KeyMap class should be used for storing and
+      retrieving values from individual cells within a Table. Each entry
+      in the KeyMap represents a single cell of the table and has an
+      associated key of the form \texttt{"} $<$COL$>$(i)\texttt{"}  where \texttt{"} $<$COL$>$\texttt{"}  is the
+      upper-case name of a table column and \texttt{"} i\texttt{"}  is the row index (the
+      first row is row 1). Keys of this form should always be used when
+      using KeyMap methods to access entries within a Table.
+
+      Columns must be declared using the
+      \htmlref{AST\_ADDCOLUMN}{AST\_ADDCOLUMN}
+      method before values can be stored within them. This also fixes the
+      type and shape of the values that may be stored in any cell of the
+      column. Cells may contain scalar or vector values of any data type
+      supported by the KeyMap class. Multi-dimensional arrays may also be
+      stored, but these must be vectorised when storing and retrieving
+      them within a table cell. All cells within a single column must
+      have the same type and shape, as specified when the column is added
+      to the Table.
+
+      Tables may have parameters that describe global properties of the
+      entire table. These are stored as entries in the parent KeyMap and
+      can be access using the get and set method of the KeyMap class.
+      However, parameters must be declared using the
+      \htmlref{AST\_ADDPARAMETER}{AST\_ADDPARAMETER}
+      method before being accessed.
+
+      Note - since accessing entries within a KeyMap is a relatively slow
+      process, it is not recommended to use the Table class to store
+      very large tables.
+   }
+   \sstconstructor{
+      \htmlref{AST\_TABLE}{AST\_TABLE}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Table class inherits from the KeyMap class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all KeyMaps, every
+      Table also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{ColumnLenC(column)}{ColumnLenC(column)}: The largest string length of any value in a column
+
+         \sstitem
+         \htmlref{ColumnLength(column)}{ColumnLength(column)}: The number of elements in each value in a column
+
+         \sstitem
+         \htmlref{ColumnNdim(column)}{ColumnNdim(column)}: The number of axes spanned by each value in a column
+
+         \sstitem
+         \htmlref{ColumnType(column)}{ColumnType(column)}: The data type of each value in a column
+
+         \sstitem
+         ColumnUnit(column): The unit string describing each value in a column
+
+         \sstitem
+         \htmlref{Ncolumn}{Ncolumn}: The number of columns currently in the Table
+
+         \sstitem
+         \htmlref{Nrow}{Nrow}: The number of rows currently in the Table
+
+         \sstitem
+         \htmlref{Nparameter}{Nparameter}: The number of global parameters currently in the Table
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all KeyMaps, the
+      following routines may also be applied to all Tables:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_ADDCOLUMN}{AST\_ADDCOLUMN}: Add a new column definition to a Table
+
+         \sstitem
+         \htmlref{AST\_ADDPARAMETER}{AST\_ADDPARAMETER}: Add a new global parameter definition to a Table
+
+         \sstitem
+         \htmlref{AST\_COLUMNNAME}{AST\_COLUMNNAME}: Return the name of the column with a given index
+
+         \sstitem
+         \htmlref{AST\_COLUMNSHAPE}{AST\_COLUMNSHAPE}: Return the shape of the values in a named column
+
+         \sstitem
+         \htmlref{AST\_HASCOLUMN}{AST\_HASCOLUMN}: Checks if a column exists in a Table
+
+         \sstitem
+         \htmlref{AST\_HASPARAMETER}{AST\_HASPARAMETER}: Checks if a global parameter exists in a Table
+
+         \sstitem
+         \htmlref{AST\_PARAMETERNAME}{AST\_PARAMETERNAME}: Return the name of the parameter with a given index
+
+         \sstitem
+         \htmlref{AST\_PURGEROWS}{AST\_PURGEROWS}: Remove all empty rows from a Table
+
+         \sstitem
+         \htmlref{AST\_REMOVECOLUMN}{AST\_REMOVECOLUMN}: Remove a column from a Table
+
+         \sstitem
+         \htmlref{AST\_REMOVEPARAMETER}{AST\_REMOVEPARAMETER}: Remove a global parameter from a Table
+
+         \sstitem
+         \htmlref{AST\_REMOVEROW}{AST\_REMOVEROW}: Remove a row from a Table
+      }
+   }
+}
+\sstroutine{
+   TimeFrame
+}{
+   Time coordinate system description
+}{
+   \sstdescription{
+      A TimeFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various coordinate systems used to describe positions in
+      time.
+
+      A TimeFrame represents a moment in time as either an Modified Julian
+      Date (MJD), a Julian Date (JD), a Besselian epoch or a Julian epoch,
+      as determined by the \htmlref{System}{System} attribute. Optionally, a zero point can be
+      specified (using attribute \htmlref{TimeOrigin}{TimeOrigin}) which results in the TimeFrame
+      representing time offsets from the specified zero point.
+
+      Even though JD and MJD are defined as being in units of days, the
+      TimeFrame class allows other units to be used (via the Unit attribute)
+      on the basis of simple scalings (60 seconds = 1 minute, 60 minutes = 1
+      hour, 24 hours = 1 day, 365.25 days = 1 year). Likewise, Julian epochs
+      can be described in units other than the usual years. Besselian epoch
+      are always represented in units of (tropical) years.
+
+      The \htmlref{TimeScale}{TimeScale} attribute allows the time scale to be specified (that
+      is, the physical process used to define the rate of flow of time).
+      MJD, JD and Julian epoch can be used to represent a time in any
+      supported time scale. However, Besselian epoch may only be used with the
+      \texttt{"} TT\texttt{"}  (Terrestrial Time) time scale. The list of supported time scales
+      includes universal time and siderial time. Strictly, these represent
+      angles rather than time scales, but are included in the list since
+      they are in common use and are often thought of as time scales.
+
+      When a time value is formatted it can be formated either as a simple
+      floating point value, or as a Gregorian date (see the Format
+      attribute).
+   }
+   \sstconstructor{
+      \htmlref{AST\_TIMEFRAME}{AST\_TIMEFRAME}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The TimeFrame class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      TimeFrame also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AlignTimeScale}{AlignTimeScale}: Time scale in which to align TimeFrames
+
+         \sstitem
+         \htmlref{LTOffset}{LTOffset}: The offset of Local Time from UTC, in hours.
+
+         \sstitem
+         \htmlref{TimeOrigin}{TimeOrigin}: The zero point for TimeFrame axis values
+
+         \sstitem
+         \htmlref{TimeScale}{TimeScale}: The timescale used by the TimeFrame
+
+      }
+      Several of the Frame attributes inherited by the TimeFrame class
+      refer to a specific axis of the Frame (for instance \htmlref{Unit(axis)}{Unit(axis)},
+      \htmlref{Label(axis)}{Label(axis)}, etc). Since a TimeFrame is strictly one-dimensional,
+      it allows these attributes to be specified without an axis index.
+      So for instance, \texttt{"} Unit\texttt{"}  is allowed in place of \texttt{"} Unit(1)\texttt{"} .
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Frames, the
+      following routines may also be applied to all TimeFrames:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_CURRENTTIME}{AST\_CURRENTTIME}: Return the current system time
+      }
+   }
+}
+\sstroutine{
+   TimeMap
+}{
+   Sequence of time coordinate conversions
+}{
+   \sstdescription{
+      A TimeMap is a specialised form of 1-dimensional \htmlref{Mapping}{Mapping} which can be
+      used to represent a sequence of conversions between standard time
+      coordinate systems.
+
+      When a TimeMap is first created, it simply performs a unit
+      (null) Mapping. Using the \htmlref{AST\_TIMEADD}{AST\_TIMEADD}
+      routine, a series of coordinate conversion steps may then be
+      added. This allows multi-step conversions between a variety of
+      time coordinate systems to be assembled out of a set of building
+      blocks.
+
+      For details of the individual coordinate conversions available,
+      see the description of the AST\_TIMEADD routine.
+   }
+   \sstconstructor{
+      \htmlref{AST\_TIMEMAP}{AST\_TIMEMAP} (also see AST\_TIMEADD)
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The TimeMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The TimeMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those routines applicable to all Mappings, the
+      following routine may also be applied to all TimeMaps:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AST\_TIMEADD}{AST\_TIMEADD}: Add a time coordinate conversion to an TimeMap
+      }
+   }
+}
+\sstroutine{
+   TranMap
+}{
+   Mapping with specified forward and inverse transformations
+}{
+   \sstdescription{
+      A TranMap is a \htmlref{Mapping}{Mapping} which combines the forward transformation of
+      a supplied Mapping with the inverse transformation of another
+      supplied Mapping, ignoring the un-used transformation in each
+      Mapping (indeed the un-used transformation need not exist).
+
+      When the forward transformation of the TranMap is referred to, the
+      transformation actually used is the forward transformation of the
+      first Mapping supplied when the TranMap was constructed. Likewise,
+      when the inverse transformation of the TranMap is referred to, the
+      transformation actually used is the inverse transformation of the
+      second Mapping supplied when the TranMap was constructed.
+   }
+   \sstconstructor{
+      \htmlref{AST\_TRANMAP}{AST\_TRANMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The TranMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The TranMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The TranMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   UnitMap
+}{
+   Unit (null) Mapping
+}{
+   \sstdescription{
+      A UnitMap is a unit (null) \htmlref{Mapping}{Mapping} that has no effect on the
+      coordinates supplied to it. They are simply copied. This can be
+      useful if a Mapping is required (e.g. to pass to another
+      routine) but you do not want it to have any effect.
+      The \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes of a UnitMap are always equal and
+      are specified when it is created.
+   }
+   \sstconstructor{
+      \htmlref{AST\_UNITMAP}{AST\_UNITMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The UnitMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The UnitMap class does not define any new attributes beyond
+      those which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The UnitMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   UnitNormMap
+}{
+   Convert a vector to a unit vector and its norm, relative to a specified centre
+}{
+   \sstdescription{
+      The forward transformation of a UnitNormMap subtracts the specified centre
+      and then transforms the resulting vector to a unit vector and the vector norm.
+      The output contains one more coordinate than the input: the initial \htmlref{Nin}{Nin} outputs
+      are in the same order as the input; the final output is the norm.
+
+      The inverse transformation of a UnitNormMap multiplies each component
+      of the provided vector by the provided norm and adds the specified centre.
+      The output contains one fewer coordinate than the input: the initial Nin inputs
+      are in the same order as the output; the final input is the norm.
+
+      UnitNormMap enables radially symmetric transformations, as follows:
+      \sstitemlist{
+
+         \sstitem
+         apply a UnitNormMap to produce a unit vector and norm (radius)
+
+         \sstitem
+         apply a one-dimensional mapping to the norm (radius), while passing the unit vector unchanged
+
+         \sstitem
+         apply the same UnitNormMap in the inverse direction to produce the result
+      }
+   }
+   \sstconstructor{
+      \htmlref{AST\_UNITNORMMAP}{AST\_UNITNORMMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The UnitNormMap class inherits from the \htmlref{Mapping}{Mapping} class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The UnitNormMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The UnitNormMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   WcsMap
+}{
+   Implement a FITS-WCS sky projection
+}{
+   \sstdescription{
+      This class is used to represent sky coordinate projections as
+      described in the FITS world coordinate system (FITS-WCS) paper II
+      \texttt{"} Representations of Celestial Coordinates in FITS\texttt{"}  by M. Calabretta
+      and E.W. Griesen. This paper defines a set of functions, or sky
+      projections, which transform longitude-latitude pairs representing
+      spherical celestial coordinates into corresponding pairs of Cartesian
+      coordinates (and vice versa).
+
+      A WcsMap is a specialised form of \htmlref{Mapping}{Mapping} which implements these
+      sky projections and applies them to a specified pair of coordinates.
+      All the projections in the FITS-WCS paper are supported, plus the now
+      deprecated \texttt{"} TAN with polynomial correction terms\texttt{"}  projection which
+      is refered to here by the code \texttt{"} TPN\texttt{"} . Using the FITS-WCS terminology,
+      the transformation is between \texttt{"} native spherical\texttt{"}  and \texttt{"} projection
+      plane\texttt{"}  coordinates (also called \texttt{"} intermediate world coordinates\texttt{"} .
+      These coordinates may, optionally, be embedded in a space with more
+      than two dimensions, the remaining coordinates being copied unchanged.
+      Note, however, that for consistency with other AST facilities, a
+      WcsMap handles coordinates that represent angles in radians (rather
+      than the degrees used by FITS-WCS).
+
+      The type of FITS-WCS projection to be used and the coordinates
+      (axes) to which it applies are specified when a WcsMap is first
+      created. The projection type may subsequently be determined
+      using the \htmlref{WcsType}{WcsType} attribute and the coordinates on which it acts
+      may be determined using the \htmlref{WcsAxis(lonlat)}{WcsAxis(lonlat)} attribute.
+
+      Each WcsMap also allows up to 100 \texttt{"} projection parameters\texttt{"}  to be
+      associated with each axis. These specify the precise form of the
+      projection, and are accessed using \htmlref{PVi\_m}{PVi\_m} attribute, where \texttt{"} i\texttt{"}  is
+      the integer axis index (starting at 1), and m is an integer
+      \texttt{"} parameter index\texttt{"}  in the range 0 to 99. The number of projection
+      parameters required by each projection, and their meanings, are
+      dependent upon the projection type (most projections either do not
+      use any projection parameters, or use parameters 1 and 2 associated
+      with the latitude axis). Before creating a WcsMap you should consult
+      the FITS-WCS paper for details of which projection parameters are
+      required, and which have defaults. When creating the WcsMap, you must
+      explicitly set values for all those required projection parameters
+      which do not have defaults defined in this paper.
+   }
+   \sstconstructor{
+      \htmlref{AST\_WCSMAP}{AST\_WCSMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The WcsMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      WcsMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{NatLat}{NatLat}: Native latitude of the reference point of a FITS-WCS projection
+
+         \sstitem
+         \htmlref{NatLon}{NatLon}: Native longitude of the reference point of a FITS-WCS projection
+
+         \sstitem
+         \htmlref{PVi\_m}{PVi\_m}: FITS-WCS projection parameters
+
+         \sstitem
+         PVMax: Maximum number of FITS-WCS projection parameters
+
+         \sstitem
+         \htmlref{WcsAxis(lonlat)}{WcsAxis(lonlat)}: FITS-WCS projection axes
+
+         \sstitem
+         \htmlref{WcsType}{WcsType}: FITS-WCS projection type
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The WcsMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   WinMap
+}{
+   Map one window on to another by scaling and shifting each axis
+}{
+   \sstdescription{
+      A Winmap is a linear \htmlref{Mapping}{Mapping} which transforms a rectangular
+      window in one coordinate system into a similar window in another
+      coordinate system by scaling and shifting each axis (the window
+      edges being parallel to the coordinate axes).
+
+      A WinMap is specified by giving the coordinates of two opposite
+      corners (A and B) of the window in both the input and output
+      coordinate systems.
+   }
+   \sstconstructor{
+      \htmlref{AST\_WINMAP}{AST\_WINMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The WinMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The WinMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The WinMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   XmlChan
+}{
+   I/O Channel using XML to represent Objects
+}{
+   \sstdescription{
+      A XmlChan is a specialised form of \htmlref{Channel}{Channel} which supports XML I/O
+      operations. Writing an \htmlref{Object}{Object} to an XmlChan (using
+      \htmlref{AST\_WRITE}{AST\_WRITE}) will, if the Object is suitable, generate an
+      XML description of that Object, and reading from an XmlChan will
+      create a new Object from its XML description.
+
+      Normally, when you use an XmlChan, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  routines which connect it to an external data store
+      by reading and writing the resulting XML text. These routines
+      should perform any conversions needed between external character
+      encodings and the internal ASCII encoding. If no such routines
+      are supplied, a Channel will read from standard input and write
+      to standard output.
+
+      Alternatively, an XmlChan can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+   }
+   \sstconstructor{
+      \htmlref{AST\_XMLCHAN}{AST\_XMLCHAN}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The XmlChan class inherits from the Channel class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Channels, every
+      XmlChan also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{XmlFormat}{XmlFormat}: \htmlref{System}{System} for formatting Objects as XML
+
+         \sstitem
+         \htmlref{XmlLength}{XmlLength}: Controls output buffer length
+
+         \sstitem
+         \htmlref{XmlPrefix}{XmlPrefix}: The namespace prefix to use when writing
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The XmlChan class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   ZoomMap
+}{
+   Zoom coordinates about the origin
+}{
+   \sstdescription{
+      The ZoomMap class implements a \htmlref{Mapping}{Mapping} which performs a \texttt{"} zoom\texttt{"}
+      transformation by multiplying all coordinate values by the same
+      scale factor (the inverse transformation is performed by
+      dividing by this scale factor). The number of coordinate values
+      representing each point is unchanged.
+   }
+   \sstconstructor{
+      \htmlref{AST\_ZOOMMAP}{AST\_ZOOMMAP}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The ZoomMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      ZoomMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Zoom}{Zoom}: ZoomMap scale factor
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The ZoomMap class does not define any new routines beyond those
+      which are applicable to all Mappings.
+   }
+}
+\normalsize
+
+\cleardoublepage
+\section{\label{ss:commanddescriptions}UNIX Command Descriptions}
+The commands described here are provided for use from the UNIX shell
+to assist with developing software which uses AST. To use these
+commands, you should ensure that the directory
+``/star/bin''\footnote{Or the equivalent directory if AST is installed
+in a non-standard location.} is on your PATH.
+\small
+\sstroutine{
+   ast\_link
+}{
+   Link a program with the AST library
+}{
+   \sstdescription{
+      This command should be used when building programs which use the AST
+      library, in order to generate the correct arguments to allow the compiler
+      to link your program. The arguments generated are written to standard
+      output but may be substituted into the compiler command line in the
+      standard UNIX way using backward quotes (see below).
+
+      By default, it is assumed that you are building a stand-alone program
+      which does not produce graphical output. However, switches are provided
+      for linking other types of program.
+   }
+   \sstinvocation{
+      f77 program.f -L/star/lib `ast\_link [switches]` -o program
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         f77 display.f -L/star/lib `ast\_link -pgplot` -o display
+      }{
+         Compiles and links a Fortran program called ``display\texttt{'} \texttt{'}  which uses
+         the standard version of PGPLOT for graphical output.
+      }
+      \sstexamplesubsection{
+         f77 plotit.f -L. -L/star/lib `ast\_link -grf` -lgrf -o plotit
+      }{
+         Compiles and links a Fortran program ``plotit\texttt{'} \texttt{'} . The ``-grf\texttt{'} \texttt{'}
+         switch indicates that graphical output will be delivered through
+         a graphical interface which you have implemented yourself, which
+         corresponds to the interface required by the current version of AST.
+         Here, this interface is supplied by means of the ``-lgrf\texttt{'} \texttt{'}  library
+         reference.
+      }
+      \sstexamplesubsection{
+         f77 plotit.f -L. -L/star/lib `ast\_link -grf\_v2.0` -lgrf -o plotit
+      }{
+         Compiles and links a Fortran program ``plotit\texttt{'} \texttt{'} . The ``-grf\_v2.0\texttt{'} \texttt{'}
+         switch indicates that graphical output will be delivered through
+         a graphical interface which you have implemented yourself, which
+         corresponds to the interface required by version 2.0 of AST.
+         Here, this interface is supplied by means of the ``-lgrf\texttt{'} \texttt{'}  library
+         reference.
+      }
+   }
+   \sstdiytopic{
+      Switches
+   }{
+      The following switches may optionally be given to this command to
+      modify its behaviour:
+
+      \sstitemlist{
+
+         \sstitem
+         ``-csla\texttt{'} \texttt{'} : Ignored. Provided for backward compatibility only.
+
+         \sstitem
+         ``-fsla\texttt{'} \texttt{'} : Ignored. Provided for backward compatibility only.
+
+         \sstitem
+         ``-ems\texttt{'} \texttt{'} : Requests that the program be linked so that error messages
+         produced by the AST library are delivered via the Starlink EMS (Error
+         Message Service) library (Starlink \htmlref{System}{System} Note SSN/4). By default,
+         error messages are simply written to standard error.
+
+         \sstitem
+         ``-drama\texttt{'} \texttt{'} : Requests that the program be linked so that error messages
+         produced by the AST library are delivered via the DRAMA Ers (Error
+         Reporting Service) library. By default, error messages are simply
+         written to standard error.
+
+         \sstitem
+         ``-grf\texttt{'} \texttt{'} : Requests that no arguments be generated to specify which
+         2D graphics system is used to display output from the AST library. You
+         should use this option only if you have implemented an interface to a
+         new graphics system yourself and wish to provide your own arguments for
+         linking with it. This switch differs from the other ``grf\texttt{'} \texttt{'}  switches in
+         that it assumes that your graphics module implements the complete
+         interface required by the current version of AST. If future versions of
+         AST introduce new functions to the graphics interface, this switch will
+         cause ``unresolved symbol\texttt{'} \texttt{'}  errors to occur during linking, warning you
+         that you need to implement new functions in your graphics module. To
+         avoid such errors, you can use one of the other, version-specific,
+         switches in place of the ``-grf\texttt{'} \texttt{'}  switch, but these will cause run-time
+         errors to be reported if any AST function is invoked which requires
+         facilities not in the implemented interface.
+
+         \sstitem
+         ``-grf\_v2.0\texttt{'} \texttt{'} : This switch is equivalent to the ``-mygrf\texttt{'} \texttt{'}  switch.
+         It indicates that you want to link with your own graphics module
+         which implements the 2D graphics interface required by V2.0 of AST.
+
+         \sstitem
+         ``-grf\_v3.2\texttt{'} \texttt{'} : Indicates that you want to link with your own
+         graphics module which implements the 2D graphics interface required by
+         V3.2 of AST.
+
+         \sstitem
+         ``-grf\_v5.6\texttt{'} \texttt{'} : Indicates that you want to link with your own
+         graphics module which implements the 2D graphics interface required by
+         V5.6 of AST.
+
+         \sstitem
+         ``-myerr\texttt{'} \texttt{'} : Requests that no arguments be generated to specify how
+         error messages produced by the AST library should be delivered. You
+         should use this option only if you have implemented an interface to a
+         new error delivery system yourself and wish to provide your own
+         arguments for linking with it.
+
+         \sstitem
+         ``-mygrf\texttt{'} \texttt{'} : This switch has been superceeded by the ``-grf\texttt{'} \texttt{'}  switch,
+         but is retained in order to allow applications to be linked with a
+         graphics module which implements the 2D interface used by AST V2.0. It
+         is equivalent to the ``-grf\_v2.0\texttt{'} \texttt{'}  switch.
+
+         \sstitem
+         ``-pgp\texttt{'} \texttt{'} : Requests that the program be linked so that 2D
+         graphical output from the AST library is displayed via the
+         Starlink version of the PGPLOT graphics package (which uses GKS
+         for its output). By default, no 2D graphics package is linked and
+         this will result in an error at run time if AST routines are
+         invoked that attempt to generate graphical output.
+
+         \sstitem
+         ``-pgplot\texttt{'} \texttt{'} : Requests that the program be linked so that 2D
+         graphical output from the AST library is displayed via
+         the standard (or ``native\texttt{'} \texttt{'} ) version of the PGPLOT graphics
+         package. By default, no 2D graphics package is linked and this will
+         result in an error at run time if AST routines are invoked that
+         attempt to generate graphical output.
+
+         \sstitem
+         ``-grf3d\texttt{'} \texttt{'} : Requests that no arguments be generated to specify which
+         3D graphics system is used to display output from the AST library. You
+         should use this option only if you have implemented an interface to a
+         new 3D graphics system yourself and wish to provide your own arguments
+         for linking with it.
+
+         \sstitem
+         ``-pgp3d\texttt{'} \texttt{'} : Requests that the program be linked so that 3D
+         graphical output from the AST library is displayed via the
+         Starlink version of the PGPLOT graphics package (which uses GKS
+         for its output). By default, no 3D graphics package is linked and
+         this will result in an error at run time if AST routines are
+         invoked that attempt to generate graphical output.
+
+         \sstitem
+         ``-pgplot3d\texttt{'} \texttt{'} : Requests that the program be linked so that 3D
+         graphical output from the AST library is displayed via
+         the standard (or ``native\texttt{'} \texttt{'} ) version of the PGPLOT graphics
+         package. By default, no 3D graphics package is linked and this will
+         result in an error at run time if AST routines are invoked that
+         attempt to generate graphical output.
+      }
+   }
+   \sstdiytopic{
+      ERFA \& PAL
+   }{
+      The AST distribution includes bundled copies of the ERFA and PAL
+      libraries. These will be used for fundamental positional astronomy
+      calculations unless the \texttt{"} --with-external\_pal\texttt{"}  option was used when
+      AST was configured. If \texttt{"} --with-external\_pal\texttt{"}  is used, this script
+      will include \texttt{"} -lpal\texttt{"}  in the returned list of linking options, and
+      the user should then ensure that external copies of the PAL and
+      ERFA libraries are available (ERFA functions are used within PAL).
+   }
+}
+\sstroutine{
+   ast\_link\_adam
+}{
+   Link an ADAM program with the AST library
+}{
+   \sstdescription{
+      This command should only be used when building Starlink ADAM programs
+      which use the AST library, in order to generate the correct arguments
+      to allow the ADAM ``alink\texttt{'} \texttt{'}  command to link the program. The arguments
+      generated are written to standard output but may be substituted into
+      the ``alink\texttt{'} \texttt{'}  command line in the standard UNIX way using backward
+      quotes (see below).
+
+      By default, it is assumed that you are building an ADAM program which
+      does not produce graphical output. However, switches are provided for
+      linking other types of program. This command should not be used when
+      building stand-alone (non-ADAM) programs. Use the ``\htmlref{ast\_link}{ast\_link}\texttt{'} \texttt{'}  command
+      instead.
+   }
+   \sstinvocation{
+      alink program.f -L/star/lib `ast\_link\_adam [switches]`
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         alink display.f -L/star/lib `ast\_link\_adam -pgplot`
+      }{
+         Compiles and links an ADAM Fortran program called ``display\texttt{'} \texttt{'}  which
+         uses the standard version of PGPLOT for graphical output.
+      }
+      \sstexamplesubsection{
+         alink plotit.f -L. -L/star/lib `ast\_link\_adam -grf` -lgrf
+      }{
+         Compiles and links an ADAM Fortran program ``plotit\texttt{'} \texttt{'} . The ``-grf\texttt{'} \texttt{'}
+         switch indicates that graphical output will be delivered through
+         a graphical interface which you have implemented yourself, which
+         corresponds to the interface required by the current version of AST.
+         Here, this interface is supplied by means of the ``-lgrf\texttt{'} \texttt{'}  library
+         reference.
+      }
+      \sstexamplesubsection{
+         alink plotit.f -L. -L/star/lib `ast\_link\_adam -grf\_v2.0` -lgrf
+      }{
+         Compiles and links an ADAM Fortran program ``plotit\texttt{'} \texttt{'} . The ``-grf\_v2.0\texttt{'} \texttt{'}
+         switch indicates that graphical output will be delivered through
+         a graphical interface which you have implemented yourself, which
+         corresponds to the interface required by version 2.0 of AST.
+         Here, this interface is supplied by means of the ``-lgrf\texttt{'} \texttt{'}  library
+         reference.
+      }
+   }
+   \sstdiytopic{
+      Switches
+   }{
+      The following switches may optionally be given to this command to
+      modify its behaviour:
+
+      \sstitemlist{
+
+         \sstitem
+         ``-csla\texttt{'} \texttt{'} : Ignored. Provided for backward compatibility only.
+
+         \sstitem
+         ``-fsla\texttt{'} \texttt{'} : Ignored. Provided for backward compatibility only.
+
+         \sstitem
+         ``-grf\texttt{'} \texttt{'} : Requests that no arguments be generated to specify which
+         2D graphics system is used to display output from the AST library. You
+         should use this option only if you have implemented an interface to a
+         new graphics system yourself and wish to provide your own arguments for
+         linking with it. This switch differs from the other ``grf\texttt{'} \texttt{'}  switches in
+         that it assumes that your graphics module implements the complete
+         interface required by the current version of AST. If future versions of
+         AST introduce new functions to the graphics interface, this switch will
+         cause ``unresolved symbol\texttt{'} \texttt{'}  errors to occur during linking, warning you
+         that you need to implement new functions in your graphics module. To
+         avoid such errors, you can use one of the other, version-specific,
+         switches in place of the ``-grf\texttt{'} \texttt{'}  switch, but these will cause run-time
+         errors to be reported if any AST function is invoked which requires
+         facilities not in the implemented interface.
+
+         \sstitem
+         ``-grf\_v2.0\texttt{'} \texttt{'} : This switch is equivalent to the ``-mygrf\texttt{'} \texttt{'}  switch.
+         It indicates that you want to link with your own graphics module which
+         implements the 2D graphics interface required by V2.0 of AST.
+
+         \sstitem
+         ``-grf\_v3.2\texttt{'} \texttt{'} : Indicates that you want to link with your own graphics
+         module which implements the 2D graphics interface required by V3.2 of AST.
+
+         \sstitem
+         ``-grf\_v5.6\texttt{'} \texttt{'} : Indicates that you want to link with your own graphics
+         module which implements the 2D graphics interface required by V5.6 of AST.
+
+         \sstitem
+         ``-myerr\texttt{'} \texttt{'} : Requests that no arguments be generated to specify how
+         error messages produced by the AST library should be delivered. You
+         should use this option only if you have implemented an interface to a
+         new error delivery system yourself and wish to provide your own
+         arguments for linking with it. By default, error messages are delivered
+         in the standard ADAM way via the EMS Error Message Service (Starlink
+         \htmlref{System}{System} Note SSN/4).
+
+         \sstitem
+         ``-mygrf\texttt{'} \texttt{'} : This switch has been superceeded by the ``-grf\texttt{'} \texttt{'}  switch,
+         but is retained in order to allow applications to be linked with a
+         graphics module which implements the interface used by AST V2.0. It is
+         equivalent to the ``-grf\_v2.0\texttt{'} \texttt{'}  switch.
+
+         \sstitem
+         ``-pgp\texttt{'} \texttt{'} : Requests that the program be linked so that 2D
+         graphical output from the AST library is displayed via the
+         Starlink version of the PGPLOT graphics package (which uses GKS
+         for its output). By default, no graphics package is linked and
+         this will result in an error at run time if AST routines are
+         invoked that attempt to generate graphical output.
+
+         \sstitem
+         ``-pgplot\texttt{'} \texttt{'} : Requests that the program be linked so that 2D
+         graphical output from the AST library is displayed via the
+         standard (or ``native\texttt{'} \texttt{'} ) version of the PGPLOT graphics
+         package. By default, no graphics package is linked and this will
+         result in an error at run time if AST routines are invoked that
+         attempt to generate graphical output.
+
+         \sstitem
+         ``-grf3d\texttt{'} \texttt{'} : Requests that no arguments be generated to specify which
+         3D graphics system is used to display output from the AST library. You
+         should use this option only if you have implemented an interface to a
+         new 3D graphics system yourself and wish to provide your own arguments
+         for linking with it.
+
+         \sstitem
+         ``-pgp3d\texttt{'} \texttt{'} : Requests that the program be linked so that 3D
+         graphical output from the AST library is displayed via the
+         Starlink version of the PGPLOT graphics package (which uses GKS
+         for its output). By default, no 3D graphics package is linked and
+         this will result in an error at run time if AST routines are
+         invoked that attempt to generate graphical output.
+
+         \sstitem
+         ``-pgplot3d\texttt{'} \texttt{'} : Requests that the program be linked so that 3D
+         graphical output from the AST library is displayed via
+         the standard (or ``native\texttt{'} \texttt{'} ) version of the PGPLOT graphics
+         package. By default, no 3D graphics package is linked and this will
+         result in an error at run time if AST routines are invoked that
+         attempt to generate graphical output.
+      }
+   }
+   \sstdiytopic{
+      SLALIB
+   }{
+      The AST distribution includes a cut down subset of the C version of
+      the SLALIB library written by Pat Wallace. This subset contains only
+      the functions needed by the AST library. It is built as part of the
+      process of building AST and is distributed under GPL (and is thus
+      compatible with the AST license). Previous version of this script
+      allowed AST applications to be linked against external SLALIB
+      libraries (either Fortran or C) rather than the internal version.
+      The current version of this script does not provide this option,
+      and always uses the internal SLALIB library. However, for backward
+      compatibility, this script still allows the \texttt{"} -fsla\texttt{"}  and \texttt{"} -csla\texttt{"}  flags
+      (previously used for selecting which version of SLALIB to use) to be
+      specified, but they will be ignored.
+   }
+}
+\normalsize
+
+
+\newpage
+\section{\xlabel{FitsWcsCoverage}\label{ss:fitswcscoverage}FITS-WCS Coverage}
+
+This appendix gives details of the \htmlref{FitsChan}{FitsChan} class
+implementation of the conventions described in the FITS-WCS papers
+available at
+\url{http://fits.gsfc.nasa.gov/fits_wcs.html}. These conventions are
+used only if the \htmlref{Encoding}{Encoding} attribute of the FitsChan
+has the value ``FITS-WCS'' (whether set explicitly or defaulted). It
+should always be possible for a \htmlref{FrameSet}{FrameSet} to be read
+(using the
+\htmlref{AST\_READ}{AST\_READ}
+function) from a FitsChan containing a header which conforms to these
+conventions. However, only those FrameSets which are compatible with the
+FITS-WCS model can be \emph{written} to a FitsChan using the
+\htmlref{AST\_WRITE}{AST\_WRITE}
+function. For instance, if the current \htmlref{Frame}{Frame} of a
+FrameSet is re-mapped using, say, an arbitrary \htmlref{MathMap}{MathMap}
+then the FrameSet will no longer be compatible with the FITS-WCS model,
+and so will not be written out successfully to a FitsChan.
+
+The following sub-sections describe the details of the implementation of
+each of the first four FITS-WCS papers. Here, the term ``pixel axes'' is
+used to refer to the FITS pixel coordinates (i.e. the centre of the
+first image pixel has a value 1.0 on each pixel axis); the term ``IWC
+axes'' is used to refer to the axes of the Intermediate World Coordinate
+system; and the term ``WCS axes'' is used to refer to the axes of the final
+physical coordinate system described by the CTYPE\emph{i} keywords.
+
+\subsection{Paper I - General Linear Coordinates}
+When reading a \htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan}, these conventions are used if the CTYPE\emph{i} keyword
+values within the FitsChan do not conform to the conventions described in
+later papers, in which case the axes are assumed to be linear. When
+writing a FrameSet to a FitsChan, these conventions are used for axes
+which are described by a simple \htmlref{Frame}{Frame} (\emph{i.e.} not a
+\htmlref{SkyFrame}{SkyFrame}, \htmlref{SpecFrame}{SpecFrame}, \emph{etc.}).
+
+\htmlref{Table}{Table} \ref{tab:fitspaper1} describes the use made by AST of each keyword
+defined by FITS-WCS paper I.
+
+\begin{table}[htbp]
+\begin{tabular}{|l|p{2.5in}|p{2.5in}|}
+\hline
+\multicolumn{1}{|c|}{\textbf{Keyword}} & \multicolumn{1}{c|}{\textbf{Read}}
+& \multicolumn{1}{c|}{\textbf{Write}} \\ \hline
+
+\fitskey{WCSAXES\emph{a}}{Ignored.}{Set to the number of axes in the WCS
+Frame - only written if different to NAXIS.}
+
+\fitskey{CRVAL\emph{ia}}{Used to create the pixel to WCS
+\htmlref{Mapping}{Mapping}.}{Always written (see ``Choice of Reference
+Point'' below).}
+
+\fitskey{CRPIX\emph{ja}}{Used to create the pixel to WCS Mapping.}{Always
+written (see ``Choice of Reference Point'' below).}
+
+\fitskey{CDELT\emph{ia}}{Used to create the pixel to WCS Mapping.}{Only
+written if the \htmlref{CDMatrix}{CDMatrix} attribute of the FitsChan is
+set to zero.}
+
+\fitskey{CROTA\emph{i}}{Used to create the pixel to WCS Mapping.}{Only
+written in FITS-AIPS and FITS-AIPS++ encodings.}
+
+\fitskey{CTYPE\emph{ia}}{Used to choose the class and attributes of the
+WCS Frame, and to create the pixel to WCS Mapping (note, ``STOKES'' and
+``COMPLEX'' axes are treated as unknown linear axes).}{Always written
+(see ``Use and Choice of CTYPE keywords'' below).}
+
+\fitskey{CUNIT\emph{ia}}{Used to set the Units attributes
+of the WCS Frame.}{Only written if the Units attribute of the WCS Frame
+has been set explicitly. If so, the Units value for each axis is used as
+the CUNIT value.}
+
+\fitskey{PC\emph{i\_j}\emph{a}}{Used to create the pixel to WCS
+Mapping.}{Only written if the CDMatrix attribute of the FitsChan is set to
+zero.}
+
+\fitskey{CD\emph{i\_j}\emph{a}}{Used to create the pixel to WCS
+Mapping.}{Only written if the CDMatrix attribute of the FitsChan is set to
+a non-zero value.}
+
+\fitskey{PV\emph{i\_ma}}{Ignored for linear axes.}{Not written if the axes
+are linear.}
+
+\fitskey{PS\emph{i\_ma}}{Ignored.}{Not used.}
+
+\fitskey{WCSNAME\emph{a}}{Used to set the \htmlref{Domain}{Domain} attribute
+of the WCS Frame.}{Only written if the Domain attribute of the WCS Frame
+has been set explicitly. If so, the Domain value is used as the WCSNAME
+value.}
+
+\fitskey{CRDER\emph{ia}}{Ignored.}{Not used.}
+
+\fitskey{CSYER\emph{ia}}{Ignored.}{Not used.}
+
+\hline
+\end{tabular}
+\vspace{3.mm}
+\caption{Use of FITS-WCS Paper I keywords}
+\label{tab:fitspaper1}
+\end{table}
+
+\subsubsection{Requirements for a Successful Write Operation}
+When writing a \htmlref{FrameSet}{FrameSet} in which the WCS
+\htmlref{Frame}{Frame} is a simple Frame to a \htmlref{FitsChan}{FitsChan},
+success depends on the \htmlref{Mapping}{Mapping} from pixel coordinates
+(the base Frame in the FrameSet) to the WCS Frame being linear. The write
+operation will fail if this is not the case.
+
+\subsubsection{Use and Choice of CTYPE\emph{i} keywords}
+When reading a \htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan} the CTYPE\emph{i} values in the FitsChan are used to set the
+Symbol attributes of the corresponding WCS \htmlref{Frame}{Frame}. The Label attributes of the WCS Frame are set from
+the CNAME\emph{i} keywords, if present in the header. Otherwise they are set
+from the CTYPE\emph{i} comments strings in the header, so long as each
+axis has a unique non-blank comment. Otherwise, the Label attributes are
+set to the CTYPE\emph{i} values. The above procedure is over-ridden if
+the axis types conform to the conventions described in paper II or III,
+as described below.
+
+When writing a FrameSet to a FitsChan, each CTYPE\emph{i} value is set to
+the value of the Symbol attribute of the corresponding axis in the Frame
+being written. If a value has been set explicitly for the axis Label
+attribute, it is used as the axis comment (except that any existing
+comments in the FitsChan take precedence if the keyword value has not
+changed). The above procedure is over-ridden if the Frame is a
+\htmlref{SkyFrame}{SkyFrame} or a \htmlref{SpecFrame}{SpecFrame}, in which
+case the CTYPE\emph{i} value is derived from the \htmlref{System}{System}
+attribute of the Frame and the nature of the pixel to WCS \htmlref{Mapping}{Mapping}
+according to the conventions of papers II and III, as described below.
+
+\subsubsection{Choice of Reference Point}
+When writing a \htmlref{FrameSet}{FrameSet} to a
+\htmlref{FitsChan}{FitsChan}, the pixel coordinates of the
+reference point for linear axes (i.e. the CRPIX\emph{j} values) are
+chosen as follows:
+
+\begin{itemize}
+\item If the FrameSet is being written to a FitsChan which previously
+contained a set of axis descriptions with the same identifying letter,
+then the previous CRVAL\emph{j}values are converted into the coordinate system
+of the \htmlref{Frame}{Frame} being written (if possible). These values are then
+transformed into the pixel Frame, and the closest integer pixel values
+are used as the CRPIX keywords.
+\item If the above step could not be performed for any reason, the
+central pixel is used as the reference point. This requires the image
+dimensions to be present in the FitsChan in the form of a set of
+NAXIS\emph{j} keyword values.
+\item If both the above two steps failed for any axis, then the pixel
+reference position is set to a value of 1.0 on the pixel axis.
+\end{itemize}
+
+The pixel to WCS \htmlref{Mapping}{Mapping} is then used to find the corresponding
+CRVAL\emph{j}values.
+
+Again, the above procedure is over-ridden if the Frame is a
+\htmlref{SkyFrame}{SkyFrame} or a \htmlref{SpecFrame}{SpecFrame}, in which
+case the conventions of papers II and III are used as described below.
+
+
+\subsubsection{Choice of Axis Ordering}
+When reading a \htmlref{FrameSet}{FrameSet} from a
+\htmlref{FitsChan}{FitsChan}, WCS axis $i$ in the current
+\htmlref{Frame}{Frame} of the
+resulting FrameSet corresponds to axis $i$ in the FITS header.
+
+When writing a FrameSet to a FitsChan, the axis ordering for the FITS
+header is chosen to make the CD\emph{i\_j} or PC\emph{i\_j} matrix
+predominately diagonal. This means that the axis numbering in the FITS
+header will not necessarily be the same as that in the AST Frame.
+
+\subsubsection{Alternate Axis Descriptions}
+When reading a \htmlref{FrameSet}{FrameSet} from a
+\htmlref{FitsChan}{FitsChan} which contains alternate axis descriptions,
+each complete set of axis descriptions results in a single \htmlref{Frame}{Frame} being added
+to the final FrameSet, connected via an appropriate
+\htmlref{Mapping}{Mapping} to the base pixel Frame. The \htmlref{Ident}{Ident} attribute of the Frame is set to hold the single alphabetical
+character which is used to identify the set of axis descriptions within
+the FITS header (a single space is used for the primary axis descriptions).
+
+When writing a FrameSet to a FitsChan, it is assumed that the base Frame
+represents pixel coordinates, and the current Frame represents the
+primary axis descriptions. If there are any other Frames present in the
+FrameSet, an attempt is made to create a complete set of ``alternate''
+set of keywords describing each additional Frame. The first character in
+the Ident attribute of the Frame is used as the single character
+descriptor to be appended to the keyword, with the proviso that a given
+character can only be used once. If a second Frame is found with an Ident
+attribute which has already been used, its Ident attribute is ignored and
+the next free character is used instead. Note, failure to write a set of
+alternate axis descriptions does not result in failure of the entire
+write operation: the primary axis descriptions are still written,
+together with any other alternate axis descriptions which can be produced
+successfully.
+
+\subsection{Paper II - Celestial Coordinates}
+These conventions are used when reading a \htmlref{FrameSet}{FrameSet}
+from a \htmlref{FitsChan}{FitsChan} containing appropriate CTYPE\emph{i}
+values, and when writing a FrameSet in which the WCS \htmlref{Frame}{Frame}
+is a \htmlref{SkyFrame}{SkyFrame}.
+
+\htmlref{Table}{Table} \ref{tab:fitspaper2} describes the use made by AST of each keyword
+whose meaning is defined or extended by FITS-WCS paper II.
+
+\begin{table}[htbp]
+\begin{tabular}{|l|p{2.5in}|p{2.5in}|}
+\hline
+\multicolumn{1}{|c|}{\textbf{Keyword}} & \multicolumn{1}{c|}{\textbf{Read}}
+& \multicolumn{1}{c|}{\textbf{Write}} \\ \hline
+
+\fitskey{CTYPE\emph{ia}}{All coordinate systems and projection types
+listed in paper II are supported (note, ``CUBEFACE'' axes are treated as
+unknown linear axes). In addition, "-HPX" (HEALPix) and "-XPH" (polar
+HEALPix) are supported.}{Determined by the \htmlref{System}{System} attribute
+of the SkyFrame and the \htmlref{WcsType}{WcsType} attribute of the
+\htmlref{WcsMap}{WcsMap} within the FrameSet.}
+
+\fitskey{CUNIT\emph{ia}}{Ignored (assumed to be 'degrees').}{Not written.}
+
+\fitskey{PV\emph{i\_ma}}{Used to create the pixel to WCS \htmlref{Mapping}{Mapping} (values
+are stored as attributes of a WcsMap within this Mapping).}{Values are
+obtained from the WcsMap in the pixel to WCS Mapping.}
+
+\fitskey{LONPOLE\emph{a}}{Used to create the pixel to WCS Mapping. Also
+stored as a \htmlref{PVi\_m}{PVi\_m} attribute for the longitude axis of the WcsMap.}{Only
+written if not equal to the default value defined in paper II (see
+``Choice of LONPOLE/LATPOLE'' below).}
+
+\fitskey{LATPOLE\emph{a}}{Used to create the pixel to WCS Mapping. Also
+stored as a PV attribute for the longitude axis of the WcsMap.}{Only
+written if not equal to the default value defined in paper II (see
+``Choice of LONPOLE/LATPOLE'' below).}
+
+\fitskey{RADESYS\emph{a}}{Used to set the attributes of the SkyFrame. All
+values supported except that ecliptic coordinates are currently always
+assumed to be FK5.}{Always written. Determined by the System attribute of
+the SkyFrame.}
+
+\fitskey{EQUINOX\emph{a}}{Used to set the \htmlref{Equinox}{Equinox} attribute
+of the SkyFrame.}{Written if relevant. Determined by the Equinox attribute of
+the SkyFrame.}
+
+\fitskey{EPOCH}{Used to set the Equinox attribute of the SkyFrame.}{Only
+written if using FITS-AIPS and FITS-AIPS++ encodings. Determined by the Equinox attribute
+of the SkyFrame.}
+
+\fitskey{MJD-OBS}{Used to set the \htmlref{Epoch}{Epoch} attribute of the
+SkyFrame. DATE-OBS is used if MJD-OBS is not present. A default value based on
+RADESYS and EQUINOX is used if used if DATE-OBS is not present
+either.}{Determined by the Epoch attribute of the SkyFrame. Only written
+if this attribute has been set to an explicit value (in which case
+DATE-OBS is also written).}
+
+\hline
+\end{tabular}
+\vspace{3.mm}
+\caption{Use of FITS-WCS Paper II keywords}
+\label{tab:fitspaper2}
+\end{table}
+
+\subsubsection{Requirements for a Successful Write Operation}
+When writing a \htmlref{FrameSet}{FrameSet} in which the WCS
+\htmlref{Frame}{Frame} is a \htmlref{SkyFrame}{SkyFrame} to a
+\htmlref{FitsChan}{FitsChan}, success depends on the following conditions
+being met:
+
+\begin{enumerate}
+\item The \htmlref{Mapping}{Mapping} from pixel coordinates (the base Frame
+in the FrameSet) to the WCS SkyFrame includes a \htmlref{WcsMap}{WcsMap}.
+\item The Mapping prior to the WcsMap (\emph{i.e.} from pixel to IWC) is linear.
+\item The Mapping after the WcsMap (\emph{i.e.} from native spherical to
+celestial coordinates) is a spherical rotation for the
+celestial axes, and linear for any other axes.
+\item The \htmlref{TabOK}{TabOK} attribute is set to a non-zero positive value in the FitsChan,
+and the longitude and latitude axes are separable. In this case the Mapping will
+be described by a pair of 1-dimensional look-up tables, using the ``-TAB''
+algorithm described in FITS-WCS paper III.
+\end{enumerate}
+
+If none of the above conditions hold, the write operation will be
+unsuccessful.
+
+\subsubsection{Choice of LONPOLE/LATPOLE}
+When writing a \htmlref{FrameSet}{FrameSet} to a \htmlref{FitsChan}{FitsChan},
+the choice of LONPOLE and LATPOLE values is determined as follows:
+
+\begin{enumerate}
+
+\item If the projection represented by the \htmlref{WcsMap}{WcsMap} is
+azimuthal, then any values set for attributes ``PV\emph{i}\_3''
+and ``PV\emph{i}\_4'' (where ``\emph{i}'' is the index of the longitude axis)
+within the WcsMap are used as the LONPOLE and LATPOLE values. Reading a
+FrameSet from a FITS-WCS header
+results in the original LONPOLE and LATPOLE values being stored within a
+WcsMap within the FrameSet. Consequently, if a FrameSet is read from a
+FITS-WCS header and it is subsequently written out to a new FITS-WCS
+header, the original LONPOLE and LATPOLE values will usually be used in
+the new header (the exception being if the WcsMap has been explicitly
+modified before being written out again). Any extra rotation of the sky
+is absorbed into the CD\emph{i\_j} or PC\emph{i\_j} matrix (this is
+possible only if the projection is azimuthal).
+
+\item If the projection represented by the WcsMap is azimuthal but no
+values have been set for the ``PV\emph{i}\_3'' and ``PV\emph{i}\_4''
+attributes within the WcsMap, then the default LONPOLE and LATPOLE values
+are used. This results in no LONPOLE or LATPOLE keywords being stored in
+the header since default values are never stored. Any extra rotation of
+the sky is absorbed into the CD\emph{i\_j} or PC\emph{i\_j} matrix (this
+is possible only if the projection is azimuthal).
+
+\item If the projection represented by the WcsMap is not azimuthal,
+then the values of LONPOLE and LATPOLE are found by transforming the
+coordinates of the celestial north pole (\emph{i.e} longitude zero,
+latitude $+\pi/2$) into native spherical coordinates using the inverse of
+the \htmlref{Mapping}{Mapping} which follows the WcsMap.
+
+\end{enumerate}
+
+\subsubsection{User Defined Fiducial Points}
+When reading a \htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan}, projection parameters
+PV\emph{i}\_0, PV\emph{i}\_1 and PV\emph{i}\_2 (for longitude axis
+``\emph{i}'') are used to indicate a user-defined fiducial point as
+described in section 2.5 of paper II. This results in a shift of IWC
+origin being applied \emph{before} the \htmlref{WcsMap}{WcsMap} which converts
+IWC into
+native spherical coordinates. The values of these projection parameters,
+if supplied, are stored as the corresponding \htmlref{PVi\_m}{PVi\_m} attributes
+of the WcsMap.
+
+When writing a FrameSet to a FitsChan, the PV attributes of the WcsMap
+determine the native coordinates of the fiducial point (the fixed
+defaults for each projection described in paper II are used if the PV
+attributes of the WcsMap have not been assigned a value). The
+corresponding celestial coordinates are used as the CRVAL\emph{i}
+keywords and the corresponding pixel coordinates as the CRPIX\emph{j}
+keywords.
+
+\subsubsection{Common Non-Standard Features}
+A collection of common non-standard features are supported when reading a
+\htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan}, in addition
+to those embodied within the
+available encodings of the FitsChan class. These are translated into the
+equivalent standard features before being used to create a FrameSet.
+Note, the reverse operation is never performed: it is not possible to
+produce non-standard features when writing a FrameSet to a FitsChan
+(other than those embodied in the available encodings of the FitsChan
+class). The supported non-standard features include:
+
+\begin{itemize}
+\item EQUINOX keywords with string values equal to a date preceded
+by the letter B or J (\emph{e.g.} ``B1995.0'').
+
+\item EQUINOX or EPOCH keywords with value zero  (these are converted to
+B1950).
+
+\item The IRAF ``ZPX'' projection is represented by a
+\htmlref{WcsMap}{WcsMap} with type of
+AST\_\_ZPN. \htmlref{Projection}{Projection} parameter values are read from any WAT\emph{i\_nnn}
+keywords, and corresponding \htmlref{PVi\_m}{PVi\_m} attributes are set in the
+WcsMap. The WAT\emph{i\_nnn} keywords may specify corrections to the basic
+ZPN projection by including ``lngcor'' or ``latcor'' terms. These are
+supported if they use half cross-terms, in either simple or Chebyshev
+representation.
+
+\item The IRAF ``TNX'' projection is represented by a WcsMap with type of
+AST\_\_TPN (a distorted TAN projection retained within the WcsMap class
+from an early draft of the FITS-WCS paper II). Projection parameter values
+are read from any WAT\emph{i\_nnn} keywords, and corresponding PV
+attributes are set in the WcsMap. If the TNX projection cannot be
+converted exactly into an AST\_\_TPN projection, ASTWARN keywords are
+added to the FitsChan containing a warning message (but only if the
+\htmlref{Warnings}{Warnings} attribute of the FitsChan is set appropriately). Currently,
+TNX projections that use half cross-terms, in either simple or Chebyshev
+representation, are supported.
+
+\item ``QV'' parameters for TAN projections (as produced by
+\xref{AUTOASTROM}{sun242}{}
+\footnote{\url{http://www.astro.gla.ac.uk/users/norman/star/autoastrom/}}
+are renamed to the equivalent ``PV'' parameters.
+
+\item TAN projections that have associated ``PV'' parameters on the
+latitude axis are converted to the corresponding TPN (distorted TAN)
+projections. This conversion can be controlled using the \htmlref{PolyTan}{PolyTan} attribute
+of the FitsChan class.
+
+\end{itemize}
+
+\subsection{Paper III - Spectral Coordinates}
+These conventions are used when reading a \htmlref{FrameSet}{FrameSet}
+from a \htmlref{FitsChan}{FitsChan} which includes appropriate
+CTYPE\emph{i} values, and when writing a FrameSet in which
+the WCS \htmlref{Frame}{Frame} is a \htmlref{SpecFrame}{SpecFrame}.
+
+\htmlref{Table}{Table} \ref{tab:fitspaper3} describes the use made by AST of each keyword
+whose meaning is defined or extended by FITS-WCS paper III.
+
+\begin{table}[htbp]
+\begin{footnotesize}
+\begin{tabular}{|l|p{2.5in}|p{2.5in}|}
+\hline
+\multicolumn{1}{|c|}{\textbf{Keyword}} & \multicolumn{1}{c|}{\textbf{Read}}
+& \multicolumn{1}{c|}{\textbf{Write}} \\ \hline
+
+\fitskey{CTYPE\emph{ia}}{All coordinate systems and projection types
+listed in paper III are supported algorithm (the ``-LOG'' algorithm may
+also be applied to non-spectral linear axes; the ``-TAB'' algorithm
+requires the \htmlref{TabOK}{TabOK} attribute to be set in the FitsChan).}{Determined by the \htmlref{System}{System} attribute of the
+SpecFrame and the nature of the pixel to SpecFrame
+\htmlref{Mapping}{Mapping}.}
+
+\fitskey{CUNIT\emph{ia}}{Used to set the Units attribute of
+the SpecFrame (note, SpecFrames always have an ``active'' Units attribute
+(see astSetActiveUnit).}{Always written.}
+
+\fitskey{PV\emph{i\_ma}}{Used to create the pixel to WCS Mapping (values
+are stored as attributes of a \htmlref{GrismMap}{GrismMap}).}
+{Set from the attributes of the GrismMap, if present, and if set explicitly.}
+
+\fitskey{SPECSYS\emph{a}}{Used to set the \htmlref{StdOfRest}{StdOfRest}
+attribute of the SpecFrame (all systems are supported except CMBDIPOL).}
+{Set from the StdOfRest attribute of the SpecFrame, but only if it has been
+set explicitly.}
+
+\fitskey{SSYSOBS\emph{a}}{Ignored.}{Never written.}
+
+\fitskey{OBSGEO-X/Y/Z}{Used to set the \htmlref{ObsLon}{ObsLon} and
+\htmlref{ObsLat}{ObsLat} attributes of the Frame (the observers
+height above sea level is ignored).}{Set from the ObsLon and ObsLat
+attributes of the Frame, if they have been set explicitly (it is
+assumed that the observer is at sea level).}
+
+\fitskey{MJD-AVG}{Used to set the \htmlref{Epoch}{Epoch} attributes of
+the SpecFrame.}{Set from the Epoch attribute of the SpecFrame, if it has
+been set explicitly.}
+
+\fitskey{SSYSSRC\emph{a}}{Used to set the \htmlref{SourceVRF}{SourceVRF} attribute of the
+SpecFrame
+(all systems are supported except CMBDIPOL).} {Set from the SourceVRF
+attribute of the SpecFrame.}
+
+\fitskey{ZSOURCE\emph{a}}{Used to set the \htmlref{SourceVel}{SourceVel}
+attribute of the SpecFrame (the SourceVRF attribute
+is first set to the system indicated by the SSYSSRC keyword, and the
+ZSOURCE value is then converted to an apparent radial velocity and stored
+as the SourceVel attribute).}
+{Set from the SourceVel attribute of
+the SpecFrame, if it has been set explicitly (the SourceVel value is
+first converted from apparent radial velocity to redshift).}
+
+\fitskey{VELOSYS\emph{a}}{Ignored.}{Set from the attributes of the
+SpecFrame that define the standard of rest and the observers position.}
+
+\fitskey{RESTFRQ\emph{a}}{Used to set the \htmlref{RestFreq}{RestFreq}
+attribute of the SpecFrame.}{Set from the RestFreq attribute of the
+SpecFrame, but only if the System attribute is not set to
+``WAVE'', ``VOPT'', ``ZOPT'' or ``AWAV'', and only if RestFreq has been set
+explicitly.}
+
+\fitskey{RESTWAV\emph{a}}{Used to set the RestFreq
+attribute of the SpecFrame (after conversion from wavelength to frequency).}
+{Set from the RestFreq attribute of the SpecFrame (after conversion), but only if the
+System attribute is set to ``WAVE'', ``VOPT'', ``ZOPT'' or
+``AWAV'', and only if RestFreq has been set explicitly.}
+
+\fitskey{CNAME\emph{ia}}{Used to set the Label attributes of
+the WCS Frame keywords.}{Set from the Label attributes of the WCS Frame,
+if they have been set explicitly.}
+\hline
+\end{tabular}
+\end{footnotesize}
+\vspace{3.mm}
+\caption{Use of FITS-WCS Paper III keywords}
+\label{tab:fitspaper3}
+\end{table}
+
+\subsubsection{Requirements for a Successful Write Operation}
+When writing a \htmlref{FrameSet}{FrameSet} in which the WCS \htmlref{Frame}{Frame} is a \htmlref{SpecFrame}{SpecFrame} to a
+\htmlref{FitsChan}{FitsChan}, the write operation is successful only if
+the \htmlref{Mapping}{Mapping} from pixel coordinates (the base Frame
+in the FrameSet) to the SpecFrame satisfies one of the following conditions:
+
+\begin{enumerate}
+\item It is linear.
+\item It is logarithmic.
+\item It is linear if the SpecFrame were to be re-mapped into one of the
+other spectral systems supported by FITS-WCS paper III.
+\item It contains a \htmlref{GrismMap}{GrismMap}, and the Mapping before the GrismMap (from
+pixel coordinates to grism parameter) is linear, and the Mapping after the
+GrismMap is either null or represents a change of spectral system from wavelength (air or
+vacuum) to one of the supported spectral systems.
+\item The \htmlref{TabOK}{TabOK} attribute is set to a non-zero positive value in the FitsChan.
+\end{enumerate}
+
+If none of the above conditions hold, the write operation will be
+unsuccessful. Note, if the FitsChan's TabOK attribute is set to a positive
+non-zero value then any Mapping that does not meet any of the earlier conditions
+will be written out as a look-up table, using the ``-TAB'' algorithm described
+in FITS-WCS paper III. If the TabOK attribute is to zero (the default) or
+negative in the FitsChan, then the write operation will be unsuccessful unless
+one of the eaerlier conditions is met.\footnote{If the -TAB algorithm is used, the
+positive value of the TabOK attribute is used as the table version number
+(the EXTVER header) in the associated FITS binary table.}
+
+\subsubsection{Common Non-Standard Features}
+The following non-standard features are supported when reading spectral
+axes from a \htmlref{FitsChan}{FitsChan}:
+
+\begin{itemize}
+\item Conversion of ``-WAV'', ``-FRQ'' and ``-VEL'' algorithm codes
+(specified in early drafts of paper III) to the corresponding
+``-X2P'' form.
+\item Conversion of ``RESTFREQ'' to ``RESTFRQ''
+\end{itemize}
+
+\subsection{Paper IV - Coordinate Distortions}
+
+This paper proposes that an additional 4 character code be appended to
+the end of the CTYPE\emph{i} keyword to specify the nature of any
+distortion away from the basic algorithm described by the first 8
+characters of the CTYPE\emph{i} value. Currently AST ignores all such
+codes when reading a \htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan} (except for the ``-SIP'' code
+defined by the Spitzer Space Telescope project - see below). This means that
+a FrameSet can still be read from such headers, but the \htmlref{Mapping}{Mapping} which gives
+the WCS position associated with a given pixel position will reflect only
+the basic algorithm and will not include the effects of the distortion.
+
+If such a FrameSet is then written out to a FitsChan, the resulting
+CTYPE\emph{i} keywords will include no distortion code.
+
+\subsubsection{The ``-SIP'' distortion code}
+
+The Spitzer Space Telescope project
+(\url{http://www.spitzer.caltech.edu/})
+has developed its own system for encoding 2-dimensional image distortion
+within a FITS header, based on the proposals of paper IV. A description
+of this system is available in
+\url{http://ssc.spitzer.caltech.edu/postbcd/doc/shupeADASS.pdf}. In this
+system, the presence of distortion is indicated by appending the
+distortion code ``-SIP'' to the CTYPE\emph{i} keyword values for the
+celestial axes. The distortion takes the form of a polynomial function
+which is applied to the pixel coordinates, after subtraction of the
+CRPIX\emph{j} values.
+
+This system is a strictly 2 dimensional system. When reading a
+\htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan} which
+includes the ``-SIP'' distortion code, AST assumes that it
+is only applied to the first 2 WCS axes in a FITS header (i.e.
+CTYPE1 and CTYPE2). If the ``-SIP'' distortion code is attached to other
+axes, it will be ignored. The distortion itself is represented by a
+\htmlref{PolyMap}{PolyMap} within the resulting FrameSet.
+
+If a FrameSet is read from a FitsChan which includes ``-SIP''
+distortion, and an attempt is then made to write this FrameSet out to a
+FitsChan, the write operation will fail unless the distortion is
+insignificant (\emph{i.e.} is so small that the tests for linearity built
+into AST are passed). In this case, no distortion code will be appended to
+the resulting CTYPE\emph{i} keyword values.
+
+\newpage
+\section{\xlabel{changes_and_new_features}\label{ss:changes}Release Notes}
+
+\subsection{Changes Introduced in V1.1}
+
+The following describes the most significant changes which occurred in
+the AST library between versions V1.0 and V1.1 (not the most recent
+version):
+
+\begin{enumerate}
+
+\item A new ``How To\ldots'' section (\secref{ss:howto}) has been
+added to this document. It contains simple recipies for performing
+commonly-required operations using AST.
+
+\item A new \htmlref{AST\_UNFORMAT}{AST\_UNFORMAT} function has been provided to read formatted
+coordinate values for the axes of a \htmlref{Frame}{Frame}
+(\secref{ss:unformattingaxisvalues}). In essence, this function is the
+inverse of \htmlref{AST\_FORMAT}{AST\_FORMAT}. It may be used to decode user-supplied
+formatted values representing coordinates, turning them into numerical
+values for processing.  Celestial coordinates may also be read using
+this function (\secref{ss:unformattingskyaxisvalues}) and free-format
+input is supported.
+
+\item The Format attribute string used by a \htmlref{SkyFrame}{SkyFrame} when formatting
+celestial coordinate values now allows the degrees/hours field to be
+omitted, so that celestial coordinates may be given in (\emph{e.g.})
+arc-minutes and/or arc-seconds
+(\secref{ss:formattingskyaxisvalues}). As a result, the degrees/hours
+field is no longer included by default.  A new ``t'' format specifier
+has been introduced (see the Format attribute) to allow minutes and/or
+seconds of time to be specified if required.
+
+\item A new routine \htmlref{AST\_MAPBOX}{AST\_MAPBOX} has been introduced. This allows you
+to find the extent of a ``bounding box'' which just encloses another
+box after it has been transformed by a \htmlref{Mapping}{Mapping}. A typical use might be
+to calculate the size which an image would have if it were transformed
+by the Mapping.
+
+\item A new class of \htmlref{Object}{Object}, the \htmlref{IntraMap}{IntraMap}, has been introduced
+(\secref{ss:intramaps}). This is a specialised form of Mapping which
+encapsulates a privately-defined coordinate transformation routine
+(\emph{e.g.}\ written in Fortran) so that it may be used like any
+other AST Mapping. This allows you to create Mappings that perform any
+conceivable coordinate transformation.
+
+\item The internal integrity of a \htmlref{FrameSet}{FrameSet} is now automatically
+preserved whenever changes are made to any attributes which affect the
+current Frame (either by setting or clearing their values). This is
+accomplished by appropriately re-mapping the current Frame to account
+for any change to the coordinate system which it represents
+(\secref{ss:framesetintegrity}).
+
+\item The internal structure of a FrameSet is now automatically tidied
+to eliminate redundant nodes whenever any of its Frames is removed or
+re-mapped. Automatic simplification of any compound Mappings which
+result may also occur. The effect of this change is to prevent the
+accumulation of unnecessary structure in FrameSets which are
+repeatedly modified.
+
+\item Some improvements have been made to the algorithms for
+simplifying compound Mappings, as used by \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}.
+
+\item The textual representation used for some Objects
+(\emph{i.e.}\ when they are written to a \htmlref{Channel}{Channel}) has changed
+slightly, but remains compatible with earlier versions of AST.
+
+
+\item A problem has been fixed which could result when using \htmlref{AST\_READ}{AST\_READ}
+to read FITS headers in which the CDELT value is zero. Previously,
+this could produce a Mapping whose inverse transformation was not
+defined and this could unnecessarily restrict the use to which it
+could be put. The problem has been overcome by supplying a suitable
+small CDELT value for FITS axes which have only a single pixel.
+
+\item A bug has been fixed which could occasionally cause a \htmlref{MatrixMap}{MatrixMap}
+to be used with the wrong \htmlref{Invert}{Invert} attribute value when it forms part of
+a compound Mapping which is being simplified using AST\_SIMPLIFY.
+
+\item A bug has been fixed which could cause the AST\_\_BAD parameter
+to have an incorrect value on some platforms.
+
+\item A problem has been fixed which could prevent tick marks being
+drawn on a coordinate axis close to a singularity in the coordinate
+system.
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.2}
+
+The following describes the most significant changes which occurred in
+the AST library between versions V1.1 and V1.2 (not the most recent
+version):
+
+\begin{enumerate}
+\item A new routine, \htmlref{AST\_POLYCURVE}{AST\_POLYCURVE}, has been introduced to allow more
+efficient plotting of multiple geodesic curves
+(\secref{ss:plottinggeodesics}).
+
+\item A new set of functions, \htmlref{AST\_RESAMPLE$<$X$>$}{AST\_RESAMPLE$<$X$>$}, has been
+introduced to perform resampling of gridded data such as images
+(\emph{i.e.}\ re-gridding) under the control of a geometrical
+transformation specified by a \htmlref{Mapping}{Mapping}.
+
+\item The command-line options ``$-$pgp'' and ``$-$pgplot'', which
+were previously synonymous when used with the ``\htmlref{ast\_link}{ast\_link}'' and
+``\htmlref{ast\_link\_adam}{ast\_link\_adam}'' commands, are no longer synonymous. The option
+``$-$pgp'' now causes linking with the Starlink version of PGPLOT
+(which uses GKS to generate its output), while ``$-$pgplot'' links
+with the standard (or ``native'') version of PGPLOT.
+
+\item The routine \htmlref{AST\_MAPBOX}{AST\_MAPBOX} has been changed to execute more
+quickly, although this has been achieved at the cost of some loss of
+robustness when used with difficult Mappings.
+
+\item A new value of ``FITS-IRAF'' has been introduced for the
+\htmlref{Encoding}{Encoding} attribute of a \htmlref{FitsChan}{FitsChan}. This new encoding provides an
+interim solution to the problem of storing coordinate system
+information in FITS headers, until the proposed new FITS-WCS standard
+becomes stable.
+
+\item When a \htmlref{FrameSet}{FrameSet} is created from a set of FITS header cards (by
+reading from a FitsChan using a ``foreign'' encoding), the base \htmlref{Frame}{Frame}
+of the resulting FrameSet now has its \htmlref{Domain}{Domain} attribute set to
+``GRID''. This reflects the fact that this Frame represents FITS data
+grid coordinates (equivalent to FITS pixel coordinates---see
+\secref{ss:domainconventions}). Previously, this Domain value was not
+set.
+
+\item \htmlref{AST\_FINDFITS}{AST\_FINDFITS} now ignores trailing spaces in its keyword template.
+
+\item \htmlref{AST\_PUTFITS}{AST\_PUTFITS} now recognises ``D'' and ``d'' as valid exponent
+characters in floating point numbers.
+
+\item The FitsChan class is now more tolerant of common minor
+violations of the FITS standard.
+
+\item The FitsChan class now incorporates an improved test for the
+linearity of Mappings, allowing more reliable conversion of AST data
+into FITS (using ``foreign'' FITS encodings).
+
+\item Some further improvements have been made to the algorithms for
+simplifying compound Mappings, as used by \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}.
+
+\item A new \htmlref{UnitRadius}{UnitRadius} attribute has been added to the \htmlref{SphMap}{SphMap}
+class. This allows improved simplification of compound Mappings
+(CmpMaps) involving SphMaps and typically improves performance when
+handling FITS world coordinate information.
+
+\item A \htmlref{MatrixMap}{MatrixMap} no longer propagates input coordinate values of
+AST\_\_BAD automatically to all output coordinates. If certain output
+coordinates do not depend on the affected input coordinate(s) because
+the relevant matrix elements are zero, then they may now remain valid.
+
+\item A minor bug has been corrected which could cause certain
+projections which involve half the celestial sphere to produce valid
+coordinates for the other (unprojected) half of the sphere as well.
+
+\item A bug has been fixed which could occasionally cause \htmlref{AST\_CONVERT}{AST\_CONVERT}
+to think that conversion between a \htmlref{CmpFrame}{CmpFrame} and another Frame was
+possible when, in fact, it wasn't.
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.3}
+
+The following describes the most significant changes which occurred in
+the AST library between versions V1.2 and V1.3 (not the most recent
+version):
+
+\begin{enumerate}
+\item A new set of functions, \htmlref{AST\_RESAMPLE$<$X$>$}{AST\_RESAMPLE$<$X$>$}, has been introduced to
+provide	efficient resampling of gridded data, such as spectra and
+images, under the control of a geometrical transformation specified by
+a \htmlref{Mapping}{Mapping}. A variety of sub-pixel interpolation schemes are supported.
+
+\item A new class, \htmlref{PcdMap}{PcdMap}, has been introduced. This is a specialised
+form of Mapping which implements 2-dimensional pincushion or barrel
+distortion.
+
+\item A bug has been fixed which could cause a \htmlref{FitsChan}{FitsChan} to produce too
+many digits when formatting floating point values for inclusion in a
+FITS header if the numerical value was in the range -0.00099999\ldots
+to -0.0001.
+
+\item A bug has been fixed which could cause a FitsChan to lose the
+comment associated with a string value in a FITS header.
+
+\item A FitsChan now reports an error if it reads a FITS header which
+identifies a non-standard sky projection (previously, this was
+accepted without error and a Cartesian projection used instead).
+
+\item A bug has been fixed which could prevent conversion between the
+coordinate systems represented by two CmpFrames. This could only occur
+if the CmpFrames contained a relatively large number of nested Frames.
+
+%\item A bug has been fixed which could cause a program to crash if
+%FrameSets were nested inside each other (for example, if one \htmlref{FrameSet}{FrameSet}
+%had another FrameSet added to it for use as a \htmlref{Frame}{Frame} or Mapping). The
+%problem could only occur if the nested structure was loaded from a data
+%c+
+%file (using astRead).
+%c-
+%f+
+%file (using \htmlref{AST\_READ}{AST\_READ}).
+%f-
+%
+\item Further improvements have been made to the simplification of
+compound Mappings, including fixes for several bugs which could cause
+indefinite looping or unwanted error messages.
+
+\item Some memory leaks have been fixed.
+
+\item A small number of documentation errors have been corrected.
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.4}
+
+The following describes the most significant changes which have occurred
+in the AST library between versions V1.3 and V1.4 (not the most recent
+version):
+
+\begin{enumerate}
+\item A new \htmlref{MathMap}{MathMap} class has been introduced. This is a form of
+\htmlref{Mapping}{Mapping} that allows you to define coordinate transformations in a
+flexible and transportable way using arithmetic operations and
+mathematical functions similar to those available in Fortran.
+
+\item {\bf{WARNING---INCOMPATIBLE CHANGE.}} Transformation routines
+used with the \htmlref{IntraMap}{IntraMap} class (see, for example, \htmlref{AST\_INTRAREG}{AST\_INTRAREG}) now
+require a THIS pointer as their first argument. \textbf{Existing
+implementations will not continue to work correctly with this version
+of AST unless this argument is added.} There is no need for existing
+software to make use of this pointer, but it must be present.
+
+This change has been introduced so that transformation functions can gain
+access to IntraMap attributes.
+
+\item A new \htmlref{IntraFlag}{IntraFlag} attribute has been added to the IntraMap
+class. This allows the transformation routines used by IntraMaps to
+adapt to produce the required transformation on a per-IntraMap basis
+(\secref{ss:intraflag}).
+
+\item The \htmlref{Plot}{Plot} attributes MajTickLen and MinTickLen, which control the
+length of major and minor tick marks on coordinate axes, may now be
+subscripted using an axis number. This allows tick marks of different
+lengths to be used on each axis. It also allows tick marks to be
+suppressed on one axis only by setting the length to zero.
+
+\item The value of the Plot attribute NumLab, which controls the
+plotting of numerical labels on coordinate axes, no longer has any
+effect on whether labelling of a coordinate grid is interior or
+exterior (as controlled by the \htmlref{Labelling}{Labelling} attribute).
+
+\item The \htmlref{FitsChan}{FitsChan} class now provides some support for the
+IRAF-specific ``ZPX'' sky projection, which is converted transparently
+into the equivalent FITS ``ZPN'' projection (see the description of the
+\htmlref{Encoding}{Encoding} attribute for details).
+
+\item The FitsChan class now recognises the coordinate system ``ICRS''
+(International Celestial Reference \htmlref{System}{System}) as equivalent to
+``FK5''. This is an interim measure and full support for the
+(exceedingly small) difference between ICRS and FK5 will be added at a
+future release.
+
+Note that ``ICRS'' is not yet recognised as a coordinate system by other
+classes such as \htmlref{SkyFrame}{SkyFrame}, so this change only facilitates the
+importation of foreign data.
+
+\item A bug in the FitsChan class has been fixed which could result in
+longitude values being incorrect by 180 degrees when using cylindrical
+sky projections, such as the FITS ``CAR'' projection.
+
+\item A bug in the FitsChan class has been fixed which could result in
+the FITS sky projection parameters ProjP(0) to ProjP(9) being
+incorrectly named PROJP1 to PROJP10 when written out as FITS cards.
+
+\item A bug in the FitsChan class has been fixed which could cause
+confusion between the FITS-IRAF and FITS-WCS encoding schemes if both
+a CD matrix and a PC matrix are erroneously present in a FITS header.
+
+\item Some minor memory leaks have been fixed.
+
+\item A small number of documentation errors have been corrected.
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.5}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V1.4 and V1.5 (not the most
+recent version):
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class has been modified to support the latest draft
+FITS WCS standard, described in the two papers ``Representation of world
+coordinates in FITS'' (E.W.\,Greisen and M.\,Calabretta, dated 30th
+November, 1999), and ``Representation of celestial coordinates in FITS''
+(M.\,Calabretta and E.W.\,Greisen, dated 24th September, 1999). These are
+available at
+\url{http://www.cv.nrao.edu/fits/documents/wcs/wcs.html}.
+
+The FITS-WCS encoding now uses these updated conventions. The main
+changes are:
+
+\begin{itemize}
+\item Rotation and scaling of pixel axes is now represented by a matrix
+of \texttt{CDj\_i} keywords instead of a combination of \texttt{PCjjjiii} and
+\texttt{CDELTj} keywords.
+\item \htmlref{Projection}{Projection} parameters are now associated with particular axes and
+are represented by \texttt{\htmlref{PVi\_m}{PVi\_m}} keywords instead of the \texttt{PROJPm}
+keywords.
+\item The tangent plane projection (``TAN'') can now include optional
+polynomial correction terms.
+\item An entire set of keywords must be supplied for each set of secondary
+axis descriptions, and each such keyword must finish with a single
+character indicating which set it belongs to. This means that keywords
+which previously occupied eight characters have been shorten to seven to
+leave room for this extra character. Thus \texttt{LONGPOLE} has become \texttt{LONPOLE} and \texttt{RADECSYS} has become \texttt{RADESYS}.
+\end{itemize}
+
+\item Two new encodings have been added to the FitsChan class:
+\begin{description}
+
+\item [FITS-PC] This encoding uses the conventions of the now superseded
+FITS WCS paper by E.W.\,Greisen and M.\,Calabretta which used keywords
+\texttt{CDELTj} and \texttt{PCjjjiii} to describe axis scaling and rotation.
+These are the conventions which were used by the FITS-WCS encoding prior
+to version 1.5 of AST. This encoding is provided to allow existing data
+which use these conventions to be read. It should not in general be used
+to create new data.
+
+\item [FITS-AIPS] This encoding is based on the conventions described in the
+document ``Non-linear Coordinate Systems in AIPS'' by Eric W. Greisen
+(revised 9th September, 1994 and available by ftp from fits.cv.nrao.edu
+/fits/documents/wcs/aips27.ps.Z). This encoding uses \texttt{CROTAi} and
+\texttt{CDELTi} keywords to describe axis rotation and scaling.
+
+\end{description}
+
+\item The FitsChan class now provides some support for the IRAF-specific
+``TNX'' sky projection, which is converted transparently into the
+equivalent FITS ``TAN'' projection (see the description of the \htmlref{Encoding}{Encoding}
+attribute for details).
+
+\item FrameSets originally read from a DSS encoded FITS header can now be
+written out using the FITS-WCS encoding (a TAN projection with correction
+terms will be used) in addition to the DSS encoding. The reverse is also
+possible: FrameSets originally read from a FITS-WCS encoded FITS header
+and which use a TAN projection can now be written out using the DSS
+encoding.
+
+\item The algorithm used by the FitsChan class to verify that a \htmlref{FrameSet}{FrameSet}
+conforms to the FITS-WCS model has been improved so that FrameSets
+including more complex mixtures of parallel and serial Mappings
+can be written out using the FITS-WCS encoding.
+
+\item The FitsChan class has been changed so that long strings included in
+the description of an \htmlref{Object}{Object} can be saved and restored without truncation
+when using the NATIVE encoding. Previously, very long \htmlref{Frame}{Frame} titles,
+mathematical expressions, \emph{etc.} were truncated if they exceeded the
+capacity of a single FITS header card. They are now split over several
+header cards so that they can be restored without truncation. Note, this
+facility is only available when using NATIVE encoding.
+
+\item The FitsChan class has a new attribute called \htmlref{Warnings}{Warnings} which
+can be used to select potentially dangerous conditions under which
+warnings should be issued. These conditions include (for instance)
+unsupported features within non-standard projections, missing keywords
+for which default values will be used, \emph{etc}.
+
+\item The \htmlref{WcsMap}{WcsMap} class has been changed to support the changes made to the
+FITS-WCS encoding in the FitsChan class:
+\begin{itemize}
+\item Projection parameters are now associated with a particular axis and
+are specified using a new set of attributes called PVj\_m. Here, ``j'' is
+the index of an axis of WcsMap, and ``m'' is the index of the projection
+parameter.
+\item The old attributes ProjP(0) to ProjP(9) are still available but are
+now deprecated in favour of the new PVj\_m attributes. They are interpreted
+as aliases for PV(axlat)\_0 to PV(axlat)\_9, where ``axlat'' is the index of
+the latitude axis.
+\item The GLS projection projection has been renamed as SFL, but the
+AST\_\_GLS type has been retained as an alias for AST\_\_SFL.
+\end{itemize}
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.6}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V1.5 and V1.6:
+
+\begin{enumerate}
+
+
+\item A bug has been fixed in the \htmlref{Plot}{Plot} class which could cause groups
+of tick marks to be skipped when using very small gaps.
+
+\item A bug has been fixed in the Plot class which could cause axes to be
+labeled outside the visible window, resulting in no axes being visible.
+
+\item The FITS-WCS encoding used by the \htmlref{FitsChan}{FitsChan} class now includes the
+WCSNAME keyword. When creating a \htmlref{FrameSet}{FrameSet} from FITS headers, the values of
+the WCSNAME keywords are now used as the \htmlref{Domain}{Domain} names for the corresponding
+Frames in the returned FrameSet. When writing a FrameSet to a FITS header
+the Domain names of each \htmlref{Frame}{Frame} are stored in WCSNAME keywords in the
+header.
+
+\item The FITS-WCS encoding used by the FitsChan class now attempts to
+retain the identification letter associated with multiple axis
+descriptions. When reading a FrameSet from a FITS header, the identification
+letter is stored in the \htmlref{Ident}{Ident} attribute for each Frame. When writing a
+FrameSet to a FITS header, the identification letter is read from the
+Ident attribute of each Frame. The letter to associate with each Frame
+can be changed by assigning a new value to the Frame's Ident attribute.
+
+\item The FITS-WCS, FITS-PC, FITS-IRAF and FITS-AIPS encodings used by the
+FitsChan class now create a \htmlref{SkyFrame}{SkyFrame} with the \htmlref{System}{System} attribute set to
+``Unknown'' if the CTYPE keywords in the supplied header refers to an
+unknown celestial coordinate system. Previously, a Frame was used instead
+of a SkyFrame.
+
+\item The FITS-WCS, FITS-PC, FITS-IRAF and FITS-AIPS encodings used by the
+FitsChan class no longer report an error if the FITS header contains no
+CTYPE keywords. It is assumed that a missing CTYPE keyword implies that
+the world coordinate system is linear and identically equal to
+``intermediate world coordinates''.
+
+\item The new value ``noctype'' is now recognized by the \htmlref{Warnings}{Warnings} attribute
+of the FitsChan class. This value causes warnings to be issued if CTYPE
+keywords are missing from foreign encodings.
+
+\item A new attribute called \htmlref{AllWarnings}{AllWarnings} has been added to the FitsChan
+class. This is a read-only, space separated list of all the known condition
+names which can be specified in the Warnings attribute.
+
+\item The FitsChan class now attempts to assigns a \htmlref{Title}{Title} to each Frame in
+a FrameSet read using a foreign encoding. The Title is based on the Domain
+name of the Frame. If the Frame has no Domain name, the default Title
+supplied by the Frame class is retained.
+
+\item The FitsChan class uses the comments associated with CTYPE
+keywords as axis labels when reading a foreign encoding. This behaviour
+has been modified so that the default labels provided by the Frame class
+are retained (instead of using the CTYPE comments) if any of the CTYPE
+comments are identical.
+
+\item A new ``interpolation'' scheme identified by the symbolic constant
+AST\_\_BLOCKAVE has been added to the \htmlref{AST\_RESAMPLE$<$X$>$}{AST\_RESAMPLE$<$X$>$} set of
+functions. The new scheme calculates each output pixel value by finding
+the mean of the input pixels in a box centred on the output pixel.
+
+\item The SkyFrame class can now be used to represent an arbitrary spherical
+coordinate system by setting its System attribute to ``Unknown''.
+
+\item The indices of the latitude and longitude axes of a SkyFrame can
+now be found using new read-only attributes \htmlref{LatAxis}{LatAxis} and \htmlref{LonAxis}{LonAxis}. The
+effects of any axis permutation is taken into account.
+
+\item A new attribute called Ident has been added to the \htmlref{Object}{Object} class.
+This serves the same purpose as the existing \htmlref{ID}{ID} attribute, but (unlike ID)
+its value is transferred to the new Object when a copy is made.
+
+\item A bug has been fixed which could prevent complex CmpFrames
+behaving correctly (for instance, resulting in the failure of attempts
+to find a \htmlref{Mapping}{Mapping} between a \htmlref{CmpFrame}{CmpFrame} and itself).
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.7}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V1.6 and V1.7:
+
+\begin{enumerate}
+
+\item The \htmlref{Frame}{Frame} class has a new method called
+\htmlref{AST\_ANGLE}{AST\_ANGLE}
+which returns the angle subtended by two points at a third point within a
+2 or 3 dimensional Frame.
+
+\item The Frame class has a new method called
+\htmlref{AST\_OFFSET2}{AST\_OFFSET2}
+which calculates a position which is offset away from a given starting
+point by a specified distance along a geodesic curve which passes
+through the starting point at a given position angle. It can only be used
+with 2-dimensional Frames.
+
+\item The Frame class has a new method called
+\htmlref{AST\_AXDISTANCE}{AST\_AXDISTANCE}
+which returns the increment between two supplied axis values. For
+axes belonging to SkyFrames, the returned value is normalized into
+the range $\pm\pi$.
+
+\item The Frame class has a new method called
+\htmlref{AST\_AXOFFSET}{AST\_AXOFFSET}
+which returns an axis value a given increment away from a specified axis
+value. For axes belonging to SkyFrames, the returned value is normalized into
+the range $\pm\pi$ (for latitude axes) or zero to $2\pi$ (for longitude
+axes).
+
+\item The \htmlref{Plot}{Plot} class has a new method called
+\htmlref{AST\_GENCURVE}{AST\_GENCURVE}
+which allows generalised user-defined curves to be drawn. The curve is
+defined by a user-supplied \htmlref{Mapping}{Mapping} which maps distance along the curve
+into the corresponding position in the current Frame of the Plot. The new
+method then maps these current Frame position into graphics coordinates,
+taking care of any non-linearities or discontinuities in the mapping.
+
+\item The Plot class has a new method called
+\htmlref{AST\_GRFSET}{AST\_GRFSET}
+which allows the underlying primitive graphics functions to be selected
+at run-time. Previously, the functions used by the Plot class to produce
+graphics could only be selected at link-time, using the options of the
+\htmlref{ast\_link}{ast\_link} command. The new Plot method allows an application to over-ride
+the functions established at link-time, by specifying alternative
+primitive graphics routines. In addition, the two new Plot methods
+\htmlref{AST\_GRFPUSH}{AST\_GRFPUSH} and \htmlref{AST\_GRFPOP}{AST\_GRFPOP}
+allow the current graphics routines to be saved and restore on a
+first-in-last-out stack, allowing temporary changes to be made to the set
+of registered graphics routines.
+
+\item The DrawAxes attribute of the Plot class can now be specified
+independantly for each axis, by appending the axis index to the
+end of the attribute name.
+
+\item A bug has been fixed in the Plot class which could result in axis
+labels being drawn on inappropriate edges of the plotting box when using
+``interior'' labelling.
+
+\item A bug has been fixed in the \htmlref{IntraMap}{IntraMap} class which could cause IntraMaps
+to be corrupted after transforming any points.
+
+\item Bugs have been fixed in the \htmlref{FitsChan}{FitsChan} class which could cause
+inappropriate ordering of headers within a FitsChan when writing or
+reading objects using NATIVE encodings.
+
+\item A bug has been fixed in the FitsChan class which could cause the
+celestial longitude of a pixel to be estimated incorrectly by 180 degrees
+if the reference point is at either the north or the south pole.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V1.8-2}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V1.7 and V1.8-2:
+
+\begin{enumerate}
+
+\item The \htmlref{SkyFrame}{SkyFrame} class has a new attribute called \htmlref{NegLon}{NegLon} which allows
+   longitude values to be displayed in the range $-\pi$ to $+\pi$, instead
+   of the usual range zero to $2.\pi$.
+
+\item Some new
+routines (\htmlref{AST\_ANGLE}{AST\_ANGLE}, \htmlref{AST\_AXANGLE}{AST\_AXANGLE}, \htmlref{AST\_RESOLVE}{AST\_RESOLVE}, \htmlref{AST\_OFFSET2}{AST\_OFFSET2}, \htmlref{AST\_AXOFFSET}{AST\_AXOFFSET},
+\htmlref{AST\_AXDISTANCE}{AST\_AXDISTANCE})
+have been added to the \htmlref{Frame}{Frame} class to allow navigation of the coordinate space
+to be performed without needing to know the underlying geometry
+of the co-ordinate system (for instance, whether it is Cartesian or
+spherical).
+
+Note, version 1.8-1 contained many of these facilities, but
+some have been changed in version 1.8-2. Particularly, positions angles
+are now referred to the second Frame axis for \emph{all} classes of Frames
+(including SkyFrames), and the
+AST\_BEAR routine has been replaced by AST\_AXANGLE.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.8-3}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V1.8-2 and V1.8-3:
+
+\begin{enumerate}
+
+\item A new method called astDecompose has been added to the \htmlref{Mapping}{Mapping} class
+which enables pointers to be obtained to the component parts of \htmlref{CmpMap}{CmpMap} and
+\htmlref{CmpFrame}{CmpFrame} objects.
+
+\item Functions within proj.c and wcstrig.c have been renamed to avoid name
+clashes with functions in more recent versions of Mark Calabretta's wcslib
+library.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.8-4}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V1.8-3 and V1.8-4:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class has a new attribute called \htmlref{DefB1950}{DefB1950} which can be
+used to select the default reference frame and equinox to be used if
+a FitsChan with foreign encoding contains no indication of the
+reference frame or equinox.
+
+\item A bug has been fixed in the FitsChan class which could prevent
+astWrite from creating a set of FITS headers from an otherwise valid
+\htmlref{FrameSet}{FrameSet}, when when using FITS-AIPS encoding.
+
+\item A bug has been fixed in the FitsChan class which could cause
+astRead to mis-interpret the FITS CROTA keyword when using FITS-AIPS
+encoding.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.8-5}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V1.8-4 and V1.8-5:
+
+\begin{enumerate}
+
+\item The \htmlref{Plot}{Plot} class defines new graphical elements Axis1, Axis2,
+Grid1, Grid2, NumLabs1, NumLabs2, TextLab1, TextLab2, Ticks1 and Ticks2.
+These allow graphical attributes (colour, width, etc) to be set for each
+axis individually. Previously, graphical attributes could only be set for
+both axes together, using graphical elements Axes, \htmlref{Grid}{Grid}, NumLabs,
+TextLabs and Ticks.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V1.8-7}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V1.8-5 and V1.8-7:
+
+\begin{enumerate}
+
+\item A new attribute called \htmlref{CarLin}{CarLin} has been added to the \htmlref{FitsChan}{FitsChan} class
+which controls the way CAR projections are handled when reading a
+\htmlref{FrameSet}{FrameSet} from a non-native FITS header. Some FITS writers use a CAR
+projection to represent a simple linear transformation between pixel
+coordinates and celestial sky coordinates. This is not consistent with
+the definition of the CAR projection in the draft FITS-WCS standard, which
+requires the resultant \htmlref{Mapping}{Mapping} to include a 3D rotation from native
+spherical coordinates to celestial spherical coordinates, thus making the
+Mapping non-linear. Setting CarLin to 1 forces
+\htmlref{AST\_READ}{AST\_READ}
+to ignore the FITS-WCS standard and treat any CAR projections as simple
+linear Mappings from pixel coordinates to celestial coordinates.
+
+\item A bug has been fixed which could result in axis Format attributes
+set by the user being ignored under certain circumstances.
+
+\item A bug in the way tick marks positions are selected in the \htmlref{Plot}{Plot} class
+has been fixed. This bug could result in extra ticks marks being displayed at
+inappropriate positions. This bug manifested itself, for instance, if the
+Mapping represented by the Plot was a simple Cartesian to Polar Mapping.
+In this example, the bug caused tick marks to be drawn at negative radius
+values.
+
+\item A bug has been fixed which could prevent attribute settings from
+being read correctly by
+\htmlref{AST\_SET}{AST\_SET},
+etc., on certain platforms (MacOS, for instance).
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.8-8}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V1.8-7 and V1.8-8:
+
+\begin{enumerate}
+
+\item A bug has been fixed in the \htmlref{FitsChan}{FitsChan} class which could cause
+problems when creating a \htmlref{FrameSet}{FrameSet} from a FITS header containing WCS
+information stored in the form of Digitised Digitised Sky Survey (DSS)
+keywords. These problems only occurred for DSS fields in the southern
+hemisphere, and resulted in pixel positions being mapped to sky positions
+close to the corresponding \emph{northern} hemispshere field.
+
+\item A new method called
+\htmlref{AST\_BOUNDINGBOX}{AST\_BOUNDINGBOX}
+has been added to the \htmlref{Plot}{Plot} class. This method returns the bounding box of
+the previous graphical output produced by a Plot method.
+
+\item A new attribute called \htmlref{Invisible}{Invisible} has been added to the Plot class
+which suppresses the graphical output normally produced by Plot methods.
+All the calculations needed to produce the normal output are still
+performed however, and so the bounding box returned by the new
+AST\_BOUNDINGBOX
+method is still usable.
+
+\item Bugs have been fixed related to the appearance of graphical output
+produced by the Plot class. These bugs were to do with the way in which
+graphical elements relating to a specific axis (e.g. \texttt{Colour(axis1)}, etc.)
+interacted with the corresponding generic element (e.g.
+\texttt{Colour(axes)}, etc.).
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V1.8-13}
+
+The following describes the most significant changes which occurred
+in the AST library between versions V1.8-8 and V1.8-13:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class has been modified so that LONPOLE keywords
+are only produced by \htmlref{AST\_WRITE}{AST\_WRITE} when necessary. For zenithal projections such as
+TAN, the LONPOLE keyword can always take its default value and so is
+not included in the FITS header produced by AST\_WRITE
+Previously, the unnecessary production of a LONPOLE keyword could prevent
+FrameSets being written out using encodings which do not support the
+LONPOLE keyword (such as FITS-IRAF).
+
+\item The FitsChan class has been modified to retain leading and trailing
+spaces within COMMENT cards.
+
+\item The FitsChan class has been modified to only use CTYPE comments as
+axis labels if all non-celestial axes have unique non-blank comments
+(otherwise the CTYPE keyword values are used as labels).
+
+\item The FitsChan class has been modified so that it does not append a
+trailing ``Z'' character to the end of DATE-OBS keyword values.
+
+\item The FitsChan class has been modified to use latest list of FITS-WCS
+projections, as described in the FITS-WCS paper II, ``Representations of
+celestial coordinates in FITS'' (Calabretta \& Greisen, draft dated 23
+April 2002). Support has been retained for the polynomial correction
+terms which previous drafts have allowed to be associated with TAN
+projections.
+
+\item The \htmlref{WcsMap}{WcsMap} class has additional projection types of AST\_\_TPN
+(which implements a distorted TAN projection) and AST\_\_SZP. The AST\_\_TAN
+projection type now represents a simple TAN projection and has no
+associated projection parameters. In addition, the usage of projection
+parameters has been brought into line with the the FITS-WCS paper II.
+
+\item The WcsMap class has been modified so that a ``get'' operation on a
+projection parameter attribute will return the default value defined in the
+FITS-WCS paper II if no value has been set for the attribute. Previously, a
+value of AST\_\_BAD was returned in such a situation.
+
+\item The \htmlref{Frame}{Frame} class has new attributes \htmlref{Top(axis)}{Top(axis)} and \htmlref{Bottom(axis)}{Bottom(axis)} which
+allow a ``plottable range'' to be specified for each Frame axis. The grid
+produced by the \htmlref{AST\_GRID}{AST\_GRID} routine will not extend beyond these limits.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V2.0}
+
+Note, \htmlref{Frame}{Frame} descriptions created using AST V2.0 will not be readable by
+applications linked with earlier versions of AST. This applies to Frame
+descriptions created using:
+\begin{itemize}
+\item the \htmlref{Channel}{Channel} class
+\item the \htmlref{FitsChan}{FitsChan} class if the NATIVE \htmlref{Encoding}{Encoding} is used
+\item the \htmlref{AST\_SHOW}{AST\_SHOW} routine.
+\end{itemize}
+
+Applications must be re-linked with AST V2.0 in order to be able to read
+Frame descriptions created by AST v2.0.
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V1.8-13 and V2.0 (the
+current version):
+
+\begin{enumerate}
+
+\item The default value for the \htmlref{Domain}{Domain} attribute provided by the \htmlref{CmpFrame}{CmpFrame}
+class has been changed from ``CMP'' to a string formed by concatenating
+the Domain attributes of the two component Frames, separated by a minus
+sign. If both component Domains are blank, then the old default of
+``CMP'' is retained for the CmpFrame Domain.
+
+\item The implementation of the
+\htmlref{AST\_WRITE}{AST\_WRITE} routine
+within the FitsChan class has been modified. It will now attempt to
+produce a set of FITS header cards to describe a \htmlref{FrameSet}{FrameSet} even if the
+number of axes in the \htmlref{Current}{Current} Frames is greater than the number in the
+\htmlref{Base}{Base} Frame (that is, if there are more WCS axes than pixel axes). This
+has always been possible with NATIVE encoding, but has not previously
+been possible for foreign encodings. The WCSAXES keyword is used to store
+the number of WCS axes in the FITS header.
+
+\item Another change to the
+AST\_WRITE routine
+within the FitsChan class is that the ordering of ``foreign'' axes
+(\emph{i.e.} CTYPE keywords) is now chosen to make the CD (or PC) matrix
+as diagonal as possible - any element of axis transposition is removed by
+this re-ordering as recommended in FITS-WCS paper I. Previously the
+ordering was determined by the order of the axes in the Current Frame of
+the supplied FrameSet. This change does not affect NATIVE encoding.
+
+\item Support for spectral coordinate systems has been introduced
+throught the addition of two new classes, \htmlref{SpecFrame}{SpecFrame} and \htmlref{SpecMap}{SpecMap}.
+The SpecFrame is a 1-dimensional Frame which can be used to describe
+positions within an electromagnetic spectrum in various systems
+(wavelength, frequency, various forms of velocity,~\emph{etc.}) and referred
+to various standards of rest (topocentric, geocentric, heliocentric
+LSRK,~\emph{etc.}). The SpecMap is a \htmlref{Mapping}{Mapping} which can transform spectral
+axis values between these various systems and standards of rest. Note,
+FitsChans which have a foreign encoding (\emph{i.e.} any encoding other
+than NATIVE) are not yet able to read or write these new classes.
+
+\item Facilities have been added to the Frame class which allow
+differences in axis units to be taken into account when finding a Mapping
+between two Frames. In previous versions of AST, the Unit attribute was a
+purely descriptive item intended only for human readers - changing the
+value of Unit made no difference to the behaviour of the Frame. As of
+version 2.0, the Unit attribute can influence the nature of the Mappings
+between Frames. For instance, if the
+AST\_FINDRAME or \htmlref{AST\_CONVERT}{AST\_CONVERT}
+method is used to find the Mapping between an \htmlref{Axis}{Axis} with Unit set to ``m''
+and another Axis with Unit set to ``km'', then the method will return a
+\htmlref{ZoomMap}{ZoomMap} which introduces a scaling factor of 0.001 between the two axes.
+These facilities assume that units are specified following the rules
+included in FITS-WCS paper I (\emph{Representation of World
+Coordinates in FITS}, Greisen \& Calabretta).
+
+In order to minimise the risk of breaking existing software, the default
+behaviour for simple Frames is to ignore the Unit attribute (\emph{i.e.}
+to retain the previous behaviour). However, the new Frame method
+\htmlref{AST\_SETACTIVEUNIT}{AST\_SETACTIVEUNIT}
+may be used to ``activate'' (or deactivate) the new facilities within a
+specific Frame. Note, the new SpecFrame class is different to the simple
+Frame class in that the new facilities for handling units are always active
+within a SpecFrame.
+
+\item The \htmlref{System}{System} and \htmlref{Epoch}{Epoch} attributes fo the \htmlref{SkyFrame}{SkyFrame} class have been
+moved to the parent Frame class. This enables all sub-classes of Frame
+(such as the new SpecFrame class) to share these attributes, and to provide
+suitable options for each class.
+
+\item The Frame class has a new attribute called \htmlref{AlignSystem}{AlignSystem}, which allows
+control over the alignment process performed by the methods
+\htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} and AST\_CONVERT.
+
+
+\item The CmpFrame class has been modified so that attributes of a
+component Frame can be accessed without needing to extract the Frame first.
+To do this, append an axis index to the end of the attribute name. For
+instance, if a CmpFrame contains a SpecFrame and a SkyFrame (in that order),
+then the \htmlref{StdOfRest}{StdOfRest} attribute of the SpecFrame can be referred to as the
+``StdOfRest(1)'' attribute of the CmpFrame. Likewise, the \htmlref{Equinox}{Equinox} attribute
+of the SkyFrame can be accessed as the ``Equinox(2)'' (or equivalently
+``Equinox(3)'') attribute of the CmpFrame. The ``System(1)'' attribute of the
+CmpFrame will refer to the System attribute of the SpecFrame, whereas the
+``System(2)'' and ``System(3)'' attributes of the CmpFrame will refer to the
+System attribute of the SkyFrame (the ``System'' attribute without an axis
+specifier will refer to the System attribute of the CmpFrame as a whole,
+since System is an attribute of all Frames, and a CmpFrame is a Frame and
+so has its own System value which is independant of the System attributes
+of its component Frames).
+
+\item The algorithms used by the \htmlref{Plot}{Plot} class for determining when to omit
+overlapping axis labels, and the abbreviation of redundant leading fields
+within sexagesimal axis labels, have been improved to avoid some anomolous
+behaviour in previous versions.
+
+\item The curve drawing algorithm used by the Plot class has been
+modified to reduce the chance of it ``missing'' small curve sections,
+such as may be produced if a grid line cuts across the plot very close to
+a corner. Previously, these missed sections could sometimes result in
+axis labels being omitted.
+
+\item A new function
+(\htmlref{AST\_VERSION}{AST\_VERSION})
+has been added to return the version of the AST library in use.
+
+\item Bugs have been fixed in the Plot class which caused serious problems
+when plotting high precision data. These problems could range from the
+omission of some tick marks to complete failure to produce a plot.
+
+\end{enumerate}
+
+Programs which are statically linked will need to be re-linked in
+order to take advantage of these new facilities.
+
+
+\subsection{Changes Introduced in V3.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V2.0 and V3.0:
+
+\begin{enumerate}
+
+\item Many changes have been made in the \htmlref{FitsChan}{FitsChan} class in order to bring
+the FITS-WCS encoding into line with the current versions of the FITS-WCS
+papers (see
+\url{http://www.atnf.csiro.au/people/mcalabre/WCS/}):
+
+\begin{itemize}
+
+\item The rotation and scaling of the pixel axes may now be specified using
+either CD\emph{i\_j} keywords, or PC\emph{i\_j} and CDELTj keywords. A new attribute
+called \htmlref{CDMatrix}{CDMatrix} has been added to the FitsChan class to indicate which
+set of keywords should be used when writing a \htmlref{FrameSet}{FrameSet} to a FITS-WCS
+header.
+
+\item The FITS-WCS encoding now supports most of the conventions
+described in FITS-WCS paper III for the description of spectral
+coordinates. The exceptions are that the SSYSOBS keyword is not
+supported, and WCS stored in tabular form (as indicated by the ``-TAB''
+algorithm code) is not supported.
+
+
+\item User-specified fiducial points for WCS projections are now
+supported by FitsChans which use FITS-WCS encoding. This use keywords
+PVi\_0, PVi\_1 and PVi\_2 for the longitude axis.
+
+\item  When reading a FITS-WCS header, a FitsChan will now use keywords PVi\_3
+and PVi\_4 for the longitude axis (if present) in preference to any LONPOLE
+and LATPOLE keywords which may be present. When writing a FITS-WCS header,
+both forms are written out.
+
+\item The number of WCS axes is stored in the WCSAXES keyword if its value
+would be different to that of the NAXIS keyword.
+
+\item Helio-ecliptic coordinates are now supported by FitsChans which use
+FITS-WCS encoding. This uses CTYPE codes ``HLON'' and ``HLAT''. The
+resulting \htmlref{SkyFrame}{SkyFrame} will have a \htmlref{System}{System} value of ``HELIOECLIPTIC'', and all
+the usual facilities, such as conversion to other celestial systems, are
+available.
+
+\item The FITS-WCS encoding now supports most of the conventions
+described in FITS-WCS paper III for the description of spectral
+coordinates. The exceptions are that the SSYSOBS keyword is not
+supported, and WCS stored in tabular form (as indicated by the ``-TAB''
+algorithm code) is not supported.
+
+\item When reading a FITS-WCS header, a FitsChan will now ignore any
+distortion codes which are present in CTYPE keywords. Here, a ``distortion
+code'' is the final group of four characters in a CTYPE value of the
+form ``xxxx-yyy-zzz'', as described in FITS-WCS paper IV. The exception
+to this is that the ``-SIP'' distortion code (as used by the Spitzer
+Space Telescope project - see
+\url{http://ssc.spitzer.caltech.edu/postbcd/doc/shupeADASS.pdf}) is
+interpreted correctly and results in a \htmlref{PolyMap}{PolyMap} being used to represent
+the distortion in the resulting FrameSet. Note, ``-SIP'' distortion codes
+can only be read, not written. A FrameSet which uses a PolyMap will not
+in general be able to be written out to a FitsChan using any foreign
+encoding (although NATIVE encoding can of course be used).
+
+\item The \htmlref{Warnings}{Warnings} attribute of the FitsChan class now accepts values
+``BadVal'' (which gives warnings about conversion errors when reading
+FITS keyword values), ``Distortion'' (which gives warnings about
+unsupported distortion codes within CTYPE values), and ``BadMat'' (which
+gives a warning if the rotation/scaling matrix cannot be inverted).
+
+\item When writing a FrameSet to a FitsChan which uses a non-Native
+encoding, the comment associated with any card already in the FitsChan
+will be retained if the keyword value being written is the same as the
+keyword value already in the FitsChan.
+
+\item A FrameSet which uses the non-FITS projection type AST\_\_TPN (a TAN
+projection with polynomial distortion terms) can now be written to a
+FitsChan if the \htmlref{Encoding}{Encoding} attribute is set to FITS-WCS. The standard
+``-TAN'' code is used within the CTYPE values, and the distortion
+coefficients are encoded in keywords of the form `` QVi\_ma'', which are
+directly analogous to the standard ``PVi\_ma'' projection parameter keywords.
+Thus a FITS reader which does not recognise the QV keywords will still
+be able to read the header, but the distortion will be ignored.
+
+\item The default value for \htmlref{DefB1950}{DefB1950} attribute now depends on the value
+of the Encoding attribute.
+
+\item A new appendix has been added to SUN/210 and SUN/211 giving details
+of the implementation provided by the FitsChan class of the
+conventions contained in the first four FITS-WCS papers.
+\end{itemize}
+
+\item The SkyFrame class now supports two new coordinate systems ``ICRS''
+and ``HELIOECLIPTIC''. The default for the System attribute for SkyFrames
+has been changed from ``FK5'' to ``ICRS''.
+
+\item The
+\htmlref{AST\_RATE}{AST\_RATE}
+function has been added which allows an estimate to be made of the rate of
+change of a \htmlref{Mapping}{Mapping} output with respect to one of the Mapping inputs.
+
+\item All attribute names for Frames of any class may now include an optional
+axis specifier. This includes those attributes which describe a property
+of the whole \htmlref{Frame}{Frame}. For instance, the \htmlref{Domain}{Domain} attribute may now be
+specified as ``Domain(1)'' in addition to the simpler ``Domain''. In cases
+such as this, where the attribute describes a property of the whole
+Frame, axis specifiers will usually be ignored. The exception is that a
+\htmlref{CmpFrame}{CmpFrame} will use the presence of an axis specifier to indicate that the
+attribute name relates to the primary Frame containing the specified
+axis, rather than to the CmpFrame as a whole.
+
+\item A new subclass of Mapping, the PolyMap, has been added which
+performs a general N-dimensional polynomial mapping.
+
+\item A new subclass of Mapping, the \htmlref{GrismMap}{GrismMap}, has been added which
+models the spectral dispersion produced by a grating, prism or grism.
+
+\item A new subclass of Mapping, the \htmlref{ShiftMap}{ShiftMap}, has been added which adds
+constant values onto all coordinates (this is equivalent to a \htmlref{WinMap}{WinMap}
+with unit scaling on all axes).
+
+\item Minor bugs have been fixed within the \htmlref{Plot}{Plot} class to do with the choice
+and placement of numerical axis labels.
+
+\item The \htmlref{SphMap}{SphMap} class has a new attribute called \htmlref{PolarLong}{PolarLong} which gives the
+longitude value to be returned when a Cartesian position corresponding to
+either the north or south pole is transformed into spherical coordinates.
+
+\item The \htmlref{WcsMap}{WcsMap} class now assigns a longitude of zero to output
+celestial coordinates which have a latitude of plus or minus 90 degrees.
+
+\item The \htmlref{NatLat}{NatLat} and \htmlref{NatLon}{NatLon} attributes of the WcsMap class have been
+changed so that they now return the fixed native coordinates of the
+projection reference point, rather than the native coordinates of the
+user-defined fiducial point.
+
+\item Notation has been changed in both the WcsMap and FitsChan classes to
+reflect the convention used in the FITS-WCS papers that index ``i'' refers
+to a world coordinate axis, and index ``j'' refers to a pixel axis.
+
+\item Changes have been made to several Mapping classes in order to allow
+the
+\htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}
+function to make simplifications in a \htmlref{CmpMap}{CmpMap} which previously were not
+possible.
+
+\item The \htmlref{SlaMap}{SlaMap} class has been extended by the addition of conversions
+between FK5 and ICRS coordinates, and between FK5 and helio-ecliptic coordinates.
+
+\item The \htmlref{SpecMap}{SpecMap} class has been changed to use the equation for the
+refractive index of air as given in the current version of FITS-WCS paper
+III. Also, the forward and inverse transformations between frequency and
+air-wavelength have been made more compatible by using an iterative
+procedure to calculate the inverse.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V3.1}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V3.0 and V3.1 (the
+current version):
+
+\begin{enumerate}
+\item Addition of a new class called \htmlref{XmlChan}{XmlChan} - a \htmlref{Channel}{Channel} which
+reads and writes AST objects in the form of XML.
+\item A bug has been fixed in the \htmlref{Plot}{Plot} class which could cause incorrect
+graphical attributes to be used for various parts of the plot if either
+axis has no tick marks (i.e. if both major and minor tick marks have zero
+length).
+\end{enumerate}
+
+Programs which are statically linked will need to be re-linked in
+order to take advantage of these new facilities.
+
+
+\subsection{Changes Introduced in V3.2}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V3.1 and V3.2:
+
+\begin{enumerate}
+
+\item A new
+routine \htmlref{AST\_PUTCARDS}{AST\_PUTCARDS}
+has been added to the \htmlref{FitsChan}{FitsChan} class. This allows multiple concatenated header
+cards to be stored in a FitsChan in a single call, providing an alternative to
+the existing
+AST\_PUTCARDS routine.
+
+\item Some signficant changes have been made to the simplification of Mappings
+ which should resultin a greater degree of simplication taking place.Some
+ bugs have also been fixed which could result in an infinite loop being
+ entered when attempting to simplify certain Mappings.
+
+\item The FitsChan class now translates the spectral algorithm codes
+``-WAV'', ``-FRQ'' and ``-VEL'' (specified in early drafts of paper III) to
+the corresponding ``-X2P'' form when reading a spectral axis description
+from a set of FITS header cards.
+
+\item A bug has been fixed in the FitsChan class which could cause
+keywords associated with alternate axis descriptions to be mis-interpreted.
+
+\item The \htmlref{Plot}{Plot} class now provides facilities for modifying the appearance
+of sub-strings within text strings such as axis labels, titles, \emph{etc},
+by producing super-scripts, sub-scripts, changing the font colour, size,
+\emph{etc}. See attribute \htmlref{Escape}{Escape}.
+
+\item The default value of the \htmlref{Tol}{Tol} attribute of the Plot class has been
+changed from 0.001 to 0.01. This should not usually cause any significant
+visible change to the plot, but should make the plotting faster. You may
+need to set a lower value for Tol if you are producing a particularly
+large plot.
+
+\item The algorithm for finding the default value for the Gap attribute
+has been changed. This attribute specifies the gap between major axis
+values in an annotated grid drawn by the Plot class. The change in
+algorithm may cause the default value to be different to previous versions
+in cirtain circumstances.
+
+\item Some bugs have been fixed in the Plot class which could cause the
+system to hang for a long time while drawing certain all-sky grids
+(notable some of the FITS Quad-cube projections).
+
+\item The \htmlref{SkyAxis}{SkyAxis} class has extended the Format attribute by the addition
+of the ``g'' option. this option is similar to the older ``l'' option in that
+it results in characters (``h'', ``m'', ``s'', \emph{etc}) being used as
+delimiters between the sexagesimal fields of the celestial position. The
+difference is that the ``g'' option includes graphics escape sequences
+in the returned formatted string which result in the field delimiter
+characters being drawn as super-scripts when plotted as numerical axis values
+by a Plot.
+
+\item The Plot class has been extended to include facilities for producing
+logarithmic axes. See attributes LogPlot, LogTicks, LogGap and LogLabel.
+
+\item New functions astGCap and astGScales have been added to the interface
+defined by file \verb+grf.h+. The \htmlref{ast\_link}{ast\_link} command has been modified so
+that the \verb+-mygrf+ switch loads dummy versions of the new grf
+functions. This means that applications should continue to build without
+any change. However, the facilities for interpreting escape sequences
+within strings drawn by the Plot class will not be available unless the
+new grf functions are implemented. If you choose to implement them, you
+should modify your linking procedure to use the \verb+-grf+ switch in
+place of the older \verb+-mygrf+ switch. See the description of the ast\_link
+command for details of the new switches. Also note that the astGQch
+function, whilst included in verb+grf.h+ in pervious versions of AST, was
+not actually called. As of this version of AST, calls are made to the
+astGQch function, and so any bugs in the implementation of astGQch may
+cause spurious behaviour when plotting text strings.
+
+\item A new 'static' method called astEscapes has been added which is used
+to control and enquire whether astGetC and astFormat will strip any graphical
+escape sequences which may be present out of the returned value.
+
+\item New attribute \htmlref{XmlPrefix}{XmlPrefix} has been added to the \htmlref{XmlChan}{XmlChan} class. It
+allows XML written by the XmlChan class to include an explicit namespace
+prefix on each element.
+
+\item New attribute \htmlref{XmlFormat}{XmlFormat} has been added to the XmlChan class. It
+specifies the format in which AST objects should be written.
+
+\item A new class of \htmlref{Mapping}{Mapping}, the \htmlref{TranMap}{TranMap}, has been introduced. A TranMap
+takes its forward transformation from an existing Mapping, and its inverse
+transformation from another existing Mapping.
+
+\item A bug has been fixed in \htmlref{WcsMap}{WcsMap} which caused error reports to
+include erroneous axis numbers when referring to missing parameter values.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V3.3}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V3.2 and V3.3:
+
+\begin{enumerate}
+
+\item Options have been added to the \htmlref{SkyFrame}{SkyFrame} class which allows the
+origin
+of celestial coordinates to be moved to any specified point. See the new
+attributes SkyRef, \htmlref{SkyRefIs}{SkyRefIs}, SkyRefP and \htmlref{AlignOffset}{AlignOffset}.
+
+\item An option has been added to the \htmlref{FitsChan}{FitsChan} class which allows extra
+Frames representing cartesian projection plane coordinates (``intermediate
+world coordinates'' in the parlance of FITS-WCS) to be created when
+reading
+WCS information from a foreign FITS header. This option is controlled by
+a new attribute called \htmlref{Iwc}{Iwc}.
+
+\item The FitsChan class which been modified to interpret FITS-WCS CAR
+projection headers correctly if the longitude reference pixel (CRPIX) is
+very large.
+
+\item The  FITS-AIPS++ encoding in the FitsChan class now recognised
+spectral axes if they conform to the AIPS convention in which the
+spectral axis is descirbed by a CTYPE keyword od the form "AAAA-BBB"
+where ``AAAA'' is one of FREQ, VELO or FELO, and ``BBB'' is one of LSR, LSD,
+HEL or OBS. Such spectral axes can be both read and written.
+
+\item The FitsChan class now has a FITS-AIPS++ encoding which represents
+WCS information using FITS header cards recognised by the AIPS++ project.
+Support for spectral axes is identical to the FITS-AIPS encoding.
+
+\item The organisation of the AST distribution and the commands for
+building it have been changed. Whereas AST used to be built and installed
+with \verb+./mk build; ./mk install+, it now builds using the more standard
+idiom \verb+./configure; make; make install+. The installation location is
+controlled by the \verb+--prefix+ argument to ./configure (as is usual
+for other packages which use this scheme).  Note that the INSTALL environment
+variable now has a \emph{different} meaning to that which it had
+before, and it should generally be \emph{unset}. Also, there is no need to
+set the SYSTEM variable.
+
+\item Shared libraries are now installed in the same directory as the
+static libraries. In addition, links to sharable libraries are installed
+with names which include version information, and ``libtool libraries''
+are also installed (see
+\url{http://www.gnu.org/software/libtool/manual.html}).
+
+\item The \verb+ast_dev+ script has been removed. Instead, the location of
+the AST include files should be specified using the -I option when
+compiling.
+
+\item The names of the installed AST include files have been changed to
+upper case.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V3.4}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V3.3 and V3.4:
+
+\begin{enumerate}
+
+\item The \htmlref{Mapping}{Mapping} class has a new method
+(\htmlref{AST\_LINEARAPPROX}{AST\_LINEARAPPROX})
+which calculates the co-efficients of a linear approximation to a Mapping.
+
+\item The Format attribute for simple Frames and SkyFrames has been extended.
+It has always been possible, in both classes, to specify a precision by
+including a dot in the Format value followed by an integer (\emph{e.g.}
+``\verb+dms.1+'' for a \htmlref{SkyFrame}{SkyFrame}, or ``\verb+%.10g+'' for a simple \htmlref{Frame}{Frame}).
+The precision can now also be specified using an asterisk in place of the
+integer (\emph{e.g.} ``\verb+dms.*+'' or ``\verb+%.*g+''). This causes the
+precision to be derived on the basis of the Digits attribute value.
+
+\item The \htmlref{Plot}{Plot} class has been changed so that the default value used for the
+Digits attribute is chosen to be the smallest value which results in no
+pair of adjacent labels being identical. For instance, if an annotated
+grid is being drawn describing a SkyFrame, and the Format(1) value is set
+to ``\verb+hms.*g+'' (the ``g'' causes field delimiters to be drawn as
+superscripts), and the Digits(1) value is unset, then the seconds field
+will have a number of decimal places which results in no pair of labels
+being identical.
+
+\item Addition of a new class classed \htmlref{DSBSpecFrame}{DSBSpecFrame}. This is a
+sub-class of \htmlref{SpecFrame}{SpecFrame} which can be used to describe spectral axes
+associated with dual sideband spectral data.
+
+\item The \htmlref{FitsChan}{FitsChan} class will now read headers which use the old ``-GLS''
+projection code, converting them to the corresponding modern ``-SFL'' code,
+provided that the celestial axes are not rotated.
+
+\item The FitsChan class has a new \htmlref{Encoding}{Encoding}, ``FITS-CLASS'', which allows
+the reading and writing of FITS headers using the conventions of the CLASS
+package - see
+\url{http://www.iram.fr/IRAMFR/GILDAS/doc/html/class-html/class.html}).
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V3.5}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V3.4 and V3.5:
+
+\begin{enumerate}
+
+\item AST now provides facilities for representing regions of various
+shapes within a coordinate system. The \htmlref{Region}{Region} class provides general
+facilities which are independent of the specific shape of region being
+used. Various sub-classes of Region are also now available which provide
+means of creating Regions of specific shape. Facilities provided by the
+Region class include testing points to see if they are inside the
+Region, testing two Regions for overlap, transforming Regions from one
+coordinate system to another \emph{etc}.
+
+\item A new class of 1-dimensional \htmlref{Frame}{Frame} called \htmlref{FluxFrame}{FluxFrame} has been added which
+can be used to describe various systems for describing ovserved value at a
+single fixed spectral position.
+
+\item A new class of 2-dimensional Frame called \htmlref{SpecFluxFrame}{SpecFluxFrame} has been added which
+can be used to describe a 2-d frame spanned by a spectral position axis
+and and an observed value axis.
+
+\item A new class of \htmlref{Mapping}{Mapping} called \htmlref{RateMap}{RateMap} has been added. A RateMap encapsulates
+a previously created Mapping. The inputs of the RateMap correspond to the
+inputs of the encapsulated Mapping. All RateMaps have just a single
+output which correspond to the rate of change of a specified output of
+the encapsulated Mapping with respect to a specified input.
+
+\item The \htmlref{SkyFrame}{SkyFrame} class now supports a value of ``J2000'' for \htmlref{System}{System}.
+This system is an equatorial system based on the mean dynamical equator and
+equinox at J2000, and differs slightly from an FK5(J2000) system.
+
+\item A new class called \htmlref{KeyMap}{KeyMap} has been added. A KeyMap can be used to
+store a collection of vector or scalar values or Objects, indexed by a
+character string rather than an integer.
+
+\item The parameter list for the
+\htmlref{AST\_RATE}{AST\_RATE}
+method of the Mapping class has been modified. It no longer returns a second
+derivative estimate. Existing code which uses this method will need to be
+changed.
+
+\item Methods
+(AST\_SETFITS<X>)
+have been added to the \htmlref{FitsChan}{FitsChan} class to allow values for named
+keywords to be changed or added.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V3.6}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V3.5 and V3.6:
+
+\begin{enumerate}
+
+\item If the Format attribute associated with an axis of a \htmlref{SkyFrame}{SkyFrame}
+starts with a percent character (``\verb+%+''), then axis values are
+now formatted and unformatted as a decimal radians value, using the
+Format syntax of a simple \htmlref{Frame}{Frame}.
+
+\item The \htmlref{Plot}{Plot} class has a new attribute called \htmlref{Clip}{Clip} which controls the
+clipping performed by AST at the plot boundary.
+
+\item The keys used to label components of the \htmlref{PolyMap}{PolyMap} structure when a
+PolyMap is written out through a \htmlref{Channel}{Channel} have been changed. The new keys
+are shorter than the old keys and so can written succesfully to a \htmlref{FitsChan}{FitsChan}.
+The new PolyMap class always writes new styles keys but can read either
+old or new style keys. Consequently, PolyMap dumps written by this
+version of AST cannot be read by older versions of AST.
+
+\item A mimimal cut down subset of the C version of SLALIB is now
+included with the AST distribution and built as part of building AST.
+This means that it is no longer necessary to have SLALIB installed
+separately at your site. The SLALIB code included with AST is distrubuted
+under the GPL. The default behaviour of the \htmlref{ast\_link}{ast\_link} script is now to
+link with this internal slalib subset. However, the ``-csla'' option can
+still be used to force linking with an external full C SLALIB library.
+A new option ``-fsla'' has been introduced which forces linking with the
+external full Fortran SLALIB library.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V3.7}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V3.6 and V3.7:
+
+\begin{enumerate}
+
+\item Support for time coordinate systems has been introduced
+throught the addition of two new classes, \htmlref{TimeFrame}{TimeFrame} and \htmlref{TimeMap}{TimeMap}.
+The TimeFrame is a 1-dimensional \htmlref{Frame}{Frame} which can be used to describe
+moments in time (either absolute or relative) in various systems (MJD,
+Julian \htmlref{Epoch}{Epoch}, \emph{etc.}) and referred to various time scales (TAI, UTC,
+UT1, GMST, \emph{etc}). The TimeMap is a \htmlref{Mapping}{Mapping} which can transform time
+values between these various systems and time scales. Note,
+FitsChans which have a foreign encoding (\emph{i.e.} any encoding other
+than NATIVE) are not able to read or write these new classes.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V4.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V3.7 and V4.0:
+
+\begin{enumerate}
+
+\item Experimental support for reading IVOA Space-Time-Coordinates (STC-X)
+descriptions using the \htmlref{XmlChan}{XmlChan} class has been added. Support is included
+for a subset of V1.20 of the draft STC specification.
+
+\item A new set of methods (AST\_REBIN<X>/astRebin<X>) has been added to
+the \htmlref{Mapping}{Mapping} class. These are flux-conserving alternatives to the existing
+AST\_RESAMPLE<X>/astResample<X> methods.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V4.1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V4.0 and V4.1:
+
+\begin{enumerate}
+
+\item A new control flag has been added to the AST\_RESAMPLE<X>/astResample<X>
+functions which produces approximate flux conservation.
+
+\item New constants AST\_\_SOMB and AST\_\_SOMBCOS have been added to
+AST\_PAR. These specify kernels for AST\_RESAMPLE and AST\_REBIN
+based on the ``Sombrero'' function ( $2*J1(x)/x$ where $J1(x)$ is the
+first order Bessel function of the first kind).
+
+\item The \htmlref{SkyFrame}{SkyFrame} class now supports a \htmlref{System}{System} value of AZEL corresponding
+to horizon (azimuth/elevation) coordinates.
+
+\item The \htmlref{FitsChan}{FitsChan} class allows the non-standard strings ``AZ--'' and
+``EL--'' to be used as axis types in FITS-WCS CTYPE keyword values.
+
+\item The \htmlref{Frame}{Frame} class now has attributes \htmlref{ObsLon}{ObsLon} and \htmlref{ObsLat}{ObsLat} to specify
+the geodetic longitude and latitude of the observer.
+
+\item The ClockLon and ClockLat attributes have been removed from the
+\htmlref{TimeFrame}{TimeFrame} class. Likewise, the GeoLon and GeoLat attributes have been
+removed from the \htmlref{SpecFrame}{SpecFrame} class. Both classes now use the ObsLon and
+ObsLat attributes of the parent Frame class instead. However, the old
+attribute names can be used as synonyms for ObsLat and ObsLon. Also,
+dumps created using the old scheme can be read succesfully by AST V4.1
+and converted to the new form.
+
+\item A new
+routine \htmlref{AST\_MAPSPLIT}{AST\_MAPSPLIT}
+has been added to the \htmlref{Mapping}{Mapping} class. This splits a Mapping into two component
+Mappings which, when combined in parallel, are equivalent to the original
+Mapping.
+
+\item The default value for the \htmlref{SkyRefIs}{SkyRefIs} attribute has been changed from
+``Origin'' to ``Ignored''. This means that if you want to use a SkyFrame
+to represent offsets from some origin position, you must now set the
+SkyRefIs attribute explicitly to either ``Pole'' or ``Origin'', in
+addition to assigning the required origin position to the SkyRef attribute.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V4.2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V4.1 and V4.2:
+
+\begin{enumerate}
+
+\item The \htmlref{SideBand}{SideBand} attribute of the \htmlref{DSBSpecFrame}{DSBSpecFrame} class can now take the
+option ``LO'' in addition to ``USB'' and ``LSB''. The new option causes the
+DSBSpecFrame to represent the offset from the local oscillator frequency,
+rather than either of the two sidebands.
+
+\item The \htmlref{FitsChan}{FitsChan} class has been changed so that it writes out a VELOSYS
+keyword when creating a FITS-WCS encoding (VELOSYS indicates the topocentric
+apparent velocity of the standard of rest). FitsChan also strips out VELOSYS
+keywords when reading a \htmlref{FrameSet}{FrameSet} from a FITS-WCS encoding.
+
+\item The FitsChan class has a new method called
+\htmlref{AST\_RETAINFITS}{AST\_RETAINFITS}
+that indicates that the current card in the FitsChan should not be
+stripped out of the FitsChan when an AST \htmlref{Object}{Object} is read from the FitsChan.
+Unless this method is used, all cards that were involved in the creation
+of the AST Object will be stripped from the FitsChan afte a read operation.
+
+\item A problem with unaligned memory access that could cause bus errors on
+Solaris has been fixed.
+
+\item A new read-only attribute called \htmlref{ObjSize}{ObjSize} has been added to the base
+Object \htmlref{Class}{Class}. This gives the number of bytes of memory occupied by the
+Object. Note, this is the size of the internal in-memory representation of
+the Object, not the size of the textual representation produced by
+writing the Object out through a \htmlref{Channel}{Channel}.
+
+\item A new function
+\htmlref{AST\_TUNE}{AST\_TUNE}
+has been added which can be used to get and set global AST tuning
+parameters. At the moment there are only two such parameter, both of
+which are concerned with memory management within AST.
+
+\item A new method called
+\htmlref{AST\_TRANGRID}{AST\_TRANGRID}
+has been added to the \htmlref{Mapping}{Mapping} class. This method creates a regular
+grid of points covering a rectangular region within the input space of a
+Mapping, and then transforms this set of points into the output space of the
+Mapping, using a piecewise-continuous linear approximation to the Mapping
+if appropriate in order to achive higher speed.
+
+\item A new subclass of Mapping has been added called \htmlref{SwitchMap}{SwitchMap}. A
+SwitchMap represents several alternate Mappings, each of which is used to
+transforms input positions within a different region of the input
+coordinate space.
+
+\item A new subclass of Mapping has been added called \htmlref{SelectorMap}{SelectorMap}. A
+SelectorMap tests each input position to see if it falls within one of
+several Regions. If it does, the index of the \htmlref{Region}{Region} containing the
+input position is returned as the Mapping output.
+
+\item The behaviour of the
+\htmlref{AST\_CONVERT}{AST\_CONVERT}
+method when trying to align a \htmlref{CmpFrame}{CmpFrame} with another \htmlref{Frame}{Frame} has been
+modified. If no conversion between positions in the Frame and CmpFrame
+can be found, an attempt is now made to find a conversion between the
+Frame and one of two component Frames contained within the CmpFrame. Thus
+is should now be possible to align a \htmlref{SkyFrame}{SkyFrame} with a CmpFrame containing a
+SkyFrame and a \htmlref{SpecFrame}{SpecFrame} (for instance). The returned Mapping produces bad
+values for the extra axes (i.e. for the SpecFrame axis in the above example).
+
+\item The ``\htmlref{\htmlref{ast\_link}{ast\_link}\_adam}{ast\_link\_adam}'' and ``ast\_link'' scripts now ignore the
+\verb+-fsla+ and \verb+-csla+ options, and always link against the
+minimal cut-down version of SLALIB distributed as part of AST.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V4.3}
+
+The following describes the most significant changes which occurred in the
+AST library between versions V4.2 and V4.3:
+
+\begin{enumerate}
+
+\item The
+AST\_GETFITSS
+function now strips trailing white space from the returned string, if the
+original string contains 8 or fewer characters
+
+\item The \htmlref{SpecFrame}{SpecFrame} class has a new attribute called \htmlref{SourceSys}{SourceSys} that specified
+whether the \htmlref{SourceVel}{SourceVel} attribute (which specifies the rest frame of the
+source) should be accessed as an apparent radial velocity or a redshift.
+Note, any existing software that assumes that SourceVel always represents
+a velocity in km/s should be changed to allow for the possibility of
+SourceVel representing a redshift value.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V4.4}
+
+The following describes the most significant changes which occurred in
+the AST library between versions V4.3 and V4.4:
+
+\begin{enumerate}
+
+\item The
+\htmlref{AST\_FINDFRAME}{AST\_FINDFRAME}
+function can now be used to search a \htmlref{CmpFrame}{CmpFrame} for an instance of a more
+specialised class of \htmlref{Frame}{Frame} (\htmlref{SkyFrame}{SkyFrame}, \htmlref{TimeFrame}{TimeFrame}, \htmlref{SpecFrame}{SpecFrame}, \htmlref{DSBSpecFrame}{DSBSpecFrame}
+or \htmlref{FluxFrame}{FluxFrame}). That is, if an instance of one of these classes is used as
+the ``template'' when calling
+AST\_FINDFRAME,
+and the ``target'' being searched is a CmpFrame (or a \htmlref{FrameSet}{FrameSet} in which the
+current Frame is a CmpFrame), then the component Frames within the CmpFrame
+will be searched for an instance of the supplied template Frame, and, if
+found, a suitable \htmlref{Mapping}{Mapping} (which will include a \htmlref{PermMap}{PermMap} to select the
+required axes from the CmpFrame) will be returned by
+AST\_FINDFRAME.
+Note, for this to work, the \htmlref{MaxAxes}{MaxAxes} and \htmlref{MinAxes}{MinAxes} attributes of the template
+Frame must be set so that they cover a range that includes the number of axes
+in the target CmpFrame.
+
+\item The SkyFrame, SpecFrame, DSBSpecFrame, TimeFrame and FluxFrame classes
+now allow the MaxAxes and MinAxes attributes to be set freely to any value.
+In previous versions of AST, any attempt to change the value of MinAxes
+or MaxAxes was ignored, resulting in them always taking the default values.
+
+\item The DSBSpecFrame class has a new attribute called AlignSB that
+specifies whether or not to take account of the \htmlref{SideBand}{SideBand} attributes when
+aligning two DSBSpecFrames using
+\htmlref{AST\_CONVERT}{AST\_CONVERT}.
+
+\item The Frame class has a new attribute called \htmlref{Dut1}{Dut1} that can be used to
+store a value for the difference between the UT1 and UTC timescales at
+the epoch referred to by the Frame.
+
+\item The number of digits used to format the Frame attributes \htmlref{ObsLat}{ObsLat} and
+\htmlref{ObsLon}{ObsLon} has been increased.
+
+\item The use of the SkyFrame attribute \htmlref{AlignOffset}{AlignOffset} has been changed. This
+attribute is used to control how two SkyFrames are aligned by
+AST\_CONVERT.
+If the template and target SkyFrames both have a non-zero value for
+AlignOffset, then alignment occurs between the offset coordinate systems
+(that is, a \htmlref{UnitMap}{UnitMap} will always be used to align the two SkyFrames).
+
+\item The \htmlref{Plot}{Plot} class has a new attribute called ForceExterior that can be
+used to force exterior (rather than interior) tick marks to be produced.
+By default, exterior ticks are only produced if this would result in
+more than 3 tick marks being drawn.
+
+\item The TimeFrame class now supports conversion between angle based
+timescales such as UT1 and atomic based timescales such as UTC.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V4.5}
+
+The following describes the most significant changes that
+occurred in the AST library between versions V4.4 and V4.5:
+
+\begin{enumerate}
+
+
+
+\item All FITS-CLASS headers are now created with a frequency axis. If the
+\htmlref{FrameSet}{FrameSet} supplied to
+\htmlref{AST\_WRITE}{AST\_WRITE}
+contains a velocity axis (or any other form
+of spectral axis) it will be converted to an equivalent frequency axis
+before being used to create the FITS-CLASS header.
+
+\item The value stored in the FITS-CLASS keyword ``VELO-LSR'' has been changed
+from the velocity of the source to the velocity of the reference channel.
+
+\item Addition of a new method call
+\htmlref{AST\_PURGEWCS}{AST\_PURGEWCS}
+to the \htmlref{FitsChan}{FitsChan}
+class. This method removes all WCS-related header cards from a FitsChan.
+
+\item The \htmlref{Plot}{Plot} class has a new attribute called GrfContext that can be used
+to comminicate context information between an application and any
+graphics functions registered with the Plot class via the
+\htmlref{AST\_GRFSET}{AST\_GRFSET} routine.
+\item Functions registered with the Plot class using
+AST\_GRFSET
+now take a new additional integer parameter, ``grfcon''. The Plot class
+sets this parameter to the value of the Plot's GrfContext attribute before
+calling the graphics function. NOTE, THIS CHANGE WILL REQUIRE EXISTING
+CODE THAT USES
+AST\_GRFSET
+TO BE MODIFIED TO INCLUDE THE NEW PARAMETER.
+\item The
+AST\_REBINSEQ routines
+now have an extra parameter that is used to record the total number of input
+data values added into the output array. This is necessary to correct a
+flaw in the calculation of output variances based on the spread of input
+values. NOTE, THIS CHANGE WILL REQUIRE EXISTING CODE TO BE MODIFIED TO
+INCLUDE THE NEW PARAMETER (CALLED "NUSED").
+\item Support has been added for the FITS-WCS ``HPX'' (HEALPix) projection.
+\item A new flag ``AST\_\_VARWGT'' can be supplied to
+AST\_REBINSEQ.
+This causes the input data values to be weighted using the reciprocals of
+the input variances (if supplied).
+
+\item The \htmlref{Frame}{Frame} class has a new read-only attribute called NormUnit that
+returns the normalised value of the Unit attribute for an axis. Here,
+``normalisation'' means cancelling redundant units, etc. So for instance, a
+Unit value of ``s*(m/s)'' would result in a NormUnit value of ``m''.
+
+\item A new
+routine \htmlref{AST\_SHOWMESH}{AST\_SHOWMESH}
+has been added to the \htmlref{Region}{Region} class. It displays a mesh of points covering
+the surface of a Region by writing out a table of axis values to standard
+output.
+
+\item The Plot class now honours the value of the LabelUp attribute even if
+numerical labels are placed around the edge of the Plot. Previously
+LabelUp was only used if the labels were drawn within the interior of
+the plot. The LabelUp attribute controls whether numerical labels are
+drawn horizontally or parallel to the axis they describe.
+
+\item A bug has been fixed that could segmentation violations when setting
+attribute values.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V4.6}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V4.5 and V4.6:
+
+\begin{enumerate}
+
+\item The \htmlref{TimeFrame}{TimeFrame} class now support Local Time as a time scale. The offset
+from UTC to Local Time is specified by a new TimeFrame attribute called
+\htmlref{LTOffset}{LTOffset}.
+
+\item A new class called \htmlref{Plot3D}{Plot3D} has been added. The Plot3D class allows
+the creation of 3-dimensional annotated coordinate grids.
+
+\item A correction for diurnal aberration is now included when
+converting between AZEL and other celestial coordinate systems. The
+correction is based on the value of the \htmlref{ObsLat}{ObsLat} \htmlref{Frame}{Frame} attribute (the
+geodetic latitude of the observer).
+
+\item A bug has been fixed which caused the DUT1 attribute to be ignored
+by the \htmlref{SkyFrame}{SkyFrame} class when finding conversions between AZEL and other
+celestial coordinate systems.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V4.6 and V5.0:
+
+\begin{enumerate}
+
+
+\item The AST library is now thread-safe (assuming that the POSIX pthreads
+library is available when AST is built). Many of the macros defined in
+the ast.h header file have changed. It is therefore necessary to
+re-compile all source code that includes ast.h.
+
+\item New methods astLock and astUnlock allow an AST \htmlref{Object}{Object} to be locked
+for exclusive use by a thread.
+
+\item The \htmlref{TimeFrame}{TimeFrame} class now support Local Time as a time scale. The offset
+from UTC to Local Time is specified by a new TimeFrame attribute called
+\htmlref{LTOffset}{LTOffset}.
+
+\item The \htmlref{Channel}{Channel} class has a new attribute called \htmlref{Strict}{Strict} which controls
+whether or not to report an error if unexpected data items are found
+within an AST Object description read from an external data source. Note,
+the default behaviour is now not to report such errors. This differs from
+previous versions of AST which always reported an error is unexpected
+input items were encountered.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.1}
+
+The following describes the most significant changes which occurred in the
+AST library between versions V5.0 and V5.1:
+
+\begin{enumerate}
+
+
+\item The \htmlref{Prism}{Prism} class has been modified so that any class of \htmlref{Region}{Region} can
+be used to define the extrusion axes. Previously, only a \htmlref{Box}{Box} or \htmlref{Interval}{Interval}
+could be used for this purpose.
+
+
+\item Improvements have been made to the way that Prisms are simplified
+when
+\htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY}
+is called.  The changes mean that more types of Prism will now simplify
+into a simpler class of Region.
+
+\item The \htmlref{PointList}{PointList} class has a new method,
+AST\_POINTS,
+that copies the axis values from the PointList into a supplied array.
+
+\item The PointList class has a new (read-only) attribute, \htmlref{ListSize}{ListSize}, that
+gives the number of points stored in the PointList.
+
+\item The handling of warnings within different classes of \htmlref{Channel}{Channel} has
+been rationalised. The XmlStrict attribute and
+AST\_XMLWARNINGS
+function have been removed. The same functionality is now available via
+the existing \htmlref{Strict}{Strict} attribute (which has had its remit widened), a new
+attribute called \htmlref{ReportLevel}{ReportLevel}, and the new
+\htmlref{AST\_WARNINGS}{AST\_WARNINGS}
+function. This new function can be used on any class of Channel. Teh
+\htmlref{FitsChan}{FitsChan} class retains its long standing ability to store warnings as
+header cards within the FitsChan, but it also now stores warnings in the
+parent Channel structure, from where they can be retrieved using the
+AST\_WARNINGS
+function.
+
+\item A new function called
+AST\_INTERCEPT
+has been added to the \htmlref{Frame}{Frame} class. This function finds the point of
+intersection beteeen two geodesic curves.
+
+\item A bug in the type-checking of Objects passed as arguments to constructor
+functions has been fixed. This bug could lead to applications crashing or
+showing strange behaviour if an inappropriate class of \htmlref{Object}{Object} was
+supplied as an argument to a constructor.
+
+\item The
+\htmlref{AST\_PICKAXES}{AST\_PICKAXES}
+function will now return a Region, if possible, when applied to a Region. If
+this is not possible, a Frame will be returned as before.
+
+\item The choice of default tick-mark for time axes has been improved, to avoid
+previous issues which could result in no suitable gap being found, or
+inappropriate tick marks when using formatted dates.
+
+\item A new function called
+\htmlref{AST\_TESTFITS}{AST\_TESTFITS}
+has been added to the FitsChan class. This function tests a FitsChan to
+see if it contains a defined value for specified FITS keyword.
+
+\item The AST\_\_UNDEF<X> parameters used to flag undefined FITS keyword values
+have been removed. Use the new
+AST\_TESTFITS
+function instead.
+
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.1 and V5.2:
+
+\begin{enumerate}
+
+\item A new method called
+\htmlref{AST\_SETFITSCM}{AST\_SETFITSCM}
+has been added to the \htmlref{FitsChan}{FitsChan} class. It stores a pure comment card in a
+FitsChan (that is, a card with no keyword name or equals sign).
+
+\item A new attribute called \htmlref{ObsAlt}{ObsAlt} has been added to the \htmlref{Frame}{Frame} class. It
+records the geodetic altitude of the observer, in metres. It defaults to
+zero. It is used when converting times to or from the TDB timescale, or
+converting spectral positions to or from the topocentric rest frame, or
+converting sky positions to or from horizon coordinates. The FitsChan
+class will include its effect when creating a set of values for the
+OBSGEO-X/Y/Z keywords, and will also assign a value to it when reading a
+set of OBSGEO-X/Y/Z keyword values from a FITS header.
+
+\item The \htmlref{TimeMap}{TimeMap} conversions ``TTTOTDB'' and ``TDBTOTT'', and the \htmlref{SpecMap}{SpecMap}
+conversions ``TPF2HL'' and ``HLF2TP'', now have an additional argument -
+the observer's geodetic altitude.
+
+\item The \htmlref{Polygon}{Polygon} class has been modified to make it consistent with the
+IVOA STC definition of a Polygon. Specifically, the inside of a polygon
+is now the area to the left of each edge as the vertices are traversed in
+an anti-clockwise manner, as seen from the inside of the celestial sphere.
+Previously, AST used the anti-clockwise convention, but viewed from the
+outside of the celestial sphere instead of the inside. Any Polygon saved
+using previous versions of AST will be identified and negated automatically
+when read by AST V5.2.
+
+\item A new class of \htmlref{Channel}{Channel}, called \htmlref{StcsChan}{StcsChan}, has been added that allows
+conversion of suitable AST Objects to and from IVOA STC-S format.
+
+\item A new method called
+\htmlref{AST\_REMOVEREGIONS}{AST\_REMOVEREGIONS}
+has been added to the \htmlref{Mapping}{Mapping} class. It searches a (possibly compound)
+Mapping (or Frame) for any instances of the AST \htmlref{Region}{Region} class, and either
+removes them, or replaces them with UnitMaps (or equivalent Frames). It
+can be used to remove the masking effects of Regions from a compound
+Mapping or Frame.
+
+\item A new method called
+\htmlref{AST\_DOWNSIZE}{AST\_DOWNSIZE}
+has been added to the Polygon class. It produces a new Polygon that
+contains a subset of the vertices in the supplied Polygon. The subset is
+chosen to retain the main features of the supplied Polygion, in so far
+as that is possible, within specified constraints.
+
+\item A new constructor called
+AST\_OUTLINE
+has been added to the Polygon class. Given a 2D data array, it identifies
+the boundary of a region within the array that holds pixels with
+specified values. It then creates a new Polygon to describe this boundary
+to a specified accuracy.
+
+\item A new set of methods, called
+AST\_MAPGETELEM<X>
+has been added to the \htmlref{KeyMap}{KeyMap} class. They allow a single element of a vector
+valued entry to be returned.
+
+\item A new attribute called \htmlref{KeyError}{KeyError} has been added to the KeyMap \htmlref{Class}{Class}. It
+controls whether the
+AST\_MAPGET...
+family of functions report an error if an entry with the requested key does
+not exist in the KeyMap.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.3}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.2 and V5.3:
+
+\begin{enumerate}
+
+\item The details of how a \htmlref{Frame}{Frame} is aligned with another Frame by the
+\htmlref{AST\_FINDFRAME}{AST\_FINDFRAME} and \htmlref{AST\_CONVERT}{AST\_CONVERT}
+functions have been changed. The changes mean that a Frame can now be
+aligned with an instance of a sub-class of Frame, so long as the number
+of axes and the \htmlref{Domain}{Domain} values are consistent. For instance, a basic
+2-dimensional Frame with Domain ``SKY'' will now align succesfully with
+a \htmlref{SkyFrame}{SkyFrame}, conversion between the two Frames being achieved using a
+\htmlref{UnitMap}{UnitMap}.
+
+
+\item Added method
+\htmlref{AST\_MATCHAXES}{AST\_MATCHAXES}
+to the Frame class. This method allows corresponding axes within two
+Frames to be identified.
+
+\item The
+\htmlref{AST\_ADDFRAME}{AST\_ADDFRAME}
+method can now be used to append one or more axes to all Frames in a \htmlref{FrameSet}{FrameSet}.
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.3-1}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V5.3 and V5.3-1:
+
+\begin{enumerate}
+
+
+\item The \htmlref{KeyMap}{KeyMap} class now supports entries that have undefined values. A
+new method called
+\htmlref{AST\_MAPPUTU}{AST\_MAPPUTU}
+will store an entry with undefined value in a keymap. Methods that
+retrieve values from a KeyMap
+(AST\_MAPGET0<X>, etc.)
+ignore entries with undefined values when searching for an entry with a given
+key.
+
+\item The KeyMap class has a new method called
+\htmlref{AST\_MAPCOPY}{AST\_MAPCOPY}
+that copies entries from one KeyMap to another KeyMap.
+
+\item The KeyMap class has a new boolean attribute called \htmlref{MapLocked}{MapLocked}. If
+.TRUE.,
+an error is reported if an attempt is made to add any new entries
+to a KeyMap (the value associated with any old entry may still be changed
+without error). The default is
+.FALSE.
+
+\item The \htmlref{Object}{Object} class has a new method called astHasAttribute/\htmlref{AST\_HASATTRIBUTE}{AST\_HASATTRIBUTE}
+that returns a boolean value indicating if a specified Object has a named
+attribute.
+
+\item The \htmlref{SkyFrame}{SkyFrame} class has two new read-only boolean attributes called
+IsLatAxis and IsLonAxis that can be used to determine the nature of a
+specified SkyFrame axis.
+
+\item A bug has been fixed in the
+AST\_REBIN(SEQ)
+methods that could cause flux to be lost from the edges of the supplied array.
+
+\item A bug has been fixed in the
+AST\_REBIN(SEQ)
+methods that caused the first user supplied parameter to be interpreted as the
+full width of the spreading kernel, rather than the half-width.
+
+\item The \htmlref{StcsChan}{StcsChan} class now ignores case when reading STC-S phrases (except
+that units strings are still case sensitive).
+
+\item A new \htmlref{Mapping}{Mapping} method,
+\htmlref{AST\_QUADAPPROX}{AST\_QUADAPPROX},
+produces a quadratic least-squares fit to a 2D Mapping.
+
+\item A new Mapping method,
+\htmlref{AST\_SKYOFFSETMAP}{AST\_SKYOFFSETMAP},
+produces a Mapping from absolute SkyFrame coordinates to offset SkyFrame
+coordinates.
+
+\item The \htmlref{Channel}{Channel} class now has an \htmlref{Indent}{Indent} attribute that controls indentation
+in the text created by
+\htmlref{AST\_WRITE}{AST\_WRITE}.
+The StcsIndent and XmlIndent attributes have been removed.
+
+\item All classes of Channel now use the string ``<bad>'' to represent the
+floating point value AST\_\_BAD, rather than the literal formatted value
+(typically ``-1.79769313486232e+308'' ).
+
+\item The KeyMap class now uses the string ``<bad>'' to represent the
+floating point value AST\_\_BAD, rather than the literal formatted value
+(typically ``-1.79769313486232e+308'' ).
+
+\item The KeyMap class has a new method called
+AST\_MAPPUTELEM<X>
+that allows a value to be put into a single element of a vector entry in
+a KeyMap. The vector entry is extended automatically to hold the new
+element if required.
+
+\item The \htmlref{DSBSpecFrame}{DSBSpecFrame} class now reports an error if the local oscillator
+frequency is less than the absoliute value of the intermediate frequency.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V5.3-2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.3-1 and V5.3-2:
+
+\begin{enumerate}
+
+\item A bug has been fixed in the \htmlref{FitsChan}{FitsChan} class that could cause wavelength
+axes to be assigned the units ``m/s'' when reading WCS information from a
+FITS header.
+
+\item The
+\htmlref{AST\_SET}{AST\_SET} routine
+now allows literal commas to be included in string attribute values. String
+attribute values that include a literal comma should be enclosed in quotation
+marks.
+
+\item A bug in FitsChan has been fixed that caused ``-SIN'' projection
+codes within FITS-WCS headers to be mis-interpreted, resulting in no
+\htmlref{FrameSet}{FrameSet} being read by astRead.
+
+\item The \htmlref{KeyMap}{KeyMap} class has a new attribute called ``\htmlref{SortBy}{SortBy}''. It controls
+the order in which keys are returned by the
+\htmlref{AST\_MAPKEY}{AST\_MAPKEY}
+function. Keys can be sorted alphabetically or by age, or left unsorted.
+
+\item Access to KeyMaps holding thousands of entries is now significantly
+faster.
+
+\item KeyMaps can now hold word (i.e.
+INTEGER*2)
+values.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V5.4-0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.3-2 and V5.4-0:
+
+\begin{enumerate}
+
+\item the \htmlref{FitsChan}{FitsChan} class now has an option to support reading and writing
+of FITS-WCS headers that use the -TAB algorithm described in FITS-WCS paper
+III. This option is controlled by a new FitsChan attribute called \htmlref{TabOK}{TabOK}.
+See the documentation for TabOK for more information.
+
+\item A new class called ``\htmlref{Table}{Table}'' has been added. A Table is a \htmlref{KeyMap}{KeyMap} in
+which each entry represents a cell in a two-dimensional table.
+
+\item A new class called ``\htmlref{FitsTable}{FitsTable}'' has been added. A FitsTable is a
+Table that has an associated FitsChan holding headers appropriate to a
+FITS binary table.
+
+\item KeyMaps can now hold byte values. These are held in variables
+of type
+BYTE.
+
+\item KeyMaps have a new attribute called \htmlref{KeyCase}{KeyCase} that can be set to zero to
+make the handling of keys case insensitive.
+
+\item a memory leak associated with the use of the
+AST\_MAPPUTELEM<X>
+functions has been fixed.
+
+\item A new method called
+\htmlref{AST\_MAPRENAME}{AST\_MAPRENAME}
+has been added to rename existing entry in a KeyMap.
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.5-0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.4-0 and V5.5-0:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} ``\htmlref{TabOK}{TabOK}'' attribute is now an integer value rather
+than a boolean value. If TabOK is set to a non-zero positive integer
+before invoking the
+\htmlref{AST\_WRITE}{AST\_WRITE}
+method, its value is used as the version number for any table that is
+created as a consequence of the write operation. This is the value stored
+in the PVi\_1a keyword in the IMAGE header, and the EXTVER keyword in the
+binary table header. In previous versions of AST, the value used for these
+headers could not be controlled and was fixed at 1. If TabOK is set to a
+negative or zero value, the -TAB algorithm will not be supported by
+either the
+AST\_WRITE or \htmlref{AST\_READ}{AST\_READ}
+methods.
+
+\end{enumerate}
+
+
+
+\subsection{Changes Introduced in V5.6-0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.5-0 and V5.6-0:
+
+\begin{enumerate}
+
+\item
+New routines \htmlref{AST\_BBUF}{AST\_BBUF} and \htmlref{AST\_EBUF}{AST\_EBUF}
+have been added to the \htmlref{Plot}{Plot} class. These control the buffering of graphical
+output produced by other Plot methods.
+
+\item New functions astGBBuf and astGEBuf have been added to the interface
+defined by file \verb+grf.h+. The \htmlref{ast\_link}{ast\_link} command has been modified so
+that the \verb+-grf_v3.2+ switch loads dummy versions of the new grf
+functions. This means that applications that use the  \verb+-grf_v3.2+
+switch should continue to build without any change. However, the new public
+routines AST\_BBUF and AST\_EBUF
+will report an error unless the new grf functions are implemented. If you
+choose to implement them, you should modify your linking procedure to
+use the \verb+-grf+ (or  \verb+-grf_v5.6+ ) switch in place of the older
+\verb+-grf_v3.2+ switch. See the description of the ast\_link command for
+details of these switches.
+
+\item New method
+\htmlref{AST\_GETREGIONMESH}{AST\_GETREGIONMESH}
+returns a set of positions covering the boundary, or volume, of a supplied
+\htmlref{Region}{Region}.
+
+\end{enumerate}
+
+
+\subsection{ChangesIntroduced in V5.6-1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.6-0 and V5.6-1:
+
+\begin{enumerate}
+
+\item Tables can now have any number of parameters describing the global
+properties of the \htmlref{Table}{Table}.
+
+\item Frames now interpret the unit string ``A'' as meaning ``Ampere''
+rather than ``Angstrom'', as specified by FITS-WCS paper I.
+
+\item A bug has been fixed in the
+\htmlref{AST\_FINDFRAME}{AST\_FINDFRAME}
+method that allowed a template \htmlref{Frame}{Frame} of a more specialised class to match
+a target frame of a less specialised class. For example, this bug would
+allow a template \htmlref{SkyFrame}{SkyFrame} to match a target Frame. This no longer
+happens.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.7-0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.6-1 and V5.7-0:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class support for the IRAF-specific ``TNX'' projection has
+been extended to include reading TNX headers that use a Chebyshev
+representation for the distortion polynomial.
+
+\item The FitsChan class support for the IRAF-specific ``ZPX'' projection has
+been extended to include reading ZPX headers that use simple or Chebyshev
+representation for the distortion polynomial.
+
+\item A bug has been fixed in the FitsChan class that caused headers
+including the Spitzer ``-SIP'' distortion code to be read incorrectly if no
+inverse polynomial was specified in the header.
+
+\item A new attribute called \htmlref{PolyTan}{PolyTan} has been added to the FitsChan class. It
+can be used to indicate that FITS headers that specify a TAN projection
+should be interpreted according to the ``distorted TAN'' convention
+included in an early draft of FITS-WCS paper II. Such headers are created
+by (for instance) the SCAMP tool (\url{http://www.astromatic.net/software/scamp}).
+
+\item The \htmlref{PolyMap}{PolyMap} class now provides a method called
+\htmlref{AST\_POLYTRAN}{AST\_POLYTRAN}
+that adds an inverse transformation to a PolyMap by sampling the forward
+transformation on a regular grid, and then fitting a polynomial function
+from the resulting output values to the grid of input values.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.7-1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.7-0 and V5.7-1:
+
+\begin{enumerate}
+
+\item - All classes of \htmlref{Channel}{Channel} can now read to and write from specified
+text files, without the need to provide source and sink functions when
+the Channel is created. The files to use are specified by the new
+attributes \htmlref{SourceFile}{SourceFile} and \htmlref{SinkFile}{SinkFile}.
+
+\item - The \htmlref{FitsChan}{FitsChan} class now ignores trailing spaces in character-valued WCS
+keywords when reading a \htmlref{FrameSet}{FrameSet} from a FITS header.
+
+\item - If the FitsChan astRead method reads a FITS header that uses the
+-SIP (Spitzer) distortion code within the CTYPE values, but which does
+not provide an inverse polynomial correction, the FitsChan class will now
+use the PolyTran method of the \htmlref{PolyMap}{PolyMap} class to create an estimate of the
+inverse polynomial correction.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V5.7-2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.7-1 and V5.7-2:
+
+\begin{enumerate}
+
+
+\item The \htmlref{PolyMap}{PolyMap} class can now use an iterative Newton-Raphson method to
+evaluate the inverse the inverse transformation if no inverse
+transformation is defined when the PolyMap is created.
+
+\item The \htmlref{FitsChan}{FitsChan} class has a new method
+\htmlref{AST\_WRITEFITS}{AST\_WRITEFITS}
+which writes out all cards currently in the FitsChan to the associated
+external data sink (specified either by the \htmlref{SinkFile}{SinkFile} attribute or the
+sink function supplied when the FitsChan was created), and then empties
+the FitsChan.
+
+\item The FitsChan class has a new read-only attribute called ``\htmlref{Nkey}{Nkey}'', which
+holds the number of keywords for which values are held in a FitsChan.
+
+\item The FitsChan
+AST\_GETFITS<X>
+methods can now be used to returned the value of the current card.
+
+\item The FitsChan class has a new read-only attribute called ``\htmlref{CardType}{CardType}'', which
+holds the data type of the keyword value for the current card.
+
+\item The FitsChan class has a new method
+\htmlref{AST\_READFITS}{AST\_READFITS}
+which forces the FitsChan to reads cards from the associated external
+source and appends them to the end of the FitsChan.
+
+\item - If the FitsChan astRead method reads a FITS header that uses the
+-SIP (Spitzer) distortion code within the CTYPE values, but which does
+not provide an inverse polynomial correction, and for which the PolyTran
+method of the PolyMap class fails to create an accurate estimate of the
+inverse polynomial correction, then an iterative method will be used to
+evaluate the inverse correction for each point transformed.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V6.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.7-2 and V6.0:
+
+\begin{enumerate}
+
+\item This version of AST is the first that can be used with the Python
+AST wrapper module, starlink.Ast, available at \url{http://github.com/timj/starlink-pyast}.
+
+\item When reading a FITS-WCS header, the \htmlref{FitsChan}{FitsChan} class now recognises the
+non-standard ``TPV'' projection code within a CTYPE keyword value. This
+code is used by SCAMP (see www.astromatic.net/software/scamp) to
+represent a distorted TAN projection.
+
+\item The \htmlref{Plot}{Plot} class has been changed to remove visual anomalies (such as
+incorrectly rotated numerical axis labels) if the graphics coordinates have
+unequal scales on the X and Y axes.
+
+- The graphics escape sequences used to produce graphical sky axis labels
+can now be changed using the new
+routine \htmlref{AST\_TUNEC}{AST\_TUNEC}.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V6.0-1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V6.0 and V6.0-1:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class now recognises the Spitzer ``-SIP'' distortion
+code within FITS headers that describe non-celestial axes, as well as
+celestial axes.
+
+\item A bug has been fixed that could cause inappropriate equinox values to
+be used when aligning SkyFrames if the \htmlref{AlignSystem}{AlignSystem} attribute is set.
+
+\item The versioning string for AST has changed from
+``$<major>.<minor>-<release>$'' to ``$<major>.<minor>.<release>$''.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.0.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V6.0-1 and V7.0.0:
+
+\begin{enumerate}
+
+\item Fundamental positional astronomy calculations are now performed
+using the IAU SOFA library where possible, and the Starlink PAL library \xref{SUN/268}{sun268}{}
+otherwise (the PAL library contains a subset of the Fortran Starlink SLALIB
+library re-written in C). Copies of these libraries are bundled with AST
+and so do not need to be obtained or built separately, although external
+copies of SOFA and PAL can be used if necessary by including the
+``\texttt{--with-external\_pal}'' option when configuring AST.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.0.1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.0 and V7.0.1:
+
+\begin{enumerate}
+
+\item The levmar and wcslib code distributed within AST is now stored in the
+main AST library (libast.so) rather than in separate libraries.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.0.2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.1 and V7.0.2:
+
+\begin{enumerate}
+
+\item The libast\_pal library is no longer built if the
+``--with-external\_pal'' option is used when AST is configured.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.0.3}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.2 and V7.0.3:
+
+\begin{enumerate}
+
+\item A bug has been fixed which could cause an incorrect axis to be used when
+accessing axis attributes within CmpFrames. This could happen if axes
+within the \htmlref{CmpFrame}{CmpFrame} have been permuted.
+
+\item A bug has been fixed in the \htmlref{SkyFrame}{SkyFrame} class that could cause the two
+values of the SkyRef and/or SkyRefP attributes to be reversed.
+
+\item Bugs have been fixed in the \htmlref{CmpRegion}{CmpRegion} class that should allow the border
+around a compound \htmlref{Region}{Region} to be plotted more quickly, and more accurately.
+Previously, component Regions nested deeply inside a CmpRegion may have
+been completely or partially ignored.
+
+\item A bug has been fixed in the \htmlref{Plot3D}{Plot3D} class that caused a segmentation
+violation if the MinTick attribute was set to zero.
+
+\item The astResampleX set of methods now includes astResampleK and
+astResampleUK that handles 64 bit integer data.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V7.0.4}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.3 and V7.0.4:
+
+
+\begin{enumerate}
+
+\item The previously private grf3d.h header file is now installed into
+prefix/include.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V7.0.5}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.4 and V7.0.5:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class can now read FITS headers that use the SAO
+convention for representing distorted TAN projections, based on the use
+of ``COi\_m'' keywords to hold the coefficients of the distortion polynomial.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V7.0.6}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.5 and V7.0.6:
+
+\begin{enumerate}
+
+\item A bug has been fixed in astRebinSeq<X> which could result in
+incorrect normalisation of the final binned data and variance values.
+
+\item When reading a \htmlref{FrameSet}{FrameSet} from a FITS-DSS header, the keywords CNPIX1
+and CNPIX2 now default to zero if absent. Previously an error was reported.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V7.1.0}
+
+The following describes the most significant changes which occurred in the
+AST library between versions V7.0.6 and V7.1.0:
+
+\begin{enumerate}
+
+\item IMPORTANT! The default behaviour of astRebinSeq is now NOT to conserve
+flux. To conserve flux, the AST\_\_CONSERVEFLUX flag should be supplied
+when calling
+AST\_REBINSEQ<X>.
+Without this flag, each output value is a weighted mean of the neighbouring
+input values.
+
+\item A new flag AST\_\_NONORM can be used with astRebinSeq<X> to indicate that
+normalisation of the output arrays is not required. In this case no
+weights array need be supplied.
+
+\item A bug has been fixed in
+\htmlref{AST\_ADDFRAME}{AST\_ADDFRAME} routine
+that could result in the incorrect inversion of Mappings within the \htmlref{FrameSet}{FrameSet}
+when the AST\_\_ALLFRAMES flag is supplied for the
+IFRAME argument.
+
+\item The
+\htmlref{AST\_RATE}{AST\_RATE} function
+has been re-written to make it faster and more reliable.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.1.1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.1.0 and V7.1.1:
+
+\begin{enumerate}
+
+\item When a \htmlref{FitsChan}{FitsChan} is used to write an ``offset'' \htmlref{SkyFrame}{SkyFrame} (see attribute
+\htmlref{SkyRefIs}{SkyRefIs}) to a FITS-WCS encoded header, two alternate axis descriptions
+are now created - one for the offset coordinates and one for the absolute
+coordinates. If such a header is subsequently read back into AST, the
+original offset SkyFrame is recreated.
+
+\item A bug has been fixed in FitsChan that caused inappropriate CTYPE values
+to be generated when writing a \htmlref{FrameSet}{FrameSet} to FITS-WCS headers if the
+current \htmlref{Frame}{Frame} describes generalised spherical coordinates (i.e. a
+SkyFrame with \htmlref{System}{System}=Unknown).
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.2.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.1.1 and V7.2.0:
+
+\begin{enumerate}
+
+\item A new method call
+\htmlref{AST\_MAPDEFINED}{AST\_MAPDEFINED}
+has been added to the \htmlref{KeyMap}{KeyMap} class. It checks if a gtiven key name has
+a defined value in a given KeyMap.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.3.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.2.0 and V7.3.0:
+
+\begin{enumerate}
+
+\item The interface for the AST\_REBINSEQ<X> family of routines has
+been changed in order to allow a greater number of pixels to be pasted
+into the output array. The NUSED parameter is now an INTEGER*8 variable,
+instead of an INTEGER. APPLICATION CODE SHOULD BE CHANGED ACCORDINGLY TO
+AVOID SEGMENTATION FAULTS AND OTHER ERRATIC BEHAVIOUR.
+
+\item Added a new facility to the \htmlref{FrameSet}{FrameSet} class to allow each \htmlref{Frame}{Frame} to be
+associated with multiple Mappings, any one of which can be used to
+connect the Frame to the other Frames in the FrameSet. The choice of
+which \htmlref{Mapping}{Mapping} to use is controlled by the new ``\htmlref{Variant}{Variant}'' attribute of the
+FrameSet class.
+
+\item Mappings (but not Frames) that have a value set for their \htmlref{Ident}{Ident}
+attribute are now left unchanged by the
+c astSimplify function.
+f \htmlref{AST\_SIMPLIFY}{AST\_SIMPLIFY} routine.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.3.1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.3.0 and V7.3.1:
+
+\begin{enumerate}
+
+\item Fix a bug that could cauise a segmentation violation when reading
+certain FITS headers that use a TNX projection.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.3.2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.3.1 and V7.3.2:
+
+\begin{enumerate}
+
+\item Fix support for reading FITS header that use a GLS projection.
+Previously, an incorrect transformation was used for such projections if
+any CRVAL or CROTA value was non-zero.
+
+\item The \htmlref{KeyMap}{KeyMap} class has new sorting options ``KeyAgeUp'' and
+``KeyAgeDown'' that retain the position of an existing entry if its value
+is changed. See the \htmlref{SortBy}{SortBy} attribute.
+
+\item A bug has been fixed in the \htmlref{FitsChan}{FitsChan} class that caused CDELT keywords
+for sky axes to be treated as radians rather than degrees when reading a
+FITS header, if the corresponding CTYPE values included no projection code.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.3.3}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.3.2 and V7.3.3:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class has new attributes \htmlref{CardName}{CardName} and \htmlref{CardComm}{CardComm}, which hold
+the keyword name and comment of the current card.
+
+\item When using the FitsChan class to read FITS-WCS headers that include
+polynomial distortion in the SIP format, any inverse transformation specified
+in the header is now ignored and a new inverse is created to replace it based
+on the supplied forward transformation. Previously, an inverse was created
+only if the header did not include an inverse. The accuracy of the inverse
+transformation has also been improved, although it may now be slower to
+evaluate in some circumstances.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.3.4}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.3.3 and V7.3.4:
+
+\begin{enumerate}
+
+\item By default, the simplification of Polygons no longer checks that the
+edges are not bent by the simplification. A new attribute, \htmlref{SimpVertices}{SimpVertices},
+can be set to zero in order to re-instate this check.
+
+\item The \htmlref{Polygon}{Polygon} class has a new mathod,
+AST\_CONVEX,
+that returns a Polygon representing the shortest polygon (i.e. convex
+hull) enclosing a specified set of pixel values within a supplied array.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V8.0.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.3.4 and V8.0.0:
+
+\begin{enumerate}
+
+\item AST is now distributed under the Lesser GPL licence.
+
+\item The \htmlref{PolyMap}{PolyMap} class now uses files copied from the C/C++ Minpack
+package (see \url{http://devernay.free.fr/hacks/cminpack/index.html}) to perform
+least squares fitting of N-dimensional polynomials.
+
+\item Use of the IAU SOFA library has been replaced by ERFA library, which is
+a re-badged copy of SOFA distributed under a less restrictive license. A
+copy of ERFA is included within AST.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V8.0.1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.0 and V8.0.1:
+
+\begin{enumerate}
+
+\item The \htmlref{Base}{Base} and \htmlref{Current}{Current} attributes of a \htmlref{FrameSet}{FrameSet} may now be set using the
+  \htmlref{Domain}{Domain} name or the index of the required \htmlref{Frame}{Frame}.
+\item The order of WCS axes within new FITS-WCS headers created by astWrite
+  can now be controlled using a new attribute called \htmlref{FitsAxisOrder}{FitsAxisOrder}.
+\item Supported added for FITS XPH (polar HEALPIX) projection.
+\item The AST\_REBIN and AST\_REBINSEQ family of functions now include support
+  for arrays with \_BYTE (byte) and and \_UBYTE (unsigned byte) data types.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V8.0.2}
+The changes that occurred in the AST library between versions V8.0.1 and
+V8.0.2 only affect the C interface. The Fortran interface remains the
+same as V8.0.1.
+
+\subsection{Changes Introduced in V8.0.3}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.2 and V8.0.3:
+
+\begin{enumerate}
+
+\item Methods
+AST\_REBIN, AST\_REBINSEQ, AST\_RESAMPLE and \htmlref{AST\_TRANGRID}{AST\_TRANGRID}.
+now report an error if an array is specified that has more pixels than
+can be counted by a 32 bit integer.
+\item The hypertext documentation is now generated using Tex4HT rather
+than latex2html. The format of the hypertext docs has changed significantly.
+\item Another bug fix associated with reading CAR projections from
+FITS-WCS headers.
+\item Trailing spaces supplied within attribute setting strings are now ignored.
+\end{enumerate}
+
+\subsection{Changes Introduced in V8.0.4}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.3 and V8.0.4:
+
+\begin{enumerate}
+
+\item The behaviour of the
+\htmlref{AST\_ADDFRAME}{AST\_ADDFRAME} method has been changed slightly. Previously, AST\_ADDFRAME
+modified the \htmlref{FrameSet}{FrameSet} by storing references to the supplied \htmlref{Mapping}{Mapping} and
+\htmlref{Frame}{Frame} objects within the FrameSet. This meant that any subsequent changes
+to the current Frame of the modified FrameSet also affected the supplied
+Frame object. Now, deep copies of the Mapping and Frame objects (rather
+than references) are stored within the modified FrameSet. This means that
+subsequent changes to the modified FrameSet will now have no effect on
+the supplied Frame.
+
+\item The choice of default tick-mark gaps for time axes has been
+improved, to avoid a previous issue which could result in no suitable gap
+being found.
+
+- A new method called
+AST\_REGIONOUTLINE
+has been added to the \htmlref{Plot}{Plot} class. It draws the outline of a supplied AST
+\htmlref{Region}{Region}.
+
+\item A bug has been fixed that could cause astSimplfy to enter an infinite loop.
+
+\item Some improvements have been made to the Mapping simplification process
+that allow more Mappings to be simplified.
+
+\item The Frame class has a new read-only attribute called InternalUnit,
+which gives the units used for the unformatted (i.e. floating-point) axis
+values used internally by application code. For most Frames, the
+InternalUnit value is just the same as the Unit value (i.e. formatted and
+unformatted axis values use the same units). However, the \htmlref{SkyFrame}{SkyFrame} class
+always returns ``\texttt{rad}'' for InternalUnit, regardless of the value of
+Unit, indicating that floating-point SkyFrame axis values are always in units
+of radians.
+
+\item The \htmlref{LutMap}{LutMap} class has a new attribute called \htmlref{LutEpsilon}{LutEpsilon}, which specifies
+the relative error of the values in the table. It is used to decide if
+the LutMap can be simplified to a straight line.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V8.0.5}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.4 and V8.0.5:
+
+\begin{enumerate}
+
+\item The \htmlref{SkyFrame}{SkyFrame} class has a new attribute called \htmlref{SkyTol}{SkyTol}, which specifies
+the smallest significant distance within the SkyFrame. It is used to
+decide if the \htmlref{Mapping}{Mapping} between two SkyFrames can be considered a unit
+transformation. The default value is 0.001 arc-seconds.
+
+\item A bug has been fixed in the \htmlref{FitsChan}{FitsChan} class that prevented illegal
+characters within FITS keyword names (i.e. characters not allowed by the
+FITS standard) being detected. This bug could under some circumstances
+cause a subsequent segmentation violation to occur.
+
+\item A ``BadKeyName'' warning is now issued by the FitsChan class if a FITS
+keyword name is encountered that contains any illegal characters. See
+attribute ``\htmlref{Warnings}{Warnings}'' and
+routine ``\htmlref{AST\_WARNINGS}{AST\_WARNINGS}''.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V8.1.0}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.5 and V8.1.0:
+
+\begin{enumerate}
+
+\item The configure script has a new option ``--without-fortran'' that allows
+AST to be built in situations where no Fortran compiler is available. The
+resulting library has no Fortran interface and so cannot be used within
+Fortran applications. Also, the link scripts do not attempt to include the
+fortran runtime libraries.
+
+\end{enumerate}
+
+\subsection{\xlabel{changes}\xlabel{list_of_most_recent_changes}Changes
+Introduced in V8.2}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.1.0 and V8.2.0:
+
+\begin{enumerate}
+
+\item A new class of \htmlref{Mapping}{Mapping} called \htmlref{UnitNormMap}{UnitNormMap} has been added that converts
+a vector to a unit vector relative to a specified centre, plus length. A
+UnitNormMap has N inputs and N+1 outputs.The lower N output coordinates
+represent a unit vector parallel to the supplied input vector, and the
+(N+1)'th output coordinate is the length of the input vector.
+
+\item The restriction that Mappings are immutable has been extended to all
+Mapping classes. This means that attributes representing parameters of
+a Mapping's forward or inverse transformation cannot be changed after
+the Mapping has been created. In order to minimise the risk to existing
+software, this rule does not apply to Mappings that have not yet been
+included in other objects such as CmpMaps or FrameSets, or which have not
+yet been cloned. In other words, an error is reported if an attempt is
+made to change the nature of a Mapping's transformation, but only if the
+reference count of the Mapping is greater than one. The Mapping classes
+affected include: \htmlref{GrismMap}{GrismMap}, \htmlref{LutMap}{LutMap}, \htmlref{PcdMap}{PcdMap}, \htmlref{SphMap}{SphMap}, \htmlref{WcsMap}{WcsMap} and \htmlref{ZoomMap}{ZoomMap}.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V8.3}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.2.0 and V8.3.0:
+
+\begin{enumerate}
+
+\item A new method called \htmlref{AST\_AXNORM}{AST\_AXNORM}
+has been added to the \htmlref{Frame}{Frame} class that normalises an array of axis
+values. When used with SkyFrames, it allows longitude values to be
+normalised into the shortest range.
+
+\item A bug has been fixed in the Fortran include file AST\_PAR that caused constants
+related to $\pi$ to be defined as single rather than double precision.
+
+\item A bug has been fixed in the astGetRegionBounds method that could
+cause the wrong bounds to be returned for regions spanning a longitude =
+zero singularity.
+
+\end{enumerate}
+
+\subsection{\xlabel{changes}\xlabel{list_of_most_recent_changes}Changes
+Introduced in V8.4}
+The following describes the most significant changes which have
+occurred in the AST library between versions V8.3.0 and V8.4.0 (the
+current version):
+
+\begin{enumerate}
+
+\item The PAL library files included in the AST distribution have been updated
+to PAL version 0.9.7.
+
+\item Multiple identical NormMaps in series will now be simplified to a
+single \htmlref{NormMap}{NormMap}.
+
+\item A NormMap that encapsulates a basic \htmlref{Frame}{Frame} will now be simplified to a
+\htmlref{UnitMap}{UnitMap}.
+
+\item The \htmlref{AST\_TIMEADD}{AST\_TIMEADD}
+method of the \htmlref{TimeMap}{TimeMap} class now include an extra argument that gives the
+number of values supplied in the arguments array. Note, any existing code
+that uses this method will need to be changed.
+
+\item The \htmlref{AST\_SLAADD}{AST\_SLAADD}
+method of the \htmlref{SlaMap}{SlaMap} class now include an extra argument that gives the
+number of values supplied in the arguments array. Note, any existing code
+that uses this method will need to be changed.
+
+\item The \htmlref{AST\_SPECADD}{AST\_SPECADD}
+method of the \htmlref{SpecMap}{SpecMap} class now include an extra argument that gives the
+number of values supplied in the arguments array. Note, any existing code
+that uses this method will need to be changed.
+
+\item Multiple identical NormMaps in series will now be simplified to a
+single NormMap.
+
+\item A NormMap that encapsulates a basic Frame will now be simplified to a
+UnitMap.
+
+\item If the
+\htmlref{AST\_MAPREGION}{AST\_MAPREGION}
+method is used to map a \htmlref{Region}{Region} into a new Frame that has fewer axes than
+the original Region, and if the inverse transformation of the supplied
+\htmlref{Mapping}{Mapping} does not specify a value for the missing axes, then those axes
+are removed entirely from the Region. Previously they were retained, but
+automatically supplied with bad values. This affects the number of mesh
+points per axes for such Regions, and so affects the accuracy of overlap
+determination.
+
+\end{enumerate}
+
+Programs which are statically linked will need to be re-linked in
+order to take advantage of these new facilities.
+
+\end{document}
diff --git a/ast/sun210_figures/cmpframe.pdf b/ast/sun210_figures/cmpframe.pdf
new file mode 100644
index 0000000..fd82293
Binary files /dev/null and b/ast/sun210_figures/cmpframe.pdf differ
diff --git a/ast/sun210_figures/complex.pdf b/ast/sun210_figures/complex.pdf
new file mode 100644
index 0000000..b743453
Binary files /dev/null and b/ast/sun210_figures/complex.pdf differ
diff --git a/ast/sun210_figures/frames.pdf b/ast/sun210_figures/frames.pdf
new file mode 100644
index 0000000..66d2b2f
Binary files /dev/null and b/ast/sun210_figures/frames.pdf differ
diff --git a/ast/sun210_figures/frameset.pdf b/ast/sun210_figures/frameset.pdf
new file mode 100644
index 0000000..d5d9a1a
Binary files /dev/null and b/ast/sun210_figures/frameset.pdf differ
diff --git a/ast/sun210_figures/fronta.pdf b/ast/sun210_figures/fronta.pdf
new file mode 100644
index 0000000..3433a3d
Binary files /dev/null and b/ast/sun210_figures/fronta.pdf differ
diff --git a/ast/sun210_figures/fronta_bw.pdf b/ast/sun210_figures/fronta_bw.pdf
new file mode 100644
index 0000000..2a5ff7e
Binary files /dev/null and b/ast/sun210_figures/fronta_bw.pdf differ
diff --git a/ast/sun210_figures/frontb.pdf b/ast/sun210_figures/frontb.pdf
new file mode 100644
index 0000000..16aeef3
Binary files /dev/null and b/ast/sun210_figures/frontb.pdf differ
diff --git a/ast/sun210_figures/frontb_bw.pdf b/ast/sun210_figures/frontb_bw.pdf
new file mode 100644
index 0000000..78fa1db
Binary files /dev/null and b/ast/sun210_figures/frontb_bw.pdf differ
diff --git a/ast/sun210_figures/frontc.pdf b/ast/sun210_figures/frontc.pdf
new file mode 100644
index 0000000..69ab837
Binary files /dev/null and b/ast/sun210_figures/frontc.pdf differ
diff --git a/ast/sun210_figures/frontc_bw.pdf b/ast/sun210_figures/frontc_bw.pdf
new file mode 100644
index 0000000..17fce95
Binary files /dev/null and b/ast/sun210_figures/frontc_bw.pdf differ
diff --git a/ast/sun210_figures/fsalign.pdf b/ast/sun210_figures/fsalign.pdf
new file mode 100644
index 0000000..7d28fb7
Binary files /dev/null and b/ast/sun210_figures/fsalign.pdf differ
diff --git a/ast/sun210_figures/fsconvert.pdf b/ast/sun210_figures/fsconvert.pdf
new file mode 100644
index 0000000..5b07041
Binary files /dev/null and b/ast/sun210_figures/fsconvert.pdf differ
diff --git a/ast/sun210_figures/fsexample.pdf b/ast/sun210_figures/fsexample.pdf
new file mode 100644
index 0000000..1ba073a
Binary files /dev/null and b/ast/sun210_figures/fsexample.pdf differ
diff --git a/ast/sun210_figures/fsmerge.pdf b/ast/sun210_figures/fsmerge.pdf
new file mode 100644
index 0000000..2098b15
Binary files /dev/null and b/ast/sun210_figures/fsmerge.pdf differ
diff --git a/ast/sun210_figures/fsremap.pdf b/ast/sun210_figures/fsremap.pdf
new file mode 100644
index 0000000..2967186
Binary files /dev/null and b/ast/sun210_figures/fsremap.pdf differ
diff --git a/ast/sun210_figures/gridplot.pdf b/ast/sun210_figures/gridplot.pdf
new file mode 100644
index 0000000..65953d1
Binary files /dev/null and b/ast/sun210_figures/gridplot.pdf differ
diff --git a/ast/sun210_figures/gridplot_bw.pdf b/ast/sun210_figures/gridplot_bw.pdf
new file mode 100644
index 0000000..67353bc
Binary files /dev/null and b/ast/sun210_figures/gridplot_bw.pdf differ
diff --git a/ast/sun210_figures/mapping.pdf b/ast/sun210_figures/mapping.pdf
new file mode 100644
index 0000000..78c12b7
Binary files /dev/null and b/ast/sun210_figures/mapping.pdf differ
diff --git a/ast/sun210_figures/overgrid.pdf b/ast/sun210_figures/overgrid.pdf
new file mode 100644
index 0000000..82c65cf
Binary files /dev/null and b/ast/sun210_figures/overgrid.pdf differ
diff --git a/ast/sun210_figures/overgrid_bw.pdf b/ast/sun210_figures/overgrid_bw.pdf
new file mode 100644
index 0000000..10d65ab
Binary files /dev/null and b/ast/sun210_figures/overgrid_bw.pdf differ
diff --git a/ast/sun210_figures/parallel.pdf b/ast/sun210_figures/parallel.pdf
new file mode 100644
index 0000000..42ff646
Binary files /dev/null and b/ast/sun210_figures/parallel.pdf differ
diff --git a/ast/sun210_figures/series.pdf b/ast/sun210_figures/series.pdf
new file mode 100644
index 0000000..df2a23b
Binary files /dev/null and b/ast/sun210_figures/series.pdf differ
diff --git a/ast/sun210_figures/simpexamp.pdf b/ast/sun210_figures/simpexamp.pdf
new file mode 100644
index 0000000..d63a5af
Binary files /dev/null and b/ast/sun210_figures/simpexamp.pdf differ
diff --git a/ast/sun211.htx_tar b/ast/sun211.htx_tar
new file mode 100644
index 0000000..cf0c791
Binary files /dev/null and b/ast/sun211.htx_tar differ
diff --git a/ast/sun211.pdf b/ast/sun211.pdf
new file mode 100644
index 0000000..02bb0de
Binary files /dev/null and b/ast/sun211.pdf differ
diff --git a/ast/sun211.tex b/ast/sun211.tex
new file mode 100644
index 0000000..9618cab
--- /dev/null
+++ b/ast/sun211.tex
@@ -0,0 +1,52927 @@
+\documentclass[twoside,11pt]{starlink}
+
+% ? Specify used packages
+% ? End of specify used packages
+
+% -----------------------------------------------------------------------------
+% ? Document identification
+% Fixed part
+\stardoccategory    {Starlink User Note}
+\stardocinitials    {SUN}
+\stardocsource      {sun\stardocnumber}
+
+% Variable part - replace [xxx] as appropriate.
+\stardocnumber      {211.27}
+\stardocauthors     {R.F. Warren-Smith \& D.S. Berry}
+\stardocdate        {26th October 2016}
+\stardoctitle     {AST\linebreak%
+                                A Library for Handling\linebreak%
+                                World Coordinate Systems\linebreak%
+                                in Astronomy}
+\stardoccopyright   {Copyright (C) 2014 Science \& Technology Facilities Council}
+\stardocversion     {V8.4}
+\stardocmanual      {Programmer's Guide\\(C Version)}
+\startitlepic{
+   \includegraphics[width=0.25\textwidth]{sun211_figures/fronta}~~~~~\hfill
+   \includegraphics[width=0.25\textwidth]{sun211_figures/frontb}~~~~~\hfill
+   \includegraphics[width=0.25\textwidth]{sun211_figures/frontc}
+}
+\stardocabstract  {
+The AST library provides a comprehensive range of facilities for
+attaching world coordinate systems to astronomical data, for
+retrieving and interpreting that information in a variety of formats,
+including FITS-WCS, and for generating graphical output based on it.
+
+This programmer's manual should be of interest to anyone writing
+astronomical applications which need to manipulate coordinate system
+data, especially celestial or spectral coordinate systems. AST is portable and
+environment-independent.
+}
+% ? End of document identification
+% -----------------------------------------------------------------------------
+% ? Document specific \providecommand or \newenvironment commands.
+
+\providecommand{\appref}[1]{Appendix~\ref{#1}}
+\providecommand{\secref}[1]{\S\ref{#1}}
+
+\providecommand{\fitskey}[3]{{#1}&{#2}&{#3}\\}
+
+% Use {\tt ... } as \texttt{...} does not work if there are new lines in #1
+\providecommand{\sstsynopsis}[1]{\sstdiytopic{Synopsis}{\tt #1}}
+
+%  Format the constructor section.
+\providecommand{\sstconstructor}[1]{\sstdiytopic{Constructor Function}{#1}}
+
+% ? End of document specific commands
+% -----------------------------------------------------------------------------
+%  \htmlref{Title}{Title} Page.
+%  ===========
+\begin{document}
+\scfrontmatter
+
+\begin{center}
+\emph{This is the C version of this document.\\
+  For the Fortran version, please see \xref{SUN/210}{sun210}{}.}
+\end{center}
+
+% Main text of document.
+\vspace{7mm}
+\section{Introduction}
+
+Welcome to the AST library. If you are writing software for astronomy
+and need to use celestial coordinates (\emph{e.g.}\ RA and Dec), spectral
+coordinates (\emph{e.g.}\ wavelength, frequency, \emph{etc.}), or
+other coordinate system information, then this library should be of
+interest. It provides solutions for most of the problems you will meet
+and allows you to write robust and flexible software. It is able to read
+and write WCS information in a variety of formats, including
+\htmladdnormallink{FITS-WCS}{http://fits.gsfc.nasa.gov/fits_wcs.html}.
+
+%\subsection{TBW---What is a World Coordinate \htmlref{System}{System}?}
+
+\subsection{What Problems Does AST Tackle?}
+
+Here are some of the main problems you may face when handling world
+coordinate system (WCS) information and the solutions that AST
+provides:
+
+\begin{description}
+\item[1. The Variety of Coordinate Systems]\mbox{}\\
+Astronomers use a wide range of differing coordinate systems to describe
+positions within a variety of physical domains. For instance, there are a
+large number of celestial coordinate systems in use within astronomy to
+describe positions on the sky. Understanding these, and knowing how to
+convert coordinates between them, can require considerable expertise. It
+can also be difficult to decide which of them your software should support.
+The same applies to coordinate systems describing other domains, such as
+position within an electro-magnetic spectrum.
+
+\textbf{Solution.} AST has built-in knowledge of many coordinate systems
+and allows you to convert freely between them without specialist
+knowledge. This avoids the need to embed details of specific
+coordinate systems in your software. You also benefit automatically
+when new coordinate systems are added to AST.
+
+\item[2. Storing and Retrieving WCS Information]\mbox{}\\
+Storing coordinate system information in astronomical datasets and
+retrieving it later can present a considerable challenge. Typically,
+it requires knowledge of rather complex conventions
+(\emph{e.g.}\ FITS) which are low-level, often mis-interpreted and may
+be subject to change. Exchanging information with other software
+systems is further complicated by the number of different conventions
+in use.
+
+\textbf{Solution.} AST combines a unifying high-level description of WCS
+information with the ability to save and restore this using a variety
+of formats. Details of the formats, which include FITS, are handled
+internally by AST. This frees you from the need to understand them or
+embed the details in your software. Again, you benefit automatically
+when new formats are added to AST.
+
+\item[3. Generating Graphical Output]\mbox{}\\
+Producing graphical displays involving curvilinear coordinate systems,
+such as celestial coordinate grids, can be complicated. Particular
+difficulties arise when handling large areas of sky, the polar regions
+and discontinuous (\emph{e.g.}\ segmented) sky projections.  Even just
+numbering and labelling curvilinear axes is rarely straightforward.
+
+\textbf{Solution.} AST provides plotting facilities especially designed
+for use with curvilinear coordinate systems. These include the
+plotting of axes and complete labelled coordinate grids.  A large
+number of options are provided for tailoring the output to your
+specific needs. Three dimensional coordinate grids can also be produced.
+
+\item[4. Aligning Data from Different Sources]\mbox{}\\
+One of the main uses of coordinate systems is to facilitate the
+inter-comparison of data from different sources. A typical use might
+be to plot (say) radio contours over an optical image.  In practice,
+however, different celestial coordinate systems may have been used,
+making accurate alignment far from simple.
+
+\textbf{Solution} AST provides a one-step method of aligning datasets,
+searching for all possible intermediate coordinate systems.  This
+makes it simple to directly inter-relate the pixel coordinates of
+different datasets.
+
+\item[5. Handling Different Types of Coordinate \htmlref{System}{System}]\mbox{}\\
+Not all coordinate systems used in astronomy are celestial ones, so if
+you are writing general-purpose software such as (say) a display tool,
+you may also need to handle axes representing wavelength, distance,
+time or whatever else comes along. Obviously, you would prefer not to
+handle each one as a special case.
+
+\textbf{Solution} AST uses the same flexible high-level model to
+describe all types of coordinate system. This allows you to write
+software that handles different kinds of coordinate axis without
+introducing special cases.
+\end{description}
+
+\subsection{Other Design Objectives}
+
+As well as its scientific objectives, the AST library's design
+includes a number of technical criteria intended to make it applicable
+to as wide a range of projects as possible. The main considerations
+are described here:
+
+\begin{enumerate}
+\item {\bf{Minimum Software Dependencies.}}
+The AST library depends on no other other software\footnote{It comes with
+bundled copies of the ERFA and
+\xref{Starlink PAL libraries}{sun268}{} which are built
+at the same time as the other AST internal libraries. Alternatively, external
+PAL and ERFA libraries may be used by specifying the ``\texttt{--with-external\_pal}'' option when configuring AST}.
+
+\item {\bf{Environment Independence.}}
+AST is designed so that it can operate in a variety of ``programming
+environments'' and is not tied to any particular one. To allow this,
+it uses simple, flexible interfaces to obtain the following services:
+
+\begin{itemize}
+\item {\bf{Data Storage.}} Data I/O operations are based on text
+and/or FITS headers. This makes it easy to interface to a wide variety
+of astronomical data formats in a machine-independent way.
+
+\item {\bf{Graphics.}} Graphical output is produced \emph{via} a
+simple generic graphics interface, which may easily be re-implemented
+over different graphics systems. AST provides a default implementation
+based on the widely-used PGPLOT graphics system
+(\xref{SUN/15}{sun15}{}).
+
+\item {\bf{Error Handling.}} Error messages are written to standard
+error by default, but go through a simple generic interface similar to
+that used for graphics (above). This permits error message delivery
+\emph{via} other routes when necessary (\emph{e.g.} in a graphical
+interface).
+\end{itemize}
+
+\item {\bf{Multiple Language Support.}}
+AST has been designed to be called from more than one language.
+Both C and Fortran interfaces are available (see
+\xref{SUN/210}{sun210}{} for the Fortran version)
+and use from C$++$ is also straightforward if the C interface is
+included using:
+
+\begin{small}
+\begin{terminalv}
+extern "C" {
+#include "ast.h"
+}
+\end{terminalv}
+\end{small}
+
+A JNI interface (known as ``JNIAST'' - see
+\url{http://www.starlink.ac.uk/jniast/}) has also been developed by Starlink
+which allows AST to be used from Java.
+
+\item {\bf{\htmlref{Object}{Object} Oriented Design.}}
+AST uses ``object oriented'' techniques internally in order to provide
+a flexible and easily-extended programming model.  A fairly
+traditional calling interface is provided, however, so that the
+library's facilities are easily accessible to programmers using
+C and Fortran.
+
+\item {\bf{Portability.}}
+AST is implemented entirely in ANSI standard C and, when called
+\emph{via} its C interface, makes no explicit use of any
+machine-dependent facilities.
+
+The Fortran interface is, unavoidably, machine dependent. However, the
+potential for problems has been minimised by encapsulating the
+interface layer in a compact set of C macros which facilitate its
+transfer to other platforms. No Fortran compiler is needed to build
+the library.
+
+Currently, AST is supported by Starlink on PC~Linux, Sun~Solaris and
+Tru64~Unix (formerly DEC~UNIX) platforms.
+\end{enumerate}
+
+\subsection{What Does ``AST'' Stand For?}
+
+The library name ``AST'' stands for ``ASTrometry Library''. The name
+arose when it was thought that knowledge of ``astrometry''
+(\emph{i.e.}\ celestial coordinate systems) would form the bulk of the
+library.  In fact, it turns out that astrometry forms only a minor
+component, but the name AST has stuck.
+
+\cleardoublepage
+\section{Overview of AST Concepts}
+
+This section presents a brief overview of AST concepts. It is intended
+as a basic orientation course before you move on to the more technical
+considerations in subsequent sections.
+
+\subsection{\label{ss:mappingoverview}Relationships Between Coordinate Systems}
+
+The relationships between coordinate systems are represented in AST by
+Objects called Mappings. A \htmlref{Mapping}{Mapping} does not represent a coordinate
+system itself, but merely the process by which you move from one
+coordinate system to another related one.
+
+   A convenient picture of a Mapping is as a ``black box''
+   (Figure~\ref{fig:mapping}) into which you can feed sets of
+   coordinates.
+   \begin{figure}[bhtp]
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun211_figures/mapping}
+   \caption{A Mapping viewed as a ``black box'' for transforming coordinates.}
+   \label{fig:mapping}
+   \end{center}
+   \end{figure}
+
+For each set you feed in, the Mapping returns a corresponding set of
+transformed coordinates. Since each set of coordinates represents a
+point in a coordinate space, the Mapping acts to inter-relate
+corresponding positions in the two spaces, although what these spaces
+represent is unspecified.  Notice that a Mapping need not have the
+same number of input and output coordinates. That is, the two
+coordinate spaces which it inter-relates need not have the same number
+of dimensions.
+
+In many cases, the transformation can, in principle, be performed in
+either direction: either from the \emph{input} coordinate space to the
+\emph{output}, or \emph{vice versa}. The first of these is termed the
+\emph{forward} transformation and the other the \emph{inverse}
+transformation.
+
+
+\textbf{Further reading:} For a more complete discussion of Mappings,
+see~\secref{ss:mappings}.
+
+\subsection{\label{ss:mappingselection}Mappings Available}
+
+The basic concept of a \htmlref{Mapping}{Mapping} (\secref{ss:mappingoverview}) is rather
+generic and obviously it is necessary to have specific Mappings that
+implement specific relationships between coordinate systems. AST
+provides a range of these, to perform transformations such as the
+following and, where appropriate, their inverses:
+
+\begin{itemize}
+\item Conversions between various celestial coordinate systems (the
+\htmlref{SlaMap}{SlaMap}).
+
+\item Conversions between various spectral coordinate systems (the
+\htmlref{SpecMap}{SpecMap} and \htmlref{GrismMap}{GrismMap}).
+
+\item Conversions between various time systems (the \htmlref{TimeMap}{TimeMap}).
+
+\item Conversion between 2-dimensional spherical celestial coordinates
+(longitude and latitude) and a 3-dimensional vectorial positions (the \htmlref{SphMap}{SphMap}).
+
+\item Various projections of the celestial sphere on to 2-dimensional
+coordinate spaces---\emph{i.e.}\ map projections (the \htmlref{DssMap}{DssMap} and \htmlref{WcsMap}{WcsMap}).
+
+\item Permutation, introduction and elimination of coordinates (the
+\htmlref{PermMap}{PermMap}).
+
+\item Various linear coordinate transformations (the \htmlref{MatrixMap}{MatrixMap}, \htmlref{WinMap}{WinMap},
+\htmlref{ShiftMap}{ShiftMap} and \htmlref{ZoomMap}{ZoomMap}).
+
+\item General N-dimensional polynomial transformations (the \htmlref{PolyMap}{PolyMap}).
+
+\item Lookup tables (the \htmlref{LutMap}{LutMap}).
+
+\item General-purpose transformations expressed using arithmetic
+operations and functions similar to those available in C (the
+\htmlref{MathMap}{MathMap}).
+
+\item Transformations for internal use within a program, based on
+private transformation functions which you write yourself in C (the
+\htmlref{IntraMap}{IntraMap}).
+\end{itemize}
+
+\textbf{Further reading:} For a more complete description of each of the
+Mappings mentioned above, see its entry in
+\appref{ss:classdescriptions}. In addition, see the discussion of the
+PermMap in \secref{ss:permmapexample}, the \htmlref{UnitMap}{UnitMap} in
+\secref{ss:unitmapexample} and the IntraMap in
+\secref{ss:intramaps}. The ZoomMap is used as an example throughout
+\secref{ss:primer}.
+
+\subsection{\label{ss:cmpmapoverview}Compound Mappings}
+
+The Mappings described in \secref{ss:mappingselection} provide a set
+of basic building blocks from which more complex Mappings may be
+constructed. The key to doing this is a type of \htmlref{Mapping}{Mapping} called a
+\htmlref{CmpMap}{CmpMap}, or compound Mapping.  A CmpMap's role is, in principle, very
+simple: it allows any other pair of Mappings to be joined together
+into a single entity which behaves as if it were a single Mapping. A
+CmpMap is therefore a container for another pair of Mappings.
+
+   A pair of Mappings may be combined using a CmpMap in either of two
+   ways. The first of these, \emph{in series}, is illustrated in
+   Figure~\ref{fig:seriescmpmap}.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun211_figures/series}
+   \caption[A CmpMap composed of two component Mappings joined in series]{A CmpMap (compound Mapping) composed of two component
+   Mappings joined in series. The output coordinates of the first Mapping
+   feed into the input coordinates of the second one, so that the whole
+   entity behaves like a single Mapping.}
+   \label{fig:seriescmpmap}
+   \end{center}
+   \end{figure}
+
+
+   Here, the transformations implemented by each component Mapping are
+   performed one after the other, with the output from the first Mapping
+   feeding into the second.  The second way, \emph{in parallel}, is shown in
+   Figure~\ref{fig:parallelcmpmap}.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.5\textwidth]{sun211_figures/parallel}
+   \caption[A CmpMap composed of two Mappings joined in parallel.]{A CmpMap composed of two Mappings joined in parallel. Each
+   component Mapping acts on a complementary subset of the input and
+   output coordinates.}
+   \label{fig:parallelcmpmap}
+   \end{center}
+   \end{figure}
+
+In this case, each Mapping acts on a complementary subset of the
+input and output coordinates.\footnote{A pair of Mappings can be combined
+in a third way using a \htmlref{TranMap}{TranMap}. A TranMap allows the forward
+transformation of one Mapping to be combined with the inverse
+transformation of another to produce a single Mapping.}
+
+   The CmpMap forms the key to building arbitrarily complex Mappings
+   because it is itself a form of Mapping. This means that a CmpMap may
+   contain other CmpMaps as components
+   (\emph{e.g.}\ Figure~\ref{fig:complexcmpmap}). This nesting of CmpMaps
+   can be repeated indefinitely, so that complex Mappings may be built in
+   a hierarchical manner out of simper ones.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.65\textwidth]{sun211_figures/complex}
+   \caption[CmpMaps may be nested in order to
+   construct complex Mappings out of simpler building blocks.]{CmpMaps
+   (compound Mappings) may be nested in order to
+   construct complex Mappings out of simpler building blocks.}
+   \label{fig:complexcmpmap}
+   \end{center}
+   \end{figure}
+   This gives AST great flexibility in the coordinate transformations it
+   can describe.
+
+\textbf{Further reading:} For a more complete description of CmpMaps,
+see \secref{ss:cmpmaps}. Also see the CmpMap entry in
+\appref{ss:classdescriptions}.
+
+\subsection{Representing Coordinate Systems}
+
+   While Mappings (\secref{ss:mappingoverview}) represent the
+   relationships between coordinate systems in AST, the coordinate
+   systems themselves are represented by Objects called Frames
+   (Figure~\ref{fig:frames}).
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.55\textwidth]{sun211_figures/frames}
+   \caption[Representing coordinate systems as Frames.]{(a) A basic Frame is used to represent a Cartesian coordinate
+   system, here 2-dimensional. (b) A \htmlref{SkyFrame}{SkyFrame} represents a (spherical)
+   celestial coordinate system. (c) The axis order of any \htmlref{Frame}{Frame} may be
+   permuted to match the coordinate space it describes.}
+   \label{fig:frames}
+   \end{center}
+   \end{figure}
+
+A Frame is similar in concept to the frame you might draw around a
+graph.  It contains information about the labels which appear on the
+axes, the axis units, a title, knowledge of how to format the
+coordinate values on each axis, \emph{etc.}  An AST Frame is not,
+however, restricted to two dimensions and may have any number of axes.
+
+A basic Frame may be used to represent a Cartesian coordinate system
+by setting values for its \emph{attributes} (all AST Objects have
+values associated with them called attributes, which may be set and
+enquired).  Usually, this would involve setting appropriate axis
+labels and units, for example.  Functions are provided for use with
+Frames to perform operations such as formatting coordinate values as
+text, calculating distances between points, interchanging axes,
+\emph{etc.}
+
+There are several more specialised forms of Frame, which provide the
+additional functionality required when handling coordinates within some
+specific physical domain. This ranges from tasks such as formatting axis
+values, to complex tasks such as determining the transformation between
+any pair of related coordinate systems. For instance, the SkyFrame
+(Figure~\ref{fig:frames}b,c), represents celestial coordinate systems,
+the \htmlref{SpecFrame}{SpecFrame} represents spectral coordinate systems, and the \htmlref{TimeFrame}{TimeFrame}
+represents time coordinate systems. All these provide a wide range of
+different systems for describing positions within their associated physical
+domain, and these may be selected by setting appropriate attributes.
+
+   As with compound Mappings (\secref{ss:cmpmapoverview}), it is possible
+   to merge two Frames together to form a compound Frame, or \htmlref{CmpFrame}{CmpFrame}, in
+   which both sets of axes are combined.  One could, for example, have
+   celestial coordinates on two axes and an unrelated coordinate
+   (wavelength, perhaps) on a third (Figure~\ref{fig:cmpframe}).
+   Knowledge of the relationships between the axes is preserved
+   internally by the process of constructing the CmpFrame which
+   represents them.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.4\textwidth]{sun211_figures/cmpframe}
+   \caption[A CmpFrame (compound Frame) formed by combining two simpler
+   Frames.]{A CmpFrame (compound Frame) formed by combining two simpler
+   Frames. Note how the special relationship which exists between the RA
+   and Dec axes is preserved within this data structure. As with compound
+   Mappings (Figure~\ref{fig:complexcmpmap}), CmpFrames may be nested in
+   order to build more complex Frames.}
+   \label{fig:cmpframe}
+   \end{center}
+   \end{figure}
+
+\textbf{Further reading:} For a more complete description of Frames see
+\secref{ss:frames}, for SkyFrames see \secref{ss:skyframes} and for
+SpecFrames see \secref{ss:specframes}.  Also see the Frame, SkyFrame,
+SpecFrame, TimeFrame and CmpFrame entries in \appref{ss:classdescriptions}.
+
+\subsection{Networks of Coordinate Systems}
+
+   Mappings and Frames may be connected together to form networks called
+   FrameSets, which are used to represent sets of inter-related
+   coordinate systems (Figure~\ref{fig:frameset}).
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.65\textwidth]{sun211_figures/frameset}
+   \caption[A FrameSet is a network of Frames.]{A FrameSet is a network of Frames inter-connected by Mappings
+   such that there is exactly one conversion path, \emph{via} Mappings,
+   between any pair of Frames.}
+   \label{fig:frameset}
+   \end{center}
+   \end{figure}
+
+
+A \htmlref{FrameSet}{FrameSet} may be extended by adding a new \htmlref{Frame}{Frame} to it, together with
+an associated \htmlref{Mapping}{Mapping} which relates the new coordinate system to one
+which is already present.  This process ensures that there is always
+exactly one path, \emph{via} Mappings, between any pair of Frames.  A
+function is provided for identifying this path and returning the
+complete Mapping.
+
+One of the Frames in a FrameSet is termed its \emph{base} Frame.  This
+underlies the FrameSet's purpose, which is to calibrate datasets and
+other entities by attaching coordinate systems to them.  In this
+context, the base Frame represents the ``native'' coordinate system
+(for example, the pixel coordinates of an image).  Similarly, one
+Frame is termed the \emph{current} Frame and represents the
+``currently-selected'' coordinates.  It might, typically, be a
+celestial or spectral coordinate system and would be used during
+interactions with
+a user, as when plotting axes on a graph or producing a table of
+results.  Other Frames within the FrameSet represent a library of
+alternative coordinate systems which a software user can select by
+making them current.
+
+\textbf{Further reading:} For a more complete description of
+FrameSets, see \secref{ss:framesets} and \secref{ss:fshigher}. Also
+see the FrameSet entry in \appref{ss:classdescriptions}.
+
+\subsection{Input/Output Facilities}
+
+AST allows you to convert any kind of \htmlref{Object}{Object} into a stream of text
+which contains a full description of that Object. This text may be
+written out by one program and read back in by another, thus allowing
+the original Object to be reconstructed.
+
+The filter which converts Objects into text and back again is itself a
+kind of Object, called a \htmlref{Channel}{Channel}. A Channel provides a number of
+options for controlling the information content of the text, such as
+the addition of comments for human interpretation.  It is also
+possible to intercept the text being processed by a Channel so that it
+may be redirected to/from any chosen external data store, such as a
+text file, an astronomical dataset, or a network connection.
+
+The text format used by the basic Channel class is peculiar to the AST
+library - no other software will understand it. However, more specialised
+forms of Channel are provided which use text formats more widely
+understood.
+
+To further facilitate the storage of coordinate system information in
+astronomical datasets, a more specialised form of Channel called a
+\htmlref{FitsChan}{FitsChan} is provided. Instead of using free-format text, a FitsChan
+converts AST Objects to and from FITS header cards. It also allows the
+information to be encoded in the FITS cards in a number of ways
+(called \emph{encodings}), so that WCS information from a variety of
+sources can be handled.
+
+Another sub-class of Channel, called \htmlref{XmlChan}{XmlChan}, is a specialised form of
+Channel that stores the text in the form of XML markup. Currently, two
+markup formats are provided by the XmlChan class, one is closely related
+to the text format produced by the basic Channel class (currently, no
+schema or DTD is available describing this format). The other is a subset
+of an early draft of the IVOA Space-Time-Coordinates XML (STC-X) schema
+(V1.20) described at
+\url{http://www.ivoa.net/Documents/WD/STC/STC-20050225.html
+}\footnote{XML documents which use only the subset of the STC schema
+supported by AST can be read by the XmlChan class to produce
+corresponding AST objects (subclasses of the \htmlref{Stc}{Stc} class). However, the
+reverse is not possible. That is, AST objects can not currently be
+written out in the form of STC documents.}. The version of STC-X that has
+been adopted by the IVOA differs in several significant respects from
+V1.20, and therefore this XmlChan format is of historical interest only.
+
+Finally, the \htmlref{StcsChan}{StcsChan} class provides facilities for reading and writing
+IVOA STC-S region descriptions. STC-S (see
+\url{http://www.ivoa.net/Documents/latest/STC-S.html}) is a linear string
+syntax that allows simple specification of STC metadata. AST supports a
+subset of the STC-S specification, allowing an STC-S description of a
+region within an AST-supported astronomical coordinate system to be converted
+into an equivalent AST \htmlref{Region}{Region} object, and vice-versa.
+
+\textbf{Further reading:} For a more complete description of Channels
+see \secref{ss:channels} and for FitsChans see \secref{ss:nativefits}
+and \secref{ss:foreignfits}. Also see the Channel and FitsChan entries
+in \appref{ss:classdescriptions} and the \htmlref{Encoding}{Encoding} entry in
+\appref{ss:attributedescriptions}.
+
+\subsection{Producing Graphical Output}
+
+Two dimensional graphical output is supported by a specialised form of
+\htmlref{FrameSet}{FrameSet} called
+a \htmlref{Plot}{Plot}, whose base \htmlref{Frame}{Frame} corresponds with the native coordinates of
+the underlying graphics system.  Plotting operations are specified in
+\emph{physical coordinates} which correspond with the Plot's current
+Frame. Typically, this might be a celestial coordinate system.
+
+Three dimensional plotting is also supported, via the \htmlref{Plot3D}{Plot3D} class -
+sub-class of Plot.
+
+Operations, such as drawing lines, are automatically transformed from
+physical to graphical coordinates before plotting, using an adaptive
+algorithm which ensures smooth curves (because the transformation is
+usually non-linear).  ``Missing'' coordinates (\emph{e.g.}\ graphical
+coordinates which do not project on to the celestial sphere),
+discontinuities and generalised clipping are all consistently handled.
+It is possible, for example, to plot in equatorial coordinates and
+clip in galactic coordinates.  The usual plotting operations are
+provided (text, markers), but a geodesic curve replaces the primitive
+straight line element.  There is also a separate function for drawing
+axis lines, since these are normally not geodesics.
+
+In addition to drawing coordinate grids over an area of the sky, another
+common use of the Plot class is to produce line plots such as flux
+against wavelength, displacement again time, \emph{etc}. For these
+situations the current Frame of the Plot would be a compound Frame
+(\htmlref{CmpFrame}{CmpFrame}) containing a pair of 1-dimensional Frames - the first
+representing the X axis quantity (wavelength, time, etc), and the second
+representing the Y axis quantity (flux, displacement, etc). The Plot
+class includes an option for axes to be plotted logarithmically.
+
+   Perhaps the most useful graphics function available is for drawing
+   fully annotated coordinate grids (\emph{e.g.}\ Figure~\ref{fig:gridplot}).
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.6\textwidth]{sun211_figures/gridplot_bw}
+   \caption[A labelled coordinate grid for an all-sky zenithal equal area
+   projection in ecliptic coordinates.]{A labelled coordinate grid for an all-sky zenithal equal area
+   projection in ecliptic coordinates. This was composed and drawn
+   \emph{via} a Plot using a
+   single function call.}
+   \label{fig:gridplot}
+   \end{center}
+   \end{figure}
+
+This uses a general algorithm which does not depend on knowledge of
+the coordinates being represented, so can also handle
+programmer-defined coordinate systems.  Grids for all-sky projections,
+including polar regions, can be drawn and most aspects of the output
+(colour, line style, \emph{etc.}) can be adjusted by setting
+appropriate Plot attributes.
+
+\textbf{Further reading:} For a more complete description of
+Plots and how to produce graphical output, see \secref{ss:plots}. Also
+see the Plot entry in \appref{ss:classdescriptions}.
+
+\cleardoublepage
+\section{\label{ss:howto}How To\ldots}
+
+For those of you with a plane to catch, this section provides some
+instant templates and recipes for performing the most
+commonly-required operations using AST, but without going into
+detail. The examples given (sort of) follow on from each other, so you
+should be able to construct a variety of programs by piecing them
+together.  Note that some of them appear longer than they actually
+are, because we have included plenty of comments and a few options
+that you probably won't need.
+
+If any of this material has you completely baffled, then you may want
+to read the introduction to AST programming concepts in
+\secref{ss:primer} first. Otherwise, references to more detailed
+reading are given after each example, just in case they don't quite do
+what you want.
+
+\subsection{\ldots Obtain and Install AST}
+The AST library is available both as a stand-alone package and also as
+part of the Starlink Software Collection\footnote{The Starlink Software
+Collection can be downloaded from
+\url{http://www.starlink.ac.uk/Download/}.}. If your site has the Starlink
+Software Collection installed then AST should already be available.
+
+If not, you can download the AST library by itself from
+\url{http://www.starlink.ac.uk/ast/}.
+
+\subsection{\ldots Structure an AST Program}
+
+An AST program normally has the following structure:
+
+\begin{small}
+\begin{terminalv}
+/* Include the interface to the AST library. */
+#include "ast.h"
+
+/* Main program (or could be any function). */
+main () {
+   <normal C declarations and statements>
+
+/* Enclose the parts which use AST between the astBegin and astEnd macros. */
+   astBegin;
+   <C statements which use AST>
+   astEnd;
+
+   <maybe more C statements>
+}
+\end{terminalv}
+\end{small}
+
+The use of \htmlref{astBegin}{astBegin} and \htmlref{astEnd}{astEnd} is optional, but has the effect of
+tidying up after you have finished using AST, so is normally
+recommended. For more details of this, see \secref{ss:contexts}. For
+details of how to access the ``ast.h'' header file, see
+\secref{ss:accessingheaderfile}.
+
+\subsection{\label{ss:howtobuild}\ldots Build an AST Program}
+
+To build a simple AST program that doesn't use graphics, use:
+
+\begin{small}
+\begin{terminalv}
+cc program.c -L/star/lib -I/star/include `ast_link` -o program
+\end{terminalv}
+\end{small}
+
+To build a program which uses PGPLOT for graphics, use:
+
+\begin{small}
+\begin{terminalv}
+cc program.c -L/star/lib `ast_link -pgplot` -o program
+\end{terminalv}
+\end{small}
+
+For more details about accessing the ``ast.h'' header file, see
+\secref{ss:accessingheaderfile}. For more
+details about linking programs, see \secref{ss:linking} and the
+description of the ``\htmlref{ast\_link}{ast\_link}'' command in
+\appref{ss:commanddescriptions}.
+
+\subsection{\label{ss:howtoreadwcs}\ldots Read a WCS Calibration from a Dataset}
+
+Precisely how you extract world coordinate system (WCS) information
+from a dataset obviously depends on what type of dataset it
+is. Usually, however, you should be able to obtain a set of FITS
+header cards which contain the WCS information (and probably much more
+besides). Suppose that ``cards'' is a pointer to a string
+containing a complete set of concatenated FITS header cards (such as
+produced by the CFITSIO function fits\_hdr2str). Then proceed as follows:
+
+\begin{small}
+\begin{terminalv}
+fitsfile *fptr;
+AstFitsChan *fitschan;
+AstFrameSet *wcsinfo;
+char *header;
+int nkeys, status;
+
+...
+
+/* Obtain all the cards in the header concatenated into a single dynamically
+   allocated null-terminated character string. Note, we do not exclude
+   any cards since we may later modify the WCS information within the
+   header and consequently want to write the entire header out again. */
+   if( fits_hdr2str( fptr, 0, NULL, 0, &header, &nkeys, &status ) )
+          printf(" Error getting header\n");
+   ...
+
+/* Header obtained succesfully... */
+   } else {
+
+/* Create a FitsChan and fill it with FITS header cards. */
+      fitschan = astFitsChan( NULL, NULL, "" );
+      astPutCards( fitschan, header );
+
+/* Free the memory holding the concatenated header cards. */
+      header = free( header );
+
+/* Read WCS information from the FitsChan. */
+      wcsinfo = astRead( fitschan );
+
+      ...
+
+\end{terminalv}
+\end{small}
+
+
+The result should be a pointer, ``wcsinfo'', to a \htmlref{FrameSet}{FrameSet} which
+contains the WCS information. This pointer can now be used to perform
+many useful tasks, some of which are illustrated in the following
+recipes.
+
+Some datasets which do not easily yield FITS header cards may require
+a different approach, possibly involving use of a \htmlref{Channel}{Channel} or \htmlref{XmlChan}{XmlChan}
+(\secref{ss:channels}) rather than a \htmlref{FitsChan}{FitsChan}. In the case of the
+Starlink NDF data format, for example, all the above may be replaced
+by a single call to the function
+\xref{ndfGtwcs}{sun33}{ndfGtwcs}---see \xref{SUN/33}{sun33}{}.  The
+whole process can probably be encapsulated in a similar way for
+most data systems, whether they use FITS header cards or not.
+
+For more details about reading WCS information from datasets, see
+\secref{ss:identifyingfitsencoding} and
+\secref{ss:readingforeignfits}. For a more general description of
+FitsChans and their use with FITS header cards, see
+\secref{ss:nativefits} and \secref{ss:foreignfits}. For more details
+about FrameSets, see \secref{ss:framesets} and \secref{ss:fshigher}.
+
+\subsection{\ldots Validate WCS Information}
+
+Once you have read WCS information from a dataset, as in
+\secref{ss:howtoreadwcs}, you may wish to check that you have been
+successful. The following will detect and classify the things that
+might possibly go wrong:
+
+\begin{small}
+\begin{terminalv}
+#include <string.h>
+
+...
+
+if ( !astOK ) {
+   <an error occurred (a message will have been issued)>
+} else if ( wcsinfo == AST__NULL ) {
+   <there was no WCS information present>
+} else if ( strcmp( astGetC( wcsinfo, "Class" ), "FrameSet" ) ) {
+   <something unexpected was read (i.e. not a FrameSet)>
+} else {
+   <WCS information was read OK>
+}
+\end{terminalv}
+\end{small}
+
+For more information about detecting errors in AST functions, see
+\secref{ss:errordetection}. For details of how to validate input data
+read by AST, see \secref{ss:validatinginput} and
+\secref{ss:readingforeignfits}.
+
+\subsection{\ldots Display AST Data}
+
+If you have a pointer to any AST \htmlref{Object}{Object}, you can display the data
+stored in that Object in textual form as follows:
+
+\begin{small}
+\begin{terminalv}
+astShow( wcsinfo );
+\end{terminalv}
+\end{small}
+
+Here, we have used a pointer to the \htmlref{FrameSet}{FrameSet} which we read earlier
+(\secref{ss:howtoreadwcs}).  The result is written to the program's
+standard output stream. This can be very useful during debugging.
+
+For more details about using \htmlref{astShow}{astShow}, see
+\secref{ss:displayingobjects}. For information about interpreting the
+output, also see \secref{ss:textualoutputformat}.
+
+\subsection{\label{ss:howtotransform}\ldots Convert Between Pixel and World Coordinates}
+
+You may use a pointer to a \htmlref{FrameSet}{FrameSet}, such as we read in
+\secref{ss:howtoreadwcs}, to transform a set of points between the
+pixel coordinates of an image and the associated world coordinates. If
+you are working in two dimensions, proceed as follows:
+
+\begin{small}
+\begin{terminalv}
+double xpixel[ N ], ypixel[ N ];
+double xworld[ N ], yworld[ N ];
+
+...
+
+astTran2( wcsinfo, N, xpixel, ypixel, 1, xworld, yworld );
+\end{terminalv}
+\end{small}
+
+Here, N is the number of points to be transformed, ``xpixel'' and
+``ypixel'' hold the pixel coordinates, and ``xworld'' and ``yworld''
+receive the returned world coordinates.\footnote{By pixel coordinates,
+we mean a coordinate system in which the first pixel in the image is
+centred on (1,1) and each pixel is a unit square.  Note that the world
+coordinates will not necessarily be celestial coordinates, but if they
+are, then they will be in radians.}  To transform in the opposite
+direction, interchange the two pairs of arrays (so that the world
+coordinates are given as input) and change the fifth argument of
+\htmlref{astTran2}{astTran2} to zero.
+
+To transform points in one dimension, use \htmlref{astTran1}{astTran1}. In any other
+number of dimensions (or if the number of dimensions is initially
+unknown), use \htmlref{astTranN}{astTranN} or \htmlref{astTranP}{astTranP}. These functions are described in
+\appref{ss:functiondescriptions}.
+
+For more information about transforming coordinates, see
+\secref{ss:transforming} and \secref{ss:framesetasmapping}. For
+details of how to handle missing coordinates, see
+\secref{ss:badcoordinates}.
+
+\subsection{\label{ss:howtotestforcelestial}\ldots Test if a WCS is a Celestial Coordinate System}
+
+The world coordinate system (WCS) currently associated with an image
+may often be a celestial coordinate system, but this need not
+necessarily be the case. For instance, instead of right ascension and
+declination, an image might have a WCS with axes representing
+wavelength and slit position, or maybe just plain old pixels.
+
+If you have obtained a WCS calibration for an image, as in
+\secref{ss:howtoreadwcs}, in the form of a pointer ``wcsinfo'' to a
+\htmlref{FrameSet}{FrameSet}, then you may determine if the current coordinate system is a
+celestial one or not, as follows:
+
+\begin{small}
+\begin{terminalv}
+AstFrame *frame;
+int issky;
+
+...
+
+/* Obtain a pointer to the current Frame and determine if it is a
+   SkyFrame. */
+frame = astGetFrame( wcsinfo, AST__CURRENT );
+issky = astIsASkyFrame( frame );
+frame = astAnnul( frame );
+\end{terminalv}
+\end{small}
+
+This will set ``issky'' to 1 if the WCS is a celestial coordinate
+system, and to zero otherwise.
+
+\subsection{\label{ss:howtotestforspectral}\ldots Test if a WCS is a Spectral Coordinate System}
+Testing for a spectral coordinate system is basically the same as testing
+for a celestial coordinate system (see the previous section). The one
+difference is that you use the
+astIsASpecFrame function
+in place of the
+astIsASkyFrame function.
+
+\subsection{\label{ss:howtoformatcoordinates}\ldots Format Coordinates for Display}
+
+Once you have converted pixel coordinates into world coordinates
+(\secref{ss:howtotransform}), you may want to format them as text
+before displaying them. Typically, this would convert from (say)
+radians into something more comprehensible. Using the \htmlref{FrameSet}{FrameSet} pointer
+``wcsinfo'' obtained in \secref{ss:howtoreadwcs} and a pair of world
+coordinates ``xw'' and ``yw'' (\emph{e.g.}\ see
+\secref{ss:howtotransform}), you could proceed as follows:
+
+\begin{small}
+\begin{terminalv}
+#include <stdio.h>
+const char *xtext, *ytext;
+double xw, yw;
+
+...
+
+xtext = astFormat( wcsinfo, 1, xw );
+ytext = astFormat( wcsinfo, 2, yw );
+
+(void) printf( "Position = %s, %s\n", xtext, ytext );
+\end{terminalv}
+\end{small}
+
+Here, the second argument to \htmlref{astFormat}{astFormat} is the axis number.
+
+With celestial coordinates, this will usually result in sexagesimal
+notation, such as ``12:34:56.7''. However, the same method may be
+applied to any type of coordinates and appropriate formatting will be
+employed.
+
+For more information about formatting coordinate values and how to
+control the style of formatting used, see
+\secref{ss:formattingaxisvalues} and
+\secref{ss:formattingskyaxisvalues}. If necessary, also see
+\secref{ss:normalising} for details of how to ``normalise'' a set of
+coordinates so that they lie within the standard range (\emph{e.g.}\ 0
+to 24 hours for right ascension and $\pm 90^\circ$ for
+declination).
+
+\subsection{\ldots Display Coordinates as they are Transformed}
+
+In addition to formatting coordinates as part of a program's output,
+you may also want to examine coordinate values while debugging your
+program. To save time, you can ``eavesdrop'' on the coordinate values
+being processed every time they are transformed. For example, when
+using the \htmlref{FrameSet}{FrameSet} pointer ``wcsinfo'' obtained in
+\secref{ss:howtoreadwcs} to transform coordinates
+(\secref{ss:howtotransform}), you could inspect the coordinate values
+as follows:
+
+\begin{small}
+\begin{terminalv}
+astSet( wcsinfo, "Report=1" );
+astTran2( wcsinfo, N, xpixel, ypixel, 1, xworld, yworld );
+\end{terminalv}
+\end{small}
+
+By setting the FrameSet's \htmlref{Report}{Report} attribute to 1, coordinate
+transformations are automatically displayed on the program's standard
+output stream, appropriately formatted, for example:
+
+\begin{terminalv}
+(42.1087, 20.2717) --> (2:06:03.0, 34:22:39)
+(43.0197, 21.1705) --> (2:08:20.6, 35:31:24)
+(43.9295, 22.0716) --> (2:10:38.1, 36:40:09)
+(44.8382, 22.9753) --> (2:12:55.6, 37:48:55)
+(45.7459, 23.8814) --> (2:15:13.1, 38:57:40)
+(46.6528, 24.7901) --> (2:17:30.6, 40:06:25)
+(47.5589, 25.7013) --> (2:19:48.1, 41:15:11)
+(48.4644, 26.6149) --> (2:22:05.6, 42:23:56)
+(49.3695, 27.5311) --> (2:24:23.1, 43:32:41)
+(50.2742, 28.4499) --> (2:26:40.6, 44:41:27)
+\end{terminalv}
+
+For a complete description of the Report attribute, see its entry in
+\appref{ss:attributedescriptions}.  For further details of how to set
+and enquire attribute values, see \secref{ss:settingattributes} and
+\secref{ss:gettingattributes}.
+
+\subsection{\ldots Read Coordinates Entered by a User}
+
+In addition to writing out coordinate values generated by your program
+(\secref{ss:howtoformatcoordinates}), you may also need to accept
+coordinates entered by a user, or perhaps read from a file. In this
+case, you will probably want to allow ``free-format'' input, so that
+the user has some flexibility in the format that can be used. You will
+probably also want to detect any typing errors.
+
+Let's assume that you want to read a number of lines of text, each
+containing the world coordinates of a single point, and to split each
+line into individual numerical coordinate values. Using the \htmlref{FrameSet}{FrameSet}
+pointer ``wcsinfo'' obtained earlier (\secref{ss:howtoreadwcs}), you
+could proceed as follows:
+
+\begin{small}
+\begin{terminalv}
+#include <stdio.h>
+char *t;
+char text[ MAXCHARS + 2 ];
+double coord[ 10 ];
+int iaxis, n, naxes;
+
+...
+
+/* Obtain the number of coordinate axes (if not already known). */
+naxes = astGetI( wcsinfo, "Naxes" );
+
+/* Loop to read each line of input text, in this case from the
+   standard input stream (your programming environment will probably
+   provide a better way of reading text than this). Set the pointer
+   "t" to the start of each line read. */
+while ( t = fgets( text, MAXCHARS + 2, stdin ) ) {
+
+/* Attempt to read a coordinate for each axis. */
+   for ( iaxis = 1; iaxis <= naxes; iaxis++ ) {
+      n = astUnformat( wcsinfo, iaxis, t, &coord[ iaxis - 1 ] );
+
+/* If nothing was read and this is not the first axis or the
+   end-of-string, try stepping over a separator and reading again. */
+   if ( !n && ( iaxis > 1 ) && *t )
+      n = astUnformat( wcsinfo, iaxis, ++t, &coord[ iaxis - 1 ] );
+
+/* Quit if nothing was read, otherwise move on to the next coordinate. */
+      if ( !n ) break;
+      t += n;
+   }
+
+/* Test for the possible errors that may occur... */
+
+/* Error detected by AST (a message will have been issued). */
+   if ( !astOK ) {
+      break;
+
+/* Error in input data at character t[n]. */
+   } else if ( *t || !n ) {
+      <handle the error, or report your own message here>
+      break;
+
+   } else {
+      <coordinates were read OK>
+   }
+}
+\end{terminalv}
+\end{small}
+
+This algorithm has the advantage of accepting free-format input in
+whatever style is appropriate for the world coordinates in use (under
+the control of the FrameSet whose pointer you provide). For example,
+wavelength values might be read as floating point numbers
+(\emph{e.g.}\ ``1.047'' or ``4787''), whereas celestial positions
+could be given in sexagesimal format (\emph{e.g.}\ ``12:34:56'' or
+``12~34.5'') and would be converted into radians. Individual
+coordinate values may be separated by white space and/or any
+non-ambiguous separator character, such as a comma.
+
+For more information on reading coordinate values using the
+\htmlref{astUnformat}{astUnformat} function, see \secref{ss:unformattingaxisvalues}. For
+details of how sexagesimal formats are handled, and the forms of input
+that may be used for celestial coordinates, see
+\secref{ss:unformattingskyaxisvalues}.
+
+\subsection{\label{ss:howtocreatenewwcs}\ldots Create a New WCS Calibration}
+
+This section describes how to add a WCS calibration to a data set which you
+are creating from scratch, rather than modifying an existing data set.
+
+In most common cases, the simplest way to create a new WCS calibration
+from scratch is probably to create a set of strings describing the
+required calibration in terms of the keywords used by the FITS WCS
+standard, and then convert these strings into an AST \htmlref{FrameSet}{FrameSet} describing
+the calibration. This FrameSet can then be used for many other purposes, or
+simply stored in the data set.
+
+The full FITS-WCS standard is quite involved, currently running to four
+separate papers, but the basic kernel is quite simple, involving the
+following keywords (all of which end with an integer axis index,
+indicated below by $<i>$):
+
+\begin{description}
+\item[CRPIX<i>]\mbox{}\\
+hold the pixel coordinates at a reference point
+\item[CRVAL<i>]\mbox{}\\
+hold the corresponding WCS coordinates at the reference point
+\item[CTYPE<i>]\mbox{}\\
+name the quantity represented by the WCS axes, together with the
+projection algorithm used to convert the scaled and rotated pixel coordinates
+to WCS coordinates.
+\item[CD<i>\_<j>]\mbox{}\\
+a set of keywords which specify the elements of a matrix. This matrix scales
+pixel offsets from the reference point into the offsets required as input
+by the projection algorithm specified by the CTYPE keywords. This matrix
+specifies the scale and rotation of the image. If there is no rotation
+the off-diagonal elements of the matrix (\emph{e.g.} CD1\_2 and
+CD2\_1) can be omitted.
+\end{description}
+
+As an example consider the common case of a simple 2D image of the sky in
+which north is parallel to the second pixel axis and east parallel to the
+(negative) first pixel axis. The image scale is 1.2 arc-seconds per pixel
+on both axes, and the image is presumed to have been obtained with a
+tangent plane projection. Furthermore, it is known that pixel coordinates
+(100.5,98.4) correspond to an RA of 11:00:10 and a Dec. of  -23:26:02.
+A suitable set of FITS-WCS header cards could be:
+
+\begin{small}
+\begin{terminalv}
+CTYPE1  = 'RA---TAN'       / Axis 1 represents RA with a tan projection
+CTYPE2  = 'DEC--TAN'       / Axis 2 represents Dec with a tan projection
+CRPIX1  = 100.5            / Pixel coordinates of reference point
+CRPIX2  = 98.4             / Pixel coordinates of reference point
+CRVAL1  = 165.04167        / Degrees equivalent of "11:00:10" hours
+CRVAL2  = -23.433889       / Decimal equivalent of "-23:26:02" degrees
+CD1_1   = -0.0003333333    / Decimal degrees equivalent of -1.2 arc-seconds
+CD2_2   = 0.0003333333     / Decimal degrees equivalent of 1.2 arc-seconds
+\end{terminalv}
+\end{small}
+
+Notes:
+\begin{itemize}
+\item a FITS header card begins with the keyword name starting at column 1,
+has an equals sign in column 9, and the keyword value in columns 11 to 80.
+\item string values must be enclosed in single quotes.
+\item celestial longitude and latitude must both be specified in decimal degrees.
+\item the CD1\_1 value is negative to indicate that RA increases as the
+first pixel axis decreases.
+\item the (RA,Dec) coordinates will be taken as ICRS coordinates. For FK5
+you should add:
+
+\begin{small}
+\begin{terminalv}
+RADESYS = 'FK5'
+EQUINOX = 2005.6
+\end{terminalv}
+\end{small}
+
+The EQUINOX value defaults to J2000.0 if omitted. FK4 can also be used in
+place of FK5, in which case EQUINOX defaults to B1950.0.
+
+\end{itemize}
+
+Once you have created these FITS-WCS header card strings, you should
+store them in a \htmlref{FitsChan}{FitsChan} and then read the corresponding FrameSet from the
+FitsChan. How to do this is described in \secref{ss:howtoreadwcs}.
+
+Having created the WCS calibration, you may want to store it in a data
+file. How to do this is described in \secref{ss:howtowritewcs}).\footnote{If
+you are writing the WCS calibration to a FITS file you obviously
+have the choice of storing the FITS-WCS cards directly.}
+
+If the required WCS calibration cannot be described as a set of FITS-WCS
+headers, then a different approach is necessary. In this case, you should
+first create a \htmlref{Frame}{Frame} describing pixel coordinates, and store this Frame
+in a new FrameSet. You should then create a new Frame describing the
+world coordinate system. This Frame may be a specific subclass of Frame such
+as a \htmlref{SkyFrame}{SkyFrame} for celestial coordinates, a \htmlref{SpecFrame}{SpecFrame} for spectral
+coordinates, a Timeframe for time coordinates, or a \htmlref{CmpFrame}{CmpFrame} for a combination
+of different coordinates.
+You also need to create a suitable \htmlref{Mapping}{Mapping} which transforms pixel
+coordinates into world coordinates. AST provides many different types of
+Mappings, all of which can be combined together in arbitrary fashions to
+create more complicated Mappings. The WCS Frame should then be added into
+the FrameSet, using the Mapping to connect the WCS Frame with the pixel
+Frame.
+
+\subsection{\label{ss:howtomodifywcs}\ldots Modify a WCS Calibration}
+
+The usual reason for wishing to modify the WCS calibration associated
+with a dataset is that the data have been geometrically transformed in
+some way (here, we will assume a 2-dimensional image dataset). This
+causes the image features (stars, galaxies, \emph{etc.}) to move with
+respect to the grid of pixels which they occupy, so that any
+coordinate systems previously associated with the image become
+invalid.
+
+To correct for this, it is necessary to set up a \htmlref{Mapping}{Mapping} which
+expresses the positions of image features in the new data grid in
+terms of their positions in the old grid. In both cases, the grid
+coordinates we use will have the first pixel centred at (1,1) with
+each pixel being a unit square.
+
+AST allows you to correct for any type of geometrical transformation
+in this way, so long as a suitable Mapping to describe it can be
+constructed. For purposes of illustration, we will assume here that
+the new image coordinates ``xnew'' and ``ynew'' can be expressed in
+terms of the old coordinates ``xold'' and ``yold'' as follows:
+
+\begin{small}
+\begin{terminalv}
+double xnew, xold, ynew, yold;
+double m[ 4 ], z[ 2 ];
+
+...
+
+xnew = xold * m[ 0 ] + yold * m[ 1 ] + z[ 0 ];
+ynew = xold * m[ 2 ] + yold * m[ 3 ] + z[ 1 ];
+\end{terminalv}
+\end{small}
+
+where ``m'' is a 2$\times$2 transformation matrix and ``z'' represents
+a shift of origin. This is therefore a general linear coordinate
+transformation which can represent displacement, rotation,
+magnification and shear.
+
+In AST, it can be represented by concatenating two Mappings. The first
+is a \htmlref{MatrixMap}{MatrixMap}, which implements the matrix multiplication. The second
+is a \htmlref{WinMap}{WinMap}, which linearly transforms one coordinate window on to
+another, but will be used here simply to implement the shift of
+origin (alternatively, a \htmlref{ShiftMap}{ShiftMap} could have been used in place of a
+WinMap). These Mappings may be constructed and concatenated as follows:
+
+\begin{small}
+\begin{terminalv}
+AstCmpMap *newmap;
+AstMatrixMap *matrixmap;
+AstWinMap *winmap;
+
+...
+
+/* The MatrixMap may be constructed directly from the matrix "m". */
+matrixmap = astMatrixMap( 2, 2, 0, m, "" );
+
+/* For the WinMap, we set up the coordinates of the corners of a unit
+   square (window) and then the same square shifted by the required
+   amount. */
+{
+   double ina[] = { 0.0, 0.0 };
+   double inb[] = { 1.0, 1.0 };
+   double outa[] = {       z[ 0 ],       z[ 1 ] };
+   double outb[] = { 1.0 + z[ 0 ], 1.0 + z[ 1 ] };
+
+/* The WinMap will then implement this shift. */
+   winmap = astWinMap( 2, ina, inb, outa, outb, "" );
+}
+
+/* Join the two Mappings together, so that they are applied one after
+   the other. */
+newmap = astCmpMap( matrixmap, winmap, 1, "" );
+\end{terminalv}
+\end{small}
+
+You might, of course, create any other form of Mapping depending on
+the type of geometrical transformation involved. For an overview of
+the Mappings provided by AST, see \secref{ss:mappingselection}, and
+for a description of the capabilities of each class of Mapping, see
+its entry in \appref{ss:classdescriptions}. For an overview of how
+individual Mappings may be combined, see \secref{ss:cmpmapoverview}
+(\secref{ss:cmpmaps} gives more details).
+
+Assuming you have obtained a WCS calibration for your original image
+in the form of a pointer to a \htmlref{FrameSet}{FrameSet}, ``wcsinfo1''
+(\secref{ss:howtoreadwcs}), the Mapping created above may be used to
+produce a calibration for the new image as follows:
+
+\begin{small}
+\begin{terminalv}
+AstFrameSet *wcsinfo1, *wcsinfo2;
+
+...
+
+/* If necessary, make a copy of the WCS calibration, since we are
+   about to alter it. */
+wcsinfo2 = astCopy( wcsinfo1 );
+
+/* Re-map the base Frame so that it refers to the new data grid
+   instead of the old one. */
+astRemapFrame( wcsinfo2, AST__BASE, newmap );
+\end{terminalv}
+\end{small}
+
+This will produce a pointer, ``wcsinfo2'', to a new FrameSet in which
+all the coordinate systems associated with your original image are
+modified so that they are correctly registered with the new image
+instead.
+
+For more information about re-mapping the Frames within a FrameSet,
+see \secref{ss:remapframe}. Also see \secref{ss:wcsprocessingexample}
+for a similar example to the above, applicable to the case of reducing
+the size of an image by binning.
+
+\subsection{\label{ss:howtowritewcs}\ldots Write a Modified WCS Calibration to a Dataset}
+
+If you have modified the WCS calibration associated with a dataset,
+such as in the example above (\secref{ss:howtomodifywcs}), then you
+will need to write the modified version out along with any new data.
+
+In the same way as when reading a WCS calibration
+(\secref{ss:howtoreadwcs}), how you do this will depend on your data
+system, but we will assume that you wish to generate a set of FITS
+header cards that can be stored with the data. You should usually make
+preparations for doing this when you first read the WCS calibration
+from your input dataset by modifying the example given in
+\secref{ss:howtoreadwcs} as follows:
+
+\begin{small}
+\begin{terminalv}
+AstFitsChan *fitschan1;
+AstFrameSet *wcsinfo1;
+const char *encode;
+
+...
+
+/* Create an input FitsChan and fill it with FITS header cards. Note,
+   if you have all the header cards in a single string, use astPutCards in
+   place of astPutFits. */
+fitschan1 = astFitsChan( NULL, NULL, "" );
+for ( icard = 0; icard < ncard; icard++ ) astPutFits( fitschan1, cards[ icard ], 0 );
+
+/* Note which encoding has been used for the WCS information. */
+encode = astGetC( fitschan1, "Encoding" );
+
+/* Rewind the input FitsChan and read the WCS information from it. */
+astClear( fitschan1, "Card" );
+wcsinfo1 = astRead( fitschan1 );
+\end{terminalv}
+\end{small}
+
+Note how we have added an enquiry to determine how the WCS information
+is encoded in the input FITS cards, storing a pointer to the resulting
+string in the ``encode'' variable. This must be done \textbf{before}
+actually reading the WCS calibration.
+
+\emph{(\textbf{N.B.}\ If you will be making extensive use of astGetC in
+your program, then you should allocate a buffer and make a copy of
+this string, because the pointer returned by astGetC will only remain
+valid for 50 invocations of the function, and you will need to use the
+\htmlref{Encoding}{Encoding} value again later on.)}
+
+Once you have produced a modified WCS calibration for the output
+dataset (\emph{e.g.}\ \secref{ss:howtomodifywcs}), in the form of a
+\htmlref{FrameSet}{FrameSet} identified by the pointer ``wcsinfo2'', you can produce a new
+\htmlref{FitsChan}{FitsChan} containing the output FITS header cards as follows:
+
+\small
+\begin{terminalv}
+AstFitsChan *fitschan2;
+AstFrameSet *wcsinfo2;
+
+...
+
+/* Make a copy of the input FitsChan, AFTER the WCS information has
+   been read from it. This will propagate all the input FITS header
+   cards, apart from those describing the input WCS calibration. */
+fitschan2 = astCopy( fitschan1 );
+
+/* If necessary, make modifications to the cards in "fitschan2"
+   (e.g. you might need to change NAXIS1, NAXIS2, etc., to account for
+   a change in image size). You probably only need to do this if your
+   data system does not provide these facilities itself. */
+<details not shown - see below>
+
+/* Alternatively, if your data system handles the propagation of FITS
+   header cards to the output dataset for you, then simply create an
+   empty FitsChan to contain the output WCS information alone.
+fitschan2 = astFitsChan( NULL, NULL, "" );
+*/
+
+/* Rewind the new FitsChan (if necessary) and attempt to write the
+   output WCS information to it using the same encoding method as the
+   input dataset. */
+astSet( fitschan2, "Card=1, Encoding=%s", encode );
+if ( !astWrite( fitschan2, wcsinfo2 ) ) {
+
+/* If this didn't work (the WCS FrameSet has become too complex), then
+   use the native AST encoding instead. */
+   astSet( fitschan2, "Encoding=NATIVE" );
+   (void) astWrite( fitschan2, wcsinfo2 );
+}
+\end{terminalv}
+\normalsize
+
+For details of how to modify the contents of the output FitsChan in
+other ways, such as by adding, over-writing or deleting header cards,
+see \secref{ss:addressingfitscards}, \secref{ss:addingmulticards}, \secref{ss:addingfitscards} and
+\secref{ss:findingandchangingfits}.
+
+Once you have assembled the output FITS cards, you may retrieve them
+from the FitsChan that contains them as follows:
+
+\small
+\begin{terminalv}
+#include <stdio.h>
+char card[ 81 ];
+
+...
+
+astClear( fitschan2, "Card" );
+while ( astFindFits( fitschan2, "%f", card, 1 ) ) (void) printf( "%s\n", card );
+\end{terminalv}
+\normalsize
+
+Here, we have simply written each card to the standard output stream,
+but you would obviously replace this with a function invocation to
+store the cards in your output dataset.
+
+For data systems that do not use FITS header cards, a different
+approach may be needed, possibly involving use of a \htmlref{Channel}{Channel} or \htmlref{XmlChan}{XmlChan}
+(\secref{ss:channels}) rather than a FitsChan.  In the case of the
+Starlink NDF data format, for example, all of the above may be
+replaced by a single call to the function
+\xref{ndfPtwcs}{sun33}{ndfPtwcs}---see \xref{SUN/33}{sun33}{}. The
+whole process can probably be encapsulated in a similar way for most
+data systems, whether they use FITS header cards or not.
+
+For an overview of how to propagate WCS information through data
+processing steps, see \secref{ss:propagatingwcsinformation}.  For more
+information about writing WCS information to FitsChans, see
+\secref{ss:writingnativefits} and \secref{ss:writingforeignfits}.  For
+information about the options for encoding WCS information in FITS
+header cards, see \secref{ss:nativeencoding},
+\secref{ss:foreignencodings}, and the description of the Encoding
+attribute in \appref{ss:attributedescriptions}.  For a complete
+understanding of FitsChans and their use with FITS header cards, you
+should read \secref{ss:nativefits} and \secref{ss:foreignfits}.
+
+\subsection{\label{ss:howtoplotgrid}\ldots Display a Graphical Coordinate Grid}
+
+   A common requirement when displaying image data is to plot an
+   associated coordinate grid (\emph{e.g.}\ Figure~\ref{fig:overgrid})
+   over the displayed image.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun211_figures/overgrid_bw}
+   \caption[An example of a displayed image with a coordinate grid
+   plotted over it.]{An example of a displayed image with a coordinate grid
+   plotted over it.}
+   \label{fig:overgrid}
+   \end{center}
+   \end{figure}
+
+The use of AST in such circumstances is independent of the underlying
+graphics system, so starting up the graphics system, setting up a
+coordinate system, displaying the image, and closing down afterwards
+can all be done using the graphics functions you would normally use.
+
+However, displaying an image at a precise location can be a little
+fiddly with some graphics systems, and obviously the grid drawn by AST
+will not be accurately registered with the image unless this is done
+correctly. In the following template, we therefore illustrate both
+steps, basing the image display on the C interface to the PGPLOT
+graphics package.\footnote{An interface is provided with AST that
+allows it to use PGPLOT (\xref{SUN/15}{sun15}{}) for its graphics,
+although interfaces to other graphics systems may also be written.}
+Plotting a coordinate grid with AST then becomes a relatively minor
+part of what is almost a complete graphics program.
+
+Once again, we assume that a pointer, ``wcsinfo'', to a suitable
+\htmlref{FrameSet}{FrameSet} associated with the image has already been obtained
+(\secref{ss:howtoreadwcs}).
+
+\small
+\begin{terminalv}
+#include "cpgplot.h"
+AstPlot *plot;
+const float *data;
+float hi, lo, scale, x1, x2, xleft, xright, xscale;
+float y1, y2, ybottom, yscale, ytop;
+int nx, ny;
+
+...
+
+/* Access the image data, which we assume has dimension sizes "nx" and
+   "ny", and will be accessed via the "data" pointer.  Also derive
+   limits for scaling it, which we assign to the variables "hi" and
+   "lo". */
+<this stage depends on your data system, so is not shown>
+
+/* Open PGPLOT using the device given by environment variable
+   PGPLOT_DEV and check for success. */
+if( cpgbeg( 0, " ", 1, 1 ) == 1 ) {
+
+/* Clear the screen and ensure equal scales on both axes. */
+   cpgpage();
+   cpgwnad( 0.0f, 1.0f, 0.0f, 1.0f );
+
+/* Obtain the extent of the plotting area (not strictly necessary for
+   PGPLOT, but possibly for other graphics systems). From this, derive
+   the display scale in graphics units per pixel so that the image
+   will fit within the display area. */
+   cpgqwin( &x1, &x2, &y1, &y2 );
+   xscale = ( x2 - x1 ) / nx;
+   yscale = ( y2 - y1 ) / ny;
+   scale = ( xscale < yscale ) ? xscale : yscale;
+
+/* Calculate the extent of the area in graphics units that the image
+   will occupy, so as to centre it within the display area. */
+   xleft   = 0.5f * ( x1 + x2 - nx * scale );
+   xright  = 0.5f * ( x1 + x2 + nx * scale );
+   ybottom = 0.5f * ( y1 + y2 - ny * scale );
+   ytop    = 0.5f * ( y1 + y2 + ny * scale );
+
+/* Set up a PGPLOT coordinate transformation matrix and display the
+   image data as a grey scale map (these details are specific to
+   PGPLOT). */
+   {
+      float tr[] = { xleft - 0.5f * scale, scale, 0.0f,
+                     ybottom - 0.5f * scale, 0.0f, scale };
+      cpggray( data, nx, ny, 1, nx, 1, ny, hi, lo, tr );
+   }
+
+/* BEGINNING OF AST BIT */
+/* ==================== */
+/* Store the locations of the bottom left and top right corners of the
+   region used to display the image, in graphics coordinates. */
+   {
+      float gbox[] = { xleft, ybottom, xright, ytop };
+
+/* Similarly, store the locations of the image's bottom left and top
+   right corners, in pixel coordinates -- with the first pixel centred
+   at (1,1). */
+      double pbox[] = { 0.5, 0.5, nx + 0.5, ny + 0.5 };
+
+/* Create a Plot, based on the FrameSet associated with the
+   image. This attaches the Plot to the graphics surface so that it
+   matches the displayed image. Specify that a complete set of grid
+   lines should be drawn (rather than just coordinate axes). */
+      plot = astPlot( wcsinfo, gbox, pbox, "Grid=1" );
+   }
+
+/* Optionally, we can now set other Plot attributes to control the
+   appearance of the grid. The values assigned here use the
+   colour/font indices defined by the underlying graphics system. */
+   astSet( plot, "Colour(grid)=2, Font(textlab)=3" );
+
+/* Use the Plot to draw the coordinate grid. */
+   astGrid( plot );
+
+   <maybe some more AST graphics here>
+
+/* Annul the Plot when finished (or use the astBegin/astEnd technique
+   shown earlier). */
+   plot = astAnnul( plot );
+
+/* END OF AST BIT */
+/* ============== */
+
+/* Close down the graphics system. */
+   cpgend();
+}
+\end{terminalv}
+\normalsize
+
+Note that once you have set up a \htmlref{Plot}{Plot} which is aligned with a
+displayed image, you may also use it to generate further graphical
+output of your own, specified in the image's world coordinate system
+(such as markers to represent astronomical objects, annotation,
+\emph{etc.}). There is also a range of Plot attributes which gives
+control over most aspects of the output's appearance.  For details of
+the facilities available, see \secref{ss:plots} and the description of
+the Plot class in \appref{ss:classdescriptions}.
+
+For details of how to build a graphics program which uses PGPLOT, see
+\secref{ss:howtobuild} and the description of the \htmlref{ast\_link}{ast\_link} command in
+\appref{ss:commanddescriptions}.
+
+\subsection{\label{ss:howtoswitchgrid}\ldots Switch to Plot a Different Celestial Coordinate Grid}
+
+Once you have set up a \htmlref{Plot}{Plot} to draw a coordinate grid
+(\secref{ss:howtoplotgrid}), it is a simple matter to change things so
+that the grid represents a different celestial coordinate system. For
+example, after creating the Plot with \htmlref{astPlot}{astPlot}, you could use:
+
+\small
+\begin{terminalv}
+astSet( plot, "System=Galactic" );
+\end{terminalv}
+\normalsize
+or:
+\small
+\begin{terminalv}
+astSet( plot, "System=FK5, Equinox=J2010" );
+\end{terminalv}
+\normalsize
+
+and any axes and/or grid drawn subsequently would represent the new
+celestial coordinate system you specified.  Note, however, that this
+will only work if the original grid represented celestial coordinates
+of some kind (see \secref{ss:howtotestforcelestial} for how to
+determine if this is the case\footnote{Note that the methods applied
+to a \htmlref{FrameSet}{FrameSet} may be used equally well with a Plot.}). If it did not,
+you will get an error message.
+
+For more information about the celestial coordinate systems available,
+see the descriptions of the \htmlref{System}{System}, \htmlref{Equinox}{Equinox} and \htmlref{Epoch}{Epoch} attributes in
+\appref{ss:attributedescriptions}.
+
+\subsection{\ldots Give a User Control Over the Appearance of a Plot}
+
+The idea of using a \htmlref{Plot}{Plot}'s attributes to control the appearance of the
+graphical output it produces (\secref{ss:howtoplotgrid} and
+\secref{ss:howtoswitchgrid}) can easily be extended to allow the user
+of a program complete control over such matters.
+
+For instance, if the file ``plot.config'' contains a series of
+plotting options in the form of Plot attribute assignments (see below
+for an example), then we could create a Plot and implement these
+assignments before producing the graphical output as follows:
+
+\small
+\begin{terminalv}
+#include <stdio.h>
+#define MAXCHARS 120
+FILE *stream;
+char line[ MAXCHARS + 2 ];
+int base;
+
+...
+
+/* Create a Plot and define the default appearance of the graphical
+   output it will produce. */
+plot = astPlot( wcsinfo, gbox, pbox,
+                "Grid=1, Colour(grid)=2, Font(textlab)=3" );
+
+/* Obtain the value of any Plot attributes we want to preserve. */
+base = astGetI( plot, "Base" );
+
+/* Open the plot configuration file, if it exists. Read each line of
+   text and use it to set new Plot attribute values. Close the file
+   when done. */
+if ( stream = fopen( "plot.config", "r" ) ) {
+   while ( fgets( line, MAXCHARS + 2, stream ) ) astSet( plot, "%s", line );
+   close( stream );
+}
+
+/* Restore any attribute values we are preserving. */
+astSetI( plot, "Base", base );
+
+/* Produce the graphical output (e.g.). */
+astGrid( plot );
+\end{terminalv}
+\normalsize
+
+Notice that we take care that the Plot's \htmlref{Base}{Base} attribute is preserved
+so that the user cannot change it. This is because graphical output
+will not be produced successfully if the base \htmlref{Frame}{Frame} does not describe
+the plotting surface to which we attached the Plot when we created it.
+
+The arrangement shown above allows the contents of the ``plot.config''
+file to control most aspects of the graphical output produced
+(including the coordinate system used; the colour, line style,
+thickness and font used for each component; the positioning of axes
+and tick marks; the precision, format and positioning of labels;
+\emph{etc.}) \emph{via} assignments of the form:
+
+\small
+\begin{terminalv}
+System=Galactic, Equinox = 2001
+Border = 1, Colour( border ) = 1
+Colour( grid ) = 2
+DrawAxes = 1
+Colour( axes ) = 3
+Digits = 8
+Labelling = Interior
+\end{terminalv}
+\normalsize
+
+For a more sophisticated interface, you could obviously perform
+pre-processing on this input---for example, to translate words like
+``red'', ``green'' and ``blue'' into colour indices, to permit
+comments and blank lines, \emph{etc.}
+
+For a full list of the attributes that may be used to control the
+appearance of graphical output, see the description of the Plot class
+in \appref{ss:classdescriptions}. For a complete description of each
+individual attribute (\emph{e.g.}\ those above), see the attribute's
+entry in \appref{ss:attributedescriptions}.
+
+\cleardoublepage
+\section{\label{ss:primer}An AST Object Primer}
+
+The AST library deals throughout with entities called Objects and a
+basic understanding of how to handle these is needed before you can
+use the library effectively.  If you are already familiar with an
+object-oriented language, such as C$++$, few of the concepts should
+seem new to you.  Be aware, however, that AST is designed to be used
+\emph{via} fairly conventional C and Fortran interfaces, so some
+things have to be done a little differently.
+
+If you are not already familiar with object-oriented programming, then
+don't worry---we will not emphasise this aspect more than is necessary
+and will not assume any background knowledge.  Instead, this section
+concentrates on presenting all the fundamental information you will
+need, explaining how AST Objects behave and how to manipulate them
+from conventional C programs.
+
+If you like to read documents from cover to cover, then you can
+consider this section as an introduction to the programming techniques
+used in the rest of the document. Otherwise, you may prefer to skim
+through it on a first reading and return to it later as reference
+material.
+
+\subsection{AST Objects}
+
+An AST \htmlref{Object}{Object} is an entity which is used to store information and
+Objects come in various kinds, called \emph{classes}, according to the
+sort of information they hold. Throughout this section, we will make
+use of a simple Object belonging to the ``\htmlref{ZoomMap}{ZoomMap}'' class to
+illustrate many of the basic concepts.
+
+A ZoomMap is an Object that contains a recipe for converting
+coordinates between two hypothetical coordinate systems.  It does this
+by multiplying all the coordinate values by a constant called the
+\emph{\htmlref{Zoom}{Zoom} factor}.  A ZoomMap is a very simple Object which exists
+mainly for use in examples. It allows us to illustrate the ways in
+which Objects are manipulated and to introduce the concept of a
+\htmlref{Mapping}{Mapping}---a recipe for converting coordinates---which is fundamental
+to the way the AST library works.
+
+\subsection{\label{ss:objectcreation}Object Creation and Pointers}
+
+Let us first consider how to create a \htmlref{ZoomMap}{ZoomMap}. This is done very
+simply as follows:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstZoomMap *zoommap;
+
+...
+
+zoommap = astZoomMap( 2, 5.0, "" )
+\end{terminalv}
+\normalsize
+
+The first step is to include the header file ``ast.h'' which declares
+the interface to the AST library.  We then declare a pointer of type
+AstZoomMap$*$ to receive the result and invoke the function \htmlref{astZoomMap}{astZoomMap}
+to create the ZoomMap. The pattern is the same for all other classes
+of AST \htmlref{Object}{Object}---you simply prefix ``ast'' to the class name to obtain
+the function that creates the Object and prefix ``Ast'' to obtain the
+type of the returned pointer.
+
+These functions are called \emph{constructor functions}, or simply
+\emph{constructors} (you can find an individual description of all AST
+functions in \appref{ss:functiondescriptions}) and the arguments
+passed to the constructor are used to initialise the new Object. In
+this case, we specify 2 as the number of coordinates (\emph{i.e.}\ we
+are going to work in a 2-dimensional
+space) and 5.0 as the \htmlref{Zoom}{Zoom} factor to be applied. Note that this is a C
+double value. We will return to the final argument, an empty string,
+shortly (\secref{ss:attributeinitialisation}).
+
+The value returned by the constructor is termed an \emph{Object pointer}
+or, in this case, a \emph{ZoomMap pointer} and is used to refer to the
+Object.  You perform all subsequent operations on the Object by
+passing this pointer to other AST functions.
+
+\subsection{\label{ss:objecthierarchy}The Object Hierarchy}
+
+Now that we have created our first \htmlref{ZoomMap}{ZoomMap}, let us examine how it
+relates to other kinds of \htmlref{Object}{Object} before investigating what we can do
+with it.
+
+We have so far indicated that a ZoomMap is a kind of Object and have
+also mentioned that it is a kind of \htmlref{Mapping}{Mapping} as well. These statements
+can be represented very simply using the following hierarchy:
+
+\small
+\begin{terminalv}
+Object
+   Mapping
+      ZoomMap
+\end{terminalv}
+\normalsize
+
+which is a way of stating that a ZoomMap is a special class of
+Mapping, while a Mapping, in turn, is a special class of Object.  This
+is exactly like saying that an Oak is a special form of Tree, while a
+Tree, in turn, is a special form of Plant. This may seem almost
+trivial, but before you turn to read something less dull, be assured
+that it is a very important idea to keep in mind in what follows.
+
+If we look at some of the other Objects used by the AST library, we
+can see how these are all related in a similar way (don't worry about
+what they do at this stage):
+\label{ss:mappinghierarchy}
+
+\small
+\begin{terminalv}
+Object
+   Mapping
+      Frame
+         FrameSet
+            Plot
+      UnitMap
+      ZoomMap
+   Channel
+      FitsChan
+      XmlChan
+\end{terminalv}
+\normalsize
+
+Notice that there are several different types of Mapping available
+(\emph{i.e.}\ there are classes of Object indented beneath the
+``Mapping'' heading) and, in addition, other types of Object which are
+not Mappings---Channels for instance (which are at the same
+hierarchical level as Mappings).
+
+The most specialised Object we have shown here is the \htmlref{Plot}{Plot} (which we
+will not discuss in detail until \secref{ss:plots}). As you can see, a
+Plot is a \htmlref{FrameSet}{FrameSet}\ldots\ and a \htmlref{Frame}{Frame}\ldots\ and a Mapping\ldots\ and,
+like everything else, ultimately an Object.
+
+What this means is that you can use a Plot not only for its own
+specialised behaviour, but also whenever any of these other
+less-specialised classes of Object is called for. The general rule is
+that an Object of a particular class may substitute for any of the
+classes appearing above it in this hierarchy. The Object is then said
+to \emph{inherit} the behaviour of these higher classes. We can
+therefore use our ZoomMap whenever a ZoomMap, a Mapping or an Object
+is called for.
+
+Sometimes, this can lead to some spectacular short-cuts by avoiding
+the need to break large Objects down in order to access their
+components. With some practice and a little lateral thinking you
+should soon be able to spot opportunities for this.
+
+You can find the full \emph{class hierarchy}, as this is called, for
+the AST library in \appref{ss:classhierarchy} and you may need to
+refer to it occasionally until you are familiar with the classes you
+need to use.
+
+\subsection{\label{ss:displayingobjects}Displaying Objects}
+
+Let us now return to the \htmlref{ZoomMap}{ZoomMap} that we created earlier
+(\secref{ss:objectcreation}) and examine what it's made of.
+There is a function for doing this, called \htmlref{astShow}{astShow}, which is provided
+mainly for looking at Objects while you are debugging programs.
+
+If you consult the description of astShow in
+\appref{ss:functiondescriptions}, you will find that it takes a
+pointer to an \htmlref{Object}{Object} (of type AstObject$*$) as its argument. Although
+we have only a ZoomMap pointer available, this is not a problem. If
+you refer to the brief class hierarchy described above
+(\secref{ss:mappinghierarchy}), you will see that a ZoomMap is an
+Object, albeit a specialised one, so it inherits the properties of all
+Objects and can be substituted wherever an Object is required.  We can
+therefore pass our ZoomMap pointer directly to astShow, as follows:
+
+\small
+\begin{terminalv}
+astShow( zoommap );
+\end{terminalv}
+\normalsize
+
+The output from this will appear on the standard output stream and
+should look like the following:
+
+\small
+\begin{terminalv}
+Begin ZoomMap
+   Nin = 2
+IsA Mapping
+   Zoom = 5
+End ZoomMap
+\end{terminalv}
+\normalsize
+
+Here, the ``Begin'' and ``End'' lines mark the beginning and end of
+the ZoomMap, while the values 2 and 5 are simply the values we
+supplied to initialise it (\secref{ss:objectcreation}). These have
+been given simple names to make them easy to refer to.
+
+The line in the middle which says ``IsA~\htmlref{Mapping}{Mapping}'' is a dividing line
+between the two values. It indicates that the ``\htmlref{Nin}{Nin}'' value is a
+property shared by all Mappings, so the ZoomMap has inherited this
+from its \emph{parent class} (Mapping). The ``\htmlref{Zoom}{Zoom}'' value, however,
+is specific to a ZoomMap and isn't shared by other kinds of Mappings.
+
+\subsection{\label{ss:gettingattributes}Getting Attribute Values}
+
+We saw above (\secref{ss:displayingobjects}) how to display the
+internal values of an \htmlref{Object}{Object}, but what about accessing these values
+from a program?  Not all internal Object values are accessible in this
+way, but many are. Those that are, are called \emph{attributes}. A
+description of all the attributes used by the AST library can be found
+in \appref{ss:attributedescriptions}.
+
+Attributes come in several data types (character string, integer,
+boolean and floating point) and there is a standard way of obtaining
+their values. As an example, consider obtaining the value of the \htmlref{Nin}{Nin}
+attribute for the \htmlref{ZoomMap}{ZoomMap} created earlier. This could be done as
+follows:
+
+\small
+\begin{terminalv}
+int nin;
+
+...
+
+nin = astGetI( zoommap, "Nin" );
+\end{terminalv}
+\normalsize
+
+Here, the function astGetI is used to extract the attribute value by
+giving it the ZoomMap pointer and the attribute name (attribute names
+are not case sensitive, but we have used consistent capitalisation in
+this document in order to identify them). Remember to use the
+``ast.h'' header file to include the function prototype.
+
+If we had wanted the value of the \htmlref{Zoom}{Zoom} attribute, we would probably
+have used astGetD instead, this being a double version of the same
+function, for example:
+
+\small
+\begin{terminalv}
+double zoom;
+
+...
+
+zoom = astGetD( zoommap, "Zoom" );
+\end{terminalv}
+\normalsize
+
+However, we could equally well have read the Nin value as double, or
+the Zoom value as an integer, or whatever we wanted.
+
+The data type you want returned is specified simply by replacing the
+final character of the astGetX function name with C~(character
+string), D~(double), F~(float), I~(int) or L~(long). If possible, the
+value is converted to the type you want. If not, an error message will
+result. Note that all floating point values are stored internally as
+double, and all integer values as int. Boolean values are also stored
+as integers, but only take the values 1 and 0 (for true/false).
+
+\subsection{\label{ss:settingattributes}Setting Attribute Values}
+
+Some attribute values are read-only and cannot be altered after an
+\htmlref{Object}{Object} has been created. The \htmlref{Nin}{Nin} attribute of a \htmlref{ZoomMap}{ZoomMap} (describing
+the number of coordinates) is like this. It is defined when the
+ZoomMap is created, but cannot then be altered.
+
+Other attributes, however, can be modified whenever you want. A
+ZoomMap's \htmlref{Zoom}{Zoom} attribute is like this. If we wanted to change it, this
+could be done simply as follows:
+
+\small
+\begin{terminalv}
+astSetD( zoommap, "Zoom", 99.6 );
+\end{terminalv}
+\normalsize
+
+which sets the value to 99.6. As when getting an attribute value
+(\secref{ss:gettingattributes}), you have a choice of which data type
+you will use to supply the new value. For instance, you could use an
+integer value, as in:
+
+\small
+\begin{terminalv}
+astSetI( zoommap, "Zoom", 99 );
+\end{terminalv}
+\normalsize
+
+and the necessary data conversion would occur.  You specify the data
+type you want to supply simply by replacing the final character of the
+astSetX function name with C~(character string), D~(double),
+F~(float), I~(int) or L~(long). Setting a boolean attribute to any
+non-zero integer causes it to take the value 1.
+
+An alternative way of setting attribute values for Objects is to use
+the \htmlref{astSet}{astSet} function (\emph{i.e.}\ with no final character specifying a
+data type). In this case, you supply the attribute values in a
+character string. The big advantage of this method is that you can
+assign values to several attributes at once, separating them with
+commas. This also reads more naturally in programs. For example:
+
+\small
+\begin{terminalv}
+astSet( zoommap, "Zoom=99.6, Report=1" );
+\end{terminalv}
+\normalsize
+
+would set values for both the Zoom attribute and the \htmlref{Report}{Report} attribute
+(about which more shortly---\secref{ss:transforming}). You don't really
+have to worry about data types with this method, as any character
+representation will do. Note, when using astSet, a
+literal comma may be included in an attribute value by enclosed the value in
+quotation marks:
+\small
+\begin{terminalv}
+      astSet( skyframe, 'SkyRef="12:13:32,-23:12:44"' );
+\end{terminalv}
+\normalsize
+
+Another attractive feature of astSet is that you can build the
+character string which contains the attribute settings in the same way
+as when using the C run time library ``printf'' function. This is most
+useful when the values you want to set are held in other
+variables. For example:
+
+\small
+\begin{terminalv}
+double zoom = 99.6;
+int report = 1;
+
+...
+
+astSet( zoommap, "Zoom=%g, Report=%d", zoom, report );
+\end{terminalv}
+\normalsize
+
+would replace the ``\%'' conversion specifications by the values
+supplied as additional arguments. Any number of additional arguments
+may be supplied and the formatting rules are exactly the same as for
+the C ``printf'' family of functions. This is a very flexible
+technique, but does contain one pitfall:
+
+\begin{quote}
+\textbf{Pitfall.} The default precision used by ``printf'' (and astSet)
+for floating point values is only 6 decimal digits, corresponding
+approximately to float on most machines, whereas the AST library
+stores such values internally as doubles. You should be careful to
+specify a larger precision (such as DBL\_DIG, as defined in
+$<$float.h$>$) when necessary. For example:
+
+\small
+\begin{terminalv}
+#include <float.h>
+
+...
+
+astSet( zoommap, "Zoom=%.*g", DBL_DIG, double_value );
+\end{terminalv}
+\normalsize
+\end{quote}
+
+Substituted strings may contain commas and this is a useful way of
+assigning such strings as attribute values without the comma being
+interpreted as an assignment separator, for example:
+
+\small
+\begin{terminalv}
+astSet( object, "Attribute=%s", "A string, containing a comma" );
+\end{terminalv}
+\normalsize
+
+This is equivalent to using astSetC and one of these two methods
+should always be used when assigning string attribute values which
+might potentially contain a comma (\emph{e.g.}\ strings obtained from
+an external source). However, you should not attempt to use astSet to
+substitute strings that contain newline characters, since these are
+used internally as separators between adjacent attribute assignments.
+\label{ss:attributeinitialisation}
+
+Finally, a very convenient way of setting attribute values is to do so
+at the same time as you create an Object. Every Object constructor
+function has a final character string argument which allows you to do
+this. Although you can simply supply an empty string, it is an ideal
+opportunity to initialise the Object to have just the attributes you
+want. For example, we might have created our original ZoomMap with:
+
+\small
+\begin{terminalv}
+zoommap = astZoomMap( 2, 5.0, "Report=1" );
+\end{terminalv}
+\normalsize
+
+and it would then start life with its Report attribute set to 1.
+The ``printf''-style substitution described above may also be used
+here.
+
+\subsection{\label{ss:defaultingattributes}Testing, Clearing and Defaulting Attributes}
+
+You can use the astGetX family of functions
+(\secref{ss:gettingattributes}) to get a value for any \htmlref{Object}{Object} attribute
+at any time, regardless of whether a value has previously been set for
+it. If no value has been set, the AST library will generate a suitable
+default value.
+
+Often, the default value of an attribute will not simply be trivial
+(zero or blank) but may involve considerable processing to
+calculate. Wherever possible, defaults are designed to be real-life,
+sensible values that convey information about the state of the
+Object. In particular, they may often be based on the values of other
+attributes, so their values may change in response to changes in these
+other attributes. The \htmlref{ZoomMap}{ZoomMap} class that we have studied so far is a
+little too simple to show this behaviour, but we will meet it later
+on.
+
+An attribute that returns a default value in this way is said to be
+\emph{un-set}. Conversely, once an explicit value has been assigned to
+an attribute, it becomes \emph{set} and will always return precisely
+that value, never a default.
+
+The distinction between set and un-set attributes is important and
+affects the behaviour of several key routines in the AST library. You
+can test if an attribute is set using the function \htmlref{astTest}{astTest}, which
+returns a boolean (integer) result, as in:
+
+\small
+\begin{terminalv}
+if ( astTest( zoommap, "Report" ) ) {
+   <the Report attribute is set>
+}
+\end{terminalv}
+\normalsize
+
+
+Once an attribute is set, you can return it to its un-set state using
+\htmlref{astClear}{astClear}. The effect is as if it had never been set in the first
+place. For example:
+
+\small
+\begin{terminalv}
+astClear( zoommap, "Report" );
+\end{terminalv}
+\normalsize
+
+would ensure that the default value of the \htmlref{Report}{Report} attribute is used
+subsequently.
+
+%\subsection{TBW--Handling Character Attributes}
+
+\subsection{\label{ss:transforming}Transforming Coordinates}
+
+We now have the necessary apparatus to start using our \htmlref{ZoomMap}{ZoomMap} to show
+what it is really for. Here, we will also encounter a routine that is
+a little more fussy about the type of pointer it will accept.
+
+The purpose of a ZoomMap is to multiply coordinates by a constant zoom
+factor. To witness this in action, we will first set the \htmlref{Report}{Report}
+attribute for our ZoomMap to a non-zero value:
+
+\small
+\begin{terminalv}
+astSet( zoommap, "Report=1" );
+\end{terminalv}
+\normalsize
+
+This boolean (integer) attribute, which is present in all Mappings
+(and a ZoomMap is a \htmlref{Mapping}{Mapping}), causes the automatic display of all
+coordinate values that the Mapping converts. It is not a good idea to
+leave this feature turned on in a finished program, but it can save a
+lot of work during debugging.
+
+Our next step is to set up some coordinates for the ZoomMap to work
+on, using two arrays ``xin'' and ``yin'', and two arrays to receive
+the transformed coordinates, ``xout'' and ``yout''.  Note that these
+are arrays of double, as are all coordinate data processed by the AST
+library:
+
+\small
+\begin{terminalv}
+double xin[ 10 ] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
+double yin[ 10 ] = { 0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0 };
+double xout[ 10 ];
+double yout[ 10 ];
+\end{terminalv}
+\normalsize
+
+We will now use the function \htmlref{astTran2}{astTran2} to transform the input
+coordinates. This is the most commonly-used (2-dimensional) coordinate
+transformation function. If you look at its description in
+\appref{ss:functiondescriptions}, you will see that it requires a
+pointer to a Mapping, so we cannot supply just any old \htmlref{Object}{Object} pointer,
+as we could with the functions discussed previously. If we passed it a
+pointer to an inappropriate Object, an error message would result.
+
+Fortunately, a ZoomMap is a Mapping (\appref{ss:classhierarchy}), so we
+can use it with astTran2 to transform our coordinates, as follows:
+
+\small
+\begin{terminalv}
+astTran2( zoommap, 10, xin, yin, 1, xout, yout );
+\end{terminalv}
+\normalsize
+
+Here, 10 is the number of points we want to transform and the fifth
+argument value of 1 indicates that we want to transform in the
+\emph{forward} direction (from input to output).
+
+Because our ZoomMap's Report attribute is set to 1, this will cause
+the effects of the ZoomMap on the coordinates to be displayed on the
+standard output stream:
+
+\small
+\begin{terminalv}
+(0, 0) --> (0, 0)
+(1, 2) --> (5, 10)
+(2, 4) --> (10, 20)
+(3, 6) --> (15, 30)
+(4, 8) --> (20, 40)
+(5, 10) --> (25, 50)
+(6, 12) --> (30, 60)
+(7, 14) --> (35, 70)
+(8, 16) --> (40, 80)
+(9, 18) --> (45, 90)
+\end{terminalv}
+\normalsize
+
+This shows the coordinate values of each point both before and after
+the ZoomMap is applied. You can see that each coordinate value has
+been multiplied by the factor 5 determined by the \htmlref{Zoom}{Zoom} attribute
+value. The transformed coordinates are now stored in the ``xout'' and
+``yout'' arrays.
+
+If we wanted to transform in the opposite direction, we need simply
+change the fifth argument of astTran2 from 1 to 0. We can also feed
+the output coordinates from the above back into the function:
+
+\small
+\begin{terminalv}
+astTran2( zoommap, 10, xout, yout, 0, xin, yin );
+\end{terminalv}
+\normalsize
+
+The output would then look like:
+
+\small
+\begin{terminalv}
+(0, 0) --> (0, 0)
+(5, 10) --> (1, 2)
+(10, 20) --> (2, 4)
+(15, 30) --> (3, 6)
+(20, 40) --> (4, 8)
+(25, 50) --> (5, 10)
+(30, 60) --> (6, 12)
+(35, 70) --> (7, 14)
+(40, 80) --> (8, 16)
+(45, 90) --> (9, 18)
+\end{terminalv}
+\normalsize
+
+This is termed the \emph{inverse} transformation (we have converted
+from output to input) and you can see that the original coordinates
+have been recovered by dividing by the Zoom factor.
+
+\subsection{\label{ss:annullingpointers}Managing Object Pointers}
+
+So far, we have looked at creating Objects and using them in various
+simple ways but have not yet considered how to get rid of them again.
+
+Every \htmlref{Object}{Object} consumes various computer resources (principally memory)
+and should be disposed of when it is no longer required, so as to free
+up these resources. One way of doing this (not necessarily the
+best---\secref{ss:contexts}) is to \emph{annul} each Object pointer once
+you have finished with it, using \htmlref{astAnnul}{astAnnul}. For example:
+
+\small
+\begin{terminalv}
+zoommap = astAnnul( zoommap );
+\end{terminalv}
+\normalsize
+
+This indicates that you have finished with the pointer. Since astAnnul
+always returns the null value AST\_\_NULL (as defined in ``ast.h''),
+the recommended way of using it, as here, is to assign the returned
+value to the pointer being annulled. This ensures that any attempt to
+use the pointer again will generate an error message.
+
+In general, this process may not delete the Object, because there may
+still be other pointers associated with it. However, each Object
+maintains a count of the number of pointers associated with it and
+will be deleted if you annul the final pointer. Using astAnnul
+consistently will therefore ensure that all Objects are disposed of at
+the correct time. You can determine how many pointers are associated
+with an Object by examining its (read-only) \htmlref{RefCount}{RefCount} attribute.
+
+\subsection{\label{ss:contexts}AST Pointer Contexts---Begin and End}
+
+The use of \htmlref{astAnnul}{astAnnul} (\secref{ss:annullingpointers}) is not completely
+foolproof, however. Consider the following:
+
+\small
+\begin{terminalv}
+astShow( astZoomMap( 2, 5.0, "" ) );
+\end{terminalv}
+\normalsize
+
+This creates a \htmlref{ZoomMap}{ZoomMap} and displays it on standard output
+(\secref{ss:displayingobjects}). Using function invocations as
+arguments to other functions in this way is very convenient because it
+avoids the need for intermediate pointer variables. However, the
+pointer generated by \htmlref{astZoomMap}{astZoomMap} is still active, and since we have not
+stored its value, we cannot use astAnnul to annul it. The ZoomMap will
+therefore stay around until the end of the program.
+
+A simple way to avoid this problem is to enclose all use of AST
+functions between invocations of \htmlref{astBegin}{astBegin} and \htmlref{astEnd}{astEnd}, for example:
+
+\small
+\begin{terminalv}
+astBegin;
+astShow( astZoomMap( 2, 5.0, "" ) );
+astEnd;
+\end{terminalv}
+\normalsize
+
+When the expansion of astEnd (which is a macro) executes, every \htmlref{Object}{Object}
+pointer created since the previous use of astBegin (also a macro) is
+automatically annulled and any Objects left without pointers are
+deleted. This provides a simple solution to managing Objects and their
+pointers, and allows you to create Objects very freely without needing
+to keep detailed track of each one.  Because this is so convenient, we
+implicitly assume that astBegin and astEnd are used in most of the
+examples given in this document.  Pointer management is not generally
+shown explicitly unless it is particularly relevant to the point being
+illustrated.
+
+If necessary, astBegin and astEnd may be nested, like blocks delimited
+by ``\{\ldots\}'' in C, to define a series of AST pointer
+contexts. Each use of astEnd will then annul only those Object
+pointers created since the matching use of astBegin.
+
+\subsection{Exporting, Importing and Exempting AST Pointers}
+The \htmlref{astExport}{astExport} function allows you to export particular pointers from
+one AST context (\secref{ss:contexts}) to the next outer one, as
+follows:
+
+\small
+\begin{terminalv}
+astExport( zoommap );
+\end{terminalv}
+\normalsize
+
+This would identify the pointer stored in ``zoommap'' as being required
+after the end of the current AST context. It causes any pointers
+nominated in this way to survive the next use of \htmlref{astEnd}{astEnd} (but only one
+such use) unscathed, so that they are available to the next outer
+context.  This facility is not needed often, but is invaluable when
+the purpose of your \htmlref{astBegin}{astBegin}\ldots astEnd block is basically to
+generate an \htmlref{Object}{Object} pointer. Without this, there is no way of getting
+that pointer out.
+
+
+The \htmlref{astImport}{astImport} routine can be used in a similar manner to import a
+pointer into the current context, so that it is deleted when the current
+context is closed using astEnd.
+
+Sometimes, you may also want to exempt a pointer from all the effects
+of AST contexts. You should not need to do this often, but it will
+prove essential if you ever need to write a library of functions that
+stores AST pointers as part of its own internal data. Without some
+form of exemption, the caller of your routines could cause the
+pointers you have stored to be annulled---thus corrupting your
+internal data---simply by using astEnd. To avoid this, you should use
+\htmlref{astExempt}{astExempt} on each pointer that you store, for example:
+
+\small
+\begin{terminalv}
+astExempt( zoommap );
+\end{terminalv}
+\normalsize
+
+This will prevent the pointer being affected by any subsequent use of
+astEnd. Of course, it then becomes your responsibility to annul this
+pointer (using \htmlref{astAnnul}{astAnnul}) when it is no longer required.
+
+
+\subsection{AST Objects within Multi-threaded Applications}
+
+When the AST library is built from source, the build process checks to
+see if the POSIX threads library (``\texttt{pthreads}'') is available. If so,
+appropriate \texttt{pthreads} calls are inserted into the AST source code to
+ensure that AST is thread-safe, and the AST\_\_THREADSAFE macro (defined
+in the ``ast.h'' header file) is set to ``\texttt{1}''. If the \texttt{pthreads}
+library cannot be found when AST is built, a working version of the AST
+library will still be created, but it will not be thread-safe. In this
+case the AST\_\_THREADSAFE macro will be set to ``\texttt{0}'' in ast.h. The
+rest of this section assumes that the thread-safe version of AST is being
+used.
+
+Note, some AST functions call externally specified functions (\emph{e.g.}
+the source and sink functions used by the \htmlref{Channel}{Channel} class or the graphics
+primitives functions used by the \htmlref{Plot}{Plot} class). AST does not know whether
+such functions are thread-safe or not. For this reason, invocations of these
+functions within a multi-threaded environment are serialised using a mutex
+in order to avoid two or more threads executing an external function
+simultaneously.
+
+If an application uses more than one thread, the possibility arises that
+an \htmlref{Object}{Object} created by one thread may be accessed by another thread, potentially
+simultaneously. If any of the threads modifies any aspect of the Object,
+this could lead to serious problems within the other threads. For this
+reason, some restrictions are placed on how Objects can be used in a
+multi-threaded application.
+
+\subsubsection{Locking AST Objects for Exclusive Use}
+The basic restriction is that a thread can only access Objects that it
+has previously locked for its own exclusive use. If a thread attempts to
+access any \htmlref{Object}{Object} that it has not locked, an error is reported.
+
+The \htmlref{astAnnul}{astAnnul} function is the one exception to this restriction. Pointers
+for Objects not currently locked by the calling thread can be annulled
+succesfully using astAnnul. This means that a thread that has finished
+with an Object pointer can unlock the Object by passing the pointer to
+\htmlref{astUnlock}{astUnlock} (so that other threads can use the Object via their own cloned
+pointers), and can then annul the pointer using astAnnul. Note, however,
+that an error will be reported by astAnnul if the supplied pointer has
+been locked by another thread using \htmlref{astLock}{astLock}.
+
+When an Object is created, it is initially locked by the calling thread.
+Therefore a thread does not need to lock an Object explicitly if it was
+created in the same thread.
+
+If the Object pointer is then passed to another thread, the first thread
+must unlock the Object using astUnlock and the second thread must then lock
+it using astLock.
+
+If a thread attempts to lock an Object that is already locked by another
+thread, it can choose to report an error immediately or to wait until the
+Object is available.
+
+The \htmlref{astThread}{astThread} function can be used to determine whether an Object is
+locked by the running thread, locked by another thread, or unlocked.
+
+If two or more threads need simultaneous access to an Object, a deep copy
+of the Object should be taken for each thread, using \htmlref{astCopy}{astCopy}, and then
+the copies should be unlocked and passed to the othe threads, which
+should then lock them. Note, if a thread modifies the Object, the
+modification will have no effect on the other threads, because the Object
+copies are independent of each other.
+
+\subsubsection{AST Pointer Contexts}
+
+Each thread maintains its own set of nested AST contexts, so when \htmlref{astEnd}{astEnd}
+is called, only Objects that are locked by the current thread will
+be annulled.
+
+If an \htmlref{Object}{Object} is unlocked by a thread using \htmlref{astUnlock}{astUnlock}, it is exempted from
+context handling so that subsequent invocations of astEnd will not cause it
+to be annulled (this is similar to using \htmlref{astExempt}{astExempt} on the Object). When the
+Object is subsequently locked by another thread using \htmlref{astLock}{astLock}, it will be
+imported into the context that was active when astLock was called.
+
+
+
+\subsection{\label{ss:copyingobjects}Copying Objects}
+
+The AST library makes extensive use of pointers, not only for
+accessing Objects directly, but also as a means of storing Objects
+inside other Objects (a number of classes of \htmlref{Object}{Object} are designed to
+hold collections of other Objects). Rather than copy an Object in its
+entirety, a pointer to the interior Object is simply stored in the
+enclosing Object.
+
+This means that Objects may frequently not be completely independent
+of each other because, for instance, they both contain pointers to the
+same sub-Object. In this situation, changing one Object (say assigning
+an attribute value) may affect the other one \emph{via} the common
+Object.
+
+It is difficult to describe all cases where this may happen, so you
+should always be alert to the possibility. Fortunately, there is a
+simple solution. If you require two Objects to be independent, then
+simply use \htmlref{astCopy}{astCopy} to make a copy of one, \emph{e.g.}:
+
+\small
+\begin{terminalv}
+AstZoomMap *zoommap1, *zoommap2;
+
+...
+
+zoommap2 = astCopy( zoommap1 );
+\end{terminalv}
+\normalsize
+
+This process will create a true copy of any Object and return a
+pointer to the copy. This copy will not contain any pointers to any
+component of the original Object (everything is duplicated), so you
+can then modify it safely, without fear of affecting either the
+original or any other Object.
+
+%\subsection{TBW - Inheritance}
+
+\subsection{C Pointer Types}
+
+At this point it is necessary to confess to a small amount of
+deception. So far, we have been passing \htmlref{Object}{Object} pointers to AST
+functions in order to perform operations on those Objects. In fact,
+however, what we were using were not true C functions at all, but
+merely macros which invoke a related set of hidden functions with
+essentially the same arguments. In practical terms, this makes very
+little difference to how you use the functions, as we will continue to
+call them.\footnote{About the only difference is that you cannot store
+a pointer to an AST ``function'' in a variable and use the variable's
+value to invoke that function again later.}
+
+The reason for this deception has to do with the rules for data typing
+in C. Recall that most AST functions can be used to process Objects
+from a range of different classes (\secref{ss:objecthierarchy}). In C,
+this means passing different pointer types to the same function and
+most C compilers will not permit this (at least, not without
+grumbling) because it usually indicates a programming error. In AST,
+however, it is perfectly safe if done properly. Some way is therefore
+needed of circumventing the normal compiler checking.
+
+The normal way of doing this in C is with a cast. This approach
+quickly becomes cumbersome, however, so we have adopted the strategy
+of wrapping each function in a macro which applies the appropriate
+cast for you. This means that you can pass pointers of any type to any
+AST function. For example, in passing a \htmlref{ZoomMap}{ZoomMap} pointer to \htmlref{astShow}{astShow}:
+
+\small
+\begin{terminalv}
+AstZoomMap *zoommap;
+
+...
+
+zoommap = astZoomMap( 2, 5.0, "" );
+astShow( zoommap );
+\end{terminalv}
+\normalsize
+
+we are exploiting this mechanism to avoid a compiler warning, because
+the notional type of astShow's parameter is AstObject$*$ (not
+AstZoomMap$*$).
+
+We must still guard against programming errors, however, so every
+pointer's type is checked by the enclosing macro immediately before
+any AST function executes. This allows pointer mis-matches (in the
+more liberal AST sense---\emph{i.e.}\ taking account of the class
+hierarchy, rather than the stricter C sense) to be detected at
+run-time and a suitable error message will be reported. This message
+should also identify the line where the error occurs.
+
+A similar strategy is used when pointers are returned by AST functions
+(\emph{i.e.}\ as the function result). In this case the pointer is
+cast to void$*$, although we retain the notional pointer type in the
+function's documentation
+(\emph{e.g.}\ \appref{ss:functiondescriptions}). This allows you to
+assign function results to pointer variables without using an explicit
+cast. For example, the \htmlref{astRead}{astRead} function returns an Object pointer, but
+might be used to read (say) a ZoomMap as follows:
+
+\small
+\begin{terminalv}
+AstChannel *channel;
+AstZoomMap *zoommap;
+
+...
+
+zoommap = astRead( channel );
+\end{terminalv}
+\normalsize
+
+Strictly, there is a C pointer mis-match here, but it is ignored
+because the operation makes perfect sense to AST.
+
+\textbf{There is an important exception to this, however, in that
+constructor functions always return strongly-typed pointers.}  What
+we mean by this is that the returned pointer is never implicitly cast
+to void$*$. You must therefore match pointer types when you initially
+create an Object using its constructor, such as in the following:
+
+\small
+\begin{terminalv}
+AstZoomMap *zoommap;
+
+...
+
+zoommap = astZoomMap( 2, 5.0, "" );
+\end{terminalv}
+\normalsize
+
+If the variable receiving the pointer is of a different type, an
+appropriate cast should be used, as in:
+
+\small
+\begin{terminalv}
+AstMapping *mapping;
+
+...
+
+mapping = (AstMapping *) astZoomMap( 2, 5.0, "" );
+\end{terminalv}
+\normalsize
+
+This is an encouragement for you to declare your pointer types
+consistently, since this is of great benefit to anyone trying to
+understand your software.
+
+Finally, we should also make one more small confession---AST pointers
+are not really pointers at all.  Although they behave like pointers,
+the actual ``values'' stored are not the addresses of C data
+structures. This means that you cannot de-reference an AST pointer to
+examine the data within (although you can use astShow
+instead---\secref{ss:displayingobjects}). This is necessary so that AST
+pointers can be made unique even although several of them might
+reference the same Object.
+
+\subsection{\label{ss:errordetection}Error Detection}
+
+If an error occurs in an AST function (for example, if you supply an
+invalid argument, such as a pointer to the wrong class of \htmlref{Object}{Object}), an
+error message will be written to the standard error stream and the
+function will immediately return.
+
+To indicate than an error has occurred, an AST \emph{error status}
+value is used. This integer value is stored internally by AST and is
+initially clear (\emph{i.e.}\ set to zero\footnote{We will assume
+throughout that the ``OK'' value is zero, as it currently is. However,
+a different value could, in principle, be used if the environment in
+which AST is running requires it. This is why a simple interface is
+provided to isolate you from the actual value of the error status.}
+to indicate no error). If an error occurs, it becomes set to a
+different \emph{error value}, which allows you to detect the error, as
+follows:
+
+\small
+\begin{terminalv}
+zoommap = astZoomMap( 2, 5.0, "Title=My ZoomMap" );
+if ( !astOK ) {
+   <an error has occurred>
+}
+\end{terminalv}
+\normalsize
+
+The macro \htmlref{astOK}{astOK} is used to test whether the AST error status is still
+OK. In this example it would not be, because we have attempted to set
+a value for the \htmlref{Title}{Title} attribute of a \htmlref{ZoomMap}{ZoomMap} and a ZoomMap does not
+have such an attribute.  The actual value of the AST error status can
+be obtained using the \htmlref{astStatus}{astStatus} macro, as follows:
+
+\small
+\begin{terminalv}
+int status;
+
+...
+
+
+status = astStatus;
+\end{terminalv}
+\normalsize
+
+A consequence of the AST error status being set is that almost all AST
+functions will subsequently cease to function and will instead simply
+return without action.  This means that you do not need to use astOK
+to check for errors very frequently. Instead, you can usually simply
+invoke a succession of AST functions. If an error occurs in any of
+them, the following ones will do nothing and you can check for the
+error at the end, for example:
+
+\small
+\begin{terminalv}
+astFunctionA( ... );
+astFunctionB( ... );
+astFunctionC( ... );
+if ( !astOK ) {
+   <an error has occurred>
+}
+\end{terminalv}
+\normalsize
+
+There are, however, a few functions which do not adhere to this
+general rule and which will attempt to execute if the AST error status
+is set. These functions, such as \htmlref{astAnnul}{astAnnul}, are concerned with cleaning
+up and recovering resources. For example, in the following:
+
+\small
+\begin{terminalv}
+zoommap = astZoomMap( 2, 5.0, "" );
+
+astFunctionX( ... );
+astFunctionY( ... );
+astFunctionZ( ... );
+
+zoommap = astAnnul( zoommap );
+if ( !astOK ) {
+   <an error has occurred>
+}
+\end{terminalv}
+\normalsize
+
+astAnnul will execute normally in order to recover the resources
+associated with the ZoomMap that was created earlier, regardless of
+whether an error has occurred in any of the intermediate functions.
+Functions which behave in this way are noted in the relevant
+descriptions in \appref{ss:functiondescriptions}.
+
+If a serious error occurs, you will probably want to abort your
+program, but sometimes you may want to recover and carry on. Because
+very few AST functions will execute once the AST error status has been
+set, you must first clear this status by using the \htmlref{astClearStatus}{astClearStatus}
+macro, as follows:
+
+\small
+\begin{terminalv}
+astClearStatus;
+\end{terminalv}
+\normalsize
+
+This will restore the AST error status to its OK value, so that AST
+functions execute normally again.
+
+Occasionally, you may also need to set the AST error status to an
+explicit error value (see \secref{ss:channelsink} for an
+example). This is done using \htmlref{astSetStatus}{astSetStatus} and can be used to
+communicate to AST that an error has occurred in some other item of
+software, for example:
+
+\small
+\begin{terminalv}
+int new_status;
+
+...
+
+astSetStatus( new_status );
+\end{terminalv}
+\normalsize
+
+The effect is that most AST routines will subsequently return without
+action, just as if an error had occurred within the AST library
+itself.
+
+\subsection{Sharing the Error Status}
+
+In some software, it is usual to maintain a single integer error
+status variable which is accessed by each function as it executes. If
+an error occurs, this status variable is set and other functions can
+detect this and take appropriate action.
+
+If you use AST in such a situation, it can be awkward to have a
+separate internal error status used by AST functions alone. To remedy
+this, AST is capable of sharing the error status variable used by any
+other software, so long as they use the same conventions
+(\emph{i.e.}\ a C int with the same ``OK'' value). To enable this
+facility, you should pass the address of your status variable to
+\htmlref{astWatch}{astWatch}, as follows:
+
+\small
+\begin{terminalv}
+int my_status;
+int *old_address;
+
+...
+
+old_address = astWatch( &my_status );
+\end{terminalv}
+\normalsize
+
+Henceforth, instead of using its own internal error status variable,
+AST will use the one you supply, so that it can detect errors flagged
+by other parts of your software. The address of the original error
+status variable is returned by astWatch, so you can restore the
+original behaviour later if necessary.
+
+Note that this facility is not available \emph{via} the Fortran
+interface to the AST library.
+
+\cleardoublepage
+\section{\label{ss:mappings}Inter-Relating Coordinate Systems (Mappings)}
+
+In \secref{ss:primer} we used the \htmlref{ZoomMap}{ZoomMap} as an example of a
+\htmlref{Mapping}{Mapping}. We saw how it could be used to transform coordinates from its
+input to its output and back again (\secref{ss:transforming}). We also
+saw how its behaviour could be controlled by setting various
+attributes, such as the \htmlref{Zoom}{Zoom} factor and the \htmlref{Report}{Report} attribute that made
+it display coordinate values as it transformed them.
+
+In this section, we will look at Mappings a bit more thoroughly and
+explore the behaviour which is common to all the Mappings provided by
+AST.  This is good background for what follows, because many of the
+Objects we discuss later will also turn out to be Mappings in various
+disguises.
+
+\subsection{\label{ss:mappingclass}The Mapping Class}
+
+Before we start, it is worth taking a quick look at the \htmlref{Mapping}{Mapping} class
+as a whole and some of the sub-classes it contains:
+
+\begin{terminalv}
+   Mapping
+      CmpMap
+      DssMap
+      GrismMap
+      IntraMap
+      LutMap
+      MathMap
+      MatrixMap
+      PermMap
+      PolyMap
+      SlaMap
+      SpecMap
+      TimeMap
+      UnitMap
+      WcsMap
+      ZoomMap
+
+      Frame
+         <various types of Frame>
+\end{terminalv}
+
+The \htmlref{Frame}{Frame} sub-class has been separated out here because it is covered
+in detail in \secref{ss:frames}. We start by looking at the parent
+class, Mapping.
+
+AST does not provide a function to create a basic Mapping
+(\emph{i.e.}\ the astMapping constructor does not exist). This is
+because the Mapping class itself is ``virtual'' and basic Mappings are
+of no use in themselves. The Mapping class serves simply to contain
+the various specialised Mappings that exist.
+However, it provides more than just a convenient heading for them
+because it bestows all classes of Mapping with common properties
+(\emph{e.g.}\ attributes) and behaviour.  By examining the Mapping
+class, we are therefore examining the things that all other Mappings
+have in common.
+
+\subsection{The Mapping Model}
+
+The concept of a \htmlref{Mapping}{Mapping} was illustrated in Figure~\ref{fig:mapping}.
+It is a black box which you can supply with a set of coordinate values
+in return for a set of transformed coordinates. The two sets are
+termed \emph{input} and \emph{output} coordinates. You can also go
+back the other way and transform output coordinates back into input
+coordinates, as we saw in \secref{ss:transforming}.
+
+\subsection{Changing Attributes of a Mapping}
+
+Many classes of \htmlref{Mapping}{Mapping} have attributes that provide values for parameter
+used within the transformation. For instance, the \htmlref{ZoomMap}{ZoomMap} class has an
+attribute called ``\htmlref{Zoom}{Zoom}'' that gives the scalar value by which each
+coordinate is to be multiplied. These attribute values should be set when
+the Mapping is created and should not be changed afterwards. Indeed, the
+AST library will report an error if an attempt is made to change the
+value of a Mapping attribute. This is because, once created, Mappings are
+often later included within other objects such as FrameSets and CmpMaps.
+This means that in general there could be many active references to a single
+Mapping object within a program. Changing an attribute of the Mapping
+via one particular reference (i.e pointer) would cause all the other
+references to change too, with often undesirable or unpredictable
+consequences. To avoid this, Mappings are considered \emph{immutable} in
+most situations. The one exception is if the Mapping has not yet been
+cloned or included in another \htmlref{Object}{Object} (\emph{i.e.} it has a reference
+couint of one) - changing the attributes of such a Mapping is allowed,
+and will not generate an error.
+
+Note, the \htmlref{Invert}{Invert} attribute of a Mapping is not subject to this rule and
+can be changed at any time.
+
+\subsection{Input and Output Coordinate Numbers}
+
+In general, the number of coordinates you feed into a \htmlref{Mapping}{Mapping} to
+represent a single point need not be the same as the number that comes
+out. Often these numbers will be the same, and often they will both
+equal 2 (because 2-dimensional coordinate systems are common), but
+this needn't necessarily be the case.
+
+The number of coordinates required to specify an input point is
+represented by the integer attribute \htmlref{Nin}{Nin} and the number required to
+specify an output point is represented by \htmlref{Nout}{Nout}. These are read-only
+attributes common to all Mappings. Generally, their values are fixed
+when a Mapping is created.
+
+In \secref{ss:objectcreation}, we saw how the Nin attribute for a
+\htmlref{ZoomMap}{ZoomMap} was initialised by the call to the constructor function
+\htmlref{astZoomMap}{astZoomMap} which created it. In this case, the Nout attribute was not
+needed and it implicitly took the same value as Nin, but we could
+have enquired about its value had we wanted, as follows:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstZoomMap *zoommap;
+int nout;
+
+...
+
+nout = astGetI( zoommap, "Nout" );
+\end{terminalv}
+\normalsize
+
+\subsection{Forward and Inverse Transformations}
+
+We stated earlier that a \htmlref{Mapping}{Mapping} may be used to transform coordinates
+either from input to output, or \emph{vice versa}. These are termed
+its \emph{forward} and \emph{inverse} transformations.
+
+This statement was not quite accurate, however, because in general
+Mappings are only \textbf{potentially} capable of working in both
+directions. In practice, coordinate transformation may only be
+feasible in one direction or the other because some functions are not
+easily inverted (they may be multi-valued, for instance). Allowance
+must be made for this, so each Mapping has two read-only boolean
+(integer) attributes, \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse}, which indicate
+whether each transformation is available.
+
+A transformation is available if the corresponding attribute is
+non-zero, otherwise it is not.\footnote{Most of the Mappings provided
+by the AST library work in both directions, although the \htmlref{LutMap}{LutMap} can
+behave otherwise.} If you enquire about the value of these attributes,
+a value of 0 or 1 is returned.  Attempting to use a Mapping to apply a
+transformation which is not available will result in an error.
+
+\subsection{\label{ss:invertingmappings}Inverting Mappings}
+
+An important attribute, common to all Mappings, is the \htmlref{Invert}{Invert}
+flag. This is a boolean (integer) attribute that can be assigned a new
+value at any time. If it is non-zero, it has the effect of
+interchanging the \htmlref{Mapping}{Mapping}'s input and output coordinates and the
+Mapping is then said to be \emph{inverted}. By default, the Invert
+attribute is zero.
+
+There is no magic in this. There is no fancy arithmetic involved in
+inverting mathematical functions, for instance. The Invert flag is
+simply a switch that interchanges a Mapping's input and output
+ports. If it is non-zero, the Mapping's \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes are
+swapped, its \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse} attributes are swapped, and
+when you ask for what was once the forward transformation you get the
+inverse transformation instead (and \emph{vice versa}). When you
+return the Invert attribute to zero, or clear it, the Mapping returns
+to its original behaviour.
+
+Often, the actual value of the Invert attribute is unimportant and you
+simply wish to invert its boolean sense, so that what was the
+Mapping's input becomes its output and \emph{vice versa}. This is most
+easily accomplished using \htmlref{astInvert}{astInvert}, as follows:
+
+\small
+\begin{terminalv}
+AstMapping *mapping;
+
+...
+
+astInvert( mapping );
+\end{terminalv}
+\normalsize
+
+If the Mapping you have happens to be the wrong way around, astInvert
+allows you to correct the problem.
+
+\subsection{Finding the Rate of Change of a Mapping Output}
+The
+\htmlref{astRate}{astRate}
+function can be used to find the rate of change of any \htmlref{Mapping}{Mapping} output
+with respect to any Mapping input, at a given input position. The method
+used produces good accuracy (typically a relative error of 10E-10 or
+less) but may require the Mapping to be evaluated 100 or more times.
+An estimate of the second derivative is also produced by this function.
+
+
+\subsection{Reporting Coordinate Transformations}
+
+We have already seen (\secref{ss:transforming}) how the boolean
+(integer) \htmlref{Report}{Report} attribute of a \htmlref{Mapping}{Mapping} works. If it is non-zero, the
+operation of transforming a set of coordinates will result in a report
+being written to standard output. This will display the coordinate
+values before and after transformation. It can save considerable time
+during program development by eliminating the need to add loops and
+output statements to your program.
+
+In a finished program, however, you should be careful that the Report
+attribute is not set to a non-zero value unless you want to see the
+output (there may often be rather a lot of this!). To help prevent
+unwanted output being produced by accident, the Report attribute is
+unusual in that its value is not preserved when a Mapping is copied
+using \htmlref{astCopy}{astCopy} (\secref{ss:copyingobjects}). Instead, it reverts to its
+default of zero (\emph{i.e.}\ un-set) in the copy. It also reverts to
+zero when a Mapping is written out, \emph{e.g.}\ to a file using a
+\htmlref{Channel}{Channel} (\secref{ss:channels}).
+
+%\subsection{TBW---More on Transforming Coordinates}
+
+\subsection{\label{ss:badcoordinates}Handling Missing (Bad) Coordinate Values}
+
+Even when coordinates can, in principle, be transformed in either
+direction by a \htmlref{Mapping}{Mapping}, there may still be instances where specific
+coordinate values cannot be handled. For example, the Mapping may be
+mathematically intractable (\emph{e.g.}\ singular) in certain places,
+or it may map a subset of one space on to another, so that some points
+in one space are not represented in the other.  Sky projections often
+show this behaviour, since it is quite common to project only half of
+the celestial sphere on to two dimensions, omitting points on the
+opposite side of the sky. There are many other examples.
+
+To indicate when coordinates cannot be transformed, for whatever
+reason, AST substitutes a special output coordinate value given by the
+macro AST\_\_BAD (as defined in the ``ast.h'' header file).  Before
+making use of coordinates generated by any of the AST transformation
+functions, therefore, you may need to check for the presence of this
+value.
+
+Because coordinates with the value AST\_\_BAD can be generated in this
+way, all other AST functions are also capable of recognising this
+value and handling it appropriately. The coordinate transformation
+functions do this by propagating any missing input coordinate
+information through to their output.  This means that if you supply
+coordinates with the value AST\_\_BAD, the returned coordinates are
+also likely to contain this value. Here, for example, is what happens
+if you use a \htmlref{ZoomMap}{ZoomMap} (with \htmlref{Zoom}{Zoom} factor 5) to transform such a set of
+coordinates:
+
+\small
+\begin{terminalv}
+(0, 0) --> (0, 0)
+(<bad>, 2) --> (<bad>, 10)
+(2, 4) --> (10, 20)
+(3, 6) --> (15, 30)
+(4, <bad>) --> (20, <bad>)
+(5, 10) --> (25, 50)
+(<bad>, <bad>) --> (<bad>, <bad>)
+(7, 14) --> (35, 70)
+(8, 16) --> (40, 80)
+(9, 18) --> (45, 90)
+\end{terminalv}
+\normalsize
+
+The AST\_\_BAD value is represented by the string ``$<$bad$>$''. This
+is a case of ``garbage in, garbage out'' but at least it's consistent
+garbage that you can recognise!
+
+Note how the presence of the AST\_\_BAD value in one input dimension
+does not necessarily result in the loss of information for all output
+dimensions. Sometimes, such loss will be unavoidable, but in general
+an attempt is made to preserve information as far as possible. The
+exact behaviour will depend on the Mapping involved.
+
+\subsection{\label{ss:unitmapexample}Example---the UnitMap}
+
+The \htmlref{UnitMap}{UnitMap} is the simplest of Mappings. It is a null \htmlref{Mapping}{Mapping}. Its
+purpose is simply to copy coordinate values, unaltered, from its input
+to its output and \emph{vice versa}.
+
+A UnitMap has no additional attributes beyond those of a basic
+Mapping. Its \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes are always equal and are
+specified by the first argument supplied to its constructor. For
+example:
+
+\small
+\begin{terminalv}
+AstUnitMap *unitmap;
+
+...
+
+unitmap = astUnitMap( 2, "" );
+\end{terminalv}
+\normalsize
+
+will create a UnitMap that copies 2-dimensional coordinates. Inverting
+a UnitMap has no effect beyond changing the value of its \htmlref{Invert}{Invert}
+attribute.
+
+The main use of a UnitMap is to allow a Mapping to be supplied when one
+is required (as an argument to a function, for example) but you wish
+it to leave coordinate values unchanged.
+
+\subsection{\label{ss:permmapexample}Example---the PermMap}
+
+The \htmlref{PermMap}{PermMap} is a rather more complicated \htmlref{Mapping}{Mapping} than we have met
+previously.  Its purpose is to change the order, or number, of
+coordinates. It is also able to substitute fixed values for
+coordinates.
+
+To illustrate its action, suppose our input coordinates are denoted by
+($x_1,x_2,x_3,x_4$) in a 4-dimensional space and suppose our output
+coordinates are to be ($x_4,x_1,x_2,x_3$). Our PermMap, therefore,
+should rotate the coordinate values by one position.
+
+To create such a PermMap, we first set up two integer arrays. One of
+these, ``outperm'', controls the selection of input coordinates for
+use in the output and the other, ``inperm'', controls selection of
+output coordinates for use in the input:
+
+\small
+\begin{terminalv}
+int outperm[ 4 ] = { 4, 1, 2, 3 };
+int inperm[ 4 ] = { 2, 3, 4, 1 };
+\end{terminalv}
+\normalsize
+
+Note that the numbers we store in these arrays are the indices of the
+coordinates that we want to select. We have chosen these so that the
+forward and inverse transformations will perform complementary
+permutations on the coordinates.
+
+The PermMap is then created by passing these arrays to its
+constructor, as follows:
+
+\small
+\begin{terminalv}
+AstPermMap *permmap;
+
+...
+
+permmap = astPermMap( 4, inperm, 4, outperm, NULL, "" );
+\end{terminalv}
+\normalsize
+
+Note that we specify the number of input and output coordinates
+separately, but set both to 4 in this example. The resulting PermMap
+would have the following effect when used to transform coordinates:
+
+\begin{terminalv}
+Forward:
+   (1, 2, 3, 4) --> (4, 1, 2, 3)
+   (2, 4, 6, 8) --> (8, 2, 4, 6)
+   (3, 6, 9, 12) --> (12, 3, 6, 9)
+   (4, 8, 12, 16) --> (16, 4, 8, 12)
+   (5, 10, 15, 20) --> (20, 5, 10, 15)
+
+Inverse:
+   (4, 1, 2, 3) --> (1, 2, 3, 4)
+   (8, 2, 4, 6) --> (2, 4, 6, 8)
+   (12, 3, 6, 9) --> (3, 6, 9, 12)
+   (16, 4, 8, 12) --> (4, 8, 12, 16)
+   (20, 5, 10, 15) --> (5, 10, 15, 20)
+\end{terminalv}
+
+If the number of input and output coordinates are unequal so, also,
+will be the size of the ``outperm'' and ``inperm'' arrays. This means,
+however, that we cannot fill them with coordinate indices so that they
+perform complementary permutations, because one transformation will
+lose information (discard a coordinate) that the other cannot recover.
+To give an example, consider the following:
+
+\small
+\begin{terminalv}
+int outperm[ 3 ] = { 4, 3, 2 };
+int inperm[ 4 ] = { -1, 3, 2, 1 };
+double con[ 1 ] = { 99.004 };
+\end{terminalv}
+\normalsize
+
+In this case, the forward transformation will change
+($x_1,x_2,x_3,x_4$) into ($x_4,x_3,x_2$) and will discard $x_1$. The
+inverse transformation restores the original coordinate order, but has
+no value to assign to the first coordinate. In this case, the number
+entered in the ``inperm'' array is $-$1.
+
+This negative value indicates that the coordinate value should be
+obtained by addressing the first element of the ``con'' array
+(\emph{i.e.}\ element zero). This array, ignored in the previous
+example, may then be used to supply a value for the missing
+coordinate.
+
+The constructor function:
+
+\small
+\begin{terminalv}
+permmap = astPermMap( 4, inperm, 3, outperm, con, "" );
+\end{terminalv}
+\normalsize
+
+will then create a PermMap with the following effect when used to
+transform coordinates:
+
+\begin{terminalv}
+Forward:
+   (1, 2, 3, 4) --> (4, 3, 2)
+   (2, 4, 6, 8) --> (8, 6, 4)
+   (3, 6, 9, 12) --> (12, 9, 6)
+   (4, 8, 12, 16) --> (16, 12, 8)
+   (5, 10, 15, 20) --> (20, 15, 10)
+
+Inverse:
+   (4, 3, 2) --> (99.004, 2, 3, 4)
+   (8, 6, 4) --> (99.004, 4, 6, 8)
+   (12, 9, 6) --> (99.004, 6, 9, 12)
+   (16, 12, 8) --> (99.004, 8, 12, 16)
+   (20, 15, 10) --> (99.004, 10, 15, 20)
+\end{terminalv}
+
+The ``con'' array may contain more than one value if necessary and may
+be addressed by both the ``inperm'' and ``outperm'' arrays using
+coordinate indices $-$1, $-$2, $-$3,~\emph{etc.}\ to refer to the
+first, second, third,~\emph{etc.}\ elements.
+
+If there is no suitable replacement value that can be supplied
+\emph{via} the ``con'' array, a value of zero may be entered into the
+``outperm'' and/or ``inperm'' arrays. This causes the value AST\_\_BAD
+to be used for the affected coordinate (as defined in the ``ast.h''
+header file), thus indicating a missing coordinate value
+(\secref{ss:badcoordinates}).
+
+The principle use for a PermMap lies in matching a coordinate system
+to a data array where there is a choice of storage order for the data.
+PermMaps are also useful for discarding unwanted coordinates so as to
+reduce the number of dimensions, such as when selecting a ``slice''
+from a multi-dimensional array.
+
+\cleardoublepage
+\section{\label{ss:cmpmaps}Compound Mappings (CmpMaps)}
+
+We now turn to a rather special form of \htmlref{Mapping}{Mapping}, the \htmlref{CmpMap}{CmpMap}. The
+Mappings we have considered so far have been atomic, in the sense that
+they perform pre-defined elementary transformations. A CmpMap,
+however, is a compound Mapping. In essence, it is a framework for
+containing other Mappings and its purpose is to allow those Mappings
+to work together in various combinations while appearing as a single
+\htmlref{Object}{Object}. A CmpMap's behaviour is therefore not pre-defined, but is
+determined by the other Mappings it contains.
+
+\subsection{\label{ss:seriescmpmap}Combining Mappings in Series}
+
+Consider a simple example based on two 2-dimensional coordinate
+systems. Suppose that to convert from one to the other we must swap
+the coordinate order and multiply both coordinates by 5, so that the
+coordinates ($x_1,x_2$) transform into ($5x_2,5x_1$). This can be done
+in two stages:
+
+\begin{enumerate}
+\item Apply a \htmlref{PermMap}{PermMap} (\secref{ss:permmapexample}) to swap the
+coordinate order.
+
+\item Apply a \htmlref{ZoomMap}{ZoomMap} (\secref{ss:transforming}) to multiply both
+coordinate values by the constant 5.
+\end{enumerate}
+
+The PermMap and ZoomMap are then said to operate \emph{in series},
+because they are applied sequentially
+(\emph{c.f.}\ Figure~\ref{fig:seriescmpmap}).  We can create a \htmlref{CmpMap}{CmpMap}
+that applies these Mappings in series as follows:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstCmpMap *cmpmap;
+AstPermMap *permmap;
+AstZoomMap *zoommap;
+
+...
+
+/* Create the individual Mappings. */
+{
+   int inperm[ 2 ] = { 2, 1 };
+   int outperm[ 2 ] = { 2, 1 };
+   permmap = astPermMap( 2, inperm, 2, outperm, NULL, "" );
+}
+zoommap = astZoomMap( 2, 5.0, "" )
+
+/* Combine them in series. */
+cmpmap = astCmpMap( permmap, zoommap, 1, "" );
+
+/* Annul the individual Mapping pointers. */
+permmap = astAnnul( permmap );
+zoommap = astAnnul( zoommap );
+\end{terminalv}
+\normalsize
+
+Here, the third argument (1) of the constructor function \htmlref{astCmpMap}{astCmpMap}
+indicates ``in series''.
+
+When used to transform coordinates in the forward direction, the
+resulting CmpMap will apply the first component \htmlref{Mapping}{Mapping} (the PermMap)
+and then the second one (the ZoomMap). When transforming in the
+inverse direction, it will apply the second one (in the inverse
+direction) and then the first one (also in the inverse direction).  In
+general, although not in this particular example, the order in which
+the two component Mappings are supplied is significant. Clearly, also,
+the \htmlref{Nout}{Nout} attribute (number of output coordinates) for the first
+Mapping must equal the \htmlref{Nin}{Nin} attribute (number of input coordinates) for
+the second one.
+
+\subsection{Combining Mappings in Parallel}
+
+Connecting two Mappings in series (\secref{ss:seriescmpmap}) is not the
+only way of combining them. The alternative, \emph{in parallel},
+involves applying the two Mappings at once but on different subsets of
+the coordinate values.
+
+Consider, for example, a set of 3-dimensional coordinates and suppose
+we wish to transform them by swapping the first two coordinate values
+and multiplying the final one by 5, so that ($x_1,x_2,x_3$) transforms
+into ($x_2,x_1,5x_3$). Again, we can perform each of these steps
+individually using Mappings similar to the \htmlref{PermMap}{PermMap} and \htmlref{ZoomMap}{ZoomMap} used
+earlier (\secref{ss:seriescmpmap}). In this case, however, the ZoomMap is
+1-dimensional and the individual Mappings are applied in parallel
+(\emph{c.f.}\ Figure~\ref{fig:parallelcmpmap}).
+
+Creating a \htmlref{CmpMap}{CmpMap} for this purpose is also very simple:
+
+\small
+\begin{terminalv}
+cmpmap = astCmpMap( permmap, zoommap, 0, "" );
+\end{terminalv}
+\normalsize
+
+The only difference is that the third argument of \htmlref{astCmpMap}{astCmpMap} is now
+zero, meaning ``in parallel''.
+
+As before, the order in which the two component Mappings are supplied
+is significant. The first one acts on the lower-numbered input
+coordinate values (however many it needs) and produces the
+lower-numbered output coordinates, while the second \htmlref{Mapping}{Mapping} acts on
+the higher-numbered input coordinates (however many remain) and
+generates the remaining higher-numbered output coordinates.  When the
+CmpMap transforms coordinates in the inverse direction, both component
+Mappings are applied to the same coordinates, but in the inverse
+direction.
+
+Note that the \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes of the component Mappings
+(\emph{i.e.}\ the numbers of input and output coordinates) will sum to
+give the Nin and Nout attributes of the overall CmpMap.
+
+\subsection{\label{ss:cmpmapcomponents}The Component Mappings}
+
+A \htmlref{CmpMap}{CmpMap} does not store copies of its component Mappings, but simply
+holds pointers to them. In the example above
+(\secref{ss:seriescmpmap}), we were free to annul the individual
+\htmlref{Mapping}{Mapping} pointers after creating the CmpMap because the pointers held
+internally by the CmpMap increased the reference count (\htmlref{RefCount}{RefCount}
+attribute) of each component Mapping by one. The individual components
+are therefore not deleted by \htmlref{astAnnul}{astAnnul}, but retained until the CmpMap
+itself is deleted and annuls the pointers it holds. Consistent use of
+astAnnul (\secref{ss:annullingpointers}) and/or pointer contexts
+(\secref{ss:contexts}) will therefore ensure that all Objects are
+deleted at the appropriate time.
+
+Note that access to a CmpMap's component Mappings is not generally
+available unless pointers to them are retained when the CmpMap is
+created. If such pointers are retained, then subsequent modifications
+to the individual components can be used to indirectly modify the
+behaviour of the overall CmpMap.
+
+There is an important exception to this, however, because a CmpMap
+retains a copy of the initial \htmlref{Invert}{Invert} flag settings of each of its
+components and uses these in order to ignore any subsequent external
+changes. This means that you may invert either component Mapping
+before inserting it into a CmpMap and need not worry if you un-invert
+it again later. The CmpMap's behaviour will not be affected by the
+later action.
+
+\subsection{\label{ss:complexcmpmap}Creating More Complex Mappings}
+
+Because a \htmlref{CmpMap}{CmpMap} is itself a \htmlref{Mapping}{Mapping}, any existing CmpMap can
+substitute (\secref{ss:objecthierarchy}) as a component Mapping when
+constructing a new CmpMap using \htmlref{astCmpMap}{astCmpMap}. This has the effect of
+nesting one CmpMap inside another and opens up many new possibilities.
+For example, combining three Mappings in series can be accomplished as
+follows:
+
+\small
+\begin{terminalv}
+AstMapping *map1, *map2, *map3;
+
+...
+
+cmpmap = astCmpMap( map1, astCmpMap( map2, map3, 1, "" ), 1, "" );
+\end{terminalv}
+\normalsize
+
+The way in which the individual component Mappings are grouped within
+the nested CmpMaps is not usually important.
+
+A similar technique can be used to combine multiple Mappings in
+parallel and, of course, mixed series and parallel combinations are
+also possible (Figure~\ref{fig:complexcmpmap}).  There is no built-in
+limit to how many CmpMaps may be nested in this way, so this mechanism
+provides an indefinitely extensible method of building complex
+Mappings out of the elemental building blocks provided by AST.
+
+In practice, you might not need to construct such complex CmpMaps
+yourself very frequently, but they will often be returned by AST
+routines.  Nested CmpMaps underlie the library's entire ability to
+represent a wide range of different coordinate transformations.
+
+\subsection{\label{ss:cmpmapexample}Example---Transforming Between Two Calibrated Images}
+
+Consider, as a practical example of CmpMaps, two images of the
+sky. Suppose that for each image we have a \htmlref{Mapping}{Mapping} which converts from
+pixel coordinates to a standard celestial coordinate system, say
+FK5~(J2000.0). If we wish to inter-compare these images, we can do so
+by using this celestial coordinate system to align them. That is, we
+first convert from pixel coordinates in the first image into FK5
+coordinates and we then convert from FK5 coordinates into pixel
+coordinates in the second image.
+
+If ``mapa'' and ``mapb'' are pointers to our two original Mappings, we
+could form a \htmlref{CmpMap}{CmpMap} which transforms directly between the pixel
+coordinates of the first and second images by combining these
+Mappings, as follows:
+
+\small
+\begin{terminalv}
+AstCmpMap *alignmap;
+AstMapping *mapa, *mapb;
+
+...
+
+astInvert( mapb );
+alignmap = astCmpMap( mapa, mapb, 1, "" );
+astInvert( mapb );
+\end{terminalv}
+\normalsize
+
+Here, we have used \htmlref{astInvert}{astInvert} (\secref{ss:invertingmappings}) to invert
+``mapb'' before inserting it into the CmpMap because, as supplied, it
+converted in the wrong direction. Afterwards, we invert it again to
+return it to its original state. The CmpMap, however, will ignore this
+subsequent change (\secref{ss:cmpmapcomponents}).
+
+The forward transformation of the resulting CmpMap will now transform
+from pixel coordinates in the first image to pixel coordinates in the
+second image, while its inverse transformation will convert in the
+opposite direction.
+
+\subsection{\label{ss:overcomplexcmpmaps}Over-Complex Compound Mappings}
+
+While a \htmlref{CmpMap}{CmpMap} provides a very flexible way of constructing
+arbitrarily complex Mappings (\secref{ss:complexcmpmap}), it
+unfortunately also provides an opportunity for representing simple
+Mappings in complex ways. Sometimes, unnecessary complexity can be
+difficult to avoid but can obscure important simplifications.
+
+Consider the example above (\secref{ss:cmpmapexample}), in which we
+inter-related two images of the sky \emph{via} a CmpMap.  If the two
+images turned out to be simply offset from each other by a shift along
+each pixel axis, then this approach would align them correctly, but it
+would be inefficient. This is because it would introduce unnecessary
+and expensive transformations to and from an intermediate celestial
+coordinate system, whereas a simple shift of pixel origin would
+suffice.
+
+Recognising that a simpler and more efficient solution exists
+obviously requires a little more than simply joining two Mappings
+end-to-end. We must also determine whether the resulting CmpMap is
+more complex than it needs to be, \emph{i.e.}\ contains redundant
+information. If it is, we then need a way to simplify it.
+
+The problem is not always just one of efficiency, however. Sometimes
+we may also need to know something about the actual form a \htmlref{Mapping}{Mapping}
+takes---\emph{i.e.}\ the nature of the operations it performs.
+Unnecessary complexity can obscure this, but such complexity can
+easily accumulate during normal data processing.
+
+For example, a Mapping that transforms pixel coordinates into
+positions on the sky might be repeatedly modified as changes are made
+to the shape and size of the image. Typically, on each occasion,
+another Mapping will be concatenated to reflect what has happened to
+the image. This could soon make it difficult to discern the overall
+nature of the transformation from the complex CmpMap that
+accumulates. If only shifts of origin were involved on each occasion,
+however, they could be combined into a single shift which could be
+represented much more simply.
+
+Suppose we now wanted to represent our image's celestial coordinate
+calibration using FITS conventions (\secref{ss:foreignfits}). This
+requires AST to determine whether the Mapping which relates pixel
+coordinate to sky positions conforms to the FITS model (for example,
+whether it is equivalent to applying a single set of shifts and scale
+factors followed by a map projection). Clearly, there is an important
+use here for some means of simplifying the internal structure of a
+CmpMap.
+
+\subsection{\label{ss:simplifyingcmpmaps}Simplifying Compound Mappings}
+
+The ability to simplify compound Mappings is provided by the
+\htmlref{astSimplify}{astSimplify} function. This function encapsulates a number of
+heuristics for converting Mappings, or combinations of Mappings within
+a \htmlref{CmpMap}{CmpMap}, into simpler, equivalent ones. When applied to a CmpMap,
+astSimplify tries to reduce the number of individual Mappings within
+it by merging neighbouring component Mappings together. It will do
+this with both series and parallel combinations of Mappings, or both,
+and will handle CmpMaps nested to any depth
+(\secref{ss:complexcmpmap}).
+
+   To illustrate how astSimplify works, consider the combination of
+   Mappings shown in Figure~\ref{fig:simplifyexample}.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun211_figures/simpexamp}
+   \caption[An over-complex compound Mapping.]{An over-complex compound Mapping, consisting of PermMaps,
+   ZoomMaps and a \htmlref{UnitMap}{UnitMap}, which can be simplified to become a single
+   UnitMap.  The enclosing nested CmpMaps have been omitted for clarity.}
+   \label{fig:simplifyexample}
+   \end{center}
+   \end{figure}
+
+If this were contained in a CmpMap, it could be simplified as follows:
+
+\small
+\begin{terminalv}
+AstMapping *simpler;
+
+...
+
+simpler = astSimplify( cmpmap );
+\end{terminalv}
+\normalsize
+
+In this case, the result would be a simple 3-dimensional UnitMap (the
+identity \htmlref{Mapping}{Mapping}).  To reach this conclusion, astSimplify will have
+made a number of deductions, roughly as follows:
+
+\begin{enumerate}
+\item The two 2-dimensional ZoomMaps in series are equivalent to a
+single \htmlref{ZoomMap}{ZoomMap} with a combined \htmlref{Zoom}{Zoom} factor of unity. This, in turn, is
+equivalent to a 2-dimensional UnitMap.
+
+\item This UnitMap in parallel with the other 1-dimensional UnitMap is
+equivalent to a single 3-dimensional UnitMap. This UnitMap, sandwiched
+between any other pair of Mappings, can then be eliminated.
+
+\item The remaining two PermMaps in series are equivalent to a single
+3-dimensional \htmlref{PermMap}{PermMap}. When these are combined, the resulting PermMap
+is found to be equivalent to a 3-dimensional UnitMap.
+\end{enumerate}
+
+This example is a little contrived, but illustrates how astSimplify
+can deal with even quite complicated compound Mappings through a
+series of incremental simplifications. Where possible, this will
+result in either a simpler compound Mapping or, if feasible, an atomic
+(non-compound) Mapping, as here. If no simplification is possible,
+astSimplify will just return a pointer to the original Mapping.
+
+Although astSimplify cannot identify every simplification that is
+theoretically possible, sufficient rules are included to deal with the
+most common and important cases.
+
+\cleardoublepage
+\section{\label{ss:frames}Representing Coordinate Systems (Frames)}
+
+An AST \htmlref{Frame}{Frame} is an \htmlref{Object}{Object} that is used to represent a coordinate
+system. Contrast this with a \htmlref{Mapping}{Mapping} (\secref{ss:mappings}), which is
+used to describe how to convert between coordinate systems. The two
+concepts are complementary and we will see how they work together in
+\secref{ss:framesets}.
+
+In this section we will discuss only basic Frames, which represent
+Cartesian coordinate systems. More specialised types of Frame
+(\emph{e.g.}\ the \htmlref{SkyFrame}{SkyFrame}, which represents celestial coordinate
+systems, and the \htmlref{SpecFrame}{SpecFrame}, which represents spectral coordinate
+systems) are covered later (\secref{ss:skyframes} and \secref{ss:specframes})
+and, naturally, inherit the properties and behaviour of the simple Frames
+discussed here.
+
+\subsection{The Frame Model}
+
+The best way to think about a \htmlref{Frame}{Frame} is like the frame that you would
+plot around a graph. In two dimensions, you would have an ``$x$'' and
+a ``$y$'' axis, a title on the graph and labels on the axes, together
+with an indication of the physical units being plotted. The values
+marked along each axis would be formatted in a human-readable way. The
+frame around a graph therefore defines a coordinate space within which
+you can locate points, draw lines, calculate distances, \emph{etc.}
+
+An AST Frame works in much the same way, embodying all of these
+concepts and a few more. It also allows any number of axes, which
+means that a Frame can represent coordinate systems with any number of
+dimensions. You specify how many when you create it.
+
+Remember that the basic Frame we are considering here is completely
+general.  It knows nothing of celestial coordinates, for example, and
+all its axes are equivalent. It can be adapted to describe any general
+purpose Cartesian coordinate system by setting its attributes, such as
+its \htmlref{Title}{Title} and axis Labels, \emph{etc.}\ to appropriate values.
+
+\subsection{\label{ss:creatingframes}Creating a Frame}
+
+Creating a \htmlref{Frame}{Frame} is straightforward and follows the usual pattern:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+astFrame *frame;
+
+...
+
+frame = astFrame( 2, "" );
+\end{terminalv}
+\normalsize
+
+The first argument of the \htmlref{astFrame}{astFrame} constructor function specifies the
+number of axes which the Frame should have.
+
+\subsection{\label{ss:frameasmapping}Using a Frame as a Mapping}
+
+We should briefly point out that the \htmlref{Frame}{Frame} we created above
+(\secref{ss:creatingframes}) is also a \htmlref{Mapping}{Mapping}
+(\secref{ss:mappingclass}) and therefore inherits the properties and
+behaviour common to other Mappings.
+
+One way to see this is to set the Frame's \htmlref{Report}{Report} attribute (inherited
+from the Mapping class) to a non-zero value and pass the Frame pointer
+to a coordinate transformation function, such as \htmlref{astTran2}{astTran2}.
+
+\small
+\begin{terminalv}
+double xin[ 5 ] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 };
+double yin[ 5 ] = { 0.0, 2.0, 4.0, 6.0, 8.0, 10.0 };
+double xout[ 5 ];
+double yout[ 5 ];
+
+...
+
+astSet( frame, "Report=1" );
+astTran2( frame, 5, xin, yin, 1, xout, yout );
+\end{terminalv}
+\normalsize
+
+The resulting output might then look like this:
+
+\begin{terminalv}
+(1, 2) --> (1, 2)
+(2, 4) --> (2, 4)
+(3, 6) --> (3, 6)
+(4, 8) --> (4, 8)
+(5, 10) --> (5, 10)
+\end{terminalv}
+
+This is not very exciting because a Frame implements an identity
+transformation just like a \htmlref{UnitMap}{UnitMap}
+(\secref{ss:unitmapexample}). However, it illustrates that a Frame can
+be used as a Mapping and that its \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes are both
+equal to the number of Frame axes.
+
+When we consider more specialised Frames
+(\emph{e.g.}~\secref{ss:framesets}), we will see that using them as
+Mappings can be very useful indeed.
+
+\subsection{\label{ss:frameaxisattributes}Frame Axis Attributes}
+
+Frames have a number of attributes which can take multiple values, one
+for each axis. These separate values are identified by appending the
+axis number in parentheses to the attribute name. For example, the
+Label(1) attribute is a character string containing the label which
+appears on the first axis.
+
+\htmlref{Axis}{Axis} attributes are accessed in the same way as all other attributes
+(\secref{ss:gettingattributes}, \secref{ss:settingattributes} and
+\secref{ss:defaultingattributes}). For example, the Label on the second
+axis might be obtained as follows:
+
+\small
+\begin{terminalv}
+const char *label;
+
+...
+
+label = astGetC( frame, "Label(2)" );
+\end{terminalv}
+\normalsize
+
+Other attribute access functions (astSetX, \htmlref{astTest}{astTest} and \htmlref{astClear}{astClear}) may
+also be applied to axis attributes in the same way.
+
+If the axis number is stored in a program variable, then its value
+must be formatted to generate a suitable attribute name before using
+this to access the attribute itself. For example, the following will
+print out the Label value for each axis of a \htmlref{Frame}{Frame}:
+
+\small
+\begin{terminalv}
+#include <stdio.h>
+char name[ 18 ];
+int iaxis, naxes;
+
+...
+
+naxes = astGetI( frame, "Naxes" );
+for ( iaxis = 1; iaxis <= naxes; iaxis++ ) {
+   (void) sprintf( name, "Label(%d)", iaxis );
+   label = astGetC( frame, name );
+   (void) printf( "Label %2d: %s\n", iaxis, label );
+}
+\end{terminalv}
+\normalsize
+
+Note the use of the \htmlref{Naxes}{Naxes} attribute to determine the number of Frame
+axes.
+
+The output from this might look like the following:
+
+\begin{terminalv}
+Label  1: Axis 1
+Label  2: Axis 2
+\end{terminalv}
+
+In this case, the Frame's default axis Labels have been revealed as
+rather un-exciting. Normally, you would set much more useful values,
+typically when you create the Frame---perhaps something like:
+
+\small
+\begin{terminalv}
+frame = astFrame( 2, "Label(1)=Offset from centre of field,"
+                     "Unit(1) =mm,"
+                     "Label(2)=Transmission coefficient,"
+                     "Unit(2) =%" );
+\end{terminalv}
+\normalsize
+
+Here, we have also set the (character string) Unit attribute for each
+axis to describe the physical units represented on that axis. All the
+attribute assignments have been combined into a single string,
+separated by commas.
+
+\subsection{\label{ss:frameattributes}Frame Attributes}
+
+We will now briefly outline the various attributes associated with a
+\htmlref{Frame}{Frame} (this is, of course, in addition to those inherited from the
+\htmlref{Mapping}{Mapping} class). We will not delve too deeply into the details of each
+attribute, for which you should consult the appropriate description in
+\appref{ss:attributedescriptions}. Instead, we aim simply to sketch
+the range of facilities available:
+
+\begin{quote}
+\begin{description}
+\item[\htmlref{Naxes}{Naxes}]\mbox{}\\
+A read-only integer giving the number of Frame axes.
+
+\item[\htmlref{Title}{Title}]\mbox{}\\
+A string describing the coordinate system which the Frame represents.
+
+\item[\htmlref{Label(axis)}{Label(axis)}]\mbox{}\\
+A label string for each axis.
+
+\item[\htmlref{Unit(axis)}{Unit(axis)}]\mbox{}\\
+A string describing the physical units on each axis. You can choose
+whether to make this attribute ``active'' or ``passive'' (using
+\htmlref{astSetActiveUnit}{astSetActiveUnit}
+). If active, its value will be taken into account when finding the
+Mapping between two Frames (\emph{e.g.} a scaling of 0.001 would be used
+to connect two axis with units of ``km'' and ``m''). If passive, its value
+is ignored. Its use is described in more detail in \secref{ss:frameunits}.
+
+\item[\htmlref{Symbol(axis)}{Symbol(axis)}]\mbox{}\\
+A string containing a ``short form'' symbol (\emph{e.g.}\ like ``X''
+or ``Y'') used to represent the quantity plotted on each axis.
+
+\item[\htmlref{Digits/Digits(axis)}{Digits/Digits(axis)}]\mbox{}\\
+The preferred number of digits of precision to be used when formatting
+values for display on each axis.
+
+\item[\htmlref{Format(axis)}{Format(axis)}]\mbox{}\\
+A string containing a \emph{format specifier} which determines exactly
+how values should be formatted for display on each axis
+(\secref{ss:formattingaxisvalues}). If this attribute is un-set, the
+formatting is based on the Digits value, otherwise the Format string
+over-rides the Digits value.
+
+\item[\htmlref{Direction(axis)}{Direction(axis)}]\mbox{}\\
+A boolean (integer) value which indicates in which direction each axis
+should be plotted. If it is non-zero (the default), the axis should be
+plotted in the conventional direction---\emph{i.e.}\ increasing to the
+right for the abscissa and increasing upwards for the ordinate. If it
+is zero, the axis should be plotted in reverse.  This attribute is
+provided as a hint only and programs are free to ignore it if they
+wish.
+
+\item[\htmlref{Domain}{Domain}]\mbox{}\\
+A character string which identifies the \emph{physical domain} to
+which the Frame's coordinate system applies. The primary purpose of
+this attribute is to prevent unwanted conversions from occurring
+between coordinate systems which are not related. Its use is described
+in more detail in \secref{ss:framedomains}.
+
+\item[\htmlref{System}{System}]\mbox{}\\
+A character string which identifies the specific coordinate system used
+to describe positions within the physical domain represented by the Frame.
+For a simple Frame, this attribute currently has a fixed value of
+``Cartesian'', but could in principle be extended to include options such
+as ``Polar'', ``Cylindrical'', \emph{etc}. More specialised Frames such
+as the \htmlref{SkyFrame}{SkyFrame}, \htmlref{TimeFrame}{TimeFrame}  and \htmlref{SpecFrame}{SpecFrame}, re-define the allowed values to be
+appropriate to the domain which they describe. For instance, the SkyFrame
+allows values such as ``FK4'' and ``Galactic'', and the SpecFrame allows
+values such as ``frequency'' and ``wavelength''.
+
+\item[\htmlref{Epoch}{Epoch}]\mbox{}\\
+This value is used to qualify a coordinate system by giving the moment in
+time when the coordinates are correct. Usually, this will be the date of
+observation. The Epoch value is important in cases where coordinates
+systems move with respect to each other over time. An example of two such
+coordinate systems are the FK4 and FK5 celestial coordinate systems.
+
+\item[\htmlref{ObsLon}{ObsLon}]\mbox{}\\
+Specifies the longitude of the observer (assumed to be on the surface of
+the earth). The basic Frame class does not use this value, but
+specialised sub-classes may. For instance, the SpecFrame class uses it to
+calculate the relative velocity of the observer and the centre of the
+earth for use in converting between standards of rest.
+
+\item[\htmlref{ObsLat}{ObsLat}]\mbox{}\\
+Specifies the latitude of the observer. Use in conjunction with ObsLon.
+
+\end{description}
+\end{quote}
+
+There are also some further Frame attributes, not described above,
+which are important when Frames are used as templates to search for
+other Frames. Their use goes beyond the present discussion.
+%TBW---Add reference here.
+
+\subsection{\label{ss:formattingaxisvalues}Formatting Axis Values}
+
+The coordinate values associated with each axis of a \htmlref{Frame}{Frame} are stored
+(\emph{e.g.}\ within your program) as double values. The Frame class
+therefore provides a function, \htmlref{astFormat}{astFormat}, to convert these values into
+formatted strings for display:
+
+\small
+\begin{terminalv}
+const char *string
+double value;
+
+...
+
+string = astFormat( frame, iaxis, value );
+\end{terminalv}
+\normalsize
+
+Here, the astFormat function is passed a Frame pointer, the number of
+an axis (``iaxis'') and a double precision value to format
+(``value''). It returns a pointer to character string containing the
+formatted value.
+\label{ss:formattingwithdigits}
+
+By default, the formatting applied will be determined by the Frame's
+Digits attribute and will normally display results with seven digits
+of precision (corresponding approximately to the C ``float'' data type
+on many machines). Setting a different Digits value, however, allows
+you to adjust the precision as necessary to suit the accuracy of the
+coordinate data you are processing.  If finer control is needed, it is
+also possible to set a Digits value for each individual axis by
+appending an axis number to the attribute name
+(\emph{e.g.}\ ``Digits(2)''). If this is done, it over-rides the
+effect of the Frame's main Digits value for that axis.
+
+Even finer control is possible by setting the (character string) Format
+attribute for a Frame axis. The string given should contain a C
+\emph{format specifier} which explicitly determines how the values on
+that axis should be formatted. This will over-ride the effects of any
+Digits value\footnote{The exception to this rule is that if the Format
+value includes a precision of ``$.*$'', then Digits will be used to
+determine the actual precision used.}. Any valid ``printf'' format
+specifier may be used so long as it consumes exactly one double value.
+
+When setting Format values, remember that the ``\%'' which appears in
+the format specifier may need to be doubled to ``\%\%'' if you are
+using a function (such as \htmlref{astSet}{astSet}) which interprets ``printf'' format
+specifiers itself.
+
+It is recommended that you use astFormat whenever you display
+formatted coordinate values, even although you could format them
+yourself using ``sprintf''. This is because it puts the Frame in
+control of formatting. When you start to handle more elaborate Frames
+(representing, say, celestial coordinates), you will need different
+formatting methods. This approach delivers them without any change to
+your software.
+
+You should also consider regularly using the \htmlref{astNorm}{astNorm} function,
+described below (\secref{ss:normalising}), for any values that will be
+made visible to the user of your software.
+
+\subsection{\label{ss:normalising}Normalising Frame Coordinates}
+
+The function \htmlref{astNorm}{astNorm} is provided to cope with the fact that some
+coordinate systems do not extend indefinitely in all directions. Some
+may have boundaries, outside which coordinates are meaningless, while
+others wrap around on themselves, so that after a certain distance you
+return to the beginning again (coordinate systems based on circles and
+spheres, for instance). A basic \htmlref{Frame}{Frame} has no such complications, but
+other more specialised Frames (such as SkyFrames, representing the
+celestial sphere---\secref{ss:skyframes}) do.
+
+The role played by astNorm is to \emph{normalise} any arbitrary set of
+coordinates by converting them into a set which is ``within bounds'',
+interpreted according to the particular Frame in question. For
+example, on the celestial sphere, a right ascension value of 24~hours
+or more can have a suitable multiple of 24~hours subtracted without
+affecting its meaning and astNorm would perform this task. Similarly,
+negative values of right ascension would have a multiple of 24~hours
+added, so that the result lies in the range zero to 24~hours. The
+coordinates in question are modified in place by astNorm, as follows:
+
+\small
+\begin{terminalv}
+double point[ 2 ];
+
+...
+
+astNorm( frame, point );
+\end{terminalv}
+\normalsize
+
+If the coordinates supplied are initially OK, as they would always be
+with a basic Frame, then they are returned unchanged.
+
+Because the main purpose of astNorm is to convert coordinates into the
+preferred range for human consumption, its use is almost always
+appropriate immediately before formatting coordinate values for
+display using \htmlref{astFormat}{astFormat} (\secref{ss:formattingaxisvalues}). Even if
+the Frame in question does not restrict the range of coordinates, so
+that astNorm does nothing, using it will allow you to process other
+more specialised Frames, where normalisation is important, without
+changing your software.
+
+\subsection{\label{ss:unformattingaxisvalues}Reading Formatted Axis Values}
+
+The process of converting a formatted coordinate value for a \htmlref{Frame}{Frame}
+axis, such as might be produced by \htmlref{astFormat}{astFormat}
+(\secref{ss:formattingaxisvalues}), back into a numerical (double)
+value ready for processing is performed by \htmlref{astUnformat}{astUnformat}.  However,
+although this process is essentially the inverse of that performed by
+astFormat, there are a number of additional difficulties that must be
+addressed in practice.
+
+The main use for astUnformat is in reading formatted coordinate values
+which have been entered by the user of a program, or read from a
+file. As such, we can rarely assume that the values are neatly
+formatted in the way that astFormat would produce. Instead, it is
+usually desirable to allow considerable flexibility in the form of
+input that can be accommodated, so as to permit ``free-format'' data
+input by the user. In addition, we may need to extract individual
+coordinate values embedded in other textual data.
+
+Underlying these requirements is the root difficulty that the textual
+format used to represent a coordinate value will depend on the class
+of Frame we are considering. For example, for a basic Frame,
+astUnformat may have to read a value like ``1.25e-6'', whereas for a
+more specialised Frame representing celestial coordinates it may have
+to handle a value like ``-07d~49m~13s''. Of course, the format might
+also depend on which axis is being considered.
+
+Ideally, we would like to write software that can handle any kind of
+Frame. However, this makes it a little more difficult to analyse
+textual input data to extract individual coordinate values, since we
+cannot make assumptions about how the values are formatted. It would
+not be safe, for example, simply to assume that the values being read
+are separated by white space. This is not just because they might be
+separated by some other character, but also because celestial
+coordinate values might themselves contain spaces. In fact, to be
+completely safe, we cannot make any assumptions about how a formatted
+coordinate value is separated from the surrounding text, except that
+it should be separated in some way which is not ambiguous.
+
+This is the very basic assumption upon which astUnformat works. It is
+invoked as follows:
+
+\small
+\begin{terminalv}
+int n;
+
+...
+
+n = astUnformat( frame, iaxis, string, &value );
+\end{terminalv}
+\normalsize
+
+It is supplied with a Frame pointer (``frame''), the number of an axis
+(``iaxis'') and a character string to be read (``string''). If it
+succeeds in reading a value, astUnformat returns the resulting
+coordinate to the address supplied \emph{via} the final argument
+(``\&value''). The returned function value indicates how many
+characters were read from the string in order to obtain this result.
+
+The string is read as follows:
+
+\begin{enumerate}
+\item Any white space at the start is skipped over.
+
+\item Further characters are considered, one at a time, until the next
+character no longer matches any of the acceptable forms of input
+(given the characters that precede it). The longest sequence of
+characters which matches is then considered ``read''.
+
+\item If a suitable sequence of characters was read successfully, it
+is converted into a coordinate value which is returned. Any white
+space following this sequence is then skipped over and the total
+number of characters consumed is returned as the function value.
+
+\item If the sequence of characters read is empty, or insufficient to
+define a coordinate value, then the string does not contain a value to
+read. In this case, the read is aborted and astUnformat returns a
+function value of zero and no coordinate value.  However, it returns
+without error.
+\end{enumerate}
+
+Note that failing to read a coordinate value does not constitute an
+error, at least so far as astUnformat is concerned. However, an error
+can occur if the sequence of characters read appears to have the
+correct form but cannot be converted into a valid coordinate
+value. Typically, this will be because it violates some constraint,
+such as a limit on the value of one of its fields. The resulting error
+message will give details.
+
+For any given Frame axis, astUnformat does not necessarily always use
+the same algorithm for converting the sequence of characters it reads
+into a coordinate value. This is because some forms of input
+(particularly free-format input) can be ambiguous and might be
+interpreted in several ways depending on the context. For example, the
+celestial longitude ``12:34:56.7'' could represent an angle in degrees
+or a right ascension in hours. To decide which to use, astUnformat may
+examine the Frame's attributes and, in particular, the appropriate
+\htmlref{Format(axis)}{Format(axis)} string which is used by astFormat when formatting
+coordinate values (\secref{ss:formattingaxisvalues}). This is done in
+order that astFormat and astUnformat should complement each other---so
+that formatting a value and then un-formatting it will yield the
+original value, subject to any rounding error.
+
+To give a simple (but crucially incomplete!) example, consider reading
+a value for the axis of a basic Frame, as follows:
+
+\small
+\begin{terminalv}
+n = astUnformat( frame, iaxis, " 1.5e6   -99.0", &value );
+\end{terminalv}
+\normalsize
+
+astUnformat will skip over the initial space in the string supplied
+and then examine each successive character. It will accept the
+sequence ``1.5e6'' as input, but reject the space which follows
+because it does not form part of the format of a floating point
+number. It will then convert the characters ``1.5e6'' into a
+coordinate value and skip over the three spaces which follow them. The
+returned function value will therefore be 9, equal to the total number
+of characters consumed. This result may be used to address the string
+during a subsequent read, so as to commence reading at the start of
+``-99.0''.
+
+Most importantly, however, note that if the user of a program
+mistakenly enters the string ``~1.5r6\ldots'' instead of
+``~1.5e6\ldots'', a coordinate value of 1.5 and a function result of 4
+will be returned, because the ``r'' would prematurely terminate the
+attempt to read the value. Because this sort of mistake does not
+automatically result in an error but can produce incorrect results, it
+is \textbf{vital} to check the returned function value to ensure that
+the expected number of characters have been read.\footnote{Anyone who
+seriously uses the C run time library ``scanf'' function will know
+about the need for this check!}  For example, if the string is
+expected to contain exactly one value, and nothing else, then the
+following would suffice:
+
+\small
+\begin{terminalv}
+n = astUnformat( frame, iaxis, string, &value );
+if ( astOK ) {
+   if ( string[ n ] || !n ) {
+      <error in input data>
+   } else {
+      <value read correctly>
+   }
+}
+\end{terminalv}
+\normalsize
+
+If astUnformat does not detect an error itself, we check that it has
+read to the end-of-string and consumed at least one character (which
+traps the case of a zero-length input string). If this reveals an
+error, the value of ``n'' indicates where it occurred.
+
+Another common requirement is to obtain a position by reading a list
+of coordinates from a string which contains one value for each axis of
+a Frame. We assume that the values are separated in some unambiguous
+manner, perhaps using white space and/or some unspecified
+single-character separator. The choice of separator is up to the data
+supplier, who must choose it so as not to conflict with the format of
+the coordinate values, but our software does not need to know what it
+is. The following is a template algorithm for reading data in this
+form:
+
+\small
+\begin{terminalv}
+const char *s;
+double values[ 10 ];
+
+...
+
+/* Initialise a string pointer. */
+s = string;
+
+/* Obtain the number of Frame axes and loop through them. */
+naxes = astGetI( frame, "Naxes" );
+for ( iaxis = 1; iaxis <= naxes; iaxis++ ) {
+
+/* Attempt to read a value for this axis. */
+   n = astUnformat( frame, iaxis, s, &values[ iaxis - 1 ] );
+
+/* If nothing was read and this is not the first axis or the
+   end-of-string, try stepping over a separator and reading again. */
+   if ( !n && ( iaxis > 1 ) && *s )
+      n = astUnformat( frame, iaxis, ++s, &values[ iaxis - 1 ] );
+
+/* Quit if nothing was read, otherwise move on to the next value. */
+   if ( !n ) break;
+   s += n;
+}
+
+/* Check for possible errors. */
+if ( astOK ) {
+   if ( *s || !n ) {
+      <error in input data>
+   } else {
+      <values read correctly>
+   }
+}
+\end{terminalv}
+\normalsize
+
+In this case, ``s'' will point to the location of any input error.
+
+Note that this algorithm is insensitive to the precise format of the
+data and will therefore work with any class of Frame and any
+reasonably unambiguous input data. For example, here is a range of
+suitable input data for a 3-dimensional basic Frame:
+
+\small
+\begin{terminalv}
+1 2.5 3
+3.1,3.2,3.3
+1.5, 2.6, -9.9e2
+-1.1+0.4-1.8
+    .1/.2/.3
+ 44.0 ; 55.1 -14
+\end{terminalv}
+\normalsize
+
+\subsection{\label{ss:permutingaxes}Permuting Frame Axes}
+
+Once a \htmlref{Frame}{Frame} has been created, it is not possible to change the number
+of axes it contains, but it is possible to change the order in which
+these axes occur. To do so, an integer \emph{permutation array} is
+filled with the numbers of the axes so as to specify the new order,
+\emph{e.g.:}
+
+\small
+\begin{terminalv}
+int perm[ 2 ] = { 2, 1 };
+\end{terminalv}
+\normalsize
+
+In this case, the axes of a 2-dimensional Frame could be interchanged
+by passing this permutation array to the \htmlref{astPermAxes}{astPermAxes} function. That
+is, an ($x_1,x_2$) coordinate system would be changed into an
+($x_2,x_1$) coordinate system by:
+
+\small
+\begin{terminalv}
+astPermAxes( frame, perm );
+\end{terminalv}
+\normalsize
+
+If the axes are permuted more than once, the effects are cumulative.
+You are, of course, not restricted to Frames with only two axes.
+
+\subsection{Selecting Frame Axes}
+
+An alternative to changing the number of \htmlref{Frame}{Frame} axes, which is not
+allowed, is to create a new Frame by selecting axes from an existing
+one. The method of doing this is very similar to the way \htmlref{astPermAxes}{astPermAxes}
+is used (\secref{ss:permutingaxes}), in that we supply an integer
+array filled with the numbers of the axes we want, in their new
+order. In this case, however, the number of array elements need not
+equal the number of Frame axes.
+
+For example, we could select axes 3 and 2 (in that order) from a
+3-dimensional Frame as follows:
+
+\small
+\begin{terminalv}
+astFrame *frame1, *frame2;
+astMapping *mapping;
+int pick[ 2 ] = { 3, 2 };
+
+...
+
+frame2 = astPickAxes( frame1, 2, pick, &mapping );
+\end{terminalv}
+\normalsize
+
+This would return a pointer to a 2-dimensional Frame (``frame2'')
+which contains the information associated with axes 3 and 2, in that
+order, from the original Frame (``frame1''). The original Frame is not
+altered by this process. Beware, however, that the axis information
+may still be shared by both Frames, so if you wish to alter either of
+them independently you may first need to use \htmlref{astCopy}{astCopy}
+(\secref{ss:copyingobjects}) to make an independent copy.
+
+In addition to the new Frame pointer, \htmlref{astPickAxes}{astPickAxes} will also return a
+pointer to a new \htmlref{Mapping}{Mapping} \emph{via} its fourth argument (you may supply a
+NULL pointer as an argument if you do not want this Mapping).  This
+Mapping will inter-relate the two Frames. By this we mean that its
+forward transformation will convert coordinates originally in the
+coordinate system represented by ``frame1'' into that represented by
+``frame2'', while its inverse transformation will convert in the
+opposite direction. In this particular case, the Mapping would be a
+\htmlref{PermMap}{PermMap} (\secref{ss:permmapexample}) and would implement the following
+transformations:
+
+\begin{terminalv}
+Forward:
+   (1, 2, 3) --> (3, 2)
+   (2, 4, 6) --> (6, 4)
+   (3, 6, 9) --> (9, 6)
+   (4, 8, 12) --> (12, 8)
+   (5, 10, 15) --> (15, 10)
+
+Inverse:
+   (3, 2) --> (<bad>, 2, 3)
+   (6, 4) --> (<bad>, 4, 6)
+   (9, 6) --> (<bad>, 6, 9)
+   (12, 8) --> (<bad>, 8, 12)
+   (15, 10) --> (<bad>, 10, 15)
+\end{terminalv}
+
+This is our first introduction to the idea of inter-relating pairs of
+Frames \emph{via} a Mapping, but this will assume a central role later on.
+
+Note that when using astPickAxes, it is also possible to request more
+axes than there were in the original Frame. This will involve
+selecting axes from the original Frame that do not exist. To do this,
+the corresponding axis number (in the ``pick'' array) should be set to
+zero and the effect is to introduce an additional new axis which is
+not derived from the original Frame. This axis will have default
+values for all its attributes. You will need to do this because
+astPickAxes does not allow you to select any of the original axes more
+than once.\footnote{It will probably not be obvious why this
+restriction is necessary, but consider creating a Frame with one
+longitude axis and two latitude axes. Which latitude axis should be
+associated with the longitude axis?}
+
+\subsection{\label{ss:distanceandoffset}Calculating Distances, Angles and Offsets}
+Some complementary
+functions
+are provided for use with Frames to allow you to perform geometric
+operations without needing to know the nature of the coordinate system
+represented by the \htmlref{Frame}{Frame}.
+
+Functions
+can be used to find the distance between two points, and to offset a
+specified distance along a line joining two points, \emph{etc.} In essence,
+these define the metric of the coordinate space which the Frame represents. In
+the case of a basic Frame, this is a Cartesian metric.
+
+The first of these functions, \htmlref{astDistance}{astDistance}, returns a double distance
+value when supplied with the Frame coordinates of two points. For
+example:
+
+\small
+\begin{terminalv}
+double dist;
+double point1[ 2 ] = { 0.0, 0.0 };
+double point2[ 2 ] = { 1.0, 1.0 };
+
+...
+
+dist = astDistance( frame, point1, point2 );
+\end{terminalv}
+\normalsize
+
+This calculates the distance between the origin (0,0) and a point at
+position (1,1). In this case, the result, as you would expect, is
+$\surd{2}$. However, this is only true for the Cartesian coordinate
+system which a basic Frame represents. In general, astDistance will
+calculate the geodesic distance between the two points, so that with a
+more specialised Frame (such as a \htmlref{SkyFrame}{SkyFrame}, representing the celestial
+sphere) a great-circle distance might be returned.
+
+The \htmlref{astOffset}{astOffset} function is really the inverse of astDistance. Given two
+points in a Frame, it calculates the coordinates of a third point
+which is offset a specified distance away from the first point along
+the geodesic joining it to the second one. For example:
+
+\small
+\begin{terminalv}
+double point1[ 2 ] = { 0.0, 0.0 };
+double point2[ 2 ] = { 1.0, 1.0 };
+double point3[ 2 ];
+
+...
+
+astOffset( frame, point1. point2, 0.5, point3 );
+\end{terminalv}
+\normalsize
+
+This would fill the ``point3'' array with the coordinates of a point
+which is offset 0.5 units away from the origin (0,0) in the direction
+of the position (1,1). Again, this is a simple result in a Cartesian
+Frame, as varying the offset will trace out a straight line. On the
+celestial sphere, however (\emph{e.g.}\ using a SkyFrame), it would
+trace out a great circle.
+
+The functions \htmlref{astAxDistance}{astAxDistance} and \htmlref{astAxOffset}{astAxOffset} are similar to astDistance
+and astOffset, except that the curves which they use as ``straight
+lines'' are not geodesics, but curves parallel to a specified axis\footnote
+{For instance, a line of constant Declination is not a geodesic}. One
+reason for using these functions is to deal with the cyclic ambiguity of
+longitude and latitude axes.
+
+The \htmlref{astOffset2}{astOffset2} function is similar to astOffset, but instead of using the
+geodesic which passes through two positions, it uses the geodesic which
+passes at a given position angle through the starting position.
+
+Position angles are always measured from the positive direction of the
+second Frame axis to the required line, with positive angles being in the
+same sense as rotation from the positive direction of the second axis to
+the positive direction of the first Frame axis. This definition applies
+to all classes of Frame, including SkyFrame. The default ordering of axes
+in a SkyFrame makes the second axis equivalent to north, and so the
+definition of position angle given above corresponds to the normal
+astronomical usage, ``from north, through east''. However, it should be
+remembered that it is possible to permute the axes of a SkyFrame (or
+indeed any Frame), so that north becomes axis 1. In this case, an AST
+``position angle'' would be the angle ``from east, through north''.
+Always take the axis ordering into account when deriving an astronomical
+position angle from an AST position angle.
+
+Within a Cartesian coordinate system, the position angle of a geodesic
+(\emph{i.e.}\ a straight line) is constant along its entire length, but
+this is not necessarily true of other coordinate systems. Within a
+spherical coordinate system, for instance, the position angle of a geodesic
+will vary along its length (except for the special cases of a meridian and
+the equator). In addition to returning the required offset position, the
+astOffset2 function
+returns the position angle of the geodesic at the
+offset position. This is useful if you want to trace out a path which
+involves turning through specified angles. For instance, tracing out a
+rectangle in which each side is a geodesic involves turning through 90
+degrees at the corners. To do this, use astOffset2 to calculate the
+position of each corner, and then add (or subtract) 90 degrees from the
+position angle returned by astOffset2.
+
+The \htmlref{astAngle}{astAngle} function
+calculates the angle subtended by two points, at a third point.
+If used with a 2-dimensional Frame the returned angle
+is signed to indicate the sense of rotation (clockwise or anti-clockwise)
+in taking the ``shortest route'' from the first point to the second.
+If the Frame has more than 2 axes, the result is un-signed and is always
+in the range zero to $\pi$.
+
+The \htmlref{astAxAngle}{astAxAngle} function is similar to astAngle,
+but the ``reference direction'', from which angles are measured, is
+a specified axis.
+
+The \htmlref{astResolve}{astResolve} function
+resolves a given displacement within a Frame into two components, parallel and
+perpendicular to a given reference direction.
+
+The displacement is specified by two positions within the Frame; the
+starting and ending positions. The reference direction is defined by the
+geodesic curve passing through the starting position and a third specified
+position. The lengths of the two components are returned, together with
+the position on the reference geodesic which is closest to the third
+supplied point.
+
+\subsection{\label{ss:framedomains}The Domain Attribute}
+
+The \htmlref{Domain}{Domain} attribute is one of the most important properties of a
+\htmlref{Frame}{Frame}, although the concept it expresses can sometimes seem a little
+subtle.  We will introduce it here, but its true value will probably
+not become apparent until later (\secref{ss:framesetconverting}).
+
+To understand the need for the Domain attribute, consider using
+different Frames to represent the following different coordinate
+systems associated with a CCD image:
+
+\begin{enumerate}
+\item A coordinate system based on pixel numbers.
+
+\item Positions on the CCD chip, measured in $\mu$m.
+
+\item Positions in the focal plane of the telescope, measured in mm.
+
+\item A celestial coordinate system, measured in radians.
+\end{enumerate}
+
+If we had two such CCD images, we might legitimately want to align
+them pixel-for-pixel (\emph{i.e.}\ using the coordinate system based
+on pixel numbers) in order to, say, divide by a flat-field exposure.
+We might similarly consider aligning them using any of the other
+coordinate systems so as to achieve different results. For example, we
+might consider merging separate images from a CCD mosaic by using
+focal plane positions.
+
+It would obviously not be legitimate, however, to directly compare
+positions in one image measured in pixels with positions in the other
+measured in mm, nor to equate chip positions in $\mu$m with sky
+coordinates in radians. If we wanted to inter-compare these
+coordinates, we would need to do it indirectly, using other
+information based on the experimental set-up. For instance, we might
+need to know the size of the pixels expressed in mm and the
+orientation of the CCD chip in the focal plane.
+
+Note that it is not simply the difference in physical units which
+prevents certain coordinates from being directly inter-compared
+(because the appropriate unit scaling factors could be included
+without any additional information). Neither is it the fact that
+different coordinate systems are in use (because we could legitimately
+inter-compare two different celestial coordinate systems without any
+extra information).  Instead, it is the different nature of the
+coordinate spaces to which these coordinate systems have been applied.
+
+We normally express this by saying that the coordinate systems apply
+to different \emph{physical domains}. Although we may establish
+\emph{ad hoc} relationships between coordinates in different physical
+domains, they are not intrinsically related to each other and we need
+to supply extra information before we can convert coordinates between
+them.
+
+In AST, the role of the (character string) Domain attribute is to
+assign Frames to their respective physical domains. The way it
+operates is as follows:
+
+\begin{itemize}
+\item Coordinate systems which apply to the same physical domain
+(\emph{i.e.}\ whose Frames have the same Domain value) can be directly
+inter-compared.
+
+If the domain has several coordinate systems associated with it
+(\emph{e.g.}\ the celestial sphere), then a coordinate conversion may
+be involved. Otherwise, coordinate values may simply be equated.
+
+\item Coordinate systems which apply to different physical domains
+(\emph{i.e.}\ whose Frames have different Domain values) cannot be
+directly inter-compared.
+
+If any relationship does exist between such coordinate systems---and
+it need not---then additional information must be supplied in order to
+establish the relationship between them in any particular case. We
+will see later (\secref{ss:framesets}) how to establish such
+relationships between Frames in different domains.
+\end{itemize}
+
+With the basic Frames we are considering here, each physical domain only
+has a single (Cartesian) coordinate system associated with it, so that if
+two such Frames have the same Domain value, their coordinate systems will
+be identical and may simply be equated. With more specialised Frames,
+however, more than one coordinate system may apply to each domain. In
+such cases, a coordinate conversion may need to be performed.
+
+When a basic Frame is created, its Domain attribute defaults to an
+empty string. This means that all such Frames belong to the same
+(null) domain by default and therefore describe the same unspecified
+physical coordinate space. In order to assign a Frame to a different
+domain, you simply need to set its Domain value. This is normally most
+conveniently done when it is created, as follows:
+
+\small
+\begin{terminalv}
+frame1 = astFrame( 2, "Domain=CCD_CHIP,"
+                      "Unit(1)=micron,"
+                      "Unit(2)=micron" );
+frame2 = astFrame( 2, "Domain=FOCAL_PLANE,"
+                      "Unit(1)=mm,"
+                      "Unit(2)=mm" );
+\end{terminalv}
+\normalsize
+
+Here, we have created two Frames in different physical
+domains. Although their coordinate values all have units of length,
+they cannot be directly inter-compared (because their axes may be
+rotated with respect to each other, for instance).
+
+All Domain values are automatically converted to upper case and white
+space is removed, but there are no other restrictions on the names you
+may use to label different physical domains. From a practical point of
+view, however, it is worth following a few conventions
+(\secref{ss:domainconventions}).
+
+\subsection{\label{ss:domainconventions}Conventions for Domain Names}
+
+When choosing a value for the \htmlref{Domain}{Domain} attribute of a \htmlref{Frame}{Frame}, it
+obviously makes sense to avoid generic names which might clash with
+those used for similar (but subtly different!) purposes by other
+programmers. If you are developing software for an instrument, for
+example, and want to identify an instrumental coordinate system, then
+it is sensible to add a distinguishing prefix. For instance, you might
+use $<$INST$>$\_FOCAL\_PLANE, where $<$INST$>$ (\emph{e.g.}\ an
+acronym) identifies your instrument.
+
+For some purposes, however, a standard choice of Domain name is
+desirable so that different items of software can communicate. For
+this purpose, the following Domain names are reserved by AST and the
+use recommended below should be carefully observed:
+
+\begin{quote}
+\begin{description}
+\item[GRAPHICS]\mbox{}\\
+Identifies the coordinate space used by an underlying computer
+graphics system to specify plotting operations. Typically, when
+performing graphical operations, AST is used to define additional
+coordinate systems which are related to these ``native'' graphical
+coordinates.  Plotting may be carried out in any of these coordinate
+systems, but the GRAPHICS domain identifies the native coordinates
+through which AST communicates with the underlying graphics system.
+
+\item[GRID]\mbox{}\\
+Identifies the instantaneous \emph{data grid} used to store and handle
+data, together with an associated coordinate system. In this
+coordinate system, the first element stored in an array of data always
+has a coordinate value of unity at its centre and all elements have
+unit extent. This applies to all dimensions.
+
+If data are copied or transformed to a new data grid (by whatever
+means), or a subset of the original grid is extracted, then the same
+rules apply to the copy or subset. Its first element therefore has
+GRID coordinate values of unity at its centre. Note that this means
+that GRID coordinates remain attached to the first element of the data
+grid and not to its data content (\emph{e.g.}\ the features in an
+image).
+
+\item[PIXEL]\mbox{}\\
+Identifies an array of pixels and an associated \emph{pixel-based}
+coordinate system which is related to the GRID coordinate system
+(above) simply by a shift of origin along each axis. This shift may be
+integral, fractional, positive, negative or zero. The data elements
+retain their unit extent along each axis.
+
+Because the amount of shift is unspecified, the PIXEL domain is
+distinct from the GRID domain. The relationship between them contains
+a degree of uncertainty, such as typically arises from the different
+conventions used by different software systems. For instance, in some
+software the first pixel is regarded as being centred at (1,1), while
+in other software it is at (0.5,0.5). In addition, some software
+packages implement a ``pixel origin'' which allows pixel coordinates
+to start at an arbitrary value.
+
+The GRID domain (which corresponds with the pixel-numbering convention
+used by FITS) is a special case of the PIXEL domain and avoids this
+uncertainty. In general, additional information is required in order
+to convert from one to the other.
+
+\item[SKY]\mbox{}\\
+Identifies the domain which contains all equivalent celestial
+coordinate systems. Because these are represented in AST by SkyFrames
+(\secref{ss:skyframes}), it should be no surprise that the default
+Domain value for a \htmlref{SkyFrame}{SkyFrame} is SKY. Since there is only one sky, you
+probably won't need to change this very often.
+
+\item[SPECTRUM]\mbox{}\\
+Identifies the domain used to describe positions within an
+electro-magnetic spectrum. The AST \htmlref{SpecFrame}{SpecFrame} (\secref{ss:specframes})
+class describes positions within this domain, allowing a wide range of
+different coordinate systems to be used (frequency, wavelength,
+\emph{etc}). The default Domain value for a SpecFrame is SPECTRUM.
+
+\item[TIME]\mbox{}\\
+Identifies the domain used to describe moments in time. The AST \htmlref{TimeFrame}{TimeFrame}
+class describes positions within this domain, allowing a wide range of
+different coordinate systems and timescales to be used. The default Domain
+value for a TimeFrame is TIME.
+
+\end{description}
+\end{quote}
+
+Although we have drawn a necessary distinction here between the GRID
+and PIXEL domains, we will continue to refer in general terms to image
+``pixels'' and ``pixel coordinates'' whenever this distinction is not
+important. This should not be taken to imply that the GRID convention
+for numbering pixels is excluded---in fact, it is usually to be
+preferred (at the level of data handling being discussed in this
+document) and we recommend it.
+
+\subsection{\label{ss:frameunits}The Unit Attribute}
+Each axis of a \htmlref{Frame}{Frame} has a Unit attribute which holds the physical units used
+to describe positions on the axis. The index of the axis to which the
+attribute refers should normally be placed in parentheses following the
+attribute name (``Unit(2)'' for instance). However, if the Frame has only
+a single axis, then the axis index can be omitted.
+
+In versions of AST prior to version 2.0, the Unit attribute was nothing
+more than a descriptive string intended purely for human readers---no
+part of the AST system used the Unit string for any purpose (other than
+inclusion in axis labels produced by the \htmlref{Plot}{Plot} class). In particular, no
+account was taken of the Unit attribute when finding the \htmlref{Mapping}{Mapping} between
+two Frames. Thus if the conversion between a pair of 1-dimensional Frames
+representing velocity was found (using
+\htmlref{astConvert}{astConvert}
+) the returned Mapping would always be a \htmlref{UnitMap}{UnitMap}, even if the Unit
+attributes of the two Frames were ``km/h'' and ``m/s''. This behaviour is
+referred to below as a \emph{passive} Unit attribute.
+
+As of AST version 2.0, a facility exists which allows the Unit attribute
+to be \emph{active}; that is, differences in the
+Unit attribute may be taken into account when finding the Mapping between
+two Frames. In order to minimise the risk of breaking older software, the
+\emph{default} behaviour of simple Frames and SkyFrames is unchanged from
+previous versions (\emph{i.e.} they have passive Unit attributes). However,
+the new
+functions \htmlref{astSetActiveUnit}{astSetActiveUnit} and \htmlref{astGetActiveUnit}{astGetActiveUnit}
+allow this default behaviour to be changed. The \htmlref{SpecFrame}{SpecFrame} and \htmlref{TimeFrame}{TimeFrame}
+classes \emph{always} have an active Unit attribute (attempts to change this
+are ignored).
+
+For instance, consider the above example of two 1-dimensional Frames
+describing velocity. These Frames can be created as follows:
+
+\small
+\begin{terminalv}
+AstFrame *frame1, *frame2;
+frame1 = astFrame( 1, "Domain=VELOCITY,Unit=km/h" );
+frame2 = astFrame( 1, "Domain=VELOCITY,Unit=m/s" );
+\end{terminalv}
+\normalsize
+
+By default, these Frames have passive Unit attributes, and so an attempt
+to find a Mapping between them would ignore the difference in their Unit
+attributes and return a unit Mapping. To avoid this, we indicate that we
+want these Frames to have \emph{active} Unit attributes, as follows:
+
+\small
+\begin{terminalv}
+astSetActiveUnit( frame1, 1 );
+astSetActiveUnit( frame2, 1 );
+\end{terminalv}
+\normalsize
+
+If we then find the Mapping between them as follows:
+
+\small
+\begin{terminalv}
+AstFrameSet *cvt;
+...
+cvt = astConvert( frame1, frame2, "" );
+\end{terminalv}
+\normalsize
+
+the Mapping contained within the \htmlref{FrameSet}{FrameSet} returned by
+astConvert
+will be a one-dimensional \htmlref{ZoomMap}{ZoomMap} which simply scales its input (a
+velocity in $km/h$) by a factor of 0.278 to create its output (a velocity
+in $m/s$).
+
+In fact we need not have set the Unit attribute active in ``frame1''
+since the behaviour of astConvert is determined by its ``to'' Frame
+(the second Frame parameter).
+
+\subsubsection{\label{ss:unitsyntax}The Syntax for Unit Strings}
+Conversion between units systems relies on the use of a specific syntax
+for the Unit attribute. If the value of the Unit attribute does not
+conform to this syntax, then an error will be reported if an attempt is
+made to use it to determine an inter-unit \htmlref{Mapping}{Mapping} (this will never happen
+if the Unit attribute is \emph{passive}).
+
+The adopted syntax is that described in FITS-WCS paper I "Representation
+of World Coordinate in FITS" by Greisen \& Calabretta. We distinguish
+here between ``basic'' units and ``derived'' units: derived units are
+defined in terms of other units (either derived or basic), whereas basic
+units have no such definitions. Derived units may be represented by their
+own \emph{symbol} (\emph{e.g.} ``Jy''---the Jansky) or by a
+\emph{mathematical expression} which combines other symbols and constants
+to form a definition of the unit (\emph{e.g.} ``km/s''---kilometres per
+second). Unit symbols may be prefixed by a string representing a standard
+multiple or sub-multiple.
+
+In addition to the unit symbols listed in FITS-WCS Paper I, any other
+arbitrary unit symbol may be used, with the proviso that it will not be
+possible to convert between Frames using such units. The exception to
+this is if both Frames refer to the same unknown unit string. For instance,
+an axis with unknown unit symbol "flop" \emph{could} be converted to an axis
+with  unit "Mflop" (Mega-flop).
+
+Unit symbols (optionally prefixed with a multiple or sub-multiple) can be
+combined together using a limited range of mathematical operators and
+functions, to produce new units. Such expressions may also contain
+parentheses and numerical constants (these may optionally use
+``scientific'' notation including an ``E'' character to represent the
+power of 10).
+
+The following tables list the symbols for the basic and derived units which
+may be included in a units string, the standard prefixes for multiples
+and sub-multiples, and the strings which may be used to represent
+mathematical operators and functions.
+
+\begin{table}[htbp]
+\begin{center}
+\begin{tabular}{|l|l|l|}
+\hline
+\multicolumn{3}{|c|}{{\large Basic units}} \\ \hline
+\multicolumn{1}{|c|}{Quantity} & \multicolumn{1}{|c|}{Symbol} &
+\multicolumn{1}{c|}{\htmlref{Full}{Full} Name} \\ \hline
+length              & m   & metre \\
+mass                & g   & gram \\
+time                & s   & second \\
+plane angle         & rad & radian \\
+solid angle         & sr  & steradian \\
+temperature         & K   & Kelvin \\
+electric current    & A   & Ampere \\
+amount of substance & mol & mole \\
+luminous intensity  & cd  & candela \\
+\hline
+\end{tabular}
+\end{center}
+\end{table}
+
+\begin{table}[htbp]
+\begin{center}
+\begin{small}
+\begin{tabular}{|l|l|l|l|}
+\hline
+\multicolumn{4}{|c|}{{\large Derived units}} \\ \hline
+\multicolumn{1}{|c|}{Quantity} & \multicolumn{1}{|c|}{Symbol} &
+\multicolumn{1}{c|}{Full Name} & \multicolumn{1}{c|}{Definition} \\ \hline
+area & barn & barn & 1.0E-28 m**2 \\
+area & pix & pixel & \\
+area & pixel & pixel & \\
+electric capacitance & F & Farad & C/V \\
+electric charge & C & Coulomb & A s \\
+electric conductance & S & Siemens & A/V \\
+electric potential & V & Volt & J/C \\
+electric resistance & Ohm & Ohm & V/A \\
+energy & J & Joule & N m \\
+energy & Ry & Rydberg & 13.605692 eV \\
+energy & eV & electron-Volt & 1.60217733E-19 J \\
+energy & erg & erg & 1.0E-7 J \\
+events & count & count & \\
+events & ct & count & \\
+events & ph & photon & \\
+events & photon & photon & \\
+flux density & Jy & Jansky & 1.0E-26 W /m**2 /Hz \\
+flux density & R & Rayleigh & 1.0E10/(4*PI) photon.m**-2 /s/sr \\
+flux density & mag & magnitude & \\
+force & N & Newton & kg m/s**2 \\
+frequency & Hz & Hertz & 1/s \\
+illuminance & lx & lux & lm/m**2 \\
+inductance & H & Henry & Wb/A \\
+length & AU & astronomical unit & 1.49598E11 m \\
+length & Angstrom & Angstrom & 1.0E-10 m \\
+length & lyr & light year & 9.460730E15 m \\
+length & pc & parsec & 3.0867E16 m \\
+length & solRad & solar radius & 6.9599E8 m \\
+luminosity & solLum & solar luminosity & 3.8268E26 W \\
+luminous flux & lm & lumen & cd sr \\
+magnetic field & G & Gauss & 1.0E-4 T \\
+magnetic flux & Wb & Weber & V s \\
+mass & solMass & solar mass & 1.9891E30 kg \\
+mass & u & unified atomic mass unit & 1.6605387E-27 kg \\
+magnetic flux density & T & Tesla & Wb/m**2 \\
+plane angle  & arcmin & arc-minute & 1/60 deg \\
+plane angle  & arcsec & arc-second & 1/3600 deg \\
+plane angle  & mas & milli-arcsecond & 1/3600000 deg \\
+plane angle & deg & degree & pi/180 rad \\
+power & W & Watt & J/s \\
+pressure, stress & Pa & Pascal & N/m**2 \\
+time  & a & year & 31557600 s \\
+time  & d & day & 86400 s \\
+time  & h & hour & 3600 s \\
+time  & yr & year & 31557600 s \\
+time  & min & minute & 60 s \\
+      & D & Debye & 1.0E-29/3 C.m \\
+\hline
+\end{tabular}
+\end{small}
+\end{center}
+\end{table}
+
+\begin{table}[htbp]
+\begin{center}
+\begin{tabular}{|lll|lll|}
+\hline
+\multicolumn{6}{|c|}{{\large Prefixes for multiples \&
+sub-multiples}} \\ \hline
+\multicolumn{1}{|c}{Sub-multiple} & \multicolumn{1}{c}{Name} &
+\multicolumn{1}{c|}{Prefix} &
+\multicolumn{1}{|c}{Sub-multiple} & \multicolumn{1}{c}{Name} &
+\multicolumn{1}{c|}{Prefix} \\ \hline
+$10^{-1}$ & deci & d & $10$ & deca & da \\
+$10^{-2}$ & centi & c & $10^{2}$ & hecto & h \\
+$10^{-3}$ & milli & m & $10^{3}$ & kilo & k \\
+$10^{-6}$ & micro & u & $10^{6}$ & mega & M \\
+$10^{-9}$ & nano & n & $10^{9}$ & giga & G \\
+$10^{-12}$ & pico & p & $10^{12}$ & tera & T \\
+$10^{-15}$ & femto & f & $10^{15}$ & peta & P \\
+$10^{-18}$ & atto & a & $10^{18}$ & exa & E \\
+$10^{-21}$ & zepto & z & $10^{21}$ & zetta & Z \\
+$10^{-24}$ & yocto & y & $10^{24}$ & yotta & Y \\
+\hline
+\end{tabular}
+\end{center}
+\end{table}
+
+\begin{table}[htbp]
+\begin{center}
+\begin{tabular}{|l|l|}
+\hline
+\multicolumn{2}{|c|}{{\large Mathematical operators \& functions}} \\
+\hline
+\multicolumn{1}{|c|}{String} & \multicolumn{1}{|c|}{Meaning} \\ \hline
+sym1 sym2           & multiplication (a space) \\
+sym1*sym2           & multiplication (an asterisk) \\
+sym1.sym2           & multiplication (a dot) \\
+sym1/sym2           & division \\
+sym1**y             & exponentiation ($y$ must be a numerical constant)\\
+sym1\verb+^+y       & exponentiation ($y$ must be a numerical constant)\\
+log(sym1)           & common logarithm \\
+ln(sym1)            & natural logarithm \\
+exp(sym1)           & exponential \\
+sqrt(sym1)          & square root \\
+\hline
+\end{tabular}
+\end{center}
+\end{table}
+
+\subsubsection{Side-effects of Changing the Unit attribute}
+If an \htmlref{Axis}{Axis} has an active Unit attribute, changing its value (either by
+setting a new value or by clearing it so that the default value is
+re-instated) may cause the Label and Symbol attributes to be changed
+accordingly. For instance, if an Axis has Unit, Label and Symbol of ``Hz'',
+``Frequency'' and ``nu'', then changing its Unit attribute to ``log(Hz)''
+will cause AST to change its Label and Symbol to ``log(Frequency)'' and
+``Log(nu)''. These changes are only made if the Unit attribute is active,
+and a \htmlref{Mapping}{Mapping} can be found from the old units to the new units. On the other
+ hand, changing the Unit from ``Hz'' to ``MHz'' would not cause any change
+to the Label or Symbol attributes.
+
+\cleardoublepage
+\section{\label{ss:skyframes}Celestial Coordinate Systems (SkyFrames)}
+
+A \htmlref{Frame}{Frame} which is specialised for representing coordinate systems on
+the celestial sphere is obviously of great importance in
+astronomy. The \htmlref{SkyFrame}{SkyFrame} is such a Frame. In this section we examine
+the additional properties and behaviour of a SkyFrame that distinguish
+it from a basic Frame (\secref{ss:frames}).
+
+\subsection{The SkyFrame Model}
+
+A \htmlref{SkyFrame}{SkyFrame} is, of course, a \htmlref{Frame}{Frame} (\secref{ss:frames}) and also a
+\htmlref{Mapping}{Mapping} (\secref{ss:mappings}), so it inherits all the properties and
+behaviour of these two ancestral classes.  When used as a Mapping, a
+SkyFrame implements a unit transformation, exactly like a basic Frame
+(\secref{ss:frameasmapping}) or a \htmlref{UnitMap}{UnitMap}, so this aspect of its
+behaviour is not of great importance.
+
+When used as a Frame, however, a SkyFrame represents a 2-dimensional
+\emph{spherical} coordinate system, in which the shortest distance
+between two points is a great circle.  A SkyFrame therefore always has
+exactly two axes which represent the longitude and latitude of a
+coordinate system residing on the celestial sphere. Many such
+coordinate systems can be represented by a SkyFrame, as we will see
+shortly.
+
+A SkyFrame can represent any of the commonly used celestial coordinate
+systems. Optionally, the origin of the longitude/latitude system can be
+moved to any specified point in the standard celestial system, allowing
+a SkyFrame to represent offsets from a specified sky position.
+
+When it is first created, a SkyFrame's axes are always in the order
+(longitude,~latitude) but this can be changed, if required, by using the
+\htmlref{astPermAxes}{astPermAxes} function (\secref{ss:permutingaxes}). The order of the axes
+can be determined at any time using the \htmlref{LatAxis}{LatAxis} and \htmlref{LonAxis}{LonAxis} attributes. A
+SkyFrame's coordinate values are always stored as angles in (double
+precision) radians, regardless of the setting of the Unit attribute
+\footnote{The units used for the internal floating-point representation of an
+axis value can be determined by examining the InternalUnit attribute of
+the Frame. For most Frames, the Unit and InternalUnit attributes will be
+equal, but InternalUnit is always set to ``\texttt{rad}'' for SkyFrames.}.
+
+\subsection{Creating a SkyFrame}
+
+The \htmlref{SkyFrame}{SkyFrame} constructor function is particularly simple and a
+SkyFrame with default attributes is created as follows:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstSkyFrame *skyframe;
+
+...
+
+skyframe = astSkyFrame( "" );
+\end{terminalv}
+\normalsize
+
+Such a SkyFrame would represent the default celestial coordinate
+system which, at present, is the ICRS system (the default was "FK5(J2000)"
+in versions of AST prior to 3.0).
+
+\subsection{Specifying a Particular Celestial Coordinate System}
+
+For many purposes, the ICRS coordinate system is perfectly
+adequate. In order to support conversion between a variety of
+celestial coordinate systems, however, you can create SkyFrames that
+represent any of these.
+
+Selection of a particular coordinate system is performed simply by
+setting a value for the \htmlref{SkyFrame}{SkyFrame}'s (character string) \htmlref{System}{System}
+attribute. This setting is most conveniently done when the SkyFrame is
+created. For example, a SkyFrame representing the old FK4~(B1950.0)
+coordinate system would be created by:
+
+\small
+\begin{terminalv}
+skyframe = astSkyFrame( "System=FK4" );
+\end{terminalv}
+\normalsize
+
+Note that specifying ``System$=$FK4'' also changes the associated
+equinox (from J2000.0 to B1950.0). This is because the default value
+of the SkyFrame's \htmlref{Equinox}{Equinox} attribute (\secref{ss:equinoxitem}) depends
+on the System attribute setting.
+
+You may change the System value at any time, although this is not
+usually needed.  The values supported are set out in the attribute's
+description in \appref{ss:attributedescriptions} and include a variety
+of equatorial coordinate systems, together with ecliptic and galactic
+coordinates.
+
+General spherical coordinates are supported by specifying
+``System$=$unknown''. You should note, though, that no \htmlref{Mapping}{Mapping} can be
+created to convert between ``unknown'' coordinates and any of the other
+celestial coordinate systems (see \secref{ss:introducingconversion} ).
+
+\subsection{Attributes which Qualify Celestial Coordinate Systems}
+
+Many celestial coordinate systems have some additional free parameters
+which serve to identify a particular coordinate system from amongst a
+broader class of related coordinate systems. For example, the
+FK5~(J2010.0) system is distinguished from the FK5~(J2000.0)
+system by a different equinox---and the coordinates of a fixed
+astronomical source would have different values when expressed in
+these two systems.
+
+In AST, these free parameters are represented by additional \htmlref{SkyFrame}{SkyFrame}
+attributes, each of which has a default appropriate to
+(\emph{i.e.}\ defined by) the setting of the main \htmlref{System}{System}
+attribute. Each of these \emph{qualifying attributes} may, however, be
+assigned an explicit value so as to select a particular coordinate
+system. Note, it is usually best to assign explicit
+values whenever possible rather than relying on defaults. Attribute
+should only be left at their default value if you ``don't care'' what
+value is used. In certain circumstances (particularly, when aligning two
+Frames), a default value for an attribute may be replaced by the value
+from another similar \htmlref{Frame}{Frame}. Such value replacement can be prevented by
+assigning an explicit value to the attribute, rather than simply relying on
+the default.
+
+
+The main SkyFrame attributes which qualify the System attribute are:
+
+\begin{quote}
+\begin{description}
+
+\item[\label{ss:epochitem}\htmlref{Epoch}{Epoch}]\mbox{}\\
+This attribute is inherited from the Frame class. It gives the moment in
+time when the coordinates are correct for the astronomical source
+under study (usually the date of observation).
+
+\item[\label{ss:equinoxitem}\htmlref{Equinox}{Equinox}]\mbox{}\\
+This value is used to qualify celestial coordinate systems that are
+notionally based on the Earth's equator and/or the ecliptic (the plane
+of the Earth's orbit around the Sun). The position of either of these
+planes is difficult to specify precisely, so in practice a model
+\emph{mean} equator and/or ecliptic are used instead. These, together
+with the point on the sky that defines the coordinate origin (termed
+the \emph{mean equinox}) move with time according to some model which
+smoothes out the more rapid fluctuations. The SkyFrame class supports
+both the old FK4 model and the newer FK5 one.
+
+Coordinates expressed in any of these systems vary with time due to
+movement (by definition) of the coordinate system itself, and must
+therefore be qualified by a moment in time (the \emph{epoch of the mean
+equinox}, or ``equinox'' for short) which specifies the position of
+the model coordinate system on the sky. This is the role of the
+Equinox attribute.
+
+Note that it is quite valid and common to relate the position of a
+source to an equinox other than the date of observation. Usually a
+standard equinox such as J2000.0 is used, meaning that the coordinates
+are referred to axes defined by where the model mean equator and
+ecliptic would lie on the sky at the Julian epoch J2000.0.
+\end{description}
+\end{quote}
+
+For further details of these attributes you should consult their
+descriptions in \appref{ss:attributedescriptions} and for details of
+the System settings for which they are relevant, see the description
+of the System attribute (also in \appref{ss:attributedescriptions}).
+For the interested reader, an excellent overview of celestial
+coordinate systems can also be found in the documentation for the
+SLALIB library (\xref{SUN/67}{sun67}{}).
+
+The value of these qualifying attributes is most conveniently set at
+the same time as the System value, \emph{e.g.}\ when a SkyFrame is
+created. For instance:
+
+\small
+\begin{terminalv}
+skyframe = astSkyFrame( "System=Ecliptic, Equinox=J2005.5" );
+\end{terminalv}
+\normalsize
+
+would create a SkyFrame representing an ecliptic coordinate system
+referred to the mean equinox and ecliptic of Julian epoch J2005.5.
+
+Note that it does no harm to assign values to qualifying attributes
+which are not relevant to the main System value. Any such values are
+stored, but are not used unless the System value is later set so that
+they become relevant.
+
+\subsection{Using Default SkyFrame Attributes}
+
+The default values supplied for many \htmlref{SkyFrame}{SkyFrame} attributes will depend
+on the value of the SkyFrame's \htmlref{System}{System} attribute. In practice, this
+means that there is usually little need to specify many of these
+attributes explicitly unless you have some special requirement. This
+can be illustrated by using \htmlref{astShow}{astShow} to examine a SkyFrame, as follows:
+
+\small
+\begin{terminalv}
+astShow( astSkyFrame( "System=FK4-NO-E, Epoch=1958" ) );
+\end{terminalv}
+\normalsize
+
+The output from this might look like the following:
+
+\begin{terminalv}
+ Begin SkyFrame         # Description of celestial coordinate system
+#   Title = "FK4 equatorial coordinates; no E-terms; mean equinox B1950.0;
+epoch B1958.0"   # Title of coordinate system
+    Naxes = 2   # Number of coordinate axes
+#   Domain = "SKY"      # Coordinate system domain
+    Epoch = 1958        # Besselian epoch of observation
+#   Lbl1 = "Right ascension"    # Label for axis 1
+#   Lbl2 = "Declination"        # Label for axis 2
+    System = "FK4-NO-E"         # Coordinate system type
+#   Uni1 = "hh:mm:ss.s"         # Units for axis 1
+#   Uni2 = "ddd:mm:ss"  # Units for axis 2
+#   Dir1 = 0    # Plot axis 1 in reverse direction
+#   Bot2 = -1.5707963267949     # Lowest legal axis value
+#   Top2 = 1.5707963267949      # Highest legal axis value
+    Ax1 =       # Axis number 1
+       Begin SkyAxis    # Celestial coordinate axis
+       End SkyAxis
+    Ax2 =       # Axis number 2
+       Begin SkyAxis    # Celestial coordinate axis
+       End SkyAxis
+ IsA Frame      # Coordinate system description
+#   Eqnox = 1950        # Besselian epoch of mean equinox
+ End SkyFrame
+\end{terminalv}
+
+Note that the defaults (indicated by the ``\verb?#?'' comment
+character at the start of the line) for attributes such as the \htmlref{Title}{Title},
+axis Labels and Format specifiers are all set to values appropriate
+for the particular equatorial coordinate system that the SkyFrame
+represents.
+
+This means, for example, that if we were to use this SkyFrame to
+format a right ascension value stored in radians using \htmlref{astFormat}{astFormat}
+(\secref{ss:formattingaxisvalues}), it would automatically result in a
+string in sexagesimal notation (such as ``12:14:35.7'') suitable for
+display.  If we changed the value of the SkyFrame's Digits attribute
+(which is inherited from the \htmlref{Frame}{Frame} class), the number of digits
+appearing would also change accordingly.
+
+These choices would be appropriate for a System value of ``FK4-NO-E'',
+but if a different System value were set, the defaults would be
+correspondingly different. For example, ecliptic longitude is
+traditionally expressed in degrees, so setting ``System=ecliptic''
+would result in coordinate values being formatted as degrees by
+default.
+
+Of course, if you do not like any of these defaults, you may always
+over-ride them by setting explicit attribute values yourself.
+
+\subsection{\label{ss:formattingskyaxisvalues}Formatting Celestial Coordinates}
+
+SkyFrames use \htmlref{astFormat}{astFormat} for formatting coordinate values in the same
+way as other Frames (\secref{ss:formattingaxisvalues}). However, they
+offer a different set of formatting options more appropriate to
+celestial coordinates.
+
+The Digits attribute of a \htmlref{SkyFrame}{SkyFrame} behaves in essentially the same way
+as for a basic \htmlref{Frame}{Frame} (\secref{ss:formattingwithdigits}), so the
+precision with which celestial coordinates are displayed can also be
+adjusted in this way. However, the range of format specifiers that can
+be given for the \htmlref{Format(axis)}{Format(axis)} attribute, and the default format
+resulting from any particular Digits value, is different.
+
+The syntax of SkyFrame format specifiers is detailed under the
+description of the Format(axis) attribute in
+\appref{ss:attributedescriptions}.  Briefly, however, it allows
+celestial coordinates to be expressed either as angles or times and to
+include one or more of the fields:
+
+\begin{quote}
+\begin{itemize}
+\item degrees or hours
+\item arc-minutes or minutes
+\item arc-seconds or seconds
+\end{itemize}
+\end{quote}
+
+with a specified number of decimal places for the final field. A range
+of field separators is also available, as the following examples show:
+
+\begin{quote}
+\begin{center}
+\begin{tabular}{|l|l|}
+\hline
+\textbf{Format Specifier} & \textbf{Example Formatted Value}\\
+\hline \hline
+{\tt{d}} & {\tt{219}}\\
+{\tt{d.3}} & {\tt{219.123}}\\
+{\tt{dm}} & {\tt{219:05}}\\
+{\tt{dm.2}} & {\tt{219:05.44}}\\
+{\tt{dms}} & {\tt{219:05:42}}\\
+{\tt{hms.1}} & {\tt{15:44:13.8}}\\
+{\tt{bdms.2}} & {\tt{219 05 42.81}}\\
+{\tt{lhms.3}} & {\tt{15h44m13.88s}}\\
+{\tt{+zlhms}} & {\tt{+06h10m44s}}\\
+{\tt{ms.1}} & {\tt{13145:42.8}}\\
+{\tt{lmst.3}} & {\tt{876m22.854s}}\\
+{\tt{s.2}} & {\tt{788742.81}}\\
+\hline
+\end{tabular}
+\end{center}
+\end{quote}
+
+Note the following key points:
+
+\begin{itemize}
+\item The required fields are specified using characters chosen from
+either ``dms'' or ``hms'' according to whether the value is to be
+formatted as an angle (in degrees) or a time (in hours).
+
+\item If no degrees or hours field is required, the distinction
+between angle and time may be made by including ``t'' to request time.
+
+\item The number of decimal places (for the final field) is indicated
+using ``\texttt{.}'' followed by an integer. An asterisk can be used in
+place of an integer, in which case the number of decimal places is
+chosen so that the total number of digits in the formatted value is equal
+to the value of the Digits attribute.
+
+\item ``b'' causes fields to be separated by blanks, while ``l''
+causes them to be separated by the appropriate letters (the default
+being a colon).
+
+\item ``z'' causes padding with leading zeros.
+
+\item ``+'' cause a plus sign to be prefixed to positive values
+(negative values always have a minus sign).
+\end{itemize}
+
+The formatting performed by a SkyFrame is also influenced by the
+\htmlref{AsTime(axis)}{AsTime(axis)} attribute, which has a boolean (integer) value for each
+SkyFrame axis.  It determines whether the default format specifier for
+an axis will present values as angles (\emph{e.g.}\ in degrees) if it
+is zero, or as times (\emph{e.g.}\ in hours) if it is non-zero.
+
+The default AsTime value depends on the celestial coordinate system
+which the SkyFrame represents which, in turn, depends on its \htmlref{System}{System}
+attribute value. For example, equatorial longitude values (right
+ascension) are normally expressed in hours, whereas ecliptic
+longitudes are normally expressed in degrees, so their default AsTime
+values will reflect this difference.
+
+The value of the AsTime attribute may be set explicitly to over-ride
+these defaults if required, with the formatting precision being
+determined by the \htmlref{Digits/Digits(axis)}{Digits/Digits(axis)} value. Alternatively, the
+Format(axis) attribute may be set explicitly to specify both the
+format and precision required. Setting an explicit Format value always
+over-rides the effects of both the Digits and AsTime attributes (unless
+the Format value does not specify the required number of decimal places,
+in which case Digits is used to determine the default number of decimal
+places)
+
+\subsection{\label{ss:unformattingskyaxisvalues}Reading Formatted Celestial Coordinates}
+
+The process of converting formatted celestial coordinates, such as
+might be produced by the \htmlref{astFormat}{astFormat} function
+(\secref{ss:formattingskyaxisvalues}), into numerical (double)
+coordinate values is performed by using \htmlref{astUnformat}{astUnformat}
+(\secref{ss:unformattingaxisvalues}) and passing it a pointer to a
+\htmlref{SkyFrame}{SkyFrame}. The use of a SkyFrame means that the range of input formats
+accepted is appropriate to positions on the sky expressed as angles
+and/or times, while the returned value is in radians.
+
+The following describes the forms of celestial coordinate which are
+supported:
+
+\begin{itemize}
+\item You may supply an optional sign, followed by between one and
+three fields representing either degrees, arc-minutes, arc-seconds or
+hours, minutes, seconds (\emph{e.g.}\ ``$-$12~42~03'').
+
+\item Each field should consist of a sequence of one or more digits,
+which may include leading zeros. At most one field may contain a
+decimal point, in which case it is taken to be the final field
+(\emph{e.g.}\ decimal degrees might be given as ``124.707'', while
+degrees and decimal arc-minutes might be given as ``$-$13~33.8'').
+
+\item The first field given may take any value, allowing angles and
+times outside the conventional ranges to be represented. However,
+subsequent fields must have values of less than 60 (\emph{e.g.}
+``720~45~31'' is valid, whereas ``11~45~61'' is not).
+
+\item Fields may be separated by white space or by ``:'' (colon), but
+the choice of separator must be used consistently throughout the
+value. Additional white space may be present around fields and
+separators (\emph{e.g.}\ ``$-$~2:~04~:~7.1'').
+
+\item The following field identification characters may be used as
+separators to replace those above (or may be appended to the final
+field), in order to identify the field to which they are appended:
+
+\begin{quote}
+\begin{tabular}{lll}
+d & -- & degrees \\
+h & -- & hours \\
+m & -- & minutes (of arc or time) \\
+s & -- & seconds (of arc or time) \\
+\texttt{'} & -- & arc-minutes \\
+\texttt{"} & -- & arc-seconds
+\end{tabular}
+\end{quote}
+
+Either lower or upper case may be used.  Fields must be given in order
+of decreasing significance
+(\emph{e.g.}\ ``$-$11D~3\texttt{'}~14.4\texttt{"}'' or ``22h14m11.2s'').
+
+\item The presence of certain field identification characters
+indicates whether the value is to be interpreted as an angle or a time
+(with 24 hours corresponding to 360 degrees), as follows:
+
+\begin{quote}
+\begin{tabular}{lll}
+d & -- & angle \\
+\texttt{'} & -- & angle \\
+\texttt{"} & -- & angle \\
+h & -- & time
+\end{tabular}
+\end{quote}
+
+Incompatible angle/time identification characters may not be mixed
+(\emph{e.g.}\ ``10h14\texttt{'}3\texttt{"}'' is not valid).  The remaining
+field identification characters and separators do not specify a
+preference for an angle or a time and may be used with either.
+
+\item If no preference for an angle or a time is expressed anywhere
+within the value, then it is interpreted as an angle if the Format
+attribute string associated with the SkyFrame axis generates an angle
+and as a time otherwise.  This ensures that values produced by
+astFormat (\secref{ss:formattingskyaxisvalues}) are correctly
+interpreted by astUnformat.
+
+\item Fields may be omitted, in which case they default to zero. The
+remaining fields may be identified by using appropriate field
+identification characters (see above) and/or by adding extra colon
+separators (e.g. ``$-$05m13s'' is equivalent to ``$-$:05:13''). If a field
+is not identified explicitly, it is assumed that adjacent fields have
+been given, after taking account of any extra separator
+characters. For example:
+
+\begin{quote}
+\begin{tabular}{lll}
+10d & -- & degrees \\
+10d12 & -- & degrees and arc-minutes \\
+11:14\texttt{"} & -- & arc-minutes and arc-seconds \\
+9h13s & -- & hours and seconds of time \\
+:45:33 & -- & minutes and seconds (of arc or time) \\
+:55: & -- & minutes (of arc or time) \\
+::13 & -- & seconds (of arc or time) \\
+$-$6::2.5 & -- & degrees/hours and seconds (of arc or time) \\
+07m14 & -- & minutes and seconds (of arc or time) \\
+$-$8:14\texttt{'} & -- & degrees and arc-minutes \\
+$-$h3:14 & -- & minutes and seconds of time \\
+h:2.1 & -- & seconds of time
+\end{tabular}
+\end{quote}
+
+\item If fields are omitted in such a way that the remaining ones
+cannot be identified uniquely (e.g. ``01:02''), then the first field
+(either given explicitly or implied by an extra leading colon
+separator) is taken to be the most significant field that astFormat
+would produce when formatting a value (using the Format attribute
+associated with the SkyFrame axis). By default, this means that the
+first field will normally be interpreted as degrees or hours. However,
+if this does not result in consistent field identification, then the
+last field (either given explicitly or implied by an extra trailing
+colon separator) is taken to to be the least significant field that
+astFormat would produce.
+
+\end{itemize}
+
+This final convention is intended to ensure that values formatted by
+astFormat which contain less than three fields will be correctly
+interpreted if read back using astUnformat, even if they do not
+contain field identification characters.  However, it also affects
+other forms of input. For example, if the \htmlref{Format(axis)}{Format(axis)} string were set
+to ``mst.1'' (producing two fields representing minutes and seconds of
+time), then formatted input would be interpreted by astUnformat as
+follows:
+
+\begin{quote}
+\begin{tabular}{lll}
+12 13 & -- & minutes and seconds \\
+12 & -- & minutes \\
+:13 & -- & seconds \\
+$-$18: & -- & minutes \\
+12.8 & -- & minutes \\
+1 2 3 & -- & hours, minutes and seconds \\
+& & \\
+4\texttt{'} & -- & arc-minutes \\
+60::\texttt{"} & -- & degrees \\
+$-$23:\texttt{"} & -- & arc-minutes \\
+$-$33h & -- & hours
+\end{tabular}
+\end{quote}
+
+(in the last four cases, explicit field identification has been given
+which overrides the implicit identification).
+
+Alternatively, if the Format(axis) string were set to ``s.3''
+(producing only an arc-seconds field), then formatted input would be
+interpreted by astUnformat as follows:
+
+\begin{quote}
+\begin{tabular}{lll}
+12.8 & -- & arc-seconds \\
+12 13 & -- & arc-minutes and arc-seconds \\
+:12 & -- & arc-seconds \\
+13: & -- & arc-minutes \\
+1 2 3 & -- & degrees, arc-minutes and arc-seconds
+\end{tabular}
+\end{quote}
+
+In general, if you are preparing formatted input data containing
+celestial coordinates and wish to omit certain fields, then you are
+advised to identify clearly those that you do provide by using the
+appropriate field identification characters and/or extra colon
+separators. This prevents you depending on the implicit field
+identification described above which, in turn, depends on an
+appropriate Format(axis) string having been set.
+
+When writing software, it is also a good idea to set the Format(axis)
+string so that data input will be as simple as possible for the
+user. Unless some special effect is desired, this normally means that
+it should contain ``d'' or ``h'' to ensure that the first field
+entered by the user will be interpreted as degrees or hours, unless
+otherwise identified. This is the normal behaviour unless an explicit
+Format(axis) value has been set to override the default.
+
+\subsection{Representing Offsets from a Specified Sky Position}
+A \htmlref{SkyFrame}{SkyFrame} can be modified so that its longitude and latitude axes are
+referred to an origin at any specified sky position. Such a coordinate
+system is referred to as an ``offset'' coordinate system. First, the \htmlref{System}{System}
+attribute should be set to represent the celestial coordinate system in
+which the origin is to be specified. Then the SkyRef attribute should be
+set to hold the coordinates of the origin within the selected celestial
+coordinate system.
+
+By default, ``north'' in the new offset coordinate system is parallel to
+north in the original celestial coordinate system. However, the direction
+of north in the offset system can be controlled by assigning a value to
+the SkyRefP attribute. This attribute should be assigned the celestial
+coordinates of a point which is on the zero longitude meridian and which
+has non-zero latitude.
+
+By default, the position given by the SkyRef attribute is used as the
+origin of the new longitude/latitude system, but an option exists to use
+it as the north pole of the system instead. This option is controlled by
+the \htmlref{SkyRefIs}{SkyRefIs} attribute. The choice of value for SkyRefIs depends on what
+sort of offset coordinate system you want. Setting SkyRefIs to
+``Origin'' (the default) produces an offset coordinate system which is
+approximately Cartesian close to the specified position. Setting SkyRefIs
+to
+``Pole'' produces an offset coordinate system which is approximately Polar
+close to the specified position.
+
+\cleardoublepage
+\section{\xlabel{ss_specframes}\label{ss:specframes}Spectral Coordinate Systems (SpecFrames)}
+
+The \htmlref{SpecFrame}{SpecFrame} is a \htmlref{Frame}{Frame} which is specialised for representing coordinate
+systems which describe a position within an electro-magnetic spectrum.
+In this section we examine the additional properties and behaviour of a
+SpecFrame that distinguish it from a basic Frame (\secref{ss:frames}).
+
+\subsection{The SpecFrame Model}
+
+As for a \htmlref{SkyFrame}{SkyFrame}, a \htmlref{SpecFrame}{SpecFrame} is a \htmlref{Frame}{Frame} (\secref{ss:frames}) and also a
+\htmlref{Mapping}{Mapping} (\secref{ss:mappings}), so it inherits all the properties and
+behaviour of these two ancestral classes.  When used as a Mapping, a
+SpecFrame implements a unit transformation, exactly like a basic Frame
+(\secref{ss:frameasmapping}) or a \htmlref{UnitMap}{UnitMap}, so this aspect of its
+behaviour is not of great importance.
+
+When used as a Frame, however, a SpecFrame represents a wide range of
+different 1-dimensional coordinate system which can be used to describe
+positions within a spectrum. The options available largely mirror those
+described in the FITS-WCS paper III \emph{Representations of spectral
+coordinates in FITS} (Greisen, Valdes, Calabretta \& Allen).
+
+\subsection{Creating a SpecFrame}
+
+The \htmlref{SpecFrame}{SpecFrame} constructor function is particularly simple and a
+SpecFrame with default attributes is created as follows:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstSpecFrame *specframe;
+
+...
+
+specframe = astSpecFrame( "" );
+\end{terminalv}
+\normalsize
+
+Such a SpecFrame would represent the default coordinate system which is
+heliocentric wavelength in metres (i.e. wavelength corrected to take into
+account the Doppler shift caused by the velocity of the observer around the
+sun).
+
+\subsection{Specifying a Particular Spectral Coordinate System}
+
+Selection of a particular coordinate system is performed simply by
+setting a value for the \htmlref{SpecFrame}{SpecFrame}'s (character string) \htmlref{System}{System}
+attribute. This setting is most conveniently done when the SpecFrame is
+created. For example, a SpecFrame representing Energy would be created by:
+
+\small
+\begin{terminalv}
+specframe = astSpecFrame( "System=Energy" );
+\end{terminalv}
+\normalsize
+
+Note that specifying ``System$=$Energy'' also changes the associated
+Unit (from metres to Joules). This is because the default value
+of the SpecFrame's Unit attribute depends on the System attribute setting.
+
+You may change the System value at any time, although this is not
+usually needed.  The values supported are set out in the attribute's
+description in \appref{ss:attributedescriptions} and include a variety
+of velocity systems, together with frequency, wavelength, energy,
+wave-number, \emph{etc}.
+
+\subsection{Attributes which Qualify Spectral Coordinate Systems}
+
+Many spectral coordinate systems have some additional free parameters
+which serve to identify a particular coordinate system from amongst a
+broader class of related coordinate systems. For example, the
+velocity systems are all parameterised by a rest frequency---the
+frequency which defines zero velocity, and all coordinate systems
+are qualified by a `standard of rest'' which indicates the rest frame to
+which the values refer.
+
+In AST, these free parameters are represented by additional \htmlref{SpecFrame}{SpecFrame}
+attributes, each of which has a default appropriate to
+(\emph{i.e.}\ defined by) the setting of the main \htmlref{System}{System}
+attribute. Each of these \emph{qualifying attributes} may, however, be
+assigned an explicit value so as to select a particular coordinate
+system. Note, it is usually best to assign explicit
+values whenever possible rather than relying on defaults. Attribute
+should only be left at their default value if you ``don't care'' what
+value is used. In certain circumstances (particularly, when aligning two
+Frames), a default value for an attribute may be replaced by the value
+from another similar \htmlref{Frame}{Frame}. Such value replacement can be prevented by
+assigning an explicit value to the attribute, rather than simply relying on
+the default.
+
+
+The main SpecFrame attributes which qualify the System attribute are:
+
+\begin{quote}
+\begin{description}
+
+\item[\htmlref{Epoch}{Epoch}]\mbox{}\\
+This attribute is inherited from the Frame class. It gives the moment in
+time when the coordinates are correct for the astronomical source
+under study (usually the date of observation). It is needed in order to
+calculate the Doppler shift produced by the velocity of the observer
+relative to the centre of the earth, and of the earth relative to the sun.
+
+\item[\htmlref{StdOfRest}{StdOfRest}]\mbox{}\\
+This specifies the rest frame in which the coordinates are correct.
+Transforming between different standards of rest involves taking account
+of the Doppler shift introduced by the relative motion of the two
+standards of rest.
+
+\item[\htmlref{RestFreq}{RestFreq}]\mbox{}\\
+Specifies the frequency which correspond to zero velocity. When setting a
+value for this attribute, the value may be supplied as a wavelength
+(including an indication of the units being used, ``nm'' ``Angstrom'',
+\emph{etc.}), which will be automatically be converted to a frequency.
+
+\item[\htmlref{RefRA}{RefRA}]\mbox{}\\
+Specifies the RA (FK5 J2000) of the source. This is used when converting
+between standards of rest. It specifies the direction along which the
+component of the relative velocity of the two standards of rest is taken.
+
+\item[\htmlref{RefDec}{RefDec}]\mbox{}\\
+Specifies the Dec (FK5 J2000) of the source. Used in conjunction with
+REFRA.
+
+\item[\htmlref{SourceVel}{SourceVel}]\mbox{}\\
+This defines the ``source'' standard of rest. This is a rest frame which
+is moving towards the position given by RefRA and RefDec, at a velocity
+given by SourceVel. The velocity is stored internally as a heliocentric
+velocity, but can be given in any of the other supported standards of rest.
+
+\end{description}
+\end{quote}
+
+For further details of these attributes you should consult their
+descriptions in \appref{ss:attributedescriptions} and for details of
+the System settings for which they are relevant, see the description
+of the System attribute (also in \appref{ss:attributedescriptions}).
+
+Note that it does no harm to assign values to qualifying attributes
+which are not relevant to the main System value. Any such values are
+stored, but are not used unless the System value is later set so that
+they become relevant.
+
+\subsection{Using Default SpecFrame Attributes}
+
+The default values supplied for many \htmlref{SpecFrame}{SpecFrame} attributes will depend
+on the value of the SpecFrame's \htmlref{System}{System} attribute. In practice, this
+means that there is usually little need to specify many of these
+attributes explicitly unless you have some special requirement. This
+can be illustrated by using \htmlref{astShow}{astShow} to examine a SpecFrame, as follows:
+
+\small
+\begin{terminalv}
+astShow( astSpecFrame( "System=Vopt, RestFreq=250 GHz" ) );
+\end{terminalv}
+\normalsize
+
+The output from this might look like the following:
+
+\begin{terminalv}
+ Begin SpecFrame        # Description of spectral coordinate system
+#   Title = "Optical velocity, rest frequency = 250 GHz"       # Title
+of coordinate system
+    Naxes = 1   # Number of coordinate axes
+#   Domain = "SPECTRUM"         # Coordinate system domain
+#   Epoch = 2000        # Julian epoch of observation
+#   Lbl1 = "Optical velocity"  # Label for axis 1
+    System = "VOPT"     # Coordinate system type
+#   Uni1 = "km/s"       # Units for axis 1
+    Ax1 =       # Axis number 1
+       Begin Axis       # Coordinate axis
+       End Axis
+ IsA Frame      # Coordinate system description
+#   SoR = "Heliocentric"        # Standard of rest
+    RstFrq = 250000000000       # Rest frequency (Hz)
+ End SpecFrame
+\end{terminalv}
+
+Note that the defaults (indicated by the ``\verb?#?'' comment
+character at the start of the line) for attributes such as the \htmlref{Title}{Title},
+axis Labels and Unit specifiers are all set to values appropriate
+for the particular velocity system that the SpecFrame represents.
+
+These choices would be appropriate for a System value of ``Vopt'',
+but if a different System value were set, the defaults would be
+correspondingly different. For example, by default frequency is measured in
+units of GHz, not $km/s$, so setting ``System=freq''
+would change the appropriate line above from:
+
+\begin{terminalv}
+#   Uni1 = "km/s"       # Units for axis 1
+\end{terminalv}
+
+to
+
+\begin{terminalv}
+#   Uni1 = "GHz"        # Units for axis 1
+\end{terminalv}
+
+Of course, if you do not like any of these defaults, you may always
+over-ride them by setting explicit attribute values yourself. For
+instance, you may choose to have your frequency axis expressed in ``kHz''
+rather than ``GHz''. To do this simply set the attribute value as follows:
+
+\small
+\begin{terminalv}
+astSetC( specframe, "Unit", "kHz" );
+\end{terminalv}
+\normalsize
+
+No error will be reported if you accidentally set an inappropriate Unit value
+(say "J" - Joules)---after all, AST cannot tell what you are about to do,
+and you \emph{may} be about to change the System value to ``Energy''.
+However, an error \emph{will} be reported if you attempt to find a
+conversion between two SpecFrames (for instance using
+\htmlref{astConvert}{astConvert}
+) if either SpecFrame has a Unit value which is inappropriate for its
+System value.
+
+SpecFrame attributes, like all other attributes, all have default
+value. However, be aware that for some attributes these default values
+can never be more than ``a legal numerical value'' and have no
+astronomical significance. For instance, the \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec} attributes
+(which give the source position) both have a default value of zero. So
+unless your source happens to be at that point (highly unlikely!) you will
+need to set new values. Likewise, the \htmlref{RestFreq}{RestFreq} (rest frequency) attribute
+has an arbitrary default value of 1.0E5 GHz. Some operations are not
+affected by inappropriate values for these attributes (for instance,
+converting from frequency to wavelength, changing axis units, \emph{etc}),
+but some are. For instance, converting from frequency to velocity
+requires a correct rest frequency, moving between different standards of
+rest requires a correct source position. The moral is, always set explicit
+values for as many attributes as possible.
+
+\subsection{\label{ss:creatingspectralcubes}Creating Spectral Cubes}
+You can use a \htmlref{SpecFrame}{SpecFrame} to describe the spectral axis in a data cube
+containing two spatial axes and a spectral axis. To do this you would
+create an appropriate SpecFrame, together with a 2-dimensional \htmlref{Frame}{Frame}
+(often a \htmlref{SkyFrame}{SkyFrame}) to describe the spatial axes. You would then combine
+these two Frames together into a single \htmlref{CmpFrame}{CmpFrame}.
+
+\small
+\begin{terminalv}
+AstSkyFrame *skyframe;
+AstSpecFrame *specframe;
+AstCmpFrame *cmpframe;
+...
+skyframe = astSkyFrame( "Epoch=J2002" );
+specframe = astSpecFrame( "System=Freq,StdOfRest=LSRK" );
+cmpframe = astCmpFrame( skyframe, specframe, "" );
+\end{terminalv}
+\normalsize
+
+In the resulting CmpFrame, axis 1 will be RA, axis 2 will be Dec and axis
+3 will be Frequency. If this is not the order you want, you can permute
+the axes using
+\htmlref{astPermAxes}{astPermAxes}.
+
+There is one potential problem with this approach if you are interested in
+unusually high accuracy. Conversion between different standards of rest
+involves taking account of the Doppler shift caused by the relative
+motion of the two standards of rest. At some point this involves finding
+the component of  the relative velocity in the direction of interest.
+For a SpecFrame, this direction is always given by the \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec}
+attributes, even if the SpecFrame is embedded within a CmpFrame as above.
+It would be more appropriate if this ``direction of interest'' was
+specified by the values passed into the CmpFrame on the RA and DEC axes,
+allowing each pixel within a data cube to have a slightly different
+correction for Doppler shift.
+
+Unfortunately, the SpecFrame class cannot do this (since it is purely a
+1-dimensional Frame), and so some small degree of error will be
+introduced when converting between standards of rest, the size of the
+error varying from pixel to pixel. It is hoped that at some point in the
+future a sub-class of CmpFrame (a SpecCubeFrame) will be added to AST which
+allows for this spatial variation in Doppler shift.
+
+The maximum velocity error introduced by this problem is of the order of
+$V*SIN(FOV)$, where $FOV$ is the angular field of view, and $V$ is the
+relative velocity of the two standards of rest. As an example, when
+correcting from the observers rest frame (i.e. the topocentric rest
+frame) to the kinematic local standard of rest the maximum value of $V$
+is about 20 $km/s$, so for 5 arc-minute field of view the maximum
+velocity error introduced by the correction will be about 0.03 $km/s$. As
+another example, the maximum error when correcting from the observers
+rest frame to the local group is about 5 $km/s$ over a 1 degree field of
+view.
+
+\subsection{\label{ss:handlingdualsidebandspectra}Handling Dual-Sideband Spectra}
+Dual sideband super-heterodyne receivers produce spectra in which each channel
+contains contributions from two different frequencies, referred to as the
+``upper sideband frequency'' and the ``lower sideband frequency''. In the
+rest frame of the observer (topocentric), these are related to each other as
+follows:
+
+\begin{quote}
+\begin{small}
+\begin{equation}
+\label{eqn:dsb}
+   f_{lsb} = 2.f_{LO} - f_{usb}
+\end{equation}
+\end{small}
+\end{quote}
+
+where $f_{LO}$ is a fixed frequency known as the ``local oscillator
+frequency''. In other words, the local oscillator frequency is always
+mid-way between any pair of corresponding upper and lower sideband
+frequencies\footnote{Note, this simple relationship only applies if all
+frequencies are topocentric.}. If you want to describe the spectral axis
+of such a spectrum using a \htmlref{SpecFrame}{SpecFrame} you must choose whether you want the
+SpecFrame to describe $f_{lsb}$ or $f_{usb}$ - a basic SpecFrame cannot
+describe both sidebands simultaneously. However, there is a sub-class of
+SpecFrame, called \htmlref{DSBSpecFrame}{DSBSpecFrame}, which overcomes this difficulty.
+
+A DSBSpecFrame has a \htmlref{SideBand}{SideBand} attribute which indicates if the
+DSBSpecFrame is currently being used to describe the upper or lower
+sideband spectral axis. The value of this attribute can be changed at any
+time. If you use the
+\htmlref{astConvert}{astConvert}
+function to find the \htmlref{Mapping}{Mapping} between two DSBSpecFrames, the setting for
+the two SideBand attributes will be taken into account. Thus, if you take
+a copy of a DSBSpecFrame, toggle its SideBand attribute, and then use
+astConvert
+to find a Mapping from the original to the modified copy, the resulting
+Mapping will be of the form of equation \ref{eqn:dsb} (if the
+DSBSpecFrame has its \htmlref{StdOfRest}{StdOfRest} attribute set to ``Topocentric'').
+
+In general, when finding a Mapping between two arbitrary DSBSpecFrames,
+the total Mapping is made of of three parts in series:
+
+\begin{enumerate}
+\item A Mapping which converts the first DSBSpecFrame into its upper
+sideband representation. If the DSBSpecFrame already represents its upper
+sideband, this Mapping will be a \htmlref{UnitMap}{UnitMap}.
+\item A Mapping which converts from the first to the second DSBSpecFrame,
+treating them as if they were both basic SpecFrames. This takes account of
+any difference in units, standard of rest, system, \emph{etc} between the
+two DSBSpecFrames.
+\item A Mapping which converts the second DSBSpecFrame from its upper
+sideband representation to its current sideband. If the DSBSpecFrame
+currently represents its upper sideband, this Mapping will be a UnitMap.
+\end{enumerate}
+
+If an attempt is made to find the Mapping between a DSBSpecFrame and a
+basic SpecFrame, then the DSBSpecFrame will be treated like a basic
+SpecFrame. In other words, the returned Mapping will not be affected by
+the setting of the SideBand attribute (or any of the other attributes
+specific to the DSBSpecFrame class).
+
+In practice, the local oscillator frequency for a dual sideband
+instrument may not be easily available to an observer. Instead, it is
+common practice to specify the spectral position of some central feature
+in the observation (commonly the centre of the instrument passband),
+together with an ``intermediate frequency''. Together, these two values
+allow the local oscillator frequency to be determined. The intermediate
+frequency is the difference between the topocentric frequency at the
+central spectral position and the topocentric frequency of the local
+oscillator. So:
+
+\begin{quote}
+\begin{small}
+\begin{equation}
+\label{eqn:dsb2}
+   f_{LO} = f_{central} + f_{if}
+\end{equation}
+\end{small}
+\end{quote}
+
+The DSBSpecFrame class uses the \htmlref{DSBCentre}{DSBCentre} attribute to specify the central
+spectral position ($f_{central}$), and the \htmlref{IF}{IF} attribute to specify the
+intermediate frequency ($f_{if}$). The DSBCentre value is given and returned
+in the spectral system described by the DSBSpecFrame (thus you do not need to
+calculate the corresponding topocentric frequency yourself - this will be
+done automatically by the DSBSpecFrame when you assign a new value to the
+DSBCentre attribute). The value assigned to the IF attribute should
+always be a topocentric frequency in units of Hz, however a negative
+value may be given to indicate that the DSBCentre value is in the upper
+sideband (that is, if $IF < 0$  then $f_{central} > f_{LO}$). A positive
+value for IF indicates that the DSBCentre value is in the lower sideband
+(that is, if $IF > 0$  then $f_{central} < f_{LO}$).
+
+
+\cleardoublepage
+\section{\xlabel{ss_timeframes}\label{ss:timeframes}Time Systems (TimeFrames)}
+
+The \htmlref{TimeFrame}{TimeFrame} is a \htmlref{Frame}{Frame} which is specialised for representing moments in
+time. In this section we examine the additional properties and behaviour of a
+TimeFrame that distinguish it from a basic Frame (\secref{ss:frames}).
+
+\subsection{The TimeFrame Model}
+
+As for a \htmlref{SkyFrame}{SkyFrame}, a \htmlref{TimeFrame}{TimeFrame} is a \htmlref{Frame}{Frame} (\secref{ss:frames}) and also a
+\htmlref{Mapping}{Mapping} (\secref{ss:mappings}), so it inherits all the properties and
+behaviour of these two ancestral classes.  When used as a Mapping, a
+TimeFrame implements a unit transformation, exactly like a basic Frame
+(\secref{ss:frameasmapping}) or a \htmlref{UnitMap}{UnitMap}, so this aspect of its
+behaviour is not of great importance.
+
+When used as a Frame, however, a TimeFrame represents a wide range of
+different 1-dimensional coordinate system which can be used to describe
+moments in time. Absolute times and relative (i.e. elapsed) times are
+supported (attribute \htmlref{TimeOrigin}{TimeOrigin}), as are a range of different time scales
+(attribute \htmlref{TimeScale}{TimeScale}). An absolute or relative value in any time scale can
+be represented in different forms such as Modified Julian Date, Julian \htmlref{Epoch}{Epoch},
+\emph{etc} (attribute \htmlref{System}{System}). AST extends the definition of these systems to
+allow them to be used with any unit of time (attribute Unit). The TimeFrame
+class also allows times to formatted as either a simple floating point value
+or as a Gregorian date and time of day (attribute Format).
+
+\subsection{Creating a TimeFrame}
+
+The \htmlref{TimeFrame}{TimeFrame} constructor function is particularly simple and a
+TimeFrame with default attributes is created as follows:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstTimeFrame *timeframe;
+
+...
+
+timeframe = astTimeFrame( "" );
+\end{terminalv}
+\normalsize
+
+Such a TimeFrame would represent the default coordinate system which is
+Modified Julian Date (with the usual units of days) in the International
+Atomic Time (TAI) time scale.
+
+\subsection{Specifying a Particular Time System}
+By setting the \htmlref{System}{System} attribute appropriately, the \htmlref{TimeFrame}{TimeFrame} can represent
+Julian Date, Modified Julian Date, Julian \htmlref{Epoch}{Epoch} or Besselian Epoch (the
+time scale is specified by a separate attribute called \htmlref{TimeScale}{TimeScale}).
+
+Selection of a particular coordinate system is performed simply by
+setting a value for the TimeFrame's (character string) System
+attribute. This setting is most conveniently done when the TimeFrame is
+created. For example, a TimeFrame representing Julian Epoch would be created
+by:
+
+\small
+\begin{terminalv}
+timeframe = astTimeFrame( "System=JEPOCH" );
+\end{terminalv}
+\normalsize
+
+Note that specifying ``System$=$JEPOCH'' also changes the associated
+default Unit (from days to years). This is because the default value
+of the TimeFrame's Unit attribute depends on the System attribute setting.
+
+You may change the System value at any time, although this is not
+usually needed.  The values supported are set out in the attribute's
+description in \appref{ss:attributedescriptions}.
+
+\subsection{Attributes which Qualify Time Coordinate Systems}
+
+Time coordinate systems require some additional free parameters to identify
+a particular coordinate system from amongst a broader class of related
+coordinate systems. For example, all TimeFrames are qualified by the time
+scale (that is, the physical process used to define the flow of time),
+and some require the position of the observer's clock.
+
+In AST, these free parameters are represented by additional \htmlref{TimeFrame}{TimeFrame}
+attributes, each of which has a default appropriate to (\emph{i.e.}\ defined
+by) the setting of the main \htmlref{System}{System} attribute. Each of these \emph{qualifying
+attributes} may, however, be assigned an explicit value so as to select a
+particular coordinate system. Note, it is usually best to assign explicit
+values whenever possible rather than relying on defaults. Attribute
+should only be left at their default value if you ``don't care'' what
+value is used. In certain circumstances (particularly, when aligning two
+Frames), a default value for an attribute may be replaced by the value
+from another similar \htmlref{Frame}{Frame}. Such value replacement can be prevented by
+assigning an explicit value to the attribute, rather than simply relying on
+the default.
+
+The main TimeFrame attributes which qualify the System attribute are:
+
+\begin{quote}
+\begin{description}
+
+\item[\htmlref{TimeScale}{TimeScale}]\mbox{}\\
+This specifies the time scale.
+
+\item[\htmlref{LTOffset}{LTOffset}]\mbox{}\\
+This specifies the offset from Local Time to UTC in hours (time zones
+east of Greenwich have positive values). Note, AST uses the value as
+supplied without making any correction for daylight saving.
+
+\item[\htmlref{TimeOrigin}{TimeOrigin}]\mbox{}\\
+This specifies the zero point from which time values are measured, within
+the system specified by the System attribute. Thus, a value of zero (the
+default) indicates that time values represent absolute times. Non-zero
+values may be used to indicate that the TimeFrame represents elapsed time
+since the specified origin.
+
+\end{description}
+\end{quote}
+
+For further details of these attributes you should consult their
+descriptions in \appref{ss:attributedescriptions} and for details of
+the System settings for which they are relevant, see the description
+of the System attribute (also in \appref{ss:attributedescriptions}).
+
+Note that it does no harm to assign values to qualifying attributes
+which are not relevant to the main System or TimeScale value. Any such
+values are stored, but are not used unless the System and/or TimeScale
+value is later set so that they become relevant.
+
+\cleardoublepage
+\section{\label{ss:cmpframes}Compound Frames (CmpFrames)}
+
+We now turn to a rather special form of \htmlref{Mapping}{Mapping}, the \htmlref{CmpFrame}{CmpFrame}. The
+Frames we have considered so far have been atomic, in the sense that
+they represent pre-defined elementary physical domains. A CmpFrame,
+however, is a compound \htmlref{Frame}{Frame}. In essence, it is a structure for
+containing other Frames and its purpose is to allow those Frames
+to work together in various combinations while appearing as a single
+\htmlref{Object}{Object}. A CmpFrame's behaviour is therefore not pre-defined, but is
+determined by the other Frames it contains (its ``component'' Frames).
+
+As with compound Mappings, compound Frames can be nested within each
+other, forming arbitrarily complex Frames.
+
+\subsection{Creating a CmpFrame}
+A very common use for a \htmlref{CmpFrame}{CmpFrame} within astronomy is to represent a
+``spectral cube''. This is a 3-dimensional \htmlref{Frame}{Frame} in which one of the axes
+represents position within a spectrum, and the other two axes represent
+position on the sky (or some other spatial domain such as the focal plane
+of a telescope). As an example, we create such a CmpFrame in which axes
+1 and 2 represent Right Ascension and Declination (ICRS), and axis 3
+represents wavelength (these are the default coordinate Systems
+represented by a \htmlref{SkyFrame}{SkyFrame} and a \htmlref{SpecFrame}{SpecFrame} respectively):
+
+\small
+\begin{terminalv}
+AstSkyFrame *skyframe;
+AstSpecFrame *specframe;
+AstCmpFrame *cmpframe;
+...
+skyframe = astSkyFrame( "" );
+specframe = astSpecFrame( "" );
+cmpframe = astCmpFrame( skyframe, specframe, "" );
+\end{terminalv}
+\normalsize
+
+If it was desired to make RA and Dec correspond to axes 1 and 3, with
+axis 2 being the spectral axis, then the axes of the CmpFrame created
+above would need to be permuted as follows:
+
+\small
+\begin{terminalv}
+int perm[ 3 ];
+...
+
+perm[ 0 ] = 0;
+perm[ 1 ] = 2;
+perm[ 2 ] = 1;
+astPermAxes( cmpframe, perm );
+\end{terminalv}
+\normalsize
+
+\subsection{The Attributes of a CmpFrame}
+
+A \htmlref{CmpFrame}{CmpFrame} \emph{is a} \htmlref{Frame}{Frame} and so has all the attributes of a Frame.
+The default value for the \htmlref{Domain}{Domain} attribute for a CmpFrame is formed by
+concatenating the Domains of the two component Frames, separated by a
+minus sign (``-'').\footnote{If both component Frames have blank Domains,
+then the default Domain for the CmpFrame is the string ``CMP''.} The (fixed)
+value for its \htmlref{System}{System} attribute is ``Compound''.\footnote{Any attempt to
+change the System value of a CmpFrame is ignored.} A CmpFrame has no
+further attributes over and above those common to all Frames. However,
+attributes of the two component Frames can be accessed as if they were
+attributes of the CmpFrame, as described below.
+
+Frame attributes which are specific to individual axes (such as Label(2),
+Format(1), \emph{etc}) simply mirror the corresponding axes of the
+relevant component Frame. That is, if the ``Label(2)'' attribute of a
+CmpFrame is accessed, the CmpFrame will forward the access request to the
+component Frame which contains axis 2. Thus, default values for axis
+attributes will be the same as those provided by the component Frames.
+
+An axis index can optionally be appended to the name of Frames attributes
+which do not normally have such an index (System, Domain, \htmlref{Epoch}{Epoch}, \htmlref{Title}{Title},
+\emph{etc}). If this is done, the access request is forwarded to the
+component Frame containing the indicated axis. For instance, if a
+CmpFrame contains a \htmlref{SpecFrame}{SpecFrame} and a \htmlref{SkyFrame}{SkyFrame} in that order, and the axes
+have not been permuted, then getting the value of attribute ``System'' will
+return  ``Compound'' as mentioned above (that is, the System value of the
+CmpFrame as a whole), whereas getting the value of attribute
+``System(1)'' will return ``Spectral''(that is, the System value of the
+component Frame containing axis 1 --- the SpecFrame).
+
+This technique is not limited to attributes common to all Frames. For
+instance, the SkyFrame class defines an attribute called \htmlref{Equinox}{Equinox} which is
+not held by other classes of Frames. To set a value for the Equinox
+attribute of the SkyFrame contained within the above CmpFrame, assign the
+value to the ``Equinox(2)'' attribute of the CmpFrame. Since the SkyFrame
+defines both axes 2 and 3 of the CmpFrame, we could equivalently have set
+a value for ``Equinox(3)'' since this would also result in the attribute
+access being forwarded to the SkyFrame.
+
+Finally, if an attribute is not qualified by a axis index, attempts will
+be made to access it using each of the CmpFrame axes in turn. Using the
+above example of the spectral cube, if an attempt was made to get the
+value of attribute ``Equinox'' (with no axis index), each axis in turn
+would be used. Since axis 1 is contained within a SpecFrame, the first
+attempt would fail since the SpecFrame class does not have an Equinox
+attribute. However, the second attempt would succeed because axis 2 is
+contained within a SkyFrame which \emph{does} have an Equinox attribute. Thus
+the returned attribute value would be that obtained from the SkyFrame
+containing axis 2. When getting or testing an attribute value, the
+returned value is determined by the \emph{first} axis which recognises
+the attribute. When setting an attribute value, \emph{all} axes
+which recognises the attribute have the attribute value set to the given
+value. Likewise, when clearing an attribute value, all axes
+which recognises the attribute have the attribute value cleared.
+
+\cleardoublepage
+\section{\label{ss:introducingconversion}An Introduction to Coordinate System Conversions}
+
+In this section, we start to look at techniques for converting between
+different coordinate systems.  At this stage, the tools we have available
+are Frames (\secref{ss:frames}), SkyFrames (\secref{ss:skyframes}),
+SpecFrames (\secref{ss:specframes}), TimeFrames (\secref{ss:timeframes}) and
+various Mappings (\secref{ss:mappings}). These are sufficient to allow us to
+begin examining the problem, but more sophisticated approaches will also emerge
+later (\secref{ss:framesetconverting}).
+
+\subsection{\label{ss:convertingskyframes}Converting between Celestial Coordinate Systems}
+
+We begin by examining how to convert between two celestial coordinate
+systems represented by SkyFrames, as this is both an illuminating and
+practical example.  Consider the problem of converting celestial
+coordinates between:
+
+\begin{enumerate}
+\item The old FK4 system, with no E terms, a Besselian epoch of
+1958.0 and a Besselian equinox of 1960.0.
+
+\item An ecliptic coordinate system based on the mean equinox and
+ecliptic of Julian epoch 2010.5.
+\end{enumerate}
+
+This example is arbitrary but not completely unrealistic. Unless you
+already have expertise with such conversions, you are unlikely to find
+it straightforward.
+
+Using AST, we begin by creating two SkyFrames to represent these
+coordinate systems, as follows:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstSkyFrame *skyframe1, *skyframe2;
+
+...
+
+skyframe1 = astSkyFrame( "System=FK4-NO-E, Epoch=B1958, Equinox=B1960" );
+skyframe2 = astSkyFrame( "System=Ecliptic, Equinox=J2010.5" );
+\end{terminalv}
+\normalsize
+
+Note how specifying the coordinate systems consists simply of
+initialising the attributes of each \htmlref{SkyFrame}{SkyFrame} appropriately.  The next
+step is to find a way of converting between these SkyFrames. This is
+done using \htmlref{astConvert}{astConvert}, as follows:
+
+\small
+\begin{terminalv}
+AstFrameSet *cvt;
+
+...
+
+cvt = astConvert( skyframe1, skyframe2, "" );
+if ( cvt == AST__NULL ) {
+   <conversion is not possible>
+} else {
+   <conversion is possible>
+}
+\end{terminalv}
+\normalsize
+
+The third argument of astConvert is not used here and should be an
+empty string.
+
+astConvert will return a null result, AST\_\_NULL (as defined in the
+``ast.h'' header file), if conversion is not possible. In this
+example, conversion is possible, so it will return a pointer to a new
+\htmlref{Object}{Object} that describes the conversion.
+
+The Object returned is called a \htmlref{FrameSet}{FrameSet}. We have not discussed
+FrameSets yet (\secref{ss:framesets}), but for the present purposes we
+can consider them simply as Objects that can behave both as Mappings
+and as Frames. It is the FrameSet's behaviour as a \htmlref{Mapping}{Mapping} in which we
+are mainly interested here, because the Mapping it implements is the
+one we require---\emph{i.e.}\ it converts between the two celestial
+coordinate systems (\secref{ss:framesetsfromconvert}).
+
+For example, if ``alpha1'' and ``delta1'' are two arrays containing
+the longitude and latitude, in radians, of N points on the sky in the
+original coordinate system (corresponding to ``skyframe1''), then they
+could be converted into the new coordinate system (represented by
+``skyframe2'') as follows:
+
+\small
+\begin{terminalv}
+#define N 10
+double alpha1[ N ], delta1[ N ];
+double alpha2[ N ], delta2[ N ];
+
+...
+
+astTran2( cvt, N, alpha1, delta1, 1, alpha2, delta2 );
+\end{terminalv}
+\normalsize
+
+The new coordinates are returned \emph{via} the ``alpha2'' and
+``delta2'' arrays.  To transform coordinates in the opposite
+direction, we simply invert the 5th (boolean int) argument to
+\htmlref{astTran2}{astTran2}, as follows:
+
+\small
+\begin{terminalv}
+astTran2( cvt, N, alpha2, delta2, 0, alpha1, delta1 );
+\end{terminalv}
+\normalsize
+
+The FrameSet returned by astConvert also contains information about
+the SkyFrames used in the conversion
+(\secref{ss:framesetsfromconvert}). As we mentioned above, a FrameSet
+may be used as a \htmlref{Frame}{Frame} and in this case it behaves like the
+``destination'' Frame used in the conversion (\emph{i.e.}\ like
+``skyframe2'').  We could therefore use the ``cvt'' FrameSet to
+calculate the distance between two points (with coordinates in
+radians) in the destination coordinate system, using \htmlref{astDistance}{astDistance}:
+
+\small
+\begin{terminalv}
+double distance, point1[ 2 ], point2[ 2 ];
+
+...
+
+distance = astDistance( cvt, point1, point2 );
+\end{terminalv}
+\normalsize
+
+and the result would be the same as if the ``skyframe2'' SkyFrame had
+been used.
+
+Another way to see how the FrameSet produced by astConvert retains
+information about the coordinate systems involved is to set its \htmlref{Report}{Report}
+attribute (inherited from the Mapping class) so that it displays the
+coordinates before and after conversion (\secref{ss:transforming}):
+
+\small
+\begin{terminalv}
+astSet( cvt, "Report=1" );
+astTran2( cvt, N, alpha1, delta1, 1, alpha2, delta2 );
+\end{terminalv}
+\normalsize
+
+The output from this might look like the following:
+
+\begin{terminalv}
+(2:06:03.0, 34:22:39) --> (42.1087, 20.2717)
+(2:08:20.6, 35:31:24) --> (43.0197, 21.1705)
+(2:10:38.1, 36:40:09) --> (43.9295, 22.0716)
+(2:12:55.6, 37:48:55) --> (44.8382, 22.9753)
+(2:15:13.1, 38:57:40) --> (45.7459, 23.8814)
+(2:17:30.6, 40:06:25) --> (46.6528, 24.7901)
+(2:19:48.1, 41:15:11) --> (47.5589, 25.7013)
+(2:22:05.6, 42:23:56) --> (48.4644, 26.6149)
+(2:24:23.1, 43:32:41) --> (49.3695, 27.5311)
+(2:26:40.6, 44:41:27) --> (50.2742, 28.4499)
+\end{terminalv}
+
+Here, we see that the input FK4 equatorial coordinate values (given in
+radians) have been formatted automatically in sexagesimal notation
+using the conventional hours for right ascension and degrees for
+declination. Conversely, the output ecliptic coordinates are shown in
+decimal degrees, as is conventional for ecliptic coordinates. Both are
+displayed using the default precision of 7 digits.\footnote{The
+leading digit is zero and is therefore not seen in this particular
+example.}
+
+In fact, the ``cvt'' FrameSet has access to all the information in the
+original SkyFrames which were passed to astConvert. If you had set a
+new Digits attribute value for either of these, the formatting above
+would reflect the different precision you requested by displaying a
+greater or smaller number of digits.
+
+
+\subsection{\label{ss:convertingspecframes}Converting between Spectral Coordinate Systems}
+The principles described in the previous section for converting between
+celestial coordinate systems also apply to the task of converting between
+spectral coordinate systems. As an example, let's look at how we might
+convert between frequency measured in $GHz$ as measured in the rest frame
+of the telescope, and radio velocity measured in $km/s$ measured with
+respect the kinematic Local Standard of Rest.
+
+First we create a default \htmlref{SpecFrame}{SpecFrame}, and then set its attributes to
+describe the required radio velocity system (this is slightly more
+convenient, given the relatively large number of attributes, than
+specifying the attribute values in a single string such as would be
+passed to the SpecFrame constructor). We then take a copy of this
+SpecFrame, and change the attribute values so that the copy describes the
+original frequency system (modifying a copy, rather than creating a new
+SpecFrame from scratch, avoids the need to specify the epoch, reference
+position, \emph{etc} a second time since they are all inherited by the copy):
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstSpecFrame *specframe1, *specframe2;
+
+...
+
+specframe1 = astSpecFrame( "" );
+astSet( specframe1, "System=vradio" );
+astSet( specframe1, "Unit=km/s" );
+astSet( specframe1, "Epoch=1996-Oct-2 12:13:56.985" );
+astSet( specframe1, "ObsLon=W155:28:18" );
+astSet( specframe1, "ObsLat=N19:49:34" );
+astSet( specframe1, "RefRA=18:14:50.6" );
+astSet( specframe1, "RefDec=-4:40:49" );
+astSet( specframe1, "RestFreq=230.538 GHz" );
+astSet( specframe1, "StdOfRest=LSRK" );
+
+specframe2 = astCopy( specframe1 );
+astSet( specframe1, "System=freq" );
+astSet( specframe1, "Unit=GHz" );
+astSet( specframe1, "StdOfRest=Topocentric" );
+
+\end{terminalv}
+\normalsize
+
+Note, the fact that a SpecFrame has only a single axis means that we were
+able to refer to the Unit attribute without an axis index. The other
+attributes are: the time of of observation (\htmlref{Epoch}{Epoch}), the geographical
+position of the telescope (\htmlref{ObsLat}{ObsLat} \& \htmlref{ObsLon}{ObsLon}), the position of the source
+on the sky (\htmlref{RefRA}{RefRA} \& \htmlref{RefDec}{RefDec}), the rest frequency (\htmlref{RestFreq}{RestFreq}) and the
+standard of rest (\htmlref{StdOfRest}{StdOfRest}).
+
+The next step is to find a way of converting between these SpecFrames. We
+use exactly the same code that we did in the previous section where we were
+converting between celestial coordinate systems:
+
+\small
+\begin{terminalv}
+AstFrameSet *cvt;
+
+...
+
+cvt = astConvert( specframe1, specframe2, "" );
+if ( cvt == AST__NULL ) {
+   <conversion is not possible>
+} else {
+   <conversion is possible>
+}
+\end{terminalv}
+\normalsize
+
+A before, this will give us a \htmlref{FrameSet}{FrameSet} (assuming conversion is possible,
+which should always be the case for our example), and we can use the
+FrameSet to convert between the two spectral coordinate systems. We use
+\htmlref{astTran1}{astTran1} in place of \htmlref{astTran2}{astTran2}
+since a SpecFrame has only one axis (unlike a \htmlref{SkyFrame}{SkyFrame} which has two).
+
+For example, if ``frq'' is an array containing the observed frequency, in
+GHz, of N spectral channels (describe by ``specframe1''), then they
+could be converted into the new coordinate system (represented by
+``specframe2'') as follows:
+
+\small
+\begin{terminalv}
+#define N 10
+double frq[ N ];
+double vel[ N ];
+
+...
+
+astTran1( cvt, N, frq, 1, vel );
+\end{terminalv}
+\normalsize
+
+The radio velocity values are returned in the ``vel'' array.
+
+
+\subsection{Converting between Time Coordinate Systems}
+All the principles outlined in the previous section about aligning
+spectral cocordinate systems (SpecFrames) can be applied directly to the
+problem of aligning time coordinate systems (TimeFrames).
+
+\subsection{\label{ss:convertingpermutedaxes}Handling SkyFrame Axis Permutations}
+
+We can illustrate an important point if we swap the axis order of
+either \htmlref{SkyFrame}{SkyFrame} in the example above (\secref{ss:convertingskyframes})
+before identifying the conversion. Let's assume we use \htmlref{astPermAxes}{astPermAxes}
+(\secref{ss:permutingaxes}) to do this to the second SkyFrame, before
+applying \htmlref{astConvert}{astConvert}, as follows:
+
+\small
+\begin{terminalv}
+int perm[ 2 ] = { 2, 1 };
+
+...
+
+astPermAxes( skyframe2, perm );
+cvt = astConvert( skyframe1, skyframe2, "" );
+\end{terminalv}
+\normalsize
+
+Now, the destination SkyFrame system no longer represents the
+coordinate system:
+
+\begin{quote}
+(ecliptic~longitude, ecliptic~latitude)
+\end{quote}
+
+but instead represents the transposed system:
+
+\begin{quote}
+(ecliptic~latitude, ecliptic~longitude)
+\end{quote}
+
+As a consequence, when we use the \htmlref{FrameSet}{FrameSet} returned by astConvert to
+apply a coordinate transformation, we obtain something like the
+following:
+
+\begin{terminalv}
+(2:06:03.0, 34:22:39) --> (20.2717, 42.1087)
+(2:08:20.6, 35:31:24) --> (21.1705, 43.0197)
+(2:10:38.1, 36:40:09) --> (22.0716, 43.9295)
+(2:12:55.6, 37:48:55) --> (22.9753, 44.8382)
+(2:15:13.1, 38:57:40) --> (23.8814, 45.7459)
+(2:17:30.6, 40:06:25) --> (24.7901, 46.6528)
+(2:19:48.1, 41:15:11) --> (25.7013, 47.5589)
+(2:22:05.6, 42:23:56) --> (26.6149, 48.4644)
+(2:24:23.1, 43:32:41) --> (27.5311, 49.3695)
+(2:26:40.6, 44:41:27) --> (28.4499, 50.2742)
+\end{terminalv}
+
+When compared to the original (\secref{ss:convertingskyframes}), the
+output coordinate order has been swapped to compensate for the
+different destination SkyFrame axis order.
+
+In all, there are four possible axis combinations, corresponding to two
+possible axis orders for each of the source and destination SkyFrames,
+and astConvert will convert correctly between any of these.
+The point to note is that a SkyFrame contains knowledge about how to
+convert to and from other SkyFrames. Since its two axes (longitude and
+latitude) are distinguishable, the conversion is able to take account
+of the axis order.
+
+If you need to identify the axes of a SkyFrame explicitly, taking into
+account any axis permutations, the \htmlref{LatAxis}{LatAxis} and \htmlref{LonAxis}{LonAxis} attributes can be
+used. These are read-only attributes which give the indices of the
+latitude and longitude axes respectively.
+
+\subsection{\label{ss:convertingframes}Converting Between Frames}
+
+Having seen how clever SkyFrames are (\secref{ss:convertingskyframes}
+and \secref{ss:convertingpermutedaxes}), we will next examine how dumb
+a basic \htmlref{Frame}{Frame} can be in comparison. For example, if we create two
+2-dimensional Frames and use \htmlref{astConvert}{astConvert} to derive a conversion between
+them, as follows:
+
+\small
+\begin{terminalv}
+AstFrame *frame1, *frame2;
+
+...
+
+frame1 = astFrame( 2, "" );
+frame2 = astFrame( 2, "" );
+cvt = astConvert( frame1, frame2, "" );
+\end{terminalv}
+\normalsize
+
+then the coordinate transformation which the ``cvt'' \htmlref{FrameSet}{FrameSet} performs
+will be as follows:
+
+\begin{terminalv}
+(1, 2) --> (1, 2)
+(2, 4) --> (2, 4)
+(3, 6) --> (3, 6)
+(4, 8) --> (4, 8)
+(5, 10) --> (5, 10)
+\end{terminalv}
+
+This is an identity transformation, exactly the same as a \htmlref{UnitMap}{UnitMap}
+(\secref{ss:unitmapexample}). Even if we permute the axis order of our
+Frames, as we did above (\secref{ss:convertingpermutedaxes}), we will
+fare no better. The conversion between our two basic Frames will
+always be an identity transformation.
+
+The reason for this is that, unlike a \htmlref{SkyFrame}{SkyFrame}, all basic Frames start
+life the same and have axes that are indistinguishable. Therefore,
+permuting their axes doesn't make them look any different---they still
+represent the same coordinate system.
+%Actually, this behaviour isn't as dumb as it seems and can actually be
+%very useful, as the following example illustrates.
+%
+%\subsection{Distinguishable and Indistinguishable Axes}
+%
+%c+
+%Imagine you have two Frames which represent the pixel coordinates of
+%two 2-dimensional images. Let's call their axes ``X'' and ``Y''.
+%Suppose you now transpose the second image and swap its Frame axes
+%(with \htmlref{astPermAxes}{astPermAxes}) to take account of this.
+%c-
+%f+
+%Imagine you have two Frames which represent the pixel coordinates of
+%two 2-dimensional images. Let's call their axes ``X'' and ``Y''.
+%Suppose you now transpose the second image and swap its Frame axes
+%(with astPermAxes) to take account of this.
+%f-
+%
+%Next, consider what happens if you want to subtract one image from the
+%other. If you have a ``subtract'' program that is intelligent and
+%tries to align the two images for you, one of two things could happen:
+%
+%\begin{enumerate}
+%c+
+%\item If the axes are distinguishable, when your program invokes
+%astConvert it will derive a transformation between the two images
+%which swaps the X and Y coordinates (corresponding to the transposition
+%you applied to the second image). However, in aligning X-with-X and
+%Y-with-Y, this will completely undo the effects of your transposition!
+%c-
+%f+
+%\item If the axes are distinguishable, when your program invokes
+%AST\_CONVERT it will derive a transformation between the two images
+%which swaps the X and Y coordinates (corresponding to the transposition
+%you applied to the second image). However, in aligning X-with-X and
+%Y-with-Y, this will completely undo the effects of your transposition!
+%f-
+%
+%\item If the axes are indistinguishable, the transformation between
+%the two images will always be an identity
+%(\secref{ss:convertingframes}). Therefore, your program will align
+%X-with-Y and Y-with-X, so that you see the effects of your earlier
+%transposition of the second image.
+%\end{enumerate}
+%
+%Clearly, if we are considering pixel coordinates, the latter behaviour
+%is preferable, since there would be no point in implementing an image
+%transposition program if we could never see the effects of it. This
+%indicates that a basic Frame, with is indistinguishable axes, is the
+%correct type of \htmlref{Object}{Object} to represent a pixel coordinate system, where
+%this behaviour is necessary.
+%
+%Conversely, the former behaviour would be more useful if the axes we
+%were considering were, say, wavelength (in nm) and slit position (in
+%mm). In this case, we would expect our ``subtract'' program to
+%subtract data at corresponding wavelengths and slit positions, not
+%just at corresponding pixels. This case requires distinguishable axes,
+%so that corresponding axes in the two images can be matched up, just
+%as happens with a SkyFrame (\secref{ss:convertingpermutedaxes}).
+%
+%Of course, there may also be intermediate cases, where some axes are
+%distinguishable and others aren't.
+
+\subsection{\label{ss:alignmentsystem}The Choice of Alignment System}
+
+In practice, when AST is asked to find a conversion between two Frames
+describing two different coordinate systems on a given physical domain,
+it uses an intermediate ``alignment'' system. Thus, when finding a
+conversion from system A to system B, AST first finds the \htmlref{Mapping}{Mapping} from
+system A to some alignment system, system C, and then finds the Mapping
+from this system C to the required system B. It finally concatenates
+these two Mappings to get the Mapping from system A to system B.
+
+One advantage of this is that it cuts down the number of conversion
+algorithms required. If there are $N$ different Systems which may be used
+to describe positions within the \htmlref{Domain}{Domain}, then this approach requires
+about $2*N$ conversion algorithms to be written. The alternative approach
+of going directly from system A to system B would require about $N*N$
+conversion algorithms.
+
+In addition, the use of an intermediate alignment system highlights the
+nature of the conversion process. What do we mean by saying that a
+Mapping ``converts a position in one coordinate system into the
+corresponding position in another''? In practice, it means that the input
+and output coordinates correspond to the same coordinates \emph{in some
+third coordinate system}. The choice of this third coordinate system, the
+``alignment'' system, can completely alter the nature of the Mapping. The
+\htmlref{Frame}{Frame} class has an attribute called \htmlref{AlignSystem}{AlignSystem} which can be used to
+specify the alignment system.
+
+As an example, consider the case of aligning two spectra calibrated in
+radio velocity, but each with a different rest frequency (each spectrum
+will be described by a \htmlref{SpecFrame}{SpecFrame}). Since the rest frequencies differ, a
+given velocity will correspond to different frequencies in the two
+spectra. So when we come to ``align'' these two spectra (that is, find a
+Mapping which converts positions in one SpecFrame to the corresponding
+positions in the other), we have the choice of aligning the frequencies
+or aligning the velocities. Different Mappings will be required to
+describe these two forms of alignment. If we set AlignSystem to ``Freq''
+then the returned Mapping will align the frequencies described by the two
+SpecFrames. On the other hand, if we set AlignSystem to ``Vradio''
+then the returned Mapping will align the velocities.
+
+Some choices of alignment system are redundant. For instance, in the
+above example, changing the alignment system from frequency to wavelength
+has no effect on the returned Mapping: if two spectra are aligned in
+frequency they will also be aligned in wavelength (assuming the speed of
+light doesn't change).
+
+The default value for AlignSystem depends on the class of Frame. For a
+SpecFrame, the default is wavelength (or equivalently, frequency)
+since this is the system in which observations are usually made. The
+SpecFrame class also has an attribute called \htmlref{AlignStdOfRest}{AlignStdOfRest} which
+allows the standard of rest of the alignment system to be specified.
+Similarly, the \htmlref{TimeFrame}{TimeFrame} class has an attribute called \htmlref{AlignTimeScale}{AlignTimeScale}
+which allows the time scale of the alignment system to be specified.
+Currently, the \htmlref{SkyFrame}{SkyFrame} uses ICRS as the default for AlignSystem, since
+this is a close approximation to an inertial frame of rest.
+
+\cleardoublepage
+\section{\label{ss:framesets}Coordinate System Networks (FrameSets)}
+
+We saw in \secref{ss:introducingconversion} how \htmlref{astConvert}{astConvert} could be
+used to find a \htmlref{Mapping}{Mapping} that inter-relates a pair of coordinate systems
+represented by Frames. There is a limitation to this, however, in that
+it can only be applied to coordinate systems that are inter-related by
+suitable conventions. In the case of celestial coordinates, the
+relevant conventions are standards set out by the International
+Astronomical Union, and others, that define what these coordinate
+systems mean. In practice, however, the relationships between many
+other coordinate systems are also of practical importance.
+
+Consider, for example, the focal plane of a telescope upon which an
+image of the sky is falling. We could measure positions in this focal
+plane in millimetres or, if there were a detector system such as a CCD
+present, we could count pixels. We could also use celestial
+coordinates of many different kinds. All of these systems are
+equivalent in their effectiveness at specifying positions in the focal
+plane, but some are more convenient than others for particular
+purposes.
+
+Although we could, in principle, convert between all of these focal
+plane coordinate systems, there is no pre-defined convention for doing
+so. This is because the conversions required depend on where the
+telescope is pointing and how the CCD is mounted in the focal
+plane. Clearly, knowledge about this cannot be built into the AST
+library and must be supplied in some other way. Note that this is
+exactly the same problem as we met in \secref{ss:framedomains} when
+discussing the \htmlref{Domain}{Domain} attribute---\emph{i.e.}\ coordinate systems that
+apply to different physical domains require that extra information be
+supplied before we can convert between them.
+
+What we need, therefore, is a general way to describe how coordinate
+systems are inter-related, so that when there is no convention already
+in place, we can define our own. We can then look forward to
+converting, say, from pixels into galactic coordinates and {\emph{vice
+versa.}  In AST, the \htmlref{FrameSet}{FrameSet} class provides this capability.
+
+\subsection{The FrameSet Model}
+
+Consider a coordinate system (call it number 1) which is represented
+by a \htmlref{Frame}{Frame} of some kind. Now consider a \htmlref{Mapping}{Mapping} which, when applied to
+the coordinates in system 1 yields coordinates in another system,
+number 2. The Mapping therefore inter-relates coordinate systems 1 and
+2.
+
+Now consider a second Mapping which inter-relates system 1 and a
+further coordinate system, number 3. If we wanted to convert
+coordinates between systems 2 and 3, we could do so by:
+
+\begin{enumerate}
+\item Applying our first Mapping in reverse, so as to convert between
+systems 2 and 1.
+
+\item Applying the second Mapping, as given, to convert between
+systems 1 and 3.
+\end{enumerate}
+
+We are not limited to three coordinate systems, of course. In fact, we
+could continue to introduce any number of further coordinate systems,
+so long as we have a suitable Mapping for each one which relates it to
+one of the Frames already present. Continuing in this way, we can
+build up a network in which Frames are inter-related by Mappings in
+such a way that there is always a way of converting between any pair
+of coordinate systems.
+
+The \htmlref{FrameSet}{FrameSet} (Figure~\ref{fig:frameset}) encapsulates these ideas.  It
+is a network composed of Frames and associated Mappings, in which
+there is always exactly one path, \emph{via} Mappings, between any
+pair of Frames.  Since we assemble FrameSets ourselves, they can be
+used to represent any coordinate systems we choose and to set up the
+particular relationships between them that we want.
+
+\subsection{\label{ss:creatingaframeset}Creating a FrameSet}
+
+Before we can create a \htmlref{FrameSet}{FrameSet}, we must have a \htmlref{Frame}{Frame} of some kind to
+put into it, so let's create a simple one:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstFrame *frame1;
+
+...
+
+frame1 = astFrame( 2, "Domain=A" );
+\end{terminalv}
+\normalsize
+
+We have set this Frame's \htmlref{Domain}{Domain} attribute (\secref{ss:framedomains}) to
+A so that it will be distinct from the others we will be using. We can
+now create a new FrameSet containing just this Frame, as follows:
+
+\small
+\begin{terminalv}
+AstFrameSet *frameset;
+
+...
+
+frameset = astFrameSet( frame1, "" );
+\end{terminalv}
+\normalsize
+
+So far, however, this Frame isn't related to any others.
+
+\subsection{\label{ss:addingframes}Adding New Frames to a FrameSet}
+
+We can now add further Frames to the \htmlref{FrameSet}{FrameSet} created above
+(\secref{ss:creatingaframeset}). To do so, we must supply a new \htmlref{Frame}{Frame}
+and an associated \htmlref{Mapping}{Mapping} that relates it to any of the Frames that
+are already present (there is only one present so far).  To keep the
+example simple, we will just use a \htmlref{ZoomMap}{ZoomMap} that multiplies coordinates
+by 10. The required Objects are created as follows:
+
+\small
+\begin{terminalv}
+AstFrame *frame2;
+AstMapping *mapping12;
+
+...
+
+frame2 = astFrame( 2, "Domain=B" );
+mapping12 = astZoomMap( 2, 10.0, "" );
+\end{terminalv}
+\normalsize
+
+To add the new Frame into our FrameSet, we use the \htmlref{astAddFrame}{astAddFrame}
+function:
+
+\small
+\begin{terminalv}
+astAddFrame( frameset, 1, mapping12, frame2 );
+\end{terminalv}
+\normalsize
+
+Whenever a Frame is added to a FrameSet, it is assigned an integer
+index. This index starts with 1 for the initial Frame used to create
+the FrameSet (\secref{ss:creatingaframeset}) and increments by one
+every time a new Frame is added. This index is the primary way of
+identifying the Frames within a FrameSet.
+
+When a Frame is added, we also have to specify which of the existing
+ones the new Frame is related to. Here, we chose number 1, the only
+one present so far, and the new one we added became number 2.
+
+Note that a FrameSet does not make copies of the Frames and Mappings
+that you insert into it. Instead, it holds pointers to them. This
+means that if you retain the original pointers to these Objects and
+alter them, you will indirectly be altering the FrameSet's
+contents. You can, of course, always use \htmlref{astCopy}{astCopy}
+(\secref{ss:copyingobjects}) to make a separate copy of any \htmlref{Object}{Object} if
+you need to ensure its independence.
+
+We could also add a third Frame into our FrameSet, this time defining
+a coordinate system which is reached by multiplying the original
+coordinates (of ``frame1'') by 5:
+
+\small
+\begin{terminalv}
+astAddFrame( frameset, 1, astZoomMap( 2, 5.0, "" ), astFrame( 2, "Domain=C" ) );
+\end{terminalv}
+\normalsize
+
+Here, we have avoided storing unnecessary pointer values by using
+function invocations directly as arguments for astAddFrame. This
+assumes that we are using \htmlref{astBegin}{astBegin} and \htmlref{astEnd}{astEnd} (\secref{ss:contexts}) to
+ensure that Objects are correctly deleted when no longer required.
+
+   Our example FrameSet now contains three Frames and two Mappings with
+   the arrangement shown in Figure~\ref{fig:fsexample}.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun211_figures/fsexample}
+   \caption[An example FrameSet.]{An example FrameSet, in which Frames~2 and 3 are related to
+   Frame~1 by multiplying its coordinates by factors of 10 and 5
+   respectively. The FrameSet's \htmlref{Base}{Base} attribute has the value 1 and its
+   \htmlref{Current}{Current} attribute has the value 3. The transformation performed when
+   the FrameSet is used as a Mapping (\emph{i.e.}\ from its base to
+   its current Frame) is shown in bold.}
+   \label{fig:fsexample}
+   \end{center}
+   \end{figure}
+   The total number of Frames is given by its read-only \htmlref{Nframe}{Nframe} attribute.
+
+\subsection{\label{ss:baseandcurrent}The Base and Current Frames}
+
+At all times, one of the Frames in a \htmlref{FrameSet}{FrameSet} is designated to be its
+\emph{base} \htmlref{Frame}{Frame} and one to be its \emph{current} Frame
+(Figure~\ref{fig:fsexample}). These Frames are identified by two
+integer FrameSet attributes, \htmlref{Base}{Base} and \htmlref{Current}{Current}, which hold the indices
+of the nominated Frames within the FrameSet.
+
+The existence of the base and current Frames reflects an important
+application of FrameSets, which is to attach coordinate systems to
+entities such as data arrays, data files, plotting surfaces (for
+graphics), \emph{etc.}  In this context, the base Frame represents the
+``native'' coordinate system of the attached entity---for example, the
+pixel coordinates of an image or the intrinsic coordinates of a
+plotting surface. The other Frames within the FrameSet represent
+alternative coordinate systems which may also be used to refer to
+positions within that entity.  The current Frame represents the
+particular coordinate system which is currently selected for use. For
+instance, if an image were being displayed, you would aim to label it
+with coordinates corresponding to the current Frame. In order to see a
+different coordinate system, a software user would arrange for a
+different Frame to be made current.
+
+The choice of base and current Frames may be changed at any time,
+simply by assigning new values to the FrameSet's Base and Current
+attributes. For example, to make the Frame with index 3 become the
+current Frame, you could use:
+
+\small
+\begin{terminalv}
+astSetI( frameset, "Current", 3 );
+\end{terminalv}
+\normalsize
+
+You can nominate the same Frame to be both the base and current Frame
+if you wish.
+\label{ss:baseandcurrentdefault}
+
+By default (\emph{i.e.}\ if the Base or Current attribute is un-set),
+the first Frame added to a FrameSet becomes its base Frame and the
+last one added becomes its current Frame.\footnote{Although this is
+reversed if the FrameSet's \htmlref{Invert}{Invert} attribute is non-zero.} Whenever a
+new Frame is added to a FrameSet, the Current attribute is modified so
+that the new Frame becomes the current one. This behaviour is
+reflected in the state of the example FrameSet in
+Figure~\ref{fig:fsexample}.
+
+\subsection{\label{ss:astbaseandastcurrent}Referring to the Base and Current Frames}
+
+It is often necessary to refer to the base and current Frames
+(\secref{ss:baseandcurrent}) within a \htmlref{FrameSet}{FrameSet}, but it can be
+cumbersome having to obtain their indices from the \htmlref{Base}{Base} and \htmlref{Current}{Current}
+attributes on each occasion. To make this easier, two macros,
+AST\_\_BASE and AST\_\_CURRENT, are defined in the ``ast.h'' header
+file and may be used to represent the indices of the base and current
+Frames respectively. They may be used whenever a \htmlref{Frame}{Frame} index is
+required.
+
+For example, when adding a new Frame to a FrameSet
+(\secref{ss:addingframes}), you could use the following to indicate
+that the new Frame is related to the existing current Frame, whatever
+its index happens to be:
+
+\small
+\begin{terminalv}
+AstFrame *frame;
+AstMapping *mapping;
+
+...
+
+astAddFrame( frameset, AST__CURRENT, mapping, frame );
+\end{terminalv}
+\normalsize
+
+Of course, the Frame you added would then become the new current
+Frame.
+
+\subsection{\label{ss:framesetasmapping}Using a FrameSet as a Mapping}
+
+The \htmlref{FrameSet}{FrameSet} class inherits properties and behaviour from the \htmlref{Frame}{Frame}
+class (\secref{ss:frames}) and, in turn, from the \htmlref{Mapping}{Mapping} class
+(\secref{ss:mappings}). Its behaviour when used as a Mapping is
+particularly important.
+
+Consider, for instance, passing a FrameSet pointer to a coordinate
+transformation function such as \htmlref{astTran2}{astTran2}:
+
+\small
+\begin{terminalv}
+#define N 10
+double xin[ N ], yin[ N ], xout[ N ], yout[ N ];
+
+...
+
+astTran2( frameset, N, xin, yin, 1, xout, yout );
+\end{terminalv}
+\normalsize
+
+The coordinate transformation applied by this FrameSet would be the
+one which converts between its base and current Frames. Using the
+FrameSet in Figure~\ref{fig:fsexample}, for example, the coordinates
+would be multiplied by a factor of 5.  If we instead requested the
+FrameSet's inverse transformation, we would be transforming from its
+current Frame to its base Frame, so our example FrameSet would then
+multiply by a factor of 0.2.
+
+Whenever the choice of base and current Frames changes, the
+transformations which a FrameSet performs when used as a Mapping also
+change to reflect this. The \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes may also change in
+consequence, because they are determined by the numbers of axes in the
+FrameSet's base and current Frames respectively. These numbers need
+not necessarily be equal, of course.
+
+Like any Mapping, a FrameSet may also be inverted by changing the
+boolean sense of its \htmlref{Invert}{Invert} attribute, \emph{e.g.}\ using \htmlref{astInvert}{astInvert}
+(\secref{ss:invertingmappings}). If this is happens, the values of the
+FrameSet's \htmlref{Base}{Base} and \htmlref{Current}{Current} attributes are interchanged, along with
+its Nin and Nout attributes, so that its base and current Frames swap
+places. When used as a Mapping, the FrameSet will therefore perform
+the inverse transformation to that which it performed previously.
+
+To summarise, a FrameSet may be used exactly like any other Mapping
+which inter-relates the coordinate systems described by its base and
+current Frames.
+
+\subsection{\label{ss:extractingamapping}Extracting a Mapping from a FrameSet}
+
+Although it is very convenient to use a \htmlref{FrameSet}{FrameSet} when a \htmlref{Mapping}{Mapping} is
+required (\secref{ss:framesetasmapping}), a FrameSet necessarily
+contains additional information and sometimes this might cause
+inefficiency or confusion.  For example, if you wanted to use a
+Mapping contained in one FrameSet and insert it into another, it would
+probably not be efficient to insert the whole of the first FrameSet
+into the second one, although it would work.
+
+In such a situation, the \htmlref{astGetMapping}{astGetMapping} function allows you to extract
+a Mapping from a FrameSet. You do this by specifying the two Frames
+which the Mapping should inter-relate using their indices within the
+FrameSet. For example:
+
+\small
+\begin{terminalv}
+map = astGetMapping( frameset, 2, 3 );
+\end{terminalv}
+\normalsize
+
+would return a pointer to a Mapping that converted between Frames~2
+and 3 in the FrameSet. Its inverse transformation would then convert
+in the opposite direction, \emph{i.e.}\ between Frames~3 and 2.  Note
+that this Mapping might not be independent of the Mappings contained
+within the FrameSet---\emph{i.e.}\ they may share sub-Objects---so
+\htmlref{astCopy}{astCopy} should be used to make a copy if you need to guarantee
+independence (\secref{ss:copyingobjects}).
+
+Very often, the Mapping returned by astGetMapping will be a compound
+Mapping, or \htmlref{CmpMap}{CmpMap} (\secref{ss:cmpmaps}). This reflects the fact that
+conversion between the two Frames may need to be done \emph{via} an
+intermediate coordinate system so that several stages may be involved.
+You can, however, easily simplify this Mapping (where this is possible)
+by using the \htmlref{astSimplify}{astSimplify} function (\secref{ss:simplifyingcmpmaps}) and
+this is recommended if you plan to use it for transforming a large
+amount of data.
+
+\subsection{\label{ss:framesetasframe}Using a FrameSet as a Frame}
+
+A \htmlref{FrameSet}{FrameSet} can also be used as a \htmlref{Frame}{Frame}, in which capacity it almost
+always behaves as if its current Frame had been used instead. For
+example, if you request the \htmlref{Title}{Title} attribute of a FrameSet using:
+
+\small
+\begin{terminalv}
+const char *title;
+
+...
+
+title = astGetC( frameset, "Title" );
+\end{terminalv}
+\normalsize
+
+the result will be the Title of the current Frame, or a suitable
+default if the current Frame's Title attribute is un-set. The same
+also applies to other attribute operations---\emph{i.e.}\ setting,
+clearing and testing attributes.  Most attributes shared by both
+Frames and FrameSets behave in this way, such as \htmlref{Naxes}{Naxes}, \htmlref{Label(axis)}{Label(axis)},
+\htmlref{Format(axis)}{Format(axis)}, \emph{etc.} There are, however, a few exceptions:
+
+\begin{quote}
+\begin{description}
+\item[\htmlref{Class}{Class}]\mbox{}\\
+Has the value ``FrameSet''.
+
+\item[\htmlref{ID}{ID}]\mbox{}\\
+Identifies the particular FrameSet (not its current Frame).
+
+\item[\htmlref{Nin}{Nin}]\mbox{}\\
+Equals the number of axes in the FrameSet's base Frame.
+
+\item[\htmlref{Invert}{Invert}]\mbox{}\\
+Is independent of any of the Objects within the FrameSet.
+
+\item[\htmlref{Nobject}{Nobject}]\mbox{}\\
+Counts the number of active FrameSets.
+
+\item[\htmlref{RefCount}{RefCount}]\mbox{}\\
+Counts the number of active pointers to the FrameSet (not to its
+current Frame).
+\end{description}
+\end{quote}
+
+Note that the set of attributes possessed by a FrameSet can vary,
+depending on the nature of its current Frame. For example, if the
+current Frame is a \htmlref{SkyFrame}{SkyFrame} (\secref{ss:skyframes}), then the FrameSet
+will acquire an \htmlref{Equinox}{Equinox} attribute from it which can be set, enquired,
+\emph{etc.}  However, if the current Frame is changed to be a basic
+Frame, which does not have an Equinox attribute, then this attribute
+will be absent from the FrameSet as well. Any attempt to reference it
+will then result in an error.
+
+\subsection{Extracting a Frame from a FrameSet}
+
+Although a \htmlref{FrameSet}{FrameSet} may be used in place of its current \htmlref{Frame}{Frame} in most
+situations, it is sometimes convenient to have direct access to a
+specified Frame within it. This may be obtained using the \htmlref{astGetFrame}{astGetFrame}
+function, as follows:
+
+\small
+\begin{terminalv}
+frame = astGetFrame( frameset, AST__BASE );
+\end{terminalv}
+\normalsize
+
+This would return a pointer (not a copy) to the base Frame within the
+FrameSet. Note the use of AST\_\_BASE
+(\secref{ss:astbaseandastcurrent}) as shorthand for the value of the
+FrameSet's \htmlref{Base}{Base} attribute, which gives the base Frame's index.
+
+\subsection{Removing a Frame from a FrameSet}
+
+Removing a \htmlref{Frame}{Frame} from a \htmlref{FrameSet}{FrameSet} is straightforward and is performed
+using the \htmlref{astRemoveFrame}{astRemoveFrame} function. You identify the Frame you wish to
+remove in the usual way, by giving its index within the FrameSet. For
+example, the following would remove the Frame with index 1:
+
+\small
+\begin{terminalv}
+astRemoveFrame( frameset, 1 );
+\end{terminalv}
+\normalsize
+
+The only restriction is that you cannot remove the last remaining
+Frame because a FrameSet must always contain at least one Frame.  When
+a Frame is removed, the Frames which follow it are re-numbered
+(\emph{i.e.}\ their indices are reduced by one) so as to preserve the
+sequence of consecutive Frame indices.  The FrameSet's \htmlref{Nframe}{Nframe}
+attribute is also decremented.
+
+If appropriate, astRemoveFrame will modify the FrameSet's \htmlref{Base}{Base} and/or
+\htmlref{Current}{Current} attributes so that they continue to identify the same Frames
+as previously. If either the base or current Frame is removed,
+however, the corresponding attribute will become un-set, so that it
+reverts to its default value (\secref{ss:baseandcurrentdefault}) and
+therefore identifies an alternative Frame.
+
+Note that it is quite permissible to remove any Frame from a FrameSet,
+even although other Frames may appear to depend on it. For example, in
+Figure~\ref{fig:fsexample}, if Frame~1 were removed, the correct
+relationship between Frames~2 and 3 would still be preserved, although
+they would be re-numbered as Frames~1 and 2.
+
+\cleardoublepage
+\section{\label{ss:fshigher}Higher Level Operations on FrameSets}
+
+\subsection{\label{ss:framesetsfromconvert}Creating FrameSets with astConvert}
+
+Before considering the important subject of using FrameSets to convert
+between coordinate systems (\secref{ss:framesetconverting}), let us
+return briefly to reconsider the output generated by \htmlref{astConvert}{astConvert}. We
+used this function earlier (\secref{ss:introducingconversion}), when
+converting between the coordinate systems represented by various kinds
+of \htmlref{Frame}{Frame}, and indicated that it returns a \htmlref{FrameSet}{FrameSet} to represent the
+coordinate conversion it identifies. We are now in a position to
+examine the structure of this FrameSet.
+
+Take our earlier example (\secref{ss:convertingskyframes}) of
+converting between the celestial coordinate systems represented by two
+SkyFrames:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstFrameSet *cvt;
+AstSkyFrame *skyframe1, *skyframe2;
+
+...
+
+skyframe1 = astSkyFrame( "System=FK4-NO-E, Epoch=B1958, Equinox=B1960" );
+skyframe2 = astSkyFrame( "System=Ecliptic, Equinox=J2010.5" );
+
+cvt = astConvert( skyframe1, skyframe2, "" );
+\end{terminalv}
+\normalsize
+
+   This will produce a pointer, ``cvt'', to the FrameSet shown in
+   Figure~\ref{fig:fsconvert}.
+   \begin{figure}[bhtp]
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun211_figures/fsconvert}
+   \caption[FrameSet produced when converting between two SkyFrames.]{The FrameSet produced when astConvert is used to convert
+   between the coordinate systems represented by two SkyFrames. The
+   source \htmlref{SkyFrame}{SkyFrame} becomes the base Frame, while the destination SkyFrame
+   becomes the current Frame. The \htmlref{Mapping}{Mapping} between them implements the
+   required conversion.}
+   \label{fig:fsconvert}
+   \end{center}
+   \end{figure}
+
+As can be seen, this FrameSet contains just two Frames.  The source
+Frame supplied to astConvert becomes its base Frame, while the
+destination Frame becomes its current Frame. (The FrameSet, of course,
+simply holds pointers to these Frames, rather than making copies.) The
+Mapping which relates the base Frame to the current Frame is the one
+which implements the required conversion.
+
+As we noted earlier (\secref{ss:convertingskyframes}), the FrameSet
+returned by astConvert may be used both as a Mapping and as a Frame to
+perform most of the functions you are likely to need. However, the
+Mapping may be extracted for use on its own if necessary, using
+\htmlref{astGetMapping}{astGetMapping} (\secref{ss:extractingamapping}), for example:
+
+\small
+\begin{terminalv}
+AstMapping *mapping;
+
+...
+
+mapping = astGetMapping( cvt, AST__BASE, AST__CURRENT );
+\end{terminalv}
+\normalsize
+
+\subsection{\label{ss:framesetconverting}Converting between FrameSet Coordinate Systems}
+
+   We now consider the process of converting between the coordinate
+   systems represented by two FrameSets. This is a most important
+   operation, as a subsequent example (\secref{ss:registeringimages})
+   will show, and is illustrated in Figure~\ref{fig:fsalign}.
+   \begin{figure}
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun211_figures/fsalign}
+   \caption[Conversion between two FrameSets is performed by establishin a link between a pair of Frames, one from each FrameSet.]{Conversion
+   between two FrameSets is performed by establishing
+   a link between a pair of Frames, one from each \htmlref{FrameSet}{FrameSet}. If conversion
+   between these two Frames is possible, then a route for converting
+   between the current Frames of both FrameSets can also be found. In
+   practice, there may be many ways of pairing Frames to find the
+   ``missing link'', so the Frames' \htmlref{Domain}{Domain} attribute may be used to
+   narrow the choice.}
+   \label{fig:fsalign}
+   \end{center}
+   \end{figure}
+
+Recalling (\secref{ss:framesetasframe}) that a FrameSet will behave
+like its current \htmlref{Frame}{Frame} when necessary, conversion between two
+FrameSets is performed using \htmlref{astConvert}{astConvert}
+(\secref{ss:convertingskyframes}), but supplying pointers to FrameSets
+instead of Frames. The effect of this is to convert between the
+coordinate systems represented by the current Frames of each FrameSet:
+
+\small
+\begin{terminalv}
+AstFrameSet *frameseta, *framesetb;
+
+...
+
+cvt = astConvert( frameseta, framesetb, "SKY" );
+\end{terminalv}
+\normalsize
+
+When using FrameSets, we are presented with considerably more
+conversion options than when using Frames alone. This is because each
+current Frame is related to all the other Frames in its respective
+FrameSet. Therefore, if we can establish a link between any pair of
+Frames, one from each FrameSet, we can form a complete conversion path
+between the two current Frames (Figure~\ref{fig:fsalign}).
+
+This expanded range of options is, of course, precisely the
+intention. By connecting Frames together within a FrameSet, we have
+extended the range of coordinate systems that can be reached from any
+one of them.  We are therefore no longer restricted to converting
+between Frames with the same Domain value (\secref{ss:framedomains}),
+but can go \emph{via} a range of intermediate coordinate systems in
+order to make the connection we require. Transformation between
+different domains has therefore become possible because, in assembling
+the FrameSets, we provided the additional information needed to
+inter-relate them.
+
+It is important to appreciate, however, that the choice of ``missing
+link'' is crucial in determining the conversion that results.
+Although each FrameSet may be perfectly self-consistent internally,
+this does not mean that all conversion paths through the combined
+network of Mappings are equivalent. Quite the contrary in fact:
+everything depends on where the inter-connecting link between the two
+FrameSets is made.  In practice, there may be a large number of
+possible pairings of Frames and hence of possible links. Other factors
+must therefore be used to restrict the choice. These are:
+
+\begin{enumerate}
+\item Not every possible pairing of Frames is legitimate. For example,
+you cannot convert directly between a basic Frame and a \htmlref{SkyFrame}{SkyFrame} which
+belong to different classes, so such pairings will be ignored.
+
+\item In a similar way, you cannot convert directly between Frames
+with different Domain values (\secref{ss:framedomains}). If the Domain
+attribute is used consistently (typically only one Frame in each
+FrameSet will have a particular Domain value), then this further
+restricts the choice.
+
+\item The third argument of astConvert may then be used to specify
+explicitly which Domain value the paired Frames should have. You may
+also supply a comma-separated list of preferences here (see below).
+
+\item If the above steps fail to uniquely identify the link, then the
+first suitable pairing of Frames is used, so that any ambiguity is
+resolved by the order in which Frames are considered for pairing (see
+the description of the astConvert function in
+\appref{ss:functiondescriptions} for details of the search
+order).\footnote{If you find that how this ambiguity is resolved
+actually makes a difference to the conversion that results, then you
+have probably constructed a FrameSet which lacks internal
+self-consistency. For example, you might have two Frames representing
+indistinguishable coordinate systems but inter-related by a non-null
+\htmlref{Mapping}{Mapping}.}
+\end{enumerate}
+
+In the example above we supplied the string ``SKY'' as the third
+argument of astConvert. This constitutes a request that a pair of
+Frames with
+the Domain value SKY (\emph{i.e.}\ representing celestial coordinate
+systems) should be used to inter-relate the two FrameSets. Note that
+this does not specify which celestial coordinate system to use, but is
+a general request that the two FrameSets be inter-related using
+coordinates on the celestial sphere.
+
+Of course, it may be that this request cannot be met because there may
+not be a celestial coordinate system in both FrameSets. If this is
+likely to happen, we can supply a list of preferences, or a
+\emph{domain search path},
+as the third argument to astConvert, such as
+the following:
+
+\small
+\begin{terminalv}
+cvt = astConvert( frameseta, framesetb, "SKY,PIXEL,GRID," );
+\end{terminalv}
+\normalsize
+
+Now, if the two FrameSets cannot be inter-related using the SKY domain,
+astConvert will attempt to use the PIXEL domain instead. If this
+also fails, it will try the GRID domain. A blank field in the domain
+search path (here indicated by the final comma) allows any Domain
+value to be used. This can be employed as a last resort when all else
+has failed.
+
+If astConvert succeeds in identifying a conversion, it will return a
+pointer to a FrameSet (\secref{ss:framesetsfromconvert}) in which the
+source and destination Frames are inter-connected by the required
+Mapping. In this case, of course, these Frames will be the current
+Frames of the two FrameSets, but in all other respects the returned
+FrameSet is the same as when converting between Frames.
+
+Very importantly, however, astConvert may modify the FrameSets you are
+converting between. It does this, in order to indicate which pairing
+of Frames was used to inter-relate them, by changing the \htmlref{Base}{Base}
+attribute for each FrameSet so that the Frame used in the pairing
+becomes its base Frame (\secref{ss:baseandcurrent}).
+
+Finally, note that astConvert may also be used to convert between a
+FrameSet and a Frame, or \emph{vice versa}. If a pointer to a Frame is
+supplied for either the first or second argument, it will behave like
+a FrameSet containing only a single Frame.
+
+\subsection{\label{ss:registeringimages}Example---Registering Two Images}
+
+Consider two images which have been calibrated by attaching FrameSets
+to them, such that the base \htmlref{Frame}{Frame} of each \htmlref{FrameSet}{FrameSet} corresponds to the
+raw data grid coordinates of each image (the GRID domain of
+\secref{ss:domainconventions}). Suppose, also, that these FrameSets
+contain an unknown number of other Frames, representing alternative
+world coordinate systems.  What we wish to do is register these two
+images, such that we can transform from a position in the data grid of
+one into the corresponding position in the data grid of the other.
+This is a very practical example because images will typically be
+calibrated using FrameSets in precisely this way.
+
+The first step will probably involve making a copy of both FrameSets
+(using \htmlref{astCopy}{astCopy}---\secref{ss:copyingobjects}), since we will be
+modifying them. Let ``frameseta'' and ``framesetb'' be pointers to
+these copies. Since we want to convert between the base Frames of
+these FrameSets (\emph{i.e.}\ their data grid coordinates), the next
+step is to make these Frames current. This is simply done by inverting
+both FrameSets, which interchanges their base and current
+Frames. \htmlref{astInvert}{astInvert} will perform this task:
+
+\small
+\begin{terminalv}
+astInvert( frameseta );
+astInvert( framesetb );
+\end{terminalv}
+\normalsize
+
+To identify the required conversion, we now use \htmlref{astConvert}{astConvert}, supplying
+a suitable domain search path with which we would like our two images
+to be registered:
+
+\small
+\begin{terminalv}
+cvt = astConvert( frameseta, framesetb, "SKY,PIXEL,GRID" );
+if ( cvt == AST__NULL ) {
+   <no conversion was possible>
+} else {
+   <conversion was possible>
+}
+\end{terminalv}
+\normalsize
+
+The effects of this are:
+
+\begin{enumerate}
+\item astConvert first attempts to register the two images on the
+celestial sphere (\emph{i.e.}\ using the SKY domain). To do this, it
+searches for a celestial coordinate system, although not necessarily
+the same one, attached to each image.  If it finds a suitable pair of
+coordinate systems, it then registers the images by matching
+corresponding positions on the sky.
+
+\item If this fails, astConvert next tries to match positions in the
+PIXEL domain (\secref{ss:framedomains}). If it succeeds, the two
+images will then be registered so that their corresponding pixel
+positions correspond. If the PIXEL domain is offset from the data grid
+(as typically happens in data reduction systems which implement a
+``pixel origin''), then this will be correctly accounted for.
+
+\item If this also fails, the GRID domain is finally used. This will
+result in image registration by matching corresponding points in the
+data grids used by both images. This means they will be
+aligned so that the first element their data arrays correspond.
+
+\item If all of the above fail, astConvert will return the value
+AST\_\_NULL. Otherwise a pointer to a FrameSet will be returned.
+\end{enumerate}
+
+The resulting ``cvt'' FrameSet may then be used directly
+(\secref{ss:convertingskyframes}) to convert between positions in the
+data grid of the first image and corresponding positions in the data
+grid of the second image.
+
+To determine which domain was used to achieve registration,
+we can use the fact that the \htmlref{Base}{Base} attribute of each FrameSet is set by
+astConvert to indicate which intermediate Frames were used. We
+can therefore simply invert either FrameSet (to make its base Frame
+become the current one) and then enquire the \htmlref{Domain}{Domain} value:
+
+\small
+\begin{terminalv}
+const char *domain;
+
+...
+
+astInvert( frameseta );
+domain = astGetC( frameseta, "Domain" );
+\end{terminalv}
+\normalsize
+
+If conversion was successful, the result will be one of the strings
+``SKY'', ``PIXEL'' or ``GRID''.
+
+\subsection{\label{ss:remapframe}Re-Defining a FrameSet Coordinate System}
+
+As discussed earlier (\secref{ss:baseandcurrent}), an important
+application of a \htmlref{FrameSet}{FrameSet} is to allow coordinate system information to
+be attached to entities such as images in order to calibrate them. In
+addition, one of the main objectives of AST is to simplify the
+propagation of such information through successive stages of data
+processing, so that it remains consistent with the associated image
+data.
+
+In such a situation, the FrameSet's base \htmlref{Frame}{Frame} would correspond with
+the image's data grid coordinates and its other Frames (if any) with
+the various alternative world coordinate systems associated with the
+image.  If the data processing being performed does not change the
+relationship between the image's data grid coordinates and any of the
+associated world coordinate systems, then propagation of the WCS
+information is straightforward and simply involves copying the
+FrameSet associated with the image.
+
+If any of these relationships change, however, then corresponding
+changes must be made to the way Frames within the FrameSet are
+inter-related. By far the most common case occurs when the image
+undergoes some geometrical transformation resulting in ``re-gridding''
+on to another data grid, but the same principles can be applied to any
+re-definition of a coordinate system.
+
+To pursue the re-gridding example, we would need to modify our
+FrameSet to account for the fact that the image's data grid coordinate
+system (corresponding to the FrameSet's base Frame) has
+changed. Looking at the steps needed in detail, we might proceed as
+follows:
+
+\begin{enumerate}
+\item Create a \htmlref{Mapping}{Mapping} which represents the relationship between the
+original data grid coordinate system and the new one.
+
+\item Obtain a Frame to represent the new data grid coordinate system
+(we could re-use the original base Frame here, using \htmlref{astGetFrame}{astGetFrame} to
+obtain a pointer to it).
+
+\item Add the new Frame to the FrameSet, related to the original base
+Frame by the new Mapping. This Frame now represents the new data grid
+coordinate system and is correctly related to all the other Frames
+present.\footnote{This is because any transformation to or from this
+new Frame must go \emph{via} the base Frame representing the original
+data grid coordinate system, which we assume was correctly related to
+all the other Frames present.}
+
+\item Remove the original base Frame (representing the old data grid
+coordinate system).
+
+\item Make the new Frame the base Frame and restore the original
+current Frame.
+\end{enumerate}
+
+   The effect of these steps is to change the relationship between the
+   base Frame and all the other Frames present. It is as if a new Mapping
+   has been interposed between the Frame we want to alter and all the
+   other Frames within the FrameSet (Figure~\ref{fig:fsremap}).
+   \begin{figure}[hbtp]
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun211_figures/fsremap}
+\caption[Interposing a Mapping into a FrameSet]{The effect
+   of \htmlref{astRemapFrame}{astRemapFrame} is to interpose a Mapping between
+   a nominated Frame within a FrameSet and the remaining contents of the
+   FrameSet. This effectively ``re-defines'' the coordinate system
+   represented by the affected Frame. It may be used to compensate (say)
+   for geometrical changes made to an associated image. The
+   inter-relationships between all the other Frames within the FrameSet
+   remain unchanged.}
+   \label{fig:fsremap}
+   \end{center}
+   \end{figure}
+
+Performing the steps above is rather lengthy, however, so the
+astRemapFrame function is provided to perform all of these operations
+in one go. A practical example of its use is given below
+(\secref{ss:wcsprocessingexample}).
+
+\subsection{\label{ss:wcsprocessingexample}Example---Binning an Image}
+
+As an example of using \htmlref{astRemapFrame}{astRemapFrame}, consider a case where the pixels
+of a 2-dimensional image have been binned 2$\times$2, so as to reduce
+the image size by a factor of two in each dimension.  We must now
+modify the associated \htmlref{FrameSet}{FrameSet} to reflect this change to the
+image. Much the same process would be needed for any other geometrical
+change the image might undergo.
+
+We first set up a \htmlref{Mapping}{Mapping} (a \htmlref{WinMap}{WinMap} in this case) which relates the
+data grid coordinates in the original image to those in the new one:
+
+\small
+\begin{terminalv}
+AstWinMap *winmap;
+double ina[ 2 ] = { 0.5, 0.5 };
+double inb[ 2 ] = { 2.5, 2.5 };
+double outa[ 2 ] = { 0.5, 0.5 };
+double outb[ 2 ] = { 1.5, 1.5 };
+
+...
+
+winmap = astWinMap( 2, ina, inb, outa, outb, "" );
+\end{terminalv}
+\normalsize
+
+Here, we have simply set up arrays containing the data grid
+coordinates of the bottom left and top right corners of the first
+element in the output image (``outa'' and ``outb'') and the
+corresponding coordinates in the input image (``ina'' and
+``inb''). \htmlref{astWinMap}{astWinMap} then creates a WinMap which performs the required
+transformation. We do not need to know the size of the image.
+
+We can then pass this WinMap to astRemapFrame. This modifies the
+relationship between our FrameSet's base \htmlref{Frame}{Frame} and the other Frames in
+the FrameSet, so that the base Frame represents the data grid
+coordinate system of the new image rather than the old one:
+
+\small
+\begin{terminalv}
+AstFrameSet *frameset;
+
+...
+
+astRemapFrame( frameset, AST__BASE, winmap );
+\end{terminalv}
+\normalsize
+
+Any other coordinate systems described by the FrameSet, no matter how
+many of these there might be, are now correctly associated with the
+new image.
+
+\subsection{\label{ss:framesetintegrity}Maintaining the Integrity of FrameSets}
+
+When constructing a \htmlref{FrameSet}{FrameSet}, you are provided with a framework into
+which you can place any combination of Frames and Mappings that you
+wish. There are relatively few constraints on this process and no
+checks are performed to see whether the FrameSet you construct makes
+physical sense.  It is quite possible, for example, to construct a
+FrameSet containing two identical SkyFrames which are inter-related by
+a non-unit \htmlref{Mapping}{Mapping}. AST will not object if you do this, but it makes
+no sense, because applying a non-unit Mapping to any set of celestial
+coordinates cannot yield positions that are still in the original
+coordinate system.  If you use such a FrameSet to perform coordinate
+conversions, you are likely to get unpredictable results because the
+information in the FrameSet is corrupt.
+
+It is, of course, your responsibility as a programmer to ensure the
+validity of any information which you insert into a
+FrameSet. Normally, this is straightforward and simply consists of
+formulating your problem correctly (a diagram can often help to
+clarify how coordinate systems are inter-related) and writing the
+appropriate bug-free code to construct the FrameSet. However, once you
+start to modify an existing FrameSet, there are new opportunities for
+corrupting it!
+
+Consider, for example, a FrameSet whose current \htmlref{Frame}{Frame} is a
+\htmlref{SkyFrame}{SkyFrame}. We can set a new value for this SkyFrame's \htmlref{Equinox}{Equinox} attribute
+simply by using \htmlref{astSet}{astSet} on the FrameSet, as follows:
+
+\small
+\begin{terminalv}
+astSet( frameset, "Equinox=J2010" );
+\end{terminalv}
+\normalsize
+
+The effect of this will be to change the celestial coordinate system
+which the current Frame represents. You can see, however, that this
+has the potential to make the FrameSet corrupt unless corresponding
+changes are also made to the Mapping which relates this SkyFrame to
+the other Frames within the FrameSet. In fact, it is a general rule
+that any change to a FrameSet which affects its current Frame can
+potentially require corresponding changes to the FrameSet's Mappings
+in order to maintain its overall integrity.
+
+Fortunately, once you have stored valid information in a FrameSet, AST
+will look after these details for you automatically, so that the
+FrameSet's integrity is maintained. In the example above, it would do
+this by appropriately re-mapping the current Frame (as if
+\htmlref{astRemapFrame}{astRemapFrame} had been used---\secref{ss:remapframe}) in response to
+the use of astSet. One way of illustrating this process is as follows:
+
+\small
+\begin{terminalv}
+AstSkyFrame *skyframe;
+
+...
+
+skyframe = astSkyFrame( "" );
+frameSet = astFrameSet( skyframe );
+astAddFrame( frameset, 1, astUnitMap( 2, "" ), skyframe );
+\end{terminalv}
+\normalsize
+
+This constructs a trivial FrameSet whose base and current Frames are
+both the same SkyFrame connected by a \htmlref{UnitMap}{UnitMap}. You can think of this
+as a ``pipe'' connecting two coordinate systems. At present, these two
+systems represent identical ICRS coordinates, so the FrameSet
+implements a unit Mapping. We can change the coordinate system on the
+current end of this pipe as follows:
+
+\small
+\begin{terminalv}
+astSet( frameset, "System=Ecliptic, Equinox=J2010" );
+\end{terminalv}
+\normalsize
+
+and the Mapping which the FrameSet implements would change
+accordingly. To change the coordinate system on the base end of the
+pipe, we might use:
+
+\small
+\begin{terminalv}
+astInvert( frameset );
+astSet( frameset, "System=Galactic" );
+astInvert( frameset );
+\end{terminalv}
+\normalsize
+
+The FrameSet would then convert between galactic and ecliptic
+coordinates.
+
+Note that astSet is not the only function which has this effect:
+\htmlref{astClear}{astClear} behaves similarly, as also does \htmlref{astPermAxes}{astPermAxes}
+(\secref{ss:permutingaxes}). If you need to circumvent this mechanism
+for any reason, this can be done by going behind the scenes and
+obtaining a pointer directly to the Frame you wish to modify. Consider
+the following, for example:
+
+\small
+\begin{terminalv}
+skyframe = astGetFrame( frameset, AST__CURRENT );
+astSet( skyframe, "Equinox=J2010" );
+skyframe = astAnnul( skyframe );
+\end{terminalv}
+\normalsize
+
+Here, astSet is applied to the SkyFrame pointer rather than the
+FrameSet pointer, so the usual checks on FrameSet integrity do not
+occur. The SkyFrame's Equinox attribute will therefore be modified
+without any corresponding change to the FrameSet's Mappings.  In this
+case you must take responsibility yourself for maintaining the
+FrameSet's integrity, perhaps through appropriate use of
+astRemapFrame.
+
+\subsection{Merging FrameSets}
+
+   As well as adding individual Frames to a \htmlref{FrameSet}{FrameSet}
+   (\secref{ss:addingframes}), it is also possible to add complete sets of
+   inter-related Frames which are contained within another
+   FrameSet. This, of course, corresponds to the process of merging two
+   FrameSets (Figure~\ref{fig:fsmerge}).
+   \begin{figure}[hbtp]
+   \begin{center}
+   \includegraphics[width=0.7\textwidth]{sun211_figures/fsmerge}
+   \caption[Two FrameSets in the process of being merged.]{Two FrameSets in the process of being merged using
+   \htmlref{astAddFrame}{astAddFrame}. FrameSet~B is being added to FrameSet~A by supplying a
+   new \htmlref{Mapping}{Mapping} which inter-relates a nominated \htmlref{Frame}{Frame} in A (here number~1)
+   and the current Frame of B. In the merged FrameSet, the Frames
+   contributed by B will be re-numbered to become Frames~4, 5 and 6. The
+   base Frame will remain unchanged, but the current Frame of B becomes
+   the new current Frame. Note that FrameSet~B itself is not
+   altered by this process.}
+   \label{fig:fsmerge}
+   \end{center}
+   \end{figure}
+
+
+
+This process is performed by adding one FrameSet to another using
+astAddFrame, in much the same manner as when adding a new Frame to an
+existing FrameSet (\secref{ss:addingframes}). It is simply a matter of
+providing a FrameSet pointer, instead of a Frame pointer, for the 4th
+argument. In performing the merger you must, as usual, supply a
+Mapping, but in this case the Mapping should relate the current Frame
+of the FrameSet being added to one of the Frames already present. For
+example, you might perform the merger shown in
+Figure~\ref{fig:fsmerge} as follows:
+
+\small
+\begin{terminalv}
+AstMapping *mapping;
+
+...
+
+astAddFrame( frameseta, 1, mapping, framesetb );
+\end{terminalv}
+\normalsize
+
+The Frames acquired by ``frameseta'' from the FrameSet being added
+(``framesetb'') are re-numbered so that they retain their original
+order and follow on consecutively after the Frames that were already
+present, whose indices remain unchanged. The base Frame of
+``frameseta'' remains unchanged, but the current Frame of
+``framesetb'' becomes its new current Frame. All the
+inter-relationships between Frames in both FrameSets remain in place
+and are preserved in the merged FrameSet.
+
+Note that while this process modifies the first FrameSet
+(``frameseta''), it leaves the original contents of the one being
+added (``framesetb'') unchanged.
+
+%\cleardoublepage
+%\section{\label{ss:searching}TBW - Searching for Coordinate Systems}
+
+\cleardoublepage
+\section{\label{ss:channels}Saving and Restoring Objects (Channels)}
+
+Facilities are provided by the AST library for performing input and
+output (I/O) with any kind of \htmlref{Object}{Object}. This means it is possible
+to write any Object into various external representations for
+storage, and then to read these representations back in, so as to
+restore the original Object. Typically, an Object would be written by
+one program and read back in by another.
+
+We refer to ``external representations'' in the plural because AST is
+designed to function independently of any particular data storage
+system. This means that Objects may need converting into a number of
+different external representations in order to be compatible with
+(say) the astronomical data storage system in which they will reside.
+
+In this section, we discuss the basic I/O facilities which support
+external representations based on a textual format referred to as the AST
+``native format''. These are implemented using a new kind of Object---a
+\htmlref{Channel}{Channel}. We will examine later how to use other representations, based on
+an XML format or on the use of FITS headers, for storing Objects. These
+are implemented using more specialised forms of Channel called \htmlref{XmlChan}{XmlChan}
+(\secref{ss:xmlchan}) and \htmlref{FitsChan}{FitsChan} (\secref{ss:nativefits}).
+
+\subsection{The Channel Model}
+
+The best way to start thinking about a \htmlref{Channel}{Channel} is like a C file
+stream, and to think of the process of creating a Channel as that
+of opening a file and obtaining a FILE pointer.  Subsequently, you can
+read and write Objects \emph{via} the Channel.
+
+This analogy is not quite perfect, however, because a Channel has, in
+principle, two ``files'' attached to it. One is used when reading, and
+the other when writing. These are termed the Channel's \emph{source}
+and \emph{sink} respectively. In practice, the source and sink may
+both be the same, in which case the analogy with the C file stream is
+correct, but this need not always be so. It is not necessarily so with
+the basic Channel, as we will now see (\secref{ss:creatingachannel}).
+
+\subsection{\label{ss:creatingachannel}Creating a Channel}
+
+The process of creating a \htmlref{Channel}{Channel} is straightforward. As you
+might expect, it uses the constructor function \htmlref{astChannel}{astChannel}:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstChannel *channel;
+
+...
+
+channel = astChannel( NULL, NULL, "" );
+\end{terminalv}
+\normalsize
+
+The first two arguments to astChannel specify the external source and
+sink that the Channel is to use. There arguments are pointers to C
+functions and we will examine their use in more detail later
+(\secref{ss:channelsource} and \secref{ss:channelsink}).
+
+In this very simple example we have supplied NULL pointers for both
+the source and sink functions. This requests the default behaviour,
+which means that textual input will be read from the program's
+standard input stream (typically, this means your keyboard) while
+textual output will go to the standard output stream (typically
+appearing on your screen). On UNIX systems, of course, either of these
+streams can easily be redirected to files. This default behaviour can be
+changed by assigning values to the Channel's \htmlref{SinkFile}{SinkFile} and/or \htmlref{SourceFile}{SourceFile}
+attributes. These attributes specify the paths to text files that are to
+be used in place of the standard input and output streams.
+
+\subsection{\label{ss:writingtoachannel}Writing Objects to a Channel}
+
+The process of saving Objects is very straightforward. You can
+simply write any \htmlref{Object}{Object} to a \htmlref{Channel}{Channel} using the \htmlref{astWrite}{astWrite}
+function, as follows:
+
+\small
+\begin{terminalv}
+int nobj;
+AstObject *object;
+
+...
+
+nobj = astWrite( channel, object );
+\end{terminalv}
+\normalsize
+
+The effect of this will be to produce a textual description of the
+Object which will appear, by default, on your program's standard
+output stream. Any class of Object may be converted into text in this
+way.
+
+astWrite returns a count of the number of Objects written. Usually,
+this will be one, unless the Object supplied cannot be
+represented. With a basic Channel all Objects can be represented, so a
+value of one will always be returned unless there has been an
+error. We will see later, however, that more specialised forms of
+Channel may impose restrictions on the kind of Object you can write
+(\secref{ss:foreignfitslimitations}). In such cases, astWrite may
+return zero to indicate that the Object was not acceptable.
+
+\subsection{\label{ss:readingfromachannel}Reading Objects from a Channel}
+
+Before discussing the format of the output produced above
+(\secref{ss:writingtoachannel}), let us consider how to read it back,
+so as to reconstruct the original \htmlref{Object}{Object}. Naturally, we would first
+need to save the output in a file. We can do that either by using the
+\htmlref{SinkFile}{SinkFile} attribute, or (on UNIX systems), by redirecting standard output
+to a file using a shell command like:
+
+\small
+\begin{terminalv}
+program1 >file
+\end{terminalv}
+\normalsize
+
+Within a subsequent program, we can read this Object back in by
+using the \htmlref{astRead}{astRead} function, having first created a suitable
+\htmlref{Channel}{Channel}:
+
+\small
+\begin{terminalv}
+object = astRead( channel );
+\end{terminalv}
+\normalsize
+
+By default, this function will read from the standard input stream
+(the default source for a basic Channel), so we would need to ensure
+that our second program reads its input from the file in which the
+Object description is stored. On UNIX systems, we could again use a
+shell redirection command such as:
+
+\small
+\begin{terminalv}
+program2 <file
+\end{terminalv}
+\normalsize
+
+Alternatively, we could have assigned a value to the SinkFile attribute
+before invoking
+astRead.
+
+\subsection{Saving and Restoring Multiple Objects}
+
+I/O operations performed on a basic \htmlref{Channel}{Channel} are sequential. This
+means that if you write more than one \htmlref{Object}{Object} to a Channel,
+each new Object's textual description is simply appended to the
+previous one. You can store any number of Objects in this way,
+subject only to the storage space you have available.
+
+After you read an Object back from a basic Channel, the
+Channel is ``positioned'' at the end of that Object's
+textual description. If you then perform another read, you will
+read the next Object's textual description and therefore
+retrieve the next Object.  This process may be repeated to read
+each Object in turn. When there are no more Objects to be
+read, \htmlref{astRead}{astRead} will return the value AST\_\_NULL to indicate an
+\emph{end-of-file}.
+
+\subsection{\label{ss:validatinginput}Validating Input}
+
+The pointer returned by \htmlref{astRead}{astRead} (\secref{ss:readingfromachannel}) could
+identify any class of \htmlref{Object}{Object}---this is determined entirely by the
+external data being read. If it is necessary to test for a particular
+class (say a \htmlref{Frame}{Frame}), this may be done as follows using the appropriate
+member of the \htmlref{astIsA$<$Class$>$}{astIsA$<$Class$>$} family of functions:
+
+\small
+\begin{terminalv}
+int ok;
+
+...
+
+ok = astIsAFrame( object );
+\end{terminalv}
+\normalsize
+
+Note, however, that this will accept any Frame, so would be equally
+happy with a basic Frame or a \htmlref{SkyFrame}{SkyFrame}.  An alternative validation
+strategy would be to obtain the value of the Object's \htmlref{Class}{Class} attribute
+and then test this character string, as follows:
+
+\small
+\begin{terminalv}
+#include <string.h>
+
+...
+
+ok = !strcmp( astGetC( object, "Class" ), "Frame" );
+\end{terminalv}
+\normalsize
+
+This would only accept a basic Frame and would reject a SkyFrame.
+
+\subsection{Storing an ID String with an Object}
+
+Occasionally, you may want to store a number of Objects and later
+retrieve them and use each for a different purpose. If the Objects are
+of the same class, you cannot use the \htmlref{Class}{Class} attribute to distinguish
+them when you read them back
+(\emph{c.f.}~\secref{ss:validatinginput}). Although relying on the
+order in which they are stored is a possible solution, this becomes
+complicated if some of the Objects are optional and may not always be
+present. It also makes extending your data format in future more
+difficult.
+
+To help with this, every AST \htmlref{Object}{Object} has an \htmlref{ID}{ID} attribute and an \htmlref{Ident}{Ident}
+attribute, both of which allows you, in effect, to attach a textual
+identification label to it. You simply set the ID or Ident attribute before
+writing the Object:
+
+\small
+\begin{terminalv}
+astSet( object, "ID=Calibration" );
+nobj = astWrite( channel, object );
+\end{terminalv}
+\normalsize
+
+You can then test its value after you read the Object back:
+
+\small
+\begin{terminalv}
+object = astRead( channel );
+if ( !strcmp( astGetC( object, "ID" ), "Calibration" ) ) {
+   <the Calibration Object has been read>
+} else {
+   <some other Object has been read>
+}
+\end{terminalv}
+\normalsize
+
+The only difference between the ID and Ident attributes is that the ID
+attribute is unique to a particular Object and is lost if, for example,
+you make a copy of the Object. The Ident attrubute, on the other hand, is
+transferred to the new Object when a copy is made. Consequently, it is
+safest to set the value of the ID attribute immediately before you
+perform the write.
+
+\subsection{\label{ss:textualoutputformat}The Textual Output Format}
+
+Let us now examine the format of the textual output produced by
+writing an \htmlref{Object}{Object} to a basic \htmlref{Channel}{Channel}
+(\secref{ss:writingtoachannel}). To give a concrete example, suppose
+the Object in question is a \htmlref{SkyFrame}{SkyFrame}, written out as follows:
+
+\small
+\begin{terminalv}
+AstSkyFrame *skyframe;
+
+...
+
+nobj = astWrite( channel, skyframe );
+\end{terminalv}
+\normalsize
+
+The output should then look like the following:
+
+\small
+\begin{terminalv}
+ Begin SkyFrame 	# Description of celestial coordinate system
+#   Title = "FK4 Equatorial Coordinates, no E-terms, Mean Equinox B1950.0, Epoch B1958.0" 	# Title of coordinate system
+    Naxes = 2 	# Number of coordinate axes
+#   Domain = "SKY" 	# Coordinate system domain
+#   Lbl1 = "Right Ascension" 	# Label for axis 1
+#   Lbl2 = "Declination" 	# Label for axis 2
+#   Uni1 = "hh:mm:ss.s" 	# Units for axis 1
+#   Uni2 = "ddd:mm:ss" 	# Units for axis 2
+#   Dir1 = 0 	# Plot axis 1 in reverse direction (hint)
+    Ax1 = 	# Axis number 1
+       Begin SkyAxis 	# Celestial coordinate axis
+       End SkyAxis
+    Ax2 = 	# Axis number 2
+       Begin SkyAxis 	# Celestial coordinate axis
+       End SkyAxis
+ IsA Frame 	# Coordinate system description
+    System = "FK4-NO-E" 	# Celestial coordinate system type
+    Epoch = 1958 	# Besselian epoch of observation
+#   Eqnox = 1950 	# Besselian epoch of mean equinox
+ End SkyFrame
+\end{terminalv}
+\normalsize
+
+You will notice that this output is designed both for a human reader,
+in that it is formatted, and also to be read back by a computer in
+order to reconstruct the SkyFrame. In fact, this is precisely the way
+that \htmlref{astShow}{astShow} works (\secref{ss:displayingobjects}), this function being
+roughly equivalent to the following use of a Channel:
+
+\small
+\begin{terminalv}
+channel = astChannel( NULL, NULL, "" );
+(void) astWrite( channel, object );
+channel = astAnnul( channel );
+\end{terminalv}
+\normalsize
+
+Some lines of the output start with a ``\verb?#?'' comment character,
+which turns the rest of the line into a comment. These lines will be
+ignored when read back in by \htmlref{astRead}{astRead}.  They typically contain default
+values, or values that can be derived in some way from the other data
+present, so that they do not actually need to be stored in order to
+reconstruct the original Object. They are provided purely for human
+information. The same comment character is also used to append
+explanatory comments to most output lines.
+
+It is not sensible to attempt a complete description of this output
+format because every class of Object is potentially different and each
+can define how its own data should be represented. However, there are
+some basic rules, which mean that the following common features will
+usually be present:
+
+\begin{enumerate}
+\item Each Object is delimited by matching ``Begin'' and ``End''
+lines, which also identify the class of Object involved.
+
+\item Within each Object description, data values are represented
+by a simple ``keyword~$=$~value'' syntax, with one value to a line.
+
+\item Lines beginning ``IsA'' are used to mark the divisions between
+data belonging to different levels in the class hierarchy
+(\appref{ss:classhierarchy}). Thus, ``IsA~\htmlref{Frame}{Frame}'' marks the end of data
+associated with the Frame class and the start of data associated with
+some derived class (a SkyFrame in the above example). ``IsA'' lines
+may be omitted if associated data values are absent and no confusion
+arises.
+
+\item Objects may contain other Objects as data. This is
+indicated by an absent value, with the description of the data
+Object following on subsequent lines.
+
+\item Indentation is used to clarify the overall structure.
+\end{enumerate}
+
+Beyond these general principles, the best guide to what a particular
+line of output represents will generally be the comment which
+accompanies it together with a general knowledge of the class of
+Object being described.
+
+\subsection{\label{ss:controllingchanneloutput}Controlling the Amount of Output}
+
+It is not always necessary for the output from \htmlref{astWrite}{astWrite}
+(\secref{ss:writingtoachannel}) to be human-readable, so a \htmlref{Channel}{Channel} has
+attributes that allow the amount of detail in the output to be
+controlled.
+
+The first of these is the integer attribute \htmlref{Full}{Full}, which controls the
+extent to which optional, commented out, output lines are produced. By
+default, Full is zero, and this results in the standard style of
+output (\secref{ss:textualoutputformat}) where default values that may
+be helpful to humans are included. To suppress these optional lines,
+Full should be set to $-$1. This is most conveniently done when the
+Channel is created, so that:
+
+\small
+\begin{terminalv}
+channel = astChannel( NULL, NULL, "Full=-1" );
+(void) astWrite( channel, skyframe );
+channel = astAnnul( channel );
+\end{terminalv}
+\normalsize
+
+would result in output containing only the essential information, such
+as:
+
+\small
+\begin{terminalv}
+ Begin SkyFrame 	# Description of celestial coordinate system
+    Naxes = 2 	# Number of coordinate axes
+    Ax1 = 	# Axis number 1
+       Begin SkyAxis 	# Celestial coordinate axis
+       End SkyAxis
+    Ax2 = 	# Axis number 2
+       Begin SkyAxis 	# Celestial coordinate axis
+       End SkyAxis
+ IsA Frame 	# Coordinate system description
+    System = "FK4-NO-E" 	# Celestial coordinate system type
+    Epoch = 1958 	# Besselian epoch of observation
+ End SkyFrame
+\end{terminalv}
+\normalsize
+
+In contrast, setting Full to $+$1 will result in additional output
+lines which will reveal every last detail of the \htmlref{Object}{Object}'s
+construction. Often this will be rather more than you want, especially
+for more complex Objects, but it can sometimes help when debugging
+programs. This is how a \htmlref{SkyFrame}{SkyFrame} appears at this level of detail:
+
+\small
+\begin{terminalv}
+ Begin SkyFrame 	# Description of celestial coordinate system
+#   RefCnt = 1 	# Count of active Object pointers
+#   Nobj = 1 	# Count of active Objects in same class
+ IsA Object 	# Astrometry Object
+#   Nin = 2 	# Number of input coordinates
+#   Nout = 2 	# Number of output coordinates
+#   Invert = 0 	# Mapping not inverted
+#   Fwd = 1 	# Forward transformation defined
+#   Inv = 1 	# Inverse transformation defined
+#   Report = 0 	# Don't report coordinate transformations
+ IsA Mapping 	# Mapping between coordinate systems
+#   Title = "FK4 Equatorial Coordinates, no E-terms, Mean Equinox B1950.0, Epoch B1958.0" 	# Title of coordinate system
+    Naxes = 2 	# Number of coordinate axes
+#   Domain = "SKY" 	# Coordinate system domain
+#   Lbl1 = "Right Ascension" 	# Label for axis 1
+#   Lbl2 = "Declination" 	# Label for axis 2
+#   Sym1 = "RA" 	# Symbol for axis 1
+#   Sym2 = "Dec" 	# Symbol for axis 2
+#   Uni1 = "hh:mm:ss.s" 	# Units for axis 1
+#   Uni2 = "ddd:mm:ss" 	# Units for axis 2
+#   Dig1 = 7 	# Individual precision for axis 1
+#   Dig2 = 7 	# Individual precision for axis 2
+#   Digits = 7 	# Default formatting precision
+#   Fmt1 = "hms.1" 	# Format specifier for axis 1
+#   Fmt2 = "dms" 	# Format specifier for axis 2
+#   Dir1 = 0 	# Plot axis 1 in reverse direction (hint)
+#   Dir2 = 1 	# Plot axis 2 in conventional direction (hint)
+#   Presrv = 0 	# Don't preserve target axes
+#   Permut = 1 	# Axes may be permuted to match
+#   MinAx = 2 	# Minimum number of axes to match
+#   MaxAx = 2 	# Maximum number of axes to match
+#   MchEnd = 0 	# Match initial target axes
+#   Prm1 = 1 	# Axis 1 not permuted
+#   Prm2 = 2 	# Axis 2 not permuted
+    Ax1 = 	# Axis number 1
+       Begin SkyAxis 	# Celestial coordinate axis
+#         RefCnt = 1 	# Count of active Object pointers
+#         Nobj = 2 	# Count of active Objects in same class
+       IsA Object 	# Astrometry Object
+#         Label = "Angle on Sky" 	# Axis Label
+#         Symbol = "delta" 	# Axis symbol
+#         Unit = "ddd:mm:ss" 	# Axis units
+#         Digits = 7 	# Default formatting precision
+#         Format = "dms" 	# Format specifier
+#         Dirn = 1 	# Plot in conventional direction
+       IsA Axis 	# Coordinate axis
+#         Format = "dms" 	# Format specifier
+#         IsLat = 0 	# Longitude axis (not latitude)
+#         AsTime = 0 	# Display values as angles (not times)
+       End SkyAxis
+    Ax2 = 	# Axis number 2
+       Begin SkyAxis 	# Celestial coordinate axis
+#         RefCnt = 1 	# Count of active Object pointers
+#         Nobj = 2 	# Count of active Objects in same class
+       IsA Object 	# Astrometry Object
+#         Label = "Angle on Sky" 	# Axis Label
+#         Symbol = "delta" 	# Axis symbol
+#         Unit = "ddd:mm:ss" 	# Axis units
+#         Digits = 7 	# Default formatting precision
+#         Format = "dms" 	# Format specifier
+#         Dirn = 1 	# Plot in conventional direction
+       IsA Axis 	# Coordinate axis
+#         Format = "dms" 	# Format specifier
+#         IsLat = 0 	# Longitude axis (not latitude)
+#         AsTime = 0 	# Display values as angles (not times)
+       End SkyAxis
+ IsA Frame 	# Coordinate system description
+    System = "FK4-NO-E" 	# Celestial coordinate system type
+    Epoch = 1958 	# Besselian epoch of observation
+#   Eqnox = 1950 	# Besselian epoch of mean equinox
+ End SkyFrame
+\end{terminalv}
+\normalsize
+
+\subsection{\label{ss:channelcommenting}Controlling Commenting}
+
+Another way of controlling output from a \htmlref{Channel}{Channel} is \emph{via} the
+boolean (integer) \htmlref{Comment}{Comment} attribute, which controls whether comments
+are appended to describe the purpose of each value. Comment has the
+value 1 by default but, if set to zero, will suppress these
+comments. This is normally appropriate only if you wish to minimise
+the amount of output, for example:
+
+\small
+\begin{terminalv}
+astSet( channel, "Full=-1, Comment=0" );
+nobj = astWrite( channel, skyframe );
+\end{terminalv}
+\normalsize
+
+might result in the following more compact output:
+
+\small
+\begin{terminalv}
+ Begin SkyFrame
+    Naxes = 2
+    Ax1 =
+       Begin SkyAxis
+       End SkyAxis
+    Ax2 =
+       Begin SkyAxis
+       End SkyAxis
+ IsA Frame
+    System = "FK4-NO-E"
+    Epoch = 1958
+ End SkyFrame
+\end{terminalv}
+\normalsize
+
+\subsection{Editing Textual Output}
+
+The safest advice about editing the textual output from \htmlref{astWrite}{astWrite} (or
+\htmlref{astShow}{astShow}) is ``don't!''---unless you know what you are doing.
+
+Having given that warning, however, it is sometimes possible to make
+changes to the text, or even to write entire \htmlref{Object}{Object} descriptions from
+scratch, and to read the results back in to construct new
+Objects. Normally, simple changes to numerical values are safest, but
+be aware that this is a back door method of creating Objects, so
+you are on your own! There are a number of potential pitfalls. In
+particular:
+
+\begin{itemize}
+\item \htmlref{astRead}{astRead} is intended for retrieving data written by astWrite and
+not for reading data input by humans. As such, the data validation
+provided is very limited and is certainly not foolproof. This makes it
+quite easy to construct Objects that are internally inconsistent by
+this means. In contrast, the normal programming interface incorporates
+numerous checks designed to make it impossible to construct invalid
+Objects. You should not necessarily think you have found a bug if your
+changes to an Object's textual description fail to produce the results
+you expected!
+
+\item In many instances the names associated with values in textual
+output will correspond with Object attributes. Sometimes, however,
+these names may differ from the attribute name. This is mainly because
+of length restrictions imposed by other common external formats, such
+as FITS headers. Some of the names used do not correspond with
+attributes at all.
+
+\item It is safest to change single numerical or string values.
+Beware of changing the size or shape of Objects (\emph{e.g.}\ the
+number of axes in a \htmlref{Frame}{Frame}). Often, these values must match others
+stored elsewhere within the Object and changing them in a haphazard
+fashion will not produce useful results.
+
+\item Be wary about un-commenting default values. Sometimes this will
+work, but often these values are derived from other Objects stored
+more deeply in the structure and the proper place to insert a new
+value is not where the default itself appears.
+\end{itemize}
+
+\subsection{\label{ss:mixingchanneltext}Mixing Objects with other Text}
+
+By default, when you use \htmlref{astRead}{astRead} to read from a basic \htmlref{Channel}{Channel}
+(\secref{ss:readingfromachannel}), it is assumed that you are reading a
+stream of text containing only AST Objects, which follow each other
+end-to-end. If any extraneous input data are encountered which do not
+appear to form part of the textual description of an \htmlref{Object}{Object}, then an
+error will result. In particular, the first input line must identify
+the start of an Object description, so you cannot start reading half
+way through an Object.
+
+Sometimes, however, you may want to store AST Object descriptions
+intermixed with other textual data. You can do this by setting the
+Channel's boolean (integer) \htmlref{Skip}{Skip} attribute to 1. This will cause every
+read to skip over extraneous data until the start of a new AST Object
+description, if any, is found. So long as your other data do not mimic
+the appearance of an AST Object description, the two sets of data can
+co-exist.
+
+For example, by setting Skip to 1, the following complete C program
+will read all the AST Objects whose descriptions appear in the source
+of this document, ignoring the other text. \htmlref{astShow}{astShow} is used to display
+those found:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+main() {
+   AstChannel *channel;
+   AstObject *object;
+
+   channel = astChannel( NULL, NULL, "Skip=1" );
+   while ( ( object = astRead( channel ) ) != AST__NULL ) {
+      astShow( object );
+      object = astAnnul( object );
+   }
+   channel = astAnnul( channel );
+}
+\end{terminalv}
+\normalsize
+
+\subsection{\label{ss:channelsource}Reading Objects from Files}
+
+Thus far, we have only considered the default behaviour of a \htmlref{Channel}{Channel}
+in reading and writing Objects through a program's standard input and
+output streams. We will now consider how to access Objects stored in
+files more directly.
+
+The simple approach is to use the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes of
+the Channel. For instance, the following will read a pair of Objects from
+a text file called ``fred.txt'':
+
+\small
+\begin{terminalv}
+astSet( channel, "SourceFile=fred.txt" );
+obj1 = astRead( channel );
+obj2 = astRead( channel );
+astClear( channel, "SourceFile" );
+\end{terminalv}
+\normalsize
+
+Note, the act of clearing the attribute tells AST that no more Objects
+are to be read from the file and so the file is then closed. If the
+attribute is not cleared, the file will remain open and further Objects
+can be read from it. The file will always be closed when the Channel is
+deleted.
+
+This simple approach will normally be sufficient. However, because the
+AST library is designed to be used from more than one language, it has
+to be a little careful about reading and writing to files. This is due
+to incompatibilities that may exist between the file I/O facilities
+provided by different languages. If such incompatibilities prevent the
+above simple system being used, we need to adopt a system that off-loads
+all file I/O to external code.
+
+What this means in practice is that if the above simple approach cannot
+be used, you must instead provide some simple C
+functions that perform the actual transfer of data to and from files
+and similar external data stores. The functions you provide are
+supplied as the source and/or sink function arguments to \htmlref{astChannel}{astChannel}
+when you create a Channel (\secref{ss:creatingachannel}). An example is
+the best way to illustrate this.
+
+Consider the following simple function called Source. It reads a
+single line of text from a C input stream and returns a pointer to it,
+or NULL if there is no more input:
+
+\small
+\begin{terminalv}
+#include <stdio.h>
+#define LEN 200
+static FILE *input_stream;
+
+const char *Source( void ) {
+   static char buffer[ LEN + 2 ];
+   return fgets( buffer, LEN + 2, input_stream );
+}
+\end{terminalv}
+\normalsize
+
+Note that the input stream is a static variable which we will also
+access from our main program. This might look something like this
+(omitting error checking for brevity):
+
+\small
+\begin{terminalv}
+/* Open the input file. */
+input_stream = fopen( "infile.ast", "r" );
+
+/* Create a Channel and read an Object from it. */
+channel = astChannel( Source, NULL, "" );
+object = astRead( channel );
+
+...
+
+/* Annul the Channel and close the file when done. */
+channel = astAnnul( channel );
+(void) fclose( input_stream );
+\end{terminalv}
+\normalsize
+
+Here, we first open the required input file, saving the resulting FILE
+pointer. We then pass a pointer to our Source function as the first
+argument to astChannel when creating a new Channel. When we read
+an \htmlref{Object}{Object} from this Channel with \htmlref{astRead}{astRead}, the Source
+function will be called to obtain the textual data from the file, the
+end-of-file being detected when this function returns NULL.
+
+Note, if a value is set for the SourceFile attribute,
+the astRead function will ignore any source function
+specified when the Channel was created.
+
+\subsection{\label{ss:channelsink}Writing Objects to Files}
+
+As for reading, writing Objects to files can be done in two different ways.
+Again, the simple approach is to use the \htmlref{SinkFile}{SinkFile} attribute of the \htmlref{Channel}{Channel}.
+For instance, the following will write a pair of Objects to a text file
+called ``fred.txt'':
+
+\small
+\begin{terminalv}
+astSet( channel, "SinkFile=fred.txt" );
+nobj = astWrite( channel, object1 );
+nobj = astWrite( channel, object2 );
+astClear( channel, "SinkFile" );
+\end{terminalv}
+\normalsize
+
+Note, the act of clearing the attribute tells AST that no more output
+will be written to the file and so the file is then closed. If the
+attribute is not cleared, the file will remain open and further Objects
+can be written to it. The file will always be closed when the Channel is
+deleted.
+
+If the details of the language's I/O system on the computer you are using
+means that the above approach cannot be used, then we can write a Sink function,
+that writes a line of output text to a file, and use it in basically the same
+way as the Source function in the previous section (\secref{ss:channelsource}):
+
+\small
+\begin{terminalv}
+static FILE *output_stream;
+
+void Sink( const char *line ) {
+   (void) fprintf( output_stream, "%s\n", line );
+}
+\end{terminalv}
+\normalsize
+
+Note that we must supply the final newline character ourselves.
+
+In this case, our main program would supply a pointer to this Sink
+function as the second argument to \htmlref{astChannel}{astChannel}, as follows:
+
+\small
+\begin{terminalv}
+/* Open the output file. */
+output_stream = fopen( "outfile.ast", "w" );
+
+/* Create a Channel and write an Object to it. */
+channel = astChannel( Source, Sink, "" );
+nobj = astWrite( channel, object );
+
+   ...
+
+/* Annul the Channel and close the file when done. */
+channel = astAnnul( channel );
+(void) fclose( output_stream );
+\end{terminalv}
+\normalsize
+
+Note that we can specify a source and/or a sink function for the
+Channel, and that these may use either the same file, or different
+files according to whether we are reading or writing. AST has no
+knowledge of the underlying file system, nor of file positioning. It
+just reads and writes sequentially. If you wish, for example, to
+reposition a file at the beginning in between reads and writes, then
+this can be done directly (and completely independently of AST) using
+standard C functions.
+
+If an error occurs in your source or sink function, you can
+communicate this to the AST library by setting its error status to any
+error value using \htmlref{astSetStatus}{astSetStatus} (\secref{ss:errordetection}). This will
+immediately terminate the read or write operation.
+
+Note, if a value is set for the SinkFile attribute,
+the \htmlref{astWrite}{astWrite} function will ignore any sink function
+specified when the Channel was created.
+
+\subsection{\label{ss:otherplaces}Reading and Writing Objects to other Places}
+
+It should be obvious from the above (\secref{ss:channelsource} and
+\secref{ss:channelsink}) that a \htmlref{Channel}{Channel}'s source and sink functions
+provide a flexible means of intercepting textual data that describes
+AST Objects as it flows in and out of your program. In fact, you might
+like to regard a Channel simply as a filter for converting AST Objects
+to and from a stream of text which is then handled by your source and
+sink functions, where the real I/O occurs.
+
+This gives you the ability to store AST Objects in virtually any data
+system, so long as you can convert a stream of text into something
+that can be stored (it need no longer be text) and retrieve it
+again. There is generally no need to retain comments.  Other
+possibilities, such as inter-process and network communication, could
+also be implemented \emph{via} source and sink functions in basically
+the same way.
+
+\cleardoublepage
+\section{\label{ss:nativefits}Storing AST Objects in FITS Headers (FitsChans)}
+
+A FITS header is a sequence of 80-character strings, formatted
+according to particular rules defined by the Flexible Image Transport
+\htmlref{System}{System}
+(FITS). \htmladdnormallinkfoot{FITS}{http://fits.gsfc.nasa.gov/}
+is a widely-used standard for data interchange in astronomy and has
+also been adopted as a data processing format in some astronomical
+data reduction systems.  The individual 80-character strings in a FITS
+header are usually called \emph{cards} or \emph{header cards} (for
+entirely anachronistic reasons).
+
+A sequence of FITS cards appears as a header at the start of every
+FITS data file, and sometimes also at other points within it, and is
+used to provide ancillary information which qualifies or describes the
+main array of data stored in the file. As such, FITS headers are prime
+territory for storing information about the coordinate systems
+associated with data held in FITS files.
+
+In this section, we will examine how to store information in FITS
+headers directly in the form of AST Objects---a process which is
+supported by a specialised class of \htmlref{Channel}{Channel} called a \htmlref{FitsChan}{FitsChan}. Our
+discussion here will turn out to be a transitional step that
+emphasises the similarities between a FitsChan and a Channel
+(\secref{ss:channels}). At the same time, it will prepare us for the
+next section (\secref{ss:foreignfits}), where we will examine how to
+use a FitsChan to tackle some of the more difficult problems that FITS
+headers can present.
+
+\subsection{\label{ss:nativeencoding}The Native FITS Encoding}
+
+As it turns out, we are not the first to have thought of storing WCS
+information in FITS headers. In fact, the original FITS standard (1981
+vintage) defined a set of header keywords for this purpose which have
+been widely used, although they have proved too limited for many
+practical purposes.
+
+At the time of writing, a number of different ways of using FITS
+headers for storing WCS information are in use, most (although not
+all) based on the original standard. We will refer to these
+alternative ways of storing the information as FITS \emph{encodings}
+but will defer a discussion of their advantages and limitations until
+the next section (\secref{ss:foreignfits}).
+
+Here, we will examine how to store AST Objects directly in FITS
+headers. In effect, this defines a new encoding, which we will term
+the \emph{native encoding}. This is a special kind of encoding,
+because not only does it allow us to associate conventional
+WCS calibration information with FITS data, but it also allows any other
+information that can be expressed in terms of AST Objects to be stored
+as well.  In fact, the native encoding provides us with facilities
+roughly analogous to those of the \htmlref{Channel}{Channel}
+(\secref{ss:channels})---\emph{i.e.}\ a lossless way of
+transferring AST Objects from program to program---but based on FITS
+headers instead of free-format text.
+
+\subsection{The FitsChan Model}
+
+I/O between AST Objects and FITS headers is supported by a specialised
+form of \htmlref{Channel}{Channel} called a \htmlref{FitsChan}{FitsChan}. A FitsChan contains a buffer which
+may hold any number, including zero, of FITS header cards. This buffer
+forms a workspace in which you can assemble FITS cards and manipulate
+them before writing them out to a file.
+
+By default, when a FitsChan is first created, it contains no cards and
+there are five ways of inserting cards into it:
+
+\begin{enumerate}
+\item You may add cards yourself, one at a time, using \htmlref{astPutFits}{astPutFits}
+(\secref{ss:addingfitscards}).
+
+\item You may add cards yourself, supplying all cards concatenated into a
+single string, using \htmlref{astPutCards}{astPutCards}
+(\secref{ss:addingmulticards}).
+
+\item You may write an AST \htmlref{Object}{Object} to the FitsChan (using \htmlref{astWrite}{astWrite}),
+which will have the effect of creating new cards within the FitsChan
+which describe the Object (\secref{ss:writingnativefits}).
+
+\item You may assign a value to the \htmlref{SourceFile}{SourceFile} attribute of the FitsChan.
+The value should be the path to a text file holding a set of FITS header
+cards, one per line. When the SourceFile value is set (using
+astSetC or \htmlref{astSet}{astSet}),
+the file is opened and the headers copied from it into the FitsChan.
+The file is then immediately closed.
+
+\item You may specify a source function which reads data from some
+external store of FITS cards, just like the source associated with a
+basic Channel (\secref{ss:channelsource}). If you supply a source
+function, it will be called when the FitsChan is created in order to
+fill it with an initial set of cards (\secref{ss:fitssourceandsink}).
+\end{enumerate}
+
+There are also four ways of removing cards from a FitsChan:
+
+\begin{enumerate}
+\item You may delete cards yourself, one at a time, using \htmlref{astDelFits}{astDelFits}
+(\secref{ss:findingandchangingfits}).
+
+\item You may read an AST Object from the FitsChan (using \htmlref{astRead}{astRead}),
+which will have the effect of removing those cards from the FitsChan
+which describe the Object (\secref{ss:readingnativefits}).
+
+\item You may assign a value to the FitsChan's \htmlref{SinkFile}{SinkFile} attribute. When
+the FitsChan is deleted, any remaining headers are written out to a text
+file with path equal to the value of the SinkFile attribute.
+
+\item Alternatively, you may specify a sink function which writes data to some
+external store of FITS cards, just like the sink associated with a
+basic Channel (\secref{ss:channelsink}). If you supply a sink function,
+it will be called when the FitsChan is deleted in order to write out
+any FITS cards that remain in it (\secref{ss:fitssourceandsink}). Note,
+the sink function is not called if the SinkFile attribute has been set.
+\end{enumerate}
+
+Note, in particular, that reading an AST Object from a FitsChan is
+\emph{destructive}. That is, it deletes the FITS cards that describe the
+Object. The reason for this is explained in
+\secref{ss:destructiveread}.
+
+In addition to the above, you may also read individual cards from a
+FitsChan using the function \htmlref{astFindFits}{astFindFits} (which is not
+destructive). This is the main means of writing out FITS cards if you
+have not supplied a sink function.  astFindFits also provides a means
+of searching for particular FITS cards (by keyword, for example) and
+there are other facilities for overwriting cards when required
+(\secref{ss:findingandchangingfits}).
+
+\subsection{\label{ss:creatingafitschan}Creating a FitsChan}
+
+The \htmlref{FitsChan}{FitsChan} constructor function, \htmlref{astFitsChan}{astFitsChan}, is straightforward to
+use:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+AstFitsChan *fitschan;
+
+...
+
+fitschan = astFitsChan( NULL, NULL, "Encoding=NATIVE" );
+\end{terminalv}
+\normalsize
+
+Here, we have omitted any source or sink functions by supplying NULL
+pointers for the first two arguments.
+We have also initialised the FitsChan's \htmlref{Encoding}{Encoding} attribute to
+NATIVE. This indicates that we will be using the native encoding
+(\secref{ss:nativeencoding}) to store and retrieve Objects. If this
+was left unspecified, the default would depend on the FitsChan's
+contents. An attempt is made to use whatever encoding appears to have
+been used previously. For an empty FitsChan, the default is NATIVE,
+but it does no harm to be sure.
+
+\subsection{\label{ss:addressingfitscards}Addressing Cards in a FitsChan}
+
+Because a \htmlref{FitsChan}{FitsChan} contains an ordered sequence of header cards, a
+mechanism is needed for addressing them. This allows you to specify
+where new cards are to be added, for example, or which card is to be
+deleted.
+
+This role is filled by the FitsChan's integer \htmlref{Card}{Card} attribute, which
+gives the index of the \emph{current card} in the FitsChan.  You can
+nominate any card you like to be current, simply by setting a new
+value for the Card attribute, for example:
+
+\small
+\begin{terminalv}
+int icard;
+
+...
+
+astSetI( fitschan, "Card", icard )
+\end{terminalv}
+\normalsize
+
+where ``icard'' contains the index of the card on which you wish to
+operate next.  Some functions will update the Card attribute as a
+means of advancing through the sequence of cards, when reading them
+for example, or to indicate which card matches a search criterion.
+
+The default value for Card is one, which is the index of the first
+card. This means that you can ``rewind'' a FitsChan to access its
+first card by clearing the Card attribute:
+
+\small
+\begin{terminalv}
+astClear( fitschan, "Card" );
+\end{terminalv}
+\normalsize
+
+The total number of cards in a FitsChan is given by the integer \htmlref{Ncard}{Ncard}
+attribute. This is a read-only attribute whose value is automatically
+updated as you add or remove cards. It means you can address all the
+cards in sequence using a loop such as the following:
+
+\small
+\begin{terminalv}
+int ncard;
+
+...
+
+ncard = astGetI( fitschan, "Ncard" );
+for ( icard = 1; icard <= ncard; icard++ ) {
+   astSetI( fitschan, "Card", icard );
+   <access the current card>
+}
+\end{terminalv}
+\normalsize
+
+However, it is usually possible to write slightly tidier loops based
+on the \htmlref{astFindFits}{astFindFits} function described later
+(\secref{ss:extractingfitscards} and
+\secref{ss:findingandchangingfits}).
+
+If you set the Card attribute to a value larger than Ncard, the
+FitsChan is regarded as being positioned at its \emph{end-of-file}. In
+this case there is no current card and an attempt to obtain a value
+for the Card attribute will always return the value Ncard~$+$~1. When
+a FitsChan is empty, it is always at the end-of-file.
+
+\subsection{\label{ss:writingnativefits}Writing Native Objects to a FitsChan}
+
+Having created an empty \htmlref{FitsChan}{FitsChan} (\secref{ss:creatingafitschan}), you
+can write any AST \htmlref{Object}{Object} to it in the native encoding using the
+\htmlref{astWrite}{astWrite} function. Let us assume we are writing a
+\htmlref{SkyFrame}{SkyFrame},\footnote{More probably, you would want to write a \htmlref{FrameSet}{FrameSet},
+but for purposes of illustration a SkyFrame contains a more manageable
+amount of data.} as follows:
+
+\small
+\begin{terminalv}
+AstSkyFrame *skyframe;
+int nobj;
+
+...
+
+nobj = astWrite( fitschan, skyframe );
+\end{terminalv}
+\normalsize
+
+Since we have selected the native encoding
+(\secref{ss:nativeencoding}), there are no restrictions on the class
+of Object we may write, so astWrite should always return a value of
+one, unless an error occurs. Unlike a basic \htmlref{Channel}{Channel}
+(\secref{ss:writingtoachannel}), this write operation will not produce
+any output from our program. The FITS headers produced are simply
+stored inside the FitsChan.
+
+After this write operation, the \htmlref{Ncard}{Ncard} attribute will be updated to
+reflect the number of new cards added to the FitsChan and the \htmlref{Card}{Card}
+attribute will point at the card immediately after the last one
+written. Since our FitsChan was initially empty, the Card attribute
+will, in this example, point at the end-of-file
+(\secref{ss:addressingfitscards}).
+
+The FITS standard imposes a limit of 68 characters on the length of
+strings which may be stored in a single header card. Sometimes, a
+description of an AST Object involves the use of strings which exceed
+this limit (\emph{e.g.}\ a \htmlref{Frame}{Frame} title can be of arbitrary length). If
+this occurs, the long string will be split over two or more header cards.
+Each ``continuation'' card will have the keyword \texttt{CONTINUE} in
+columns 1 to 8, and will contain a space in column 9 (instead of the
+usual equals sign). An ampersand (``\texttt{\&}'') is appended to the end of
+each of the strings (except the last one) to indicate that the string is
+continued on the next card.
+
+Note, this splitting of long strings over several cards only occurs when
+writing AST Objects to a FitsChan using the astWrite function and the
+\emph{native} encoding. If a long string is stored in a FitsChan using
+(for instance) the \htmlref{astPutFits}{astPutFits} or \htmlref{astPutCards}{astPutCards} function, it will simply be truncated.
+
+
+\subsection{\label{ss:extractingfitscards}Extracting Individual Cards from a FitsChan}
+
+To examine the contents of the \htmlref{FitsChan}{FitsChan} after writing the \htmlref{SkyFrame}{SkyFrame}
+above (\secref{ss:writingnativefits}), we must write a simple loop to
+extract each card in turn and print it out. We must also remember to
+rewind the FitsChan first, \emph{e.g.}\ using \htmlref{astClear}{astClear}. The following
+loop would do:
+
+\small
+\begin{terminalv}
+#include <stdio.h>
+char card[ 81 ];
+
+...
+
+astClear( fitschan, "Card" );
+while ( astFindFits( fitschan, "%f", card, 1 ) ) (void) printf( "%s\n", card );
+\end{terminalv}
+\normalsize
+
+Here, we have used the \htmlref{astFindFits}{astFindFits} function to find a FITS card by
+keyword. It is given a keyword template of ``\%f'', which matches any
+FITS keyword, so it always finds the current card, which it
+returns. Its fourth argument is set to 1, to indicate that the \htmlref{Card}{Card}
+attribute should be incremented afterwards so that the following card
+will be found the next time around the loop. astFindFits returns zero
+when it reaches the end-of-file and this terminates the loop.
+
+If we were storing the FITS headers in an output FITS file instead of
+printing them out, we might use a loop like this but replace
+``printf'' with a suitable data storage operation. This would only be
+necessary if we had not provided a sink function for the FitsChan
+(\secref{ss:fitssourceandsink}).
+
+\subsection{The Native FitsChan Output Format}
+
+If we print out the FITS header cards describing the \htmlref{SkyFrame}{SkyFrame} we wrote
+earlier (\secref{ss:writingnativefits}), we should obtain something
+like the following:
+
+\small
+\begin{terminalv}
+COMMENT AST ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ AST
+COMMENT AST            Beginning of AST data for SkyFrame object             AST
+COMMENT AST ................................................................ AST
+BEGAST_A= 'SkyFrame'           / Description of celestial coordinate system
+NAXES_A =                    2 / Number of coordinate axes
+AX1_A   = '        '           / Axis number 1
+BEGAST_B= 'SkyAxis '           / Celestial coordinate axis
+ENDAST_A= 'SkyAxis '           / End of object definition
+AX2_A   = '        '           / Axis number 2
+BEGAST_C= 'SkyAxis '           / Celestial coordinate axis
+ENDAST_B= 'SkyAxis '           / End of object definition
+ISA_A   = 'Frame   '           / Coordinate system description
+SYSTEM_A= 'FK4-NO-E'           / Celestial coordinate system type
+EPOCH_A =               1958.0 / Besselian epoch of observation
+ENDAST_C= 'SkyFrame'           / End of object definition
+COMMENT AST ................................................................ AST
+COMMENT AST               End of AST data for SkyFrame object                AST
+COMMENT AST ---------------------------------------------------------------- AST
+\end{terminalv}
+\normalsize
+
+As you can see, this resembles the information that would be written
+to a basic \htmlref{Channel}{Channel} to describe the same SkyFrame
+(\secref{ss:textualoutputformat}), except that it has been formatted
+into 80-character header cards according to FITS conventions.
+
+There are also a number of other differences worth noting:
+
+\begin{enumerate}
+\item There is no unnecessary information about default values
+provided for the benefit of the human reader. This is because the \htmlref{Full}{Full}
+attribute for a \htmlref{FitsChan}{FitsChan} defaults to $-$1, thus suppressing this
+information (\emph{c.f.}~\secref{ss:controllingchanneloutput}). You
+can restore the information if you wish by setting Full to 0 or $+$1,
+in which case additional COMMENT cards will be generated to hold it.
+
+\item The information is not indented, because FITS does not allow
+this. However, if you change the Full attribute to 0 or $+$1, comments
+will be included that are intended to help break up the sequence of
+headers and highlight its structure. This will probably only be of use
+if you are attempting to track down a problem by examining the FITS
+cards produced in detail.
+
+\item The FITS keywords which appear to the left of the ``$=$'' signs
+have additional characters (``\_A'', ``\_B'', \emph{etc.}) appended to
+them. This is done in order to make each keyword unique.
+\end{enumerate}
+
+This last point is worth further comment and is necessary because the
+FITS standard only allows for certain keywords (such as COMMENT and
+HISTORY) to appear more than once. \htmlref{astWrite}{astWrite} therefore appends an
+arbitrary sequence of two characters to each new keyword it generates
+in order to ensure that it does not duplicate any already present in
+the FitsChan.
+
+The main risk from not following this convention is that some software
+might ignore (say) all but the last occurrence of a keyword before
+passing the FITS headers on. Such an event is unlikely, but would
+obviously destroy the information present, so astWrite enforces the
+uniqueness of the keywords it uses. The extra characters added are
+ignored when the information is read back.
+
+As with a basic Channel, you can also suppress the comments produced
+in a FitsChan by setting the boolean (integer) \htmlref{Comment}{Comment} attribute to
+zero (\secref{ss:channelcommenting}). However, FITS headers are
+traditionally generously commented, so this is not recommended.
+
+\subsection{\label{ss:addingfitscards}Adding Individual Cards to a FitsChan}
+
+To insert individual cards into a \htmlref{FitsChan}{FitsChan}, prior to reading them back
+as Objects for example, you should use the \htmlref{astPutFits}{astPutFits} function. You
+can insert a card in front of the current one as follows:
+
+\small
+\begin{terminalv}
+astPutFits( fitschan, card, 0 );
+\end{terminalv}
+\normalsize
+
+where the third argument of zero indicates that the current card
+should not be overwritten. Note that facilities are not provided by
+AST for formatting the card contents.
+
+After inserting a card, the FitsChan's \htmlref{Card}{Card} attribute points at the
+original Card, or at the end-of-file if the FitsChan was originally
+empty. Entering a sequence of cards is therefore straightforward. If
+``cards'' is an array of pointers to strings containing FITS header
+cards and ``ncards'' is the number of cards, then a loop such as the
+following will insert the cards in sequence into a FitsChan:
+
+\small
+\begin{terminalv}
+#define MAXCARD 100
+char *cards[ MAXCARD ];
+int ncard;
+
+...
+
+for ( icard = 0; icard < ncard; icard++ ) astPutFits( fitschan, cards[ icard ], 0 );
+\end{terminalv}
+\normalsize
+
+The string containing a card need not be null terminated if it is at
+least 80 characters long (we have not allocated space for the strings
+themselves in this brief example).
+
+Note that astPutFits enforces the validity of a FitsChan by rejecting
+any cards which do not adhere to the FITS standard. If any such cards
+are detected, an error will result.
+
+\subsection{\label{ss:addingmulticards}Adding Concatenated Cards to a FitsChan}
+
+If you have all your cards concatenated together into a single long string,
+each occupying 80 characters (with no delimiters), you can insert them
+into a \htmlref{FitsChan}{FitsChan} in a single call using
+\htmlref{astPutCards}{astPutCards}.
+This call first empties the supplied FitsChan of any existing cards, then
+inserts the new cards, and finally rewinds the FitsChan so that a
+subsequent call to
+\htmlref{astRead}{astRead}
+will start reading from the first supplied card. The
+astPutCards function uses \htmlref{astPutFits}{astPutFits}
+internally to interpret and store each individual card, and so the
+caveats in \secref{ss:addingfitscards} should be read.
+
+For instance, if you are using the CFITSIO library for access to FITS
+files, you can use the CFITSIO fits\_hdr2str function to obtain a string suitable
+for passing to astPutCards:
+
+\small
+\begin{terminalv}
+
+
+if( !fits_hdr2str( fptr, 0, NULL, 0, &header, &nkeys, &status ) )
+   fitschan = astFitsChan( NULL, NULL, "" );
+   astPutCards( fitschan, header );
+   header = free( header );
+   wcsinfo = astRead( fitschan );
+
+   ...
+}
+\end{terminalv}
+\normalsize
+
+
+
+\subsection{\label{ss:readingnativefits}Reading Native Objects From a FitsChan}
+
+Once you have stored a FITS header description of an \htmlref{Object}{Object} in a
+\htmlref{FitsChan}{FitsChan} using the native encoding (\secref{ss:writingnativefits}),
+you can read it back using \htmlref{astRead}{astRead} in much the same way as with a
+basic \htmlref{Channel}{Channel} (\secref{ss:readingfromachannel}). Similar comments
+about validating the Object you read also apply
+(\secref{ss:validatinginput}).  If you have just written to the
+FitsChan, you must remember to rewind it first:
+
+\small
+\begin{terminalv}
+AstObject *object;
+
+...
+
+astClear( fitschan, "Card" );
+object = astRead( fitschan );
+\end{terminalv}
+\normalsize
+
+An important feature of a FitsChan is that read operations are
+destructive. This means that if an Object description is found, it
+will be consumed by astRead which will remove all the cards involved,
+including associated COMMENT cards, from the FitsChan. Thus, if you
+write an Object to a FitsChan, rewind, and read the same Object back,
+you should end up with the original FitsChan contents.  If you need to
+circumvent this behaviour for any reason, it is a simple matter to
+make a copy of a FitsChan using \htmlref{astCopy}{astCopy}
+(\secref{ss:copyingobjects}). If you then read from the copy, the
+original FitsChan will remain untouched.
+
+After a read completes, the FitsChan's \htmlref{Card}{Card} attribute identifies the
+card immediately following the last card read, or the end-of-file of
+there are no more cards.
+
+Since the \emph{native} encoding is being used, any long strings involved
+in the object description will have been split into two or more adjacent
+contuation cards when the Object was stored in the header using function
+\htmlref{astWrite}{astWrite}. The astRead function reverses this process by concatenating any
+such adjacent continuation cards to re-create the original long string.
+
+
+\subsection{Saving and Restoring Multiple Objects in a FitsChan}
+
+When using the native FITS encoding, multiple Objects may be stored
+and all I/O operations are sequential.  This means that you can simply
+write a sequence of Objects to a \htmlref{FitsChan}{FitsChan}. After each write operation,
+the \htmlref{Card}{Card} attribute will be updated so that the next write appends the
+next \htmlref{Object}{Object} description to the previous one.
+
+If you then rewind the FitsChan, you can read the Objects back in the
+original order. Reading them back will, of course, remove their
+descriptions from the FitsChan (\secref{ss:readingnativefits}) but the
+behaviour of the Card attribute is such that successive reads will
+simply return each Object in sequence.
+
+The only thing that may require care, given that a FitsChan can always
+be addressed randomly by setting its Card attribute, is to avoid
+writing one Object on top of another. For obvious reasons, the Object
+descriptions in a FitsChan must remain separate if they are to make
+sense when read back.
+
+\subsection{Mixing Native Objects with Other FITS Cards}
+
+Of course, any real FITS header will contain other information besides
+AST Objects, if only the mandatory FITS cards that must accompany all
+FITS data. When FITS headers are read in from a real dataset,
+therefore, any native AST \htmlref{Object}{Object} descriptions will be inter-mixed with
+many other cards.
+
+Because this is the normal state of affairs, the boolean (integer)
+\htmlref{Skip}{Skip} attribute for a \htmlref{FitsChan}{FitsChan} defaults to one. This means that when
+you read an Object From a FitsChan, any irrelevant cards will simply
+be skipped over until the start of the next Object description, if
+any, is found. If you start reading part way through an Object
+description, no error will result. The remainder of the description
+will simply be skipped.
+
+Setting Skip to zero will change this behaviour to resemble that of a
+basic \htmlref{Channel}{Channel} (\secref{ss:mixingchanneltext}), where extraneous data
+are not permitted by default, but this will probably rarely be useful.
+
+\subsection{\label{ss:findingandchangingfits}Finding and Changing Cards in a FitsChan}
+
+You can search for, and retrieve, particular cards in a \htmlref{FitsChan}{FitsChan} by
+keyword, using the function \htmlref{astFindFits}{astFindFits}. This performs a search,
+starting at the current card, until it finds a card whose keyword
+matches the template you supply, or the end-of-file is reached.
+
+If a suitable card is found, astFindFits optionally returns the card's
+contents and then sets the FitsChan's \htmlref{Card}{Card} attribute either to
+identify the card found, or the one following it. The way you want the
+Card attribute to be set is indicated by the final boolean (int)
+argument to astFindFits. A value of one is returned to indicate
+success.  If a suitable card cannot be found, astFindFits returns a
+value of zero to indicate failure and sets the FitsChan's Card
+attribute to the end-of-file.
+
+Requesting that the Card attribute be set to indicate the card that
+astFindFits finds is useful if you want to replace that card with a
+new one, as in this example:
+
+\small
+\begin{terminalv}
+char newcard[ 81 ];
+
+...
+
+(void) astFindFits( fitschan, "AIRMASS", NULL, 0 );
+astPutFits( fitschan, newcard, 1 );
+\end{terminalv}
+\normalsize
+
+Here, astFindFits is used to search for a card with the keyword
+AIRMASS, with a NULL pointer being given to indicate that we do not
+want the card's contents returned. If the card is found, \htmlref{astPutFits}{astPutFits}
+then overwrites it with a new card.  Otherwise, the Card attribute
+ends up pointing at the end-of-file and the new card is simply
+appended to the end of the FitsChan.
+
+A similar approach can be used to delete selected cards from a
+FitsChan using \htmlref{astDelFits}{astDelFits}, which deletes the current card:
+
+\small
+\begin{terminalv}
+if ( astFindFits( fitschan, "BSCALE", NULL, 0 ) ) astDelFits( fitschan );
+\end{terminalv}
+\normalsize
+
+This deletes the first card, if any, with the BSCALE keyword.
+
+Requesting that astFindFits increments the Card attribute to identify
+the card following the one found is more useful when writing loops.
+For example, the following loop extracts each card whose keyword
+matches the template ``CD\%6d'' (that is, ``CD'' followed by six
+decimal digits):
+
+\small
+\begin{terminalv}
+while ( astFindFits( fitschan, "CD%6d", card, 1 ) {
+   <process the card's contents>
+}
+\end{terminalv}
+\normalsize
+
+For further details of keyword templates, see the description of
+astFindFits in \appref{ss:functiondescriptions}.
+
+\subsection{\label{ss:fitssourceandsink}Source and Sink Functions for FitsChans}
+
+The use of source and sink functions with a \htmlref{FitsChan}{FitsChan} is optional. This
+is because you can always arrange to explicitly fill a FitsChan with
+FITS cards (\secref{ss:addingfitscards} and \secref{ss:addingmulticards})
+and you can also extract any
+cards that remain and write them out yourself
+(\secref{ss:extractingfitscards}) before you delete the FitsChan.
+
+If you choose to use these functions, however, they behave in a very
+similar manner to those used by a \htmlref{Channel}{Channel} (\secref{ss:channelsource}
+and \secref{ss:channelsink}). You supply pointers to these functions,
+as arguments to the constructor function \htmlref{astFitsChan}{astFitsChan} when you create
+the FitsChan (\secref{ss:creatingafitschan}). The source function is
+invoked implicitly at this point to fill the FitsChan with FITS cards
+and the FitsChan is then rewound, so that the first card becomes
+current. The sink function is automatically invoked later, when the
+FitsChan is deleted, in order to write out any cards that remain in
+it.
+
+The only real difference between the source and sink functions for a
+FitsChan and a basic Channel is that FITS cards are limited in length
+to 80~characters, so the choice of buffer size is simplified.  The
+``Source'' and ``Sink'' functions in \secref{ss:channelsource} and
+\secref{ss:channelsink} could therefore be used to access FITS headers
+stored in text files simply by changing LEN to be 80.  If you were not
+accessing a text file, however, appropriate changes to the I/O
+statements would be needed since the separating newline characters
+would be absent. The details obviously depend on the format of the
+file you are handling, which need not necessarily be a true FITS file.
+
+
+\cleardoublepage
+\section{\label{ss:foreignfits}Using Foreign FITS Encodings}
+
+We saw in the previous section (\secref{ss:nativefits}) how to store
+and retrieve any kind of AST \htmlref{Object}{Object} in a FITS header by using a
+\htmlref{FitsChan}{FitsChan}. To achieve this, we set the FitsChan's \htmlref{Encoding}{Encoding} attribute to
+NATIVE. However, the Objects we wrote could then only be read back by
+other programs that use AST.
+
+In practice, we will also encounter FITS headers containing WCS
+information written by other software systems.  We will probably also
+need to write FITS headers in a format that can be understood by these
+systems. Indeed, this interchange of data is one of the main reasons
+for the existence of FITS, so in this section we will examine how to
+accommodate these requirements.
+
+\subsection{\label{ss:foreignencodings}The Foreign FITS Encodings}
+
+As mentioned previously (\secref{ss:nativeencoding}), there are a
+number of conventions currently in use for storing WCS information in
+FITS headers, which we call \emph{encodings}. Here, we are concerned
+with those encodings defined by software systems other than AST, which
+we term \emph{foreign encodings}.
+
+Currently, AST supports six foreign encodings, which may be selected
+by setting the \htmlref{Encoding}{Encoding} attribute of a \htmlref{FitsChan}{FitsChan} to one of the
+following (character string) values:
+
+\begin{quote}
+\begin{description}
+\item[DSS]\mbox{}\\
+This encoding stores WCS information using the convention developed at
+the Space Telescope Science Institute for the Digitised Sky Survey
+(DSS) astrometric plate calibrations.  DSS images which use this
+convention are widely available and it is understood by a number of
+important and well-established astronomy applications.
+
+However, the calibration model used (based on a polynomial fit) is not
+easily applicable to other types of data and creating the polynomial
+coefficients needed to calibrate your own images can prove
+difficult. For this reason, the DSS encoding is probably best viewed
+as a ``read-only'' format. It is possible, however, to read in WCS
+information using this encoding and then to write it back out again,
+so long as only minor changes have been made.
+
+\item[FITS-WCS]\mbox{}\\
+This encoding is very important because it is based on a new FITS standard
+which should, for the first time, address the problem of celestial coordinate
+systems in a proper manner, by considerably extending the original FITS
+standard.
+
+The conventions used are described in a series of papers by
+E.W.\,Greisen, M.\,Calabretta, \emph{et. al.}, often referred to as the
+``FITS-WCS papers''. They are described at
+\url{http://fits.gsfc.nasa.gov/fits_wcs.html}. Now that the first two papers
+in this series have been agreed, this encoding should be understood by any
+FITS-WCS compliant software and it is likely to be adopted widely for FITS
+data in future.  For details of the coverage of these conventions provided
+by the FitsChan class, see \appref{ss:fitswcscoverage}.
+
+\item[FITS-IRAF]\mbox{}\\
+This encoding is based on the conventions described in the document
+``World Coordinate Systems Representations Within the FITS Format'' by R.J.
+Hanisch and D.G. Wells, 1988.\footnote{Available by ftp from
+fits.cv.nrao.edu /fits/documents/wcs/wcs88.ps.Z} It is employed
+by the IRAF data analysis facility, so its use will facilitate data
+exchange with IRAF. This encoding is in effect a sub-set of the current
+FITS-WCS encoding.
+
+\item[FITS-PC]\mbox{}\\
+This encoding is based on a previous version of the proposed new FITS WCS
+standard which used \texttt{PCjjjjiii} and \texttt{CDELTj} keywords to describe
+axis rotation and scaling. Versions of AST prior to V1.5 used this scheme
+for the FITS-WCS encoding. As of V1.5, FITS-WCS uses \texttt{CDi\_j}
+keywords instead.\footnote{There are many other differences between the
+previous and the current FITS-WCS encodings. The keywords to describe
+axis rotation and scaling is used purely as a label to identify the
+scheme.} The FITS-PC encoding is included in AST V1.5 only to allow
+FITS-WCS data created with previous versions to be read. It should not,
+in general, be used to create new data sets.
+
+\item[FITS-AIPS]\mbox{}\\
+This encoding is based on the conventions described in the document
+``Non-linear Coordinate Systems in AIPS'' by Eric W. Greisen (revised 9th
+September, 1994).\footnote{Available by ftp from fits.cv.nrao.edu
+/fits/documents/wcs/aips27.ps.Z} It is currently employed by the AIPS
+data analysis facility, so its use will facilitate data exchange with
+AIPS. This encoding uses \texttt{CROTAi} and \texttt{CDELTi} keywords to
+describe axis rotation and scaling.
+
+\item[FITS-AIPS++]\mbox{}\\
+Encodes coordinate system information in FITS
+header cards using the conventions used by the AIPS++ project.
+This is an extension of FITS-AIPS which includes some of the
+features of FITS-PC and FITS-IRAF.
+\end{description}
+\end{quote}
+
+For more detail about the above encodings, see the description of the
+Encoding attribute in \appref{ss:attributedescriptions}.
+
+\subsection{\label{ss:foreignfitslimitations}Limitations of Foreign Encodings}
+
+The foreign encodings available for storing WCS information in FITS
+headers have a number of limitations when compared with the native
+encoding of AST Objects (\secref{ss:nativefits}). The main ones are:
+
+\begin{enumerate}
+\item Only one class of AST \htmlref{Object}{Object}, the \htmlref{FrameSet}{FrameSet}, may be represented
+using a foreign FITS encoding. This should not come as a surprise,
+because the purpose of storing WCS information in FITS headers is to
+attach coordinate systems to an associated array of data. Since the
+FrameSet is the AST Object designed for the same purpose
+(\secref{ss:baseandcurrent}), there is a natural correspondence.
+
+The way in which a FrameSet is translated to and from the foreign
+encoding also follows from this correspondence. The FrameSet's base
+\htmlref{Frame}{Frame} identifies the data grid coordinates of the associated FITS
+data. These are the same as FITS pixel coordinates, in which the first
+pixel (in 2 dimensions) has coordinates (1,1) at its
+centre. Similarly, the current Frame of the FrameSet identifies the
+FITS world coordinate system associated with the data.
+
+\item You may store a representation of only a single FrameSet in any
+individual set of FITS header cards (\emph{i.e.}\ in a single
+\htmlref{FitsChan}{FitsChan}) at one time. If you attempt to store more than one, you may
+over-write the previous one or generate an invalid representation of
+your WCS information.
+
+This is mainly a consequence of the use of fixed FITS keywords by
+foreign encodings and the fact that you cannot, in general, have
+multiple FITS cards with the same keyword.
+
+\item In general, it will not be possible to store every possible
+FrameSet that you might construct. Depending on the encoding, only
+certain FrameSets that conform to particular restrictions can be
+represented and, even then, some of their information may be lost. See
+the description of the \htmlref{Encoding}{Encoding} attribute in
+\appref{ss:attributedescriptions} for more details of these
+limitations.
+\end{enumerate}
+
+It should be understood that using foreign encodings to read and write
+information held in AST Objects is essentially a process of converting
+the data format. As such, it potentially suffers from the same
+problems faced by all such processes, \emph{i.e.}\ differences between
+the AST data model and that of the foreign encoding may cause some
+information to be lost.  Because the AST model is extremely flexible,
+however, any data loss can largely be eliminated when reading.
+Instead, this effect manifests itself in the form of the above
+encoding-dependent restrictions on the kind of AST Objects which may
+be written.
+
+One of the aims of the AST library, of course, is to insulate you from
+the details of these foreign encodings and the restrictions they
+impose. We will see shortly, therefore, how AST provides a mechanism
+for determining whether your WCS information satisfies the necessary
+conditions and allows you to make an automatic choice of which
+encoding to use.
+
+\subsection{\label{ss:identifyingfitsencoding}Identifying Foreign Encodings on Input}
+
+Let us now examine the practicalities of extracting WCS information
+from a set of FITS header cards which have been written by some other
+software system. We will pretend that our program does not know which
+encoding has been used for the WCS information and must discover this
+for itself. In order to have a concrete example, however, we will use
+the following set of cards. These use the FITS-AIPS encoding and
+contain a typical mix of other FITS cards which are irrelevant to the
+WCS information in which we are interested:
+
+\small
+\begin{terminalv}
+SIMPLE  =                    T / Written by IDL:  30-Jul-1997 05:35:42.00
+BITPIX  =                  -32 / Bits per pixel.
+NAXIS   =                    2 / Number of dimensions
+NAXIS1  =                  300 / Length of x axis.
+NAXIS2  =                  300 / Length of y axis.
+CTYPE1  = 'GLON-ZEA'           / X-axis type
+CTYPE2  = 'GLAT-ZEA'           / Y-axis type
+CRVAL1  =           -149.56866 / Reference pixel value
+CRVAL2  =           -19.758201 / Reference pixel value
+CRPIX1  =              150.500 / Reference pixel
+CRPIX2  =              150.500 / Reference pixel
+CDELT1  =             -1.20000 / Degrees/pixel
+CDELT2  =              1.20000 / Degrees/pixel
+CROTA1  =              0.00000 / Rotation in degrees.
+SURVEY  = 'COBE DIRBE'
+BUNITS  = 'MJy/sr  '           /
+ORIGIN  = 'CDAC    '           / Cosmology Data Analysis Center
+TELESCOP= 'COBE    '           / COsmic Background Explorer satellite
+INSTRUME= 'DIRBE   '           / COBE instrument [DIRBE, DMR, FIRAS]
+PIXRESOL=                    9 / Quad tree pixel resolution [6, 9]
+DATE    = '27/09/94'           / FITS file creation date (dd/mm/yy)
+DATE-MAP= '16/09/94'           / Date of original file creation (dd/mm/yy)
+COMMENT     COBE specific keywords
+DATE-BEG= '08/12/89'           / date of initial data represented (dd/mm/yy)
+DATE-END= '25/09/90'           / date of final data represented   (dd/mm/yy)
+\end{terminalv}
+\normalsize
+
+The first step is to create a \htmlref{FitsChan}{FitsChan} and insert these cards into
+it. If ``cards'' is an array of pointers to character strings holding
+the header cards and ``ncards'' is the number of cards, this could be
+done as follows:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+#define MAXCARD 100
+AstFitsChan *fitschan;
+char *cards[ MAXCARD ];
+int icard, ncard;
+
+...
+
+fitschan = astFitsChan( NULL, NULL, "" );
+for ( icard = 0; icard < ncard; icard++ ) astPutFits( fitschan, cards[ icard ], 0 );
+\end{terminalv}
+\normalsize
+
+Note that we have not initialised the \htmlref{Encoding}{Encoding} attribute of the
+FitsChan as we did in \secref{ss:creatingafitschan} when we wanted to
+use the native encoding. This is because we are pretending not to know
+which encoding to use and want AST to determine this for us. By
+leaving the Encoding attribute un-set, its default value will adjust
+to whichever encoding AST considers to be most appropriate, according
+to the FITS header cards present. For details of how this choice is
+made, see the description of the Encoding attribute in
+\appref{ss:attributedescriptions}.
+
+This approach has the obvious advantages of making our program simpler
+and more flexible and of freeing us from having to know about the
+different encodings available. As a bonus, it also means that the
+program will be able to read any new encodings that AST may support in
+future, without needing to be changed.
+
+At this point, we could enquire the default value of the Encoding
+attribute, which indicates which encoding AST intends to use, as
+follows:
+
+\small
+\begin{terminalv}
+const char *encode;
+
+...
+
+
+encode = astGetC( fitschan, "Encoding" );
+\end{terminalv}
+\normalsize
+
+The result of this enquiry would be the string ``FITS-AIPS''.  Note
+that we could also have set the FitsChan's Encoding attribute
+explicitly, such as when creating it:
+
+\small
+\begin{terminalv}
+fitschan = astFitsChan( NULL, NULL, "Encoding=FITS-AIPS" );
+\end{terminalv}
+\normalsize
+
+If we tried to read information using this encoding
+(\secref{ss:readingforeignfits}), but failed, we could then change the
+encoding and try again.  This would allow our program to take control
+of how the optimum choice of encoding is arrived at. However, it would
+also involve using explicit knowledge of the encodings available and
+this is best avoided if possible.
+
+\subsection{\label{ss:readingforeignfits}Reading Foreign WCS Information from a FITS Header}
+
+Having stored a set of FITS header cards in a \htmlref{FitsChan}{FitsChan} and determined
+how the WCS information is encoded
+(\secref{ss:identifyingfitsencoding}), the next step is to read an AST
+\htmlref{Object}{Object} from the FitsChan using \htmlref{astRead}{astRead}. We must also remember to
+rewind the FitsChan first, if necessary, such as by clearing its \htmlref{Card}{Card}
+attribute, which defaults to 1:
+
+\small
+\begin{terminalv}
+AstObject *wcsinfo;
+
+...
+
+astClear( fitschan, "Card" );
+wcsinfo = astRead( fitschan );
+\end{terminalv}
+\normalsize
+
+If the pointer returned by astRead is not equal to AST\_\_NULL, then
+an Object has been read successfully. Otherwise, there was either no
+information to read or the choice of FITS encoding
+(\secref{ss:identifyingfitsencoding}) was inappropriate.
+
+At this point you might like to indulge in a little data validation
+along the lines described in \secref{ss:validatinginput}, for example:
+
+\small
+\begin{terminalv}
+if ( !strcmp( astGetC( wcsinfo, "Class" ), "FrameSet" ) ) {
+   <the Object is a FrameSet, so use it>
+} else {
+   <something unexpected was read>
+}
+\end{terminalv}
+\normalsize
+
+If a foreign encoding has definitely been used, then the Object will
+automatically be a \htmlref{FrameSet}{FrameSet} (\secref{ss:foreignfitslimitations}), so
+this stage can be omitted. However, if the native encoding
+(\secref{ss:nativeencoding}) might have been employed, which is a
+possibility if you accept the FitsChan's default \htmlref{Encoding}{Encoding} value, then
+any class of Object might have been read and a quick check would be
+worthwhile.
+
+If you used \htmlref{astShow}{astShow} (\secref{ss:displayingobjects}) to examine the
+FrameSet which results from reading our example FITS header
+(\secref{ss:identifyingfitsencoding}), you would find that its base
+\htmlref{Frame}{Frame} describes the image's pixel coordinate system and that its
+current Frame is a \htmlref{SkyFrame}{SkyFrame} representing galactic coordinates. These
+two Frames are inter-related by a \htmlref{Mapping}{Mapping} (actually a \htmlref{CmpMap}{CmpMap}) which
+incorporates the effects of various rotations, scalings and a
+``zenithal equal area'' sky projection, so that each pixel of the FITS
+image is mapped on to a corresponding sky position in galactic
+coordinates.
+
+Because this FrameSet may be used both as a Mapping
+(\secref{ss:framesetasmapping}) and as a Frame
+(\secref{ss:framesetasframe}), it may be employed directly to perform
+many useful operations without any need to decompose it into its
+component parts. These include:
+
+\begin{itemize}
+\item Transforming data grid (FITS pixel) coordinates into galactic
+coordinates and \emph{vice versa} (\secref{ss:framesetasmapping}).
+
+\item Formatting coordinate values (either pixel or galactic
+coordinates) ready for display to a user
+(\secref{ss:formattingaxisvalues} and \secref{ss:normalising}).
+
+\item Enquiring about axis labels (or other axis
+information---\secref{ss:frameattributes}) which might be used, for
+example, to label columns of coordinates in a table
+(\secref{ss:frameaxisattributes}).
+
+\item Aligning the image with another image from which a similar
+FrameSet has been obtained (\secref{ss:registeringimages}).
+
+\item Creating a \htmlref{Plot}{Plot} (\secref{ss:plots}), which can be used to overlay
+a variety of graphical information (including a coordinate
+grid---Figure~\ref{fig:gridplot}) on the displayed image.
+
+\item Generating a new FrameSet which reflects any geometrical
+processing you perform on the associated image data
+(\secref{ss:wcsprocessingexample}). This new FrameSet could then be
+written out as FITS headers to describe the modified image
+(\secref{ss:writingforeignfits}).
+\end{itemize}
+
+If the FrameSet contains other Frames (apart from the base and current
+Frames), then you would also have access to information about other
+coordinate systems associated with the image.
+
+\subsection{\label{ss:destructiveread}Removing WCS Information from FITS Headers---the Destructive Read}
+
+It is instructive at this point to examine the contents of a \htmlref{FitsChan}{FitsChan}
+after we have read a \htmlref{FrameSet}{FrameSet} from it
+(\secref{ss:readingforeignfits}). The following would rewind our
+FitsChan and display its contents:
+
+\small
+\begin{terminalv}
+#include <stdio.h>
+char card[ 81 ];
+
+...
+
+astClear( fitschan, "Card" );
+while ( astFindFits( fitschan, "%f", card, 1 ) ) (void) printf( "%s\n", card );
+\end{terminalv}
+\normalsize
+
+The output, if we started with the example FITS header in
+\secref{ss:identifyingfitsencoding}, might look like this:
+
+\small
+\begin{terminalv}
+SIMPLE  =                    T /  Written by IDL:  30-Jul-1997 05:35:42.00
+BITPIX  =                  -32 /  Bits per pixel.
+NAXIS   =                    2 /  Number of dimensions
+NAXIS1  =                  300 /  Length of x axis.
+NAXIS2  =                  300 /  Length of y axis.
+SURVEY  = 'COBE DIRBE'
+BUNITS  = 'MJy/sr  '
+ORIGIN  = 'CDAC    '           /  Cosmology Data Analysis Center
+TELESCOP= 'COBE    '           /  COsmic Background Explorer satellite
+INSTRUME= 'DIRBE   '           /  COBE instrument [DIRBE, DMR, FIRAS]
+PIXRESOL=                    9 /  Quad tree pixel resolution [6, 9]
+DATE    = '27/09/94'           /  FITS file creation date (dd/mm/yy)
+DATE-MAP= '16/09/94'           /  Date of original file creation (dd/mm/yy)
+COMMENT     COBE specific keywords
+DATE-BEG= '08/12/89'           /  date of initial data represented (dd/mm/yy)
+DATE-END= '25/09/90'           /  date of final data represented   (dd/mm/yy)
+\end{terminalv}
+\normalsize
+
+Comparing this with the original, you can see that all the FITS cards
+that represent WCS information have been removed. They have
+effectively been ``sucked out'' of the FitsChan by the destructive
+read that \htmlref{astRead}{astRead} performs and converted into an equivalent
+FrameSet. AST remembers where they were stored, however, so that if we
+later write WCS information back into the FitsChan
+(\secref{ss:writingforeignfits}) they will, as far as possible, go
+back into their original locations. This helps to preserve the overall
+layout of the FITS header.
+
+You can now see why astRead performs destructive reads. It is a
+mechanism for removing WCS information from a FITS header while
+insulating you, as a programmer, from the details of the encoding
+being used. It means you can ensure that all relevant header cards
+have been removed, giving you a clean slate, without having to know
+which FITS keywords any particular encoding uses.
+
+Clearing this WCS information out of a FITS header is particularly
+important when considering how to write new WCS information back after
+processing (\secref{ss:writingforeignfits}). If any relevant FITS
+cards are left over from the input dataset and find their way into the
+new processed header, they could interfere with the new information
+being written.\footnote{This can happen if a particular keyword is
+present in the input header but is not used in the output header
+(whether particular keywords are used can depend on the WCS
+information being stored). In such a case, the original value would
+not be over-written by a new output value, so would remain erroneously
+present.} The destructive read mechanism ensures that this doesn't
+happen.
+
+\subsection{\label{ss:propagatingwcsinformation}Propagating WCS Information through Data Processing Steps}
+
+One of the purposes of AST is to make it feasible to propagate WCS
+information through successive stages of data processing, so that it
+remains consistent with the associated image data. As far as possible,
+this should happen regardless of the FITS encoding used to store the
+original WCS information.
+
+If the data processing being performed does not change the
+relationship between image pixel and world coordinates (whatever these
+may be), then propagation of the WCS information is
+straightforward. You can simply copy the FITS header from input to
+output.
+
+If this relationship changes, however, then the WCS information must
+be processed alongside the image data and a new FITS header generated
+to represent it. In this case, the sequence of operations within your
+program would probably be as follows:
+
+\begin{enumerate}
+\item Read the image data and associated FITS header from the input
+dataset, putting the header cards into a \htmlref{FitsChan}{FitsChan}
+(\secref{ss:identifyingfitsencoding}).
+
+\item Read an AST \htmlref{Object}{Object}, a \htmlref{FrameSet}{FrameSet}, from the FitsChan (typically
+using a foreign FITS encoding---\secref{ss:readingforeignfits}).
+
+\item Process the image data and modify the FrameSet accordingly
+(\emph{e.g.}~\secref{ss:wcsprocessingexample}).
+
+\item Write the FrameSet back into the FitsChan
+(\secref{ss:writingforeignfits}).
+
+\item Perform any other modification of FITS header cards your program
+may require.
+
+\item Write the FitsChan contents (\emph{i.e.}\ processed header
+cards) and image data to the output dataset.
+\end{enumerate}
+
+In stage (2), the original WCS information will be removed from the
+FitsChan by a destructive read. Later, in stage (4), new WCS
+information is written to replace it. This is the process which we
+consider next (\secref{ss:writingforeignfits}).
+
+\subsection{\label{ss:writingforeignfits}Writing Foreign WCS Information to a FITS Header}
+
+Before we can write processed WCS information held in a \htmlref{FrameSet}{FrameSet} back
+into a \htmlref{FitsChan}{FitsChan} in preparation for output, we must select the FITS
+encoding to use.  Unfortunately, we cannot simply depend on the
+default value of the \htmlref{Encoding}{Encoding} attribute, as we did when reading the
+input information (\secref{ss:identifyingfitsencoding}), because the
+destructive action of reading the WCS data
+(\secref{ss:destructiveread}) will have altered the FitsChan's
+contents. This, in turn, will have changed the choice of default
+encoding, probably causing it to revert to NATIVE.
+
+We will return to the question of the optimum choice of encoding
+below.  For now, let's assume that we want to use the same encoding
+for output as we used for input. Since we enquired what that was
+before we read the input WCS data from the FitsChan
+(\secref{ss:identifyingfitsencoding}), we can now set that value
+explicitly. We can also set the FitsChan's \htmlref{Card}{Card} attribute back to 1 at
+the same time (because the write will fail if the FitsChan is not
+rewound). \htmlref{astWrite}{astWrite} can then be used to write the output WCS
+information into the FitsChan:
+
+\small
+\begin{terminalv}
+int nobj;
+
+...
+
+astSet( fitschan, "Card=1, Encoding=%s", encode );
+nobj = astWrite( fitschan, wcsinfo );
+\end{terminalv}
+\normalsize
+
+The value returned by astWrite (assigned to ``nobj'') indicates how
+many Objects were written. This will either be 1 or zero. A value of
+zero is used to indicate that the information could not be encoded in
+the form you requested. If this happens, nothing will have been
+written.
+
+If your choice of encoding proves inadequate, the probable reason is
+that the changes you have made to the FrameSet have caused it to
+depart from the data model which the encoding assumes.  AST knows
+about the data model used by each encoding and will attempt to
+simplify the FrameSet you provide so as to fit into that model, thus
+relieving you of the need to understand the details and limitations of
+each encoding yourself.\footnote{Storing values in the FitsChan for
+FITS headers NAXIS1, NAXIS2, \emph{etc.} (the grid dimensions in pixels),
+before invoking
+astWrite
+can sometimes help to produce a successful write.} When this attempt fails,
+however, you must consider what alternative encoding to use.
+
+Ideally, you would probably want to try a sequence of alternative
+encodings, using an approach such as the following:
+
+\small
+\begin{terminalv}
+/* 1. */
+astSet( fitschan, "Card=1, Encoding=FITS-IRAF" );
+if ( !astWrite( fitschan, wcsinfo ) ) {
+
+/* 2. */
+   astSetC( fitschan, "Encoding", encode );
+   if ( !astWrite( fitschan, wcsinfo ) ) {
+
+/* 3. */
+      astSet( fitschan, "Encoding=NATIVE" );
+      (void) astWrite( fitschan, wcsinfo );
+   }
+}
+\end{terminalv}
+\normalsize
+
+That is:
+
+\begin{enumerate}
+\item Start by trying the FITS-WCS encoding, on the grounds that FITS
+should provide a universal interchange standard in which all WCS
+information should be expressed if possible.
+
+\item If that fails, then try the original encoding used for the input
+WCS information, on the grounds that you are at least not making the
+information any harder for others to read than it originally was.
+
+\item If that also fails, then you are probably trying to store fairly
+complex information for which you need the native encoding. Only other
+AST programs will then be able to read this information, but these are
+probably the only programs that will be able to do anything sensible
+with it anyway.
+\end{enumerate}
+
+An alternative approach might be to encode the WCS information in several
+ways, since this gives the maximum chance that other software will be
+able to read it. This approach is only possible if there is no
+significant conflict between the FITS keywords used by the different
+encodings\footnote{In practice, this means you should avoid mixing
+FITS-IRAF, FITS-WCS, FITS-AIPS, FITS-AIPS++ and FITS-PC encodings since they share
+many keywords.}.  Adopting this approach would simply require multiple
+calls to astWrite, rewinding the FitsChan and changing its Encoding value
+before each one.
+
+Unfortunately, however, there is a drawback to duplicating WCS
+information in the FITS header in this way, because any program which
+modifies one version of this information and simply copies the
+remainder of the header will risk producing two inconsistent sets of
+information. This could obviously be confusing to subsequent
+software. Whether you consider this a worthwhile risk probably depends
+on the use to which you expect your data to be put.
+
+\cleardoublepage
+\section{\label{ss:xmlchan}Storing AST Objects as XML (XmlChan)}
+
+\htmladdnormallinkfoot{XML}{http://www.w3.org/XML/}
+is fast becoming the standard format for passing structured data around
+the internet, and much general purpose software has been written for
+tasks such as the parsing, editing, display and transformation of XML
+data. The \htmlref{XmlChan}{XmlChan} class (a specialised form of \htmlref{Channel}{Channel}) provides
+facilities for storing AST objects externally in the form of XML documents,
+thus allowing such software to be used.
+
+The primary XML format used by the XmlChan class is a fairly close
+transliteration of the AST native format produced by the basic Channel
+class. Currently, there is no DTD or schema defining the structure of data
+produced in this format by an XmlChan. The following is a native AST
+representation of a simple 1-D \htmlref{Frame}{Frame} (including comments and with the \htmlref{Full}{Full}
+attribute set to zero so that some default attribute values are included
+as extra comments):
+
+\small
+\begin{terminalv}
+ Begin Frame    # Coordinate system description
+#   Title = "1-d coordinate system"     # Title of coordinate system
+    Naxes = 1   # Number of coordinate axes
+    Domain = "SCREEN"   # Coordinate system domain
+#   Lbl1 = "Axis 1"     # Label for axis 1
+#   Uni1 = "cm"         # Units for axis 1
+    Ax1 =       # Axis number 1
+       Begin Axis       # Coordinate axis
+          Unit = "cm"   # Axis units
+       End Axis
+ End Frame
+\end{terminalv}
+\normalsize
+
+The corresponding XmlChan output would look like:
+
+\small
+\begin{terminalv}
+ <Frame xmlns="http://www.starlink.ac.uk/ast/xml/"
+        desc="Coordinate system description">
+    <_attribute name="Title" quoted="true" value="1-d coordinate system"
+                desc="Title of coordinate system" default="true"/>
+    <_attribute name="Naxes" value="1" desc="Number of coordinate axes"/>
+    <_attribute name="Domain" quoted="true" value="SCREEN"
+                desc="Coordinate system domain"/>
+    <_attribute name="Lbl1" quoted="true" value="Axis 1"
+                desc="Label for axis 1" default="true"/>
+    <_attribute name="Uni1" quoted="true" value="cm"
+                desc="Units for axis 1" default="true"/>
+    <Axis label="Ax1" desc="Coordinate axis">
+       <!--Axis number 1-->
+       <_attribute name="Unit" quoted="true" value="cm" desc="Axis units"/>
+    </Axis>
+ </Frame>
+\end{terminalv}
+\normalsize
+
+
+Notes:
+
+\begin{enumerate}
+\item The AST class name is used as the name for an XML element which contain
+a description of an AST object.
+
+\item AST attributes are described by XML elements with the name
+``\_attribute''. Unfortunately, the word ``attribute'' is also used by XML
+to refer to a ``name=value'' pair within an element start tag. So for
+instance, the ``\htmlref{Title}{Title}'' attribute of the AST Frame object is described
+within an XML element with name ``\_attribute'' in which the XML attribute
+``name'' has the value ``Title'', and the XML attribute ``value'' has the
+value ``1-d coordinate system''. The moral is always to be clear clear
+about the context (AST or XML) in which the word \emph{attribute} is being
+used!
+
+\item The XML includes comments both as XML attributes with the name ``desc'',
+and as separate comment tags.
+
+\item Elements which describe default values are identified by the fact
+that they have an XML attribute called ``default'' set to the value
+``true''. These elements are ignored when being read back into an XmlChan.
+
+\item The outer-most XML element of an AST object will set the default
+namespace to \verb+http://www.starlink.ac.uk/ast/xml/+ which will be
+inherited by all nested elements.
+
+\end{enumerate}
+
+
+The XmlChan class changes the default value for the \htmlref{Comment}{Comment} and Full
+attributes (inherited from the base Channel class) to zero and -1,
+resulting in terse output by default. With the default values for these
+attributes, the above XML is reduced to the following:
+
+\small
+\begin{terminalv}
+ <Frame xmlns="http://www.starlink.ac.uk/ast/xml/">
+    <_attribute name="Naxes" value="1"/>
+    <_attribute name="Domain" quoted="true" value="SCREEN"/>
+    <Axis label="Ax1">
+       <_attribute name="Unit" quoted="true" value="cm"/>
+    </Axis>
+ </Frame>
+\end{terminalv}
+\normalsize
+
+
+The XmlChan class uses the \htmlref{Skip}{Skip} attributes very similarly to the Channel
+class. If Skip is zero (the default) then an error will be reported if the text
+supplied by the source function does not begin with an AST \htmlref{Object}{Object}. If
+Skip is non-zero, then initial text is skipped over without error until
+the start of an AST object is found. this allows an AST object to be
+located within a larger XML document.
+
+\subsection{Reading IVOA Space-Time-Coordinates XML (STC-X) Descriptions}
+The \htmlref{XmlChan}{XmlChan} class also provides support for reading (but not writing) XML
+documents which use a restricted subset of an early draft (V1.20) of the
+IVOA Space-Time-Coordinates XML (STC-X) system. The version of STC-X
+finally adopted by the IVOA differs in several significant respects from
+V1.20, and so the STC-X support currently provided by AST is mainly of
+historical interest. Note, AST also supports the alternative ``STC-S''
+linear string description of the STC model (see \secref{ss:stcschans}).
+
+STC-X V1.20 is documented at
+\url{http://www.ivoa.net/Documents/WD/STC/STC-20050225.html}, and the current
+version is documented at
+\url{http://www.ivoa.net/Documents/latest/STC-X.html}.
+
+When an STC-X document is read using an XmlChan, the read operation
+produces an AST \htmlref{Object}{Object} of the \htmlref{Stc}{Stc} class, which is itself a subclass of
+\htmlref{Region}{Region}. Specifically, each such Object will be an instance of
+\htmlref{StcSearchLocation}{StcSearchLocation}, \htmlref{StcResourceProfile}{StcResourceProfile}, \htmlref{StcCatalogEntryLocation}{StcCatalogEntryLocation} or
+\htmlref{StcObsDataLocation}{StcObsDataLocation}. See the description of the XmlChan class and the
+\htmlref{XmlFormat}{XmlFormat} attribute for further details.
+
+\cleardoublepage
+\section{\label{ss:stcschans}Reading and writing STC-S descriptions (StcsChans)}
+
+The \htmlref{StcsChan}{StcsChan} class provides facilities for reading and writing
+IVOA ``STC-S'' descriptions. STC-S (see
+\url{http://www.ivoa.net/Documents/latest/STC-S.html}) is a linear string
+syntax that allows simple specification of the STC metadata describing a
+region in an astronomical coordinate system. AST supports a
+subset of the STC-S specification, allowing an STC-S description of a
+region within an AST-supported astronomical coordinate system to be converted
+into an equivalent AST \htmlref{Region}{Region} object, and vice-versa. For further
+details, see the full description of the StcsChan class in
+\appref{ss:classdescriptions}.
+
+
+\cleardoublepage
+\section{\label{ss:intramaps}Creating Your Own Private Mappings (IntraMaps)}
+
+\subsection{The Need for Extensibility}
+
+However many \htmlref{Mapping}{Mapping} classes are provided by AST, sooner or later you
+will want to transform coordinates in some way that has not been
+foreseen. You might want to plot a graph in some novel curvilinear
+coordinate system (perhaps you already have a WCS system in your
+software and just want to use AST for its graphical capabilities).
+Alternatively, you might need to calibrate a complex dataset (like an
+objective prism plate) where each position must be converted to world
+coordinates with reference to calibration data under the control of an
+elaborate algorithm.
+
+In such cases, it is clear that the basic pre-formed components
+provided by AST for building Mappings are just not enough. What you
+need is access to a programming language. However, if you write your
+own software to transform coordinate values, then it must be made
+available in the form of an AST class (from which you can create
+Objects) before it can be used in conjunction with other AST
+facilities.
+
+At this point you might consider writing your own AST class, but this
+is not recommended. Not only would the internal conventions used by
+AST take some time to master, but you might also find yourself having
+to change your software whenever a new version of AST was
+released. Fortunately, there is a much easier route provided by the
+\htmlref{IntraMap}{IntraMap} class.
+
+\subsection{The IntraMap Model}
+
+To allow you to write your own Mappings, AST provides a special kind
+of \htmlref{Mapping}{Mapping} called an \htmlref{IntraMap}{IntraMap}. An IntraMap is a sort of ``wrapper''
+for a coordinate transformation function written in C. You write this
+function yourself and then register it with AST. This, in effect,
+creates a new class from which you can create Mappings
+(\emph{i.e.}\ IntraMaps) which will transform coordinates in whatever
+way your transformation function specifies.
+
+Because IntraMaps are Mappings, they may be used in the same way as
+any other Mapping. For instance, they may be combined in series or
+parallel with other Mappings using a \htmlref{CmpMap}{CmpMap} (\secref{ss:cmpmaps}),
+they may be inverted (\secref{ss:invertingmappings}), you may enquire
+about their attributes (\secref{ss:gettingattributes}), they may be
+inserted into FrameSets (\secref{ss:framesets}), \emph{etc.} They do,
+however, have some important limitations of which you should be aware
+before we go on to consider how to create them.
+
+\subsection{\label{ss:intramaplimitations}Limitations of IntraMaps}
+
+By now, you might be wondering why any other kind of \htmlref{Mapping}{Mapping} is
+required at all. After all, why not simply write your own coordinate
+transformation functions in C, wrap them up in IntraMaps and do away
+with all the other Mapping classes in AST?
+
+The reason is not too hard to find. Any transformation function you
+write is created solely by you, so it is a private extension which
+does not form a permanent part of AST. If you use it to calibrate some
+data and then pass that data to someone else, who has only the
+standard version of AST, then they will not be able to interpret it.
+
+Thus, while an \htmlref{IntraMap}{IntraMap} is fine for use by you and your collaborators
+(who we assume have access to the same transformation functions), it
+does not address the need for universal data exchange like other AST
+Mappings do. This is where the ``Intra'' in the class name
+``IntraMap'' comes from, implying private or internal usage.
+
+For this reason, it is unwise to store IntraMaps in datasets, unless
+they will be used solely for communication between collaborating items
+of software which share conventions about their use.  A private
+database describing coordinate systems on a graphics device might be
+an example where IntraMaps would be suitable, because the data would
+probably never be accessed by anyone else's software. Restricting
+IntraMap usage to within a single program (\emph{i.e.} never writing
+it out) is, of course, completely safe.
+
+If, by accident, an IntraMap should happen to escape as part of a
+dataset, then the unsuspecting recipient is likely to receive an error
+message when they attempt to read the data. However, AST will
+associate details of the IntraMap's transformation function and its
+author (if provided) with the data, so that the recipient can make an
+intelligent enquiry to obtain the necessary software if this proves
+essential.
+
+\subsection{\label{ss:transformationfunctions}Writing a Transformation Function}
+
+The first stage in creating an \htmlref{IntraMap}{IntraMap} is to write the coordinate
+transformation function. This should have a calling interface like the
+\htmlref{astTranP}{astTranP} function provided by AST (\emph{q.v.}). Here is a simple
+example of a suitable transformation function which transforms
+coordinates by squaring them:
+\xlabel{SqrTran}
+
+\small
+\begin{terminalv}
+#include "ast.h"
+#include <math.h>
+
+void SqrTran( AstMapping *this, int npoint, int ncoord_in,
+              const double *ptr_in[], int forward, int ncoord_out,
+              double *ptr_out[] ) {
+   int point, coord;
+   double x;
+
+/* Forward transformation. */
+   if ( forward ) {
+      for ( point = 0; point < npoint; point++ ) {
+         for ( coord = 0; coord < ncoord_in; coord++ ) {
+            x = ptr_in[ coord ][ point ];
+            ptr_out[ coord ][ point ] = ( x == AST__BAD ) ? AST__BAD : x * x;
+         }
+      }
+
+/* Inverse transformation. */
+   } else {
+      for ( point = 0; point < npoint; point++ ) {
+         for ( coord = 0; coord < ncoord_in; coord++ ) {
+            x = ptr_in[ coord ][ point ];
+            ptr_out[ coord ][ point ] =
+               ( x < 0.0 || x == AST__BAD ) ? AST__BAD : sqrt( x );
+         }
+      }
+   }
+}
+\end{terminalv}
+\normalsize
+
+As you can see, the function comes in two halves which implement the
+forward and inverse coordinate transformations. The number of points
+to be transformed (``npoint'') and the numbers of input and output
+coordinates per point (``ncoord\_in'' and ``ncoord\_out''---in this
+case both are assumed equal) are passed to the function. A pair of
+loops then accesses all the coordinate values.  Note that it is
+legitimate to omit one or other of the forward/inverse transformations
+and simply not to implement it, if it will not be required. It is also
+permissible to require that the numbers of input and output
+coordinates be fixed (\emph{e.g.}\ at 2), or to write the function so
+that it can handle arbitrary dimensionality, as here.
+
+Before using an incoming coordinate, the function must first check
+that it is not set to the value AST\_\_BAD, which indicates missing
+data (\secref{ss:badcoordinates}). If it is, the same value is also
+assigned to any affected output coordinates. The value AST\_\_BAD is
+also generated if any coordinates cannot be transformed. In this
+example, this can happen with the inverse transformation if negative
+values are encountered, so that the square root cannot be taken.
+
+There are very few restrictions on what a coordinate transformation
+function may do. For example, it may freely perform I/O to access any
+external data needed, it may invoke other AST facilities (but beware
+of unwanted recursion), \emph{etc.} Typically, you may also want to
+pass information to it \emph{via}\ global variables. Remember,
+however, that whatever facilities the transformation function requires
+must be available in every program which uses it.
+
+Generally, it is not a good idea to retain context information within
+a transformation function. That is, it should transform each set of
+coordinates as a single point and retain no memory of the points it
+has transformed before. This is in order to conform with the AST model
+of a \htmlref{Mapping}{Mapping}.
+
+If an error occurs within a transformation function, it should use the
+\htmlref{astSetStatus}{astSetStatus} function (\secref{ss:errordetection}) to set the AST
+status to an error value before returning. This will alert AST to the
+error, causing it to abort the current operation. The error value
+AST\_\_ITFER is available for this purpose, but other values may also
+be used (\emph{e.g.}\ if you wish to distinguish different types of
+error).
+
+\subsection{\label{ss:registeringintramaps}Registering a Transformation Function}
+
+Having written your coordinate transformation function, the next step
+is to register it with AST. Registration is performed using
+\htmlref{astIntraReg}{astIntraReg}, as follows:
+
+\small
+\begin{terminalv}
+void SqrTran( AstMapping *, int, int, const double *[], int, int, double *[] );
+
+const char *author, *contact, *purpose;
+
+...
+
+purpose = "Square each coordinate value";
+author =  "R.F. Warren-Smith & D.S. Berry";
+contact = "http://www.starlink.ac.uk/cgi-bin/htxserver/sun211.htx/?xref_SqrTran";
+
+astIntraReg( "SqrTran", 2, 2, SqrTran, 0, purpose, author, contact );
+\end{terminalv}
+\normalsize
+
+Note that you should also provide a function prototype to describe the
+transformation function (the implementation of the function itself
+would suffice, of course).
+
+The first argument to astIntraReg is a name by which the
+transformation function will be known. This will be used when we come
+to create an \htmlref{IntraMap}{IntraMap} and is case sensitive. We recommend that you use
+the actual function name here and make this sufficiently unusual that
+it is unlikely to clash with any other functions in most people's
+software.
+
+The next two arguments specify the number of input and output
+coordinates which the transformation function will handle. These
+correspond with the \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes of the IntraMap we will
+create. Here, we have set them both to 2, which means that we will
+only be able to create IntraMaps with 2 input and 2 output coordinates
+(despite the fact that the transformation function can actually handle
+other dimensionalities). We will see later
+(\secref{ss:variableintramapcoordinates}) how to remove this
+restriction.
+
+The fourth argument should contain a set of flags which describe the
+transformation function in a little more detail. We will return to
+this shortly (\secref{ss:restrictedintramaps} \&
+\secref{ss:simplifyingintramaps}). For now, we supply a value of zero.
+
+The remaining arguments are character strings which document the
+transformation function, mainly for the benefit of anyone who is
+unfortunate enough to encounter a reference to it in their data which
+they cannot interpret. As explained above
+(\secref{ss:intramaplimitations}), you should try and avoid this, but
+accidents will happen, so you should always provide strings containing
+the following:
+
+\begin{enumerate}
+\item A short description of what the transformation function is for.
+\item The name of the author.
+\item Contact details, such as an e-mail or WWW address.
+\end{enumerate}
+
+The idea is that anyone finding an IntraMap in their data, but lacking
+the necessary transformation function, should be able to contact the
+author and make a sensible enquiry in order to obtain it. If you
+expect many enquiries, you may like to set up a World Wide Web page
+and use that instead (in the example above, we use the WWW address of
+the relevant part of this document).
+
+\subsection{Creating an IntraMap}
+
+Once a transformation function has been registered, creating an
+\htmlref{IntraMap}{IntraMap} from it is simple:
+
+\small
+\begin{terminalv}
+AstIntraMap *intramap;
+
+...
+
+intramap = astIntraMap( "SqrTran", 2, 2, "" );
+\end{terminalv}
+\normalsize
+
+We simply use the \htmlref{astIntraMap}{astIntraMap} constructor function and pass it the
+name of the transformation function to use. This name is the same
+(case sensitive) one that we associated with the function when we
+registered it using \htmlref{astIntraReg}{astIntraReg} (\secref{ss:registeringintramaps}).
+
+You can, of course, register any number of transformation functions
+and select which one to use whenever you create an IntraMap. You can
+also create any number of independent IntraMaps using each
+transformation function. In this sense, each transformation function
+you register effectively creates a new ``sub-class'' of IntraMap, from
+which you can create Objects just like any other class. However, an
+error will occur if you attempt to use a transformation function that
+has not yet been registered.
+
+The second and third arguments to astIntraMap are the numbers of input
+and output coordinates. These define the \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes for
+the IntraMap that is created and they must match the corresponding
+numbers given when the transformation function was registered.
+
+The final argument is the usual attribute initialisation string. You
+may set attribute values for an IntraMap in exactly the same way as
+for any other \htmlref{Mapping}{Mapping} (\secref{ss:settingattributes}, and also see
+\secref{ss:intraflag}).
+
+\subsection{\label{ss:restrictedintramaps}Restricted Implementations of Transformation Functions}
+
+You may not always want to use both the forward and inverse
+transformations when you create an \htmlref{IntraMap}{IntraMap}, so it is possible to omit
+either from the underlying coordinate transformation
+function. Consider the following, for example:
+
+\small
+\begin{terminalv}
+void Poly3Tran( AstMapping *this, int npoint, int ncoord_in,
+                const double *ptr_in[], int forward, int ncoord_out,
+                double *ptr_out[] ) {
+   double x;
+   int point;
+
+/* Forward transformation. */
+   for ( point = 0; point < npoint; point++ ) {
+      x = ptr_in[ 0 ][ point ];
+      ptr_out[ 0 ][ point ] = ( x == AST__BAD ) ? AST__BAD :
+         6.18 + x * ( 0.12 + x * ( -0.003 + x * 0.0000101 ) );
+   }
+}
+\end{terminalv}
+\normalsize
+
+This implements a 1-dimensional cubic polynomial transformation. Since
+this is somewhat awkward to invert, however, we have only implemented
+the forward transformation.  When registering the function, this is
+indicated via the ``flags'' argument to \htmlref{astIntraReg}{astIntraReg}, as follows:
+
+\small
+\begin{terminalv}
+void Poly3Tran( AstMapping *, int, int, const double *[], int, int, double *[] );
+
+...
+
+astIntraReg( "Poly3Tran", 1, 1, Poly3Tran, AST__NOINV,
+             purpose, author, contact );
+\end{terminalv}
+\normalsize
+
+Here, the fifth argument has been set to the flag value AST\_\_NOINV
+to indicate the lack of an inverse. If the forward transformation were
+absent, we would use AST\_\_NOFOR instead. Flag values for this
+argument may be combined using a bitwise OR if necessary.
+
+\subsection{\label{ss:variableintramapcoordinates}Variable Numbers of Coordinates}
+
+In our earlier examples, we have used a fixed number of input and
+output coordinates when registering a coordinate transformation
+function. It is not necessary to impose this restriction, however, if
+the transformation function can cope with a variable number of
+coordinates (as with the example in
+\secref{ss:transformationfunctions}). We indicate the acceptability of
+a variable number when registering the transformation function by
+supplying the value AST\_\_ANY for the number of input and/or output
+coordinates, as follows:
+
+\small
+\begin{terminalv}
+astIntraReg( "SqrTran", AST__ANY, AST__ANY, SqrTran, 0,
+             purpose, author, contact );
+\end{terminalv}
+\normalsize
+
+The result is that an \htmlref{IntraMap}{IntraMap} may now be created with any number of
+input and output coordinates. For example:
+
+\small
+\begin{terminalv}
+AstIntraMap *intramap1, *intramap2;
+
+...
+
+intramap1 = astIntraMap( "SqrTran", 1, 1, "" );
+intramap2 = astIntraMap( "SqrTran", 3, 3, "Invert=1" );
+\end{terminalv}
+\normalsize
+
+It is possible to fix either the number of input or output coordinates
+(by supplying an explicit number to \htmlref{astIntraReg}{astIntraReg}), but more subtle
+restrictions on the number of coordinates, such as requiring that \htmlref{Nin}{Nin}
+and \htmlref{Nout}{Nout} be equal, are not supported. This means that:
+
+\small
+\begin{terminalv}
+intramap = astIntraMap( "SqrTran", 1, 2, "" );
+\end{terminalv}
+\normalsize
+
+will be accepted without error, although the transformation function
+cannot actually handle such a combination sensibly. If this is
+important, it would be worth adding a check within the transformation
+function itself, so that the error would be detected when it came to
+be used.
+
+\subsection{\label{ss:intraflag}Adapting a Transformation Function to Individual IntraMaps}
+
+In the examples given so far, our coordinate transformation functions
+have not made use of the ``this'' pointer passed to them (which
+identifies the \htmlref{IntraMap}{IntraMap} whose transformation we are implementing). In
+practice, this will often be the case. However, the presence of the
+``this'' pointer allows the transformation function to invoke any
+other AST function on the IntraMap, and this permits enquiries about
+its attributes. The transformation function's behaviour can therefore
+be modified according to any attribute values which are set. This
+turns out to be a useful thing to do, so each IntraMap has a special
+\htmlref{IntraFlag}{IntraFlag} attribute reserved for exactly this purpose.
+
+Consider, for instance, the case where the transformation function has
+access to several alternative sets of internally-stored data which it
+may apply to perform its transformation. Rather than implement many
+different versions of the transformation function, you may switch
+between them by setting a value for the IntraFlag attribute when you
+create an instance of an IntraMap, for example:
+
+\small
+\begin{terminalv}
+intramap1 = astIntraMap( "MyTran", 2, 2, "IntraFlag=A" );
+intramap2 = astIntraMap( "MyTran", 2, 2, "IntraFlag=B" );
+\end{terminalv}
+\normalsize
+
+The transformation function may then enquire the value of the IntraFlag
+attribute (\emph{e.g.}\ using astGetC and passing it the ``this''
+pointer) and use whichever dataset is required for that particular
+IntraMap.
+
+This approach is particularly useful when the number of possible
+transformations is unbounded or not known in advance, in which case
+the IntraFlag attribute may be used to hold numerical values encoded
+as part of a character string (effectively using them as data for the
+IntraMap). It is also superior to the use of a global switch for
+communication (\emph{e.g.}\ setting an index to select the ``current''
+data before using the IntraMap), because it continues to work when
+several IntraMaps are embedded within a more complex compound \htmlref{Mapping}{Mapping},
+when you may have no control over the order in which they are used.
+
+\subsection{\xlabel{MaxTran}\label{ss:simplifyingintramaps}Simplifying IntraMaps}
+
+A notable disadvantage of IntraMaps is that they are ``black boxes''
+as far as AST is concerned. This means that they have limited ability
+to participate in the simplification of compound Mappings performed,
+\emph{e.g.}, by \htmlref{astSimplify}{astSimplify} (\secref{ss:simplifyingcmpmaps}), because
+AST cannot know how they interact with other Mappings. In reality, of
+course, they will often implement such specialised coordinate
+transformations that the simplification possibilities will be rather
+limited anyway.
+
+One important simplification, however, is the ability of a \htmlref{Mapping}{Mapping} to
+cancel with its own inverse to yield a unit Mapping (a \htmlref{UnitMap}{UnitMap}). This
+is important because Mappings are frequently used to relate a dataset
+to some external standard (a celestial coordinate system, for
+example). When inter-relating two similar datasets calibrated using
+the same standard, part of the Mapping often cancels, because it is
+applied first in one direction and then the other, effectively
+eliminating the reference to the standard. This is often a useful
+simplification and can lead to greater efficiency.
+
+Many transformations have this property of cancelling with their own
+inverse, but not necessarily all. Consider the following
+transformation function, for example:
+
+\small
+\begin{terminalv}
+void MaxTran( AstMapping *this, int npoint, int ncoord_in,
+              const double *ptr_in[], int forward, int ncoord_out,
+              double *ptr_out[] ) {
+   double hi, x;
+   int coord, point;
+
+/* Forward transformation. */
+   if ( forward ) {
+      for ( point = 0; point < npoint; point++ ) {
+         hi = AST__BAD;
+         for ( coord = 0; coord < ncoord_in; coord++ ) {
+            x = ptr_in[ coord ][ point ];
+            if ( x != AST__BAD ) {
+               if ( x > hi || hi == AST__BAD ) hi = x;
+            }
+         }
+         ptr_out[ 0 ][ point ] = hi;
+      }
+
+/* Inverse transformation. */
+   } else {
+      for ( coord = 0; coord < ncoord_out; coord++ ) {
+         for ( point = 0; point < npoint; point++ ) {
+            ptr_out[ coord ][ point ] = ptr_in[ 0 ][ point ];
+         }
+      }
+   }
+}
+\end{terminalv}
+\normalsize
+
+This function takes any number of input coordinates and returns a
+single output coordinate which is the maximum value of the input
+coordinates. Its inverse (actually a ``pseudo-inverse'') sets all the
+input coordinates to the value of the output
+coordinate.\footnote{Remember that ``ptr\_in'' identifies the original
+``output'' coordinates when applying the inverse transformation and
+``ptr\_out'' identifies the original ``input'' coordinates.}
+
+If this function is applied in the forward direction and then in the
+inverse direction, it does \textbf{not} in general restore the original
+coordinate values. However, if applied in the inverse direction and
+then the forward direction, it does. Hence, replacing the sequence of
+operations with an equivalent UnitMap is possible in the latter case,
+but not in the former.
+
+To distinguish these possibilities, two flag values are provided for
+use with \htmlref{astIntraReg}{astIntraReg} to indicate what simplification (if any) is
+possible. For example, to register the above transformation function,
+we might use:
+
+\small
+\begin{terminalv}
+void MaxTran( AstMapping *, int, int, const double *[], int, int, double *[] );
+
+...
+
+astIntraReg( "MaxTran", AST__ANY, 1, MaxTran, AST__SIMPIF,
+             purpose, author, contact );
+\end{terminalv}
+\normalsize
+
+Here, the flag value AST\_\_SIMPIF supplied for the fifth argument
+indicates that simplification is possible if the transformation is
+applied in the inverse direction followed by the forward direction. To
+indicate the complementary case, the flag AST\_\_SIMPFI would be used
+instead. If both simplifications are possible (as with the SqrTran
+function in \secref{ss:transformationfunctions}), then we would use
+the bitwise OR of both values.
+
+In practice, some judgement is usually necessary when deciding whether
+to allow simplification. For example, seen in one light our SqrTran
+function (\secref{ss:transformationfunctions}) does not cancel with
+its own inverse, because squaring a coordinate value and then taking
+its square root can change the original value, if this was
+negative. Therefore, replacing this combination with a UnitMap will
+change the behaviour of a compound Mapping and should not be
+allowed. Seen in another light, however, where the coordinates being
+processed are intrinsically all positive, it is a permissible and
+probably useful simplification.
+
+If such distinctions are ever important in practice, it is simple to
+register the same transformation function twice with different flag
+values (use a separate name for each) and then use whichever is
+appropriate when creating an \htmlref{IntraMap}{IntraMap}.
+
+\subsection{\label{ss:readingandwritingintramaps}Writing and Reading IntraMaps}
+
+It is most important to realise that when you write an \htmlref{IntraMap}{IntraMap} to a
+\htmlref{Channel}{Channel} (\secref{ss:writingtoachannel}), the transformation function
+which it uses is not stored with it. To do so is impossible, because
+the function has been compiled and loaded into memory ready for
+execution before AST gets to see it. However, AST does store the name
+associated with the transformation function and various details about
+the IntraMap itself.
+
+
+This means that any program attempting to read the IntraMap
+(\secref{ss:readingfromachannel}) cannot make use of it unless it also
+has independent access to the original transformation function. If it
+does not have access to this function, an error will occur at the
+point where the IntraMap is read and the associated error message will
+direct the user to the author of the transformation function for more
+information.
+
+However, if the necessary transformation function is available, and
+has been registered before the read operation takes place, then AST is
+able to re-create the original IntraMap and will do so. Registration
+of the transformation function must, of course, use the same name
+(and, in fact, be identical in most particulars) as was used in the
+original program which wrote the data.
+
+This means that a set of co-operating programs which all have access
+to the same set of transformation functions and register them in
+identical fashion (see \secref{ss:intramaplibrary} for how this can
+best be achieved) can freely exchange data that contain IntraMaps. The
+need to avoid exporting such data to unsuspecting third parties
+(\secref{ss:intramaplimitations}) must, however, be re-iterated.
+
+\subsection{\label{ss:intramaplibrary}Managing Transformation Functions in Libraries}
+
+If you are developing a large suite of data reduction software, you
+may have a need to use IntraMaps at various points within it. Very
+probably this will occur in unrelated modules which are compiled
+separately and then stored in a library. Since the transformation
+functions required must be registered before they can be used, this
+makes it difficult to decide where to perform this registration,
+especially since any particular data reduction program may use an
+arbitrary subset of the modules in your library.
+
+To assist with this problem, AST allows you to perform the same
+registration of a transformation function any number of times, so long
+as it is performed using an identical invocation of \htmlref{astIntraReg}{astIntraReg} on
+each occasion (\emph{i.e.}\ all of its arguments must be
+identical). This means you do not have to keep track of whether a
+particular function has already been registered but could, in fact,
+register it on each occasion immediately before it is required
+(wherever that may be). In order that all registrations are identical,
+however, it is recommended that you group them all together into a
+single function, perhaps as follows:
+
+\small
+\begin{terminalv}
+void MyTrans( void ) {
+
+   ...
+
+   astIntraReg( "MaxTran", AST__ANY, 1, MaxTran, AST__SIMPIF,
+                purpose, author, contact );
+
+   ...
+
+   astIntraReg( "Poly3Tran", 1, 1, Poly3Tran, AST__NOINV,
+                purpose, author, contact );
+
+   ...
+
+   astIntraReg( "SqrTran", 2, 2, SqrTran, 0,
+                purpose, author, contact );
+}
+\end{terminalv}
+\normalsize
+
+You can then simply invoke this function wherever necessary. It is, in
+fact, particularly important to register all relevant transformation
+functions in this way before you attempt to read an \htmlref{Object}{Object} that might
+be (or contain) an \htmlref{IntraMap}{IntraMap}
+(\secref{ss:readingandwritingintramaps}). This is because you may not
+know in advance which of these transformation functions the IntraMap
+will use, so they must all be available in order to avoid an error.
+
+\cleardoublepage
+\section{\label{ss:plots}Producing Graphical Output (Plots)}
+
+Graphical output from AST is performed though an \htmlref{Object}{Object} called a \htmlref{Plot}{Plot},
+which is a specialised form of \htmlref{FrameSet}{FrameSet}. A Plot does not represent the
+graphical content itself, but is a route through which plotting
+operations, such as drawing lines and curves, are conveyed on to a
+plotting surface to appear as visible graphics.
+
+\subsection{The Plot Model}
+
+When a \htmlref{Plot}{Plot} is created, it is initialised by providing a \htmlref{FrameSet}{FrameSet} whose
+base \htmlref{Frame}{Frame} (as specified by its \htmlref{Base}{Base} attribute) is mapped linearly or
+logarithmically (as specified by the LogPlot attribues) on to a
+\emph{plotting area}. This is a rectangular region in the graphical
+coordinate space of the underlying graphics system and becomes the new
+base Frame of the Plot. In effect, the Plot becomes attached to the
+plotting surface, in rather the same way that a basic FrameSet might be
+attached to (say) an image.
+
+The current Frame of the Plot (derived from the current Frame of the
+FrameSet supplied) is used to represent a \emph{physical coordinate
+system}. This is the system in which plotting operations are
+performed by your program.  Every plotting operation is then
+transformed through the \htmlref{Mapping}{Mapping} which inter-relates the Plot's current
+and base Frames in order to appear on the plotting surface.
+
+An example may help here. Suppose we start with a FrameSet whose base
+Frame describes the pixel coordinates of an image and whose current
+Frame describes a celestial (equatorial) coordinate system. Let us
+assume that these two Frames are inter-related by a Mapping within the
+FrameSet which represents a particular sky projection.
+
+When a Plot is created from this FrameSet, we specify how the pixel
+coordinates (the base Frame) maps on to the plotting surface. This
+simply corresponds to telling the Plot where we have previously
+plotted the image data. If we now use the Plot to plot a line with
+latitude zero in our physical coordinate system, as given by the
+current Frame, this line would appear as a curve (the equator) on the
+plotting surface, correctly registered with the image.
+
+There are a number of plotting functions provided, which all work in a
+similar way. Plotting operations are transformed through the Mapping
+which the Plot represents before they appear on the plotting
+surface.\footnote{Like any FrameSet, a Plot can be used as a
+Mapping. In this case it is the inverse transformation which is used
+when plotting (\emph{i.e.}\ that which transforms between the current
+and base Frames).}  It is possible to draw symbols, lines, axes,
+entire grids and more in this way.
+
+%\subsection{TBW---Creating a Plot}
+
+\subsection{Plotting Symbols}
+
+The simplest form of plotting is to draw symbols (termed
+\emph{markers}) at a set of points. This is performed by \htmlref{astMark}{astMark},
+which is supplied with a set of physical coordinates at which to place
+the markers:
+
+\small
+\begin{terminalv}
+#include "ast.h"
+#define NCOORD 2
+#define NMARK 10
+double in[ NCOORD ][ NMARK ];
+int type;
+
+...
+
+astMark( plot, NMARK, NCOORD, NMARK, in, type );
+\end{terminalv}
+\normalsize
+
+Here, NMARK specifies how many markers to plot and NCOORD specifies
+how many coordinates are being supplied for each
+point.\footnote{Remember, the physical coordinate space need not
+necessarily be 2-dimensional, even if the plotting surface is.} The
+array ``in'' supplies the coordinates and the integer ``type''
+specifies which type of marker to plot.
+
+\subsection{\label{ss:plottinggeodesics}Plotting Geodesic Curves}
+
+There is no \htmlref{Plot}{Plot} routine to draw a straight line, because any straight
+line in physical coordinates can potentially turn into a curve in
+graphical coordinates. We therefore start by considering how to draw
+geodesic curves.  These are curves which trace the path of shortest
+distance between two points in physical coordinates
+ and are the basic drawing element in a Plot.
+
+In many instances, the geodesic will, in fact, be a straight line, but
+this depends on the Plot's current \htmlref{Frame}{Frame}. If this represents a
+celestial coordinate system, for instance, it will be a great circle
+(corresponding with the behaviour of the \htmlref{astDistance}{astDistance} function which
+defines the metric of the physical coordinate space).  The geodesic
+will, of course, be transformed into graphics coordinates before being
+plotted. A geodesic curve is plotted using \htmlref{astCurve}{astCurve} as follows:
+
+\small
+\begin{terminalv}
+double start[ NCOORD ], finish[ NCOORD ];
+
+...
+
+astCurve( plot, start, finish );
+\end{terminalv}
+\normalsize
+
+Here, ``start'' and ``finish'' are arrays containing the starting and
+finishing coordinates of the curve. The \htmlref{astOffset}{astOffset} and astDistance
+functions can often be useful for computing these
+(\secref{ss:distanceandoffset}).
+
+If you need to draw a series of curves end-to-end (when drawing a
+contour line, for example), then a more efficient alternative is to
+use \htmlref{astPolyCurve}{astPolyCurve}. This has the same effect as a sequence of
+invocations of astCurve, but allows you to supply a whole set of
+points at one time. astPolyCurve then joins them, in sequence, using
+geodesic curves:
+
+\small
+\begin{terminalv}
+#define NPOINT 100
+double coords[ NCOORD ][ NPOINT ];
+
+...
+
+astPolyCurve( plot, NPOINT, NCOORD, NPOINT, coords );
+\end{terminalv}
+\normalsize
+
+Here, NPOINT specifies how many points are to be joined and NCOORD
+specifies how many coordinates are being supplied for each point.  The
+array ``coords'' supplies the coordinates of the points in the Plot's
+physical coordinate system.
+
+\subsection{Plotting Curves Parallel to Axes}
+
+As there is no \htmlref{Plot}{Plot} function to draw a ``straight line'', drawing axes
+and grid lines to represent coordinate systems requires a slightly
+different approach. The problem is that for some coordinate systems,
+these grid lines will not be geodesics, so \htmlref{astCurve}{astCurve} and \htmlref{astPolyCurve}{astPolyCurve}
+(\secref{ss:plottinggeodesics}) cannot easily be used (you would have
+to resort to approximating grid lines by many small elements). Lines
+of constant celestial latitude provide an example of this, with the
+exception of the equator which is a geodesic.
+
+The \htmlref{astGridLine}{astGridLine} function allows these curves to be drawn, as follows:
+
+\small
+\begin{terminalv}
+int axis;
+double length;
+
+...
+
+astGridLine( plot, axis, start, length );
+\end{terminalv}
+\normalsize
+
+Here, ``axis'' specifies which physical coordinate axis we wish to
+draw parallel to. The ``start'' array contains the coordinates of the
+start of the curve and ``length'' specifies the distance to draw along
+the axis in physical coordinate space.
+
+\subsection{\label{ss:plottinggeneralizedcurves}Plotting Generalized Curves}
+We have seen how geodesic curves and grid lines can be drawn. The \htmlref{Plot}{Plot}
+class includes another method,
+\htmlref{astGenCurve}{astGenCurve},
+which allows curves of \emph{any} form to be drawn. The caller supplies a
+\htmlref{Mapping}{Mapping} which maps offset along the curve\footnote{normalized so that the
+start of the curve is at offset 0.0 and the end of the curve is at offset
+1.0 - offset need not be linearly related to distance.} into the
+corresponding position in the current \htmlref{Frame}{Frame} of the Plot.
+astGenCurve,
+then takes care of Mapping these positions into graphics coordinates. The
+choice of exactly which positions along the curve are to be used to
+define the curve is also made by
+astGenCurve,
+using an adaptive algorithm which concentrates points around areas where
+the curve is bending sharply or is discontinuous in graphics coordinates.
+
+The \htmlref{IntraMap}{IntraMap} class may be of particular use in this context since it allows
+you to code your own Mappings to do any transformation you choose.
+
+
+\subsection{\label{ss:clipping}Clipping}
+
+Like many graphics systems, a \htmlref{Plot}{Plot} allows you to \emph{clip} the graphics
+you produce. This means that plotting is restricted to certain regions
+of the plotting surface so that anything drawn outside these regions
+will not appear.  All Plots automatically clip at the edges of the
+plotting area specified when the Plot is created. This means that
+graphics are ultimately restricted to the rectangular region of
+plotting space to which you have attached the Plot.
+
+In addition to this, you may also specify lower and upper limits on
+each axis at which clipping should occur. This permits you to further
+restrict the plotting region. Moreover, you may attach these clipping
+limits to \emph{any} of the Frames in the Plot. This allows you to
+place restrictions on where plotting will take place in either the
+physical coordinate system, the graphical coordinate system, or in any
+other coordinate system which is described by a \htmlref{Frame}{Frame} within the Plot.
+
+For example, you could plot using equatorial coordinates and set up
+clipping limits in galactic coordinates. In general, you could set up
+arbitrary clipping regions by adding a new Frame to a Plot (in which
+clipping will be performed) and inter-relating this to the other
+Frames in a suitable way.
+
+Clipping limits are defined using the \htmlref{astClip}{astClip} function, as follows:
+
+\small
+\begin{terminalv}
+#define NAXES 2
+int iframe;
+double lbnd[ NAXES ], ubnd[ NAXES ];
+
+...
+astClip( plot, iframe, lbnd, ubnd);
+\end{terminalv}
+\normalsize
+
+Here, the ``iframe'' value gives the index of the Frame within the
+Plot to which clipping is to be applied, while ``lbnd'' and ``ubnd''
+give the limits on each axis of the selected Frame (NAXES is the
+number of axes in this Frame).
+
+You can remove clipping by giving a value of AST\_\_NOFRAME for ``iframe''.
+
+\subsection{Using a Plot as a Mapping}
+
+All Plots are also Mappings (just like the FrameSets from which they
+are derived), so can be used to transform coordinates.
+
+Like FrameSets, the forward transformation of a \htmlref{Plot}{Plot} will convert
+coordinates between the base and current Frames (\emph{i.e.}\ between
+graphical and physical coordinates). This would be useful if you were
+(say) reading a cursor position in graphical coordinates and needed to
+convert this into physical coordinates for display.
+
+Conversely, a Plot's inverse transformation converts between its
+current and base Frames (\emph{i.e.}\ from physical coordinates to
+graphical coordinates). This transformation is applied automatically
+whenever plotting operations are carried out by AST functions. It may
+also be useful to apply it directly, however, if you wish to perform
+additional plotting operations (\emph{e.g.}\ those provided by the
+native graphics system) at positions specified in physical
+coordinates.
+
+There is, however, one important difference between using a \htmlref{FrameSet}{FrameSet}
+and a Plot to transform coordinates, and this is that clipping may be
+applied by a Plot (if it has been enabled using
+\htmlref{astClip}{astClip}---\secref{ss:clipping}). Any point which lies within the
+clipped region of a Plot will, when transformed, yield coordinates
+with the value AST\_\_BAD. If you wish to avoid this clipping, you
+should extract the relevant \htmlref{Mapping}{Mapping} from the Plot (using
+\htmlref{astGetMapping}{astGetMapping}) and use this, instead of the Plot, to transform the
+coordinates.
+
+\subsection{Using a Plot as a Frame}
+
+Every \htmlref{Plot}{Plot} is also a \htmlref{Frame}{Frame}, so can be used to obtain the values of
+Frame attributes such as a \htmlref{Title}{Title}, axis Labels, axis Units,
+\emph{etc.}, which are typically used when displaying data and/or
+coordinates. These attributes are, as for any \htmlref{FrameSet}{FrameSet}, derived from
+the current Frame of the Plot (\secref{ss:framesetasframe}). They are
+also used automatically when using the Plot to plot coordinate axes
+and coordinate grids (\emph{e.g.}\ for labelling
+them---\secref{ss:plottingagrid}).
+
+Because the current Frame of a Plot represents physical coordinates,
+any Frame operation applied to the Plot will effectively be working in
+this coordinate system. For example, the \htmlref{astDistance}{astDistance} and \htmlref{astOffset}{astOffset}
+functions will compute distances and offsets in physical coordinate
+space, while \htmlref{astFormat}{astFormat} and \htmlref{astNorm}{astNorm} will format physical coordinates in
+an appropriate way for display.
+
+\subsection{\label{ss:validphysicalcoordinates}Regions of Valid Physical Coordinates}
+
+When points in physical coordinate space are transformed by a \htmlref{Plot}{Plot}
+into graphics coordinates for plotting, they may not always yield
+valid coordinates, irrespective of any clipping being applied
+(\secref{ss:clipping}). To indicate this, the resulting coordinate
+values will be set to the value AST\_\_BAD
+(\secref{ss:badcoordinates}).
+
+There are a number of reasons why this may occur, but typically it
+will be because physical coordinates only map on to a subset of the
+graphics coordinate space. This situation is commonly encountered with
+all-sky projections where, typically, the celestial sphere appears,
+when plotted, as a distorted shape (\emph{e.g.}\ an ellipse) which
+does not entirely fill the graphics space. In some cases, there may
+even be multiple regions of valid and invalid physical coordinates.
+
+When plotting is performed \emph{via} a Plot, graphical output will
+only appear in the regions of valid physical coordinates. Nothing will
+appear where invalid coordinates occur. Such output is effectively
+clipped. If you wish to plot in these areas, you must change
+coordinate system and use, say, graphical coordinates to address the
+plotting surface directly.
+
+\subsection{Plotting Borders}
+
+The \htmlref{astBorder}{astBorder} function is provided to draw a (line) border around your
+graphical output. With most graphics systems, this would simply be a
+rectangular box around the plotting area. With a \htmlref{Plot}{Plot}, however, this
+boundary follows the edge of each region containing valid, unclipped
+physical coordinates (\secref{ss:validphysicalcoordinates}).
+
+This means, for example, that if you were plotting an all-sky
+projection, this boundary would outline the perimeter of the celestial
+sphere when projected on to your plotting surface. Of course, if there
+is no clipping and all physical coordinates are valid, then you will
+get the traditional rectangular box. astBorder requires only
+a pointer to the Plot:
+
+\small
+\begin{terminalv}
+int holes;
+
+...
+
+holes = astBorder( plot );
+\end{terminalv}
+\normalsize
+
+It returns a boolean (integer) value to indicate if any invalid or
+clipped physical coordinates were found within the plotting area. If
+they were, it will draw around the valid unclipped regions and return
+a value of one.  Otherwise, it will draw a simple rectangular border
+and return zero.
+
+\subsection{Plotting Text}
+
+Using a \htmlref{Plot}{Plot} to draw text involves supplying a string of text to be
+displayed and a position in physical coordinates where the text is to
+appear. The position is transformed into graphical coordinates to
+determine where the text should appear on the plotting surface. You
+must also provide a 2-element ``up'' vector which gives the upward
+direction of the text in graphical coordinates. This allows text to be
+drawn at any angle.
+
+Plotting is performed by \htmlref{astText}{astText}, for example:
+
+\small
+\begin{terminalv}
+char text[ 21 ];
+double pos[ NCOORD ];
+float up[ 2 ] = { 0.0f, 1.0f };
+
+...
+
+astText( plot, text, pos, up, "TL" );
+\end{terminalv}
+\normalsize
+
+Here, ``text'' contains the string to be drawn, ``pos'' is an array of
+physical coordinates and ``up'' specifies the upward vector. In this
+case, the text will be drawn horizontally. The final argument
+specifies the text justification, here indicating that the top left
+corner of the text should appear at the position given.
+
+Further control over the appearance of the text is possible by setting
+values for various Plot attributes, for example Colour, Font and Size.
+Sub-strings within the displayed text can be given different appearances,
+or turned into super-scripts or sub-scripts, by the inclusion of escape
+sequences (see section~\secref{ss:escapes}) within the supplied text string.
+
+\subsection{\label{ss:plottingagrid}Plotting a Grid}
+
+The most comprehensive plotting function available is \htmlref{astGrid}{astGrid}, which
+can be used to draw labelled coordinate axes and, optionally, to
+overlay coordinate grids on the plotting area
+(Figure~\ref{fig:gridplot}). The routine is straightforward to use,
+simply requiring a pointer to the \htmlref{Plot}{Plot}:
+
+\small
+\begin{terminalv}
+astGrid( plot );
+\end{terminalv}
+\normalsize
+
+It will draw both linear and curvilinear axes and grids, as required
+by the particular Plot. The appearance of the output can be modified
+in a wide variety of ways by setting various Plot attributes.
+The Label attributes of the current \htmlref{Frame}{Frame} are displayed as the axis
+labels in the grid, and the \htmlref{Title}{Title} attribute as the plot title. Sub-strings
+within these strings can be given different appearances, or turned into
+super-scripts or sub-scripts, by the inclusion of escape sequences (see
+section~\secref{ss:escapes}) within the Label attributes.
+
+\subsection{\label{ss:escapes}Controlling the Appearance of Sub-strings}
+Normally, each string of characters displayed using a \htmlref{Plot}{Plot} will be
+plotted so that all characters in the string have the same font size,
+colour, \emph{etc.}, specified by the appropriate attributes of the
+Plot. However, it is possible to include \emph{escape sequences} within
+the text to modify the appearance of sub-strings. \htmlref{Escape}{Escape} sequences can be
+used to change, colour, font, size, width, to introduce extra horizontal
+space between characters, and to change the base line of characters (thus
+allowing super-scripts and sub-scripts to be created). See the entry for
+the Escape attribute in \appref{ss:attributedescriptions} for details.
+
+As an example, if the character string ``\verb+10\%^50+\%s70+0.5+'' is
+plotted, it will be displayed as ``$10^{0.5}$'' - that is, with a
+super-scripted exponent. The exponent text will be 70\% of the size of
+normal text (as determined by the Size attribute), and its baseline will
+be raised by 50\% of the height of a normal character.
+
+Such escape sequences can be used in the strings assigned to textual
+attributes of the Plot (such as the axis Labels), and may also be
+included in strings plotted using
+\htmlref{astText}{astText}.
+
+The Format attribute for the \htmlref{SkyAxis}{SkyAxis} class includes the ``g'' option
+which will cause escape sequences to be included when formatting
+celestial positions so that super-script characters are used as
+delimiters for the various fields (a super-script ``h'' for hours, ``m''
+for minutes, \emph{etc}).
+
+Note, the facility for interpreting escape sequences is only available if
+the graphics wrapper functions which provide the interface to the
+underlying graphics system support all the functions included in the
+\verb+grf.h+ file as of AST V3.2. Older grf interfaces may need to be
+extended by the addition of new functions before escape sequences can be
+interpretted.
+
+\subsection{\label{ss:logaxes}Producing Logarithmic Axes}
+In certain situations you may wish for one or both of the plotted axes to
+be displayed logarithmically rather than linearly. For instance, you may
+wish to do this when using a \htmlref{Plot}{Plot} to represent a spectrum of, say, flux
+against frequency. In this case, you can cause the frequency axis to be drawn
+logarithmically simply by setting the boolean LogPlot attribute for the
+frequency axis to a non-zero value. This causes several things to happen:
+
+\begin{enumerate}
+
+\item The \htmlref{Mapping}{Mapping} between the base \htmlref{Frame}{Frame} of the Plot (which represents
+the underlying graphics world coordinate system) and the base Frame of
+the \htmlref{FrameSet}{FrameSet} supplied when the Plot was created, is modified. By
+default, this mapping is linear on both axes, but setting LogPlot non-zero
+for an axis causes the Mapping to be modified so that it is logarithmic
+on the specified axis. This is only possible if the displayed section of
+the axis does not include the value zero (otherwise the attempt to set
+a new value for LogPlot is ignored,and it retains its default value of
+zero).
+
+\item The major tick marks drawn as part of the annotated coordinate grid
+are spaced logarithmically rather than linearly. That is, major axis
+values are chosen so that there is a constant ratio between adjacent
+tick mark values. This ratio is constrained to be a power of ten. The
+minor tick marks are drawn at linearly distributed points between the
+adjoining major tick values. Thus if a pair of adjacent major tick values
+are drawn at axis values 10.0 and 100.0, minor ticks will be placed at
+20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0 and 90.0 (note only 8 minor tick
+marks are drawn).
+
+\item If possible, numerical axis labels are shown as powers of ten.
+This depends on the facilities implemented by the graphics wrapper
+functions (see the next section). Extra functions were introduced to this
+set of wrapper functions at AST V3.2 which enable super-scripts and
+sub-scripts to be produced. Some older wrappers may not yet have
+implemented these functiosn and this will result in axis labels being
+drawn in usual scientific or decimal notation.
+
+\end{enumerate}
+
+Whilst the LogPlot attribute can be used to control all three of the above
+facilities, it is possible to control them individually as well. The
+LogTicks and LogLabel attributes control the behaviour specified in items
+2 and 3 above, but the default values for these attributes depend on the
+setting of the LogPlot attribute. This means that setting LogPlot
+non-zero will swicth all three facilites on, so long as zero values have
+not been assigned explicitly to LogTicks or LogLabel.
+
+
+\subsection{\label{ss:choosingagraphicspackage}Choosing a Graphics Package}
+The \htmlref{Plot}{Plot} class itself does not include any code for actually drawing on a
+graphics device. Instead, it requires a set of functions to be provided
+which it uses to draw the required graphics. These include functions
+to draw a straight line, draw a text string, \emph{etc}. You may choose
+to provide functions from your favorite graphics package, or you can even
+write your own! To accomodate variations in the calling interfaces of
+different graphics packages, AST defines a standard interface for these
+routines. If this interface differs from the interface provided by your
+graphics package (which in general it will), then you must write a set of
+\emph{wrapper functions}, which provide the interface expected by AST but
+which then call functions from your graphics package to provide the
+required functionality. AST comes with wrapper functions suitable for
+the PGPLOT graphics package (see \xref{SUN/15}{sun15}{}).
+
+There are two ways of indicating which wrapper functions are to be used by
+the Plot class:
+\begin{enumerate}
+
+\item A file containing C functions with pre-defined names can be written
+and linked with the application using options of the \htmlref{ast\_link}{ast\_link} command.
+(see \secref{ss:howtobuild} and \appref{ss:commanddescriptions}). AST is
+distributed with such a file (called \texttt{grf\_pgplot.c}) which calls PGPLOT
+functions to implement the required functionality. This file can be used
+as a template for writing your own.
+
+\item The
+\htmlref{astGrfSet}{astGrfSet}
+method of the Plot class can be used to ``register''
+wrapper functions at run-time. This allows an application to switch
+between graphics systems if required. Graphics functions registered in
+this way do not need to have the pre-defined names used in the link-time
+method described above.
+
+\end{enumerate}
+
+For details of the interfaces of the wrapper routines, see
+either the \texttt{grf\_pgplot.c} file included in the AST source
+distribution, or the reference documentation for the astGrfSet method.
+
+\cleardoublepage
+\section{Compiling and Linking Software that Uses AST}
+
+A small number of UNIX commands are provided by AST to assist with the
+process of building software. A description of these can be found in
+\appref{ss:commanddescriptions} and their use is discussed here.  Note
+that in order to access these commands, the appropriate directory
+(normally ``/star/bin'') should be on your PATH.\footnote{If you have
+not installed AST in the usual location, then substitute the
+appropriate directory in place of ``/star'' wherever it occurs.}
+
+\subsection{\label{ss:accessingheaderfile}Accessing the ``ast.h'' Header File}
+
+The ``ast.h'' header file defines the external interface to the AST library,
+including all constants, function prototypes, macros, \emph{etc.}. This file
+should be located using the usual compiler options for finding C
+include files, for instance:
+
+\small
+\begin{terminalv}
+cc prog.c -I/star/include -o prog
+\end{terminalv}
+\normalsize
+
+This is preferable to specifying the file's absolute name within your
+software.
+
+\subsection{\label{ss:linking}Linking with AST Facilities}
+
+C programs which use AST facilities may be linked by including
+execution of the command ``\htmlref{ast\_link}{ast\_link}'' on the compiler command
+line. Thus, to compile and link a program called ``prog'', the
+following might be used:
+
+\small
+\begin{terminalv}
+cc prog.c -L/star/lib `ast_link` -o prog
+\end{terminalv}
+\normalsize
+
+Note the use of backward quote characters, which cause the
+``ast\_link'' command to be executed and its result substituted into
+the compiler command. An alternative is to save the output from
+``ast\_link'' in (say) a shell variable and use this instead. You may
+find this a little faster if you are building software repeatedly
+during development.
+
+Programs which use AST can also be linked in a number of other ways,
+depending on the facilities they require. In the example above, we
+have used the default method which assumes that the program will not
+be generating graphical output, so that no graphics libraries need be
+linked. If you need other facilities, then various switches can be
+applied to the ``ast\_link'' command in order to control the linking
+process.
+
+For example, if you were producing graphical output using the PGPLOT
+graphics package, you could link with the AST/PGPLOT interface by
+using the ``$-$pgplot'' switch with ``ast\_link'', as
+follows:\footnote{Use the ``$-$pgp'' option instead if you wish to use
+the Starlink version of PGPLOT which uses GKS to generate its output.}
+
+\small
+\begin{terminalv}
+cc prog.c -L/star/lib `ast_link -pgplot` -o prog
+\end{terminalv}
+\normalsize
+
+See the ``ast\_link'' command description in
+\appref{ss:commanddescriptions} for details of the options available.
+
+\subsection{Building ADAM Applications that Use AST}
+
+Users of Starlink's \xref{ADAM}{sg4}{} programming environment
+\latex{(SG/4)} on UNIX should use the
+``\xref{alink}{sun144}{ADAM_link_scripts}'' command
+(\xref{SUN/144}{sun144}{}) to compile and link applications and can
+access the AST library by including execution of the command
+``\htmlref{ast\_link\_adam}{ast\_link\_adam}'' on the command line, as follows:
+
+\begin{small}
+\begin{terminalv}
+alink adamprog.c `ast_link_adam`
+\end{terminalv}
+\end{small}
+
+Note the use of backward quote characters.
+
+By default, AST error messages produced by applications built in this
+way will be delivered \emph{via} the Starlink EMS Error Message
+Service (\xref{SSN/4}{ssn4}{}) so that error handling by AST is
+consistent with the \xref{\emph{inherited
+status}}{sun104}{inherited_status} error handling normally used in
+Starlink software.
+
+Switches may be given to the ``ast\_link\_adam'' command (in a similar
+way to ``\htmlref{ast\_link}{ast\_link}''---\secref{ss:linking}) in order to link with
+additional AST-related facilities, such as a graphics interface. See
+the ``ast\_link\_adam'' command description in
+\appref{ss:commanddescriptions} for details of the options available.
+
+\appendix
+\cleardoublepage
+\section{\label{ss:classhierarchy}The AST Class Hierarchy}
+The following table shows the hierarchy of classes in the AST library.
+For a description of each class, you should consult
+\appref{ss:classdescriptions}.
+
+\small
+\begin{terminalv}
+Object             - Base class for all AST Objects
+   Axis            - Store axis information
+      SkyAxis      - Store celestial axis information
+   Channel         - Basic (textual) I/O channel
+      FitsChan     - I/O Channel using FITS header cards
+      XmlChan      - I/O Channel using XML
+      StcsChan     - I/O Channel using IVOA STC-S descriptions
+   KeyMap          - Store a set of key/value pairs
+      Table        - Store a 2-dimensional table of values
+   Mapping         - Inter-relate two coordinate systems
+      CmpMap       - Compound Mapping
+      DssMap       - Map points using Digitised Sky Survey plate solution
+      Frame        - Coordinate system description
+         CmpFrame  - Compound Frame
+            SpecFluxFrame - Observed value versus spectral position
+         FluxFrame - Observed value at a given fixed spectral position
+         FrameSet  - Set of inter-related coordinate systems
+            Plot   - Provide facilities for 2D graphical output
+               Plot3D - Provide facilities for 3D graphical output
+         Region    - Specify areas within a coordinate system
+            Box    - A box region with sides parallel to the axes of a Frame
+            Circle - A circular or spherical region within a Frame
+            CmpRegion  - A combination of two regions within a single Frame
+            Ellipse    - An elliptical region within a 2-dimensional Frame
+            Interval   - Intervals on one or more axes of a Frame.
+            NullRegion - A boundless region within a Frame
+            PointList  - A collection of points in a Frame
+            Polygon    - A polygonal region within a 2-dimensional Frame
+            Prism  - An extrusion of a Region into orthogonal dimensions
+            Stc    - Represents an generic instance of an IVOA STC-X description
+               StcResourceProfile - Represents an an IVOA STC-X ResourceProfile
+               StcSearchLocation  - Represents an an IVOA STC-X SearchLocation
+               StcCatalogEntryLocation - Represents an an IVOA STC-X CatalogEntryLocation
+               StcObsDataLocation - Represents an an IVOA STC-X ObsDataLocation
+         SkyFrame  - Celestial coordinate system description
+         SpecFrame - Spectral coordinate system description
+            DSBSpecFrame - Dual sideband spectral coordinate system description
+         TimeFrame - Time coordinate system description
+      GrismMap     - Models the spectral dispersion produced by a grism
+      IntraMap     - Map points using a private transformation function
+      LutMap       - Transform 1-dimensional coordinates using a lookup table
+      MathMap      - Transform coordinates using mathematical expressions
+      MatrixMap    - Map positions by multiplying them by a matrix
+      NormMap      - Normalise coordinates using a supplied Frame
+      PcdMap       - Apply 2-dimensional pincushion/barrel distortion
+      PermMap      - Coordinate permutation Mapping
+      PolyMap      - General N-dimensional polynomial Mapping
+      RateMap      - Calculates an element of a Mapping's Jacobian matrix
+      SelectorMap  - Locates positions within a set of Regions
+      ShiftMap     - Shifts each axis by a constant amount
+      SlaMap       - Sequence of celestial coordinate conversions
+      SpecMap      - Sequence of spectral coordinate conversions
+      SphMap       - Map 3-d Cartesian to 2-d spherical coordinates
+      SwitchMap    - Encapuslates a set of alternate Mappings
+      TimeMap      - Sequence of time coordinate conversions
+      TranMap      - Combine fwd. and inv. transformations from two Mappings
+      UnitMap      - Unit (null) Mapping
+      UnitNormMap  - Converts a vector to a unit vector plus length
+      WcsMap       - Implement a FITS-WCS sky projection
+      WinMap       - Match windows by scaling and shifting each axis
+      ZoomMap      - Zoom coordinates about the origin
+\end{terminalv}
+\normalsize
+
+\cleardoublepage
+\section{\label{ss:functiondescriptions}AST Function Descriptions}
+\small
+\sstroutine{
+   astSet
+}{
+   Set attribute values for an Object
+}{
+   \sstdescription{
+      This function assigns a set of attribute values to an \htmlref{Object}{Object},
+      over-riding any previous values. The attributes and their new
+      values are specified via a character string, which should
+      contain a comma-separated list of the form:
+
+         \texttt{"} attribute\_1 = value\_1, attribute\_2 = value\_2, ... \texttt{"}
+
+      where \texttt{"} attribute\_n\texttt{"}  specifies an attribute name, and the value
+      to the right of each \texttt{"} =\texttt{"}  sign should be a suitable textual
+      representation of the value to be assigned. This value will be
+      interpreted according to the attribute\texttt{'} s data type.
+
+      The string supplied may also contain \texttt{"} printf\texttt{"} -style format
+      specifiers, identified by \texttt{"} \%\texttt{"}  signs in the usual way. If
+      present, these will be substituted by values supplied as
+      additional optional arguments (using the normal \texttt{"} printf\texttt{"}  rules)
+      before the string is used.
+   }
+   \sstsynopsis{
+      void astSet( AstObject $*$this, const char $*$settings, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         settings
+      }{
+         Pointer to a null-terminated character string containing a
+         comma-separated list of attribute settings in the form described
+         above.
+      }
+      \sstsubsection{
+         ...
+      }{
+         Optional additional arguments which supply values to be
+         substituted for any \texttt{"} printf\texttt{"} -style format specifiers that
+         appear in the \texttt{"} settings\texttt{"}  string.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         astSet( map, \texttt{"} \htmlref{Report}{Report} = 1, \htmlref{Zoom}{Zoom} = 25.0\texttt{"}  );
+      }{
+         Sets the Report attribute for Object \texttt{"} map\texttt{"}  to the value 1 and
+         the Zoom attribute to 25.0.
+      }
+      \sstexamplesubsection{
+         astSet( frame, \texttt{"} Label( \%d ) =Offset along axis \%d\texttt{"} , axis, axis );
+      }{
+         Sets the \htmlref{Label(axis)}{Label(axis)} attribute for Object \texttt{"} frame\texttt{"}  to a
+         suitable string, where the axis number is obtained from
+         \texttt{"} axis\texttt{"} , a variable of type int.
+      }
+      \sstexamplesubsection{
+         astSet( frame, \texttt{"} \htmlref{Title}{Title} =\%s\texttt{"} , mystring );
+      }{
+         Sets the Title attribute for Object \texttt{"} frame\texttt{"}  to the contents of
+         the string \texttt{"} mystring\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         White space may also surround attribute values, where it will
+         generally be ignored (except for string-valued attributes where
+         it is significant and forms part of the value to be assigned).
+
+         \sstitem
+         To include a literal comma in the value assigned to an attribute,
+         the whole attribute value should be enclosed in quotation markes.
+         Alternatively, you can use \texttt{"} \%s\texttt{"}  format and supply the value as a
+         separate additional argument to astSet (or use the astSetC
+         function instead).
+
+         \sstitem
+         The same procedure may be adopted if \texttt{"} \%\texttt{"}  signs are to be included
+         and are not to be interpreted as format specifiers (alternatively,
+         the \texttt{"} printf\texttt{"}  convention of writing \texttt{"} \%\%\texttt{"}  may be used).
+
+         \sstitem
+         An error will result if an attempt is made to set a value for
+         a read-only attribute.
+      }
+   }
+}
+\sstroutine{
+   astAddColumn
+}{
+   Add a new column definition to a table
+}{
+   \sstdescription{
+      Adds the definition of a new column to the supplied table. Initially,
+      the column is empty. Values may be added subsequently using the
+      methods of the \htmlref{KeyMap}{KeyMap} class.
+   }
+   \sstsynopsis{
+      void astAddColumn( AstTable $*$this, const char $*$name, int type, int ndim,
+                         int $*$dims, const char $*$unit )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Table}{Table}.
+      }
+      \sstsubsection{
+         name
+      }{
+         The column name. Trailing spaces are ignored (all other spaces
+         are significant). The supplied string is converted to upper case.
+      }
+      \sstsubsection{
+         type
+      }{
+         The data type associated with the column. See \texttt{"} Applicability:\texttt{"}
+         below.
+      }
+      \sstsubsection{
+         ndim
+      }{
+         The number of dimensions spanned by the values stored in a single
+         cell of the column. Zero if the column holds scalar values.
+      }
+      \sstsubsection{
+         dims
+      }{
+         An array holding the the lengths of each of the axes spanned by
+         the values stored in a single cell of the column. Ignored if the
+         column holds scalara values.
+      }
+      \sstsubsection{
+         unit
+      }{
+         A string specifying the units of the column. Supply a blank
+         string if the column is unitless.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Table
+      }{
+         Tables can hold columns with any of the following data types -
+         AST\_\_INTTYPE (for integer), AST\_\_SINTTYPE (for
+         short int),
+         AST\_\_BYTETYPE (for
+         unsigned bytes - i.e. unsigned chars),
+         AST\_\_DOUBLETYPE (for double
+         precision floating point), AST\_\_FLOATTYPE (for single
+         precision floating point), AST\_\_STRINGTYPE (for character string),
+         AST\_\_OBJECTTYPE (for AST \htmlref{Object}{Object} pointer), AST\_\_POINTERTYPE (for
+         arbitrary C pointer) or AST\_\_UNDEFTYPE (for undefined values
+         created by
+         \htmlref{astMapPutU}{astMapPutU}).
+      }
+      \sstsubsection{
+         \htmlref{FitsTable}{FitsTable}
+      }{
+         FitsTables can hold columns with any of the following data types -
+         AST\_\_INTTYPE (for integer), AST\_\_SINTTYPE (for
+         short int),
+         AST\_\_BYTETYPE (for
+         unsigned bytes - i.e. unsigned chars),
+         AST\_\_DOUBLETYPE (for double
+         precision floating point), AST\_\_FLOATTYPE (for single
+         precision floating point), AST\_\_STRINGTYPE (for character string).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This
+         function
+         returns without action if a column already exists in the Table
+         with the supplied name and properties. However an error is
+         reported if any of the properties differ.
+      }
+   }
+}
+\sstroutine{
+   astAddFrame
+}{
+   Add a Frame to a FrameSet to define a new coordinate system
+}{
+   \sstdescription{
+      This function adds a new \htmlref{Frame}{Frame} and an associated \htmlref{Mapping}{Mapping} to a
+      \htmlref{FrameSet}{FrameSet} so as to define a new coordinate system, derived from
+      one which already exists within the FrameSet. The new Frame then
+      becomes the FrameSet\texttt{'} s current Frame.
+
+      This function
+      may also be used to merge two FrameSets, or to append extra axes
+      to every Frame in a FrameSet.
+   }
+   \sstsynopsis{
+      void astAddFrame( AstFrameSet $*$this, int iframe, AstMapping $*$map,
+                        AstFrame $*$frame )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         iframe
+      }{
+         The index of the Frame within the FrameSet which describes
+         the coordinate system upon which the new one is to be based.
+         This value should lie in the range from 1 to the number of
+         Frames already in the FrameSet (as given by its \htmlref{Nframe}{Nframe}
+         attribute). As a special case, AST\_\_ALLFRAMES may be supplied,
+         in which case the axes defined by the supplied Frame are appended
+         to every Frame in the FrameSet (see the Notes section for details).
+      }
+      \sstsubsection{
+         map
+      }{
+         Pointer to a Mapping which describes how to convert
+         coordinates from the old coordinate system (described by the
+         Frame with index \texttt{"} iframe\texttt{"} ) into coordinates in the new
+         system. The Mapping\texttt{'} s forward transformation should perform
+         this conversion, and its inverse transformation should
+         convert in the opposite direction. The supplied Mapping is ignored
+         if parameter \texttt{"} iframe\texttt{"} is equal to AST\_\_ALLFRAMES.
+      }
+      \sstsubsection{
+         frame
+      }{
+         Pointer to a Frame that describes the new coordinate system.
+         Any class of Frame may be supplied (including Regions and
+         FrameSets).
+
+         This function may also be used to merge two FrameSets by
+         supplying a pointer to a second FrameSet for this parameter
+         (see the Notes section for details).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Deep copies of the supplied
+         \texttt{"} mapping\texttt{"}  and \texttt{"} frame\texttt{"}
+         objects are stored within the modified FrameSet. So any changes made
+         to the FrameSet after calling this method will have no effect on the
+         supplied Mapping and Frame objects.
+
+         \sstitem
+         A value of AST\_\_BASE or AST\_\_CURRENT may be given for the
+         \texttt{"} iframe\texttt{"}  parameter to specify the base Frame or the current
+         Frame respectively.
+
+         \sstitem
+         This function sets the value of the \htmlref{Current}{Current} attribute for the
+         FrameSet so that the new Frame subsequently becomes the current
+         Frame.
+
+         \sstitem
+         The number of input coordinate values accepted by the supplied
+         Mapping (its \htmlref{Nin}{Nin} attribute) must match the number of axes in the
+         Frame identified by the \texttt{"} iframe\texttt{"}  parameter. Similarly, the
+         number of output coordinate values generated by this Mapping
+         (its \htmlref{Nout}{Nout} attribute) must match the number of axes in the new
+         Frame.
+
+         \sstitem
+         As a special case, if a pointer to a FrameSet is given for the
+         \texttt{"} frame\texttt{"}  parameter, this is treated as a request to merge a pair of
+         FrameSets.  This is done by appending all the new Frames (in the
+         \texttt{"} frame\texttt{"}  FrameSet) to the original FrameSet, while preserving
+         their order and retaining all the inter-relationships
+         (i.e. Mappings) between them. The two sets of Frames are
+         inter-related within the merged FrameSet by using the Mapping
+         supplied. This should convert between the Frame identified by
+         the \texttt{"} iframe\texttt{"}  parameter (in the original FrameSet) and the current
+         Frame of the \texttt{"} frame\texttt{"}  FrameSet. This latter Frame becomes the
+         current Frame in the merged FrameSet.
+
+         \sstitem
+         As another special case, if a value of AST\_\_ALLFRAMES is supplied
+         for parameter
+         \texttt{"} iframe\texttt{"} ,
+         then the supplied Mapping is ignored, and the axes defined by the
+         supplied Frame are appended to each Frame in the FrameSet. In detail,
+         each Frame in the FrameSet is replaced by a \htmlref{CmpFrame}{CmpFrame} containing the
+         original Frame and the Frame specified by parameter
+         \texttt{"} frame\texttt{"} .
+         In addition, each Mapping in the FrameSet is replaced by a \htmlref{CmpMap}{CmpMap}
+         containing the original Mapping and a \htmlref{UnitMap}{UnitMap} in parallel. The Nin and
+         Nout attributes of the UnitMap are set equal to the number of axes
+         in the supplied Frame. Each new CmpMap is simplified using
+         \htmlref{astSimplify}{astSimplify}
+         before being stored in the FrameSet.
+      }
+   }
+}
+\sstroutine{
+   astAddParameter
+}{
+   Add a new global parameter definition to a table
+}{
+   \sstdescription{
+      Adds the definition of a new global parameter to the supplied
+      table. Note, this does not store a value for the parameter. To get
+      or set the parameter value, the methods of the paremt \htmlref{KeyMap}{KeyMap} class
+      should be used, using the name of the parameter as the key.
+   }
+   \sstsynopsis{
+      void astAddParameter( AstTable $*$this, const char $*$name )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Table}{Table}.
+      }
+      \sstsubsection{
+         name
+      }{
+         The parameter name. Trailing spaces are ignored (all other spaces
+         are significant). The supplied string is converted to upper case.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Unlike columns, the definition of a parameter does not specify its type,
+         size or dimensionality.
+      }
+   }
+}
+\sstroutine{
+   astAddVariant
+}{
+   Store a new variant Mapping for the current Frame in a FrameSet
+}{
+   \sstdescription{
+      This function
+      allows a new variant \htmlref{Mapping}{Mapping} to be stored with the current \htmlref{Frame}{Frame}
+      in a \htmlref{FrameSet}{FrameSet}. See the \texttt{"} \htmlref{Variant}{Variant}\texttt{"}  attribute for more details. It can
+      also be used to rename the currently selected variant Mapping.
+   }
+   \sstsynopsis{
+      void astAddVariant( AstFrameSet $*$this, AstMapping $*$map,
+                          const char $*$name )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         map
+      }{
+         Pointer to a Mapping which describes how to convert
+         coordinates from the current Frame to the new variant of the
+         current Frame. If
+         NULL
+         is supplied, then the name associated with the currently selected
+         variant of the current Frame is set to the value supplied for
+         \texttt{"} name\texttt{"} , but no new variant is added.
+      }
+      \sstsubsection{
+         name
+      }{
+         The name to associate with the new variant Mapping (or the currently
+         selected variant Mapping if
+         \texttt{"} map\texttt{"}  is NULL).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The newly added Variant becomes the current variant on exit (this is
+         equivalent to setting the Variant attribute to the value supplied for
+         \texttt{"} name).
+
+         \sstitem
+         An error is reported if a variant with the supplied name already
+         exists in the current Frame.
+
+         \sstitem
+         An error is reported if the current Frame is a mirror for the
+         variant Mappings in another Frame. This is only the case if the
+         \htmlref{astMirrorVariants}{astMirrorVariants} function
+         has been called to make the current Frame act as a mirror.
+      }
+   }
+}
+\sstroutine{
+   astAngle
+}{
+   Calculate the angle subtended by two points at a third point
+}{
+   \sstdescription{
+      This function
+      finds the angle at point B between the line joining points A and B,
+      and the line joining points C and B. These lines will in fact be
+      geodesic curves appropriate to the \htmlref{Frame}{Frame} in use. For instance, in
+      \htmlref{SkyFrame}{SkyFrame}, they will be great circles.
+   }
+   \sstsynopsis{
+      double astAngle( AstFrame $*$this, const double a[], const double b[],
+                       const double c[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         a
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute) containing the coordinates of the first point.
+      }
+      \sstsubsection{
+         b
+      }{
+         An array of double, with one element for each Frame axis
+         (Naxes attribute) containing the coordinates of the second point.
+      }
+      \sstsubsection{
+         c
+      }{
+         An array of double, with one element for each Frame axis
+         (Naxes attribute) containing the coordinates of the third point.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astAngle
+      }{
+         The angle in radians, from the line AB to the line CB. If the
+         Frame is 2-dimensional, it will be in the range \$$\backslash$pm $\backslash$pi\$,
+         and positive rotation is in the same sense as rotation from
+         the positive direction of axis 2 to the positive direction of
+         axis 1. If the Frame has more than 2 axes, a positive value will
+         always be returned in the range zero to \$$\backslash$pi\$.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of AST\_\_BAD will also be returned if points A and B are
+         co-incident, or if points B and C are co-incident.
+
+         \sstitem
+         A value of AST\_\_BAD will also be returned if this function is
+         invoked with the AST error status set, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   astAnnul
+}{
+   Annul a pointer to an Object
+}{
+   \sstdescription{
+      This function annuls a pointer to an \htmlref{Object}{Object} so that it is no
+      longer recognised as a valid pointer by the AST library. Any
+      resources associated with the pointer are released and made
+      available for re-use.
+
+      This function also decrements the Object\texttt{'} s \htmlref{RefCount}{RefCount} attribute by
+      one. If this attribute reaches zero (which happens when the last
+      pointer to the Object is annulled), then the Object is deleted.
+   }
+   \sstsynopsis{
+      AstObject $*$astAnnul( AstObject $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         The Object pointer to be annulled.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astAnnul()
+      }{
+         A null Object pointer (AST\_\_NULL) is always returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function will attempt to annul the pointer even if the
+         Object is not currently locked by the calling thread (see \htmlref{astLock}{astLock}).
+
+         \sstitem
+         This function attempts to execute even if the AST error
+         status is set
+         on entry, although no further error report will be
+         made if it subsequently fails under these circumstances. In
+         particular, it will fail if the pointer suppled is not valid,
+         but this will only be reported if the error status is clear on
+         entry.
+      }
+   }
+}
+\sstroutine{
+   astAxAngle
+}{
+   Returns the angle from an axis, to a line through two points
+}{
+   \sstdescription{
+      This function
+      finds the angle, as seen from point A, between the positive
+      direction of a specified axis, and the geodesic curve joining point
+      A to point B.
+   }
+   \sstsynopsis{
+      double astAxAngle( AstFrame $*$this, const double a[], const double b[], int axis )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Frame}{Frame}.
+      }
+      \sstsubsection{
+         a
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute) containing the coordinates of the first point.
+      }
+      \sstsubsection{
+         b
+      }{
+         An array of double, with one element for each Frame axis
+         (Naxes attribute) containing the coordinates of the second point.
+      }
+      \sstsubsection{
+         axis
+      }{
+         The number of the Frame axis from which the angle is to be
+         measured (axis numbering starts at 1 for the first axis).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astAxAngle
+      }{
+         The angle in radians, from the positive direction of the
+         specified axis, to the line AB. If the Frame is 2-dimensional,
+         it will be in the range [-PI/2,$+$PI/2], and positive rotation is in
+         the same sense as rotation from the positive direction of axis 2
+         to the positive direction of axis 1. If the Frame has more than 2
+         axes, a positive value will always be returned in the range zero
+         to PI.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The geodesic curve used by this function is the path of
+         shortest distance between two points, as defined by the
+         \htmlref{astDistance}{astDistance} function.
+
+         \sstitem
+         This function will return \texttt{"} bad\texttt{"}  coordinate values (AST\_\_BAD)
+         if any of the input coordinates has this value, or if the require
+         position angle is undefined.
+      }
+   }
+}
+\sstroutine{
+   astAxDistance
+}{
+   Find the distance between two axis values
+}{
+   \sstdescription{
+      This function returns a signed value representing the axis increment
+      from axis value v1 to axis value v2.
+
+      For a simple \htmlref{Frame}{Frame}, this is a trivial operation returning the
+      difference between the two axis values. But for other derived classes
+      of Frame (such as a \htmlref{SkyFrame}{SkyFrame}) this is not the case.
+   }
+   \sstsynopsis{
+      double astAxDistance( AstFrame $*$this, int axis, double v1, double v2 )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         axis
+      }{
+         The index of the axis to which the supplied values refer. The
+         first axis has index 1.
+      }
+      \sstsubsection{
+         v1
+      }{
+         The first axis value.
+      }
+      \sstsubsection{
+         v2
+      }{
+         The second axis value.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astAxDistance
+      }{
+         The distance from the first to the second axis value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function will return a \texttt{"} bad\texttt{"}  result value (AST\_\_BAD) if
+         any of the input values has this value.
+
+         \sstitem
+         A \texttt{"} bad\texttt{"}  value will also be returned if this function is
+         invoked with the AST error status set, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   astAxNorm
+}{
+   Normalise an array of axis values
+}{
+   \sstdescription{
+      This function
+      modifies a supplied array of axis values so that they are normalised
+      in the manner indicated by
+      parameter \texttt{"} oper\texttt{"} .
+
+      No normalisation is possible for a simple \htmlref{Frame}{Frame} and so the supplied
+      values are returned unchanged. However, this may not be the case for
+      specialised sub-classes of Frame. For instance, a \htmlref{SkyFrame}{SkyFrame} has a
+      discontinuity at zero longitude and so a longitude value can be
+      expressed in the range [-Pi,$+$PI] or the range [0,2$*$PI]. See the
+      \texttt{"} Applicability:\texttt{"}  section below for details.
+   }
+   \sstsynopsis{
+      void astAxNorm( AstFrame $*$this, int axis, int oper, int nval,
+                      double $*$values, int $*$status )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         axis
+      }{
+         The index of the axis to which the supplied values refer. The
+         first axis has index 1.
+      }
+      \sstsubsection{
+         oper
+      }{
+         Indicates the type of normalisation to be applied. If zero is
+         supplied, the normalisation will be the same as that performed by
+         function \htmlref{astNorm}{astNorm}.
+         If 1 is supplied, the normalisation will be chosen automatically
+         so that the resulting list has the smallest range.
+      }
+      \sstsubsection{
+         nval
+      }{
+         The number of points in the values array.
+      }
+      \sstsubsection{
+         values
+      }{
+         On entry, the axis values to be normalised. Modified on exit to
+         hold the normalised values.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         If \texttt{"} oper\texttt{"}
+         is 0, longitude values are returned in the range [0,2$*$PI].
+         If \texttt{"} oper\texttt{"}
+         is 1, longitude values are returned in either the range
+         [0,2$*$PI] or [-PI,PI]. The choice is made so that that the
+         resulting list has the smallest range. Latitude values are
+         always returned in the range [-PI,PI].
+      }
+      \sstsubsection{
+         All other classes of Frame
+      }{
+         The supplied axis values are returned unchanged.
+      }
+   }
+}
+\sstroutine{
+   astAxOffset
+}{
+   Add an increment onto a supplied axis value
+}{
+   \sstdescription{
+      This function returns an axis value formed by adding a signed axis
+      increment onto a supplied axis value.
+
+      For a simple \htmlref{Frame}{Frame}, this is a trivial operation returning the
+      sum of the two supplied values. But for other derived classes
+      of Frame (such as a \htmlref{SkyFrame}{SkyFrame}) this is not the case.
+   }
+   \sstsynopsis{
+      double astAxOffset( AstFrame $*$this, int axis, double v1, double dist )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         axis
+      }{
+         The index of the axis to which the supplied values refer. The
+         first axis has index 1.
+      }
+      \sstsubsection{
+         v1
+      }{
+         The original axis value.
+      }
+      \sstsubsection{
+         dist
+      }{
+         The axis increment to add to the original axis value.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astAxOffset
+      }{
+         The incremented axis value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function will return a \texttt{"} bad\texttt{"}  result value (AST\_\_BAD) if
+         any of the input values has this value.
+
+         \sstitem
+         A \texttt{"} bad\texttt{"}  value will also be returned if this function is
+         invoked with the AST error status set, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   astBBuf
+}{
+   Begin a new graphical buffering context
+}{
+   \sstdescription{
+      This function
+      starts a new graphics buffering context. A matching call to the
+      function \htmlref{astEBuf}{astEBuf}
+      should be used to end the context.
+   }
+   \sstsynopsis{
+      void astBBuf( AstPlot $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Plot}{Plot}.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The nature of the buffering is determined by the underlying
+         graphics system (as defined by the current grf module). Each call
+         to this function
+         to this function
+         simply invokes the astGBBuf function in the grf module.
+      }
+   }
+}
+\sstroutine{
+   astBegin
+}{
+   Begin a new AST context
+}{
+   \sstdescription{
+      This macro invokes a function to begin a new AST context.
+      Any \htmlref{Object}{Object} pointers
+      created within this context will be annulled when it is later
+      ended using \htmlref{astEnd}{astEnd} (just as if \htmlref{astAnnul}{astAnnul} had been invoked),
+      unless they have first been exported using \htmlref{astExport}{astExport} or rendered
+      exempt using \htmlref{astExempt}{astExempt}. If
+      annulling a pointer causes an Object\texttt{'} s \htmlref{RefCount}{RefCount} attribute to
+      fall to zero (which happens when the last pointer to it is
+      annulled), then the Object will be deleted.
+   }
+   \sstsynopsis{
+      void astBegin
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This macro applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         astBegin attempts to execute even if the AST error status
+         is set on entry.
+
+         \sstitem
+         Contexts delimited by astBegin and astEnd may be nested to any
+         depth.
+      }
+   }
+}
+\sstroutine{
+   astBorder
+}{
+   Draw a border around valid regions of a Plot
+}{
+   \sstdescription{
+      This function draws a (line) border around regions of the
+      plotting area of a \htmlref{Plot}{Plot} which correspond to valid, unclipped
+      physical coordinates. For example, when plotting using an
+      all-sky map projection, this function could be used to draw the
+      boundary of the celestial sphere when it is projected on to the
+      plotting surface.
+
+      If the entire plotting area contains valid, unclipped physical
+      coordinates, then the boundary will just be a rectangular box
+      around the edges of the plotting area.
+
+      If the Plot is a \htmlref{Plot3D}{Plot3D}, this method is applied individually to
+      each of the three 2D Plots encapsulated within the Plot3D (each of
+      these Plots corresponds to a single 2D plane in the 3D graphics
+      system). In addition, if the entire plotting volume has valid
+      coordinates in the 3D current \htmlref{Frame}{Frame} of the Plot3D, then additional
+      lines are drawn along the edges of the 3D plotting volume so that
+      the entire plotting volume is enclosed within a cuboid grid.
+   }
+   \sstsynopsis{
+      int astBorder( AstPlot $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astBorder()
+      }{
+         Zero is returned if the plotting space is completely filled by
+         valid, unclipped physical coordinates (so that only a
+         rectangular box was drawn around the edge). Otherwise, one is
+         returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any
+         reason.
+
+         \sstitem
+         An error results if either the current Frame or the base Frame
+         of the Plot is not 2-dimensional or (for a Plot3D) 3-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the base
+         and current Frames of the Plot is not defined (i.e. the Plot\texttt{'} s
+         \htmlref{TranForward}{TranForward} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   astBoundingBox
+}{
+   Return a bounding box for previously drawn graphics
+}{
+   \sstdescription{
+      This function returns the bounds of a box which just encompasess the
+      graphics produced by the previous call to any of the \htmlref{Plot}{Plot} methods
+      which produce graphical output. If no such previous call has yet
+      been made, or if the call failed for any reason, then the bounding box
+      returned by this function is undefined.
+   }
+   \sstsynopsis{
+      void astBoundingBox( AstPlot $*$this, float lbnd[2], float ubnd[2] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         A two element array in which is returned the lower limits of the
+         bounding box on each of the two axes of the graphics coordinate
+         system (the base \htmlref{Frame}{Frame} of the Plot).
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         A two element array in which is returned the upper limits of the
+         bounding box on each of the two axes of the graphics coordinate
+         system (the base Frame of the Plot).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error results if the base Frame of the Plot is not
+         2-dimensional.
+      }
+   }
+}
+\sstroutine{
+   astBox
+}{
+   Create a Box
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Box}{Box} and optionally initialises its
+      attributes.
+
+      The Box class implements a \htmlref{Region}{Region} which represents a box with sides
+      parallel to the axes of a \htmlref{Frame}{Frame} (i.e. an area which encloses a given
+      range of values on each axis). A Box is similar to an \htmlref{Interval}{Interval}, the
+      only real difference being that the Interval class allows some axis
+      limits to be unspecified. Note, a Box will only look like a box if
+      the Frame geometry is approximately flat. For instance, a Box centred
+      close to a pole in a \htmlref{SkyFrame}{SkyFrame} will look more like a fan than a box
+      (the \htmlref{Polygon}{Polygon} class can be used to create a box-like region close to a
+      pole).
+   }
+   \sstsynopsis{
+      AstBox $*$astBox( AstFrame $*$frame, int form, const double point1[],
+                      const double point2[], AstRegion $*$unc,
+                      const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         A pointer to the Frame in which the region is defined. A deep
+         copy is taken of the supplied Frame. This means that any
+         subsequent changes made to the Frame using the supplied pointer
+         will have no effect the Region.
+      }
+      \sstsubsection{
+         form
+      }{
+         Indicates how the box is described by the remaining parameters.
+         A value of zero indicates that the box is specified by a centre
+         position and a corner position. A value of one indicates that the
+         box is specified by a two opposite corner positions.
+      }
+      \sstsubsection{
+         point1
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). If
+         \texttt{"} form\texttt{"}
+         is zero, this array should contain the coordinates at the centre of
+         the box.
+         If \texttt{"} form\texttt{"}
+         is one, it should contain the coordinates at the corner of the box
+         which is diagonally opposite the corner specified by
+         \texttt{"} point2\texttt{"} .
+      }
+      \sstsubsection{
+         point2
+      }{
+         An array of double, with one element for each Frame axis
+         (Naxes attribute) containing the coordinates at any corner of the
+         box.
+      }
+      \sstsubsection{
+         unc
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with the boundary of the Box being created.
+         The uncertainty in any point on the boundary of the Box is found by
+         shifting the supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at
+         the boundary point being considered. The area covered by the
+         shifted uncertainty Region then represents the uncertainty in the
+         boundary position. The uncertainty is assumed to be the same for
+         all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. Box, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Box. Alternatively,
+         a NULL \htmlref{Object}{Object} pointer
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the Box being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{astOverlap}{astOverlap}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{astSimplify}{astSimplify}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Box. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astBox()
+      }{
+         A pointer to the new Box.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astChannel
+}{
+   Create a Channel
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Channel}{Channel} and optionally initialises
+      its attributes.
+
+      A Channel implements low-level input/output for the AST library.
+      Writing an \htmlref{Object}{Object} to a Channel (using \htmlref{astWrite}{astWrite}) will generate a
+      textual representation of that Object, and reading from a
+      Channel (using \htmlref{astRead}{astRead}) will create a new Object from its
+      textual representation.
+
+      Normally, when you use a Channel, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  functions which connect it to an external data store
+      by reading and writing the resulting text. By default, however,
+      a Channel will read from standard input and write to standard
+      output. Alternatively, a Channel can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+   }
+   \sstsynopsis{
+      AstChannel $*$astChannel( const char $*$($*$ source)( void ),
+                              void ($*$ sink)( const char $*$ ),
+                              const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         source
+      }{
+         Pointer to a source function that takes no arguments and
+         returns a pointer to a null-terminated string.  If no value
+         has been set for the SourceFile attribute, this function
+         will be used by the Channel to obtain lines of input text. On
+         each invocation, it should return a pointer to the next input
+         line read from some external data store, and a NULL pointer
+         when there are no more lines to read.
+
+         If \texttt{"} source\texttt{"}  is NULL and no value has been set for the SourceFile
+         attribute, the Channel will read from standard input instead.
+      }
+      \sstsubsection{
+         sink
+      }{
+         Pointer to a sink function that takes a pointer to a
+         null-terminated string as an argument and returns void.
+         If no value has been set for the SinkFile attribute, this
+         function will be used by the Channel to deliver lines of
+         output text. On each invocation, it should deliver the
+         contents of the string supplied to some external data store.
+
+         If \texttt{"} sink\texttt{"}  is NULL, and no value has been set for the SinkFile
+         attribute, the Channel will write to standard output instead.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Channel. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astChannel()
+      }{
+         A pointer to the new Channel.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Application code can pass arbitrary data (such as file
+         descriptors, etc) to source and sink functions using the
+         \htmlref{astPutChannelData}{astPutChannelData} function. The source or sink function should use
+         the \htmlref{astChannelData}{astChannelData} macro to retrieve this data.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astChannelData
+}{
+   Return a pointer to user-supplied data stored with a Channel
+}{
+   \sstdescription{
+      This macro is intended to be used within the source or sink
+      functions associated with a \htmlref{Channel}{Channel}. It returns any pointer
+      previously stored in the Channel (that is, the Channel that has
+      invoked the source or sink function) using \htmlref{astPutChannelData}{astPutChannelData}.
+
+      This mechanism is a thread-safe alternative to passing file
+      descriptors, etc, via static global variables.
+   }
+   \sstsynopsis{
+      void $*$astChannelData
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         This macro applies to all Channels.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astChannelData
+      }{
+         The pointer previously stored with the Channel using
+         astPutChannelData. A NULL pointer will be returned if no such
+         pointer has been stored with the Channel.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine is not available in the Fortran 77 interface to
+         the AST library.
+      }
+   }
+}
+\sstroutine{
+   astCircle
+}{
+   Create a Circle
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Circle}{Circle} and optionally initialises its
+      attributes.
+
+      A Circle is a \htmlref{Region}{Region} which represents a circle or sphere within the
+      supplied \htmlref{Frame}{Frame}.
+   }
+   \sstsynopsis{
+      AstCircle $*$astCircle( AstFrame $*$frame, int form, const double centre[],
+                            const double point[], AstRegion $*$unc,
+                            const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         A pointer to the Frame in which the region is defined. A deep
+         copy is taken of the supplied Frame. This means that any
+         subsequent changes made to the Frame using the supplied pointer
+         will have no effect the Region.
+      }
+      \sstsubsection{
+         form
+      }{
+         Indicates how the circle is described by the remaining parameters.
+         A value of zero indicates that the circle is specified by a
+         centre position and a position on the circumference. A value of one
+         indicates that the circle is specified by a centre position and a
+         scalar radius.
+      }
+      \sstsubsection{
+         centre
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute) containing the coordinates at the centre of
+         the circle or sphere.
+      }
+      \sstsubsection{
+         point
+      }{
+         If \texttt{"} form\texttt{"}
+         is zero, then this array should have one element for each Frame
+         axis (Naxes attribute), and should be supplied holding the
+         coordinates at a point on the circumference of the circle or sphere.
+         If \texttt{"} form\texttt{"}
+         is one, then this array should have one element only which should
+         be supplied holding the scalar radius of the circle or sphere,
+         as a geodesic distance within the Frame.
+      }
+      \sstsubsection{
+         unc
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with the boundary of the Circle being created.
+         The uncertainty in any point on the boundary of the Circle is found by
+         shifting the supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at
+         the boundary point being considered. The area covered by the
+         shifted uncertainty Region then represents the uncertainty in the
+         boundary position. The uncertainty is assumed to be the same for
+         all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. \htmlref{Box}{Box}, Circle, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Circle. Alternatively,
+         a NULL \htmlref{Object}{Object} pointer
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the Circle being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{astOverlap}{astOverlap}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{astSimplify}{astSimplify}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Circle. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astCircle()
+      }{
+         A pointer to the new Circle.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astCirclePars
+}{
+   Returns the geometric parameters of an Circle
+}{
+   \sstdescription{
+      This function
+      returns the geometric parameters describing the supplied \htmlref{Circle}{Circle}.
+   }
+   \sstsynopsis{
+      void astCirclePars( AstCircle $*$this, double $*$centre, double $*$radius,
+                          double $*$p1 )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         centre
+      }{
+         Pointer to an array
+         in which to return the coordinates of the Circle centre.
+         The length of this array should be no less than the number of
+         axes in the associated coordinate system.
+      }
+      \sstsubsection{
+         radius
+      }{
+         Returned holding the radius of the Circle, as an geodesic
+         distance in the associated coordinate system.
+      }
+      \sstsubsection{
+         p1
+      }{
+         Pointer to an array
+         in which to return the coordinates of a point on the
+         circumference of the Circle. The length of this array should be
+         no less than the number of axes in the associated coordinate system.
+         A NULL pointer can be supplied if the circumference position is
+         not needed.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the coordinate system represented by the Circle has been
+         changed since it was first created, the returned parameters refer
+         to the new (changed) coordinate system, rather than the original
+         coordinate system. Note however that if the transformation from
+         original to new coordinate system is non-linear, the shape
+         represented by the supplied Circle object may not be an accurate
+         circle.
+      }
+   }
+}
+\sstroutine{
+   astClear
+}{
+   Clear attribute values for an Object
+}{
+   \sstdescription{
+      This function clears the values of a specified set of attributes
+      for an \htmlref{Object}{Object}. Clearing an attribute cancels any value that has
+      previously been explicitly set for it, so that the standard
+      default attribute value will subsequently be used instead. This
+      also causes the \htmlref{astTest}{astTest} function to return the value zero for
+      the attribute, indicating that no value has been set.
+   }
+   \sstsynopsis{
+      void astClear( AstObject $*$this, const char $*$attrib )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         attrib
+      }{
+         Pointer to a null-terminated character string containing a
+         comma-separated list of the names of the attributes to be cleared.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         It does no harm to clear an attribute whose value has not been
+         set.
+
+         \sstitem
+         An error will result if an attempt is made to clear the value
+         of a read-only attribute.
+      }
+   }
+}
+\sstroutine{
+   astClearStatus
+}{
+   Clear the AST error status
+}{
+   \sstdescription{
+      This macro resets the AST error status to an OK value,
+      indicating that an error condition (if any) has been cleared.
+   }
+   \sstsynopsis{
+      void astClearStatus
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the AST error status is set to an error value (after an
+         error), most AST functions will not execute and will simply
+         return without action. Using astClearStatus will restore normal
+         behaviour.
+      }
+   }
+}
+\sstroutine{
+   astClip
+}{
+   Set up or remove clipping for a Plot
+}{
+   \sstdescription{
+      This function defines regions of a \htmlref{Plot}{Plot} which are to be clipped.
+      Any subsequent graphical output created using the Plot will then
+      be visible only within the unclipped regions of the plotting
+      area. See also the \htmlref{Clip}{Clip} attribute.
+   }
+   \sstsynopsis{
+      void astClip( AstPlot $*$this, int iframe, const double lbnd[],
+                    const double ubnd[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         iframe
+      }{
+         The index of the \htmlref{Frame}{Frame} within the Plot to which the clipping
+         limits supplied in \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"}  (below) refer. Clipping
+         may be applied to any of the coordinate systems associated
+         with a Plot (as defined by the Frames it contains), so this
+         index may take any value from 1 to the number of Frames in
+         the Plot (\htmlref{Nframe}{Nframe} attribute). In addition, the values
+         AST\_\_BASE and AST\_\_CURRENT may be used to specify the base
+         and current Frames respectively.
+
+         For example, a value of AST\_\_CURRENT causes clipping to be
+         performed in physical coordinates, while a value of AST\_\_BASE
+         would clip in graphical coordinates. Clipping may also be
+         removed completely by giving a value of AST\_\_NOFRAME. In this
+         case any clipping bounds supplied (below) are ignored.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         An array with one element for each axis of the clipping Frame
+         (identified by the index \texttt{"} iframe\texttt{"} ). This should contain the
+         lower bound, on each axis, of the region which is to remain
+         visible (unclipped).
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         An array with one element for each axis of the clipping Frame
+         (identified by the index \texttt{"} iframe\texttt{"} ). This should contain the
+         upper bound, on each axis, of the region which is to remain
+         visible (unclipped).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Only one clipping Frame may be active at a time. This function
+         will deactivate any previously-established clipping Frame before
+         setting up new clipping limits.
+
+         \sstitem
+         The clipping produced by this function is in addition to that
+         specified by the Clip attribute which occurs at the edges of the
+         plotting area
+         established when the Plot is created (see \htmlref{astPlot}{astPlot}). The
+         underlying graphics system may also impose further clipping.
+
+         \sstitem
+         When testing a graphical position for clipping, it is first
+         transformed into the clipping Frame. The resulting coordinate on
+         each axis is then checked against the clipping limits (given by
+         \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"} ). By default, a position is clipped if any
+         coordinate lies outside these limits. However, if a non-zero
+         value is assigned to the Plot\texttt{'} s \htmlref{ClipOp}{ClipOp} attribute, then a
+         position is only clipped if the coordinates on all axes lie
+         outside their clipping limits.
+
+         \sstitem
+         If the lower clipping limit exceeds the upper limit for any
+         axis, then the sense of clipping for that axis is reversed (so
+         that coordinate values lying between the limits are clipped
+         instead of those lying outside the limits). To produce a \texttt{"} hole\texttt{"}
+         in a coordinate space (that is, an internal region where nothing
+         is plotted), you should supply all the bounds in reversed order,
+         and set the ClipOp attribute for the Plot to a non-zero value.
+
+         \sstitem
+         Either clipping limit may be set to the value AST\_\_BAD, which
+         is equivalent to setting it to infinity (or minus infinity for a
+         lower bound) so that it is not used.
+
+         \sstitem
+         If a graphical position results in any bad coordinate values
+         (AST\_\_BAD) when transformed into the clipping Frame, then it is
+         treated (for the purposes of producing graphical output) as if
+         it were clipped.
+
+         \sstitem
+         When a Plot is used as a \htmlref{Mapping}{Mapping} to transform points
+         (e.g. using \htmlref{astTran2}{astTran2}), any clipped output points are assigned
+         coordinate values of AST\_\_BAD.
+
+         \sstitem
+         An error results if the base Frame of the Plot is not
+         2-dimensional.
+      }
+   }
+}
+\sstroutine{
+   astClone
+}{
+   Clone (duplicate) an Object pointer
+}{
+   \sstdescription{
+      This function returns a duplicate pointer to an existing
+      \htmlref{Object}{Object}. It also increments the Object\texttt{'} s \htmlref{RefCount}{RefCount} attribute to
+      keep track of how many pointers have been issued.
+
+      Note that this function is NOT equivalent to an assignment
+      statement, as in general the two pointers will not have the same
+      value.
+   }
+   \sstsynopsis{
+      AstObject $*$astClone( AstObject $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Original pointer to the Object.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astClone()
+      }{
+         A duplicate pointer to the same Object.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astCmpFrame
+}{
+   Create a CmpFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{CmpFrame}{CmpFrame} and optionally initialises
+      its attributes.
+
+      A CmpFrame is a compound \htmlref{Frame}{Frame} which allows two component Frames
+      (of any class) to be merged together to form a more complex
+      Frame. The axes of the two component Frames then appear together
+      in the resulting CmpFrame (those of the first Frame, followed by
+      those of the second Frame).
+
+      Since a CmpFrame is itself a Frame, it can be used as a
+      component in forming further CmpFrames. Frames of arbitrary
+      complexity may be built from simple individual Frames in this
+      way.
+
+      Also since a Frame is a \htmlref{Mapping}{Mapping}, a CmpFrame can also be used as a
+      Mapping. Normally, a CmpFrame is simply equivalent to a \htmlref{UnitMap}{UnitMap},
+      but if either of the component Frames within a CmpFrame is a \htmlref{Region}{Region}
+      (a sub-class of Frame), then the CmpFrame will use the Region as a
+      Mapping when transforming values for axes described by the Region.
+      Thus input axis values corresponding to positions which are outside the
+      Region will result in bad output axis values.
+   }
+   \sstsynopsis{
+      AstCmpFrame $*$astCmpFrame( AstFrame $*$frame1, AstFrame $*$frame2,
+                                const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame1
+      }{
+         Pointer to the first component Frame.
+      }
+      \sstsubsection{
+         frame2
+      }{
+         Pointer to the second component Frame.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new CmpFrame. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astCmpFrame()
+      }{
+         A pointer to the new CmpFrame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astCmpMap
+}{
+   Create a CmpMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{CmpMap}{CmpMap} and optionally initialises
+      its attributes.
+
+      A CmpMap is a compound \htmlref{Mapping}{Mapping} which allows two component
+      Mappings (of any class) to be connected together to form a more
+      complex Mapping. This connection may either be \texttt{"} in series\texttt{"}
+      (where the first Mapping is used to transform the coordinates of
+      each point and the second mapping is then applied to the
+      result), or \texttt{"} in parallel\texttt{"}  (where one Mapping transforms the
+      earlier coordinates for each point and the second Mapping
+      simultaneously transforms the later coordinates).
+
+      Since a CmpMap is itself a Mapping, it can be used as a
+      component in forming further CmpMaps. Mappings of arbitrary
+      complexity may be built from simple individual Mappings in this
+      way.
+   }
+   \sstsynopsis{
+      AstCmpMap $*$astCmpMap( AstMapping $*$map1, AstMapping $*$map2, int series,
+                            const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         map1
+      }{
+         Pointer to the first component Mapping.
+      }
+      \sstsubsection{
+         map2
+      }{
+         Pointer to the second component Mapping.
+      }
+      \sstsubsection{
+         series
+      }{
+         If a non-zero value is given for this parameter, the two
+         component Mappings will be connected in series. A zero
+         value requests that they are connected in parallel.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new CmpMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astCmpMap()
+      }{
+         A pointer to the new CmpMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the component Mappings are connected in series, then using
+         the resulting CmpMap to transform coordinates will cause the
+         first Mapping to be applied, followed by the second Mapping. If
+         the inverse CmpMap transformation is requested, the two
+         component Mappings will be applied in both the reverse order and
+         the reverse direction.
+
+         \sstitem
+         When connecting two component Mappings in series, the number
+         of output coordinates generated by the first Mapping (its \htmlref{Nout}{Nout}
+         attribute) must equal the number of input coordinates accepted
+         by the second Mapping (its \htmlref{Nin}{Nin} attribute).
+
+         \sstitem
+         If the component Mappings of a CmpMap are connected in
+         parallel, then the first Mapping will be used to transform the
+         earlier input coordinates for each point (and to produce the
+         earlier output coordinates) and the second Mapping will be used
+         simultaneously to transform the remaining input coordinates (to
+         produce the remaining output coordinates for each point). If the
+         inverse transformation is requested, each Mapping will still be
+         applied to the same coordinates, but in the reverse direction.
+
+         \sstitem
+         When connecting two component Mappings in parallel, there is
+         no restriction on the number of input and output coordinates for
+         each Mapping.
+
+         \sstitem
+         Note that the component Mappings supplied are not copied by
+         astCmpMap (the new CmpMap simply retains a reference to
+         them). They may continue to be used for other purposes, but
+         should not be deleted. If a CmpMap containing a copy of its
+         component Mappings is required, then a copy of the CmpMap should
+         be made using \htmlref{astCopy}{astCopy}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astCmpRegion
+}{
+   Create a CmpRegion
+}{
+   \sstdescription{
+      This function creates a new \htmlref{CmpRegion}{CmpRegion} and optionally initialises
+      its attributes.
+
+      A CmpRegion is a \htmlref{Region}{Region} which allows two component
+      Regions (of any class) to be combined to form a more complex
+      Region. This combination may be performed a boolean AND, OR
+      or XOR (exclusive OR) operator. If the AND operator is
+      used, then a position is inside the CmpRegion only if it is
+      inside both of its two component Regions. If the OR operator is
+      used, then a position is inside the CmpRegion if it is inside
+      either (or both) of its two component Regions. If the XOR operator
+      is used, then a position is inside the CmpRegion if it is inside
+      one but not both of its two component Regions. Other operators can
+      be formed by negating one or both component Regions before using
+      them to construct a new CmpRegion.
+
+      The two component Region need not refer to the same coordinate
+      \htmlref{Frame}{Frame}, but it must be possible for the
+      \htmlref{astConvert}{astConvert}
+      function to determine a \htmlref{Mapping}{Mapping} between them (an error will be
+      reported otherwise when the CmpRegion is created). For instance,
+      a CmpRegion may combine a Region defined within an ICRS \htmlref{SkyFrame}{SkyFrame}
+      with a Region defined within a Galactic SkyFrame. This is
+      acceptable because the SkyFrame class knows how to convert between
+      these two systems, and consequently the
+      astConvert
+      function will also be able to convert between them. In such cases,
+      the second component Region will be mapped into the coordinate Frame
+      of the first component Region, and the Frame represented by the
+      CmpRegion as a whole will be the Frame of the first component Region.
+
+      Since a CmpRegion is itself a Region, it can be used as a
+      component in forming further CmpRegions. Regions of arbitrary
+      complexity may be built from simple individual Regions in this
+      way.
+   }
+   \sstsynopsis{
+      AstCmpRegion $*$astCmpRegion( AstRegion $*$region1, AstRegion $*$region2,
+                                  int oper, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         region1
+      }{
+         Pointer to the first component Region.
+      }
+      \sstsubsection{
+         region2
+      }{
+         Pointer to the second component Region. This Region will be
+         transformed into the coordinate Frame of the first region before
+         use. An error will be reported if this is not possible.
+      }
+      \sstsubsection{
+         oper
+      }{
+         The boolean operator with which to combine the two Regions. This
+         must be one of the symbolic constants AST\_\_AND, AST\_\_OR or AST\_\_XOR.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new CmpRegion. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astCmpRegion()
+      }{
+         A pointer to the new CmpRegion.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If one of the supplied Regions has an associated uncertainty,
+         that uncertainty will also be used for the returned CmpRegion.
+         If both supplied Regions have associated uncertainties, the
+         uncertainty associated with the first Region will be used for the
+         returned CmpRegion.
+
+         \sstitem
+         Deep copies are taken of the supplied Regions. This means that
+         any subsequent changes made to the component Regions using the
+         supplied pointers will have no effect on the CmpRegion.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astColumnName
+}{
+   Get the name of the column at a given index within the Table
+}{
+   \sstdescription{
+      This function returns a string holding the name of the column with
+      the given index within the \htmlref{Table}{Table}.
+
+      This function is intended primarily as a means of iterating round all
+      the columns in a Table. For this purpose, the number of columns in
+      the Table is given by the \htmlref{Ncolumn}{Ncolumn} attribute of the Table. This function
+      could then be called in a loop, with the index value going from
+      zero to one less than Ncolumn.
+
+      Note, the index associated with a column decreases monotonically with
+      the age of the column: the oldest Column in the Table will have index
+      one, and the Column added most recently to the Table will have the
+      largest index.
+   }
+   \sstsynopsis{
+      const char $*$astColumnName( AstTable $*$this, int index )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         index
+      }{
+         The index into the list of columns. The first column has index
+         one, and the last has index \texttt{"} Ncolumn\texttt{"} .
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astColumnName()
+      }{
+         A pointer to a null-terminated string containing the
+         upper case column name.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned pointer is guaranteed to remain valid and the
+         string to which it points will not be over-written for a total
+         of 50 successive invocations of this function. After this, the
+         memory containing the string may be re-used, so a copy of the
+         string should be made if it is needed for longer than this.
+
+         \sstitem
+         A NULL pointer will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   astColumnNull
+}{
+   Get or set the null value for an integer column of a FITS table
+}{
+   \sstdescription{
+      This function allows a null value to be stored with a named
+      integer-valued column in a \htmlref{FitsTable}{FitsTable}. The supplied null value is
+      assigned to the TNULLn keyword in the FITS header associated with
+      the FitsTable. A value in the named column is then considered to be
+      null if 1) it equals the null value supplied to this function, or
+      2) no value has yet been stored in the cell.
+
+      As well as setting a new null value, this function also returns the
+      previous null value. If no null value has been set previously, a
+      default value will be returned. This default will be an integer
+      value that does not currently occur anywhere within the named column.
+      If no such value can be found, what happens depends on whether the
+      column contains any cells in which no values have yet been stored.
+      If so, an error will be reported. Otherwise (i.e. if there are no
+      null values in the column), an arbitrary value of zero will be
+      returned as the function value, and no TNULLn keyword will be
+      stored in the FITS header.
+
+      A flag is returned indicating if the returned null value was set
+      explicitly by a previous call to this function, or is a default
+      value.
+
+      A second flag is returned indicating if the named column contains
+      any null values (i.e. values equal to the supplied null value, or
+      cells to which no value has yet been assigned).
+   }
+   \sstsynopsis{
+      int astColumnNull( AstFitsTable $*$this, const char $*$column, int set,
+                         int newval, int $*$wasset, int $*$hasnull )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Table}{Table}.
+      }
+      \sstsubsection{
+         column
+      }{
+         The character string holding the name of the column. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         set
+      }{
+         If non-zero, the value supplied for parameter \texttt{"} newval\texttt{"}
+         will be stored as the current null value, replacing any value
+         set by a previous call to this function.
+         If zero, the value supplied for parameter \texttt{"} newval\texttt{"}
+         is ignored and the current null value is left unchanged.
+      }
+      \sstsubsection{
+         newval
+      }{
+         The new null value to use. Ignored if
+         \texttt{"} set\texttt{"}  is zero.
+         An error will be reported if the supplied value is outside the
+         range of values that can be stored in the integer data type
+         associated with the column.
+      }
+      \sstsubsection{
+         wasset
+      }{
+         Pointer to an int that will be returned non-zero
+         if the returned null value was set previously via an
+         earlier invocation of this function.
+         Zero
+         is returned otherwise. If the named column does not exist, or an
+         error occurs, a value of
+         zero is returned.
+      }
+      \sstsubsection{
+         hasnull
+      }{
+         Pointer to an int that will be returned non-zero
+         if and only if the named column currently contains any values
+         equal to the null value on exit (i.e.
+         \texttt{"} newval\texttt{"}  if \texttt{"} set\texttt{"}  is non-zero,
+         or the returned function value otherwise), or contains any empty
+         cells. If the named column does not exist, or an error occurs, a
+         value of
+         zero is returned.
+         If a NULL pointer is supplied for \texttt{"} hasnull\texttt{"} , no check on the
+         presence of null values will be performed.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astColumnNull()
+      }{
+         The null value that was in use on entry to this function. If a
+         null value has been set by a previous invocation of this
+         function, it will be returned. Otherwise, if
+         \texttt{"} set\texttt{"}  is non-zero, the supplied \texttt{"} newval\texttt{"}
+         value is returned. Otherwise, a default value is chosen (if
+         possible) that does not currently occur in the named column. If
+         all available values are in use in the column, an error is
+         reported if and only if the column contains any empty cells.
+         Otherwise, a value of zero is returned. A value of zero is also
+         returned if the named column does not exist, or an error occurs.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The FITS binary table definition allows only integer-valued
+         columns to have an associated null value. This routine will return
+         without action if the column is not integer-valued.
+      }
+   }
+}
+\sstroutine{
+   astColumnShape
+}{
+   Returns the shape of the values in a named column
+}{
+   \sstdescription{
+      This function
+      returns the number of dimensions spaned by each value in a named
+      column of a \htmlref{Table}{Table}, together with the length of each dimension.
+      These are the values supplied when the column was created using
+      \htmlref{astAddColumn}{astAddColumn}.
+   }
+   \sstsynopsis{
+      void astColumnShape( AstTable $*$this, const char $*$column, int mxdim,
+                           int $*$ndim, int $*$dims )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         column
+      }{
+         The character string holding the upper case name of the column. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         mxdim
+      }{
+         The length of the
+         \texttt{"} dims\texttt{"}  array.
+      }
+      \sstsubsection{
+         ndim
+      }{
+         Pointer to an int in which to return the
+         number of dimensions spanned by values in the named column.
+         This will be zero if the column contains scalar values.
+      }
+      \sstsubsection{
+         dims
+      }{
+         Pointer to an
+         array in which to return the length of each dimension. Any
+         excess trailing elements will be filled with the value 1.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No error is reported if the requested column cannot be found in the
+         given Table. A value of zero is returned for
+         \texttt{"} ndim\texttt{"}  and the supplied values in \texttt{"} dims\texttt{"}
+         are left unchanged.
+
+         \sstitem
+         A value of zero is returned for
+         \texttt{"} ndim\texttt{"}
+         if an error occurs.
+      }
+   }
+}
+\sstroutine{
+   astColumnSize
+}{
+   Get the number of bytes needed to hold a full column of data
+}{
+   \sstdescription{
+      This function returns the number of bytes of memory that must be
+      allocated prior to retrieving the data from a column using
+      \htmlref{astGetColumnData}{astGetColumnData}.
+   }
+   \sstsynopsis{
+      size\_t astColumnSize( AstFitsTable $*$this, const char $*$column,
+                            int $*$hasnull )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Table}{Table}.
+      }
+      \sstsubsection{
+         column
+      }{
+         The character string holding the name of the column. Trailing
+         spaces are ignored.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         \htmlref{astColumnNull}{astColumnNull}()
+      }{
+         The number of bytes required to store the column data.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error will be reported if the named column does not exist in
+         the \htmlref{FitsTable}{FitsTable}.
+
+         \sstitem
+         Zero will be returned as the function value in an error occurs.
+      }
+   }
+}
+\sstroutine{
+   astConvert
+}{
+   Determine how to convert between two coordinate systems
+}{
+   \sstdescription{
+      This function compares two Frames and determines whether it is
+      possible to convert between the coordinate systems which they
+      represent. If conversion is possible, it returns a \htmlref{FrameSet}{FrameSet}
+      which describes the conversion and which may be used (as a
+      \htmlref{Mapping}{Mapping}) to transform coordinate values in either direction.
+
+      The same function may also be used to determine how to convert
+      between two FrameSets (or between a \htmlref{Frame}{Frame} and a FrameSet, or
+      vice versa). This mode is intended for use when (for example)
+      two images have been calibrated by attaching a FrameSet to each.
+      astConvert might then be used to search for a
+      celestial coordinate system that both images have in common, and
+      the result could then be used to convert between the pixel
+      coordinates of both images -- having effectively used their
+      celestial coordinate systems to align them.
+
+      When using FrameSets, there may be more than one possible
+      intermediate coordinate system in which to perform the
+      conversion (for instance, two FrameSets might both have
+      celestial coordinates, detector coordinates, pixel coordinates,
+      etc.). A comma-separated list of coordinate system domains may
+      therefore be given which defines a priority order to use when
+      selecting the intermediate coordinate system.  The path used for
+      conversion must go via an intermediate coordinate system whose
+      \htmlref{Domain}{Domain} attribute matches one of the domains given. If conversion
+      cannot be achieved using the first domain, the next one is
+      considered, and so on, until success is achieved.
+   }
+   \sstsynopsis{
+      AstFrameSet $*$astConvert( AstFrame $*$from, AstFrame $*$to,
+                               const char $*$domainlist )
+   }
+   \sstparameters{
+      \sstsubsection{
+         from
+      }{
+         Pointer to a Frame which represents the \texttt{"} source\texttt{"}  coordinate
+         system.  This is the coordinate system in which you already
+         have coordinates available.
+
+         If a FrameSet is given, its current Frame (as determined by
+         its \htmlref{Current}{Current} attribute) is taken to describe the source
+         coordinate system. Note that the \htmlref{Base}{Base} attribute of this
+         FrameSet may be modified by this function to indicate which
+         intermediate coordinate system was used (see under
+         \texttt{"} FrameSets\texttt{"}  in the \texttt{"} Applicability\texttt{"}  section for details).
+      }
+      \sstsubsection{
+         to
+      }{
+         Pointer to a Frame which represents the \texttt{"} destination\texttt{"}
+         coordinate system. This is the coordinate system into which
+         you wish to convert your coordinates.
+
+         If a FrameSet is given, its current Frame (as determined by
+         its Current attribute) is taken to describe the destination
+         coordinate system. Note that the Base attribute of this
+         FrameSet may be modified by this function to indicate which
+         intermediate coordinate system was used (see under
+         \texttt{"} FrameSets\texttt{"}  in the \texttt{"} Applicability\texttt{"}  section for details).
+      }
+      \sstsubsection{
+         domainlist
+      }{
+         Pointer to a null-terminated character string containing a
+         comma-separated list of Frame domains. This may be used to
+         define a priority order for the different intermediate
+         coordinate systems that might be used to perform the
+         conversion.
+
+         The function will first try to obtain a conversion by making
+         use only of an intermediate coordinate system whose Domain
+         attribute matches the first domain in this list. If this
+         fails, the second domain in the list will be used, and so on,
+         until conversion is achieved. A blank domain (e.g. two
+         consecutive commas) indicates that all coordinate systems
+         should be considered, regardless of their domains.
+
+         This list is case-insensitive and all white space is ignored.
+         If you do not wish to restrict the domain in this way,
+         you should supply an empty string. This is normally
+         appropriate if either of the source or destination coordinate
+         systems are described by Frames (rather than FrameSets),
+         since there is then usually only one possible choice of
+         intermediate coordinate system.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{DSBSpecFrame}{DSBSpecFrame}
+      }{
+         If the \htmlref{AlignSideBand}{AlignSideBand} attribute is non-zero, alignment occurs in the
+         upper sideband expressed within the spectral system and standard of
+         rest given by attributes \htmlref{AlignSystem}{AlignSystem} and \htmlref{AlignStdOfRest}{AlignStdOfRest}. If
+         AlignSideBand is zero, the two DSBSpecFrames are aligned as if
+         they were simple SpecFrames (i.e. the \htmlref{SideBand}{SideBand} is ignored).
+      }
+      \sstsubsection{
+         Frame
+      }{
+         This function applies to all Frames. Alignment occurs within the
+         coordinate system given by attribute AlignSystem.
+      }
+      \sstsubsection{
+         FrameSet
+      }{
+         If either of the \texttt{"} from\texttt{"}  or \texttt{"} to\texttt{"}  parameters is a pointer to a
+         FrameSet, then astConvert will attempt to convert from the
+         coordinate system described by the current Frame of the \texttt{"} from\texttt{"}
+         FrameSet to that described by the current Frame of the \texttt{"} to\texttt{"}
+         FrameSet.
+
+         To achieve this, it will consider all of the Frames within
+         each FrameSet as a possible way of reaching an intermediate
+         coordinate system that can be used for the conversion. There
+         is then the possibility that more than one conversion path
+         may exist and, unless the choice is sufficiently restricted
+         by the \texttt{"} domainlist\texttt{"}  string, the sequence in which the Frames
+         are considered can be important. In this case, the search
+         for a conversion path proceeds as follows:
+         \sstitemlist{
+
+            \sstitem
+            Each field in the \texttt{"} domainlist\texttt{"}  string is considered in turn.
+
+            \sstitem
+            The Frames within each FrameSet are considered in a
+            specific order: (1) the base Frame is always considered
+            first, (2) after this come all the other Frames in
+            Frame-index order (but omitting the base and current Frames),
+            (3) the current Frame is always considered last.  However, if
+            either FrameSet\texttt{'} s \htmlref{Invert}{Invert} attribute is set to a non-zero value
+            (so that the FrameSet is inverted), then its Frames are
+            considered in reverse order. (Note that this still means that
+            the base Frame is considered first and the current Frame
+            last, because the Invert value will also cause these Frames
+            to swap places.)
+
+            \sstitem
+            All source Frames are first considered (in the appropriate
+            order) for conversion to the first destination Frame. If no
+            suitable intermediate coordinate system emerges, they are
+            then considered again for conversion to the second
+            destination Frame (in the appropriate order), and so on.
+
+            \sstitem
+            Generally, the first suitable intermediate coordinate
+            system found is used. However, the overall Mapping between
+            the source and destination coordinate systems is also
+            examined.  Preference is given to cases where both the
+            forward and inverse transformations are defined (as indicated
+            by the \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse} attributes). If only one
+            transformation is defined, the forward one is preferred.
+
+            \sstitem
+            If the domain of the intermediate coordinate system matches
+            the current \texttt{"} domainlist\texttt{"}  field, the conversion path is
+            accepted. Otherwise, the next \texttt{"} domainlist\texttt{"}  field is considered
+            and the process repeated.
+
+         }
+         If conversion is possible, the Base attributes of the two
+         FrameSets will be modified on exit to identify the Frames
+         used to access the intermediate coordinate system which was
+         finally accepted.
+
+         Note that it is possible to force a particular Frame within a
+         FrameSet to be used as the basis for the intermediate
+         coordinate system, if it is suitable, by (a) focussing
+         attention on
+         it by specifying its domain in the \texttt{"} domainlist\texttt{"}  string, or (b)
+         making it the base Frame, since this is always considered
+         first.
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         Alignment occurs within the spectral system and standard of rest
+         given by attributes AlignSystem and AlignStdOfRest.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         Alignment occurs within the time system and time scale given by
+         attributes AlignSystem and \htmlref{AlignTimeScale}{AlignTimeScale}.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astConvert()
+      }{
+         If the requested coordinate conversion is possible, the
+         function returns a pointer to a FrameSet which describes the
+         conversion. Otherwise, a null \htmlref{Object}{Object} pointer (AST\_\_NULL) is
+         returned without error.
+
+         If a FrameSet is returned, it will contain two Frames. Frame
+         number 1 (its base Frame) will describe the source coordinate
+         system, corresponding to the \texttt{"} from\texttt{"}  parameter. Frame number 2
+         (its current Frame) will describe the destination coordinate
+         system, corresponding to the \texttt{"} to\texttt{"}  parameter. The Mapping
+         which inter-relates these two Frames will perform the
+         required conversion between their respective coordinate
+         systems.
+
+         Note that a FrameSet may be used both as a Mapping and as a
+         Frame.  If the result is used as a Mapping (e.g. with
+         \htmlref{astTran2}{astTran2}), then it provides a means of converting coordinates
+         from the source to the destination coordinate system (or
+         vice versa if its inverse transformation is selected). If it
+         is used as a Frame, its attributes will describe the
+         destination coordinate system.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         cvt = astConvert( a, b, \texttt{"} \texttt{"}  );
+      }{
+         Attempts to convert between the coordinate systems represented
+         by \texttt{"} a\texttt{"}  and \texttt{"} b\texttt{"}  (assumed to be Frames). If successful, a FrameSet
+         is returned via the \texttt{"} cvt\texttt{"}  pointer which may be used to apply the
+         conversion to sets of coordinates (e.g. using astTran2).
+      }
+      \sstexamplesubsection{
+         cvt = astConvert( \htmlref{astSkyFrame}{astSkyFrame}(\texttt{"} \texttt{"} ), astSkyFrame(\texttt{"} \htmlref{Equinox}{Equinox}=2005\texttt{"} ), \texttt{"} \texttt{"}  );
+      }{
+         Creates a FrameSet which describes precession in the default
+         FK5 celestial coordinate system between equinoxes J2000 (also
+         the default) and J2005. The returned \texttt{"} cvt\texttt{"}  pointer may then
+         be passed to astTran2 to apply this precession correction to
+         any number of coordinate values given in radians.
+
+         Note that the returned FrameSet also contains information
+         about how to format coordinate values. This means that
+         setting its \htmlref{Report}{Report} attribute to 1 is a simple way to obtain
+         printed output (formatted in sexagesimal notation) to show
+         the coordinate values before and after conversion.
+      }
+      \sstexamplesubsection{
+         cvt = astConvert( a, b, \texttt{"} sky,detector,\texttt{"}  );
+      }{
+         Attempts to convert between the coordinate systems
+         represented by the current Frames of \texttt{"} a\texttt{"}  and \texttt{"} b\texttt{"}
+         (now assumed to be FrameSets), via the intermediate \texttt{"} SKY\texttt{"}
+         coordinate system.  This, by default, is the Domain
+         associated with a celestial coordinate system represented by
+         a \htmlref{SkyFrame}{SkyFrame}.
+
+         If this fails (for example, because either FrameSet lacks
+         celestial coordinate information), then the user-defined
+         \texttt{"} DETECTOR\texttt{"}  coordinate system is used instead. If this also
+         fails, then all other possible ways of achieving conversion
+         are considered before giving up.
+
+         The returned pointer \texttt{"} cvt\texttt{"}  indicates whether conversion was
+         possible and will have the value AST\_\_NULL if it was not. If
+         conversion was possible, \texttt{"} cvt\texttt{"}  will point at a new FrameSet
+         describing the conversion.
+
+         The Base attributes of the two FrameSets
+         will be set by astConvert to indicate which of their Frames was
+         used for the intermediate coordinate system. This means that
+         you can subsequently determine which coordinate system was
+         used by enquiring the Domain attribute of either base Frame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Mapping represented by the returned FrameSet results in
+         alignment taking place in the coordinate system specified by the
+         AlignSystem attribute of the \texttt{"} to\texttt{"}  Frame. See the description of the
+         AlignSystem attribute for further details.
+
+         \sstitem
+         When aligning (say) two images, which have been calibrated by
+         attaching FrameSets to them, it is usually necessary to convert
+         between the base Frames (representing \texttt{"} native\texttt{"}  pixel
+         coordinates) of both FrameSets. This may be achieved by
+         inverting the FrameSets (e.g. using \htmlref{astInvert}{astInvert}) so as to
+         interchange their base and current Frames before using
+         astConvert.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astConvex$<$X$>$
+}{
+   Create a new Polygon representing the convex hull of a 2D data grid
+}{
+   \sstdescription{
+      This is a set of functions that create the shortest \htmlref{Polygon}{Polygon} that
+      encloses all pixels with a specified value within a gridded
+      2-dimensional data array (e.g. an image).
+
+      A basic 2-dimensional \htmlref{Frame}{Frame} is used to represent the pixel coordinate
+      system in the returned Polygon. The \htmlref{Domain}{Domain} attribute is set to
+      \texttt{"} PIXEL\texttt{"} , the \htmlref{Title}{Title} attribute is set to \texttt{"} Pixel coordinates\texttt{"} , and the
+      Unit attribute for each axis is set to \texttt{"} pixel\texttt{"} . All other
+      attributes are left unset. The nature of the pixel coordinate system
+      is determined by parameter
+      \texttt{"} starpix\texttt{"} .
+
+      You should use a function which matches the numerical type of the
+      data you are processing by replacing $<$X$>$ in the generic function
+      name
+      astConvex$<$X$>$
+      by an appropriate 1- or 2-character type code. For example, if you
+      are procesing data with type
+      \texttt{"} float\texttt{"} , you should use the function astConvexF
+      (see the \texttt{"} Data Type Codes\texttt{"}  section below for the codes appropriate to
+      other numerical types).
+   }
+   \sstsynopsis{
+      AstPolygon $*$astConvex$<$X$>$( $<$Xtype$>$ value, int oper, const $<$Xtype$>$ array[],
+                                 const int lbnd[2], const int ubnd[2], int starpix )
+   }
+   \sstparameters{
+      \sstsubsection{
+         value
+      }{
+         A data value that specifies the pixels to be included within the
+         convex hull.
+      }
+      \sstsubsection{
+         oper
+      }{
+         Indicates how the
+         \texttt{"} value\texttt{"}
+         parameter is used to select the required pixels. It can
+         have any of the following values:
+         \sstitemlist{
+
+            \sstitem
+            AST\_\_LT: include pixels with value less than \texttt{"} value\texttt{"} .
+
+            \sstitem
+            AST\_\_LE: include pixels with value less than or equal to \texttt{"} value\texttt{"} .
+
+            \sstitem
+            AST\_\_EQ: include pixels with value equal to \texttt{"} value\texttt{"} .
+
+            \sstitem
+            AST\_\_NE: include pixels with value not equal to \texttt{"} value\texttt{"} .
+
+            \sstitem
+            AST\_\_GE: include pixels with value greater than or equal to \texttt{"} value\texttt{"} .
+
+            \sstitem
+            AST\_\_GT: include pixels with value greater than \texttt{"} value\texttt{"} .
+         }
+      }
+      \sstsubsection{
+         array
+      }{
+         Pointer to a
+         2-dimensional array containing the data to be processed.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using astConvexF, the type of each array element
+         should be \texttt{"} float\texttt{"} ).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the second dimension least rapidly
+         (i.e. Fortran array indexing is used).
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an array of two integers
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an array of two integers
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"}  together define the shape
+         and size of the input grid, its extent along a particular
+         (j\texttt{'} th) dimension being ubnd[j]-lbnd[j]$+$1 (assuming the
+         index \texttt{"} j\texttt{"}  to be zero-based). They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre or upper corner, as selected by parameter
+         \texttt{"} starpix\texttt{"} .
+      }
+      \sstsubsection{
+         starpix
+      }{
+         A flag indicating the nature of the pixel coordinate system used
+         to describe the vertex positions in the returned Polygon. If
+         non-zero,
+         the standard Starlink definition of pixel coordinate is used in
+         which a pixel with integer index I spans a range of pixel coordinate
+         from (I-1) to I (i.e. pixel corners have integral pixel coordinates).
+         If zero,
+         the definition of pixel coordinate used by other AST functions
+         such as astResample, astMask,
+         etc., is used. In this definition, a pixel with integer index I
+         spans a range of pixel coordinate from (I-0.5) to (I$+$0.5) (i.e.
+         pixel centres have integral pixel coordinates).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astConvex$<$X$>$()
+      }{
+         A pointer to the required Polygon.
+         NULL
+         is returned without error if the array contains no pixels that
+         satisfy the criterion specified by
+         \texttt{"} value\texttt{"}  and \texttt{"} oper\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         NULL
+         will be returned if this function is invoked with the global
+         error status set, or if it should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate masking function, you should
+      replace $<$X$>$ in the generic function name astConvex$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         L: long int
+
+         \sstitem
+         UL: unsigned long int
+
+         \sstitem
+         I: int
+
+         \sstitem
+         UI: unsigned int
+
+         \sstitem
+         S: short int
+
+         \sstitem
+         US: unsigned short int
+
+         \sstitem
+         B: byte (signed char)
+
+         \sstitem
+         UB: unsigned byte (unsigned char)
+
+      }
+      For example, astConvexD would be used to process \texttt{"} double\texttt{"}
+      data, while astConvexS would be used to process \texttt{"} short int\texttt{"}
+      data, etc.
+   }
+}
+\sstroutine{
+   astCopy
+}{
+   Copy an Object
+}{
+   \sstdescription{
+      This function creates a copy of an \htmlref{Object}{Object} and returns a pointer
+      to the resulting new Object. It makes a \texttt{"} deep\texttt{"}  copy, which
+      contains no references to any other Object (i.e. if the original
+      Object contains references to other Objects, then the actual
+      data are copied, not simply the references). This means that
+      modifications may safely be made to the copy without indirectly
+      affecting any other Object.
+   }
+   \sstsynopsis{
+      AstObject $*$astCopy( const AstObject $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object to be copied.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astCopy()
+      }{
+         Pointer to the new Object.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astCurrentTime
+}{
+   Return the current system time
+}{
+   \sstdescription{
+      This function
+      returns the current system time, represented in the form specified
+      by the supplied \htmlref{TimeFrame}{TimeFrame}. That is, the returned floating point
+      value should be interpreted using the attribute values of the
+      TimeFrame. This includes \htmlref{System}{System}, \htmlref{TimeOrigin}{TimeOrigin}, \htmlref{LTOffset}{LTOffset}, \htmlref{TimeScale}{TimeScale},
+      and Unit.
+   }
+   \sstsynopsis{
+      double astCurrentTime( AstTimeFrame $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the TimeFrame.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astCurrentTime()
+      }{
+         A TimeFrame axis value representing the current system time.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Values of AST\_\_BAD will be returned if this function is
+         invoked with the AST error status set, or if it should fail for
+         any reason.
+
+         \sstitem
+         It is assumes that the system time (returned by the C time()
+         function) follows the POSIX standard, representing a continuous
+         monotonic increasing count of SI seconds since the epoch 00:00:00
+         UTC 1 January 1970 AD (equivalent to TAI with a constant offset).
+         Resolution is one second.
+
+         \sstitem
+         An error will be reported if the TimeFrame has a TimeScale value
+         which cannot be converted to TAI (e.g. \texttt{"} angular\texttt{"}  systems such as
+         UT1, GMST, LMST and LAST).
+
+         \sstitem
+         Any inaccuracy in the system clock will be reflected in the value
+         returned by this function.
+      }
+   }
+}
+\sstroutine{
+   astCurve
+}{
+   Draw a geodesic curve
+}{
+   \sstdescription{
+      This function draws a geodesic curve between two points in the
+      physical coordinate system of a \htmlref{Plot}{Plot}.  The curve drawn is the
+      path of shortest distance joining the two points (as defined by
+      the \htmlref{astDistance}{astDistance} function for the current \htmlref{Frame}{Frame} of the Plot).
+      For example, if the current Frame is a basic Frame, then the
+      curve joining the two points will be a straight line in physical
+      coordinate space.  If the current Frame is more specialised and
+      describes, for instance, a sky coordinate system, then the
+      geodesic curve would be a great circle in physical coordinate
+      space passing through the two sky positions given.
+
+      Note that the geodesic curve is transformed into graphical
+      coordinate space for plotting, so that a straight line in
+      physical coordinates may result in a curved line being drawn if
+      the \htmlref{Mapping}{Mapping} involved is non-linear. Any discontinuities in the
+      Mapping between physical and graphical coordinates are
+      catered for, as is any clipping established using \htmlref{astClip}{astClip}.
+
+      If you need to draw many geodesic curves end-to-end, then the
+      \htmlref{astPolyCurve}{astPolyCurve} function is equivalent to repeatedly using
+      astCurve, but will usually be more efficient.
+
+      If you need to draw curves which are not geodesics, see \htmlref{astGenCurve}{astGenCurve}
+      or \htmlref{astGridLine}{astGridLine}.
+   }
+   \sstsynopsis{
+      void astCurve( AstPlot $*$this, const double start[],
+                                    const double finish[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         start
+      }{
+         An array, with one element for each axis of the Plot, giving
+         the physical coordinates of the first point on the geodesic
+         curve.
+      }
+      \sstsubsection{
+         finish
+      }{
+         An array, with one element for each axis of the Plot, giving
+         the physical coordinates of the second point on the geodesic
+         curve.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No curve is drawn if either of the \texttt{"} start\texttt{"}  or \texttt{"} finish\texttt{"}  arrays
+         contains any coordinates with the value AST\_\_BAD.
+
+         \sstitem
+         An error results if the base Frame of the Plot is not 2-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   astDSBSpecFrame
+}{
+   Create a DSBSpecFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{DSBSpecFrame}{DSBSpecFrame} and optionally initialises its
+      attributes.
+
+      A DSBSpecFrame is a specialised form of \htmlref{SpecFrame}{SpecFrame} which represents
+      positions in a spectrum obtained using a dual sideband instrument.
+      Such an instrument produces a spectrum in which each point contains
+      contributions from two distinctly different frequencies, one from
+      the \texttt{"} lower side band\texttt{"}  (LSB) and one from the \texttt{"} upper side band\texttt{"}  (USB).
+      Corresponding LSB and USB frequencies are connected by the fact
+      that they are an equal distance on either side of a fixed central
+      frequency known as the \texttt{"} Local Oscillator\texttt{"}  (LO) frequency.
+
+      When quoting a position within such a spectrum, it is necessary to
+      indicate whether the quoted position is the USB position or the
+      corresponding LSB position. The \htmlref{SideBand}{SideBand} attribute provides this
+      indication. Another option that the SideBand attribute provides is
+      to represent a spectral position by its topocentric offset from the
+      LO frequency.
+
+      In practice, the LO frequency is specified by giving the distance
+      from the LO frequency to some \texttt{"} central\texttt{"}  spectral position. Typically
+      this central position is that of some interesting spectral feature.
+      The distance from this central position to the LO frequency is known
+      as the \texttt{"} intermediate frequency\texttt{"}  (\htmlref{IF}{IF}). The value supplied for IF can
+      be a signed value in order to indicate whether the LO frequency is
+      above or below the central position.
+   }
+   \sstsynopsis{
+      AstDSBSpecFrame $*$astDSBSpecFrame( const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new DSBSpecFrame. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astDSBSpecFrame()
+      }{
+         A pointer to the new DSBSpecFrame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astDecompose
+}{
+   Decompose a Mapping into two component Mappings
+}{
+   \sstdescription{
+      This function returns pointers to two Mappings which, when applied
+      either in series or parallel, are equivalent to the supplied \htmlref{Mapping}{Mapping}.
+
+      Since the \htmlref{Frame}{Frame} class inherits from the Mapping class, Frames can
+      be considered as special types of Mappings and so this method can
+      be used to decompose either CmpMaps or CmpFrames.
+   }
+   \sstsynopsis{
+      void astDecompose( AstMapping $*$this, AstMapping $*$$*$map1,
+                         AstMapping $*$$*$map2, int $*$series, int $*$invert1,
+                         int $*$invert2  )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping.
+      }
+      \sstsubsection{
+         map1
+      }{
+         Address of a location to receive a pointer to first component
+         Mapping.
+      }
+      \sstsubsection{
+         map2
+      }{
+         Address of a location to receive a pointer to second component
+         Mapping.
+      }
+      \sstsubsection{
+         series
+      }{
+         Address of a location to receive a value indicating if the
+         component Mappings are applied in series or parallel. A non-zero
+         value means that the supplied Mapping is equivalent to applying map1
+         followed by map2 in series. A zero value means that the supplied
+         Mapping is equivalent to applying map1 to the lower numbered axes
+         and map2 to the higher numbered axes, in parallel.
+      }
+      \sstsubsection{
+         invert1
+      }{
+         The value of the \htmlref{Invert}{Invert} attribute to be used with map1.
+      }
+      \sstsubsection{
+         invert2
+      }{
+         The value of the Invert attribute to be used with map2.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         If the supplied Mapping is a CmpMap, then map1 and map2 will be
+         returned holding pointers to the component Mappings used to
+         create the CmpMap, either in series or parallel. Note, changing
+         the Invert attribute of either of the component Mappings using
+         the returned pointers will have no effect on the supplied CmpMap.
+         This is because the CmpMap remembers and uses the original settings
+         of the Invert attributes (that is, the values of the Invert
+         attributes when the CmpMap was first created). These are the
+         Invert values which are returned in invert1 and invert2.
+      }
+      \sstsubsection{
+         \htmlref{TranMap}{TranMap}
+      }{
+         If the supplied Mapping is a TranMap, then map1 and map2 will be
+         returned holding pointers to the forward and inverse Mappings
+         represented by the TranMap (zero will be returned for
+         series).
+         Note, changing the Invert attribute of
+         either of the component Mappings using the returned pointers will
+         have no effect on the supplied TranMap. This is because the TranMap
+         remembers and uses the original settings of the Invert attributes
+         (that is, the values of the Invert attributes when the TranMap was
+         first created). These are the
+         Invert values which are returned in invert1 and invert2.
+      }
+      \sstsubsection{
+         Mapping
+      }{
+         For any class of Mapping other than a CmpMap, map1 will be
+         returned holding a clone of the supplied Mapping pointer, and map2
+         will be returned holding a NULL pointer. Invert1 will be returned
+         holding the current value of the Invert attribute for the supplied
+         Mapping, and invert2 will be returned holding zero.
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         If the supplied Mapping is a CmpFrame, then map1 and map2 will be
+         returned holding pointers to the component Frames used to
+         create the CmpFrame. The component Frames are considered to be in
+         applied in parallel.
+      }
+      \sstsubsection{
+         Frame
+      }{
+         For any class of Frame other than a CmpFrame, map1 will be
+         returned holding a clone of the supplied Frame pointer, and map2
+         will be returned holding a NULL pointer.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned Invert values should be used in preference to the
+         current values of the Invert attribute in map1 and map2. This is
+         because the attributes may have changed value since the Mappings
+         were combined.
+
+         \sstitem
+         Any changes made to the component Mappings using the returned
+         pointers will be reflected in the supplied Mapping.
+      }
+   }
+}
+\sstroutine{
+   astDelFits
+}{
+   Delete the current FITS card in a FitsChan
+}{
+   \sstdescription{
+      This function deletes the current FITS card from a \htmlref{FitsChan}{FitsChan}. The
+      current card may be selected using the \htmlref{Card}{Card} attribute (if its index
+      is known) or by using \htmlref{astFindFits}{astFindFits} (if only the FITS keyword is
+      known).
+
+      After deletion, the following card becomes the current card.
+   }
+   \sstsynopsis{
+      void astDelFits( AstFitsChan $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function returns without action if the FitsChan is
+         initially positioned at the \texttt{"} end-of-file\texttt{"}  (i.e. if the Card
+         attribute exceeds the number of cards in the FitsChan).
+
+         \sstitem
+         If there are no subsequent cards in the FitsChan, then the
+         Card attribute is left pointing at the \texttt{"} end-of-file\texttt{"}  after
+         deletion (i.e. is set to one more than the number of cards in
+         the FitsChan).
+      }
+   }
+}
+\sstroutine{
+   astDelete
+}{
+   Delete an Object
+}{
+   \sstdescription{
+      This function deletes an \htmlref{Object}{Object}, freeing all resources
+      associated with it and rendering any remaining pointers to the
+      Object invalid.
+
+      Note that deletion is unconditional, regardless of whether other
+      pointers to the Object are still in use (possibly within other
+      Objects). A safer approach is to defer deletion, until all
+      references to an Object have expired, by using \htmlref{astBegin}{astBegin}/\htmlref{astEnd}{astEnd}
+      (together with \htmlref{astClone}{astClone} and \htmlref{astAnnul}{astAnnul} if necessary).
+   }
+   \sstsynopsis{
+      AstObject $*$astDelete( AstObject $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object to be deleted.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astDelete()
+      }{
+         A null Object pointer (AST\_\_NULL) is always returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function attempts to execute even if the AST error status
+         is set
+         on entry, although no further error report will be
+         made if it subsequently fails under these circumstances.
+      }
+   }
+}
+\sstroutine{
+   astDistance
+}{
+   Calculate the distance between two points in a Frame
+}{
+   \sstdescription{
+      This function finds the distance between two points whose \htmlref{Frame}{Frame}
+      coordinates are given. The distance calculated is that along
+      the geodesic curve that joins the two points.
+
+      For example, in a basic Frame, the distance calculated will be
+      the Cartesian distance along the straight line joining the two
+      points. For a more specialised Frame describing a sky coordinate
+      system, however, it would be the distance along the great circle
+      passing through two sky positions.
+   }
+   \sstsynopsis{
+      double astDistance( AstFrame $*$this,
+                          const double point1[], const double point2[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         point1
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute) containing the coordinates of the first point.
+      }
+      \sstsubsection{
+         point2
+      }{
+         An array of double, with one element for each Frame axis
+         containing the coordinates of the second point.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astDistance
+      }{
+         The distance between the two points.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function will return a \texttt{"} bad\texttt{"}  result value (AST\_\_BAD) if
+         any of the input coordinates has this value.
+
+         \sstitem
+         A \texttt{"} bad\texttt{"}  value will also be returned if this function is
+         invoked with the AST error status set, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   astDownsize
+}{
+   Reduce the number of vertices in a Polygon
+}{
+   \sstdescription{
+      This function returns a pointer to a new \htmlref{Polygon}{Polygon} that contains a
+      subset of the vertices in the supplied Polygon. The subset is
+      chosen so that the returned Polygon is a good approximation to
+      the supplied Polygon, within the limits specified by the supplied
+      parameter values. That is, the density of points in the returned
+      Polygon is greater at points where the curvature of the boundary of
+      the supplied Polygon is greater.
+   }
+   \sstsynopsis{
+      AstPolygon $*$astDownsize( AstPolygon $*$this, double maxerr, int maxvert )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Polygon.
+      }
+      \sstsubsection{
+         maxerr
+      }{
+         The maximum allowed discrepancy between the supplied and
+         returned Polygons, expressed as a geodesic distance within the
+         Polygon\texttt{'} s coordinate frame. If this is zero or less, the
+         returned Polygon will have the number of vertices specified by
+         maxvert.
+      }
+      \sstsubsection{
+         maxvert
+      }{
+         The maximum allowed number of vertices in the returned Polygon.
+         If this is less than 3, the number of vertices in the returned
+         Polygon will be the minimum needed to achieve the maximum
+         discrepancy specified by
+         maxerr.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astDownsize()
+      }{
+         Pointer to the new Polygon.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astEBuf
+}{
+   End the current graphical buffering context
+}{
+   \sstdescription{
+      This function
+      ends the current graphics buffering context. It should match a
+      corresponding call to the
+      \htmlref{astBBuf}{astBBuf} function.
+   }
+   \sstsynopsis{
+      void astEBuf( AstPlot $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Plot}{Plot}.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The nature of the buffering is determined by the underlying
+         graphics system (as defined by the current grf module). Each call
+         to this function
+         simply invokes the astGEBuf function in the grf module.
+      }
+   }
+}
+\sstroutine{
+   astEllipse
+}{
+   Create a Ellipse
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Ellipse}{Ellipse} and optionally initialises its
+      attributes.
+
+      A Ellipse is a \htmlref{Region}{Region} which represents a elliptical area within the
+      supplied 2-dimensional \htmlref{Frame}{Frame}.
+   }
+   \sstsynopsis{
+      AstEllipse $*$astEllipse( AstFrame $*$frame, int form, const double centre[2],
+                              const double point1[2], const double point2[2],
+                              AstRegion $*$unc, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         A pointer to the Frame in which the region is defined. It must
+         have exactly 2 axes. A deep copy is taken of the supplied Frame.
+         This means that any subsequent changes made to the Frame using the
+         supplied pointer will have no effect the Region.
+      }
+      \sstsubsection{
+         form
+      }{
+         Indicates how the ellipse is described by the remaining parameters.
+         A value of zero indicates that the ellipse is specified by a
+         centre position and two positions on the circumference. A value of
+         one indicates that the ellipse is specified by its centre position,
+         the half-lengths of its two axes, and the orientation of its first
+         axis.
+      }
+      \sstsubsection{
+         centre
+      }{
+         An array of 2 doubles,
+         containing the coordinates at the centre of
+         the ellipse.
+      }
+      \sstsubsection{
+         point1
+      }{
+         An array of 2 doubles. If \texttt{"} form\texttt{"}
+         is zero, this array should contain the coordinates of one of the four
+         points where an axis of the ellipse crosses the circumference of the
+         ellipse.
+         If \texttt{"} form\texttt{"}
+         is one, it should contain the lengths of semi-major and
+         semi-minor axes of the ellipse, given as geodesic distances
+         within the Frame.
+      }
+      \sstsubsection{
+         point2
+      }{
+         An array of 2 doubles. If \texttt{"} form\texttt{"}
+         is zero, this array should containing the coordinates at some other
+         point on the circumference of the ellipse, distinct from
+         \texttt{"} point1\texttt{"} . If \texttt{"} form\texttt{"}
+         is one, the first element of this array should hold the angle
+         between the second axis of the Frame and the first ellipse axis
+         (i.e. the ellipse axis which is specified first in the
+         \texttt{"} point1\texttt{"}
+         array), and the second element will be ignored. The angle should be
+         given in radians, measured positive in the same sense as rotation
+         from the positive direction of the second Frame axis to the positive
+         direction of the first Frame axis.
+      }
+      \sstsubsection{
+         unc
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with the boundary of the Ellipse being created.
+         The uncertainty in any point on the boundary of the Ellipse is found by
+         shifting the supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at
+         the boundary point being considered. The area covered by the
+         shifted uncertainty Region then represents the uncertainty in the
+         boundary position. The uncertainty is assumed to be the same for
+         all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. \htmlref{Box}{Box}, \htmlref{Circle}{Circle}, Ellipse, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Ellipse. Alternatively,
+         a NULL \htmlref{Object}{Object} pointer
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the Ellipse being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{astOverlap}{astOverlap}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{astSimplify}{astSimplify}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Ellipse. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astEllipse()
+      }{
+         A pointer to the new Ellipse.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astEllipsePars
+}{
+   Returns the geometric parameters of an Ellipse
+}{
+   \sstdescription{
+      This function
+      returns the geometric parameters describing the supplied ellipse.
+   }
+   \sstsynopsis{
+      void astEllipsePars( AstEllipse $*$this, double centre[2], double $*$a,
+                           double $*$b, double $*$angle, double p1[2], double p2[2] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         centre
+      }{
+         The coordinates of the \htmlref{Ellipse}{Ellipse} centre are returned in this arrays.
+      }
+      \sstsubsection{
+         a
+      }{
+         Returned holding the half-length of the first axis of the
+         ellipse.
+      }
+      \sstsubsection{
+         b
+      }{
+         Returned holding the half-length of the second axis of the
+         ellipse.
+      }
+      \sstsubsection{
+         angle
+      }{
+         If the coordinate system in which the Ellipse is defined has
+         axes (X,Y), then
+         \texttt{"} $*$angle\texttt{"}
+         is returned holding the angle from the positive direction of
+         the Y axis to the first axis of the ellipse, in radians.
+         Positive rotation is in the same sense as rotation from the
+         positive direction of Y to the positive direction of X.
+      }
+      \sstsubsection{
+         p1
+      }{
+         An array in which to return the coordinates at one of the two ends
+         of the first axis  of the ellipse.
+         A NULL pointer can be supplied if these coordinates are not needed.
+      }
+      \sstsubsection{
+         p2
+      }{
+         An array in which to return the coordinates at one of the two ends
+         of the second axis  of the ellipse.
+         A NULL pointer can be supplied if these coordinates are not needed.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the coordinate system represented by the Ellipse has been
+         changed since it was first created, the returned parameters refer
+         to the new (changed) coordinate system, rather than the original
+         coordinate system. Note however that if the transformation from
+         original to new coordinate system is non-linear, the shape
+         represented by the supplied Ellipse object may not be an accurate
+         ellipse.
+
+         \sstitem
+         Values of AST\_\_BAD are returned for the parameters without error
+         if the ellipse is degenerate or undefined.
+      }
+   }
+}
+\sstroutine{
+   astEmptyFits
+}{
+   Delete all cards in a FitsChan
+}{
+   \sstdescription{
+      This function
+      deletes all cards and associated information from a \htmlref{FitsChan}{FitsChan}.
+   }
+   \sstsynopsis{
+      void astEmptyFits( AstFitsChan $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This method simply deletes the cards currently in the FitsChan.
+         Unlike \htmlref{astWriteFits}{astWriteFits},
+         they are not first written out to the sink function or sink file.
+
+         \sstitem
+         Any Tables or warnings stored in the FitsChan are also deleted.
+
+         \sstitem
+         This method attempt to execute even if an error has occurred
+         previously.
+      }
+   }
+}
+\sstroutine{
+   astEnd
+}{
+   End an AST context
+}{
+   \sstdescription{
+      This macro invokes a function to end an AST context which was
+      begun with a matching invocation of \htmlref{astBegin}{astBegin}. Any \htmlref{Object}{Object}
+      pointers created within this context will be annulled (just as
+      if \htmlref{astAnnul}{astAnnul} had been invoked) and will cease to be valid
+      afterwards, unless they have previously been exported using
+      \htmlref{astExport}{astExport} or rendered exempt using \htmlref{astExempt}{astExempt}.
+      If annulling a pointer causes an Object\texttt{'} s \htmlref{RefCount}{RefCount} attribute to
+      fall to zero (which happens when the last pointer to it is
+      annulled), then the Object will be deleted.
+   }
+   \sstsynopsis{
+      void astEnd
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This macro applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         astEnd attempts to execute even if the AST error status is set.
+
+         \sstitem
+         Contexts delimited by astBegin and astEnd may be nested to any
+         depth.
+      }
+   }
+}
+\sstroutine{
+   astEscapes
+}{
+   Control whether graphical escape sequences are included in strings
+}{
+   \sstdescription{
+      The \htmlref{Plot}{Plot} class defines a set of escape sequences which can be
+      included within a text string in order to control the appearance of
+      sub-strings within the text. See the \htmlref{Escape}{Escape} attribute for a
+      description of these escape sequences. It is usually inappropriate
+      for AST to return strings containing such escape sequences when
+      called by application code. For instance, an application which
+      displays the value of the \htmlref{Title}{Title} attribute of a \htmlref{Frame}{Frame} usually does
+      not want the displayed string to include potentially long escape
+      sequences which a human read would have difficuly interpreting.
+      Therefore the default behaviour is for AST to strip out such escape
+      sequences when called by application code. This default behaviour
+      can be changed using this function.
+   }
+   \sstsynopsis{
+      int astEscapes( int new\_value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         new\_value
+      }{
+         A flag which indicates if escapes sequences should be included
+         in returned strings. If zero is supplied, escape sequences will
+         be stripped out of all strings returned by any AST function. If
+         a positive value is supplied, then any escape sequences will be
+         retained in the value returned to the caller. If a negative
+         value is supplied, the current value of the flag will be left
+         unchanged.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Object}{Object}
+      }{
+         This macro applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astEscapes
+      }{
+         The value of the flag on entry to this function.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function also controls whether the
+         \htmlref{astStripEscapes}{astStripEscapes}
+         function removes escape sequences from the supplied string, or
+         returns the supplied string without change.
+
+         \sstitem
+         This function attempts to execute even if an error has already
+         occurred.
+      }
+   }
+}
+\sstroutine{
+   astExempt
+}{
+   Exempt an Object pointer from AST context handling
+}{
+   \sstdescription{
+      This function exempts an \htmlref{Object}{Object} pointer from AST context
+      handling, as implemented by \htmlref{astBegin}{astBegin} and \htmlref{astEnd}{astEnd}. This means that
+      the pointer will not be affected when astEnd is invoked and will
+      remain active until the end of the program, or until explicitly
+      annulled using \htmlref{astAnnul}{astAnnul}.
+
+      If possible, you should avoid using this function when writing
+      applications. It is provided mainly for developers of other
+      libraries, who may wish to retain references to AST Objects in
+      internal data structures, and who therefore need to avoid the
+      effects of astBegin and astEnd.
+   }
+   \sstsynopsis{
+      void astExempt( AstObject $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Object pointer to be exempted from context handling.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+}
+\sstroutine{
+   astExport
+}{
+   Export an Object pointer to an outer context
+}{
+   \sstdescription{
+      This function exports an \htmlref{Object}{Object} pointer from the current AST context
+      into the context that encloses the current one. This means that
+      the pointer will no longer be annulled when the current context
+      is ended (with \htmlref{astEnd}{astEnd}), but only when the next outer context (if
+      any) ends.
+   }
+   \sstsynopsis{
+      void astExport( AstObject $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Object pointer to be exported.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         It is only sensible to apply this function to pointers that
+         have been created within (or exported to) the current context
+         and have not been rendered exempt using \htmlref{astExempt}{astExempt}.
+         Applying it to an unsuitable Object pointer has no effect.
+      }
+   }
+}
+\sstroutine{
+   astFindFits
+}{
+   Find a FITS card in a FitsChan by keyword
+}{
+   \sstdescription{
+      This function searches for a card in a \htmlref{FitsChan}{FitsChan} by keyword. The
+      search commences at the current card (identified by the \htmlref{Card}{Card}
+      attribute) and ends when a card is found whose FITS keyword
+      matches the template supplied, or when the last card in the
+      FitsChan has been searched.
+
+      If the search is successful (i.e. a card is found which matches
+      the template), the contents of the card are (optionally)
+      returned and the Card attribute is adjusted to identify the card
+      found or, if required, the one following it. If the search is
+      not successful, the function returns zero and the Card attribute
+      is set to the \texttt{"} end-of-file\texttt{"} .
+   }
+   \sstsynopsis{
+      int astFindFits( AstFitsChan $*$this, const char $*$name, char card[ 81 ],
+                       int inc )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         name
+      }{
+         Pointer to a null-terminated character string containing a
+         template for the keyword to be found. In the simplest case,
+         this should simply be the keyword name (the search is case
+         insensitive and trailing spaces are ignored). However, this
+         template may also contain \texttt{"} field specifiers\texttt{"}  which are
+         capable of matching a range of characters (see the \texttt{"} Keyword
+         Templates\texttt{"}  section for details). In this case, the first card
+         with a keyword which matches the template will be found. To
+         find the next FITS card regardless of its keyword, you should
+         use the template \texttt{"} \%f\texttt{"} .
+      }
+      \sstsubsection{
+         card
+      }{
+         An array of at least 81 characters (to allow room for a
+         terminating null)
+         in which the FITS card which is found will be returned.  If
+         the search is not successful (or a NULL pointer is given), a
+         card will not be returned.
+      }
+      \sstsubsection{
+         inc
+      }{
+         If this value is zero (and the search is successful), the
+         FitsChan\texttt{'} s Card attribute will be set to the index of the card
+         that was found. If it is non-zero, however, the Card
+         attribute will be incremented to identify the card which
+         follows the one found.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFindFits()
+      }{
+         One if the search was successful, otherwise zero.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         result = astFindFits( fitschan, \texttt{"} \%f\texttt{"} , card, 1 );
+      }{
+         Returns the current card in a FitsChan and advances the Card
+         attribute to identify the card that follows (the \texttt{"} \%f\texttt{"}
+         template matches any keyword).
+      }
+      \sstexamplesubsection{
+         result = astFindFits( fitschan, \texttt{"} BITPIX\texttt{"} , card, 1 );
+      }{
+         Searches a FitsChan for a FITS card with the \texttt{"} BITPIX\texttt{"}  keyword
+         and returns that card. The Card attribute is then incremented
+         to identify the card that follows it.
+      }
+      \sstexamplesubsection{
+         result = astFindFits( fitschan, \texttt{"} COMMENT\texttt{"} , NULL, 0 );
+      }{
+         Sets the Card attribute of a FitsChan to identify the next
+         COMMENT card (if any). The card itself is not returned.
+      }
+      \sstexamplesubsection{
+         result = astFindFits( fitschan, \texttt{"} CRVAL\%1d\texttt{"} , card, 1 );
+      }{
+         Searches a FitsChan for the next card with a keyword of the
+         form \texttt{"} CRVALi\texttt{"}  (for example, any of the keywords \texttt{"} CRVAL1\texttt{"} ,
+         \texttt{"} CRVAL2\texttt{"}  or \texttt{"} CRVAL3\texttt{"}  would be matched). The card found (if
+         any) is returned, and the Card attribute is then incremented
+         to identify the following card (ready to search for another
+         keyword with the same form, perhaps).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The search always starts with the current card, as identified
+         by the Card attribute. To ensure you search the entire contents
+         of a FitsChan, you should first clear the Card attribute (using
+         \htmlref{astClear}{astClear}). This effectively \texttt{"} rewinds\texttt{"}  the FitsChan.
+
+         \sstitem
+         If a search is unsuccessful, the Card attribute is set to the
+         \texttt{"} end-of-file\texttt{"}  (i.e. to one more than the number of cards in the
+         FitsChan). No error occurs.
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any
+         reason.
+      }
+   }
+   \sstdiytopic{
+      Keyword Templates
+   }{
+      The templates used to match FITS keywords are normally composed
+      of literal characters, which must match the keyword exactly
+      (apart from case). However, a template may also contain \texttt{"} field
+      specifiers\texttt{"}  which can match a range of possible characters. This
+      allows you to search for keywords that contain (for example)
+      numbers, where the digits comprising the number are not known in
+      advance.
+
+      A field specifier starts with a \texttt{"} \%\texttt{"}  character. This is followed
+      by an optional single digit (0 to 9) specifying a field
+      width. Finally, there is a single character which specifies the
+
+      type of character to be matched, as follows:
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} c\texttt{"} : matches all upper case letters,
+
+         \sstitem
+         \texttt{"} d\texttt{"} : matches all decimal digits,
+
+         \sstitem
+         \texttt{"} f\texttt{"} : matches all characters which are permitted within a FITS
+         keyword (upper case letters, digits, underscores and hyphens).
+
+      }
+      If the field width is omitted, the field specifier matches one
+      or more characters. If the field width is zero, it matches zero
+      or more characters. Otherwise, it matches exactly the number of
+
+      characters specified. In addition to this:
+
+      \sstitemlist{
+
+         \sstitem
+         The template \texttt{"} \%f\texttt{"}  will match a blank FITS keyword consisting
+         of 8 spaces (as well as matching all other keywords).
+
+         \sstitem
+         A template consisting of 8 spaces will match a blank keyword
+         (only).
+
+      }
+      For example:
+
+      \sstitemlist{
+
+         \sstitem
+         The template \texttt{"} BitPix\texttt{"}  will match the keyword \texttt{"} BITPIX\texttt{"}  only.
+
+         \sstitem
+         The template \texttt{"} crpix\%1d\texttt{"}  will match keywords consisting of
+         \texttt{"} CRPIX\texttt{"}  followed by one decimal digit.
+
+         \sstitem
+         The template \texttt{"} P\%c\texttt{"}  will match any keyword starting with \texttt{"} P\texttt{"}
+         and followed by one or more letters.
+
+         \sstitem
+         The template \texttt{"} E\%0f\texttt{"}  will match any keyword beginning with \texttt{"} E\texttt{"} .
+
+         \sstitem
+         The template \texttt{"} \%f\texttt{"}  will match any keyword at all (including a
+         blank one).
+      }
+   }
+}
+\sstroutine{
+   astFindFrame
+}{
+   Find a coordinate system with specified characteristics
+}{
+   \sstdescription{
+      This function uses a \texttt{"} template\texttt{"}  \htmlref{Frame}{Frame} to search another Frame
+      (or \htmlref{FrameSet}{FrameSet}) to identify a coordinate system which has a
+      specified set of characteristics. If a suitable coordinate
+      system can be found, the function returns a pointer to a
+      FrameSet which describes the required coordinate system and how
+      to convert coordinates to and from it.
+
+      This function is provided to help answer general questions about
+      coordinate systems, such as typically arise when coordinate
+      information is imported into a program as part of an initially
+      unknown dataset. For example:
+      \sstitemlist{
+
+         \sstitem
+         Is there a wavelength scale?
+
+         \sstitem
+         Is there a 2-dimensional coordinate system?
+
+         \sstitem
+         Is there a celestial coordinate system?
+
+         \sstitem
+         Can I plot the data in ecliptic coordinates?
+
+      }
+      You can also use this function as a means of reconciling a
+      user\texttt{'} s preference for a particular coordinate system (for
+      example, what type of axes to draw) with what is actually
+      possible given the coordinate information available.
+
+      To perform a search, you supply a \texttt{"} target\texttt{"}  Frame (or FrameSet)
+      which represents the set of coordinate systems to be searched.
+      If a basic Frame is given as the target, this set of coordinate
+      systems consists of the one described by this Frame, plus all
+      other \texttt{"} virtual\texttt{"}  coordinate systems which can potentially be
+      reached from it by applying built-in conversions (for example,
+      any of the celestial coordinate conversions known to the AST
+      library would constitute a \texttt{"} built-in\texttt{"}  conversion). If a FrameSet
+      is given as the target, the set of coordinate systems to be
+      searched consists of the union of those represented by all the
+      individual Frames within it.
+
+      To select from this large set of possible coordinate systems,
+      you supply a \texttt{"} template\texttt{"}  Frame which is an instance of the type
+      of Frame you are looking for. Effectively, you then ask the
+      function to \texttt{"} find a coordinate system that looks like this\texttt{"} .
+
+      You can make your request more or less specific by setting
+      attribute values for the template Frame. If a particular
+      attribute is set in the template, then the function will only
+      find coordinate systems which have exactly the same value for
+      that attribute.  If you leave a template attribute un-set,
+      however, then the function has discretion about the value the
+      attribute should have in any coordinate system it finds. The
+      attribute will then take its value from one of the actual
+      (rather than virtual) coordinate systems in the target. If the
+      target is a FrameSet, its \htmlref{Current}{Current} attribute will be modified to
+      indicate which of its Frames was used for this purpose.
+
+      The result of this process is a coordinate system represented by
+      a hybrid Frame which acquires some attributes from the template
+      (but only if they were set) and the remainder from the
+      target. This represents the \texttt{"} best compromise\texttt{"}  between what you
+      asked for and what was available. A \htmlref{Mapping}{Mapping} is then generated
+      which converts from the target coordinate system to this hybrid
+      one, and the returned FrameSet encapsulates all of this
+      information.
+   }
+   \sstsynopsis{
+      AstFrameSet $*$astFindFrame( AstFrame $*$target, AstFrame $*$template,
+                                 const char $*$domainlist )
+   }
+   \sstparameters{
+      \sstsubsection{
+         target
+      }{
+         Pointer to the target Frame (or FrameSet).
+
+         Note that if a FrameSet is supplied (and a suitable
+         coordinate system is found), then its Current attribute will
+         be modified to indicate which Frame was used to obtain
+         attribute values which were not specified by the template.
+         This Frame will, in some sense, represent the \texttt{"} closest\texttt{"}
+         non-virtual coordinate system to the one you requested.
+      }
+      \sstsubsection{
+         template
+      }{
+         Pointer to the template Frame, which should be an instance of
+         the type of Frame you wish to find. If you wanted to find a
+         Frame describing a celestial coordinate system, for example,
+         then you might use a \htmlref{SkyFrame}{SkyFrame} here. See the \texttt{"} Examples\texttt{"}
+         section for more ideas.
+      }
+      \sstsubsection{
+         domainlist
+      }{
+         Pointer to a null-terminated character string containing a
+         comma-separated list of Frame domains. This may be used to
+         establish a priority order for the different types of
+         coordinate system that might be found.
+
+         The function will first try to find a suitable coordinate
+         system whose \htmlref{Domain}{Domain} attribute equals the first domain in this
+         list. If this fails, the second domain in the list will be
+         used, and so on, until a result is obtained. A blank domain
+         (e.g. two consecutive commas) indicates that any coordinate
+         system is acceptable (subject to the template) regardless of
+         its domain.
+
+         This list is case-insensitive and all white space is ignored.
+         If you do not wish to restrict the domain in this way,
+         you should supply an empty string.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         This function applies to all Frames.
+      }
+      \sstsubsection{
+         FrameSet
+      }{
+         If the target is a FrameSet, the possibility exists that
+         several of the Frames within it might be matched by the
+         template.  Unless the choice is sufficiently restricted by
+         the \texttt{"} domainlist\texttt{"}  string, the sequence in which Frames are
+         searched can then become important. In this case, the search
+         proceeds as follows:
+         \sstitemlist{
+
+            \sstitem
+            Each field in the \texttt{"} domainlist\texttt{"}  string is considered in turn.
+
+            \sstitem
+            An attempt is made to match the template to each of the
+            target\texttt{'} s Frames in the order: (1) the current Frame, (2) the
+            base Frame, (3) each remaining Frame in the order of being
+            added to the target FrameSet.
+
+            \sstitem
+            Generally, the first match found is used. However, the
+            Mapping between the target coordinate system and the
+            resulting Frame is also examined. Preference is given to
+            cases where both the forward and inverse transformations are
+            defined (as indicated by the \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse}
+            attributes). If only one transformation is defined, the
+            forward one is preferred.
+
+            \sstitem
+            If a match is found and the domain of the resulting Frame also
+            matches the current \texttt{"} domainlist\texttt{"}  field, it is
+            accepted. Otherwise, the next \texttt{"} domainlist\texttt{"}  field is considered
+            and the process repeated.
+
+         }
+         If a suitable coordinate system is found, the Current
+         attribute of the target FrameSet will be modified on exit to
+         identify the Frame whose match with the target was eventually
+         accepted.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFindFrame()
+      }{
+         If the search is successful, the function returns a pointer
+         to a FrameSet which contains the Frame found and a
+         description of how to convert to (and from) the coordinate
+         system it represents. Otherwise, a null \htmlref{Object}{Object} pointer
+         (AST\_\_NULL) is returned without error.
+
+         If a FrameSet is returned, it will contain two Frames. Frame
+         number 1 (its base Frame) represents the target coordinate
+         system and will be the same as the (base Frame of the)
+         target. Frame number 2 (its current Frame) will be a Frame
+         representing the coordinate system which the function
+         found. The Mapping which inter-relates these two Frames will
+         describe how to convert between their respective coordinate
+         systems.
+
+         Note that a FrameSet may be used both as a Mapping and as a
+         Frame. If the result is used as a Mapping (e.g. with
+         \htmlref{astTran2}{astTran2}), then it provides a means of converting coordinates
+         from the target coordinate system into the new coordinate
+         system that was found (and vice versa if its inverse
+         transformation is selected). If it is used as a Frame, its
+         attributes will describe the new coordinate system.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         result = astFindFrame( target, \htmlref{astFrame}{astFrame}( 3, \texttt{"} \texttt{"}  ), \texttt{"} \texttt{"}  );
+      }{
+         Searches for a 3-dimensional coordinate system in the target
+         Frame (or FrameSet). No attributes have been set in the
+         template Frame (created by astFrame), so no restriction has
+         been placed on the required coordinate system, other than
+         that it should have 3 dimensions. The first suitable Frame
+         found will be returned as part of the \texttt{"} result\texttt{"}  FrameSet.
+      }
+      \sstexamplesubsection{
+         result = astFindFrame( target, \htmlref{astSkyFrame}{astSkyFrame}( \texttt{"} \texttt{"}  ), \texttt{"} \texttt{"}  );
+      }{
+         Searches for a celestial coordinate system in the target
+         Frame (or FrameSet). The type of celestial coordinate system
+         is unspecified, so astFindFrame will return the first one
+         found as part of the \texttt{"} result\texttt{"}  FrameSet. If the target is
+         a FrameSet, then its Current attribute will be updated to
+         identify the Frame that was used.
+
+         If no celestial coordinate system can be found, a value of
+         AST\_\_NULL will be returned without error.
+      }
+      \sstexamplesubsection{
+         result = astFindFrame( target, astSkyFrame( \texttt{"} \htmlref{MaxAxes}{MaxAxes}=100\texttt{"}  ), \texttt{"} \texttt{"}  );
+      }{
+         This is like the last example, except that in the event of the
+         target being a \htmlref{CmpFrame}{CmpFrame}, the component Frames encapsulated by the
+         CmpFrame will be searched for a SkyFrame. If found, the returned
+         Mapping will included a \htmlref{PermMap}{PermMap} which selects the required axes
+         from the target CmpFrame.
+
+         This is acomplished by setting the MaxAxes attribute of the
+         template SkyFrame to a large number (larger than or equal to the
+         number of axes in the target CmpFrame). This allows the SkyFrame
+         to be used as a match for Frames containing from 2 to 100 axes.
+      }
+      \sstexamplesubsection{
+         result = astFindFrame( target, astSkyFrame( \texttt{"} \htmlref{System}{System}=FK5\texttt{"}  ), \texttt{"} \texttt{"}  );
+      }{
+         Searches for an equatorial (FK5) coordinate system in the
+         target. The \htmlref{Equinox}{Equinox} value for the coordinate system has not
+         been specified, so will be obtained from the target. If the
+         target is a FrameSet, its Current attribute will be updated
+         to indicate which SkyFrame was used to obtain this value.
+      }
+      \sstexamplesubsection{
+         result = astFindFrame( target, astFrame( 2, \texttt{"} \texttt{"}  ), \texttt{"} sky,pixel,\texttt{"}  );
+      }{
+         Searches for a 2-dimensional coordinate system in the
+         target. Initially, a search is made for a suitable coordinate
+         system whose Domain attribute has the value \texttt{"} SKY\texttt{"} . If this
+         search fails, a search is then made for one with the domain
+         \texttt{"} PIXEL\texttt{"} . If this also fails, then any 2-dimensional
+         coordinate system is returned as part of the \texttt{"} result\texttt{"}
+         FrameSet.
+
+         Only if no 2-dimensional coordinate systems can be reached by
+         applying built-in conversions to any of the Frames in the
+         target will a value of AST\_\_NULL be returned.
+      }
+      \sstexamplesubsection{
+         result = astFindFrame( target, astFrame( 1, \texttt{"} Domain=WAVELENGTH\texttt{"}  ), \texttt{"} \texttt{"}  );
+      }{
+         Searches for any 1-dimensional coordinate system in the
+         target which has the domain \texttt{"} WAVELENGTH\texttt{"} .
+      }
+      \sstexamplesubsection{
+         result = astFindFrame( target, astFrame( 1, \texttt{"} \texttt{"}  ), \texttt{"} wavelength\texttt{"}  );
+      }{
+         This example has exactly the same effect as that above. It
+         illustrates the equivalence of the template\texttt{'} s Domain attribute
+         and the fields in the \texttt{"} domainlist\texttt{"}  string.
+      }
+      \sstexamplesubsection{
+         result = astFindFrame( target, astFrame( 1, \texttt{"} MaxAxes=3\texttt{"}  ), \texttt{"} \texttt{"}  );
+      }{
+         This is a more advanced example which will search for any
+         coordinate system in the target having 1, 2 or 3
+         dimensions. The Frame returned (as part of the \texttt{"} result\texttt{"}
+         FrameSet) will always be 1-dimensional, but will be related
+         to the coordinate system that was found by a suitable Mapping
+         (e.g. a PermMap) which simply extracts the first axis.
+
+         If we had wanted a Frame representing the actual (1, 2 or
+         3-dimensional) coordinate system found, we could set the
+         \htmlref{PreserveAxes}{PreserveAxes} attribute to a non-zero value in the template.
+      }
+      \sstexamplesubsection{
+         result = astFindFrame( target, astSkyFrame( \texttt{"} \htmlref{Permute}{Permute}=0\texttt{"}  ), \texttt{"} \texttt{"}  );
+      }{
+         Searches for any celestial coordinate system in the target,
+         but only finds one if its axes are in the conventional
+         (longitude,latitude) order and have not been permuted
+         (e.g. with \htmlref{astPermAxes}{astPermAxes}).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Mapping represented by the returned FrameSet results in
+         alignment taking place in the coordinate system specified by the
+         \htmlref{AlignSystem}{AlignSystem} attribute of the \texttt{"} template\texttt{"}  Frame. See the description
+         of the AlignSystem attribute for further details.
+
+         \sstitem
+         Beware of setting the Domain attribute of the template and then
+         using a \texttt{"} domainlist\texttt{"}  string which does not include the template\texttt{'} s domain
+         (or a blank field). If you do so, no coordinate system will be
+         found.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      More on Using Templates
+   }{
+      A Frame (describing a coordinate system) will be found by this
+      function if (a) it is \texttt{"} matched\texttt{"}  by the template you supply, and
+      (b) the value of its Domain attribute appears in the \texttt{"} domainlist\texttt{"}
+      string (except that a blank field in this string permits any
+      domain). A successful match by the template depends on a number
+      of criteria, as outlined below:
+      \sstitemlist{
+
+         \sstitem
+         In general, a template will only match another Frame which
+         belongs to the same class as the template, or to a derived (more
+         specialised) class. For example, a SkyFrame template will match
+         any other SkyFrame, but will not match a basic
+         Frame. Conversely, a basic Frame template will match any class
+         of Frame.
+
+         \sstitem
+         The exception to this is that a Frame of any class can be used to
+         match a CmpFrame, if that CmpFrame contains a Frame of the same
+         class as the template. Note however, the MaxAxes and \htmlref{MinAxes}{MinAxes}
+         attributes of the template must be set to suitable values to allow
+         it to match the CmpFrame. That is, the MinAxes attribute must be
+         less than or equal to the number of axes in the target, and the MaxAxes
+         attribute must be greater than or equal to the number of axes in
+         the target.
+
+         \sstitem
+         If using a CmpFrame as a template frame, the MinAxes and MaxAxes
+         for the template are determined by the MinAxes and MaxAxes values of
+         the component Frames within the template. So if you want a template
+         CmpFrame to be able to match Frames with different numbers of axes,
+         then you must set the MaxAxes and/or MinAxes attributes in the component
+         template Frames, before combining them together into the template
+         CmpFrame.
+
+         \sstitem
+         If a template has a value set for any of its main attributes, then
+         it will only match Frames which have an identical value for that
+         attribute (or which can be transformed, using a built-in
+         conversion, so that they have the required value for that
+         attribute). If any attribute in the template is un-set, however,
+         then Frames are matched regardless of the value they may have
+         for that attribute. You may therefore make a template more or
+         less specific by choosing the attributes for which you set
+         values. This requirement does not apply to \texttt{'} descriptive\texttt{'}  attributes
+         such as titles, labels, symbols, etc.
+
+         \sstitem
+         An important application of this principle involves the Domain
+         attribute. Setting the Domain attribute of the template has the
+         effect of restricting the search to a particular type of Frame
+         (with the domain you specify).  Conversely, if the Domain
+         attribute is not set in the template, then the domain of the
+         Frame found is not relevant, so all Frames are searched.  Note
+         that the
+         \texttt{"} domainlist\texttt{"}  string provides an alternative way of restricting the
+         search in the same manner, but is a more convenient interface if
+         you wish to search automatically for another domain if the first
+         search fails.
+
+         \sstitem
+         Normally, a template will only match a Frame which has the
+         same number of axes as itself. However, for some classes of
+         template, this default behaviour may be changed by means of the
+         MinAxes, MaxAxes and \htmlref{MatchEnd}{MatchEnd} attributes. In addition, the
+         behaviour of a template may be influenced by its Permute and
+         PreserveAxes attributes, which control whether it matches Frames
+         whose axes have been permuted, and whether this permutation is
+         retained in the Frame which is returned (as opposed to returning
+         the axes in the order specified in the template, which is the
+         default behaviour). You should consult the descriptions of these
+         attributes for details of this more advanced use of templates.
+      }
+   }
+}
+\sstroutine{
+   astFitsChan
+}{
+   Create a FitsChan
+}{
+   \sstdescription{
+      This function creates a new \htmlref{FitsChan}{FitsChan} and optionally initialises
+      its attributes.
+
+      A FitsChan is a specialised form of \htmlref{Channel}{Channel} which supports I/O
+      operations involving the use of FITS (Flexible Image Transport
+      \htmlref{System}{System}) header cards. Writing an \htmlref{Object}{Object} to a FitsChan (using
+      \htmlref{astWrite}{astWrite}) will, if the Object is suitable, generate a
+      description of that Object composed of FITS header cards, and
+      reading from a FitsChan will create a new Object from its FITS
+      header card description.
+
+      While a FitsChan is active, it represents a buffer which may
+      contain zero or more 80-character \texttt{"} header cards\texttt{"}  conforming to
+      FITS conventions. Any sequence of FITS-conforming header cards
+      may be stored, apart from the \texttt{"} END\texttt{"}  card whose existence is
+      merely implied.  The cards may be accessed in any order by using
+      the FitsChan\texttt{'} s integer \htmlref{Card}{Card} attribute, which identifies a \texttt{"} current\texttt{"}
+      card, to which subsequent operations apply. Searches
+      based on keyword may be performed (using \htmlref{astFindFits}{astFindFits}), new
+      cards may be inserted (\htmlref{astPutFits}{astPutFits}, \htmlref{astPutCards}{astPutCards}, \htmlref{astSetFits$<$X$>$}{astSetFits$<$X$>$}) and
+      existing ones may be deleted (\htmlref{astDelFits}{astDelFits}) or changed (astSetFits$<$X$>$).
+
+      When you create a FitsChan, you have the option of specifying
+      \texttt{"} source\texttt{"}  and \texttt{"} sink\texttt{"}  functions which connect it to external data
+      stores by reading and writing FITS header cards. If you provide
+      a source function, it is used to fill the FitsChan with header cards
+      when it is accessed for the first time. If you do not provide a
+      source function, the FitsChan remains empty until you explicitly enter
+      data into it (e.g. using astPutFits, astPutCards, astWrite
+      or by using the \htmlref{SourceFile}{SourceFile} attribute to specifying a text file from
+      which headers should be read). When the FitsChan is deleted, any
+      remaining header cards in the FitsChan can be saved in either of
+      two ways: 1) by specifying a value for the \htmlref{SinkFile}{SinkFile} attribute (the
+      name of a text file to which header cards should be written), or 2)
+      by providing a sink function (used to to deliver header cards to an
+      external data store). If you do not provide a sink function or a
+      value for SinkFile, any header cards remaining when the FitsChan
+      is deleted will be lost, so you should arrange to extract them
+      first if necessary
+      (e.g. using astFindFits or \htmlref{astRead}{astRead}).
+
+      Coordinate system information may be described using FITS header
+      cards using several different conventions, termed
+      \texttt{"} encodings\texttt{"} . When an AST Object is written to (or read from) a
+      FitsChan, the value of the FitsChan\texttt{'} s \htmlref{Encoding}{Encoding} attribute
+      determines how the Object is converted to (or from) a
+      description involving FITS header cards. In general, different
+      encodings will result in different sets of header cards to
+      describe the same Object. Examples of encodings include the DSS
+      encoding (based on conventions used by the STScI Digitised Sky
+      Survey data), the FITS-WCS encoding (based on a proposed FITS
+      standard) and the NATIVE encoding (a near loss-less way of
+      storing AST Objects in FITS headers).
+
+      The available encodings differ in the range of Objects they can
+      represent, in the number of Object descriptions that can coexist
+      in the same FitsChan, and in their accessibility to other
+      (external) astronomy applications (see the Encoding attribute
+      for details). Encodings are not necessarily mutually exclusive
+      and it may sometimes be possible to describe the same Object in
+      several ways within a particular set of FITS header cards by
+      using several different encodings.
+
+      The detailed behaviour of astRead and astWrite, when used with
+      a FitsChan, depends on the encoding in use. In general, however,
+      all use of astRead is destructive, so that FITS header cards
+      are consumed in the process of reading an Object, and are
+      removed from the FitsChan (this deletion can be prevented for
+      specific cards by calling the
+      \htmlref{astRetainFits}{astRetainFits} function).
+
+      If the encoding in use allows only a single Object description
+      to be stored in a FitsChan (e.g. the DSS, FITS-WCS and FITS-IRAF
+      encodings), then write operations using astWrite will
+      over-write any existing Object description using that
+      encoding. Otherwise (e.g. the NATIVE encoding), multiple Object
+      descriptions are written sequentially and may later be read
+      back in the same sequence.
+   }
+   \sstsynopsis{
+      AstFitsChan $*$astFitsChan( const char $*$($*$ source)( void ),
+                                void ($*$ sink)( const char $*$ ),
+                                const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         source
+      }{
+         Pointer to a source function which takes no arguments and
+         returns a pointer to a null-terminated string. This function
+         will be used by the FitsChan to obtain input FITS header
+         cards. On each invocation, it should read the next input card
+         from some external source (such as a FITS file), and return a
+         pointer to the (null-terminated) contents of the card. It
+         should return a NULL pointer when there are no more cards to
+         be read.
+
+         If \texttt{"} source\texttt{"}  is NULL, the FitsChan will remain empty until
+         cards are explicitly stored in it (e.g. using astPutCards,
+         astPutFits or via the SourceFile attribute).
+      }
+      \sstsubsection{
+         sink
+      }{
+         Pointer to a sink function that takes a pointer to a
+         null-terminated string as an argument and returns void.  If
+         no value has been set for the SinkFile attribute, this
+         function will be used by the FitsChan to deliver any FITS
+         header cards it contains when it is finally deleted. On
+         each invocation, it should deliver the contents of the character
+         string passed to it as a FITS header card to some external
+         data store (such as a FITS file).
+
+         If \texttt{"} sink\texttt{"}  is NULL,
+         and no value has been set for the SinkFile attribute, the
+         contents of the FitsChan will be lost when it is deleted.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new FitsChan. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+
+         Note, the FITSCHAN\_OPTIONS environment variable may be used
+         to specify default options for all newly created FitsChans.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFitsChan()
+      }{
+         A pointer to the new FitsChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No FITS \texttt{"} END\texttt{"}  card will be written via the sink function. You
+         should add this card yourself after the FitsChan has been
+         deleted.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astFitsTable
+}{
+   Create a FitsTable
+}{
+   \sstdescription{
+      This function creates a new \htmlref{FitsTable}{FitsTable} and optionally initialises
+      its attributes.
+
+      The FitsTable class is a representation of a FITS binary table. It
+      inherits from the \htmlref{Table}{Table} class. The parent Table is used to hold the
+      binary data of the main table, and a \htmlref{FitsChan}{FitsChan} is used to hold the FITS
+      header. Note, there is no provision for binary data following the main
+      table (such data is referred to as a \texttt{"} heap\texttt{"}  in the FITS standard).
+
+      Note - it is not recommended to use the FitsTable class to store
+      very large tables.
+   }
+   \sstsynopsis{
+      AstFitsTable $*$astFitsTable( AstFitsChan $*$header, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         header
+      }{
+         Pointer to an optional FitsChan containing headers to be stored
+         in the FitsTable.
+         NULL
+         may be supplied if the new FitsTable is to be left empty. If
+         supplied, and if the headers describe columns of a FITS binary
+         table, then equivalent (empty) columns are added to the FitsTable.
+         Each column has the same index in the FitsTable that it has in
+         the supplied header.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new FitsTable. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFitsTable()
+      }{
+         A pointer to the new FitsTable.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list described above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astFluxFrame
+}{
+   Create a FluxFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{FluxFrame}{FluxFrame} and optionally initialises
+      its attributes.
+
+      A FluxFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various systems used to represent the signal level in an
+      observation. The particular coordinate system to be used is specified
+      by setting the FluxFrame\texttt{'} s \htmlref{System}{System} attribute qualified, as necessary, by
+      other attributes such as the units, etc (see the description of the
+      System attribute for details).
+
+      All flux values are assumed to be measured at the same frequency or
+      wavelength (as given by the \htmlref{SpecVal}{SpecVal} attribute). Thus this class is
+      more appropriate for use with images rather than spectra.
+   }
+   \sstsynopsis{
+      AstFluxFrame $*$astFluxFrame( double specval, AstSpecFrame $*$specfrm,
+                                  const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         specval
+      }{
+         The spectral value to which the flux values refer, given in the
+         spectral coordinate system specified by
+         \texttt{"} specfrm\texttt{"} . The value supplied for the \texttt{"} specval\texttt{"}
+         parameter becomes the default value for the SpecVal attribute.
+         A value of AST\_\_BAD may be supplied if the spectral position is
+         unknown, but this may result in it not being possible for the
+         \htmlref{astConvert}{astConvert}
+         function to determine a \htmlref{Mapping}{Mapping} between the new FluxFrame and
+         some other FluxFrame.
+      }
+      \sstsubsection{
+         specfrm
+      }{
+         A pointer to a \htmlref{SpecFrame}{SpecFrame} describing the spectral coordinate system
+         in which the
+         \texttt{"} specval\texttt{"}
+         parameter is given. A deep copy of this object is taken, so any
+         subsequent changes to the SpecFrame using the supplied pointer will
+         have no effect on the new FluxFrame.
+         A NULL pointer can be supplied if AST\_\_BAD is supplied for \texttt{"} specval\texttt{"} .
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new FluxFrame. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFluxFrame()
+      }{
+         A pointer to the new FluxFrame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When conversion between two FluxFrames is requested (as when
+         supplying FluxFrames to astConvert),
+         account will be taken of the nature of the flux coordinate systems
+         they represent, together with any qualifying attribute values, including
+         the \htmlref{AlignSystem}{AlignSystem} attribute. The results will therefore fully reflect the
+         relationship between positions measured in the two systems. In addition,
+         any difference in the Unit attributes of the two systems will also be
+         taken into account.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astFormat
+}{
+   Format a coordinate value for a Frame axis
+}{
+   \sstdescription{
+      This function returns a pointer to a string containing the
+      formatted (character) version of a coordinate value for a \htmlref{Frame}{Frame}
+      axis. The formatting applied is determined by the Frame\texttt{'} s
+      attributes and, in particular, by any Format attribute string
+      that has been set for the axis. A suitable default format (based
+      on the Digits attribute value) will be applied if necessary.
+   }
+   \sstsynopsis{
+      const char $*$astFormat( AstFrame $*$this, int axis, double value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         axis
+      }{
+         The number of the Frame axis for which formatting is to be
+         performed (axis numbering starts at 1 for the first axis).
+      }
+      \sstsubsection{
+         value
+      }{
+         The coordinate value to be formatted.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFormat()
+      }{
+         A pointer to a null-terminated string containing the formatted
+         value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned pointer is guaranteed to remain valid and the
+         string to which it points will not be over-written for a total
+         of 50 successive invocations of this function. After this, the
+         memory containing the string may be re-used, so a copy of the
+         string should be made if it is needed for longer than this.
+
+         \sstitem
+         A formatted value may be converted back into a numerical (double)
+         value using \htmlref{astUnformat}{astUnformat}.
+
+         \sstitem
+         A NULL pointer will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   astFrame
+}{
+   Create a Frame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Frame}{Frame} and optionally initialises its
+      attributes.
+
+      A Frame is used to represent a coordinate system. It does this
+      in rather the same way that a frame around a graph describes the
+      coordinate space in which data are plotted. Consequently, a
+      Frame has a \htmlref{Title}{Title} (string) attribute, which describes the
+      coordinate space, and contains axes which in turn hold
+      information such as Label and Units strings which are used for
+      labelling (e.g.) graphical output. In general, however, the
+      number of axes is not restricted to two.
+
+      Functions are available for converting Frame coordinate values
+      into a form suitable for display, and also for calculating
+      distances and offsets between positions within the Frame.
+
+      Frames may also contain knowledge of how to transform to and
+      from related coordinate systems.
+   }
+   \sstsynopsis{
+      AstFrame $*$astFrame( int naxes, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         naxes
+      }{
+         The number of Frame axes (i.e. the number of dimensions of
+         the coordinate space which the Frame describes).
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Frame. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFrame()
+      }{
+         A pointer to the new Frame.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         frame = astFrame( 2, \texttt{"} Title=Energy Spectrum: \htmlref{Plot}{Plot} \%d\texttt{"} , n );
+      }{
+         Creates a new 2-dimensional Frame and initialises its Title
+         attribute to the string \texttt{"} Energy Spectrum: Plot $<$n$>$\texttt{"} , where
+         $<$n$>$ takes the value of the int variable \texttt{"} n\texttt{"} .
+      }
+      \sstexamplesubsection{
+         frame = astFrame( 2, \texttt{"} Label(1)=Energy, Label(2)=Response\texttt{"}  );
+      }{
+         Creates a new 2-dimensional Frame and initialises its axis
+         Label attributes to suitable string values.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astFrameSet
+}{
+   Create a FrameSet
+}{
+   \sstdescription{
+      This function creates a new \htmlref{FrameSet}{FrameSet} and optionally initialises
+      its attributes.
+
+      A FrameSet consists of a set of one or more Frames (which
+      describe coordinate systems), connected together by Mappings
+      (which describe how the coordinate systems are inter-related). A
+      FrameSet makes it possible to obtain a \htmlref{Mapping}{Mapping} between any pair
+      of these Frames (i.e. to convert between any of the coordinate
+      systems which it describes).  The individual Frames are
+      identified within the FrameSet by an integer index, with Frames
+      being numbered consecutively from one as they are added to the
+      FrameSet.
+
+      Every FrameSet has a \texttt{"} base\texttt{"}  \htmlref{Frame}{Frame} and a \texttt{"} current\texttt{"}  Frame (which
+      are allowed to be the same). Any of the Frames may be nominated
+      to hold these positions, and the choice is determined by the
+      values of the FrameSet\texttt{'} s \htmlref{Base}{Base} and \htmlref{Current}{Current} attributes, which hold
+      the indices of the relevant Frames.  By default, the first Frame
+      added to a FrameSet is its base Frame, and the last one added is
+      its current Frame.
+
+      The base Frame describes the \texttt{"} native\texttt{"}  coordinate system of
+      whatever the FrameSet is used to calibrate (e.g. the pixel
+      coordinates of an image) and the current Frame describes the
+      \texttt{"} apparent\texttt{"}  coordinate system in which it should be viewed
+      (e.g. displayed, etc.). Any further Frames represent a library
+      of alternative coordinate systems, which may be selected by
+      making them current.
+
+      When a FrameSet is used in a context that requires a Frame,
+      (e.g. obtaining its \htmlref{Title}{Title} value, or number of axes), the current
+      Frame is used. A FrameSet may therefore be used in place of its
+      current Frame in most situations.
+
+      When a FrameSet is used in a context that requires a Mapping,
+      the Mapping used is the one between its base Frame and its
+      current Frame. Thus, a FrameSet may be used to convert \texttt{"} native\texttt{"}
+      coordinates into \texttt{"} apparent\texttt{"}  ones, and vice versa. Like any
+      Mapping, a FrameSet may also be inverted (see \htmlref{astInvert}{astInvert}), which
+      has the effect of interchanging its base and current Frames and
+      hence of reversing the Mapping between them.
+
+      Regions may be added into a FrameSet (since a \htmlref{Region}{Region} is a type of
+      Frame), either explicitly or as components within CmpFrames. In this
+      case the Mapping between a pair of Frames within a FrameSet will
+      include the effects of the clipping produced by any Regions included
+      in the path between the Frames.
+   }
+   \sstsynopsis{
+      AstFrameSet $*$astFrameSet( AstFrame $*$frame, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         Pointer to the first Frame to be inserted into the
+         FrameSet. This initially becomes both the base and the
+         current Frame. (Further Frames may be added using the
+         \htmlref{astAddFrame}{astAddFrame} function.)
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new FrameSet. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFrameSet()
+      }{
+         A pointer to the new FrameSet.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If a pointer to an existing FrameSet is given for the \texttt{"} frame\texttt{"}
+         parameter, then the new FrameSet will (as a special case) be
+         initialised to contain the same Frames and Mappings, and to have
+         the same attribute values, as the one supplied. This process is
+         similar to making a copy of a FrameSet (see \htmlref{astCopy}{astCopy}), except
+         that the Frames and Mappings contained in the original are not
+         themselves copied, but are shared by both FrameSets.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astFromString
+}{
+   Re-create an Object from an in-memory serialisation
+}{
+   \sstdescription{
+      This function returns a pointer to a new \htmlref{Object}{Object} created from the
+      supplied text string, which should have been created by \htmlref{astToString}{astToString}.
+   }
+   \sstsynopsis{
+      AstObject $*$astFromString( const char $*$string )
+   }
+   \sstparameters{
+      \sstsubsection{
+         string
+      }{
+         Pointer to a text string holding an Object serialisation created
+         previously by astToString.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFromString()
+      }{
+         Pointer to a new Object created from the supplied serialisation,
+         or NULL if the serialisation was invalid, or an error occurred.
+      }
+   }
+}
+\sstroutine{
+   astGenCurve
+}{
+   Draw a generalized curve
+}{
+   \sstdescription{
+      This function draws a general user-defined curve defined by the
+      supplied \htmlref{Mapping}{Mapping}. Note that the curve is transformed into graphical
+      coordinate space for plotting, so that a straight line in
+      physical coordinates may result in a curved line being drawn if
+      the Mapping involved is non-linear. Any discontinuities in the
+      Mapping between physical and graphical coordinates are
+      catered for, as is any clipping established using \htmlref{astClip}{astClip}.
+
+      If you need to draw simple straight lines (geodesics), \htmlref{astCurve}{astCurve}
+      or \htmlref{astPolyCurve}{astPolyCurve} will usually be easier to use and faster.
+   }
+   \sstsynopsis{
+      void astGenCurve( AstPlot $*$this, astMapping $*$map )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Plot}{Plot}.
+      }
+      \sstsubsection{
+         map
+      }{
+         Pointer to a Mapping. This Mapping should have 1 input
+         coordinate representing offset along the required curve,
+         normalized so that the start of the curve is at offset 0.0,
+         and the end of the curve is at offset 1.0. Note, this offset
+         does not need to be linearly related to distance along the curve.
+         The number of output coordinates should equal the number of axes
+         in the current \htmlref{Frame}{Frame} of the Plot. The Mapping should map a
+         specified offset along the curve, into the corresponding
+         coordinates in the current Frame of the Plot. The inverse
+         transformation need not be defined.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error results if the base Frame of the Plot is not 2-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   astGet$<$X$>$
+}{
+   Get an attribute value for an Object
+}{
+   \sstdescription{
+      This is a family of functions which return a specified attribute
+      value for an \htmlref{Object}{Object} using one of several different data
+      types. The type is selected by replacing $<$X$>$ in the function name
+      by C, D, F, I or L, to obtain a result in const char$*$ (i.e. string),
+      double, float, int, or long format, respectively.
+
+      If possible, the attribute value is converted to the type you
+      request. If conversion is not possible, an error will result.
+   }
+   \sstsynopsis{
+      $<$X$>$type astGet$<$X$>$( AstObject $*$this, const char $*$attrib )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         attrib
+      }{
+         Pointer to a null-terminated string containing the name of
+         the attribute whose value is required.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         These functions apply to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGet$<$X$>$()
+      }{
+         The attribute value, in the data type corresponding to $<$X$>$ (or,
+         in the case of astGetC, a pointer to a constant null-terminated
+         character string containing this value).
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         printf( \texttt{"} \htmlref{RefCount}{RefCount} = \%d$\backslash$n\texttt{"} , astGetI( z, \texttt{"} RefCount\texttt{"}  ) );
+      }{
+         Prints the RefCount attribute value for Object \texttt{"} z\texttt{"}  as an int.
+      }
+      \sstexamplesubsection{
+         title = astGetC( axis, \texttt{"} \htmlref{Title}{Title}\texttt{"}  );
+      }{
+         Obtains a pointer to a null-terminated character string containing
+         the Title attribute of Object \texttt{"} axis\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         An appropriate \texttt{"} null\texttt{"}  value will be returned if this function
+         is invoked with the AST error status set, or if it should
+         fail for any reason. This null value is zero for numeric
+         values and NULL for pointer values.
+
+         \sstitem
+         The pointer returned by astGetC is guaranteed to remain valid
+         and the string to which it points will not be over-written for a
+         total of 50 successive invocations of this function. After this,
+         the memory containing the string may be re-used, so a copy of
+         the string should be made if it is needed for longer than this.
+      }
+   }
+}
+\sstroutine{
+   astGetActiveUnit
+}{
+   Determines how the Unit attribute will be used
+}{
+   \sstdescription{
+      This function
+      returns the current value of the ActiveUnit flag for a \htmlref{Frame}{Frame}. See
+      the description of the \htmlref{astSetActiveUnit}{astSetActiveUnit} function
+      for a description of the ActiveUnit flag.
+   }
+   \sstsynopsis{
+      int astGetActiveUnit( AstFrame $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetActiveUnit
+      }{
+         The current value of the ActiveUnit flag.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A zero value will be returned if this function is
+         invoked with the AST error status set, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   astGetColumnData
+}{
+   Retrieve all the data values stored in a column
+}{
+   \sstdescription{
+      This function
+      copies all data values from a named column into a supplied buffer
+   }
+   \sstsynopsis{
+      void astGetColumnData( AstFitsTable $*$this, const char $*$column,
+                             float fnull, double dnull, size\_t mxsize,
+                             void $*$coldata, int $*$nelem )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{FitsTable}{FitsTable}.
+      }
+      \sstsubsection{
+         column
+      }{
+         The character string holding the name of the column. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         fnull
+      }{
+         The value to return in
+         \texttt{"} coldata\texttt{"}
+         for any cells for which no value has been stored in the
+         FitsTable. Ignored if the column\texttt{'} s data type is not
+         AST\_\_FLOATTYPE. Supplying
+         AST\_\_NANF
+         will cause a single precision IEEE NaN value to be used.
+      }
+      \sstsubsection{
+         dnull
+      }{
+         The value to return in
+         \texttt{"} coldata\texttt{"}
+         for any cells for which no value has been stored in the
+         FitsTable. Ignored if the column\texttt{'} s data type is not
+         AST\_\_DOUBLETYPE. Supplying AST\_\_NAN will cause a double precision
+         IEEE NaN value to be used.
+      }
+      \sstsubsection{
+         mxsize
+      }{
+         The size of the
+         \texttt{"} coldata\texttt{"}
+         array, in bytes. The amount of memory needed to hold the data
+         from a column may be determined using
+         \htmlref{astColumnSize}{astColumnSize}.
+         If the supplied array is too small to hold all the column data,
+         trailing column values will be omitted from the returned array,
+         but no error will be reported.
+      }
+      \sstsubsection{
+         coldata
+      }{
+         A pointer to an
+         area of memory in which to return the data
+         values currently stored in the column. The values are stored in
+         row order. If the column holds non-scalar values, the elements
+         of each value are stored in \texttt{"} Fortran\texttt{"}  order. No data type
+         conversion is performed - the data type of each returned value
+         is the data type associated with the column when the column was
+         added to the table. If the column holds strings, the returned
+         strings will be null terminated. Any excess room at the end of
+         the array will be left unchanged.
+      }
+      \sstsubsection{
+         nelem
+      }{
+         The number of elements returned in the
+         \texttt{"} coldata\texttt{"}
+         array. This is the product of the number of rows returned and
+         the number of elements in each column value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The \texttt{"} fnull\texttt{"}  and \texttt{"} dnull\texttt{"}  parameters
+         specify the value to be returned for any empty cells within columns
+         holding floating point values. For columns holding integer values,
+         the value returned for empty cells is the value returned by the
+         astColumNull function.
+         For columns holding string values, the ASCII NULL character is returned
+         for empty cells.
+      }
+   }
+}
+\sstroutine{
+   astGetFits$<$X$>$
+}{
+   Get a named keyword value from a FitsChan
+}{
+   \sstdescription{
+      This is a family of functions which gets a value for a named keyword,
+      or the value of the current card, from a \htmlref{FitsChan}{FitsChan} using one of several
+      different data types. The data type of the returned value is selected
+      by replacing $<$X$>$ in the function name by one of the following strings
+      representing the recognised FITS data types:
+
+      \sstitemlist{
+
+         \sstitem
+         CF - Complex floating point values.
+
+         \sstitem
+         CI - Complex integer values.
+
+         \sstitem
+         F  - Floating point values.
+
+         \sstitem
+         I  - Integer values.
+
+         \sstitem
+         L  - Logical (i.e. boolean) values.
+
+         \sstitem
+         S  - String values.
+
+         \sstitem
+         CN - A \texttt{"} CONTINUE\texttt{"}  value, these are treated like string values, but
+                are encoded without an equals sign.
+
+      }
+      The data type of the \texttt{"} value\texttt{"}
+      parameter
+
+      depends on $<$X$>$ as follows:
+
+      \sstitemlist{
+
+         \sstitem
+         CF - \texttt{"} double $*$\texttt{"}  (a pointer to a 2 element array to hold the real and
+                imaginary parts of the complex value).
+
+         \sstitem
+         CI - \texttt{"} int $*$\texttt{"}  (a pointer to a 2 element array to hold the real and
+                imaginary parts of the complex value).
+
+         \sstitem
+         F  - \texttt{"} double $*$\texttt{"} .
+
+         \sstitem
+         I  - \texttt{"} int $*$\texttt{"} .
+
+         \sstitem
+         L  - \texttt{"} int $*$\texttt{"} .
+
+         \sstitem
+         S  - \texttt{"} char $*$$*$\texttt{"}  (a pointer to a static \texttt{"} char\texttt{"}  array is returned at the
+                location given by the \texttt{"} value\texttt{"}  parameter, Note, the stored string
+                may change on subsequent invocations of astGetFitsS so a
+                permanent copy should be taken of the string if necessary).
+
+         \sstitem
+         CN - Like\texttt{"} S\texttt{"} .
+      }
+   }
+   \sstsynopsis{
+      int astGetFits$<$X$>$( AstFitsChan $*$this, const char $*$name, $<$X$>$type $*$value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         name
+      }{
+         Pointer to a null-terminated character string
+         containing the FITS keyword name. This may be a complete FITS
+         header card, in which case the keyword to use is extracted from
+         it. No more than 80 characters are read from this string. If
+         NULL
+         is supplied, the value of the current card is returned.
+      }
+      \sstsubsection{
+         value
+      }{
+         A pointer to a
+         buffer to receive the keyword value. The data type depends on $<$X$>$
+         as described above. The conents of the buffer on entry are left
+         unchanged if the keyword is not found.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetFits$<$X$>$$<$X$>$()
+      }{
+         A value of zero
+         is returned if the keyword was not found in the FitsChan (no error
+         is reported). Otherwise, a value of
+         one
+         is returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If a name is supplied, the card following the current card is
+         checked first. If this is not the required card, then the rest of the
+         FitsChan is searched, starting with the first card added to the
+         FitsChan. Therefore cards should be accessed in the order they are
+         stored in the FitsChan (if possible) as this will minimise the time
+         spent searching for cards.
+
+         \sstitem
+         If the requested card is found, it becomes the current card,
+         otherwise the current card is left pointing at the \texttt{"} end-of-file\texttt{"} .
+
+         \sstitem
+         If the stored keyword value is not of the requested type, it is
+         converted into the requested type.
+
+         \sstitem
+         If the keyword is found in the FitsChan, but has no associated
+         value, an error is reported. If necessary, the
+         \htmlref{astTestFits}{astTestFits}
+         function can be used to determine if the keyword has a defined
+         value in the FitsChan prior to calling this function.
+
+         \sstitem
+         An error will be reported if the keyword name does not conform
+         to FITS requirements.
+
+         \sstitem
+         Zero
+
+         \sstitem
+         .FALSE.
+         is returned as the function value if an error has already occurred,
+         or if this function should fail for any reason.
+
+         \sstitem
+         The FITS standard says that string keyword values should be
+         padded with trailing spaces if they are shorter than 8 characters.
+         For this reason, trailing spaces are removed from the string
+         returned by
+         astGetFitsS
+         if the original string (including any trailing spaces) contains 8
+         or fewer characters. Trailing spaces are not removed from longer
+         strings.
+      }
+   }
+}
+\sstroutine{
+   astGetFrame
+}{
+   Obtain a pointer to a specified Frame in a FrameSet
+}{
+   \sstdescription{
+      This function returns a pointer to a specified \htmlref{Frame}{Frame} in a
+      \htmlref{FrameSet}{FrameSet}.
+   }
+   \sstsynopsis{
+      AstFrame $*$astGetFrame( AstFrameSet $*$this, int iframe )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         iframe
+      }{
+         The index of the required Frame within the FrameSet.  This
+         value should lie in the range from 1 to the number of Frames
+         in the FrameSet (as given by its \htmlref{Nframe}{Nframe} attribute).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetFrame()
+      }{
+         A pointer to the requested Frame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of AST\_\_BASE or AST\_\_CURRENT may be given for the
+         \texttt{"} iframe\texttt{"}  parameter to specify the base Frame or the current
+         Frame respectively.
+
+         \sstitem
+         This function increments the \htmlref{RefCount}{RefCount} attribute of the
+         selected Frame by one.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astGetGrfContext
+}{
+   Return the KeyMap that describes a Plot\texttt{'} s graphics context
+}{
+   \sstdescription{
+      This function
+      returns a reference to a \htmlref{KeyMap}{KeyMap} that will be passed to any drawing
+      functions registered using \htmlref{astGrfSet}{astGrfSet}.
+      This KeyMap can be used by an application to pass information to
+      the drawing functions
+      about the context in which they are being called. The contents of
+      the KeyMap are never accessed byt the \htmlref{Plot}{Plot} class itself.
+   }
+   \sstsynopsis{
+      AstKeyMap $*$astGetGrfContext( AstPlot $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetGrfContext()
+      }{
+         A pointer to the graphics context KeyMap. The returned pointer
+         should be annulled when it is no longer needed.
+      }
+   }
+}
+\sstroutine{
+   astGetMapping
+}{
+   Obtain a Mapping that converts between two Frames in a FrameSet
+}{
+   \sstdescription{
+      This function returns a pointer to a \htmlref{Mapping}{Mapping} that will convert
+      coordinates between the coordinate systems represented by two
+      Frames in a \htmlref{FrameSet}{FrameSet}.
+   }
+   \sstsynopsis{
+      AstMapping $*$astGetMapping( AstFrameSet $*$this, int iframe1, int iframe2 )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         iframe1
+      }{
+         The index of the first \htmlref{Frame}{Frame} in the FrameSet. This Frame describes
+         the coordinate system for the \texttt{"} input\texttt{"}  end of the Mapping.
+      }
+      \sstsubsection{
+         iframe2
+      }{
+         The index of the second Frame in the FrameSet. This Frame
+         describes the coordinate system for the \texttt{"} output\texttt{"}  end of the
+         Mapping.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetMapping()
+      }{
+         Pointer to a Mapping whose forward transformation converts
+         coordinates from the first coordinate system to the second
+         one, and whose inverse transformation converts coordinates in
+         the opposite direction.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned Mapping will include the clipping effect of any
+         Regions which occur on the path between the two supplied Frames
+         (this includes the two supplied Frames themselves).
+
+         \sstitem
+         The values given for the \texttt{"} iframe1\texttt{"}  and \texttt{"} iframe2\texttt{"}  parameters
+         should lie in the range from 1 to the number of Frames in the
+         FrameSet (as given by its \htmlref{Nframe}{Nframe} attribute). A value of
+         AST\_\_BASE or AST\_\_CURRENT may also be given to identify the
+         FrameSet\texttt{'} s base Frame or current Frame respectively.  It is
+         permissible for both these parameters to have the same value, in
+         which case a unit Mapping (\htmlref{UnitMap}{UnitMap}) is returned.
+
+         \sstitem
+         It should always be possible to generate the Mapping
+         requested, but this does necessarily guarantee that it will be
+         able to perform the required coordinate conversion. If
+         necessary, the \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse} attributes of the
+         returned Mapping should be inspected to determine if the
+         required transformation is available.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astGetRefPos
+}{
+   Return the reference position in a specified celestial coordinate system
+}{
+   \sstdescription{
+      This function
+      returns the reference position (specified by attributes \htmlref{RefRA}{RefRA} and
+      \htmlref{RefDec}{RefDec}) converted to the celestial coordinate system represented by
+      a supplied \htmlref{SkyFrame}{SkyFrame}. The celestial longitude and latitude values
+      are returned in radians.
+   }
+   \sstsynopsis{
+      void astGetRefPos( AstSpecFrame $*$this, AstSkyFrame $*$frm, double $*$lon,
+                         double $*$lat )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{SpecFrame}{SpecFrame}.
+      }
+      \sstsubsection{
+         frm
+      }{
+         Pointer to the SkyFrame which defines the required celestial
+         coordinate system.
+         If NULL
+         is supplied, then the longitude and latitude values are returned
+         as FK5 J2000 RA and Dec values.
+      }
+      \sstsubsection{
+         lon
+      }{
+         A pointer to a double in which to store the
+         longitude of the reference point, in the coordinate system
+         represented by the supplied SkyFrame (radians).
+      }
+      \sstsubsection{
+         lat
+      }{
+         A pointer to a double in which to store the
+         latitude of the reference point, in the coordinate system
+         represented by the supplied SkyFrame (radians).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Values of AST\_\_BAD will be returned if this function is
+         invoked with the AST error status set, or if it should fail for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   astGetRegionBounds
+}{
+   Returns the bounding box of Region
+}{
+   \sstdescription{
+      This function
+      returns the upper and lower limits of a box which just encompasses
+      the supplied \htmlref{Region}{Region}. The limits are returned as axis values within
+      the \htmlref{Frame}{Frame} represented by the Region. The value of the \htmlref{Negated}{Negated}
+      attribute is ignored (i.e. it is assumed that the Region has not
+      been negated).
+   }
+   \sstsynopsis{
+      void astGetRegionBounds( AstRegion $*$this, double $*$lbnd, double $*$ubnd )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an
+         array in which to return the lower axis bounds covered by the Region.
+         It should have at least as many elements as there are axes in the
+         Region. If an axis has no lower limit, the returned value will
+         be the largest possible negative value.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an
+         array in which to return the upper axis bounds covered by the Region.
+         It should have at least as many elements as there are axes in the
+         Region. If an axis has no upper limit, the returned value will
+         be the largest possible positive value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The value of the Negated attribute is ignored (i.e. it is assumed that
+         the Region has not been negated).
+
+         \sstitem
+         If an axis has no extent on an axis then the lower limit will be
+         returned larger than the upper limit. Note, this is different to an
+         axis which has a constant value (in which case both lower and upper
+         limit will be returned set to the constant value).
+
+         \sstitem
+         If the bounds on an axis cannot be determined, AST\_\_BAD is returned for
+         both upper and lower bounds
+      }
+   }
+}
+\sstroutine{
+   astGetRegionFrame
+}{
+   Obtain a pointer to the encapsulated Frame within a Region
+}{
+   \sstdescription{
+      This function returns a pointer to the \htmlref{Frame}{Frame} represented by a
+      \htmlref{Region}{Region}.
+   }
+   \sstsynopsis{
+      AstFrame $*$astGetRegionFrame( AstRegion $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Region.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetRegionFrame()
+      }{
+         A pointer to a deep copy of the Frame represented by the Region.
+         Using this pointer to modify the Frame will have no effect on
+         the Region. To modify the Region, use the Region pointer directly.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astGetRegionFrameSet
+}{
+   Obtain a pointer to the encapsulated FrameSet within a Region
+}{
+   \sstdescription{
+      This function returns a pointer to the \htmlref{FrameSet}{FrameSet} encapsulated by a
+      \htmlref{Region}{Region}. The base \htmlref{Frame}{Frame} is the Frame in which the box was originally
+      defined, and the current Frame is the Frame into which the Region
+      is currently mapped (i.e. it will be the same as the Frame returned
+      by \htmlref{astGetRegionFrame}{astGetRegionFrame}).
+   }
+   \sstsynopsis{
+      AstFrame $*$astGetRegionFrameSet( AstRegion $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Region.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetRegionFrameSet()
+      }{
+         A pointer to a deep copy of the FrameSet represented by the Region.
+         Using this pointer to modify the FrameSet will have no effect on
+         the Region.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astGetRegionMesh
+}{
+   Return a mesh of points covering the surface or volume of a Region
+}{
+   \sstdescription{
+      This function
+      returns the axis values at a mesh of points either covering the
+      surface (i.e. boundary) of the supplied \htmlref{Region}{Region}, or filling the
+      interior (i.e. volume) of the Region. The number of points in
+      the mesh is approximately equal to the \htmlref{MeshSize}{MeshSize} attribute.
+   }
+   \sstsynopsis{
+      void astGetRegionMesh( AstRegion $*$this, int surface, int maxpoint,
+                             int maxcoord, int $*$npoint, double $*$points )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         surface
+      }{
+         If non-zero,
+         the returned points will cover the surface or the Region.
+         Otherwise, they will fill the interior of the Region.
+      }
+      \sstsubsection{
+         maxpoint
+      }{
+         If zero, the number of points in the mesh is returned in
+         \texttt{"} $*$npoint\texttt{"} ,
+         but no axis values are returned and all other parameters are ignored.
+         If not zero, the supplied value should be the length of the
+         second dimension of the \texttt{"} points\texttt{"}
+         array. An error is reported if the number of points in the mesh
+         exceeds this number.
+      }
+      \sstsubsection{
+         maxcoord
+      }{
+         The length of the
+         first dimension of the \texttt{"} points\texttt{"}  array.
+         An error is reported if the number of axes in the supplied Region
+         exceeds this number.
+      }
+      \sstsubsection{
+         npoint
+      }{
+         A pointer to an integer in which to return the
+         number of points in the returned mesh.
+      }
+      \sstsubsection{
+         points
+      }{
+         The address of the first element in a 2-dimensional array of
+         shape \texttt{"} [maxcoord][maxpoint]\texttt{"} , in which to return the coordinate
+         values at the mesh positions. These are stored such that the
+         value of coordinate number \texttt{"} coord\texttt{"}  for point number \texttt{"} point\texttt{"}  is
+         found in element \texttt{"} points[coord][point]\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error is reported if the Region is unbounded.
+
+         \sstitem
+         If the coordinate system represented by the Region has been
+         changed since it was first created, the returned axis values refer
+         to the new (changed) coordinate system, rather than the original
+         coordinate system. Note however that if the transformation from
+         original to new coordinate system is non-linear, the shape within
+         the new coordinate system may be distorted, and so may not match
+         that implied by the name of the Region subclass (\htmlref{Circle}{Circle}, \htmlref{Box}{Box}, etc).
+      }
+   }
+}
+\sstroutine{
+   astGetRegionPoints
+}{
+   Returns the positions that define the given Region
+}{
+   \sstdescription{
+      This function
+      returns the axis values at the points that define the supplied
+      \htmlref{Region}{Region}. The particular meaning of these points will depend on the
+      type of class supplied, as listed below under \texttt{"} Applicability:\texttt{"} .
+   }
+   \sstsynopsis{
+      void astGetRegionPoints( AstRegion $*$this, int maxpoint, int maxcoord,
+                               int $*$npoint, double $*$points )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         maxpoint
+      }{
+         If zero, the number of points needed to define the Region is
+         returned in
+         \texttt{"} $*$npoint\texttt{"} ,
+         but no axis values are returned and all other parameters are ignored.
+         If not zero, the supplied value should be the length of the
+         second dimension of the \texttt{"} points\texttt{"}
+         array. An error is reported if the number of points needed to define
+         the Region exceeds this number.
+      }
+      \sstsubsection{
+         maxcoord
+      }{
+         The length of the
+         first dimension of the \texttt{"} points\texttt{"}  array.
+         An error is reported if the number of axes in the supplied Region
+         exceeds this number.
+      }
+      \sstsubsection{
+         npoint
+      }{
+         A pointer to an integer in which to return the
+         number of points defining the Region.
+      }
+      \sstsubsection{
+         points
+      }{
+         The address of the first element in a 2-dimensional array of
+         shape \texttt{"} [maxcoord][maxpoint]\texttt{"} , in which to return
+         the coordinate values at the positions that define the Region.
+         These are stored such that the value of coordinate number
+         \texttt{"} coord\texttt{"}  for point number \texttt{"} point\texttt{"}  is found in element
+         \texttt{"} points[coord][point]\texttt{"} .
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Box}{Box}
+      }{
+         The first returned position is the Box centre, and the second is
+         a Box corner.
+      }
+      \sstsubsection{
+         \htmlref{Circle}{Circle}
+      }{
+         The first returned position is the Circle centre, and the second is
+         a point on the circumference.
+      }
+      \sstsubsection{
+         \htmlref{CmpRegion}{CmpRegion}
+      }{
+         Returns a value of zero for
+         \texttt{"} $*$npoint\texttt{"}
+         and leaves the supplied array contents unchanged. To find the
+         points defining a CmpRegion, use this method on the component
+         Regions, which can be accessed by invoking
+         \htmlref{astDecompose}{astDecompose}
+         on the CmpRegion.
+      }
+      \sstsubsection{
+         \htmlref{Ellipse}{Ellipse}
+      }{
+         The first returned position is the Ellipse centre. The second is
+         the end of one of the axes of the ellipse. The third is some
+         other point on the circumference of the ellipse, distinct from
+         the second point.
+      }
+      \sstsubsection{
+         \htmlref{Interval}{Interval}
+      }{
+         The first point corresponds to the lower bounds position, and
+         the second point corresponds to the upper bounds position. These
+         are reversed to indicate an extcluded interval rather than an
+         included interval. See the Interval constructor for more
+         information.
+      }
+      \sstsubsection{
+         \htmlref{NullRegion}{NullRegion}
+      }{
+         Returns a value of zero for
+         \texttt{"} $*$npoint\texttt{"}
+         and leaves the supplied array contents unchanged.
+      }
+      \sstsubsection{
+         \htmlref{PointList}{PointList}
+      }{
+         The positions returned are those that were supplied when the
+         PointList was constructed.
+      }
+      \sstsubsection{
+         \htmlref{Polygon}{Polygon}
+      }{
+         The positions returned are the vertex positions that were supplied
+         when the Polygon was constructed.
+      }
+      \sstsubsection{
+         \htmlref{Prism}{Prism}
+      }{
+         Returns a value of zero for
+         \texttt{"} $*$npoint\texttt{"}
+         and leaves the supplied array contents unchanged. To find the
+         points defining a Prism, use this method on the component
+         Regions, which can be accessed by invoking
+         astDecompose
+         on the CmpRegion.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the coordinate system represented by the Region has been
+         changed since it was first created, the returned axis values refer
+         to the new (changed) coordinate system, rather than the original
+         coordinate system. Note however that if the transformation from
+         original to new coordinate system is non-linear, the shape within
+         the new coordinate system may be distorted, and so may not match
+         that implied by the name of the Region subclass (Circle, Box, etc).
+      }
+   }
+}
+\sstroutine{
+   astGetStcCoord
+}{
+   Return information about an AstroCoords element stored in an Stc
+}{
+   \sstdescription{
+      When any sub-class of \htmlref{Stc}{Stc} is created, the constructor function
+      allows one or more AstroCoords elements to be stored within the Stc.
+      This function allows any one of these AstroCoords elements to be
+      retrieved. The format of the returned information is the same as
+      that used to pass the original information to the Stc constructor.
+      That is, the information is returned in a \htmlref{KeyMap}{KeyMap} structure
+      containing elements with one or more of the keys given by symbolic
+      constants AST\_\_STCNAME, AST\_\_STCVALUE, AST\_\_STCERROR, AST\_\_STCRES,
+      AST\_\_STCSIZE and AST\_\_STCPIXSZ.
+
+      If the coordinate system represented by the Stc has been changed
+      since it was created (for instance, by changing its \htmlref{System}{System}
+      attribute), then the sizes and positions in the returned KeyMap
+      will reflect the change in coordinate system.
+   }
+   \sstsynopsis{
+      AstKeyMap $*$astGetStcCoord( AstStc $*$this, int icoord )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Stc.
+      }
+      \sstsubsection{
+         icoord
+      }{
+         The index of the AstroCoords element required. The first has index
+         one. The number of AstroCoords elements in the Stc can be found using
+         function astGetStcNcoord.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetStcCoord()
+      }{
+         A pointer to a new KeyMap containing the required information.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astGetStcNCoord
+}{
+   Return the number of AstroCoords elements stored in an Stc
+}{
+   \sstdescription{
+      This function returns the number of AstroCoords elements stored in
+      an \htmlref{Stc}{Stc}.
+   }
+   \sstsynopsis{
+      int astGetStcNCoord( AstStc $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Stc.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetStcNCoord()
+      }{
+         The number of  AstroCoords elements stored in the Stc.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Zero will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astGetStcRegion
+}{
+   Obtain a copy of the encapsulated Region within a Stc
+}{
+   \sstdescription{
+      This function returns a pointer to a deep copy of the \htmlref{Region}{Region}
+      supplied when the \htmlref{Stc}{Stc} was created.
+   }
+   \sstsynopsis{
+      AstRegion $*$astGetStcRegion( AstStc $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Stc.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetStcRegion()
+      }{
+         A pointer to a deep copy of the Region encapsulated within the
+         supplied Stc.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astGetTableHeader
+}{
+   Get the FITS headers from a FitsTable
+}{
+   \sstdescription{
+      This function returns a pointer to a \htmlref{FitsChan}{FitsChan} holding copies of
+      the FITS headers associated with a \htmlref{FitsTable}{FitsTable}.
+   }
+   \sstsynopsis{
+      AstFitsChan $*$astGetTableHeader( AstFitsTable $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsTable.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetTableHeader()
+      }{
+         A pointer to a deep copy of the FitsChan stored within the
+         FitsTable.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned pointer should be annulled using
+         \htmlref{astAnnul}{astAnnul}
+         when it is no longer needed.
+
+         \sstitem
+         Changing the contents of the returned FitsChan will have no effect
+         on the FitsTable. To modify the FitsTable, the modified FitsChan must
+         be stored in the FitsTable using
+         \htmlref{astPutTableHeader}{astPutTableHeader}.
+      }
+   }
+}
+\sstroutine{
+   astGetTables
+}{
+   Retrieve any FitsTables currently in a FitsChan
+}{
+   \sstdescription{
+      If the supplied \htmlref{FitsChan}{FitsChan} currently contains any tables, then this
+      function returns a pointer to a \htmlref{KeyMap}{KeyMap}. Each entry in the KeyMap
+      is a pointer to a \htmlref{FitsTable}{FitsTable} holding the data for a FITS binary
+      table. The key used to access each entry is the FITS extension
+      name in which the table should be stored.
+
+      Tables can be present in a FitsChan as a result either of using the
+      \htmlref{astPutTable}{astPutTable} (or \htmlref{astPutTables}{astPutTables})
+      method to store existing tables in the FitsChan, or of using the
+      \htmlref{astWrite}{astWrite}
+      method to write a \htmlref{FrameSet}{FrameSet} to the FitsChan. For the later case, if
+      the FitsChan \texttt{"} \htmlref{TabOK}{TabOK}\texttt{"}  attribute is positive and the FrameSet requires
+      a look-up table to describe one or more axes, then the \texttt{"} -TAB\texttt{"}
+      algorithm code described in FITS-WCS paper III is used and the table
+      values are stored in the FitsChan in the form of a FitsTable object
+      (see the documentation for the \texttt{"} TabOK\texttt{"}  attribute).
+   }
+   \sstsynopsis{
+      AstKeyMap $*$astGetTables( AstFitsChan $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetTables()
+      }{
+         A pointer to a deep copy of the KeyMap holding the tables currently
+         in the FitsChan, or
+         NULL
+         if the FitsChan does not contain any tables. The returned
+         pointer should be annulled using
+         \htmlref{astAnnul}{astAnnul}
+         when no longer needed.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astGetUnc
+}{
+   Obtain uncertainty information from a Region
+}{
+   \sstdescription{
+      This function returns a \htmlref{Region}{Region} which represents the uncertainty
+      associated with positions within the supplied Region. See
+      \htmlref{astSetUnc}{astSetUnc}
+      for more information about Region uncertainties and their use.
+   }
+   \sstsynopsis{
+      AstRegion $*$astGetUnc( AstRegion $*$this, int def )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         def
+      }{
+         Controls what is returned if no uncertainty information has been
+         associated explicitly with the supplied Region. If
+         a non-zero value
+         is supplied, then the default uncertainty Region used internally
+         within AST is returned (see \texttt{"} Applicability\texttt{"}  below). If
+         zero is supplied, then NULL
+         will be returned (without error).
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{CmpRegion}{CmpRegion}
+      }{
+         The default uncertainty for a CmpRegion is taken from one of the
+         two component Regions. If the first component Region has a
+         non-default uncertainty, then it is used as the default uncertainty
+         for the parent CmpRegion. Otherwise, if the second component Region
+         has a non-default uncertainty, then it is used as the default
+         uncertainty for the parent CmpRegion. If neither of the
+         component Regions has non-default uncertainty, then the default
+         uncertainty for the CmpRegion is 1.0E-6 of the bounding box of
+         the CmpRegion.
+      }
+      \sstsubsection{
+         \htmlref{Prism}{Prism}
+      }{
+         The default uncertainty for a Prism is formed by combining the
+         uncertainties from the two component Regions. If a component
+         Region does not have a non-default uncertainty, then its default
+         uncertainty will be used to form the default uncertainty of the
+         parent Prism.
+      }
+      \sstsubsection{
+         Region
+      }{
+         For other classes of Region, the default uncertainty is 1.0E-6
+         of the bounding box of the Region. If the bounding box has zero
+         width on any axis, then the uncertainty will be 1.0E-6 of the
+         axis value.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGetUnc()
+      }{
+         A pointer to a Region describing the uncertainty in the supplied
+         Region.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If uncertainty information is associated with a Region, and the
+         coordinate system described by the Region is subsequently changed
+         (e.g. by changing the value of its \htmlref{System}{System} attribute, or using the
+         \htmlref{astMapRegion}{astMapRegion}
+         function), then the uncertainty information returned by this function
+         will be modified so that it refers to the coordinate system currently
+         described by the supplied Region.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astGrfPop
+}{
+   Restore previously saved graphics functions used by a Plot
+}{
+   \sstdescription{
+      This function restores a snapshot of the graphics functions
+      stored previously by calling \htmlref{astGrfPush}{astGrfPush}. The restored graphics
+      functions become the current graphics functions used by the \htmlref{Plot}{Plot}.
+
+      The astGrfPush and astGrfPop functions are intended for situations
+      where it is necessary to make temporary changes to the graphics
+      functions used by the Plot. The current functions should first be
+      saved by calling astGrfPush. New functions should then be registered
+      using \htmlref{astGrfSet}{astGrfSet}. The required graphics should then be produced.
+      Finally, astGrfPop should be called to restore the original graphics
+      functions.
+   }
+   \sstsynopsis{
+      void astGrfPop( AstPlot $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function returns without action if there are no snapshots to
+         restore. No error is reported in this case.
+      }
+   }
+}
+\sstroutine{
+   astGrfPush
+}{
+   Save the current graphics functions used by a Plot
+}{
+   \sstdescription{
+      This function takes a snapshot of the graphics functions which are
+      currently registered with the supplied \htmlref{Plot}{Plot}, and saves the snapshot
+      on a first-in-last-out stack within the Plot. The snapshot can be
+      restored later using function
+      \htmlref{astGrfPop}{astGrfPop}.
+
+      The astGrfPush and astGrfPop functions are intended for situations
+      where it is necessary to make temporary changes to the graphics
+      functions used by the Plot. The current functions should first be
+      saved by calling astGrfPush. New functions should then be registered
+      using \htmlref{astGrfSet}{astGrfSet}. The required graphics should then be produced.
+      Finally, astGrfPop should be called to restore the original graphics
+      functions.
+   }
+   \sstsynopsis{
+      void astGrfPush( AstPlot $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+   }
+}
+\sstroutine{
+   astGrfSet
+}{
+   Register a graphics function for use by a Plot
+}{
+   \sstdescription{
+      This function can be used to select the underlying graphics
+      functions to be used when the supplied \htmlref{Plot}{Plot} produces graphical output.
+      If this function is not called prior to producing graphical
+      output, then the underlying graphics functions selected at
+      link-time (using the \htmlref{ast\_link}{ast\_link} command) will be used. To use
+      alternative graphics functions, call this function before
+      the graphical output is created, specifying the graphics
+      functions to be used. This will register the function for future
+      use, but the function will not actually be used until the \htmlref{Grf}{Grf}
+      attribute is given a non-zero value.
+   }
+   \sstsynopsis{
+      void astGrfSet( AstPlot $*$this, const char $*$name, AstGrfFun fun )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         name
+      }{
+         A name indicating the graphics function to be replaced.
+         Various graphics functions are used by the
+         Plot class, and any combination of them may be supplied by calling
+         this function once for each function to be replaced. If any of the
+         graphics functions are not replaced in this way, the
+         corresponding functions in the graphics interface selected at
+         link-time (using the ast\_link command) are used. The allowed
+         names are:
+
+         \sstitemlist{
+
+            \sstitem
+            Attr -  Enquire or set a graphics attribute value
+
+            \sstitem
+            BBuf - Start a new graphics buffering context
+
+            \sstitem
+            Cap -  Inquire a capability
+
+            \sstitem
+            EBuf - End the current graphics buffering context
+
+            \sstitem
+            Flush - Flush all pending graphics to the output device
+
+            \sstitem
+            Line - Draw a polyline (i.e. a set of connected lines)
+
+            \sstitem
+            Mark -  Draw a set of markers
+
+            \sstitem
+            Qch -  Return the character height in world coordinates
+
+            \sstitem
+            Scales -  Get the axis scales
+
+            \sstitem
+            Text - Draw a character string
+
+            \sstitem
+            TxExt -  Get the extent of a character string
+
+         }
+         The string is case insensitive. For details of the interface
+         required for each, see the sections below.
+      }
+      \sstsubsection{
+         fun
+      }{
+         A Pointer to the function to be used to provide the
+         functionality indicated by parameter name. The interface for
+         each function is described below, but the function pointer should
+         be cast to a type of AstGrfFun when calling astGrfSet.
+
+         Once a function has been provided, a null pointer can be supplied
+         in a subsequent call to astGrfSet to reset the function to the
+         corresponding function in the graphics interface selected at
+         link-time.
+      }
+   }
+   \sstdiytopic{
+      Function Interfaces
+   }{
+      All the functions listed below (except for \texttt{"} Cap\texttt{"} ) should return an
+      integer value of 0 if an error occurs, and 1 otherwise. All x and y
+      values refer
+      to \texttt{"} graphics cordinates\texttt{"}  as defined by the graphbox parameter of
+      the \htmlref{astPlot}{astPlot} call which created the Plot.
+
+      The first parameter (\texttt{"} grfcon\texttt{"} )
+      for each function is an AST \htmlref{KeyMap}{KeyMap} pointer that can be used by the
+      called function to establish the context in which it is being called.
+      The contents of the KeyMap are determined by the calling
+      application, which should obtain a pointer to the KeyMap using the
+      \htmlref{astGetGrfContext}{astGetGrfContext} function,
+      and then store any necessary information in the KeyMap using the
+      methods of the KeyMap class. Note, the functions listed below
+      should never annul or delete the supplied KeyMap pointer.
+   }
+   \sstdiytopic{
+      Attr
+   }{
+      The \texttt{"} Attr\texttt{"}  function returns the current value of a specified graphics
+      attribute, and optionally establishes a new value. The supplied
+      value is converted to an integer value if necessary before use.
+      It requires the following interface:
+
+      int Attr( AstObject $*$grfcon, int attr, double value, double $*$old\_value, int prim )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         attr - An integer value identifying the required attribute.
+           The following symbolic values are defined in grf.h:
+           GRF\_\_STYLE (Line style),
+           GRF\_\_WIDTH (Line width),
+           GRF\_\_SIZE (Character and marker size scale factor),
+           GRF\_\_FONT (Character font),
+           GRF\_\_COLOUR (Colour index).
+
+         \sstitem
+         value -
+           A new value to store for the attribute. If this is AST\_\_BAD
+           no value is stored.
+
+         \sstitem
+         old\_value - A pointer to a double in which to return
+           the attribute value.
+           If this is NULL, no value is returned.
+
+         \sstitem
+         prim -
+           The sort of graphics primitive to be drawn with the new attribute.
+           Identified by the following values defined in grf.h:
+           GRF\_\_LINE,
+           GRF\_\_MARK,
+           GRF\_\_TEXT.
+      }
+   }
+   \sstdiytopic{
+      BBuf
+   }{
+      The \texttt{"} BBuf\texttt{"}  function should start a new graphics buffering context.
+      A matching call to the function \texttt{"} EBuf\texttt{"}  should be used to end the
+      context. The nature of the buffering is determined by the underlying
+      graphics system.
+
+      int BBuf( AstObject $*$grfcon )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+      }
+   }
+   \sstdiytopic{
+      Cap
+   }{
+      The \texttt{"} Cap\texttt{"}  function is called to determine if the grf module has a
+      given capability, as indicated by the \texttt{"} cap\texttt{"}  argument:
+
+      int Cap( AstObject $*$grfcon, int cap, int value )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         cap -
+            The capability being inquired about. This will be one of the
+            following constants defined in grf.h:
+
+      }
+         GRF\_\_SCALES: This function should return a non-zero value if the
+         \texttt{"} Scales\texttt{"}  function is implemented, and zero otherwise. The supplied
+         \texttt{"} value\texttt{"}  argument should be ignored.
+
+         GRF\_\_MJUST: This function should return a non-zero value if
+         the \texttt{"} Text\texttt{"}  and \texttt{"} TxExt\texttt{"}  functions recognise \texttt{"} M\texttt{"}  as a
+         character in the justification string. If the first character of
+         a justification string is \texttt{"} M\texttt{"} , then the text should be justified
+         with the given reference point at the bottom of the bounding box.
+         This is different to \texttt{"} B\texttt{"}  justification, which requests that the
+         reference point be put on the baseline of the text, since some
+         characters hang down below the baseline. If the \texttt{"} Text\texttt{"}  or
+         \texttt{"} TxExt\texttt{"}  function cannot differentiate between \texttt{"} M\texttt{"}  and \texttt{"} B\texttt{"} ,
+         then this function should return zero, in which case \texttt{"} M\texttt{"}
+         justification will never be requested by Plot. The supplied
+         \texttt{"} value\texttt{"}  argument should be ignored.
+
+         GRF\_\_ESC: This function should return a non-zero value if the
+         \texttt{"} Text\texttt{"}  and \texttt{"} TxExt\texttt{"}  functions can recognise and interpret
+         graphics escape sequences within the supplied string (see
+         attribute \htmlref{Escape}{Escape}). Zero should be returned if escape sequences
+         cannot be interpreted (in which case the Plot class will interpret
+         them itself if needed). The supplied \texttt{"} value\texttt{"}  argument should be
+         ignored only if escape sequences cannot be interpreted by \texttt{"} Text\texttt{"}  and
+         \texttt{"} TxExt\texttt{"} . Otherwise, \texttt{"} value\texttt{"}  indicates whether \texttt{"} Text\texttt{"}  and \texttt{"} TxExt\texttt{"}
+         should interpret escape sequences in subsequent calls. If \texttt{"} value\texttt{"}  is
+         non-zero then escape sequences should be interpreted by \texttt{"} Text\texttt{"}  and
+         \texttt{"} TxExt\texttt{"} . Otherwise, they should be drawn as literal text.
+
+      \sstitemlist{
+
+         \sstitem
+         value -
+            The use of this parameter depends on the value of \texttt{"} cap\texttt{"}  as
+            described above.
+
+         \sstitem
+         Returned Function Value:
+            The value returned by the function depends on the value of \texttt{"} cap\texttt{"}
+            as described above. Zero should be returned if the supplied
+            capability is not recognised.
+      }
+   }
+   \sstdiytopic{
+      EBuf
+   }{
+      The \texttt{"} EBuf\texttt{"}  function should end the current graphics buffering
+      context. See the description of \texttt{"} BBuf\texttt{"}  above for further details.
+      It requires the following interface:
+
+      int EBuf( AstObject $*$grfcon )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+      }
+   }
+   \sstdiytopic{
+      Flush
+   }{
+      The \texttt{"} Flush\texttt{"}  function ensures that the display device is up-to-date,
+      by flushing any pending graphics to the output device. It
+      requires the following interface:
+
+      int Flush( AstObject $*$grfcon )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+      }
+   }
+   \sstdiytopic{
+      Line
+   }{
+      The \texttt{"} Line\texttt{"}  function displays lines joining the given positions and
+      requires the following interface:
+
+      int Line( AstObject $*$grfcon, int n, const float $*$x, const float $*$y )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         n - The number of positions to be joined together.
+
+         \sstitem
+         x - A pointer to an array holding the \texttt{"} n\texttt{"}  x values.
+
+         \sstitem
+         y - A pointer to an array holding the \texttt{"} n\texttt{"}  y values.
+      }
+   }
+   \sstdiytopic{
+      Mark
+   }{
+      The \texttt{"} Mark\texttt{"}  function displays markers at the given positions. It
+      requires the following interface:
+
+      int Mark( AstObject $*$grfcon, int n, const float $*$x, const float $*$y, int type )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         n - The number of positions to be marked.
+
+         \sstitem
+         x - A pointer to an array holding the \texttt{"} n\texttt{"}  x values.
+
+         \sstitem
+         y - A pointer to an array holding the \texttt{"} n\texttt{"}  y values.
+
+         \sstitem
+         type - An integer which can be used to indicate the type of marker
+           symbol required.
+      }
+   }
+   \sstdiytopic{
+      Qch
+   }{
+      The \texttt{"} Qch\texttt{"}  function returns the heights of characters drawn vertically
+      and horizontally in graphics coordinates. It requires the following
+      interface:
+
+      int Qch( AstObject $*$grfcon, float $*$chv, float $*$chh )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         chv - A pointer to the float which is to receive the height of
+         characters drawn with a vertical baseline. This will be an
+         increment in the X axis.
+
+         \sstitem
+         chh - A pointer to the float which is to receive the height of
+         characters drawn with a horizontal baseline. This will be an
+         increment in the Y axis.
+      }
+   }
+   \sstdiytopic{
+      Scales
+   }{
+      The \texttt{"} Scales\texttt{"}  function returns two values (one for each axis) which
+      scale increments on the corresponding axis into a \texttt{"} normal\texttt{"}  coordinate
+      system in which: 1) the axes have equal scale in terms of (for instance)
+      millimetres per unit distance, 2) X values increase from left to
+      right, and 3) Y values increase from bottom to top. It requires the
+      following interface:
+
+      int Scales( AstObject $*$grfcon, float $*$alpha, float $*$beta )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         alpha - A pointer to the float which is to receive the
+         scale for the X axis (i.e. Xnorm = alpha$*$Xworld).
+
+         \sstitem
+         beta - A pointer to the float which is to receive the
+         scale for the Y axis (i.e. Ynorm = beta$*$Yworld).
+      }
+   }
+   \sstdiytopic{
+      Text
+   }{
+      The \texttt{"} Text\texttt{"}  function displays a character string at a given
+      position using a specified justification and up-vector. It
+      requires the following interface:
+
+      int Text( AstObject $*$grfcon, const char $*$text, float x, float y, const char $*$just,
+                float upx, float upy )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         text - Pointer to a null-terminated character string to be displayed.
+
+         \sstitem
+         x - The reference x coordinate.
+
+         \sstitem
+         y - The reference y coordinate.
+
+         \sstitem
+         just - A character string which specifies the location within the
+            text string which is to be placed at the reference position
+            given by x and y. The first character may be \texttt{'} T\texttt{'}  for \texttt{"} top\texttt{"} ,
+            \texttt{'} C\texttt{'}  for \texttt{"} centre\texttt{"} , or \texttt{'} B\texttt{'}  for \texttt{"} bottom\texttt{"} , and specifies the
+            vertical location of the reference position. Note, \texttt{"} bottom\texttt{"}
+            corresponds to the base-line of normal text. Some characters
+            (eg \texttt{"} y\texttt{"} , \texttt{"} g\texttt{"} , \texttt{"} p\texttt{"} , etc) descend below the base-line. The second
+            character may be \texttt{'} L\texttt{'}  for \texttt{"} left\texttt{"} , \texttt{'} C\texttt{'}  for \texttt{"} centre\texttt{"} , or \texttt{'} R\texttt{'}
+            for \texttt{"} right\texttt{"} , and specifies the horizontal location of the
+            reference position. If the string has less than 2 characters
+            then \texttt{'} C\texttt{'}  is used for the missing characters.
+
+         \sstitem
+         upx - The x component of the up-vector for the text.
+            If necessary the supplied value should be negated
+            to ensure that positive values always refer to displacements from
+            left to right on the screen.
+
+         \sstitem
+         upy - The y component of the up-vector for the text.
+            If necessary the supplied value should be negated
+            to ensure that positive values always refer to displacements from
+            bottom to top on the screen.
+      }
+   }
+   \sstdiytopic{
+      TxExt
+   }{
+      The \texttt{"} TxExt\texttt{"}  function returns the corners of a box which would enclose
+      the supplied character string if it were displayed using the
+      Text function described above. The returned box includes any leading
+      or trailing spaces. It requires the following interface:
+
+      int TxExt( AstObject $*$grfcon, const char $*$text, float x, float y, const char $*$just,
+                 float upx, float upy, float $*$xb, float $*$yb )
+
+      \sstitemlist{
+
+         \sstitem
+         grfcon -
+           A KeyMap containing information passed from the calling application.
+
+         \sstitem
+         text - Pointer to a null-terminated character string to be displayed.
+
+         \sstitem
+         x - The reference x coordinate.
+
+         \sstitem
+         y - The reference y coordinate.
+
+         \sstitem
+         just - A character string which specifies the location within the
+            text string which is to be placed at the reference position
+            given by x and y. See \texttt{"} Text\texttt{"}  above.
+
+         \sstitem
+         upx - The x component of the up-vector for the text.
+            See \texttt{"} Text\texttt{"}  above.
+
+         \sstitem
+         upy - The y component of the up-vector for the text.
+            See \texttt{"} Text\texttt{"}  above.
+
+         \sstitem
+         xb - An array of 4 elements in which to return the x coordinate of
+            each corner of the bounding box.
+
+         \sstitem
+         yb - An array of 4 elements in which to return the y coordinate of
+            each corner of the bounding box.
+      }
+   }
+}
+\sstroutine{
+   astGrid
+}{
+   Draw a set of labelled coordinate axes
+}{
+   \sstdescription{
+      This function draws a complete annotated set of
+      coordinate axes for a \htmlref{Plot}{Plot} with (optionally) a coordinate grid
+      superimposed. Details of the axes and grid can be controlled by
+      setting values for the various attributes defined by the Plot
+      class (q.v.).
+   }
+   \sstsynopsis{
+      void astGrid( AstPlot $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the supplied Plot is a \htmlref{Plot3D}{Plot3D}, the axes will be annotated
+         using three 2-dimensional Plots, one for each 2D plane in the 3D
+         current coordinate system. The plots will be \texttt{"} pasted\texttt{"}  onto 3 faces
+         of the cuboid graphics volume specified when the Plot3D was
+         constructed. The faces to be used can be controlled by the \texttt{"} \htmlref{RootCorner}{RootCorner}\texttt{"}
+         attribute.
+
+         \sstitem
+         An error results if either the current \htmlref{Frame}{Frame} or the base Frame
+         of the Plot is not 2-dimensional or (for a Plot3D) 3-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the base
+         and current Frames of the Plot is not defined in either
+         direction (i.e. the Plot\texttt{'} s \htmlref{TranForward}{TranForward} or \htmlref{TranInverse}{TranInverse} attribute
+         is zero).
+      }
+   }
+}
+\sstroutine{
+   astGridLine
+}{
+   Draw a grid line (or axis) for a Plot
+}{
+   \sstdescription{
+      This function draws a curve in the physical coordinate system of
+      a \htmlref{Plot}{Plot} by varying only one of the coordinates along the length
+      of the curve. It is intended for drawing coordinate axes,
+      coordinate grids, and tick marks on axes (but note that these
+      are also available via the more comprehensive \htmlref{astGrid}{astGrid} function).
+
+      The curve is transformed into graphical coordinate space for
+      plotting, so that a straight line in physical coordinates may
+      result in a curved line being drawn if the \htmlref{Mapping}{Mapping} involved is
+      non-linear. Any discontinuities in the Mapping between physical
+      and graphical coordinates are catered for, as is any
+      clipping established using \htmlref{astClip}{astClip}.
+   }
+   \sstsynopsis{
+      void astGridLine( AstPlot $*$this, int axis, const double start[],
+                        double length )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         axis
+      }{
+         The index of the Plot axis whose physical coordinate value is
+         to be varied along the length of the curve (all other
+         coordinates will remain fixed). This value should lie in the
+         range from 1 to the number of Plot axes (\htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         start
+      }{
+         An array, with one element for each axis of the Plot, giving
+         the physical coordinates of the start of the curve.
+      }
+      \sstsubsection{
+         length
+      }{
+         The length of curve to be drawn, given as an increment along
+         the selected physical axis. This may be positive or negative.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No curve is drawn if the \texttt{"} start\texttt{"}  array contains any
+         coordinates with the value AST\_\_BAD, nor if \texttt{"} length\texttt{"}  has this value.
+
+         \sstitem
+         An error results if the base \htmlref{Frame}{Frame} of the Plot is not 2-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   astGrismMap
+}{
+   Create a GrismMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{GrismMap}{GrismMap} and optionally initialises
+      its attributes.
+
+      A GrismMap is a specialised form of \htmlref{Mapping}{Mapping} which transforms
+      1-dimensional coordinates using the spectral dispersion equation
+      described in FITS-WCS paper III \texttt{"} Representation of spectral
+      coordinates in FITS\texttt{"} . This describes the dispersion produced by
+      gratings, prisms and grisms.
+
+      When initially created, the forward transformation of a GrismMap
+      transforms input \texttt{"} grism parameter\texttt{"}  values into output wavelength
+      values. The \texttt{"} grism parameter\texttt{"}  is a dimensionless value which is
+      linearly related to position on the detector. It is defined in FITS-WCS
+      paper III as \texttt{"} the offset on the detector from the point of intersection
+      of the camera axis, measured in units of the effective local length\texttt{"} .
+      The units in which wavelength values are expected or returned is
+      determined by the values supplied for the \htmlref{GrismWaveR}{GrismWaveR}, \htmlref{GrismNRP}{GrismNRP} and
+      \htmlref{GrismG}{GrismG} attribute: whatever units are used for these attributes will
+      also be used for the wavelength values.
+   }
+   \sstsynopsis{
+      AstGrismMap $*$astGrismMap( const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new GrismMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGrismMap()
+      }{
+         A pointer to the new GrismMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astHasAttribute
+}{
+   Test if an Object has a named attribute
+}{
+   \sstdescription{
+      This function returns a boolean result (0 or 1) to indicate
+      whether the supplied \htmlref{Object}{Object} has an attribute with the supplied name.
+   }
+   \sstsynopsis{
+      int astHasAttribute( AstObject $*$this, const char $*$attrib )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the first Object.
+      }
+      \sstsubsection{
+         attrib
+      }{
+         Pointer to a string holding the
+         name of the attribute to be tested.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astHasAttribute()
+      }{
+         One if the Object has the named attribute, otherwise zero.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astHasColumn
+}{
+   Returns a flag indicating if a column is present in a Table
+}{
+   \sstdescription{
+      This function
+      returns a flag indicating if a named column exists in a \htmlref{Table}{Table}, for
+      instance, by having been added to to the Table using
+      \htmlref{astAddColumn}{astAddColumn}.
+   }
+   \sstsynopsis{
+      int astHasColumn( AstTable $*$this, const char $*$column )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         column
+      }{
+         The character string holding the upper case name of the column. Trailing
+         spaces are ignored.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of
+         zero
+         is returned for if an error occurs.
+      }
+   }
+}
+\sstroutine{
+   astHasParameter
+}{
+   Returns a flag indicating if a named global parameter is present in a Table
+}{
+   \sstdescription{
+      This function
+      returns a flag indicating if a named parameter exists in a \htmlref{Table}{Table}, for
+      instance, by having been added to to the Table using
+      \htmlref{astAddParameter}{astAddParameter}.
+   }
+   \sstsynopsis{
+      int astHasParameter( AstTable $*$this, const char $*$parameter )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         parameter
+      }{
+         The character string holding the upper case name of the parameter. Trailing
+         spaces are ignored.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of
+         zero
+         is returned for if an error occurs.
+      }
+   }
+}
+\sstroutine{
+   astImport
+}{
+   Import an Object pointer to the current context
+}{
+   \sstdescription{
+      This function
+      imports an \htmlref{Object}{Object} pointer that was created in a higher or lower
+      level context, into the current AST context.
+      This means that the pointer will be annulled when the current context
+      is ended (with \htmlref{astEnd}{astEnd}).
+   }
+   \sstsynopsis{
+      void astImport( AstObject $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Object pointer to be imported.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+}
+\sstroutine{
+   astIntersect
+}{
+   Find the point of intersection between two geodesic curves
+}{
+   \sstdescription{
+      This function
+      finds the coordinate values at the point of intersection between
+      two geodesic curves. Each curve is specified by two points on
+      the curve.  It can only be used with 2-dimensional Frames.
+
+      For example, in a basic \htmlref{Frame}{Frame}, it will find the point of
+      intersection between two straight lines. But for a \htmlref{SkyFrame}{SkyFrame} it
+      will find an intersection of two great circles.
+   }
+   \sstsynopsis{
+      void astIntersect( AstFrame $*$this, const double a1[2],
+                         const double a2[2], const double b1[2],
+                         const double b2[2], double cross[2] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         a1
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). This should contain the coordinates of the
+         first point on the first geodesic curve.
+      }
+      \sstsubsection{
+         a2
+      }{
+         An array of double, with one element for each Frame axis
+         (Naxes attribute). This should contain the coordinates of a
+         second point on the first geodesic curve. It should not be
+         co-incident with the first point.
+      }
+      \sstsubsection{
+         b1
+      }{
+         An array of double, with one element for each Frame axis
+         (Naxes attribute). This should contain the coordinates of the
+         first point on the second geodesic curve.
+      }
+      \sstsubsection{
+         b2
+      }{
+         An array of double, with one element for each Frame axis
+         (Naxes attribute). This should contain the coordinates of a
+         second point on the second geodesic curve. It should not be
+         co-incident with the first point.
+      }
+      \sstsubsection{
+         cross
+      }{
+         An array of double, with one element for each Frame axis
+         in which the coordinates of the required intersection will
+         be returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For SkyFrames each curve will be a great circle, and in general
+         each pair of curves will intersect at two diametrically opposite
+         points on the sky. The returned position is the one which is
+         closest to point
+         \texttt{"} a1\texttt{"} .
+
+         \sstitem
+         This function will return \texttt{"} bad\texttt{"}  coordinate values (AST\_\_BAD)
+         if any of the input coordinates has this value, or if the two
+         points defining either geodesic are co-incident, or if the two
+         curves do not intersect.
+
+         \sstitem
+         The geodesic curve used by this function is the path of
+         shortest distance between two points, as defined by the
+         \htmlref{astDistance}{astDistance} function.
+
+         \sstitem
+         An error will be reported if the Frame is not 2-dimensional.
+      }
+   }
+}
+\sstroutine{
+   astInterval
+}{
+   Create a Interval
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Interval}{Interval} and optionally initialises its
+      attributes.
+
+      A Interval is a \htmlref{Region}{Region} which represents upper and/or lower limits on
+      one or more axes of a \htmlref{Frame}{Frame}. For a point to be within the region
+      represented by the Interval, the point must satisfy all the
+      restrictions placed on all the axes. The point is outside the region
+      if it fails to satisfy any one of the restrictions. Each axis may have
+      either an upper limit, a lower limit, both or neither. If both limits
+      are supplied but are in reverse order (so that the lower limit is
+      greater than the upper limit), then the interval is an excluded
+      interval, rather than an included interval.
+
+      At least one axis limit must be supplied.
+
+      Note, The Interval class makes no allowances for cyclic nature of
+      some coordinate systems (such as \htmlref{SkyFrame}{SkyFrame} coordinates). A \htmlref{Box}{Box}
+      should usually be used in these cases since this requires the user
+      to think about suitable upper and lower limits,
+   }
+   \sstsynopsis{
+      AstInterval $*$astInterval( AstFrame $*$frame, const double lbnd[],
+                                const double ubnd[], AstRegion $*$unc,
+                                const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         A pointer to the Frame in which the region is defined. A deep
+         copy is taken of the supplied Frame. This means that any
+         subsequent changes made to the Frame using the supplied pointer
+         will have no effect the Region.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute) containing the lower limits on each axis.
+         Set a value to AST\_\_BAD to indicate that the axis has no lower
+         limit.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         An array of double, with one element for each Frame axis
+         (Naxes attribute) containing the upper limits on each axis.
+         Set a value to AST\_\_BAD to indicate that the axis has no upper
+         limit.
+      }
+      \sstsubsection{
+         unc
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with the boundary of the Interval being created.
+         The uncertainty in any point on the boundary of the Interval is found by
+         shifting the supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at
+         the boundary point being considered. The area covered by the
+         shifted uncertainty Region then represents the uncertainty in the
+         boundary position. The uncertainty is assumed to be the same for
+         all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. Box, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Interval. Alternatively,
+         a NULL \htmlref{Object}{Object} pointer
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the Interval being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{astOverlap}{astOverlap}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{astSimplify}{astSimplify}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Interval. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astInterval()
+      }{
+         A pointer to the new Interval.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astIntraMap
+}{
+   Create an IntraMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{IntraMap}{IntraMap} and optionally initialises
+      its attributes.
+
+      An IntraMap is a specialised form of \htmlref{Mapping}{Mapping} which encapsulates
+      a privately-defined coordinate transformation function
+      (e.g. written in C) so that it may be used like any other AST
+      Mapping. This allows you to create Mappings that perform any
+      conceivable coordinate transformation.
+
+      However, an IntraMap is intended for use within a single program
+      or a private suite of software, where all programs have access
+      to the same coordinate transformation functions (i.e. can be
+      linked against them). IntraMaps should not normally be stored in
+      datasets which may be exported for processing by other software,
+      since that software will not have the necessary transformation
+      functions available, resulting in an error.
+
+      You must register any coordinate transformation functions to be
+      used using \htmlref{astIntraReg}{astIntraReg} before creating an IntraMap.
+   }
+   \sstsynopsis{
+      AstIntraMap $*$astIntraMap( const char $*$name, int nin, int nout,
+                                const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         name
+      }{
+         Pointer to a null-terminated string containing the name of
+         the transformation function to use (which should previously
+         have been registered using astIntraReg). This name is case
+         sensitive. All white space will be removed before use.
+      }
+      \sstsubsection{
+         nin
+      }{
+         The number of input coordinates. This must be compatible with
+         the number of input coordinates accepted by the
+         transformation function (as specified when this function was
+         registered using astIntraReg).
+      }
+      \sstsubsection{
+         nout
+      }{
+         The number of output coordinates. This must be compatible
+         with the number of output coordinates produced by the
+         transformation function (as specified when this function was
+         registered using astIntraReg).
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new IntraMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astIntraMap()
+      }{
+         A pointer to the new IntraMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astIntraReg
+}{
+   Register a transformation function for use by an IntraMap
+}{
+   \sstdescription{
+      This function registers a privately-defined coordinate
+      transformation function written in C so that it may be used to
+      create an \htmlref{IntraMap}{IntraMap}. An IntraMap is a specialised form of \htmlref{Mapping}{Mapping}
+      which encapsulates the C function so that it may be used like
+      any other AST Mapping. This allows you to create Mappings that
+      perform any conceivable coordinate transformation.
+
+      Registration of relevant transformation functions is required
+      before using the \htmlref{astIntraMap}{astIntraMap} constructor function to create an
+      IntraMap or reading an external representation of an IntraMap
+      from a \htmlref{Channel}{Channel}.
+   }
+   \sstsynopsis{
+      astIntraReg( const char $*$name, int nin, int nout,
+                   void ($*$ tran)( AstMapping $*$, int, int, const double $*$[],
+                                  int, int, double $*$[] ),
+                   unsigned int flags, const char $*$purpose, const char $*$author,
+                   const char $*$contact )
+   }
+   \sstparameters{
+      \sstsubsection{
+         name
+      }{
+         Pointer to a null-terminated string containing a unique name
+         to be associated with the transformation function in order to
+         identify it. This name is case sensitive. All white space
+         will be removed before use.
+      }
+      \sstsubsection{
+         nin
+      }{
+         The number of input coordinates accepted by the
+         transformation function (i.e. the number of dimensions of the
+         space in which the input points reside). A value of AST\_\_ANY
+         may be given if the function is able to accommodate a
+         variable number of input coordinates.
+      }
+      \sstsubsection{
+         nout
+      }{
+         The number of output coordinates produced by the
+         transformation function (i.e. the number of dimensions of the
+         space in which the output points reside). A value of AST\_\_ANY
+         may be given if the function is able to produce a variable
+         number of output coordinates.
+      }
+      \sstsubsection{
+         tran
+      }{
+         Pointer to the transformation function to be registered.
+         This function should perform whatever coordinate
+         transformations are required and should have an interface
+         like \htmlref{astTranP}{astTranP} (q.v.).
+      }
+      \sstsubsection{
+         flags
+      }{
+         This value may be used to supply a set of flags which
+         describe the transformation function and which may affect the
+         behaviour of any IntraMap which uses it.  Often, a value of
+         zero will be given here, but you may also supply the bitwise
+         OR of a set of flags as described in the \texttt{"} Transformation
+         Flags\texttt{"}  section (below).
+      }
+      \sstsubsection{
+         purpose
+      }{
+         Pointer to a null-terminated string containing a short (one
+         line) textual comment to describe the purpose of the
+         transformation function.
+      }
+      \sstsubsection{
+         author
+      }{
+         Pointer to a null-terminated string containing the name of
+         the author of the transformation function.
+      }
+      \sstsubsection{
+         contact
+      }{
+         Pointer to a null-terminated string containing contact
+         details for the author of the transformation function
+         (e.g. an e-mail or WWW address). If any IntraMap which uses
+         this transformation function is exported as part of a dataset
+         to an external user who does not have access to the function,
+         then these contact details should allow them to obtain the
+         necessary code.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Beware that an external representation of an IntraMap (created
+         by writing it to a Channel) will not include the coordinate
+         transformation function which it uses, so will only refer to the
+         function by its name (as assigned using astIntraReg).
+         Consequently, the external representation cannot be utilised by
+         another program unless that program has also registered the same
+         transformation function with the same name using an identical
+         invocation of astIntraReg. If no such registration has been
+         performed, then attempting to read the external representation
+         will result in an error.
+
+         \sstitem
+         You may use astIntraReg to register a transformation function
+         with the same name more than once, but only if the arguments
+         supplied are identical on each occasion (i.e there is no way of
+         changing things once a function has been successfully registered
+         under a given name, and attempting to do so will result in an
+         error). This feature simply allows registration to be performed
+         independently, but consistently, at several places within your
+         program, without having to check whether it has already been
+         done.
+
+         \sstitem
+         If an error occurs in the transformation function, this may be
+         indicated by setting the AST error status to an error value
+         (using \htmlref{astSetStatus}{astSetStatus}) before it returns.  This will immediately
+         terminate the current AST operation.  The error value AST\_\_ITFER
+         is available for this purpose, but other values may also be used
+         (e.g. if you wish to distinguish different types of error).
+      }
+   }
+   \sstdiytopic{
+      Transformation Flags
+   }{
+      The following flags are defined in the ``ast.h\texttt{'} \texttt{'}  header file and
+      allow you to provide further information about the nature of the
+      transformation function. Having selected the set of flags which
+      apply, you should supply the bitwise OR of their values as the
+      ``flags\texttt{'} \texttt{'}  argument to astIntraReg.
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_NOFWD: If this flag is set, it indicates that the
+         transformation function does not implement a forward coordinate
+         transformation. In this case, any IntraMap which uses it will
+         have a \htmlref{TranForward}{TranForward} attribute value of zero and the
+         transformation function itself will not be invoked with its
+         ``forward\texttt{'} \texttt{'}  argument set to a non-zero value.  By default, it is
+         assumed that a forward transformation is provided.
+
+         \sstitem
+         AST\_\_NOINV: If this flag is set, it indicates that the
+         transformation function does not implement an inverse coordinate
+         transformation. In this case, any IntraMap which uses it will
+         have a \htmlref{TranInverse}{TranInverse} attribute value of zero and the
+         transformation function itself will not be invoked with its
+         ``forward\texttt{'} \texttt{'}  argument set to zero.  By default, it is assumed
+         that an inverse transformation is provided.
+
+         \sstitem
+         AST\_\_SIMPFI: You may set this flag if applying the
+         transformation function\texttt{'} s forward coordinate transformation,
+         followed immediately by the matching inverse transformation,
+         should always restore the original set of coordinates. It
+         indicates that AST may replace such a sequence of operations by
+         an identity Mapping (a \htmlref{UnitMap}{UnitMap}) if it is encountered while
+         simplifying a compound Mapping (e.g. using \htmlref{astSimplify}{astSimplify}).  It is
+         not necessary that both transformations have actually been
+         implemented.
+
+         \sstitem
+         AST\_\_SIMPIF: You may set this flag if applying the
+         transformation function\texttt{'} s inverse coordinate transformation,
+         followed immediately by the matching forward transformation,
+         should always restore the original set of coordinates. It
+         indicates that AST may replace such a sequence of operations by
+         an identity Mapping (a UnitMap) if it is encountered while
+         simplifying a compound Mapping (e.g. using astSimplify).  It is
+         not necessary that both transformations have actually been
+         implemented.
+      }
+   }
+}
+\sstroutine{
+   astInvert
+}{
+   Invert a Mapping
+}{
+   \sstdescription{
+      This function inverts a \htmlref{Mapping}{Mapping} by reversing the boolean sense
+      of its \htmlref{Invert}{Invert} attribute. If this attribute is zero (the
+      default), the Mapping will transform coordinates in the way
+      specified when it was created. If it is non-zero, the input and
+      output coordinates will be inter-changed so that the direction
+      of the Mapping is reversed. This will cause it to display the
+      inverse of its original behaviour.
+   }
+   \sstsynopsis{
+      void astInvert( AstMapping $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping.
+      }
+   }
+}
+\sstroutine{
+   astIsA$<$Class$>$
+}{
+   Test membership of a class by an Object
+}{
+   \sstdescription{
+      This is a family of functions which test whether an \htmlref{Object}{Object} is a
+      member of the class called $<$\htmlref{Class}{Class}$>$, or of any class derived from
+      it.
+   }
+   \sstsynopsis{
+      int astIsA$<$Class$>$( const Ast$<$Class$>$ $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         These functions apply to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astIsA$<$Class$>$()
+      }{
+         One if the Object belongs to the class called $<$Class$>$ (or to a
+         class derived from it), otherwise zero.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         member = astIsAFrame( obj );
+      }{
+         Tests whether Object \texttt{"} obj\texttt{"}  is a member of the \htmlref{Frame}{Frame} class, or
+         of any class derived from a Frame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Every AST class provides a function (astIsA$<$Class$>$) of this
+         form, where $<$Class$>$ should be replaced by the class name.
+
+         \sstitem
+         This function attempts to execute even if the AST error status
+         is set
+         on entry, although no further error report will be made
+         if it subsequently fails under these circumstances.
+
+         \sstitem
+         A value of zero will be returned if this function should fail
+         for any reason. In particular, it will fail if the pointer
+         supplied does not identify an Object of any sort.
+      }
+   }
+}
+\sstroutine{
+   astKeyMap
+}{
+   Create a KeyMap
+}{
+   \sstdescription{
+      This function creates a new empty \htmlref{KeyMap}{KeyMap} and optionally initialises its
+      attributes. Entries can then be added to the KeyMap using the
+      \htmlref{astMapPut0$<$X$>$}{astMapPut0$<$X$>$} and \htmlref{astMapPut1$<$X$>$}{astMapPut1$<$X$>$} functions.
+
+      The KeyMap class is used to store a set of values with associated keys
+      which identify the values. The keys are strings. These may be case
+      sensitive or insensitive as selected by the \htmlref{KeyCase}{KeyCase} attribute, and
+      trailing spaces are ignored. The value associated with a key can be
+      integer (signed 4 and 2 byte, or unsigned 1 byte), floating point
+      (single or double precision),
+      void pointer,
+      character string or AST \htmlref{Object}{Object} pointer. Each
+      value can be a scalar or a one-dimensional vector. A KeyMap is
+      conceptually similar to a \htmlref{Mapping}{Mapping} in that a KeyMap transforms an
+      input into an output - the input is the key, and the output is the
+      value associated with the key. However, this is only a conceptual
+      similarity, and it should be noted that the KeyMap class inherits from
+      the Object class rather than the Mapping class. The methods of the
+      Mapping class cannot be used with a KeyMap.
+   }
+   \sstsynopsis{
+      AstKeyMap $*$astKeyMap( const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new KeyMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astKeyMap()
+      }{
+         A pointer to the new KeyMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astLinearApprox
+}{
+   Obtain a linear approximation to a Mapping, if appropriate
+}{
+   \sstdescription{
+      This function tests the forward coordinate transformation
+      implemented by a \htmlref{Mapping}{Mapping} over a given range of input coordinates. If
+      the transformation is found to be linear to a specified level of
+      accuracy, then an array of fit coefficients is returned. These
+      may be used to implement a linear approximation to the Mapping\texttt{'} s
+      forward transformation within the specified range of output coordinates.
+      If the transformation is not sufficiently linear, no coefficients
+      are returned.
+   }
+   \sstsynopsis{
+      int astLinearApprox( AstMapping $*$this, const double $*$lbnd,
+                           const double $*$ubnd, double tol, double $*$fit )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an array of doubles
+         containing the lower bounds of a box defined within the input
+         coordinate system of the Mapping. The number of elements in this
+         array should equal the value of the Mapping\texttt{'} s \htmlref{Nin}{Nin} attribute. This
+         box should specify the region over which linearity is required.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an array of doubles
+         containing the upper bounds of the box specifying the region over
+         which linearity is required.
+      }
+      \sstsubsection{
+         tol
+      }{
+         The maximum permitted deviation from linearity, expressed as
+         a positive Cartesian displacement in the output coordinate
+         space of the Mapping. If a linear fit to the forward
+         transformation of the Mapping deviates from the true transformation
+         by more than this amount at any point which is tested, then no fit
+         coefficients will be returned.
+      }
+      \sstsubsection{
+         fit
+      }{
+         Pointer to an array of doubles
+         in which to return the co-efficients of the linear
+         approximation to the specified transformation. This array should
+         have at least \texttt{"} ( Nin $+$ 1 ) $*$ \htmlref{Nout}{Nout}\texttt{"} , elements. The first Nout elements
+         hold the constant offsets for the transformation outputs. The
+         remaining elements hold the gradients. So if the Mapping has 2 inputs
+         and 3 outputs the linear approximation to the forward transformation
+         is:
+
+            X\_out = fit[0] $+$ fit[3]$*$X\_in $+$ fit[4]$*$Y\_in
+
+            Y\_out = fit[1] $+$ fit[5]$*$X\_in $+$ fit[6]$*$Y\_in
+
+            Z\_out = fit[2] $+$ fit[7]$*$X\_in $+$ fit[8]$*$Y\_in
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astLinearApprox()
+      }{
+         If the forward transformation is sufficiently linear,
+         a non-zero value is returned. Otherwise zero is returned
+         and the fit co-efficients are set to AST\_\_BAD.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function fits the Mapping\texttt{'} s forward transformation. To fit
+         the inverse transformation, the Mapping should be inverted using
+         \htmlref{astInvert}{astInvert}
+         before invoking this function.
+
+         \sstitem
+         A value of zero
+         will be returned if this function is invoked
+         with the global error status set, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   astLock
+}{
+   Lock an Object for exclusive use by the calling thread
+}{
+   \sstdescription{
+      The thread-safe public interface to AST is designed so that an
+      error is reported if any thread attempts to use an \htmlref{Object}{Object} that it
+      has not previously locked for its own exclusive use using this
+      function. When an Object is created, it is initially locked by the
+      thread that creates it, so newly created objects do not need to be
+      explicitly locked. However, if an Object pointer is passed to
+      another thread, the original thread must first unlock it (using
+      \htmlref{astUnlock}{astUnlock}) and the new thread must then lock it (using astLock)
+      before the new thread can use the Object.
+
+      The \texttt{"} wait\texttt{"}  parameter determines what happens if the supplied Object
+      is curently locked by another thread when this function is invoked.
+   }
+   \sstsynopsis{
+      void astLock( AstObject $*$this, int wait )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object to be locked.
+      }
+      \sstsubsection{
+         wait
+      }{
+         If the Object is curently locked by another thread then this
+         function will either report an error or block. If a non-zero value
+         is supplied for \texttt{"} wait\texttt{"} , the calling thread waits until the object
+         is available for it to use. Otherwise, an error is reported and
+         the function returns immediately without locking the Object.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The \htmlref{astAnnul}{astAnnul} function is exceptional in that it can be used on
+         pointers for Objects that are not currently locked by the calling
+         thread. All other AST functions will report an error.
+
+         \sstitem
+         The Locked object will belong to the current AST context.
+
+         \sstitem
+         This function returns without action if the Object is already
+         locked by the calling thread.
+
+         \sstitem
+         If simultaneous use of the same object is required by two or more
+         threads, \htmlref{astCopy}{astCopy} should be used to to produce a deep copy of
+         the Object for each thread. Each copy should then be unlocked by
+         the parent thread (i.e. the thread that created the copy), and then
+         locked by the child thread (i.e. the thread that wants to use the
+         copy).
+
+         \sstitem
+         This function is only available in the C interface.
+
+         \sstitem
+         This function returns without action if the AST library has
+         been built without POSIX thread support (i.e. the \texttt{"} -with-pthreads\texttt{"}
+         option was not specified when running the \texttt{"} configure\texttt{"}  script).
+      }
+   }
+}
+\sstroutine{
+   astLutMap
+}{
+   Create a LutMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{LutMap}{LutMap} and optionally initialises
+      its attributes.
+
+      A LutMap is a specialised form of \htmlref{Mapping}{Mapping} which transforms
+      1-dimensional coordinates by using linear interpolation in a
+      lookup table.  Each input coordinate value is first scaled to
+      give the index of an entry in the table by subtracting a
+      starting value (the input coordinate corresponding to the first
+      table entry) and dividing by an increment (the difference in
+      input coordinate value between adjacent table entries).
+
+      The resulting index will usually contain a fractional part, so
+      the output coordinate value is then generated by interpolating
+      linearly between the appropriate entries in the table. If the
+      index lies outside the range of the table, linear extrapolation
+      is used based on the two nearest entries (i.e. the two entries
+      at the start or end of the table, as appropriate).
+
+      If the lookup table entries increase or decrease monotonically,
+      then the inverse transformation may also be performed.
+   }
+   \sstsynopsis{
+      AstLutMap $*$astLutMap( int nlut, const double lut[],
+                            double start, double inc,
+                            const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         nlut
+      }{
+         The number of entries in the lookup table. This value must be
+         at least 2.
+      }
+      \sstsubsection{
+         lut
+      }{
+         An array containing the \texttt{"} nlut\texttt{"}
+         lookup table entries.
+      }
+      \sstsubsection{
+         start
+      }{
+         The input coordinate value which corresponds to the first lookup
+         table entry.
+      }
+      \sstsubsection{
+         inc
+      }{
+         The lookup table spacing (the increment in input coordinate
+         value between successive lookup table entries). This value
+         may be positive or negative, but must not be zero.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new LutMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astLutMap()
+      }{
+         A pointer to the new LutMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the entries in the lookup table either increase or decrease
+         monotonically, then the new LutMap\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute will
+         have a value of one, indicating that the inverse transformation
+         can be performed. Otherwise, it will have a value of zero, so
+         that any attempt to use the inverse transformation will result
+         in an error.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astMapBox
+}{
+   Find a bounding box for a Mapping
+}{
+   \sstdescription{
+      This function allows you to find the \texttt{"} bounding box\texttt{"}  which just
+      encloses another box after it has been transformed by a \htmlref{Mapping}{Mapping}
+      (using either its forward or inverse transformation). A typical
+      use might be to calculate the size of an image after being
+      transformed by a Mapping.
+
+      The function works on one dimension at a time. When supplied
+      with the lower and upper bounds of a rectangular region (box) of
+      input coordinate space, it finds the lowest and highest values
+      taken by a nominated output coordinate within that
+      region. Optionally, it also returns the input coordinates where
+      these bounding values are attained. It should be used repeatedly
+      to obtain the extent of the bounding box in more than one
+      dimension.
+   }
+   \sstsynopsis{
+      void astMapBox( AstMapping $*$this,
+                      const double lbnd\_in[], const double ubnd\_in[],
+                      int forward, int coord\_out,
+                      double $*$lbnd\_out, double $*$ubnd\_out,
+                      double xl[], double xu[] );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping.
+      }
+      \sstsubsection{
+         lbnd\_in
+      }{
+         Pointer to an array of double, with one element for each
+         Mapping input coordinate. This should contain the lower bound
+         of the input box in each input dimension.
+      }
+      \sstsubsection{
+         ubnd\_in
+      }{
+         Pointer to an array of double, with one element for each
+         Mapping input coordinate. This should contain the upper bound
+         of the input box in each input dimension.
+
+         Note that it is permissible for the upper bound to be less
+         than the corresponding lower bound, as the values will simply
+         be swapped before use.
+      }
+      \sstsubsection{
+         forward
+      }{
+         If this value is non-zero, then the Mapping\texttt{'} s forward
+         transformation will be used to transform the input
+         box. Otherwise, its inverse transformation will be used.
+
+         (If the inverse transformation is selected, then references
+         to \texttt{"} input\texttt{"}  and \texttt{"} output\texttt{"}  coordinates in this description
+         should be transposed. For example, the size of the \texttt{"} lbnd\_in\texttt{"}
+         and \texttt{"} ubnd\_in\texttt{"}  arrays should match the number of output
+         coordinates, as given by the Mapping\texttt{'} s \htmlref{Nout}{Nout}
+         attribute. Similarly, the \texttt{"} coord\_out\texttt{"}  parameter, below,
+         should nominate one of the Mapping\texttt{'} s input coordinates.)
+      }
+      \sstsubsection{
+         coord\_out
+      }{
+         The index of the output coordinate for which the lower and
+         upper bounds are required. This value should be at least one,
+         and no larger than the number of Mapping output coordinates.
+      }
+      \sstsubsection{
+         lbnd\_out
+      }{
+         Pointer to a double in which to return the lowest value taken
+         by the nominated output coordinate within the specified
+         region of input coordinate space.
+      }
+      \sstsubsection{
+         ubnd\_out
+      }{
+         Pointer to a double in which to return the highest value
+         taken by the nominated output coordinate within the specified
+         region of input coordinate space.
+      }
+      \sstsubsection{
+         xl
+      }{
+         An optional pointer to an array of double, with one element
+         for each Mapping input coordinate. If given, this array will
+         be filled with the coordinates of an input point (although
+         not necessarily a unique one) for which the nominated output
+         coordinate attains the lower bound value returned in
+         \texttt{"} $*$lbnd\_out\texttt{"} .
+
+         If these coordinates are not required, a NULL pointer may be
+         supplied.
+      }
+      \sstsubsection{
+         xu
+      }{
+         An optional pointer to an array of double, with one element
+         for each Mapping input coordinate. If given, this array will
+         be filled with the coordinates of an input point (although
+         not necessarily a unique one) for which the nominated output
+         coordinate attains the upper bound value returned in
+         \texttt{"} $*$ubnd\_out\texttt{"} .
+
+         If these coordinates are not required, a NULL pointer may be
+         supplied.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Any input points which are transformed by the Mapping to give
+         output coordinates containing the value AST\_\_BAD are regarded as
+         invalid and are ignored. They will make no contribution to
+         determining the output bounds, even although the nominated
+         output coordinate might still have a valid value at such points.
+
+         \sstitem
+         An error will occur if the required output bounds cannot be
+         found. Typically, this might happen if all the input points
+         which the function considers turn out to be invalid (see
+         above). The number of points considered before generating such
+         an error is quite large, so this is unlikely to occur by
+         accident unless valid points are restricted to a very small
+         subset of the input coordinate space.
+
+         \sstitem
+         The values returned via \texttt{"} lbnd\_out\texttt{"} , \texttt{"} ubnd\_out\texttt{"} , \texttt{"} xl\texttt{"}  and \texttt{"} xu\texttt{"}
+         will be set to the value AST\_\_BAD if this function should fail
+         for any reason. Their initial values on entry will not be
+         altered if the function is invoked with the AST error status
+         set.
+      }
+   }
+}
+\sstroutine{
+   astMapCopy
+}{
+   Copy entries from one KeyMap into another
+}{
+   \sstdescription{
+      This function
+      copies all entries from one \htmlref{KeyMap}{KeyMap} into another.
+   }
+   \sstsynopsis{
+      void astMapCopy( AstKeyMap $*$this, AstKeyMap $*$that )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the destination KeyMap.
+      }
+      \sstsubsection{
+         that
+      }{
+         Pointer to the source KeyMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Entries from the source KeyMap will replace any existing entries in
+         the destination KeyMap that have the same key.
+
+         \sstitem
+         The one exception to the above rule is that if a source entry
+         contains a scalar KeyMap entry, and the destination contains a
+         scalar KeyMap entry with the same key, then the source KeyMap entry
+         will be copied into the destination KeyMap entry using this function,
+         rather than simply replacing the destination KeyMap entry.
+
+         \sstitem
+         If the destination entry has a non-zero value for its \htmlref{MapLocked}{MapLocked}
+         attribute, then an error will be reported if the source KeyMap
+         contains any keys that do not already exist within the destination
+         KeyMap.
+      }
+   }
+}
+\sstroutine{
+   astMapDefined
+}{
+   Check if a KeyMap contains a defined value for a key
+}{
+   \sstdescription{
+      This function checks to see if a \htmlref{KeyMap}{KeyMap} contains a defined value for
+      a given key. If the key is present in the KeyMap but has an
+      undefined value it returns
+      zero (unlike \htmlref{astMapHasKey}{astMapHasKey} which would return non-zero).
+   }
+   \sstsynopsis{
+      int astMapDefined( AstKeyMap $*$this, const char $*$key );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         key
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored. The supplied string is converted to upper
+         case before use if the \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapDefined()
+      }{
+         A non-zero value
+         is returned if the requested key name is present in the KeyMap
+         and has a defined value.
+      }
+   }
+}
+\sstroutine{
+   astMapGet0$<$X$>$
+}{
+   Get a scalar value from a KeyMap
+}{
+   \sstdescription{
+      This is a set of functions for retrieving a scalar value from a \htmlref{KeyMap}{KeyMap}.
+      You should replace $<$X$>$ in the generic function name
+      astMapGet0$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+      The stored value is converted to the data type indiced by $<$X$>$
+      before being returned (an error is reported if it is not possible to
+      convert the stored value to the requested data type).
+   }
+   \sstsynopsis{
+      int astMapGet0$<$X$>$( AstKeyMap $*$this, const char $*$key, $<$X$>$type $*$value );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         key
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored. The supplied string is converted to upper
+         case before use if the \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         value
+      }{
+         A pointer to a buffer in which to return the requested value.
+         If the requested key is not found, or if it is found but has an
+         undefined value (see
+         \htmlref{astMapPutU}{astMapPutU}),
+         then the contents of the
+         buffer on entry to this function will be unchanged on exit.
+         For pointer types (\texttt{"} A\texttt{"}  and \texttt{"} C\texttt{"} ), the buffer should be a suitable
+         pointer, and the address of this pointer should be supplied as the
+         \texttt{"} value\texttt{"}  parameter.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapGet0$<$X$>$()
+      }{
+         A non-zero value
+         is returned if the requested key name was found, and does not have
+         an undefined value (see
+         astMapPutU). Zero
+         is returned otherwise.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No error is reported if the requested key cannot be found in the
+         given KeyMap, but a
+         zero
+         value will be returned as the function value. The supplied buffer
+         will be returned unchanged.
+
+         \sstitem
+         If the stored value is a vector value, then the first value in
+         the vector will be returned.
+
+         \sstitem
+         A string pointer returned by astMapGet0C is guaranteed to remain valid
+         and the string to which it points will not be over-written for a
+         total of 50 successive invocations of this function. After this,
+         the memory containing the string may be re-used, so a copy of
+         the string should be made if it is needed for longer than this. The
+         calling code should never attempt to free the returned pointer
+         (for instance, using \htmlref{astFree}{astFree}).
+
+         \sstitem
+         If the returned value is an AST \htmlref{Object}{Object} pointer, the Object\texttt{'} s reference
+         count is incremented by this call. Any subsequent changes made to
+         the Object using the returned pointer will be reflected in any
+         any other active pointers for the Object. The returned pointer
+         should be annulled using
+         \htmlref{astAnnul}{astAnnul}
+         when it is no longer needed.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      function, you should replace $<$X$>$ in the generic function name astMapGet0$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         F: float
+
+         \sstitem
+         D: double
+
+         \sstitem
+         I: int
+
+         \sstitem
+         C: \texttt{"} const\texttt{"}  pointer to null terminated character string
+
+         \sstitem
+         A: Pointer to AstObject
+
+         \sstitem
+         P: Generic \texttt{"} void $*$\texttt{"}  pointer
+
+         \sstitem
+         S: short int
+
+         \sstitem
+         B: Unsigned byte (i.e. word)
+
+      }
+      For example, astMapGet0D would be used to get a \texttt{"} double\texttt{"}  value,
+      while astMapGet0I would be used to get an \texttt{"} int\texttt{"} , etc.
+   }
+}
+\sstroutine{
+   astMapGet1$<$X$>$
+}{
+   Get a vector value from a KeyMap
+}{
+   \sstdescription{
+      This is a set of functions for retrieving a vector value from a \htmlref{KeyMap}{KeyMap}.
+      You should replace $<$X$>$ in the generic function name
+      astMapGet1$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+      The stored value is converted to the data type indiced by $<$X$>$
+      before being returned (an error is reported if it is not possible to
+      convert the stored value to the requested data type).
+      Note, the astMapGet1C function has an extra parameter \texttt{"} l\texttt{"}  which
+      specifies the maximum length of each string to be stored in the
+      \texttt{"} value\texttt{"}  buffer (see the \texttt{"} astMapGet1C\texttt{"}  section below).
+   }
+   \sstsynopsis{
+      int astMapGet1$<$X$>$( AstKeyMap $*$this, const char $*$key, int mxval,
+                         int $*$nval, $<$X$>$type $*$value )
+      int astMapGet1C( AstKeyMap $*$this, const char $*$key, int l, int mxval,
+                       int $*$nval, const char $*$value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         key
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         mxval
+      }{
+         The number of elements in the
+         \texttt{"} value\texttt{"}  array.
+      }
+      \sstsubsection{
+         nval
+      }{
+         The address of an integer in which to put the
+         number of elements stored in the
+         \texttt{"} value\texttt{"}  array.
+         Any unused elements of the array are left unchanged.
+      }
+      \sstsubsection{
+         value
+      }{
+         A pointer to an array in which to return the requested values.
+         If the requested key is not found, or if it is found but has an
+         undefined value (see
+         \htmlref{astMapPutU}{astMapPutU}),
+         then the contents of the
+         buffer on entry to this function will be unchanged on exit.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapGet1$<$X$>$()
+      }{
+         A non-zero value
+         is returned if the requested key name was found, and does not have
+         an undefined value (see
+         astMapPutU). Zero
+         is returned otherwise.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No error is reported if the requested key cannot be found in the
+         given KeyMap, but a
+         zero
+         value will be returned as the function value. The supplied array
+         will be returned unchanged.
+
+         \sstitem
+         If the stored value is a scalar value, then the value will be
+         returned in the first element of the supplied array, and
+         \texttt{"} nval\texttt{"}
+         will be returned set to 1.
+      }
+   }
+   \sstdiytopic{
+      astMapGet1C
+   }{
+      The \texttt{"} value\texttt{"}  buffer supplied to the astMapGet1C function should be a
+      pointer to a character array with \texttt{"} mxval$*$l\texttt{"}  elements, where \texttt{"} l\texttt{"}  is
+      the maximum length of a string to be returned. The value of \texttt{"} l\texttt{"}
+      should be supplied as an extra parameter following \texttt{"} key\texttt{"}  when
+      invoking astMapGet1C, and should include space for a terminating
+      null character.
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      function, you should replace $<$X$>$ in the generic function name astMapGet1$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         I: int
+
+         \sstitem
+         C: \texttt{"} const\texttt{"}  pointer to null terminated character string
+
+         \sstitem
+         A: Pointer to AstObject
+
+         \sstitem
+         P: Generic \texttt{"} void $*$\texttt{"}  pointer
+
+         \sstitem
+         S: short int
+
+         \sstitem
+         B: Unsigned byte (i.e. char)
+
+      }
+      For example, astMapGet1D would be used to get \texttt{"} double\texttt{"}  values, while
+      astMapGet1I would be used to get \texttt{"} int\texttt{"}  values, etc. For D or I, the
+      supplied \texttt{"} value\texttt{"}  parameter should be a pointer to an array of doubles
+      or ints, with \texttt{"} mxval\texttt{"}  elements. For C, the supplied \texttt{"} value\texttt{"}  parameter
+      should be a pointer to a character string with \texttt{"} mxval$*$l\texttt{"}  elements.
+      For A, the supplied \texttt{"} value\texttt{"}  parameter should be a pointer to an
+      array of AstObject pointers.
+   }
+}
+\sstroutine{
+   astMapGetElem$<$X$>$
+}{
+   Get a single element of a vector value from a KeyMap
+}{
+   \sstdescription{
+      This is a set of functions for retrieving a single element of a vector
+      value from a \htmlref{KeyMap}{KeyMap}. You should replace $<$X$>$ in the generic function name
+      astMapGetElem$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+      The stored value is converted to the data type indiced by $<$X$>$
+      before being returned (an error is reported if it is not possible to
+      convert the stored value to the requested data type).
+      Note, the astMapGetElemC function has an extra parameter \texttt{"} l\texttt{"}  which
+      specifies the maximum length of the string to be stored in the
+      \texttt{"} value\texttt{"}  buffer (see the \texttt{"} astMapGetElemC\texttt{"}  section below).
+   }
+   \sstsynopsis{
+      int astMapGetElem$<$X$>$( AstKeyMap $*$this, const char $*$key, int elem,
+                            $<$X$>$type $*$value )
+      int astMapGetElemC( AstKeyMap $*$this, const char $*$key, int l, int elem,
+                          char $*$value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         key
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         elem
+      }{
+         The index of the required vector element, starting at
+         zero.
+         An error will be reported if the value is outside the range of
+         the vector.
+      }
+      \sstsubsection{
+         value
+      }{
+         A pointer to a buffer in which to return the requested value.
+         If the requested key is not found, or if it is found but has an
+         undefined value (see
+         \htmlref{astMapPutU}{astMapPutU}),
+         then the contents of the
+         buffer on entry to this function will be unchanged on exit.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapGetElem$<$X$>$()
+      }{
+         A non-zero value
+         is returned if the requested key name was found, and does not have
+         an undefined value (see
+         astMapPutU). Zero
+         is returned otherwise.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No error is reported if the requested key cannot be found in the
+         given KeyMap, or if it has an undefined value, but a
+         zero
+         value will be returned as the function value.
+      }
+   }
+   \sstdiytopic{
+      astMapGetElemC
+   }{
+      The \texttt{"} value\texttt{"}  buffer supplied to the astMapGetElemC function should be a
+      pointer to a character array with \texttt{"} l\texttt{"}  elements, where \texttt{"} l\texttt{"}  is the
+      maximum length of the string to be returned. The value of \texttt{"} l\texttt{"}
+      should be supplied as an extra parameter following \texttt{"} key\texttt{"}  when
+      invoking astMapGetElemC, and should include space for a terminating
+      null character.
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      function, you should replace $<$X$>$ in the generic function name
+      astMapGetElem$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         I: int
+
+         \sstitem
+         C: \texttt{"} const\texttt{"}  pointer to null terminated character string
+
+         \sstitem
+         A: Pointer to AstObject
+
+         \sstitem
+         P: Generic \texttt{"} void $*$\texttt{"}  pointer
+
+         \sstitem
+         S: short int
+
+         \sstitem
+         B: Unsigned byte (i.e. char)
+
+      }
+      For example, astMapGetElemD would be used to get a \texttt{"} double\texttt{"}  value, while
+      astMapGetElemI would be used to get an \texttt{"} int\texttt{"}  value, etc. For D or I, the
+      supplied \texttt{"} value\texttt{"}  parameter should be a pointer to a double or int. For
+      C, the supplied \texttt{"} value\texttt{"}  parameter should be a pointer to a character
+      string with \texttt{"} l\texttt{"}  elements. For A, the supplied \texttt{"} value\texttt{"}  parameter should
+      be a pointer to an AstObject pointer.
+   }
+}
+\sstroutine{
+   astMapHasKey
+}{
+   Check if an entry with a given key exists in a KeyMap
+}{
+   \sstdescription{
+      This function returns a flag indicating if the \htmlref{KeyMap}{KeyMap} contains an
+      entry with the given key.
+   }
+   \sstsynopsis{
+      int astMapHasKey( AstKeyMap $*$this, const char $*$key )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         key
+      }{
+         The character string identifying the KeyMap entry. Trailing spaces are
+         ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapHasKey()
+      }{
+         Non-zero if the key was found, and zero otherwise.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A non-zero function value
+         is returned if the key exists but has an undefined value (that is,
+         the returned value does not depend on whether the entry has a
+         defined value or not). See also
+         \htmlref{astMapDefined}{astMapDefined}, which returns zero in such a case.
+
+         \sstitem
+         A function value of
+         zero
+         will be returned if an error has already occurred, or if this
+         function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astMapKey
+}{
+   Get the key at a given index within the KeyMap
+}{
+   \sstdescription{
+      This function returns a string holding the key for the entry with
+      the given index within the \htmlref{KeyMap}{KeyMap}.
+
+      This function is intended primarily as a means of iterating round all
+      the elements in a KeyMap. For this purpose, the number of entries in
+      the KeyMap should first be found using
+      \htmlref{astMapSize}{astMapSize}
+      and this function should then be called in a loop, with the index
+      value going from
+      zero to one less than the size of the KeyMap.
+      The index associated with a given entry is determined by the \htmlref{SortBy}{SortBy}
+      attribute.
+   }
+   \sstsynopsis{
+      const char $*$astMapKey( AstKeyMap $*$this, int index )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         index
+      }{
+         The index into the KeyMap. The first entry has index
+         zero, and the last has index \texttt{"} size-1\texttt{"} , where \texttt{"} size\texttt{"}  is the value
+         returned by the astMapSize function.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapKey()
+      }{
+         A pointer to a null-terminated string containing the key.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned pointer is guaranteed to remain valid and the
+         string to which it points will not be over-written for a total
+         of 50 successive invocations of this function. After this, the
+         memory containing the string may be re-used, so a copy of the
+         string should be made if it is needed for longer than this.
+
+         \sstitem
+         A NULL pointer will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   astMapLenC
+}{
+   Get the number of characters in a character entry in a KeyMap
+}{
+   \sstdescription{
+      This function returns the minimum length which a character variable
+      which must have in order to be able to store a specified entry in
+      the supplied \htmlref{KeyMap}{KeyMap}. If the named entry is a vector entry, then the
+      returned value is the length of the longest element of the vector
+      value.
+   }
+   \sstsynopsis{
+      int astMapLenC( AstKeyMap $*$this, const char $*$key )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         key
+      }{
+         The character string identifying the KeyMap entry. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapLenC()
+      }{
+         The length (i.e. number of characters) of the longest formatted
+         value associated with the named entry.
+         This does not include the trailing null character.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A function value of zero will be returned without error if the
+         named entry cannot be formatted as a character string.
+
+         \sstitem
+         A function value of zero will be returned if an error has already
+         occurred, or if this function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astMapLength
+}{
+   Get the vector length of an entry in a KeyMap
+}{
+   \sstdescription{
+      This function returns the vector length of a named entry in a \htmlref{KeyMap}{KeyMap},
+      (that is, how many values are associated with the entry).
+   }
+   \sstsynopsis{
+      int astMapLength( AstKeyMap $*$this, const char $*$key )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         key
+      }{
+         The character string identifying the KeyMap entry. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapLength()
+      }{
+         The length of the entry. One for a scalar, greater than one for
+         a vector. A value of zero is returned if the KeyMap does not
+         contain the named entry.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A function value of zero will be returned if an error has already
+         occurred, or if this function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astMapPut0$<$X$>$
+}{
+   Add a scalar value to a KeyMap
+}{
+   \sstdescription{
+      This is a set of functions
+      for adding scalar values to a \htmlref{KeyMap}{KeyMap}. You should use a
+      function
+      which matches the data type of the data you wish to add to the KeyMap
+      by replacing $<$X$>$ in the generic
+      function name astMapPut0$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+   }
+   \sstsynopsis{
+      void astMapPut0$<$X$>$( AstKeyMap $*$this, const char $*$key, $<$X$>$type value,
+                          const char $*$comment );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap in which to store the supplied value.
+      }
+      \sstsubsection{
+         key
+      }{
+         A character string to be stored with the value, which can later
+         be used to identify the value. Trailing spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         value
+      }{
+         The value to be stored. The data type of this value should match the
+         1-character type code appended to the
+         function name (e.g. if you are using astMapPut0A, the type of this
+         value should be \texttt{"} pointer to AstObject\texttt{"} ).
+      }
+      \sstsubsection{
+         comment
+      }{
+         A pointer to a null-terminated comment string to be stored with the
+         value. A NULL pointer may be supplied, in which case no comment is
+         stored.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the supplied key is already in use in the KeyMap, the new value
+         will replace the old value.
+
+         \sstitem
+         If the stored value is an AST \htmlref{Object}{Object} pointer, the Object\texttt{'} s reference
+         count is incremented by this call. Any subsequent changes made to
+         the Object using the returned pointer will be reflected in any
+         any other active pointers for the Object, including any obtained
+         later using
+         astMapget0A.
+         The reference count for the Object will be decremented when the
+         KeyMap is destroyed, or the entry is removed or over-written with a
+         different pointer.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      function, you should replace $<$X$>$ in the generic function name astMapPut0$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         I: int
+
+         \sstitem
+         C: \texttt{"} const\texttt{"}  pointer to null terminated character string
+
+         \sstitem
+         A: Pointer to AstObject
+
+         \sstitem
+         P: Generic \texttt{"} void $*$\texttt{"}  pointer
+
+         \sstitem
+         S: short int
+
+         \sstitem
+         B: unsigned byte (i.e. unsigned char)
+
+      }
+      For example, astMapPut0D would be used to store a \texttt{"} double\texttt{"}  value,
+      while astMapPut0I would be used to store an \texttt{"} int\texttt{"} , etc.
+
+      Note that KeyMaps containing generic \texttt{"} void $*$\texttt{"}  pointers cannot be
+      written out using \htmlref{astShow}{astShow} or \htmlref{astWrite}{astWrite}. An error will be reported if
+      this is attempted.
+   }
+}
+\sstroutine{
+   astMapPut1$<$X$>$
+}{
+   Add a vector value to a KeyMap
+}{
+   \sstdescription{
+      This is a set of functions
+      for adding vector values to a \htmlref{KeyMap}{KeyMap}. You should use a
+      function
+      which matches the data type of the data you wish to add to the KeyMap
+      by replacing $<$X$>$ in the generic
+      function name astMapPut1$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+   }
+   \sstsynopsis{
+      void astMapPut1$<$X$>$( AstKeyMap $*$this, const char $*$key, int size,
+                          const $<$X$>$type value[], const char $*$comment );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap in which to store the supplied values.
+      }
+      \sstsubsection{
+         key
+      }{
+         A character string to be stored with the values, which can later
+         be used to identify the values. Trailing spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         size
+      }{
+         The number of elements in the supplied array of values.
+      }
+      \sstsubsection{
+         value
+      }{
+         The array of values to be stored. The data type of this value should
+         match the 1-character type code appended to the
+         function name (e.g. if you are using astMapPut1A, the type of this
+         value should be \texttt{"} array of pointers to AstObject\texttt{"} ).
+      }
+      \sstsubsection{
+         comment
+      }{
+         A pointer to a null-terminated comment string to be stored with the
+         values. A NULL pointer may be supplied, in which case no comment is
+         stored.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the supplied key is already in use in the KeyMap, the new values
+         will replace the old values.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      function, you should replace $<$X$>$ in the generic function name astMapPut1$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         I: int
+
+         \sstitem
+         C: \texttt{"} const\texttt{"}  pointer to null terminated character string
+
+         \sstitem
+         A: Pointer to AstObject
+
+         \sstitem
+         P: Generic \texttt{"} void $*$\texttt{"}  pointer
+
+         \sstitem
+         S: short int
+
+         \sstitem
+         B: Unsigned byte (i.e. char)
+
+      }
+      For example, astMapPut1D would be used to store \texttt{"} double\texttt{"}  values,
+      while astMapPut1I would be used to store \texttt{"} int\texttt{"} , etc.
+
+      Note that KeyMaps containing generic \texttt{"} void $*$\texttt{"}  pointers cannot be
+      written out using \htmlref{astShow}{astShow} or \htmlref{astWrite}{astWrite}. An error will be reported if
+      this is attempted.
+   }
+}
+\sstroutine{
+   astMapPutElem$<$X$>$
+}{
+   Put a value into an element of a vector value in a KeyMap
+}{
+   \sstdescription{
+      This is a set of functions for storing a value in a single element of
+      a vector value in a \htmlref{KeyMap}{KeyMap}. You should replace $<$X$>$ in the generic
+      function name
+      astMapPutElem$<$X$>$
+      by an appropriate 1-character type code (see the \texttt{"} Data Type Codes\texttt{"}
+      section below for the code appropriate to each supported data type).
+      The supplied value is converted from the data type indicated by $<$X$>$
+      to the data type of the KeyMap entry before being stored (an error
+      is reported if it is not possible to convert the value to the
+      required data type).
+   }
+   \sstsynopsis{
+      void astMapPutElem$<$X$>$( AstKeyMap $*$this, const char $*$key, int elem,
+                             $<$X$>$type $*$value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         key
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         elem
+      }{
+         The index of the vector element to modify, starting at
+         zero.
+      }
+      \sstsubsection{
+         value
+      }{
+         The value to store.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         If the
+         \texttt{"} elem\texttt{"}
+         index is outside the range of the vector, the length of
+         the vector will be increased by one element and the supplied
+         value will be stored at the end of the vector in the new element.
+      }
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         If the
+         \texttt{"} elem\texttt{"}
+         index is outside the range of the vector, an error will be
+         reported. The number of elements in each cell of a column is
+         specified when the column is created using
+         \htmlref{astAddColumn}{astAddColumn}.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the entry originally holds a scalar value, it will be treated
+         like a vector entry of length 1.
+
+         \sstitem
+         If the specified key cannot be found in the given KeyMap, or is
+         found but has an undefined value, a new
+         vector entry with the given name, and data type implied by $<$X$>$, is
+         created and the supplied value is stored in its first entry.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate
+      function, you should replace $<$X$>$ in the generic function name
+      astMapPutElem$<$X$>$
+      with a 1-character data type code, so as to match the data type $<$X$>$type
+      of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         I: int
+
+         \sstitem
+         C: \texttt{"} const\texttt{"}  pointer to null terminated character string
+
+         \sstitem
+         A: Pointer to AstObject
+
+         \sstitem
+         P: Generic \texttt{"} void $*$\texttt{"}  pointer
+
+         \sstitem
+         S: short int
+
+         \sstitem
+         B: Unsigned byte (i.e. char)
+
+      }
+      For example, astMapPutElemD would be used to put a \texttt{"} double\texttt{"}  value, while
+      astMapPutElemI would be used to put an \texttt{"} int\texttt{"}  value, etc. For D or I, the
+      supplied \texttt{"} value\texttt{"}  parameter should be a double or int. For
+      C, the supplied \texttt{"} value\texttt{"}  parameter should be a pointer to a character
+      string. For A, the supplied \texttt{"} value\texttt{"}  parameter should be an AstObject
+      pointer.
+   }
+}
+\sstroutine{
+   astMapPutU
+}{
+   Add an entry to a KeyMap with an undefined value
+}{
+   \sstdescription{
+      This function
+      adds a new entry to a \htmlref{KeyMap}{KeyMap}, but no value is stored with the
+      entry. The entry therefore has a special data type represented by
+      symbolic constant AST\_\_UNDEFTYPE.
+
+      An example use is to add entries with undefined values to a KeyMap
+      prior to locking them with the \htmlref{MapLocked}{MapLocked} attribute. Such entries
+      can act as placeholders for values that can be added to the KeyMap
+      later.
+   }
+   \sstsynopsis{
+      void astMapPutU( AstKeyMap $*$this, const char $*$key, const char $*$comment );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap in which to store the supplied value.
+      }
+      \sstsubsection{
+         key
+      }{
+         A character string to be stored with the value, which can later
+         be used to identify the value. Trailing spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         comment
+      }{
+         A pointer to a null-terminated comment string to be stored with the
+         value. A NULL pointer may be supplied, in which case no comment is
+         stored.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the supplied key is already in use in the KeyMap, the value
+         associated with the key will be removed.
+      }
+   }
+}
+\sstroutine{
+   astMapRegion
+}{
+   Transform a Region into a new Frame using a given Mapping
+}{
+   \sstdescription{
+      This function returns a pointer to a new \htmlref{Region}{Region} which corresponds to
+      supplied Region described by some other specified coordinate system. A
+      \htmlref{Mapping}{Mapping} is supplied which transforms positions between the old and new
+      coordinate systems. The new Region may not be of the same class as
+      the original region.
+   }
+   \sstsynopsis{
+      AstRegion $*$astMapRegion( AstRegion $*$this, AstMapping $*$map,
+                               AstFrame $*$frame )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         map
+      }{
+         Pointer to a Mapping which transforms positions from the
+         coordinate system represented by the supplied Region to the
+         coordinate system specified by
+         \texttt{"} frame\texttt{"} .
+         The supplied Mapping should define both forward and inverse
+         transformations, and these transformations should form a genuine
+         inverse pair. That is, transforming a position using the forward
+         transformation and then using the inverse transformation should
+         produce the original input position. Some Mapping classes (such
+         as \htmlref{PermMap}{PermMap}, \htmlref{MathMap}{MathMap}, \htmlref{SphMap}{SphMap}) can result in Mappings for which this
+         is not true.
+      }
+      \sstsubsection{
+         frame
+      }{
+         Pointer to a \htmlref{Frame}{Frame} describing the coordinate system in which
+         the new Region is required.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapRegion()
+      }{
+         A pointer to a new Region. This Region will represent the area
+         within the coordinate system specified by
+         \texttt{"} frame\texttt{"}
+         which corresponds to the supplied Region.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The uncertainty associated with the supplied Region is modified
+         using the supplied Mapping.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astMapRemove
+}{
+   Removed a named entry from a KeyMap
+}{
+   \sstdescription{
+      This function
+      removes a named entry from a \htmlref{KeyMap}{KeyMap}. It returns without action if the
+      KeyMap does not contain the specified key.
+   }
+   \sstsynopsis{
+      void astMapRemove( AstKeyMap $*$this, const char $*$key )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         key
+      }{
+         The character string identifying the value to be retrieved. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+   }
+}
+\sstroutine{
+   astMapRename
+}{
+   Rename an existing KeyMap entry
+}{
+   \sstdescription{
+      This function
+      associated a new key with an existing entry in a \htmlref{KeyMap}{KeyMap}. It returns
+      without action if the oldkey does not exist in the KeyMap.
+   }
+   \sstsynopsis{
+      void astMapRename( AstKeyMap $*$this, const char $*$oldkey, const char $*$newkey )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         oldkey
+      }{
+         The character string identifying the entry to be renamed. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+      \sstsubsection{
+         newkey
+      }{
+         The new character string to associated with the renamed entry.
+         Trailing spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         KeyCase attribute is currently set to zero.
+      }
+   }
+}
+\sstroutine{
+   astMapSize
+}{
+   Get the number of entries in a KeyMap
+}{
+   \sstdescription{
+      This function returns the number of entries in a \htmlref{KeyMap}{KeyMap}.
+   }
+   \sstsynopsis{
+      int astMapSize( AstKeyMap $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapSize()
+      }{
+         The number of entries in the KeyMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A function value of zero will be returned if an error has already
+         occurred, or if this function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astMapSplit
+}{
+   Split a Mapping up into parallel component Mappings
+}{
+   \sstdescription{
+      This function
+      creates a new \htmlref{Mapping}{Mapping} which connects specified inputs within a
+      supplied Mapping to the corresponding outputs of the supplied Mapping.
+      This is only possible if the specified inputs correspond to some
+      subset of the Mapping outputs. That is, there must exist a subset of
+      the Mapping outputs for which each output depends only on the selected
+      Mapping inputs, and not on any of the inputs which have not been
+      selected. Also, any output which is not in this subset must not depend
+      on any of the selected inputs. If these conditions are not met by the
+      supplied Mapping, then
+      a NULL
+      Mapping pointer is returned.
+   }
+   \sstsynopsis{
+      void astMapSplit( AstMapping $*$this, int nin, const int $*$in, int $*$out,
+                        AstMapping $*$$*$map )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping to be split.
+      }
+      \sstsubsection{
+         nin
+      }{
+         The number of inputs to pick from \texttt{"} this\texttt{"} .
+      }
+      \sstsubsection{
+         in
+      }{
+         Pointer to an
+         array holding the indices within the supplied Mapping of the inputs
+         which are to be picked from the Mapping.
+         This array should have \texttt{"} nin\texttt{"}  elements.
+         If \texttt{"} \htmlref{Nin}{Nin}\texttt{"}  is the number of inputs of the supplied Mapping, then each
+         element should have a value in the range 1 to Nin.
+      }
+      \sstsubsection{
+         out
+      }{
+         Pointer to an
+         array in which to return the indices of the outputs of the supplied
+         Mapping which are fed by the picked inputs. A value of one is
+         used to refer to the first Mapping output. The supplied array should
+         have a length at least equal to the number of outputs in the
+         supplied Mapping. The number of values stored in the array on
+         exit will equal the number of outputs in the returned Mapping.
+         The i\texttt{'} th element in the returned array holds the index within
+         the supplied Mapping which corresponds to the i\texttt{'} th output of
+         the returned Mapping.
+      }
+      \sstsubsection{
+         map
+      }{
+         Address of a location at which to return a pointer to the
+         returned Mapping. This Mapping will have
+         \texttt{"} nin\texttt{"}  inputs (the number of outputs may be different to \texttt{"} nin\texttt{"} ). NULL
+         is returned if the supplied Mapping has no subset of outputs which
+         depend only on the selected inputs. The returned Mapping is a
+         deep copy of the required parts of the supplied Mapping.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If this
+         function
+         is invoked with the global error status set, or if it should fail for
+         any reason, then
+         a NULL value
+         will be returned for
+         the \texttt{"} map\texttt{"}  pointer.
+      }
+   }
+}
+\sstroutine{
+   astMapType
+}{
+   Get the data type of an entry in a KeyMap
+}{
+   \sstdescription{
+      This function returns a value indicating the data type of a
+      named entry in a \htmlref{KeyMap}{KeyMap}. This is the data type which was used when the
+      entry was added to the KeyMap.
+   }
+   \sstsynopsis{
+      int astMapType( AstKeyMap $*$this, const char $*$key )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the KeyMap.
+      }
+      \sstsubsection{
+         key
+      }{
+         The character string identifying the KeyMap entry. Trailing
+         spaces are ignored.
+         The supplied string is converted to upper case before use if the
+         \htmlref{KeyCase}{KeyCase} attribute is currently set to zero.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMapType()
+      }{
+         One of AST\_\_INTTYPE (for integer), AST\_\_SINTTYPE (for
+         short int),
+         AST\_\_BYTETYPE (for unsigned bytes
+         \sstitemlist{
+
+            \sstitem
+            i.e. unsigned chars
+            ) AST\_\_DOUBLETYPE (for double
+            precision floating point), AST\_\_FLOATTYPE (for single
+            precision floating point), AST\_\_STRINGTYPE (for character string),
+            AST\_\_OBJECTTYPE (for AST \htmlref{Object}{Object} pointer), AST\_\_POINTERTYPE (for
+            arbitrary C pointer) or AST\_\_UNDEFTYPE (for undefined values
+            created by
+            \htmlref{astMapPutU}{astMapPutU}).
+            AST\_\_BADTYPE is returned if the supplied key is not found in the KeyMap.
+         }
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A function value of AST\_\_BADTYPE will be returned if an error has
+         already occurred, or if this function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astMark
+}{
+   Draw a set of markers for a Plot
+}{
+   \sstdescription{
+      This function draws a set of markers (symbols) at positions
+      specified in the physical coordinate system of a \htmlref{Plot}{Plot}. The
+      positions are transformed into graphical coordinates to
+      determine where the markers should appear within the plotting
+      area.
+   }
+   \sstsynopsis{
+      void astMark( AstPlot $*$this, int nmark, int ncoord, int indim,
+                    const double $*$in, int type )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         nmark
+      }{
+         The number of markers to draw. This may be zero, in which
+         case nothing will be drawn.
+      }
+      \sstsubsection{
+         ncoord
+      }{
+         The number of coordinates being supplied for each mark
+         (i.e. the number of axes in the current \htmlref{Frame}{Frame} of the Plot, as
+         given by its \htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         indim
+      }{
+         The number of elements along the second dimension of the \texttt{"} in\texttt{"}
+         array (which contains the marker coordinates). This value is
+         required so that the coordinate values can be correctly
+         located if they do not entirely fill this array. The value
+         given should not be less than \texttt{"} nmark\texttt{"} .
+      }
+      \sstsubsection{
+         in
+      }{
+         The address of the first element of a 2-dimensional array of
+         shape \texttt{"} [ncoord][indim]\texttt{"}  giving the
+         physical coordinates of the points where markers are to be
+         drawn. These should be stored such that the value of
+         coordinate number \texttt{"} coord\texttt{"}  for input mark number \texttt{"} mark\texttt{"}  is
+         found in element \texttt{"} in[coord][mark]\texttt{"} .
+      }
+      \sstsubsection{
+         type
+      }{
+         A value specifying the type (e.g. shape) of marker to be
+         drawn. The set of values which may be used (and the shapes
+         that will result) is determined by the underlying graphics
+         system.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Markers are not drawn at positions which have any coordinate
+         equal to the value AST\_\_BAD (or where the transformation into
+         graphical coordinates yields coordinates containing the value
+         AST\_\_BAD).
+
+         \sstitem
+         If any marker position is clipped (see \htmlref{astClip}{astClip}), then the
+         entire marker is not drawn.
+
+         \sstitem
+         An error results if the base Frame of the Plot is not 2-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   astMask$<$X$>$
+}{
+   Mask a region of a data grid
+}{
+   \sstdescription{
+      This is a set of functions for masking out regions within gridded data
+      (e.g. an image). The functions modifies a given data grid by
+      assigning a specified value to all samples which are inside (or outside
+      if \texttt{"} inside\texttt{"}  is zero)
+      the specified \htmlref{Region}{Region}.
+
+      You should use a masking function which matches the numerical
+      type of the data you are processing by replacing $<$X$>$ in
+      the generic function name astMask$<$X$>$ by an appropriate 1- or
+      2-character type code. For example, if you are masking data
+      with type \texttt{"} float\texttt{"} , you should use the function astMaskF (see
+      the \texttt{"} Data Type Codes\texttt{"}  section below for the codes appropriate to
+      other numerical types).
+   }
+   \sstsynopsis{
+      int astMask$<$X$>$( AstRegion $*$this, AstMapping $*$map, int inside, int ndim,
+                      const int lbnd[], const int ubnd[], $<$Xtype$>$ in[],
+                      $<$Xtype$>$ val )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to a Region.
+      }
+      \sstsubsection{
+         map
+      }{
+         Pointer to a \htmlref{Mapping}{Mapping}. The forward transformation should map
+         positions in the coordinate system of the supplied Region
+         into pixel coordinates as defined by the
+         \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"}  parameters. A NULL pointer
+         can be supplied if the coordinate system of the supplied Region
+         corresponds to pixel coordinates. This is equivalent to
+         supplying a \htmlref{UnitMap}{UnitMap}.
+
+         The number of inputs for this Mapping (as given by its \htmlref{Nin}{Nin} attribute)
+         should match the number of axes in the supplied Region (as given
+         by the \htmlref{Naxes}{Naxes} attribute of the Region).
+         The number of outputs for the Mapping (as given by its \htmlref{Nout}{Nout} attribute)
+         should match the number of
+         grid dimensions given by the value of \texttt{"} ndim\texttt{"}
+         below.
+      }
+      \sstsubsection{
+         inside
+      }{
+         A boolean value which indicates which pixel are to be masked. If
+         a non-zero value
+         is supplied, then all grid pixels with centres inside the supplied
+         Region are assigned the value given by
+         \texttt{"} val\texttt{"} ,
+         and all other pixels are left unchanged. If
+         zero
+         is supplied, then all grid pixels with centres not inside the supplied
+         Region are assigned the value given by
+         \texttt{"} val\texttt{"} ,
+         and all other pixels are left unchanged. Note, the \htmlref{Negated}{Negated}
+         attribute of the Region is used to determine which pixel are
+         inside the Region and which are outside. So the inside of a Region
+         which has not been negated is the same as the outside of the
+         corresponding negated Region.
+
+         For types of Region such as \htmlref{PointList}{PointList} which have zero volume,
+         pixel centres will rarely fall exactly within the Region. For
+         this reason, the inclusion criterion is changed for zero-volume
+         Regions so that pixels are included (or excluded) if any part of
+         the Region passes through the pixel. For a PointList, this means
+         that pixels are included (or excluded) if they contain at least
+         one of the points listed in the PointList.
+      }
+      \sstsubsection{
+         ndim
+      }{
+         The number of dimensions in the input grid. This should be at
+         least one.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\texttt{"}  elements,
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\texttt{"}  elements,
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"}  together define the shape
+         and size of the input grid, its extent along a particular
+         (j\texttt{'} th) dimension being ubnd[j]-lbnd[j]$+$1 (assuming the
+         index \texttt{"} j\texttt{"}  to be zero-based). They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre.
+      }
+      \sstsubsection{
+         in
+      }{
+         Pointer to an array, with one element for each pixel in the
+         input grid, containing the data to be masked.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using astMaskF, the type of each array element
+         should be \texttt{"} float\texttt{"} ).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. Fortran array indexing is used).
+
+         On exit, the samples specified by
+         \texttt{"} inside\texttt{"}  are set to the value of \texttt{"} val\texttt{"} .
+         All other samples are left unchanged.
+      }
+      \sstsubsection{
+         val
+      }{
+         This argument should have the same type as the elements of
+         the \texttt{"} in\texttt{"}  array. It specifies the value used to flag the
+         masked data (see
+         \texttt{"} inside\texttt{"} ).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMask$<$X$>$()
+      }{
+         The number of pixels to which a value of
+         \texttt{"} badval\texttt{"}
+         has been assigned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with the global error status set, or if it should fail for any
+         reason.
+
+         \sstitem
+         An error will be reported if the overlap of the Region and
+         the array cannot be determined.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate masking function, you should
+      replace $<$X$>$ in the generic function name astMask$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         L: long int
+
+         \sstitem
+         UL: unsigned long int
+
+         \sstitem
+         I: int
+
+         \sstitem
+         UI: unsigned int
+
+         \sstitem
+         S: short int
+
+         \sstitem
+         US: unsigned short int
+
+         \sstitem
+         B: byte (signed char)
+
+         \sstitem
+         UB: unsigned byte (unsigned char)
+
+      }
+      For example, astMaskD would be used to process \texttt{"} double\texttt{"}
+      data, while astMaskS would be used to process \texttt{"} short int\texttt{"}
+      data, etc.
+   }
+}
+\sstroutine{
+   astMatchAxes
+}{
+   Find any corresponding axes in two Frames
+}{
+   \sstdescription{
+      This function looks for corresponding axes within two supplied
+      Frames. An array of integers is returned that contains an element
+      for each axis in the second supplied \htmlref{Frame}{Frame}. An element in this array
+      will be set to zero if the associated axis within the second Frame
+      has no corresponding axis within the first Frame. Otherwise, it
+      will be set to the index (a non-zero positive integer) of the
+      corresponding axis within the first supplied Frame.
+   }
+   \sstsynopsis{
+      void astMatchAxes( AstFrame $*$frm1, AstFrame $*$frm2, int $*$axes )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frm1
+      }{
+         Pointer to the first Frame.
+      }
+      \sstsubsection{
+         frm2
+      }{
+         Pointer to the second Frame.
+      }
+      \sstsubsection{
+         axes
+      }{
+         Pointer to an
+         integer array in which to return the indices of the axes (within
+         the first Frame) that correspond to each axis within the second
+         Frame. \htmlref{Axis}{Axis} indices start at 1. A value of zero will be stored
+         in the returned array for each axis in the second Frame that has
+         no corresponding axis in the first Frame.
+
+         The number of elements in this array must be greater than or
+         equal to the number of axes in the second Frame.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         This function applies to all Frames.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Corresponding axes are identified by the fact that a \htmlref{Mapping}{Mapping} can
+         be found between them using
+         \htmlref{astFindFrame}{astFindFrame} or \htmlref{astConvert}{astConvert}.
+         Thus, \texttt{"} corresponding axes\texttt{"}  are not necessarily identical. For
+         instance, \htmlref{SkyFrame}{SkyFrame} axes in two Frames will match even if they
+         describe different celestial coordinate systems
+      }
+   }
+}
+\sstroutine{
+   astMathMap
+}{
+   Create a MathMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{MathMap}{MathMap} and optionally initialises its
+      attributes.
+
+      A MathMap is a \htmlref{Mapping}{Mapping} which allows you to specify a set of forward
+      and/or inverse transformation functions using arithmetic operations
+      and mathematical functions similar to those available in C. The
+      MathMap interprets these functions at run-time, whenever its forward
+      or inverse transformation is required. Because the functions are not
+      compiled in the normal sense (unlike an \htmlref{IntraMap}{IntraMap}), they may be used to
+      describe coordinate transformations in a transportable manner. A
+      MathMap therefore provides a flexible way of defining new types of
+      Mapping whose descriptions may be stored as part of a dataset and
+      interpreted by other programs.
+   }
+   \sstsynopsis{
+      AstMathMap $*$astMathMap( int nin, int nout,
+                              int nfwd, const char $*$fwd[],
+                              int ninv, const char $*$inv[],
+                              const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         nin
+      }{
+         Number of input variables for the MathMap. This determines the
+         value of its \htmlref{Nin}{Nin} attribute.
+      }
+      \sstsubsection{
+         nout
+      }{
+         Number of output variables for the MathMap. This determines the
+         value of its \htmlref{Nout}{Nout} attribute.
+      }
+      \sstsubsection{
+         nfwd
+      }{
+         The number of forward transformation functions being supplied.
+         This must be at least equal to \texttt{"} nout\texttt{"} , but may be increased to
+         accommodate any additional expressions which define intermediate
+         variables for the forward transformation (see the \texttt{"} Calculating
+         Intermediate Values\texttt{"}  section below).
+      }
+      \sstsubsection{
+         fwd
+      }{
+         An array (with \texttt{"} nfwd\texttt{"}  elements) of pointers to null terminated strings
+         which contain the expressions defining the forward transformation.
+         The syntax of these expressions is described below.
+      }
+      \sstsubsection{
+         ninv
+      }{
+         The number of inverse transformation functions being supplied.
+         This must be at least equal to \texttt{"} nin\texttt{"} , but may be increased to
+         accommodate any additional expressions which define intermediate
+         variables for the inverse transformation (see the \texttt{"} Calculating
+         Intermediate Values\texttt{"}  section below).
+      }
+      \sstsubsection{
+         inv
+      }{
+         An array (with \texttt{"} ninv\texttt{"}  elements) of pointers to null terminated strings
+         which contain the expressions defining the inverse transformation.
+         The syntax of these expressions is described below.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new MathMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMathMap()
+      }{
+         A pointer to the new MathMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The sequence of numbers produced by the random number functions
+         available within a MathMap is normally unpredictable and different for
+         each MathMap. However, this behaviour may be controlled by means of
+         the MathMap\texttt{'} s \htmlref{Seed}{Seed} attribute.
+
+         \sstitem
+         Normally, compound Mappings (CmpMaps) which involve MathMaps will
+         not be subject to simplification (e.g. using \htmlref{astSimplify}{astSimplify}) because AST
+         cannot know how different MathMaps will interact. However, in the
+         special case where a MathMap occurs in series with its own inverse,
+         then simplification may be possible. Whether simplification does, in
+         fact, occur under these circumstances is controlled by the MathMap\texttt{'} s
+         \htmlref{SimpFI}{SimpFI} and \htmlref{SimpIF}{SimpIF} attributes.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Defining Transformation Functions
+   }{
+      A MathMap\texttt{'} s transformation functions are supplied as a set of
+      expressions in an array of character strings. Normally you would
+      supply the same number of expressions for the forward transformation,
+      via the \texttt{"} fwd\texttt{"}  parameter, as there are output variables (given by the
+      MathMap\texttt{'} s Nout attribute). For instance, if Nout is 2 you might use:
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} r = sqrt( x $*$ x $+$ y $*$ y )\texttt{"}
+
+         \sstitem
+         \texttt{"} theta = atan2( y, x )\texttt{"}
+
+      }
+      which defines a transformation from Cartesian to polar
+      coordinates. Here, the variables that appear on the left of each
+      expression (\texttt{"} r\texttt{"}  and \texttt{"} theta\texttt{"} ) provide names for the output variables
+      and those that appear on the right (\texttt{"} x\texttt{"}  and \texttt{"} y\texttt{"} ) are references to
+      input variables.
+
+      To complement this, you must also supply expressions for the inverse
+      transformation via the \texttt{"} inv\texttt{"}  parameter.  In this case, the number of
+      expressions given would normally match the number of MathMap input
+      coordinates (given by the Nin attribute).  If Nin is 2, you might use:
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} x = r $*$ cos( theta )\texttt{"}
+
+         \sstitem
+         \texttt{"} y = r $*$ sin( theta )\texttt{"}
+
+      }
+      which expresses the transformation from polar to Cartesian
+      coordinates. Note that here the input variables (\texttt{"} x\texttt{"}  and \texttt{"} y\texttt{"} ) are
+      named on the left of each expression, and the output variables (\texttt{"} r\texttt{"}
+      and \texttt{"} theta\texttt{"} ) are referenced on the right.
+
+      Normally, you cannot refer to a variable on the right of an expression
+      unless it is named on the left of an expression in the complementary
+      set of functions. Therefore both sets of functions (forward and
+      inverse) must be formulated using the same consistent set of variable
+      names. This means that if you wish to leave one of the transformations
+      undefined, you must supply dummy expressions which simply name each of
+      the output (or input) variables.  For example, you might use:
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} x\texttt{"}
+
+         \sstitem
+         \texttt{"} y\texttt{"}
+
+      }
+      for the inverse transformation above, which serves to name the input
+      variables but without defining an inverse transformation.
+   }
+   \sstdiytopic{
+      Calculating Intermediate Values
+   }{
+      It is sometimes useful to calculate intermediate values and then to
+      use these in the final expressions for the output (or input)
+      variables. This may be done by supplying additional expressions for
+      the forward (or inverse) transformation functions. For instance, the
+      following array of five expressions describes 2-dimensional pin-cushion
+      distortion:
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} r = sqrt( xin $*$ xin $+$ yin $*$ yin )\texttt{"}
+
+         \sstitem
+         \texttt{"} rout = r $*$ ( 1 $+$ 0.1 $*$ r $*$ r )\texttt{"}
+
+         \sstitem
+         \texttt{"} theta = atan2( yin, xin )\texttt{"}
+
+         \sstitem
+         \texttt{"} xout = rout $*$ cos( theta )\texttt{"}
+
+         \sstitem
+         \texttt{"} yout = rout $*$ sin( theta )\texttt{"}
+
+      }
+      Here, we first calculate three intermediate results (\texttt{"} r\texttt{"} , \texttt{"} rout\texttt{"}
+      and \texttt{"} theta\texttt{"} ) and then use these to calculate the final results (\texttt{"} xout\texttt{"}
+      and \texttt{"} yout\texttt{"} ). The MathMap knows that only the final two results
+      constitute values for the output variables because its Nout attribute
+      is set to 2. You may define as many intermediate variables in this
+      way as you choose. Having defined a variable, you may then refer to it
+      on the right of any subsequent expressions.
+
+      Note that when defining the inverse transformation you may only refer
+      to the output variables \texttt{"} xout\texttt{"}  and \texttt{"} yout\texttt{"} .  The intermediate variables
+      \texttt{"} r\texttt{"} , \texttt{"} rout\texttt{"}  and \texttt{"} theta\texttt{"}  (above) are private to the forward
+      transformation and may not be referenced by the inverse
+      transformation. The inverse transformation may, however, define its
+      own private intermediate variables.
+   }
+   \sstdiytopic{
+      Expression Syntax
+   }{
+      The expressions given for the forward and inverse transformations
+      closely follow the syntax of the C programming language (with some
+      extensions for compatibility with Fortran). They may contain
+      references to variables and literal constants, together with
+      arithmetic, boolean, relational and bitwise operators, and function
+      invocations. A set of symbolic constants is also available. Each of
+      these is described in detail below. Parentheses may be used to
+      over-ride the normal order of evaluation. There is no built-in limit
+      to the length of expressions and they are insensitive to case or the
+      presence of additional white space.
+   }
+   \sstdiytopic{
+      Variables
+   }{
+      Variable names must begin with an alphabetic character and may contain
+      only alphabetic characters, digits, and the underscore character
+      \texttt{"} \_\texttt{"} . There is no built-in limit to the length of variable names.
+   }
+   \sstdiytopic{
+      Literal Constants
+   }{
+      Literal constants, such as \texttt{"} 0\texttt{"} , \texttt{"} 1\texttt{"} , \texttt{"} 0.007\texttt{"}  or \texttt{"} 2.505e-16\texttt{"}  may appear
+      in expressions, with the decimal point and exponent being optional (a
+      \texttt{"} D\texttt{"}  may also be used as an exponent character for compatibility with
+      Fortran). A unary minus \texttt{"} -\texttt{"}  may be used as a prefix.
+   }
+   \sstdiytopic{
+      Arithmetic Precision
+   }{
+      All arithmetic is floating point, performed in double precision.
+   }
+   \sstdiytopic{
+      Propagation of Missing Data
+   }{
+      Unless indicated otherwise, if any argument of a function or operator
+      has the value AST\_\_BAD (indicating missing data), then the result of
+      that function or operation is also AST\_\_BAD, so that such values are
+      propagated automatically through all operations performed by MathMap
+      transformations.  The special value AST\_\_BAD can be represented in
+      expressions by the symbolic constant \texttt{"} $<$bad$>$\texttt{"} .
+
+      A $<$bad$>$ result (i.e. equal to AST\_\_BAD) is also produced in response
+      to any numerical error (such as division by zero or numerical
+      overflow), or if an invalid argument value is provided to a function
+      or operator.
+   }
+   \sstdiytopic{
+      Arithmetic Operators
+   }{
+      The following arithmetic operators are available:
+      \sstitemlist{
+
+         \sstitem
+         x1 $+$ x2: Sum of \texttt{"} x1\texttt{"}  and \texttt{"} x2\texttt{"} .
+
+         \sstitem
+         x1 - x2: Difference of \texttt{"} x1\texttt{"}  and \texttt{"} x2\texttt{"} .
+
+         \sstitem
+         x1 $*$ x2: Product of \texttt{"} x1\texttt{"}  and \texttt{"} x1\texttt{"} .
+
+         \sstitem
+         x1 / x2: Ratio of \texttt{"} x1\texttt{"}  and \texttt{"} x2\texttt{"} .
+
+         \sstitem
+         x1 $*$$*$ x2: \texttt{"} x1\texttt{"}  raised to the power of \texttt{"} x2\texttt{"} .
+
+         \sstitem
+         $+$ x: Unary plus, has no effect on its argument.
+
+         \sstitem
+         - x: Unary minus, negates its argument.
+      }
+   }
+   \sstdiytopic{
+      Boolean Operators
+   }{
+      Boolean values are represented using zero to indicate false and
+      non-zero to indicate true. In addition, the value AST\_\_BAD is taken to
+      mean \texttt{"} unknown\texttt{"} . The values returned by boolean operators may therefore
+      be 0, 1 or AST\_\_BAD. Where appropriate, \texttt{"} tri-state\texttt{"}  logic is
+      implemented. For example, \texttt{"} a$|$$|$b\texttt{"}  may evaluate to 1 if \texttt{"} a\texttt{"}  is non-zero,
+      even if \texttt{"} b\texttt{"}  has the value AST\_\_BAD. This is because the result of the
+      operation would not be affected by the value of \texttt{"} b\texttt{"} , so long as \texttt{"} a\texttt{"}  is
+      non-zero.
+
+      The following boolean operators are available:
+      \sstitemlist{
+
+         \sstitem
+         x1 \&\& x2: Boolean AND between \texttt{"} x1\texttt{"}  and \texttt{"} x2\texttt{"} , returning 1 if both \texttt{"} x1\texttt{"}
+         and \texttt{"} x2\texttt{"}  are non-zero, and 0 otherwise. This operator implements
+         tri-state logic. (The synonym \texttt{"} .and.\texttt{"}  is also provided for compatibility
+         with Fortran.)
+
+         \sstitem
+         x1 $|$$|$ x2: Boolean OR between \texttt{"} x1\texttt{"}  and \texttt{"} x2\texttt{"} , returning 1 if either \texttt{"} x1\texttt{"}
+         or \texttt{"} x2\texttt{"}  are non-zero, and 0 otherwise. This operator implements
+         tri-state logic. (The synonym \texttt{"} .or.\texttt{"}  is also provided for compatibility
+         with Fortran.)
+
+         \sstitem
+         x1 $\wedge$$\wedge$ x2: Boolean exclusive OR (XOR) between \texttt{"} x1\texttt{"}  and \texttt{"} x2\texttt{"} , returning
+         1 if exactly one of \texttt{"} x1\texttt{"}  and \texttt{"} x2\texttt{"}  is non-zero, and 0 otherwise. Tri-state
+         logic is not used with this operator. (The synonyms \texttt{"} .neqv.\texttt{"}  and \texttt{"} .xor.\texttt{"}
+         are also provided for compatibility with Fortran, although the second
+         of these is not standard.)
+
+         \sstitem
+         x1 .eqv. x2: This is provided only for compatibility with Fortran
+         and tests whether the boolean states of \texttt{"} x1\texttt{"}  and \texttt{"} x2\texttt{"}  (i.e. true/false)
+         are equal. It is the negative of the exclusive OR (XOR) function.
+         Tri-state logic is not used with this operator.
+
+         \sstitem
+         ! x: Boolean unary NOT operation, returning 1 if \texttt{"} x\texttt{"}  is zero, and
+         0 otherwise. (The synonym \texttt{"} .not.\texttt{"}  is also provided for compatibility
+         with Fortran.)
+      }
+   }
+   \sstdiytopic{
+      Relational Operators
+   }{
+      Relational operators return the boolean result (0 or 1) of comparing
+      the values of two floating point values for equality or inequality. The
+      value AST\_\_BAD may also be returned if either argument is $<$bad$>$.
+
+      The following relational operators are available:
+      \sstitemlist{
+
+         \sstitem
+         x1 == x2: Tests whether \texttt{"} x1\texttt{"}  equals \texttt{"} x1\texttt{"} . (The synonym \texttt{"} .eq.\texttt{"}  is
+         also provided for compatibility with Fortran.)
+
+         \sstitem
+         x1 != x2: Tests whether \texttt{"} x1\texttt{"}  is unequal to \texttt{"} x2\texttt{"} . (The synonym \texttt{"} .ne.\texttt{"}
+         is also provided for compatibility with Fortran.)
+
+         \sstitem
+         x1 $>$ x2: Tests whether \texttt{"} x1\texttt{"}  is greater than \texttt{"} x2\texttt{"} . (The synonym
+         \texttt{"} .gt.\texttt{"}  is also provided for compatibility with Fortran.)
+
+         \sstitem
+         x1 $>$= x2: Tests whether \texttt{"} x1\texttt{"}  is greater than or equal to \texttt{"} x2\texttt{"} . (The
+         synonym \texttt{"} .ge.\texttt{"}   is also provided for compatibility with Fortran.)
+
+         \sstitem
+         x1 $<$ x2: Tests whether \texttt{"} x1\texttt{"}  is less than \texttt{"} x2\texttt{"} . (The synonym \texttt{"} .lt.\texttt{"}
+         is also provided for compatibility with Fortran.)
+
+         \sstitem
+         x1 $<$= x2: Tests whether \texttt{"} x1\texttt{"}  is less than or equal to \texttt{"} x2\texttt{"} . (The
+         synonym \texttt{"} .le.\texttt{"}  is also provided for compatibility with Fortran.)
+
+      }
+      Note that relational operators cannot usefully be used to compare
+      values with the $<$bad$>$ value (representing missing data), because the
+      result is always $<$bad$>$. The isbad() function should be used instead.
+   }
+   \sstdiytopic{
+      Bitwise Operators
+   }{
+      The bitwise operators provided by C are often useful when operating on
+      raw data (e.g. from instruments), so they are also provided for use in
+      MathMap expressions. In this case, however, the values on which they
+      operate are floating point values rather than pure integers. In order
+      to produce results which match the pure integer case, the operands are
+      regarded as fixed point binary numbers (i.e. with the binary
+      equivalent of a decimal point) with negative numbers represented using
+      twos-complement notation. For integer values, the resulting bit
+      pattern corresponds to that of the equivalent signed integer (digits
+      to the right of the point being zero). Operations on the bits
+      representing the fractional part are also possible, however.
+
+      The following bitwise operators are available:
+      \sstitemlist{
+
+         \sstitem
+         x1 $>$$>$ x2: Rightward bit shift. The integer value of \texttt{"} x2\texttt{"}  is taken
+         (rounding towards zero) and the bits representing \texttt{"} x1\texttt{"}  are then
+         shifted this number of places to the right (or to the left if the
+         number of places is negative). This is equivalent to dividing \texttt{"} x1\texttt{"}  by
+         the corresponding power of 2.
+
+         \sstitem
+         x1 $<$$<$ x2: Leftward bit shift. The integer value of \texttt{"} x2\texttt{"}  is taken
+         (rounding towards zero), and the bits representing \texttt{"} x1\texttt{"}  are then
+         shifted this number of places to the left (or to the right if the
+         number of places is negative). This is equivalent to multiplying \texttt{"} x1\texttt{"}
+         by the corresponding power of 2.
+
+         \sstitem
+         x1 \& x2: Bitwise AND between the bits of \texttt{"} x1\texttt{"}  and those of \texttt{"} x2\texttt{"}
+         (equivalent to a boolean AND applied at each bit position in turn).
+
+         \sstitem
+         x1 $|$ x2: Bitwise OR between the bits of \texttt{"} x1\texttt{"}  and those of \texttt{"} x2\texttt{"}
+         (equivalent to a boolean OR applied at each bit position in turn).
+
+         \sstitem
+         x1 $\wedge$ x2: Bitwise exclusive OR (XOR) between the bits of \texttt{"} x1\texttt{"}  and
+         those of \texttt{"} x2\texttt{"}  (equivalent to a boolean XOR applied at each bit
+         position in turn).
+
+      }
+      Note that no bit inversion operator (\texttt{"} $\sim$\texttt{"}  in C) is provided. This is
+      because inverting the bits of a twos-complement fixed point binary
+      number is equivalent to simply negating it. This differs from the
+      pure integer case because bits to the right of the binary point are
+      also inverted. To invert only those bits to the left of the binary
+      point, use a bitwise exclusive OR with the value -1 (i.e. \texttt{"} x$\wedge$-1\texttt{"} ).
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The following functions are available:
+      \sstitemlist{
+
+         \sstitem
+         abs(x): Absolute value of \texttt{"} x\texttt{"}  (sign removal), same as fabs(x).
+
+         \sstitem
+         acos(x): Inverse cosine of \texttt{"} x\texttt{"} , in radians.
+
+         \sstitem
+         acosd(x): Inverse cosine of \texttt{"} x\texttt{"} , in degrees.
+
+         \sstitem
+         acosh(x): Inverse hyperbolic cosine of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         acoth(x): Inverse hyperbolic cotangent of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         acsch(x): Inverse hyperbolic cosecant of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         aint(x): Integer part of \texttt{"} x\texttt{"}  (round towards zero), same as int(x).
+
+         \sstitem
+         asech(x): Inverse hyperbolic secant of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         asin(x): Inverse sine of \texttt{"} x\texttt{"} , in radians.
+
+         \sstitem
+         asind(x): Inverse sine of \texttt{"} x\texttt{"} , in degrees.
+
+         \sstitem
+         asinh(x): Inverse hyperbolic sine of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         atan(x): Inverse tangent of \texttt{"} x\texttt{"} , in radians.
+
+         \sstitem
+         atand(x): Inverse tangent of \texttt{"} x\texttt{"} , in degrees.
+
+         \sstitem
+         atanh(x): Inverse hyperbolic tangent of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         atan2(x1, x2): Inverse tangent of \texttt{"} x1/x2\texttt{"} , in radians.
+
+         \sstitem
+         atan2d(x1, x2): Inverse tangent of \texttt{"} x1/x2\texttt{"} , in degrees.
+
+         \sstitem
+         ceil(x): Smallest integer value not less then \texttt{"} x\texttt{"}  (round towards
+           plus infinity).
+
+         \sstitem
+         cos(x): Cosine of \texttt{"} x\texttt{"}  in radians.
+
+         \sstitem
+         cosd(x): Cosine of \texttt{"} x\texttt{"}  in degrees.
+
+         \sstitem
+         cosh(x): Hyperbolic cosine of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         coth(x): Hyperbolic cotangent of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         csch(x): Hyperbolic cosecant of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         dim(x1, x2): Returns \texttt{"} x1-x2\texttt{"}  if \texttt{"} x1\texttt{"}  is greater than \texttt{"} x2\texttt{"} , otherwise 0.
+
+         \sstitem
+         exp(x): Exponential function of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         fabs(x): Absolute value of \texttt{"} x\texttt{"}  (sign removal), same as abs(x).
+
+         \sstitem
+         floor(x): Largest integer not greater than \texttt{"} x\texttt{"}  (round towards
+           minus infinity).
+
+         \sstitem
+         fmod(x1, x2): Remainder when \texttt{"} x1\texttt{"}  is divided by \texttt{"} x2\texttt{"} , same as
+           mod(x1, x2).
+
+         \sstitem
+         gauss(x1, x2): Random sample from a Gaussian distribution with mean
+           \texttt{"} x1\texttt{"}  and standard deviation \texttt{"} x2\texttt{"} .
+
+         \sstitem
+         int(x): Integer part of \texttt{"} x\texttt{"}  (round towards zero), same as aint(x).
+
+         \sstitem
+         isbad(x): Returns 1 if \texttt{"} x\texttt{"}  has the $<$bad$>$ value (AST\_\_BAD), otherwise 0.
+
+         \sstitem
+         log(x): Natural logarithm of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         log10(x): Logarithm of \texttt{"} x\texttt{"}  to base 10.
+
+         \sstitem
+         max(x1, x2, ...): Maximum of two or more values.
+
+         \sstitem
+         min(x1, x2, ...): Minimum of two or more values.
+
+         \sstitem
+         mod(x1, x2): Remainder when \texttt{"} x1\texttt{"}  is divided by \texttt{"} x2\texttt{"} , same as
+           fmod(x1, x2).
+
+         \sstitem
+         nint(x): Nearest integer to \texttt{"} x\texttt{"}  (round to nearest).
+
+         \sstitem
+         poisson(x): Random integer-valued sample from a Poisson
+           distribution with mean \texttt{"} x\texttt{"} .
+
+         \sstitem
+         pow(x1, x2): \texttt{"} x1\texttt{"}  raised to the power of \texttt{"} x2\texttt{"} .
+
+         \sstitem
+         qif(x1, x2, x3): Returns \texttt{"} x2\texttt{"}  if \texttt{"} x1\texttt{"}  is true, and \texttt{"} x3\texttt{"}  otherwise.
+
+         \sstitem
+         rand(x1, x2): Random sample from a uniform distribution in the
+           range \texttt{"} x1\texttt{"}  to \texttt{"} x2\texttt{"}  inclusive.
+
+         \sstitem
+         sech(x): Hyperbolic secant of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         sign(x1, x2): Absolute value of \texttt{"} x1\texttt{"}  with the sign of \texttt{"} x2\texttt{"}
+           (transfer of sign).
+
+         \sstitem
+         sin(x): Sine of \texttt{"} x\texttt{"}  in radians.
+
+         \sstitem
+         sinc(x): Sinc function of \texttt{"} x\texttt{"}  [= \texttt{"} sin(x)/x\texttt{"} ].
+
+         \sstitem
+         sind(x): Sine of \texttt{"} x\texttt{"}  in degrees.
+
+         \sstitem
+         sinh(x): Hyperbolic sine of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         sqr(x): Square of \texttt{"} x\texttt{"}  (= \texttt{"} x$*$x\texttt{"} ).
+
+         \sstitem
+         sqrt(x): Square root of \texttt{"} x\texttt{"} .
+
+         \sstitem
+         tan(x): Tangent of \texttt{"} x\texttt{"}  in radians.
+
+         \sstitem
+         tand(x): Tangent of \texttt{"} x\texttt{"}  in degrees.
+
+         \sstitem
+         tanh(x): Hyperbolic tangent of \texttt{"} x\texttt{"} .
+      }
+   }
+   \sstdiytopic{
+      Symbolic Constants
+   }{
+      The following symbolic constants are available (the enclosing \texttt{"} $<$$>$\texttt{"}
+      brackets must be included):
+      \sstitemlist{
+
+         \sstitem
+         $<$bad$>$: The \texttt{"} bad\texttt{"}  value (AST\_\_BAD) used to flag missing data. Note
+         that you cannot usefully compare values with this constant because the
+         result is always $<$bad$>$. The isbad() function should be used instead.
+
+         \sstitem
+         $<$dig$>$: Number of decimal digits of precision available in a
+         floating point (double) value.
+
+         \sstitem
+         $<$e$>$: \htmlref{Base}{Base} of natural logarithms.
+
+         \sstitem
+         $<$epsilon$>$: Smallest positive number such that 1.0$+$$<$epsilon$>$ is
+         distinguishable from unity.
+
+         \sstitem
+         $<$mant\_dig$>$: The number of base $<$radix$>$ digits stored in the
+         mantissa of a floating point (double) value.
+
+         \sstitem
+         $<$max$>$: Maximum representable floating point (double) value.
+
+         \sstitem
+         $<$max\_10\_exp$>$: Maximum integer such that 10 raised to that power
+         can be represented as a floating point (double) value.
+
+         \sstitem
+         $<$max\_exp$>$: Maximum integer such that $<$radix$>$ raised to that
+         power minus 1 can be represented as a floating point (double) value.
+
+         \sstitem
+         $<$min$>$: Smallest positive number which can be represented as a
+         normalised floating point (double) value.
+
+         \sstitem
+         $<$min\_10\_exp$>$: Minimum negative integer such that 10 raised to that
+         power can be represented as a normalised floating point (double) value.
+
+         \sstitem
+         $<$min\_exp$>$: Minimum negative integer such that $<$radix$>$ raised to
+         that power minus 1 can be represented as a normalised floating point
+         (double) value.
+
+         \sstitem
+         $<$pi$>$: Ratio of the circumference of a circle to its diameter.
+
+         \sstitem
+         $<$radix$>$: The radix (number base) used to represent the mantissa of
+         floating point (double) values.
+
+         \sstitem
+         $<$rounds$>$: The mode used for rounding floating point results after
+         addition. Possible values include: -1 (indeterminate), 0 (toward
+         zero), 1 (to nearest), 2 (toward plus infinity) and 3 (toward minus
+         infinity). Other values indicate machine-dependent behaviour.
+      }
+   }
+   \sstdiytopic{
+      Evaluation Precedence and Associativity
+   }{
+      Items appearing in expressions are evaluated in the following order
+      (highest precedence first):
+      \sstitemlist{
+
+         \sstitem
+         Constants and variables
+
+         \sstitem
+         Function arguments and parenthesised expressions
+
+         \sstitem
+         Function invocations
+
+         \sstitem
+         Unary $+$ - ! .not.
+
+         \sstitem
+         $*$$*$
+
+         \sstitem
+         $*$ /
+
+         \sstitem
+         $+$ -
+
+         \sstitem
+         $<$$<$ $>$$>$
+
+         \sstitem
+         $<$ .lt. $<$= .le. $>$ .gt. $>$= .ge.
+
+         \sstitem
+         == .eq. != .ne.
+
+         \sstitem
+         \&
+
+         \sstitem
+         $\wedge$
+
+         \sstitem
+         $|$
+
+         \sstitem
+         \&\& .and.
+
+         \sstitem
+         $\wedge$$\wedge$
+
+         \sstitem
+         $|$$|$ .or
+
+         \sstitem
+         .eqv. .neqv. .xor.
+
+      }
+      All operators associate from left-to-right, except for unary $+$,
+      unary -, !, .not. and $*$$*$ which associate from right-to-left.
+   }
+}
+\sstroutine{
+   astMatrixMap
+}{
+   Create a MatrixMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{MatrixMap}{MatrixMap} and optionally initialises
+      its attributes.
+
+      A MatrixMap is a form of \htmlref{Mapping}{Mapping} which performs a general linear
+      transformation.  Each set of input coordinates, regarded as a
+      column-vector, are pre-multiplied by a matrix (whose elements
+      are specified when the MatrixMap is created) to give a new
+      column-vector containing the output coordinates. If appropriate,
+      the inverse transformation may also be performed.
+   }
+   \sstsynopsis{
+      AstMatrixMap $*$astMatrixMap( int nin, int nout, int form,
+                                  const double matrix[],
+                                  const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         nin
+      }{
+         The number of input coordinates, which determines the number
+         of columns in the matrix.
+      }
+      \sstsubsection{
+         nout
+      }{
+         The number of output coordinates, which determines the number
+         of rows in the matrix.
+      }
+      \sstsubsection{
+         form
+      }{
+         An integer which indicates the form in which the matrix
+         elements will be supplied.
+
+         A value of zero indicates that a full \texttt{"} nout\texttt{"}  x \texttt{"} nin\texttt{"}  matrix
+         of values will be supplied via the \texttt{"} matrix\texttt{"}  parameter
+         (below). In this case, the elements should be given in row
+         order (the elements of the first row, followed by the
+         elements of the second row, etc.).
+
+         A value of 1 indicates that only the diagonal elements of the
+         matrix will be supplied, and that all others should be
+         zero. In this case, the elements of \texttt{"} matrix\texttt{"}  should contain
+         only the diagonal elements, stored consecutively.
+
+         A value of 2 indicates that a \texttt{"} unit\texttt{"}  matrix is required,
+         whose diagonal elements are set to unity (with all other
+         elements zero).  In this case, the \texttt{"} matrix\texttt{"}  parameter is
+         ignored and a NULL pointer may be supplied.
+      }
+      \sstsubsection{
+         matrix
+      }{
+         The array of matrix elements to be used, stored according to
+         the value of \texttt{"} form\texttt{"} .
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new MatrixMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMatrixMap()
+      }{
+         A pointer to the new MatrixMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         In general, a MatrixMap\texttt{'} s forward transformation will always
+         be available (as indicated by its \htmlref{TranForward}{TranForward} attribute), but
+         its inverse transformation (\htmlref{TranInverse}{TranInverse} attribute) will only be
+         available if the associated matrix is square and non-singular.
+
+         \sstitem
+         As an exception to this, the inverse transformation is always
+         available if a unit or diagonal matrix is specified. In this
+         case, if the matrix is not square, one or more of the input
+         coordinate values may not be recoverable from a set of output
+         coordinates. Any coordinates affected in this way will simply be
+         set to the value zero.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astMirrorVariants
+}{
+   Make the current Frame mirror the variant Mappings in another Frame
+}{
+   \sstdescription{
+      This function
+      indicates that all access to the \htmlref{Variant}{Variant} attribute of the current
+      \htmlref{Frame}{Frame} should should be forwarded to some other nominated Frame in
+      the \htmlref{FrameSet}{FrameSet}. For instance, if a value is set subsequently for the
+      Variant attribute of the current Frame, the current Frame will be left
+      unchanged and the setting is instead applied to the nominated Frame.
+      Likewise, if the value of the Variant attribute is requested, the
+      value returned is the value stored for the nominated Frame rather
+      than the current Frame itself.
+
+      This provides a mechanism for propagating the effects of variant
+      Mappings around a FrameSet. If a new Frame is added to a FrameSet
+      by connecting it to an pre-existing Frame that has two or more variant
+      Mappings, then it may be appropriate to set the new Frame so that it
+      mirrors the variants Mappings of the pre-existing Frame. If this is
+      done, then it will be possible to select a specific variant \htmlref{Mapping}{Mapping}
+      using either the pre-existing Frame or the new Frame.
+   }
+   \sstsynopsis{
+      void astMirrorVariants( AstFrameSet $*$this, int iframe )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         iframe
+      }{
+         The index of the Frame within the FrameSet which is to be
+         mirrored by the current Frame. This value should lie in the range
+         from 1 to the number of Frames in the FrameSet (as given by its
+         \htmlref{Nframe}{Nframe} attribute). If AST\_\_NOFRAME is supplied (or the current
+         Frame is specified), then any mirroring established by a previous
+         call to this
+         function
+         is disabled.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Mirrors can be chained. That is, if Frame B is set to be a mirror
+         of Frame A, and Frame C is set to be a mirror of Frame B, then
+         Frame C will act as a mirror of Frame A.
+
+         \sstitem
+         Variant Mappings cannot be added to the current Frame if it is
+         mirroring another Frame. So calls to the
+         \htmlref{astAddVariant}{astAddVariant} function
+         will cause an error to be reported if the current Frame is
+         mirroring another Frame.
+
+         \sstitem
+         A value of AST\_\_BASE may be given for the
+         \texttt{"} iframe\texttt{"}  parameter
+         to specify the base Frame.
+
+         \sstitem
+         Any variant Mappings explicitly added to the current Frame using
+         astAddVariant
+         will be ignored if the current Frame is mirroring another Frame.
+      }
+   }
+}
+\sstroutine{
+   astNegate
+}{
+   Negate the area represented by a Region
+}{
+   \sstdescription{
+      This function negates the area represented by a \htmlref{Region}{Region}. That is,
+      points which were previously inside the region will then be
+      outside, and points which were outside will be inside. This is
+      acomplished by toggling the state of the \htmlref{Negated}{Negated} attribute for
+      the supplied region.
+   }
+   \sstsynopsis{
+      void astNegate( AstRegion $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Region.
+      }
+   }
+}
+\sstroutine{
+   astNorm
+}{
+   Normalise a set of Frame coordinates
+}{
+   \sstdescription{
+      This function normalises a set of \htmlref{Frame}{Frame} coordinate values which
+      might be unsuitable for display (e.g. may lie outside the
+      expected range) into a set of acceptable values suitable for
+      display.
+   }
+   \sstsynopsis{
+      void astNorm( AstFrame $*$this, double value[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         value
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). Initially, this should contain a set of
+         coordinate values representing a point in the space which the
+         Frame describes. If these values lie outside the expected
+         range for the Frame, they will be replaced with more
+         acceptable (normalised) values. Otherwise, they will be
+         returned unchanged.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For some classes of Frame, whose coordinate values are not
+         constrained, this function will never modify the values
+         supplied. However, for Frames whose axes represent cyclic
+         quantities (such as angles or positions on the sky), coordinates
+         will typically be wrapped into an appropriate standard range,
+         such as zero to 2$*$pi.
+
+         \sstitem
+         The \htmlref{NormMap}{NormMap} class is a \htmlref{Mapping}{Mapping} which can be used to normalise a
+         set of points using the
+         astNorm function
+         of a specified Frame.
+
+         \sstitem
+         It is intended to be possible to put any set of coordinates
+         into a form suitable for display by using this function to
+         normalise them, followed by appropriate formatting
+         (using \htmlref{astFormat}{astFormat}).
+      }
+   }
+}
+\sstroutine{
+   astNormMap
+}{
+   Create a NormMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{NormMap}{NormMap} and optionally initialises its
+      attributes.
+
+      A NormMap is a \htmlref{Mapping}{Mapping} which normalises coordinate values using the
+      \htmlref{astNorm}{astNorm} function
+      of the supplied \htmlref{Frame}{Frame}. The number of inputs and outputs of a NormMap
+      are both equal to the number of axes in the supplied Frame.
+
+      The forward and inverse transformation of a NormMap are both
+      defined but are identical (that is, they do not form a real inverse
+      pair in that the inverse transformation does not undo the
+      normalisation, instead it reapplies it). However, the
+      \htmlref{astSimplify}{astSimplify}
+      function will replace neighbouring pairs of forward and inverse
+      NormMaps by a single \htmlref{UnitMap}{UnitMap} (so long as the Frames encapsulated by
+      the two NormMaps are equal - i.e. have the same class and the same
+      attribute values). This means, for instance, that if a \htmlref{CmpMap}{CmpMap} contains
+      a NormMap, the CmpMap will still cancel with its own inverse.
+   }
+   \sstsynopsis{
+      AstNormMap $*$astNormMap( AstFrame $*$frame, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         A pointer to the Frame which is to be used to normalise the
+         supplied axis values.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new NormMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astNormMap()
+      }{
+         A pointer to the new NormMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astNullRegion
+}{
+   Create a NullRegion
+}{
+   \sstdescription{
+      This function creates a new \htmlref{NullRegion}{NullRegion} and optionally initialises its
+      attributes.
+
+      A NullRegion is a \htmlref{Region}{Region} with no bounds. If the \htmlref{Negated}{Negated} attribute of a
+      NullRegion is false, the NullRegion represents a Region containing no
+      points. If the Negated attribute of a NullRegion is true, the NullRegion
+      represents an infinite Region containing all points within the
+      coordinate system.
+   }
+   \sstsynopsis{
+      AstNullRegion $*$astNullRegion( AstFrame $*$frame, AstRegion $*$unc, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         A pointer to the \htmlref{Frame}{Frame} in which the region is defined. A deep
+         copy is taken of the supplied Frame. This means that any
+         subsequent changes made to the Frame using the supplied pointer
+         will have no effect the Region.
+      }
+      \sstsubsection{
+         unc
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with positions in the supplied Frame.
+         The uncertainty in any point in the Frame is found by shifting the
+         supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at the point
+         being considered. The area covered by the shifted uncertainty
+         Region then represents the uncertainty in the position. The
+         uncertainty is assumed to be the same for all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. \htmlref{Box}{Box}, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created NullRegion. Alternatively,
+         a NULL \htmlref{Object}{Object} pointer
+         may be supplied, in which case a default uncertainty of zero is
+         used.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new NullRegion. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astNullRegion()
+      }{
+         A pointer to the new NullRegion.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astOK
+}{
+   Test whether AST functions have been successful
+}{
+   \sstdescription{
+      This macro returns a boolean value (0 or 1) to indicate if
+      preceding AST functions have completed successfully
+      (i.e. without setting the AST error status). If the error status
+      is set to an error value, a value of zero is returned, otherwise
+      the result is one.
+   }
+   \sstsynopsis{
+      int astOK
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astOK
+      }{
+         One if the AST error status is OK, otherwise zero.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the AST error status is set to an error value (after an
+         error), most AST functions will not execute and will simply
+         return without action. To clear the error status and restore
+         normal behaviour, use \htmlref{astClearStatus}{astClearStatus}.
+      }
+   }
+}
+\sstroutine{
+   astOffset
+}{
+   Calculate an offset along a geodesic curve
+}{
+   \sstdescription{
+      This function finds the \htmlref{Frame}{Frame} coordinate values of a point which
+      is offset a specified distance along the geodesic curve between
+      two other points.
+
+      For example, in a basic Frame, this offset will be along the
+      straight line joining two points. For a more specialised Frame
+      describing a sky coordinate system, however, it would be along
+      the great circle passing through two sky positions.
+   }
+   \sstsynopsis{
+      void astOffset( AstFrame $*$this,
+                      const double point1[], const double point2[],
+                      double offset, double point3[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         point1
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). This should contain the coordinates of the
+         point marking the start of the geodesic curve.
+      }
+      \sstsubsection{
+         point2
+      }{
+         An array of double, with one element for each Frame axis
+         This should contain the coordinates of the point marking the
+         end of the geodesic curve.
+      }
+      \sstsubsection{
+         offset
+      }{
+         The required offset from the first point along the geodesic
+         curve. If this is positive, it will be towards the second
+         point. If it is negative, it will be in the opposite
+         direction. This offset need not imply a position lying
+         between the two points given, as the curve will be
+         extrapolated if necessary.
+      }
+      \sstsubsection{
+         point3
+      }{
+         An array of double, with one element for each Frame axis
+         in which the coordinates of the required point will be returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The geodesic curve used by this function is the path of
+         shortest distance between two points, as defined by the
+         \htmlref{astDistance}{astDistance} function.
+
+         \sstitem
+         This function will return \texttt{"} bad\texttt{"}  coordinate values (AST\_\_BAD)
+         if any of the input coordinates has this value.
+
+         \sstitem
+         \texttt{"} Bad\texttt{"}  coordinate values will also be returned if the two
+         points supplied are coincident (or otherwise fail to uniquely
+         specify a geodesic curve) but the requested offset is non-zero.
+      }
+   }
+}
+\sstroutine{
+   astOffset2
+}{
+   Calculate an offset along a geodesic curve in a 2D Frame
+}{
+   \sstdescription{
+      This function finds the \htmlref{Frame}{Frame} coordinate values of a point which
+      is offset a specified distance along the geodesic curve at a
+      given angle from a specified starting point. It can only be
+      used with 2-dimensional Frames.
+
+      For example, in a basic Frame, this offset will be along the
+      straight line joining two points. For a more specialised Frame
+      describing a sky coordinate system, however, it would be along
+      the great circle passing through two sky positions.
+   }
+   \sstsynopsis{
+      double astOffset2( AstFrame $*$this, const double point1[2], double angle,
+                         double offset, double point2[2] );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         point1
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). This should contain the coordinates of the
+         point marking the start of the geodesic curve.
+      }
+      \sstsubsection{
+         angle
+      }{
+         The angle (in radians) from the positive direction of the second
+         axis, to the direction of the required position, as seen from
+         the starting position. Positive rotation is in the sense of
+         rotation from the positive direction of axis 2 to the positive
+         direction of axis 1.
+      }
+      \sstsubsection{
+         offset
+      }{
+         The required offset from the first point along the geodesic
+         curve. If this is positive, it will be in the direction of the
+         given angle. If it is negative, it will be in the opposite
+         direction.
+      }
+      \sstsubsection{
+         point2
+      }{
+         An array of double, with one element for each Frame axis
+         in which the coordinates of the required point will be returned.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astOffset2
+      }{
+         The direction of the geodesic curve at the end point. That is, the
+         angle (in radians) between the positive direction of the second
+         axis and the continuation of the geodesic curve at the requested
+         end point. Positive rotation is in the sense of rotation from
+         the positive direction of axis 2 to the positive direction of axis
+         1.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The geodesic curve used by this function is the path of
+         shortest distance between two points, as defined by the
+         \htmlref{astDistance}{astDistance} function.
+
+         \sstitem
+         An error will be reported if the Frame is not 2-dimensional.
+
+         \sstitem
+         This function will return \texttt{"} bad\texttt{"}  coordinate values (AST\_\_BAD)
+         if any of the input coordinates has this value.
+      }
+   }
+}
+\sstroutine{
+   astOutline$<$X$>$
+}{
+   Create a new Polygon outling values in a 2D data grid
+}{
+   \sstdescription{
+      This is a set of functions that create a \htmlref{Polygon}{Polygon} enclosing a single
+      contiguous set of pixels that have a specified value within a gridded
+      2-dimensional data array (e.g. an image).
+
+      A basic 2-dimensional \htmlref{Frame}{Frame} is used to represent the pixel coordinate
+      system in the returned Polygon. The \htmlref{Domain}{Domain} attribute is set to
+      \texttt{"} PIXEL\texttt{"} , the \htmlref{Title}{Title} attribute is set to \texttt{"} Pixel coordinates\texttt{"} , and the
+      Unit attribute for each axis is set to \texttt{"} pixel\texttt{"} . All other
+      attributes are left unset. The nature of the pixel coordinate system
+      is determined by parameter
+      \texttt{"} starpix\texttt{"} .
+
+      The
+      \texttt{"} maxerr\texttt{"}  and \texttt{"} maxvert\texttt{"}
+      parameters can be used to control how accurately the returned
+      Polygon represents the required region in the data array. The
+      number of vertices in the returned Polygon will be the minimum
+      needed to achieve the required accuracy.
+
+      You should use a function which matches the numerical type of the
+      data you are processing by replacing $<$X$>$ in the generic function
+      name
+      astOutline$<$X$>$
+      by an appropriate 1- or 2-character type code. For example, if you
+      are procesing data with type
+      \texttt{"} float\texttt{"} , you should use the function astOutlineF
+      (see the \texttt{"} Data Type Codes\texttt{"}  section below for the codes appropriate to
+      other numerical types).
+   }
+   \sstsynopsis{
+      AstPolygon $*$astOutline$<$X$>$( $<$Xtype$>$ value, int oper, const $<$Xtype$>$ array[],
+                                 const int lbnd[2], const int ubnd[2], double maxerr,
+                                 int maxvert, const int inside[2], int starpix )
+   }
+   \sstparameters{
+      \sstsubsection{
+         value
+      }{
+         A data value that specifies the pixels to be outlined.
+      }
+      \sstsubsection{
+         oper
+      }{
+         Indicates how the
+         \texttt{"} value\texttt{"}
+         parameter is used to select the outlined pixels. It can
+         have any of the following values:
+         \sstitemlist{
+
+            \sstitem
+            AST\_\_LT: outline pixels with value less than \texttt{"} value\texttt{"} .
+
+            \sstitem
+            AST\_\_LE: outline pixels with value less than or equal to \texttt{"} value\texttt{"} .
+
+            \sstitem
+            AST\_\_EQ: outline pixels with value equal to \texttt{"} value\texttt{"} .
+
+            \sstitem
+            AST\_\_NE: outline pixels with value not equal to \texttt{"} value\texttt{"} .
+
+            \sstitem
+            AST\_\_GE: outline pixels with value greater than or equal to \texttt{"} value\texttt{"} .
+
+            \sstitem
+            AST\_\_GT: outline pixels with value greater than \texttt{"} value\texttt{"} .
+         }
+      }
+      \sstsubsection{
+         array
+      }{
+         Pointer to a
+         2-dimensional array containing the data to be processed.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using astOutlineF, the type of each array element
+         should be \texttt{"} float\texttt{"} ).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the second dimension least rapidly
+         (i.e. Fortran array indexing is used).
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an array of two integers
+         containing the pixel index of the first pixel in the input grid
+         along each dimension.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an array of two integers
+         containing the pixel index of the last pixel in the input grid
+         along each dimension.
+
+         Note that \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"}  together define the shape
+         and size of the input pixel grid, its extent along a particular
+         (j\texttt{'} th) dimension being ubnd[j]-lbnd[j]$+$1 pixels.
+         For FITS images,
+         the lbnd values will be 1 and the ubnd
+         values will be equal to the NAXISi header values. Other
+         data systems, such as the Starlink NDF system, allow an
+         arbitrary pixel origin to be used (i.e. lbnd
+         is not necessarily 1).
+
+         These bounds also define the input grid\texttt{'} s floating point coordinate
+         system, each pixel having unit extent along each dimension with
+         integral coordinate values at its centre or upper corner, as selected
+         by parameter
+         \texttt{"} starpix\texttt{"} .
+      }
+      \sstsubsection{
+         maxerr
+      }{
+         Together with
+         \texttt{"} maxvert\texttt{"} ,
+         this determines how accurately the returned Polygon represents
+         the required region of the data array. It gives the target
+         discrepancy between the returned Polygon and the accurate outline
+         in the data array, expressed as a number of pixels. Insignificant
+         vertices are removed from the accurate outline, one by one, until
+         the number of vertices remaining in the returned Polygon equals
+         \texttt{"} maxvert\texttt{"} ,
+         or the largest discrepancy between the accurate outline and the
+         returned Polygon is greater than
+         \texttt{"} maxerr\texttt{"} . If \texttt{"} maxerr\texttt{"}
+         is zero or less, its value is ignored and the returned Polygon will
+         have the number of vertices specified by
+         \texttt{"} maxvert\texttt{"} .
+      }
+      \sstsubsection{
+         maxvert
+      }{
+         Together with
+         \texttt{"} maxerr\texttt{"} ,
+         this determines how accurately the returned Polygon represents
+         the required region of the data array. It gives the maximum
+         allowed number of vertices in the returned Polygon. Insignificant
+         vertices are removed from the accurate outline, one by one, until
+         the number of vertices remaining in the returned Polygon equals
+         \texttt{"} maxvert\texttt{"} ,
+         or the largest discrepancy between the accurate outline and the
+         returned Polygon is greater than
+         \texttt{"} maxerr\texttt{"} . If \texttt{"} maxvert\texttt{"}
+         is less than 3, its value is ignored and the number of vertices in
+         the returned Polygon will be the minimum needed to ensure that the
+         discrepancy between the accurate outline and the returned
+         Polygon is less than
+         \texttt{"} maxerr\texttt{"} .
+      }
+      \sstsubsection{
+         inside
+      }{
+         Pointer to an array of two integers
+         containing the pixel indices of a pixel known to be inside the
+         required region. This is needed because the supplied data
+         array may contain several disjoint areas of pixels that satisfy
+         the criterion specified by
+         \texttt{"} value\texttt{"}  and \texttt{"} oper\texttt{"} .
+         In such cases, the area described by the returned Polygon will
+         be the one that contains the pixel specified by
+         \texttt{"} inside\texttt{"} .
+         If the specified pixel is outside the bounds given by
+         \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"} ,
+         or has a value that does not meet the criterion specified by
+         \texttt{"} value\texttt{"}  and \texttt{"} oper\texttt{"} ,
+         then this function will search for a suitable pixel. The search
+         starts at the central pixel and proceeds in a spiral manner until
+         a pixel is found that meets the specified crierion.
+      }
+      \sstsubsection{
+         starpix
+      }{
+         A flag indicating the nature of the pixel coordinate system used
+         to describe the vertex positions in the returned Polygon. If
+         non-zero,
+         the standard Starlink definition of pixel coordinate is used in
+         which a pixel with integer index I spans a range of pixel coordinate
+         from (I-1) to I (i.e. pixel corners have integral pixel coordinates).
+         If zero,
+         the definition of pixel coordinate used by other AST functions
+         such as astResample, astMask,
+         etc., is used. In this definition, a pixel with integer index I
+         spans a range of pixel coordinate from (I-0.5) to (I$+$0.5) (i.e.
+         pixel centres have integral pixel coordinates).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astOutline$<$X$>$()
+      }{
+         A pointer to the required Polygon.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function proceeds by first finding a very accurate polygon,
+         and then removing insignificant vertices from this fine polygon
+         using
+         \htmlref{astDownsize}{astDownsize}.
+
+         \sstitem
+         The returned Polygon is the outer boundary of the contiguous set
+         of pixels that includes ths specified \texttt{"} inside\texttt{"}  point, and satisfy
+         the specified value requirement. This set of pixels may potentially
+         include \texttt{"} holes\texttt{"}  where the pixel values fail to meet the specified
+         value requirement. Such holes will be ignored by this function.
+
+         \sstitem
+         NULL
+         will be returned if this function is invoked with the global
+         error status set, or if it should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate masking function, you should
+      replace $<$X$>$ in the generic function name astOutline$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         L: long int
+
+         \sstitem
+         UL: unsigned long int
+
+         \sstitem
+         I: int
+
+         \sstitem
+         UI: unsigned int
+
+         \sstitem
+         S: short int
+
+         \sstitem
+         US: unsigned short int
+
+         \sstitem
+         B: byte (signed char)
+
+         \sstitem
+         UB: unsigned byte (unsigned char)
+
+      }
+      For example, astOutlineD would be used to process \texttt{"} double\texttt{"}
+      data, while astOutlineS would be used to process \texttt{"} short int\texttt{"}
+      data, etc.
+   }
+}
+\sstroutine{
+   astOverlap
+}{
+   Test if two regions overlap each other
+}{
+   \sstdescription{
+      This function returns an integer value indicating if the two
+      supplied Regions overlap. The two Regions are converted to a commnon
+      coordinate system before performing the check. If this conversion is
+      not possible (for instance because the two Regions represent areas in
+      different domains), then the check cannot be performed and a zero value
+      is returned to indicate this.
+   }
+   \sstsynopsis{
+      int astOverlap( AstRegion $*$this, AstRegion $*$that )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the first \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         that
+      }{
+         Pointer to the second Region.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astOverlap()
+      }{
+         A value indicating if there is any overlap between the two Regions.
+         Possible values are:
+
+         0 - The check could not be performed because the second Region
+             could not be mapped into the coordinate system of the first
+             Region.
+
+         1 - There is no overlap between the two Regions.
+
+         2 - The first Region is completely inside the second Region.
+
+         3 - The second Region is completely inside the first Region.
+
+         4 - There is partial overlap between the two Regions.
+
+         5 - The Regions are identical to within their uncertainties.
+
+         6 - The second Region is the exact negation of the first Region
+             to within their uncertainties.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned values 5 and 6 do not check the value of the \htmlref{Closed}{Closed}
+         attribute in the two Regions.
+
+         \sstitem
+         A value of zero will be returned if this function is invoked with the
+         AST error status set, or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astParameterName
+}{
+   Get the name of the global parameter at a given index within the Table
+}{
+   \sstdescription{
+      This function returns a string holding the name of the global parameter with
+      the given index within the \htmlref{Table}{Table}.
+
+      This function is intended primarily as a means of iterating round all
+      the parameters in a Table. For this purpose, the number of parameters in
+      the Table is given by the \htmlref{Nparameter}{Nparameter} attribute of the Table. This function
+      could then be called in a loop, with the index value going from
+      zero to one less than Nparameter.
+
+      Note, the index associated with a parameter decreases monotonically with
+      the age of the parameter: the oldest Parameter in the Table will have index
+      one, and the Parameter added most recently to the Table will have the
+      largest index.
+   }
+   \sstsynopsis{
+      const char $*$astParameterName( AstTable $*$this, int index )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         index
+      }{
+         The index into the list of parameters. The first parameter has index
+         one, and the last has index \texttt{"} Nparameter\texttt{"} .
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astParameterName()
+      }{
+         A pointer to a null-terminated string containing the
+         upper case parameter name.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned pointer is guaranteed to remain valid and the
+         string to which it points will not be over-written for a total
+         of 50 successive invocations of this function. After this, the
+         memory containing the string may be re-used, so a copy of the
+         string should be made if it is needed for longer than this.
+
+         \sstitem
+         A NULL pointer will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   astPcdMap
+}{
+   Create a PcdMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{PcdMap}{PcdMap} and optionally initialises its
+      attributes.
+
+      A PcdMap is a non-linear \htmlref{Mapping}{Mapping} which transforms 2-dimensional
+      positions to correct for the radial distortion introduced by some
+      cameras and telescopes. This can take the form either of pincushion
+      or barrel distortion, and is characterized by a single distortion
+      coefficient.
+
+      A PcdMap is specified by giving this distortion coefficient and the
+      coordinates of the centre of the radial distortion. The forward
+      transformation of a PcdMap applies the distortion:
+
+         RD = R $*$ ( 1 $+$ C $*$ R $*$ R )
+
+      where R is the undistorted radial distance from the distortion
+      centre (specified by attribute PcdCen), RD is the radial distance
+      from the same centre in the presence of distortion, and C is the
+      distortion coefficient (given by attribute \htmlref{Disco}{Disco}).
+
+      The inverse transformation of a PcdMap removes the distortion
+      produced by the forward transformation. The expression used to derive
+      R from RD is an approximate inverse of the expression above, obtained
+      from two iterations of the Newton-Raphson method. The mismatch between
+      the forward and inverse expressions is negligible for astrometric
+      applications (to reach 1 milliarcsec at the edge of the Anglo-Australian
+      Telescope triplet or a Schmidt field would require field diameters of
+      2.4 and 42 degrees respectively).
+
+      If a PcdMap is inverted (e.g. using \htmlref{astInvert}{astInvert}) then the roles of the
+      forward and inverse transformations are reversed; the forward
+      transformation will remove the distortion, and the inverse
+      transformation will apply it.
+   }
+   \sstsynopsis{
+      AstPcdMap $*$astPcdMap( double disco, const double pcdcen[2],
+                            const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         disco
+      }{
+         The distortion coefficient. Negative values give barrel
+         distortion, positive values give pincushion distortion, and
+         zero gives no distortion.
+      }
+      \sstsubsection{
+         pcdcen
+      }{
+         A 2-element array containing the coordinates of the centre of the
+         distortion.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new PcdMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astPcdMap()
+      }{
+         A pointer to the new PcdMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astPermAxes
+}{
+   Permute the axis order in a Frame
+}{
+   \sstdescription{
+      This function permutes the order in which a \htmlref{Frame}{Frame}\texttt{'} s axes occur.
+   }
+   \sstsynopsis{
+      void astPermAxes( AstFrame $*$this, const int perm[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         perm
+      }{
+         An array with one element for each axis of the Frame (\htmlref{Naxes}{Naxes}
+         attribute). This should list the axes in their new order,
+         using the original axis numbering (which starts at 1 for the
+         first axis).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Only genuine permutations of the axis order are permitted, so
+         each axis must be referenced exactly once in the \texttt{"} perm\texttt{"}  array.
+
+         \sstitem
+         If successive axis permutations are applied to a Frame, then
+         the effects are cumulative.
+      }
+   }
+}
+\sstroutine{
+   astPermMap
+}{
+   Create a PermMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{PermMap}{PermMap} and optionally initialises its
+      attributes.
+
+      A PermMap is a \htmlref{Mapping}{Mapping} which permutes the order of coordinates,
+      and possibly also changes the number of coordinates, between its
+      input and output.
+
+      In addition to permuting the coordinate order, a PermMap may
+      also assign constant values to coordinates. This is useful when
+      the number of coordinates is being increased as it allows fixed
+      values to be assigned to any new ones.
+   }
+   \sstsynopsis{
+      AstPermMap $*$astPermMap( int nin, const int inperm[], int nout,
+                              const int outperm[], double constant[],
+                              const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         nin
+      }{
+         The number of input coordinates.
+      }
+      \sstsubsection{
+         inperm
+      }{
+         An optional array with \texttt{"} nin\texttt{"}  elements which, for each input
+         coordinate, should contain the number of the output
+         coordinate whose value is to be used (note that this array
+         therefore defines the inverse coordinate transformation).
+         Coordinates are numbered starting from 1.
+
+         For details of additional special values that may be used in
+         this array, see the description of the \texttt{"} constant\texttt{"}  parameter.
+
+         If a NULL pointer is supplied instead of an array, each input
+         coordinate will obtain its value from the corresponding
+         output coordinate (or will be assigned the value AST\_\_BAD if
+         there is no corresponding output coordinate).
+      }
+      \sstsubsection{
+         nout
+      }{
+         The number of output coordinates.
+      }
+      \sstsubsection{
+         outperm
+      }{
+         An optional array with \texttt{"} nout\texttt{"}  elements which, for each output
+         coordinate, should contain the number of the input coordinate
+         whose value is to be used (note that this array therefore
+         defines the forward coordinate transformation).  Coordinates
+         are numbered starting from 1.
+
+         For details of additional special values that may be used in
+         this array, see the description of the \texttt{"} constant\texttt{"}  parameter.
+
+         If a NULL pointer is supplied instead of an array, each output
+         coordinate will obtain its value from the corresponding
+         input coordinate (or will be assigned the value AST\_\_BAD if
+         there is no corresponding input coordinate).
+      }
+      \sstsubsection{
+         constant
+      }{
+         An optional array containing values which may be assigned to
+         input and/or output coordinates instead of deriving them
+         from other coordinate values. If either of the \texttt{"} inperm\texttt{"}  or
+         \texttt{"} outperm\texttt{"}  arrays contains a negative value, it is used to
+         address this \texttt{"} constant\texttt{"}  array (such that -1 addresses the
+         first element, -2 addresses the second element, etc.) and the
+         value obtained is used as the corresponding coordinate value.
+
+         Care should be taken to ensure that locations lying outside
+         the extent of this array are not accidentally addressed. The
+         array is not used if the \texttt{"} inperm\texttt{"}  and \texttt{"} outperm\texttt{"}  arrays do not
+         contain negative values.
+
+         If a NULL pointer is supplied instead of an array, the
+         behaviour is as if the array were of infinite length and
+         filled with the value AST\_\_BAD.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new PermMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astPermMap()
+      }{
+         A pointer to the new PermMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If either of the \texttt{"} inperm\texttt{"}  or \texttt{"} outperm\texttt{"}  arrays contains a
+         zero value (or a positive value which does not identify a valid
+         output/input coordinate, as appropriate), then the value
+         AST\_\_BAD is assigned as the new coordinate value.
+
+         \sstitem
+         This function does not attempt to ensure that the forward and
+         inverse transformations performed by the PermMap are
+         self-consistent in any way. You are therefore free to supply
+         coordinate permutation arrays that achieve whatever effect is
+         desired.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astPickAxes
+}{
+   Create a new Frame by picking axes from an existing one
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Frame}{Frame} whose axes are copied from an
+      existing Frame along with other Frame attributes, such as its
+      \htmlref{Title}{Title}. Any number (zero or more) of the original Frame\texttt{'} s axes
+      may be copied, in any order, and additional axes with default
+      attributes may also be included in the new Frame.
+
+      Optionally, a \htmlref{Mapping}{Mapping} that converts between the coordinate
+      systems described by the two Frames will also be returned.
+   }
+   \sstsynopsis{
+      AstFrame $*$astPickAxes( AstFrame $*$this, int naxes, const int axes[],
+                             AstMapping $*$$*$map )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the original Frame.
+      }
+      \sstsubsection{
+         naxes
+      }{
+         The number of axes required in the new Frame.
+      }
+      \sstsubsection{
+         axes
+      }{
+         An array, with \texttt{"} naxes\texttt{"}  elements, which lists the axes to be
+         copied. These should be given in the order required in the
+         new Frame, using the axis numbering in the original Frame
+         (which starts at 1 for the first axis). Axes may be selected
+         in any order, but each may only be used once.  If additional
+         (default) axes are also to be included, the corresponding
+         elements of this array should be set to zero.
+      }
+      \sstsubsection{
+         map
+      }{
+         Address of a location in which to return a pointer to a new
+         Mapping. This will be a \htmlref{PermMap}{PermMap} (or a \htmlref{UnitMap}{UnitMap} as a special
+         case) that describes the axis permutation that has taken
+         place between the original and new Frames. The Mapping\texttt{'} s
+         forward transformation will convert coordinates from the
+         original Frame into the new one, and vice versa.
+
+         If this Mapping is not required, a NULL value may be supplied
+         for this parameter.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         This function applies to all Frames. The class of Frame returned
+         may differ from that of the original Frame, depending on which
+         axes are selected. For example, if a single axis is picked from a
+         \htmlref{SkyFrame}{SkyFrame} (which must always have two axes) then the resulting
+         Frame cannot be a valid SkyFrame, so will revert to the parent
+         class (Frame) instead.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         Using this function on a FrameSet is identical to using it on
+         the current Frame in the FrameSet. The returned Frame will not
+         be a FrameSet.
+      }
+      \sstsubsection{
+         \htmlref{Region}{Region}
+      }{
+         If this function is used on a Region, an attempt is made to
+         retain the bounds information on the selected axes. If
+         succesful, the returned Frame will be a Region of some class.
+         Otherwise, the returned Frame is obtained by calling this
+         function on the Frame represented by the supplied Region (the
+         returned Frame will then not be a Region). In order to be
+         succesful, the selected axes in the Region must be independent
+         of the others. For instance, a \htmlref{Box}{Box} can be split in this way but
+         a \htmlref{Circle}{Circle} cannot. Another requirement for success is that no
+         default axes are added (that is, the
+         \texttt{"} axes\texttt{"}
+         array must not contain any zero values.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astPickAxes()
+      }{
+         A pointer to the new Frame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The new Frame will contain a \texttt{"} deep\texttt{"}  copy (c.f. \htmlref{astCopy}{astCopy}) of all
+         the data selected from the original Frame. Modifying any aspect
+         of the new Frame will therefore not affect the original one.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astPlot
+}{
+   Create a Plot
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Plot}{Plot} and optionally initialises its
+      attributes.
+
+      A Plot is a specialised form of \htmlref{FrameSet}{FrameSet}, in which the base
+      \htmlref{Frame}{Frame} describes a \texttt{"} graphical\texttt{"}  coordinate system and is
+      associated with a rectangular plotting area in the underlying
+      graphics system. This plotting area is where graphical output
+      appears. It is defined when the Plot is created.
+
+      The current Frame of a Plot describes a \texttt{"} physical\texttt{"}  coordinate
+      system, which is the coordinate system in which plotting
+      operations are specified. The results of each plotting operation
+      are automatically transformed into graphical coordinates so as
+      to appear in the plotting area (subject to any clipping which
+      may be in effect).
+
+      Because the \htmlref{Mapping}{Mapping} between physical and graphical coordinates
+      may often be non-linear, or even discontinuous, most plotting
+      does not result in simple straight lines. The basic plotting
+      element is therefore not a straight line, but a geodesic curve
+      (see \htmlref{astCurve}{astCurve}). A Plot also provides facilities for drawing
+      markers or symbols (\htmlref{astMark}{astMark}), text (\htmlref{astText}{astText}) and grid lines
+      (\htmlref{astGridLine}{astGridLine}). It is also possible to draw curvilinear axes with
+      optional coordinate grids (\htmlref{astGrid}{astGrid}).
+      A range of Plot attributes is available to allow precise control
+      over the appearance of graphical output produced by these
+      functions.
+
+      You may select different physical coordinate systems in which to
+      plot (including the native graphical coordinate system itself)
+      by selecting different Frames as the current Frame of a Plot,
+      using its \htmlref{Current}{Current} attribute.  You may also set up clipping (see
+      \htmlref{astClip}{astClip}) to limit the extent of any plotting you perform, and
+      this may be done in any of the coordinate systems associated
+      with the Plot, not necessarily the one you are plotting in.
+
+      Like any FrameSet, a Plot may also be used as a Frame. In this
+      case, it behaves like its current Frame, which describes the
+      physical coordinate system.
+
+      When used as a Mapping, a Plot describes the inter-relation
+      between graphical coordinates (its base Frame) and physical
+      coordinates (its current Frame).  It differs from a normal
+      FrameSet, however, in that an attempt to transform points which
+      lie in clipped areas of the Plot will result in bad coordinate
+      values (AST\_\_BAD).
+   }
+   \sstsynopsis{
+      AstPlot $*$astPlot( AstFrame $*$frame, const float graphbox[ 4 ],
+                        const double basebox[ 4 ], const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         Pointer to a Frame describing the physical coordinate system
+         in which to plot. A pointer to a FrameSet may also be given,
+         in which case its current Frame will be used to define the
+         physical coordinate system and its base Frame will be mapped
+         on to graphical coordinates (see below).
+
+         If a null \htmlref{Object}{Object} pointer (AST\_\_NULL) is given, a default
+         2-dimensional Frame will be used to describe the physical
+         coordinate system. Labels, etc. may then be attached to this
+         by setting the appropriate Frame attributes
+         (e.g. \htmlref{Label(axis)}{Label(axis)}) for the Plot.
+      }
+      \sstsubsection{
+         graphbox
+      }{
+         An array giving the position and extent of the plotting area
+         (on the plotting surface of the underlying graphics system)
+         in which graphical output is to appear. This must be
+         specified using graphical coordinates appropriate to the
+         underlying graphics system.
+
+         The first pair of values should give the coordinates of the
+         bottom left corner of the plotting area and the second pair
+         should give the coordinates of the top right corner. The
+         coordinate on the horizontal axis should be given first in
+         each pair. Note that the order in which these points are
+         given is important because it defines up, down, left and
+         right for subsequent graphical operations.
+      }
+      \sstsubsection{
+         basebox
+      }{
+         An array giving the coordinates of two points in the supplied
+         Frame (or in the base Frame if a FrameSet was supplied) which
+         correspond to the bottom left and top right corners of the
+         plotting area, as specified above. This range of coordinates
+         will be mapped linearly on to the plotting area. The
+         coordinates should be given in the same order as above.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Plot. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astPlot()
+      }{
+         A pointer to the new Plot.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The base Frame of the returned Plot will be a new Frame which
+         is created by this function to represent the coordinate system
+         of the underlying graphics system (graphical coordinates). It is
+         given a Frame index of 1 within the Plot. The choice of base
+         Frame (\htmlref{Base}{Base} attribute) should not, in general, be changed once a
+         Plot has been created (although you could use this as a way of
+         moving the plotting area around on the plotting surface).
+
+         \sstitem
+         If a Frame is supplied (via the \texttt{"} frame\texttt{"}  pointer), then it
+         becomes the current Frame of the new Plot and is given a Frame
+         index of 2.
+
+         \sstitem
+         If a FrameSet is supplied (via the \texttt{"} frame\texttt{"}  pointer), then
+         all the Frames within this FrameSet become part of the new Plot
+         (where their Frame indices are increased by 1), with the
+         FrameSet\texttt{'} s current Frame becoming the current Frame of the Plot.
+
+         \sstitem
+         If a null Object pointer (AST\_\_NULL) is supplied (via the
+         \texttt{"} frame\texttt{"}  pointer), then the returned Plot will contain two
+         Frames, both created by this function. The base Frame will
+         describe graphics coordinates (as above) and the current Frame
+         will be a basic Frame with no attributes set (this will
+         therefore give default values for such things as the Plot \htmlref{Title}{Title}
+         and the Label on each axis). Physical coordinates will be mapped
+         linearly on to graphical coordinates.
+
+         \sstitem
+         An error will result if the Frame supplied (or the base Frame
+         if a FrameSet was supplied) is not 2-dimensional.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astPlot3D
+}{
+   Create a Plot3D
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Plot3D}{Plot3D} and optionally initialises
+      its attributes.
+
+      A Plot3D is a specialised form of \htmlref{Plot}{Plot} that provides facilities
+      for producing 3D graphical output.
+   }
+   \sstsynopsis{
+      AstPlot3D $*$astPlot3D( AstFrame $*$frame, const float graphbox[ 6 ],
+                            const double basebox[ 6 ], const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         Pointer to a \htmlref{Frame}{Frame} describing the physical coordinate system
+         in which to plot. A pointer to a \htmlref{FrameSet}{FrameSet} may also be given,
+         in which case its current Frame will be used to define the
+         physical coordinate system and its base Frame will be mapped
+         on to graphical coordinates (see below).
+
+         If a null \htmlref{Object}{Object} pointer (AST\_\_NULL) is given, a default
+         3-dimensional Frame will be used to describe the physical
+         coordinate system. Labels, etc. may then be attached to this
+         by setting the appropriate Frame attributes
+         (e.g. \htmlref{Label(axis)}{Label(axis)}) for the Plot.
+      }
+      \sstsubsection{
+         graphbox
+      }{
+         An array giving the position and extent of the plotting volume
+         (within the plotting space of the underlying graphics system)
+         in which graphical output is to appear. This must be
+         specified using graphical coordinates appropriate to the
+         underlying graphics system.
+
+         The first triple of values should give the coordinates of the
+         bottom left corner of the plotting volume and the second triple
+         should give the coordinates of the top right corner. The
+         coordinate on the horizontal axis should be given first in
+         each pair. Note that the order in which these points are
+         given is important because it defines up, down, left and
+         right for subsequent graphical operations.
+      }
+      \sstsubsection{
+         basebox
+      }{
+         An array giving the coordinates of two points in the supplied
+         Frame (or in the base Frame if a FrameSet was supplied) which
+         correspond to the bottom left and top right corners of the
+         plotting volume, as specified above. This range of coordinates
+         will be mapped linearly on to the plotting area. The
+         coordinates should be given in the same order as above.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Plot3D. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astPlot3D()
+      }{
+         A pointer to the new Plot3D.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The base Frame of the returned Plot3D will be a new Frame which
+         is created by this function to represent the coordinate system
+         of the underlying graphics system (graphical coordinates). It is
+         given a Frame index of 1 within the Plot3D. The choice of base
+         Frame (\htmlref{Base}{Base} attribute) should not, in general, be changed once a
+         Plot3D has been created (although you could use this as a way of
+         moving the plotting area around on the plotting surface).
+
+         \sstitem
+         If a Frame is supplied (via the \texttt{"} frame\texttt{"}  pointer), then it
+         becomes the current Frame of the new Plot3D and is given a Frame
+         index of 2.
+
+         \sstitem
+         If a FrameSet is supplied (via the \texttt{"} frame\texttt{"}  pointer), then
+         all the Frames within this FrameSet become part of the new Plot3D
+         (where their Frame indices are increased by 1), with the
+         FrameSet\texttt{'} s current Frame becoming the current Frame of the Plot3D.
+
+         \sstitem
+         At least one of the three axes of the current Frame must be
+         independent of the other two current Frame axes.
+
+         \sstitem
+         If a null Object pointer (AST\_\_NULL) is supplied (via the
+         \texttt{"} frame\texttt{"}  pointer), then the returned Plot3D will contain two
+         Frames, both created by this function. The base Frame will
+         describe graphics coordinates (as above) and the current Frame
+         will be a basic Frame with no attributes set (this will
+         therefore give default values for such things as the Plot3D \htmlref{Title}{Title}
+         and the Label on each axis). Physical coordinates will be mapped
+         linearly on to graphical coordinates.
+
+         \sstitem
+         An error will result if the Frame supplied (or the base Frame
+         if a FrameSet was supplied) is not 3-dimensional.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astPointList
+}{
+   Create a PointList
+}{
+   \sstdescription{
+      This function creates a new \htmlref{PointList}{PointList} object and optionally initialises
+      its attributes.
+
+      A PointList object is a specialised type of \htmlref{Region}{Region} which represents a
+      collection of points in a coordinate \htmlref{Frame}{Frame}.
+   }
+   \sstsynopsis{
+      AstPointList $*$astPointList( AstFrame $*$frame, int npnt, int ncoord, int dim,
+                                  const double $*$points, AstRegion $*$unc,
+                                  const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         A pointer to the Frame in which the region is defined. A deep
+         copy is taken of the supplied Frame. This means that any
+         subsequent changes made to the Frame using the supplied pointer
+         will have no effect the Region.
+      }
+      \sstsubsection{
+         npnt
+      }{
+         The number of points in the Region.
+      }
+      \sstsubsection{
+         ncoord
+      }{
+         The number of coordinates being supplied for each point. This
+         must equal the number of axes in the supplied Frame, given by
+         its \htmlref{Naxes}{Naxes} attribute.
+      }
+      \sstsubsection{
+         dim
+      }{
+         The number of elements along the second dimension of the \texttt{"} points\texttt{"}
+         array (which contains the point coordinates). This value is
+         required so that the coordinate values can be correctly
+         located if they do not entirely fill this array. The value
+         given should not be less than \texttt{"} npnt\texttt{"} .
+      }
+      \sstsubsection{
+         points
+      }{
+         The address of the first element of a 2-dimensional array of
+         shape \texttt{"} [ncoord][dim]\texttt{"}  giving the physical coordinates of the
+         points. These should be stored such that the value of coordinate
+         number \texttt{"} coord\texttt{"}  for point number \texttt{"} pnt\texttt{"}  is found in element
+         \texttt{"} in[coord][pnt]\texttt{"} .
+      }
+      \sstsubsection{
+         unc
+      }{
+         An optional pointer to an existing Region which specifies the uncertainties
+         associated with each point in the PointList being created. The
+         uncertainty at any point in the PointList is found by shifting the
+         supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at the point
+         being considered. The area covered by the shifted uncertainty Region
+         then represents the uncertainty in the position. The uncertainty is
+         assumed to be the same for all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. \htmlref{Box}{Box}, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Box. Alternatively,
+         a NULL \htmlref{Object}{Object} pointer
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the bounding box of the
+         PointList being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{astOverlap}{astOverlap}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{astSimplify}{astSimplify}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new PointList. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astPointList()
+      }{
+         A pointer to the new PointList.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astPolyCurve
+}{
+   Draw a series of connected geodesic curves
+}{
+   \sstdescription{
+      This function joins a series of points specified in the physical
+      coordinate system of a \htmlref{Plot}{Plot} by drawing a sequence of geodesic
+      curves.  It is equivalent to making repeated use of the \htmlref{astCurve}{astCurve}
+      function (q.v.), except that astPolyCurve will generally be more
+      efficient when drawing many geodesic curves end-to-end. A
+      typical application of this might be in drawing contour lines.
+
+      As with astCurve, full account is taken of the \htmlref{Mapping}{Mapping} between
+      physical and graphical coordinate systems. This includes any
+      discontinuities and clipping established using \htmlref{astClip}{astClip}.
+   }
+   \sstsynopsis{
+      void astPolyCurve( AstPlot $*$this, int npoint, int ncoord, int indim,
+                         const double $*$in )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         npoint
+      }{
+         The number of points between which geodesic curves are to be drawn.
+      }
+      \sstsubsection{
+         ncoord
+      }{
+         The number of coordinates being supplied for each point (i.e.
+         the number of axes in the current \htmlref{Frame}{Frame} of the Plot, as given
+         by its \htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         indim
+      }{
+         The number of elements along the second dimension of the \texttt{"} in\texttt{"}
+         array (which contains the input coordinates). This value is
+         required so that the coordinate values can be correctly
+         located if they do not entirely fill this array. The value
+         given should not be less than \texttt{"} npoint\texttt{"} .
+      }
+      \sstsubsection{
+         in
+      }{
+         The address of the first element in a 2-dimensional array of shape
+         \texttt{"} [ncoord][indim]\texttt{"}  giving the
+         physical coordinates of the points which are to be joined in
+         sequence by geodesic curves. These should be stored such that
+         the value of coordinate number \texttt{"} coord\texttt{"}  for point number
+         \texttt{"} point\texttt{"}  is found in element \texttt{"} in[coord][point]\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         No curve is drawn on either side of any point which has any
+         coordinate equal to the value AST\_\_BAD.
+
+         \sstitem
+         An error results if the base Frame of the Plot is not
+         2-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   astPolyMap
+}{
+   Create a PolyMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{PolyMap}{PolyMap} and optionally initialises
+      its attributes.
+
+      A PolyMap is a form of \htmlref{Mapping}{Mapping} which performs a general polynomial
+      transformation.  Each output coordinate is a polynomial function of
+      all the input coordinates. The coefficients are specified separately
+      for each output coordinate. The forward and inverse transformations
+      are defined independantly by separate sets of coefficients. If no
+      inverse transformation is supplied, an iterative method can be used
+      to evaluate the inverse based only on the forward transformation.
+   }
+   \sstsynopsis{
+      AstPolyMap $*$astPolyMap( int nin, int nout, int ncoeff\_f, const double coeff\_f[],
+                              int ncoeff\_i, const double coeff\_i[],
+                              const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         nin
+      }{
+         The number of input coordinates.
+      }
+      \sstsubsection{
+         nout
+      }{
+         The number of output coordinates.
+      }
+      \sstsubsection{
+         ncoeff\_f
+      }{
+         The number of non-zero coefficients necessary to define the
+         forward transformation of the PolyMap. If zero is supplied, the
+         forward transformation will be undefined.
+      }
+      \sstsubsection{
+         coeff\_f
+      }{
+         An array containing
+         \texttt{"} ncoeff\_f$*$( 2 $+$ nin )\texttt{"}  elements. Each group of \texttt{"} 2 $+$ nin\texttt{"}
+         adjacent elements describe a single coefficient of the forward
+         transformation. Within each such group, the first element is the
+         coefficient value; the next element is the integer index of the
+         PolyMap output which uses the coefficient within its defining
+         polynomial (the first output has index 1); the remaining elements
+         of the group give the integer powers to use with each input
+         coordinate value (powers must not be negative, and floating
+         point values are rounded to the nearest integer).
+         If \texttt{"} ncoeff\_f\texttt{"}  is zero, a NULL pointer may be supplied for \texttt{"} coeff\_f\texttt{"} .
+
+         For instance, if the PolyMap has 3 inputs and 2 outputs, each group
+         consisting of 5 elements, A groups such as \texttt{"} (1.2, 2.0, 1.0, 3.0, 0.0)\texttt{"}
+         describes a coefficient with value 1.2 which is used within the
+         definition of output 2. The output value is incremented by the
+         product of the coefficient value, the value of input coordinate
+         1 raised to the power 1, and the value of input coordinate 2 raised
+         to the power 3. Input coordinate 3 is not used since its power is
+         specified as zero. As another example, the group \texttt{"} (-1.0, 1.0,
+         0.0, 0.0, 0.0 )\texttt{"}  describes adds a constant value -1.0 onto
+         output 1 (it is a constant value since the power for every input
+         axis is given as zero).
+
+         Each final output coordinate value is the sum of the \texttt{"} ncoeff\_f\texttt{"}  terms
+         described by the \texttt{"} ncoeff\_f\texttt{"}  groups within the supplied array.
+      }
+      \sstsubsection{
+         ncoeff\_i
+      }{
+         The number of non-zero coefficients necessary to define the
+         inverse transformation of the PolyMap. If zero is supplied, the
+         inverse transformation will be undefined.
+      }
+      \sstsubsection{
+         coeff\_i
+      }{
+         An array containing
+         \texttt{"} ncoeff\_i$*$( 2 $+$ nout )\texttt{"}  elements. Each group of \texttt{"} 2 $+$ nout\texttt{"}
+         adjacent elements describe a single coefficient of the inverse
+         transformation, using the same schame as \texttt{"} coeff\_f\texttt{"} ,
+         except that \texttt{"} inputs\texttt{"}  and \texttt{"} outputs\texttt{"}  are transposed.
+         If \texttt{"} ncoeff\_i\texttt{"}  is zero, a NULL pointer may be supplied for \texttt{"} coeff\_i\texttt{"} .
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new PolyMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astPolyMap()
+      }{
+         A pointer to the new PolyMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astPolyTran
+}{
+   Fit a PolyMap inverse or forward transformation
+}{
+   \sstdescription{
+      This function creates a new \htmlref{PolyMap}{PolyMap} which is a copy of the supplied
+      PolyMap, in which a specified transformation (forward or inverse)
+      has been replaced by a new polynomial transformation. The
+      coefficients of the new transformation are estimated by sampling
+      the other transformation and performing a least squares polynomial
+      fit in the opposite direction to the sampled positions and values.
+
+      This method can only be used on (1-input,1-output) or (2-input,2-output)
+      PolyMaps.
+
+      The transformation to create is specified by the
+      \texttt{"} forward\texttt{"}  parameter.
+      In what follows \texttt{"} X\texttt{"}  refers to the inputs of the PolyMap, and \texttt{"} Y\texttt{"}  to
+      the outputs of the PolyMap. The forward transformation transforms
+      input values (X) into output values (Y), and the inverse transformation
+      transforms output values (Y) into input values (X). Within a PolyMap,
+      each transformation is represented by an independent set of
+      polynomials, P\_f or P\_i: Y=P\_f(X) for the forward transformation and
+      X=P\_i(Y) for the inverse transformation.
+
+      The \texttt{"} forward\texttt{"}
+      parameter specifies the transformation to be replaced. If it is
+      non-zero,
+      a new forward transformation is created
+      by first finding the input values (X) using the inverse transformation
+      (which must be available) at a regular grid of points (Y) covering a
+      rectangular region of the PolyMap\texttt{'} s output space. The coefficients of
+      the required forward polynomial, Y=P\_f(X), are chosen in order to
+      minimise the sum of the squared residuals between the sampled values
+      of Y and P\_f(X).
+
+      If \texttt{"} forward\texttt{"}  is zero (probably the most likely case),
+      a new inverse transformation is created by
+      first finding the output values (Y) using the forward transformation
+      (which must be available) at a regular grid of points (X) covering a
+      rectangular region of the PolyMap\texttt{'} s input space. The coefficients of
+      the required inverse polynomial, X=P\_i(Y), are chosen in order to
+      minimise the sum of the squared residuals between the sampled values
+      of X and P\_i(Y).
+
+      This fitting process is performed repeatedly with increasing
+      polynomial orders (starting with linear) until the target
+      accuracy is achieved, or a specified maximum order is reached. If
+      the target accuracy cannot be achieved even with this maximum-order
+      polynomial, the best fitting maximum-order polynomial is returned so
+      long as its accuracy is better than
+      \texttt{"} maxacc\texttt{"} .
+      If it is not, an error is reported.
+   }
+   \sstsynopsis{
+      AstPolyMap $*$astPolyTran( AstPolyMap $*$this, int forward, double acc,
+                               double maxacc, int maxorder, const double $*$lbnd,
+                               const double $*$ubnd )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the original \htmlref{Mapping}{Mapping}.
+      }
+      \sstsubsection{
+         forward
+      }{
+         If non-zero,
+         the forward PolyMap transformation is replaced. Otherwise the
+         inverse transformation is replaced.
+      }
+      \sstsubsection{
+         acc
+      }{
+         The target accuracy, expressed as a geodesic distance within
+         the PolyMap\texttt{'} s input space (if
+         \texttt{"} forward\texttt{"}  is zero)  or output space (if \texttt{"} forward\texttt{"}  is non-zero).
+      }
+      \sstsubsection{
+         maxacc
+      }{
+         The maximum allowed accuracy for an acceptable polynomial,
+         expressed as a geodesic distance within the PolyMap\texttt{'} s input
+         space (if
+         \texttt{"} forward\texttt{"}  is zero)  or output space (if \texttt{"} forward\texttt{"}  is non-zero).
+      }
+      \sstsubsection{
+         maxorder
+      }{
+         The maximum allowed polynomial order. This is one more than the
+         maximum power of either input axis. So for instance, a value of
+         3 refers to a quadratic polynomial. Note, cross terms with total
+         powers greater than or equal to
+         maxorder
+         are not inlcuded in the fit. So the maximum number of terms in
+         each of the fitted polynomials is
+         maxorder$*$(maxorder$+$1)/2.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an
+         array holding the lower bounds of a rectangular region within
+         the PolyMap\texttt{'} s input space (if
+         \texttt{"} forward\texttt{"}  is zero)  or output space (if \texttt{"} forward\texttt{"}  is non-zero).
+         The new polynomial will be evaluated over this rectangle. The
+         length of this array should equal the value of the PolyMap\texttt{'} s \htmlref{Nin}{Nin}
+         or \htmlref{Nout}{Nout} attribute, depending on
+         \texttt{"} forward\texttt{"} .
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an
+         array holding the upper bounds of a rectangular region within
+         the PolyMap\texttt{'} s input space (if
+         \texttt{"} forward\texttt{"}  is zero)  or output space (if \texttt{"} forward\texttt{"}  is non-zero).
+         The new polynomial will be evaluated over this rectangle.  The
+         length of this array should equal the value of the PolyMap\texttt{'} s Nin
+         or Nout attribute, depending on
+         \texttt{"} forward\texttt{"} .
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astPolyTran()
+      }{
+         A pointer to the new PolyMap.
+         A NULL pointer
+         will be returned if the fit fails to achieve the accuracy
+         specified by
+         \texttt{"} maxacc\texttt{"} ,
+         but no error will be reported.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function can only be used on 1D or 2D PolyMaps which have
+         the same number of inputs and outputs.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astPolygon
+}{
+   Create a Polygon
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Polygon}{Polygon} object and optionally initialises
+      its attributes.
+
+      The Polygon class implements a polygonal area, defined by a
+      collection of vertices, within a 2-dimensional \htmlref{Frame}{Frame}. The vertices
+      are connected together by geodesic curves within the encapsulated Frame.
+      For instance, if the encapsulated Frame is a simple Frame then the
+      geodesics will be straight lines, but if the Frame is a \htmlref{SkyFrame}{SkyFrame} then
+      the geodesics will be great circles. Note, the vertices must be
+      supplied in an order such that the inside of the polygon is to the
+      left of the boundary as the vertices are traversed. Supplying them
+      in the reverse order will effectively negate the polygon.
+
+      Within a SkyFrame, neighbouring vertices are always joined using the
+      shortest path. Thus if an edge of 180 degrees or more in length is
+      required, it should be split into section each of which is less
+      than 180 degrees. The closed path joining all the vertices in order
+      will divide the celestial sphere into two disjoint regions. The
+      inside of the polygon is the region which is circled in an
+      anti-clockwise manner (when viewed from the inside of the celestial
+      sphere) when moving through the list of vertices in the order in
+      which they were supplied when the Polygon was created (i.e. the
+      inside is to the left of the boundary when moving through the
+      vertices in the order supplied).
+   }
+   \sstsynopsis{
+      AstPolygon $*$astPolygon( AstFrame $*$frame, int npnt, int dim,
+                              const double $*$points, AstRegion $*$unc,
+                              const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame
+      }{
+         A pointer to the Frame in which the region is defined. It must
+         have exactly 2 axes. A deep copy is taken of the supplied Frame.
+         This means that any subsequent changes made to the Frame using the
+         supplied pointer will have no effect the \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         npnt
+      }{
+         The number of points in the Region.
+      }
+      \sstsubsection{
+         dim
+      }{
+         The number of elements along the second dimension of the \texttt{"} points\texttt{"}
+         array (which contains the point coordinates). This value is
+         required so that the coordinate values can be correctly
+         located if they do not entirely fill this array. The value
+         given should not be less than \texttt{"} npnt\texttt{"} .
+      }
+      \sstsubsection{
+         points
+      }{
+         The address of the first element of a 2-dimensional array of
+         shape \texttt{"} [2][dim]\texttt{"}  giving the physical coordinates of the vertices.
+         These should be stored such that the value of coordinate
+         number \texttt{"} coord\texttt{"}  for point number \texttt{"} pnt\texttt{"}  is found in element
+         \texttt{"} in[coord][pnt]\texttt{"} .
+      }
+      \sstsubsection{
+         unc
+      }{
+         An optional pointer to an existing Region which specifies the
+         uncertainties associated with the boundary of the Polygon being created.
+         The uncertainty in any point on the boundary of the Polygon is found by
+         shifting the supplied \texttt{"} uncertainty\texttt{"}  Region so that it is centred at
+         the boundary point being considered. The area covered by the
+         shifted uncertainty Region then represents the uncertainty in the
+         boundary position. The uncertainty is assumed to be the same for
+         all points.
+
+         If supplied, the uncertainty Region must be of a class for which
+         all instances are centro-symetric (e.g. \htmlref{Box}{Box}, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse}, etc.)
+         or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions. A deep
+         copy of the supplied Region will be taken, so subsequent changes to
+         the uncertainty Region using the supplied pointer will have no
+         effect on the created Polygon. Alternatively,
+         a NULL \htmlref{Object}{Object} pointer
+         may be supplied, in which case a default uncertainty is used
+         equivalent to a box 1.0E-6 of the size of the Polygon being created.
+
+         The uncertainty Region has two uses: 1) when the
+         \htmlref{astOverlap}{astOverlap}
+         function compares two Regions for equality the uncertainty
+         Region is used to determine the tolerance on the comparison, and 2)
+         when a Region is mapped into a different coordinate system and
+         subsequently simplified (using
+         \htmlref{astSimplify}{astSimplify}),
+         the uncertainties are used to determine if the transformed boundary
+         can be accurately represented by a specific shape of Region.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Polygon. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astPolygon()
+      }{
+         A pointer to the new Polygon.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astPrism
+}{
+   Create a Prism
+}{
+   \sstdescription{
+      This function creates a new \htmlref{Prism}{Prism} and optionally initialises
+      its attributes.
+
+      A Prism is a \htmlref{Region}{Region} which represents an extrusion of an existing Region
+      into one or more orthogonal dimensions (specified by another Region).
+      If the Region to be extruded has N axes, and the Region defining the
+      extrusion has M axes, then the resulting Prism will have (M$+$N) axes.
+      A point is inside the Prism if the first N axis values correspond to
+      a point inside the Region being extruded, and the remaining M axis
+      values correspond to a point inside the Region defining the extrusion.
+
+      As an example, a cylinder can be represented by extruding an existing
+      \htmlref{Circle}{Circle}, using an \htmlref{Interval}{Interval} to define the extrusion. Ih this case, the
+      Interval would have a single axis and would specify the upper and
+      lower limits of the cylinder along its length.
+   }
+   \sstsynopsis{
+      AstPrism $*$astPrism( AstRegion $*$region1, AstRegion $*$region2,
+                          const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         region1
+      }{
+         Pointer to the Region to be extruded.
+      }
+      \sstsubsection{
+         region2
+      }{
+         Pointer to the Region defining the extent of the extrusion.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Prism. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astPrism()
+      }{
+         A pointer to the new Prism.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Deep copies are taken of the supplied Regions. This means that
+         any subsequent changes made to the component Regions using the
+         supplied pointers will have no effect on the Prism.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astPurgeRows
+}{
+   Remove all empty rows from a table
+}{
+   \sstdescription{
+      This function removes all empty rows from the \htmlref{Table}{Table}, renaming
+      the key associated with each table cell accordingly.
+   }
+   \sstsynopsis{
+      void astPurgeRows( AstTable $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Table.
+      }
+   }
+}
+\sstroutine{
+   astPurgeWCS
+}{
+   Delete all cards in the FitsChan describing WCS information
+}{
+   \sstdescription{
+      This function
+      deletes all cards in a \htmlref{FitsChan}{FitsChan} that relate to any of the recognised
+      WCS encodings. On exit, the current card is the first remaining card
+      in the FitsChan.
+   }
+   \sstsynopsis{
+      void astPurgeWCS( AstFitsChan $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+   }
+}
+\sstroutine{
+   astPutCards
+}{
+   Store a set of FITS header cards in a FitsChan
+}{
+   \sstdescription{
+      This function
+      stores a set of FITS header cards in a \htmlref{FitsChan}{FitsChan}. The cards are
+      supplied concatenated together into a single character string.
+      Any existing cards in the FitsChan are removed before the new cards
+      are added. The FitsChan is \texttt{"} re-wound\texttt{"}  on exit by clearing its \htmlref{Card}{Card}
+      attribute. This means that a subsequent invocation of
+      \htmlref{astRead}{astRead}
+      can be made immediately without the need to re-wind the FitsChan
+      first.
+   }
+   \sstsynopsis{
+      void astPutCards( AstFitsChan $*$this, const char $*$cards )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         cards
+      }{
+         Pointer to a null-terminated character string
+         containing the FITS cards to be stored. Each individual card
+         should occupy 80 characters in this string, and there should be
+         no delimiters, new lines, etc, between adjacent cards. The final
+         card may be less than 80 characters long.
+         This is the format produced by the fits\_hdr2str function in the
+         CFITSIO library.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error will result if the supplied string contains any cards
+         which cannot be interpreted.
+      }
+   }
+}
+\sstroutine{
+   astPutChannelData
+}{
+   Store arbitrary data to be passed to a source or sink function
+}{
+   \sstdescription{
+      This function stores a supplied arbitrary pointer in the \htmlref{Channel}{Channel}.
+      When a source or sink function is invoked by the Channel, the
+      invoked function can use the \htmlref{astChannelData}{astChannelData} macro to retrieve the
+      pointer. This provides a thread-safe alternative to passing file
+      descriptors, etc, via global static variables.
+   }
+   \sstsynopsis{
+      void astPutChannelData( AstChannel $*$this, void $*$data )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Channel.
+      }
+      \sstsubsection{
+         data
+      }{
+         A pointer to be made available to the source and sink functions
+         via the astChannelData macro. May be NULL.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         All Channels have this function.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This routine is not available in the Fortran 77 interface to
+         the AST library.
+      }
+   }
+}
+\sstroutine{
+   astPutColumnData
+}{
+   Store new data values for all rows of a column
+}{
+   \sstdescription{
+      This function
+      copies data values from a supplied buffer into a named column. The
+      first element in the buffer becomes the first element in the first
+      row of the column. If the buffer does not completely fill the
+      column, then any trailing rows are filled with null values.
+   }
+   \sstsynopsis{
+      void astPutColumnData( AstFitsTable $*$this, const char $*$column,
+                             int clen, size\_t size, void $*$coldata )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{FitsTable}{FitsTable}.
+      }
+      \sstsubsection{
+         column
+      }{
+         The character string holding the name of the column. Trailing
+         spaces are ignored.
+      }
+      \sstsubsection{
+         clen
+      }{
+         If the column holds character strings, then this must be set to
+         the length of each fixed length string in the supplied array.
+         This is often determined by the appropriate TFORMn keyword in
+         the binary table header. The supplied value is ignored if the
+         column does not hold character data.
+      }
+      \sstsubsection{
+         size
+      }{
+         The size of the
+         \texttt{"} coldata\texttt{"}
+         array, in bytes. This should be an integer multiple of the
+         number of bytes needed to hold the full vector value stored in a
+         single cell of the column. An error is reported if this is not
+         the case.
+      }
+      \sstsubsection{
+         coldata
+      }{
+         A pointer to an
+         area of memory holding the data to copy into the column. The values
+         should be stored in row order. If the column holds non-scalar values,
+         the elements of each value should be stored in \texttt{"} Fortran\texttt{"}  order. No
+         data type conversion is performed.
+      }
+   }
+}
+\sstroutine{
+   astPutFits
+}{
+   Store a FITS header card in a FitsChan
+}{
+   \sstdescription{
+      This function stores a FITS header card in a \htmlref{FitsChan}{FitsChan}. The card
+      is either inserted before the current card (identified by the
+      \htmlref{Card}{Card} attribute), or over-writes the current card, as required.
+   }
+   \sstsynopsis{
+      void astPutFits( AstFitsChan $*$this, const char card[ 80 ],
+                       int overwrite )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         card
+      }{
+         Pointer to a possibly null-terminated character string
+         containing the FITS card to be stored. No more than 80
+         characters will be used from this string (or fewer if a null
+         occurs earlier).
+      }
+      \sstsubsection{
+         overwrite
+      }{
+         If this value is zero, the new card is inserted in front of
+         the current card in the FitsChan (as identified by the
+         initial value of the Card attribute). If it is non-zero, the
+         new card replaces the current card. In either case, the Card
+         attribute is then incremented by one so that it subsequently
+         identifies the card following the one stored.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the Card attribute initially points at the \texttt{"} end-of-file\texttt{"}
+         (i.e. exceeds the number of cards in the FitsChan), then the new
+         card is appended as the last card in the FitsChan.
+
+         \sstitem
+         An error will result if the supplied string cannot be interpreted
+         as a FITS header card.
+      }
+   }
+}
+\sstroutine{
+   astPutTable
+}{
+   Store a single FitsTable in a FitsChan
+}{
+   \sstdescription{
+      This function
+      allows a representation of a single FITS binary table to be
+      stored in a \htmlref{FitsChan}{FitsChan}. For instance, this may provide the coordinate
+      look-up tables needed subequently when reading FITS-WCS headers
+      for axes described using the \texttt{"} -TAB\texttt{"}  algorithm. Since, in general,
+      the calling application may not know which tables will be needed -
+      if any - prior to calling
+      \htmlref{astRead}{astRead}, the astTablesSource function
+      provides an alternative mechanism in which a caller-supplied
+      function is invoked to store a named table in the FitsChan.
+   }
+   \sstsynopsis{
+      void astPutTable( AstFitsChan $*$this, AstFitsTable $*$table,
+                        const char $*$extnam )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         table
+      }{
+         Pointer to a \htmlref{FitsTable}{FitsTable} to be added to the FitsChan. If a FitsTable
+         with the associated extension name already exists in the FitsChan,
+         it is replaced with the new one. A deep copy of the FitsTable is
+         stored in the FitsChan, so any subsequent changes made to the
+         FitsTable will have no effect on the behaviour of the FitsChan.
+      }
+      \sstsubsection{
+         extnam
+      }{
+         The name of the FITS extension associated with the table.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Tables stored in the FitsChan may be retrieved using
+         \htmlref{astGetTables}{astGetTables}.
+
+         \sstitem
+         The \htmlref{astPutTables}{astPutTables} method can add multiple FitsTables in a single call.
+      }
+   }
+}
+\sstroutine{
+   astPutTableHeader
+}{
+   Store new FITS headers in a FitsTable
+}{
+   \sstdescription{
+      This function
+      stores new FITS headers in the supplied \htmlref{FitsTable}{FitsTable}. Any existing
+      headers are first deleted.
+   }
+   \sstsynopsis{
+      void astPutTableHeader( AstFitsTable $*$this, AstFitsChan $*$header )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsTable.
+      }
+      \sstsubsection{
+         header
+      }{
+         Pointer to a \htmlref{FitsChan}{FitsChan} holding the headers for the FitsTable.
+         A deep copy of the supplied FitsChan is stored in the FitsTable,
+         replacing the current FitsChan in the Fitstable. Keywords that
+         are fixed either by the properties of the \htmlref{Table}{Table}, or by the FITS
+         standard, are removed from the copy (see \texttt{"} Notes:\texttt{"}  below).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The attributes of the supplied FitsChan, together with any source
+         and sink functions associated with the FitsChan, are copied to the
+         FitsTable.
+
+         \sstitem
+         Values for the following keywords are generated automatically by
+         the FitsTable (any values for these keywords in the supplied
+         FitsChan will be ignored): \texttt{"} XTENSION\texttt{"} , \texttt{"} BITPIX\texttt{"} , \texttt{"} NAXIS\texttt{"} , \texttt{"} NAXIS1\texttt{"} ,
+         \texttt{"} NAXIS2\texttt{"} , \texttt{"} PCOUNT\texttt{"} , \texttt{"} GCOUNT\texttt{"} , \texttt{"} TFIELDS\texttt{"} , \texttt{"} TFORM\%d\texttt{"} , \texttt{"} TTYPE\%d\texttt{"} ,
+         \texttt{"} TNULL\%d\texttt{"} , \texttt{"} THEAP\texttt{"} , \texttt{"} TDIM\%d\texttt{"} .
+      }
+   }
+}
+\sstroutine{
+   astPutTables
+}{
+   Store one or more FitsTables in a FitsChan
+}{
+   \sstdescription{
+      This function
+      allows representations of one or more FITS binary tables to be
+      stored in a \htmlref{FitsChan}{FitsChan}. For instance, these may provide the coordinate
+      look-up tables needed subequently when reading FITS-WCS headers
+      for axes described using the \texttt{"} -TAB\texttt{"}  algorithm. Since, in general,
+      the calling application may not know which tables will be needed -
+      if any - prior to calling
+      \htmlref{astRead}{astRead}, the astTablesSource function
+      provides an alternative mechanism in which a caller-supplied
+      function is invoked to store a named table in the FitsChan.
+   }
+   \sstsynopsis{
+      void astPutTables( AstFitsChan $*$this, AstKeyMap $*$tables )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         tables
+      }{
+         Pointer to a \htmlref{KeyMap}{KeyMap} holding the tables that are to be added
+         to the FitsChan. Each entry should hold a scalar value which is a
+         pointer to a \htmlref{FitsTable}{FitsTable} to be added to the FitsChan. Any unusable
+         entries are ignored. The key associated with each entry should be
+         the name of the FITS binary extension from which the table was
+         read. If a FitsTable with the associated key already exists in the
+         FitsChan, it is replaced with the new one. A deep copy of each
+         usable FitsTable is stored in the FitsChan, so any subsequent
+         changes made to the FitsTables will have no effect on the
+         behaviour of the FitsChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Tables stored in the FitsChan may be retrieved using
+         \htmlref{astGetTables}{astGetTables}.
+
+         \sstitem
+         The tables in the supplied KeyMap are added to any tables already
+         in the FitsChan.
+
+         \sstitem
+         The \htmlref{astPutTable}{astPutTable}
+         method provides a simpler means of adding a single table to a FitsChan.
+      }
+   }
+}
+\sstroutine{
+   astQuadApprox
+}{
+   Obtain a quadratic approximation to a 2D Mapping
+}{
+   \sstdescription{
+      This function returns the co-efficients of a quadratic fit to the
+      supplied \htmlref{Mapping}{Mapping} over the input area specified by
+      \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"} .
+      The Mapping must have 2 inputs, but may have any number of outputs.
+      The i\texttt{'} th Mapping output is modelled as a quadratic function of the
+      2 inputs (x,y):
+
+      output\_i = a\_i\_0 $+$ a\_i\_1$*$x $+$ a\_i\_2$*$y $+$ a\_i\_3$*$x$*$y $+$ a\_i\_4$*$x$*$x $+$
+                 a\_i\_5$*$y$*$y
+
+      The \texttt{"} fit\texttt{"}
+      array is returned holding the values of the co-efficients a\_0\_0,
+      a\_0\_1, etc.
+   }
+   \sstsynopsis{
+      int QuadApprox( AstMapping $*$this,  const double lbnd[2],
+                      const double ubnd[2], int nx, int ny, double $*$fit,
+                      double $*$rms )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an array of doubles
+         containing the lower bounds of a box defined within the input
+         coordinate system of the Mapping. The number of elements in this
+         array should equal the value of the Mapping\texttt{'} s \htmlref{Nin}{Nin} attribute. This
+         box should specify the region over which the fit is to be
+         performed.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an array of doubles
+         containing the upper bounds of the box specifying the region over
+         which the fit is to be performed.
+      }
+      \sstsubsection{
+         nx
+      }{
+         The number of points to place along the first Mapping input. The
+         first point is at
+         \texttt{"} lbnd[0]\texttt{"}  and the last is at \texttt{"} ubnd[0]\texttt{"} .
+         If a value less than three is supplied a value of three will be used.
+      }
+      \sstsubsection{
+         ny
+      }{
+         The number of points to place along the second Mapping input. The
+         first point is at
+         \texttt{"} lbnd[1]\texttt{"}  and the last is at \texttt{"} ubnd[1]\texttt{"} .
+         If a value less than three is supplied a value of three will be used.
+      }
+      \sstsubsection{
+         fit
+      }{
+         Pointer to an array of doubles
+         in which to return the co-efficients of the quadratic
+         approximation to the specified transformation. This array should
+         have at least \texttt{"} 6$*$\htmlref{Nout}{Nout}\texttt{"} , elements. The first 6 elements hold the
+         fit to the first Mapping output. The next 6 elements hold the
+         fit to the second Mapping output, etc. So if the Mapping has 2
+         inputs and 2 outputs the quadratic approximation to the forward
+         transformation is:
+
+            X\_out = fit[0] $+$ fit[1]$*$X\_in $+$ fit[2]$*$Y\_in $+$ fit[3]$*$X\_in$*$Y\_in $+$
+                    fit[4]$*$X\_in$*$X\_in $+$ fit[5]$*$Y\_in$*$Y\_in
+            Y\_out = fit[6] $+$ fit[7]$*$X\_in $+$ fit[8]$*$Y\_in $+$ fit[9]$*$X\_in$*$Y\_in $+$
+                    fit[10]$*$X\_in$*$X\_in $+$ fit[11]$*$Y\_in$*$Y\_in
+      }
+      \sstsubsection{
+         rms
+      }{
+         Pointer to a double in which to return the
+         RMS residual between the fit and the Mapping, summed over all
+         Mapping outputs.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astQuadApprox()
+      }{
+         If a quadratic approximation was created,
+         a non-zero value is returned. Otherwise zero is returned
+         and the fit co-efficients are set to AST\_\_BAD.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function fits the Mapping\texttt{'} s forward transformation. To fit
+         the inverse transformation, the Mapping should be inverted using
+         \htmlref{astInvert}{astInvert}
+         before invoking this function.
+
+         \sstitem
+         A value of zero
+         will be returned if this function is invoked
+         with the global error status set, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   astRate
+}{
+   Calculate the rate of change of a Mapping output
+}{
+   \sstdescription{
+      This function
+      evaluates the rate of change of a specified output of the supplied
+      \htmlref{Mapping}{Mapping} with respect to a specified input, at a specified input
+      position.
+
+      The result is estimated by interpolating the function using a
+      fourth order polynomial in the neighbourhood of the specified
+      position. The size of the neighbourhood used is chosen to minimise
+      the RMS residual per unit length between the interpolating
+      polynomial and the supplied Mapping function. This method produces
+      good accuracy but can involve evaluating the Mapping 100 or more
+      times.
+   }
+   \sstsynopsis{
+      double astRate( AstMapping $*$this, double $*$at, int ax1, int ax2 )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         at
+      }{
+         The address of an
+         array holding the axis values at the position at which the rate
+         of change is to be evaluated. The number of elements in this
+         array should equal the number of inputs to the Mapping.
+      }
+      \sstsubsection{
+         ax1
+      }{
+         The index of the Mapping output for which the rate of change is to
+         be found (output numbering starts at 1 for the first output).
+      }
+      \sstsubsection{
+         ax2
+      }{
+         The index of the Mapping input which is to be varied in order to
+         find the rate of change (input numbering starts at 1 for the first
+         input).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astRate()
+      }{
+         The rate of change of Mapping output \texttt{"} ax1\texttt{"}  with respect to input
+         \texttt{"} ax2\texttt{"} , evaluated at \texttt{"} at\texttt{"} , or AST\_\_BAD if the value cannot be
+         calculated.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of AST\_\_BAD will be returned if this function is invoked
+         with the global error status set, or if it should fail for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   astRateMap
+}{
+   Create a RateMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{RateMap}{RateMap} and optionally initialises
+      its attributes.
+
+      A RateMap is a \htmlref{Mapping}{Mapping} which represents a single element of the
+      Jacobian matrix of another Mapping. The Mapping for which the
+      Jacobian is required is specified when the new RateMap is created,
+      and is referred to as the \texttt{"} encapsulated Mapping\texttt{"}  below.
+
+      The number of inputs to a RateMap is the same as the number of inputs
+      to its encapsulated Mapping. The number of outputs from a RateMap
+      is always one. This one output equals the rate of change of a
+      specified output of the encapsulated Mapping with respect to a
+      specified input of the encapsulated Mapping (the input and output
+      to use are specified when the RateMap is created).
+
+      A RateMap which has not been inverted does not define an inverse
+      transformation. If a RateMap has been inverted then it will define
+      an inverse transformation but not a forward transformation.
+   }
+   \sstsynopsis{
+      AstRateMap $*$astRateMap( AstMapping $*$map, int ax1, int ax2,
+                              const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         map
+      }{
+         Pointer to the encapsulated Mapping.
+      }
+      \sstsubsection{
+         ax1
+      }{
+         Index of the output from the encapsulated Mapping for which the
+         rate of change is required. This corresponds to the delta
+         quantity forming the numerator of the required element of the
+         Jacobian matrix. The first axis has index 1.
+      }
+      \sstsubsection{
+         ax2
+      }{
+         Index of the input to the encapsulated Mapping which is to be
+         varied. This corresponds to the delta quantity forming the
+         denominator of the required element of the Jacobian matrix.
+         The first axis has index 1.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new RateMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astRateMap()
+      }{
+         A pointer to the new RateMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The forward transformation of the encapsulated Mapping must be
+         defined.
+
+         \sstitem
+         Note that the component Mappings supplied are not copied by
+         astRateMap (the new RateMap simply retains a reference to
+         them). They may continue to be used for other purposes, but
+         should not be deleted. If a RateMap containing a copy of its
+         component Mappings is required, then a copy of the RateMap should
+         be made using \htmlref{astCopy}{astCopy}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astRead
+}{
+   Read an Object from a Channel
+}{
+   \sstdescription{
+      This function reads the next \htmlref{Object}{Object} from a \htmlref{Channel}{Channel} and returns a
+      pointer to the new Object.
+   }
+   \sstsynopsis{
+      AstObject $*$astRead( AstChannel $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Channel.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         All successful use of astRead on a FitsChan is destructive, so that
+         FITS header cards are consumed in the process of reading an Object,
+         and are removed from the FitsChan (this deletion can be prevented
+         for specific cards by calling the FitsChan
+         \htmlref{astRetainFits}{astRetainFits} function).
+         An unsuccessful call of
+         astRead
+         (for instance, caused by the FitsChan not containing the necessary
+         FITS headers cards needed to create an Object) results in the
+         contents of the FitsChan being left unchanged.
+      }
+      \sstsubsection{
+         \htmlref{StcsChan}{StcsChan}
+      }{
+         The AST Object returned by a successful use of
+         astRead
+         on an StcsChan, will be either a \htmlref{Region}{Region} or a \htmlref{KeyMap}{KeyMap}, depending
+         on the values of the \htmlref{StcsArea}{StcsArea}, \htmlref{StcsCoords}{StcsCoords} and \htmlref{StcsProps}{StcsProps}
+         attributes. See the documentation for these attributes for further
+         information.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astRead()
+      }{
+         A pointer to the new Object. The class to which this will
+         belong is determined by the input data, so is not known in
+         advance.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned, without
+         error, if the Channel contains no further Objects to be read.
+
+         \sstitem
+         A null Object pointer will also be returned if this function
+         is invoked with the AST error status set, or if it should fail
+         for any reason.
+      }
+   }
+}
+\sstroutine{
+   astReadFits
+}{
+   Read cards into a FitsChan from the source function
+}{
+   \sstdescription{
+      This function
+      reads cards from the source function that was specified when the
+      \htmlref{FitsChan}{FitsChan} was created, and stores them in the FitsChan. This
+      normally happens once-only, when the FitsChan is accessed for the
+      first time.
+      This function
+      provides a means of forcing a re-read of the external source, and
+      may be useful if (say) new cards have been deposited into the
+      external source. Any newcards read from the source are appended to
+      the end of the current contents of the FitsChan.
+   }
+   \sstsynopsis{
+      void astReadFits( AstFitsChan $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function returns without action if no source function was
+         specified when the FitsChan was created.
+
+         \sstitem
+         The \htmlref{SourceFile}{SourceFile} attribute is ignored by this
+         function.
+         New cards are read from the source file whenever a new value is
+         assigned to the SourceFile attribute.
+      }
+   }
+}
+\sstroutine{
+   astRebin$<$X$>$
+}{
+   Rebin a region of a data grid
+}{
+   \sstdescription{
+      This is a set of functions for rebinning gridded data (e.g. an
+      image) under the control of a geometrical transformation, which
+      is specified by a \htmlref{Mapping}{Mapping}.  The functions operate on a pair of
+      data grids (input and output), each of which may have any number
+      of dimensions. Rebinning may be restricted to a specified
+      region of the input grid. An associated grid of error estimates
+      associated with the input data may also be supplied (in the form
+      of variance values), so as to produce error estimates for the
+      rebined output data. Propagation of missing data (bad pixels)
+      is supported.
+
+      Note, if you will be rebining a sequence of input arrays and then
+      co-adding them into a single array, the alternative
+      \htmlref{astRebinSeq$<$X$>$}{astRebinSeq$<$X$>$} functions
+      will in general be more efficient.
+
+      You should use a rebinning function which matches the numerical
+      type of the data you are processing by replacing $<$X$>$ in
+      the generic function name astRebin$<$X$>$ by an appropriate 1- or
+      2-character type code. For example, if you are rebinning data
+      with type \texttt{"} float\texttt{"} , you should use the function astRebinF (see
+      the \texttt{"} Data Type Codes\texttt{"}  section below for the codes appropriate to
+      other numerical types).
+
+      Rebinning of the grid of input data is performed by transforming
+      the coordinates of the centre of each input grid element (or pixel)
+      into the coordinate system of the output grid. The input pixel
+      value is then divided up and assigned to the output pixels in the
+      neighbourhood of the central output coordinates. A choice of
+      schemes are provided for determining how each input pixel value is
+      divided up between the output pixels. In general, each output pixel
+      may be assigned values from more than one input pixel. All
+      contributions to a given output pixel are summed to produce the
+      final output pixel value. Output pixels can be set to the supplied
+      bad value if they receive contributions from an insufficient number
+      of input pixels. This is controlled by the
+      \texttt{"} wlim\texttt{"}  parameter.
+
+      Input pixel coordinates are transformed into the coordinate
+      system of the output grid using the forward transformation of the
+      Mapping which is supplied. This means that geometrical features
+      in the input data are subjected to the Mapping\texttt{'} s forward
+      transformation as they are transferred from the input to the
+      output grid.
+
+      In practice, transforming the coordinates of every pixel of a
+      large data grid can be time-consuming, especially if the Mapping
+      involves complicated functions, such as sky projections. To
+      improve performance, it is therefore possible to approximate
+      non-linear Mappings by a set of linear transformations which are
+      applied piece-wise to separate sub-regions of the data. This
+      approximation process is applied automatically by an adaptive
+      algorithm, under control of an accuracy criterion which
+      expresses the maximum tolerable geometrical distortion which may
+      be introduced, as a fraction of a pixel.
+
+      This algorithm first attempts to approximate the Mapping with a
+      linear transformation applied over the whole region of the
+      input grid which is being used. If this proves to be
+      insufficiently accurate, the input region is sub-divided into
+      two along its largest dimension and the process is repeated
+      within each of the resulting sub-regions. This process of
+      sub-division continues until a sufficiently good linear
+      approximation is found, or the region to which it is being
+      applied becomes too small (in which case the original Mapping is
+      used directly).
+   }
+   \sstsynopsis{
+      void astRebin$<$X$>$( AstMapping $*$this, double wlim, int ndim\_in,
+                       const int lbnd\_in[], const int ubnd\_in[],
+                       const $<$Xtype$>$ in[], const $<$Xtype$>$ in\_var[],
+                       int spread, const double params[], int flags,
+                       double tol, int maxpix,
+                       $<$Xtype$>$ badval, int ndim\_out,
+                       const int lbnd\_out[], const int ubnd\_out[],
+                       const int lbnd[], const int ubnd[],
+                       $<$Xtype$>$ out[], $<$Xtype$>$ out\_var[] );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to a Mapping, whose forward transformation will be
+         used to transform the coordinates of pixels in the input
+         grid into the coordinate system of the output grid.
+
+         The number of input coordinates used by this Mapping (as
+         given by its \htmlref{Nin}{Nin} attribute) should match the number of input
+         grid dimensions given by the value of \texttt{"} ndim\_in\texttt{"}
+         below. Similarly, the number of output coordinates (\htmlref{Nout}{Nout}
+         attribute) should match the number of output grid dimensions
+         given by \texttt{"} ndim\_out\texttt{"} .
+      }
+      \sstsubsection{
+         wlim
+      }{
+         Gives the required number of input pixel values which must contribute
+         to an output pixel in order for the output pixel value to be
+         considered valid. If the sum of the input pixel weights contributing
+         to an output pixel is less than the supplied
+         \texttt{"} wlim\texttt{"}
+         value, then the output pixel value is returned set to the
+         supplied bad value.
+      }
+      \sstsubsection{
+         ndim\_in
+      }{
+         The number of dimensions in the input grid. This should be at
+         least one.
+      }
+      \sstsubsection{
+         lbnd\_in
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         ubnd\_in
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that \texttt{"} lbnd\_in\texttt{"}  and \texttt{"} ubnd\_in\texttt{"}  together define the shape
+         and size of the input grid, its extent along a particular
+         (j\texttt{'} th) dimension being ubnd\_in[j]-lbnd\_in[j]$+$1 (assuming the
+         index \texttt{"} j\texttt{"}  to be zero-based). They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre.
+      }
+      \sstsubsection{
+         in
+      }{
+         Pointer to an array, with one element for each pixel in the
+         input grid, containing the input data to be rebined.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using astRebinF, the type of each array element
+         should be \texttt{"} float\texttt{"} ).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. Fortran array indexing is used).
+      }
+      \sstsubsection{
+         in\_var
+      }{
+         An optional pointer to a second array with the same size and
+         type as the \texttt{"} in\texttt{"}  array. If given, this should contain a set
+         of non-negative values which represent estimates of the
+         statistical variance associated with each element of the \texttt{"} in\texttt{"}
+         array. If this array is supplied (together with the
+         corresponding \texttt{"} out\_var\texttt{"}  array), then estimates of the
+         variance of the rebined output data will be calculated.
+
+         If no input variance estimates are being provided, a NULL
+         pointer should be given.
+      }
+      \sstsubsection{
+         spread
+      }{
+         This parameter specifies the scheme to be used for dividing
+         each input data value up amongst the corresponding output pixels.
+         It may be used to select
+         from a set of pre-defined schemes by supplying one of the
+         values described in the \texttt{"} Pixel Spreading Schemes\texttt{"}
+         section below.  If a value of zero is supplied, then the
+         default linear spreading scheme is used (equivalent to
+         supplying the value AST\_\_LINEAR).
+      }
+      \sstsubsection{
+         params
+      }{
+         An optional pointer to an array of double which should contain
+         any additional parameter values required by the pixel
+         spreading scheme. If such parameters are required, this
+         will be noted in the \texttt{"} Pixel Spreading Schemes\texttt{"}
+         section below.
+
+         If no additional parameters are required, this array is not
+         used and a NULL pointer may be given.
+      }
+      \sstsubsection{
+         flags
+      }{
+         The bitwise OR of a set of flag values which may be used to
+         provide additional control over the rebinning operation. See
+         the \texttt{"} Control Flags\texttt{"}  section below for a description of the
+         options available.  If no flag values are to be set, a value
+         of zero should be given.
+      }
+      \sstsubsection{
+         tol
+      }{
+         The maximum tolerable geometrical distortion which may be
+         introduced as a result of approximating non-linear Mappings
+         by a set of piece-wise linear transformations. This should be
+         expressed as a displacement in pixels in the output grid\texttt{'} s
+         coordinate system.
+
+         If piece-wise linear approximation is not required, a value
+         of zero may be given. This will ensure that the Mapping is
+         used without any approximation, but may increase execution
+         time.
+
+         If the value is too high, discontinuities between the linear
+         approximations used in adjacent panel will be higher, and may
+         cause the edges of the panel to be visible when viewing the output
+         image at high contrast. If this is a problem, reduce the
+         tolerance value used.
+      }
+      \sstsubsection{
+         maxpix
+      }{
+         A value which specifies an initial scale size (in pixels) for
+         the adaptive algorithm which approximates non-linear Mappings
+         with piece-wise linear transformations. Normally, this should
+         be a large value (larger than any dimension of the region of
+         the input grid being used). In this case, a first attempt to
+         approximate the Mapping by a linear transformation will be
+         made over the entire input region.
+
+         If a smaller value is used, the input region will first be
+         divided into sub-regions whose size does not exceed \texttt{"} maxpix\texttt{"}
+         pixels in any dimension. Only at this point will attempts at
+         approximation commence.
+
+         This value may occasionally be useful in preventing false
+         convergence of the adaptive algorithm in cases where the
+         Mapping appears approximately linear on large scales, but has
+         irregularities (e.g. holes) on smaller scales. A value of,
+         say, 50 to 100 pixels can also be employed as a safeguard in
+         general-purpose software, since the effect on performance is
+         minimal.
+
+         If too small a value is given, it will have the effect of
+         inhibiting linear approximation altogether (equivalent to
+         setting \texttt{"} tol\texttt{"}  to zero). Although this may degrade
+         performance, accurate results will still be obtained.
+      }
+      \sstsubsection{
+         badval
+      }{
+         This argument should have the same type as the elements of
+         the \texttt{"} in\texttt{"}  array. It specifies the value used to flag missing
+         data (bad pixels) in the input and output arrays.
+
+         If the AST\_\_USEBAD flag is set via the \texttt{"} flags\texttt{"}  parameter,
+         then this value is used to test for bad pixels in the \texttt{"} in\texttt{"}
+         (and \texttt{"} in\_var\texttt{"} ) array(s).
+
+         In all cases, this value is also used to flag any output
+         elements in the \texttt{"} out\texttt{"}  (and \texttt{"} out\_var\texttt{"} ) array(s) for which
+         rebined values could not be obtained (see the \texttt{"} Propagation
+         of Missing Data\texttt{"}  section below for details of the
+         circumstances under which this may occur).
+      }
+      \sstsubsection{
+         ndim\_out
+      }{
+         The number of dimensions in the output grid. This should be
+         at least one. It need not necessarily be equal to the number
+         of dimensions in the input grid.
+      }
+      \sstsubsection{
+         lbnd\_out
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_out\texttt{"}  elements,
+         containing the coordinates of the centre of the first pixel
+         in the output grid along each dimension.
+      }
+      \sstsubsection{
+         ubnd\_out
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_out\texttt{"}  elements,
+         containing the coordinates of the centre of the last pixel in
+         the output grid along each dimension.
+
+         Note that \texttt{"} lbnd\_out\texttt{"}  and \texttt{"} ubnd\_out\texttt{"}  together define the
+         shape, size and coordinate system of the output grid in the
+         same way as \texttt{"} lbnd\_in\texttt{"}  and \texttt{"} ubnd\_in\texttt{"}  define the shape, size
+         and coordinate system of the input grid.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the first pixel in the region
+         of the input grid which is to be included in the rebined output
+         array.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the last pixel in the region of
+         the input grid which is to be included in the rebined output
+         array.
+
+         Note that \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"}  together define the shape and
+         position of a (hyper-)rectangular region of the input grid
+         which is to be included in the rebined output array. This region
+         should lie wholly within the extent of the input grid (as
+         defined by the \texttt{"} lbnd\_in\texttt{"}  and \texttt{"} ubnd\_in\texttt{"}  arrays). Regions of
+         the input grid lying outside this region will not be used.
+      }
+      \sstsubsection{
+         out
+      }{
+         Pointer to an array, with one element for each pixel in the
+         output grid, in which the rebined data values will be
+         returned. The numerical type of this array should match that
+         of the \texttt{"} in\texttt{"}  array, and the data storage order should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. Fortran array indexing is used).
+      }
+      \sstsubsection{
+         out\_var
+      }{
+         An optional pointer to an array with the same type and size
+         as the \texttt{"} out\texttt{"}  array. If given, this array will be used to
+         return variance estimates for the rebined data values. This
+         array will only be used if the \texttt{"} in\_var\texttt{"}  array has also been
+         supplied.
+
+         The output variance values will be calculated on the
+         assumption that errors on the input data values are
+         statistically independent and that their variance estimates
+         may simply be summed (with appropriate weighting factors)
+         when several input pixels contribute to an output data
+         value. If this assumption is not valid, then the output error
+         estimates may be biased. In addition, note that the
+         statistical errors on neighbouring output data values (as
+         well as the estimates of those errors) may often be
+         correlated, even if the above assumption about the input data
+         is correct, because of the pixel spreading schemes
+         employed.
+
+         If no output variance estimates are required, a NULL pointer
+         should be given.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate rebinning function, you should
+      replace $<$X$>$ in the generic function name astRebin$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         I: int
+
+         \sstitem
+         B: byte (signed char)
+
+         \sstitem
+         UB: unsigned byte (unsigned char)
+
+      }
+      For example, astRebinD would be used to process \texttt{"} double\texttt{"}
+      data, while astRebinI would be used to process \texttt{"} int\texttt{"}
+      data, etc.
+
+      Note that, unlike
+      \htmlref{astResample$<$X$>$}{astResample$<$X$>$}, the astRebin$<$X$>$
+      set of functions does not yet support unsigned integer data types
+      or integers of different sizes.
+   }
+   \sstdiytopic{
+      Pixel Spreading Schemes
+   }{
+      The pixel spreading scheme specifies the Point Spread Function (PSF)
+      applied to each input pixel value as it is copied into the output
+      array. It can be thought of as the inverse of the sub-pixel
+      interpolation schemes used by the
+      astResample$<$X$>$
+      group of functions. That is, in a sub-pixel interpolation scheme the
+      kernel specifies the weight to assign to each input pixel when
+      forming the weighted mean of the input pixels, whereas the kernel in a
+      pixel spreading scheme specifies the fraction of the input data value
+      which is to be assigned to each output pixel. As for interpolation, the
+      choice of suitable pixel spreading scheme involves stricking a balance
+      between schemes which tend to degrade sharp features in the data by
+      smoothing them, and those which attempt to preserve sharp features but
+      which often tend to introduce unwanted artifacts. See the
+      astResample$<$X$>$
+      documentation for further discussion.
+
+      The binning algorithm used has the ability to introduce artifacts
+      not seen when using a resampling algorithm. Particularly, when
+      viewing the output image at high contrast, systems of curves lines
+      covering the entire image may be visible. These are caused by a
+      beating effect between the input pixel positions and the output pixels
+      position, and their nature and strength depend critically upon the
+      nature of the Mapping and the spreading function being used. In
+      general, the nearest neighbour spreading function demonstrates this
+      effect more clearly than the other functions, and for this reason
+      should be used with caution.
+
+      The following values (defined in the
+      \texttt{"} ast.h\texttt{"}  header file)
+      may be assigned to the
+      \texttt{"} spread\texttt{"}
+      parameter. See the
+      astResample$<$X$>$
+      documentation for details of these schemes including the use of the
+      \texttt{"} fspread\texttt{"}  and \texttt{"} params\texttt{"}  parameters:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_NEAREST
+
+         \sstitem
+         AST\_\_LINEAR
+
+         \sstitem
+         AST\_\_SINC
+
+         \sstitem
+         AST\_\_SINCSINC
+
+         \sstitem
+         AST\_\_SINCCOS
+
+         \sstitem
+         AST\_\_SINCGAUSS
+
+         \sstitem
+         AST\_\_SOMBCOS
+
+      }
+      In addition, the following schemes can be used with
+      astRebin$<$X$>$ but not with astResample$<$X$>$:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_GAUSS: This scheme uses a kernel of the form exp(-k$*$x$*$x), with k
+         a positive constant determined by the full-width at half-maximum (FWHM).
+         The FWHM should be supplied in units of output pixels by means of the
+         \texttt{"} params[1]\texttt{"}
+         value and should be at least 0.1. The
+         \texttt{"} params[0]\texttt{"}
+         value should be used to specify at what point the Gaussian is truncated
+         to zero. This should be given as a number of output pixels on either
+         side of the central output point in each dimension (the nearest integer
+         value is used).
+      }
+   }
+   \sstdiytopic{
+      Control Flags
+   }{
+      The following flags are defined in the \texttt{"} ast.h\texttt{"}  header file and
+      may be used to provide additional control over the rebinning
+      process. Having selected a set of flags, you should supply the
+      bitwise OR of their values via the \texttt{"} flags\texttt{"}  parameter:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_USEBAD: Indicates that there may be bad pixels in the
+         input array(s) which must be recognised by comparing with the
+         value given for \texttt{"} badval\texttt{"}  and propagated to the output array(s).
+         If this flag is not set, all input values are treated literally
+         and the \texttt{"} badval\texttt{"}  value is only used for flagging output array
+         values.
+      }
+   }
+   \sstdiytopic{
+      Propagation of Missing Data
+   }{
+      Instances of missing data (bad pixels) in the output grid are
+      identified by occurrences of the \texttt{"} badval\texttt{"}  value in the \texttt{"} out\texttt{"}
+      array. These are produced if the sum of the weights of the
+      contributing input pixels is less than
+      \texttt{"} wlim\texttt{"} .
+
+      An input pixel is considered bad (and is consequently ignored) if
+      its
+      data value is equal to \texttt{"} badval\texttt{"}  and the AST\_\_USEBAD flag is
+      set via the \texttt{"} flags\texttt{"}  parameter.
+
+      In addition, associated output variance estimates (if
+      calculated) may be declared bad and flagged with the \texttt{"} badval\texttt{"}
+      value in the \texttt{"} out\_var\texttt{"}  array for similar reasons.
+   }
+}
+\sstroutine{
+   astRebinSeq$<$X$>$
+}{
+   Rebin a region of a sequence of data grids
+}{
+   \sstdescription{
+      This set of
+      functions is identical to \htmlref{astRebin$<$X$>$}{astRebin$<$X$>$}
+      except that the rebinned input data is added into the supplied
+      output arrays, rather than simply over-writing the contents of the
+      output arrays. Thus, by calling this
+      function
+      repeatedly, a sequence of input arrays can be rebinned and accumulated
+      into a single output array, effectively forming a mosaic of the
+      input data arrays.
+
+      In addition, the weights associated with each output pixel are
+      returned. The weight of an output pixel indicates the number of input
+      pixels which have been accumulated in that output pixel. If the entire
+      value of an input pixel is assigned to a single output pixel, then the
+      weight of that output pixel is incremented by one. If some fraction of
+      the value of an input pixel is assigned to an output pixel, then the
+      weight of that output pixel is incremented by the fraction used.
+
+      The start of a new sequence is indicated by specifying the
+      AST\_\_REBININIT flag via the
+      \texttt{"} flags\texttt{"}  parameter.
+      This causes the supplied arrays to be filled with zeros before the
+      rebinned input data is added into them. Subsequenct invocations
+      within the same sequence should omit the AST\_\_REBININIT flag.
+
+      The last call in a sequence is indicated by specifying the
+      AST\_\_REBINEND flag. Depending on which flags are supplied, this may
+      cause the output data and variance arrays to be normalised before
+      being returned. This normalisation consists of dividing the data
+      array by the weights array, and can eliminate artifacts which may be
+      introduced into the rebinned data as a consequence of aliasing
+      between the input and output grids. This results in each output
+      pixel value being the weighted mean of the input pixel values that
+      fall in the neighbourhood of the output pixel (rather like
+      \htmlref{astResample$<$X$>$}{astResample$<$X$>$}).
+      Optionally, these normalised
+      values can then be multiplied by a scaling factor to ensure that the
+      total data sum in any small area is unchanged. This scaling factor
+      is equivalent to the number of input pixel values that fall into each
+      output pixel. In addition to
+      normalisation of the output data values, any output variances are
+      also appropriately normalised, and any output data values with
+      weight less than
+      \texttt{"} wlim\texttt{"}  are set to \texttt{"} badval\texttt{"} .
+
+      Output variances can be generated in two ways; by rebinning the supplied
+      input variances with appropriate weights, or by finding the spread of
+      input data values contributing to each output pixel (see the AST\_\_GENVAR
+      and AST\_\_USEVAR flags).
+   }
+   \sstsynopsis{
+      void astRebinSeq$<$X$>$( AstMapping $*$this, double wlim, int ndim\_in,
+                           const int lbnd\_in[], const int ubnd\_in[],
+                           const $<$Xtype$>$ in[], const $<$Xtype$>$ in\_var[],
+                           int spread, const double params[], int flags,
+                           double tol, int maxpix, $<$Xtype$>$ badval,
+                           int ndim\_out, const int lbnd\_out[],
+                           const int ubnd\_out[], const int lbnd[],
+                           const int ubnd[], $<$Xtype$>$ out[], $<$Xtype$>$ out\_var[],
+                           double weights[], int64\_t $*$nused );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to a \htmlref{Mapping}{Mapping}, whose forward transformation will be
+         used to transform the coordinates of pixels in the input
+         grid into the coordinate system of the output grid.
+
+         The number of input coordinates used by this Mapping (as
+         given by its \htmlref{Nin}{Nin} attribute) should match the number of input
+         grid dimensions given by the value of \texttt{"} ndim\_in\texttt{"}
+         below. Similarly, the number of output coordinates (\htmlref{Nout}{Nout}
+         attribute) should match the number of output grid dimensions
+         given by \texttt{"} ndim\_out\texttt{"} .
+         If \texttt{"} in\texttt{"}  is NULL, the Mapping will not be used, but a valid
+         Mapping must still be supplied.
+      }
+      \sstsubsection{
+         wlim
+      }{
+         This value is only used if the AST\_\_REBINEND flag is specified
+         via the
+         \texttt{"} flags\texttt{"}  parameter.
+         It gives the required number of input pixel values which must
+         contribute to an output pixel (i.e. the output pixel weight) in
+         order for the output pixel value to be considered valid. If the sum
+         of the input pixel weights contributing to an output pixel is less
+         than the supplied
+         \texttt{"} wlim\texttt{"}
+         value, then the output pixel value is returned set to the
+         supplied bad value. If the supplied value is less than 1.0E-10
+         then 1.0E-10 is used instead.
+      }
+      \sstsubsection{
+         ndim\_in
+      }{
+         The number of dimensions in the input grid. This should be at
+         least one.
+         Not used if \texttt{"} in\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         lbnd\_in
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+         Not used if \texttt{"} in\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         ubnd\_in
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that \texttt{"} lbnd\_in\texttt{"}  and \texttt{"} ubnd\_in\texttt{"}  together define the shape
+         and size of the input grid, its extent along a particular
+         (j\texttt{'} th) dimension being ubnd\_in[j]-lbnd\_in[j]$+$1 (assuming the
+         index \texttt{"} j\texttt{"}  to be zero-based). They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre.
+         Not used if \texttt{"} in\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         in
+      }{
+         Pointer to an array, with one element for each pixel in the
+         input grid, containing the input data to be rebined.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using astRebinSeqF, the type of each array element
+         should be \texttt{"} float\texttt{"} ).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. Fortran array indexing is used).
+         If a NULL pointer is supplied for \texttt{"} in\texttt{"} , then no data is added to
+         the output arrays, but any initialisation or normalisation
+         requested by \texttt{"} flags\texttt{"}  is still performed.
+      }
+      \sstsubsection{
+         in\_var
+      }{
+         An optional
+         pointer to a
+         second array with the same size and type as the
+         \texttt{"} in\texttt{"}
+         array. If given, this should contain a set of non-negative values
+         which represent estimates of the statistical variance associated
+         with each element of the
+         \texttt{"} in\texttt{"}
+         array.
+         If neither the AST\_\_USEVAR nor the AST\_\_VARWGT flag is set, no
+         input variance estimates are required and this
+         pointer
+         will not be used.
+         A NULL pointer
+         may then be supplied.
+      }
+      \sstsubsection{
+         spread
+      }{
+         This parameter specifies the scheme to be used for dividing
+         each input data value up amongst the corresponding output pixels.
+         It may be used to select
+         from a set of pre-defined schemes by supplying one of the
+         values described in the \texttt{"} Pixel Spreading Schemes\texttt{"}
+         section in the description of the
+         astRebin$<$X$>$ functions.
+         If a value of zero is supplied, then the default linear spreading
+         scheme is used (equivalent to supplying the value AST\_\_LINEAR).
+         Not used if \texttt{"} in\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         params
+      }{
+         An optional pointer to an array of double which should contain
+         any additional parameter values required by the pixel
+         spreading scheme. If such parameters are required, this
+         will be noted in the \texttt{"} Pixel Spreading Schemes\texttt{"}  section in the
+         description of the
+         astRebin$<$X$>$ functions.
+
+         If no additional parameters are required, this array is not
+         used and a NULL pointer may be given.
+         Not used if \texttt{"} in\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         flags
+      }{
+         The bitwise OR of a set of flag values which may be used to
+         provide additional control over the rebinning operation. See
+         the \texttt{"} Control Flags\texttt{"}  section below for a description of the
+         options available.  If no flag values are to be set, a value
+         of zero should be given.
+      }
+      \sstsubsection{
+         tol
+      }{
+         The maximum tolerable geometrical distortion which may be
+         introduced as a result of approximating non-linear Mappings
+         by a set of piece-wise linear transformations. This should be
+         expressed as a displacement in pixels in the output grid\texttt{'} s
+         coordinate system.
+
+         If piece-wise linear approximation is not required, a value
+         of zero may be given. This will ensure that the Mapping is
+         used without any approximation, but may increase execution
+         time.
+
+         If the value is too high, discontinuities between the linear
+         approximations used in adjacent panel will be higher, and may
+         cause the edges of the panel to be visible when viewing the output
+         image at high contrast. If this is a problem, reduce the
+         tolerance value used.
+         Not used if \texttt{"} in\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         maxpix
+      }{
+         A value which specifies an initial scale size (in pixels) for
+         the adaptive algorithm which approximates non-linear Mappings
+         with piece-wise linear transformations. Normally, this should
+         be a large value (larger than any dimension of the region of
+         the input grid being used). In this case, a first attempt to
+         approximate the Mapping by a linear transformation will be
+         made over the entire input region.
+
+         If a smaller value is used, the input region will first be
+         divided into sub-regions whose size does not exceed \texttt{"} maxpix\texttt{"}
+         pixels in any dimension. Only at this point will attempts at
+         approximation commence.
+
+         This value may occasionally be useful in preventing false
+         convergence of the adaptive algorithm in cases where the
+         Mapping appears approximately linear on large scales, but has
+         irregularities (e.g. holes) on smaller scales. A value of,
+         say, 50 to 100 pixels can also be employed as a safeguard in
+         general-purpose software, since the effect on performance is
+         minimal.
+
+         If too small a value is given, it will have the effect of
+         inhibiting linear approximation altogether (equivalent to
+         setting \texttt{"} tol\texttt{"}  to zero). Although this may degrade
+         performance, accurate results will still be obtained.
+         Not used if \texttt{"} in\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         badval
+      }{
+         This argument should have the same type as the elements of
+         the \texttt{"} in\texttt{"}  array. It specifies the value used to flag missing
+         data (bad pixels) in the input and output arrays.
+
+         If the AST\_\_USEBAD flag is set via the \texttt{"} flags\texttt{"}  parameter,
+         then this value is used to test for bad pixels in the \texttt{"} in\texttt{"}
+         (and \texttt{"} in\_var\texttt{"} ) array(s).
+
+         In all cases, this value is also used to flag any output
+         elements in the \texttt{"} out\texttt{"}  (and \texttt{"} out\_var\texttt{"} ) array(s) for which
+         rebined values could not be obtained (see the \texttt{"} Propagation
+         of Missing Data\texttt{"}  section below for details of the
+         circumstances under which this may occur).
+      }
+      \sstsubsection{
+         ndim\_out
+      }{
+         The number of dimensions in the output grid. This should be
+         at least one. It need not necessarily be equal to the number
+         of dimensions in the input grid.
+      }
+      \sstsubsection{
+         lbnd\_out
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_out\texttt{"}  elements,
+         containing the coordinates of the centre of the first pixel
+         in the output grid along each dimension.
+      }
+      \sstsubsection{
+         ubnd\_out
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_out\texttt{"}  elements,
+         containing the coordinates of the centre of the last pixel in
+         the output grid along each dimension.
+
+         Note that \texttt{"} lbnd\_out\texttt{"}  and \texttt{"} ubnd\_out\texttt{"}  together define the
+         shape, size and coordinate system of the output grid in the
+         same way as \texttt{"} lbnd\_in\texttt{"}  and \texttt{"} ubnd\_in\texttt{"}  define the shape, size
+         and coordinate system of the input grid.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the first pixel in the region
+         of the input grid which is to be included in the rebined output
+         array.
+         Not used if \texttt{"} in\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the last pixel in the region of
+         the input grid which is to be included in the rebined output
+         array.
+
+         Note that \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"}  together define the shape and
+         position of a (hyper-)rectangular region of the input grid
+         which is to be included in the rebined output array. This region
+         should lie wholly within the extent of the input grid (as
+         defined by the \texttt{"} lbnd\_in\texttt{"}  and \texttt{"} ubnd\_in\texttt{"}  arrays). Regions of
+         the input grid lying outside this region will not be used.
+         Not used if \texttt{"} in\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         out
+      }{
+         Pointer to an array, with one element for each pixel in the
+         output grid. The rebined data values will be added into the
+         original contents of this array. The numerical type of this array
+         should match that of the
+         \texttt{"} in\texttt{"}  array, and the data storage order should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. Fortran array indexing is used).
+      }
+      \sstsubsection{
+         out\_var
+      }{
+         A
+         pointer to an
+         array with the same type and size as the
+         \texttt{"} out\texttt{"}
+         array. This
+         pointer
+         will only be used if the AST\_\_USEVAR or AST\_\_GENVAR flag is set
+         in which case variance estimates for the rebined data values will
+         be added into the array. If neither the AST\_\_USEVAR flag nor the
+         AST\_\_GENVAR flag is set, no output variance estimates will be
+         calculated and this
+         pointer
+         will not be used. A
+         NULL pointer
+         may then be supplied.
+      }
+      \sstsubsection{
+         weights
+      }{
+         Pointer to an array of double,
+         with one or two elements for each pixel in the output grid,
+         depending on whether or not the AST\_\_GENVAR flag has been supplied
+         via the
+         \texttt{"} flags\texttt{"}  parameter.
+         If AST\_\_GENVAR has not been specified then the array should have
+         one element for each output pixel, and it will be used to
+         accumulate the weight associated with each output pixel.
+         If AST\_\_GENVAR has been specified then the array should have
+         two elements for each output pixel. The first half of the array
+         is again used to accumulate the weight associated with each output
+         pixel, and the second half is used to accumulate the square of
+         the weights. In each half, the data storage order should be such that
+         the index of the first grid dimension varies most rapidly and that of
+         the final dimension least rapidly
+         (i.e. Fortran array indexing is used).
+      }
+      \sstsubsection{
+         nused
+      }{
+         A pointer to an int64\_t containing the
+         number of input data values that have been added into the output
+         array so far. The supplied value is incremented on exit by the
+         number of input values used. The value is initially set to zero
+         if the AST\_\_REBININIT flag is set in
+         \texttt{"} flags\texttt{"} .
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate rebinning function, you should
+      replace $<$X$>$ in the generic function name astRebinSeq$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         I: int
+
+         \sstitem
+         B: byte (signed char)
+
+         \sstitem
+         UB: unsigned byte (unsigned char)
+
+      }
+      For example, astRebinSeqD would be used to process \texttt{"} double\texttt{"}
+      data, while astRebinSeqI would be used to process \texttt{"} int\texttt{"}
+      data, etc.
+
+      Note that, unlike
+      astResample$<$X$>$, the astRebinSeq$<$X$>$
+      set of functions does not yet support unsigned integer data types
+      or integers of different sizes.
+   }
+   \sstdiytopic{
+      Control Flags
+   }{
+      The following flags are defined in the \texttt{"} ast.h\texttt{"}  header file and
+      may be used to provide additional control over the rebinning
+      process. Having selected a set of flags, you should supply the
+      bitwise OR of their values via the \texttt{"} flags\texttt{"}  parameter:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_REBININIT: Used to mark the first call in a sequence. It indicates
+         that the supplied
+         \texttt{"} out\texttt{"} , \texttt{"} out\_var\texttt{"}  and \texttt{"} weights\texttt{"}
+         arrays should be filled with zeros (thus over-writing any supplied
+         values) before adding the rebinned input data into them. This flag
+         should be used when rebinning the first input array in a sequence.
+
+         \sstitem
+         AST\_\_REBINEND: Used to mark the last call in a sequence. It causes
+         each value in the
+         \texttt{"} out\texttt{"}  and \texttt{"} out\_var\texttt{"}
+         arrays to be divided by a normalisation factor before being
+         returned. The normalisation factor for each output data value is just
+         the corresponding value from the weights array. The normalisation
+         factor for each output variance value is the square of the data value
+         normalisation factor (see also AST\_\_CONSERVEFLUX). It also causes
+         output data values to be set bad if the corresponding weight is less
+         than the value supplied for
+         parameter \texttt{"} wlim\texttt{"} .
+         It also causes any temporary values stored in the output variance array
+         (see flag AST\_\_GENVAR below) to be converted into usable variance values.
+         Note, this flag is ignored if the AST\_\_NONORM flag is set.
+
+         \sstitem
+         AST\_\_USEBAD: Indicates that there may be bad pixels in the
+         input array(s) which must be recognised by comparing with the
+         value given for \texttt{"} badval\texttt{"}  and propagated to the output array(s).
+         If this flag is not set, all input values are treated literally
+         and the \texttt{"} badval\texttt{"}  value is only used for flagging output array
+         values.
+
+         \sstitem
+         AST\_\_USEVAR: Indicates that output variance estimates should be
+         created by rebinning the supplied input variance estimates. An
+         error will be reported if both this flag and the AST\_\_GENVAR flag
+         are supplied.
+
+         \sstitem
+         AST\_\_GENVAR: Indicates that output variance estimates should be
+         created based on the spread of input data values contributing to each
+         output pixel. An error will be reported if both this flag and the
+         AST\_\_USEVAR flag are supplied. If the AST\_\_GENVAR flag is specified,
+         the supplied output variance array is first used as a work array to
+         accumulate the temporary values needed to generate the output
+         variances. When the sequence ends (as indicated by the
+         AST\_\_REBINEND flag), the contents of the output variance array are
+         converted into the required variance estimates. If the generation of
+         such output variances is required, this flag should be used on every
+         invocation of this
+         function
+         within a sequence, and any supplied input variances will have no effect
+         on the output variances (although input variances will still be used
+         to weight the input data if the AST\_\_VARWGT flag is also supplied).
+         The statistical meaning of these output varianes is determined by
+         the presence or absence of the AST\_\_DISVAR flag (see below).
+
+         \sstitem
+         AST\_\_DISVAR: This flag is ignored unless the AST\_\_GENVAR flag
+         has also been specified. It determines the statistical meaning of
+         the generated output variances. If AST\_\_DISVAR is not specified,
+         generated variances represent variances on the output mean  values. If
+         AST\_\_DISVAR is specified, the generated variances represent the variance
+         of the distribution from which the input values were taken. Each output
+         variance created with AST\_\_DISVAR will be larger than that created
+         without AST\_\_DISVAR by a factor equal to the number of input samples
+         that contribute to the output sample.
+
+         \sstitem
+         AST\_\_VARWGT: Indicates that the input data should be weighted by
+         the reciprocal of the input variances. Otherwise, all input data are
+         given equal weight. If this flag is specified, the calculation of the
+         output variances (if any) is modified to take account of the
+         varying weights assigned to the input data values.
+
+         \sstitem
+         AST\_\_NONORM: If the simple unnormalised sum of all input data falling
+         in each output pixel is required, then this flag should be set on
+         each call in the sequence and the AST\_\_REBINEND should not be used
+         on the last call. In this case
+         NULL pointers can be supplied for \texttt{"} weights\texttt{"}  and \texttt{"} nused\texttt{"} .
+         This flag cannot be used with the AST\_\_CONSERVEFLUX, AST\_\_GENVAR
+         or AST\_\_VARWGT flag.
+
+         \sstitem
+         AST\_\_CONSERVEFLUX: Indicates that the normalized output pixel values
+         generated by the AST\_\_REBINEND flag should be scaled in such a way as
+         to preserve the total data value in a feature on the sky. Without this
+         flag, each normalised output pixel value represents a weighted mean
+         of the input data values around the corresponding input position.
+         is appropriate if the input data represents the spatial density of
+         some quantity (e.g. surface brightness in Janskys per square
+         arc-second) because the output pixel values will have the same
+         normalisation and units as the input pixel values. However, if the
+         input data values represent flux (or some other physical quantity)
+         per pixel, then the AST\_\_CONSERVEFLUX flag could be of use. It causes
+         each output pixel value to be scaled by the ratio of the output pixel
+         size to the input pixel size.
+
+      }
+      This flag can only be used if the Mapping is successfully approximated
+      by one or more linear transformations. Thus an error will be reported
+      if it used when the
+      \texttt{"} tol\texttt{"}  parameter
+      is set to zero (which stops the use of linear approximations), or
+      if the Mapping is too non-linear to be approximated by a piece-wise
+      linear transformation. The ratio of output to input pixel size is
+      evaluated once for each panel of the piece-wise linear approximation to
+      the Mapping, and is assumed to be constant for all output pixels in the
+      panel. The scaling factors for adjacent panels will in general
+      differ slightly, and so the joints between panels may be visible when
+      viewing the output image at high contrast. If this is a problem,
+      reduce the value of the
+      \texttt{"} tol\texttt{"}  parameter
+      until the difference between adjacent panels is sufficiently small
+      to be insignificant.
+
+      This flag should normally be supplied on each invocation of
+      astRebinSeq$<$X$>$
+      within a given sequence.
+
+      Note, this flag cannot be used in conjunction with the AST\_\_NOSCALE
+      flag (an error will be reported if both flags are specified).
+   }
+   \sstdiytopic{
+      Propagation of Missing Data
+   }{
+      Instances of missing data (bad pixels) in the output grid are
+      identified by occurrences of the \texttt{"} badval\texttt{"}  value in the \texttt{"} out\texttt{"}
+      array. These are only produced if the AST\_\_REBINEND flag is
+      specified and a pixel has zero weight.
+
+      An input pixel is considered bad (and is consequently ignored) if
+      its
+      data value is equal to \texttt{"} badval\texttt{"}  and the AST\_\_USEBAD flag is
+      set via the \texttt{"} flags\texttt{"}  parameter.
+
+      In addition, associated output variance estimates (if
+      calculated) may be declared bad and flagged with the \texttt{"} badval\texttt{"}
+      value in the \texttt{"} out\_var\texttt{"}  array for similar reasons.
+   }
+}
+\sstroutine{
+   astRegionOutline
+}{
+   Draw the outline of an AST Region
+}{
+   \sstdescription{
+      This function draws an outline around the supplied AST \htmlref{Region}{Region} object.
+   }
+   \sstsynopsis{
+      void astRegionOutline( AstPlot $*$this, AstRegion $*$region )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Plot}{Plot}.
+      }
+      \sstsubsection{
+         region
+      }{
+         Pointer to the Region.
+      }
+   }
+}
+\sstroutine{
+   astRemapFrame
+}{
+   Modify a Frame\texttt{'} s relationship to other Frames in a FrameSet
+}{
+   \sstdescription{
+      This function modifies the relationship (i.e. \htmlref{Mapping}{Mapping}) between a
+      specified \htmlref{Frame}{Frame} in a \htmlref{FrameSet}{FrameSet} and the other Frames in that
+      FrameSet.
+
+      Typically, this might be required if the FrameSet has been used
+      to calibrate (say) an image, and that image is re-binned. The
+      Frame describing the image will then have undergone a coordinate
+      transformation, and this should be communicated to the associated
+      FrameSet using this function.
+   }
+   \sstsynopsis{
+      void astRemapFrame( AstFrameSet $*$this, int iframe, AstMapping $*$map )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         iframe
+      }{
+         The index within the FrameSet of the Frame to be modified.
+         This value should lie in the range from 1 to the number of
+         Frames in the FrameSet (as given by its \htmlref{Nframe}{Nframe} attribute).
+      }
+      \sstsubsection{
+         map
+      }{
+         Pointer to a Mapping whose forward transformation converts
+         coordinate values from the original coordinate system
+         described by the Frame to the new one, and whose inverse
+         transformation converts in the opposite direction.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of AST\_\_BASE or AST\_\_CURRENT may be given for the
+         \texttt{"} iframe\texttt{"}  parameter to specify the base Frame or the current
+         Frame respectively.
+
+         \sstitem
+         The relationship between the selected Frame and any other
+         Frame within the FrameSet will be modified by this function,
+         but the relationship between all other Frames in the FrameSet
+         remains unchanged.
+
+         \sstitem
+         The number of input coordinate values accepted by the Mapping
+         (its \htmlref{Nin}{Nin} attribute) and the number of output coordinate values
+         generated (its \htmlref{Nout}{Nout} attribute) must be equal and must match the
+         number of axes in the Frame being modified.
+
+         \sstitem
+         If a simple change of axis order is required, then the
+         \htmlref{astPermAxes}{astPermAxes} function may provide a more straightforward method
+         of making the required changes to the FrameSet.
+
+         \sstitem
+         This function cannot be used to change the number of Frame
+         axes. To achieve this, a new Frame must be added to the FrameSet
+         (\htmlref{astAddFrame}{astAddFrame}) and the original one removed if necessary
+         (\htmlref{astRemoveFrame}{astRemoveFrame}).
+
+         \sstitem
+         Any variant Mappings associated with the remapped Frame (except
+         for the current variant) will be lost as a consequence of calling this
+         method (see attribute \texttt{"} \htmlref{Variant}{Variant}\texttt{"} ).
+      }
+   }
+}
+\sstroutine{
+   astRemoveColumn
+}{
+   Remove a column from a table
+}{
+   \sstdescription{
+      This function removes a specified column from the supplied table.
+      The
+      function
+      returns without action if the named column does not exist in the
+      \htmlref{Table}{Table} (no error is reported).
+   }
+   \sstsynopsis{
+      void astRemoveColumn( AstTable $*$this, const char $*$name )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         name
+      }{
+         The column name. Trailing spaces are ignored (all other spaces
+         are significant). Case is significant.
+      }
+   }
+}
+\sstroutine{
+   astRemoveFrame
+}{
+   Remove a Frame from a FrameSet
+}{
+   \sstdescription{
+      This function removes a \htmlref{Frame}{Frame} from a \htmlref{FrameSet}{FrameSet}. All other Frames
+      in the FrameSet have their indices re-numbered from one (if
+      necessary), but are otherwise unchanged.
+   }
+   \sstsynopsis{
+      void astRemoveFrame( AstFrameSet $*$this, int iframe )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FrameSet.
+      }
+      \sstsubsection{
+         iframe
+      }{
+         The index within the FrameSet of the Frame to be removed.
+         This value should lie in the range from 1 to the number of
+         Frames in the FrameSet (as given by its \htmlref{Nframe}{Nframe} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Removing a Frame from a FrameSet does not affect the
+         relationship between other Frames in the FrameSet, even if they
+         originally depended on the Frame being removed.
+
+         \sstitem
+         The number of Frames in a FrameSet cannot be reduced to zero.
+         An error will result if an attempt is made to remove the only
+         remaining Frame.
+
+         \sstitem
+         A value of AST\_\_BASE or AST\_\_CURRENT may be given for the
+         \texttt{"} iframe\texttt{"}  parameter to specify the base Frame or the current
+         Frame respectively.
+
+         \sstitem
+         If a FrameSet\texttt{'} s base or current Frame is removed, the \htmlref{Base}{Base} or
+         \htmlref{Current}{Current} attribute (respectively) of the FrameSet will have its
+         value cleared, so that another Frame will then assume its role
+         by default.
+
+         \sstitem
+         If any other Frame is removed, the base and current Frames
+         will remain the same. To ensure this, the Base and/or Current
+         attributes of the FrameSet will be changed, if necessary, to
+         reflect any change in the indices of these Frames.
+      }
+   }
+}
+\sstroutine{
+   astRemoveParameter
+}{
+   Remove a global parameter from a table
+}{
+   \sstdescription{
+      This function removes a specified global parameter from the supplied table.
+      The
+      function
+      returns without action if the named parameter does not exist in the
+      \htmlref{Table}{Table} (no error is reported).
+   }
+   \sstsynopsis{
+      void astRemoveParameter( AstTable $*$this, const char $*$name )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         name
+      }{
+         The parameter name. Trailing spaces are ignored (all other spaces
+         are significant). Case is significant.
+      }
+   }
+}
+\sstroutine{
+   astRemoveRegions
+}{
+   Remove any Regions from a Mapping
+}{
+   \sstdescription{
+      This function searches the suppliedMapping (which may be a
+      compound \htmlref{Mapping}{Mapping} such as a \htmlref{CmpMap}{CmpMap}) for any component Mappings
+      that are instances of the AST \htmlref{Region}{Region} class. It then creates a new
+      Mapping from which all Regions have been removed. If a Region
+      cannot simply be removed (for instance, if it is a component of a
+      parallel CmpMap), then it is replaced with an equivalent \htmlref{UnitMap}{UnitMap}
+      in the returned Mapping.
+   }
+   \sstsynopsis{
+      AstMapping $*$astRemoveRegions( AstMapping $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the original Mapping.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         If the supplied Mapping is a CmpFrame, any component Frames that
+         are instances of the Region class are replaced by the equivalent
+         \htmlref{Frame}{Frame}.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         If the supplied Mapping is a FrameSet, the returned Mapping
+         will be a copy of the supplied FrameSet in which Regions have
+         been removed from all the inter-Frame Mappings, and any Frames
+         which are instances of the Region class are repalced by the
+         equivalent Frame.
+      }
+      \sstsubsection{
+         Mapping
+      }{
+         This function applies to all Mappings.
+      }
+      \sstsubsection{
+         Region
+      }{
+         If the supplied Mapping is a Region, the returned Mapping will
+         be the equivalent Frame.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astRemoveRegions()
+      }{
+         A new pointer to the (possibly modified) Mapping.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function can safely be applied even to Mappings which
+         contain no Regions. If no Regions are found, it
+         behaves exactly like \htmlref{astClone}{astClone} and returns a pointer to the
+         original Mapping.
+
+         \sstitem
+         The Mapping returned by this function may not be independent
+         of the original (even if some Regions were removed), and
+         modifying it may therefore result in indirect modification of
+         the original. If a completely independent result is required, a
+         copy should be made using \htmlref{astCopy}{astCopy}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astRemoveRow
+}{
+   Remove a row from a table
+}{
+   \sstdescription{
+      This function removes a specified row from the supplied table.
+      The
+      function
+      returns without action if the row does not exist in the
+      \htmlref{Table}{Table} (no error is reported).
+   }
+   \sstsynopsis{
+      void astRemoveRow( AstTable $*$this, int index )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Table.
+      }
+      \sstsubsection{
+         index
+      }{
+         The index of the row to be removed. The first row has index 1.
+      }
+   }
+}
+\sstroutine{
+   astRemoveTables
+}{
+   Remove one or more tables from a FitsChan
+}{
+   \sstdescription{
+      This function
+      removes the named tables from the \htmlref{FitsChan}{FitsChan}, it they exist (no error
+      is reported if any the tables do not exist).
+   }
+   \sstsynopsis{
+      void astRemoveTables( AstFitsChan $*$this, const char $*$key )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         key
+      }{
+         The key indicating which tables to exist. A single key or a
+         comma-separated list of keys can be supplied. If a blank string
+         is supplied, all tables are removed.
+      }
+   }
+}
+\sstroutine{
+   astResample$<$X$>$
+}{
+   Resample a region of a data grid
+}{
+   \sstdescription{
+      This is a set of functions for resampling gridded data (e.g. an
+      image) under the control of a geometrical transformation, which
+      is specified by a \htmlref{Mapping}{Mapping}.  The functions operate on a pair of
+      data grids (input and output), each of which may have any number
+      of dimensions. Resampling may be restricted to a specified
+      region of the output grid. An associated grid of error estimates
+      associated with the input data may also be supplied (in the form
+      of variance values), so as to produce error estimates for the
+      resampled output data. Propagation of missing data (bad pixels)
+      is supported.
+
+      You should use a resampling function which matches the numerical
+      type of the data you are processing by replacing $<$X$>$ in
+      the generic function name astResample$<$X$>$ by an appropriate 1- or
+      2-character type code. For example, if you are resampling data
+      with type \texttt{"} float\texttt{"} , you should use the function astResampleF (see
+      the \texttt{"} Data Type Codes\texttt{"}  section below for the codes appropriate to
+      other numerical types).
+
+      Resampling of the grid of input data is performed by
+      transforming the coordinates of the centre of each output grid
+      element (or pixel) into the coordinate system of the input grid.
+      Since the resulting coordinates will not, in general, coincide
+      with the centre of an input pixel, sub-pixel interpolation is
+      performed between the neighbouring input pixels. This produces a
+      resampled value which is then assigned to the output pixel. A
+      choice of sub-pixel interpolation schemes is provided, but you
+      may also implement your own.
+
+      This algorithm samples the input data value, it does not integrate
+      it. Thus total data value in the input image will not, in general,
+      be conserved. However, an option is provided (see the \texttt{"} Control Flags\texttt{"}
+      section below) which can produce approximate flux conservation by
+      scaling the output values using the ratio of the output pixel size
+      to the input pixel size. However, if accurate flux conservation is
+      important to you, consder using the
+      \htmlref{astRebin$<$X$>$}{astRebin$<$X$>$} or \htmlref{astRebinSeq$<$X$>$}{astRebinSeq$<$X$>$} family of functions
+      instead.
+
+      Output pixel coordinates are transformed into the coordinate
+      system of the input grid using the inverse transformation of the
+      Mapping which is supplied. This means that geometrical features
+      in the input data are subjected to the Mapping\texttt{'} s forward
+      transformation as they are transferred from the input to the
+      output grid (although the Mapping\texttt{'} s forward transformation is
+      not explicitly used).
+
+      In practice, transforming the coordinates of every pixel of a
+      large data grid can be time-consuming, especially if the Mapping
+      involves complicated functions, such as sky projections. To
+      improve performance, it is therefore possible to approximate
+      non-linear Mappings by a set of linear transformations which are
+      applied piece-wise to separate sub-regions of the data. This
+      approximation process is applied automatically by an adaptive
+      algorithm, under control of an accuracy criterion which
+      expresses the maximum tolerable geometrical distortion which may
+      be introduced, as a fraction of a pixel.
+
+      This algorithm first attempts to approximate the Mapping with a
+      linear transformation applied over the whole region of the
+      output grid which is being used. If this proves to be
+      insufficiently accurate, the output region is sub-divided into
+      two along its largest dimension and the process is repeated
+      within each of the resulting sub-regions. This process of
+      sub-division continues until a sufficiently good linear
+      approximation is found, or the region to which it is being
+      applied becomes too small (in which case the original Mapping is
+      used directly).
+   }
+   \sstsynopsis{
+      int astResample$<$X$>$( AstMapping $*$this, int ndim\_in,
+                          const int lbnd\_in[], const int ubnd\_in[],
+                          const $<$Xtype$>$ in[], const $<$Xtype$>$ in\_var[],
+                          int interp, void ($*$ finterp)( void ),
+                          const double params[], int flags,
+                          double tol, int maxpix,
+                          $<$Xtype$>$ badval, int ndim\_out,
+                          const int lbnd\_out[], const int ubnd\_out[],
+                          const int lbnd[], const int ubnd[],
+                          $<$Xtype$>$ out[], $<$Xtype$>$ out\_var[] );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to a Mapping, whose inverse transformation will be
+         used to transform the coordinates of pixels in the output
+         grid into the coordinate system of the input grid. This
+         yields the positions which are used to obtain resampled
+         values by sub-pixel interpolation within the input grid.
+
+         The number of input coordinates used by this Mapping (as
+         given by its \htmlref{Nin}{Nin} attribute) should match the number of input
+         grid dimensions given by the value of \texttt{"} ndim\_in\texttt{"}
+         below. Similarly, the number of output coordinates (\htmlref{Nout}{Nout}
+         attribute) should match the number of output grid dimensions
+         given by \texttt{"} ndim\_out\texttt{"} .
+      }
+      \sstsubsection{
+         ndim\_in
+      }{
+         The number of dimensions in the input grid. This should be at
+         least one.
+      }
+      \sstsubsection{
+         lbnd\_in
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         ubnd\_in
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that \texttt{"} lbnd\_in\texttt{"}  and \texttt{"} ubnd\_in\texttt{"}  together define the shape
+         and size of the input grid, its extent along a particular
+         (j\texttt{'} th) dimension being ubnd\_in[j]-lbnd\_in[j]$+$1 (assuming the
+         index \texttt{"} j\texttt{"}  to be zero-based). They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre.
+      }
+      \sstsubsection{
+         in
+      }{
+         Pointer to an array, with one element for each pixel in the
+         input grid, containing the input data to be resampled.  The
+         numerical type of this array should match the 1- or
+         2-character type code appended to the function name (e.g. if
+         you are using astResampleF, the type of each array element
+         should be \texttt{"} float\texttt{"} ).
+
+         The storage order of data within this array should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. Fortran array indexing is used).
+      }
+      \sstsubsection{
+         in\_var
+      }{
+         An optional pointer to a second array with the same size and
+         type as the \texttt{"} in\texttt{"}  array. If given, this should contain a set
+         of non-negative values which represent estimates of the
+         statistical variance associated with each element of the \texttt{"} in\texttt{"}
+         array. If this array is supplied (together with the
+         corresponding \texttt{"} out\_var\texttt{"}  array), then estimates of the
+         variance of the resampled output data will be calculated.
+
+         If no input variance estimates are being provided, a NULL
+         pointer should be given.
+      }
+      \sstsubsection{
+         interp
+      }{
+         This parameter specifies the scheme to be used for sub-pixel
+         interpolation within the input grid. It may be used to select
+         from a set of pre-defined schemes by supplying one of the
+         values described in the \texttt{"} Sub-Pixel Interpolation Schemes\texttt{"}
+         section below.  If a value of zero is supplied, then the
+         default linear interpolation scheme is used (equivalent to
+         supplying the value AST\_\_LINEAR).
+
+         Alternatively, you may supply a value which indicates that
+         you will provide your own function to perform sub-pixel
+         interpolation by means of the \texttt{"} finterp \texttt{"}  parameter. Again, see
+         the \texttt{"} Sub-Pixel Interpolation Schemes\texttt{"}  section below for
+         details.
+      }
+      \sstsubsection{
+         finterp
+      }{
+         If the value given for the \texttt{"} interp\texttt{"}  parameter indicates that
+         you will provide your own function for sub-pixel
+         interpolation, then a pointer to that function should be
+         given here. For details of the interface which the function
+         should have (several are possible, depending on the value of
+         \texttt{"} interp\texttt{"} ), see the \texttt{"} Sub-Pixel Interpolation Schemes\texttt{"}  section
+         below.
+
+         If the \texttt{"} interp\texttt{"}  parameter has any other value, corresponding
+         to one of the pre-defined interpolation schemes, then this
+         function will not be used and you may supply a NULL pointer.
+      }
+      \sstsubsection{
+         params
+      }{
+         An optional pointer to an array of double which should contain
+         any additional parameter values required by the sub-pixel
+         interpolation scheme. If such parameters are required, this
+         will be noted in the \texttt{"} Sub-Pixel Interpolation Schemes\texttt{"}
+         section below (you may also use this array to pass values
+         to your own interpolation function).
+
+         If no additional parameters are required, this array is not
+         used and a NULL pointer may be given.
+      }
+      \sstsubsection{
+         flags
+      }{
+         The bitwise OR of a set of flag values which may be used to
+         provide additional control over the resampling operation. See
+         the \texttt{"} Control Flags\texttt{"}  section below for a description of the
+         options available.  If no flag values are to be set, a value
+         of zero should be given.
+      }
+      \sstsubsection{
+         tol
+      }{
+         The maximum tolerable geometrical distortion which may be
+         introduced as a result of approximating non-linear Mappings
+         by a set of piece-wise linear transformations. This should be
+         expressed as a displacement in pixels in the input grid\texttt{'} s
+         coordinate system.
+
+         If piece-wise linear approximation is not required, a value
+         of zero may be given. This will ensure that the Mapping is
+         used without any approximation, but may increase execution
+         time.
+      }
+      \sstsubsection{
+         maxpix
+      }{
+         A value which specifies an initial scale size (in pixels) for
+         the adaptive algorithm which approximates non-linear Mappings
+         with piece-wise linear transformations. Normally, this should
+         be a large value (larger than any dimension of the region of
+         the output grid being used). In this case, a first attempt to
+         approximate the Mapping by a linear transformation will be
+         made over the entire output region.
+
+         If a smaller value is used, the output region will first be
+         divided into sub-regions whose size does not exceed \texttt{"} maxpix\texttt{"}
+         pixels in any dimension. Only at this point will attempts at
+         approximation commence.
+
+         This value may occasionally be useful in preventing false
+         convergence of the adaptive algorithm in cases where the
+         Mapping appears approximately linear on large scales, but has
+         irregularities (e.g. holes) on smaller scales. A value of,
+         say, 50 to 100 pixels can also be employed as a safeguard in
+         general-purpose software, since the effect on performance is
+         minimal.
+
+         If too small a value is given, it will have the effect of
+         inhibiting linear approximation altogether (equivalent to
+         setting \texttt{"} tol\texttt{"}  to zero). Although this may degrade
+         performance, accurate results will still be obtained.
+      }
+      \sstsubsection{
+         badval
+      }{
+         This argument should have the same type as the elements of
+         the \texttt{"} in\texttt{"}  array. It specifies the value used to flag missing
+         data (bad pixels) in the input and output arrays.
+
+         If the AST\_\_USEBAD flag is set via the \texttt{"} flags\texttt{"}  parameter,
+         then this value is used to test for bad pixels in the \texttt{"} in\texttt{"}
+         (and \texttt{"} in\_var\texttt{"} ) array(s).
+
+         Unless the AST\_\_NOBAD flag is set via the \texttt{"} flags\texttt{"}  parameter,
+         this value is also used to flag any output
+         elements in the \texttt{"} out\texttt{"}  (and \texttt{"} out\_var\texttt{"} ) array(s) for which
+         resampled values could not be obtained (see the \texttt{"} Propagation
+         of Missing Data\texttt{"}  section below for details of the
+         circumstances under which this may occur). The astResample$<$X$>$
+         function return value indicates whether any such values have
+         been produced. If the AST\_\_NOBAD flag is set. then output array
+         elements for which no resampled value could be obtained are
+         left set to the value they had on entry to this function.
+      }
+      \sstsubsection{
+         ndim\_out
+      }{
+         The number of dimensions in the output grid. This should be
+         at least one. It need not necessarily be equal to the number
+         of dimensions in the input grid.
+      }
+      \sstsubsection{
+         lbnd\_out
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_out\texttt{"}  elements,
+         containing the coordinates of the centre of the first pixel
+         in the output grid along each dimension.
+      }
+      \sstsubsection{
+         ubnd\_out
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_out\texttt{"}  elements,
+         containing the coordinates of the centre of the last pixel in
+         the output grid along each dimension.
+
+         Note that \texttt{"} lbnd\_out\texttt{"}  and \texttt{"} ubnd\_out\texttt{"}  together define the
+         shape, size and coordinate system of the output grid in the
+         same way as \texttt{"} lbnd\_in\texttt{"}  and \texttt{"} ubnd\_in\texttt{"}  define the shape, size
+         and coordinate system of the input grid.
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_out\texttt{"}  elements,
+         containing the coordinates of the first pixel in the region
+         of the output grid for which a resampled value is to be
+         calculated.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_out\texttt{"}  elements,
+         containing the coordinates of the last pixel in the region of
+         the output grid for which a resampled value is to be
+         calculated.
+
+         Note that \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"}  together define the shape and
+         position of a (hyper-)rectangular region of the output grid
+         for which resampled values should be produced. This region
+         should lie wholly within the extent of the output grid (as
+         defined by the \texttt{"} lbnd\_out\texttt{"}  and \texttt{"} ubnd\_out\texttt{"}  arrays). Regions of
+         the output grid lying outside this region will not be
+         modified.
+      }
+      \sstsubsection{
+         out
+      }{
+         Pointer to an array, with one element for each pixel in the
+         output grid, into which the resampled data values will be
+         returned. The numerical type of this array should match that
+         of the \texttt{"} in\texttt{"}  array, and the data storage order should be such
+         that the index of the first grid dimension varies most
+         rapidly and that of the final dimension least rapidly
+         (i.e. Fortran array indexing is used).
+      }
+      \sstsubsection{
+         out\_var
+      }{
+         An optional pointer to an array with the same type and size
+         as the \texttt{"} out\texttt{"}  array. If given, this array will be used to
+         return variance estimates for the resampled data values. This
+         array will only be used if the \texttt{"} in\_var\texttt{"}  array has also been
+         supplied.
+
+         The output variance values will be calculated on the
+         assumption that errors on the input data values are
+         statistically independent and that their variance estimates
+         may simply be summed (with appropriate weighting factors)
+         when several input pixels contribute to an output data
+         value. If this assumption is not valid, then the output error
+         estimates may be biased. In addition, note that the
+         statistical errors on neighbouring output data values (as
+         well as the estimates of those errors) may often be
+         correlated, even if the above assumption about the input data
+         is correct, because of the sub-pixel interpolation schemes
+         employed.
+
+         If no output variance estimates are required, a NULL pointer
+         should be given.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astResample$<$X$>$()
+      }{
+         The number of output pixels for which no valid resampled value
+         could be obtained. Thus, in the absence of any error, a returned
+         value of zero indicates that all the required output pixels
+         received valid resampled data values (and variances). See the
+         \texttt{"} badval\texttt{"}  and \texttt{"} flags\texttt{"}  parameters.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with the global error status set, or if it should fail for any
+         reason.
+      }
+   }
+   \sstdiytopic{
+      Data Type Codes
+   }{
+      To select the appropriate resampling function, you should
+      replace $<$X$>$ in the generic function name astResample$<$X$>$ with a
+      1- or 2-character data type code, so as to match the numerical
+      type $<$Xtype$>$ of the data you are processing, as follows:
+      \sstitemlist{
+
+         \sstitem
+         D: double
+
+         \sstitem
+         F: float
+
+         \sstitem
+         L: long int (may be 32 or 64 bit)
+
+         \sstitem
+         K: 64 bit int
+
+         \sstitem
+         UL: unsigned long int (may be 32 or 64 bit)
+
+         \sstitem
+         UK: unsigned 64 bit int
+
+         \sstitem
+         I: int
+
+         \sstitem
+         UI: unsigned int
+
+         \sstitem
+         S: short int
+
+         \sstitem
+         US: unsigned short int
+
+         \sstitem
+         B: byte (signed char)
+
+         \sstitem
+         UB: unsigned byte (unsigned char)
+
+      }
+      For example, astResampleD would be used to process \texttt{"} double\texttt{"}
+      data, while astResampleS would be used to process \texttt{"} short int\texttt{"}
+      data, etc.
+   }
+   \sstdiytopic{
+      Sub-Pixel Interpolation Schemes
+   }{
+      There is no such thing as a perfect sub-pixel interpolation
+      scheme and, in practice, all resampling will result in some
+      degradation of gridded data.  A range of schemes is therefore
+      provided, from which you can choose the one which best suits
+      your needs.
+
+      In general, a balance must be struck between schemes which tend
+      to degrade sharp features in the data by smoothing them, and
+      those which attempt to preserve sharp features. The latter will
+      often tend to introduce unwanted oscillations, typically visible
+      as \texttt{"} ringing\texttt{"}  around sharp features and edges, especially if the
+      data are under-sampled (i.e. if the sharpest features are less
+      than about two pixels across). In practice, a good interpolation
+      scheme is likely to be a compromise and may exhibit some aspects
+      of both these features.
+
+      For under-sampled data, some interpolation schemes may appear to
+      preserve data resolution because they transform single input
+      pixels into single output pixels, rather than spreading their
+      data between several output pixels. While this may look
+      better cosmetically, it can result in a geometrical shift of
+      sharp features in the data. You should beware of this if you
+      plan to use such features (e.g.) for image alignment.
+
+      The following are two easy-to-use sub-pixel interpolation
+      schemes which are generally applicable. They are selected by
+      supplying the appropriate value (defined in the \texttt{"} ast.h\texttt{"}  header
+      file) via the \texttt{"} interp\texttt{"}  parameter. In these cases, the \texttt{"} finterp\texttt{"}
+      and \texttt{"} params\texttt{"}  parameters are not used:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_NEAREST: This is the simplest possible scheme, in which
+         the value of the input pixel with the nearest centre to the
+         interpolation point is used. This is very quick to execute and
+         will preserve single-pixel features in the data, but may
+         displace them by up to half their width along each dimension. It
+         often gives a good cosmetic result, so is useful for quick-look
+         processing, but is unsuitable if accurate geometrical
+         transformation is required.
+
+         \sstitem
+         AST\_\_LINEAR: This is the default scheme, which uses linear
+         interpolation between the nearest neighbouring pixels in the
+         input grid (there are two neighbours in one dimension, four
+         neighbours in two dimensions, eight in three dimensions,
+         etc.). It is superior to the nearest-pixel scheme (above) in not
+         displacing features in the data, yet it still executes fairly
+         rapidly. It is generally a safe choice if you do not have any
+         particular reason to favour another scheme, since it cannot
+         introduce oscillations. However, it does introduce some spatial
+         smoothing which varies according to the distance of the
+         interpolation point from the neighbouring pixels. This can
+         degrade the shape of sharp features in the data in a
+         position-dependent way. It may also show in the output variance
+         grid (if used) as a pattern of stripes or fringes.
+
+      }
+      An alternative set of interpolation schemes is based on forming
+      the interpolated value from the weighted sum of a set of
+      surrounding pixel values (not necessarily just the nearest
+      neighbours). This approach has its origins in the theory of
+      digital filtering, in which interpolated values are obtained by
+      conceptually passing the sampled data (represented by a grid of
+      delta functions) through a linear filter which implements a
+      convolution. Because the convolution kernel is continuous, the
+      convolution yields a continuous function which may then be
+      evaluated at fractional pixel positions. The (possibly
+      multi-dimensional) kernel is usually regarded as \texttt{"} separable\texttt{"}  and
+      formed from the product of a set of identical 1-dimensional
+      kernel functions, evaluated along each dimension. Different
+      interpolation schemes are then distinguished by the choice of
+      this 1-dimensional interpolation kernel. The number of
+      surrounding pixels which contribute to the result may also be
+      varied.
+
+      From a practical standpoint, it is useful to divide the weighted
+      sum of pixel values by the sum of the weights when determining
+      the interpolated value.  Strictly, this means that a true
+      convolution is no longer being performed. However, the
+      distinction is rarely important in practice because (for
+      slightly subtle reasons) the sum of weights is always
+      approximately constant for good interpolation kernels. The
+      advantage of this technique, which is used here, is that it can
+      easily accommodate missing data and tends to minimise unwanted
+      oscillations at the edges of the data grid.
+
+      In the following schemes, which are based on a 1-dimensional
+      interpolation kernel, the first element of the \texttt{"} params\texttt{"}  array
+      should be used to specify how many pixels are to contribute to the
+      interpolated result on either side of the interpolation point in
+      each dimension (the nearest integer value is used). Execution time
+      increases rapidly with this number. Typically, a value of 2 is
+      appropriate and the minimum value used will be 1 (i.e. two pixels
+      altogether, one on either side of the interpolation point).
+      A value of zero or less may be given for \texttt{"} params[0]\texttt{"}
+      to indicate that a suitable number of pixels should be calculated
+      automatically.
+
+      In each of these cases, the \texttt{"} finterp\texttt{"}  parameter is not used:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_GAUSS: This scheme uses a kernel of the form exp(-k$*$x$*$x), with
+         k a positive constant. The full-width at half-maximum (FWHM) is
+         given by
+         \texttt{"} params[1]\texttt{"}
+         to zero will select the number of contributing pixels so as to utilise
+         the width of the kernel out to where the envelope declines to 1\% of its
+         maximum value). This kernel suppresses noise at the expense of
+         smoothing the output array.
+
+         \sstitem
+         AST\_\_SINC: This scheme uses a sinc(pi$*$x) kernel, where x is the
+         pixel offset from the interpolation point and sinc(z)=sin(z)/z. This
+         sometimes features as an \texttt{"} optimal\texttt{"}  interpolation kernel in books on
+         image processing. Its supposed optimality depends on the assumption
+         that the data are band-limited (i.e. have no spatial frequencies above
+         a certain value) and are adequately sampled. In practice, astronomical
+         data rarely meet these requirements. In addition, high spatial
+         frequencies are often present due (e.g.) to image defects and cosmic
+         ray events. Consequently, substantial ringing can be experienced with
+         this kernel. The kernel also decays slowly with distance, so that
+         many surrounding pixels are required, leading to poor performance.
+         Abruptly truncating it, by using only a few neighbouring pixels,
+         improves performance and may reduce ringing (if \texttt{"} params[0]\texttt{"}  is set to
+         zero, then only two pixels will be used on either side). However, a
+         more gradual truncation, as implemented by other kernels, is generally
+         to be preferred. This kernel is provided mainly so that you can
+         convince yourself not to use it!
+
+         \sstitem
+         AST\_\_SINCSINC: This scheme uses an improved kernel, of the form
+         sinc(pi$*$x).sinc(k$*$pi$*$x), with k a constant, out to the point where
+         sinc(k$*$pi$*$x) goes to zero, and zero beyond. The second sinc() factor
+         provides an \texttt{"} envelope\texttt{"}  which gradually rolls off the normal sinc(pi$*$x)
+         kernel at large offsets. The width of this envelope is specified by
+         giving the number of pixels offset at which it goes to zero by means
+         of the \texttt{"} params[1]\texttt{"}  value, which should be at least 1.0 (in addition,
+         setting \texttt{"} params[0]\texttt{"}  to zero will select the number of contributing
+         pixels so as to utilise the full width of the kernel, out to where it
+         reaches zero). The case given by \texttt{"} params[0]=2, params[1]=2\texttt{"}  is typically
+         a good choice and is sometimes known as the Lanczos kernel. This is a
+         valuable general-purpose interpolation scheme, intermediate in its
+         visual effect on images between the AST\_\_NEAREST and AST\_\_LINEAR
+         schemes. Although the kernel is slightly oscillatory, ringing is
+         adequately suppressed if the data are well sampled.
+
+         \sstitem
+         AST\_\_SINCCOS: This scheme uses a kernel of the form
+         sinc(pi$*$x).cos(k$*$pi$*$x), with k a constant, out to the point where
+         cos(k$*$pi$*$x) goes to zero, and zero beyond. As above, the cos() factor
+         provides an envelope which gradually rolls off the sinc() kernel
+         at large offsets. The width of this envelope is specified by giving
+         the number of pixels offset at which it goes to zero by means
+         of the \texttt{"} params[1]\texttt{"}  value, which should be at least 1.0 (in addition,
+         setting \texttt{"} params[0]\texttt{"}  to zero will select the number of contributing
+         pixels so as to utilise the full width of the kernel, out to where it
+         reaches zero). This scheme gives similar results to the
+         AST\_\_SINCSINC scheme, which it resembles.
+
+         \sstitem
+         AST\_\_SINCGAUSS: This scheme uses a kernel of the form
+         sinc(pi$*$x).exp(-k$*$x$*$x), with k a positive constant. Here, the sinc()
+         kernel is rolled off using a Gaussian envelope which is specified by
+         giving its full-width at half-maximum (FWHM) by means of the \texttt{"} params[1]\texttt{"}
+         value, which should be at least 0.1 (in addition, setting \texttt{"} params[0]\texttt{"}
+         to zero will select the number of contributing pixels so as to utilise
+         the width of the kernel out to where the envelope declines to 1\% of its
+         maximum value). On astronomical images and spectra, good results are
+         often obtained by approximately matching the FWHM of the
+         envelope function, given by \texttt{"} params[1]\texttt{"} , to the point spread function
+         of the input data. However, there does not seem to be any theoretical
+         reason for this.
+
+         \sstitem
+         AST\_\_SOMB: This scheme uses a somb(pi$*$x) kernel (a \texttt{"} sombrero\texttt{"}
+         function), where x is the pixel offset from the interpolation point
+         and somb(z)=2$*$J1(z)/z  (J1 is a Bessel function of the first kind of
+         order 1). It is similar to the AST\_\_SINC kernel, and has the same
+         parameter usage.
+
+         \sstitem
+         AST\_\_SOMBCOS: This scheme uses a kernel of the form
+         somb(pi$*$x).cos(k$*$pi$*$x), with k a constant, out to the point where
+         cos(k$*$pi$*$x) goes to zero, and zero beyond. It is similar to the
+         AST\_\_SINCCOS kernel, and has the same parameter usage.
+
+      }
+      In addition, the following schemes are provided which are not based
+      on a 1-dimensional kernel:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_BLOCKAVE: This scheme simply takes an average of all the
+         pixels on the input grid in a cube centred on the interpolation
+         point.  The number of pixels in the cube is determined by the
+         value of the first element of the \texttt{"} params\texttt{"}  array, which gives
+         the number of pixels in each dimension on either side of the
+         central point.  Hence a block of (2 $*$ params[0])$\wedge$ndim\_in
+         pixels in the input grid will be examined to determine the
+         value of the output pixel.  If the variance is not being used
+         (var\_in or var\_out = NULL) then all valid pixels in this cube
+         will be averaged in to the result with equal weight.
+         If variances are being used, then each input pixel will be
+         weighted proportionally to the reciprocal of its variance; any
+         pixel without a valid variance will be discarded.  This scheme
+         is suitable where the output grid is much coarser than the
+         input grid; if the ratio of pixel sizes is R then a suitable
+         value of params[0] may be R/2.
+
+      }
+      Finally, supplying the following values for \texttt{"} interp\texttt{"}  allows you
+      to implement your own sub-pixel interpolation scheme by means of
+      your own function. You should supply a pointer to this function
+      via the \texttt{"} finterp\texttt{"}  parameter:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_UKERN1: In this scheme, you supply a function to evaluate
+         your own 1-dimensional interpolation kernel, which is then used
+         to perform sub-pixel interpolation (as described above). The
+         function you supply should have the same interface as the
+         fictitious \htmlref{astUkern1}{astUkern1} function (q.v.).  In addition, a value
+         should be given via \texttt{"} params[0]\texttt{"}  to specify the number of
+         neighbouring pixels which are to contribute to each interpolated
+         value (in the same way as for the pre-defined interpolation
+         schemes described above). Other elements of the \texttt{"} params\texttt{"}  array
+         are available to pass values to your interpolation function.
+
+         \sstitem
+         AST\_\_UINTERP: This is a completely general scheme, in which
+         your interpolation function has access to all of the input
+         data. This allows you to implement any interpolation algorithm
+         you choose, which could (for example) be non-linear, or
+         adaptive. In this case, the astResample$<$X$>$ functions play no
+         role in the sub-pixel interpolation process and simply handle
+         the geometrical transformation of coordinates and other
+         housekeeping. The function you supply should have the same
+         interface as the fictitious \htmlref{astUinterp}{astUinterp} function (q.v.). In this
+         case, the \texttt{"} params\texttt{"}  parameter is not used by astResample$<$X$>$, but
+         is available to pass values to your interpolation function.
+      }
+   }
+   \sstdiytopic{
+      Control Flags
+   }{
+      The following flags are defined in the \texttt{"} ast.h\texttt{"}  header file and
+      may be used to provide additional control over the resampling
+      process. Having selected a set of flags, you should supply the
+      bitwise OR of their values via the \texttt{"} flags\texttt{"}  parameter:
+
+      \sstitemlist{
+
+         \sstitem
+         AST\_\_NOBAD: Indicates that any output array elements for which no
+         resampled value could be obtained should be left set to the value
+         they had on entry to this function. If this flag is not supplied,
+         such output array elements are set to the value supplied for
+         parameter \texttt{"} badval\texttt{"} . Note, this flag cannot be used in conjunction
+         with the AST\_\_CONSERVEFLUX flag (an error will be reported if both
+         flags are specified).
+
+         \sstitem
+         AST\_\_URESAMP1, 2, 3 \& 4: A set of four flags which are
+         reserved for your own use. They may be used to pass private
+         information to any sub-pixel interpolation function which you
+         implement yourself. They are ignored by all the pre-defined
+         interpolation schemes.
+
+         \sstitem
+         AST\_\_USEBAD: Indicates that there may be bad pixels in the
+         input array(s) which must be recognised by comparing with the
+         value given for \texttt{"} badval\texttt{"}  and propagated to the output array(s).
+         If this flag is not set, all input values are treated literally
+         and the \texttt{"} badval\texttt{"}  value is only used for flagging output array
+         values.
+
+         \sstitem
+         AST\_\_CONSERVEFLUX: Indicates that the output pixel values should
+         be scaled in such a way as to preserve (approximately) the total data
+         value in a feature on the sky. Without this flag, each output pixel
+         value represents an instantaneous sample of the input data values at
+         the corresponding input position. This is appropriate if the input
+         data represents the spatial density of some quantity (e.g. surface
+         brightness in Janskys per square arc-second) because the output
+         pixel values will have the same normalisation and units as the
+         input pixel values. However, if the input data values represent
+         flux (or some other physical quantity) per pixel, then the
+         AST\_\_CONSERVEFLUX flag could be used. This causes each output
+         pixel value to be scaled by the ratio of the output pixel size to
+         the input pixel size.
+
+      }
+      This flag can only be used if the Mapping is successfully approximated
+      by one or more linear transformations. Thus an error will be reported
+      if it used when the
+      \texttt{"} tol\texttt{"}  parameter
+      is set to zero (which stops the use of linear approximations), or
+      if the Mapping is too non-linear to be approximated by a piece-wise
+      linear transformation. The ratio of output to input pixel size is
+      evaluated once for each panel of the piece-wise linear approximation to
+      the Mapping, and is assumed to be constant for all output pixels in the
+      panel. The scaling factors for adjacent panels will in general
+      differ slightly, and so the joints between panels may be visible when
+      viewing the output image at high contrast. If this is a problem,
+      reduce the value of the
+      \texttt{"} tol\texttt{"}  parameter
+      until the difference between adjacent panels is sufficiently small
+      to be insignificant.
+
+      Note, this flag cannot be used in conjunction with the AST\_\_NOBAD
+      flag (an error will be reported if both flags are specified).
+   }
+   \sstdiytopic{
+      Propagation of Missing Data
+   }{
+      Unless the AST\_\_NOBAD flag is specified, instances of missing data
+      (bad pixels) in the output grid are
+      identified by occurrences of the \texttt{"} badval\texttt{"}  value in the \texttt{"} out\texttt{"}
+      array. These may be produced if any of the following happen:
+
+      \sstitemlist{
+
+         \sstitem
+         The input position (the transformed position of the output
+         pixel\texttt{'} s centre) lies outside the boundary of the grid of input
+         pixels.
+
+         \sstitem
+         The input position lies inside the boundary of a bad input
+         pixel. In this context, an input pixel is considered bad if its
+         data value is equal to \texttt{"} badval\texttt{"}  and the AST\_\_USEBAD flag is
+         set via the \texttt{"} flags\texttt{"}  parameter.
+         (Positions which have half-integral coordinate values, and
+         therefore lie on a pixel boundary, are regarded as lying within
+         the pixel with the larger, i.e. more positive, index.)
+
+         \sstitem
+         The set of neighbouring input pixels (excluding those which
+         are bad) is unsuitable for calculating an interpolated
+         value. Whether this is true may depend on the sub-pixel
+         interpolation scheme in use.
+
+         \sstitem
+         The interpolated value lies outside the range which can be
+         represented using the data type of the \texttt{"} out\texttt{"}  array.
+
+      }
+      In addition, associated output variance estimates (if
+      calculated) may be declared bad and flagged with the \texttt{"} badval\texttt{"}
+      value in the \texttt{"} out\_var\texttt{"}  array under any of the following
+      circumstances:
+
+      \sstitemlist{
+
+         \sstitem
+         The associated resampled data value (in the \texttt{"} out\texttt{"}  array) is bad.
+
+         \sstitem
+         The set of neighbouring input pixels which contributed to the
+         output data value do not all have valid variance estimates
+         associated with them. In this context, an input variance
+         estimate may be regarded as bad either because it has the value
+         \texttt{"} badval\texttt{"}  (and the AST\_\_USEBAD flag is set), or because it is
+         negative.
+
+         \sstitem
+         The set of neighbouring input pixels for which valid variance
+         values are available is unsuitable for calculating an overall
+         variance value. Whether this is true may depend on the sub-pixel
+         interpolation scheme in use.
+
+         \sstitem
+         The variance value lies outside the range which can be
+         represented using the data type of the \texttt{"} out\_var\texttt{"}  array.
+
+      }
+      If the AST\_\_NOBAD flag is specified via
+      parameter \texttt{"} flags\texttt{"} ,
+      then output array elements that would otherwise be set to
+      \texttt{"} badval\texttt{"}
+      are instead left holding the value they had on entry to this
+      function. The number of such array elements is returned as
+      the function value.
+   }
+}
+\sstroutine{
+   astResolve
+}{
+   Resolve a vector into two orthogonal components
+}{
+   \sstdescription{
+      This function resolves a vector into two perpendicular components.
+      The vector from point 1 to point 2 is used as the basis vector.
+      The vector from point 1 to point 3 is resolved into components
+      parallel and perpendicular to this basis vector. The lengths of the
+      two components are returned, together with the position of closest
+      aproach of the basis vector to point 3.
+   }
+   \sstsynopsis{
+      void astResolve( AstFrame $*$this, const double point1[],
+                       const double point2[], const double point3[],
+                       double point4[], double $*$d1, double $*$d2 );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{Frame}{Frame}.
+      }
+      \sstsubsection{
+         point1
+      }{
+         An array of double, with one element for each Frame axis
+         (\htmlref{Naxes}{Naxes} attribute). This marks the start of the basis vector,
+         and of the vector to be resolved.
+      }
+      \sstsubsection{
+         point2
+      }{
+         An array of double, with one element for each Frame axis
+         (Naxes attribute). This marks the end of the basis vector.
+      }
+      \sstsubsection{
+         point3
+      }{
+         An array of double, with one element for each Frame axis
+         (Naxes attribute). This marks the end of the vector to be
+         resolved.
+      }
+      \sstsubsection{
+         point4
+      }{
+         An array of double, with one element for each Frame axis
+         in which the coordinates of the point of closest approach of the
+         basis vector to point 3 will be returned.
+      }
+      \sstsubsection{
+         d1
+      }{
+         The address of a location at which to return the distance from
+         point 1 to point 4 (that is, the length of the component parallel
+         to the basis vector). Positive values are in the same sense as
+         movement from point 1 to point 2.
+      }
+      \sstsubsection{
+         d2
+      }{
+         The address of a location at which to return the distance from
+         point 4 to point 3 (that is, the length of the component
+         perpendicular to the basis vector). The value is always positive.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Each vector used in this function is the path of
+         shortest distance between two points, as defined by the
+         \htmlref{astDistance}{astDistance} function.
+
+         \sstitem
+         This function will return \texttt{"} bad\texttt{"}  coordinate values (AST\_\_BAD)
+         if any of the input coordinates has this value, or if the required
+         output values are undefined.
+      }
+   }
+}
+\sstroutine{
+   astRetainFits
+}{
+   Indicate that the current card in a FitsChan should be retained
+}{
+   \sstdescription{
+      This function
+      stores a flag with the current card in the \htmlref{FitsChan}{FitsChan} indicating that
+      the card should not be removed from the FitsChan when an \htmlref{Object}{Object} is
+      read from the FitsChan using
+      \htmlref{astRead}{astRead}.
+
+      Cards that have not been flagged in this way are removed when a
+      read operation completes succesfully, but only if the card was used
+      in the process of creating the returned AST Object. Any cards that
+      are irrelevant to the creation of the AST Object are retained whether
+      or not they are flagged.
+   }
+   \sstsynopsis{
+      void astRetainFits( AstFitsChan $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function returns without action if the FitsChan is
+         initially positioned at the \texttt{"} end-of-file\texttt{"}  (i.e. if the \htmlref{Card}{Card}
+         attribute exceeds the number of cards in the FitsChan).
+
+         \sstitem
+         The current card is not changed by this function.
+      }
+   }
+}
+\sstroutine{
+   astSame
+}{
+   Test if two AST pointers refer to the same Object
+}{
+   \sstdescription{
+      This function returns a boolean result (0 or 1) to indicate
+      whether two pointers refer to the same \htmlref{Object}{Object}.
+   }
+   \sstsynopsis{
+      int astSame( AstObject $*$this,  AstObject $*$that )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the first Object.
+      }
+      \sstsubsection{
+         that
+      }{
+         Pointer to the second Object.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSame()
+      }{
+         One if the two pointers refer to the same Object, otherwise zero.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Two independent Objects that happen to be identical are not
+         considered to be the same Object by this function.
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astSelectorMap
+}{
+   Create a SelectorMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SelectorMap}{SelectorMap} and optionally initialises
+      its attributes.
+
+      A SelectorMap is a \htmlref{Mapping}{Mapping} that identifies which \htmlref{Region}{Region} contains
+      a given input position.
+
+      A SelectorMap encapsulates a number of Regions that all have the same
+      number of axes and represent the same coordinate \htmlref{Frame}{Frame}. The number of
+      inputs (\htmlref{Nin}{Nin} attribute) of the SelectorMap equals the number of axes
+      spanned by one of the encapsulated Region. All SelectorMaps have only
+      a single output. SelectorMaps do not define an inverse transformation.
+
+      For each input position, the forward transformation of a SelectorMap
+      searches through the encapsulated Regions (in the order supplied when
+      the SelectorMap was created) until a Region is found which contains
+      the input position. The index associated with this Region is
+      returned as the SelectorMap output value (the index value is the
+      position of the Region within the list of Regions supplied when the
+      SelectorMap was created, starting at 1 for the first Region). If an
+      input position is not contained within any Region, a value of zero is
+      returned by the forward transformation.
+
+      If a compound Mapping contains a SelectorMap in series with its own
+      inverse, the combination of the two adjacent SelectorMaps will be
+      replaced by a \htmlref{UnitMap}{UnitMap} when the compound Mapping is simplified using
+      \htmlref{astSimplify}{astSimplify}.
+
+      In practice, SelectorMaps are often used in conjunction with SwitchMaps.
+   }
+   \sstsynopsis{
+      AstSelectorMap $*$astSelectorMap( int nreg, AstRegion $*$regs[],
+                                      double badval, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         nreg
+      }{
+         The number of supplied Regions.
+      }
+      \sstsubsection{
+         regs
+      }{
+         An array of pointers to the Regions. All the supplied Regions must
+         relate to the same coordinate Frame. The number of axes in this
+         coordinate Frame defines the number of inputs for the SelectorMap.
+      }
+      \sstsubsection{
+         badval
+      }{
+         The value to be returned by the forward transformation of the
+         SelectorMap for any input positions that have a bad (AST\_\_BAD)
+         value on any axis.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new SelectorMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSelectorMap()
+      }{
+         A pointer to the new SelectorMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Deep copies are taken of the supplied Regions. This means that
+         any subsequent changes made to the component Regions using the
+         supplied pointers will have no effect on the SelectorMap.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astSet
+}{
+   Set attribute values for an Object
+}{
+   \sstdescription{
+      This function assigns a set of attribute values to an \htmlref{Object}{Object},
+      over-riding any previous values. The attributes and their new
+      values are specified via a character string, which should
+      contain a comma-separated list of the form:
+
+         \texttt{"} attribute\_1 = value\_1, attribute\_2 = value\_2, ... \texttt{"}
+
+      where \texttt{"} attribute\_n\texttt{"}  specifies an attribute name, and the value
+      to the right of each \texttt{"} =\texttt{"}  sign should be a suitable textual
+      representation of the value to be assigned. This value will be
+      interpreted according to the attribute\texttt{'} s data type.
+
+      The string supplied may also contain \texttt{"} printf\texttt{"} -style format
+      specifiers, identified by \texttt{"} \%\texttt{"}  signs in the usual way. If
+      present, these will be substituted by values supplied as
+      additional optional arguments (using the normal \texttt{"} printf\texttt{"}  rules)
+      before the string is used.
+   }
+   \sstsynopsis{
+      void astSet( AstObject $*$this, const char $*$settings, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         settings
+      }{
+         Pointer to a null-terminated character string containing a
+         comma-separated list of attribute settings in the form described
+         above.
+      }
+      \sstsubsection{
+         ...
+      }{
+         Optional additional arguments which supply values to be
+         substituted for any \texttt{"} printf\texttt{"} -style format specifiers that
+         appear in the \texttt{"} settings\texttt{"}  string.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         astSet( map, \texttt{"} \htmlref{Report}{Report} = 1, \htmlref{Zoom}{Zoom} = 25.0\texttt{"}  );
+      }{
+         Sets the Report attribute for Object \texttt{"} map\texttt{"}  to the value 1 and
+         the Zoom attribute to 25.0.
+      }
+      \sstexamplesubsection{
+         astSet( frame, \texttt{"} Label( \%d ) =Offset along axis \%d\texttt{"} , axis, axis );
+      }{
+         Sets the \htmlref{Label(axis)}{Label(axis)} attribute for Object \texttt{"} frame\texttt{"}  to a
+         suitable string, where the axis number is obtained from
+         \texttt{"} axis\texttt{"} , a variable of type int.
+      }
+      \sstexamplesubsection{
+         astSet( frame, \texttt{"} \htmlref{Title}{Title} =\%s\texttt{"} , mystring );
+      }{
+         Sets the Title attribute for Object \texttt{"} frame\texttt{"}  to the contents of
+         the string \texttt{"} mystring\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         White space may also surround attribute values, where it will
+         generally be ignored (except for string-valued attributes where
+         it is significant and forms part of the value to be assigned).
+
+         \sstitem
+         To include a literal comma in the value assigned to an attribute,
+         the whole attribute value should be enclosed in quotation markes.
+         Alternatively, you can use \texttt{"} \%s\texttt{"}  format and supply the value as a
+         separate additional argument to astSet (or use the astSetC
+         function instead).
+
+         \sstitem
+         The same procedure may be adopted if \texttt{"} \%\texttt{"}  signs are to be included
+         and are not to be interpreted as format specifiers (alternatively,
+         the \texttt{"} printf\texttt{"}  convention of writing \texttt{"} \%\%\texttt{"}  may be used).
+
+         \sstitem
+         An error will result if an attempt is made to set a value for
+         a read-only attribute.
+      }
+   }
+}
+\sstroutine{
+   astSet$<$X$>$
+}{
+   Set an attribute value for an Object
+}{
+   \sstdescription{
+      This is a family of functions which set a specified attribute
+      value for an \htmlref{Object}{Object} using one of several different data
+      types. The type is selected by replacing $<$X$>$ in the function name
+      by C, D, F, I or L, to supply a value in const char$*$ (i.e. string),
+      double, float, int, or long format, respectively.
+
+      If possible, the value you supply is converted to the type of
+      the attribute. If conversion is not possible, an error will
+      result.
+   }
+   \sstsynopsis{
+      void astSet$<$X$>$( AstObject $*$this, const char $*$attrib, $<$X$>$type value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         attrib
+      }{
+         Pointer to a null-terminated character string containing the
+         name of the attribute whose value is to be set.
+      }
+      \sstsubsection{
+         value
+      }{
+         The value to be set for the attribute, in the data type corresponding
+         to $<$X$>$ (or, in the case of astSetC, a pointer to a null-terminated
+         character string containing this value).
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         These functions apply to all Objects.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         astSetI( frame, \texttt{"} Preserve\texttt{"} , 1 );
+      }{
+         Sets the Preserve attribute value for Object \texttt{"} frame\texttt{"}  to 1.
+      }
+      \sstexamplesubsection{
+         astSetC( plot, \texttt{"} Format(1)\texttt{"} , \texttt{"} \%.2g\texttt{"}  );
+      }{
+         Sets the Format(1) attribute value for Object \texttt{"} plot\texttt{"}  to the
+         character string \texttt{"} \%.2g\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         An error will result if an attempt is made to set a value for
+         a read-only attribute.
+      }
+   }
+}
+\sstroutine{
+   astSetActiveUnit
+}{
+   Specify how the Unit attribute should be used
+}{
+   \sstdescription{
+      This function
+      sets the current value of the ActiveUnit flag for a \htmlref{Frame}{Frame}, which
+      controls how the Frame behaves when it is used (by
+      \htmlref{astFindFrame}{astFindFrame} or \htmlref{astConvert}{astConvert})
+      to match another Frame. If the ActiveUnit flag is set in both
+      template and target Frames then the returned \htmlref{Mapping}{Mapping} takes into account
+      any differences in axis units. The default value for simple Frames is
+      zero, which preserves the behaviour of versions of AST prior to
+      version 2.0.
+
+      If the ActiveUnit flag of either Frame is
+      zero,
+      then the Mapping will ignore any difference in the Unit attributes of
+      corresponding template and target axes. In this mode, the Unit
+      attributes are purely descriptive commentary for the benefit of
+      human readers and do not influence the Mappings between Frames.
+      This is the behaviour which all Frames had in older version of AST,
+      prior to the introduction of this attribute.
+
+      If the ActiveUnit flag of both Frames is
+      non-zero,
+      then the Mapping from template to target will take account of any
+      difference in the axis Unit attributes, where-ever possible. For
+      instance, if corresponding target and template axes have Unit strings of
+      \texttt{"} km\texttt{"}  and \texttt{"} m\texttt{"} , then the \htmlref{FrameSet}{FrameSet} class will use a \htmlref{ZoomMap}{ZoomMap} to connect
+      them which introduces a scaling of 1000. If no Mapping can be found
+      between the corresponding units string, then an error is reported.
+      In this mode, it is assumed that values of the Unit attribute conform
+      to the syntax for units strings described in the FITS WCS Paper I
+      \texttt{"} Representations of world coordinates in FITS\texttt{"}  (Greisen \& Calabretta).
+      Particularly, any of the named unit symbols, functions, operators or
+      standard multiplier prefixes listed within that paper can be used within
+      a units string. A units string may contain symbols for unit which are
+      not listed in the FITS paper, but transformation to any other units
+      will then not be possible (except to units which depend only on the
+      same unknown units - thus \texttt{"} flops\texttt{"}  can be transformed to \texttt{"} Mflops\texttt{"}
+      even though \texttt{"} flops\texttt{"}  is not a standard FITS unit symbol).
+
+      A range of common non-standard variations of unit names and multiplier
+      prefixes are also allowed, such as adding an \texttt{"} s\texttt{"}  to the end of Angstrom,
+      using a lower case \texttt{"} a\texttt{"}  at the start of \texttt{"} angstrom\texttt{"} , \texttt{"} micron\texttt{"}  instead of
+      \texttt{"} um\texttt{"} , \texttt{"} sec\texttt{"}  instead of \texttt{"} s\texttt{"} , etc.
+
+      If the ActiveUnit flag is non-zero, setting a new Unit value for an
+      axis may also change its Label and Symbol attributes. For instance, if
+      an axis has Unit \texttt{"} Hz\texttt{"}  and Label \texttt{"} frequency\texttt{"} , then changing its Unit to
+      \texttt{"} log(Hz)\texttt{"}  will change its Label to \texttt{"} log( frequency )\texttt{"} . In addition,
+      the \htmlref{Axis}{Axis} Format attribute will be cleared when-ever a new value
+      is assigned to the Unit attribute.
+
+      Note, if a non-zero value is set for the ActiveUnit flag, then changing a
+      Unit value for the current Frame within a FrameSet will result in the
+      Frame being re-mapped (that is, the Mappings which define the
+      relationships between Frames within the FrameSet will be modified to
+      take into account the change in Units).
+   }
+   \sstsynopsis{
+      void astSetActiveUnit( AstFrame $*$this, int value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         value
+      }{
+         The new value to use.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The ActiveUnit flag for a SkyFrame is always 0 (any value
+         supplied using this function is ignored).
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         The ActiveUnit flag for a SpecFrame is always 1 (any value
+         supplied using this function is ignored).
+      }
+      \sstsubsection{
+         \htmlref{FluxFrame}{FluxFrame}
+      }{
+         The ActiveUnit flag for a FluxFrame is always 1 (any value
+         supplied using this function is ignored).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The default ActiveUnit flag for a CmpFrame is 1 if both of the
+         component Frames are using active units, and zero otherwise. When
+         a new value is set for the ActiveUnit flag, the flag value
+         is propagated to the component Frames. This change will be
+         reflected through all references to the component Frames, not
+         just those encapsulated within the CmpFrame.
+      }
+      \sstsubsection{
+         \htmlref{Region}{Region}:
+      }{
+         Regions always use active units if possible.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The ActiveUnit flag resembles a Frame attribute, except that it
+         cannot be tested or cleared, and it cannot be accessed using the
+         generic \htmlref{astGet$<$X$>$}{astGet$<$X$>$} and \htmlref{astSet$<$X$>$}{astSet$<$X$>$} functions.
+
+         \sstitem
+         The \htmlref{astGetActiveUnit}{astGetActiveUnit} function can be used to retrieve the current
+         value of the ActiveUnit flag.
+      }
+   }
+}
+\sstroutine{
+   astSetFits$<$X$>$
+}{
+   Store a keyword value in a FitsChan
+}{
+   \sstdescription{
+      This is a family of functions which store values for named keywords
+      within a \htmlref{FitsChan}{FitsChan} at the current card position. The supplied keyword
+      value can either over-write an existing keyword value, or can be
+      inserted as a new header card into the FitsChan.
+
+      The keyword data type is selected by replacing $<$X$>$ in the function name
+      by one of the following strings representing the recognised FITS data
+
+      types:
+
+      \sstitemlist{
+
+         \sstitem
+         CF - Complex floating point values.
+
+         \sstitem
+         CI - Complex integer values.
+
+         \sstitem
+         F  - Floating point values.
+
+         \sstitem
+         I  - Integer values.
+
+         \sstitem
+         L  - Logical (i.e. boolean) values.
+
+         \sstitem
+         S  - String values.
+
+         \sstitem
+         CN - A \texttt{"} CONTINUE\texttt{"}  value, these are treated like string values, but
+                are encoded without an equals sign.
+
+      }
+      The data type of the \texttt{"} value\texttt{"}  parameter depends on $<$X$>$ as follows:
+
+      \sstitemlist{
+
+         \sstitem
+         CF - \texttt{"} double $*$\texttt{"}  (a pointer to a 2 element array holding the real and
+                imaginary parts of the complex value).
+
+         \sstitem
+         CI - \texttt{"} int $*$\texttt{"}  (a pointer to a 2 element array holding the real and
+                imaginary parts of the complex value).
+
+         \sstitem
+         F  - \texttt{"} double\texttt{"} .
+
+         \sstitem
+         I  - \texttt{"} int\texttt{"} .
+
+         \sstitem
+         L  - \texttt{"} int\texttt{"} .
+
+         \sstitem
+         S  - \texttt{"} const char $*$\texttt{"} .
+
+         \sstitem
+         CN - \texttt{"} const char $*$\texttt{"} .
+      }
+   }
+   \sstsynopsis{
+      void astSetFits$<$X$>$( AstFitsChan $*$this, const char $*$name, $<$X$>$type value,
+                          const char $*$comment, int overwrite )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         name
+      }{
+         Pointer to a null-terminated character string
+         containing the FITS keyword name. This may be a complete FITS
+         header card, in which case the keyword to use is extracted from
+         it. No more than 80 characters are read from this string.
+      }
+      \sstsubsection{
+         value
+      }{
+         The keyword value to store with the named keyword. The data type
+         of this parameter depends on $<$X$>$ as described above.
+      }
+      \sstsubsection{
+         comment
+      }{
+         A pointer to a null terminated string
+         holding a comment to associated with the keyword.
+         If a NULL pointer or
+         a blank string is supplied, then any comment included in the string
+         supplied for the
+         \texttt{"} name\texttt{"}  parameter is used instead. If \texttt{"} name\texttt{"}
+         contains no comment, then any existing comment in the card being
+         over-written is retained. Otherwise, no comment is stored with
+         the card.
+      }
+      \sstsubsection{
+         overwrite
+      }{
+         If non-zero,
+         the new card formed from the supplied keyword name, value and comment
+         string over-writes the current card, and the current card is
+         incremented to refer to the next card (see the \texttt{"} \htmlref{Card}{Card}\texttt{"}  attribute). If
+         zero,
+         the new card is inserted in front of the current card and the current
+         card is left unchanged. In either case, if the current card on entry
+         points to the \texttt{"} end-of-file\texttt{"} , the new card is appended to the end of
+         the list.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The
+         function \htmlref{astSetFitsU}{astSetFitsU}
+         can be used to indicate that no value is associated with a keyword.
+
+         \sstitem
+         The
+         function \htmlref{astSetFitsCM}{astSetFitsCM}
+         can be used to store a pure comment card (i.e. a card with a blank
+         keyword).
+
+         \sstitem
+         To assign a new value for an existing keyword within a FitsChan,
+         first find the card describing the keyword using \htmlref{astFindFits}{astFindFits}, and
+         then use one of the astSetFits$<$X$>$ family to over-write the old value.
+
+         \sstitem
+         If, on exit, there are no cards following the card written by
+         this function, then the current card is left pointing at the
+         \texttt{"} end-of-file\texttt{"} .
+
+         \sstitem
+         An error will be reported if the keyword name does not conform
+         to FITS requirements.
+      }
+   }
+}
+\sstroutine{
+   astSetFitsCM
+}{
+   Store a comment card in a FitsChan
+}{
+   \sstdescription{
+      This
+      function
+      stores a comment card ( i.e. a card with no keyword name or equals
+      sign) within a \htmlref{FitsChan}{FitsChan} at the current card position. The new card
+      can either over-write an existing card, or can be inserted as a new
+      card into the FitsChan.
+   }
+   \sstsynopsis{
+      void astSetFitsCM( AstFitsChan $*$this, const char $*$comment,
+                         int overwrite )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         comment
+      }{
+         A pointer to a null terminated string
+         holding the text of the comment card.
+         If a NULL pointer or
+         a blank string is supplied, then a totally blank card is produced.
+      }
+      \sstsubsection{
+         overwrite
+      }{
+         If non-zero,
+         the new card over-writes the current card, and the current card is
+         incremented to refer to the next card (see the \texttt{"} \htmlref{Card}{Card}\texttt{"}  attribute). If
+         zero,
+         the new card is inserted in front of the current card and the current
+         card is left unchanged. In either case, if the current card on entry
+         points to the \texttt{"} end-of-file\texttt{"} , the new card is appended to the end of
+         the list.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If, on exit, there are no cards following the card written by
+         this function, then the current card is left pointing at the
+         \texttt{"} end-of-file\texttt{"} .
+      }
+   }
+}
+\sstroutine{
+   astSetFitsU
+}{
+   Store an undefined keyword value in a FitsChan
+}{
+   \sstdescription{
+      This
+      function
+      stores an undefined value for a named keyword within
+      a \htmlref{FitsChan}{FitsChan} at the current card position. The new undefined value
+      can either over-write an existing keyword value, or can be inserted
+      as a new header card into the FitsChan.
+   }
+   \sstsynopsis{
+      void astSetFitsU( AstFitsChan $*$this, const char $*$name,
+                        const char $*$comment, int overwrite )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         name
+      }{
+         Pointer to a null-terminated character string
+         containing the FITS keyword name. This may be a complete FITS
+         header card, in which case the keyword to use is extracted from
+         it. No more than 80 characters are read from this string.
+      }
+      \sstsubsection{
+         comment
+      }{
+         A pointer to a null terminated string
+         holding a comment to associated with the keyword.
+         If a NULL pointer or
+         a blank string is supplied, then any comment included in the string
+         supplied for the
+         \texttt{"} name\texttt{"}  parameter is used instead. If \texttt{"} name\texttt{"}
+         contains no comment, then any existing comment in the card being
+         over-written is retained. Otherwise, no comment is stored with
+         the card.
+      }
+      \sstsubsection{
+         overwrite
+      }{
+         If non-zero,
+         the new card formed from the supplied keyword name and comment
+         string over-writes the current card, and the current card is
+         incremented to refer to the next card (see the \texttt{"} \htmlref{Card}{Card}\texttt{"}  attribute). If
+         zero,
+         the new card is inserted in front of the current card and the current
+         card is left unchanged. In either case, if the current card on entry
+         points to the \texttt{"} end-of-file\texttt{"} , the new card is appended to the end of
+         the list.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If, on exit, there are no cards following the card written by
+         this function, then the current card is left pointing at the
+         \texttt{"} end-of-file\texttt{"} .
+
+         \sstitem
+         An error will be reported if the keyword name does not conform
+         to FITS requirements.
+      }
+   }
+}
+\sstroutine{
+   astSetRefPos
+}{
+   Set the reference position in a specified celestial coordinate system
+}{
+   \sstdescription{
+      This function
+      sets the reference position (see attributes \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec}) using
+      axis values (in radians) supplied within the celestial coordinate
+      system represented by a supplied \htmlref{SkyFrame}{SkyFrame}.
+   }
+   \sstsynopsis{
+      void astSetRefPos( AstSpecFrame $*$this, AstSkyFrame $*$frm, double lon,
+                         double lat )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the \htmlref{SpecFrame}{SpecFrame}.
+      }
+      \sstsubsection{
+         frm
+      }{
+         Pointer to the SkyFrame which defines the celestial coordinate
+         system in which the longitude and latitude values are supplied.
+         If NULL
+         is supplied, then the supplied longitude and latitude values are
+         assumed to be FK5 J2000 RA and Dec values.
+      }
+      \sstsubsection{
+         lon
+      }{
+         The longitude of the reference point, in the coordinate system
+         represented by the supplied SkyFrame (radians).
+      }
+      \sstsubsection{
+         lat
+      }{
+         The latitude of the reference point, in the coordinate system
+         represented by the supplied SkyFrame (radians).
+      }
+   }
+}
+\sstroutine{
+   astSetStatus
+}{
+   Set the AST error status to an explicit value
+}{
+   \sstdescription{
+      This function sets the AST error status to the value supplied.
+      It does not cause any error message to be produced and should
+      not be used as part of normal error reporting. Its purpose is
+      simply to communicate to AST that an error has occurred in some
+      other item of software.
+
+      For example, a source or sink function supplied as an argument
+      to \htmlref{astChannel}{astChannel} or \htmlref{astFitsChan}{astFitsChan} might use this to signal that an
+      input/output error has occurred. AST could then respond by
+      terminating the current read or write operation.
+   }
+   \sstsynopsis{
+      void astSetStatus( int status\_value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         status\_value
+      }{
+         The new error status value to be set.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the AST error status is set to an error value, most AST
+         functions will not execute and will simply return without
+         action. To clear the error status and restore normal behaviour,
+         use \htmlref{astClearStatus}{astClearStatus}.
+      }
+   }
+}
+\sstroutine{
+   astSetUnc
+}{
+   Store uncertainty information in a Region
+}{
+   \sstdescription{
+      Each \htmlref{Region}{Region} (of any class) can have an \texttt{"} uncertainty\texttt{"}  which specifies
+      the uncertainties associated with the boundary of the Region. This
+      information is supplied in the form of a second Region. The uncertainty
+      in any point on the boundary of a Region is found by shifting the
+      associated \texttt{"} uncertainty\texttt{"}  Region so that it is centred at the boundary
+      point being considered. The area covered by the shifted uncertainty
+      Region then represents the uncertainty in the boundary position.
+      The uncertainty is assumed to be the same for all points.
+
+      The uncertainty is usually specified when the Region is created, but
+      this
+      function
+      allows it to be changed at any time.
+   }
+   \sstsynopsis{
+      void astSetUnc( AstRegion $*$this, AstRegion $*$unc )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Region which is to be assigned a new uncertainty.
+      }
+      \sstsubsection{
+         unc
+      }{
+         Pointer to the new uncertainty Region. This must be of a class for
+         which all instances are centro-symetric (e.g. \htmlref{Box}{Box}, \htmlref{Circle}{Circle}, \htmlref{Ellipse}{Ellipse},
+         etc.) or be a \htmlref{Prism}{Prism} containing centro-symetric component Regions.
+         A deep copy of the supplied Region will be taken, so subsequent
+         changes to the uncertainty Region using the supplied pointer will
+         have no effect on the Region
+         \texttt{"} this\texttt{"} .
+      }
+   }
+}
+\sstroutine{
+   astShiftMap
+}{
+   Create a ShiftMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{ShiftMap}{ShiftMap} and optionally initialises its
+      attributes.
+
+      A ShiftMap is a linear \htmlref{Mapping}{Mapping} which shifts each axis by a
+      specified constant value.
+   }
+   \sstsynopsis{
+      AstShiftMap $*$astShiftMap( int ncoord, const double shift[],
+                                const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ncoord
+      }{
+         The number of coordinate values for each point to be
+         transformed (i.e. the number of dimensions of the space in
+         which the points will reside). The same number is applicable
+         to both input and output points.
+      }
+      \sstsubsection{
+         shift
+      }{
+         An array containing the values to be added on to the input
+         coordinates in order to create the output coordinates. A separate
+         value should be supplied for each coordinate.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new ShiftMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astShiftMap()
+      }{
+         A pointer to the new ShiftMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astShow
+}{
+   Display a textual representation of an Object on standard output
+}{
+   \sstdescription{
+      This function displays a textual description of any AST \htmlref{Object}{Object}
+      on standard output. It is provided primarily as an aid to
+      debugging.
+   }
+   \sstsynopsis{
+      void astShow( AstObject $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object to be displayed.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+}
+\sstroutine{
+   astShowFits
+}{
+   Display the contents of a FitsChan on standard output
+}{
+   \sstdescription{
+      This function
+      formats and displays all the cards in a \htmlref{FitsChan}{FitsChan} on standard output.
+   }
+   \sstsynopsis{
+      void astShowFits( AstFitsChan $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+   }
+}
+\sstroutine{
+   astShowMesh
+}{
+   Display a mesh of points covering the surface of a Region
+}{
+   \sstdescription{
+      This function
+      writes a table to standard output containing the axis values at a
+      mesh of points covering the surface of the supplied \htmlref{Region}{Region}. Each row
+      of output contains a tab-separated list of axis values, one for
+      each axis in the \htmlref{Frame}{Frame} encapsulated by the Region. The number of
+      points in the mesh is determined by the \htmlref{MeshSize}{MeshSize} attribute.
+
+      The table is preceded by a given title string, and followed by a
+      single line containing the word \texttt{"} ENDMESH\texttt{"} .
+   }
+   \sstsynopsis{
+      void astShowMesh( AstRegion $*$this, int format, const char $*$ttl )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Region.
+      }
+      \sstsubsection{
+         format
+      }{
+         A boolean value indicating if the displayed axis values should
+         be formatted according to the Format attribute associated with
+         the Frame\texttt{'} s axis. Otherwise, they are displayed as simple
+         floating point values.
+      }
+      \sstsubsection{
+         ttl
+      }{
+         A title to display before displaying the first position.
+      }
+   }
+}
+\sstroutine{
+   astSimplify
+}{
+   Simplify a Mapping
+}{
+   \sstdescription{
+      This function simplifies a \htmlref{Mapping}{Mapping} (which may be a compound
+      Mapping such as a \htmlref{CmpMap}{CmpMap}) to eliminate redundant computational
+      steps, or to merge separate steps which can be performed more
+      efficiently in a single operation.
+
+      As a simple example, a Mapping which multiplied coordinates by
+      5, and then multiplied the result by 10, could be simplified to
+      a single step which multiplied by 50. Similarly, a Mapping which
+      multiplied by 5, and then divided by 5, could be reduced to a
+      simple copying operation.
+
+      This function should typically be applied to Mappings which have
+      undergone substantial processing or have been formed by merging
+      other Mappings. It is of potential benefit, for example, in
+      reducing execution time if applied before using a Mapping to
+      transform a large number of coordinates.
+   }
+   \sstsynopsis{
+      AstMapping $*$astSimplify( AstMapping $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the original Mapping.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         This function applies to all Mappings.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         If the supplied Mapping is a FrameSet, the returned Mapping
+         will be a copy of the supplied FrameSet in which all the
+         inter-\htmlref{Frame}{Frame} Mappings have been simplified.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSimplify()
+      }{
+         A new pointer to the (possibly simplified) Mapping.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Mappings that have a set value for their \htmlref{Ident}{Ident} attribute are
+         left unchanged after simplification. This is so that their
+         individual identity is preserved. This restriction does not
+         apply to the simplification of Frames.
+
+         \sstitem
+         This function can safely be applied even to Mappings which
+         cannot be simplified. If no simplification is possible, it
+         behaves exactly like \htmlref{astClone}{astClone} and returns a pointer to the
+         original Mapping.
+
+         \sstitem
+         The Mapping returned by this function may not be independent
+         of the original (even if simplification was possible), and
+         modifying it may therefore result in indirect modification of
+         the original. If a completely independent result is required, a
+         copy should be made using \htmlref{astCopy}{astCopy}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astSkyFrame
+}{
+   Create a SkyFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SkyFrame}{SkyFrame} and optionally initialises
+      its attributes.
+
+      A SkyFrame is a specialised form of \htmlref{Frame}{Frame} which describes
+      celestial longitude/latitude coordinate systems. The particular
+      celestial coordinate system to be represented is specified by
+      setting the SkyFrame\texttt{'} s \htmlref{System}{System} attribute (currently, the default
+      is ICRS) qualified, as necessary, by a mean \htmlref{Equinox}{Equinox} value and/or
+      an \htmlref{Epoch}{Epoch}.
+
+      For each of the supported celestial coordinate systems, a SkyFrame
+      can apply an optional shift of origin to create a coordinate system
+      representing offsets within the celestial coordinate system from some
+      specified point. This offset coordinate system can also be rotated to
+      define new longitude and latitude axes. See attributes SkyRef, \htmlref{SkyRefIs}{SkyRefIs}
+      and SkyRefP
+
+      All the coordinate values used by a SkyFrame are in
+      radians. These may be formatted in more conventional ways for
+      display by using \htmlref{astFormat}{astFormat}.
+   }
+   \sstsynopsis{
+      AstSkyFrame $*$astSkyFrame( const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new SkyFrame. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSkyFrame()
+      }{
+         A pointer to the new SkyFrame.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         frame = astSkyFrame( \texttt{"} \texttt{"}  );
+      }{
+         Creates a SkyFrame to describe the default ICRS celestial
+         coordinate system.
+      }
+      \sstexamplesubsection{
+         frame = astSkyFrame( \texttt{"} System = FK5, Equinox = J2005, Digits = 10\texttt{"}  );
+      }{
+         Creates a SkyFrame to describe the FK5 celestial
+         coordinate system, with a mean Equinox of J2005.0.
+         Because especially accurate coordinates will be used,
+         additional precision (10 digits) has been requested. This will
+         be used when coordinate values are formatted for display.
+      }
+      \sstexamplesubsection{
+         frame = astSkyFrame( \texttt{"} System = FK4, Equinox = 1955-sep-2\texttt{"}  );
+      }{
+         Creates a SkyFrame to describe the old FK4 celestial
+         coordinate system.  A default Epoch value (B1950.0) is used,
+         but the mean Equinox value is given explicitly as \texttt{"} 1955-sep-2\texttt{"} .
+      }
+      \sstexamplesubsection{
+         frame = astSkyFrame( \texttt{"} System = GAPPT, Epoch = \%s\texttt{"} , date );
+      }{
+         Creates a SkyFrame to describe the Geocentric Apparent
+         celestial coordinate system. The Epoch value, which specifies
+         the date of observation, is obtained from a date/time string
+         supplied via the string pointer \texttt{"} date\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Currently, the default celestial coordinate system is
+         ICRS. However, this default may change in future as new
+         astrometric standards evolve. The intention is to track the most
+         modern appropriate standard. For this reason, you should use the
+         default only if this is what you intend (and can tolerate any
+         associated slight change in behaviour with future versions of
+         this function). If you intend to use the ICRS system
+         indefinitely, then you should specify it explicitly using an
+         \texttt{"} options\texttt{"}  value of \texttt{"} System=ICRS\texttt{"} .
+
+         \sstitem
+         Whichever celestial coordinate system is represented, it will
+         have two axes.  The first of these will be the longitude axis
+         and the second will be the latitude axis. This order can be
+         changed using \htmlref{astPermAxes}{astPermAxes} if required.
+
+         \sstitem
+         When conversion between two SkyFrames is requested (as when
+         supplying SkyFrames to \htmlref{astConvert}{astConvert}),
+         account will be taken of the nature of the celestial coordinate
+         systems they represent, together with any qualifying mean Equinox or
+         Epoch values, etc. The \htmlref{AlignSystem}{AlignSystem} attribute will also be taken into
+         account. The results will therefore fully reflect the
+         relationship between positions on the sky measured in the two
+         systems.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astSkyOffsetMap
+}{
+   Returns a Mapping which goes from absolute coordinates to offset
+   coordinates
+}{
+   \sstdescription{
+      This function returns a \htmlref{Mapping}{Mapping} in which the forward transformation
+      transforms a position in the coordinate system given by the \htmlref{System}{System}
+      attribute of the supplied \htmlref{SkyFrame}{SkyFrame}, into the offset coordinate system
+      specified by the SkyRef, SkyRefP and \htmlref{SkyRefIs}{SkyRefIs} attributes of the
+      supplied SkyFrame.
+
+      A \htmlref{UnitMap}{UnitMap} is returned if the SkyFrame does not define an offset
+      coordinate system.
+   }
+   \sstsynopsis{
+      AstMapping $*$astSkyOffsetMap( AstSkyFrame $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the SkyFrame.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSkyOffsetMap()
+      }{
+         Pointer to the returned Mapping.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astSlaAdd
+}{
+   Add a celestial coordinate conversion to an SlaMap
+}{
+   \sstdescription{
+      This function adds one of the standard celestial coordinate
+      system conversions provided by the SLALIB Positional Astronomy
+      Library (Starlink User Note SUN/67) to an existing \htmlref{SlaMap}{SlaMap}.
+
+      When an SlaMap is first created (using \htmlref{astSlaMap}{astSlaMap}), it simply
+      performs a unit (null) \htmlref{Mapping}{Mapping}. By using astSlaAdd (repeatedly
+      if necessary), one or more coordinate conversion steps may then
+      be added, which the SlaMap will perform in sequence. This allows
+      multi-step conversions between a variety of celestial coordinate
+      systems to be assembled out of the building blocks provided by
+      SLALIB.
+
+      Normally, if an SlaMap\texttt{'} s \htmlref{Invert}{Invert} attribute is zero (the default),
+      then its forward transformation is performed by carrying out
+      each of the individual coordinate conversions specified by
+      astSlaAdd in the order given (i.e. with the most recently added
+      conversion applied last).
+
+      This order is reversed if the SlaMap\texttt{'} s Invert attribute is
+      non-zero (or if the inverse transformation is requested by any
+      other means) and each individual coordinate conversion is also
+      replaced by its own inverse. This process inverts the overall
+      effect of the SlaMap. In this case, the first conversion to be
+      applied would be the inverse of the one most recently added.
+   }
+   \sstsynopsis{
+      void astSlaAdd( AstSlaMap $*$this, const char $*$cvt, int narg,
+                      const double args[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the SlaMap.
+      }
+      \sstsubsection{
+         cvt
+      }{
+         Pointer to a null-terminated string which identifies the
+         celestial coordinate conversion to be added to the
+         SlaMap. See the \texttt{"} SLALIB Conversions\texttt{"}  section for details of
+         those available.
+      }
+      \sstsubsection{
+         narg
+      }{
+         The number of argument values supplied in the
+         \texttt{"} args\texttt{"}  array.
+      }
+      \sstsubsection{
+         args
+      }{
+         An array containing argument values for the celestial
+         coordinate conversion. The number of arguments required, and
+         hence the number of array elements used, depends on the
+         conversion specified (see the \texttt{"} SLALIB Conversions\texttt{"}
+         section). This array is ignored
+         and a NULL pointer may be supplied
+         if no arguments are needed.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         All coordinate values processed by an SlaMap are in
+         radians. The first coordinate is the celestial longitude and the
+         second coordinate is the celestial latitude.
+
+         \sstitem
+         When assembling a multi-stage conversion, it can sometimes be
+         difficult to determine the most economical conversion path. For
+         example, converting to the standard FK5 coordinate system as an
+         intermediate stage is often sensible in formulating the problem,
+         but may introduce unnecessary extra conversion steps. A solution
+         to this is to include all the steps which are (logically)
+         necessary, but then to use \htmlref{astSimplify}{astSimplify} to simplify the resulting
+         SlaMap. The simplification process will eliminate any steps
+         which turn out not to be needed.
+
+         \sstitem
+         This function does not check to ensure that the sequence of
+         coordinate conversions added to an SlaMap is physically
+         meaningful.
+      }
+   }
+   \sstdiytopic{
+      SLALIB Conversions
+   }{
+      The following strings (which are case-insensitive) may be supplied
+      via the \texttt{"} cvt\texttt{"}  parameter to indicate which celestial coordinate
+      conversion is to be added to the SlaMap. Each string is derived
+      from the name of the SLALIB routine that performs the
+      conversion and the relevant documentation (SUN/67) should be
+      consulted for details.  Where arguments are needed by
+      the conversion, they are listed in parentheses. Values for
+      these arguments should be given, via the \texttt{"} args\texttt{"}  array, in the
+      order indicated. The argument names match the corresponding
+      SLALIB routine arguments and their values should be given using
+      exactly the same units, time scale, calendar, etc. as described
+      in SUN/67:
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} ADDET\texttt{"}  (EQ): Add E-terms of aberration.
+
+         \sstitem
+         \texttt{"} SUBET\texttt{"}  (EQ): Subtract E-terms of aberration.
+
+         \sstitem
+         \texttt{"} PREBN\texttt{"}  (BEP0,BEP1): Apply Bessel-Newcomb pre-IAU 1976 (FK4)
+         precession model.
+
+         \sstitem
+         \texttt{"} PREC\texttt{"}  (EP0,EP1): Apply IAU 1975 (FK5) precession model.
+
+         \sstitem
+         \texttt{"} FK45Z\texttt{"}  (BEPOCH): Convert FK4 to FK5 (no proper motion or parallax).
+
+         \sstitem
+         \texttt{"} FK54Z\texttt{"}  (BEPOCH): Convert FK5 to FK4 (no proper motion or parallax).
+
+         \sstitem
+         \texttt{"} AMP\texttt{"}  (DATE,EQ): Convert geocentric apparent to mean place.
+
+         \sstitem
+         \texttt{"} MAP\texttt{"}  (EQ,DATE): Convert mean place to geocentric apparent.
+
+         \sstitem
+         \texttt{"} ECLEQ\texttt{"}  (DATE): Convert ecliptic coordinates to FK5 J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} EQECL\texttt{"}  (DATE): Convert equatorial FK5 J2000.0 to ecliptic coordinates.
+
+         \sstitem
+         \texttt{"} GALEQ\texttt{"} : Convert galactic coordinates to FK5 J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} EQGAL\texttt{"} : Convert FK5 J2000.0 equatorial to galactic coordinates.
+
+         \sstitem
+         \texttt{"} HFK5Z\texttt{"}  (JEPOCH): Convert ICRS coordinates to FK5 J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} FK5HZ\texttt{"}  (JEPOCH): Convert FK5 J2000.0 equatorial coordinates to ICRS.
+
+         \sstitem
+         \texttt{"} GALSUP\texttt{"} : Convert galactic to supergalactic coordinates.
+
+         \sstitem
+         \texttt{"} SUPGAL\texttt{"} : Convert supergalactic coordinates to galactic.
+
+         \sstitem
+         \texttt{"} J2000H\texttt{"} : Convert dynamical J2000.0 to ICRS.
+
+         \sstitem
+         \texttt{"} HJ2000\texttt{"} : Convert ICRS to dynamical J2000.0.
+
+         \sstitem
+         \texttt{"} R2H\texttt{"}  (LAST): Convert RA to Hour Angle.
+
+         \sstitem
+         \texttt{"} H2R\texttt{"}  (LAST): Convert Hour Angle to RA.
+
+      }
+      For example, to use the \texttt{"} ADDET\texttt{"}  conversion, which takes a single
+      argument EQ, you should consult the documentation for the SLALIB
+      routine SLA\_ADDET. This describes the conversion in detail and
+      shows that EQ is the Besselian epoch of the mean equator and
+      equinox.
+      This value should then be supplied to astSlaAdd in args[0].
+
+      In addition the following strings may be supplied for more complex
+      conversions which do not correspond to any one single SLALIB routine
+      (DIURAB is the magnitude of the diurnal aberration vector in units
+      of \texttt{"} day/(2.PI)\texttt{"} , DATE is the Modified Julian Date of the observation,
+      and (OBSX,OBSY,OBZ) are the Heliocentric-Aries-Ecliptic cartesian
+      coordinates, in metres, of the observer):
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} HPCEQ\texttt{"}  (DATE,OBSX,OBSY,OBSZ): Convert Helioprojective-Cartesian coordinates to J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} EQHPC\texttt{"}  (DATE,OBSX,OBSY,OBSZ): Convert J2000.0 equatorial coordinates to Helioprojective-Cartesian.
+
+         \sstitem
+         \texttt{"} HPREQ\texttt{"}  (DATE,OBSX,OBSY,OBSZ): Convert Helioprojective-Radial coordinates to J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} EQHPR\texttt{"}  (DATE,OBSX,OBSY,OBSZ): Convert J2000.0 equatorial coordinates to Helioprojective-Radial.
+
+         \sstitem
+         \texttt{"} HEEQ\texttt{"}  (DATE): Convert helio-ecliptic coordinates to J2000.0 equatorial.
+
+         \sstitem
+         \texttt{"} EQHE\texttt{"}  (DATE): Convert J2000.0 equatorial coordinates to helio-ecliptic.
+
+         \sstitem
+         \texttt{"} H2E\texttt{"}  (LAT,DIRUAB): Convert horizon coordinates to equatorial.
+
+         \sstitem
+         \texttt{"} E2H\texttt{"}  (LAT,DIURAB): Convert equatorial coordinates to horizon.
+
+      }
+      Note, the \texttt{"} H2E\texttt{"}  and \texttt{"} E2H\texttt{"}  conversions convert between topocentric
+      horizon coordinates (azimuth,elevation), and apparent local equatorial
+      coordinates (hour angle,declination). Thus, the effects of diurnal
+      aberration are taken into account in the conversions but the effects
+      of atmospheric refraction are not.
+   }
+}
+\sstroutine{
+   astSlaMap
+}{
+   Create an SlaMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SlaMap}{SlaMap} and optionally initialises
+      its attributes.
+
+      An SlaMap is a specialised form of \htmlref{Mapping}{Mapping} which can be used to
+      represent a sequence of conversions between standard celestial
+      (longitude, latitude) coordinate systems.
+
+      When an SlaMap is first created, it simply performs a unit
+      (null) Mapping on a pair of coordinates. Using the \htmlref{astSlaAdd}{astSlaAdd}
+      function, a series of coordinate conversion steps may then be
+      added, selected from those provided by the SLALIB Positional
+      Astronomy Library (Starlink User Note SUN/67). This allows
+      multi-step conversions between a variety of celestial coordinate
+      systems to be assembled out of the building blocks provided by
+      SLALIB.
+
+      For details of the individual coordinate conversions available,
+      see the description of the astSlaAdd function.
+   }
+   \sstsynopsis{
+      AstSlaMap $*$astSlaMap( int flags, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         flags
+      }{
+         This parameter is reserved for future use and should currently
+         always be set to zero.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new SlaMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSlaMap()
+      }{
+         A pointer to the new SlaMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes (number of input and output
+         coordinates) for an SlaMap are both equal to 2. The first
+         coordinate is the celestial longitude and the second coordinate
+         is the celestial latitude. All coordinate values are in radians.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astSpecAdd
+}{
+   Add a spectral coordinate conversion to a SpecMap
+}{
+   \sstdescription{
+      This function adds one of the standard spectral coordinate
+      system conversions listed below to an existing \htmlref{SpecMap}{SpecMap}.
+
+      When a SpecMap is first created (using \htmlref{astSpecMap}{astSpecMap}), it simply
+      performs a unit (null) \htmlref{Mapping}{Mapping}. By using astSpecAdd (repeatedly
+      if necessary), one or more coordinate conversion steps may then
+      be added, which the SpecMap will perform in sequence. This allows
+      multi-step conversions between a variety of spectral coordinate
+      systems to be assembled out of the building blocks provided by
+      this class.
+
+      Normally, if a SpecMap\texttt{'} s \htmlref{Invert}{Invert} attribute is zero (the default),
+      then its forward transformation is performed by carrying out
+      each of the individual coordinate conversions specified by
+      astSpecAdd in the order given (i.e. with the most recently added
+      conversion applied last).
+
+      This order is reversed if the SpecMap\texttt{'} s Invert attribute is
+      non-zero (or if the inverse transformation is requested by any
+      other means) and each individual coordinate conversion is also
+      replaced by its own inverse. This process inverts the overall
+      effect of the SpecMap. In this case, the first conversion to be
+      applied would be the inverse of the one most recently added.
+   }
+   \sstsynopsis{
+      void astSpecAdd( AstSpecMap $*$this, const char $*$cvt, int narg,
+                       const double args[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the SpecMap.
+      }
+      \sstsubsection{
+         cvt
+      }{
+         Pointer to a null-terminated string which identifies the
+         spectral coordinate conversion to be added to the
+         SpecMap. See the \texttt{"} Available Conversions\texttt{"}  section for details of
+         those available.
+      }
+      \sstsubsection{
+         narg
+      }{
+         The number of argument values supplied in the
+         \texttt{"} args\texttt{"}  array.
+      }
+      \sstsubsection{
+         args
+      }{
+         An array containing argument values for the spectral
+         coordinate conversion. The number of arguments required, and
+         hence the number of array elements used, depends on the
+         conversion specified (see the \texttt{"} Available Conversions\texttt{"}
+         section). This array is ignored
+         and a NULL pointer may be supplied
+         if no arguments are needed.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When assembling a multi-stage conversion, it can sometimes be
+         difficult to determine the most economical conversion path. For
+         example, when converting between reference frames, converting first
+         to the heliographic reference frame as an intermediate stage is often
+         sensible in formulating the problem, but may introduce unnecessary
+         extra conversion steps. A solution to this is to include all the steps
+         which are (logically) necessary, but then to use
+         \htmlref{astSimplify}{astSimplify} to simplify the resulting
+         SpecMap. The simplification process will eliminate any steps
+         which turn out not to be needed.
+
+         \sstitem
+         This function does not check to ensure that the sequence of
+         coordinate conversions added to a SpecMap is physically
+         meaningful.
+      }
+   }
+   \sstdiytopic{
+      Available Conversions
+   }{
+      The following strings (which are case-insensitive) may be supplied
+      via the \texttt{"} cvt\texttt{"}  parameter to indicate which spectral coordinate
+      conversion is to be added to the SpecMap. Where arguments are needed by
+      the conversion, they are listed in parentheses. Values for
+      these arguments should be given, via the \texttt{"} args\texttt{"}  array, in the
+      order indicated. Units and argument names are described at the end of
+      the list of conversions.
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} FRTOVL\texttt{"}  (RF): Convert frequency to relativistic velocity.
+
+         \sstitem
+         \texttt{"} VLTOFR\texttt{"}  (RF): Convert relativistic velocity to Frequency.
+
+         \sstitem
+         \texttt{"} ENTOFR\texttt{"} : Convert energy to frequency.
+
+         \sstitem
+         \texttt{"} FRTOEN\texttt{"} : Convert frequency to energy.
+
+         \sstitem
+         \texttt{"} WNTOFR\texttt{"} : Convert wave number to frequency.
+
+         \sstitem
+         \texttt{"} FRTOWN\texttt{"} : Convert frequency to wave number.
+
+         \sstitem
+         \texttt{"} WVTOFR\texttt{"} : Convert wavelength (vacuum) to frequency.
+
+         \sstitem
+         \texttt{"} FRTOWV\texttt{"} : Convert frequency to wavelength (vacuum).
+
+         \sstitem
+         \texttt{"} AWTOFR\texttt{"} : Convert wavelength (air) to frequency.
+
+         \sstitem
+         \texttt{"} FRTOAW\texttt{"} : Convert frequency to wavelength (air).
+
+         \sstitem
+         \texttt{"} VRTOVL\texttt{"} : Convert radio to relativistic velocity.
+
+         \sstitem
+         \texttt{"} VLTOVR\texttt{"} : Convert relativistic to radio velocity.
+
+         \sstitem
+         \texttt{"} VOTOVL\texttt{"} : Convert optical to relativistic velocity.
+
+         \sstitem
+         \texttt{"} VLTOVO\texttt{"} : Convert relativistic to optical velocity.
+
+         \sstitem
+         \texttt{"} ZOTOVL\texttt{"} : Convert redshift to relativistic velocity.
+
+         \sstitem
+         \texttt{"} VLTOZO\texttt{"} : Convert relativistic velocity to redshift.
+
+         \sstitem
+         \texttt{"} BTTOVL\texttt{"} : Convert beta factor to relativistic velocity.
+
+         \sstitem
+         \texttt{"} VLTOBT\texttt{"} : Convert relativistic velocity to beta factor.
+
+         \sstitem
+         \texttt{"} USF2HL\texttt{"}  (VOFF,RA,DEC): Convert frequency from a user-defined
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2US\texttt{"}  (VOFF,RA,DEC): Convert frequency from heliocentric
+         reference frame to user-defined.
+
+         \sstitem
+         \texttt{"} TPF2HL\texttt{"}  (OBSLON,OBSLAT,OBSALT,EPOCH,RA,DEC): Convert frequency from
+         topocentric reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2TP\texttt{"}  (OBSLON,OBSLAT,OBSALT,EPOCH,RA,DEC): Convert frequency from
+         heliocentric reference frame to topocentric.
+
+         \sstitem
+         \texttt{"} GEF2HL\texttt{"}  (EPOCH,RA,DEC): Convert frequency from geocentric
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2GE\texttt{"}  (EPOCH,RA,DEC): Convert frequency from
+         heliocentric reference frame to geocentric.
+
+         \sstitem
+         \texttt{"} BYF2HL\texttt{"}  (EPOCH,RA,DEC): Convert frequency from
+         barycentric reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2BY\texttt{"}  (EPOCH,RA,DEC): Convert frequency from
+         heliocentric reference frame to barycentric.
+
+         \sstitem
+         \texttt{"} LKF2HL\texttt{"}  (RA,DEC): Convert frequency from kinematic LSR
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2LK\texttt{"}  (RA,DEC): Convert frequency from heliocentric
+         reference frame to kinematic LSR.
+
+         \sstitem
+         \texttt{"} LDF2HL\texttt{"}  (RA,DEC): Convert frequency from dynamical LSR
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2LD\texttt{"}  (RA,DEC): Convert frequency from heliocentric
+         reference frame to dynamical LSR.
+
+         \sstitem
+         \texttt{"} LGF2HL\texttt{"}  (RA,DEC): Convert frequency from local group
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2LG\texttt{"}  (RA,DEC): Convert frequency from heliocentric
+         reference frame to local group.
+
+         \sstitem
+         \texttt{"} GLF2HL\texttt{"}  (RA,DEC): Convert frequency from galactic
+         reference frame to heliocentric.
+
+         \sstitem
+         \texttt{"} HLF2GL\texttt{"}  (RA,DEC): Convert frequency from heliocentric
+         reference frame to galactic.
+
+      }
+      The units for the values processed by the above conversions are as
+      follows:
+
+      \sstitemlist{
+
+         \sstitem
+         all velocities: metres per second (positive if the source receeds from
+           the observer).
+
+         \sstitem
+         frequency: Hertz.
+
+         \sstitem
+         all wavelengths: metres.
+
+         \sstitem
+         energy: Joules.
+
+         \sstitem
+         wave number: cycles per metre.
+
+      }
+      The arguments used in the above conversions are as follows:
+
+      \sstitemlist{
+
+         \sstitem
+         RF: Rest frequency (Hz).
+
+         \sstitem
+         OBSALT: Geodetic altitude of observer (IAU 1975, metres).
+
+         \sstitem
+         OBSLAT: Geodetic latitude of observer (IAU 1975, radians).
+
+         \sstitem
+         OBSLON: Longitude of observer (radians - positive eastwards).
+
+         \sstitem
+         EPOCH: \htmlref{Epoch}{Epoch} of observation (UT1 expressed as a Modified Julian Date).
+
+         \sstitem
+         RA: Right Ascension of source (radians, FK5 J2000).
+
+         \sstitem
+         DEC: Declination of source (radians, FK5 J2000).
+
+         \sstitem
+         VOFF: Velocity of the user-defined reference frame, towards the
+         position given by RA and DEC, measured in the heliocentric
+         reference frame.
+
+      }
+      If the SpecMap is 3-dimensional, source positions are provided by the
+      values supplied to inputs 2 and 3 of the SpecMap (which are simply
+      copied to outputs 2 and 3). Note, usable values are still required
+      for the RA and DEC arguments in order to define the \texttt{"} user-defined\texttt{"}
+      reference frame used by USF2HL and HLF2US. However, AST\_\_BAD can be
+      supplied for RA and DEC if the user-defined reference frame is not
+      required.
+   }
+}
+\sstroutine{
+   astSpecFluxFrame
+}{
+   Create a SpecFluxFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SpecFluxFrame}{SpecFluxFrame} and optionally initialises
+      its attributes.
+
+      A SpecFluxFrame combines a \htmlref{SpecFrame}{SpecFrame} and a \htmlref{FluxFrame}{FluxFrame} into a single
+      2-dimensional compound \htmlref{Frame}{Frame}. Such a Frame can for instance be used
+      to describe a \htmlref{Plot}{Plot} of a spectrum in which the first axis represents
+      spectral position and the second axis represents flux.
+   }
+   \sstsynopsis{
+      AstSpecFluxFrame $*$astSpecFluxFrame( AstSpecFrame $*$frame1, AstFluxFrame $*$frame2,
+                                          const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         frame1
+      }{
+         Pointer to the SpecFrame. This will form the first axis in the
+         new SpecFluxFrame.
+      }
+      \sstsubsection{
+         frame2
+      }{
+         Pointer to the FluxFrame. This will form the second axis in the
+         new SpecFluxFrame. The \texttt{"} \htmlref{SpecVal}{SpecVal}\texttt{"}  attribute of this FluxFrame is
+         not used by the SpecFluxFrame class and so may be set to AST\_\_BAD
+         when the FluxFrame is created.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new SpecFluxFrame. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSpecFluxFrame()
+      }{
+         A pointer to the new SpecFluxFrame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The supplied Frame pointers are stored directly, rather than
+         being used to create deep copies of the supplied Frames. This means
+         that any subsequent changes made to the Frames via the supplied
+         pointers will result in equivalent changes being visible in the
+         SpecFluxFrame.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astSpecFrame
+}{
+   Create a SpecFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SpecFrame}{SpecFrame} and optionally initialises
+      its attributes.
+
+      A SpecFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various coordinate systems used to describe positions within
+      an electro-magnetic spectrum. The particular coordinate system to be
+      used is specified by setting the SpecFrame\texttt{'} s \htmlref{System}{System} attribute (the
+      default is wavelength) qualified, as necessary, by other attributes
+      such as the rest frequency, the standard of rest, the epoch of
+      observation, etc (see the description of the System attribute for
+      details).
+
+      By setting a value for thr \htmlref{SpecOrigin}{SpecOrigin} attribute, a SpecFrame can be made
+      to represent offsets from a given spectral position, rather than absolute
+   }
+   \sstsynopsis{
+      AstSpecFrame $*$astSpecFrame( const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new SpecFrame. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSpecFrame()
+      }{
+         A pointer to the new SpecFrame.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         frame = astSpecFrame( \texttt{"} \texttt{"}  );
+      }{
+         Creates a SpecFrame to describe the default wavelength spectral
+         coordinate system. The \htmlref{RestFreq}{RestFreq} attribute (rest frequency) is
+         unspecified, so it will not be possible to align this SpecFrame
+         with another SpecFrame on the basis of a velocity-based system. The
+         standard of rest is also unspecified. This means that alignment
+         will be possible with other SpecFrames, but no correction will be
+         made for Doppler shift caused by change of rest frame during the
+         alignment.
+      }
+      \sstexamplesubsection{
+         frame = astSpecFrame( \texttt{"} System=VELO, RestFreq=1.0E15, \htmlref{StdOfRest}{StdOfRest}=LSRK\texttt{"}  );
+      }{
+         Creates a SpecFrame describing a apparent radial velocity (\texttt{"} VELO\texttt{"} ) axis
+         with rest frequency 1.0E15 Hz (about 3000 Angstroms), measured
+         in the kinematic Local Standard of Rest (\texttt{"} LSRK\texttt{"} ). Since the
+         source position has not been specified (using attributes \htmlref{RefRA}{RefRA} and
+         \htmlref{RefDec}{RefDec}), it will only be possible to align this SpecFrame with
+         other SpecFrames which are also measured in the LSRK standard of
+         rest.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When conversion between two SpecFrames is requested (as when
+         supplying SpecFrames to \htmlref{astConvert}{astConvert}),
+         account will be taken of the nature of the spectral coordinate systems
+         they represent, together with any qualifying rest frequency, standard
+         of rest, epoch values, etc. The \htmlref{AlignSystem}{AlignSystem} and \htmlref{AlignStdOfRest}{AlignStdOfRest}
+         attributes will also be taken into account. The results will therefore
+         fully reflect the relationship between positions measured in the two
+         systems. In addition, any difference in the Unit attributes of the two
+         systems will also be taken into account.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astSpecMap
+}{
+   Create a SpecMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SpecMap}{SpecMap} and optionally initialises
+      its attributes.
+
+      An SpecMap is a specialised form of \htmlref{Mapping}{Mapping} which can be used to
+      represent a sequence of conversions between standard spectral
+      coordinate systems. This includes conversions between frequency,
+      wavelength, and various forms of velocity, as well as conversions
+      between different standards of rest.
+
+      When a SpecMap is first created, it simply performs a unit
+      (null) Mapping. Using the \htmlref{astSpecAdd}{astSpecAdd} function,
+      a series of coordinate conversion steps may then be added, selected
+      from the list of supported conversions. This allows multi-step
+      conversions between a variety of spectral coordinate systems to
+      be assembled out of the building blocks provided by this class.
+
+      For details of the individual coordinate conversions available,
+      see the description of the astSpecAdd function.
+
+      Conversions are available to transform between standards of rest.
+      Such conversions need to know the source position as an RA and DEC.
+      This information can be supplied in the form of parameters for
+      the relevant conversions, in which case the SpecMap is 1-dimensional,
+      simply transforming the spectral axis values. This means that the
+      same source position will always be used by the SpecMap. However, this
+      may not be appropriate for an accurate description of a 3-D spectral
+      cube, where changes of spatial position can produce significant
+      changes in the Doppler shift introduced when transforming between
+      standards of rest. For this situation, a 3-dimensional SpecMap can
+      be created in which axes 2 and 3 correspond to the source RA and DEC
+      The SpecMap simply copies values for axes 2 and 3 from input to
+      output).
+   }
+   \sstsynopsis{
+      AstSpecMap $*$astSpecMap( int nin, int flags, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         nin
+      }{
+         The number of inputs to the Mapping (this will also equal the
+         number of outputs). This value must be either 1 or 3. In either
+         case, the first input and output correspoindis the spectral axis.
+         For a 3-axis SpecMap, the second and third axes give the RA and
+         DEC (J2000 FK5) of the source. This positional information is
+         used by conversions which transform between standards of rest,
+         and replaces the \texttt{"} RA\texttt{"}  and \texttt{"} DEC\texttt{"}  arguments for the individual
+         conversions listed in description of the \texttt{"} SpecAdd\texttt{"}
+         function.
+      }
+      \sstsubsection{
+         flags
+      }{
+         This parameter is reserved for future use and should currently
+         always be set to zero.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new SpecMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSpecMap()
+      }{
+         A pointer to the new SpecMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The nature and units of the coordinate values supplied for the
+         first input (i.e. the spectral input) of a SpecMap must be appropriate
+         to the first conversion step applied by the SpecMap. For instance, if
+         the first conversion step is \texttt{"} FRTOVL\texttt{"}  (frequency to relativistic
+         velocity), then the coordinate values for the first input should
+         be frequency in units of Hz. Similarly, the nature and units of the
+         coordinate values returned by a SpecMap will be determined by the
+         last conversion step applied by the SpecMap. For instance, if the
+         last conversion step is \texttt{"} VLTOVO\texttt{"}  (relativistic velocity to optical
+         velocity), then the coordinate values for the first output will be optical
+         velocity in units of metres per second. See the description of the
+         astSpecAdd function for the units expected and returned by each
+         conversion.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astSphMap
+}{
+   Create a SphMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SphMap}{SphMap} and optionally initialises
+      its attributes.
+
+      A SphMap is a \htmlref{Mapping}{Mapping} which transforms points from a
+      3-dimensional Cartesian coordinate system into a 2-dimensional
+      spherical coordinate system (longitude and latitude on a unit
+      sphere centred at the origin). It works by regarding the input
+      coordinates as position vectors and finding their intersection
+      with the sphere surface. The inverse transformation always
+      produces points which are a unit distance from the origin
+      (i.e. unit vectors).
+   }
+   \sstsynopsis{
+      AstSphMap $*$astSphMap( const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new SphMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSphMap()
+      }{
+         A pointer to the new SphMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The spherical coordinates are longitude (positive
+         anti-clockwise looking from the positive latitude pole) and
+         latitude. The Cartesian coordinates are right-handed, with the x
+         axis (axis 1) at zero longitude and latitude, and the z axis
+         (axis 3) at the positive latitude pole.
+
+         \sstitem
+         At either pole, the longitude is set to the value of the
+         \htmlref{PolarLong}{PolarLong} attribute.
+
+         \sstitem
+         If the Cartesian coordinates are all zero, then the longitude
+         and latitude are set to the value AST\_\_BAD.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astStatus
+}{
+   Obtain the current AST error status value
+}{
+   \sstdescription{
+      This function returns the current value of the AST error status.
+   }
+   \sstsynopsis{
+      int astStatus
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astStatus
+      }{
+         The AST error status value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the AST error status is set to an error value (after an
+         error), most AST functions will not execute and will simply
+         return without action. To clear the error status and restore
+         normal behaviour, use \htmlref{astClearStatus}{astClearStatus}.
+      }
+   }
+}
+\sstroutine{
+   astStcCatalogEntryLocation
+}{
+   Create a StcCatalogEntryLocation
+}{
+   \sstdescription{
+      This function creates a new \htmlref{StcCatalogEntryLocation}{StcCatalogEntryLocation} and optionally initialises its
+      attributes.
+
+      The StcCatalogEntryLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of the datasets contained in some VO resource.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstsynopsis{
+      AstStcCatalogEntryLocation $*$astStcCatalogEntryLocation( AstRegion $*$region,
+                   int ncoords, AstKeyMap $*$coords[], const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         region
+      }{
+         Pointer to the encapsulated \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         ncoords
+      }{
+         The length of the \texttt{"} coords\texttt{"}  array. Supply zero if \texttt{"} coords\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         coords
+      }{
+         Pointer to an array holding \texttt{"} ncoords\texttt{"}  AstKeyMap pointers (if \texttt{"} ncoords\texttt{"}
+         is zero, the supplied value is ignored). Each supplied \htmlref{KeyMap}{KeyMap}
+         describes the contents of a single STC $<$AstroCoords$>$ element, and
+         should have elements with keys given by constants AST\_\_STCNAME,
+         AST\_\_STCVALUE, AST\_\_STCERROR, AST\_\_STCRES, AST\_\_STCSIZE,
+         AST\_\_STCPIXSZ. Any of these elements may be omitted, but no other
+         elements should be included. If supplied, the AST\_\_STCNAME element
+         should be a vector of character string pointers holding the \texttt{"} Name\texttt{"}
+         item for each axis in the coordinate system represented by
+         \texttt{"} region\texttt{"} .
+         Any other supplied elements should be scalar elements, each  holding
+         a pointer to a Region describing the associated item of ancillary
+         information (error, resolution, size, pixel size or value). These
+         Regions should describe a volume within the coordinate system
+         represented by \texttt{"} region\texttt{"} .
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new StcCatalogEntryLocation. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astStcCatalogEntryLocation()
+      }{
+         A pointer to the new StcCatalogEntryLocation.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A deep copy is taken of the supplied Region. This means that
+         any subsequent changes made to the encapsulated Region using the
+         supplied pointer will have no effect on the Stc.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astStcObsDataLocation
+}{
+   Create a StcObsDataLocation
+}{
+   \sstdescription{
+      This function creates a new \htmlref{StcObsDataLocation}{StcObsDataLocation} and optionally initialises its
+      attributes.
+
+      The StcObsDataLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of the datasets contained in some VO resource.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstsynopsis{
+      AstStcObsDataLocation $*$astStcObsDataLocation( AstRegion $*$region,
+                   int ncoords, AstKeyMap $*$coords[], const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         region
+      }{
+         Pointer to the encapsulated \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         ncoords
+      }{
+         The length of the \texttt{"} coords\texttt{"}  array. Supply zero if \texttt{"} coords\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         coords
+      }{
+         Pointer to an array holding \texttt{"} ncoords\texttt{"}  AstKeyMap pointers (if \texttt{"} ncoords\texttt{"}
+         is zero, the supplied value is ignored). Each supplied \htmlref{KeyMap}{KeyMap}
+         describes the contents of a single STC $<$AstroCoords$>$ element, and
+         should have elements with keys given by constants AST\_\_STCNAME,
+         AST\_\_STCVALUE, AST\_\_STCERROR, AST\_\_STCRES, AST\_\_STCSIZE,
+         AST\_\_STCPIXSZ. Any of these elements may be omitted, but no other
+         elements should be included. If supplied, the AST\_\_STCNAME element
+         should be a vector of character string pointers holding the \texttt{"} Name\texttt{"}
+         item for each axis in the coordinate system represented by
+         \texttt{"} region\texttt{"} .
+         Any other supplied elements should be scalar elements, each  holding
+         a pointer to a Region describing the associated item of ancillary
+         information (error, resolution, size, pixel size or value). These
+         Regions should describe a volume within the coordinate system
+         represented by \texttt{"} region\texttt{"} .
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new StcObsDataLocation. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astStcObsDataLocation()
+      }{
+         A pointer to the new StcObsDataLocation.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A deep copy is taken of the supplied Region. This means that
+         any subsequent changes made to the encapsulated Region using the
+         supplied pointer will have no effect on the Stc.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astStcResourceProfile
+}{
+   Create a StcResourceProfile
+}{
+   \sstdescription{
+      This function creates a new \htmlref{StcResourceProfile}{StcResourceProfile} and optionally initialises its
+      attributes.
+
+      The StcResourceProfile class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of the datasets contained in some VO resource.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstsynopsis{
+      AstStcResourceProfile $*$astStcResourceProfile( AstRegion $*$region,
+                   int ncoords, AstKeyMap $*$coords[], const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         region
+      }{
+         Pointer to the encapsulated \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         ncoords
+      }{
+         The length of the \texttt{"} coords\texttt{"}  array. Supply zero if \texttt{"} coords\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         coords
+      }{
+         Pointer to an array holding \texttt{"} ncoords\texttt{"}  AstKeyMap pointers (if \texttt{"} ncoords\texttt{"}
+         is zero, the supplied value is ignored). Each supplied \htmlref{KeyMap}{KeyMap}
+         describes the contents of a single STC $<$AstroCoords$>$ element, and
+         should have elements with keys given by constants AST\_\_STCNAME,
+         AST\_\_STCVALUE, AST\_\_STCERROR, AST\_\_STCRES, AST\_\_STCSIZE,
+         AST\_\_STCPIXSZ. Any of these elements may be omitted, but no other
+         elements should be included. If supplied, the AST\_\_STCNAME element
+         should be a vector of character string pointers holding the \texttt{"} Name\texttt{"}
+         item for each axis in the coordinate system represented by
+         \texttt{"} region\texttt{"} .
+         Any other supplied elements should be scalar elements, each  holding
+         a pointer to a Region describing the associated item of ancillary
+         information (error, resolution, size, pixel size or value). These
+         Regions should describe a volume within the coordinate system
+         represented by \texttt{"} region\texttt{"} .
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new StcResourceProfile. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astStcResourceProfile()
+      }{
+         A pointer to the new StcResourceProfile.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A deep copy is taken of the supplied Region. This means that
+         any subsequent changes made to the encapsulated Region using the
+         supplied pointer will have no effect on the Stc.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astStcSearchLocation
+}{
+   Create a StcSearchLocation
+}{
+   \sstdescription{
+      This function creates a new \htmlref{StcSearchLocation}{StcSearchLocation} and optionally initialises its
+      attributes.
+
+      The StcSearchLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of a VO query.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstsynopsis{
+      AstStcResourceProfile $*$astStcSearchLocation( AstRegion $*$region,
+                    int ncoords, AstKeyMap $*$coords[], const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         region
+      }{
+         Pointer to the encapsulated \htmlref{Region}{Region}.
+      }
+      \sstsubsection{
+         ncoords
+      }{
+         The length of the \texttt{"} coords\texttt{"}  array. Supply zero if \texttt{"} coords\texttt{"}  is NULL.
+      }
+      \sstsubsection{
+         coords
+      }{
+         Pointer to an array holding \texttt{"} ncoords\texttt{"}  AstKeyMap pointers (if \texttt{"} ncoords\texttt{"}
+         is zero, the supplied value is ignored). Each supplied \htmlref{KeyMap}{KeyMap}
+         describes the contents of a single STC $<$AstroCoords$>$ element, and
+         should have elements with keys given by constants AST\_\_STCNAME,
+         AST\_\_STCVALUE, AST\_\_STCERROR, AST\_\_STCRES, AST\_\_STCSIZE,
+         AST\_\_STCPIXSZ. Any of these elements may be omitted, but no other
+         elements should be included. If supplied, the AST\_\_STCNAME element
+         should be a vector of character string pointers holding the \texttt{"} Name\texttt{"}
+         item for each axis in the coordinate system represented by
+         \texttt{"} region\texttt{"} .
+         Any other supplied elements should be scalar elements, each  holding
+         a pointer to a Region describing the associated item of ancillary
+         information (error, resolution, size, pixel size or value). These
+         Regions should describe a volume within the coordinate system
+         represented by \texttt{"} region\texttt{"} .
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new StcSearchLocation. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astStcSearchLocation()
+      }{
+         A pointer to the new StcSearchLocation.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A deep copy is taken of the supplied Region. This means that
+         any subsequent changes made to the encapsulated Region using the
+         supplied pointer will have no effect on the Stc.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astStcsChan
+}{
+   Create an StcsChan
+}{
+   \sstdescription{
+      This function creates a new \htmlref{StcsChan}{StcsChan} and optionally initialises
+      its attributes.
+
+      A StcsChan is a specialised form of \htmlref{Channel}{Channel} which supports STC-S
+      I/O operations. Writing an \htmlref{Object}{Object} to an StcsChan (using
+      \htmlref{astWrite}{astWrite}) will, if the Object is suitable, generate an
+      STC-S description of that Object, and reading from an StcsChan will
+      create a new Object from its STC-S description.
+
+      Normally, when you use an StcsChan, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  functions which connect it to an external data store
+      by reading and writing the resulting text. These functions
+      should perform any conversions needed between external character
+      encodings and the internal ASCII encoding. If no such functions
+      are supplied, a Channel will read from standard input and write
+      to standard output.
+
+      Alternatively, an \htmlref{XmlChan}{XmlChan} can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+   }
+   \sstsynopsis{
+      AstStcsChan $*$astStcsChan( const char $*$($*$ source)( void ),
+                                void ($*$ sink)( const char $*$ ),
+                                const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         source
+      }{
+         Pointer to a source function that takes no arguments and
+         returns a pointer to a null-terminated string.  If no value
+         has been set for the SourceFile attribute, this function
+         will be used by the StcsChan to obtain lines of input text. On
+         each invocation, it should return a pointer to the next input
+         line read from some external data store, and a NULL pointer
+         when there are no more lines to read.
+
+         If \texttt{"} source\texttt{"}  is NULL and no value has been set for the SourceFile
+         attribute, the StcsChan will read from standard input instead.
+      }
+      \sstsubsection{
+         sink
+      }{
+         Pointer to a sink function that takes a pointer to a
+         null-terminated string as an argument and returns void.
+         If no value has been set for the SinkFile attribute, this
+         function will be used by the StcsChan to deliver lines of
+         output text. On each invocation, it should deliver the
+         contents of the string supplied to some external data store.
+
+         If \texttt{"} sink\texttt{"}  is NULL, and no value has been set for the SinkFile
+         attribute, the StcsChan will write to standard output instead.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new StcsChan. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astStcsChan()
+      }{
+         A pointer to the new StcsChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the external data source or sink uses a character encoding
+         other than ASCII, the supplied source and sink functions should
+         translate between the external character encoding and the internal
+         ASCII encoding used by AST.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astStripEscapes
+}{
+   Remove AST escape sequences from a string
+}{
+   \sstdescription{
+      This function removes AST escape sequences from a supplied string,
+      returning the resulting text as the function value. The behaviour
+      of this function can be controlled by invoking the
+      \htmlref{astEscapes}{astEscapes} function,
+      which can be used to supress or enable the removal of escape
+      sequences by this function.
+
+      AST escape sequences are used by the \htmlref{Plot}{Plot} class to modify the
+      appearance and position of sub-strings within a plotted text string.
+      See the \texttt{"} \htmlref{Escape}{Escape}\texttt{"}  attribute for further information.
+   }
+   \sstsynopsis{
+      const char $*$astStripEscapes( const char $*$text )
+   }
+   \sstparameters{
+      \sstsubsection{
+         text
+      }{
+         Pointer to the string to be checked.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astStripEscapes()
+      }{
+         Pointer to the modified string. If no escape sequences were found
+         in the supplied string, then a copy of the supplied pointer is
+         returned. Otherwise, the pointer will point to a static buffer
+         holding the modified text. This text will be over-written by
+         subsequent invocations of this function. If the astEscapes function
+         has been called indicating that escape sequences should not be
+         stripped, then the supplied string is returned without change.
+      }
+   }
+}
+\sstroutine{
+   astSwitchMap
+}{
+   Create a SwitchMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{SwitchMap}{SwitchMap} and optionally initialises
+      its attributes.
+
+      A SwitchMap is a \htmlref{Mapping}{Mapping} which represents a set of alternate
+      Mappings, each of which is used to transform positions within a
+      particular region of the input or output coordinate system of the
+      SwitchMap.
+
+      A SwitchMap can encapsulate any number of Mappings, but they must
+      all have the same number of inputs (\htmlref{Nin}{Nin} attribute value) and the
+      same number of outputs (\htmlref{Nout}{Nout} attribute value). The SwitchMap itself
+      inherits these same values for its Nin and Nout attributes. Each of
+      these Mappings represents a \texttt{"} route\texttt{"}  through the switch, and are
+      referred to as \texttt{"} route\texttt{"}  Mappings below. Each route Mapping transforms
+      positions between the input and output coordinate space of the entire
+      SwitchMap, but only one Mapping will be used to transform any given
+      position. The selection of the appropriate route Mapping to use with
+      any given input position is made by another Mapping, called the
+      \texttt{"} selector\texttt{"}  Mapping. Each SwitchMap encapsulates two selector
+      Mappings in addition to its route Mappings; one for use with the
+      SwitchMap\texttt{'} s forward transformation (called the \texttt{"} forward selector
+      Mapping\texttt{"} ), and one for use with the SwitchMap\texttt{'} s inverse transformation
+      (called the \texttt{"} inverse selector Mapping\texttt{"} ). The forward selector Mapping
+      must have the same number of inputs as the route Mappings, but
+      should have only one output. Likewise, the inverse selector Mapping
+      must have the same number of outputs as the route Mappings, but
+      should have only one input.
+
+      When the SwitchMap is used to transform a position in the forward
+      direction (from input to output), each supplied input position is
+      first transformed by the forward transformation of the forward selector
+      Mapping. This produces a single output value for each input position
+      referred to as the selector value. The nearest integer to the selector
+      value is found, and is used to index the array of route Mappings (the
+      first supplied route Mapping has index 1, the second route Mapping has
+      index 2, etc). If the nearest integer to the selector value is less
+      than 1 or greater than the number of route Mappings, then the SwitchMap
+      output position is set to a value of AST\_\_BAD on every axis. Otherwise,
+      the forward transformation of the selected route Mapping is used to
+      transform the supplied input position to produce the SwitchMap output
+      position.
+
+      When the SwitchMap is used to transform a position in the inverse
+      direction (from \texttt{"} output\texttt{"}  to \texttt{"} input\texttt{"} ), each supplied \texttt{"} output\texttt{"}  position
+      is first transformed by the inverse transformation of the inverse
+      selector Mapping. This produces a selector value for each \texttt{"} output\texttt{"}
+      position. Again, the nearest integer to the selector value is found,
+      and is used to index the array of route Mappings. If this selector
+      index value is within the bounds of the array of route Mappings, then
+      the inverse transformation of the selected route Mapping is used to
+      transform the supplied \texttt{"} output\texttt{"}  position to produce the SwitchMap
+      \texttt{"} input\texttt{"}  position. If the selector index value is outside the bounds
+      of the array of route Mappings, then the SwitchMap \texttt{"} input\texttt{"}  position is
+      set to a value of AST\_\_BAD on every axis.
+
+      In practice, appropriate selector Mappings should be chosen to
+      associate a different route Mapping with each region of coordinate
+      space. Note that the \htmlref{SelectorMap}{SelectorMap} class of Mapping is particularly
+      appropriate for this purpose.
+
+      If a compound Mapping contains a SwitchMap in series with its own
+      inverse, the combination of the two adjacent SwitchMaps will be
+      replaced by a \htmlref{UnitMap}{UnitMap} when the compound Mapping is simplified using
+      \htmlref{astSimplify}{astSimplify}.
+   }
+   \sstsynopsis{
+      AstSwitchMap $*$astSwitchMap( AstMapping $*$fsmap, AstMapping $*$ismap,
+                                  int nroute, AstMapping $*$routemaps[],
+                                  const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         fsmap
+      }{
+         Pointer to the forward selector Mapping. This must have a
+         defined forward transformation, but need not have a defined
+         inverse transformation. It must have one output, and the number of
+         inputs must match the number of inputs of each of the supplied
+         route Mappings.
+         NULL
+         may be supplied, in which case the SwitchMap will have an undefined
+         forward Mapping.
+      }
+      \sstsubsection{
+         ismap
+      }{
+         Pointer to the inverse selector Mapping. This must have a
+         defined inverse transformation, but need not have a defined
+         forward transformation. It must have one input, and the number of
+         outputs must match the number of outputs of each of the supplied
+         route Mappings.
+         NULL
+         may be supplied, in which case the SwitchMap will have an undefined
+         inverse Mapping.
+      }
+      \sstsubsection{
+         nroute
+      }{
+         The number of supplied route Mappings.
+      }
+      \sstsubsection{
+         routemaps
+      }{
+         An array of pointers to the route Mappings. All the supplied
+         route Mappings must have common values for the Nin and Nout
+         attributes, and these values define the number of inputs and
+         outputs of the SwitchMap.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new SwitchMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSwitchMap()
+      }{
+         A pointer to the new SwitchMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Note that the component Mappings supplied are not copied by
+         astSwitchMap (the new SwitchMap simply retains a reference to
+         them). They may continue to be used for other purposes, but
+         should not be deleted. If a SwitchMap containing a copy of its
+         component Mappings is required, then a copy of the SwitchMap should
+         be made using \htmlref{astCopy}{astCopy}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astTable
+}{
+   Create a Table
+}{
+   \sstdescription{
+      This function creates a new empty \htmlref{Table}{Table} and optionally initialises
+      its attributes.
+
+      The Table class is a type of \htmlref{KeyMap}{KeyMap} that represents a two-dimensional
+      table of values. The
+      astMapGet... and astMapPut...
+      methods provided by the KeyMap class should be used for storing and
+      retrieving values from individual cells within a Table. Each entry
+      in the KeyMap represents a single cell of the table and has an
+      associated key of the form \texttt{"} $<$COL$>$(i)\texttt{"}  where \texttt{"} $<$COL$>$\texttt{"}  is the name of a
+      table column and \texttt{"} i\texttt{"}  is the row index (the first row is row 1). Keys
+      of this form should always be used when using KeyMap methods to access
+      entries within a Table.
+
+      Columns must be declared using the
+      \htmlref{astAddColumn}{astAddColumn}
+      method before values can be stored within them. This also fixes the
+      type and shape of the values that may be stored in any cell of the
+      column. Cells may contain scalar or vector values of any data type
+      supported by the KeyMap class. Multi-dimensional arrays may also be
+      stored, but these must be vectorised when storing and retrieving
+      them within a table cell. All cells within a single column must
+      have the same type and shape (specified when the column is declared).
+
+      Tables may have parameters that describe global properties of the
+      entire table. These are stored as entries in the parent KeyMap and
+      can be access using the get and set method of the KeyMap class.
+      However, parameters must be declared using the
+      \htmlref{astAddParameter}{astAddParameter}
+      method before being accessed.
+
+      Note - since accessing entries within a KeyMap is a relatively slow
+      process, it is not recommended to use the Table class to store
+      very large tables.
+   }
+   \sstsynopsis{
+      AstTable $*$astTable( const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new Table. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astTable()
+      }{
+         A pointer to the new Table.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list described above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astTableSource
+}{
+   Register a source function for accessing tables in FITS files
+}{
+   \sstdescription{
+      This function can be used to register a call-back function
+      with a \htmlref{FitsChan}{FitsChan}. The registered
+      function
+      is called when-ever the FitsChan needs to read information from a
+      binary table contained within a FITS file. This occurs if the
+      \htmlref{astRead}{astRead}
+      function is invoked to read a \htmlref{FrameSet}{FrameSet} from a set of FITS headers
+      that use the \texttt{"} -TAB\texttt{"}  algorithm to describe one or more axes. Such
+      axes use a FITS binary table to store a look-up table of axis values.
+      The FitsChan will fail to read such axes unless the \texttt{"} \htmlref{TabOK}{TabOK}\texttt{"}  attribute
+      is set to a non-zero positive integer value. The table containing the
+      axis values must be made available to the FitsChan either by storing
+      the table contents in the FitsChan (using
+      \htmlref{astPutTables}{astPutTables} or \htmlref{astPutTable}{astPutTable}) prior to invoking astRead
+      or by registering a call-back
+      function using astTableSource.
+      The first method is possibly simpler, but requires that the name of
+      the extension containing the table be known in advance. Since the
+      table name is embedded in the FITS headers, the name is often not
+      known in advance. If a call-back is registered, the FitsChan will
+      determine the name of the required table and invoke the call-back
+      function
+      to supply the table at the point where it is needed (i.e. within
+      the astRead method).
+   }
+   \sstsynopsis{
+      void astTableSource( AstFitsChan $*$this,
+                           void ($*$ tabsource)( AstFitsChan $*$, const char $*$,
+                                               int, int, int $*$ ) )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         tabsource
+      }{
+         Pointer to the table source function to use.
+         It takes five arguments - the first is a pointer to the
+         FitsChan, the second is a string holding the name of the
+         FITS extension containing the required binary table (\texttt{"} EXTNAME\texttt{"} ),
+         the third is the integer FITS \texttt{"} EXTVER\texttt{"}  header value for the
+         required extension, the fourth is the integer FITS \texttt{"} EXTLEVEL\texttt{"}
+         header value for the required extension, and the fifth is
+         a pointer to
+         the inherited integer status value.
+
+         The call-back should read the entire contents (header and data)
+         of the binary table in the named extension of the external FITS
+         file, storing the contents in a newly created \htmlref{FitsTable}{FitsTable} object. It
+         should then store this FitsTable in the FitsChan using the
+         astPutTables or astPutTable
+         method, and finally annull its local copy of the FitsTable pointer.
+         If the table cannot be read for any reason, or if any other
+         error occurs, it should return a non-zero integer for the final
+         (third) argument.
+
+         If \texttt{"} tabsource\texttt{"}  is NULL,
+         any registered call-back function will be removed.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Application code can pass arbitrary data (such as file
+         descriptors, etc) to the table source function using the
+         \htmlref{astPutChannelData}{astPutChannelData} function. The source function should use
+         the \htmlref{astChannelData}{astChannelData} macro to retrieve this data.
+      }
+   }
+}
+\sstroutine{
+   astTest
+}{
+   Test if an Object attribute value is set
+}{
+   \sstdescription{
+      This function returns a boolean result (0 or 1) to indicate
+      whether a value has been explicitly set for one of an \htmlref{Object}{Object}\texttt{'} s
+      attributes.
+   }
+   \sstsynopsis{
+      int astTest( AstObject $*$this, const char $*$attrib )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object.
+      }
+      \sstsubsection{
+         attrib
+      }{
+         Pointer to a null-terminated character string containing
+         the name of the attribute to be tested.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astTest()
+      }{
+         One if a value has previously been explicitly set for the attribute
+         (and hasn\texttt{'} t been cleared), otherwise zero.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Attribute names are not case sensitive and may be surrounded
+         by white space.
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any reason.
+
+         \sstitem
+         A value of zero will also be returned if this function is used
+         to test a read-only attribute, although no error will result.
+      }
+   }
+}
+\sstroutine{
+   astTestFits
+}{
+   See if a named keyword has a defined value in a FitsChan
+}{
+   \sstdescription{
+      This function serches for a named keyword in a \htmlref{FitsChan}{FitsChan}. If found,
+      and if the keyword has a value associated with it, a
+      non-zero
+      value is returned. If the keyword is not found, or if it does not
+      have an associated value, a
+      zero
+      value is returned.
+   }
+   \sstsynopsis{
+      int astTestFits( AstFitsChan $*$this, const char $*$name, int $*$there )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+      \sstsubsection{
+         name
+      }{
+         Pointer to a null-terminated character string
+         containing the FITS keyword name. This may be a complete FITS
+         header card, in which case the keyword to use is extracted from
+         it. No more than 80 characters are read from this string.
+      }
+      \sstsubsection{
+         there
+      }{
+         Pointer to an integer which will be returned holding a non-zero
+         value if the keyword was found in the header, and zero otherwise.
+         This parameter allows a distinction to be made between the case
+         where a keyword is not present, and the case where a keyword is
+         present but has no associated value.
+         A NULL pointer may be supplied if this information is not
+         required.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astTestFits()
+      }{
+         A value of zero
+         is returned if the keyword was not found in the FitsChan or has
+         no associated value. Otherwise, a value of
+         one
+         is returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The current card is left unchanged by this function.
+
+         \sstitem
+         The card following the current card is checked first. If this is
+         not the required card, then the rest of the FitsChan is searched,
+         starting with the first card added to the FitsChan. Therefore cards
+         should be accessed in the order they are stored in the FitsChan (if
+         possible) as this will minimise the time spent searching for cards.
+
+         \sstitem
+         An error will be reported if the keyword name does not conform
+         to FITS requirements.
+
+         \sstitem
+         Zero
+         is returned as the function value if an error has already occurred,
+         or if this function should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astText
+}{
+   Draw a text string for a Plot
+}{
+   \sstdescription{
+      This function draws a string of text at a position specified in
+      the physical coordinate system of a \htmlref{Plot}{Plot}. The physical position
+      is transformed into graphical coordinates to determine where the
+      text should appear within the plotting area.
+   }
+   \sstsynopsis{
+      void astText( AstPlot $*$this, const char $*$text, const double pos[],
+                    const float up[], const char $*$just )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Plot.
+      }
+      \sstsubsection{
+         text
+      }{
+         Pointer to a null-terminated character string containing the
+         text to be drawn. Trailing white space is ignored.
+      }
+      \sstsubsection{
+         pos
+      }{
+         An array, with one element for each axis of the Plot, giving
+         the physical coordinates of the point where the reference
+         position of the text string is to be placed.
+      }
+      \sstsubsection{
+         up
+      }{
+         An array holding the components of a vector in the \texttt{"} up\texttt{"}
+         direction of the text (in graphical coordinates). For
+         example, to get horizontal text, the vector \{0.0f,1.0f\} should
+         be supplied. For a basic Plot, 2 values should be supplied. For
+         a \htmlref{Plot3D}{Plot3D}, 3 values should be supplied, and the actual up vector
+         used is the projection of the supplied up vector onto the text plane
+         specified by the current value of the Plot3D\texttt{'} s Norm attribute.
+      }
+      \sstsubsection{
+         just
+      }{
+         Pointer to a null-terminated character string identifying the
+         reference point for the text being drawn. The first character in
+         this string identifies the reference position in the \texttt{"} up\texttt{"}  direction
+         and may be \texttt{"} B\texttt{"}  (baseline), \texttt{"} C\texttt{"}  (centre), \texttt{"} T\texttt{"}  (top) or \texttt{"} M\texttt{"}  (bottom).
+         The second character identifies the side-to-side reference position
+         and may be \texttt{"} L\texttt{"}  (left), \texttt{"} C\texttt{"}  (centre) or \texttt{"} R\texttt{"}  (right ). The string is
+         case-insensitive, and only the first two characters are significant.
+
+         For example, a value of \texttt{"} BL\texttt{"}  means that the left end of the
+         baseline of the original (un-rotated) text is to be drawn at the
+         position given by \texttt{"} pos\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Plot3D class currently does not interpret graphical escape
+         sequences contained within text displayed using this method.
+
+         \sstitem
+         Text is not drawn at positions which have any coordinate equal
+         to the value AST\_\_BAD (or where the transformation into
+         graphical coordinates yields coordinates containing the value
+         AST\_\_BAD).
+
+         \sstitem
+         If the plotting position is clipped (see \htmlref{astClip}{astClip}), then no
+         text is drawn.
+
+         \sstitem
+         An error results if the base \htmlref{Frame}{Frame} of the Plot is not
+         2-dimensional or (for a Plot3D) 3-dimensional.
+
+         \sstitem
+         An error also results if the transformation between the
+         current and base Frames of the Plot is not defined (i.e. the
+         Plot\texttt{'} s \htmlref{TranInverse}{TranInverse} attribute is zero).
+      }
+   }
+}
+\sstroutine{
+   astThread
+}{
+   Determine the thread that owns an Object
+}{
+   \sstdescription{
+      Returns an integer that indicates whether the supplied \htmlref{Object}{Object} (or
+      Object pointer) is currently unlocked, or is currently locked by
+      the running thread, or another thread.
+   }
+   \sstsynopsis{
+      int astThread( AstObject $*$this, int ptr )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object to be checked.
+      }
+      \sstsubsection{
+         ptr
+      }{
+         If non-zero, returns information about the supplied Object
+         pointer, rather than the Object structure itself. See \texttt{"} Object
+         Pointers and Structures\texttt{"}  below.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astThread()
+      }{
+         A value of AST\_\_UNLOCKED is returned if the Object (or pointer)
+         is currently unlocked (i.e. has been unlocked using \htmlref{astUnlock}{astUnlock}
+         but has not yet been locked using \htmlref{astLock}{astLock}). A value of
+         AST\_\_RUNNING is returned if the Object (or pointer) is currently
+         locked by the running thread. A value of AST\_\_OTHER is returned
+         if the Object (or pointer) is currently locked by the another
+         thread.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function attempts to execute even if the global error
+         status is set, but no further error report will be made if it
+         subsequently fails under these circumstances.
+
+         \sstitem
+         This function is only available in the C interface.
+
+         \sstitem
+         This function always returns AST\_\_RUNNING if the AST library has
+         been built without POSIX thread support (i.e. the \texttt{"} -with-pthreads\texttt{"}
+         option was not specified when running the \texttt{"} configure\texttt{"}  script).
+      }
+   }
+   \sstdiytopic{
+      Object Pointers and Structures
+   }{
+      At any one time, an AST Object can have several distinct pointers,
+      any one of which can be used to access the Object structure. For
+      instance, the \htmlref{astClone}{astClone} function will produce a new distinct pointer
+      for a given Object. In fact, an AST \texttt{"} pointer\texttt{"}  is not a real pointer
+      at all - it is an identifier for a \texttt{"} handle\texttt{"}  structure, encoded to
+      make it look like a pointer. Each handle contains (amongst othere
+      things) a \texttt{"} real\texttt{"}  pointer to the Object structure. This allows more
+      than one handle to refer to the same Object structure. So when you
+      call astClone (for instance) you get back an identifier for a new
+      handle that refers to the same Object as the supplied handle.
+
+      In order to use an Object for anything useful, it must be locked
+      for use by the running thread (either implicitly at creation or
+      explicitly using astLock). The identity of the thread is stored in
+      both the Object structure, and in the handle that was passed to
+      astLock (or returned by the constructor function). Thus it is
+      possible for a thread to have active pointers for Objects that are
+      currently locked by another thread. In general, if such a pointer is
+      passed to an AST function an error will be reported indicating that
+      the Object is currently locked by another thread. The two exceptions
+      to this is that \htmlref{astAnnul}{astAnnul} can be used to annull such a pointer, and
+      this function can be used to return information about the pointer.
+
+      The other practical consequence of this is that when \htmlref{astEnd}{astEnd} is
+      called, all active pointers currently owned by the running thread
+      (at the current context level) are annulled. This includes pointers
+      for Objects that are currently locked by other threads.
+
+      If the \texttt{"} ptr\texttt{"}  parameter is zero, then the returned value describes
+      the Object structure itself. If \texttt{"} ptr\texttt{"}  is non-zero, then the returned
+      value describes the supplied Object pointer (i.e. handle), rather
+      than the Object structure.
+   }
+}
+\sstroutine{
+   astTimeAdd
+}{
+   Add a time coordinate conversion to a TimeMap
+}{
+   \sstdescription{
+      This function adds one of the standard time coordinate
+      system conversions listed below to an existing \htmlref{TimeMap}{TimeMap}.
+
+      When a TimeMap is first created (using \htmlref{astTimeMap}{astTimeMap}), it simply
+      performs a unit (null) \htmlref{Mapping}{Mapping}. By using astTimeAdd (repeatedly
+      if necessary), one or more coordinate conversion steps may then
+      be added, which the TimeMap will perform in sequence. This allows
+      multi-step conversions between a variety of time coordinate
+      systems to be assembled out of the building blocks provided by
+      this class.
+
+      Normally, if a TimeMap\texttt{'} s \htmlref{Invert}{Invert} attribute is zero (the default),
+      then its forward transformation is performed by carrying out
+      each of the individual coordinate conversions specified by
+      astTimeAdd in the order given (i.e. with the most recently added
+      conversion applied last).
+
+      This order is reversed if the TimeMap\texttt{'} s Invert attribute is
+      non-zero (or if the inverse transformation is requested by any
+      other means) and each individual coordinate conversion is also
+      replaced by its own inverse. This process inverts the overall
+      effect of the TimeMap. In this case, the first conversion to be
+      applied would be the inverse of the one most recently added.
+   }
+   \sstsynopsis{
+      void astTimeAdd( AstTimeMap $*$this, const char $*$cvt, int narg,
+                       const double args[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the TimeMap.
+      }
+      \sstsubsection{
+         cvt
+      }{
+         Pointer to a null-terminated string which identifies the
+         time coordinate conversion to be added to the
+         TimeMap. See the \texttt{"} Available Conversions\texttt{"}  section for details of
+         those available.
+      }
+      \sstsubsection{
+         narg
+      }{
+         The number of argument values supplied in the
+         \texttt{"} args\texttt{"}  array.
+      }
+      \sstsubsection{
+         args
+      }{
+         An array containing argument values for the time
+         coordinate conversion. The number of arguments required, and
+         hence the number of array elements used, depends on the
+         conversion specified (see the \texttt{"} Available Conversions\texttt{"}
+         section). This array is ignored
+         and a NULL pointer may be supplied
+         if no arguments are needed.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When assembling a multi-stage conversion, it can sometimes be
+         difficult to determine the most economical conversion path. A solution
+         to this is to include all the steps which are (logically) necessary,
+         but then to use
+         \htmlref{astSimplify}{astSimplify} to simplify the resulting
+         TimeMap. The simplification process will eliminate any steps
+         which turn out not to be needed.
+
+         \sstitem
+         This function does not check to ensure that the sequence of
+         coordinate conversions added to a TimeMap is physically
+         meaningful.
+      }
+   }
+   \sstdiytopic{
+      Available Conversions
+   }{
+      The following strings (which are case-insensitive) may be supplied
+      via the \texttt{"} cvt\texttt{"}  parameter to indicate which time coordinate
+      conversion is to be added to the TimeMap. Where arguments are needed by
+      the conversion, they are listed in parentheses. Values for
+      these arguments should be given, via the \texttt{"} args\texttt{"}  array, in the
+      order indicated. Units and argument names are described at the end of
+      the list of conversions, and \texttt{"} MJD\texttt{"}  means Modified Julian Date.
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} MJDTOMJD\texttt{"}   (MJDOFF1,MJDOFF2): Convert MJD from one offset to another.
+
+         \sstitem
+         \texttt{"} MJDTOJD\texttt{"}   (MJDOFF,JDOFF): Convert MJD to Julian Date.
+
+         \sstitem
+         \texttt{"} JDTOMJD\texttt{"}   (JDOFF,MJDOFF): Convert Julian Date to MJD.
+
+         \sstitem
+         \texttt{"} MJDTOBEP\texttt{"}  (MJDOFF,BEPOFF): Convert MJD to Besselian epoch.
+
+         \sstitem
+         \texttt{"} BEPTOMJD\texttt{"}  (BEPOFF,MJDOFF): Convert Besselian epoch to MJD.
+
+         \sstitem
+         \texttt{"} MJDTOJEP\texttt{"}  (MJDOFF,JEPOFF): Convert MJD to Julian epoch.
+
+         \sstitem
+         \texttt{"} JEPTOMJD\texttt{"}  (JEPOFF,MJDOFF): Convert Julian epoch to MJD.
+
+         \sstitem
+         \texttt{"} TAITOUTC\texttt{"}  (MJDOFF): Convert a TAI MJD to a UTC MJD.
+
+         \sstitem
+         \texttt{"} UTCTOTAI\texttt{"}  (MJDOFF): Convert a UTC MJD to a TAI MJD.
+
+         \sstitem
+         \texttt{"} TAITOTT\texttt{"}   (MJDOFF): Convert a TAI MJD to a TT MJD.
+
+         \sstitem
+         \texttt{"} TTTOTAI\texttt{"}   (MJDOFF): Convert a TT MJD to a TAI MJD.
+
+         \sstitem
+         \texttt{"} TTTOTDB\texttt{"}   (MJDOFF, OBSLON, OBSLAT, OBSALT): Convert a TT MJD to a TDB MJD.
+
+         \sstitem
+         \texttt{"} TDBTOTT\texttt{"}   (MJDOFF, OBSLON, OBSLAT, OBSALT): Convert a TDB MJD to a TT MJD.
+
+         \sstitem
+         \texttt{"} TTTOTCG\texttt{"}   (MJDOFF): Convert a TT MJD to a TCG MJD.
+
+         \sstitem
+         \texttt{"} TCGTOTT\texttt{"}   (MJDOFF): Convert a TCG MJD to a TT MJD.
+
+         \sstitem
+         \texttt{"} TDBTOTCB\texttt{"}  (MJDOFF): Convert a TDB MJD to a TCB MJD.
+
+         \sstitem
+         \texttt{"} TCBTOTDB\texttt{"}  (MJDOFF): Convert a TCB MJD to a TDB MJD.
+
+         \sstitem
+         \texttt{"} UTTOGMST\texttt{"}  (MJDOFF): Convert a UT MJD to a GMST MJD.
+
+         \sstitem
+         \texttt{"} GMSTTOUT\texttt{"}  (MJDOFF): Convert a GMST MJD to a UT MJD.
+
+         \sstitem
+         \texttt{"} GMSTTOLMST\texttt{"}  (MJDOFF, OBSLON, OBSLAT): Convert a GMST MJD to a LMST MJD.
+
+         \sstitem
+         \texttt{"} LMSTTOGMST\texttt{"}  (MJDOFF, OBSLON, OBSLAT): Convert a LMST MJD to a GMST MJD.
+
+         \sstitem
+         \texttt{"} LASTTOLMST\texttt{"}  (MJDOFF, OBSLON, OBSLAT): Convert a GMST MJD to a LMST MJD.
+
+         \sstitem
+         \texttt{"} LMSTTOLAST\texttt{"}  (MJDOFF, OBSLON, OBSLAT): Convert a LMST MJD to a GMST MJD.
+
+         \sstitem
+         \texttt{"} UTTOUTC\texttt{"}  (DUT1): Convert a UT1 MJD to a UTC MJD.
+
+         \sstitem
+         \texttt{"} UTCTOUT\texttt{"}  (DUT1): Convert a UTC MJD to a UT1 MJD.
+
+         \sstitem
+         \texttt{"} LTTOUTC\texttt{"}  (LTOFF): Convert a Local Time MJD to a UTC MJD.
+
+         \sstitem
+         \texttt{"} UTCTOLT\texttt{"}  (LTOFF): Convert a UTC MJD to a Local Time MJD.
+
+      }
+      The units for the values processed by the above conversions are as
+      follows:
+
+      \sstitemlist{
+
+         \sstitem
+         Julian epochs and offsets: Julian years
+
+         \sstitem
+         Besselian epochs and offsets: Tropical years
+
+         \sstitem
+         Modified Julian Dates and offsets: days
+
+         \sstitem
+         Julian Dates and offsets: days
+
+      }
+      The arguments used in the above conversions are the zero-points
+      used by the
+      astTransform function.
+      The axis values supplied and returned by
+      astTransform
+      are offsets away from these zero-points:
+
+      \sstitemlist{
+
+         \sstitem
+         MJDOFF: The zero-point being used with MJD values.
+
+         \sstitem
+         JDOFF: The zero-point being used with Julian Date values.
+
+         \sstitem
+         BEPOFF: The zero-point being used with Besselian epoch values.
+
+         \sstitem
+         JEPOFF: The zero-point being used with Julian epoch values.
+
+         \sstitem
+         OBSLON: Observer longitude in radians ($+$ve westwards).
+
+         \sstitem
+         OBSLAT: Observer geodetic latitude (IAU 1975) in radians ($+$ve northwards).
+
+         \sstitem
+         OBSALT: Observer geodetic altitude (IAU 1975) in metres.
+
+         \sstitem
+         DUT1: The UT1-UTC value to use.
+
+         \sstitem
+         LTOFF: The offset between Local Time and UTC (in hours, positive
+         for time zones east of Greenwich).
+      }
+   }
+}
+\sstroutine{
+   astTimeFrame
+}{
+   Create a TimeFrame
+}{
+   \sstdescription{
+      This function creates a new \htmlref{TimeFrame}{TimeFrame} and optionally initialises
+      its attributes.
+
+      A TimeFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various coordinate systems used to describe positions in
+      time.
+
+      A TimeFrame represents a moment in time as either an Modified Julian
+      Date (MJD), a Julian Date (JD), a Besselian epoch or a Julian epoch,
+      as determined by the \htmlref{System}{System} attribute. Optionally, a zero point can be
+      specified (using attribute \htmlref{TimeOrigin}{TimeOrigin}) which results in the TimeFrame
+      representing time offsets from the specified zero point.
+
+      Even though JD and MJD are defined as being in units of days, the
+      TimeFrame class allows other units to be used (via the Unit attribute)
+      on the basis of simple scalings (60 seconds = 1 minute, 60 minutes = 1
+      hour, 24 hours = 1 day, 365.25 days = 1 year). Likewise, Julian epochs
+      can be described in units other than the usual years. Besselian epoch
+      are always represented in units of (tropical) years.
+
+      The \htmlref{TimeScale}{TimeScale} attribute allows the time scale to be specified (that
+      is, the physical proces used to define the rate of flow of time).
+      MJD, JD and Julian epoch can be used to represent a time in any
+      supported time scale. However, Besselian epoch may only be used with the
+      \texttt{"} TT\texttt{"}  (Terrestrial Time) time scale. The list of supported time scales
+      includes universal time and siderial time. Strictly, these represent
+      angles rather than time scales, but are included in the list since
+      they are in common use and are often thought of as time scales.
+
+      When a time value is formatted it can be formated either as a simple
+      floating point value, or as a Gregorian date (see the Format
+      attribute).
+   }
+   \sstsynopsis{
+      AstTimeFrame $*$astTimeFrame( const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new TimeFrame. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astTimeFrame()
+      }{
+         A pointer to the new TimeFrame.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When conversion between two TimeFrames is requested (as when
+         supplying TimeFrames to \htmlref{astConvert}{astConvert}),
+         account will be taken of the nature of the time coordinate systems
+         they represent, together with any qualifying time scale, offset,
+         unit, etc. The \htmlref{AlignSystem}{AlignSystem} and \htmlref{AlignTimeScale}{AlignTimeScale} attributes will also be
+         taken into account.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astTimeMap
+}{
+   Create a TimeMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{TimeMap}{TimeMap} and optionally initialises
+      its attributes.
+
+      A TimeMap is a specialised form of 1-dimensional \htmlref{Mapping}{Mapping} which can be
+      used to represent a sequence of conversions between standard time
+      coordinate systems.
+
+      When a TimeMap is first created, it simply performs a unit
+      (null) Mapping. Using the \htmlref{astTimeAdd}{astTimeAdd}
+      function, a series of coordinate conversion steps may then be
+      added. This allows multi-step conversions between a variety of
+      time coordinate systems to be assembled out of a set of building
+      blocks.
+
+      For details of the individual coordinate conversions available,
+      see the description of the astTimeAdd function.
+   }
+   \sstsynopsis{
+      AstTimeMap $*$astTimeMap( int flags, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         flags
+      }{
+         This parameter is reserved for future use and should currently
+         always be set to zero.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new TimeMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+         If no initialisation is required, a zero-length string may be
+         supplied.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astTimeMap()
+      }{
+         A pointer to the new TimeMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The nature and units of the coordinate values supplied for the
+         first input (i.e. the time input) of a TimeMap must be appropriate
+         to the first conversion step applied by the TimeMap. For instance, if
+         the first conversion step is \texttt{"} MJDTOBEP\texttt{"}  (Modified Julian Date to
+         Besselian epoch) then the coordinate values for the first input should
+         be date in units of days. Similarly, the nature and units of the
+         coordinate values returned by a TimeMap will be determined by the
+         last conversion step applied by the TimeMap.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astToString
+}{
+   Create an in-memory serialisation of an Object
+}{
+   \sstdescription{
+      This function returns a string holding a minimal textual
+      serialisation of the supplied AST \htmlref{Object}{Object}. The Object can re
+      re-created from the serialisation using \htmlref{astFromString}{astFromString}.
+   }
+   \sstsynopsis{
+      char $*$astToString( AstObject $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object to be serialised.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astToString()
+      }{
+         Pointer to dynamically allocated memory holding the
+         serialisation, or NULL if an error occurs. The pointer
+         should be freed when no longer needed using \htmlref{astFree}{astFree}.
+      }
+   }
+}
+\sstroutine{
+   astTran1
+}{
+   Transform 1-dimensional coordinates
+}{
+   \sstdescription{
+      This function applies a \htmlref{Mapping}{Mapping} to transform the coordinates of
+      a set of points in one dimension.
+   }
+   \sstsynopsis{
+      void astTran1( AstMapping $*$this, int npoint, const double xin[],
+                     int forward, double xout[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         npoint
+      }{
+         The number of points to be transformed.
+      }
+      \sstsubsection{
+         xin
+      }{
+         An array of \texttt{"} npoint\texttt{"}  coordinate values for the input
+         (untransformed) points.
+      }
+      \sstsubsection{
+         forward
+      }{
+         A non-zero value indicates that the Mapping\texttt{'} s forward
+         coordinate transformation is to be applied, while a zero
+         value indicates that the inverse transformation should be
+         used.
+      }
+      \sstsubsection{
+         xout
+      }{
+         An array (with \texttt{"} npoint\texttt{"}  elements) into which the
+         coordinates of the output (transformed) points will be written.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Mapping supplied must have the value 1 for both its \htmlref{Nin}{Nin}
+         and \htmlref{Nout}{Nout} attributes.
+      }
+   }
+}
+\sstroutine{
+   astTran2
+}{
+   Transform 2-dimensional coordinates
+}{
+   \sstdescription{
+      This function applies a \htmlref{Mapping}{Mapping} to transform the coordinates of
+      a set of points in two dimensions.
+   }
+   \sstsynopsis{
+      void astTran2( AstMapping $*$this,
+                     int npoint, const double xin[], const double yin[],
+                     int forward, double xout[], double yout[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         npoint
+      }{
+         The number of points to be transformed.
+      }
+      \sstsubsection{
+         xin
+      }{
+         An array of \texttt{"} npoint\texttt{"}  X-coordinate values for the input
+         (untransformed) points.
+      }
+      \sstsubsection{
+         yin
+      }{
+         An array of \texttt{"} npoint\texttt{"}  Y-coordinate values for the input
+         (untransformed) points.
+      }
+      \sstsubsection{
+         forward
+      }{
+         A non-zero value indicates that the Mapping\texttt{'} s forward
+         coordinate transformation is to be applied, while a zero
+         value indicates that the inverse transformation should be
+         used.
+      }
+      \sstsubsection{
+         xout
+      }{
+         An array (with \texttt{"} npoint\texttt{"}  elements) into which the
+         X-coordinates of the output (transformed) points will be written.
+      }
+      \sstsubsection{
+         yout
+      }{
+         An array (with \texttt{"} npoint\texttt{"}  elements) into which the
+         Y-coordinates of the output (transformed) points will be written.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Mapping supplied must have the value 2 for both its \htmlref{Nin}{Nin}
+         and \htmlref{Nout}{Nout} attributes.
+      }
+   }
+}
+\sstroutine{
+   astTranGrid
+}{
+   Transform a grid of positions
+}{
+   \sstdescription{
+      This function uses the supplied \htmlref{Mapping}{Mapping} to transforms a regular square
+      grid of points covering a specified box. It attempts to do this
+      quickly by first approximating the Mapping with a linear transformation
+      applied over the whole region of the input grid which is being used.
+      If this proves to be insufficiently accurate, the input region is
+      sub-divided into two along its largest dimension and the process is
+      repeated within each of the resulting sub-regions. This process of
+      sub-division continues until a sufficiently good linear approximation
+      is found, or the region to which it is being applied becomes too small
+      (in which case the original Mapping is used directly).
+   }
+   \sstsynopsis{
+      void astTranGrid( AstMapping $*$this, int ncoord\_in,
+                        const int lbnd[], const int ubnd[],
+                        double tol, int maxpix, int forward,
+                        int ncoord\_out, int outdim, double $*$out );
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         ncoord\_in
+      }{
+         The number of coordinates being supplied for each box corner
+         (i.e. the number of dimensions of the space in which the
+         input points reside).
+      }
+      \sstsubsection{
+         lbnd
+      }{
+         Pointer to an array of integers, with \texttt{"} ncoord\_in\texttt{"}  elements,
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         ubnd
+      }{
+         Pointer to an array of integers, with \texttt{"} ncoord\_in\texttt{"}  elements,
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that \texttt{"} lbnd\texttt{"}  and \texttt{"} ubnd\texttt{"}  together define the shape
+         and size of the input grid, its extent along a particular
+         (j\texttt{'} th) dimension being ubnd[j]-lbnd[j]$+$1 (assuming the
+         index \texttt{"} j\texttt{"}  to be zero-based). They also define
+         the input grid\texttt{'} s coordinate system, each pixel having unit
+         extent along each dimension with integral coordinate values
+         at its centre.
+      }
+      \sstsubsection{
+         tol
+      }{
+         The maximum tolerable geometrical distortion which may be
+         introduced as a result of approximating non-linear Mappings
+         by a set of piece-wise linear transformations. This should be
+         expressed as a displacement within the output coordinate system
+         of the Mapping.
+
+         If piece-wise linear approximation is not required, a value
+         of zero may be given. This will ensure that the Mapping is
+         used without any approximation, but may increase execution
+         time.
+
+         If the value is too high, discontinuities between the linear
+         approximations used in adjacent panel will be higher. If this
+         is a problem, reduce the tolerance value used.
+      }
+      \sstsubsection{
+         maxpix
+      }{
+         A value which specifies an initial scale size (in input grid points)
+         for the adaptive algorithm which approximates non-linear Mappings
+         with piece-wise linear transformations. Normally, this should
+         be a large value (larger than any dimension of the region of
+         the input grid being used). In this case, a first attempt to
+         approximate the Mapping by a linear transformation will be
+         made over the entire input region.
+
+         If a smaller value is used, the input region will first be
+         divided into sub-regions whose size does not exceed \texttt{"} maxpix\texttt{"}
+         grid points in any dimension. Only at this point will attempts
+         at approximation commence.
+
+         This value may occasionally be useful in preventing false
+         convergence of the adaptive algorithm in cases where the
+         Mapping appears approximately linear on large scales, but has
+         irregularities (e.g. holes) on smaller scales. A value of,
+         say, 50 to 100 grid points can also be employed as a safeguard
+         in general-purpose software, since the effect on performance is
+         minimal.
+
+         If too small a value is given, it will have the effect of
+         inhibiting linear approximation altogether (equivalent to
+         setting \texttt{"} tol\texttt{"}  to zero). Although this may degrade
+         performance, accurate results will still be obtained.
+      }
+      \sstsubsection{
+         forward
+      }{
+         A non-zero value indicates that the Mapping\texttt{'} s forward
+         coordinate transformation is to be applied, while a zero
+         value indicates that the inverse transformation should be
+         used.
+      }
+      \sstsubsection{
+         ncoord\_out
+      }{
+         The number of coordinates being generated by the Mapping for
+         each output point (i.e. the number of dimensions of the
+         space in which the output points reside). This need not be
+         the same as \texttt{"} ncoord\_in\texttt{"} .
+      }
+      \sstsubsection{
+         outdim
+      }{
+         The number of elements along the second dimension of the \texttt{"} out\texttt{"}
+         array (which will contain the output coordinates). The value
+         given should not be less than the number of points in the grid.
+      }
+      \sstsubsection{
+         out
+      }{
+         The address of the first element in a 2-dimensional array of
+         shape \texttt{"} [ncoord\_out][outdim]\texttt{"} , into
+         which the coordinates of the output (transformed) points will
+         be written. These will be stored such that the value of
+         coordinate number \texttt{"} coord\texttt{"}  for output point number \texttt{"} point\texttt{"}
+         will be found in element \texttt{"} out[coord][point]\texttt{"} .
+         The points are ordered such that the first axis of the input
+         grid changes most rapidly. For example, if the input grid is
+         2-dimensional and extends from (2,-1) to (3,1), the output
+         points will be stored in the order (2,-1), (3, -1), (2,0), (3,0),
+         (2,1), (3,1).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the forward coordinate transformation is being applied, the
+         Mapping supplied must have the value of \texttt{"} ncoord\_in\texttt{"}  for its \htmlref{Nin}{Nin}
+         attribute and the value of \texttt{"} ncoord\_out\texttt{"}  for its \htmlref{Nout}{Nout} attribute. If
+         the inverse transformation is being applied, these values should
+         be reversed.
+      }
+   }
+}
+\sstroutine{
+   astTranMap
+}{
+   Create a TranMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{TranMap}{TranMap} and optionally initialises
+      its attributes.
+
+      A TranMap is a \htmlref{Mapping}{Mapping} which combines the forward transformation of
+      a supplied Mapping with the inverse transformation of another
+      supplied Mapping, ignoring the un-used transformation in each
+      Mapping (indeed the un-used transformation need not exist).
+
+      When the forward transformation of the TranMap is referred to, the
+      transformation actually used is the forward transformation of the
+      first Mapping supplied when the TranMap was constructed. Likewise,
+      when the inverse transformation of the TranMap is referred to, the
+      transformation actually used is the inverse transformation of the
+      second Mapping supplied when the TranMap was constructed.
+   }
+   \sstsynopsis{
+      AstTranMap $*$astTranMap( AstMapping $*$map1, AstMapping $*$map2,
+                            const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         map1
+      }{
+         Pointer to the first component Mapping, which defines the
+         forward transformation.
+      }
+      \sstsubsection{
+         map2
+      }{
+         Pointer to the second component Mapping, which defines the
+         inverse transformation.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new TranMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astTranMap()
+      }{
+         A pointer to the new TranMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The number of output coordinates generated by the two Mappings
+         (their \htmlref{Nout}{Nout} attribute) must be equal, as must the number of input
+         coordinates accepted by each Mapping (their \htmlref{Nin}{Nin} attribute).
+
+         \sstitem
+         The forward transformation of the first Mapping must exist.
+
+         \sstitem
+         The inverse transformation of the second Mapping must exist.
+
+         \sstitem
+         Note that the component Mappings supplied are not copied by
+         astTranMap (the new TranMap simply retains a reference to
+         them). They may continue to be used for other purposes, but
+         should not be deleted. If a TranMap containing a copy of its
+         component Mappings is required, then a copy of the TranMap should
+         be made using \htmlref{astCopy}{astCopy}.
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astTranN
+}{
+   Transform N-dimensional coordinates
+}{
+   \sstdescription{
+      This function applies a \htmlref{Mapping}{Mapping} to transform the coordinates of
+      a set of points in an arbitrary number of dimensions. It is the
+      appropriate routine to use if the coordinates are not purely 1-
+      or 2-dimensional and are stored in a single array (which they
+      need not fill completely).
+
+      If the coordinates are not stored in a single array, then the
+      \htmlref{astTranP}{astTranP} function might be more suitable.
+   }
+   \sstsynopsis{
+      void astTranN( AstMapping $*$this, int npoint,
+                     int ncoord\_in, int indim, const double $*$in,
+                     int forward,
+                     int ncoord\_out, int outdim, double $*$out )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         npoint
+      }{
+         The number of points to be transformed.
+      }
+      \sstsubsection{
+         ncoord\_in
+      }{
+         The number of coordinates being supplied for each input point
+         (i.e. the number of dimensions of the space in which the
+         input points reside).
+      }
+      \sstsubsection{
+         indim
+      }{
+         The number of elements along the second dimension of the \texttt{"} in\texttt{"}
+         array (which contains the input coordinates). This value is
+         required so that the coordinate values can be correctly
+         located if they do not entirely fill this array. The value
+         given should not be less than \texttt{"} npoint\texttt{"} .
+      }
+      \sstsubsection{
+         in
+      }{
+         The address of the first element in a 2-dimensional array of
+         shape \texttt{"} [ncoord\_in][indim]\texttt{"} ,
+         containing the coordinates of the input (untransformed)
+         points. These should be stored such that the value of
+         coordinate number \texttt{"} coord\texttt{"}  for input point number \texttt{"} point\texttt{"}  is
+         found in element \texttt{"} in[coord][point]\texttt{"} .
+      }
+      \sstsubsection{
+         forward
+      }{
+         A non-zero value indicates that the Mapping\texttt{'} s forward
+         coordinate transformation is to be applied, while a zero
+         value indicates that the inverse transformation should be
+         used.
+      }
+      \sstsubsection{
+         ncoord\_out
+      }{
+         The number of coordinates being generated by the Mapping for
+         each output point (i.e. the number of dimensions of the
+         space in which the output points reside). This need not be
+         the same as \texttt{"} ncoord\_in\texttt{"} .
+      }
+      \sstsubsection{
+         outdim
+      }{
+         The number of elements along the second dimension of the \texttt{"} out\texttt{"}
+         array (which will contain the output coordinates). This value
+         is required so that the coordinate values can be correctly
+         located if they will not entirely fill this array. The value
+         given should not be less than \texttt{"} npoint\texttt{"} .
+      }
+      \sstsubsection{
+         out
+      }{
+         The address of the first element in a 2-dimensional array of
+         shape \texttt{"} [ncoord\_out][outdim]\texttt{"} , into
+         which the coordinates of the output (transformed) points will
+         be written. These will be stored such that the value of
+         coordinate number \texttt{"} coord\texttt{"}  for output point number \texttt{"} point\texttt{"}
+         will be found in element \texttt{"} out[coord][point]\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the forward coordinate transformation is being applied, the
+         Mapping supplied must have the value of \texttt{"} ncoord\_in\texttt{"}  for its \htmlref{Nin}{Nin}
+         attribute and the value of \texttt{"} ncoord\_out\texttt{"}  for its \htmlref{Nout}{Nout} attribute. If
+         the inverse transformation is being applied, these values should
+         be reversed.
+      }
+   }
+}
+\sstroutine{
+   astTranP
+}{
+   Transform N-dimensional coordinates held in separate arrays
+}{
+   \sstdescription{
+      This function applies a \htmlref{Mapping}{Mapping} to transform the coordinates of
+      a set of points in an arbitrary number of dimensions. It is the
+      appropriate routine to use if the coordinates are not purely 1-
+      or 2-dimensional and are stored in separate arrays, since each
+      coordinate array is located by supplying a separate pointer to
+      it.
+
+      If the coordinates are stored in a single (2-dimensional) array,
+      then the \htmlref{astTranN}{astTranN} function might be more suitable.
+   }
+   \sstsynopsis{
+      void astTranP( AstMapping $*$this, int npoint,
+                     int ncoord\_in, const double $*$ptr\_in[],
+                     int forward, int ncoord\_out, double $*$ptr\_out[] )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Mapping to be applied.
+      }
+      \sstsubsection{
+         npoint
+      }{
+         The number of points to be transformed.
+      }
+      \sstsubsection{
+         ncoord\_in
+      }{
+         The number of coordinates being supplied for each input point
+         (i.e. the number of dimensions of the space in which the
+         input points reside).
+      }
+      \sstsubsection{
+         ptr\_in
+      }{
+         An array of pointers to double, with \texttt{"} ncoord\_in\texttt{"}
+         elements. Element \texttt{"} ptr\_in[coord]\texttt{"}  should point at the first
+         element of an array of double (with \texttt{"} npoint\texttt{"}  elements) which
+         contain the values of coordinate number \texttt{"} coord\texttt{"}  for each
+         input (untransformed) point. The value of coordinate number
+         \texttt{"} coord\texttt{"}  for input point number \texttt{"} point\texttt{"}  is therefore given by
+         \texttt{"} ptr\_in[coord][point]\texttt{"}  (assuming both indices are
+         zero-based).
+      }
+      \sstsubsection{
+         forward
+      }{
+         A non-zero value indicates that the Mapping\texttt{'} s forward
+         coordinate transformation is to be applied, while a zero
+         value indicates that the inverse transformation should be
+         used.
+      }
+      \sstsubsection{
+         ncoord\_out
+      }{
+         The number of coordinates being generated by the Mapping for
+         each output point (i.e. the number of dimensions of the space
+         in which the output points reside). This need not be the same
+         as \texttt{"} ncoord\_in\texttt{"} .
+      }
+      \sstsubsection{
+         ptr\_out
+      }{
+         An array of pointers to double, with \texttt{"} ncoord\_out\texttt{"}
+         elements. Element \texttt{"} ptr\_out[coord]\texttt{"}  should point at the first
+         element of an array of double (with \texttt{"} npoint\texttt{"}  elements) into
+         which the values of coordinate number \texttt{"} coord\texttt{"}  for each output
+         (transformed) point will be written.  The value of coordinate
+         number \texttt{"} coord\texttt{"}  for output point number \texttt{"} point\texttt{"}  will therefore
+         be found in \texttt{"} ptr\_out[coord][point]\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the forward coordinate transformation is being applied, the
+         Mapping supplied must have the value of \texttt{"} ncoord\_in\texttt{"}  for its \htmlref{Nin}{Nin}
+         attribute and the value of \texttt{"} ncoord\_out\texttt{"}  for its \htmlref{Nout}{Nout}
+         attribute. If the inverse transformation is being applied, these
+         values should be reversed.
+
+         \sstitem
+         This routine is not available in the Fortran 77 interface to
+         the AST library.
+      }
+   }
+}
+\sstroutine{
+   astTune
+}{
+   Set or get an integer-valued AST global tuning parameter
+}{
+   \sstdescription{
+      This function returns the current value of an integer-valued AST
+      global tuning parameter, optionally storing a new value for the
+      parameter. For character-valued tuning parameters, see
+      \htmlref{astTuneC}{astTuneC}.
+   }
+   \sstsynopsis{
+      int astTune( const char $*$name, int value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         name
+      }{
+         The name of the tuning parameter (case-insensitive).
+      }
+      \sstsubsection{
+         value
+      }{
+         The new value for the tuning parameter. If this is AST\_\_TUNULL,
+         the existing current value will be retained.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astTune()
+      }{
+         The original value of the tuning parameter. A default value will
+         be returned if no value has been set for the parameter.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function attempts to execute even if the AST error
+         status is set
+         on entry, although no further error report will be
+         made if it subsequently fails under these circumstances.
+
+         \sstitem
+         All threads in a process share the same AST tuning parameters
+         values.
+      }
+   }
+   \sstdiylist{
+      Tuning Parameters
+   }{
+      \sstsubsection{
+         ObjectCaching
+      }{
+         A boolean flag which indicates what should happen
+         to the memory occupied by an AST \htmlref{Object}{Object} when the Object is deleted
+         (i.e. when its reference count falls to zero or it is deleted using
+         \htmlref{astDelete}{astDelete}).
+         If this is zero, the memory is simply freed using the systems \texttt{"} free\texttt{"}
+         function. If it is non-zero, the memory is not freed. Instead a
+         pointer to it is stored in a pool of such pointers, all of which
+         refer to allocated but currently unused blocks of memory. This allows
+         AST to speed up subsequent Object creation by re-using previously
+         allocated memory blocks rather than allocating new memory using the
+         systems malloc function. The default value for this parameter is
+         zero. Setting it to a non-zero value will result in Object memory
+         being cached in future. Setting it back to zero causes any memory
+         blocks currently in the pool to be freed. Note, this tuning parameter
+         only controls the caching of memory used to store AST Objects. To
+         cache other memory blocks allocated by AST, use MemoryCaching.
+      }
+      \sstsubsection{
+         MemoryCaching
+      }{
+         A boolean flag similar to ObjectCaching except
+         that it controls caching of all memory blocks of less than 300 bytes
+         allocated by AST (whether for internal or external use), not just
+         memory used to store AST Objects.
+      }
+   }
+}
+\sstroutine{
+   astTuneC
+}{
+   Set or get a character-valued AST global tuning parameter
+}{
+   \sstdescription{
+      This function returns the current value of a character-valued
+      AST global tuning parameter, optionally storing a new value
+      for the parameter. For integer-valued tuning parameters, see
+      \htmlref{astTune}{astTune}.
+   }
+   \sstsynopsis{
+      void astTuneC( const char $*$name, const char $*$value, char $*$buff,
+                     int bufflen )
+   }
+   \sstparameters{
+      \sstsubsection{
+         name
+      }{
+         The name of the tuning parameter (case-insensitive).
+      }
+      \sstsubsection{
+         value
+      }{
+         The new value for the tuning parameter. If this is
+         NULL,
+         the existing current value will be retained.
+      }
+      \sstsubsection{
+         buff
+      }{
+         A character string in which to return the original value of
+         the tuning parameter. An error will be reported if the buffer
+         is too small to hold the value.
+         NULL may be supplied if the old value is not required.
+      }
+      \sstsubsection{
+         bufflen
+      }{
+         The size of the supplied \texttt{"} buff\texttt{"}  array. Ignored if \texttt{"} buff\texttt{"}  is NULL.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function attempts to execute even if the AST error
+         status is set
+         on entry, although no further error report will be
+         made if it subsequently fails under these circumstances.
+
+         \sstitem
+         All threads in a process share the same AST tuning parameters
+         values.
+      }
+   }
+   \sstdiylist{
+      Tuning Parameters
+   }{
+      \sstsubsection{
+         HRDel
+      }{
+         A string to be drawn following the hours field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use (see the Format
+         attribute). This string may include escape sequences to produce
+         super-scripts, etc. (see the Escapes attribute for details
+         of the escape sequences allowed). The default value is
+         \texttt{"} \%-\%$\wedge$50$+$\%s70$+$h\%$+$\texttt{"}  which produces a super-script \texttt{"} h\texttt{"} .
+      }
+      \sstsubsection{
+         MNDel
+      }{
+         A string to be drawn following the minutes field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use. The default value is
+         \texttt{"} \%-\%$\wedge$50$+$\%s70$+$m\%$+$\texttt{"}  which produces a super-script \texttt{"} m\texttt{"} .
+      }
+      \sstsubsection{
+         SCDel
+      }{
+         A string to be drawn following the seconds field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use. The default value is
+         \texttt{"} \%-\%$\wedge$50$+$\%s70$+$s\%$+$\texttt{"}  which produces a super-script \texttt{"} s\texttt{"} .
+      }
+      \sstsubsection{
+         DGDel
+      }{
+         A string to be drawn following the degrees field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use. The default value is
+         \texttt{"} \%-\%$\wedge$53$+$\%s60$+$o\%$+$\texttt{"}  which produces a super-script \texttt{"} o\texttt{"} .
+      }
+      \sstsubsection{
+         AMDel
+      }{
+         A string to be drawn following the arc-minutes field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use. The default value is
+         \texttt{"} \%-\%$\wedge$20$+$\%s85$+$\texttt{'} \%$+$\texttt{"}  which produces a super-script \texttt{"} \texttt{'} \texttt{"}  (single quote).
+      }
+      \sstsubsection{
+         ASDel
+      }{
+         A string to be drawn following the arc-seconds field in a formatted
+         sky axis value when \texttt{"} g\texttt{"}  format is in use. The default value is
+         \texttt{"} \%-\%$\wedge$20$+$\%s85$+$$\backslash$\texttt{"} \%$+$\texttt{"}  which produces a super-script \texttt{"} \texttt{"} \texttt{"}  (double quote).
+      }
+      \sstsubsection{
+         EXDel
+      }{
+         A string to be drawn to introduce the exponent in a value when \texttt{"} g\texttt{"}
+         format is in use. The default value is \texttt{"} 10\%-\%$\wedge$50$+$\%s70$+$\texttt{"}  which
+         produces \texttt{"} 10\texttt{"}  followed by the exponent as a super-script.
+      }
+   }
+}
+\sstroutine{
+   astUinterp
+}{
+   Perform sub-pixel interpolation on a grid of data
+}{
+   \sstdescription{
+      This is a fictitious function which does not actually
+      exist. Instead, this description constitutes a template so that
+      you may implement a function with this interface for yourself
+      (and give it any name you wish). A pointer to such a function
+      may be passed via the \texttt{"} finterp\texttt{"}  parameter of the \htmlref{astResample$<$X$>$}{astResample$<$X$>$}
+      functions (q.v.) in order to perform sub-pixel interpolation
+      during resampling of gridded data (you must also set the
+      \texttt{"} interp\texttt{"}  parameter of astResample$<$X$>$ to the value
+      AST\_\_UINTERP). This allows you to use your own interpolation
+      algorithm in addition to those which are pre-defined.
+
+      The function interpolates an input grid of data (and,
+      optionally, processes associated statistical variance estimates)
+      at a specified set of points.
+   }
+   \sstsynopsis{
+      void astUinterp( int ndim\_in, const int lbnd\_in[], const int ubnd\_in[],
+                       const $<$Xtype$>$ in[], const $<$Xtype$>$ in\_var[],
+                       int npoint, const int offset[],
+                       const double $*$const coords[], const double params[],
+                       int flags, $<$Xtype$>$ badval,
+                       $<$Xtype$>$ out[], $<$Xtype$>$ out\_var[], int $*$nbad )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ndim\_in
+      }{
+         The number of dimensions in the input grid. This will be at
+         least one.
+      }
+      \sstsubsection{
+         lbnd\_in
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the centre of the first pixel
+         in the input grid along each dimension.
+      }
+      \sstsubsection{
+         ubnd\_in
+      }{
+         Pointer to an array of integers, with \texttt{"} ndim\_in\texttt{"}  elements,
+         containing the coordinates of the centre of the last pixel in
+         the input grid along each dimension.
+
+         Note that \texttt{"} lbnd\_in\texttt{"}  and \texttt{"} ubnd\_in\texttt{"}  together define the shape,
+         size and coordinate system of the input grid in the same
+         way as they do in astResample$<$X$>$.
+      }
+      \sstsubsection{
+         in
+      }{
+         Pointer to an array, with one element for each pixel in the
+         input grid, containing the input data. This will be the same
+         array as was passed to astResample$<$X$>$ via the \texttt{"} in\texttt{"}  parameter.
+         The numerical type of this array should match that of the
+         data being processed.
+      }
+      \sstsubsection{
+         in\_var
+      }{
+         Pointer to an optional second array with the same size and
+         type as the \texttt{"} in\texttt{"}  array. If given, this will contain the set
+         of variance values associated with the input data and will be
+         the same array as was passed to astResample$<$X$>$ via the
+         \texttt{"} in\_var\texttt{"}  parameter.
+
+         If no variance values are being processed, this will be a
+         NULL pointer.
+      }
+      \sstsubsection{
+         npoint
+      }{
+         The number of points at which the input grid is to be
+         interpolated. This will be at least one.
+      }
+      \sstsubsection{
+         offset
+      }{
+         Pointer to an array of integers with \texttt{"} npoint\texttt{"}  elements. For
+         each interpolation point, this will contain the zero-based
+         index in the \texttt{"} out\texttt{"}  (and \texttt{"} out\_var\texttt{"} ) array(s) at which the
+         interpolated value (and its variance, if required) should be
+         stored. For example, the interpolated value for point number
+         \texttt{"} point\texttt{"}  should be stored in \texttt{"} out[offset[point]]\texttt{"}  (assuming
+         the index \texttt{"} point\texttt{"}  is zero-based).
+      }
+      \sstsubsection{
+         coords
+      }{
+         An array of pointers to double, with \texttt{"} ndim\_in\texttt{"}
+         elements. Element \texttt{"} coords[coord]\texttt{"}  will point at the first
+         element of an array of double (with \texttt{"} npoint\texttt{"}  elements) which
+         contains the values of coordinate number \texttt{"} coord\texttt{"}  for each
+         interpolation point. The value of coordinate number \texttt{"} coord\texttt{"}
+         for interpolation point number \texttt{"} point\texttt{"}  is therefore given by
+         \texttt{"} coords[coord][point]\texttt{"}  (assuming both indices are
+         zero-based).
+
+         If any interpolation point has any of its coordinates equal
+         to the value AST\_\_BAD (as defined in the \texttt{"} ast.h\texttt{"}  header
+         file), then the corresponding output data (and variance)
+         should either be set to the value given by \texttt{"} badval\texttt{"} ,
+         or left unchanged, depending on whether the AST\_\_NOBAD flag is
+         specified by \texttt{"} flags\texttt{"} .
+      }
+      \sstsubsection{
+         params
+      }{
+         This will be a pointer to the same array as was given via the
+         \texttt{"} params\texttt{"}  parameter of astResample$<$X$>$. You may use this to
+         pass any additional parameter values required by your
+         interpolation algorithm.
+      }
+      \sstsubsection{
+         flags
+      }{
+         This will be the same value as was given via the \texttt{"} flags\texttt{"}
+         parameter of astResample$<$X$>$. You may test this value to
+         provide additional control over the operation of your
+         resampling algorithm. Note that the special flag values
+         AST\_\_URESAMP1, 2, 3 \& 4 are reserved for you to use for your
+         own purposes and will not clash with other pre-defined flag
+         values (see astResample$<$X$>$).
+      }
+      \sstsubsection{
+         badval
+      }{
+         This will be the same value as was given via the \texttt{"} badval\texttt{"}
+         parameter of astResample$<$X$>$, and will have the same numerical
+         type as the data being processed (i.e. as elements of the
+         \texttt{"} in\texttt{"}  array).  It should be used to test for bad pixels in the
+         input grid (but only if the AST\_\_USEBAD flag is set via the
+         \texttt{"} flags\texttt{"}  parameter) and (unless the AST\_\_NOBAD flag is set in
+         \texttt{"} flags\texttt{"} ) for identifying bad output values in
+         the \texttt{"} out\texttt{"}  (and \texttt{"} out\_var\texttt{"} ) array(s).
+      }
+      \sstsubsection{
+         out
+      }{
+         Pointer to an array with the same numerical type as the \texttt{"} in\texttt{"}
+         array, into which the interpolated data values should be
+         returned.  Note that details of the storage order and number
+         of dimensions of this array are not required, since the
+         \texttt{"} offset\texttt{"}  array contains all necessary information about where
+         each returned value should be stored.
+
+         In general, not all elements of this array (or the \texttt{"} out\_var\texttt{"}
+         array below) may be used in any particular invocation of the
+         function. Those which are not used should be returned
+         unchanged.
+      }
+      \sstsubsection{
+         out\_var
+      }{
+         Pointer to an optional array with the same type and size as
+         the \texttt{"} out\texttt{"}  array, into which variance estimates for the
+         resampled values should be returned.  This array will only be
+         given if the \texttt{"} in\_var\texttt{"}  array has also been given.
+
+         If given, it is addressed in exactly the same way (via the
+         \texttt{"} offset\texttt{"}  array) as the \texttt{"} out\texttt{"}  array. The values returned
+         should be estimates of the statistical variance of the
+         corresponding values in the \texttt{"} out\texttt{"}  array, on the assumption
+         that all errors in input data values are statistically
+         independent and that their variance estimates may simply be
+         summed (with appropriate weighting factors).
+
+         If no output variance estimates are required, a NULL pointer
+         will be given.
+      }
+      \sstsubsection{
+         nbad
+      }{
+         Pointer to an int in which to return the number of interpolation
+         points at
+         which no valid interpolated value could be obtained.  The maximum
+         value that should be returned is \texttt{"} npoint\texttt{"} , and the minimum is
+         zero (indicating that all output values were successfully
+         obtained).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The data type $<$Xtype$>$ indicates the numerical type of the data
+         being processed, as for astResample$<$X$>$.
+
+         \sstitem
+         This function will typically be invoked more than once for each
+         invocation of astResample$<$X$>$.
+
+         \sstitem
+         If an error occurs within this function, it should use
+         \htmlref{astSetStatus}{astSetStatus} to set the AST error status to an error value.
+         This will cause an immediate return from astResample$<$X$>$. The error
+         value AST\_\_UINER is available for this purpose, but other values may
+         also be used (e.g. if you wish to distinguish different types of
+         error).
+      }
+   }
+}
+\sstroutine{
+   astUkern1
+}{
+   1-dimensional sub-pixel interpolation kernel
+}{
+   \sstdescription{
+      This is a fictitious function which does not actually
+      exist. Instead, this description constitutes a template so that
+      you may implement a function with this interface for yourself
+      (and give it any name you wish). A pointer to such a function
+      may be passed via the \texttt{"} finterp\texttt{"}  parameter of the \htmlref{astResample$<$X$>$}{astResample$<$X$>$}
+      functions (q.v.) in order to supply a 1-dimensional
+      interpolation kernel to the algorithm which performs sub-pixel
+      interpolation during resampling of gridded data (you must also
+      set the \texttt{"} interp\texttt{"}  parameter of astResample$<$X$>$ to the value
+      AST\_\_UKERN1). This allows you to use your own interpolation
+      kernel in addition to those which are pre-defined.
+
+      The function calculates the value of a 1-dimensional sub-pixel
+      interpolation kernel. This determines how the weight given to
+      neighbouring pixels in calculating an interpolated value depends
+      on the pixel\texttt{'} s offset from the interpolation point. In more than
+      one dimension, the weight assigned to a pixel is formed by
+      evaluating this 1-dimensional kernel using the offset along each
+      dimension in turn. The product of the returned values is then
+      used as the pixel weight.
+   }
+   \sstsynopsis{
+      void astUkern1( double offset, const double params[], int flags,
+                      double $*$value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         offset
+      }{
+         This will be the offset of the pixel from the interpolation
+         point, measured in pixels. This value may be positive or
+         negative, but for most practical interpolation schemes its
+         sign should be ignored.
+      }
+      \sstsubsection{
+         params
+      }{
+         This will be a pointer to the same array as was given via the
+         \texttt{"} params\texttt{"}  parameter of astResample$<$X$>$. You may use this to
+         pass any additional parameter values required by your kernel,
+         but note that \texttt{"} params[0]\texttt{"}  will already have been used to specify
+         the number of neighbouring pixels which contribute to the
+         interpolated value.
+      }
+      \sstsubsection{
+         flags
+      }{
+         This will be the same value as was given via the \texttt{"} flags\texttt{"}
+         parameter of astResample$<$X$>$. You may test this value to
+         provide additional control over the operation of your
+         function. Note that the special flag values AST\_\_URESAMP1, 2,
+         3 \& 4 are reserved for you to use for your own purposes and
+         will not clash with other pre-defined flag
+         values (see astResample$<$X$>$).
+      }
+      \sstsubsection{
+         value
+      }{
+         Pointer to a double to receive the calculated kernel value,
+         which may be positive or negative.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Not all functions make good interpolation kernels. In general,
+         acceptable kernels tend to be symmetrical about zero, to have a
+         positive peak (usually unity) at zero, and to evaluate to zero
+         whenever the pixel offset has any other integral value (this
+         ensures that the interpolated values pass through the original
+         data). An interpolation kernel may or may not have regions with
+         negative values. You should consult a good book on image
+         processing for more details.
+
+         \sstitem
+         If an error occurs within this function, it should use
+         \htmlref{astSetStatus}{astSetStatus} to set the AST error status to an error value.
+         This will cause an immediate return from astResample$<$X$>$. The error
+         value AST\_\_UK1ER is available for this purpose, but other values may
+         also be used (e.g. if you wish to distinguish different types of
+         error).
+      }
+   }
+}
+\sstroutine{
+   astUnformat
+}{
+   Read a formatted coordinate value for a Frame axis
+}{
+   \sstdescription{
+      This function reads a formatted coordinate value (given as a
+      character string) for a \htmlref{Frame}{Frame} axis and returns the equivalent
+      numerical (double) value. It also returns the number of
+      characters read from the string.
+
+      The principle use of this function is in decoding user-supplied
+      input which contains formatted coordinate values. Free-format
+      input is supported as far as possible. If input is ambiguous, it
+      is interpreted with reference to the Frame\texttt{'} s attributes (in
+      particular, the Format string associated with the Frame\texttt{'} s
+      axis). This function is, in essence, the inverse of \htmlref{astFormat}{astFormat}.
+   }
+   \sstsynopsis{
+      int astUnformat( AstFrame $*$this, int axis, const char $*$string,
+                       double $*$value )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Frame.
+      }
+      \sstsubsection{
+         axis
+      }{
+         The number of the Frame axis for which a coordinate value is to
+         be read (axis numbering starts at 1 for the first axis).
+      }
+      \sstsubsection{
+         string
+      }{
+         Pointer to a null-terminated character string containing the
+         formatted coordinate value.
+         This string may contain additional information following the
+         value to be read, in which case reading stops at the first
+         character which cannot be interpreted as part of the value.
+         Any white space before or after the value is discarded.
+      }
+      \sstsubsection{
+         value
+      }{
+         Pointer to a double in which the coordinate value read will be
+         returned.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         This function applies to all Frames. See the \texttt{"} Frame Input
+         Format\texttt{"}  section below for details of the input formats
+         accepted by a basic Frame.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the input format to be suitable
+         for representing angles and times, with the resulting
+         coordinate value returned in radians.  See the \texttt{"} SkyFrame
+         Input Format\texttt{"}  section below for details of the formats
+         accepted.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The input formats accepted by a FrameSet are determined by
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astUnformat()
+      }{
+         The number of characters read from the string in order to
+         obtain the coordinate value. This will include any white
+         space which occurs before or after the value.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A function value of zero (and no coordinate value) will be
+         returned, without error, if the string supplied does not contain
+         a suitably formatted value.
+
+         \sstitem
+         Beware that it is possible for a formatting error part-way
+         through an input string to terminate input before it has been
+         completely read, but to yield a coordinate value that appears
+         valid. For example, if a user types \texttt{"} 1.5r6\texttt{"}  instead of \texttt{"} 1.5e6\texttt{"} ,
+         the \texttt{"} r\texttt{"}  will terminate input, giving an incorrect coordinate
+         value of 1.5. It is therefore most important to check the return
+         value of this function to ensure that the correct number of
+         characters have been read.
+
+         \sstitem
+         An error will result if a value is read which appears to have
+         the correct format, but which cannot be converted into a valid
+         coordinate value (for instance, because the value of one or more
+         of its fields is invalid).
+
+         \sstitem
+         The string \texttt{"} $<$bad$>$\texttt{"}  is recognised as a special case and will
+         yield the coordinate value AST\_\_BAD without error. The test for
+         this string is case-insensitive and also permits embedded white
+         space.
+
+         \sstitem
+         A function result of zero will be returned and no coordinate
+         value will be returned via the \texttt{"} value\texttt{"}  pointer if this function
+         is invoked with the AST error status set, or if it should fail
+         for any reason.
+      }
+   }
+   \sstdiytopic{
+      Frame Input Format
+   }{
+      The input format accepted for a basic Frame axis is as follows:
+      \sstitemlist{
+
+         \sstitem
+         An optional sign, followed by:
+
+         \sstitem
+         A sequence of one or more digits possibly containing a decimal point,
+         followed by:
+
+         \sstitem
+         An optional exponent field.
+
+         \sstitem
+         The exponent field, if present, consists of \texttt{"} E\texttt{"}  or \texttt{"} e\texttt{"}
+         followed by a possibly signed integer.
+
+      }
+      Examples of acceptable Frame input formats include:
+      \sstitemlist{
+
+         \sstitem
+         99
+
+         \sstitem
+         1.25
+
+         \sstitem
+         -1.6
+
+         \sstitem
+         1E8
+
+         \sstitem
+         -.99e-17
+
+         \sstitem
+         $<$bad$>$
+      }
+   }
+   \sstdiytopic{
+      SkyFrame Input Format
+   }{
+      The input format accepted for a SkyFrame axis is as follows:
+      \sstitemlist{
+
+         \sstitem
+         An optional sign, followed by between one and three fields
+         representing either degrees, arc-minutes, arc-seconds or hours,
+         minutes, seconds (e.g. \texttt{"} -12 42 03\texttt{"} ).
+
+         \sstitem
+         Each field should consist of a sequence of one or more digits,
+         which may include leading zeros. At most one field may contain a
+         decimal point, in which case it is taken to be the final field
+         (e.g. decimal degrees might be given as \texttt{"} 124.707\texttt{"} , while degrees
+         and decimal arc-minutes might be given as \texttt{"} -13 33.8\texttt{"} ).
+
+         \sstitem
+         The first field given may take any value, allowing angles and
+         times outside the conventional ranges to be
+         represented. However, subsequent fields must have values of less
+         than 60 (e.g. \texttt{"} 720 45 31\texttt{"}  is valid, whereas \texttt{"} 11 45 61\texttt{"}  is not).
+
+         \sstitem
+         Fields may be separated by white space or by \texttt{"} :\texttt{"}  (colon), but
+         the choice of separator must be used consistently throughout the
+         value. Additional white space may be present around fields and
+         separators (e.g. \texttt{"} - 2: 04 : 7.1\texttt{"} ).
+
+         \sstitem
+         The following field identification characters may be used as
+         separators to replace either of those above (or may be appended
+         to the final field), in order to identify the field to which
+         they are appended: \texttt{"} d\texttt{"} ---degrees; \texttt{"} h\texttt{"} ---hours; \texttt{"} m\texttt{"} ---minutes of
+         arc or time; \texttt{"} s\texttt{"} ---seconds of arc or time; \texttt{"} \texttt{'} \texttt{"}  (single
+         quote)---minutes of arc; \texttt{"} \texttt{"} \texttt{"}  (double quote)---seconds of arc.
+         Either lower or upper case may be used.  Fields must be given in
+         order of decreasing significance (e.g. \texttt{"} -11D 3\texttt{'}  14.4\texttt{"} \texttt{"}  or
+         \texttt{"} 22h14m11.2s\texttt{"} ).
+
+         \sstitem
+         The presence of any of the field identification characters
+         \texttt{"} d\texttt{"} , \texttt{"} \texttt{'} \texttt{"}  (single quote) or \texttt{"} \texttt{"} \texttt{"}  (double quote) indicates that the
+         value is to be interpreted as an angle. Conversely, the presence
+         of \texttt{"} h\texttt{"}  indicates that it is to be interpreted as a time (with 24
+         hours corresponding to 360 degrees). Incompatible angle/time
+         identification characters may not be mixed (e.g. \texttt{"} 10h14\texttt{'} 3\texttt{"} \texttt{"}  is
+         not valid).  The remaining field identification characters and
+         separators do not specify a preference for an angle or a time
+         and may be used with either.
+
+         \sstitem
+         If no preference for an angle or a time is expressed anywhere
+         within the value, it is interpreted as an angle if the Format
+         attribute string associated with the SkyFrame axis generates an
+         angle and as a time otherwise. This ensures that values produced
+         by astFormat are correctly interpreted by astUnformat.
+
+         \sstitem
+         Fields may be omitted, in which case they default to zero. The
+         remaining fields may be identified by using appropriate field
+         identification characters (see above) and/or by adding extra
+         colon separators (e.g. \texttt{"} -05m13s\texttt{"}  is equivalent to \texttt{"} -:05:13\texttt{"} ). If
+         a field is not identified explicitly, it is assumed that
+         adjacent fields have been given, after taking account of any
+         extra separator characters (e.g. \texttt{"} 14:25.4s\texttt{"}  specifies minutes
+         and seconds, while \texttt{"} 14::25.4s\texttt{"}  specifies degrees and seconds).
+
+         \sstitem
+         If fields are omitted in such a way that the remaining ones
+         cannot be identified uniquely (e.g. \texttt{"} 01:02\texttt{"} ), then the first
+         field (either given explicitly or implied by an extra leading
+         colon separator) is taken to be the most significant field that
+         astFormat would produce when formatting a value (using the
+         Format attribute associated with the SkyFrame axis).  By
+         default, this means that the first field will normally be
+         interpreted as degrees or hours. However, if this does not
+         result in consistent field identification, then the last field
+         (either given explicitly or implied by an extra trailing colon
+         separator) is taken to to be the least significant field that
+         astFormat would produce.
+
+      }
+      This final convention is intended to ensure that values formatted
+      by astFormat which contain less than three fields will be
+      correctly interpreted if read back using astUnformat, even if
+      they do not contain field identification characters.
+
+      Examples of acceptable SkyFrame input formats (with
+      interpretation in parentheses) include:
+      \sstitemlist{
+
+         \sstitem
+         -14d 13m 22.2s (-14d 13\texttt{'}  22.2\texttt{"} )
+
+         \sstitem
+         $+$ 12:34:56.7 (12d 34\texttt{'}  56.7\texttt{"}  or 12h 34m 56.7s)
+
+         \sstitem
+         001 : 02 : 03.4 (1d 02\texttt{'}  03.4\texttt{"}  or 1h 02m 03.4s)
+
+         \sstitem
+         22h 30 (22h 30m 00s)
+
+         \sstitem
+         136::10\texttt{"}  (136d 00\texttt{'}  10\texttt{"}  or 136h 00m 10s)
+
+         \sstitem
+         -14M 27S (-0d 14\texttt{'}  27\texttt{"}  or -0h 14m 27s)
+
+         \sstitem
+         -:14: (-0d 14\texttt{'}  00\texttt{"}  or -0h 14m 00s)
+
+         \sstitem
+         -::4.1 (-0d 00\texttt{'}  04.1\texttt{"}  or -0h 00m 04.1s)
+
+         \sstitem
+         .9\texttt{"}  (0d 00\texttt{'}  00.9\texttt{"} )
+
+         \sstitem
+         d12m (0d 12\texttt{'}  00\texttt{"} )
+
+         \sstitem
+         H 12:22.3s (0h 12m 22.3s)
+
+         \sstitem
+         $<$bad$>$ (AST\_\_BAD)
+
+      }
+      Where alternative interpretations are shown, the choice of angle or
+      time depends on the associated \htmlref{Format(axis)}{Format(axis)} attribute.
+   }
+}
+\sstroutine{
+   astUnitMap
+}{
+   Create a UnitMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{UnitMap}{UnitMap} and optionally initialises
+      its attributes.
+
+      A UnitMap is a unit (null) \htmlref{Mapping}{Mapping} that has no effect on the
+      coordinates supplied to it. They are simply copied. This can be
+      useful if a Mapping is required (e.g. to pass to another
+      function) but you do not want it to have any effect.
+   }
+   \sstsynopsis{
+      AstUnitMap $*$astUnitMap( int ncoord, const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ncoord
+      }{
+         The number of input and output coordinates (these numbers are
+         necessarily the same).
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new UnitMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astUnitMap()
+      }{
+         A pointer to the new UnitMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astUnitNormMap
+}{
+   Create a UnitNormMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{UnitNormMap}{UnitNormMap} and optionally initialises its
+      attributes.
+
+      The forward transformation of a UnitNormMap subtracts the specified centre
+      and then transforms the resulting vector to a unit vector and the vector norm.
+      The output contains one more coordinate than the input: the initial \htmlref{Nin}{Nin} outputs
+      are in the same order as the input; the final output is the norm.
+
+      The inverse transformation of a UnitNormMap multiplies each component
+      of the provided vector by the provided norm and adds the specified centre.
+      The output contains one fewer coordinate than the input: the initial Nin inputs
+      are in the same order as the output; the final input is the norm.
+
+      UnitNormMap enables radially symmetric transformations, as follows:
+      \sstitemlist{
+
+         \sstitem
+         apply a UnitNormMap to produce a unit vector and norm (radius)
+
+         \sstitem
+         apply a one-dimensional mapping to the norm (radius), while passing the unit vector unchanged
+
+         \sstitem
+         apply the same UnitNormMap in the inverse direction to produce the result
+      }
+   }
+   \sstsynopsis{
+      AstUnitNormMap $*$astUnitNormMap( int ncoord, const double centre[],
+                                const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ncoord
+      }{
+         The number of coordinate values for each point to be
+         transformed (i.e. the number of dimensions of the space in
+         which the points will reside). Output will include one additional coordinate.
+      }
+      \sstsubsection{
+         centre
+      }{
+         An array containing the values to be subtracted from the input
+         coordinates before computing unit vector and norm. A separate
+         value must be supplied for each coordinate.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new UnitNormMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astUnitNormMap()
+      }{
+         A pointer to the new UnitNormMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astUnlock
+}{
+   Unlock an Object for use by other threads
+}{
+   \sstdescription{
+      Unlocks an \htmlref{Object}{Object} previously locked using \htmlref{astLock}{astLock}, so that other
+      threads can use the Object. See astLock for further details.
+   }
+   \sstsynopsis{
+      void astUnlock( AstObject $*$this, int report )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Object to be unlocked.
+      }
+      \sstsubsection{
+         report
+      }{
+         If non-zero, an error will be reported if the supplied Object,
+         or any Object contained within the supplied Object, is not
+         currently locked by the running thread. If zero, such Objects
+         will be left unchanged, and no error will be reported.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         This function applies to all Objects.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function attempts to execute even if the global error
+         status is set, but no further error report will be made if it
+         subsequently fails under these circumstances.
+
+         \sstitem
+         All unlocked Objects are excluded from AST context handling until
+         they are re-locked using astLock.
+
+         \sstitem
+         This function is only available in the C interface.
+
+         \sstitem
+         This function returns without action if the Object is not currently
+         locked by any thread. If it is locked by the running thread, it is
+         unlocked. If it is locked by another thread, an error will be reported
+         if \texttt{"} error\texttt{"}  is non-zero.
+
+         \sstitem
+         This function returns without action if the AST library has
+         been built without POSIX thread support (i.e. the \texttt{"} -with-pthreads\texttt{"}
+         option was not specified when running the \texttt{"} configure\texttt{"}  script).
+      }
+   }
+}
+\sstroutine{
+   astVersion
+}{
+   Return the version of the AST library being used
+}{
+   \sstdescription{
+      This macro invokes a function which
+      returns an integer representing the version of the AST library
+      being used. The library version is formatted as a string such as
+      \texttt{"} 2.0-7\texttt{"}  which contains integers representing the \texttt{"} major version\texttt{"}  (2),
+      the \texttt{"} minor version\texttt{"}  (0) and the \texttt{"} release\texttt{"}  (7). The integer returned
+      by this function combines all three integers together into a single
+      integer using the expresion:
+
+      (major version)$*$1E6 $+$ (minor version)$*$1E3 $+$ (release)
+   }
+   \sstsynopsis{
+      int astVersion
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Object}{Object}
+      }{
+         This macro applies to all Objects.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astVersion
+      }{
+         The major version, minor version and release numbers for the AST
+         library, encoded as a single integer.
+      }
+   }
+}
+\sstroutine{
+   astWarnings
+}{
+   Returns any warnings issued by the previous read or write operation
+}{
+   \sstdescription{
+      This function returns an AST \htmlref{KeyMap}{KeyMap} object holding the text of any
+      warnings issued as a result of the previous invocation of the
+      \htmlref{astRead}{astRead} or \htmlref{astWrite}{astWrite}
+      function on the \htmlref{Channel}{Channel}. If no warnings were issued, a
+      a NULL value
+      will be returned.
+
+      Such warnings are non-fatal and will not prevent the
+      read or write operation succeeding. However, the converted object
+      may not be identical to the original object in all respects.
+      Differences which would usually be deemed as insignificant in most
+      usual cases will generate a warning, whereas more significant
+      differences will generate an error.
+
+      The \texttt{"} \htmlref{Strict}{Strict}\texttt{"}  attribute allows this warning facility to be switched
+      off, so that a fatal error is always reported for any conversion
+      error.
+   }
+   \sstsynopsis{
+      AstKeyMap $*$astWarnings( AstChannel $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Channel.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         The basic Channel class generates a warning when ever an
+         un-recognised item is encountered whilst reading an \htmlref{Object}{Object} from
+         an external data source. If Strict is zero (the default), then
+         unexpected items in the Object description are simply ignored,
+         and any remaining items are used to construct the returned
+         Object. If Strict is non-zero, an error will be reported and a
+         NULL Object pointer returned if any unexpected items are
+         encountered.
+
+         As AST continues to be developed, new attributes are added
+         occasionally to selected classes. If an older version of AST is
+         used to read external Object descriptions created by a more
+         recent version of AST, then the Channel class will, by default,
+         ignore the new attributes, using the remaining attributes to
+         construct the Object. This is usually a good thing. However,
+         since external Object descriptions are often stored in plain
+         text, it is possible to edit them using a text editor. This
+         gives rise to the possibility of genuine errors in the
+         description due to finger-slips, typos, or simple
+         mis-understanding. Such inappropriate attributes will be ignored
+         if Strict is left at its default zero value. This will cause the
+         mis-spelled attribute to revert to its default value,
+         potentially causing subtle changes in the behaviour of
+         application software. If such an effect is suspected, the Strict
+         attribute can be set non-zero, resulting in the erroneous
+         attribute being identified in an error message.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         The returned KeyMap will contain warnings for all conditions
+         listed in the \htmlref{Warnings}{Warnings} attribute.
+      }
+      \sstsubsection{
+         \htmlref{XmlChan}{XmlChan}
+      }{
+         Reports conversion errors that result in what are usally
+         insignificant  changes.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astWarnings()
+      }{
+         A pointer to the KeyMap holding the warning messages, or
+         NULL
+         if no warnings were issued during the previous read operation.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The returned KeyMap uses keys of the form \texttt{"} Warning\_1\texttt{"} ,
+         \texttt{"} Warning\_2\texttt{"} , etc.
+
+         \sstitem
+         A value of
+         NULL will be returned if this function is invoked with the AST
+         error status set,
+         or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astWatch
+}{
+   Identify a new error status variable for the AST library
+}{
+   \sstdescription{
+      This function allows a new error status variable to be accessed
+      by the AST library when checking for and reporting error
+      conditions.
+
+      By default, the library uses an internal integer error status
+      which is set to an error value if an error occurs. Use of
+      astWatch allows the internal error status to be replaced by an
+      integer variable of your choosing, so that the AST library can
+      share its error status directly with other code which uses the
+      same error detection convention.
+
+      If an alternative error status variable is supplied, it is used
+      by all related AST functions and macros (e.g. \htmlref{astOK}{astOK}, \htmlref{astStatus}{astStatus}
+      and \htmlref{astClearStatus}{astClearStatus}).
+   }
+   \sstsynopsis{
+      int $*$astWatch( int $*$status\_ptr )
+   }
+   \sstparameters{
+      \sstsubsection{
+         status\_ptr
+      }{
+         Pointer to an int whose value is to be used subsequently as
+         the AST inherited status value. If a NULL pointer is supplied,
+         the AST library will revert to using its own internal error status.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astWatch()
+      }{
+         Address of the previous error status variable. This may later
+         be passed back to astWatch to restore the previous behaviour
+         of the library. (Note that on the first invocation of
+         astWatch the returned value will be the address of the
+         internal error status variable.)
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This function is not available in the FORTRAN 77 interface to
+         the AST library.
+      }
+   }
+}
+\sstroutine{
+   astWcsMap
+}{
+   Create a WcsMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{WcsMap}{WcsMap} and optionally initialises its
+      attributes.
+
+      A WcsMap is used to represent sky coordinate projections as
+      described in the (draft) FITS world coordinate system (FITS-WCS)
+      paper by E.W. Griesen and M. Calabretta (A \& A, in preparation).
+      This paper defines a set of functions, or sky projections, which
+      transform longitude-latitude pairs representing spherical
+      celestial coordinates into corresponding pairs of Cartesian
+      coordinates (and vice versa).
+
+      A WcsMap is a specialised form of \htmlref{Mapping}{Mapping} which implements these
+      sky projections and applies them to a specified pair of coordinates.
+      All the projections in the FITS-WCS paper are supported, plus the now
+      deprecated \texttt{"} TAN with polynomial correction terms\texttt{"}  projection which
+      is refered to here by the code \texttt{"} TPN\texttt{"} . Using the FITS-WCS terminology,
+      the transformation is between \texttt{"} native spherical\texttt{"}  and \texttt{"} projection
+      plane\texttt{"}  coordinates.  These coordinates may, optionally, be embedded in
+      a space with more than two dimensions, the remaining coordinates being
+      copied unchanged. Note, however, that for consistency with other AST
+      facilities, a WcsMap handles coordinates that represent angles
+      in radians (rather than the degrees used by FITS-WCS).
+
+      The type of FITS-WCS projection to be used and the coordinates
+      (axes) to which it applies are specified when a WcsMap is first
+      created. The projection type may subsequently be determined
+      using the \htmlref{WcsType}{WcsType} attribute and the coordinates on which it acts
+      may be determined using the \htmlref{WcsAxis(lonlat)}{WcsAxis(lonlat)} attribute.
+
+      Each WcsMap also allows up to 100 \texttt{"} projection parameters\texttt{"}  to be
+      associated with each axis. These specify the precise form of the
+      projection, and are accessed using \htmlref{PVi\_m}{PVi\_m} attribute, where \texttt{"} i\texttt{"}  is
+      the integer axis index (starting at 1), and m is an integer
+      \texttt{"} parameter index\texttt{"}  in the range 0 to 99. The number of projection
+      parameters required by each projection, and their meanings, are
+      dependent upon the projection type (most projections either do not
+      use any projection parameters, or use parameters 1 and 2 associated
+      with the latitude axis). Before creating a WcsMap you should consult
+      the FITS-WCS paper for details of which projection parameters are
+      required, and which have defaults. When creating the WcsMap, you must
+      explicitly set values for all those required projection parameters
+      which do not have defaults defined in this paper.
+   }
+   \sstsynopsis{
+      AstWcsMap $*$astWcsMap( int ncoord, int type, int lonax, int latax,
+                            const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ncoord
+      }{
+         The number of coordinate values for each point to be
+         transformed (i.e. the number of dimensions of the space in
+         which the points will reside). This must be at least 2. The
+         same number is applicable to both input and output points.
+      }
+      \sstsubsection{
+         type
+      }{
+         The type of FITS-WCS projection to apply. This should be
+         given using a macro value such as AST\_\_TAN (for a tangent
+         plane projection), where the characters following the double
+         underscore give the projection type code (in upper case) as
+         used in the FITS-WCS \texttt{"} CTYPEi\texttt{"}  keyword. You should consult the
+         FITS-WCS paper for a list of the available projections. The
+         additional code of AST\_\_TPN can be supplied which represents a
+         TAN projection with polynomial correction terms as defined in an
+         early draft of the FITS-WCS paper.
+      }
+      \sstsubsection{
+         lonax
+      }{
+         The index of the longitude axis. This should lie in the range
+         1 to \texttt{"} ncoord\texttt{"} .
+      }
+      \sstsubsection{
+         latax
+      }{
+         The index of the latitude axis. This should lie in the range
+         1 to \texttt{"} ncoord\texttt{"}  and be distinct from \texttt{"} lonax\texttt{"} .
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new WcsMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+
+         If the sky projection to be implemented requires projection
+         parameter values to be set, then this should normally be done
+         here via the PVi\_m attribute (see the \texttt{"} Examples\texttt{"}
+         section). Setting values for these parameters is mandatory if
+         they do not have default values (as defined in the FITS-WCS
+         paper).
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astWcsMap()
+      }{
+         A pointer to the new WcsMap.
+      }
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         wcsmap = astWcsMap( 2, AST\_\_MER, 1, 2, \texttt{"} \texttt{"}  );
+      }{
+         Creates a WcsMap that implements a FITS-WCS Mercator
+         projection on pairs of coordinates, with coordinates 1 and 2
+         representing the longitude and latitude respectively. Note
+         that the FITS-WCS Mercator projection does not require any
+         projection parameters.
+      }
+      \sstexamplesubsection{
+         wcsmap = astWcsMap( 3, AST\_\_COE, 2, 3, \texttt{"} PV3\_1=40.0\texttt{"}  );
+      }{
+         Creates a WcsMap that implements a FITS-WCS conical equal
+         area projection. The WcsMap acts on points in a 3-dimensional
+         space; coordinates 2 and 3 represent longitude and latitude
+         respectively, while the values of coordinate 1 are copied
+         unchanged.  \htmlref{Projection}{Projection} parameter 1 associatyed with the latitude
+         axis (corresponding to FITS keyword \texttt{"} PV3\_1\texttt{"} ) is required and has
+         no default, so is set explicitly to 40.0 degrees. Projection
+         parameter 2 (corresponding to FITS keyword \texttt{"} PV3\_2\texttt{"} ) is required
+         but has a default of zero, so need not be specified.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The forward transformation of a WcsMap converts between
+         FITS-WCS \texttt{"} native spherical\texttt{"}  and \texttt{"} relative physical\texttt{"}  coordinates,
+         while the inverse transformation converts in the opposite
+         direction. This arrangement may be reversed, if required, by
+         using \htmlref{astInvert}{astInvert} or by setting the \htmlref{Invert}{Invert} attribute to a non-zero
+         value.
+
+         \sstitem
+         If any set of coordinates cannot be transformed (for example,
+         many projections do not cover the entire celestial sphere), then
+         a WcsMap will yield coordinate values of AST\_\_BAD.
+
+         \sstitem
+         The validity of any projection parameters given via the PVi\_m
+         parameter in the \texttt{"} options\texttt{"}  string is not checked by this
+         function. However, their validity is checked when the resulting
+         WcsMap is used to transform coordinates, and an error will
+         result if the projection parameters do not satisfy all the
+         required constraints (as defined in the FITS-WCS paper).
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astWinMap
+}{
+   Create a WinMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{WinMap}{WinMap} and optionally initialises its
+      attributes.
+
+      A Winmap is a linear \htmlref{Mapping}{Mapping} which transforms a rectangular
+      window in one coordinate system into a similar window in another
+      coordinate system by scaling and shifting each axis (the window
+      edges being parallel to the coordinate axes).
+
+      A WinMap is specified by giving the coordinates of two opposite
+      corners (A and B) of the window in both the input and output
+      coordinate systems.
+   }
+   \sstsynopsis{
+      AstWinMap $*$astWinMap( int ncoord,
+                            const double ina[],  const double inb[],
+                            const double outa[], const double outb[],
+                            const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ncoord
+      }{
+         The number of coordinate values for each point to be
+         transformed (i.e. the number of dimensions of the space in
+         which the points will reside). The same number is applicable
+         to both input and output points.
+      }
+      \sstsubsection{
+         ina
+      }{
+         An array containing the \texttt{"} ncoord\texttt{"}
+         coordinates of corner A of the window in the input coordinate
+         system.
+      }
+      \sstsubsection{
+         inb
+      }{
+         An array containing the \texttt{"} ncoord\texttt{"}
+         coordinates of corner B of the window in the input coordinate
+         system.
+      }
+      \sstsubsection{
+         outa
+      }{
+         An array containing the \texttt{"} ncoord\texttt{"}
+         coordinates of corner A of the window in the output coordinate
+         system.
+      }
+      \sstsubsection{
+         outb
+      }{
+         An array containing the \texttt{"} ncoord\texttt{"}
+         coordinates of corner B of the window in the output coordinate
+         system.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new WinMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astWinMap()
+      }{
+         A pointer to the new WinMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\sstroutine{
+   astWrite
+}{
+   Write an Object to a Channel
+}{
+   \sstdescription{
+      This function writes an \htmlref{Object}{Object} to a \htmlref{Channel}{Channel}, appending it to any
+      previous Objects written to that Channel.
+   }
+   \sstsynopsis{
+      int astWrite( AstChannel $*$this, AstObject $*$object )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the Channel.
+      }
+      \sstsubsection{
+         object
+      }{
+         Pointer to the Object which is to be written.
+      }
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         If the FitsChan uses a foreign encoding (e.g. FITS-WCS) rather
+         than the native AST encoding, then storing values in the
+         FitsChan for keywords NAXIS1, NAXIS2, etc., before invoking
+         astWrite
+         can help to produce a successful write.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astWrite()
+      }{
+         The number of Objects written to the Channel by this
+         invocation of astWrite (normally, this will be one).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero will be returned if this function is invoked
+         with the AST error status set, or if it should fail for any
+         reason.
+
+         \sstitem
+         Invoking this function will usually cause the sink function
+         associated with the channel to be called in order to transfer a
+         textual description of the supplied object to some external data
+         store. However, the FitsChan class behaves differently. Invoking
+         this function on a FitsChan causes new FITS header cards to be
+         added to an internal buffer (the sink function is not invoked).
+         This buffer is written out through the sink function only when the
+         FitsChan is deleted.
+      }
+   }
+}
+\sstroutine{
+   astWriteFits
+}{
+   Write out all cards in a FitsChan to the sink function
+}{
+   \sstdescription{
+      This function
+      writes out all cards currently in the \htmlref{FitsChan}{FitsChan}. If the \htmlref{SinkFile}{SinkFile}
+      attribute is set, they will be written out to the specified sink file.
+      Otherwise, they will be written out using the sink function specified
+      when the FitsChan was created. All cards are then deleted from the
+      FitsChan.
+   }
+   \sstsynopsis{
+      void astWriteFits( AstFitsChan $*$this )
+   }
+   \sstparameters{
+      \sstsubsection{
+         this
+      }{
+         Pointer to the FitsChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the SinkFile is unset, and no sink function is available, this
+         method simply empties the FitsChan, and is then equivalent to
+         \htmlref{astEmptyFits}{astEmptyFits}.
+
+         \sstitem
+         This method attempt to execute even if an error has occurred
+         previously.
+      }
+   }
+}
+\sstroutine{
+   astXmlChan
+}{
+   Create an XmlChan
+}{
+   \sstdescription{
+      This function creates a new \htmlref{XmlChan}{XmlChan} and optionally initialises
+      its attributes.
+
+      A XmlChan is a specialised form of \htmlref{Channel}{Channel} which supports XML I/O
+      operations. Writing an \htmlref{Object}{Object} to an XmlChan (using
+      \htmlref{astWrite}{astWrite}) will, if the Object is suitable, generate an
+      XML description of that Object, and reading from an XmlChan will
+      create a new Object from its XML description.
+
+      Normally, when you use an XmlChan, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  functions which connect it to an external data store
+      by reading and writing the resulting XML text. By default, however,
+      an XmlChan will read from standard input and write to standard
+      output.
+
+      Alternatively, an XmlChan can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+   }
+   \sstsynopsis{
+      AstXmlChan $*$astXmlChan( const char $*$($*$ source)( void ),
+                              void ($*$ sink)( const char $*$ ),
+                              const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         source
+      }{
+         Pointer to a source function that takes no arguments and
+         returns a pointer to a null-terminated string.  If no value
+         has been set for the SourceFile attribute, this function
+         will be used by the XmlChan to obtain lines of input text. On
+         each invocation, it should return a pointer to the next input
+         line read from some external data store, and a NULL pointer
+         when there are no more lines to read.
+
+         If \texttt{"} source\texttt{"}  is NULL and no value has been set for the SourceFile
+         attribute, the XmlChan will read from standard input instead.
+      }
+      \sstsubsection{
+         sink
+      }{
+         Pointer to a sink function that takes a pointer to a
+         null-terminated string as an argument and returns void.
+         If no value has been set for the SinkFile attribute, this
+         function will be used by the XmlChan to deliver lines of
+         output text. On each invocation, it should deliver the
+         contents of the string supplied to some external data store.
+
+         If \texttt{"} sink\texttt{"}  is NULL, and no value has been set for the SinkFile
+         attribute, the XmlChan will write to standard output instead.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new XmlChan. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astXmlChan()
+      }{
+         A pointer to the new XmlChan.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the external data source or sink uses a character encoding
+         other than ASCII, the supplied source and sink functions should
+         translate between the external character encoding and the internal
+         ASCII encoding used by AST.
+
+         \sstitem
+         A null Object pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astZoomMap
+}{
+   Create a ZoomMap
+}{
+   \sstdescription{
+      This function creates a new \htmlref{ZoomMap}{ZoomMap} and optionally initialises its
+      attributes.
+
+      A ZoomMap is a \htmlref{Mapping}{Mapping} which \texttt{"} zooms\texttt{"}  a set of points about the
+      origin by multiplying all coordinate values by the same scale
+      factor (the inverse transformation is performed by dividing by
+      this scale factor).
+   }
+   \sstsynopsis{
+      AstZoomMap $*$astZoomMap( int ncoord, double zoom,
+                              const char $*$options, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ncoord
+      }{
+         The number of coordinate values for each point to be
+         transformed (i.e. the number of dimensions of the space in
+         which the points will reside). The same number is applicable
+         to both input and output points.
+      }
+      \sstsubsection{
+         zoom
+      }{
+         Initial scale factor by which coordinate values should be
+         multiplied (by the forward transformation) or divided (by the
+         inverse transformation). This factor may subsequently be
+         changed via the ZoomMap\texttt{'} s \htmlref{Zoom}{Zoom} attribute. It may be positive
+         or negative, but should not be zero.
+      }
+      \sstsubsection{
+         options
+      }{
+         Pointer to a null-terminated string containing an optional
+         comma-separated list of attribute assignments to be used for
+         initialising the new ZoomMap. The syntax used is identical to
+         that for the \htmlref{astSet}{astSet} function and may include \texttt{"} printf\texttt{"}  format
+         specifiers identified by \texttt{"} \%\texttt{"}  symbols in the normal way.
+      }
+      \sstsubsection{
+         ...
+      }{
+         If the \texttt{"} options\texttt{"}  string contains \texttt{"} \%\texttt{"}  format specifiers, then
+         an optional list of additional arguments may follow it in
+         order to supply values to be substituted for these
+         specifiers. The rules for supplying these are identical to
+         those for the astSet function (and for the C \texttt{"} printf\texttt{"}
+         function).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astZoomMap()
+      }{
+         A pointer to the new ZoomMap.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A null \htmlref{Object}{Object} pointer (AST\_\_NULL) will be returned if this
+         function is invoked with the AST error status set, or if it
+         should fail for any reason.
+      }
+   }
+   \sstdiytopic{
+      Status Handling
+   }{
+      The protected interface to this function includes an extra
+      parameter at the end of the parameter list descirbed above. This
+      parameter is a pointer to the integer inherited status
+      variable: \texttt{"} int $*$status\texttt{"} .
+   }
+}
+\normalsize
+
+\cleardoublepage
+\section{\label{ss:attributedescriptions}AST Attribute Descriptions}
+\small
+\sstroutine{
+   Abbrev(axis)
+}{
+   Abbreviate leading fields within numerical axis labels?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether matching leading fields should be removed from adjacent
+      numerical axis labels. It takes a separate value for each physical
+      axis of a \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} Abbrev(2)=0\texttt{"}
+      specifies that matching leading fields should not be removed on
+      the second axis.
+
+      If the Abbrev value of a Plot is non-zero (the default), then
+      leading fields will be removed from adjacent axis labels if they
+      are equal.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} Abbrev\texttt{"}  instead of
+         \texttt{"} Abbrev(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the Abbrev(1) value.
+      }
+   }
+}
+\sstroutine{
+   Adaptive
+}{
+   Should the area adapt to changes in the coordinate system?
+}{
+   \sstdescription{
+      The coordinate system represented by a \htmlref{Region}{Region} may be changed by
+      assigning new values to attributes such as \htmlref{System}{System}, Unit, etc.
+      For instance, a Region representing an area on the sky in ICRS
+      coordinates may have its System attribute changed so that it
+      represents (say) Galactic coordinates instead of ICRS. This
+      attribute controls what happens when the coordinate system
+      represented by a Region is changed in this way.
+
+      If Adaptive is non-zero (the default), then area represented by the
+      Region adapts to the new coordinate system. That is, the numerical
+      values which define the area represented by the Region are changed
+      by mapping them from the old coordinate system into the new coordinate
+      system. Thus the Region continues to represent the same physical
+      area.
+
+      If Adaptive is zero, then area represented by the Region does not adapt
+      to the new coordinate system. That is, the numerical values which
+      define the area represented by the Region are left unchanged. Thus
+      the physical area represented by the Region will usually change.
+
+      As an example, consider a Region describe a range of wavelength from
+      2000 Angstrom to 4000 Angstrom. If the Unit attribute for the Region
+      is changed from Angstrom to \texttt{"} nm\texttt{"}  (nanometre), what happens depends
+      on the setting of Adaptive. If Adaptive is non-zero, the \htmlref{Mapping}{Mapping}
+      from the old to the new coordinate system is found. In this case it
+      is a simple scaling by a factor of 0.1 (since 1 Angstrom is 0.1 nm).
+      This Mapping is then used to modify the numerical values within the
+      Region, changing 2000 to 200 and 4000 to 400. Thus the modified
+      region represents 200 nm to 400 nm, the same physical space as
+      the original 2000 Angstrom to 4000 Angstrom. However, if Adaptive
+      had been zero, then the numerical values would not have been changed,
+      resulting in the final Region representing 2000 nm to 4000 nm.
+
+      Setting Adaptive to zero can be necessary if you want correct
+      inaccurate attribute settings in an existing Region. For instance,
+      when creating a Region you may not know what \htmlref{Epoch}{Epoch} value to use, so
+      you would leave Epoch unset resulting in some default value being used.
+      If at some later point in the application, the correct Epoch value
+      is determined, you could assign the correct value to the Epoch
+      attribute. However, you would first need to set Adaptive temporarily
+      to zero, because otherwise the area represented by the Region would
+      be Mapped from the spurious default Epoch to the new correct Epoch,
+      which is not what is required.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AlignOffset
+}{
+   Align SkyFrames using the offset coordinate system?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how a \htmlref{SkyFrame}{SkyFrame}
+      behaves when it is used (by
+      \htmlref{astFindFrame}{astFindFrame} or \htmlref{astConvert}{astConvert}) as a template to match another (target)
+      SkyFrame. It determines the coordinate system in which the two
+      SkyFrames are aligned if a match occurs.
+
+      If the template and target SkyFrames both have defined offset coordinate
+      systems (i.e. the \htmlref{SkyRefIs}{SkyRefIs} attribute is set to either \texttt{"} Origin\texttt{"}  or \texttt{"}
+      Pole\texttt{"} ), and they both have a non-zero value for AlignOffset, then
+      alignment occurs within the offset coordinate systems (that is, a
+      \htmlref{UnitMap}{UnitMap} will always be used to align the two SkyFrames). If either
+      the template or target SkyFrame has zero (the default value) for
+      AlignOffset, or if either SkyFrame has SkyRefIs set to \texttt{"} Ignored\texttt{"} , then
+      alignment occurring within the coordinate system specified by the
+      \htmlref{AlignSystem}{AlignSystem} attribute.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AlignSideBand
+}{
+   Should the SideBand attribute be taken into account when aligning
+   this \htmlref{DSBSpecFrame}{DSBSpecFrame} with another DSBSpecFrame?
+}{
+   \sstdescription{
+      This attribute controls how a DSBSpecFrame behaves when an attempt
+      is made to align it with another DSBSpecFrame using
+      \htmlref{astFindFrame}{astFindFrame} or \htmlref{astConvert}{astConvert}.
+      If both DSBSpecFrames have a non-zero value for AlignSideBand, the
+      value of the \htmlref{SideBand}{SideBand} attribute in each DSBSpecFrame is used so that
+      alignment occurs between sidebands. That is, if one DSBSpecFrame
+      represents USB and the other represents LSB then
+      astFindFrame and astConvert
+      will recognise that the DSBSpecFrames represent different sidebands
+      and will take this into account when constructing the \htmlref{Mapping}{Mapping} that
+      maps positions in one DSBSpecFrame into the other. If AlignSideBand
+      in either DSBSpecFrame is set to zero, then the values of the SideBand
+      attributes are ignored. In the above example, this would result in a
+      frequency in the first DSBSpecFrame being mapped onto the same
+      frequency in the second DSBSpecFrame, even though those frequencies
+      refer to different sidebands. In other words, if either AlignSideBand
+      attribute is zero, then the two DSBSpecFrames aligns like basic
+      SpecFrames. The default value for AlignSideBand is zero.
+
+      When astFindFrame or astConvert
+      is used on two DSBSpecFrames (potentially describing different spectral
+      coordinate systems and/or sidebands), it returns a Mapping which can be
+      used to transform a position in one DSBSpecFrame into the corresponding
+      position in the other. The Mapping is made up of the following steps in
+      the indicated order:
+
+      \sstitemlist{
+
+         \sstitem
+         If both DSBSpecFrames have a value of 1 for the AlignSideBand
+         attribute, map values from the target\texttt{'} s current sideband (given by its
+         SideBand attribute) to the observed sideband (whether USB or LSB). If
+         the target already represents the observed sideband, this step will
+         leave the values unchanged. If either of the two DSBSpecFrames have a
+         value of zero for its AlignSideBand attribute, then this step is omitted.
+
+         \sstitem
+         Map the values from the spectral system of the target to the spectral
+         system of the template. This Mapping takes into account all the
+         inherited \htmlref{SpecFrame}{SpecFrame} attributes such as \htmlref{System}{System}, \htmlref{StdOfRest}{StdOfRest}, Unit, etc.
+
+         \sstitem
+         If both DSBSpecFrames have a value of 1 for the AlignSideBand
+         attribute, map values from the result\texttt{'} s observed sideband to the
+         result\texttt{'} s current sideband (given by its SideBand attribute). If the
+         result already represents the observed sideband, this step will leave
+         the values unchanged. If either of the two DSBSpecFrames have a value
+         of zero for its AlignSideBand attribute, then this step is omitted.
+      }
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         DSBSpecFrame
+      }{
+         All DSBSpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AlignSpecOffset
+}{
+   Align SpecFrames using the offset coordinate system?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how a \htmlref{SpecFrame}{SpecFrame}
+      behaves when it is used (by
+      \htmlref{astFindFrame}{astFindFrame} or \htmlref{astConvert}{astConvert}) as a template to match another (target)
+      SpecFrame. It determines whether alignment occurs between the offset
+      values defined by the current value of the SpecOffset attribute, or
+      between the corresponding absolute spectral values.
+
+      The default value of zero results in the two SpecFrames being aligned
+      so that a given absolute spectral value in one is mapped to the same
+      absolute value in the other. A non-zero value results in the SpecFrames
+      being aligned so that a given offset value in one is mapped to the same
+      offset value in the other.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SpecFrame
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AlignStdOfRest
+}{
+   Standard of rest to use when aligning SpecFrames
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{SpecFrame}{SpecFrame} behaves when it is used (by
+      \htmlref{astFindFrame}{astFindFrame} or \htmlref{astConvert}{astConvert}) as a template to match another (target)
+      SpecFrame. It identifies the standard of rest in which alignment is
+      to occur. See the \htmlref{StdOfRest}{StdOfRest} attribute for a desription of the values
+      which may be assigned to this attribute. The default AlignStdOfRest
+      value is \texttt{"} Helio\texttt{"}  (heliographic).
+
+      When astFindFrame or astConvert is used on two SpecFrames (potentially
+      describing different spectral coordinate systems), it returns a \htmlref{Mapping}{Mapping}
+      which can be used to transform a position in one SpecFrame into the
+      corresponding position in the other. The Mapping is made up of the
+      following steps in the indicated order:
+
+      \sstitemlist{
+
+         \sstitem
+         Map values from the system used by the target (wavelength,
+         apparent radial velocity, etc) to the system specified by the
+         \htmlref{AlignSystem}{AlignSystem} attribute, using the target\texttt{'} s rest frequency if necessary.
+
+         \sstitem
+         Map these values from the target\texttt{'} s standard of rest to the standard of
+         rest specified by the AlignStdOfRest attribute, using the \htmlref{Epoch}{Epoch}, \htmlref{ObsLat}{ObsLat},
+         \htmlref{ObsLon}{ObsLon}, \htmlref{ObsAlt}{ObsAlt}, \htmlref{RefDec}{RefDec} and \htmlref{RefRA}{RefRA} attributes of the target to define the
+         two standards of rest.
+
+         \sstitem
+         Map these values from the standard of rest specified by the
+         AlignStdOfRest attribute, to the template\texttt{'} s standard of rest, using the
+         Epoch, ObsLat, ObsLon, ObsAlt, RefDec and RefRA attributes of the
+         template to define the two standards of rest.
+
+         \sstitem
+         Map these values from the system specified by the AlignSystem
+         attribute, to the system used by the template, using the template\texttt{'} s
+         rest frequency if necessary.
+      }
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SpecFrame
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AlignSystem
+}{
+   Coordinate system in which to align the Frame
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{Frame}{Frame} behaves when it is used (by
+      \htmlref{astFindFrame}{astFindFrame} or \htmlref{astConvert}{astConvert}) as a template to match another (target)
+      Frame. It identifies the coordinate system in which the two Frames
+      will be aligned by the match.
+
+      The values which may be assigned to this attribute, and its default
+      value, depend on the class of Frame and are described in the
+      \texttt{"} Applicability\texttt{"}  section below. In general, the AlignSystem attribute
+      will accept any of the values which may be assigned to the \htmlref{System}{System}
+      attribute.
+
+      The \htmlref{Mapping}{Mapping} returned by AST\_FINDFRAME or AST\_CONVERT will use the
+      coordinate system specified by the AlignSystem attribute as an
+      intermediate coordinate system. The total returned Mapping will first
+      map positions from the first Frame into this intermediate coordinate
+      system, using the attributes of the first Frame. It will then map
+      these positions from the intermediate coordinate system into the
+      second Frame, using the attributes of the second Frame.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The AlignSystem attribute for a basic Frame always equals \texttt{"} Cartesian\texttt{"} ,
+         and may not be altered.
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The AlignSystem attribute for a CmpFrame always equals \texttt{"} Compound\texttt{"} ,
+         and may not be altered.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The AlignSystem attribute of a FrameSet is the same as that of its
+         current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The default AlignSystem attribute for a SkyFrame is \texttt{"} ICRS\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         The default AlignSystem attribute for a SpecFrame is \texttt{"} Wave\texttt{"}
+         (wavelength).
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The default AlignSystem attribute for a TimeFrame is \texttt{"} MJD\texttt{"} .
+      }
+   }
+}
+\sstroutine{
+   AlignTimeScale
+}{
+   Time scale to use when aligning TimeFrames
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{TimeFrame}{TimeFrame} behaves when it is used (by
+      \htmlref{astFindFrame}{astFindFrame} or \htmlref{astConvert}{astConvert}) as a template to match another (target)
+      TimeFrame. It identifies the time scale in which alignment is
+      to occur. See the \htmlref{TimeScale}{TimeScale} attribute for a desription of the values
+      which may be assigned to this attribute. The default AlignTimeScale
+      value depends on the current value of TimeScale: if TimeScale is
+      UT1, GMST, LMST or LAST, the default for AlignTimeScale is UT1, for all
+      other TimeScales the default is TAI.
+
+      When astFindFrame or astConvert is used on two TimeFrames (potentially
+      describing different time coordinate systems), it returns a \htmlref{Mapping}{Mapping}
+      which can be used to transform a position in one TimeFrame into the
+      corresponding position in the other. The Mapping is made up of the
+      following steps in the indicated order:
+
+      \sstitemlist{
+
+         \sstitem
+         Map values from the system used by the target (MJD, JD, etc) to the
+         system specified by the \htmlref{AlignSystem}{AlignSystem} attribute.
+
+         \sstitem
+         Map these values from the target\texttt{'} s time scale to the time scale
+         specified by the AlignTimeScale attribute.
+
+         \sstitem
+         Map these values from the time scale specified by the AlignTimeScale
+         attribute, to the template\texttt{'} s time scale.
+
+         \sstitem
+         Map these values from the system specified by the AlignSystem
+         attribute, to the system used by the template.
+      }
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         TimeFrame
+      }{
+         All TimeFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AllVariants
+}{
+   A list of the variant Mappings associated with the current Frame
+}{
+   \sstdescription{
+      This attribute gives a space separated list of the names of all the
+      variant Mappings associated with the current \htmlref{Frame}{Frame} (see attribute
+      \texttt{"} \htmlref{Variant}{Variant}\texttt{"} ). If the current Frame has no variant Mappings, then the
+      list will hold a single entry equal to the \htmlref{Domain}{Domain} name of the
+      current Frame.
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         All FrameSets have this attribute.
+      }
+   }
+}
+\sstroutine{
+   AllWarnings
+}{
+   A list of all currently available condition names
+}{
+   \sstdescription{
+      This read-only attribute is a space separated list of all the conditions
+      names recognized by the \htmlref{Warnings}{Warnings} attribute. The names are listed
+      below.
+   }
+   \sstattributetype{
+      String, read-only
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Conditions
+   }{
+      The following conditions are currently recognised (all are
+      case-insensitive):
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} BadCel\texttt{"} : This condition arises when reading a \htmlref{FrameSet}{FrameSet} from a
+         non-Native encoded FitsChan if an unknown celestial co-ordinate
+         system is specified by the CTYPE keywords.
+
+         \sstitem
+         \texttt{"} BadCTYPE\texttt{"} : This condition arises when reading a FrameSet from a
+         non-Native encoded FitsChan if an illegal algorithm code is specified
+         by a CTYPE keyword, and the illegal code can be converted to an
+         equivalent legal code.
+
+         \sstitem
+         \texttt{"} BadKeyName\texttt{"} : This condition arises if a FITS keyword name is
+         encountered that contains an illegal character (i.e. one not allowed
+         by the FITS standard).
+
+         \sstitem
+         \texttt{"} BadKeyValue\texttt{"} : This condition arises if the value of a FITS keyword
+         cannot be determined from the content of the header card.
+
+         \sstitem
+         \texttt{"} BadLat\texttt{"} : This condition arises when reading a FrameSet from a
+         non-Native encoded FitsChan if the latitude of the reference point
+         has an absolute value greater than 90 degrees. The actual absolute
+         value used is set to exactly 90 degrees in these cases.
+
+         \sstitem
+         \texttt{"} BadMat\texttt{"} : This condition arises if the matrix describing the
+         transformation from pixel offsets to intermediate world coordinates
+         cannot be inverted. This matrix describes the scaling, rotation, shear,
+         etc., applied to the pixel axes, and is specified by keywords such as
+         PCi\_j, CDi\_j, CROTA, etc. For example, the matrix will not be invertable
+         if any rows or columns consist entirely of zeros. The FITS-WCS Paper I
+         \texttt{"} Representation of World Coordinates in FITS\texttt{"}  by Greisen \& Calabretta
+         requires that this matrix be invertable. Many operations (such as
+         grid plotting) will not be possible if the matrix cannot be inverted.
+
+         \sstitem
+         \texttt{"} BadPV\texttt{"} : This condition arises when reading a FrameSet from a
+         non-Native encoded FitsChan. It is issued if a \htmlref{PVi\_m}{PVi\_m} header is found
+         that refers to a projection parameter that is not used by the
+         projection type specified by CTYPE, or the PV values are otherwise
+         inappropriate for the projection type.
+
+         \sstitem
+         \texttt{"} BadVal\texttt{"} : This condition arises when reading a FrameSet from a
+         non-Native encoded FitsChan if it is not possible to convert the
+         value of a FITS keywords to the expected type. For instance, this
+         can occur if the FITS header contains a string value for a keyword
+         which should have a floating point value, or if the keyword has no
+         value at all (i.e. is a comment card).
+
+         \sstitem
+         \texttt{"} Distortion\texttt{"} : This condition arises when reading a FrameSet from a
+         non-Native encoded FitsChan if any of the CTYPE keywords specify an
+         unsupported distortion code using the \texttt{"} 4-3-3\texttt{"}  format specified in
+         FITS-WCS paper IV. Such distortion codes are ignored.
+
+         \sstitem
+         \texttt{"} NoCTYPE\texttt{"} : This condition arises if a default CTYPE value is used
+         within \htmlref{astRead}{astRead}, due to no value being present in the supplied FitsChan.
+         This condition is only tested for when using non-Native encodings.
+
+         \sstitem
+         \texttt{"} NoEquinox\texttt{"} : This condition arises if a default equinox value is used
+         within astRead, due to no value being present in the supplied FitsChan.
+         This condition is only tested for when using non-Native encodings.
+
+         \sstitem
+         \texttt{"} NoRadesys\texttt{"} : This condition arises if a default reference frame is
+         used for an equatorial co-ordinate system within astRead, due to no
+         value being present in the supplied FitsChan. This condition is only
+         tested for when using non-Native encodings.
+
+         \sstitem
+         \texttt{"} NoLonpole\texttt{"} : This condition arises if a default value is used for
+         the LONPOLE keyword within astRead, due to no value being present
+         in the supplied FitsChan. This condition is only tested for when
+         using non-Native encodings.
+
+         \sstitem
+         \texttt{"} NoLatpole\texttt{"} : This condition arises if a default value is used for
+         the LATPOLE keyword within astRead, due to no value being present
+         in the supplied FitsChan. This condition is only tested for when
+         using non-Native encodings.
+
+         \sstitem
+         \texttt{"} NoMjd-obs\texttt{"} : This condition arises if a default value is used for
+         the date of observation within astRead, due to no value being present
+         in the supplied FitsChan. This condition is only tested for when using
+         non-Native encodings.
+
+         \sstitem
+         \texttt{"} Tnx\texttt{"} : This condition arises if a FrameSet is read from a FITS
+         header containing an IRAF \texttt{"} TNX\texttt{"}  projection which includes terms
+         not supproted by AST. Such terms are ignored and so the resulting
+         FrameSet may be inaccurate.
+
+         \sstitem
+         \texttt{"} Zpx\texttt{"} : This condition arises if a FrameSet is read from a FITS
+         header containing an IRAF \texttt{"} ZPX\texttt{"}  projection which includes \texttt{"} lngcor\texttt{"}
+         or \texttt{"} latcor\texttt{"}  correction terms. These terms are not supported by AST
+         and are ignored. The resulting FrameSet may therefore be inaccurate.
+      }
+   }
+}
+\sstroutine{
+   AsTime(axis)
+}{
+   Format celestal coordinates as times?
+}{
+   \sstdescription{
+      This attribute specifies the default style of formatting to be
+      used (e.g. by \htmlref{astFormat}{astFormat}) for the celestial coordinate values
+      described by a \htmlref{SkyFrame}{SkyFrame}. It takes a separate boolean value for
+      each SkyFrame axis so that, for instance, the setting
+      \texttt{"} AsTime(2)=0\texttt{"}  specifies the default formatting style for
+      celestial latitude values.
+
+      If the AsTime attribute for a SkyFrame axis is zero, then
+      coordinates on that axis will be formatted as angles by default
+      (using degrees, minutes and seconds), otherwise they will be
+      formatted as times (using hours, minutes and seconds).
+
+      The default value of AsTime is chosen according to the sky
+      coordinate system being represented, as determined by the
+      SkyFrame\texttt{'} s \htmlref{System}{System} attribute. This ensures, for example, that
+      right ascension values will be formatted as times by default,
+      following normal conventions.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The AsTime attribute operates by changing the default value of
+         the corresponding \htmlref{Format(axis)}{Format(axis)} attribute. This, in turn, may
+         also affect the value of the \htmlref{Unit(axis)}{Unit(axis)} attribute.
+
+         \sstitem
+         Only the default style of formatting is affected by the AsTime
+         value. If an explicit Format(axis) value is set, it will
+         over-ride any effect from the AsTime attribute.
+      }
+   }
+}
+\sstroutine{
+   Base
+}{
+   FrameSet base Frame index
+}{
+   \sstdescription{
+      This attribute gives the index of the \htmlref{Frame}{Frame} which is to be
+      regarded as the \texttt{"} base\texttt{"}  Frame within a \htmlref{FrameSet}{FrameSet}. The default is
+      the first Frame added to the FrameSet when it is created (this
+      Frame always has an index of 1).
+
+      When setting a new value for this attribute, a string may be
+      supplied instead of an integer index. In this case a search
+      is made within the FrameSet for a Frame that has its \htmlref{Domain}{Domain}
+      attribute value equal to the supplied string (the comparison is
+      case-insensitive). If found, the Frame is made the base Frame.
+      Otherwise an error is reported.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FrameSet
+      }{
+         All FrameSets have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Inverting a FrameSet (inverting the boolean sense of its
+         \htmlref{Invert}{Invert} attribute, with the \htmlref{astInvert}{astInvert} function for example) will
+         interchange the values of its Base and \htmlref{Current}{Current} attributes.
+      }
+   }
+}
+\sstroutine{
+   Border
+}{
+   Draw a border around valid regions of a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether a border is drawn around regions corresponding to the
+      valid physical coordinates of a \htmlref{Plot}{Plot} (c.f. \htmlref{astBorder}{astBorder}).
+
+      If the Border value of a Plot is non-zero, then this border will
+      be drawn as part of the grid. Otherwise, the border is not drawn
+      (although axis labels and tick marks will still appear, unless
+      other relevant Plot attributes indicate that they should
+      not). The default behaviour is to draw the border if tick marks
+      and numerical labels will be drawn around the edges of the
+      plotting area (see the \htmlref{Labelling}{Labelling} attribute), but to omit it
+      otherwise.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Bottom(axis)
+}{
+   Lowest axis value to display
+}{
+   \sstdescription{
+      This attribute gives the lowest axis value to be displayed (for
+      instance, by the \htmlref{astGrid}{astGrid} method).
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         The default supplied by the Frame class is to display all axis
+         values, without any limit.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Bottom value to -90 degrees
+         for latitude axes, and 0 degrees for co-latitude axes. The
+         default for longitude axes is to display all axis values.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   Bounded
+}{
+   Is the Region bounded?
+}{
+   \sstdescription{
+      This is a read-only attribute indicating if the \htmlref{Region}{Region} is bounded.
+      A Region is bounded if it is contained entirely within some
+      finite-size bounding box.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+   }
+}
+\sstroutine{
+   CDMatrix
+}{
+   Use CDi\_j keywords to represent pixel scaling, rotation, etc?
+}{
+   \sstdescription{
+      This attribute is a boolean value which specifies how the linear
+      transformation from pixel coordinates to intermediate world
+      coordinates should be represented within a \htmlref{FitsChan}{FitsChan} when using
+      FITS-WCS encoding. This transformation describes the scaling,
+      rotation, shear, etc., of the pixel axes.
+
+      If the attribute has a non-zero value then the transformation is
+      represented by a set of CDi\_j keywords representing a square matrix
+      (where \texttt{"} i\texttt{"}  is the index of an intermediate world coordinate axis
+      and \texttt{"} j\texttt{"}  is the index of a pixel axis). If the attribute has a zero
+      value the transformation is represented by a set of PCi\_j keywords
+      (which also represent a square matrix) together with a corresponding
+      set of CDELTi keywords representing the axis scalings. See FITS-WCS
+      paper II \texttt{"} Representation of Celestial Coordinates in FITS\texttt{"}  by
+      M. Calabretta \& E.W. Greisen, for a complete description of these two
+      schemes.
+
+      The default value of the CDMatrix attribute is determined by the
+      contents of the FitsChan at the time the attribute is accessed. If
+      the FitsChan contains any CDi\_j keywords then the default value is
+      non-zero. Otherwise it is zero. Note, reading a \htmlref{FrameSet}{FrameSet} from a
+      FitsChan will in general consume any CDi\_j keywords present in the
+      FitsChan. Thus the default value for CDMatrix following a read will
+      usually be zero, even if the FitsChan originally contained some
+      CDi\_j keywords. This behaviour is similar to that of the \htmlref{Encoding}{Encoding}
+      attribute, the default value for which is determined by the contents
+      of the FitsChan at the time the attribute is accessed. If you wish
+      to retain the original value of the CDMatrix attribute (that is,
+      the value before reading the FrameSet) then you should enquire the
+      default value before doing the read, and then set that value
+      explicitly.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   CarLin
+}{
+   Ignore spherical rotations on CAR projections?
+}{
+   \sstdescription{
+      This attribute is a boolean value which specifies how FITS \texttt{"} CAR\texttt{"}
+      (plate carree, or \texttt{"} Cartesian\texttt{"} ) projections should be treated when
+      reading a \htmlref{FrameSet}{FrameSet} from a foreign encoded FITS header. If zero (the
+      default), it is assumed that the CAR projection conforms to the
+      conventions described in the FITS world coordinate system (FITS-WCS)
+      paper II \texttt{"} Representation of Celestial Coordinates in FITS\texttt{"}  by
+      M. Calabretta \& E.W. Greisen. If CarLin is non-zero, then these
+      conventions are ignored, and it is assumed that the mapping from pixel
+      coordinates to celestial coordinates is a simple linear transformation
+      (hence the attribute name \texttt{"} CarLin\texttt{"} ). This is appropriate for some older
+      FITS data which claims to have a \texttt{"} CAR\texttt{"}  projection, but which in fact do
+      not conform to the conventions of the FITS-WCS paper.
+
+      The FITS-WCS paper specifies that headers which include a CAR projection
+      represent a linear mapping from pixel coordinates to \texttt{"} native spherical
+      coordinates\texttt{"} , NOT celestial coordinates. An extra mapping is then
+      required from native spherical to celestial. This mapping is a 3D
+      rotation and so the overall \htmlref{Mapping}{Mapping} from pixel to celestial coordinates
+      is NOT linear. See the FITS-WCS papers for further details.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Card
+}{
+   Index of current FITS card in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the index of the \texttt{"} current\texttt{"}  FITS header card
+      within a \htmlref{FitsChan}{FitsChan}, the first card having an index of 1. The
+      choice of current card affects the behaviour of functions that
+      access the contents of the FitsChan, such as \htmlref{astDelFits}{astDelFits},
+      \htmlref{astFindFits}{astFindFits} and \htmlref{astPutFits}{astPutFits}.
+
+      A value assigned to Card will position the FitsChan at any
+      desired point, so that a particular card within it can be
+      accessed. Alternatively, the value of Card may be enquired in
+      order to determine the current position of a FitsChan.
+
+      The default value of Card is 1. This means that clearing
+      this attribute (using \htmlref{astClear}{astClear}) effectively \texttt{"} rewinds\texttt{"}  the
+      FitsChan, so that the first card is accessed next.  If Card is
+      set to a value which exceeds the total number of cards in the
+      FitsChan (as given by its \htmlref{Ncard}{Ncard} attribute), it is regarded as
+      pointing at the \texttt{"} end-of-file\texttt{"} . In this case, the value returned
+      in response to an enquiry is always one more than the number of
+      cards in the FitsChan.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   CardComm
+}{
+   The comment for the current card in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the comment for the current card of the
+      \htmlref{FitsChan}{FitsChan}. A zero-length string is returned if the card has no comment.
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   CardName
+}{
+   The keyword name of the current card in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the name of the keyword for the
+      current card of the \htmlref{FitsChan}{FitsChan}.
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   CardType
+}{
+   The data type of the current card in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the data type of the keyword value for the
+      current card of the \htmlref{FitsChan}{FitsChan}. It will be one of the following
+      integer constants: AST\_\_NOTYPE, AST\_\_COMMENT, AST\_\_INT, AST\_\_FLOAT,
+      AST\_\_STRING, AST\_\_COMPLEXF, AST\_\_COMPLEXI, AST\_\_LOGICAL,
+      AST\_\_CONTINUE, AST\_\_UNDEF.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Class
+}{
+   Object class name
+}{
+   \sstdescription{
+      This attribute gives the name of the class to which an \htmlref{Object}{Object}
+      belongs.
+   }
+   \sstattributetype{
+      Character string, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Clean
+}{
+   Remove cards used whilst reading even if an error occurs?
+}{
+   \sstdescription{
+      This attribute indicates whether or not cards should be removed from
+      the \htmlref{FitsChan}{FitsChan} if an error occurs within
+      \htmlref{astRead}{astRead}.
+      A succesful read on a FitsChan always results in the removal of
+      the cards which were involved in the description of the returned
+      \htmlref{Object}{Object}. However, in the event of an error during the read (for instance
+      if the cards in the FitsChan have illegal values, or if some required
+      cards are missing) no cards will be removed from the FitsChan if
+      the Clean attribute is zero (the default). If Clean is non-zero then
+      any cards which were used in the aborted attempt to read an object
+      will be removed.
+
+      This provides a means of \texttt{"} cleaning\texttt{"}  a FitsChan of WCS related cards
+      which works even in the event of the cards not forming a legal WCS
+      description.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Clip
+}{
+   Clip lines and/or markers at the Plot boundary?
+}{
+   \sstdescription{
+      This attribute controls whether curves and markers are clipped at the
+      boundary of the graphics box specified when the \htmlref{Plot}{Plot} was created. A
+      value of 3 implies both markers and curves are clipped at the Plot
+      boundary. A value of 2 implies markers are clipped, but not curves. A
+      value of 1 implies curves are clipped, but not markers. A value of
+      zero implies neither curves nor markers are clipped. The default
+      value is 1. Note, this attributes controls only the clipping
+      performed internally within AST. The underlying graphics system may
+      also apply clipping. In such cases, removing clipping using this
+      attribute does not guarantee that no clipping will be visible in the
+      final plot.
+
+      The \htmlref{astClip}{astClip} function
+      can be used to establish generalised clipping within arbitrary
+      regions of the Plot.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ClipOp
+}{
+   Combine Plot clipping limits using a boolean OR?
+}{
+   \sstdescription{
+      This attribute controls how the clipping limits specified for
+      each axis of a \htmlref{Plot}{Plot} (using the \htmlref{astClip}{astClip} function) are
+      combined. This, in turn, determines which parts of the graphical
+      output will be visible.
+
+      If the ClipOp attribute of a Plot is zero (the default),
+      graphical output is visible only if it satisfies the clipping
+      limits on all the axes of the clipping \htmlref{Frame}{Frame} (a boolean
+      AND). Otherwise, if ClipOp is non-zero, output is visible if it
+      satisfies the clipping limits on one or more axes (a boolean
+      OR).
+
+      An important use of this attribute is to allow areas of a Plot
+      to be left clear (e.g. as a background for some text). To
+      achieve this, the lower and upper clipping bounds supplied to
+      astClip should be reversed, and the ClipOp attribute of the
+      Plot should be set to a non-zero value.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Closed
+}{
+   Should the boundary be considered to be inside the region?
+}{
+   \sstdescription{
+      This attribute controls whether points on the boundary of a \htmlref{Region}{Region}
+      are considered to be inside or outside the region. If the attribute
+      value is non-zero (the default), points on the boundary are considered
+      to be inside the region (that is, the Region is \texttt{"} closed\texttt{"} ). However,
+      if the attribute value is zero, points on the bounary are considered
+      to be outside the region.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{PointList}{PointList}
+      }{
+         The value of the Closed attribute is ignored by PointList regions.
+         If the PointList region has not been negated, then it is always
+         assumed to be closed. If the PointList region has been negated, then
+         it is always assumed to be open. This is required since points
+         have zero volume and therefore consist entirely of boundary.
+      }
+      \sstsubsection{
+         \htmlref{CmpRegion}{CmpRegion}
+      }{
+         The default Closed value for a CmpRegion is the Closed value of its
+         first component Region.
+      }
+      \sstsubsection{
+         \htmlref{Stc}{Stc}
+      }{
+         The default Closed value for an Stc is the Closed value of its
+         encapsulated Region.
+      }
+   }
+}
+\sstroutine{
+   Colour(element)
+}{
+   Colour index for a Plot element
+}{
+   \sstdescription{
+      This attribute determines the colour index used when drawing
+      each element of graphical output produced by a \htmlref{Plot}{Plot}. It takes a
+      separate value for each graphical element so that, for instance,
+      the setting \texttt{"} Colour(title)=2\texttt{"}  causes the Plot title to be drawn
+      using colour index 2. The synonym \texttt{"} Color\texttt{"}  may also be used.
+
+      The range of integer colour indices available and their
+      appearance is determined by the underlying graphics system. The
+      default behaviour is for all graphical elements to be drawn
+      using the default colour index supplied by this graphics system
+      (normally, this is likely to result in white plotting on a black
+      background, or vice versa).
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of the graphical elements available, see the
+         description of the Plot class.
+
+         \sstitem
+         If no graphical element is specified, (e.g. \texttt{"} Colour\texttt{"}  instead
+         of \texttt{"} Colour(title)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will
+         affect the attribute value of all graphical elements, while a
+         \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}  operation will use just the Colour(TextLab)
+         value.
+      }
+   }
+}
+\sstroutine{
+   ColumnLenC(column)
+}{
+   The largest string length of any value in a column
+}{
+   \sstdescription{
+      This attribute holds the minimum length which a character variable
+      must have in order to be able to store the longest value currently
+      present (at any row) in a specified column of the supplied \htmlref{Table}{Table}.
+      This does not include room for a trailing null character.
+      The required column name should be placed inside the parentheses in
+      the attribute name. If the named column holds vector values, then
+      the attribute value is the length of the longest element of the
+      vector value.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Table
+      }{
+         All Tables have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the named column holds numerical values, the length returned
+         is the length of the largest string that would be generated if the
+         column values were accessed as strings.
+      }
+   }
+}
+\sstroutine{
+   ColumnLength(column)
+}{
+   The number of elements in each value in a column
+}{
+   \sstdescription{
+      This attribute holds the number of elements in each value stored
+      in a named column. Each value can be a scalar (in which case the
+      ColumnLength attribute has a value of 1), or a multi-dimensional
+      array ( in which case the ColumnLength value is equal to the
+      product of the array dimensions).
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ColumnNdim(column)
+}{
+   The number of axes spanned by each value in a column
+}{
+   \sstdescription{
+      This attribute holds the number of axes spanned by each value in a
+      column. If each cell in the column is a scalar, ColumnNdim will be
+      zero. If each cell in the column is a 1D spectrum, ColumnNdim will
+      be one. If each cell in the column is a 2D image, ColumnNdim will be
+      two, etc. The required column name should be placed inside the
+      parentheses in the attribute name.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ColumnType(column)
+}{
+   The data type of each value in a column
+}{
+   \sstdescription{
+      This attribute holds a integer value indicating the data type of
+      a named column in a \htmlref{Table}{Table}. This is the data type which was used
+      when the column was added to the Table using \htmlref{astAddColumn}{astAddColumn}. The
+      required column name should be placed inside the parentheses in
+      the attribute name.
+
+      The attribute value will be one of AST\_\_INTTYPE (for integer),
+      AST\_\_SINTTYPE (for
+      short int),
+      AST\_\_BYTETYPE (for
+      unsigned bytes - i.e. unsigned chars),
+      AST\_\_DOUBLETYPE (for double
+      precision floating point), AST\_\_FLOATTYPE (for single
+      precision floating point), AST\_\_STRINGTYPE (for character string),
+      AST\_\_OBJECTTYPE (for AST \htmlref{Object}{Object} pointer), AST\_\_POINTERTYPE (for
+      arbitrary C pointer) or AST\_\_UNDEFTYPE (for undefined values
+      created by
+      \htmlref{astMapPutU}{astMapPutU}).
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Table
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Comment
+}{
+   Include textual comments in output?
+}{
+   \sstdescription{
+      This is a boolean attribute which controls whether textual
+      comments are to be included in the output generated by a
+      \htmlref{Channel}{Channel}. If included, they will describe what each item of
+      output represents.
+
+      If Comment is non-zero, then comments will be included. If
+      it is zero, comments will be omitted.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         The default value is non-zero for a normal Channel.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         The default value is non-zero for a FitsChan.
+      }
+      \sstsubsection{
+         \htmlref{XmlChan}{XmlChan}
+      }{
+         The default value is zero for an XmlChan.
+      }
+   }
+}
+\sstroutine{
+   Current
+}{
+   FrameSet current Frame index
+}{
+   \sstdescription{
+      This attribute gives the index of the \htmlref{Frame}{Frame} which is to be
+      regarded as the \texttt{"} current\texttt{"}  Frame within a \htmlref{FrameSet}{FrameSet}. The default
+      is the most recent Frame added to the FrameSet (this Frame
+      always has an index equal to the FrameSet\texttt{'} s \htmlref{Nframe}{Nframe} attribute).
+
+      When setting a new value for this attribute, a string may be
+      supplied instead of an integer index. In this case a search
+      is made within the FrameSet for a Frame that has its \htmlref{Domain}{Domain}
+      attribute value equal to the supplied string (the comparison is
+      case-insensitive). If found, the Frame is made the current Frame.
+      Otherwise an error is reported.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FrameSet
+      }{
+         All FrameSets have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Inverting a FrameSet (inverting the boolean sense of its
+         \htmlref{Invert}{Invert} attribute, with the \htmlref{astInvert}{astInvert} function for example) will
+         interchange the values of its \htmlref{Base}{Base} and Current attributes.
+      }
+   }
+}
+\sstroutine{
+   DSBCentre
+}{
+   The central position of interest in a dual sideband spectrum
+}{
+   \sstdescription{
+      This attribute specifies the central position of interest in a dual
+      sideband spectrum. Its sole use is to determine the local oscillator
+      frequency (the frequency which marks the boundary between the lower
+      and upper sidebands). See the description of the \htmlref{IF}{IF} (intermediate
+      frequency) attribute for details of how the local oscillator frequency
+      is calculated. The sideband containing this central position is
+      referred to as the \texttt{"} observed\texttt{"}  sideband, and the other sideband as
+      the \texttt{"} image\texttt{"}  sideband.
+
+      The value is accessed as a position in the spectral system
+      represented by the \htmlref{SpecFrame}{SpecFrame} attributes inherited by this class, but
+      is stored internally as topocentric frequency. Thus, if the \htmlref{System}{System}
+      attribute of the \htmlref{DSBSpecFrame}{DSBSpecFrame} is set to \texttt{"} VRAD\texttt{"} , the Unit attribute
+      set to \texttt{"} m/s\texttt{"}  and the \htmlref{StdOfRest}{StdOfRest} attribute set to \texttt{"} LSRK\texttt{"} , then values
+      for the DSBCentre attribute should be supplied as radio velocity in
+      units of \texttt{"} m/s\texttt{"}  relative to the kinematic LSR (alternative units may
+      be used by appending a suitable units string to the end of the value).
+      This value is then converted to topocentric frequency and stored. If
+      (say) the Unit attribute is subsequently changed to \texttt{"} km/s\texttt{"}  before
+      retrieving the current value of the DSBCentre attribute, the stored
+      topocentric frequency will be converted back to LSRK radio velocity,
+      this time in units of \texttt{"} km/s\texttt{"} , before being returned.
+
+      The default value for this attribute is 30 GHz.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         DSBSpecFrame
+      }{
+         All DSBSpecFrames have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Note
+   }{
+      \sstitemlist{
+
+         \sstitem
+         The attributes which define the transformation to or from topocentric
+         frequency should be assigned their correct values before accessing
+         this attribute. These potentially include System, Unit, StdOfRest,
+         \htmlref{ObsLon}{ObsLon}, \htmlref{ObsLat}{ObsLat}, \htmlref{ObsAlt}{ObsAlt}, \htmlref{Epoch}{Epoch}, \htmlref{RefRA}{RefRA}, \htmlref{RefDec}{RefDec} and \htmlref{RestFreq}{RestFreq}.
+      }
+   }
+}
+\sstroutine{
+   DefB1950
+}{
+   Use FK4 B1950 as defaults?
+}{
+   \sstdescription{
+      This attribute is a boolean value which specifies a default equinox
+      and reference frame to use when reading a \htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan}
+      with a foreign (i.e. non-native) encoding. It is only used if the FITS
+      header contains RA and DEC axes but contains no information about the
+      reference frame or equinox. If this is the case, then values of FK4 and
+      B1950 are assumed if the DefB1950 attribute has a non-zero value and
+      ICRS is assumed if DefB1950 is zero. The default value for DefB1950
+      depends on the value of the \htmlref{Encoding}{Encoding} attribute: for FITS-WCS encoding
+      the default is zero, and for all other encodings it is one.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Digits/Digits(axis)
+}{
+   Number of digits of precision
+}{
+   \sstdescription{
+      This attribute specifies how many digits of precision are
+      required by default when a coordinate value is formatted for a
+      \htmlref{Frame}{Frame} axis (e.g. using \htmlref{astFormat}{astFormat}). Its value may be set either
+      for a Frame as a whole, or (by subscripting the attribute name
+      with the number of an axis) for each axis individually. Any
+      value set for an individual axis will over-ride the value for
+      the Frame as a whole.
+
+      Note that the Digits value acts only as a means of determining a
+      default Format string. Its effects are over-ridden if a Format
+      string is set explicitly for an axis. However, if the Format
+      attribute specifies the precision using the string \texttt{"} .$*$\texttt{"} , then
+      the Digits attribute is used to determine the number of decimal
+      places to produce.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default Digits value supplied by the Frame class is 7. If
+         a value less than 1 is supplied, then 1 is used instead.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Digits attribute of a FrameSet (or one of its axes) is
+         the same as that of its current Frame (as specified by the
+         \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         \htmlref{Plot}{Plot}
+      }{
+         The default Digits value used by the Plot class when drawing
+         annotated axis labels is the smallest value which results in all
+         adjacent labels being distinct.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The Digits attribute is ignored when a TimeFrame formats a value
+         as a date and time string (see the Format attribute).
+      }
+   }
+}
+\sstroutine{
+   Direction(axis)
+}{
+   Display axis in conventional direction?
+}{
+   \sstdescription{
+      This attribute is a boolean value which suggests how the axes of
+      a \htmlref{Frame}{Frame} should be displayed (e.g.) in graphical output. By
+      default, it has the value one, indicating that they should be
+      shown in the conventional sense (increasing left to right for an
+      abscissa, and bottom to top for an ordinate). If set to zero,
+      this attribute indicates that the direction should be reversed,
+      as would often be done for an astronomical magnitude or a right
+      ascension axis.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default Direction value supplied by the Frame class is 1,
+         indicating that all axes should be displayed in the
+         conventional direction.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Direction value to
+         suggest that certain axes (e.g. right ascension) should be
+         plotted in reverse when appropriate.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Direction attribute of a FrameSet axis is the same as
+         that of its current Frame (as specified by the \htmlref{Current}{Current}
+         attribute).
+      }
+      \sstsubsection{
+         \htmlref{Plot}{Plot}
+      }{
+         The Direction attribute of the base Frame in a Plot is set to
+         indicate the sense of the two graphics axes, as implied by the
+         graphics bounding box supplied when the Plot was created.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+
+         \sstitem
+         The Direction attribute does not directly affect the behaviour
+         of the AST library. Instead, it serves as a hint to applications
+         programs about the orientation in which they may wish to display
+         any data associated with the Frame. Applications are free to
+         ignore this hint if they wish.
+      }
+   }
+}
+\sstroutine{
+   Disco
+}{
+   PcdMap pincushion/barrel distortion coefficient
+}{
+   \sstdescription{
+      This attribute specifies the pincushion/barrel distortion coefficient
+      used by a \htmlref{PcdMap}{PcdMap}. This coefficient is set when the PcdMap is created,
+      but may later be modified. If the attribute is cleared, its default
+      value is zero, which gives no distortion. For pincushion distortion,
+      the value should be positive. For barrel distortion, it should be
+      negative.
+
+      Note that the forward transformation of a PcdMap applies the
+      distortion specified by this attribute and the inverse
+      transformation removes this distortion. If the PcdMap is inverted
+      (e.g. using \htmlref{astInvert}{astInvert}), then the forward transformation will
+      remove the distortion and the inverse transformation will apply
+      it. The distortion itself will still be given by the same value of
+      Disco.
+
+      Note, the value of this attribute may changed only if the PcdMap
+      has no more than one reference. That is, an error is reported if the
+      PcdMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         PcdMap
+      }{
+         All PcdMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Domain
+}{
+   Coordinate system domain
+}{
+   \sstdescription{
+      This attribute contains a string which identifies the physical
+      domain of the coordinate system that a \htmlref{Frame}{Frame} describes.
+
+      The Domain attribute also controls how a Frame behaves when it is
+      used (by \htmlref{astFindFrame}{astFindFrame}) as a template to match another (target)
+      Frame. It does this by specifying the Domain that the target
+      Frame should have in order to match the template. If the Domain
+      value in the template Frame is set, then only targets with the
+      same Domain value will be matched. If the template\texttt{'} s Domain
+      value is not set, however, then the target\texttt{'} s Domain will be
+      ignored.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default Domain value supplied by the Frame class is an
+         empty string.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Domain value to be
+         \texttt{"} SKY\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The CmpFrame class re-defines the default Domain value to be
+         of the form \texttt{"} $<$dom1$>$-$<$dom2$>$\texttt{"} , where $<$dom1$>$ and $<$dom2$>$ are the
+         Domains of the two component Frames. If both these Domains are
+         blank, then the string \texttt{"} CMP\texttt{"}  is used as the default Domain name.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Domain attribute of a FrameSet is the same as that of its
+         current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         The SpecFrame class re-defines the default Domain value to be
+         \texttt{"} SPECTRUM\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{DSBSpecFrame}{DSBSpecFrame}
+      }{
+         The DSBSpecFrame class re-defines the default Domain value to be
+         \texttt{"} DSBSPECTRUM\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{FluxFrame}{FluxFrame}
+      }{
+         The FluxFrame class re-defines the default Domain value to be
+         \texttt{"} FLUX\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{SpecFluxFrame}{SpecFluxFrame}
+      }{
+         The FluxFrame class re-defines the default Domain value to be
+         \texttt{"} SPECTRUM-FLUX\texttt{"} .
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class re-defines the default Domain value to be
+         \texttt{"} TIME\texttt{"} .
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         All Domain values are converted to upper case and white space
+         is removed before use.
+      }
+   }
+}
+\sstroutine{
+   DrawAxes(axis)
+}{
+   Draw axes for a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether curves representing coordinate axes should be drawn.
+      It takes a separate value for each physical axis of a
+      \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} DrawAxes(2)=0\texttt{"}
+      specifies that no axis should be drawn for the second axis.
+
+      If drawn, these axis lines will pass through any tick marks
+      associated with numerical labels drawn to mark values on the
+      axes. The location of these tick marks and labels (and hence the
+      axis lines) is determined by the Plot\texttt{'} s \htmlref{LabelAt(axis)}{LabelAt(axis)} attribute.
+
+      If the DrawAxes value of a Plot is non-zero (the default), then
+      axis lines will be drawn, otherwise they will be omitted.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Axis}{Axis} lines are drawn independently of any coordinate grid
+         lines (see the \htmlref{Grid}{Grid} attribute) so grid lines may be used to
+         substitute for axis lines if required.
+
+         \sstitem
+         In some circumstances, numerical labels and tick marks are
+         drawn around the edges of the plotting area (see the \htmlref{Labelling}{Labelling}
+         attribute).  In this case, the value of the DrawAxes attribute
+         is ignored.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} DrawAxes\texttt{"}  instead of
+         \texttt{"} DrawAxes(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the DrawAxes(1) value.
+      }
+   }
+}
+\sstroutine{
+   DrawTitle
+}{
+   Draw a title for a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether a title is drawn.
+
+      If the DrawTitle value of a \htmlref{Plot}{Plot} is non-zero (the default), then
+      the title will be drawn, otherwise it will be omitted.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Plot3D}{Plot3D}
+      }{
+         The Plot3D class ignores this attributes, assuming a value of
+         zero.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The text used for the title is obtained from the Plot\texttt{'} s \htmlref{Title}{Title}
+         attribute.
+
+         \sstitem
+         The vertical placement of the title can be controlled using
+         the \htmlref{TitleGap}{TitleGap} attribute.
+      }
+   }
+}
+\sstroutine{
+   Dut1
+}{
+   The UT1-UTC correction
+}{
+   \sstdescription{
+      This attribute is used when calculating the Local Apparent Sidereal
+      Time corresponding to \htmlref{SkyFrame}{SkyFrame}\texttt{'} s \htmlref{Epoch}{Epoch} value (used when converting
+      positions to or from the \texttt{"} AzEl\texttt{"}  system). It should be set to the
+      difference, in seconds, between the UT1 and UTC timescales at the
+      moment in time represented by the SkyFrame\texttt{'} s Epoch attribute. The
+      value to use is unpredictable and depends on changes in the earth\texttt{'} s
+      rotation speed. Values for UT1-UTC can be obtained from the
+      International Earth Rotation and Reference Systems Service
+      (IERS) at http://www.iers.org/.
+
+      Currently, the correction is always less than 1 second. This is
+      ensured by the occasional introduction of leap seconds into the UTC
+      timescale. Therefore no great error will usually result if no value
+      is assigned to this attribute (in which case a default value of
+      zero is used). However, it is possible that a decision may be taken
+      at some time in the future to abandon the introduction of leap
+      seconds, in which case the DUT correction could grow to significant
+      sizes.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         All Frames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Edge(axis)
+}{
+   Which edges to label in a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      which edges of a \htmlref{Plot}{Plot} are used for displaying numerical and
+      descriptive axis labels. It takes a separate value for each
+      physical axis of the Plot so that, for instance, the setting
+      \texttt{"} Edge(2)=left\texttt{"}  specifies which edge to use to display labels for
+      the second axis.
+
+      The values \texttt{"} left\texttt{"} , \texttt{"} top\texttt{"} , \texttt{"} right\texttt{"}  and \texttt{"} bottom\texttt{"}  (or any
+      abbreviation) can be supplied for this attribute. The default is
+      usually \texttt{"} bottom\texttt{"}  for the first axis and \texttt{"} left\texttt{"}  for the second
+      axis. However, if exterior labelling was requested (see the
+      \htmlref{Labelling}{Labelling} attribute) but cannot be produced using these default
+      Edge values, then the default values will be swapped if this
+      enables exterior labelling to be produced.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Plot3D}{Plot3D}
+      }{
+         The Plot3D class ignores this attributes. Instead it uses its
+         own \htmlref{RootCorner}{RootCorner} attribute to determine which edges of the 3D plot
+         to label.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         In some circumstances, numerical labels will be drawn along
+         internal grid lines instead of at the edges of the plotting area
+         (see the Labelling attribute). In this case, the Edge attribute
+         only affects the placement of the descriptive labels (these are
+         drawn at the edges of the plotting area, rather than along the
+         axis lines).
+      }
+   }
+}
+\sstroutine{
+   Encoding
+}{
+   System for encoding Objects as FITS headers
+}{
+   \sstdescription{
+      This attribute specifies the encoding system to use when AST
+      Objects are stored as FITS header cards in a \htmlref{FitsChan}{FitsChan}. It
+      affects the behaviour of the \htmlref{astWrite}{astWrite} and \htmlref{astRead}{astRead} functions when
+      they are used to transfer any AST \htmlref{Object}{Object} to or from an external
+      representation consisting of FITS header cards (i.e. whenever a
+      write or read operation is performed using a FitsChan as the I/O
+      \htmlref{Channel}{Channel}).
+
+      There are several ways (conventions) by which coordinate system
+      information may be represented in the form of FITS headers and
+      the Encoding attribute is used to specify which of these should
+      be used. The encoding options available are outlined in the
+      \texttt{"} Encodings Available\texttt{"}  section below, and in more detail in the
+      sections which follow.
+
+      Encoding systems differ in the range of possible Objects
+      (e.g. classes) they can represent, in the restrictions they
+      place on these Objects (e.g. compatibility with some
+      externally-defined coordinate system model) and in the number of
+      Objects that can be stored together in any particular set of
+      FITS header cards (e.g. multiple Objects, or only a single
+      Object). The choice of encoding also affects the range of
+      external applications which can potentially read and interpret
+      the FITS header cards produced.
+
+      The encoding options available are not necessarily mutually
+      exclusive, and it may sometimes be possible to store multiple
+      Objects (or the same Object several times) using different
+      encodings within the same set of FITS header cards. This
+      possibility increases the likelihood of other applications being
+      able to read and interpret the information.
+
+      By default, a FitsChan will attempt to determine which encoding
+      system is already in use, and will set the default Encoding
+      value accordingly (so that subsequent I/O operations adopt the
+      same conventions). It does this by looking for certain critical
+      FITS keywords which only occur in particular encodings. For
+      details of how this works, see the \texttt{"} Choice of Default Encoding\texttt{"}
+      section below. If you wish to ensure that a particular encoding
+      system is used, independently of any FITS cards already present,
+      you should set an explicit Encoding value yourself.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Encodings Available
+   }{
+      The Encoding attribute can take any of the following (case
+      insensitive) string values to select the corresponding encoding
+
+      system:
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} DSS\texttt{"} : Encodes coordinate system information in FITS header
+         cards using the convention developed at the Space Telescope
+         Science Institute (STScI) for the Digitised Sky Survey (DSS)
+         astrometric plate calibrations. The main advantages of this
+         encoding are that FITS images which use it are widely available
+         and it is understood by a number of important and
+         well-established astronomy applications. For further details,
+         see the section \texttt{"} The DSS Encoding\texttt{"}  below.
+
+         \sstitem
+         \texttt{"} FITS-WCS\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions described in the FITS
+         world coordinate system (FITS-WCS) papers by E.W. Greisen,
+         M. Calabretta, et al. The main advantages of this encoding are that
+         it should be understood by any FITS-WCS compliant application and
+         is likely to be adopted widely for FITS data in future. For further
+         details, see the section \texttt{"} The FITS-WCS Encoding\texttt{"}  below.
+
+         \sstitem
+         \texttt{"} FITS-PC\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions described in an earlier draft
+         of the FITS world coordinate system papers by E.W. Greisen and
+         M. Calabretta. This encoding uses a combination of CDELTi and
+         PCiiijjj keywords to describe the scale and rotation of the pixel
+         axes. This encoding is included to support existing data and
+         software which uses these now superceded conventions. In general,
+         the \texttt{"} FITS-WCS\texttt{"}  encoding (which uses CDi\_j or PCi\_j keywords to
+         describe the scale and rotation) should be used in preference to
+         \texttt{"} FITS-PC\texttt{"} .
+
+         \sstitem
+         \texttt{"} FITS-IRAF\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions described in the document
+         \texttt{"} World Coordinate Systems Representations Within the FITS
+         Format\texttt{"}  by R.J. Hanisch and D.G. Wells, 1988.  This encoding is
+         currently employed by the IRAF data analysis facility, so its
+         use will facilitate data exchange with IRAF. Its main advantages
+         are that it is a stable convention which approximates to a
+         subset of the propsed FITS-WCS encoding (above). This makes it
+         suitable as an interim method for storing coordinate system
+         information in FITS headers until the FITS-WCS encoding becomes
+         stable. Since many datasets currently use the FITS-IRAF
+         encoding, conversion of data from FITS-IRAF to the final form of
+         FITS-WCS is likely to be well supported.
+
+         \sstitem
+         \texttt{"} FITS-AIPS\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions originally introduced by the
+         AIPS data analysis facility. This is base on the use of CDELTi and
+         CROTAi keuwords to desribe the scale and rotation of each axis.
+         These conventions have been superceded but are still widely used.
+
+         \sstitem
+         \texttt{"} FITS-AIPS$+$$+$\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions used by the AIPS$+$$+$ project.
+         This is an extension of FITS-AIPS which includes some of the
+         features of FITS-IRAF and FITS-PC.
+
+         \sstitem
+         \texttt{"} FITS-CLASS\texttt{"} : Encodes coordinate system information in FITS
+         header cards using the conventions used by the CLASS project.
+         CLASS is a software package for reducing single-dish radio and
+         sub-mm spectroscopic data. See the section \texttt{"} CLASS FITS format\texttt{"}  at
+         http://www.iram.fr/IRAMFR/GILDAS/doc/html/class-html/.
+
+         \sstitem
+         \texttt{"} NATIVE\texttt{"} : Encodes AST Objects in FITS header cards using a
+         convention which is private to the AST library (but adheres to
+         the general FITS standard) and which uses FITS keywords that
+         will not clash with other encoding systems. The main advantages
+         of this are that any class of AST Object may be encoded, and any
+         (reasonable) number of Objects may be stored sequentially in the
+         same FITS header. This makes FITS headers an almost loss-less
+         communication path for passing AST Objects between applications
+         (although all such applications must, of course, make use of the
+         AST library to interpret the information). For further details,
+         see the section \texttt{"} The NATIVE Encoding\texttt{"}  below.
+      }
+   }
+   \sstdiytopic{
+      Choice of Default Encoding
+   }{
+      If the Encoding attribute of a FitsChan is not set, the default
+      value it takes is determined by the presence of certain critical
+      FITS keywords within the FitsChan. The sequence of decisions
+
+      used to arrive at the default value is as follows:
+
+      \sstitemlist{
+
+         \sstitem
+         If the FitsChan contains any keywords beginning with the
+         string \texttt{"} BEGAST\texttt{"} , then NATIVE encoding is used,
+
+         \sstitem
+         Otherwise, FITS-CLASS is used if the FitsChan contains a DELTAV
+         keyword and a keyword of the form VELO-xxx, where xxx indicates one
+         of the rest frames used by class (e.g. \texttt{"} VELO-LSR\texttt{"} ), or \texttt{"} VLSR\texttt{"} .
+
+         \sstitem
+         Otherwise, if the FitsChan contains a CTYPE keyword which
+         represents a spectral axis using the conventions of the AIPS and
+         AIPS$+$$+$ projects (e.g. \texttt{"} FELO-LSR\texttt{"} , etc), then one of FITS-AIPS or
+         FITS-AIPS$+$$+$ encoding is used. FITS-AIPS$+$$+$ is used if any of the
+         keywords CDi\_j, PROJP, LONPOLE or LATPOLE are
+         found in the FitsChan. Otherwise FITS-AIPS is used.
+
+         \sstitem
+         Otherwise, if the FitsChan contains a keyword of the form
+         \texttt{"} PCiiijjj\texttt{"} , where \texttt{"} i\texttt{"}  and \texttt{"} j\texttt{"}  are single digits, then
+         FITS-PC encoding is used,
+
+         \sstitem
+         Otherwise, if the FitsChan contains a keyword of the form
+         \texttt{"} CDiiijjj\texttt{"} , where \texttt{"} i\texttt{"}  and \texttt{"} j\texttt{"}  are single digits, then
+         FITS-IRAF encoding is used,
+
+         \sstitem
+         Otherwise, if the FitsChan contains a keyword of the form
+         \texttt{"} CDi\_j\texttt{"} , and at least one of RADECSYS, PROJPi, or CjVALi
+         where \texttt{"} i\texttt{"}  and \texttt{"} j\texttt{"}  are single digits, then FITS-IRAF encoding is
+         used.
+
+         \sstitem
+         Otherwise, if the FitsChan contains any keywords of the form
+         PROJPi, CjVALi or RADECSYS, where \texttt{"} i\texttt{"}  and \texttt{"} j\texttt{"}  are single digits,
+         then FITS-PC encoding is used.
+
+         \sstitem
+         Otherwise, if the FitsChan contains a keyword of the form
+         CROTAi, where \texttt{"} i\texttt{"}  is a single digit, then FITS-AIPS encoding is
+         used.
+
+         \sstitem
+         Otherwise, if the FitsChan contains a keyword of the form
+         CRVALi, where \texttt{"} i\texttt{"}  is a single digit, then FITS-WCS encoding is
+         used.
+
+         \sstitem
+         Otherwise, if the FitsChan contains the \texttt{"} PLTRAH\texttt{"}  keyword, then
+         DSS encoding is used,
+
+         \sstitem
+         Otherwise, if none of these conditions is met (as would be the
+         case when using an empty FitsChan), then NATIVE encoding is
+         used.
+
+      }
+      Except for the NATIVE and DSS encodings, all the above checks
+      also require that the header contains at least one CTYPE, CRPIX and
+      CRVAL keyword (otherwise the checking process continues to the next
+      case).
+
+      Setting an explicit value for the Encoding attribute always
+      over-rides this default behaviour.
+
+      Note that when writing information to a FitsChan, the choice of
+      encoding will depend greatly on the type of application you
+      expect to be reading the information in future. If you do not
+      know this, there may sometimes be an advantage in writing the
+      information several times, using a different encoding on each
+      occasion.
+   }
+   \sstdiytopic{
+      The DSS Encoding
+   }{
+      The DSS encoding uses FITS header cards to store a multi-term
+      polynomial which relates pixel positions on a digitised
+      photographic plate to celestial coordinates (right ascension and
+      declination). This encoding may only be used to store a single
+      AST Object in any set of FITS header cards, and that Object must
+      be a \htmlref{FrameSet}{FrameSet} which conforms to the STScI/DSS coordinate system
+      model (this means the \htmlref{Mapping}{Mapping} which relates its base and current
+      Frames must include either a \htmlref{DssMap}{DssMap} or a \htmlref{WcsMap}{WcsMap} with type
+      AST\_\_TAN or AST\_\_TPN).
+
+      When reading a DSS encoded Object (using astRead), the FitsChan
+      concerned must initially be positioned at the first card (its
+      \htmlref{Card}{Card} attribute must equal 1) and the result of the read, if
+      successful, will always be a pointer to a FrameSet. The base
+      \htmlref{Frame}{Frame} of this FrameSet represents DSS pixel coordinates, and the
+      current Frame represents DSS celestial coordinates. Such a read
+      is always destructive and causes the FITS header cards required
+      for the construction of the FrameSet to be removed from the
+      FitsChan, which is then left positioned at the \texttt{"} end-of-file\texttt{"} . A
+      subsequent read using the same encoding will therefore not
+      return another FrameSet, even if the FitsChan is rewound.
+
+      When astWrite is used to store a FrameSet using DSS encoding,
+      an attempt is first made to simplify the FrameSet to see if it
+      conforms to the DSS model.  Specifically, the current Frame must
+      be a FK5 \htmlref{SkyFrame}{SkyFrame}; the projection must be a tangent plane
+      (gnomonic) projection with polynomial corrections conforming to
+      DSS requirements, and north must be parallel to the second base
+      Frame axis.
+
+      If the simplification process succeeds, a description of the
+      FrameSet is written to the FitsChan using appropriate DSS FITS
+      header cards. The base Frame of the FrameSet is used to form the
+      DSS pixel coordinate system and the current Frame gives the DSS
+      celestial coordinate system.  A successful write operation will
+      over-write any existing DSS encoded data in the FitsChan, but
+      will not affect other (non-DSS) header cards. If a destructive
+      read of a DSS encoded Object has previously occurred, then an
+      attempt will be made to store the FITS header cards back in
+      their original locations.
+
+      If an attempt to simplify a FrameSet to conform to the DSS model
+      fails (or if the Object supplied is not a FrameSet), then no
+      data will be written to the FitsChan and astWrite will return
+      zero. No error will result.
+   }
+   \sstdiytopic{
+      The FITS-WCS Encoding
+   }{
+      The FITS-WCS convention uses FITS header cards to describe the
+      relationship between pixels in an image (not necessarily
+      2-dimensional) and one or more related \texttt{"} world coordinate systems\texttt{"} .
+      The FITS-WCS encoding may only be used to store a single AST Object
+      in any set of FITS header cards, and that Object must be a FrameSet
+      which conforms to the FITS-WCS model (the FrameSet may, however,
+      contain multiple Frames which will be result in multiple FITS
+      \texttt{"} alternate axis descriptions\texttt{"} ). Details of the use made by this
+      Encoding of the conventions described in the FITS-WCS papers are
+      given in the appendix \texttt{"} FITS-WCS Coverage\texttt{"}  of this document. A few
+      main points are  described below.
+
+      The rotation and scaling of the intermediate world coordinate system
+      can be specified using either \texttt{"} CDi\_j\texttt{"}  keywords, or \texttt{"} PCi\_j\texttt{"}  together
+      with \texttt{"} CDELTi\texttt{"}  keywords. When writing a FrameSet to a FitsChan, the
+      the value of the \htmlref{CDMatrix}{CDMatrix} attribute of the FitsChan determines
+      which system is used.
+
+      In addition, this encoding supports the \texttt{"} TAN with polynomial correction
+      terms\texttt{"}  projection which was included in a draft of the FITS-WCS paper,
+      but was not present in the final version. A \texttt{"} TAN with polynomial
+      correction terms\texttt{"}  projection is represented using a WcsMap with type
+      AST\_\_TPN (rather than AST\_\_TAN which is used to represent simple
+      TAN projections). When reading a FITS header, a CTYPE keyword value
+      including a \texttt{"} -TAN\texttt{"}  code results in an AST\_\_TPN projection if there are
+      any projection parameters (given by the \htmlref{PVi\_m}{PVi\_m} keywords) associated with
+      the latitude axis, or if there are projection parameters associated
+      with the longitude axis for m greater than 4. When writing a
+      FrameSet to a FITS header, an AST\_\_TPN projection gives rise to a
+      CTYPE value including the normal \texttt{"} -TAN\texttt{"}  code, but the projection
+      parameters are stored in keywords with names \texttt{"} QVi\_m\texttt{"} , instead of the
+      usual \texttt{"} PVi\_m\texttt{"} . Since these QV parameters are not part of the
+      FITS-WCS standard they will be ignored by other non-AST software,
+      resulting in the WCS being interpreted as a simple TAN projection
+      without any corrections. This should be seen as an interim solution
+      until such time as an agreed method for describing projection
+      distortions within FITS-WCS has been published.
+
+      AST extends the range of celestial coordinate systems which may be
+      described using this encoding by allowing the inclusion of
+      \texttt{"} AZ--\texttt{"}  and \texttt{"} EL--\texttt{"}  as the coordinate specification within CTYPE
+      values. These form a longitude/latitude pair of axes which describe
+      azimuth and elevation. The geographic position of the observer
+      should be supplied using the OBSGEO-X/Y/Z keywords described in FITS-WCS
+      paper III. Currently, a simple model is used which includes diurnal
+      aberration, but ignores atmospheric refraction, polar motion, etc.
+      These may be added in a later release.
+
+      If an AST SkyFrame that represents offset rather than absolute
+      coordinates (see attribute \htmlref{SkyRefIs}{SkyRefIs}) is written to a FitsChan using
+      FITS-WCS encoding, two alternate axis descriptions will be created.
+      One will describe the offset coordinates, and will use \texttt{"} OFLN\texttt{"}  and
+      \texttt{"} OFLT\texttt{"}  as the axis codes in the CTYPE keywords. The other will
+      describe absolute coordinates as specified by the \htmlref{System}{System} attribute
+      of the SkyFrame, using the usual CTYPE codes (\texttt{"} RA--\texttt{"} /\texttt{"} DEC-\texttt{"} , etc).
+      In addition, the absolute coordinates description will contain
+      AST-specific keywords (SREF1/2, SREFP1/2 and SREFIS) that allow the
+      header to be read back into AST in order to reconstruct the original
+      SkyFrame.
+
+      When reading a FITS-WCS encoded Object (using astRead), the FitsChan
+      concerned must initially be positioned at the first card (its
+      Card attribute must equal 1) and the result of the read, if
+      successful, will always be a pointer to a FrameSet. The base
+      Frame of this FrameSet represents FITS-WCS pixel coordinates,
+      and the current Frame represents the physical coordinate system
+      described by the FITS-WCS primary axis descriptions. If
+      secondary axis descriptions are also present, then the FrameSet
+      may contain additional (non-current) Frames which represent
+      these.  Such a read is always destructive and causes the FITS
+      header cards required for the construction of the FrameSet to be
+      removed from the FitsChan, which is then left positioned at the
+      \texttt{"} end-of-file\texttt{"} . A subsequent read using the same encoding will
+      therefore not return another FrameSet, even if the FitsChan is
+      rewound.
+
+      When astWrite is used to store a FrameSet using FITS-WCS
+      encoding, an attempt is first made to simplify the FrameSet to
+      see if it conforms to the FITS-WCS model. If this simplification
+      process succeeds (as it often should, as the model is reasonably
+      flexible), a description of the FrameSet is written to the
+      FitsChan using appropriate FITS header cards. The base Frame of
+      the FrameSet is used to form the FITS-WCS pixel coordinate
+      system and the current Frame gives the physical coordinate
+      system to be described by the FITS-WCS primary axis
+      descriptions.  Any additional Frames in the FrameSet may be used
+      to construct secondary axis descriptions, where appropriate.
+
+      A successful write operation will over-write any existing
+      FITS-WCS encoded data in the FitsChan, but will not affect other
+      (non-FITS-WCS) header cards. If a destructive read of a FITS-WCS
+      encoded Object has previously occurred, then an attempt will be
+      made to store the FITS header cards back in their original
+      locations. Otherwise, the new cards will be inserted following
+      any other FITS-WCS related header cards present or, failing
+      that, in front of the current card (as given by the Card
+      attribute).
+
+      If an attempt to simplify a FrameSet to conform to the FITS-WCS
+      model fails (or if the Object supplied is not a FrameSet), then
+      no data will be written to the FitsChan and astWrite will
+      return zero. No error will result.
+   }
+   \sstdiytopic{
+      The FITS-IRAF Encoding
+   }{
+      The FITS-IRAF encoding can, for most purposes, be considered as
+      a subset of the FITS-WCS encoding (above), although it differs
+      in the details of the FITS keywords used. It is used in exactly
+      the same way and has the same restrictions, but with the
+
+      addition of the following:
+
+      \sstitemlist{
+
+         \sstitem
+         The only celestial coordinate systems that may be represented
+         are equatorial, galactic and ecliptic,
+
+         \sstitem
+         Sky projections can be represented only if any associated
+         projection parameters are set to their default values.
+
+         \sstitem
+         Secondary axis descriptions are not supported, so when writing
+         a FrameSet to a FitsChan, only information from the base and
+         current Frames will be stored.
+
+      }
+      Note that this encoding is provided mainly as an interim measure to
+      provide a more stable alternative to the FITS-WCS encoding until the
+      FITS standard for encoding WCS information is finalised.  The name
+      \texttt{"} FITS-IRAF\texttt{"}  indicates the general keyword conventions used and does
+      not imply that this encoding will necessarily support all features of
+      the WCS scheme used by IRAF software. Nevertheless, an attempt has
+      been made to support a few such features where they are known to be
+      used by important sources of data.
+
+      When writing a FrameSet using the FITS-IRAF encoding, axis rotations
+      are specified by a matrix of FITS keywords of the form \texttt{"} CDi\_j\texttt{"} , where
+      \texttt{"} i\texttt{"}  and \texttt{"} j\texttt{"}  are single digits. The alternative form \texttt{"} CDiiijjj\texttt{"} , which
+      is also in use, is recognised when reading an Object, but is never
+      written.
+
+      In addition, the experimental IRAF \texttt{"} ZPX\texttt{"}  and \texttt{"} TNX\texttt{"}  sky projections will
+      be accepted when reading, but will never be written (the corresponding
+      FITS \texttt{"} ZPN\texttt{"}  or \texttt{"} distorted TAN\texttt{"}  projection being used instead). However,
+      there are restrictions on the use of these experimental projections.
+      For \texttt{"} ZPX\texttt{"} , longitude and latitude correction surfaces (appearing as
+      \texttt{"} lngcor\texttt{"}  or \texttt{"} latcor\texttt{"}  terms in the IRAF-specific \texttt{"} WAT\texttt{"}  keywords) are
+      not supported. For \texttt{"} TNX\texttt{"}  projections, only cubic surfaces encoded as
+      simple polynomials with \texttt{"} half cross-terms\texttt{"}  are supported. If an
+      un-usable \texttt{"} TNX\texttt{"}  or \texttt{"} ZPX\texttt{"}  projection is encountered while reading
+      from a FitsChan, a simpler form of TAN or ZPN projection is used
+      which ignores the unsupported features and may therefore be
+      inaccurate. If this happens, a warning message is added to the
+      contents of the FitsChan as a set of cards using the keyword \texttt{"} ASTWARN\texttt{"} .
+
+      You should not normally attempt to mix the foreign FITS encodings within
+      the same FitsChan, since there is a risk that keyword clashes may occur.
+   }
+   \sstdiytopic{
+      The FITS-PC Encoding
+   }{
+      The FITS-PC encoding can, for most purposes, be considered as
+      equivalent to the FITS-WCS encoding (above), although it differs
+      in the details of the FITS keywords used. It is used in exactly
+      the same way and has the same restrictions.
+   }
+   \sstdiytopic{
+      The FITS-AIPS Encoding
+   }{
+      The FITS-AIPS encoding can, for most purposes, be considered as
+      equivalent to the FITS-WCS encoding (above), although it differs
+      in the details of the FITS keywords used. It is used in exactly
+      the same way and has the same restrictions, but with the
+
+      addition of the following:
+
+      \sstitemlist{
+
+         \sstitem
+         The only celestial coordinate systems that may be represented
+         are equatorial, galactic and ecliptic,
+
+         \sstitem
+         Spectral axes can only be represented if they represent
+         frequency, radio velocity or optical velocity, and are linearly
+         sampled in frequency. In addition, the standard of rest
+         must be LSRK, LSRD, barycentric or geocentric.
+
+         \sstitem
+         Sky projections can be represented only if any associated
+         projection parameters are set to their default values.
+
+         \sstitem
+         The AIT, SFL and MER projections can only be written if the CRVAL
+         keywords are zero for both longitude and latitude axes.
+
+         \sstitem
+         Secondary axis descriptions are not supported, so when writing
+         a FrameSet to a FitsChan, only information from the base and
+         current Frames will be stored.
+
+         \sstitem
+         If there are more than 2 axes in the base and current Frames, any
+         rotation must be restricted to the celestial plane, and must involve
+         no shear.
+      }
+   }
+   \sstdiytopic{
+      The FITS-AIPS$+$$+$ Encoding
+   }{
+      The FITS-AIPS$+$$+$ encoding is based on the FITS-AIPS encoding, but
+      includes some features of the FITS-IRAF and FITS-PC encodings.
+      Specifically, any celestial projections supported by FITS-PC may be
+      used, including those which require parameterisation, and the axis
+      rotation and scaling may be specified using CDi\_j keywords. When
+      writing a FITS header, rotation will be specified using CROTA/CDELT
+      keywords if possible, otherwise CDi\_j keywords will be used instead.
+   }
+   \sstdiytopic{
+      The FITS-CLASS Encoding
+   }{
+      The FITS-CLASS encoding uses the conventions of the CLASS project.
+      These are described in the section \texttt{"} Developer Manual\texttt{"} /\texttt{"} CLASS FITS
+
+      Format\texttt{"}  contained in the CLASS documentation at:
+
+      http://www.iram.fr/IRAMFR/GILDAS/doc/html/class-html/class.html.
+
+      This encoding is similar to FITS-AIPS with the following restrictions:
+
+      \sstitemlist{
+
+         \sstitem
+         When a \htmlref{SpecFrame}{SpecFrame} is created by reading a FITS-CLASS header, the
+           attributes describing the observer\texttt{'} s position (\htmlref{ObsLat}{ObsLat}, \htmlref{ObsLon}{ObsLon} and
+           \htmlref{ObsAlt}{ObsAlt}) are left unset because the CLASS encoding does not specify
+           these values. Conversions to or from the topocentric standard of rest
+           will therefore be inaccurate (typically by up to about 0.5 km/s)
+           unless suitable values are assigned to these attributes after the
+           FrameSet has been created.
+
+         \sstitem
+         When writing a FrameSet to a FITS-CLASS header, the current Frame
+           in the FrameSet must have at least 3 WCS axes, of which one must be
+           a linear spectral axis. The spectral axis in the created header will
+           always describe frequency. If the spectral axis in the supplied
+           FrameSet refers to some other system (e.g. radio velocity, etc),
+           then it will be converted to frequency.
+
+         \sstitem
+         There must be a pair of celestial axes - either (RA,Dec) or
+           (GLON,GLAT). RA and Dec must be either FK4/B1950 or FK5/J2000.
+
+         \sstitem
+         A limited range of projection codes (TAN, ARC, STG, AIT, SFL, SIN)
+           can be used. For AIT and SFL, the reference point must be at the
+           origin of longitude and latitude. For SIN, the associated projection
+           parameters must both be zero.
+
+         \sstitem
+         No rotation of the celestial axes is allowed, unless the spatial
+           axes are degenerate (i.e. cover only a single pixel).
+
+         \sstitem
+         The frequency axis in the created header will always describe
+           frequency in the source rest frame. If the supplied FrameSet uses
+           some other standard of rest then suitable conversion will be applied.
+
+         \sstitem
+         The source velocity must be defined. In other words, the SpecFrame
+           attributes \htmlref{SourceVel}{SourceVel} and \htmlref{SourceVRF}{SourceVRF} must have been assigned values.
+
+         \sstitem
+         The frequency axis in a FITS-CLASS header does not represent
+           absolute frequency, but instead represents offsets from the rest
+           frequency in the standard of rest of the source.
+
+      }
+      When writing a FrameSet out using FITS-CLASS encoding, the current
+      Frame may be temporarily modified if this will allow the header
+      to be produced. If this is done, the associated pixel-$>$WCS Mapping
+      will also be modified to take account of the changes to the Frame.
+      The modifications performed include re-ordering axes (WCS axes, not
+      pixel axes), changing spectral coordinate system and standard of
+      rest, changing the celestial coordinate system and reference equinox,
+      and changing axis units.
+   }
+   \sstdiytopic{
+      The NATIVE Encoding
+   }{
+      The NATIVE encoding may be used to store a description of any
+      class of AST Object in the form of FITS header cards, and (for
+      most practical purposes) any number of these Object descriptions
+      may be stored within a single set of FITS cards. If multiple
+      Object descriptions are stored, they are written and read
+      sequentially. The NATIVE encoding makes use of unique FITS
+      keywords which are designed not to clash with keywords that have
+      already been used for other purposes (if a potential clash is
+      detected, an alternative keyword is constructed to avoid the
+      clash).
+
+      When reading a NATIVE encoded object from a FitsChan (using
+      astRead), FITS header cards are read, starting at the current
+      card (as determined by the Card attribute), until the start of
+      the next Object description is found. This description is then
+      read and converted into an AST Object, for which a pointer is
+      returned. Such a read is always destructive and causes all the
+      FITS header cards involved in the Object description to be
+      removed from the FitsChan, which is left positioned at the
+      following card.
+
+      The Object returned may be of any class, depending on the
+      description that was read, and other AST routines may be used to
+      validate it (for example, by examining its \htmlref{Class}{Class} or \htmlref{ID}{ID} attribute
+      using astGetC). If further NATIVE encoded Object descriptions
+      exist in the FitsChan, subsequent calls to astRead will return
+      the Objects they describe in sequence (and destroy their
+      descriptions) until no more remain between the current card and
+      the \texttt{"} end-of-file\texttt{"} .
+
+      When astWrite is used to write an Object using NATIVE encoding,
+      a description of the Object is inserted immediately before the
+      current card (as determined by the Card attribute).  Multiple
+      Object descriptions may be written in this way and are stored
+      separately (and sequentially if the Card attribute is not
+      modified between the writes). A write operation using the NATIVE
+      encoding does not over-write previously written Object
+      descriptions. Note, however, that subsequent behaviour is
+      undefined if an Object description is written inside a
+      previously-written description, so this should be avoided.
+
+      When an Object is written to a FitsChan using NATIVE encoding,
+      astWrite should (barring errors) always transfer data and
+      return a value of 1.
+   }
+}
+\sstroutine{
+   Epoch
+}{
+   Epoch of observation
+}{
+   \sstdescription{
+      This attribute is used to qualify the coordinate systems described by
+      a \htmlref{Frame}{Frame}, by giving the moment in time when the coordinates are known
+      to be correct. Often, this will be the date of observation, and is
+      important in cases where coordinates systems move with respect to each
+      other over the course of time.
+
+      The Epoch attribute is stored as a Modified Julian Date, but
+      when setting its value it may be given in a variety of
+      formats. See the \texttt{"} Input Formats\texttt{"}  section (below) for details.
+      Strictly, the Epoch value should be supplied in the TDB timescale,
+      but for some purposes (for instance, for converting sky positions
+      between different types of equatorial system) the timescale is not
+      significant, and UTC may be used.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute. The basic Frame class provides
+         a default of J2000.0 (Julian) but makes no use of the Epoch value.
+         This is because the Frame class does not distinguish between
+         different Cartesian coordinate systems (see the \htmlref{System}{System} attribute).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The default Epoch value for a CmpFrame is selected as follows;
+         if the Epoch attribute has been set in the first component Frame
+         then the Epoch value from the first component Frame is used as
+         the default for the CmpFrame. Otherwise, if the Epoch attribute has
+         been set in the second component Frame then the Epoch value from the
+         second component Frame is used as the default for the CmpFrame.
+         Otherwise, the default Epoch value from the first component
+         Frame is used as the default for the CmpFrame. When the Epoch
+         attribute of a CmpFrame is set or cleared, it is also set or
+         cleared in the two component Frames.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Epoch attribute of a FrameSet is the same as that of its current
+         Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The coordinates of sources within a SkyFrame can change with time
+         for various reasons, including: (i) changing aberration of light
+         caused by the observer\texttt{'} s velocity (e.g. due to the Earth\texttt{'} s motion
+         around the Sun), (ii) changing gravitational deflection by the Sun
+         due to changes in the observer\texttt{'} s position with time, (iii) fictitious
+         motion due to rotation of non-inertial coordinate systems (e.g. the
+         old FK4 system), and (iv) proper motion of the source itself (although
+         this last effect is not handled by the SkyFrame class because it
+         affects individual sources rather than the coordinate system as
+         a whole).
+
+         The default Epoch value in a SkyFrame is B1950.0 (Besselian) for the
+         old FK4-based coordinate systems (see the System attribute) and
+         J2000.0 (Julian) for all others.
+
+         Care must be taken to distinguish the Epoch value, which relates to
+         motion (or apparent motion) of the source, from the superficially
+         similar \htmlref{Equinox}{Equinox} value. The latter is used to qualify a coordinate
+         system which is itself in motion in a (notionally) predictable way
+         as a result of being referred to a slowly moving reference plane
+         (e.g. the equator).
+
+         See the description of the System attribute for details of which
+         qualifying attributes apply to each celestial coordinate system.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         A TimeFrame describes a general time axis and so cannot be completely
+         characterised by a single Epoch value. For this reason the TimeFrame
+         class makes no use of the Epoch attribute. However, user code can
+         still make use of the attribute if necessary to represent a \texttt{"} typical\texttt{"}
+         time spanned by the TimeFrame. The default Epoch value for a TimeFrame
+         will be the TDB equivalent of the current value of the TimeFrame\texttt{'} s
+         \htmlref{TimeOrigin}{TimeOrigin} attribute. If no value has been set for TimeOrigin,
+         then the default Epoch value is J2000.0.
+      }
+   }
+   \sstdiytopic{
+      Input Formats
+   }{
+      The formats accepted when setting an Epoch value are listed
+      below. They are all case-insensitive and are generally tolerant
+      of extra white space and alternative field delimiters:
+
+      \sstitemlist{
+
+         \sstitem
+         Besselian Epoch: Expressed in decimal years, with or without
+         decimal places (\texttt{"} B1950\texttt{"}  or \texttt{"} B1976.13\texttt{"}  for example).
+
+         \sstitem
+         Julian Epoch: Expressed in decimal years, with or without
+         decimal places (\texttt{"} J2000\texttt{"}  or \texttt{"} J2100.9\texttt{"}  for example).
+
+         \sstitem
+         Year: Decimal years, with or without decimal places (\texttt{"} 1996.8\texttt{"}
+         for example).  Such values are interpreted as a Besselian epoch
+         (see above) if less than 1984.0 and as a Julian epoch otherwise.
+
+         \sstitem
+         Julian Date: With or without decimal places (\texttt{"} JD 2454321.9\texttt{"}  for
+         example).
+
+         \sstitem
+         Modified Julian Date: With or without decimal places
+         (\texttt{"} MJD 54321.4\texttt{"}  for example).
+
+         \sstitem
+         Gregorian Calendar Date: With the month expressed either as an
+         integer or a 3-character abbreviation, and with optional decimal
+         places to represent a fraction of a day (\texttt{"} 1996-10-2\texttt{"}  or
+         \texttt{"} 1996-Oct-2.6\texttt{"}  for example). If no fractional part of a day is
+         given, the time refers to the start of the day (zero hours).
+
+         \sstitem
+         Gregorian Date and Time: Any calendar date (as above) but with
+         a fraction of a day expressed as hours, minutes and seconds
+         (\texttt{"} 1996-Oct-2 12:13:56.985\texttt{"}  for example). The date and time can be
+         separated by a space or by a \texttt{"} T\texttt{"}  (as used by ISO8601 format).
+      }
+   }
+   \sstdiytopic{
+      Output Format
+   }{
+      When enquiring Epoch values, the format used is the \texttt{"} Year\texttt{"}
+      format described under \texttt{"} Input Formats\texttt{"} . This is a value in
+      decimal years which will be a Besselian epoch if less than
+      1984.0 and a Julian epoch otherwise.  By omitting any character
+      prefix, this format allows the Epoch value to be obtained as
+      either a character string or a floating point value.
+   }
+}
+\sstroutine{
+   Equinox
+}{
+   Epoch of the mean equinox
+}{
+   \sstdescription{
+      This attribute is used to qualify those celestial coordinate
+      systems described by a \htmlref{SkyFrame}{SkyFrame} which are notionally based on
+      the ecliptic (the plane of the Earth\texttt{'} s orbit around the Sun)
+      and/or the Earth\texttt{'} s equator.
+
+      Both of these planes are in motion and their positions are
+      difficult to specify precisely. In practice, therefore, a model
+      ecliptic and/or equator are used instead. These, together with
+      the point on the sky that defines the coordinate origin (the
+      intersection of the two planes termed the \texttt{"} mean equinox\texttt{"} ) move
+      with time according to some model which removes the more rapid
+      fluctuations. The SkyFrame class supports both the FK4 and
+      FK5 models.
+
+      The position of a fixed source expressed in any of these
+      coordinate systems will appear to change with time due to
+      movement of the coordinate system itself (rather than motion of
+      the source).  Such coordinate systems must therefore be
+      qualified by a moment in time (the \texttt{"} epoch of the mean equinox\texttt{"}
+      or \texttt{"} equinox\texttt{"}  for short) which allows the position of the model
+      coordinate system on the sky to be determined. This is the role
+      of the Equinox attribute.
+
+      The Equinox attribute is stored as a Modified Julian Date, but
+      when setting or getting its value you may use the same formats
+      as for the \htmlref{Epoch}{Epoch} attribute (q.v.).
+
+      The default Equinox value is B1950.0 (Besselian) for the old
+      FK4-based coordinate systems (see the \htmlref{System}{System} attribute) and
+      J2000.0 (Julian) for all others.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Care must be taken to distinguish the Equinox value, which
+         relates to the definition of a time-dependent coordinate system
+         (based on solar system reference planes which are in motion),
+         from the superficially similar Epoch value. The latter is used
+         to qualify coordinate systems where the positions of sources
+         change with time (or appear to do so) for a variety of other
+         reasons, such as aberration of light caused by the observer\texttt{'} s
+         motion, etc.
+
+         \sstitem
+         See the description of the System attribute for details of
+         which qualifying attributes apply to each celestial coordinate
+         system.
+      }
+   }
+}
+\sstroutine{
+   Escape
+}{
+   Allow changes of character attributes within strings?
+}{
+   \sstdescription{
+      This attribute controls the appearance of text strings and numerical
+      labels drawn by the \htmlref{astGrid}{astGrid} and (for the \htmlref{Plot}{Plot} class) \htmlref{astText}{astText} functions,
+      by determining if any escape sequences contained within the strings
+      should be used to control the appearance of the text, or should
+      be printed literally. Note, the \htmlref{Plot3D}{Plot3D} class only interprets escape
+      sequences within the
+      astGrid function.
+
+      If the Escape value of a Plot is one (the default), then any
+      escape sequences in text strings produce the effects described
+      below when printed. Otherwise, they are printed literally.
+
+      See also the \htmlref{astEscapes}{astEscapes} function.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Escape Sequences
+   }{
+      Escape sequences are introduced into the text string by a percent
+      \texttt{"} \%\texttt{"}  character. Any unrecognised, illegal or incomplete escape sequences
+      are printed literally. The following escape sequences are
+      currently recognised (\texttt{"} ...\texttt{"}  represents a string of one or more
+      decimal digits):
+
+        \%\%      - Print a literal \texttt{"} \%\texttt{"}  character.
+
+        \%$\wedge$...$+$  - Draw subsequent characters as super-scripts. The digits
+                  \texttt{"} ...\texttt{"}  give the distance from the base-line of \texttt{"} normal\texttt{"}
+                  text to the base-line of the super-script text, scaled
+                  so that a value of \texttt{"} 100\texttt{"}  corresponds to the height of
+                  \texttt{"} normal\texttt{"}  text.
+        \%$\wedge$$+$     - Draw subsequent characters with the normal base-line.
+
+        \%v...$+$  - Draw subsequent characters as sub-scripts. The digits
+                  \texttt{"} ...\texttt{"}  give the distance from the base-line of \texttt{"} normal\texttt{"}
+                  text to the base-line of the sub-script text, scaled
+                  so that a value of \texttt{"} 100\texttt{"}  corresponds to the height of
+                  \texttt{"} normal\texttt{"}  text.
+
+        \%v$+$     - Draw subsequent characters with the normal base-line
+                  (equivalent to \%$\wedge$$+$).
+
+        \%$>$...$+$  - Leave a gap before drawing subsequent characters.
+                  The digits \texttt{"} ...\texttt{"}  give the size of the gap, scaled
+                  so that a value of \texttt{"} 100\texttt{"}  corresponds to the height of
+                  \texttt{"} normal\texttt{"}  text.
+
+        \%$<$...$+$  - Move backwards before drawing subsequent characters.
+                  The digits \texttt{"} ...\texttt{"}  give the size of the movement, scaled
+                  so that a value of \texttt{"} 100\texttt{"}  corresponds to the height of
+                  \texttt{"} normal\texttt{"}  text.
+
+        \%s...$+$  - Change the Size attribute for subsequent characters. The
+                  digits \texttt{"} ...\texttt{"}  give the new Size as a fraction of the
+                  \texttt{"} normal\texttt{"}  Size, scaled so that a value of \texttt{"} 100\texttt{"}  corresponds
+                  to 1.0;
+
+        \%s$+$     - Reset the Size attribute to its \texttt{"} normal\texttt{"}  value.
+
+        \%w...$+$  - Change the Width attribute for subsequent characters. The
+                  digits \texttt{"} ...\texttt{"}  give the new width as a fraction of the
+                  \texttt{"} normal\texttt{"}  Width, scaled so that a value of \texttt{"} 100\texttt{"}  corresponds
+                  to 1.0;
+
+        \%w$+$     - Reset the Size attribute to its \texttt{"} normal\texttt{"}  value.
+
+        \%f...$+$  - Change the Font attribute for subsequent characters. The
+                  digits \texttt{"} ...\texttt{"}  give the new Font value.
+
+        \%f$+$     - Reset the Font attribute to its \texttt{"} normal\texttt{"}  value.
+
+        \%c...$+$  - Change the Colour attribute for subsequent characters. The
+                  digits \texttt{"} ...\texttt{"}  give the new Colour value.
+
+        \%c$+$     - Reset the Colour attribute to its \texttt{"} normal\texttt{"}  value.
+
+        \%t...$+$  - Change the Style attribute for subsequent characters. The
+                  digits \texttt{"} ...\texttt{"}  give the new Style value.
+
+        \%t$+$     - Reset the Style attribute to its \texttt{"} normal\texttt{"}  value.
+
+        \%h$+$     - Remember the current horizontal position (see \texttt{"} \%g$+$\texttt{"} )
+
+        \%g$+$     - Go to the horizontal position of the previous \texttt{"} \%h$+$\texttt{"}  (if any).
+
+        \%-      - Push the current graphics attribute values onto the top of
+                  the stack (see \texttt{"} \%$+$\texttt{"} ).
+
+        \%$+$      - Pop attributes values of the top the stack (see \texttt{"} \%-\texttt{"} ). If
+                  the stack is empty, \texttt{"} normal\texttt{"}  attribute values are restored.
+   }
+}
+\sstroutine{
+   FillFactor
+}{
+   Fraction of the Region which is of interest
+}{
+   \sstdescription{
+      This attribute indicates the fraction of the \htmlref{Region}{Region} which is of
+      interest. AST does not use this attribute internally for any purpose.
+      Typically, it could be used to indicate the fraction of the Region for
+      which data is available.
+
+      The supplied value must be in the range 0.0 to 1.0, and the default
+      value is 1.0 (except as noted below).
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpRegion}{CmpRegion}
+      }{
+         The default FillFactor for a CmpRegion is the FillFactor of its
+         first component Region.
+      }
+      \sstsubsection{
+         \htmlref{Prism}{Prism}
+      }{
+         The default FillFactor for a Prism is the product of the
+         FillFactors of its two component Regions.
+      }
+      \sstsubsection{
+         \htmlref{Stc}{Stc}
+      }{
+         The default FillFactor for an Stc is the FillFactor of its
+         encapsulated Region.
+      }
+   }
+}
+\sstroutine{
+   FitsAxisOrder
+}{
+   Frame title
+}{
+   \sstdescription{
+      This attribute specifies the order for the WCS axes in any new
+      FITS-WCS headers created using the
+      \htmlref{astWrite}{astWrite}
+      method.
+
+      The value of the FitsAxisOrder attribute can be either \texttt{"} $<$auto$>$\texttt{"}
+      (the default value), \texttt{"} $<$copy$>$\texttt{"}  or a space-separated list of axis
+      symbols:
+
+      \texttt{"} $<$auto$>$\texttt{"} : causes the WCS axis order to be chosen automatically so that
+      the i\texttt{'} th WCS axis in the new FITS header is the WCS axis which is
+      more nearly parallel to the i\texttt{'} th pixel axis.
+
+      \texttt{"} $<$copy$>$\texttt{"} : causes the WCS axis order to be set so that the i\texttt{'} th WCS
+      axis in the new FITS header is the i\texttt{'} th WCS axis in the current
+      \htmlref{Frame}{Frame} of the \htmlref{FrameSet}{FrameSet} being written out to the header.
+
+      \texttt{"} Sym1 Sym2...\texttt{"} : the space-separated list is seached in turn for
+      the Symbol attribute of each axis in the current Frame of the
+      FrameSet. The order in which these Symbols occur within the
+      space-separated list defines the order of the WCS axes in the
+      new FITS header. An error is reported if Symbol for a current
+      Frame axis is not present in the supplied list. However, no error
+      is reported if the list contains extra words that do not correspond
+      to the Symbol of any current Frame axis.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   FitsDigits
+}{
+   Digits of precision for floating point FITS values
+}{
+   \sstdescription{
+      This attribute gives the number of significant decimal digits to
+      use when formatting floating point values for inclusion in the
+      FITS header cards within a \htmlref{FitsChan}{FitsChan}.
+
+      By default, a positive value is used which results in no loss of
+      information, assuming that the value\texttt{'} s precision is double.
+      Usually, this causes no problems.
+
+      However, to adhere strictly to the recommendations of the FITS
+      standard, the width of the formatted value (including sign,
+      decimal point and exponent) ought not to be more than 20
+      characters. If you are concerned about this, you should set
+      FitsDigits to a negative value, such as -15. In this case, the
+      absolute value ($+$15) indicates the maximum number of significant
+      digits to use, but the actual number used may be fewer than this
+      to ensure that the FITS recommendations are satisfied. When
+      using this approach, the resulting number of significant digits
+      may depend on the value being formatted and on the presence of
+      any sign, decimal point or exponent.
+
+      The value of this attribute is effective when FITS header cards
+      are output, either using
+      \htmlref{astFindFits}{astFindFits} or by the action of the FitsChan\texttt{'} s sink function
+      when it is finally deleted.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Font(element)
+}{
+   Character font for a Plot element
+}{
+   \sstdescription{
+      This attribute determines the character font index used when
+      drawing each element of graphical output produced by a \htmlref{Plot}{Plot}. It
+      takes a separate value for each graphical element so that, for
+      instance, the setting \texttt{"} Font(title)=2\texttt{"}  causes the Plot title to
+      be drawn using font number 2.
+
+      The range of integer font indices available and the appearance
+      of the resulting text is determined by the underlying graphics
+      system.  The default behaviour is for all graphical elements to
+      be drawn using the default font supplied by this graphics
+      system.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of the graphical elements available, see the
+         description of the Plot class.
+
+         \sstitem
+         If no graphical element is specified, (e.g. \texttt{"} Font\texttt{"}  instead
+         of \texttt{"} Font(title)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will
+         affect the attribute value of all graphical elements, while a
+         \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}  operation will use just the Font(TextLab)
+         value.
+      }
+   }
+}
+\sstroutine{
+   Format(axis)
+}{
+   Format specification for axis values
+}{
+   \sstdescription{
+      This attribute specifies the format to be used when displaying
+      coordinate values associated with a particular \htmlref{Frame}{Frame} axis
+      (i.e. to convert values from binary to character form). It is
+      interpreted by the \htmlref{astFormat}{astFormat} function and determines the
+      formatting which it applies.
+
+      If no Format value is set for a Frame axis, a default value is
+      supplied instead. This is based on the value of the Digits, or
+      Digits(axis), attribute and is chosen so that it displays the
+      requested number of digits of precision.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The Frame class interprets this attribute as a format
+         specification string to be passed to the C \texttt{"} printf\texttt{"}  function
+         (e.g. \texttt{"} \%1.7G\texttt{"} ) in order to format a single coordinate value
+         (supplied as a double precision number).
+
+         When supplying a value for this attribute, beware that the
+         \texttt{"} \%\texttt{"}  character may be interpreted directly as a format
+         specification by some printf-like functions (such as
+         \htmlref{astSet}{astSet}). You may need to double it (i.e. use \texttt{"} \%\%\texttt{"} ) to avoid
+         this.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the syntax and default value of
+         the Format string to allow the formatting of sexagesimal
+         values as appropriate for the particular celestial coordinate
+         system being represented. The syntax of SkyFrame Format
+         strings is described (below) in the \texttt{"} SkyFrame Formats\texttt{"}
+         section.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Format attribute of a FrameSet axis is the same as that
+         of its current Frame (as specified by the \htmlref{Current}{Current}
+         attribute). Note that the syntax of the Format string is also
+         determined by the current Frame.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class extends the syntax of the Format string to
+         allow the formatting of TimeFrame axis values as Gregorian calendar
+         dates and times. The syntax of TimeFrame Format strings is described
+         (below) in the \texttt{"} TimeFrame Formats\texttt{"}  section.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+   \sstdiytopic{
+      SkyFrame Formats
+   }{
+      The Format string supplied for a SkyFrame should contain zero or
+      more of the following characters. These may occur in any order,
+      but the following is recommended for clarity:
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} $+$\texttt{"} : Indicates that a plus sign should be prefixed to positive
+         values. By default, no plus sign is used.
+
+         \sstitem
+         \texttt{"} z\texttt{"} : Indicates that leading zeros should be prefixed to the
+         value so that the first field is of constant width, as would be
+         required in a fixed-width table (leading zeros are always
+         prefixed to any fields that follow). By default, no leading
+         zeros are added.
+
+         \sstitem
+         \texttt{"} i\texttt{"} : Use the standard ISO field separator (a colon) between
+         fields. This is the default behaviour.
+
+         \sstitem
+         \texttt{"} b\texttt{"} : Use a blank to separate fields.
+
+         \sstitem
+         \texttt{"} l\texttt{"} : Use a letter (\texttt{"} h\texttt{"} /\texttt{"} d\texttt{"} , \texttt{"} m\texttt{"}  or \texttt{"} s\texttt{"}  as appropriate) to
+         separate fields.
+
+         \sstitem
+         \texttt{"} g\texttt{"} : Use a letter and symbols to separate fields (\texttt{"} h\texttt{"} /\texttt{"} d\texttt{"} , \texttt{"} m\texttt{"}  or \texttt{"} s\texttt{"} ,
+         etc, as appropriate), but include escape sequences in the formatted
+         value so that the \htmlref{Plot}{Plot} class will draw the separators as small
+         super-scripts.
+         The default escape sequences are optimised for the pgplot graphics
+         package, but new escape sequences may be specified using function
+         astSetSkyDelim.
+
+         \sstitem
+         \texttt{"} d\texttt{"} : Include a degrees field. Expressing the angle purely in
+         degrees is also the default if none of \texttt{"} h\texttt{"} , \texttt{"} m\texttt{"} , \texttt{"} s\texttt{"}  or \texttt{"} t\texttt{"}  are
+         given.
+
+         \sstitem
+         \texttt{"} h\texttt{"} : Express the angle as a time and include an hours field
+         (where 24 hours correspond to 360 degrees). Expressing the angle
+         purely in hours is also the default if \texttt{"} t\texttt{"}  is given without
+         either \texttt{"} m\texttt{"}  or \texttt{"} s\texttt{"} .
+
+         \sstitem
+         \texttt{"} m\texttt{"} : Include a minutes field. By default this is not included.
+
+         \sstitem
+         \texttt{"} s\texttt{"} : Include a seconds field. By default this is not included.
+         This request is ignored if \texttt{"} d\texttt{"}  or \texttt{"} h\texttt{"}  is given, unless a minutes
+         field is also included.
+
+         \sstitem
+         \texttt{"} t\texttt{"} : Express the angle as a time (where 24 hours correspond to
+         360 degrees). This option is ignored if either \texttt{"} d\texttt{"}  or \texttt{"} h\texttt{"}  is
+         given and is intended for use where the value is to be expressed
+         purely in minutes and/or seconds of time (with no hours
+         field). If \texttt{"} t\texttt{"}  is given without \texttt{"} d\texttt{"} , \texttt{"} h\texttt{"} , \texttt{"} m\texttt{"}  or \texttt{"} s\texttt{"}  being
+         present, then it is equivalent to \texttt{"} h\texttt{"} .
+
+         \sstitem
+         \texttt{"} .\texttt{"} : Indicates that decimal places are to be given for the
+         final field in the formatted string (whichever field this
+         is). The \texttt{"} .\texttt{"}  should be followed immediately by an unsigned
+         integer which gives the number of decimal places required, or by an
+         asterisk. If an asterisk is supplied, a default number of decimal
+         places is used which is based on the value of the Digits
+         attribute.
+
+      }
+      All of the above format specifiers are case-insensitive. If
+      several characters make conflicting requests (e.g. if both \texttt{"} i\texttt{"}
+      and \texttt{"} b\texttt{"}  appear), then the character occurring last takes
+      precedence, except that \texttt{"} d\texttt{"}  and \texttt{"} h\texttt{"}  always override \texttt{"} t\texttt{"} .
+
+      If the format string starts with a percentage sign (\%), then the
+      whole format string is assumed to conform to the syntax defined by
+      the Frame class, and the axis values is formated as a decimal
+      radians value.
+   }
+   \sstdiytopic{
+      TimeFrame Formats
+   }{
+      The Format string supplied for a TimeFrame should either use the
+      syntax defined by the base Frame class (i.e. a C \texttt{"} printf\texttt{"}  format
+      string), or the extended \texttt{"} iso\texttt{"}  syntax described below (the default
+      value is inherited from the Frame class):
+
+      \sstitemlist{
+
+         \sstitem
+         C \texttt{"} printf\texttt{"}  syntax: If the Format string is a C \texttt{"} printf\texttt{"}  format
+         description such as \texttt{"} \%1.7G\texttt{"} , the TimeFrame axis value will be
+         formatted without change as a floating point value using this format.
+         The formatted string will thus represent an offset from the zero point
+         specified by the TimeFrame\texttt{'} s \htmlref{TimeOrigin}{TimeOrigin} attribute, measured in
+         units given by the TimeFrame\texttt{'} s Unit attribute.
+
+         \sstitem
+         \texttt{"} iso\texttt{"}  syntax: This is used to format a TimeFrame axis value as a
+         Gregorian date followed by an optional time of day. If the Format
+         value commences with the string \texttt{"} iso\texttt{"}  then the TimeFrame axis value
+         will be converted to an absolute MJD, including the addition of the
+         current TimeOrigin value, and then formatted as a Gregorian date
+         using the format \texttt{"} yyyy-mm-dd\texttt{"} . Optionally, the Format value may
+         include an integer precision following the \texttt{"} iso\texttt{"}  specification (e.g.
+         \texttt{"} iso.2\texttt{"} ), in which case the time of day will be appended to the
+         formatted date (if no time of day is included, the date field is
+         rounded to the nearest day). The integer value in the Format string
+         indicates the number of decimal places to use in the seconds field. For
+         instance, a Format value of \texttt{"} iso.0\texttt{"}  produces a time of day of the form
+         \texttt{"} hh:mm:ss\texttt{"} , and a Format value of \texttt{"} iso.2\texttt{"}  produces a time of day of the
+         form \texttt{"} hh:mm:ss.ss\texttt{"} . The date and time fields will be separated by a
+         space unless \texttt{'} T\texttt{'}  is appended to the end of string, in which case
+         the letter T (upper case) will be used as the separator. The value of
+         the Digits attribute is ignored when using this \texttt{"} iso\texttt{"}  format.
+      }
+   }
+}
+\sstroutine{
+   Full
+}{
+   Set level of output detail
+}{
+   \sstdescription{
+      This attribute is a three-state flag and takes values of -1, 0
+      or $+$1.  It controls the amount of information included in the
+      output generated by a \htmlref{Channel}{Channel}.
+
+      If Full is zero, then a modest amount of
+      non-essential but useful information will be included in the
+      output. If Full is negative, all non-essential information will
+      be suppressed to minimise the amount of output, while if it is
+      positive, the output will include the maximum amount of detailed
+      information about the \htmlref{Object}{Object} being written.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         The default value is zero for a normal Channel.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         The default value is zero for a FitsChan.
+      }
+      \sstsubsection{
+         \htmlref{XmlChan}{XmlChan}
+      }{
+         The default value is -1 for an XmlChan.
+      }
+      \sstsubsection{
+         \htmlref{StcsChan}{StcsChan}
+      }{
+         The default value is zero for an StcsChan. Set a positive value
+         to cause default values to be included in STC-S descriptions.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         All positive values supplied for this attribute are converted
+         to $+$1 and all negative values are converted to -1.
+      }
+   }
+}
+\sstroutine{
+   Gap(axis)
+}{
+   Interval between linearly spaced major axis values of a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      the linear interval between the \texttt{"} major\texttt{"}  axis values of a \htmlref{Plot}{Plot}, at
+      which (for example) major tick marks are drawn. It takes a separate
+      value for each physical axis of the Plot so that, for instance,
+      the setting \texttt{"} Gap(2)=3.0\texttt{"}  specifies the difference between adjacent major
+      values along the second axis. The Gap attribute is only used when
+      the LogTicks attribute indicates that the spacing between major axis
+      values is to be linear. If major axis values are logarithmically spaced
+      then the gap is specified using attribute LogGap.
+
+      The Gap value supplied will usually be rounded to the nearest
+      \texttt{"} nice\texttt{"}  value, suitable (e.g.) for generating axis labels, before
+      use. To avoid this \texttt{"} nicing\texttt{"}  you should set an explicit format
+      for the axis using the \htmlref{Format(axis)}{Format(axis)} or \htmlref{Digits/Digits(axis)}{Digits/Digits(axis)}
+      attribute. The default behaviour is for the Plot to generate its
+      own Gap value when required, based on the range of axis values
+      to be represented.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Gap value should use the same units as are used internally
+         for storing coordinate values on the corresponding axis. For
+         example, with a celestial coordinate system, the Gap value
+         should be in radians, not hours or degrees.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} Gap\texttt{"}  instead of \texttt{"} Gap(2)\texttt{"} ),
+         then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the attribute
+         value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}  operation
+         will use just the Gap(1) value.
+      }
+   }
+}
+\sstroutine{
+   Grf
+}{
+   Use Grf functions registered through astGrfSet?
+}{
+   \sstdescription{
+      This attribute selects the functions which are used to draw graphics by
+      the \htmlref{Plot}{Plot} class. If it is zero, then the functions in the graphics
+      interface selected at link-time are used (see the \htmlref{ast\_link}{ast\_link} script).
+      Otherwise, functions registered using \htmlref{astGrfSet}{astGrfSet} are used. In this
+      case, if a function is needed which has not been registered,
+      then the function in the graphics interface selected at link-time is
+      used.
+
+      The default is to use the graphics interface
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Plot3D}{Plot3D}
+      }{
+         The Plot3D class ignores this attributes, assuming a value of
+         zero.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The value of this attribute is not saved when the Plot is written
+         out through a \htmlref{Channel}{Channel} to an external data store. On re-loading such
+         a Plot using \htmlref{astRead}{astRead}, the attribute will be cleared, resulting in the
+         graphics interface selected at link-time being used.
+      }
+   }
+}
+\sstroutine{
+   Grid
+}{
+   Draw grid lines for a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether grid lines (a grid of curves marking the \texttt{"} major\texttt{"}  values
+      on each axis) are drawn across the plotting area.
+
+      If the Grid value of a \htmlref{Plot}{Plot} is non-zero, then grid lines will be
+      drawn. Otherwise, short tick marks on the axes are used to mark
+      the major axis values.  The default behaviour is to use tick
+      marks if the entire plotting area is filled by valid physical
+      coordinates, but to draw grid lines otherwise.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The spacing between major axis values, which determines the
+         spacing of grid lines, may be set using the \htmlref{Gap(axis)}{Gap(axis)} attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismAlpha
+}{
+   The angle of incidence of the incoming light on the grating surface
+}{
+   \sstdescription{
+      This attribute holds the angle between the incoming light and the
+      normal to the grating surface, in radians. The default value is 0.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismEps
+}{
+   The angle between the normal and the dispersion plane
+}{
+   \sstdescription{
+      This attribute holds the angle (in radians) between the normal to
+      the grating or exit prism face, and the dispersion plane. The
+      dispersion plane is the plane spanned by the incoming and outgoing
+      ray. The default value is 0.0.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismG
+}{
+   The grating ruling density
+}{
+   \sstdescription{
+      This attribute holds the number of grating rulings per unit length.
+      The unit of length used should be consistent with the units used
+      for attributes \htmlref{GrismWaveR}{GrismWaveR} and \htmlref{GrismNRP}{GrismNRP}. The default value is 0.0.
+      (the appropriate value for a pure prism disperser with no grating).
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismM
+}{
+   The interference order
+}{
+   \sstdescription{
+      This attribute holds the interference order being considered.
+      The default value is 0.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismNR
+}{
+   The refractive index at the reference wavelength
+}{
+   \sstdescription{
+      This attribute holds refractive index of the grism material at the
+      reference wavelength (given by attribute \htmlref{GrismWaveR}{GrismWaveR}). The default
+      value is 1.0.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismNRP
+}{
+   The rate of change of refractive index with wavelength
+}{
+   \sstdescription{
+      This attribute holds the rate of change of the refractive index of the
+      grism material with respect to wavelength at the reference wavelength
+      (given by attribute \htmlref{GrismWaveR}{GrismWaveR}). The default value is 0.0 (the
+      appropriate value for a pure grating disperser with no prism). The
+      units of this attribute should be consistent with those of attributes
+      GrismWaveR and \htmlref{GrismG}{GrismG}.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismTheta
+}{
+   Angle between normal to detector plane and reference ray
+}{
+   \sstdescription{
+      This attribute gives the angle of incidence of light of the
+      reference wavelength (given by attribute \htmlref{GrismWaveR}{GrismWaveR}) onto the
+      detector. Specifically, it holds the angle (in radians) between
+      the normal to the detector plane and an incident ray at the reference
+      wavelength. The default value is 0.0.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   GrismWaveR
+}{
+   The reference wavelength
+}{
+   \sstdescription{
+      This attribute holds reference wavelength. The default value is
+      5000 (Angstrom). The units of this attribute should be consistent with
+      those of attributes \htmlref{GrismNRP}{GrismNRP} and \htmlref{GrismG}{GrismG}.
+
+      Note, the value of this attribute may changed only if the \htmlref{GrismMap}{GrismMap}
+      has no more than one reference. That is, an error is reported if the
+      GrismMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         GrismMap
+      }{
+         All GrismMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ID
+}{
+   Object identification string
+}{
+   \sstdescription{
+      This attribute contains a string which may be used to identify
+      the \htmlref{Object}{Object} to which it is attached. There is no restriction on
+      the contents of this string, which is not used internally by the
+      AST library, and is simply returned without change when
+      required. The default value is an empty string.
+
+      An identification string can be valuable when, for example,
+      several Objects have been stored in a file (using \htmlref{astWrite}{astWrite}) and
+      are later retrieved (using \htmlref{astRead}{astRead}). Consistent use of the ID
+      attribute allows the retrieved Objects to be identified without
+      depending simply on the order in which they were stored.
+
+      This attribute may also be useful during debugging, to
+      distinguish similar Objects when using \htmlref{astShow}{astShow} to display them.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Unlike most other attributes, the value of the ID attribute is
+         not transferred when an Object is copied. Instead, its value is
+         undefined (and therefore defaults to an empty string) in any
+         copy. However, it is retained in any external representation of
+         an Object produced by the astWrite function.
+      }
+   }
+}
+\sstroutine{
+   IF
+}{
+   The intermediate frequency in a dual sideband spectrum
+}{
+   \sstdescription{
+      This attribute specifies the (topocentric) intermediate frequency in
+      a dual sideband spectrum. Its sole use is to determine the local
+      oscillator (LO) frequency (the frequency which marks the boundary
+      between the lower and upper sidebands). The LO frequency is
+      equal to the sum of the centre frequency and the intermediate
+      frequency. Here, the \texttt{"} centre frequency\texttt{"}  is the topocentric
+      frequency in Hz corresponding to the current value of the \htmlref{DSBCentre}{DSBCentre}
+      attribute. The value of the IF attribute may be positive or
+      negative: a positive value results in the LO frequency being above
+      the central frequency, whilst a negative IF value results in the LO
+      frequency being below the central frequency. The sign of the IF
+      attribute value determines the default value for the \htmlref{SideBand}{SideBand}
+      attribute.
+
+      When setting a new value for this attribute, the units in which the
+      frequency value is supplied may be indicated by appending a suitable
+      string to the end of the formatted value. If the units are not
+      specified, then the supplied value is assumed to be in units of GHz.
+      For instance, the following strings all result in an IF of 4 GHz being
+      used: \texttt{"} 4.0\texttt{"} , \texttt{"} 4.0 GHz\texttt{"} , \texttt{"} 4.0E9 Hz\texttt{"} , etc.
+
+      When getting the value of this attribute, the returned value is
+      always in units of GHz. The default value for this attribute is 4 GHz.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{DSBSpecFrame}{DSBSpecFrame}
+      }{
+         All DSBSpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Ident
+}{
+   Permanent Object identification string
+}{
+   \sstdescription{
+      This attribute is like the \htmlref{ID}{ID} attribute, in that it contains a
+      string which may be used to identify the \htmlref{Object}{Object} to which it is
+      attached. The only difference between ID and Ident is that Ident
+      is transferred when an Object is copied, but ID is not.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ImagFreq
+}{
+   The image sideband equivalent of the rest frequency
+}{
+   \sstdescription{
+      This is a read-only attribute giving the frequency which
+      corresponds to the rest frequency but is in the opposite sideband.
+
+      The value is calculated by first transforming the rest frequency
+      (given by the \htmlref{RestFreq}{RestFreq} attribute) from the standard of rest of the
+      source (given by the \htmlref{SourceVel}{SourceVel} and \htmlref{SourceVRF}{SourceVRF} attributes) to the
+      standard of rest of the observer (i.e. the topocentric standard of
+      rest). The resulting topocentric frequency is assumed to be in the
+      same sideband as the value given for the \htmlref{DSBCentre}{DSBCentre} attribute (the
+      \texttt{"} observed\texttt{"}  sideband), and is transformed to the other sideband (the
+      \texttt{"} image\texttt{"}  sideband). The new frequency is converted back to the standard
+      of rest of the source, and the resulting value is returned as the
+      attribute value, in units of GHz.
+   }
+   \sstattributetype{
+      Floating point, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{DSBSpecFrame}{DSBSpecFrame}
+      }{
+         All DSBSpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Indent
+}{
+   Specifies the indentation to use in text produced by a Channel
+}{
+   \sstdescription{
+      This attribute controls the indentation within the output text produced by
+      the \htmlref{astWrite}{astWrite} function.
+      It gives the increase in the indentation for each level in the object
+      heirarchy. If it is set to zero, no indentation will be used. [3]
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Channel}{Channel}
+      }{
+         The default value is zero for a basic Channel.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         The FitsChan class ignores this attribute.
+      }
+      \sstsubsection{
+         \htmlref{StcsChan}{StcsChan}
+      }{
+         The default value for an StcsChan is zero, which causes the entire
+         STC-S description is written out by a single invocation of the sink
+         function. The text supplied to the sink function will not contain
+         any linefeed characters, and each pair of adjacent words will be
+         separated by a single space. The text may thus be arbitrarily large
+         and the \htmlref{StcsLength}{StcsLength} attribute is ignored.
+
+         If Indent is non-zero, then the text is written out via multiple
+         calls to the sink function, each call corresponding to a single
+         \texttt{"} line\texttt{"}  of text (although no line feed characters will be inserted
+         by AST). The complete STC-S description is broken into lines so that:
+
+         \sstitemlist{
+
+            \sstitem
+            the line length specified by attribute StcsLength is not exceeded
+
+            \sstitem
+            each sub-phrase (time, space, etc.) starts on a new line
+
+            \sstitem
+            each argument in a compound spatial region starts on a new line
+
+         }
+         If this causes a sub-phrase to extend to two or more lines, then the
+         second and subsequent lines will be indented by three spaces compared
+         to the first line. In addition, lines within a compound spatial region
+         will have extra indentation to highlight the nesting produced by the
+         parentheses. Each new level of nesting will be indented by a further
+         three spaces.
+      }
+      \sstsubsection{
+         \htmlref{XmlChan}{XmlChan}
+      }{
+         The default value for an XmlChan is zero, which results in no
+         linefeeds or indentation strings being added to output text.
+         If any non-zero value is assigned to Indent, then extra linefeed and
+         space characters will be inserted as necessary to ensure that each
+         XML tag starts on a new line, and each tag will be indented by
+         a further 3 spaces to show its depth in the containment hierarchy.
+      }
+   }
+}
+\sstroutine{
+   InternalUnit(axis)
+}{
+   Physical units for unformated axis values
+}{
+   \sstdescription{
+      This read-only attribute contains a textual representation of the
+      physical units used to represent unformatted (i.e. floating point)
+      values on a particular axis of a \htmlref{Frame}{Frame}, typically handled internally
+      within application code. In most cases, the value of the InternalUnit
+      attribute will be the same as Unit attribute (i.e. formatted and
+      unformatted axis values will normally use the same system of units).
+      The main exception to this is the \htmlref{SkyFrame}{SkyFrame} class, which represents
+      unformatted axis values in radians, regardless of the current
+      setting of the Unit attribute.
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   IntraFlag
+}{
+   IntraMap identification string
+}{
+   \sstdescription{
+      This attribute allows an \htmlref{IntraMap}{IntraMap} to be flagged so that it is
+      distinguishable from other IntraMaps. The transformation function
+      associated with the IntraMap may then enquire the value of this
+      attribute and adapt the transformation it provides according to the
+      particular IntraMap involved.
+
+      Although this is a string attribute, it may often be useful to store
+      numerical values here, encoded as a character string, and to use these
+      as data within the transformation function. Note, however, that this
+      mechanism is not suitable for transferring large amounts of data (more
+      than about 1000 characters) to an IntraMap. For that purpose, global
+      variables are recommended, although the IntraFlag value can be used to
+      supplement this approach. The default IntraFlag value is an empty
+      string.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         IntraMap
+      }{
+         All IntraMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A pair of IntraMaps whose transformations may potentially cancel
+         cannot be simplified to produce a \htmlref{UnitMap}{UnitMap} (e.g. using \htmlref{astSimplify}{astSimplify})
+         unless they have the same IntraFlag values. The test for equality is
+         case-sensitive.
+      }
+   }
+}
+\sstroutine{
+   Invert
+}{
+   Mapping inversion flag
+}{
+   \sstdescription{
+      This attribute controls which one of a \htmlref{Mapping}{Mapping}\texttt{'} s two possible
+      coordinate transformations is considered the \texttt{"} forward\texttt{"}
+      transformation (the other being the \texttt{"} inverse\texttt{"}
+      transformation). If the attribute value is zero (the default),
+      the Mapping\texttt{'} s behaviour will be the same as when it was first
+      created. However, if it is non-zero, its two transformations
+      will be inter-changed, so that the Mapping displays the inverse
+      of its original behaviour.
+
+      Inverting the boolean sense of the Invert attribute will cause
+      the values of a Mapping\texttt{'} s \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes to be
+      interchanged. The values of its \htmlref{TranForward}{TranForward} and \htmlref{TranInverse}{TranInverse}
+      attributes will also be interchanged. This operation may be
+      performed with the \htmlref{astInvert}{astInvert} function.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{UnitMap}{UnitMap}
+      }{
+         The value of the Invert attribute has no effect on the
+         behaviour of a UnitMap.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         Inverting the boolean sense of the Invert attribute for a
+         FrameSet will cause its base and current Frames (and its \htmlref{Base}{Base}
+         and \htmlref{Current}{Current} attributes) to be interchanged. This, in turn,
+         may affect other properties and attributes of the FrameSet
+         (such as Nin, Nout, \htmlref{Naxes}{Naxes}, TranForward, TranInverse,
+         etc.). The Invert attribute of a FrameSet is not itself
+         affected by selecting a new base or current \htmlref{Frame}{Frame}.
+      }
+   }
+}
+\sstroutine{
+   Invisible
+}{
+   Draw graphics using invisible ink?
+}{
+   \sstdescription{
+      This attribute controls the appearance of all graphics produced by
+      \htmlref{Plot}{Plot} methods by determining whether graphics should be visible or
+      invisible.
+
+      If the Invisible value of a Plot is non-zero, then all the Plot
+      methods which normally generate graphical output do not do so (you
+      can think of them drawing with \texttt{"} invisible ink\texttt{"} ). Such methods do,
+      however, continue to do all the calculations which would be needed to
+      produce the graphics. In particular, the bounding box enclosing the
+      graphics is still calculated and can be retrieved as normal using
+      \htmlref{astBoundingBox}{astBoundingBox}. The default value is zero, resulting in all methods
+      drawing graphics as normal, using visible ink.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+}
+\sstroutine{
+   IsLatAxis(axis)
+}{
+   Is the specified celestial axis a latitude axis?
+}{
+   \sstdescription{
+      This is a read-only boolean attribute that indicates the nature of
+      the specified axis. The attribute has a non-zero value if the
+      specified axis is a celestial latitude axis (Declination, Galactic
+      latitude, etc), and is zero otherwise.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the SkyFrame axis to be tested.
+      }
+   }
+}
+\sstroutine{
+   IsLinear
+}{
+   Is the Mapping linear?
+}{
+   \sstdescription{
+      This attribute indicates whether a \htmlref{Mapping}{Mapping} is an instance of a
+      class that always represents a linear transformation. Note, some
+      Mapping classes can represent linear or non-linear transformations
+      (the \htmlref{MathMap}{MathMap} class for instance). Such classes have a zero value for
+      the IsLinear attribute. Specific instances of such classes can be
+      tested for linearity using the
+      \htmlref{astLinearApprox}{astLinearApprox} function.
+      AST\_LINEARAPPROX routine.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         The IsLinear value for a CmpMap is determined by the classes
+         of the encapsulated Mappings. For instance, a CmpMap that combines
+         a \htmlref{ZoomMap}{ZoomMap} and a \htmlref{ShiftMap}{ShiftMap} will have a non-zero value for its IsLinear
+         attribute, but a CmpMap that contains a MathMap will have a
+         value of zero for its IsLinear attribute.
+      }
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         The IsLinear value for a Frame is 1 (since a Frame is equivalent
+         to a \htmlref{UnitMap}{UnitMap}).
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The IsLinear value for a FrameSet is obtained from the Mapping
+         from the base Frame to the current Frame.
+      }
+   }
+}
+\sstroutine{
+   IsLonAxis(axis)
+}{
+   Is the specified celestial axis a longitude axis?
+}{
+   \sstdescription{
+      This is a read-only boolean attribute that indicates the nature of
+      the specified axis. The attribute has a non-zero value if the
+      specified axis is a celestial longitude axis (Right Ascension, Galactic
+      longitude, etc), and is zero otherwise.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the SkyFrame axis to be tested.
+      }
+   }
+}
+\sstroutine{
+   IsSimple
+}{
+   Has the Mapping been simplified?
+}{
+   \sstdescription{
+      This attribute indicates whether a \htmlref{Mapping}{Mapping} has been simplified
+      by the
+      \htmlref{astSimplify}{astSimplify}
+      method. If the IsSimple value is non-zero, then the Mapping has
+      been simplified and so there is nothing to be gained by simplifying
+      it again. Indeed, the
+      astSimplify
+      method will immediately return the Mapping unchanged if the IsSimple
+      attribute indicates that the Mapping has already been simplified.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         All classes of Frame return zero for the IsSimple attribute.
+         This is because changes can be made to a Frame which affect the
+         Mapping represented by the Frame, and so there can be no
+         guarantee that the Mapping may not need re-simplifying. Most
+         non-Frame Mappings, on the other hand, are immutable and so when
+         they are simplified it is certain that they weill remain in a
+         simple state.
+      }
+   }
+}
+\sstroutine{
+   IterInverse
+}{
+   Provide an iterative inverse transformation?
+}{
+   \sstdescription{
+      This attribute indicates whether the inverse transformation of
+      the \htmlref{PolyMap}{PolyMap} should be implemented via an iterative Newton-Raphson
+      approximation that uses the forward transformation to transform
+      candidate input positions until an output position is found which
+      is close to the required output position. By default, an iterative
+      inverse is provided if, and only if, no inverse polynomial was supplied
+      when the PolyMap was constructed.
+
+      The \htmlref{NiterInverse}{NiterInverse} and \htmlref{TolInverse}{TolInverse} attributes provide parameters that
+      control the behaviour of the inverse approximation method.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         PolyMap
+      }{
+         All PolyMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An iterative inverse can only be used if the PolyMap has equal
+         numbers of inputs and outputs, as given by the \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout}
+         attributes. An error will be reported if IterInverse is set non-zero
+         for a PolyMap that does not meet this requirement.
+      }
+   }
+}
+\sstroutine{
+   Iwc
+}{
+   Include a Frame representing FITS-WCS intermediate world coordinates?
+}{
+   \sstdescription{
+      This attribute is a boolean value which is used when a \htmlref{FrameSet}{FrameSet} is
+      read from a \htmlref{FitsChan}{FitsChan} with a foreign FITS encoding (e.g. FITS-WCS) using
+      \htmlref{astRead}{astRead}.
+      If it has a non-zero value then the returned FrameSet will include
+      Frames representing \texttt{"} intermediate world coordinates\texttt{"}  (IWC). These
+      Frames will have \htmlref{Domain}{Domain} name \texttt{"} IWC\texttt{"}  for primary axis descriptions, and
+      \texttt{"} IWCa\texttt{"}  for secondary axis descriptions, where \texttt{"} a\texttt{"}  is replaced by
+      the single alternate axis description character, as used in the
+      FITS-WCS header. The default value for \texttt{"} Iwc\texttt{"}  is zero.
+
+      FITS-WCS paper 1 defines IWC as a Cartesian coordinate system with one
+      axis for each WCS axis, and is the coordinate system produced by the
+      rotation matrix (represented by FITS keyword PCi\_j, CDi\_j, etc).
+      For instance, for a 2-D FITS-WCS header describing projected
+      celestial longitude and latitude, the intermediate world
+      coordinates represent offsets in degrees from the reference point
+      within the plane of projection.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   KeyCase
+}{
+   Are keys case sensitive?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how keys are
+      used. If KeyCase is zero, then key strings supplied to any method
+      are automatically converted to upper case before being used. If
+      KeyCase is non-zero (the default), then supplied key strings are
+      used without modification.
+
+      The value of this attribute can only be changed if the \htmlref{KeyMap}{KeyMap} is
+      empty. Its value can be set conveniently when creating the KeyMap.
+      An error will be reported if an attempt is made to change the
+      attribute value when the KeyMap contains any entries.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         All KeyMaps have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         The Table class over-rides this attribute by forcing it to zero.
+         That is, keys within a Table are always case insensitive.
+      }
+   }
+}
+\sstroutine{
+   KeyError
+}{
+   Report an error when getting the value of a non-existant KeyMap entry?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how the
+      astMapGet...
+      functions behave if the requested key is not found in the \htmlref{KeyMap}{KeyMap}.
+      If KeyError is zero (the default), then these functions will return
+      zero
+      but no error will be reported. If KeyError is non-zero, then the
+      same values are returned but an error is also reported.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         All KeyMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When setting a new value for KeyError, the supplied value is
+         propagated to any KeyMaps contained within the supplied KeyMap.
+
+         \sstitem
+         When clearing the KeyError attribute, the attribute is also
+         cleared in any KeyMaps contained within the supplied KeyMap.
+      }
+   }
+}
+\sstroutine{
+   LTOffset
+}{
+   The offset from UTC to Local Time, in hours
+}{
+   \sstdescription{
+      This specifies the offset from UTC to Local Time, in hours (fractional
+      hours can be supplied). It is positive for time zones east of Greenwich.
+      AST uses the figure as given, without making any attempt to correct for
+      daylight saving. The default value is zero.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         All TimeFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Label(axis)
+}{
+   Axis label
+}{
+   \sstdescription{
+      This attribute specifies a label to be attached to each axis of
+      a \htmlref{Frame}{Frame} when it is represented (e.g.) in graphical output.
+
+      If a Label value has not been set for a Frame axis, then a
+      suitable default is supplied.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default supplied by the Frame class is the string \texttt{"} \htmlref{Axis}{Axis}
+         $<$n$>$\texttt{"} , where $<$n$>$ is 1, 2, etc. for each successive axis.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Label value
+         (e.g. to \texttt{"} Right ascension\texttt{"}  or \texttt{"} Galactic latitude\texttt{"} ) as
+         appropriate for the particular celestial coordinate system
+         being represented.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class re-defines the default Label value as
+         appropriate for the particular time system being represented.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Label attribute of a FrameSet axis is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Axis labels are intended purely for interpretation by human
+         readers and not by software.
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   LabelAt(axis)
+}{
+   Where to place numerical labels for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      where numerical axis labels and associated tick marks are
+      placed.  It takes a separate value for each physical axis of a
+      \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} LabelAt(2)=10.0\texttt{"}
+      specifies where the numerical labels and tick marks for the
+      second axis should be drawn.
+
+      For each axis, the LabelAt value gives the value on the other
+      axis at which numerical labels and tick marks should be placed
+      (remember that Plots suitable for use with astGrid may only
+      have two axes).  For example, in a celestial (RA,Dec) coordinate
+      system, LabelAt(1) gives a Dec value which defines a line (of
+      constant Dec) along which the numerical RA labels and their
+      associated tick marks will be drawn. Similarly, LabelAt(2) gives
+      the RA value at which the Dec labels and ticks will be drawn.
+
+      The default bahaviour is for the Plot to generate its own
+      position for numerical labels and tick marks.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The LabelAt value should use the same units as are used
+         internally for storing coordinate values on the appropriate
+         axis. For example, with a celestial coordinate system, the
+         LabelAt value should be in radians, not hours or degrees.
+
+         \sstitem
+         Normally, the LabelAt value also determines where the lines
+         representing coordinate axes will be drawn, so that the tick
+         marks will lie on these lines (but also see the DrawAxes
+         attribute).
+
+         \sstitem
+         In some circumstances, numerical labels and tick marks are
+         drawn around the edges of the plotting area (see the \htmlref{Labelling}{Labelling}
+         attribute).  In this case, the value of the LabelAt attribute is
+         ignored.
+      }
+   }
+}
+\sstroutine{
+   LabelUnits(axis)
+}{
+   Use axis unit descriptions in a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether the descriptive labels drawn for each axis of a \htmlref{Plot}{Plot}
+      should include a description of the units being used on the
+      axis.  It takes a separate value for each physical axis of a
+      Plot so that, for instance, the setting \texttt{"} LabelUnits(2)=1\texttt{"}
+      specifies that a unit description should be included in the
+      label for the second axis.
+
+      If the LabelUnits value of a Plot axis is non-zero, a unit
+      description will be included in the descriptive label for that
+      axis, otherwise it will be omitted.  The default behaviour is to
+      include a unit description unless the current \htmlref{Frame}{Frame} of the Plot
+      is a \htmlref{SkyFrame}{SkyFrame} representing equatorial, ecliptic, galactic or
+      supergalactic coordinates, in which case it is omitted.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The text used for the unit description is obtained from the
+         Plot\texttt{'} s \htmlref{Unit(axis)}{Unit(axis)} attribute.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LabelUnits\texttt{"}  instead of
+         \texttt{"} LabelUnits(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the LabelUnits(1) value.
+
+         \sstitem
+         If the current Frame of the Plot is not a SkyFrame, but includes
+         axes which were extracted from a SkyFrame, then the default behaviour
+         is to include a unit description only for those axes which were not
+         extracted from a SkyFrame.
+      }
+   }
+}
+\sstroutine{
+   LabelUp(axis)
+}{
+   Draw numerical Plot labels upright?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether the numerical labels for each axis of a \htmlref{Plot}{Plot} should be
+      drawn upright or not. It takes a separate value for each
+      physical axis of a Plot so that, for instance, the setting
+      \texttt{"} LabelUp(2)=1\texttt{"}  specifies that numerical labels for the second
+      axis should be drawn upright.
+
+      If the LabelUp value of a Plot axis is non-zero, it causes
+      numerical labels for that axis to be plotted upright (i.e. as
+      normal, horizontal text), otherwise labels are drawn parallel to
+      the axis to which they apply.
+
+      The default is to produce upright labels if the labels are placed
+      around the edge of the plot, and to produce labels that follow the
+      axes if the labels are placed within the interior of the plot (see
+      attribute \htmlref{Labelling}{Labelling}).
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         In some circumstances, numerical labels and tick marks are
+         drawn around the edges of the plotting area (see the Labelling
+         attribute).  In this case, the value of the LabelUp attribute is
+         ignored.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LabelUp\texttt{"}  instead of
+         \texttt{"} LabelUp(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the
+         attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the LabelUp(1) value.
+      }
+   }
+}
+\sstroutine{
+   Labelling
+}{
+   Label and tick placement option for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      the strategy for placing numerical labels and tick marks for a \htmlref{Plot}{Plot}.
+
+      If the Labelling value of a Plot is \texttt{"} exterior\texttt{"}  (the default), then
+      numerical labels and their associated tick marks are placed
+      around the edges of the plotting area, if possible. If this is
+      not possible, or if the Labelling value is \texttt{"} interior\texttt{"} , then they
+      are placed along grid lines inside the plotting area.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The \htmlref{LabelAt(axis)}{LabelAt(axis)} attribute may be used to determine the exact
+         placement of labels and tick marks that are drawn inside the
+         plotting area.
+      }
+   }
+}
+\sstroutine{
+   LatAxis
+}{
+   Index of the latitude axis
+}{
+   \sstdescription{
+      This read-only attribute gives the index (1 or 2) of the latitude
+      axis within the \htmlref{SkyFrame}{SkyFrame} (taking into account any current axis
+      permutations).
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ListSize
+}{
+   Number of points in a PointList
+}{
+   \sstdescription{
+      This is a read-only attribute giving the number of points in a
+      \htmlref{PointList}{PointList}. This value is determined when the PointList is created.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         PointList
+      }{
+         All PointLists have this attribute.
+      }
+   }
+}
+\sstroutine{
+   LogGap(axis)
+}{
+   Interval between major axis values of a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      the logarithmic interval between the \texttt{"} major\texttt{"}  axis values of a \htmlref{Plot}{Plot}, at
+      which (for example) major tick marks are drawn. It takes a separate
+      value for each physical axis of the Plot so that, for instance,
+      the setting \texttt{"} LogGap(2)=100.0\texttt{"}  specifies the ratio between adjacent major
+      values along the second axis. The LogGap attribute is only used when
+      the LogTicks attribute indicates that the spacing between major axis
+      values is to be logarithmic. If major axis values are linearly spaced
+      then the gap is specified using attribute Gap.
+
+      The LogGap value supplied will be rounded to the nearest power of 10.
+      The reciprocal of the supplied value may be used if this is necessary
+      to produce usable major axis values. If a zero or negative value is
+      supplied, an error will be reported when the grid is drawn. The default
+      behaviour is for the Plot to generate its own LogGap value when
+      required, based on the range of axis values to be represented.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The LogGap value is a ratio between axis values and is therefore
+         dimensionless.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LogGap\texttt{"}  instead of \texttt{"} LogGap(2)\texttt{"} ),
+         then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the attribute
+         value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}  operation
+         will use just the LogGap(1) value.
+      }
+   }
+}
+\sstroutine{
+   LogLabel(axis)
+}{
+   Use exponential format for numerical axis labels?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether the numerical axis labels should be in normal decimal form
+      or should be represented as 10 raised to the appropriate power.
+      That is, an axis value of 1000.0 will be drawn as \texttt{"} 1000.0\texttt{"}  if
+      LogLabel is zero, but as \texttt{"} 10$\wedge$3\texttt{"}  if LogLabel is non-zero. If
+      graphical escape sequences are supported (see attribute \htmlref{Escape}{Escape}),
+      the power in such exponential labels will be drawn as a small
+      superscript instead of using a \texttt{"} $\wedge$\texttt{"}  character to represent
+      exponentiation.
+
+      The default is to produce exponential labels if the major tick
+      marks are logarithmically spaced (see the LogTicks attribute).
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Plot}{Plot}
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LogLabel\texttt{"}  instead of
+         \texttt{"} LogLabel(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the
+         attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the LogLabel(1) value.
+      }
+   }
+}
+\sstroutine{
+   LogPlot(axis)
+}{
+   Map the plot logarithmically onto the screen?
+}{
+   \sstdescription{
+      This attribute controls the appearance of all graphics produced by
+      the \htmlref{Plot}{Plot}, by determining whether the axes of the plotting surface
+      are mapped logarithmically or linearly onto the base \htmlref{Frame}{Frame} of the
+      \htmlref{FrameSet}{FrameSet} supplied when the Plot was constructed. It takes a separate
+      value for each axis of the graphics coordinate system (i.e. the
+      base Frame in the Plot) so that, for instance, the setting
+      \texttt{"} LogPlot(2)=1\texttt{"}  specifies that the second axis of the graphics
+      coordinate system (usually the vertical axis) should be mapped
+      logarithmically onto the second axis of the base Frame of the
+      FrameSet supplied when the Plot was constructed.
+
+      If the LogPlot value of a Plot axis is non-zero, it causes that
+      axis to be mapped logarithmically, otherwise (the default) the axis
+      is mapped linearly.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The setting of the LogPlot attribute provides the default value
+         for the related LogTicks attribute. By selecting suitable values for
+         LogPlot and LogTicks, it is possible to have tick marks which are evenly
+         spaced in value but which are mapped logarithmically onto the screen
+         (and vice-versa).
+
+         \sstitem
+         An axis may only be mapped logarithmically if the visible part of
+         the axis does not include the value zero. The visible part of the
+         axis is that part which is mapped onto the plotting area, and is
+         measured within the base Frame of the FrameSet which was supplied when
+         the Plot was constructed. Any attempt to set LogPlot to a non-zero value
+         will be ignored (without error) if the visible part of the axis
+         includes the value zero
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LogPlot\texttt{"}  instead of
+         \texttt{"} LogPlot(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the
+         attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the LogPlot(1) value.
+      }
+   }
+}
+\sstroutine{
+   LogTicks(axis)
+}{
+   Space the major tick marks logarithmically?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether the major tick marks should be spaced logarithmically or
+      linearly in axis value. It takes a separate value for each physical
+      axis of the \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} LogTicks(2)=1\texttt{"}
+      specifies that the major tick marks on the second axis should be
+      spaced logarithmically.
+
+      If the LogTicks value for a physical axis is non-zero, the major
+      tick marks on that axis will be spaced logarithmically (that is,
+      there will be a constant ratio between the axis values at adjacent
+      major tick marks). An error will be reported if the dynamic range of
+      the axis (the ratio of the largest to smallest displayed axis value)
+      is less than 10.0. If the LogTicks value is zero, the major tick marks
+      will be evenly spaced (that is, there will be a constant difference
+      between the axis values at adjacent major tick marks). The default is
+      to produce logarithmically spaced tick marks if the corresponding
+      LogPlot attribute is non-zero and the ratio of maximum axis value
+      to minimum axis value is 100 or more. If either of these conditions
+      is not met, the default is to produce linearly spaced tick marks.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The setting of the LogTicks attribute does not affect the mapping
+         of the plot onto the screen, which is controlled by attribute LogPlot.
+         By selecting suitable values for LogPlot and LogTicks, it is possible to
+         have tick marks which are evenly spaced in value but which are mapped
+         logarithmically onto the screen (and vica-versa).
+
+         \sstitem
+         An error will be reported when drawing an annotated axis grid if
+         the visible part of the physical axis includes the value zero.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} LogTicks\texttt{"}  instead of
+         \texttt{"} LogTicks(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the
+         attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the LogTicks(1) value.
+      }
+   }
+}
+\sstroutine{
+   LonAxis
+}{
+   Index of the longitude axis
+}{
+   \sstdescription{
+      This read-only attribute gives the index (1 or 2) of the longitude
+      axis within the \htmlref{SkyFrame}{SkyFrame} (taking into account any current axis
+      permutations).
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   LutEpsilon
+}{
+   The relative error of the values held in the took-up table
+}{
+   \sstdescription{
+      This attribute holds the relative error of the values held in the
+      took-up table. It is used when simplifying a \htmlref{LutMap}{LutMap}, to determine
+      if the LutMap should be considered linear. Setting a larger value
+      makes it more likely that a LutMap will be replaced by a \htmlref{WinMap}{WinMap}
+      (i.e. a linear \htmlref{Mapping}{Mapping}) when simplified.
+
+      The default value is the value of the system constant DBL\_EPSILON
+      (typically around 1e-16 or 2E-16). If the values in the look-up
+      table were derived from single precision data, it may be appropriate
+      to set this attribute to a value around 1E-7.
+
+      Note, the value of this attribute may changed only if the LutMap
+      has no more than one reference. That is, an error is reported if the
+      LutMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         LutMap
+      }{
+         All LutMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   LutInterp
+}{
+   Look-up table interpolation method
+}{
+   \sstdescription{
+      This attribute indicates the method to be used when finding the
+      output value of a \htmlref{LutMap}{LutMap} for an input value part way between two
+      table entries. If it is set to 0 (the default) then linear
+      interpolation is used. Otherwise, nearest neighbour interpolation
+      is used.
+
+      Using nearest neighbour interpolation causes AST\_\_BAD to be returned
+      for any point which falls outside the bounds of the table. Linear
+      interpolation results in an extrapolated value being returned based
+      on the two end entries in the table.
+
+      Note, the value of this attribute may changed only if the LutMap
+      has no more than one reference. That is, an error is reported if the
+      LutMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         LutMap
+      }{
+         All LutMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   MajTickLen(axis)
+}{
+   Length of major tick marks for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      the length of the major tick marks drawn on the axes of a \htmlref{Plot}{Plot}.
+      It takes a separate value for each physical axis of the Plot so
+      that, for instance, the setting \texttt{"} MajTickLen(2)=0\texttt{"}  specifies the
+      length of the major tick marks drawn on the second axis.
+
+      The MajTickLen value should be given as a fraction of the
+      minimum dimension of the plotting area. Negative values cause
+      major tick marks to be placed on the outside of the
+      corresponding grid line or axis (but subject to any clipping
+      imposed by the underlying graphics system), while positive
+      values cause them to be placed on the inside.
+
+      The default behaviour depends on whether a coordinate grid is
+      drawn inside the plotting area (see the \htmlref{Grid}{Grid} attribute). If so,
+      the default MajTickLen value is zero (so that major ticks are
+      not drawn), otherwise the default is $+$0.015.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} MajTickLen\texttt{"}  instead of
+         \texttt{"} MajTickLen(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the MajTickLen(1) value.
+      }
+   }
+}
+\sstroutine{
+   MapLocked
+}{
+   Prevent new entries being added to a KeyMap?
+}{
+   \sstdescription{
+      If this boolean attribute is set to
+      a non-zero value,
+      an error will be reported if an attempt is made to add a new entry
+      to the \htmlref{KeyMap}{KeyMap}. Note, the value associated with any existing entries
+      can still be changed, but no new entries can be stored in the KeyMap.
+      The default value
+      (zero)
+      allows new entries to be added to the KeyMap.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         All KeyMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When setting a new value for MapLocked, the supplied value is
+         propagated to any KeyMaps contained within the supplied KeyMap.
+
+         \sstitem
+         When clearing the MapLocked attribute, the attribute is also
+         cleared in any KeyMaps contained within the supplied KeyMap.
+      }
+   }
+}
+\sstroutine{
+   MatchEnd
+}{
+   Match trailing axes?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how a \htmlref{Frame}{Frame}
+      behaves when it is used (by \htmlref{astFindFrame}{astFindFrame}) as a template to match
+      another (target) Frame. It applies only in the case where a
+      match occurs between template and target Frames with different
+      numbers of axes.
+
+      If the MatchEnd value of the template Frame is zero, then the
+      axes which occur first in the target Frame will be matched and
+      any trailing axes (in either the target or template) will be
+      disregarded. If it is non-zero, the final axes in each Frame
+      will be matched and any un-matched leading axes will be
+      disregarded instead.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default MatchEnd value for a Frame is zero, so that
+         trailing axes are disregarded.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The MatchEnd attribute of a FrameSet is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+}
+\sstroutine{
+   MaxAxes
+}{
+   Maximum number of Frame axes to match
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{Frame}{Frame} behaves when it is used (by
+      \htmlref{astFindFrame}{astFindFrame}) as a template to match another (target) Frame. It
+      specifies the maximum number of axes that the target Frame may
+      have in order to match the template.
+
+      Normally, this value will equal the number of Frame axes, so
+      that a template Frame will only match another Frame with the
+      same number of axes as itself. By setting a different value,
+      however, the matching process may be used to identify Frames
+      with specified numbers of axes.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default MaxAxes value for a Frame is equal to the number
+         of Frame axes (\htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The MaxAxes attribute of a CmpFrame defaults to a large number
+         (1000000) which is much larger than any likely number of axes in
+         a Frame. Combined with the \htmlref{MinAxes}{MinAxes} default of zero (for a
+         CmpFrame), this means that the default behaviour for a CmpFrame
+         is to match any target Frame that consists of a subset of the
+         axes in the template CmpFrame. To change this so that a CmpFrame
+         will only match Frames that have the same number of axes, you
+         should set the CmpFrame MaxAxes and MinAxes attributes to the
+         number of axes in the CmpFrame.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The MaxAxes attribute of a FrameSet is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When setting a MaxAxes value, the value of the MinAxes
+         attribute may also be silently changed so that it remains
+         consistent with (i.e. does not exceed) the new value. The
+         default MaxAxes value may also be reduced to remain consistent
+         with the MinAxes value.
+
+         \sstitem
+         If a template Frame is used to match a target with a different
+         number of axes, the \htmlref{MatchEnd}{MatchEnd} attribute of the template is used
+         to determine how the individual axes of each Frame should match.
+      }
+   }
+}
+\sstroutine{
+   MeshSize
+}{
+   Number of points used to represent the boundary of a Region
+}{
+   \sstdescription{
+      This attribute controls how many points are used when creating a
+      mesh of points covering the boundary or volume of a \htmlref{Region}{Region}. Such a
+      mesh is returned by the
+      \htmlref{astGetRegionMesh}{astGetRegionMesh}
+      method. The boundary mesh is also used when testing for overlap
+      between two Regions: each point in the bomdary mesh of the first
+      Region is checked to see if it is inside or outside the second Region.
+      Thus, the reliability of the overlap check depends on the value assigned
+      to this attribute. If the value used is very low, it is possible for
+      overlaps to go unnoticed. High values produce more reliable results, but
+      can result in the overlap test being very slow. The default value is 200
+      for two dimensional Regions and 2000 for three or more dimensional
+      Regions (this attribute is not used for 1-dimensional regions since the
+      boundary of a simple 1-d Region can only ever have two points). A
+      value of five is used if the supplied value is less than five.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpRegion}{CmpRegion}
+      }{
+         The default MeshSize for a CmpRegion is the MeshSize of its
+         first component Region.
+      }
+      \sstsubsection{
+         \htmlref{Stc}{Stc}
+      }{
+         The default MeshSize for an Stc is the MeshSize of its
+         encapsulated Region.
+      }
+   }
+}
+\sstroutine{
+   MinAxes
+}{
+   Minimum number of Frame axes to match
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{Frame}{Frame} behaves when it is used (by
+      \htmlref{astFindFrame}{astFindFrame}) as a template to match another (target) Frame. It
+      specifies the minimum number of axes that the target Frame may
+      have in order to match the template.
+
+      Normally, this value will equal the number of Frame axes, so
+      that a template Frame will only match another Frame with the
+      same number of axes as itself. By setting a different value,
+      however, the matching process may be used to identify Frames
+      with specified numbers of axes.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default MinAxes value for a Frame is equal to the number
+         of Frame axes (\htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The MinAxes attribute of a CmpFrame defaults to zero. Combined
+         with the \htmlref{MaxAxes}{MaxAxes} default of 1000000 (for a CmpFrame), this means
+         that the default behaviour for a CmpFrame is to match any target
+         Frame that consists of a subset of the axes in the template
+         CmpFrame. To change this so that a CmpFrame will only match Frames
+         that have the same number of axes, you should set the CmpFrame
+         MinAxes and MaxAxes attributes to the number of axes in the CmpFrame.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The MinAxes attribute of a FrameSet is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When setting a MinAxes value, the value of the MaxAxes
+         attribute may also be silently changed so that it remains
+         consistent with (i.e. is not less than) the new value. The
+         default MinAxes value may also be reduced to remain consistent
+         with the MaxAxes value.
+
+         \sstitem
+         If a template Frame is used to match a target with a different
+         number of axes, the \htmlref{MatchEnd}{MatchEnd} attribute of the template is used
+         to determine how the individual axes of each Frame should match.
+      }
+   }
+}
+\sstroutine{
+   MinTick(axis)
+}{
+   Density of minor tick marks for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      the density of minor tick marks which appear between the major
+      axis values of a \htmlref{Plot}{Plot}. It takes a separate value for each
+      physical axis of a Plot so that, for instance, the setting
+      \texttt{"} MinTick(2)=2\texttt{"}  specifies the density of minor tick marks along
+      the second axis.
+
+      The value supplied should be the number of minor divisions
+      required between each pair of major axis values, this being one
+      more than the number of minor tick marks to be drawn.  By
+      default, a value is chosen that depends on the gap between major
+      axis values and the nature of the axis.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} MinTick\texttt{"}  instead of
+         \texttt{"} MinTick(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the MinTick(1) value.
+      }
+   }
+}
+\sstroutine{
+   MinTickLen(axis)
+}{
+   Length of minor tick marks for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      the length of the minor tick marks drawn on the axes of a \htmlref{Plot}{Plot}.
+      It takes a separate value for each physical axis of the Plot so
+      that, for instance, the setting \texttt{"} MinTickLen(2)=0\texttt{"}  specifies the
+      length of the minor tick marks drawn on the second axis.
+
+      The MinTickLen value should be given as a fraction of the
+      minimum dimension of the plotting area. Negative values cause
+      minor tick marks to be placed on the outside of the
+      corresponding grid line or axis (but subject to any clipping
+      imposed by the underlying graphics system), while positive
+      values cause them to be placed on the inside.
+
+      The default value is $+$0.007.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The number of minor tick marks drawn is determined by the
+         Plot\texttt{'} s \htmlref{MinTick(axis)}{MinTick(axis)} attribute.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} MinTickLen\texttt{"}  instead of
+         \texttt{"} MinTickLen(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the MinTickLen(1) value.
+      }
+   }
+}
+\sstroutine{
+   NatLat
+}{
+   Native latitude of the reference point of a FITS-WCS projection
+}{
+   \sstdescription{
+      This attribute gives the latitude of the reference point of the
+      FITS-WCS projection implemented by a \htmlref{WcsMap}{WcsMap}. The value is in
+      radians in the \texttt{"} native spherical\texttt{"}  coordinate system. This value is
+      fixed for most projections, for instance it is PI/2 (90 degrees)
+      for all zenithal projections. For some projections (e.g. the conics)
+      the value is not fixed, but is specified by parameter one on the
+      latitude axis.
+
+      FITS-WCS paper II introduces the concept of a \texttt{"} fiducial point\texttt{"}
+      which is logical distinct from the projection reference point.
+      It is easy to confuse the use of these two points. The fiducial
+      point is the point which has celestial coordinates given by the
+      CRVAL FITS keywords. The native spherical coordinates for this point
+      default to the values of the NatLat and \htmlref{NatLon}{NatLon}, but these defaults
+      mey be over-ridden by values stored in the PVi\_j keywords. Put
+      another way, the CRVAL keywords will by default give the celestial
+      coordinates of the projection reference point, but may refer to
+      some other point if alternative native longitude and latitude values
+      are provided through the PVi\_j keywords.
+
+      The NatLat attribute is read-only.
+   }
+   \sstattributetype{
+      Floating point, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A default value of AST\_\_BAD is used if no latitude value is available.
+      }
+   }
+}
+\sstroutine{
+   NatLon
+}{
+   Native longitude of the reference point of a FITS-WCS projection
+}{
+   \sstdescription{
+      This attribute gives the longitude of the reference point of the
+      FITS-WCS projection implemented by a \htmlref{WcsMap}{WcsMap}. The value is in
+      radians in the \texttt{"} native spherical\texttt{"}  coordinate system, and will
+      usually be zero. See the description of attribute \htmlref{NatLat}{NatLat} for further
+      information.
+
+      The NatLon attribute is read-only.
+   }
+   \sstattributetype{
+      Floating point, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Naxes
+}{
+   Number of Frame axes
+}{
+   \sstdescription{
+      This is a read-only attribute giving the number of axes in a
+      \htmlref{Frame}{Frame} (i.e. the number of dimensions of the coordinate space
+      which the Frame describes). This value is determined when the
+      Frame is created.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Naxes attribute of a FrameSet is the same as that of its
+         current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The Naxes attribute of a CmpFrame is equal to the sum of the
+         Naxes values of its two component Frames.
+      }
+   }
+}
+\sstroutine{
+   Ncard
+}{
+   Number of FITS header cards in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the total number of FITS header cards
+      stored in a \htmlref{FitsChan}{FitsChan}. It is updated as cards are added or
+      deleted.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Ncolumn
+}{
+   The number of columns in the table
+}{
+   \sstdescription{
+      This attribute holds the number of columns currently in the table. Columns
+      are added and removed using the
+      \htmlref{astAddColumn}{astAddColumn} and \htmlref{astRemoveColumn}{astRemoveColumn}
+      functions.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   NegLon
+}{
+   Display negative longitude values?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how longitude values
+      are normalized for display by \htmlref{astNorm}{astNorm}.
+
+      If the NegLon attribute is zero, then normalized
+      longitude values will be in the range zero to 2.pi. If NegLon is
+      non-zero, then normalized longitude values will be in the range -pi
+      to pi.
+
+      The default value depends on the current value of the \htmlref{SkyRefIs}{SkyRefIs}
+      attribute, If SkyRefIs has a value of \texttt{"} Origin\texttt{"} , then the default for
+      NegLon is one, otherwise the default is zero.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Negated
+}{
+   Region negation flag
+}{
+   \sstdescription{
+      This attribute controls whether a \htmlref{Region}{Region} represents the \texttt{"} inside\texttt{"}  or
+      the \texttt{"} outside\texttt{"}  of the area which was supplied when the Region was
+      created. If the attribute value is zero (the default), the Region
+      represents the inside of the original area. However, if it is non-zero,
+      it represents the outside of the original area. The value of this
+      attribute may be toggled using the
+      \htmlref{astNegate}{astNegate} function.
+
+      Note, whether the boundary is considered to be inside the Region or
+      not is controlled by the \htmlref{Closed}{Closed} attribute. Changing the value of
+      the Negated attribute does not change the value of the Closed attribute.
+      Thus, if Region is closed, then the boundary of the Region will be
+      inside the Region, whatever the setting of the Negated attribute.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Region
+      }{
+         All Regions have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Nframe
+}{
+   Number of Frames in a FrameSet
+}{
+   \sstdescription{
+      This attribute gives the number of Frames in a \htmlref{FrameSet}{FrameSet}. This
+      value will change as Frames are added or removed, but will
+      always be at least one.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FrameSet
+      }{
+         All FrameSets have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Nin
+}{
+   Number of input coordinates for a Mapping
+}{
+   \sstdescription{
+      This attribute gives the number of coordinate values required to
+      specify an input point for a \htmlref{Mapping}{Mapping} (i.e. the number of
+      dimensions of the space in which the Mapping\texttt{'} s input points
+      reside).
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         If a CmpMap\texttt{'} s component Mappings are joined in series, then
+         its Nin attribute is equal to the Nin attribute of the first
+         component (or to the \htmlref{Nout}{Nout} attribute of the second component
+         if the the CmpMap\texttt{'} s \htmlref{Invert}{Invert} attribute is non-zero).
+
+         If a CmpMap\texttt{'} s component Mappings are joined in parallel, then
+         its Nin attribute is given by the sum of the Nin attributes
+         of each component (or to the sum of their Nout attributes if
+         the CmpMap\texttt{'} s Invert attribute is non-zero).
+      }
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         The Nin attribute for a Frame is always equal to the number
+         of Frame axes (\htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Nin attribute of a FrameSet is equal to the number of
+         axes (Naxes attribute) of its base Frame (as specified by the
+         FrameSet\texttt{'} s \htmlref{Base}{Base} attribute). The Nin attribute value may
+         therefore change if a new base Frame is selected.
+      }
+   }
+}
+\sstroutine{
+   NiterInverse
+}{
+   Maximum number of iterations for the iterative inverse transformation
+}{
+   \sstdescription{
+      This attribute controls the iterative inverse transformation
+      used if the \htmlref{IterInverse}{IterInverse} attribute is non-zero.
+
+      Its value gives the maximum number of iterations of the
+      Newton-Raphson algorithm to be used for each transformed position.
+      The default value is 4. See also attribute \htmlref{TolInverse}{TolInverse}.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{PolyMap}{PolyMap}
+      }{
+         All PolyMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Nkey
+}{
+   Number of unique FITS keywords in a FitsChan
+}{
+   \sstdescription{
+      This attribute gives the total number of unique FITS keywords
+      stored in a \htmlref{FitsChan}{FitsChan}. It is updated as cards are added or
+      deleted. If no keyword occurrs more than once in the FitsChan, the
+      \htmlref{Ncard}{Ncard} and Nkey attributes will be equal. If any keyword occurrs
+      more than once, the Nkey attribute value will be smaller than
+      the Ncard attribute value.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Nobject
+}{
+   Number of Objects in class
+}{
+   \sstdescription{
+      This attribute gives the total number of Objects currently in
+      existence in the same class as the \htmlref{Object}{Object} whose attribute value
+      is requested. This count does not include Objects which belong
+      to derived (more specialised) classes.
+
+      This attribute is mainly intended for debugging. It can be used
+      to detect whether Objects which should have been deleted have,
+      in fact, been deleted.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Norm(axis)
+}{
+   Specifies the plane upon which a Plot3D draws text and markers
+}{
+   \sstdescription{
+      This attribute controls the appearance of text and markers drawn
+      by a \htmlref{Plot3D}{Plot3D}. It specifies the orientation of the plane upon which
+      text and markers will be drawn by all subsequent invocations of the
+      \htmlref{astText}{astText} and \htmlref{astMark}{astMark} functions.
+
+      When setting or getting the Norm attribute, the attribute name must
+      be qualified by an axis index in the range 1 to 3. The 3 elements of
+      the Norm attribute are together interpreted as a vector in 3D graphics
+      coordinates that is normal to the plane upon which text and marks
+      should be drawn. When testing or clearing the attribute, the axis
+      index is optional. If no index is supplied, then clearing the Norm
+      attribute will clear all three elements, and testing the Norm attribute
+      will return a non-zero value if any of the three elements are set.
+
+      The default value is 1.0 for each of the 3 elements. The length of
+      the vector is insignificant, but an error will be reported when
+      attempting to draw text or markers if the vector has zero length.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Plot}{Plot}
+      }{
+         All Plot3Ds have this attribute.
+      }
+   }
+}
+\sstroutine{
+   NormUnit(axis)
+}{
+   Normalised physical units for formatted axis values
+}{
+   \sstdescription{
+      The value of this read-only attribute is derived from the current
+      value of the Unit attribute. It will represent an equivalent system
+      of units to the Unit attribute, but will potentially be simplified.
+      For instance, if Unit is set to \texttt{"} s$*$(m/s)\texttt{"} , the NormUnit value will
+      be \texttt{"} m\texttt{"} . If no simplification can be performed, the value of the
+      NormUnit attribute will equal that of the Unit attribute.
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         All Frames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   Nout
+}{
+   Number of output coordinates for a Mapping
+}{
+   \sstdescription{
+      This attribute gives the number of coordinate values generated
+      by a \htmlref{Mapping}{Mapping} to specify each output point (i.e. the number of
+      dimensions of the space in which the Mapping\texttt{'} s output points
+      reside).
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         If a CmpMap\texttt{'} s component Mappings are joined in series, then
+         its Nout attribute is equal to the Nout attribute of the
+         second component (or to the \htmlref{Nin}{Nin} attribute of the first
+         component if the the CmpMap\texttt{'} s \htmlref{Invert}{Invert} attribute is non-zero).
+
+         If a CmpMap\texttt{'} s component Mappings are joined in parallel, then
+         its Nout attribute is given by the sum of the Nout attributes
+         of each component (or to the sum of their Nin attributes if
+         the CmpMap\texttt{'} s Invert attribute is non-zero).
+      }
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         The Nout attribute for a Frame is always equal to the number
+         of Frame axes (\htmlref{Naxes}{Naxes} attribute).
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Nout attribute of a FrameSet is equal to the number of
+         FrameSet axes (Naxes attribute) which, in turn, is equal to
+         the Naxes attribute of the FrameSet\texttt{'} s current Frame (as
+         specified by the \htmlref{Current}{Current} attribute). The Nout attribute value
+         may therefore change if a new current Frame is selected.
+      }
+   }
+}
+\sstroutine{
+   Nparameter
+}{
+   The number of global parameters in the table
+}{
+   \sstdescription{
+      This attribute holds the number of global parameters currently in the table.
+      Parameters are added and removed using the
+      \htmlref{astAddParameter}{astAddParameter} and \htmlref{astRemoveParameter}{astRemoveParameter}
+      functions.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Nrow
+}{
+   The number of rows in the table
+}{
+   \sstdescription{
+      This attribute holds the index of the last row to which any
+      contents have been added using any of the
+      astMapPut...
+      AST\_MAPPUT...
+      functions. The first row has index 1.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Table}{Table}
+      }{
+         All Tables have this attribute.
+      }
+   }
+}
+\sstroutine{
+   NumLab(axis)
+}{
+   Draw numerical axis labels for a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether labels should be drawn to represent the numerical values
+      along each axis of a \htmlref{Plot}{Plot}. It takes a separate value for each
+      physical axis of a Plot so that, for instance, the setting
+      \texttt{"} NumLab(2)=1\texttt{"}  specifies that numerical labels should be drawn
+      for the second axis.
+
+      If the NumLab value of a Plot axis is non-zero (the default),
+      then numerical labels will be drawn for that axis, otherwise
+      they will be omitted.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The drawing of associated descriptive axis labels for a Plot
+         (describing the quantity being plotted along each axis) is
+         controlled by the \htmlref{TextLab(axis)}{TextLab(axis)} attribute.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} NumLab\texttt{"}  instead of
+         \texttt{"} NumLab(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect the
+         attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the NumLab(1) value.
+      }
+   }
+}
+\sstroutine{
+   NumLabGap(axis)
+}{
+   Spacing of numerical labels for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      where numerical axis labels are placed relative to the axes they
+      describe.  It takes a separate value for each physical axis of a
+      \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} NumLabGap(2)=-0.01\texttt{"}
+      specifies where the numerical label for the second axis should
+      be drawn.
+
+      For each axis, the NumLabGap value gives the spacing between the
+      axis line (or edge of the plotting area, if appropriate) and the
+      nearest edge of the corresponding numerical axis
+      labels. Positive values cause the descriptive label to be placed
+      on the opposite side of the line to the default tick marks,
+      while negative values cause it to be placed on the same side.
+
+      The NumLabGap value should be given as a fraction of the minimum
+      dimension of the plotting area, the default value being $+$0.01.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} NumLabGap\texttt{"}  instead of
+         \texttt{"} NumLabGap(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the NumLabGap(1) value.
+      }
+   }
+}
+\sstroutine{
+   ObjSize
+}{
+   The in-memory size of the Object
+}{
+   \sstdescription{
+      This attribute gives the total number of bytes of memory used by
+      the \htmlref{Object}{Object}. This includes any Objects which are encapsulated within
+      the supplied Object.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   ObsAlt
+}{
+   The geodetic altitude of the observer
+}{
+   \sstdescription{
+      This attribute specifies the geodetic altitude of the observer, in
+      metres, relative to the IAU 1976 reference ellipsoid. The basic \htmlref{Frame}{Frame}
+      class makes no use of this attribute, but specialised subclasses of
+      Frame may use it. For instance, the \htmlref{SpecFrame}{SpecFrame}, \htmlref{SkyFrame}{SkyFrame} and \htmlref{TimeFrame}{TimeFrame}
+      classes use it. The default value is zero.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+      \sstsubsection{
+         SpecFrame
+      }{
+         Together with the \htmlref{ObsLon}{ObsLon}, \htmlref{Epoch}{Epoch}, \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec} attributes,
+         it defines the Doppler shift introduced by the observers diurnal
+         motion around the earths axis, which is needed when converting to
+         or from the topocentric standard of rest. The maximum velocity
+         error which can be caused by an incorrect value is 0.5 km/s. The
+         default value for the attribute is zero.
+      }
+      \sstsubsection{
+         TimeFrame
+      }{
+         Together with the ObsLon attribute, it is used when converting
+         between certain time scales (TDB, TCB, LMST, LAST)
+      }
+   }
+}
+\sstroutine{
+   ObsLat
+}{
+   The geodetic latitude of the observer
+}{
+   \sstdescription{
+      This attribute specifies the geodetic latitude of the observer, in
+      degrees, relative to the IAU 1976 reference ellipsoid. The basic \htmlref{Frame}{Frame}
+      class makes no use of this attribute, but specialised subclasses of
+      Frame may use it. For instance, the \htmlref{SpecFrame}{SpecFrame}, \htmlref{SkyFrame}{SkyFrame} and \htmlref{TimeFrame}{TimeFrame}
+      classes use it. The default value is zero.
+
+      The value is stored internally in radians, but is converted to and
+      from a degrees string for access. Some example input formats are:
+      \texttt{"} 22:19:23.2\texttt{"} , \texttt{"} 22 19 23.2\texttt{"} , \texttt{"} 22:19.387\texttt{"} , \texttt{"} 22.32311\texttt{"} , \texttt{"} N22.32311\texttt{"} ,
+      \texttt{"} -45.6\texttt{"} , \texttt{"} S45.6\texttt{"} . As indicated, the sign of the latitude can
+      optionally be indicated using characters \texttt{"} N\texttt{"}  and \texttt{"} S\texttt{"}  in place of the
+      usual \texttt{"} $+$\texttt{"}  and \texttt{"} -\texttt{"} . When converting the stored value to a string, the
+      format \texttt{"} [s]dd:mm:ss.ss\texttt{"}  is used, when \texttt{"} [s]\texttt{"}  is \texttt{"} N\texttt{"}  or \texttt{"} S\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+      \sstsubsection{
+         SpecFrame
+      }{
+         Together with the \htmlref{ObsLon}{ObsLon}, \htmlref{Epoch}{Epoch}, \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec} attributes,
+         it defines the Doppler shift introduced by the observers diurnal
+         motion around the earths axis, which is needed when converting to
+         or from the topocentric standard of rest. The maximum velocity
+         error which can be caused by an incorrect value is 0.5 km/s. The
+         default value for the attribute is zero.
+      }
+      \sstsubsection{
+         TimeFrame
+      }{
+         Together with the ObsLon attribute, it is used when converting
+         between certain time scales (TDB, TCB, LMST, LAST)
+      }
+   }
+}
+\sstroutine{
+   ObsLon
+}{
+   The geodetic longitude of the observer
+}{
+   \sstdescription{
+      This attribute specifies the geodetic (or equivalently, geocentric)
+      longitude of the observer, in degrees, measured positive eastwards.
+      See also attribute \htmlref{ObsLat}{ObsLat}. The basic \htmlref{Frame}{Frame} class makes no use of this
+      attribute, but specialised subclasses of Frame may use it. For instance,
+      the \htmlref{SpecFrame}{SpecFrame}, \htmlref{SkyFrame}{SkyFrame} and \htmlref{TimeFrame}{TimeFrame} classes use it. The default value
+      is zero.
+
+      The value is stored internally in radians, but is converted to and
+      from a degrees string for access. Some example input formats are:
+      \texttt{"} 155:19:23.2\texttt{"} , \texttt{"} 155 19 23.2\texttt{"} , \texttt{"} 155:19.387\texttt{"} , \texttt{"} 155.32311\texttt{"} , \texttt{"} E155.32311\texttt{"} ,
+      \texttt{"} -204.67689\texttt{"} , \texttt{"} W204.67689\texttt{"} . As indicated, the sign of the longitude can
+      optionally be indicated using characters \texttt{"} E\texttt{"}  and \texttt{"} W\texttt{"}  in place of the
+      usual \texttt{"} $+$\texttt{"}  and \texttt{"} -\texttt{"} . When converting the stored value to a string, the
+      format \texttt{"} [s]ddd:mm:ss.ss\texttt{"}  is used, when \texttt{"} [s]\texttt{"}  is \texttt{"} E\texttt{"}  or \texttt{"} W\texttt{"}  and the
+      numerical value is chosen to be less than 180 degrees.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+      \sstsubsection{
+         SpecFrame
+      }{
+         Together with the ObsLon, \htmlref{Epoch}{Epoch}, \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec} attributes,
+         it defines the Doppler shift introduced by the observers diurnal
+         motion around the earths axis, which is needed when converting to
+         or from the topocentric standard of rest. The maximum velocity
+         error which can be caused by an incorrect value is 0.5 km/s. The
+         default value for the attribute is zero.
+      }
+      \sstsubsection{
+         TimeFrame
+      }{
+         Together with the ObsLon attribute, it is used when converting
+         between certain time scales (TDB, TCB, LMST, LAST)
+      }
+   }
+}
+\sstroutine{
+   PVMax(i)
+}{
+   Maximum number of FITS-WCS projection parameters
+}{
+   \sstdescription{
+      This attribute specifies the largest legal index for a PV projection
+      parameter attached to a specified axis of the \htmlref{WcsMap}{WcsMap} (i.e. the
+      largest legal value for \texttt{"} m\texttt{"}  when accessing the \texttt{"} \htmlref{PVi\_m}{PVi\_m}\texttt{"}  attribute).
+      The axis index is specified by i, and should be in the range 1 to 99.
+      The value for each axis is determined by the projection type specified
+      when the WcsMap
+      is first created using \htmlref{astWcsMap}{astWcsMap} and cannot subsequently be
+      changed.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   PVi\_m
+}{
+   FITS-WCS projection parameters
+}{
+   \sstdescription{
+      This attribute specifies the projection parameter values to be
+      used by a \htmlref{WcsMap}{WcsMap} when implementing a FITS-WCS sky projection.
+      Each PV attribute name should include two integers, i and m,
+      separated by an underscore. The axis index is specified
+      by i, and should be in the range 1 to 99. The parameter number
+      is specified by m, and should be in the range 0 to 99. For
+      example, \texttt{"} PV2\_1=45.0\texttt{"}  would specify a value for projection
+      parameter 1 of axis 2 in a WcsMap.
+
+      These projection parameters correspond exactly to the values
+      stored using the FITS-WCS keywords \texttt{"} PV1\_1\texttt{"} , \texttt{"} PV1\_2\texttt{"} , etc. This
+      means that projection parameters which correspond to angles must
+      be given in degrees (despite the fact that the angular
+      coordinates and other attributes used by a WcsMap are in
+      radians).
+
+      The set of projection parameters used by a WcsMap depends on the
+      type of projection, which is determined by its \htmlref{WcsType}{WcsType}
+      parameter.  Most projections either do not require projection
+      parameters, or use parameters 1 and 2 associated with the latitude
+      axis. You should consult the FITS-WCS paper for details.
+
+      Some projection parameters have default values (as defined in
+      the FITS-WCS paper) which apply if no explicit value is given.
+      You may omit setting a value for these \texttt{"} optional\texttt{"}  parameters and the
+      default will apply. Some projection parameters, however, have no
+      default and a value must be explicitly supplied.  This is most
+      conveniently
+      done using the \texttt{"} options\texttt{"}  argument of \htmlref{astWcsMap}{astWcsMap} (q.v.) when a WcsMap
+      is first created. An error will result when a WcsMap is used to
+      transform coordinates if any of its required projection
+      parameters has not been set and lacks a default value.
+
+      A \texttt{"} get\texttt{"}  operation for a parameter which has not been assigned a value
+      will return the default value defined in the FITS-WCS paper, or
+      AST\_\_BAD if the paper indicates that the parameter has no default.
+      A default value of zero is returned for parameters which are not
+      accessed by the projection.
+
+      Note, the FITS-WCS paper reserves parameters 1 and 2 on the longitude
+      axis to hold the native longitude and latitude of the fiducial
+      point of the projection, in degrees. The default values for these
+      parameters are determined by the projection type. The AST-specific
+      TPN projection does not use this convention - all projection
+      parameters for both axes are used to represent polynomical correction
+      terms, and the native longitude and latitude at the fiducial point may
+      not be changed from the default values of zero and 90 degrees.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The value of this attribute may changed only if the WcsMap
+         has no more than one reference. That is, an error is reported if the
+         WcsMap has been cloned, either by including it within another object
+         such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+         \htmlref{astClone}{astClone}
+         function.
+
+         \sstitem
+         If the projection parameter values given for a WcsMap do not
+         satisfy all the required constraints (as defined in the FITS-WCS
+         paper), then an error will result when the WcsMap is used to
+         transform coordinates.
+      }
+   }
+}
+\sstroutine{
+   PcdCen(axis)
+}{
+   Centre coordinates of pincushion/barrel distortion
+}{
+   \sstdescription{
+      This attribute specifies the centre of the pincushion/barrel
+      distortion implemented by a \htmlref{PcdMap}{PcdMap}. It takes a separate value for
+      each axis of the PcdMap so that, for instance, the settings
+      \texttt{"} PcdCen(1)=345.0,PcdCen(2)=-104.4\texttt{"}  specify that the pincushion
+      distortion is centred at positions of 345.0 and -104.4 on axes 1 and 2
+      respectively. This attribute is set when a PcdMap is created, but may
+      later be modified. If the attribute is cleared, the default value for
+      both axes is zero.
+
+      Note, the value of this attribute may changed only if the PcdMap
+      has no more than one reference. That is, an error is reported if the
+      PcdMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         PcdMap
+      }{
+         All PcdMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} PcdCen\texttt{"}  instead of
+         \texttt{"} PcdCen(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of both axes, while a \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}
+         operation will use just the PcdCen(1) value.
+      }
+   }
+}
+\sstroutine{
+   Permute
+}{
+   Permute axis order?
+}{
+   \sstdescription{
+      This attribute is a boolean value which controls how a \htmlref{Frame}{Frame}
+      behaves when it is used (by \htmlref{astFindFrame}{astFindFrame}) as a template to match
+      another (target) Frame. It specifies whether the axis order of
+      the target Frame may be permuted in order to obtain a match.
+
+      If the template\texttt{'} s Permute value is zero, it will match a target
+      only if it can do so without changing the order of its
+      axes. Otherwise, it will attempt to permute the target\texttt{'} s axes as
+      necessary.
+
+      The default value is 1, so that axis permutation will be attempted.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute. However, the Frame class
+         effectively ignores this attribute and behaves as if it has
+         the value 1. This is because the axes of a basic Frame are
+         not distinguishable and will always match any other Frame
+         whatever their order.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         Unlike a basic Frame, the SkyFrame class makes use of this
+         attribute.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Permute attribute of a FrameSet is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+}
+\sstroutine{
+   PolarLong
+}{
+   The longitude value to assign to either pole
+}{
+   \sstdescription{
+      This attribute holds the longitude value, in radians, to be
+      returned when a Cartesian position corresponding to either the north
+      or south pole is transformed into spherical coordinates. The
+      default value is zero.
+
+      Note, the value of this attribute may changed only if the \htmlref{SphMap}{SphMap}
+      has no more than one reference. That is, an error is reported if the
+      SphMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SphMap
+      }{
+         All SphMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   PolyTan
+}{
+   Use PVi\_m keywords to define distorted TAN projection?
+}{
+   \sstdescription{
+      This attribute is a boolean value which specifies how FITS \texttt{"} TAN\texttt{"}
+      projections should be treated when reading a \htmlref{FrameSet}{FrameSet} from a foreign
+      encoded FITS header. If zero, the projection is assumed to conform
+      to the published FITS-WCS standard. If positive, the convention
+      for a distorted TAN projection included in an early draft version
+      of FITS-WCS paper II are assumed. In this convention the
+      coefficients of a polynomial distortion to be applied to
+      intermediate world coordinates are specified by the \htmlref{PVi\_m}{PVi\_m} keywords.
+      This convention was removed from the paper before publication and so
+      does not form part of the standard. Indeed, it is incompatible with
+      the published standard because it re-defines the meaning of the
+      first five PVi\_m keywords on the longitude axis, which are reserved
+      by the published standard for other purposes. However, headers that
+      use this convention are still to be found, for instance the SCAMP
+      utility (http://www.astromatic.net/software/scamp) creates them.
+
+      The default value for the PolyTan attribute is -1. A negative
+      values causes the used convention to depend on the contents
+      of the \htmlref{FitsChan}{FitsChan}. If the FitsChan contains any PVi\_m keywords for
+      the latitude axis, or if it contains PVi\_m keywords for the
+      longitude axis with \texttt{"} m\texttt{"}  greater than 4, then the distorted TAN
+      convention is used. Otherwise, the standard convention is used.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   PreserveAxes
+}{
+   Preserve axes?
+}{
+   \sstdescription{
+      This attribute controls how a \htmlref{Frame}{Frame} behaves when it is used (by
+      \htmlref{astFindFrame}{astFindFrame}) as a template to match another (target) Frame. It
+      determines which axes appear (and in what order) in the \texttt{"} result\texttt{"}
+      Frame produced.
+
+      If PreserveAxes is zero in the template Frame, then the result
+      Frame will have the same number (and order) of axes as the
+      template. If it is non-zero, however, the axes of the target
+      Frame will be preserved, so that the result Frame will have the
+      same number (and order) of axes as the target.
+
+      The default value is zero, so that target axes are not preserved
+      and the result Frame resembles the template.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         All Frames have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The PreserveAxes attribute of a FrameSet is the same as that
+         of its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+}
+\sstroutine{
+   ProjP(m)
+}{
+   FITS-WCS projection parameters
+}{
+   \sstdescription{
+      This attribute provides aliases for the PV attributes, which
+      specifies the projection parameter values to be used by a \htmlref{WcsMap}{WcsMap}
+      when implementing a FITS-WCS sky projection. ProjP is retained for
+      compatibility with previous versions of FITS-WCS and AST. New
+      applications should use the PV attibute instead.
+
+      Attributes ProjP(0) to ProjP(9) correspond to attributes PV$<$axlat$>$\_0
+      to PV$<$axlat$>$\_9, where $<$axlat$>$ is replaced by the index of the
+      latitude axis (given by attribute WcsAxis(2)). See PV for further
+      details.
+
+      Note, the value of this attribute may changed only if the WcsMap
+      has no more than one reference. That is, an error is reported if the
+      WcsMap has been cloned, either by including it within another object
+      such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet} or by calling the
+      \htmlref{astClone}{astClone}
+      function.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Projection
+}{
+   Sky projection description
+}{
+   \sstdescription{
+      This attribute provides a place to store a description of the
+      type of sky projection used when a \htmlref{SkyFrame}{SkyFrame} is attached to a
+      2-dimensional object, such as an image or plotting surface. For
+      example, typical values might be \texttt{"} orthographic\texttt{"} , \texttt{"} Hammer-Aitoff\texttt{"}
+      or \texttt{"} cylindrical equal area\texttt{"} .
+
+      The Projection value is purely descriptive and does not affect
+      the celestial coordinate system represented by the SkyFrame in
+      any way. If it is set to a non-blank string, the description
+      provided may be used when forming the default value for the
+      SkyFrame\texttt{'} s \htmlref{Title}{Title} attribute (so that typically it will appear in
+      graphical output, for instance). The default value is an empty
+      string.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   RefCount
+}{
+   Count of active Object pointers
+}{
+   \sstdescription{
+      This attribute gives the number of active pointers associated
+      with an \htmlref{Object}{Object}. It is modified whenever pointers are created or
+      annulled (by \htmlref{astClone}{astClone}, \htmlref{astAnnul}{astAnnul} or \htmlref{astEnd}{astEnd} for example). The count
+      includes the initial pointer issued when the Object was created.
+
+      If the reference count for an Object falls to zero as the result
+      of annulling a pointer to it, then the Object will be deleted.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   RefDec
+}{
+   The declination of the reference point
+}{
+   \sstdescription{
+      This attribute specifies the FK5 J2000.0 declination of a reference
+      point on the sky. See the description of attribute \htmlref{RefRA}{RefRA} for details.
+      The default RefDec is \texttt{"} 0:0:0\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   RefRA
+}{
+   The right ascension of the reference point
+}{
+   \sstdescription{
+      This attribute, together with the \htmlref{RefDec}{RefDec} attribute, specifies the FK5
+      J2000.0 coordinates of a reference point on the sky. For 1-dimensional
+      spectra, this should normally be the position of the source. For
+      spectral data with spatial coverage (spectral cubes, etc), this should
+      be close to centre of the spatial coverage. It is used to define the
+      correction for Doppler shift to be applied when using the
+      \htmlref{astFindFrame}{astFindFrame} or \htmlref{astConvert}{astConvert}
+      method to convert between different standards of rest.
+
+      The \htmlref{SpecFrame}{SpecFrame} class assumes this velocity correction is spatially
+      invariant. If a single SpecFrame is used (for instance, as a
+      component of a \htmlref{CmpFrame}{CmpFrame}) to describe spectral values at different
+      points on the sky, then it is assumes that the doppler shift at any
+      spatial position is the same as at the reference position. The
+      maximum velocity error introduced by this assumption is of the order
+      of V$*$SIN(FOV), where FOV is the angular field of view, and V is the
+      relative velocity of the two standards of rest. As an example, when
+      correcting from the observers rest frame (i.e. the topocentric rest
+      frame) to the kinematic local standard of rest the maximum value of V
+      is about 20 km/s, so for 5 arc-minute field of view the maximum velocity
+      error introduced by the correction will be about 0.03 km/s. As another
+      example, the maximum error when correcting from the observers rest frame
+      to the local group is about 5 km/s over a 1 degree field of view.
+
+      The RefRA and RefDec attributes are stored internally in radians, but
+      are converted to and from a string for access. The format \texttt{"} hh:mm:ss.ss\texttt{"}
+      is used for RefRA, and \texttt{"} dd:mm:ss.s\texttt{"}  is used for RefDec. The methods
+      \htmlref{astSetRefPos}{astSetRefPos} and \htmlref{astGetRefPos}{astGetRefPos} may be used to access the values of
+      these attributes directly as unformatted values in radians.
+
+      The default for RefRA is \texttt{"} 0:0:0\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SpecFrame
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   RegionClass
+}{
+   The AST class name of the Region encapsulated within an Stc
+}{
+   \sstdescription{
+      This is a read-only attribute giving the AST class name of the
+      \htmlref{Region}{Region} encapsulated within an \htmlref{Stc}{Stc} (that is, the class of the Region
+      which was supplied when the Stc was created).
+   }
+   \sstattributetype{
+      String, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Stc
+      }{
+         All Stc objects this attribute.
+      }
+   }
+}
+\sstroutine{
+   Report
+}{
+   Report transformed coordinates?
+}{
+   \sstdescription{
+      This attribute controls whether coordinate values are reported
+      whenever a \htmlref{Mapping}{Mapping} is used to transform a set of points. If its
+      value is zero (the default), no report is made. However, if it
+      is non-zero, the coordinates of each point are reported (both
+      before and after transformation) by writing them to standard
+      output.
+
+      This attribute is provided as an aid to debugging, and to avoid
+      having to report values explicitly in simple programs.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         When applied to a compound Mapping (CmpMap), only the Report
+         attribute of the CmpMap, and not those of its component
+         Mappings, is used.  Coordinate information is never reported
+         for the component Mappings individually, only for the
+         complete CmpMap.
+      }
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         When applied to any Frame, the formatting capabilities of the
+         Frame (as provided by the \htmlref{astFormat}{astFormat} function) will be used to
+         format the reported coordinates.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         When applied to any FrameSet, the formatting capabilities of
+         the base and current Frames will be used (as above) to
+         individually format the input and output coordinates, as
+         appropriate. The Report attribute of a FrameSet is not itself
+         affected by selecting a new base or current Frame, but the
+         resulting formatting capabilities may be.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Unlike most other attributes, the value of the Report
+         attribute is not transferred when a Mapping is copied. Instead,
+         its value is undefined (and therefore defaults to zero) in any
+         copy. Similarly, it becomes undefined in any external
+         representation of a Mapping produced by the \htmlref{astWrite}{astWrite} function.
+      }
+   }
+}
+\sstroutine{
+   ReportLevel
+}{
+   Determines which read/write conditions are reported
+}{
+   \sstdescription{
+      This attribute determines which, if any, of the conditions that occur
+      whilst reading or writing an \htmlref{Object}{Object} should be reported. These
+      conditions will generate either a fatal error or a warning, as
+      determined by attribute \htmlref{Strict}{Strict}. ReportLevel can take any of the
+      following values:
+
+      0 - Do not report any conditions.
+
+      1 - \htmlref{Report}{Report} only conditions where significant information content has been
+      changed. For instance, an unsupported time-scale has been replaced by a
+      supported near-equivalent time-scale. Another example is if a basic
+      \htmlref{Channel}{Channel} unexpected encounters data items that may have been introduced
+      by later versions of AST.
+
+      2 - Report the above, and in addition report significant default
+      values. For instance, if no time-scale was specified when reading an
+      Object from an external data source, report the default time-scale
+      that is being used.
+
+      3 - Report the above, and in addition report any other potentially
+      interesting conditions that have no significant effect on the
+      conversion. For instance, report if a time-scale of \texttt{"} TT\texttt{"}
+      (terrestrial time) is used in place of \texttt{"} ET\texttt{"}  (ephemeris time). This
+      change has no signficiant effect because ET is the predecessor of,
+      and is continuous with, TT. Synonyms such as \texttt{"} IAT\texttt{"}  and \texttt{"} TAI\texttt{"}  are
+      another example.
+
+      The default value is 1. Note, there are many other conditions that
+      can occur whilst reading or writing an Object that completely
+      prevent the conversion taking place. Such conditions will always
+      generate errors, irrespective of the ReportLevel and Strict attributes.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         All Channels have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         All the conditions selected by the FitsChan \htmlref{Warnings}{Warnings} attribute are
+         reported at level 1.
+      }
+   }
+}
+\sstroutine{
+   RestFreq
+}{
+   The rest frequency
+}{
+   \sstdescription{
+      This attribute specifies the frequency corresponding to zero
+      velocity. It is used when converting between between velocity-based
+      coordinate systems and and other coordinate systems (such as frequency,
+      wavelength, energy, etc). The default value is 1.0E5 GHz.
+
+      When setting a new value for this attribute, the new value can be
+      supplied either directly as a frequency, or indirectly as a wavelength
+      or energy, in which case the supplied value is converted to a frequency
+      before being stored. The nature of the supplied value is indicated by
+      appending text to the end of the numerical value indicating the units in
+      which the value is supplied. If the units are not specified, then the
+      supplied value is assumed to be a frequency in units of GHz. If the
+      supplied unit is a unit of frequency, the supplied value is assumed to
+      be a frequency in the given units. If the supplied unit is a unit of
+      length, the supplied value is assumed to be a (vacuum) wavelength. If
+      the supplied unit is a unit of energy, the supplied value is assumed to
+      be an energy. For instance, the following strings all result in
+      a rest frequency of around 1.4E14 Hz being used: \texttt{"} 1.4E5\texttt{"} , \texttt{"} 1.4E14 Hz\texttt{"} ,
+      \texttt{"} 1.4E14 s$*$$*$-1\texttt{"} , \texttt{"} 1.4E5 GHz\texttt{"} , \texttt{"} 2.14E-6 m\texttt{"} , \texttt{"} 21400 Angstrom\texttt{"} , \texttt{"} 9.28E-20 J\texttt{"} ,
+      \texttt{"} 9.28E-13 erg\texttt{"} , \texttt{"} 0.58 eV\texttt{"} , etc.
+
+      When getting the value of this attribute, the returned value is
+      always a frequency in units of GHz.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   RootCorner
+}{
+   Specifies which edges of the 3D box should be annotated
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      which edges of the cube enclosing the 3D graphics space are used
+      for displaying numerical and descriptive axis labels. The attribute
+      value identifies one of the eight corners of the cube within
+      which graphics are being drawn (i.e. the cube specified by the
+      \texttt{"} graphbox\texttt{"}  parameter when \htmlref{astPlot3D}{astPlot3D}
+      was called tp create the \htmlref{Plot3D}{Plot3D}). \htmlref{Axis}{Axis} labels and tick marks will
+      be placed on the three cube edges that meet at the given corner.
+
+      The attribute value should consist of three character, each of
+      which must be either \texttt{"} U\texttt{"}  or \texttt{"} L\texttt{"} . The first character in the string
+      specifies the position of the corner on the first graphics axis.
+      If the character is \texttt{"} U\texttt{"}  then the corner is at the upper bound on the
+      first graphics axis. If it is \texttt{"} L\texttt{"} , then the corner is at the lower
+      bound on the first axis. Likewise, the second and third characters
+      in the string specify the location of the corner on the second and
+      third graphics axes.
+
+      For instance, corner \texttt{"} LLL\texttt{"}  is the corner that is at the lower bound
+      on all three graphics axes, and corner \texttt{"} ULU\texttt{"}  is at the upper bound
+      on axes 1 and 3 but at the lower bound on axis 2.
+
+      The default value is \texttt{"} LLL\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot3D
+      }{
+         All Plot3Ds have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Seed
+}{
+   Random number seed for a MathMap
+}{
+   \sstdescription{
+      This attribute, which may take any integer value, determines the
+      sequence of random numbers produced by the random number functions in
+      \htmlref{MathMap}{MathMap} expressions. It is set to an unpredictable default value when
+      a MathMap is created, so that by default each MathMap uses a different
+      set of random numbers.
+
+      If required, you may set this Seed attribute to a value of your
+      choosing in order to produce repeatable behaviour from the random
+      number functions. You may also enquire the Seed value (e.g. if an
+      initially unpredictable value has been used) and then use it to
+      reproduce the resulting sequence of random numbers, either from the
+      same MathMap or from another one.
+
+      Clearing the Seed attribute gives it a new unpredictable default
+      value.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         MathMap
+      }{
+         All MathMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SideBand
+}{
+   Indicates which sideband a dual sideband spectrum represents
+}{
+   \sstdescription{
+      This attribute indicates whether the \htmlref{DSBSpecFrame}{DSBSpecFrame} currently
+      represents its lower or upper sideband, or an offset from the local
+      oscillator frequency. When querying the current value, the returned
+      string is always one of \texttt{"} usb\texttt{"}  (for upper sideband), \texttt{"} lsb\texttt{"}  (for lower
+      sideband), or \texttt{"} lo\texttt{"}  (for offset from the local oscillator frequency).
+      When setting a new value, any of the strings \texttt{"} lsb\texttt{"} , \texttt{"} usb\texttt{"} , \texttt{"} observed\texttt{"} ,
+      \texttt{"} image\texttt{"}  or \texttt{"} lo\texttt{"}  may be supplied (case insensitive). The \texttt{"} observed\texttt{"}
+      sideband is which ever sideband (upper or lower) contains the central
+      spectral position given by attribute \htmlref{DSBCentre}{DSBCentre}, and the \texttt{"} image\texttt{"}
+      sideband is the other sideband. It is the sign of the \htmlref{IF}{IF} attribute
+      which determines if the observed sideband is the upper or lower
+      sideband. The default value for SideBand is the observed sideband.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         DSBSpecFrame
+      }{
+         All DSBSpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SimpFI
+}{
+   Forward-inverse MathMap pairs simplify?
+}{
+   \sstdescription{
+      This attribute should be set to a non-zero value if applying a
+      \htmlref{MathMap}{MathMap}\texttt{'} s forward transformation, followed immediately by the matching
+      inverse transformation will always restore the original set of
+      coordinates. It indicates that AST may replace such a sequence of
+      operations by an identity \htmlref{Mapping}{Mapping} (a \htmlref{UnitMap}{UnitMap}) if it is encountered
+      while simplifying a compound Mapping (e.g. using \htmlref{astSimplify}{astSimplify}).
+
+      By default, the SimpFI attribute is zero, so that AST will not perform
+      this simplification unless you have set SimpFI to indicate that it is
+      safe to do so.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         MathMap
+      }{
+         All MathMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For simplification to occur, the two MathMaps must be in series and
+         be identical (with textually identical transformation
+         functions). Functional equivalence is not sufficient.
+
+         \sstitem
+         The consent of both MathMaps is required before simplification can
+         take place. If either has a SimpFI value of zero, then simplification
+         will not occur.
+
+         \sstitem
+         The SimpFI attribute controls simplification only in the case where
+         a MathMap\texttt{'} s forward transformation is followed by the matching inverse
+         transformation. It does not apply if an inverse transformation is
+         followed by a forward transformation. This latter case is controlled
+         by the \htmlref{SimpIF}{SimpIF} attribute.
+
+         \sstitem
+         The \texttt{"} forward\texttt{"}  and \texttt{"} inverse\texttt{"}  transformations referred to are those
+         defined when the MathMap is created (corresponding to the \texttt{"} fwd\texttt{"}  and
+         \texttt{"} inv\texttt{"}  parameters of its constructor function). If the MathMap is
+         inverted (i.e. its \htmlref{Invert}{Invert} attribute is non-zero), then the role of the
+         SimpFI and SimpIF attributes will be interchanged.
+      }
+   }
+}
+\sstroutine{
+   SimpIF
+}{
+   Inverse-forward MathMap pairs simplify?
+}{
+   \sstdescription{
+      This attribute should be set to a non-zero value if applying a
+      \htmlref{MathMap}{MathMap}\texttt{'} s inverse transformation, followed immediately by the matching
+      forward transformation will always restore the original set of
+      coordinates. It indicates that AST may replace such a sequence of
+      operations by an identity \htmlref{Mapping}{Mapping} (a \htmlref{UnitMap}{UnitMap}) if it is encountered
+      while simplifying a compound Mapping (e.g. using \htmlref{astSimplify}{astSimplify}).
+
+      By default, the SimpIF attribute is zero, so that AST will not perform
+      this simplification unless you have set SimpIF to indicate that it is
+      safe to do so.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         MathMap
+      }{
+         All MathMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For simplification to occur, the two MathMaps must be in series and
+         be identical (with textually identical transformation
+         functions). Functional equivalence is not sufficient.
+
+         \sstitem
+         The consent of both MathMaps is required before simplification can
+         take place. If either has a SimpIF value of zero, then simplification
+         will not occur.
+
+         \sstitem
+         The SimpIF attribute controls simplification only in the case where
+         a MathMap\texttt{'} s inverse transformation is followed by the matching forward
+         transformation. It does not apply if a forward transformation is
+         followed by an inverse transformation. This latter case is controlled
+         by the \htmlref{SimpFI}{SimpFI} attribute.
+
+         \sstitem
+         The \texttt{"} forward\texttt{"}  and \texttt{"} inverse\texttt{"}  transformations referred to are those
+         defined when the MathMap is created (corresponding to the \texttt{"} fwd\texttt{"}  and
+         \texttt{"} inv\texttt{"}  parameters of its constructor function). If the MathMap is
+         inverted (i.e. its \htmlref{Invert}{Invert} attribute is non-zero), then the role of the
+         SimpFI and SimpIF attributes will be interchanged.
+      }
+   }
+}
+\sstroutine{
+   SimpVertices
+}{
+   Simplify a Polygon by transforming its vertices?
+}{
+   \sstdescription{
+      This attribute controls the behaviour of the
+      \htmlref{astSimplify}{astSimplify}
+      method when applied to a \htmlref{Polygon}{Polygon}. The simplified Polygon is created
+      by transforming the vertices from the \htmlref{Frame}{Frame} in which the Polygon
+      was originally defined into the Frame currently represented by the
+      Polygon. If SimpVertices is non-zero (the default) then this
+      simplified Polygon is returned without further checks. If SimpVertices
+      is zero, a check is made that the edges of the new Polygon do not
+      depart significantly from the edges of the original Polygon (as
+      determined by the uncertainty associated with the Polygon). This
+      could occur, for instance, if the \htmlref{Mapping}{Mapping} frrm the original to the
+      current Frame is highly non-linear. If this check fails, the
+      original unsimplified Polygon is returned without change.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Polygon
+      }{
+         All Polygons have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SinkFile
+}{
+   Output file to which to data should be written
+}{
+   \sstdescription{
+      This attribute specifies the name of a file to which the \htmlref{Channel}{Channel}
+      should write data. If specified it is used in preference to any sink
+      function specified when the Channel was created.
+
+      Assigning a new value to this attribute will cause any previously
+      opened SinkFile to be closed. The first subsequent call to
+      \htmlref{astWrite}{astWrite}
+      will attempt to open the new file (an error will be reported if the
+      file cannot be opened), and write data to it. All subsequent call to
+      astWrite
+      will write data to the new file, until the SinkFile attribute is
+      cleared or changed.
+
+      Clearing the attribute causes any open SinkFile to be closed. All
+      subsequent data writes will use the sink function specified when the
+      Channel was created, or will write to standard output if no sink
+      function was specified.
+
+      If no value has been assigned to SinkFile, a null string will be
+      returned if an attempt is made to get the attribute value.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         When the FitsChan is destroyed, any headers in the FitsChan will be
+         written out to the sink file, if one is specified (if not, the
+         sink function used when the FitsChan was created is used). The
+         sink file is a text file (not a FITS file) containing one header
+         per line.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A new SinkFile will over-write any existing file with the same
+         name unless the existing file is write protected, in which case an
+         error will be reported.
+
+         \sstitem
+         Any open SinkFile is closed when the Channel is deleted.
+
+         \sstitem
+         If the Channel is copied or dumped
+         (using \htmlref{astCopy}{astCopy} or \htmlref{astShow}{astShow})
+         the SinkFile attribute is left in a cleared state in the output
+         Channel (i.e. the value of the SinkFile attribute is not copied).
+      }
+   }
+}
+\sstroutine{
+   Size(element)
+}{
+   Character size for a Plot element
+}{
+   \sstdescription{
+      This attribute determines the character size used when drawing
+      each element of graphical output produced by a \htmlref{Plot}{Plot}. It takes a
+      separate value for each graphical element so that, for instance,
+      the setting \texttt{"} Size(title)=2.0\texttt{"}  causes the Plot title to be drawn
+      using twice the default character size.
+
+      The range of character sizes available and the appearance of the
+      resulting text is determined by the underlying graphics system.
+      The default behaviour is for all graphical elements to be drawn
+      using the default character size supplied by this graphics
+      system.
+   }
+   \sstattributetype{
+      Floating Point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of the graphical elements available, see the
+         description of the Plot class.
+
+         \sstitem
+         If no graphical element is specified, (e.g. \texttt{"} Size\texttt{"}  instead
+         of \texttt{"} Size(title)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will
+         affect the attribute value of all graphical elements, while a
+         \texttt{"} get\texttt{"}  or \texttt{"} test\texttt{"}  operation will use just the Size(TextLab)
+         value.
+      }
+   }
+}
+\sstroutine{
+   SizeGuess
+}{
+   The expected size of the KeyMap
+}{
+   \sstdescription{
+      This is attribute gives an estimate of the number of entries that
+      will be stored in the \htmlref{KeyMap}{KeyMap}. It is used to tune the internal
+      properties of the KeyMap for speed and efficiency. A larger value
+      will result in faster access at the expense of increased memory
+      requirements. However if the SizeGuess value is much larger than
+      the actual size of the KeyMap, then there will be little, if any,
+      speed gained by making the SizeGuess even larger. The default value
+      is 300.
+
+      The value of this attribute can only be changed if the KeyMap is
+      empty. Its value can be set conveniently when creating the KeyMap.
+      An error will be reported if an attempt is made to set or clear the
+      attribute when the KeyMap contains any entries.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         All KeyMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Skip
+}{
+   Skip irrelevant data?
+}{
+   \sstdescription{
+      This is a boolean attribute which indicates whether the \htmlref{Object}{Object}
+      data being read through a \htmlref{Channel}{Channel} are inter-mixed with other,
+      irrelevant, external data.
+
+      If Skip is zero (the default), then the source of input data is
+      expected to contain descriptions of AST Objects and comments and
+      nothing else (if anything else is read, an error will
+      result). If Skip is non-zero, then any non-Object data
+      encountered between Objects will be ignored and simply skipped
+      over in order to reach the next Object.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         All Channels have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         The FitsChan class sets the default value of this attribute
+         to 1, so that all irrelevant FITS headers will normally be
+         ignored.
+      }
+   }
+}
+\sstroutine{
+   SkyRef(axis)
+}{
+   Position defining the offset coordinate system
+}{
+   \sstdescription{
+      This attribute allows a \htmlref{SkyFrame}{SkyFrame} to represent offsets, rather than
+      absolute axis values, within the coordinate system specified by the
+      \htmlref{System}{System} attribute. If supplied, SkyRef should be set to hold the
+      longitude and latitude of a point within the coordinate system
+      specified by the System attribute. The coordinate system represented
+      by the SkyFrame will then be rotated in order to put the specified
+      position at either the pole or the origin of the new coordinate system
+      (as indicated by the \htmlref{SkyRefIs}{SkyRefIs} attribute). The orientation of the
+      modified coordinate system is then controlled using the SkyRefP
+      attribute.
+
+      If an integer axis index is included in the attribute name (e.g.
+      \texttt{"} SkyRef(1)\texttt{"} ) then the attribute value should be supplied as a single
+      floating point axis value, in radians, when setting a value for the
+      attribute, and will be returned in the same form when getting the value
+      of the attribute. In this case the integer axis index should be \texttt{"} 1\texttt{"}
+      or \texttt{"} 2\texttt{"}  (the values to use for longitude and latitude axes are
+      given by the \htmlref{LonAxis}{LonAxis} and \htmlref{LatAxis}{LatAxis} attributes).
+
+      If no axis index is included in the attribute name (e.g. \texttt{"} SkyRef\texttt{"} ) then
+      the attribute value should be supplied as a character string
+      containing two formatted axis values (an axis 1 value followed by a
+      comma, followed by an axis 2 value). The same form
+      will be used when getting the value of the attribute.
+
+      The default values for SkyRef are zero longitude and zero latitude.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the System attribute of the SkyFrame is changed, any position
+         given for SkyRef is transformed into the new System.
+
+         \sstitem
+         If a value has been assigned to SkyRef attribute, then
+         the default values for certain attributes are changed as follows:
+         the default axis Labels for the SkyFrame are modified by appending
+         \texttt{"}  offset\texttt{"}  to the end, the default axis Symbols for the SkyFrame are
+         modified by prepending the character \texttt{"} D\texttt{"}  to the start, and the
+         default title is modified by replacing the projection information by the
+         origin information.
+      }
+   }
+   \sstdiytopic{
+      Aligning SkyFrames with Offset Coordinate Systems
+   }{
+      The offset coordinate system within a SkyFrame should normally be
+      considered as a superficial \texttt{"} re-badging\texttt{"}  of the axes of the coordinate
+      system specified by the System attribute - it merely provides an
+      alternative numerical \texttt{"} label\texttt{"}  for each position in the System coordinate
+      system. The SkyFrame retains full knowledge of the celestial coordinate
+      system on which the offset coordinate system is based (given by the
+      System attribute). For instance, the SkyFrame retains knowledge of the
+      way that one celestial coordinate system may \texttt{"} drift\texttt{"}  with respect to
+      another over time. Normally, if you attempt to align two SkyFrames (e.g.
+      using the \htmlref{astConvert}{astConvert} or \htmlref{astFindFrame}{astFindFrame} routine),
+      the effect of any offset coordinate system defined in either SkyFrame
+      will be removed, resulting in alignment being performed in the
+      celestial coordinate system given by the \htmlref{AlignSystem}{AlignSystem} attribute.
+      However, by setting the \htmlref{AlignOffset}{AlignOffset} attribute to a non-zero value, it
+      is possible to change this behaviour so that the effect of the offset
+      coordinate system is not removed when aligning two SkyFrames.
+   }
+}
+\sstroutine{
+   SkyRefIs
+}{
+   Selects the nature of the offset coordinate system
+}{
+   \sstdescription{
+      This attribute controls how the values supplied for the SkyRef and
+      SkyRefP attributes are used. These three attributes together allow
+      a \htmlref{SkyFrame}{SkyFrame} to represent offsets relative to some specified origin
+      or pole within the coordinate system specified by the \htmlref{System}{System} attribute,
+      rather than absolute axis values. SkyRefIs can take one of the
+      case-insensitive values \texttt{"} Origin\texttt{"} , \texttt{"} Pole\texttt{"}  or \texttt{"} Ignored\texttt{"} .
+
+      If SkyRefIs is set to \texttt{"} Origin\texttt{"} , then the coordinate system
+      represented by the SkyFrame is modified to put the origin of longitude
+      and latitude at the position specified by the SkyRef attribute.
+
+      If SkyRefIs is set to \texttt{"} Pole\texttt{"} , then the coordinate system represented
+      by the SkyFrame is modified to put the north pole at the position
+      specified by the SkyRef attribute.
+
+      If SkyRefIs is set to \texttt{"} Ignored\texttt{"}  (the default), then any value set for the
+      SkyRef attribute is ignored, and the SkyFrame represents the coordinate
+      system specified by the System attribute directly without any rotation.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SkyRefP(axis)
+}{
+   Position on primary meridian of offset coordinate system
+}{
+   \sstdescription{
+      This attribute is used to control the orientation of the offset
+      coordinate system defined by attributes SkyRef and \htmlref{SkyRefIs}{SkyRefIs}. If used,
+      it should be set to hold the longitude and latitude of a point within
+      the coordinate system specified by the \htmlref{System}{System} attribute. The offset
+      coordinate system represented by the \htmlref{SkyFrame}{SkyFrame} will then be rotated in
+      order to put the position supplied for SkyRefP on the zero longitude
+      meridian. This rotation is about an axis from the centre of the
+      celestial sphere to the point specified by the SkyRef attribute.
+      The default value for SkyRefP is usually the north pole (that is, a
+      latitude of $+$90 degrees in the coordinate system specified by the System
+      attribute). The exception to this is if the SkyRef attribute is
+      itself set to either the north or south pole. In these cases the
+      default for SkyRefP is the origin (that is, a (0,0) in the coordinate
+      system specified by the System attribute).
+
+      If an integer axis index is included in the attribute name (e.g.
+      \texttt{"} SkyRefP(1)\texttt{"} ) then the attribute value should be supplied as a single
+      floating point axis value, in radians, when setting a value for the
+      attribute, and will be returned in the same form when getting the value
+      of the attribute. In this case the integer axis index should be \texttt{"} 1\texttt{"}
+      or \texttt{"} 2\texttt{"}  (the values to use for longitude and latitude axes are
+      given by the \htmlref{LonAxis}{LonAxis} and \htmlref{LatAxis}{LatAxis} attributes).
+
+      If no axis index is included in the attribute name (e.g. \texttt{"} SkyRefP\texttt{"} ) then
+      the attribute value should be supplied as a character string
+      containing two formatted axis values (an axis 1 value followed by a
+      comma, followed by an axis 2 value). The same form
+      will be used when getting the value of the attribute.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If the position given by the SkyRef attribute defines the origin
+         of the offset coordinate system (that is, if the SkyRefIs attribute
+         is set to \texttt{"} origin\texttt{"} ), then there will in general be two orientations
+         which will put the supplied SkyRefP position on the zero longitude
+         meridian. The orientation which is actually used is the one which
+         gives the SkyRefP position a positive latitude in the offset coordinate
+         system (the other possible orientation would give the SkyRefP position
+         a negative latitude).
+
+         \sstitem
+         An error will be reported if an attempt is made to use a
+         SkyRefP value which is co-incident with SkyRef or with the point
+         diametrically opposite to SkyRef on the celestial sphere. The
+         reporting of this error is deferred until the SkyRef and SkyRefP
+         attribute values are used within a calculation.
+
+         \sstitem
+         If the System attribute of the SkyFrame is changed, any position
+         given for SkyRefP is transformed into the new System.
+      }
+   }
+}
+\sstroutine{
+   SkyTol
+}{
+   The smallest significant shift in sky coordinates
+}{
+   \sstdescription{
+      This attribute indicates the accuracy of the axis values that will
+      be represented by the \htmlref{SkyFrame}{SkyFrame}. If the arc-distance between two
+      positions within the SkyFrame is smaller than the value of SkyTol,
+      then the two positions will (for the puposes indicated below) be
+      considered to be co-incident.
+
+      This value is used only when constructing the \htmlref{Mapping}{Mapping} between
+      two different SkyFrames (for instance, when calling
+      \htmlref{astConvert}{astConvert} or \htmlref{astFindFrame}{astFindFrame}).
+      If the transformation between the two SkyFrames causes positions to
+      shift by less than SkyTol arc-seconds, then the transformation is
+      replaced by a \htmlref{UnitMap}{UnitMap}.  This could in certain circumatances allow
+      major simplifications to be made to the transformation between
+      any pixel grids associated with the two SkyFrames (for instance, if
+      each SkyFrame is part of the WCS \htmlref{FrameSet}{FrameSet} associated with an image).
+
+      A common case is when two SkyFrames use the FK5 system, but have
+      slightly different \htmlref{Epoch}{Epoch} values. If the \htmlref{AlignSystem}{AlignSystem} attribute has
+      its default value of \texttt{"} ICRS\texttt{"} , then the transformation between the
+      two SkyFrames will include a very small rotation (FK5 rotates with
+      respect to ICRS as a rate of about 0.0005 arc-seconds per year). In
+      most circumstances such a small rotation is insignificant. Setting
+      SkyTol to some suitably small non-zero value will cause this
+      rotation to be ignored, allowing much simpler transformations to
+      be used.
+
+      The test to determine the shift introduced by transforming between
+      the two SkyFrames is performed by transforming a set of 14 position
+      spread evenly over the whole sky. The largest shift produced at any
+      of these 14 positions is compared to the value of SkyTol.
+
+      The SkyTol value is in units of arc-seconds, and the default value
+      is 0.001.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SkyFrame
+      }{
+         All SkyFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SortBy
+}{
+   Determines how keys are sorted in a KeyMap
+}{
+   \sstdescription{
+      This attribute determines the order in which keys are returned by the
+      \htmlref{astMapKey}{astMapKey}
+      function. It may take the following values (the default is \texttt{"} None\texttt{"} ):
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} None\texttt{"} : The keys are returned in an arbitrary order. This is the
+         fastest method as it avoids the need for a sorted list of keys to
+         be maintained and used.
+
+         \sstitem
+         \texttt{"} AgeDown\texttt{"} : The keys are returned in the order in which values were
+         stored in the \htmlref{KeyMap}{KeyMap}, with the key for the most recent value being
+         returned last. If the value of an existing entry is changed, it goes
+         to the end of the list.
+
+         \sstitem
+         \texttt{"} AgeUp\texttt{"} : The keys are returned in the order in which values were
+         stored in the KeyMap, with the key for the most recent value being
+         returned first. If the value of an existing entry is changed, it goes
+         to the top of the list.
+
+         \sstitem
+         \texttt{"} KeyAgeDown\texttt{"} : The keys are returned in the order in which they
+         were originally stored in the KeyMap, with the most recent key being
+         returned last. If the value of an existing entry is changed, its
+         position in the list does not change.
+
+         \sstitem
+         \texttt{"} KeyAgeUp\texttt{"} : The keys are returned in the order in which they
+         were originally stored in the KeyMap, with the most recent key being
+         returned first. If the value of an existing entry is changed, its
+         position in the list does not change.
+
+         \sstitem
+         \texttt{"} KeyDown\texttt{"} : The keys are returned in alphabetical order, with \texttt{"} A...\texttt{"}
+         being returned last.
+
+         \sstitem
+         \texttt{"} KeyUp\texttt{"} : The keys are returned in alphabetical order, with \texttt{"} A...\texttt{"}
+         being returned first.
+      }
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         KeyMap
+      }{
+         All KeyMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If a new value is assigned to SortBy (or if SortBy is cleared),
+         all entries currently in the KeyMap are re-sorted according to the
+         new SortBy value.
+      }
+   }
+}
+\sstroutine{
+   SourceFile
+}{
+   Input file from which to read data
+}{
+   \sstdescription{
+      This attribute specifies the name of a file from which the \htmlref{Channel}{Channel}
+      should read data. If specified it is used in preference to any source
+      function specified when the Channel was created.
+
+      Assigning a new value to this attribute will cause any previously
+      opened SourceFile to be closed. The first subsequent call to
+      \htmlref{astRead}{astRead}
+      will attempt to open the new file (an error will be reported if the
+      file cannot be opened), and read data from it. All subsequent call to
+      astRead
+      will read data from the new file, until the SourceFile attribute is
+      cleared or changed.
+
+      Clearing the attribute causes any open SourceFile to be closed. All
+      subsequent data reads will use the source function specified when the
+      Channel was created, or will read from standard input if no source
+      function was specified.
+
+      If no value has been assigned to SourceFile, a null string will be
+      returned if an attempt is made to get the attribute value.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{FitsChan}{FitsChan}
+      }{
+         In the case of a FitsChan, the specified SourceFile supplements
+         the source function specified when the FitsChan was created,
+         rather than replacing the source function. The source file
+         should be a text file (not a FITS file) containing one header per
+         line. When a value is assigned to SourceFile, the file is opened
+         and read immediately, and all headers read from the file are
+         appended to the end of any header already in the FitsChan. The file
+         is then closed. Clearing the SourceFile attribute has no further
+         effect, other than nullifying the string (i.e. the file name)
+         associated with the attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Any open SourceFile is closed when the Channel is deleted.
+
+         \sstitem
+         If the Channel is copied or dumped
+         (using \htmlref{astCopy}{astCopy} or \htmlref{astShow}{astShow})
+         the SourceFile attribute is left in a cleared state in the output
+         Channel (i.e. the value of the SourceFile attribute is not copied).
+      }
+   }
+}
+\sstroutine{
+   SourceSys
+}{
+   Spectral system in which the source velocity is stored
+}{
+   \sstdescription{
+      This attribute identifies the spectral system in which the
+      \htmlref{SourceVel}{SourceVel} attribute value (the source velocity) is supplied and
+      returned. It can be one of the following:
+
+      \sstitemlist{
+
+         \sstitem
+            \texttt{"} VRAD\texttt{"}  or \texttt{"} VRADIO\texttt{"} : Radio velocity (km/s)
+
+         \sstitem
+            \texttt{"} VOPT\texttt{"}  or \texttt{"} VOPTICAL\texttt{"} : Optical velocity (km/s)
+
+         \sstitem
+            \texttt{"} ZOPT\texttt{"}  or \texttt{"} REDSHIFT\texttt{"} : Redshift (dimensionless)
+
+         \sstitem
+            \texttt{"} BETA\texttt{"} : Beta factor (dimensionless)
+
+         \sstitem
+            \texttt{"} VELO\texttt{"}  or \texttt{"} VREL\texttt{"} : Apparent radial (\texttt{"} relativistic\texttt{"} ) velocity (km/s)
+
+      }
+      When setting a new value for the SourceVel attribute, the source
+      velocity should be supplied in the spectral system indicated
+      by this attribute. Likewise, when getting the value of the SourceVel
+      attribute, the velocity will be returned in this spectral system.
+
+      If the value of SourceSys is changed, the value stored for SourceVel
+      will be converted from the old to the new spectral systems.
+
+      The default value is \texttt{"} VELO\texttt{"}  (apparent radial velocity).
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SourceVRF
+}{
+   Rest frame in which the source velocity is stored
+}{
+   \sstdescription{
+      This attribute identifies the rest frame in which the source
+      velocity or redshift is stored (the source velocity or redshift is
+      accessed using attribute \htmlref{SourceVel}{SourceVel}). When setting a new value for the
+      SourceVel attribute, the source velocity or redshift should be supplied
+      in the rest frame indicated by this attribute. Likewise, when getting
+      the value of the SourceVel attribute, the velocity or redshift will be
+      returned in this rest frame.
+
+      If the value of SourceVRF is changed, the value stored for SourceVel
+      will be converted from the old to the new rest frame.
+
+      The values which can be supplied are the same as for the \htmlref{StdOfRest}{StdOfRest}
+      attribute (except that SourceVRF cannot be set to \texttt{"} Source\texttt{"} ). The
+      default value is \texttt{"} Helio\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SourceVel
+}{
+   The source velocity
+}{
+   \sstdescription{
+      This attribute (together with \htmlref{SourceSys}{SourceSys}, \htmlref{SourceVRF}{SourceVRF}, \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec})
+      defines the \texttt{"} Source\texttt{"}  standard of rest (see attribute \htmlref{StdOfRest}{StdOfRest}). This is
+      a rest frame which is moving towards the position given by RefRA and
+      RefDec at a  velocity given by SourceVel. A positive value means
+      the source is moving away from the observer. When a new value is
+      assigned to this attribute, the supplied value is assumed to refer
+      to the spectral system specified by the SourceSys attribute. For
+      instance, the SourceVel value may be supplied as a radio velocity, a
+      redshift, a beta factor, etc. Similarly, when the current value of
+      the SourceVel attribute is obtained, the returned value will refer
+      to the spectral system specified by the SourceSys value. If the
+      SourceSys value is changed, any value previously stored for the SourceVel
+      attribute will be changed automatically from the old spectral system
+      to the new spectral system.
+
+      When setting a value for SourceVel, the value should be supplied in the
+      rest frame specified by the SourceVRF attribute. Likewise, when getting
+      the value of SourceVel, it will be returned in the rest frame specified
+      by the SourceVRF attribute.
+
+      The default SourceVel value is zero.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         It is important to set an appropriate value for SourceVRF and
+         SourceSys before setting a value for SourceVel. If a new value is later
+         set for SourceVRF or SourceSys, the value stored for SourceVel will
+         simultaneously be changed to the new standard of rest or spectral
+         system.
+      }
+   }
+}
+\sstroutine{
+   SpecOrigin
+}{
+   The zero point for SpecFrame axis values
+}{
+   \sstdescription{
+      This specifies the origin from which all spectral values are measured.
+      The default value (zero) results in the \htmlref{SpecFrame}{SpecFrame} describing
+      absolute spectral values in the system given by the \htmlref{System}{System} attribute
+      (e.g. frequency, velocity, etc). If a SpecFrame is to be used to
+      describe offset from some origin, the SpecOrigin attribute
+      should be set to hold the required origin value. The SpecOrigin value
+      stored inside the SpecFrame structure is modified whenever SpecFrame
+      attribute values are changed so that it refers to the original spectral
+      position.
+
+      When setting a new value for this attribute, the supplied value is assumed
+      to be in the system, units and standard of rest described by the SpecFrame.
+      Likewise, when getting the value of this attribute, the value is returned
+      in the system, units and standard of rest described by the SpecFrame. If
+      any of these attributes are changed, then any previously stored SpecOrigin
+      value will also be changed so that refers to the new system, units or
+      standard of rest.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SpecFrame
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   SpecVal
+}{
+   The spectral position at which flux values are measured
+}{
+   \sstdescription{
+      This attribute specifies the spectral position (frequency, wavelength,
+      etc.), at which the values described by the \htmlref{FluxFrame}{FluxFrame} are measured.
+      It is used when determining the \htmlref{Mapping}{Mapping} between between FluxFrames.
+
+      The default value and spectral system used for this attribute are
+      both specified when the FluxFrame is created.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FluxFrame
+      }{
+         All FluxFrames have this attribute.
+      }
+   }
+}
+\sstroutine{
+   StcsArea
+}{
+   Return the CoordinateArea component when reading an STC-S document?
+}{
+   \sstdescription{
+      This is a boolean attribute which controls what is returned
+      by the
+      \htmlref{astRead}{astRead}
+      function when it is used to read from an \htmlref{StcsChan}{StcsChan}.
+      If StcsArea is set non-zero (the default), then a \htmlref{Region}{Region}
+      representing the STC CoordinateArea will be returned by
+      astRead.
+      If StcsArea is set to zero, then the STC CoordinateArea
+      will not be returned.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         StcsChan
+      }{
+         All StcsChans have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Other attributes such as \htmlref{StcsCoords}{StcsCoords} and \htmlref{StcsProps}{StcsProps} can be used to
+         specify other Objects to be returned by
+         astRead.
+         If more than one of these attributes is set non-zero, then the
+         actual \htmlref{Object}{Object} returned by
+         astRead
+         will be a \htmlref{KeyMap}{KeyMap}, containing the requested Objects. In this
+         case, the Region representing the STC CoordinateArea will be
+         stored in the returned KeyMap using the key \texttt{"} AREA\texttt{"} . If StcsArea
+         is the only attribute to be set non-zero, then the Object returned by
+         astRead
+         will be the CoordinateArea Region itself.
+
+         \sstitem
+         The class of Region used to represent the CoordinateArea for each
+         STC-S sub-phrase is determined by the first word in the
+         sub-phrase (the \texttt{"} sub-phrase identifier\texttt{"} ). The individual sub-phrase
+         Regions are combined into a single \htmlref{Prism}{Prism}, which is then simplified
+         using \htmlref{astSimplify}{astSimplify}
+         to form the returned region.
+
+         \sstitem
+         Sub-phrases that represent a single value ( that is, have
+         identifiers \texttt{"} Time\texttt{"} , \texttt{"} Position\texttt{"} , \texttt{"} Spectral\texttt{"}  or \texttt{"} Redshift\texttt{"}  ) are
+         considered to be be part of the STC CoordinateArea component.
+
+         \sstitem
+         The \htmlref{TimeFrame}{TimeFrame} used to represent a time STC-S sub-phrase will have
+         its \htmlref{TimeOrigin}{TimeOrigin} attribute set to the sub-phrase start time. If no
+         start time is specified by the sub-phrase, then the stop time will be
+         used instead. If no stop time is specified by the sub-phrase, then
+         the single time value specified in the sub-phrase will be used
+         instead. Subsequently clearing the TimeOrigin attribute (or setting
+         its value to zero) will cause the TimeFrame to reprsent absolute times.
+
+         \sstitem
+         The \htmlref{Epoch}{Epoch} attribute for the returned Region is set in the same
+         way as the TimeOrigin attribute (see above).
+      }
+   }
+}
+\sstroutine{
+   StcsCoords
+}{
+   Return the Coordinates component when reading an STC-S document?
+}{
+   \sstdescription{
+      This is a boolean attribute which controls what is returned
+      by the
+      \htmlref{astRead}{astRead}
+      function when it is used to read from an \htmlref{StcsChan}{StcsChan}.
+      If StcsCoords is set non-zero, then a \htmlref{PointList}{PointList}
+      representing the STC Coordinates will be returned by
+      astRead.
+      If StcsCoords is set to zero (the default), then the STC
+      Coordinates will not be returned.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         StcsChan
+      }{
+         All StcsChans have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Other attributes such as \htmlref{StcsArea}{StcsArea} and \htmlref{StcsProps}{StcsProps} can be used to
+         specify other Objects to be returned by
+         astRead.
+         If more than one of these attributes is set non-zero, then the
+         actual \htmlref{Object}{Object} returned by
+         astRead
+         will be a \htmlref{KeyMap}{KeyMap}, containing the requested Objects. In this
+         case, the PointList representing the STC Coordinates will be
+         stored in the returned KeyMap using the key \texttt{"} COORDS\texttt{"} . If StcsCoords
+         is the only attribute to be set non-zero, then the Object returned by
+         astRead
+         will be the Coordinates PointList itself.
+
+         \sstitem
+         The Coordinates component is specified by the additional axis
+         values embedded within the body of each STC-S sub-phrase that
+         represents an extended area. Sub-phrases that represent a single
+         value ( that is, have identifiers \texttt{"} Time\texttt{"} , \texttt{"} Position\texttt{"} , \texttt{"} Spectral\texttt{"}
+         or \texttt{"} Redshift\texttt{"}  ) are not considered to be be part of the STC
+         Coordinates component.
+
+         \sstitem
+         If the STC-S documents does not contain a Coordinates component,
+         then a NULL object pointer
+         will be returned by
+         astRead
+         if the Coordinates component is the only object being returned. If
+         other objects are also being returned (see attributes StcsProps and
+         StcsArea), then the returned KeyMap will contain a \texttt{"} COORDS\texttt{"}  key
+         only if the Coordinates component is read succesfully.
+
+         \sstitem
+         The \htmlref{TimeFrame}{TimeFrame} used to represent a time STC-S sub-phrase will have
+         its \htmlref{TimeOrigin}{TimeOrigin} attribute set to the sub-phrase start time. If no
+         start time is specified by the sub-phrase, then the stop time will be
+         used instead. If no stop time is specified by the sub-phrase, then
+         the single time value specified in the sub-phrase will be used
+         instead. Subsequently clearing the TimeOrigin attribute (or setting
+         its value to zero) will cause the TimeFrame to reprsent absolute times.
+
+         \sstitem
+         The \htmlref{Epoch}{Epoch} attribute for the returned \htmlref{Region}{Region} is set in the same
+         way as the TimeOrigin attribute (see above).
+      }
+   }
+}
+\sstroutine{
+   StcsLength
+}{
+   Controls output line length
+}{
+   \sstdescription{
+      This attribute specifies the maximum length to use when writing out
+      text through the sink function supplied when the \htmlref{StcsChan}{StcsChan} was created.
+      It is ignored if the \htmlref{Indent}{Indent} attribute is zero (in which case the text
+      supplied to the sink function can be of any length). The default value
+      is 70.
+
+      The number of characters in each string written out through the sink
+      function will not usually be greater than the value of this attribute
+      (but may be less). However, if any single word in the STC-S
+      description exceeds the specified length, then the word will be
+      written out as a single line.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         StcsChan
+      }{
+         All StcsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   StcsProps
+}{
+   Return all properties when reading an STC-S document?
+}{
+   \sstdescription{
+      This is a boolean attribute which controls what is returned
+      by the
+      \htmlref{astRead}{astRead}
+      function when it is used to read from an \htmlref{StcsChan}{StcsChan}.
+      If StcsProps is set non-zero, then a \htmlref{KeyMap}{KeyMap} containing all the
+      properties read from the STC-S document will be returned by
+      astRead.
+      If StcsProps is set to zero (the default), then the properties
+      will not be returned.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         StcsChan
+      }{
+         All StcsChans have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         Other attributes such as \htmlref{StcsCoords}{StcsCoords} and \htmlref{StcsArea}{StcsArea} can be used to
+         specify other Objects to be returned by
+         astRead.
+         If more than one of these attributes is set non-zero, then the
+         actual \htmlref{Object}{Object} returned by
+         astRead
+         will be a KeyMap containing the requested Objects. In this
+         case, the properties KeyMap will be stored in the returned KeyMap
+         using the key \texttt{"} PROPS\texttt{"} . If StcsProps is the only attribute to be
+         set non-zero, then the Object returned by
+         astRead
+         will be the properties KeyMap itself.
+
+         \sstitem
+         The KeyMap containing the properties will have entries for one or
+         more of the following keys: \texttt{"} TIME\_PROPS\texttt{"} , \texttt{"} SPACE\_PROPS\texttt{"} , \texttt{"} SPECTRAL\_PROPS\texttt{"}
+         and \texttt{"} REDSHIFT\_PROPS\texttt{"} . Each of these entries will be another KeyMap
+         containing the properties of the corresponding STC-S sub-phrase.
+      }
+   }
+}
+\sstroutine{
+   StdOfRest
+}{
+   Standard of rest
+}{
+   \sstdescription{
+      This attribute identifies the standard of rest to which the spectral
+      axis values of a \htmlref{SpecFrame}{SpecFrame} refer, and may take any of the values
+      listed in the \texttt{"} Standards of Rest\texttt{"}  section (below).
+
+      The default StdOfRest value is \texttt{"} Helio\texttt{"} .
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SpecFrame
+      }{
+         All SpecFrames have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Standards of Rest
+   }{
+      The SpecFrame class supports the following StdOfRest values (all are
+      case-insensitive):
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} Topocentric\texttt{"} , \texttt{"} Topocent\texttt{"}  or \texttt{"} Topo\texttt{"} : The observers rest-frame (assumed
+         to be on the surface of the earth). Spectra recorded in this standard of
+         rest suffer a Doppler shift which varies over the course of a day
+         because of the rotation of the observer around the axis of the earth.
+         This standard of rest must be qualified using the \htmlref{ObsLat}{ObsLat}, \htmlref{ObsLon}{ObsLon},
+         \htmlref{ObsAlt}{ObsAlt}, \htmlref{Epoch}{Epoch}, \htmlref{RefRA}{RefRA} and \htmlref{RefDec}{RefDec} attributes.
+
+         \sstitem
+         \texttt{"} Geocentric\texttt{"} , \texttt{"} Geocentr\texttt{"}  or \texttt{"} Geo\texttt{"} : The rest-frame of the earth centre.
+         Spectra recorded in this standard of rest suffer a Doppler shift which
+         varies over the course of a year because of the rotation of the earth
+         around the Sun. This standard of rest must be qualified using the Epoch,
+         RefRA and RefDec attributes.
+
+         \sstitem
+         \texttt{"} Barycentric\texttt{"} , \texttt{"} Barycent\texttt{"}  or \texttt{"} Bary\texttt{"} : The rest-frame of the solar-system
+         barycentre. Spectra recorded in this standard of rest suffer a Doppler
+         shift which depends both on the velocity of the Sun through the Local
+         Standard of Rest, and on the movement of the planets through the solar
+         system. This standard of rest must be qualified using the Epoch, RefRA
+         and RefDec attributes.
+
+         \sstitem
+         \texttt{"} Heliocentric\texttt{"} , \texttt{"} Heliocen\texttt{"}  or \texttt{"} Helio\texttt{"} : The rest-frame of the Sun.
+         Spectra recorded in this standard of rest suffer a Doppler shift which
+         depends on the velocity of the Sun through the Local Standard of Rest.
+         This standard of rest must be qualified using the RefRA and RefDec
+         attributes.
+
+         \sstitem
+         \texttt{"} LSRK\texttt{"} , \texttt{"} LSR\texttt{"} : The rest-frame of the kinematical Local Standard of
+         Rest. Spectra recorded in this standard of rest suffer a Doppler shift
+         which depends on the velocity of the kinematical Local Standard of Rest
+         through the galaxy. This standard of rest must be qualified using the
+         RefRA and RefDec attributes.
+
+         \sstitem
+         \texttt{"} LSRD\texttt{"} : The rest-frame of the dynamical Local Standard of Rest. Spectra
+         recorded in this standard of rest suffer a Doppler shift which depends
+         on the velocity of the dynamical Local Standard of Rest through the
+         galaxy.  This standard of rest must be qualified using the RefRA and
+         RefDec attributes.
+
+         \sstitem
+         \texttt{"} Galactic\texttt{"} , \texttt{"} Galactoc\texttt{"}  or \texttt{"} Gal\texttt{"} : The rest-frame of the galactic centre.
+         Spectra recorded in this standard of rest suffer a Doppler shift which
+         depends on the velocity of the galactic centre through the local group.
+         This standard of rest must be qualified using the RefRA and RefDec
+         attributes.
+
+         \sstitem
+         \texttt{"} Local\_group\texttt{"} , \texttt{"} Localgrp\texttt{"}  or \texttt{"} LG\texttt{"} : The rest-frame of the local group.
+         This standard of rest must be qualified using the RefRA and RefDec
+         attributes.
+
+         \sstitem
+         \texttt{"} Source\texttt{"} , or \texttt{"} src\texttt{"} : The rest-frame of the source. This standard of
+         rest must be qualified using the RefRA, RefDec and \htmlref{SourceVel}{SourceVel} attributes.
+
+      }
+      Where more than one alternative \htmlref{System}{System} value is shown above, the
+      first of these will be returned when an enquiry is made.
+   }
+}
+\sstroutine{
+   Strict
+}{
+   Report an error if any unexpeted data items are found?
+}{
+   \sstdescription{
+      This is a boolean attribute which indicates whether a warning
+      rather than an error should be issed for insignificant conversion
+      problems. If it is set non-zero, then fatal errors are issued
+      instead of warnings, resulting in the
+      AST error status being set.
+      If Strict is zero (the default), then execution continues after minor
+      conversion problems, and a warning message is added to the \htmlref{Channel}{Channel}
+      structure. Such messages can be retrieved using the
+      \htmlref{astWarnings}{astWarnings}
+      function.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Channel
+      }{
+         All Channels have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This attribute was introduced in AST version 5.0. Prior to this
+         version of AST unexpected data items read by a basic Channel always
+         caused an error to be reported. So applications linked against
+         versions of AST prior to version 5.0 may not be able to read \htmlref{Object}{Object}
+         descriptions created by later versions of AST, if the Object\texttt{'} s class
+         description has changed.
+      }
+   }
+}
+\sstroutine{
+   Style(element)
+}{
+   Line style for a Plot element
+}{
+   \sstdescription{
+      This attribute determines the line style used when drawing each
+      element of graphical output produced by a \htmlref{Plot}{Plot}. It takes a
+      separate value for each graphical element so that, for instance,
+      the setting \texttt{"} Style(border)=2\texttt{"}  causes the Plot border to be drawn
+      using line style 2 (which might result in, say, a dashed line).
+
+      The range of integer line styles available and their appearance
+      is determined by the underlying graphics system.  The default
+      behaviour is for all graphical elements to be drawn using the
+      default line style supplied by this graphics system (normally,
+      this is likely to give a solid line).
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of the graphical elements available, see the
+         description of the Plot class.
+
+         \sstitem
+         If no graphical element is specified, (e.g. \texttt{"} Style\texttt{"}  instead of
+         \texttt{"} Style(border)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all graphical elements, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the Style(\htmlref{Border}{Border}) value.
+      }
+   }
+}
+\sstroutine{
+   Symbol(axis)
+}{
+   Axis symbol
+}{
+   \sstdescription{
+      This attribute specifies a short-form symbol to be used to
+      represent coordinate values for a particular axis of a
+      \htmlref{Frame}{Frame}. This might be used (e.g.) in algebraic expressions where
+      a full description of the axis would be inappropriate. Examples
+      include \texttt{"} RA\texttt{"}  and \texttt{"} Dec\texttt{"}  (for Right Ascension and Declination).
+
+      If a Symbol value has not been set for a Frame axis, then a
+      suitable default is supplied.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default Symbol value supplied by the Frame class is the
+         string \texttt{"} $<$\htmlref{Domain}{Domain}$>$$<$n$>$\texttt{"} , where $<$n$>$ is 1, 2, etc. for successive
+         axes, and $<$Domain$>$ is the value of the Frame\texttt{'} s Domain
+         attribute (truncated if necessary so that the final string
+         does not exceed 15 characters). If no Domain value has been
+         set, \texttt{"} x\texttt{"}  is used as the $<$Domain$>$ value in constructing this
+         default string.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Symbol value
+         (e.g. to \texttt{"} RA\texttt{"}  or \texttt{"} Dec\texttt{"} ) as appropriate for the particular
+         celestial coordinate system being represented.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class re-defines the default Symbol value as
+         appropriate for the particular time system being represented.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Symbol attribute of a FrameSet axis is the same as that
+         of its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   System
+}{
+   Coordinate system used to describe positions within the domain
+}{
+   \sstdescription{
+      In general it is possible for positions within a given physical
+      domain to be described using one of several different coordinate
+      systems. For instance, the \htmlref{SkyFrame}{SkyFrame} class can use galactic
+      coordinates, equatorial coordinates, etc, to describe positions on
+      the sky. As another example, the \htmlref{SpecFrame}{SpecFrame} class can use frequency,
+      wavelength, velocity, etc, to describe a position within an
+      electromagnetic spectrum. The System attribute identifies the particular
+      coordinate system represented by a \htmlref{Frame}{Frame}. Each class of Frame
+      defines a set of acceptable values for this attribute, as listed
+      below (all are case insensitive). Where more than one alternative
+      System value is shown, the first of will be returned when an
+      enquiry is made.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The System attribute for a basic Frame always equals \texttt{"} Cartesian\texttt{"} ,
+         and may not be altered.
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The System attribute for a CmpFrame always equals \texttt{"} Compound\texttt{"} ,
+         and may not be altered. In addition, the CmpFrame class allows
+         the System attribute to be referenced for a component Frame by
+         including the index of an axis within the required component
+         Frame. For instance, \texttt{"} System(3)\texttt{"}  refers to the System attribute
+         of the component Frame which includes axis 3 of the CmpFrame.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The System attribute of a FrameSet is the same as that of its
+         current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+      \sstsubsection{
+         SkyFrame
+      }{
+         The SkyFrame class supports the following System values and
+         associated celestial coordinate systems:
+
+         \sstitemlist{
+
+            \sstitem
+            \texttt{"} AZEL\texttt{"} : Horizon coordinates. The longitude axis is azimuth
+            such that geographic north has an azimuth of zero and geographic
+            east has an azimuth of $+$PI/2 radians. The zenith has elevation
+            $+$PI/2. When converting to and from other celestial coordinate
+            systems, no corrections are applied for atmospheric refraction
+            or polar motion (however, a correction for diurnal aberattion is
+            applied). Note, unlike most other
+            celestial coordinate systems, this system is right handed. Also,
+            unlike other SkyFrame systems, the AzEl system is sensitive to
+            the timescale in which the \htmlref{Epoch}{Epoch} value is supplied. This is
+            because of the gross diurnal rotation which this system undergoes,
+            causing a small change in time to translate to a large rotation.
+            When converting to or from an AzEl system, the Epoch value for
+            both source and destination SkyFrames should be supplied in the
+            TDB timescale. The difference between TDB and TT is between 1
+            and 2 milliseconds, and so a TT value can usually be supplied in
+            place of a TDB value. The TT timescale is related to TAI via
+            TT = TAI $+$ 32.184 seconds.
+
+            \sstitem
+            \texttt{"} ECLIPTIC\texttt{"} : Ecliptic coordinates (IAU 1980), referred to the
+            ecliptic and mean equinox specified by the qualifying \htmlref{Equinox}{Equinox}
+            value.
+
+            \sstitem
+            \texttt{"} FK4\texttt{"} : The old FK4 (barycentric) equatorial coordinate system,
+            which should be qualified by an Equinox value. The underlying
+            model on which this is based is non-inertial and rotates slowly
+            with time, so for accurate work FK4 coordinate systems should
+            also be qualified by an Epoch value.
+
+            \sstitem
+            \texttt{"} FK4-NO-E\texttt{"}  or \texttt{"} FK4\_NO\_E\texttt{"} : The old FK4 (barycentric) equatorial
+            system but without the \texttt{"} E-terms of aberration\texttt{"}  (e.g. some radio
+            catalogues). This coordinate system should also be qualified by
+            both an Equinox and an Epoch value.
+
+            \sstitem
+            \texttt{"} FK5\texttt{"}  or \texttt{"} EQUATORIAL\texttt{"} : The modern FK5 (barycentric) equatorial
+            coordinate system. This should be qualified by an Equinox value.
+
+            \sstitem
+            \texttt{"} GALACTIC\texttt{"} : Galactic coordinates (IAU 1958).
+
+            \sstitem
+            \texttt{"} GAPPT\texttt{"} , \texttt{"} GEOCENTRIC\texttt{"}  or \texttt{"} APPARENT\texttt{"} : The geocentric apparent
+            equatorial coordinate system, which gives the apparent positions
+            of sources relative to the true plane of the Earth\texttt{'} s equator and
+            the equinox (the coordinate origin) at a time specified by the
+            qualifying Epoch value. (Note that no Equinox is needed to
+            qualify this coordinate system because no model \texttt{"} mean equinox\texttt{"}
+            is involved.)  These coordinates give the apparent right
+            ascension and declination of a source for a specified date of
+            observation, and therefore form an approximate basis for
+            pointing a telescope. Note, however, that they are applicable to
+            a fictitious observer at the Earth\texttt{'} s centre, and therefore
+            ignore such effects as atmospheric refraction and the (normally
+            much smaller) aberration of light due to the rotational velocity
+            of the Earth\texttt{'} s surface.  Geocentric apparent coordinates are
+            derived from the standard FK5 (J2000.0) barycentric coordinates
+            by taking account of the gravitational deflection of light by
+            the Sun (usually small), the aberration of light caused by the
+            motion of the Earth\texttt{'} s centre with respect to the barycentre
+            (larger), and the precession and nutation of the Earth\texttt{'} s spin
+            axis (normally larger still).
+
+            \sstitem
+            \texttt{"} HELIOECLIPTIC\texttt{"} : Ecliptic coordinates (IAU 1980), referred to the
+            ecliptic and mean equinox of J2000.0, in which an offset is added to
+            the longitude value which results in the centre of the sun being at
+            zero longitude at the date given by the Epoch attribute. Attempts to
+            set a value for the Equinox attribute will be ignored, since this
+            system is always referred to J2000.0.
+
+            \sstitem
+            \texttt{"} ICRS\texttt{"} : The Internation Celestial Reference System, realised
+            through the Hipparcos catalogue. Whilst not an equatorial system
+            by definition, the ICRS is very close to the FK5 (J2000) system
+            and is usually treated as an equatorial system. The distinction
+            between ICRS and FK5 (J2000) only becomes important when accuracies
+            of 50 milli-arcseconds or better are required. ICRS need not be
+            qualified by an Equinox value.
+
+            \sstitem
+            \texttt{"} J2000\texttt{"} : An equatorial coordinate system based on the mean
+            dynamical equator and equinox of the J2000 epoch. The dynamical
+            equator and equinox differ slightly from those used by the FK5
+            model, and so a \texttt{"} J2000\texttt{"}  SkyFrame will differ slightly from an
+            \texttt{"} FK5(Equinox=J2000)\texttt{"}  SkyFrame. The J2000 System need not be
+            qualified by an Equinox value
+
+            \sstitem
+            \texttt{"} SUPERGALACTIC\texttt{"} : De Vaucouleurs Supergalactic coordinates.
+
+            \sstitem
+            \texttt{"} UNKNOWN\texttt{"} : Any other general spherical coordinate system. No
+            \htmlref{Mapping}{Mapping} can be created between a pair of SkyFrames if either of the
+            SkyFrames has System set to \texttt{"} Unknown\texttt{"} .
+
+         }
+         Currently, the default System value is \texttt{"} ICRS\texttt{"} . However, this
+         default may change in future as new astrometric standards
+         evolve. The intention is to track the most modern appropriate
+         standard. For this reason, you should use the default only if
+         this is what you intend (and can tolerate any associated slight
+         change in future). If you intend to use the ICRS system
+         indefinitely, then you should specify it explicitly.
+      }
+      \sstsubsection{
+         SpecFrame
+      }{
+         The SpecFrame class supports the following System values and
+         associated spectral coordinate systems (the default is \texttt{"} WAVE\texttt{"}  -
+         wavelength). They are all defined in FITS-WCS paper III:
+
+         \sstitemlist{
+
+            \sstitem
+            \texttt{"} FREQ\texttt{"} : Frequency (GHz)
+
+            \sstitem
+            \texttt{"} ENER\texttt{"}  or \texttt{"} ENERGY\texttt{"} : Energy (J)
+
+            \sstitem
+            \texttt{"} WAVN\texttt{"}  or \texttt{"} WAVENUM\texttt{"} : Wave-number (1/m)
+
+            \sstitem
+            \texttt{"} WAVE\texttt{"}  or \texttt{"} WAVELEN\texttt{"} : Vacuum wave-length (Angstrom)
+
+            \sstitem
+            \texttt{"} AWAV\texttt{"}  or \texttt{"} AIRWAVE\texttt{"} : Wave-length in air (Angstrom)
+
+            \sstitem
+            \texttt{"} VRAD\texttt{"}  or \texttt{"} VRADIO\texttt{"} : Radio velocity (km/s)
+
+            \sstitem
+            \texttt{"} VOPT\texttt{"}  or \texttt{"} VOPTICAL\texttt{"} : Optical velocity (km/s)
+
+            \sstitem
+            \texttt{"} ZOPT\texttt{"}  or \texttt{"} REDSHIFT\texttt{"} : Redshift (dimensionless)
+
+            \sstitem
+            \texttt{"} BETA\texttt{"} : Beta factor (dimensionless)
+
+            \sstitem
+            \texttt{"} VELO\texttt{"}  or \texttt{"} VREL\texttt{"} : Apparent radial (\texttt{"} relativistic\texttt{"} ) velocity (km/s)
+
+         }
+         The default value for the Unit attribute for each system is shown
+         in parentheses. Note that the default value for the ActiveUnit flag
+         is non-zero
+         for a SpecFrame, meaning that changes to the Unit attribute for
+         a SpecFrame will result in the SpecFrame being re-mapped within
+         its enclosing FrameSet in order to reflect the change in units
+         (see \htmlref{astSetActiveUnit}{astSetActiveUnit} function for further information).
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class supports the following System values and
+         associated coordinate systems (the default is \texttt{"} MJD\texttt{"} ):
+
+         \sstitemlist{
+
+            \sstitem
+            \texttt{"} MJD\texttt{"} : Modified Julian Date (d)
+
+            \sstitem
+            \texttt{"} JD\texttt{"} : Julian Date (d)
+
+            \sstitem
+            \texttt{"} JEPOCH\texttt{"} : Julian epoch (yr)
+
+            \sstitem
+            \texttt{"} BEPOCH\texttt{"} : Besselian (yr)
+
+         }
+         The default value for the Unit attribute for each system is shown
+         in parentheses. Strictly, these systems should not allow changes
+         to be made to the units. For instance, the usual definition of
+         \texttt{"} MJD\texttt{"}  and \texttt{"} JD\texttt{"}  include the statement that the values will be in
+         units of days. However, AST does allow the use of other units
+         with all the above supported systems (except BEPOCH), on the
+         understanding that conversion to the \texttt{"} correct\texttt{"}  units involves
+         nothing more than a simple scaling (1 yr = 365.25 d, 1 d = 24 h,
+         1 h = 60 min, 1 min = 60 s). Besselian epoch values are defined
+         in terms of tropical years of 365.2422 days, rather than the
+         usual Julian year of 365.25 days. Therefore, to avoid any
+         confusion, the Unit attribute is automatically cleared to \texttt{"} yr\texttt{"}  when
+         a System value of BEPOCH System is selected, and an error is
+         reported if any attempt is subsequently made to change the Unit
+         attribute.
+
+         Note that the default value for the ActiveUnit flag
+         is non-zero
+         for a TimeFrame, meaning that changes to the Unit attribute for
+         a TimeFrame will result in the TimeFrame being re-mapped within
+         its enclosing FrameSet in order to reflect the change in units
+         (see astSetActiveUnit function for further information).
+      }
+      \sstsubsection{
+         \htmlref{FluxFrame}{FluxFrame}
+      }{
+         The FluxFrame class supports the following System values and
+         associated systems for measuring observed value:
+
+         \sstitemlist{
+
+            \sstitem
+            \texttt{"} FLXDN\texttt{"} : Flux per unit frequency (W/m$\wedge$2/Hz)
+
+            \sstitem
+            \texttt{"} FLXDNW\texttt{"} : Flux per unit wavelength (W/m$\wedge$2/Angstrom)
+
+            \sstitem
+            \texttt{"} SFCBR\texttt{"} : Surface brightness in frequency units (W/m$\wedge$2/Hz/arcmin$*$$*$2)
+
+            \sstitem
+            \texttt{"} SFCBRW\texttt{"} : Surface brightness in wavelength units (W/m$\wedge$2/Angstrom/arcmin$*$$*$2)
+
+         }
+         The above lists specified the default units for each System. If an
+         explicit value is set for the Unit attribute but no value is set
+         for System, then the default System value is determined by the Unit
+         string (if the units are not appropriate for describing any of the
+         supported Systems then an error will be reported when an attempt is
+         made to access the System value). If no value has been specified for
+         either Unit or System, then System=FLXDN and Unit=W/m$\wedge$2/Hz are
+         used.
+      }
+   }
+}
+\sstroutine{
+   TabOK
+}{
+   Should the FITS-WCS -TAB algorithm be recognised?
+}{
+   \sstdescription{
+      This attribute is an integer value which indicates if the \texttt{"} -TAB\texttt{"}
+      algorithm, defined in FITS-WCS paper III, should be supported by
+      the \htmlref{FitsChan}{FitsChan}. The default value is zero. A zero or negative value
+      results in no support for -TAB axes (i.e. axes that have \texttt{"} -TAB\texttt{"}
+      in their CTYPE keyword value). In this case, the
+      \htmlref{astWrite}{astWrite}
+      method will return zero if the write operation would required the
+      use of the -TAB algorithm, and the
+      \htmlref{astRead}{astRead}
+      method will return
+      a NULL pointer
+      if any axis in the supplied header uses the -TAB algorithm.
+
+      If TabOK is set to a non-zero positive integer, these methods will
+      recognise and convert axes described by the -TAB algorithm, as
+      follows:
+
+      The astWrite
+      method will generate headers that use the -TAB algorithm (if
+      possible) if no other known FITS-WCS algorithm can be used to
+      describe the supplied \htmlref{FrameSet}{FrameSet}. This will result in a table of
+      coordinate values and index vectors being stored in the FitsChan.
+      After the write operation, the calling application should check to
+      see if such a table has been stored in the FitsChan. If so, the
+      table should be retrived from the FitsChan using the
+      \htmlref{astGetTables}{astGetTables}
+      method, and the data (and headers) within it copied into a new
+      FITS binary table extension. See
+      astGetTables
+      for more information. The FitsChan uses a \htmlref{FitsTable}{FitsTable} object to store
+      the table data and headers. This FitsTable will contain the required
+      columns and headers as described by FITS-WCS paper III - the
+      coordinates array will be in a column named \texttt{"} COORDS\texttt{"} , and the index
+      vector(s) will be in columns named \texttt{"} INDEX$<$i$>$\texttt{"}  (where $<$i$>$ is the index
+      of the corresponding FITS WCS  axis). Note, index vectors are only
+      created if required. The EXTNAME value will be set to the value of the
+      AST\_\_TABEXTNAME constant (currently \texttt{"} WCS-TAB\texttt{"} ). The EXTVER header
+      will be set to the positive integer value assigned to the TabOK
+      attribute. No value will be stored for the EXTLEVEL header, and should
+      therefore be considered to default to 1.
+
+      The astRead
+      method will generate a FrameSet from headers that use the -TAB
+      algorithm so long as the necessary FITS binary tables are made
+      available. There are two ways to do this: firstly, if the application
+      knows which FITS binary tables will be needed, then it can create a
+      Fitstable describing each such table and store it in the FitsChan
+      (using method
+      \htmlref{astPutTables}{astPutTables} or \htmlref{astPutTable}{astPutTable}) before invoking the astRead method.
+      Secondly, if the application does not know which FITS binary tables
+      will be needed by
+      astRead,
+      then it can register a call-back function with the FitsChan using
+      method
+      \htmlref{astTableSource}{astTableSource}.
+      This call-back function will be called from within
+      astRead
+      if and when a -TAB header is encountered. When called, its arguments
+      will give the name, version and level of the FITS extension containing
+      a required table. The call-back function should read this table from
+      an external FITS file, and create a corresponding FitsTable which
+      it should then return to
+      astRead. Note, currently astRead
+      can only handle -TAB headers that describe 1-dimensional (i.e.
+      separable) axes.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   TextLab(axis)
+}{
+   Draw descriptive axis labels for a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether textual labels should be drawn to describe the quantity
+      being represented on each axis of a \htmlref{Plot}{Plot}. It takes a separate
+      value for each physical axis of a Plot so that, for instance,
+      the setting \texttt{"} TextLab(2)=1\texttt{"}  specifies that descriptive labels
+      should be drawn for the second axis.
+
+      If the TextLab value of a Plot axis is non-zero, then
+      descriptive labels will be drawn for that axis, otherwise they
+      will be omitted. The default behaviour is to draw descriptive
+      labels if tick marks and numerical labels are being drawn around
+      the edges of the plotting area (see the \htmlref{Labelling}{Labelling} attribute),
+      but to omit them otherwise.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The text used for the descriptive labels is derived from the
+         Plot\texttt{'} s \htmlref{Label(axis)}{Label(axis)} attribute, together with its \htmlref{Unit(axis)}{Unit(axis)}
+         attribute if appropriate (see the \htmlref{LabelUnits(axis)}{LabelUnits(axis)} attribute).
+
+         \sstitem
+         The drawing of numerical axis labels for a Plot (which
+         indicate values on the axis) is controlled by the \htmlref{NumLab(axis)}{NumLab(axis)}
+         attribute.
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} TextLab\texttt{"}  instead of
+         \texttt{"} TextLab(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the TextLab(1) value.
+      }
+   }
+}
+\sstroutine{
+   TextLabGap(axis)
+}{
+   Spacing of descriptive axis labels for a Plot
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      where descriptive axis labels are placed relative to the axes they
+      describe.  It takes a separate value for each physical axis of a
+      \htmlref{Plot}{Plot} so that, for instance, the setting \texttt{"} TextLabGap(2)=0.01\texttt{"}
+      specifies where the descriptive label for the second axis should
+      be drawn.
+
+      For each axis, the TextLabGap value gives the spacing between the
+      descriptive label and the edge of a box enclosing all other parts
+      of the annotated grid (excluding other descriptive labels). The gap
+      is measured to the nearest edge of the label (i.e. the top or the
+      bottom). Positive values cause the descriptive label to be placed
+      outside the bounding box, while negative values cause it to be placed
+      inside.
+
+      The TextLabGap value should be given as a fraction of the minimum
+      dimension of the plotting area, the default value being $+$0.01.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If drawn, descriptive labels are always placed at the edges of
+         the plotting area, even although the corresponding numerical
+         labels may be drawn along axis lines in the interior of the
+         plotting area (see the \htmlref{Labelling}{Labelling} attribute).
+
+         \sstitem
+         If no axis is specified, (e.g. \texttt{"} TextLabGap\texttt{"}  instead of
+         \texttt{"} TextLabGap(2)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all the Plot axes, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the TextLabGap(1) value.
+      }
+   }
+}
+\sstroutine{
+   TickAll
+}{
+   Draw tick marks on all edges of a Plot?
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      whether tick marks should be drawn on all edges of a \htmlref{Plot}{Plot}.
+
+      If the TickAll value of a Plot is non-zero (the default), then
+      tick marks will be drawn on all edges of the Plot. Otherwise,
+      they will be drawn only on those edges where the numerical and
+      descriptive axis labels are drawn (see the \htmlref{Edge(axis)}{Edge(axis)}
+      attribute).
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         In some circumstances, numerical labels and tick marks are
+         drawn along grid lines inside the plotting area, rather than
+         around its edges (see the \htmlref{Labelling}{Labelling} attribute).  In this case,
+         the value of the TickAll attribute is ignored.
+      }
+   }
+}
+\sstroutine{
+   TimeOrigin
+}{
+   The zero point for TimeFrame axis values
+}{
+   \sstdescription{
+      This specifies the origin from which all time values are measured.
+      The default value (zero) results in the \htmlref{TimeFrame}{TimeFrame} describing
+      absolute time values in the system given by the \htmlref{System}{System} attribute
+      (e.g. MJD, Julian epoch, etc). If a TimeFrame is to be used to
+      describe elapsed time since some origin, the TimeOrigin attribute
+      should be set to hold the required origin value. The TimeOrigin value
+      stored inside the TimeFrame structure is modified whenever TimeFrame
+      attribute values are changed so that it refers to the original moment
+      in time.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         TimeFrame
+      }{
+         All TimeFrames have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Input Formats
+   }{
+      The formats accepted when setting a TimeOrigin value are listed
+      below. They are all case-insensitive and are generally tolerant
+      of extra white space and alternative field delimiters:
+
+      \sstitemlist{
+
+         \sstitem
+         Besselian \htmlref{Epoch}{Epoch}: Expressed in decimal years, with or without
+         decimal places (\texttt{"} B1950\texttt{"}  or \texttt{"} B1976.13\texttt{"}  for example).
+
+         \sstitem
+         Julian Epoch: Expressed in decimal years, with or without
+         decimal places (\texttt{"} J2000\texttt{"}  or \texttt{"} J2100.9\texttt{"}  for example).
+
+         \sstitem
+         Units: An unqualified decimal value is interpreted as a value in
+         the system specified by the TimeFrame\texttt{'} s System attribute, in the
+         units given by the TimeFrame\texttt{'} s Unit attribute. Alternatively, an
+         appropriate unit string can be appended to the end of the floating
+         point value (\texttt{"} 123.4 d\texttt{"}  for example), in which case the supplied value
+         is scaled into the units specified by the Unit attribute.
+
+         \sstitem
+         Julian Date: With or without decimal places (\texttt{"} JD 2454321.9\texttt{"}  for
+         example).
+
+         \sstitem
+         Modified Julian Date: With or without decimal places
+         (\texttt{"} MJD 54321.4\texttt{"}  for example).
+
+         \sstitem
+         Gregorian Calendar Date: With the month expressed either as an
+         integer or a 3-character abbreviation, and with optional decimal
+         places to represent a fraction of a day (\texttt{"} 1996-10-2\texttt{"}  or
+         \texttt{"} 1996-Oct-2.6\texttt{"}  for example). If no fractional part of a day is
+         given, the time refers to the start of the day (zero hours).
+
+         \sstitem
+         Gregorian Date and Time: Any calendar date (as above) but with
+         a fraction of a day expressed as hours, minutes and seconds
+         (\texttt{"} 1996-Oct-2 12:13:56.985\texttt{"}  for example). The date and time can be
+         separated by a space or by a \texttt{"} T\texttt{"}  (as used by ISO8601 format).
+      }
+   }
+   \sstdiytopic{
+      Output Format
+   }{
+      When enquiring TimeOrigin values, the returned formatted floating
+      point value represents a value in the TimeFrame\texttt{'} s System, in the unit
+      specified by the TimeFrame\texttt{'} s Unit attribute.
+   }
+}
+\sstroutine{
+   TimeScale
+}{
+   Time scale
+}{
+   \sstdescription{
+      This attribute identifies the time scale to which the time axis values
+      of a \htmlref{TimeFrame}{TimeFrame} refer, and may take any of the values listed in the
+      \texttt{"} Time Scales\texttt{"}  section (below).
+
+      The default TimeScale value depends on the current \htmlref{System}{System} value; if
+      the current TimeFrame system is \texttt{"} Besselian epoch\texttt{"}  the default is
+      \texttt{"} TT\texttt{"} , otherwise it is \texttt{"} TAI\texttt{"} . Note, if the System attribute is set
+      so that the TimeFrame represents Besselian \htmlref{Epoch}{Epoch}, then an error
+      will be reported if an attempt is made to set the TimeScale to
+      anything other than TT.
+
+      Note, the supported time scales fall into two groups. The first group
+      containing  UT1, GMST, LAST and LMST define time in terms of the
+      orientation of the earth. The second group (containing all the remaining
+      time scales) define time in terms of an atomic process. Since the rate of
+      rotation of the earth varies in an unpredictable way, conversion between
+      two timescales in different groups relies on a value being supplied for
+      the \htmlref{Dut1}{Dut1} attribute (defined by the parent \htmlref{Frame}{Frame} class). This attribute
+      specifies the difference between the UT1 and UTC time scales, in seconds,
+      and defaults to zero. See the documentation for the Dut1 attribute for
+      further details.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         TimeFrame
+      }{
+         All TimeFrames have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Time Scales
+   }{
+      The TimeFrame class supports the following TimeScale values (all are
+      case-insensitive):
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} TAI\texttt{"}  - International Atomic Time
+
+         \sstitem
+         \texttt{"} UTC\texttt{"}  - Coordinated Universal Time
+
+         \sstitem
+         \texttt{"} UT1\texttt{"}  - Universal Time
+
+         \sstitem
+         \texttt{"} GMST\texttt{"}  - Greenwich Mean Sidereal Time
+
+         \sstitem
+         \texttt{"} LAST\texttt{"}  - Local Apparent Sidereal Time
+
+         \sstitem
+         \texttt{"} LMST\texttt{"}  - Local Mean Sidereal Time
+
+         \sstitem
+         \texttt{"} TT\texttt{"}  - Terrestrial Time
+
+         \sstitem
+         \texttt{"} TDB\texttt{"}  - Barycentric Dynamical Time
+
+         \sstitem
+         \texttt{"} TCB\texttt{"}  - Barycentric Coordinate Time
+
+         \sstitem
+         \texttt{"} TCG\texttt{"}  - Geocentric Coordinate Time
+
+         \sstitem
+         \texttt{"} LT\texttt{"}  - Local Time (the offset from UTC is given by attribute \htmlref{LTOffset}{LTOffset})
+
+      }
+      An very informative description of these and other time scales is
+      available at http://www.ucolick.org/$\sim$sla/leapsecs/timescales.html.
+   }
+   \sstdiytopic{
+      UTC \htmlref{Warnings}{Warnings}
+   }{
+      UTC should ideally be expressed using separate hours, minutes and
+      seconds fields (or at least in seconds for a given date) if leap seconds
+      are to be taken into account. Since the TimeFrame class represents
+      each moment in time using a single floating point number (the axis value)
+      there will be an ambiguity during a leap second. Thus an error of up to
+      1 second can result when using AST to convert a UTC time to another
+      time scale if the time occurs within a leap second. Leap seconds
+      occur at most twice a year, and are introduced to take account of
+      variation in the rotation of the earth. The most recent leap second
+      occurred on 1st January 1999. Although in the vast majority of cases
+      leap second ambiguities won\texttt{'} t matter, there are potential problems in
+      on-line data acquisition systems and in critical applications involving
+      taking the difference between two times.
+   }
+}
+\sstroutine{
+   Title
+}{
+   Frame title
+}{
+   \sstdescription{
+      This attribute holds a string which is used as a title in (e.g.)
+      graphical output to describe the coordinate system which a \htmlref{Frame}{Frame}
+      represents. Examples might be \texttt{"} Detector Coordinates\texttt{"}  or
+      \texttt{"} Galactic Coordinates\texttt{"} .
+
+      If a Title value has not been set for a Frame, then a suitable
+      default is supplied, depending on the class of the Frame.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default supplied by the Frame class is \texttt{"} $<$n$>$-d coordinate
+         system\texttt{"} , where $<$n$>$ is the number of Frame axes (\htmlref{Naxes}{Naxes}
+         attribute).
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The CmpFrame class re-defines the default Title value to be
+         \texttt{"} $<$n$>$-d compound coordinate system\texttt{"} , where $<$n$>$ is the number
+         of CmpFrame axes (Naxes attribute).
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Title attribute of a FrameSet is the same as that of its
+         current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A Frame\texttt{'} s Title is intended purely for interpretation by human
+         readers and not by software.
+      }
+   }
+}
+\sstroutine{
+   TitleGap
+}{
+   Vertical spacing for a Plot title
+}{
+   \sstdescription{
+      This attribute controls the appearance of an annotated
+      coordinate grid (drawn with the \htmlref{astGrid}{astGrid} function) by determining
+      where the title of a \htmlref{Plot}{Plot} is drawn.
+
+      Its value gives the spacing between the bottom edge of the title
+      and the top edge of a bounding box containing all the other parts
+      of the annotated grid. Positive values cause the title to be
+      drawn outside the box, while negative values cause it to be drawn
+      inside.
+
+      The TitleGap value should be given as a fraction of the minimum
+      dimension of the plotting area, the default value being $+$0.05.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{Plot3D}{Plot3D}
+      }{
+         The Plot3D class ignores this attributes since it does not draw
+         a \htmlref{Title}{Title}.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The text used for the title is obtained from the Plot\texttt{'} s Title
+         attribute.
+      }
+   }
+}
+\sstroutine{
+   Tol
+}{
+   Plotting tolerance
+}{
+   \sstdescription{
+      This attribute specifies the plotting tolerance (or resolution)
+      to be used for the graphical output produced by a \htmlref{Plot}{Plot}.  Smaller
+      values will result in smoother and more accurate curves being
+      drawn, but may slow down the plotting process. Conversely,
+      larger values may speed up the plotting process in cases where
+      high resolution is not required.
+
+      The Tol value should be given as a fraction of the minimum
+      dimension of the plotting area, and should lie in the range
+      from 1.0e-7 to 1.0. By default, a value of 0.01 is used.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+}
+\sstroutine{
+   TolInverse
+}{
+   Target relative error for the iterative inverse transformation
+}{
+   \sstdescription{
+      This attribute controls the iterative inverse transformation
+      used if the \htmlref{IterInverse}{IterInverse} attribute is non-zero.
+
+      Its value gives the target relative error in teh axis values of
+      each transformed position. Further iterations will be performed
+      until the target relative error is reached, or the maximum number
+      of iterations given by attribute \htmlref{NiterInverse}{NiterInverse} is reached.
+
+      The default value is 1.0E-6.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{PolyMap}{PolyMap}
+      }{
+         All PolyMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Top(axis)
+}{
+   Highest axis value to display
+}{
+   \sstdescription{
+      This attribute gives the highest axis value to be displayed (for
+      instance, by the \htmlref{astGrid}{astGrid} method).
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Frame}{Frame}
+      }{
+         The default supplied by the Frame class is to display all axis
+         values, without any limit.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Top value to $+$90 degrees
+         for latitude axes, and 180 degrees for co-latitude axes. The
+         default for longitude axes is to display all axis values.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   TranForward
+}{
+   Forward transformation defined?
+}{
+   \sstdescription{
+      This attribute indicates whether a \htmlref{Mapping}{Mapping} is able to transform
+      coordinates in the \texttt{"} forward\texttt{"}  direction (i.e. converting input
+      coordinates into output coordinates). If this attribute is
+      non-zero, the forward transformation is available. Otherwise, it
+      is not.
+   }
+   \sstattributetype{
+      Integer (boolean), read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         The TranForward attribute value for a CmpMap is given by the
+         boolean AND of the value for each component Mapping.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The TranForward attribute of a FrameSet applies to the
+         transformation which converts between the FrameSet\texttt{'} s base
+         \htmlref{Frame}{Frame} and its current Frame (as specified by the \htmlref{Base}{Base} and
+         \htmlref{Current}{Current} attributes). This value is given by the boolean AND
+         of the TranForward values which apply to each of the
+         individual sub-Mappings required to perform this conversion.
+         The TranForward attribute value for a FrameSet may therefore
+         change if a new Base or Current Frame is selected.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error will result if a Mapping with a TranForward value of
+         zero is used to transform coordinates in the forward direction.
+      }
+   }
+}
+\sstroutine{
+   TranInverse
+}{
+   Inverse transformation defined?
+}{
+   \sstdescription{
+      This attribute indicates whether a \htmlref{Mapping}{Mapping} is able to transform
+      coordinates in the \texttt{"} inverse\texttt{"}  direction (i.e. converting output
+      coordinates back into input coordinates). If this attribute is
+      non-zero, the inverse transformation is available. Otherwise, it
+      is not.
+   }
+   \sstattributetype{
+      Integer (boolean), readonly.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Mapping
+      }{
+         All Mappings have this attribute.
+      }
+      \sstsubsection{
+         \htmlref{CmpMap}{CmpMap}
+      }{
+         The TranInverse attribute value for a CmpMap is given by the
+         boolean AND of the value for each component Mapping.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The TranInverse attribute of a FrameSet applies to the
+         transformation which converts between the FrameSet\texttt{'} s current
+         \htmlref{Frame}{Frame} and its base Frame (as specified by the \htmlref{Current}{Current} and
+         \htmlref{Base}{Base} attributes). This value is given by the boolean AND of
+         the TranInverse values which apply to each of the individual
+         sub-Mappings required to perform this conversion.
+         The TranInverse attribute value for a FrameSet may therefore
+         change if a new Base or Current Frame is selected.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         An error will result if a Mapping with a TranInverse value of
+         zero is used to transform coordinates in the inverse direction.
+      }
+   }
+}
+\sstroutine{
+   Unit(axis)
+}{
+   Physical units for formatted axis values
+}{
+   \sstdescription{
+      This attribute contains a textual representation of the physical
+      units used to represent formatted coordinate values on a particular
+      axis of a \htmlref{Frame}{Frame}.
+      The \htmlref{astSetActiveUnit}{astSetActiveUnit} function controls how the Unit values
+      are used.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Frame
+      }{
+         The default supplied by the Frame class is an empty string.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         The SkyFrame class re-defines the default Unit value (e.g. to
+         \texttt{"} hh:mm:ss.sss\texttt{"} ) to describe the character string returned by
+         the \htmlref{astFormat}{astFormat} function when formatting coordinate values.
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         The SpecFrame class re-defines the default Unit value so that it
+         is appropriate for the current \htmlref{System}{System} value. See the System
+         attribute for details. An error will be reported if an attempt
+         is made to use an inappropriate Unit.
+      }
+      \sstsubsection{
+         \htmlref{TimeFrame}{TimeFrame}
+      }{
+         The TimeFrame class re-defines the default Unit value so that it
+         is appropriate for the current System value. See the System
+         attribute for details. An error will be reported if an attempt
+         is made to use an inappropriate Unit (e.g. \texttt{"} km\texttt{"} ).
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The Unit attribute of a FrameSet axis is the same as that of
+         its current Frame (as specified by the \htmlref{Current}{Current} attribute).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This attribute described the units used when an axis value is
+         formatted into a string using
+         astFormat.
+         In some cases these units may be different to those used to represent
+         floating point axis values within application code (for instance a
+         SkyFrame always uses radians to represent floating point axis values).
+         The InternalUnit attribute described the units used for floating
+         point values.
+
+         \sstitem
+         When specifying this attribute by name, it should be
+         subscripted with the number of the Frame axis to which it
+         applies.
+      }
+   }
+}
+\sstroutine{
+   UnitRadius
+}{
+   SphMap input vectors lie on a unit sphere?
+}{
+   \sstdescription{
+      This is a boolean attribute which indicates whether the
+      3-dimensional vectors which are supplied as input to a \htmlref{SphMap}{SphMap}
+      are known to always have unit length, so that they lie on a unit
+      sphere centred on the origin.
+
+      If this condition is true (indicated by setting UnitRadius
+      non-zero), it implies that a \htmlref{CmpMap}{CmpMap} which is composed of a
+      SphMap applied in the forward direction followed by a similar
+      SphMap applied in the inverse direction may be simplified
+      (e.g. by \htmlref{astSimplify}{astSimplify}) to become a \htmlref{UnitMap}{UnitMap}. This is because the
+      input and output vectors will both have unit length and will
+      therefore have the same coordinate values.
+
+      If UnitRadius is zero (the default), then although the output
+      vector produced by the CmpMap (above) will still have unit
+      length, the input vector may not have. This will, in general,
+      change the coordinate values, so it prevents the pair of SphMaps
+      being simplified.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         SphMap
+      }{
+         All SphMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This attribute is intended mainly for use when SphMaps are
+         involved in a sequence of Mappings which project (e.g.) a
+         dataset on to the celestial sphere. By regarding the celestial
+         sphere as a unit sphere (and setting UnitRadius to be non-zero)
+         it becomes possible to cancel the SphMaps present, along with
+         associated sky projections, when two datasets are aligned using
+         celestial coordinates. This often considerably improves
+         performance.
+
+         \sstitem
+         Such a situations often arises when interpreting FITS data and
+         is handled automatically by the \htmlref{FitsChan}{FitsChan} class.
+
+         \sstitem
+         The value of the UnitRadius attribute is used only to control
+         the simplification of Mappings and has no effect on the value of
+         the coordinates transformed by a SphMap. The lengths of the
+         input 3-dimensional Cartesian vectors supplied are always
+         ignored, even if UnitRadius is non-zero.
+
+         \sstitem
+         The value of this attribute may changed only if the SphMap
+         has no more than one reference. That is, an error is reported if the
+         SphMap has been cloned, either by including it within another object
+         such as a CmpMap or \htmlref{FrameSet}{FrameSet} or by calling the
+         \htmlref{astClone}{astClone}
+         function.
+      }
+   }
+}
+\sstroutine{
+   UseDefs
+}{
+   Use default values for unspecified attributes?
+}{
+   \sstdescription{
+      This attribute specifies whether default values should be used
+      internally for object attributes which have not been assigned a
+      value explicitly. If a non-zero value (the default) is supplied for
+      UseDefs, then default values will be used for attributes which have
+      not explicitly been assigned a value. If zero is supplied for UseDefs,
+      then an error will be reported if an attribute for which no explicit
+      value has been supplied is needed internally within AST.
+
+      Many attributes (including the UseDefs attribute itself) are unaffected
+      by the setting of the UseDefs attribute, and default values will always
+      be used without error for such attributes. The \texttt{"} Applicability:\texttt{"}  section
+      below lists the attributes which are affected by the setting of UseDefs.
+
+      Note, UseDefs only affects access to attributes internally within
+      AST. The public accessor functions such as
+      astGetC
+      is unaffected by the UseDefs attribute - default values will always
+      be returned if no value has been set. Application code should use the
+      \htmlref{astTest}{astTest}
+      function if required to determine if a value has been set for an
+      attribute.
+   }
+   \sstattributetype{
+      Integer (boolean).
+   }
+   \sstapplicability{
+      \sstsubsection{
+         \htmlref{Object}{Object}
+      }{
+         All Objects have this attribute, but ignore its setting except
+         as described below for individual classes.
+      }
+      \sstsubsection{
+         \htmlref{FrameSet}{FrameSet}
+      }{
+         The default value of UseDefs for a FrameSet is redefined to be
+         the UseDefs value of its current \htmlref{Frame}{Frame}.
+      }
+      \sstsubsection{
+         \htmlref{CmpFrame}{CmpFrame}
+      }{
+         The default value of UseDefs for a CmpFrame is redefined to be
+         the UseDefs value of its first component Frame.
+      }
+      \sstsubsection{
+         \htmlref{Region}{Region}
+      }{
+         The default value of UseDefs for a Region is redefined to be
+         the UseDefs value of its encapsulated Frame.
+      }
+      \sstsubsection{
+         Frame
+      }{
+         If UseDefs is zero, an error is reported when aligning Frames if the
+         \htmlref{Epoch}{Epoch}, \htmlref{ObsLat}{ObsLat} or \htmlref{ObsLon}{ObsLon} attribute is required but has not been
+         assigned a value explicitly.
+      }
+      \sstsubsection{
+         \htmlref{SkyFrame}{SkyFrame}
+      }{
+         If UseDefs is zero, an error is reported when aligning SkyFrames
+         if any of the following attributes are required but have not been
+         assigned a value explicitly: Epoch, \htmlref{Equinox}{Equinox}.
+      }
+      \sstsubsection{
+         \htmlref{SpecFrame}{SpecFrame}
+      }{
+         If UseDefs is zero, an error is reported when aligning SpecFrames
+         if any of the following attributes are required but have not been
+         assigned a value explicitly: Epoch, \htmlref{RefRA}{RefRA}, \htmlref{RefDec}{RefDec}, \htmlref{RestFreq}{RestFreq},
+         \htmlref{SourceVel}{SourceVel}, \htmlref{StdOfRest}{StdOfRest}.
+      }
+      \sstsubsection{
+         \htmlref{DSBSpecFrame}{DSBSpecFrame}
+      }{
+         If UseDefs is zero, an error is reported when aligning DSBSpecFrames
+         or when accessing the \htmlref{ImagFreq}{ImagFreq} attribute if any of the following
+         attributes are required but have not been assigned a value explicitly:
+         Epoch, \htmlref{DSBCentre}{DSBCentre}, \htmlref{IF}{IF}.
+      }
+   }
+}
+\sstroutine{
+   Variant
+}{
+   Indicates which variant of the current Frame is to be used
+}{
+   \sstdescription{
+      This attribute can be used to change the \htmlref{Mapping}{Mapping} that connects the
+      current \htmlref{Frame}{Frame} to the other Frames in the \htmlref{FrameSet}{FrameSet}. By default, each
+      Frame in a FrameSet is connected to the other Frames by a single
+      Mapping that can only be changed by using the
+      \htmlref{astRemapFrame}{astRemapFrame}
+      method. However, it is also possible to associate multiple Mappings
+      with a Frame, each Mapping having an identifying name. If this is
+      done, the \texttt{"} Variant\texttt{"}  attribute can be set to indicate the name of
+      the Mapping that is to be used with the current Frame.
+
+      A possible (if unlikely) use-case is to create a FrameSet that can
+      be used to describe the WCS of an image formed by co-adding images
+      of two different parts of the sky. In such an image, each pixel contains
+      flux from two points on the sky.and so the WCS for the image should
+      ideally contain one pixel Frame and two SkyFrames - one describing
+      each of the two co-added images. There is nothing to prevent a
+      FrameSet containing two explicit SkyFrames, but the problem then arises
+      of how to distinguish between them. The two primary characteristics of
+      a Frame that distinguishes it from other Frames  are its class and its
+      \htmlref{Domain}{Domain} attribute value. The class of a Frame cannot be changed, but we
+      could in principle use two different Domain values to distinguish the
+      two SkyFrames. However, in practice it is not uncommon for application
+      software to assume that SkyFrames will have the default Domain value
+      of \texttt{"} SKY\texttt{"} . That is, instead of searching for Frames that have a class
+      of \texttt{"} \htmlref{SkyFrame}{SkyFrame}\texttt{"} , such software searches for Frames that have a Domain
+      of \texttt{"} SKY\texttt{"} . To alleviate this problem, it is possible to add a single
+      SkyFrame to the FrameSet, but specifying two alternate Mappings to
+      use with the SkyFrame. Setting the \texttt{"} Variant\texttt{"}  attribute to the name
+      of one or the other of these alternate Mappings will cause the
+      SkyFrame to be remapped within the FrameSet so that it uses the
+      specified Mapping. The same facility can be used with any class of
+      Frame, not just SkyFrames.
+
+      To use this facility, the Frame should first be added to the
+      FrameSet in the usual manner using the
+      \htmlref{astAddFrame}{astAddFrame} method. By default, the Mapping supplied to astAddFrame
+      is assigned a name equal to the Domain name of the Frame. To assign a
+      different name to it, the
+      \htmlref{astAddVariant}{astAddVariant}
+      method should then be called specifying the required name and a NULL
+      Mapping. The
+      astAddVariant
+      method should then be called repeatedly to add each required extra
+      Mapping to the current Frame, supplying a unique name for each one.
+
+      Each Frame in a FrameSet can have its own set of variant Mappings.
+      To control the Mappings in use with a specific Frame, you need first
+      to make it the current Frame in the FrameSet.
+
+      The
+      \htmlref{astMirrorVariants}{astMirrorVariants} function
+      allows the effects of variant Mappings associated with a nominated
+      Frame to be propagated to other Frames in the FrameSet.
+
+      Once this has been done, setting a new value for the \texttt{"} Variant\texttt{"}
+      attribute of a FrameSet will cause the current Frame in the
+      FrameSet to be remapped to use the specified variant Mapping. An
+      error will be reported if the current Frame has no variant Mapping
+      with the supplied name.
+
+      Getting the value of the \texttt{"} Variant\texttt{"}  attribute will return the name
+      of the variant Mapping currently in use with the current Frame. If
+      the Frame has no variant Mappings, the value will default to the
+      Domain name of the current Frame.
+
+      Clearing the \texttt{"} Variant\texttt{"}  attribute will have the effect of removing
+      all variant Mappings (except for the currently selected Mapping) from
+      the current Frame.
+
+      Testing the \texttt{"} Variant\texttt{"}  attribute will return
+      a non-zero value
+      if the current Frame contains any variant Mappings, and
+      zero
+      otherwise.
+
+      A complete list of the names associated with all the available
+      variant Mappings in the current Frame can be obtained from the
+      \htmlref{AllVariants}{AllVariants} attribute.
+
+      If a Frame with variant Mappings is remapped using the
+      astRemapFrame
+      method, the currently selected variant Mapping is used by
+      astRemapFrame
+      and the other variant Mappings are removed from the Frame.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FrameSet
+      }{
+         All FrameSets have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Warnings
+}{
+   Controls the issuing of warnings about various conditions
+}{
+   \sstdescription{
+      This attribute controls the issuing of warnings about selected
+      conditions when an \htmlref{Object}{Object} or keyword is read from or written to a
+      \htmlref{FitsChan}{FitsChan}. The value supplied for the Warnings attribute should
+      consist of a space separated list of condition names (see the
+      \htmlref{AllWarnings}{AllWarnings} attribute for a list of the currently defined names).
+      Each name indicates a condition which should be reported. The default
+      value for Warnings is the string \texttt{"} BadKeyName BadKeyValue Tnx Zpx
+      BadCel BadMat BadPV BadCTYPE\texttt{"} .
+
+      The text of any warning will be stored within the FitsChan in the
+      form of one or more new header cards with keyword ASTWARN. If
+      required, applications can check the FitsChan for ASTWARN cards
+      (using \htmlref{astFindFits}{astFindFits}) after the call to \htmlref{astRead}{astRead} or \htmlref{astWrite}{astWrite} has been
+      performed, and report the text of any such cards to the user. ASTWARN
+      cards will be propagated to any output header unless they are
+      deleted from the FitsChan using \htmlref{astDelFits}{astDelFits}.
+   }
+   \sstattributetype{
+      String
+   }
+   \sstapplicability{
+      \sstsubsection{
+         FitsChan
+      }{
+         All FitsChans have this attribute.
+      }
+   }
+   \sstnotes{
+      This attribute only controls the warnings that are to be stored as
+      a set of header cards in the FitsChan as described above. It has no
+      effect on the storage of warnings in the parent \htmlref{Channel}{Channel} structure.
+      All warnings are stored in the parent Channel structure, from where
+      they can be retrieved using the
+      \htmlref{astWarnings}{astWarnings}
+      function.
+   }
+}
+\sstroutine{
+   WcsAxis(lonlat)
+}{
+   FITS-WCS projection axes
+}{
+   \sstdescription{
+      This attribute gives the indices of the longitude and latitude
+      coordinates of the FITS-WCS projection within the coordinate
+      space used by a \htmlref{WcsMap}{WcsMap}. These indices are defined when the
+      WcsMap is first created using \htmlref{astWcsMap}{astWcsMap} and cannot
+      subsequently be altered.
+
+      If \texttt{"} lonlat\texttt{"}  is 1, the index of the longitude axis is
+      returned. Otherwise, if it is 2, the index of the latitude axis
+      is returned.
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+}
+\sstroutine{
+   WcsType
+}{
+   FITS-WCS projection type
+}{
+   \sstdescription{
+      This attribute specifies which type of FITS-WCS projection will
+      be performed by a \htmlref{WcsMap}{WcsMap}. The value is specified when a WcsMap
+      is first created using \htmlref{astWcsMap}{astWcsMap} and cannot subsequently be
+      changed.
+
+      The values used are represented by macros with names of
+      the form \texttt{"} AST\_\_XXX\texttt{"} , where \texttt{"} XXX\texttt{"}  is the (upper case) 3-character
+      code used by the FITS-WCS \texttt{"} CTYPEi\texttt{"}  keyword to identify the
+      projection. For example, possible values are AST\_\_TAN (for the
+      tangent plane or gnomonic projection) and AST\_\_AIT (for the
+      Hammer-Aitoff projection). AST\_\_TPN is an exception in that it
+      is not part of the FITS-WCS standard (it represents a TAN
+      projection with polynomial correction terms as defined in an early
+      draft of the FITS-WCS paper).
+   }
+   \sstattributetype{
+      Integer, read-only.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         WcsMap
+      }{
+         All WcsMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of available projections, see the FITS-WCS paper.
+      }
+   }
+}
+\sstroutine{
+   Width(element)
+}{
+   Line width for a Plot element
+}{
+   \sstdescription{
+      This attribute determines the line width used when drawing each
+      element of graphical output produced by a \htmlref{Plot}{Plot}.  It takes a
+      separate value for each graphical element so that, for instance,
+      the setting \texttt{"} Width(border)=2.0\texttt{"}  causes the Plot border to be
+      drawn using a line width of 2.0. A value of 1.0 results in a
+      line thickness which is approximately 0.0005 times the length of
+      the diagonal of the entire display surface.
+
+      The actual appearance of lines drawn with any particular width,
+      and the range of available widths, is determined by the
+      underlying graphics system.  The default behaviour is for all
+      graphical elements to be drawn using the default line width
+      supplied by this graphics system. This will not necessarily
+      correspond to a Width value of 1.0.
+   }
+   \sstattributetype{
+      Floating point.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Plot
+      }{
+         All Plots have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         For a list of the graphical elements available, see the
+         description of the Plot class.
+
+         \sstitem
+         If no graphical element is specified, (e.g. \texttt{"} Width\texttt{"}  instead of
+         \texttt{"} Width(border)\texttt{"} ), then a \texttt{"} set\texttt{"}  or \texttt{"} clear\texttt{"}  operation will affect
+         the attribute value of all graphical elements, while a \texttt{"} get\texttt{"}  or
+         \texttt{"} test\texttt{"}  operation will use just the Width(\htmlref{Border}{Border}) value.
+      }
+   }
+}
+\sstroutine{
+   XmlFormat
+}{
+   System for formatting Objects as XML
+}{
+   \sstdescription{
+      This attribute specifies the formatting system to use when AST
+      Objects are written out as XML through an \htmlref{XmlChan}{XmlChan}. It
+      affects the behaviour of the \htmlref{astWrite}{astWrite} function when
+      they are used to transfer any AST \htmlref{Object}{Object} to or from an external
+      XML representation.
+
+      The XmlChan class allows AST objects to be represented in the form
+      of XML in several ways (conventions) and the XmlFormat attribute is
+      used to specify which of these should be used. The formatting options
+      available are outlined in the \texttt{"} Formats Available\texttt{"}  section below.
+
+      By default, an XmlChan will attempt to determine which format system
+      is already in use, and will set the default XmlFormat value
+      accordingly (so that subsequent I/O operations adopt the same
+      conventions). It does this by looking for certain critical items
+      which only occur in particular formats. For details of how this
+      works, see the \texttt{"} Choice of Default Format\texttt{"}  section below. If you wish
+      to ensure that a particular format system is used, independently of
+      any XML already read, you should set an explicit XmlFormat value
+      yourself.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         XmlChan
+      }{
+         All XmlChans have this attribute.
+      }
+   }
+   \sstdiytopic{
+      Formats Available
+   }{
+      The XmlFormat attribute can take any of the following (case
+      insensitive) string values to select the corresponding formatting
+      system:
+
+      \sstitemlist{
+
+         \sstitem
+         \texttt{"} NATIVE\texttt{"} : This is a direct conversion to XML of the heirarchical
+         format used by a standard XML channel (and also by the NATIVE
+         encoding of a \htmlref{FitsChan}{FitsChan}).
+
+         \sstitem
+         \texttt{"} QUOTED\texttt{"} : This is the same as NATIVE format except that extra
+         information is included which allows client code to convert the
+         XML into a form which can be read by a standard AST \htmlref{Channel}{Channel}. This
+         extra information indicates which AST attribute values should be
+         enclosed in quotes before being passed to a Channel.
+
+         \sstitem
+         \texttt{"} IVOA\texttt{"} : This is a format that uses an early draft of the STC-X schema
+         developed by the International Virtual Observatory Alliance (IVOA -
+         see \texttt{"} http://www.ivoa.net/\texttt{"} ) to describe coordinate systems, regions,
+         mappings, etc. Support is limited to V1.20 described at
+         \texttt{"} http://www.ivoa.net/Documents/WD/STC/STC-20050225.html\texttt{"} . Since the
+         version of STC-X finally adopted by the IVOA differs in several
+         significant respects from V1.20, this format is now mainly of
+         historical interest. Note, the alternative \texttt{"} STC-S\texttt{"}  format (a
+         simpler non-XML encoding of the STC metadata) is supported by the
+         \htmlref{StcsChan}{StcsChan} class.
+      }
+   }
+   \sstdiytopic{
+      Choice of Default Format;
+   }{
+      If the XmlFormat attribute of an XmlChan is not set, the default
+      value it takes is determined by the presence of certain critical
+      items within the document most recently read using
+      \htmlref{astRead}{astRead}.
+      The sequence of decision used to arrive at the default value is as
+      follows:
+
+      \sstitemlist{
+
+         \sstitem
+         If the previous document read contained any elements in any of the STC
+         namespaces (\texttt{"} urn:nvo-stc\texttt{"} , \texttt{"} urn:nvo-coords\texttt{"}  or \texttt{"} urn:nvo-region\texttt{"} ), then
+         the default value is IVOA.
+
+         \sstitem
+         If the previous document read contained any elements in the AST
+         namespace which had an associated XML attribute called \texttt{"} quoted\texttt{"} , then
+         the default value is QUOTED.
+
+         \sstitem
+         Otherwise, if none of these conditions is met (as would be the
+         case if no document had yet been read), then NATIVE format is
+         used.
+
+      }
+      Setting an explicit value for the XmlFormat attribute always
+      over-rides this default behaviour.
+   }
+   \sstdiytopic{
+      The IVOA Format
+   }{
+      The IVOA support caters only for certain parts of V1.20 of the
+      draft Space-Time Coordinate (STC) schema (see
+      http://www.ivoa.net/Documents/WD/STC/STC-20050225.html). Note, this
+      draft has now been superceded by an officially adopted version that
+      differs in several significant respects from V1.20. Consequently,
+      the \texttt{"} IVOA\texttt{"}  XmlChan format is of historical interest only.
+
+      The following points should be noted when using an XmlChan to read
+      or write STC information (note, this list is currently incomplete):
+
+      \sstitemlist{
+
+         \sstitem
+         Objects can currently only be read using this format, not written.
+
+         \sstitem
+         The AST object generated by reading an $<$STCMetadata$>$ element will
+         be an instance of one of the AST \texttt{"} \htmlref{Stc}{Stc}\texttt{"}  classes: \htmlref{StcResourceProfile}{StcResourceProfile},
+         \htmlref{StcSearchLocation}{StcSearchLocation}, \htmlref{StcCatalogEntryLocation}{StcCatalogEntryLocation}, \htmlref{StcObsDataLocation}{StcObsDataLocation}.
+
+         \sstitem
+         When reading an $<$STCMetadata$>$ element, the axes in the returned
+         AST Object will be in the order space, time, spectral, redshift,
+         irrespective of the order in which the axes occur in the $<$STCMetadata$>$
+         element. If the supplied $<$STCMetadata$>$ element does not contain all of
+         these axes, the returned AST Object will also omit them, but the
+         ordering of those axes which are present will be as stated above. If
+         the spatial frame represents a celestial coordinate system the
+         spatial axes will be in the order (longitude, latitude).
+
+         \sstitem
+         Until such time as the AST \htmlref{TimeFrame}{TimeFrame} is complete, a simple
+         1-dimensional \htmlref{Frame}{Frame} (with \htmlref{Domain}{Domain} set to TIME) will be used to
+         represent the STC $<$TimeFrame$>$ element. Consequently, most of the
+         information within a $<$TimeFrame$>$ element is currently ignored.
+
+         \sstitem
+         $<$SpaceFrame$>$ elements can only be read if they describe a celestial
+         longitude and latitude axes supported by the AST \htmlref{SkyFrame}{SkyFrame} class. The
+         space axes will be returned in the order (longitude, latitude).
+
+         \sstitem
+         Velocities associated with SpaceFrames cannot be read.
+
+         \sstitem
+         Any $<$GenericCoordFrame$>$ elements within an $<$AstroCoordSystem$>$ element
+         are currently ignored.
+
+         \sstitem
+         Any second or subsequent $<$AstroCoordSystem$>$ found within an
+         STCMetaData element is ignored.
+
+         \sstitem
+         Any second or subsequent $<$AstroCoordArea$>$ found within an
+         STCMetaData element is ignored.
+
+         \sstitem
+         Any $<$OffsetCenter$>$ found within a $<$SpaceFrame$>$ is ignored.
+
+         \sstitem
+         Any CoordFlavor element found within a $<$SpaceFrame$>$ is ignored.
+
+         \sstitem
+         $<$SpaceFrame$>$ elements can only be read if they refer to
+         one of the following space reference frames: ICRS, GALACTIC\_II,
+         SUPER\_GALACTIC, HEE, FK4, FK5, ECLIPTIC.
+
+         \sstitem
+         $<$SpaceFrame$>$ elements can only be read if the reference
+         position is TOPOCENTER. Also, any planetary ephemeris is ignored.
+
+         \sstitem
+         Regions: there is currently no support for STC regions of type
+         Sector, ConvexHull or SkyIndex.
+
+         \sstitem
+         The AST \htmlref{Region}{Region} read from a CoordInterval element is considered to
+         be open if either the lo\_include or the hi\_include attribute is
+         set to false.
+
+         \sstitem
+         $<$RegionFile$>$ elements are not supported.
+
+         \sstitem
+         Vertices within $<$\htmlref{Polygon}{Polygon}$>$ elements are always considered to be
+         joined using great circles (that is, $<$SmallCircle$>$ elements are
+         ignored).
+      }
+   }
+}
+\sstroutine{
+   XmlLength
+}{
+   Controls output buffer length
+}{
+   \sstdescription{
+      This attribute specifies the maximum length to use when writing out
+      text through the sink function supplied when the \htmlref{XmlChan}{XmlChan} was created.
+
+      The number of characters in each string written out through the sink
+      function will not be greater than the value of this attribute (but
+      may be less). A value of zero (the default) means there is no limit -
+      each string can be of any length.
+   }
+   \sstattributetype{
+      Integer.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         XmlChan
+      }{
+         All XmlChans have this attribute.
+      }
+   }
+}
+\sstroutine{
+   XmlPrefix
+}{
+   The namespace prefix to use when writing
+}{
+   \sstdescription{
+      This attribute is a string which is to be used as the namespace
+      prefix for all XML elements created as a result of writing an AST
+      \htmlref{Object}{Object} out through an \htmlref{XmlChan}{XmlChan}. The URI associated with the namespace
+      prefix is given by the symbolic constant AST\_\_XMLNS defined in
+      ast.h.
+      A definition of the namespace prefix is included in each top-level
+      element produced by the XmlChan.
+
+      The default value is a blank string which causes no prefix to be
+      used. In this case each top-level element will set the default
+      namespace to be the value of AST\_\_XMLNS.
+   }
+   \sstattributetype{
+      String.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         Object
+      }{
+         All Objects have this attribute.
+      }
+   }
+}
+\sstroutine{
+   Zoom
+}{
+   ZoomMap scale factor
+}{
+   \sstdescription{
+      This attribute holds the \htmlref{ZoomMap}{ZoomMap} scale factor, by which
+      coordinate values are multiplied (by the forward transformation)
+      or divided (by the inverse transformation). The default value
+      is unity.
+
+      Note that if a ZoomMap is inverted (e.g. by using \htmlref{astInvert}{astInvert}),
+      then the reciprocal of this zoom factor will, in effect, be
+      used.
+
+      In general, \htmlref{Mapping}{Mapping} attributes cannot be changed after the Mapping
+      has been created (the exception to this is the \htmlref{Invert}{Invert} attribute,
+      which can be changed at any time). However, several of the oldest
+      Mapping classes - including the ZoomMap class - were introduced
+      into the AST library before this restriction was enforced. To
+      reduce the chances of breaking existing software, the attributes of
+      such Mappings may still be changed, but only for Mapping instances
+      that have exactly one active reference. In other words, an error will
+      be reported if an attempt is made to set or clear an attribute of a
+      Mapping (other than the Invert attribute) if that Mapping has been
+      cloned. Mappings are cloned when they are incorporated into another
+      object such as a \htmlref{CmpMap}{CmpMap} or \htmlref{FrameSet}{FrameSet}, or when the
+      \htmlref{astClone}{astClone}
+      function is used.
+   }
+   \sstattributetype{
+      Double precision.
+   }
+   \sstapplicability{
+      \sstsubsection{
+         ZoomMap
+      }{
+         All ZoomMaps have this attribute.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         The Zoom attribute may not be set to zero.
+      }
+   }
+}
+\normalsize
+
+\cleardoublepage
+\section{\label{ss:classdescriptions}AST Class Descriptions}
+\small
+\sstroutine{
+   Axis
+}{
+   Store axis information
+}{
+   \sstdescription{
+      The Axis class is used to store information associated with a
+      particular axis of a \htmlref{Frame}{Frame}. It is used internally by the AST
+      library and has no constructor function. You should encounter it
+      only within textual output (e.g. from \htmlref{astWrite}{astWrite}).
+   }
+   \sstconstructor{
+      None.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Axis class inherits from the \htmlref{Object}{Object} class.
+   }
+}
+\sstroutine{
+   Box
+}{
+   A box region with sides parallel to the axes of a Frame
+}{
+   \sstdescription{
+      The Box class implements a \htmlref{Region}{Region} which represents a box with sides
+      parallel to the axes of a \htmlref{Frame}{Frame} (i.e. an area which encloses a given
+      range of values on each axis). A Box is similar to an \htmlref{Interval}{Interval}, the
+      only real difference being that the Interval class allows some axis
+      limits to be unspecified. Note, a Box will only look like a box if
+      the Frame geometry is approximately flat. For instance, a Box centred
+      close to a pole in a \htmlref{SkyFrame}{SkyFrame} will look more like a fan than a box
+      (the \htmlref{Polygon}{Polygon} class can be used to create a box-like region close to a
+      pole).
+   }
+   \sstconstructor{
+      \htmlref{astBox}{astBox}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Box class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The Box class does not define any new attributes beyond
+      those which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The Box class does not define any new functions beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   Channel
+}{
+   Basic (textual) I/O channel
+}{
+   \sstdescription{
+      The Channel class implements low-level input/output for the AST
+      library.  Writing an \htmlref{Object}{Object} to a Channel will generate a textual
+      representation of that Object, and reading from a Channel will
+      create a new Object from its textual representation.
+
+      Normally, when you use a Channel, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  functions which connect it to an external data store
+      by reading and writing the resulting text. By default, however,
+      a Channel will read from standard input and write to standard
+      output. Alternatively, a Channel can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+   }
+   \sstconstructor{
+      \htmlref{astChannel}{astChannel}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Channel class inherits from the Object class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Objects, every
+      Channel also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Comment}{Comment}: Include textual comments in output?
+
+         \sstitem
+         \htmlref{Full}{Full}: Set level of output detail
+
+         \sstitem
+         \htmlref{Indent}{Indent}: Indentation increment between objects
+
+         \sstitem
+         \htmlref{ReportLevel}{ReportLevel}: Selects the level of error reporting
+
+         \sstitem
+         \htmlref{SinkFile}{SinkFile}: The path to a file to which the Channel should write
+
+         \sstitem
+         \htmlref{Skip}{Skip}: Skip irrelevant data?
+
+         \sstitem
+         \htmlref{SourceFile}{SourceFile}: The path to a file from which the Channel should read
+
+         \sstitem
+         \htmlref{Strict}{Strict}: Generate errors instead of warnings?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Objects, the
+      following functions may also be applied to all Channels:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astWarnings}{astWarnings}: Return warnings from the previous read or write
+
+         \sstitem
+         \htmlref{astPutChannelData}{astPutChannelData}: Store data to pass to source or sink functions
+
+         \sstitem
+         \htmlref{astRead}{astRead}: Read an Object from a Channel
+
+         \sstitem
+         \htmlref{astWrite}{astWrite}: Write an Object to a Channel
+      }
+   }
+}
+\sstroutine{
+   Circle
+}{
+   A circular or spherical region within a Frame
+}{
+   \sstdescription{
+      The Circle class implements a \htmlref{Region}{Region} which represents a circle or
+      sphere within a \htmlref{Frame}{Frame}.
+   }
+   \sstconstructor{
+      \htmlref{astCircle}{astCircle}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Circle class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The Circle class does not define any new attributes beyond
+      those which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Regions, the
+      following functions may also be applied to all Circles:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astCirclePars}{astCirclePars}: Get the geometric parameters of the Circle
+      }
+   }
+}
+\sstroutine{
+   CmpFrame
+}{
+   Compound Frame
+}{
+   \sstdescription{
+      A CmpFrame is a compound \htmlref{Frame}{Frame} which allows two component Frames
+      (of any class) to be merged together to form a more complex
+      Frame. The axes of the two component Frames then appear together
+      in the resulting CmpFrame (those of the first Frame, followed by
+      those of the second Frame).
+
+      Since a CmpFrame is itself a Frame, it can be used as a
+      component in forming further CmpFrames. Frames of arbitrary
+      complexity may be built from simple individual Frames in this
+      way.
+
+      Also since a Frame is a \htmlref{Mapping}{Mapping}, a CmpFrame can also be used as a
+      Mapping. Normally, a CmpFrame is simply equivalent to a \htmlref{UnitMap}{UnitMap},
+      but if either of the component Frames within a CmpFrame is a \htmlref{Region}{Region}
+      (a sub-class of Frame), then the CmpFrame will use the Region as a
+      Mapping when transforming values for axes described by the Region.
+      Thus input axis values corresponding to positions which are outside the
+      Region will result in bad output axis values.
+   }
+   \sstconstructor{
+      \htmlref{astCmpFrame}{astCmpFrame}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The CmpFrame class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The CmpFrame class does not define any new attributes beyond
+      those which are applicable to all Frames. However, the attributes
+      of the component Frames can be accessed as if they were attributes
+      of the CmpFrame. For instance, if a CmpFrame contains a \htmlref{SpecFrame}{SpecFrame}
+      and a \htmlref{SkyFrame}{SkyFrame}, then the CmpFrame will recognise the \texttt{"} \htmlref{Equinox}{Equinox}\texttt{"}
+      attribute and forward access requests to the component SkyFrame.
+      Likewise, it will recognise the \texttt{"} \htmlref{RestFreq}{RestFreq}\texttt{"}  attribute and forward
+      access requests to the component SpecFrame. An axis index can
+      optionally be appended to the end of any attribute name, in which
+      case the request to access the attribute will be forwarded to the
+      primary Frame defining the specified axis.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The CmpFrame class does not define any new functions beyond those
+      which are applicable to all Frames.
+   }
+}
+\sstroutine{
+   CmpMap
+}{
+   Compound Mapping
+}{
+   \sstdescription{
+      A CmpMap is a compound \htmlref{Mapping}{Mapping} which allows two component
+      Mappings (of any class) to be connected together to form a more
+      complex Mapping. This connection may either be \texttt{"} in series\texttt{"}
+      (where the first Mapping is used to transform the coordinates of
+      each point and the second mapping is then applied to the
+      result), or \texttt{"} in parallel\texttt{"}  (where one Mapping transforms the
+      earlier coordinates for each point and the second Mapping
+      simultaneously transforms the later coordinates).
+
+      Since a CmpMap is itself a Mapping, it can be used as a
+      component in forming further CmpMaps. Mappings of arbitrary
+      complexity may be built from simple individual Mappings in this
+      way.
+   }
+   \sstconstructor{
+      \htmlref{astCmpMap}{astCmpMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The CmpMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The CmpMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The CmpMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   CmpRegion
+}{
+   A combination of two regions within a single Frame
+}{
+   \sstdescription{
+      A CmpRegion is a \htmlref{Region}{Region} which allows two component
+      Regions (of any class) to be combined to form a more complex
+      Region. This combination may be performed a boolean AND, OR
+      or XOR (exclusive OR) operator. If the AND operator is
+      used, then a position is inside the CmpRegion only if it is
+      inside both of its two component Regions. If the OR operator is
+      used, then a position is inside the CmpRegion if it is inside
+      either (or both) of its two component Regions. If the XOR operator
+      is used, then a position is inside the CmpRegion if it is inside
+      one but not both of its two component Regions. Other operators can
+      be formed by negating one or both component Regions before using
+      them to construct a new CmpRegion.
+
+      The two component Region need not refer to the same coordinate
+      \htmlref{Frame}{Frame}, but it must be possible for the
+      \htmlref{astConvert}{astConvert}
+      function to determine a \htmlref{Mapping}{Mapping} between them (an error will be
+      reported otherwise when the CmpRegion is created). For instance,
+      a CmpRegion may combine a Region defined within an ICRS \htmlref{SkyFrame}{SkyFrame}
+      with a Region defined within a Galactic SkyFrame. This is
+      acceptable because the SkyFrame class knows how to convert between
+      these two systems, and consequently the
+      astConvert
+      function will also be able to convert between them. In such cases,
+      the second component Region will be mapped into the coordinate Frame
+      of the first component Region, and the Frame represented by the
+      CmpRegion as a whole will be the Frame of the first component Region.
+
+      Since a CmpRegion is itself a Region, it can be used as a
+      component in forming further CmpRegions. Regions of arbitrary
+      complexity may be built from simple individual Regions in this
+      way.
+   }
+   \sstconstructor{
+      \htmlref{astCmpRegion}{astCmpRegion}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The CmpRegion class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The CmpRegion class does not define any new attributes beyond those
+      which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The CmpRegion class does not define any new functions beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   DSBSpecFrame
+}{
+   Dual sideband spectral coordinate system description
+}{
+   \sstdescription{
+      A DSBSpecFrame is a specialised form of \htmlref{SpecFrame}{SpecFrame} which represents
+      positions in a spectrum obtained using a dual sideband instrument.
+      Such an instrument produces a spectrum in which each point contains
+      contributions from two distinctly different frequencies, one from
+      the \texttt{"} lower side band\texttt{"}  (LSB) and one from the \texttt{"} upper side band\texttt{"}  (USB).
+      Corresponding LSB and USB frequencies are connected by the fact
+      that they are an equal distance on either side of a fixed central
+      frequency known as the \texttt{"} Local Oscillator\texttt{"}  (LO) frequency.
+
+      When quoting a position within such a spectrum, it is necessary to
+      indicate whether the quoted position is the USB position or the
+      corresponding LSB position. The \htmlref{SideBand}{SideBand} attribute provides this
+      indication. Another option that the SideBand attribute provides is
+      to represent a spectral position by its topocentric offset from the
+      LO frequency.
+
+      In practice, the LO frequency is specified by giving the distance
+      from the LO frequency to some \texttt{"} central\texttt{"}  spectral position. Typically
+      this central position is that of some interesting spectral feature.
+      The distance from this central position to the LO frequency is known
+      as the \texttt{"} intermediate frequency\texttt{"}  (\htmlref{IF}{IF}). The value supplied for IF can
+      be a signed value in order to indicate whether the LO frequency is
+      above or below the central position.
+   }
+   \sstconstructor{
+      \htmlref{astDSBSpecFrame}{astDSBSpecFrame}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The DSBSpecFrame class inherits from the SpecFrame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all SpecFrames, every
+      DSBSpecFrame also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AlignSideBand}{AlignSideBand}: Should alignment occur between sidebands?
+
+         \sstitem
+         \htmlref{DSBCentre}{DSBCentre}: The central position of interest.
+
+         \sstitem
+         \htmlref{IF}{IF}: The intermediate frequency used to define the LO frequency.
+
+         \sstitem
+         \htmlref{ImagFreq}{ImagFreq}: The image sideband equivalent of the rest frequency.
+
+         \sstitem
+         \htmlref{SideBand}{SideBand}: Indicates which sideband the DSBSpecFrame represents.
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The DSBSpecFrame class does not define any new functions beyond those
+      which are applicable to all SpecFrames.
+   }
+}
+\sstroutine{
+   DssMap
+}{
+   Map points using a Digitised Sky Survey plate solution
+}{
+   \sstdescription{
+      The DssMap class implements a \htmlref{Mapping}{Mapping} which transforms between
+      2-dimensional pixel coordinates and an equatorial sky coordinate
+      system (right ascension and declination) using a Digitised Sky
+      Survey (DSS) astrometric plate solution.
+
+      The input coordinates are pixel numbers along the first and
+      second dimensions of an image, where the centre of the first
+      pixel is located at (1,1) and the spacing between pixel centres
+      is unity.
+
+      The output coordinates are right ascension and declination in
+      radians. The celestial coordinate system used (FK4, FK5, etc.)
+      is unspecified, and will usually be indicated by appropriate
+      keywords in a FITS header.
+   }
+   \sstconstructor{
+      The DssMap class does not have a constructor function.  A DssMap
+      is created only as a by-product of reading a \htmlref{FrameSet}{FrameSet} (using
+      \htmlref{astRead}{astRead}) from a \htmlref{FitsChan}{FitsChan} which contains FITS header cards
+      describing a DSS plate solution, and whose \htmlref{Encoding}{Encoding} attribute is
+      set to \texttt{"} DSS\texttt{"} . The result of such a read, if successful, is a
+      FrameSet whose base and current Frames are related by a DssMap.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The DssMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The DssMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The DssMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Ellipse
+}{
+   An elliptical region within a 2-dimensional Frame
+}{
+   \sstdescription{
+      The Ellipse class implements a \htmlref{Region}{Region} which represents a ellipse
+      within a 2-dimensional \htmlref{Frame}{Frame}.
+   }
+   \sstconstructor{
+      \htmlref{astEllipse}{astEllipse}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Ellipse class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The Ellipse class does not define any new attributes beyond
+      those which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Regions, the
+      following functions may also be applied to all Ellipses:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astEllipsePars}{astEllipsePars}: Get the geometric parameters of the Ellipse
+      }
+   }
+}
+\sstroutine{
+   FitsChan
+}{
+   I/O Channel using FITS header cards to represent Objects
+}{
+   \sstdescription{
+      A FitsChan is a specialised form of \htmlref{Channel}{Channel} which supports I/O
+      operations involving the use of FITS (Flexible Image Transport
+      \htmlref{System}{System}) header cards. Writing an \htmlref{Object}{Object} to a FitsChan (using
+      \htmlref{astWrite}{astWrite}) will, if the Object is suitable, generate a
+      description of that Object composed of FITS header cards, and
+      reading from a FitsChan will create a new Object from its FITS
+      header card description.
+
+      While a FitsChan is active, it represents a buffer which may
+      contain zero or more 80-character \texttt{"} header cards\texttt{"}  conforming to
+      FITS conventions. Any sequence of FITS-conforming header cards
+      may be stored, apart from the \texttt{"} END\texttt{"}  card whose existence is
+      merely implied.  The cards may be accessed in any order by using
+      the FitsChan\texttt{'} s integer \htmlref{Card}{Card} attribute, which identifies a \texttt{"} current\texttt{"}
+      card, to which subsequent operations apply. Searches
+      based on keyword may be performed (using \htmlref{astFindFits}{astFindFits}), new
+      cards may be inserted (\htmlref{astPutFits}{astPutFits}, \htmlref{astPutCards}{astPutCards}, \htmlref{astSetFits$<$X$>$}{astSetFits$<$X$>$}) and
+      existing ones may be deleted (\htmlref{astDelFits}{astDelFits}), extracted (\htmlref{astGetFits$<$X$>$}{astGetFits$<$X$>$}),
+      or changed (astSetFits$<$X$>$).
+
+      When you create a FitsChan, you have the option of specifying
+      \texttt{"} source\texttt{"}  and \texttt{"} sink\texttt{"}  functions which connect it to external data
+      stores by reading and writing FITS header cards. If you provide
+      a source function, it is used to fill the FitsChan with header cards
+      when it is accessed for the first time. If you do not provide a
+      source function, the FitsChan remains empty until you explicitly enter
+      data into it (e.g. using astPutFits, astPutCards, astWrite
+      or by using the \htmlref{SourceFile}{SourceFile} attribute to specifying a text file from
+      which headers should be read). When the FitsChan is deleted, any
+      remaining header cards in the FitsChan can be saved in either of
+      two ways: 1) by specifying a value for the \htmlref{SinkFile}{SinkFile} attribute (the
+      name of a text file to which header cards should be written), or 2)
+      by providing a sink function (used to to deliver header cards to an
+      external data store). If you do not provide a sink function or a
+      value for SinkFile, any header cards remaining when the FitsChan
+      is deleted will be lost, so you should arrange to extract them
+      first if necessary
+      (e.g. using astFindFits or \htmlref{astRead}{astRead}).
+
+      Coordinate system information may be described using FITS header
+      cards using several different conventions, termed
+      \texttt{"} encodings\texttt{"} . When an AST Object is written to (or read from) a
+      FitsChan, the value of the FitsChan\texttt{'} s \htmlref{Encoding}{Encoding} attribute
+      determines how the Object is converted to (or from) a
+      description involving FITS header cards. In general, different
+      encodings will result in different sets of header cards to
+      describe the same Object. Examples of encodings include the DSS
+      encoding (based on conventions used by the STScI Digitised Sky
+      Survey data), the FITS-WCS encoding (based on a proposed FITS
+      standard) and the NATIVE encoding (a near loss-less way of
+      storing AST Objects in FITS headers).
+
+      The available encodings differ in the range of Objects they can
+      represent, in the number of Object descriptions that can coexist
+      in the same FitsChan, and in their accessibility to other
+      (external) astronomy applications (see the Encoding attribute
+      for details). Encodings are not necessarily mutually exclusive
+      and it may sometimes be possible to describe the same Object in
+      several ways within a particular set of FITS header cards by
+      using several different encodings.
+
+      The detailed behaviour of astRead and astWrite, when used with
+      a FitsChan, depends on the encoding in use. In general, however,
+      all successful use of astRead is destructive, so that FITS header cards
+      are consumed in the process of reading an Object, and are
+      removed from the FitsChan (this deletion can be prevented for
+      specific cards by calling the
+      \htmlref{astRetainFits}{astRetainFits} function).
+      An unsuccessful call of
+      astRead
+      (for instance, caused by the FitsChan not containing the necessary
+      FITS headers cards needed to create an Object) results in the
+      contents of the FitsChan being left unchanged.
+
+      If the encoding in use allows only a single Object description
+      to be stored in a FitsChan (e.g. the DSS, FITS-WCS and FITS-IRAF
+      encodings), then write operations using astWrite will
+      over-write any existing Object description using that
+      encoding. Otherwise (e.g. the NATIVE encoding), multiple Object
+      descriptions are written sequentially and may later be read
+      back in the same sequence.
+   }
+   \sstconstructor{
+      \htmlref{astFitsChan}{astFitsChan}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The FitsChan class inherits from the Channel class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Channels, every
+
+      FitsChan also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AllWarnings}{AllWarnings}: A list of the available conditions
+
+         \sstitem
+         \htmlref{Card}{Card}: Index of current FITS card in a FitsChan
+
+         \sstitem
+         \htmlref{CardComm}{CardComm}: The comment of the current FITS card in a FitsChan
+
+         \sstitem
+         \htmlref{CardName}{CardName}: The keyword name of the current FITS card in a FitsChan
+
+         \sstitem
+         \htmlref{CardType}{CardType}: The data type of the current FITS card in a FitsChan
+
+         \sstitem
+         \htmlref{CarLin}{CarLin}: Ignore spherical rotations on CAR projections?
+
+         \sstitem
+         \htmlref{CDMatrix}{CDMatrix}: Use a CD matrix instead of a PC matrix?
+
+         \sstitem
+         \htmlref{Clean}{Clean}: Remove cards used whilst reading even if an error occurs?
+
+         \sstitem
+         \htmlref{DefB1950}{DefB1950}: Use FK4 B1950 as default equatorial coordinates?
+
+         \sstitem
+         \htmlref{Encoding}{Encoding}: System for encoding Objects as FITS headers
+
+         \sstitem
+         \htmlref{FitsAxisOrder}{FitsAxisOrder}: Sets the order of WCS axes within new FITS-WCS headers
+
+         \sstitem
+         \htmlref{FitsDigits}{FitsDigits}: Digits of precision for floating-point FITS values
+
+         \sstitem
+         \htmlref{Iwc}{Iwc}: Add a \htmlref{Frame}{Frame} describing Intermediate World Coords?
+
+         \sstitem
+         \htmlref{Ncard}{Ncard}: Number of FITS header cards in a FitsChan
+
+         \sstitem
+         \htmlref{Nkey}{Nkey}: Number of unique keywords in a FitsChan
+
+         \sstitem
+         \htmlref{TabOK}{TabOK}: Should the FITS \texttt{"} -TAB\texttt{"}  algorithm be recognised?
+
+         \sstitem
+         \htmlref{PolyTan}{PolyTan}: Use \htmlref{PVi\_m}{PVi\_m} keywords to define distorted TAN projection?
+
+         \sstitem
+         \htmlref{Warnings}{Warnings}: Produces warnings about selected conditions
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Channels, the
+      following functions may also be applied to all FitsChans:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astDelFits}{astDelFits}: Delete the current FITS card in a FitsChan
+
+         \sstitem
+         \htmlref{astEmptyFits}{astEmptyFits}: Delete all cards in a FitsChan
+
+         \sstitem
+         \htmlref{astFindFits}{astFindFits}: Find a FITS card in a FitsChan by keyword
+
+         \sstitem
+         \htmlref{astGetFits$<$X$>$}{astGetFits$<$X$>$}: Get a keyword value from a FitsChan
+
+         \sstitem
+         \htmlref{astGetTables}{astGetTables}: Retrieve any FitsTables from a FitsChan
+
+         \sstitem
+         \htmlref{astPurgeWCS}{astPurgeWCS}: Delete all WCS-related cards in a FitsChan
+
+         \sstitem
+         \htmlref{astPutCards}{astPutCards}: Stores a set of FITS header card in a FitsChan
+
+         \sstitem
+         \htmlref{astPutFits}{astPutFits}: Store a FITS header card in a FitsChan
+
+         \sstitem
+         \htmlref{astPutTable}{astPutTable}: Store a single \htmlref{FitsTable}{FitsTable} in a FitsChan
+
+         \sstitem
+         \htmlref{astPutTables}{astPutTables}: Store multiple FitsTables in a FitsChan
+
+         \sstitem
+         \htmlref{astReadFits}{astReadFits}: Read cards in through the source function
+
+         \sstitem
+         \htmlref{astRemoveTables}{astRemoveTables}: Remove one or more FitsTables from a FitsChan
+
+         \sstitem
+         \htmlref{astRetainFits}{astRetainFits}: Ensure current card is retained in a FitsChan
+
+         \sstitem
+         \htmlref{astSetFits$<$X$>$}{astSetFits$<$X$>$}: Store a new keyword value in a FitsChan
+
+         \sstitem
+         \htmlref{astShowFits}{astShowFits}: Display the contents of a FitsChan on standard output
+
+         \sstitem
+         \htmlref{astTableSource}{astTableSource}: Register a source function for FITS table access
+
+         \sstitem
+         \htmlref{astTestFits}{astTestFits}: Test if a keyword has a defined value in a FitsChan
+
+         \sstitem
+         \htmlref{astWriteFits}{astWriteFits}: Write all cards out to the sink function
+
+         \sstitem
+         AST\_SHOWFITS: Display the contents of a FitsChan on standard output
+      }
+   }
+}
+\sstroutine{
+   FitsTable
+}{
+   A representation of a FITS binary table
+}{
+   \sstdescription{
+      The FitsTable class is a representation of a FITS binary table. It
+      inherits from the \htmlref{Table}{Table} class. The parent Table is used to hold the
+      binary data of the main table, and a \htmlref{FitsChan}{FitsChan} (encapsulated within
+      the FitsTable) is used to hold the FITS header.
+
+      Note - it is not recommended to use the FitsTable class to store
+      very large tables.
+
+      FitsTables are primarily geared towards the needs of the \texttt{"} -TAB\texttt{"}
+      algorithm defined in FITS-WCS paper 2, and so do not support all
+      features of FITS binary tables. In particularly, they do not
+      provide any equivalent to the following features of FITS binary
+      tables: \texttt{"} heap\texttt{"}  data (i.e. binary data following the main table),
+      columns holding complex values, columns holding variable length
+      arrays, scaled columns, column formats, columns holding bit values,
+      8-byte integer values or logical values.
+   }
+   \sstconstructor{
+      \htmlref{astFitsTable}{astFitsTable}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The FitsTable class inherits from the Table class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The FitsTable class does not define any new attributes beyond
+      those which are applicable to all Tables.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Tables, the
+      following functions may also be applied to all FitsTables:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astColumnNull}{astColumnNull}: Get/set the null value for a column of a FitsTable
+
+         \sstitem
+         \htmlref{astColumnSize}{astColumnSize}: Get number of bytes needed to hold a full column of data
+
+         \sstitem
+         \htmlref{astGetColumnData}{astGetColumnData}: Retrieve all the data values stored in a column
+
+         \sstitem
+         \htmlref{astGetTableHeader}{astGetTableHeader}: Get the FITS headers from a FitsTable
+
+         \sstitem
+         \htmlref{astPutColumnData}{astPutColumnData}: Store data values in a column
+
+         \sstitem
+         \htmlref{astPutTableHeader}{astPutTableHeader}: Store FITS headers within a FitsTable
+      }
+   }
+}
+\sstroutine{
+   FluxFrame
+}{
+   Measured flux description
+}{
+   \sstdescription{
+      A FluxFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various systems used to represent the signal level in an
+      observation. The particular coordinate system to be used is specified
+      by setting the FluxFrame\texttt{'} s \htmlref{System}{System} attribute qualified, as necessary, by
+      other attributes such as the units, etc (see the description of the
+      System attribute for details).
+
+      All flux values are assumed to be measured at the same frequency or
+      wavelength (as given by the \htmlref{SpecVal}{SpecVal} attribute). Thus this class is
+      more appropriate for use with images rather than spectra.
+   }
+   \sstconstructor{
+      \htmlref{astFluxFrame}{astFluxFrame}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The FluxFrame class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      FluxFrame also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{SpecVal}{SpecVal}: The spectral position at which the flux values are measured.
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The FluxFrame class does not define any new functions beyond those
+      which are applicable to all Frames.
+   }
+}
+\sstroutine{
+   Frame
+}{
+   Coordinate system description
+}{
+   \sstdescription{
+      This class is used to represent coordinate systems. It does this
+      in rather the same way that a frame around a graph describes the
+      coordinate space in which data are plotted. Consequently, a
+      Frame has a \htmlref{Title}{Title} (string) attribute, which describes the
+      coordinate space, and contains axes which in turn hold
+      information such as Label and Units strings which are used for
+      labelling (e.g.) graphical output. In general, however, the
+      number of axes is not restricted to two.
+
+      Functions are available for converting Frame coordinate values
+      into a form suitable for display, and also for calculating
+      distances and offsets between positions within the Frame.
+
+      Frames may also contain knowledge of how to transform to and
+      from related coordinate systems.
+   }
+   \sstconstructor{
+      \htmlref{astFrame}{astFrame}
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When used as a \htmlref{Mapping}{Mapping}, a Frame implements a unit (null)
+         transformation in both the forward and inverse directions
+         (equivalent to a \htmlref{UnitMap}{UnitMap}). The \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attribute values are
+         both equal to the number of Frame axes.
+      }
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Frame class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      Frame also has the following attributes (if the Frame has only one
+      axis, the axis specifier can be omited from the following attribute
+      names):
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AlignSystem}{AlignSystem}: Coordinate system used to align Frames
+
+         \sstitem
+         \htmlref{Bottom(axis)}{Bottom(axis)}: Lowest axis value to display
+
+         \sstitem
+         \htmlref{Digits/Digits(axis)}{Digits/Digits(axis)}: Number of digits of precision
+
+         \sstitem
+         \htmlref{Direction(axis)}{Direction(axis)}: Display axis in conventional direction?
+
+         \sstitem
+         \htmlref{Domain}{Domain}: Coordinate system domain
+
+         \sstitem
+         \htmlref{Dut1}{Dut1}: Difference between the UT1 and UTC timescale
+
+         \sstitem
+         \htmlref{Epoch}{Epoch}: Epoch of observation
+
+         \sstitem
+         \htmlref{Format(axis)}{Format(axis)}: Format specification for axis values
+
+         \sstitem
+         \htmlref{InternalUnit(axis)}{InternalUnit(axis)}: Physical units for unformated axis values
+
+         \sstitem
+         \htmlref{Label(axis)}{Label(axis)}: \htmlref{Axis}{Axis} label
+
+         \sstitem
+         \htmlref{MatchEnd}{MatchEnd}: Match trailing axes?
+
+         \sstitem
+         \htmlref{MaxAxes}{MaxAxes}: Maximum number of Frame axes to match
+
+         \sstitem
+         \htmlref{MinAxes}{MinAxes}: Minimum number of Frame axes to match
+
+         \sstitem
+         \htmlref{Naxes}{Naxes}: Number of Frame axes
+
+         \sstitem
+         \htmlref{NormUnit(axis)}{NormUnit(axis)}: Normalised physical units for formatted axis values
+
+         \sstitem
+         \htmlref{ObsAlt}{ObsAlt}: Geodetic altitude of observer
+
+         \sstitem
+         \htmlref{ObsLat}{ObsLat}: Geodetic latitude of observer
+
+         \sstitem
+         \htmlref{ObsLon}{ObsLon}: Geodetic longitude of observer
+
+         \sstitem
+         \htmlref{Permute}{Permute}: Permute axis order?
+
+         \sstitem
+         \htmlref{PreserveAxes}{PreserveAxes}: Preserve axes?
+
+         \sstitem
+         \htmlref{Symbol(axis)}{Symbol(axis)}: Axis symbol
+
+         \sstitem
+         \htmlref{System}{System}: Coordinate system used to describe the domain
+
+         \sstitem
+         \htmlref{Title}{Title}: Frame title
+
+         \sstitem
+         \htmlref{Top(axis)}{Top(axis)}: Highest axis value to display
+
+         \sstitem
+         \htmlref{Unit(axis)}{Unit(axis)}: Physical units for formatted axis values
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Mappings, the
+      following functions may also be applied to all Frames:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astAngle}{astAngle}: Calculate the angle subtended by two points at a third point
+
+         \sstitem
+         \htmlref{astAxAngle}{astAxAngle}: Find the angle from an axis, to a line through two points
+
+         \sstitem
+         \htmlref{astAxDistance}{astAxDistance}: Calculate the distance between two axis values
+
+         \sstitem
+         \htmlref{astAxNorm}{astAxNorm}: Normalises an array of axis values
+
+         \sstitem
+         \htmlref{astAxOffset}{astAxOffset}: Calculate an offset along an axis
+
+         \sstitem
+         \htmlref{astConvert}{astConvert}: Determine how to convert between two coordinate systems
+
+         \sstitem
+         \htmlref{astDistance}{astDistance}: Calculate the distance between two points in a Frame
+
+         \sstitem
+         \htmlref{astFindFrame}{astFindFrame}: Find a coordinate system with specified characteristics
+
+         \sstitem
+         \htmlref{astFormat}{astFormat}: Format a coordinate value for a Frame axis
+
+         \sstitem
+         \htmlref{astGetActiveUnit}{astGetActiveUnit}: Determines how the Unit attribute will be used
+
+         \sstitem
+         \htmlref{astIntersect}{astIntersect}: Find the intersection between two geodesic curves
+
+         \sstitem
+         \htmlref{astMatchAxes}{astMatchAxes}: Find any corresponding axes in two Frames
+
+         \sstitem
+         \htmlref{astNorm}{astNorm}: Normalise a set of Frame coordinates
+
+         \sstitem
+         \htmlref{astOffset}{astOffset}: Calculate an offset along a geodesic curve
+
+         \sstitem
+         \htmlref{astOffset2}{astOffset2}: Calculate an offset along a geodesic curve in a 2D Frame
+
+         \sstitem
+         \htmlref{astPermAxes}{astPermAxes}: Permute the order of a Frame\texttt{'} s axes
+
+         \sstitem
+         \htmlref{astPickAxes}{astPickAxes}: Create a new Frame by picking axes from an existing one
+
+         \sstitem
+         \htmlref{astResolve}{astResolve}: Resolve a vector into two orthogonal components
+
+         \sstitem
+         \htmlref{astSetActiveUnit}{astSetActiveUnit}: Specify how the Unit attribute should be used
+
+         \sstitem
+         \htmlref{astUnformat}{astUnformat}: Read a formatted coordinate value for a Frame axis
+      }
+   }
+}
+\sstroutine{
+   FrameSet
+}{
+   Set of inter-related coordinate systems
+}{
+   \sstdescription{
+      A FrameSet consists of a set of one or more Frames (which
+      describe coordinate systems), connected together by Mappings
+      (which describe how the coordinate systems are inter-related). A
+      FrameSet makes it possible to obtain a \htmlref{Mapping}{Mapping} between any pair
+      of these Frames (i.e. to convert between any of the coordinate
+      systems which it describes).  The individual Frames are
+      identified within the FrameSet by an integer index, with Frames
+      being numbered consecutively from one as they are added to the
+      FrameSet.
+
+      Every FrameSet has a \texttt{"} base\texttt{"}  \htmlref{Frame}{Frame} and a \texttt{"} current\texttt{"}  Frame (which
+      are allowed to be the same). Any of the Frames may be nominated
+      to hold these positions, and the choice is determined by the
+      values of the FrameSet\texttt{'} s \htmlref{Base}{Base} and \htmlref{Current}{Current} attributes, which hold
+      the indices of the relevant Frames.  By default, the first Frame
+      added to a FrameSet is its base Frame, and the last one added is
+      its current Frame.
+
+      The base Frame describes the \texttt{"} native\texttt{"}  coordinate system of
+      whatever the FrameSet is used to calibrate (e.g. the pixel
+      coordinates of an image) and the current Frame describes the
+      \texttt{"} apparent\texttt{"}  coordinate system in which it should be viewed
+      (e.g. displayed, etc.). Any further Frames represent a library
+      of alternative coordinate systems, which may be selected by
+      making them current.
+
+      When a FrameSet is used in a context that requires a Frame,
+      (e.g. obtaining its \htmlref{Title}{Title} value, or number of axes), the current
+      Frame is used. A FrameSet may therefore be used in place of its
+      current Frame in most situations.
+
+      When a FrameSet is used in a context that requires a Mapping,
+      the Mapping used is the one between its base Frame and its
+      current Frame. Thus, a FrameSet may be used to convert \texttt{"} native\texttt{"}
+      coordinates into \texttt{"} apparent\texttt{"}  ones, and vice versa. Like any
+      Mapping, a FrameSet may also be inverted (see \htmlref{astInvert}{astInvert}), which
+      has the effect of interchanging its base and current Frames and
+      hence of reversing the Mapping between them.
+
+      Regions may be added into a FrameSet (since a \htmlref{Region}{Region} is a type of
+      Frame), either explicitly or as components within CmpFrames. In this
+      case the Mapping between a pair of Frames within a FrameSet will
+      include the effects of the clipping produced by any Regions included
+      in the path between the Frames.
+   }
+   \sstconstructor{
+      \htmlref{astFrameSet}{astFrameSet}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The FrameSet class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      FrameSet also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AllVariants}{AllVariants}: List of all variant mappings stored with current Frame
+
+         \sstitem
+         \htmlref{Base}{Base}: FrameSet base Frame index
+
+         \sstitem
+         \htmlref{Current}{Current}: FrameSet current Frame index
+
+         \sstitem
+         \htmlref{Nframe}{Nframe}: Number of Frames in a FrameSet
+
+         \sstitem
+         \htmlref{Variant}{Variant}: Name of variant mapping in use by current Frame
+
+      }
+      Every FrameSet also inherits any further attributes that belong
+      to its current Frame, regardless of that Frame\texttt{'} s class. (For
+      example, the \htmlref{Equinox}{Equinox} attribute, defined by the \htmlref{SkyFrame}{SkyFrame} class, is
+      inherited by any FrameSet which has a SkyFrame as its current
+      Frame.) The set of attributes belonging to a FrameSet may therefore
+      change when a new current Frame is selected.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Frames, the
+      following functions may also be applied to all FrameSets:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astAddFrame}{astAddFrame}: Add a Frame to a FrameSet to define a new coordinate
+         system
+
+         \sstitem
+         \htmlref{astAddVariant}{astAddVariant}: Add a variant Mapping to the current Frame
+
+         \sstitem
+         \htmlref{astGetFrame}{astGetFrame}: Obtain a pointer to a specified Frame in a FrameSet
+
+         \sstitem
+         \htmlref{astGetMapping}{astGetMapping}: Obtain a Mapping between two Frames in a FrameSet
+
+         \sstitem
+         \htmlref{astMirrorVariants}{astMirrorVariants}: Make the current Frame mirror variant Mappings in another Frame
+
+         \sstitem
+         \htmlref{astRemapFrame}{astRemapFrame}: Modify a Frame\texttt{'} s relationship to the other Frames in a
+         FrameSet
+
+         \sstitem
+         \htmlref{astRemoveFrame}{astRemoveFrame}: Remove a Frame from a FrameSet
+      }
+   }
+}
+\sstroutine{
+   GrismMap
+}{
+   Transform 1-dimensional coordinates using a grism dispersion equation
+}{
+   \sstdescription{
+      A GrismMap is a specialised form of \htmlref{Mapping}{Mapping} which transforms
+      1-dimensional coordinates using the spectral dispersion equation
+      described in FITS-WCS paper III \texttt{"} Representation of spectral
+      coordinates in FITS\texttt{"} . This describes the dispersion produced by
+      gratings, prisms and grisms.
+
+      When initially created, the forward transformation of a GrismMap
+      transforms input \texttt{"} grism parameter\texttt{"}  values into output wavelength
+      values. The \texttt{"} grism parameter\texttt{"}  is a dimensionless value which is
+      linearly related to position on the detector. It is defined in FITS-WCS
+      paper III as \texttt{"} the offset on the detector from the point of intersection
+      of the camera axis, measured in units of the effective local length\texttt{"} .
+      The units in which wavelength values are expected or returned is
+      determined by the values supplied for the \htmlref{GrismWaveR}{GrismWaveR}, \htmlref{GrismNRP}{GrismNRP} and
+      \htmlref{GrismG}{GrismG} attribute: whatever units are used for these attributes will
+      also be used for the wavelength values.
+   }
+   \sstconstructor{
+      \htmlref{astGrismMap}{astGrismMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The GrismMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      GrismMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{GrismNR}{GrismNR}: The refractive index at the reference wavelength
+
+         \sstitem
+         \htmlref{GrismNRP}{GrismNRP}: Rate of change of refractive index with wavelength
+
+         \sstitem
+         \htmlref{GrismWaveR}{GrismWaveR}: The reference wavelength
+
+         \sstitem
+         \htmlref{GrismAlpha}{GrismAlpha}: The angle of incidence of the incoming light
+
+         \sstitem
+         \htmlref{GrismG}{GrismG}: The grating ruling density
+
+         \sstitem
+         \htmlref{GrismM}{GrismM}: The interference order
+
+         \sstitem
+         \htmlref{GrismEps}{GrismEps}: The angle between the normal and the dispersion plane
+
+         \sstitem
+         \htmlref{GrismTheta}{GrismTheta}: Angle between normal to detector plane and reference ray
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The GrismMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Interval
+}{
+   A region representing an interval on one or more axes of a Frame
+}{
+   \sstdescription{
+      The Interval class implements a \htmlref{Region}{Region} which represents upper
+      and/or lower limits on one or more axes of a \htmlref{Frame}{Frame}. For a point to
+      be within the region represented by the Interval, the point must
+      satisfy all the restrictions placed on all the axes. The point is
+      outside the region if it fails to satisfy any one of the restrictions.
+      Each axis may have either an upper limit, a lower limit, both or
+      neither. If both limits are supplied but are in reverse order (so
+      that the lower limit is greater than the upper limit), then the
+      interval is an excluded interval, rather than an included interval.
+
+      Note, The Interval class makes no allowances for cyclic nature of
+      some coordinate systems (such as \htmlref{SkyFrame}{SkyFrame} coordinates). A \htmlref{Box}{Box}
+      should usually be used in these cases since this requires the user
+      to think about suitable upper and lower limits,
+   }
+   \sstconstructor{
+      \htmlref{astInterval}{astInterval}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Interval class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The Interval class does not define any new attributes beyond
+      those which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The Interval class does not define any new functions beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   IntraMap
+}{
+   Map points using a private transformation function
+}{
+   \sstdescription{
+      The IntraMap class provides a specialised form of \htmlref{Mapping}{Mapping} which
+      encapsulates a privately-defined coordinate transformation
+      other AST Mapping. This allows you to create Mappings that
+      perform any conceivable coordinate transformation.
+
+      However, an IntraMap is intended for use within a single program
+      or a private suite of software, where all programs have access
+      to the same coordinate transformation functions (i.e. can be
+      linked against them). IntraMaps should not normally be stored in
+      datasets which may be exported for processing by other software,
+      since that software will not have the necessary transformation
+      functions available, resulting in an error.
+
+      You must register any coordinate transformation functions to be
+      used using \htmlref{astIntraReg}{astIntraReg} before creating an IntraMap.
+   }
+   \sstconstructor{
+      \htmlref{astIntraMap}{astIntraMap} (also see astIntraReg)
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The IntraMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      IntraMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{IntraFlag}{IntraFlag}: IntraMap identification string
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The IntraMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   KeyMap
+}{
+   Store a set of key/value pairs
+}{
+   \sstdescription{
+      The KeyMap class is used to store a set of values with associated keys
+      which identify the values. The keys are strings. These may be case
+      sensitive or insensitive as selected by the \htmlref{KeyCase}{KeyCase} attribute, and
+      trailing spaces are ignored. The value associated with a key can be
+      integer (signed 4 and 2 byte, or unsigned 1 byte), floating point
+      (single or double precision),
+      void pointer,
+      character string or AST \htmlref{Object}{Object} pointer. Each
+      value can be a scalar or a one-dimensional vector. A KeyMap is
+      conceptually similar to a \htmlref{Mapping}{Mapping} in that a KeyMap transforms an
+      input into an output - the input is the key, and the output is the
+      value associated with the key. However, this is only a conceptual
+      similarity, and it should be noted that the KeyMap class inherits from
+      the Object class rather than the Mapping class. The methods of the
+      Mapping class cannot be used with a KeyMap.
+   }
+   \sstconstructor{
+      \htmlref{astKeyMap}{astKeyMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The KeyMap class inherits from the Object class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Objects, every
+      KeyMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{KeyCase}{KeyCase}: Sets the case in which keys are stored
+
+         \sstitem
+         \htmlref{KeyError}{KeyError}: \htmlref{Report}{Report} an error if the requested key does not exist?
+
+         \sstitem
+         \htmlref{SizeGuess}{SizeGuess}: The expected size of the KeyMap.
+
+         \sstitem
+         \htmlref{SortBy}{SortBy}: Determines how keys are sorted in a KeyMap.
+
+         \sstitem
+         \htmlref{MapLocked}{MapLocked}: Prevent new entries being added to the KeyMap?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Objects, the
+      following functions may also be applied to all KeyMaps:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astMapDefined}{astMapDefined}: Does a KeyMap contain a defined value for a key?
+
+         \sstitem
+         \htmlref{astMapGet0$<$X$>$}{astMapGet0$<$X$>$}: Get a named scalar entry from a KeyMap
+
+         \sstitem
+         \htmlref{astMapGet1$<$X$>$}{astMapGet1$<$X$>$}: Get a named vector entry from a KeyMap
+
+         \sstitem
+         \htmlref{astMapGetElem$<$X$>$}{astMapGetElem$<$X$>$}: Get an element of a named vector entry from a KeyMap
+
+         \sstitem
+         \htmlref{astMapHasKey}{astMapHasKey}: Does the KeyMap contain a named entry?
+
+         \sstitem
+         \htmlref{astMapKey}{astMapKey}: Return the key name at a given index in the KeyMap
+
+         \sstitem
+         \htmlref{astMapLenC}{astMapLenC}: Get the length of a named character entry in a KeyMap
+
+         \sstitem
+         \htmlref{astMapLength}{astMapLength}: Get the length of a named entry in a KeyMap
+
+         \sstitem
+         \htmlref{astMapCopy}{astMapCopy}: Copy entries from one KeyMap into another
+
+         \sstitem
+         \htmlref{astMapPut0$<$X$>$}{astMapPut0$<$X$>$}: Add a new scalar entry to a KeyMap
+
+         \sstitem
+         \htmlref{astMapPut1$<$X$>$}{astMapPut1$<$X$>$}: Add a new vector entry to a KeyMap
+
+         \sstitem
+         \htmlref{astMapPutElem$<$X$>$}{astMapPutElem$<$X$>$}: Puts a value into a vector entry in a KeyMap
+
+         \sstitem
+         \htmlref{astMapPutU}{astMapPutU}: Add a new entry to a KeyMap with an undefined value
+
+         \sstitem
+         \htmlref{astMapRemove}{astMapRemove}: Removed a named entry from a KeyMap
+
+         \sstitem
+         \htmlref{astMapRename}{astMapRename}: Rename an existing entry in a KeyMap
+
+         \sstitem
+         \htmlref{astMapSize}{astMapSize}: Get the number of entries in a KeyMap
+
+         \sstitem
+         \htmlref{astMapType}{astMapType}: Return the data type of a named entry in a map
+      }
+   }
+}
+\sstroutine{
+   LutMap
+}{
+   Transform 1-dimensional coordinates using a lookup table
+}{
+   \sstdescription{
+      A LutMap is a specialised form of \htmlref{Mapping}{Mapping} which transforms
+      1-dimensional coordinates by using linear interpolation in a
+      lookup table.
+
+      Each input coordinate value is first scaled to give the index of
+      an entry in the table by subtracting a starting value (the input
+      coordinate corresponding to the first table entry) and dividing
+      by an increment (the difference in input coordinate value
+      between adjacent table entries).
+
+      The resulting index will usually contain a fractional part, so
+      the output coordinate value is then generated by interpolating
+      linearly between the appropriate entries in the table. If the
+      index lies outside the range of the table, linear extrapolation
+      is used based on the two nearest entries (i.e. the two entries
+      at the start or end of the table, as appropriate). If either of the
+      entries used for the interplation has a value of AST\_\_BAD, then the
+      interpolated value is returned as AST\_\_BAD.
+
+      If the lookup table entries increase or decrease monotonically
+      (ignoring any flat sections), then the inverse transformation may
+      also be performed.
+   }
+   \sstconstructor{
+      \htmlref{astLutMap}{astLutMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The LutMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      LutMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{LutEpsilon}{LutEpsilon}: The relative error of the values in the table.
+
+         \sstitem
+         \htmlref{LutInterp}{LutInterp}: The interpolation method to use between table entries.
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The LutMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Mapping
+}{
+   Inter-relate two coordinate systems
+}{
+   \sstdescription{
+      This class provides the basic facilities for transforming a set
+      of coordinates (representing \texttt{"} input\texttt{"}  points) to give a new set
+      of coordinates (representing \texttt{"} output\texttt{"}  points).  It is used to
+      describe the relationship which exists between two different
+      coordinate systems and to implement operations which make use of
+      this (such as transforming coordinates and resampling grids of
+      data).  However, the Mapping class does not have a constructor
+      function of its own, as it is simply a container class for a
+      family of specialised Mappings which implement particular types
+      of coordinate transformation.
+   }
+   \sstconstructor{
+      None.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Mapping class inherits from the \htmlref{Object}{Object} class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Objects, every
+      Mapping also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Invert}{Invert}: Mapping inversion flag
+
+         \sstitem
+         \htmlref{IsLinear}{IsLinear}: Is the Mapping linear?
+
+         \sstitem
+         \htmlref{IsSimple}{IsSimple}: Has the Mapping been simplified?
+
+         \sstitem
+         \htmlref{Nin}{Nin}: Number of input coordinates for a Mapping
+
+         \sstitem
+         \htmlref{Nout}{Nout}: Number of output coordinates for a Mapping
+
+         \sstitem
+         \htmlref{Report}{Report}: Report transformed coordinates?
+
+         \sstitem
+         \htmlref{TranForward}{TranForward}: Forward transformation defined?
+
+         \sstitem
+         \htmlref{TranInverse}{TranInverse}: Inverse transformation defined?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Objects, the
+      following functions may also be applied to all Mappings:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astDecompose}{astDecompose}: Decompose a Mapping into two component Mappings
+
+         \sstitem
+         \htmlref{astTranGrid}{astTranGrid}: Transform a grid of positions
+
+         \sstitem
+         \htmlref{astInvert}{astInvert}: Invert a Mapping
+
+         \sstitem
+         \htmlref{astLinearApprox}{astLinearApprox}: Calculate a linear approximation to a Mapping
+
+         \sstitem
+         \htmlref{astMapBox}{astMapBox}: Find a bounding box for a Mapping
+
+         \sstitem
+         \htmlref{astMapSplit}{astMapSplit}: Split a Mapping up into parallel component Mappings
+
+         \sstitem
+         \htmlref{astQuadApprox}{astQuadApprox}: Calculate a quadratic approximation to a 2D Mapping
+
+         \sstitem
+         \htmlref{astRate}{astRate}: Calculate the rate of change of a Mapping output
+
+         \sstitem
+         \htmlref{astRebin$<$X$>$}{astRebin$<$X$>$}: Rebin a region of a data grid
+
+         \sstitem
+         \htmlref{astRebinSeq$<$X$>$}{astRebinSeq$<$X$>$}: Rebin a region of a sequence of data grids
+
+         \sstitem
+         \htmlref{astResample$<$X$>$}{astResample$<$X$>$}: Resample a region of a data grid
+
+         \sstitem
+         \htmlref{astRemoveRegions}{astRemoveRegions}: Remove any Regions from a Mapping
+
+         \sstitem
+         \htmlref{astSimplify}{astSimplify}: Simplify a Mapping
+
+         \sstitem
+         \htmlref{astTran1}{astTran1}: Transform 1-dimensional coordinates
+
+         \sstitem
+         \htmlref{astTran2}{astTran2}: Transform 2-dimensional coordinates
+
+         \sstitem
+         \htmlref{astTranN}{astTranN}: Transform N-dimensional coordinates
+
+         \sstitem
+         \htmlref{astTranP}{astTranP}: Transform N-dimensional coordinates held in separate arrays
+      }
+   }
+}
+\sstroutine{
+   MathMap
+}{
+   Transform coordinates using mathematical expressions
+}{
+   \sstdescription{
+      A MathMap is a \htmlref{Mapping}{Mapping} which allows you to specify a set of forward
+      and/or inverse transformation functions using arithmetic operations
+      and mathematical functions similar to those available in C. The
+      MathMap interprets these functions at run-time, whenever its forward
+      or inverse transformation is required. Because the functions are not
+      compiled in the normal sense (unlike an \htmlref{IntraMap}{IntraMap}), they may be used to
+      describe coordinate transformations in a transportable manner. A
+      MathMap therefore provides a flexible way of defining new types of
+      Mapping whose descriptions may be stored as part of a dataset and
+      interpreted by other programs.
+   }
+   \sstconstructor{
+      \htmlref{astMathMap}{astMathMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The MathMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      MathMap also has the following attributes:
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Seed}{Seed}: Random number seed
+
+         \sstitem
+         \htmlref{SimpFI}{SimpFI}: Forward-inverse MathMap pairs simplify?
+
+         \sstitem
+         \htmlref{SimpIF}{SimpIF}: Inverse-forward MathMap pairs simplify?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The MathMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   MatrixMap
+}{
+   Map coordinates by multiplying by a matrix
+}{
+   \sstdescription{
+      A MatrixMap is form of \htmlref{Mapping}{Mapping} which performs a general linear
+      transformation. Each set of input coordinates, regarded as a
+      column-vector, are pre-multiplied by a matrix (whose elements
+      are specified when the MatrixMap is created) to give a new
+      column-vector containing the output coordinates. If appropriate,
+      the inverse transformation may also be performed.
+   }
+   \sstconstructor{
+      \htmlref{astMatrixMap}{astMatrixMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The MatrixMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The MatrixMap class does not define any new attributes beyond
+      those which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The MatrixMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   NormMap
+}{
+   Normalise coordinates using a supplied Frame
+}{
+   \sstdescription{
+      The NormMap class implements a \htmlref{Mapping}{Mapping} which normalises coordinate
+      values using the
+      \htmlref{astNorm}{astNorm} function
+      of a supplied \htmlref{Frame}{Frame}. The number of inputs and outputs of a NormMap
+      are both equal to the number of axes in the supplied Frame.
+
+      The forward and inverse transformation of a NormMap are both
+      defined but are identical (that is, they do not form a real inverse
+      pair in that the inverse transformation does not undo the
+      normalisation, instead it reapplies it). However, the
+      \htmlref{astSimplify}{astSimplify}
+      function will replace neighbouring pairs of forward and inverse
+      NormMaps by a single \htmlref{UnitMap}{UnitMap} (so long as the Frames encapsulated by
+      the two NormMaps are equal - i.e. have the same class and the same
+      attribute values). This means, for instance, that if a \htmlref{CmpMap}{CmpMap} contains
+      a NormMap, the CmpMap will still cancel with its own inverse.
+   }
+   \sstconstructor{
+      \htmlref{astNormMap}{astNormMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The NormMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The NormMap class does not define any new attributes beyond
+      those which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The NormMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   NullRegion
+}{
+   A boundless region within a Frame
+}{
+   \sstdescription{
+      The NullRegion class implements a \htmlref{Region}{Region} with no bounds within a \htmlref{Frame}{Frame}.
+      If the \htmlref{Negated}{Negated} attribute of a NullRegion is false, the NullRegion
+      represents a Region containing no points. If the Negated attribute of
+      a NullRegion is true, the NullRegion represents an infinite Region
+      (that is, all points in the coordinate system are inside the NullRegion).
+   }
+   \sstconstructor{
+      \htmlref{astNullRegion}{astNullRegion}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The NullRegion class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The NullRegion class does not define any new attributes beyond
+      those which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The NullRegion class does not define any new functions beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   Object
+}{
+   Base class for all AST Objects
+}{
+   \sstdescription{
+      This class is the base class from which all other classes in the
+      AST library are derived. It provides all the basic Object
+      behaviour and Object manipulation facilities required throughout
+      the library. There is no Object constructor, however, as Objects
+      on their own are not useful.
+   }
+   \sstconstructor{
+      None.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Object base class does not inherit from any other class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      All Objects have the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Class}{Class}: Object class name
+
+         \sstitem
+         \htmlref{ID}{ID}: Object identification string
+
+         \sstitem
+         \htmlref{Ident}{Ident}: Permanent Object identification string
+
+         \sstitem
+         \htmlref{Nobject}{Nobject}: Number of Objects in class
+
+         \sstitem
+         \htmlref{ObjSize}{ObjSize}: The in-memory size of the Object in bytes
+
+         \sstitem
+         \htmlref{RefCount}{RefCount}: Count of active Object pointers
+
+         \sstitem
+         \htmlref{UseDefs}{UseDefs}: Allow use of default values for Object attributes?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The following functions may be applied to all Objects:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astAnnul}{astAnnul}: Annul a pointer to an Object
+
+         \sstitem
+         \htmlref{astBegin}{astBegin}: Begin a new AST context
+
+         \sstitem
+         \htmlref{astClear}{astClear}: Clear attribute values for an Object
+
+         \sstitem
+         \htmlref{astClone}{astClone}: Clone a pointer to an Object
+
+         \sstitem
+         \htmlref{astCopy}{astCopy}: Copy an Object
+
+         \sstitem
+         \htmlref{astDelete}{astDelete}: Delete an Object
+
+         \sstitem
+         \htmlref{astEnd}{astEnd}: End an AST context
+
+         \sstitem
+         \htmlref{astEscapes}{astEscapes}: Control whether graphical escape sequences are removed
+
+         \sstitem
+         \htmlref{astExempt}{astExempt}: Exempt an Object pointer from AST context handling
+
+         \sstitem
+         \htmlref{astExport}{astExport}: Export an Object pointer to an outer context
+
+         \sstitem
+         \htmlref{astGet$<$X$>$}{astGet$<$X$>$}: Get an attribute value for an Object
+
+         \sstitem
+         \htmlref{astHasAttribute}{astHasAttribute}: Test if an Object has a named attribute
+
+         \sstitem
+         \htmlref{astImport}{astImport}: Import an Object pointer to the current context
+
+         \sstitem
+         \htmlref{astIsA$<$Class$>$}{astIsA$<$Class$>$}: Test class membership
+
+         \sstitem
+         \htmlref{astLock}{astLock}: Lock an Object for use by the calling thread
+
+         \sstitem
+         \htmlref{astToString}{astToString}: Create an in-memory serialisation of an Object
+
+         \sstitem
+         \htmlref{astSame}{astSame}: Do two AST pointers refer to the same Object?
+
+         \sstitem
+         \htmlref{astSet}{astSet}: Set attribute values for an Object
+
+         \sstitem
+         \htmlref{astSet$<$X$>$}{astSet$<$X$>$}: Set an attribute value for an Object
+
+         \sstitem
+         \htmlref{astShow}{astShow}: Display a textual representation of an Object on standard
+         output
+
+         \sstitem
+         \htmlref{astTest}{astTest}: Test if an attribute value is set for an Object
+
+         \sstitem
+         \htmlref{astTune}{astTune}: Set or get an integer AST tuning parameter
+
+         \sstitem
+         \htmlref{astTuneC}{astTuneC}: Set or get a character AST tuning parameter
+
+         \sstitem
+         \htmlref{astUnlock}{astUnlock}: Unlock an Object for use by other threads
+
+         \sstitem
+         \htmlref{astFromString}{astFromString}: Re-create an Object from an in-memory serialisation
+
+         \sstitem
+         \htmlref{astVersion}{astVersion}: Return the verson of the AST library being used.
+      }
+   }
+}
+\sstroutine{
+   PcdMap
+}{
+   Apply 2-dimensional pincushion/barrel distortion
+}{
+   \sstdescription{
+      A PcdMap is a non-linear \htmlref{Mapping}{Mapping} which transforms 2-dimensional
+      positions to correct for the radial distortion introduced by some
+      cameras and telescopes. This can take the form either of pincushion
+      or barrel distortion, and is characterized by a single distortion
+      coefficient.
+
+      A PcdMap is specified by giving this distortion coefficient and the
+      coordinates of the centre of the radial distortion. The forward
+      transformation of a PcdMap applies the distortion:
+
+         RD = R $*$ ( 1 $+$ C $*$ R $*$ R )
+
+      where R is the undistorted radial distance from the distortion
+      centre (specified by attribute PcdCen), RD is the radial distance
+      from the same centre in the presence of distortion, and C is the
+      distortion coefficient (given by attribute \htmlref{Disco}{Disco}).
+
+      The inverse transformation of a PcdMap removes the distortion
+      produced by the forward transformation. The expression used to derive
+      R from RD is an approximate inverse of the expression above.
+   }
+   \sstconstructor{
+      \htmlref{astPcdMap}{astPcdMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The PcdMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      PcdMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Disco}{Disco}: PcdMap pincushion/barrel distortion coefficient
+
+         \sstitem
+         \htmlref{PcdCen(axis)}{PcdCen(axis)}: Centre coordinates of pincushion/barrel distortion
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The PcdMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   PermMap
+}{
+   Coordinate permutation Mapping
+}{
+   \sstdescription{
+      A PermMap is a \htmlref{Mapping}{Mapping} which permutes the order of coordinates,
+      and possibly also changes the number of coordinates, between its
+      input and output.
+
+      In addition to permuting the coordinate order, a PermMap may
+      also assign constant values to coordinates. This is useful when
+      the number of coordinates is being increased as it allows fixed
+      values to be assigned to any new ones.
+   }
+   \sstconstructor{
+      \htmlref{astPermMap}{astPermMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The PermMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The PermMap class does not define any new attributes beyond
+      those which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The PermMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Plot
+}{
+   Provide facilities for 2D graphical output
+}{
+   \sstdescription{
+      This class provides facilities for producing 2D graphical output.
+      A Plot is a specialised form of \htmlref{FrameSet}{FrameSet}, in which the base
+      \htmlref{Frame}{Frame} describes a \texttt{"} graphical\texttt{"}  coordinate system and is
+      associated with a rectangular plotting area in the underlying
+      graphics system. This plotting area is where graphical output
+      appears. It is defined when the Plot is created.
+
+      The current Frame of a Plot describes a \texttt{"} physical\texttt{"}  coordinate
+      system, which is the coordinate system in which plotting
+      operations are specified. The results of each plotting operation
+      are automatically transformed into graphical coordinates so as
+      to appear in the plotting area (subject to any clipping which
+      may be in effect).
+
+      Because the \htmlref{Mapping}{Mapping} between physical and graphical coordinates
+      may often be non-linear, or even discontinuous, most plotting
+      does not result in simple straight lines. The basic plotting
+      element is therefore not a straight line, but a geodesic curve
+      (see \htmlref{astCurve}{astCurve}, \htmlref{astGenCurve}{astGenCurve} and \htmlref{astPolyCurve}{astPolyCurve}). A Plot also provides facilities for
+      drawing markers or symbols (\htmlref{astMark}{astMark}), text (\htmlref{astText}{astText}) and grid lines
+      (\htmlref{astGridLine}{astGridLine}). It is also possible to draw curvilinear axes with
+      optional coordinate grids (\htmlref{astGrid}{astGrid}).
+      A range of Plot attributes is available to allow precise control
+      over the appearance of graphical output produced by these
+      functions.
+
+      You may select different physical coordinate systems in which to
+      plot (including the native graphical coordinate system itself)
+      by selecting different Frames as the current Frame of a Plot,
+      using its \htmlref{Current}{Current} attribute.  You may also set up clipping (see
+      \htmlref{astClip}{astClip}) to limit the extent of any plotting you perform, and
+      this may be done in any of the coordinate systems associated
+      with the Plot, not necessarily the one you are plotting in.
+
+      Like any FrameSet, a Plot may also be used as a Frame. In this
+      case, it behaves like its current Frame, which describes the
+      physical coordinate system.
+
+      When used as a Mapping, a Plot describes the inter-relation
+      between graphical coordinates (its base Frame) and physical
+      coordinates (its current Frame).  It differs from a normal
+      FrameSet, however, in that an attempt to transform points which
+      lie in clipped areas of the Plot will result in bad coordinate
+      values (AST\_\_BAD).
+   }
+   \sstconstructor{
+      \htmlref{astPlot}{astPlot}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Plot class inherits from the FrameSet class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all FrameSets, every
+      Plot also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         Abbrev: Abbreviate leading fields?
+
+         \sstitem
+         \htmlref{Border}{Border}: Draw a border around valid regions of a Plot?
+
+         \sstitem
+         \htmlref{Clip}{Clip}: Clip lines and/or markers at the Plot boundary?
+
+         \sstitem
+         \htmlref{ClipOp}{ClipOp}: Combine Plot clipping limits using a boolean OR?
+
+         \sstitem
+         \htmlref{Colour(element)}{Colour(element)}: Colour index for a Plot element
+
+         \sstitem
+         \htmlref{DrawAxes(axis)}{DrawAxes(axis)}: Draw axes for a Plot?
+
+         \sstitem
+         \htmlref{DrawTitle}{DrawTitle}: Draw a title for a Plot?
+
+         \sstitem
+         \htmlref{Escape}{Escape}: Allow changes of character attributes within strings?
+
+         \sstitem
+         \htmlref{Edge(axis)}{Edge(axis)}: Which edges to label in a Plot
+
+         \sstitem
+         \htmlref{Font(element)}{Font(element)}: Character font for a Plot element
+
+         \sstitem
+         \htmlref{Gap(axis)}{Gap(axis)}: \htmlref{Interval}{Interval} between linearly spaced major axis values
+
+         \sstitem
+         \htmlref{Grf}{Grf}: Select the graphics interface to use.
+
+         \sstitem
+         \htmlref{Grid}{Grid}: Draw grid lines for a Plot?
+
+         \sstitem
+         \htmlref{Invisible}{Invisible}: Draw graphics in invisible ink?
+
+         \sstitem
+         \htmlref{LabelAt(axis)}{LabelAt(axis)}: Where to place numerical labels for a Plot
+
+         \sstitem
+         \htmlref{LabelUnits(axis)}{LabelUnits(axis)}: Use axis unit descriptions in a Plot?
+
+         \sstitem
+         \htmlref{LabelUp(axis)}{LabelUp(axis)}: Draw numerical Plot labels upright?
+
+         \sstitem
+         \htmlref{Labelling}{Labelling}: Label and tick placement option for a Plot
+
+         \sstitem
+         \htmlref{LogGap(axis)}{LogGap(axis)}: Interval between logarithmically spaced major axis values
+
+         \sstitem
+         \htmlref{LogPlot(axis)}{LogPlot(axis)}: Map the plot onto the screen logarithmically?
+
+         \sstitem
+         \htmlref{LogTicks(axis)}{LogTicks(axis)}: Space the major tick marks logarithmically?
+
+         \sstitem
+         \htmlref{MajTickLen(axis)}{MajTickLen(axis)}: Length of major tick marks for a Plot
+
+         \sstitem
+         \htmlref{MinTickLen(axis)}{MinTickLen(axis)}: Length of minor tick marks for a Plot
+
+         \sstitem
+         \htmlref{MinTick(axis)}{MinTick(axis)}: Density of minor tick marks for a Plot
+
+         \sstitem
+         \htmlref{NumLab(axis)}{NumLab(axis)}: Draw numerical axis labels for a Plot?
+
+         \sstitem
+         \htmlref{NumLabGap(axis)}{NumLabGap(axis)}: Spacing of numerical axis labels for a Plot
+
+         \sstitem
+         \htmlref{Size(element)}{Size(element)}: Character size for a Plot element
+
+         \sstitem
+         \htmlref{Style(element)}{Style(element)}: Line style for a Plot element
+
+         \sstitem
+         \htmlref{TextLab(axis)}{TextLab(axis)}: Draw descriptive axis labels for a Plot?
+
+         \sstitem
+         \htmlref{TextLabGap(axis)}{TextLabGap(axis)}: Spacing of descriptive axis labels for a Plot
+
+         \sstitem
+         \htmlref{TickAll}{TickAll}: Draw tick marks on all edges of a Plot?
+
+         \sstitem
+         \htmlref{TitleGap}{TitleGap}: Vertical spacing for a Plot title
+
+         \sstitem
+         \htmlref{Tol}{Tol}: Plotting tolerance
+
+         \sstitem
+         \htmlref{Width(element)}{Width(element)}: Line width for a Plot element
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all FrameSets, the
+      following functions may also be applied to all Plots:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astBBuf}{astBBuf}: Begin a new graphical buffering context
+
+         \sstitem
+         \htmlref{astBorder}{astBorder}: Draw a border around valid regions of a Plot
+
+         \sstitem
+         \htmlref{astBoundingBox}{astBoundingBox}: Returns a bounding box for previously drawn graphics
+
+         \sstitem
+         \htmlref{astClip}{astClip}: Set up or remove clipping for a Plot
+
+         \sstitem
+         \htmlref{astCurve}{astCurve}: Draw a geodesic curve
+
+         \sstitem
+         \htmlref{astEBuf}{astEBuf}: End the current graphical buffering context
+
+         \sstitem
+         \htmlref{astGenCurve}{astGenCurve}: Draw a generalized curve
+
+         \sstitem
+         \htmlref{astGetGrfContext}{astGetGrfContext}: Get the graphics context for a Plot
+
+         \sstitem
+         \htmlref{astGrfPop}{astGrfPop}: Retrieve previously saved graphics functions
+
+         \sstitem
+         \htmlref{astGrfPush}{astGrfPush}: Save the current graphics functions
+
+         \sstitem
+         \htmlref{astGrfSet}{astGrfSet}: Register a graphics routine for use by a Plot
+
+         \sstitem
+         \htmlref{astGrid}{astGrid}: Draw a set of labelled coordinate axes
+
+         \sstitem
+         \htmlref{astGridLine}{astGridLine}: Draw a grid line (or axis) for a Plot
+
+         \sstitem
+         \htmlref{astMark}{astMark}: Draw a set of markers for a Plot
+
+         \sstitem
+         \htmlref{astPolyCurve}{astPolyCurve}: Draw a series of connected geodesic curves
+
+         \sstitem
+         \htmlref{astRegionOutline}{astRegionOutline}: Draw the outline of an AST \htmlref{Region}{Region}
+
+         \sstitem
+         \htmlref{astText}{astText}: Draw a text string for a Plot
+      }
+   }
+   \sstdiytopic{
+      Graphical Elements
+   }{
+      The colour index, character font, character size, line style and
+      line width used for plotting can be set independently for
+      various elements of the graphical output produced by a Plot.
+      The different graphical elements are identified by appending the
+      strings listed below as subscripts to the Plot attributes
+      Colour(element), Font(element), Size(element), Style(element)
+      and Width(element). These strings are case-insensitive and
+      unambiguous abbreviations may be used. Elements of the graphical
+      output which relate to individual axes can be referred to either
+      independently (e.g. \texttt{"} (Grid1)\texttt{"}  and \texttt{"} (Grid2)\texttt{"}  ) or together (e.g.
+      \texttt{"} (Grid)\texttt{"} ):
+
+      \sstitemlist{
+
+         \sstitem
+         Axes: \htmlref{Axis}{Axis} lines drawn through tick marks using astGrid
+
+         \sstitem
+         Axis1: Axis line drawn through tick marks on axis 1 using astGrid
+
+         \sstitem
+         Axis2: Axis line drawn through tick marks on axis 2 using astGrid
+
+         \sstitem
+         Border: The Plot border drawn using astBorder, astGrid or astRegionOutline
+
+         \sstitem
+         Curves: Geodesic curves drawn using astCurve, astGenCurve or astPolyCurve
+
+         \sstitem
+         Grid: Grid lines drawn using astGridLine or astGrid
+
+         \sstitem
+         Grid1: Grid lines which cross axis 1, drawn using astGridLine or astGrid
+
+         \sstitem
+         Grid2: Grid lines which cross axis 2, drawn using astGridLine or astGrid
+
+         \sstitem
+         Markers: Graphical markers (symbols) drawn using astMark
+
+         \sstitem
+         NumLab: Numerical axis labels drawn using astGrid
+
+         \sstitem
+         NumLab1: Numerical labels for axis 1 drawn using astGrid
+
+         \sstitem
+         NumLab2: Numerical labels for axis 2 drawn using astGrid
+
+         \sstitem
+         Strings: Text strings drawn using astText
+
+         \sstitem
+         TextLab: Descriptive axis labels drawn using astGrid
+
+         \sstitem
+         TextLab1: Descriptive label for axis 1 drawn using astGrid
+
+         \sstitem
+         TextLab2: Descriptive label for axis 2 drawn using astGrid
+
+         \sstitem
+         Ticks: Tick marks (both major and minor) drawn using astGrid
+
+         \sstitem
+         Ticks1: Tick marks (both major and minor) for axis 1 drawn using astGrid
+
+         \sstitem
+         Ticks2: Tick marks (both major and minor) for axis 2 drawn using astGrid
+
+         \sstitem
+         \htmlref{Title}{Title}: The Plot title drawn using astGrid
+      }
+   }
+}
+\sstroutine{
+   Plot3D
+}{
+   Provide facilities for 3D graphical output
+}{
+   \sstdescription{
+      A Plot3D is a specialised form of \htmlref{Plot}{Plot} that provides facilities
+      for producing 3D graphical output, including fully annotated 3D
+      coordinate grids. The base \htmlref{Frame}{Frame} in a Plot3D describes a 3-dimensional
+      \texttt{"} graphical\texttt{"}  coordinate system. The axes of this coordinate system are
+      assumed to be right-handed (that is, if X appears horizontally to the
+      right and Y vertically upwards, then Z is out of the screen towards
+      the viewer), and are assumed to be equally scaled (that is, the same
+      units are used to measure positions on each of the 3 axes). The upper
+      and lower bounds of a volume within this graphical coordinate system
+      is specified when the Plot3D is created, and all subsequent graphics
+      are \texttt{"} drawn\texttt{"}  in this volume.
+
+      The Plot3D class does not itself include any ability to draw on a
+      graphics device. Instead it calls upon function in an externally
+      supplied module (the \texttt{"} grf3d\texttt{"}  module) to do the required drawing.
+      A module should be written that implements the functions of the
+      grf3d interface using the facilities of a specific graphics system
+      This module should then be linked into the application so that the
+      Plot3D class can use its functions (see the description of the
+      \htmlref{ast\_link}{ast\_link} commands for details of how to do this). The grf3d interface
+      defines a few simple functions for drawing primitives such as straight
+      lines, markers and character strings. These functions all accept
+      positions in the 3D graphics coordinate system (the base Frame of the
+      Plot3D), and so the grf3d module must also manage the projection of
+      these 3D coordinates onto the 2D viewing surface, including the choice
+      of \texttt{"} eye\texttt{"} /\texttt{"} camera\texttt{"}  position, direction of viewing, etc. The AST
+      library includes a sample implementation of the grf3d interface
+      based on the PGPLOT graphics system (see file grf3d\_pgplot.c). This
+      implementation also serves to document the grf3d interface itself and
+      should be consulted for details before writing a new implementation.
+
+      The current Frame of a Plot3D describes a \texttt{"} physical\texttt{"}  3-dimensional
+      coordinate system, which is the coordinate system in which plotting
+      operations are specified when invoking the methods of the Plot3D
+      class. The results of each plotting operation are automatically
+      transformed into 3D graphical coordinates before being plotted
+      using the facilities of the grf3d module linked into the application.
+      Note, at least one of the three axes of the current Frame must be
+      independent of the other two current Frame axes.
+
+      You may select different physical coordinate systems in which to
+      plot (including the native graphical coordinate system itself)
+      by selecting different Frames as the current Frame of a Plot3D,
+      using its \htmlref{Current}{Current} attribute.
+
+      Like any \htmlref{FrameSet}{FrameSet}, a Plot3D may also be used as a Frame. In this
+      case, it behaves like its current Frame, which describes the
+      physical coordinate system.
+
+      When used as a \htmlref{Mapping}{Mapping}, a Plot3D describes the inter-relation
+      between 3D graphical coordinates (its base Frame) and 3D physical
+      coordinates (its current Frame).
+
+      Although the Plot3D class inherits from the Plot class, several of
+      the facilities of the Plot class are not available in the Plot3D
+      class, and an error will be reported if any attempt is made to use
+      them. Specifically, the Plot3D class does not support clipping
+      using the
+      \htmlref{astClip}{astClip} function.
+      Nor does it support the specification of graphics primitive functions
+      at run-time using the
+      \htmlref{astGrfSet}{astGrfSet}, \htmlref{astGrfPop}{astGrfPop}, \htmlref{astGrfPush}{astGrfPush} and \htmlref{astGetGrfContext}{astGetGrfContext} functions.
+   }
+   \sstconstructor{
+      \htmlref{astPlot3D}{astPlot3D}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Plot3D class inherits from the Plot class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Plots, every
+      Plot3D also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         Norm: Normal vector defining the 2D plane used for text and markers
+
+         \sstitem
+         \htmlref{RootCorner}{RootCorner}: Specifies which edges of the 3D box should be annotated.
+
+      }
+      Some attributes of the Plot class refer to specific physical
+      coordinate axes (e.g. Gap, LabelUp, DrawAxes, etc). For a basic
+      Plot, the axis index must be 1 or 2, but for a Plot3D the axis index
+      can be 1, 2 or 3.
+
+      Certain Plot attributes are ignored by the Plot3D class (e.g. Edge,
+      \htmlref{DrawTitle}{DrawTitle}, \htmlref{TitleGap}{TitleGap}, etc). Consult the Plot attribute documentation
+      for details. All other Plot attributes can be set for a specific
+      plane of the 3-d plot by appending one of the strings \texttt{"} \_XY\texttt{"} , \texttt{"} \_XZ\texttt{"}
+      or \texttt{"} \_YZ\texttt{"}  to the end of the Plot attribute name. For instance,
+      \texttt{"} \htmlref{Grid}{Grid}\_YZ\texttt{"}  refers to the \texttt{"} Grid\texttt{"}  attribute for the plane spanning
+      the second (Y) and third (Z) axes of the 3-d plot.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+       The Plot3D class does not define any new functions beyond those
+       which are applicable to all Plots. Note, however, that the
+       following methods inherited from the Plot class cannot be used with
+       a Plot3D and will report an error if called:
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astBoundingBox}{astBoundingBox}, astClip, \htmlref{astCurve}{astCurve}, \htmlref{astGenCurve}{astGenCurve},
+         astGetGrfContext, astGrfPop, astGrfPush, astGrfSet, \htmlref{astGridLine}{astGridLine},
+         \htmlref{astPolyCurve}{astPolyCurve}.
+      }
+   }
+}
+\sstroutine{
+   PointList
+}{
+   A collection of points in a Frame
+}{
+   \sstdescription{
+      The PointList class implements a \htmlref{Region}{Region} which represents a collection
+      of points in a \htmlref{Frame}{Frame}.
+   }
+   \sstconstructor{
+      \htmlref{astPointList}{astPointList}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The PointList class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Regions, every
+      PointList also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{ListSize}{ListSize}: The number of positions stored in the PointList
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The PointList class does not define any new functions beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   PolyMap
+}{
+   Map coordinates using polynomial functions
+}{
+   \sstdescription{
+      A PolyMap is a form of \htmlref{Mapping}{Mapping} which performs a general polynomial
+      transformation.  Each output coordinate is a polynomial function of
+      all the input coordinates. The coefficients are specified separately
+      for each output coordinate. The forward and inverse transformations
+      are defined independantly by separate sets of coefficients. If no
+      inverse transformation is supplied, an iterative method can be used
+      to evaluate the inverse based only on the forward transformation.
+   }
+   \sstconstructor{
+      \htmlref{astPolyMap}{astPolyMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The PolyMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      PolyMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{IterInverse}{IterInverse}: Provide an iterative inverse transformation?
+
+         \sstitem
+         \htmlref{NiterInverse}{NiterInverse}: Maximum number of iterations for iterative inverse
+
+         \sstitem
+         \htmlref{TolInverse}{TolInverse}: Target relative error for iterative inverse
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Objects, the
+      following functions may also be applied to all Mappings:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astPolyTran}{astPolyTran}: Fit a PolyMap inverse or forward transformation
+      }
+   }
+}
+\sstroutine{
+   Polygon
+}{
+   A polygonal region within a 2-dimensional Frame
+}{
+   \sstdescription{
+      The Polygon class implements a polygonal area, defined by a
+      collection of vertices, within a 2-dimensional \htmlref{Frame}{Frame}. The vertices
+      are connected together by geodesic curves within the encapsulated Frame.
+      For instance, if the encapsulated Frame is a simple Frame then the
+      geodesics will be straight lines, but if the Frame is a \htmlref{SkyFrame}{SkyFrame} then
+      the geodesics will be great circles. Note, the vertices must be
+      supplied in an order such that the inside of the polygon is to the
+      left of the boundary as the vertices are traversed. Supplying them
+      in the reverse order will effectively negate the polygon.
+
+      Within a SkyFrame, neighbouring vertices are always joined using the
+      shortest path. Thus if an edge of 180 degrees or more in length is
+      required, it should be split into section each of which is less
+      than 180 degrees. The closed path joining all the vertices in order
+      will divide the celestial sphere into two disjoint regions. The
+      inside of the polygon is the region which is circled in an
+      anti-clockwise manner (when viewed from the inside of the celestial
+      sphere) when moving through the list of vertices in the order in
+      which they were supplied when the Polygon was created (i.e. the
+      inside is to the left of the boundary when moving through the
+      vertices in the order supplied).
+   }
+   \sstconstructor{
+      \htmlref{astPolygon}{astPolygon}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Polygon class inherits from the \htmlref{Region}{Region} class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Regions, every
+      Polygon also has the following attributes:
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{SimpVertices}{SimpVertices}: Simplify by transforming the vertices?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Regions, the
+      following functions may also be applied to all Polygons:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astDownsize}{astDownsize}: Reduce the number of vertices in a Polygon.
+
+         \sstitem
+         \htmlref{astConvex$<$X$>$}{astConvex$<$X$>$}: Create a Polygon giving the convex hull of a pixel array
+
+         \sstitem
+         \htmlref{astOutline$<$X$>$}{astOutline$<$X$>$}: Create a Polygon outlining values in a pixel array
+      }
+   }
+}
+\sstroutine{
+   Prism
+}{
+   An extrusion of a region into higher dimensions
+}{
+   \sstdescription{
+      A Prism is a \htmlref{Region}{Region} which represents an extrusion of an existing Region
+      into one or more orthogonal dimensions (specified by another Region).
+      If the Region to be extruded has N axes, and the Region defining the
+      extrusion has M axes, then the resulting Prism will have (M$+$N) axes.
+      A point is inside the Prism if the first N axis values correspond to
+      a point inside the Region being extruded, and the remaining M axis
+      values correspond to a point inside the Region defining the extrusion.
+
+      As an example, a cylinder can be represented by extruding an existing
+      \htmlref{Circle}{Circle}, using an \htmlref{Interval}{Interval} to define the extrusion. Ih this case, the
+      Interval would have a single axis and would specify the upper and
+      lower limits of the cylinder along its length.
+   }
+   \sstconstructor{
+      \htmlref{astPrism}{astPrism}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Prism class inherits from the Region class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The Prism class does not define any new attributes beyond those
+      which are applicable to all Regions.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The Prism class does not define any new functions beyond those
+      which are applicable to all Regions.
+   }
+}
+\sstroutine{
+   RateMap
+}{
+   Mapping which represents differentiation
+}{
+   \sstdescription{
+      A RateMap is a \htmlref{Mapping}{Mapping} which represents a single element of the
+      Jacobian matrix of another Mapping. The Mapping for which the
+      Jacobian is required is specified when the new RateMap is created,
+      and is referred to as the \texttt{"} encapsulated Mapping\texttt{"}  below.
+
+      The number of inputs to a RateMap is the same as the number of inputs
+      to its encapsulated Mapping. The number of outputs from a RateMap
+      is always one. This one output equals the rate of change of a
+      specified output of the encapsulated Mapping with respect to a
+      specified input of the encapsulated Mapping (the input and output
+      to use are specified when the RateMap is created).
+
+      A RateMap which has not been inverted does not define an inverse
+      transformation. If a RateMap has been inverted then it will define
+      an inverse transformation but not a forward transformation.
+   }
+   \sstconstructor{
+      \htmlref{astRateMap}{astRateMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The RateMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The RateMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The RateMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Region
+}{
+   Represents a region within a coordinate system
+}{
+   \sstdescription{
+      This class provides the basic facilities for describing a region within
+      a specified coordinate system. However, the Region class does not
+      have a constructor function of its own, as it is simply a container
+      class for a family of specialised sub-classes such as \htmlref{Circle}{Circle}, \htmlref{Box}{Box}, etc,
+      which implement Regions with particular shapes.
+
+      All sub-classes of Region require a \htmlref{Frame}{Frame} to be supplied when the Region
+      is created. This Frame describes the coordinate system in which the
+      Region is defined, and is referred to as the \texttt{"} encapsulated Frame\texttt{"}  below.
+      Constructors will also typically required one or more positions to be
+      supplied which define the location and extent of the region. These
+      positions must be supplied within the encapsulated Frame.
+
+      The Region class inherits from the Frame class, and so a Region can be
+      supplied where-ever a Frame is expected. In these cases, supplying a
+      Region is equivalent to supplying a reference to its encapsulated Frame.
+      Thus all the methods of the Frame class can be used on the Region class.
+      For instance, the
+      \htmlref{astFormat}{astFormat} function
+      may be used on a Region to format an axis value.
+
+      In addition, since Frame inherits from \htmlref{Mapping}{Mapping}, a Region is also a sort
+      of Mapping. Transforming positions by supplying a Region to one of the
+      astTran$<$X$>$ functions
+      is the way to determine if a given position is inside or outside the
+      Region. When used as a Mapping, most classes of Frame are equivalent to
+      a \htmlref{UnitMap}{UnitMap}. However, the Region class modifies this behaviour so that a
+      Region acts like a UnitMap only for input positions which are within the
+      area represented by the Region. Input positions which are outside the
+      area produce bad output values (i.e. the output values are equal to
+      AST\_\_BAD). This behaviour is the same for both the forward and the
+      inverse transformation. In this sense the \texttt{"} inverse transformation\texttt{"}
+      is not a true inverse of the forward transformation, since applying
+      the forward transformation to a point outside the Region, and then
+      applying the inverse transformation results, in a set of AST\_\_BAD axis
+      values rather than the original axis values. If required, the
+      \htmlref{astRemoveRegions}{astRemoveRegions}
+      function can be used to remove the \texttt{"} masking\texttt{"}  effect of any Regions
+      contained within a compound Mapping or \htmlref{FrameSet}{FrameSet}. It does this by
+      replacing each Region with a UnitMap or equivalent Frame (depending
+      on the context in which the Region is used).
+
+      If the coordinate system represented by the Region is changed (by
+      changing the values of one or more of the attribute which the Region
+      inherits from its encapsulated Frame), the area represented by
+      the Region is mapped into the new coordinate system. For instance, let\texttt{'} s
+      say a Circle (a subclass of Region) is created, a \htmlref{SkyFrame}{SkyFrame} being
+      supplied to the constructor so that the Circle describes a circular
+      area on the sky in FK4 equatorial coordinates. Since Region inherits
+      from Frame, the Circle will have a \htmlref{System}{System} attribute and this attribute
+      will be set to \texttt{"} FK4\texttt{"} . If the System attribute of the Region is then
+      changed from FK4 to FK5, the circular area represented by the Region
+      will automatically be mapped from the FK4 system into the FK5 system.
+      In general, changing the coordinate system in this way may result in the
+      region changing shape - for instance, a circle may change into an
+      ellipse if the transformation from the old to the new coordinate system
+      is linear but with different scales on each axis. Thus the specific
+      class of a Region cannot be used as a guarantee of the shape in any
+      particular coordinate system. If the
+      \htmlref{astSimplify}{astSimplify} function
+      is used on a Region, it will endeavour to return a new Region of
+      a sub-class which accurately describes the shape in the current
+      coordinate system of the Region (but this may not always be possible).
+
+      It is possible to negate an existing Region so that it represents all
+      areas of the encapsulated Frame except for the area specified when
+      the Region was created.
+   }
+   \sstconstructor{
+      None.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Region class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      Region also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Adaptive}{Adaptive}: Should the area adapt to changes in the coordinate system?
+
+         \sstitem
+         \htmlref{Negated}{Negated}: Has the original region been negated?
+
+         \sstitem
+         \htmlref{Closed}{Closed}: Should the boundary be considered to be inside the region?
+
+         \sstitem
+         \htmlref{MeshSize}{MeshSize}: Number of points used to create a mesh covering the Region
+
+         \sstitem
+         \htmlref{FillFactor}{FillFactor}: Fraction of the Region which is of interest
+
+         \sstitem
+         \htmlref{Bounded}{Bounded}: Is the Region bounded?
+
+      }
+      Every Region also inherits any further attributes that belong
+      to the encapsulated Frame, regardless of that Frame\texttt{'} s class. (For
+      example, the \htmlref{Equinox}{Equinox} attribute, defined by the SkyFrame class, is
+      inherited by any Region which represents a SkyFrame.)
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Frames, the
+      following functions may also be applied to all Regions:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astGetRegionBounds}{astGetRegionBounds}: Get the bounds of a Region
+
+         \sstitem
+         \htmlref{astGetRegionFrame}{astGetRegionFrame}: Get a copy of the Frame represent by a Region
+
+         \sstitem
+         \htmlref{astGetRegionFrameSet}{astGetRegionFrameSet}: Get a copy of the Frameset encapsulated by a Region
+
+         \sstitem
+         \htmlref{astGetRegionMesh}{astGetRegionMesh}: Get a mesh of points covering a Region
+
+         \sstitem
+         \htmlref{astGetRegionPoints}{astGetRegionPoints}: Get the positions that define a Region
+
+         \sstitem
+         \htmlref{astGetUnc}{astGetUnc}: Obtain uncertainty information from a Region
+
+         \sstitem
+         \htmlref{astMapRegion}{astMapRegion}: Transform a Region into a new coordinate system
+
+         \sstitem
+         \htmlref{astNegate}{astNegate}: Toggle the value of the Negated attribute
+
+         \sstitem
+         \htmlref{astOverlap}{astOverlap}: Determines the nature of the overlap between two Regions
+
+         \sstitem
+         \htmlref{astMask$<$X$>$}{astMask$<$X$>$}: Mask a region of a data grid
+
+         \sstitem
+         \htmlref{astSetUnc}{astSetUnc}: Associate a new uncertainty with a Region
+
+         \sstitem
+         \htmlref{astShowMesh}{astShowMesh}: Display a mesh of points on the surface of a Region
+      }
+   }
+}
+\sstroutine{
+   SelectorMap
+}{
+   A Mapping that locates positions within one of a set of alternate
+   Regions
+}{
+   \sstdescription{
+      A SelectorMap is a \htmlref{Mapping}{Mapping} that identifies which \htmlref{Region}{Region} contains
+      a given input position.
+
+      A SelectorMap encapsulates a number of Regions that all have the same
+      number of axes and represent the same coordinate \htmlref{Frame}{Frame}. The number of
+      inputs (\htmlref{Nin}{Nin} attribute) of the SelectorMap equals the number of axes
+      spanned by one of the encapsulated Region. All SelectorMaps have only
+      a single output. SelectorMaps do not define an inverse transformation.
+
+      For each input position, the forward transformation of a SelectorMap
+      searches through the encapsulated Regions (in the order supplied when
+      the SelectorMap was created) until a Region is found which contains
+      the input position. The index associated with this Region is
+      returned as the SelectorMap output value (the index value is the
+      position of the Region within the list of Regions supplied when the
+      SelectorMap was created, starting at 1 for the first Region). If an
+      input position is not contained within any Region, a value of zero is
+      returned by the forward transformation.
+
+      If a compound Mapping contains a SelectorMap in series with its own
+      inverse, the combination of the two adjacent SelectorMaps will be
+      replaced by a \htmlref{UnitMap}{UnitMap} when the compound Mapping is simplified using
+      \htmlref{astSimplify}{astSimplify}.
+
+      In practice, SelectorMaps are often used in conjunction with SwitchMaps.
+   }
+   \sstconstructor{
+      \htmlref{astSelectorMap}{astSelectorMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SelectorMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The SelectorMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The SelectorMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   ShiftMap
+}{
+   Add a constant value to each coordinate
+}{
+   \sstdescription{
+      A ShiftMap is a linear \htmlref{Mapping}{Mapping} which shifts each axis by a
+      specified constant value.
+   }
+   \sstconstructor{
+      \htmlref{astShiftMap}{astShiftMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The ShiftMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The ShiftMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The ShiftMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   SkyAxis
+}{
+   Store celestial axis information
+}{
+   \sstdescription{
+      The SkyAxis class is used to store information associated with a
+      particular axis of a \htmlref{SkyFrame}{SkyFrame}. It is used internally by the AST
+      library and has no constructor function. You should encounter it
+      only within textual output (e.g. from \htmlref{astWrite}{astWrite}).
+   }
+   \sstconstructor{
+      None.
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SkyAxis class inherits from the \htmlref{Axis}{Axis} class.
+   }
+}
+\sstroutine{
+   SkyFrame
+}{
+   Celestial coordinate system description
+}{
+   \sstdescription{
+      A SkyFrame is a specialised form of \htmlref{Frame}{Frame} which describes
+      celestial longitude/latitude coordinate systems. The particular
+      celestial coordinate system to be represented is specified by
+      setting the SkyFrame\texttt{'} s \htmlref{System}{System} attribute (currently, the default
+      is ICRS) qualified, as necessary, by a mean \htmlref{Equinox}{Equinox} value and/or
+      an \htmlref{Epoch}{Epoch}.
+
+      For each of the supported celestial coordinate systems, a SkyFrame
+      can apply an optional shift of origin to create a coordinate system
+      representing offsets within the celestial coordinate system from some
+      specified reference point. This offset coordinate system can also be
+      rotated to define new longitude and latitude axes. See attributes
+      SkyRef, \htmlref{SkyRefIs}{SkyRefIs}, SkyRefP and \htmlref{AlignOffset}{AlignOffset}.
+
+      All the coordinate values used by a SkyFrame are in
+      radians. These may be formatted in more conventional ways for
+      display by using \htmlref{astFormat}{astFormat}.
+      For a SkyFrame, the Unit attribute describes the formatted value of
+      a SkyFrame axis, and may for instance be \texttt{"} h:m:s\texttt{"} , indicating that a
+      formatted axis value contains colon-separated fields for hours, minutes
+      and seconds. On the other hand, the InternalUnit attribute for a
+      SkyFrame is always set to \texttt{"} rad\texttt{"}  (i.e. radians), indicating that the
+      unformatted (i.e. floating point) axis values used by application code
+      are always in units of radians
+   }
+   \sstconstructor{
+      \htmlref{astSkyFrame}{astSkyFrame}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SkyFrame class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      SkyFrame also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AlignOffset}{AlignOffset}: Align SkyFrames using the offset coordinate system?
+
+         \sstitem
+         \htmlref{AsTime(axis)}{AsTime(axis)}: Format celestial coordinates as times?
+
+         \sstitem
+         \htmlref{Equinox}{Equinox}: Epoch of the mean equinox
+
+         \sstitem
+         IsLatAxis: Is the specified axis the latitude axis?
+
+         \sstitem
+         IsLonAxis: Is the specified axis the longitude axis?
+
+         \sstitem
+         \htmlref{LatAxis}{LatAxis}: Index of the latitude axis
+
+         \sstitem
+         \htmlref{LonAxis}{LonAxis}: Index of the longitude axis
+
+         \sstitem
+         \htmlref{NegLon}{NegLon}: Display longitude values in the range [-pi,pi]?
+
+         \sstitem
+         \htmlref{Projection}{Projection}: Sky projection description.
+
+         \sstitem
+         SkyRef: Position defining location of the offset coordinate system
+
+         \sstitem
+         \htmlref{SkyRefIs}{SkyRefIs}: Selects the nature of the offset coordinate system
+
+         \sstitem
+         SkyRefP: Position defining orientation of the offset coordinate system
+
+         \sstitem
+         \htmlref{SkyTol}{SkyTol}: Smallest significant shift in sky coordinates
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those
+      functions
+      applicable to all Frames, the following
+      functions
+      may also be applied to all SkyFrames:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astSkyOffsetMap}{astSkyOffsetMap}: Obtain a \htmlref{Mapping}{Mapping} from absolute to offset coordinates
+      }
+   }
+}
+\sstroutine{
+   SlaMap
+}{
+   Sequence of celestial coordinate conversions
+}{
+   \sstdescription{
+      An SlaMap is a specialised form of \htmlref{Mapping}{Mapping} which can be used to
+      represent a sequence of conversions between standard celestial
+      (longitude, latitude) coordinate systems.
+
+      When an SlaMap is first created, it simply performs a unit
+      (null) Mapping on a pair of coordinates. Using the \htmlref{astSlaAdd}{astSlaAdd}
+      function, a series of coordinate conversion steps may then be
+      added, selected from those provided by the SLALIB Positional
+      Astronomy Library (Starlink User Note SUN/67). This allows
+      multi-step conversions between a variety of celestial coordinate
+      systems to be assembled out of the building blocks provided by
+      SLALIB.
+
+      For details of the individual coordinate conversions available,
+      see the description of the astSlaAdd function.
+   }
+   \sstconstructor{
+      \htmlref{astSlaMap}{astSlaMap} (also see astSlaAdd)
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SlaMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The SlaMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Mappings, the
+      following function may also be applied to all SlaMaps:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astSlaAdd}{astSlaAdd}: Add a celestial coordinate conversion to an SlaMap
+      }
+   }
+}
+\sstroutine{
+   SpecFluxFrame
+}{
+   Compound spectrum/flux Frame
+}{
+   \sstdescription{
+      A SpecFluxFrame combines a \htmlref{SpecFrame}{SpecFrame} and a \htmlref{FluxFrame}{FluxFrame} into a single
+      2-dimensional compound \htmlref{Frame}{Frame}. Such a Frame can for instance be used
+      to describe a \htmlref{Plot}{Plot} of a spectrum in which the first axis represents
+      spectral position and the second axis represents flux.
+   }
+   \sstconstructor{
+      \htmlref{astSpecFluxFrame}{astSpecFluxFrame}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SpecFluxFrame class inherits from the \htmlref{CmpFrame}{CmpFrame} class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The SpecFluxFrame class does not define any new attributes beyond
+      those which are applicable to all CmpFrames. However, the attributes
+      of the component Frames can be accessed as if they were attributes
+      of the SpecFluxFrame. For instance, the SpecFluxFrame will recognise
+      the \texttt{"} \htmlref{StdOfRest}{StdOfRest}\texttt{"}  attribute and forward access requests to the component
+      SpecFrame. An axis index can optionally be appended to the end of any
+      attribute name, in which case the request to access the attribute will
+      be forwarded to the primary Frame defining the specified axis.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The SpecFluxFrame class does not define any new functions beyond those
+      which are applicable to all CmpFrames.
+   }
+}
+\sstroutine{
+   SpecFrame
+}{
+   Spectral coordinate system description
+}{
+   \sstdescription{
+      A SpecFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various coordinate systems used to describe positions within
+      an electro-magnetic spectrum. The particular coordinate system to be
+      used is specified by setting the SpecFrame\texttt{'} s \htmlref{System}{System} attribute (the
+      default is wavelength) qualified, as necessary, by other attributes
+      such as the rest frequency, the standard of rest, the epoch of
+      observation, units, etc (see the description of the System attribute
+      for details).
+
+      By setting a value for thr \htmlref{SpecOrigin}{SpecOrigin} attribute, a SpecFrame can be made
+      to represent offsets from a given spectral position, rather than absolute
+      spectral values.
+   }
+   \sstconstructor{
+      \htmlref{astSpecFrame}{astSpecFrame}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SpecFrame class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      SpecFrame also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AlignSpecOffset}{AlignSpecOffset}: Align SpecFrames using the offset coordinate system?
+
+         \sstitem
+         \htmlref{AlignStdOfRest}{AlignStdOfRest}: Standard of rest in which to align SpecFrames
+
+         \sstitem
+         \htmlref{RefDec}{RefDec}: Declination of the source (FK5 J2000)
+
+         \sstitem
+         \htmlref{RefRA}{RefRA}: Right ascension of the source (FK5 J2000)
+
+         \sstitem
+         \htmlref{RestFreq}{RestFreq}: Rest frequency
+
+         \sstitem
+         \htmlref{SourceSys}{SourceSys}: Source velocity spectral system
+
+         \sstitem
+         \htmlref{SourceVel}{SourceVel}: Source velocity
+
+         \sstitem
+         \htmlref{SourceVRF}{SourceVRF}: Source velocity rest frame
+
+         \sstitem
+         \htmlref{SpecOrigin}{SpecOrigin}: The zero point for SpecFrame axis values
+
+         \sstitem
+         \htmlref{StdOfRest}{StdOfRest}: Standard of rest
+
+      }
+      Several of the Frame attributes inherited by the SpecFrame class
+      refer to a specific axis of the Frame (for instance \htmlref{Unit(axis)}{Unit(axis)},
+      \htmlref{Label(axis)}{Label(axis)}, etc). Since a SpecFrame is strictly one-dimensional,
+      it allows these attributes to be specified without an axis index.
+      So for instance, \texttt{"} Unit\texttt{"}  is allowed in place of \texttt{"} Unit(1)\texttt{"} .
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Frames, the
+      following functions may also be applied to all SpecFrames:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astSetRefPos}{astSetRefPos}: Set reference position in any celestial system
+
+         \sstitem
+         \htmlref{astGetRefPos}{astGetRefPos}: Get reference position in any celestial system
+      }
+   }
+}
+\sstroutine{
+   SpecMap
+}{
+   Sequence of spectral coordinate conversions
+}{
+   \sstdescription{
+      A SpecMap is a specialised form of \htmlref{Mapping}{Mapping} which can be used to
+      represent a sequence of conversions between standard spectral
+      coordinate systems.
+
+      When an SpecMap is first created, it simply performs a unit
+      (null) Mapping. Using the \htmlref{astSpecAdd}{astSpecAdd}
+      function, a series of coordinate conversion steps may then be
+      added. This allows multi-step conversions between a variety of
+      spectral coordinate systems to be assembled out of a set of building
+      blocks.
+
+      Conversions are available to transform between standards of rest.
+      Such conversions need to know the source position as an RA and DEC.
+      This information can be supplied in the form of parameters for
+      the relevant conversions, in which case the SpecMap is 1-dimensional,
+      simply transforming the spectral axis values. This means that the
+      same source position will always be used by the SpecMap. However, this
+      may not be appropriate for an accurate description of a 3-D spectral
+      cube, where changes of spatial position can produce significant
+      changes in the Doppler shift introduced when transforming between
+      standards of rest. For this situation, a 3-dimensional SpecMap can
+      be created in which axes 2 and 3 correspond to the source RA and DEC
+      The SpecMap simply copies values for axes 2 and 3 from input to
+      output), but modifies axis 1 values (the spectral axis) appropriately.
+
+      For details of the individual coordinate conversions available,
+      see the description of the astSpecAdd function.
+   }
+   \sstconstructor{
+      \htmlref{astSpecMap}{astSpecMap} (also see astSpecAdd)
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SpecMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The SpecMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Mappings, the
+      following function may also be applied to all SpecMaps:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astSpecAdd}{astSpecAdd}: Add a spectral coordinate conversion to an SpecMap
+      }
+   }
+}
+\sstroutine{
+   SphMap
+}{
+   Map 3-d Cartesian to 2-d spherical coordinates
+}{
+   \sstdescription{
+      A SphMap is a \htmlref{Mapping}{Mapping} which transforms points from a
+      3-dimensional Cartesian coordinate system into a 2-dimensional
+      spherical coordinate system (longitude and latitude on a unit
+      sphere centred at the origin). It works by regarding the input
+      coordinates as position vectors and finding their intersection
+      with the sphere surface. The inverse transformation always
+      produces points which are a unit distance from the origin
+      (i.e. unit vectors).
+   }
+   \sstconstructor{
+      \htmlref{astSphMap}{astSphMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SphMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      SphMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{UnitRadius}{UnitRadius}: SphMap input vectors lie on a unit sphere?
+
+         \sstitem
+         \htmlref{PolarLong}{PolarLong}: The longitude value to assign to either pole
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The SphMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Stc
+}{
+   Represents an instance of the IVOA STC class
+}{
+   \sstdescription{
+      The Stc class is an implementation of the IVOA STC class which forms
+      part of the IVOA Space-Time Coordinate Metadata system. See:
+
+      http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+
+      The Stc class does not have a constructor function of its own, as it
+      is simply a container class for a family of specialised sub-classes
+      including \htmlref{StcCatalogEntryLocation}{StcCatalogEntryLocation}, \htmlref{StcResourceProfile}{StcResourceProfile}, \htmlref{StcSearchLocation}{StcSearchLocation}
+      and \htmlref{StcObsDataLocation}{StcObsDataLocation}.
+   }
+   \sstconstructor{
+      astStc
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Stc class inherits from the \htmlref{Region}{Region} class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Regions, every
+      Stc also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{RegionClass}{RegionClass}: The class name of the encapsulated Region.
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Regions, the
+      following functions may also be applied to all Stc\texttt{'} s:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astGetStcRegion}{astGetStcRegion}: Get a pointer to the encapsulated Region
+
+         \sstitem
+         \htmlref{astGetStcCoord}{astGetStcCoord}: Get information about an AstroCoords element
+
+         \sstitem
+         \htmlref{astGetStcNCoord}{astGetStcNCoord}: Returns the number of AstroCoords elements in an Stc
+      }
+   }
+}
+\sstroutine{
+   StcCatalogEntryLocation
+}{
+   Correspond to the IVOA STCCatalogEntryLocation class
+}{
+   \sstdescription{
+      The StcCatalogEntryLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of the datasets contained in some VO resource.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstconstructor{
+      \htmlref{astStcCatalogEntryLocation}{astStcCatalogEntryLocation}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The StcCatalogEntryLocation class inherits from the Stc class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The StcCatalogEntryLocation class does not define any new attributes beyond
+      those which are applicable to all Stcs.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The StcCatalogEntryLocation class does not define any new functions beyond those
+      which are applicable to all Stcs.
+   }
+}
+\sstroutine{
+   StcObsDataLocation
+}{
+   Correspond to the IVOA ObsDataLocation class
+}{
+   \sstdescription{
+      The StcObsDataLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coordinate space occupied by a particular observational dataset.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+
+      An STC ObsDataLocation element specifies the extent of the
+      observation within a specified coordinate system, and also specifies
+      the observatory location within a second coordinate system.
+
+      The AST StcObsDataLocation class inherits from Stc, and therefore
+      an StcObsDataLocation can be used directly as an Stc. When used
+      in this way, the StcObsDataLocation describes the location of the
+      observation (not the observatory).
+
+      Eventually, this class will have a method for returning an Stc
+      describing the observatory location. However, AST currently does not
+      include any classes of \htmlref{Frame}{Frame} for describing terrestrial or solar
+      system positions. Therefore, the provision for returning observatory
+      location as an Stc is not yet available. However, for terrestrial
+      observations, the position of the observatory can still be recorded
+      using the \htmlref{ObsLon}{ObsLon} and \htmlref{ObsLat}{ObsLat} attributes of the Frame encapsulated
+      within the Stc representing the observation location (this assumes
+      the observatory is located at sea level).
+   }
+   \sstconstructor{
+      \htmlref{astStcObsDataLocation}{astStcObsDataLocation}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The StcObsDataLocation class inherits from the Stc class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The StcObsDataLocation class does not define any new attributes beyond
+      those which are applicable to all Stcs.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The StcObsDataLocation class does not define any new functions beyond those
+      which are applicable to all Stcs.
+   }
+}
+\sstroutine{
+   StcResourceProfile
+}{
+   Correspond to the IVOA STCResourceProfile class
+}{
+   \sstdescription{
+      The StcResourceProfile class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of the datasets contained in some VO resource.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstconstructor{
+      \htmlref{astStcResourceProfile}{astStcResourceProfile}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The StcResourceProfile class inherits from the Stc class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The StcResourceProfile class does not define any new attributes beyond
+      those which are applicable to all Stcs.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The StcResourceProfile class does not define any new functions beyond those
+      which are applicable to all Stcs.
+   }
+}
+\sstroutine{
+   StcSearchLocation
+}{
+   Correspond to the IVOA SearchLocation class
+}{
+   \sstdescription{
+      The StcSearchLocation class is a sub-class of \htmlref{Stc}{Stc} used to describe
+      the coverage of a query.
+
+      See http://hea-www.harvard.edu/$\sim$arots/nvometa/STC.html
+   }
+   \sstconstructor{
+      \htmlref{astStcSearchLocation}{astStcSearchLocation}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The StcSearchLocation class inherits from the Stc class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The StcSearchLocation class does not define any new attributes beyond
+      those which are applicable to all Stcs.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The StcSearchLocation class does not define any new functions beyond those
+      which are applicable to all Stcs.
+   }
+}
+\sstroutine{
+   StcsChan
+}{
+   I/O Channel using STC-S to represent Objects
+}{
+   \sstdescription{
+      A StcsChan is a specialised form of \htmlref{Channel}{Channel} which supports STC-S
+      I/O operations. Writing an \htmlref{Object}{Object} to an StcsChan (using
+      \htmlref{astWrite}{astWrite}) will, if the Object is suitable, generate an
+      STC-S description of that Object, and reading from an StcsChan will
+      create a new Object from its STC-S description.
+
+      When an STC-S description is read using
+      \htmlref{astRead}{astRead},
+      the returned AST Object may be 1) a \htmlref{PointList}{PointList} describing the STC
+      AstroCoords (i.e. a single point of interest within the coordinate frame
+      described by the STC-S description), or 2) a \htmlref{Region}{Region} describing the STC
+      AstrCoordsArea (i.e. an area or volume of interest within the coordinate
+      frame described by the STC-S description), or 3) a \htmlref{KeyMap}{KeyMap}
+      containing the uninterpreted property values read form the STC-S
+      description, or 4) a KeyMap containing any combination of the first
+      3 options. The attributes \htmlref{StcsArea}{StcsArea}, \htmlref{StcsCoords}{StcsCoords} and \htmlref{StcsProps}{StcsProps}
+      control which of the above is returned by
+      astRead.
+
+      When an STC-S description is created from an AST Object using
+      astWrite,
+      the AST Object must be either a Region or a KeyMap. If it is a
+      Region, it is assumed to define the AstroCoordsArea or (if the
+      Region is a single point) the AstroCoords to write to the STC-S
+      description. If the Object is a KeyMap, it may contain an entry
+      with the key \texttt{"} AREA\texttt{"} , holding a Region to be used to define the
+      AstroCoordsArea. It may also contain an entry with the key \texttt{"} COORDS\texttt{"} ,
+      holding a Region (a PointList) to be used to create the
+      AstroCoords. It may also contain an entry with key \texttt{"} PROPS\texttt{"} , holding
+      a KeyMap that contains uninterpreted property values to be used as
+      defaults for any STC-S properties that are not determined by the
+      other supplied Regions. In addition, a KeyMap supplied to
+      astWrite
+      may itself hold the default STC-S properties (rather than defaults
+      being held in a secondary KeyMap, stored as the \texttt{"} PROPS\texttt{"}  entry in the
+      supplied KeyMap).
+
+      The
+      astRead and astWrite
+      functions work together so that any Object returned by
+      astRead can immediately be re-written using astWrite.
+
+      Normally, when you use an StcsChan, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  functions which connect it to an external data store
+      by reading and writing the resulting text. These functions
+      should perform any conversions needed between external character
+      encodings and the internal ASCII encoding. If no such functions
+      are supplied, a Channel will read from standard input and write
+      to standard output.
+
+      Alternatively, an \htmlref{XmlChan}{XmlChan} can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+
+      Support for STC-S is currently based on the IVOA document \texttt{"} STC-S:
+      Space-Time Coordinate (STC) Metadata Linear String Implementation\texttt{"} ,
+      version 1.30 (dated 5th December 2007), available at
+      http://www.ivoa.net/Documents/latest/STC-S.html. Note, this
+      document is a recommednation only and does not constitute an accepted
+      IVOA standard.
+
+      The full text of version 1.30 is supported by the StcsChan class,
+      with the following exceptions and provisos:
+
+      \sstitemlist{
+
+         \sstitem
+         When reading an STC-S phrase, case is ignored except when reading
+         units strings.
+
+         \sstitem
+         There is no support for multiple intervals specified within a
+         TimeInterval, PositionInterval, SpectralInterval or RedshiftInterval.
+
+         \sstitem
+         If the ET timescale is specified, TT is used instead.
+
+         \sstitem
+         If the TEB timescale is specified, TDB is used instead.
+
+         \sstitem
+         The LOCAL timescale is not supported.
+
+         \sstitem
+         The AST \htmlref{TimeFrame}{TimeFrame} and \htmlref{SkyFrame}{SkyFrame} classes do not currently allow a
+         reference position to be specified. Consequently, any $<$refpos$>$
+         specified within the Time or Space sub-phrase of an STC-S document
+         is ignored.
+
+         \sstitem
+         The Convex identifier for the space sub-phrase is not supported.
+
+         \sstitem
+         The GEO\_C and GEO\_D space frames are not supported.
+
+         \sstitem
+         The UNITSPHERE and SPHER3 space flavours are not supported.
+
+         \sstitem
+         If any Error values are supplied in a space sub-phrase, then the
+         number of values supplied should equal the number of spatial axes,
+         and the values are assumed to specify an error box (i.e. error
+         circles, ellipses, etc, are not supported).
+
+         \sstitem
+         The spectral and redshift sub-phrases do not support the
+         following $<$refpos$>$ values: LOCAL\_GROUP\_CENTER, UNKNOWNRefPos,
+         EMBARYCENTER, MOON, MERCURY, VENUS, MARS, JUPITER, SATURN, URANUS,
+         NEPTUNE, PLUTO.
+
+         \sstitem
+         Error values are supported but error ranges are not.
+
+         \sstitem
+         Resolution, PixSize and Size values are ignored.
+
+         \sstitem
+         Space velocity sub-phrases are ignored.
+      }
+   }
+   \sstconstructor{
+      \htmlref{astStcsChan}{astStcsChan}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The StcsChan class inherits from the Channel class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Channels, every
+      StcsChan also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{StcsArea}{StcsArea}: Return the CoordinateArea component after reading an STC-S?
+
+         \sstitem
+         \htmlref{StcsCoords}{StcsCoords}: Return the Coordinates component after reading an STC-S?
+
+         \sstitem
+         \htmlref{StcsLength}{StcsLength}: Controls output buffer length
+
+         \sstitem
+         \htmlref{StcsProps}{StcsProps}: Return the STC-S properties after reading an STC-S?
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The StcsChan class does not define any new functions beyond those
+      which are applicable to all Channels.
+   }
+}
+\sstroutine{
+   SwitchMap
+}{
+   A Mapping that encapsulates a set of alternate Mappings
+}{
+   \sstdescription{
+      A SwitchMap is a \htmlref{Mapping}{Mapping} which represents a set of alternate
+      Mappings, each of which is used to transform positions within a
+      particular region of the input or output coordinate system of the
+      SwitchMap.
+
+      A SwitchMap can encapsulate any number of Mappings, but they must
+      all have the same number of inputs (\htmlref{Nin}{Nin} attribute value) and the
+      same number of outputs (\htmlref{Nout}{Nout} attribute value). The SwitchMap itself
+      inherits these same values for its Nin and Nout attributes. Each of
+      these Mappings represents a \texttt{"} route\texttt{"}  through the switch, and are
+      referred to as \texttt{"} route\texttt{"}  Mappings below. Each route Mapping transforms
+      positions between the input and output coordinate space of the entire
+      SwitchMap, but only one Mapping will be used to transform any given
+      position. The selection of the appropriate route Mapping to use with
+      any given input position is made by another Mapping, called the
+      \texttt{"} selector\texttt{"}  Mapping. Each SwitchMap encapsulates two selector
+      Mappings in addition to its route Mappings; one for use with the
+      SwitchMap\texttt{'} s forward transformation (called the \texttt{"} forward selector
+      Mapping\texttt{"} ), and one for use with the SwitchMap\texttt{'} s inverse transformation
+      (called the \texttt{"} inverse selector Mapping\texttt{"} ). The forward selector Mapping
+      must have the same number of inputs as the route Mappings, but
+      should have only one output. Likewise, the inverse selector Mapping
+      must have the same number of outputs as the route Mappings, but
+      should have only one input.
+
+      When the SwitchMap is used to transform a position in the forward
+      direction (from input to output), each supplied input position is
+      first transformed by the forward transformation of the forward selector
+      Mapping. This produces a single output value for each input position
+      referred to as the selector value. The nearest integer to the selector
+      value is found, and is used to index the array of route Mappings (the
+      first supplied route Mapping has index 1, the second route Mapping has
+      index 2, etc). If the nearest integer to the selector value is less
+      than 1 or greater than the number of route Mappings, then the SwitchMap
+      output position is set to a value of AST\_\_BAD on every axis. Otherwise,
+      the forward transformation of the selected route Mapping is used to
+      transform the supplied input position to produce the SwitchMap output
+      position.
+
+      When the SwitchMap is used to transform a position in the inverse
+      direction (from \texttt{"} output\texttt{"}  to \texttt{"} input\texttt{"} ), each supplied \texttt{"} output\texttt{"}  position
+      is first transformed by the inverse transformation of the inverse
+      selector Mapping. This produces a selector value for each \texttt{"} output\texttt{"}
+      position. Again, the nearest integer to the selector value is found,
+      and is used to index the array of route Mappings. If this selector
+      index value is within the bounds of the array of route Mappings, then
+      the inverse transformation of the selected route Mapping is used to
+      transform the supplied \texttt{"} output\texttt{"}  position to produce the SwitchMap
+      \texttt{"} input\texttt{"}  position. If the selector index value is outside the bounds
+      of the array of route Mappings, then the SwitchMap \texttt{"} input\texttt{"}  position is
+      set to a value of AST\_\_BAD on every axis.
+
+      In practice, appropriate selector Mappings should be chosen to
+      associate a different route Mapping with each region of coordinate
+      space. Note that the \htmlref{SelectorMap}{SelectorMap} class of Mapping is particularly
+      appropriate for this purpose.
+
+      If a compound Mapping contains a SwitchMap in series with its own
+      inverse, the combination of the two adjacent SwitchMaps will be
+      replaced by a \htmlref{UnitMap}{UnitMap} when the compound Mapping is simplified using
+      \htmlref{astSimplify}{astSimplify}.
+   }
+   \sstconstructor{
+      \htmlref{astSwitchMap}{astSwitchMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The SwitchMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The SwitchMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The SwitchMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   Table
+}{
+   A 2-dimensional table of values
+}{
+   \sstdescription{
+      The Table class is a type of \htmlref{KeyMap}{KeyMap} that represents a two-dimensional
+      table of values. The
+      astMapGet... and astMapPut...
+      methods provided by the KeyMap class should be used for storing and
+      retrieving values from individual cells within a Table. Each entry
+      in the KeyMap represents a single cell of the table and has an
+      associated key of the form \texttt{"} $<$COL$>$(i)\texttt{"}  where \texttt{"} $<$COL$>$\texttt{"}  is the
+      upper-case name of a table column and \texttt{"} i\texttt{"}  is the row index (the
+      first row is row 1). Keys of this form should always be used when
+      using KeyMap methods to access entries within a Table.
+
+      Columns must be declared using the
+      \htmlref{astAddColumn}{astAddColumn}
+      method before values can be stored within them. This also fixes the
+      type and shape of the values that may be stored in any cell of the
+      column. Cells may contain scalar or vector values of any data type
+      supported by the KeyMap class. Multi-dimensional arrays may also be
+      stored, but these must be vectorised when storing and retrieving
+      them within a table cell. All cells within a single column must
+      have the same type and shape, as specified when the column is added
+      to the Table.
+
+      Tables may have parameters that describe global properties of the
+      entire table. These are stored as entries in the parent KeyMap and
+      can be access using the get and set method of the KeyMap class.
+      However, parameters must be declared using the
+      \htmlref{astAddParameter}{astAddParameter}
+      method before being accessed.
+
+      Note - since accessing entries within a KeyMap is a relatively slow
+      process, it is not recommended to use the Table class to store
+      very large tables.
+   }
+   \sstconstructor{
+      \htmlref{astTable}{astTable}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The Table class inherits from the KeyMap class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all KeyMaps, every
+      Table also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{ColumnLenC(column)}{ColumnLenC(column)}: The largest string length of any value in a column
+
+         \sstitem
+         \htmlref{ColumnLength(column)}{ColumnLength(column)}: The number of elements in each value in a column
+
+         \sstitem
+         \htmlref{ColumnNdim(column)}{ColumnNdim(column)}: The number of axes spanned by each value in a column
+
+         \sstitem
+         \htmlref{ColumnType(column)}{ColumnType(column)}: The data type of each value in a column
+
+         \sstitem
+         ColumnUnit(column): The unit string describing each value in a column
+
+         \sstitem
+         \htmlref{Ncolumn}{Ncolumn}: The number of columns currently in the Table
+
+         \sstitem
+         \htmlref{Nrow}{Nrow}: The number of rows currently in the Table
+
+         \sstitem
+         \htmlref{Nparameter}{Nparameter}: The number of global parameters currently in the Table
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all KeyMaps, the
+      following functions may also be applied to all Tables:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astAddColumn}{astAddColumn}: Add a new column definition to a Table
+
+         \sstitem
+         \htmlref{astAddParameter}{astAddParameter}: Add a new global parameter definition to a Table
+
+         \sstitem
+         \htmlref{astColumnName}{astColumnName}: Return the name of the column with a given index
+
+         \sstitem
+         \htmlref{astColumnShape}{astColumnShape}: Return the shape of the values in a named column
+
+         \sstitem
+         \htmlref{astHasColumn}{astHasColumn}: Checks if a column exists in a Table
+
+         \sstitem
+         \htmlref{astHasParameter}{astHasParameter}: Checks if a global parameter exists in a Table
+
+         \sstitem
+         \htmlref{astParameterName}{astParameterName}: Return the name of the parameter with a given index
+
+         \sstitem
+         \htmlref{astPurgeRows}{astPurgeRows}: Remove all empty rows from a Table
+
+         \sstitem
+         \htmlref{astRemoveColumn}{astRemoveColumn}: Remove a column from a Table
+
+         \sstitem
+         \htmlref{astRemoveParameter}{astRemoveParameter}: Remove a global parameter from a Table
+
+         \sstitem
+         \htmlref{astRemoveRow}{astRemoveRow}: Remove a row from a Table
+      }
+   }
+}
+\sstroutine{
+   TimeFrame
+}{
+   Time coordinate system description
+}{
+   \sstdescription{
+      A TimeFrame is a specialised form of one-dimensional \htmlref{Frame}{Frame} which
+      represents various coordinate systems used to describe positions in
+      time.
+
+      A TimeFrame represents a moment in time as either an Modified Julian
+      Date (MJD), a Julian Date (JD), a Besselian epoch or a Julian epoch,
+      as determined by the \htmlref{System}{System} attribute. Optionally, a zero point can be
+      specified (using attribute \htmlref{TimeOrigin}{TimeOrigin}) which results in the TimeFrame
+      representing time offsets from the specified zero point.
+
+      Even though JD and MJD are defined as being in units of days, the
+      TimeFrame class allows other units to be used (via the Unit attribute)
+      on the basis of simple scalings (60 seconds = 1 minute, 60 minutes = 1
+      hour, 24 hours = 1 day, 365.25 days = 1 year). Likewise, Julian epochs
+      can be described in units other than the usual years. Besselian epoch
+      are always represented in units of (tropical) years.
+
+      The \htmlref{TimeScale}{TimeScale} attribute allows the time scale to be specified (that
+      is, the physical process used to define the rate of flow of time).
+      MJD, JD and Julian epoch can be used to represent a time in any
+      supported time scale. However, Besselian epoch may only be used with the
+      \texttt{"} TT\texttt{"}  (Terrestrial Time) time scale. The list of supported time scales
+      includes universal time and siderial time. Strictly, these represent
+      angles rather than time scales, but are included in the list since
+      they are in common use and are often thought of as time scales.
+
+      When a time value is formatted it can be formated either as a simple
+      floating point value, or as a Gregorian date (see the Format
+      attribute).
+   }
+   \sstconstructor{
+      \htmlref{astTimeFrame}{astTimeFrame}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The TimeFrame class inherits from the Frame class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Frames, every
+      TimeFrame also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{AlignTimeScale}{AlignTimeScale}: Time scale in which to align TimeFrames
+
+         \sstitem
+         \htmlref{LTOffset}{LTOffset}: The offset of Local Time from UTC, in hours.
+
+         \sstitem
+         \htmlref{TimeOrigin}{TimeOrigin}: The zero point for TimeFrame axis values
+
+         \sstitem
+         \htmlref{TimeScale}{TimeScale}: The timescale used by the TimeFrame
+
+      }
+      Several of the Frame attributes inherited by the TimeFrame class
+      refer to a specific axis of the Frame (for instance \htmlref{Unit(axis)}{Unit(axis)},
+      \htmlref{Label(axis)}{Label(axis)}, etc). Since a TimeFrame is strictly one-dimensional,
+      it allows these attributes to be specified without an axis index.
+      So for instance, \texttt{"} Unit\texttt{"}  is allowed in place of \texttt{"} Unit(1)\texttt{"} .
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Frames, the
+      following functions may also be applied to all TimeFrames:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astCurrentTime}{astCurrentTime}: Return the current system time
+      }
+   }
+}
+\sstroutine{
+   TimeMap
+}{
+   Sequence of time coordinate conversions
+}{
+   \sstdescription{
+      A TimeMap is a specialised form of 1-dimensional \htmlref{Mapping}{Mapping} which can be
+      used to represent a sequence of conversions between standard time
+      coordinate systems.
+
+      When a TimeMap is first created, it simply performs a unit
+      (null) Mapping. Using the \htmlref{astTimeAdd}{astTimeAdd}
+      function, a series of coordinate conversion steps may then be
+      added. This allows multi-step conversions between a variety of
+      time coordinate systems to be assembled out of a set of building
+      blocks.
+
+      For details of the individual coordinate conversions available,
+      see the description of the astTimeAdd function.
+   }
+   \sstconstructor{
+      \htmlref{astTimeMap}{astTimeMap} (also see astTimeAdd)
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The TimeMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The TimeMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      In addition to those functions applicable to all Mappings, the
+      following function may also be applied to all TimeMaps:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{astTimeAdd}{astTimeAdd}: Add a time coordinate conversion to an TimeMap
+      }
+   }
+}
+\sstroutine{
+   TranMap
+}{
+   Mapping with specified forward and inverse transformations
+}{
+   \sstdescription{
+      A TranMap is a \htmlref{Mapping}{Mapping} which combines the forward transformation of
+      a supplied Mapping with the inverse transformation of another
+      supplied Mapping, ignoring the un-used transformation in each
+      Mapping (indeed the un-used transformation need not exist).
+
+      When the forward transformation of the TranMap is referred to, the
+      transformation actually used is the forward transformation of the
+      first Mapping supplied when the TranMap was constructed. Likewise,
+      when the inverse transformation of the TranMap is referred to, the
+      transformation actually used is the inverse transformation of the
+      second Mapping supplied when the TranMap was constructed.
+   }
+   \sstconstructor{
+      \htmlref{astTranMap}{astTranMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The TranMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The TranMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The TranMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   UnitMap
+}{
+   Unit (null) Mapping
+}{
+   \sstdescription{
+      A UnitMap is a unit (null) \htmlref{Mapping}{Mapping} that has no effect on the
+      coordinates supplied to it. They are simply copied. This can be
+      useful if a Mapping is required (e.g. to pass to another
+      function) but you do not want it to have any effect.
+      The \htmlref{Nin}{Nin} and \htmlref{Nout}{Nout} attributes of a UnitMap are always equal and
+      are specified when it is created.
+   }
+   \sstconstructor{
+      \htmlref{astUnitMap}{astUnitMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The UnitMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The UnitMap class does not define any new attributes beyond
+      those which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The UnitMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   UnitNormMap
+}{
+   Convert a vector to a unit vector and its norm, relative to a specified centre
+}{
+   \sstdescription{
+      The forward transformation of a UnitNormMap subtracts the specified centre
+      and then transforms the resulting vector to a unit vector and the vector norm.
+      The output contains one more coordinate than the input: the initial \htmlref{Nin}{Nin} outputs
+      are in the same order as the input; the final output is the norm.
+
+      The inverse transformation of a UnitNormMap multiplies each component
+      of the provided vector by the provided norm and adds the specified centre.
+      The output contains one fewer coordinate than the input: the initial Nin inputs
+      are in the same order as the output; the final input is the norm.
+
+      UnitNormMap enables radially symmetric transformations, as follows:
+      \sstitemlist{
+
+         \sstitem
+         apply a UnitNormMap to produce a unit vector and norm (radius)
+
+         \sstitem
+         apply a one-dimensional mapping to the norm (radius), while passing the unit vector unchanged
+
+         \sstitem
+         apply the same UnitNormMap in the inverse direction to produce the result
+      }
+   }
+   \sstconstructor{
+      \htmlref{astUnitNormMap}{astUnitNormMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The UnitNormMap class inherits from the \htmlref{Mapping}{Mapping} class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The UnitNormMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The UnitNormMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   WcsMap
+}{
+   Implement a FITS-WCS sky projection
+}{
+   \sstdescription{
+      This class is used to represent sky coordinate projections as
+      described in the FITS world coordinate system (FITS-WCS) paper II
+      \texttt{"} Representations of Celestial Coordinates in FITS\texttt{"}  by M. Calabretta
+      and E.W. Griesen. This paper defines a set of functions, or sky
+      projections, which transform longitude-latitude pairs representing
+      spherical celestial coordinates into corresponding pairs of Cartesian
+      coordinates (and vice versa).
+
+      A WcsMap is a specialised form of \htmlref{Mapping}{Mapping} which implements these
+      sky projections and applies them to a specified pair of coordinates.
+      All the projections in the FITS-WCS paper are supported, plus the now
+      deprecated \texttt{"} TAN with polynomial correction terms\texttt{"}  projection which
+      is refered to here by the code \texttt{"} TPN\texttt{"} . Using the FITS-WCS terminology,
+      the transformation is between \texttt{"} native spherical\texttt{"}  and \texttt{"} projection
+      plane\texttt{"}  coordinates (also called \texttt{"} intermediate world coordinates\texttt{"} .
+      These coordinates may, optionally, be embedded in a space with more
+      than two dimensions, the remaining coordinates being copied unchanged.
+      Note, however, that for consistency with other AST facilities, a
+      WcsMap handles coordinates that represent angles in radians (rather
+      than the degrees used by FITS-WCS).
+
+      The type of FITS-WCS projection to be used and the coordinates
+      (axes) to which it applies are specified when a WcsMap is first
+      created. The projection type may subsequently be determined
+      using the \htmlref{WcsType}{WcsType} attribute and the coordinates on which it acts
+      may be determined using the \htmlref{WcsAxis(lonlat)}{WcsAxis(lonlat)} attribute.
+
+      Each WcsMap also allows up to 100 \texttt{"} projection parameters\texttt{"}  to be
+      associated with each axis. These specify the precise form of the
+      projection, and are accessed using \htmlref{PVi\_m}{PVi\_m} attribute, where \texttt{"} i\texttt{"}  is
+      the integer axis index (starting at 1), and m is an integer
+      \texttt{"} parameter index\texttt{"}  in the range 0 to 99. The number of projection
+      parameters required by each projection, and their meanings, are
+      dependent upon the projection type (most projections either do not
+      use any projection parameters, or use parameters 1 and 2 associated
+      with the latitude axis). Before creating a WcsMap you should consult
+      the FITS-WCS paper for details of which projection parameters are
+      required, and which have defaults. When creating the WcsMap, you must
+      explicitly set values for all those required projection parameters
+      which do not have defaults defined in this paper.
+   }
+   \sstconstructor{
+      \htmlref{astWcsMap}{astWcsMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The WcsMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      WcsMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{NatLat}{NatLat}: Native latitude of the reference point of a FITS-WCS projection
+
+         \sstitem
+         \htmlref{NatLon}{NatLon}: Native longitude of the reference point of a FITS-WCS projection
+
+         \sstitem
+         \htmlref{PVi\_m}{PVi\_m}: FITS-WCS projection parameters
+
+         \sstitem
+         PVMax: Maximum number of FITS-WCS projection parameters
+
+         \sstitem
+         \htmlref{WcsAxis(lonlat)}{WcsAxis(lonlat)}: FITS-WCS projection axes
+
+         \sstitem
+         \htmlref{WcsType}{WcsType}: FITS-WCS projection type
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The WcsMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   WinMap
+}{
+   Map one window on to another by scaling and shifting each axis
+}{
+   \sstdescription{
+      A Winmap is a linear \htmlref{Mapping}{Mapping} which transforms a rectangular
+      window in one coordinate system into a similar window in another
+      coordinate system by scaling and shifting each axis (the window
+      edges being parallel to the coordinate axes).
+
+      A WinMap is specified by giving the coordinates of two opposite
+      corners (A and B) of the window in both the input and output
+      coordinate systems.
+   }
+   \sstconstructor{
+      \htmlref{astWinMap}{astWinMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The WinMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      The WinMap class does not define any new attributes beyond those
+      which are applicable to all Mappings.
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The WinMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   XmlChan
+}{
+   I/O Channel using XML to represent Objects
+}{
+   \sstdescription{
+      A XmlChan is a specialised form of \htmlref{Channel}{Channel} which supports XML I/O
+      operations. Writing an \htmlref{Object}{Object} to an XmlChan (using
+      \htmlref{astWrite}{astWrite}) will, if the Object is suitable, generate an
+      XML description of that Object, and reading from an XmlChan will
+      create a new Object from its XML description.
+
+      Normally, when you use an XmlChan, you should provide \texttt{"} source\texttt{"}
+      and \texttt{"} sink\texttt{"}  functions which connect it to an external data store
+      by reading and writing the resulting XML text. These functions
+      should perform any conversions needed between external character
+      encodings and the internal ASCII encoding. If no such functions
+      are supplied, a Channel will read from standard input and write
+      to standard output.
+
+      Alternatively, an XmlChan can be told to read or write from
+      specific text files using the \htmlref{SinkFile}{SinkFile} and \htmlref{SourceFile}{SourceFile} attributes,
+      in which case no sink or source function need be supplied.
+   }
+   \sstconstructor{
+      \htmlref{astXmlChan}{astXmlChan}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The XmlChan class inherits from the Channel class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Channels, every
+      XmlChan also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{XmlFormat}{XmlFormat}: \htmlref{System}{System} for formatting Objects as XML
+
+         \sstitem
+         \htmlref{XmlLength}{XmlLength}: Controls output buffer length
+
+         \sstitem
+         \htmlref{XmlPrefix}{XmlPrefix}: The namespace prefix to use when writing
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The XmlChan class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\sstroutine{
+   ZoomMap
+}{
+   Zoom coordinates about the origin
+}{
+   \sstdescription{
+      The ZoomMap class implements a \htmlref{Mapping}{Mapping} which performs a \texttt{"} zoom\texttt{"}
+      transformation by multiplying all coordinate values by the same
+      scale factor (the inverse transformation is performed by
+      dividing by this scale factor). The number of coordinate values
+      representing each point is unchanged.
+   }
+   \sstconstructor{
+      \htmlref{astZoomMap}{astZoomMap}
+   }
+   \sstdiytopic{
+      Inheritance
+   }{
+      The ZoomMap class inherits from the Mapping class.
+   }
+   \sstdiytopic{
+      Attributes
+   }{
+      In addition to those attributes common to all Mappings, every
+      ZoomMap also has the following attributes:
+
+      \sstitemlist{
+
+         \sstitem
+         \htmlref{Zoom}{Zoom}: ZoomMap scale factor
+      }
+   }
+   \sstdiytopic{
+      Functions
+   }{
+      The ZoomMap class does not define any new functions beyond those
+      which are applicable to all Mappings.
+   }
+}
+\normalsize
+
+\cleardoublepage
+\section{\label{ss:commanddescriptions}UNIX Command Descriptions}
+The commands described here are provided for use from the UNIX shell
+to assist with developing software which uses AST. To use these
+commands, you should ensure that the directory
+``/star/bin''\footnote{Or the equivalent directory if AST is installed
+in a non-standard location.} is on your PATH.
+\small
+\sstroutine{
+   ast\_link
+}{
+   Link a program with the AST library
+}{
+   \sstdescription{
+      This command should be used when building programs which use the AST
+      library, in order to generate the correct arguments to allow the compiler
+      to link your program. The arguments generated are written to standard
+      output but may be substituted into the compiler command line in the
+      standard UNIX way using backward quotes (see below).
+
+      By default, it is assumed that you are building a stand-alone program
+      which does not produce graphical output. However, switches are provided
+      for linking other types of program.
+   }
+   \sstinvocation{
+      cc program.c -L/star/lib `ast\_link [switches]` -o program
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         cc display.c -L/star/lib `ast\_link -pgplot` -o display
+      }{
+         Compiles and links a C program called ``display\texttt{'} \texttt{'}  which uses
+         the standard version of PGPLOT for graphical output.
+      }
+      \sstexamplesubsection{
+         cc plotit.c -L. -L/star/lib `ast\_link -grf` -lgrf -o plotit
+      }{
+         Compiles and links a C program ``plotit\texttt{'} \texttt{'} . The ``-grf\texttt{'} \texttt{'}
+         switch indicates that graphical output will be delivered through
+         a graphical interface which you have implemented yourself, which
+         corresponds to the interface required by the current version of AST.
+         Here, this interface is supplied by means of the ``-lgrf\texttt{'} \texttt{'}  library
+         reference.
+      }
+      \sstexamplesubsection{
+         cc plotit.c -L. -L/star/lib `ast\_link -grf\_v2.0` -lgrf -o plotit
+      }{
+         Compiles and links a C program ``plotit\texttt{'} \texttt{'} . The ``-grf\_v2.0\texttt{'} \texttt{'}
+         switch indicates that graphical output will be delivered through
+         a graphical interface which you have implemented yourself, which
+         corresponds to the interface required by version 2.0 of AST.
+         Here, this interface is supplied by means of the ``-lgrf\texttt{'} \texttt{'}  library
+         reference.
+      }
+   }
+   \sstdiytopic{
+      Switches
+   }{
+      The following switches may optionally be given to this command to
+      modify its behaviour:
+
+      \sstitemlist{
+
+         \sstitem
+         ``-csla\texttt{'} \texttt{'} : Ignored. Provided for backward compatibility only.
+
+         \sstitem
+         ``-fsla\texttt{'} \texttt{'} : Ignored. Provided for backward compatibility only.
+
+         \sstitem
+         ``-ems\texttt{'} \texttt{'} : Requests that the program be linked so that error messages
+         produced by the AST library are delivered via the Starlink EMS (Error
+         Message Service) library (Starlink \htmlref{System}{System} Note SSN/4). By default,
+         error messages are simply written to standard error.
+
+         \sstitem
+         ``-drama\texttt{'} \texttt{'} : Requests that the program be linked so that error messages
+         produced by the AST library are delivered via the DRAMA Ers (Error
+         Reporting Service) library. By default, error messages are simply
+         written to standard error.
+
+         \sstitem
+         ``-grf\texttt{'} \texttt{'} : Requests that no arguments be generated to specify which
+         2D graphics system is used to display output from the AST library. You
+         should use this option only if you have implemented an interface to a
+         new graphics system yourself and wish to provide your own arguments for
+         linking with it. This switch differs from the other ``grf\texttt{'} \texttt{'}  switches in
+         that it assumes that your graphics module implements the complete
+         interface required by the current version of AST. If future versions of
+         AST introduce new functions to the graphics interface, this switch will
+         cause ``unresolved symbol\texttt{'} \texttt{'}  errors to occur during linking, warning you
+         that you need to implement new functions in your graphics module. To
+         avoid such errors, you can use one of the other, version-specific,
+         switches in place of the ``-grf\texttt{'} \texttt{'}  switch, but these will cause run-time
+         errors to be reported if any AST function is invoked which requires
+         facilities not in the implemented interface.
+
+         \sstitem
+         ``-grf\_v2.0\texttt{'} \texttt{'} : This switch is equivalent to the ``-mygrf\texttt{'} \texttt{'}  switch.
+         It indicates that you want to link with your own graphics module
+         which implements the 2D graphics interface required by V2.0 of AST.
+
+         \sstitem
+         ``-grf\_v3.2\texttt{'} \texttt{'} : Indicates that you want to link with your own
+         graphics module which implements the 2D graphics interface required by
+         V3.2 of AST.
+
+         \sstitem
+         ``-grf\_v5.6\texttt{'} \texttt{'} : Indicates that you want to link with your own
+         graphics module which implements the 2D graphics interface required by
+         V5.6 of AST.
+
+         \sstitem
+         ``-myerr\texttt{'} \texttt{'} : Requests that no arguments be generated to specify how
+         error messages produced by the AST library should be delivered. You
+         should use this option only if you have implemented an interface to a
+         new error delivery system yourself and wish to provide your own
+         arguments for linking with it.
+
+         \sstitem
+         ``-mygrf\texttt{'} \texttt{'} : This switch has been superceeded by the ``-grf\texttt{'} \texttt{'}  switch,
+         but is retained in order to allow applications to be linked with a
+         graphics module which implements the 2D interface used by AST V2.0. It
+         is equivalent to the ``-grf\_v2.0\texttt{'} \texttt{'}  switch.
+
+         \sstitem
+         ``-pgp\texttt{'} \texttt{'} : Requests that the program be linked so that 2D
+         graphical output from the AST library is displayed via the
+         Starlink version of the PGPLOT graphics package (which uses GKS
+         for its output). By default, no 2D graphics package is linked and
+         this will result in an error at run time if AST routines are
+         invoked that attempt to generate graphical output.
+
+         \sstitem
+         ``-pgplot\texttt{'} \texttt{'} : Requests that the program be linked so that 2D
+         graphical output from the AST library is displayed via
+         the standard (or ``native\texttt{'} \texttt{'} ) version of the PGPLOT graphics
+         package. By default, no 2D graphics package is linked and this will
+         result in an error at run time if AST routines are invoked that
+         attempt to generate graphical output.
+
+         \sstitem
+         ``-grf3d\texttt{'} \texttt{'} : Requests that no arguments be generated to specify which
+         3D graphics system is used to display output from the AST library. You
+         should use this option only if you have implemented an interface to a
+         new 3D graphics system yourself and wish to provide your own arguments
+         for linking with it.
+
+         \sstitem
+         ``-pgp3d\texttt{'} \texttt{'} : Requests that the program be linked so that 3D
+         graphical output from the AST library is displayed via the
+         Starlink version of the PGPLOT graphics package (which uses GKS
+         for its output). By default, no 3D graphics package is linked and
+         this will result in an error at run time if AST routines are
+         invoked that attempt to generate graphical output.
+
+         \sstitem
+         ``-pgplot3d\texttt{'} \texttt{'} : Requests that the program be linked so that 3D
+         graphical output from the AST library is displayed via
+         the standard (or ``native\texttt{'} \texttt{'} ) version of the PGPLOT graphics
+         package. By default, no 3D graphics package is linked and this will
+         result in an error at run time if AST routines are invoked that
+         attempt to generate graphical output.
+      }
+   }
+   \sstdiytopic{
+      ERFA \& PAL
+   }{
+      The AST distribution includes bundled copies of the ERFA and PAL
+      libraries. These will be used for fundamental positional astronomy
+      calculations unless the \texttt{"} --with-external\_pal\texttt{"}  option was used when
+      AST was configured. If \texttt{"} --with-external\_pal\texttt{"}  is used, this script
+      will include \texttt{"} -lpal\texttt{"}  in the returned list of linking options, and
+      the user should then ensure that external copies of the PAL and
+      ERFA libraries are available (ERFA functions are used within PAL).
+   }
+}
+\sstroutine{
+   ast\_link\_adam
+}{
+   Link an ADAM program with the AST library
+}{
+   \sstdescription{
+      This command should only be used when building Starlink ADAM programs
+      which use the AST library, in order to generate the correct arguments
+      to allow the ADAM ``alink\texttt{'} \texttt{'}  command to link the program. The arguments
+      generated are written to standard output but may be substituted into
+      the ``alink\texttt{'} \texttt{'}  command line in the standard UNIX way using backward
+      quotes (see below).
+
+      By default, it is assumed that you are building an ADAM program which
+      does not produce graphical output. However, switches are provided for
+      linking other types of program. This command should not be used when
+      building stand-alone (non-ADAM) programs. Use the ``\htmlref{ast\_link}{ast\_link}\texttt{'} \texttt{'}  command
+      instead.
+   }
+   \sstinvocation{
+      alink program.o -L/star/lib `ast\_link\_adam [switches]`
+   }
+   \sstexamples{
+      \sstexamplesubsection{
+         alink display.o -L/star/lib `ast\_link\_adam -pgplot`
+      }{
+         Links an ADAM program ``display\texttt{'} \texttt{'}  which uses the standard
+         version of PGPLOT for graphical output.
+      }
+      \sstexamplesubsection{
+         alink plotit.o -L. -L/star/lib `ast\_link\_adam -grf` -lgrf
+      }{
+         Links an ADAM program ``plotit\texttt{'} \texttt{'} , written in C. The ``-grf\texttt{'} \texttt{'}
+         switch indicates that graphical output will be delivered through
+         a graphical interface which you have implemented yourself, which
+         corresponds to the interface required by the current version of AST.
+         Here, this interface is supplied by means of the ``-lgrf\texttt{'} \texttt{'}  library
+         reference.
+      }
+      \sstexamplesubsection{
+         alink plotit.o -L. -L/star/lib `ast\_link\_adam -grf\_v2.0` -lgrf
+      }{
+         Links an ADAM program ``plotit\texttt{'} \texttt{'} , written in C. The ``-grf\_v2.0\texttt{'} \texttt{'}
+         switch indicates that graphical output will be delivered through
+         a graphical interface which you have implemented yourself, which
+         corresponds to the interface required by version 2.0 of AST. Here,
+         this interface is supplied by means of the ``-lgrf\texttt{'} \texttt{'}  library
+         reference.
+      }
+   }
+   \sstdiytopic{
+      Switches
+   }{
+      The following switches may optionally be given to this command to
+      modify its behaviour:
+
+      \sstitemlist{
+
+         \sstitem
+         ``-csla\texttt{'} \texttt{'} : Ignored. Provided for backward compatibility only.
+
+         \sstitem
+         ``-fsla\texttt{'} \texttt{'} : Ignored. Provided for backward compatibility only.
+
+         \sstitem
+         ``-grf\texttt{'} \texttt{'} : Requests that no arguments be generated to specify which
+         2D graphics system is used to display output from the AST library. You
+         should use this option only if you have implemented an interface to a
+         new graphics system yourself and wish to provide your own arguments for
+         linking with it. This switch differs from the other ``grf\texttt{'} \texttt{'}  switches in
+         that it assumes that your graphics module implements the complete
+         interface required by the current version of AST. If future versions of
+         AST introduce new functions to the graphics interface, this switch will
+         cause ``unresolved symbol\texttt{'} \texttt{'}  errors to occur during linking, warning you
+         that you need to implement new functions in your graphics module. To
+         avoid such errors, you can use one of the other, version-specific,
+         switches in place of the ``-grf\texttt{'} \texttt{'}  switch, but these will cause run-time
+         errors to be reported if any AST function is invoked which requires
+         facilities not in the implemented interface.
+
+         \sstitem
+         ``-grf\_v2.0\texttt{'} \texttt{'} : This switch is equivalent to the ``-mygrf\texttt{'} \texttt{'}  switch.
+         It indicates that you want to link with your own graphics module which
+         implements the 2D graphics interface required by V2.0 of AST.
+
+         \sstitem
+         ``-grf\_v3.2\texttt{'} \texttt{'} : Indicates that you want to link with your own graphics
+         module which implements the 2D graphics interface required by V3.2 of AST.
+
+         \sstitem
+         ``-grf\_v5.6\texttt{'} \texttt{'} : Indicates that you want to link with your own graphics
+         module which implements the 2D graphics interface required by V5.6 of AST.
+
+         \sstitem
+         ``-myerr\texttt{'} \texttt{'} : Requests that no arguments be generated to specify how
+         error messages produced by the AST library should be delivered. You
+         should use this option only if you have implemented an interface to a
+         new error delivery system yourself and wish to provide your own
+         arguments for linking with it. By default, error messages are delivered
+         in the standard ADAM way via the EMS Error Message Service (Starlink
+         \htmlref{System}{System} Note SSN/4).
+
+         \sstitem
+         ``-mygrf\texttt{'} \texttt{'} : This switch has been superceeded by the ``-grf\texttt{'} \texttt{'}  switch,
+         but is retained in order to allow applications to be linked with a
+         graphics module which implements the interface used by AST V2.0. It is
+         equivalent to the ``-grf\_v2.0\texttt{'} \texttt{'}  switch.
+
+         \sstitem
+         ``-pgp\texttt{'} \texttt{'} : Requests that the program be linked so that 2D
+         graphical output from the AST library is displayed via the
+         Starlink version of the PGPLOT graphics package (which uses GKS
+         for its output). By default, no graphics package is linked and
+         this will result in an error at run time if AST routines are
+         invoked that attempt to generate graphical output.
+
+         \sstitem
+         ``-pgplot\texttt{'} \texttt{'} : Requests that the program be linked so that 2D
+         graphical output from the AST library is displayed via the
+         standard (or ``native\texttt{'} \texttt{'} ) version of the PGPLOT graphics
+         package. By default, no graphics package is linked and this will
+         result in an error at run time if AST routines are invoked that
+         attempt to generate graphical output.
+
+         \sstitem
+         ``-grf3d\texttt{'} \texttt{'} : Requests that no arguments be generated to specify which
+         3D graphics system is used to display output from the AST library. You
+         should use this option only if you have implemented an interface to a
+         new 3D graphics system yourself and wish to provide your own arguments
+         for linking with it.
+
+         \sstitem
+         ``-pgp3d\texttt{'} \texttt{'} : Requests that the program be linked so that 3D
+         graphical output from the AST library is displayed via the
+         Starlink version of the PGPLOT graphics package (which uses GKS
+         for its output). By default, no 3D graphics package is linked and
+         this will result in an error at run time if AST routines are
+         invoked that attempt to generate graphical output.
+
+         \sstitem
+         ``-pgplot3d\texttt{'} \texttt{'} : Requests that the program be linked so that 3D
+         graphical output from the AST library is displayed via
+         the standard (or ``native\texttt{'} \texttt{'} ) version of the PGPLOT graphics
+         package. By default, no 3D graphics package is linked and this will
+         result in an error at run time if AST routines are invoked that
+         attempt to generate graphical output.
+      }
+   }
+   \sstdiytopic{
+      SLALIB
+   }{
+      The AST distribution includes a cut down subset of the C version of
+      the SLALIB library written by Pat Wallace. This subset contains only
+      the functions needed by the AST library. It is built as part of the
+      process of building AST and is distributed under GPL (and is thus
+      compatible with the AST license). Previous version of this script
+      allowed AST applications to be linked against external SLALIB
+      libraries (either Fortran or C) rather than the internal version.
+      The current version of this script does not provide this option,
+      and always uses the internal SLALIB library. However, for backward
+      compatibility, this script still allows the \texttt{"} -fsla\texttt{"}  and \texttt{"} -csla\texttt{"}  flags
+      (previously used for selecting which version of SLALIB to use) to be
+      specified, but they will be ignored.
+   }
+}
+\normalsize
+
+\cleardoublepage
+\section{\label{ss:memoryfunctions}AST Memory Management and Utility Functions}
+AST provides a memory management layer that can be used in place of
+system functions such as \texttt{malloc}, \texttt{free}, \texttt{realloc},
+\emph{etc.} The AST replacements for these functions ( \texttt{\htmlref{astMalloc}{astMalloc}},
+\texttt{\htmlref{astFree}{astFree}} and \texttt{\htmlref{astRealloc}{astRealloc}}) add extra information to each
+allocated memory block that allows AST to check the validity of supplied
+pointers. For example, this extra information allows \texttt{astFree} to
+detect if the supplied pointer has already been freed, and if so to issue
+an appropriate error message. The existence of this extra information is
+invisible to outside callers, and stored in a header block located just
+before the returned memory block.
+
+In addition to the standard functions, AST provides other memory management
+functions, such as:
+
+\begin{description}
+\item [\texttt{\htmlref{astStore}{astStore}}] - stores data in dynamically allocated memory, allocating
+the memory (or adjusting the size of previously allocated memory) to match
+the amount of data to be stored.
+\item [\texttt{\htmlref{astGrow}{astGrow}}] - allocates and expands memory to hold an adjustable-sized array.
+\item [\texttt{\htmlref{astAppendString}{astAppendString}}] - allocates and expands memory to hold a
+concatenated string.
+\end{description}
+
+Theses are just a few of the available utilities functions in the AST
+memory management layer. Prototypes for all AST memory management
+functions are included in the header file ``\texttt{ast.h}''.
+
+An important restriction on these functions is that pointers created by
+other memory management functions, such as the system version of \texttt{malloc} \emph{etc.}, should never supplied to an AST memory management
+function. Only pointers created by AST should be used by these functions.
+
+In addition to memory management functions, AST provides various other
+utility functions, such as a basic regular expression facility, and other
+string manipulation functions. These are also documented in this appendix.
+
+The AST memory management layer is implemented on top of the usual \texttt{malloc}, {tt free} and \texttt{realloc} functions. By default these will be
+the standard functions provided by <stdlib.h>. However, the facilities of
+the STARMEM package (included in the Starlink Software Collection) can be
+used to specify alternative functions to use. This requires that AST be
+configured using the ``--with-starmem'' option when it is built.
+
+The STARMEM package provides a wrapper for the standard malloc
+implementation that enables the user to switch malloc schemes at runtime
+by setting the STARMEM\_MALLOC environment variable. Currently allowed
+values for this variable are:
+
+\begin{description}
+\item [SYSTEM] - standard system malloc/free - the default
+\item [DL] - Doug Lea's malloc/free
+\item [GC] - Hans-Boehm Garbage Collection
+\end{description}
+
+\small
+\sstroutine{
+   astAppendString
+}{
+   Append a string to another string which grows dynamically
+}{
+   \sstdescription{
+      This function appends one string to another dynamically
+      allocated string, extending the dynamic string as necessary to
+      accommodate the new characters (plus the final null).
+   }
+   \sstsynopsis{
+      char $*$astAppendString( char $*$str1, int $*$nc, const char $*$str2 )
+   }
+   \sstparameters{
+      \sstsubsection{
+         str1
+      }{
+         Pointer to the null-terminated dynamic string, whose memory
+         has been allocated using an AST memory allocation function.
+         If no space has yet been allocated for this string, a NULL
+         pointer may be given and fresh space will be allocated by this
+         function.
+      }
+      \sstsubsection{
+         nc
+      }{
+         Pointer to an integer containing the number of characters in
+         the dynamic string (excluding the final null). This is used
+         to save repeated searching of this string to determine its
+         length and it defines the point where the new string will be
+         appended. Its value is updated by this function to include
+         the extra characters appended.
+
+         If \texttt{"} str1\texttt{"}  is NULL, the initial value supplied for \texttt{"} $*$nc\texttt{"}  will
+         be ignored and zero will be used.
+      }
+      \sstsubsection{
+         str2
+      }{
+         Pointer to a constant null-terminated string, a copy of which
+         is to be appended to \texttt{"} str1\texttt{"} .
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astAppendString()
+      }{
+         A possibly new pointer to the dynamic string with the new string
+         appended (its location in memory may have to change if it has to
+         be extended, in which case the original memory is automatically
+         freed by this function). When the string is no longer required,
+         its memory should be freed using \htmlref{astFree}{astFree}.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If this function is invoked with the global error status set
+         or if it should fail for any reason, then the returned pointer
+         will be equal to \texttt{"} str1\texttt{"}  and the dynamic string contents will be
+         unchanged.
+      }
+   }
+}
+\sstroutine{
+   astAppendStringf
+}{
+   Append a string to another string, allowing printf format specifiers
+}{
+   \sstdescription{
+      This function appends one string to another dynamically
+      allocated string, extending the dynamic string as necessary to
+      accommodate the new characters (plus the final null). It is the
+      same as \htmlref{astAppendString}{astAppendString}, except that the \texttt{"} str2\texttt{"}  string ay include
+      printf format specifiers.
+   }
+   \sstsynopsis{
+      char $*$astAppendStringf( char $*$str1, int $*$nc, const char $*$str2, ... )
+   }
+   \sstparameters{
+      \sstsubsection{
+         str1
+      }{
+         Pointer to the null-terminated dynamic string, whose memory
+         has been allocated using an AST memory allocation function.
+         If no space has yet been allocated for this string, a NULL
+         pointer may be given and fresh space will be allocated by this
+         function.
+      }
+      \sstsubsection{
+         nc
+      }{
+         Pointer to an integer containing the number of characters in
+         the dynamic string (excluding the final null). This is used
+         to save repeated searching of this string to determine its
+         length and it defines the point where the new string will be
+         appended. Its value is updated by this function to include
+         the extra characters appended.
+
+         If \texttt{"} str1\texttt{"}  is NULL, the initial value supplied for \texttt{"} $*$nc\texttt{"}  will
+         be ignored and zero will be used.
+      }
+      \sstsubsection{
+         str2
+      }{
+         Pointer to a constant null-terminated string, a copy of which
+         is to be appended to \texttt{"} str1\texttt{"} . It may contain format
+         specifications such as used with the C \texttt{"} printf\texttt{"}  family of
+         functions.
+      }
+      \sstsubsection{
+         ...
+      }{
+         Additional optional arguments (as used by e.g. \texttt{"} printf\texttt{"} )
+         which specify values which are to be substituted into the \texttt{"} str2\texttt{"}
+         string in place of any format specifications.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astAppendString()
+      }{
+         A possibly new pointer to the dynamic string with the new string
+         appended (its location in memory may have to change if it has to
+         be extended, in which case the original memory is automatically
+         freed by this function). When the string is no longer required,
+         its memory should be freed using \htmlref{astFree}{astFree}.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If this function is invoked with the global error status set
+         or if it should fail for any reason, then the returned pointer
+         will be equal to \texttt{"} str1\texttt{"}  and the dynamic string contents will be
+         unchanged.
+      }
+   }
+}
+\sstroutine{
+   astCalloc
+}{
+   Allocate and initialise memory
+}{
+   \sstdescription{
+      This function allocates memory in a similar manner to the
+      standard C \texttt{"} calloc\texttt{"}  function, but with improved security
+      (against memory leaks, etc.) and with error reporting. It also
+      fills the allocated memory with zeros.
+
+      Like \htmlref{astMalloc}{astMalloc}, it allows zero-sized memory allocation
+      (without error), resulting in a NULL returned pointer value.
+   }
+   \sstsynopsis{
+      void $*$astCalloc( size\_t nmemb, size\_t size )
+   }
+   \sstparameters{
+      \sstsubsection{
+         nmemb
+      }{
+         The number of array elements for which memory is to be allocated.
+      }
+      \sstsubsection{
+         size
+      }{
+         The size of each array element, in bytes.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astCalloc()
+      }{
+         If successful, the function returns a pointer to the start of
+         the allocated memory region. If the size allocated is zero, this
+         will be a NULL pointer.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A pointer value of NULL is returned if this function is
+         invoked with the global error status set or if it fails for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   astChr2Double
+}{
+   read a double value from a string
+}{
+   \sstdescription{
+      This function reads a double from the supplied null-terminated string,
+      ignoring leading and trailing white space. AST\_\_BAD is ereturned
+      without error if the string is not a numerical value.
+   }
+   \sstsynopsis{
+      double astChr2Double( const char $*$str )
+   }
+   \sstparameters{
+      \sstsubsection{
+         str
+      }{
+         Pointer to the string.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astChr2Double()
+      }{
+         The double value, or AST\_\_BAD.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of AST\_\_BAD is returned if this function is invoked with
+         the global error status set or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astChrCase
+}{
+   Convert a string to upper or lower case
+}{
+   \sstdescription{
+      This function converts a supplied string to upper or lower case,
+      storing the result in a supplied buffer. The \htmlref{astStringCase}{astStringCase} function
+      is similar, but stores the result in a dynamically allocated buffer.
+   }
+   \sstsynopsis{
+      void astChrCase( const char $*$in, char $*$out, int upper, int blen, int $*$status )
+   }
+   \sstparameters{
+      \sstsubsection{
+         in
+      }{
+         Pointer to the null terminated string to be converted. If this
+         is NULL, the supplied contents of the \texttt{"} out\texttt{"}  string are used as
+         the input string.
+      }
+      \sstsubsection{
+         out
+      }{
+         Pointer to the buffer to receive the converted string. The
+         length of this buffer is given by \texttt{"} blen\texttt{"} . If NULL is supplied
+         for \texttt{"} in\texttt{"} , then the supplied contents of \texttt{"} out\texttt{"}  are converted and
+         written back into \texttt{"} out\texttt{"}  over-writing the supplied contents.
+      }
+      \sstsubsection{
+         upper
+      }{
+         If non-zero, the string is converted to upper case. Otherwise it
+         is converted to lower case.
+      }
+      \sstsubsection{
+         blen
+      }{
+         The length of the output buffer. Ignored if \texttt{"} in\texttt{"}  is NULL. No
+         more than \texttt{"} blen - 1\texttt{"}  characters will be copied from \texttt{"} in\texttt{"}  to
+         \texttt{"} out\texttt{"} , and a terminating null character will then be added.
+      }
+   }
+}
+\sstroutine{
+   astChrLen
+}{
+   Determine the used length of a string
+}{
+   \sstdescription{
+      This function returns the used length of a string. This excludes any
+      trailing white space or non-printable characters (such as the
+      trailing null character).
+   }
+   \sstsynopsis{
+      size\_t astChrLen( const char $*$string )
+   }
+   \sstparameters{
+      \sstsubsection{
+         string
+      }{
+         Pointer to the string.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astChrLen()
+      }{
+         The number of characters in the supplied string, not including the
+         trailing newline, and any trailing white-spaces or non-printable
+         characters.
+      }
+   }
+}
+\sstroutine{
+   astChrMatch
+}{
+   Case insensitive string comparison
+}{
+   \sstdescription{
+      This function compares two null terminated strings for equality,
+      discounting differences in case and any trailing white space in either
+      string.
+   }
+   \sstsynopsis{
+      int astChrMatch( const char $*$str1, const char $*$str2 )
+   }
+   \sstparameters{
+      \sstsubsection{
+         str1
+      }{
+         Pointer to the first string.
+      }
+      \sstsubsection{
+         str2
+      }{
+         Pointer to the second string.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astChrMatch()
+      }{
+         Non-zero if the two strings match, otherwise zero.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero is returned if this function is invoked with the
+         global error status set or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astChrMatchN
+}{
+   Case insensitive string comparison of at most N characters
+}{
+   \sstdescription{
+      This function compares two null terminated strings for equality,
+      discounting differences in case and any trailing white space in either
+      string. No more than \texttt{"} n\texttt{"}  characters are compared.
+   }
+   \sstsynopsis{
+      int astChrMatchN( const char $*$str1, const char $*$str2, size\_t n )
+   }
+   \sstparameters{
+      \sstsubsection{
+         str1
+      }{
+         Pointer to the first string.
+      }
+      \sstsubsection{
+         str2
+      }{
+         Pointer to the second string.
+      }
+      \sstsubsection{
+         n
+      }{
+         Maximum number of characters to compare.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astChrMatchN()
+      }{
+         Non-zero if the two strings match, otherwise zero.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero is returned if this function is invoked with the
+         global error status set or if it should fail for any reason.
+      }
+   }
+}
+\sstroutine{
+   astChrSplit
+}{
+   Extract words from a supplied string
+}{
+   \sstdescription{
+      This function extracts all space-separated words form the supplied
+      string and returns them in an array of dynamically allocated strings.
+   }
+   \sstsynopsis{
+      char $*$$*$astChrSplit\_( const char $*$str, int $*$n )
+   }
+   \sstparameters{
+      \sstsubsection{
+         str
+      }{
+         Pointer to the string to be split.
+      }
+      \sstsubsection{
+         n
+      }{
+         Address of an int in which to return the number of words returned.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astChrSplit()
+      }{
+         A pointer to a dynamically allocated array containing \texttt{"} $*$n\texttt{"}  elements.
+         Each element is a pointer to a dynamically allocated character
+         string containing a word extracted from the supplied string. Each
+         of these words will have no leading or trailing white space.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A NULL pointer is returned if this function is invoked with the
+         global error status set or if it should fail for any reason, or if
+         the supplied string contains no words.
+      }
+   }
+}
+\sstroutine{
+   astChrSplitC
+}{
+   Split a string using a specified character delimiter
+}{
+   \sstdescription{
+      This function extracts all sub-strings separated by a given
+      character from the supplied string and returns them in an array
+      of dynamically allocated strings. The delimiter character itself
+      is not included in the returned strings.
+
+      Delimiter characters that are preceded by \texttt{"} $\backslash$\texttt{"}  are not used as
+      delimiters but are included in the returned word instead (without
+      the \texttt{"} $\backslash$\texttt{"} ).
+   }
+   \sstsynopsis{
+      char $*$$*$astChrSplitC( const char $*$str, char c, int $*$n )
+   }
+   \sstparameters{
+      \sstsubsection{
+         str
+      }{
+         Pointer to the string to be split.
+      }
+      \sstsubsection{
+         c
+      }{
+         The delimiter character.
+      }
+      \sstsubsection{
+         n
+      }{
+         Address of an int in which to return the number of words returned.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astChrSplitC()
+      }{
+         A pointer to a dynamically allocated array containing \texttt{"} $*$n\texttt{"}  elements.
+         Each element is a pointer to a dynamically allocated character
+         string containing a word extracted from the supplied string.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A NULL pointer is returned if this function is invoked with the
+         global error status set or if it should fail for any reason, or if
+         the supplied string contains no words.
+      }
+   }
+}
+\sstroutine{
+   astChrSplitRE
+}{
+   Extract sub-strings matching a specified regular expression
+}{
+   \sstdescription{
+      This function compares the supplied string with the supplied
+      regular expression. If they match, each section of the test string
+      that corresponds to a parenthesised sub-string in the regular
+      expression is copied and stored in the returned array.
+   }
+   \sstsynopsis{
+      char $*$$*$astChrSplitRE( const char $*$str, const char $*$regexp, int $*$n,
+                            const char $*$$*$matchend )
+   }
+   \sstparameters{
+      \sstsubsection{
+         str
+      }{
+         Pointer to the string to be split.
+      }
+      \sstsubsection{
+         regexp
+      }{
+         The regular expression. See \texttt{"} Template Syntax:\texttt{"}  in the \htmlref{astChrSub}{astChrSub}
+         prologue. Note, this function differs from astChrSub in that any
+         equals signs (=) in the regular expression are treated literally.
+      }
+      \sstsubsection{
+         n
+      }{
+         Address of an int in which to return the number of sub-strings
+         returned.
+      }
+      \sstsubsection{
+         matchend
+      }{
+         A pointer to a location at which to return a pointer to the
+         character that follows the last character within the supplied test
+         string that matched any parenthesises sub-section of \texttt{"} regexp\texttt{"} . A
+         NULL pointer is returned if no matches were found. A NULL pointer
+         may be supplied if the location of the last matching character is
+         not needed.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astChrSplitRE()
+      }{
+         A pointer to a dynamically allocated array containing \texttt{"} $*$n\texttt{"}  elements.
+         Each element is a pointer to a dynamically allocated character
+         string containing a sub-string extracted from the supplied string.
+         The array itself, and the strings within it, should all be freed
+         using \htmlref{astFree}{astFree} when no longer needed.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If a parenthesised sub-string in the regular expression is matched
+         by more than one sub-string within the test string, then only the
+         first is returned. To return multiple matches, the regular
+         expression should include multiple copies of the parenthesised
+         sub-string (for instance, separated by \texttt{"} .$+$?\texttt{"}  if the intervening
+         string is immaterial).
+
+         \sstitem
+         A NULL pointer is returned if this function is invoked with the
+         global error status set or if it should fail for any reason, or if
+         the supplied string contains no words.
+      }
+   }
+}
+\sstroutine{
+   astChrSub
+}{
+   Performs substitutions on a supplied string
+}{
+   \sstdescription{
+      This function checks a supplied test string to see if it matches a
+      supplied template. If it does, specified sub-sections of the test
+      string may optionally be replaced by supplied substitution strings.
+      The resulting string is returned.
+   }
+   \sstsynopsis{
+      char $*$astChrSub( const char $*$test, const char $*$pattern,
+                       const char $*$subs[], int nsub )
+   }
+   \sstparameters{
+      \sstsubsection{
+         test
+      }{
+         The string to be tested.
+      }
+      \sstsubsection{
+         pattern
+      }{
+         The template string. See \texttt{"} Template Syntax:\texttt{"}  below.
+      }
+      \sstsubsection{
+         subs
+      }{
+         An array of strings that are to replace the sections of the test
+         string that match each parenthesised sub-string in \texttt{"} pattern\texttt{"} . The
+         first element of \texttt{"} subs\texttt{"}  replaces the part of the test string that
+         matches the first parenthesised sub-string in the template, etc.
+
+         If \texttt{"} nsub\texttt{"}  is zero, then the \texttt{"} subs\texttt{"}  pointer is ignored. In this
+         case, substitution strings may be specified by appended them to
+         the end of the \texttt{"} pattern\texttt{"}  string, separated by \texttt{"} =\texttt{"}  characters.
+         Note, if you need to include a literal \texttt{"} =\texttt{"}  character in the
+         pattern, precede it by an escape \texttt{"} $\backslash$\texttt{"}  character.
+      }
+      \sstsubsection{
+         nsub
+      }{
+         The number of substitution strings supplied in array \texttt{"} subs\texttt{"} .
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astChrSub()
+      }{
+         A pointer to a dynamically allocated string holding the result
+         of the substitutions, or NULL if the test string does not match
+         the template string. This string should be freed using \htmlref{astFree}{astFree}
+         when no longer needed. If no substituions are specified then a
+         copy of the test string is returned if it matches the template.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A NULL pointer is returned if this function is invoked with the
+         global error status set or if it should fail for any reason, or if
+         the supplied test string does not match the template.
+      }
+   }
+   \sstdiytopic{
+      Template Syntax
+   }{
+      The template syntax is a minimal form of regular expression, The
+      quantifiers allowed are \texttt{"} $*$\texttt{"} , \texttt{"} ?\texttt{"} , \texttt{"} $+$\texttt{"} , \texttt{"} \{n\}\texttt{"} , \texttt{"} $*$?\texttt{"}  and \texttt{"} $+$?\texttt{"}  (the
+      last two are non-greedy - they match the minimum length possible
+      that still gives an overall match to the template). The only
+      constraints allowed are \texttt{"} $\wedge$\texttt{"}  and \texttt{"} \$\texttt{"} . The following atoms are allowed:
+
+      \sstitemlist{
+
+         \sstitem
+         [chars]: Matches any of the specified characters.
+
+         \sstitem
+         [$\wedge$chars]: Matches anything but the specified characters.
+
+         \sstitem
+         .: Matches any single character.
+
+         \sstitem
+         x: Matches the character x so long as x has no other significance.
+
+         \sstitem
+         $\backslash$x: Always matches the character x (except for [dDsSwW]).
+
+         \sstitem
+         $\backslash$d: Matches a single digit.
+
+         \sstitem
+         $\backslash$D: Matches anything but a single digit.
+
+         \sstitem
+         $\backslash$w: Matches any alphanumeric character, and \texttt{"} \_\texttt{"} .
+
+         \sstitem
+         $\backslash$W: Matches anything but alphanumeric characters, and \texttt{"} \_\texttt{"} .
+
+         \sstitem
+         $\backslash$s: Matches white space.
+
+         \sstitem
+         $\backslash$S: Matches anything but white space.
+
+      }
+      Note, minus signs (\texttt{"} -\texttt{"} ) within brackets have no special significance,
+      so ranges of characters must be specified explicitly.
+
+      Multiple template strings can be concatenated, using the \texttt{"} $|$\texttt{"}
+      character to separate them. The test string is compared against
+      each one in turn until a match is found.
+
+      Parentheses are used within each template to identify sub-strings
+      that are to be replaced by the strings supplied in \texttt{"} sub\texttt{"} .
+
+      If \texttt{"} nsub\texttt{"}  is supplied as zero, then substitution strings may be
+      specified by appended them to the end of the \texttt{"} pattern\texttt{"}  string,
+      separated by \texttt{"} =\texttt{"}  characters. If \texttt{"} nsub\texttt{"}  is not zero, then any
+      substitution strings appended to the end of \texttt{"} pattern\texttt{"}  are ignored.
+
+      Each element of \texttt{"} subs\texttt{"}
+      may contain a reference to a token of the
+      form \texttt{"} \$1\texttt{"} , \texttt{"} \$2\texttt{"} , etc. The \texttt{"} \$1\texttt{"}  token will be replaced by the part
+      of the test string that matched the first parenthesised sub-string
+      in \texttt{"} pattern\texttt{"} . The \texttt{"} \$2\texttt{"}  token will be replaced by the part of the
+      test string that matched the second parenthesised sub-string in
+      \texttt{"} pattern\texttt{"} , etc.
+   }
+}
+\sstroutine{
+   astChrTrunc
+}{
+   Terminate a string to exclude trailing spaces
+}{
+   \sstdescription{
+      This function pokes a null character into the supplied string to
+      remove any trailing spaces.
+   }
+   \sstsynopsis{
+      void astChrTrunc( char $*$text )
+   }
+   \sstparameters{
+      \sstsubsection{
+         text
+      }{
+         The string to be truncated.
+      }
+   }
+}
+\sstroutine{
+   astFree
+}{
+   Free previously allocated memory
+}{
+   \sstdescription{
+      This function frees memory that has previouly been dynamically
+      allocated using one of the AST memory function.
+   }
+   \sstsynopsis{
+      void $*$astFree( void $*$ptr )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ptr
+      }{
+         Pointer to previously allocated memory. An error will result
+         if the memory has not previously been allocated by another
+         function in this module. However, a NULL pointer value is
+         accepted (without error) as indicating that no memory has yet
+         been allocated, so that no action is required.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFree()
+      }{
+         Always returns a NULL pointer.
+      }
+   }
+}
+\sstroutine{
+   astFreeDouble
+}{
+   Free previously double allocated memory
+}{
+   \sstdescription{
+      This function frees memory that has previouly been dynamically
+      allocated using one of the AST memory function. It assumes that
+      the supplied pointer is a pointer to an array of pointers. Each
+      of these pointers is first freed, and then the supplied pointer
+      is freed.
+
+      Note, this routine should not be used with arrays allocated
+      by \htmlref{astGrow}{astGrow} since astGrow over-allocates and so there may be
+      non-initialised pointers at the end of the array.
+   }
+   \sstsynopsis{
+      void $*$astFreeDouble( void $*$ptr )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ptr
+      }{
+         Pointer to previously allocated memory. An error will result
+         if the memory has not previously been allocated by another
+         function in this module. However, a NULL pointer value is
+         accepted (without error) as indicating that no memory has yet
+         been allocated, so that no action is required.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astFreeDouble()
+      }{
+         Always returns a NULL pointer.
+      }
+   }
+}
+\sstroutine{
+   astGrow
+}{
+   Allocate memory for an adjustable array
+}{
+   \sstdescription{
+      This function allocates memory in which to store an array of
+      data whose eventual size is unknown. It should be invoked
+      whenever a new array size is determined and will appropriately
+      increase the amount of memory allocated when necessary. In
+      general, it will over-allocate in anticipation of future growth
+      so that the amount of memory does not need adjusting on every
+      invocation.
+   }
+   \sstsynopsis{
+      void $*$astGrow( void $*$ptr, int n, size\_t size )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ptr
+      }{
+         Pointer to previously allocated memory (or NULL if none has
+         yet been allocated).
+      }
+      \sstsubsection{
+         n
+      }{
+         Number of array elements to be stored (may be zero).
+      }
+      \sstsubsection{
+         size
+      }{
+         The size of each array element.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astGrow()
+      }{
+         If the memory was allocated successfully, a pointer to the start
+         of the possibly new memory region is returned (this may be the
+         same as the original pointer).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         When new memory is allocated, the existing contents are preserved.
+
+         \sstitem
+         This function does not free memory once it is allocated, so
+         the size allocated grows to accommodate the maximum size of the
+         array (or \texttt{"} high water mark\texttt{"} ). Other memory handling routines may
+         be used to free the memory (or alter its size) if necessary.
+
+         \sstitem
+         If this function is invoked with the global error status set,
+         or if it fails for any reason, the original pointer value is
+         returned and the memory contents are unchanged.
+      }
+   }
+}
+\sstroutine{
+   astIsDynamic
+}{
+   Returns a flag indicating if memory was allocated dynamically
+}{
+   \sstdescription{
+      This function takes a pointer to a region of memory and tests if
+      the memory has previously been dynamically allocated using other
+      functions from this module. It does this by checking for the
+      presence of a \texttt{"} magic\texttt{"}  number in the header which precedes the
+      allocated memory. If the magic number is not present (or the
+      pointer is invalid for any other reason), zero is returned.
+      Otherwise 1 is returned.
+   }
+   \sstsynopsis{
+      int astIsDynamic\_( const void $*$ptr )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ptr
+      }{
+         Pointer to test.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astIsDynamic()
+      }{
+         Non-zero if the memory was allocated dynamically. Zero is returned
+         if the supplied pointer is NULL.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero is returned if this function is invoked with
+         the global error status set, or if it fails for any reason.
+      }
+   }
+}
+\sstroutine{
+   astMalloc
+}{
+   Allocate memory
+}{
+   \sstdescription{
+      This function allocates memory in a similar manner to the
+      standard C \texttt{"} malloc\texttt{"}  function, but with improved security
+      (against memory leaks, etc.) and with error reporting. It also
+      allows zero-sized memory allocation (without error), resulting
+      in a NULL returned pointer value.
+   }
+   \sstsynopsis{
+      void $*$astMalloc( size\_t size )
+   }
+   \sstparameters{
+      \sstsubsection{
+         size
+      }{
+         The size of the memory region required (may be zero).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMalloc()
+      }{
+         If successful, the function returns a pointer to the start of
+         the allocated memory region. If the size allocated is zero, this
+         will be a NULL pointer.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A pointer value of NULL is returned if this function is
+         invoked with the global error status set or if it fails for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   astMemCaching
+}{
+   Controls whether allocated but unused memory is cached in this module
+}{
+   \sstdescription{
+      This function sets a flag indicating if allocated but unused memory
+      should be cached or not. It also returns the original value of the
+      flag.
+
+      If caching is switched on or off as a result of this call, then the
+      current contents of the cache are discarded.
+
+      Note, each thread has a separate cache. Calling this function
+      affects only the currently executing thread.
+   }
+   \sstsynopsis{
+      int astMemCaching( int newval )
+   }
+   \sstparameters{
+      \sstsubsection{
+         newval
+      }{
+         The new value for the MemoryCaching tuning parameter (see
+         \htmlref{astTune}{astTune} in objectc.c). If AST\_\_TUNULL is supplied, the current
+         value is left unchanged.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astMemCaching()
+      }{
+         The original value of the MemoryCaching tuning parameter.
+      }
+   }
+}
+\sstroutine{
+   astRealloc
+}{
+   Change the size of a dynamically allocated region of memory
+}{
+   \sstdescription{
+      This function changes the size of a dynamically allocated region
+      of memory, preserving its contents up to the minimum of the old
+      and new sizes. This may involve copying the contents to a new
+      location, so a new pointer is returned (and the old memory freed
+      if necessary).
+
+      This function is similar to the standard C \texttt{"} realloc\texttt{"}  function
+      except that it provides better security against programming
+      errors and also supports the allocation of zero-size memory
+      regions (indicated by a NULL pointer).
+   }
+   \sstsynopsis{
+      void $*$astRealloc( void $*$ptr, size\_t size )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ptr
+      }{
+         Pointer to previously allocated memory (or NULL if the
+         previous size of the allocated memory was zero).
+      }
+      \sstsubsection{
+         size
+      }{
+         New size required for the memory region. This may be zero, in
+         which case a NULL pointer is returned (no error results). It
+         should not be negative.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astRealloc()
+      }{
+         If the memory was reallocated successfully, a pointer to the
+         start of the new memory region is returned (this may be the same
+         as the original pointer). If size was given as zero, a NULL
+         pointer is returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         If this function is invoked with the error status set, or if
+         it fails for any reason, the original pointer value is returned
+         and the memory contents are unchanged. Note that this behaviour
+         differs from that of the standard C \texttt{"} realloc\texttt{"}  function which
+         returns NULL if it fails.
+      }
+   }
+}
+\sstroutine{
+   astRemoveLeadingBlanks
+}{
+   Remove any leading white space from a string
+}{
+   \sstdescription{
+      This function moves characters in the supplied string to the left
+      in order to remove any leading white space.
+   }
+   \sstsynopsis{
+      void astRemoveLeadingBlanks( char $*$string )
+   }
+   \sstparameters{
+      \sstsubsection{
+         string
+      }{
+         Pointer to the string.
+      }
+   }
+}
+\sstroutine{
+   astSizeOf
+}{
+   Determine the size of a dynamically allocated region of memory
+}{
+   \sstdescription{
+      This function returns the size of a region of dynamically
+      allocated memory.
+   }
+   \sstsynopsis{
+      size\_t astSizeOf( const void $*$ptr )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ptr
+      }{
+         Pointer to dynamically allocated memory (or NULL if the size
+         of the allocated memory was zero).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astSizeOf()
+      }{
+         The allocated size. This will be zero if a NULL pointer was
+         supplied (no error will result).
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A value of zero is returned if this function is invoked with
+         the global error status set, or if it fails for any reason.
+      }
+   }
+}
+\sstroutine{
+   astStore
+}{
+   Store data in dynamically allocated memory
+}{
+   \sstdescription{
+      This function stores data in dynamically allocated memory,
+      allocating the memory (or adjusting the size of previously
+      allocated memory) to match the amount of data to be stored.
+   }
+   \sstsynopsis{
+      void $*$astStore( void $*$ptr, const void $*$data, size\_t size )
+   }
+   \sstparameters{
+      \sstsubsection{
+         ptr
+      }{
+         Pointer to previously allocated memory (or NULL if none has
+         yet been allocated).
+      }
+      \sstsubsection{
+         data
+      }{
+         Pointer to the start of the data to be stored. This may be
+         given as NULL if there are no data, in which case it will be
+         ignored and this function behaves like \htmlref{astRealloc}{astRealloc}, preserving
+         the existing memory contents.
+      }
+      \sstsubsection{
+         size
+      }{
+         The total size of the data to be stored and/or the size of
+         memory to be allocated. This may be zero, in which case the
+         data parameter is ignored, any previously-allocated memory is
+         freed and a NULL pointer is returned.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astStore()
+      }{
+         If the data were stored successfully, a pointer to the start of
+         the possibly new memory region is returned (this may be the same
+         as the original pointer). If size was given as zero, a NULL
+         pointer is returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         This is a convenience function for use when storing data of
+         arbitrary size in memory which is to be allocated
+         dynamically. It is appropriate when the size of the data will
+         not change frequently because the size of the memory region will
+         be adjusted to fit the data on every invocation.
+
+         \sstitem
+         If this function is invoked with the error status set, or if
+         it fails for any reason, the original pointer value is returned
+         and the memory contents are unchanged.
+      }
+   }
+}
+\sstroutine{
+   astString
+}{
+   Create a C string from an array of characters
+}{
+   \sstdescription{
+      This function allocates memory to hold a C string and fills the
+      string with the sequence of characters supplied. It then
+      terminates the string with a null character and returns a
+      pointer to its start. The memory used for the string may later
+      be de-allocated using \htmlref{astFree}{astFree}.
+
+      This function is intended for constructing null terminated C
+      strings from arrays of characters which are not null terminated,
+      such as when importing a character argument from a Fortran 77
+      program.
+   }
+   \sstsynopsis{
+      char $*$astString( const char $*$chars, int nchars )
+   }
+   \sstparameters{
+      \sstsubsection{
+         chars
+      }{
+         Pointer to the array of characters to be used to fill the string.
+      }
+      \sstsubsection{
+         nchars
+      }{
+         The number of characters in the array (zero or more).
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astString()
+      }{
+         If successful, the function returns a pointer to the start of
+         the allocated string. If the number of characters is zero, a
+         zero-length string is still allocated and a pointer to it is
+         returned.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A pointer value of NULL is returned if this function is
+         invoked with the global error status set or if it fails for any
+         reason.
+      }
+   }
+}
+\sstroutine{
+   astStringArray
+}{
+   Create an array of C strings from an array of characters
+}{
+   \sstdescription{
+      This function turns an array of fixed-length character data into
+      a dynamicllay allocated array of null-terminated C strings with
+      an index array that may be used to access them.
+
+      The array of character data supplied is assumed to hold \texttt{"} nel\texttt{"}
+      adjacent fixed-length strings (without terminating nulls), each
+      of length \texttt{"} len\texttt{"}  characters. This function allocates memory and
+      creates a null-terminated copy of each of these strings. It also
+      creates an array of \texttt{"} nel\texttt{"}  pointers which point at the start of
+      each of these new strings. A pointer to this index array is
+      returned.
+
+      The memory used is allocated in a single block and should later
+      be de-allocated using \htmlref{astFree}{astFree}.
+   }
+   \sstsynopsis{
+      char $*$$*$astStringArray( const char $*$chars, int nel, int len )
+   }
+   \sstparameters{
+      \sstsubsection{
+         chars
+      }{
+         Pointer to the array of input characters. The number of characters
+         in this array should be at least equal to (nel $*$ len).
+      }
+      \sstsubsection{
+         nel
+      }{
+         The number of fixed-length strings in the input character
+         array. This may be zero but should not be negative.
+      }
+      \sstsubsection{
+         len
+      }{
+         The number of characters in each fixed-length input
+         string. This may be zero but should not be negative.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astStringArray()
+      }{
+         A pointer to the start of the index array, which contains \texttt{"} nel\texttt{"}
+         pointers pointing at the start of each null-terminated output
+         string.
+
+         The returned pointer should be passed to astFree to de-allocate
+         the memory used when it is no longer required. This will free
+         both the index array and the memory used by the strings it
+         points at.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A NULL pointer will also be returned if the value of \texttt{"} nel\texttt{"}  is
+         zero, in which case no memory is allocated.
+
+         \sstitem
+         A pointer value of NULL will also be returned if this function
+         is invoked with the global error status set or if it fails for
+         any reason.
+      }
+   }
+}
+\sstroutine{
+   astStringCase
+}{
+   Convert a string to upper or lower case
+}{
+   \sstdescription{
+      This function converts a supplied string to upper or lower case,
+      storing the result in dynamically allocated memory. The \htmlref{astChrCase}{astChrCase}
+      function is similar, but stores the result in a supplied buffer.
+   }
+   \sstsynopsis{
+      char $*$astStringCase( const char string, int upper )
+   }
+   \sstparameters{
+      \sstsubsection{
+         string
+      }{
+         Pointer to the null terminated string to be converted.
+      }
+      \sstsubsection{
+         upper
+      }{
+         If non-zero, the string is converted to upper case. Otherwise it
+         is converted to lower case.
+      }
+   }
+   \sstreturnedvalue{
+      \sstsubsection{
+         astStringCase()
+      }{
+         If successful, the function returns a pointer to the start of
+         the allocated string. The returned memory should be freed using
+         \htmlref{astFree}{astFree} when no longer needed.
+      }
+   }
+   \sstnotes{
+      \sstitemlist{
+
+         \sstitem
+         A pointer value of NULL is returned if this function is
+         invoked with the global error status set or if it fails for any
+         reason.
+      }
+   }
+}
+\normalsize
+
+\newpage
+\section{\xlabel{FitsWcsCoverage}\label{ss:fitswcscoverage}FITS-WCS Coverage}
+
+This appendix gives details of the \htmlref{FitsChan}{FitsChan} class
+implementation of the conventions described in the FITS-WCS papers
+available at
+\url{http://fits.gsfc.nasa.gov/fits_wcs.html}. These conventions are
+used only if the \htmlref{Encoding}{Encoding} attribute of the FitsChan
+has the value ``FITS-WCS'' (whether set explicitly or defaulted). It
+should always be possible for a \htmlref{FrameSet}{FrameSet} to be read
+(using the
+\htmlref{astRead}{astRead}
+function) from a FitsChan containing a header which conforms to these
+conventions. However, only those FrameSets which are compatible with the
+FITS-WCS model can be \emph{written} to a FitsChan using the
+\htmlref{astWrite}{astWrite}
+function. For instance, if the current \htmlref{Frame}{Frame} of a
+FrameSet is re-mapped using, say, an arbitrary \htmlref{MathMap}{MathMap}
+then the FrameSet will no longer be compatible with the FITS-WCS model,
+and so will not be written out successfully to a FitsChan.
+
+The following sub-sections describe the details of the implementation of
+each of the first four FITS-WCS papers. Here, the term ``pixel axes'' is
+used to refer to the FITS pixel coordinates (i.e. the centre of the
+first image pixel has a value 1.0 on each pixel axis); the term ``IWC
+axes'' is used to refer to the axes of the Intermediate World Coordinate
+system; and the term ``WCS axes'' is used to refer to the axes of the final
+physical coordinate system described by the CTYPE\emph{i} keywords.
+
+\subsection{Paper I - General Linear Coordinates}
+When reading a \htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan}, these conventions are used if the CTYPE\emph{i} keyword
+values within the FitsChan do not conform to the conventions described in
+later papers, in which case the axes are assumed to be linear. When
+writing a FrameSet to a FitsChan, these conventions are used for axes
+which are described by a simple \htmlref{Frame}{Frame} (\emph{i.e.} not a
+\htmlref{SkyFrame}{SkyFrame}, \htmlref{SpecFrame}{SpecFrame}, \emph{etc.}).
+
+\htmlref{Table}{Table} \ref{tab:fitspaper1} describes the use made by AST of each keyword
+defined by FITS-WCS paper I.
+
+\begin{table}[htbp]
+\begin{tabular}{|l|p{2.5in}|p{2.5in}|}
+\hline
+\multicolumn{1}{|c|}{\textbf{Keyword}} & \multicolumn{1}{c|}{\textbf{Read}}
+& \multicolumn{1}{c|}{\textbf{Write}} \\ \hline
+
+\fitskey{WCSAXES\emph{a}}{Ignored.}{Set to the number of axes in the WCS
+Frame - only written if different to NAXIS.}
+
+\fitskey{CRVAL\emph{ia}}{Used to create the pixel to WCS
+\htmlref{Mapping}{Mapping}.}{Always written (see ``Choice of Reference
+Point'' below).}
+
+\fitskey{CRPIX\emph{ja}}{Used to create the pixel to WCS Mapping.}{Always
+written (see ``Choice of Reference Point'' below).}
+
+\fitskey{CDELT\emph{ia}}{Used to create the pixel to WCS Mapping.}{Only
+written if the \htmlref{CDMatrix}{CDMatrix} attribute of the FitsChan is
+set to zero.}
+
+\fitskey{CROTA\emph{i}}{Used to create the pixel to WCS Mapping.}{Only
+written in FITS-AIPS and FITS-AIPS++ encodings.}
+
+\fitskey{CTYPE\emph{ia}}{Used to choose the class and attributes of the
+WCS Frame, and to create the pixel to WCS Mapping (note, ``STOKES'' and
+``COMPLEX'' axes are treated as unknown linear axes).}{Always written
+(see ``Use and Choice of CTYPE keywords'' below).}
+
+\fitskey{CUNIT\emph{ia}}{Used to set the Units attributes
+of the WCS Frame.}{Only written if the Units attribute of the WCS Frame
+has been set explicitly. If so, the Units value for each axis is used as
+the CUNIT value.}
+
+\fitskey{PC\emph{i\_j}\emph{a}}{Used to create the pixel to WCS
+Mapping.}{Only written if the CDMatrix attribute of the FitsChan is set to
+zero.}
+
+\fitskey{CD\emph{i\_j}\emph{a}}{Used to create the pixel to WCS
+Mapping.}{Only written if the CDMatrix attribute of the FitsChan is set to
+a non-zero value.}
+
+\fitskey{PV\emph{i\_ma}}{Ignored for linear axes.}{Not written if the axes
+are linear.}
+
+\fitskey{PS\emph{i\_ma}}{Ignored.}{Not used.}
+
+\fitskey{WCSNAME\emph{a}}{Used to set the \htmlref{Domain}{Domain} attribute
+of the WCS Frame.}{Only written if the Domain attribute of the WCS Frame
+has been set explicitly. If so, the Domain value is used as the WCSNAME
+value.}
+
+\fitskey{CRDER\emph{ia}}{Ignored.}{Not used.}
+
+\fitskey{CSYER\emph{ia}}{Ignored.}{Not used.}
+
+\hline
+\end{tabular}
+\vspace{3.mm}
+\caption{Use of FITS-WCS Paper I keywords}
+\label{tab:fitspaper1}
+\end{table}
+
+\subsubsection{Requirements for a Successful Write Operation}
+When writing a \htmlref{FrameSet}{FrameSet} in which the WCS
+\htmlref{Frame}{Frame} is a simple Frame to a \htmlref{FitsChan}{FitsChan},
+success depends on the \htmlref{Mapping}{Mapping} from pixel coordinates
+(the base Frame in the FrameSet) to the WCS Frame being linear. The write
+operation will fail if this is not the case.
+
+\subsubsection{Use and Choice of CTYPE\emph{i} keywords}
+When reading a \htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan} the CTYPE\emph{i} values in the FitsChan are used to set the
+Symbol attributes of the corresponding WCS \htmlref{Frame}{Frame}. The Label attributes of the WCS Frame are set from
+the CNAME\emph{i} keywords, if present in the header. Otherwise they are set
+from the CTYPE\emph{i} comments strings in the header, so long as each
+axis has a unique non-blank comment. Otherwise, the Label attributes are
+set to the CTYPE\emph{i} values. The above procedure is over-ridden if
+the axis types conform to the conventions described in paper II or III,
+as described below.
+
+When writing a FrameSet to a FitsChan, each CTYPE\emph{i} value is set to
+the value of the Symbol attribute of the corresponding axis in the Frame
+being written. If a value has been set explicitly for the axis Label
+attribute, it is used as the axis comment (except that any existing
+comments in the FitsChan take precedence if the keyword value has not
+changed). The above procedure is over-ridden if the Frame is a
+\htmlref{SkyFrame}{SkyFrame} or a \htmlref{SpecFrame}{SpecFrame}, in which
+case the CTYPE\emph{i} value is derived from the \htmlref{System}{System}
+attribute of the Frame and the nature of the pixel to WCS \htmlref{Mapping}{Mapping}
+according to the conventions of papers II and III, as described below.
+
+\subsubsection{Choice of Reference Point}
+When writing a \htmlref{FrameSet}{FrameSet} to a
+\htmlref{FitsChan}{FitsChan}, the pixel coordinates of the
+reference point for linear axes (i.e. the CRPIX\emph{j} values) are
+chosen as follows:
+
+\begin{itemize}
+\item If the FrameSet is being written to a FitsChan which previously
+contained a set of axis descriptions with the same identifying letter,
+then the previous CRVAL\emph{j}values are converted into the coordinate system
+of the \htmlref{Frame}{Frame} being written (if possible). These values are then
+transformed into the pixel Frame, and the closest integer pixel values
+are used as the CRPIX keywords.
+\item If the above step could not be performed for any reason, the
+central pixel is used as the reference point. This requires the image
+dimensions to be present in the FitsChan in the form of a set of
+NAXIS\emph{j} keyword values.
+\item If both the above two steps failed for any axis, then the pixel
+reference position is set to a value of 1.0 on the pixel axis.
+\end{itemize}
+
+The pixel to WCS \htmlref{Mapping}{Mapping} is then used to find the corresponding
+CRVAL\emph{j}values.
+
+Again, the above procedure is over-ridden if the Frame is a
+\htmlref{SkyFrame}{SkyFrame} or a \htmlref{SpecFrame}{SpecFrame}, in which
+case the conventions of papers II and III are used as described below.
+
+
+\subsubsection{Choice of Axis Ordering}
+When reading a \htmlref{FrameSet}{FrameSet} from a
+\htmlref{FitsChan}{FitsChan}, WCS axis $i$ in the current
+\htmlref{Frame}{Frame} of the
+resulting FrameSet corresponds to axis $i$ in the FITS header.
+
+When writing a FrameSet to a FitsChan, the axis ordering for the FITS
+header is chosen to make the CD\emph{i\_j} or PC\emph{i\_j} matrix
+predominately diagonal. This means that the axis numbering in the FITS
+header will not necessarily be the same as that in the AST Frame.
+
+\subsubsection{Alternate Axis Descriptions}
+When reading a \htmlref{FrameSet}{FrameSet} from a
+\htmlref{FitsChan}{FitsChan} which contains alternate axis descriptions,
+each complete set of axis descriptions results in a single \htmlref{Frame}{Frame} being added
+to the final FrameSet, connected via an appropriate
+\htmlref{Mapping}{Mapping} to the base pixel Frame. The \htmlref{Ident}{Ident} attribute of the Frame is set to hold the single alphabetical
+character which is used to identify the set of axis descriptions within
+the FITS header (a single space is used for the primary axis descriptions).
+
+When writing a FrameSet to a FitsChan, it is assumed that the base Frame
+represents pixel coordinates, and the current Frame represents the
+primary axis descriptions. If there are any other Frames present in the
+FrameSet, an attempt is made to create a complete set of ``alternate''
+set of keywords describing each additional Frame. The first character in
+the Ident attribute of the Frame is used as the single character
+descriptor to be appended to the keyword, with the proviso that a given
+character can only be used once. If a second Frame is found with an Ident
+attribute which has already been used, its Ident attribute is ignored and
+the next free character is used instead. Note, failure to write a set of
+alternate axis descriptions does not result in failure of the entire
+write operation: the primary axis descriptions are still written,
+together with any other alternate axis descriptions which can be produced
+successfully.
+
+\subsection{Paper II - Celestial Coordinates}
+These conventions are used when reading a \htmlref{FrameSet}{FrameSet}
+from a \htmlref{FitsChan}{FitsChan} containing appropriate CTYPE\emph{i}
+values, and when writing a FrameSet in which the WCS \htmlref{Frame}{Frame}
+is a \htmlref{SkyFrame}{SkyFrame}.
+
+\htmlref{Table}{Table} \ref{tab:fitspaper2} describes the use made by AST of each keyword
+whose meaning is defined or extended by FITS-WCS paper II.
+
+\begin{table}[htbp]
+\begin{tabular}{|l|p{2.5in}|p{2.5in}|}
+\hline
+\multicolumn{1}{|c|}{\textbf{Keyword}} & \multicolumn{1}{c|}{\textbf{Read}}
+& \multicolumn{1}{c|}{\textbf{Write}} \\ \hline
+
+\fitskey{CTYPE\emph{ia}}{All coordinate systems and projection types
+listed in paper II are supported (note, ``CUBEFACE'' axes are treated as
+unknown linear axes). In addition, "-HPX" (HEALPix) and "-XPH" (polar
+HEALPix) are supported.}{Determined by the \htmlref{System}{System} attribute
+of the SkyFrame and the \htmlref{WcsType}{WcsType} attribute of the
+\htmlref{WcsMap}{WcsMap} within the FrameSet.}
+
+\fitskey{CUNIT\emph{ia}}{Ignored (assumed to be 'degrees').}{Not written.}
+
+\fitskey{PV\emph{i\_ma}}{Used to create the pixel to WCS \htmlref{Mapping}{Mapping} (values
+are stored as attributes of a WcsMap within this Mapping).}{Values are
+obtained from the WcsMap in the pixel to WCS Mapping.}
+
+\fitskey{LONPOLE\emph{a}}{Used to create the pixel to WCS Mapping. Also
+stored as a \htmlref{PVi\_m}{PVi\_m} attribute for the longitude axis of the WcsMap.}{Only
+written if not equal to the default value defined in paper II (see
+``Choice of LONPOLE/LATPOLE'' below).}
+
+\fitskey{LATPOLE\emph{a}}{Used to create the pixel to WCS Mapping. Also
+stored as a PV attribute for the longitude axis of the WcsMap.}{Only
+written if not equal to the default value defined in paper II (see
+``Choice of LONPOLE/LATPOLE'' below).}
+
+\fitskey{RADESYS\emph{a}}{Used to set the attributes of the SkyFrame. All
+values supported except that ecliptic coordinates are currently always
+assumed to be FK5.}{Always written. Determined by the System attribute of
+the SkyFrame.}
+
+\fitskey{EQUINOX\emph{a}}{Used to set the \htmlref{Equinox}{Equinox} attribute
+of the SkyFrame.}{Written if relevant. Determined by the Equinox attribute of
+the SkyFrame.}
+
+\fitskey{EPOCH}{Used to set the Equinox attribute of the SkyFrame.}{Only
+written if using FITS-AIPS and FITS-AIPS++ encodings. Determined by the Equinox attribute
+of the SkyFrame.}
+
+\fitskey{MJD-OBS}{Used to set the \htmlref{Epoch}{Epoch} attribute of the
+SkyFrame. DATE-OBS is used if MJD-OBS is not present. A default value based on
+RADESYS and EQUINOX is used if used if DATE-OBS is not present
+either.}{Determined by the Epoch attribute of the SkyFrame. Only written
+if this attribute has been set to an explicit value (in which case
+DATE-OBS is also written).}
+
+\hline
+\end{tabular}
+\vspace{3.mm}
+\caption{Use of FITS-WCS Paper II keywords}
+\label{tab:fitspaper2}
+\end{table}
+
+\subsubsection{Requirements for a Successful Write Operation}
+When writing a \htmlref{FrameSet}{FrameSet} in which the WCS
+\htmlref{Frame}{Frame} is a \htmlref{SkyFrame}{SkyFrame} to a
+\htmlref{FitsChan}{FitsChan}, success depends on the following conditions
+being met:
+
+\begin{enumerate}
+\item The \htmlref{Mapping}{Mapping} from pixel coordinates (the base Frame
+in the FrameSet) to the WCS SkyFrame includes a \htmlref{WcsMap}{WcsMap}.
+\item The Mapping prior to the WcsMap (\emph{i.e.} from pixel to IWC) is linear.
+\item The Mapping after the WcsMap (\emph{i.e.} from native spherical to
+celestial coordinates) is a spherical rotation for the
+celestial axes, and linear for any other axes.
+\item The \htmlref{TabOK}{TabOK} attribute is set to a non-zero positive value in the FitsChan,
+and the longitude and latitude axes are separable. In this case the Mapping will
+be described by a pair of 1-dimensional look-up tables, using the ``-TAB''
+algorithm described in FITS-WCS paper III.
+\end{enumerate}
+
+If none of the above conditions hold, the write operation will be
+unsuccessful.
+
+\subsubsection{Choice of LONPOLE/LATPOLE}
+When writing a \htmlref{FrameSet}{FrameSet} to a \htmlref{FitsChan}{FitsChan},
+the choice of LONPOLE and LATPOLE values is determined as follows:
+
+\begin{enumerate}
+
+\item If the projection represented by the \htmlref{WcsMap}{WcsMap} is
+azimuthal, then any values set for attributes ``PV\emph{i}\_3''
+and ``PV\emph{i}\_4'' (where ``\emph{i}'' is the index of the longitude axis)
+within the WcsMap are used as the LONPOLE and LATPOLE values. Reading a
+FrameSet from a FITS-WCS header
+results in the original LONPOLE and LATPOLE values being stored within a
+WcsMap within the FrameSet. Consequently, if a FrameSet is read from a
+FITS-WCS header and it is subsequently written out to a new FITS-WCS
+header, the original LONPOLE and LATPOLE values will usually be used in
+the new header (the exception being if the WcsMap has been explicitly
+modified before being written out again). Any extra rotation of the sky
+is absorbed into the CD\emph{i\_j} or PC\emph{i\_j} matrix (this is
+possible only if the projection is azimuthal).
+
+\item If the projection represented by the WcsMap is azimuthal but no
+values have been set for the ``PV\emph{i}\_3'' and ``PV\emph{i}\_4''
+attributes within the WcsMap, then the default LONPOLE and LATPOLE values
+are used. This results in no LONPOLE or LATPOLE keywords being stored in
+the header since default values are never stored. Any extra rotation of
+the sky is absorbed into the CD\emph{i\_j} or PC\emph{i\_j} matrix (this
+is possible only if the projection is azimuthal).
+
+\item If the projection represented by the WcsMap is not azimuthal,
+then the values of LONPOLE and LATPOLE are found by transforming the
+coordinates of the celestial north pole (\emph{i.e} longitude zero,
+latitude $+\pi/2$) into native spherical coordinates using the inverse of
+the \htmlref{Mapping}{Mapping} which follows the WcsMap.
+
+\end{enumerate}
+
+\subsubsection{User Defined Fiducial Points}
+When reading a \htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan}, projection parameters
+PV\emph{i}\_0, PV\emph{i}\_1 and PV\emph{i}\_2 (for longitude axis
+``\emph{i}'') are used to indicate a user-defined fiducial point as
+described in section 2.5 of paper II. This results in a shift of IWC
+origin being applied \emph{before} the \htmlref{WcsMap}{WcsMap} which converts
+IWC into
+native spherical coordinates. The values of these projection parameters,
+if supplied, are stored as the corresponding \htmlref{PVi\_m}{PVi\_m} attributes
+of the WcsMap.
+
+When writing a FrameSet to a FitsChan, the PV attributes of the WcsMap
+determine the native coordinates of the fiducial point (the fixed
+defaults for each projection described in paper II are used if the PV
+attributes of the WcsMap have not been assigned a value). The
+corresponding celestial coordinates are used as the CRVAL\emph{i}
+keywords and the corresponding pixel coordinates as the CRPIX\emph{j}
+keywords.
+
+\subsubsection{Common Non-Standard Features}
+A collection of common non-standard features are supported when reading a
+\htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan}, in addition
+to those embodied within the
+available encodings of the FitsChan class. These are translated into the
+equivalent standard features before being used to create a FrameSet.
+Note, the reverse operation is never performed: it is not possible to
+produce non-standard features when writing a FrameSet to a FitsChan
+(other than those embodied in the available encodings of the FitsChan
+class). The supported non-standard features include:
+
+\begin{itemize}
+\item EQUINOX keywords with string values equal to a date preceded
+by the letter B or J (\emph{e.g.} ``B1995.0'').
+
+\item EQUINOX or EPOCH keywords with value zero  (these are converted to
+B1950).
+
+\item The IRAF ``ZPX'' projection is represented by a
+\htmlref{WcsMap}{WcsMap} with type of
+AST\_\_ZPN. \htmlref{Projection}{Projection} parameter values are read from any WAT\emph{i\_nnn}
+keywords, and corresponding \htmlref{PVi\_m}{PVi\_m} attributes are set in the
+WcsMap. The WAT\emph{i\_nnn} keywords may specify corrections to the basic
+ZPN projection by including ``lngcor'' or ``latcor'' terms. These are
+supported if they use half cross-terms, in either simple or Chebyshev
+representation.
+
+\item The IRAF ``TNX'' projection is represented by a WcsMap with type of
+AST\_\_TPN (a distorted TAN projection retained within the WcsMap class
+from an early draft of the FITS-WCS paper II). Projection parameter values
+are read from any WAT\emph{i\_nnn} keywords, and corresponding PV
+attributes are set in the WcsMap. If the TNX projection cannot be
+converted exactly into an AST\_\_TPN projection, ASTWARN keywords are
+added to the FitsChan containing a warning message (but only if the
+\htmlref{Warnings}{Warnings} attribute of the FitsChan is set appropriately). Currently,
+TNX projections that use half cross-terms, in either simple or Chebyshev
+representation, are supported.
+
+\item ``QV'' parameters for TAN projections (as produced by
+\xref{AUTOASTROM}{sun242}{}
+\footnote{\url{http://www.astro.gla.ac.uk/users/norman/star/autoastrom/}}
+are renamed to the equivalent ``PV'' parameters.
+
+\item TAN projections that have associated ``PV'' parameters on the
+latitude axis are converted to the corresponding TPN (distorted TAN)
+projections. This conversion can be controlled using the \htmlref{PolyTan}{PolyTan} attribute
+of the FitsChan class.
+
+\end{itemize}
+
+\subsection{Paper III - Spectral Coordinates}
+These conventions are used when reading a \htmlref{FrameSet}{FrameSet}
+from a \htmlref{FitsChan}{FitsChan} which includes appropriate
+CTYPE\emph{i} values, and when writing a FrameSet in which
+the WCS \htmlref{Frame}{Frame} is a \htmlref{SpecFrame}{SpecFrame}.
+
+\htmlref{Table}{Table} \ref{tab:fitspaper3} describes the use made by AST of each keyword
+whose meaning is defined or extended by FITS-WCS paper III.
+
+\begin{table}[htbp]
+\begin{footnotesize}
+\begin{tabular}{|l|p{2.5in}|p{2.5in}|}
+\hline
+\multicolumn{1}{|c|}{\textbf{Keyword}} & \multicolumn{1}{c|}{\textbf{Read}}
+& \multicolumn{1}{c|}{\textbf{Write}} \\ \hline
+
+\fitskey{CTYPE\emph{ia}}{All coordinate systems and projection types
+listed in paper III are supported algorithm (the ``-LOG'' algorithm may
+also be applied to non-spectral linear axes; the ``-TAB'' algorithm
+requires the \htmlref{TabOK}{TabOK} attribute to be set in the FitsChan).}{Determined by the \htmlref{System}{System} attribute of the
+SpecFrame and the nature of the pixel to SpecFrame
+\htmlref{Mapping}{Mapping}.}
+
+\fitskey{CUNIT\emph{ia}}{Used to set the Units attribute of
+the SpecFrame (note, SpecFrames always have an ``active'' Units attribute
+(see \htmlref{astSetActiveUnit}{astSetActiveUnit}).}{Always written.}
+
+\fitskey{PV\emph{i\_ma}}{Used to create the pixel to WCS Mapping (values
+are stored as attributes of a \htmlref{GrismMap}{GrismMap}).}
+{Set from the attributes of the GrismMap, if present, and if set explicitly.}
+
+\fitskey{SPECSYS\emph{a}}{Used to set the \htmlref{StdOfRest}{StdOfRest}
+attribute of the SpecFrame (all systems are supported except CMBDIPOL).}
+{Set from the StdOfRest attribute of the SpecFrame, but only if it has been
+set explicitly.}
+
+\fitskey{SSYSOBS\emph{a}}{Ignored.}{Never written.}
+
+\fitskey{OBSGEO-X/Y/Z}{Used to set the \htmlref{ObsLon}{ObsLon} and
+\htmlref{ObsLat}{ObsLat} attributes of the Frame (the observers
+height above sea level is ignored).}{Set from the ObsLon and ObsLat
+attributes of the Frame, if they have been set explicitly (it is
+assumed that the observer is at sea level).}
+
+\fitskey{MJD-AVG}{Used to set the \htmlref{Epoch}{Epoch} attributes of
+the SpecFrame.}{Set from the Epoch attribute of the SpecFrame, if it has
+been set explicitly.}
+
+\fitskey{SSYSSRC\emph{a}}{Used to set the \htmlref{SourceVRF}{SourceVRF} attribute of the
+SpecFrame
+(all systems are supported except CMBDIPOL).} {Set from the SourceVRF
+attribute of the SpecFrame.}
+
+\fitskey{ZSOURCE\emph{a}}{Used to set the \htmlref{SourceVel}{SourceVel}
+attribute of the SpecFrame (the SourceVRF attribute
+is first set to the system indicated by the SSYSSRC keyword, and the
+ZSOURCE value is then converted to an apparent radial velocity and stored
+as the SourceVel attribute).}
+{Set from the SourceVel attribute of
+the SpecFrame, if it has been set explicitly (the SourceVel value is
+first converted from apparent radial velocity to redshift).}
+
+\fitskey{VELOSYS\emph{a}}{Ignored.}{Set from the attributes of the
+SpecFrame that define the standard of rest and the observers position.}
+
+\fitskey{RESTFRQ\emph{a}}{Used to set the \htmlref{RestFreq}{RestFreq}
+attribute of the SpecFrame.}{Set from the RestFreq attribute of the
+SpecFrame, but only if the System attribute is not set to
+``WAVE'', ``VOPT'', ``ZOPT'' or ``AWAV'', and only if RestFreq has been set
+explicitly.}
+
+\fitskey{RESTWAV\emph{a}}{Used to set the RestFreq
+attribute of the SpecFrame (after conversion from wavelength to frequency).}
+{Set from the RestFreq attribute of the SpecFrame (after conversion), but only if the
+System attribute is set to ``WAVE'', ``VOPT'', ``ZOPT'' or
+``AWAV'', and only if RestFreq has been set explicitly.}
+
+\fitskey{CNAME\emph{ia}}{Used to set the Label attributes of
+the WCS Frame keywords.}{Set from the Label attributes of the WCS Frame,
+if they have been set explicitly.}
+\hline
+\end{tabular}
+\end{footnotesize}
+\vspace{3.mm}
+\caption{Use of FITS-WCS Paper III keywords}
+\label{tab:fitspaper3}
+\end{table}
+
+\subsubsection{Requirements for a Successful Write Operation}
+When writing a \htmlref{FrameSet}{FrameSet} in which the WCS \htmlref{Frame}{Frame} is a \htmlref{SpecFrame}{SpecFrame} to a
+\htmlref{FitsChan}{FitsChan}, the write operation is successful only if
+the \htmlref{Mapping}{Mapping} from pixel coordinates (the base Frame
+in the FrameSet) to the SpecFrame satisfies one of the following conditions:
+
+\begin{enumerate}
+\item It is linear.
+\item It is logarithmic.
+\item It is linear if the SpecFrame were to be re-mapped into one of the
+other spectral systems supported by FITS-WCS paper III.
+\item It contains a \htmlref{GrismMap}{GrismMap}, and the Mapping before the GrismMap (from
+pixel coordinates to grism parameter) is linear, and the Mapping after the
+GrismMap is either null or represents a change of spectral system from wavelength (air or
+vacuum) to one of the supported spectral systems.
+\item The \htmlref{TabOK}{TabOK} attribute is set to a non-zero positive value in the FitsChan.
+\end{enumerate}
+
+If none of the above conditions hold, the write operation will be
+unsuccessful. Note, if the FitsChan's TabOK attribute is set to a positive
+non-zero value then any Mapping that does not meet any of the earlier conditions
+will be written out as a look-up table, using the ``-TAB'' algorithm described
+in FITS-WCS paper III. If the TabOK attribute is to zero (the default) or
+negative in the FitsChan, then the write operation will be unsuccessful unless
+one of the eaerlier conditions is met.\footnote{If the -TAB algorithm is used, the
+positive value of the TabOK attribute is used as the table version number
+(the EXTVER header) in the associated FITS binary table.}
+
+\subsubsection{Common Non-Standard Features}
+The following non-standard features are supported when reading spectral
+axes from a \htmlref{FitsChan}{FitsChan}:
+
+\begin{itemize}
+\item Conversion of ``-WAV'', ``-FRQ'' and ``-VEL'' algorithm codes
+(specified in early drafts of paper III) to the corresponding
+``-X2P'' form.
+\item Conversion of ``RESTFREQ'' to ``RESTFRQ''
+\end{itemize}
+
+\subsection{Paper IV - Coordinate Distortions}
+
+This paper proposes that an additional 4 character code be appended to
+the end of the CTYPE\emph{i} keyword to specify the nature of any
+distortion away from the basic algorithm described by the first 8
+characters of the CTYPE\emph{i} value. Currently AST ignores all such
+codes when reading a \htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan} (except for the ``-SIP'' code
+defined by the Spitzer Space Telescope project - see below). This means that
+a FrameSet can still be read from such headers, but the \htmlref{Mapping}{Mapping} which gives
+the WCS position associated with a given pixel position will reflect only
+the basic algorithm and will not include the effects of the distortion.
+
+If such a FrameSet is then written out to a FitsChan, the resulting
+CTYPE\emph{i} keywords will include no distortion code.
+
+\subsubsection{The ``-SIP'' distortion code}
+
+The Spitzer Space Telescope project
+(\url{http://www.spitzer.caltech.edu/})
+has developed its own system for encoding 2-dimensional image distortion
+within a FITS header, based on the proposals of paper IV. A description
+of this system is available in
+\url{http://ssc.spitzer.caltech.edu/postbcd/doc/shupeADASS.pdf}. In this
+system, the presence of distortion is indicated by appending the
+distortion code ``-SIP'' to the CTYPE\emph{i} keyword values for the
+celestial axes. The distortion takes the form of a polynomial function
+which is applied to the pixel coordinates, after subtraction of the
+CRPIX\emph{j} values.
+
+This system is a strictly 2 dimensional system. When reading a
+\htmlref{FrameSet}{FrameSet} from a \htmlref{FitsChan}{FitsChan} which
+includes the ``-SIP'' distortion code, AST assumes that it
+is only applied to the first 2 WCS axes in a FITS header (i.e.
+CTYPE1 and CTYPE2). If the ``-SIP'' distortion code is attached to other
+axes, it will be ignored. The distortion itself is represented by a
+\htmlref{PolyMap}{PolyMap} within the resulting FrameSet.
+
+If a FrameSet is read from a FitsChan which includes ``-SIP''
+distortion, and an attempt is then made to write this FrameSet out to a
+FitsChan, the write operation will fail unless the distortion is
+insignificant (\emph{i.e.} is so small that the tests for linearity built
+into AST are passed). In this case, no distortion code will be appended to
+the resulting CTYPE\emph{i} keyword values.
+
+\newpage
+\section{\xlabel{changes_and_new_features}\label{ss:changes}Release Notes}
+
+\subsection{Changes Introduced in V1.1}
+
+The following describes the most significant changes which occurred in
+the AST library between versions V1.0 and V1.1 (not the most recent
+version):
+
+\begin{enumerate}
+
+\item A new ``How To\ldots'' section (\secref{ss:howto}) has been
+added to this document. It contains simple recipies for performing
+commonly-required operations using AST.
+
+\item A new \htmlref{astUnformat}{astUnformat} function has been provided to read formatted
+coordinate values for the axes of a \htmlref{Frame}{Frame}
+(\secref{ss:unformattingaxisvalues}). In essence, this function is the
+inverse of \htmlref{astFormat}{astFormat}. It may be used to decode user-supplied formatted
+values representing coordinates, turning them into numerical values
+for processing. Celestial coordinates may also be read using this
+function (\secref{ss:unformattingskyaxisvalues}) and free-format input
+is supported.
+
+\item The Format attribute string used by a \htmlref{SkyFrame}{SkyFrame} when formatting
+celestial coordinate values now allows the degrees/hours field to be
+omitted, so that celestial coordinates may be given in (\emph{e.g.})
+arc-minutes and/or arc-seconds
+(\secref{ss:formattingskyaxisvalues}). As a result, the degrees/hours
+field is no longer included by default.  A new ``t'' format specifier
+has been introduced (see the Format attribute) to allow minutes and/or
+seconds of time to be specified if required.
+
+\item A new function \htmlref{astMapBox}{astMapBox} has been introduced. This allows you to
+find the extent of a ``bounding box'' which just encloses another box
+after it has been transformed by a \htmlref{Mapping}{Mapping}. A typical use might be to
+calculate the size which an image would have if it were transformed by
+the Mapping.
+
+\item A new class of \htmlref{Object}{Object}, the \htmlref{IntraMap}{IntraMap}, has been introduced
+(\secref{ss:intramaps}). This is a specialised form of Mapping which
+encapsulates a privately-defined coordinate transformation function
+(\emph{e.g.}\ written in C) so that it may be used like any other AST
+Mapping. This allows you to create Mappings that perform any
+conceivable coordinate transformation.
+
+\item The internal integrity of a \htmlref{FrameSet}{FrameSet} is now automatically
+preserved whenever changes are made to any attributes which affect the
+current Frame (either by setting or clearing their values). This is
+accomplished by appropriately re-mapping the current Frame to account
+for any change to the coordinate system which it represents
+(\secref{ss:framesetintegrity}).
+
+\item The internal structure of a FrameSet is now automatically tidied
+to eliminate redundant nodes whenever any of its Frames is removed or
+re-mapped. Automatic simplification of any compound Mappings which
+result may also occur. The effect of this change is to prevent the
+accumulation of unnecessary structure in FrameSets which are
+repeatedly modified.
+
+\item Some improvements have been made to the algorithms for
+simplifying compound Mappings, as used by \htmlref{astSimplify}{astSimplify}.
+
+\item The textual representation used for some Objects
+(\emph{i.e.}\ when they are written to a \htmlref{Channel}{Channel}) has changed
+slightly, but remains compatible with earlier versions of AST.
+
+\item Interfaces to the internal functions and macros used by AST for
+handling memory and error conditions are now provided \emph{via} the
+``ast.h'' header file. This is for the benefit of those writing
+(\emph{e.g.}) new graphics interfaces for AST.
+
+\item A problem has been fixed which could result when using \htmlref{astRead}{astRead}
+to read FITS headers in which the CDELT value is zero. Previously,
+this could produce a Mapping whose inverse transformation was not
+defined and this could unnecessarily restrict the use to which it
+could be put. The problem has been overcome by supplying a suitable
+small CDELT value for FITS axes which have only a single pixel.
+
+\item A bug has been fixed which could occasionally cause a \htmlref{MatrixMap}{MatrixMap}
+to be used with the wrong \htmlref{Invert}{Invert} attribute value when it forms part of
+a compound Mapping which is being simplified using astSimplify.
+
+
+\item A problem has been fixed which could prevent tick marks being
+drawn on a coordinate axis close to a singularity in the coordinate
+system.
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.2}
+
+The following describes the most significant changes which occurred in
+the AST library between versions V1.1 and V1.2 (not the most recent
+version):
+
+\begin{enumerate}
+\item A new function, \htmlref{astPolyCurve}{astPolyCurve}, has been introduced to allow more
+efficient plotting of multiple geodesic curves
+(\secref{ss:plottinggeodesics}).
+
+\item A new set of functions, \htmlref{astResample$<$X$>$}{astResample$<$X$>$}, has been introduced
+to perform resampling of gridded data such as images
+(\emph{i.e.}\ re-gridding) under the control of a geometrical
+transformation specified by a \htmlref{Mapping}{Mapping}.
+
+\item The command-line options ``$-$pgp'' and ``$-$pgplot'', which
+were previously synonymous when used with the ``\htmlref{ast\_link}{ast\_link}'' and
+``\htmlref{ast\_link\_adam}{ast\_link\_adam}'' commands, are no longer synonymous. The option
+``$-$pgp'' now causes linking with the Starlink version of PGPLOT
+(which uses GKS to generate its output), while ``$-$pgplot'' links
+with the standard (or ``native'') version of PGPLOT.
+
+\item The function \htmlref{astMapBox}{astMapBox} has been changed to execute more quickly,
+although this has been achieved at the cost of some loss of robustness
+when used with difficult Mappings.
+
+\item A new value of ``FITS-IRAF'' has been introduced for the
+\htmlref{Encoding}{Encoding} attribute of a \htmlref{FitsChan}{FitsChan}. This new encoding provides an
+interim solution to the problem of storing coordinate system
+information in FITS headers, until the proposed new FITS-WCS standard
+becomes stable.
+
+\item When a \htmlref{FrameSet}{FrameSet} is created from a set of FITS header cards (by
+reading from a FitsChan using a ``foreign'' encoding), the base \htmlref{Frame}{Frame}
+of the resulting FrameSet now has its \htmlref{Domain}{Domain} attribute set to
+``GRID''. This reflects the fact that this Frame represents FITS data
+grid coordinates (equivalent to FITS pixel coordinates---see
+\secref{ss:domainconventions}). Previously, this Domain value was not
+set.
+
+\item \htmlref{astFindFits}{astFindFits} now ignores trailing spaces in its keyword template.
+
+\item \htmlref{astPutFits}{astPutFits} now recognises ``D'' and ``d'' as valid exponent
+characters in floating point numbers.
+
+\item The FitsChan class is now more tolerant of common minor
+violations of the FITS standard.
+
+\item The FitsChan class now incorporates an improved test for the
+linearity of Mappings, allowing more reliable conversion of AST data
+into FITS (using ``foreign'' FITS encodings).
+
+\item Some further improvements have been made to the algorithms for
+simplifying compound Mappings, as used by \htmlref{astSimplify}{astSimplify}.
+
+\item A new \htmlref{UnitRadius}{UnitRadius} attribute has been added to the \htmlref{SphMap}{SphMap}
+class. This allows improved simplification of compound Mappings
+(CmpMaps) involving SphMaps and typically improves performance when
+handling FITS world coordinate information.
+
+\item A \htmlref{MatrixMap}{MatrixMap} no longer propagates input coordinate values of
+AST\_\_BAD automatically to all output coordinates. If certain output
+coordinates do not depend on the affected input coordinate(s) because
+the relevant matrix elements are zero, then they may now remain valid.
+
+\item A minor bug has been corrected which could cause certain
+projections which involve half the celestial sphere to produce valid
+coordinates for the other (unprojected) half of the sphere as well.
+
+\item A bug has been fixed which could occasionally cause \htmlref{astConvert}{astConvert}
+to think that conversion between a \htmlref{CmpFrame}{CmpFrame} and another Frame was
+possible when, in fact, it wasn't.
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.3}
+
+The following describes the most significant changes which occurred in
+the AST library between versions V1.2 and V1.3 (not the most recent
+version):
+
+\begin{enumerate}
+\item A new set of functions, \htmlref{astResample$<$X$>$}{astResample$<$X$>$}, has been introduced to
+provide	efficient resampling of gridded data, such as spectra and
+images, under the control of a geometrical transformation specified by
+a \htmlref{Mapping}{Mapping}. A variety of sub-pixel interpolation schemes are supported.
+
+\item A new class, \htmlref{PcdMap}{PcdMap}, has been introduced. This is a specialised
+form of Mapping which implements 2-dimensional pincushion or barrel
+distortion.
+
+\item A bug has been fixed which could cause a \htmlref{FitsChan}{FitsChan} to produce too
+many digits when formatting floating point values for inclusion in a
+FITS header if the numerical value was in the range -0.00099999\ldots
+to -0.0001.
+
+\item A bug has been fixed which could cause a FitsChan to lose the
+comment associated with a string value in a FITS header.
+
+\item A FitsChan now reports an error if it reads a FITS header which
+identifies a non-standard sky projection (previously, this was
+accepted without error and a Cartesian projection used instead).
+
+\item A bug has been fixed which could prevent conversion between the
+coordinate systems represented by two CmpFrames. This could only occur
+if the CmpFrames contained a relatively large number of nested Frames.
+
+%\item A bug has been fixed which could cause a program to crash if
+%FrameSets were nested inside each other (for example, if one \htmlref{FrameSet}{FrameSet}
+%had another FrameSet added to it for use as a \htmlref{Frame}{Frame} or Mapping). The
+%problem could only occur if the nested structure was loaded from a data
+%c+
+%file (using \htmlref{astRead}{astRead}).
+%c-
+%f+
+%file (using AST\_READ).
+%f-
+%
+\item Further improvements have been made to the simplification of
+compound Mappings, including fixes for several bugs which could cause
+indefinite looping or unwanted error messages.
+
+\item Some memory leaks have been fixed.
+
+\item A small number of documentation errors have been corrected.
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.4}
+
+The following describes the most significant changes which have occurred
+in the AST library between versions V1.3 and V1.4 (not the most recent
+version):
+
+\begin{enumerate}
+\item A new \htmlref{MathMap}{MathMap} class has been introduced. This is a form of
+\htmlref{Mapping}{Mapping} that allows you to define coordinate transformations in a
+flexible and transportable way using arithmetic operations and
+mathematical functions similar to those available in C.
+
+\item {\bf{WARNING---INCOMPATIBLE CHANGE.}} Transformation functions
+used with the \htmlref{IntraMap}{IntraMap} class (see, for example, \htmlref{astIntraReg}{astIntraReg}) now
+require a ``this'' pointer as their first parameter. \textbf{Existing
+implementations will not continue to work correctly with this version
+of AST unless this parameter is added.} There is no need for existing
+software to make use of this pointer, but it must be present.
+
+This change has been introduced so that transformation functions can gain
+access to IntraMap attributes.
+
+\item A new \htmlref{IntraFlag}{IntraFlag} attribute has been added to the IntraMap
+class. This allows the transformation functions used by IntraMaps to
+adapt to produce the required transformation on a per-IntraMap basis
+(\secref{ss:intraflag}).
+
+\item The \htmlref{Plot}{Plot} attributes MajTickLen and MinTickLen, which control the
+length of major and minor tick marks on coordinate axes, may now be
+subscripted using an axis number. This allows tick marks of different
+lengths to be used on each axis. It also allows tick marks to be
+suppressed on one axis only by setting the length to zero.
+
+\item The value of the Plot attribute NumLab, which controls the
+plotting of numerical labels on coordinate axes, no longer has any
+effect on whether labelling of a coordinate grid is interior or
+exterior (as controlled by the \htmlref{Labelling}{Labelling} attribute).
+
+\item The \htmlref{FitsChan}{FitsChan} class now provides some support for the
+IRAF-specific ``ZPX'' sky projection, which is converted transparently
+into the equivalent FITS ``ZPN'' projection (see the description of the
+\htmlref{Encoding}{Encoding} attribute for details).
+
+\item The FitsChan class now recognises the coordinate system ``ICRS''
+(International Celestial Reference \htmlref{System}{System}) as equivalent to
+``FK5''. This is an interim measure and full support for the
+(exceedingly small) difference between ICRS and FK5 will be added at a
+future release.
+
+Note that ``ICRS'' is not yet recognised as a coordinate system by other
+classes such as \htmlref{SkyFrame}{SkyFrame}, so this change only facilitates the
+importation of foreign data.
+
+\item A bug in the FitsChan class has been fixed which could result in
+longitude values being incorrect by 180 degrees when using cylindrical
+sky projections, such as the FITS ``CAR'' projection.
+
+\item A bug in the FitsChan class has been fixed which could result in
+the FITS sky projection parameters ProjP(0) to ProjP(9) being
+incorrectly named PROJP1 to PROJP10 when written out as FITS cards.
+
+\item A bug in the FitsChan class has been fixed which could cause
+confusion between the FITS-IRAF and FITS-WCS encoding schemes if both
+a CD matrix and a PC matrix are erroneously present in a FITS header.
+
+\item Some minor memory leaks have been fixed.
+
+\item A small number of documentation errors have been corrected.
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.5}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V1.4 and V1.5 (not the most
+recent version):
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class has been modified to support the latest draft
+FITS WCS standard, described in the two papers ``Representation of world
+coordinates in FITS'' (E.W.\,Greisen and M.\,Calabretta, dated 30th
+November, 1999), and ``Representation of celestial coordinates in FITS''
+(M.\,Calabretta and E.W.\,Greisen, dated 24th September, 1999). These are
+available at
+\url{http://www.cv.nrao.edu/fits/documents/wcs/wcs.html}.
+
+The FITS-WCS encoding now uses these updated conventions. The main
+changes are:
+
+\begin{itemize}
+\item Rotation and scaling of pixel axes is now represented by a matrix
+of \texttt{CDj\_i} keywords instead of a combination of \texttt{PCjjjiii} and
+\texttt{CDELTj} keywords.
+\item \htmlref{Projection}{Projection} parameters are now associated with particular axes and
+are represented by \texttt{\htmlref{PVi\_m}{PVi\_m}} keywords instead of the \texttt{PROJPm}
+keywords.
+\item The tangent plane projection (``TAN'') can now include optional
+polynomial correction terms.
+\item An entire set of keywords must be supplied for each set of secondary
+axis descriptions, and each such keyword must finish with a single
+character indicating which set it belongs to. This means that keywords
+which previously occupied eight characters have been shorten to seven to
+leave room for this extra character. Thus \texttt{LONGPOLE} has become \texttt{LONPOLE} and \texttt{RADECSYS} has become \texttt{RADESYS}.
+\end{itemize}
+
+\item Two new encodings have been added to the FitsChan class:
+\begin{description}
+
+\item [FITS-PC] This encoding uses the conventions of the now superseded
+FITS WCS paper by E.W.\,Greisen and M.\,Calabretta which used keywords
+\texttt{CDELTj} and \texttt{PCjjjiii} to describe axis scaling and rotation.
+These are the conventions which were used by the FITS-WCS encoding prior
+to version 1.5 of AST. This encoding is provided to allow existing data
+which use these conventions to be read. It should not in general be used
+to create new data.
+
+\item [FITS-AIPS] This encoding is based on the conventions described in the
+document ``Non-linear Coordinate Systems in AIPS'' by Eric W. Greisen
+(revised 9th September, 1994 and available by ftp from fits.cv.nrao.edu
+/fits/documents/wcs/aips27.ps.Z). This encoding uses \texttt{CROTAi} and
+\texttt{CDELTi} keywords to describe axis rotation and scaling.
+
+\end{description}
+
+\item The FitsChan class now provides some support for the IRAF-specific
+``TNX'' sky projection, which is converted transparently into the
+equivalent FITS ``TAN'' projection (see the description of the \htmlref{Encoding}{Encoding}
+attribute for details).
+
+\item FrameSets originally read from a DSS encoded FITS header can now be
+written out using the FITS-WCS encoding (a TAN projection with correction
+terms will be used) in addition to the DSS encoding. The reverse is also
+possible: FrameSets originally read from a FITS-WCS encoded FITS header
+and which use a TAN projection can now be written out using the DSS
+encoding.
+
+\item The algorithm used by the FitsChan class to verify that a \htmlref{FrameSet}{FrameSet}
+conforms to the FITS-WCS model has been improved so that FrameSets
+including more complex mixtures of parallel and serial Mappings
+can be written out using the FITS-WCS encoding.
+
+\item The FitsChan class has been changed so that long strings included in
+the description of an \htmlref{Object}{Object} can be saved and restored without truncation
+when using the NATIVE encoding. Previously, very long \htmlref{Frame}{Frame} titles,
+mathematical expressions, \emph{etc.} were truncated if they exceeded the
+capacity of a single FITS header card. They are now split over several
+header cards so that they can be restored without truncation. Note, this
+facility is only available when using NATIVE encoding.
+
+\item The FitsChan class has a new attribute called \htmlref{Warnings}{Warnings} which
+can be used to select potentially dangerous conditions under which
+warnings should be issued. These conditions include (for instance)
+unsupported features within non-standard projections, missing keywords
+for which default values will be used, \emph{etc}.
+
+\item The \htmlref{WcsMap}{WcsMap} class has been changed to support the changes made to the
+FITS-WCS encoding in the FitsChan class:
+\begin{itemize}
+\item Projection parameters are now associated with a particular axis and
+are specified using a new set of attributes called PVj\_m. Here, ``j'' is
+the index of an axis of WcsMap, and ``m'' is the index of the projection
+parameter.
+\item The old attributes ProjP(0) to ProjP(9) are still available but are
+now deprecated in favour of the new PVj\_m attributes. They are interpreted
+as aliases for PV(axlat)\_0 to PV(axlat)\_9, where ``axlat'' is the index of
+the latitude axis.
+\item The GLS projection projection has been renamed as SFL, but the
+AST\_\_GLS type has been retained as an alias for AST\_\_SFL.
+\end{itemize}
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.6}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V1.5 and V1.6:
+
+\begin{enumerate}
+
+\item The C interface to several methods (\htmlref{astTranN}{astTranN}, \htmlref{astMark}{astMark} and
+\htmlref{astPolyCurve}{astPolyCurve}) have been changed to make them easier to call from C++.
+Parameters which previously had type ``double (*)[]'' have been changed
+to the simpler ``double *''. Using the old types may result in non-fatal
+compiler warnings, but should not change the behaviour of the methods.
+
+\item A bug has been fixed in the \htmlref{Plot}{Plot} class which could cause groups
+of tick marks to be skipped when using very small gaps.
+
+\item A bug has been fixed in the Plot class which could cause axes to be
+labeled outside the visible window, resulting in no axes being visible.
+
+\item The FITS-WCS encoding used by the \htmlref{FitsChan}{FitsChan} class now includes the
+WCSNAME keyword. When creating a \htmlref{FrameSet}{FrameSet} from FITS headers, the values of
+the WCSNAME keywords are now used as the \htmlref{Domain}{Domain} names for the corresponding
+Frames in the returned FrameSet. When writing a FrameSet to a FITS header
+the Domain names of each \htmlref{Frame}{Frame} are stored in WCSNAME keywords in the
+header.
+
+\item The FITS-WCS encoding used by the FitsChan class now attempts to
+retain the identification letter associated with multiple axis
+descriptions. When reading a FrameSet from a FITS header, the identification
+letter is stored in the \htmlref{Ident}{Ident} attribute for each Frame. When writing a
+FrameSet to a FITS header, the identification letter is read from the
+Ident attribute of each Frame. The letter to associate with each Frame
+can be changed by assigning a new value to the Frame's Ident attribute.
+
+\item The FITS-WCS, FITS-PC, FITS-IRAF and FITS-AIPS encodings used by the
+FitsChan class now create a \htmlref{SkyFrame}{SkyFrame} with the \htmlref{System}{System} attribute set to
+``Unknown'' if the CTYPE keywords in the supplied header refers to an
+unknown celestial coordinate system. Previously, a Frame was used instead
+of a SkyFrame.
+
+\item The FITS-WCS, FITS-PC, FITS-IRAF and FITS-AIPS encodings used by the
+FitsChan class no longer report an error if the FITS header contains no
+CTYPE keywords. It is assumed that a missing CTYPE keyword implies that
+the world coordinate system is linear and identically equal to
+``intermediate world coordinates''.
+
+\item The new value ``noctype'' is now recognized by the \htmlref{Warnings}{Warnings} attribute
+of the FitsChan class. This value causes warnings to be issued if CTYPE
+keywords are missing from foreign encodings.
+
+\item A new attribute called \htmlref{AllWarnings}{AllWarnings} has been added to the FitsChan
+class. This is a read-only, space separated list of all the known condition
+names which can be specified in the Warnings attribute.
+
+\item The FitsChan class now attempts to assigns a \htmlref{Title}{Title} to each Frame in
+a FrameSet read using a foreign encoding. The Title is based on the Domain
+name of the Frame. If the Frame has no Domain name, the default Title
+supplied by the Frame class is retained.
+
+\item The FitsChan class uses the comments associated with CTYPE
+keywords as axis labels when reading a foreign encoding. This behaviour
+has been modified so that the default labels provided by the Frame class
+are retained (instead of using the CTYPE comments) if any of the CTYPE
+comments are identical.
+
+\item A new ``interpolation'' scheme identified by the symbolic constant
+AST\_\_BLOCKAVE has been added to the AST\_RESAMPLE$<$X$>$ set of
+functions. The new scheme calculates each output pixel value by finding
+the mean of the input pixels in a box centred on the output pixel.
+
+\item The SkyFrame class can now be used to represent an arbitrary spherical
+coordinate system by setting its System attribute to ``Unknown''.
+
+\item The indices of the latitude and longitude axes of a SkyFrame can
+now be found using new read-only attributes \htmlref{LatAxis}{LatAxis} and \htmlref{LonAxis}{LonAxis}. The
+effects of any axis permutation is taken into account.
+
+\item A new attribute called Ident has been added to the \htmlref{Object}{Object} class.
+This serves the same purpose as the existing \htmlref{ID}{ID} attribute, but (unlike ID)
+its value is transferred to the new Object when a copy is made.
+
+\item A bug has been fixed which could prevent complex CmpFrames
+behaving correctly (for instance, resulting in the failure of attempts
+to find a \htmlref{Mapping}{Mapping} between a \htmlref{CmpFrame}{CmpFrame} and itself).
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.7}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V1.6 and V1.7:
+
+\begin{enumerate}
+
+\item The \htmlref{Frame}{Frame} class has a new method called
+\htmlref{astAngle}{astAngle}
+which returns the angle subtended by two points at a third point within a
+2 or 3 dimensional Frame.
+
+\item The Frame class has a new method called
+\htmlref{astOffset2}{astOffset2}
+which calculates a position which is offset away from a given starting
+point by a specified distance along a geodesic curve which passes
+through the starting point at a given position angle. It can only be used
+with 2-dimensional Frames.
+
+\item The Frame class has a new method called
+\htmlref{astAxDistance}{astAxDistance}
+which returns the increment between two supplied axis values. For
+axes belonging to SkyFrames, the returned value is normalized into
+the range $\pm\pi$.
+
+\item The Frame class has a new method called
+\htmlref{astAxOffset}{astAxOffset}
+which returns an axis value a given increment away from a specified axis
+value. For axes belonging to SkyFrames, the returned value is normalized into
+the range $\pm\pi$ (for latitude axes) or zero to $2\pi$ (for longitude
+axes).
+
+\item The \htmlref{Plot}{Plot} class has a new method called
+\htmlref{astGenCurve}{astGenCurve}
+which allows generalised user-defined curves to be drawn. The curve is
+defined by a user-supplied \htmlref{Mapping}{Mapping} which maps distance along the curve
+into the corresponding position in the current Frame of the Plot. The new
+method then maps these current Frame position into graphics coordinates,
+taking care of any non-linearities or discontinuities in the mapping.
+
+\item The Plot class has a new method called
+\htmlref{astGrfSet}{astGrfSet}
+which allows the underlying primitive graphics functions to be selected
+at run-time. Previously, the functions used by the Plot class to produce
+graphics could only be selected at link-time, using the options of the
+\htmlref{ast\_link}{ast\_link} command. The new Plot method allows an application to over-ride
+the functions established at link-time, by specifying alternative
+primitive graphics routines. In addition, the two new Plot methods
+\htmlref{astGrfPush}{astGrfPush} and \htmlref{astGrfPop}{astGrfPop}
+allow the current graphics routines to be saved and restore on a
+first-in-last-out stack, allowing temporary changes to be made to the set
+of registered graphics routines.
+
+\item The DrawAxes attribute of the Plot class can now be specified
+independantly for each axis, by appending the axis index to the
+end of the attribute name.
+
+\item A bug has been fixed in the Plot class which could result in axis
+labels being drawn on inappropriate edges of the plotting box when using
+``interior'' labelling.
+
+\item A bug has been fixed in the \htmlref{IntraMap}{IntraMap} class which could cause IntraMaps
+to be corrupted after transforming any points.
+
+\item Bugs have been fixed in the \htmlref{FitsChan}{FitsChan} class which could cause
+inappropriate ordering of headers within a FitsChan when writing or
+reading objects using NATIVE encodings.
+
+\item A bug has been fixed in the FitsChan class which could cause the
+celestial longitude of a pixel to be estimated incorrectly by 180 degrees
+if the reference point is at either the north or the south pole.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V1.8-2}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V1.7 and V1.8-2:
+
+\begin{enumerate}
+
+\item The \htmlref{SkyFrame}{SkyFrame} class has a new attribute called \htmlref{NegLon}{NegLon} which allows
+   longitude values to be displayed in the range $-\pi$ to $+\pi$, instead
+   of the usual range zero to $2.\pi$.
+
+\item Some new
+functions (\htmlref{astAngle}{astAngle}, \htmlref{astAxAngle}{astAxAngle}, \htmlref{astResolve}{astResolve}, \htmlref{astOffset2}{astOffset2}, \htmlref{astAxOffset}{astAxOffset},
+\htmlref{astAxDistance}{astAxDistance})
+have been added to the \htmlref{Frame}{Frame} class to allow navigation of the coordinate space
+to be performed without needing to know the underlying geometry
+of the co-ordinate system (for instance, whether it is Cartesian or
+spherical).
+
+Note, version 1.8-1 contained many of these facilities, but
+some have been changed in version 1.8-2. Particularly, positions angles
+are now referred to the second Frame axis for \emph{all} classes of Frames
+(including SkyFrames), and the
+astBear function has been replaced by astAxAngle.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.8-3}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V1.8-2 and V1.8-3:
+
+\begin{enumerate}
+
+\item A new method called \htmlref{astDecompose}{astDecompose} has been added to the \htmlref{Mapping}{Mapping} class
+which enables pointers to be obtained to the component parts of \htmlref{CmpMap}{CmpMap} and
+\htmlref{CmpFrame}{CmpFrame} objects.
+
+\item Functions within proj.c and wcstrig.c have been renamed to avoid name
+clashes with functions in more recent versions of Mark Calabretta's wcslib
+library.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.8-4}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V1.8-3 and V1.8-4:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class has a new attribute called \htmlref{DefB1950}{DefB1950} which can be
+used to select the default reference frame and equinox to be used if
+a FitsChan with foreign encoding contains no indication of the
+reference frame or equinox.
+
+\item A bug has been fixed in the FitsChan class which could prevent
+\htmlref{astWrite}{astWrite} from creating a set of FITS headers from an otherwise valid
+\htmlref{FrameSet}{FrameSet}, when when using FITS-AIPS encoding.
+
+\item A bug has been fixed in the FitsChan class which could cause
+\htmlref{astRead}{astRead} to mis-interpret the FITS CROTA keyword when using FITS-AIPS
+encoding.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.8-5}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V1.8-4 and V1.8-5:
+
+\begin{enumerate}
+
+\item The \htmlref{Plot}{Plot} class defines new graphical elements Axis1, Axis2,
+Grid1, Grid2, NumLabs1, NumLabs2, TextLab1, TextLab2, Ticks1 and Ticks2.
+These allow graphical attributes (colour, width, etc) to be set for each
+axis individually. Previously, graphical attributes could only be set for
+both axes together, using graphical elements Axes, \htmlref{Grid}{Grid}, NumLabs,
+TextLabs and Ticks.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V1.8-7}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V1.8-5 and V1.8-7:
+
+\begin{enumerate}
+
+\item A new attribute called \htmlref{CarLin}{CarLin} has been added to the \htmlref{FitsChan}{FitsChan} class
+which controls the way CAR projections are handled when reading a
+\htmlref{FrameSet}{FrameSet} from a non-native FITS header. Some FITS writers use a CAR
+projection to represent a simple linear transformation between pixel
+coordinates and celestial sky coordinates. This is not consistent with
+the definition of the CAR projection in the draft FITS-WCS standard, which
+requires the resultant \htmlref{Mapping}{Mapping} to include a 3D rotation from native
+spherical coordinates to celestial spherical coordinates, thus making the
+Mapping non-linear. Setting CarLin to 1 forces
+\htmlref{astRead}{astRead}
+to ignore the FITS-WCS standard and treat any CAR projections as simple
+linear Mappings from pixel coordinates to celestial coordinates.
+
+\item A bug has been fixed which could result in axis Format attributes
+set by the user being ignored under certain circumstances.
+
+\item A bug in the way tick marks positions are selected in the \htmlref{Plot}{Plot} class
+has been fixed. This bug could result in extra ticks marks being displayed at
+inappropriate positions. This bug manifested itself, for instance, if the
+Mapping represented by the Plot was a simple Cartesian to Polar Mapping.
+In this example, the bug caused tick marks to be drawn at negative radius
+values.
+
+\item A bug has been fixed which could prevent attribute settings from
+being read correctly by
+\htmlref{astSet}{astSet},
+etc., on certain platforms (MacOS, for instance).
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V1.8-8}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V1.8-7 and V1.8-8:
+
+\begin{enumerate}
+
+\item A bug has been fixed in the \htmlref{FitsChan}{FitsChan} class which could cause
+problems when creating a \htmlref{FrameSet}{FrameSet} from a FITS header containing WCS
+information stored in the form of Digitised Digitised Sky Survey (DSS)
+keywords. These problems only occurred for DSS fields in the southern
+hemisphere, and resulted in pixel positions being mapped to sky positions
+close to the corresponding \emph{northern} hemispshere field.
+
+\item A new method called
+\htmlref{astBoundingBox}{astBoundingBox}
+has been added to the \htmlref{Plot}{Plot} class. This method returns the bounding box of
+the previous graphical output produced by a Plot method.
+
+\item A new attribute called \htmlref{Invisible}{Invisible} has been added to the Plot class
+which suppresses the graphical output normally produced by Plot methods.
+All the calculations needed to produce the normal output are still
+performed however, and so the bounding box returned by the new
+astBoundingBox
+method is still usable.
+
+\item Bugs have been fixed related to the appearance of graphical output
+produced by the Plot class. These bugs were to do with the way in which
+graphical elements relating to a specific axis (e.g. \texttt{Colour(axis1)}, etc.)
+interacted with the corresponding generic element (e.g.
+\texttt{Colour(axes)}, etc.).
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V1.8-13}
+
+The following describes the most significant changes which occurred
+in the AST library between versions V1.8-8 and V1.8-13:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class has been modified so that LONPOLE keywords
+are only produced by \htmlref{astWrite}{astWrite} when necessary. For zenithal projections such as
+TAN, the LONPOLE keyword can always take its default value and so is
+not included in the FITS header produced by astWrite.
+Previously, the unnecessary production of a LONPOLE keyword could prevent
+FrameSets being written out using encodings which do not support the
+LONPOLE keyword (such as FITS-IRAF).
+
+\item The FitsChan class has been modified to retain leading and trailing
+spaces within COMMENT cards.
+
+\item The FitsChan class has been modified to only use CTYPE comments as
+axis labels if all non-celestial axes have unique non-blank comments
+(otherwise the CTYPE keyword values are used as labels).
+
+\item The FitsChan class has been modified so that it does not append a
+trailing ``Z'' character to the end of DATE-OBS keyword values.
+
+\item The FitsChan class has been modified to use latest list of FITS-WCS
+projections, as described in the FITS-WCS paper II, ``Representations of
+celestial coordinates in FITS'' (Calabretta \& Greisen, draft dated 23
+April 2002). Support has been retained for the polynomial correction
+terms which previous drafts have allowed to be associated with TAN
+projections.
+
+\item The \htmlref{WcsMap}{WcsMap} class has additional projection types of AST\_\_TPN
+(which implements a distorted TAN projection) and AST\_\_SZP. The AST\_\_TAN
+projection type now represents a simple TAN projection and has no
+associated projection parameters. In addition, the usage of projection
+parameters has been brought into line with the the FITS-WCS paper II.
+
+\item The WcsMap class has been modified so that a ``get'' operation on a
+projection parameter attribute will return the default value defined in the
+FITS-WCS paper II if no value has been set for the attribute. Previously, a
+value of AST\_\_BAD was returned in such a situation.
+
+\item The \htmlref{Frame}{Frame} class has new attributes \htmlref{Top(axis)}{Top(axis)} and \htmlref{Bottom(axis)}{Bottom(axis)} which
+allow a ``plottable range'' to be specified for each Frame axis. The grid
+produced by the \htmlref{astGrid}{astGrid} method will not extend beyond these limits.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V2.0}
+
+Note, \htmlref{Frame}{Frame} descriptions created using AST V2.0 will not be readable by
+applications linked with earlier versions of AST. This applies to Frame
+descriptions created using:
+\begin{itemize}
+\item the \htmlref{Channel}{Channel} class
+\item the \htmlref{FitsChan}{FitsChan} class if the NATIVE \htmlref{Encoding}{Encoding} is used
+\item the \htmlref{astShow}{astShow} function
+\end{itemize}
+
+Applications must be re-linked with AST V2.0 in order to be able to read
+Frame descriptions created by AST v2.0.
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V1.8-13 and V2.0 (the
+current version):
+
+\begin{enumerate}
+
+\item The default value for the \htmlref{Domain}{Domain} attribute provided by the \htmlref{CmpFrame}{CmpFrame}
+class has been changed from ``CMP'' to a string formed by concatenating
+the Domain attributes of the two component Frames, separated by a minus
+sign. If both component Domains are blank, then the old default of
+``CMP'' is retained for the CmpFrame Domain.
+
+\item The implementation of the
+\htmlref{astWrite}{astWrite} function
+within the FitsChan class has been modified. It will now attempt to
+produce a set of FITS header cards to describe a \htmlref{FrameSet}{FrameSet} even if the
+number of axes in the \htmlref{Current}{Current} Frames is greater than the number in the
+\htmlref{Base}{Base} Frame (that is, if there are more WCS axes than pixel axes). This
+has always been possible with NATIVE encoding, but has not previously
+been possible for foreign encodings. The WCSAXES keyword is used to store
+the number of WCS axes in the FITS header.
+
+\item Another change to the
+astWrite function
+within the FitsChan class is that the ordering of ``foreign'' axes
+(\emph{i.e.} CTYPE keywords) is now chosen to make the CD (or PC) matrix
+as diagonal as possible - any element of axis transposition is removed by
+this re-ordering as recommended in FITS-WCS paper I. Previously the
+ordering was determined by the order of the axes in the Current Frame of
+the supplied FrameSet. This change does not affect NATIVE encoding.
+
+\item Support for spectral coordinate systems has been introduced
+throught the addition of two new classes, \htmlref{SpecFrame}{SpecFrame} and \htmlref{SpecMap}{SpecMap}.
+The SpecFrame is a 1-dimensional Frame which can be used to describe
+positions within an electromagnetic spectrum in various systems
+(wavelength, frequency, various forms of velocity,~\emph{etc.}) and referred
+to various standards of rest (topocentric, geocentric, heliocentric
+LSRK,~\emph{etc.}). The SpecMap is a \htmlref{Mapping}{Mapping} which can transform spectral
+axis values between these various systems and standards of rest. Note,
+FitsChans which have a foreign encoding (\emph{i.e.} any encoding other
+than NATIVE) are not yet able to read or write these new classes.
+
+\item Facilities have been added to the Frame class which allow
+differences in axis units to be taken into account when finding a Mapping
+between two Frames. In previous versions of AST, the Unit attribute was a
+purely descriptive item intended only for human readers - changing the
+value of Unit made no difference to the behaviour of the Frame. As of
+version 2.0, the Unit attribute can influence the nature of the Mappings
+between Frames. For instance, if the
+astFindrame or \htmlref{astConvert}{astConvert}
+method is used to find the Mapping between an \htmlref{Axis}{Axis} with Unit set to ``m''
+and another Axis with Unit set to ``km'', then the method will return a
+\htmlref{ZoomMap}{ZoomMap} which introduces a scaling factor of 0.001 between the two axes.
+These facilities assume that units are specified following the rules
+included in FITS-WCS paper I (\emph{Representation of World
+Coordinates in FITS}, Greisen \& Calabretta).
+
+In order to minimise the risk of breaking existing software, the default
+behaviour for simple Frames is to ignore the Unit attribute (\emph{i.e.}
+to retain the previous behaviour). However, the new Frame method
+\htmlref{astSetActiveUnit}{astSetActiveUnit}
+may be used to ``activate'' (or deactivate) the new facilities within a
+specific Frame. Note, the new SpecFrame class is different to the simple
+Frame class in that the new facilities for handling units are always active
+within a SpecFrame.
+
+\item The \htmlref{System}{System} and \htmlref{Epoch}{Epoch} attributes fo the \htmlref{SkyFrame}{SkyFrame} class have been
+moved to the parent Frame class. This enables all sub-classes of Frame
+(such as the new SpecFrame class) to share these attributes, and to provide
+suitable options for each class.
+
+\item The Frame class has a new attribute called \htmlref{AlignSystem}{AlignSystem}, which allows
+control over the alignment process performed by the methods
+\htmlref{astFindFrame}{astFindFrame} and astConvert.
+
+
+\item The CmpFrame class has been modified so that attributes of a
+component Frame can be accessed without needing to extract the Frame first.
+To do this, append an axis index to the end of the attribute name. For
+instance, if a CmpFrame contains a SpecFrame and a SkyFrame (in that order),
+then the \htmlref{StdOfRest}{StdOfRest} attribute of the SpecFrame can be referred to as the
+``StdOfRest(1)'' attribute of the CmpFrame. Likewise, the \htmlref{Equinox}{Equinox} attribute
+of the SkyFrame can be accessed as the ``Equinox(2)'' (or equivalently
+``Equinox(3)'') attribute of the CmpFrame. The ``System(1)'' attribute of the
+CmpFrame will refer to the System attribute of the SpecFrame, whereas the
+``System(2)'' and ``System(3)'' attributes of the CmpFrame will refer to the
+System attribute of the SkyFrame (the ``System'' attribute without an axis
+specifier will refer to the System attribute of the CmpFrame as a whole,
+since System is an attribute of all Frames, and a CmpFrame is a Frame and
+so has its own System value which is independant of the System attributes
+of its component Frames).
+
+\item The algorithms used by the \htmlref{Plot}{Plot} class for determining when to omit
+overlapping axis labels, and the abbreviation of redundant leading fields
+within sexagesimal axis labels, have been improved to avoid some anomolous
+behaviour in previous versions.
+
+\item The curve drawing algorithm used by the Plot class has been
+modified to reduce the chance of it ``missing'' small curve sections,
+such as may be produced if a grid line cuts across the plot very close to
+a corner. Previously, these missed sections could sometimes result in
+axis labels being omitted.
+
+\item A new function
+(\htmlref{astVersion}{astVersion})
+has been added to return the version of the AST library in use.
+
+\item Bugs have been fixed in the Plot class which caused serious problems
+when plotting high precision data. These problems could range from the
+omission of some tick marks to complete failure to produce a plot.
+
+\end{enumerate}
+
+Programs which are statically linked will need to be re-linked in
+order to take advantage of these new facilities.
+
+
+\subsection{Changes Introduced in V3.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V2.0 and V3.0:
+
+\begin{enumerate}
+
+\item Many changes have been made in the \htmlref{FitsChan}{FitsChan} class in order to bring
+the FITS-WCS encoding into line with the current versions of the FITS-WCS
+papers (see
+\url{http://www.atnf.csiro.au/people/mcalabre/WCS/}):
+
+\begin{itemize}
+
+\item The rotation and scaling of the pixel axes may now be specified using
+either CD\emph{i\_j} keywords, or PC\emph{i\_j} and CDELTj keywords. A new attribute
+called \htmlref{CDMatrix}{CDMatrix} has been added to the FitsChan class to indicate which
+set of keywords should be used when writing a \htmlref{FrameSet}{FrameSet} to a FITS-WCS
+header.
+
+\item The FITS-WCS encoding now supports most of the conventions
+described in FITS-WCS paper III for the description of spectral
+coordinates. The exceptions are that the SSYSOBS keyword is not
+supported, and WCS stored in tabular form (as indicated by the ``-TAB''
+algorithm code) is not supported.
+
+
+\item User-specified fiducial points for WCS projections are now
+supported by FitsChans which use FITS-WCS encoding. This use keywords
+PVi\_0, PVi\_1 and PVi\_2 for the longitude axis.
+
+\item  When reading a FITS-WCS header, a FitsChan will now use keywords PVi\_3
+and PVi\_4 for the longitude axis (if present) in preference to any LONPOLE
+and LATPOLE keywords which may be present. When writing a FITS-WCS header,
+both forms are written out.
+
+\item The number of WCS axes is stored in the WCSAXES keyword if its value
+would be different to that of the NAXIS keyword.
+
+\item Helio-ecliptic coordinates are now supported by FitsChans which use
+FITS-WCS encoding. This uses CTYPE codes ``HLON'' and ``HLAT''. The
+resulting \htmlref{SkyFrame}{SkyFrame} will have a \htmlref{System}{System} value of ``HELIOECLIPTIC'', and all
+the usual facilities, such as conversion to other celestial systems, are
+available.
+
+\item The FITS-WCS encoding now supports most of the conventions
+described in FITS-WCS paper III for the description of spectral
+coordinates. The exceptions are that the SSYSOBS keyword is not
+supported, and WCS stored in tabular form (as indicated by the ``-TAB''
+algorithm code) is not supported.
+
+\item When reading a FITS-WCS header, a FitsChan will now ignore any
+distortion codes which are present in CTYPE keywords. Here, a ``distortion
+code'' is the final group of four characters in a CTYPE value of the
+form ``xxxx-yyy-zzz'', as described in FITS-WCS paper IV. The exception
+to this is that the ``-SIP'' distortion code (as used by the Spitzer
+Space Telescope project - see
+\url{http://ssc.spitzer.caltech.edu/postbcd/doc/shupeADASS.pdf}) is
+interpreted correctly and results in a \htmlref{PolyMap}{PolyMap} being used to represent
+the distortion in the resulting FrameSet. Note, ``-SIP'' distortion codes
+can only be read, not written. A FrameSet which uses a PolyMap will not
+in general be able to be written out to a FitsChan using any foreign
+encoding (although NATIVE encoding can of course be used).
+
+\item The \htmlref{Warnings}{Warnings} attribute of the FitsChan class now accepts values
+``BadVal'' (which gives warnings about conversion errors when reading
+FITS keyword values), ``Distortion'' (which gives warnings about
+unsupported distortion codes within CTYPE values), and ``BadMat'' (which
+gives a warning if the rotation/scaling matrix cannot be inverted).
+
+\item When writing a FrameSet to a FitsChan which uses a non-Native
+encoding, the comment associated with any card already in the FitsChan
+will be retained if the keyword value being written is the same as the
+keyword value already in the FitsChan.
+
+\item A FrameSet which uses the non-FITS projection type AST\_\_TPN (a TAN
+projection with polynomial distortion terms) can now be written to a
+FitsChan if the \htmlref{Encoding}{Encoding} attribute is set to FITS-WCS. The standard
+``-TAN'' code is used within the CTYPE values, and the distortion
+coefficients are encoded in keywords of the form `` QVi\_ma'', which are
+directly analogous to the standard ``PVi\_ma'' projection parameter keywords.
+Thus a FITS reader which does not recognise the QV keywords will still
+be able to read the header, but the distortion will be ignored.
+
+\item The default value for \htmlref{DefB1950}{DefB1950} attribute now depends on the value
+of the Encoding attribute.
+
+\item A new appendix has been added to SUN/210 and SUN/211 giving details
+of the implementation provided by the FitsChan class of the
+conventions contained in the first four FITS-WCS papers.
+\end{itemize}
+
+\item The SkyFrame class now supports two new coordinate systems ``ICRS''
+and ``HELIOECLIPTIC''. The default for the System attribute for SkyFrames
+has been changed from ``FK5'' to ``ICRS''.
+
+\item The
+\htmlref{astRate}{astRate}
+function has been added which allows an estimate to be made of the rate of
+change of a \htmlref{Mapping}{Mapping} output with respect to one of the Mapping inputs.
+
+\item All attribute names for Frames of any class may now include an optional
+axis specifier. This includes those attributes which describe a property
+of the whole \htmlref{Frame}{Frame}. For instance, the \htmlref{Domain}{Domain} attribute may now be
+specified as ``Domain(1)'' in addition to the simpler ``Domain''. In cases
+such as this, where the attribute describes a property of the whole
+Frame, axis specifiers will usually be ignored. The exception is that a
+\htmlref{CmpFrame}{CmpFrame} will use the presence of an axis specifier to indicate that the
+attribute name relates to the primary Frame containing the specified
+axis, rather than to the CmpFrame as a whole.
+
+\item A new subclass of Mapping, the PolyMap, has been added which
+performs a general N-dimensional polynomial mapping.
+
+\item A new subclass of Mapping, the \htmlref{GrismMap}{GrismMap}, has been added which
+models the spectral dispersion produced by a grating, prism or grism.
+
+\item A new subclass of Mapping, the \htmlref{ShiftMap}{ShiftMap}, has been added which adds
+constant values onto all coordinates (this is equivalent to a \htmlref{WinMap}{WinMap}
+with unit scaling on all axes).
+
+\item Minor bugs have been fixed within the \htmlref{Plot}{Plot} class to do with the choice
+and placement of numerical axis labels.
+
+\item The \htmlref{SphMap}{SphMap} class has a new attribute called \htmlref{PolarLong}{PolarLong} which gives the
+longitude value to be returned when a Cartesian position corresponding to
+either the north or south pole is transformed into spherical coordinates.
+
+\item The \htmlref{WcsMap}{WcsMap} class now assigns a longitude of zero to output
+celestial coordinates which have a latitude of plus or minus 90 degrees.
+
+\item The \htmlref{NatLat}{NatLat} and \htmlref{NatLon}{NatLon} attributes of the WcsMap class have been
+changed so that they now return the fixed native coordinates of the
+projection reference point, rather than the native coordinates of the
+user-defined fiducial point.
+
+\item Notation has been changed in both the WcsMap and FitsChan classes to
+reflect the convention used in the FITS-WCS papers that index ``i'' refers
+to a world coordinate axis, and index ``j'' refers to a pixel axis.
+
+\item Changes have been made to several Mapping classes in order to allow
+the
+\htmlref{astSimplify}{astSimplify}
+function to make simplifications in a \htmlref{CmpMap}{CmpMap} which previously were not
+possible.
+
+\item The \htmlref{SlaMap}{SlaMap} class has been extended by the addition of conversions
+between FK5 and ICRS coordinates, and between FK5 and helio-ecliptic coordinates.
+
+\item The \htmlref{SpecMap}{SpecMap} class has been changed to use the equation for the
+refractive index of air as given in the current version of FITS-WCS paper
+III. Also, the forward and inverse transformations between frequency and
+air-wavelength have been made more compatible by using an iterative
+procedure to calculate the inverse.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V3.1}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V3.0 and V3.1 (the
+current version):
+
+\begin{enumerate}
+\item Addition of a new class called \htmlref{XmlChan}{XmlChan} - a \htmlref{Channel}{Channel} which
+reads and writes AST objects in the form of XML.
+\item A bug has been fixed in the \htmlref{Plot}{Plot} class which could cause incorrect
+graphical attributes to be used for various parts of the plot if either
+axis has no tick marks (i.e. if both major and minor tick marks have zero
+length).
+\end{enumerate}
+
+Programs which are statically linked will need to be re-linked in
+order to take advantage of these new facilities.
+
+
+\subsection{Changes Introduced in V3.2}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V3.1 and V3.2:
+
+\begin{enumerate}
+
+\item A new
+function \htmlref{astPutCards}{astPutCards}
+has been added to the \htmlref{FitsChan}{FitsChan} class. This allows multiple concatenated header
+cards to be stored in a FitsChan in a single call, providing an alternative to
+the existing
+astPutCards function.
+
+\item Some signficant changes have been made to the simplification of Mappings
+ which should resultin a greater degree of simplication taking place.Some
+ bugs have also been fixed which could result in an infinite loop being
+ entered when attempting to simplify certain Mappings.
+
+\item The FitsChan class now translates the spectral algorithm codes
+``-WAV'', ``-FRQ'' and ``-VEL'' (specified in early drafts of paper III) to
+the corresponding ``-X2P'' form when reading a spectral axis description
+from a set of FITS header cards.
+
+\item A bug has been fixed in the FitsChan class which could cause
+keywords associated with alternate axis descriptions to be mis-interpreted.
+
+\item The \htmlref{Plot}{Plot} class now provides facilities for modifying the appearance
+of sub-strings within text strings such as axis labels, titles, \emph{etc},
+by producing super-scripts, sub-scripts, changing the font colour, size,
+\emph{etc}. See attribute \htmlref{Escape}{Escape}.
+
+\item The default value of the \htmlref{Tol}{Tol} attribute of the Plot class has been
+changed from 0.001 to 0.01. This should not usually cause any significant
+visible change to the plot, but should make the plotting faster. You may
+need to set a lower value for Tol if you are producing a particularly
+large plot.
+
+\item The algorithm for finding the default value for the Gap attribute
+has been changed. This attribute specifies the gap between major axis
+values in an annotated grid drawn by the Plot class. The change in
+algorithm may cause the default value to be different to previous versions
+in cirtain circumstances.
+
+\item Some bugs have been fixed in the Plot class which could cause the
+system to hang for a long time while drawing certain all-sky grids
+(notable some of the FITS Quad-cube projections).
+
+\item The \htmlref{SkyAxis}{SkyAxis} class has extended the Format attribute by the addition
+of the ``g'' option. this option is similar to the older ``l'' option in that
+it results in characters (``h'', ``m'', ``s'', \emph{etc}) being used as
+delimiters between the sexagesimal fields of the celestial position. The
+difference is that the ``g'' option includes graphics escape sequences
+in the returned formatted string which result in the field delimiter
+characters being drawn as super-scripts when plotted as numerical axis values
+by a Plot.
+
+\item The Plot class has been extended to include facilities for producing
+logarithmic axes. See attributes LogPlot, LogTicks, LogGap and LogLabel.
+
+\item New functions astGCap and astGScales have been added to the interface
+defined by file \verb+grf.h+. The \htmlref{ast\_link}{ast\_link} command has been modified so
+that the \verb+-mygrf+ switch loads dummy versions of the new grf
+functions. This means that applications should continue to build without
+any change. However, the facilities for interpreting escape sequences
+within strings drawn by the Plot class will not be available unless the
+new grf functions are implemented. If you choose to implement them, you
+should modify your linking procedure to use the \verb+-grf+ switch in
+place of the older \verb+-mygrf+ switch. See the description of the ast\_link
+command for details of the new switches. Also note that the astGQch
+function, whilst included in verb+grf.h+ in pervious versions of AST, was
+not actually called. As of this version of AST, calls are made to the
+astGQch function, and so any bugs in the implementation of astGQch may
+cause spurious behaviour when plotting text strings.
+
+\item A new 'static' method called \htmlref{astEscapes}{astEscapes} has been added which is used
+to control and enquire whether astGetC and \htmlref{astFormat}{astFormat} will strip any graphical
+escape sequences which may be present out of the returned value.
+
+\item New attribute \htmlref{XmlPrefix}{XmlPrefix} has been added to the \htmlref{XmlChan}{XmlChan} class. It
+allows XML written by the XmlChan class to include an explicit namespace
+prefix on each element.
+
+\item New attribute \htmlref{XmlFormat}{XmlFormat} has been added to the XmlChan class. It
+specifies the format in which AST objects should be written.
+
+\item A new class of \htmlref{Mapping}{Mapping}, the \htmlref{TranMap}{TranMap}, has been introduced. A TranMap
+takes its forward transformation from an existing Mapping, and its inverse
+transformation from another existing Mapping.
+
+\item A bug has been fixed in \htmlref{WcsMap}{WcsMap} which caused error reports to
+include erroneous axis numbers when referring to missing parameter values.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V3.3}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V3.2 and V3.3:
+
+\begin{enumerate}
+
+\item Options have been added to the \htmlref{SkyFrame}{SkyFrame} class which allows the
+origin
+of celestial coordinates to be moved to any specified point. See the new
+attributes SkyRef, \htmlref{SkyRefIs}{SkyRefIs}, SkyRefP and \htmlref{AlignOffset}{AlignOffset}.
+
+\item An option has been added to the \htmlref{FitsChan}{FitsChan} class which allows extra
+Frames representing cartesian projection plane coordinates (``intermediate
+world coordinates'' in the parlance of FITS-WCS) to be created when
+reading
+WCS information from a foreign FITS header. This option is controlled by
+a new attribute called \htmlref{Iwc}{Iwc}.
+
+\item The FitsChan class which been modified to interpret FITS-WCS CAR
+projection headers correctly if the longitude reference pixel (CRPIX) is
+very large.
+
+\item The  FITS-AIPS++ encoding in the FitsChan class now recognised
+spectral axes if they conform to the AIPS convention in which the
+spectral axis is descirbed by a CTYPE keyword od the form "AAAA-BBB"
+where ``AAAA'' is one of FREQ, VELO or FELO, and ``BBB'' is one of LSR, LSD,
+HEL or OBS. Such spectral axes can be both read and written.
+
+\item The FitsChan class now has a FITS-AIPS++ encoding which represents
+WCS information using FITS header cards recognised by the AIPS++ project.
+Support for spectral axes is identical to the FITS-AIPS encoding.
+
+\item The organisation of the AST distribution and the commands for
+building it have been changed. Whereas AST used to be built and installed
+with \verb+./mk build; ./mk install+, it now builds using the more standard
+idiom \verb+./configure; make; make install+. The installation location is
+controlled by the \verb+--prefix+ argument to ./configure (as is usual
+for other packages which use this scheme).  Note that the INSTALL environment
+variable now has a \emph{different} meaning to that which it had
+before, and it should generally be \emph{unset}. Also, there is no need to
+set the SYSTEM variable.
+
+\item Shared libraries are now installed in the same directory as the
+static libraries. In addition, links to sharable libraries are installed
+with names which include version information, and ``libtool libraries''
+are also installed (see
+\url{http://www.gnu.org/software/libtool/manual.html}).
+
+\item The \verb+ast_dev+ script has been removed. Instead, the location of
+the AST include files should be specified using the -I option when
+compiling.
+
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V3.4}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V3.3 and V3.4:
+
+\begin{enumerate}
+
+\item The \htmlref{Mapping}{Mapping} class has a new method
+(\htmlref{astLinearApprox}{astLinearApprox})
+which calculates the co-efficients of a linear approximation to a Mapping.
+
+\item The Format attribute for simple Frames and SkyFrames has been extended.
+It has always been possible, in both classes, to specify a precision by
+including a dot in the Format value followed by an integer (\emph{e.g.}
+``\verb+dms.1+'' for a \htmlref{SkyFrame}{SkyFrame}, or ``\verb+%.10g+'' for a simple \htmlref{Frame}{Frame}).
+The precision can now also be specified using an asterisk in place of the
+integer (\emph{e.g.} ``\verb+dms.*+'' or ``\verb+%.*g+''). This causes the
+precision to be derived on the basis of the Digits attribute value.
+
+\item The \htmlref{Plot}{Plot} class has been changed so that the default value used for the
+Digits attribute is chosen to be the smallest value which results in no
+pair of adjacent labels being identical. For instance, if an annotated
+grid is being drawn describing a SkyFrame, and the Format(1) value is set
+to ``\verb+hms.*g+'' (the ``g'' causes field delimiters to be drawn as
+superscripts), and the Digits(1) value is unset, then the seconds field
+will have a number of decimal places which results in no pair of labels
+being identical.
+
+\item Addition of a new class classed \htmlref{DSBSpecFrame}{DSBSpecFrame}. This is a
+sub-class of \htmlref{SpecFrame}{SpecFrame} which can be used to describe spectral axes
+associated with dual sideband spectral data.
+
+\item The \htmlref{FitsChan}{FitsChan} class will now read headers which use the old ``-GLS''
+projection code, converting them to the corresponding modern ``-SFL'' code,
+provided that the celestial axes are not rotated.
+
+\item The FitsChan class has a new \htmlref{Encoding}{Encoding}, ``FITS-CLASS'', which allows
+the reading and writing of FITS headers using the conventions of the CLASS
+package - see
+\url{http://www.iram.fr/IRAMFR/GILDAS/doc/html/class-html/class.html}).
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V3.5}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V3.4 and V3.5:
+
+\begin{enumerate}
+
+\item AST now provides facilities for representing regions of various
+shapes within a coordinate system. The \htmlref{Region}{Region} class provides general
+facilities which are independent of the specific shape of region being
+used. Various sub-classes of Region are also now available which provide
+means of creating Regions of specific shape. Facilities provided by the
+Region class include testing points to see if they are inside the
+Region, testing two Regions for overlap, transforming Regions from one
+coordinate system to another \emph{etc}.
+
+\item A new class of 1-dimensional \htmlref{Frame}{Frame} called \htmlref{FluxFrame}{FluxFrame} has been added which
+can be used to describe various systems for describing ovserved value at a
+single fixed spectral position.
+
+\item A new class of 2-dimensional Frame called \htmlref{SpecFluxFrame}{SpecFluxFrame} has been added which
+can be used to describe a 2-d frame spanned by a spectral position axis
+and and an observed value axis.
+
+\item A new class of \htmlref{Mapping}{Mapping} called \htmlref{RateMap}{RateMap} has been added. A RateMap encapsulates
+a previously created Mapping. The inputs of the RateMap correspond to the
+inputs of the encapsulated Mapping. All RateMaps have just a single
+output which correspond to the rate of change of a specified output of
+the encapsulated Mapping with respect to a specified input.
+
+\item The \htmlref{SkyFrame}{SkyFrame} class now supports a value of ``J2000'' for \htmlref{System}{System}.
+This system is an equatorial system based on the mean dynamical equator and
+equinox at J2000, and differs slightly from an FK5(J2000) system.
+
+\item A new class called \htmlref{KeyMap}{KeyMap} has been added. A KeyMap can be used to
+store a collection of vector or scalar values or Objects, indexed by a
+character string rather than an integer.
+
+\item The parameter list for the
+\htmlref{astRate}{astRate}
+method of the Mapping class has been modified. It no longer returns a second
+derivative estimate. Existing code which uses this method will need to be
+changed.
+
+\item Methods
+(astSetFits<X>)
+have been added to the \htmlref{FitsChan}{FitsChan} class to allow values for named
+keywords to be changed or added.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V3.6}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V3.5 and V3.6:
+
+\begin{enumerate}
+
+\item If the Format attribute associated with an axis of a \htmlref{SkyFrame}{SkyFrame}
+starts with a percent character (``\verb+%+''), then axis values are
+now formatted and unformatted as a decimal radians value, using the
+Format syntax of a simple \htmlref{Frame}{Frame}.
+
+\item The \htmlref{Plot}{Plot} class has a new attribute called \htmlref{Clip}{Clip} which controls the
+clipping performed by AST at the plot boundary.
+
+\item The keys used to label components of the \htmlref{PolyMap}{PolyMap} structure when a
+PolyMap is written out through a \htmlref{Channel}{Channel} have been changed. The new keys
+are shorter than the old keys and so can written succesfully to a \htmlref{FitsChan}{FitsChan}.
+The new PolyMap class always writes new styles keys but can read either
+old or new style keys. Consequently, PolyMap dumps written by this
+version of AST cannot be read by older versions of AST.
+
+\item A mimimal cut down subset of the C version of SLALIB is now
+included with the AST distribution and built as part of building AST.
+This means that it is no longer necessary to have SLALIB installed
+separately at your site. The SLALIB code included with AST is distrubuted
+under the GPL. The default behaviour of the \htmlref{ast\_link}{ast\_link} script is now to
+link with this internal slalib subset. However, the ``-csla'' option can
+still be used to force linking with an external full C SLALIB library.
+A new option ``-fsla'' has been introduced which forces linking with the
+external full Fortran SLALIB library.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V3.7}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V3.6 and V3.7:
+
+\begin{enumerate}
+
+\item Support for time coordinate systems has been introduced
+throught the addition of two new classes, \htmlref{TimeFrame}{TimeFrame} and \htmlref{TimeMap}{TimeMap}.
+The TimeFrame is a 1-dimensional \htmlref{Frame}{Frame} which can be used to describe
+moments in time (either absolute or relative) in various systems (MJD,
+Julian \htmlref{Epoch}{Epoch}, \emph{etc.}) and referred to various time scales (TAI, UTC,
+UT1, GMST, \emph{etc}). The TimeMap is a \htmlref{Mapping}{Mapping} which can transform time
+values between these various systems and time scales. Note,
+FitsChans which have a foreign encoding (\emph{i.e.} any encoding other
+than NATIVE) are not able to read or write these new classes.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V4.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V3.7 and V4.0:
+
+\begin{enumerate}
+
+\item Experimental support for reading IVOA Space-Time-Coordinates (STC-X)
+descriptions using the \htmlref{XmlChan}{XmlChan} class has been added. Support is included
+for a subset of V1.20 of the draft STC specification.
+
+\item A new set of methods (AST\_REBIN<X>/astRebin<X>) has been added to
+the \htmlref{Mapping}{Mapping} class. These are flux-conserving alternatives to the existing
+AST\_RESAMPLE<X>/astResample<X> methods.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V4.1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V4.0 and V4.1:
+
+\begin{enumerate}
+
+\item A new control flag has been added to the AST\_RESAMPLE<X>/astResample<X>
+functions which produces approximate flux conservation.
+
+\item New constants AST\_\_SOMB and AST\_\_SOMBCOS have been added to
+ast.h. These specify kernels for astResample and astRebin
+based on the ``Sombrero'' function ( $2*J1(x)/x$ where $J1(x)$ is the
+first order Bessel function of the first kind).
+
+\item The \htmlref{SkyFrame}{SkyFrame} class now supports a \htmlref{System}{System} value of AZEL corresponding
+to horizon (azimuth/elevation) coordinates.
+
+\item The \htmlref{FitsChan}{FitsChan} class allows the non-standard strings ``AZ--'' and
+``EL--'' to be used as axis types in FITS-WCS CTYPE keyword values.
+
+\item The \htmlref{Frame}{Frame} class now has attributes \htmlref{ObsLon}{ObsLon} and \htmlref{ObsLat}{ObsLat} to specify
+the geodetic longitude and latitude of the observer.
+
+\item The ClockLon and ClockLat attributes have been removed from the
+\htmlref{TimeFrame}{TimeFrame} class. Likewise, the GeoLon and GeoLat attributes have been
+removed from the \htmlref{SpecFrame}{SpecFrame} class. Both classes now use the ObsLon and
+ObsLat attributes of the parent Frame class instead. However, the old
+attribute names can be used as synonyms for ObsLat and ObsLon. Also,
+dumps created using the old scheme can be read succesfully by AST V4.1
+and converted to the new form.
+
+\item A new
+function \htmlref{astMapSplit}{astMapSplit}
+has been added to the \htmlref{Mapping}{Mapping} class. This splits a Mapping into two component
+Mappings which, when combined in parallel, are equivalent to the original
+Mapping.
+
+\item The default value for the \htmlref{SkyRefIs}{SkyRefIs} attribute has been changed from
+``Origin'' to ``Ignored''. This means that if you want to use a SkyFrame
+to represent offsets from some origin position, you must now set the
+SkyRefIs attribute explicitly to either ``Pole'' or ``Origin'', in
+addition to assigning the required origin position to the SkyRef attribute.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V4.2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V4.1 and V4.2:
+
+\begin{enumerate}
+
+\item The \htmlref{SideBand}{SideBand} attribute of the \htmlref{DSBSpecFrame}{DSBSpecFrame} class can now take the
+option ``LO'' in addition to ``USB'' and ``LSB''. The new option causes the
+DSBSpecFrame to represent the offset from the local oscillator frequency,
+rather than either of the two sidebands.
+
+\item The \htmlref{FitsChan}{FitsChan} class has been changed so that it writes out a VELOSYS
+keyword when creating a FITS-WCS encoding (VELOSYS indicates the topocentric
+apparent velocity of the standard of rest). FitsChan also strips out VELOSYS
+keywords when reading a \htmlref{FrameSet}{FrameSet} from a FITS-WCS encoding.
+
+\item The FitsChan class has a new method called
+\htmlref{astRetainFits}{astRetainFits}
+that indicates that the current card in the FitsChan should not be
+stripped out of the FitsChan when an AST \htmlref{Object}{Object} is read from the FitsChan.
+Unless this method is used, all cards that were involved in the creation
+of the AST Object will be stripped from the FitsChan afte a read operation.
+
+\item A problem with unaligned memory access that could cause bus errors on
+Solaris has been fixed.
+
+\item A new read-only attribute called \htmlref{ObjSize}{ObjSize} has been added to the base
+Object \htmlref{Class}{Class}. This gives the number of bytes of memory occupied by the
+Object. Note, this is the size of the internal in-memory representation of
+the Object, not the size of the textual representation produced by
+writing the Object out through a \htmlref{Channel}{Channel}.
+
+\item A new function
+\htmlref{astTune}{astTune}
+has been added which can be used to get and set global AST tuning
+parameters. At the moment there are only two such parameter, both of
+which are concerned with memory management within AST.
+
+\item A new method called
+\htmlref{astTranGrid}{astTranGrid}
+has been added to the \htmlref{Mapping}{Mapping} class. This method creates a regular
+grid of points covering a rectangular region within the input space of a
+Mapping, and then transforms this set of points into the output space of the
+Mapping, using a piecewise-continuous linear approximation to the Mapping
+if appropriate in order to achive higher speed.
+
+\item A new subclass of Mapping has been added called \htmlref{SwitchMap}{SwitchMap}. A
+SwitchMap represents several alternate Mappings, each of which is used to
+transforms input positions within a different region of the input
+coordinate space.
+
+\item A new subclass of Mapping has been added called \htmlref{SelectorMap}{SelectorMap}. A
+SelectorMap tests each input position to see if it falls within one of
+several Regions. If it does, the index of the \htmlref{Region}{Region} containing the
+input position is returned as the Mapping output.
+
+\item The behaviour of the
+\htmlref{astConvert}{astConvert}
+method when trying to align a \htmlref{CmpFrame}{CmpFrame} with another \htmlref{Frame}{Frame} has been
+modified. If no conversion between positions in the Frame and CmpFrame
+can be found, an attempt is now made to find a conversion between the
+Frame and one of two component Frames contained within the CmpFrame. Thus
+is should now be possible to align a \htmlref{SkyFrame}{SkyFrame} with a CmpFrame containing a
+SkyFrame and a \htmlref{SpecFrame}{SpecFrame} (for instance). The returned Mapping produces bad
+values for the extra axes (i.e. for the SpecFrame axis in the above example).
+
+\item The ``\htmlref{\htmlref{ast\_link}{ast\_link}\_adam}{ast\_link\_adam}'' and ``ast\_link'' scripts now ignore the
+\verb+-fsla+ and \verb+-csla+ options, and always link against the
+minimal cut-down version of SLALIB distributed as part of AST.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V4.3}
+
+The following describes the most significant changes which occurred in the
+AST library between versions V4.2 and V4.3:
+
+\begin{enumerate}
+
+\item The
+astGetFitsS
+function now strips trailing white space from the returned string, if the
+original string contains 8 or fewer characters
+
+\item The \htmlref{SpecFrame}{SpecFrame} class has a new attribute called \htmlref{SourceSys}{SourceSys} that specified
+whether the \htmlref{SourceVel}{SourceVel} attribute (which specifies the rest frame of the
+source) should be accessed as an apparent radial velocity or a redshift.
+Note, any existing software that assumes that SourceVel always represents
+a velocity in km/s should be changed to allow for the possibility of
+SourceVel representing a redshift value.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V4.4}
+
+The following describes the most significant changes which occurred in
+the AST library between versions V4.3 and V4.4:
+
+\begin{enumerate}
+
+\item The
+\htmlref{astFindFrame}{astFindFrame}
+function can now be used to search a \htmlref{CmpFrame}{CmpFrame} for an instance of a more
+specialised class of \htmlref{Frame}{Frame} (\htmlref{SkyFrame}{SkyFrame}, \htmlref{TimeFrame}{TimeFrame}, \htmlref{SpecFrame}{SpecFrame}, \htmlref{DSBSpecFrame}{DSBSpecFrame}
+or \htmlref{FluxFrame}{FluxFrame}). That is, if an instance of one of these classes is used as
+the ``template'' when calling
+astFindFrame,
+and the ``target'' being searched is a CmpFrame (or a \htmlref{FrameSet}{FrameSet} in which the
+current Frame is a CmpFrame), then the component Frames within the CmpFrame
+will be searched for an instance of the supplied template Frame, and, if
+found, a suitable \htmlref{Mapping}{Mapping} (which will include a \htmlref{PermMap}{PermMap} to select the
+required axes from the CmpFrame) will be returned by
+astFindFrame.
+Note, for this to work, the \htmlref{MaxAxes}{MaxAxes} and \htmlref{MinAxes}{MinAxes} attributes of the template
+Frame must be set so that they cover a range that includes the number of axes
+in the target CmpFrame.
+
+\item The SkyFrame, SpecFrame, DSBSpecFrame, TimeFrame and FluxFrame classes
+now allow the MaxAxes and MinAxes attributes to be set freely to any value.
+In previous versions of AST, any attempt to change the value of MinAxes
+or MaxAxes was ignored, resulting in them always taking the default values.
+
+\item The DSBSpecFrame class has a new attribute called AlignSB that
+specifies whether or not to take account of the \htmlref{SideBand}{SideBand} attributes when
+aligning two DSBSpecFrames using
+\htmlref{astConvert}{astConvert}.
+
+\item The Frame class has a new attribute called \htmlref{Dut1}{Dut1} that can be used to
+store a value for the difference between the UT1 and UTC timescales at
+the epoch referred to by the Frame.
+
+\item The number of digits used to format the Frame attributes \htmlref{ObsLat}{ObsLat} and
+\htmlref{ObsLon}{ObsLon} has been increased.
+
+\item The use of the SkyFrame attribute \htmlref{AlignOffset}{AlignOffset} has been changed. This
+attribute is used to control how two SkyFrames are aligned by
+astConvert.
+If the template and target SkyFrames both have a non-zero value for
+AlignOffset, then alignment occurs between the offset coordinate systems
+(that is, a \htmlref{UnitMap}{UnitMap} will always be used to align the two SkyFrames).
+
+\item The \htmlref{Plot}{Plot} class has a new attribute called ForceExterior that can be
+used to force exterior (rather than interior) tick marks to be produced.
+By default, exterior ticks are only produced if this would result in
+more than 3 tick marks being drawn.
+
+\item The TimeFrame class now supports conversion between angle based
+timescales such as UT1 and atomic based timescales such as UTC.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V4.5}
+
+The following describes the most significant changes that
+occurred in the AST library between versions V4.4 and V4.5:
+
+\begin{enumerate}
+
+
+
+\item All FITS-CLASS headers are now created with a frequency axis. If the
+\htmlref{FrameSet}{FrameSet} supplied to
+\htmlref{astWrite}{astWrite}
+contains a velocity axis (or any other form
+of spectral axis) it will be converted to an equivalent frequency axis
+before being used to create the FITS-CLASS header.
+
+\item The value stored in the FITS-CLASS keyword ``VELO-LSR'' has been changed
+from the velocity of the source to the velocity of the reference channel.
+
+\item Addition of a new method call
+\htmlref{astPurgeWCS}{astPurgeWCS}
+to the \htmlref{FitsChan}{FitsChan}
+class. This method removes all WCS-related header cards from a FitsChan.
+
+\item The \htmlref{Plot}{Plot} class has a new attribute called GrfContext that can be used
+to comminicate context information between an application and any
+graphics functions registered with the Plot class via the
+\htmlref{astGrfSet}{astGrfSet} function.
+\item Functions registered with the Plot class using
+astGrfSet
+now take a new additional integer parameter, ``grfcon''. The Plot class
+sets this parameter to the value of the Plot's GrfContext attribute before
+calling the graphics function. NOTE, THIS CHANGE WILL REQUIRE EXISTING
+CODE THAT USES
+astGrfSet
+TO BE MODIFIED TO INCLUDE THE NEW PARAMETER.
+\item The
+astRebinSeq functions
+now have an extra parameter that is used to record the total number of input
+data values added into the output array. This is necessary to correct a
+flaw in the calculation of output variances based on the spread of input
+values. NOTE, THIS CHANGE WILL REQUIRE EXISTING CODE TO BE MODIFIED TO
+INCLUDE THE NEW PARAMETER (CALLED "NUSED").
+\item Support has been added for the FITS-WCS ``HPX'' (HEALPix) projection.
+\item A new flag ``AST\_\_VARWGT'' can be supplied to
+astRebinSeq.
+This causes the input data values to be weighted using the reciprocals of
+the input variances (if supplied).
+
+\item The \htmlref{Frame}{Frame} class has a new read-only attribute called NormUnit that
+returns the normalised value of the Unit attribute for an axis. Here,
+``normalisation'' means cancelling redundant units, etc. So for instance, a
+Unit value of ``s*(m/s)'' would result in a NormUnit value of ``m''.
+
+\item A new
+function \htmlref{astShowMesh}{astShowMesh}
+has been added to the \htmlref{Region}{Region} class. It displays a mesh of points covering
+the surface of a Region by writing out a table of axis values to standard
+output.
+
+\item The Plot class now honours the value of the LabelUp attribute even if
+numerical labels are placed around the edge of the Plot. Previously
+LabelUp was only used if the labels were drawn within the interior of
+the plot. The LabelUp attribute controls whether numerical labels are
+drawn horizontally or parallel to the axis they describe.
+
+\item A bug has been fixed that could segmentation violations when setting
+attribute values.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V4.6}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V4.5 and V4.6:
+
+\begin{enumerate}
+
+\item The \htmlref{TimeFrame}{TimeFrame} class now support Local Time as a time scale. The offset
+from UTC to Local Time is specified by a new TimeFrame attribute called
+\htmlref{LTOffset}{LTOffset}.
+
+\item A new class called \htmlref{Plot3D}{Plot3D} has been added. The Plot3D class allows
+the creation of 3-dimensional annotated coordinate grids.
+
+\item A correction for diurnal aberration is now included when
+converting between AZEL and other celestial coordinate systems. The
+correction is based on the value of the \htmlref{ObsLat}{ObsLat} \htmlref{Frame}{Frame} attribute (the
+geodetic latitude of the observer).
+
+\item A bug has been fixed which caused the DUT1 attribute to be ignored
+by the \htmlref{SkyFrame}{SkyFrame} class when finding conversions between AZEL and other
+celestial coordinate systems.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V4.6 and V5.0:
+
+\begin{enumerate}
+
+
+\item The AST library is now thread-safe (assuming that the POSIX pthreads
+library is available when AST is built). Many of the macros defined in
+the ast.h header file have changed. It is therefore necessary to
+re-compile all source code that includes ast.h.
+
+\item New methods \htmlref{astLock}{astLock} and \htmlref{astUnlock}{astUnlock} allow an AST \htmlref{Object}{Object} to be locked
+for exclusive use by a thread.
+
+\item The \htmlref{TimeFrame}{TimeFrame} class now support Local Time as a time scale. The offset
+from UTC to Local Time is specified by a new TimeFrame attribute called
+\htmlref{LTOffset}{LTOffset}.
+
+\item The \htmlref{Channel}{Channel} class has a new attribute called \htmlref{Strict}{Strict} which controls
+whether or not to report an error if unexpected data items are found
+within an AST Object description read from an external data source. Note,
+the default behaviour is now not to report such errors. This differs from
+previous versions of AST which always reported an error is unexpected
+input items were encountered.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.1}
+
+The following describes the most significant changes which occurred in the
+AST library between versions V5.0 and V5.1:
+
+\begin{enumerate}
+
+\item The \htmlref{astUnlock}{astUnlock} function now has an extra parameter that controls whether
+or not an error is reported if the \htmlref{Object}{Object} is currently locked by another
+thread.
+
+\item The \htmlref{Prism}{Prism} class has been modified so that any class of \htmlref{Region}{Region} can
+be used to define the extrusion axes. Previously, only a \htmlref{Box}{Box} or \htmlref{Interval}{Interval}
+could be used for this purpose.
+
+\item The values of the AST\_\_THREADSAFE macro (defined in ast.h) have
+been changed from ``yes'' and ``no'' to ``1'' and ``0''.
+
+\item Improvements have been made to the way that Prisms are simplified
+when
+\htmlref{astSimplify}{astSimplify}
+is called.  The changes mean that more types of Prism will now simplify
+into a simpler class of Region.
+
+\item The \htmlref{PointList}{PointList} class has a new method,
+astPoints,
+that copies the axis values from the PointList into a supplied array.
+
+\item The PointList class has a new (read-only) attribute, \htmlref{ListSize}{ListSize}, that
+gives the number of points stored in the PointList.
+
+\item The handling of warnings within different classes of \htmlref{Channel}{Channel} has
+been rationalised. The XmlStrict attribute and
+astXmlWarnings
+function have been removed. The same functionality is now available via
+the existing \htmlref{Strict}{Strict} attribute (which has had its remit widened), a new
+attribute called \htmlref{ReportLevel}{ReportLevel}, and the new
+\htmlref{astWarnings}{astWarnings}
+function. This new function can be used on any class of Channel. Teh
+\htmlref{FitsChan}{FitsChan} class retains its long standing ability to store warnings as
+header cards within the FitsChan, but it also now stores warnings in the
+parent Channel structure, from where they can be retrieved using the
+astWarnings
+function.
+
+\item A new function called
+astIntercept
+has been added to the \htmlref{Frame}{Frame} class. This function finds the point of
+intersection beteeen two geodesic curves.
+
+\item A bug in the type-checking of Objects passed as arguments to constructor
+functions has been fixed. This bug could lead to applications crashing or
+showing strange behaviour if an inappropriate class of Object was
+supplied as an argument to a constructor.
+
+\item The
+\htmlref{astPickAxes}{astPickAxes}
+function will now return a Region, if possible, when applied to a Region. If
+this is not possible, a Frame will be returned as before.
+
+\item The choice of default tick-mark for time axes has been improved, to avoid
+previous issues which could result in no suitable gap being found, or
+inappropriate tick marks when using formatted dates.
+
+\item A new function called
+\htmlref{astTestFits}{astTestFits}
+has been added to the FitsChan class. This function tests a FitsChan to
+see if it contains a defined value for specified FITS keyword.
+
+\item The AST\_\_UNDEF<X> parameters used to flag undefined FITS keyword values
+have been removed. Use the new
+astTestFits
+function instead.
+
+\item The astIsUndef<X> functions used to test FITS keyword values
+have been removed. Use the new astTestFits function instead.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.1 and V5.2:
+
+\begin{enumerate}
+
+\item A new method called
+\htmlref{astSetFitsCM}{astSetFitsCM}
+has been added to the \htmlref{FitsChan}{FitsChan} class. It stores a pure comment card in a
+FitsChan (that is, a card with no keyword name or equals sign).
+
+\item A new attribute called \htmlref{ObsAlt}{ObsAlt} has been added to the \htmlref{Frame}{Frame} class. It
+records the geodetic altitude of the observer, in metres. It defaults to
+zero. It is used when converting times to or from the TDB timescale, or
+converting spectral positions to or from the topocentric rest frame, or
+converting sky positions to or from horizon coordinates. The FitsChan
+class will include its effect when creating a set of values for the
+OBSGEO-X/Y/Z keywords, and will also assign a value to it when reading a
+set of OBSGEO-X/Y/Z keyword values from a FITS header.
+
+\item The \htmlref{TimeMap}{TimeMap} conversions ``TTTOTDB'' and ``TDBTOTT'', and the \htmlref{SpecMap}{SpecMap}
+conversions ``TPF2HL'' and ``HLF2TP'', now have an additional argument -
+the observer's geodetic altitude.
+
+\item The \htmlref{Polygon}{Polygon} class has been modified to make it consistent with the
+IVOA STC definition of a Polygon. Specifically, the inside of a polygon
+is now the area to the left of each edge as the vertices are traversed in
+an anti-clockwise manner, as seen from the inside of the celestial sphere.
+Previously, AST used the anti-clockwise convention, but viewed from the
+outside of the celestial sphere instead of the inside. Any Polygon saved
+using previous versions of AST will be identified and negated automatically
+when read by AST V5.2.
+
+\item A new class of \htmlref{Channel}{Channel}, called \htmlref{StcsChan}{StcsChan}, has been added that allows
+conversion of suitable AST Objects to and from IVOA STC-S format.
+
+\item A new method called
+\htmlref{astRemoveRegions}{astRemoveRegions}
+has been added to the \htmlref{Mapping}{Mapping} class. It searches a (possibly compound)
+Mapping (or Frame) for any instances of the AST \htmlref{Region}{Region} class, and either
+removes them, or replaces them with UnitMaps (or equivalent Frames). It
+can be used to remove the masking effects of Regions from a compound
+Mapping or Frame.
+
+\item A new method called
+\htmlref{astDownsize}{astDownsize}
+has been added to the Polygon class. It produces a new Polygon that
+contains a subset of the vertices in the supplied Polygon. The subset is
+chosen to retain the main features of the supplied Polygion, in so far
+as that is possible, within specified constraints.
+
+\item A new constructor called
+astOutline
+has been added to the Polygon class. Given a 2D data array, it identifies
+the boundary of a region within the array that holds pixels with
+specified values. It then creates a new Polygon to describe this boundary
+to a specified accuracy.
+
+\item A new set of methods, called
+astMapGetElem<X>
+has been added to the \htmlref{KeyMap}{KeyMap} class. They allow a single element of a vector
+valued entry to be returned.
+
+\item A new attribute called \htmlref{KeyError}{KeyError} has been added to the KeyMap \htmlref{Class}{Class}. It
+controls whether the
+astMapGet...
+family of functions report an error if an entry with the requested key does
+not exist in the KeyMap.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.3}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.2 and V5.3:
+
+\begin{enumerate}
+
+\item The details of how a \htmlref{Frame}{Frame} is aligned with another Frame by the
+\htmlref{astFindFrame}{astFindFrame} and \htmlref{astConvert}{astConvert}
+functions have been changed. The changes mean that a Frame can now be
+aligned with an instance of a sub-class of Frame, so long as the number
+of axes and the \htmlref{Domain}{Domain} values are consistent. For instance, a basic
+2-dimensional Frame with Domain ``SKY'' will now align succesfully with
+a \htmlref{SkyFrame}{SkyFrame}, conversion between the two Frames being achieved using a
+\htmlref{UnitMap}{UnitMap}.
+
+\item The arrays that supply input values for astMapPut1<X> are now
+declared ``const''.
+
+\item Added method
+\htmlref{astMatchAxes}{astMatchAxes}
+to the Frame class. This method allows corresponding axes within two
+Frames to be identified.
+
+\item The
+\htmlref{astAddFrame}{astAddFrame}
+method can now be used to append one or more axes to all Frames in a \htmlref{FrameSet}{FrameSet}.
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.3-1}
+
+The following describes the most significant changes which have
+occurred in the AST library between versions V5.3 and V5.3-1:
+
+\begin{enumerate}
+
+\item The utility functions provided by the AST memory management layer
+are now documented in an appendix.
+
+\item The \htmlref{KeyMap}{KeyMap} class now supports entries that have undefined values. A
+new method called
+\htmlref{astMapPutU}{astMapPutU}
+will store an entry with undefined value in a keymap. Methods that
+retrieve values from a KeyMap
+(astMapGet0<X>, etc.)
+ignore entries with undefined values when searching for an entry with a given
+key.
+
+\item The KeyMap class has a new method called
+\htmlref{astMapCopy}{astMapCopy}
+that copies entries from one KeyMap to another KeyMap.
+
+\item The KeyMap class has a new boolean attribute called \htmlref{MapLocked}{MapLocked}. If
+non-zero,
+an error is reported if an attempt is made to add any new entries
+to a KeyMap (the value associated with any old entry may still be changed
+without error). The default is
+zero.
+
+\item The \htmlref{Object}{Object} class has a new method called \htmlref{astHasAttribute}{astHasAttribute}/AST\_HASATTRIBUTE
+that returns a boolean value indicating if a specified Object has a named
+attribute.
+
+\item The \htmlref{SkyFrame}{SkyFrame} class has two new read-only boolean attributes called
+IsLatAxis and IsLonAxis that can be used to determine the nature of a
+specified SkyFrame axis.
+
+\item A bug has been fixed in the
+astRebin(Seq)
+methods that could cause flux to be lost from the edges of the supplied array.
+
+\item A bug has been fixed in the
+astRebin(Seq)
+methods that caused the first user supplied parameter to be interpreted as the
+full width of the spreading kernel, rather than the half-width.
+
+\item The \htmlref{StcsChan}{StcsChan} class now ignores case when reading STC-S phrases (except
+that units strings are still case sensitive).
+
+\item A new \htmlref{Mapping}{Mapping} method,
+\htmlref{astQuadApprox}{astQuadApprox},
+produces a quadratic least-squares fit to a 2D Mapping.
+
+\item A new Mapping method,
+\htmlref{astSkyOffsetMap}{astSkyOffsetMap},
+produces a Mapping from absolute SkyFrame coordinates to offset SkyFrame
+coordinates.
+
+\item The \htmlref{Channel}{Channel} class now has an \htmlref{Indent}{Indent} attribute that controls indentation
+in the text created by
+\htmlref{astWrite}{astWrite}.
+The StcsIndent and XmlIndent attributes have been removed.
+
+\item All classes of Channel now use the string ``<bad>'' to represent the
+floating point value AST\_\_BAD, rather than the literal formatted value
+(typically ``-1.79769313486232e+308'' ).
+
+\item The KeyMap class now uses the string ``<bad>'' to represent the
+floating point value AST\_\_BAD, rather than the literal formatted value
+(typically ``-1.79769313486232e+308'' ).
+
+\item The KeyMap class has a new method called
+astMapPutElem<X>
+that allows a value to be put into a single element of a vector entry in
+a KeyMap. The vector entry is extended automatically to hold the new
+element if required.
+
+\item The \htmlref{DSBSpecFrame}{DSBSpecFrame} class now reports an error if the local oscillator
+frequency is less than the absoliute value of the intermediate frequency.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V5.3-2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.3-1 and V5.3-2:
+
+\begin{enumerate}
+
+\item A bug has been fixed in the \htmlref{FitsChan}{FitsChan} class that could cause wavelength
+axes to be assigned the units ``m/s'' when reading WCS information from a
+FITS header.
+
+\item The
+\htmlref{astSet}{astSet} function
+now allows literal commas to be included in string attribute values. String
+attribute values that include a literal comma should be enclosed in quotation
+marks.
+
+\item A bug in FitsChan has been fixed that caused ``-SIN'' projection
+codes within FITS-WCS headers to be mis-interpreted, resulting in no
+\htmlref{FrameSet}{FrameSet} being read by \htmlref{astRead}{astRead}.
+
+\item The \htmlref{KeyMap}{KeyMap} class has a new attribute called ``\htmlref{SortBy}{SortBy}''. It controls
+the order in which keys are returned by the
+\htmlref{astMapKey}{astMapKey}
+function. Keys can be sorted alphabetically or by age, or left unsorted.
+
+\item Access to KeyMaps holding thousands of entries is now significantly
+faster.
+
+\item KeyMaps can now hold word (i.e.
+short integer)
+values.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V5.4-0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.3-2 and V5.4-0:
+
+\begin{enumerate}
+
+\item the \htmlref{FitsChan}{FitsChan} class now has an option to support reading and writing
+of FITS-WCS headers that use the -TAB algorithm described in FITS-WCS paper
+III. This option is controlled by a new FitsChan attribute called \htmlref{TabOK}{TabOK}.
+See the documentation for TabOK for more information.
+
+\item A new class called ``\htmlref{Table}{Table}'' has been added. A Table is a \htmlref{KeyMap}{KeyMap} in
+which each entry represents a cell in a two-dimensional table.
+
+\item A new class called ``\htmlref{FitsTable}{FitsTable}'' has been added. A FitsTable is a
+Table that has an associated FitsChan holding headers appropriate to a
+FITS binary table.
+
+\item KeyMaps can now hold byte values. These are held in variables
+of type
+"unsigned char".
+
+\item KeyMaps have a new attribute called \htmlref{KeyCase}{KeyCase} that can be set to zero to
+make the handling of keys case insensitive.
+
+\item a memory leak associated with the use of the
+astMapPutElem<X>
+functions has been fixed.
+
+\item A new method called
+\htmlref{astMapRename}{astMapRename}
+has been added to rename existing entry in a KeyMap.
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.5-0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.4-0 and V5.5-0:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} ``\htmlref{TabOK}{TabOK}'' attribute is now an integer value rather
+than a boolean value. If TabOK is set to a non-zero positive integer
+before invoking the
+\htmlref{astWrite}{astWrite}
+method, its value is used as the version number for any table that is
+created as a consequence of the write operation. This is the value stored
+in the PVi\_1a keyword in the IMAGE header, and the EXTVER keyword in the
+binary table header. In previous versions of AST, the value used for these
+headers could not be controlled and was fixed at 1. If TabOK is set to a
+negative or zero value, the -TAB algorithm will not be supported by
+either the
+astWrite or \htmlref{astRead}{astRead}
+methods.
+
+\end{enumerate}
+
+
+
+\subsection{Changes Introduced in V5.6-0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.5-0 and V5.6-0:
+
+\begin{enumerate}
+
+\item
+New functions \htmlref{astBBuf}{astBBuf} and \htmlref{astEBuf}{astEBuf}
+have been added to the \htmlref{Plot}{Plot} class. These control the buffering of graphical
+output produced by other Plot methods.
+
+\item New functions astGBBuf and astGEBuf have been added to the interface
+defined by file \verb+grf.h+. The \htmlref{ast\_link}{ast\_link} command has been modified so
+that the \verb+-grf_v3.2+ switch loads dummy versions of the new grf
+functions. This means that applications that use the  \verb+-grf_v3.2+
+switch should continue to build without any change. However, the new public
+functions astBBuf and astEBuf
+will report an error unless the new grf functions are implemented. If you
+choose to implement them, you should modify your linking procedure to
+use the \verb+-grf+ (or  \verb+-grf_v5.6+ ) switch in place of the older
+\verb+-grf_v3.2+ switch. See the description of the ast\_link command for
+details of these switches.
+
+\item New method
+\htmlref{astGetRegionMesh}{astGetRegionMesh}
+returns a set of positions covering the boundary, or volume, of a supplied
+\htmlref{Region}{Region}.
+
+\end{enumerate}
+
+
+\subsection{ChangesIntroduced in V5.6-1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.6-0 and V5.6-1:
+
+\begin{enumerate}
+
+\item Tables can now have any number of parameters describing the global
+properties of the \htmlref{Table}{Table}.
+
+\item Frames now interpret the unit string ``A'' as meaning ``Ampere''
+rather than ``Angstrom'', as specified by FITS-WCS paper I.
+
+\item A bug has been fixed in the
+\htmlref{astFindFrame}{astFindFrame}
+method that allowed a template \htmlref{Frame}{Frame} of a more specialised class to match
+a target frame of a less specialised class. For example, this bug would
+allow a template \htmlref{SkyFrame}{SkyFrame} to match a target Frame. This no longer
+happens.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.7-0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.6-1 and V5.7-0:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class support for the IRAF-specific ``TNX'' projection has
+been extended to include reading TNX headers that use a Chebyshev
+representation for the distortion polynomial.
+
+\item The FitsChan class support for the IRAF-specific ``ZPX'' projection has
+been extended to include reading ZPX headers that use simple or Chebyshev
+representation for the distortion polynomial.
+
+\item A bug has been fixed in the FitsChan class that caused headers
+including the Spitzer ``-SIP'' distortion code to be read incorrectly if no
+inverse polynomial was specified in the header.
+
+\item A new attribute called \htmlref{PolyTan}{PolyTan} has been added to the FitsChan class. It
+can be used to indicate that FITS headers that specify a TAN projection
+should be interpreted according to the ``distorted TAN'' convention
+included in an early draft of FITS-WCS paper II. Such headers are created
+by (for instance) the SCAMP tool (\url{http://www.astromatic.net/software/scamp}).
+
+\item The \htmlref{PolyMap}{PolyMap} class now provides a method called
+\htmlref{astPolyTran}{astPolyTran}
+that adds an inverse transformation to a PolyMap by sampling the forward
+transformation on a regular grid, and then fitting a polynomial function
+from the resulting output values to the grid of input values.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V5.7-1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.7-0 and V5.7-1:
+
+\begin{enumerate}
+
+\item - All classes of \htmlref{Channel}{Channel} can now read to and write from specified
+text files, without the need to provide source and sink functions when
+the Channel is created. The files to use are specified by the new
+attributes \htmlref{SourceFile}{SourceFile} and \htmlref{SinkFile}{SinkFile}.
+
+\item - The \htmlref{FitsChan}{FitsChan} class now ignores trailing spaces in character-valued WCS
+keywords when reading a \htmlref{FrameSet}{FrameSet} from a FITS header.
+
+\item - If the FitsChan \htmlref{astRead}{astRead} method reads a FITS header that uses the
+-SIP (Spitzer) distortion code within the CTYPE values, but which does
+not provide an inverse polynomial correction, the FitsChan class will now
+use the PolyTran method of the \htmlref{PolyMap}{PolyMap} class to create an estimate of the
+inverse polynomial correction.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V5.7-2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.7-1 and V5.7-2:
+
+\begin{enumerate}
+
+\item The \htmlref{Object}{Object} class has a new function \htmlref{astToString}{astToString} (C only), which creates
+an in-memory textual serialisation of a given AST Object. A corresponding
+new function called \htmlref{astFromString}{astFromString} re-creates the Object from its
+serialisation.
+
+\item The \htmlref{PolyMap}{PolyMap} class can now use an iterative Newton-Raphson method to
+evaluate the inverse the inverse transformation if no inverse
+transformation is defined when the PolyMap is created.
+
+\item The \htmlref{FitsChan}{FitsChan} class has a new method
+\htmlref{astWriteFits}{astWriteFits}
+which writes out all cards currently in the FitsChan to the associated
+external data sink (specified either by the \htmlref{SinkFile}{SinkFile} attribute or the
+sink function supplied when the FitsChan was created), and then empties
+the FitsChan.
+
+\item The FitsChan class has a new read-only attribute called ``\htmlref{Nkey}{Nkey}'', which
+holds the number of keywords for which values are held in a FitsChan.
+
+\item The FitsChan
+astGetFits<X>
+methods can now be used to returned the value of the current card.
+
+\item The FitsChan class has a new read-only attribute called ``\htmlref{CardType}{CardType}'', which
+holds the data type of the keyword value for the current card.
+
+\item The FitsChan class has a new method
+\htmlref{astReadFits}{astReadFits}
+which forces the FitsChan to reads cards from the associated external
+source and appends them to the end of the FitsChan.
+
+\item - If the FitsChan \htmlref{astRead}{astRead} method reads a FITS header that uses the
+-SIP (Spitzer) distortion code within the CTYPE values, but which does
+not provide an inverse polynomial correction, and for which the PolyTran
+method of the PolyMap class fails to create an accurate estimate of the
+inverse polynomial correction, then an iterative method will be used to
+evaluate the inverse correction for each point transformed.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V6.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V5.7-2 and V6.0:
+
+\begin{enumerate}
+
+\item This version of AST is the first that can be used with the Python
+AST wrapper module, starlink.Ast, available at \url{http://github.com/timj/starlink-pyast}.
+
+\item When reading a FITS-WCS header, the \htmlref{FitsChan}{FitsChan} class now recognises the
+non-standard ``TPV'' projection code within a CTYPE keyword value. This
+code is used by SCAMP (see www.astromatic.net/software/scamp) to
+represent a distorted TAN projection.
+
+\item The \htmlref{Plot}{Plot} class has been changed to remove visual anomalies (such as
+incorrectly rotated numerical axis labels) if the graphics coordinates have
+unequal scales on the X and Y axes.
+
+- The graphics escape sequences used to produce graphical sky axis labels
+can now be changed using the new
+function \htmlref{astTuneC}{astTuneC}.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V6.0-1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V6.0 and V6.0-1:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class now recognises the Spitzer ``-SIP'' distortion
+code within FITS headers that describe non-celestial axes, as well as
+celestial axes.
+
+\item A bug has been fixed that could cause inappropriate equinox values to
+be used when aligning SkyFrames if the \htmlref{AlignSystem}{AlignSystem} attribute is set.
+
+\item The versioning string for AST has changed from
+``$<major>.<minor>-<release>$'' to ``$<major>.<minor>.<release>$''.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.0.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V6.0-1 and V7.0.0:
+
+\begin{enumerate}
+
+\item Fundamental positional astronomy calculations are now performed
+using the IAU SOFA library where possible, and the Starlink PAL library \xref{SUN/268}{sun268}{}
+otherwise (the PAL library contains a subset of the Fortran Starlink SLALIB
+library re-written in C). Copies of these libraries are bundled with AST
+and so do not need to be obtained or built separately, although external
+copies of SOFA and PAL can be used if necessary by including the
+``\texttt{--with-external\_pal}'' option when configuring AST.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.0.1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.0 and V7.0.1:
+
+\begin{enumerate}
+
+\item The levmar and wcslib code distributed within AST is now stored in the
+main AST library (libast.so) rather than in separate libraries.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.0.2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.1 and V7.0.2:
+
+\begin{enumerate}
+
+\item The libast\_pal library is no longer built if the
+``--with-external\_pal'' option is used when AST is configured.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.0.3}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.2 and V7.0.3:
+
+\begin{enumerate}
+
+\item A bug has been fixed which could cause an incorrect axis to be used when
+accessing axis attributes within CmpFrames. This could happen if axes
+within the \htmlref{CmpFrame}{CmpFrame} have been permuted.
+
+\item A bug has been fixed in the \htmlref{SkyFrame}{SkyFrame} class that could cause the two
+values of the SkyRef and/or SkyRefP attributes to be reversed.
+
+\item Bugs have been fixed in the \htmlref{CmpRegion}{CmpRegion} class that should allow the border
+around a compound \htmlref{Region}{Region} to be plotted more quickly, and more accurately.
+Previously, component Regions nested deeply inside a CmpRegion may have
+been completely or partially ignored.
+
+\item A bug has been fixed in the \htmlref{Plot3D}{Plot3D} class that caused a segmentation
+violation if the MinTick attribute was set to zero.
+
+\item The astResampleX set of methods now includes astResampleK and
+astResampleUK that handles 64 bit integer data.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V7.0.4}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.3 and V7.0.4:
+
+
+\begin{enumerate}
+
+\item The previously private grf3d.h header file is now installed into
+prefix/include.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V7.0.5}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.4 and V7.0.5:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class can now read FITS headers that use the SAO
+convention for representing distorted TAN projections, based on the use
+of ``COi\_m'' keywords to hold the coefficients of the distortion polynomial.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V7.0.6}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.0.5 and V7.0.6:
+
+\begin{enumerate}
+
+\item A bug has been fixed in astRebinSeq<X> which could result in
+incorrect normalisation of the final binned data and variance values.
+
+\item When reading a \htmlref{FrameSet}{FrameSet} from a FITS-DSS header, the keywords CNPIX1
+and CNPIX2 now default to zero if absent. Previously an error was reported.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V7.1.0}
+
+The following describes the most significant changes which occurred in the
+AST library between versions V7.0.6 and V7.1.0:
+
+\begin{enumerate}
+
+\item IMPORTANT! The default behaviour of astRebinSeq is now NOT to conserve
+flux. To conserve flux, the AST\_\_CONSERVEFLUX flag should be supplied
+when calling
+astRebinSeq<X>.
+Without this flag, each output value is a weighted mean of the neighbouring
+input values.
+
+\item A new flag AST\_\_NONORM can be used with astRebinSeq<X> to indicate that
+normalisation of the output arrays is not required. In this case no
+weights array need be supplied.
+
+\item A bug has been fixed in
+\htmlref{astAddFrame}{astAddFrame} method
+that could result in the incorrect inversion of Mappings within the \htmlref{FrameSet}{FrameSet}
+when the AST\_\_ALLFRAMES flag is supplied for the
+"iframe" parameter.
+
+\item The
+\htmlref{astRate}{astRate} method
+has been re-written to make it faster and more reliable.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.1.1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.1.0 and V7.1.1:
+
+\begin{enumerate}
+
+\item When a \htmlref{FitsChan}{FitsChan} is used to write an ``offset'' \htmlref{SkyFrame}{SkyFrame} (see attribute
+\htmlref{SkyRefIs}{SkyRefIs}) to a FITS-WCS encoded header, two alternate axis descriptions
+are now created - one for the offset coordinates and one for the absolute
+coordinates. If such a header is subsequently read back into AST, the
+original offset SkyFrame is recreated.
+
+\item A bug has been fixed in FitsChan that caused inappropriate CTYPE values
+to be generated when writing a \htmlref{FrameSet}{FrameSet} to FITS-WCS headers if the
+current \htmlref{Frame}{Frame} describes generalised spherical coordinates (i.e. a
+SkyFrame with \htmlref{System}{System}=Unknown).
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.2.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.1.1 and V7.2.0:
+
+\begin{enumerate}
+
+\item A new method call
+\htmlref{astMapDefined}{astMapDefined}
+has been added to the \htmlref{KeyMap}{KeyMap} class. It checks if a gtiven key name has
+a defined value in a given KeyMap.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.3.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.2.0 and V7.3.0:
+
+\begin{enumerate}
+
+\item The interface for the astRebinSeq<X> family of functions has
+been changed in order to allow a greater number of pixels to be pasted
+into the output array. The "nused" parameter is now a pointer to a
+"int64\_t" variable, instead of an "int". APPLICATION CODE SHOULD BE
+CHANGED ACCORDINGLY TO AVOID SEGMENTATION FAULTS AND OTHER ERRATIC
+BEHAVIOUR.
+
+\item Added a new facility to the \htmlref{FrameSet}{FrameSet} class to allow each \htmlref{Frame}{Frame} to be
+associated with multiple Mappings, any one of which can be used to
+connect the Frame to the other Frames in the FrameSet. The choice of
+which \htmlref{Mapping}{Mapping} to use is controlled by the new ``\htmlref{Variant}{Variant}'' attribute of the
+FrameSet class.
+
+\item Mappings (but not Frames) that have a value set for their \htmlref{Ident}{Ident}
+attribute are now left unchanged by the
+c \htmlref{astSimplify}{astSimplify} function.
+f AST\_SIMPLIFY routine.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.3.1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.3.0 and V7.3.1:
+
+\begin{enumerate}
+
+\item Fix a bug that could cauise a segmentation violation when reading
+certain FITS headers that use a TNX projection.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.3.2}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.3.1 and V7.3.2:
+
+\begin{enumerate}
+
+\item Fix support for reading FITS header that use a GLS projection.
+Previously, an incorrect transformation was used for such projections if
+any CRVAL or CROTA value was non-zero.
+
+\item The \htmlref{KeyMap}{KeyMap} class has new sorting options ``KeyAgeUp'' and
+``KeyAgeDown'' that retain the position of an existing entry if its value
+is changed. See the \htmlref{SortBy}{SortBy} attribute.
+
+\item A bug has been fixed in the \htmlref{FitsChan}{FitsChan} class that caused CDELT keywords
+for sky axes to be treated as radians rather than degrees when reading a
+FITS header, if the corresponding CTYPE values included no projection code.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.3.3}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.3.2 and V7.3.3:
+
+\begin{enumerate}
+
+\item The \htmlref{FitsChan}{FitsChan} class has new attributes \htmlref{CardName}{CardName} and \htmlref{CardComm}{CardComm}, which hold
+the keyword name and comment of the current card.
+
+\item When using the FitsChan class to read FITS-WCS headers that include
+polynomial distortion in the SIP format, any inverse transformation specified
+in the header is now ignored and a new inverse is created to replace it based
+on the supplied forward transformation. Previously, an inverse was created
+only if the header did not include an inverse. The accuracy of the inverse
+transformation has also been improved, although it may now be slower to
+evaluate in some circumstances.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V7.3.4}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.3.3 and V7.3.4:
+
+\begin{enumerate}
+
+\item By default, the simplification of Polygons no longer checks that the
+edges are not bent by the simplification. A new attribute, \htmlref{SimpVertices}{SimpVertices},
+can be set to zero in order to re-instate this check.
+
+\item The \htmlref{Polygon}{Polygon} class has a new mathod,
+astConvex,
+that returns a Polygon representing the shortest polygon (i.e. convex
+hull) enclosing a specified set of pixel values within a supplied array.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V8.0.0}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V7.3.4 and V8.0.0:
+
+\begin{enumerate}
+
+\item AST is now distributed under the Lesser GPL licence.
+
+\item The \htmlref{PolyMap}{PolyMap} class now uses files copied from the C/C++ Minpack
+package (see \url{http://devernay.free.fr/hacks/cminpack/index.html}) to perform
+least squares fitting of N-dimensional polynomials.
+
+\item Use of the IAU SOFA library has been replaced by ERFA library, which is
+a re-badged copy of SOFA distributed under a less restrictive license. A
+copy of ERFA is included within AST.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V8.0.1}
+
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.0 and V8.0.1:
+
+\begin{enumerate}
+
+\item The \htmlref{Base}{Base} and \htmlref{Current}{Current} attributes of a \htmlref{FrameSet}{FrameSet} may now be set using the
+  \htmlref{Domain}{Domain} name or the index of the required \htmlref{Frame}{Frame}.
+\item The order of WCS axes within new FITS-WCS headers created by \htmlref{astWrite}{astWrite}
+  can now be controlled using a new attribute called \htmlref{FitsAxisOrder}{FitsAxisOrder}.
+\item Supported added for FITS XPH (polar HEALPIX) projection.
+\item The macro used to invoke the \htmlref{astAppendString}{astAppendString} utility function has
+  changed to allow printf-style converstions to be included in the
+  supplied text. Any code that uses this macro must be re-compiled.
+\item The astRebin and astRebinSeq family of functions now include support
+  for arrays with char (byte) and unsigned char (unsigned byte) data types.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V8.0.2}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.1 and V8.0.2:
+
+\begin{enumerate}
+\item For security reasons, the change introduced to \htmlref{astAppendString}{astAppendString} in
+  V8.0.1 has been moved to a new function called \htmlref{astAppendStringf}{astAppendStringf}, and
+  astAppendString itself has been reverted to its V8.0.0 version.
+  Any software that has been built against V8.0.1 will need to be
+  re-compiled and re-linked against V8.0.2.
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V8.0.3}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.2 and V8.0.3:
+
+\begin{enumerate}
+
+\item Methods
+astRebin, astRebinSeq, astResample and \htmlref{astTranGrid}{astTranGrid}
+now report an error if an array is specified that has more pixels than
+can be counted by a 32 bit integer.
+\item The hypertext documentation is now generated using Tex4HT rather
+than latex2html. The format of the hypertext docs has changed significantly.
+\item Another bug fix associated with reading CAR projections from
+FITS-WCS headers.
+\item Constructor options strings of the form ``\texttt{..., "\%s", text );}''
+can now be supplied. This avoids a security issue associated with the
+alternative form ``\texttt{..., text );}''.
+\end{enumerate}
+
+\subsection{Changes Introduced in V8.0.4}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.3 and V8.0.4:
+
+\begin{enumerate}
+
+\item The behaviour of the
+\htmlref{astAddFrame}{astAddFrame} method has been changed slightly. Previously, astAddFrame
+modified the \htmlref{FrameSet}{FrameSet} by storing references to the supplied \htmlref{Mapping}{Mapping} and
+\htmlref{Frame}{Frame} objects within the FrameSet. This meant that any subsequent changes
+to the current Frame of the modified FrameSet also affected the supplied
+Frame object. Now, deep copies of the Mapping and Frame objects (rather
+than references) are stored within the modified FrameSet. This means that
+subsequent changes to the modified FrameSet will now have no effect on
+the supplied Frame.
+
+\item The choice of default tick-mark gaps for time axes has been
+improved, to avoid a previous issue which could result in no suitable gap
+being found.
+
+- A new method called
+\htmlref{astRegionOutline}{astRegionOutline}
+has been added to the \htmlref{Plot}{Plot} class. It draws the outline of a supplied AST
+\htmlref{Region}{Region}.
+
+\item A bug has been fixed that could cause astSimplfy to enter an infinite loop.
+
+\item Some improvements have been made to the Mapping simplification process
+that allow more Mappings to be simplified.
+
+\item The Frame class has a new read-only attribute called InternalUnit,
+which gives the units used for the unformatted (i.e. floating-point) axis
+values used internally by application code. For most Frames, the
+InternalUnit value is just the same as the Unit value (i.e. formatted and
+unformatted axis values use the same units). However, the \htmlref{SkyFrame}{SkyFrame} class
+always returns ``\texttt{rad}'' for InternalUnit, regardless of the value of
+Unit, indicating that floating-point SkyFrame axis values are always in units
+of radians.
+
+\item The \htmlref{LutMap}{LutMap} class has a new attribute called \htmlref{LutEpsilon}{LutEpsilon}, which specifies
+the relative error of the values in the table. It is used to decide if
+the LutMap can be simplified to a straight line.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V8.0.5}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.4 and V8.0.5:
+
+\begin{enumerate}
+
+\item The \htmlref{SkyFrame}{SkyFrame} class has a new attribute called \htmlref{SkyTol}{SkyTol}, which specifies
+the smallest significant distance within the SkyFrame. It is used to
+decide if the \htmlref{Mapping}{Mapping} between two SkyFrames can be considered a unit
+transformation. The default value is 0.001 arc-seconds.
+
+\item A bug has been fixed in the \htmlref{FitsChan}{FitsChan} class that prevented illegal
+characters within FITS keyword names (i.e. characters not allowed by the
+FITS standard) being detected. This bug could under some circumstances
+cause a subsequent segmentation violation to occur.
+
+\item A ``BadKeyName'' warning is now issued by the FitsChan class if a FITS
+keyword name is encountered that contains any illegal characters. See
+attribute ``\htmlref{Warnings}{Warnings}'' and
+function ``\htmlref{astWarnings}{astWarnings}''.
+
+\end{enumerate}
+
+\subsection{Changes Introduced in V8.1.0}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.0.5 and V8.1.0:
+
+\begin{enumerate}
+
+\item The configure script has a new option ``--without-fortran'' that allows
+AST to be built in situations where no Fortran compiler is available. The
+resulting library has no Fortran interface and so cannot be used within
+Fortran applications. Also, the link scripts do not attempt to include the
+fortran runtime libraries.
+
+\end{enumerate}
+
+\subsection{\xlabel{changes}\xlabel{list_of_most_recent_changes}Changes
+Introduced in V8.2}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.1.0 and V8.2.0:
+
+\begin{enumerate}
+
+\item A new class of \htmlref{Mapping}{Mapping} called \htmlref{UnitNormMap}{UnitNormMap} has been added that converts
+a vector to a unit vector relative to a specified centre, plus length. A
+UnitNormMap has N inputs and N+1 outputs.The lower N output coordinates
+represent a unit vector parallel to the supplied input vector, and the
+(N+1)'th output coordinate is the length of the input vector.
+
+\item The restriction that Mappings are immutable has been extended to all
+Mapping classes. This means that attributes representing parameters of
+a Mapping's forward or inverse transformation cannot be changed after
+the Mapping has been created. In order to minimise the risk to existing
+software, this rule does not apply to Mappings that have not yet been
+included in other objects such as CmpMaps or FrameSets, or which have not
+yet been cloned. In other words, an error is reported if an attempt is
+made to change the nature of a Mapping's transformation, but only if the
+reference count of the Mapping is greater than one. The Mapping classes
+affected include: \htmlref{GrismMap}{GrismMap}, \htmlref{LutMap}{LutMap}, \htmlref{PcdMap}{PcdMap}, \htmlref{SphMap}{SphMap}, \htmlref{WcsMap}{WcsMap} and \htmlref{ZoomMap}{ZoomMap}.
+
+\end{enumerate}
+
+
+\subsection{Changes Introduced in V8.3}
+The following describes the most significant changes which
+occurred in the AST library between versions V8.2.0 and V8.3.0:
+
+\begin{enumerate}
+
+\item A new method called \htmlref{astAxNorm}{astAxNorm}
+has been added to the \htmlref{Frame}{Frame} class that normalises an array of axis
+values. When used with SkyFrames, it allows longitude values to be
+normalised into the shortest range.
+
+
+\item A bug has been fixed in the \htmlref{astGetRegionBounds}{astGetRegionBounds} method that could
+cause the wrong bounds to be returned for regions spanning a longitude =
+zero singularity.
+
+\end{enumerate}
+
+\subsection{\xlabel{changes}\xlabel{list_of_most_recent_changes}Changes
+Introduced in V8.4}
+The following describes the most significant changes which have
+occurred in the AST library between versions V8.3.0 and V8.4.0 (the
+current version):
+
+\begin{enumerate}
+
+\item The PAL library files included in the AST distribution have been updated
+to PAL version 0.9.7.
+
+\item Multiple identical NormMaps in series will now be simplified to a
+single \htmlref{NormMap}{NormMap}.
+
+\item A NormMap that encapsulates a basic \htmlref{Frame}{Frame} will now be simplified to a
+\htmlref{UnitMap}{UnitMap}.
+
+\item The \htmlref{astTimeAdd}{astTimeAdd}
+method of the \htmlref{TimeMap}{TimeMap} class now include an extra argument that gives the
+number of values supplied in the arguments array. Note, any existing code
+that uses this method will need to be changed.
+
+\item The \htmlref{astSlaAdd}{astSlaAdd}
+method of the \htmlref{SlaMap}{SlaMap} class now include an extra argument that gives the
+number of values supplied in the arguments array. Note, any existing code
+that uses this method will need to be changed.
+
+\item The \htmlref{astSpecAdd}{astSpecAdd}
+method of the \htmlref{SpecMap}{SpecMap} class now include an extra argument that gives the
+number of values supplied in the arguments array. Note, any existing code
+that uses this method will need to be changed.
+
+\item Multiple identical NormMaps in series will now be simplified to a
+single NormMap.
+
+\item A NormMap that encapsulates a basic Frame will now be simplified to a
+UnitMap.
+
+\item If the
+\htmlref{astMapRegion}{astMapRegion}
+method is used to map a \htmlref{Region}{Region} into a new Frame that has fewer axes than
+the original Region, and if the inverse transformation of the supplied
+\htmlref{Mapping}{Mapping} does not specify a value for the missing axes, then those axes
+are removed entirely from the Region. Previously they were retained, but
+automatically supplied with bad values. This affects the number of mesh
+points per axes for such Regions, and so affects the accuracy of overlap
+determination.
+
+\end{enumerate}
+
+Programs which are statically linked will need to be re-linked in
+order to take advantage of these new facilities.
+
+\end{document}
diff --git a/ast/sun211_figures/cmpframe.pdf b/ast/sun211_figures/cmpframe.pdf
new file mode 100644
index 0000000..63ca4e1
Binary files /dev/null and b/ast/sun211_figures/cmpframe.pdf differ
diff --git a/ast/sun211_figures/complex.pdf b/ast/sun211_figures/complex.pdf
new file mode 100644
index 0000000..8e5bbb9
Binary files /dev/null and b/ast/sun211_figures/complex.pdf differ
diff --git a/ast/sun211_figures/frames.pdf b/ast/sun211_figures/frames.pdf
new file mode 100644
index 0000000..dbf5070
Binary files /dev/null and b/ast/sun211_figures/frames.pdf differ
diff --git a/ast/sun211_figures/frameset.pdf b/ast/sun211_figures/frameset.pdf
new file mode 100644
index 0000000..697fe38
Binary files /dev/null and b/ast/sun211_figures/frameset.pdf differ
diff --git a/ast/sun211_figures/fronta.pdf b/ast/sun211_figures/fronta.pdf
new file mode 100644
index 0000000..3433a3d
Binary files /dev/null and b/ast/sun211_figures/fronta.pdf differ
diff --git a/ast/sun211_figures/fronta_bw.pdf b/ast/sun211_figures/fronta_bw.pdf
new file mode 100644
index 0000000..54cf906
Binary files /dev/null and b/ast/sun211_figures/fronta_bw.pdf differ
diff --git a/ast/sun211_figures/frontb.pdf b/ast/sun211_figures/frontb.pdf
new file mode 100644
index 0000000..16aeef3
Binary files /dev/null and b/ast/sun211_figures/frontb.pdf differ
diff --git a/ast/sun211_figures/frontb_bw.pdf b/ast/sun211_figures/frontb_bw.pdf
new file mode 100644
index 0000000..03df1c2
Binary files /dev/null and b/ast/sun211_figures/frontb_bw.pdf differ
diff --git a/ast/sun211_figures/frontc.pdf b/ast/sun211_figures/frontc.pdf
new file mode 100644
index 0000000..69ab837
Binary files /dev/null and b/ast/sun211_figures/frontc.pdf differ
diff --git a/ast/sun211_figures/frontc_bw.pdf b/ast/sun211_figures/frontc_bw.pdf
new file mode 100644
index 0000000..29fa5e1
Binary files /dev/null and b/ast/sun211_figures/frontc_bw.pdf differ
diff --git a/ast/sun211_figures/fsalign.pdf b/ast/sun211_figures/fsalign.pdf
new file mode 100644
index 0000000..e0b8c15
Binary files /dev/null and b/ast/sun211_figures/fsalign.pdf differ
diff --git a/ast/sun211_figures/fsconvert.pdf b/ast/sun211_figures/fsconvert.pdf
new file mode 100644
index 0000000..3950b3b
Binary files /dev/null and b/ast/sun211_figures/fsconvert.pdf differ
diff --git a/ast/sun211_figures/fsexample.pdf b/ast/sun211_figures/fsexample.pdf
new file mode 100644
index 0000000..097114d
Binary files /dev/null and b/ast/sun211_figures/fsexample.pdf differ
diff --git a/ast/sun211_figures/fsmerge.pdf b/ast/sun211_figures/fsmerge.pdf
new file mode 100644
index 0000000..5d6c198
Binary files /dev/null and b/ast/sun211_figures/fsmerge.pdf differ
diff --git a/ast/sun211_figures/fsremap.pdf b/ast/sun211_figures/fsremap.pdf
new file mode 100644
index 0000000..31772d3
Binary files /dev/null and b/ast/sun211_figures/fsremap.pdf differ
diff --git a/ast/sun211_figures/gridplot.pdf b/ast/sun211_figures/gridplot.pdf
new file mode 100644
index 0000000..c06b370
Binary files /dev/null and b/ast/sun211_figures/gridplot.pdf differ
diff --git a/ast/sun211_figures/gridplot_bw.pdf b/ast/sun211_figures/gridplot_bw.pdf
new file mode 100644
index 0000000..67353bc
Binary files /dev/null and b/ast/sun211_figures/gridplot_bw.pdf differ
diff --git a/ast/sun211_figures/mapping.pdf b/ast/sun211_figures/mapping.pdf
new file mode 100644
index 0000000..8228188
Binary files /dev/null and b/ast/sun211_figures/mapping.pdf differ
diff --git a/ast/sun211_figures/overgrid.pdf b/ast/sun211_figures/overgrid.pdf
new file mode 100644
index 0000000..f78a965
Binary files /dev/null and b/ast/sun211_figures/overgrid.pdf differ
diff --git a/ast/sun211_figures/overgrid_bw.pdf b/ast/sun211_figures/overgrid_bw.pdf
new file mode 100644
index 0000000..0d972d4
Binary files /dev/null and b/ast/sun211_figures/overgrid_bw.pdf differ
diff --git a/ast/sun211_figures/parallel.pdf b/ast/sun211_figures/parallel.pdf
new file mode 100644
index 0000000..18f9911
Binary files /dev/null and b/ast/sun211_figures/parallel.pdf differ
diff --git a/ast/sun211_figures/series.pdf b/ast/sun211_figures/series.pdf
new file mode 100644
index 0000000..11656da
Binary files /dev/null and b/ast/sun211_figures/series.pdf differ
diff --git a/ast/sun211_figures/simpexamp.pdf b/ast/sun211_figures/simpexamp.pdf
new file mode 100644
index 0000000..a685837
Binary files /dev/null and b/ast/sun211_figures/simpexamp.pdf differ
diff --git a/ast/switchmap.c b/ast/switchmap.c
new file mode 100644
index 0000000..23dec4b
--- /dev/null
+++ b/ast/switchmap.c
@@ -0,0 +1,2875 @@
+/*
+*class++
+*  Name:
+*     SwitchMap
+
+*  Purpose:
+*     A Mapping that encapsulates a set of alternate Mappings.
+
+*  Constructor Function:
+c     astSwitchMap
+f     AST_SWITCHMAP
+
+*  Description:
+*     A SwitchMap is a Mapping which represents a set of alternate
+*     Mappings, each of which is used to transform positions within a
+*     particular region of the input or output coordinate system of the
+*     SwitchMap.
+*
+*     A SwitchMap can encapsulate any number of Mappings, but they must
+*     all have the same number of inputs (Nin attribute value) and the
+*     same number of outputs (Nout attribute value). The SwitchMap itself
+*     inherits these same values for its Nin and Nout attributes. Each of
+*     these Mappings represents a "route" through the switch, and are
+*     referred to as "route" Mappings below. Each route Mapping transforms
+*     positions between the input and output coordinate space of the entire
+*     SwitchMap, but only one Mapping will be used to transform any given
+*     position. The selection of the appropriate route Mapping to use with
+*     any given input position is made by another Mapping, called the
+*     "selector" Mapping. Each SwitchMap encapsulates two selector
+*     Mappings in addition to its route Mappings; one for use with the
+*     SwitchMap's forward transformation (called the "forward selector
+*     Mapping"), and one for use with the SwitchMap's inverse transformation
+*     (called the "inverse selector Mapping"). The forward selector Mapping
+*     must have the same number of inputs as the route Mappings, but
+*     should have only one output. Likewise, the inverse selector Mapping
+*     must have the same number of outputs as the route Mappings, but
+*     should have only one input.
+*
+*     When the SwitchMap is used to transform a position in the forward
+*     direction (from input to output), each supplied input position is
+*     first transformed by the forward transformation of the forward selector
+*     Mapping. This produces a single output value for each input position
+*     referred to as the selector value. The nearest integer to the selector
+*     value is found, and is used to index the array of route Mappings (the
+*     first supplied route Mapping has index 1, the second route Mapping has
+*     index 2, etc). If the nearest integer to the selector value is less
+*     than 1 or greater than the number of route Mappings, then the SwitchMap
+*     output position is set to a value of AST__BAD on every axis. Otherwise,
+*     the forward transformation of the selected route Mapping is used to
+*     transform the supplied input position to produce the SwitchMap output
+*     position.
+*
+*     When the SwitchMap is used to transform a position in the inverse
+*     direction (from "output" to "input"), each supplied "output" position
+*     is first transformed by the inverse transformation of the inverse
+*     selector Mapping. This produces a selector value for each "output"
+*     position. Again, the nearest integer to the selector value is found,
+*     and is used to index the array of route Mappings. If this selector
+*     index value is within the bounds of the array of route Mappings, then
+*     the inverse transformation of the selected route Mapping is used to
+*     transform the supplied "output" position to produce the SwitchMap
+*     "input" position. If the selector index value is outside the bounds
+*     of the array of route Mappings, then the SwitchMap "input" position is
+*     set to a value of AST__BAD on every axis.
+*
+*     In practice, appropriate selector Mappings should be chosen to
+*     associate a different route Mapping with each region of coordinate
+*     space. Note that the SelectorMap class of Mapping is particularly
+*     appropriate for this purpose.
+*
+*     If a compound Mapping contains a SwitchMap in series with its own
+*     inverse, the combination of the two adjacent SwitchMaps will be
+*     replaced by a UnitMap when the compound Mapping is simplified using
+c     astSimplify.
+f     AST_SIMPLIFY.
+
+*  Inheritance:
+*     The SwitchMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The SwitchMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     The SwitchMap class does not define any new functions beyond those
+f     The SwitchMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     13-MAR-2006 (DSB):
+*        Original version.
+*     17-MAR-2006 (DSB):
+*        Guard against AST__BAD selector values.
+*     9-MAY-2006 (DSB):
+*        Check selector Mapping pointers are not NULL before calling
+*        astEqual in Equal.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS SwitchMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+#include "unitmap.h"             /* Unit Mappings */
+#include "channel.h"             /* I/O channels */
+#include "switchmap.h"           /* Interface definition for this class */
+#include "frame.h"               /* Frames */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(SwitchMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(SwitchMap,Class_Init)
+#define class_vtab astGLOBAL(SwitchMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstSwitchMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstSwitchMap *astSwitchMapId_( void *, void *, int, void **, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *RemoveRegions( AstMapping *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetObjSize( AstObject *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static AstMapping *GetSelector( AstSwitchMap *, int, int *, int * );
+static AstMapping *GetRoute( AstSwitchMap *, double, int *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+/* Member functions. */
+/* ================= */
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two SwitchMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     SwitchMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two SwitchMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a SwitchMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the SwitchMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *fsmap1;
+   AstMapping *fsmap2;
+   AstMapping *ismap1;
+   AstMapping *ismap2;
+   AstMapping *rmap1;
+   AstMapping *rmap2;
+   AstSwitchMap *that;
+   AstSwitchMap *this;
+   int fsinv1;
+   int fsinv2;
+   int isinv1;
+   int i;
+   int isinv2;
+   int nroute;
+   int result;
+   int rinv1;
+   int rinv2;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two SwitchMap structures. */
+   this = (AstSwitchMap *) this_object;
+   that = (AstSwitchMap *) that_object;
+
+/* Check the second object is a SwitchMap. We know the first is a
+   SwitchMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsASwitchMap( that ) ) {
+
+/* Check they have the same number of route mappings. */
+      nroute = this->nroute;
+      if( that->nroute == nroute ) {
+
+/* Get the forward selector Mappings from the two SwitchMaps. */
+         fsmap1 = GetSelector( this, 1, &fsinv1, status );
+         fsmap2 = GetSelector( that, 1, &fsinv2, status );
+
+/* Are they equal? */
+         if( ( !fsmap1 && !fsmap2 ) ||
+             ( fsmap1 && fsmap2 && astEqual( fsmap1, fsmap2 ) ) ) {
+
+/* Get the inverse selector Mappings from the two SwitchMaps. */
+            ismap1 = GetSelector( this, 0, &isinv1, status );
+            ismap2 = GetSelector( that, 0, &isinv2, status );
+
+/* Are they equal? */
+            if( ( !ismap1 && !ismap2 ) ||
+                ( ismap1 && ismap2 && astEqual( ismap1, ismap2 ) ) ) {
+
+/* Loop over the route mappings, breaking as soon as two unequal route
+   Mappings are found. Re-instate the original values for the route
+   Mapping Invert flag after testing the route Mappings for equality. */
+               result = 1;
+               for( i = 0; result && i < nroute; i++ ) {
+                  rmap1 = GetRoute( this, (double) ( i + 1 ), &rinv1, status );
+                  rmap2 = GetRoute( that, (double) ( i + 1 ), &rinv2, status );
+                  if( !astEqual( rmap1, rmap2 ) ) result = 0;
+                  astSetInvert( rmap2, rinv2 );
+                  astSetInvert( rmap1, rinv1 );
+               }
+            }
+
+/* Reinstate the invert flags for the inverse selector Mappings. Ensure
+   this is done in the opposite order to which the selector Mappings were
+   obtained (in case they are in fact the same Mapping). */
+            if( ismap2 ) astSetInvert( ismap2, isinv2 );
+            if( ismap1 ) astSetInvert( ismap1, isinv1 );
+         }
+
+/* Reinstate the invert flags for the forward selector Mappings. Ensure
+   this is done in the oppsote order to which the selector Mappings were
+   obtained (in case they are in fact the same Mapping). */
+         if( fsmap2 ) astSetInvert( fsmap2, fsinv2 );
+         if( fsmap1 ) astSetInvert( fsmap1, fsinv1 );
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     SwitchMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied SwitchMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the SwitchMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstSwitchMap *this;
+   int i;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the SwitchMap structure. */
+   this = (AstSwitchMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by this class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astGetObjSize( this->fsmap );
+   result += astGetObjSize( this->ismap );
+
+   for( i = 0; i < this->nroute; i++ ) {
+      result += astGetObjSize( this->routemap[ i ] );
+   }
+
+   result += astGetObjSize( this->routeinv );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static AstMapping *GetRoute( AstSwitchMap *this, double sel, int *inv, int *status ){
+/*
+*  Name:
+*     GetRoute
+
+*  Purpose:
+*     Return a pointer to a route Mapping, handling all Invert flags.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     AstMapping *GetRoute( AstSwitchMap *this, double sel, int *inv, int *status )
+
+*  Class Membership:
+*     SwitchMap method.
+
+*  Description:
+*     This function returns a pointer to a route Mapping (specified by a
+*     floating point selector value) for the given SwitchMap, taking account
+*     of the state of the Invert flag of both the route Mapping and the
+*     SwitchMap.
+
+*  Parameters:
+*     this
+*        Pointer to the SwitchMap.
+*     sel
+*        The selector value. The nearest integer value (minus 1) is used
+*        to index the array of route Mappings stored in the SwitchMap. A
+*        NULL pointer is returned if the selector value is out of range.
+*     inv
+*        Pointer to an int in which to return the original value of the
+*        Invert flag of the returned Mapping. The astSetInvert method
+*        should be used to re-instate this value once all use of the Mapping
+*        has been completed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     A pointer to the route Mapping to use. Note, the returned pointer
+*     should NOT be annulled when no longer needed. NULL is returned
+*     (without error) if the SwitchMap does not have a route Mapping for the
+*     requested selector value. The forward transformation of the
+*     returned Mapping will implenment the forward transformation of the
+*     required route Mapping (and vice-versa).
+
+*/
+
+/* Local Variables: */
+   AstMapping *ret;
+   int rindex;
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Check selector value is good. */
+   if( sel != AST__BAD ) {
+
+/* Convert the supplied floating point selector value into an integer
+   index into the array of route Mappings held in the supplied SwitchMap. */
+      rindex = (int)( sel + 0.5 ) - 1;
+
+/* Return the null pointer if the index is out of range. */
+      if( rindex >= 0 && rindex < this->nroute ) {
+
+/* Get the required route Mapping. */
+         ret = ( this->routemap )[ rindex ];
+
+/* Return its original invert flag. */
+         *inv = astGetInvert( ret );
+
+/* Set the Invert flag back to the value it had when the SwitchMap was
+   created. */
+         astSetInvert( ret, this->routeinv[ rindex ] );
+
+/* If the SwitchMap has since been inverted, also invert the returned
+   route Mapping, so that the forward transformation of the returned
+   Mapping implements the forward transformation of the supplied
+   SwitchMap (and vice-versa). */
+         if( astGetInvert( this ) ) astInvert( ret );
+      }
+   }
+
+/* Return the pointer. */
+   return ret;
+
+}
+
+static AstMapping *GetSelector( AstSwitchMap *this, int fwd, int *inv, int *status ){
+/*
+*  Name:
+*     GetSelector
+
+*  Purpose:
+*     Return a pointer to a selector Mapping, handling all Invert flags.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     AstMapping *GetSelector( AstSwitchMap *this, int fwd, int *inv, int *status )
+
+*  Class Membership:
+*     SwitchMap method.
+
+*  Description:
+*     This function returns a pointer to either the forward or inverse
+*     selector Mapping for the given SwitchMap, taking account of the
+*     state of the Invert flag of bothe the selector Mapping and the
+*     SwitchMap.
+
+*  Parameters:
+*     this
+*        Pointer to the SwitchMap.
+*     fwd
+*        If non-zero, return the forward selector Mapping. Otherwise,
+*        return the inverse selector Mapping.
+*     inv
+*        Pointer to an int in which to return the original value of the
+*        Invert flag of the returned Mapping. The astSetInvert method
+*        should be used to re-instate this value once all use of the Mapping
+*        has been completed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returns:
+*     A pointer to the selector Mapping to use. Note, the returned pointer
+*     should NOT be annulled when no longer needed. NULL is returned
+*     (without error) if the SwitchMap does not have a Mapping for the
+*     requested selector.
+
+*/
+
+/* Local Variables: */
+   AstMapping *ret;
+   int swinv;
+
+/* Initialise */
+   ret = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* See if the SwitchMap has been inverted. */
+   swinv = astGetInvert( this );
+
+/* If the SwitchMap has been inverted, the forward and inverse selector
+   Mappings should be reversed. */
+   if( ( !swinv && !fwd ) || ( swinv && fwd ) ){
+      ret = this->ismap;
+      if( ret ) {
+         *inv = astGetInvert( ret );
+         astSetInvert( ret, this->isinv );
+      }
+
+   } else {
+      ret = this->fsmap;
+      if( ret ) {
+         *inv = astGetInvert( ret );
+         astSetInvert( ret, this->fsinv );
+      }
+   }
+
+   if( ret && swinv ) astInvert( ret );
+
+/* Return the pointer. */
+   return ret;
+
+}
+
+void astInitSwitchMapVtab_(  AstSwitchMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSwitchMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a SwitchMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     void astInitSwitchMapVtab( AstSwitchMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     SwitchMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the SwitchMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsASwitchMap) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+/* None. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+   mapping->Rate = Rate;
+   mapping->RemoveRegions = RemoveRegions;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "SwitchMap", "Alternate regionalised Mapping" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     SwitchMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstSwitchMap *this;       /* Pointer to SwitchMap structure */
+   int i;                      /* Loop count */
+   int result;                 /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the SwitchMap structure. */
+   this = (AstSwitchMap *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->fsmap, mode, extra, fail );
+   if( !result ) result = astManageLock( this->ismap, mode, extra, fail );
+   for( i = 0; i < this->nroute; i++ ) {
+      if( !result ) result = astManageLock( this->routemap[ i ], mode,
+                                            extra, fail );
+   }
+
+   return result;
+
+}
+#endif
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a SwitchMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     SwitchMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated SwitchMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated SwitchMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated SwitchMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the SwitchMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        SwitchMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated SwitchMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstSwitchMap *map;
+   AstMapping *new;
+   int i;
+   int nroute;
+   int result;
+   int fsinv_old;
+   int isinv_old;
+   int *rinv_old;
+   AstMapping *sfsmap;
+   AstMapping *sismap;
+   int simp;
+   AstMapping **srmap;
+   AstSwitchMap *swneb;
+   int ilo;
+   int equal;
+
+/* Initialise.*/
+   result = -1;
+
+/* Check the inherited status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to this SwitchMap, and note the number of route Mappings. */
+   map = (AstSwitchMap *) this;
+   nroute = map->nroute;
+
+/* Temporarily put the Invert flag of all encapsulated Mappings (both
+   route and selector) back to the values they had when the SwitchMap was
+   created, noting their current values so that they can be re-instated
+   later. If the SwitchMap itself has been inverted, swap all the original
+   invert flags. */
+   if( map->fsmap ) {
+      fsinv_old = astGetInvert( map->fsmap );
+      astSetInvert( map->fsmap, map->fsinv );
+   } else {
+      fsinv_old = 0;
+   }
+
+   if( map->ismap ) {
+      isinv_old = astGetInvert( map->ismap );
+      astSetInvert( map->ismap, map->isinv );
+   } else {
+      isinv_old = 0;
+   }
+
+   rinv_old = astMalloc( sizeof( int )*nroute );
+   if( astOK ) {
+      for( i = 0; i < nroute; i++ ) {
+         rinv_old[ i ] = astGetInvert( map->routemap[ i ] );
+         astSetInvert( map->routemap[ i ], map->routeinv[ i ] );
+      }
+   }
+
+/* If possible, merge the SwitchMap with a neighbouring SwitchMap. */
+/* =============================================================== */
+/* Only do this if we are combining the Mappings in series. */
+   if( series ) {
+
+/* Is the higher neighbour a SwitchMap? If so get a pointer to it, and
+   note the index of the lower of the two adjacent SwitchMaps. */
+      if( where < ( *nmap - 1 ) &&
+          astIsASwitchMap( ( *map_list )[ where + 1 ] ) ){
+         swneb = (AstSwitchMap *) ( *map_list )[ where + 1 ];
+         ilo = where;
+
+/* If not, is the lower neighbour a SwitchMap? If so get a pointer to it, and
+   note the index of the lower of the two adjacent SwitchMaps. */
+      } else if( where > 0 &&
+                 astIsASwitchMap( ( *map_list )[ where - 1 ] ) ){
+         swneb = (AstSwitchMap *) ( *map_list )[ where - 1 ];
+         ilo =  where - 1;
+
+      } else {
+         swneb = NULL;
+      }
+
+/* If a neighbouring SwitchMap was found, we can replace the pair by a
+   UnitMap if the two SwitchMaps are equal but have opposite values for
+   their Invert flags. Temporarily invert the neighbour, then compare
+   the two SwitchMaps for equality, then re-invert the neighbour. */
+      if( swneb ) {
+         astInvert( swneb );
+         equal = astEqual( map, swneb );
+         astInvert( swneb );
+
+/* If the two SwitchMaps are equal but opposite, annul the first of the two
+   Mappings, and replace it with a UnitMap. Also set the invert flag. */
+         if( equal ) {
+            new = (AstMapping *) astUnitMap( astGetNin( ( *map_list )[ ilo ] ), "", status );
+            (void) astAnnul( ( *map_list )[ ilo ] );
+            ( *map_list )[ ilo ] = new;
+            ( *invert_list )[ ilo ] = 0;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+            (void) astAnnul( ( *map_list )[ ilo + 1 ] );
+            for ( i = ilo + 2; i < *nmap; i++ ) {
+               ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+               ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+            }
+
+/* Clear the vacated element at the end. */
+            ( *map_list )[ *nmap - 1 ] = NULL;
+            ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+            ( *nmap )--;
+            result = where;
+         }
+      }
+   }
+
+/* Attempt to simplify the SwitchMap on its own. */
+/* ============================================= */
+/* Only do this if no change was made above. */
+   if( result == -1 ) {
+
+/* If the SwitchMap is inverted, create an equal SwitchMap which is not
+   inverted. To do this, invert and swap the selector Mappings, and
+   invert all the route Mappings. We use astSetInvert rather than astInvert
+   because two or more more stored pointers may point to the same Mapping
+   in which case that Mapping would be inverted more than once with
+   unpredictable results. */
+      if( ( *invert_list )[ where ] ) {
+         if( map->fsmap ) astSetInvert( map->fsmap, !(map->fsinv) );
+         if( map->ismap ) astSetInvert( map->ismap, !(map->isinv) );
+         for( i = 0; i < nroute; i++ ) {
+            astSetInvert( map->routemap[ i ], !(map->routeinv[ i ]) );
+         }
+
+         new = (AstMapping *) astSwitchMap( map->ismap, map->fsmap, nroute, (void **) map->routemap, "", status );
+
+         (void) astAnnul( ( *map_list )[ where ] );
+         ( *map_list )[ where ] = (AstMapping *) new;
+         ( *invert_list )[ where ] = 0;
+         result = where;
+
+/* Otherwise, try to simplify each of the encapsulated Mappings, noting
+   if any simplification takes place. */
+      } else {
+         sfsmap = ( map->fsmap ) ? astSimplify( map->fsmap ) : NULL;
+         sismap = ( map->ismap ) ? astSimplify( map->ismap ) : NULL;
+         simp = ( sfsmap != map->fsmap ) || ( sismap != map->ismap );
+
+         srmap = astMalloc( sizeof( AstMapping * )*nroute );
+         if( astOK ) {
+            for( i = 0; i < nroute; i++ ) {
+               srmap[ i ] = astSimplify( map->routemap[ i ] );
+               simp = simp || ( srmap[ i ] != map->routemap[ i ] );
+            }
+         }
+
+/* If any simplification took place, construct a new SwitchMap from these
+    simplified Mappings. */
+         if( simp ) {
+            (void) astAnnul( ( *map_list )[ where ] );
+            ( *map_list )[ where ] = (AstMapping *) astSwitchMap( sfsmap, sismap,
+                                                    nroute, (void **) srmap, "", status );
+            result = where;
+         }
+
+/* Release resources. */
+         if( sfsmap ) sfsmap = astAnnul( sfsmap );
+         if( sismap ) sismap = astAnnul( sismap );
+         if( srmap ) {
+            for( i = 0; i < nroute; i++ ) srmap[ i ] = astAnnul( srmap[ i ] );
+            srmap = astFree( srmap );
+         }
+      }
+   }
+
+/* Re-instate the original Invert values for the encapsulated Mappings. */
+   if( map->fsmap ) astSetInvert( map->fsmap, fsinv_old );
+   if( map->ismap ) astSetInvert( map->ismap, isinv_old );
+   if( rinv_old ) {
+      for( i = 0; i < nroute; i++ ) {
+         astSetInvert( map->routemap[ i ], rinv_old[ i ] );
+      }
+      rinv_old = astFree( rinv_old );
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     SwitchMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position. Also evaluates the second derivative.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*/
+
+/* Local Variables: */
+   AstSwitchMap *map;
+   AstMapping *smap;
+   AstMapping *rmap;
+   double result;
+   double sel;
+   int fsinv;
+   int rinv;
+   int nin;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Get a pointer to the SwitchMap structure. */
+   map = (AstSwitchMap *) this;
+
+/* Get a pointer to the effective foward selector Mapping, and its current
+   invert flag (this takes account of whether the SwtichMap has been
+   inverted or not). This call resets the selector's invert flag temporarily
+   back to the value it had when the SwitchMap was created. */
+   smap = GetSelector( map, 1, &fsinv, status );
+
+/* If the SwitchMap has no forward selector Mapping, return AST__BAD. */
+   if( smap ) {
+
+/* Get the number of inputs */
+      nin = astGetNin( smap );
+
+/* Transform the supplied position using the selector Mapping. The output
+   value is the selector value that indicates which route Mapping to use. */
+      astTranN( smap, 1, nin, 1, at, 1, 1, 1, &sel );
+
+/* Get the index of the route Mapping to use, and check it is valid (if
+   not, return AST__BAD if not). This takes account of whether the
+   SwitchMap has been inverted, and also temporarily re-instates the
+   original value of the route Mapping's Invert flag . */
+      rmap = GetRoute( map, sel, &rinv, status );
+      if( rmap ) {
+
+/* Use the astRate method of the route Mapping. */
+         result = astRate( rmap, at, ax1, ax2 );
+
+/* Reset the Invert flag for the route Mapping. */
+         astSetInvert( rmap, rinv );
+      }
+
+/* Reset the Invert flag for the selector Mapping. */
+      astSetInvert( smap, fsinv );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstMapping *RemoveRegions( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     RemoveRegions
+
+*  Purpose:
+*     Remove any Regions from a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     AstMapping *RemoveRegions( AstMapping *this, int *status )
+
+*  Class Membership:
+*     SwitchMap method (over-rides the astRemoveRegions method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function searches the supplied Mapping (which may be a
+*     compound Mapping such as a SwitchMap) for any component Mappings
+*     that are instances of the AST Region class. It then creates a new
+*     Mapping from which all Regions have been removed. If a Region
+*     cannot simply be removed (for instance, if it is a component of a
+*     parallel SwitchMap), then it is replaced with an equivalent UnitMap
+*     in the returned Mapping.
+*
+*     The implementation provided by the SwitchMap class invokes the
+*     astRemoveRegions method on all the component Mappings, and joins
+*     the results together into a new SwitchMap.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the modified mapping.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstMapping **temp;            /* Array of new route Mappings */
+   AstMapping *newfsmap;         /* New forward selector Mapping */
+   AstMapping *newismap;         /* New inverse selector Mapping */
+   AstMapping *result;           /* Result pointer to return */
+   AstSwitchMap *new;            /* Pointer to new SwitchMap */
+   AstSwitchMap *this;           /* Pointer to SwitchMap structure */
+   int changed;                  /* Has any mapping been changed? */
+   int i;                        /* Loop count */
+   int nax;                      /* Number of Frame axes */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the SwitchMap. */
+   this = (AstSwitchMap *) this_mapping;
+
+/* Allocate an array to hold the modified Mapping pointers. */
+   temp = astMalloc( sizeof( AstMapping *)*( this->nroute ) );
+   if( astOK ) {
+
+/* Invoke the astRemoveRegions method on all the component Mappings. */
+      changed = 0;
+      for( i = 0; i < this->nroute; i++ ) {
+         temp[ i ] = astRemoveRegions( this->routemap[ i ] );
+
+/* Note if any Mapping was changed. */
+         if( temp[ i ] != this->routemap[ i ] ) {
+            changed = 1;
+
+/* The implementation of the astRemoveRegions method provided by the
+   Region class returns a Frame rather than a UnitMap. But we need
+   Mappings here, not Frames. So if the new Mapping is a Frame, replace
+   it with an equivalent UnitMap. */
+            if( astIsAFrame( temp[ i ] ) ) {
+               nax = astGetNin( temp[ i ] );
+               (void) astAnnul( temp[ i ] );
+               temp[ i ] = (AstMapping *) astUnitMap( nax, " ", status );
+            }
+         }
+      }
+
+/* And on the other ancillary Mappings */
+      if( this->fsmap ) {
+         newfsmap = astRemoveRegions( this->fsmap );
+         if( newfsmap != this->fsmap ) {
+            changed = 1;
+            if( astIsAFrame( newfsmap ) ) {
+               nax = astGetNin( newfsmap );
+               (void) astAnnul( newfsmap );
+               newfsmap = (AstMapping *) astUnitMap( nax, " ", status );
+            }
+         }
+
+      } else {
+         newfsmap = NULL;
+      }
+
+      if( this->ismap ) {
+         newismap = astRemoveRegions( this->ismap );
+         if( newismap != this->ismap ) {
+            changed = 1;
+            if( astIsAFrame( newismap ) ) {
+               nax = astGetNin( newismap );
+               (void) astAnnul( newismap );
+               newismap = (AstMapping *) astUnitMap( nax, " ", status );
+            }
+         }
+
+      } else {
+         newismap = NULL;
+      }
+
+/* If no component was modified, just return a clone of the supplied
+   pointer. */
+      if( ! changed ) {
+         result = astClone( this );
+
+/* Otherwise, we need to create a new Mapping to return. We take a deep
+   copy of the supplied SwitchMap and then modify the Mappings so that
+   we retain any extra information in the supplied SwitchMap. */
+      } else {
+         new = astCopy( this );
+
+         for( i = 0; i < this->nroute; i++ ) {
+            (void) astAnnul( new->routemap[ i ] );
+            new->routemap[ i ] = astClone( temp[ i ] );
+         }
+
+         if( newfsmap ) {
+            (void) astAnnul( new->fsmap );
+            new->fsmap = astClone( newfsmap );
+         }
+
+         if( newismap ) {
+            (void) astAnnul( new->ismap );
+            new->ismap = astClone( newismap );
+         }
+
+         result = (AstMapping *) new;
+      }
+
+/* Free resources. */
+      for( i = 0; i < this->nroute; i++ ) {
+         temp[ i ] = astAnnul( temp[ i ] );
+      }
+
+      if( newfsmap ) newfsmap = astAnnul( newfsmap );
+      if( newismap ) newismap = astAnnul( newismap );
+   }
+
+   temp = astFree( temp );
+
+/* Annul the returned Mapping if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+int astSwitchList_( AstSwitchMap *this, int invert, int *nmap,
+                    AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*+
+*  Name:
+*     astSwitchList
+
+*  Purpose:
+*     Extract the selector and route Mappings from a SwitchMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     int astSwitchList( AstSwitchMap *this, int invert, int *nmap,
+*                        AstMapping ***map_list, int **invert_list )
+
+*  Class Membership:
+*     SwitchMap member function.
+
+*  Description:
+*     This function extracts the route and selector Mappings form a
+*     SwitchMap.
+
+*  Parameters:
+*     this
+*        Pointer to the SwitchMap to be decomposed (it is not actually
+*        modified by this function).
+*     invert
+*        The value to which the SwitchMap's Invert attribute is to be
+*        (notionally) set before performing the decomposition. Normally,
+*        the value supplied here will be the actual Invert value obtained
+*        from the SwitchMap (e.g. using astGetInvert).  Sometimes, however,
+*        when a SwitchMap is encapsulated within another structure, that
+*        structure may retain an Invert value (in order to prevent external
+*        interference) which should be used instead.
+*
+*        Note that the actual Invert value of the SwitchMap supplied is
+*        not used (or modified) by this function.
+*     nmap
+*        The address of an int in which to return a count of the number of
+*        individual Mappings in the decomposition. The supplied value is
+*        ignored.
+*     map_list
+*        Address of a pointer to an array of Mapping pointers. The value
+*        supplied on entry is ignored. On exit, it points at a dynamically
+*        allocated array containing Mapping pointers ("*nmap" in number) that
+*        result from the decomposition requested.
+*
+*        The returned Mapping pointers returned will identify the following
+*        sequence of Mappings; forward selector mapping (or NULL if the
+*        SwitchMap has no forward selector Mapping), inverse selector
+*        mapping (or NULL if the SwitchMap has no inverse selector Mapping),
+*        the route Mappings in the order they were supplied when the
+*        SwitchMap was constructed.
+*
+*        All the Mapping pointers returned by this function should be
+*        annulled by the caller, using astAnnul, when no longer
+*        required. The dynamic array holding these pointers should
+*        also be freed, using astFree.
+*     invert_list
+*        Address of a pointer to an array of int. The value supplied on
+*        entry is ignored. On exit, it points at a dynamically allocated
+*        array containing Invert attribute values ("*nmap" in number) that
+*        result from the decomposition requested.
+*
+*        The returned Invert values returned identify the values which must
+*        be assigned to the Invert attributes of the corresponding
+*        Mappings (whose pointers are in the "*map_list" array) before
+*        they are applied. Note that these values may differ from the
+*        actual Invert attribute values of these Mappings, which are
+*        not relevant.
+*
+*        The dynamic array holding these values should be freed by the
+*        caller, using astFree, when no longer required.
+
+*  Returned Value:
+*     The number of route Mappings stored in the SwitchMap.
+
+*  Notes:
+*     - It is unspecified to what extent the original SwitchMap and the
+*     individual (decomposed) Mappings are inter-dependent. Consequently,
+*     the individual Mappings cannot be modified without risking
+*     modification of the original SwitchMap.
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then the *nmap value, the
+*     list of Mapping pointers and the list of Invert values will all
+*     be returned unchanged.
+*-
+*/
+
+/* Local Variables: */
+   AstMapping *map;              /* Pointer to Mapping to return */
+   int inv;                      /* Original Invert flag for Mapping */
+   int i;                        /* Route Mapping index */
+   int oldinv;                   /* Original Invert flag for SwitchMap */
+   int result;                   /* Returned value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Store the numbe of route Mappings */
+   result = this->nroute;
+   *nmap = result + 2;
+
+/* Allocate the required arrays. */
+   *map_list = astMalloc( sizeof( AstMapping * )*(size_t) *nmap );
+   *invert_list = astMalloc( sizeof( int )*(size_t) *nmap );
+
+/* Check the pointers can be used safely. */
+   if( astOK ) {
+
+/* Temporaily set the requested Invert flag for the SwitchMap. */
+      oldinv = astGetInvert( this );
+      astSetInvert( this, invert );
+
+/* Get the forward selector Mapping. */
+      map = GetSelector( this, 1, &inv, status );
+
+/* If the SwitchMap has a forward selector Mapping, return a clone of the
+   Mapping pointer, and the invert flag to be used with it, then
+   re-instate the original invert flag value (which was modified by
+   GetSelector). */
+      if( map ) {
+         ( *map_list )[ 0 ] = astClone( map );
+         ( *invert_list )[ 0 ] = astGetInvert( map );
+         astSetInvert( map, inv );
+
+/* If the SwitchMap does not has a forward selector Mapping, return a
+   NULL pointer. */
+      } else {
+         ( *map_list )[ 0 ] = NULL;
+         ( *invert_list )[ 0 ] = 0;
+      }
+
+/* Likewise, get and return the inverse selector Mapping.*/
+      map = GetSelector( this, 0, &inv, status );
+      if( map ) {
+         ( *map_list )[ 1 ] = astClone( map );
+         ( *invert_list )[ 1 ] = astGetInvert( map );
+         astSetInvert( map, inv );
+      } else {
+         ( *map_list )[ 1 ] = NULL;
+         ( *invert_list )[ 1 ] = 0;
+      }
+
+/* Loop round all route Mappings. */
+      for( i = 0; i < result; i++ ){
+
+/* Get the next route Mapping. */
+         map = GetRoute( this, (double) i + 1.0, &inv, status );
+
+/* If the SwitchMap has a route Mapping for the current selector value,
+   return a clone of the Mapping pointer, and the invert flag to be used
+   with it, then re-instate the original invert flag value (which was
+   modified by GetRoute). */
+         if( map ) {
+            ( *map_list )[ i + 2 ] = astClone( map );
+            ( *invert_list )[ i + 2 ] = astGetInvert( map );
+            astSetInvert( map, inv );
+
+/* If the SwitchMap does not has a route Mapping for the current selector
+   value, return a NULL pointer. */
+         } else {
+            ( *map_list )[ i + 2 ] = NULL;
+            ( *invert_list )[ i + 2 ] = 0;
+         }
+
+      }
+
+/* Re-instate the original Ivert flag for the SwitchMap. */
+      astSetInvert( this, oldinv );
+
+   }
+
+/* If an error has occurred, free the returned arrays. */
+   if( !astOK ) {
+      *map_list = astFree( *map_list );
+      *invert_list= astFree( *invert_list );
+      result= 0;
+      *nmap = 0;
+   }
+
+/* Return the result */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a SwitchMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     SwitchMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a SwitchMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required Mapping.
+*     This implies applying each of the SwitchMap's component Mappings in turn,
+*     either in series or in parallel.
+
+*  Parameters:
+*     this
+*        Pointer to the SwitchMap.
+*     in
+*        Pointer to the PointSet associated with the input coordinate values.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the SwitchMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstMapping *rmap;
+   AstMapping *selmap;
+   AstPointSet *ps1;
+   AstPointSet *ps1a;
+   AstPointSet *ps2;
+   AstPointSet *ps2a;
+   AstPointSet *result;
+   AstPointSet *selps;
+   AstSwitchMap *map;
+   double **in_ptr;
+   double **out_ptr;
+   double **ptr1;
+   double **ptr2;
+   double **sel_ptr;
+   double *outv;
+   double *sel;
+   int *popmap;
+   int iroute;
+   int ipoint;
+   int j;
+   int k;
+   int maxpop;
+   int ncin;
+   int ncout;
+   int npoint;
+   int nroute;
+   int rindex;
+   int rinv;
+   int selinv;
+   int totpop;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the SwitchMap. */
+   map = (AstSwitchMap *) this;
+
+/* Apply the parent Mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We now extend the parent astTransform method by applying the component
+   Mappings of the SwitchMap to generate the output coordinate values. */
+
+/* Get the number of input and output coords. */
+   if( forward ) {
+      ncin = astGetNin( this );
+      ncout = astGetNout( this );
+   } else {
+      ncin = astGetNout( this );
+      ncout = astGetNin( this );
+   }
+
+/* Get the appropriate selector Mapping. */
+   selmap = GetSelector( map, forward, &selinv, status );
+
+/* Transform the supplied positions using the above selector Mapping. */
+   selps = astTransform( selmap, in, forward, NULL );
+
+/* Get a pointer to the array holding the selector value. */
+   sel_ptr = astGetPoints( selps );
+
+/* Get a pointer to the array holding the input values. */
+   in_ptr = astGetPoints( in );
+
+/* Get a pointer to the array in which to store the results, and the total
+   number of points being transformed. */
+   out_ptr = astGetPoints( result );
+   npoint = astGetNpoint( result );
+
+/* We now count how many positions are to be tranformed by each of the
+   route Mappings. */
+   nroute = map->nroute;
+   popmap = astMalloc( sizeof( int )*nroute );
+   if( astOK ) {
+      for( iroute = 0; iroute < nroute; iroute++ ) popmap[ iroute ] = 0;
+
+      sel = sel_ptr[ 0 ];
+      for( ipoint = 0; ipoint < npoint; ipoint++,sel++ ) {
+         if( *sel != AST__BAD ) {
+            rindex = (int)( *sel + 0.5 ) - 1;
+            if( rindex >= 0 && rindex < nroute ) ( popmap[ rindex ] )++;
+         }
+      }
+
+/* Find the number of points transformed by the most popular route Mapping.
+   Also find the total number of points transformed by any route Mapping. */
+      totpop = 0;
+      maxpop = 0;
+      for( iroute = 0; iroute < nroute; iroute++ ) {
+         if( popmap[ iroute ] > maxpop ) maxpop = popmap[ iroute ];
+         totpop += popmap[ iroute ];
+      }
+      if( maxpop == 0 ) maxpop = 1;
+
+/* If some of the points are not transformed by any route Mapping.
+   Initialise the whole output array to hold AST__BAD at every point. */
+      if( totpop < npoint ) {
+         for( j = 0; j < ncout; j++ ) {
+            outv = out_ptr[ j ];
+            for( ipoint = 0; ipoint < npoint; ipoint++ ) *(outv++) = AST__BAD;
+         }
+      }
+
+/* Create a PointSet large enough to hold all the supplied positions
+   which are to be transformed by the most popular route Mapping. */
+      ps1 = astPointSet( maxpop, ncin, "", status );
+      ptr1 = astGetPoints( ps1 );
+
+/* Create a PointSet large enough to hold all the output positions
+   created by the most popular route Mapping. */
+      ps2 = astPointSet( maxpop, ncout, "", status );
+      ptr2 = astGetPoints( ps2 );
+      if( astOK ) {
+
+/* Loop round each route Mapping which is used by at least 1 point. */
+         for( iroute = 0; iroute < nroute; iroute++ ) {
+            if( popmap[ iroute ] >0 ) {
+               rmap = GetRoute( map, (double)( iroute + 1 ), &rinv, status );
+
+/* Construct two PointSets of the correct size to hold the input and
+   output points to be processed with the current route Mapping. We
+   re-use the memory allocated for the largest route Mapping's PointSet. */
+               if( popmap[ iroute ] != maxpop ) {
+                  ps1a = astPointSet( popmap[ iroute ], ncin, "", status );
+                  astSetPoints( ps1a, ptr1 );
+                  ps2a = astPointSet( popmap[ iroute ], ncout, "", status );
+                  astSetPoints( ps2a, ptr2 );
+               } else {
+                  ps1a = astClone( ps1 );
+                  ps2a = astClone( ps2 );
+               }
+
+/* Fill the input PointSet with the input positions which are to be
+   transformed using the current route Mapping. */
+               sel = sel_ptr[ 0 ];
+               k = 0;
+               for( ipoint = 0; ipoint < npoint; ipoint++,sel++ ) {
+                  if( *sel != AST__BAD ) {
+                     rindex = (int)( *sel + 0.5 ) - 1;
+                     if( rindex == iroute ) {
+                        for( j = 0; j < ncin; j++ ) {
+                           ptr1[ j ][ k ] = in_ptr[ j ][ ipoint ];
+                        }
+                        k++;
+                     }
+                  }
+               }
+
+/* Use the route Mapping to transform this PointSet. */
+               (void) astTransform( rmap, ps1a, forward, ps2a );
+
+/* Copy the axis values from the resulting PointSet back into the results
+   array. */
+               sel = sel_ptr[ 0 ];
+               k = 0;
+               for( ipoint = 0; ipoint < npoint; ipoint++,sel++ ) {
+                  if( *sel != AST__BAD ) {
+                     rindex = (int)( *sel + 0.5 ) - 1;
+                     if( rindex == iroute ) {
+                        for( j = 0; j < ncout; j++ ) {
+                           out_ptr[ j ][ ipoint ] = ptr2[ j ][ k ];
+                        }
+                        k++;
+                     }
+                  }
+               }
+
+/* Free resources. */
+               ps1a = astAnnul( ps1a );
+               ps2a = astAnnul( ps2a );
+
+/* Re-instate the Invert flag for the route Mapping. */
+               astSetInvert( rmap, rinv );
+            }
+         }
+      }
+
+/* Free resources. */
+      ps1 = astAnnul( ps1 );
+      ps2 = astAnnul( ps2 );
+   }
+
+   selps = astAnnul( selps );
+   popmap = astFree( popmap );
+
+/* Re-instate the Invert flag of the selector Mapping. */
+   astSetInvert( selmap, selinv );
+
+/* If an error occurred, clean up by deleting the output PointSet (if
+   allocated by this function) and setting a NULL result pointer. */
+   if ( !astOK ) {
+      if ( !out ) result = astDelete( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for SwitchMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for SwitchMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the component
+*     Mappings within the SwitchMap.
+*/
+
+/* Local Variables: */
+   AstSwitchMap *in;                /* Pointer to input SwitchMap */
+   AstSwitchMap *out;               /* Pointer to output SwitchMap */
+   int i;                           /* Loop count */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output SwitchMaps. */
+   in = (AstSwitchMap *) objin;
+   out = (AstSwitchMap *) objout;
+
+/* For safety, start by clearing any references to the input component
+   Mappings,etc, from the output SwitchMap. */
+   out->fsmap = NULL;
+   out->ismap = NULL;
+   out->routemap = NULL;
+   out->routeinv = NULL;
+
+/* Make copies of these Mappings, etc, and store pointers to them in the output
+   SwitchMap structure. */
+   if( in->fsmap ) out->fsmap = astCopy( in->fsmap );
+   if( in->ismap ) out->ismap = astCopy( in->ismap );
+
+   out->routemap = astMalloc( sizeof( AstMapping * )*( in->nroute ) );
+   out->routeinv = astMalloc( sizeof( int )*( in->nroute ) );
+   if( astOK ) {
+      for( i = 0; i < in->nroute; i++ ) {
+         out->routemap[ i ] = astCopy( in->routemap[ i ] );
+         out->routeinv[ i ] = in->routeinv[ i ];
+      }
+   }
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for SwitchMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for SwitchMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstSwitchMap *this;              /* Pointer to SwitchMap */
+   int i;
+
+/* Obtain a pointer to the SwitchMap structure. */
+   this = (AstSwitchMap *) obj;
+
+/* Free dynamically allocated resources. */
+   if( this->fsmap ) this->fsmap = astAnnul( this->fsmap );
+   if( this->ismap ) this->ismap = astAnnul( this->ismap );
+   for( i = 0; i < this->nroute; i++ ) {
+      this->routemap[ i ] = astAnnul( this->routemap[ i ] );
+   }
+   this->routemap = astFree( this->routemap );
+   this->routeinv = astFree( this->routeinv );
+
+/* Clear the remaining SwitchMap variables. */
+   this->nroute = 0;
+   this->fsinv = 0;
+   this->isinv = 0;
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for SwitchMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the SwitchMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the SwitchMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstSwitchMap *this;
+   int ival;
+   int set;
+   int i;
+   char buf[ 20 ];
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the SwitchMap structure. */
+   this = (AstSwitchMap *) this_object;
+
+/* Write out values representing the instance variables for the SwitchMap
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Forward selector Mapping */
+/* ------------------------ */
+   if( this->fsmap ) {
+      astWriteObject( channel, "FSMap", 1, 1, this->fsmap,
+                      "Forward selector Mapping" );
+
+/* Forward selector Invert flag. */
+/* ----------------------------- */
+      ival = this->fsinv;
+      set = ( ival != 0 );
+      astWriteInt( channel, "FSInv", set, 0, ival,
+                   ival ? "Fwd selector used in inverse direction" :
+                          "Fwd selector used in forward direction" );
+   }
+
+
+/* Inverse selector Mapping */
+/* ------------------------ */
+   if( this->ismap ) {
+      astWriteObject( channel, "ISMap", 1, 1, this->ismap,
+                      "Inverse selector Mapping" );
+
+/* Forward selector Invert flag. */
+/* ----------------------------- */
+      ival = this->isinv;
+      set = ( ival != 0 );
+      astWriteInt( channel, "ISInv", set, 0, ival,
+                   ival ? "Inv selector used in inverse direction" :
+                          "Inv selector used in forward direction" );
+   }
+
+/* Loop to dump each route Mapping and its invert flag. */
+/* ---------------------------------------------------- */
+   for( i = 0; i < this->nroute; i++ ) {
+      sprintf( buf, "RMap%d", i + 1 );
+      astWriteObject( channel, buf, 1, 1, this->routemap[ i ],
+                      "Route Mapping" );
+
+      ival = this->routeinv[ i ];
+      set = ( ival != 0 );
+      sprintf( buf, "RInv%d", i + 1 );
+      astWriteInt( channel, buf, set, 0, ival,
+                   ival ? "Route Mapping used in inverse direction" :
+                          "Route Mapping used in forward direction" );
+   }
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsASwitchMap and astCheckSwitchMap functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(SwitchMap,Mapping)
+astMAKE_CHECK(SwitchMap)
+
+AstSwitchMap *astSwitchMap_( void *fsmap_void, void *ismap_void, int nroute,
+                             void **routemaps_void, const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astSwitchMap
+
+*  Purpose:
+*     Create a SwitchMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     AstSwitchMap *astSwitchMap( AstMapping *fsmap, AstMapping *ismap,
+*                                 int nroute, AstMapping **routemaps,
+*                                 const char *options, ... )
+
+*  Class Membership:
+*     SwitchMap constructor.
+
+*  Description:
+*     This function creates a new SwitchMap and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     fsmap
+*        Pointer to the forward selector Mapping
+*     ismap
+*        Pointer to the inverse selector Mapping
+*     nroute
+*        The number of route Mappings.
+*     routemaps
+*        An array of pointers to the route Mappings.
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new SwitchMap. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new SwitchMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic SwitchMap constructor which is
+*     available via the protected interface to the SwitchMap class.  A
+*     public interface is provided by the astSwitchMapId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "map1" and "map2" parameters are of type (void *) and are
+*     converted and validated within the function itself.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSwitchMap *new;            /* Pointer to new SwitchMap */
+   AstMapping *fsmap;            /* Pointer to fwd selector Mapping */
+   AstMapping *ismap;            /* Pointer to inv selector Mapping */
+   AstMapping **routemaps;       /* Array of route Mapping pointers */
+   int i;                        /* Route Mappings index */
+   va_list args;                 /* Variable argument list */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Report an error if no route Mappings have been supplied. */
+   if( nroute <= 0 ) astError( AST__BDPAR, "astSwitchMap(SwitchMap): "
+                               "Bad number of route Mappings (%d) specified.", status,
+                               nroute );
+
+/* Otherwise create an array to hold the route Mapping pointers. */
+   routemaps = astMalloc( sizeof( AstMapping * )*nroute );
+
+/* Obtain and validate pointers to the Mapping structures provided. */
+   if( astOK ) {
+      fsmap = fsmap_void ? astCheckMapping( fsmap_void ) : NULL;
+      ismap = ismap_void ? astCheckMapping( ismap_void ) : NULL;
+      for( i = 0; i < nroute; i++ ) {
+         routemaps[ i ] = astCheckMapping( routemaps_void[ i ] );
+      }
+   }
+
+   if ( astOK ) {
+
+/* Initialise the SwitchMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitSwitchMap( NULL, sizeof( AstSwitchMap ), !class_init, &class_vtab,
+                              "SwitchMap", fsmap, ismap, nroute, routemaps );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new SwitchMap's
+   attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Free memory used to hold the route Mapping pointers. */
+   routemaps = astFree( routemaps );
+
+/* Return a pointer to the new SwitchMap. */
+   return new;
+}
+
+AstSwitchMap *astSwitchMapId_( void *fsmap_void, void *ismap_void, int nroute,
+                               void **routemaps_void, const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astSwitchMap
+f     AST_SWITCHMAP
+
+*  Purpose:
+*     Create a SwitchMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "switchmap.h"
+c     AstSwitchMap *astSwitchMap( AstMapping *fsmap, AstMapping *ismap,
+c                                 int nroute, AstMapping *routemaps[],
+c                                 const char *options, ... )
+f     RESULT = AST_SWITCHMAP( FSMAP, ISMAP, NROUTE, ROUTEMAPS, OPTIONS,
+f                             STATUS )
+
+*  Class Membership:
+*     SwitchMap constructor.
+
+*  Description:
+*     This function creates a new SwitchMap and optionally initialises
+*     its attributes.
+*
+*     A SwitchMap is a Mapping which represents a set of alternate
+*     Mappings, each of which is used to transform positions within a
+*     particular region of the input or output coordinate system of the
+*     SwitchMap.
+*
+*     A SwitchMap can encapsulate any number of Mappings, but they must
+*     all have the same number of inputs (Nin attribute value) and the
+*     same number of outputs (Nout attribute value). The SwitchMap itself
+*     inherits these same values for its Nin and Nout attributes. Each of
+*     these Mappings represents a "route" through the switch, and are
+*     referred to as "route" Mappings below. Each route Mapping transforms
+*     positions between the input and output coordinate space of the entire
+*     SwitchMap, but only one Mapping will be used to transform any given
+*     position. The selection of the appropriate route Mapping to use with
+*     any given input position is made by another Mapping, called the
+*     "selector" Mapping. Each SwitchMap encapsulates two selector
+*     Mappings in addition to its route Mappings; one for use with the
+*     SwitchMap's forward transformation (called the "forward selector
+*     Mapping"), and one for use with the SwitchMap's inverse transformation
+*     (called the "inverse selector Mapping"). The forward selector Mapping
+*     must have the same number of inputs as the route Mappings, but
+*     should have only one output. Likewise, the inverse selector Mapping
+*     must have the same number of outputs as the route Mappings, but
+*     should have only one input.
+*
+*     When the SwitchMap is used to transform a position in the forward
+*     direction (from input to output), each supplied input position is
+*     first transformed by the forward transformation of the forward selector
+*     Mapping. This produces a single output value for each input position
+*     referred to as the selector value. The nearest integer to the selector
+*     value is found, and is used to index the array of route Mappings (the
+*     first supplied route Mapping has index 1, the second route Mapping has
+*     index 2, etc). If the nearest integer to the selector value is less
+*     than 1 or greater than the number of route Mappings, then the SwitchMap
+*     output position is set to a value of AST__BAD on every axis. Otherwise,
+*     the forward transformation of the selected route Mapping is used to
+*     transform the supplied input position to produce the SwitchMap output
+*     position.
+*
+*     When the SwitchMap is used to transform a position in the inverse
+*     direction (from "output" to "input"), each supplied "output" position
+*     is first transformed by the inverse transformation of the inverse
+*     selector Mapping. This produces a selector value for each "output"
+*     position. Again, the nearest integer to the selector value is found,
+*     and is used to index the array of route Mappings. If this selector
+*     index value is within the bounds of the array of route Mappings, then
+*     the inverse transformation of the selected route Mapping is used to
+*     transform the supplied "output" position to produce the SwitchMap
+*     "input" position. If the selector index value is outside the bounds
+*     of the array of route Mappings, then the SwitchMap "input" position is
+*     set to a value of AST__BAD on every axis.
+*
+*     In practice, appropriate selector Mappings should be chosen to
+*     associate a different route Mapping with each region of coordinate
+*     space. Note that the SelectorMap class of Mapping is particularly
+*     appropriate for this purpose.
+*
+*     If a compound Mapping contains a SwitchMap in series with its own
+*     inverse, the combination of the two adjacent SwitchMaps will be
+*     replaced by a UnitMap when the compound Mapping is simplified using
+c     astSimplify.
+f     AST_SIMPLIFY.
+
+*  Parameters:
+c     fsmap
+f     FSMAP = INTEGER (Given)
+*        Pointer to the forward selector Mapping. This must have a
+*        defined forward transformation, but need not have a defined
+*        inverse transformation. It must have one output, and the number of
+*        inputs must match the number of inputs of each of the supplied
+*        route Mappings.
+c        NULL
+f        AST__NULL
+*        may be supplied, in which case the SwitchMap will have an undefined
+*        forward Mapping.
+c     ismap
+f     ISMAP = INTEGER (Given)
+*        Pointer to the inverse selector Mapping. This must have a
+*        defined inverse transformation, but need not have a defined
+*        forward transformation. It must have one input, and the number of
+*        outputs must match the number of outputs of each of the supplied
+*        route Mappings.
+c        NULL
+f        AST__NULL
+*        may be supplied, in which case the SwitchMap will have an undefined
+*        inverse Mapping.
+c     nroute
+f     NROUTE = INTEGER (Given)
+*        The number of supplied route Mappings.
+c     routemaps
+f     ROUTEMAPS( NROUTE ) = INTEGER (Given)
+*        An array of pointers to the route Mappings. All the supplied
+*        route Mappings must have common values for the Nin and Nout
+*        attributes, and these values define the number of inputs and
+*        outputs of the SwitchMap.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new SwitchMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new SwitchMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astSwitchMap()
+f     AST_SWITCHMAP = INTEGER
+*        A pointer to the new SwitchMap.
+
+*  Notes:
+c     - Note that the component Mappings supplied are not copied by
+c     astSwitchMap (the new SwitchMap simply retains a reference to
+c     them). They may continue to be used for other purposes, but
+c     should not be deleted. If a SwitchMap containing a copy of its
+c     component Mappings is required, then a copy of the SwitchMap should
+c     be made using astCopy.
+f     - Note that the component Mappings supplied are not copied by
+f     AST_SWITCHMAP (the new SwitchMap simply retains a reference to
+f     them). They may continue to be used for other purposes, but
+f     should not be deleted. If a SwitchMap containing a copy of its
+f     component Mappings is required, then a copy of the SwitchMap should
+f     be made using AST_COPY.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astSwitchMap constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astSwitchMap_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - Because no checking or casting of arguments is performed
+*     before the function is invoked, the "map1" and "map2" parameters
+*     are of type (void *) and are converted from an ID value to a
+*     pointer and validated within the function itself.
+*     - The variable argument list also prevents this function from
+*     invoking astSwitchMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the conversions between IDs
+*     and pointers on input/output of Objects.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSwitchMap *new;            /* Pointer to new SwitchMap */
+   AstMapping *fsmap;            /* Pointer to fwd selector Mapping */
+   AstMapping *ismap;            /* Pointer to inv selector Mapping */
+   AstMapping **routemaps;       /* Array of route Mapping pointers */
+   int i;                        /* Route Mappings index */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Report an error if no route Mappings have been supplied. */
+   if( nroute <= 0 ) astError( AST__BDPAR, "astSwitchMap(SwitchMap): "
+                               " Bad number of route Mappings (%d) specified.", status,
+                               nroute );
+
+/* Otherwise create an array to hold the route Mapping pointers. */
+   routemaps = astMalloc( sizeof( AstMapping * )*nroute );
+
+/* Obtain and validate pointers to the Mapping structures provided. */
+   if( astOK ) {
+      fsmap = fsmap_void ? astCheckMapping( astMakePointer(fsmap_void) ) : NULL;
+      ismap = ismap_void ? astCheckMapping( astMakePointer(ismap_void) ) : NULL;
+      for( i = 0; i < nroute; i++ ) {
+         routemaps[ i ] = astVerifyMapping( astMakePointer(routemaps_void[ i ]) );
+      }
+   }
+
+   if ( astOK ) {
+
+/* Initialise the SwitchMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitSwitchMap( NULL, sizeof( AstSwitchMap ), !class_init, &class_vtab,
+                              "SwitchMap", fsmap, ismap, nroute, routemaps );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new SwitchMap's
+   attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Free memory used to hold the route Mapping pointers. */
+   routemaps = astFree( routemaps );
+
+/* Return an ID value for the new SwitchMap. */
+   return astMakeId( new );
+}
+
+AstSwitchMap *astInitSwitchMap_( void *mem, size_t size, int init,
+                                 AstSwitchMapVtab *vtab, const char *name,
+                                 AstMapping *fsmap, AstMapping *ismap,
+                                 int nroute, AstMapping **routemaps, int *status ) {
+/*
+*+
+*  Name:
+*     astInitSwitchMap
+
+*  Purpose:
+*     Initialise a SwitchMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     AstSwitchMap *astInitSwitchMap( void *mem, size_t size, int init,
+*                                     AstSwitchMapVtab *vtab, const char *name,
+*                                     AstMapping *fsmap, AstMapping *ismap,
+*                                     int nroute, AstMapping **routemaps )
+
+*  Class Membership:
+*     SwitchMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new SwitchMap object. It allocates memory (if necessary) to
+*     accommodate the SwitchMap plus any additional data associated with the
+*     derived class. It then initialises a SwitchMap structure at the start
+*     of this memory. If the "init" flag is set, it also initialises the
+*     contents of a virtual function table for a SwitchMap at the start of
+*     the memory passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the SwitchMap is to be initialised.
+*        This must be of sufficient size to accommodate the SwitchMap data
+*        (sizeof(SwitchMap)) plus any data used by the derived class. If a
+*        value of NULL is given, this function will allocate the memory itself
+*        using the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the SwitchMap (plus derived class
+*        data). This will be used to allocate memory if a value of NULL is
+*        given for the "mem" parameter. This value is also stored in the
+*        SwitchMap structure, so a valid value must be supplied even if not
+*        required for allocating memory.
+*     init
+*        A logical flag indicating if the SwitchMap's virtual function table
+*        is to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new SwitchMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     fsmap
+*        Pointer to the forward selector Mapping.
+*     ismap
+*        Pointer to the inverse selector Mapping.
+*     nroute
+*        The number of route Mappings supplied.
+*     routemaps
+*        An array holdiong pointers to the route Mappings.
+
+*  Returned Value:
+*     A pointer to the new SwitchMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstSwitchMap *new;            /* Pointer to new SwitchMap */
+   int i;                        /* Loop count */
+   int nin;                      /* No. input coordinates for SwitchMap */
+   int nout;                     /* No. output coordinates for SwitchMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitSwitchMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check that all route Mappings have common values for Nin and Nout.*/
+   nin = astGetNin( routemaps[ 0 ] );
+   nout = astGetNout( routemaps[ 0 ] );
+   for( i = 1; i < nroute; i++ ) {
+      if( nin != astGetNin( routemaps[ i ] ) ){
+         if( astOK ) {
+            astError( AST__BADNI, "astInitSwitchMap(%s): Route Mapping "
+                      "number %d has %d input(s) but the first route "
+                      "Mapping has %d input(s).", status, name, i + 1,
+                      astGetNin( routemaps[ i ] ), nin );
+         }
+
+      } else if( nout != astGetNout( routemaps[ i ] ) ){
+         if( astOK ) {
+            astError( AST__BADNO, "astInitSwitchMap(%s): Route Mapping "
+                      "number %d has %d output(s) but the first route "
+                      "Mapping has %d output(s).", status, name, i + 1,
+                      astGetNin( routemaps[ i ] ), nin );
+         }
+      }
+   }
+
+/* If supplied, report an error if fsmap has no forward transformation,
+   or if it has an incorrect number of inputs or output. */
+   if( fsmap && astOK ) {
+      if( !astGetTranForward( fsmap ) ) {
+         astError( AST__INTRD, "astInitSwitchMap(%s): The forward selector Mapping "
+              "is not able to transform coordinates in the forward direction.", status,
+              name );
+
+      } else if( astGetNin( fsmap ) != nin ){
+         astError( AST__BADNI, "astInitSwitchMap(%s): The forward selector "
+                   "Mapping has %d input(s) but the SwitchMap has %d "
+                   "input(s).", status, name, astGetNin( fsmap ), nin );
+
+      } else if( astGetNout( fsmap ) != 1 ){
+         astError( AST__BADNO, "astInitSwitchMap(%s): The forward selector "
+                   "Mapping has %d outputs but should only have 1.", status, name,
+                   astGetNout( fsmap ) );
+      }
+   }
+
+/* If supplied, report an error if ismap has no inverse transformation,
+   or if it has an incorrect number of inputs or outputs. */
+   if( ismap && astOK ) {
+      if( !astGetTranInverse( ismap ) ) {
+         astError( AST__INTRD, "astInitSwitchMap(%s): The inverse selector Mapping "
+              "is not able to transform coordinates in the inverse direction.", status,
+              name );
+      } else if( nout != astGetNout( ismap ) ){
+         astError( AST__BADNO, "astInitSwitchMap(%s): The inverse selector "
+                   "Mapping has %d output(s) but the SwitchMap has %d "
+                   "output(s).", status, name, astGetNout( ismap ), nout );
+
+      } else if( astGetNin( ismap ) != 1 ){
+         astError( AST__BADNI, "astInitSwitchMap(%s): The inverse selector "
+                   "Mapping has %d inputs but should only have 1.", status, name,
+                   astGetNin( ismap ) );
+
+      }
+   }
+
+/* Report an error if neither ismap nor fsmap were supplied. */
+   if( !fsmap && !ismap && astOK ) {
+      astError( AST__INTRD, "astInitSwitchMap(%s): No selector Mappings "
+                "supplied.", status, name );
+   }
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the SwitchMap structure, allocating memory if necessary. Specify
+   the number of input and output coordinates and in which directions the
+   Mapping should be defined. */
+   if ( astOK ) {
+      new = (AstSwitchMap *) astInitMapping( mem, size, 0,
+                                             (AstMappingVtab *) vtab, name,
+                                             nin, nout,
+                                             ( fsmap != NULL ),
+                                             ( ismap != NULL ) );
+      if ( astOK ) {
+
+/* Initialise the SwitchMap data. */
+/* --------------------------- */
+/* Store pointers to the selector Mappings. */
+         new->fsmap = fsmap ? astClone( fsmap ) : NULL;
+         new->ismap = ismap ? astClone( ismap ) : NULL;
+
+/* Save the initial values of the inversion flags for these Mappings. */
+         new->fsinv = fsmap ? astGetInvert( fsmap ) : 0;
+         new->isinv = ismap ? astGetInvert( ismap ) : 0;
+
+/* Create arrays for the route Mappings. */
+         new->routemap = astMalloc( sizeof( AstMapping * )*nroute );
+         new->routeinv = astMalloc( sizeof( int )*nroute );
+
+/* Store pointers to the route Mappings and their invert flags. */
+         if( astOK ) {
+            new->nroute = nroute;
+            for( i = 0; i < nroute; i++ ) {
+               new->routemap[ i ] = astClone( routemaps[ i ] );
+               new->routeinv[ i ] = astGetInvert( routemaps[ i ] );
+            }
+         } else {
+            new->nroute = 0;
+         }
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstSwitchMap *astLoadSwitchMap_( void *mem, size_t size,
+                                 AstSwitchMapVtab *vtab, const char *name,
+                                 AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadSwitchMap
+
+*  Purpose:
+*     Load a SwitchMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "switchmap.h"
+*     AstSwitchMap *astLoadSwitchMap( void *mem, size_t size,
+*                                     AstSwitchMapVtab *vtab, const char *name,
+*                                     AstChannel *channel )
+
+*  Class Membership:
+*     SwitchMap loader.
+
+*  Description:
+*     This function is provided to load a new SwitchMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     SwitchMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a SwitchMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the SwitchMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        SwitchMap data (sizeof(SwitchMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the SwitchMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the SwitchMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstSwitchMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new SwitchMap. If this is NULL, a pointer to
+*        the (static) virtual function table for the SwitchMap class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "SwitchMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new SwitchMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstSwitchMap *new;
+   AstMapping *rmap;
+   int i;
+   char buf[ 20 ];
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this SwitchMap. In this case the
+   SwitchMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstSwitchMap );
+      vtab = &class_vtab;
+      name = "SwitchMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitSwitchMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built SwitchMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "SwitchMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Forward Selector Mapping and its Invert flag. */
+/* --------------------------------------------- */
+      new->fsmap = astReadObject( channel, "fsmap", NULL );
+      new->fsinv = astReadInt( channel, "fsinv", 0 );
+      new->fsinv = ( new->fsinv != 0 );
+
+/* Inverse Selector Mapping and its Invert flag. */
+/* --------------------------------------------- */
+      new->ismap = astReadObject( channel, "ismap", NULL );
+      new->isinv = astReadInt( channel, "isinv", new->fsinv );
+      new->isinv = ( new->isinv != 0 );
+
+/* Loop to load each route Mapping and its invert flag. */
+/* ---------------------------------------------------- */
+      new->routemap = NULL;
+      new->routeinv = NULL;
+      i = 0;
+      while( astOK ) {
+         sprintf( buf, "rmap%d", i + 1 );
+         rmap = astReadObject( channel, buf, NULL );
+         if( rmap ) {
+            new->routemap = astGrow( new->routemap, i + 1, sizeof( AstMapping *) );
+            new->routeinv = astGrow( new->routeinv, i + 1, sizeof( int ) );
+            if( astOK ) {
+               new->routemap[ i ] = rmap;
+               sprintf( buf, "rinv%d", i + 1 );
+               new->routeinv[ i ] = astReadInt( channel, buf, 0 );
+               new->routeinv[ i ] = ( new->routeinv[ i ] != 0 );
+               i++;
+            }
+         } else {
+            break;
+         }
+      }
+
+/* Number of route Mappings. */
+      new->nroute = i;
+
+/* If an error occurred, clean up by deleting the new SwitchMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new SwitchMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+/* None. */
+
+
+
+
diff --git a/ast/switchmap.h b/ast/switchmap.h
new file mode 100644
index 0000000..40c60a9
--- /dev/null
+++ b/ast/switchmap.h
@@ -0,0 +1,289 @@
+#if !defined( SWITCHMAP_INCLUDED )  /* Include this file only once */
+#define SWITCHMAP_INCLUDED
+/*
+*+
+*  Name:
+*     switchmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the SwitchMap class.
+
+*  Invocation:
+*     #include "switchmap.h"
+
+*  Description:
+*     This include file defines the interface to the SwitchMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc. needed to use this class.
+
+*  Inheritance:
+*     The SwitchMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astMapMerge
+*           Merge a SwitchMap within a sequence of Mappings.
+*        astTransform
+*           Transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsASwitchMap
+*           Test class membership.
+*        astSwitchMap
+*           Create a SwitchMap.
+*
+*     Protected:
+*        astCheckSwitchMap
+*           Validate class membership.
+*        astInitSwitchMap
+*           Initialise a SwitchMap.
+*        astInitSwitchMapVtab
+*           Initialise the virtual function table for the SwitchMap class.
+*        astLoadSwitchMap
+*           Load a SwitchMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstSwitchMap
+*           SwitchMap object type.
+*
+*     Protected:
+*        AstSwitchMapVtab
+*           SwitchMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     13-MAR-2006 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* SwitchMap structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstSwitchMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;       /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstMapping *fsmap;       /* Pointer to forward selector Mapping */
+   AstMapping *ismap;       /* Pointer to inverse selector Mapping */
+   int fsinv;               /* Inversion flag for forward selector Mapping */
+   int isinv;               /* Inversion flag for inverse selector Mapping */
+   int nroute;              /* The number of route Mappings in the SwitchMap */
+   AstMapping **routemap;   /* Array of route Mapping pointers */
+   int *routeinv;           /* Array of inversion flags for route Mappings */
+} AstSwitchMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstSwitchMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+/* None. */
+} AstSwitchMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstSwitchMapGlobals {
+   AstSwitchMapVtab Class_Vtab;
+   int Class_Init;
+} AstSwitchMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitSwitchMapGlobals_( AstSwitchMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(SwitchMap)           /* Check class membership */
+astPROTO_ISA(SwitchMap)             /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstSwitchMap *astSwitchMap_( void *, void *, int, void **, const char *, int *, ...);
+#else
+AstSwitchMap *astSwitchMapId_( void *, void *, int, void **, const char *, ... )__attribute__((format(printf,5,6)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstSwitchMap *astInitSwitchMap_( void *, size_t, int, AstSwitchMapVtab *,
+                                 const char *, AstMapping *, AstMapping *,
+                                 int, AstMapping **, int * );
+
+/* Vtab initialiser. */
+void astInitSwitchMapVtab_( AstSwitchMapVtab *, const char *, int * );
+
+/* Loader. */
+AstSwitchMap *astLoadSwitchMap_( void *, size_t, AstSwitchMapVtab *,
+                                 const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+#if defined(astCLASS)            /* Protected */
+
+int astSwitchList_( AstSwitchMap *, int, int *, AstMapping ***, int **, int * );
+
+#endif
+
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckSwitchMap(this) astINVOKE_CHECK(SwitchMap,this,0)
+#define astVerifySwitchMap(this) astINVOKE_CHECK(SwitchMap,this,1)
+
+/* Test class membership. */
+#define astIsASwitchMap(this) astINVOKE_ISA(SwitchMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astSwitchMap astINVOKE(F,astSwitchMap_)
+#else
+#define astSwitchMap astINVOKE(F,astSwitchMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitSwitchMap(mem,size,init,vtab,name,fsmap,ismap,nroute,routemaps) \
+astINVOKE(O,astInitSwitchMap_(mem,size,init,vtab,name,astCheckMapping(fsmap),\
+          astCheckMapping(ismap),nroute,routemaps,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitSwitchMapVtab(vtab,name) astINVOKE(V,astInitSwitchMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadSwitchMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadSwitchMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+
+#define astSwitchList astSwitchList_
+
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckSwitchMap to validate SwitchMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+/* None. */
+#endif
+
+
+
+
+
diff --git a/ast/table.c b/ast/table.c
new file mode 100644
index 0000000..0d283bc
--- /dev/null
+++ b/ast/table.c
@@ -0,0 +1,5246 @@
+/*
+*class++
+*  Name:
+*     Table
+
+*  Purpose:
+*     A 2-dimensional table of values.
+
+*  Constructor Function:
+c     astTable
+f     AST_TABLE
+
+*  Description:
+*     The Table class is a type of KeyMap that represents a two-dimensional
+*     table of values. The
+c     astMapGet... and astMapPut...
+f     AST_MAPGET... and AST_MAPPUT...
+*     methods provided by the KeyMap class should be used for storing and
+*     retrieving values from individual cells within a Table. Each entry
+*     in the KeyMap represents a single cell of the table and has an
+*     associated key of the form "<COL>(i)" where "<COL>" is the
+*     upper-case name of a table column and "i" is the row index (the
+*     first row is row 1). Keys of this form should always be used when
+*     using KeyMap methods to access entries within a Table.
+*
+*     Columns must be declared using the
+c     astAddColumn
+f     AST_ADDCOLUMN
+*     method before values can be stored within them. This also fixes the
+*     type and shape of the values that may be stored in any cell of the
+*     column. Cells may contain scalar or vector values of any data type
+*     supported by the KeyMap class. Multi-dimensional arrays may also be
+*     stored, but these must be vectorised when storing and retrieving
+*     them within a table cell. All cells within a single column must
+*     have the same type and shape, as specified when the column is added
+*     to the Table.
+*
+*     Tables may have parameters that describe global properties of the
+*     entire table. These are stored as entries in the parent KeyMap and
+*     can be access using the get and set method of the KeyMap class.
+*     However, parameters must be declared using the
+c     astAddParameter
+f     AST_ADDPARAMETER
+*     method before being accessed.
+*
+*     Note - since accessing entries within a KeyMap is a relatively slow
+*     process, it is not recommended to use the Table class to store
+*     very large tables.
+
+*  Inheritance:
+*     The Table class inherits from the KeyMap class.
+
+*  Attributes:
+*     In addition to those attributes common to all KeyMaps, every
+*     Table also has the following attributes:
+*
+*     - ColumnLenC(column): The largest string length of any value in a column
+*     - ColumnLength(column): The number of elements in each value in a column
+*     - ColumnNdim(column): The number of axes spanned by each value in a column
+*     - ColumnType(column): The data type of each value in a column
+*     - ColumnUnit(column): The unit string describing each value in a column
+*     - Ncolumn: The number of columns currently in the Table
+*     - Nrow: The number of rows currently in the Table
+*     - Nparameter: The number of global parameters currently in the Table
+
+*  Functions:
+c     In addition to those functions applicable to all KeyMaps, the
+c     following functions may also be applied to all Tables:
+f     In addition to those routines applicable to all KeyMaps, the
+f     following routines may also be applied to all Tables:
+*
+c     - astAddColumn: Add a new column definition to a Table
+c     - astAddParameter: Add a new global parameter definition to a Table
+c     - astColumnName: Return the name of the column with a given index
+c     - astColumnShape: Return the shape of the values in a named column
+c     - astHasColumn: Checks if a column exists in a Table
+c     - astHasParameter: Checks if a global parameter exists in a Table
+c     - astParameterName: Return the name of the parameter with a given index
+c     - astPurgeRows: Remove all empty rows from a Table
+c     - astRemoveColumn: Remove a column from a Table
+c     - astRemoveParameter: Remove a global parameter from a Table
+c     - astRemoveRow: Remove a row from a Table
+f     - AST_ADDCOLUMN: Add a new column definition to a Table
+f     - AST_ADDPARAMETER: Add a new global parameter definition to a Table
+f     - AST_COLUMNNAME: Return the name of the column with a given index
+f     - AST_COLUMNSHAPE: Return the shape of the values in a named column
+f     - AST_HASCOLUMN: Checks if a column exists in a Table
+f     - AST_HASPARAMETER: Checks if a global parameter exists in a Table
+f     - AST_PARAMETERNAME: Return the name of the parameter with a given index
+f     - AST_PURGEROWS: Remove all empty rows from a Table
+f     - AST_REMOVECOLUMN: Remove a column from a Table
+f     - AST_REMOVEPARAMETER: Remove a global parameter from a Table
+f     - AST_REMOVEROW: Remove a row from a Table
+
+*  Copyright:
+*     Copyright (C) 2010-2011 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-NOV-2010 (DSB):
+*        Original version.
+*     13-MAY-2011 (DSB):
+*        Added support for table parameters.
+*     16-NOV-2013 (DSB):
+*        Fix bug in forming keys in GetColumnLenC.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS Table
+
+/* Fixed KeyMap entry names */
+#define LENGTH "Length"
+#define NAME "Name"
+#define NROW "Nrow"
+#define SHAPE "Shape"
+#define SIZE "Size"
+#define TYPE "Type"
+#define UNIT "Unit"
+
+/* A function macro that puts quotes around a value */
+#define STRING(w) #w
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "keymap.h"              /* Coordinate keymaps (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "table.h"               /* Interface definition for this class */
+
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static void (* parent_setkeycase)( AstKeyMap *, int, int * );
+static void (* parent_clearkeycase)( AstKeyMap *, int * );
+static int (* parent_equal)( AstObject *, AstObject *, int * );
+static int (* parent_getobjsize)( AstObject *, int * );
+static int (* parent_mapget0a)( AstKeyMap *, const char *, AstObject * *, int *);
+static int (* parent_mapget0c)( AstKeyMap *, const char *, const char **, int *);
+static int (* parent_mapget0d)( AstKeyMap *, const char *, double *, int *);
+static int (* parent_mapget0f)( AstKeyMap *, const char *, float *, int *);
+static int (* parent_mapget0i)( AstKeyMap *, const char *, int *, int *);
+static int (* parent_mapget0p)( AstKeyMap *, const char *, void **, int *);
+static int (* parent_mapget0b)( AstKeyMap *, const char *, unsigned char *, int *);
+static int (* parent_mapget0s)( AstKeyMap *, const char *, short int *, int *);
+static int (* parent_mapget1a)( AstKeyMap *, const char *, int, int *, AstObject **, int * );
+static int (* parent_mapget1c)( AstKeyMap *, const char *, int, int, int *, char *, int * );
+static int (* parent_mapget1d)( AstKeyMap *, const char *, int, int *, double *, int * );
+static int (* parent_mapget1f)( AstKeyMap *, const char *, int, int *, float *, int * );
+static int (* parent_mapget1i)( AstKeyMap *, const char *, int, int *, int *, int * );
+static int (* parent_mapget1p)( AstKeyMap *, const char *, int, int *, void **, int * );
+static int (* parent_mapget1s)( AstKeyMap *, const char *, int, int *, short int *, int * );
+static int (* parent_mapget1b)( AstKeyMap *, const char *, int, int *, unsigned char *, int * );
+static int (* parent_mapgetelema)( AstKeyMap *, const char *, int, AstObject **, int * );
+static int (* parent_mapgetelemc)( AstKeyMap *, const char *, int, int, char *, int * );
+static int (* parent_mapgetelemd)( AstKeyMap *, const char *, int, double *, int * );
+static int (* parent_mapgetelemf)( AstKeyMap *, const char *, int, float *, int * );
+static int (* parent_mapgetelemi)( AstKeyMap *, const char *, int, int *, int * );
+static int (* parent_mapgetelemp)( AstKeyMap *, const char *, int, void **, int * );
+static int (* parent_mapgetelems)( AstKeyMap *, const char *, int, short int *, int * );
+static int (* parent_mapgetelemb)( AstKeyMap *, const char *, int, unsigned char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_mapput0a)( AstKeyMap *, const char *, AstObject *, const char *, int *);
+static void (* parent_mapput0c)( AstKeyMap *, const char *, const char *, const char *, int *);
+static void (* parent_mapput0d)( AstKeyMap *, const char *, double, const char *, int *);
+static void (* parent_mapput0f)( AstKeyMap *, const char *, float, const char *, int *);
+static void (* parent_mapput0i)( AstKeyMap *, const char *, int, const char *, int *);
+static void (* parent_mapput0p)( AstKeyMap *, const char *, void *, const char *, int *);
+static void (* parent_mapput0b)( AstKeyMap *, const char *, unsigned char, const char *, int *);
+static void (* parent_mapput0s)( AstKeyMap *, const char *, short int, const char *, int *);
+static void (* parent_mapput1a)( AstKeyMap *, const char *, int, AstObject *const [], const char *, int * );
+static void (* parent_mapput1c)( AstKeyMap *, const char *, int, const char *const [], const char *, int * );
+static void (* parent_mapput1d)( AstKeyMap *, const char *, int, const double *, const char *, int * );
+static void (* parent_mapput1f)( AstKeyMap *, const char *, int, const float *, const char *, int * );
+static void (* parent_mapput1i)( AstKeyMap *, const char *, int, const int *, const char *, int * );
+static void (* parent_mapput1p)( AstKeyMap *, const char *, int, void *const [], const char *, int * );
+static void (* parent_mapput1b)( AstKeyMap *, const char *, int, const unsigned char *, const char *, int * );
+static void (* parent_mapput1s)( AstKeyMap *, const char *, int, const short int *, const char *, int * );
+static void (* parent_mapputelema)( AstKeyMap *, const char *, int, AstObject *, int * );
+static void (* parent_mapputelemc)( AstKeyMap *, const char *, int, const char *, int * );
+static void (* parent_mapputelemd)( AstKeyMap *, const char *, int, double, int * );
+static void (* parent_mapputelemf)( AstKeyMap *, const char *, int, float, int * );
+static void (* parent_mapputelemi)( AstKeyMap *, const char *, int, int, int * );
+static void (* parent_mapputelemp)( AstKeyMap *, const char *, int, void *, int * );
+static void (* parent_mapputelemb)( AstKeyMap *, const char *, int, unsigned char, int * );
+static void (* parent_mapputelems)( AstKeyMap *, const char *, int, short int, int * );
+static void (* parent_mapremove)( AstKeyMap *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static void (* parent_mapputu)( AstKeyMap *, const char *, const char *, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(Table)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(Table,Class_Init)
+#define class_vtab astGLOBAL(Table,Class_Vtab)
+#define getattrib_buff astGLOBAL(Table,GetAttrib_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 101 ];
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstTableVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstTable *astTableId_( const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstKeyMap *ColumnProps( AstTable *, int * );
+static AstKeyMap *ParameterProps( AstTable *, int * );
+static const char *ColumnName( AstTable *, int index, int * );
+static const char *ParameterName( AstTable *, int index, int * );
+static const char *GetColumnUnit( AstTable *, const char *, int * );
+static const char *TypeString( int );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetColumnLenC( AstTable *, const char *, int * );
+static int GetColumnLength( AstTable *, const char *, int * );
+static int GetColumnNdim( AstTable *, const char *, int * );
+static int GetColumnType( AstTable *, const char *, int * );
+static int GetNcolumn( AstTable *, int * );
+static int GetNparameter( AstTable *, int * );
+static int GetObjSize( AstObject *, int * );
+static int HasColumn( AstTable *, const char *, int *);
+static int HasParameter( AstTable *, const char *, int *);
+static int MapGet0A( AstKeyMap *, const char *, AstObject **, int * );
+static int MapGet0B( AstKeyMap *, const char *, unsigned char *, int * );
+static int MapGet0C( AstKeyMap *, const char *, const char **, int * );
+static int MapGet0D( AstKeyMap *, const char *, double *, int * );
+static int MapGet0F( AstKeyMap *, const char *, float *, int * );
+static int MapGet0I( AstKeyMap *, const char *, int *, int * );
+static int MapGet0P( AstKeyMap *, const char *, void **, int * );
+static int MapGet0S( AstKeyMap *, const char *, short int *, int * );
+static int MapGet1A( AstKeyMap *, const char *, int, int *, AstObject **, int * );
+static int MapGet1B( AstKeyMap *, const char *, int, int *, unsigned char *, int * );
+static int MapGet1C( AstKeyMap *, const char *, int, int, int *, char *, int * );
+static int MapGet1D( AstKeyMap *, const char *, int, int *, double *, int * );
+static int MapGet1F( AstKeyMap *, const char *, int, int *, float *, int * );
+static int MapGet1I( AstKeyMap *, const char *, int, int *, int *, int * );
+static int MapGet1P( AstKeyMap *, const char *, int, int *, void **, int * );
+static int MapGet1S( AstKeyMap *, const char *, int, int *, short int *, int * );
+static int MapGetElemA( AstKeyMap *, const char *, int, AstObject **, int * );
+static int MapGetElemB( AstKeyMap *, const char *, int, unsigned char *, int * );
+static int MapGetElemC( AstKeyMap *, const char *, int, int, char *, int * );
+static int MapGetElemD( AstKeyMap *, const char *, int, double *, int * );
+static int MapGetElemF( AstKeyMap *, const char *, int, float *, int * );
+static int MapGetElemI( AstKeyMap *, const char *, int, int *, int * );
+static int MapGetElemP( AstKeyMap *, const char *, int, void **, int * );
+static int MapGetElemS( AstKeyMap *, const char *, int, short int *, int * );
+static int ParseKey( AstTable *, const char *, int, char *, int *, AstKeyMap **, const char *, int * );
+static void AddColumn( AstTable *, const char *, int, int, int *, const char *, int * );
+static void AddParameter( AstTable *, const char *, int * );
+static void ColumnShape( AstTable *, const char *, int, int *, int *, int *);
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void MapPut0A( AstKeyMap *, const char *, AstObject *, const char *, int * );
+static void MapPut0B( AstKeyMap *, const char *, unsigned char, const char *, int * );
+static void MapPut0C( AstKeyMap *, const char *, const char *, const char *, int * );
+static void MapPut0D( AstKeyMap *, const char *, double, const char *, int * );
+static void MapPut0F( AstKeyMap *, const char *, float, const char *, int * );
+static void MapPut0I( AstKeyMap *, const char *, int, const char *, int * );
+static void MapPut0P( AstKeyMap *, const char *, void *, const char *, int * );
+static void MapPut0S( AstKeyMap *, const char *, short int, const char *, int * );
+static void MapPut1A( AstKeyMap *, const char *, int, AstObject *const [], const char *, int * );
+static void MapPut1B( AstKeyMap *, const char *, int, const unsigned char *, const char *, int * );
+static void MapPut1C( AstKeyMap *, const char *, int, const char *const [], const char *, int * );
+static void MapPut1D( AstKeyMap *, const char *, int, const double *, const char *, int * );
+static void MapPut1F( AstKeyMap *, const char *, int, const float *, const char *, int * );
+static void MapPut1I( AstKeyMap *, const char *, int, const int *, const char *, int * );
+static void MapPut1P( AstKeyMap *, const char *, int, void *const [], const char *, int * );
+static void MapPut1S( AstKeyMap *, const char *, int, const short int *, const char *, int * );
+static void MapPutElemA( AstKeyMap *, const char *, int, AstObject *, int * );
+static void MapPutElemB( AstKeyMap *, const char *, int, unsigned char, int * );
+static void MapPutElemC( AstKeyMap *, const char *, int, const char *, int * );
+static void MapPutElemD( AstKeyMap *, const char *, int, double, int * );
+static void MapPutElemF( AstKeyMap *, const char *, int, float, int * );
+static void MapPutElemI( AstKeyMap *, const char *, int, int, int * );
+static void MapPutElemP( AstKeyMap *, const char *, int, void *, int * );
+static void MapPutElemS( AstKeyMap *, const char *, int, short int, int * );
+static void MapPutU( AstKeyMap *, const char *, const char *, int * );
+static void PurgeRows( AstTable *, int * );
+static void RemoveColumn( AstTable *, const char *, int * );
+static void RemoveParameter( AstTable *, const char *, int * );
+static void RemoveRow( AstTable *, int, int * );
+static void SetKeyCase( AstKeyMap *, int, int * );
+static void ClearKeyCase( AstKeyMap *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+static int GetNrow( AstTable *, int * );
+static void SetNrow( AstTable *, int, int * );
+
+static int GetNcolumn( AstTable *, int * );
+static int GetNparameter( AstTable *, int * );
+
+
+/* Member functions. */
+/* ================= */
+static void AddColumn( AstTable *this, const char *name, int type,
+                       int ndim, int *dims, const char *unit, int *status ) {
+/*
+*++
+*  Name:
+c     astAddColumn
+f     AST_ADDCOLUMN
+
+*  Purpose:
+*     Add a new column definition to a table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     void astAddColumn( AstTable *this, const char *name, int type, int ndim,
+c                        int *dims, const char *unit )
+f     CALL AST_ADDCOLUMN( THIS, NAME, TYPE, NDIM, DIMS, UNIT, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     Adds the definition of a new column to the supplied table. Initially,
+*     the column is empty. Values may be added subsequently using the
+*     methods of the KeyMap class.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+*        The column name. Trailing spaces are ignored (all other spaces
+*        are significant). The supplied string is converted to upper case.
+c     type
+f     TYPE = INTEGER (Given)
+*        The data type associated with the column. See "Applicability:"
+*        below.
+c     ndim
+f     NDIM = INTEGER (Given)
+*        The number of dimensions spanned by the values stored in a single
+*        cell of the column. Zero if the column holds scalar values.
+c     dims
+f     DIMS( NDIM ) = INTEGER (Given)
+*        An array holding the the lengths of each of the axes spanned by
+*        the values stored in a single cell of the column. Ignored if the
+*        column holds scalara values.
+c     unit
+f     UNIT = CHARACTER * ( * ) (Given)
+*        A string specifying the units of the column. Supply a blank
+*        string if the column is unitless.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Applicability:
+*     Table
+*        Tables can hold columns with any of the following data types -
+*        AST__INTTYPE (for integer), AST__SINTTYPE (for
+c        short int),
+f        INTEGER*2),
+*        AST__BYTETYPE (for
+c        unsigned bytes - i.e. unsigned chars),
+f        bytes),
+*        AST__DOUBLETYPE (for double
+*        precision floating point), AST__FLOATTYPE (for single
+*        precision floating point), AST__STRINGTYPE (for character string),
+*        AST__OBJECTTYPE (for AST Object pointer), AST__POINTERTYPE (for
+*        arbitrary C pointer) or AST__UNDEFTYPE (for undefined values
+*        created by
+c        astMapPutU).
+f        AST_MAPPUTU).
+*     FitsTable
+*        FitsTables can hold columns with any of the following data types -
+*        AST__INTTYPE (for integer), AST__SINTTYPE (for
+c        short int),
+f        INTEGER*2),
+*        AST__BYTETYPE (for
+c        unsigned bytes - i.e. unsigned chars),
+f        bytes),
+*        AST__DOUBLETYPE (for double
+*        precision floating point), AST__FLOATTYPE (for single
+*        precision floating point), AST__STRINGTYPE (for character string).
+
+*  Notes:
+*     - This
+c     function
+f     routine
+*     returns without action if a column already exists in the Table
+*     with the supplied name and properties. However an error is
+*     reported if any of the properties differ.
+
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;      /* KeyMap holding all column details */
+   AstKeyMap *col_km;    /* KeyMap holding new column details */
+   const char *oldunit;  /* Pointer to the old coumn unit string */
+   int *olddims;         /* Shape of pre-existing column */
+   int idim;             /* Axis index */
+   int namlen;           /* Used length of "name" */
+   int nval;             /* Number of values returned */
+   int oldtype;          /* Data type of pre-existing column */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Verify supplied values. */
+   namlen = astChrLen( name );
+   if( namlen == 0 ) {
+      astError( AST__BADKEY, "astAddColumn(%s): Illegal blank column name "
+               "supplied.", status, astGetClass( this ) );
+
+   } else if( namlen > AST__MXCOLNAMLEN ) {
+      astError( AST__BADKEY, "astAddColumn(%s): Column name '%s' is too "
+               "long (must be no more than %d characters).", status,
+               astGetClass( this ), name, AST__MXCOLNAMLEN );
+
+   } else if( ndim < 0 ) {
+      astError( AST__NAXIN, "astAddColumn(%s): No of axes (%d) for values in "
+               "new column %s is invalid.", status, astGetClass( this ),
+               ndim, name );
+
+   } else if( TypeString( type ) == NULL ) {
+      astError( AST__NAXIN, "astAddColumn(%s): Bad data type supplied (%d) "
+                "for new column %s.", status, astGetClass( this ), type,
+                name );
+
+   } else {
+      for( idim = 0; idim < ndim; idim++ ) {
+         if( dims[ idim ] < 1 ) {
+            astError( AST__DIMIN, "astAddColumn(%s): Length of axis %d (%d) "
+                      "for new column %s is invalid.", status,
+                      astGetClass( this ), idim + 1, dims[ idim ], name );
+            break;
+         }
+      }
+   }
+
+/* If there is already a column with the given name, check its properties
+   match the supplied properties. */
+   if( astOK ) {
+      cols = astColumnProps( this );
+      if( astMapGet0A( cols, name, &col_km ) ) {
+
+         astMapGet0I( col_km, TYPE, &oldtype );
+         if( oldtype != type && astOK ) {
+            astError( AST__OLDCOL, "astAddColumn(%s): A column called "
+                      "%s already exists in the table with a different "
+                      "data type (%s).", status, astGetClass( this ),
+                      name, TypeString( oldtype ) );
+         }
+
+         if( !astMapGet0C( col_km, UNIT, &oldunit ) ) oldunit = "";
+         if( strcmp( oldunit, unit ) && astOK ) {
+            astError( AST__OLDCOL, "astAddColumn(%s): A column called "
+                      "%s already exists in the table with a different "
+                      "unit string ('%s').", status, astGetClass( this ),
+                      name, oldunit );
+         }
+
+         if( ndim != astMapLength( col_km, SHAPE ) && astOK ) {
+            astError( AST__OLDCOL, "astAddColumn(%s): A column called "
+                      "%s already exists in the table with a different "
+                      "number of axes (%d).", status, astGetClass( this ),
+                      name, astMapLength( col_km, SHAPE ) );
+         }
+
+         if( ndim > 0 && astOK ) {
+            olddims = astMalloc( sizeof( int )*ndim );
+            (void) astMapGet1I( col_km, SHAPE, ndim, &nval, olddims );
+            for( idim = 0; idim < ndim && astOK; idim++ ) {
+               if( dims[ idim ] != olddims[ idim ] ) {
+                  astError( AST__OLDCOL, "astAddColumn(%s): A column called "
+                            "%s already exists in the table with a different "
+                            "shape.", status, astGetClass( this ), name );
+               }
+            }
+            olddims = astFree( olddims );
+         }
+
+/* Otherwise, add a new column to the table. */
+      } else {
+
+/* Add a suitable entry describing the column to the Columns KeyMap. */
+         col_km = astKeyMap( " ", status );
+         astMapPut0C( col_km, NAME, name, NULL );
+         astMapPut0I( col_km, TYPE, type, NULL );
+         if( ndim ) astMapPut1I( col_km, SHAPE, ndim, dims, NULL );
+         astMapPut0C( col_km, UNIT, unit, NULL );
+
+/* Put the column KeyMap into the KeyMap holding details of all columns.
+   Use the column name as the key. */
+         astMapPut0A( cols, name, col_km, NULL );
+      }
+
+/* Annul the local KeyMap pointers. */
+      col_km = astAnnul( col_km );
+      cols = astAnnul( cols );
+   }
+}
+
+static void AddParameter( AstTable *this, const char *name, int *status ) {
+/*
+*++
+*  Name:
+c     astAddParameter
+f     AST_ADDPARAMETER
+
+*  Purpose:
+*     Add a new global parameter definition to a table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     void astAddParameter( AstTable *this, const char *name )
+f     CALL AST_ADDPARAMETER( THIS, NAME, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     Adds the definition of a new global parameter to the supplied
+*     table. Note, this does not store a value for the parameter. To get
+*     or set the parameter value, the methods of the paremt KeyMap class
+*     should be used, using the name of the parameter as the key.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+*        The parameter name. Trailing spaces are ignored (all other spaces
+*        are significant). The supplied string is converted to upper case.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - Unlike columns, the definition of a parameter does not specify its type,
+*     size or dimensionality.
+
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *pars;      /* KeyMap holding all parameter details */
+   int namlen;           /* Used length of "name" */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Verify supplied values. */
+   namlen = astChrLen( name );
+   if( namlen == 0 ) {
+      astError( AST__BADKEY, "astAddParameter(%s): Illegal blank parameter name "
+               "supplied.", status, astGetClass( this ) );
+
+   } else if( namlen > AST__MXCOLNAMLEN ) {
+      astError( AST__BADKEY, "astAddParameter(%s): Parameter name '%s' is too "
+               "long (must be no more than %d characters).", status,
+               astGetClass( this ), name, AST__MXCOLNAMLEN );
+   }
+
+/* Do nothing if there is already a parameter with the given name. */
+   if( astOK ) {
+      pars = astParameterProps( this );
+      if( !astMapHasKey( pars, name ) ) {
+
+/* Add a suitable entry to the Parameters KeyMap. The value is arbitrary
+   and currently unused. */
+         astMapPut0I( pars, name, 1, NULL );
+      }
+
+/* Annul the local KeyMap pointer. */
+      pars = astAnnul( pars );
+   }
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     void ClearAttrib( AstObject *this, const char *attrib )
+
+*  Class Membership:
+*     Table member function (over-rides the astClearAttrib protected
+*     method inherited from the KeyMap class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     Table, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*/
+
+/* Local Variables: */
+   AstTable *this;
+   int nc;
+   int len;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Table structure. */
+   this = (AstTable *) this_object;
+
+/* Get the length of the attribute string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+   /* None yet */
+
+/* Define a macro to see if the attribute string matches any of the
+   read-only column attributes of this class. */
+#define MATCH(attr) \
+        ( nc = 0, ( 0 == astSscanf( attrib, attr "(%*s)%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* If the name was not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then report an
+   error. */
+   if ( !strcmp( attrib, "nrow" ) ||
+        !strcmp( attrib, "ncolumn" ) ||
+        !strcmp( attrib, "nparameter" ) ||
+        MATCH( "columnlenc" ) ||
+        MATCH( "columnlength" ) ||
+        MATCH( "columnndim" ) ||
+        MATCH( "columntype" ) ||
+        MATCH( "columnunit" ) ) {
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+
+#undef MATCH
+
+}
+
+static void ClearKeyCase( AstKeyMap *this, int *status ) {
+/*
+*  Name:
+*     ClearKeyCase
+
+*  Purpose:
+*     Clear the KeyCase attribute value for a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymape.h"
+*     void ClearKeyCase( AstKeyMap *this, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astClearKeyCase protected
+*     method inherited from the KeyMap class).
+
+*  Description:
+*     This function clears the value of the KeyCase attribute for a
+*     Table. For a Table, the KeyCase attribute cannot be changed so this
+*     function exits without action.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*/
+
+}
+
+static const char *ColumnName( AstTable *this, int index, int *status ) {
+/*
+*++
+*  Name:
+c     astColumnName
+f     AST_COLUMNNAME
+
+*  Purpose:
+*     Get the name of the column at a given index within the Table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     const char *astColumnName( AstTable *this, int index )
+f     RESULT = AST_COLUMNNAME( THIS, INDEX, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function returns a string holding the name of the column with
+*     the given index within the Table.
+*
+*     This function is intended primarily as a means of iterating round all
+*     the columns in a Table. For this purpose, the number of columns in
+*     the Table is given by the Ncolumn attribute of the Table. This function
+*     could then be called in a loop, with the index value going from
+c     zero to one less than Ncolumn.
+f     one to Ncolumn.
+*
+*     Note, the index associated with a column decreases monotonically with
+*     the age of the column: the oldest Column in the Table will have index
+*     one, and the Column added most recently to the Table will have the
+*     largest index.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     index
+f     INDEX = INTEGER (Given)
+*        The index into the list of columns. The first column has index
+*        one, and the last has index "Ncolumn".
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astColumnName()
+c        A pointer to a null-terminated string containing the
+f     AST_COLUMNNAME = CHARACTER * ( AST__SZCHR )
+f        The
+*        upper case column name.
+
+*  Notes:
+c     - The returned pointer is guaranteed to remain valid and the
+c     string to which it points will not be over-written for a total
+c     of 50 successive invocations of this function. After this, the
+c     memory containing the string may be re-used, so a copy of the
+c     string should be made if it is needed for longer than this.
+c     - A NULL pointer will be returned if this function is invoked
+c     with the AST error status set, or if it should fail for any
+c     reason.
+f     - A blank string will be returned if this function is invoked
+f     with STATUS set to an error value, or if it should fail for any
+f     reason.
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;        /* KeyMap holding column definitions */
+   const char *result;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get apointer to the KeyMap holding all column definitions. */
+   cols = astColumnProps( this );
+
+/* Issue a more useful error message than that issued by astMapKey if the
+   index is invalid. */
+   if( index < 1 || index > astMapSize( cols ) ) {
+      astError( AST__MPIND, "astColumnName(%s): Cannot find column "
+                "%d (zero-based) of the %s - invalid index.", status,
+                astGetClass( this ), index, astGetClass( this ) );
+   }
+
+/* Get the column name. */
+   result = astMapKey( cols, index - 1 );
+
+/* Free resources. */
+   cols = astAnnul( cols );
+
+/* Return a pointer to the required column name. */
+   return result;
+}
+
+static AstKeyMap *ColumnProps( AstTable *this, int *status ) {
+/*
+*+
+*  Name:
+*     astColumnProps
+
+*  Purpose:
+*     Returns a pointer to the KeyMap holding column properties.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "table.h"
+*     AstKeyMap *astColumnProps( AstTable *this )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function returns a pointer to the KeyMap that holds
+*     definitions of all the coumns added to the Table.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+
+*  Returned Value:
+*        A pointer to the KeyMap. It shpould be annulled using astAnnul
+*        when no longer needed.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Return a cloned pointer to the required KeyMap. */
+   return astClone( this->columns );
+}
+
+static void ColumnShape( AstTable *this, const char *column, int mxdim,
+                         int *ndim, int *dims, int *status ){
+/*
+*++
+*  Name:
+c     astColumnShape
+f     AST_COLUMNSHAPE
+
+*  Purpose:
+*     Returns the shape of the values in a named column.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     void astColumnShape( AstTable *this, const char *column, int mxdim,
+c                          int *ndim, int *dims )
+f     CALL AST_COLUMNSHAPE( THIS, COLUMN, MXDIM, NDIM, DIMS, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns the number of dimensions spaned by each value in a named
+*     column of a Table, together with the length of each dimension.
+*     These are the values supplied when the column was created using
+c     astAddColumn.
+f     AST_ADDCOLUMN.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     column
+f     COLUMN = CHARACTER * ( * ) (Given)
+*        The character string holding the upper case name of the column. Trailing
+*        spaces are ignored.
+c     mxdim
+f     MXDIM = INTEGER (Given)
+*        The length of the
+c        "dims" array.
+f        DIMS array.
+c     ndim
+f     NDIM = INTEGER (Returned)
+c        Pointer to an int in which to return the
+f        The
+*        number of dimensions spanned by values in the named column.
+*        This will be zero if the column contains scalar values.
+c     dims
+f     DIMS( MXDIM ) = INTEGER (Returned)
+c        Pointer to an
+f        An
+*        array in which to return the length of each dimension. Any
+*        excess trailing elements will be filled with the value 1.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - No error is reported if the requested column cannot be found in the
+*     given Table. A value of zero is returned for
+c     "ndim" and the supplied values in "dims"
+f     NDIM and the supplied values in DIMS
+*     are left unchanged.
+*     - A value of zero is returned for
+c     "ndim"
+f     NDIM
+*     if an error occurs.
+
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;        /* Pointer to KeyMap holding all column info */
+   AstKeyMap *col_km;      /* Pointer to KeyMap holding requested column info */
+   int idim;               /* Axis index */
+
+/* Initialise */
+   *ndim = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return;
+
+
+/* Get the KeyMap holding information about the requested column. */
+   cols = astColumnProps( this );
+   if( astMapGet0A( cols, column, &col_km ) ) {
+
+/* Get the shape of the column values. */
+      (void) astMapGet1I( col_km, SHAPE, mxdim, ndim, dims );
+
+/* Fill excess array elements with 1. */
+      for( idim = *ndim; idim < mxdim; idim++ ) dims[ idim ] = 1;
+
+/* Free resources. */
+      col_km = astAnnul( col_km );
+   }
+   cols = astAnnul( cols );
+
+/* If an error has occurred, set ndim to zero. */
+   if( !astOK ) *ndim = 0;
+
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two Tables are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astEqual protected
+*     method inherited from the astKeyMap class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two Tables are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a Table).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the Tables are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstKeyMap *this_km;
+   AstKeyMap *that_km;
+   AstTable *that;
+   AstTable *this;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two Table structures. */
+   this = (AstTable *) this_object;
+   that = (AstTable *) that_object;
+
+/* Check the second object is a Table. We know the first is a
+   Table since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsATable( that ) ) {
+
+/* Check the Tables are equal when compared as KeyMaps. */
+      if( (*parent_equal)( this_object, that_object, status ) ) {
+
+/* Check the Columns KeyMaps are equal.  */
+         this_km = astColumnProps( this );
+         that_km = astColumnProps( that );
+         result = astEqual( this_km, that_km );
+         this_km = astAnnul( this_km );
+         that_km = astAnnul( that_km );
+
+/* Check the Parameter KeyMaps are equal.  */
+         this_km = astParameterProps( this );
+         that_km = astParameterProps( that );
+         result = astEqual( this_km, that_km );
+         this_km = astAnnul( this_km );
+         that_km = astAnnul( that_km );
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the protected astGetAttrib
+*     method inherited from the KeyMap class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a Table, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     attrib
+*        Pointer to a null terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the Table, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the Table. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   char cname[ AST__MXCOLNAMLEN + 1 ]; /* Column name */
+   AstTable *this;               /* Pointer to the Table structure */
+   const char *result;           /* Pointer value to return */
+   int ival;                     /* Int attribute value */
+   int len;                      /* Length of attrib string */
+   int nc;                       /* No. characters read by astSscanf */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the Table structure. */
+   this = (AstTable *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null terminated string in an
+   appropriate format.  Set "result" to point at the result string. */
+
+/* Table properties */
+/* ================ */
+
+/* Ncolumn */
+/* ------- */
+   if( !strcmp( attrib, "ncolumn" ) ) {
+      ival = astGetNcolumn( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Nrow */
+/* ---- */
+   } else if( !strcmp( attrib, "nrow" ) ) {
+      ival = astGetNrow( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* Nparameter */
+/* ---------- */
+   } else if( !strcmp( attrib, "nparameter" ) ) {
+      ival = astGetNparameter( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+
+
+/* Column properties */
+/* ================= */
+
+/* A macro that gives the scannf pattern to test for a given column
+   property. Needed since the buffer length is defined by a macro
+   (AST__MXCOLNAMLEN). */
+#define PATTERN(cnam,blen) #cnam "(%" STRING(blen) "[^()])%n"
+
+/* ColumnNdim */
+   } else if ( nc = 0,
+             ( 1 == astSscanf( attrib, PATTERN(columnndim,AST__MXCOLNAMLEN),
+                               cname, &nc ) ) && ( nc >= len ) ) {
+      ival = astGetColumnNdim( this, cname );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* ColumnLenC */
+   } else if ( nc = 0,
+             ( 1 == astSscanf( attrib, PATTERN(columnlenc,AST__MXCOLNAMLEN),
+                               cname, &nc ) ) && ( nc >= len ) ) {
+      ival = astGetColumnLenC( this, cname );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* ColumnType */
+   } else if ( nc = 0,
+             ( 1 == astSscanf( attrib, PATTERN(columntype,AST__MXCOLNAMLEN),
+                               cname, &nc ) ) && ( nc >= len ) ) {
+      ival = astGetColumnType( this, cname );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* ColumnLength */
+   } else if ( nc = 0,
+             ( 1 == astSscanf( attrib, PATTERN(columnlength,AST__MXCOLNAMLEN),
+                               cname, &nc ) ) && ( nc >= len ) ) {
+      ival = astGetColumnLength( this, cname );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* ColumnUnit */
+   } else if ( nc = 0,
+             ( 1 == astSscanf( attrib, PATTERN(columnunit,AST__MXCOLNAMLEN),
+                               cname, &nc ) ) && ( nc >= len ) ) {
+      result = astGetColumnUnit( this, cname );
+
+#undef PATTERN
+
+
+/* Unknown attributes */
+/* ================== */
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetColumnLenC( AstTable *this, const char *column, int *status ) {
+/*
+*+
+*  Name:
+*     astGetColumnLenC
+
+*  Purpose:
+*     Get the maximum formatted length of any value in a column.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int astGetColumnLenC( AstTable *this, const char *column )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function returns the minimum length which a character variable
+*     must have in order to be able to store the longest value currently
+*     present (at any row) in a specified column of the supplied Table. If
+*     the named column holds vector values, then the returned value is
+*     the length of the longest element of the vector value.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     column
+*        The character string holding the upper-case name of the column.
+*        Trailing spaces are ignored. An error is reported if the supplied
+*        column is not found in the Table.
+
+*  Returned Value:
+*     The length (i.e. number of characters) of the longest formatted
+*     value associated with the named column. This does not include the
+*     trailing null character.
+
+*  Notes:
+*     - Automatic data type conversion occurs if the named column holds
+*     numerical values.
+*     - An error will be reported if named column does not exist or cannot
+*     be formatted as a character
+*     string.
+*     - A function value of zero will be returned if an error has already
+*     occurred, or if this function should fail for any reason.
+
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;        /* KeyMap holding column definitions */
+   char key[ AST__MXCOLKEYLEN ]; /* Current cell key string */
+   int irow;               /* Current row index */
+   int len;                /* Length needed to format current cell */
+   int nrow;               /* Number of rows in table */
+   int result;             /* Returned value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the KeyMap holding information about all columns. */
+   cols = astColumnProps( this );
+
+/* Check the table contains the requested column. */
+   if( astMapHasKey( cols, column ) ) {
+
+/* Loop round all rows in the table. */
+      nrow = astGetNrow( this );
+      for( irow = 1; irow <= nrow; irow++ ) {
+
+/* Format the cell name. */
+         sprintf( key, "%.*s(%d)", (int) astChrLen(column), column, irow );
+
+/* Get the maximum length needed to format a string in the current
+   row/column. */
+         len = astMapLenC( this, key );
+
+/* Return the largest value found for any row. */
+         if( len > result ) result = len;
+      }
+
+/* Report an error if the column does not exist. */
+   } else if( astOK ) {
+      astError( AST__BADCOL, "astGetColumnLenC(%s): No column named '%s' "
+                "exists in the table.", status, astGetClass( this ), column );
+   }
+
+/* Free resources */
+   cols = astAnnul( cols );
+
+/* Return AST__BADTYPE if an error occurred. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetColumnLength( AstTable *this, const char *column, int *status ) {
+/*
+*+
+*  Name:
+*     astGetColumnLength
+
+*  Purpose:
+*     Get the number of elements in each value in a column.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int astGetColumnLength( AstTable *this, const char *column )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function returns the number of elements in each value stored
+*     in a named column. Each value can be a scalar (in which case the
+*     ColumnLength attribute has a value of 1), or a multi-dimensional
+*     array ( in which case the ColumnLength value is equal to the
+*     product of the array dimensions).
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     column
+*        The character string holding the upper-case name of the column.
+*        Trailing spaces are ignored. An error is reported if the supplied
+*        column is not found in the Table.
+
+*  Returned Value:
+*     The number of elements in each column value.
+
+*  Notes:
+*     - An error will be reported if named column does not exist or cannot
+*     be formatted as a character
+*     string.
+*     - A function value of zero will be returned if an error has already
+*     occurred, or if this function should fail for any reason.
+
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *col_km;      /* KeyMap holding requested column definition */
+   AstKeyMap *cols;        /* KeyMap holding all column definitions */
+   int *dims;              /* Pointer to array holding dimensions */
+   int idim;               /* Index of dimension */
+   int ndim;               /* Number of dimensions */
+   int result;             /* Returned value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the KeyMap holding information about all columns. */
+   cols = astColumnProps( this );
+
+/* Get the KeyMap holding information about the requested column. */
+   if( astMapGet0A( cols, column, &col_km ) ) {
+
+/* If the Column properties includes the length, return it. Otherwise,
+   calculate the length and store it in the KeyMap as a column property. */
+      if( ! astMapGet0I( col_km, LENGTH, &result ) ) {
+
+/* Get the number of axes spanned by each column value, and allocate an
+   array big enough to hold the dimensions of these axes. */
+         ndim = astMapLength( col_km, SHAPE );
+         dims = astMalloc( sizeof( int )*ndim );
+         if( astOK ) {
+
+/* Get the dimensions. */
+            astMapGet1I( col_km, SHAPE, ndim, &ndim, dims );
+
+/* Find the number of elements. */
+            result = 1;
+            for( idim = 0; idim < ndim; idim++ ) {
+               result *= dims[ idim ];
+            }
+
+/* Store the result in the column KeyMap. */
+            astMapPut0I( col_km, LENGTH, result, NULL );
+         }
+         dims = astFree( dims );
+      }
+
+/* Free resources */
+      col_km = astAnnul( col_km );
+
+/* Report an error if the column does not exist. */
+   } else if( astOK ) {
+      astError( AST__BADCOL, "astGetColumnLength(%s): No column named '%s' "
+                "exists in the table.", status, astGetClass( this ), column );
+   }
+
+/* Free resources */
+   cols = astAnnul( cols );
+
+/* Return AST__BADTYPE if an error occurred. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetColumnNdim( AstTable *this, const char *column, int *status ) {
+/*
+*+
+*  Name:
+*     astGetColumnNdim
+
+*  Purpose:
+*     Get the number of dimensions for a column in a Table.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int astGetColumnNdim( AstTable *this, const char *column )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function attribute holds the number of axes spanned by each value
+*     in a column. If each cell in the column is a scalar, ColumnNdim will
+*     be zero. If each cell in the column is a 1D spectrum, ColumnNdim will
+*     be one. If each cell in the column is a 2D image, ColumnNdim will be
+*     two, etc.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     column
+*        The character string holding the upper-case name of the column.
+*        Trailing spaces are ignored. An error is reported if the supplied
+*        column is not found in the Table.
+
+*  Returned Value:
+*     The number of dimensions - zero for a scalar.
+
+*  Notes:
+*     - A function value of zero will be returned if an error has
+*     already occurred, or if this function should fail for any reason.
+
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;        /* KeyMap holding column definitions */
+   AstKeyMap *col_km;      /* Pointer to KeyMap holding column info */
+   int result;             /* Returned value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the KeyMap holding information about all columns. */
+   cols = astColumnProps( this );
+
+/* Get the KeyMap holding information about the requested column. */
+   if( astMapGet0A( cols, column, &col_km ) ) {
+
+/* Get the number of dimensions. */
+      result = astMapLength( col_km, SHAPE );
+
+/* Free resources */
+      col_km = astAnnul( col_km );
+
+/* Report an error if the column does not exist. */
+   } else if( astOK ) {
+      astError( AST__BADCOL, "astGetColumnNdim(%s): No column named '%s' "
+                "exists in the table.", status, astGetClass( this ), column );
+   }
+   cols = astAnnul( cols );
+
+/* Return AST__BADTYPE if an error occurred. */
+   if( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetColumnType( AstTable *this, const char *column, int *status ) {
+/*
+*+
+*  Name:
+*     astGetColumnType
+
+*  Purpose:
+*     Get the data type of a column in a Table.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int astGetColumnType( AstTable *this, const char *column )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function returns a value indicating the data type of a
+*     named column in a Table. This is the data type which was used
+*     when the column was added to the Table using astAddColumn.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     column
+*        The character string holding the upper-case name of the column.
+*        Trailing spaces are ignored. An error is reported if the supplied
+*        column is not found in the Table.
+
+*  Returned Value:
+*     One of AST__INTTYPE (for integer), AST__SINTTYPE (for short int),
+*     AST__BYTETYPE (for unsigned bytes - i.e. unsigned chars),
+*     AST__DOUBLETYPE (for double precision floating point),
+*     AST__FLOATTYPE (for single precision floating point), AST__STRINGTYPE
+*     (for character string), AST__OBJECTTYPE (for AST Object pointer),
+*     AST__POINTERTYPE (for arbitrary C pointer) or AST__UNDEFTYPE (for
+*     undefined values created by astMapPutU).
+
+*  Notes:
+*     - A function value of AST__BADTYPE will be returned if an error has
+*     already occurred, or if this function should fail for any reason.
+
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;        /* Pointer to KeyMap holding all column info */
+   AstKeyMap *col_km;      /* Pointer to KeyMap holding requested column info */
+   int result;             /* Returned value */
+
+/* Initialise */
+   result = AST__BADTYPE;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the KeyMap holding information about the requested column. */
+   cols = astColumnProps( this );
+   if( astMapGet0A( cols, column, &col_km ) ) {
+
+/* Get the column data type. */
+      (void) astMapGet0I( col_km, TYPE, &result );
+
+/* Annul the KeyMap pointer. */
+      col_km = astAnnul( col_km );
+
+/* Report an error if the column does not exist. */
+   } else if( astOK ) {
+      astError( AST__BADCOL, "astGetColumnType(%s): No column named '%s' "
+                "exists in the table.", status, astGetClass( this ), column );
+   }
+   cols = astAnnul( cols );
+
+/* Return AST__BADTYPE if an error occurred. */
+   if( !astOK ) result = AST__BADTYPE;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetColumnUnit( AstTable *this, const char *column, int *status ) {
+/*
+*+
+*  Name:
+*     astGetColumnUnit
+
+*  Purpose:
+*     Get the unit string for a column in a Table.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "table.h"
+*     const char *astGetColumnUnit( AstTable *this, const char *column )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function returns the unit string for a named column in a Table.
+*     This is the unit string that was provided when the column was added to
+*     the Table using astAddColumn.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     column
+*        The character string holding the upper-case name of the column.
+*        Trailing spaces are ignored. An error is reported if the supplied
+*        column is not found in the Table.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the column units.
+
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *col_km;    /* Pointer to KeyMap holding requested column info */
+   AstKeyMap *cols;      /* Pointer to KeyMap holding all column info */
+   const char *result;   /* Returned value */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the KeyMap holding information about the requested column. */
+   cols = astColumnProps( this );
+   if( astMapGet0A( cols, column, &col_km ) ) {
+
+/* Get the column unit string. */
+      (void) astMapGet0C( col_km, UNIT, &result );
+
+/* Annul the KeyMap pointer. */
+      col_km = astAnnul( col_km );
+
+/* Report an error if the column does not exist. */
+   } else if( astOK ) {
+      astError( AST__BADCOL, "astGetColumnUnit(%s): No column named '%s' "
+                "exists in the table.", status, astGetClass( this ), column );
+   }
+   cols = astAnnul( cols );
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetNcolumn( AstTable *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNcolumn
+
+*  Purpose:
+*     Get the number of columns in a Table.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int astGetNcolumn( AstTable *this )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function returns the number of columns currently in the Table.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+
+*  Returned Value:
+*     Number of columns.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;
+   int result;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get a pointer to the KeyMap holding the column definitions. */
+   cols = astColumnProps( this );
+
+/* Get the number of column definitions in the KeyMap. */
+   result = astMapSize( cols );
+
+/* Annul the KeyMap pointer. */
+   cols = astAnnul( cols );
+
+/* Return the result. */
+   return result;
+}
+
+static int GetNparameter( AstTable *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetNparameter
+
+*  Purpose:
+*     Get the number of global parameters in a Table.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int astGetNparameter( AstTable *this )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function returns the number of global parameters currently in the Table.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+
+*  Returned Value:
+*     Number of parameters.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstKeyMap *pars;
+   int result;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Get a pointer to the KeyMap holding the parameter definitions. */
+   pars = astParameterProps( this );
+
+/* Get the number of parameter definitions in the KeyMap. */
+   result = astMapSize( pars );
+
+/* Annul the KeyMap pointer. */
+   pars = astAnnul( pars );
+
+/* Return the result. */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied Tables,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Table size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstKeyMap *km;             /* KeyMap holding column/parameter definitions */
+   AstTable *this;            /* Pointer to Table structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the Table structure. */
+   this = (AstTable *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent KeyMap class, and
+   then add on any components of the class structure defined by this class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   km = astColumnProps( this );
+   result += astGetObjSize( km );
+   km = astAnnul( km );
+
+   km = astParameterProps( this );
+   result += astGetObjSize( km );
+   km = astAnnul( km );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int HasColumn( AstTable *this, const char *column, int *status ){
+/*
+*++
+*  Name:
+c     astHasColumn
+f     AST_HASCOLUMN
+
+*  Purpose:
+*     Returns a flag indicating if a column is present in a Table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     int astHasColumn( AstTable *this, const char *column )
+f     RESULT = AST_HASCOLUMN( THIS, COLUMN, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns a flag indicating if a named column exists in a Table, for
+*     instance, by having been added to to the Table using
+c     astAddColumn.
+f     AST_ADDCOLUMN.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     column
+f     COLUMN = CHARACTER * ( * ) (Given)
+*        The character string holding the upper case name of the column. Trailing
+*        spaces are ignored.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - A value of
+c     zero
+f     .FALSE.
+*     is returned for if an error occurs.
+
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get the KeyMap holding information about all columns. */
+   cols = astColumnProps( this );
+
+/* Seeif it contains an entry for the named column. */
+   result = astMapHasKey( cols, column );
+
+/* Free resources. */
+   cols = astAnnul( cols );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) result = 0;
+   return result;
+}
+
+static int HasParameter( AstTable *this, const char *parameter, int *status ){
+/*
+*++
+*  Name:
+c     astHasParameter
+f     AST_HASPARAMETER
+
+*  Purpose:
+*     Returns a flag indicating if a named global parameter is present in a Table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     int astHasParameter( AstTable *this, const char *parameter )
+f     RESULT = AST_HASPARAMETER( THIS, PARAMETER, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns a flag indicating if a named parameter exists in a Table, for
+*     instance, by having been added to to the Table using
+c     astAddParameter.
+f     AST_ADDPARAMETER.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     parameter
+f     PARAMETER = CHARACTER * ( * ) (Given)
+*        The character string holding the upper case name of the parameter. Trailing
+*        spaces are ignored.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - A value of
+c     zero
+f     .FALSE.
+*     is returned for if an error occurs.
+
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *pars;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Get the KeyMap holding information about all parameters. */
+   pars = astParameterProps( this );
+
+/* See if it contains an entry for the named parameter. */
+   result = astMapHasKey( pars, parameter );
+
+/* Free resources. */
+   pars = astAnnul( pars );
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) result = 0;
+   return result;
+}
+
+void astInitTableVtab_(  AstTableVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitTableVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a Table.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "table.h"
+*     void astInitTableVtab( AstTableVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Table vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the Table class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstKeyMapVtab *keymap;        /* Pointer to KeyMap component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitKeyMapVtab( (AstKeyMapVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsATable) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstKeyMapVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->AddColumn = AddColumn;
+   vtab->AddParameter = AddParameter;
+   vtab->ColumnName = ColumnName;
+   vtab->ParameterName = ParameterName;
+   vtab->ColumnProps = ColumnProps;
+   vtab->ColumnShape = ColumnShape;
+   vtab->GetColumnLenC = GetColumnLenC;
+   vtab->GetColumnLength = GetColumnLength;
+   vtab->GetColumnNdim = GetColumnNdim;
+   vtab->GetColumnType = GetColumnType;
+   vtab->GetColumnUnit = GetColumnUnit;
+   vtab->GetNcolumn = GetNcolumn;
+   vtab->GetNparameter = GetNparameter;
+   vtab->GetNrow = GetNrow;
+   vtab->HasColumn = HasColumn;
+   vtab->HasParameter = HasParameter;
+   vtab->ParameterProps = ParameterProps;
+   vtab->PurgeRows = PurgeRows;
+   vtab->RemoveColumn = RemoveColumn;
+   vtab->RemoveParameter = RemoveParameter;
+   vtab->RemoveRow = RemoveRow;
+   vtab->SetNrow = SetNrow;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   keymap = (AstKeyMapVtab *) vtab;
+
+   parent_equal = object->Equal;
+   object->Equal = Equal;
+
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_mapremove = keymap->MapRemove;
+
+/* Define convenience macros for overriding methods inherited from the
+   parent KeyMap class using all data type supported by KeyMap. */
+#define OVERRIDE(method,code,methodlc,codelc) \
+   parent_##methodlc##codelc = keymap->method##code; \
+   keymap->method##code = method##code; \
+
+#define OVERRIDE_METHOD(method,methodlc) \
+   OVERRIDE(method,A,methodlc,a) \
+   OVERRIDE(method,P,methodlc,p) \
+   OVERRIDE(method,C,methodlc,c) \
+   OVERRIDE(method,D,methodlc,d) \
+   OVERRIDE(method,F,methodlc,f) \
+   OVERRIDE(method,I,methodlc,i) \
+   OVERRIDE(method,S,methodlc,s) \
+   OVERRIDE(method,B,methodlc,b)
+
+/* Use these macros to override the required methods. */
+   OVERRIDE_METHOD(MapPut0,mapput0)
+   OVERRIDE_METHOD(MapGet0,mapget0)
+   OVERRIDE_METHOD(MapPut1,mapput1)
+   OVERRIDE_METHOD(MapGet1,mapget1)
+   OVERRIDE_METHOD(MapPutElem,mapputelem)
+   OVERRIDE_METHOD(MapGetElem,mapgetelem)
+   OVERRIDE(MapPut,U,mapput,u)
+   OVERRIDE(SetKeyCase,,setkeycase,)
+   OVERRIDE(ClearKeyCase,,clearkeycase,)
+
+/* Remove the macros. */
+#undef OVERRIDE_METHOD
+#undef OVERRIDE
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "Table", "Two-dimensional table of data values" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstTable *this;         /* Pointer to Table structure */
+   int result;             /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the Table structure. */
+   this = (AstTable *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result && this->columns ) result = astManageLock( this->columns, mode, extra,
+                                                          fail );
+
+   if( !result && this->parameters ) result = astManageLock( this->parameters, mode, extra,
+                                                             fail );
+
+/* Return the result. */
+   return result;
+}
+#endif
+
+/*
+*  Name:
+*     MapGet0<X>
+
+*  Purpose:
+*     Get a scalar value from a cell of a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int MapGet0<X>( AstKeyMap *this, const char *key, <X>type *value );
+
+*  Class Membership:
+*     Table member function (over-rides the astMapGet0<X> method inherited
+*     from the KeyMap class).
+
+*  Description:
+*     This is a set of functions for retrieving a scalar value from a
+*     cell of a Table. You should replace <X> in the generic function name
+*     MapGet0<X> by an appropriate 1-character type code (see the "Data
+*     Type Codes" section in the astMapGet0<X> docs). The stored value is
+*     converted to the data type indiced by <X> before being returned (an
+*     error is reported if it is not possible to convert the stored value
+*     to the requested data type).
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     key
+*        A character string identifying the cell from which the value is
+*        to be retrieved. It should have the form "COLNAME(irow)", where
+*        "COLNAME" is replaced by the name of a column that has been
+*        defined previously using the astAddColumn method, and "irow" is
+*        an integer row index (the first row is row 1).
+*     value
+*        A pointer to a buffer in which to return the requested value.
+*        If the requested cell is not found, or if it is found but has an
+*        undefined value (see astMapPutU), then the contents of the buffer
+*        on entry to this function will be unchanged on exit. For pointer
+*        types ("A" and "C"), the buffer should be a suitable pointer, and
+*        the address of this pointer should be supplied as the "value"
+*        parameter.
+*     status
+*        Pointer to inherited status value.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested key name was found, and
+*     does not have an undefined value (see astMapPutU). Zero is returned
+*     otherwise.
+
+*  Notes:
+*     - No error is reported if the requested cell cannot be found in the
+*     given KeyMap, but a zero value will be returned as the function value.
+*     The supplied buffer will be returned unchanged.
+*     - Key names are case insensitive, and white space is considered
+*     significant.
+*     - If the stored value is a vector value, then the first value in
+*     the vector will be returned.
+*     - A string pointer returned by astMapGet0C is guaranteed to remain
+*     valid and the string to which it points will not be over-written for
+*     a total of 50 successive invocations of this function. After this,
+*     the memory containing the string may be re-used, so a copy of
+*     the string should be made if it is needed for longer than this.
+*     - If the returned value is an AST Object pointer, the Object's reference
+*     count is incremented by this call. Any subsequent changes made to
+*     the Object using the returned pointer will be reflected in any
+*     any other active pointers for the Object. The returned pointer
+*     should be annulled using astAnnul when it is no longer needed.
+*/
+
+/* Define a macro to implement the function for a specific data type. */
+#define MAKE_MAPGET0(X,Xlc,Xtype,Itype) \
+static int MapGet0##X( AstKeyMap *this_keymap, const char *key, Xtype *value, \
+                       int *status ) { \
+\
+/* Local Variables: */ \
+   AstTable *this;     /* Pointer to Table structure */ \
+   char colname[ AST__MXCOLNAMLEN + 1 ]; /* Column name read from string */ \
+   int irow;           /* Row index within key string */ \
+   int result;         /* Returned flag */ \
+\
+/* Initialise */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Get a pointer to the Table structure. */ \
+   this = (AstTable *) this_keymap; \
+\
+/* If the key is the name of a global table parameter, use the parent \
+   method to get the value of hte parameter. */ \
+   if( astHasParameter( this, key ) ) { \
+      result = (*parent_mapget0##Xlc)( this_keymap, key, value, status ); \
+\
+/* Check the supplied key looks like a table cell key, and get the \
+   the column name and the row number. Also checks that the table \
+   contains a column with the specified name. */ \
+   } else if( ParseKey( this, key, astGetKeyError( this ), colname, &irow, \
+                        NULL, "astMapGet0" #X, status ) ) { \
+\
+/* If the row index is larger than the current number of rows in the \
+   table, do nothing more. */ \
+      if( irow <= astGetNrow( this ) ){ \
+\
+/* Use the astMapGet0<X> method in the parent keyMap class to get the \
+   cell contents. */ \
+         result = (*parent_mapget0##Xlc)( this_keymap, key, value, status ); \
+      } \
+   } \
+\
+/* If an error occurred, return zero. */ \
+   if( !astOK ) result = 0; \
+\
+/* Return the result.*/ \
+   return result; \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+MAKE_MAPGET0(I,i,int,AST__INTTYPE)
+MAKE_MAPGET0(D,d,double,AST__DOUBLETYPE)
+MAKE_MAPGET0(F,f,float,AST__FLOATTYPE)
+MAKE_MAPGET0(C,c,const char *,AST__STRINGTYPE)
+MAKE_MAPGET0(A,a,AstObject *,AST__OBJECTTYPE)
+MAKE_MAPGET0(P,p,void *,AST__POINTERTYPE)
+MAKE_MAPGET0(S,s,short int,AST__SINTTYPE)
+MAKE_MAPGET0(B,b,unsigned char,AST__BYTETYPE)
+
+/* Undefine the macro. */
+#undef MAKE_MAPGET0
+
+/*
+*  Name:
+*     MapGet1<X>
+
+*  Purpose:
+*     Get a vector value from a cell of a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int MapGet1<X>( AstKeyMap *this, const char *key, int mxval,
+*                     int *nval, <X>type *value )
+*     int MapGet1C( AstKeyMap *this, const char *key, int l, int mxval,
+*                   int *nval, const char *value )
+
+*  Class Membership:
+*     Table member function (over-rides the astMapGet1<X> method inherited
+*     from the KeyMap class).
+
+*  Description:
+*     This is a set of functions for retrieving a vector value from a
+*     cell of a Table. You should replace <X> in the generic function name
+*     MapGet1<X> by an appropriate 1-character type code (see the "Data
+*     Type Codes" section in the astMapGet1<X> docs). The stored value is
+*     converted to the data type indiced by <X> before being returned (an
+*     error is reported if it is not possible to convert the stored value
+*     to the requested data type).
+*
+*     Note, the MapGet1C function has an extra parameter "l" which
+*     specifies the maximum length of each string to be stored in the
+*     "value" buffer (see the "astMapGet1C" docs).
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     key
+*        A character string identifying the cell from which the value is
+*        to be retrieved. It should have the form "COLNAME(irow)", where
+*        "COLNAME" is replaced by the name of a column that has been
+*        defined previously using the astAddColumn method, and "irow" is
+*        an integer row index (the first row is row 1).
+*     mxval
+*        The number of elements in the "value" array.
+*     nval
+*        The address of an integer in which to put the number of elements
+*        stored in the "value" array. Any unused elements of the array are
+*        left unchanged.
+*     value
+*        A pointer to an array in which to return the requested values.
+*        If the requested cell is not found, or if it is found but has an
+*        undefined value (see astMapPutU), then the contents of the buffer
+*        on entry to this function will be unchanged on exit.
+*     status
+*        Pointer to inherited status value.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested key name was found, and
+*     does not have an undefined value (see astMapPutU). Zero is returned
+*     otherwise.
+
+*  MapGet1C:
+*     The "value" buffer supplied to the MapGet1C function should be a
+*     pointer to a character array with "mxval*l" elements, where "l" is
+*     the maximum length of a string to be returned. The value of "l"
+*     should be supplied as an extra parameter following "key" when
+*     invoking MapGet1C, and should include space for a terminating
+*     null character.
+
+*  Notes:
+*     - No error is reported if the requested cell cannot be found in the
+*     given KeyMap, but a zero value will be returned as the function value.
+*     The supplied buffer will be returned unchanged.
+*     - Key names are case insensitive, and white space is considered
+*     significant.
+*     - If the stored value is a scalar value, then the value will be
+*     returned in the first element of the supplied array, and "nval"
+*     will be returned set to 1.
+*/
+
+/* Define a macro to implement the function for a specific data type
+   (excluding "C" since that needs an extra parameter). */
+#define MAKE_MAPGET1(X,Xlc,Xtype,Itype) \
+static int MapGet1##X( AstKeyMap *this_keymap, const char *key, int mxval, int *nval, \
+                       Xtype *value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstTable *this;     /* Pointer to Table structure */ \
+   char colname[ AST__MXCOLNAMLEN + 1 ]; /* Column name read from string */ \
+   int irow;           /* Row index within key string */ \
+   int result;         /* Returned flag */ \
+\
+/* Initialise */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Get a pointer to the Table structure. */ \
+   this = (AstTable *) this_keymap; \
+\
+/* If the key is the name of a global table parameter, use the parent \
+   method to get the value of hte parameter. */ \
+   if( astHasParameter( this, key ) ) { \
+      result = (*parent_mapget1##Xlc)( this_keymap, key, mxval, nval, \
+                value, status ); \
+\
+/* Check the supplied key looks like a table cell key, and get the \
+   the column name and the row number. Also checks that the table \
+   contains a column with the specified name. */ \
+   } else if( ParseKey( this, key, astGetKeyError( this ), colname, &irow, \
+                 NULL, "astMapGet1" #X, status ) ) { \
+\
+/* If the row index is larger than the current number of rows in the \
+   table, do nothing more. */ \
+      if( irow <= astGetNrow( this ) ){ \
+\
+/* Use the astMapGet1<X> method in the parent keyMap class to get the \
+   cell contents. */ \
+         result = (*parent_mapget1##Xlc)( this_keymap, key, mxval, nval, \
+                   value, status ); \
+      } \
+   } \
+\
+/* If an error occurred, return zero. */ \
+   if( !astOK ) result = 0; \
+\
+/* Return the result.*/ \
+   return result; \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+MAKE_MAPGET1(I,i,int,AST__INTTYPE)
+MAKE_MAPGET1(D,d,double,AST__DOUBLETYPE)
+MAKE_MAPGET1(F,f,float,AST__FLOATTYPE)
+MAKE_MAPGET1(A,a,AstObject *,AST__OBJECTTYPE)
+MAKE_MAPGET1(P,p,void *,AST__POINTERTYPE)
+MAKE_MAPGET1(S,s,short int,AST__SINTTYPE)
+MAKE_MAPGET1(B,b,unsigned char,AST__BYTETYPE)
+
+/* Undefine the macro. */
+#undef MAKE_MAPGET1
+
+
+static int MapGet1C( AstKeyMap *this_keymap, const char *key, int l, int mxval,
+                     int *nval, char *value, int *status ) {
+/*
+*  Name:
+*     MapGet1C
+
+*  Purpose:
+*     Get a vector value from a cell of a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int MapGet1C( AstKeyMap *this, const char *key, int l, int mxval,
+*                   int *nval, const char *value )
+
+*  Class Membership:
+*     Table member function (over-rides the astMapGet1C method inherited
+*     from the KeyMap class).
+
+*  Description:
+*     This is the implementation of MapGet1<X> for <X> = "C". We
+*     cannot use the MAKE_MAPGET1 macro for this because the string
+*     version of this function has an extra parameter giving the maximum
+*     length of each string which can be stored in the supplied buffer.
+
+*  Parameters:
+*     (see MapGet1<X>)
+*/
+
+/* Local Variables: */
+   AstTable *this;     /* Pointer to Table structure */
+   char colname[ AST__MXCOLNAMLEN + 1 ]; /* Column name read from string */
+   int irow;           /* Row index within key string */
+   int result;         /* Returned flag */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Table structure. */
+   this = (AstTable *) this_keymap;
+
+/* If the key is the name of a global table parameter, use the parent
+   method to get the value of hte parameter. */
+   if( astHasParameter( this, key ) ) {
+      result = (*parent_mapget1c)( this_keymap, key, l, mxval, nval,
+                value, status );
+
+/* Check the supplied key looks like a table cell key, and get the
+   the column name and the row number. Also checks that the table
+   contains a column with the specified name. */
+   } else if( ParseKey( this, key, astGetKeyError( this ), colname, &irow,
+                        NULL, "astMapGet1C", status ) ) {
+
+/* If the row index is larger than the current number of rows in the
+   table, do nothing more. */
+      if( irow <= astGetNrow( this ) ){
+
+/* Use the astMapGet1<X> method in the parent keyMap class to get the
+   cell contents. */
+         result = (*parent_mapget1c)( this_keymap, key, l, mxval, nval,
+                   value, status );
+      }
+   }
+
+/* If an error occurred, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result.*/
+   return result;
+}
+
+/*
+*  Name:
+*     MapGetElem<X>
+
+*  Purpose:
+*     Get a single element of a vector value from a cell of a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int MapGetElem<X>( AstKeyMap *this, const char *key, int elem,
+*                        <X>type *value, int *status )
+*     int MapGetElemC( AstKeyMap *this, const char *key, int l, int elem,
+*                      char *value, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astMapGetElem<X> method inherited
+*     from the KeyMap class).
+
+*  Description:
+*     This is a set of functions for retrieving a single element of a vector
+*     value from a cell of a Table. You should replace <X> in the generic
+*     function name MapGetElem<X> by an appropriate 1-character type code
+*     (see the "Data Type Codes" section in the astMapGetElem<X> docs). The
+*     stored value is converted to the data type indiced by <X> before being
+*     returned (an error is reported if it is not possible to convert the
+*     stored value to the requested data type).
+*
+*     Note, the MapGetElemC function has an extra parameter "l" which
+*     specifies the maximum length of each string to be stored in the
+*     "value" buffer (see the "MapGetElemC" docs).
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     key
+*        A character string identifying the cell from which the value is
+*        to be retrieved. It should have the form "COLNAME(irow)", where
+*        "COLNAME" is replaced by the name of a column that has been
+*        defined previously using the astAddColumn method, and "irow" is
+*        an integer row index (the first row is row 1).
+*     elem
+*        The index of the vector element to modify, starting at zero.
+*        If the index is outside the range of the vector, an error will
+*        be reported.
+*     value
+*        A pointer to a buffer in which to return the requested values.
+*        If the requested cell is not found, or if it is found but has an
+*        undefined value (see astMapPutU), then the contents of the buffer
+*        on entry to this function will be unchanged on exit.
+*     status
+*        Pointer to inherited status value.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested key name was found, and
+*     does not have an undefined value (see astMapPutU). Zero is returned
+*     otherwise.
+
+*  MapGetElemC:
+*     The "value" buffer supplied to the MapGetElemC function should be a
+*     pointer to a character array with "l" elements, where "l" is
+*     the maximum length of a string to be returned. The value of "l"
+*     should be supplied as an extra parameter following "key" when
+*     invoking MapGetElemC, and should include space for a terminating
+*     null character.
+
+*  Notes:
+*     - No error is reported if the requested cell cannot be found in the
+*     given KeyMap, but a zero value will be returned as the function value.
+*     The supplied buffer will be returned unchanged.
+*     - Key names are case insensitive, and white space is considered
+*     significant.
+*     - If the stored value is a scalar value, then the value will be
+*     returned in the first element of the supplied array, and "nval"
+*     will be returned set to 1.
+*/
+
+/* Define a macro to implement the function for a specific data type
+(excluding "C" since that needs an extra parameter). */
+#define MAKE_MAPGETELEM(X,Xlc,Xtype,Itype) \
+static int MapGetElem##X( AstKeyMap *this_keymap, const char *key, int elem, \
+                          Xtype *value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstTable *this;     /* Pointer to Table structure */ \
+   char colname[ AST__MXCOLNAMLEN + 1 ]; /* Column name read from string */ \
+   int irow;           /* Row index within key string */ \
+   int result;         /* Returned flag */ \
+\
+/* Initialise */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Get a pointer to the Table structure. */ \
+   this = (AstTable *) this_keymap; \
+\
+/* If the key is the name of a global table parameter, use the parent \
+   method to get the value of hte parameter. */ \
+   if( astHasParameter( this, key ) ) { \
+      result = (*parent_mapgetelem##Xlc)( this_keymap, key, elem, \
+                                          value, status ); \
+\
+/* Check the supplied key looks like a table cell key, and get the \
+   the column name and the row number. Also checks that the table \
+   contains a column with the specified name. */ \
+   } else if( ParseKey( this, key, astGetKeyError( this ), colname, &irow, \
+                        NULL, "astMapGetElem" #X, status ) ) { \
+\
+/* If the row index is larger than the current number of rows in the \
+   table, do nothing more. */ \
+      if( irow <= astGetNrow( this ) ){ \
+\
+/* Use the astMapGetElem<X> method in the parent keyMap class to get the \
+   cell contents. */ \
+         result = (*parent_mapgetelem##Xlc)( this_keymap, key, elem, \
+                                             value, status ); \
+      } \
+   } \
+\
+/* If an error occurred, return zero. */ \
+   if( !astOK ) result = 0; \
+\
+/* Return the result.*/ \
+   return result; \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+MAKE_MAPGETELEM(I,i,int,AST__INTTYPE)
+MAKE_MAPGETELEM(D,d,double,AST__DOUBLETYPE)
+MAKE_MAPGETELEM(F,f,float,AST__FLOATTYPE)
+MAKE_MAPGETELEM(A,a,AstObject *,AST__OBJECTTYPE)
+MAKE_MAPGETELEM(P,p,void *,AST__POINTERTYPE)
+MAKE_MAPGETELEM(S,s,short int,AST__SINTTYPE)
+MAKE_MAPGETELEM(B,b,unsigned char,AST__BYTETYPE)
+
+/* Undefine the macro. */
+#undef MAKE_MAPGETELEM
+
+static int MapGetElemC( AstKeyMap *this_keymap, const char *key, int l,
+                        int elem, char *value, int *status ) {
+/*
+*  Name:
+*     MapGetElemC
+
+*  Purpose:
+*     Get a single element of a vector value from a cell of a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int MapGetElemC( AstKeyMap *this, const char *key, int l, int elem,
+*                      char *value, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astMapGetElemC method inherited
+*     from the KeyMap class).
+
+*  Description:
+*     This is the implementation of MapGetElem<X> for <X> = "C". We
+*     cannot use the MAKE_MAPGETELEM macro for this because the string
+*     version of this function has an extra parameter giving the maximum
+*     length of each string which can be stored in the supplied buffer.
+
+*  Parameters:
+*     (see MapGetElem<X>)
+*/
+
+/* Local Variables: */
+   AstTable *this;     /* Pointer to Table structure */
+   char colname[ AST__MXCOLNAMLEN + 1 ]; /* Column name read from string */
+   int irow;           /* Row index within key string */
+   int result;         /* Returned flag */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the Table structure. */
+   this = (AstTable *) this_keymap;
+
+/* If the key is the name of a global table parameter, use the parent
+   method to get the value of hte parameter. */
+   if( astHasParameter( this, key ) ) {
+      result = (*parent_mapgetelemc)( this_keymap, key, l, elem,
+                                      value, status );
+
+/* Check the supplied key looks like a table cell key, and get the
+   the column name and the row number. Also checks that the table
+   contains a column with the specified name. */
+   } else if( ParseKey( this, key, astGetKeyError( this ), colname, &irow,
+                        NULL, "astMapGetElemC", status ) ) {
+
+/* If the row index is larger than the current number of rows in the
+   table, do nothing more. */
+      if( irow <= astGetNrow( this ) ){
+
+/* Use the astMapGetElem<X> method in the parent keyMap class to get the
+   cell contents. */
+         result = (*parent_mapgetelemc)( this_keymap, key, l, elem,
+                                         value, status );
+      }
+   }
+
+/* If an error occurred, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the result.*/
+   return result;
+}
+
+/*
+*  Name:
+*     MapPut0<X>
+
+*  Purpose:
+*     Stores a scalar value in a cell of a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     void MapPut0<X>( AstKeyMap *this, const char *key, <X>type value,
+*                      const char *comment, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astMapPut0<X> method inherited
+*     from the KeyMap class).
+
+*  Description:
+*     This is a set of functions for storing a scalar value in a cell of
+*     a Table. You should use a function which matches the data type of the
+*     data you wish to add to the Table by replacing <X> in the generic
+*     function name MapPut0<X> by an appropriate 1-character type code (see
+*     the "Data Type Codes" section in the astMapPut0<X> docs).
+
+*  Parameters:
+*     this
+*        Pointer to the Table in which to store the supplied value.
+*     key
+*        A character string identifying the cell in which the value is
+*        to be stored. It should have the form "COLNAME(irow)", where
+*        "COLNAME" is replaced by the name of a column that has been
+*        defined previously using the astAddColumn method, and "irow" is
+*        an integer row index (the first row is row 1).
+*     value
+*        The value to be stored. The data type of this value should match
+*        the 1-character type code appended to the function name (e.g. if
+*        you are using astMapPut0A, the type of this value should be "pointer
+*        to AstObject"). An error will be reported if this data type is
+*        different to the data type assigned to the column when it was
+*        created via astAddColumn.
+*     comment
+*        A pointer to a null-terminated comment string to be stored with the
+*        value. A NULL pointer may be supplied, in which case no comment is
+*        stored.
+*     status
+*        Pointer to inherited status value.
+
+*  Notes:
+*     - Key names are case insensitive, and white space is considered
+*     significant.
+*     - The new value will replace any old value already stored in the
+*     Table for the specified cell.
+*     - If the stored value is an AST Object pointer, the Object's reference
+*     count is incremented by this call. Any subsequent changes made to
+*     the Object using the returned pointer will be reflected in any
+*     any other active pointers for the Object, including any obtained
+*     later using astMapget0A. The reference count for the Object will be
+*     decremented when the KeyMap is destroyed, or the entry is removed or
+*     over-written with a different pointer.
+
+*/
+/* Define a macro to implement the function for a specific data type. */
+#define MAKE_MAPPUT0(X,Xlc,Xtype,Itype,ValExp) \
+static void MapPut0##X( AstKeyMap *this_keymap, const char *key, Xtype value, \
+                        const char *comment, int *status ) { \
+\
+/* Local Variables: */ \
+   AstKeyMap *col_km;  /* KeyMap holding details of the requested column */ \
+   AstTable *this;     /* Pointer to Table structure */ \
+   char colname[ AST__MXCOLNAMLEN + 1 ]; /* Column name read from string */ \
+   int irow;           /* Row index within key string */ \
+   int type;           /* Data type of the requested column */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Get a pointer to the Table structure. */ \
+   this = (AstTable *) this_keymap; \
+\
+/* If the key is the name of a global table parameter, use the parent \
+   method to put the value of the parameter. */ \
+   if( astHasParameter( this, key ) ) { \
+      (*parent_mapput0##Xlc)( this_keymap, key, value, comment, status ); \
+\
+/* Check the supplied key looks like a table cell key, and get the \
+   the column name and the row number. Also checks that the table \
+   contains a column with the specified name. */ \
+   } else if( ParseKey( this, key, 1, colname, &irow, &col_km, "astMapPut0" #X, \
+                        status ) ) { \
+\
+/* Check the column holds scalar values of the type implied by the <X> \
+   code in the function name. */ \
+      (void) astMapGet0I( col_km, TYPE, &type ); \
+      if( type != Itype && astOK ) { \
+         astError( AST__BADTYP, "astMapPut0" #X "(%s): Failed to store a " \
+                   #Xtype " value for cell \"%s\": column %s holds %s " \
+                   "values.", status, astGetClass( this ), key, colname, \
+                   TypeString( type ) ); \
+      } \
+\
+      if( astMapHasKey( col_km, SHAPE ) && astOK ) { \
+         astError( AST__BADTYP, "astMapPut0" #X "(%s): Failed to store a " \
+                   "scalar value for cell \"%s\": column %s holds vector " \
+                   " values.", status, astGetClass( this ), key, colname ); \
+      } \
+\
+/* If the row index is larger than the current number of rows in the \
+   table, update the number of rows in the table. */ \
+      if( irow > astGetNrow( this ) ) astSetNrow( this, irow ); \
+\
+/* Use the astMapPut0<X> method in the parent keyMap class to store the \
+   new cell contents. */ \
+      (*parent_mapput0##Xlc)( this_keymap, key, value, comment, status ); \
+\
+/* Free resources. */ \
+      col_km = astAnnul( col_km ); \
+   } \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+MAKE_MAPPUT0(I,i,int,AST__INTTYPE,value)
+MAKE_MAPPUT0(D,d,double,AST__DOUBLETYPE,value)
+MAKE_MAPPUT0(F,f,float,AST__FLOATTYPE,value)
+MAKE_MAPPUT0(C,c,const char *,AST__STRINGTYPE,astStore(NULL,value,strlen(value)+1))
+MAKE_MAPPUT0(A,a,AstObject *,AST__OBJECTTYPE,(value?astClone(value):NULL))
+MAKE_MAPPUT0(P,p,void *,AST__POINTERTYPE,value)
+MAKE_MAPPUT0(S,s,short int,AST__SINTTYPE,value)
+MAKE_MAPPUT0(B,b,unsigned char,AST__BYTETYPE,value)
+
+/* Undefine the macro. */
+#undef MAKE_MAPPUT0
+
+/*
+*  Name:
+*     MapPut1<X>
+
+*  Purpose:
+*     Stores a vectorised value in a cell of a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     void MapPut1<X>( AstKeyMap *this, const char *key, int size,
+*                      const <X>type value[], const char *comment,
+*                      int *status );
+
+*  Class Membership:
+*     Table member function (over-rides the astMapPut1<X> method inherited
+*     from the KeyMap class).
+
+*  Description:
+*     This is a set of functions for storing a vectorised value in a cell of
+*     a Table. You should use a function which matches the data type of the
+*     data you wish to add to the Table by replacing <X> in the generic
+*     function name MapPut1<X> by an appropriate 1-character type code (see
+*     the "Data Type Codes" section in the astMapPut1<X> docs).
+
+*  Parameters:
+*     this
+*        Pointer to the Table in which to store the supplied value.
+*     key
+*        A character string identifying the cell in which the value is
+*        to be stored. It should have the form "COLNAME(irow)", where
+*        "COLNAME" is replaced by the name of a column that has been
+*        defined previously using the astAddColumn method, and "irow" is
+*        an integer row index (the first row is row 1).
+*     size
+*        The number of elements in the supplied array of values.
+*     value
+*        The value to be stored. The data type of this value should match
+*        the 1-character type code appended to the function name (e.g. if
+*        you are using astMapPut0A, the type of this value should be "pointer
+*        to AstObject"). An error will be reported if this data type is
+*        different to the data type assigned to the column when it was
+*        created via astAddColumn.
+*     comment
+*        A pointer to a null-terminated comment string to be stored with the
+*        value. A NULL pointer may be supplied, in which case no comment is
+*        stored.
+*     status
+*        Pointer to inherited status value.
+
+*  Notes:
+*     - Key names are case insensitive, and white space is considered
+*     significant.
+*     - The new value will replace any old value already stored in the
+*     Table for the specified cell.
+
+*/
+
+/* Define a macro to implement the function for a specific data type. */
+#define MAKE_MAPPUT1(X,Xlc,Xtype,Itype,ValExp) \
+static void MapPut1##X( AstKeyMap *this_keymap, const char *key, int size, \
+                        Xtype value[], const char *comment, \
+                        int *status ) { \
+\
+/* Local Variables: */ \
+   AstTable *this;     /* Pointer to Table structure */ \
+   char colname[ AST__MXCOLNAMLEN + 1 ]; /* Column name read from string */ \
+   int irow;           /* Row index within key string */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Get a pointer to the Table structure. */ \
+   this = (AstTable *) this_keymap; \
+\
+/* If the key is the name of a global table parameter, use the parent \
+   method to put the value of the parameter. */ \
+   if( astHasParameter( this, key ) ) { \
+      (*parent_mapput1##Xlc)( this_keymap, key, size, value, \
+                              comment, status ); \
+\
+/* Check the supplied key looks like a table cell key, and get the \
+   the column name and the row number. Also checks that the table \
+   contains a column with the specified name. */ \
+   } else if( ParseKey( this, key, 1, colname, &irow, NULL, "astMapPut1" #X, \
+                        status ) ) { \
+\
+/* Check the column holds vector values of the type implied by the <X> \
+   code in the function name. */ \
+      if(  astGetColumnType( this, colname ) != Itype && astOK ) { \
+         astError( AST__BADTYP, "astMapPut1" #X "(%s): Failed to store " \
+                   #Xtype " values for cell \"%s\": column %s holds %s values.", \
+                   status, astGetClass( this ), key, colname, \
+                   TypeString( astGetColumnType( this, colname ) ) ); \
+      } \
+\
+/* Check the column holds vectors with length equal to the supplied vector. */ \
+      if( astGetColumnLength( this, colname ) != size && astOK ) { \
+         astError( AST__BADTYP, "astMapPut1" #X "(%s): Failed to " \
+                   "store a vector value for cell \"%s\": column " \
+                   "%s needs %d values per cell but %d were supplied.", \
+                   status, astGetClass( this ), key, colname, \
+                   astGetColumnLength( this, colname ), size ); \
+      } \
+\
+/* If all is OK, update the number of rows in the table if required, and \
+   store the vector in the parent KeyMap. */ \
+      if( astOK ) { \
+         if( irow > astGetNrow( this ) ) astSetNrow( this, irow ); \
+         (*parent_mapput1##Xlc)( this_keymap, key, size, value, \
+                                 comment, status ); \
+      } \
+\
+   } \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+MAKE_MAPPUT1(D,d,const double,AST__DOUBLETYPE,value[i])
+MAKE_MAPPUT1(F,f,const float,AST__FLOATTYPE,value[i])
+MAKE_MAPPUT1(I,i,const int,AST__INTTYPE,value[i])
+MAKE_MAPPUT1(C,c,const char *const,AST__STRINGTYPE,astStore(NULL,value[i],strlen(value[i])+1))
+MAKE_MAPPUT1(A,a,AstObject *const,AST__OBJECTTYPE,(value[i]?astClone(value[i]):NULL))
+MAKE_MAPPUT1(P,p,void *const,AST__POINTERTYPE,value[i])
+MAKE_MAPPUT1(S,s,const short int,AST__SINTTYPE,value[i])
+MAKE_MAPPUT1(B,b,const unsigned char,AST__BYTETYPE,value[i])
+
+/* Undefine the macro. */
+#undef MAKE_MAPPUT1
+
+/*
+*  Name:
+*     MapPutElem<X>
+
+*  Purpose:
+*     Put a value into an element of a vector value in a cell of a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     void MapPutElem<X>( AstKeyMap *this, const char *key, int elem,
+*                         <X>type *value, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astMapPutElem<X> method inherited
+*     from the KeyMap class).
+
+*  Description:
+*     This is a set of functions for storing a value in a single element
+*     of a vector value stored in a cell of a Table. You should use a
+*     function which matches the data type of the data you wish to add to
+*     the Table by replacing <X> in the generic function name MapPutElem<X>
+*     by an appropriate 1-character type code (see the "Data Type Codes"
+*     section in the astMapPutElem<X> docs).
+
+*  Parameters:
+*     this
+*        Pointer to the Table in which to store the supplied value.
+*     key
+*        A character string identifying the cell in which the value is
+*        to be stored. It should have the form "COLNAME(irow)", where
+*        "COLNAME" is replaced by the name of a column that has been
+*        defined previously using the astAddColumn method, and "irow" is
+*        an integer row index (the first row is row 1).
+*     elem
+*        The index of the vector element to modify, starting at zero.
+*        If the index is outside the range of the vector, an error will
+*        be reported.
+*     value
+*        The value to be stored. The data type of this value should match
+*        the 1-character type code appended to the function name (e.g. if
+*        you are using astMapPut0A, the type of this value should be "pointer
+*        to AstObject"). An error will be reported if this data type is
+*        different to the data type assigned to the column when it was
+*        created via astAddColumn.
+*     status
+*        Pointer to inherited status value.
+
+*  Notes:
+*     - Key names are case insensitive, and white space is considered
+*     significant.
+*     - The new value will replace any old value already stored in the
+*     Table for the specified cell.
+*     - If the stored value is an AST Object pointer, the Object's reference
+*     count is incremented by this call. Any subsequent changes made to
+*     the Object using the returned pointer will be reflected in any
+*     any other active pointers for the Object, including any obtained
+*     later using astMapget0A. The reference count for the Object will be
+*     decremented when the KeyMap is destroyed, or the entry is removed or
+*     over-written with a different pointer.
+
+*/
+/* Define a macro to implement the function for a specific data type. */
+
+#define MAKE_MAPPUTELEM(X,Xlc,Xtype,Itype) \
+static void MapPutElem##X( AstKeyMap *this_keymap, const char *key, int elem, \
+                           Xtype value, int *status ) { \
+\
+/* Local Variables: */ \
+   AstTable *this;     /* Pointer to Table structure */ \
+   char colname[ AST__MXCOLNAMLEN + 1 ]; /* Column name read from string */ \
+   int irow;           /* Row index within key string */ \
+   int type;           /* Data type of the requested column */ \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Get a pointer to the Table structure. */ \
+   this = (AstTable *) this_keymap; \
+\
+/* If the key is the name of a global table parameter, use the parent \
+   method to put the value of the parameter. */ \
+   if( astHasParameter( this, key ) ) { \
+      (*parent_mapputelem##Xlc)( this_keymap, key, elem, value, \
+                                 status ); \
+\
+/* Check the supplied key looks like a table cell key, and get the \
+   the column name and the row number. Also checks that the table \
+   contains a column with the specified name. */ \
+   } else if( ParseKey( this, key, 1, colname, &irow, NULL, "astMapPutElem" #X, \
+                        status ) ) { \
+\
+/* Check the column holds vector values of the type implied by the <X> \
+   code in the function name. */ \
+      type = astGetColumnType( this, colname ); \
+      if(  type != Itype && astOK ) { \
+         astError( AST__BADTYP, "astMapPutElem" #X "(%s): Failed to store a " \
+                   #Xtype " value in cell \"%s\": column %s holds %s values.", \
+                   status, astGetClass( this ), key, colname, \
+                   TypeString( type ) ); \
+      } \
+\
+/* Check the column holds vectors with length equal to the supplied vector. */ \
+      if( astGetColumnLength( this, colname ) <= elem && astOK ) { \
+         astError( AST__BADTYP, "astMapPutElem" #X "(%s): Failed to " \
+                   "store a value for element %d (zero-based) of " \
+                   "cell \"%s\": column %s has only %d values per " \
+                   "cell.", status, astGetClass( this ), elem, key, \
+                   colname, astGetColumnLength( this, colname ) ); \
+      } \
+\
+/* If all is OK, update the number of rows in the table if required, and \
+   store the value in the parent KeyMap. */ \
+      if( astOK ) { \
+         if( irow > astGetNrow( this ) ) astSetNrow( this, irow ); \
+         (*parent_mapputelem##Xlc)( this_keymap, key, elem, value, \
+                                    status ); \
+      } \
+   } \
+}
+
+/* Expand the above macro to generate a function for each required
+   data type. */
+MAKE_MAPPUTELEM(I,i,int,AST__INTTYPE)
+MAKE_MAPPUTELEM(D,d,double,AST__DOUBLETYPE)
+MAKE_MAPPUTELEM(F,f,float,AST__FLOATTYPE)
+MAKE_MAPPUTELEM(A,a,AstObject *,AST__OBJECTTYPE)
+MAKE_MAPPUTELEM(P,p,void *,AST__POINTERTYPE)
+MAKE_MAPPUTELEM(C,c,const char *,AST__STRINGTYPE)
+MAKE_MAPPUTELEM(S,s,short int,AST__SINTTYPE)
+MAKE_MAPPUTELEM(B,b,unsigned char,AST__BYTETYPE)
+
+/* Undefine the macro. */
+#undef MAKE_MAPPUTELEM
+
+static void MapPutU( AstKeyMap *this_keymap, const char *key, const char *comment,
+                     int *status ) {
+/*
+*  Name:
+*     MapPutU
+
+*  Purpose:
+*     Stores a undefined value in a cell of a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     void MapPutU( AstKeyMap *this, const char *key, const char *comment,
+*                   int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astMapPutU method inherited
+*     from the KeyMap class).
+
+*  Description:
+*     This function adds a new cell to a Table, but no value is stored with
+*     the cell. The cell therefore has a special data type represented by
+*     symbolic constant AST__UNDEFTYPE.
+*
+*     An example use is to add cells with undefined values to a Table
+*     prior to locking them with the MapLocked attribute. Such cells
+*     can act as placeholders for values that can be added to the KeyMap
+*     later.
+
+*  Parameters:
+*     this
+*        Pointer to the Table in which to store the supplied value.
+*     key
+*        A character string identifying the cell in which the value is
+*        to be stored. It should have the form "COLNAME(irow)", where
+*        "COLNAME" is replaced by the name of a column that has been
+*        defined previously using the astAddColumn method, and "irow" is
+*        an integer row index (the first row is row 1).
+*     comment
+*        A pointer to a null-terminated comment string to be stored with the
+*        value. A NULL pointer may be supplied, in which case no comment is
+*        stored.
+*     status
+*        Pointer to inherited status value.
+
+*  Notes:
+*     - Key names are case insensitive, and white space is considered
+*     significant.
+*     - The new undefined value will replace any old value already stored in
+*     the Table for the specified cell.
+
+*/
+
+/* Local Variables: */
+   AstTable *this;     /* Pointer to Table structure */
+   char colname[ AST__MXCOLNAMLEN + 1 ]; /* Column name read from string */
+   int irow;           /* Row index within key string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the Table structure. */
+   this = (AstTable *) this_keymap;
+
+/* If the key is the name of a global table parameter, use the parent
+   method to put the value of the parameter. */
+   if( astHasParameter( this, key ) ) {
+      (*parent_mapputu)( this_keymap, key, comment, status );
+
+/* Check the supplied key looks like a table cell key, and get the
+   the column name and the row number. Also checks that the table
+   contains a column with the specified name. */
+   } else if( ParseKey( this, key, 1, colname, &irow, NULL, "astMapPutU",
+                        status ) ) {
+
+/* If the row index is larger than the current number of rows in the
+   table, update the number of rows in the table. */
+      if( irow > astGetNrow( this ) ) astSetNrow( this, irow );
+
+/* Use the astMapPutU method in the parent keyMap class to store the
+   new cell contents. */
+      (*parent_mapputu)( this_keymap, key, comment, status );
+   }
+}
+
+static const char *ParameterName( AstTable *this, int index, int *status ) {
+/*
+*++
+*  Name:
+c     astParameterName
+f     AST_PARAMETERNAME
+
+*  Purpose:
+*     Get the name of the global parameter at a given index within the Table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     const char *astParameterName( AstTable *this, int index )
+f     RESULT = AST_PARAMETERNAME( THIS, INDEX, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function returns a string holding the name of the global parameter with
+*     the given index within the Table.
+*
+*     This function is intended primarily as a means of iterating round all
+*     the parameters in a Table. For this purpose, the number of parameters in
+*     the Table is given by the Nparameter attribute of the Table. This function
+*     could then be called in a loop, with the index value going from
+c     zero to one less than Nparameter.
+f     one to Nparameter.
+*
+*     Note, the index associated with a parameter decreases monotonically with
+*     the age of the parameter: the oldest Parameter in the Table will have index
+*     one, and the Parameter added most recently to the Table will have the
+*     largest index.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     index
+f     INDEX = INTEGER (Given)
+*        The index into the list of parameters. The first parameter has index
+*        one, and the last has index "Nparameter".
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astParameterName()
+c        A pointer to a null-terminated string containing the
+f     AST_PARAMETERNAME = CHARACTER * ( AST__SZCHR )
+f        The
+*        upper case parameter name.
+
+*  Notes:
+c     - The returned pointer is guaranteed to remain valid and the
+c     string to which it points will not be over-written for a total
+c     of 50 successive invocations of this function. After this, the
+c     memory containing the string may be re-used, so a copy of the
+c     string should be made if it is needed for longer than this.
+c     - A NULL pointer will be returned if this function is invoked
+c     with the AST error status set, or if it should fail for any
+c     reason.
+f     - A blank string will be returned if this function is invoked
+f     with STATUS set to an error value, or if it should fail for any
+f     reason.
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *pars;        /* KeyMap holding parameter definitions */
+   const char *result;
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get apointer to the KeyMap holding all parameter definitions. */
+   pars = astParameterProps( this );
+
+/* Issue a more useful error message than that issued by astMapKey if the
+   index is invalid. */
+   if( index < 1 || index > astMapSize( pars ) ) {
+      astError( AST__MPIND, "astParameterName(%s): Cannot find parameter "
+                "%d (zero-based) of the %s - invalid index.", status,
+                astGetClass( this ), index, astGetClass( this ) );
+   }
+
+/* Get the parameter name. */
+   result = astMapKey( pars, index - 1 );
+
+/* Free resources. */
+   pars = astAnnul( pars );
+
+/* Return a pointer to the required parameter name. */
+   return result;
+}
+
+static AstKeyMap *ParameterProps( AstTable *this, int *status ) {
+/*
+*+
+*  Name:
+*     astParameterProps
+
+*  Purpose:
+*     Returns a pointer to the KeyMap holding parameter properties.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "table.h"
+*     AstKeyMap *astParameterProps( AstTable *this )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function returns a pointer to the KeyMap that holds
+*     definitions of all the parameters added to the Table.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+
+*  Returned Value:
+*        A pointer to the KeyMap. It should be annulled using astAnnul
+*        when no longer needed.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Return a cloned pointer to the required KeyMap. */
+   return astClone( this->parameters );
+}
+
+static int ParseKey( AstTable *this, const char *key, int report,
+                     char colname[ AST__MXCOLNAMLEN + 1 ], int *irow,
+                     AstKeyMap **col_km, const char *method, int *status ){
+/*
+*  Name:
+*     ParseKey
+
+*  Purpose:
+*     Find the column name and row index in a KeyMap key.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int ParseKey( AstTable *this, const char *key, int report,
+*                   char colname[ AST__MXCOLNAMLEN + 1 ], int *irow,
+*                   AstKeyMap **col_km, const char *method, int *status )
+
+*  Class Membership:
+*     Table member function
+
+*  Description:
+*     This function checks that the supplied KeyMap key conforms to the
+*     format expected for Table cells: i.e. "COLNAME(irow)", where
+*     "COLNAME" is the name of a column and "irow" is an integer row
+*     index (the first row is row 1), An error is reported if this is
+*     not the case.
+
+*  Parameters:
+*     this
+*        Pointer to the table.
+*     key
+*        The key string to test.
+*     report
+*        If non-zero, an error will be reported if the key does not
+*        correspond to a cell of an existing column. Otherwise, no
+*        error will be reported.
+*     colname
+*        A buffer in which to return the column name.
+*     irow
+*        Address of an int in which to return the row index.
+*     col_km
+*        Address of a KeyMap pointer in which to return a pointer to the
+*        KeyMap holding the information about the column. The returned
+*        KeyMap pointer should be annulled using astAnnul when no lngerr
+*        needed. If "col_km" is NULL, no KeyMap pointer is returned.
+*     method
+*        Pointer to a string holding the name of the method to include in
+*        any error message.
+*     status
+*        Address of the inherited status value.
+
+*  Returned Value:
+*     Zero is returned if the key is not a valid Table cell key, or if an
+*     error occurs.
+
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;    /* KeyMap holding column definitions */
+   int result;         /* Returned flag */
+   int collen;         /* Length of column name */
+   int nctot;          /* Number of characters read */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Check the supplied key looks like a table cell key, and extract the
+   column name and row number. */
+   nctot = 0;
+   if( 1 == astSscanf( key, "%*[^(]%n(%d) %n", &collen, irow, &nctot )
+       && ( nctot >= strlen( key ) ) ) {
+
+/* Check the column name is not too long. */
+      if( collen > AST__MXCOLNAMLEN ) {
+         if( report ) {
+            astError( AST__BADKEY, "%s(%s): Failed to store a value for cell "
+                      "\"%s\": column name is too long.", status, method,
+                      astGetClass( this ), key );
+         }
+
+/* Check the row index is positive. */
+      } else if( *irow < 1 ) {
+         if( report ) {
+            astError( AST__BADKEY, "%s(%s): Failed to store a value for cell "
+                      "\"%s\": row index %d is invalid.", status, method,
+                      astGetClass( this ), key, *irow );
+         }
+
+/* If the key looks OK so far... */
+      } else {
+
+/* Convert the column name to upper case and store in the returned buffer. */
+         astChrCase( key, colname, 1, collen + 1 );
+         colname[ collen ] = 0;
+
+/* check that the column exists in the Table, returning a pointer to the
+   column KeyMap is reequired. */
+         cols = astColumnProps( this );
+         if( col_km ) {
+            result = astMapGet0A( cols, colname, col_km );
+         } else {
+            result = astMapHasKey( cols, colname );
+         }
+         cols = astAnnul( cols );
+
+/* Report an error if the table does not contain the specified column. */
+         if( !result && astOK && report) {
+            astError( AST__BADKEY, "%s(%s): Failed to store a value for "
+                      "cell \"%s\": the table does not contain a column "
+                      "called '%s'.", status, method, astGetClass( this ),
+                      key, colname );
+         }
+      }
+
+/* Report an error if the cell key has the wrong format. */
+   } else if( report ) {
+      astError( AST__BADKEY, "%s(%s): Failed to store a value for cell "
+                "\"%s\": the cell name is invalid.", status, method,
+                astGetClass( this ), key );
+   }
+
+/* Return the result.*/
+   return result;
+}
+
+static void PurgeRows( AstTable *this, int *status ) {
+/*
+*++
+*  Name:
+c     astPurgeRows
+f     AST_PURGEROWS
+
+*  Purpose:
+*     Remove all empty rows from a table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     void astPurgeRows( AstTable *this )
+f     CALL AST_PURGEROWS( THIS, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function removes all empty rows from the Table, renaming
+*     the key associated with each table cell accordingly.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Local Variables: */
+   char newkey[ AST__MXCOLKEYLEN + 1 ]; /* New cell key string */
+   char oldkey[ AST__MXCOLKEYLEN + 1 ]; /* Old cell key string */
+   const char *col;              /* Column name */
+   const char *key;              /* Pointer to key string */
+   const char *op;               /* Pointer to opening parenthesis */
+   int *w1;                      /* Work space pointer */
+   int icol;                     /* Column index */
+   int inew;                     /* New row index */
+   int iold;                     /* Old row index */
+   int ncol;                     /* Number of columns in table */
+   int nrow;                     /* Number of rows in table */
+   int reset;                    /* Start a new pass through the KeyMap? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the number of rows in the table. */
+   nrow = astGetNrow( this );
+
+/* Create workspace to hold the number of defined values stored in each
+   row. Initialise every row to have zero defined values. */
+   w1 = astCalloc( nrow, sizeof( int ) );
+   if( astOK ) {
+
+/* Iterate round all keys in the KeyMap. */
+      reset = 1;
+      while( ( key = astMapIterate( this, reset ) ) && astOK ) {
+         reset = 0;
+
+/* Extract the row number from the key. */
+         op = strchr( key, '(' );
+         if( !op || astSscanf( op + 1, "%d", &iold ) != 1 ||
+             iold > nrow ) {
+            astError( AST__INTER, "astPurgeRows(%s): Illegal key '%s' "
+                      "found in a %s (internal programming error).",
+                      status, astGetClass( this ), key, astGetClass( this ) );
+
+/* Increment the number of values in this row. Note row indices are
+   one-based. */
+         } else {
+            w1[ iold - 1 ]++;
+         }
+      }
+
+/* Loop round all columns in the Table. */
+      ncol = astGetNcolumn( this );
+      inew = nrow;
+      for( icol = 1; icol <= ncol; icol++ ) {
+
+/* Get the column name */
+         col = astColumnName( this, icol );
+
+/* Loop round all the old row numbers. Skip empty rows.*/
+         inew = 0;
+         for( iold = 0; iold < nrow; iold++ ) {
+            if( w1[ iold ] > 0 ) {
+
+/* Increment the row number to use in place of the old row number. If the
+   old and new row numbers are the same, we do not need to rename the cell. */
+               if( iold != inew++ ) {
+
+/* For the old and new cell names */
+                  sprintf( oldkey, "%s(%d)", col, iold + 1 );
+                  sprintf( newkey, "%s(%d)", col, inew );
+
+/* Rename the KeyMap entry. */
+                  astMapRename( this, oldkey, newkey );
+               }
+            }
+         }
+
+/* If all rows were used, we do not need to check any more columns. */
+         if( iold == inew ) break;
+      }
+
+/* Store the new number of rows. */
+      astSetNrow( this, inew );
+   }
+
+/* Free resources. */
+   w1 = astFree( w1 );
+
+}
+
+static void RemoveColumn( AstTable *this, const char *name, int *status ) {
+/*
+*++
+*  Name:
+c     astRemoveColumn
+f     AST_REMOVECOLUMN
+
+*  Purpose:
+*     Remove a column from a table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     void astRemoveColumn( AstTable *this, const char *name )
+f     CALL AST_REMOVECOLUMN( THIS, NAME, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function removes a specified column from the supplied table.
+*     The
+c     function
+f     routine
+*     returns without action if the named column does not exist in the
+*     Table (no error is reported).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+*        The column name. Trailing spaces are ignored (all other spaces
+*        are significant). Case is significant.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;      /* KeyMap holding column definitions */
+   char key[ AST__MXCOLKEYLEN + 1 ]; /* Cell key string */
+   int irow;             /* Row index */
+   int namlen;           /* Used length of "name" */
+   int nrow;             /* Number of rows in table */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Verify supplied values. */
+   namlen = astChrLen( name );
+   if( namlen == 0 ) {
+      astError( AST__BADKEY, "astRemoveColumn(%s): Illegal blank column name "
+               "supplied.", status, astGetClass( this ) );
+   }
+
+/* Get the number of rows in the table. */
+   nrow = astGetNrow( this );
+
+/* If there is no column with the given name in the Table, do nothing more. */
+   cols = astColumnProps( this );
+   if( astOK && astMapHasKey( cols, name ) ) {
+
+/* Remove the column description from the columns keymap. */
+      astMapRemove( cols, name );
+
+/* Remove any column cells with defined values from the parent KeyMap. */
+      for( irow = 1; irow <= nrow; irow++ ) {
+         sprintf( key, "%.*s(%d)", namlen, name, irow );
+         (*parent_mapremove)( (AstKeyMap *) this, key, status );
+      }
+   }
+   cols = astAnnul( cols );
+}
+
+static void RemoveParameter( AstTable *this, const char *name, int *status ) {
+/*
+*++
+*  Name:
+c     astRemoveParameter
+f     AST_REMOVEPARAMETER
+
+*  Purpose:
+*     Remove a global parameter from a table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     void astRemoveParameter( AstTable *this, const char *name )
+f     CALL AST_REMOVEPARAMETER( THIS, NAME, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function removes a specified global parameter from the supplied table.
+*     The
+c     function
+f     routine
+*     returns without action if the named parameter does not exist in the
+*     Table (no error is reported).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     name
+f     NAME = CHARACTER * ( * ) (Given)
+*        The parameter name. Trailing spaces are ignored (all other spaces
+*        are significant). Case is significant.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *pars;      /* KeyMap holding parameter definitions */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Verify supplied values. */
+   if( astChrLen( name ) == 0 ) {
+      astError( AST__BADKEY, "astRemoveParameter(%s): Illegal blank parameter name "
+               "supplied.", status, astGetClass( this ) );
+   }
+
+/* If there is no parameter with the given name in the Table, do nothing more. */
+   pars = astParameterProps( this );
+   if( astOK && astMapHasKey( pars, name ) ) {
+
+/* Remove the parameter description from the parameters keymap. */
+      astMapRemove( pars, name );
+
+/* Remove any entry holding the parameter value from the parent KeyMap. */
+      (*parent_mapremove)( (AstKeyMap *) this, name, status );
+   }
+   pars = astAnnul( pars );
+}
+
+static void RemoveRow( AstTable *this, int index, int *status ) {
+/*
+*++
+*  Name:
+c     astRemoveRow
+f     AST_REMOVEROW
+
+*  Purpose:
+*     Remove a row from a table.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "table.h"
+c     void astRemoveRow( AstTable *this, int index )
+f     CALL AST_REMOVEROW( THIS, INDEX, STATUS )
+
+*  Class Membership:
+*     Table method.
+
+*  Description:
+*     This function removes a specified row from the supplied table.
+*     The
+c     function
+f     routine
+*     returns without action if the row does not exist in the
+*     Table (no error is reported).
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the Table.
+c     index
+f     INDEX = INTEGER (Given)
+*        The index of the row to be removed. The first row has index 1.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*--
+*/
+
+/* Local Variables: */
+   AstKeyMap *cols;              /* KeyMap holding column definitions */
+   char key[ AST__MXCOLKEYLEN + 1 ]; /* Cell key string */
+   const char *col;              /* Column name */
+   int icol;                     /* Column index */
+   int ncol;                     /* Number of columns in table */
+   int nrow;                     /* Number of rows in table */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the number of rows in the table. */
+   nrow = astGetNrow( this );
+
+/* Do nothing if the specified row is out of bounds. */
+   if( index > 0 && index <= nrow ) {
+
+/* Loop round all columns in the table. */
+      cols = astColumnProps( this );
+      ncol = astMapSize( cols );
+      for( icol = 0; icol < ncol; icol++ ) {
+         col = astMapKey( cols, icol );
+
+/* Remove the cell of the current column at the requested row. */
+         sprintf( key, "%s(%d)", col, index );
+         (*parent_mapremove)( (AstKeyMap *) this, key, status );
+      }
+      cols = astAnnul( cols );
+
+/* If the removed row was the last row, reduce the number of rows in the
+   Table. */
+      if( index == nrow ) astSetNrow( this, index - 1 );
+   }
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astSetAttrib protected
+*     method inherited from the KeyMap class).
+
+*  Description:
+*     This function assigns an attribute value for a Table, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstTable *this;               /* Pointer to the Table structure */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Table structure. */
+   this = (AstTable *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+   /* None as yet */
+
+/* Define a macro to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+#define MATCH2(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "(%*s) =%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* If the attribute was not recognised, use this macro to report an error
+   if a read-only attribute has been specified. */
+   if ( MATCH( "ncolumn" ) ||
+        MATCH( "nparameter" ) ||
+        MATCH( "nrow" ) ||
+        MATCH2( "columnlenc" ) ||
+        MATCH2( "columnlength" ) ||
+        MATCH2( "columnndim" ) ||
+        MATCH2( "columntype" ) ||
+        MATCH2( "columnunit" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+#undef MATCH2
+}
+
+static void SetKeyCase( AstKeyMap *this, int keycase, int *status ) {
+/*
+*  Name:
+*     SetKeyCase
+
+*  Purpose:
+*     Set a value for the KeyCase attribute value for a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "keymape.h"
+*     void SetKeyCase( AstKeyMap *this, int keycase, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astSetKeyCase protected
+*     method inherited from the KeyMap class).
+
+*  Description:
+*     This function assigns a new valeu to the KeyCase attribute for a
+*     Table. For a Table, the KeyCase attribute cannot be changed so this
+*     function exits without action.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     keycase
+*        The new value to set.
+*/
+
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Table member function (over-rides the astTestAttrib protected
+*     method inherited from the KeyMap class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a Table's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the Table.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   int len;                      /* Length of attribute string */
+   int nc;                       /* Number of characters read by astSscanf */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the length of the attribute string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+   /* None as yet */
+
+/* Define a macro to see if the attribute string matches any of the
+   read-only column attributes of this class. */
+#define MATCH(attr) \
+        ( nc = 0, ( 0 == astSscanf( attrib, attr "(%*s)%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+/* If the name is not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then return
+   zero. */
+   if ( !strcmp( attrib, "ncolumn" ) ||
+        !strcmp( attrib, "nparameter" ) ||
+        !strcmp( attrib, "nrow" ) ||
+        MATCH( "columnlenc" ) ||
+        MATCH( "columnlength" ) ||
+        MATCH( "columnndim" ) ||
+        MATCH( "columntype" ) ||
+        MATCH( "columnunit" ) ) {
+      result = 0;
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+
+#undef MATCH
+}
+
+static const char *TypeString( int type ) {
+/*
+*  Name:
+*     TypeString
+
+*  Purpose:
+*     Return a pointer to a string describing a data type.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*      const char *TypeString( int type );
+
+*  Description:
+*     This function returns a pointer to a string describing a data type.
+
+*  Parameters:
+*     type
+*        The integer data type code.
+
+*  Returned Value:
+*     Pointer to a a string descirbing the data type (typically the C
+*     data type).
+
+*/
+
+/* Local Variables: */
+   const char *result;
+
+/* Compare the supplied type code against each supported value. */
+   if( type == AST__INTTYPE ) {
+      result = "int";
+
+   } else if( type == AST__BYTETYPE ) {
+      result = "byte";
+
+   } else if( type == AST__DOUBLETYPE ) {
+      result = "double";
+
+   } else if( type == AST__STRINGTYPE ) {
+      result = "string";
+
+   } else if( type == AST__OBJECTTYPE ) {
+      result = "Object";
+
+   } else if( type == AST__FLOATTYPE ) {
+      result = "float";
+
+   } else if( type == AST__POINTERTYPE ) {
+      result = "pointer";
+
+   } else if( type == AST__SINTTYPE ) {
+      result = "short int";
+
+   } else if( type == AST__UNDEFTYPE ) {
+      result = "undefined";
+
+   } else {
+      result = NULL;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     ColumnLenC(column)
+
+*  Purpose:
+*     The largest string length of any value in a column
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute holds the minimum length which a character variable
+*     must have in order to be able to store the longest value currently
+*     present (at any row) in a specified column of the supplied Table.
+c     This does not include room for a trailing null character.
+*     The required column name should be placed inside the parentheses in
+*     the attribute name. If the named column holds vector values, then
+*     the attribute value is the length of the longest element of the
+*     vector value.
+
+*  Applicability:
+*     Table
+*        All Tables have this attribute.
+
+*  Notes:
+*     - If the named column holds numerical values, the length returned
+*     is the length of the largest string that would be generated if the
+*     column values were accessed as strings.
+
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     ColumnLength(column)
+
+*  Purpose:
+*     The number of elements in each value in a column
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute holds the number of elements in each value stored
+*     in a named column. Each value can be a scalar (in which case the
+*     ColumnLength attribute has a value of 1), or a multi-dimensional
+*     array ( in which case the ColumnLength value is equal to the
+*     product of the array dimensions).
+
+*  Applicability:
+*     Table
+*        All Tables have this attribute.
+
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     ColumnNdim(column)
+
+*  Purpose:
+*     The number of axes spanned by each value in a column
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute holds the number of axes spanned by each value in a
+*     column. If each cell in the column is a scalar, ColumnNdim will be
+*     zero. If each cell in the column is a 1D spectrum, ColumnNdim will
+*     be one. If each cell in the column is a 2D image, ColumnNdim will be
+*     two, etc. The required column name should be placed inside the
+*     parentheses in the attribute name.
+
+*  Applicability:
+*     Table
+*        All Tables have this attribute.
+
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     ColumnType(column)
+
+*  Purpose:
+*     The data type of each value in a column
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute holds a integer value indicating the data type of
+*     a named column in a Table. This is the data type which was used
+*     when the column was added to the Table using astAddColumn. The
+*     required column name should be placed inside the parentheses in
+*     the attribute name.
+*
+*     The attribute value will be one of AST__INTTYPE (for integer),
+*     AST__SINTTYPE (for
+c     short int),
+f     INTEGER*2),
+*     AST__BYTETYPE (for
+c     unsigned bytes - i.e. unsigned chars),
+f     bytes),
+*     AST__DOUBLETYPE (for double
+*     precision floating point), AST__FLOATTYPE (for single
+*     precision floating point), AST__STRINGTYPE (for character string),
+*     AST__OBJECTTYPE (for AST Object pointer), AST__POINTERTYPE (for
+*     arbitrary C pointer) or AST__UNDEFTYPE (for undefined values
+*     created by
+c     astMapPutU).
+f     AST_MAPPUTU).
+
+*  Applicability:
+*     Table
+*        All Tables have this attribute.
+
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     Ncolumn
+
+*  Purpose:
+*     The number of columns in the table.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute holds the number of columns currently in the table. Columns
+*     are added and removed using the
+c     astAddColumn and astRemoveColumn
+f     AST_ADDCOLUMN and AST_REMOVECOLUMN
+*     functions.
+
+*  Applicability:
+*     Table
+*        All Tables have this attribute.
+
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     Nparameter
+
+*  Purpose:
+*     The number of global parameters in the table.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute holds the number of global parameters currently in the table.
+*     Parameters are added and removed using the
+c     astAddParameter and astRemoveParameter
+f     AST_ADDPARAMETER and AST_REMOVEPARAMETER
+*     functions.
+
+*  Applicability:
+*     Table
+*        All Tables have this attribute.
+
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     Nrow
+
+*  Purpose:
+*     The number of rows in the table.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute holds the index of the last row to which any
+*     contents have been added using any of the
+*     astMapPut...
+*     AST_MAPPUT...
+*     functions. The first row has index 1.
+
+*  Applicability:
+*     Table
+*        All Tables have this attribute.
+
+*att--
+*/
+astMAKE_GET(Table,Nrow,int,0,this->nrow)
+astMAKE_SET(Table,Nrow,int,nrow,value)
+
+
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for Table objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for Table objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the component
+*     Mappings within the Table.
+*/
+
+/* Local Variables: */
+   AstTable *in;                /* Pointer to input Table */
+   AstTable *out;               /* Pointer to output Table */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output Tables. */
+   in = (AstTable *) objin;
+   out = (AstTable *) objout;
+
+/* Make copies of the component KeyMaps and store pointers to them in the
+   output Table structure. */
+   out->columns = in->columns ? astCopy( in->columns ) : NULL;
+   out->parameters = in->parameters ? astCopy( in->parameters ) : NULL;
+}
+
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for Table objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for Table objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstTable *this;              /* Pointer to Table */
+
+/* Obtain a pointer to the Table structure. */
+   this = (AstTable *) obj;
+
+/* Annul the pointers to the component KeyMaps. */
+   if( this->columns ) this->columns = astAnnul( this->columns );
+   if( this->parameters ) this->parameters = astAnnul( this->parameters );
+
+}
+
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for Table objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the Table class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the Table whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstTable *this;             /* Pointer to the Table structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the Table structure. */
+   this = (AstTable *) this_object;
+
+/* Write out values representing the instance variables for the Table
+   class.  Note, the primitive data in the Table will be written out
+   by the parent KeyMap Dump function. This function deals just with the
+   extra information held in the Table structure. */
+
+/* Write out the number of rows in the table. */
+   astWriteInt( channel, "Nrow", 1, 1, astGetNrow( this ),
+                "Number of rows in table" );
+
+/* Write out the KeyMap holding definitions of each column. */
+   if( this->columns ) {
+      astWriteObject( channel, "Columns", 1, 0, this->columns, "KeyMap holding "
+                      "column definitions" );
+   }
+
+/* Write out the KeyMap holding definitions of each global parameter. */
+   if( this->parameters ) {
+      astWriteObject( channel, "Params", 1, 0, this->parameters, "KeyMap holding "
+                      "parameter definitions" );
+   }
+}
+
+
+
+
+
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsATable and astCheckTable functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(Table,KeyMap)
+astMAKE_CHECK(Table)
+
+AstTable *astTable_( const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astTable
+f     AST_TABLE
+
+*  Purpose:
+*     Create a Table.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "table.h"
+c     AstTable *astTable( const char *options, ... )
+f     RESULT = AST_TABLE( OPTIONS, STATUS )
+
+*  Class Membership:
+*     Table constructor.
+
+*  Description:
+*     This function creates a new empty Table and optionally initialises
+*     its attributes.
+*
+*     The Table class is a type of KeyMap that represents a two-dimensional
+*     table of values. The
+c     astMapGet... and astMapPut...
+f     AST_MAPGET... and AST_MAPPUT...
+*     methods provided by the KeyMap class should be used for storing and
+*     retrieving values from individual cells within a Table. Each entry
+*     in the KeyMap represents a single cell of the table and has an
+*     associated key of the form "<COL>(i)" where "<COL>" is the name of a
+*     table column and "i" is the row index (the first row is row 1). Keys
+*     of this form should always be used when using KeyMap methods to access
+*     entries within a Table.
+*
+*     Columns must be declared using the
+c     astAddColumn
+f     AST_ADDCOLUMN
+*     method before values can be stored within them. This also fixes the
+*     type and shape of the values that may be stored in any cell of the
+*     column. Cells may contain scalar or vector values of any data type
+*     supported by the KeyMap class. Multi-dimensional arrays may also be
+*     stored, but these must be vectorised when storing and retrieving
+*     them within a table cell. All cells within a single column must
+*     have the same type and shape (specified when the column is declared).
+*
+*     Tables may have parameters that describe global properties of the
+*     entire table. These are stored as entries in the parent KeyMap and
+*     can be access using the get and set method of the KeyMap class.
+*     However, parameters must be declared using the
+c     astAddParameter
+f     AST_ADDPARAMETER
+*     method before being accessed.
+*
+*     Note - since accessing entries within a KeyMap is a relatively slow
+*     process, it is not recommended to use the Table class to store
+*     very large tables.
+
+*  Parameters:
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new Table. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new Table. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astTable()
+f     AST_TABLE = INTEGER
+*        A pointer to the new Table.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list described above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstTable *new;              /* Pointer to new Table */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the Table, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitTable( NULL, sizeof( AstTable ), !class_init, &class_vtab,
+                       "Table" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Table's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Table. */
+   return new;
+}
+
+AstTable *astTableId_( const char *options, ... ) {
+/*
+*  Name:
+*     astTableId_
+
+*  Purpose:
+*     Create a Table.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "table.h"
+*     AstTable *astTableId_( const char *options, ... )
+
+*  Class Membership:
+*     Table constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astTable constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astTable_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astTable_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astTable_.
+
+*  Returned Value:
+*     The ID value associated with the new Table.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstTable *new;                /* Pointer to new Table */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the Table, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitTable( NULL, sizeof( AstTable ), !class_init, &class_vtab,
+                         "Table" );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new Table's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new Table. */
+   return astMakeId( new );
+}
+
+AstTable *astInitTable_( void *mem, size_t size, int init,
+                         AstTableVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitTable
+
+*  Purpose:
+*     Initialise a Table.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "table.h"
+*     AstTable *astInitTable( void *mem, size_t size, int init,
+*                                 AstTableVtab *vtab, const char *name )
+
+*  Class Membership:
+*     Table initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new Table object. It allocates memory (if necessary) to accommodate
+*     the Table plus any additional data associated with the derived class.
+*     It then initialises a Table structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a Table at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the Table is to be initialised.
+*        This must be of sufficient size to accommodate the Table data
+*        (sizeof(Table)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the Table (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the Table
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the Table's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new Table.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+
+*  Returned Value:
+*     A pointer to the new Table.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstTable *new;              /* Pointer to new Table */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitTableVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Initialise a KeyMap structure (the parent class) as the first component
+   within the Table structure, allocating memory if necessary. Specify that
+   the KeyMap should be defined in both the forward and inverse directions. */
+   new = (AstTable *) astInitKeyMap( mem, size, 0, (AstKeyMapVtab *) vtab,
+                                     name );
+   if ( astOK ) {
+
+/* Initialise the Table data. */
+/* ---------------------------- */
+      new->nrow = 0;
+      new->columns = astKeyMap( "KeyCase=0,Sortby=AgeDown", status );
+      new->parameters = astKeyMap( "KeyCase=0,Sortby=AgeDown", status );
+
+/* Tables require the KeyCase attribute to be zero. */
+      (*parent_setkeycase)( (AstKeyMap *) new, 0, status );
+
+/* If an error occurred, clean up by deleting the new Table. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new Table. */
+   return new;
+}
+
+AstTable *astLoadTable_( void *mem, size_t size, AstTableVtab *vtab,
+                         const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadTable
+
+*  Purpose:
+*     Load a Table.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "table.h"
+*     AstTable *astLoadTable( void *mem, size_t size, AstTableVtab *vtab,
+*                             const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     Table loader.
+
+*  Description:
+*     This function is provided to load a new Table using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     Table structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a Table at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the Table is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        Table data (sizeof(Table)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the Table (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the Table structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstTable) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new Table. If this is NULL, a pointer
+*        to the (static) virtual function table for the Table class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "Table" is used instead.
+
+*  Returned Value:
+*     A pointer to the new Table.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstTable *new;                /* Pointer to the new Table */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this Table. In this case the
+   Table belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstTable );
+      vtab = &class_vtab;
+      name = "Table";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitTableVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built Table. */
+   new = astLoadKeyMap( mem, size, (AstKeyMapVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "Table" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* The number of rows. */
+      new->nrow = astReadInt( channel, "nrow", 0 );
+
+/* KeyMap holding columns definitions. */
+      new->columns = astReadObject( channel, "columns", NULL );
+
+/* KeyMap holding parameter definitions. */
+      new->parameters = astReadObject( channel, "params", NULL );
+
+/* If an error occurred, clean up by deleting the new Table. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new Table pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+void astAddColumn_( AstTable *this, const char *name, int type,
+                    int ndim, int *dims, const char *unit, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Table,AddColumn))(this,name,type,ndim,dims,unit,status);
+}
+void astAddParameter_( AstTable *this, const char *name, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,Table,AddParameter))(this,name,status);
+}
+void astRemoveColumn_( AstTable *this, const char *name, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Table,RemoveColumn))(this,name,status);
+}
+void astRemoveParameter_( AstTable *this, const char *name, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Table,RemoveParameter))(this,name,status);
+}
+void astRemoveRow_( AstTable *this, int index, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Table,RemoveRow))(this,index,status);
+}
+void astPurgeRows_( AstTable *this, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Table,PurgeRows))(this,status);
+}
+int astGetNcolumn_( AstTable *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Table,GetNcolumn))( this, status );
+}
+int astGetNparameter_( AstTable *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Table,GetNparameter))( this, status );
+}
+const char *astColumnName_( AstTable *this, int index, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Table,ColumnName))(this,index,status);
+}
+const char *astParameterName_( AstTable *this, int index, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Table,ParameterName))(this,index,status);
+}
+int astGetColumnType_( AstTable *this, const char *column, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Table,GetColumnType))(this,column,status);
+}
+const char *astGetColumnUnit_( AstTable *this, const char *column, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Table,GetColumnUnit))(this,column,status);
+}
+int astGetColumnLenC_( AstTable *this, const char *column, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Table,GetColumnLenC))(this,column,status);
+}
+int astGetColumnLength_( AstTable *this, const char *column, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Table,GetColumnLength))(this,column,status);
+}
+int astGetColumnNdim_( AstTable *this, const char *column, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Table,GetColumnNdim))(this,column,status);
+}
+void astColumnShape_( AstTable *this, const char *column, int mxdim,
+                      int *ndim, int *dims, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,Table,ColumnShape))( this, column, mxdim, ndim,
+                                          dims, status );
+}
+AstKeyMap *astColumnProps_( AstTable *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Table,ColumnProps))(this,status);
+}
+AstKeyMap *astParameterProps_( AstTable *this, int *status ){
+   if ( !astOK ) return NULL;
+   return (**astMEMBER(this,Table,ParameterProps))(this,status);
+}
+int astHasColumn_( AstTable *this, const char *column, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Table,HasColumn))(this,column,status);
+}
+int astHasParameter_( AstTable *this, const char *parameter, int *status ){
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,Table,HasParameter))(this,parameter,status);
+}
diff --git a/ast/table.h b/ast/table.h
new file mode 100644
index 0000000..f51690e
--- /dev/null
+++ b/ast/table.h
@@ -0,0 +1,309 @@
+#if !defined( TABLE_INCLUDED )  /* Include this file only once */
+#define TABLE_INCLUDED
+/*
+*+
+*  Name:
+*     table.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the Table class.
+
+*  Invocation:
+*     #include "table.h"
+
+*  Description:
+*     This include file defines the interface to the Table class and
+*     provides the type definitions, function prototypes and macros,
+*     etc. needed to use this class.
+
+*  Inheritance:
+*     The Table class inherits from the KeyMap class.
+
+*  Copyright:
+*     Copyright (C) 2010 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-NOV-2010 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "keymap.h"              /* Parent class */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Maximum length of a column name */
+#define AST__MXCOLNAMLEN 100
+
+/* Maximum length of a key for a column cell */
+#define AST__MXCOLKEYLEN ( AST__MXCOLNAMLEN + 23 )
+
+#endif
+
+
+/* Type Definitions. */
+/* ================= */
+/* Table structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstTable {
+
+/* Attributes inherited from the parent class. */
+   AstKeyMap keymap;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int nrow;                     /* Mo. of rows in table */
+   AstKeyMap *columns;           /* KeyMap holding column definitions */
+   AstKeyMap *parameters;        /* KeyMap holding parameter definitions */
+} AstTable;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstTableVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstKeyMapVtab keymap_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   AstKeyMap *(* ColumnProps)( AstTable *, int * );
+   AstKeyMap *(* ParameterProps)( AstTable *, int * );
+   const char *(* ColumnName)( AstTable *, int, int * );
+   const char *(* ParameterName)( AstTable *, int, int * );
+   const char *(* GetColumnUnit)( AstTable *, const char *, int * );
+   int (* GetColumnLenC)( AstTable *, const char *, int * );
+   int (* GetColumnLength)( AstTable *, const char *, int * );
+   int (* GetColumnNdim)( AstTable *, const char *, int * );
+   int (* GetColumnType)( AstTable *, const char *, int * );
+   int (* GetNcolumn)( AstTable *, int * );
+   int (* GetNparameter)( AstTable *, int * );
+   int (* GetNrow)( AstTable *, int * );
+   int (* HasColumn)( AstTable *, const char *, int * );
+   int (* HasParameter)( AstTable *, const char *, int * );
+   void (* AddColumn)( AstTable *, const char *, int, int, int *, const char *, int * );
+   void (* AddParameter)( AstTable *, const char *, int * );
+   void (* ColumnShape)( AstTable *, const char *, int, int *, int *, int * );
+   void (* PurgeRows)( AstTable *, int * );
+   void (* RemoveColumn)( AstTable *, const char *, int * );
+   void (* RemoveParameter)( AstTable *, const char *, int * );
+   void (* RemoveRow)( AstTable *, int, int * );
+   void (* SetNrow)( AstTable *, int, int * );
+} AstTableVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstTableGlobals {
+   AstTableVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 101 ];
+} AstTableGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitTableGlobals_( AstTableGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(Table)           /* Check class membership */
+astPROTO_ISA(Table)             /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstTable *astTable_( const char *, int *, ...);
+#else
+AstTable *astTableId_( const char *, ... )__attribute__((format(printf,1,2)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstTable *astInitTable_( void *, size_t, int, AstTableVtab *,
+                         const char *, int * );
+
+/* Vtab initialiser. */
+void astInitTableVtab_( AstTableVtab *, const char *, int * );
+
+/* Loader. */
+AstTable *astLoadTable_( void *, size_t, AstTableVtab *,
+                         const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+void astAddColumn_( AstTable *, const char *, int, int, int *, const char *, int * );
+void astAddParameter_( AstTable *, const char *, int * );
+void astRemoveColumn_( AstTable *, const char *, int * );
+void astRemoveParameter_( AstTable *, const char *, int * );
+void astRemoveRow_( AstTable *, int, int * );
+void astPurgeRows_( AstTable *, int * );
+const char *astColumnName_( AstTable *, int, int * );
+const char *astParameterName_( AstTable *, int, int * );
+void astColumnShape_( AstTable *, const char *, int, int *, int *, int * );
+int astHasColumn_( AstTable *, const char *, int * );
+int astHasParameter_( AstTable *, const char *, int * );
+
+#if defined(astCLASS)            /* Protected */
+AstKeyMap *astColumnProps_( AstTable *, int * );
+AstKeyMap *astParameterProps_( AstTable *, int * );
+const char *astGetColumnUnit_( AstTable *, const char *, int * );
+int astGetColumnLenC_( AstTable *, const char *, int * );
+int astGetColumnLength_( AstTable *, const char *, int * );
+int astGetColumnNdim_( AstTable *, const char *, int * );
+int astGetColumnType_( AstTable *, const char *, int * );
+int astGetNcolumn_( AstTable *, int * );
+int astGetNparameter_( AstTable *, int * );
+int astGetNrow_( AstTable *, int * );
+void astSetNrow_( AstTable *, int, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckTable(this) astINVOKE_CHECK(Table,this,0)
+#define astVerifyTable(this) astINVOKE_CHECK(Table,this,1)
+
+/* Test class membership. */
+#define astIsATable(this) astINVOKE_ISA(Table,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astTable astINVOKE(F,astTable_)
+#else
+#define astTable astINVOKE(F,astTableId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitTable(mem,size,init,vtab,name) \
+astINVOKE(O,astInitTable_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitTableVtab(vtab,name) astINVOKE(V,astInitTableVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadTable(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadTable_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckTable to validate Table pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#define astAddColumn(this,name,type,ndim,dims,unit) astINVOKE(V,astAddColumn_(astCheckTable(this),name,type,ndim,dims,unit, STATUS_PTR))
+#define astAddParameter(this,name) astINVOKE(V,astAddParameter_(astCheckTable(this),name,STATUS_PTR))
+#define astRemoveColumn(this,name) astINVOKE(V,astRemoveColumn_(astCheckTable(this),name,STATUS_PTR))
+#define astRemoveParameter(this,name) astINVOKE(V,astRemoveParameter_(astCheckTable(this),name,STATUS_PTR))
+#define astRemoveRow(this,index) astINVOKE(V,astRemoveRow_(astCheckTable(this),index,STATUS_PTR))
+#define astPurgeRows(this) astINVOKE(V,astPurgeRows_(astCheckTable(this),STATUS_PTR))
+#define astColumnName(this,index) astINVOKE(V,astColumnName_(astCheckTable(this),index,STATUS_PTR))
+#define astParameterName(this,index) astINVOKE(V,astParameterName_(astCheckTable(this),index,STATUS_PTR))
+#define astColumnShape(this,column,mxdim,ndim,dims) astINVOKE(V,astColumnShape_(astCheckTable(this),column,mxdim,ndim,dims,STATUS_PTR))
+#define astHasColumn(this,column) astINVOKE(V,astHasColumn_(astCheckTable(this),column,STATUS_PTR))
+#define astHasParameter(this,param) astINVOKE(V,astHasParameter_(astCheckTable(this),param,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+
+#define astColumnProps(this) \
+astINVOKE(O,astColumnProps_(astCheckTable(this),STATUS_PTR))
+#define astParameterProps(this) \
+astINVOKE(O,astParameterProps_(astCheckTable(this),STATUS_PTR))
+#define astGetNcolumn(this) \
+astINVOKE(V,astGetNcolumn_(astCheckTable(this),STATUS_PTR))
+#define astGetNparameter(this) \
+astINVOKE(V,astGetNparameter_(astCheckTable(this),STATUS_PTR))
+#define astGetNrow(this) \
+astINVOKE(V,astGetNrow_(astCheckTable(this),STATUS_PTR))
+#define astSetNrow(this,value) \
+astINVOKE(V,astSetNrow_(astCheckTable(this),value,STATUS_PTR))
+#define astGetColumnLenC(this,column) \
+astINVOKE(V,astGetColumnLenC_(astCheckTable(this),column,STATUS_PTR))
+#define astGetColumnLength(this,column) \
+astINVOKE(V,astGetColumnLength_(astCheckTable(this),column,STATUS_PTR))
+#define astGetColumnNdim(this,column) \
+astINVOKE(V,astGetColumnNdim_(astCheckTable(this),column,STATUS_PTR))
+#define astGetColumnType(this,column) \
+astINVOKE(V,astGetColumnType_(astCheckTable(this),column,STATUS_PTR))
+#define astGetColumnUnit(this,column) \
+astINVOKE(V,astGetColumnUnit_(astCheckTable(this),column,STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/timeframe.c b/ast/timeframe.c
new file mode 100644
index 0000000..4165b0a
--- /dev/null
+++ b/ast/timeframe.c
@@ -0,0 +1,7481 @@
+/*
+*class++
+*  Name:
+*     TimeFrame
+
+*  Purpose:
+*     Time coordinate system description.
+
+*  Constructor Function:
+c     astTimeFrame
+f     AST_TIMEFRAME
+
+*  Description:
+*     A TimeFrame is a specialised form of one-dimensional Frame which
+*     represents various coordinate systems used to describe positions in
+*     time.
+*
+*     A TimeFrame represents a moment in time as either an Modified Julian
+*     Date (MJD), a Julian Date (JD), a Besselian epoch or a Julian epoch,
+*     as determined by the System attribute. Optionally, a zero point can be
+*     specified (using attribute TimeOrigin) which results in the TimeFrame
+*     representing time offsets from the specified zero point.
+*
+*     Even though JD and MJD are defined as being in units of days, the
+*     TimeFrame class allows other units to be used (via the Unit attribute)
+*     on the basis of simple scalings (60 seconds = 1 minute, 60 minutes = 1
+*     hour, 24 hours = 1 day, 365.25 days = 1 year). Likewise, Julian epochs
+*     can be described in units other than the usual years. Besselian epoch
+*     are always represented in units of (tropical) years.
+*
+*     The TimeScale attribute allows the time scale to be specified (that
+*     is, the physical process used to define the rate of flow of time).
+*     MJD, JD and Julian epoch can be used to represent a time in any
+*     supported time scale. However, Besselian epoch may only be used with the
+*     "TT" (Terrestrial Time) time scale. The list of supported time scales
+*     includes universal time and siderial time. Strictly, these represent
+*     angles rather than time scales, but are included in the list since
+*     they are in common use and are often thought of as time scales.
+*
+*     When a time value is formatted it can be formated either as a simple
+*     floating point value, or as a Gregorian date (see the Format
+*     attribute).
+
+*  Inheritance:
+*     The TimeFrame class inherits from the Frame class.
+
+*  Attributes:
+*     In addition to those attributes common to all Frames, every
+*     TimeFrame also has the following attributes:
+*
+*     - AlignTimeScale: Time scale in which to align TimeFrames
+*     - LTOffset: The offset of Local Time from UTC, in hours.
+*     - TimeOrigin: The zero point for TimeFrame axis values
+*     - TimeScale: The timescale used by the TimeFrame
+*
+*     Several of the Frame attributes inherited by the TimeFrame class
+*     refer to a specific axis of the Frame (for instance Unit(axis),
+*     Label(axis), etc). Since a TimeFrame is strictly one-dimensional,
+*     it allows these attributes to be specified without an axis index.
+*     So for instance, "Unit" is allowed in place of "Unit(1)".
+
+*  Functions:
+c     In addition to those functions applicable to all Frames, the
+c     following functions may also be applied to all TimeFrames:
+f     In addition to those routines applicable to all Frames, the
+f     following routines may also be applied to all TimeFrames:
+*
+c     - astCurrentTime: Return the current system time
+f     - AST_CURRENTTIME: Return the current system time
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     NG: Norman Gray (Starlink)
+*     DSB: David Berry (Starlink)
+
+*  History:
+*     XX-Aug-2003 (NG):
+*        Original version, drawing heavily on specframe.c.
+*     20-MAY-2005 (NG):
+*        Merged into main AST system.
+*     25-MAY-2005 (DSB):
+*        Extensive modifications to add extra timescales, unit support,
+*        support for relative times, etc, and to make it more like the
+*        other AST Frame classes.
+*     12-AUG-2005 (DSB):
+*        Remove ClockLon and ClockLat attributes. Use the new ObsLon and
+*        ObsLat attributes in the parent Frame class instead. Note, for
+*        backward compatibility the public attribute accessors and the
+*        astLoadTimeFrame functions still recogonise ClockLon and ClockLat,
+*        but use the ObsLat/ObsLon attributes internally.
+*     1-MAR-2006 (DSB):
+*        Replace astSetPermMap within DEBUG blocks by astBeginPM/astEndPM.
+*     29-JUN-2006 (DSB):
+*        - Activate astAbbrev function for abbreviating leading fields in
+*        plot labels.
+*        - Include TimeOrigin in default Label.
+*     30-JUN-2006 (DSB):
+*        When splitting a date/time string into fields, allow each field
+*        to include a decimal point.
+*     30-JUN-2006 (DSB):
+*        Allow astAbbrev to have a null "str1" value.
+*     16-OCT-2006 (DSB):
+*        Allow conversions between UTC and UT1 (using the new Frame attribute
+*     1-NOV-2006 (DSB):
+*        Correct sign of longitude passed to TimeMap contrutcorss in
+*        function MakeMap.
+*     31-JAN-2007 (DSB):
+*        Modified so that a TimeFrame can be used as a template to find a
+*        TimeFrame contained within a CmpFrame. This involves changes in
+*        Match and the removal of the local versions of SetMaxAxes and
+*        SetMinAxes.
+*     3-SEP-2007 (DSB):
+*        In SubFrame, since AlignSystem is extended by the TimeFrame class
+*        it needs to be cleared before invoking the parent SubFrame
+*        method in cases where the result Frame is not a TimeFrame.
+*     2-OCT-2007 (DSB):
+*        In Overlay, clear AlignSystem as well as System before calling
+*        the parent overlay method.
+*     2-OCT-2007 (DSB):
+*        Added "LT" (Local Time) time scale.
+*     9-DEC-2008 (DSB):
+*        Ensure Format string pointer is used correctly.
+*     19-JAN-2009 (DSB):
+*        Ensure "<bad>" is returned by astFormat if the axis value is bad.
+*     31-MAR-2009 (DSB):
+*        Extend TimeFrame "iso" Format to allow it to specify the character to
+*        place between the time and date strings.
+*     15-APR-2009 (DSB):
+*        Increase the number of nice calendar time axis gaps allowed by
+*        the Gap function. Previously, there was a jump from 1 day to 1
+*        year making it difficult to plot calendar axes covering time
+*        ranges of the order of 0.5 to 2 years. Now, nice numbers of days
+*        are allowed as intermediate gaps. Since months do not all have
+*        the same number of days, this means that the day number at major
+*        ticks will bounce around a bit.
+*     29-APR-2011 (DSB):
+*        Prevent astFindFrame from matching a subclass template against a
+*        superclass target.
+*     16-APR-2015 (DSB):
+*        Add more choices when chosing gaps on time axes.
+*     17-APR-2015 (DSB):
+*        - Added Centre.
+*        - Remove some "set but unused" variables.
+*     21-APR-2016 (DSB):
+*        - Over-ride astFields.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS TimeFrame
+
+/* Define the first and last acceptable System values. */
+#define FIRST_SYSTEM AST__MJD
+#define LAST_SYSTEM AST__BEPOCH
+
+/* Define the first and last acceptable TimeScale values. */
+#define FIRST_TS AST__TAI
+#define LAST_TS AST__LT
+
+/* The supported time scales fall into two groups. Time scales in the
+   first group depend on the clock position. That is, transformation
+   between a time scale in one group and a timescale in the other group
+   requires the clock position, as does transformation between two time
+   scales within the first group. Define a macro which tests if a given
+   timescale belongs to the first group. */
+#define CLOCK_SCALE(ts) \
+      ( ( ts == AST__LMST || \
+          ts == AST__LAST || \
+          ts == AST__TDB || \
+          ts == AST__TCB ) ? 1 : 0 )
+
+
+/* Define a macro which tests if a given timescale requires a Dut1 value
+   in order to convert from the timescale to UTC. */
+#define DUT1_SCALE(ts) \
+      ( ( ts == AST__LMST || \
+          ts == AST__LAST || \
+          ts == AST__GMST || \
+          ts == AST__UT1 ) ? 1 : 0 )
+
+/* Define a macro which tests if a given timescale requires a LTOffset value
+   in order to convert from the timescale to UTC. */
+#define LTOFFSET_SCALE(ts) \
+      ( ( ts == AST__LT ) ? 1 : 0 )
+
+/* The Unix epoch (00:00:00 UTC 1 January 1970 AD) as an absolute MJD in
+   the UTC timescale. */
+#define UNIX_EPOCH 40587.0
+
+/* Header files. */
+/* ============= */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "unit.h"                /* Units management facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "timemap.h"             /* Time coordinate Mappings */
+#include "frame.h"               /* Parent Frame class */
+#include "timeframe.h"           /* Interface definition for this class */
+#include "mapping.h"             /* Coordinate Mappings */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "shiftmap.h"            /* Shift of origins */
+#include "pal.h"              /* SlaLib interface */
+
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <math.h>
+#include <time.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are used or extended by this
+   class. */
+static AstSystemType (* parent_getalignsystem)( AstFrame *, int * );
+static AstSystemType (* parent_getsystem)( AstFrame *, int * );
+static double (* parent_centre)( AstFrame *, int, double, double, int * );
+static double (* parent_gap)( AstFrame *, int, double, int *, int * );
+static const char *(* parent_abbrev)( AstFrame *, int, const char *, const char *, const char *, int * );
+static const char *(* parent_format)( AstFrame *, int, double, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static const char *(* parent_getdomain)( AstFrame *, int * );
+static const char *(* parent_getlabel)( AstFrame *, int, int * );
+static const char *(* parent_getsymbol)( AstFrame *, int, int * );
+static const char *(* parent_gettitle)( AstFrame *, int * );
+static const char *(* parent_getunit)( AstFrame *, int, int * );
+static double (* parent_getepoch)( AstFrame *, int * );
+static int (* parent_fields)( AstFrame *, int, const char *, const char *, int, char **, int *, double *, int * );
+static int (* parent_match)( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int (* parent_subframe)( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static int (* parent_unformat)( AstFrame *, int, const char *, double *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_clearsystem)( AstFrame *, int * );
+static void (* parent_overlay)( AstFrame *, const int *, AstFrame *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static void (* parent_setsystem)( AstFrame *, AstSystemType, int * );
+static void (* parent_setunit)( AstFrame *, int, const char *, int * );
+
+/* The Unix epoch (00:00:00 UTC 1 January 1970 AD) as an absolute MJD in
+   the TAI timescale. */
+static double tai_epoch;
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->Format_Buff[ 0 ] = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->GetLabel_Buff[ 0 ] = 0; \
+   globals->GetSymbol_Buff[ 0 ] = 0; \
+   globals->GetTitle_Buff[ 0 ] = 0; \
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(TimeFrame)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(TimeFrame,Class_Init)
+#define class_vtab astGLOBAL(TimeFrame,Class_Vtab)
+#define format_buff astGLOBAL(TimeFrame,Format_Buff)
+#define getattrib_buff astGLOBAL(TimeFrame,GetAttrib_Buff)
+#define getlabel_buff astGLOBAL(TimeFrame,GetLabel_Buff)
+#define getsymbol_buff astGLOBAL(TimeFrame,GetSymbol_Buff)
+#define gettitle_buff astGLOBAL(TimeFrame,GetTitle_Buff)
+
+
+
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* Buffers for strings returned by various functions. */
+static char getattrib_buff[ AST__TIMEFRAME_GETATTRIB_BUFF_LEN + 1 ];
+static char format_buff[ AST__TIMEFRAME_FORMAT_BUFF_LEN + 1 ];
+static char getlabel_buff[ AST__TIMEFRAME_GETLABEL_BUFF_LEN + 1 ];
+static char getsymbol_buff[ AST__TIMEFRAME_GETSYMBOL_BUFF_LEN + 1 ];
+static char gettitle_buff[ AST__TIMEFRAME_GETTITLE_BUFF_LEN + 1 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstTimeFrameVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+
+#endif
+
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *MakeMap( AstTimeFrame *, AstSystemType, AstSystemType, AstTimeScaleType, AstTimeScaleType, double, double, const char *, const char *, const char *, int * );
+static AstSystemType GetAlignSystem( AstFrame *, int * );
+static AstSystemType SystemCode( AstFrame *, const char *, int * );
+static AstSystemType ValidateSystem( AstFrame *, AstSystemType, const char *, int * );
+static AstTimeScaleType TimeScaleCode( const char *, int * );
+static const char *DefUnit( AstSystemType, const char *, const char *, int * );
+static const char *Format( AstFrame *, int, double, int * );
+static const char *GetDomain( AstFrame *, int * );
+static const char *GetLabel( AstFrame *, int, int * );
+static const char *GetSymbol( AstFrame *, int, int * );
+static const char *GetTitle( AstFrame *, int * );
+static const char *GetUnit( AstFrame *, int, int * );
+static const char *SystemLabel( AstSystemType, int * );
+static const char *SystemString( AstFrame *, AstSystemType, int * );
+static const char *TimeScaleString( AstTimeScaleType, int * );
+static double CurrentTime( AstTimeFrame *, int * );
+static double FromMJD( AstTimeFrame *, double, int * );
+static double GetEpoch( AstFrame *, int * );
+static double GetTimeOriginCur( AstTimeFrame *, int * );
+static double ToMJD( AstSystemType, double, int * );
+static double ToUnits( AstTimeFrame *, const char *, double, const char *, int * );
+static int DateFormat( const char *, int *, char *, int * );
+static int Fields( AstFrame *, int, const char *, const char *, int, char **, int *, double *, int * );
+static int GetActiveUnit( AstFrame *, int * );
+static int MakeTimeMapping( AstTimeFrame *, AstTimeFrame *, AstTimeFrame *, int, AstMapping **, int * );
+static int Match( AstFrame *, AstFrame *, int, int **, int **, AstMapping **, AstFrame **, int * );
+static int SubFrame( AstFrame *, AstFrame *, int, const int *, const int *, AstMapping **, AstFrame **, int * );
+static int TestActiveUnit( AstFrame *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void OriginScale( AstTimeFrame *, AstTimeScaleType, const char *, int * );
+static void OriginSystem( AstTimeFrame *, AstSystemType, const char *, int * );
+static void Overlay( AstFrame *, const int *, AstFrame *, int * );
+static void SetUnit( AstFrame *, int, const char *, int * );
+static void VerifyAttrs( AstTimeFrame *, const char *, const char *, const char *, int * );
+static AstMapping *ToMJDMap( AstSystemType, double, int * );
+static int Unformat( AstFrame *, int, const char *, double *, int * );
+static const char *Abbrev( AstFrame *, int, const char *, const char *, const char *, int * );
+static double Centre( AstFrame *, int, double, double, int * );
+static double Gap( AstFrame *, int, double, int *, int * );
+
+static AstSystemType GetSystem( AstFrame *, int * );
+static void SetSystem( AstFrame *, AstSystemType, int * );
+static void ClearSystem( AstFrame *, int * );
+
+static double GetTimeOrigin( AstTimeFrame *, int * );
+static int TestTimeOrigin( AstTimeFrame *, int * );
+static void ClearTimeOrigin( AstTimeFrame *, int * );
+static void SetTimeOrigin( AstTimeFrame *, double, int * );
+
+static double GetLTOffset( AstTimeFrame *, int * );
+static int TestLTOffset( AstTimeFrame *, int * );
+static void ClearLTOffset( AstTimeFrame *, int * );
+static void SetLTOffset( AstTimeFrame *, double, int * );
+
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+
+static AstTimeScaleType GetAlignTimeScale( AstTimeFrame *, int * );
+static int TestAlignTimeScale( AstTimeFrame *, int * );
+static void ClearAlignTimeScale( AstTimeFrame *, int * );
+static void SetAlignTimeScale( AstTimeFrame *, AstTimeScaleType, int * );
+
+static AstTimeScaleType GetTimeScale( AstTimeFrame *, int * );
+static int TestTimeScale( AstTimeFrame *, int * );
+static void ClearTimeScale( AstTimeFrame *, int * );
+static void SetTimeScale( AstTimeFrame *, AstTimeScaleType, int * );
+
+/* Member functions. */
+/* ================= */
+static const char *Abbrev( AstFrame *this_frame, int axis,  const char *fmt,
+                           const char *str1, const char *str2, int *status ) {
+/*
+*  Name:
+*     Abbrev
+
+*  Purpose:
+*     Abbreviate a formatted Frame axis value by skipping leading fields.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *Abbrev( AstFrame *this, int axis, const char *fmt,
+*                         const char *str1, const char *str2, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astAbbrev protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function compares two Frame axis values that have been
+*     formatted (using astFormat) and determines if they have any
+*     redundant leading fields (i.e. leading fields in common which
+*     can be suppressed when tabulating the values or plotting them on
+*     the axis of a graph).
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the Frame axis for which the values have been
+*        formatted (axis numbering starts at zero for the first axis).
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format specification used to format the two values.
+*     str1
+*        Pointer to a constant null-terminated string containing the
+*        first formatted value. If this is null, the returned pointer
+*        points to the start of the final field in str2.
+*     str2
+*        Pointer to a constant null-terminated string containing the
+*        second formatted value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer into the "str2" string which locates the first
+*     character in the first field that differs between the two
+*     formatted values.
+*
+*     If the two values have no leading fields in common, the returned
+*     value will point at the start of string "str2". If the two
+*     values are equal, it will point at the terminating null at the
+*     end of this string.
+
+*  Notes:
+*     - This function assumes that the format specification used was
+*     the same when both values were formatted and that they both
+*     apply to the same Frame axis.
+*     - A pointer to the start of "str2" will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   const char *p1;
+   const char *p2;
+   const char *result;
+   int df;
+   int nc1;
+   int nc2;
+   int ndp;
+
+/* Initialise. */
+   result = str2;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate the axis index. */
+   astValidateAxis( this_frame, axis, 1, "astAbbrev" );
+
+/* Use the parent astAbbrev function unless the Format attribute indicates
+   that axis values are to be formatted as multi-field date/time strings. */
+   df = DateFormat( fmt, &ndp, NULL, status );
+   if( !df ) {
+      result = (*parent_abbrev)( this_frame, axis,  fmt, str1, str2, status );
+
+/* Otherwise, if no "str1" string was supplied find the start of the
+   last field in "str2". */
+   } else if( !str1 ){
+
+/* Initialise a pointer to the start of the next field in the "str2" string
+   (skip leading spaces). */
+      p2 = str2;
+      while( *p2 && isspace( *p2 ) ) p2++;
+
+/* Check the entire string, saving the start of the next field as the
+   returned pointer. */
+      while( *p2 ) {
+         result = p2;
+
+/* Each field in a date/time field consists of digits only (and maybe a
+   decimal point). Find the number of leading digits/dots in this field
+   and increment the point to the following character (the first delimiter
+   character). */
+         p2 += strspn( p2, "0123456789." );
+
+/* Skip inter-field (non-numeric) delimiters. */
+         p2 += strcspn( p2, "0123456789." );
+      }
+
+/* Otherwise, if an "str1" string was supplied find the start of the
+   first differing field in "str2". */
+   } else {
+
+/* Initialise pointers to the start of the next field in each string
+   (skip leading spaces). */
+      p1 = str1;
+      p2 = str2;
+      while( *p1 && isspace( *p1 ) ) p1++;
+      while( *p2 && isspace( *p2 ) ) p2++;
+
+/* Check the entire string */
+      result = p2;
+      while( *p1 && *p2 ) {
+
+/* Each field in a date/time field consists of digits only (and maybe a
+   decimal point). Find the number of leading digits/dots in each string */
+         nc1 = strspn( p1, "0123456789." );
+         nc2 = strspn( p2, "0123456789." );
+
+/* If the next field has different lengths in the two strings, or of the
+   content of the fields differ, break out of th eloop, leaving "result"
+   pointing to the start of the current field. */
+         if( nc1 != nc2 || strncmp( p1, p2, nc1 ) ) {
+            break;
+
+/* If the next field is identical in the two strings, skip to the
+   character following the end of the field. */
+         } else {
+            p1 += nc1;
+            p2 += nc2;
+
+/* Skip inter-field (non-numeric) delimiters. */
+            p1 += strcspn( p1, "0123456789." );
+            p2 += strcspn( p2, "0123456789." );
+         }
+
+/* Prepare to check the next field. */
+         result = p2;
+      }
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = str2;
+
+/* Return the result. */
+   return result;
+}
+
+static double Centre( AstFrame *this_frame, int axis, double value,
+                      double gap, int *status ) {
+/*
+*  Name:
+*     Centre
+
+*  Purpose:
+*     Find a "nice" central value for tabulating Frame axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     double Centre( AstFrame *this_frame, int axis, double value,
+*                    double gap, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the protected astCentre method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns an axis value which produces a nice formatted
+*     value suitable for a major tick mark on a plot axis, close to the
+*     supplied axis value.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the axis (zero-based) for which a central value
+*        is to be found.
+*     value
+*        An arbitrary axis value in the section that is being plotted.
+*     gap
+*        The gap size.
+
+*  Returned Value:
+*     The nice central axis value.
+
+*  Notes:
+*     - The supplied axis value is returned if the supplied gap size is
+*     zero, or if this function is invoked with the global error status
+*     set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;
+   char *date1;
+   char *date2;
+   char *f1;
+   char *f2;
+   char *fres;
+   char *p1;
+   char *p2;
+   char *pres;
+   const char *fmt;
+   const char *date;
+   double result;
+   int df;
+   int fmod;
+   int nc1;
+   int nc2;
+   int ndp;
+   int nres;
+   int v1;
+   int v2;
+
+/* Initialise. */
+   result = value;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate the axis index. */
+   astValidateAxis( this_frame, axis, 1, "astCentre" );
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* Use the parent astCentre function unless the Format attribute indicates
+   that axis values are to be formatted as multi-field date/time strings. */
+   fmt = astGetFormat( this, 0 );
+   df = DateFormat( fmt, &ndp, NULL, status );
+   if( !df ) {
+      result = (*parent_centre)( this_frame, axis, value, gap, status );
+
+/* Otherwise. */
+   } else {
+
+/* Format time values one gap above the supplied axis value and one gap below
+   it. Take copies of each string since the astFormat buffer will be
+   over-written by each call. */
+      date = astFormat( this, 0, value - gap );
+      if( date ) date1 = astStore( NULL, date, strlen( date ) + 1 );
+      date = astFormat( this, 0, value + gap );
+      if( date ) date2 = astStore( NULL, date, strlen( date ) + 1 );
+      if( astOK ) {
+
+/* Initialise a formatted version of the returned central value to be
+   equal to "date1". */
+         nres = strlen( date1 );
+         fres = astStore( NULL, date1, nres + 1 );
+
+/* Loop over all characters within the first date. */
+         fmod = 0;
+         nc1 = 0;
+         f1 = NULL;
+         p1 = date1;
+         nc2 = 0;
+         f2 = NULL;
+         p2 = date2;
+         while( 1 ) {
+
+/* If we have not yet found the length of the next numerical field in
+   date1, continue looking for it. */
+            if( !nc1 ) {
+
+/* If we are currently looking for the start of a numerical field, indicate
+   we have found one if the current character is a digit. */
+               if( !f1 ) {
+                  if( isdigit( *p1 ) ) f1 = p1;
+
+/* If we are currently looking for the end of a numeric field, we have
+   found the end if the current character is not a digit. */
+               } else {
+                  if( !isdigit( *p1 ) ) {
+                     nc1 = p1 - f1;
+                  }
+               }
+
+/* Look at the next character */
+               p1++;
+            }
+
+/* If we have not yet found the length of the next numerical field in
+   date2, continue looking for it. */
+            if( !nc2 ) {
+
+/* If we are currently looking for the start of a numerical field, indicate
+   we have found one if the current character is a digit. */
+               if( !f2 ) {
+                  if( isdigit( *p2 ) ) f2 = p2;
+
+/* If we are currently looking for the end of a numeric field, we have
+   found the end if the current character is not a digit. */
+               } else {
+                  if( !isdigit( *p2 ) ) {
+                     nc2 = p2 - f2;
+                  }
+               }
+
+/* Look at the next character */
+               p2++;
+            }
+
+/* If we have found the next numerical field in both dates, convert them
+   to integers. */
+            if( nc1 && nc2 ) {
+               v1 = atoi( f1 );
+               v2 = atoi( f2 );
+
+/* If the values are different, replace this field and all subsequent
+   fields with zeros in the formatted version of the returned central
+   value, and leave the loop. */
+               if( v1 != v2 ) {
+
+                  pres = fres + ( f1 - date1 ) - 1;
+                  while( *(++pres) ) {
+                     if( isdigit( *pres ) ) *pres = '0';
+                  }
+                  fmod = 1;
+
+                  break;
+               }
+
+/* Prepare to look for the next numerical field in both strings. */
+               nc1 = nc2 = 0;
+               f1 = f2 = NULL;
+
+/* If either string has been exhausted, leave the loop. */
+               if( !*p1 || !*p2 ) break;
+            }
+         }
+
+/* If the formatted "nice" value was changed, unformatted it to get the
+   returned axis value. Otherwise we rettina the returned value set
+   earlier. */
+         if( fmod ) {
+            if( astUnformat( this, 0, fres, &result ) != nres && astOK ) {
+               astError( AST__INTER, "astCentre(%s): Error unformatting "
+                         "the central time axis value '%s' (internal AST "
+                         "programming error).", status, astClass( this ), fres );
+            }
+         }
+
+/* Free resources. */
+         fres = astFree( fres );
+      }
+      date1 = astFree( date1 );
+      date2 = astFree( date2 );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static int DateFormat( const char *fmt, int *ndp, char *sep, int *status ){
+/*
+*  Name:
+*     DateFormat
+
+*  Purpose:
+*     Determine if TimeFrame values should be formatted as a date.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     int DateFormat( const char *fmt, int *ndp, char *sep, int *status )
+
+*  Class Membership:
+*     TimeFrame member function
+
+*  Description:
+*    This function returns a flag indicating if the supplied Format string
+*    requires the TimeFrame value to be formatted as a date and/or time of
+*    day.
+
+*  Parameters:
+*     fmt
+*        Pointer to Format string.
+*     ndp
+*        A pointer to an integer in which is returned a value indicating
+*        if a time is required as well as a date. A value of -1 will be
+*        returned in no time is required, otherwise the returned value will
+*        equal the number of decimal places required for the seconds field.
+*     sep
+*        A pointer to a char in which is returned the character that
+*        should be used to separate the date and time fields. Ignored if
+*        NULL.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the formatted TimeFrame value should include a date.
+
+*/
+
+/* Local Variables: */
+   const char *c;
+   int nc;
+   int result;
+
+/* Initialise  */
+   result = 0;
+   *ndp = -1;
+
+/* Check the Format string */
+   if( fmt ) {
+
+/* Find the first non-white character */
+      c = fmt;
+      while( *c && isspace( *c ) ) c++;
+
+/* If the first non-white character starts the string "iso"
+   assume a date is required. If so see if a time is also required
+   (indicated by 1 dot following) and how many seconds of precision are
+   required (the interegr following the dot). */
+      if( !strncmp( c, "iso", 3 ) ) {
+         result = 1;
+         if( astSscanf( c, "iso.%d%n", ndp, &nc ) == 1 ) {
+
+/* Check the separate character (if any) at the end of the format string.
+   Only "T" is allowed. A space is used if no separator is given. */
+            if( sep ) *sep = ( c[ nc ] == 'T' || c[ nc ] == 't' ) ? 'T' : ' ';
+
+         } else {
+            *ndp = -1;
+         }
+      }
+   }
+
+   return result;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astClearAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     TimeFrame, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;           /* Pointer to the TimeFrame structure */
+   char *new_attrib;             /* Pointer value to new attribute name */
+   int len;                      /* Length of attrib string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_object;
+
+/* Obtain the length of the "attrib" string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* First look for axis attributes defined by the Frame class. Since a
+   TimeFrame has only 1 axis, we allow these attributes to be specified
+   without a trailing "(axis)" string. */
+   if ( !strcmp( attrib, "direction" ) ||
+        !strcmp( attrib, "bottom" ) ||
+        !strcmp( attrib, "top" ) ||
+        !strcmp( attrib, "format" ) ||
+        !strcmp( attrib, "label" ) ||
+        !strcmp( attrib, "symbol" ) ||
+        !strcmp( attrib, "unit" ) ) {
+
+/* Create a new attribute name from the original by appending the string
+   "(1)" and then use the parent ClearAttrib method. */
+      new_attrib = astMalloc( len + 4 );
+      if( new_attrib ) {
+         memcpy( new_attrib, attrib, len );
+         memcpy( new_attrib + len, "(1)", 4 );
+         (*parent_clearattrib)( this_object, new_attrib, status );
+         new_attrib = astFree( new_attrib );
+      }
+
+/* AlignTimeScale. */
+/* --------------- */
+   } else if ( !strcmp( attrib, "aligntimescale" ) ) {
+      astClearAlignTimeScale( this );
+
+/* ClockLat. */
+/* --------- */
+/* Retained for backward compatibility with older versions of AST in which
+   TimeFrame had ClockLon/Lat attributes (now ObsLon/Lat are used instead). */
+   } else if ( !strcmp( attrib, "clocklat" ) ) {
+      astClearAttrib( this, "obslat" );
+
+/* ClockLon. */
+/* --------- */
+/* Retained for backward compatibility with older versions of AST in which
+   TimeFrame had ClockLon/Lat attributes (now ObsLon/Lat are used instead). */
+   } else if ( !strcmp( attrib, "clocklon" ) ) {
+      astClearAttrib( this, "obslon" );
+
+/* LTOffset. */
+/* --------- */
+   } else if ( !strcmp( attrib, "ltoffset" ) ) {
+      astClearLTOffset( this );
+
+/* TimeOrigin. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "timeorigin" ) ) {
+      astClearTimeOrigin( this );
+
+/* TimeScale. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "timescale" ) ) {
+      astClearTimeScale( this );
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     ClearSystem
+
+*  Purpose:
+*     Clear the System attribute for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void ClearSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astClearSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function clears the System attribute for a TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstSystemType oldsys;         /* System before clearing */
+   AstTimeFrame *this;           /* Pointer to TimeFrame structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* Save the original system */
+   oldsys = astGetSystem( this_frame );
+
+/* Use the parent ClearSystem method to clear the System value. */
+   (*parent_clearsystem)( this_frame, status );
+
+/* Do nothing more if the system has not actually changed. */
+   if( astGetSystem( this_frame ) != oldsys ) {
+
+/* Modify the TimeOrigin value to use the new System */
+      OriginSystem( this, oldsys, "astClearSystem", status );
+
+/* Clear attributes which have system-specific defaults. */
+      astClearLabel( this_frame, 0 );
+      astClearSymbol( this_frame, 0 );
+      astClearTitle( this_frame );
+
+/* If the old system was BEPOCH also clear units and timescale. This is
+   because we need to ensure that TimeScale=TT and Unit=yr will be used
+   in future (these are the only acceptable values for System=BEPOCH). */
+      if( oldsys == AST__BEPOCH ) {
+         astClearUnit( this_frame, 0 );
+         astClearTimeScale( (AstTimeFrame *) this_frame );
+      }
+   }
+}
+
+static void ClearTimeScale( AstTimeFrame *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearTimeScale
+
+*  Purpose:
+*     Clear the TimeScale attribute for a TimeFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void astClearTimeScale( AstTimeFrame *this )
+
+*  Class Membership:
+*     TimeFrame virtual function
+
+*  Description:
+*     This function clears the TimeScale attribute for a TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If the System is currently set to BEPOCH, then the TimeScale will
+   either be set to TT or will be unset (since SetTimeScale will not
+   allow any other value than TT if the System is BEPOCH). Therefore, if
+   System is BEPOCH, we will not need to modify the TimeOrigin value,
+   since it will already be appropriate. Otherwise, we modify the
+   TimeOrigin value stored in the TimeFrame structure to refer to the
+   default timescale (TAI or TT). */
+   if( astGetSystem( this ) != AST__BEPOCH ) OriginScale( this, AST__TAI,
+                                                     "astClearTimeScale", status );
+
+/* Store a bad value for the timescale in the TimeFrame structure. */
+   this->timescale = AST__BADTS;
+}
+
+static double CurrentTime( AstTimeFrame *this, int *status ){
+/*
+*++
+*  Name:
+c     astCurrentTime
+f     AST_CURRENTTIME
+
+*  Purpose:
+*     Return the current system time.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "timeframe.h"
+c     double astCurrentTime( AstTimeFrame *this )
+f     RESULT = AST_CURRENTTIME( THIS, STATUS )
+
+*  Class Membership:
+*     TimeFrame method.
+
+*  Description:
+c     This function
+f     This routine
+*     returns the current system time, represented in the form specified
+*     by the supplied TimeFrame. That is, the returned floating point
+*     value should be interpreted using the attribute values of the
+*     TimeFrame. This includes System, TimeOrigin, LTOffset, TimeScale,
+*     and Unit.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the TimeFrame.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astCurrentTime()
+f     AST_CURRENTTIME = DOUBLE
+c        A TimeFrame axis value representing the current system time.
+
+*  Notes:
+*     - Values of AST__BAD will be returned if this function is
+c     invoked with the AST error status set, or if it should fail for
+f     invoked with STATUS set to an error value, or if it should fail for
+*     any reason.
+*     - It is assumes that the system time (returned by the C time()
+*     function) follows the POSIX standard, representing a continuous
+*     monotonic increasing count of SI seconds since the epoch 00:00:00
+*     UTC 1 January 1970 AD (equivalent to TAI with a constant offset).
+*     Resolution is one second.
+*     - An error will be reported if the TimeFrame has a TimeScale value
+*     which cannot be converted to TAI (e.g. "angular" systems such as
+*     UT1, GMST, LMST and LAST).
+*     - Any inaccuracy in the system clock will be reflected in the value
+*     returned by this function.
+*--
+*/
+
+/* Local Constants: */
+
+/* Local Variables: */
+   AstMapping *map;
+   double result;
+   double systime;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a Mapping from the system time (TAI seconds relative to "tai_epoch")
+   to the system represented by the supplied TimeFrame. */
+   map = MakeMap( this, AST__MJD, astGetSystem( this ),
+                  AST__TAI, astGetTimeScale( this ),
+                  tai_epoch, astGetTimeOrigin( this ),
+                  "s", astGetUnit( this, 0 ), "astCurrentTime", status );
+   if( !map ) {
+      astError( AST__INCTS, "astCurrentTime(%s): Cannot convert the "
+                "current system time to the required timescale (%s).", status,
+                astGetClass( this ),
+                TimeScaleString( astGetTimeScale( this ), status ) );
+
+/* Get the system time. The "time" function returns a "time_t" which may be
+   encoded in any way. We use "difftime" to convert this into a floating
+   point number of seconds by taking the difference between the current
+   time and zero time. This assumes nothing about the structure of a
+  "time_t" except that zero can be cast into a time_t representing
+   the epoch. */
+   } else {
+      systime = difftime( time( NULL ), (time_t) 0 );
+
+/* Use the Mapping to convert the time into the requied system. */
+      astTran1( map, 1, &systime, 1, &result );
+
+/* Free resources */
+      map = astAnnul( map );
+   }
+
+/* Set result to AST__BAD if an error occurred. */
+   if( !astOK ) result = AST__BAD;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *DefUnit( AstSystemType system, const char *method,
+                            const char *class, int *status ){
+/*
+*  Name:
+*     DefUnit
+
+*  Purpose:
+*     Return the default units for a time coordinate system type.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *DefUnit( AstSystemType system, const char *method,
+*                          const char *class, int *status )
+
+*  Class Membership:
+*     TimeFrame member function.
+
+*  Description:
+*     This function returns a textual representation of the default
+*     units associated with the specified time coordinate system.
+
+*  Parameters:
+*     system
+*        The time coordinate system.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A string describing the default units. This string follows the
+*     units syntax described in FITS WCS paper I "Representations of world
+*     coordinates in FITS" (Greisen & Calabretta).
+
+*  Notes:
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Value to return */
+
+/* Initialize */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get an identifier for the default units. */
+   if( system == AST__MJD ) {
+      result = "d";
+   } else if( system == AST__JD ) {
+      result = "d";
+   } else if( system == AST__BEPOCH ) {
+      result = "yr";
+   } else if( system == AST__JEPOCH ) {
+      result = "yr";
+
+/* Report an error if the coordinate system was not recognised. */
+   } else {
+      astError( AST__SCSIN, "%s(%s): Corrupt %s contains illegal System "
+                "identification code (%d).", status, method, class, class,
+                (int) system );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int Fields( AstFrame *this_frame, int axis, const char *fmt,
+                   const char *str, int maxfld, char **fields,
+                   int *nc, double *val, int *status ) {
+/*
+*  Name:
+*     Fields
+
+*  Purpose:
+*     Identify numerical fields within a formatted Axis value.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "frame.h"
+*     int Fields( AstFrame *this, int axis, const char *fmt,
+*                 const char *str, int maxfld, char **fields,
+*                 int *nc, double *val )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astFields protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function identifies the numerical fields within a Frame axis
+*     value that has been formatted using astAxisFormat. It assumes that
+*     the value was formatted using the supplied format string. It also
+*     returns the equivalent floating point value.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the Frame axis for which the values have been
+*        formatted (axis numbering starts at zero for the first axis).
+*     fmt
+*        Pointer to a constant null-terminated string containing the
+*        format used when creating "str".
+*     str
+*        Pointer to a constant null-terminated string containing the
+*        formatted value.
+*     maxfld
+*        The maximum number of fields to identify within "str".
+*     fields
+*        A pointer to an array of at least "maxfld" character pointers.
+*        Each element is returned holding a pointer to the start of the
+*        corresponding field  in "str" (in the order in which they occur
+*        within "str"), or NULL if no corresponding field can be found.
+*     nc
+*        A pointer to an array of at least "maxfld" integers. Each
+*        element is returned holding the number of characters in the
+*        corresponding field, or zero if no corresponding field can be
+*        found.
+*     val
+*        Pointer to a location at which to store the value
+*        equivalent to the returned field values. If this is NULL,
+*        it is ignored.
+
+*  Returned Value:
+*     The number of fields succesfully identified and returned.
+
+*  Notes:
+*     - Leading and trailing spaces are ignored.
+*     - If the formatted value is not consistent with the supplied format
+*     string, then a value of zero will be returned, "fields" will be
+*     returned holding NULLs, "nc" will be returned holding zeros, and
+*     "val" is returned holding VAL__BAD.
+*     - Fields are counted from the start of the formatted string. If the
+*     string contains more than "maxfld" fields, then trailing fields are
+*     ignored.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;
+   char *p;
+   int bad;
+   int df;
+   int ifld;
+   int ndp;
+   int result;
+   int state;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astFields" );
+
+/* Call the method inherited from the parent Frame class, unless the
+   format string indicates date-time formatting. */
+   df = DateFormat( fmt, &ndp, NULL, status );
+   if( !df ) {
+      result = (*parent_fields)( this_frame, axis, fmt, str, maxfld, fields,
+                                 nc, val, status );
+
+/* Now handle date/time formats.... */
+   } else {
+
+/* Initialise. */
+      for( ifld = 0; ifld < maxfld; ifld++ ) {
+         fields[ ifld ] = NULL;
+         nc[ ifld ] = 0;
+      }
+      if( val ) *val = AST__BAD;
+
+/* The formatted string should always include a date in ISO format - three
+   integer fields separated by dashes. Loop round each character until
+   all characters have been read, or the max number of fields have been
+   obtained, or it is shown that the string is badly formatted. */
+      bad = 0;
+      state = 0;
+      ifld = 0;
+      p = (char *) str - 1;
+      while( *(++p)  && ifld < maxfld && !bad ){
+
+/* Looking for the start of the year field. */
+         if( state == 0 ) {
+            if( isdigit( *p ) ) {
+               fields[ ifld ] = p;
+               nc[ ifld ] = 1;
+               state = 1;
+            } else if( !isspace( *p ) ) {
+               bad = 1;
+            }
+
+/* Looking for the end of the year field. */
+         } else if( state == 1 ) {
+            if( isdigit( *p ) ) {
+               nc[ ifld ]++;
+            } else if( *p != '-' ){
+               bad = 1;
+            } else {
+               state = 2;
+               ifld++;
+            }
+
+/* Looking for the start of the month field. */
+         } else if( state == 2 ) {
+            if( isdigit( *p ) ) {
+               fields[ ifld ] = p;
+               nc[ ifld ] = 1;
+               state = 3;
+            } else {
+               bad = 1;
+            }
+
+/* Looking for the end of the month field. */
+         } else if( state == 3 ) {
+            if( isdigit( *p ) ) {
+               nc[ ifld ]++;
+            } else if( *p != '-' ){
+               bad = 1;
+            } else {
+               state = 4;
+               ifld++;
+            }
+
+/* Looking for the start of the day field. */
+         } else if( state == 4 ) {
+            if( isdigit( *p ) ) {
+               fields[ ifld ] = p;
+               nc[ ifld ] = 1;
+               state = 5;
+            } else {
+               bad = 1;
+            }
+
+/* Looking for the end of the day field. */
+         } else if( state == 5 ) {
+            if( isdigit( *p ) ) {
+               nc[ ifld ]++;
+            } else if( *p != ' ' && *p != 'T' ){
+               bad = 1;
+            } else {
+               state = 6;
+               ifld++;
+            }
+
+/* Looking for the start of the hour field. */
+         } else if( state == 6 ) {
+            if( isdigit( *p ) ) {
+               fields[ ifld ] = p;
+               nc[ ifld ] = 1;
+               state = 7;
+            } else {
+               bad = 1;
+            }
+
+/* Looking for the end of the hour field. */
+         } else if( state == 7 ) {
+            if( isdigit( *p ) ) {
+               nc[ ifld ]++;
+            } else if( *p != ':' ){
+               bad = 1;
+            } else {
+               state = 8;
+               ifld++;
+            }
+
+/* Looking for the start of the minute field. */
+         } else if( state == 8 ) {
+            if( isdigit( *p ) ) {
+               fields[ ifld ] = p;
+               nc[ ifld ] = 1;
+               state = 9;
+            } else {
+               bad = 1;
+            }
+
+/* Looking for the end of the minute field. */
+         } else if( state == 9 ) {
+            if( isdigit( *p ) ) {
+               nc[ ifld ]++;
+            } else if( *p != ':' ){
+               bad = 1;
+            } else {
+               state = 10;
+               ifld++;
+            }
+
+/* Looking for the start of the integer part of the seconds field. */
+         } else if( state == 10 ) {
+            if( isdigit( *p ) ) {
+               fields[ ifld ] = p;
+               nc[ ifld ] = 1;
+               state = 11;
+            } else {
+               bad = 1;
+            }
+
+/* Looking for the end of the integer part of the seconds field. */
+         } else if( state == 11 ) {
+            if( isdigit( *p ) ) {
+               nc[ ifld ]++;
+            } else if( *p != '.' ){
+               bad = 1;
+            } else {
+               state = 12;
+               ifld++;
+            }
+
+/* Looking for the start of the decimal part of the seconds field. */
+         } else if( state == 12 ) {
+            if( isdigit( *p ) ) {
+               fields[ ifld ] = p;
+               nc[ ifld ] = 1;
+               state = 13;
+            } else {
+               bad = 1;
+            }
+
+/* Looking for the end of the decimal part of the seconds field. */
+         } else if( state == 13 ) {
+            if( isdigit( *p ) ) {
+               nc[ ifld ]++;
+            } else if( !isspace( *p ) ){
+               bad = 1;
+            }
+
+         } else {
+            bad = 1;
+         }
+      }
+
+/* If he string is badly formatted, return null values. */
+      if( bad ) {
+         result = 0;
+         for( ifld = 0; ifld < maxfld; ifld++ ) {
+            fields[ ifld ] = NULL;
+            nc[ ifld ] = 0;
+         }
+
+/* Otherwise, unformat the string if required. */
+      } else if( val ) {
+         (void) astUnformat( this, axis, str, val );
+      }
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *Format( AstFrame *this_frame, int axis, double value, int *status ) {
+/*
+*  Name:
+*     Format
+
+*  Purpose:
+*     Format a coordinate value for a TimeFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *Format( AstFrame *this, int axis, double value, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astFormat method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to a string containing the formatted
+*     (character) version of a coordinate value for a TimeFrame axis. The
+*     formatting applied is that specified by a previous invocation of the
+*     astSetFormat method. A suitable default format is applied if necessary.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     axis
+*        The number of the axis (zero-based) for which formatting is to be
+*        performed.
+*     value
+*        The coordinate value to be formatted, in radians.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the formatted value.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS
+   AstMapping *map;
+   AstSystemType sys;
+   AstTimeFrame *this;
+   AstTimeScaleType ts;
+   char *d;
+   char sep;
+   char tbuf[ 100 ];
+   char sign[ 2 ];
+   const char *fmt;
+   const char *result;
+   const char *u;
+   double fd;
+   double mjd;
+   double off;
+   int df;
+   int id;
+   int ihmsf[ 4 ];
+   int im;
+   int iy;
+   int j;
+   int ndp;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_frame);
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astFormat" );
+
+/* Check if a bad coordinate value was supplied and return a pointer to an
+   appropriate string if necessary. */
+   if ( value == AST__BAD ) {
+      result = "<bad>";
+   } else {
+
+/* If the format string does not indicate a date/time format, invoke the
+   parent Format method. */
+      fmt = astGetFormat( this, 0 );
+      df = DateFormat( fmt, &ndp, &sep, status );
+      if( !df ) {
+         result = (*parent_format)( this_frame, axis, value, status );
+
+/* Otherwise, format the value as a date/time */
+      } else {
+
+/* Convert the value to an absolute MJD in units of days. */
+         ts = astGetTimeScale( this );
+         sys = astGetSystem( this );
+         off = astGetTimeOrigin( this );
+         u = astGetUnit( this, 0 );
+         map = MakeMap( this, sys, AST__MJD, ts, ts, off, 0.0, u, "d",
+                        "astFormat", status );
+         if( map ) {
+            astTran1( map, 1, &value, 1, &mjd );
+            map = astAnnul( map );
+
+/* If no time fields will be produced, round to the nearest day. */
+            if( ndp < 0 ) mjd = (int) ( mjd + 0.5 );
+
+/* Convert the MJD into a set of numeric date fields, plus day fraction,
+   and format them. */
+            palDjcl( mjd, &iy, &im, &id, &fd, &j );
+            d = format_buff;
+            d += sprintf( d, "%4d-%2.2d-%2.2d", iy, im, id );
+
+/* If required, convert the day fraction into a set of numerical time
+   fields. */
+            if( ndp >= 0 ) {
+               palDd2tf( ndp, fd, sign, ihmsf );
+
+/* Format the time fields. */
+               if( ndp > 0 ) {
+                  (void) sprintf( tbuf, "%c%2.2d:%2.2d:%2.2d.%*.*d", sep,
+                                  ihmsf[0], ihmsf[1], ihmsf[2], ndp, ndp,
+                                  ihmsf[3] );
+               } else {
+                  (void) sprintf( tbuf, "%c%2.2d:%2.2d:%2.2d", sep, ihmsf[0],
+                                  ihmsf[1], ihmsf[2] );
+               }
+
+/* Add in the formatted time. */
+               d += sprintf( d, "%s", tbuf );
+
+            }
+            result = format_buff;
+         }
+      }
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static double FromMJD( AstTimeFrame *this, double oldval, int *status ){
+/*
+*
+*  Name:
+*     FromMJD
+
+*  Purpose:
+*     Convert a supplied MJD value to the System of the supplied TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     double FromMJD( AstTimeFrame *this, double oldval, int *status )
+
+*  Class Membership:
+*     TimeFrame member function
+
+*  Description:
+*     This function converts the supplied time value from an MJD to
+*     the System of the supplied TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     oldval
+*        The value to be converted. It is assume to be an absolute MJD
+*        value (i.e. zero offset) in units of days.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The converted value (with zero offset), in the default units
+*     associated with the System of "this".
+
+*/
+
+/* Local Variables: */
+   AstTimeMap *timemap;
+   AstSystemType newsys;
+   double args[ 2 ];
+   double result;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the System attribute from the supplied TimeFrame. */
+   newsys = astGetSystem( this );
+
+/* If this is MJD just return the value unchanged. */
+   if( newsys == AST__MJD ) {
+      result = oldval;
+
+/* Otherwise create a TimeMap wich converts from the MJD to the required
+   system, and use it to transform the supplied value. */
+   } else {
+      timemap = astTimeMap( 0, "", status );
+
+/* The supplied and returned values are assumed to have zero offset.*/
+      args[ 0 ] = 0.0;
+      args[ 1 ] = 0.0;
+
+/* If required, add a TimeMap conversion which converts from MJD to the
+   new system. */
+      if( newsys == AST__JD ) {
+         astTimeAdd( timemap, "MJDTOJD", 2, args );
+
+      } else if( newsys == AST__JEPOCH ) {
+         astTimeAdd( timemap, "MJDTOJEP", 2, args );
+
+      } else if( newsys == AST__BEPOCH ) {
+         astTimeAdd( timemap, "MJDTOBEP", 2, args );
+      }
+
+/* Use the TimeMap to convert the supplied value. */
+      astTran1( timemap, 1, &oldval, 1, &result );
+
+/* Free resources */
+      timemap = astAnnul( timemap );
+
+   }
+
+/* Return the result */
+   return result;
+}
+
+
+static double Gap( AstFrame *this_frame, int axis, double gap, int *ntick, int *status ) {
+/*
+*  Name:
+*     Gap
+
+*  Purpose:
+*     Find a "nice" gap for tabulating Frame axis values.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     double Gap( AstFrame *this, int axis, double gap, int *ntick, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astGap protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a gap size which produces a nicely spaced
+*     series of formatted values for a Frame axis, the returned gap
+*     size being as close as possible to the supplied target gap
+*     size. It also returns a convenient number of divisions into
+*     which the gap can be divided.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     axis
+*        The number of the axis (zero-based) for which a gap is to be found.
+*     gap
+*        The target gap size.
+*     ntick
+*        Address of an int in which to return a convenient number of
+*        divisions into which the gap can be divided.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The nice gap size.
+
+*  Notes:
+*     - A value of zero is returned if the target gap size is zero.
+*     - A negative gap size is returned if the supplied gap size is negative.
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   AstTimeFrame *this;
+   AstTimeScaleType ts;
+   const char *fmt;
+   double mjdgap;
+   double result;
+   double xin[2];
+   double xout[2];
+   int df;
+   int ndp;
+
+/* Initialise. */
+   result = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Validate the axis index. */
+   astValidateAxis( this_frame, axis, 1, "astGap" );
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* Use the parent astGap function unless the Format attribute indicates
+   that axis values are to be formatted as multi-field date/time strings. */
+   fmt = astGetFormat( this, 0 );
+   df = DateFormat( fmt, &ndp, NULL, status );
+   if( !df ) {
+      result = (*parent_gap)( this_frame, axis, gap, ntick, status );
+
+/* Otherwise. */
+   } else {
+
+/* Get a Mapping which converts TimeFrame values to MJD values. */
+      ts = astGetTimeScale( this );
+      map = MakeMap( this, astGetSystem( this ), AST__MJD, ts, ts,
+                     astGetTimeOrigin( this ), 0.0, astGetUnit( this, 0 ),
+                     "d", "astGap", status );
+      if( map ) {
+
+/* Use it to transform two TimeFrame times to MJD. The first is the
+   current time, and the second is the current time plus the target gap. */
+         xin[ 0 ] = astCurrentTime( this );
+         xin[ 1 ] = xin[ 0 ] + gap;
+         astTran1( map, 2, xin, 1, xout );
+
+/* Find the target MJD gap. */
+         mjdgap = xout[ 1 ] - xout[ 0 ];
+
+/* If it is 1 year or more, use the parent astGap method to find a nice
+   number of years, and convert back to days. */
+         if( mjdgap >= 365.25 ) {
+            mjdgap = 365.25*(*parent_gap)( this_frame, axis, mjdgap/365.25, ntick, status );
+
+/* If it is more than 270 days days use 1 year. */
+         } else if( mjdgap > 270.0 ) {
+            mjdgap = 365.25;
+            *ntick = 4;
+
+/* If it is more than 150 days days use 180 days (roughly half a year).
+   Use 6 divisions (30 days each, or roughly 1 month). */
+         } else if( mjdgap > 150.0 ) {
+            mjdgap = 180.0;
+            *ntick = 6;
+
+/* If it is more than 90 days days use 120 days (roughly 4 months). */
+         } else if( mjdgap > 90.0 ) {
+            mjdgap = 120.0;
+            *ntick = 4;
+
+/* If it is more than 45 days days use 60 days (roughly 2 months). */
+         } else if( mjdgap > 45.0 ) {
+            mjdgap = 60.0;
+            *ntick = 2;
+
+/* If it is more than 22 days days use 30 days (roughly one month). Use 3
+   ten day divisions. */
+         } else if( mjdgap > 22.0 ) {
+            mjdgap = 30.0;
+            *ntick = 3;
+
+/* If it is more than 12 days days use 15 days (roughly half a month). */
+         } else if( mjdgap > 12.0 ) {
+            mjdgap = 15.0;
+            *ntick = 3;
+
+/* If it is more than 7.5 days days use 10 days, with 5 two-day divisions. */
+         } else if( mjdgap > 7.5 ) {
+            mjdgap = 10.0;
+            *ntick = 5;
+
+/* If it is more than 4.5 days days use 5 days. */
+         } else if( mjdgap > 4.5 ) {
+            mjdgap = 5.0;
+            *ntick = 5;
+
+/* If it is more than 3 days days use 4 days. */
+         } else if( mjdgap > 3.0 ) {
+            mjdgap = 4.0;
+            *ntick = 4;
+
+/* If it is more than 1.5 days days use 2 days. */
+         } else if( mjdgap > 1.5 ) {
+            mjdgap = 2.0;
+            *ntick = 2;
+
+/* If it is more than 0.5 of a day use 1 day. */
+         } else if( mjdgap > 0.5 ) {
+            mjdgap = 1.0;
+            *ntick = 4;
+
+/* Otherwise, if the format indicates that no time field is allowed,
+   use 1 day. */
+         } else if( ndp < 0 ) {
+            mjdgap = 1.0;
+            *ntick = 2;
+
+/* Otherwise (i.e. if the target gap is 0.5 day or less and the format
+   indicates that a time field is allowed), choose a value which looks
+   nice. */
+         } else if( mjdgap >= 6.0/24.0 ) {    /* 12 hours */
+            mjdgap = 12.0/24.0;
+            *ntick = 4;
+
+         } else if( mjdgap >= 3.0/24.0 ) {     /* 6 hours */
+            mjdgap = 6.0/24.0;
+            *ntick = 3;
+
+         } else if( mjdgap >= 1.0/24.0 ) {     /* 2 hours */
+            mjdgap = 2.0/24.0;
+            *ntick = 4;
+
+         } else if( mjdgap >= 30.0/1440.0 ) {     /* 1 hour */
+            mjdgap = 60.0/1440.0;
+            *ntick = 4;
+
+         } else if( mjdgap >= 15.0/1440.0 ) {  /* 30 minutes */
+            mjdgap = 30.0/1440.0;
+            *ntick = 3;
+
+         } else if( mjdgap >= 5.0/1440.0 ) {  /* 10 minutes */
+            mjdgap = 10.0/1440.0;
+            *ntick = 5;
+
+         } else if( mjdgap >= 2.5/1440.0 ) {  /* 5 minutes */
+            mjdgap = 5.0/1440.0;
+            *ntick = 5;
+
+         } else if( mjdgap >= 1.0/1440.0 ) {  /* 2 minutes */
+            mjdgap = 2.0/1440.0;
+            *ntick = 4;
+
+         } else if( mjdgap >= 0.5/1440.0 ) {   /* 1 minute */
+            mjdgap = 1.0/1440.0;
+            *ntick = 4;
+
+         } else if( mjdgap >= 15.0/86400.0 ) { /* 30 seconds */
+            mjdgap = 30.0/86400.0;
+            *ntick = 3;
+
+         } else if( mjdgap >= 5.0/86400.0 ) { /* 10 seconds */
+            mjdgap = 10.0/86400.0;
+            *ntick = 5;
+
+         } else if( mjdgap >= 2.5/86400.0 ) { /* 5 seconds */
+            mjdgap = 5.0/86400.0;
+            *ntick = 5;
+
+         } else if( mjdgap >= 1.0/86400.0 ) { /* 2 seconds */
+            mjdgap = 2.0/86400.0;
+            *ntick = 4;
+
+         } else if( mjdgap >= 0.5/86400.0 ) {  /* 1 second */
+            mjdgap = 1.0/86400.0;
+            *ntick = 4;
+
+         } else {                              /* Less than 1 second */
+            mjdgap = 86400.0*(*parent_gap)( this_frame, axis, mjdgap/86400.0, ntick, status );
+
+         }
+
+/* Convert the MJD gap back into the system of the supplied TimeFrame. */
+         xout[ 1 ] = xout[ 0 ] + mjdgap;
+         astTran1( map, 2, xout, 0, xin );
+         result = xin[ 1 ] - xin[ 0 ];
+
+/* Free resources */
+         map = astAnnul( map );
+
+/* If no Mapping could be found, use the parent astGap method. */
+      } else {
+         result = (*parent_gap)( this_frame, axis, gap, ntick, status );
+      }
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = 0.0;
+
+/* Return the result. */
+   return result;
+}
+
+static int GetActiveUnit( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetActiveUnit
+
+*  Purpose:
+*     Obtain the value of the ActiveUnit flag for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     int GetActiveUnit( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astGetActiveUnit protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function returns the value of the ActiveUnit flag for a
+*    TimeFrame, which is always 1.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The value to use for the ActiveUnit flag (1).
+
+*/
+   return 1;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the protected astGetAttrib
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a TimeFrame, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - The returned string pointer may point at memory allocated
+*     within the TimeFrame, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the TimeFrame. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;           /* Pointer to the TimeFrame structure */
+   AstTimeScaleType ts;          /* Time scale */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   char *new_attrib;             /* Pointer value to new attribute name */
+   const char *result;           /* Pointer value to return */
+   double dval;                  /* Attribute value */
+   int len;                      /* Length of attrib string */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* First look for axis attributes defined by the Frame class. Since a
+   TimeFrame has only 1 axis, we allow these attributes to be specified
+   without a trailing "(axis)" string. */
+   if ( !strcmp( attrib, "direction" ) ||
+        !strcmp( attrib, "bottom" ) ||
+        !strcmp( attrib, "top" ) ||
+        !strcmp( attrib, "format" ) ||
+        !strcmp( attrib, "label" ) ||
+        !strcmp( attrib, "symbol" ) ||
+        !strcmp( attrib, "unit" ) ) {
+
+/* Create a new attribute name from the original by appending the string
+   "(1)" and then use the parent GetAttrib method. */
+      new_attrib = astMalloc( len + 4 );
+      if( new_attrib ) {
+         memcpy( new_attrib, attrib, len );
+         memcpy( new_attrib + len, "(1)", 4 );
+         result = (*parent_getattrib)( this_object, new_attrib, status );
+         new_attrib = astFree( new_attrib );
+      }
+
+/* AlignTimeScale. */
+/* --------------- */
+/* Obtain the AlignTimeScale code and convert to a string. */
+   } else if ( !strcmp( attrib, "aligntimescale" ) ) {
+      ts = astGetAlignTimeScale( this );
+      if ( astOK ) {
+         result = TimeScaleString( ts, status );
+
+/* Report an error if the value was not recognised. */
+         if ( !result ) {
+            astError( AST__SCSIN,
+                     "astGetAttrib(%s): Corrupt %s contains invalid AlignTimeScale "
+                     "identification code (%d).", status, astGetClass( this ),
+                     astGetClass( this ), (int) ts );
+         }
+      }
+
+/* ClockLat. */
+/* ------- */
+   } else if ( !strcmp( attrib, "clocklat" ) ) {
+      result = astGetAttrib( this, "obslat" );
+
+/* ClockLon. */
+/* ------- */
+   } else if ( !strcmp( attrib, "clocklon" ) ) {
+      result = astGetAttrib( this, "obslon" );
+
+/* TimeOrigin. */
+/* ----------- */
+   } else if ( !strcmp( attrib, "timeorigin" ) ) {
+      dval = GetTimeOriginCur( this, status );
+      if( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* LTOffset. */
+/* --------- */
+   } else if ( !strcmp( attrib, "ltoffset" ) ) {
+      dval = astGetLTOffset( this );
+      if( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* TimeScale. */
+/* ---------- */
+/* Obtain the TimeScale code and convert to a string. */
+   } else if ( !strcmp( attrib, "timescale" ) ) {
+      ts = astGetTimeScale( this );
+      if ( astOK ) {
+         result = TimeScaleString( ts, status );
+
+/* Report an error if the value was not recognised. */
+         if ( !result ) {
+            astError( AST__SCSIN,
+                     "astGetAttrib(%s): Corrupt %s contains invalid TimeScale "
+                     "identification code (%d).", status, astGetClass( this ),
+                     astGetClass( this ), (int) ts );
+         }
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetTimeOriginCur( AstTimeFrame *this, int *status ) {
+/*
+*  Name:
+*     GetTimeOriginCur
+
+*  Purpose:
+*     Obtain the TimeOrigin attribute for a TimeFrame in current units.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     double GetTimeOriginCur( AstTimeFrame *this, int *status )
+
+*  Class Membership:
+*     TimeFrame virtual function
+
+*  Description:
+*     This function returns the TimeOrigin attribute for a TimeFrame, in
+*     the current units of the TimeFrame. The protected astGetTimeOrigin
+*     method can be used to obtain the time origin in the default units of
+*     the TimeFrame's System.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The TimeOrigin value, in the units, system and timescale specified
+*     by the current values of the Unit, System and TimeScale attributes
+*     within "this".
+
+*  Notes:
+*     - AST__BAD is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   const char *cur;
+   const char *def;
+   double result;
+   double defval;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the value in the default units */
+   result = astGetTimeOrigin( this );
+
+/* If non-zero we convert to the current units.*/
+   if( result != 0.0 && result != AST__BAD ) {
+
+/* Get the default units for the TimeFrame's System. */
+      def = DefUnit( astGetSystem( this ), "astGetTimeOrigin", "TimeFrame", status );
+
+/* Get the current units from the TimeFrame. */
+      cur = astGetUnit( this, 0 );
+
+/* If the units differ, get a Mapping from default to current units. */
+      if( cur && def ){
+         if( strcmp( cur, def ) ) {
+            map = astUnitMapper( def, cur, NULL, NULL );
+
+/* Report an error if the units are incompatible. */
+            if( !map ) {
+               astError( AST__BADUN, "%s(%s): The current units (%s) are not suitable "
+                         "for a TimeFrame.", status, "astGetTimeOrigin", astGetClass( this ),
+                         cur );
+
+/* Otherwise, transform the stored origin value.*/
+            } else {
+               defval = result;
+               astTran1( map, 1, &defval, 1, &result );
+               map = astAnnul( map );
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetDomain( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetDomain
+
+*  Purpose:
+*     Obtain a pointer to the Domain attribute string for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *GetDomain( AstFrame *this, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astGetDomain protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function returns a pointer to the Domain attribute string
+*    for a TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a constant null-terminated string containing the
+*     Domain value.
+
+*  Notes:
+*     - The returned pointer or the string it refers to may become
+*     invalid following further invocation of this function or
+*     modification of the TimeFrame.
+*     - A NULL pointer is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;           /* Pointer to TimeFrame structure */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* If a Domain attribute string has been set, invoke the parent method
+   to obtain a pointer to it. */
+   if ( astTestDomain( this ) ) {
+      result = (*parent_getdomain)( this_frame, status );
+
+/* Otherwise, provide a pointer to a suitable default string. */
+   } else {
+      result = "TIME";
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetEpoch( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetEpoch
+
+*  Purpose:
+*     Get a value for the Epoch attribute of a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     double GetEpoch( AstFrame *this, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astGetEpoch method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function returns a value for the Epoch attribute of a
+*     TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Epoch attribute value.
+
+*  Notes:
+*     - A value of AST__BAD will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   AstSystemType sys;
+   AstTimeFrame *this;
+   AstTimeScaleType ts;
+   const char *u;
+   double oldval;
+   double result;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* If an Epoch attribute value has been set, invoke the parent method
+   to obtain it. */
+   if ( astTestEpoch( this ) ) {
+      result = (*parent_getepoch)( this_frame, status );
+
+/* Otherwise, if the TimeOrigin value is set in the TimeFrame,
+   return it, converted to an absolute TDB MJD. */
+   } else if( astTestTimeOrigin( this ) ){
+
+/* Get the required properties of the TimeFrame. */
+      oldval = astGetTimeOrigin( this );
+      ts = astGetTimeScale( this );
+      sys = astGetSystem( this );
+      u = DefUnit( sys, "astGetEpoch", "TimeFrame", status );
+
+/* Epoch is defined as a TDB value. If the timescale is stored in an angular
+   timescale such as UT1, then we would not normally be able to convert it
+   to TDB since knowledge of DUT1 is required (the difference between UTC
+   and UT1). Since the default Epoch value is not critical we assume a DUT1
+   value of zero in this case. We first map the stored value to UT1 then
+   from UTC to TDB (using the approximation UT1 == UTC). */
+      if( ts == AST__UT1 || ts == AST__GMST ||
+          ts == AST__LAST || ts == AST__LMST ) {
+         map = MakeMap( this, sys, AST__MJD, ts, AST__UT1, 0.0, 0.0, u,
+                        "d", "astGetEpoch", status );
+         if( map ) {
+            astTran1( map, 1, &oldval, 1, &result );
+            map = astAnnul( map );
+
+/* Update the values to use when converting to TBD. */
+            oldval = result;
+            ts = AST__UTC;
+            sys = AST__MJD;
+            u = "d";
+
+         } else if( astOK ) {
+            astError( AST__INTER, "astGetEpoch(%s): No Mapping from %s to "
+                      "UT1 (AST internal programming error).", status,
+                      astGetClass( this ), TimeScaleString(  ts, status ) );
+         }
+      }
+
+/* Now convert to TDB */
+      map = MakeMap( this, sys, AST__MJD, ts, AST__TDB, 0.0, 0.0, u,
+                     "d", "astGetEpoch", status );
+      if( map ) {
+         oldval = astGetTimeOrigin( this );
+         astTran1( map, 1, &oldval, 1, &result );
+         map = astAnnul( map );
+
+      } else if( astOK ) {
+         astError( AST__INTER, "astGetEpoch(%s): No Mapping from %s to "
+                   "TDB (AST internal programming error).", status,
+                   astGetClass( this ), TimeScaleString(  ts, status ) );
+      }
+
+/* Otherwise, return the default Epoch value from the parent Frame. */
+   } else {
+      result =  (*parent_getepoch)( this_frame, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetLabel( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetLabel
+
+*  Purpose:
+*     Access the Label string for a TimeFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *GetLabel( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astGetLabel method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Label string for a specified axis
+*     of a TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated character string containing the
+*     requested information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstMapping *map;              /* Mapping between units */
+   AstSystemType system;         /* Code identifying type of time coordinates */
+   char *new_lab;                /* Modified label string */
+   const char *fmt;              /* Pointer to original Format string */
+   const char *result;           /* Pointer to label string */
+   double ltoff;                 /* Local Time offset from UTC (hours) */
+   double orig;                  /* Time origin (seconds) */
+   int fmtSet;                   /* Was Format attribute set? */
+   int ndp;                      /* Number of decimal places for seconds field */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise. */
+   result = NULL;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astGetLabel" );
+
+/* Check if a value has been set for the required axis label string. If so,
+   invoke the parent astGetLabel method to obtain a pointer to it. */
+   if ( astTestLabel( this, axis ) ) {
+      result = (*parent_getlabel)( this, axis, status );
+
+/* Otherwise, provide a suitable default label. */
+   } else {
+
+/* If the Format attribute indicates that time values will be formatted
+   as dates, then choose a suitable label. */
+      fmt = astGetFormat( this, 0 );
+      if( DateFormat( fmt, &ndp, NULL, status ) ) {
+         result = ( ndp >= 0 ) ? "Date/Time" : "Date";
+
+/* Otherwise, identify the time coordinate system described by the
+   TimeFrame. */
+      } else {
+         system = astGetSystem( this );
+
+/* If OK, supply a pointer to a suitable default label string. */
+         if ( astOK ) {
+            result = strcpy( getlabel_buff, SystemLabel( system, status ) );
+            getlabel_buff[ 0 ] = toupper( getlabel_buff[ 0 ] );
+
+/* If a non-zero TimeOrigin has been specified, include the offset now as a
+   date/time string. */
+            orig = astGetTimeOrigin( this );
+            if( orig != 0.0 ) {
+
+/* Save the Format attribute, and then temporarily set it to give a date/time
+   string. */
+               fmt = astStore( NULL, fmt, strlen( fmt ) + 1 );
+               fmtSet = astTestFormat( this, 0 );
+               astSetFormat( this, 0, "iso.0" );
+
+/* Format the origin value as an absolute time and append it to the
+   returned label string. Note, the origin always corresponds to a
+   TimeFrame axis value of zero. */
+               sprintf( getlabel_buff + strlen( getlabel_buff ), " offset from %s",
+                        astFormat( this, 0, 0.0 ) );
+
+/* Re-instate the original Format value. */
+               if( fmtSet ) {
+                  astSetFormat( this, 0, fmt );
+               } else {
+                  astClearFormat( this, 0 );
+               }
+
+/* Free the memory holding the copy of the format string. */
+               fmt = astFree( (char *) fmt );
+
+/* If the time of day is "00:00:00", remove it. */
+               if( !strcmp( getlabel_buff + strlen( getlabel_buff ) - 8, "00:00:00" ) ) {
+                  getlabel_buff[ strlen( getlabel_buff ) - 8 ] = 0;
+               }
+            }
+
+/* Modify this default to take account of the current value of the Unit
+   attribute, if set. */
+            if( astTestUnit( this, axis ) ) {
+
+/* Find a Mapping from the default Units for the current System, to the
+   units indicated by the Unit attribute. This Mapping is used to modify
+   the existing default label appropriately. For instance, if the default
+   units is "yr" and the actual units is "log(yr)", then the default label
+   of "Julian epoch" is changed to "log( Julian epoch )". */
+               map = astUnitMapper( DefUnit( system, "astGetLabel",
+                                             astGetClass( this ), status ),
+                                    astGetUnit( this, axis ), result,
+                                    &new_lab );
+               if( new_lab ) {
+                  result = strcpy( getlabel_buff, new_lab );
+                  new_lab = astFree( new_lab );
+               }
+
+/* Annul the unused Mapping. */
+               if( map ) map = astAnnul( map );
+            }
+         }
+      }
+
+/* If the time is a Local Time, indicate the offset from UTC. */
+      if( astGetTimeScale( this ) == AST__LT ) {
+         ltoff = astGetLTOffset( this );
+         if( ltoff >= 0.0 ) {
+            sprintf( getlabel_buff, "%s (UTC+%g)", result, ltoff );
+         } else {
+            sprintf( getlabel_buff, "%s (UTC-%g)", result, -ltoff );
+         }
+         result = getlabel_buff;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetSymbol( AstFrame *this, int axis, int *status ) {
+/*
+*  Name:
+*     GetSymbol
+
+*  Purpose:
+*     Obtain a pointer to the Symbol string for a TimeFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *GetSymbol( AstFrame *this, int axis, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astGetSymbol method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Symbol string for a specified axis
+*     of a TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     axis
+*        Axis index (zero-based) identifying the axis for which information is
+*        required.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated character string containing the
+*     requested information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstMapping *map;              /* Mapping between units */
+   AstSystemType system;         /* Code identifying type of sky coordinates */
+   char *new_sym;                /* Modified symbol string */
+   const char *result;           /* Pointer to symbol string */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Initialise. */
+   result = NULL;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astGetSymbol" );
+
+/* Check if a value has been set for the required axis symbol string. If so,
+   invoke the parent astGetSymbol method to obtain a pointer to it. */
+   if ( astTestSymbol( this, axis ) ) {
+      result = (*parent_getsymbol)( this, axis, status );
+
+/* Otherwise, identify the sky coordinate system described by the TimeFrame. */
+   } else {
+      system = astGetSystem( this );
+
+/* If OK, supply a pointer to a suitable default Symbol string. */
+      if ( astOK ) {
+
+         if( system == AST__MJD ) {
+	    result = "MJD";
+         } else if( system == AST__JD ) {
+	    result = "JD";
+         } else if( system == AST__BEPOCH ) {
+	    result = "BEP";
+         } else if( system == AST__JEPOCH ) {
+	    result = "JEP";
+
+/* Report an error if the coordinate system was not recognised. */
+         } else {
+	    astError( AST__SCSIN, "astGetSymbol(%s): Corrupt %s contains "
+		      "invalid System identification code (%d).", status,
+                      astGetClass( this ), astGetClass( this ), (int) system );
+         }
+
+/* Modify this default to take account of the current value of the Unit
+   attribute, if set. */
+         if( astTestUnit( this, axis ) ) {
+
+/* Find a Mapping from the default Units for the current System, to the
+   units indicated by the Unit attribute. This Mapping is used to modify
+   the existing default symbol appropriately. For instance, if the default
+   units is "yr" and the actual units is "log(yr)", then the default symbol
+   of "JEP" is changed to "log( JEP )". */
+            map = astUnitMapper( DefUnit( system, "astGetSymbol",
+                                          astGetClass( this ), status ),
+                                 astGetUnit( this, axis ), result,
+                                 &new_sym );
+            if( new_sym ) {
+               result = strcpy( getsymbol_buff, new_sym );
+               new_sym = astFree( new_sym );
+            }
+
+/* Annul the unused Mapping. */
+            if( map ) map = astAnnul( map );
+
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType GetAlignSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetAlignSystem
+
+*  Purpose:
+*     Obtain the AlignSystem attribute for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "Specframe.h"
+*     AstSystemType GetAlignSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astGetAlignSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the AlignSystem attribute for a TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The AlignSystem value.
+
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;           /* Pointer to TimeFrame structure */
+   AstSystemType result;         /* Value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* If a AlignSystem attribute has been set, invoke the parent method to obtain
+   it. */
+   if ( astTestAlignSystem( this ) ) {
+      result = (*parent_getalignsystem)( this_frame, status );
+
+/* Otherwise, provide a suitable default. */
+   } else {
+      result = AST__MJD;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstTimeScaleType GetAlignTimeScale( AstTimeFrame *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetAlignTimeScale
+
+*  Purpose:
+*     Obtain the AlignTimeScale attribute for a TimeFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     AstTimeScaleType GetAlignTimeScale( AstTimeFrame *this )
+
+*  Class Membership:
+*     TimeFrame virtual function
+
+*  Description:
+*     This function returns the System attribute for a TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+
+*  Returned Value:
+*     The System value.
+
+*  Notes:
+*     - AST__BADTS is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstTimeScaleType result;
+   AstTimeScaleType ts;
+
+/* Initialise. */
+   result = AST__BADTS;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If a value has been set, return it. */
+   if( this->aligntimescale != AST__BADTS ) {
+      result = this->aligntimescale;
+
+/* Otherwise, return a default depending on the current TimeScale value */
+   } else {
+      ts = astGetTimeScale( this );
+      if ( ts == AST__UT1 || ts == AST__LAST || ts == AST__LMST || ts == AST__GMST ) {
+         result = AST__UT1;
+      } else {
+         result = AST__TAI;
+      }
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstSystemType GetSystem( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetSystem
+
+*  Purpose:
+*     Obtain the System attribute for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     AstSystemType GetSystem( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astGetSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns the System attribute for a TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System value.
+
+*  Notes:
+*     - AST__BADSYSTEM is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;           /* Pointer to TimeFrame structure */
+   AstSystemType result;         /* Value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* If a System attribute has been set, invoke the parent method to obtain
+   it. */
+   if ( astTestSystem( this ) ) {
+      result = (*parent_getsystem)( this_frame, status );
+
+/* Otherwise, provide a suitable default. */
+   } else {
+      result = AST__MJD;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstTimeScaleType GetTimeScale( AstTimeFrame *this, int *status ) {
+/*
+*+
+*  Name:
+*     astGetTimeScale
+
+*  Purpose:
+*     Obtain the TimeScale attribute for a TimeFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     AstTimeScaleType GetTimeScale( AstTimeFrame *this )
+
+*  Class Membership:
+*     TimeFrame virtual function
+
+*  Description:
+*     This function returns the System attribute for a TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+
+*  Returned Value:
+*     The System value.
+
+*  Notes:
+*     - AST__BADTS is returned if this function is invoked with
+*     the global error status set or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstTimeScaleType result;
+
+/* Initialise. */
+   result = AST__BADTS;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If a value has been set, return it. */
+   if( this->timescale != AST__BADTS ) {
+      result = this->timescale;
+
+/* Otherwise, return a default depending on the current System value. */
+   } else {
+      if ( astGetSystem( this ) == AST__BEPOCH ) {
+         result = AST__TT;
+      } else {
+         result = AST__TAI;
+      }
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetTitle( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     GetTitle
+
+*  Purpose:
+*     Obtain a pointer to the Title string for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *GetTitle( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astGetTitle method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Title string for a TimeFrame.
+*     A pointer to a suitable default string is returned if no Title value has
+*     previously been set.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a null-terminated character string containing the requested
+*     information.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstSystemType system;         /* Code identifying type of coordinates */
+   AstTimeScaleType ts;          /* Time scale value */
+   AstTimeFrame *this;           /* Pointer to TimeFrame structure */
+   const char *fmt;              /* Pointer to original Format string */
+   const char *result;           /* Pointer to result string */
+   double ltoff;                 /* Local Time offset from UTC (hours) */
+   double orig;                  /* Time origin (seconds) */
+   int fmtSet;                   /* Was Format attribute set? */
+   int nc;                       /* No. of characters added */
+   int ndp;                      /* Number of decimal places */
+   int pos;                      /* Buffer position to enter text */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_frame);
+
+/* Initialise. */
+   result = NULL;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* See if a Title string has been set. If so, use the parent astGetTitle
+   method to obtain a pointer to it. */
+   if ( astTestTitle( this ) ) {
+      result = (*parent_gettitle)( this_frame, status );
+
+/* Otherwise, we will generate a default Title string. Obtain the values of the
+   TimeFrame's attributes that determine what this string will be. */
+   } else {
+      system = astGetSystem( this );
+      orig = GetTimeOriginCur( this, status );
+      ts = astGetTimeScale( this );
+      if ( astOK ) {
+         result = gettitle_buff;
+
+/* Begin with the system's default label. */
+         pos = sprintf( gettitle_buff, "%s", SystemLabel( system, status ) );
+         gettitle_buff[ 0 ] = toupper( gettitle_buff[ 0 ] );
+
+/* Append the time scale code, if a value has been set for the timescale.
+   Do not do this if the system is BEPOCH since BEPOCH can only be used
+   with the TT timescale. */
+         if( system != AST__BEPOCH && astTestTimeScale( this ) ) {
+            nc = sprintf( gettitle_buff + pos, " [%s", TimeScaleString( ts, status ) );
+            pos += nc;
+
+/* For Local Time, include the offset from UTC. */
+            if( ts == AST__LT ) {
+               ltoff = astGetLTOffset( this );
+               if( ltoff >= 0.0 ) {
+                  nc = sprintf( gettitle_buff + pos, " (UTC+%g)", ltoff );
+               } else {
+                  nc = sprintf( gettitle_buff + pos, " (UTC-%g)", -ltoff );
+               }
+               pos += nc;
+            }
+
+/* Close the brackets. */
+            nc = sprintf( gettitle_buff + pos, "]" );
+            pos += nc;
+         }
+
+/* If a non-zero offset has been specified, and the Format attribute does
+   not indicate a date string (which is always absolute), include the
+   offset now as a date/time string. */
+         fmt = astGetFormat( this, 0 );
+         if( orig != 0.0 && !DateFormat( fmt, &ndp, NULL, status ) ) {
+
+/* Save the Format attribute, and then temporarily set it to give a date/time
+   string. */
+            fmt = astStore( NULL, fmt, strlen( fmt ) + 1 );
+            fmtSet = astTestFormat( this, 0 );
+            astSetFormat( this, 0, "iso.0" );
+
+/* Format the origin value as an absolute time and append it to the
+   returned title string. Note, the origin always corresponds to a
+   TimeFrame axis value of zero. */
+            nc = sprintf( gettitle_buff+pos, " offset from %s",
+                          astFormat( this, 0, 0.0 ) );
+            pos += nc;
+
+/* Re-instate the original Format value. */
+            if( fmtSet ) {
+               astSetFormat( this, 0, fmt );
+            } else {
+               astClearFormat( this, 0 );
+            }
+
+/* Free the Format string copy. */
+            fmt = astFree( (char *) fmt );
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the returned pointer value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *GetUnit( AstFrame *this_frame, int axis, int *status ) {
+/*
+*  Name:
+*     GetUnit
+
+*  Purpose:
+*     Obtain a pointer to the Unit string for a TimeFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *GetUnit( AstFrame *this_frame, int axis )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astGetUnit method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function returns a pointer to the Unit string for a specified axis
+*     of a TimeFrame. If the Unit attribute has not been set for the axis, a
+*     pointer to a suitable default string is returned instead.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     axis
+*        The number of the axis (zero-based) for which information is required.
+
+*  Returned Value:
+*     A pointer to a null-terminated string containing the Unit value.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;           /* Pointer to the TimeFrame structure */
+   AstSystemType system;         /* The TimeFrame's System value */
+   const char *result;           /* Pointer value to return */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astGetUnit" );
+
+/* If a value has been set for the Unit attribute, use the parent
+   GetUnit method to return a pointer to the required Unit string. */
+   if( astTestUnit( this, axis ) ){
+      result = (*parent_getunit)( this_frame, axis, status );
+
+/* Otherwise, identify the time coordinate system described by the
+   TimeFrame. */
+   } else {
+      system = astGetSystem( this );
+
+/* Return a string describing the default units. */
+      result = DefUnit( system, "astGetUnit", astGetClass( this ), status );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+void astInitTimeFrameVtab_(  AstTimeFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitTimeFrameVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a TimeFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void astInitTimeFrameVtab( AstTimeFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     TimeFrame vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the TimeFrame class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstFrameVtab *frame;          /* Pointer to Frame component of Vtab */
+   AstMapping *map;              /* Temporary Maping */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   double utc_epoch;             /* Unix epoch as a UTC MJD */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitFrameVtab( (AstFrameVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsATimeFrame) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstFrameVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+   vtab->ClearAlignTimeScale = ClearAlignTimeScale;
+   vtab->TestAlignTimeScale = TestAlignTimeScale;
+   vtab->GetAlignTimeScale = GetAlignTimeScale;
+   vtab->SetAlignTimeScale = SetAlignTimeScale;
+
+   vtab->ClearTimeOrigin = ClearTimeOrigin;
+   vtab->TestTimeOrigin = TestTimeOrigin;
+   vtab->GetTimeOrigin = GetTimeOrigin;
+   vtab->SetTimeOrigin = SetTimeOrigin;
+
+   vtab->ClearLTOffset = ClearLTOffset;
+   vtab->TestLTOffset = TestLTOffset;
+   vtab->GetLTOffset = GetLTOffset;
+   vtab->SetLTOffset = SetLTOffset;
+
+   vtab->ClearTimeScale = ClearTimeScale;
+   vtab->TestTimeScale = TestTimeScale;
+   vtab->GetTimeScale = GetTimeScale;
+   vtab->SetTimeScale = SetTimeScale;
+
+   vtab->CurrentTime = CurrentTime;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   frame = (AstFrameVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_getdomain = frame->GetDomain;
+   frame->GetDomain = GetDomain;
+
+   parent_getsystem = frame->GetSystem;
+   frame->GetSystem = GetSystem;
+   parent_setsystem = frame->SetSystem;
+   frame->SetSystem = SetSystem;
+   parent_clearsystem = frame->ClearSystem;
+   frame->ClearSystem = ClearSystem;
+
+   parent_getalignsystem = frame->GetAlignSystem;
+   frame->GetAlignSystem = GetAlignSystem;
+
+   parent_getlabel = frame->GetLabel;
+   frame->GetLabel = GetLabel;
+
+   parent_getsymbol = frame->GetSymbol;
+   frame->GetSymbol = GetSymbol;
+
+   parent_gettitle = frame->GetTitle;
+   frame->GetTitle = GetTitle;
+
+   parent_getepoch = frame->GetEpoch;
+   frame->GetEpoch = GetEpoch;
+
+   parent_getunit = frame->GetUnit;
+   frame->GetUnit = GetUnit;
+
+   parent_setunit = frame->SetUnit;
+   frame->SetUnit = SetUnit;
+
+   parent_match = frame->Match;
+   frame->Match = Match;
+
+   parent_overlay = frame->Overlay;
+   frame->Overlay = Overlay;
+
+   parent_subframe = frame->SubFrame;
+   frame->SubFrame = SubFrame;
+
+   parent_format = frame->Format;
+   frame->Format = Format;
+
+   parent_unformat = frame->Unformat;
+   frame->Unformat = Unformat;
+
+   parent_abbrev = frame->Abbrev;
+   frame->Abbrev = Abbrev;
+
+   parent_fields = frame->Fields;
+   frame->Fields = Fields;
+
+   parent_gap = frame->Gap;
+   frame->Gap = Gap;
+
+   parent_centre = frame->Centre;
+   frame->Centre = Centre;
+
+/* Store replacement pointers for methods which will be over-ridden by new
+   member functions implemented here. */
+   frame->GetActiveUnit = GetActiveUnit;
+   frame->TestActiveUnit = TestActiveUnit;
+   frame->ValidateSystem = ValidateSystem;
+   frame->SystemString = SystemString;
+   frame->SystemCode = SystemCode;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetDump( vtab, Dump, "TimeFrame",
+               "Description of time coordinate system" );
+
+/* Convert the Unix Epoch (00:00:00 UTC 1 January 1970 AD) from UTC to TAI. */
+   LOCK_MUTEX2
+   map = MakeMap( NULL, AST__MJD, AST__MJD, AST__UTC, AST__TAI,
+                  0.0, 0.0, "d", "d", "astInitTimeFrameVtab", status );
+   utc_epoch = UNIX_EPOCH;
+   astTran1( map, 1, &utc_epoch, 1, &tai_epoch );
+   map = astAnnul( map );
+   UNLOCK_MUTEX2
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static AstMapping *MakeMap( AstTimeFrame *this, AstSystemType sys1,
+                            AstSystemType sys2, AstTimeScaleType ts1,
+                            AstTimeScaleType ts2, double off1, double off2,
+                            const char *unit1, const char *unit2,
+                            const char *method, int *status ){
+/*
+*  Name:
+*     MakeMap
+
+*  Purpose:
+*     Make a Mapping between stated timescales and systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     AstMapping *MakeMap( AstTimeFrame *this, AstSystemType sys1,
+*                          AstSystemType sys2, AstTimeScaleType ts1,
+*                          AstTimeScaleType ts2, double off1, double off2,
+*                          const char *unit1, const char unit2,
+*                          const char *method, int *status )
+
+*  Class Membership:
+*     TimeFrame member function
+
+*  Description:
+*     This function creates a Mapping from a stated pair of System and
+*     TimeScale to another stated pair.
+
+*  Parameters:
+*     this
+*        A TimeFrame which specifies extra attributes (the clock position,
+*        time zone, etc) for both input and output.
+*     sys1
+*        The input System.
+*     sys2
+*        The output System.
+*     ts1
+*        The input System.
+*     ts2
+*        The output System.
+*     off1
+*        The axis offset used with the input, in the defaults units
+*        associated with "sys1".
+*     off2
+*        The axis offset used with the output, in the defaults units
+*        associated with "sys2".
+*     unit1
+*        The input units.
+*     unit2
+*        The output units.
+*     method
+*        A string containing the method name to include in error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Mapping. NULL if the timescales were
+*     incompatible.
+
+*/
+
+
+/* Local Variables: */
+   AstMapping *result;
+   AstMapping *tmap;
+   AstMapping *tmap2;
+   AstMapping *umap;
+   AstMapping *umap1;
+   AstMapping *umap2;
+   AstTimeMap *timemap;
+   const char *du;
+   double args[ 4 ];
+   double args_lt[ 1 ];
+   double args_ut[ 1 ];
+   double shift;
+
+/* Check the global error status. */
+   result = NULL;
+   if ( !astOK ) return result;
+
+/* If the timescales are equal... */
+   if( ts1 == ts2 ) {
+
+/* and the time systems are equal... */
+      if( sys1 == sys2 ) {
+
+/* and the time offsets are equal... */
+         if( astEQUALS( off1, off2, 1.0E3 ) ) {
+
+/* and the units are equal, return a UnitMap. */
+            if( !strcmp( unit1, unit2 ) ) {
+               result = (AstMapping *) astUnitMap( 1, "", status );
+
+/* If only the units differ, return the appropriate units Mapping. */
+            } else {
+               result = astUnitMapper( unit1, unit2, NULL, NULL );
+            }
+
+/* If the time offsets differ... */
+         } else {
+
+/* Transform the difference in offsets from the default units associated
+   with the (common) system, to the units associated with the output. */
+            shift = off1 - off2;
+            du = DefUnit( sys1, method, "TimeFrame", status );
+            if( du && strcmp( du, unit2 ) && shift != 0.0 ) {
+               umap = astUnitMapper( DefUnit( sys1, method, "TimeFrame", status ),
+                                     unit2, NULL, NULL );
+               astTran1( umap, 1, &shift, 1, &shift );
+               umap = astAnnul( umap );
+            }
+
+/* Create a ShiftMap to apply the shift. */
+            result = (AstMapping *) astShiftMap( 1, &shift, "", status );
+
+/* If the input and output units also differ, include the appropriate units
+   Mapping. */
+            if( strcmp( unit1, unit2 ) ) {
+               umap = astUnitMapper( unit1, unit2, NULL, NULL );
+               tmap = (AstMapping *) astCmpMap( umap, result, 1, "", status );
+               umap = astAnnul( umap );
+               (void) astAnnul( result );
+               result = tmap;
+            }
+         }
+      }
+   }
+
+/* If the systems and/or timescales differ, we convert first from the
+   input frame to a common frame, then from the common frame to the output
+   frame. */
+   if( !result ) {
+
+/* First, a Mapping from the input units to the default units for the
+   input System (these are the units expected by the TimeMap conversions). */
+      umap1 = astUnitMapper( unit1, DefUnit( sys1, method, "TimeFrame", status ),
+                             NULL, NULL );
+
+/* Now create a null TimeMap. */
+      timemap = astTimeMap( 0, "", status );
+
+/* Store the input time offsets to use. They correspond to the same moment in
+   time (the second is the MJD equivalent of the first). */
+      args[ 0 ] = off1;
+      args[ 1 ] = ToMJD( sys1, off1, status );
+
+/* Add a conversion from the input System to MJD. */
+      if( sys1 == AST__JD ) {
+         astTimeAdd( timemap, "JDTOMJD", 2, args );
+
+      } else if( sys1 == AST__JEPOCH ) {
+         astTimeAdd( timemap, "JEPTOMJD", 2, args );
+
+      } else if( sys1 == AST__BEPOCH ) {
+         astTimeAdd( timemap, "BEPTOMJD", 2, args );
+      }
+
+/* All timescale conversions except UTTOUTC and UTCTOUT require the input (MJD)
+   offset as the first argument. In general, the observers longitude, latitude
+   and altitude are also needed. The Frame class stores longitude values in a
+   +ve eastwards sense, but the TimeMap class needs +ve westwards, so negate
+   the longitude. */
+      args[ 0 ] = args[ 1 ];
+      args[ 1 ] = this ? -astGetObsLon( this ) : 0.0;
+      args[ 2 ] = this ? astGetObsLat( this ) : 0.0;
+      args[ 3 ] = this ? astGetObsAlt( this ) : 0.0;
+
+/* The UTTOUTC and UTCTOUT conversions required just the DUT1 value. */
+      args_ut[ 0 ] = this ? astGetDut1( this ) : 0.0;
+
+/* The LTTOUTC and UTCTOLT conversions required just the time zone
+   correction. */
+      args_lt[ 0 ] = this ? astGetLTOffset( this ) : 0.0;
+
+/* If the input and output timescales differ, now add a conversion from the
+   input timescale to TAI. */
+      if( ts1 != ts2 ) {
+         if( ts1 == AST__TAI ) {
+
+         } else if( ts1 == AST__UTC ) {
+            astTimeAdd( timemap, "UTCTOTAI", 1, args );
+
+         } else if( ts1 == AST__TT ) {
+            astTimeAdd( timemap, "TTTOTAI", 1, args );
+
+         } else if( ts1 == AST__TDB ) {
+            astTimeAdd( timemap, "TDBTOTT", 4, args );
+            astTimeAdd( timemap, "TTTOTAI", 1, args );
+
+         } else if( ts1 == AST__TCG ) {
+            astTimeAdd( timemap, "TCGTOTT", 1, args );
+            astTimeAdd( timemap, "TTTOTAI", 1, args );
+
+         } else if( ts1 == AST__LT ) {
+            astTimeAdd( timemap, "LTTOUTC", 1, args_lt );
+            astTimeAdd( timemap, "UTCTOTAI", 1, args );
+
+         } else if( ts1 == AST__TCB ) {
+            astTimeAdd( timemap, "TCBTOTDB", 1, args );
+            astTimeAdd( timemap, "TDBTOTT", 4, args );
+            astTimeAdd( timemap, "TTTOTAI", 1, args );
+
+         } else if( ts1 == AST__UT1 ) {
+            astTimeAdd( timemap, "UTTOUTC", 1, args_ut );
+            astTimeAdd( timemap, "UTCTOTAI", 1, args );
+
+         } else if( ts1 == AST__GMST ) {
+            astTimeAdd( timemap, "GMSTTOUT", 1, args );
+            astTimeAdd( timemap, "UTTOUTC", 1, args_ut );
+            astTimeAdd( timemap, "UTCTOTAI", 1, args );
+
+         } else if( ts1 == AST__LAST ) {
+            astTimeAdd( timemap, "LASTTOLMST", 3, args );
+            astTimeAdd( timemap, "LMSTTOGMST", 3, args );
+            astTimeAdd( timemap, "GMSTTOUT", 1, args );
+            astTimeAdd( timemap, "UTTOUTC", 1, args_ut );
+            astTimeAdd( timemap, "UTCTOTAI", 1, args );
+
+         } else if( ts1 == AST__LMST ) {
+            astTimeAdd( timemap, "LMSTTOGMST", 3, args );
+            astTimeAdd( timemap, "GMSTTOUT", 1, args );
+            astTimeAdd( timemap, "UTTOUTC", 1, args_ut );
+            astTimeAdd( timemap, "UTCTOTAI", 1, args );
+         }
+
+/* Now add a conversion from TAI to the output timescale. */
+         if( ts2 == AST__TAI ) {
+
+         } else if( ts2 == AST__UTC ) {
+            astTimeAdd( timemap, "TAITOUTC", 1, args );
+
+         } else if( ts2 == AST__TT ) {
+            astTimeAdd( timemap, "TAITOTT", 1, args );
+
+         } else if( ts2 == AST__TDB ) {
+            astTimeAdd( timemap, "TAITOTT", 1, args );
+            astTimeAdd( timemap, "TTTOTDB", 4, args );
+
+         } else if( ts2 == AST__TCG ) {
+            astTimeAdd( timemap, "TAITOTT", 1, args );
+            astTimeAdd( timemap, "TTTOTCG", 1, args );
+
+         } else if( ts2 == AST__TCB ) {
+            astTimeAdd( timemap, "TAITOTT", 1, args );
+            astTimeAdd( timemap, "TTTOTDB", 4, args );
+            astTimeAdd( timemap, "TDBTOTCB", 1, args );
+
+         } else if( ts2 == AST__UT1 ) {
+            astTimeAdd( timemap, "TAITOUTC", 1, args );
+            astTimeAdd( timemap, "UTCTOUT", 1, args_ut );
+
+         } else if( ts2 == AST__GMST ) {
+            astTimeAdd( timemap, "TAITOUTC", 1, args );
+            astTimeAdd( timemap, "UTCTOUT", 1, args_ut );
+            astTimeAdd( timemap, "UTTOGMST", 1, args );
+
+         } else if( ts2 == AST__LAST ) {
+            astTimeAdd( timemap, "TAITOUTC", 1, args );
+            astTimeAdd( timemap, "UTCTOUT", 1, args_ut );
+            astTimeAdd( timemap, "UTTOGMST", 1, args );
+            astTimeAdd( timemap, "GMSTTOLMST", 3, args );
+            astTimeAdd( timemap, "LMSTTOLAST", 3, args );
+
+         } else if( ts2 == AST__LMST ) {
+            astTimeAdd( timemap, "TAITOUTC", 1, args );
+            astTimeAdd( timemap, "UTCTOUT", 1, args_ut );
+            astTimeAdd( timemap, "UTTOGMST", 1, args );
+            astTimeAdd( timemap, "GMSTTOLMST", 3, args );
+
+         } else if( ts2 == AST__LT ) {
+            astTimeAdd( timemap, "TAITOUTC", 1, args );
+            astTimeAdd( timemap, "UTCTOLT", 1, args_lt );
+
+         }
+      }
+
+/* Add a conversion from MJD to the output System, if needed. */
+      args[ 1 ] = off2;
+      if( sys2 == AST__MJD ) {
+         if( args[ 0 ] != off2 ) astTimeAdd( timemap, "MJDTOMJD", 2, args );
+
+      } else if( sys2 == AST__JD ) {
+         astTimeAdd( timemap, "MJDTOJD", 2, args );
+
+      } else if( sys2 == AST__JEPOCH ) {
+         astTimeAdd( timemap, "MJDTOJEP", 2, args );
+
+      } else if( sys2 == AST__BEPOCH ) {
+         astTimeAdd( timemap, "MJDTOBEP", 2, args );
+      }
+
+/* Now, create a Mapping from the default units for the output System (these
+   are the units produced by the TimeMap conversions) to the requested
+   output units. */
+      umap2 = astUnitMapper( DefUnit( sys2, method, "TimeFrame", status ), unit2,
+                             NULL, NULL );
+
+/* If OK, combine the Mappings in series. Note, umap1 and umap2 should
+   always be non-NULL because the suitablity of units strings is checked
+   within OriginSystem - called from within SetSystem. */
+      if( umap1 && umap2 ) {
+         tmap = (AstMapping *) astCmpMap( umap1, timemap, 1, "", status );
+         tmap2 = (AstMapping *) astCmpMap( tmap, umap2, 1, "", status );
+         tmap = astAnnul( tmap );
+         result = astSimplify( tmap2 );
+         tmap2 = astAnnul( tmap2 );
+      }
+
+/* Free remaining resources */
+      if( umap1 ) umap1 = astAnnul( umap1 );
+      if( umap2 ) umap2 = astAnnul( umap2 );
+      timemap = astAnnul( timemap );
+   }
+
+/* Return NULL if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+
+}
+
+static int MakeTimeMapping( AstTimeFrame *target, AstTimeFrame *result,
+                            AstTimeFrame *align_frm, int report,
+                            AstMapping **map, int *status ) {
+/*
+*  Name:
+*     MakeTimeMapping
+
+*  Purpose:
+*     Generate a Mapping between two TimeFrames.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     int MakeTimeMapping( AstTimeFrame *target, AstTimeFrame *result,
+*                          AstTimeFrame *align_frm, int report,
+*                          AstMapping **map, int *status ) {
+
+*  Class Membership:
+*     TimeFrame member function.
+
+*  Description:
+*     This function takes two TimeFrames and generates a Mapping that
+*     converts between them, taking account of differences in their
+*     coordinate systems, offsets, timescales, units, etc.
+
+*  Parameters:
+*     target
+*        Pointer to the first TimeFrame.
+*     result
+*        Pointer to the second TimeFrame.
+*     align_frm
+*        A TimeFrame defining the system and time scale in which to
+*        align the target and result TimeFrames. The AlignSystem and
+*        AlignTimeScale attributes are used for this purpose.
+*     report
+*        Should errors be reported if no match is possible? These reports
+*        will describe why no match was possible.
+*     map
+*        Pointer to a location which is to receive a pointer to the
+*        returned Mapping. The forward transformation of this Mapping
+*        will convert from "target" coordinates to "result"
+*        coordinates, and the inverse transformation will convert in
+*        the opposite direction (all coordinate values in radians).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the Mapping could be generated, or zero if the two
+*     TimeFrames are sufficiently un-related that no meaningful Mapping
+*     can be produced (albeit an "unmeaningful" Mapping will be returned
+*     in this case, which will need to be annulled).
+
+*  Notes:
+*     A value of zero is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map1;             /* Intermediate Mapping */
+   AstMapping *map2;             /* Intermediate Mapping */
+   AstMapping *tmap;             /* Intermediate Mapping */
+   AstSystemType sys1;           /* Code to identify input system */
+   AstSystemType sys2;           /* Code to identify output system */
+   AstTimeScaleType align_ts;    /* Alignment time scale */
+   AstTimeScaleType ts1;         /* Input time scale */
+   AstTimeScaleType ts2;         /* Output time scale */
+   const char *align_unit;       /* Units used for alignment */
+   const char *u1;               /* Input target units */
+   const char *u2;               /* Output target units */
+   double align_off;             /* Axis offset */
+   double ltoff1;                /* Input axis Local Time offset */
+   double ltoff2;                /* Output axis Local Time offset */
+   double off1;                  /* Input axis offset */
+   double off2;                  /* Output axis offset */
+   int arclk;                    /* Align->result depends on clock position? */
+   int ardut;                    /* Align->result depends on Dut1? */
+   int arlto;                    /* Align->result depends on LT offset? */
+   int clkdiff;                  /* Do target and result clock positions differ? */
+   int dut1diff;                 /* Do target and result Dut1 values differ? */
+   int ltodiff;                  /* Do target and result LTOffset values differ? */
+   int match;                    /* Mapping can be generated? */
+   int taclk;                    /* Target->align depends on clock position? */
+   int tadut;                    /* Target->align depends on Dut1? */
+   int talto;                    /* Target->align depends on LT offset? */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise the returned values. */
+   match = 0;
+   *map = NULL;
+
+/* Get the required properties of the input (target) TimeFrame */
+   sys1 = astGetSystem( target );
+   ts1 = astGetTimeScale( target );
+   off1 = astGetTimeOrigin( target );
+   u1 = astGetUnit( target, 0 );
+   ltoff1= astGetLTOffset( target );
+
+/* Get the required properties of the output (result) TimeFrame */
+   sys2 = astGetSystem( result );
+   ts2 = astGetTimeScale( result );
+   off2 = astGetTimeOrigin( result );
+   u2 = astGetUnit( result, 0 );
+   ltoff2= astGetLTOffset( result );
+
+/* Get the timescale in which alignment is to be performed. The alignment
+   System does not matter since they all supported time systems are linearly
+   related, and so the choice of alignment System has no effect on the total
+   Mapping. We arbitrarily choose MJD as the alignment System (if needed). */
+   align_ts = astGetAlignTimeScale( align_frm );
+
+/* The main difference between this function and the MakeMap function is
+   that this function takes account of the requested alignment frame. But
+   the alignment Frame only makes a difference to the overall Mapping if
+   1) the observer's positions are different in the target and result Frame,
+   and 2) one or both of the Mappings to or from the alignment frame depends
+   on the observer's position. If either of these 2 conditions is not met,
+   then the alignment frame can be ignored, and the simpler MakeMap function
+   can be called. See if the observer's positions differ. */
+   clkdiff = ( astGetObsLon( target ) != astGetObsLon( result ) ||
+               astGetObsLat( target ) != astGetObsLat( result ) ||
+               astGetObsAlt( target ) != astGetObsAlt( result ) );
+
+/* See if the Mapping from target to alignment frame depends on the
+   observer's position. */
+   taclk = CLOCK_SCALE( ts1 ) || CLOCK_SCALE( align_ts );
+
+/* See if the Mapping from alignment to result frame depends on the
+   observer's position. */
+   arclk = CLOCK_SCALE( align_ts ) || CLOCK_SCALE( ts2 );
+
+/* In addition, the alignment frame is significant if either of the Mappings
+   depends on DUT1 and the values of the DUT1 attribute are different for the
+   two TimeFrames. */
+   dut1diff = ( astGetDut1( target ) != astGetDut1( result ) );
+   tadut = DUT1_SCALE( ts1 ) != DUT1_SCALE( align_ts );
+   ardut = DUT1_SCALE( align_ts ) != DUT1_SCALE( ts2 );
+
+/* In addition, the alignment frame is significant if either of the Mappings
+   depends on LTOffset and the values of the LTOffset attribute are different
+   for the two TimeFrames. */
+   ltodiff = ( ltoff1 != ltoff2 );
+   talto = LTOFFSET_SCALE( ts1 ) != LTOFFSET_SCALE( align_ts );
+   arlto = LTOFFSET_SCALE( align_ts ) != LTOFFSET_SCALE( ts2 );
+
+/* If the alignment frame can be ignored, use MakeMap */
+   if( ( !clkdiff || !( taclk || arclk ) ) &&
+       ( !ltodiff || !( talto || arlto ) ) &&
+       ( !dut1diff || !( tadut || ardut ) ) ) {
+      *map = MakeMap( target, sys1, sys2, ts1, ts2, off1, off2, u1, u2,
+                      "astSubFrame", status );
+      if( *map ) match = 1;
+
+/* Otherwise, we create the Mapping in two parts; first a Mapping from
+   the target Frame to the alignment Frame (using the target clock, dut1
+   and ltoffset), then a Mapping from the alignment Frame to the results
+   Frame (using the result clock, dut1 and ltoffset). */
+   } else {
+
+/* Create a Mapping from target units/system/timescale/offset to MJD in
+   the alignment timescale with default units and offset equal to the MJD
+   equivalent of the target offset. */
+      align_off = ToMJD( sys1, off1, status );
+      align_unit = DefUnit( AST__MJD, "MakeTimeMap", "TimeFrame", status );
+      map1 = MakeMap( target, sys1, AST__MJD, ts1, align_ts, off1, align_off,
+                      u1, align_unit, "MakeTimeMap", status );
+
+/* Report an error if the timescales were incompatible. */
+      if( !map1 ){
+         match = 0;
+         if( report && astOK ) {
+            astError( AST__INCTS, "astMatch(%s): Alignment in requested "
+                   "timescale (%s) is not possible since one or both of the "
+                   "TimeFrames being aligned refer to the %s timescale.", status,
+                   astGetClass( target ), TimeScaleString( align_ts, status ),
+                   TimeScaleString( ts1, status ) );
+         }
+      }
+
+/* We now create a Mapping that converts from the alignment System (MJD),
+   TimeScale and offset to the result coordinate system. */
+      map2 = MakeMap( result, AST__MJD, sys2, align_ts, ts2, align_off, off2,
+                      align_unit, u2, "MakeTimeMap", status );
+
+/* Report an error if the timescales were incompatible. */
+      if( !map2 ){
+         match = 0;
+         if( report && astOK ) {
+            astError( AST__INCTS, "astMatch(%s): Alignment in requested "
+                   "timescale (%s) is not possible since one or both of the "
+                   "TimeFrames being aligned refer to the %s timescale.", status,
+                   astGetClass( result ), TimeScaleString( align_ts, status ),
+                   TimeScaleString( ts2, status ) );
+         }
+      }
+
+/* Combine these two Mappings. */
+      if( map1 && map2 ) {
+         match = 1;
+         tmap = (AstMapping *) astCmpMap( map1, map2, 1, "", status );
+         *map = astSimplify( tmap );
+         tmap = astAnnul( tmap );
+      }
+
+/* Free resources. */
+      if( map1 ) map1 = astAnnul( map1 );
+      if( map2 ) map2 = astAnnul( map2 );
+   }
+
+/* If an error occurred, annul the returned Mapping and clear the returned
+   values. */
+   if ( !astOK ) {
+      *map = astAnnul( *map );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static int Match( AstFrame *template_frame, AstFrame *target, int matchsub,
+                  int **template_axes, int **target_axes, AstMapping **map,
+                  AstFrame **result, int *status ) {
+/*
+*  Name:
+*     Match
+
+*  Purpose:
+*     Determine if conversion is possible between two coordinate systems.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     int Match( AstFrame *template, AstFrame *target, int matchsub,
+*                int **template_axes, int **target_axes,
+*                AstMapping **map, AstFrame **result, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the protected astMatch method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function matches a "template" TimeFrame to a "target" Frame and
+*     determines whether it is possible to convert coordinates between them.
+*     If it is, a mapping that performs the transformation is returned along
+*     with a new Frame that describes the coordinate system that results when
+*     this mapping is applied to the "target" coordinate system. In addition,
+*     information is returned to allow the axes in this "result" Frame to be
+*     associated with the corresponding axes in the "target" and "template"
+*     Frames from which they are derived.
+
+*  Parameters:
+*     template
+*        Pointer to the template TimeFrame. This describes the coordinate
+*        system (or set of possible coordinate systems) into which we wish to
+*        convert our coordinates.
+*     target
+*        Pointer to the target Frame. This describes the coordinate system in
+*        which we already have coordinates.
+*     matchsub
+*        If zero then a match only occurs if the template is of the same
+*        class as the target, or of a more specialised class. If non-zero
+*        then a match can occur even if this is not the case.
+*     template_axes
+*        Address of a location where a pointer to int will be returned if the
+*        requested coordinate conversion is possible. This pointer will point
+*        at a dynamically allocated array of integers with one element for each
+*        axis of the "result" Frame (see below). It must be freed by the caller
+*        (using astFree) when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element of this
+*        array will return the index of the template TimeFrame axis from
+*        which it is derived. If it is not derived from any template
+*        TimeFrame axis, a value of -1 will be returned instead.
+*     target_axes
+*        Address of a location where a pointer to int will be returned if the
+*        requested coordinate conversion is possible. This pointer will point
+*        at a dynamically allocated array of integers with one element for each
+*        axis of the "result" Frame (see below). It must be freed by the caller
+*        (using astFree) when no longer required.
+*
+*        For each axis in the result Frame, the corresponding element of this
+*        array will return the index of the target Frame axis from which it
+*        is derived. If it is not derived from any target Frame axis, a value
+*        of -1 will be returned instead.
+*     map
+*        Address of a location where a pointer to a new Mapping will be
+*        returned if the requested coordinate conversion is possible. If
+*        returned, the forward transformation of this Mapping may be used to
+*        convert coordinates between the "target" Frame and the "result"
+*        Frame (see below) and the inverse transformation will convert in the
+*        opposite direction.
+*     result
+*        Address of a location where a pointer to a new Frame will be returned
+*        if the requested coordinate conversion is possible. If returned, this
+*        Frame describes the coordinate system that results from applying the
+*        returned Mapping (above) to the "target" coordinate system. In
+*        general, this Frame will combine attributes from (and will therefore
+*        be more specific than) both the target and the template Frames. In
+*        particular, when the template allows the possibility of transformaing
+*        to any one of a set of alternative coordinate systems, the "result"
+*        Frame will indicate which of the alternatives was used.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if the requested coordinate conversion is
+*     possible. Otherwise zero is returned (this will not in itself result in
+*     an error condition).
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+
+*  Implementation Notes:
+*     This implementation addresses the matching of a TimeFrame class
+*     object to any other class of Frame. A TimeFrame will match any class
+*     of TimeFrame (i.e. possibly from a derived class) but will not match
+*     a less specialised class of Frame.
+*/
+
+   AstFrame *frame0;             /* Pointer to Frame underlying axis 0 */
+   AstTimeFrame *template;       /* Pointer to template TimeFrame structure */
+   int iaxis0;                   /* Axis index underlying axis 0 */
+   int iaxis;                    /* Axis index */
+   int match;                    /* Coordinate conversion possible? */
+   int target_axis0;             /* Index of TimeFrame axis in the target */
+   int target_naxes;             /* Number of target axes */
+
+/* Initialise the returned values. */
+   *template_axes = NULL;
+   *target_axes = NULL;
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the template TimeFrame structure. */
+   template = (AstTimeFrame *) template_frame;
+
+/* Obtain the number of axes in the target Frame. */
+   target_naxes = astGetNaxes( target );
+
+/* The first criterion for a match is that the template matches as a
+   Frame class object. This ensures that the number of axes (1) and
+   domain, etc. of the target Frame are suitable. Invoke the parent
+   "astMatch" method to verify this. */
+   match = (*parent_match)( template_frame, target, matchsub,
+                            template_axes, target_axes, map, result, status );
+
+/* If a match was found, annul the returned objects, which are not
+   needed, but keep the memory allocated for the axis association
+   arrays, which we will re-use. */
+   if ( astOK && match ) {
+      *map = astAnnul( *map );
+      *result = astAnnul( *result );
+   }
+
+/* If OK so far, obtain pointers to the primary Frames which underlie
+   all target axes. Stop when a TimeFrame axis is found. */
+   if ( match && astOK ) {
+      match = 0;
+      for( iaxis = 0; iaxis < target_naxes; iaxis++ ) {
+         astPrimaryFrame( target, iaxis, &frame0, &iaxis0 );
+         if( astIsATimeFrame( frame0 ) ) {
+            frame0 = astAnnul( frame0 );
+            target_axis0 = iaxis;
+            match = 1;
+            break;
+         } else {
+            frame0 = astAnnul( frame0 );
+         }
+      }
+   }
+
+/* Check at least one TimeFrame axis was found it the target. Store the
+   axis associataions. */
+   if( match && astOK ) {
+      (*template_axes)[ 0 ] = 0;
+      (*target_axes)[ 0 ] = target_axis0;
+
+/* Use the target's "astSubFrame" method to create a new Frame (the
+   result Frame) with copies of the target axes in the required
+   order. This process also overlays the template attributes on to the
+   target Frame and returns a Mapping between the target and result
+   Frames which effects the required coordinate conversion. */
+      match = astSubFrame( target, template, 1, *target_axes, *template_axes,
+                           map, result );
+   }
+
+/* If an error occurred, or conversion to the result Frame's coordinate
+   system was not possible, then free all memory, annul the returned
+   objects, and reset the returned value. */
+   if ( !astOK || !match ) {
+      *template_axes = astFree( *template_axes );
+      *target_axes = astFree( *target_axes );
+      if( *map ) *map = astAnnul( *map );
+      if( *result ) *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+}
+
+static void OriginScale( AstTimeFrame *this, AstTimeScaleType newts,
+                         const char *method, int *status ){
+/*
+*  Name:
+*     OriginScale
+
+*  Purpose:
+*     Convert the TimeOrigin in a TimeFrame to a new timescale.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void OriginScale( AstTimeFrame *this, AstTimeScaleType newts,
+*                       const char *method, int *status )
+
+*  Class Membership:
+*     TimeFrame member function
+
+*  Description:
+*     This function converts the value of the TimeOrigin attribute stored
+*     within a supplied TimeFrame from the timescale currently associated
+*     with the TimeFrame, to the new timescale indicated by "newts".
+
+*  Parameters:
+*     this
+*        Point to the TimeFrame. On entry, the TimeOrigin value is
+*        assumed to refer to the timescale given by the astGetTimeScale
+*        method. On exit, the TimeOrigin value refers to the timescale
+*        supplied in "newts". The TimeScale attribute of the TimeFrame
+*        should then be modified in order to keep things consistent.
+*     newts
+*        The timescale to which the TimeOrigin value stored within "this"
+*        should refer on exit.
+*     method
+*        Pointer to a string holding the name of the method to be
+*        included in any error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+
+/* Local Variables: */
+   AstMapping *map;
+   AstSystemType sys;
+   AstTimeScaleType oldts;
+   const char *u;
+   double newval;
+   double oldval;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Do nothing if the TimeOrigin attribute has not been assigned a value. */
+   if( astTestTimeOrigin( this ) ) {
+
+/* Do nothing if the Scale will not change. */
+      oldts = astGetTimeScale( this );
+      if( newts != oldts ) {
+
+/* Create a Mapping to perform the TimeScale change. */
+         sys = astGetSystem( this );
+         u = DefUnit( sys, method, "TimeFrame", status ),
+         map = MakeMap( this, sys, sys, oldts, newts, 0.0, 0.0, u, u,
+                        method, status );
+
+/* Use the Mapping to convert the stored TimeOrigin value. */
+         if( map ) {
+            oldval = astGetTimeOrigin( this );
+            astTran1( map, 1, &oldval, 1, &newval );
+
+/* Store the new value */
+            astSetTimeOrigin( this, newval );
+
+/* Free resources */
+            map = astAnnul( map );
+
+         } else if( astOK ) {
+            astError( AST__INCTS, "%s(%s): Cannot convert the TimeOrigin "
+                      "value to a different timescale because of "
+                      "incompatible time scales.", status, method,
+                      astGetClass( this ) );
+         }
+      }
+   }
+}
+
+static void OriginSystem( AstTimeFrame *this, AstSystemType oldsys,
+                          const char *method, int *status ){
+/*
+*  Name:
+*     OriginSystem
+
+*  Purpose:
+*     Convert the TimeOrigin in a TimeFrame to a new System.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void OriginSystem( AstTimeFrame *this, AstSystemType oldsys,
+*                        const char *method, int *status )
+
+*  Class Membership:
+*     TimeFrame member function
+
+*  Description:
+*     This function converts the value of the TimeOrigin attribute stored
+*     within a supplied TimeFrame from its original System, etc, to the
+*     System, etc, currently associated with the TimeFrame.
+
+*  Parameters:
+*     this
+*        Point to the TimeFrame. On entry, the TimeOrigin value is
+*        assumed to refer to the System given by "oldsys", etc. On exit, the
+*        TimeOrigin value refers to the System returned by the astGetSystem
+*        method, etc.
+*     oldsys
+*        The System to which the TimeOrigin value stored within "this"
+*        refers on entry.
+*     method
+*        A string containing the method name for error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   AstSystemType newsys;
+   AstTimeScaleType ts;
+   const char *oldu;
+   const char *newu;
+   double newval;
+   double oldval;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Do nothing if the TimeOrigin attribute has not been assigned a value. */
+   if( astTestTimeOrigin( this ) ) {
+
+/* Do nothing if the System has not changed. */
+      newsys = astGetSystem( this );
+      if( oldsys != newsys ) {
+
+/* Create a Mapping to perform the System change. */
+         ts = astGetTimeScale( this );
+         oldu = DefUnit( oldsys, method, "TimeFrame", status ),
+         newu = DefUnit( newsys, method, "TimeFrame", status ),
+         map = MakeMap( this, oldsys, newsys, ts, ts, 0.0, 0.0, oldu, newu,
+                        method, status );
+
+/* Use the Mapping to convert the stored TimeOrigin value. */
+         if( map ) {
+            oldval = astGetTimeOrigin( this );
+            astTran1( map, 1, &oldval, 1, &newval );
+
+/* Store the new value */
+            astSetTimeOrigin( this, newval );
+
+/* Free resources */
+            map = astAnnul( map );
+
+         } else if( astOK ) {
+            astError( AST__INCTS, "%s(%s): Cannot convert the TimeOrigin "
+                      "value to a different System because of incompatible "
+                      "time scales.", status, method, astGetClass( this ) );
+         }
+      }
+   }
+}
+
+static void Overlay( AstFrame *template, const int *template_axes,
+                     AstFrame *result, int *status ) {
+/*
+*  Name:
+*     Overlay
+
+*  Purpose:
+*     Overlay the attributes of a template TimeFrame on to another Frame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void Overlay( AstFrame *template, const int *template_axes,
+*                   AstFrame *result, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the protected astOverlay method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function overlays attributes of a TimeFrame (the "template") on to
+*     another Frame, so as to over-ride selected attributes of that second
+*     Frame. Normally only those attributes which have been specifically set
+*     in the template will be transferred. This implements a form of
+*     defaulting, in which a Frame acquires attributes from the template, but
+*     retains its original attributes (as the default) if new values have not
+*     previously been explicitly set in the template.
+*
+*     Note that if the result Frame is a TimeFrame and a change of time
+*     coordinate system occurs as a result of overlaying its System
+*     attribute, then some of its original attribute values may no
+*     longer be appropriate (e.g. the Title, or attributes describing
+*     its axes). In this case, these will be cleared before overlaying
+*     any new values.
+
+*  Parameters:
+*     template
+*        Pointer to the template TimeFrame, for which values should have been
+*        explicitly set for any attribute which is to be transferred.
+*     template_axes
+*        Pointer to an array of int, with one element for each axis of the
+*        "result" Frame (see below). For each axis in the result frame, the
+*        corresponding element of this array should contain the (zero-based)
+*        index of the template axis to which it corresponds. This array is used
+*        to establish from which template axis any axis-dependent attributes
+*        should be obtained.
+*
+*        If any axis in the result Frame is not associated with a template
+*        axis, the corresponding element of this array should be set to -1.
+*
+*        If a NULL pointer is supplied, the template and result axis
+*        indices are assumed to be identical.
+*     result
+*        Pointer to the Frame which is to receive the new attribute values.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  In general, if the result Frame is not from the same class as the
+*     template TimeFrame, or from a class derived from it, then attributes may
+*     exist in the template TimeFrame which do not exist in the result Frame.
+*     In this case, these attributes will not be transferred.
+*/
+
+
+/* Local Variables: */
+   AstSystemType new_alignsystem;/* Code identifying new alignment coords */
+   AstSystemType new_system;     /* Code identifying new cordinates */
+   AstSystemType old_system;     /* Code identifying old coordinates */
+   int resetSystem;              /* Was the template System value cleared? */
+   int timeframe;                /* Result Frame is a TimeFrame? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get the old and new systems. */
+   old_system = astGetSystem( result );
+   new_system = astGetSystem( template );
+
+/* If the result Frame is a TimeFrame, we must test to see if overlaying its
+   System attribute will change the type of coordinate system it describes.
+   Determine the value of this attribute for the result and template
+   TimeFrames. */
+   resetSystem = 0;
+   timeframe = astIsATimeFrame( result );
+   if( timeframe ) {
+
+/* If the coordinate system will change, any value already set for the result
+   TimeFrame's Title, etc,  will no longer be appropriate, so clear it. */
+      if ( new_system != old_system ) {
+         astClearTitle( result );
+         astClearLabel( result, 0 );
+         astClearSymbol( result, 0 );
+      }
+
+/* If the result Frame is not a TimeFrame, we must temporarily clear the
+   System and AlignSystem values since the values used by this class are only
+   appropriate to this class. */
+   } else {
+      if( astTestSystem( template ) ) {
+         astClearSystem( template );
+
+         new_alignsystem = astGetAlignSystem( template );
+         astClearAlignSystem( template );
+
+         resetSystem = 1;
+      }
+   }
+
+/* Invoke the parent class astOverlay method to transfer attributes inherited
+   from the parent class. */
+   (*parent_overlay)( template, template_axes, result, status );
+
+/* Reset the System and AlignSystem values if necessary */
+   if( resetSystem ) {
+      astSetSystem( template, new_system );
+      astSetAlignSystem( template, new_alignsystem );
+   }
+
+/* Check if the result Frame is a TimeFrame or from a class derived from
+   TimeFrame. If not, we cannot transfer TimeFrame attributes to it as it is
+   insufficiently specialised. In this case simply omit these attributes. */
+   if ( timeframe && astOK ) {
+
+/* Define macros that test whether an attribute is set in the template and,
+   if so, transfers its value to the result. */
+#define OVERLAY(attribute) \
+   if ( astTest##attribute( template ) ) { \
+      astSet##attribute( result, astGet##attribute( template ) ); \
+   }
+
+/* Use the macro to transfer each TimeFrame attribute in turn. Note,
+   SourceVRF must be overlayed before SourceVel. Otherwise the stored value
+   for SourceVel would be changed from the default SourceVRF to the specified
+   SourceVRF when SourceVRF was overlayed. */
+      OVERLAY(AlignTimeScale)
+      OVERLAY(LTOffset)
+      OVERLAY(TimeOrigin)
+      OVERLAY(TimeScale)
+   }
+
+/* Undefine macros local to this function. */
+#undef OVERLAY
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void SetAttrib( AstObject *this, const char *setting, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (extends the astSetAttrib method inherited from
+*     the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a TimeFrame, the attribute
+*     and its value being specified by means of a string of the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in lower
+*     case with no white space present. The value to the right of the "="
+*     should be a suitable textual representation of the value to be assigned
+*     and this will be interpreted according to the attribute's data type.
+*     White space surrounding the value is only significant for string
+*     attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This protected method is intended to be invoked by the Object astSet
+*     method and makes additional attributes accessible to it.
+*/
+
+/* Local Vaiables: */
+   AstTimeFrame *this;           /* Pointer to the TimeFrame structure */
+   AstTimeScaleType ts;          /* time scale type code */
+   char *a;                      /* Pointer to next character */
+   char *new_setting;            /* Pointer value to new attribute setting */
+   double dval;                  /* Double atribute value */
+   double mjd;                   /* MJD read from setting */
+   double origin;                /* TimeOrigin value */
+   int len;                      /* Length of setting string */
+   int namelen;                  /* Length of attribute name in setting */
+   int nc;                       /* Number of characters read by astSscanf */
+   int off;                      /* Offset of attribute value */
+   int rep;                      /* Original error reporting state */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse the
+   setting string and extract the attribute value (or an offset to it in the
+   case of string values). In each case, use the value set in "nc" to check
+   that the entire string was matched. Once a value has been obtained, use the
+   appropriate method to set it. */
+
+/* First look for axis attributes defined by the Frame class. Since a
+   TimeFrame has only 1 axis, we allow these attributes to be specified
+   without a trailing "(axis)" string. */
+   if ( !strncmp( setting, "direction=", 10 ) ||
+        !strncmp( setting, "bottom=", 7 ) ||
+        !strncmp( setting, "top=", 4 ) ||
+        !strncmp( setting, "format=", 7 ) ||
+        !strncmp( setting, "label=", 6 ) ||
+        !strncmp( setting, "symbol=", 7 ) ||
+        !strncmp( setting, "unit=", 5 ) ) {
+
+/* Create a new setting string from the original by appending the string
+   "(1)" to the end of the attribute name and then use the parent SetAttrib
+   method. */
+      new_setting = astMalloc( len + 4 );
+      if( new_setting ) {
+         memcpy( new_setting, setting, len + 1 );
+         a = strchr( new_setting, '=' );
+         namelen = a - new_setting;
+         memcpy( a, "(1)", 4 );
+         a += 3;
+         strcpy( a, setting + namelen );
+         (*parent_setattrib)( this_object, new_setting, status );
+         new_setting = astFree( new_setting );
+      }
+
+/* AlignTimeScale. */
+/* --------------- */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "aligntimescale=%n%*s %n", &off, &nc ) )
+        && ( nc >= len ) ) {
+
+/* Convert the string to a TimeScale code before use. */
+      ts = TimeScaleCode( setting + off, status );
+      if ( ts != AST__BADTS ) {
+         astSetAlignTimeScale( this, ts );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid time scale "
+                   "description \"%s\".", status, astGetClass( this ), setting+off );
+      }
+
+/* ClockLat. */
+/* ------- */
+   } else if ( nc = 0,
+              ( 0 == astSscanf( setting, "clocklat=%n%*s %n", &off, &nc ) )
+              && ( nc >= 7 ) ) {
+      new_setting = astMalloc( sizeof( char )*(size_t) len + 1 );
+      new_setting[ 0 ] = 'o';
+      new_setting[ 1 ] = 'b';
+      new_setting[ 2 ] = 's';
+      strcpy( new_setting + 3, setting + 5 );
+      astSetAttrib( this, new_setting );
+      new_setting = astFree( new_setting );
+
+/* ClockLon. */
+/* ------- */
+   } else if ( nc = 0,
+              ( 0 == astSscanf( setting, "clocklon=%n%*s %n", &off, &nc ) )
+              && ( nc >= 7 ) ) {
+      new_setting = astMalloc( sizeof( char )*(size_t) len + 1 );
+      new_setting[ 0 ] = 'o';
+      new_setting[ 1 ] = 'b';
+      new_setting[ 2 ] = 's';
+      strcpy( new_setting + 3, setting + 5 );
+      astSetAttrib( this, new_setting );
+      new_setting = astFree( new_setting );
+
+/* LTOffset */
+/* -------- */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "ltoffset= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+      astSetLTOffset( this, dval );
+
+/* TimeOrigin */
+/* ---------- */
+
+/* Floating-point without any units indication - assume the current Unit
+   value. */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "timeorigin= %lg %n", &dval, &nc ) )
+        && ( nc >= len ) ) {
+
+      astSetTimeOrigin( this, ToUnits( this, astGetUnit( this, 0 ), dval,
+                                       "astSetTimeOrigin", status ) );
+
+/* Floating-point with units. */
+   } else if ( nc = 0,
+        ( 1 == astSscanf( setting, "timeorigin= %lg %n%*s %n", &dval, &off, &nc ) )
+        && ( nc >= len ) ) {
+
+/* Defer error reporting in case a date string was given which starts
+   with a floating point number, then convert the supplied value to the
+   default units for the TimeFrame's System. */
+      rep = astReporting( 0 );
+      origin = ToUnits( this, setting + off, dval, "astSetTimeOrigin", status );
+      if( !astOK ) astClearStatus;
+      astReporting( rep );
+
+/* If the origin was converted, store it. */
+      if( origin != AST__BAD ) {
+         astSetTimeOrigin( this, origin );
+
+/* Otherwise, interpret the string as a date. Convert first to MJD then to
+   default system. */
+      } else if ( nc = 0,
+           ( 0 == astSscanf( setting, "timeorigin=%n%*[^\n]%n", &off, &nc ) )
+           && ( nc >= len ) ) {
+         mjd = astReadDateTime( setting + off );
+         if ( astOK ) {
+            astSetTimeOrigin( this, FromMJD( this, mjd, status ) );
+
+/* Report contextual information if the conversion failed. */
+         } else {
+            astError( AST__ATTIN, "astSetAttrib(%s): Invalid TimeOrigin value "
+                      "\"%s\" given.", status, astGetClass( this ), setting + off );
+         }
+      }
+
+/* String (assumed to be a date). Convert first to MJD then to default
+   system. */
+   } else if ( nc = 0,
+        ( 0 == astSscanf( setting, "timeorigin=%n%*[^\n]%n", &off, &nc ) )
+        && ( nc >= len ) ) {
+      mjd = astReadDateTime( setting + off );
+      if ( astOK ) {
+         astSetTimeOrigin( this, FromMJD( this, mjd, status ) );
+
+/* Report contextual information if the conversion failed. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid TimeOrigin value "
+                   "\"%s\" given.", status, astGetClass( this ), setting + off );
+      }
+
+/* TimeScale. */
+/* ---------- */
+   } else if ( nc = 0,
+              ( 0 == astSscanf( setting, "timescale=%n%*s %n", &off, &nc ) )
+              && ( nc >= len ) ) {
+
+/* Convert the string to a TimeScale code before use. */
+      ts = TimeScaleCode( setting + off, status );
+      if ( ts != AST__BADTS ) {
+         astSetTimeScale( this, ts );
+
+/* Report an error if the string value wasn't recognised. */
+      } else {
+         astError( AST__ATTIN, "astSetAttrib(%s): Invalid time scale "
+                   "description \"%s\".", status, astGetClass( this ), setting + off );
+      }
+
+/* Pass any unrecognised setting to the parent method for further
+   interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SetSystem( AstFrame *this_frame, AstSystemType newsys, int *status ) {
+/*
+*  Name:
+*     SetSystem
+
+*  Purpose:
+*     Set the System attribute for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void SetSystem( AstFrame *this_frame, AstSystemType newsys, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astSetSystem protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function sets the System attribute for a TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     newsys
+*        The new System value to be stored.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;           /* Pointer to TimeFrame structure */
+   AstSystemType oldsys;         /* Original System value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* If we are changing the System to BEPOCH, set the Unit attribute to
+   "yr" and TimeScale to "TT". */
+   if( newsys == AST__BEPOCH ) {
+      astSetUnit( this_frame, 0, "yr" );
+      astSetTimeScale( (AstTimeFrame *) this_frame, AST__TT );
+   }
+
+/* Save the original System value */
+   oldsys = astGetSystem( this_frame );
+
+/* Use the parent SetSystem method to store the new System value. */
+   (*parent_setsystem)( this_frame, newsys, status );
+
+/* If the system has changed... */
+   if( oldsys != newsys ) {
+
+/* Modify the stored TimeOrigin. */
+      OriginSystem( this, oldsys, "astSetSystem", status );
+
+/* Clear all attributes which have system-specific defaults. */
+      astClearLabel( this_frame, 0 );
+      astClearSymbol( this_frame, 0 );
+      astClearTitle( this_frame );
+   }
+}
+
+static void SetTimeScale( AstTimeFrame *this, AstTimeScaleType value, int *status ) {
+/*
+*+
+*  Name:
+*     astSetTimeScale
+
+*  Purpose:
+*     Set the TimeScale attribute for a TimeFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void astSetTimeScale( AstTimeFrame *this, AstTimeScaleType value )
+
+*  Class Membership:
+*     TimeFrame virtual function
+
+*  Description:
+*     This function set a new value for the TimeScale attribute for a
+*     TimeFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     value
+*        The new value.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Verify the supplied timescale value */
+   if( value < FIRST_TS || value > LAST_TS ) {
+      astError( AST__ATTIN, "%s(%s): Bad value (%d) given for TimeScale "
+                "attribute.", status, "astSetTimeScale", astGetClass( this ),
+                (int) value );
+
+/* Report an error if System is set to BEPOCH and an in appropriate
+   TimeScale was supplied. */
+   } else if( astGetSystem( this ) == AST__BEPOCH &&
+              value != AST__TT ) {
+      astError( AST__ATTIN, "%s(%s): Supplied TimeScale (%s) cannot be "
+                "used because the %s represents Besselian Epoch which "
+                "is defined in terms of TT.", status, "astSetTimeScale",
+                astGetClass( this ), TimeScaleString( value, status ),
+                astGetClass( this ) );
+
+/* Otherwise set the new TimeScale */
+   } else {
+
+/* Modify the TimeOrigin value stored in the TimeFrame structure to refer
+   to the new timescale. */
+      OriginScale( this, value, "astSetTimeScale", status );
+
+/* Store the new value for the timescale in the TimeFrame structure. */
+      this->timescale = value;
+
+   }
+}
+
+static void SetUnit( AstFrame *this_frame, int axis, const char *value, int *status ) {
+/*
+*  Name:
+*     SetUnit
+
+*  Purpose:
+*     Set a pointer to the Unit string for a TimeFrame's axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void SetUnit( AstFrame *this_frame, int axis, const char *value )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astSetUnit method inherited
+*     from the Frame class).
+
+*  Description:
+*     This function stores a pointer to the Unit string for a specified axis
+*     of a TimeFrame. It also stores the string in the "usedunits" array
+*     in the TimeFrame structure, in the element associated with the
+*     current System.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     axis
+*        The number of the axis (zero-based) for which information is required.
+*     unit
+*        The new string to store.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* Validate the axis index. */
+   astValidateAxis( this, axis, 1, "astSetUnit" );
+
+/* Report an error if System is set to BEPOCH and an in appropriate
+   Unit was supplied. */
+   if( astGetSystem( this ) == AST__BEPOCH && strcmp( "yr", value ) ) {
+      astError( AST__ATTIN, "astSetUnit(%s): Supplied Unit (%s) cannot "
+                "be used because the %s represents Besselian Epoch which "
+                "is defined in units of years (yr).", status, astGetClass( this ),
+                value, astGetClass( this ) );
+
+/* Otherwise use the parent SetUnit method to store the value in the Axis
+   structure */
+   } else {
+      (*parent_setunit)( this_frame, axis, value, status );
+   }
+}
+
+static AstTimeScaleType TimeScaleCode( const char *ts, int *status ) {
+/*
+*  Name:
+*     TimeScaleCode
+
+*  Purpose:
+*     Convert a string into a time scale type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     AstTimeScaleType TimeScaleCode( const char *ts )
+
+*  Class Membership:
+*     TimeFrame member function.
+
+*  Description:
+*     This function converts a string used for the external description of
+*     a time scale into a TimeFrame time scale type code  (TimeScale attribute
+*     value). It is the inverse of the TimeScaleString function.
+
+*  Parameters:
+*     ts
+*        Pointer to a constant null-terminated string containing the
+*        external description of the time scale.
+
+*  Returned Value:
+*     The TimeScale type code.
+
+*  Notes:
+*     - A value of AST__BADTS is returned if the time scale
+*     description was not recognised. This does not produce an error.
+*     - A value of AST__BADTS is also returned if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstTimeScaleType result;      /* Result value to return */
+
+/* Initialise. */
+   result = AST__BADTS;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the timescale string against each possibility and assign the
+   result. */
+   if ( astChrMatch( "TAI", ts ) ) {
+      result = AST__TAI;
+
+   } else if ( astChrMatch( "UTC", ts ) ) {
+      result = AST__UTC;
+
+   } else if ( astChrMatch( "UT1", ts ) ) {
+      result = AST__UT1;
+
+   } else if ( astChrMatch( "GMST", ts ) ) {
+      result = AST__GMST;
+
+   } else if ( astChrMatch( "LAST", ts ) ) {
+      result = AST__LAST;
+
+   } else if ( astChrMatch( "LMST", ts ) ) {
+      result = AST__LMST;
+
+   } else if ( astChrMatch( "TT", ts ) ) {
+      result = AST__TT;
+
+   } else if ( astChrMatch( "TDB", ts ) ) {
+      result = AST__TDB;
+
+   } else if ( astChrMatch( "TCG", ts ) ) {
+      result = AST__TCG;
+
+   } else if ( astChrMatch( "TCB", ts ) ) {
+      result = AST__TCB;
+
+   } else if ( astChrMatch( "LT", ts ) ) {
+      result = AST__LT;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *TimeScaleString( AstTimeScaleType ts, int *status ) {
+/*
+*  Name:
+*     TimeScaleString
+
+*  Purpose:
+*     Convert a time scale type code into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *TimeScaleString( AstTimeScaleType ts, int *status )
+
+*  Class Membership:
+*     TimeFrame member function.
+
+*  Description:
+*     This function converts a TimeFrame time scale type code (TimeScale
+*     attribute value) into a string suitable for use as an external
+*     representation of the time scale type.
+
+*  Parameters:
+*     ts
+*        The time scale type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the time scale
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the timescale value against each possibility and convert to a
+   string pointer. */
+   switch ( ts ) {
+
+   case AST__TAI:
+      result = "TAI";
+      break;
+
+   case AST__UTC:
+      result = "UTC";
+      break;
+
+   case AST__UT1:
+      result = "UT1";
+      break;
+
+   case AST__GMST:
+      result = "GMST";
+      break;
+
+   case AST__LAST:
+      result = "LAST";
+      break;
+
+   case AST__LMST:
+      result = "LMST";
+      break;
+
+   case AST__TT:
+      result = "TT";
+      break;
+
+   case AST__TDB:
+      result = "TDB";
+      break;
+
+   case AST__TCB:
+      result = "TCB";
+      break;
+
+   case AST__TCG:
+      result = "TCG";
+      break;
+
+   case AST__LT:
+      result = "LT";
+      break;
+
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static int SubFrame( AstFrame *target_frame, AstFrame *template,
+                     int result_naxes, const int *target_axes,
+                     const int *template_axes, AstMapping **map,
+                     AstFrame **result, int *status ) {
+/*
+*  Name:
+*     SubFrame
+
+*  Purpose:
+*     Select axes from a TimeFrame and convert to the new coordinate
+*     system.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     int SubFrame( AstFrame *target, AstFrame *template,
+*                   int result_naxes, const int *target_axes,
+*                   const int *template_axes, AstMapping **map,
+*                   AstFrame **result, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the protected astSubFrame
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function selects a requested sub-set (or super-set) of the axes
+*     from a "target" TimeFrame and creates a new Frame with copies of
+*     the selected axes assembled in the requested order. It then
+*     optionally overlays the attributes of a "template" Frame on to the
+*     result. It returns both the resulting Frame and a Mapping that
+*     describes how to convert between the coordinate systems described by
+*     the target and result Frames. If necessary, this Mapping takes
+*     account of any differences in the Frames' attributes due to the
+*     influence of the template.
+
+*  Parameters:
+*     target
+*        Pointer to the target TimeFrame, from which axes are to be
+*        selected.
+*     template
+*        Pointer to the template Frame, from which new attributes for the
+*        result Frame are to be obtained. Optionally, this may be NULL, in
+*        which case no overlaying of template attributes will be performed.
+*     result_naxes
+*        Number of axes to be selected from the target Frame. This number may
+*        be greater than or less than the number of axes in this Frame (or
+*        equal).
+*     target_axes
+*        Pointer to an array of int with result_naxes elements, giving a list
+*        of the (zero-based) axis indices of the axes to be selected from the
+*        target TimeFrame. The order in which these are given determines
+*        the order in which the axes appear in the result Frame. If any of the
+*        values in this array is set to -1, the corresponding result axis will
+*        not be derived from the target Frame, but will be assigned default
+*        attributes instead.
+*     template_axes
+*        Pointer to an array of int with result_naxes elements. This should
+*        contain a list of the template axes (given as zero-based axis indices)
+*        with which the axes of the result Frame are to be associated. This
+*        array determines which axes are used when overlaying axis-dependent
+*        attributes of the template on to the result. If any element of this
+*        array is set to -1, the corresponding result axis will not receive any
+*        template attributes.
+*
+*        If the template argument is given as NULL, this array is not used and
+*        a NULL pointer may also be supplied here.
+*     map
+*        Address of a location to receive a pointer to the returned Mapping.
+*        The forward transformation of this Mapping will describe how to
+*        convert coordinates from the coordinate system described by the target
+*        TimeFrame to that described by the result Frame. The inverse
+*        transformation will convert in the opposite direction.
+*     result
+*        Address of a location to receive a pointer to the result Frame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value is returned if coordinate conversion is possible
+*     between the target and the result Frame. Otherwise zero is returned and
+*     *map and *result are returned as NULL (but this will not in itself
+*     result in an error condition). In general, coordinate conversion should
+*     always be possible if no template Frame is supplied but may not always
+*     be possible otherwise.
+
+*  Notes:
+*     -  A value of zero will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+
+*  Implementation Notes:
+*     -  This implementation addresses the selection of axes from a
+*     TimeFrame object. This results in another object of the same class
+*     only if the single TimeFrame axis is selected exactly once.
+*     Otherwise, the result is a Frame class object which inherits the
+*     TimeFrame's axis information (if appropriate) but none of the other
+*     properties of a TimeFrame.
+*     -  In the event that a TimeFrame results, the returned Mapping will
+*     take proper account of the relationship between the target and result
+*     coordinate systems.
+*     -  In the event that a Frame class object results, the returned Mapping
+*     will only represent a selection/permutation of axes.
+
+*  Implementation Deficiencies:
+*     -  Any axis selection is currently permitted. Probably this should be
+*     restricted so that each axis can only be selected once. The
+*     astValidateAxisSelection method will do this but currently there are bugs
+*     in the CmpFrame class that cause axis selections which will not pass this
+*     test. Install the validation when these are fixed.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *target;      /* Pointer to the TimeFrame structure */
+   AstTimeFrame *temp;        /* Pointer to copy of target TimeFrame */
+   AstTimeFrame *align_frm;   /* Frame in which to align the TimeFrames */
+   int match;                 /* Coordinate conversion is possible? */
+
+/* Initialise the returned values. */
+   *map = NULL;
+   *result = NULL;
+   match = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return match;
+
+/* Obtain a pointer to the target TimeFrame structure. */
+   target = (AstTimeFrame *) target_frame;
+
+/* Result is a TimeFrame. */
+/* -------------------------- */
+/* Check if the result Frame is to have one axis obtained by selecting
+   the single target TimeFrame axis. If so, the result will also be
+   a TimeFrame. */
+   if ( ( result_naxes == 1 ) && ( target_axes[ 0 ] == 0 ) ) {
+
+/* Form the result from a copy of the target. */
+      *result = astCopy( target );
+
+/* If required, overlay the template attributes on to the result TimeFrame.
+   Also choose the Frame which defined the alignment system and time scale
+   (via its AlignSystem and AlignTimeScale attributes) in which to align the
+   two TimeFrames. This is the template (if there is a template). */
+      if ( template ) {
+         astOverlay( template, template_axes, *result );
+         if( astIsATimeFrame( template ) ) {
+            align_frm = astClone( template );
+         } else {
+            align_frm = astClone( target );
+         }
+
+/* If no template was supplied, align in the System and TimeScale of the
+   target. */
+      } else {
+         VerifyAttrs( target, "convert between different time systems",
+                      "TimeScale", "astMatch", status );
+         align_frm = astClone( target );
+      }
+
+/* Generate a Mapping that takes account of changes in the sky coordinate
+   system (equinox, epoch, etc.) between the target TimeFrame and the result
+   TimeFrame. If this Mapping can be generated, set "match" to indicate that
+   coordinate conversion is possible. */
+      match = ( MakeTimeMapping( target, (AstTimeFrame *) *result,
+                align_frm, 0, map, status ) != 0 );
+
+/* Free resources. */
+      align_frm = astAnnul( align_frm );
+
+/* Result is not a TimeFrame. */
+/* ------------------------------ */
+/* In this case, we select axes as if the target were from the Frame
+   class.  However, since the resulting data will then be separated
+   from their enclosing TimeFrame, default attribute values may differ
+   if the methods for obtaining them were over-ridden by the TimeFrame
+   class. To overcome this, we ensure that these values are explicitly
+   set for the result Frame (rather than relying on their defaults). */
+   } else {
+
+/* Make a temporary copy of the target TimeFrame. We will explicitly
+   set the attribute values in this copy so as not to modify the original. */
+      temp = astCopy( target );
+
+/* Define a macro to test if an attribute is set. If not, set it
+   explicitly to its default value. */
+#define SET(attribute) \
+   if ( !astTest##attribute( temp ) ) { \
+      astSet##attribute( temp, astGet##attribute( temp ) ); \
+   }
+
+/* Set attribute values which apply to the Frame as a whole and which
+   we want to retain, but whose defaults are over-ridden by the
+   TimeFrame class. */
+      SET(Domain)
+      SET(Title)
+
+/* Define a macro to test if an attribute is set for axis zero (the only
+   axis of a TimeFrame). If not, set it explicitly to its default value. */
+#define SET_AXIS(attribute) \
+   if ( !astTest##attribute( temp, 0 ) ) { \
+      astSet##attribute( temp, 0, \
+                         astGet##attribute( temp, 0 ) ); \
+   }
+
+/* Use this macro to set explicit values for all the axis attributes
+   for which the TimeFrame class over-rides the default value. */
+      SET_AXIS(Label)
+      SET_AXIS(Symbol)
+      SET_AXIS(Unit)
+
+/* Clear attributes which have an extended range of values allowed by
+   this class. */
+      astClearSystem( temp );
+      astClearAlignSystem( temp );
+
+/* Invoke the astSubFrame method inherited from the Frame class to
+   produce the result Frame by selecting the required set of axes and
+   overlaying the template Frame's attributes. */
+      match = (*parent_subframe)( (AstFrame *) temp, template,
+                                  result_naxes, target_axes, template_axes,
+                                  map, result, status );
+
+/* Delete the temporary copy of the target TimeFrame. */
+      temp = astDelete( temp );
+   }
+
+/* If an error occurred or no match was found, annul the returned
+   objects and reset the returned result. */
+   if ( !astOK || !match ) {
+      if( *map ) *map = astAnnul( *map );
+      if( *result ) *result = astAnnul( *result );
+      match = 0;
+   }
+
+/* Return the result. */
+   return match;
+
+/* Undefine macros local to this function. */
+#undef SET
+#undef SET_AXIS
+}
+
+static AstSystemType SystemCode( AstFrame *this, const char *system, int *status ) {
+/*
+*  Name:
+*     SystemCode
+
+*  Purpose:
+*     Convert a string into a coordinate system type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     AstSystemType SystemCode( AstFrame *this, const char *system, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astSystemCode method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a string used for the external description of
+*     a coordinate system into a TimeFrame coordinate system type code
+*     (System attribute value). It is the inverse of the astSystemString
+*     function.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     system
+*        Pointer to a constant null-terminated string containing the
+*        external description of the sky coordinate system.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The System type code.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM is returned if the sky coordinate
+*     system description was not recognised. This does not produce an
+*     error.
+*     - A value of AST__BADSYSTEM is also returned if this function
+*     is invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;      /* Result value to return */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" string against each possibility and assign the
+   result. */
+   if ( astChrMatch( "MJD", system ) || astChrMatch( "Modified Julian Date", system ) ) {
+      result = AST__MJD;
+
+   } else if ( astChrMatch( "JD", system ) || astChrMatch( "Julian Date", system ) ) {
+      result = AST__JD;
+
+   } else if ( astChrMatch( "BEPOCH", system ) || astChrMatch( "Besselian Epoch", system ) ) {
+      result = AST__BEPOCH;
+
+   } else if ( astChrMatch( "JEPOCH", system ) || astChrMatch( "Julian Epoch", system ) ) {
+      result = AST__JEPOCH;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *SystemLabel( AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SystemLabel
+
+*  Purpose:
+*     Return a label for a coordinate system type code.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *SystemLabel( AstSystemType system, int *status )
+
+*  Class Membership:
+*     TimeFrame member function.
+
+*  Description:
+*     This function converts a TimeFrame coordinate system type code
+*     (System attribute value) into a descriptive string for human readers.
+
+*  Parameters:
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the sky coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" value against each possibility and convert to a
+   string pointer. */
+   switch ( system ) {
+
+   case AST__MJD:
+      result = "Modified Julian Date";
+      break;
+
+   case AST__JD:
+      result = "Julian Date";
+      break;
+
+   case AST__JEPOCH:
+      result = "Julian Epoch";
+      break;
+
+   case AST__BEPOCH:
+      result = "Besselian Epoch";
+      break;
+
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static const char *SystemString( AstFrame *this, AstSystemType system, int *status ) {
+/*
+*  Name:
+*     SystemString
+
+*  Purpose:
+*     Convert a coordinate system type code into a string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     const char *SystemString( AstFrame *this, AstSystemType system, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astSystemString method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function converts a TimeFrame coordinate system type code
+*     (System attribute value) into a string suitable for use as an
+*     external representation of the coordinate system type.
+
+*  Parameters:
+*     this
+*        The Frame.
+*     system
+*        The coordinate system type code.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string containing the
+*     textual equivalent of the type code supplied.
+
+*  Notes:
+*     - A NULL pointer value is returned if the sky coordinate system
+*     code was not recognised. This does not produce an error.
+*     - A NULL pointer value is also returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Match the "system" value against each possibility and convert to a
+   string pointer. (Where possible, return the same string as would be
+   used in the FITS WCS representation of the coordinate system). */
+   switch ( system ) {
+
+   case AST__MJD:
+      result = "MJD";
+      break;
+
+   case AST__JD:
+      result = "JD";
+      break;
+
+   case AST__JEPOCH:
+      result = "JEPOCH";
+      break;
+
+   case AST__BEPOCH:
+      result = "BEPOCH";
+      break;
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static int TestActiveUnit( AstFrame *this_frame, int *status ) {
+/*
+*  Name:
+*     TestActiveUnit
+
+*  Purpose:
+*     Test the ActiveUnit flag for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     int TestActiveUnit( AstFrame *this_frame, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astTestActiveUnit protected
+*     method inherited from the Frame class).
+
+*  Description:
+*    This function test the value of the ActiveUnit flag for a TimeFrame,
+*    which is always "unset".
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The result of the test (0).
+
+*/
+   return 0;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astTestAttrib protected
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a TimeFrame's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - This function uses one-based axis numbering so that it is
+*     suitable for external (public) use.
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;           /* Pointer to the TimeFrame structure */
+   char *new_attrib;             /* Pointer value to new attribute name */
+   int len;                      /* Length of attrib string */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* First look for axis attributes defined by the Frame class. Since a
+   TimeFrame has only 1 axis, we allow these attributes to be specified
+   without a trailing "(axis)" string. */
+   if ( !strcmp( attrib, "direction" ) ||
+        !strcmp( attrib, "bottom" ) ||
+        !strcmp( attrib, "top" ) ||
+        !strcmp( attrib, "format" ) ||
+        !strcmp( attrib, "label" ) ||
+        !strcmp( attrib, "symbol" ) ||
+        !strcmp( attrib, "unit" ) ) {
+
+/* Create a new attribute name from the original by appending the string
+   "(1)" and then use the parent TestAttrib method. */
+      new_attrib = astMalloc( len + 4 );
+      if( new_attrib ) {
+         memcpy( new_attrib, attrib, len );
+         memcpy( new_attrib + len, "(1)", 4 );
+         result = (*parent_testattrib)( this_object, new_attrib, status );
+         new_attrib = astFree( new_attrib );
+      }
+
+/* AlignTimeScale. */
+/* --------------- */
+   } else if ( !strcmp( attrib, "aligntimescale" ) ) {
+      result = astTestAlignTimeScale( this );
+
+/* ClockLat. */
+/* ------- */
+   } else if ( !strcmp( attrib, "clocklat" ) ) {
+      result = astTestAttrib( this, "obslat" );
+
+/* ClockLon. */
+/* ------- */
+   } else if ( !strcmp( attrib, "clocklon" ) ) {
+      result = astTestAttrib( this, "obslon" );
+
+/* LTOffset. */
+/* --------- */
+   } else if ( !strcmp( attrib, "ltoffset" ) ) {
+      result = astTestLTOffset( this );
+
+/* TimeOrigin. */
+/* --------- */
+   } else if ( !strcmp( attrib, "timeorigin" ) ) {
+      result = astTestTimeOrigin( this );
+
+/* TimeScale. */
+/* ---------- */
+   } else if ( !strcmp( attrib, "timescale" ) ) {
+      result = astTestTimeScale( this );
+
+/* If the attribute is not recognised, pass it on to the parent method
+   for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static double ToMJD( AstSystemType oldsys, double oldval, int *status ){
+/*
+*  Name:
+*     ToMJD
+
+*  Purpose:
+*     Convert a time value from TimeFrame's System to MJD.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     double ToMJD( AstSystemType oldsys, double oldval, int *status ){
+
+*  Class Membership:
+*     TimeFrame member function
+
+*  Description:
+*     This function converts the supplied value from the supplied System
+*     to an MJD.
+
+*  Parameters:
+*     oldsys
+*        The System in which the oldval is supplied.
+*     oldval
+*        The value to convert, assumed to be in the default units
+*        associated with "oldsys".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The MJD value corresponding to "oldval"
+
+*  Notes:
+*     - Both old and new value are assumed to be absolute (i.e. have zero
+*     offset).
+
+*/
+
+/* Local Variables; */
+   AstMapping *map;
+   double result;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the old system is MJD just return the value unchanged. */
+   if( oldsys == AST__MJD ) {
+      result = oldval;
+
+/* Otherwise create a TimeMap wich converts from the TimeFrame system to
+   MJD, and use it to transform the supplied value. */
+   } else {
+      map = ToMJDMap( oldsys, 0.0, status );
+
+/* Use the TimeMap to convert the supplied value. */
+      astTran1( map, 1, &oldval, 1, &result );
+
+/* Free resources */
+      map = astAnnul( map );
+
+   }
+
+/* Return the result */
+   return result;
+}
+
+static AstMapping *ToMJDMap( AstSystemType oldsys, double off, int *status ){
+/*
+*  Name:
+*     ToMJDMap
+
+*  Purpose:
+*     Create a Mapping from a specified System to MJD.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     AstMapping *ToMJDMap( AstSystemType oldsys, double off, int *status ){
+
+*  Class Membership:
+*     TimeFrame member function
+
+*  Description:
+*     This function creates a Mapping which converts from the supplied
+*     system and offset to absolute MJD.
+
+*  Parameters:
+*     oldsys
+*        The System in which the oldval is supplied.
+*     off
+*        The axis offset used with the old System, assumed to be in the
+*        default system associated with oldsys.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Mapping.
+
+*/
+
+/* Local Variables; */
+   AstTimeMap *timemap;
+   double args[ 2 ];
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Create a null TimeMap */
+   timemap = astTimeMap( 0, "", status );
+
+/* Set the offsets for the supplied and returned values. */
+   args[ 0 ] = off;
+   args[ 1 ] = 0.0;
+
+/* If required, add a TimeMap conversion which converts from the TimeFrame
+   system to MJD. */
+   if( oldsys == AST__MJD ) {
+/*      if( off != 0.0 ) astTimeAdd( timemap, "MJDTOMJD", 2, args ); */
+      astTimeAdd( timemap, "MJDTOMJD", 2, args );
+
+   } else if( oldsys == AST__JD ) {
+      astTimeAdd( timemap, "JDTOMJD", 2, args );
+
+   } else if( oldsys == AST__JEPOCH ) {
+      astTimeAdd( timemap, "JEPTOMJD", 2, args );
+
+   } else if( oldsys == AST__BEPOCH ) {
+      astTimeAdd( timemap, "BEPTOMJD", 2, args );
+   }
+
+/* Return the result */
+   return (AstMapping *) timemap;
+}
+
+static double ToUnits( AstTimeFrame *this, const char *oldunit, double oldval,
+                       const char *method, int *status ){
+/*
+*
+*  Name:
+*     ToUnits
+
+*  Purpose:
+*     Convert a supplied time value to the default units of the supplied TimeFrame.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     double ToUnits( AstTimeFrame *this, const char *oldunit, double oldval,
+*                     const char *method, int *status )
+
+*  Class Membership:
+*     TimeFrame member function
+
+*  Description:
+*     This function converts the supplied time value from the supplied
+*     units to the default units associated with the supplied TimeFrame's
+*     System.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     oldunit
+*        The units in which "oldval" is supplied.
+*     oldval
+*        The value to be converted.
+*     method
+*        Pointer to a string holding the name of the method to be
+*        included in any error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The converted value.
+
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   const char *defunit;
+   double result;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get default units associated with the System attribute of the supplied
+   TimeFrame, and find a Mapping from the old units to the default. */
+   defunit = DefUnit( astGetSystem( this ), method, "TimeFrame", status );
+   map = astUnitMapper( oldunit, defunit, NULL, NULL );
+   if( map ) {
+
+/* Use the Mapping to convert the supplied value. */
+      astTran1( map, 1, &oldval, 1, &result );
+
+/* Free resources. */
+      map = astAnnul( map );
+
+/* Report an error if no conversion is possible. */
+   } else if( astOK ){
+      astError( AST__BADUN, "%s(%s): Cannot convert the supplied attribute "
+                "value from units of %s to %s.", status, method, astGetClass( this ),
+                 oldunit, defunit );
+   }
+
+/* Return the result */
+   return result;
+}
+
+static int Unformat( AstFrame *this_frame, int axis, const char *string,
+                     double *value, int *status ) {
+/*
+*  Name:
+*     Unformat
+
+*  Purpose:
+*     Read a formatted coordinate value for a TimeFrame axis.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     int Unformat( AstFrame *this, int axis, const char *string,
+*                   double *value, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the public astUnformat
+*     method inherited from the Frame class).
+
+*  Description:
+*     This function reads a formatted coordinate value for a TimeFrame
+*     axis (supplied as a string) and returns the equivalent numerical
+*     value as a double. It also returns the number of characters read
+*     from the string.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     axis
+*        The number of the TimeFrame axis for which the coordinate
+*        value is to be read (axis numbering starts at zero for the
+*        first axis).
+*     string
+*        Pointer to a constant null-terminated string containing the
+*        formatted coordinate value.
+*     value
+*        Pointer to a double in which the coordinate value read will
+*        be returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of characters read from the string to obtain the
+*     coordinate value.
+
+*  Notes:
+*     - Any white space at the beginning of the string will be
+*     skipped, as also will any trailing white space following the
+*     coordinate value read. The function's return value will reflect
+*     this.
+*     - A function value of zero (and no coordinate value) will be
+*     returned, without error, if the string supplied does not contain
+*     a suitably formatted value.
+*     - The string "<bad>" is recognised as a special case and will
+*     generate the value AST__BAD, without error. The test for this
+*     string is case-insensitive and permits embedded white space.
+*     - A function result of zero will be returned and no coordinate
+*     value will be returned via the "value" pointer if this function
+*     is invoked with the global error status set, or if it should
+*     fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map;
+   AstTimeFrame *this;
+   AstTimeScaleType ts1;
+   AstTimeScaleType ts2;
+   const char *c;
+   char *old_fmt;
+   char *str;
+   const char *txt;
+   double mjd;
+   double val1;
+   int l;
+   int lt;
+   int nc1;
+   int nc;
+   int ndp;
+   int rep;
+
+/* Initialise. */
+   nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return nc;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_frame;
+
+/* Validate the axis index. */
+   (void) astValidateAxis( this, axis, 1, "astUnformat" );
+
+/* First attempt to read the value using the parent unformat method, and
+   note how many characters were used. We temporarily clear the Format
+   attribute if it has been set to a date format, since the parent Frame
+   class does not understand date format.*/
+   txt = astGetFormat( this, axis );
+   if( DateFormat( txt, &ndp, NULL, status ) ) {
+       old_fmt = astStore( NULL, txt, strlen( txt ) + 1 );
+       astClearFormat( this, axis );
+   } else {
+       old_fmt = NULL;
+   }
+
+   nc1 = (*parent_unformat)( this_frame, axis, string, &val1, status );
+
+/* Re-instate the original Format */
+   if( old_fmt ) {
+      astSetFormat( this,axis, old_fmt );
+      old_fmt = astFree( old_fmt );
+   }
+
+/* The astReadDateTime function (defined within frame.c) does not allow
+   for any extra text to be appended to the end of the formatted date/time
+   (AST__BAD is returned if any such extra text is present). But astUnformat
+   is contracted to allow such text. So we need to make multiple attempts
+   at reading the date/time in order to find the longest leading string
+   which gives a non-bad value. First take a copy of the supplied string
+   si we can terminate it at any point we wish. */
+   l = astChrLen( string );
+   str = astStore( NULL, string, l + 1 );
+
+/* Now attempt to read an ISO date from the start of the string. We
+   switch off error reporting to avoid reports of unsuitable syntax. */
+   rep = astReporting( 0 );
+
+/* Attempt to read a date/time from the whol string. If this fails
+   terminate the string in order to make it one character shorter and try
+   again. */
+   for( lt = l; lt > 0; lt-- ) {
+      str[ lt ] = 0;
+      mjd = astReadDateTime( str );
+      if( !astOK ) astClearStatus;
+      if( mjd != AST__BAD ) break;
+   }
+
+/* Re-instate error reporting. */
+   astReporting( rep );
+
+/* Free resources. */
+   str = astFree( str );
+
+/* If the whole non-blank start of the string was consumed, add on any
+   trailing white space. */
+   if( lt >= l ) lt = strlen( string );
+
+/* If no date/time could be read, or if reading the value as a
+   floating point value was at least as good, return the floating point
+   value (assumed to be in the system and units of the TimeFrame. */
+   if( mjd == AST__BAD || nc1 >= l ) {
+      *value = val1;
+      nc = nc1;
+
+/* Otherwise, if a date/time was read convert it to the TimeFrame system,
+   etc. */
+   } else if( mjd != AST__BAD ) {
+
+/* Save the number of character read from the supplied string. */
+      nc = lt;
+
+/* We require a value in the timescale of the supplied TimeFrame. Get
+   this TimeScale. */
+      ts2 = astGetTimeScale( this );
+
+/* If the supplied string gave the date/time as a Besselian epoch, the
+   input timescale is TT, otherwise it is assumed to be the TimeScale of
+   the TimeFrame. Locate the first non-space character. */
+      c = string;
+      while( *c && isspace( *c ) ) c++;
+
+/* If the first non-space is a "B", assuming a TT timescale. Otherwise
+   assume the timescale of the supplied TimeFrame. */
+      ts1 = ( *c == 'B' || *c == 'b' ) ? AST__TT : ts2;
+
+/* Create the Mapping and use it to transform the mjd value. */
+      map = MakeMap( this, AST__MJD, astGetSystem( this ), ts1, ts2,
+                     0.0, astGetTimeOrigin( this ), "d",
+                     astGetUnit( this, 0 ), "astFormat", status );
+      if( map ) {
+         astTran1( map, 1, &mjd, 1, value );
+         map = astAnnul( map );
+      } else {
+         astError( AST__INCTS, "astUnformat(%s): Cannot convert the "
+                "supplied date/time string (%s) to the required "
+                "timescale (%s).", status, astGetClass( this ), string,
+                TimeScaleString( ts2, status ) );
+      }
+   }
+
+/* Return the number of characters read. */
+   return nc;
+}
+
+static int ValidateSystem( AstFrame *this, AstSystemType system, const char *method, int *status ) {
+/*
+*
+*  Name:
+*     ValidateSystem
+
+*  Purpose:
+*     Validate a value for a Frame's System attribute.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     int ValidateSystem( AstFrame *this, AstSystemType system,
+*                         const char *method, int *status )
+
+*  Class Membership:
+*     TimeFrame member function (over-rides the astValidateSystem method
+*     inherited from the Frame class).
+
+*  Description:
+*     This function checks the validity of the supplied system value.
+*     If the value is valid, it is returned unchanged. Otherwise, an
+*     error is reported and a value of AST__BADSYSTEM is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the Frame.
+*     system
+*        The system value to be checked.
+*     method
+*        Pointer to a constant null-terminated character string
+*        containing the name of the method that invoked this function
+*        to validate an axis index. This method name is used solely
+*        for constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The validated system value.
+
+*  Notes:
+*     - A value of AST__BADSYSTEM will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstSystemType result;              /* Validated system value */
+
+/* Initialise. */
+   result = AST__BADSYSTEM;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the value is out of bounds, report an error. */
+   if ( system < FIRST_SYSTEM || system > LAST_SYSTEM ) {
+         astError( AST__AXIIN, "%s(%s): Bad value (%d) given for the System "
+                   "or AlignSystem attribute of a %s.", status, method,
+                   astGetClass( this ), (int) system, astGetClass( this ) );
+
+/* Otherwise, return the supplied value. */
+   } else {
+      result = system;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void VerifyAttrs( AstTimeFrame *this, const char *purp,
+                         const char *attrs, const char *method, int *status ) {
+/*
+*  Name:
+*     VerifyAttrs
+
+*  Purpose:
+*     Verify that usable attribute values are available.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     void VerifyAttrs( AstTimeFrame *this, const char *purp,
+*                       const char *attrs, const char *method, int *status  )
+
+*  Class Membership:
+*     TimeFrame member function
+
+*  Description:
+*     This function tests each attribute listed in "attrs". It returns
+*     without action if 1) an explicit value has been set for each attribute
+*     or 2) the UseDefs attribute of the supplied TimeFrame is non-zero.
+*
+*     If UseDefs is zero (indicating that default values should not be
+*     used for attributes), and any of the named attributes does not have
+*     an explicitly set value, then an error is reported.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame.
+*     purp
+*        Pointer to a text string containing a message which will be
+*        included in any error report. This shouldindicate the purpose
+*        for which the attribute value is required.
+*     attrs
+*        A string holding a space separated list of attribute names.
+*     method
+*        A string holding the name of the calling method for use in error
+*        messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   const char *a;
+   const char *desc;
+   const char *p;
+   int len;
+   int set;
+   int state;
+
+/* Check inherited status */
+   if( !astOK ) return;
+
+/* Stop compiler warnings about uninitialised variables */
+   a = NULL;
+   desc = NULL;
+   len = 0;
+   set = 0;
+
+/* If the TimeFrame has a non-zero value for its UseDefs attribute, then
+   all attributes are assumed to have usable values, since the defaults
+   will be used if no explicit value has been set. So we only need to do
+   any checks if UseDefs is zero. */
+   if( !astGetUseDefs( this ) ) {
+
+/* Loop round the "attrs" string identifying the start and length of each
+   non-blank word in the string. */
+      state = 0;
+      p = attrs;
+      while( 1 ) {
+         if( state == 0 ) {
+            if( !isspace( *p ) ) {
+               a = p;
+               len = 1;
+               state = 1;
+            }
+         } else {
+            if( isspace( *p ) || !*p ) {
+
+/* The end of a word has just been reached. Compare it to each known
+   attribute value. Get a flag indicating if the attribute has a set
+   value, and a string describing the attribute.*/
+               if( len > 0 ) {
+
+                  if( !strncmp( "ObsLat", a, len ) ) {
+                     set = astTestObsLat( this );
+                     desc = "observer latitude";
+
+                  } else if( !strncmp( "ObsLon", a, len ) ) {
+                     set = astTestObsLon( this );
+                     desc = "observer longitude";
+
+                  } else if( !strncmp( "ObsAlt", a, len ) ) {
+                     set = astTestObsAlt( this );
+                     desc = "observer altitude";
+
+                  } else if( !strncmp( "Dut1", a, len ) ) {
+                     set = astTestDut1( this );
+                     desc = "UT1-UTC correction";
+
+                  } else if( !strncmp( "TimeOrigin", a, len ) ) {
+                     set = astTestTimeOrigin( this );
+                     desc = "time offset";
+
+                  } else if( !strncmp( "LTOffset", a, len ) ) {
+                     set = astTestLTOffset( this );
+                     desc = "local time offset";
+
+                  } else if( !strncmp( "TimeScale", a, len ) ) {
+                     set = astTestTimeScale( this );
+                     desc = "time scale";
+
+                  } else {
+                     astError( AST__INTER, "VerifyAttrs(TimeFrame): "
+                               "Unknown attribute name \"%.*s\" supplied (AST "
+                               "internal programming error).", status, len, a );
+                  }
+
+/* If the attribute does not have a set value, report an error. */
+                  if( !set && astOK ) {
+                     astError( AST__NOVAL, "%s(%s): Cannot %s.", status, method,
+                               astGetClass( this ), purp );
+                     astError( AST__NOVAL, "No value has been set for "
+                               "the AST \"%.*s\" attribute (%s).", status, len, a,
+                               desc );
+                  }
+
+/* Continue the word search algorithm. */
+               }
+               len = 0;
+               state = 0;
+            } else {
+               len++;
+            }
+         }
+         if( !*(p++) ) break;
+      }
+   }
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+
+/*
+*att++
+*  Name:
+*     TimeOrigin
+
+*  Purpose:
+*     The zero point for TimeFrame axis values
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This specifies the origin from which all time values are measured.
+*     The default value (zero) results in the TimeFrame describing
+*     absolute time values in the system given by the System attribute
+*     (e.g. MJD, Julian epoch, etc). If a TimeFrame is to be used to
+*     describe elapsed time since some origin, the TimeOrigin attribute
+*     should be set to hold the required origin value. The TimeOrigin value
+*     stored inside the TimeFrame structure is modified whenever TimeFrame
+*     attribute values are changed so that it refers to the original moment
+*     in time.
+*
+*  Input Formats:
+*     The formats accepted when setting a TimeOrigin value are listed
+*     below. They are all case-insensitive and are generally tolerant
+*     of extra white space and alternative field delimiters:
+*
+*     - Besselian Epoch: Expressed in decimal years, with or without
+*     decimal places ("B1950" or "B1976.13" for example).
+*
+*     - Julian Epoch: Expressed in decimal years, with or without
+*     decimal places ("J2000" or "J2100.9" for example).
+*
+*     - Units: An unqualified decimal value is interpreted as a value in
+*     the system specified by the TimeFrame's System attribute, in the
+*     units given by the TimeFrame's Unit attribute. Alternatively, an
+*     appropriate unit string can be appended to the end of the floating
+*     point value ("123.4 d" for example), in which case the supplied value
+*     is scaled into the units specified by the Unit attribute.
+*
+*     - Julian Date: With or without decimal places ("JD 2454321.9" for
+*     example).
+*
+*     - Modified Julian Date: With or without decimal places
+*     ("MJD 54321.4" for example).
+*
+*     - Gregorian Calendar Date: With the month expressed either as an
+*     integer or a 3-character abbreviation, and with optional decimal
+*     places to represent a fraction of a day ("1996-10-2" or
+*     "1996-Oct-2.6" for example). If no fractional part of a day is
+*     given, the time refers to the start of the day (zero hours).
+*
+*     - Gregorian Date and Time: Any calendar date (as above) but with
+*     a fraction of a day expressed as hours, minutes and seconds
+*     ("1996-Oct-2 12:13:56.985" for example). The date and time can be
+*     separated by a space or by a "T" (as used by ISO8601 format).
+
+*  Output Format:
+*     When enquiring TimeOrigin values, the returned formatted floating
+*     point value represents a value in the TimeFrame's System, in the unit
+*     specified by the TimeFrame's Unit attribute.
+
+*  Applicability:
+*     TimeFrame
+*        All TimeFrames have this attribute.
+
+*att--
+*/
+/* The time origin, stored internally in the default units associated
+   with the current System value. Clear the TimeOrigin value  by setting it
+   to AST__BAD, which gives 0.0 as the default value. Any value is acceptable. */
+astMAKE_CLEAR(TimeFrame,TimeOrigin,timeorigin,AST__BAD)
+astMAKE_GET(TimeFrame,TimeOrigin,double,0.0,((this->timeorigin!=AST__BAD)?this->timeorigin:0.0))
+astMAKE_SET(TimeFrame,TimeOrigin,double,timeorigin,value)
+astMAKE_TEST(TimeFrame,TimeOrigin,( this->timeorigin != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     LTOffset
+
+*  Purpose:
+*     The offset from UTC to Local Time, in hours.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This specifies the offset from UTC to Local Time, in hours (fractional
+*     hours can be supplied). It is positive for time zones east of Greenwich.
+*     AST uses the figure as given, without making any attempt to correct for
+*     daylight saving. The default value is zero.
+
+*  Applicability:
+*     TimeFrame
+*        All TimeFrames have this attribute.
+
+*att--
+*/
+astMAKE_CLEAR(TimeFrame,LTOffset,ltoffset,AST__BAD)
+astMAKE_GET(TimeFrame,LTOffset,double,0.0,((this->ltoffset!=AST__BAD)?this->ltoffset:0.0))
+astMAKE_SET(TimeFrame,LTOffset,double,ltoffset,value)
+astMAKE_TEST(TimeFrame,LTOffset,( this->ltoffset != AST__BAD ))
+
+/*
+*att++
+*  Name:
+*     AlignTimeScale
+
+*  Purpose:
+*     Time scale to use when aligning TimeFrames.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute controls how a TimeFrame behaves when it is used (by
+c     astFindFrame or astConvert) as a template to match another (target)
+f     AST_FINDFRAME or AST_CONVERT) as a template to match another (target)
+*     TimeFrame. It identifies the time scale in which alignment is
+*     to occur. See the TimeScale attribute for a desription of the values
+*     which may be assigned to this attribute. The default AlignTimeScale
+*     value depends on the current value of TimeScale: if TimeScale is
+*     UT1, GMST, LMST or LAST, the default for AlignTimeScale is UT1, for all
+*     other TimeScales the default is TAI.
+*
+c     When astFindFrame or astConvert is used on two TimeFrames (potentially
+f     When AST_FindFrame or AST_CONVERT is used on two TimeFrames (potentially
+*     describing different time coordinate systems), it returns a Mapping
+*     which can be used to transform a position in one TimeFrame into the
+*     corresponding position in the other. The Mapping is made up of the
+*     following steps in the indicated order:
+*
+*     - Map values from the system used by the target (MJD, JD, etc) to the
+*     system specified by the AlignSystem attribute.
+*
+*     - Map these values from the target's time scale to the time scale
+*     specified by the AlignTimeScale attribute.
+*
+*     - Map these values from the time scale specified by the AlignTimeScale
+*     attribute, to the template's time scale.
+*
+*     - Map these values from the system specified by the AlignSystem
+*     attribute, to the system used by the template.
+
+*  Applicability:
+*     TimeFrame
+*        All TimeFrames have this attribute.
+
+*att--
+*/
+astMAKE_TEST(TimeFrame,AlignTimeScale,( this->aligntimescale != AST__BADTS ))
+astMAKE_CLEAR(TimeFrame,AlignTimeScale,aligntimescale,AST__BADTS)
+astMAKE_SET(TimeFrame,AlignTimeScale,AstTimeScaleType,aligntimescale,(
+            ( ( value >= FIRST_TS ) && ( value <= LAST_TS ) ) ?
+                 value :
+                 ( astError( AST__ATTIN, "%s(%s): Bad value (%d) "
+                             "given for AlignTimeScale attribute.", status,
+                             "astSetAlignTimeScale", astGetClass( this ), (int) value ),
+
+/* Leave the value unchanged on error. */
+                                            this->aligntimescale ) ) )
+
+/*
+*att++
+*  Name:
+*     TimeScale
+
+*  Purpose:
+*     Time scale.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute identifies the time scale to which the time axis values
+*     of a TimeFrame refer, and may take any of the values listed in the
+*     "Time Scales" section (below).
+*
+*     The default TimeScale value depends on the current System value; if
+*     the current TimeFrame system is "Besselian epoch" the default is
+*     "TT", otherwise it is "TAI". Note, if the System attribute is set
+*     so that the TimeFrame represents Besselian Epoch, then an error
+*     will be reported if an attempt is made to set the TimeScale to
+*     anything other than TT.
+*
+*     Note, the supported time scales fall into two groups. The first group
+*     containing  UT1, GMST, LAST and LMST define time in terms of the
+*     orientation of the earth. The second group (containing all the remaining
+*     time scales) define time in terms of an atomic process. Since the rate of
+*     rotation of the earth varies in an unpredictable way, conversion between
+*     two timescales in different groups relies on a value being supplied for
+*     the Dut1 attribute (defined by the parent Frame class). This attribute
+*     specifies the difference between the UT1 and UTC time scales, in seconds,
+*     and defaults to zero. See the documentation for the Dut1 attribute for
+*     further details.
+
+*  Applicability:
+*     TimeFrame
+*        All TimeFrames have this attribute.
+
+*  Time Scales:
+*     The TimeFrame class supports the following TimeScale values (all are
+*     case-insensitive):
+*
+*     - "TAI" - International Atomic Time
+*     - "UTC" - Coordinated Universal Time
+*     - "UT1" - Universal Time
+*     - "GMST" - Greenwich Mean Sidereal Time
+*     - "LAST" - Local Apparent Sidereal Time
+*     - "LMST" - Local Mean Sidereal Time
+*     - "TT" - Terrestrial Time
+*     - "TDB" - Barycentric Dynamical Time
+*     - "TCB" - Barycentric Coordinate Time
+*     - "TCG" - Geocentric Coordinate Time
+*     - "LT" - Local Time (the offset from UTC is given by attribute LTOffset)
+*
+*     An very informative description of these and other time scales is
+*     available at http://www.ucolick.org/~sla/leapsecs/timescales.html.
+
+*  UTC Warnings:
+*     UTC should ideally be expressed using separate hours, minutes and
+*     seconds fields (or at least in seconds for a given date) if leap seconds
+*     are to be taken into account. Since the TimeFrame class represents
+*     each moment in time using a single floating point number (the axis value)
+*     there will be an ambiguity during a leap second. Thus an error of up to
+*     1 second can result when using AST to convert a UTC time to another
+*     time scale if the time occurs within a leap second. Leap seconds
+*     occur at most twice a year, and are introduced to take account of
+*     variation in the rotation of the earth. The most recent leap second
+*     occurred on 1st January 1999. Although in the vast majority of cases
+*     leap second ambiguities won't matter, there are potential problems in
+*     on-line data acquisition systems and in critical applications involving
+*     taking the difference between two times.
+
+*att--
+*/
+astMAKE_TEST(TimeFrame,TimeScale,( this->timescale != AST__BADTS ))
+
+/* Copy constructor. */
+/* ----------------- */
+
+/* Destructor. */
+/* ----------- */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for TimeFrame objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the TimeFrame class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeFrame whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstTimeFrame *this;           /* Pointer to the TimeFrame structure */
+   AstTimeScaleType ts;          /* TimeScale attribute value */
+   const char *sval;             /* Pointer to string value */
+   double dval;                  /* Double value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the TimeFrame structure. */
+   this = (AstTimeFrame *) this_object;
+
+/* Write out values representing the instance variables for the
+   TimeFrame class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* TimeScale. */
+/* ---------- */
+   set = TestTimeScale( this, status );
+   ts = set ? GetTimeScale( this, status ) : astGetTimeScale( this );
+
+/* If set, convert explicitly to a string for the external
+   representation. */
+   sval = "";
+   if ( set ) {
+      if ( astOK ) {
+         sval = TimeScaleString( ts, status );
+
+/* Report an error if the TimeScale value was not recognised. */
+         if ( !sval ) {
+            astError( AST__SCSIN,
+                     "%s(%s): Corrupt %s contains invalid time scale "
+                     "identification code (%d).", status, "astWrite",
+                     astGetClass( channel ), astGetClass( this ), (int) ts );
+         }
+      }
+
+/* If not set, use astGetAttrib which returns a string value using
+   (possibly over-ridden) methods. */
+   } else {
+      sval = astGetAttrib( this_object, "timescale" );
+   }
+
+/* Write out the value. */
+   astWriteString( channel, "TmScl", set, 1, sval, "Time scale" );
+
+/* AlignTimeScale. */
+/* --------------- */
+   set = TestAlignTimeScale( this, status );
+   ts = set ? GetAlignTimeScale( this, status ) : astGetAlignTimeScale( this );
+
+/* If set, convert explicitly to a string for the external representation. */
+   if ( set ) {
+      if ( astOK ) {
+         sval = TimeScaleString( ts, status );
+
+/* Report an error if the TimeScale value was not recognised. */
+         if ( !sval ) {
+            astError( AST__SCSIN,
+                     "%s(%s): Corrupt %s contains invalid alignment time "
+                     "scale identification code (%d).", status, "astWrite",
+                     astGetClass( channel ), astGetClass( this ), (int) ts );
+         }
+      }
+
+/* If not set, use astGetAttrib which returns a string value using
+   (possibly over-ridden) methods. */
+   } else {
+      sval = astGetAttrib( this_object, "aligntimescale" );
+   }
+
+/* Write out the value. */
+   astWriteString( channel, "ATmScl", set, 0, sval, "Alignment time scale" );
+
+/* TimeOrigin. */
+/* ----------- */
+   set = TestTimeOrigin( this, status );
+   dval = set ? GetTimeOrigin( this, status ) : astGetTimeOrigin( this );
+   astWriteDouble( channel, "TmOrg", set, 0, dval, "Time offset" );
+
+/* LTOffset. */
+/* --------- */
+   set = TestLTOffset( this, status );
+   dval = set ? GetLTOffset( this, status ) : astGetLTOffset( this );
+   astWriteDouble( channel, "LTOff", set, 0, dval, "Local Time offset from UTC" );
+
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsATimeFrame and astCheckTimeFrame functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(TimeFrame,Frame)
+astMAKE_CHECK(TimeFrame)
+
+AstTimeFrame *astTimeFrame_( const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astTimeFrame
+
+*  Purpose:
+*     Create a TimeFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     AstTimeFrame *astTimeFrame( const char *options, int *status, ... )
+
+*  Class Membership:
+*     TimeFrame constructor.
+
+*  Description:
+*     This function creates a new TimeFrame and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new TimeFrame. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new TimeFrame.
+
+*  Notes:
+*     -  A NULL pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic TimeFrame constructor which
+*     is available via the protected interface to the TimeFrame class.
+*     A public interface is provided by the astTimeFrameId_ function.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMapping *um;               /* Mapping from default to actual units */
+   AstTimeFrame *new;            /* Pointer to new TimeFrame */
+   AstSystemType s;              /* System */
+   const char *u;                /* Units string */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the TimeFrame, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitTimeFrame( NULL, sizeof( AstTimeFrame ), !class_init,
+                           &class_vtab, "TimeFrame" );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new TimeFrame's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* Check the Units are appropriate for the System. */
+      u = astGetUnit( new, 0 );
+      s = astGetSystem( new );
+      um = astUnitMapper( DefUnit( s, "astTimeFrame", "TimeFrame", status ),
+                          u, NULL, NULL );
+      if( um ) {
+         um = astAnnul( um );
+      } else {
+         astError( AST__BADUN, "astTimeFrame: Inappropriate units (%s) "
+                   "specified for a %s axis.", status, u, SystemLabel( s, status ) );
+      }
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new TimeFrame. */
+   return new;
+}
+
+AstTimeFrame *astInitTimeFrame_( void *mem, size_t size, int init,
+                                 AstTimeFrameVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitTimeFrame
+
+*  Purpose:
+*     Initialise a TimeFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     AstTimeFrame *astInitTimeFrame( void *mem, size_t size, int init,
+*                                     AstFrameVtab *vtab, const char *name )
+
+*  Class Membership:
+*     TimeFrame initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new TimeFrame object. It allocates memory (if
+*     necessary) to accommodate the TimeFrame plus any additional data
+*     associated with the derived class. It then initialises a
+*     TimeFrame structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual function
+*     table for a TimeFrame at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the TimeFrame is to be
+*	created. This must be of sufficient size to accommodate the
+*	TimeFrame data (sizeof(TimeFrame)) plus any data used by
+*	the derived class. If a value of NULL is given, this function
+*	will allocate the memory itself using the "size" parameter to
+*	determine its size.
+*     size
+*        The amount of memory used by the TimeFrame (plus derived
+*	class data). This will be used to allocate memory if a value of
+*	NULL is given for the "mem" parameter. This value is also stored
+*	in the TimeFrame structure, so a valid value must be supplied
+*	even if not required for allocating memory.
+*     init
+*        A logical flag indicating if the TimeFrame's virtual function
+*	table is to be initialised. If this value is non-zero, the
+*	virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*	associated with the new TimeFrame.
+*     name
+*        Pointer to a constant null-terminated character string which
+*	contains the name of the class to which the new object belongs
+*	(it is this pointer value that will subsequently be returned by
+*	the astGetClass method).
+
+*  Returned Value:
+*     A pointer to the new TimeFrame.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstTimeFrame *new;        /* Pointer to the new TimeFrame */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitTimeFrameVtab( vtab, name );
+
+/* Initialise a 1D Frame structure (the parent class) as the first component
+   within the TimeFrame structure, allocating memory if necessary. */
+   new = (AstTimeFrame *) astInitFrame( mem, size, 0,
+                                        (AstFrameVtab *) vtab, name, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the TimeFrame data. */
+/* ----------------------------- */
+/* Initialise all attributes to their "undefined" values. */
+      new->timeorigin = AST__BAD;
+      new->ltoffset = AST__BAD;
+      new->timescale = AST__BADTS;
+      new->aligntimescale = AST__BADTS;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstTimeFrame *astLoadTimeFrame_( void *mem, size_t size,
+                                 AstTimeFrameVtab *vtab,
+                                 const char *name, AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadTimeFrame
+
+*  Purpose:
+*     Load a TimeFrame.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timeframe.h"
+*     AstTimeFrame *astLoadTimeFrame( void *mem, size_t size,
+*                                      AstTimeFrameVtab *vtab,
+*                                      const char *name, AstChannel *channel )
+
+*  Class Membership:
+*     TimeFrame loader.
+
+*  Description:
+*     This function is provided to load a new TimeFrame using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     TimeFrame structure in this memory, using data read from the
+*     input Channel.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the TimeFrame is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        TimeFrame data (sizeof(TimeFrame)) plus any data used by
+*        derived classes. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the TimeFrame (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the TimeFrame structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstTimeFrame) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new TimeFrame. If this is NULL, a pointer
+*        to the (static) virtual function table for the TimeFrame class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "TimeFrame" is used instead.
+
+*  Returned Value:
+*     A pointer to the new TimeFrame.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstTimeFrame *new;            /* Pointer to the new TimeFrame */
+   char *sval;                   /* Pointer to string value */
+   double obslat;                /* Value for ObsLat attribute */
+   double obslon;                /* Value for ObsLon attribute */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this TimeFrame. In this case the
+   TimeFrame belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstTimeFrame );
+      vtab = &class_vtab;
+      name = "TimeFrame";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitTimeFrameVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built TimeFrame. */
+   new = astLoadFrame( mem, size, (AstFrameVtab *) vtab, name,
+                       channel );
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+       astReadClassData( channel, "TimeFrame" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* TimeScale. */
+/* ---------- */
+/* Set the default and read the external representation as a string. */
+       new->timescale = AST__BADTS;
+       sval = astReadString( channel, "tmscl", NULL );
+
+/* If a value was read, convert from a string to a TimeScale code. */
+       if ( sval ) {
+          if ( astOK ) {
+             new->timescale = TimeScaleCode( sval, status );
+
+/* Report an error if the value wasn't recognised. */
+             if ( new->timescale == AST__BADTS ) {
+                astError( AST__ATTIN,
+                          "astRead(%s): Invalid time scale description "
+                          "\"%s\".", status, astGetClass( channel ), sval );
+             }
+          }
+
+/* Free the string value. */
+          sval = astFree( sval );
+       }
+
+/* AlignTimeScale. */
+/* --------------- */
+/* Set the default and read the external representation as a string. */
+       new->aligntimescale = AST__BADTS;
+       sval = astReadString( channel, "atmscl", NULL );
+
+/* If a value was read, convert from a string to a TimeScale code. */
+       if ( sval ) {
+          if ( astOK ) {
+             new->aligntimescale = TimeScaleCode( sval, status );
+
+/* Report an error if the value wasn't recognised. */
+             if ( new->aligntimescale == AST__BADTS ) {
+                astError( AST__ATTIN,
+                          "astRead(%s): Invalid alignment time scale "
+                          "description \"%s\".", status, astGetClass( channel ), sval );
+             }
+          }
+
+/* Free the string value. */
+          sval = astFree( sval );
+       }
+
+/* ClockLat. */
+/* --------- */
+/* Retained for backward compatibility with older versions of AST in
+   which TimeFrame had a ClockLat attribute (now ObsLat is used instead). */
+      if( !astTestObsLat( new ) ) {
+         obslat = astReadDouble( channel, "cllat", AST__BAD );
+         if ( obslat != AST__BAD ) astSetObsLat( new, obslat );
+      }
+
+/* ClockLon. */
+/* ------- */
+/* Retained for backward compatibility with older versions of AST in
+   which TimeFrame had a ClockLon attribute (now ObsLon is used instead). */
+      if( !astTestObsLon( new ) ) {
+         obslon = astReadDouble( channel, "cllon", AST__BAD );
+         if ( obslon != AST__BAD ) astSetObsLon( new, obslon );
+      }
+
+/* TimeOrigin. */
+/* --------- */
+      new->timeorigin = astReadDouble( channel, "tmorg", AST__BAD );
+      if ( TestTimeOrigin( new, status ) ) SetTimeOrigin( new, new->timeorigin, status );
+
+/* LTOffset. */
+/* --------- */
+      new->ltoffset = astReadDouble( channel, "ltoff", AST__BAD );
+      if ( TestLTOffset( new, status ) ) SetLTOffset( new, new->ltoffset, status );
+
+/* If an error occurred, clean up by deleting the new TimeFrame. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new TimeFrame pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+void astSetTimeScale_( AstTimeFrame *this, AstTimeScaleType value, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,TimeFrame,SetTimeScale))(this,value, status );
+}
+
+void astClearTimeScale_( AstTimeFrame *this, int *status ){
+   if ( !astOK ) return;
+   (**astMEMBER(this,TimeFrame,ClearTimeScale))(this, status );
+}
+
+AstTimeScaleType astGetAlignTimeScale_( AstTimeFrame *this, int *status ) {
+   if ( !astOK ) return AST__BADTS;
+   return (**astMEMBER(this,TimeFrame,GetAlignTimeScale))(this, status );
+}
+
+AstTimeScaleType astGetTimeScale_( AstTimeFrame *this, int *status ) {
+   if ( !astOK ) return AST__BADTS;
+   return (**astMEMBER(this,TimeFrame,GetTimeScale))(this, status );
+}
+
+double astCurrentTime_( AstTimeFrame *this, int *status ){
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,TimeFrame,CurrentTime))(this, status );
+}
+
+
+
+/* Special public interface functions. */
+/* =================================== */
+/* These provide the public interface to certain special functions
+   whose public interface cannot be handled using macros (such as
+   astINVOKE) alone. In general, they are named after the
+   corresponding protected version of the function, but with "Id"
+   appended to the name. */
+
+/* Public Interface Function Prototypes. */
+/* ------------------------------------- */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstTimeFrame *astTimeFrameId_( const char *, ... );
+
+/* Special interface function implementations. */
+/* ------------------------------------------- */
+AstTimeFrame *astTimeFrameId_( const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astTimeFrame
+f     AST_TIMEFRAME
+
+*  Purpose:
+*     Create a TimeFrame.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "timeframe.h"
+c     AstTimeFrame *astTimeFrame( const char *options, ... )
+f     RESULT = AST_TIMEFRAME( OPTIONS, STATUS )
+
+*  Class Membership:
+*     TimeFrame constructor.
+
+*  Description:
+*     This function creates a new TimeFrame and optionally initialises
+*     its attributes.
+*
+*     A TimeFrame is a specialised form of one-dimensional Frame which
+*     represents various coordinate systems used to describe positions in
+*     time.
+*
+*     A TimeFrame represents a moment in time as either an Modified Julian
+*     Date (MJD), a Julian Date (JD), a Besselian epoch or a Julian epoch,
+*     as determined by the System attribute. Optionally, a zero point can be
+*     specified (using attribute TimeOrigin) which results in the TimeFrame
+*     representing time offsets from the specified zero point.
+*
+*     Even though JD and MJD are defined as being in units of days, the
+*     TimeFrame class allows other units to be used (via the Unit attribute)
+*     on the basis of simple scalings (60 seconds = 1 minute, 60 minutes = 1
+*     hour, 24 hours = 1 day, 365.25 days = 1 year). Likewise, Julian epochs
+*     can be described in units other than the usual years. Besselian epoch
+*     are always represented in units of (tropical) years.
+*
+*     The TimeScale attribute allows the time scale to be specified (that
+*     is, the physical proces used to define the rate of flow of time).
+*     MJD, JD and Julian epoch can be used to represent a time in any
+*     supported time scale. However, Besselian epoch may only be used with the
+*     "TT" (Terrestrial Time) time scale. The list of supported time scales
+*     includes universal time and siderial time. Strictly, these represent
+*     angles rather than time scales, but are included in the list since
+*     they are in common use and are often thought of as time scales.
+*
+*     When a time value is formatted it can be formated either as a simple
+*     floating point value, or as a Gregorian date (see the Format
+*     attribute).
+
+*  Parameters:
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new TimeFrame. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new TimeFrame. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astTimeFrame()
+f     AST_TIMEFRAME = INTEGER
+*        A pointer to the new TimeFrame.
+
+*  Notes:
+*     - When conversion between two TimeFrames is requested (as when
+c     supplying TimeFrames to astConvert),
+f     supplying TimeFrames AST_CONVERT),
+*     account will be taken of the nature of the time coordinate systems
+*     they represent, together with any qualifying time scale, offset,
+*     unit, etc. The AlignSystem and AlignTimeScale attributes will also be
+*     taken into account.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astTimeFrame constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astTimeFrame_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - The variable argument list also prevents this function from
+*     invoking astTimeFrame_ directly, so it must be a
+*     re-implementation of it in all respects, except for the final
+*     conversion of the result to an ID value.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMapping *um;               /* Mapping from default to actual units */
+   AstTimeFrame *new;            /* Pointer to new TimeFrame */
+   AstSystemType s;              /* System */
+   const char *u;                /* Units string */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the TimeFrame, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitTimeFrame( NULL, sizeof( AstTimeFrame ), !class_init,
+                           &class_vtab, "TimeFrame" );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new TimeFrame's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* Check the Units are appropriate for the System. */
+      u = astGetUnit( new, 0 );
+      s = astGetSystem( new );
+      um = astUnitMapper( DefUnit( s, "astTimeFrame", "TimeFrame", status ),
+                          u, NULL, NULL );
+      if( um ) {
+         um = astAnnul( um );
+      } else {
+         astError( AST__BADUN, "astTimeFrame: Inappropriate units (%s) "
+                   "specified for a %s axis.", status, u, SystemLabel( s, status ) );
+      }
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new TimeFrame. */
+   return astMakeId( new );
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ast/timeframe.h b/ast/timeframe.h
new file mode 100644
index 0000000..4aae267
--- /dev/null
+++ b/ast/timeframe.h
@@ -0,0 +1,324 @@
+#if !defined( TIMEFRAME_INCLUDED ) /* Include this file only once */
+#define TIMEFRAME_INCLUDED
+/*
+*+
+*  Name:
+*     timeframe.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the TimeFrame class.
+
+*  Invocation:
+*     #include "timeframe.h"
+
+*  Description:
+*     This include file defines the interface to the TimeFrame class
+*     and provides the type definitions, function prototypes and
+*     macros, etc. needed to use this class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     20-MAY-2005 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "object.h"              /* Base Object class */
+#include "frame.h"               /* Parent Frame class */
+#include "skyframe.h"            /* Celestial coordinate systems */
+
+/* Macros. */
+/* ======= */
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Values used to represent different System attribute values. */
+#define AST__MJD    1
+#define AST__JD     2
+#define AST__JEPOCH 3
+#define AST__BEPOCH 4
+
+/* Values used to represent different TimeScale attribute values. */
+#define AST__BADTS        0
+#define AST__TAI          1
+#define AST__UTC          2
+#define AST__UT1          3
+#define AST__GMST         4
+#define AST__LAST         5
+#define AST__LMST         6
+#define AST__TT           7
+#define AST__TDB          8
+#define AST__TCB          9
+#define AST__TCG         10
+#define AST__LT          11
+
+/* Define constants used to size global arrays in this module. */
+#define AST__TIMEFRAME_FORMAT_BUFF_LEN 200
+#define AST__TIMEFRAME_GETATTRIB_BUFF_LEN 50
+#define AST__TIMEFRAME_GETLABEL_BUFF_LEN 200
+#define AST__TIMEFRAME_GETSYMBOL_BUFF_LEN 20
+#define AST__TIMEFRAME_GETTITLE_BUFF_LEN 200
+
+#endif
+
+/* Type Definitions. */
+/* ================= */
+
+/* Integer type used to store the TimeScale attribute. */
+typedef int AstTimeScaleType;
+
+/* TimeFrame structure. */
+/* ------------------- */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstTimeFrame {
+
+/* Attributes inherited from the parent class. */
+   AstFrame frame;               /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double ltoffset;              /* Offset from UTC to Local Time */
+   double timeorigin;            /* Zero point for time axis */
+   AstTimeScaleType timescale;   /* Time scale */
+   AstTimeScaleType aligntimescale; /* Alignment time scale */
+} AstTimeFrame;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all objects in the
+   class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstTimeFrameVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstFrameVtab frame_vtab;      /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   double (* CurrentTime)( AstTimeFrame *, int * );
+
+   double (* GetLTOffset)( AstTimeFrame *, int * );
+   int (* TestLTOffset)( AstTimeFrame *, int * );
+   void (* ClearLTOffset)( AstTimeFrame *, int * );
+   void (* SetLTOffset)( AstTimeFrame *, double, int * );
+
+   double (* GetTimeOrigin)( AstTimeFrame *, int * );
+   int (* TestTimeOrigin)( AstTimeFrame *, int * );
+   void (* ClearTimeOrigin)( AstTimeFrame *, int * );
+   void (* SetTimeOrigin)( AstTimeFrame *, double, int * );
+
+   AstTimeScaleType (* GetTimeScale)( AstTimeFrame *, int * );
+   int (* TestTimeScale)( AstTimeFrame *, int * );
+   void (* ClearTimeScale)( AstTimeFrame *, int * );
+   void (* SetTimeScale)( AstTimeFrame *, AstTimeScaleType, int * );
+
+   AstTimeScaleType (* GetAlignTimeScale)( AstTimeFrame *, int * );
+   int (* TestAlignTimeScale)( AstTimeFrame *, int * );
+   void (* ClearAlignTimeScale)( AstTimeFrame *, int * );
+   void (* SetAlignTimeScale)( AstTimeFrame *, AstTimeScaleType, int * );
+
+} AstTimeFrameVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstTimeFrameGlobals {
+   AstTimeFrameVtab Class_Vtab;
+   int Class_Init;
+   char Format_Buff[ AST__TIMEFRAME_FORMAT_BUFF_LEN + 1 ];
+   char GetAttrib_Buff[ AST__TIMEFRAME_GETATTRIB_BUFF_LEN + 1 ];
+   char GetLabel_Buff[ AST__TIMEFRAME_GETLABEL_BUFF_LEN + 1 ];
+   char GetSymbol_Buff[ AST__TIMEFRAME_GETSYMBOL_BUFF_LEN + 1 ];
+   char GetTitle_Buff[ AST__TIMEFRAME_GETTITLE_BUFF_LEN + 1 ];
+} AstTimeFrameGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(TimeFrame)         /* Check class membership */
+astPROTO_ISA(TimeFrame)           /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+AstTimeFrame *astTimeFrame_( const char *, int *, ...);
+#else
+AstTimeFrame *astTimeFrameId_( const char *, ... )__attribute__((format(printf,1,2)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstTimeFrame *astInitTimeFrame_( void *, size_t, int, AstTimeFrameVtab *,
+                                 const char *, int * );
+
+/* Vtab initialiser. */
+void astInitTimeFrameVtab_( AstTimeFrameVtab *, const char *, int * );
+
+/* Loader. */
+AstTimeFrame *astLoadTimeFrame_( void *, size_t, AstTimeFrameVtab *,
+                                 const char *, AstChannel *channel, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitTimeFrameGlobals_( AstTimeFrameGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+double astCurrentTime_( AstTimeFrame *, int * );
+
+#if defined(astCLASS)            /* Protected */
+
+double astGetLTOffset_( AstTimeFrame *, int * );
+int astTestLTOffset_( AstTimeFrame *, int * );
+void astClearLTOffset_( AstTimeFrame *, int * );
+void astSetLTOffset_( AstTimeFrame *, double, int * );
+
+double astGetTimeOrigin_( AstTimeFrame *, int * );
+int astTestTimeOrigin_( AstTimeFrame *, int * );
+void astClearTimeOrigin_( AstTimeFrame *, int * );
+void astSetTimeOrigin_( AstTimeFrame *, double, int * );
+
+AstTimeScaleType astGetTimeScale_( AstTimeFrame *, int * );
+int astTestTimeScale_( AstTimeFrame *, int * );
+void astClearTimeScale_( AstTimeFrame *, int * );
+void astSetTimeScale_( AstTimeFrame *, AstTimeScaleType, int * );
+
+AstTimeScaleType astGetAlignTimeScale_( AstTimeFrame *, int * );
+int astTestAlignTimeScale_( AstTimeFrame *, int * );
+void astClearAlignTimeScale_( AstTimeFrame *, int * );
+void astSetAlignTimeScale_( AstTimeFrame *, AstTimeScaleType, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckTimeFrame(this) astINVOKE_CHECK(TimeFrame,this,0)
+#define astVerifyTimeFrame(this) astINVOKE_CHECK(TimeFrame,this,1)
+
+/* Test class membership. */
+#define astIsATimeFrame(this) astINVOKE_ISA(TimeFrame,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected */
+#define astTimeFrame astINVOKE(F,astTimeFrame_)
+#else
+#define astTimeFrame astINVOKE(F,astTimeFrameId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitTimeFrame(mem,size,init,vtab,name) \
+astINVOKE(O,astInitTimeFrame_(mem,size,init,vtab,name,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitTimeFrameVtab(vtab,name) astINVOKE(V,astInitTimeFrameVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadTimeFrame(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadTimeFrame_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+
+/* None. */
+
+/* Interfaces to protected member functions. */
+/* ----------------------------------------- */
+/* Here we make use of astCheckTimeFrame to validate TimeFrame pointers
+   before use. This provides a contextual error report if a pointer to
+   the wrong sort of object is supplied. */
+
+#define astCurrentTime(this) astINVOKE(V,astCurrentTime_(astCheckTimeFrame(this),STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+
+#define astGetTimeOrigin(this) astINVOKE(V,astGetTimeOrigin_(astCheckTimeFrame(this),STATUS_PTR))
+#define astTestTimeOrigin(this) astINVOKE(V,astTestTimeOrigin_(astCheckTimeFrame(this),STATUS_PTR))
+#define astClearTimeOrigin(this) astINVOKE(V,astClearTimeOrigin_(astCheckTimeFrame(this),STATUS_PTR))
+#define astSetTimeOrigin(this,value) astINVOKE(V,astSetTimeOrigin_(astCheckTimeFrame(this),value,STATUS_PTR))
+
+#define astGetLTOffset(this) astINVOKE(V,astGetLTOffset_(astCheckTimeFrame(this),STATUS_PTR))
+#define astTestLTOffset(this) astINVOKE(V,astTestLTOffset_(astCheckTimeFrame(this),STATUS_PTR))
+#define astClearLTOffset(this) astINVOKE(V,astClearLTOffset_(astCheckTimeFrame(this),STATUS_PTR))
+#define astSetLTOffset(this,value) astINVOKE(V,astSetLTOffset_(astCheckTimeFrame(this),value,STATUS_PTR))
+
+#define astGetTimeScale(this) astINVOKE(V,astGetTimeScale_(astCheckTimeFrame(this),STATUS_PTR))
+#define astTestTimeScale(this) astINVOKE(V,astTestTimeScale_(astCheckTimeFrame(this),STATUS_PTR))
+#define astClearTimeScale(this) astINVOKE(V,astClearTimeScale_(astCheckTimeFrame(this),STATUS_PTR))
+#define astSetTimeScale(this,value) astINVOKE(V,astSetTimeScale_(astCheckTimeFrame(this),value,STATUS_PTR))
+
+#define astGetAlignTimeScale(this) astINVOKE(V,astGetAlignTimeScale_(astCheckTimeFrame(this),STATUS_PTR))
+#define astTestAlignTimeScale(this) astINVOKE(V,astTestAlignTimeScale_(astCheckTimeFrame(this),STATUS_PTR))
+#define astClearAlignTimeScale(this) astINVOKE(V,astClearAlignTimeScale_(astCheckTimeFrame(this),STATUS_PTR))
+#define astSetAlignTimeScale(this,value) astINVOKE(V,astSetAlignTimeScale_(astCheckTimeFrame(this),value,STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/timemap.c b/ast/timemap.c
new file mode 100644
index 0000000..45cdf7a
--- /dev/null
+++ b/ast/timemap.c
@@ -0,0 +1,5219 @@
+/*
+*class++
+*  Name:
+*     TimeMap
+
+*  Purpose:
+*     Sequence of time coordinate conversions.
+
+*  Constructor Function:
+c     astTimeMap (also see astTimeAdd)
+f     AST_TIMEMAP (also see AST_TIMEADD)
+
+*  Description:
+*     A TimeMap is a specialised form of 1-dimensional Mapping which can be
+*     used to represent a sequence of conversions between standard time
+*     coordinate systems.
+*
+*     When a TimeMap is first created, it simply performs a unit
+c     (null) Mapping. Using the astTimeAdd
+f     (null) Mapping. Using the AST_TIMEADD
+c     function, a series of coordinate conversion steps may then be
+f     routine, a series of coordinate conversion steps may then be
+*     added. This allows multi-step conversions between a variety of
+*     time coordinate systems to be assembled out of a set of building
+*     blocks.
+*
+*     For details of the individual coordinate conversions available,
+c     see the description of the astTimeAdd function.
+f     see the description of the AST_TIMEADD routine.
+
+*  Inheritance:
+*     The TimeMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The TimeMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     In addition to those functions applicable to all Mappings, the
+c     following function may also be applied to all TimeMaps:
+f     In addition to those routines applicable to all Mappings, the
+f     following routine may also be applied to all TimeMaps:
+*
+c     - astTimeAdd: Add a time coordinate conversion to an TimeMap
+f     - AST_TIMEADD: Add a time coordinate conversion to an TimeMap
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     NG: Norman Gray (Starlink)
+*     DSB: David Berry (Starlink)
+
+*  History:
+*     5-Sep-2003 (NG):
+*        Original version (drawing heavily on specmap.c)
+*     25-MAY-2005 (DSB):
+*        Extensive modifications to make it more AST-like.
+*     10-AUG-2005 (DSB):
+*        Add 2006 leap second.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*     15-OCT-2006 (DSB):
+*        Add conversions between UT1 and UTC (UTTOUTC and UTCTOUT).
+*     3-APR-2008 (DSB):
+*        Only call memcpy if the source and destination pointers are
+*        different.
+*     15-APR-2008 (DSB):
+*        Add missing "break;" statement to "case AST__LMSTTOGMST:"
+*        in Transform.
+*     20-MAY-2008 (DSB):
+*        Add conversions between Local Time and UTC (LTTOUTC and UTCTOLT).
+*     18-JUN-2009 (DSB):
+*        Add OBSALT to argument list for TTTOTDB and TDBTOTT. Change
+*        CLOCKLAT/LON to OBSLAT/LON for consistency with other classes.
+*     1-SEP-2016 (DSB):
+*        Add 2017 January 1 leap second.
+*     11-NOV-2016 (DSB):
+*        Add argument "narg" to astTimeAdd method.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS TimeMap
+
+/* Codes to identify time coordinate conversions. */
+#define AST__TIME_NULL   0       /* Null value */
+#define AST__MJDTOMJD    1       /* MJD to MJD */
+#define AST__MJDTOJD     2       /* MJD to JD */
+#define AST__JDTOMJD     3       /* JD to MJD */
+#define AST__MJDTOBEP    4       /* MJD to Besselian epoch */
+#define AST__BEPTOMJD    5       /* Besselian epoch to MJD */
+#define AST__MJDTOJEP    6       /* MJD to Julian epoch */
+#define AST__JEPTOMJD    7       /* Julian epoch to MJD */
+#define AST__TAITOUTC    8       /* TAI to UTC */
+#define AST__UTCTOTAI    9       /* UTC to TAI */
+#define AST__TTTOTAI    10       /* TT to TAI */
+#define AST__TAITOTT    11       /* TAI to TT */
+#define AST__TDBTOTT    12       /* TDB to TT */
+#define AST__TTTOTDB    13       /* TT to TDB */
+#define AST__TCGTOTT    14       /* TCG to TT */
+#define AST__TTTOTCG    15       /* TT to TCG */
+#define AST__TCBTOTDB   16       /* TCB to TDB */
+#define AST__TDBTOTCB   17       /* TDB to TCB */
+#define AST__UTTOGMST   18       /* UT to GMST */
+#define AST__GMSTTOUT   19       /* GMST to UT1 */
+#define AST__GMSTTOLMST 20       /* GMST to LMST */
+#define AST__LMSTTOGMST 21       /* LMST to GMST */
+#define AST__LASTTOLMST 22       /* LAST to LMST */
+#define AST__LMSTTOLAST 23       /* LMST to LAST */
+#define AST__UTTOUTC    24       /* UT1 to UTC */
+#define AST__UTCTOUT    25       /* UTC to UT1 */
+#define AST__LTTOUTC    26       /* Local Time to UTC */
+#define AST__UTCTOLT    27       /* UTC to Local Time */
+
+/* Maximum number of arguments required by a conversion. */
+#define MAX_ARGS 6
+
+/* The alphabet (used for generating keywords for arguments). */
+#define ALPHABET "abcdefghijklmnopqrstuvwxyz"
+
+/* Angle conversion */
+#define PI 3.1415926535897932384626433832795028841971693993751
+#define D2PI (2*PI)
+#define PIBY2 (PI/2.0)
+#define D2R (PI/180.0)
+#define R2D (180.0/PI)
+
+/* Other constants */
+#define SPD 86400
+#define LG 6.969290134E-10
+#define LB 1.55051976772E-8
+#define P0 6.55E-5
+#define TTOFF 32.184
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "pal.h"              /* SLALIB interface */
+#include "slamap.h"              /* Spatial sla mappings */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+#include "unitmap.h"             /* Unit (null) Mappings */
+#include "timemap.h"             /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double (* parent_rate)( AstMapping *, double *, int, int, int * );
+
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(TimeMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(TimeMap,Class_Init)
+#define class_vtab astGLOBAL(TimeMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstTimeMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstTimeMap *astTimeMapId_( int, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *CvtString( int, const char **, int *, int *, const char *[ MAX_ARGS ], int * );
+static double Gmsta( double, double, int, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static double Rcc( double, double, double, double, double, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int CvtCode( const char *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static void AddArgs( int, double *, int * );
+static void AddTimeCvt( AstTimeMap *, int, int, const double *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void TimeAdd( AstTimeMap *, const char *, int, const double[], int * );
+
+static int GetObjSize( AstObject *, int * );
+/* Member functions. */
+/* ================= */
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two TimeMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     TimeMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two TimeMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a TimeMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the TimeMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstTimeMap *that;
+   AstTimeMap *this;
+   const char *argdesc[ MAX_ARGS ];
+   const char *comment;
+   int i, j;
+   int nargs;
+   int nin;
+   int nout;
+   int result;
+   int szargs;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two TimeMap structures. */
+   this = (AstTimeMap *) this_object;
+   that = (AstTimeMap *) that_object;
+
+/* Check the second object is a TimeMap. We know the first is a
+   TimeMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsATimeMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two TimeMaps differ, it may still be possible
+   for them to be equivalent. First compare the TimeMaps if their Invert
+   flags are the same. In this case all the attributes of the two TimeMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+            if( this->ncvt == that->ncvt ) {
+               result = 1;
+               for( i = 0; i < this->ncvt && result; i++ ) {
+                  if( this->cvttype[ i ] != that->cvttype[ i ] ) {
+                     result = 0;
+                  } else {
+                     CvtString( this->cvttype[ i ], &comment, &nargs, &szargs, argdesc, status );
+                     for( j = 0; j < nargs; j++ ) {
+                        if( !astEQUAL( this->cvtargs[ i ][ j ],
+                                       that->cvtargs[ i ][ j ] ) ){
+                           result = 0;
+                           break;
+                        }
+                     }
+                  }
+               }
+            }
+
+/* If the Invert flags for the two TimeMaps differ, the attributes of the two
+   TimeMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a TimeMap, Invert flags must be equal. */
+            result = 0;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     TimeMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied TimeMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstTimeMap *this;         /* Pointer to TimeMap structure */
+   int result;               /* Result value to return */
+   int cvt;                  /* Loop counter for coordinate conversions */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the TimeMap structure. */
+   this = (AstTimeMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   for ( cvt = 0; cvt < this->ncvt; cvt++ ) {
+      result += astTSizeOf( this->cvtargs[ cvt ] );
+   }
+
+   result += astTSizeOf( this->cvtargs );
+   result += astTSizeOf( this->cvttype );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+
+static void AddArgs( int cvttype, double *cvtargs, int *status ) {
+/*
+*  Name:
+*     AddArgs
+
+*  Purpose:
+*     Set values for addition conversion arguments.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     void AddArgs( int cvttype, double *cvtargs, int *status )
+
+*  Class Membership:
+*     TimeMap member function.
+
+*  Description:
+*     This function stores value for additional conversion arguments,
+*     based on the values supplied for the user arguments.
+
+*  Parameters:
+*     cvttype
+*        The conversion type.
+*     cvtargs
+*        The arguments for the conversion.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   double r;                     /* Distance from Earth axis (AU) */
+   double z;                     /* Distance from plane of Earth equator (AU) */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Test for each valid code value in turn and assign the appropriate
+   extra values. */
+   switch ( cvttype ) {
+
+   case AST__MJDTOMJD:
+      cvtargs[ 2 ] = cvtargs[ 0 ] - cvtargs[ 1 ];
+      break;
+
+   case AST__MJDTOJD:
+      cvtargs[ 2 ] = cvtargs[ 0 ] - cvtargs[ 1 ] + 2400000.5;
+      break;
+
+   case AST__JDTOMJD:
+      cvtargs[ 2 ] = cvtargs[ 0 ] - cvtargs[ 1 ] - 2400000.5;
+      break;
+
+   case AST__MJDTOBEP:
+      cvtargs[ 2 ] = palEpb( cvtargs[ 0 ] ) - palEpb( 0.0 ) - cvtargs[ 1 ];
+      cvtargs[ 3 ] = palEpb2d( cvtargs[ 1 ] ) - palEpb2d( 0.0 ) - cvtargs[ 0 ];
+      break;
+
+   case AST__BEPTOMJD:
+      cvtargs[ 2 ] = palEpb2d( cvtargs[ 0 ] ) - palEpb2d( 0.0 ) - cvtargs[ 1 ];
+      cvtargs[ 3 ] = palEpb( cvtargs[ 1 ] ) - palEpb( 0.0 ) - cvtargs[ 0 ];
+      break;
+
+   case AST__MJDTOJEP:
+      cvtargs[ 2 ] = palEpj( cvtargs[ 0 ] ) - palEpj( 0.0 ) - cvtargs[ 1 ];
+      cvtargs[ 3 ] = palEpj2d( cvtargs[ 1 ] ) - palEpj2d( 0.0 ) - cvtargs[ 0 ];
+      break;
+
+   case AST__JEPTOMJD:
+      cvtargs[ 2 ] = palEpj2d( cvtargs[ 0 ] ) - palEpj2d( 0.0 ) - cvtargs[ 1 ];
+      cvtargs[ 3 ] = palEpj( cvtargs[ 1 ] ) - palEpj( 0.0 ) - cvtargs[ 0 ];
+      break;
+
+   case AST__TTTOTDB:
+      palGeoc( cvtargs[ 2 ], cvtargs[ 3 ], &r, &z );
+      cvtargs[ 4 ] = 0.001*r*AST__AU;
+      cvtargs[ 5 ] = 0.001*z*AST__AU;
+      break;
+
+   case AST__TDBTOTT:
+      palGeoc( cvtargs[ 2 ], cvtargs[ 3 ], &r, &z );
+      cvtargs[ 4 ] = 0.001*r*AST__AU;
+      cvtargs[ 5 ] = 0.001*z*AST__AU;
+      break;
+
+   case AST__TDBTOTCB:
+      cvtargs[ 1 ] = LB*( cvtargs[ 0 ] - (TTOFF/SPD)
+                                        - 43144.0 ) + P0/SPD;
+      break;
+
+   case AST__TCBTOTDB:
+      cvtargs[ 1 ] = LB*( cvtargs[ 0 ] - (TTOFF/SPD)
+                                        - 43144.0 ) + P0/SPD;
+      break;
+
+   case AST__TTTOTCG:
+      cvtargs[ 1 ] = LG*( cvtargs[ 0 ] - (TTOFF/SPD) - 43144.0 );
+      break;
+
+   case AST__TCGTOTT:
+      cvtargs[ 1 ] = LG*( cvtargs[ 0 ] - (TTOFF/SPD) - 43144.0 );
+      break;
+
+   }
+}
+
+static void AddTimeCvt( AstTimeMap *this, int cvttype, int narg,
+                        const double *args, int *status ) {
+/*
+*  Name:
+*     AddTimeCvt
+
+*  Purpose:
+*     Add a coordinate conversion step to an TimeMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     void AddTimeCvt( AstTimeMap *this, int cvttype, int narg, const
+*                      double *args )
+
+*  Class Membership:
+*     TimeMap member function.
+
+*  Description:
+*     This function allows one of the supported time coordinate
+*     conversions to be appended to a TimeMap. When a TimeMap is first
+*     created (using astTimeMap), it simply performs a unit mapping. By
+*     using AddTimeCvt repeatedly, a series of coordinate conversions may
+*     then be specified which the TimeMap will subsequently perform in
+*     sequence. This allows a complex coordinate conversion to be
+*     assembled out of the basic building blocks. The TimeMap will also
+*     perform the inverse coordinate conversion (applying the individual
+*     conversion steps in reverse) if required.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeMap.
+*     cvttype
+*        A code to identify which time coordinate conversion is to be
+*        appended.  See the "Coordinate Conversions" section for details
+*        of those available.
+*     narg
+*        The number of argument values supplied in "args".
+*     args
+*        Pointer to an array of double containing the argument values
+*        required to fully specify the required coordinate
+*        conversion. The number of arguments depends on the conversion
+*        (see the "Coordinate Conversions" section for details). This
+*        value is ignored and may be NULL if no arguments are required.
+
+*  Returned Value:
+*     void.
+
+*  Coordinate Conversions:
+*     The following values may be supplied for the "cvttype" parameter
+*     in order to specify the coordinate conversion to be performed.
+*     The argument(s) required to fully specify each conversion are
+*     indicated in parentheses after each value, and described at the end
+*     of the list. Values for these should be given in the array pointed
+*     at by "args".
+*
+*        AST__MJDTOMJD( MJDOFF1, MJDOFF2 )
+*           Convert Modified Julian Date from one offset to another.
+*        AST__MJDTOJD( MJDOFF, JDOFF )
+*           Convert Modified Julian Date to Julian Date.
+*        AST__JDTOMJD( JDOFF, MJDOFF )
+*           Convert Julian Date to Modified Julian Date.
+*        AST__MJDTOBEP( MJDOFF, BEPOFF )
+*           Convert Modified Julian Date to Besselian epoch.
+*        AST__BEPTOMJD( BEPOFF, MJDOFF )
+*           Convert Besselian epoch to Modified Julian Date.
+*        AST__MJDTOJEP( MJDOFF, JEPOFF )
+*           Convert Modified Julian Date to Julian epoch.
+*        AST__JEPTOMJD( JEPOFF, MJDOFF )
+*           Convert Julian epoch to Modified Julian Date.
+*        AST__TAITOUTC( MJDOFF )
+*           Convert a TAI MJD to a UTC MJD.
+*        AST__UTCTOTAI( MJDOFF )
+*           Convert a UTC MJD to a TAI MJD.
+*        AST__TAITOTT( MJDOFF )
+*           Convert a TAI MJD to a TT MJD.
+*        AST__TTTOTAI( MJDOFF )
+*           Convert a TT MJD to a TAI MJD.
+*        AST__TTTOTDB( MJDOFF, OBSLON, OBSLAT, OBSALT )
+*           Convert a TT MJD to a TDB MJD.
+*        AST__TDBTOTT( MJDOFF, OBSLON, OBSLAT, OBSALT )
+*           Convert a TDB MJD to a TT MJD.
+*        AST__TTTOTCG( MJDOFF )
+*           Convert a TT MJD to a TCG MJD.
+*        AST__TCGTOTT( MJDOFF )
+*           Convert a TCG MJD to a TT MJD.
+*        AST__TDBTOTCB( MJDOFF)
+*           Convert a TAI MJD to a TCB MJD.
+*        AST__TCBTOTDB( MJDOFF)
+*           Convert a TCB MJD to a TDB MJD.
+*        AST__UTTOGMST( MJDOFF )
+*           Convert a UT MJD to a GMST MJD.
+*        AST__GMSTTOUT( MJDOFF )
+*           Convert a GMST MJD to a UT MJD.
+*        AST__GMSTTOLMST( MJDOFF, OBSLON, OBSLAT )
+*           Convert a GMST MJD to a LMST MJD.
+*        AST__LMSTTOGMST( MJDOFF, OBSLON, OBSLAT )
+*           Convert a LMST MJD to a GMST MJD.
+*        AST__LASTTOLMST( MJDOFF, OBSLON, OBSLAT )
+*           Convert a LAST MJD to a LMST MJD.
+*        AST__LMSTTOLAST( MJDOFF, OBSLON, OBSLAT )
+*           Convert a LMST MJD to a LAST MJD.
+*        AST__UTTOUTC( DUT1 )
+*           Convert a UT1 MJD to a UTC MJD.
+*        AST__UTCTOUT( DUT1 )
+*           Convert a UTC MJD to a UT1 MJD.
+*        AST__LTTOUTC( LTOFF )
+*           Convert a local time MJD to a UTC MJD.
+*        AST__UTCTOLT( LTOFF )
+*           Convert a UTC MJD to a local time MJD.
+*
+*     The units for the values processed by the above conversions are as
+*     follows:
+*
+*     - MJD, MJDOFF, JD, JDOFF: days
+*     - Julian epochs, BEPOFF: Tropical years
+*     - Besselian epochs, JEPOFF: Julian years
+*
+*     The arguments used in the above conversions are as follows:
+*
+*     - MJDOFF: Offset to be added to each MJD value
+*     - JDOFF: Offset to be added to each JD value
+*     - JEPOFF: Offset to be added to each Julian epoch value
+*     - BEPOFF: Offset to be added to each Besselian epoch value
+*     - OBSLON: Observer's longitude in radians (+ve westwards)
+*     - OBSLAT: Observer's geodetic latitude in radians (+ve northwards)
+*     - OBSALT: Observer's geodetic altitude in metres.
+*     - DUT1: The value of UT1-UTC
+*     - LTOFF: The offset between Local Time and UTC (in hours, positive
+*     for time zones east of Greenwich).
+
+*  Notes:
+*     - The specified conversion is appended only if the TimeMap's
+*     Invert attribute is zero. If it is non-zero, this function
+*     effectively prefixes the inverse of the conversion specified
+*     instead.
+*/
+
+/* Local Variables: */
+   const char *argdesc[ MAX_ARGS ]; /* Pointers to argument descriptions */
+   const char *comment;          /* Pointer to comment string */
+   const char *cvt_string;       /* Pointer to conversion type string */
+   int i;                        /* Argument index */
+   int nargs;                    /* Number of user-supplied arguments */
+   int ncvt;                     /* Number of coordinate conversions */
+   int szargs;                   /* Size of arguments array */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Validate the coordinate conversion type and obtain the number of
+   required user-supplied arguments, and the size of the array in which
+   to put the user-supplied arguments (the array may leave room after
+   the user-supplied arguments for various useful pre-calculated values). */
+   cvt_string = CvtString( cvttype, &comment, &nargs, &szargs, argdesc, status );
+
+/* If the coordinate conversion type was not valid, then report an
+   error. */
+   if ( astOK && !cvt_string ) {
+      astError( AST__TIMIN, "AddTimeCvt(%s): Invalid time coordinate "
+                "conversion type (%d).", status, astGetClass( this ),
+                (int) cvttype );
+   }
+
+/* If the number of supplied arguments is incorrect, then report an error. */
+   if ( astOK && nargs != narg ) {
+      astError( AST__TIMIN, "AddTimeCvt(%s): Invalid no. of arguments for time "
+                "coordinate conversion type %d - %d supplied, %d required.",
+                status, astGetClass( this ), (int) cvttype, narg, nargs );
+   }
+
+/* Note the number of coordinate conversions already stored in the TimeMap. */
+   if ( astOK ) {
+      ncvt = this->ncvt;
+
+/* Extend the array of conversion types and the array of pointers to
+   their argument lists to accommodate the new one. */
+      this->cvttype = (int *) astGrow( this->cvttype, ncvt + 1,
+                                       sizeof( int ) );
+      this->cvtargs = (double **) astGrow( this->cvtargs, ncvt + 1,
+                                           sizeof( double * ) );
+
+/* Allocate memory for the argument list, putting a pointer to it into
+   the TimeMap. */
+      this->cvtargs[ ncvt ] = astMalloc( sizeof( double ) * (size_t) szargs );
+
+/* Store the conversion type and increment the conversion count. Also
+   copy the supplied arguments into the memory allocated above and put
+   suitable values in any elements of the argument array which are beyond
+   the end of the user-supplied arguments. These are intermediate values
+   calculated on the basis of the user-supplied arguments. */
+      if ( astOK ) {
+         this->cvttype[ ncvt ] = cvttype;
+         for( i = 0; i < nargs; i++ ) this->cvtargs[ ncvt ][ i ] = args[ i ];
+         for( i = nargs; i < szargs; i++ ) this->cvtargs[ ncvt ][ i ] = AST__BAD;
+         this->ncvt++;
+
+/* Test for each valid code value in turn and assign the appropriate extra values. */
+         AddArgs( cvttype, this->cvtargs[ ncvt ], status );
+      }
+   }
+}
+
+static int CvtCode( const char *cvt_string, int *status ) {
+/*
+*  Name:
+*     CvtCode
+
+*  Purpose:
+*     Convert a conversion type from a string representation to a code value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     int CvtCode( const char *cvt_string, int *status )
+
+*  Class Membership:
+*     TimeMap member function.
+
+*  Description:
+*     This function accepts a string used to repersent one of the
+*     TimeMap coordinate conversions and converts it into a code
+*     value for internal use.
+
+*  Parameters:
+*     cvt_string
+*        Pointer to a constant null-terminated string representing a
+*        time coordinate conversion. This is case sensitive and should
+*        contain no unnecessary white space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The equivalent conversion code. If the string was not
+*     recognised, the code AST__TIME_NULL is returned, without error.
+
+*  Notes:
+*     - A value of AST__TIME_NULL will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = AST__TIME_NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Test the string against each recognised value in turn and assign
+   the result. */
+   if ( astChrMatch( cvt_string, "MJDTOJD" ) ) {
+      result = AST__MJDTOJD;
+
+   } else if ( astChrMatch( cvt_string, "MJDTOMJD" ) ) {
+      result = AST__MJDTOMJD;
+
+   } else if ( astChrMatch( cvt_string, "JDTOMJD" ) ) {
+      result = AST__JDTOMJD;
+
+   } else if ( astChrMatch( cvt_string, "JDTOMJD" ) ) {
+      result = AST__JDTOMJD;
+
+   } else if ( astChrMatch( cvt_string, "MJDTOBEP" ) ) {
+      result = AST__MJDTOBEP;
+
+   } else if ( astChrMatch( cvt_string, "BEPTOMJD" ) ) {
+      result = AST__BEPTOMJD;
+
+   } else if ( astChrMatch( cvt_string, "MJDTOJEP" ) ) {
+      result = AST__MJDTOJEP;
+
+   } else if ( astChrMatch( cvt_string, "JEPTOMJD" ) ) {
+      result = AST__JEPTOMJD;
+
+   } else if ( astChrMatch( cvt_string, "TAITOUTC" ) ) {
+      result = AST__TAITOUTC;
+
+   } else if ( astChrMatch( cvt_string, "UTCTOTAI" ) ) {
+      result = AST__UTCTOTAI;
+
+   } else if ( astChrMatch( cvt_string, "TAITOTT" ) ) {
+      result = AST__TAITOTT;
+
+   } else if ( astChrMatch( cvt_string, "TTTOTAI" ) ) {
+      result = AST__TTTOTAI;
+
+   } else if ( astChrMatch( cvt_string, "TTTOTDB" ) ) {
+      result = AST__TTTOTDB;
+
+   } else if ( astChrMatch( cvt_string, "TDBTOTT" ) ) {
+      result = AST__TDBTOTT;
+
+   } else if ( astChrMatch( cvt_string, "TTTOTCG" ) ) {
+      result = AST__TTTOTCG;
+
+   } else if ( astChrMatch( cvt_string, "TCGTOTT" ) ) {
+      result = AST__TCGTOTT;
+
+   } else if ( astChrMatch( cvt_string, "TDBTOTCB" ) ) {
+      result = AST__TDBTOTCB;
+
+   } else if ( astChrMatch( cvt_string, "TCBTOTDB" ) ) {
+      result = AST__TCBTOTDB;
+
+   } else if ( astChrMatch( cvt_string, "UTTOGMST" ) ) {
+      result = AST__UTTOGMST;
+
+   } else if ( astChrMatch( cvt_string, "GMSTTOUT" ) ) {
+      result = AST__GMSTTOUT;
+
+   } else if ( astChrMatch( cvt_string, "GMSTTOLMST" ) ) {
+      result = AST__GMSTTOLMST;
+
+   } else if ( astChrMatch( cvt_string, "LMSTTOGMST" ) ) {
+      result = AST__LMSTTOGMST;
+
+   } else if ( astChrMatch( cvt_string, "LASTTOLMST" ) ) {
+      result = AST__LASTTOLMST;
+
+   } else if ( astChrMatch( cvt_string, "LMSTTOLAST" ) ) {
+      result = AST__LMSTTOLAST;
+
+   } else if ( astChrMatch( cvt_string, "UTTOUTC" ) ) {
+      result = AST__UTTOUTC;
+
+   } else if ( astChrMatch( cvt_string, "UTCTOUT" ) ) {
+      result = AST__UTCTOUT;
+
+   } else if ( astChrMatch( cvt_string, "LTTOUTC" ) ) {
+      result = AST__LTTOUTC;
+
+   } else if ( astChrMatch( cvt_string, "UTCTOLT" ) ) {
+      result = AST__UTCTOLT;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *CvtString( int cvt_code, const char **comment,
+                              int *nargs, int *szargs,
+                              const char *arg[ MAX_ARGS ], int *status ) {
+/*
+*  Name:
+*     CvtString
+
+*  Purpose:
+*     Convert a conversion type from a code value to a string representation.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     const char *CvtString( int cvt_code, const char **comment, int *nargs,
+*                            int *szargs, const char *arg[ MAX_ARGS ], int *status )
+
+*  Class Membership:
+*     TimeMap member function.
+
+*  Description:
+*     This function accepts a code value used to represent one of the
+*     TimeMap coordinate conversions and converts it into an
+*     equivalent string representation. It also returns a descriptive
+*     comment and information about the arguments required in order to
+*     perform the conversion.
+
+*  Parameters:
+*     cvt_code
+*        The conversion code.
+*     comment
+*        Address of a location to return a pointer to a constant
+*        null-terminated string containing a description of the
+*        conversion.
+*     nargs
+*        Address of an int in which to return the number of arguments
+*        required from the user in order to perform the conversion (may
+*        be zero).
+*     szargs
+*        Address of an int in which to return the number of arguments
+*        associated with the conversion. This may be bigger than "nargs"
+*        if the conversion can pre-calculate useful values on the basis
+*        of the user-supplied values. Such precalculated values are
+*        stored after the last user-supplied argument.
+*     arg
+*        An array in which to return a pointer to a constant
+*        null-terminated string for each argument (above) containing a
+*        description of what each argument represents. This includes both
+*        user-supplied arguments and pre-calculated values.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a constant null-terminated string representation of
+*     the conversion code value supplied. If the code supplied is not
+*     valid, a NULL pointer will be returned, without error.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set, or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   const char *result;          /* Result pointer to return */
+
+/* Initialise the returned values. */
+   *comment = NULL;
+   *nargs = 0;
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Test for each valid code value in turn and assign the appropriate
+   return values. */
+   switch ( cvt_code ) {
+
+   case AST__MJDTOMJD:
+      *comment = "Convert MJD between offsets";
+      result = "MJDTOMJD";
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "Input MJD offset";
+      arg[ 1 ] = "Output MJD offset";
+      arg[ 2 ] = "Combined offset";
+      break;
+
+   case AST__MJDTOJD:
+      *comment = "Convert MJD to JD";
+      result = "MJDTOJD";
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "JD offset";
+      arg[ 2 ] = "Combined offset";
+      break;
+
+   case AST__JDTOMJD:
+      *comment = "Convert JD to MJD";
+      result = "JDTOMJD";
+      *nargs = 2;
+      *szargs = 3;
+      arg[ 0 ] = "JD offset";
+      arg[ 1 ] = "MJD offset";
+      arg[ 2 ] = "Combined offset";
+      break;
+
+   case AST__MJDTOBEP:
+      *comment = "Convert MJD to Besselian epoch";
+      result = "MJDTOBEP";
+      *nargs = 2;
+      *szargs = 4;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "Besselian epoch offset";
+      arg[ 2 ] = "Combined forward offset";
+      arg[ 3 ] = "Combined inverse offset";
+      break;
+
+   case AST__BEPTOMJD:
+      *comment = "Convert Besselian epoch to MJD";
+      result = "BEPTOMJD";
+      *nargs = 2;
+      *szargs = 4;
+      arg[ 0 ] = "Besselian epoch offset";
+      arg[ 1 ] = "MJD offset";
+      arg[ 2 ] = "Combined forward offset";
+      arg[ 3 ] = "Combined inverse offset";
+      break;
+
+   case AST__MJDTOJEP:
+      *comment = "Convert MJD to Julian epoch";
+      result = "MJDTOJEP";
+      *nargs = 2;
+      *szargs = 4;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "Julian epoch offset";
+      arg[ 2 ] = "Combined forward offset";
+      arg[ 3 ] = "Combined inverse offset";
+      break;
+
+   case AST__JEPTOMJD:
+      *comment = "Convert Julian epoch to MJD";
+      result = "JEPTOMJD";
+      *nargs = 2;
+      *szargs = 4;
+      arg[ 0 ] = "Julian epoch offset";
+      arg[ 1 ] = "MJD offset";
+      arg[ 2 ] = "Combined forward offset";
+      arg[ 3 ] = "Combined inverse offset";
+      break;
+
+   case AST__TAITOUTC:
+      *comment = "Convert TAI to UTC";
+      result = "TAITOUTC";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "MJD offset";
+      break;
+
+   case AST__UTCTOTAI:
+      *comment = "Convert UTC to TAI";
+      result = "UTCTOTAI";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "MJD offset";
+      break;
+
+   case AST__TAITOTT:
+      *comment = "Convert TAI to TT";
+      result = "TAITOTT";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "MJD offset";
+      break;
+
+   case AST__TTTOTAI:
+      *comment = "Convert TT to TAI";
+      result = "TTTOTAI";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "MJD offset";
+      break;
+
+   case AST__TTTOTDB:
+      *comment = "Convert TT to TDB";
+      result = "TTTOTDB";
+      *nargs = 4;
+      *szargs = 6;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "Observer longitude";
+      arg[ 2 ] = "Observer latitude";
+      arg[ 3 ] = "Observer altitude";
+      arg[ 4 ] = "Distance from earth spin axis";
+      arg[ 5 ] = "Distance north of equatorial plane";
+      break;
+
+   case AST__TDBTOTT:
+      *comment = "Convert TDB to TT";
+      result = "TDBTOTT";
+      *nargs = 4;
+      *szargs = 6;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "Observer longitude";
+      arg[ 2 ] = "Observer latitude";
+      arg[ 3 ] = "Observer altitude";
+      arg[ 4 ] = "Distance from earth spin axis";
+      arg[ 5 ] = "Distance north of equatorial plane";
+      break;
+
+   case AST__TTTOTCG:
+      *comment = "Convert TT to TCG";
+      result = "TTTOTCG";
+      *nargs = 1;
+      *szargs = 2;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "TCG offset";
+      break;
+
+   case AST__TCGTOTT:
+      *comment = "Convert TCG to TT";
+      result = "TCGTOTT";
+      *nargs = 1;
+      *szargs = 2;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "TCG offset";
+      break;
+
+   case AST__TDBTOTCB:
+      *comment = "Convert TDB to TCB";
+      result = "TDBTOTCB";
+      *nargs = 1;
+      *szargs = 2;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "TCB offset";
+      break;
+
+   case AST__TCBTOTDB:
+      *comment = "Convert TCB to TDB";
+      result = "TCBTOTDB";
+      *nargs = 1;
+      *szargs = 2;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "TCB offset";
+      break;
+
+   case AST__UTTOGMST:
+      *comment = "Convert UT to GMST";
+      result = "UTTOGMST";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "MJD offset";
+      break;
+
+   case AST__GMSTTOUT:
+      *comment = "Convert GMST to UT";
+      result = "GMSTTOUT";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "MJD offset";
+      break;
+
+   case AST__GMSTTOLMST:
+      *comment = "Convert GMST to LMST";
+      result = "GMSTTOLMST";
+      *nargs = 3;
+      *szargs = 3;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "Observer longitude";
+      arg[ 2 ] = "Observer latitude";
+      break;
+
+   case AST__LMSTTOGMST:
+      *comment = "Convert LMST to GMST";
+      result = "LMSTTOGMST";
+      *nargs = 3;
+      *szargs = 3;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "Observer longitude";
+      arg[ 2 ] = "Observer latitude";
+      break;
+
+   case AST__LASTTOLMST:
+      *comment = "Convert LAST to LMST";
+      result = "LASTTOLMST";
+      *nargs = 3;
+      *szargs = 3;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "Observer longitude";
+      arg[ 2 ] = "Observer latitude";
+      break;
+
+   case AST__LMSTTOLAST:
+      *comment = "Convert LMST to LAST";
+      result = "LMSTTOLAST";
+      *nargs = 3;
+      *szargs = 3;
+      arg[ 0 ] = "MJD offset";
+      arg[ 1 ] = "Observer longitude";
+      arg[ 2 ] = "Observer latitude";
+      break;
+
+   case AST__UTTOUTC:
+      *comment = "Convert UT1 to UTC";
+      result = "UTTOUTC";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "DUT1";
+      break;
+
+   case AST__UTCTOUT:
+      *comment = "Convert UTC to UT1";
+      result = "UTCTOUT";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "DUT1";
+      break;
+
+   case AST__LTTOUTC:
+      *comment = "Convert Local Time to UTC";
+      result = "LTTOUTC";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "LTOFF";
+      break;
+
+   case AST__UTCTOLT:
+      *comment = "Convert UTC to Local Time";
+      result = "UTCTOLT";
+      *nargs = 1;
+      *szargs = 1;
+      arg[ 0 ] = "LTOFF";
+      break;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+double astDat_( double in, int forward, int *status ){
+/*
+*+
+*  Name:
+*     Dat
+
+*  Purpose:
+*     Convert between UTC and TAI.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     double astDat( double in, int forward )
+
+*  Class Membership:
+*     TimeMap member function
+
+*  Description:
+*     This function returns the difference between Coordinated Universal Time
+*     (UTC) and International Atomic Time (TAI), at a given epoch.
+
+*  Parameters:
+*     in
+*        UTC date or TAI time (as selected by "forward"), as an absolute
+*        MJD.
+*     forward
+*        If non-zero, "in" should be a UTC value, and the returned value
+*        is TAI-UTC. If zero, "in" should be a TAI value, and the returned
+*        value is UTC-TAI.
+
+*  Returned Value:
+*     Either UTC-TAI or TAI-UTC (as indicated by "forward") in units of
+*     seconds.
+
+*  Notes:
+*     - The UTC is specified to be a date rather than a time to indicate
+*     that care needs to be taken not to specify an instant which lies
+*     within a leap second.  Though in most cases UTC can include the
+*     fractional part, correct behaviour on the day of a leap second
+*     can only be guaranteed up to the end of the second 23:59:59.
+*     - For epochs from 1961 January 1 onwards, the expressions from the
+*     file ftp://maia.usno.navy.mil/ser7/tai-utc.dat are used.
+*     - The 5ms time step at 1961 January 1 is taken from 2.58.1 (p87) of
+*     the 1992 Explanatory Supplement.
+*     - UTC began at 1960 January 1.0 (JD 2436934.5) and it is improper
+*     to call the routine with an earlier epoch.  However, if this
+*     is attempted, the TAI-UTC expression for the year 1960 is used.
+
+*  Implementation Details:
+*     - This function is based on SLA_DAT by P.T.Wallace.
+*     - This routine must be updated on each occasion that a leap second is
+*     announced
+*     - Latest leap second:  2017 January 1
+
+*-
+*/
+
+/* Local Variables: */
+   double result;
+
+/* Initialise the returned value. */
+   if( in == AST__BAD ) return AST__BAD;
+
+/* First do TAI-UTC at a given UTC
+   ------------------------------- */
+   if( forward ) {
+
+/* 2017 January 1 */
+      if ( in >= 57754.0 ) {
+         result = 37.0;
+
+/* 2015 July 1 */
+      } else if ( in >= 57204.0 ) {
+         result = 36.0;
+
+/* 2012 July 1 */
+      } else if ( in >= 56109.0 ) {
+         result = 35.0;
+
+/* 2009 January 1 */
+      } else if ( in >= 54832.0 ) {
+         result = 34.0;
+
+/* 2006 January 1 */
+      } else if( in >= 53736.0 ) {
+         result = 33.0;
+
+/* 1999 January 1 */
+      } else if( in >= 51179.0 ){
+         result = 32.0;
+
+/* 1997 July 1 */
+      } else if( in >= 50630.0 ){
+         result = 31.0;
+
+/* 1996 January 1 */
+      } else if( in >= 50083.0 ){
+         result = 30.0;
+
+/* 1994 July 1 */
+      } else if( in >= 49534.0 ){
+         result = 29.0;
+
+/* 1993 July 1 */
+      } else if( in >= 49169.0 ){
+         result = 28.0;
+
+/* 1992 July 1 */
+      } else if( in >= 48804.0 ){
+         result = 27.0;
+
+/* 1991 January 1 */
+      } else if( in >= 48257.0 ){
+         result = 26.0;
+
+/* 1990 January 1 */
+      } else if( in >= 47892.0 ){
+         result = 25.0;
+
+/* 1988 January 1 */
+      } else if( in >= 47161.0 ){
+         result = 24.0;
+
+/* 1985 July 1 */
+      } else if( in >= 46247.0 ){
+         result = 23.0;
+
+/* 1983 July 1 */
+      } else if( in >= 45516.0 ){
+         result = 22.0;
+
+/* 1982 July 1 */
+      } else if( in >= 45151.0 ){
+         result = 21.0;
+
+/* 1981 July 1 */
+      } else if( in >= 44786.0 ){
+         result = 20.0;
+
+/* 1980 January 1 */
+      } else if( in >= 44239.0 ){
+         result = 19.0;
+
+/* 1979 January 1 */
+      } else if( in >= 43874.0 ){
+         result = 18.0;
+
+/* 1978 January 1 */
+      } else if( in >= 43509.0 ){
+         result = 17.0;
+
+/* 1977 January 1 */
+      } else if( in >= 43144.0 ){
+         result = 16.0;
+
+/* 1976 January 1 */
+      } else if( in >= 42778.0 ){
+         result = 15.0;
+
+/* 1975 January 1 */
+      } else if( in >= 42413.0 ){
+         result = 14.0;
+
+/* 1974 January 1 */
+      } else if( in >= 42048.0 ){
+         result = 13.0;
+
+/* 1973 January 1 */
+      } else if( in >= 41683.0 ){
+         result = 12.0;
+
+/* 1972 July 1 */
+      } else if( in >= 41499.0 ){
+         result = 11.0;
+
+/* 1972 January 1 */
+      } else if( in >= 41317.0 ){
+         result = 10.0;
+
+/* 1968 February 1 */
+      } else if( in >= 39887.0 ){
+         result = 4.2131700 + ( in - 39126.0 )*0.002592;
+
+/* 1966 January 1 */
+      } else if( in >= 39126.0 ){
+         result = 4.3131700 + ( in - 39126.0 )*0.002592;
+
+/* 1965 September 1 */
+      } else if( in >= 39004.0 ){
+         result = 3.8401300 + ( in - 38761.0 )*0.001296;
+
+/* 1965 July 1 */
+      } else if( in >= 38942.0 ){
+         result = 3.7401300 + ( in - 38761.0 )*0.001296;
+
+/* 1965 March 1 */
+      } else if( in >= 38820.0 ){
+         result = 3.6401300 + ( in - 38761.0 )*0.001296;
+
+/* 1965 January 1 */
+      } else if( in >= 38761.0 ){
+         result = 3.5401300 + ( in - 38761.0 )*0.001296;
+
+/* 1964 September 1 */
+      } else if( in >= 38639.0 ){
+         result = 3.4401300 + ( in - 38761.0 )*0.001296;
+
+/* 1964 April 1 */
+      } else if( in >= 38486.0 ){
+         result = 3.3401300 + ( in - 38761.0 )*0.001296;
+
+/* 1964 January 1 */
+      } else if( in >= 38395.0 ){
+         result = 3.2401300 + ( in - 38761.0 )*0.001296;
+
+/* 1963 November 1 */
+      } else if( in >= 38334.0 ){
+         result = 1.9458580 + ( in - 37665.0 )*0.0011232;
+
+/* 1962 January 1 */
+      } else if( in >= 37665.0 ){
+         result = 1.8458580 + ( in - 37665.0 )*0.0011232;
+
+/* 1961 August 1 */
+      } else if( in >= 37512.0 ){
+         result = 1.3728180 + ( in - 37300.0 )*0.001296;
+
+/* 1961 January 1 */
+      } else if( in >= 37300.0 ){
+         result = 1.4228180 + ( in - 37300.0 )*0.001296;
+
+/* Before that */
+      } else {
+         result = 1.4178180 + ( in - 37300.0 )*0.001296;
+      }
+
+/* Now do UTC-TAI at a given TAI.
+   ------------------------------  */
+   } else {
+
+
+/* 2017 January 1 */
+      if ( in >= 57754.0 + 37.0/SPD ) {
+         result = -37.0;
+
+/* 2015 July 1 */
+      } else if ( in >= 57204.0 + 36.0/SPD ) {
+         result = -36.0;
+
+/* 2012 July 1 */
+      } else if( in >= 56109.0 + 35.0/SPD ) {
+         result = -35.0;
+
+/* 2009 January 1 */
+      } else if( in >= 54832.0 + 34.0/SPD ) {
+         result = -34.0;
+
+/* 2006 January 1 */
+      } else if( in >= 53736.0 + 33.0/SPD ){
+         result = -33.0;
+
+/* 1999 January 1 */
+      } else if( in >= 51179.0 + 32.0/SPD ){
+         result = -32.0;
+
+/* 1997 July 1 */
+      } else if( in >= 50630.0 + 31.0/SPD ){
+         result = -31.0;
+
+/* 1996 January 1 */
+      } else if( in >= 50083.0 + 30.0/SPD ){
+         result = -30.0;
+
+/* 1994 July 1 */
+      } else if( in >= 49534.0 + 29.0/SPD ){
+         result = -29.0;
+
+/* 1993 July 1 */
+      } else if( in >= 49169.0 + 28.0/SPD ){
+         result = -28.0;
+
+/* 1992 July 1 */
+      } else if( in >= 48804.0 + 27.0/SPD ){
+         result = -27.0;
+
+/* 1991 January 1 */
+      } else if( in >= 48257.0 + 26.0/SPD ){
+         result = -26.0;
+
+/* 1990 January 1 */
+      } else if( in >= 47892.0 + 25.0/SPD ){
+         result = -25.0;
+
+/* 1988 January 1 */
+      } else if( in >= 47161.0 + 24.0/SPD ){
+         result = -24.0;
+
+/* 1985 July 1 */
+      } else if( in >= 46247.0 + 23.0/SPD ){
+         result = -23.0;
+
+/* 1983 July 1 */
+      } else if( in >= 45516.0 + 22.0/SPD ){
+         result = -22.0;
+
+/* 1982 July 1 */
+      } else if( in >= 45151.0 + 21.0/SPD ){
+         result = -21.0;
+
+/* 1981 July 1 */
+      } else if( in >= 44786.0 + 20.0/SPD ){
+         result = -20.0;
+
+/* 1980 January 1 */
+      } else if( in >= 44239.0 + 19.0/SPD ){
+         result = -19.0;
+
+/* 1979 January 1 */
+      } else if( in >= 43874.0 + 18.0/SPD ){
+         result = -18.0;
+
+/* 1978 January 1 */
+      } else if( in >= 43509.0 + 17.0/SPD ){
+         result = -17.0;
+
+/* 1977 January 1 */
+      } else if( in >= 43144.0 + 16.0/SPD ){
+         result = -16.0;
+
+/* 1976 January 1 */
+      } else if( in >= 42778.0 + 15.0/SPD ){
+         result = -15.0;
+
+/* 1975 January 1 */
+      } else if( in >= 42413.0 + 14.0/SPD ){
+         result = -14.0;
+
+/* 1974 January 1 */
+      } else if( in >= 42048.0 + 13.0/SPD ){
+         result = -13.0;
+
+/* 1973 January 1 */
+      } else if( in >= 41683.0 + 12.0/SPD ){
+         result = -12.0;
+
+/* 1972 July 1 */
+      } else if( in >= 41499.0 + 11.0/SPD ){
+         result = -11.0;
+
+/* 1972 January 1 */
+      } else if( in >= 41317.0 + 10.0/SPD ){
+         result = -10.0;
+
+/* 1968 February 1 */
+      } else if( in >= 39887.0 + ( 4.2131700
+                                  + ( 39887.0 - 39126.0 )*0.002592 )/SPD ){
+         result = -( 4.2131700 + ( in - 39126.0 )*0.002592 )/1.02592;
+
+/* 1966 January 1 */
+      } else if( in >= 39126.0 + ( 4.3131700
+                                  + ( 39126.0 - 39126.0 )*0.002592 )/SPD ){
+         result = -( 4.2131700 + ( in - 39126.0 )*0.002592 )/1.02592;
+
+/* 1965 September 1 */
+      } else if( in >= 39004.0 + ( 3.8401300
+                                  + ( 39004.0 - 38761.0 )*0.001296 )/SPD ){
+         result = -( 3.8401300 + ( in - 38761.0 )*0.001296 )/1.001296;
+
+/* 1965 July 1 */
+      } else if( in >= 38942.0 + ( 3.7401300
+                                  + ( 38942.0 - 38761.0 )*0.001296 )/SPD ){
+         result = -( 3.7401300 + ( in - 38761.0 )*0.001296 )/1.01296;
+
+/* 1965 March 1 */
+      } else if( in >= 38820.0 + ( 3.6401300
+                                  + ( 38820.0 - 38761.0 )*0.001296 )/SPD ){
+         result = -( 3.6401300 + ( in - 38761.0 )*0.001296 )/1.001296;
+
+/* 1965 January 1 */
+      } else if( in >= 38761.0 + ( 3.5401300
+                                   + ( 38761.0 - 38761.0 )*0.001296 )/SPD ){
+         result = -( 3.5401300 + ( in - 38761.0 )*0.001296 )/1.001296;
+
+/* 1964 September 1 */
+      } else if( in >= 38639.0 + ( 3.4401300
+                                   + ( 38639.0 - 38761.0 )*0.001296 )/SPD ){
+         result = -( 3.4401300 + ( in - 38761.0 )*0.001296 )/1.001296;
+
+/* 1964 April 1 */
+      } else if( in >= 38486.0 + ( 3.3401300
+                                   + ( 38486.0 - 38761.0 )*0.001296 )/SPD ){
+         result = -( 3.3401300 + ( in - 38761.0 )*0.001296 )/1.001296;
+
+/* 1964 January 1 */
+      } else if( in >= 38395.0 + ( 3.2401300
+                                   + ( 38395.0 - 38761.0 )*0.001296 )/SPD ){
+         result = -( 3.2401300 + ( in - 38761.0 )*0.001296 )/1.001296;
+
+/* 1963 November 1 */
+      } else if( in >= 38334.0 + ( 1.9458580
+                                   + ( 38334.0 - 37665.0 )*0.0011232 )/SPD ){
+         result = -( 1.9458580 + ( in - 37665.0 )*0.0011232 )/1.0011232;
+
+/* 1962 January 1 */
+      } else if( in >= 37665.0 + ( 1.8458580
+                                   + ( 37665.0 - 37665.0 )*0.0011232 )/SPD ){
+         result = -( 1.8458580 + ( in - 37665.0 )*0.0011232 )/1.0011232;
+
+/* 1961 August 1 */
+      } else if( in >= 37512.0 + ( 1.3728180
+                                   + ( 37512.0 - 37300.0 )*0.001296 )/SPD ){
+         result = -( 1.3728180 + ( in - 37300.0 )*0.001296 )/1.001296;
+
+/* 1961 January 1 */
+      } else if( in >= 37300.0 + ( 1.4228180
+                                   + ( 37300.0 - 37300.0 )*0.001296 )/SPD ){
+         result = -( 1.4228180 + ( in - 37300.0 )*0.001296 )/1.001296;
+
+/* Before that */
+      } else {
+         result = -( 1.4178180 + ( in - 37300.0 )*0.001296 )/1.001296;
+      }
+   }
+
+/* Return the result */
+   return result;
+}
+
+static double Gmsta( double in, double off, int forward, int *status ){
+/*
+*  Name:
+*     Gmsta
+
+*  Purpose:
+*     Convert between  Universal Time (UT) and Greenwich Mean Sidereal Time (GMST).
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     double Gmsta( double in, double off, int forward, int *status ){
+
+*  Class Membership:
+*     TimeMap member function
+
+*  Description:
+*     This functions converts between UT and GMST. Both timescales are
+*     represented by an MJD, rather than as an angle (as is done by SLALIB)
+*     in order to facilitate conversions from GMST to UT1. This means
+*     that whole days are retained.
+
+*  Parameters:
+*     in
+*        The time to convert, represented as an offset in days from the MJD
+*        zero-point specified by "off". The time is either UT1 or GMST, as
+*        selected by "forward").
+*     off
+*        The MJD value corresponding to a value of 0.0 for "in".
+*     forward
+*        If non-zero, "in" should be a UT1 value, and the returned value
+*        is GMST. If zero, "in" should be a GMST value, and the returned
+*        value is UT1.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     An offset in days from the MJD given by "off". When the returned
+*     value is added to "off" the sum is a GMST MJD (if "forward" is
+*     non-zero), or a UT1 MJD (if "forward" is zero),
+
+*  Notes:
+*     - This function is based on SLA_GMST by P.T.Wallace.
+
+*/
+
+/* Local Variables: */
+   double dgdu;
+   double g;
+   double result;
+   double t;
+   double utl;
+   int nit;
+
+/* Initialise the returned value. */
+   if( in == AST__BAD || off == AST__BAD ) return AST__BAD;
+
+/* First deal with UT1 -> GMST
+   --------------------------- */
+   if( forward ) {
+
+/* Julian centuries since J2000. */
+      t = ( off + in - 51544.5 )/36525.0;
+
+/* GMST at this UT1. */
+      result = in + ( 24110.54841 + ( 8640184.812866 + ( 0.093104 -
+                      6.2E-6*t )*t )*t )/86400.0;
+
+/* Now deal with GMST -> UT1
+   ----------------------- */
+   } else {
+
+/* Form an initial guess at the UT1 value using the inverse of a linear
+   approximation to the UT1->GMST equation. */
+      result = 0.996997348638869*in + 154.49194372222 - 0.00300265136113098*off;
+
+/* Loop round improving the guess, until the guess stops changing, or 10
+   iterations have been performed. */
+      utl = AST__BAD;
+      nit = 0;
+      while( result != utl && nit++ < 10 ){
+
+/* Calculate the GMST at the current UT1 guess. */
+         t = ( off + result - 51544.5 )/36525.0;
+         g = result + ( 24110.54841 + ( 8640184.812866 + ( 0.093104 -
+                      6.2E-6*t )*t )*t )/86400.0;
+
+/* Calculate the rate of change of GMST with respect to UT1 at the current
+   UT1 guess. */
+         dgdu = 1.0 + ( 8640184.812866 +
+                  ( 0.186208 - 12.4E-6*t )*t)/(36525.0*86400.0);
+
+/* Improve the UT1 guess. */
+         utl = result;
+         result = result - ( g - in )/dgdu;
+      }
+   }
+
+/* Return the result */
+   return result;
+}
+
+void astInitTimeMapVtab_(  AstTimeMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitTimeMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a TimeMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     void astInitTimeMapVtab( AstTimeMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     TimeMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the TimeMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsATimeMap) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+   vtab->TimeAdd = TimeAdd;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_rate = mapping->Rate;
+   mapping->Rate = Rate;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the copy constructor, destructor and class dump
+   function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "TimeMap",
+               "Conversion between time coordinate systems" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a TimeMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     TimeMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated TimeMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated TimeMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated TimeMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the TimeMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        TimeMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated TimeMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *new;              /* Pointer to replacement Mapping */
+   AstTimeMap *timemap;            /* Pointer to TimeMap */
+   const char *argdesc[ MAX_ARGS ]; /* Argument descriptions (junk) */
+   const char *class;            /* Pointer to Mapping class string */
+   const char *comment;          /* Pointer to comment string (junk) */
+   double (*cvtargs)[ MAX_ARGS ]; /* Pointer to argument arrays */
+   double tmp;                   /* Temporary storage */
+   int *cvttype;                 /* Pointer to transformation type codes */
+   int *narg;                    /* Pointer to argument count */
+   int *szarg;                   /* Pointer to argument array size */
+   int done;                     /* Finished (no further simplification)? */
+   int iarg;                     /* Loop counter for arguments */
+   int icvt1;                    /* Loop initial value */
+   int icvt2;                    /* Loop final value */
+   int icvt;                     /* Loop counter for transformation steps */
+   int ikeep;                    /* Index to store step being kept */
+   int imap1;                    /* Index of first TimeMap to merge */
+   int imap2;                    /* Index of last TimeMap to merge */
+   int imap;                     /* Loop counter for Mappings */
+   int inc;                      /* Increment for transformation step loop */
+   int invert;                   /* TimeMap applied in inverse direction? */
+   int istep;                    /* Loop counter for transformation steps */
+   int keep;                     /* Keep transformation step? */
+   int ngone;                    /* Number of Mappings eliminated */
+   int nstep0;                   /* Original number of transformation steps */
+   int nstep;                    /* Total number of transformation steps */
+   int result;                   /* Result value to return */
+   int simpler;                  /* Simplification possible? */
+   int unit;                     /* Replacement Mapping is a UnitMap? */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* TimeMaps can only be merged if they are in series (or if there is
+   only one Mapping present, in which case it makes no difference), so
+   do nothing if they are not. */
+   if ( series || ( *nmap == 1 ) ) {
+
+/* Initialise the number of transformation steps to be merged to equal
+   the number in the nominated TimeMap. */
+      nstep = ( (AstTimeMap *) ( *map_list )[ where ] )->ncvt;
+
+/* Search adjacent lower-numbered Mappings until one is found which is
+   not a TimeMap. Accumulate the number of transformation steps involved in
+   any TimeMaps found. */
+      imap1 = where;
+      while ( ( imap1 - 1 >= 0 ) && astOK ) {
+         class = astGetClass( ( *map_list )[ imap1 - 1 ] );
+         if ( !astOK || strcmp( class, "TimeMap" ) ) break;
+         nstep += ( (AstTimeMap *) ( *map_list )[ imap1 - 1 ] )->ncvt;
+         imap1--;
+      }
+
+/* Similarly search adjacent higher-numbered Mappings. */
+      imap2 = where;
+      while ( ( imap2 + 1 < *nmap ) && astOK ) {
+         class = astGetClass( ( *map_list )[ imap2 + 1 ] );
+         if ( !astOK || strcmp( class, "TimeMap" ) ) break;
+         nstep += ( (AstTimeMap *) ( *map_list )[ imap2 + 1 ] )->ncvt;
+         imap2++;
+      }
+
+/* Remember the initial number of transformation steps. */
+      nstep0 = nstep;
+
+/* Allocate memory for accumulating a list of all the transformation
+   steps involved in all the TimeMaps found. */
+      cvttype = astMalloc( sizeof( int ) * (size_t) nstep );
+      cvtargs = astMalloc( sizeof( double[ MAX_ARGS ] ) * (size_t) nstep );
+      szarg = astMalloc( sizeof( int ) * (size_t) nstep );
+      narg = astMalloc( sizeof( int ) * (size_t) nstep );
+
+/* Loop to obtain the transformation data for each TimeMap being merged. */
+      nstep = 0;
+      for ( imap = imap1; astOK && ( imap <= imap2 ); imap++ ) {
+
+/* Obtain a pointer to the TimeMap and note if it is being applied in
+   its inverse direction. */
+         timemap = (AstTimeMap *) ( *map_list )[ imap ];
+         invert = ( *invert_list )[ imap ];
+
+/* Set up loop limits and an increment to scan the transformation
+   steps in each TimeMap in either the forward or reverse direction, as
+   dictated by the associated "invert" value. */
+         icvt1 = invert ? timemap->ncvt - 1 : 0;
+         icvt2 = invert ? -1 : timemap->ncvt;
+         inc = invert ? -1 : 1;
+
+/* Loop through each transformation step in the TimeMap. */
+         for ( icvt = icvt1; icvt != icvt2; icvt += inc ) {
+
+/* Store the transformation type code and use "CvtString" to determine
+   the associated number of arguments. Then store these arguments. */
+            cvttype[ nstep ] = timemap->cvttype[ icvt ];
+            (void) CvtString( cvttype[ nstep ], &comment,
+                              narg + nstep, szarg + nstep, argdesc, status );
+            if ( !astOK ) break;
+            for ( iarg = 0; iarg < szarg[ nstep ]; iarg++ ) {
+               cvtargs[ nstep ][ iarg ] = timemap->cvtargs[ icvt ][ iarg ];
+            }
+
+/* If the TimeMap is inverted, we must not only accumulate its
+   transformation steps in reverse, but also apply them in
+   reverse. For some steps this means changing arguments, for some it
+   means changing the transformation type code to a complementary
+   value, and for others it means both.  Define macros to perform each
+   of the required changes. */
+
+/* Macro to exchange a transformation type code for its inverse (and
+   vice versa). */
+#define SWAP_CODES( code1, code2 ) \
+            if ( cvttype[ nstep ] == code1 ) { \
+               cvttype[ nstep ] = code2; \
+               AddArgs(  code2, cvtargs[ nstep ], status ); \
+            } else if ( cvttype[ nstep ] == code2 ) { \
+               cvttype[ nstep ] = code1; \
+               AddArgs(  code1, cvtargs[ nstep ], status ); \
+            }
+
+/* Macro to exchange a transformation type code for its inverse (and
+   vice versa), and swap the order of its 2 arguments. */
+#define SWAP_CODES2( code1, code2 ) \
+            if ( cvttype[ nstep ] == code1 ) { \
+               cvttype[ nstep ] = code2; \
+               tmp = cvtargs[ nstep ][ 0 ]; \
+               cvtargs[ nstep ][ 0 ] = cvtargs[ nstep ][ 1 ]; \
+               cvtargs[ nstep ][ 1 ] = tmp; \
+               AddArgs(  cvttype[ nstep ], cvtargs[ nstep ], status ); \
+            } else if ( cvttype[ nstep ] == code2 ) { \
+               cvttype[ nstep ] = code1; \
+               tmp = cvtargs[ nstep ][ 0 ]; \
+               cvtargs[ nstep ][ 0 ] = cvtargs[ nstep ][ 1 ]; \
+               cvtargs[ nstep ][ 1 ] = tmp; \
+               AddArgs(  cvttype[ nstep ], cvtargs[ nstep ], status ); \
+            }
+
+/* Use these macros to apply the changes where needed. */
+            if ( invert ) {
+
+/* Exchange transformation codes for their inverses. */
+               SWAP_CODES( AST__TAITOUTC, AST__UTCTOTAI )
+               SWAP_CODES( AST__TAITOTT, AST__TTTOTAI )
+               SWAP_CODES( AST__TTTOTDB, AST__TDBTOTT )
+               SWAP_CODES( AST__TDBTOTCB, AST__TCBTOTDB )
+               SWAP_CODES( AST__TTTOTCG, AST__TCGTOTT )
+               SWAP_CODES( AST__UTTOGMST, AST__GMSTTOUT )
+               SWAP_CODES( AST__GMSTTOLMST, AST__LMSTTOGMST )
+               SWAP_CODES( AST__LASTTOLMST, AST__LMSTTOLAST )
+               SWAP_CODES( AST__UTTOUTC, AST__UTCTOUT )
+               SWAP_CODES( AST__LTTOUTC, AST__UTCTOLT )
+
+/* Exchange transformation codes for their inverses, and swap the offset
+   values. */
+               SWAP_CODES2( AST__MJDTOMJD, AST__MJDTOMJD )
+               SWAP_CODES2( AST__MJDTOJD, AST__JDTOMJD )
+               SWAP_CODES2( AST__MJDTOBEP, AST__BEPTOMJD )
+               SWAP_CODES2( AST__MJDTOJEP, AST__JEPTOMJD )
+
+            }
+
+/* Undefine the local macros. */
+#undef SWAP_CODES
+#undef SWAP_CODES2
+
+/* Count the transformation steps. */
+            nstep++;
+         }
+      }
+
+/* Loop to simplify the sequence of transformation steps until no
+   further improvement is possible. */
+      done = 0;
+      while ( astOK && !done ) {
+
+/* Examine each remaining transformation step in turn.  */
+         ikeep = -1;
+         for ( istep = 0; istep < nstep; istep++ ) {
+
+/* Initially assume we will retain the current step. */
+            keep = 1;
+
+/* We can eliminate changes of system which have no effect. */
+            if( ( cvttype[ istep ] == AST__MJDTOMJD ||
+                  cvttype[ istep ] == AST__MJDTOJD ||
+                  cvttype[ istep ] == AST__JDTOMJD ) &&
+                cvtargs[ istep ][ 2 ] == 0.0 ) {
+               keep = 0;
+
+/* The only simplifications for the conversions currently in this class act
+   to combine adjacent transformation steps, so only apply them while there
+   are at least 2 steps left. */
+            } else if ( istep < ( nstep - 1 ) ) {
+
+/* Define a macro to test if two adjacent transformation type codes
+   have specified values. */
+#define PAIR_CVT( code1, code2 ) \
+               ( ( cvttype[ istep ] == code1 ) && \
+                 ( cvttype[ istep + 1 ] == code2 ) )
+
+/* Define a macro to test if two adjacent transformation type codes
+   have specified values, either way round. */
+#define PAIR_CVT2( code1, code2 ) \
+               ( ( PAIR_CVT( code1, code2 ) ) || \
+                 ( PAIR_CVT( code2, code1 ) ) )
+
+/* If a correction is followed by its inverse, and the user-supplied argument
+   values are unchanged (we do not need to test values stored in the
+   argument array which were not supplied by the user), we can eliminate them.
+   First check for conversions which have a single user-supplied argument. */
+               if( ( PAIR_CVT2( AST__TAITOUTC, AST__UTCTOTAI ) ||
+                     PAIR_CVT2( AST__TAITOTT, AST__TTTOTAI ) ||
+                     PAIR_CVT2( AST__UTTOGMST, AST__GMSTTOUT ) ||
+                     PAIR_CVT2( AST__TTTOTCG, AST__TCGTOTT ) ||
+                     PAIR_CVT2( AST__TTTOTCG, AST__TCGTOTT ) ||
+                     PAIR_CVT2( AST__UTTOUTC, AST__UTCTOUT ) ||
+                     PAIR_CVT2( AST__LTTOUTC, AST__UTCTOLT ) ) &&
+                          astEQUAL( cvtargs[ istep ][ 0 ],
+                                 cvtargs[ istep + 1 ][ 0 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Now check for conversions which have two user-supplied arguments
+   (test they are swapped). */
+               } else if( ( PAIR_CVT2( AST__MJDTOJD, AST__JDTOMJD ) ||
+                            PAIR_CVT2( AST__MJDTOMJD, AST__MJDTOMJD ) ||
+                            PAIR_CVT2( AST__MJDTOBEP, AST__BEPTOMJD ) ||
+                            PAIR_CVT2( AST__MJDTOJEP, AST__JEPTOMJD ) ) &&
+                          astEQUAL( cvtargs[ istep ][ 0 ],
+                                 cvtargs[ istep + 1 ][ 1 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 1 ],
+                                 cvtargs[ istep + 1 ][ 0 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Now check for conversions which have three user-supplied arguments. */
+               } else if( ( PAIR_CVT2( AST__TDBTOTCB, AST__TCBTOTDB ) ||
+                            PAIR_CVT2( AST__GMSTTOLMST, AST__LMSTTOGMST ) ||
+                            PAIR_CVT2( AST__LASTTOLMST, AST__LMSTTOLAST ) ) &&
+                          astEQUAL( cvtargs[ istep ][ 0 ],
+                                 cvtargs[ istep + 1 ][ 0 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 1 ],
+                                 cvtargs[ istep + 1 ][ 1 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 2 ],
+                                 cvtargs[ istep + 1 ][ 2 ] ) ) {
+                  istep++;
+                  keep = 0;
+
+/* Now check for conversions which have four user-supplied arguments. */
+               } else if( ( PAIR_CVT2( AST__TTTOTDB, AST__TDBTOTT ) ) &&
+                          astEQUAL( cvtargs[ istep ][ 0 ],
+                                 cvtargs[ istep + 1 ][ 0 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 1 ],
+                                 cvtargs[ istep + 1 ][ 1 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 2 ],
+                                 cvtargs[ istep + 1 ][ 2 ] ) &&
+                          astEQUAL( cvtargs[ istep ][ 3 ],
+                                 cvtargs[ istep + 1 ][ 3 ] ) ) {
+                  istep++;
+                  keep = 0;
+               }
+
+/* Undefine the local macros. */
+#undef PAIR_CVT
+#undef PAIR_CVT2
+            }
+
+/* If the current transformation (possibly modified above) is being
+   kept, then increment the index that identifies its new location in
+   the list of transformation steps. */
+            if ( keep ) {
+               ikeep++;
+
+/* If the new location is different to its current location, copy the
+   transformation data into the new location. */
+               if ( ikeep != istep ) {
+                  cvttype[ ikeep ] = cvttype[ istep ];
+                  for ( iarg = 0; iarg < szarg[ istep ]; iarg++ ) {
+                     cvtargs[ ikeep ][ iarg ] = cvtargs[ istep ][ iarg ];
+                  }
+                  szarg[ ikeep ] = szarg[ istep ];
+                  narg[ ikeep ] = narg[ istep ];
+               }
+            }
+         }
+
+/* Note if no simplification was achieved on this iteration (i.e. the
+   number of transformation steps was not reduced). This is the signal
+   to quit. */
+         done = ( ( ikeep + 1 ) >= nstep );
+
+/* Note how many transformation steps now remain. */
+         nstep = ikeep + 1;
+      }
+
+/* Determine how many Mappings can be eliminated by condensing all
+   those considered above into a single Mapping. */
+      if ( astOK ) {
+         ngone = imap2 - imap1;
+
+/* Determine if the replacement Mapping can be a UnitMap (a null
+   Mapping). This will only be the case if all the transformation
+   steps were eliminated above. */
+         unit = ( nstep == 0 );
+
+/* Determine if simplification is possible. This will be the case if
+   (a) Mappings were eliminated ("ngone" is non-zero), or (b) the
+   number of transformation steps was reduced, or (c) the TimeMap(s)
+   can be replaced by a UnitMap, or (d) if there was initially only
+   one TimeMap present, its invert flag was set (this flag will always
+   be cleared in the replacement Mapping). */
+         simpler = ngone || ( nstep < nstep0 ) || unit ||
+                   ( *invert_list )[ where ];
+
+/* Do nothing more unless simplification is possible. */
+         if ( simpler ) {
+
+/* If the replacement Mapping is a UnitMap, then create it. */
+            if ( unit ) {
+               new = (AstMapping *)
+                        astUnitMap( astGetNin( ( *map_list )[ where ] ), "", status );
+
+/* Otherwise, create a replacement TimeMap and add each of the
+   remaining transformation steps to it. */
+            } else {
+               new = (AstMapping *) astTimeMap( 0, "", status );
+               for ( istep = 0; istep < nstep; istep++ ) {
+                  AddTimeCvt( (AstTimeMap *) new, cvttype[ istep ],
+                             narg[ istep ], cvtargs[ istep ], status );
+               }
+            }
+
+/* Annul the pointers to the Mappings being eliminated. */
+            if ( astOK ) {
+               for ( imap = imap1; imap <= imap2; imap++ ) {
+                  ( *map_list )[ imap ] = astAnnul( ( *map_list )[ imap ] );
+               }
+
+/* Insert the pointer and invert value for the new Mapping. */
+               ( *map_list )[ imap1 ] = new;
+               ( *invert_list )[ imap1 ] = 0;
+
+/* Move any subsequent Mapping information down to close the gap. */
+               for ( imap = imap2 + 1; imap < *nmap; imap++ ) {
+                  ( *map_list )[ imap - ngone ] = ( *map_list )[ imap ];
+                  ( *invert_list )[ imap - ngone ] = ( *invert_list )[ imap ];
+               }
+
+/* Blank out any information remaining at the end of the arrays. */
+               for ( imap = ( *nmap - ngone ); imap < *nmap; imap++ ) {
+                  ( *map_list )[ imap ] = NULL;
+                  ( *invert_list )[ imap ] = 0;
+               }
+
+/* Decrement the Mapping count and return the index of the first
+   Mapping which was eliminated. */
+               ( *nmap ) -= ngone;
+               result = imap1;
+
+/* If an error occurred, annul the new Mapping pointer. */
+            } else {
+               new = astAnnul( new );
+            }
+         }
+      }
+
+/* Free the memory used for the transformation steps. */
+      cvttype = astFree( cvttype );
+      cvtargs = astFree( cvtargs );
+      szarg = astFree( szarg );
+      narg = astFree( narg );
+   }
+
+/* If an error occurred, clear the returned value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     TimeMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*  Implementation Deficiencies:
+*     The initial version of this implementation only deals with
+*     frequency->wavelength conversions. This is because the slowness of
+*     the numerical differentiation implemented by the astRate method in
+*     the parent Mapping class is cripples conversion between SpecFluxFrames.
+*     Such conversions only rely on rate of change of wavelength with
+*     respect to frequency. This implementation should be extended when
+*     needed.
+
+*/
+
+/* Local Variables: */
+   AstTimeMap *map;
+   double result;
+   int cvt;
+   int i;
+
+/* Check inherited status */
+   if( !astOK ) return AST__BAD;
+
+/* Get a pointer to the TimeMap structure. */
+   map = (AstTimeMap *) this;
+
+/* Initialise the returned value. */
+   result = 1.0;
+
+/* Loop round each conversion. */
+   for( i = 0; i < map->ncvt; i++ ) {
+
+/* Store the type of the current conversion.*/
+      cvt = map->cvttype[ i ];
+
+/* Many of the time conversions are linear. If this is the case, multiply
+   the total rate of change by the rate of change for this step. */
+      if( cvt == AST__MJDTOBEP ) {
+         result *= 1.0/365.242198781;
+
+      } else if( cvt == AST__BEPTOMJD ) {
+         result *= 365.242198781;
+
+      } else if( cvt == AST__MJDTOJEP ) {
+         result *= 1.0/365.25;
+
+      } else if( cvt == AST__JEPTOMJD ) {
+         result *= 365.25;
+
+/* The GMST scales is not linear, so break if we encounter it, and use the
+   (numerical) parent astRate method. The other time scale conversions are
+   assumed to have a slope of unity. In fact the slope will be ever so
+   slightly different to unity. */
+      } else if( cvt == AST__UTTOGMST || cvt == AST__GMSTTOUT ) {
+         result = AST__BAD;
+         break;
+      }
+   }
+
+/* If this is non-linear TimeMap, use the astRate method inherited from the
+   parent Mapping class. */
+   if( result == AST__BAD ) result = (*parent_rate)( this, at, ax1, ax2, status );
+
+/* Return the result. */
+   return result;
+}
+
+static double Rcc( double tdb, double ut1, double wl, double u, double v, int *status ){
+/*
+*  Name:
+*     Rcc
+
+*  Purpose:
+*     Find difference between TDB and TT.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     double Rcc( double tdb, double ut1, double wl, double u, double v, int *status )
+
+*  Class Membership:
+*     TimeMap member function
+
+*  Description:
+*     Relativistic clock correction:  the difference between proper time at
+*     a point on the surface of the Earth and coordinate time in the Solar
+*     System barycentric space-time frame of reference.
+*
+*     The proper time is terrestrial time, TT;  the coordinate time is an
+*     implementation of barycentric dynamical time, TDB.
+
+*  Parameters:
+*     tdb
+*        TDB as an MJD.
+*     ut1
+*        Universal time (only the fraction of the day is relevant)
+*     wl
+*        Observer longitude (radians west)
+*     u
+*        Observer distance from Earth spin axis (km)
+*     v
+*        Observer distance north of Earth equatorial plane (km)
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The clock correction, TDB-TT, in seconds. TDB is coordinate time in the
+*     solar system barycentre frame of reference, in units chosen to eliminate
+*     the scale difference with respect to terrestrial time. TT is the proper
+*     time for clocks at mean sea level on the Earth.
+
+*  Notes:
+*     - This function is a translation of the fortran routine SLA_RCC
+*     written by by P.T.Wallace.
+*
+*     - The argument TDB is, strictly, the barycentric coordinate time;
+*     however, the terrestrial time TT can in practice be used without
+*     any significant loss of accuracy.
+*
+*     - The result returned by Rcc comprises a main (annual)
+*     sinusoidal term of amplitude approximately 0.00166 seconds, plus
+*     planetary and lunar terms up to about 20 microseconds, and diurnal
+*     terms up to 2 microseconds.  The variation arises from the
+*     transverse Doppler effect and the gravitational red-shift as the
+*     observer varies in speed and moves through different gravitational
+*     potentials.
+*
+*     - The geocentric model is that of Fairhead & Bretagnon (1990), in
+*     its full form.  It was supplied by Fairhead (private
+*     communication) as a FORTRAN subroutine.  The original Fairhead
+*     routine used explicit formulae, in such large numbers that
+*     problems were experienced with certain compilers (Microsoft
+*     Fortran on PC aborted with stack overflow, Convex compiled
+*     successfully but extremely slowly).  The present implementation is
+*     a complete recoding, with the original Fairhead coefficients held
+*     in a table.  To optimise arithmetic precision, the terms are
+*     accumulated in reverse order, smallest first.  A number of other
+*     coding changes were made, in order to match the calling sequence
+*     of previous versions of the present routine, and to comply with
+*     Starlink programming standards.  The numerical results compared
+*     with those from the Fairhead form are essentially unaffected by
+*     the changes, the differences being at the 10^-20 sec level.
+*
+*     - The topocentric part of the model is from Moyer (1981) and
+*     Murray (1983).  It is an approximation to the expression
+*     ( v / c ) . ( r / c ), where v is the barycentric velocity of
+*     the Earth, r is the geocentric position of the observer and
+*     c is the speed of light.
+*
+*     - During the interval 1950-2050, the absolute accuracy of is better
+*     than +/- 3 nanoseconds relative to direct numerical integrations
+*     using the JPL DE200/LE200 solar system ephemeris.
+*
+*     - The IAU definition of TDB was that it must differ from TT only by
+*     periodic terms.  Though practical, this is an imprecise definition
+*     which ignores the existence of very long-period and secular
+*     effects in the dynamics of the solar system.  As a consequence,
+*     different implementations of TDB will, in general, differ in zero-
+*     point and will drift linearly relative to one other.
+*
+*     - TDB was, in principle, superseded by new coordinate timescales
+*     which the IAU introduced in 1991:  geocentric coordinate time,
+*     TCG, and barycentric coordinate time, TCB.  However, Rcc
+*     can be used to implement the periodic part of TCB-TCG.
+
+*  References:
+*     - Fairhead, L., & Bretagnon, P., Astron.Astrophys., 229, 240-247
+*     (1990).
+*
+*     - Moyer, T.D., Cel.Mech., 23, 33 (1981).
+*
+*     - Murray, C.A., Vectorial Astrometry, Adam Hilger (1983).
+*
+*     - Seidelmann, P.K. et al, Explanatory Supplement to the
+*     Astronomical Almanac, Chapter 2, University Science Books
+*     (1992).
+*
+*     - Simon J.L., Bretagnon P., Chapront J., Chapront-Touze M.,
+*     Francou G. & Laskar J., Astron.Astrophys., 282, 663-683 (1994).
+*/
+
+
+
+
+
+/* -----------------------------------------------------------------------
+*
+*  Fairhead and Bretagnon canonical coefficients
+*
+*  787 sets of three coefficients.
+*
+*  Each set is amplitude (microseconds)
+*              frequency (radians per Julian millennium since J2000),
+*              phase (radians).
+*
+*  Sets   0-473 are the T**0 terms,
+*   "   474-678  "   "  T**1   "
+*   "   679-763  "   "  T**2   "
+*   "   764-783  "   "  T**3   "
+*   "   784-786  "   "  T**4   "  .
+*/
+   static double fairhd[ 787 ][ 3 ] = {
+
+     { 1656.674564E-6,    6283.075849991, 6.240054195},
+     {   22.417471E-6,    5753.384884897, 4.296977442},
+     {   13.839792E-6,   12566.151699983, 6.196904410},
+     {    4.770086E-6,     529.690965095, 0.444401603},
+     {    4.676740E-6,    6069.776754553, 4.021195093},
+     {    2.256707E-6,     213.299095438, 5.543113262},
+     {    1.694205E-6,      -3.523118349, 5.025132748},
+     {    1.554905E-6,   77713.771467920, 5.198467090},
+     {    1.276839E-6,    7860.419392439, 5.988822341},
+     {    1.193379E-6,    5223.693919802, 3.649823730},
+     {    1.115322E-6,    3930.209696220, 1.422745069},
+     {    0.794185E-6,   11506.769769794, 2.322313077},
+     {    0.447061E-6,      26.298319800, 3.615796498},
+     {    0.435206E-6,    -398.149003408, 4.349338347},
+     {    0.600309E-6,    1577.343542448, 2.678271909},
+     {    0.496817E-6,    6208.294251424, 5.696701824},
+     {    0.486306E-6,    5884.926846583, 0.520007179},
+     {    0.432392E-6,      74.781598567, 2.435898309},
+     {    0.468597E-6,    6244.942814354, 5.866398759},
+     {    0.375510E-6,    5507.553238667, 4.103476804},
+     {    0.243085E-6,    -775.522611324, 3.651837925},
+     {    0.173435E-6,   18849.227549974, 6.153743485},
+     {    0.230685E-6,    5856.477659115, 4.773852582},
+     {    0.203747E-6,   12036.460734888, 4.333987818},
+     {    0.143935E-6,    -796.298006816, 5.957517795},
+     {    0.159080E-6,   10977.078804699, 1.890075226},
+     {    0.119979E-6,      38.133035638, 4.551585768},
+     {    0.118971E-6,    5486.777843175, 1.914547226},
+     {    0.116120E-6,    1059.381930189, 0.873504123},
+     {    0.137927E-6,   11790.629088659, 1.135934669},
+     {    0.098358E-6,    2544.314419883, 0.092793886},
+     {    0.101868E-6,   -5573.142801634, 5.984503847},
+     {    0.080164E-6,     206.185548437, 2.095377709},
+     {    0.079645E-6,    4694.002954708, 2.949233637},
+     {    0.062617E-6,      20.775395492, 2.654394814},
+     {    0.075019E-6,    2942.463423292, 4.980931759},
+     {    0.064397E-6,    5746.271337896, 1.280308748},
+     {    0.063814E-6,    5760.498431898, 4.167901731},
+     {    0.048042E-6,    2146.165416475, 1.495846011},
+     {    0.048373E-6,     155.420399434, 2.251573730},
+     {    0.058844E-6,     426.598190876, 4.839650148},
+     {    0.046551E-6,      -0.980321068, 0.921573539},
+     {    0.054139E-6,   17260.154654690, 3.411091093},
+     {    0.042411E-6,    6275.962302991, 2.869567043},
+     {    0.040184E-6,      -7.113547001, 3.565975565},
+     {    0.036564E-6,    5088.628839767, 3.324679049},
+     {    0.040759E-6,   12352.852604545, 3.981496998},
+     {    0.036507E-6,     801.820931124, 6.248866009},
+     {    0.036955E-6,    3154.687084896, 5.071801441},
+     {    0.042732E-6,     632.783739313, 5.720622217},
+     {    0.042560E-6,  161000.685737473, 1.270837679},
+     {    0.040480E-6,   15720.838784878, 2.546610123},
+     {    0.028244E-6,   -6286.598968340, 5.069663519},
+     {    0.033477E-6,    6062.663207553, 4.144987272},
+     {    0.034867E-6,     522.577418094, 5.210064075},
+     {    0.032438E-6,    6076.890301554, 0.749317412},
+     {    0.030215E-6,    7084.896781115, 3.389610345},
+     {    0.029247E-6,  -71430.695617928, 4.183178762},
+     {    0.033529E-6,    9437.762934887, 2.404714239},
+     {    0.032423E-6,    8827.390269875, 5.541473556},
+     {    0.027567E-6,    6279.552731642, 5.040846034},
+     {    0.029862E-6,   12139.553509107, 1.770181024},
+     {    0.022509E-6,   10447.387839604, 1.460726241},
+     {    0.020937E-6,    8429.241266467, 0.652303414},
+     {    0.020322E-6,     419.484643875, 3.735430632},
+     {    0.024816E-6,   -1194.447010225, 1.087136918},
+     {    0.025196E-6,    1748.016413067, 2.901883301},
+     {    0.021691E-6,   14143.495242431, 5.952658009},
+     {    0.017673E-6,    6812.766815086, 3.186129845},
+     {    0.022567E-6,    6133.512652857, 3.307984806},
+     {    0.016155E-6,   10213.285546211, 1.331103168},
+     {    0.014751E-6,    1349.867409659, 4.308933301},
+     {    0.015949E-6,    -220.412642439, 4.005298270},
+     {    0.015974E-6,   -2352.866153772, 6.145309371},
+     {    0.014223E-6,   17789.845619785, 2.104551349},
+     {    0.017806E-6,      73.297125859, 3.475975097},
+     {    0.013671E-6,    -536.804512095, 5.971672571},
+     {    0.011942E-6,    8031.092263058, 2.053414715},
+     {    0.014318E-6,   16730.463689596, 3.016058075},
+     {    0.012462E-6,     103.092774219, 1.737438797},
+     {    0.010962E-6,       3.590428652, 2.196567739},
+     {    0.015078E-6,   19651.048481098, 3.969480770},
+     {    0.010396E-6,     951.718406251, 5.717799605},
+     {    0.011707E-6,   -4705.732307544, 2.654125618},
+     {    0.010453E-6,    5863.591206116, 1.913704550},
+     {    0.012420E-6,    4690.479836359, 4.734090399},
+     {    0.011847E-6,    5643.178563677, 5.489005403},
+     {    0.008610E-6,    3340.612426700, 3.661698944},
+     {    0.011622E-6,    5120.601145584, 4.863931876},
+     {    0.010825E-6,     553.569402842, 0.842715011},
+     {    0.008666E-6,    -135.065080035, 3.293406547},
+     {    0.009963E-6,     149.563197135, 4.870690598},
+     {    0.009858E-6,    6309.374169791, 1.061816410},
+     {    0.007959E-6,     316.391869657, 2.465042647},
+     {    0.010099E-6,     283.859318865, 1.942176992},
+     {    0.007147E-6,    -242.728603974, 3.661486981},
+     {    0.007505E-6,    5230.807466803, 4.920937029},
+     {    0.008323E-6,   11769.853693166, 1.229392026},
+     {    0.007490E-6,   -6256.777530192, 3.658444681},
+     {    0.009370E-6,  149854.400134205, 0.673880395},
+     {    0.007117E-6,      38.027672636, 5.294249518},
+     {    0.007857E-6,   12168.002696575, 0.525733528},
+     {    0.007019E-6,    6206.809778716, 0.837688810},
+     {    0.006056E-6,     955.599741609, 4.194535082},
+     {    0.008107E-6,   13367.972631107, 3.793235253},
+     {    0.006731E-6,    5650.292110678, 5.639906583},
+     {    0.007332E-6,      36.648562930, 0.114858677},
+     {    0.006366E-6,    4164.311989613, 2.262081818},
+     {    0.006858E-6,    5216.580372801, 0.642063318},
+     {    0.006919E-6,    6681.224853400, 6.018501522},
+     {    0.006826E-6,    7632.943259650, 3.458654112},
+     {    0.005308E-6,   -1592.596013633, 2.500382359},
+     {    0.005096E-6,   11371.704689758, 2.547107806},
+     {    0.004841E-6,    5333.900241022, 0.437078094},
+     {    0.005582E-6,    5966.683980335, 2.246174308},
+     {    0.006304E-6,   11926.254413669, 2.512929171},
+     {    0.006603E-6,   23581.258177318, 5.393136889},
+     {    0.005123E-6,      -1.484472708, 2.999641028},
+     {    0.004648E-6,    1589.072895284, 1.275847090},
+     {    0.005119E-6,    6438.496249426, 1.486539246},
+     {    0.004521E-6,    4292.330832950, 6.140635794},
+     {    0.005680E-6,   23013.539539587, 4.557814849},
+     {    0.005488E-6,      -3.455808046, 0.090675389},
+     {    0.004193E-6,    7234.794256242, 4.869091389},
+     {    0.003742E-6,    7238.675591600, 4.691976180},
+     {    0.004148E-6,    -110.206321219, 3.016173439},
+     {    0.004553E-6,   11499.656222793, 5.554998314},
+     {    0.004892E-6,    5436.993015240, 1.475415597},
+     {    0.004044E-6,    4732.030627343, 1.398784824},
+     {    0.004164E-6,   12491.370101415, 5.650931916},
+     {    0.004349E-6,   11513.883316794, 2.181745369},
+     {    0.003919E-6,   12528.018664345, 5.823319737},
+     {    0.003129E-6,    6836.645252834, 0.003844094},
+     {    0.004080E-6,   -7058.598461315, 3.690360123},
+     {    0.003270E-6,      76.266071276, 1.517189902},
+     {    0.002954E-6,    6283.143160294, 4.447203799},
+     {    0.002872E-6,      28.449187468, 1.158692983},
+     {    0.002881E-6,     735.876513532, 0.349250250},
+     {    0.003279E-6,    5849.364112115, 4.893384368},
+     {    0.003625E-6,    6209.778724132, 1.473760578},
+     {    0.003074E-6,     949.175608970, 5.185878737},
+     {    0.002775E-6,    9917.696874510, 1.030026325},
+     {    0.002646E-6,   10973.555686350, 3.918259169},
+     {    0.002575E-6,   25132.303399966, 6.109659023},
+     {    0.003500E-6,     263.083923373, 1.892100742},
+     {    0.002740E-6,   18319.536584880, 4.320519510},
+     {    0.002464E-6,     202.253395174, 4.698203059},
+     {    0.002409E-6,       2.542797281, 5.325009315},
+     {    0.003354E-6,  -90955.551694697, 1.942656623},
+     {    0.002296E-6,    6496.374945429, 5.061810696},
+     {    0.003002E-6,    6172.869528772, 2.797822767},
+     {    0.003202E-6,   27511.467873537, 0.531673101},
+     {    0.002954E-6,   -6283.008539689, 4.533471191},
+     {    0.002353E-6,     639.897286314, 3.734548088},
+     {    0.002401E-6,   16200.772724501, 2.605547070},
+     {    0.003053E-6,  233141.314403759, 3.029030662},
+     {    0.003024E-6,   83286.914269554, 2.355556099},
+     {    0.002863E-6,   17298.182327326, 5.240963796},
+     {    0.002103E-6,   -7079.373856808, 5.756641637},
+     {    0.002303E-6,   83996.847317911, 2.013686814},
+     {    0.002303E-6,   18073.704938650, 1.089100410},
+     {    0.002381E-6,      63.735898303, 0.759188178},
+     {    0.002493E-6,    6386.168624210, 0.645026535},
+     {    0.002366E-6,       3.932153263, 6.215885448},
+     {    0.002169E-6,   11015.106477335, 4.845297676},
+     {    0.002397E-6,    6243.458341645, 3.809290043},
+     {    0.002183E-6,    1162.474704408, 6.179611691},
+     {    0.002353E-6,    6246.427287062, 4.781719760},
+     {    0.002199E-6,    -245.831646229, 5.956152284},
+     {    0.001729E-6,    3894.181829542, 1.264976635},
+     {    0.001896E-6,   -3128.388765096, 4.914231596},
+     {    0.002085E-6,      35.164090221, 1.405158503},
+     {    0.002024E-6,   14712.317116458, 2.752035928},
+     {    0.001737E-6,    6290.189396992, 5.280820144},
+     {    0.002229E-6,     491.557929457, 1.571007057},
+     {    0.001602E-6,   14314.168113050, 4.203664806},
+     {    0.002186E-6,     454.909366527, 1.402101526},
+     {    0.001897E-6,   22483.848574493, 4.167932508},
+     {    0.001825E-6,   -3738.761430108, 0.545828785},
+     {    0.001894E-6,    1052.268383188, 5.817167450},
+     {    0.001421E-6,      20.355319399, 2.419886601},
+     {    0.001408E-6,   10984.192351700, 2.732084787},
+     {    0.001847E-6,   10873.986030480, 2.903477885},
+     {    0.001391E-6,   -8635.942003763, 0.593891500},
+     {    0.001388E-6,      -7.046236698, 1.166145902},
+     {    0.001810E-6,  -88860.057071188, 0.487355242},
+     {    0.001288E-6,   -1990.745017041, 3.913022880},
+     {    0.001297E-6,   23543.230504682, 3.063805171},
+     {    0.001335E-6,    -266.607041722, 3.995764039},
+     {    0.001376E-6,   10969.965257698, 5.152914309},
+     {    0.001745E-6,  244287.600007027, 3.626395673},
+     {    0.001649E-6,   31441.677569757, 1.952049260},
+     {    0.001416E-6,    9225.539273283, 4.996408389},
+     {    0.001238E-6,    4804.209275927, 5.503379738},
+     {    0.001472E-6,    4590.910180489, 4.164913291},
+     {    0.001169E-6,    6040.347246017, 5.841719038},
+     {    0.001039E-6,    5540.085789459, 2.769753519},
+     {    0.001004E-6,    -170.672870619, 0.755008103},
+     {    0.001284E-6,   10575.406682942, 5.306538209},
+     {    0.001278E-6,      71.812653151, 4.713486491},
+     {    0.001321E-6,   18209.330263660, 2.624866359},
+     {    0.001297E-6,   21228.392023546, 0.382603541},
+     {    0.000954E-6,    6282.095528923, 0.882213514},
+     {    0.001145E-6,    6058.731054289, 1.169483931},
+     {    0.000979E-6,    5547.199336460, 5.448375984},
+     {    0.000987E-6,   -6262.300454499, 2.656486959},
+     {    0.001070E-6, -154717.609887482, 1.827624012},
+     {    0.000991E-6,    4701.116501708, 4.387001801},
+     {    0.001155E-6,     -14.227094002, 3.042700750},
+     {    0.001176E-6,     277.034993741, 3.335519004},
+     {    0.000890E-6,   13916.019109642, 5.601498297},
+     {    0.000884E-6,   -1551.045222648, 1.088831705},
+     {    0.000876E-6,    5017.508371365, 3.969902609},
+     {    0.000806E-6,   15110.466119866, 5.142876744},
+     {    0.000773E-6,   -4136.910433516, 0.022067765},
+     {    0.001077E-6,     175.166059800, 1.844913056},
+     {    0.000954E-6,   -6284.056171060, 0.968480906},
+     {    0.000737E-6,    5326.786694021, 4.923831588},
+     {    0.000845E-6,    -433.711737877, 4.749245231},
+     {    0.000819E-6,    8662.240323563, 5.991247817},
+     {    0.000852E-6,     199.072001436, 2.189604979},
+     {    0.000723E-6,   17256.631536341, 6.068719637},
+     {    0.000940E-6,    6037.244203762, 6.197428148},
+     {    0.000885E-6,   11712.955318231, 3.280414875},
+     {    0.000706E-6,   12559.038152982, 2.824848947},
+     {    0.000732E-6,    2379.164473572, 2.501813417},
+     {    0.000764E-6,   -6127.655450557, 2.236346329},
+     {    0.000908E-6,     131.541961686, 2.521257490},
+     {    0.000907E-6,   35371.887265976, 3.370195967},
+     {    0.000673E-6,    1066.495477190, 3.876512374},
+     {    0.000814E-6,   17654.780539750, 4.627122566},
+     {    0.000630E-6,      36.027866677, 0.156368499},
+     {    0.000798E-6,     515.463871093, 5.151962502},
+     {    0.000798E-6,     148.078724426, 5.909225055},
+     {    0.000806E-6,     309.278322656, 6.054064447},
+     {    0.000607E-6,     -39.617508346, 2.839021623},
+     {    0.000601E-6,     412.371096874, 3.984225404},
+     {    0.000646E-6,   11403.676995575, 3.852959484},
+     {    0.000704E-6,   13521.751441591, 2.300991267},
+     {    0.000603E-6,  -65147.619767937, 4.140083146},
+     {    0.000609E-6,   10177.257679534, 0.437122327},
+     {    0.000631E-6,    5767.611978898, 4.026532329},
+     {    0.000576E-6,   11087.285125918, 4.760293101},
+     {    0.000674E-6,   14945.316173554, 6.270510511},
+     {    0.000726E-6,    5429.879468239, 6.039606892},
+     {    0.000710E-6,   28766.924424484, 5.672617711},
+     {    0.000647E-6,   11856.218651625, 3.397132627},
+     {    0.000678E-6,   -5481.254918868, 6.249666675},
+     {    0.000618E-6,   22003.914634870, 2.466427018},
+     {    0.000738E-6,    6134.997125565, 2.242668890},
+     {    0.000660E-6,     625.670192312, 5.864091907},
+     {    0.000694E-6,    3496.032826134, 2.668309141},
+     {    0.000531E-6,    6489.261398429, 1.681888780},
+     {    0.000611E-6, -143571.324284214, 2.424978312},
+     {    0.000575E-6,   12043.574281889, 4.216492400},
+     {    0.000553E-6,   12416.588502848, 4.772158039},
+     {    0.000689E-6,    4686.889407707, 6.224271088},
+     {    0.000495E-6,    7342.457780181, 3.817285811},
+     {    0.000567E-6,    3634.621024518, 1.649264690},
+     {    0.000515E-6,   18635.928454536, 3.945345892},
+     {    0.000486E-6,    -323.505416657, 4.061673868},
+     {    0.000662E-6,   25158.601719765, 1.794058369},
+     {    0.000509E-6,     846.082834751, 3.053874588},
+     {    0.000472E-6,  -12569.674818332, 5.112133338},
+     {    0.000461E-6,    6179.983075773, 0.513669325},
+     {    0.000641E-6,   83467.156352816, 3.210727723},
+     {    0.000520E-6,   10344.295065386, 2.445597761},
+     {    0.000493E-6,   18422.629359098, 1.676939306},
+     {    0.000478E-6,    1265.567478626, 5.487314569},
+     {    0.000472E-6,     -18.159247265, 1.999707589},
+     {    0.000559E-6,   11190.377900137, 5.783236356},
+     {    0.000494E-6,    9623.688276691, 3.022645053},
+     {    0.000463E-6,    5739.157790895, 1.411223013},
+     {    0.000432E-6,   16858.482532933, 1.179256434},
+     {    0.000574E-6,   72140.628666286, 1.758191830},
+     {    0.000484E-6,   17267.268201691, 3.290589143},
+     {    0.000550E-6,    4907.302050146, 0.864024298},
+     {    0.000399E-6,      14.977853527, 2.094441910},
+     {    0.000491E-6,     224.344795702, 0.878372791},
+     {    0.000432E-6,   20426.571092422, 6.003829241},
+     {    0.000481E-6,    5749.452731634, 4.309591964},
+     {    0.000480E-6,    5757.317038160, 1.142348571},
+     {    0.000485E-6,    6702.560493867, 0.210580917},
+     {    0.000426E-6,    6055.549660552, 4.274476529},
+     {    0.000480E-6,    5959.570433334, 5.031351030},
+     {    0.000466E-6,   12562.628581634, 4.959581597},
+     {    0.000520E-6,   39302.096962196, 4.788002889},
+     {    0.000458E-6,   12132.439962106, 1.880103788},
+     {    0.000470E-6,   12029.347187887, 1.405611197},
+     {    0.000416E-6,   -7477.522860216, 1.082356330},
+     {    0.000449E-6,   11609.862544012, 4.179989585},
+     {    0.000465E-6,   17253.041107690, 0.353496295},
+     {    0.000362E-6,   -4535.059436924, 1.583849576},
+     {    0.000383E-6,   21954.157609398, 3.747376371},
+     {    0.000389E-6,      17.252277143, 1.395753179},
+     {    0.000331E-6,   18052.929543158, 0.566790582},
+     {    0.000430E-6,   13517.870106233, 0.685827538},
+     {    0.000368E-6,   -5756.908003246, 0.731374317},
+     {    0.000330E-6,   10557.594160824, 3.710043680},
+     {    0.000332E-6,   20199.094959633, 1.652901407},
+     {    0.000384E-6,   11933.367960670, 5.827781531},
+     {    0.000387E-6,   10454.501386605, 2.541182564},
+     {    0.000325E-6,   15671.081759407, 2.178850542},
+     {    0.000318E-6,     138.517496871, 2.253253037},
+     {    0.000305E-6,    9388.005909415, 0.578340206},
+     {    0.000352E-6,    5749.861766548, 3.000297967},
+     {    0.000311E-6,    6915.859589305, 1.693574249},
+     {    0.000297E-6,   24072.921469776, 1.997249392},
+     {    0.000363E-6,    -640.877607382, 5.071820966},
+     {    0.000323E-6,   12592.450019783, 1.072262823},
+     {    0.000341E-6,   12146.667056108, 4.700657997},
+     {    0.000290E-6,    9779.108676125, 1.812320441},
+     {    0.000342E-6,    6132.028180148, 4.322238614},
+     {    0.000329E-6,    6268.848755990, 3.033827743},
+     {    0.000374E-6,   17996.031168222, 3.388716544},
+     {    0.000285E-6,    -533.214083444, 4.687313233},
+     {    0.000338E-6,    6065.844601290, 0.877776108},
+     {    0.000276E-6,      24.298513841, 0.770299429},
+     {    0.000336E-6,   -2388.894020449, 5.353796034},
+     {    0.000290E-6,    3097.883822726, 4.075291557},
+     {    0.000318E-6,     709.933048357, 5.941207518},
+     {    0.000271E-6,   13095.842665077, 3.208912203},
+     {    0.000331E-6,    6073.708907816, 4.007881169},
+     {    0.000292E-6,     742.990060533, 2.714333592},
+     {    0.000362E-6,   29088.811415985, 3.215977013},
+     {    0.000280E-6,   12359.966151546, 0.710872502},
+     {    0.000267E-6,   10440.274292604, 4.730108488},
+     {    0.000262E-6,     838.969287750, 1.327720272},
+     {    0.000250E-6,   16496.361396202, 0.898769761},
+     {    0.000325E-6,   20597.243963041, 0.180044365},
+     {    0.000268E-6,    6148.010769956, 5.152666276},
+     {    0.000284E-6,    5636.065016677, 5.655385808},
+     {    0.000301E-6,    6080.822454817, 2.135396205},
+     {    0.000294E-6,    -377.373607916, 3.708784168},
+     {    0.000236E-6,    2118.763860378, 1.733578756},
+     {    0.000234E-6,    5867.523359379, 5.575209112},
+     {    0.000268E-6, -226858.238553767, 0.069432392},
+     {    0.000265E-6,  167283.761587465, 4.369302826},
+     {    0.000280E-6,   28237.233459389, 5.304829118},
+     {    0.000292E-6,   12345.739057544, 4.096094132},
+     {    0.000223E-6,   19800.945956225, 3.069327406},
+     {    0.000301E-6,   43232.306658416, 6.205311188},
+     {    0.000264E-6,   18875.525869774, 1.417263408},
+     {    0.000304E-6,   -1823.175188677, 3.409035232},
+     {    0.000301E-6,     109.945688789, 0.510922054},
+     {    0.000260E-6,     813.550283960, 2.389438934},
+     {    0.000299E-6,  316428.228673312, 5.384595078},
+     {    0.000211E-6,    5756.566278634, 3.789392838},
+     {    0.000209E-6,    5750.203491159, 1.661943545},
+     {    0.000240E-6,   12489.885628707, 5.684549045},
+     {    0.000216E-6,    6303.851245484, 3.862942261},
+     {    0.000203E-6,    1581.959348283, 5.549853589},
+     {    0.000200E-6,    5642.198242609, 1.016115785},
+     {    0.000197E-6,     -70.849445304, 4.690702525},
+     {    0.000227E-6,    6287.008003254, 2.911891613},
+     {    0.000197E-6,     533.623118358, 1.048982898},
+     {    0.000205E-6,   -6279.485421340, 1.829362730},
+     {    0.000209E-6,  -10988.808157535, 2.636140084},
+     {    0.000208E-6,    -227.526189440, 4.127883842},
+     {    0.000191E-6,     415.552490612, 4.401165650},
+     {    0.000190E-6,   29296.615389579, 4.175658539},
+     {    0.000264E-6,   66567.485864652, 4.601102551},
+     {    0.000256E-6,   -3646.350377354, 0.506364778},
+     {    0.000188E-6,   13119.721102825, 2.032195842},
+     {    0.000185E-6,    -209.366942175, 4.694756586},
+     {    0.000198E-6,   25934.124331089, 3.832703118},
+     {    0.000195E-6,    4061.219215394, 3.308463427},
+     {    0.000234E-6,    5113.487598583, 1.716090661},
+     {    0.000188E-6,    1478.866574064, 5.686865780},
+     {    0.000222E-6,   11823.161639450, 1.942386641},
+     {    0.000181E-6,   10770.893256262, 1.999482059},
+     {    0.000171E-6,    6546.159773364, 1.182807992},
+     {    0.000206E-6,      70.328180442, 5.934076062},
+     {    0.000169E-6,   20995.392966449, 2.169080622},
+     {    0.000191E-6,   10660.686935042, 5.405515999},
+     {    0.000228E-6,   33019.021112205, 4.656985514},
+     {    0.000184E-6,   -4933.208440333, 3.327476868},
+     {    0.000220E-6,    -135.625325010, 1.765430262},
+     {    0.000166E-6,   23141.558382925, 3.454132746},
+     {    0.000191E-6,    6144.558353121, 5.020393445},
+     {    0.000180E-6,    6084.003848555, 0.602182191},
+     {    0.000163E-6,   17782.732072784, 4.960593133},
+     {    0.000225E-6,   16460.333529525, 2.596451817},
+     {    0.000222E-6,    5905.702242076, 3.731990323},
+     {    0.000204E-6,     227.476132789, 5.636192701},
+     {    0.000159E-6,   16737.577236597, 3.600691544},
+     {    0.000200E-6,    6805.653268085, 0.868220961},
+     {    0.000187E-6,   11919.140866668, 2.629456641},
+     {    0.000161E-6,     127.471796607, 2.862574720},
+     {    0.000205E-6,    6286.666278643, 1.742882331},
+     {    0.000189E-6,     153.778810485, 4.812372643},
+     {    0.000168E-6,   16723.350142595, 0.027860588},
+     {    0.000149E-6,   11720.068865232, 0.659721876},
+     {    0.000189E-6,    5237.921013804, 5.245313000},
+     {    0.000143E-6,    6709.674040867, 4.317625647},
+     {    0.000146E-6,    4487.817406270, 4.815297007},
+     {    0.000144E-6,    -664.756045130, 5.381366880},
+     {    0.000175E-6,    5127.714692584, 4.728443327},
+     {    0.000162E-6,    6254.626662524, 1.435132069},
+     {    0.000187E-6,   47162.516354635, 1.354371923},
+     {    0.000146E-6,   11080.171578918, 3.369695406},
+     {    0.000180E-6,    -348.924420448, 2.490902145},
+     {    0.000148E-6,     151.047669843, 3.799109588},
+     {    0.000157E-6,    6197.248551160, 1.284375887},
+     {    0.000167E-6,     146.594251718, 0.759969109},
+     {    0.000133E-6,   -5331.357443741, 5.409701889},
+     {    0.000154E-6,      95.979227218, 3.366890614},
+     {    0.000148E-6,   -6418.140930027, 3.384104996},
+     {    0.000128E-6,   -6525.804453965, 3.803419985},
+     {    0.000130E-6,   11293.470674356, 0.939039445},
+     {    0.000152E-6,   -5729.506447149, 0.734117523},
+     {    0.000138E-6,     210.117701700, 2.564216078},
+     {    0.000123E-6,    6066.595360816, 4.517099537},
+     {    0.000140E-6,   18451.078546566, 0.642049130},
+     {    0.000126E-6,   11300.584221356, 3.485280663},
+     {    0.000119E-6,   10027.903195729, 3.217431161},
+     {    0.000151E-6,    4274.518310832, 4.404359108},
+     {    0.000117E-6,    6072.958148291, 0.366324650},
+     {    0.000165E-6,   -7668.637425143, 4.298212528},
+     {    0.000117E-6,   -6245.048177356, 5.379518958},
+     {    0.000130E-6,   -5888.449964932, 4.527681115},
+     {    0.000121E-6,    -543.918059096, 6.109429504},
+     {    0.000162E-6,    9683.594581116, 5.720092446},
+     {    0.000141E-6,    6219.339951688, 0.679068671},
+     {    0.000118E-6,   22743.409379516, 4.881123092},
+     {    0.000129E-6,    1692.165669502, 0.351407289},
+     {    0.000126E-6,    5657.405657679, 5.146592349},
+     {    0.000114E-6,     728.762966531, 0.520791814},
+     {    0.000120E-6,      52.596639600, 0.948516300},
+     {    0.000115E-6,      65.220371012, 3.504914846},
+     {    0.000126E-6,    5881.403728234, 5.577502482},
+     {    0.000158E-6,  163096.180360983, 2.957128968},
+     {    0.000134E-6,   12341.806904281, 2.598576764},
+     {    0.000151E-6,   16627.370915377, 3.985702050},
+     {    0.000109E-6,    1368.660252845, 0.014730471},
+     {    0.000131E-6,    6211.263196841, 0.085077024},
+     {    0.000146E-6,    5792.741760812, 0.708426604},
+     {    0.000146E-6,     -77.750543984, 3.121576600},
+     {    0.000107E-6,    5341.013788022, 0.288231904},
+     {    0.000138E-6,    6281.591377283, 2.797450317},
+     {    0.000113E-6,   -6277.552925684, 2.788904128},
+     {    0.000115E-6,    -525.758811831, 5.895222200},
+     {    0.000138E-6,    6016.468808270, 6.096188999},
+     {    0.000139E-6,   23539.707386333, 2.028195445},
+     {    0.000146E-6,   -4176.041342449, 4.660008502},
+     {    0.000107E-6,   16062.184526117, 4.066520001},
+     {    0.000142E-6,   83783.548222473, 2.936315115},
+     {    0.000128E-6,    9380.959672717, 3.223844306},
+     {    0.000135E-6,    6205.325306007, 1.638054048},
+     {    0.000101E-6,    2699.734819318, 5.481603249},
+     {    0.000104E-6,    -568.821874027, 2.205734493},
+     {    0.000103E-6,    6321.103522627, 2.440421099},
+     {    0.000119E-6,    6321.208885629, 2.547496264},
+     {    0.000138E-6,    1975.492545856, 2.314608466},
+     {    0.000121E-6,     137.033024162, 4.539108237},
+     {    0.000123E-6,   19402.796952817, 4.538074405},
+     {    0.000119E-6,   22805.735565994, 2.869040566},
+     {    0.000133E-6,   64471.991241142, 6.056405489},
+     {    0.000129E-6,     -85.827298831, 2.540635083},
+     {    0.000131E-6,   13613.804277336, 4.005732868},
+     {    0.000104E-6,    9814.604100291, 1.959967212},
+     {    0.000112E-6,   16097.679950283, 3.589026260},
+     {    0.000123E-6,    2107.034507542, 1.728627253},
+     {    0.000121E-6,   36949.230808424, 6.072332087},
+     {    0.000108E-6,  -12539.853380183, 3.716133846},
+     {    0.000113E-6,   -7875.671863624, 2.725771122},
+     {    0.000109E-6,    4171.425536614, 4.033338079},
+     {    0.000101E-6,    6247.911759770, 3.441347021},
+     {    0.000113E-6,    7330.728427345, 0.656372122},
+     {    0.000113E-6,   51092.726050855, 2.791483066},
+     {    0.000106E-6,    5621.842923210, 1.815323326},
+     {    0.000101E-6,     111.430161497, 5.711033677},
+     {    0.000103E-6,     909.818733055, 2.812745443},
+     {    0.000101E-6,    1790.642637886, 1.965746028},
+     {  102.156724E-6,    6283.075849991, 4.249032005},
+     {    1.706807E-6,   12566.151699983, 4.205904248},
+     {    0.269668E-6,     213.299095438, 3.400290479},
+     {    0.265919E-6,     529.690965095, 5.836047367},
+     {    0.210568E-6,      -3.523118349, 6.262738348},
+     {    0.077996E-6,    5223.693919802, 4.670344204},
+     {    0.054764E-6,    1577.343542448, 4.534800170},
+     {    0.059146E-6,      26.298319800, 1.083044735},
+     {    0.034420E-6,    -398.149003408, 5.980077351},
+     {    0.032088E-6,   18849.227549974, 4.162913471},
+     {    0.033595E-6,    5507.553238667, 5.980162321},
+     {    0.029198E-6,    5856.477659115, 0.623811863},
+     {    0.027764E-6,     155.420399434, 3.745318113},
+     {    0.025190E-6,    5746.271337896, 2.980330535},
+     {    0.022997E-6,    -796.298006816, 1.174411803},
+     {    0.024976E-6,    5760.498431898, 2.467913690},
+     {    0.021774E-6,     206.185548437, 3.854787540},
+     {    0.017925E-6,    -775.522611324, 1.092065955},
+     {    0.013794E-6,     426.598190876, 2.699831988},
+     {    0.013276E-6,    6062.663207553, 5.845801920},
+     {    0.011774E-6,   12036.460734888, 2.292832062},
+     {    0.012869E-6,    6076.890301554, 5.333425680},
+     {    0.012152E-6,    1059.381930189, 6.222874454},
+     {    0.011081E-6,      -7.113547001, 5.154724984},
+     {    0.010143E-6,    4694.002954708, 4.044013795},
+     {    0.009357E-6,    5486.777843175, 3.416081409},
+     {    0.010084E-6,     522.577418094, 0.749320262},
+     {    0.008587E-6,   10977.078804699, 2.777152598},
+     {    0.008628E-6,    6275.962302991, 4.562060226},
+     {    0.008158E-6,    -220.412642439, 5.806891533},
+     {    0.007746E-6,    2544.314419883, 1.603197066},
+     {    0.007670E-6,    2146.165416475, 3.000200440},
+     {    0.007098E-6,      74.781598567, 0.443725817},
+     {    0.006180E-6,    -536.804512095, 1.302642751},
+     {    0.005818E-6,    5088.628839767, 4.827723531},
+     {    0.004945E-6,   -6286.598968340, 0.268305170},
+     {    0.004774E-6,    1349.867409659, 5.808636673},
+     {    0.004687E-6,    -242.728603974, 5.154890570},
+     {    0.006089E-6,    1748.016413067, 4.403765209},
+     {    0.005975E-6,   -1194.447010225, 2.583472591},
+     {    0.004229E-6,     951.718406251, 0.931172179},
+     {    0.005264E-6,     553.569402842, 2.336107252},
+     {    0.003049E-6,    5643.178563677, 1.362634430},
+     {    0.002974E-6,    6812.766815086, 1.583012668},
+     {    0.003403E-6,   -2352.866153772, 2.552189886},
+     {    0.003030E-6,     419.484643875, 5.286473844},
+     {    0.003210E-6,      -7.046236698, 1.863796539},
+     {    0.003058E-6,    9437.762934887, 4.226420633},
+     {    0.002589E-6,   12352.852604545, 1.991935820},
+     {    0.002927E-6,    5216.580372801, 2.319951253},
+     {    0.002425E-6,    5230.807466803, 3.084752833},
+     {    0.002656E-6,    3154.687084896, 2.487447866},
+     {    0.002445E-6,   10447.387839604, 2.347139160},
+     {    0.002990E-6,    4690.479836359, 6.235872050},
+     {    0.002890E-6,    5863.591206116, 0.095197563},
+     {    0.002498E-6,    6438.496249426, 2.994779800},
+     {    0.001889E-6,    8031.092263058, 3.569003717},
+     {    0.002567E-6,     801.820931124, 3.425611498},
+     {    0.001803E-6,  -71430.695617928, 2.192295512},
+     {    0.001782E-6,       3.932153263, 5.180433689},
+     {    0.001694E-6,   -4705.732307544, 4.641779174},
+     {    0.001704E-6,   -1592.596013633, 3.997097652},
+     {    0.001735E-6,    5849.364112115, 0.417558428},
+     {    0.001643E-6,    8429.241266467, 2.180619584},
+     {    0.001680E-6,      38.133035638, 4.164529426},
+     {    0.002045E-6,    7084.896781115, 0.526323854},
+     {    0.001458E-6,    4292.330832950, 1.356098141},
+     {    0.001437E-6,      20.355319399, 3.895439360},
+     {    0.001738E-6,    6279.552731642, 0.087484036},
+     {    0.001367E-6,   14143.495242431, 3.987576591},
+     {    0.001344E-6,    7234.794256242, 0.090454338},
+     {    0.001438E-6,   11499.656222793, 0.974387904},
+     {    0.001257E-6,    6836.645252834, 1.509069366},
+     {    0.001358E-6,   11513.883316794, 0.495572260},
+     {    0.001628E-6,    7632.943259650, 4.968445721},
+     {    0.001169E-6,     103.092774219, 2.838496795},
+     {    0.001162E-6,    4164.311989613, 3.408387778},
+     {    0.001092E-6,    6069.776754553, 3.617942651},
+     {    0.001008E-6,   17789.845619785, 0.286350174},
+     {    0.001008E-6,     639.897286314, 1.610762073},
+     {    0.000918E-6,   10213.285546211, 5.532798067},
+     {    0.001011E-6,   -6256.777530192, 0.661826484},
+     {    0.000753E-6,   16730.463689596, 3.905030235},
+     {    0.000737E-6,   11926.254413669, 4.641956361},
+     {    0.000694E-6,    3340.612426700, 2.111120332},
+     {    0.000701E-6,    3894.181829542, 2.760823491},
+     {    0.000689E-6,    -135.065080035, 4.768800780},
+     {    0.000700E-6,   13367.972631107, 5.760439898},
+     {    0.000664E-6,    6040.347246017, 1.051215840},
+     {    0.000654E-6,    5650.292110678, 4.911332503},
+     {    0.000788E-6,    6681.224853400, 4.699648011},
+     {    0.000628E-6,    5333.900241022, 5.024608847},
+     {    0.000755E-6,    -110.206321219, 4.370971253},
+     {    0.000628E-6,    6290.189396992, 3.660478857},
+     {    0.000635E-6,   25132.303399966, 4.121051532},
+     {    0.000534E-6,    5966.683980335, 1.173284524},
+     {    0.000543E-6,    -433.711737877, 0.345585464},
+     {    0.000517E-6,   -1990.745017041, 5.414571768},
+     {    0.000504E-6,    5767.611978898, 2.328281115},
+     {    0.000485E-6,    5753.384884897, 1.685874771},
+     {    0.000463E-6,    7860.419392439, 5.297703006},
+     {    0.000604E-6,     515.463871093, 0.591998446},
+     {    0.000443E-6,   12168.002696575, 4.830881244},
+     {    0.000570E-6,     199.072001436, 3.899190272},
+     {    0.000465E-6,   10969.965257698, 0.476681802},
+     {    0.000424E-6,   -7079.373856808, 1.112242763},
+     {    0.000427E-6,     735.876513532, 1.994214480},
+     {    0.000478E-6,   -6127.655450557, 3.778025483},
+     {    0.000414E-6,   10973.555686350, 5.441088327},
+     {    0.000512E-6,    1589.072895284, 0.107123853},
+     {    0.000378E-6,   10984.192351700, 0.915087231},
+     {    0.000402E-6,   11371.704689758, 4.107281715},
+     {    0.000453E-6,    9917.696874510, 1.917490952},
+     {    0.000395E-6,     149.563197135, 2.763124165},
+     {    0.000371E-6,    5739.157790895, 3.112111866},
+     {    0.000350E-6,   11790.629088659, 0.440639857},
+     {    0.000356E-6,    6133.512652857, 5.444568842},
+     {    0.000344E-6,     412.371096874, 5.676832684},
+     {    0.000383E-6,     955.599741609, 5.559734846},
+     {    0.000333E-6,    6496.374945429, 0.261537984},
+     {    0.000340E-6,    6055.549660552, 5.975534987},
+     {    0.000334E-6,    1066.495477190, 2.335063907},
+     {    0.000399E-6,   11506.769769794, 5.321230910},
+     {    0.000314E-6,   18319.536584880, 2.313312404},
+     {    0.000424E-6,    1052.268383188, 1.211961766},
+     {    0.000307E-6,      63.735898303, 3.169551388},
+     {    0.000329E-6,      29.821438149, 6.106912080},
+     {    0.000357E-6,    6309.374169791, 4.223760346},
+     {    0.000312E-6,   -3738.761430108, 2.180556645},
+     {    0.000301E-6,     309.278322656, 1.499984572},
+     {    0.000268E-6,   12043.574281889, 2.447520648},
+     {    0.000257E-6,   12491.370101415, 3.662331761},
+     {    0.000290E-6,     625.670192312, 1.272834584},
+     {    0.000256E-6,    5429.879468239, 1.913426912},
+     {    0.000339E-6,    3496.032826134, 4.165930011},
+     {    0.000283E-6,    3930.209696220, 4.325565754},
+     {    0.000241E-6,   12528.018664345, 3.832324536},
+     {    0.000304E-6,    4686.889407707, 1.612348468},
+     {    0.000259E-6,   16200.772724501, 3.470173146},
+     {    0.000238E-6,   12139.553509107, 1.147977842},
+     {    0.000236E-6,    6172.869528772, 3.776271728},
+     {    0.000296E-6,   -7058.598461315, 0.460368852},
+     {    0.000306E-6,   10575.406682942, 0.554749016},
+     {    0.000251E-6,   17298.182327326, 0.834332510},
+     {    0.000290E-6,    4732.030627343, 4.759564091},
+     {    0.000261E-6,    5884.926846583, 0.298259862},
+     {    0.000249E-6,    5547.199336460, 3.749366406},
+     {    0.000213E-6,   11712.955318231, 5.415666119},
+     {    0.000223E-6,    4701.116501708, 2.703203558},
+     {    0.000268E-6,    -640.877607382, 0.283670793},
+     {    0.000209E-6,    5636.065016677, 1.238477199},
+     {    0.000193E-6,   10177.257679534, 1.943251340},
+     {    0.000182E-6,    6283.143160294, 2.456157599},
+     {    0.000184E-6,    -227.526189440, 5.888038582},
+     {    0.000182E-6,   -6283.008539689, 0.241332086},
+     {    0.000228E-6,   -6284.056171060, 2.657323816},
+     {    0.000166E-6,    7238.675591600, 5.930629110},
+     {    0.000167E-6,    3097.883822726, 5.570955333},
+     {    0.000159E-6,    -323.505416657, 5.786670700},
+     {    0.000154E-6,   -4136.910433516, 1.517805532},
+     {    0.000176E-6,   12029.347187887, 3.139266834},
+     {    0.000167E-6,   12132.439962106, 3.556352289},
+     {    0.000153E-6,     202.253395174, 1.463313961},
+     {    0.000157E-6,   17267.268201691, 1.586837396},
+     {    0.000142E-6,   83996.847317911, 0.022670115},
+     {    0.000152E-6,   17260.154654690, 0.708528947},
+     {    0.000144E-6,    6084.003848555, 5.187075177},
+     {    0.000135E-6,    5756.566278634, 1.993229262},
+     {    0.000134E-6,    5750.203491159, 3.457197134},
+     {    0.000144E-6,    5326.786694021, 6.066193291},
+     {    0.000160E-6,   11015.106477335, 1.710431974},
+     {    0.000133E-6,    3634.621024518, 2.836451652},
+     {    0.000134E-6,   18073.704938650, 5.453106665},
+     {    0.000134E-6,    1162.474704408, 5.326898811},
+     {    0.000128E-6,    5642.198242609, 2.511652591},
+     {    0.000160E-6,     632.783739313, 5.628785365},
+     {    0.000132E-6,   13916.019109642, 0.819294053},
+     {    0.000122E-6,   14314.168113050, 5.677408071},
+     {    0.000125E-6,   12359.966151546, 5.251984735},
+     {    0.000121E-6,    5749.452731634, 2.210924603},
+     {    0.000136E-6,    -245.831646229, 1.646502367},
+     {    0.000120E-6,    5757.317038160, 3.240883049},
+     {    0.000134E-6,   12146.667056108, 3.059480037},
+     {    0.000137E-6,    6206.809778716, 1.867105418},
+     {    0.000141E-6,   17253.041107690, 2.069217456},
+     {    0.000129E-6,   -7477.522860216, 2.781469314},
+     {    0.000116E-6,    5540.085789459, 4.281176991},
+     {    0.000116E-6,    9779.108676125, 3.320925381},
+     {    0.000129E-6,    5237.921013804, 3.497704076},
+     {    0.000113E-6,    5959.570433334, 0.983210840},
+     {    0.000122E-6,    6282.095528923, 2.674938860},
+     {    0.000140E-6,     -11.045700264, 4.957936982},
+     {    0.000108E-6,   23543.230504682, 1.390113589},
+     {    0.000106E-6,  -12569.674818332, 0.429631317},
+     {    0.000110E-6,    -266.607041722, 5.501340197},
+     {    0.000115E-6,   12559.038152982, 4.691456618},
+     {    0.000134E-6,   -2388.894020449, 0.577313584},
+     {    0.000109E-6,   10440.274292604, 6.218148717},
+     {    0.000102E-6,    -543.918059096, 1.477842615},
+     {    0.000108E-6,   21228.392023546, 2.237753948},
+     {    0.000101E-6,   -4535.059436924, 3.100492232},
+     {    0.000103E-6,      76.266071276, 5.594294322},
+     {    0.000104E-6,     949.175608970, 5.674287810},
+     {    0.000101E-6,   13517.870106233, 2.196632348},
+     {    0.000100E-6,   11933.367960670, 4.056084160},
+     {    4.322990E-6,    6283.075849991, 2.642893748},
+     {    0.406495E-6,       0.000000000, 4.712388980},
+     {    0.122605E-6,   12566.151699983, 2.438140634},
+     {    0.019476E-6,     213.299095438, 1.642186981},
+     {    0.016916E-6,     529.690965095, 4.510959344},
+     {    0.013374E-6,      -3.523118349, 1.502210314},
+     {    0.008042E-6,      26.298319800, 0.478549024},
+     {    0.007824E-6,     155.420399434, 5.254710405},
+     {    0.004894E-6,    5746.271337896, 4.683210850},
+     {    0.004875E-6,    5760.498431898, 0.759507698},
+     {    0.004416E-6,    5223.693919802, 6.028853166},
+     {    0.004088E-6,      -7.113547001, 0.060926389},
+     {    0.004433E-6,   77713.771467920, 3.627734103},
+     {    0.003277E-6,   18849.227549974, 2.327912542},
+     {    0.002703E-6,    6062.663207553, 1.271941729},
+     {    0.003435E-6,    -775.522611324, 0.747446224},
+     {    0.002618E-6,    6076.890301554, 3.633715689},
+     {    0.003146E-6,     206.185548437, 5.647874613},
+     {    0.002544E-6,    1577.343542448, 6.232904270},
+     {    0.002218E-6,    -220.412642439, 1.309509946},
+     {    0.002197E-6,    5856.477659115, 2.407212349},
+     {    0.002897E-6,    5753.384884897, 5.863842246},
+     {    0.001766E-6,     426.598190876, 0.754113147},
+     {    0.001738E-6,    -796.298006816, 2.714942671},
+     {    0.001695E-6,     522.577418094, 2.629369842},
+     {    0.001584E-6,    5507.553238667, 1.341138229},
+     {    0.001503E-6,    -242.728603974, 0.377699736},
+     {    0.001552E-6,    -536.804512095, 2.904684667},
+     {    0.001370E-6,    -398.149003408, 1.265599125},
+     {    0.001889E-6,   -5573.142801634, 4.413514859},
+     {    0.001722E-6,    6069.776754553, 2.445966339},
+     {    0.001124E-6,    1059.381930189, 5.041799657},
+     {    0.001258E-6,     553.569402842, 3.849557278},
+     {    0.000831E-6,     951.718406251, 2.471094709},
+     {    0.000767E-6,    4694.002954708, 5.363125422},
+     {    0.000756E-6,    1349.867409659, 1.046195744},
+     {    0.000775E-6,     -11.045700264, 0.245548001},
+     {    0.000597E-6,    2146.165416475, 4.543268798},
+     {    0.000568E-6,    5216.580372801, 4.178853144},
+     {    0.000711E-6,    1748.016413067, 5.934271972},
+     {    0.000499E-6,   12036.460734888, 0.624434410},
+     {    0.000671E-6,   -1194.447010225, 4.136047594},
+     {    0.000488E-6,    5849.364112115, 2.209679987},
+     {    0.000621E-6,    6438.496249426, 4.518860804},
+     {    0.000495E-6,   -6286.598968340, 1.868201275},
+     {    0.000456E-6,    5230.807466803, 1.271231591},
+     {    0.000451E-6,    5088.628839767, 0.084060889},
+     {    0.000435E-6,    5643.178563677, 3.324456609},
+     {    0.000387E-6,   10977.078804699, 4.052488477},
+     {    0.000547E-6,  161000.685737473, 2.841633844},
+     {    0.000522E-6,    3154.687084896, 2.171979966},
+     {    0.000375E-6,    5486.777843175, 4.983027306},
+     {    0.000421E-6,    5863.591206116, 4.546432249},
+     {    0.000439E-6,    7084.896781115, 0.522967921},
+     {    0.000309E-6,    2544.314419883, 3.172606705},
+     {    0.000347E-6,    4690.479836359, 1.479586566},
+     {    0.000317E-6,     801.820931124, 3.553088096},
+     {    0.000262E-6,     419.484643875, 0.606635550},
+     {    0.000248E-6,    6836.645252834, 3.014082064},
+     {    0.000245E-6,   -1592.596013633, 5.519526220},
+     {    0.000225E-6,    4292.330832950, 2.877956536},
+     {    0.000214E-6,    7234.794256242, 1.605227587},
+     {    0.000205E-6,    5767.611978898, 0.625804796},
+     {    0.000180E-6,   10447.387839604, 3.499954526},
+     {    0.000229E-6,     199.072001436, 5.632304604},
+     {    0.000214E-6,     639.897286314, 5.960227667},
+     {    0.000175E-6,    -433.711737877, 2.162417992},
+     {    0.000209E-6,     515.463871093, 2.322150893},
+     {    0.000173E-6,    6040.347246017, 2.556183691},
+     {    0.000184E-6,    6309.374169791, 4.732296790},
+     {    0.000227E-6,  149854.400134205, 5.385812217},
+     {    0.000154E-6,    8031.092263058, 5.120720920},
+     {    0.000151E-6,    5739.157790895, 4.815000443},
+     {    0.000197E-6,    7632.943259650, 0.222827271},
+     {    0.000197E-6,      74.781598567, 3.910456770},
+     {    0.000138E-6,    6055.549660552, 1.397484253},
+     {    0.000149E-6,   -6127.655450557, 5.333727496},
+     {    0.000137E-6,    3894.181829542, 4.281749907},
+     {    0.000135E-6,    9437.762934887, 5.979971885},
+     {    0.000139E-6,   -2352.866153772, 4.715630782},
+     {    0.000142E-6,    6812.766815086, 0.513330157},
+     {    0.000120E-6,   -4705.732307544, 0.194160689},
+     {    0.000131E-6,  -71430.695617928, 0.000379226},
+     {    0.000124E-6,    6279.552731642, 2.122264908},
+     {    0.000108E-6,   -6256.777530192, 0.883445696},
+     {    0.143388E-6,    6283.075849991, 1.131453581},
+     {    0.006671E-6,   12566.151699983, 0.775148887},
+     {    0.001480E-6,     155.420399434, 0.480016880},
+     {    0.000934E-6,     213.299095438, 6.144453084},
+     {    0.000795E-6,     529.690965095, 2.941595619},
+     {    0.000673E-6,    5746.271337896, 0.120415406},
+     {    0.000672E-6,    5760.498431898, 5.317009738},
+     {    0.000389E-6,    -220.412642439, 3.090323467},
+     {    0.000373E-6,    6062.663207553, 3.003551964},
+     {    0.000360E-6,    6076.890301554, 1.918913041},
+     {    0.000316E-6,     -21.340641002, 5.545798121},
+     {    0.000315E-6,    -242.728603974, 1.884932563},
+     {    0.000278E-6,     206.185548437, 1.266254859},
+     {    0.000238E-6,    -536.804512095, 4.532664830},
+     {    0.000185E-6,     522.577418094, 4.578313856},
+     {    0.000245E-6,   18849.227549974, 0.587467082},
+     {    0.000180E-6,     426.598190876, 5.151178553},
+     {    0.000200E-6,     553.569402842, 5.355983739},
+     {    0.000141E-6,    5223.693919802, 1.336556009},
+     {    0.000104E-6,    5856.477659115, 4.239842759},
+     {    0.003826E-6,    6283.075849991, 5.705257275},
+     {    0.000303E-6,   12566.151699983, 5.407132842},
+     {    0.000209E-6,     155.420399434, 1.989815753}
+   };
+
+/* -------------------------------------------------------------------- */
+
+/* Local Variables: */
+   double t, tsol, w, elsun, emsun, d, elj, els, wt, w0, w1, w2, w3, w4,
+          wf, wj;
+   int i;
+
+
+/* Time since J2000.0 in Julian millennia. */
+   t = ( tdb - 51544.5 )/365250;
+
+
+
+/* -------------------- Topocentric terms ----------------------------- */
+
+/* Convert UT1 to local solar time in radians. */
+   tsol = fmod( ut1, 1.0 )*D2PI - wl;
+
+/* FUNDAMENTAL ARGUMENTS:  Simon et al 1994 */
+
+/* Combine time argument (millennia ) with deg/arcsec factor. */
+   w = t / 3600.0;
+
+/* Sun Mean Longitude. */
+   elsun = fmod( 280.46645683 + 1296027711.03429*w, 360.0 )*D2R;
+
+/* Sun Mean Anomaly. */
+   emsun = fmod( 357.52910918 + 1295965810.481*w, 360.0 )*D2R;
+
+/* Mean Elongation of Moon from Sun. */
+   d = fmod( 297.85019547 + 16029616012.090*w, 360.0 )*D2R;
+
+/* Mean Longitude of Jupiter. */
+   elj = fmod( 34.35151874 + 109306899.89453*w, 360.0 )*D2R;
+
+/* Mean Longitude of Saturn. */
+   els = fmod( 50.07744430 + 44046398.47038*w, 360.0 )*D2R;
+
+/* TOPOCENTRIC TERMS:  Moyer 1981 and Murray 1983. */
+   wt =   + 0.00029E-10*u*sin( tsol + elsun - els )
+          + 0.00100E-10*u*sin( tsol - 2*emsun )
+          + 0.00133E-10*u*sin( tsol - d )
+          + 0.00133E-10*u*sin( tsol + elsun - elj )
+          - 0.00229E-10*u*sin( tsol + 2*elsun + emsun )
+          - 0.0220E-10*v*cos( elsun + emsun )
+          + 0.05312E-10*u*sin( tsol - emsun )
+          - 0.13677E-10*u*sin( tsol + 2*elsun )
+          - 1.3184E-10*v*cos( elsun )
+          + 3.17679E-10*u*sin( tsol );
+
+
+
+/* --------------- Fairhead model --------------------------------------- */
+
+/* t**0 */
+   w0 = 0;
+   for( i = 473; i >= 0; i-- ) {
+      w0 = w0 + fairhd[ i ][ 0 ]*sin( fairhd[ i ][ 1 ]*t + fairhd[ i ][ 2 ] );
+   }
+
+/* t**1 */
+   w1 = 0;
+   for( i = 678; i >= 474; i-- ) {
+      w1 = w1 + fairhd[ i ][ 0 ]*sin( fairhd[ i ][ 1 ]*t + fairhd[ i ][ 2 ] );
+   }
+
+/* t**2 */
+   w2 = 0;
+   for( i = 763; i >= 679; i-- ) {
+      w2 = w2 + fairhd[ i ][ 0 ]*sin( fairhd[ i ][ 1 ]*t + fairhd[ i ][ 2 ] );
+   }
+
+/* t**3 */
+   w3 = 0;
+   for( i = 783; i >= 764; i-- ) {
+      w3 = w3 + fairhd[ i ][ 0 ]*sin( fairhd[ i ][ 1 ]*t + fairhd[ i ][ 2 ] );
+   }
+
+/* t**4 */
+   w4 = 0;
+   for( i = 786; i >= 784; i-- ) {
+      w4 = w4 + fairhd[ i ][ 0 ]*sin( fairhd[ i ][ 1 ]*t + fairhd[ i ][ 2 ] );
+   }
+
+/* Multiply by powers of T and combine. */
+   wf = t*( t*( t*( t*w4 + w3 ) + w2 ) + w1 ) + w0;
+
+/* Adjustments to use JPL planetary masses instead of IAU. */
+   wj =    0.00065E-6  * sin(  6069.776754  *t  +  4.021194  )  +
+           0.00033E-6  * sin(   213.299095  *t  +  5.543132  )  +
+        ( -0.00196E-6  * sin( 6208.294251   *t  +  5.696701  ) ) +
+        ( -0.00173E-6  * sin(   74.781599   *t  +  2.435900  ) ) +
+           0.03638E-6*t*t;
+
+
+
+/* -------------------------------------------------------------------- */
+
+/* Final result:  TDB-TT in seconds. */
+   return wt + wf + wj;
+
+}
+
+static void TimeAdd( AstTimeMap *this, const char *cvt, int narg,
+                     const double args[], int *status ) {
+/*
+*++
+*  Name:
+c     astTimeAdd
+f     AST_TIMEADD
+
+*  Purpose:
+*     Add a time coordinate conversion to a TimeMap.
+
+*  Type:
+*     Public virtual function.
+
+*  Synopsis:
+c     #include "timemap.h"
+c     void astTimeAdd( AstTimeMap *this, const char *cvt, int narg,
+c                      const double args[] )
+f     CALL AST_TIMEADD( THIS, CVT, NARG, ARGS, STATUS )
+
+*  Class Membership:
+*     TimeMap method.
+
+*  Description:
+c     This function adds one of the standard time coordinate
+f     This routine adds one of the standard time coordinate
+*     system conversions listed below to an existing TimeMap.
+*
+c     When a TimeMap is first created (using astTimeMap), it simply
+f     When a TimeMap is first created (using AST_TIMEMAP), it simply
+c     performs a unit (null) Mapping. By using astTimeAdd (repeatedly
+f     performs a unit (null) Mapping. By using AST_TIMEADD (repeatedly
+*     if necessary), one or more coordinate conversion steps may then
+*     be added, which the TimeMap will perform in sequence. This allows
+*     multi-step conversions between a variety of time coordinate
+*     systems to be assembled out of the building blocks provided by
+*     this class.
+*
+*     Normally, if a TimeMap's Invert attribute is zero (the default),
+*     then its forward transformation is performed by carrying out
+*     each of the individual coordinate conversions specified by
+c     astTimeAdd in the order given (i.e. with the most recently added
+f     AST_TIMEADD in the order given (i.e. with the most recently added
+*     conversion applied last).
+*
+*     This order is reversed if the TimeMap's Invert attribute is
+*     non-zero (or if the inverse transformation is requested by any
+*     other means) and each individual coordinate conversion is also
+*     replaced by its own inverse. This process inverts the overall
+*     effect of the TimeMap. In this case, the first conversion to be
+*     applied would be the inverse of the one most recently added.
+
+*  Parameters:
+c     this
+f     THIS = INTEGER (Given)
+*        Pointer to the TimeMap.
+c     cvt
+f     CVT = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string which identifies the
+f        A character string which identifies the
+*        time coordinate conversion to be added to the
+*        TimeMap. See the "Available Conversions" section for details of
+*        those available.
+c     narg
+f     NARG = INTEGER (Given)
+*        The number of argument values supplied in the
+c        "args" array.
+f        ARGS array.
+c     args
+f     ARGS( * ) = DOUBLE PRECISION (Given)
+*        An array containing argument values for the time
+*        coordinate conversion. The number of arguments required, and
+*        hence the number of array elements used, depends on the
+*        conversion specified (see the "Available Conversions"
+*        section). This array is ignored
+c        and a NULL pointer may be supplied
+*        if no arguments are needed.
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Notes:
+*     - When assembling a multi-stage conversion, it can sometimes be
+*     difficult to determine the most economical conversion path. A solution
+*     to this is to include all the steps which are (logically) necessary,
+*     but then to use
+c     astSimplify to simplify the resulting
+f     AST_SIMPLIFY to simplify the resulting
+*     TimeMap. The simplification process will eliminate any steps
+*     which turn out not to be needed.
+c     - This function does not check to ensure that the sequence of
+f     - This routine does not check to ensure that the sequence of
+*     coordinate conversions added to a TimeMap is physically
+*     meaningful.
+
+*  Available Conversions:
+*     The following strings (which are case-insensitive) may be supplied
+c     via the "cvt" parameter to indicate which time coordinate
+f     via the CVT argument to indicate which time coordinate
+*     conversion is to be added to the TimeMap. Where arguments are needed by
+*     the conversion, they are listed in parentheses. Values for
+c     these arguments should be given, via the "args" array, in the
+f     these arguments should be given, via the ARGS array, in the
+*     order indicated. Units and argument names are described at the end of
+*     the list of conversions, and "MJD" means Modified Julian Date.
+*
+*     - "MJDTOMJD"  (MJDOFF1,MJDOFF2): Convert MJD from one offset to another.
+*     - "MJDTOJD"  (MJDOFF,JDOFF): Convert MJD to Julian Date.
+*     - "JDTOMJD"  (JDOFF,MJDOFF): Convert Julian Date to MJD.
+*     - "MJDTOBEP" (MJDOFF,BEPOFF): Convert MJD to Besselian epoch.
+*     - "BEPTOMJD" (BEPOFF,MJDOFF): Convert Besselian epoch to MJD.
+*     - "MJDTOJEP" (MJDOFF,JEPOFF): Convert MJD to Julian epoch.
+*     - "JEPTOMJD" (JEPOFF,MJDOFF): Convert Julian epoch to MJD.
+*     - "TAITOUTC" (MJDOFF): Convert a TAI MJD to a UTC MJD.
+*     - "UTCTOTAI" (MJDOFF): Convert a UTC MJD to a TAI MJD.
+*     - "TAITOTT"  (MJDOFF): Convert a TAI MJD to a TT MJD.
+*     - "TTTOTAI"  (MJDOFF): Convert a TT MJD to a TAI MJD.
+*     - "TTTOTDB"  (MJDOFF, OBSLON, OBSLAT, OBSALT): Convert a TT MJD to a TDB MJD.
+*     - "TDBTOTT"  (MJDOFF, OBSLON, OBSLAT, OBSALT): Convert a TDB MJD to a TT MJD.
+*     - "TTTOTCG"  (MJDOFF): Convert a TT MJD to a TCG MJD.
+*     - "TCGTOTT"  (MJDOFF): Convert a TCG MJD to a TT MJD.
+*     - "TDBTOTCB" (MJDOFF): Convert a TDB MJD to a TCB MJD.
+*     - "TCBTOTDB" (MJDOFF): Convert a TCB MJD to a TDB MJD.
+*     - "UTTOGMST" (MJDOFF): Convert a UT MJD to a GMST MJD.
+*     - "GMSTTOUT" (MJDOFF): Convert a GMST MJD to a UT MJD.
+*     - "GMSTTOLMST" (MJDOFF, OBSLON, OBSLAT): Convert a GMST MJD to a LMST MJD.
+*     - "LMSTTOGMST" (MJDOFF, OBSLON, OBSLAT): Convert a LMST MJD to a GMST MJD.
+*     - "LASTTOLMST" (MJDOFF, OBSLON, OBSLAT): Convert a GMST MJD to a LMST MJD.
+*     - "LMSTTOLAST" (MJDOFF, OBSLON, OBSLAT): Convert a LMST MJD to a GMST MJD.
+*     - "UTTOUTC" (DUT1): Convert a UT1 MJD to a UTC MJD.
+*     - "UTCTOUT" (DUT1): Convert a UTC MJD to a UT1 MJD.
+*     - "LTTOUTC" (LTOFF): Convert a Local Time MJD to a UTC MJD.
+*     - "UTCTOLT" (LTOFF): Convert a UTC MJD to a Local Time MJD.
+*
+*     The units for the values processed by the above conversions are as
+*     follows:
+*
+*     - Julian epochs and offsets: Julian years
+*     - Besselian epochs and offsets: Tropical years
+*     - Modified Julian Dates and offsets: days
+*     - Julian Dates and offsets: days
+*
+*     The arguments used in the above conversions are the zero-points
+*     used by the
+c     astTransform function.
+f     AST_TRANSFORM routine.
+*     The axis values supplied and returned by
+c     astTransform
+f     AST_TRANSFORM
+*     are offsets away from these zero-points:
+*
+*     - MJDOFF: The zero-point being used with MJD values.
+*     - JDOFF: The zero-point being used with Julian Date values.
+*     - BEPOFF: The zero-point being used with Besselian epoch values.
+*     - JEPOFF: The zero-point being used with Julian epoch values.
+*     - OBSLON: Observer longitude in radians (+ve westwards).
+*     - OBSLAT: Observer geodetic latitude (IAU 1975) in radians (+ve northwards).
+*     - OBSALT: Observer geodetic altitude (IAU 1975) in metres.
+*     - DUT1: The UT1-UTC value to use.
+*     - LTOFF: The offset between Local Time and UTC (in hours, positive
+*     for time zones east of Greenwich).
+*--
+*/
+
+/* Local Variables: */
+   int cvttype;                  /* Conversion type code */
+
+/* Check the inherited status. */
+   if ( !astOK ) return;
+
+/* Validate the type string supplied and obtain the equivalent
+   conversion type code. */
+   cvttype = CvtCode( cvt, status );
+
+/* If the string was not recognised, then report an error. */
+   if ( astOK && ( cvttype == AST__TIME_NULL ) ) {
+      astError( AST__TIMIN,
+                "%s(%s): Invalid TimeMap time coordinate "
+                "conversion type \"%s\".", status, "astAddTime", astGetClass( this ), cvt );
+   }
+
+/* Add the new conversion to the TimeMap. */
+   AddTimeCvt( this, cvttype, narg, args, status );
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a TimeMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     TimeMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a TimeMap and a set of points encapsulated
+*     in a PointSet and transforms the points so as to perform the
+*     sequence of time coordinate conversions specified by
+*     previous invocations of astTimeAdd.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the TimeMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstTimeMap *map;              /* Pointer to TimeMap to be applied */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *args;                 /* Pointer to argument list for conversion */
+   double *time;                 /* Pointer to output time axis value array */
+   double gmstx;                 /* GMST offset (in days) */
+   double tai;                   /* Absolute TAI value (in days) */
+   double tdb;                   /* Absolute TDB value (in days) */
+   double tt;                    /* Absolute TT value (in days) */
+   double utc;                   /* Absolute UTC value (in days) */
+   int ct;                       /* Conversion type */
+   int cvt;                      /* Loop counter for conversions */
+   int end;                      /* Termination index for conversion loop */
+   int inc;                      /* Increment for conversion loop */
+   int npoint;                   /* Number of points */
+   int point;                    /* Loop counter for points */
+   int start;                    /* Starting index for conversion loop */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the TimeMap. */
+   map = (AstTimeMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   coordinate conversions needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse transformation, according
+   to the direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( this ) ) forward = !forward;
+
+/* Transform the coordinate values. */
+/* -------------------------------- */
+/* Use "time" as a synonym for the array of time axis values stored in
+   the output PointSet. */
+   if ( astOK ) {
+      time = ptr_out[ 0 ];
+
+/* Initialise the output coordinate values by copying the input ones. */
+      if( time != ptr_in[ 0 ] ) {
+         (void) memcpy( time, ptr_in[ 0 ], sizeof( double ) * (size_t) npoint );
+      }
+
+/* We will loop to apply each time coordinate conversion in turn to the
+   (time) array. However, if the inverse transformation was requested,
+   we must loop through these transformations in reverse order, so set up
+   appropriate limits and an increment to control this loop. */
+      start = forward ? 0 : map->ncvt - 1;
+      end = forward ? map->ncvt : -1;
+      inc = forward ? 1 : -1;
+
+/* Loop through the coordinate conversions in the required order and obtain a
+   pointer to the argument list for the current conversion. */
+      for ( cvt = start; cvt != end; cvt += inc ) {
+         args = map->cvtargs[ cvt ];
+
+/* Classify the SLALIB sky coordinate conversion to be applied. */
+         ct = map->cvttype[ cvt ];
+         switch ( ct ) {
+
+/* MJD to MJD. */
+/* ---------- */
+            case AST__MJDTOMJD:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += args[ 2 ];
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= args[ 2 ];
+                     }
+                  }
+               }
+               break;
+
+/* MJD to JD. */
+/* ---------- */
+            case AST__MJDTOJD:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += args[ 2 ];
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= args[ 2 ];
+                     }
+                  }
+               }
+               break;
+
+/* JD to MJD. */
+/* ---------- */
+            case AST__JDTOMJD:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += args[ 2 ];
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= args[ 2 ];
+                     }
+                  }
+               }
+               break;
+
+/* MJD to Besselian epoch. */
+/* ----------------------- */
+            case AST__MJDTOBEP:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = palEpb( time[ point ] ) + args[ 2 ];
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = palEpb2d( time[ point ] ) + args[ 3 ];
+                     }
+                  }
+               }
+               break;
+
+/* Besselian epoch to MJD. */
+/* ----------------------- */
+            case AST__BEPTOMJD:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = palEpb2d( time[ point ] ) + args[ 2 ];
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = palEpb( time[ point ] ) + args[ 3 ];
+                     }
+                  }
+               }
+               break;
+
+/* MJD to Julian epoch. */
+/* -------------------- */
+            case AST__MJDTOJEP:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = palEpj( time[ point ] ) + args[ 2 ];
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = palEpj2d( time[ point ] ) + args[ 3 ];
+                     }
+                  }
+               }
+               break;
+
+/* Julian epoch to MJD. */
+/* -------------------- */
+            case AST__JEPTOMJD:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = palEpj2d( time[ point ] ) + args[ 2 ];
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = palEpj( time[ point ] ) + args[ 3 ];
+                     }
+                  }
+               }
+               break;
+
+/* TAI to UTC. */
+/* ----------- */
+            case AST__TAITOUTC:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += astDat( time[ point ] +
+                                              args[ 0 ], 0 )/SPD;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += astDat( time[ point ] +
+                                              args[ 0 ], 1 )/SPD;
+                     }
+                  }
+               }
+               break;
+
+/* UTC to TAI. */
+/* ----------- */
+            case AST__UTCTOTAI:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += astDat( time[ point ] +
+                                              args[ 0 ], 1 )/SPD;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += astDat( time[ point ] +
+                                              args[ 0 ], 0 )/SPD;
+                     }
+                  }
+               }
+               break;
+
+/* TAI to TT. */
+/* ---------- */
+            case AST__TAITOTT:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += (TTOFF/SPD);
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= (TTOFF/SPD);
+                     }
+                  }
+               }
+               break;
+
+/* TT to TAI. */
+/* ---------- */
+            case AST__TTTOTAI:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= (TTOFF/SPD);
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += (TTOFF/SPD);
+                     }
+                  }
+               }
+               break;
+
+/* TT to TDB. */
+/* ---------- */
+/* For the purpose of estimating TDB-TT, we assume UTC is a good approximation
+   to UT1, and that TT is a good approximation to TDB. */
+            case AST__TTTOTDB:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        tt = time[ point ] + args[ 0 ];
+                        tai = tt - (TTOFF/SPD);
+                        utc = tai + astDat( tai, 0 )/SPD;
+                        time[ point ] += Rcc( tt, utc, args[ 1 ], args[ 4 ],
+                                              args[ 5 ], status )/SPD;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        tdb = time[ point ] + args[ 0 ];
+                        tai = tdb - (TTOFF/SPD);
+                        utc = tai + astDat( tai, 0 )/SPD;
+                        time[ point ] -= Rcc( tdb, utc, args[ 1 ], args[ 4 ],
+                                                args[ 5 ], status )/SPD;
+                     }
+                  }
+               }
+               break;
+
+/* TDB to TT. */
+/* ---------- */
+/* For the purpose of estimating TDB-TT, we assume UTC is a good approximation
+   to UT1, and that TT is a good approximation to TDB. */
+            case AST__TDBTOTT:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        tdb = time[ point ] + args[ 0 ];
+                        tai = tdb - (TTOFF/SPD);
+                        utc = tai + astDat( tai, 0 )/SPD;
+                        time[ point ] -= Rcc( tdb, utc, args[ 1 ], args[ 4 ],
+                                                args[ 5 ], status )/SPD;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        tt = time[ point ] + args[ 0 ];
+                        tai = tt - (TTOFF/SPD);
+                        utc = tai + astDat( tai, 0 )/SPD;
+                        time[ point ] += Rcc( tt, utc, args[ 1 ], args[ 4 ],
+                                              args[ 5 ], status )/SPD;
+                     }
+                  }
+               }
+               break;
+
+/* TT to TCG. */
+/* ---------- */
+            case AST__TTTOTCG:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += time[ point ]*LG + args[ 1 ];
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = ( time[ point ] - args[ 1 ] ) /
+                                         ( 1.0 + LG );
+                     }
+                  }
+               }
+               break;
+
+/* TCG to TT. */
+/* ---------- */
+            case AST__TCGTOTT:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = ( time[ point ] - args[ 1 ] ) /
+                                         ( 1.0 + LG );
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += time[ point ]*LG + args[ 1 ];
+                     }
+                  }
+               }
+               break;
+
+/* TDB to TCB. */
+/* ----------- */
+/* For the purpose of estimating TDB-TT, we assume UTC is a good approximation
+   to UT1, and that TT is a good approximation to both TDB and TCB. */
+            case AST__TDBTOTCB:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += time[ point ]*LB + args[ 1 ];
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = ( time[ point ] - args[ 1 ] ) /
+                                         ( 1.0 + LB );
+                     }
+                  }
+               }
+               break;
+
+/* TCB to TDB. */
+/* ----------- */
+/* For the purpose of estimating TDB-TT, we assume UTC is a good approximation
+   to UT1, and that TT is a good approximation to TDB. */
+            case AST__TCBTOTDB:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = ( time[ point ] - args[ 1 ] ) /
+                                         ( 1.0 + LB );
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += time[ point ]*LB + args[ 1 ];
+                     }
+                  }
+               }
+               break;
+
+/* UT to GMST . */
+/* ------------ */
+            case AST__UTTOGMST:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = Gmsta( time[ point ], args[ 0 ], 1, status );
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = Gmsta( time[ point ], args[ 0 ], 0, status );
+                     }
+                  }
+               }
+               break;
+
+/* GMST to UT. */
+/* ----------- */
+            case AST__GMSTTOUT:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = Gmsta( time[ point ], args[ 0 ], 0, status );
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] = Gmsta( time[ point ], args[ 0 ], 1, status );
+                     }
+                  }
+               }
+               break;
+
+/* GMST to LMST. */
+/* ------------- */
+            case AST__GMSTTOLMST:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= args[ 1 ]/D2PI;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += args[ 1 ]/D2PI;
+                     }
+                  }
+               }
+               break;
+
+/* LMST to GMST. */
+/* ------------- */
+            case AST__LMSTTOGMST:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += args[ 1 ]/D2PI;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= args[ 1 ]/D2PI;
+                     }
+                  }
+               }
+               break;
+
+/* UT1 to UTC. */
+/* ------------- */
+            case AST__UTTOUTC:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= args[ 0 ]/86400.0;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += args[ 0 ]/86400.0;
+                     }
+                  }
+               }
+               break;
+
+
+/* UTC to UT1. */
+/* ------------- */
+            case AST__UTCTOUT:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += args[ 0 ]/86400.0;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= args[ 0 ]/86400.0;
+                     }
+                  }
+               }
+               break;
+
+/* LT to UTC. */
+/* ---------- */
+            case AST__LTTOUTC:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= args[ 0 ]/24.0;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += args[ 0 ]/24.0;
+                     }
+                  }
+               }
+               break;
+
+
+/* UTC to LT. */
+/* ---------- */
+            case AST__UTCTOLT:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] += args[ 0 ]/24.0;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        time[ point ] -= args[ 0 ]/24.0;
+                     }
+                  }
+               }
+               break;
+
+/* LMST to LAST. */
+/* ------------- */
+/* Calculating the equation of the equinoxes required TDB. So we need to
+   convert the given LMST to TDB. We first convert LMST to UT1. UT1 is
+   equal to UTC to within 1 second. We then add on 32 seconds to get TAI
+   (this value is correct since 1999 - for earlier epochs an error of the
+   order of a minute will be introduced in the TAI value). We then add on
+   TTOFF seconds to get TT. This TT is then used as an approximation to
+   TDB. The total error in TDB is of the order of a few minutes, which
+   corresponds to an error of a few tens of microseconds in the equation of
+   the equinoxes. The sla precession-nutation model is accurate to around 3
+   mas = 200 us, so the error in TDB will be insignificant. */
+            case AST__LMSTTOLAST:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        gmstx = time[ point ] + args[ 1 ]/D2PI;
+                        tdb = Gmsta( gmstx, args[ 0 ], 0, status )
+                              + args[ 0 ] + (32 + TTOFF)/SPD;
+                        time[ point ] += palEqeqx( tdb )/D2PI;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        gmstx = time[ point ] + args[ 1 ]/D2PI;
+                        tdb = Gmsta( gmstx, args[ 0 ], 0, status )
+                              + args[ 0 ] + (32+TTOFF)/SPD;
+                        time[ point ] -= palEqeqx( tdb )/D2PI;
+                     }
+                  }
+               }
+               break;
+
+/* LAST to LMST. */
+/* ------------- */
+            case AST__LASTTOLMST:
+               if ( forward ) {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        gmstx = time[ point ] + args[ 1 ]/D2PI;
+                        tdb = Gmsta( gmstx, args[ 0 ], 0, status )
+                              + args[ 0 ] + (32+TTOFF)/SPD;
+                        time[ point ] -= palEqeqx( tdb )/D2PI;
+                     }
+                  }
+               } else {
+                  for ( point = 0; point < npoint; point++ ) {
+                     if ( time[ point ] != AST__BAD ) {
+                        gmstx = time[ point ] + args[ 1 ]/D2PI;
+                        tdb = Gmsta( gmstx, args[ 0 ], 0, status )
+                              + args[ 0 ] + (32 + TTOFF)/SPD;
+                        time[ point ] += palEqeqx( tdb )/D2PI;
+                     }
+                  }
+               }
+
+         }
+      }
+   }
+
+/* If an error has occurred and a new PointSet may have been created, then
+   clean up by annulling it. In any case, ensure that a NULL result is
+   returned.*/
+   if ( !astOK ) {
+      if ( !out ) result = astAnnul( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for TimeMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for TimeMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstTimeMap *in;                /* Pointer to input TimeMap */
+   AstTimeMap *out;               /* Pointer to output TimeMap */
+   int cvt;                       /* Loop counter for coordinate conversions */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output TimeMap structures. */
+   in = (AstTimeMap *) objin;
+   out = (AstTimeMap *) objout;
+
+/* For safety, first clear any references to the input memory from the output
+   TimeMap. */
+   out->cvtargs = NULL;
+   out->cvttype = NULL;
+
+/* Allocate memory for the output array of argument list pointers. */
+   out->cvtargs = astMalloc( sizeof( double * ) * (size_t) in->ncvt );
+
+/* If necessary, allocate memory and make a copy of the input array of
+   coordinate conversion codes. */
+   if ( in->cvttype ) out->cvttype = astStore( NULL, in->cvttype,
+                                               sizeof( int )
+                                               * (size_t) in->ncvt );
+
+/* If OK, loop through each conversion in the input TimeMap and make a copy of
+   its argument list, storing the new pointer in the output argument list
+   array. */
+   if ( astOK ) {
+      for ( cvt = 0; cvt < in->ncvt; cvt++ ) {
+         out->cvtargs[ cvt ] = astStore( NULL, in->cvtargs[ cvt ],
+                                         astSizeOf( in->cvtargs[ cvt ] ) );
+      }
+
+/* If an error occurred while copying the argument lists, loop through the
+   conversions again and clean up by ensuring that the new memory allocated for
+   each argument list is freed. */
+      if ( !astOK ) {
+         for ( cvt = 0; cvt < in->ncvt; cvt++ ) {
+            out->cvtargs[ cvt ] = astFree( out->cvtargs[ cvt ] );
+	 }
+      }
+   }
+
+/* If an error occurred, free all other memory allocated above. */
+   if ( !astOK ) {
+      out->cvtargs = astFree( out->cvtargs );
+      out->cvttype = astFree( out->cvttype );
+   }
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for TimeMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for TimeMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstTimeMap *this;              /* Pointer to TimeMap */
+   int cvt;                       /* Loop counter for coordinate conversions */
+
+/* Obtain a pointer to the TimeMap structure. */
+   this = (AstTimeMap *) obj;
+
+/* Loop to free the memory containing the argument list for each coordinate
+   conversion. */
+   for ( cvt = 0; cvt < this->ncvt; cvt++ ) {
+      this->cvtargs[ cvt ] = astFree( this->cvtargs[ cvt ] );
+   }
+
+/* Free the memory holding the array of conversion types and the array of
+   argument list pointers. */
+   this->cvtargs = astFree( this->cvtargs );
+   this->cvttype = astFree( this->cvttype );
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for TimeMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the TimeMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the TimeMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstTimeMap *this;             /* Pointer to the TimeMap structure */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword string */
+   const char *argdesc[ MAX_ARGS ]; /* Pointers to argument descriptions */
+   const char *comment;          /* Pointer to comment string */
+   const char *sval;             /* Pointer to string value */
+   int iarg;                     /* Loop counter for arguments */
+   int icvt;                     /* Loop counter for conversion steps */
+   int ival;                     /* Integer value */
+   int nargs;                    /* Number of user-supplied arguments */
+   int szargs;                   /* Number of stored arguments */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the TimeMap structure. */
+   this = (AstTimeMap *) this_object;
+
+/* Write out values representing the instance variables for the TimeMap
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Number of conversion steps. */
+/* --------------------------- */
+/* Regard this as "set" if it is non-zero. */
+   ival = this->ncvt;
+   set = ( ival != 0 );
+   astWriteInt( channel, "Ntime", set, 0, ival, "Number of conversion steps" );
+
+/* Write out data for each conversion step... */
+   for ( icvt = 0; icvt < this->ncvt; icvt++ ) {
+
+/* Conversion type. */
+/* ---------------- */
+/* Change each conversion type code into an equivalent string and
+   obtain associated descriptive information. If the conversion code
+   was not recognised, report an error and give up. */
+      if ( astOK ) {
+         sval = CvtString( this->cvttype[ icvt ], &comment,
+                           &nargs, &szargs, argdesc, status );
+         if ( astOK && !sval ) {
+            astError( AST__TIMIN,
+                      "astWrite(%s): Corrupt %s contains invalid TimeMap "
+                      "time coordinate conversion code (%d).", status,
+                      astGetClass( channel ), astGetClass( this ),
+                      (int) this->cvttype[ icvt ] );
+            break;
+         }
+
+/* Create an appropriate keyword and write out the conversion code
+   information. */
+         (void) sprintf( key, "Time%d", icvt + 1 );
+         astWriteString( channel, key, 1, 1, sval, comment );
+
+/* Write out data for each conversion argument... */
+         for ( iarg = 0; iarg < szargs; iarg++ ) {
+
+/* Arguments. */
+/* ---------- */
+/* Create an appropriate keyword and write out the argument value,
+   accompanied by the descriptive comment obtained above. */
+            if( this->cvtargs[ icvt ][ iarg ] != AST__BAD ) {
+               (void) sprintf( key, "Time%d%c", icvt + 1, ALPHABET[ iarg ] );
+               astWriteDouble( channel, key, 1, 1, this->cvtargs[ icvt ][ iarg ],
+                               argdesc[ iarg ] );
+            }
+         }
+
+/* Quit looping if an error occurs. */
+         if ( !astOK ) break;
+      }
+   }
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsATimeMap and astCheckTimeMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(TimeMap,Mapping)
+astMAKE_CHECK(TimeMap)
+
+AstTimeMap *astTimeMap_( int flags, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astTimeMap
+f     AST_TIMEMAP
+
+*  Purpose:
+*     Create a TimeMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "timemap.h"
+c     AstTimeMap *astTimeMap( int flags, const char *options, ... )
+f     RESULT = AST_TIMEMAP( FLAGS, OPTIONS, STATUS )
+
+*  Class Membership:
+*     TimeMap constructor.
+
+*  Description:
+*     This function creates a new TimeMap and optionally initialises
+*     its attributes.
+*
+*     A TimeMap is a specialised form of 1-dimensional Mapping which can be
+*     used to represent a sequence of conversions between standard time
+*     coordinate systems.
+*
+*     When a TimeMap is first created, it simply performs a unit
+c     (null) Mapping. Using the astTimeAdd
+f     (null) Mapping. Using the AST_TIMEADD
+c     function, a series of coordinate conversion steps may then be
+f     routine, a series of coordinate conversion steps may then be
+*     added. This allows multi-step conversions between a variety of
+*     time coordinate systems to be assembled out of a set of building
+*     blocks.
+*
+*     For details of the individual coordinate conversions available,
+c     see the description of the astTimeAdd function.
+f     see the description of the AST_TIMEADD routine.
+
+*  Parameters:
+c     flags
+f     FLAGS = INTEGER (Given)
+c        This parameter is reserved for future use and should currently
+f        This argument is reserved for future use and should currently
+*        always be set to zero.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new TimeMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+c        If no initialisation is required, a zero-length string may be
+c        supplied.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new TimeMap. The syntax used is identical to that for the
+f        AST_SET routine. If no initialisation is required, a blank
+f        value may be supplied.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astTimeMap()
+f     AST_TIMEMAP = INTEGER
+*        A pointer to the new TimeMap.
+
+*  Notes:
+*     - The nature and units of the coordinate values supplied for the
+*     first input (i.e. the time input) of a TimeMap must be appropriate
+*     to the first conversion step applied by the TimeMap. For instance, if
+*     the first conversion step is "MJDTOBEP" (Modified Julian Date to
+*     Besselian epoch) then the coordinate values for the first input should
+*     be date in units of days. Similarly, the nature and units of the
+*     coordinate values returned by a TimeMap will be determined by the
+*     last conversion step applied by the TimeMap.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstTimeMap *new;              /* Pointer to the new TimeMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the TimeMap, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitTimeMap( NULL, sizeof( AstTimeMap ), !class_init, &class_vtab,
+                        "TimeMap", flags );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new TimeMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new TimeMap. */
+   return new;
+}
+
+AstTimeMap *astTimeMapId_( int flags, const char *options, ... ) {
+/*
+*  Name:
+*     astTimeMapId_
+
+*  Purpose:
+*     Create a TimeMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     AstTimeMap *astTimeMapId_( int flags, const char *options, ... )
+
+*  Class Membership:
+*     TimeMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astTimeMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astTimeMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astTimeMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astTimeMap_.
+
+*  Returned Value:
+*     The ID value associated with the new TimeMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstTimeMap *new;              /* Pointer to the new TimeMap */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the TimeMap, allocating memory and initialising the virtual
+   function table as well if necessary. */
+   new = astInitTimeMap( NULL, sizeof( AstTimeMap ), !class_init, &class_vtab,
+                        "TimeMap", flags );
+
+/* If successful, note that the virtual function table has been initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new TimeMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new TimeMap. */
+   return astMakeId( new );
+}
+
+AstTimeMap *astInitTimeMap_( void *mem, size_t size, int init,
+                             AstTimeMapVtab *vtab, const char *name,
+                             int flags, int *status ) {
+/*
+*+
+*  Name:
+*     astInitTimeMap
+
+*  Purpose:
+*     Initialise a TimeMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     AstTimeMap *astInitTimeMap( void *mem, size_t size, int init,
+*                               AstTimeMapVtab *vtab, const char *name,
+*                               int flags )
+
+*  Class Membership:
+*     TimeMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new TimeMap object. It allocates memory (if necessary) to accommodate
+*     the TimeMap plus any additional data associated with the derived class.
+*     It then initialises a TimeMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a TimeMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the TimeMap is to be initialised.
+*        This must be of sufficient size to accommodate the TimeMap data
+*        (sizeof(TimeMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the TimeMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the TimeMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the TimeMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new TimeMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astClass
+*        method).
+*     flags
+*        This parameter is reserved for future use. It is currently ignored.
+
+*  Returned Value:
+*     A pointer to the new TimeMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstTimeMap *new;               /* Pointer to the new TimeMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitTimeMapVtab( vtab, name );
+
+/* Initialise a 1D Mapping structure (the parent class) as the first component
+   within the TimeMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstTimeMap *) astInitMapping( mem, size, 0,
+                                       (AstMappingVtab *) vtab, name,
+                                       1, 1, 1, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the TimeMap data. */
+/* --------------------------- */
+/* The initial state is with no conversions set, in which condition the
+   TimeMap simply implements a unit mapping. */
+      new->ncvt = 0;
+      new->cvtargs = NULL;
+      new->cvttype = NULL;
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstTimeMap *astLoadTimeMap_( void *mem, size_t size,
+                           AstTimeMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadTimeMap
+
+*  Purpose:
+*     Load a TimeMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "timemap.h"
+*     AstTimeMap *astLoadTimeMap( void *mem, size_t size,
+*                               AstTimeMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     TimeMap loader.
+
+*  Description:
+*     This function is provided to load a new TimeMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     TimeMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a TimeMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the TimeMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        TimeMap data (sizeof(TimeMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the TimeMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the TimeMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstTimeMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new TimeMap. If this is NULL, a pointer to
+*        the (static) virtual function table for the TimeMap class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "TimeMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new TimeMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstTimeMap *new;              /* Pointer to the new TimeMap */
+   char *sval;                   /* Pointer to string value */
+   char key[ KEY_LEN + 1 ];      /* Buffer for keyword string */
+   const char *argdesc[ MAX_ARGS ]; /* Pointers to argument descriptions */
+   const char *comment;          /* Pointer to comment string */
+   int iarg;                     /* Loop counter for arguments */
+   int icvt;                     /* Loop counter for conversion steps */
+   int nargs;                    /* Number of user-supplied arguments */
+   int szargs;                   /* Number of stored arguments */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this TimeMap. In this case the
+   TimeMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstTimeMap );
+      vtab = &class_vtab;
+      name = "TimeMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitTimeMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built TimeMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "TimeMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Number of conversion steps. */
+/* --------------------------- */
+/* Read the number of conversion steps and allocate memory to hold
+   data for each step. */
+      new->ncvt = astReadInt( channel, "ntime", 0 );
+      if ( new->ncvt < 0 ) new->ncvt = 0;
+      new->cvttype = astMalloc( sizeof( int ) * (size_t) new->ncvt );
+      new->cvtargs = astMalloc( sizeof( double * ) * (size_t) new->ncvt );
+
+/* If an error occurred, ensure that all allocated memory is freed. */
+      if ( !astOK ) {
+         new->cvttype = astFree( new->cvttype );
+         new->cvtargs = astFree( new->cvtargs );
+
+/* Otherwise, initialise the argument pointer array. */
+      } else {
+         for ( icvt = 0; icvt < new->ncvt; icvt++ ) {
+            new->cvtargs[ icvt ] = NULL;
+         }
+
+/* Read in data for each conversion step... */
+         for ( icvt = 0; icvt < new->ncvt; icvt++ ) {
+
+/* Conversion type. */
+/* ---------------- */
+/* Create an appropriate keyword and read the string representation of
+   the conversion type. */
+            (void) sprintf( key, "time%d", icvt + 1 );
+            sval = astReadString( channel, key, NULL );
+
+/* If no value was read, report an error. */
+            if ( astOK ) {
+               if ( !sval ) {
+                  astError( AST__BADIN,
+                            "astRead(%s): A time coordinate conversion "
+                            "type is missing from the input TimeMap data.", status,
+                            astGetClass( channel ) );
+
+/* Otherwise, convert the string representation into the required
+   conversion type code. */
+               } else {
+                  new->cvttype[ icvt ] = CvtCode( sval, status );
+
+/* If the string was not recognised, report an error. */
+                  if ( new->cvttype[ icvt ] == AST__TIME_NULL ) {
+                     astError( AST__BADIN,
+                              "astRead(%s): Invalid time conversion "
+                              "type \"%s\" in TimeMap data.", status,
+                              astGetClass( channel ), sval );
+                  }
+               }
+
+/* Free the memory holding the string value. */
+               sval = astFree( sval );
+            }
+
+/* Obtain the number of arguments associated with the conversion and
+   allocate memory to hold them. */
+            (void) CvtString( new->cvttype[ icvt ], &comment,
+                              &nargs, &szargs, argdesc, status );
+            new->cvtargs[ icvt ] = astMalloc( sizeof( double ) *
+                                              (size_t) szargs );
+
+/* Read in data for each argument... */
+            if ( astOK ) {
+               for ( iarg = 0; iarg < szargs; iarg++ ) {
+
+/* Arguments. */
+/* ---------- */
+/* Create an appropriate keyword and read each argument value. */
+                  (void) sprintf( key, "time%d%c", icvt + 1, ALPHABET[ iarg ] );
+                  new->cvtargs[ icvt ][ iarg ] = astReadDouble( channel, key,
+                                                                AST__BAD );
+               }
+            }
+
+/* Quit looping if an error occurs. */
+            if ( !astOK ) break;
+         }
+      }
+
+/* If an error occurred, clean up by deleting the new TimeMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new TimeMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+void astTimeAdd_( AstTimeMap *this, const char *cvt, int narg, const double args[],
+                  int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,TimeMap,TimeAdd))( this, cvt, narg, args, status );
+}
+
+
+
+
diff --git a/ast/timemap.h b/ast/timemap.h
new file mode 100644
index 0000000..fa24730
--- /dev/null
+++ b/ast/timemap.h
@@ -0,0 +1,285 @@
+#if !defined( TIMEMAP_INCLUDED )  /* Include this file only once */
+#define TIMEMAP_INCLUDED
+/*
+*+
+*  Name:
+*     timemap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the TimeMap class.
+
+*  Invocation:
+*     #include "timemap.h"
+
+*  Description:
+*     This include file defines the interface to the TimeMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The TimeMap class encapsulates various time coordinate
+*     conversions. Since, typically, a sequence of these conversions is
+*     required, a TimeMap can be used to accumulate a series of conversions
+*     which it then applies in sequence.
+
+*  Inheritance:
+*     The TimeMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        astTransform
+*           Use an TimeMap to transform a set of points.
+
+*     Protected:
+*        astMapMerge
+*           Simplify a sequence of Mappings containing an TimeMap.
+
+*  New Methods Defined:
+*     Public:
+*        astTimeAdd
+*           Add a coordinate conversion step to an TimeMap.
+
+*     Private:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsATimeMap
+*           Test class membership.
+*        astTimeMap
+*           Create an TimeMap.
+
+*     Protected:
+*        astCheckTimeMap
+*           Validate class membership.
+*        astInitTimeMap
+*           Initialise an TimeMap.
+*        astLoadTimeMap
+*           Load an TimeMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstTimeMap
+*           TimeMap object type.
+
+*     Protected:
+*        AstTimeMapVtab
+*           TimeMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     24-MAY-2005 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* TimeMap structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstTimeMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int *cvttype;                 /* Pointer to array of conversion types */
+   double **cvtargs;             /* Pointer to argument list pointer array */
+   int ncvt;                     /* Number of conversions to perform */
+} AstTimeMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstTimeMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   void (* TimeAdd)( AstTimeMap *, const char *, int, const double[], int * );
+} AstTimeMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstTimeMapGlobals {
+   AstTimeMapVtab Class_Vtab;
+   int Class_Init;
+} AstTimeMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitTimeMapGlobals_( AstTimeMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(TimeMap)           /* Check class membership */
+astPROTO_ISA(TimeMap)             /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstTimeMap *astTimeMap_( int, const char *, int *, ...);
+#else
+AstTimeMap *astTimeMapId_( int, const char *, ... )__attribute__((format(printf,2,3)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstTimeMap *astInitTimeMap_( void *, size_t, int, AstTimeMapVtab *,
+                             const char *, int, int * );
+
+/* Vtab initialiser. */
+void astInitTimeMapVtab_( AstTimeMapVtab *, const char *, int * );
+
+/* Loader. */
+AstTimeMap *astLoadTimeMap_( void *, size_t, AstTimeMapVtab *,
+                             const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+void astTimeAdd_( AstTimeMap *, const char *, int, const double[], int * );
+
+#if defined(astCLASS)            /* Protected. */
+double astDat_( double, int, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckTimeMap(this) astINVOKE_CHECK(TimeMap,this,0)
+#define astVerifyTimeMap(this) astINVOKE_CHECK(TimeMap,this,1)
+
+/* Test class membership. */
+#define astIsATimeMap(this) astINVOKE_ISA(TimeMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astTimeMap astINVOKE(F,astTimeMap_)
+#else
+#define astTimeMap astINVOKE(F,astTimeMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitTimeMap(mem,size,init,vtab,name,flags) \
+astINVOKE(O,astInitTimeMap_(mem,size,init,vtab,name,flags,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitTimeMapVtab(vtab,name) astINVOKE(V,astInitTimeMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadTimeMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadTimeMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckTimeMap to validate TimeMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+#define astTimeAdd(this,cvt,narg,args) \
+astINVOKE(V,astTimeAdd_(astCheckTimeMap(this),cvt,narg,args,STATUS_PTR))
+
+#if defined(astCLASS)            /* Protected */
+#define astDat(in,forward) astDat_(in,forward,STATUS_PTR)
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/tpn.c b/ast/tpn.c
new file mode 100644
index 0000000..0d30503
--- /dev/null
+++ b/ast/tpn.c
@@ -0,0 +1,393 @@
+#include <math.h>
+#include <stdlib.h>
+#include "wcsmath.h"
+#include "wcstrig.h"
+#include "proj.h"
+
+#define icopysign(X, Y) ((Y) < 0.0 ? -abs(X) : abs(X))
+#define TPN 999
+
+/*============================================================================
+*   TAN: gnomonic projection, with correction terms.
+*
+*   This projection is no longer part of the FITSWCS standard, but is
+*   retained here for use b the AST library as a means of implementing
+*   the IRAF TNX projection, and the DSS encoding.
+*
+*   Given and/or returned:
+*      prj->p       Array of latitude coefficients
+*      prj->p2      Array of longitude coefficients
+*      prj->flag    TPN, or -TPN if prj->flag is given < 0.
+*      prj->r0      r0; reset to 180/pi if 0.
+*      prj->n       If zero, only do poly part of transformation (i.e. omit
+*                   the TAN projection).
+*
+*   Returned:
+*      prj->code    "TPN"
+*      prj->phi0     0.0
+*      prj->theta0  90.0
+*      prj->astPRJfwd  Pointer to astTPNfwd().
+*      prj->astPRJrev  Pointer to astTPNrev().
+*      prj->w[ 0 ]     Set to 0.0 if a simple tan projection is required
+*                      (with no polynomial correction). Otherwise, set to 1.0.
+*===========================================================================*/
+
+int astTPNset(prj)
+
+struct AstPrjPrm *prj;
+
+{
+   int m;
+
+   prj->flag   = icopysign(TPN, prj->flag);
+   prj->phi0   =  0.0;
+   prj->theta0 = 90.0;
+
+   if (prj->r0 == 0.0) prj->r0 = R2D;
+
+   prj->astPRJfwd = astTPNfwd;
+   prj->astPRJrev = astTPNrev;
+
+/* If all co-efficients have their "unit" values, we do not need to
+   use the polynomial correction. */
+   prj->w[ 0 ] = 0.0;
+
+   if( prj->p[ 0 ] != 0.0 || prj->p2[ 0 ] != 0.0 ) {
+      prj->w[ 0 ] = 1.0;
+
+   } else if( prj->p[ 1 ] != 1.0 || prj->p2[ 1 ] != 1.0 ) {
+      prj->w[ 0 ] = 1.0;
+
+   } else {
+      for( m = 2; m < WCSLIB_MXPAR; m++ ){
+         if( prj->p[ m ] != 0.0 || prj->p2[ m ] != 0.0 ){
+            prj->w[ 0 ] = 1.0;
+            break;
+         }
+      }
+   }
+
+   return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astTPNfwd(phi, theta, prj, xx, yy)
+
+const double phi, theta;
+double *xx, *yy;
+struct AstPrjPrm *prj;
+
+{
+   double r, xi, eta, x2, xy, y2, r2, x3, x2y, xy2, y3, r3, x4, x3y,
+          x2y2, xy3, y4, x5, x4y, x3y2, x2y3, xy4, y5, r5, x6, x5y, x4y2,
+	  x3y3, x2y4, xy5, y6, x7, x6y, x5y2, x4y3, x3y4, x2y5, xy6, y7,
+	  r7, tol, f, g, fx, fy, gx, gy, dx, dy, x, y, denom;
+   double *a, *b;
+   int i, ok;
+
+   if (abs(prj->flag) != TPN ) {
+      if (astTPNset(prj)) return 1;
+   }
+
+   if( prj->n ) {
+      double s = astSind(theta);
+      if (prj->flag > 0 && s < 0.0) {
+         return 2;
+      }
+      r =  prj->r0*astCosd(theta)/s;
+      xi =  r*astSind(phi);
+      eta = -r*astCosd(phi);
+   } else {
+      xi = phi;
+      eta = theta;
+   }
+
+   /* Simple tan */
+   if( prj->w[ 0 ] == 0.0 ){
+      *xx = xi;
+      *yy = eta;
+
+   /* Tan with polynomial corrections: Iterate using Newton's method to
+      get the (x,y) corresponding to the above (xi,eta). */
+   } else {
+      a = prj->p2;
+      b = prj->p;
+
+   /* Initial guess: linear solution assuming a3,... and b3,... are zero. */
+      denom = a[1]*b[1] - a[2]*b[2];
+      if( denom != 0.0 ) {
+         x = ( xi*b[1] - eta*a[2] - a[0]*b[1] + b[0]*a[2] )/denom;
+         y = -( xi*b[2] - eta*a[1] - a[0]*b[2] + b[0]*a[1] )/denom;
+
+      } else {
+         if( a[1] != 0.0 ){
+            x = ( xi - a[0] )/a[1];
+         } else {
+            x = a[0];
+         }
+
+         if( b[1] != 0.0 ){
+            y = ( eta - b[0] )/b[1];
+         } else {
+            y = b[0];
+         }
+      }
+
+/* Iterate up to 50 times, until the required relative accuracy is
+   achieved. */
+      tol = 1.0E-5;
+      ok = 0;
+      for (i = 0; i < 50; i++) {
+
+/* Get required products of the current x and y values */
+         x2 = x*x;
+         xy = x*y;
+         y2 = y*y;
+
+         r2 = x2 + y2;
+         r = sqrt( r2 );
+
+         x3 = x2*x;
+         x2y = x2*y;
+         xy2 = x*y2;
+         y3 = y*y2;
+
+         r3 = r*r2;
+
+         x4 = x3*x;
+         x3y = x3*y;
+         x2y2 = x2*y2;
+         xy3 = x*y3;
+         y4 = y*y3;
+
+         x5 = x4*x;
+         x4y = x4*y;
+         x3y2 = x3*y2;
+         x2y3 = x2*y3;
+         xy4 = x*y4;
+         y5 = y*y4;
+
+         r5 = r3*r2;
+
+         x6 = x5*x;
+         x5y = x5*y;
+         x4y2 = x4*y2;
+         x3y3 = x3*y3;
+         x2y4 = x2*y4;
+         xy5 = x*y5;
+         y6 = y*y5;
+
+         x7 = x6*x;
+         x6y = x6*y;
+         x5y2 = x5*y2;
+         x4y3 = x4*y3;
+         x3y4 = x3*y4;
+         x2y5 = x2*y5;
+         xy6 = x*y6;
+         y7 = y*y6;
+
+         r7 = r5*r2;
+
+/* Get the xi and eta models corresponding to the current x and y values */
+         f =   a[0]       + a[1]*x     + a[2]*y     + a[3]*r     + a[4]*x2
+              + a[5]*xy    + a[6]*y2    + a[7]*x3    + a[8]*x2y   + a[9]*xy2
+              + a[10]*y3   + a[11]*r3   + a[12]*x4   + a[13]*x3y  + a[14]*x2y2
+              + a[15]*xy3  + a[16]*y4   + a[17]*x5   + a[18]*x4y  + a[19]*x3y2
+              + a[20]*x2y3 + a[21]*xy4  + a[22]*y5   + a[23]*r5   + a[24]*x6
+              + a[25]*x5y  + a[26]*x4y2 + a[27]*x3y3 + a[28]*x2y4 + a[29]*xy5
+              + a[30]*y6   + a[31]*x7   + a[32]*x6y  + a[33]*x5y2 + a[34]*x4y3
+              + a[35]*x3y4 + a[36]*x2y5 + a[37]*xy6  + a[38]*y7   + a[39]*r7;
+
+         g =  b[0]       + b[1]*y     + b[2]*x     + b[3]*r     + b[4]*y2
+              + b[5]*xy    + b[6]*x2    + b[7]*y3    + b[8]*xy2   + b[9]*x2y
+              + b[10]*x3   + b[11]*r3   + b[12]*y4   + b[13]*xy3  + b[14]*x2y2
+              + b[15]*x3y  + b[16]*x4   + b[17]*y5   + b[18]*xy4  + b[19]*x2y3
+              + b[20]*x3y2 + b[21]*x4y  + b[22]*x5   + b[23]*r5   + b[24]*y6
+              + b[25]*xy5  + b[26]*x2y4 + b[27]*x3y3 + b[28]*x4y2 + b[29]*x5y
+              + b[30]*x6   + b[31]*y7   + b[32]*xy6  + b[33]*x2y5 + b[34]*x3y4
+              + b[35]*x4y3 + b[36]*x5y2 + b[37]*x6y  + b[38]*x7   + b[39]*r7;
+
+/* Partial derivative of xi wrt x... */
+         fx = a[1]         + a[3]*( (r!=0.0)?(x/r):0.0 ) + 2*a[4]*x +
+              a[5]*y       + 3*a[7]*x2    + 2*a[8]*xy    + a[9]*y2 +
+              3*a[11]*r*x  + 4*a[12]*x3   + 3*a[13]*x2y  + 2*a[14]*xy2  +
+	      a[15]*y3     + 5*a[17]*x4   + 4*a[18]*x3y  + 3*a[19]*x2y2 +
+	      2*a[20]*xy3  + a[21]*y4     + 5*a[23]*r3*x + 6*a[24]*x5  +
+              5*a[25]*x4y  + 4*a[26]*x3y2 + 3*a[27]*x2y3 + 2*a[28]*xy4  +
+	      a[29]*y5     + 7*a[31]*x6   + 6*a[32]*x5y  + 5*a[33]*x4y2 +
+              4*a[34]*x3y3 + 3*a[35]*x2y4 + 2*a[36]*xy5  + a[37]*y6 +
+              7*a[39]*r5*x;
+
+/* Partial derivative of xi wrt y... */
+         fy = a[2]         + a[3]*( (r!=0.0)?(y/r):0.0 ) + a[5]*x +
+              2*a[6]*y     + a[8]*x2      + 2*a[9]*xy    + 3*a[10]*y2 +
+	      3*a[11]*r*y  + a[13]*x3     + 2*a[14]*x2y  + 3*a[15]*xy2 +
+	      4*a[16]*y3   + a[18]*x4     + 2*a[19]*x3y  + 3*a[20]*x2y2 +
+	      4*a[21]*xy3  + 5*a[22]*y4   + 5*a[23]*r3*y + a[25]*x5 +
+	      2*a[26]*x4y  + 3*a[27]*x3y2 + 4*a[28]*x2y3 + 5*a[29]*xy4 +
+              6*a[30]*y5   + a[32]*x6     + 2*a[33]*x5y  + 3*a[34]*x4y2 +
+              4*a[35]*x3y3 + 5*a[36]*x2y4 + 6*a[37]*xy5  + 7*a[38]*y6 +
+              7*a[39]*r5*y;
+
+/* Partial derivative of eta wrt x... */
+         gx = b[2]         + b[3]*( (r!=0.0)?(x/r):0.0 ) + b[5]*y +
+              2*b[6]*x     + b[8]*y2      + 2*b[9]*xy    + 3*b[10]*x2 +
+	      3*b[11]*r*x  + b[13]*y3     + 2*b[14]*xy2  + 3*b[15]*x2y +
+	      4*b[16]*x3   + b[18]*y4     + 2*b[19]*xy3  + 3*b[20]*x2y2 +
+	      4*b[21]*x3y  + 5*b[22]*x4   + 5*b[23]*r3*x + b[25]*y5 +
+	      2*b[26]*xy4  + 3*b[27]*x2y3 + 4*b[28]*x3y2 + 5*b[29]*x4y +
+	      6*b[30]*x5   + b[32]*y6     + 2*b[33]*xy5  + 3*b[34]*x2y4 +
+	      4*b[35]*x3y3 + 5*b[36]*x4y2 + 6*b[37]*x5y  + 7*b[38]*x6 +
+	      7*b[39]*r5*x;
+
+/* Partial derivative of eta wrt y... */
+         gy = b[1]         + b[3]*( (r!=0.0)?(y/r):0.0 ) + 2*b[4]*y +
+              b[5]*x       + 3*b[7]*y2    + 2*b[8]*xy    + b[9]*x2 +
+              3*b[11]*r*y  + 4*b[12]*y3   + 3*b[13]*xy2  + 2*b[14]*x2y  +
+	      b[15]*x3     + 5*b[17]*y4   + 4*b[18]*xy3  + 3*b[19]*x2y2 +
+	      2*b[20]*x3y  + b[21]*x4     + 5*b[23]*r3*y + 6*b[24]*y5  +
+	      5*b[25]*xy4  + 4*b[26]*x2y3 + 3*b[27]*x3y2 + 2*b[28]*x4y  +
+	      b[29]*x5     + 7*b[31]*y6   + 6*b[32]*xy5  + 5*b[33]*x2y4 +
+	      4*b[34]*x3y3 + 3*b[35]*x4y2 + 2*b[36]*x5y  + b[37]*x6     +
+              7*b[39]*r5*y;
+
+/* Calculate new x and y values. */
+         f = f - xi;
+         g = g - eta;
+         dx = ( (-f*gy) + (g*fy) ) / ( (fx*gy) - (fy*gx) );
+         dy = ( (-g*fx) + (f*gx) ) / ( (fx*gy) - (fy*gx) );
+         x += dx;
+         y += dy;
+
+/* Check if convergence has been achieved. */
+         if( fabs(dx) <= tol*fabs(x) && fabs(dy) <= tol*fabs(y) ) {
+            ok = 1;
+            break;
+         }
+      }
+
+      *xx = x;
+      *yy = y;
+
+      if( !ok ) return 2;
+
+   }
+
+   return 0;
+
+}
+
+/*--------------------------------------------------------------------------*/
+
+int astTPNrev(x, y, prj, phi, theta)
+
+const double x, y;
+double *phi, *theta;
+struct AstPrjPrm *prj;
+
+{
+   double r, xi, eta, x2, xy, y2, r2, x3, x2y, xy2, y3, r3, x4, x3y,
+          x2y2, xy3, y4, x5, x4y, x3y2, x2y3, xy4, y5, r5, x6, x5y, x4y2,
+	  x3y3, x2y4, xy5, y6, x7, x6y, x5y2, x4y3, x3y4, x2y5, xy6, y7,
+	  r7;
+   double *a, *b;
+
+   if (abs(prj->flag) != TPN ) {
+      if (astTPNset(prj)) return 1;
+   }
+
+   /* Simple tan */
+   if( prj->w[ 0 ] == 0.0 ){
+      xi = x;
+      eta = y;
+
+   /* Tan with polynomial corrections. */
+   } else {
+      x2 = x*x;
+      xy = x*y;
+      y2 = y*y;
+
+      r2 = x2 + y2;
+      r = sqrt( r2 );
+
+      x3 = x2*x;
+      x2y = x2*y;
+      xy2 = x*y2;
+      y3 = y*y2;
+
+      r3 = r*r2;
+
+      x4 = x3*x;
+      x3y = x3*y;
+      x2y2 = x2*y2;
+      xy3 = x*y3;
+      y4 = y*y3;
+
+      x5 = x4*x;
+      x4y = x4*y;
+      x3y2 = x3*y2;
+      x2y3 = x2*y3;
+      xy4 = x*y4;
+      y5 = y*y4;
+
+      r5 = r3*r2;
+
+      x6 = x5*x;
+      x5y = x5*y;
+      x4y2 = x4*y2;
+      x3y3 = x3*y3;
+      x2y4 = x2*y4;
+      xy5 = x*y5;
+      y6 = y*y5;
+
+      x7 = x6*x;
+      x6y = x6*y;
+      x5y2 = x5*y2;
+      x4y3 = x4*y3;
+      x3y4 = x3*y4;
+      x2y5 = x2*y5;
+      xy6 = x*y6;
+      y7 = y*y6;
+
+      r7 = r5*r2;
+
+      a = prj->p2;
+      xi =   a[0]       + a[1]*x     + a[2]*y     + a[3]*r     + a[4]*x2
+           + a[5]*xy    + a[6]*y2    + a[7]*x3    + a[8]*x2y   + a[9]*xy2
+           + a[10]*y3   + a[11]*r3   + a[12]*x4   + a[13]*x3y  + a[14]*x2y2
+           + a[15]*xy3  + a[16]*y4   + a[17]*x5   + a[18]*x4y  + a[19]*x3y2
+           + a[20]*x2y3 + a[21]*xy4  + a[22]*y5   + a[23]*r5   + a[24]*x6
+           + a[25]*x5y  + a[26]*x4y2 + a[27]*x3y3 + a[28]*x2y4 + a[29]*xy5
+           + a[30]*y6   + a[31]*x7   + a[32]*x6y  + a[33]*x5y2 + a[34]*x4y3
+           + a[35]*x3y4 + a[36]*x2y5 + a[37]*xy6  + a[38]*y7   + a[39]*r7;
+
+      b = prj->p;
+      eta =  b[0]       + b[1]*y     + b[2]*x     + b[3]*r     + b[4]*y2
+           + b[5]*xy    + b[6]*x2    + b[7]*y3    + b[8]*xy2   + b[9]*x2y
+           + b[10]*x3   + b[11]*r3   + b[12]*y4   + b[13]*xy3  + b[14]*x2y2
+           + b[15]*x3y  + b[16]*x4   + b[17]*y5   + b[18]*xy4  + b[19]*x2y3
+           + b[20]*x3y2 + b[21]*x4y  + b[22]*x5   + b[23]*r5   + b[24]*y6
+           + b[25]*xy5  + b[26]*x2y4 + b[27]*x3y3 + b[28]*x4y2 + b[29]*x5y
+           + b[30]*x6   + b[31]*y7   + b[32]*xy6  + b[33]*x2y5 + b[34]*x3y4
+           + b[35]*x4y3 + b[36]*x5y2 + b[37]*x6y  + b[38]*x7   + b[39]*r7;
+
+   }
+
+   /* Now do the tan projection */
+   if( prj->n ) {
+      r = sqrt(xi*xi + eta*eta);
+      if (r == 0.0) {
+         *phi = 0.0;
+      } else {
+         *phi = astATan2d(xi, -eta);
+      }
+      *theta = astATan2d(prj->r0, r);
+   } else {
+      *phi = xi;
+      *theta = eta;
+   }
+
+   return 0;
+}
+
diff --git a/ast/tranmap.c b/ast/tranmap.c
new file mode 100644
index 0000000..5abd75d
--- /dev/null
+++ b/ast/tranmap.c
@@ -0,0 +1,2327 @@
+/*
+*class++
+*  Name:
+*     TranMap
+
+*  Purpose:
+*     Mapping with specified forward and inverse transformations.
+
+*  Constructor Function:
+c     astTranMap
+f     AST_TRANMAP
+
+*  Description:
+*     A TranMap is a Mapping which combines the forward transformation of
+*     a supplied Mapping with the inverse transformation of another
+*     supplied Mapping, ignoring the un-used transformation in each
+*     Mapping (indeed the un-used transformation need not exist).
+*
+*     When the forward transformation of the TranMap is referred to, the
+*     transformation actually used is the forward transformation of the
+*     first Mapping supplied when the TranMap was constructed. Likewise,
+*     when the inverse transformation of the TranMap is referred to, the
+*     transformation actually used is the inverse transformation of the
+*     second Mapping supplied when the TranMap was constructed.
+
+*  Inheritance:
+*     The TranMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The TranMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     The TranMap class does not define any new functions beyond those
+f     The TranMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     10-FEB-2004 (DSB):
+*        Original version.
+*     19-JAN-2005 (DSB):
+*        Fix memory leak.
+*     14-FEB-2006 (DSB):
+*        - Over-ride the astDecompose method.
+*        - Fix bug in MapSplit related to use of invert flags.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS TranMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "permmap.h"             /* Coordinate permutation Mappings */
+#include "cmpmap.h"              /* Compound Mappings */
+#include "unitmap.h"             /* Unit Mappings */
+#include "tranmap.h"             /* Interface definition for this class */
+#include "frame.h"               /* Frames */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int *(* parent_mapsplit)( AstMapping *, int, const int *, AstMapping **, int * );
+
+#if defined(THREAD_SAFE)
+static int (* parent_managelock)( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(TranMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(TranMap,Class_Init)
+#define class_vtab astGLOBAL(TranMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstTranMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstTranMap *astTranMapId_( void *, void *, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstMapping *RemoveRegions( AstMapping *, int * );
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void Decompose( AstMapping *, AstMapping **, AstMapping **, int *, int *, int *, int * );
+static int GetObjSize( AstObject *, int * );
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *, int, int, AstObject **, int * );
+#endif
+
+
+/* Member functions. */
+/* ================= */
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two TranMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     TranMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two TranMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a TranMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the TranMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstTranMap *that;
+   AstTranMap *this;
+   int nin;
+   int nout;
+   int result;
+   int that_inv1;
+   int that_inv2;
+   int this_inv1;
+   int this_inv2;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two TranMap structures. */
+   this = (AstTranMap *) this_object;
+   that = (AstTranMap *) that_object;
+
+/* Check the second object is a TranMap. We know the first is a
+   TranMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsATranMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* Temporarily re-instate the original Invert flag values. */
+         that_inv1 = astGetInvert( that->map1 );
+         that_inv2 = astGetInvert( that->map2 );
+         this_inv1 = astGetInvert( this->map1 );
+         this_inv2 = astGetInvert( this->map2 );
+
+         astSetInvert( this->map1, this->invert1 );
+         astSetInvert( this->map2, this->invert2 );
+         astSetInvert( that->map1, that->invert1 );
+         astSetInvert( that->map2, that->invert2 );
+
+/* If the Invert flags for the two TranMaps differ, it may still be possible
+   for them to be equivalent. First compare the TranMaps if their Invert
+   flags are the same. In this case all the attributes of the two TranMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+            if( astEqual( this->map1, that->map1 ) &&
+                astEqual( this->map2, that->map2 ) ) {
+               result = 1;
+            }
+
+/* If the Invert flags for the two TranMaps differ, the attributes of the two
+   TranMaps must be inversely related to each other. */
+         } else {
+
+            astInvert( that->map1 );
+            astInvert( that->map2 );
+
+            if( astEqual( this->map1, that->map2 ) &&
+                astEqual( this->map2, that->map1 ) ) {
+               result = 1;
+            }
+
+         }
+
+/* Restore the original Invert flag values. */
+         astSetInvert( this->map1, this_inv1 );
+         astSetInvert( this->map2, this_inv2 );
+         astSetInvert( that->map1, that_inv1 );
+         astSetInvert( that->map2, that_inv2 );
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     TranMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied TranMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the TranMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstTranMap *this;         /* Pointer to TranMap structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the TranMap structure. */
+   this = (AstTranMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astGetObjSize( this->map1 );
+   result += astGetObjSize( this->map2 );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static void Decompose( AstMapping *this_mapping, AstMapping **map1,
+                       AstMapping **map2, int *series, int *invert1,
+                       int *invert2, int *status ) {
+/*
+*
+*  Name:
+*     Decompose
+
+*  Purpose:
+*     Decompose a Mapping into two component Mappings.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     void Decompose( AstMapping *this, AstMapping **map1,
+*                     AstMapping **map2, int *series,
+*                     int *invert1, int *invert2, int *status )
+
+*  Class Membership:
+*     TranMap member function (over-rides the protected astDecompose
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns pointers to the two Mappings encapsulated by
+*     a TranMap.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping.
+*     map1
+*        Address of a location to receive a pointer to first component
+*        Mapping (the forward Mapping).
+*     map2
+*        Address of a location to receive a pointer to second component
+*        Mapping (the inverse Mapping).
+*     series
+*        Address of a location to receive a value indicating if the
+*        component Mappings are applied in series or parallel. A non-zero
+*        value means that the supplied Mapping is equivalent to applying map1
+*        followed by map2 in series. A zero value means that the supplied
+*        Mapping is equivalent to applying map1 to the lower numbered axes
+*        and map2 to the higher numbered axes, in parallel. Zero is
+*        returned for a TranMap.
+*     invert1
+*        The value of the Invert attribute to be used with map1.
+*     invert2
+*        The value of the Invert attribute to be used with map2.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - Any changes made to the component Mappings using the returned
+*     pointers will be reflected in the supplied Mapping.
+
+*-
+*/
+
+
+/* Local Variables: */
+   AstTranMap *this;             /* Pointer to TranMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the TranMap structure. */
+   this = (AstTranMap *) this_mapping;
+
+/* If the TranMap has been inverted, return the Mappings in reverse
+   order with inverted Invert falgs. */
+   if( astGetInvert( this ) ) {
+      if( map1 ) *map1 = astClone( this->map2 );
+      if( map2 ) *map2 = astClone( this->map1 );
+      if( invert1 ) *invert1 = this->invert2 ? 0 : 1;
+      if( invert2 ) *invert2 = this->invert1 ? 0 : 1;
+
+/* If the TranMap has not been inverted, return the Mappings in their
+   original order with their original Invert flags. */
+   } else {
+      if( map1 ) *map1 = astClone( this->map1 );
+      if( map2 ) *map2 = astClone( this->map2 );
+      if( invert1 ) *invert1 = this->invert1;
+      if( invert2 ) *invert2 = this->invert2;
+   }
+}
+
+void astInitTranMapVtab_(  AstTranMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitTranMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a TranMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     void astInitTranMapVtab( AstTranMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     TranMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the TranMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsATranMap) to determine if an object belongs to
+   this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+/* None. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   mapping->RemoveRegions = RemoveRegions;
+
+#if defined(THREAD_SAFE)
+   parent_managelock = object->ManageLock;
+   object->ManageLock = ManageLock;
+#endif
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_mapsplit = mapping->MapSplit;
+   mapping->MapSplit = MapSplit;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->Decompose = Decompose;
+   mapping->MapMerge = MapMerge;
+   mapping->Rate = Rate;
+
+/* Declare the copy constructor, destructor and class dump function. */
+   astSetCopy( vtab, Copy );
+   astSetDelete( vtab, Delete );
+   astSetDump( vtab, Dump, "TranMap", "Compound Transformation Mapping" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+#if defined(THREAD_SAFE)
+static int ManageLock( AstObject *this_object, int mode, int extra,
+                       AstObject **fail, int *status ) {
+/*
+*  Name:
+*     ManageLock
+
+*  Purpose:
+*     Manage the thread lock on an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "object.h"
+*     AstObject *ManageLock( AstObject *this, int mode, int extra,
+*                            AstObject **fail, int *status )
+
+*  Class Membership:
+*     TranMap member function (over-rides the astManageLock protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function manages the thread lock on the supplied Object. The
+*     lock can be locked, unlocked or checked by this function as
+*     deteremined by parameter "mode". See astLock for details of the way
+*     these locks are used.
+
+*  Parameters:
+*     this
+*        Pointer to the Object.
+*     mode
+*        An integer flag indicating what the function should do:
+*
+*        AST__LOCK: Lock the Object for exclusive use by the calling
+*        thread. The "extra" value indicates what should be done if the
+*        Object is already locked (wait or report an error - see astLock).
+*
+*        AST__UNLOCK: Unlock the Object for use by other threads.
+*
+*        AST__CHECKLOCK: Check that the object is locked for use by the
+*        calling thread (report an error if not).
+*     extra
+*        Extra mode-specific information.
+*     fail
+*        If a non-zero function value is returned, a pointer to the
+*        Object that caused the failure is returned at "*fail". This may
+*        be "this" or it may be an Object contained within "this". Note,
+*        the Object's reference count is not incremented, and so the
+*        returned pointer should not be annulled. A NULL pointer is
+*        returned if this function returns a value of zero.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*    A local status value:
+*        0 - Success
+*        1 - Could not lock or unlock the object because it was already
+*            locked by another thread.
+*        2 - Failed to lock a POSIX mutex
+*        3 - Failed to unlock a POSIX mutex
+*        4 - Bad "mode" value supplied.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variables: */
+   AstTranMap *this;       /* Pointer to TranMap structure */
+   int result;             /* Returned status value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the supplied pointer is not NULL. */
+   if( !this_object ) return result;
+
+/* Obtain a pointers to the TranMap structure. */
+   this = (AstTranMap *) this_object;
+
+/* Invoke the ManageLock method inherited from the parent class. */
+   if( !result ) result = (*parent_managelock)( this_object, mode, extra,
+                                                fail, status );
+
+/* Invoke the astManageLock method on any Objects contained within
+   the supplied Object. */
+   if( !result ) result = astManageLock( this->map1, mode, extra, fail );
+   if( !result ) result = astManageLock( this->map2, mode, extra, fail );
+
+   return result;
+
+}
+#endif
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a TranMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     TranMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated TranMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated TranMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated TranMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the TranMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        TranMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated TranMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpMap *cmap;              /* Pointer to compound Mapping */
+   AstMapping *cmap_f;           /* Pointer to compound Mapping */
+   AstMapping *cmap_i;           /* Pointer to compound Mapping */
+   AstMapping *hmap1;            /* Pointer to 1st comp of higher TranMap */
+   AstMapping *hmap2;            /* Pointer to 2nd comp of higher TranMap */
+   AstMapping *hmap_f;           /* Pointer to fwd Mapping of higher TranMap */
+   AstMapping *hmap_i;           /* Pointer to inv Mapping of higher TranMap */
+   AstMapping *map1;             /* Pointer to 1st comp of nominated TranMap */
+   AstMapping *map2;             /* Pointer to 2nd comp of nominated TranMap */
+   AstMapping *map_f;            /* Pointer to fwd Mapping of nominated TranMap */
+   AstMapping *map_i;            /* Pointer to inv Mapping of nominated TranMap */
+   AstMapping *smap;             /* Pointer to simplified Mapping */
+   AstMapping *smap_f;           /* Pointer to simplified Mapping */
+   AstMapping *smap_i;           /* Pointer to simplified Mapping */
+   AstTranMap *hmap;             /* Pointer to higher TranMap */
+   AstTranMap *map;              /* Pointer to this TranMap */
+   AstTranMap *new;              /* Pointer to merged TranMap */
+   int i;                        /* Loop count */
+   int old_hinv1;                /* Original Invert flag for hmap->map1 */
+   int old_hinv2;                /* Original Invert flag for hmap->map2 */
+   int old_inv1;                 /* Original Invert flag for this->map1 */
+   int old_inv2;                 /* Original Invert flag for this->map2 */
+   int result;                   /* The value to return */
+
+/* Initialise.*/
+   result = -1;
+
+/* Check the inherited status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to this TranMap. */
+   map = (AstTranMap *) this;
+
+/* Get the two component Mappings,and temporarily set their Invert
+   attributes back to the values they had when the TranMap was created,
+   saving their current Invert values so that they can be re-instated later. */
+   map1 = map->map1;
+   old_inv1 = astGetInvert( map1 );
+   astSetInvert( map1, map->invert1 );
+
+   map2 = map->map2;
+   old_inv2 = astGetInvert( map2 );
+   astSetInvert( map2, map->invert2 );
+
+/* Simplify the TranMap on its own. */
+/* ================================ */
+
+/* If the TranMap is inverted, creat an equal TranMap which is not inverted.
+   To do this, invert and swap the component Mappings. */
+   if( ( *invert_list )[ where ] ) {
+      astInvert( map1 );
+      astInvert( map2 );
+      new = astTranMap( map2, map1, "", status );
+      astInvert( map1 );
+      astInvert( map2 );
+
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = (AstMapping *) new;
+      ( *invert_list )[ where ] = 0;
+      result = where;
+
+/* Otherwise, try to simplify each of the component Mappings. */
+   } else {
+      smap_f = astSimplify( map1 );
+      smap_i = astSimplify( map2 );
+
+/* Assume some simplification took place if the pointers have changed. */
+      if( smap_f != map1 || smap_i != map2 ) {
+
+/* Construct a new TranMap from these simplifgied Mappings. */
+         (void) astAnnul( ( *map_list )[ where ] );
+         ( *map_list )[ where ] = (AstMapping *) astTranMap( smap_f, smap_i, "", status );
+         result = where;
+
+/* Otherwise, if the both component Mappings are defined in both directions... */
+      } else if( astGetTranForward( map1 ) && astGetTranInverse( map1 ) &&
+                 astGetTranForward( map2 ) && astGetTranInverse( map2 ) ) {
+
+/* Form a series CmpMap from the two component Mappings, with the second
+   Mapping inverted. */
+         astInvert( map2 );
+         cmap = astCmpMap( map1, map2, 1, "", status );
+         astInvert( map2 );
+
+/* If this CmpMap simplifies to a UnitMap, then the two components of the
+   TranMap are equal, and so we can replace the entire TranMap with either
+   of its components. Note, we leave the supplied invert flag unchanged,
+   since the copycreated below refers to the Mapping as it was when the
+   TranMap was created. However, we invert the returned Mapping if
+   necessary. */
+         smap = astSimplify( cmap );
+         if( astIsAUnitMap( smap ) ) {
+            (void) astAnnul( ( *map_list )[ where ] );
+            ( *map_list )[ where ] = astCopy( map1 );
+            if( ( *invert_list )[ where ] ) astInvert( ( *map_list )[ where ] );
+            result = where;
+         }
+
+/* Release resources. */
+         smap = astAnnul( smap );
+         cmap = astAnnul( cmap );
+      }
+
+/* Release resources. */
+      smap_f = astAnnul( smap_f );
+      smap_i = astAnnul( smap_i );
+   }
+
+/* Merge the TranMap with a neighbouring TranMap. */
+/* ============================================== */
+/* Only do this if no change was made above, and we are combining the
+   Mappings in series. */
+   if( result == -1 && series ) {
+
+/* Is the higher neighbour a TranMap? */
+      if( where < ( *nmap - 1 ) &&
+          astIsATranMap( ( *map_list )[ where + 1 ] ) ){
+
+/* Get the two component Mappings of the higher TranMap, and temporarily set
+   their Invert attributes back to the values they had when the TranMap was
+   created, saving their current Invert values so that they can be re-instated
+   later. */
+         hmap = (AstTranMap *) ( *map_list )[ where + 1 ];
+
+         hmap1 = hmap->map1;
+         old_hinv1 = astGetInvert( hmap1 );
+         astSetInvert( hmap1, hmap->invert1 );
+
+         hmap2 = hmap->map2;
+         old_hinv2 = astGetInvert( hmap2 );
+         astSetInvert( hmap2, hmap->invert2 );
+
+/* Get the Mappings which defines the forward and inverse transformation of
+   the lower TranMap ("this"). Then, map_f and map_i are pointers to
+   Mappings which could be used to construct a new TranMap which would be
+   equivalent to "this" with the supplied invert setting. */
+         if( ( *invert_list )[ where ] ) {
+            map_f = map2;
+            map_i = map1;
+            astInvert( map_f );
+            astInvert( map_i );
+         } else {
+            map_f = map1;
+            map_i = map2;
+         }
+
+/* Likewise, get the Mappings which defines the forward and inverse
+   transformation of the higher TranMap. */
+         if( ( *invert_list )[ where + 1 ] ) {
+            hmap_f = hmap2;
+            hmap_i = hmap1;
+            astInvert( hmap_f );
+            astInvert( hmap_i );
+         } else {
+            hmap_f = hmap1;
+            hmap_i = hmap2;
+         }
+
+/* Combine the two forward Mappings together into a series CmpMap, and
+   simplify it. */
+         cmap_f = (AstMapping *) astCmpMap( map_f,  hmap_f, 1, "", status );
+         smap_f = astSimplify( cmap_f );
+
+/* Do the same for the inverse Mappings */
+         cmap_i = (AstMapping *) astCmpMap( map_i,  hmap_i, 1, "", status );
+         smap_i = astSimplify( cmap_i );
+
+/* Was any simplification performed? We assume this is the case if the
+   either of the simplied pointer differs from the original pointer. */
+         if( cmap_f != smap_f || cmap_i != smap_i ) {
+
+/* In which case,construct a new TranMap from the simplified Mappings. */
+            new = astTranMap( smap_f, smap_i, "", status );
+
+         } else {
+            new = NULL;
+         }
+
+/* Free resources.*/
+         cmap_f = astAnnul( cmap_f );
+         smap_f = astAnnul( smap_f );
+         cmap_i = astAnnul( cmap_i );
+         smap_i = astAnnul( smap_i );
+
+/* Re-instate the original Invert values for the component Mappings of
+   the higher TranMap. */
+         astSetInvert( hmap1, old_hinv1 );
+         astSetInvert( hmap2, old_hinv2 );
+
+/* If we have a new TranMap, annul the first of the two Mappings, and replace
+   it with the merged TranMap. Also set the invert flag. */
+         if( new ) {
+            (void) astAnnul( ( *map_list )[ where ] );
+            ( *map_list )[ where ] = (AstMapping *) new;
+            ( *invert_list )[ where ] = 0;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+            (void) astAnnul( ( *map_list )[ where + 1 ] );
+            for ( i = where + 2; i < *nmap; i++ ) {
+               ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+               ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+            }
+
+/* Clear the vacated element at the end. */
+            ( *map_list )[ *nmap - 1 ] = NULL;
+            ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+            ( *nmap )--;
+            result = where;
+         }
+      }
+   }
+
+/* Re-instate the original Invert values for the component Mappings. */
+   astSetInvert( map1, old_inv1 );
+   astSetInvert( map2, old_inv2 );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     TranMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     TranMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing TranMap. This is only possible if the specified inputs
+*     correspond to some subset of the TranMap outputs. That is, there
+*     must exist a subset of the TranMap outputs for which each output
+*     depends only on the selected TranMap inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied TranMap, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the TranMap to be split (the TranMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied TranMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied TranMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied TranMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstMapping *fmap;          /* Pointer to forward Mapping in supplied TranMap */
+   AstMapping *imap;          /* Pointer to inverse Mapping in supplied TranMap */
+   AstMapping *rfmap;         /* Pointer to split forward Mapping */
+   AstMapping *rimap;         /* Pointer to split inverse Mapping */
+   AstTranMap *this;          /* Pointer to TranMap structure */
+   int *ires;                 /* I/ps of inv Mapping dependent on selected o/ps */
+   int *out;                  /* O/ps of fwd Mapping dependent on selected i/ps */
+   int *result;               /* Pointer to returned array */
+   int finv;                  /* Invert flag to use with fmap */
+   int i;                     /* Loop count */
+   int iinv;                  /* Invert flag to use with imap */
+   int nout;                  /* No. of outputs dependent on selected inputs */
+   int ok;                    /* Can required Mapping be created? */
+   int old_finv;              /* Original Invert flag for fmap */
+   int old_iinv;              /* Original Invert flag for imap */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the parent astMapSplit method to see if it can do the job. */
+   result = (*parent_mapsplit)( this_map, nin, in, map, status );
+
+/* If not, we provide a special implementation here. */
+   if( !result ) {
+
+/* Get a pointer to the TranMap structure. */
+      this = (AstTranMap *) this_map;
+
+/* Get pointers to the forward and inverse Mappings, taking into account
+   whether the TranMap has been inverted. */
+      if( !astGetInvert( this ) ) {
+         fmap = this->map1;
+         finv = this->invert1;
+         imap = this->map2;
+         iinv = this->invert2;
+      } else {
+         imap = this->map1;
+         iinv = !( this->invert1 );
+         fmap = this->map2;
+         finv = !( this->invert2 );
+      }
+
+/* Temporarily set the Invert flag of both Mappings back to their
+   original values. */
+      old_finv = astGetInvert( fmap );
+      astSetInvert( fmap, finv );
+      old_iinv = astGetInvert( imap );
+      astSetInvert( imap, iinv );
+
+/* Try to split the forward Mapping. */
+      out = astMapSplit( fmap, nin, in, &rfmap );
+
+/* Check the split could be done. */
+      if( out ) {
+
+/* Get the number of outputs which are fed by the selected inputs. */
+         nout = astGetNout( rfmap );
+
+/* See if the inverse Mapping can be split using these outputs as inputs. */
+         astInvert( imap );
+         ires = astMapSplit( imap, nout, out, &rimap );
+         astInvert( imap );
+         if( ires ) {
+            astInvert( rimap );
+
+/* Check that the resulting inputs are the same as the supplied inputs. */
+            if( astGetNin( rimap ) == nin ) {
+               ok = 1;
+               for( i = 0; i < nin; i++ ) {
+                  if( in[ i ] != ires[ i ] ) {
+                     ok = 0;
+                     break;
+                  }
+               }
+
+/* If so create the required new TranMap. */
+               if( ok ) {
+                  *map = (AstMapping *) astTranMap( rfmap, rimap, "", status );
+                  result = out;
+               }
+            }
+
+/* Free resources. */
+            ires = astFree( ires );
+            rimap = astAnnul( rimap );
+         }
+
+         if( !result ) out = astFree( out );
+         rfmap = astAnnul( rfmap );
+      }
+
+/* Re-instate the Invert flags of the component Mappings. */
+      astSetInvert( fmap, old_finv );
+      astSetInvert( imap, old_iinv );
+   }
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     TranMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position. Also evaluates the second derivative.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*/
+
+/* Local Variables: */
+   AstTranMap *map;
+   AstMapping *cmap;
+   double result;
+   int cinv;
+   int old_inv;
+
+/* Check inherited status */
+   if( !astOK ) return AST__BAD;
+
+/* Get a pointer to the TranMap structure. */
+   map = (AstTranMap *) this;
+
+/* Choose the component Mapping to use, and get its original Invert
+   value. Invert this if the TranMap itself has been inverted (this is
+   because the astRate function has no "invert" argument so we need to
+   invert the Mapping before calling astRate). */
+   if( astGetInvert( this ) ) {
+      cmap = map->map2;
+      cinv = !(map->invert2);
+   } else {
+      cmap = map->map1;
+      cinv = map->invert1;
+   }
+
+/* Temporarily set the Invert flag of the component Mapping back to its
+   original value. */
+   old_inv = astGetInvert( cmap );
+   astSetInvert( cmap, cinv );
+
+/* Use the astRate method of the component Mapping. */
+   result = astRate( cmap, at, ax1, ax2 );
+
+/* Re-instate the Invert flag of the component Mapping. */
+   astSetInvert( cmap, old_inv );
+
+/* Return the result. */
+   return result;
+}
+
+static AstMapping *RemoveRegions( AstMapping *this_mapping, int *status ) {
+/*
+*  Name:
+*     RemoveRegions
+
+*  Purpose:
+*     Remove any Regions from a Mapping.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     AstMapping *RemoveRegions( AstMapping *this, int *status )
+
+*  Class Membership:
+*     TranMap method (over-rides the astRemoveRegions method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function searches the supplied Mapping (which may be a
+*     compound Mapping such as a TranMap) for any component Mappings
+*     that are instances of the AST Region class. It then creates a new
+*     Mapping from which all Regions have been removed. If a Region
+*     cannot simply be removed (for instance, if it is a component of a
+*     parallel TranMap), then it is replaced with an equivalent UnitMap
+*     in the returned Mapping.
+*
+*     The implementation provided by the TranMap class invokes the
+*     astRemoveRegions method on the two component Mappings, and joins
+*     the results together into a new TranMap.
+
+*  Parameters:
+*     this
+*        Pointer to the original Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the modified mapping.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the AST error status set, or if it should fail for
+*     any reason.
+*/
+
+/* Local Variables: */
+   AstTranMap *new;              /* Pointer to new TranMap */
+   AstTranMap *this;             /* Pointer to TranMap structure */
+   AstMapping *newmap1;          /* New first component Mapping */
+   AstMapping *newmap2;          /* New second component Mapping */
+   AstMapping *result;           /* Result pointer to return */
+   int nax;                      /* Number of Frame axes */
+   int unit1;                    /* Is new first Mapping a UnitMap? */
+   int unit2;                    /* Is new second Mapping a UnitMap? */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the TranMap. */
+   this = (AstTranMap *) this_mapping;
+
+/* Invoke the astRemoveRegions method on the two component Mappings. */
+   newmap1 = astRemoveRegions( this->map1 );
+   newmap2 = astRemoveRegions( this->map2 );
+
+/* If neither component was modified, just return a clone of the supplied
+   pointer. */
+   if( this->map1 == newmap1 && this->map2 == newmap2 ) {
+      result = astClone( this );
+
+/* Otherwise, we need to create a new Mapping to return. */
+   } else {
+
+/* The implementation of the astRemoveRegions method provided by the
+   Region class returns a Frame rather than a UnitMap. But we need
+   Mappings here, not Frames. So if either of these new Mappings is
+   a Frame, replace it with an equivalent UnitMap. Also, get flags
+   indicating if either Mapping is a UnitMap.*/
+      if( astIsAFrame( newmap1 ) ) {
+         nax = astGetNin( newmap1 );
+         (void) astAnnul( newmap1 );
+         newmap1 = (AstMapping *) astUnitMap( nax, " ", status );
+         unit1 = 1;
+      } else {
+         unit1 = astIsAUnitMap( newmap1 );
+      }
+
+      if( astIsAFrame( newmap2 ) ) {
+         nax = astGetNin( newmap2 );
+         (void) astAnnul( newmap2 );
+         newmap2 = (AstMapping *) astUnitMap( nax, " ", status );
+         unit2 = 1;
+      } else {
+         unit2 = astIsAUnitMap( newmap2 );
+      }
+
+/* If both new Mappings are UnitMaps, return an equivalent UnitMap. */
+      if( unit1 && unit2 ) {
+         result = (AstMapping *) astUnitMap( astGetNin( newmap1 ) +
+                                             astGetNin( newmap2 ), " ",
+                                             status );
+
+/* Otherwise, return a new TranMap containing the two new Mappings. */
+      } else {
+         new = astCopy( this );
+         (void) astAnnul( new->map1 );
+         (void) astAnnul( new->map2 );
+         new->map1 = astClone( newmap1 );
+         new->map2 = astClone( newmap2 );
+         result = (AstMapping *) new;
+      }
+   }
+
+/* Free resources. */
+   newmap1 = astAnnul( newmap1 );
+   newmap2 = astAnnul( newmap2 );
+
+/* Annul the returned Mapping if an error has occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a TranMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     TranMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a TranMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required Mapping.
+*     This implies applying each of the TranMap's component Mappings in turn,
+*     either in series or in parallel.
+
+*  Parameters:
+*     this
+*        Pointer to the TranMap.
+*     in
+*        Pointer to the PointSet associated with the input coordinate values.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the TranMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstMapping *cmap;             /* Mapping which defines the required transformation */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstTranMap *map;              /* Pointer to TranMap to be applied */
+   int cinv;                     /* Invert flag when TranMap was created */
+   int old_inv;                  /* Invert flag on entry to this function */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the TranMap. */
+   map = (AstTranMap *) this;
+
+/* Apply the parent Mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We now extend the parent astTransform method by applying the component
+   Mappings of the TranMap to generate the output coordinate values. */
+
+/* Determine whether to apply the forward or inverse Mapping, according to the
+   direction specified and whether the Mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* Choose the component Mapping to use, and get its original Invert value. */
+   if( forward ) {
+      cmap = map->map1;
+      cinv = map->invert1;
+   }else {
+      cmap = map->map2;
+      cinv = map->invert2;
+   }
+
+/* Temporarily set the Invert flag of the component Mapping back to its
+   original value. */
+   old_inv = astGetInvert( cmap );
+   astSetInvert( cmap, cinv );
+
+/* Use the Transform method of the component Mapping. */
+   result = astTransform( cmap, in, forward, out );
+
+/* Re-instate the Invert flag of the component Mapping. */
+   astSetInvert( cmap, old_inv );
+
+/* If an error occurred, clean up by deleting the output PointSet (if
+   allocated by this function) and setting a NULL result pointer. */
+   if ( !astOK ) {
+      if ( !out ) result = astDelete( result );
+      result = NULL;
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for TranMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for TranMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the component
+*     Mappings within the TranMap.
+*/
+
+/* Local Variables: */
+   AstTranMap *in;                /* Pointer to input TranMap */
+   AstTranMap *out;               /* Pointer to output TranMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output TranMaps. */
+   in = (AstTranMap *) objin;
+   out = (AstTranMap *) objout;
+
+/* For safety, start by clearing any references to the input component
+   Mappings from the output TranMap. */
+   out->map1 = NULL;
+   out->map2 = NULL;
+
+/* Make copies of these Mappings and store pointers to them in the output
+   TranMap structure. */
+   out->map1 = astCopy( in->map1 );
+   out->map2 = astCopy( in->map2 );
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for TranMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for TranMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstTranMap *this;              /* Pointer to TranMap */
+
+/* Obtain a pointer to the TranMap structure. */
+   this = (AstTranMap *) obj;
+
+/* Annul the pointers to the component Mappings. */
+   this->map1 = astAnnul( this->map1 );
+   this->map2 = astAnnul( this->map2 );
+
+/* Clear the remaining TranMap variables. */
+   this->invert1 = 0;
+   this->invert2 = 0;
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for TranMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the TranMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the TranMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstTranMap *this;              /* Pointer to the TranMap structure */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the TranMap structure. */
+   this = (AstTranMap *) this_object;
+
+/* Write out values representing the instance variables for the TranMap
+   class.  Accompany these with appropriate comment strings, possibly
+   depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* First Invert flag. */
+/* ------------------ */
+   ival = this->invert1;
+   set = ( ival != 0 );
+   astWriteInt( channel, "InvA", set, 0, ival,
+                ival ? "First Mapping used in inverse direction" :
+                       "First Mapping used in forward direction" );
+
+/* Second Invert flag. */
+/* ------------------- */
+   ival = this->invert2;
+   set = ( ival != 0 );
+   astWriteInt( channel, "InvB", set, 0, ival,
+                ival ? "Second Mapping used in inverse direction" :
+                       "Second Mapping used in forward direction" );
+
+/* First Mapping. */
+/* -------------- */
+   astWriteObject( channel, "MapA", 1, 1, this->map1,
+                   "Mapping for forward transformation" );
+
+/* Second Mapping. */
+/* --------------- */
+   astWriteObject( channel, "MapB", 1, 1, this->map2,
+                   "Mapping for inverse transformation" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsATranMap and astCheckTranMap functions using the
+   macros defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(TranMap,Mapping)
+astMAKE_CHECK(TranMap)
+
+AstTranMap *astTranMap_( void *map1_void, void *map2_void, const char *options, int *status, ...) {
+/*
+*+
+*  Name:
+*     astTranMap
+
+*  Purpose:
+*     Create a TranMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     AstTranMap *astTranMap( AstMapping *map1, AstMapping *map2, const char *options, int *status, ... )
+
+*  Class Membership:
+*     TranMap constructor.
+
+*  Description:
+*     This function creates a new TranMap and optionally initialises its
+*     attributes.
+
+*  Parameters:
+*     map1
+*        Pointer to the first Mapping (which deinfes the forward
+*        transformation).
+*     map2
+*        Pointer to the second Mapping (which deinfes the inverse
+*        transformation).
+*     options
+*        Pointer to a null terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new TranMap. The syntax used is the same as for the
+*        astSet method and may include "printf" format specifiers identified
+*        by "%" symbols in the normal way.
+*     status
+*        Pointer to the inherited status variable.
+*     ...
+*        If the "options" string contains "%" format specifiers, then an
+*        optional list of arguments may follow it in order to supply values to
+*        be substituted for these specifiers. The rules for supplying these
+*        are identical to those for the astSet method (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     A pointer to the new TranMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+
+*  Implementation Notes:
+*     - This function implements the basic TranMap constructor which is
+*     available via the protected interface to the TranMap class.  A
+*     public interface is provided by the astTranMapId_ function.
+*     - Because this function has a variable argument list, it is
+*     invoked by a macro that evaluates to a function pointer (not a
+*     function invocation) and no checking or casting of arguments is
+*     performed before the function is invoked. Because of this, the
+*     "map1" and "map2" parameters are of type (void *) and are
+*     converted and validated within the function itself.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstTranMap *new;              /* Pointer to new TranMap */
+   AstMapping *map1;             /* Pointer to first Mapping structure */
+   AstMapping *map2;             /* Pointer to second Mapping structure */
+   va_list args;                 /* Variable argument list */
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain and validate pointers to the Mapping structures provided. */
+   map1 = astCheckMapping( map1_void );
+   map2 = astCheckMapping( map2_void );
+   if ( astOK ) {
+
+/* Initialise the TranMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitTranMap( NULL, sizeof( AstTranMap ), !class_init, &class_vtab,
+                           "TranMap", map1, map2 );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new TranMap's
+   attributes. */
+         va_start( args, status );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new TranMap. */
+   return new;
+}
+
+AstTranMap *astTranMapId_( void *map1_void, void *map2_void,
+                           const char *options, ... ) {
+/*
+*++
+*  Name:
+c     astTranMap
+f     AST_TRANMAP
+
+*  Purpose:
+*     Create a TranMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "tranmap.h"
+c     AstTranMap *astTranMap( AstMapping *map1, AstMapping *map2,
+c                           const char *options, ... )
+f     RESULT = AST_TRANMAP( MAP1, MAP2, OPTIONS, STATUS )
+
+*  Class Membership:
+*     TranMap constructor.
+
+*  Description:
+*     This function creates a new TranMap and optionally initialises
+*     its attributes.
+*
+*     A TranMap is a Mapping which combines the forward transformation of
+*     a supplied Mapping with the inverse transformation of another
+*     supplied Mapping, ignoring the un-used transformation in each
+*     Mapping (indeed the un-used transformation need not exist).
+*
+*     When the forward transformation of the TranMap is referred to, the
+*     transformation actually used is the forward transformation of the
+*     first Mapping supplied when the TranMap was constructed. Likewise,
+*     when the inverse transformation of the TranMap is referred to, the
+*     transformation actually used is the inverse transformation of the
+*     second Mapping supplied when the TranMap was constructed.
+
+*  Parameters:
+c     map1
+f     MAP1 = INTEGER (Given)
+*        Pointer to the first component Mapping, which defines the
+*        forward transformation.
+c     map2
+f     MAP2 = INTEGER (Given)
+*        Pointer to the second component Mapping, which defines the
+*        inverse transformation.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new TranMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new TranMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astTranMap()
+f     AST_TRANMAP = INTEGER
+*        A pointer to the new TranMap.
+
+*  Notes:
+*     - The number of output coordinates generated by the two Mappings
+*     (their Nout attribute) must be equal, as must the number of input
+*     coordinates accepted by each Mapping (their Nin attribute).
+*     - The forward transformation of the first Mapping must exist.
+*     - The inverse transformation of the second Mapping must exist.
+c     - Note that the component Mappings supplied are not copied by
+c     astTranMap (the new TranMap simply retains a reference to
+c     them). They may continue to be used for other purposes, but
+c     should not be deleted. If a TranMap containing a copy of its
+c     component Mappings is required, then a copy of the TranMap should
+c     be made using astCopy.
+f     - Note that the component Mappings supplied are not copied by
+f     AST_TRANMAP (the new TranMap simply retains a reference to
+f     them). They may continue to be used for other purposes, but
+f     should not be deleted. If a TranMap containing a copy of its
+f     component Mappings is required, then a copy of the TranMap should
+f     be made using AST_COPY.
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+
+*  Implementation Notes:
+*     - This function implements the external (public) interface to
+*     the astTranMap constructor function. It returns an ID value
+*     (instead of a true C pointer) to external users, and must be
+*     provided because astTranMap_ has a variable argument list which
+*     cannot be encapsulated in a macro (where this conversion would
+*     otherwise occur).
+*     - Because no checking or casting of arguments is performed
+*     before the function is invoked, the "map1" and "map2" parameters
+*     are of type (void *) and are converted from an ID value to a
+*     pointer and validated within the function itself.
+*     - The variable argument list also prevents this function from
+*     invoking astTranMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the conversions between IDs
+*     and pointers on input/output of Objects.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstTranMap *new;               /* Pointer to new TranMap */
+   AstMapping *map1;             /* Pointer to first Mapping structure */
+   AstMapping *map2;             /* Pointer to second Mapping structure */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise. */
+   new = NULL;
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return new;
+
+/* Obtain the Mapping pointers from the ID's supplied and validate the
+   pointers to ensure they identify valid Mappings. */
+   map1 = astVerifyMapping( astMakePointer( map1_void ) );
+   map2 = astVerifyMapping( astMakePointer( map2_void ) );
+   if ( astOK ) {
+
+/* Initialise the TranMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+      new = astInitTranMap( NULL, sizeof( AstTranMap ), !class_init, &class_vtab,
+                           "TranMap", map1, map2 );
+
+/* If successful, note that the virtual function table has been initialised. */
+      if ( astOK ) {
+         class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new TranMap's
+   attributes. */
+         va_start( args, options );
+         astVSet( new, options, NULL, args );
+         va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return an ID value for the new TranMap. */
+   return astMakeId( new );
+}
+
+AstTranMap *astInitTranMap_( void *mem, size_t size, int init,
+                           AstTranMapVtab *vtab, const char *name,
+                           AstMapping *map1, AstMapping *map2, int *status ) {
+/*
+*+
+*  Name:
+*     astInitTranMap
+
+*  Purpose:
+*     Initialise a TranMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     AstTranMap *astInitTranMap( void *mem, size_t size, int init,
+*                               AstTranMapVtab *vtab, const char *name,
+*                               AstMapping *map1, AstMapping *map2 )
+
+*  Class Membership:
+*     TranMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new TranMap object. It allocates memory (if necessary) to
+*     accommodate the TranMap plus any additional data associated with the
+*     derived class. It then initialises a TranMap structure at the start
+*     of this memory. If the "init" flag is set, it also initialises the
+*     contents of a virtual function table for a TranMap at the start of
+*     the memory passed via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the TranMap is to be initialised.
+*        This must be of sufficient size to accommodate the TranMap data
+*        (sizeof(TranMap)) plus any data used by the derived class. If a
+*        value of NULL is given, this function will allocate the memory itself
+*        using the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the TranMap (plus derived class
+*        data). This will be used to allocate memory if a value of NULL is
+*        given for the "mem" parameter. This value is also stored in the
+*        TranMap structure, so a valid value must be supplied even if not
+*        required for allocating memory.
+*     init
+*        A logical flag indicating if the TranMap's virtual function table
+*        is to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new TranMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     map1
+*        Pointer to the first Mapping.
+*     map2
+*        Pointer to the second Mapping.
+
+*  Returned Value:
+*     A pointer to the new TranMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstTranMap *new;               /* Pointer to new TranMap */
+   int nin;                      /* No. input coordinates for TranMap */
+   int nout;                     /* No. output coordinates for TranMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitTranMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Report an error if map1 has no forward transformation. */
+   if( !astGetTranForward( map1 ) && astOK ) {
+      astError( AST__INTRD, "astInitTranMap(%s): The first supplied Mapping "
+              "is not able to transform coordinates in the forward direction.", status,
+              name );
+   }
+
+/* Report an error if map2 has no inverse transformation. */
+   if( !astGetTranInverse( map2 ) && astOK ) {
+      astError( AST__INTRD, "astInitTranMap(%s): The second supplied Mapping "
+              "is not able to transform coordinates in the inverse direction.", status,
+              name );
+   }
+
+/* Check that the number of coordinates are compatible and report an error if
+   they are not. */
+   nout = astGetNout( map1 );
+   if ( astGetNout( map2 ) != nout && astOK ) {
+      astError( AST__INNCO, "astInitTranMap(%s): The number of output "
+                      "coordinates per point (%d) for the first Mapping "
+                      "supplied does not match the number of output "
+                      "coordinates (%d) for the second Mapping.", status, name, nout,
+                      astGetNout( map2 ) );
+   }
+
+   nin = astGetNin( map1 );
+   if ( astGetNin( map2 ) != nin && astOK ) {
+      astError( AST__INNCO, "astInitTranMap(%s): The number of input "
+                      "coordinates per point (%d) for the first Mapping "
+                      "supplied does not match the number of input "
+                      "coordinates (%d) for the second Mapping.", status, name, nin,
+                      astGetNin( map2 ) );
+   }
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the TranMap structure, allocating memory if necessary. Specify
+   the number of input and output coordinates and in which directions the
+   Mapping should be defined. */
+   if ( astOK ) {
+      new = (AstTranMap *) astInitMapping( mem, size, 0,
+                                          (AstMappingVtab *) vtab, name,
+                                          nin, nout, 1, 1 );
+
+      if ( astOK ) {
+
+/* Initialise the TranMap data. */
+/* --------------------------- */
+/* Store pointers to the component Mappings. */
+         new->map1 = astClone( map1 );
+         new->map2 = astClone( map2 );
+
+/* Save the initial values of the inversion flags for these Mappings. */
+         new->invert1 = astGetInvert( map1 );
+         new->invert2 = astGetInvert( map2 );
+
+/* If an error occurred, clean up by annulling the Mapping pointers and
+   deleting the new object. */
+         if ( !astOK ) {
+            new->map1 = astAnnul( new->map1 );
+            new->map2 = astAnnul( new->map2 );
+            new = astDelete( new );
+         }
+      }
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstTranMap *astLoadTranMap_( void *mem, size_t size,
+                           AstTranMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadTranMap
+
+*  Purpose:
+*     Load a TranMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "tranmap.h"
+*     AstTranMap *astLoadTranMap( void *mem, size_t size,
+*                               AstTranMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     TranMap loader.
+
+*  Description:
+*     This function is provided to load a new TranMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     TranMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a TranMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the TranMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        TranMap data (sizeof(TranMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the TranMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the TranMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstTranMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new TranMap. If this is NULL, a pointer to
+*        the (static) virtual function table for the TranMap class is
+*        used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "TranMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new TranMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstTranMap *new;               /* Pointer to the new TranMap */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this TranMap. In this case the
+   TranMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstTranMap );
+      vtab = &class_vtab;
+      name = "TranMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitTranMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built TranMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "TranMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* First Invert flag. */
+/* ------------------ */
+      new->invert1 = astReadInt( channel, "inva", 0 );
+      new->invert1 = ( new->invert1 != 0 );
+
+/* Second Invert flag. */
+/* ------------------- */
+      new->invert2 = astReadInt( channel, "invb", 0 );
+      new->invert2 = ( new->invert2 != 0 );
+
+/* First Mapping. */
+/* -------------- */
+      new->map1 = astReadObject( channel, "mapa", NULL );
+
+/* Second Mapping. */
+/* --------------- */
+      new->map2 = astReadObject( channel, "mapb", NULL );
+
+/* If an error occurred, clean up by deleting the new TranMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new TranMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+/* None. */
+
+
+
+
diff --git a/ast/tranmap.h b/ast/tranmap.h
new file mode 100644
index 0000000..ae16f18
--- /dev/null
+++ b/ast/tranmap.h
@@ -0,0 +1,276 @@
+#if !defined( TRANMAP_INCLUDED )  /* Include this file only once */
+#define TRANMAP_INCLUDED
+/*
+*+
+*  Name:
+*     tranmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the TranMap class.
+
+*  Invocation:
+*     #include "tranmap.h"
+
+*  Description:
+*     This include file defines the interface to the TranMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc. needed to use this class.
+
+*  Inheritance:
+*     The TranMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astMapMerge
+*           Merge a TranMap within a sequence of Mappings.
+*        astTransform
+*           Transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsATranMap
+*           Test class membership.
+*        astTranMap
+*           Create a TranMap.
+*
+*     Protected:
+*        astCheckTranMap
+*           Validate class membership.
+*        astInitTranMap
+*           Initialise a TranMap.
+*        astInitTranMapVtab
+*           Initialise the virtual function table for the TranMap class.
+*        astLoadTranMap
+*           Load a TranMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstTranMap
+*           TranMap object type.
+*
+*     Protected:
+*        AstTranMapVtab
+*           TranMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     10-FEB-2004 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate Mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* TranMap structure. */
+/* ----------------- */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstTranMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   AstMapping *map1;             /* Pointer to first Mapping */
+   AstMapping *map2;             /* Pointer to second Mapping */
+   int invert1;                  /* Inversion flag for first Mapping */
+   int invert2;                  /* Inversion flag for second Mapping */
+} AstTranMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstTranMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+/* None. */
+} AstTranMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstTranMapGlobals {
+   AstTranMapVtab Class_Vtab;
+   int Class_Init;
+} AstTranMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitTranMapGlobals_( AstTranMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(TranMap)           /* Check class membership */
+astPROTO_ISA(TranMap)             /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstTranMap *astTranMap_( void *, void *, const char *, int *, ...);
+#else
+AstTranMap *astTranMapId_( void *, void *, const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstTranMap *astInitTranMap_( void *, size_t, int, AstTranMapVtab *,
+                           const char *, AstMapping *, AstMapping *, int * );
+
+/* Vtab initialiser. */
+void astInitTranMapVtab_( AstTranMapVtab *, const char *, int * );
+
+/* Loader. */
+AstTranMap *astLoadTranMap_( void *, size_t, AstTranMapVtab *,
+                           const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+/* None. */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckTranMap(this) astINVOKE_CHECK(TranMap,this,0)
+#define astVerifyTranMap(this) astINVOKE_CHECK(TranMap,this,1)
+
+/* Test class membership. */
+#define astIsATranMap(this) astINVOKE_ISA(TranMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astTranMap astINVOKE(F,astTranMap_)
+#else
+#define astTranMap astINVOKE(F,astTranMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitTranMap(mem,size,init,vtab,name,map1,map2) \
+astINVOKE(O,astInitTranMap_(mem,size,init,vtab,name,astCheckMapping(map1),astCheckMapping(map2),STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitTranMapVtab(vtab,name) astINVOKE(V,astInitTranMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadTranMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadTranMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckTranMap to validate TranMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+/* None. */
+#endif
+
+
+
+
+
diff --git a/ast/unit.c b/ast/unit.c
new file mode 100644
index 0000000..bec4b0a
--- /dev/null
+++ b/ast/unit.c
@@ -0,0 +1,6218 @@
+/*
+*  Name:
+*     unit.c
+
+*  Purpose:
+*     Implement unit conversion functions.
+
+*  Description:
+*     This file implements the Unit module which is used for identifying
+*     units and transforming between them. It follows the recommendations
+*     for unit handling contained within FITS WCS paper I (Greisen &
+*     Calabretta). All methods have protected access.
+
+*  Methods:
+*     astUnitMapper: Create a Mapping between two systems of units.
+*     astUnitLabel: Returns a label for a given unit symbol.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     10-DEC-2002 (DSB):
+*        Original version.
+*     10-FEB-2004 (DSB):
+*        Added debug conditional code to keep track of memory leaks.
+*     15-JUL-2004 (DSB):
+*        In astUnitMapper: if no Mapping can be found from input to
+*        output units (e.g. because fo the less than perfect simplication
+*        algorithm in SimplifyTree), try finding a Mapping from output to
+*        input units and inverting the result.
+*     14-DEC-2004 (DSB):
+*        In CreateTree, move the invocation of FixConstants from after
+*        InvertConstants to before InvertConstants. This is because
+*        InvertConstants ignores nodes which contain all constant
+*        arguments. This results in constants not being inverted in
+*        expressions such as "1/60 deg" (because all arguments are
+*        constant in the the "1/60" node).
+*     18-JAN-2005 (DSB):
+*        Fix memory leaks.
+*     2-FEB-2005 (DSB):
+*        - Avoid using astStore to allocate more storage than is supplied
+*        in the "data" pointer. This can cause access violations since
+*        astStore will then read beyond the end of the "data" area.
+*     15-FEB-2005 (DSB):
+*        - Modified CleanExp to fix up some common units mistakes.
+*     21-FEB-2005 (DSB):
+*        - Modified CleanExp to accept <word><digit> as equivalent to
+*        <word>^<digit>.
+*        - Modified MakeTree to do case insensitive checking if case
+*        sensitive checking failsto produce a match to a multiplier/unit
+*        symbol combination. If this still produces no match, do a case
+*        insensitive match for multiplier/unit label.
+*     1-MAR-2006 (DSB):
+*        Replace astSetPermMap within DEBUG blocks by astBeginPM/astEndPM.
+*     6-APR-2006 (DSB):
+*        Modify CleanExp to convert "MJY/STER" to standard form ("MJy/sr").
+*     7-JUL-2006 (DSB):
+*        Correct initialisation of "word" flag in CleanExp.
+*     17-MAY-2007 (DSB):
+*        Simplify the units string returned by astUnitNormaliser.
+*        - Fix indexing bug in CombineFactors.
+*     26-MAY-2007 (DSB):
+*        - Correct error reporting in astUNitNormaliser.
+*        - Fix bug in CleanExp that caused (for example) "-2" to be
+*        converted to "^-2".
+*     2-DEC-2008 (DSB):
+*        Correct memory allocation bug in CleanExp.
+*     6-MAY-2011 (DSB):
+*        Include "adu" as basic unit.
+*     9-MAY-2011 (DSB):
+*        Change "A" to be Ampere (as defined by FITS-WCS paper 1) rather
+*        than "Angstrom".
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Define the astCLASS macro (even although this is not a class
+   implementation) to obtain access to the protected error and memory
+   handling functions. */
+#define astCLASS
+#define PI 3.141592653589793
+#define E 2.718281828459045
+
+/* Macro which returns the nearest integer to a given floating point
+   value. */
+#define NINT(x) (int)((x)+(((x)>0.0)?0.5:-0.5))
+
+/* Macro identifying a character as lower or upper case letter, digit or
++ or -. */
+#define ISWORD(c) (isalnum(c)||((c)=='+')||((c)=='-'))
+
+/* The number of basic dimension quantities used for dimensional analysis.
+   In addition to the usual M, L and T, this includes pseudo-dimensions
+   describing strictly dimensionless quantities such as plane angle,
+   magnitude, etc. */
+#define NQUANT 10
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "error.h"
+#include "memory.h"
+#include "pointset.h"
+#include "mapping.h"
+#include "unitmap.h"
+#include "zoommap.h"
+#include "mathmap.h"
+#include "unit.h"
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <limits.h>
+#include <math.h>
+
+#ifdef THREAD_SAFE
+#include <pthread.h>
+#endif
+
+/* Module Type Definitions. */
+/* ======================== */
+
+/* This declaration enumerates the codes for the operations which may
+   legally be included in a units expression. */
+typedef enum {
+   OP_LDCON,                     /* Load constant */
+   OP_LDVAR,                     /* Load variable */
+   OP_LOG,                       /* Common logarithm */
+   OP_LN,                        /* Natural logarithm */
+   OP_EXP,                       /* Natural exponential */
+   OP_SQRT,                      /* Square root */
+   OP_POW,                       /* Exponentiate */
+   OP_DIV,                       /* Division */
+   OP_MULT,                      /* Multiplication */
+   OP_LDPI,                      /* Load constant PI */
+   OP_LDE,                       /* Load constant E */
+   OP_NULL                       /* Null operation */
+} Oper;
+
+/* A structure describing a standard multiplier prefix. */
+typedef struct Multiplier {
+   const char *label;       /* Multipler label string (null terminated) */
+   const char *sym;         /* Multipler symbol string (null terminated) */
+   int symlen;              /* Length of symbol (without trailing null ) */
+   int lablen;              /* Length of label (without trailing null ) */
+   double scale;            /* The scale factor associated with the prefix */
+   struct Multiplier *next; /* Next Multiplier in linked list */
+} Multiplier;
+
+/* A structure describing a single node in a tree representation of a
+   single units expression. */
+typedef struct UnitNode {
+   Oper opcode;             /* Code for operation performed by this node */
+   int narg;                /* No. of arguments used by the operation */
+   struct UnitNode **arg;   /* Array of pointers to argument nodes */
+   double con;              /* Constant to be loaded by OP_LDCON operations */
+   struct KnownUnit *unit;  /* Known unit referred to by OP_LDVAR nodes */
+   Multiplier *mult;        /* Multiplier used by OP_LDVAR nodes */
+   const char *name;        /* User-defined unit referred to by OP_LDVAR
+                               nodes (no multiplier prefix included) */
+} UnitNode;
+
+/* A structure describing a known unit. */
+typedef struct KnownUnit {
+   const char *sym;         /* Unit symbol string (null terminated) */
+   const char *label;       /* Unit label string (null terminated) */
+   int symlen;              /* Length of symbol (without trailing null ) */
+   int lablen;              /* Length of label (without trailing null ) */
+   struct UnitNode *head;   /* Head of definition tree (NULL for basic units) */
+   struct KnownUnit *next;  /* Next KnownUnit in linked list */
+   struct KnownUnit *use;   /* KnownUnit to be used in place of this one */
+} KnownUnit;
+
+/* Module Variables. */
+/* ================= */
+
+/* A pointer to the KnownUnit structure at the head of a linked list of
+   such structures containing definitions of all known units. */
+static KnownUnit *known_units = NULL;
+
+/* An array of pointers to KnownUnits which list the basic quantities
+used in dimensional analysis. */
+static KnownUnit *quant_units[ NQUANT ];
+
+/* A pointer to the Multiplier structure at the head of a linked list of
+   such structures containing definitions of all known multipliers. */
+static Multiplier *multipliers = NULL;
+
+/* Set up mutexes */
+#ifdef THREAD_SAFE
+
+static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX1 pthread_mutex_lock( &mutex1 );
+#define UNLOCK_MUTEX1 pthread_mutex_unlock( &mutex1 );
+
+static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
+#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );
+
+#else
+
+#define LOCK_MUTEX1
+#define UNLOCK_MUTEX1
+
+#define LOCK_MUTEX2
+#define UNLOCK_MUTEX2
+
+#endif
+
+/* Prototypes for Private Functions. */
+/* ================================= */
+static AstMapping *MakeMapping( UnitNode *, int * );
+static KnownUnit *GetKnownUnits( int, int * );
+static Multiplier *GetMultipliers( int * );
+static UnitNode *ConcatTree( UnitNode *, UnitNode *, int * );
+static UnitNode *CopyTree( UnitNode *, int * );
+static UnitNode *CreateTree( const char *, int, int, int * );
+static UnitNode *FixUnits( UnitNode *, UnitNode *, int * );
+static UnitNode *FreeTree( UnitNode *, int * );
+static UnitNode *MakeTree( const char *, int, int, int * );
+static UnitNode *MakeLabelTree( const char *, int, int * );
+static UnitNode *NewNode( UnitNode *, Oper, int * );
+static UnitNode *CombineFactors( UnitNode **, double *, int, double, int * );
+static const char *CleanExp( const char *, int * );
+static int EndsWith( const char *, int, const char *, int * );
+static int CmpTree( UnitNode *, UnitNode *, int, int * );
+static void FixConstants( UnitNode **, int, int * );
+static void InvertConstants( UnitNode **, int * );
+static UnitNode *InvertTree( UnitNode *, UnitNode *, int * );
+static void LocateUnits( UnitNode *, UnitNode ***, int *, int * );
+static void MakeKnownUnit( const char *, const char *, const char *, int * );
+static void MakeUnitAlias( const char *, const char *, int * );
+static void RemakeTree( UnitNode **, int * );
+static int SimplifyTree( UnitNode **, int, int * );
+static int ComplicateTree( UnitNode **, int * );
+static int ReplaceNode( UnitNode *, UnitNode *, UnitNode *, int * );
+static void FindFactors( UnitNode *, UnitNode ***, double **, int *, double *, int * );
+static const char *MakeExp( UnitNode *, int, int, int * );
+static int DimAnal( UnitNode *, double[NQUANT], double *, int * );
+static int Ustrcmp( const char *, const char *, int * );
+static int Ustrncmp( const char *, const char *, size_t, int * );
+static int SplitUnit( const char *, int, const char *, int, Multiplier **, int *, int * );
+static UnitNode *ModifyPrefix( UnitNode *, int * );
+static int ConStart( const char *, double *, int *, int * );
+
+/*  Debug functions...
+static const char *DisplayTree( UnitNode *, int );
+static void OpSym( UnitNode *, char * );
+static const char *OpName( Oper );
+static const char *TreeExp( UnitNode * );
+*/
+
+/* Function implementations. */
+/* ========================= */
+static const char *CleanExp( const char *exp, int *status ) {
+/*
+*  Name:
+*     CleanExp
+
+*  Purpose:
+*     Produce a clean copy of a units expression.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     const char *CleanExp( const char *exp )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function returns a pointer to dynamic memory containing a
+*     cleaned copy of the supplied units expression. Cleaning consists of
+*     the following operations:
+*        - removal of leading and trailing white space.
+*        - replacement of multiple adjacent spaces by a single space
+*        - removal of spaces adjacent to a parenthesis
+*        - removal of spaces adjacent to a binary operator
+*        - translates various common non-standard units into equivalent
+*          standard units.
+*
+*     Such carefull handling of spaces is necessary since a space is
+*     recognised by the MakeTree function as a multiplication operator.
+
+*  Parameters:
+*     exp
+*        A pointer to the expression to be cleaned.
+
+*  Returned Value:
+*     A pointer to the cleaned expression, which should be freed using
+*     astFree when no longer needed.
+
+*  Notes:
+*     - This function returns NULL if it is invoked with the global error
+*     status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   char **tok;
+   char *p;
+   char *r;
+   char *result;
+   char *s;
+   char *t;
+   char *w;
+   const char *start;
+   int i;
+   int l;
+   int len;
+   char *tt;
+   int ntok;
+   int po;
+   int ps;
+   int word;
+
+/* Initialise */
+   result = NULL;
+
+/* Check inherited status */
+   if( !astOK ) return result;
+
+/* Split the supplied string up into tokens. Each block of contiguous
+   alphanumeric characters is a token. Each contiguous block of
+   non-alphanumerical characters is also a token. The + and - signs are
+   counted as alphanumeric. */
+   start = exp;
+   p = (char *) exp - 1;
+   word = ISWORD( *( p + 1 ) );
+   ntok = 0;
+   tok = NULL;
+   while( *(++p) ){
+      if( word ) {
+         if( !ISWORD( *p ) ) {
+            l = p - start;
+            t = astStore( NULL, start, l + 1 );
+            if( t ) t[ l ] = 0;
+            tok = astGrow( tok, ntok + 1, sizeof( char * ) );
+            if( tok ) tok[ ntok++ ] = t;
+            start = p;
+            word = 0;
+         }
+      } else {
+         if( ISWORD( *p ) ) {
+            l = p - start;
+            t = astStore( NULL, start, l + 1 );
+            if( t ) t[ l ] = 0;
+            tok = astGrow( tok, ntok + 1, sizeof( char * ) );
+            if( tok ) tok[ ntok++ ] = t;
+            start = p;
+            word = 1;
+         }
+      }
+   }
+
+   l = p - start;
+   t = astStore( NULL, start, l + 1 );
+   if( t ) t[ l ] = 0;
+   tok = astGrow( tok, ntok + 1, sizeof( char * ) );
+   if( tok ) tok[ ntok++ ] = t;
+
+/* Check the tokens for known non-standard unit syntax, and replace with the
+   equivalent standard syntax. Starlink SPLAT has a class called UnitUtilities
+   which has more of these common units mistakes. AST has to be a bit
+   more conservative than SPLAT though because of its wider remit. */
+   len = 0;
+   tt = NULL;
+   for( i = 0; i < ntok; i++ ) {
+      t = tok[ i ];
+      l = strlen( t );
+      tt = astStore( tt, t, l + 1 );
+
+/* Any alphabetical word followed by a digit is taken as <word>^<digit>.
+   Any alphabetical word followed by a sign and a digit is taken as
+   <word>^<sign><digit>. */
+      if( l > 1 && *t != '-' && *t != '+' &&
+          strcspn( t, "0123456789" ) == l - 1 ) {
+         tok[ i ] = astMalloc( l + 2 );
+         if( tok[ i ] ) {
+            strcpy( tok[ i ], t );
+            w = t + l - 2;
+            if( *w != '+' && *w != '-' ) {
+               tok[ i ][ l - 1 ] = '^';
+               strcpy( tok[ i ] + l, t + l - 1 );
+            } else {
+               tok[ i ][ l - 2 ] = '^';
+               strcpy( tok[ i ] + l - 1, t + l - 2 );
+            }
+            t = astFree( t );
+         }
+         l++;
+
+/* If the word ends with "micron" change to "(<start>m*1.0E-6)". Should be OK
+   for things like "Kmicron". */
+      } else if( ( s = strstr( t, "micron" ) ) ) {
+         tok[ i ] = astMalloc( s - t + 11 );
+         if( tok[ i ] ) {
+            w = tok[ i ];
+            *(w++) = '(';
+            if( s > t ) {
+               strncpy( w, t, s - t );
+               w += s - t;
+            }
+            strcpy( w, "m*1.0E-6)" );
+            l = s - t + 11;
+            t = astFree( t );
+         }
+
+/* Convert "STER" to "sr". */
+      } else if( !Ustrcmp( t, "STER", status ) ) {
+         tok[ i ] = astStore( NULL, "sr", 3 );
+         l = 2;
+         t = astFree( t );
+
+/* If the word ends with "JY" and is preceded by a single character, change
+   to "<start>Jy". Should be OK for things like "MJY". */
+      } else if( l == 3 && !strcmp( t + 1, "JY" ) ) {
+         tok[ i ][ 2 ] = 'y';
+
+/* If the word begins with "nano" (case-insensitive) change "nano" to
+   "n". Such changes are usually handled by SplitUnit, but we need to
+   handle this as a special case here since scanf seems to read "nan" as
+   a string representation of NaN. */
+      } else if( !Ustrncmp( t, "nano", 4, status ) ) {
+         tok[ i ] = astStore( NULL, t + 3, l - 2 );
+         if( tok[ i ] ) {
+            *(tok[ i ]) = 'n';
+            t = astFree( t );
+         }
+         l -= 3;
+      }
+
+/* Update the total length of the string. */
+      len += l;
+   }
+   tt = astFree( tt );
+
+/* Concatentate the tokens into a single string, freeing the individual
+   strings. */
+   result = astMalloc( len + 1 );
+   if( result ) {
+      p = result;
+      for( i = 0; i < ntok; i++ ) {
+         len = strlen( tok[ i ] );
+         memcpy( p, tok[ i ], len );
+         p += len;
+         tok[ i ] = astFree( tok[ i ] );
+      }
+      *p = 0;
+      tok = astFree( tok );
+
+/* Now do other cleaning.
+   ---------------------- */
+
+/* Initialise a pointer to the previous character read from the string. */
+      r = result - 1;
+
+/* Initialise a pointer to the next character to be written to the string. */
+      w = result;
+
+/* Pretend the previous character written to the string was a space. */
+      ps = 1;
+
+/* Read all the supplied string, copying it to earlier parts of the
+   string discarding leading spaces and multiple adjacent embedded spaces in
+   the process. */
+      while( *(++r) ) {
+
+/* If the character read is a space, only write it to the string if the
+   previous character written was not a space (in which case set a flag
+   to indicate that the previous character written to the string is now a
+   space). */
+         if( isspace( *r ) ) {
+            if( !ps ) {
+               *(w++) = *r;
+               ps = 1;
+            }
+
+/* Write all non-space characters to the string, and clear the flag which
+   indicates if the previous character written to the string was a space. */
+         } else {
+            *(w++) = *r;
+            ps = 0;
+         }
+      }
+
+/* If the last character written to the string was a space, reduce the
+   length of the string by one since we do not want any trailing spaces. */
+      if( ps ) w--;
+
+/* Terminate the string. */
+      *w = 0;
+
+/* We now need to pass through the string again, this time removing any
+   spaces which are adjacent to a binary operator or a parenthesis. */
+      r = result - 1;
+      w = result;
+      ps = 0;
+      po = 0;
+      while( *(++r) ) {
+
+/* If the current character is a space, only write it if the previous
+   written character was not an operator or parenthesis. */
+         if( isspace( *r ) ) {
+            if( !po ) {
+               *(w++) = *r;
+               po = 1;
+               ps = 1;
+            }
+
+/* If the current character is an operator or parenthesis, back up one
+   character before writing it out if the previous written character was
+   a space. */
+         } else if( *r == '*' || *r == '/' || *r == '^' || *r == '.' ||
+                    *r == ')' || *r == '(' ) {
+            if( ps ) w--;
+            *(w++) = *r;
+            po = 1;
+            ps = 0;
+
+/* If the current character is not a space and not an operator symbol,
+   just write it out. */
+         } else {
+            *(w++) = *r;
+            po = 0;
+            ps = 0;
+         }
+      }
+
+/* Terminate the string. */
+      if( ps ) w--;
+      *w = 0;
+
+   }
+
+/* Return the result. */
+   return (const char *) result;
+}
+
+static int CmpTree( UnitNode *tree1, UnitNode *tree2, int exact, int *status ) {
+/*
+*  Name:
+*     CmpTree
+
+*  Purpose:
+*     Compares two trees of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     int CmpTree( UnitNode *tree1, UnitNode *tree2, int exact, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function returns a zero value if the two trees are
+*     equivalent. This requires the trees to have identical structure
+*     except that, if "exact" is zero, arguments for OP_MULT nodes can
+*     be swapped.
+*
+*     If the trees are not equivalent then a value of +1 or -1 is returned
+*     depending on whether tree1 should be placed before or after tree2
+*     in a sorted list of trees.
+
+*  Parameters:
+*     tree1
+*        A pointer to the UnitNode at the head of the first tree.
+*     tree2
+*        A pointer to the UnitNode at the head of the second tree.
+*     exact
+*        If non-zero, then OP_MULT nodes must have their arguments the
+*        same way round in order for the OP_MULT nodes to match. Otherwise,
+*        OP_MULT nodes with equivalent arguments match even if the
+*        arguments are swapped.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the two trees are equal. +1 if tree1 should be placed before
+*     tree2 in a sorted list of trees. -1 if tree1 should be placed after
+*     tree2 in a sorted list of trees.
+
+* Notes:
+*     - Zero is returned if an error has already occurred, or
+*     if this function fails for any reason.
+
+*/
+
+/* Local Variables: */
+   int result;
+   int i;
+   Oper op;
+
+/* Initialise. */
+   result = 0;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* If the op codes differ, compare them as integers. */
+   op = tree1->opcode;
+   if( op != tree2->opcode ) {
+      result = ( op > tree2->opcode ) ? 1: -1;
+
+/* If both supplied nodes are OP_LDVAR nodes, compare the associated names. */
+   } else if( op == OP_LDVAR ){
+      result = strcmp( tree1->name, tree2->name );
+
+/* If both supplied nodes are constant nodes, compare the constant values. */
+   } else if( tree1->con != AST__BAD ){
+      result = astEQUAL( tree1->con, tree2->con ) ? 0 : (
+                 ( tree1->con > tree2->con ) ? 1 : -1 );
+
+/* Otherwise, compare the arguments for the node. */
+   } else {
+      for( i = 0; i < tree1->narg; i++ ) {
+         result = CmpTree( tree1->arg[ i ], tree2->arg[ i ], exact, status );
+         if( result ) break;
+      }
+
+/* If the head nodes of the two trees are OP_MULT nodes, and the above
+   check determined they are different, this may be just because they
+   have their operands swapped. If "exact" si zero, this is considered an
+   insignificant difference between the two trees which we should ignore.
+   To check for this try comparing the arguments again, this time swapping
+   the arguments of tree2. */
+      if( result && op == OP_MULT && !exact ) {
+         for( i = 0; i < tree1->narg; i++ ) {
+            result = CmpTree( tree1->arg[ i ], tree2->arg[ 1 - i ], 0, status );
+            if( result ) break;
+         }
+      }
+   }
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the answer. */
+   return result;
+}
+
+static UnitNode *CombineFactors( UnitNode **factors, double *powers,
+                                 int nfactor, double coeff, int *status ) {
+/*
+*  Name:
+*     CombineFactors
+
+*  Purpose:
+*     Create a tree which represents the product of the supplied factors.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *CombineFactors( UnitNode **factors, double *powers,
+*                               int nfactor, double coeff, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function createa a tree of UnitNodes which represents the
+*     product of the supplied factors, and the supplied coefficient.
+*     The factors are sorted before being combined, using the sort order
+*     implemented by the CmpTree function.
+
+*  Parameters:
+*     factors
+*        A pointer to an array with "nfactor" elements, each element being
+*        a pointer to a UnitNode which is a factor of the required tree.
+*        On exit, the array is sorted.
+*     powers
+*        A pointer to an array with "nfactor" elements, each element being a
+*        double holding the power of the associated factor in "factors".
+*        On exit, the array reflects the sorting applied to "factors".
+*     nfactor
+*        The number of elements in the "factors" and "powers" arrays.
+*     coeff
+*        The overall coefficient to be applied to the product of the factors.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a UnitNode which is at the head of the new tree.
+
+* Notes:
+*     - A NULL pointer is returned if an error has already occurred, or
+*     if this function fails for any reason.
+
+*/
+
+/* Local Variables: */
+   UnitNode *result;
+   int i;
+   int j;
+   int jp;
+   int done;
+   UnitNode *ftmp;
+   UnitNode *node1;
+   UnitNode *node2;
+   UnitNode *pnode;
+   double ptmp;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* Sort the supplied list of factors, modifying the powers array
+   correspondingly. A simple bubblesort algorithm is used since there
+   will only be a handfull of factors. */
+   for( i = nfactor - 1; i > 0; i-- ) {
+      done = 1;
+      for( j = 0, jp = 1; j < i; j++, jp++ ) {
+         if( CmpTree( factors[ j ], factors[ jp ], 0, status ) > 0 ) {
+            ftmp = factors[ j ];
+            factors[ j ] = factors[ jp ];
+            factors[ jp ] = ftmp;
+
+            ptmp = powers[ j ];
+            powers[ j ] = powers[ jp ];
+            powers[ jp ] = ptmp;
+
+            done = 0;
+         }
+      }
+      if( done ) break;
+   }
+
+/* The first root term of the returned tree is the coefficient, unless the
+   coefficient is 1.0, in which case it will be the first factor. */
+   if( coeff != 1.0 ) {
+      node1 = NewNode( NULL, OP_LDCON, status );
+      if( astOK ) node1->con = coeff;
+   } else {
+      node1 = NULL;
+   }
+
+/* Loop through the factors. */
+   for( i = 0; i < nfactor; i++ ) {
+
+/* If the power of this factor is zero, we ignore the factor. */
+      if( powers[ i ] != 0.0 ) {
+
+/* If the power of this factor is one, we use the factor directly. */
+         if( astEQUAL( powers[ i ], 1.0 ) ) {
+            node2 = CopyTree( factors[ i ], status );
+
+/* Otherwise, for non-zero, non-unity powers, we create a POW node for
+   the factor. */
+         } else {
+            node2 = NewNode( NULL, OP_POW, status );
+            pnode = NewNode( NULL, OP_LDCON, status );
+            if( astOK ) {
+               pnode->con = powers[ i ];
+               node2->arg[ 0 ] = CopyTree( factors[ i ], status );
+               node2->arg[ 1 ] = pnode;
+            }
+         }
+
+/* We now combine node1 and node2 using an OP_MULT node, which becomes
+   the "node1" for the next pass. On the first pass we may have no node1 (if
+   the supplied coefficient was 1.0), in which case we reserve the current
+   node2 as the node1 for the next pass. */
+         if( node1 ) {
+            result = NewNode( NULL, OP_MULT, status );
+            if( astOK ) {
+               result->arg[ 0 ] = node1;
+               result->arg[ 1 ] = node2;
+               node1 = result;
+            }
+         } else {
+            node1 = node2;
+         }
+      }
+   }
+
+/* Ensure we have a node to return. */
+   if( astOK ) {
+      if( !result ) result = node1;
+      if( !result ) {
+         result = NewNode( NULL, OP_LDCON, status );
+         if( astOK ) result->con = 1.0;
+      }
+   }
+
+/* If an error has occurred, free any new tree. */
+   if( !astOK ) result = FreeTree( result, status );
+
+/* Return the answer. */
+   return result;
+}
+
+static int ComplicateTree( UnitNode **node, int *status ) {
+/*
+*  Name:
+*     ComplicateTree
+
+*  Purpose:
+*     Removes standardisations introduced by SimplifyTree.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     int ComplicateTree( UnitNode **node )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function modifies a tree of UnitNodes by removing standardisations
+*     introduced by SimplifyTree. The standardisations removed are ones
+*     which would make the corresponding algebraic expression (as produced
+*     by MakeExp) unnatural to a human reader.
+
+*  Parameters:
+*     node
+*        The address of a pointer to the UnitNode at the head of the tree
+*        which is to be complicated. On exit the supplied tree is freed and
+*        a pointer to a new tree is placed at the given address.
+
+*  Returned Value:
+*     Non-zero if any change was introduced into the tree.
+
+*/
+
+/* Local Variables: */
+   int i;
+   UnitNode *newnode;
+   UnitNode *node1;
+   UnitNode *node2;
+   UnitNode *node3;
+   Oper op;
+   double con;
+   double fk;
+   int k;
+   int result;
+   double kcon;
+
+/* Initialise */
+   result = 0;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* Initiallially, we have no replacement node. */
+   newnode = NULL;
+   node1 = NULL;
+   node3 = NULL;
+
+/* Complicate the sub-trees corresponding to the arguments of the node at
+   the head of the supplied tree. */
+   for( i = 0; i < (*node)->narg; i++ ) {
+      if( ComplicateTree( &( (*node)->arg[ i ] ), status ) ) result = 1;
+   }
+
+/* Now undo specific simplifications appropriate to the nature of the node at
+   the head of the tree. */
+   op = (*node)->opcode;
+
+/* If the head is an OP_MULT node with a constant first argument and
+   a "LN" second argument, rearrange the nodes to represent ln(x**k) instead
+   of k*ln(x). If k is an integer multiple of "0.1/ln(10)" convert the "ln"
+   function into a "log" (base 10) function. Check for "k==1" in which
+   case we do not need a POW node. */
+   if( (*node)->opcode == OP_MULT ) {
+
+      con = (*node)->arg[ 0 ]->con;
+      if( con != AST__BAD && (*node)->arg[ 1 ]->opcode == OP_LN ) {
+         fk = 10.0*con*log( 10.0 );
+         k = NINT(fk);
+         if( astEQUAL(fk,((double)k)) ) {
+            newnode = NewNode( NULL, OP_LOG, status );
+            con = k/10.0;
+         } else {
+            newnode = NewNode( NULL, OP_LN, status );
+         }
+
+         node2 = CopyTree( (*node)->arg[ 1 ]->arg[ 0 ], status );
+         if( !astEQUAL( con, 1.0 ) ){
+            node1 = CopyTree( (*node)->arg[ 0 ], status );
+            node3 = NewNode( NULL, OP_POW, status );
+         }
+
+         if( astOK ) {
+            if( !astEQUAL( con, 1.0 ) ){
+               node1->con = con;
+               node3->arg[ 0 ] = node2;
+               node3->arg[ 1 ] = node1;
+               newnode->arg[ 0 ] = node3;
+            } else {
+               newnode->arg[ 0 ] = node2;
+            }
+         }
+
+/* Replace "(A**-1)*B" with "B/A" */
+      } else if( (*node)->arg[ 0 ]->opcode == OP_POW &&
+                 astEQUAL( (*node)->arg[ 0 ]->arg[ 1 ]->con, -1.0 )) {
+         newnode = NewNode( NULL, OP_DIV, status );
+         if( astOK ) {
+            newnode->arg[ 0 ] = CopyTree( (*node)->arg[ 1 ], status );
+            newnode->arg[ 1 ] = CopyTree( (*node)->arg[ 0 ]->arg[ 0 ], status );
+         }
+
+/* Replace "B*(A**-1)" with "B/A" */
+      } else if( (*node)->arg[ 1 ]->opcode == OP_POW &&
+                 astEQUAL( (*node)->arg[ 1 ]->arg[ 1 ]->con, -1.0 )) {
+         newnode = NewNode( NULL, OP_DIV, status );
+         if( astOK ) {
+            newnode->arg[ 0 ] = CopyTree( (*node)->arg[ 0 ], status );
+            newnode->arg[ 1 ] = CopyTree( (*node)->arg[ 1 ]->arg[ 0 ], status );
+         }
+
+/* Convert (x**k)*(y**k) to (x*y)**k. */
+      } else if( (*node)->arg[ 0 ]->opcode == OP_POW &&
+                 (*node)->arg[ 1 ]->opcode == OP_POW &&
+                 astEQUAL( (*node)->arg[ 0 ]->arg[ 1 ]->con,
+                        (*node)->arg[ 1 ]->arg[ 1 ]->con )) {
+         newnode = NewNode( NULL, OP_POW, status );
+         node1 = NewNode( NULL, OP_MULT, status );
+         if( astOK ) {
+            node1->arg[ 0 ] = CopyTree( (*node)->arg[ 0 ]->arg[ 0 ], status );
+            node1->arg[ 1 ] = CopyTree( (*node)->arg[ 1 ]->arg[ 0 ], status );
+            newnode->arg[ 0 ] = node1;
+            newnode->arg[ 1 ] = CopyTree( (*node)->arg[ 0 ]->arg[ 1 ], status );
+         }
+
+/* Convert c*sqrt(x) to sqrt((c**2)*x) (if c > 0). */
+      } else if( (kcon=(*node)->arg[ 0 ]->con) != AST__BAD &&
+                 kcon > 0.0 && (*node)->arg[ 1 ]->opcode == OP_SQRT ) {
+         newnode = NewNode( NULL, OP_SQRT, status );
+         node1 = NewNode( NULL, OP_MULT, status );
+         node2 = NewNode( NULL, OP_LDCON, status );
+         if( astOK ) {
+            node2->con = kcon*kcon;
+            node1->arg[ 0 ] = node2;
+            node1->arg[ 1 ] = CopyTree( (*node)->arg[ 1 ]->arg[ 0 ], status );
+            newnode->arg[ 0 ] = node1;
+         }
+      }
+
+/* If the head node is a POW node, replace "x**0.5" by sqrt(x) */
+   } else if( (*node)->opcode == OP_POW ) {
+      if( astEQUAL( (*node)->arg[ 1 ]->con, 0.5 ) ) {
+         newnode = NewNode( NULL, OP_SQRT, status );
+         if( astOK ) {
+            newnode->arg[ 0 ] = CopyTree( (*node)->arg[ 0 ], status );
+         }
+      }
+   }
+
+/* If we have produced a new node which is identical to the old node,
+   free it. Otherwise, indicate we have made some changes. */
+   if( newnode ) {
+      if( !CmpTree( newnode, *node, 1, status ) ) {
+         newnode = FreeTree( newnode, status );
+      } else {
+         result = 1;
+      }
+   }
+
+/* If an error has occurred, free any new node. */
+   if( !astOK ) {
+      newnode = FreeTree( newnode, status );
+      result = 0;
+   }
+
+/* If we have a replacement node, free the supplied tree and return a
+   pointer to the new tree. */
+   if( newnode ) {
+      FreeTree( *node, status );
+      *node = newnode;
+   }
+
+/* If the above produced some change, try simplifying (without
+   re-introducing the standardisation we have just got rid of!) and
+   then re-complicating the tree. */
+   if( result ) {
+      SimplifyTree( node, 0, status );
+      ComplicateTree( node, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static UnitNode *ConcatTree( UnitNode *tree1, UnitNode *tree2, int *status ) {
+/*
+*  Name:
+*     ConcatTree
+
+*  Purpose:
+*     Concatenate two trees together.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *ConcatTree( UnitNode *tree1, UnitNode *tree2, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function a pointer to the head of a new tree of UnitNodes which
+*     is formed by feeding the output of "tree1" (i.e. the quantity
+*     represented by the node at the head of tree1) into the (single)
+*     input of "tree2" (i.e. the single OP_LDVAR Node containined within
+*     tree2).
+
+*  Parameters:
+*     tree1
+*        A pointer to the first tree.
+*     tree2
+*        A pointer to the second tree. This should have no more than one
+*        OP_LDVAR node.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a UnitNode which is at the head of the new tree.
+
+* Notes:
+*     - If "tree2" contains zero units, a NULL pointer is returned but no
+*     error is reported.
+*     - If "tree2" contains more than one unit, an error is reported
+*     error is reported.
+*     - A NULL pointer is returned if an error has already occurred, or
+*     if this function fails for any reason.
+
+*/
+
+/* Local Variables: */
+   UnitNode *result;
+   UnitNode **units;
+   int nunits;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* Produce a copy of tree2. */
+   result = CopyTree( tree2, status );
+
+/* Locate the OP_LDVAR node in the copy of tree2. */
+   units = NULL;
+   nunits = 0;
+   LocateUnits( result, &units, &nunits, status );
+
+/* If no OP_LDVAR nodes were found in tree2, we cannot concatenate the
+   trees. */
+   if( nunits > 0 ) {
+
+/* Report an error if the number of pointers returned is larger than 1. */
+      if( nunits > 1 && astOK ) {
+         astError( AST__INTER, "ConcatTree(unit): tree2 uses %d units - "
+                   "should be 1 (internal AST programming error).", status, nunits );
+      }
+
+/* Replace the OP_LDVAR node in the copy of tree2 with a copy of tree1. */
+      if( astOK ) {
+
+/* If the node at the head of the supplied tree2 is the node to be
+   replaced, just free the tree created earlier and return a copy of
+   tree1. */
+         if( units[ 0 ] == result ) {
+            FreeTree( result, status );
+            result = CopyTree( tree1, status );
+
+/* Otherwise, search for the node to be replaced and do the substitution
+   within the tree created earlier. */
+         } else {
+            ReplaceNode( result, units[ 0 ], CopyTree( tree1, status ), status );
+         }
+      }
+   }
+
+/* Free resources. */
+   units = astFree( units );
+
+/* If an error has occurred, free any new tree. */
+   if( !astOK ) result = FreeTree( result, status );
+
+/* Return the answer. */
+   return result;
+}
+
+static int ConStart( const char *text, double *val, int *nc, int *status ) {
+/*
+*  Name:
+*     ConStart
+
+*  Purpose:
+*     See if the supplied string starts with a literal numeric constant.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     int ConStart( const char *text, double *val, int *nc, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function checks if the supplied string starts with a literal
+*     numeric constant and returns it if it does. It is a wrap-up for scanf
+*     since scanf has non-standard behaviour on some platforms (e.g. Cygwin
+*     scanf interprets the character "n" as a floating point number!).
+
+*  Parameters:
+*     text
+*        The text to check.
+*     val
+*        Address of a double to receive any numerical constant read
+*        from the start of the string. Unity is returned if the string
+*        does not start with a numerical constant.
+*     nc
+*        Address of an int to receive the number of characters used to
+*        create the value returned in "val". Zero is returned if the
+*        string does not start with a numerical constant.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the text started with a numerical constant.
+
+*/
+
+/* Local Variables: */
+   int result;
+   const char *c;
+
+/* Initialise */
+   *nc = 0;
+   *val = 1.0;
+
+/* Return zero if no text was supplied */
+   if( !text ) return 0;
+
+/* Use sscanf to see if the string begin with a numerical constant */
+   result = astSscanf( text, "%lf%n", val, nc );
+
+/* If so, check that the first non-blank character in the string
+   is not "N" (interpreted by Cygwin as numerical zero!). */
+   if( result ) {
+      c = text;
+      while( isspace( *c ) ) c++;
+      if( *c == 'n' || *c == 'N' ) {
+         result = 0;
+         *nc = 0;
+         *val = 1.0;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static UnitNode *CopyTree( UnitNode *tree, int *status ) {
+/*
+*  Name:
+*     CopyTree
+
+*  Purpose:
+*     Create a new tree of UnitNodes containing a copy of a given tree.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *CopyTree( UnitNode *tree, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function creates a copy of the supplied tree of UnitNodes.
+
+*  Parameters:
+*     tree
+*        The UnitNode at the head of the tree to be copied.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the UnitNode at the head of the new tree.
+
+*  Notes:
+*     - A value of NULL will be returned if this function is invoked with
+*     the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   UnitNode **args;
+   UnitNode *result;
+   int i;
+   int narg;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the inherited status. */
+   if( !astOK || !tree ) return result;
+
+/* Create a new node to represent the head of the supplied tree. */
+   result = astMalloc( sizeof( UnitNode ) );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Copy the fields of the supplied node. */
+      narg = tree->narg;
+
+      result->arg = NULL;
+      result->unit = tree->unit;
+      result->mult = tree->mult;
+      result->opcode = tree->opcode;
+      result->narg = narg;
+      result->con = tree->con;
+      result->name = tree->name ? astStore( NULL, tree->name,
+                                            strlen( tree->name ) + 1 ) : NULL;
+
+/* Create an array of UnitNode pointers for the arguments. */
+      args = astMalloc( narg*sizeof( UnitNode * ) );
+      if( astOK ) {
+         result->arg = args;
+
+/* Copy the sub-trees headed by the argument nodes. */
+         for( i = 0; i < narg; i++ ) {
+            args[ i ] = CopyTree( tree->arg[ i ], status );
+         }
+      }
+   }
+
+/* Free any result if an error occurred. */
+   if( !astOK ) result = FreeTree( result, status );
+
+/* Return the answer. */
+   return result;
+}
+
+static UnitNode *CreateTree( const char *exp, int basic, int lock, int *status ){
+/*
+*  Name:
+*     CreateTree
+
+*  Purpose:
+*     Convert an algebraic units expression into a tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *CreateTree( const char *exp, int basic, int lock, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function converts the supplied algebraic units expression into
+*     a tree of UnitNodes. The result tree can optionally be expanded to
+*     create a tree in which the "roots" (LDVAR nodes) all refer to
+*     basic units.
+
+*  Parameters:
+*     exp
+*        The units expression. This should not include any leading or
+*        trailing spaces.
+*     basic
+*        Should the tree created from parsing "exp" be expanded so that
+*        the leaf nodes of the tree are all basic units?
+*     lock
+*        Use a mutex to guard access to the KnownUnits list?
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a UnitNode which forms the head of a tree of UnitNodes
+*     representing the supplied unit expression.
+
+*  Notes:
+*     -  A NULL value is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   UnitNode *result;
+   const char *cleanex;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status, and that we have a string. */
+   if ( !astOK ) return result;
+
+/* Produce a clean copy of the supplied string. This has no leading
+   or trailing white space, and any spaces adjacent to operators within
+   the string are removed (this is needed because spaces are treated as
+   multiplication symbols). */
+   cleanex = CleanExp( exp, status );
+
+/* If the string is blank, return the NULL pointer. Otherwise, create a
+   tree of UnitNodes describing the units. The returned tree has LDVAR
+   nodes which refer to the unit symbols contained in the supplied string. */
+   if( cleanex && (*cleanex) ) {
+      result = MakeTree( cleanex, strlen( cleanex ), lock, status );
+
+/* Replace each subtree which simply combines constants (i.e. which has no
+   OP_LDVAR nodes) with a single OP_LDCON node. */
+      FixConstants( &result, 0, status );
+
+/* Invert literal constant unit multipliers. */
+      InvertConstants( &result, status );
+
+/* Now replace each LDVAR node which refers to a known derived unit with
+   a sub-tree which defines the derived unit in terms of known basic units.
+   The LDVAR nodes in the resulting tree all refer to basic units. */
+      if( basic ) RemakeTree( &result, status );
+   }
+
+/* Free resources. */
+   cleanex = astFree( (void *) cleanex );
+
+/* Free any returned tree if an error has occurred. */
+   if( !astOK ) result = FreeTree( result, status );
+
+/* Return the result. */
+   return result;
+}
+
+static int DimAnal( UnitNode *node, double powers[NQUANT], double *scale, int *status ) {
+/*
+*  Name:
+*     DimAnal
+
+*  Purpose:
+*     Perform a dimensional analysis of a unit tree.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     int DimAnal( UnitNode *node, double powers[NQUANT], double *scale, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function returns a set of powers and a scaling factor which
+*     represent the units tree.
+
+*  Parameters:
+*     node
+*        Pointer to the UnitNode at the head of the unit tree.
+*     powers
+*        An array in which are returned the powers for each of the following
+*        basic units (in the order shown): kilogramme, metre, second, radian,
+*        Kelvin, count, adu, photon, magnitude, pixel. If the supplied unit
+*        does not depend on a given basic unit a value of 0.0 will be returned
+*        in the array. The returns values represent a system of units which is
+*        a scaled form of the supplied units, expressed in the basic units of
+*        m, kg, s, rad, K, count, adu, photon, mag and pixel. For instance, a
+*        returned array of [1,0,-2,0,0,0,0,0,0,0] would represent "m/s**2".
+*     scale
+*        Pointer to a location at which to return a scaling factor for the
+*        supplied units. The is the value, in the units represented by the
+*        returned powers, which corresponds to a value of 1.0 in the supplied
+*        units.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the tree was analysed succesfully. Zero otherwise.
+
+*  Notes:
+*     -  Zero is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables; */
+   Oper oper;
+   int result;
+   int i;
+   double p0[ NQUANT ];
+   double p1[ NQUANT ];
+   double s0;
+   double s1;
+
+/* Check inherited status */
+   if( !astOK ) return 0;
+
+/* Initialise the powers of all dimensions to zero, and set the scaling
+   factor to unity. */
+   result = 1;
+   *scale = 1.0;
+   for( i = 0; i < NQUANT; i++ ) powers[ i ] = 0.0;
+
+/* Load constant: constant is dimensionaless so leave powers unchanged,
+   and set the scaling factor. */
+   oper = node->opcode;
+   if( oper == OP_LDCON ) {
+      *scale = 1.0/node->con;
+
+/* Load variable: check it is one of the basic known dimensional
+   quantities. If so, set the power of the quantity to unity and store
+   the scale factor. If the unit is "g" modify the scale factor so that
+   the analysis quantity is "kg". */
+   } else if( oper == OP_LDVAR ) {
+      result = 0;
+      for( i = 0; i < NQUANT; i++ ) {
+         if( node->unit == quant_units[ i ] ) {
+            powers[ i ] = 1.0;
+            *scale = node->mult ? 1.0/node->mult->scale : 1.0;
+            if( !strcmp( node->unit->sym, "g" ) ) *scale *= 0.001;
+            result = 1;
+            break;
+         }
+      }
+
+/* How does dimensional analysis handle log or exp units?*/
+   } else if( oper == OP_LOG ) {
+      result= 0;
+
+   } else if( oper == OP_LN ) {
+      result= 0;
+
+   } else if( oper == OP_EXP ) {
+      result= 0;
+
+/* Get the powers for the child unit and then multiply each by 0.5 and
+   take the square root of the scale factor. */
+   } else if( oper == OP_SQRT ) {
+      result = DimAnal( node->arg[0], powers, scale, status );
+      if( result ) {
+         for( i = 0; i < NQUANT; i++ ) powers[ i ]*= 0.5;
+         *scale = sqrt( *scale );
+      }
+
+/* Similarly for pow nodes. */
+   } else if( oper == OP_POW ) {
+      result = DimAnal( node->arg[0], powers, scale, status );
+      if( result ) {
+         double power = node->arg[1]->con;
+         for( i = 0; i < NQUANT; i++ ) powers[ i ]*= power;
+         *scale = pow( *scale, power );
+      }
+
+/* Binary operators. Analyses the operands dimensions and combine. */
+   } else if( oper == OP_DIV ) {
+      if( DimAnal( node->arg[0], p0, &s0, status ) &&
+          DimAnal( node->arg[1], p1, &s1, status ) ) {
+         for( i = 0; i < NQUANT; i++ ) powers[ i ] = p0[ i ] - p1[ i ];
+         *scale = s0/s1;
+      } else {
+         result = 0;
+      }
+
+   } else if( oper == OP_MULT ) {
+      if( DimAnal( node->arg[0], p0, &s0, status ) &&
+          DimAnal( node->arg[1], p1, &s1, status ) ) {
+         for( i = 0; i < NQUANT; i++ ) powers[ i ] = p0[ i ] + p1[ i ];
+         *scale = s0*s1;
+      } else {
+         result = 0;
+      }
+
+/* Named constants are dimensionless */
+   } else if( oper == OP_LDPI ) {
+      *scale = 1.0/PI;
+
+   } else if( oper == OP_LDE ) {
+      *scale = 1.0/E;
+
+   }
+
+   return result;
+
+}
+
+static int EndsWith( const char *c, int nc, const char *test, int *status ){
+/*
+*  Name:
+*     EndsWith
+
+*  Purpose:
+*     See if a string ends with another string
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     int EndsWith( const char *c, int nc, const char *test, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function sees if the string given by "c" ends with the string
+*     given by "test". The comparison is case-insensitive.
+
+*  Parameters:
+*     c
+*        A pointer to the last character in the string to be tested.
+*     nc
+*        The number of characters in the string to be tested.
+*     test
+*        A pointer to the string to be tested for.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the string "c" ends with the string "test".
+
+*/
+
+/* Local Variables: */
+   const char *start;
+   int i;
+   int result;
+   int tlen;
+
+/* initialise. */
+   result = 0;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* Check the string being tested for is not longer than the string being
+   tested. */
+   tlen = strlen( test );
+   if( tlen <= nc ){
+
+/* Get a pointer to where the matching string would start if the string "c"
+   ends with the required string "test". */
+      start = c - tlen + 1;
+
+/* Do the comparison. */
+      result = 1;
+      for( i = 0; i < tlen; i++ ) {
+         if( tolower( start[ i ] ) != tolower( test[ i ] ) ) {
+            result = 0;
+            break;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+static void FindFactors( UnitNode *node, UnitNode ***factors, double **powers,
+                         int *nfactor, double *coeff, int *status ){
+/*
+*  Name:
+*     FindFactors
+
+*  Purpose:
+*     Find the factors within an expression given by a tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     void FindFactors( UnitNode *node, UnitNode ***factors, double **powers,
+*                       int *nfactor, double *coeff, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function analyses the supplied tree of UnitNoes and returns
+*     an array of pointers to nodes within the supplied tree which form
+*     factors of the tree. The power associated with each factor is also
+*     returned, together with an overall coefficient for the tree. The
+*     expression represented by the tree is thus the product of the
+*     coefficient with each of the factors, each raised to the associated
+*     power.
+
+*  Parameters:
+*     node
+*        A pointer to the UnitNode at the head of the tree which is to be
+*        analysed.
+*     factors
+*        The address at which to return a pointer to an array with "*nfactor"
+*        elements, each element being a pointer to a UnitNode within the
+*        supplied tree which is a factor of the supplied tree.
+*     powers
+*        The address at which to return a pointer to an array with "*nfactor"
+*        elements, each element being a double holding the power of the
+*        associated factor in "*factors".
+*     nfactor
+*        The address of an int containing the number of elements in the
+*        returned "*factors" and "*powers" arrays.
+*     coeff
+*        The address of a double containing the overall coefficient to be
+*        applied to the product of the factors.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - If the supplied node is a constant node, then "*coeff" is
+*     returned holding the value of the constant and "*nfactor" is returned
+*     equal to zero ("*factors" and "*powers" are returned holding NULL).
+*     - If an error has already occurred, or if this function fails, then
+*     "*factors" and "*powers" are returned holding NULL, "*nfactor" is
+*     returned holding zero and "*coeff" is returned holding 1.0.
+
+*/
+
+/* Local Variables: */
+   int i;
+   int j;
+   int found;
+   UnitNode **fact1;
+   double *pow1;
+   double coeff1;
+   int nfac1;
+   double con;
+
+/* Initialise */
+   *factors = NULL;
+   *powers = NULL;
+   *nfactor = 0;
+   *coeff = 1.0;
+
+/* Check inherited status. */
+   if( !astOK ) return;
+
+/* If the node at the head of the supplied tree is an OP_MULT node... */
+   if( node->opcode == OP_MULT ) {
+
+/* Find the factors of the two arguments of the OP_MULT node. */
+      FindFactors( node->arg[ 0 ], factors, powers, nfactor, coeff, status );
+      FindFactors( node->arg[ 1 ], &fact1, &pow1, &nfac1, &coeff1, status );
+
+/* Combine the two lists. Loop round the factors of the seocnd argument. */
+      for( i = 0; i < nfac1; i++ ) {
+
+/* See if there is already an equivalent factor in the returned list of
+   factors. */
+         found = 0;
+         for( j = 0; j < *nfactor; j++ ) {
+            if( !CmpTree( (*factors)[ j ], fact1[ i ], 0, status ) ){
+               found = 1;
+               break;
+            }
+         }
+
+/* If so, increment the power of the factor. */
+         if( found ) {
+            (*powers)[ j ] += pow1[ i ];
+
+/* Otherwise, add the factor to the end of the returned list. */
+         } else {
+            *factors = astGrow( *factors, *nfactor + 1, sizeof( UnitNode *) );
+            *powers = astGrow( *powers, *nfactor + 1, sizeof( double ) );
+            if( astOK ) {
+               (*factors)[ *nfactor ] = fact1[ i ];
+               (*powers)[ (*nfactor)++ ] = pow1[ i ];
+            }
+         }
+      }
+
+/* Modify the overall coefficient. */
+      *coeff *= coeff1;
+
+/* Free resources */
+      fact1 = astFree( fact1 );
+      pow1 = astFree( pow1 );
+
+/* If the node at the head of the supplied tree is an OP_POW node, */
+   } else if( node->opcode == OP_POW ) {
+
+/* Find the factors of the first argument. */
+      FindFactors( node->arg[ 0 ], factors, powers, nfactor, coeff, status );
+
+/* Multiply all the factor powers by the constant exponent of the POW
+   node. */
+      con = node->arg[ 1 ]->con;
+      for( j = 0; j < *nfactor; j++ ) {
+         (*powers)[ j ] *= con;
+      }
+
+/* Exponentiate the coefficient. */
+      if( *coeff >= 0.0 || (int) con == con ) {
+         *coeff = pow( *coeff, con );
+      } else {
+         astError( AST__BADUN, "Simplifying a units expression requires a "
+                   "negative value to be raised to a non-intergal power." , status);
+      }
+
+/* If the node at the head of the supplied tree is an OP_DIV node, */
+   } else if( node->opcode == OP_DIV ) {
+
+/* Find the factors of the two arguments of the OP_DIV node. */
+      FindFactors( node->arg[ 0 ], factors, powers, nfactor, coeff, status );
+      FindFactors( node->arg[ 1 ], &fact1, &pow1, &nfac1, &coeff1, status );
+
+/* Combine the two lists. Loop round the factors of the second argument
+   (the denominator). */
+      for( i = 0; i < nfac1; i++ ) {
+
+/* See if there is already an equivalent factor in the returned list of
+   factors. */
+         found = 0;
+         for( j = 0; j < *nfactor; j++ ) {
+            if( !CmpTree( (*factors)[ j ], fact1[ i ], 0, status ) ){
+               found = 1;
+               break;
+            }
+         }
+
+/* If so, decrement the power of the factor. */
+         if( found ) {
+            (*powers)[ j ] -= pow1[ i ];
+
+/* Otherwise, add the factor to the end of the returned list, with a
+   negated power. */
+         } else {
+            *factors = astGrow( *factors, *nfactor + 1, sizeof( UnitNode *) );
+            *powers = astGrow( *powers, *nfactor + 1, sizeof( double ) );
+            if( astOK ) {
+               (*factors)[ *nfactor ] = fact1[ i ];
+               (*powers)[ (*nfactor)++ ] = -pow1[ i ];
+            }
+         }
+      }
+
+/* Modify the overall coefficient. */
+      if( coeff1 != 0.0 ) {
+         *coeff /= coeff1;
+      } else {
+         astError( AST__BADUN, "Simplifying a units expression"
+                   "requires a division by zero." , status);
+      }
+
+/* Free resources */
+      fact1 = astFree( fact1 );
+      pow1 = astFree( pow1 );
+
+/* If the node at the head of the supplied tree is an OP_SQRT node, */
+   } else if( node->opcode == OP_SQRT ) {
+
+/* Find the factors of the argument. */
+      FindFactors( node->arg[ 0 ], factors, powers, nfactor, coeff, status );
+
+/* Multiply all the factor powers by 0.5. */
+      for( j = 0; j < *nfactor; j++ ) {
+         (*powers)[ j ] *= 0.5;
+      }
+
+/* Square root the coefficient. */
+      if( *coeff >= 0.0 ) {
+         *coeff = sqrt( *coeff );
+      } else {
+         astError( AST__BADUN, "Simplifying a units expression requires "
+                   "the square root of a negative value to be taken." , status);
+      }
+
+/* If the node at the head of the supplied tree is constant we have no
+   factors but we have a coeffcient. */
+   } else if( node->con != AST__BAD ) {
+      *coeff = node->con;
+
+/* Other nodes have no factors other than themselves, so just return a
+   pointer to the supplied node. */
+   } else {
+      *factors = astMalloc( sizeof( UnitNode *) );
+      *powers = astMalloc( sizeof( double ) );
+      if( astOK ) {
+         *nfactor = 1;
+         (*factors)[ 0 ] = node;
+         (*powers)[ 0 ] = 1.0;
+         *coeff = 1.0;
+      }
+   }
+
+/* If an error has occurred, free any returned resources. */
+   if( !astOK ) {
+      *factors = astFree( *factors );
+      *powers = astFree( *powers );
+      *nfactor = 0;
+      *coeff = 1.0;
+   }
+}
+
+static void FixConstants( UnitNode **node, int unity, int *status ) {
+/*
+*  Name:
+*     FixConstants
+
+*  Purpose:
+*     Take the reciprocal of all constants in a tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     void FixConstants( UnitNode **node, int unity, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function replaces sub-trees which have a constant value by
+*     a single OP_LDCON node which loads the appropriate constant.
+
+*  Parameters:
+*     node
+*        The address of a pointer to the UnitNode at the head of the tree
+*        which is to be fixed. On exit the supplied tree is freed and a
+*        pointer to a new tree is palced at he given address.
+*     unity
+*        If non-zero, then all multiplicative constants are set to 1.0, and
+*        their original values are forgotten, but only if the other
+*        argument of the OP_MULT node is an OP_LDVAR, OP_POW or OP_SQRT Node.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int i;
+   UnitNode *newnode;
+   int allcon;
+   Oper op;
+   double newcon;
+
+/* Check inherited status and pointer. */
+   if( !astOK || !node || !(*node) ) return;
+
+/* Initiallially, we have no replacement node */
+   newnode = NULL;
+   newcon = AST__BAD;
+
+/* There is nothing to fix if the node has no arguments. */
+   if( (*node)->narg > 0 ) {
+
+/* Note the op code for the node. */
+      op = (*node)->opcode;
+
+/* Fix up the argument nodes. Also note if all the arguments are
+   constants. */
+      allcon = 1;
+      for( i = 0; i < (*node)->narg; i++ ) {
+         FixConstants( &( (*node)->arg[ i ] ), unity, status );
+         if( (*node)->arg[ i ]->con == AST__BAD ) allcon = 0;
+      }
+
+/* If an OP_MULT nodes within a simplified tree has a constant argument,
+   it will always be argument zero.  If this is an OP_MULT node and arg[0]
+   is constant and "unity" is non-zero and arg[1] is an OP_LDVAR, OP_POW
+   or OP_SQRT node, replace the constant value by 1.0. */
+      if( unity && op == OP_MULT &&
+          (*node)->arg[ 0 ]->con != AST__BAD &&
+          ( (*node)->arg[ 1 ]->opcode == OP_LDVAR ||
+            (*node)->arg[ 1 ]->opcode == OP_SQRT ||
+            (*node)->arg[ 1 ]->opcode == OP_POW ) ) {
+         (*node)->arg[ 0 ]->con = 1.0;
+      }
+
+/* If the arguments of this node are all constants, replace the node by
+   an OP_LDCON node which loads the resulting constant value. */
+      if( allcon ) {
+         if( (*node)->narg > 0 ) {
+            newnode = NewNode( NULL, OP_LDCON, status );
+            if( astOK ) {
+               if( op == OP_LOG ) {
+                  if( (*node)->arg[ 0 ]->con > 0.0 ) {
+                     newcon = log10( (*node)->arg[ 0 ]->con );
+                  } else {
+                     astError( AST__BADUN, "Illegal negative or zero constant "
+                               "value '%g' encountered.", status,
+                               (*node)->arg[ 0 ]->con );
+                  }
+               } else if( op == OP_LN ){
+                  if( (*node)->arg[ 0 ]->con > 0.0 ) {
+                     newcon = log( (*node)->arg[ 0 ]->con );
+                  } else {
+                     astError( AST__BADUN, "Illegal negative or zero constant value "
+                               "'%g' encountered.", status, (*node)->arg[ 0 ]->con );
+                  }
+               } else if( op == OP_EXP ){
+                  newcon = exp( (*node)->arg[ 0 ]->con );
+
+               } else if( op == OP_SQRT ){
+                  if( (*node)->arg[ 0 ]->con >= 0.0 ) {
+                     newcon = sqrt( (*node)->arg[ 0 ]->con );
+                  } else {
+                     astError( AST__BADUN, "Illegal negative constant value "
+                               "'%g' encountered.", status, (*node)->arg[ 0 ]->con );
+                  }
+
+               } else if( op == OP_POW ){
+                  if( (*node)->arg[ 0 ]->con >= 0.0 ||
+                      (int) (*node)->arg[ 1 ]->con == (*node)->arg[ 1 ]->con ) {
+                     newcon = pow( (*node)->arg[ 0 ]->con,
+                                   (*node)->arg[ 1 ]->con );
+                  } else {
+                     astError( AST__BADUN, "Illegal negative constant value "
+                               "'%g' encountered.", status, (*node)->arg[ 0 ]->con );
+                  }
+
+               } else if( op == OP_DIV ){
+                  if( (*node)->arg[ 1 ]->con != 0.0 ) {
+                     newcon = (*node)->arg[ 0 ]->con / (*node)->arg[ 1 ]->con;
+                  } else {
+                     astError( AST__BADUN, "Illegal zero constant value encountered." , status);
+                  }
+
+               } else if( op == OP_MULT ){
+                  newcon = (*node)->arg[ 0 ]->con * (*node)->arg[ 1 ]->con;
+
+               }
+
+
+               if( astOK ) newnode->con = newcon;
+            }
+         }
+      }
+   }
+
+/* If an error has occurred, free any new node. */
+   if( !astOK ) newnode = FreeTree( newnode, status );
+
+/* If we have a replacement node, free the supplied tree and return a
+   pointer to the new tree. */
+   if( newnode ) {
+      FreeTree( *node, status );
+      *node = newnode;
+   }
+
+}
+
+static UnitNode *FixUnits( UnitNode *node, UnitNode *test, int *status ) {
+/*
+*  Name:
+*     FixUnits
+
+*  Purpose:
+*     Assign a constant value to all units except for one.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *FixUnits( UnitNode *node, UnitNode *test, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function returns a copy of the supplied tree of UnitNodes. All
+*     OP_LDVAR nodes within the copy which refer to units which differ
+*     from those referred to by the supplied test node are replaced by
+*     OP_LDCON nodes which load the constant value 1.0.
+
+*  Parameters:
+*     node
+*        A pointer to the UnitNode at the head of the tree to be used.
+*     test
+*        A pointer to an OP_LDVAR node which defines the units which are
+*        *not* to be replaced by a constant value of 1.0.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a UnitNode which is at the head of a tree of UnitNodes
+*     which forms the required copy of th einput tree.
+
+* Notes:
+*     - A NULL pointer is returned if an error has already occurred, or
+*     if this function fails for any reason.
+
+*/
+
+/* Local Variables: */
+   int i;
+   UnitNode *result;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* Create a complete copy of the supplied tree. */
+   result = CopyTree( node, status );
+
+/* Is the node at the head of the supplied tree an OP_LDVAR node? */
+   if( node->opcode == OP_LDVAR ) {
+
+/* Does it refer to a unit which differs from that of the test node? If so
+   annul the copy created above and return a new OP_LDCON node which loads
+   the constant value 1.0. */
+      if( strcmp( test->name, node->name ) ) {
+         FreeTree( result, status );
+         result = NewNode( NULL, OP_LDCON, status );
+         if( astOK ) result->con = 1.0;
+      }
+
+/* If the supplied node is not an OP_LDVAR node, check each argument of
+   the head node. */
+   } else {
+      for( i = 0; i < node->narg; i++ ) {
+
+/* Free the resources used to hold this argument in the tree copy created
+   above. */
+         FreeTree( result->arg[ i ], status );
+
+/* Create a new argument tree by calling this function recursively to
+   fix units in the argument sub-trees. */
+         result->arg[ i ] = FixUnits( node->arg[ i ], test, status );
+      }
+   }
+
+/* If an error has occurred, free any new tree. */
+   if( !astOK ) result = FreeTree( result, status );
+
+/* Return the answer. */
+   return result;
+}
+
+static UnitNode *FreeTree( UnitNode *node, int *status ) {
+/*
+*  Name:
+*     FreeTree
+
+*  Purpose:
+*     Free resources used by a tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *FreeTree( UnitNode *node, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function frees the memory used to store a tree of UnitNodes.
+
+*  Parameters:
+*     node
+*        A pointer to the UnitNode at the head of the tree which is to be
+*        freed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A NULL pointer is returned.
+
+*  Notes:
+*     - This function attempts to execute even if it is invoked with
+*     the global error status set.
+*/
+
+/* Local Variables: */
+   int i;
+
+/* Check a node was supplied. */
+   if( node ) {
+
+/* Recursively free any argument nodes. */
+      if( node->arg ) {
+         for( i = 0; i < node->narg; i++ ) {
+            (node->arg)[ i ] = FreeTree( (node->arg)[ i ], status );
+         }
+         node->arg = astFree( node->arg );
+      }
+
+/* Nullify other pointers for safety. */
+      node->unit = NULL;
+      node->mult = NULL;
+
+/* Free the copy of the symbol string (if any). */
+      node->name = astFree( (char *) node->name );
+
+/* Free the memory holding the node. */
+      node = astFree( node );
+   }
+
+/* Return a null pointer. */
+   return NULL;
+}
+
+static KnownUnit *GetKnownUnits( int lock, int *status ) {
+/*
+*  Name:
+*     GetKnownUnits
+
+*  Purpose:
+*     Get a pointer to the head of a linked list of known unit definitions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     KnownUnit *GetKnownUnits( int lock, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function returns a pointer to the head of a linked list of known
+*     unit definitions. The unit definitions are created as static module
+*     variables if they have not previously been created.
+
+*  Parameters:
+*     lock
+*        If non-zero, then lock a mutex prior to accessing the list of
+*        known units.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the first known unit definition.
+
+*  Notes:
+*     - A NULL pointer is returned if it is invoked with the global error
+*     status set, or if an error occurs.
+*/
+
+/* Local Variables: */
+   int iq;
+   KnownUnit *result;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* Ensure the known units list is only initialised once. */
+   if( lock ) {
+      LOCK_MUTEX1
+   }
+
+/* If the linked list of KnownUnit structures describing the known units
+   has not yet been created, create it now. A pointer to the head of the
+   linked list is put into the static variable "known_units". */
+   if( !known_units ) {
+
+/* At the same time we store pointers to the units describing the basic
+   quantities used in dimensional analysis. Initialise th index of the
+   next such unit. */
+      iq = 0;
+
+/* Create definitions for the known units. First do all IAU basic units.
+   We include "g" instead of "kg" because otherwise we would have to
+   refer to a gramme as a milli-kilogramme. */
+      MakeKnownUnit( "g", "gram", NULL, status );
+      quant_units[ iq++ ] = known_units;
+      MakeKnownUnit( "m", "metre", NULL, status );
+      quant_units[ iq++ ] = known_units;
+      MakeKnownUnit( "s", "second", NULL, status );
+      quant_units[ iq++ ] = known_units;
+      MakeKnownUnit( "rad", "radian", NULL, status );
+      quant_units[ iq++ ] = known_units;
+      MakeKnownUnit( "K", "Kelvin", NULL, status );
+      quant_units[ iq++ ] = known_units;
+      MakeKnownUnit( "A", "Ampere", NULL, status );
+      MakeKnownUnit( "mol", "mole", NULL, status );
+      MakeKnownUnit( "cd", "candela", NULL, status );
+
+/* Now do all IAU derived units. Unit definitions may only refer to units
+   which have already been defined. */
+      MakeKnownUnit( "sr", "steradian", "rad rad", status );
+      MakeKnownUnit( "Hz", "Hertz", "1/s", status );
+      MakeKnownUnit( "N", "Newton", "kg m/s**2", status );
+      MakeKnownUnit( "J", "Joule", "N m", status );
+      MakeKnownUnit( "W", "Watt", "J/s", status );
+      MakeKnownUnit( "C", "Coulomb", "A s", status );
+      MakeKnownUnit( "V", "Volt", "J/C", status );
+      MakeKnownUnit( "Pa", "Pascal", "N/m**2", status );
+      MakeKnownUnit( "Ohm", "Ohm", "V/A", status );
+      MakeKnownUnit( "S", "Siemens", "A/V", status );
+      MakeKnownUnit( "F", "Farad", "C/V", status );
+      MakeKnownUnit( "Wb", "Weber", "V s", status );
+      MakeKnownUnit( "T", "Tesla", "Wb/m**2", status );
+      MakeKnownUnit( "H", "Henry", "Wb/A", status );
+      MakeKnownUnit( "lm", "lumen", "cd sr", status );
+      MakeKnownUnit( "lx", "lux", "lm/m**2", status );
+
+/* Now do additional derived and basic units listed in the FITS-WCS paper. */
+      MakeKnownUnit( "deg", "degree", "pi/180 rad", status );
+      MakeKnownUnit( "arcmin", "arc-minute", "1/60 deg", status );
+      MakeKnownUnit( "arcsec", "arc-second", "1/3600 deg", status );
+      MakeKnownUnit( "mas", "milli-arcsecond", "1/3600000 deg", status );
+      MakeKnownUnit( "min", "minute", "60 s", status );
+      MakeKnownUnit( "h", "hour", "3600 s", status );
+      MakeKnownUnit( "d", "day", "86400 s", status );
+      MakeKnownUnit( "yr", "year", "31557600 s", status );
+      MakeKnownUnit( "a", "year", "31557600 s", status );
+      MakeKnownUnit( "eV", "electron-Volt", "1.60217733E-19 J", status );
+      MakeKnownUnit( "erg", "erg", "1.0E-7 J", status );
+      MakeKnownUnit( "Ry", "Rydberg", "13.605692 eV", status );
+      MakeKnownUnit( "solMass", "solar mass", "1.9891E30 kg", status );
+      MakeKnownUnit( "u", "unified atomic mass unit", "1.6605387E-27 kg", status );
+      MakeKnownUnit( "solLum", "solar luminosity", "3.8268E26 W", status );
+      MakeKnownUnit( "Angstrom", "Angstrom", "1.0E-10 m", status );
+      MakeKnownUnit( "micron", "micron", "1.0E-6 m", status );
+      MakeKnownUnit( "solRad", "solar radius", "6.9599E8 m", status );
+      MakeKnownUnit( "AU", "astronomical unit", "1.49598E11 m", status );
+      MakeKnownUnit( "lyr", "light year", "9.460730E15 m", status );
+      MakeKnownUnit( "pc", "parsec", "3.0867E16 m", status );
+      MakeKnownUnit( "count", "count", NULL, status );
+      quant_units[ iq++ ] = known_units;
+      MakeKnownUnit( "adu", "analogue-to-digital unit", NULL, status );
+      quant_units[ iq++ ] = known_units;
+      MakeKnownUnit( "photon", "photon", NULL, status );
+      quant_units[ iq++ ] = known_units;
+      MakeKnownUnit( "Jy", "Jansky", "1.0E-26 W /m**2 /Hz", status );
+      MakeKnownUnit( "mag", "magnitude", NULL, status );
+      quant_units[ iq++ ] = known_units;
+      MakeKnownUnit( "G", "Gauss", "1.0E-4 T", status );
+      MakeKnownUnit( "pixel", "pixel", NULL, status );
+      quant_units[ iq++ ] = known_units;
+      MakeKnownUnit( "barn", "barn", "1.0E-28 m**2", status );
+      MakeKnownUnit( "D", "Debye", "(1.0E-29/3) C.m", status );
+
+      if( iq != NQUANT && astOK ) {
+         astError( AST__INTER, "unit(GetKnownUnits): %d basic quantities "
+                   "noted but this should be %d (internal AST programming "
+                   "error).", status, iq, NQUANT );
+      }
+
+/* Unit aliases... */
+      MakeUnitAlias( "Angstrom", "Ang", status );
+      MakeUnitAlias( "count", "ct", status );
+      MakeUnitAlias( "photon", "ph", status );
+      MakeUnitAlias( "Jy", "Jan", status );
+      MakeUnitAlias( "pixel", "pix", status );
+      MakeUnitAlias( "s", "sec", status );
+      MakeUnitAlias( "m", "meter", status );
+   }
+
+/* If succesful, return the pointer to the head of the list. */
+   if( astOK ) result = known_units;
+
+/* Allow the next thread to proceed. */
+   if( lock ) {
+      UNLOCK_MUTEX1
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static Multiplier *GetMultipliers( int *status ) {
+/*
+*  Name:
+*     GetMultiplier
+
+*  Purpose:
+*     Get a pointer to the head of a linked list of multiplier definitions.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     Multiplier *Multipliers( void )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function returns a pointer to the head of a linked list of known
+*     multiplier definitions. The multiplier definitions are created as
+*     static module variables if they have not previously been created.
+
+*  Returned Value:
+*     A pointer to the first known multiplier definition.
+
+*  Notes:
+*     - A NULL pointer is returned if it is invoked with the global error
+*     status set, or if an error occurs.
+*/
+
+/* Local Variables: */
+   Multiplier *result;
+   Multiplier *mult;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* Ensure the list is only initialised by one thread. */
+   LOCK_MUTEX2
+
+/* If the linked list of Multiplier structures describing the known
+   multipliers has not yet been created, create it now. A pointer to the
+   head of the linked list is put into the static variable "multipliers". */
+   if( !multipliers ) {
+
+/* Define a macro to create a multiplier struncture and add it to the
+   linked list of multiplier structures. */
+#define MAKEMULT(s,sl,sc,lab,ll) \
+      mult = astMalloc( sizeof( Multiplier ) ); \
+      if( astOK ) { \
+         mult->sym = s; \
+         mult->symlen = sl; \
+         mult->lablen = ll; \
+         mult->scale = sc; \
+         mult->label = lab; \
+         mult->next = multipliers; \
+         multipliers = mult; \
+      }
+
+/* Use the above macro to create all the standard multipliers listed in the
+   FITS WCS paper I. */
+      MAKEMULT("d",1,1.0E-1,"deci",4)
+      MAKEMULT("c",1,1.0E-2,"centi",5)
+      MAKEMULT("m",1,1.0E-3,"milli",5)
+      MAKEMULT("u",1,1.0E-6,"micro",5)
+      MAKEMULT("n",1,1.0E-9,"nano",4)
+      MAKEMULT("p",1,1.0E-12,"pico",4)
+      MAKEMULT("f",1,1.0E-15,"femto",5)
+      MAKEMULT("a",1,1.0E-18,"atto",4)
+      MAKEMULT("z",1,1.0E-21,"zepto",5)
+      MAKEMULT("y",1,1.0E-24,"yocto",5)
+      MAKEMULT("da",2,1.0E1,"deca",4)
+      MAKEMULT("h",1,1.0E2,"hecto",5)
+      MAKEMULT("k",1,1.0E3,"kilo",4)
+      MAKEMULT("M",1,1.0E6,"mega",4)
+      MAKEMULT("G",1,1.0E9,"giga",4)
+      MAKEMULT("T",1,1.0E12,"tera",4)
+      MAKEMULT("P",1,1.0E15,"peta",4)
+      MAKEMULT("E",1,1.0E18,"exa",3)
+      MAKEMULT("Z",1,1.0E21,"zetta",5)
+      MAKEMULT("Y",1,1.0E24,"yotta",5)
+
+/* Undefine the macro. */
+#undef MAKEMULT
+
+   }
+
+/* If succesful, return the pointer to the head of the list. */
+   if( astOK ) result = multipliers;
+
+/* Allow the next thread to proceed. */
+   UNLOCK_MUTEX2
+
+/* Return the result. */
+   return result;
+}
+
+static void InvertConstants( UnitNode **node, int *status ) {
+/*
+*  Name:
+*     InvertConstants
+
+*  Purpose:
+*     Take the reciprocal of all constants in a tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     void InvertConstants( UnitNode **node, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function replaces constant unit coefficients by their reciprocal.
+*     This is because a string such as "0.01 m" will be interpreted as
+*     meaning "multiply a value in metres by 0.01 to get the value in the
+*     required units", whereas what is actually meant is "use units of
+*     0.01 of a metre" which requires us to divide the value in metres by
+*     0.01, not multiply it.
+
+*  Parameters:
+*     node
+*        The address of a pointer to the UnitNode at the head of the tree.
+*        On exit the supplied tree is freed and a pointer to a new tree is
+*        placed at the given address.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int i;
+   UnitNode *newnode;
+   int allcon;
+   Oper op;
+
+/* Check inherited status and pointer. */
+   if( !astOK || !node || !(*node) ) return;
+
+/* Initiallially, we have no replacement node */
+   newnode = NULL;
+
+/* There is nothing to fix if the node has no arguments. */
+   if( (*node)->narg > 0 ) {
+
+/* Note the op code for the node. */
+      op = (*node)->opcode;
+
+/* Fix up the argument nodes. Also note if all the arguments are
+   constants. */
+      allcon = 1;
+      for( i = 0; i < (*node)->narg; i++ ) {
+         InvertConstants( &( (*node)->arg[ i ] ), status );
+         if( (*node)->arg[ i ]->con == AST__BAD ) allcon = 0;
+      }
+
+/* If all nodes are constant, there are no co-efficients to invert. */
+      if( !allcon ) {
+
+/* Iif this is a multiplication node, see if either of its arguments
+   is a constant. If so, invert the constant. This is because a string like
+   "0.01 m" means "each unit is 0.01 of a metre". Therefore, to transform
+   a value in metres into required units means multiplying the metres
+   value by 100.0 (i.e the reciprocal of 0.01), not 0.01. */
+         if( op == OP_MULT ) {
+            for( i = 0; i < 2; i++ ) {
+               if( (*node)->arg[ i ]->con != AST__BAD ) {
+                  if( (*node)->arg[ i ]->con != 0.0 ) {
+
+                     (*node)->arg[ i ]->con = 1.0/(*node)->arg[ i ]->con;
+                  } else {
+                     astError( AST__BADUN, "Illegal zero constant encountered." , status);
+                  }
+               }
+            }
+
+/* Likewise, check for division nodes in which the denominator is
+   constant. */
+         } else if( op == OP_DIV ) {
+            if( (*node)->arg[ 1 ]->con != AST__BAD ) {
+               if( (*node)->arg[ 1 ]->con != 0.0 ) {
+                  (*node)->arg[ 1 ]->con = 1.0/(*node)->arg[ 1 ]->con;
+               } else {
+                  astError( AST__BADUN, "Illegal zero constant encountered." , status);
+               }
+            }
+
+/* If this is a "pow" node check that the second argument is constant
+   (required by FITS WCS paper I). */
+         } else if( op == OP_POW ) {
+            if( (*node)->arg[ 1 ]->con == AST__BAD ) {
+               astError( AST__BADUN, "Illegal variable exponent." , status);
+            }
+         }
+      }
+   }
+
+/* If an error has occurred, free any new node. */
+   if( !astOK ) newnode = FreeTree( newnode, status );
+
+/* If we have a replacement node, free the supplied tree and return a
+   pointer to the new tree. */
+   if( newnode ) {
+      FreeTree( *node, status );
+      *node = newnode;
+   }
+}
+
+static UnitNode *InvertTree( UnitNode *fwdnode, UnitNode *src, int *status ) {
+/*
+*  Name:
+*     InvertTree
+
+*  Purpose:
+*     Invert a tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *InvertTree( UnitNode *fwdnode, UnitNode *src )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function inverts a tree of UnitNodes. The supplied tree should
+*     have exactly one OP_LDVAR node. This will be the quantity represented
+*     by the node at the head of the returned tree.
+
+*  Parameters:
+*     fwdnode
+*        A pointer to the UnitNode at the head of the tree which is to be
+*        inverted.
+*     src
+*        A pointer to a UnitNode which is to be used as the root of the
+*        inverted tree. That is, the output from this node should form
+*        the (one and only) varying input to the inverted tree. If the
+*        supplied tree is succesfulyl inverted, the tree of which "src"
+*        is the head will be contained within the returned inverted tree.
+*        Therefore "src" only needs to be freed explicitly if this
+*        function fails to invert the supplied tree for any reason. If
+*        this function succeeds, then "src" will be freed as part of
+*        freeing the returned inverted tree.
+
+*  Returned Value:
+*     A pointer to a UnitNode which forms the head of the inverted tree.
+
+*  Algorithm:
+*     The algorithm works through the supplied forward tree, from the head
+*     to the roots. First, the supplied node at the head of the forward
+*     tree is inverted. To be invertable, the supplied head node must have
+*     exactly one varying argument (any other arguments must be fixed,
+*     i.e. not vary). This varying argument becomes the output of the
+*     inverted node. The other (fixed) arguments to the forward node are
+*     also used as arguments to the inverted node. The supplied "src" node
+*     is used as the single varying input to the inverted node. Having
+*     inverted the supplied forward head node, this function is called
+*     recursively to invert the lower parts of the forward tree (i.e. the
+*     part of the forward tree which provided the varying input to node
+*     which has just been inverted).
+
+*  Notes:
+*     - It is assumed that he supplied forward tree has been simplified
+*     using SimplifyTree. This means that the tree contains no nodes with
+*     the following op codes: OP_LOG, OP_SQRT. OP_DIV (SimplifyTree
+*     converts these nodes into OP_LN, OP_POW and OP_MULT nodes).
+*     - A value of NULL will be returned if this function is invoked with
+*     the global error status set, or if it should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   UnitNode *newnode;
+   UnitNode *nextnode;
+   UnitNode *result;
+   UnitNode *node1;
+   Oper fop;
+   int varg;
+
+/* Initialise */
+   result = NULL;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* Initiallially, we have no replacement node */
+   newnode = NULL;
+   nextnode = NULL;
+
+/* Save the op code at the head of the forward tree. */
+   fop = fwdnode->opcode;
+
+/* If the head of the forward tree is a OP_EXP node. Inverse of
+   "exp(x)" is "ln(x)". */
+   if( fop == OP_EXP ) {
+      newnode = NewNode( NULL, OP_LN, status );
+      if( astOK ) {
+         newnode->arg[ 0 ] = src;
+         nextnode = fwdnode->arg[ 0 ];
+      }
+
+/* If the head of the forward tree is a OP_LN node. Inverse of
+   "ln(x)" is "exp(x)". */
+   } else if( fop == OP_LN ) {
+      newnode = NewNode( NULL, OP_EXP, status );
+      if( astOK ) {
+         newnode->arg[ 0 ] = src;
+         nextnode = fwdnode->arg[ 0 ];
+      }
+
+/* If the head of the forward tree is a OP_POW node. Inverse of
+   "x**k" is "x**(1/k)" */
+   } else if( fop == OP_POW ) {
+      newnode = NewNode( NULL, OP_POW, status );
+      node1 = NewNode( NULL, OP_LDCON, status );
+      if( astOK ) {
+         node1->con = 1.0/fwdnode->arg[ 1 ]->con;
+         newnode->arg[ 0 ] = src;
+         newnode->arg[ 1 ] = node1;
+         nextnode = fwdnode->arg[ 0 ];
+      }
+
+/* If the head of the forward tree is a OP_MULT node... */
+   } else if( fop == OP_MULT ) {
+
+/* The node is only invertable if it has one constant node and one
+   non-constant node. Get the index of the varying argument. */
+      if( fwdnode->arg[ 0 ]->con != AST__BAD &&
+          fwdnode->arg[ 1 ]->con == AST__BAD ) {
+         varg = 1;
+      } else if( fwdnode->arg[ 0 ]->con == AST__BAD &&
+                 fwdnode->arg[ 1 ]->con != AST__BAD ) {
+         varg = 0;
+      } else {
+         varg = -1;
+      }
+      if( varg != -1 ) {
+
+/* The inverse of "k*x" is "(1/k)*x" (we use MULT nodes instead of DIV
+   nodes to maintain the standardisation implemented by SimplifyTree). */
+         newnode = NewNode( NULL, OP_MULT, status );
+         node1 = NewNode( NULL, OP_LDCON, status );
+         if( astOK ) {
+            node1->con = 1.0/fwdnode->arg[ 1 - varg ]->con;
+            newnode->arg[ 0 ] = node1;
+            newnode->arg[ 1 ] = src;
+            nextnode = fwdnode->arg[ varg ];
+         }
+      }
+
+/* If the head of the forward tree is a OP_LDVAR node, there is nothing
+   left to invert. SO return a pointer to the suppleid source node. */
+   } else if( fop == OP_LDVAR ) {
+      result = src;
+      nextnode = NULL;
+
+/* If the head of the forward tree is any other node (e.g. a OP_LDCON node),
+   the tree cannot be inverted. */
+   } else {
+      nextnode = NULL;
+   }
+
+/* If we managed to invert the node at the head of the supplied tree,
+   continue to invert its varying argument node (if any). */
+   if( nextnode && newnode ) result = InvertTree( nextnode, newnode, status );
+
+/* If the tree could not be inverted, free the newnode. */
+   if( !result ) newnode = FreeTree( newnode, status );
+
+/* If an error has occurred, free any new node. */
+   if( !astOK ) result = FreeTree( result, status );
+
+/* Return the result. */
+   return result;
+
+}
+
+static void LocateUnits( UnitNode *node, UnitNode ***units, int *nunits, int *status ){
+/*
+*  Name:
+*     LocateUnits
+
+*  Purpose:
+*     Locate the units used by a supplied tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     void LocateUnits( UnitNode *node, UnitNode ***units, int *nunits, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function locates the units used by a supplied tree of
+*     UnitNodes.
+
+*  Parameters:
+*     node
+*        A pointer to the UnitNode at the head of the tree to be searched.
+*     units
+*        The address at which is stored a pointer to an array of "*nunits"
+*        elements. Each element of the array holds a pointer to a UnitNode.
+*        The array is extended on exit to hold pointers to the OP_LDVAR nodes
+*        within the supplied tree (i.e. nodes which represent named units,
+*        either known or unknown). A node is only included in the returned
+*        array if no other node for the same unit is already included in the
+*        array. A NULL pointer should be supplied on the first invocation of
+*        this function.
+*     nunits
+*        The address of an integer which holds the number of elements in
+*        the array given by "*units". Updated on exit to included any
+*        elements added to the array. Zero should be supplied on the first
+*        invocation of this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   int i;
+   int found;
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Is the node at the head of the supplied tree an OP_LDVAR node? */
+   if( node->opcode == OP_LDVAR ) {
+
+/* If an array was supplied, see if it already contains a pointer to a node
+   which refers to the same units. */
+      found = 0;
+      if( *units ) {
+         for( i = 0; i < *nunits; i++ ) {
+            if( !strcmp( (*units)[ i ]->name, node->name ) ) {
+               found = 1;
+               break;
+            }
+         }
+      }
+
+/* If not, ensure the array is big enough and add a pointer to the
+   supplied node to the array. */
+      if( !found ) {
+         *units = astGrow( *units, *nunits + 1, sizeof( UnitNode * ) );
+         if( astOK ) (*units)[ (*nunits)++ ] = node;
+      }
+
+/* If the supplied node is not an OP_LDVAR node, call this function
+   recursively to search the argument sub-trees. */
+   } else {
+      for( i = 0; i < node->narg; i++ ) {
+         LocateUnits( node->arg[ i ], units, nunits, status );
+      }
+   }
+}
+
+static const char *MakeExp( UnitNode *tree, int mathmap, int top, int *status ) {
+/*
+*  Name:
+*     MakeExp
+
+*  Purpose:
+*     Make an algebraic expression from a supplied tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     const char *MakeExp( UnitNode *tree, int mathmap, int top, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function produces a string holding an algebraic expression
+*     corresponding to a supplied tree of UnitNodes.
+
+*  Parameters:
+*     tree
+*        A pointer to the UnitNode at the head of the tree to be converted
+*        into an algebraic expression.
+*     mathmap
+*        If zero, format as an axis label expression. If 1, format as a
+*        MathMap expression. If 2, format as a FITS unit string.
+*     top
+*        Should be non-zero for a top-level entry to this function, and
+*        zero for a recursive entry.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the cleaned expression, which should be freed using
+*     astFree when no longer needed.
+
+*  Notes:
+*     - This function returns NULL if it is invoked with the global error
+*     status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   UnitNode *newtree;
+   UnitNode *sunit;
+   char *a;
+   char *result;
+   char buff[200];
+   const char *arg0;
+   const char *arg1;
+   const char *mtxt;
+   int larg0;
+   int larg1;
+   int lbuff;
+   int mlen;
+   int par;
+   int tlen;
+
+/* Check inherited status. */
+   result = NULL;
+   if( !astOK ) return result;
+
+/* Modify the tree to make the resulting transformation functions more
+   natural to human readers. */
+   newtree = CopyTree( tree, status );
+   ComplicateTree( &newtree, status );
+
+/* If we are producing an axis label... */
+   if( !mathmap ) {
+
+/* Fix all multiplicative constants to 1.0 if they multiply an OP_LDVAR
+   OP_SQRT or OP_POW node. This is on the assumption that the returned label
+   should not include any simple unit scaling (e.g. if the output label would
+   be "2.345*wavelength", we prefer simply to use "wavelength" since a scaled
+   wavelength is still a wavelength - i.e. simple scaling does not change
+   the dimensions of a quantity). */
+      FixConstants( &newtree, 1, status );
+
+/* Simplify the tree again to get rid of the 1.0 terms which may have
+   been introduced by the previous line (but do not re-introduce any
+   standardisations - removing them was the reason for calling ComplicateTree).
+   If this simplication introduces any changes, try fixing multiplicative
+   constants again, and so on, until no more changes occur. */
+      while( SimplifyTree( &newtree, 0, status ) ) {
+         FixConstants( &newtree, 1, status );
+      }
+
+   }
+
+/* Produce a string describing the action performed by the UnitNode at
+   the head of the supplied tree, and then invoke this function recursively
+   to format any arguments of the head node. */
+
+/* Constant valued nodes... just format the constant in a local buffer and
+   then copy the buffer. */
+   if( newtree->con != AST__BAD ) {
+      lbuff = sprintf( buff, "%.*g", DBL_DIG, newtree->con );
+      result = astStore( NULL, buff, lbuff + 1 );
+
+/* "Load Variable Value" nodes - return the variable name. If this is a
+   recursive call to this function, and we are producing a label, append a
+   single space before and after the name. */
+   } else if( newtree->opcode ==  OP_LDVAR ) {
+      tlen = strlen( newtree->name );
+
+      if( !mathmap && !top ){
+         result = astMalloc( tlen + 3 );
+         if( result ) {
+            result[ 0 ] = ' ';
+            memcpy( result + 1, newtree->name, tlen );
+            memcpy( result + tlen + 1, " ", 2 );
+         }
+
+      } else if( mathmap == 2 ) {
+
+         if( newtree->mult ) {
+           mlen = newtree->mult->symlen;
+           mtxt = newtree->mult->sym;
+         } else {
+           mlen = 0;
+           mtxt = NULL;
+         }
+
+         result = astMalloc( tlen + 1 + mlen );
+         if( result ) {
+            if( mtxt ) memcpy( result, mtxt, mlen );
+            memcpy( result + mlen, newtree->name, tlen + 1 );
+         }
+
+      } else {
+         result = astStore( NULL, newtree->name, tlen + 1 );
+      }
+
+/* Single argument functions... place the argument in parentheses after
+   the function name. */
+   } else if( newtree->opcode ==  OP_LOG ) {
+      arg0 = MakeExp( newtree->arg[ 0 ], mathmap, 0, status );
+      larg0 = strlen( arg0 );
+      if( mathmap == 1 ) {
+         result = astMalloc( larg0 + 8 );
+         if( result ) memcpy( result, "log10(", 7 );
+         a = result + 6;
+      } else {
+         result = astMalloc( larg0 + 6 );
+         if( result ) memcpy( result, "log(", 5 );
+         a = result + 4;
+      }
+      if( result ){
+         memcpy( a, arg0, larg0 + 1 );
+         memcpy( a + larg0, ")", 2 );
+      }
+      arg0 = astFree( (void *) arg0 );
+
+   } else if( newtree->opcode ==  OP_LN ) {
+      arg0 = MakeExp( newtree->arg[ 0 ], mathmap, 0, status );
+      larg0 = strlen( arg0 );
+      if( mathmap == 1 ) {
+         result = astMalloc( larg0 + 6 );
+         if( result ) memcpy( result, "log(", 5 );
+         a = result + 4;
+      } else {
+         result = astMalloc( larg0 + 5 );
+         if( result ) memcpy( result, "ln(", 4 );
+         a = result + 3;
+      }
+      if( astOK ){
+         memcpy( a, arg0, larg0 );
+         memcpy( a + larg0, ")", 2 );
+      }
+      arg0 = astFree( (void *) arg0 );
+
+   } else if( newtree->opcode ==  OP_EXP ) {
+      arg0 = MakeExp( newtree->arg[ 0 ], mathmap, 0, status );
+      larg0 = strlen( arg0 );
+      result = astMalloc( larg0 + 6 );
+      if( result ){
+         memcpy( result, "exp(", 5 );
+         memcpy( result + 4, arg0, larg0 );
+         memcpy( result + 4 + larg0, ")", 2 );
+      }
+      arg0 = astFree( (void *) arg0 );
+
+   } else if( newtree->opcode ==  OP_SQRT ) {
+      arg0 = MakeExp( newtree->arg[ 0 ], mathmap, 0, status );
+      larg0 = strlen( arg0 );
+      result = astMalloc( larg0 + 7 );
+      if( result ){
+         memcpy( result, "sqrt(", 6 );
+         memcpy( result + 5, arg0, larg0 );
+         memcpy( result + 5 + larg0, ")", 2 );
+      }
+      arg0 = astFree( (void *) arg0 );
+
+/* POW... the exponent (arg[1]) is always a constant and so does not need
+   to be placed in parentheses. The first argument only needs to be
+   placed in parentheses if it is a two arg node (except we also put it
+   in parentheses if it is an OP_LDVAR node and "mathmap" is zero - this is
+   because such OP_LDVAR nodes will correspond to axis labels which will
+   have spaces before and after them which would look odd if not encloses
+   in parentheses). */
+   } else if( newtree->opcode ==  OP_POW ) {
+
+      arg0 = MakeExp( newtree->arg[ 0 ], mathmap, 0, status );
+      larg0 = strlen( arg0 );
+
+      arg1 = MakeExp( newtree->arg[ 1 ], mathmap, 0, status );
+      larg1 = strlen( arg1 );
+
+      if( newtree->arg[ 0 ]->narg == 2 ||
+          (newtree->arg[ 0 ]->opcode == OP_LDVAR && !mathmap) ) {
+         par = 1;
+         result = astMalloc( larg0 + larg1 + 7 );
+         if( result ) memcpy( result, "(", 2 );
+         a = result + 1;
+      } else {
+         par = 0;
+         result = astMalloc( larg0 + larg1 + 5 );
+         a = result;
+      }
+
+      if( result ) {
+         memcpy( a, arg0, larg0 );
+         a += larg0;
+         if( par ) *(a++) = ')';
+         memcpy( a, "**", 3 );
+         a += 2;
+         memcpy( a, arg1, larg1 );
+         a += larg1;
+         *a = 0;
+      }
+
+      arg0 = astFree( (void *) arg0 );
+      arg1 = astFree( (void *) arg1 );
+
+/* DIV... the first argument (numerator) never needs to be in parentheses.
+   The second argument (denominator) only needs to be placed in parentheses
+   if it is a MULT node. */
+   } else if( newtree->opcode ==  OP_DIV ) {
+
+      if( mathmap == 2 && ( sunit = ModifyPrefix( newtree, status ) ) ) {
+         result = (char *) MakeExp( sunit, mathmap, 0, status );
+         sunit = FreeTree( sunit, status );
+
+      } else {
+         arg0 = MakeExp( newtree->arg[ 0 ], mathmap, 0, status );
+         larg0 = strlen( arg0 );
+
+         arg1 = MakeExp( newtree->arg[ 1 ], mathmap, 0, status );
+         larg1 = strlen( arg1 );
+
+         if( newtree->arg[ 1 ]->opcode == OP_MULT &&
+             strchr( arg1, '*' ) ) {
+            par = 1;
+            result = astMalloc( larg0 + larg1 + 4 );
+         } else {
+            par = 0;
+            result = astMalloc( larg0 + larg1 + 2 );
+         }
+
+         if( result ) {
+            memcpy( result, arg0, larg0 );
+            a = result + larg0;
+            *(a++) = '/';
+            if( par ) *(a++) = '(';
+            memcpy( a, arg1, larg1 );
+            a += larg1;
+            if( par ) *(a++) = ')';
+            *a = 0;
+         }
+
+         arg0 = astFree( (void *) arg0 );
+         arg1 = astFree( (void *) arg1 );
+      }
+
+/* MULT... the second argument never needs to be in parentheses. The first
+   argument only needs to be placed in parentheses if it is a DIV or POW
+   node. */
+   } else if( newtree->opcode ==  OP_MULT ) {
+      if( mathmap == 2 && ( sunit = ModifyPrefix( newtree, status ) ) ) {
+         result = (char *) MakeExp( sunit, mathmap, 0, status );
+         sunit = FreeTree( sunit, status );
+
+      } else {
+         arg0 = MakeExp( newtree->arg[ 0 ], mathmap, 0, status );
+         larg0 = strlen( arg0 );
+
+         arg1 = MakeExp( newtree->arg[ 1 ], mathmap, 0, status );
+         larg1 = strlen( arg1 );
+
+/* If this is a top-level entry and we are producing an axis label, do
+   not include any constant multiplicative terms. */
+         if( top && !mathmap ) {
+            if( newtree->arg[ 0 ]->con != AST__BAD ) arg0 = astFree( (void *) arg0 );
+            if( newtree->arg[ 1 ]->con != AST__BAD ) arg1 = astFree( (void *) arg1 );
+         }
+
+/* If we have two arguments, concatentate them, placing the operands in
+   parentheses if necessary. */
+         if( arg0 && arg1 ) {
+
+            if( ( newtree->arg[ 0 ]->opcode == OP_DIV &&
+                  strchr( arg0, '/' ) ) ||
+                ( newtree->arg[ 0 ]->opcode == OP_POW &&
+                  strstr( arg0, "**" ) ) ) {
+               par = 1;
+               result = astMalloc( larg0 + larg1 + 4 );
+               if( result ) result[ 0 ] = '(';
+               a = result + 1;
+            } else {
+               par = 0;
+               result = astMalloc( larg0 + larg1 + 2 );
+               a = result;
+            }
+
+            if( result ) {
+               memcpy( a, arg0, larg0 );
+               a += larg0;
+               if( par ) *(a++) = ')';
+               *(a++) = '*';
+               memcpy( a, arg1, larg1 );
+               a += larg1;
+               *a = 0;
+            }
+
+            arg0 = astFree( (void *) arg0 );
+            arg1 = astFree( (void *) arg1 );
+
+/* If we do not have two arguments, just return the one we do have. */
+         } else if( arg0 ){
+            result = (char *) arg0;
+
+         } else {
+            result = (char *) arg1;
+         }
+      }
+   }
+
+/* Free the complicated tree. */
+   newtree = FreeTree( newtree, status );
+
+/* Free the returned string if an error has occurred. */
+   if( !astOK ) result = astFree( result );
+
+/* Return the result. */
+   return (const char *) result;
+}
+
+static void MakeKnownUnit( const char *sym, const char *label, const char *exp, int *status ){
+/*
+*  Name:
+*     MakeKnownUnit
+
+*  Purpose:
+*     Create a KnownUnit structure describing a known unit.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     void MakeKnownUnit( const char *sym, const char *label, const char *exp, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function creates a KnownUnit structure decribing a known unit,
+*     and adds it to the head of the linked list of known units stored in
+*     a module variable.
+
+*  Parameters:
+*     sym
+*        A pointer to a string which can be used as a symbol to represent
+*        the new named unit. Once defined, this symbol can be included within
+*        the definition of other derived units. The string should contain
+*        only alphabetical characters (no digits, spaces, punctuation,
+*        etc). Symbols are case sensitive (e.g. "s" is second, but "S" is
+*        Siemens). The string should not include any multiplier prefix.
+*     label
+*        Pointer to a null terminated string containing the label for
+*        the required units. No restriction on content.
+*     exp
+*        This should be a pointer to a null terminated string containing
+*        a definition of the required unit. See the description of the
+*        "in" and "out" parameters for the astUnitMapper function.
+*
+*        A NULL pointer or a blank string may supplied for "exp", which
+*        is interpreted as a request for a new basic unit to be created with
+*        the symbol and label given by the other parameters.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  The supplied symbol and label strings are not copied. The
+*     supplied pointers are simply stored in the returned structure.
+*     Therefore the strings to which the pointers point should not be
+*     modified after this function returned (in fact this function is
+*     always called with literal strings for these arguments).
+*/
+
+/* Local Variables: */
+   KnownUnit *result;
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Indicate that subsequent memory allocations may never be freed (other
+   than by any AST exit handler). */
+   astBeginPM;
+
+/* Allocate memory for the structure, and check the returned pointer can
+   be used safely. */
+   result = astMalloc( sizeof( KnownUnit ) );
+   if( astOK ) {
+
+/* In case of errors, first nullify the pointer to the next KnownUnit. */
+      result->next = NULL;
+
+/* Store the supplied label and symbol pointers. */
+      result->sym = sym;
+      result->label = label;
+
+/* Store the length of the symbol (without the trailing null character). */
+      result->symlen = strlen( sym );
+
+/* Store the length of the label (without the trailing null character). */
+      result->lablen = strlen( label );
+
+/* Create a tree of UnitNodes describing the unit if an expression was
+   supplied. */
+      result->head = exp ? CreateTree( exp, 1, 0, status ) : NULL;
+
+/* Unit aliases are replaced in use by the KnownUnit pointed to by the
+   "use" component of the structure. Indicate this KnownUnitis not an
+    alias by setting its "use" component NULL. */
+      result->use = NULL;
+   }
+
+/* Mark the end of the section in which memory allocations may never be
+   freed (other than by any AST exit handler). */
+   astEndPM;
+
+/* If an error has occurred, free any returned structure. */
+   if( !astOK ) {
+      result->head = FreeTree( result->head, status );
+      result = astFree( result ) ;
+
+/* Otherwise, add the new KnownUnit to the head of the linked list of
+   known units. */
+   } else {
+      result->next = known_units;
+      known_units = result;
+   }
+
+}
+
+static AstMapping *MakeMapping( UnitNode *tree, int *status ) {
+/*
+*  Name:
+*     MakeMapping
+
+*  Purpose:
+*     Create a new Mapping from a given tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     AstMapping *MakeMapping( UnitNode *tree )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function creates a Mapping with a forward transformation equal
+*     to the transformation described by the tree of UnitNodes. The head
+*     node of the tree corresponds to the output of the Mapping.
+
+*  Parameters:
+*     tree
+*        The UnitNode at the head of the tree to be used. It should have
+*        exactly one OP_LDVAR node, and should have been simplified using
+*        the SimplifyTree function.
+
+*  Returned Value:
+*     A pointer to the Mapping. Its Nin and Nout attributes will both be 1.
+
+*  Notes:
+*     - A value of NULL will be returned if this function is invoked with
+*     the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *result;
+   UnitNode *inv;
+   UnitNode *src;
+   const char *fwdexp;
+   char *fwdfun;
+   const char *invexp;
+   char *invfun;
+   int lfwd;
+   int linv;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* First see if a UnitMap can be used to represent the Mapping from
+   input units to output units. This will be the case if the supplied tree
+   consists of a aingle OP_LDVAR node (corresponding to the input units). */
+   if( tree->opcode == OP_LDVAR ) {
+      result = (AstMapping *) astUnitMap( 1, "", status );
+
+/* Now see if a UnitMap or ZoomMap can be used to represent the Mapping from
+   input units to output units. This will be the case if the supplied tree
+   consists of a OP_MULT node with one constant argument and on OP_LDVAR
+   argument (corresponding to the input units). The standardisation done by
+   SimplifyTree will have ensured that the constant will be argument 0
+   (and will also have converted "x/k" trees into "(1/k)*x" trees). */
+   } else if( tree->opcode == OP_MULT &&
+              tree->arg[ 0 ]->con != AST__BAD &&
+              tree->arg[ 1 ]->opcode == OP_LDVAR ) {
+
+      if( tree->arg[ 0 ]->con == 1.0 ) {
+         result = (AstMapping *) astUnitMap( 1, "", status );
+      } else {
+         result = (AstMapping *) astZoomMap( 1, tree->arg[ 0 ]->con, "", status );
+      }
+
+/* For other trees we need to create a MathMap. */
+   } else {
+
+/* Format the supplied tree as an algebraic expression, and get its length. */
+      fwdexp = MakeExp( tree, 1, 1, status );
+      lfwd = strlen( fwdexp );
+
+/* The MathMap constructor requires the forward and inverse
+   transformation functions to be specified as equations (i.e. including an
+   equals sign). We use the output variable name "output_units" (the
+   astUnitMapper function creates the supplied tree usign the variable
+   name "input_units" ). */
+      lfwd += 13;
+
+/* Invert the supplied tree and create an algebraic expression from it. */
+      src = NewNode( NULL, OP_LDVAR, status );
+      if( astOK ) src->name = astStore( NULL, "output_units", 13 );
+      inv = InvertTree( tree, src, status );
+      if( !inv ) {
+         src = FreeTree( src, status );
+         astError( AST__BADUN, "MakeMapping(Unit): Failed to invert "
+                   "supplied tree '%s' (internal AST programming error).", status,
+                   fwdexp );
+
+/* If inverted succesfully (which it should be since astUnitMapper should
+   have checked this)... */
+      } else {
+
+/* Format the inverted tree as an algebraic expression, and get its
+   length, adding on extra characters for the variable name ("input_units")
+   and equals sign. */
+         invexp = MakeExp( inv, 1, 1, status );
+         linv = strlen( invexp );
+         linv += 12;
+
+/* Allocate memory for the transformation functions, plus an extra
+   character for the trailing null. */
+         fwdfun = astMalloc( lfwd + 1 );
+         invfun = astMalloc( linv + 1 );
+         if( invfun ) {
+            memcpy( fwdfun, "output_units=", 14 );
+            memcpy( invfun, "input_units=", 13 );
+
+/* Append the expressions following the equals signs. */
+            strcpy( fwdfun + 13, fwdexp );
+            strcpy( invfun + 12, invexp );
+
+/* Create the MathMap. */
+            result = (AstMapping *) astMathMap( 1, 1, 1,
+                                                (const char **) &fwdfun, 1,
+                                                (const char **) &invfun,
+                                                "SimpFI=1,SimpIF=1", status );
+         }
+
+/* Free resources. */
+         inv = FreeTree( inv, status );
+         fwdfun = astFree( fwdfun );
+         invfun = astFree( invfun );
+         invexp = astFree( (void *) invexp );
+      }
+      fwdexp = astFree( (void *) fwdexp );
+   }
+
+/* Free any result if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the answer. */
+   return result;
+}
+
+static UnitNode *MakeLabelTree( const char *lab, int nc, int *status ){
+/*
+*  Name:
+*     MakeLabelTree
+
+*  Purpose:
+*     Convert an axis label into a tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *MakeLabelTree( const char *lab, int nc, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function converts an axis label into a tree of UnitNodes.
+*     It is assumed the supplied label represents some "basic" label
+*     modified by the application of one or more single function arguments
+*     and/or exponentiation operators. The (single) OP_LDVAR node in the
+*     returned tree refers to the basic label (it is stored as the "name"
+*     component of UnitNode structure).
+
+*  Parameters:
+*     lab
+*        The label expression.
+*     nc
+*        The number of characters from "lab" to use.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a UnitNode which forms the head of a tree of UnitNodes
+*     representing the supplied label expression.
+
+*  Notes:
+*     -  A NULL value is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   Oper op;
+   UnitNode *result;
+   char buff[ 10 ];
+   const char *c;
+   const char *exp;
+   int depth;
+   int i;
+   int oplen;
+   int n;
+   double con;
+
+/* Initialise */
+   result = NULL;
+   oplen = 0;
+
+/* Check the global error status, and that we have a string. */
+   if ( !astOK || !lab || !nc ) return result;
+
+/* Get a pointer to the first non-blank character, and store the number of
+   characters to examine (this excludes any trailing white space). */
+   exp = lab;
+   while( isspace( *exp ) ) exp++;
+   c = lab + nc - 1;
+   while( c >= exp && isspace( *c ) ) c--;
+   nc = c - exp + 1;
+
+/* Scan through the supplied string looking for the first pow operator at
+   zero depth of nesting within parentheses. */
+   depth = 0;
+   c = exp;
+   i = 0;
+   op = OP_NULL;
+   while( i < nc && *c ){
+
+/* If this character is an opening parenthesis, increment the depth of
+   nesting. */
+      if( *c == '(' ) {
+         depth++;
+
+/* If this character is an closing parenthesis, decrement the depth of
+   nesting. Report an error if it ever goes negative. */
+      } else if( *c == ')' ) {
+         depth--;
+         if( depth < 0 && astOK ) {
+            astError( AST__BADUN, "Missing opening parenthesis." , status);
+            break;
+         }
+
+/* Ignore all other characters unless they are at zero depth of nesting.
+   Also ignore spaces. */
+      } else if( depth == 0 && !isspace( *c ) ) {
+
+/* Compare the next part of the string with each of the "pow" operators. */
+         if( !strncmp( c, "**", 2 ) ) {
+            op = OP_POW;
+            oplen = 2;
+         } else if( *c == '^' ) {
+            op = OP_POW;
+            oplen = 1;
+         }
+
+/* If an operator was found, break out of the loop. */
+         if( op != OP_NULL ) break;
+      }
+
+/* Pass on to check the next character. */
+      i++;
+      c++;
+   }
+
+/* If a "pow" operator was found, the strings on either side of it should be
+   valid unit expressions, in which case we use this routine recursively to
+   create corresponding trees of UnitNodes. */
+   if( op != OP_NULL ) {
+
+/* Create a UnitNode for the operator. */
+      result = NewNode( NULL, op, status );
+      if( astOK ) {
+
+/* Create a tree of unit nodes from the string which precedes the binary
+   operator. Report an error if it cannot be done. */
+        result->arg[ 0 ] = MakeLabelTree( exp, i, status );
+        if( !result->arg[ 0 ] && astOK ) {
+           for( i = 0; i < oplen; i++ ) buff[ i ] = c[ i ];
+           buff[ oplen ] = 0;
+           astError( AST__BADUN, "Missing operand before '%s'.", status, buff );
+        }
+
+/* Create a tree of unit nodes from the string which follows the binary
+   operator. Report an error if it cannot be done. */
+         result->arg[ 1 ] = MakeLabelTree( c + oplen, nc - i - oplen, status );
+         if( !result->arg[ 1 ] && astOK ) {
+             for( i = 0; i < oplen; i++ ) buff[ i ] = c[ i ];
+            buff[ oplen ] = 0;
+            astError( AST__BADUN, "Missing operand after '%s'.", status, buff );
+         }
+      }
+
+/* If no binary operator was found at depth zero, see if the supplied string
+   starts with a function name (the only legal place for a function name
+   given that the string has no binary operators at depth zero). */
+   } else {
+      if( !strncmp( exp, "sqrt(", 5 ) || !strncmp( exp, "SQRT(", 5 ) ) {
+         op = OP_SQRT;
+         oplen = 4;
+      } else if( !strncmp( exp, "exp(", 4 ) || !strncmp( exp, "EXP(", 4 ) ) {
+         op = OP_EXP;
+         oplen = 3;
+      } else if( !strncmp( exp, "ln(", 3 ) || !strncmp( exp, "LN(", 3 ) ) {
+         op = OP_LN;
+         oplen = 2;
+      } else if( !strncmp( exp, "log(", 4 ) || !strncmp( exp, "LOG(", 4 ) ) {
+         op = OP_LOG;
+         oplen = 3;
+      }
+
+/* If a function was found, the string following the function name
+   (including the opening parenthesis) should form a legal units
+   expresssion (all the supported functions take a single argument and
+   so we do not need to worry about comma-separated lists of function
+   arguments). Use this routine recursively to create a tree of UnitNodes
+   from the string which forms the function argument. */
+      if( op != OP_NULL ) {
+
+/* Create a UnitNode for the function. */
+         result = NewNode( NULL, op, status );
+         if( astOK ) {
+
+/* Create a tree of unit nodes from the string which follows the function
+   name. Report an error if it cannot be done. */
+            result->arg[ 0 ] = MakeLabelTree( exp + oplen, nc - oplen, status );
+            if( !result->arg[ 0 ] && astOK ) {
+               for( i = 0; i < oplen; i++ ) buff[ i ] = c[ i ];
+               buff[ oplen ] = 0;
+               astError( AST__BADUN, "Missing argument for '%s'.", status, buff );
+            }
+         }
+
+/* Arrive here if the supplied string does not contain a POW operator
+   or function at depth zero. Check to see if the whole string is contained
+   within parentheses, In which we interpret the contents of the
+   parentheses as a units expression. It is safe simply to check the
+   first and last characters (a string like "(fred)(Harry)" is not a
+   legal possibility since there should be an operator in the middle).*/
+      } else if( nc > 0 && ( exp[ 0 ] == '(' && exp[ nc - 1 ] == ')' ) ) {
+         result = MakeLabelTree( exp + 1, nc - 2, status );
+
+/* Does the string begin with a numerical constant? */
+      } else if( ConStart( exp, &con, &n, status ) == 1 ) {
+
+/* If the entire string was a numerical constant, represent it by a LDCON
+   node. */
+         if( n == nc ) {
+            result = NewNode( NULL, OP_LDCON, status );
+            if( astOK ) result->con = con;
+
+/* If there was anything following the numerical constant, report an
+   error. */
+         } else if( astOK ){
+            astError( AST__BADUN, "Missing operator after "
+                      "numerical string '%.*s'.", status, n, exp );
+         }
+
+/* The only legal possibility left is that the string represents the basic
+   label. Create an OP_LDVAR node for it and store the basic label as
+   the node name, omitting any enclosing white space. */
+      } else {
+         result = NewNode( NULL, OP_LDVAR, status );
+         if( astOK ) {
+            result->name = astStore( NULL, exp, nc + 1 );
+            if( astOK ) ( (char *) result->name)[ nc ] = 0;
+         }
+      }
+   }
+
+/* Free any returned tree if an error has occurred. */
+   if( !astOK ) result = FreeTree( result, status );
+
+/* Return the result. */
+   return result;
+}
+
+static UnitNode *MakeTree( const char *exp, int nc, int lock, int *status ){
+/*
+*  Name:
+*     MakeTree
+
+*  Purpose:
+*     Convert an algebraic units expression into a tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *MakeTree( const char *exp, int nc, int lock, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function converts an algebraic units expression into a tree of
+*     UnitNodes. It is a service routine for CreateTree. The roots of the
+*     returned tree (i.e. the LDVAR nodes) refer to the unit symbols
+*     contained within the supplied expression (i.e. definitions of these
+*     units are not grafted onto the tree in place of the original nodes,
+*     as is done by CreateTree).
+
+*  Parameters:
+*     exp
+*        The units expression. This should not include any leading or
+*        trailing spaces.
+*     nc
+*        The number of characters from "exp" to use.
+*     lock
+*        Use a mutex to guard access to the KnownUnits list?
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a UnitNode which forms the head of a tree of UnitNodes
+*     representing the supplied unit expression.
+
+*  Notes:
+*     -  A NULL value is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   KnownUnit *munit;
+   KnownUnit *unit;
+   Multiplier *mmult;
+   Multiplier *mult;
+   Oper op;
+   UnitNode *result;
+   char buff[ 10 ];
+   char d;
+   const char *c;
+   double con;
+   int depth;
+   int i;
+   int l;
+   int maxlen;
+   int n;
+   int oplen;
+   int plural;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status, and that we have a string. */
+   if ( !astOK || !exp || nc <= 0 ) return result;
+
+/* Scan through the supplied string from the end to the start looking for
+   the last multiplication or division operator at zero depth of nesting
+   within parentheses. We go backwards through the string in order to
+   give the correct priority to multiple division operators (i.e. "a/b/c"
+   needs to be interpreted as "(a/b)/c", not "a/(b/c)"). */
+   op = OP_NULL;
+   oplen = 1;
+   depth = 0;
+   c = exp + nc - 1;
+   i = nc - 1;
+   while( i >= 0 ){
+
+/* If this character is an opening parenthesis, decrement the depth of
+   nesting. Report an error if it ever goes negative. */
+      if( *c == '(' ) {
+         depth--;
+         if( depth < 0 && astOK ) {
+            astError( AST__BADUN, "Missing closing parenthesis." , status);
+            break;
+         }
+
+/* An opening parenthesis at level zero must always be either the first
+   character in the string, or be preceded by the name of a function, or
+   be preceded by an operator. If none of these are true, assume there is
+   an implicit multiplication operator before the parenthesis. */
+         if( depth == 0 && i > 0 ) {
+            d = *( c - 1 );
+            if( d != '*' && d != '/' && d != '^' && d != '.' && d != ' ' &&
+                !EndsWith( c, i + 1, "sqrt(", status ) && !EndsWith( c, i + 1, "exp(", status ) &&
+                !EndsWith( c, i + 1, "ln(", status ) && !EndsWith( c, i + 1, "log(", status ) ) {
+               op = OP_MULT;
+               oplen = 0;
+               break;
+            }
+         }
+
+/* If this character is an closing parenthesis, increment the depth of
+   nesting. */
+      } else if( *c == ')' ) {
+         depth++;
+
+/* A closing parenthesis at level zero must always be either the last
+   character in the string, or be followed by an operator. If neither of
+   these are true, assume there is an implicit multiplication operator. */
+         if( depth == 1 && i < nc - 1 ) {
+            d = *(c+1);
+            if( d != '*' && d != '/' && d != '^' && d != '.' && d != ' ') {
+               op = OP_MULT;
+               oplen = 0;
+
+/* Correct "i" so that it gives the length of the left hand operand of
+   the implicit MULT operator, correct "c" so that it points to the first
+   character in the right hand operand, and leave the loop. */
+               i++;
+               c++;
+               break;
+            }
+         }
+
+/* Ignore all other characters unless they are at zero depth of nesting. */
+      } else if( depth == 0 ) {
+
+/* Compare the next part of the string with each of the multiplication
+   and division operators. */
+         if( *c == '/' ) {
+            op = OP_DIV;
+
+         } else if( *c == ' ' ) {
+            op = OP_MULT;
+
+/* An asterisk is only treated as a multiplication symbol if it does not occur
+   before or after another asterisk. */
+         } else if( *c == '*' ) {
+            if(  c == exp ) {
+               if( *(c+1) != '*' ) op = OP_MULT;
+            } else if( i == nc - 1 ) {
+               if( *(c-1) != '*' ) op = OP_MULT;
+            } else {
+               if( *(c+1) != '*' && *(c-1) != '*' ) op = OP_MULT;
+            }
+
+/* A dot is only treated as a multiplication symbol if it does not occur
+   between two digits. */
+         } else if( *c == '.' ) {
+            if( ( c == exp || !isdigit( *(c-1) ) ) &&
+                ( i == nc - 1 || !isdigit( *(c+1) ) ) ) {
+               op = OP_MULT;
+            }
+         }
+      }
+
+/* If an operator was found, break out of the loop. */
+      if( op != OP_NULL ) break;
+
+/* Pass on to check the next character. */
+      i--;
+      c--;
+   }
+
+/* If a multiplication or division operator was found, the strings on either
+   side of it should be valid unit expressions, in which case we use this
+   routine recursively to create corresponding trees of UnitNodes. */
+   if( op != OP_NULL ) {
+
+/* Create a UnitNode for the binary operator. */
+      result = NewNode( NULL, op, status );
+      if( astOK ) {
+
+/* Create a tree of unit nodes from the string which precedes the binary
+   operator. Report an error if it cannot be done. */
+         result->arg[ 0 ] = MakeTree( exp, i, lock, status );
+         if( !result->arg[ 0 ] && astOK ) {
+            for( i = 0; i < oplen; i++ ) buff[ i ] = c[ i ];
+            buff[ oplen ] = 0;
+            astError( AST__BADUN, "Missing operand before '%s'.", status, buff );
+         }
+
+/* Create a tree of unit nodes from the string which follows the binary
+   operator. Report an error if it cannot be done. */
+         result->arg[ 1 ] = MakeTree( c + oplen, nc - i - oplen, lock, status );
+         if( !result->arg[ 1 ] && astOK ) {
+            for( i = 0; i < oplen; i++ ) buff[ i ] = c[ i ];
+            buff[ oplen ] = 0;
+            astError( AST__BADUN, "Missing operand after '%s'.", status, buff );
+         }
+      }
+
+/* If no multiplication or division operator was found at depth zero, check
+   that the final depth of nesting was zero. Report an error if not. */
+   } else if( depth > 0 && astOK ) {
+      astError( AST__BADUN, "Missing opening parenthesis." , status);
+
+/* Otherwise check for a "Pow" operator at depth zero. */
+   } else {
+
+/* Scan through the supplied string looking for the first pow operator at
+   zero depth of nesting within parentheses. */
+      depth = 0;
+      c = exp;
+      i = 0;
+      while( i < nc && *c ){
+
+/* If this character is an opening parenthesis, increment the depth of
+   nesting. */
+         if( *c == '(' ) {
+            depth++;
+
+/* If this character is an closing parenthesis, decrement the depth of
+   nesting. Report an error if it ever goes negative. */
+         } else if( *c == ')' ) {
+            depth--;
+            if( depth < 0 && astOK ) {
+               astError( AST__BADUN, "Missing opening parenthesis." , status);
+               break;
+            }
+
+/* Ignore all other characters unless they are at zero depth of nesting. */
+         } else if( depth == 0 ) {
+
+/* Compare the next part of the string with each of the "pow" operators. */
+            if( !strncmp( c, "**", 2 ) ) {
+               op = OP_POW;
+               oplen = 2;
+            } else if( *c == '^' ) {
+               op = OP_POW;
+               oplen = 1;
+            }
+
+/* If an operator was found, break out of the loop. */
+            if( op != OP_NULL ) break;
+         }
+
+/* Pass on to check the next character. */
+         i++;
+         c++;
+      }
+
+/* If a "pow" operator was found, the strings on either side of it should be
+   valid unit expressions, in which case we use this routine recursively to
+   create corresponding trees of UnitNodes. */
+      if( op != OP_NULL ) {
+
+/* Create a UnitNode for the operator. */
+         result = NewNode( NULL, op, status );
+         if( astOK ) {
+
+/* Create a tree of unit nodes from the string which precedes the binary
+   operator. Report an error if it cannot be done. */
+            result->arg[ 0 ] = MakeTree( exp, i, lock, status );
+            if( !result->arg[ 0 ] && astOK ) {
+               for( i = 0; i < oplen; i++ ) buff[ i ] = c[ i ];
+               buff[ oplen ] = 0;
+               astError( AST__BADUN, "Missing operand before '%s'.", status, buff );
+            }
+
+/* Create a tree of unit nodes from the string which follows the binary
+   operator. Report an error if it cannot be done. */
+            result->arg[ 1 ] = MakeTree( c + oplen, nc - i - oplen, lock, status );
+            if( !result->arg[ 1 ] && astOK ) {
+                for( i = 0; i < oplen; i++ ) buff[ i ] = c[ i ];
+               buff[ oplen ] = 0;
+               astError( AST__BADUN, "Missing operand after '%s'.", status, buff );
+            }
+         }
+
+/* If no binary operator was found at depth zero, see if the supplied string
+   starts with a function name (the only legal place for a function name
+   given that the string has no binary operators at depth zero). */
+      } else {
+         if( !strncmp( exp, "sqrt(", 5 ) || !strncmp( exp, "SQRT(", 5 ) ) {
+            op = OP_SQRT;
+            oplen = 4;
+         } else if( !strncmp( exp, "exp(", 4 ) || !strncmp( exp, "EXP(", 4 ) ) {
+            op = OP_EXP;
+            oplen = 3;
+         } else if( !strncmp( exp, "ln(", 3 ) || !strncmp( exp, "LN(", 3 ) ) {
+            op = OP_LN;
+            oplen = 2;
+         } else if( !strncmp( exp, "log(", 4 ) || !strncmp( exp, "LOG(", 4 ) ) {
+            op = OP_LOG;
+            oplen = 3;
+         }
+
+/* If a function was found, the string following the function name
+   (including the opening parenthesis) should form a legal units
+   expresssion (all the supported functions take a single argument and
+   so we do not need to worry about comma-separated lists of function
+   arguments). Use this routine recursively to create a tree of UnitNodes
+   from the string which forms the function argument. */
+         if( op != OP_NULL ) {
+
+/* Create a UnitNode for the function. */
+            result = NewNode( NULL, op, status );
+            if( astOK ) {
+
+/* Create a tree of unit nodes from the string which follows the function
+   name. Report an error if it cannot be done. */
+               result->arg[ 0 ] = MakeTree( exp + oplen, nc - oplen, lock, status );
+               if( !result->arg[ 0 ] && astOK ) {
+                  for( i = 0; i < oplen; i++ ) buff[ i ] = c[ i ];
+                  buff[ oplen ] = 0;
+                  astError( AST__BADUN, "Missing argument for '%s'.", status, buff );
+               }
+            }
+
+/* Arrive here if the supplied string does not contain a binary operator
+   or function at depth zero. Check to see if the whole string is contained
+   within parentheses, In which we interpret the contents of the
+   parentheses as a units expression. It is safe simply to check the
+   first and last characters (a string like "(fred)(Harry)" is not a
+   legal possibility since there should be an operator in the middle).*/
+         } else if( exp[ 0 ] == '(' && exp[ nc - 1 ] == ')' ) {
+            result = MakeTree( exp + 1, nc - 2, lock, status );
+
+/* Does the string begin with a numerical constant? */
+         } else if( ConStart( exp, &con, &n, status ) == 1 ) {
+
+/* If the entire string was a numerical constant, represent it by a LDCON
+   node. */
+            if( n == nc ) {
+               result = NewNode( NULL, OP_LDCON, status );
+               if( astOK ) result->con = con;
+
+/* If there was anything following the numerical constant, report an
+   error. */
+            } else if( astOK ){
+               astError( AST__BADUN, "Missing operator after "
+                         "numerical string '%.*s'.", status, n, exp );
+            }
+
+/* Does the string represent one of the named constants? If so represent it
+   by a an appropriate operator. */
+         } else if( nc == 2 && ( !strncmp( exp, "pi", 2 ) ||
+                                 !strncmp( exp, "PI", 2 ) ) ) {
+            result = NewNode( NULL, OP_LDPI, status );
+
+         } else if( nc == 1 && ( !strncmp( exp, "e", 1 ) ||
+                                 !strncmp( exp, "E", 1 ) ) ) {
+            result = NewNode( NULL, OP_LDE, status );
+
+/* The only legal possibility left is that the string represents the name
+   of a basic unit, possibly prefixed by a multiplier character. */
+         } else {
+
+/* See if the string ends with the symbol for any of the known basic
+   units. If it matches more than one basic unit, choose the longest.
+   First ensure descriptions of the known units are  available. */
+            mmult = NULL;
+            plural = 0;
+            while( 1 ) {
+               unit = GetKnownUnits( lock, status );
+
+               maxlen = -1;
+               munit = NULL;
+               while( unit ) {
+                  if( SplitUnit( exp, nc, unit->sym, 1, &mult, &l, status ) ) {
+                     if( l > maxlen ) {
+                        maxlen = l;
+                        munit = unit;
+                        mmult = mult;
+                     }
+                  }
+                  unit = unit->next;
+               }
+
+/* If the above did not produce a match, try matching the unit symbol
+   case insensitive. */
+               if( !munit ) {
+                  unit = GetKnownUnits( lock, status );
+                  while( unit ) {
+                     if( SplitUnit( exp, nc, unit->sym, 0, &mult, &l, status ) ) {
+                        if( l > maxlen ) {
+                           maxlen = l;
+                           munit = unit;
+                           mmult = mult;
+                        }
+                     }
+                     unit = unit->next;
+                  }
+               }
+
+/* If the above did not produce a match, try matching the unit label
+   case insensitive. */
+               if( !munit ) {
+                  unit = GetKnownUnits( lock, status );
+                  while( unit ) {
+                     if( SplitUnit( exp, nc, unit->label, 0, &mult, &l, status ) ) {
+                        if( l > maxlen ) {
+                           maxlen = l;
+                           munit = unit;
+                           mmult = mult;
+                        }
+                     }
+                     unit = unit->next;
+                  }
+               }
+
+/* If we still do not have a match, and if the string ends with "s", try
+   removing the "s" (which could be a plural as in "Angstroms") and
+   trying again. */
+               if( !munit && nc > 1 && !plural &&
+                   ( exp[ nc - 1 ] == 's' || exp[ nc - 1 ] == 'S' ) ) {
+                  plural = 1;
+                  nc--;
+               } else {
+                  break;
+               }
+            }
+            if( plural ) nc++;
+
+/* If a known unit and multiplier combination was found, create an
+   OP_LDVAR node from it. */
+            unit = munit;
+            mult = mmult;
+            if( unit ) {
+
+/* If the unit is an alias for another unit, it will have a non-NULL
+   value for its "use" component.In this case, use the unit for which the
+   identified unit is an alias. */
+               result = NewNode( NULL, OP_LDVAR, status );
+               if( astOK ) {
+                  result->unit = unit->use ? unit->use : unit;
+                  result->mult = mult;
+                  result->name = astStore( NULL, result->unit->sym, result->unit->symlen + 1 );
+               }
+
+/* If no known unit and multiplier combination was found, we assume the
+   string represents a new user-defined basic unit, possibly preceded by a
+   standard multiplier prefix. */
+            } else {
+
+/* Check the string to see if starts with a known multiplier prefix (but
+   do not allow the multiplier to account for the entire string). */
+               mult = GetMultipliers( status );
+               c = exp;
+               while( mult ) {
+                  n = nc - mult->symlen;
+                  if( n > 0 && !strncmp( exp, mult->sym, mult->symlen ) ) {
+                     c += mult->symlen;
+                     break;
+                  }
+                  mult = mult->next;
+               }
+               if( !mult ) n = nc;
+
+/* Check there are no illegal characters in the following string. */
+               for( i = 0; i < n && astOK; i++ ) {
+                  if( !isalpha( c[ i ] ) ) {
+                     astError( AST__BADUN, "Illegal character '%c' found.", status, c[ i ] );
+                     break;
+                  }
+               }
+
+/* If succesfull, create an OP_LDVAR node for th user-defined basic unit. */
+               if( astOK ) {
+                  result = NewNode( NULL, OP_LDVAR, status );
+                  if( astOK ) {
+                     result->mult = mult;
+                     result->name = astStore( NULL, c, n + 1 );
+                     if( astOK ) ( (char *) result->name)[ n ] = 0;
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Free any returned tree if an error has occurred. */
+   if( !astOK ) result = FreeTree( result, status );
+
+/* Return the result. */
+   return result;
+}
+
+static void MakeUnitAlias( const char *sym, const char *alias, int *status ){
+/*
+*  Name:
+*     MakeUnitAlias
+
+*  Purpose:
+*     Create a KnownUnit structure describing an alias for a known unit.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     void MakeUnitAlias( const char *sym, const char *alias, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function creates a KnownUnit structure decribing an alias for a
+*     known unit, and adds it to the head of the linked list of known units
+*     stored in a module variable. An alias is a KnownUnit which is
+*     identical to an existing known but which has a different symbol.
+
+*  Parameters:
+*     sym
+*        A pointer to the symbol string of an existing KnwonUnit. The string
+*        should not include any multiplier prefix.
+*     alias
+*        A pointer to the symbol string to use as the alasi for the existing
+*        KnownUnit. The string should not include any multiplier prefix.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  The supplied symbol and label strings are not copied. The
+*     supplied pointers are simply stored in the returned structure.
+*     Therefore the strings to which the pointers point should not be
+*     modified after this function returned (in fact this function is
+*     always called with literal strings for these arguments).
+*/
+
+/* Local Variables: */
+   KnownUnit *unit;
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Search the existing list of KnownUnits for the specified symbol. */
+   unit = known_units;
+   while( unit ) {
+      if( !strcmp( sym, unit->sym ) ) {
+
+/* Create a new KnownUnit for the alias. It will becomes the head of the
+   known units chain. */
+         MakeKnownUnit( alias, unit->label, NULL, status );
+
+/* Store a pointer to the KnownUnit which is to be used in place of the
+   alias. */
+         known_units->use = unit;
+
+/* Leave the loop. */
+         break;
+      }
+
+/* Move on to check the next existing KnownUnit. */
+      unit = unit->next;
+   }
+
+/* Report an error if the supplied unit was not found. */
+   if( !unit ) {
+      astError( AST__INTER, "MakeUnitAlias(Unit): Cannot find existing "
+                "units \"%s\" to associate with the alias \"%s\" (AST "
+                "internal programming error).", status, sym, alias );
+   }
+}
+
+static UnitNode *ModifyPrefix( UnitNode *old, int *status ) {
+/*
+*  Name:
+*     ModifyPrefix
+
+*  Purpose:
+*     Replace a MULT or DIV node with a LDVAR and suitable multiplier.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *ModifyPrefix( UnitNode *old, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function checks the supplied node. If it is a DIV or MULT node
+*     in which one argument is an LDVAR and the other is a constant, then
+*     its checks to see if the constant can be absorbed into the LDVAR by
+*     changing the multiplier in the LDVAR node. If so, it returns a new
+*     node which is an LDVAR with the modified multiplier. Otherwise it
+*     returns NULL.
+
+*  Parameters:
+*     old
+*        Pointer to an existing UnitNode to be checked.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new UnitNode.
+
+*  Notes:
+*     - A value of NULL will be returned if this function is invoked with
+*     the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   Multiplier *mult;
+   Multiplier *mmult;
+   UnitNode *ldcon;
+   UnitNode *ldvar;
+   UnitNode *newtree;
+   UnitNode *result;
+   double con;
+   double cmult;
+   double r;
+   double rmin;
+   int recip;
+   int changed;
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* Indicate that we have not yet found any reason to return a changed
+   node. */
+   changed = 0;
+
+/* Check the supplied node is a DIV or MULT node. */
+   if( old->opcode == OP_DIV || old->opcode == OP_MULT ) {
+
+/* Get a copy of the supplied tree which we can modify safely. */
+      newtree = CopyTree( old, status );
+
+/* Identify the LDVAR argument (if any). */
+      if( newtree->arg[ 0 ]->opcode == OP_LDVAR ) {
+         ldvar = newtree->arg[ 0 ];
+
+      } else if( newtree->arg[ 1 ]->opcode == OP_LDVAR ) {
+         ldvar = newtree->arg[ 1 ];
+
+      } else {
+         ldvar = NULL;
+      }
+
+/* Identify the LDCON argument (if any). */
+      if( newtree->arg[ 0 ]->opcode == OP_LDCON ) {
+         ldcon = newtree->arg[ 0 ];
+
+      } else if( newtree->arg[ 1 ]->opcode == OP_LDCON ) {
+         ldcon = newtree->arg[ 1 ];
+
+      } else {
+         ldcon = NULL;
+      }
+
+/* If either was not found, return NULL. */
+      if( !ldvar || !ldcon ) {
+         newtree = FreeTree( newtree, status );
+
+/* Otherwise, extract the multiplier constant. If there is no multiplier, the
+   constant is 1.0. */
+      } else {
+         cmult = ldvar->mult ? ldvar->mult->scale: 1.0;
+
+/* Extract the constant. */
+         con = ldcon->con;
+
+/* Combine the multiplier and the constant. The resulting constant is a
+   factor which is used to multiply the LDVAR quantity. If the original
+   node is a DIV node in which the LDVAR is in the denominator, then
+   flag that we need to reciprocate the new MULT node which represents
+   "constant*LDVAR" before returning. */
+         if( newtree->opcode == OP_MULT ) {
+            con = con*cmult;
+            recip = 0;
+         } else {
+            con = cmult/con;
+            recip = ( ldvar == newtree->arg[ 1 ] );
+         }
+
+/* Find the closest known multiplier to the new constant. */
+         rmin = ( con > 1 ) ? con : 1.0/con;
+         mmult = NULL;
+         mult = GetMultipliers( status );
+         while( mult ) {
+            r = ( con > mult->scale) ? con/mult->scale : mult->scale/con;
+            if( r < rmin ) {
+               mmult = mult;
+               rmin = r;
+            }
+            mult = mult->next;
+         }
+
+/* Modify the constant to take account of the new multiplier chosen
+   above. "mmult" will be NULL if the best multiplier is unity. */
+         if( mmult ) con = con/mmult->scale;
+
+/* If they have changed, associate the chosen multiplier with the LDVAR node,
+   and the constant with the LDCON node. */
+         if( ldvar->mult != mmult ) {
+            ldvar->mult = mmult;
+            changed = 1;
+         }
+
+         if( ldcon->con != con ) {
+            ldcon->con = con;
+            changed = 1;
+         }
+
+/* Unless the node is proportional to the reciprocal of the variable, the
+   new node should be a MULT node (it may originally have been a DIV). */
+         if( !recip ) {
+            if( newtree->opcode != OP_MULT ){
+               newtree->opcode = OP_MULT;
+               changed = 1;
+            }
+
+/* If the constant is 1.0 we can just return the LDVAR node by itself. */
+            if( fabs( con - 1.0 ) < 1.0E-6 ) {
+               result = CopyTree( ldvar, status );
+               newtree = FreeTree( newtree, status );
+               changed = 1;
+
+/* Otherwise return the modified tree containing both LDVAR and LDCON nodes. */
+            } else {
+               result = newtree;
+            }
+
+/* If the node is proportional to the reciprocal of the variable, the
+   new node will already be a DIV node and will have an LDCON as the first
+   argument (numerator) and an LDVAR as the second argument (denominator). */
+         } else {
+
+/* The first argument (the numerator) should be the reciprocal of the constant
+   found above. */
+            ldcon->con = 1.0/ldcon->con;
+            if( !astEQUAL( ldcon->con, old->arg[0]->con ) ) changed = 1;
+
+/* Return the modified tree containing both LDVAR and LDCON nodes. */
+            result = newtree;
+         }
+      }
+   }
+
+/* If the new and old trees are equivalent, then we do not need to return
+   it. */
+   if( !changed && result ) result = FreeTree( result, status );
+
+/* Return the answer. */
+   return result;
+}
+
+
+static UnitNode *NewNode( UnitNode *old, Oper code, int *status ) {
+/*
+*  Name:
+*     NewNode
+
+*  Purpose:
+*     Create and initialise a new UnitNode.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     UnitNode *NewNode( UnitNode *old, Oper code, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function creates and initialises a new UnitNode, or
+*     re-initialises an existing UnitNode to use a different op code.
+
+*  Parameters:
+*     old
+*        Pointer to an existing UnitNode to be modified, or NULL to create
+*        a new UnitNode.
+*     code
+*        The op code for the new UnitNode.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new UnitNode.
+
+*  Notes:
+*     - A value of NULL will be returned if this function is invoked with
+*     the global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   UnitNode **args;
+   UnitNode *result;
+   int i;
+
+/* Initialise. */
+   result = NULL;
+   args = NULL;
+
+/* Check the inherited status. */
+   if( !astOK ) return result;
+
+/* If an existig UnitNode was supplied, free any memory used to hold
+   pointers to its arguments. */
+   if( old ) {
+      old->arg = astFree( old->arg );
+      result = old;
+
+/* Otherwise, allocate memory for a new structure. */
+   } else {
+      result = astMalloc( sizeof( UnitNode ) );
+   }
+
+/* Check the pointer can be used safely. */
+   if( astOK ) {
+
+/* Initialise the members of the UnitNode structure. */
+      result->opcode = code;
+      result->arg = NULL;
+      result->con = AST__BAD;
+      result->name = NULL;
+      result->unit = NULL;
+      result->mult = NULL;
+      result->narg = 0;
+
+      switch( code ){
+         case OP_LDPI:
+            result->con = PI;
+            break;
+
+         case OP_LDE:
+            result->con = E;
+            break;
+
+         case OP_LOG:
+         case OP_LN:
+         case OP_EXP:
+         case OP_SQRT:
+            result->narg = 1;
+            break;
+
+         case OP_POW:
+         case OP_DIV:
+         case OP_MULT:
+            result->narg = 2;
+            break;
+
+         default:
+            ;
+      }
+
+/* Allocate memory for the UnitNode pointers which will locate the
+   nodes forming the arguments to the new node. */
+      args = astMalloc( (result->narg)*sizeof( UnitNode * ) );
+      if( astOK ) {
+         result->arg = args;
+
+/* Initialise the argument pointers to NULL. */
+         for( i = 0; i < result->narg; i++ ) args[ i ] = NULL;
+      }
+   }
+
+/* Free any result if an error occurred. */
+   if( !astOK ) {
+      args = astFree( args );
+      result = astFree( result );
+   }
+
+/* Return the answer. */
+   return result;
+}
+
+static void RemakeTree( UnitNode **node, int *status ) {
+/*
+*  Name:
+*     RemakeTree
+
+*  Purpose:
+*     Replace derived units within a tree of UnitNodes by basic units.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     void RemakeTree( UnitNode **node, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function searches for LDVAR nodes (i.e. references to unit
+*     symbols) within the given tree, and replaces each such node which
+*     refers to known derived unit with a sub-tree of nodes which
+*     define the derived unit in terms of known basic units.
+
+*  Parameters:
+*     node
+*        The address of a pointer to the UnitNode at the head of the tree
+*        which is to be simplified. On exit the supplied tree is freed and a
+*        pointer to a new tree is placed at the given address.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   KnownUnit *unit;
+   int i;
+   UnitNode *newnode;
+
+/* Check inherited status. */
+   if( !astOK ) return;
+
+/* Initially, we have no replacement node */
+   newnode = NULL;
+
+/* If this is an LDVAR node... */
+   if( (*node)->opcode == OP_LDVAR ) {
+
+/* If the LDVAR node has a multiplier associated with it, we need to
+   introduce a OP_MULT node to perform the scaling. */
+      if( (*node)->mult ) {
+         newnode = NewNode( NULL, OP_MULT, status );
+         if( astOK ) {
+            newnode->arg[0] = NewNode( NULL, OP_LDCON, status );
+            if( astOK ) {
+               newnode->arg[0]->con = 1.0/(*node)->mult->scale;
+
+/* See if the node refers to a known unit. If not, or if the known unit
+   is a basic unit (i.e. not a derived unit) use the supplied node for
+   the second argument of the OP_MULT node (without the multiplier).
+   Otherwise, use a copy of the tree which defines the derived unit. */
+               unit = (*node)->unit;
+               if( unit && unit->head ) {
+                  newnode->arg[1] = CopyTree( unit->head, status );
+               } else {
+                  newnode->arg[1] = CopyTree( *node, status );
+                  if( astOK ) newnode->arg[1]->mult = NULL;
+               }
+            }
+         }
+
+/* If no multiplier is supplied, the replacement node is simply the tree
+   which defines the unscaled unit (if known), or the original node (if
+   unknown). */
+      } else {
+         unit = (*node)->unit;
+         if( unit && unit->head ) newnode = CopyTree( unit->head, status );
+      }
+
+/* If this is not an LDVAR Node, remake the sub-trees which form the
+   arguments of this node. */
+   } else {
+      for( i = 0; i < (*node)->narg; i++ ) {
+         RemakeTree( &((*node)->arg[ i ]), status );
+      }
+   }
+
+/* If an error has occurred, free any new node. */
+   if( !astOK ) newnode = FreeTree( newnode, status );
+
+/* If we have a replacement node, free the supplied tree and return a
+   pointer to the new tree. */
+   if( newnode ) {
+      FreeTree( *node, status );
+      *node = newnode;
+   }
+}
+
+static int ReplaceNode( UnitNode *target, UnitNode *old, UnitNode *new, int *status ) {
+/*
+*  Name:
+*     ReplaceNode
+
+*  Purpose:
+*     Replace a node within a tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     int ReplaceNode( UnitNode *target, UnitNode *old, UnitNode *new, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function replaces a specified node within a tree of UnitNodes
+*     with another given node. The original node is freed if found.
+
+*  Parameters:
+*     target
+*        A pointer to the UnitNode at the head of the tree containing the
+*        node to be replaced.
+*     old
+*        A pointer to the UnitNode to be replaced.
+*     new
+*        A pointer to the UnitNode to replace "old".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Return Value:
+*     Non-zero if the "old" node was found and replaced (in which case
+*     the "old" node will have been freed).
+
+*  Notes:
+*     - It is assumed that the "old" node occurs at most once within the
+*     target tree.
+*     - The node at the head of the target tree is not compared with the
+*     "old" node. It is assumed the called will already have done this.
+*     - A value of zero is returned if an error has already occurred, or
+*     if this function fails for any reason.
+
+*/
+
+/* Local Variables: */
+   int i;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* Loop round the arguments of the node at the head of the target tree.
+   Break out of the loop as soone as the old node is found. */
+   for( i = 0; i < target->narg; i++ ) {
+
+/* If this argument is the node to be replaced, free the old one and store
+   the new one, and then leave the loop. */
+      if( target->arg[ i ] == old ) {
+         FreeTree( old, status );
+         target->arg[ i ] = new;
+         result = 1;
+         break;
+
+/* Otherwise use this function recursively to search for the old node
+   within the current argument. */
+      } else {
+         if( ReplaceNode( target->arg[ i ], old, new, status ) ) break;
+      }
+   }
+
+/* If an error has occurred, return zero. */
+   if( !astOK ) result = 0;
+
+/* Return the answer. */
+   return result;
+}
+
+static int SimplifyTree( UnitNode **node, int std, int *status ) {
+/*
+*  Name:
+*     SimplifyTree
+
+*  Purpose:
+*     Simplify a tree of UnitNodes.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     int SimplifyTree( UnitNode **node, int std, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function simplifies a tree of UnitNodes. It is assumed that
+*     all the OP_LDVAR nodes in the tree refer to the same basic unit.
+*     A primary purpose of this function is to standardise the tree so
+*     that trees which implement equivalent transformations but which
+*     have different structures can be compared (for instance, so that
+*     "2*x" and "x*2" are treated as equal trees). If "std" is non-zero,
+*     reducing the complexity of the tree is only of secondary importance.
+*     This explains why some "simplifications" actually produced trees which
+*     are more complicated.
+
+*  Parameters:
+*     node
+*        The address of a pointer to the UnitNode at the head of the tree
+*        which is to be simplified. On exit the supplied tree is freed and a
+*        pointer to a new tree is placed at the given address.
+*     std
+*        If non-zero, perform standardisations. Otherwise only perform
+*        genuine simplifications.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if some change was made to the tree.
+
+*/
+
+/* Local Variables: */
+   int i;
+   UnitNode *newnode;
+   UnitNode *node1;
+   UnitNode *node2;
+   Oper op;
+   UnitNode **factors;
+   double *powers;
+   int nfactor;
+   double coeff;
+   int result;
+
+/* Initialise */
+   result = 0;
+
+/* Check inherited status. */
+   if( !astOK ) return result;
+
+/* Initiallially, we have no replacement node. */
+   newnode = NULL;
+
+/* First replace any complex constant expressions any corresponding
+   OP_LDCON nodes. */
+   FixConstants( node, 0, status );
+
+/* Simplify the sub-trees corresponding to the arguments of the node at
+   the head of the supplied tree. */
+   for( i = 0; i < (*node)->narg; i++ ) {
+      if( SimplifyTree( &( (*node)->arg[ i ] ), std, status ) ) result = 1;
+   }
+
+/* Now do specific simplifications appropriate to the nature of the node at
+   the head of the tree. */
+   op = (*node)->opcode;
+
+/* Natural log */
+/* =========== */
+/* We standardise argument powers into coefficients of the LN value. */
+   if( op == OP_LN ) {
+
+/* If the argument is a OP_EXP node, they cancel out. Return a copy of the
+   argument of OP_EXP node. */
+      if( (*node)->arg[ 0 ]->opcode == OP_EXP ) {
+         newnode = CopyTree( (*node)->arg[ 0 ]->arg[ 0 ], status );
+
+/* If the argument is an OP_POW node, rearrange the nodes to represent
+   k*ln(x) instead of ln(x**k) (note pow nodes always have a constant
+   exponent - this is checked in InvertConstants). SQRT arguments will
+   not occur because they will have been changed into POW nodes when the
+   arguments of the supplied head node were simplified above. */
+      } else if( std && (*node)->arg[ 0 ]->opcode == OP_POW ) {
+         newnode = NewNode( NULL, OP_MULT, status );
+         node1 = CopyTree( (*node)->arg[ 0 ]->arg[ 1 ], status );
+         node2 = NewNode( NULL, OP_LN, status );
+         if( astOK ) {
+            node2->arg[ 0 ] = CopyTree( (*node)->arg[ 0 ]->arg[ 0 ], status );
+            newnode->arg[ 0 ] = node1;
+            newnode->arg[ 1 ] = node2;
+         }
+      }
+
+/* Common log */
+/* ========== */
+/* We standardise natural logs into common logs. */
+   } else if( op == OP_LOG ) {
+      if( std ) {
+         newnode = NewNode( NULL, OP_DIV, status );
+         node1 = NewNode( NULL, OP_LN, status );
+         node2 = NewNode( NULL, OP_LDCON, status );
+         if( astOK ) {
+            node1->arg[ 0 ] = CopyTree( (*node)->arg[ 0 ], status );
+            node2->con = log( 10.0 );
+            newnode->arg[ 0 ] = node1;
+            newnode->arg[ 1 ] = node2;
+         }
+      }
+
+/* Exponential */
+/* =========== */
+/* We prefer to minimise the number of EXP nodes, so, for instance, we do not
+   change "exp(x*y)" to "exp(x)+exp(y)" (and the code for ADD nodes does
+   the inverse conversion). */
+   } else if( op == OP_EXP ) {
+
+/* If the argument is an OP_LN node, they cancel out. Return a copy of the
+   argument of the OP_LN node. Common log arguments will not occur because
+   they will have been changed into natural logs when the arguments of
+   the supplied head node were simplified above. */
+      if( (*node)->arg[ 0 ]->opcode == OP_LN ) {
+         newnode = CopyTree( (*node)->arg[ 0 ]->arg[ 0 ], status );
+      }
+
+/* Square root */
+/* =========== */
+/* We standardise sqrt nodes into pow nodes. */
+   } else if( op == OP_SQRT ) {
+      if( std ) {
+         newnode = NewNode( NULL, OP_POW, status );
+         node1 = CopyTree( (*node)->arg[ 0 ], status );
+         node2 = NewNode( NULL, OP_LDCON, status );
+         if( astOK ) {
+            node2->con = 0.5;
+            newnode->arg[ 0 ] = node1;
+            newnode->arg[ 1 ] = node2;
+         }
+      }
+
+/* Exponentiation */
+/* ============== */
+/* We want to simplfy factors. So, for instance, (x*y)**k is converted to
+   (x**k)*(y**k). */
+   } else if( op == OP_POW ) {
+
+/* If the first argument is an OP_EXP node, then change "(e**x)**k" into
+   "e**(k*x)" */
+      if( (*node)->arg[ 0 ]->opcode == OP_EXP ) {
+         newnode = NewNode( NULL, OP_EXP, status );
+         node1 = NewNode( NULL, OP_MULT, status );
+         if( astOK ) {
+            node1->arg[ 0 ] = CopyTree( (*node)->arg[ 1 ], status );
+            node1->arg[ 1 ] = CopyTree( (*node)->arg[ 0 ]->arg[ 0 ], status );
+            newnode->arg[ 0 ] = node1;
+         }
+
+/* "x**0" can be replaced by 1.0 */
+      } else if( (*node)->arg[ 1 ]->con == 0.0 ) {
+         newnode = NewNode( NULL, OP_LDCON, status );
+         if( astOK ) newnode->con = 1.0;
+
+/* "x**1" can be replaced by x */
+      } else if( astEQUAL( (*node)->arg[ 1 ]->con, 1.0 ) ) {
+         newnode = CopyTree( (*node)->arg[ 0 ], status );
+
+/* If the first argument is an OP_POW node, then change "(x**k1)**k2" into
+   "x**(k1*k2)" */
+      } else if( (*node)->arg[ 0 ]->opcode == OP_POW ) {
+         newnode = NewNode( NULL, OP_POW, status );
+         node1 = NewNode( NULL, OP_LDCON, status );
+         if( astOK ) {
+            node1->con = ( (*node)->arg[ 0 ]->arg[ 1 ]->con )*
+                         ( (*node)->arg[ 1 ]->con );
+            newnode->arg[ 0 ] = CopyTree( (*node)->arg[ 0 ]->arg[ 0 ], status );
+            newnode->arg[ 1 ] = node1;
+         }
+
+/* If the first argument is an OP_MULT node, then change "(x*y)**k" into
+   "(x**(k))*(y**(k))" */
+      } else if( std && (*node)->arg[ 0 ]->opcode == OP_MULT ) {
+         newnode = NewNode( NULL, OP_MULT, status );
+         node1 = NewNode( NULL, OP_POW, status );
+         if( astOK ) {
+            node1->arg[ 1 ] = CopyTree( (*node)->arg[ 1 ], status );
+            node2 = CopyTree( node1, status );
+            node1->arg[ 0 ] = CopyTree( (*node)->arg[ 0 ]->arg[ 0 ], status );
+            node2->arg[ 0 ] = CopyTree( (*node)->arg[ 0 ]->arg[ 1 ], status );
+            newnode->arg[ 0 ] = node1;
+            newnode->arg[ 1 ] = node2;
+         }
+      }
+
+/* Division. */
+/* ========= */
+/* We standardise divisions into corresponding multiplications. */
+   } else if( op == OP_DIV ) {
+
+/* Division by 1 is removed. */
+      if( astEQUAL( (*node)->arg[ 1 ]->con, 1.0 ) ){
+         newnode = CopyTree( (*node)->arg[ 0 ], status );
+
+/* Division by any other constant (except zero) is turned into a
+   multiplication by the reciprocal constant. */
+      } else if( (*node)->arg[ 1 ]->con != AST__BAD ) {
+         if( (*node)->arg[ 1 ]->con != 0.0 ) {
+            newnode = NewNode( NULL, OP_MULT, status );
+            node1 = NewNode( NULL, OP_LDCON, status );
+            if( astOK ) {
+               node1->con = 1.0/(*node)->arg[ 1 ]->con;
+               newnode->arg[ 0 ] = node1;
+               newnode->arg[ 1 ] = CopyTree( (*node)->arg[ 0 ], status );
+            }
+         } else {
+            astError( AST__BADUN, "Simplifying a units expression"
+                      "requires a division by zero." , status);
+         }
+
+/* Other divisions "x/y" are turned into "x*(y**(-1))" */
+      } else if( std ) {
+         newnode = NewNode( NULL, OP_MULT, status );
+         node1 = NewNode( NULL, OP_POW, status );
+         node2 = NewNode( NULL, OP_LDCON, status );
+         if( astOK ) {
+            node2->con = -1.0;
+            node1->arg[ 0 ] = CopyTree( (*node)->arg[ 1 ], status );
+            node1->arg[ 1 ] = node2;
+            newnode->arg[ 0 ] = CopyTree( (*node)->arg[ 0 ], status );
+            newnode->arg[ 1 ] = node1;
+         }
+      }
+
+/* Multiplication */
+/* ============== */
+   } else if( op == OP_MULT ) {
+
+/* If the right hand argument is constant, swap the arguments. */
+      if( (*node)->arg[ 1 ]->con != AST__BAD ) {
+         newnode = NewNode( NULL, OP_MULT, status );
+         if( astOK ) {
+            newnode->arg[ 0 ] = CopyTree( (*node)->arg[ 1 ], status );
+            newnode->arg[ 1 ] = CopyTree( (*node)->arg[ 0 ], status );
+         }
+
+/* Multiplication by zero produces a constant zero. */
+      } else if( (*node)->arg[ 0 ]->con == 0.0 ){
+         newnode = NewNode( NULL, OP_LDCON, status );
+         if( astOK ) newnode->con = 0.0;
+
+/* Multiplication by 1 is removed. */
+      } else if( astEQUAL( (*node)->arg[ 0 ]->con, 1.0 ) ){
+         newnode = CopyTree( (*node)->arg[ 1 ], status );
+
+/* For other MULT nodes, analyse the tree to find a list of all its
+   factors with an associated power for each one, and an overall constant
+   coefficient. */
+      } else if( std ) {
+         FindFactors( (*node), &factors, &powers, &nfactor, &coeff, status );
+
+/* Produce a new tree from these factors. The factors are standardised by
+   ordering them alphabetically (after conversion to a character string). */
+         newnode = CombineFactors( factors, powers, nfactor, coeff, status );
+
+/* Free resources */
+         factors = astFree( factors );
+         powers = astFree( powers );
+
+      }
+   }
+
+/* If we have produced a new node which is identical to the old node,
+   free it. Otherwise, indicate we have made some changes. */
+   if( newnode ) {
+      if( !CmpTree( newnode, *node, 1, status ) ) {
+         newnode = FreeTree( newnode, status );
+      } else {
+         result = 1;
+      }
+   }
+
+/* If an error has occurred, free any new node. */
+   if( !astOK ) newnode = FreeTree( newnode, status );
+
+/* If we have a replacement node, free the supplied tree and return a
+   pointer to the new tree. */
+   if( newnode ) {
+      FreeTree( *node, status );
+      *node = newnode;
+   }
+
+/* If the above produced some change, try re-simplifying the tree. */
+   if( result ) SimplifyTree( node, std, status );
+
+/* Return the result. */
+   return result;
+
+}
+
+static int SplitUnit( const char *str, int ls, const char *u, int cs,
+                      Multiplier **mult, int *l, int *status ) {
+/*
+*  Name:
+*     SplitUnit
+
+*  Purpose:
+*     Split a given string into unit name and multiplier.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     int SplitUnit( const char *str, int ls, const char *u, int cs,
+*                    Multiplier **mult, int *l, int *status  )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     Returns non-zer0 if the supplied string ends with the supplied unit
+*     name or label, and any leading string is a known multiplier.
+
+*  Parameters:
+*     str
+*        The string to test, typically containing a multiplier and a unit
+*        symbol or label.
+*     ls
+*        Number of characters to use from "str" (not including trailing null)
+*     u
+*        Pointer to the unit label or symbol string to be searched for.
+*     cs
+*        If non-zero, the test for "u" is case insensitive.
+*     mult
+*        Address of a location at which to return the multiplier at the
+*        start of the supplied string. NULL is returned if the supplied
+*        string does not match the supplied unit, or if the string
+*        includes no multiplier.
+*     l
+*        Address of an int in which to return the length of "u".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if "str" ends with "u" and starts with a null string or a
+*     known multiplier string.
+
+*/
+
+/* Local Variables: */
+   int ret;
+   int lm;
+   int lu;
+
+/* Initialise */
+   ret = 0;
+   *mult = NULL;
+   *l = 0;
+
+/* Check inherited status. */
+   if( !astOK ) return ret;
+
+/* Find the number of characters in the supplied unit label or symbol. The
+   difference between lu and ls must be the length of the multiplier. */
+   lu = strlen( u );
+   lm = ls - lu;
+
+/* Make sure "str" is not shorter than "u" */
+   if( lm >= 0 ) {
+
+/* Compare the end of "str" against "u" */
+      if( cs ? !strncmp( str + lm, u, lu ) :
+               !Ustrncmp( str + lm, u, lu, status ) ) {
+         ret = 1;
+
+/* If "str" ends with "u", see if it starts with a known multiplier */
+         if( lm > 0 ) {
+            ret = 0;
+            *mult = GetMultipliers( status );
+            while( *mult ) {
+               if( (*mult)->symlen == lm && !strncmp( str, (*mult)->sym, lm ) ) {
+                  ret = 1;
+                  break;
+               }
+               *mult = (*mult)->next;
+            }
+
+/* If not, try again using case-insensitive matching. */
+            if( !ret ) {
+               *mult = GetMultipliers( status );
+               while( *mult ) {
+                  if( (*mult)->symlen == lm && !Ustrncmp( str, (*mult)->sym, lm, status ) ) {
+                     ret = 1;
+                     break;
+                  }
+                  *mult = (*mult)->next;
+               }
+            }
+
+/* If not, try again using case-insensitive matching against the
+   multiplier label. */
+            if( !ret ) {
+               *mult = GetMultipliers( status );
+               while( *mult ) {
+                  if( (*mult)->lablen == lm && !Ustrncmp( str, (*mult)->label, lm, status ) ) {
+                     ret = 1;
+                     break;
+                  }
+                  *mult = (*mult)->next;
+               }
+            }
+
+         }
+      }
+   }
+
+   *l = lu;
+   return ret;
+}
+
+double astUnitAnalyser_( const char *in, double powers[9], int *status ){
+/*
+*+
+*  Name:
+*     astUnitAnalyser
+
+*  Purpose:
+*     Perform a dimensional analysis of a unti string.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     double astUnitAnalyser_( const char *in, double powers[9] )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function parses the supplied units string if possible, and
+*     returns a set of pwoers and a scaling factor which represent the
+*     units string.
+
+*  Parameters:
+*     in
+*        A string representation of the units, for instance "km/h".
+*     powers
+*        An array in which are returned the powers for each of the following
+*        basic units (in the order shown): kilogramme, metre, second, radian,
+*        Kelvin, count, adu, photon, magnitude, pixel. If the supplied unit
+*        does not depend on a given basic unit a value of 0.0 will be returned
+*        in the array. The returns values represent a system of units which is
+*        a scaled form of the supplied units, expressed in the basic units of
+*        m, kg, s, rad, K, count, adu, photon, mag and pixel. For instance, a
+*        returned array of [1,0,-2,0,0,0,0,0,0] would represent "m/s**2".
+
+*  Returned Value:
+*     A scaling factor for the supplied units. The is the value, in the
+*     units represented by the returned powers, which corresponds to a
+*     value of 1.0 in the supplied units.
+
+*  Notes:
+*     -  An error will be reported if the units string cannot be parsed
+*     or refers to units or functions which cannot be analysed in this way.
+*     -  AST__BAD is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   UnitNode *in_tree;
+   double result;
+
+/* Initialise */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Parse the input units string, producing a tree of UnitNodes which
+   represents the input units. A pointer to the UnitNode at the head of
+   the tree is returned if succesfull. Report a context message if this
+   fails. */
+   in_tree = CreateTree( in, 1, 1, status );
+   if( in_tree ) {
+
+/* Analyse the tree */
+      if( !DimAnal( in_tree, powers, &result, status ) && astOK ) {
+         result = AST__BAD;
+         astError( AST__BADUN, "astUnitAnalyser: Error analysing input "
+                   "units string '%s' (it may contain unsupported "
+                   "functions or dimensionless units).", status, in );
+      }
+
+/* Free the tree. */
+      in_tree = FreeTree( in_tree, status );
+
+   } else if( astOK ) {
+      astError( AST__BADUN, "astUnitAnalyser: Error parsing input "
+                "units string '%s'.", status, in );
+   }
+
+/* Return the result */
+   return result;
+}
+
+const char *astUnitLabel_( const char *sym, int *status ){
+/*
+*+
+*  Name:
+*     astUnitLabel
+
+*  Purpose:
+*     Return a string label for a given unit symbol.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     const char *astUnitLabel( const char *sym )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function returns a pointer to a constant string containing a
+*     descriptive label for the unit specified by the given unit symbol.
+
+*  Parameters:
+*     sym
+*        A string holing a known unit symbol.
+
+*  Returned Value:
+*     A pointer to constant string holding a descriptive label for the
+*     supplied unit. A NULL pointer is returned (without error) if the
+*     supplied unit is unknown.
+
+*  Notes:
+*     -  A NULL pointer is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   const char *result;
+   KnownUnit *unit;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Ensure descriptions of the known units are available. */
+   unit = GetKnownUnits( 1, status );
+
+/* Loop through the chain of known units looking for a unit with a symbol
+   equal to the supplied string. If found, store a pointer to its label
+   and break out of the loop. */
+   while( unit ) {
+      if( !strcmp( sym, unit->sym ) ) {
+         result = unit->label;
+         break;
+      }
+
+      unit = unit->next;
+   }
+
+/* Return the answer. */
+   return result;
+}
+
+AstMapping *astUnitMapper_( const char *in, const char *out,
+                            const char *in_lab, char **out_lab, int *status ){
+/*
+*+
+*  Name:
+*     astUnitMapper
+
+*  Purpose:
+*     Create a Mapping between two system of units.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     AstMapping *astUnitMapper( const char *in, const char *out,
+*                                const char *in_lab, char **out_lab )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function creates a Mapping between two specified system of
+*     units. It also modifes a supplied label (which is typically
+*     the axis label associated with the input units) so that it includes
+*     any functional change implied by the supplied "in" and "out" units.
+
+*  Parameters:
+*     in
+*        A string representation of the input units, for instance "km/h".
+*        See "Unit Representations:" below.
+*     out
+*        A string representation of the output units, for instance "m/s".
+*        See "Unit Representations:" below.
+*     in_lab
+*        A label describing the quantity associated with the input units.
+*        If the "in" string is the Units attribute of an Axis, then
+*        "in_lab" should be the Label of the same Axis. May be supplied
+*        NULL in which case "out_lab" is ignored.
+*     out_lab
+*        The address at which to return a pointer to a label describing the
+*        quantity associated with the output units. For instance, if the
+*        input and output units are "Hz" and "sqrt(Hz)", and the input
+*        label is "Frequency", then the returned output label will be
+*        "sqrt( Frequency )". The returned label is stored in dynamically
+*        allocated memory which should be freed (using astFree) when no longer
+*        needed.
+
+*  Returned Value:
+*     A pointer to a Mapping which can be used to transform values in the
+*     "in" system of units into the "out" system of units. The Mapping
+*     will have 1 input and 1 output.
+
+*  Unit Representations:
+*     The string supplied for "in" and "out" should represent a system of
+*     units following the recommendations of the FITS WCS paper I
+*     "Representation of World Coordinates in FITS" (Greisen & Calabretta).
+*     Various commonly used variants are also allowed.
+*
+*     To summarise, a string describing a system of units should be an
+*     algebraic expression which combines one or more named units. The
+*     following functions and operators may be used within these algebraic
+*     expressions:
+*
+*     - "*": multiplication. A period "." or space " " may also be used
+*       to represent multiplication (a period is only interpreted as a
+*       multiplication operator if it is not positioned between two digits,
+*       and a space is only interpreted as a multiplication operator if it
+*       occurs between two operands).
+*     - "/": division.
+*     - "**": exponentiation. The exponent (i.e. the operand following the
+*       exponentiation operator) must be a constant. The symbol "^" is also
+*       interpreted as an exponentiation operator. Exponentiation is also
+*       implied by an integer following a unit name without any separator
+*       (e.g. "cm2" is "cm^2").
+*     - log(): Common logarithm.
+*     - ln(): Natural logarithm.
+*     - sqrt(): Square root.
+*     - exp(): Exponential.
+*
+*     Function names are case insensitive. White space may be included
+*     within an expression (note that white space between two operands
+*     will be interpreted as a muiltiplication operator as described
+*     above). Parentheses may be used to indicate the order in which
+*     expressions are to be evaluated (normal mathematical precedence is
+*     used otherwise). The following symbols may be used to represent
+*     constants:
+*
+*     - "pi"
+*     - "e"
+*
+*     These symbols are also case in-sensitive.
+*
+*     The above operators and functions are used to combine together one
+*     or more "unit symbols". The following base unit symbols are recognised:
+*
+*     - "m":  metre.
+*     - "g":  gram.
+*     - "s":  second.
+*     - "rad":  radian.
+*     - "sr":  steradian.
+*     - "K":  Kelvin.
+*     - "mol":  mole.
+*     - "cd":  candela.
+*
+*     The following symbols for units derived fro the above basic units are
+*     recognised:
+*
+*     - "sec":  second (1 s)
+*     - "Hz":  Hertz  (1/s).
+*     - "N":  Newton  (kg m/s**2).
+*     - "J":  Joule  (N m).
+*     - "W":  Watt  (J/s).
+*     - "C":  Coulomb  (A s).
+*     - "V":  Volt  (J/C).
+*     - "Pa":  Pascal  (N/m**2).
+*     - "Ohm":  Ohm  (V/A).
+*     - "S":  Siemens  (A/V).
+*     - "F":  Farad  (C/V).
+*     - "Wb":  Weber  (V s).
+*     - "T":  Tesla  (Wb/m**2).
+*     - "H":  Henry  (Wb/A).
+*     - "lm":  lumen  (cd sr).
+*     - "lx":  lux  (lm/m**2).
+*     - "deg":  degree  (pi/180 rad).
+*     - "arcmin":  arc-minute  (1/60 deg).
+*     - "arcsec":  arc-second  (1/3600 deg).
+*     - "mas":  milli-arcsecond  (1/3600000 deg).
+*     - "min":  minute  (60 s).
+*     - "h":  hour  (3600 s).
+*     - "d":  day  (86400 s).
+*     - "yr":  year  (31557600 s).
+*     - "a":  year  (31557600 s).
+*     - "eV":  electron-Volt  (1.60217733E-19 J).
+*     - "erg":  erg  (1.0E-7 J).
+*     - "Ry":  Rydberg  (13.605692 eV).
+*     - "solMass":  solar mass  (1.9891E30 kg).
+*     - "u":  unified atomic mass unit  (1.6605387E-27 kg).
+*     - "solLum":  solar luminosity  (3.8268E26 W).
+*     - "Angstrom":  Angstrom  (1.0E-10 m).
+*     - "Ang":  Angstrom
+*     - "A":  Ampere
+*     - "micron":  micron (1.0E-6 m).
+*     - "solRad":  solar radius  (6.9599E8 m).
+*     - "AU":  astronomical unit  (1.49598E11 m).
+*     - "lyr":  light year  (9.460730E15 m).
+*     - "pc":  parsec  (3.0867E16 m).
+*     - "count":  count.
+*     - "ct":  count.
+*     - "adu":  analogue-to-digital converter unit.
+*     - "photon":  photon.
+*     - "ph":  photon.
+*     - "Jy":  Jansky  (1.0E-26 W /m**2 /Hz).
+*     - "Jan":  Jansky
+*     - "mag":  magnitude.
+*     - "G":  Gauss  (1.0E-4 T).
+*     - "pixel":  pixel.
+*     - "pix":  pixel.
+*     - "barn":  barn  (1.0E-28 m**2).
+*     - "D":  Debye  (1.0E-29/3 C.m).
+*
+*     In addition, any other unknown unit symbol may be used (but of course
+*     no mapping will be possible between unknown units).
+*
+*     Unit symbols may be preceded with a numerical constant (for
+*     instance "1000 m") or a standard multiplier symbol (for instance "km")
+*     to represent some multiple of the unit. The following standard
+*     multipliers are recognised:
+*
+*     - "d":   deci   (1.0E-1)
+*     - "c":   centi  (1.0E-2)
+*     - "m":   milli  (1.0E-3)
+*     - "u":   micro  (1.0E-6)
+*     - "n":   nano   (1.0E-9)
+*     - "p":   pico   (1.0E-12)
+*     - "f":   femto  (1.0E-15)
+*     - "a":   atto   (1.0E-18)
+*     - "z":   zepto  (1.0E-21)
+*     - "y":   yocto  (1.0E-24)
+*     - "da":  deca   (1.0E1)
+*     - "h":   hecto  (1.0E2)
+*     - "k":   kilo   (1.0E3)
+*     - "M":   mega   (1.0E6)
+*     - "G":   giga   (1.0E9)
+*     - "T":   tera   (1.0E12)
+*     - "P":   peta   (1.0E15)
+*     - "E":   exa    (1.0E18)
+*     - "Z":   zetta  (1.0E21)
+*     - "Y":   yotta  (1.0E24)
+
+*  Notes:
+*     -  NULL values are returned without error if the supplied units are
+*     incompatible (for instance, if the input and output units are "kg"
+*     and "m" ).
+*     -  NULL values are returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstMapping *result;
+   UnitNode **units;
+   UnitNode *in_tree;
+   UnitNode *intemp;
+   UnitNode *inv;
+   UnitNode *labtree;
+   UnitNode *newtest;
+   UnitNode *out_tree;
+   UnitNode *outtemp;
+   UnitNode *src;
+   UnitNode *testtree;
+   UnitNode *tmp;
+   UnitNode *totaltree;
+   UnitNode *totlabtree;
+   const char *c;
+   const char *exp;
+   int i;
+   int nc;
+   int nunits;
+   int ipass;
+
+/* Initialise */
+   result = NULL;
+   if( in_lab ) *out_lab = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* A quick check for a common simple case: if the two strings are
+   identical, return a UnitMap.*/
+   if( !strcmp( in, out ) ) {
+      if( in_lab ) *out_lab = astStore( NULL, in_lab, strlen( in_lab ) + 1 );
+      return (AstMapping *) astUnitMap( 1, "", status );
+   }
+
+/* More initialisation. */
+   in_tree = NULL;
+   out_tree = NULL;
+   units = NULL;
+
+/* Parse the input units string, producing a tree of UnitNodes which
+   represents the input units. A pointer to the UnitNode at the head of
+   the tree is returned if succesfull. Report a context message if this
+   fails. The returned tree contains branch nodes which correspond to
+   operators or functions, and leaf nodes which represent constant values
+   or named basic units (m, s, g, K, etc). Each branch node has one or more
+   "arguments" (i.e. child nodes) which are operated on or combined by
+   the branch node in some way to produce the nodes "value". This value
+   is then used as an argument for the node's parent node (if any). If
+   the string supplied by the user refers to any known derived units (e.g. "N",
+   Newton) then each such unit is represented in the returned tree by a
+   complete sub-tree in which the head node corresponds to the derived
+   unit (e.g. "N") and the leaf nodes correspond to the basic units needed
+   to define the derived unit ( for instance, "m", "s" and "g" - metres,
+   seconds and grammes), or numerical constants. Thus every leaf node in the
+   returned tree will be a basic unit (i.e. a unit which is not defined in
+   terms of other units), or a numerical constant. */
+   in_tree = CreateTree( in, 1, 1, status );
+   if( !astOK ) astError( AST__BADUN, "astUnitMapper: Error parsing input "
+                          "units string '%s'.", status, in );
+
+/* Do the same for the output units. */
+   if( astOK ) {
+      out_tree = CreateTree( out, 1, 1, status );
+      if( !astOK ) astError( AST__BADUN, "astUnitMapper: Error parsing output "
+                             "units string '%s'.", status, out );
+   }
+
+/* If a blank string is supplied for both input and output units, then
+   assume a UnitMap is the appropriate Mapping. */
+   if( !in_tree && !out_tree && astOK ) {
+      result = (AstMapping *) astUnitMap( 1, "", status );
+      if( in_lab ) *out_lab = astStore( NULL, in_lab, strlen( in_lab ) + 1 );
+
+/* Otherwise, if we have both input and output trees... */
+   } else if( in_tree && out_tree && astOK ) {
+
+/* Locate all the basic units used within either of these two trees. An
+   array is formed in which each element is a pointer to a UnitNode
+   contained within one of the trees created above. Each basic unit
+   referred to in either tree will have a single entry in this array
+   (even if the unit is referred to more than once). */
+      units = NULL;
+      nunits = 0;
+      LocateUnits( in_tree, &units, &nunits, status );
+      LocateUnits( out_tree, &units, &nunits, status );
+
+/* Due to the simple nature of the simplification process in SimplifyTree,
+   the following alogorithm sometimes fails to find a Mapping form input
+   to output units, but can find a Mapping from output to input units.
+   In this latter case, we can get the required Mapping from input to
+   output  simply by inverting the Mapign from output to input. So try
+   first with the units in the original order. If this fails to find a
+   Mapping, try again with the units swapped, and note that the final
+   Mapping should be inverted before being used. */
+      for( ipass = 0; ipass < 2; ipass++ ){
+         if( ipass == 1 ) {
+            tmp = in_tree;
+            in_tree = out_tree;
+            out_tree = tmp;
+         }
+
+/* We are going to create a new tree of UnitNodes in which the head node
+   corresponds to the requested output units, and which has a single
+   non-constant leaf node corresponding to the input units. Initialise a
+   pointer to this new tree to indicate that it has not yet been created. */
+         testtree = NULL;
+
+/* Loop round each basic unit used in the definition of either the input
+   or the output units (i.e. the elements of the array created above by
+   "LocateUnits"). The unit selected by this loop is referred to as the
+   "current" unit. On each pass through this loop, we create a tree which
+   is a candidate for the final required tree (the "test tree" pointed to
+   by the testtree pointer initialised above). In order for a mapping to
+   be possible between input and output units, the test tree created on
+   each pass through this loop must be equivalent to the test tree for the
+   previous pass (in other words, all the test trees must be equivalent).
+   We break out of the loop (and return a NULL Mapping) as soon as we find
+   a test tree which differs from the previous test tree. */
+         for( i = 0; i < nunits; i++ ) {
+
+/* Create copies of the trees describing the input and output units, in which
+   all units other than the current unit are set to a constant value of 1.
+   This is done by replacing OP_LDVAR nodes (i.e. nodes which "load" the
+   value of a named basic unit) by OP_LDCON nodes (i.e. nodes which load
+   a specified constant value) in the tree copy. */
+            intemp = FixUnits( in_tree, units[ i ], status );
+            outtemp = FixUnits( out_tree, units[ i ], status );
+
+/* Simplify these trees. An important side-effect of this simplification
+   is that trees are "standardised" which allows them to be compared for
+   equivalence. A single mathematical expression can often be represented
+   in many different ways (for instance "A/B" is equivalent to "(B**(-1))*A").
+   Standardisation is a process of forcing all equivalent representations
+   into a single "standard" form. Without standardisation, trees representing
+   the above two expressions would not be considered to be equivalent
+   since thy would contain different nodes and have different structures.
+   As a consequence of this standardisation, the "simplification" performed
+   by SimplifyTree can sometimes actually make the tree more complicated
+   (in terms of the number of nodes in the tree). */
+            SimplifyTree( &intemp, 1, status );
+            SimplifyTree( &outtemp, 1, status );
+
+/* If either of the simplified trees does not depend on the current unit,
+   then the node at the head of the simplified tree will have a constant
+   value (because all the units other than the current unit have been fixed
+   to a constant value of 1.0 above by FixUnits, leaving only the current
+   unit to vary in value). If both simplified trees are constants, then
+   neither tree depends on the current basic unit (i.e. references to the
+   current basic unit cancel out within each string expression - for
+   instance if converting from "m.s.Hz" to "km" and the current unit
+   is "s", then the "s.Hz" term will cause the "s" units to cancel out). In
+   this case ignore this basic unit and pass on to the next. */
+            if( outtemp->con != AST__BAD && intemp->con != AST__BAD ) {
+
+/* If just one simplified tree is constant, then the two units cannot
+   match since one depends on the current basic unit and the other does
+   not. Free any test tree from previous passes and break out of the loop. */
+            } else if( outtemp->con != AST__BAD || intemp->con != AST__BAD ) {
+               intemp = FreeTree( intemp, status );
+               outtemp = FreeTree( outtemp, status );
+               testtree = FreeTree( testtree, status );
+               break;
+
+/* If neither simplified tree is constant, both depend on the current
+   basic unit and so we can continue to see if their dependencies are
+   equivalent. */
+            } else {
+
+/* We are going to create a new tree which is the inverse of the above
+   simplified "intemp" tree. That is, the new tree will have a head node
+   corresponding to the current unit, and a single non-constant leaf node
+   corresponding to the input units. Create an OP_LDVAR node which can be
+   used as the leaf node for this inverted tree. If the input tree is
+   inverted successfully, this root node becomes part of the inverted tree,
+   and so does not need to be freed explicitly (it will be freed when the
+   inverted tree is freed). */
+               src = NewNode( NULL, OP_LDVAR, status );
+               if( astOK ) src->name = astStore( NULL, "input_units", 12 );
+
+/* Now produce the inverted input tree. If the tree cannot be inverted, a
+   null pointer is returned. Check for this. Otherwise a pointer to the
+   UnitNode at the head of the inverted tree is returned. */
+               inv = InvertTree( intemp, src, status );
+               if( inv ) {
+
+/* Concatenate this tree (which goes from "input units" to "current unit")
+   with the simplified output tree (which goes from "current unit" to
+   "output units"), to get a new tree which goes from input units to output
+   units. */
+                  totaltree = ConcatTree( inv, outtemp, status );
+
+/* Simplify this tree. */
+                  SimplifyTree( &totaltree, 1, status );
+
+/* Compare this simplified tree with the tree produced for the previous
+   unit (if any). If they differ, we cannot map between the supplied
+   units so annul the test tree and break out of the loop. If this is the
+   first unit to be tested, use the total tree as the test tree for the
+   next unit. */
+                  if( testtree ) {
+                     if( CmpTree( totaltree, testtree, 0, status ) ) testtree = FreeTree( testtree, status );
+                     totaltree = FreeTree( totaltree, status );
+                     if( !testtree ) break;
+                  } else {
+                     testtree = totaltree;
+                  }
+               }
+
+/* If the input tree was inverted, free the inverted tree. */
+               if( inv ) {
+                  inv = FreeTree( inv, status );
+
+/* If the input tree could not be inverted, we cannot convert between input
+   and output units. Free the node which was created to be the root of the
+   inverted tree (and which has consequently not been incorporated into the
+   inverted tree), free any testtree and break out of the loop. */
+               } else {
+                  src = FreeTree( src, status );
+                  testtree = FreeTree( testtree, status );
+                  break;
+               }
+            }
+
+/* Free the other trees. */
+            intemp = FreeTree( intemp, status );
+            outtemp = FreeTree( outtemp, status );
+
+         }
+
+/* If all the basic units used by either of the supplied system of units
+   produced the same test tree, leave the "swap in and out units" loop. */
+         if( testtree ) break;
+
+      }
+
+/* If the input and output units have been swapped, swap them back to
+   their original order, and invert the test tree (if there is one). */
+      if( ipass > 0 ) {
+         tmp = in_tree;
+         in_tree = out_tree;
+         out_tree = tmp;
+         if( testtree ) {
+            src = NewNode( NULL, OP_LDVAR, status );
+            if( astOK ) src->name = astStore( NULL, "input_units", 12 );
+            newtest = InvertTree( testtree, src, status );
+            FreeTree( testtree, status );
+            testtree = newtest;
+            if( !newtest ) src = FreeTree( src, status );
+         }
+      }
+
+/* If all the basic units used by either of the supplied system of units
+   produced the same test tree, create a Mapping which is equivalent to the
+   test tree and return it. */
+      if( testtree ) {
+         result = MakeMapping( testtree, status );
+
+/* We now go on to produce the output axis label from the supplied input
+   axis label. Get a tree of UnitNodes which describes the supplied label
+   associated with the input axis. The tree will have single OP_LDVAR node
+   corresponding to the basic label (i.e. the label without any single
+   argument functions or exponentiation operators applied). */
+         if( in_lab && astOK ) {
+
+/* Get a pointer to the first non-blank character, and store the number of
+   characters to examine (this excludes any trailing white space). */
+            exp = in_lab;
+            while( isspace( *exp ) ) exp++;
+            c = exp + strlen( exp ) - 1;
+            while( c >= exp && isspace( *c ) ) c--;
+            nc = c - exp + 1;
+
+/* Create the tree. */
+            labtree = MakeLabelTree( exp, nc, status );
+            if( astOK ) {
+
+/* Concatenate this tree (which goes from "basic label" to "input label")
+   with the test tree found above (which goes from "input units" to "output
+   units"), to get a tree which goes from basic label to output label. */
+               totlabtree = ConcatTree( labtree, testtree, status );
+
+/* Simplify this tree. */
+               SimplifyTree( &totlabtree, 1, status );
+
+/* Create the output label from this tree. */
+               *out_lab = (char *) MakeExp( totlabtree, 0, 1, status );
+
+/* Free the trees. */
+               totlabtree = FreeTree( totlabtree, status );
+               labtree = FreeTree( labtree, status );
+
+/* Report a context error if the input label could not be parsed. */
+            } else {
+               astError( AST__BADUN, "astUnitMapper: Error parsing axis "
+                         "label '%s'.", status, in_lab );
+            }
+         }
+
+/* Free the units tree. */
+         testtree = FreeTree( testtree, status );
+
+      }
+   }
+
+/* Free resources. */
+   in_tree = FreeTree( in_tree, status );
+   out_tree = FreeTree( out_tree, status );
+   units = astFree( units );
+
+/* If an error has occurred, annul the returned Mapping. */
+   if( !astOK ) {
+      result = astAnnul( result );
+      if( in_lab ) *out_lab = astFree( *out_lab );
+   }
+
+/* Return the result. */
+   return result;
+
+}
+
+const char *astUnitNormaliser_( const char *in, int *status ){
+/*
+*+
+*  Name:
+*     astUnitNormalizer
+
+*  Purpose:
+*     Normalise a unit string into FITS-WCS format.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     const char *astUnitNormaliser( const char *in )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     This function returns a standard FITS-WCS form of the supplied unit
+*     string.
+
+*  Parameters:
+*     in
+*        A string representation of the units, for instance "km/h".
+
+*  Returned Value:
+*     A pointer to a dynamically allocated string holding the normalized
+*     unit string. It should be freed using astFree when no longer needed.
+
+*  Notes:
+*     -  An error will be reported if the units string cannot be parsed.
+*     -  NULL is returned if this function is invoked with the
+*     global error status set or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   UnitNode *in_tree;
+   double dval;
+   const char *result;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Parse the input units string, producing a tree of UnitNodes which
+   represents the input units. A pointer to the UnitNode at the head of
+   the tree is returned if succesfull. Report a context message if this
+   fails. */
+   in_tree = CreateTree( in, 0, 1, status );
+   if( in_tree ) {
+
+/* Simplify the units expression, only doing genuine simplifications. */
+      SimplifyTree( &in_tree, 1, status );
+
+/* Invert literal constant unit multipliers. This is because a constant of
+   say 1000 for a unit of "m" means "multiply the value in metres by 1000",
+   but a unit string of "1000 m" means "value in units of 1000 m" (i.e.
+   *divide* the value in metres by 1000). */
+      InvertConstants( &in_tree, status );
+
+/* Convert the tree into string form. */
+      result = MakeExp( in_tree, 2, 1, status );
+
+/* If the result is a constant value, return a blank string. */
+      if( 1 == astSscanf( result, "%lg", &dval ) ) {
+         *((char *) result) = 0;
+      }
+
+/* Free the tree. */
+      in_tree = FreeTree( in_tree, status );
+
+   } else {
+      astError( AST__BADUN, "astUnitNormaliser: Error parsing input "
+                "units string '%s'.", status, in );
+   }
+
+/* Return the result */
+   return result;
+}
+
+static int Ustrcmp( const char *a, const char *b, int *status ){
+/*
+*  Name:
+*     Ustrcmp
+
+*  Purpose:
+*     A case blind version of strcmp.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     int Ustrcmp( const char *a, const char *b, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     Returns 0 if there are no differences between the two strings, and 1
+*     otherwise. Comparisons are case blind.
+
+*  Parameters:
+*     a
+*        Pointer to first string.
+*     b
+*        Pointer to second string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the strings match, otherwise one.
+
+*  Notes:
+*     -  This function does not consider the sign of the difference between
+*     the two strings, whereas "strcmp" does.
+*     -  This function attempts to execute even if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   const char *aa;         /* Pointer to next "a" character */
+   const char *bb;         /* Pointer to next "b" character */
+   int ret;                /* Returned value */
+
+/* Initialise the returned value to indicate that the strings match. */
+   ret = 0;
+
+/* Initialise pointers to the start of each string. */
+   aa = a;
+   bb = b;
+
+/* Loop round each character. */
+   while( 1 ){
+
+/* We leave the loop if either of the strings has been exhausted. */
+      if( !(*aa ) || !(*bb) ){
+
+/* If one of the strings has not been exhausted, indicate that the
+   strings are different. */
+         if( *aa || *bb ) ret = 1;
+
+/* Break out of the loop. */
+         break;
+
+/* If neither string has been exhausted, convert the next characters to
+   upper case and compare them, incrementing the pointers to the next
+   characters at the same time. If they are different, break out of the
+   loop. */
+      } else {
+
+         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
+            ret = 1;
+            break;
+         }
+
+      }
+
+   }
+
+/* Return the result. */
+   return ret;
+
+}
+
+static int Ustrncmp( const char *a, const char *b, size_t n, int *status ){
+/*
+*  Name:
+*     Ustrncmp
+
+*  Purpose:
+*     A case blind version of strncmp.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unit.h"
+*     int Ustrncmp( const char *a, const char *b, size_t n, int *status )
+
+*  Class Membership:
+*     Unit member function.
+
+*  Description:
+*     Returns 0 if there are no differences between the first "n"
+*     characters of the two strings, and 1 otherwise. Comparisons are
+*     case blind.
+
+*  Parameters:
+*     a
+*        Pointer to first string.
+*     b
+*        Pointer to second string.
+*     n
+*        The maximum number of characters to compare.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the strings match, otherwise one.
+
+*  Notes:
+*     -  This function does not consider the sign of the difference between
+*     the two strings, whereas "strncmp" does.
+*     -  This function attempts to execute even if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   const char *aa;         /* Pointer to next "a" character */
+   const char *bb;         /* Pointer to next "b" character */
+   int i;                  /* Character index */
+   int ret;                /* Returned value */
+
+
+/* Initialise the returned value to indicate that the strings match. */
+   ret = 0;
+
+/* Check pointer have been supplied. */
+   if( !a || !b ) return ret;
+
+/* Initialise pointers to the start of each string. */
+   aa = a;
+   bb = b;
+
+/* Compare up to "n" characters. */
+   for( i = 0; i < (int) n; i++ ){
+
+/* We leave the loop if either of the strings has been exhausted. */
+      if( !(*aa ) || !(*bb) ){
+
+/* If one of the strings has not been exhausted, indicate that the
+   strings are different. */
+         if( *aa || *bb ) ret = 1;
+
+/* Break out of the loop. */
+         break;
+
+/* If neither string has been exhausted, convert the next characters to
+   upper case and compare them, incrementing the pointers to the next
+   characters at the same time. If they are different, break out of the
+   loop. */
+      } else {
+
+         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
+            ret = 1;
+            break;
+         }
+
+      }
+
+   }
+
+/* Return the result. */
+   return ret;
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+/* The rest of this file contains functions which are of use for debugging
+   this module. They are usually commented out.
+
+static const char *DisplayTree( UnitNode *node, int ind ) {
+   int i;
+   char buf[200];
+   const char *result;
+   char *a;
+   const char *arg[ 2 ];
+   int rl;
+   int slen;
+   const opsym[ 100 ];
+
+   result = "";
+
+   for( i = 0; i < ind; i++ ) buf[ i ] = ' ';
+   buf[ ind ] = 0;
+
+   if( !node ) {
+      printf( "%s <null>\n", buf );
+   } else {
+
+      printf( "%s Code: '%s' (%d)\n", buf, OpName( node->opcode ), node->opcode );
+      printf( "%s Narg: %d\n", buf, node->narg );
+      printf( "%s Constant: %g\n", buf, node->con );
+      printf( "%s Name: %s\n", buf, node->name?node->name:"" );
+      printf( "%s Unit: %s\n", buf, node->unit?node->unit->sym:"" );
+      printf( "%s Mult: %s\n", buf, node->mult?node->mult->sym:"" );
+
+      OpSym( node, opsym );
+      slen = strlen( opsym );
+      rl = slen;
+
+      if( node->narg == 0 ) {
+         result = astMalloc( rl + 1 );
+         if( astOK ) strcpy( (char *) result, opsym );
+
+      } else if( node->narg == 1 ) {
+         rl += 2;
+         printf( "%s Arg 0:\n", buf );
+         arg[ 0 ] = DisplayTree( (node->arg)[ 0 ], ind + 2 );
+         rl += strlen( arg[ 0 ] );
+
+         result = astMalloc( rl + 1 );
+         if( astOK ) {
+            a = (char *) result;
+            strcpy( a, opsym );
+            a += slen;
+            *(a++) = '(';
+            strcpy( a, arg[0] );
+            a += strlen( arg[ 0 ] );
+            *(a++) = ')';
+         }
+
+      } else {
+         rl += 4;
+         for( i = 0; i < node->narg; i++ ) {
+            printf( "%s Arg %d:\n", buf, i );
+            arg[ i ] = DisplayTree( (node->arg)[ i ], ind + 2 );
+            rl += strlen( arg[ i ] );
+         }
+
+         result = astMalloc( rl + 1 );
+         if( astOK ) {
+            a = (char *) result;
+            *(a++) = '(';
+            strcpy( a, arg[0] );
+            a += strlen( arg[ 0 ] );
+            *(a++) = ')';
+            strcpy( a, opsym );
+            a += slen;
+            *(a++) = '(';
+            strcpy( a, arg[1] );
+            a += strlen( arg[ 1 ] );
+            *(a++) = ')';
+         }
+      }
+   }
+
+   if( !astOK ) {
+      astFree( (void *) result );
+      result = "";
+   }
+
+   return result;
+}
+
+static const char *OpName( Oper op ) {
+   const char *name;
+
+   if( op ==  OP_LDCON ) {
+      name = "LDCON";
+   } else if( op ==  OP_LDVAR ) {
+      name = "LDVAR";
+   } else if( op ==  OP_LOG ) {
+      name = "LOG";
+   } else if( op ==  OP_LN ) {
+      name = "LN";
+   } else if( op ==  OP_EXP ) {
+      name = "EXP";
+   } else if( op ==  OP_SQRT ) {
+      name = "SQRT";
+   } else if( op ==  OP_POW ) {
+      name = "POW";
+   } else if( op ==  OP_DIV ) {
+      name = "DIV";
+   } else if( op ==  OP_MULT ) {
+      name = "MULT";
+   } else if( op ==  OP_LDPI ) {
+      name = "LDPI";
+   } else if( op ==  OP_LDE ) {
+      name = "LDE";
+   } else if( op ==  OP_NULL ) {
+      name = "NULL";
+   } else {
+      name = "<unknown op code>";
+   }
+
+   return name;
+}
+
+static void OpSym( UnitNode *node, char *buff ) {
+   const char *sym = NULL;
+
+   if( node->con != AST__BAD ) {
+      sprintf( buff, "%g", node->con );
+
+   } else if( node->opcode ==  OP_LDVAR ) {
+      sym = node->name;
+
+   } else if( node->opcode ==  OP_LOG ) {
+      sym = "log";
+
+   } else if( node->opcode ==  OP_LN ) {
+      sym = "ln";
+
+   } else if( node->opcode ==  OP_EXP ) {
+      sym = "exp";
+
+   } else if( node->opcode ==  OP_SQRT ) {
+      sym = "sqrt";
+
+   } else if( node->opcode ==  OP_POW ) {
+      sym = "**";
+
+   } else if( node->opcode ==  OP_DIV ) {
+      sym = "/";
+
+   } else if( node->opcode ==  OP_MULT ) {
+      sym = "*";
+
+   } else if( node->opcode ==  OP_NULL ) {
+      sym = "NULL";
+
+   } else {
+      sym = "<unknown op code>";
+   }
+
+   if( sym ) strcpy( buff, sym );
+}
+
+static const char *TreeExp( UnitNode *node ) {
+   char buff[ 100 ];
+   char buff2[ 100 ];
+
+   if( node->narg == 0 ) {
+      OpSym( node, buff );
+
+   } else if( node->narg == 1 ) {
+      OpSym( node, buff2 );
+      sprintf( buff, "%s(%s)", buff2, TreeExp( node->arg[ 0 ] ) );
+
+   } else if( node->narg == 2 ) {
+      OpSym( node, buff2 );
+      sprintf( buff, "(%s)%s(%s)", TreeExp( node->arg[ 0 ] ), buff2,
+                                   TreeExp( node->arg[ 1 ] ) );
+   }
+
+   return astStore( NULL, buff, strlen( buff ) + 1 );
+}
+
+*/
+
+
+
+
diff --git a/ast/unit.h b/ast/unit.h
new file mode 100644
index 0000000..3637f13
--- /dev/null
+++ b/ast/unit.h
@@ -0,0 +1,83 @@
+#if !defined( UNIT_INCLUDED )  /* Include this file only once */
+#define UNIT_INCLUDED
+/*
+*+
+*  Name:
+*     unit.h
+
+*  Purpose:
+*     Define the interface to the Unit module.
+
+*  Description:
+*     This module defines functions which identify units and transform
+*     between them.
+*
+*     Note that this module is not a class implementation, although it
+*     resembles one.
+
+*  Functions Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     10-DEC-2002 (DSB):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+#include "mapping.h"             /* Coordinate mappings */
+
+/* C header files. */
+/* --------------- */
+
+/* Function prototypes. */
+/* ==================== */
+#if defined(astCLASS)            /* Protected  */
+AstMapping *astUnitMapper_( const char *, const char *, const char *,
+                            char **, int * );
+const char *astUnitLabel_( const char *, int * );
+double astUnitAnalyser_( const char *, double[9], int * );
+const char *astUnitNormaliser_( const char *, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These wrap up the functions defined by this module. */
+
+#if defined(astCLASS)            /* Protected */
+#define astUnitMapper(in,out,inlab,outlab) astINVOKE(O,astUnitMapper_(in,out,inlab,outlab,STATUS_PTR))
+#define astUnitAnalyser(in,powers) astUnitAnalyser_(in,powers,STATUS_PTR)
+#define astUnitNormaliser(in) astUnitNormaliser_(in,STATUS_PTR)
+#define astUnitLabel(sym) astINVOKE(O,astUnitLabel_(sym,STATUS_PTR))
+#endif
+#endif
+
+
+
diff --git a/ast/unitmap.c b/ast/unitmap.c
new file mode 100644
index 0000000..a371031
--- /dev/null
+++ b/ast/unitmap.c
@@ -0,0 +1,1425 @@
+/*
+*class++
+*  Name:
+*     UnitMap
+
+*  Purpose:
+*     Unit (null) Mapping.
+
+*  Constructor Function:
+c     astUnitMap
+f     AST_UNITMAP
+
+*  Description:
+*     A UnitMap is a unit (null) Mapping that has no effect on the
+*     coordinates supplied to it. They are simply copied. This can be
+*     useful if a Mapping is required (e.g. to pass to another
+c     function) but you do not want it to have any effect.
+f     routine) but you do not want it to have any effect.
+*     The Nin and Nout attributes of a UnitMap are always equal and
+*     are specified when it is created.
+
+*  Inheritance:
+*     The UnitMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The UnitMap class does not define any new attributes beyond
+*     those which are applicable to all Mappings.
+
+*  Functions:
+c     The UnitMap class does not define any new functions beyond those
+f     The UnitMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S Berry (JAC, Hawaii)
+
+*  History:
+*     7-FEB-1996 (RFWS):
+*        Original version.
+*     13-DEC-1996 (RFWS):
+*        Over-ride the astMapMerge method.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitUnitMapVtab
+*        method.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*     17-FEB-2012 (DSB):
+*        In Transform, do not copy the coordinate values if the input and
+*        output array are the same.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS UnitMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "object.h"              /* Base Object class */
+#include "memory.h"              /* AST memory management */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "unitmap.h"             /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(UnitMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(UnitMap,Class_Init)
+#define class_vtab astGLOBAL(UnitMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstUnitMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstUnitMap *astUnitMapId_( int, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetIsLinear( AstMapping *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+
+/* Member functions. */
+/* ================= */
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two UnitMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unitmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     UnitMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two UnitMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a UnitMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the UnitMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstUnitMap *that;
+   AstUnitMap *this;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two UnitMap structures. */
+   this = (AstUnitMap *) this_object;
+   that = (AstUnitMap *) that_object;
+
+/* Check the second object is a UnitMap. We know the first is a
+   UnitMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAUnitMap( that ) ) {
+
+/* Get the number of inputs check they are the same for both. */
+      result = ( astGetNin( this ) == astGetNin( that ) );
+
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetIsLinear( AstMapping *this_mapping, int *status ){
+/*
+*  Name:
+*     GetIsLinear
+
+*  Purpose:
+*     Return the value of the IsLinear attribute for a UnitMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GetIsLinear( AstMapping *this, int *status )
+
+*  Class Membership:
+*     UnitMap member function (over-rides the protected astGetIsLinear
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns the value of the IsLinear attribute for a
+*     Frame, which is always one.
+
+*  Parameters:
+*     this
+*        Pointer to the UnitMap.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   return 1;
+}
+
+void astInitUnitMapVtab_(  AstUnitMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitUnitMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a UnitMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unitmap.h"
+*     void astInitUnitMapVtab( AstUnitMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     UnitMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the UnitMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAUnitMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that
+   provide virtual methods for this class. */
+
+/* None. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+   mapping->MapSplit = MapSplit;
+   mapping->Rate = Rate;
+   mapping->GetIsLinear = GetIsLinear;
+
+/* Declare the class dump function. There is no copy constructor or
+   destructor. */
+   astSetDump( vtab, Dump, "UnitMap", "Unit (null) Mapping" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a UnitMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     UnitMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated UnitMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated UnitMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated UnitMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the UnitMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        UnitMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated UnitMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *new;              /* Pointer to replacement UnitMap */
+   const char *class;            /* Pointer to Mapping class string */
+   int i1;                       /* Index of first UnitMap to merge */
+   int i2;                       /* Index of last UnitMap to merge */
+   int i;                        /* Loop counter for Mappings */
+   int ngone;                    /* Number of UnitMaps eliminated */
+   int nin;                      /* Number of coordinates for UnitMap */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* If the Mapping sequence consists of a single UnitMap, simply check
+   if the asociated Invert flag is set, and clear it if necessary. */
+   if ( *nmap == 1 ) {
+      if ( ( *invert_list )[ 0 ] ) {
+         ( *invert_list )[ 0 ] = 0;
+
+/* Note if the Mapping sequence was modified. */
+         result = 0;
+      }
+
+/* In series. */
+/* ========== */
+/* If a UnitMap occurs in series with any other Mapping, it can simply
+   be eliminated, so annul its Mapping pointer. */
+   } else if ( *nmap > 1 ) {
+      if ( series ) {
+         ( *map_list )[ where ] = astAnnul( ( *map_list )[ where ] );
+
+/* Loop to move any following pointers and their Invert flags down in
+   the array to fill the gap, and clear the vacated elements at the
+   end of the arrays. */
+         for ( i = where + 1; i < *nmap; i++ ) {
+            ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+            ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+         }
+         ( *map_list )[ *nmap - 1 ] = NULL;
+         ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+         ( *nmap )--;
+         result = where;
+
+/* In parallel. */
+/* ============ */
+/* If a UnitMap occurs in parallel with other Mappings, it can be
+   merged with any UnitMaps on either side of itself. */
+      } else {
+
+/* Initialise indices to identify any adjacent UnitMaps and the number
+   of coordinates to be handled by the replacement. */
+         i1 = i2 = where;
+         nin = astGetNin( ( *map_list )[ where ] );
+
+/* Loop to inspect earlier Mappings, obtaining the Mapping Class name
+   and checking it is "UnitMap". Quit looping as soon as any other
+   class of Mapping is found. */
+         while ( i1 - 1 >= 0 ) {
+            class = astGetClass( ( *map_list )[ i1 - 1 ] );
+            if ( !astOK || strcmp( class, "UnitMap" ) ) break;
+
+/* Update the index of the earliest UnitMap that is to be merged and
+   increment the total number of coordinates involved. */
+            i1--;
+            nin += astGetNin( ( *map_list )[ i1 ] );
+         }
+
+/* Repeat the above process to inspect any later Mappings in the
+   sequence. */
+         while ( i2 + 1 < *nmap ) {
+            class = astGetClass( ( *map_list )[ i2 + 1 ] );
+            if ( !astOK || strcmp( class, "UnitMap" ) ) break;
+            i2++;
+            nin += astGetNin( ( *map_list )[ i2 ] );
+         }
+
+/* Calculate the net number of Mappings that can be removed from the
+   sequence and check if this is zero, meaning that there were no
+   adjacent UnitMaps to merge with. */
+         if ( astOK ) {
+            ngone = i2 - i1;
+            if ( !ngone ) {
+
+/* If so, simply check if the nominated UnitMap's Invert flag is set,
+   and clear it if necessary. */
+               if ( ( *invert_list )[ where ] ) {
+                  ( *invert_list )[ where ] = 0;
+
+/* Note if the Mapping sequence was modified. */
+                  result = where;
+               }
+
+/* If UnitMaps can be merged, create the replacement UnitMap. */
+            } else {
+               new = (AstMapping *) astUnitMap( nin, "", status );
+               if ( astOK ) {
+
+/* Annul the pointers to all the UnitMaps that are being replaced. */
+                  for ( i = i1; i <= i2; i++ ) {
+                     ( *map_list )[ i ] = astAnnul( ( *map_list )[ i ] );
+                  }
+
+/* Insert the new pointer and the associated Invert flag. */
+                  ( *map_list )[ i1 ] = new;
+                  ( *invert_list )[ i1 ] = 0;
+
+/* Loop to close the resulting gap by moving subsequent elements down
+   in the arrays. */
+                  for ( i = i2 + 1; i < *nmap; i++ ) {
+                     ( *map_list )[ i - ngone ] = ( *map_list )[ i ];
+                     ( *invert_list )[ i - ngone ] = ( *invert_list )[ i ];
+                  }
+
+/* Clear the vacated elements at the end. */
+                  for ( i = *nmap - ngone; i < *nmap; i++ ) {
+                     ( *map_list )[ i ] = NULL;
+                     ( *invert_list )[ i ] = 0;
+                  }
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+                  ( *nmap ) -= ngone;
+                  result = i1;
+               }
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     UnitMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unitmap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     UnitMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing UnitMap. This is only possible if the specified inputs
+*     correspond to some subset of the UnitMap outputs. That is, there
+*     must exist a subset of the UnitMap outputs for which each output
+*     depends only on the selected UnitMap inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied UnitMap, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the UnitMap to be split (the UnitMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied UnitMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied UnitMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied UnitMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstUnitMap *this;          /* Pointer to UnitMap structure */
+   int *result;               /* Pointer to returned array */
+   int i;                     /* Loop count */
+   int iin;                   /* Mapping input index */
+   int mnin;                  /* No. of Mapping inputs */
+   int ok;                    /* Are input indices OK? */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the UnitMap structure. */
+   this = (AstUnitMap *) this_map;
+
+/* Allocate memory for the returned array and create a UnitMap with the
+   required number of axes. */
+   result = astMalloc( sizeof( int )*(size_t) nin );
+   *map = (AstMapping *) astUnitMap( nin, "", status );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Store the required output axis indices. At the same time check that each
+   axis is valid. */
+      mnin = astGetNin( this );
+      ok = 1;
+      for( i = 0; i < nin; i++ ) {
+         iin = in[ i ];
+         if( iin >= 0 && iin < mnin ) {
+            result[ i ] = iin;
+         } else {
+            ok = 0;
+            break;
+         }
+      }
+
+/* If the "in" array contained any invalid values, free the returned
+   resources. */
+      if( !ok ) {
+         result = astFree( result );
+         *map = astAnnul( *map );
+      }
+   }
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unitmap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     UnitMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*/
+
+   return ( ax1 == ax2 ) ? 1.0 : 0.0;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a UnitMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unitmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     UnitMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a UnitMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to perform the identity
+*     transformation (i.e. simply copies the coordinate values).
+
+*  Parameters:
+*     this
+*        Pointer to the UnitMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation. In this case, both transformations are equivalent.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the UnitMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstUnitMap *map;              /* Pointer to UnitMap to be applied */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   int coord;                    /* Loop counter for coordinates */
+   int ncoord_in;                /* Number of coordinates per input point */
+   int npoint;                   /* Number of points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the UnitMap. */
+   map = (AstUnitMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   coordinate copy needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   ncoord_in = astGetNcoord( in );
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* We need not determine whether to apply the forward or inverse
+   transformation, as they are both the same. */
+
+/* Copy the coordinate values. */
+/* --------------------------- */
+   if ( astOK ) {
+
+/* Loop to copy the values for each coordinate. Use a memory copy for speed.
+   Do not do the copy if the input and output arrays are the same. */
+      for ( coord = 0; coord < ncoord_in; coord++ ) {
+         if( ptr_out[ coord ] != ptr_in[ coord ] ) {
+            (void) memcpy( (void *) ptr_out[ coord ],
+                           (const void *) ptr_in[ coord ],
+                           sizeof( double ) * (size_t) npoint );
+         }
+      }
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Copy constructor. */
+/* ----------------- */
+/* No copy constructor is needed, as a byte-by-byte copy suffices. */
+
+/* Destructor. */
+/* ----------- */
+/* No destructor is needed as no memory, etc. needs freeing. */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for UnitMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the UnitMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the UnitMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstUnitMap *this;             /* Pointer to the UnitMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the UnitMap structure. */
+   this = (AstUnitMap *) this_object;
+
+/* Write out values representing the instance variables for the
+   UnitMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* There are no values to write, so return without further action. */
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAUnitMap and astCheckUnitMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(UnitMap,Mapping)
+astMAKE_CHECK(UnitMap)
+
+AstUnitMap *astUnitMap_( int ncoord, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astUnitMap
+f     AST_UNITMAP
+
+*  Purpose:
+*     Create a UnitMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "unitmap.h"
+c     AstUnitMap *astUnitMap( int ncoord, const char *options, ... )
+f     RESULT = AST_UNITMAP( NCOORD, OPTIONS, STATUS )
+
+*  Class Membership:
+*     UnitMap constructor.
+
+*  Description:
+*     This function creates a new UnitMap and optionally initialises
+*     its attributes.
+*
+*     A UnitMap is a unit (null) Mapping that has no effect on the
+*     coordinates supplied to it. They are simply copied. This can be
+*     useful if a Mapping is required (e.g. to pass to another
+c     function) but you do not want it to have any effect.
+f     routine) but you do not want it to have any effect.
+
+*  Parameters:
+c     ncoord
+f     NCOORD = INTEGER (Given)
+*        The number of input and output coordinates (these numbers are
+*        necessarily the same).
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new UnitMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new UnitMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astUnitMap()
+f     AST_UNITMAP = INTEGER
+*        A pointer to the new UnitMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstUnitMap *new;              /* Pointer to new UnitMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the UnitMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitUnitMap( NULL, sizeof( AstUnitMap ), !class_init, &class_vtab,
+                         "UnitMap", ncoord );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   UnitMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new UnitMap. */
+   return new;
+}
+
+AstUnitMap *astUnitMapId_( int ncoord, const char *options, ... ) {
+/*
+*  Name:
+*     astUnitMapId_
+
+*  Purpose:
+*     Create a UnitMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unitmap.h"
+*     AstUnitMap *astUnitMapId_( int ncoord, const char *options, ... )
+
+*  Class Membership:
+*     UnitMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astUnitMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astUnitMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astUnitMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astUnitMap_.
+
+*  Returned Value:
+*     The ID value associated with the new UnitMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstUnitMap *new;              /* Pointer to new UnitMap */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the UnitMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitUnitMap( NULL, sizeof( AstUnitMap ), !class_init, &class_vtab,
+                         "UnitMap", ncoord );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   UnitMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new UnitMap. */
+   return astMakeId( new );
+}
+
+AstUnitMap *astInitUnitMap_( void *mem, size_t size, int init,
+                             AstUnitMapVtab *vtab, const char *name,
+                             int ncoord, int *status ) {
+/*
+*+
+*  Name:
+*     astInitUnitMap
+
+*  Purpose:
+*     Initialise a UnitMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unitmap.h"
+*     AstUnitMap *astInitUnitMap( void *mem, size_t size, int init,
+*                                 AstUnitMapVtab *vtab, const char *name,
+*                                 int ncoord )
+
+*  Class Membership:
+*     UnitMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new UnitMap object. It allocates memory (if necessary) to accommodate
+*     the UnitMap plus any additional data associated with the derived class.
+*     It then initialises a UnitMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a UnitMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the UnitMap is to be initialised.
+*        This must be of sufficient size to accommodate the UnitMap data
+*        (sizeof(UnitMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the UnitMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the UnitMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the UnitMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new UnitMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the Object
+*        astClass function).
+*     ncoord
+*        The number of coordinate values per point.
+
+*  Returned Value:
+*     A pointer to the new UnitMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstUnitMap *new;              /* Pointer to new UnitMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitUnitMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the UnitMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstUnitMap *) astInitMapping( mem, size, 0,
+                                        (AstMappingVtab *) vtab, name,
+                                        ncoord, ncoord, 1, 1 );
+
+      if ( astOK ) {
+
+/* Initialise the UnitMap data. */
+/* ---------------------------- */
+/* There is nothing else to store. */
+
+/* If an error occurred, clean up by deleting the new object (if any other
+   resources had been allocated, we would free these first). */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstUnitMap *astLoadUnitMap_( void *mem, size_t size,
+                             AstUnitMapVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadUnitMap
+
+*  Purpose:
+*     Load a UnitMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unitmap.h"
+*     AstUnitMap *astLoadUnitMap( void *mem, size_t size,
+*                                 AstUnitMapVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     UnitMap loader.
+
+*  Description:
+*     This function is provided to load a new UnitMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     UnitMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a UnitMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the UnitMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        UnitMap data (sizeof(UnitMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the UnitMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the UnitMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstUnitMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new UnitMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the UnitMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "UnitMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new UnitMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstUnitMap *new;              /* Pointer to the new UnitMap */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this UnitMap. In this case the
+   UnitMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstUnitMap );
+      vtab = &class_vtab;
+      name = "UnitMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitUnitMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built UnitMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "UnitMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* There are no values to read. */
+/* ---------------------------- */
+
+/* If an error occurred, clean up by deleting the new UnitMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new UnitMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+/* There are none of these. */
+
+
+
+
diff --git a/ast/unitmap.h b/ast/unitmap.h
new file mode 100644
index 0000000..c808ed1
--- /dev/null
+++ b/ast/unitmap.h
@@ -0,0 +1,288 @@
+#if !defined( UNITMAP_INCLUDED ) /* Include this file only once */
+#define UNITMAP_INCLUDED
+/*
+*+
+*  Name:
+*     unitmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the UnitMap class.
+
+*  Invocation:
+*     #include "unitmap.h"
+
+*  Description:
+*     This include file defines the interface to the UnitMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The UnitMap class implements Mappings that perform an identity
+*     (unit) coordinate transformation, simply by copying each
+*     coordinate value from input to output (and similarly for the
+*     inverse transformation). UnitMaps are therefore of little use
+*     for converting coordinates, but they serve a useful role as
+*     "null" Mappings in cases where a Mapping is needed (e.g.  to
+*     pass to a function), but no coordinate transformation is wanted.
+
+*  Inheritance:
+*     The UnitMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astMapMerge
+*           Simplify a sequence of Mappings containing a UnitMap.
+*        astTransform
+*           Transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAUnitMap
+*           Test class membership.
+*        astUnitMap
+*           Create a UnitMap.
+*
+*     Protected:
+*        astCheckUnitMap
+*           Validate class membership.
+*        astInitUnitMap
+*           Initialise a UnitMap.
+*        astInitUnitMapVtab
+*           Initialise the virtual function table for the UnitMap class.
+*        astLoadUnitMap
+*           Load a UnitMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstUnitMap
+*           UnitMap object type.
+*
+*     Protected:
+*        AstUnitMapVtab
+*           UnitMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     7-FEB-1996 (RFWS):
+*        Original version.
+*     13-DEC-1996 (RFWS):
+*        Over-ride the astMapMerge method.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitUnitMapVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* UnitMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each
+   object in the class (e.g. its instance variables). */
+typedef struct AstUnitMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+/* None. */
+} AstUnitMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstUnitMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+/* None. */
+} AstUnitMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstUnitMapGlobals {
+   AstUnitMapVtab Class_Vtab;
+   int Class_Init;
+} AstUnitMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitUnitMapGlobals_( AstUnitMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(UnitMap)          /* Check class membership */
+astPROTO_ISA(UnitMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstUnitMap *astUnitMap_( int, const char *, int *, ...);
+#else
+AstUnitMap *astUnitMapId_( int, const char *, ... )__attribute__((format(printf,2,3)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstUnitMap *astInitUnitMap_( void *, size_t, int, AstUnitMapVtab *,
+                             const char *, int, int * );
+
+/* Vtab initialiser. */
+void astInitUnitMapVtab_( AstUnitMapVtab *, const char *, int * );
+
+/* Loader. */
+AstUnitMap *astLoadUnitMap_( void *, size_t, AstUnitMapVtab *,
+                             const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+/* None. */
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckUnitMap(this) astINVOKE_CHECK(UnitMap,this,0)
+#define astVerifyUnitMap(this) astINVOKE_CHECK(UnitMap,this,1)
+
+/* Test class membership. */
+#define astIsAUnitMap(this) astINVOKE_ISA(UnitMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astUnitMap astINVOKE(F,astUnitMap_)
+#else
+#define astUnitMap astINVOKE(F,astUnitMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitUnitMap(mem,size,init,vtab,name,ncoord) \
+astINVOKE(O,astInitUnitMap_(mem,size,init,vtab,name,ncoord,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitUnitMapVtab(vtab,name) astINVOKE(V,astInitUnitMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadUnitMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadUnitMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckUnitMap to validate UnitMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+/* None. */
+#endif
+
+
+
+
+
diff --git a/ast/unitnormmap.c b/ast/unitnormmap.c
new file mode 100644
index 0000000..d7abd4c
--- /dev/null
+++ b/ast/unitnormmap.c
@@ -0,0 +1,1637 @@
+/*
+*class++
+*  Name:
+*     UnitNormMap
+
+*  Purpose:
+*     Convert a vector to a unit vector and its norm, relative to a specified centre.
+
+*  Constructor Function:
+c     astUnitNormMap
+f     AST_UNITNORMMAP
+
+*  Description:
+*     The forward transformation of a UnitNormMap subtracts the specified centre
+*     and then transforms the resulting vector to a unit vector and the vector norm.
+*     The output contains one more coordinate than the input: the initial Nin outputs
+*     are in the same order as the input; the final output is the norm.
+*
+*     The inverse transformation of a UnitNormMap multiplies each component
+*     of the provided vector by the provided norm and adds the specified centre.
+*     The output contains one fewer coordinate than the input: the initial Nin inputs
+*     are in the same order as the output; the final input is the norm.
+*
+*     UnitNormMap enables radially symmetric transformations, as follows:
+*     - apply a UnitNormMap to produce a unit vector and norm (radius)
+*     - apply a one-dimensional mapping to the norm (radius), while passing the unit vector unchanged
+*     - apply the same UnitNormMap in the inverse direction to produce the result
+
+*  Inheritance:
+*     The UnitNormMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The UnitNormMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     The UnitNormMap class does not define any new functions beyond those
+f     The UnitNormMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 2016 University of Washington
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RO: Russell Owen (LSST)
+
+*  History:
+*     20-APR-2016 (RO):
+*        Original version.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS UnitNormMap
+
+/* Macros which return the maximum and minimum of two values. */
+#define MAX(aa,bb) ((aa)>(bb)?(aa):(bb))
+#define MIN(aa,bb) ((aa)<(bb)?(aa):(bb))
+
+/* Macro to check for equality of floating point values. We cannot
+   compare bad values directory because of the danger of floating point
+   exceptions, so bad values are dealt with explicitly. */
+#define EQUAL(aa,bb) (((aa)==AST__BAD)?(((bb)==AST__BAD)?1:0):(((bb)==AST__BAD)?0:(fabs((aa)-(bb))<=1.0E9*MAX((fabs(aa)+fabs(bb))*DBL_EPSILON,DBL_MIN))))
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory management facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "unitmap.h"             /* Unit mappings */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "shiftmap.h"            /* ShiftMap */
+#include "winmap.h"            /* ShiftMap */
+#include "channel.h"             /* I/O channels */
+#include "unitnormmap.h"         /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(UnitNormMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(UnitNormMap,Class_Init)
+#define class_vtab astGLOBAL(UnitNormMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstUnitNormMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstUnitNormMap *astUnitNormMapId_( int, const double [], const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static int GetMappingType( AstMapping *, int * );
+static AstMapping * MakeMergedMap( AstMapping *, AstMapping *, int * );
+static int GetObjSize( AstObject *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static int GetIsLinear( AstMapping *, int * );
+
+/* Member functions. */
+/* ================= */
+static int GetMappingType( AstMapping *map, int *status ) {
+/*
+*
+*  Name:
+*     GetMappingType
+
+*  Purpose:
+*     Return a code describing the mapping type, to aid simplification.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unitnormmap.h"
+*     int MakeMergedMap( AstMapping *map1, AstMapping *map2, int inv1, int inv2, int *status )
+
+*  Class Membership:
+*     UnitNormMap internal utility function.
+
+*  Description:
+*     This function returns an integer code describing the type of a mapping,
+*     to help with merging two mappings. The codes are:
+*     3 = WinMap
+*     2 = ShiftMap
+*     1 = UnitNormMap
+*     0 = other
+
+*  Parameters:
+*     map
+*        A pointer to the mapping
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the merged mapping, or NULL if the Mappings cannot be merged
+
+*/
+   const char *class = astGetClass( map );
+   int type = 0;
+   if( strcmp( class, "UnitNormMap" ) == 0 ) {
+      type = 1;
+   } else if( strcmp( class, "ShiftMap" ) == 0 ) {
+      type = 2;
+   } else if( strcmp( class, "WinMap" ) == 0){
+      type = 3;
+   }
+   return type;
+}
+
+static AstMapping * MakeMergedMap( AstMapping *map1, AstMapping *map2, int *status ){
+/*
+*
+*  Name:
+*     MakeMergedMap
+
+*  Purpose:
+*     Make a single Mapping that is equivalent to the merging of two other Mappings,
+*     one of which must be a UnitNormMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unitnormmap.h"
+*     int MakeMergedMap( AstMapping *map1, AstMapping *map2, int inv1, int inv2, int *status )
+
+*  Class Membership:
+*     UnitNormMap internal utility function.
+
+*  Description:
+*     This function returns a single Mapping that is the equivalent of the two supplied Mappings,
+*     if they can be merged. One of the pair must be a UnitNormMap.
+*
+*     Supported merges, which must be in series:
+*     ShiftMap               + UnitNormMap(forward)            = UnitNormMap(forward)
+*     WinMap with unit scale + UnitNormMap(forward)            = UnitNormMap(forward)
+*     UnitNormMap(inverted)  + ShiftMap                        = UnitNormMap(inverted)
+*     UnitNormMap(inverted)  + WinMap with unit scale          = UnitNormMap(inverted)
+*     UnitNormMap(forward)   + identical UnitNormMap(inverted) = UnitMap
+*     UnitNormMap(inverted)  + identical UnitNormMap(forward)  = UnitMap
+*     UnitNormMap(forward)   + UnitNormMap(inverted)           = ShiftMap
+*
+*
+*     Notes:
+*     - A UnitMap before or after a UnitNormMap = the UnitNormMap, but UnitMap takes care of that merge.
+*     - The code that checks for ShiftMap + UnitNormMap probably never runs, because ShiftMap is
+*       converted to WinMap during simplification before it gets here.
+
+*  Parameters:
+*     map1
+*        A pointer to the first mapping.
+*     map2
+*        A pointer to the second mapping.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the merged mapping, or NULL if the Mappings cannot be merged
+
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return NULL;
+
+/* Initialise returned mapping */
+   AstMapping *retmap = NULL;
+
+/* Set type to 1=UnitNormMap, 2=ShiftMap, 0=other for both maps */
+   int type1 = GetMappingType( map1, status );
+   int type2 = GetMappingType( map2, status );
+   if( !astOK ) return NULL;
+
+   if( type1 == 0 || type2 == 0 ) return NULL;  /* can only merge with UnitNormMap or ShiftMap */
+
+   if( !(type1 == 1 || type2 == 1) ) return NULL;  /* one must be a UnitNormMap */
+
+   if( type1 == 2 ) {
+      if( astGetInvert( map2 )) return NULL;  /* ShiftMap + UnitNormMap(inverted) not supported */
+
+/* ShiftMap + UnitNormMap(forward) = UnitNormMap(forward) */
+      AstShiftMap *shiftmap = (AstShiftMap *) map1;
+      AstUnitNormMap *unm = (AstUnitNormMap *) map2;
+      int nin = astGetNin( shiftmap );
+      double shiftmult = astGetInvert( shiftmap ) ? -1 : 1;
+      double *newcentre = astMalloc( sizeof(double)*(size_t)nin );
+      int coord = 0;
+      for( coord = 0; coord < nin; coord++ ){
+         newcentre[coord] = unm->centre[coord] - shiftmult*shiftmap->shift[coord];
+      }
+      retmap = (AstMapping *) astUnitNormMap( nin, newcentre, "", status );
+      if( retmap == NULL ){
+         astFree( (void *) newcentre );
+      }
+   } else if( type1 == 3 ) {
+      if( astGetInvert( map2 )) return NULL;  /* WinMap + UnitNormMap(inverted) not supported */
+
+/* WinMap with unit scale + UnitNormMap(forward) = UnitNormMap(forward);
+   abort if WinMap does not have unit scale */
+      AstWinMap *winmap = (AstWinMap *) map1;
+      AstUnitNormMap *unm = (AstUnitNormMap *) map2;
+      int nin = astGetNin( winmap );
+      double shiftmult = astGetInvert( winmap ) ? -1 : 1;
+      double *newcentre = astMalloc( sizeof(double)*(size_t)nin );
+      int coord = 0;
+      for( coord = 0; coord < nin; coord++ ){
+         if( !EQUAL( winmap->b[coord], 1.0 )) return NULL;
+         newcentre[coord] = unm->centre[coord] - shiftmult*winmap->a[coord];
+      }
+      retmap = (AstMapping *) astUnitNormMap( nin, newcentre, "", status );
+      if( retmap == NULL ){
+         astFree( (void *) newcentre );
+      }
+   } else if( type2 == 2 ) {
+      if( !astGetInvert( map1 )) return NULL;  /* UnitNormMap(forward) + ShiftMap not supported */
+
+/* UnitNormMap(inverted) + ShiftMap = UnitNormMap(inverted) */
+      AstShiftMap *shiftmap = (AstShiftMap *) map2;
+      AstUnitNormMap *unm = (AstUnitNormMap *) map1;
+      int nin = astGetNin( shiftmap );
+      double shiftmult = astGetInvert( shiftmap ) ? -1 : 1;
+      double *newcentre = astMalloc( sizeof(double)*(size_t)nin );
+      int coord = 0;
+      for( coord = 0; coord < nin; coord++ ){
+         newcentre[coord] = unm->centre[coord] + shiftmult*shiftmap->shift[coord];
+      }
+      retmap = (AstMapping *) astUnitNormMap( nin, newcentre, "Invert=1", status );
+      if( retmap == NULL ){
+         astFree( (void *) newcentre );
+      }
+   } else if( type2 == 3 ) {
+      if( !astGetInvert( map1 )) return NULL;  /* UnitNormMap(forward) + WinMap not supported */
+
+/* UnitNormMap(inverted) + WinMap = UnitNormMap(inverted);
+   abort if WinMap does not have unit scale */
+      AstWinMap *winmap = (AstWinMap *) map2;
+      AstUnitNormMap *unm = (AstUnitNormMap *) map1;
+      int nin = astGetNin( winmap );
+      double shiftmult = astGetInvert( winmap ) ? -1 : 1;
+      double *newcentre = astMalloc( sizeof(double)*(size_t)nin );
+      int coord = 0;
+      for( coord = 0; coord < nin; coord++ ){
+         if( !EQUAL( winmap->b[coord], 1.0 )) return NULL;
+         newcentre[coord] = unm->centre[coord] + shiftmult*winmap->a[coord];
+      }
+      retmap = (AstMapping *) astUnitNormMap( nin, newcentre, "Invert=1", status );
+      if( retmap == NULL ){
+         astFree( (void *) newcentre );
+      }
+   } else {
+      if( !astGetInvert( map1 ) == !astGetInvert( map2 )) return NULL;  /* UNMs must have opposite dir. */
+
+/* Two UnitNormMaps in opposite directions */
+      AstUnitNormMap *unm1 = (AstUnitNormMap *) map1;
+      AstUnitNormMap *unm2 = (AstUnitNormMap *) map2;
+
+      int ctrlen = MIN( astGetNin( map1 ), astGetNin( map2 ) );
+      int centres_equal = 1;
+      int i = 0;
+      for( i = 0; i < ctrlen; i++ ){
+         if( !EQUAL( unm1->centre[i], unm2->centre[i] )){
+            centres_equal = 0;
+            break;
+         }
+      }
+      if( centres_equal ) {
+
+/* Two UnitNormMap in opposite directions with identical centres = UnitMap */
+         retmap = (AstMapping *) astUnitMap( astGetNin( map1 ), "", status );
+      } else {
+         if( astGetInvert( map2 )) {
+
+/* UnitNormMap(forward) + UnitNormMap(inverted) = ShiftMap */
+            int nin = astGetNin( map1 );
+            double *shift = astMalloc( sizeof(double)*(size_t)nin );
+            int coord = 0;
+            for( coord = 0; coord < nin; coord++ ){
+               shift[coord] = unm2->centre[coord] - unm1->centre[coord];
+            }
+            retmap = (AstMapping *) astShiftMap( nin, shift, "", status );
+            if( retmap == NULL ){
+               astFree( (void *) shift );
+            }
+         }
+      }
+   }
+
+   return astOK ? retmap : NULL;
+}
+
+static int GetIsLinear( AstMapping *this_mapping, int *status ){
+/*
+*  Name:
+*     GetIsLinear
+
+*  Purpose:
+*     Return the value of the IsLinear attribute for a UnitNormMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GetIsLinear( AstMapping *this, int *status )
+
+*  Class Membership:
+*     UnitNormMap member function (over-rides the protected astGetIsLinear
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns the value of the IsLinear attribute for a
+*     Frame, which is always one.
+
+*  Parameters:
+*     this
+*        Pointer to the UnitNormMap.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   return 0;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unitnormmap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     UnitNormMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied UnitNormMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the UnitNormMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Initialise. */
+   int result = 0;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* Obtain a pointers to the UnitNormMap structure. */
+   AstUnitNormMap *this = (AstUnitNormMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astTSizeOf( this->centre );
+
+/* If an error occurred, clear the result value. */
+   if( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+void astInitUnitNormMapVtab_(  AstUnitNormMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitUnitNormMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a UnitNormMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unitnormmap.h"
+*     void astInitUnitNormMapVtab( AstUnitNormMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     UnitNormMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the UnitNormMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Check the local error status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAUnitNormMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   AstObjectVtab *object = (AstObjectVtab *) vtab;
+   AstMappingVtab *mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   mapping->MapMerge = MapMerge;
+   mapping->GetIsLinear = GetIsLinear;
+
+/* Declare the class dump, copy and delete functions.*/
+   astSetDump( vtab, Dump, "UnitNormMap", "Compute unit vector and norm relative to a centre" );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a UnitNormMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     UnitNormMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated UnitNormMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated UnitNormMap with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated UnitNormMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the UnitNormMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        UnitNormMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated UnitNormMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Initialise. */
+   int result = -1;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+   if( series ) {
+
+/* In series */
+/* Try to merge UnitNormMap with either of its neighbours. */
+      AstMapping *map1 = NULL;    /* The first Mapping to merge */
+      AstMapping *map2 = NULL;    /* The second Mapping to merge */
+      AstMapping *newmap = NULL;  /* The merged Mapping */
+      int i1 = MAX( where - 1, 0 );
+      int i2 = i1 + 1;
+      for( ; i2 < *nmap; i1++, i2++ ){
+         map1 = ( *map_list )[ i1 ];
+         map2 = ( *map_list )[ i2 ];
+         int wasinverted1 = astGetInvert( map1 );
+         int wasinverted2 = astGetInvert( map2 );
+         astSetInvert( map1, ( *invert_list )[ i1 ] );
+         astSetInvert( map2, ( *invert_list )[ i2 ] );
+         newmap = MakeMergedMap( map1, map2, status );
+         astSetInvert( map1, wasinverted1 );
+         astSetInvert( map2, wasinverted2 );
+         if( newmap ) break;
+      }
+
+      if( !newmap ) return -1;
+      if( !astOK ) {
+         astAnnul( newmap );
+         return -1;
+      }
+
+/* Annul the first of the two Mappings, and replace it with the merged Mapping.
+   Also update the invert flag. */
+      (void) astAnnul( map1 );
+      ( *map_list )[ i1 ] = newmap;
+      ( *invert_list )[ i1 ] = astGetInvert( newmap );
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the list to fill the gap. */
+      (void) astAnnul( map2 );
+      int i = 0;
+      for( i = i2 + 1; i < *nmap; i++ ) {
+         ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+         ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+      }
+
+/* Clear the vacated element at the end. */
+      ( *map_list )[ *nmap - 1 ] = NULL;
+      ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first modified element. */
+      ( *nmap )--;
+      result = i1;
+
+   } else {
+
+/* In parallel. */
+/* UnitNormMaps cannot combine in parallel with any other Mappings. */
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a UnitNormMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unitnormmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     UnitNormMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a UnitNormMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to map them into the
+*     required window.
+
+*  Parameters:
+*     this
+*        Pointer to the UnitNormMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the UnitNormMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return NULL;
+
+/* Obtain a pointer to the UnitNormMap. */
+   AstUnitNormMap *map = (AstUnitNormMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   AstPointSet *result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   int ncoord_in = astGetNcoord( in );
+   int ncoord_out = astGetNcoord( result );
+   int npoint = astGetNpoint(in);
+   double **ptr_in = astGetPoints(in);
+   double **ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse mapping, according to the
+   direction specified and whether the mapping has been inverted. */
+   if( astGetInvert(map) ){
+      forward = !forward;
+   }
+
+/* Report an error if the UnitNormMap does not contain a centre. */
+   if( !map->centre && astOK ){
+      const char *class = astGetClass( this );
+      astError( AST__BADSM, "astTransform(%s): The supplied %s does not "
+                "contain any centre information.", status, class, class );
+   }
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if( astOK ){
+
+/* If any centre coordinate is bad then set all outputs bad */
+      int const ncoord_centre = forward ? ncoord_in : ncoord_out;
+      int coord_ctr = 0;
+      for( coord_ctr = 0; coord_ctr < ncoord_centre; coord_ctr++ ){
+         if( (map->centre)[coord_ctr] == AST__BAD ){
+            int coord_out = 0;
+            for( coord_out = 0; coord_out < ncoord_out; coord_out++ ){
+               int point = 0;
+               for( point = 0; point < npoint; point++ ){
+                  ptr_out[point][coord_out] = AST__BAD;
+               }
+            }
+            return result;
+         }
+      }
+
+      if( forward ){
+
+/* Forward transformation: vector to unit vector, norm */
+         int point = 0;
+         for( point = 0; point < npoint; point++ ){
+
+/* Compute max_relin: the maximum absolute input value relative to centre */
+            double max_relin = 0;
+            int coord_in = 0;
+            for( coord_in = 0; coord_in < ncoord_in; coord_in++ ){
+               double in = ptr_in[coord_in][point];
+               if( in == AST__BAD ){
+
+/* An input coord is bad, so all output coords are bad */
+                  int coord_out = 0;
+                  for( coord_out = 0; coord_out < ncoord_out; coord_out++ ){
+                     ptr_out[coord_out][point] = AST__BAD;
+                  }
+                  goto forward_next_point;
+               }
+               double abs_relin = fabs(in - map->centre[coord_in]);
+               if( abs_relin > max_relin ){
+                  max_relin = abs_relin;
+               }
+            }
+
+            if( max_relin == 0 ){
+
+/* All inputs are 0, so all outputs are bad */
+               int coord = 0;
+               for( coord = 0; coord < ncoord_out; coord++ ){
+                  ptr_out[coord][point] = AST__BAD;
+               }
+               continue;
+            }
+
+/* All is well; compute scaled_sum as the sum of (relin/max_relin)^2 for each input
+   where relin = in - centre (the scaling avoids overflow),
+   then compute norm = max_relin * sqrt(scaled_sum) and set all outputs */
+            double scaled_sum = 0;
+            int coord = 0;
+            for( coord = 0; coord < ncoord_in; coord++ ){
+               double scaled_in = (ptr_in[coord][point] - map->centre[coord])/max_relin;
+               scaled_sum += scaled_in*scaled_in;
+            }
+            double norm = max_relin*sqrt(scaled_sum);
+            for( coord = 0; coord < ncoord_in; coord++ ){
+               ptr_out[coord][point] = (ptr_in[coord][point] - map->centre[coord])/norm;
+            }
+            ptr_out[ncoord_out - 1][point] = norm;
+
+            forward_next_point: ;
+         }
+      } else {
+
+/* Inverse transformation: unit vector, norm -> vector */
+         int point = 0;
+         for( point = 0; point < npoint; point++ ){
+            double norm = ptr_in[ncoord_in-1][point];
+            if( norm == AST__BAD ){
+
+/* Norm is bad for this point; set all output coords bad */
+               int coord = 0;
+               for( coord = 0; coord < ncoord_out; coord++ ){
+                  ptr_out[coord][point] = AST__BAD;
+               }
+            } else {
+               int coord = 0;
+               for( coord = 0; coord < ncoord_out; coord++ ){
+                  double in = ptr_in[coord][point];
+                  if( in == AST__BAD ){
+                     ptr_out[coord][point] = AST__BAD;
+                  } else {
+                     ptr_out[coord][point] = in*norm + map->centre[coord];
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for UnitNormMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for UnitNormMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the UnitNormMap to be copied.
+*     objout
+*        Pointer to the UnitNormMap being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstUnitNormMap *out;              /* Pointer to output UnitNormMap */
+   AstUnitNormMap *in;               /* Pointer to input UnitNormMap */
+   int ncoord;                  /* No. of axes for the mapping */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Obtain a pointer to the input and output UnitNormMaps. */
+   in= (AstUnitNormMap *) objin;
+   out = (AstUnitNormMap *) objout;
+
+/* Get the number of coordinates mapped by the UnitNormMap. */
+   ncoord = astGetNin( in );
+
+/* Allocate memory holding copies of the centre defining the mapping. */
+   out->centre = (double *) astStore( NULL, (void *) in->centre,
+                                     sizeof(double)*(size_t)ncoord );
+
+/* If an error occurred, free any allocated memory. */
+   if( !astOK ) {
+      out->centre = (double *) astFree( (void *) out->centre );
+   }
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for UnitNormMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for UnitNormMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the UnitNormMap to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This destructor does nothing and exists only to maintain a
+*     one-to-one correspondence between destructors and copy
+*     constructors.
+*/
+
+/* Local Variables: */
+   AstUnitNormMap *this;              /* Pointer to UnitNormMap */
+
+/* Obtain a pointer to the UnitNormMap structure. */
+   this = (AstUnitNormMap *) obj;
+
+/* Free the memory holding the centre. */
+   this->centre = (double *) astFree( (void *) this->centre );
+
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for UnitNormMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the UnitNormMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the UnitNormMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define COMMENT_LEN 50           /* Maximum length of a comment string */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   char buff[ KEY_LEN + 1 ];        /* Buffer for keyword string */
+   char comment[ COMMENT_LEN + 1 ]; /* Buffer for comment string */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Obtain a pointer to the UnitNormMap structure. */
+   AstUnitNormMap *this = (AstUnitNormMap *) this_object;
+
+/* Get the number of coordinates to be mapped. */
+   int ncoord = astGetNin( this );
+
+/* Write out values representing the instance variables for the
+   UnitNormMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* The centre. */
+   int axis = 0;
+   for( axis = 0; axis < ncoord; axis++ ){
+      (void) sprintf( buff, "Ctr%d", axis + 1 );
+      (void) sprintf( comment, "Centre for axis %d", axis + 1 );
+      astWriteDouble( channel, buff, (this->centre)[ axis ] != 0.0, 0,
+                      (this->centre)[ axis ], comment );
+   }
+
+/* Undefine macros local to this function. */
+#undef COMMENT_LEN
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAUnitNormMap and astCheckUnitNormMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(UnitNormMap,Mapping)
+astMAKE_CHECK(UnitNormMap)
+
+AstUnitNormMap *astUnitNormMap_( int ncoord, const double centre[], const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astUnitNormMap
+f     AST_UNITNORMMAP
+
+*  Purpose:
+*     Create a UnitNormMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "unitnormmap.h"
+c     AstUnitNormMap *astUnitNormMap( int ncoord, const double centre[],
+c                               const char *options, ... )
+f     RESULT = AST_UNITNORMMAP( NCOORD, CENTRE, OPTIONS, STATUS )
+
+*  Class Membership:
+*     UnitNormMap constructor.
+
+*  Description:
+*     This function creates a new UnitNormMap and optionally initialises its
+*     attributes.
+*
+*     The forward transformation of a UnitNormMap subtracts the specified centre
+*     and then transforms the resulting vector to a unit vector and the vector norm.
+*     The output contains one more coordinate than the input: the initial Nin outputs
+*     are in the same order as the input; the final output is the norm.
+*
+*     The inverse transformation of a UnitNormMap multiplies each component
+*     of the provided vector by the provided norm and adds the specified centre.
+*     The output contains one fewer coordinate than the input: the initial Nin inputs
+*     are in the same order as the output; the final input is the norm.
+*
+*     UnitNormMap enables radially symmetric transformations, as follows:
+*     - apply a UnitNormMap to produce a unit vector and norm (radius)
+*     - apply a one-dimensional mapping to the norm (radius), while passing the unit vector unchanged
+*     - apply the same UnitNormMap in the inverse direction to produce the result
+
+*  Parameters:
+c     ncoord
+f     NCOORD = INTEGER (Given)
+*        The number of coordinate values for each point to be
+*        transformed (i.e. the number of dimensions of the space in
+*        which the points will reside). Output will include one additional coordinate.
+c     centre
+f     CENTRE( NCOORD ) = DOUBLE PRECISION (Given)
+*        An array containing the values to be subtracted from the input
+*        coordinates before computing unit vector and norm. A separate
+*        value must be supplied for each coordinate.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new UnitNormMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new UnitNormMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astUnitNormMap()
+f     AST_UNITNORMMAP = INTEGER
+*        A pointer to the new UnitNormMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Get a pointer to the thread specific global data structure. */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if( !astOK ) return NULL;
+
+/* Initialise the UnitNormMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   AstUnitNormMap *new = astInitUnitNormMap( NULL, sizeof( AstUnitNormMap ), !class_init, &class_vtab,
+                        "UnitNormMap", ncoord, centre );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new UnitNormMap's attributes. */
+      va_list args;
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new UnitNormMap. */
+   return new;
+}
+
+AstUnitNormMap *astUnitNormMapId_( int ncoord, const double centre[],
+                             const char *options, ... ) {
+/*
+*  Name:
+*     astUnitNormMapId_
+
+*  Purpose:
+*     Create a UnitNormMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "unitnormmap.h"
+*     AstUnitNormMap *astUnitNormMapId_( int ncoord, const double centre[],
+*                                  const char *options, ... )
+
+*  Class Membership:
+*     UnitNormMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astUnitNormMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astUnitNormMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astUnitNormMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astUnitNormMap_.
+
+*  Returned Value:
+*     The ID value associated with the new UnitNormMap.
+*/
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if( !astOK ) return NULL;
+
+/* Initialise the UnitNormMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   AstUnitNormMap *new = astInitUnitNormMap( NULL, sizeof( AstUnitNormMap ), !class_init, &class_vtab,
+                          "UnitNormMap", ncoord, centre );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new UnitNormMap's attributes. */
+      va_list args;                /* Variable argument list */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new UnitNormMap. */
+   return astMakeId( new );
+}
+
+AstUnitNormMap *astInitUnitNormMap_( void *mem, size_t size, int init,
+                              AstUnitNormMapVtab *vtab, const char *name,
+                              int ncoord, const double *centre, int *status ) {
+/*
+*+
+*  Name:
+*     astInitUnitNormMap
+
+*  Purpose:
+*     Initialise a UnitNormMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unitnormmap.h"
+*     AstUnitNormMap *astInitUnitNormMap( void *mem, size_t size, int init,
+*                                   AstUnitNormMapVtab *vtab, const char *name,
+*                                   int ncoord, const double *centre )
+
+*  Class Membership:
+*     UnitNormMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new UnitNormMap object. It allocates memory (if necessary) to accommodate
+*     the UnitNormMap plus any additional data associated with the derived class.
+*     It then initialises a UnitNormMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a UnitNormMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the UnitNormMap is to be initialised.
+*        This must be of sufficient size to accommodate the UnitNormMap data
+*        (sizeof(UnitNormMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the UnitNormMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the UnitNormMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the UnitNormMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new UnitNormMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     ncoord
+*        The number of coordinate values per point.
+*     centre
+*        Pointer to an array of centres, one for each coordinate.
+
+*  Returned Value:
+*     A pointer to the new UnitNormMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstUnitNormMap *new;            /* Pointer to new UnitNormMap */
+
+/* Check the global status. */
+   if( !astOK ) return NULL;
+
+/* Check centre */
+   if( ncoord <= 0 ){
+      astError( AST__BADSM, "The centre must have at least one axis", status );
+      return NULL;
+   }
+
+/* If necessary, initialise the virtual function table. */
+   if( init ) astInitUnitNormMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the UnitNormMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstUnitNormMap *) astInitMapping( mem, size, 0,
+                                         (AstMappingVtab *) vtab, name,
+                                         ncoord, ncoord+1, 1, 1 );
+
+   if( astOK ) {
+
+/* Initialise the UnitNormMap data. */
+/* ---------------------------- */
+/* Allocate memory to hold the centre for each axis. */
+      new->centre = (double *) astMalloc( sizeof(double)*(size_t)ncoord );
+
+/* Check the pointers can be used */
+      if( astOK ){
+
+/* Store the centre for each axis. */
+         int axis = 0;
+         for( axis = 0; axis < ncoord; axis++ ){
+            (new->centre)[ axis ] = centre ? centre[ axis ] : AST__BAD;
+         }
+
+      }
+
+/* If an error occurred, clean up by deleting the new UnitNormMap. */
+      if( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new UnitNormMap. */
+   return new;
+}
+
+AstUnitNormMap *astLoadUnitNormMap_( void *mem, size_t size,
+                               AstUnitNormMapVtab *vtab, const char *name,
+                               AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadUnitNormMap
+
+*  Purpose:
+*     Load a UnitNormMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "unitnormmap.h"
+*     AstUnitNormMap *astLoadUnitNormMap( void *mem, size_t size,
+*                                   AstUnitNormMapVtab *vtab, const char *name,
+*                                   AstChannel *channel )
+
+*  Class Membership:
+*     UnitNormMap loader.
+
+*  Description:
+*     This function is provided to load a new UnitNormMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     UnitNormMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a UnitNormMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the UnitNormMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        UnitNormMap data (sizeof(UnitNormMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the UnitNormMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the UnitNormMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstUnitNormMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new UnitNormMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the UnitNormMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "UnitNormMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new UnitNormMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants. */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   char buff[ KEY_LEN + 1 ];    /* Buffer for keyword string */
+
+/* Get a pointer to the thread specific global data structure. */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   AstUnitNormMap *new = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this UnitNormMap. In this case the
+   UnitNormMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if( !vtab ) {
+      size = sizeof( AstUnitNormMap );
+      vtab = &class_vtab;
+      name = "UnitNormMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if( !class_init ) {
+         astInitUnitNormMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built UnitNormMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if( astOK ) {
+
+/* Get the number of axis for the mapping. */
+      int ncoord = astGetNin( (AstMapping *) new );
+
+/* Allocate memory to hold the centre. */
+      new->centre = (double *) astMalloc( sizeof(double)*(size_t)ncoord );
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "UnitNormMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* The centre. */
+      int axis = 0;
+      for( axis = 0; axis < ncoord; axis++ ){
+         (void) sprintf( buff, "ctr%d", axis + 1 );
+         (new->centre)[ axis ] = astReadDouble( channel, buff, 0.0 );
+      }
+   }
+
+/* If an error occurred, clean up by deleting the new UnitNormMap. */
+   if( !astOK ) new = astDelete( new );
+
+/* Return the new UnitNormMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
diff --git a/ast/unitnormmap.h b/ast/unitnormmap.h
new file mode 100644
index 0000000..c18ba14
--- /dev/null
+++ b/ast/unitnormmap.h
@@ -0,0 +1,299 @@
+#if !defined( UNITNORMMAP_INCLUDED ) /* Include this file only once */
+#define UNITNORMMAP_INCLUDED
+/*
+*+
+*  Name:
+*     unitnormmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the UnitNormMap class.
+
+*  Invocation:
+*     #include "unitnormmap.h"
+
+*  Description:
+*     This include file defines the interface to the UnitNormMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     A UnitNormMap is a Mapping which, in the forward direction,
+*     subtracts the specified centre and then transforms the resulting vector
+*     to a unit vector and the vector norm.
+*     The forward direction outputs one more coordinate than is input.
+*
+*     The inverse transformation of a UnitNormMap multiplies each component
+*     of the provided vector by the provided norm and adds the specified centre.
+*     The forward direction outputs one fewer coordinate than is input.
+*
+*     Example: if centre = [1, -1] then [5, 2] transforms to [4, 3] after subtracting the centre;
+*     the norm is 5, so the output is [0.8, 0.6, 5]
+*
+*     UnitNormMap is intended for applying radially symmetric distortions, as follows:
+*     - apply a UnitNormMap to produce a unit vector and norm (radius)
+*     - apply some one-dimensional mapping to the norm (radius), while passing the unit vector unchanged
+*     - apply the same UnitNormMap in the inverse direction to produce the result
+*
+
+*  Inheritance:
+*     The UnitNormMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        ClearAttrib
+*           Clear an attribute value for a UnitNormMap.
+*        GetAttrib
+*           Get an attribute value for a UnitNormMap.
+*        SetAttrib
+*           Set an attribute value for a UnitNormMap.
+*        TestAttrib
+*           Test if an attribute value has been set for a UnitNormMap.
+*        astMapMerge
+*           Simplify a sequence of Mappings containing a UnitNormMap.
+*        astTransform
+*           Apply a UnitNormMap to transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        None.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAUnitNormMap
+*           Test class membership.
+*        astUnitNormMap
+*           Create a UnitNormMap.
+*
+*     Protected:
+*        astCheckUnitNormMap
+*           Validate class membership.
+*        astInitUnitNormMap
+*           Initialise a UnitNormMap.
+*        astInitUnitNormMapVtab
+*           Initialise the virtual function table for the UnitNormMap class.
+*        astLoadUnitNormMap
+*           Load a UnitNormMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstUnitNormMap
+*           UnitNormMap object type.
+*
+*     Protected:
+*        AstUnitNormMapVtab
+*           UnitNormMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 2016 University of Washington
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RO: Russell Owen (LSST)
+
+*  History:
+*     20-APR-2016 (RO):
+*        Original version.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* UnitNormMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstUnitNormMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *centre;                /* Pointer to array of shifts */
+
+} AstUnitNormMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstUnitNormMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+
+} AstUnitNormMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstUnitNormMapGlobals {
+   AstUnitNormMapVtab Class_Vtab;
+   int Class_Init;
+} AstUnitNormMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitUnitNormMapGlobals_( AstUnitNormMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(UnitNormMap)          /* Check class membership */
+astPROTO_ISA(UnitNormMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstUnitNormMap *astUnitNormMap_( int, const double [], const char *, int *, ...);
+#else
+AstUnitNormMap *astUnitNormMapId_( int, const double [], const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstUnitNormMap *astInitUnitNormMap_( void *, size_t, int, AstUnitNormMapVtab *,
+                               const char *, int, const double *, int * );
+
+/* Vtab initialiser. */
+void astInitUnitNormMapVtab_( AstUnitNormMapVtab *, const char *, int * );
+
+/* Loader. */
+AstUnitNormMap *astLoadUnitNormMap_( void *, size_t, AstUnitNormMapVtab *,
+                               const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckUnitNormMap(this) astINVOKE_CHECK(UnitNormMap,this,0)
+#define astVerifyUnitNormMap(this) astINVOKE_CHECK(UnitNormMap,this,1)
+
+/* Test class membership. */
+#define astIsAUnitNormMap(this) astINVOKE_ISA(UnitNormMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astUnitNormMap astINVOKE(F,astUnitNormMap_)
+#else
+#define astUnitNormMap astINVOKE(F,astUnitNormMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define \
+astInitUnitNormMap(mem,size,init,vtab,name,ncoord,centre) \
+astINVOKE(O,astInitUnitNormMap_(mem,size,init,vtab,name,ncoord,centre,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitUnitNormMapVtab(vtab,name) astINVOKE(V,astInitUnitNormMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadUnitNormMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadUnitNormMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckUnitNormMap to validate UnitNormMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#endif
+
+#endif
diff --git a/ast/version.h b/ast/version.h
new file mode 100644
index 0000000..52764cf
--- /dev/null
+++ b/ast/version.h
@@ -0,0 +1,73 @@
+#if !defined( VERSION_INCLUDED )
+#define VERSION_INCLUDED 1
+/*
+*+
+*  Name:
+*     version.h
+
+*  Purpose:
+*     Declare version numbers
+
+*  Description:
+*     Defines macros which expand to the components of the AST version
+*     number, namely the major and minor version numbers, and the
+*     release number.  The version number as a string is available by
+*     including the file config.h, which defines macros PACKAGE_STRING,
+*     PACKAGE_VERSION and (equivalently to the latter) VERSION.
+*
+*     For example, the version string `3.2.1' corresponds to major version
+*     3, minor version 2, release 1.
+
+*  Macros defined:
+*     AST__VMAJOR
+*        The AST major version number
+*     AST__VMINOR
+*        The AST minor version number
+*     AST__RELEASE
+*        The AST release number
+*
+*     For backwards compatibility, this module also declares macros
+*     AST_MAJOR_VERS, AST_MINOR_VERS and AST_RELEASE.  The AST__*
+*     macros should be used in preference to these, since the latter
+*     use (non-standard) single underscores.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     NG: Norman Gray (Starlink)
+
+*  History:
+*     25-NOV-2003 (NG):
+*        Original version
+*-
+*/
+
+/* The current version of AST is 8.4.0 */
+#define AST__VMAJOR    8
+#define AST__VMINOR    4
+#define AST__RELEASE   0
+
+/* Deprecated macros */
+#define AST_MAJOR_VERS 8
+#define AST_MINOR_VERS 4
+#define AST_RELEASE    0
+
+#endif /* #if ! defined(VERSION_INCLUDED) */
diff --git a/ast/wcsmap.c b/ast/wcsmap.c
new file mode 100644
index 0000000..3f90d4d
--- /dev/null
+++ b/ast/wcsmap.c
@@ -0,0 +1,6063 @@
+/*
+*class++
+*  Name:
+*     WcsMap
+
+*  Purpose:
+*     Implement a FITS-WCS sky projection.
+
+*  Constructor Function:
+c     astWcsMap
+f     AST_WCSMAP
+
+*  Description:
+*     This class is used to represent sky coordinate projections as
+*     described in the FITS world coordinate system (FITS-WCS) paper II
+*     "Representations of Celestial Coordinates in FITS" by M. Calabretta
+*     and E.W. Griesen. This paper defines a set of functions, or sky
+*     projections, which transform longitude-latitude pairs representing
+*     spherical celestial coordinates into corresponding pairs of Cartesian
+*     coordinates (and vice versa).
+*
+*     A WcsMap is a specialised form of Mapping which implements these
+*     sky projections and applies them to a specified pair of coordinates.
+*     All the projections in the FITS-WCS paper are supported, plus the now
+*     deprecated "TAN with polynomial correction terms" projection which
+*     is refered to here by the code "TPN". Using the FITS-WCS terminology,
+*     the transformation is between "native spherical" and "projection
+*     plane" coordinates (also called "intermediate world coordinates".
+*     These coordinates may, optionally, be embedded in a space with more
+*     than two dimensions, the remaining coordinates being copied unchanged.
+*     Note, however, that for consistency with other AST facilities, a
+*     WcsMap handles coordinates that represent angles in radians (rather
+*     than the degrees used by FITS-WCS).
+*
+*     The type of FITS-WCS projection to be used and the coordinates
+*     (axes) to which it applies are specified when a WcsMap is first
+*     created. The projection type may subsequently be determined
+*     using the WcsType attribute and the coordinates on which it acts
+*     may be determined using the WcsAxis(lonlat) attribute.
+*
+*     Each WcsMap also allows up to 100 "projection parameters" to be
+*     associated with each axis. These specify the precise form of the
+*     projection, and are accessed using PVi_m attribute, where "i" is
+*     the integer axis index (starting at 1), and m is an integer
+*     "parameter index" in the range 0 to 99. The number of projection
+*     parameters required by each projection, and their meanings, are
+*     dependent upon the projection type (most projections either do not
+*     use any projection parameters, or use parameters 1 and 2 associated
+*     with the latitude axis). Before creating a WcsMap you should consult
+*     the FITS-WCS paper for details of which projection parameters are
+*     required, and which have defaults. When creating the WcsMap, you must
+*     explicitly set values for all those required projection parameters
+*     which do not have defaults defined in this paper.
+
+*  Inheritance:
+*     The WcsMap class inherits from the Mapping class.
+
+*  Attributes:
+*     In addition to those attributes common to all Mappings, every
+*     WcsMap also has the following attributes:
+*
+*     - NatLat: Native latitude of the reference point of a FITS-WCS projection
+*     - NatLon: Native longitude of the reference point of a FITS-WCS projection
+*     - PVi_m: FITS-WCS projection parameters
+*     - PVMax: Maximum number of FITS-WCS projection parameters
+*     - WcsAxis(lonlat): FITS-WCS projection axes
+*     - WcsType: FITS-WCS projection type
+
+*  Functions:
+c     The WcsMap class does not define any new functions beyond those
+f     The WcsMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     15-FEB-1996 (DSB):
+*        Original version.
+*     23-MAR-1996 (DSB):
+*        Added support for PointSets with more than 2 axes.
+*     14-NOV-1996 (DSB):
+*        Added I/O facilities, external interface, attributes, etc.
+*     16-JAN-1997 (DSB):
+*        Allowed WCSBAD as a projection type in astWcsMap.
+*     24-APR-1997 (RFWS):
+*        Tidied prologues.
+*     26-SEP-1997 (DSB):
+*        o  Long descriptions of projections changed to include a textual
+*        description as well as the three letter acronym.
+*        o  Added protected function astPrjDesc.
+*        o  String values now used instead of integers to represent
+*        "choice" attributes externally (eg WcsType).
+*     8-APR-1998 (DSB):
+*        Modified MapMerge so that a WcsMap can merge with its own
+*        inverse when astSimplify is used.
+*     20-APR-1998 (DSB):
+*        Modified MapMerge to avoid the possibility of returning an
+*        empty mappings list.
+*     4-SEP-1998 (DSB):
+*        Changed MapMerge to allow WcsMaps to swap with PermMaps in
+*        order to bring mergable WcsMaps closer together.
+*     5-MAY-1999 (DSB):
+*        More corrections to MapMerge: Cleared up errors in the use of the
+*        supplied invert flags, and corrected logic for deciding which
+*        neighbouring Mapping to swap with.
+*     12-JUL-1999 (DSB):
+*        -  Report an error if too many or two few projection parameters are
+*        supplied in a WcsMap.
+*        -  Corrected MapMerge to prevent unset projection parameters
+*        being copied to any new WcsMaps.
+*        -  Correct handling of invert flags in WcsPerm.
+*     16-JUL-1999 (DSB):
+*        Fixed memory leak in MapMerge.
+*     11-FEB-2000 (DSB):
+*        Added PVj_m attributes. Attributes ProjP(0) to ProjP(9) are now
+*        aliases for PV(axlat)_0 to PV(axlat)_9. Renamed GLS projection
+*        as SFL (GLS retained as alias for SFL).
+*     10-AUG-2000 (DSB):
+*        MapMerge no longer simplifies a CAR projection. Previously they
+*        were replaced by a UnitMap, but this removed the cylic nature of
+*        the mapping (i.e. 2.PI == 0 ).
+*     6-OCT-2000 (DSB):
+*        Ignore leading and trailing spaces in astWCsPrjType (some
+*        CTYPE FITS keywords have appeared with trailing white space).
+*     26-SEP-2001 (DSB):
+*        Changed names of all functions and structure to avoid name clashes
+*        with wcslib.
+*     10-OCT-2002 (DSB):
+*        Added astIsZenithal.
+*     22-OCT-2002 (DSB):
+*        - GetPV now returns the FITS default value instead of AST__BAD
+*        if a defaultable latitude projection parameter has not been set.
+*        - A number of changes needed to support WcsLib v2.9.
+*        - Added AST__TPN projection.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitWcsMapVtab
+*        method.
+*     4-JUN-2003 (DSB):
+*        - Added attribute "NatLon".
+*        - Changed to allow a user-specified fiducial point to be stored
+*        in projection parameter PVi_1 and PVi_2 for the longitude axis.
+*        - Changed "PVj_m" to "PVi_m" for consistency with FITS-WCS paper II.
+*     18-AUG-2003 (DSB):
+*        In function Map, assign zero longitude to output positions which
+*        are very close to a pole.
+*     23-SEP-2003 (DSB):
+*        - Changed so that the NatLat and NatLon attributes refer to the
+*        fixed values for the projections defined in FITS-WCS paper II, rather
+*        than the user-defined values stored in projection parameter PVi_1 and
+*        PVi_2 for the longitude axis.
+*     11-FEB-2004 (DSB):
+*        Corrected axis numbering when reporting missing keywords in
+*        Transform.
+*     23-APR-2004 (DSB):
+*        Changes to simplification algorithm.
+*     1-SEP-2004 (DSB):
+*        CopyPV rewritten to avoid assumption that the input and output
+*        WcsMaps have the same number of axes and that the lon/lat axes have
+*        the same indices.
+*     7-DEC-2005 (DSB):
+*        Free memory allocated by calls to astReadString.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     15-FEB-2006 (DSB):
+*        Use dynamic rather than static memory for the parameter arrays in
+*        the AstPrjPrm structure.Override astGetObjSize. This is to
+*        reduce the in-memory size of a WcsMap.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*     10-AUG-2006 (DSB):
+*        Correct astLoadWcsMap to take acount of the different number of
+*        PVi_m values that can be associated with each axis.
+*     4-JAN-2007 (DSB):
+*        Correct astLoadWcsMap to load the projection parameter with
+*        highest index correctly.
+*     23-FEB-2007 (DSB):
+*        Added HPX projection.
+*     1-MAR-2011 (DSB):
+*        In function Map, do not allow valid longitude range to include both
+*        the high limit and the low limt (since they are both the same point on
+*        the sky).
+*     7-MAR-2011 (DSB):
+*        In function Map, only do the longitude check if the projection
+*        is not cyclic.
+*     24-MAY-2011 (DSB):
+*        Added protected FITSProj and TPNTan attributes (they should be
+*        removed when the PolyMap class has an iterative inverse).
+*     6-MAR-2014 (DSB):
+*        Revert the change made on 18-AUG-2003 since setting the
+*        longitude arbitrarily to zero for points close to the pole
+*        causes significant round trip errors when doing pixel->sky->pixel
+*        transformation for points very close to the pole, if the pixel
+*        size is very small. The longitude at the pole is indeterminate,
+*        but whatever random numerical value is returned by atan2 is
+*        no less useful (and no more useful) than a fixed value of zero.
+*     12-JUN-2014 (DSB):
+*        Added XPH projection.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS WcsMap
+
+/* Macros which return the maximum and minimum of two values. */
+#define MAX(aa,bb) ((aa)>(bb)?(aa):(bb))
+#define MIN(aa,bb) ((aa)<(bb)?(aa):(bb))
+
+/* Macros to check for equality of floating point values. We cannot
+   compare bad values directory because of the danger of floating point
+   exceptions, so bad values are dealt with explicitly. */
+#define EQUAL(aa,bb) (((aa)==AST__BAD)?(((bb)==AST__BAD)?1:0):(((bb)==AST__BAD)?0:(fabs((aa)-(bb))<=1.0E5*MAX((fabs(aa)+fabs(bb))*DBL_EPSILON,DBL_MIN))))
+
+/*
+*
+*  Name:
+*     MAKE_CLEAR
+
+*  Purpose:
+*     Implement a method to clear a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     MAKE_CLEAR(attr,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the WcsMap class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Clear<Attribute>( AstWcsMap *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        void astClear<Attribute>_( AstWcsMap *this, int axis )
+*
+*     which implement a method for clearing a single value in a specified
+*     multi-valued attribute for an axis of a WcsMap.
+
+*  Parameters:
+*     attr
+*        The name of the attribute to be cleared, as it appears in the function
+*        name (e.g. Label in "astClearLabelAt").
+*     component
+*        The name of the class structure component that holds the attribute
+*        value.
+*     assign
+*        An expression that evaluates to the value to assign to the component
+*        to clear its value. The variable "axis" can be used to refer to
+*        the zero-based index of the attribute component being cleared.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_CLEAR(attr,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Clear##attr( AstWcsMap *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astClear" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Assign the "clear" value. */ \
+   } else { \
+      this->component[ axis ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astClear##attr##_( AstWcsMap *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,WcsMap,Clear##attr))( this, axis, status ); \
+}
+
+
+/*
+*
+*  Name:
+*     MAKE_GET
+
+*  Purpose:
+*     Implement a method to get a single value in a multi-valued attribute.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     MAKE_GET(attr,type,bad_value,assign,nval)
+
+*  Class Membership:
+*     Defined by the WcsMap class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static <Type> Get<Attribute>( AstWcsMap *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        <Type> astGet<Attribute>_( AstWcsMap *this, int axis )
+*
+*     which implement a method for getting a single value from a specified
+*     multi-valued attribute for an axis of a WcsMap.
+
+*  Parameters:
+*     attr
+*        The name of the attribute whose value is to be obtained, as it
+*        appears in the function name (e.g. Label in "astGetLabel").
+*     type
+*        The C type of the attribute.
+*     bad_value
+*        A constant value to return if the global error status is set, or if
+*        the function fails.
+*     assign
+*        An expression that evaluates to the value to be returned. This can
+*        use the string "axis" to represent the zero-based value index.
+*     nval
+*        Specifies the number of values in the multi-valued attribute. The
+*        "axis" values supplied to the created function should be in the
+*        range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*
+*/
+
+/* Define the macro. */
+#define MAKE_GET(attr,type,bad_value,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static type Get##attr( AstWcsMap *this, int axis, int *status ) { \
+   type result;                  /* Result to be returned */ \
+\
+/* Initialise */ \
+   result = (bad_value); \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astGet" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Assign the result value. */ \
+   } else { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = (bad_value); \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */  \
+type astGet##attr##_( AstWcsMap *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return (bad_value); \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,WcsMap,Get##attr))( this, axis, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_SET
+
+*  Purpose:
+*     Implement a method to set a single value in a multi-valued attribute
+*     for a WcsMap.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     MAKE_SET(attr,type,component,assign,nval)
+
+*  Class Membership:
+*     Defined by the WcsMap class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static void Set<Attribute>( AstWcsMap *this, int axis, <Type> value )
+*
+*     and an external interface function of the form:
+*
+*        void astSet<Attribute>_( AstWcsMap *this, int axis, <Type> value )
+*
+*     which implement a method for setting a single value in a specified
+*     multi-valued attribute for a WcsMap.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be set, as it appears in the function
+*         name (e.g. Label in "astSetLabelAt").
+*      type
+*         The C type of the attribute.
+*      component
+*         The name of the class structure component that holds the attribute
+*         value.
+*      assign
+*         An expression that evaluates to the value to be assigned to the
+*         component.
+*      nval
+*         Specifies the number of values in the multi-valued attribute. The
+*         "axis" values supplied to the created function should be in the
+*         range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_SET(attr,type,component,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static void Set##attr( AstWcsMap *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astSet" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Store the new value in the structure component. */ \
+   } else { \
+      this->component[ axis ] = (assign); \
+   } \
+} \
+\
+/* External interface. */ \
+/* ------------------- */ \
+void astSet##attr##_( AstWcsMap *this, int axis, type value, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   (**astMEMBER(this,WcsMap,Set##attr))( this, axis, value, status ); \
+}
+
+/*
+*
+*  Name:
+*     MAKE_TEST
+
+*  Purpose:
+*     Implement a method to test if a single value has been set in a
+*     multi-valued attribute for a class.
+
+*  Type:
+*     Private macro.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     MAKE_TEST(attr,assign,nval)
+
+*  Class Membership:
+*     Defined by the WcsMap class.
+
+*  Description:
+*     This macro expands to an implementation of a private member function of
+*     the form:
+*
+*        static int Test<Attribute>( AstWcsMap *this, int axis )
+*
+*     and an external interface function of the form:
+*
+*        int astTest<Attribute>_( AstWcsMap *this, int axis )
+*
+*     which implement a method for testing if a single value in a specified
+*     multi-valued attribute has been set for a class.
+
+*  Parameters:
+*      attr
+*         The name of the attribute to be tested, as it appears in the function
+*         name (e.g. Label in "astTestLabelAt").
+*      assign
+*         An expression that evaluates to 0 or 1, to be used as the returned
+*         value. This can use the string "axis" to represent the zero-based
+*         index of the value within the attribute.
+*      nval
+*         Specifies the number of values in the multi-valued attribute. The
+*         "axis" values supplied to the created function should be in the
+*         range zero to (nval - 1).
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*-
+*/
+
+/* Define the macro. */
+#define MAKE_TEST(attr,assign,nval) \
+\
+/* Private member function. */ \
+/* ------------------------ */ \
+static int Test##attr( AstWcsMap *this, int axis, int *status ) { \
+   int result;                   /* Value to return */ \
+\
+/* Initialise */ \
+   result = 0; \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return result; \
+\
+\
+/* Validate the axis index. */ \
+   if( axis < 0 || axis >= nval ){ \
+      astError( AST__AXIIN, "%s(%s): Index (%d) is invalid for attribute " \
+                #attr " - it should be in the range 1 to %d.", status, \
+                "astTest" #attr, astGetClass( this ), \
+                axis + 1, nval ); \
+\
+/* Assign the result value. */ \
+   } else { \
+      result = (assign); \
+   } \
+\
+/* Check for errors and clear the result if necessary. */ \
+   if ( !astOK ) result = 0; \
+\
+/* Return the result. */ \
+   return result; \
+} \
+/* External interface. */ \
+/* ------------------- */ \
+int astTest##attr##_( AstWcsMap *this, int axis, int *status ) { \
+\
+/* Check the global error status. */ \
+   if ( !astOK ) return 0; \
+\
+/* Invoke the required method via the virtual function table. */ \
+   return (**astMEMBER(this,WcsMap,Test##attr))( this, axis, status ); \
+}
+
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "unitmap.h"             /* Unit mappings */
+#include "permmap.h"             /* Axis permutation mappings */
+#include "wcsmap.h"              /* Interface definition for this class */
+#include "pal.h"                 /* SLALIB function prototypes */
+#include "channel.h"             /* I/O channels */
+#include "proj.h"                /* WCSLIB projections and WCSLIB_MXPAR */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+/* Local Type Definitions. */
+/* ----------------------- */
+/* This structure is used to hold information describing a WCSLIB
+   projection. */
+typedef struct PrjData {
+   int prj;                     /* WCSLIB projection identifier value */
+   int mxpar;                   /* Max index for a lat axis projection param */
+   int mxpar2;                  /* Max index for a lon axis projection param */
+   char desc[60];               /* Long projection description */
+   char ctype[5];               /* FITS CTYPE identifying string */
+   int (* WcsFwd)(double, double, struct AstPrjPrm *, double *, double *);
+                                /* Pointer to forward projection function */
+   int (* WcsRev)(double, double, struct AstPrjPrm *, double *, double *);
+                                /* Pointer to reverse projection function */
+   double theta0;               /* Default native latitude of fiducial point */
+} PrjData;
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static int *(* parent_mapsplit)( AstMapping *, int, const int *, AstMapping **, int * );
+
+/* The following array of PrjData structured describes each of the WCSLIB
+   projections. The last entry in the list should be for the AST__WCSBAD
+   projection. This marks the end of the list. */
+static PrjData PrjInfo[] = {
+   { AST__AZP,  2, 4, "zenithal perspective", "-AZP", astAZPfwd, astAZPrev, AST__DPIBY2 },
+   { AST__SZP,  3, 4, "slant zenithal perspective", "-SZP", astSZPfwd, astSZPrev, AST__DPIBY2 },
+   { AST__TAN,  0, 4, "gnomonic", "-TAN",  astTANfwd, astTANrev, AST__DPIBY2 },
+   { AST__STG,  0, 4, "stereographic", "-STG",  astSTGfwd, astSTGrev, AST__DPIBY2 },
+   { AST__SIN,  2, 4, "orthographic", "-SIN",  astSINfwd, astSINrev, AST__DPIBY2 },
+   { AST__ARC,  0, 4, "zenithal equidistant", "-ARC",  astARCfwd, astARCrev, AST__DPIBY2 },
+   { AST__ZPN,  WCSLIB_MXPAR, 4, "zenithal polynomial", "-ZPN",  astZPNfwd, astZPNrev, AST__DPIBY2 },
+   { AST__ZEA,  0, 4, "zenithal equal area", "-ZEA",  astZEAfwd, astZEArev, AST__DPIBY2 },
+   { AST__AIR,  1, 4, "Airy", "-AIR",  astAIRfwd, astAIRrev, AST__DPIBY2 },
+   { AST__CYP,  2, 4, "cylindrical perspective", "-CYP",  astCYPfwd, astCYPrev, 0.0 },
+   { AST__CEA,  1, 4, "cylindrical equal area", "-CEA",  astCEAfwd, astCEArev, 0.0 },
+   { AST__CAR,  0, 4, "Cartesian", "-CAR",  astCARfwd, astCARrev, 0.0 },
+   { AST__MER,  0, 4, "Mercator", "-MER",  astMERfwd, astMERrev, 0.0 },
+   { AST__SFL,  0, 4, "Sanson-Flamsteed", "-SFL",  astSFLfwd, astSFLrev, 0.0 },
+   { AST__PAR,  0, 4, "parabolic", "-PAR",  astPARfwd, astPARrev, 0.0 },
+   { AST__MOL,  0, 4, "Mollweide", "-MOL",  astMOLfwd, astMOLrev, 0.0 },
+   { AST__AIT,  0, 4, "Hammer-Aitoff", "-AIT",  astAITfwd, astAITrev, 0.0 },
+   { AST__COP,  2, 4, "conical perspective", "-COP",  astCOPfwd, astCOPrev, AST__BAD },
+   { AST__COE,  2, 4, "conical equal area", "-COE",  astCOEfwd, astCOErev, AST__BAD },
+   { AST__COD,  2, 4, "conical equidistant", "-COD",  astCODfwd, astCODrev, AST__BAD },
+   { AST__COO,  2, 4, "conical orthomorphic", "-COO",  astCOOfwd, astCOOrev, AST__BAD },
+   { AST__BON,  1, 4, "Bonne's equal area", "-BON",  astBONfwd, astBONrev, 0.0 },
+   { AST__PCO,  0, 4, "polyconic", "-PCO",  astPCOfwd, astPCOrev, 0.0 },
+   { AST__TSC,  0, 4, "tangential spherical cube", "-TSC",  astTSCfwd, astTSCrev, 0.0 },
+   { AST__CSC,  0, 4, "cobe quadrilateralized spherical cube", "-CSC", astCSCfwd, astCSCrev, 0.0 },
+   { AST__QSC,  0, 4, "quadrilateralized spherical cube", "-QSC",  astQSCfwd, astQSCrev, 0.0 },
+   { AST__NCP,  2, 4, "AIPS north celestial pole", "-NCP",  NULL,   NULL, 0.0 },
+   { AST__GLS,  0, 4, "sinusoidal", "-GLS",  astSFLfwd, astSFLrev, 0.0 },
+   { AST__HPX,  2, 4, "HEALPix", "-HPX",  astHPXfwd, astHPXrev, 0.0 },
+   { AST__XPH,  0, 4, "polar HEALPix", "-XPH",  astXPHfwd, astXPHrev, AST__DPIBY2 },
+   { AST__TPN,  WCSLIB_MXPAR, WCSLIB_MXPAR, "gnomonic polynomial", "-TPN",  astTPNfwd, astTPNrev, AST__DPIBY2 },
+   { AST__WCSBAD, 0, 4, "<null>",   "    ",  NULL,   NULL, 0.0 } };
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(WcsMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(WcsMap,Class_Init)
+#define class_vtab astGLOBAL(WcsMap,Class_Vtab)
+#define getattrib_buff astGLOBAL(WcsMap,GetAttrib_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstWcsMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstWcsMap *astWcsMapId_( int, int, int, int, const char *options, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static int GetObjSize( AstObject *, int * );
+static double GetPV( AstWcsMap *, int, int, int * );
+static int TestPV( AstWcsMap *, int, int, int * );
+static void ClearPV( AstWcsMap *, int, int, int * );
+static void SetPV( AstWcsMap *, int, int, double, int * );
+
+static int GetPVMax( AstWcsMap *, int, int * );
+static int GetWcsType( AstWcsMap *, int * );
+static double GetNatLat( AstWcsMap *, int * );
+static double GetNatLon( AstWcsMap *, int * );
+static int GetWcsAxis( AstWcsMap *, int, int * );
+
+static int GetFITSProj( AstWcsMap *, int * );
+static int TestFITSProj( AstWcsMap *, int * );
+static void ClearFITSProj( AstWcsMap *, int * );
+static void SetFITSProj( AstWcsMap *, int, int * );
+
+static int GetTPNTan( AstWcsMap *, int * );
+static int TestTPNTan( AstWcsMap *, int * );
+static void ClearTPNTan( AstWcsMap *, int * );
+static void SetTPNTan( AstWcsMap *, int, int * );
+
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const PrjData *FindPrjData( int, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static int CanMerge( AstMapping *, int, AstMapping *, int, int * );
+static int CanSwap( AstMapping *, AstMapping *, int, int, int *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetNP( AstWcsMap *, int, int * );
+static int IsZenithal( AstWcsMap *, int * );
+static int LongRange( const PrjData *, struct AstPrjPrm *, double *, double *, int * );
+static int Map( AstWcsMap *, int, int, double *, double *, double *, double *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void CopyPV( AstWcsMap *, AstWcsMap *, int * );
+static void Delete( AstObject *obj, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void FreePV( AstWcsMap *, int * );
+static void InitPrjPrm( AstWcsMap *, int * );
+static void PermGet( AstPermMap *, int **, int **, double **, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void WcsPerm( AstMapping **, int *, int, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+
+/* Member functions. */
+/* ================= */
+static int CanMerge( AstMapping *map1, int inv1, AstMapping *map2, int inv2, int *status ){
+/*
+*
+*  Name:
+*     CanMerge
+
+*  Purpose:
+*     Checks if two WcsMaps can be merged.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int CanMerge( AstMapping *map1, int inv1, AstMapping *map2, int inv2, int *status )
+
+*  Class Membership:
+*     WcsMap internal utility function.
+
+*  Description:
+*     This function checks the two supplied Mappings to see if they are
+*     two WcsMaps which can be merged. This is only possible if they
+*     form an inverse pair.
+
+*  Parameters:
+*     map1
+*        A pointer to the first mapping.
+*     map2
+*        A pointer to the second mapping.
+*     inv1
+*        The invert flag to use with the first mapping.
+*     inv2
+*        The invert flag to use with the second mapping.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     1 if the WcsMaps can be merged, zero otherwise.
+
+*/
+
+/* Local Variables: */
+   AstWcsMap *wcs1;               /* Pointer to first WcsMap */
+   AstWcsMap *wcs2;               /* Pointer to second WcsMap */
+   int m;                         /* Projection parameter index */
+   int ret;                       /* Can the Mappings be merged? */
+   int i;                         /* Axis index */
+
+/* Initialise the returned value. */
+   ret = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Both Mappings must be WcsMaps to merge. */
+   if( !strcmp( "WcsMap", astGetClass( map1 ) ) &&
+       !strcmp( "WcsMap", astGetClass( map2 ) ) ) {
+
+/* Get pointers to the WcsMaps. */
+      wcs1 = (AstWcsMap *) map1;
+      wcs2 = (AstWcsMap *) map2;
+
+/* Check that the two WcsMaps performs the same sort of projection, and
+   have the same number of axes. */
+      if( astGetWcsType( wcs1 ) == astGetWcsType( wcs2 ) &&
+          astGetNin( wcs1 ) == astGetNin( wcs2 ) ) {
+
+/* Check that the Mappings are applied in opposite senses. */
+         if( inv1 != inv2 ) {
+
+/* Check that the latitude and longitude axes have the same indices in
+   both WcsMaps. */
+            if( astGetWcsAxis( wcs1, 0 ) == astGetWcsAxis( wcs2, 0 ) &&
+                astGetWcsAxis( wcs1, 1 ) == astGetWcsAxis( wcs2, 1 ) ){
+
+/* We nopw check the projection parameters are equal. Assume they are for
+   the moment. */
+               ret = 1;
+
+/* Check the parameters for each axis in turn. */
+               for( i = 0; i < astGetNin( wcs1 ); i++ ){
+
+/* If the two WcsMaps have a different number of parameters for this axes,
+   they cannot merge. */
+                  if( GetNP( wcs1, i, status ) != GetNP( wcs1, i, status ) ){
+                     ret = 0;
+                     break;
+
+/* Otherwise, check each parameter value in turn. If any are found which
+   are not equal, the WcsMaps cannot merge. */
+                  } else {
+                     for( m = 0; m < GetNP( wcs1, i, status ); m++ ){
+                        if( !EQUAL( astGetPV( wcs1, i, m ),
+                                    astGetPV( wcs2, i, m ) ) ){
+                           ret = 0;
+                           break;
+                        }
+                     }
+                     if( !ret ) break;
+                  }
+               }
+            }
+         }
+      }
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static int CanSwap( AstMapping *map1, AstMapping *map2, int inv1, int inv2,
+                    int *simpler, int *status ){
+/*
+*  Name:
+
+*     CanSwap
+
+*  Purpose:
+*     Determine if two Mappings could be swapped.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int CanSwap( AstMapping *map1, AstMapping *map2, int inv1, int inv2,
+*                  int *simpler )
+
+*  Class Membership:
+*     WcsMap member function
+
+*  Description:
+*     This function returns a flag indicating if the pair of supplied
+*     Mappings could be replaced by an equivalent pair of Mappings from the
+*     same classes as the supplied pair, but in reversed order. Each pair
+*     of Mappings is considered to be compunded in series. The supplied
+*     Mapings are not changed in any way.
+
+*  Parameters:
+*     map1
+*        The Mapping to be applied first.
+*     map2
+*        The Mapping to be applied second.
+*     inv1
+*        The invert flag to use with map1. A value of zero causes the forward
+*        mapping to be used, and a non-zero value causes the inverse
+*        mapping to be used.
+*     inv2
+*        The invert flag to use with map2.
+*     simpler
+*        Addresss of a location at which to return a flag indicating if
+*        the swapped Mappings would be intrinsically simpler than the
+*        original Mappings.
+
+*  Returned Value:
+*     1 if the Mappings could be swapped, 0 otherwise.
+
+*  Notes:
+*     -  One of the supplied pair of Mappings must be a WcsMap.
+*     -  A value of 0 is returned if the two Mappings could be merged into
+*     a single Mapping.
+*     -  A value of 0 is returned if an error has already occurred, or if
+*     this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *nowcs;        /* Pointer to non-WcsMap Mapping */
+   AstWcsMap  *wcs;          /* Pointer to WcsMap Mapping */
+   const char *class1;       /* Pointer to map1 class string */
+   const char *class2;       /* Pointer to map2 class string */
+   const char *nowcs_class;  /* Pointer to non-WcsMap class string */
+   double *consts;           /* Pointer to constants array */
+   int *inperm;              /* Pointer to input axis permutation array */
+   int *outperm;             /* Pointer to output axis permutation array */
+   int i;                    /* Loop count */
+   int invert[ 2 ];          /* Original invert flags */
+   int latax;                /* Index of latitude axis in WcsMap */
+   int lonax;                /* Index of longitude axis in WcsMap */
+   int nin;                  /* No. of input coordinates for the PermMap */
+   int nout;                 /* No. of output coordinates for the PermMap */
+   int ret;                  /* Returned flag */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise */
+   ret = 0;
+   *simpler = 0;
+
+/* Temporarily set the Invert attributes of both Mappings to the supplied
+   values. */
+   invert[ 0 ] = astGetInvert( map1 );
+   astSetInvert( map1, inv1 );
+
+   invert[ 1 ] = astGetInvert( map2 );
+   astSetInvert( map2, inv2 );
+
+/* Get the classes of the two mappings. */
+   class1 = astGetClass( map1 );
+   class2 = astGetClass( map2 );
+   if( astOK ){
+
+/* Get a pointer to the non-WcsMap Mapping. */
+      if( !strcmp( class1, "WcsMap" ) ){
+         wcs = (AstWcsMap *) map1;
+         nowcs = map2;
+         nowcs_class = class2;
+      } else {
+         nowcs = map1;
+         wcs = (AstWcsMap *) map2;
+         nowcs_class = class1;
+      }
+
+/* If it is a PermMap, the Mappings can be swapped so long as:
+   1) all links between input and output axes in the PermMap are
+   bi-directional. This does not preclude the existence of unconnected axes,
+   which do not have links (bi-directional or otherwise).
+   2) The PermMap passesd though both the longitude and latitude axes of
+   the WcsMap */
+      if( !strcmp( nowcs_class, "PermMap" ) ){
+
+/* Get the number of input and output coordinates. */
+         nin = astGetNin( nowcs );
+         nout = astGetNout( nowcs );
+
+/* We need to know the axis permutation arrays and constants array for
+   the PermMap. */
+         PermGet( (AstPermMap *) nowcs, &outperm, &inperm, &consts, status );
+         if( astOK ) {
+
+/* Indicate we can swap with the PermMap. */
+            ret = 1;
+
+/* Check each output axis. If any links between axes are found which are
+   not bi-directional, indicate that we cannot swap with the PermMap. */
+            for( i = 0; i < nout; i++ ){
+               if( outperm[ i ] >= 0 && outperm[ i ] < nin ) {
+                  if( inperm[ outperm[ i ] ] != i ) {
+                     ret = 0;
+                     break;
+                  }
+               }
+            }
+
+/* Check each input axis. If any links between axes are found which are
+   not bi-directional, indicate that we cannot swap with the PermMap. */
+            for( i = 0; i < nin; i++ ){
+               if( inperm[ i ] >= 0 && inperm[ i ] < nout ) {
+                  if( outperm[ inperm[ i ] ] != i ) {
+                     ret = 0;
+                     break;
+                  }
+               }
+            }
+
+/* Check that the longitude and latitude axes both have bi-directional
+   links in the PermMap, or are both unassigned. */
+            if( ret ) {
+
+/* Get the indices of the longitude and latitude axes in the WcsMap */
+               lonax = astGetWcsAxis( wcs, 0 );
+               latax = astGetWcsAxis( wcs, 1 );
+
+/* If the WcsMap is applied first... */
+               if( wcs == (AstWcsMap *) map1 ) {
+                  if( inperm[ lonax] < 0 && inperm[ latax ] < 0 ) {
+                     ret = 1;
+                  } else if( inperm[ lonax ] < 0 || inperm[ lonax ] >= nout ||
+                             inperm[ latax ] < 0 || inperm[ latax ] >= nout ) {
+                     ret = 0;
+                  }
+
+/* If the WcsMap is applied second ... */
+               } else {
+                  if( outperm[ lonax ] < 0 && outperm[ latax ] < 0 ) {
+                     ret = 1;
+                  } else if( outperm[ lonax ] < 0 || outperm[ lonax ] >= nin ||
+                             outperm[ latax ] < 0 || outperm[ latax ] >= nin ) {
+                     ret = 0;
+                  }
+               }
+            }
+
+/* If we can swap with the PermMap, the swapped Mappings may be
+   intrinsically simpler than the original mappings. */
+            if( ret ) {
+
+/* If the PermMap precedes the WcsMap, this will be the case if the PermMap
+   has more outputs than inputs. If the WcsMap precedes the PermMap, this
+   will be the case if the PermMap has more inputs than outputs. */
+               *simpler = ( nowcs == map1 ) ? nout > nin : nin > nout;
+            }
+
+/* Free the axis permutation and constants arrays. */
+            outperm = (int *) astFree( (void *) outperm );
+            inperm = (int *) astFree( (void *) inperm );
+            consts = (double *) astFree( (void *) consts );
+         }
+      }
+   }
+
+/* Re-instate the original settings of the Invert attributes for the
+   supplied MatrixMaps. */
+   astSetInvert( map1, invert[ 0 ] );
+   astSetInvert( map2, invert[ 1 ] );
+
+/* Return the answer. */
+   return astOK ? ret : 0;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a WcsMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     WcsMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     WcsMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the WcsMap.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstWcsMap *this;             /* Pointer to the WcsMap structure */
+   int i;                       /* Axis index */
+   int len;                     /* Length of the attribute name */
+   int m;                       /* Projection parameter index */
+   int nc;                      /* No. of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the WcsMap structure. */
+   this = (AstWcsMap *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* ProjP. */
+/* ------ */
+   if ( nc = 0, ( 1 == astSscanf( attrib, "prpjp(%d)%n", &m, &nc ) )
+                  && ( nc >= len ) ) {
+      astClearPV( this, astGetWcsAxis( this, 1 ), m );
+
+/* PV. */
+/* ------ */
+   } else if ( nc = 0, ( 2 == astSscanf( attrib, "pv%d_%d%n", &i, &m, &nc ) )
+                  && ( nc >= len ) ) {
+      astClearPV( this, i - 1, m );
+
+/* If the name was not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then report an
+   error. */
+   } else if ( ( nc = 0, ( 1 == astSscanf( attrib, "wcsaxis(%d)%n", &i, &nc ) )
+                           && ( nc >= len ) ) ||
+        !strcmp( attrib, "wcstype" ) ||
+        !strcmp( attrib, "natlat" ) ||
+        !strcmp( attrib, "natlon" ) ){
+      astError( AST__NOWRT, "astClear: Invalid attempt to clear the \"%s\" "
+                "value for a %s.", status, attrib, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static void ClearPV( AstWcsMap *this, int i, int m, int *status ) {
+/*
+*+
+*  Name:
+*     astClearPV
+
+*  Purpose:
+*     Clear a PVi_m attribute.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     void astClearPV( AstWcsMap *this, int i, int m )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function clears a specified member of the PV attribute array, by
+*     resetting its value to AST__BAD.
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+*     i
+*        Zero based axis index.
+*     m
+*        Zero based parameter index.
+
+*-
+*/
+/* Local Variables; */
+   int npar;
+   int mxpar;
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Report an error if the object has been cloned (i.e. has a reference
+   count that is greater than one). */
+   if( astGetRefCount( this ) > 1 ) {
+      astError( AST__IMMUT, "astClear(%s): Projection parameter values "
+                "within the supplied %s cannot be cleared because the %s has "
+                "been cloned (programming error).", status,
+                astGetClass(this), astGetClass(this), astGetClass(this) );
+
+/* Validate the axis index. */
+   } else if( i < 0 || i >= astGetNin( this ) ){
+      astError( AST__AXIIN, "astClearPV(%s): Axis index (%d) is invalid in "
+                "attribute PV%d_%d  - it should be in the range 1 to %d.",
+                status, astGetClass( this ), i + 1, i + 1, m,
+                astGetNin( this ) );
+
+   } else {
+
+/* Find the maximum number of parameters allowed for the axis. */
+      mxpar = astGetPVMax( this, i );
+
+/* Ignore unused parameters. */
+      if( m < 0 || m > mxpar ){
+
+/* See if the parameter is currently set. Is so, set its value to
+   AST__BAD. */
+      } else if( this->np && this->p ){
+         npar = this->np[ i ];
+         if( m < npar && this->p[ i ] ) this->p[ i ][ m ] = AST__BAD;
+      }
+
+/* Re-initialize the values stored in the "AstPrjPrm" structure. */
+      InitPrjPrm( this, status );
+   }
+}
+
+static void ClearTPNTan( AstWcsMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     astClearTPNTan
+
+*  Purpose:
+*     Clear the TPNTan attribute.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     void ClearTPNTan( AstWcsMap *this, int *status )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function clears the TPNTan attribute, ensuring the projection
+*     parameters used by WCSLIB are adjusted accordingly.
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Clear the value. */
+   this->tpn_tan = -INT_MAX;
+
+/* Re-initialize the values stored in the "AstPrjPrm" structure. */
+   InitPrjPrm( this, status );
+}
+
+static void CopyPV( AstWcsMap *in, AstWcsMap *out, int *status ) {
+/*
+*  Name:
+*     CopyPV
+
+*  Purpose:
+*     Copy projection parameter information from one WcsMap to another.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void CopyPV( AstWcsMap *in, AstWcsMap *out )
+
+*  Description:
+*     This function copies projection parameter information from one
+*     WcsMap to another.
+
+*  Parameters:
+*     in
+*        Pointer to the input WcsMap.
+*     out
+*        Pointer to the output WcsMap.
+
+*/
+
+
+/* Local Variables: */
+   int i;                        /* Axis index */
+   int latax_in;                 /* Index of input latitude axis */
+   int latax_out;                /* Index of output latitude axis */
+   int lonax_in;                 /* Index of input longitude axis */
+   int lonax_out;                /* Index of output longitude axis */
+   int nax_out;                  /* No. of axis in the output WcsMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Nullify the pointers stored in the output WcsMap since these may
+   currently be pointing at good data. Otherwise, the good data could be
+   freed by accident if the output object is deleted due to an error
+   occuring in this function. */
+   out->np = NULL;
+   out->p = NULL;
+
+/* Do nothing more if either of the input pointers are null (i.e. if there
+   are no projection parameters. */
+   if( in->np && in->p ){
+
+/* Store the number of axes in the input and output WcsMaps */
+      nax_out = astGetNin( out );
+
+/* Allocate memory for the array holding the number of projection parameters
+   associated with each axis. */
+      out->np = (int *) astMalloc( sizeof( int )*nax_out );
+
+/* Allocate memory for the array of pointers which identify the arrays
+   holding the parameter values. */
+      out->p = (double **) astMalloc( sizeof( double *)*nax_out );
+
+/* Check pointers can be used */
+      if( astOK ) {
+
+/* Initialise the above arrays. */
+         for( i = 0; i < nax_out; i++ ) {
+            (out->np)[ i ] = 0;
+            (out->p)[ i ] = NULL;
+         }
+
+/* Copy the longitude and latitude values from in to out (other axes do
+   not have projection parameters). */
+         lonax_in = astGetWcsAxis( in, 0 );
+         latax_in = astGetWcsAxis( in, 1 );
+         lonax_out = astGetWcsAxis( out, 0 );
+         latax_out = astGetWcsAxis( out, 1 );
+
+         (out->np)[ lonax_out ] = (in->np)[ lonax_in ];
+         (out->p)[ lonax_out ] = (double *) astStore( NULL,
+                                          (void *) (in->p)[ lonax_in ],
+                                          sizeof(double)*(in->np)[ lonax_in ] );
+
+         (out->np)[ latax_out ] = (in->np)[ latax_in ];
+         (out->p)[ latax_out ] = (double *) astStore( NULL,
+                                          (void *) (in->p)[ latax_in ],
+                                          sizeof(double)*(in->np)[ latax_in ] );
+      }
+
+/* If an error has occurred, free the output arrays. */
+      if( !astOK ) FreePV( out, status );
+
+   }
+
+/* Re-initialize the values stored in the "AstPrjPrm" structure. */
+   InitPrjPrm( out, status );
+
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two WcsMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     WcsMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two WcsMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a WcsMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the WcsMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstWcsMap *that;
+   AstWcsMap *this;
+   int i, j;
+   int nin;
+   int nout;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two WcsMap structures. */
+   this = (AstWcsMap *) this_object;
+   that = (AstWcsMap *) that_object;
+
+/* Check the second object is a WcsMap. We know the first is a
+   WcsMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAWcsMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two WcsMaps differ, it may still be possible
+   for them to be equivalent. First compare the WcsMaps if their Invert
+   flags are the same. In this case all the attributes of the two WcsMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+
+            if( this->type == that->type &&
+                this->wcsaxis[ 0 ] == that->wcsaxis[ 0 ] &&
+                this->wcsaxis[ 1 ] == that->wcsaxis[ 1 ] ) {
+
+               result = 1;
+
+               if( this->np && that->np ){
+
+                  for( i = 0; i < nout && result; i++ ) {
+
+                     if( (this->np)[ i ] != (that->np)[ i ] ) {
+                        result = 0;
+
+                     } else if( (this->p)[ i ] && !(this->p)[ i ] ) {
+                        result = 0;
+
+                     } else if( !(this->p)[ i ] && (this->p)[ i ] ) {
+                        result = 0;
+
+                     } else if( (this->p)[ i ] && (this->p)[ i ] ) {
+
+                        for( j = 0; j < (this->np)[ i ]; j++ ) {
+                           if( !astEQUAL( (this->p)[ i ][ j ],
+                                          (that->p)[ i ][ j ] ) ) {
+                              result = 0;
+                              break;
+                           }
+                        }
+                     }
+                  }
+               }
+
+            } else if( this->np || that->np ){
+               result = 0;
+            }
+
+/* If the Invert flags for the two WcsMaps differ, the attributes of the two
+   WcsMaps must be inversely related to each other. */
+         } else {
+
+/* In the specific case of a WcsMap, Invert flags must be equal. */
+            result = 0;
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const PrjData *FindPrjData( int type, int *status ){
+/*
+*+
+*  Name:
+*     FindPrjData
+
+*  Purpose:
+*     Get information about a WCSLIB projection given a projection type.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     const PrjData *FindPrjData( int type, int *status )
+
+*  Class Membership:
+*     WcsMap member function
+
+*  Description:
+*     This function returns a pointer to an PrjData structure describing
+*     the WCSLIB projection with the supplied type.
+
+*  Parameters:
+*     type
+*        The projection type.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the "const" PrjData structure describing the projection.
+
+*  Notes:
+*     -  The returned pointer points to an element in a static array and
+*     should not be freed.
+*     -  This function attempts to execute even if an error has already
+*     occurred. A description of a "null" projection will be returned if
+*     this function subsequently fails (for instance if the projection is
+*     not recognised).
+*-
+*/
+
+   const PrjData *data;
+   data = PrjInfo;
+   while( data->prj != AST__WCSBAD && data->prj != type ) data++;
+   return data;
+}
+
+static void FreePV( AstWcsMap *this, int *status ) {
+/*
+*
+*  Name:
+*     FreePV
+
+*  Purpose:
+*     Free memory used to hold projection parameters
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     FreePV( AstWcsMap *this, int *status )
+
+*  Class Membership:
+*     WcsMap private function
+
+*  Description:
+*     This function frees all the dynamic memory used to store projection
+*     parameter information in the supplied WcsMap.
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if an error has already occurred.
+
+*
+*/
+   int i;              /* Axis index */
+
+   if( this->np ) this->np = (int *) astFree( (void *) this->np );
+   if( this->p ){
+      for( i = 0; i < astGetNin( this ); i++ ){
+         this->p[ i ]  = (double *) astFree( (void *) this->p[ i ] );
+      }
+      this->p = (double **) astFree( (void *) this->p );
+   }
+
+/* Re-initialize the values stored in the "AstPrjPrm" structure. */
+   InitPrjPrm( this, status );
+
+
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     WcsMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied WcsMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the WcsMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstWcsMap *this;         /* Pointer to WcsMap structure */
+   int result;              /* Result value to return */
+   int i;                   /* Axis index */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the WcsMap structure. */
+   this = (AstWcsMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+
+   result += astTSizeOf( this->np );
+   if( this->p ){
+      for( i = 0; i < astGetNin( this ); i++ ){
+         result += astTSizeOf( (void *) this->p[ i ] );
+      }
+      result += astTSizeOf( this->p );
+   }
+
+   result += astTSizeOf( this->params.p );
+   result += astTSizeOf( this->params.p2 );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a WcsMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     WcsMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a WcsMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the WcsMap.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the WcsMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the WcsMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
+   AstWcsMap *this;             /* Pointer to the WcsMap structure */
+   const char *result;          /* Pointer value to return */
+   double dval;                 /* Floating point attribute value */
+   int i;                       /* Axis index */
+   int ival;                    /* Integer attribute value */
+   int len;                     /* Length of attribute string */
+   int m;                       /* Projection parameter index */
+   int nc;                      /* No. of characters read by astSscanf */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the WcsMap structure. */
+   this = (AstWcsMap *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* ProjP. */
+/* ------ */
+   if ( nc = 0, ( 1 == astSscanf( attrib, "projp(%d)%n", &m, &nc ) )
+                  && ( nc >= len ) ) {
+      dval = astGetPV( this, astGetWcsAxis( this, 1 ), m );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* PV. */
+/* --- */
+   } else if ( nc = 0, ( 2 == astSscanf( attrib, "pv%d_%d%n", &i, &m, &nc ) )
+                  && ( nc >= len ) ) {
+      dval = astGetPV( this, i - 1, m );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* WcsType */
+/* ======= */
+   } else if ( !strcmp( attrib, "wcstype" ) ) {
+      ival = astGetWcsType( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* PVMax */
+/* ===== */
+   } else if ( nc = 0, ( 1 == astSscanf( attrib, "pvmax(%d)%n", &i, &nc ) )
+                  && ( nc >= len ) ) {
+      ival = astGetPVMax( this, i - 1 );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* NatLat */
+/* ====== */
+   } else if ( !strcmp( attrib, "natlat" ) ) {
+      dval = astGetNatLat( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+/* NatLon */
+/* ====== */
+   } else if ( !strcmp( attrib, "natlon" ) ) {
+      dval = astGetNatLon( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, dval );
+         result = getattrib_buff;
+      }
+
+
+/* WcsAxis */
+/* ======= */
+   } else if ( nc = 0, ( 1 == astSscanf( attrib, "wcsaxis(%d)%n", &i, &nc ) )
+                         && ( nc >= len ) ) {
+      ival = astGetWcsAxis( this, i - 1 ) + 1;
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double GetNatLat( AstWcsMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     GetNatLat
+
+*  Purpose:
+*     Get the value of the NatLat attribute.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     double GetNatLat( AstWcsMap *this, int *status )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function returns the value of the NatLat attribute. This is
+*     fixed value for most projection types , defined in the FITS-WCS paper
+*     II. For instance, all zenithal projections have NatLat = PI/2 (90
+*     degrees). For some prjections (e.g. conics), the value is defined
+*     by a projection parameter.
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The attribute value, in radians.
+
+*-
+*/
+   double ret;     /* Returned value */
+
+/* The native latitude of the reference point of the projection is
+   constant for most projection, but for some (the conics) it is
+   specified by projection one on the latitude axis. */
+   ret = FindPrjData( this->type, status )->theta0;
+   if( ret == AST__BAD ){
+      ret = astGetPV( this, astGetWcsAxis( this, 1 ), 1 );
+      if( ret != AST__BAD ) ret *= AST__DD2R;
+   }
+
+/* Return the result. */
+   return ret;
+}
+
+static double GetNatLon( AstWcsMap *this, int *status ) {
+/*
+*+
+*  Name:
+*     GetNatLon
+
+*  Purpose:
+*     Get the value of the NatLon attribute.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     double GetNatLon( AstWcsMap *this, int *status )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function returns the value of the NatLon attribute. This is
+*     fixed value of zero for all projection types.
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The attribute value, in radians.
+
+*-
+*/
+   return 0.0;
+}
+
+static int GetNP( AstWcsMap *this, int i, int *status ) {
+/*
+*+
+*  Name:
+*     GetNP
+
+*  Purpose:
+*     Get the number of projection parameters for a specified axis.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int GetNP( AstWcsMap *this, int i, int *status )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function returns the current number of projection parameters
+*     associated with the speified axis. Some of these may be unset (i.e.
+*     equal to AST__BAD). The returned number is the size of the array
+*     holding the projection parameters.
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+*     i
+*        Zero based axis index.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of projection parameters for the specified axis.
+
+*-
+*/
+   double ret;
+
+/* Initialise */
+   ret = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Validate the axis index, and get the count. */
+   if( i >= 0 && this->np && i < astGetNin( this ) ) ret = this->np[ i ];
+
+   return ret;
+
+}
+
+static double GetPV( AstWcsMap *this, int i, int m, int *status ) {
+/*
+*+
+*  Name:
+*     astGetPV
+
+*  Purpose:
+*     Get the value of a PVi_m attribute.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     double astGetPV( AstWcsMap *this, int i, int m )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function returns the current value of a specified member of the
+*     PV attribute array. A value of AST__BAD is returned if no value has
+*     been set for the parameter.
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+*     i
+*        Zero based axis index.
+*     m
+*        Zero based parameter index.
+
+*  Returned Value:
+*     The value of the requested attribute, of AST__BAD if not set.
+
+*-
+*/
+
+/* Local Variables: */
+   double ret;
+   int npar;
+   int mxpar;
+
+/* Initialise */
+   ret = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Validate the axis index. */
+   if( i < 0 || i >= astGetNin( this ) ){
+      astError( AST__AXIIN, "astGetPV(%s): Axis index (%d) is invalid in "
+                "attribute PV%d_%d  - it should be in the range 1 to %d.",
+                status, astGetClass( this ), i + 1, i + 1, m, astGetNin( this ) );
+
+/* Find the maximum number of parameters allowed for the axis. */
+   } else {
+      mxpar = astGetPVMax( this, i );
+
+/* Validate the parameter index. */
+      if( m < 0 || m > mxpar ){
+         astError( AST__AXIIN, "astGetPV(%s): Parameter index (%d) is invalid "
+                   "in attribute PV%d_%d for a \"%s\" projection - it should be "
+                   "in the range 0 to %d.", status, astGetClass( this ), m, i + 1, m,
+                   FindPrjData( this->type, status )->ctype, mxpar );
+
+/* For latitude parameters use the values in the "params" structure which will
+   have been defaulted. */
+      } else if( i == astGetWcsAxis( this, 1 ) ) {
+         ret = (this->params).p[ m ];
+
+/* For other axes, see if the arrays stored in the WcsMap structure extend as
+   far as the requested parameter. If so, return the required attribute value.
+   Otherwise the AST__BAD value initialised above is retained. */
+      } else if( this->np && this->p ){
+         npar = this->np[ i ];
+         if( m < npar && this->p[ i ] ) ret = this->p[ i ][ m ];
+      }
+
+/* FITS-WCS paper II gives defaults for the first 3 longitude axis
+   parameters. The AST-specific TPN projection does not use this
+   convention since it needs all projection parameters to specify
+   correction terms. */
+      if( ret == AST__BAD && i == astGetWcsAxis( this, 0 ) &&
+          astGetWcsType( this ) != AST__TPN ) {
+
+/* Parameter zero has a default of zero. */
+         if( m == 0 ) {
+            ret = 0.0;
+
+/* Parameter one has a default equal to the native longitude of the
+   reference point of the projection, in degrees. */
+         } else if( m == 1 ) {
+            ret = astGetNatLon( this )*AST__DR2D;
+
+/* Parameter two has a default equal to the native latitude of the
+   reference point of the projection (in degrees). This is constant for
+   most projection, but for some (the conics) it is specified by
+   projection one on the latitude axis. */
+         } else if( m == 2 ) {
+            ret = astGetNatLat( this )*AST__DR2D;
+         }
+      }
+   }
+
+   return ret;
+
+}
+
+static int GetPVMax( AstWcsMap *this, int i, int *status ) {
+/*
+*+
+*  Name:
+*     astGetPVMax
+
+*  Purpose:
+*     Get the maximum projection parameter index for a WcsMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int astGetPVMax( AstWcsMap *this, int i )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function returns the largest legal projection parameter index
+*     for a specified axis of the given WcsMap (i.e. the largest value of
+*     "m" in the attribute "PVi_m").
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+*     i
+*        Zero based axis index.
+
+*  Returned Value:
+*     The largest legal projection parameter index, or -1 if no
+*     projection parameters are allowed on the specified axis.
+
+*-
+*/
+
+/* Local Variables: */
+   int mxpar;
+
+/* Initialise */
+   mxpar = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return -1;
+
+/* Validate the axis index. */
+   if( i < 0 || i >= astGetNin( this ) ){
+      astError( AST__AXIIN, "astGetPVMax(%s): Axis index (%d) is invalid in "
+                "attribute PVMax(%d)  - it should be in the range 1 to %d.",
+                status, astGetClass( this ), i + 1, i + 1, astGetNin( this ) );
+
+/* Find the maximum number of parameters allowed for the axis. */
+   } else if( i == astGetWcsAxis( this, 0 ) ) {
+      mxpar = astSizeOf( this->params.p2 )/sizeof( double );
+
+   } else if( i == astGetWcsAxis( this, 1 ) ) {
+      mxpar = astSizeOf( this->params.p )/sizeof( double );
+
+   }
+
+/* The mxpar variable holds the max number of parameters. Return the the
+   largest legal parameter index (one less than the max number of
+   parameters). */
+   return mxpar - 1;
+}
+
+static void InitPrjPrm( AstWcsMap *this, int *status ) {
+/*
+*  Name:
+*     InitPrjPrm
+
+*  Purpose:
+*     Initialise the WcsLib PrjPrm structure, assigning default values for
+*     missing parameters.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void InitPrjPrm( AstWcsMap *this, int *status )
+
+*  Description:
+*     This function initializes the projection parameter information
+*     stored within the WcsLib AstPrjPrm structure associated with the
+*     supplied WcsMap. Default values are assigned to any unspecified
+*     parameter values. AST__BAD values are assigned if any parameters
+*     have not been supplied for which there is no default.
+
+*  Parameters:
+*     this
+*        The WcsMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+
+/* Local Variables: */
+   struct AstPrjPrm *params;     /* The AstPrjPrm structure from the WcsMap */
+   int i;                        /* Loop index */
+   int latax;                    /* Index of latitude axis */
+   int lonax;                    /* Index of longitude axis */
+   int npar;                     /* No. of parameters supplied */
+   int plen;                     /* Length of params array */
+   int plen2;                    /* Length of latitude params array */
+   int type;                     /* Projection type */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the AstPrjPrm structure*/
+   params = &(this->params);
+
+/* Tell the routines within the WcsLib "proj.c" module  to re-calculate
+   intermediate values. */
+   params->flag = 0;
+   params->r0 = 0;
+
+/* If this is a TPN projection, indicate whether or not the
+   transformation should include the TAN projection or just the
+   polynomial transformation. */
+   if( this->type == AST__TPN ) params->n = astGetTPNTan( this );
+
+/* Find the max number of projection parameters associated with each
+   axis.*/
+   plen2 = astSizeOf( params->p2 )/sizeof( double );
+   plen = astSizeOf( params->p )/sizeof( double );
+
+/* Initially set all parameter to AST__BAD. */
+   for( i = 0; i < plen; i++ ) (params->p)[i] = AST__BAD;
+   for( i = 0; i < plen2; i++ ) (params->p2)[i] = AST__BAD;
+
+/* If the WcsMap contains any projection parameter values... */
+   if( this->np && this->p ){
+
+/* Get the index of the latitude axis. Currently, all projection
+   parameters are associated with the latitude axis (except for
+   the TPN projection, which is a hang-over from a earlier draft of the
+   FITS-WCS paper). */
+      latax = astGetWcsAxis( this, 1 );
+
+/* Find the number of projection parameters in the WcsMap for the
+   latitude axis. */
+      npar = (this->np)[ latax ];
+      if( npar > plen ) {
+         astError( AST__INTER, "InitPrjPrm(WcsMap): Too many projection "
+                   "parameters on the latitude axis (%d > %d) (internal "
+                   "AST programming error).", status, npar, plen );
+      }
+
+/* Copy the parameters to the AstPrjPrm structure. Do not copy more than
+   can be stored in the AstPrjPrm structure. */
+      for( i = 0; i < npar && i < plen; i++ ) {
+         (params->p)[ i ] = (this->p)[ latax ][ i ];
+      }
+
+/* Do the same for the longitude axis (for the benefit of the TPN projection). */
+      lonax = astGetWcsAxis( this, 0 );
+      npar = (this->np)[ lonax ];
+
+      if( npar > plen2 ) {
+         astError( AST__INTER, "InitPrjPrm(WcsMap): Too many projection "
+                   "parameters on the longitude axis (%d > %d) (internal "
+                   "AST programming error).", status, npar, plen2 );
+      }
+
+      for( i = 0; i < npar && i < plen2; i++ ) {
+         (params->p2)[ i ] = (this->p)[ lonax ][ i ];
+      }
+
+   }
+
+/* Get the projection type. */
+   type = astGetWcsType( this );
+
+/* First supply default values for any missing projection parameters which
+   do not default to zero. */
+   if( type == AST__SZP ){
+      if( (params->p)[ 3 ] == AST__BAD ) (params->p)[ 3 ] = 90.0;
+
+   } else if( type == AST__AIR ){
+      if( (params->p)[ 1 ] == AST__BAD ) (params->p)[ 1 ] = 90.0;
+
+   } else if( type == AST__CYP ){
+      if( (params->p)[ 1 ] == AST__BAD ) (params->p)[ 1 ] = 1.0;
+      if( (params->p)[ 2 ] == AST__BAD ) (params->p)[ 2 ] = 1.0;
+
+   } else if( type == AST__CEA ){
+      if( (params->p)[ 1 ] == AST__BAD ) (params->p)[ 1 ] = 1.0;
+
+   } else if( type == AST__TPN ){
+      if( (params->p)[ 1 ] == AST__BAD ) (params->p)[ 1 ] = 1.0;
+      if( (params->p2)[ 1 ] == AST__BAD ) (params->p2)[ 1 ] = 1.0;
+
+   } else if( type == AST__HPX ){
+      if( (params->p)[ 1 ] == AST__BAD ) (params->p)[ 1 ] = 4.0;
+      if( (params->p)[ 2 ] == AST__BAD ) (params->p)[ 2 ] = 3.0;
+
+   }
+
+/* Now use a default value of zero for any remaining unspecified values,
+   except for un-defaultable projection parameters. */
+   for( i = 0; i < plen; i++ ){
+
+/* Retain any AST__BAD value for these undefaultable parameters. */
+      if( i == 1 && ( type == AST__BON ||
+                     type == AST__COP || type == AST__COE ||
+                     type == AST__COD || type == AST__COO ) ){
+
+/* Use a default of zero for all other parameters. */
+      } else {
+         if( (params->p)[ i ] == AST__BAD ) (params->p)[ i ] = 0.0;
+      }
+   }
+
+/* Do the same for the latitude projection parameters (if any) */
+   for( i = 0; i < plen2; i++ ){
+      if( i == 1 && ( type == AST__BON ||
+                      type == AST__COP || type == AST__COE ||
+                      type == AST__COD || type == AST__COO ) ){
+      } else {
+         if( (params->p2)[ i ] == AST__BAD ) (params->p2)[ i ] = 0.0;
+      }
+   }
+}
+
+void astInitWcsMapVtab_(  AstWcsMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitWcsMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a WcsMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     void astInitWcsMapVtab( AstWcsMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     WcsMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the WcsMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAWcsMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->ClearPV = ClearPV;
+   vtab->GetNatLat = GetNatLat;
+   vtab->GetNatLon = GetNatLon;
+   vtab->GetPV = GetPV;
+   vtab->GetWcsAxis = GetWcsAxis;
+   vtab->GetPVMax = GetPVMax;
+   vtab->GetWcsType = GetWcsType;
+   vtab->SetPV = SetPV;
+   vtab->TestPV = TestPV;
+   vtab->IsZenithal = IsZenithal;
+
+   vtab->ClearFITSProj = ClearFITSProj;
+   vtab->TestFITSProj = TestFITSProj;
+   vtab->GetFITSProj = GetFITSProj;
+   vtab->SetFITSProj = SetFITSProj;
+
+   vtab->ClearTPNTan = ClearTPNTan;
+   vtab->TestTPNTan = TestTPNTan;
+   vtab->GetTPNTan = GetTPNTan;
+   vtab->SetTPNTan = SetTPNTan;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+   parent_mapsplit = mapping->MapSplit;
+   mapping->MapSplit = MapSplit;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+
+/* Declare the destructor and copy constructor. */
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+
+/* Declare the class dump function. */
+   astSetDump( vtab, Dump, "WcsMap", "FITS-WCS sky projection" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int IsZenithal( AstWcsMap *this, int *status ){
+/*
+*+
+*  Name:
+*     IsZenithal
+
+*  Purpose:
+*     Determine if this WcsMap represents a zenithal projection.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int IsZenithal( AstWcsMap *this, int *status )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function returns a flag indicating if this WcsMap is a zenithal
+*     projection. Some projections which are classed as zenithal in the
+*     Calabretta and Greisen paper are only genuinely zenithal if the
+*     projection parameters have certain values. These projections are
+*     not considered to be zenithal unless the projection parameters have
+*     appropriate values.
+
+*  Parameters:
+*     this
+*        The WcsMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A non-zero value if the WcsMap represents a zenithal projection.
+
+*-
+*/
+
+/* Local Variables: */
+   double p1;                    /* PVi_1 */
+   double p2;                    /* PVi_2 */
+   double p3;                    /* PVi_3 */
+   int latax;                    /* Index of latitude axis */
+   int ret;                      /* Returned flag */
+   int type;                     /* Projection type */
+
+/* Initialise the returned value. */
+   ret = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Get the projection type. */
+   type = astGetWcsType( this );
+
+/* Get the index of the latitude axis. */
+   latax = astGetWcsAxis( this, 1 );
+
+/* The following are always zenithal... */
+   if( type == AST__TAN  || type == AST__STG  || type == AST__ARC  ||
+       type == AST__ZPN  || type == AST__ZEA  || type == AST__AIR ||
+       type == AST__TPN ) {
+      ret = 1;
+
+/* The following are sometimes zenithal... */
+   } else if( type == AST__AZP ) {
+      p2 = astGetPV( this, latax, 2 );
+      if( p2 == AST__BAD || p2 == 0.0 ) ret = 1;
+
+   } else if( type == AST__SIN ) {
+      p1 = astGetPV( this, latax, 1 );
+      p2 = astGetPV( this, latax, 2 );
+      if( p1 == AST__BAD ) p1 = 0.0;
+      if( p2 == AST__BAD ) p2 = 0.0;
+      if( p1 == 0.0 && p2 == 0.0 ) ret = 1;
+
+   } else if( type == AST__SZP ) {
+      p3 = astGetPV( this, latax, 2 );
+      if( p3 == AST__BAD ) p3 = 90.0;
+      if( p3 == 90.0 || p3 == -90.0 ) ret = 1;
+
+   }
+
+   return ret;
+}
+
+static int LongRange( const PrjData *prjdata, struct AstPrjPrm *params,
+                       double *high, double *low, int *status ){
+/*
+*
+*  Name:
+*     LongRange
+
+*  Purpose:
+*     See if primary range of longitude produced by a WCSLIB mapping is
+*     [0,360] or [-180,+180].
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     void LongRange( const PrjData *prjdata, struct AstPrjPrm *params,
+*                     double *high, double *low, int *status )
+
+*  Class Membership:
+*     WcsMap internal utility function.
+
+*  Description:
+*     This function uses the WCSLIB library to transform the supplied input
+*     positions.
+
+*  Parameters:
+*     prjdata
+*        A pointer to information about the mapping.
+*     params
+*        Pointer to a WCSLIB "AstPrjPrm" structure containing the projection
+*        parameters, etc.
+*     high
+*        A pointer to a location at which is returned the upper bound of
+*        the primary longitude range.
+*     low
+*        A pointer to a location at which is returned the lower bound of
+*        the primary longitude range.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A flag indicating if the sky->xy transformation is cyclic (i.e.
+*     [a,0] gets mapped to the same (x,y) position as [a+2.PI,0]).
+*/
+
+
+/* Local Variables: */
+   int point;                    /* Loop counter for points */
+   static double xx[ 4 ] = { -1.0E-6,    0.0, 1.0E-6,     0.0 };
+   static double yy[ 4 ] = {     0.0, 1.0E-6,    0.0, -1.0E-6 };
+   double aa;
+   double bb;
+   double xxx[ 2 ];
+   double yyy[ 2 ];
+   int cyclic;
+
+/* Initialise the returned values. */
+   *high = 180.0;
+   *low = -180.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Project each of the points. If any longitude value is found which is
+   greater than 180 degrees, return [0,360] as the longitude range. */
+   for( point = 0; point < 4; point++ ){
+      if( !prjdata->WcsRev( xx[ point ], yy[ point ], params, &aa, &bb ) ){
+         if( aa > 180.0 ){
+            *high = 360.0;
+            *low = 0.0;
+            break;
+         }
+      }
+   }
+
+/* See if the projection is cyclic. Transform the sky positions [90,bb] and
+   [450,bb] into cartesian positions and see if they are the same. */
+   prjdata->WcsFwd( 90.0, bb, params, xxx, yyy );
+   prjdata->WcsFwd( 450.0, bb, params, xxx + 1, yyy + 1 );
+   cyclic = ( fabs( xxx[ 0 ] - xxx[ 1 ] ) < 1.0E-10 &&
+              fabs( yyy[ 0 ] - yyy[ 1 ] ) < 1.0E-10 );
+
+/* Return. */
+   return cyclic;
+
+}
+
+static int Map( AstWcsMap *this, int forward, int npoint, double *in0,
+                double *in1, double *out0, double *out1, int *status ){
+/*
+*
+*  Name:
+*     Map
+
+*  Purpose:
+*     Transform a set of points using a function from the WCSLIB library.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int Map( AstWcsMap *this, int forward, int npoint, double *in0,
+*              double *in1, double *out0, double *out1 )
+
+*  Class Membership:
+*     WcsMap internal utility function.
+
+*  Description:
+*     This function uses the WCSLIB library to transform the supplied input
+*     positions.
+
+*  Parameters:
+*     this
+*        Pointer to the WcsMap.
+*     forward
+*        A non-zero value indicates that the forward projection from
+*        (long,lat) to (x,y) is required, while a zero value requests the
+*        reverse transformation.
+*     npoint
+*        The number of points to transform (i.e. the size of the
+*        in0, in1, out0 and out1 arrays).
+*     in0
+*        A pointer to the input coordinate data for the 0th axis (i.e.
+*        longitude or X depending on "forward").
+*     in1
+*        A pointer to the input coordinate data for the 1st axis (i.e.
+*        latitude or Y depending on "forward").
+*     out0
+*        A pointer to the returned output coordinate data for the 0th axis
+*        (i.e. X or longitude depending on "forward").
+*     out1
+*        A pointer to the returned output coordinate data for the 1st axis
+*        (i.e. Y or latitude depending on "forward").
+
+*  Returned Value:
+*     The status value: 0 - Success
+*                       1 - Unrecognised projection type
+*                       2 - Invalid projection parameters values.
+*                       4 - Error existed on entry
+*                       100 - 399: Longitude axis projection parameter
+*                           (status-100) not supplied.
+*                       400 - 699: Latitude axis projection parameter
+*                           (status-400) not supplied.
+
+
+*  Notes:
+*     -  This function does not report any errors. Reporting of suitable
+*     error messages is the responsibility of the calling function.
+*     -  The value 4 will be returned if this function is invoked with the
+*     global error status set.
+*
+*/
+
+/* Local Variables: */
+   const PrjData *prjdata;       /* Information about the projection */
+   double factor;                /* Factor that scales input into radians. */
+   double latitude;              /* Latitude value in degrees */
+   double longhi;                /* Upper longitude limit in degrees */
+   double longitude;             /* Longitude value in degrees */
+   double longlo;                /* Lower longitude limit in degrees */
+   double x;                     /* X Cartesian coordinate in degrees */
+   double y;                     /* Y Cartesian coordinate in degrees */
+   int cyclic;                   /* Is sky->xy transformation cyclic? */
+   int i;                        /* Loop count */
+   int plen;                     /* Length of proj par array */
+   int point;                    /* Loop counter for points */
+   int type;                     /* Projection type */
+   int wcs_status;               /* Status from WCSLIB functions */
+   struct AstPrjPrm *params;     /* Pointer to structure holding WCSLIB info */
+
+/* Check the global error status. */
+   if ( !astOK ) return 4;
+
+/* Initialise variables to avoid compiler warnings. */
+   longlo = AST__BAD;
+   longhi = AST__BAD;
+
+/* Store the projection type. */
+   type = astGetWcsType( this );
+
+/* Get information about the projection. */
+   prjdata = FindPrjData( type, status );
+
+/* Return if there are no WcsLib mapping functons associated with the
+   projection. */
+   if( ( !prjdata->WcsFwd && forward ) ||
+       ( !prjdata->WcsRev && !forward ) ) return 1;
+
+/* Check that all necessary projection parameters have been supplied, or
+   can be defaulted. */
+   params = &(this->params);
+   plen = astSizeOf( params->p )/sizeof( double );
+   for( i = 0; i < plen; i++ ) {
+      if( ( params->p)[ i ] == AST__BAD ) return 400+i;
+   }
+
+/* If we are doing a reverse mapping, get the acceptable range of longitude
+   values. */
+   cyclic = forward ? 0 : LongRange( prjdata, params, &longhi, &longlo,
+                                     status );
+
+/* The WcsMap input and output values are normally in radians, but if
+   the TPNTan attribute has been reset then they are in degrees. The
+   WCSLIB projection functions always expect and return degrees. Get
+   the factor that scales the WcsMap input into radians. */
+   factor = astGetTPNTan( this ) ? 1.0 : AST__DD2R;
+
+/* Loop to apply the projection to each point in turn, checking for
+   (and propagating) bad values in the process. */
+   for ( point = 0; point < npoint; point++ ) {
+       if ( in0[ point ] == AST__BAD ||
+           in1[ point ] == AST__BAD ){
+          out0[ point ] = AST__BAD;
+          out1[ point ] = AST__BAD;
+       } else {
+
+/* First deal with forward projection calls */
+         if ( forward ){
+
+/* The input coordinates are assumed to be longitude and latitude, in
+   radians or degrees (as specified by the TPNTan attribute). Convert them
+   to degrees ensuring that the longitude value is in the range [-180,180]
+   and the latitude is in the range [-90,90] (as required by the WCSLIB
+   library). Any point with a latitude outside the range [-90,90] is
+   converted to the equivalent point on the complementary meridian. */
+            latitude = AST__DR2D*palDrange(  factor*in1[ point ] );
+            if ( latitude > 90.0 ){
+               latitude = 180.0 - latitude;
+               longitude = AST__DR2D*palDrange( AST__DPI + factor*in0[ point ] );
+
+            } else if ( latitude < -90.0 ){
+               latitude = -180.0 - latitude;
+               longitude = AST__DR2D*palDrange( AST__DPI + factor*in0[ point ] );
+
+            } else {
+               longitude = AST__DR2D*palDrange( factor*in0[ point ] );
+            }
+
+/* Call the relevant WCSLIB forward projection function. */
+            wcs_status = prjdata->WcsFwd( longitude, latitude, params, &x, &y );
+
+/* Store the returned Cartesian coordinates, converting them from degrees
+   to radians. If the position could not be projected, use the value
+   AST__BAD.  Abort for any other bad status. */
+            if( wcs_status == 0 ){
+               out0[ point ] = (AST__DD2R/factor)*x;
+               out1[ point ] = (AST__DD2R/factor)*y;
+
+            } else if( wcs_status == 1 ){
+               return 2;
+
+            } else if( wcs_status == 2 ){
+               out0[ point ] = AST__BAD;
+               out1[ point ] = AST__BAD;
+
+            } else {
+               return wcs_status;
+            }
+
+/* Now deal with reverse projection calls */
+         } else {
+
+/* Convert the supplied Cartesian coordinates from radians to degrees. */
+            x = (AST__DR2D*factor)*in0[ point ];
+            y = (AST__DR2D*factor)*in1[ point ];
+
+/* Call the relevant WCSLIB reverse projection function. */
+            wcs_status = prjdata->WcsRev( x, y, params, &longitude, &latitude );
+
+/* Store the returned longitude and latitude, converting them from degrees
+   to radians. Many projections (ARC, AIT, ZPN, etc) are not cyclic (i.e.
+   [long,lat]=[0,0] does not get mapped to the same place as
+   [long,lat]=[360,0] ). Only accept values in the primary longitude or
+   latitude ranges. This avoids (x,y) points outside the physical domain
+   of the mapping being assigned valid (long,lat) values. */
+            if( wcs_status == 0 ){
+               if( ( cyclic || ( longitude < longhi &&
+                                 longitude >= longlo ) ) &&
+                   fabs( latitude ) <= 90.0 ){
+
+                  out0[ point ] = (AST__DD2R/factor)*longitude;
+                  out1[ point ] = (AST__DD2R/factor)*latitude;
+
+               } else {
+                  out0[ point ] = AST__BAD;
+                  out1[ point ] = AST__BAD;
+               }
+
+/* Abort if projection parameters were unusable. */
+            } else if( wcs_status == 1 ){
+               return 2;
+
+/* If the position could not be projected, use the value AST__BAD. */
+            } else if( wcs_status == 2 ){
+               out0[ point ] = AST__BAD;
+               out1[ point ] = AST__BAD;
+
+/* Abort if projection parameters were not supplied. */
+            } else {
+               return wcs_status;
+            }
+
+         }
+
+      }
+
+   }
+
+   return 0;
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a WcsMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     WcsMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated WcsMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated WcsMap with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated WcsMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the WcsMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        WcsMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated WcsMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *mc[2];    /* Copies of supplied Mappings to swap */
+   AstMapping *smc0;     /* Simplied Mapping */
+   AstMapping *smc1;     /* Simplied Mapping */
+   const char *nclass;   /* Pointer to neighbouring Mapping class */
+   const char *class1;   /* Pointer to first Mapping class string */
+   const char *class2;   /* Pointer to second Mapping class string */
+   int do1;              /* Would a backward swap make a simplification? */
+   int do2;              /* Would a forward swap make a simplification? */
+   int i1;               /* Lower index of the two WcsMaps being merged */
+   int i2;               /* Upper index of the two WcsMaps being merged */
+   int i;                /* Mapping index */
+   int ic[2];            /* Copies of supplied invert flags to swap */
+   int merge;            /* Can WcsMap merge with a neighbour? */
+   int nin;              /* Number of coordinates for WcsMap */
+   int nstep1;           /* No. of Mappings backwards to next mergable Mapping */
+   int nstep2;           /* No. of Mappings forward to next mergable Mapping */
+   int result;           /* Result value to return */
+   int swaphi;           /* Can WcsMap be swapped with higher neighbour? */
+   int swaplo;           /* Can WcsMap be swapped with lower neighbour? */
+   int type;             /* Projection type */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   i1 = 0;
+   i2 = 0;
+
+/* Get the number of axes for the WcsMap. */
+   nin = astGetNin( ( *map_list )[ where ] );
+
+/* First of all, see if the WcsMap can be replaced by a simpler Mapping,
+   without reference to the neighbouring Mappings in the list.           */
+/* ======================================================================*/
+/* WcsMaps with map type of AST__WCSBAD are equivalent to a UnitMap. */
+   type = astGetWcsType( this );
+   if( type == AST__WCSBAD ){
+
+/* Annul the WcsMap pointer in the list and replace it with a UnitMap
+   pointer, and indicate that the forward transformation of the returned
+   UnitMap should be used. */
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = (AstMapping *) astUnitMap( nin, "", status );
+      ( *invert_list )[ where ] = 0;
+
+/* Return the index of the first modified element. */
+      result = where;
+
+/* If the WcsMap itself could not be simplified, see if it can be merged
+   with the Mappings on either side of it in the list. This can only be
+   done in series for a WcsMap. */
+/* ===================================================================== */
+   } else if( series && *nmap > 1 ) {
+
+/* Store the classes of the neighbouring Mappings in the list. */
+      class1 = ( where > 0 ) ? astGetClass( ( *map_list )[ where - 1 ] ) : NULL;
+      class2 = ( where < *nmap - 1 ) ? astGetClass( ( *map_list )[ where + 1 ] ) : NULL;
+
+/* A WcsMap can only combine with its own inverse. Set a flag indicating
+   that we have not yet found a neighbour with which the WcsMap can be
+   merged. */
+      merge = 0;
+
+/* First check the lower neighbour (if any). */
+      if( where > 0 ) {
+         i1 = where - 1;
+         i2 = where;
+         merge = CanMerge( ( *map_list )[ i1 ], (* invert_list)[ i1 ],
+                           ( *map_list )[ i2 ], (* invert_list)[ i2 ], status );
+      }
+
+/* If the WcsMap can not be merged with its lower neighbour, check its
+   upper neighbour (if any) in the same way. */
+      if( !merge && where < *nmap - 1 ) {
+         i1 = where;
+         i2 = where + 1;
+         merge = CanMerge( ( *map_list )[ i1 ], (* invert_list)[ i1 ],
+                           ( *map_list )[ i2 ], (* invert_list)[ i2 ], status );
+      }
+
+/* If either neighbour has passed these checks, it is the inverse of the
+   WcsMap being checked. The pair of WcsMaps can be replaced by a single
+   UnitMap. */
+      if( merge ) {
+
+/* Annul the two WcsMaps. */
+         (void) astAnnul( ( *map_list )[ i1 ] );
+         (void) astAnnul( ( *map_list )[ i2 ] );
+
+/* Create a UnitMap, and store a pointer for it in place of the first
+   WcsMap. */
+         ( *map_list )[ i1 ] = (AstMapping *) astUnitMap( nin, "", status );
+         ( *invert_list )[ i1 ] = 0;
+
+/* Shuffle down the remaining Mappings to fill the hole left by the
+   second WcsMap. */
+         for ( i = i2 + 1; i < *nmap; i++ ) {
+            ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+            ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+         }
+
+/* Clear the vacated element at the end. */
+         ( *map_list )[ *nmap - 1 ] = NULL;
+         ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+         (*nmap)--;
+         result = i1;
+
+/* If the WcsMap could not merge directly with either of its neighbours,
+   we consider whether it would be worthwhile to swap the WcsMap with
+   either of its neighbours. This can only be done for certain PermMaps,
+   and will usually require both Mappings to be modified (unless they are
+   commutative). The advantage of swapping the order of the Mappings is that
+   it may result in the WcsMap being adjacent to a Mapping with which it can
+   merge directly on the next invocation of this function, thus reducing the
+   number of Mappings in the list. */
+      } else {
+
+/* Set a flag if we could swap the WcsMap with its higher neighbour. "do2"
+   is returned if swapping the Mappings would simplify either of the
+   Mappings. */
+         if( where + 1 < *nmap ){
+            swaphi = CanSwap(  ( *map_list )[ where ],
+                               ( *map_list )[ where + 1 ],
+                               ( *invert_list )[ where ],
+                               ( *invert_list )[ where + 1 ], &do2, status );
+         } else {
+            do2 = 0;
+            swaphi = 0;
+         }
+
+/* If so, step through each of the Mappings which follow the WcsMap,
+   looking for a Mapping with which the WcsMap could merge directly. Stop
+   when such a Mapping is found, or if a Mapping is found with which the
+   WcsMap could definitely not swap. Note the number of Mappings which
+   separate the WcsMap from the Mapping with which it could merge (if
+   any). */
+         nstep2 = -1;
+         if( swaphi ){
+            for( i2 = where + 1; i2 < *nmap; i2++ ){
+
+/* See if we can merge with this Mapping. If so, note the number of steps
+   between the two Mappings and leave the loop. */
+               if( CanMerge( ( *map_list )[ i2 ], ( *invert_list )[ i2 ],
+                             ( *map_list )[ where ], ( *invert_list )[ where ], status ) ) {
+                  nstep2 = i2 - where - 1;
+                  break;
+               }
+
+/* If there is no chance that we can swap with this Mapping, leave the loop
+   with -1 for the number of steps to indicate that no merging is possible.
+   WcsMaps can swap only with some PermMaps. */
+               nclass = astGetClass( ( *map_list )[ i2 ] );
+               if( strcmp( nclass, "PermMap" ) ) {
+                  break;
+               }
+
+            }
+
+         }
+
+/* Do the same working forward from the WcsMap towards the start of the map
+   list. */
+         if( where > 0 ){
+            swaplo = CanSwap(  ( *map_list )[ where - 1 ],
+                               ( *map_list )[ where ],
+                               ( *invert_list )[ where - 1 ],
+                               ( *invert_list )[ where ], &do1, status );
+         } else {
+            do1 = 0;
+            swaplo = 0;
+         }
+
+         nstep1 = -1;
+         if( swaplo ){
+            for( i1 = where - 1; i1 >= 0; i1-- ){
+
+               if( CanMerge( ( *map_list )[ i1 ], ( *invert_list )[ i1 ],
+                             ( *map_list )[ where ], ( *invert_list )[ where ], status ) ) {
+                  nstep1 = where - 1 - i1;
+                  break;
+               }
+
+               nclass = astGetClass( ( *map_list )[ i1 ] );
+               if( strcmp( nclass, "PermMap" ) ) {
+                  break;
+               }
+
+            }
+
+         }
+
+/* Choose which neighbour to swap with so that the WcsMap moves towards the
+   nearest Mapping with which it can merge. */
+         if( do1 || (
+             nstep1 != -1 && ( nstep2 == -1 || nstep2 > nstep1 ) ) ){
+            nclass = class1;
+            i1 = where - 1;
+            i2 = where;
+         } else if( do2 || nstep2 != -1 ){
+            nclass = class2;
+            i1 = where;
+            i2 = where + 1;
+         } else {
+            nclass = NULL;
+         }
+
+/* If there is a target Mapping in the list with which the WcsMap could
+   merge, replace the supplied Mappings with swapped Mappings to bring a
+   WcsMap closer to the target Mapping. */
+         if( nclass ){
+
+            WcsPerm( (*map_list) + i1, (*invert_list) + i1, where - i1, status );
+
+/* Store the index of the first modified Mapping. */
+            result = i1;
+
+/* If there is no Mapping available for merging, it may still be
+   advantageous to swap with a neighbour because the swapped Mapping may
+   be simpler than the original Mappings. */
+         } else if( swaphi || swaplo ) {
+
+/*  Choose a neightbour to swap with. If both are suitable for swapping,
+    swap with the lower. */
+            if( swaplo ){
+               nclass = class1;
+               i1 = where - 1;
+               i2 = where;
+            } else {
+               nclass = class2;
+               i1 = where;
+               i2 = where + 1;
+            }
+
+/* Take copies of the Mapping and Invert flag arrays so we do not change
+   the supplied values. */
+            mc[ 0 ] = (AstMapping *) astCopy( ( (*map_list) + i1 )[0] );
+            mc[ 1 ] = (AstMapping *) astCopy( ( (*map_list) + i1 )[1] );
+            ic[ 0 ] = ( (*invert_list) + i1 )[0];
+            ic[ 1 ] = ( (*invert_list) + i1 )[1];
+
+/* Swap these Mappings. */
+            WcsPerm( mc, ic, where - i1, status );
+
+/* If neither of the swapped Mappings can be simplified further, then there
+   is no point in swapping the Mappings, so just annul the map copies. */
+            smc0 = astSimplify( mc[0] );
+            smc1 = astSimplify( mc[1] );
+
+            if( astGetClass( smc0 ) == astGetClass( mc[0] ) &&
+                astGetClass( smc1 ) == astGetClass( mc[1] ) ) {
+
+               mc[ 0 ] = (AstMapping *) astAnnul( mc[ 0 ] );
+               mc[ 1 ] = (AstMapping *) astAnnul( mc[ 1 ] );
+
+/* If one or both of the swapped Mappings could be simplified, then annul
+   the supplied Mappings and return the swapped mappings, storing the index
+   of the first modified Mapping. */
+            } else {
+               (void ) astAnnul( ( (*map_list) + i1 )[0] );
+               (void ) astAnnul( ( (*map_list) + i1 )[1] );
+
+               ( (*map_list) + i1 )[0] = mc[ 0 ];
+               ( (*map_list) + i1 )[1] = mc[ 1 ];
+
+               ( (*invert_list) + i1 )[0] = ic[ 0 ];
+               ( (*invert_list) + i1 )[1] = ic[ 1 ];
+
+               result = i1;
+
+            }
+
+/* Annul the simplied Mappings */
+            smc0 = astAnnul( smc0 );
+            smc1 = astAnnul( smc1 );
+
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     WcsMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     WcsMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing WcsMap. This is only possible if the specified inputs
+*     correspond to some subset of the WcsMap outputs. That is, there
+*     must exist a subset of the WcsMap outputs for which each output
+*     depends only on the selected WcsMap inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied WcsMap, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the WcsMap to be split (the WcsMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied WcsMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied WcsMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied WcsMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstWcsMap *newwcs;         /* Pointer to returned WcsMap */
+   AstWcsMap *this;           /* Pointer to WcsMap structure */
+   int *result;               /* Pointer to returned array */
+   int *inperm;               /* Input axis permutation array */
+   int *outperm;              /* Output axis permutation array */
+   int i;                     /* Loop count */
+   int iin;                   /* Mapping input index */
+   int ilat;                  /* Index of latitude axis in new WcsMap */
+   int ilatlon;               /* Index of last lat or lon axis */
+   int ilon;                  /* Index of longitude axis in new WcsMap */
+   int latax;                 /* Index of latitude axis in supplied WcsMap */
+   int lonax;                 /* Index of longitude axis in supplied WcsMap */
+   int mnin;                  /* No. of Mapping inputs */
+   int ok;                    /* Are input indices OK? */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the parent astMapSplit method to see if it can do the job. */
+   result = (*parent_mapsplit)( this_map, nin, in, map, status );
+
+/* If not, we provide a special implementation here. */
+   if( !result ) {
+
+/* Get a pointer to the WcsMap structure. */
+      this = (AstWcsMap *) this_map;
+
+/* Prevent compiler warnings. */
+      ilatlon = -1;
+
+/* Allocate memory for the returned array. */
+      result = astMalloc( sizeof( int )*(size_t) nin );
+      if( astOK ) {
+
+/* Get the indices of the longitude and latitude axes in the WcsMap */
+         lonax = astGetWcsAxis( this, 0 );
+         latax = astGetWcsAxis( this, 1 );
+
+/* See if the selected axes include the longitude and/or latitude axis.
+   At the same time check the axis indices are ok, and set up the output
+   axis array. */
+         ilat = -1;
+         ilon = -1;
+         mnin = astGetNin( this );
+         ok = 1;
+         for( i = 0; i < nin; i++ ) {
+            iin = in[ i ];
+            if( iin < 0 || iin >= mnin ) {
+               ok = 0;
+               break;
+            } else if( iin == lonax ) {
+               ilon = i;
+               ilatlon = i;
+            } else if( iin == latax ) {
+               ilat = i;
+               ilatlon = i;
+            }
+            result[ i ] = iin;
+         }
+
+/* If any of the input indices were invalid, free the returned array. */
+         if( !ok ) {
+            result = astFree( result );
+
+/* If both longitude and latitude axes are selected, then the returned Mapping
+   is a WcsMap. Create one based on the supplied WcsMap. */
+         } else if( ilat != -1 && ilon != -1 ) {
+            newwcs = astWcsMap( nin, astGetWcsType( this ), ilon + 1, ilat + 1,
+                                "", status );
+            CopyPV( this, newwcs, status );
+            astSetInvert( newwcs, astGetInvert( this ) );
+            *map = (AstMapping *) newwcs;
+
+/* If neither the longitude nor the latitude axis has been selected, then
+   the returned Mapping is a UnitMap. */
+         } else if( ilat == -1 && ilon == -1 ) {
+            *map = (AstMapping *) astUnitMap( nin, "", status );
+
+/* If only one of the latitude and longitude axes was selected we remove
+   it from the returned Mapping (a PermMap) and list of outputs */
+         } else if( nin > 1 ) {
+
+            for( i = ilatlon; i < nin - 1; i++ ) {
+               result[ i ] = result[ i + 1 ];
+            }
+            result[ i ] = -1;
+
+            inperm = astMalloc( sizeof( int )*(size_t) nin );
+            outperm = astMalloc( sizeof( int )*(size_t) ( nin - 1 ) );
+            if( outperm ) {
+               for( i = 0; i < ilatlon; i++ ) {
+                  inperm[ i ] = i;
+                  outperm[ i ] = i;
+               }
+               inperm[ ilatlon ] = INT_MAX;
+               for( i = ilatlon + 1; i < nin; i++ ) {
+                  inperm[ i ] = i - 1;
+                  outperm[ i - 1 ] = i;
+               }
+
+               *map = (AstMapping *) astPermMap( nin, inperm, nin - 1, outperm, NULL, " ", status );
+
+            }
+            inperm = astFree( inperm );
+            outperm = astFree( outperm );
+
+         } else {
+            result = astFree( result );
+         }
+      }
+   }
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static void PermGet( AstPermMap *map, int **outperm, int **inperm,
+                     double **consts, int *status ){
+/*
+*  Name:
+*     PermGet
+
+*  Purpose:
+*     Get the axis permutation and constants array for a PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     void PermGet( AstPermMap *map, int **outperm, int **inperm,
+*                   double **const, int *status )
+
+*  Class Membership:
+*     WcsMap member function
+
+*  Description:
+*     This function returns axis permutation and constants arrays which can
+*     be used to create a PermMap which is equivalent to the supplied PermMap.
+
+*  Parameters:
+*     map
+*        The PermMap.
+*     outperm
+*        An address at which to return a popinter to an array of ints
+*        holding the output axis permutation array. The array should be
+*        released using astFree when no longer needed.
+*     inperm
+*        An address at which to return a popinter to an array of ints
+*        holding the input axis permutation array. The array should be
+*        released using astFree when no longer needed.
+*     consts
+*        An address at which to return a popinter to an array of doubles
+*        holding the constants array. The array should be released using
+*        astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  NULL pointers are returned if an error has already occurred, or if
+*     this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;       /* PointSet holding input positions for PermMap */
+   AstPointSet *pset2;       /* PointSet holding output positions for PermMap */
+   double **ptr1;            /* Pointer to pset1 data */
+   double **ptr2;            /* Pointer to pset2 data */
+   double *cnst;             /* Pointer to constants array */
+   double cn;                /* Potential new constant value */
+   double ip;                /* Potential output axis index */
+   double op;                /* Potential input axis index */
+   int *inprm;               /* Pointer to input axis permutation array */
+   int *outprm;              /* Pointer to output axis permutation array */
+   int i;                    /* Axis count */
+   int nc;                   /* Number of constants stored so far */
+   int nin;                  /* No. of input coordinates for the PermMap */
+   int nout;                 /* No. of output coordinates for the PermMap */
+
+/* Initialise. */
+   if( outperm ) *outperm = NULL;
+   if( inperm ) *inperm = NULL;
+   if( consts ) *consts = NULL;
+
+/* Check the global error status and the supplied pointers. */
+   if ( !astOK || !outperm || !inperm || !consts ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   nc = 0;
+
+/* Get the number of input and output axes for the supplied PermMap. */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+
+/* Allocate the memory for the returned arrays. */
+   outprm = (int *) astMalloc( sizeof( int )* (size_t) nout );
+   inprm = (int *) astMalloc( sizeof( int )* (size_t) nin );
+   cnst = (double *) astMalloc( sizeof( double )* (size_t) ( nout + nin ) );
+
+/* Returned the pointers to these arrays.*/
+   *outperm = outprm;
+   *inperm = inprm;
+   *consts = cnst;
+
+/* Create two PointSets, each holding two points, which can be used for
+   input and output positions with the PermMap. */
+   pset1 = astPointSet( 2, nin, "", status );
+   pset2 = astPointSet( 2, nout, "", status );
+
+/* Set up the two input positions to be [0,1,2...] and [-1,-1,-1,...]. The
+   first position is used to enumerate the axes, and the second is used to
+   check for constant axis values. */
+   ptr1 = astGetPoints( pset1 );
+   if( astOK ){
+      for( i = 0; i < nin; i++ ){
+         ptr1[ i ][ 0 ] = ( double ) i;
+         ptr1[ i ][ 1 ] = -1.0;
+      }
+   }
+
+/* Use the PermMap to transform these positions in the forward direction. */
+   (void) astTransform( map, pset1, 1, pset2 );
+
+/* Look at the mapped positions to determine the output axis permutation
+   array. */
+   ptr2 = astGetPoints( pset2 );
+   if( astOK ){
+
+/* No constant axis valeus found yet. */
+      nc = 0;
+
+/* Do each output axis. */
+      for( i = 0; i < nout; i++ ){
+
+/* If the output axis value is copied from an input axis value, the index
+   of the appropriate input axis will be in the mapped first position. */
+         op = ptr2[ i ][ 0 ];
+
+/* If the output axis value is assigned a constant value, the result of
+   mapping the two different input axis values will be the same. */
+         cn = ptr2[ i ][ 1 ];
+         if( op == cn ) {
+
+/* We have found another constant. Store it in the constants array, and
+   store the index of the constant in the output axis permutation array. */
+            cnst[ nc ] = cn;
+            outprm[ i ] = -( nc + 1 );
+            nc++;
+
+/* If the output axis values are different, then the output axis value
+   must be copied from the input axis value. */
+         } else {
+            outprm[ i ] = (int) ( op + 0.5 );
+         }
+      }
+   }
+
+/* Now do the same thing to determine the input permutation array. */
+   if( astOK ){
+      for( i = 0; i < nout; i++ ){
+         ptr2[ i ][ 0 ] = ( double ) i;
+         ptr2[ i ][ 1 ] = -1.0;
+      }
+   }
+
+   (void) astTransform( map, pset2, 0, pset1 );
+
+   if( astOK ){
+
+      for( i = 0; i < nin; i++ ){
+
+         ip = ptr1[ i ][ 0 ];
+         cn = ptr1[ i ][ 1 ];
+         if( ip == cn ) {
+
+            cnst[ nc ] = cn;
+            inprm[ i ] = -( nc + 1 );
+            nc++;
+
+         } else {
+            inprm[ i ] = (int) ( ip + 0.5 );
+         }
+      }
+   }
+
+/* Annul the PointSets. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* If an error has occurred, attempt to free the returned arrays. */
+   if( !astOK ) {
+      *outperm = (int *) astFree( (void *) *outperm );
+      *inperm = (int *) astFree( (void *) *inperm );
+      *consts = (double *) astFree( (void *) *consts );
+   }
+
+/* Return. */
+   return;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a WcsMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     WcsMap member function (over-rides the astSetAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a WcsMap, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the WcsMap.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstWcsMap *this;              /* Pointer to the WcsMap structure */
+   double dval;                  /* Attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+   int i;                        /* Axis index */
+   int m;                        /* Projection parameter number */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the WcsMap structure. */
+   this = (AstWcsMap *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* ProjP(i). */
+/* --------- */
+   if ( nc = 0, ( 2 == astSscanf( setting, "projp(%d)= %lg %n", &m, &dval, &nc ) )
+                  && ( nc >= len ) ) {
+      astSetPV( this, astGetWcsAxis( this, 1 ), m, dval );
+
+/* PV. */
+/* --- */
+   } else if ( nc = 0, ( 3 == astSscanf( setting, "pv%d_%d= %lg %n", &i, &m, &dval, &nc ) )
+                  && ( nc >= len ) ) {
+      astSetPV( this, i - 1, m, dval );
+
+/* Define macros to see if the setting string matches any of the
+   read-only attributes of this class. */
+#define MATCH(attrib) \
+        ( nc = 0, ( 0 == astSscanf( setting, attrib "=%*[^\n]%n", &nc ) ) && \
+                  ( nc >= len ) )
+
+#define MATCH2(attrib) \
+        ( nc = 0, ( 1 == astSscanf( setting, attrib "(%d)=%*[^\n]%n", &i, &nc ) ) && \
+                  ( nc >= len ) )
+
+/* If the attribute was not recognised, use this macro to report an error
+   if a read-only attribute has been specified. */
+   } else if ( MATCH( "wcstype" ) ||
+        MATCH( "natlat" ) ||
+        MATCH( "natlon" ) ||
+        MATCH2( "wcsaxis" ) ) {
+      astError( AST__NOWRT, "astSet: The setting \"%s\" is invalid for a %s.", status,
+                setting, astGetClass( this ) );
+      astError( AST__NOWRT, "This is a read-only attribute." , status);
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+
+/* Undefine macros local to this function. */
+#undef MATCH
+#undef MATCH2
+}
+
+static void SetPV( AstWcsMap *this, int i, int m, double val, int *status ) {
+/*
+*+
+*  Name:
+*     astSetPV
+
+*  Purpose:
+*     Set the value of a PVi_m attribute.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     void astSetPV( AstWcsMap *this, int i, int m, double val )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function stores a value for the specified member of the PV
+*     attribute array.
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+*     i
+*        Zero based axis index.
+*     m
+*        Zero based parameter index.
+
+*-
+*/
+/* Local Variables: */
+   int naxis;       /* No. of axes in WcsMap */
+   int mm;          /* Loop count */
+   int mxpar;       /* Max number of parameters allowed for the axis */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Find the number of axes in the WcsMap. */
+   naxis = astGetNin( this );
+
+/* Report an error if the object has been cloned (i.e. has a reference
+   count that is greater than one). */
+   if( astGetRefCount( this ) > 1 ) {
+      astError( AST__IMMUT, "astSet(%s): Projection parameter values "
+                "within the supplied %s cannot be changed because the %s has "
+                "been cloned (programming error).", status,
+                astGetClass(this), astGetClass(this), astGetClass(this) );
+
+/* Validate the axis index. */
+   } else if( i < 0 || i >= naxis ){
+      astError( AST__AXIIN, "astSetPV(%s): Axis index (%d) is invalid in "
+                "attribute PV%d_%d  - it should be in the range 1 to %d.",
+                status, astGetClass( this ), i + 1, i + 1, m, naxis );
+
+/* Validate the parameter index. */
+   } else {
+      mxpar = astGetPVMax( this, i );
+      if( m < 0 || m > mxpar ){
+         astError( AST__AXIIN, "astSetPV(%s): Parameter index (%d) is invalid "
+                   "in attribute PV%d_%d for a \"%s\" projection - it should be "
+                   "in the range 0 to %d.", status, astGetClass( this ), m, i + 1, m,
+                   FindPrjData( this->type, status )->ctype, mxpar );
+
+/* If the dynamic arrays used to hold the parameters have not yet been
+   created, create them now, and store pointers to them in the WcsMap
+   structure. */
+      } else {
+         if( !this->np || !this->p ) {
+            this->np = (int *) astMalloc( sizeof(int)*naxis );
+            this->p = (double **) astMalloc( sizeof(double *)*naxis );
+            if( astOK ) {
+               for( mm = 0; mm < naxis; mm++ ) {
+                  this->np[ mm ] = 0;
+                  this->p[ mm ] = NULL;
+               }
+            }
+
+/* Release the dynamic arrays if an error has occurred. */
+            if( !astOK ) FreePV( this, status );
+
+         }
+      }
+
+/* Check we can use the arrays. */
+      if( astOK ) {
+
+/* Ensure the dynamic array used to hold parameter values for the
+   specified axis is big enough to hold the specified parameter. */
+         this->p[ i ] = (double *) astGrow( (void *) this->p[ i ],
+                                            m + 1, sizeof(double) );
+
+/* Check we can use this array. */
+         if( astOK ) {
+
+/* Store the supplied value in the relevant element of this array. */
+            this->p[ i ][ m ] = val;
+
+/* If the array was extended to hold this parameter... */
+            if( this->np[ i ] <= m ) {
+
+/* Fill any other new elements in this array with AST__BAD */
+               for( mm = this->np[ i ]; mm < m; mm++ ) this->p[ i ][ mm ] = AST__BAD;
+
+/* Remember the new array size. */
+               this->np[ i ] = m + 1;
+            }
+         }
+      }
+   }
+
+/* Re-initialize the values stored in the "AstPrjPrm" structure. */
+   InitPrjPrm( this, status );
+}
+
+static void SetTPNTan( AstWcsMap *this, int val, int *status ) {
+/*
+*+
+*  Name:
+*     astSetTPNTan
+
+*  Purpose:
+*     Set the value of a TPNTan attribute.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     void astSetTPNTan( AstWcsMap *this, int val )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function stores a value for the TPNTan attribute and updates
+*     the projection parameters used by WCSLIB accordingly.
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+*     val
+*        New attribute value.
+
+*-
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store the new value. */
+   this->tpn_tan = ( val != 0 );
+
+/* Re-initialize the values stored in the "AstPrjPrm" structure. */
+   InitPrjPrm( this, status );
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a WcsMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     WcsMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a WcsMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the WcsMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstWcsMap *this;             /* Pointer to the WcsMap structure */
+   int i;                       /* Axis index */
+   int m;                       /* Projection parameter index */
+   int len;                     /* Length os supplied string */
+   int nc;                      /* No. of characters read by astSscanf */
+   int result;                  /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the WcsMap structure. */
+   this = (AstWcsMap *) this_object;
+
+/* Obtain the length of the attrib string. */
+   len = strlen( attrib );
+
+/* Check the attribute name and test the appropriate attribute. */
+
+
+/* ProjP(i). */
+/* --------- */
+   if ( nc = 0, ( 1 == astSscanf( attrib, "projp(%d)%n", &m, &nc ) )
+                  && ( nc >= len ) ) {
+      result = astTestPV( this, astGetWcsAxis( this, 1 ), m );
+
+/* PV. */
+/* --- */
+   } else if ( nc = 0, ( 2 == astSscanf( attrib, "pv%d_%d%n", &i, &m, &nc ) )
+                  && ( nc >= len ) ) {
+      result = astTestPV( this, i - 1, m );
+
+/* If the name is not recognised, test if it matches any of the
+   read-only attributes of this class. If it does, then return
+   zero. */
+   } else if ( !strcmp( attrib, "wcstype" ) ||
+               !strcmp( attrib, "natlat" ) ||
+               !strcmp( attrib, "natlon" ) ||
+               ( nc = 0, ( 1 == astSscanf( attrib, "wcsaxis(%d)%n", &i, &nc ) )
+                           && ( nc >= len ) ) ) {
+      result = 0;
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static int TestPV( AstWcsMap *this, int i, int m, int *status ) {
+/*
+*+
+*  Name:
+*     astTestPV
+
+*  Purpose:
+*     Test a PVi_m attribute.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int astTestPV( AstWcsMap *this, int i, int m )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function returns 1 if a specified member of the PV attribute
+*     array is currently set, and 0 otherwise.
+
+*  Parameters:
+*     this
+*        A pointer to the WcsMap.
+*     i
+*        Zero based axis index.
+*     m
+*        Zero based parameter index.
+
+*  Returned Value:
+*     1 if the attribute is set, 0 otherwise.
+
+*-
+*/
+/* Local Variables: */
+   int ret;
+   int npar;
+   int mxpar;
+
+/* Initialise */
+   ret = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return ret;
+
+/* Validate the axis index. */
+   if( i < 0 || i >= astGetNin( this ) ){
+      astError( AST__AXIIN, "astTestPV(%s): Axis index (%d) is invalid in "
+                "attribute PV%d_%d  - it should be in the range 1 to %d.",
+                status, astGetClass( this ), i + 1, i + 1, m, astGetNin( this ) );
+
+/* Find the maximum number of parameters allowed for the axis. */
+   } else {
+      mxpar = astGetPVMax( this, i );
+
+/* Ignore unused parameters. */
+      if( m < 0 || m > mxpar ){
+
+/* See if the parameter is currently set. */
+      } else if( this->np && this->p ){
+         npar = this->np[ i ];
+         if( m < npar && this->p[ i ] ){
+            ret = ( this->p[ i ][ m ] != AST__BAD );
+         }
+      }
+   }
+
+   return ret;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*+
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a WcsMap to a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     WcsMap member function (over-rides the astTransform method inherited
+*     from the Mapping class).
+
+*  Description:
+*     This function takes a WcsMap and a set of points encapsulated in a
+*     PointSet and transforms the points using the requested projection.
+
+*  Parameters:
+*     this
+*        Pointer to the WcsMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  Assuming the WcsMap has not been inverted, the forward mapping
+*     transforms spherical (longitude,latitude) pairs into Cartesian (x,y)
+*     pairs. Longitude and latitude values are given and returned in radians,
+*     and no restrictions are imposed on their ranges. X and Y values are
+*     also given and returned in radians, with no restrictions imposed on
+*     their ranges.
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the WcsMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*-
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstWcsMap *map;               /* Pointer to WcsMap to be applied */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   int i;                        /* Axis count */
+   int latax;                    /* Latitude axis index */
+   int lonax;                    /* Longitude axis index */
+   int npoint;                   /* Number of points */
+   int status_value;                   /* Status from Map function */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the WcsMap. */
+   map = (AstWcsMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points from the input PointSet and obtain pointers
+   for accessing the input and output coordinate values. */
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* If a genuine FITS-WCS projection is being performed... */
+   if( astGetWcsType( map ) != AST__WCSBAD ){
+
+/* Determine whether to apply the forward or inverse mapping, according to the
+   direction specified and whether the mapping has been inverted. */
+      if ( astGetInvert( map ) ) forward = !forward;
+
+/* Do the coordinate transformation. */
+      lonax = astGetWcsAxis( map, 0 );
+      latax = astGetWcsAxis( map, 1 );
+      status_value = Map( map, forward, npoint, ptr_in[ lonax ], ptr_in[ latax ],
+                          ptr_out[ lonax ], ptr_out[ latax ], status );
+
+/* Report an error if the projection type was unrecognised. */
+      if ( status_value == 1 ) {
+         astError( AST__WCSTY, "astTransform(%s): The %s specifies an "
+                   "illegal projection type ('%s').", status, astClass( this ),
+                   astClass( this ), FindPrjData( map->type, status )->desc  );
+
+/* Report an error if the projection parameters were invalid. */
+       } else if ( status_value == 2 ) {
+          astError( AST__WCSPA, "astTransform(%s): The %s projection "
+                    "parameter values in this %s are unusable.", status,
+                    astClass( this ), FindPrjData( map->type, status )->desc,
+                    astClass( this )  );
+
+/* Report an error if required projection parameters were not supplied. */
+       } else if ( status_value >= 400 ) {
+          astError( AST__WCSPA, "astTransform(%s): Required projection "
+                    "parameter PV%d_%d was not supplied for a %s "
+                    "projection.", status, astClass( this ), latax+1, status_value - 400,
+                    FindPrjData( map->type, status )->desc  );
+
+       } else if ( status_value >= 100 ) {
+          astError( AST__WCSPA, "astTransform(%s): Required projection "
+                    "parameter PV%d_%d was not supplied for a %s "
+                    "projection.", status, astClass( this ), lonax+1, status_value - 100,
+                    FindPrjData( map->type, status )->desc  );
+       }
+
+/* Copy the remaining axes (i.e. all axes except the longitude and latitude
+   axes) from the input to the output. */
+      for( i = 0; i < astGetNcoord( in ); i++ ){
+         if( ( i != lonax && i != latax ) ){
+            (void) memcpy( ptr_out[ i ], ptr_in[ i ], sizeof( double )*
+                           (size_t) npoint );
+         }
+
+      }
+
+/* If there is no FITS-WCS projection, just copy all the axes from input to
+   output. */
+   } else {
+      for( i = 0; i < astGetNcoord( in ); i++ ){
+         (void) memcpy( ptr_out[ i ], ptr_in[ i ], sizeof( double )*
+                        (size_t) npoint );
+      }
+   }
+
+/* If an error has occurred, attempt to delete the results PointSet. */
+   if ( !astOK ) result = astDelete( result );
+
+/* Return a pointer to the output PointSet. */
+   return result;
+
+}
+
+int astWcsPrjType_( const char *ctype, int *status ){
+/*
+*+
+*  Name:
+*     astWcsPrjType
+
+*  Purpose:
+*     Get the integer identifier for a WCSLIB projection given by a FITS
+*     CTYPE keyword value.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     int astWcsPrjType( const char *ctype )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function returns the integer identifier for the WCSLIB projection
+*     specified by the supplied FITS CTYPE keyword value. The returned
+*     value can be passed to astWcsMap to create a WcsMap which implements
+*     the corresponding projection.
+
+*  Parameters:
+*     ctype
+*        A pointer to the last 4 characters of a FITS CTYPE1 (etc) keyword.
+
+*  Returned Value:
+*     The integer identifier associated with the projection.
+
+*  Notes:
+*     -  A value of AST__WCSBAD is returned if the projection type is
+*     unknown, but no error is reported.
+*-
+*/
+
+   PrjData *data;
+   char buffer[81];
+   const char *a;
+   char *b;
+
+/* Remove leading and trailing blanks from the supplied string. */
+   a = ctype;
+   b = buffer;
+   while( *a && (b - buffer) < 80 ){
+      if( !isspace( (int) *a ) ) {
+         *(b++) = *a;
+      }
+      a++;
+   }
+   *b = 0;
+
+/* Search for the projection in the list of available projectons. */
+   data = PrjInfo;
+   while( data->prj != AST__WCSBAD && strcmp( data->ctype, buffer ) ) data ++;
+
+   return data->prj;
+}
+
+const char *astWcsPrjName_( int type, int *status ){
+/*
+*+
+*  Name:
+*     astWcsPrjName
+
+*  Purpose:
+*     Get the name of a projection given its integer identifier.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     const char *astWcsPrjName( int type )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function returns a string holding 4 characters which can be
+*     used as the last 4 characters of a FITS CTYPE keyword value
+*     describing the WCSLIB projection specified by the supplied type.
+
+*  Parameters:
+*     type
+*        The projection type.
+
+*  Returned Value:
+*     A pointer to a null-terminated const character string holding the
+*     last 4 CTYPE characters describing the projection.
+
+*  Notes:
+*     -  This function attempts to execute even if an error has already
+*     occurred.
+*-
+*/
+
+   PrjData *data;
+   data = PrjInfo;
+   while( data->prj != AST__WCSBAD && data->prj != type ) data ++;
+   return data->ctype;
+}
+
+const char *astWcsPrjDesc_( int type, int *status ){
+/*
+*+
+*  Name:
+*     astWcsPrjDesc
+
+*  Purpose:
+*     Get a textual description of a projection given its integer
+*     identifier.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     const char *astWcsPrjDesc( int type )
+
+*  Class Membership:
+*     WcsMap protected function
+
+*  Description:
+*     This function returns a pointer to a string string holding a
+*     textual description of the specified projection type.
+
+*  Parameters:
+*     type
+*        The projection type.
+
+*  Returned Value:
+*     A pointer to a null-terminated const character string holding the
+*     projection description.
+
+*  Notes:
+*     -  This function attempts to execute even if an error has already
+*     occurred.
+*-
+*/
+
+   PrjData *data;
+   data = PrjInfo;
+   while( data->prj != AST__WCSBAD && data->prj != type ) data ++;
+   return data->desc;
+}
+
+static void WcsPerm( AstMapping **maps, int *inverts, int iwm, int *status ){
+/*
+*  Name:
+*     WcsPerm
+
+*  Purpose:
+*     Swap a WcsMap and a PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     void WcsPerm( AstMapping **maps, int *inverts, int iwm, int *status )
+
+*  Class Membership:
+*     WcsMap member function
+
+*  Description:
+*     A list of two Mappings is supplied containing a WcsMap and a
+*     PermMap. These Mappings are annulled, and replaced with
+*     another pair of Mappings consisting of a WcsMap and a PermMap
+*     in the opposite order. These Mappings are chosen so that their
+*     combined effect is the same as the original pair of Mappings.
+
+*  Parameters:
+*     maps
+*        A pointer to an array of two Mapping pointers.
+*     inverts
+*        A pointer to an array of two invert flags.
+*     iwm
+*        The index within "maps" of the WcsMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  All links between input and output axes in the PermMap must
+*     be bi-directional, but there can be unconnected axes, and there
+*     need not be the same number of input and output axes. Both
+*     longitude and latitude axes must be passed by the PermMap.
+
+*/
+
+/* Local Variables: */
+   AstPermMap *pm;               /* Pointer to the supplied PermMap */
+   AstPermMap *newpm;            /* Pointer to the returned PermMap */
+   AstMapping *newwm;            /* Pointer to the returned WcsMap */
+   AstWcsMap *wm;                /* Pointer to the supplied WcsMap */
+   double *consts;               /* Pointer to constants array */
+   int *inperm;                  /* Pointer to input axis permutation array */
+   int *outperm;                 /* Pointer to output axis permutation array */
+   int latax;                    /* Index of latitude axis */
+   int lonax;                    /* Index of longitude axis */
+   int npin;                     /* No. of input axes in supplied PermMap */
+   int npout;                    /* No. of output axes in supplied PermMap */
+   int old_pinv;                 /* Invert value for the supplied PermMap */
+   int old_winv;                 /* Invert value for the supplied WcsMap */
+   int type;                     /* Projection type */
+   int done;                     /* Have Mappings been swapped? */
+   int i;                        /* AXis index */
+   double *p;                    /* Pointer to input position */
+   double *q;                    /* Pointer to output position */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   newpm = NULL;
+   newwm = NULL;
+
+/* Store pointers to the supplied WcsMap and the PermMap. */
+   wm = (AstWcsMap *) maps[ iwm ];
+   pm = (AstPermMap *) maps[ 1 - iwm ];
+
+/* Temporarily set the Invert attribute of the supplied PermMap to the
+   supplied values. */
+   old_pinv = astGetInvert( pm );
+   astSetInvert( pm, inverts[ 1 - iwm ] );
+   old_winv = astGetInvert( wm );
+   astSetInvert( wm, inverts[ iwm ] );
+
+/* Get the projection type of the supplied WcsMap and the indices of the
+   longitude and latitude axes. */
+   type = astGetWcsType( wm );
+   lonax = astGetWcsAxis( wm, 0 );
+   latax = astGetWcsAxis( wm, 1 );
+
+/* Get the axis permutation and constants arrays representing the
+   PermMap. Note, no constants are used more than once in the returned
+   arrays (i.e. duplicate constants are returned in "consts" if more than
+   one axis uses a given constant). */
+   PermGet( pm, &outperm, &inperm, &consts, status );
+
+   if( astOK ) {
+
+/* Get the number of input and output axes in the PermMap. */
+      npin = astGetNin( pm );
+      npout = astGetNout( pm );
+
+/* If the lon and lat axes of the WcsMap are unassigned, we return a
+   UnitMap instead of a WcsMap.
+   ================================================================ */
+      done = 0;
+
+/* If the PermMap comes after the WcsMap... */
+      if( iwm == 0 ) {
+         if( inperm[ lonax ] < 0 && inperm[ latax ] < 0 ) {
+            done = 1;
+
+/* Transform the constant values, using AST__BAD for other axes. */
+            p = (double *) astMalloc( sizeof( double )*(size_t) npin );
+            q = (double *) astMalloc( sizeof( double )*(size_t) npin );
+            if( astOK ) {
+               for( i = 0; i < npin; i++ ) {
+                  if( inperm[ i ] < 0 ) {
+                     p[ i ] = consts[ -inperm[ i ] - 1 ];
+                  } else {
+                     p[ i ] = AST__BAD;
+                  }
+               }
+
+/* Transform this position using the inverse WcsMap. */
+               astTranN( wm, 1, npin, 1, p, 0, npin, 1, q );
+
+/* The new PermMap has the same axis permutations as the original, but it
+   has different constants. */
+               for( i = 0; i < npin; i++ ) {
+                  if( inperm[ i ] < 0 ) {
+                     consts[ -inperm[ i ] - 1 ] = q[ i ];
+                  }
+               }
+
+               newpm = astPermMap( npin, inperm, npout, outperm, consts, "", status );
+
+/* Use a UnitMap instead of the WcsMap. */
+               newwm = (AstMapping *) astUnitMap( npout, "", status );
+
+            }
+
+/* Free memory */
+            p = astFree( p );
+            q = astFree( q );
+
+         }
+
+/* If the WcsMap comes after the PermMap... */
+      } else {
+         if( outperm[ lonax ] < 0 && outperm[ latax ] < 0 ) {
+            done = 1;
+
+/* Transform the constant values, using AST__BAD for other axes. */
+            p = (double *) astMalloc( sizeof( double )*(size_t) npout );
+            q = (double *) astMalloc( sizeof( double )*(size_t) npout );
+            if( astOK ) {
+               for( i = 0; i < npout; i++ ) {
+                  if( outperm[ i ] < 0 ) {
+                     p[ i ] = consts[ -outperm[ i ] - 1 ];
+                  } else {
+                     p[ i ] = AST__BAD;
+                  }
+               }
+
+/* Transform this position using the forward WcsMap. */
+               astTranN( wm, 1, npout, 1, p, 1, npout, 1, q );
+
+/* The new PermMap has the same axis permutations as the original, but it
+   has different constants. */
+               for( i = 0; i < npout; i++ ) {
+                  if( outperm[ i ] < 0 ) {
+                     consts[ -outperm[ i ] - 1 ] = q[ i ];
+                  }
+               }
+
+               newpm = astPermMap( npin, inperm, npout, outperm, consts, "", status );
+
+/* Use a UnitMap instead ofhte WcsMap. */
+               newwm = (AstMapping *) astUnitMap( npin, "", status );
+
+            }
+
+/* Free memory */
+            p = astFree( p );
+            q = astFree( q );
+
+         }
+      }
+
+/* If the lon and lat axes of the WcsMap are both assigned, we return a
+   WcsMap.
+   ================================================================ */
+      if( !done ) {
+
+/* Create the new WcsMap with permuted longitude and latitude axes. Note,
+   the private interface to astWcsMap uses 1-based axis indices. */
+         if( iwm == 0 ) {
+            newwm = (AstMapping *) astWcsMap( npout, type, inperm[ lonax ] + 1,
+                                              inperm[ latax ] + 1, "", status );
+         } else {
+            newwm = (AstMapping *) astWcsMap( npin, type, outperm[ lonax ] + 1,
+                                              outperm[ latax ] + 1, "", status );
+         }
+
+/* Copy any projection parameters which have been set. */
+         CopyPV( wm, (AstWcsMap *) newwm, status );
+
+/* Set the invert flag. */
+         astSetInvert( newwm, inverts[ iwm ] );
+
+/* The returned PermMap is a clone of the supplied PermMap */
+         newpm = astClone( pm );
+      }
+
+/* Free the axis permutation and constants arrays. */
+      outperm = (int *) astFree( (void *) outperm );
+      inperm = (int *) astFree( (void *) inperm );
+      consts = (double *) astFree( (void *) consts );
+   }
+
+/* Re-instate the original value of the Invert attributes. */
+   astSetInvert( pm, old_pinv );
+   astSetInvert( wm, old_winv );
+
+/* Annul the supplied pointers */
+   pm = astAnnul( pm );
+   wm = astAnnul( wm );
+
+/* Store the returned Mappings. */
+   maps[ iwm ] = (AstMapping *) newpm;
+   inverts[ iwm ] = astGetInvert( newpm );
+   maps[ 1 - iwm ] = newwm;
+   inverts[ 1 - iwm ] = astGetInvert( newwm );
+
+/* Return. */
+   return;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+
+/*
+*att+
+*  Name:
+*     FITSProj
+
+*  Purpose:
+*     Is this WcsMap used as a FITS-WCS projection?
+
+*  Type:
+*     Protected attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls how a WcsMap is used when creating a set
+*     of FITS headers. If the WcsMap is contained within a FrameSet
+*     that is to be  converted into a set of FITS headers using the
+c     astWrite funtion,
+f     AST_WRITE routine,
+*     the WcsMap will be used to define the projection code appended to
+*     the FITS "CTYPEi" keywords if, and only if, the FITSProj attribute
+*     is set non-zero in the WcsMap. In order for the conversion to be
+*     successful, the compound Mapping connecting the base and current
+*     Frames in the FrameSet must contained one (and only one) WcsMap
+*     that has a non-zero value for its FITSProj attribute.
+*
+*     The default value is one.
+
+*  Applicability:
+*     WcsMap
+*        All Frames have this attribute.
+*att-
+*/
+astMAKE_CLEAR(WcsMap,FITSProj,fits_proj,-INT_MAX)
+astMAKE_GET(WcsMap,FITSProj,int,1,( ( this->fits_proj != -INT_MAX ) ?
+                                       this->fits_proj : 1 ))
+astMAKE_SET(WcsMap,FITSProj,int,fits_proj,( value != 0 ))
+astMAKE_TEST(WcsMap,FITSProj,( this->fits_proj != -INT_MAX ))
+
+/*
+*att+
+*  Name:
+*     TPNTan
+
+*  Purpose:
+*     Should the TPN projection include a TAN projection?
+
+*  Type:
+*     Protected attribute.
+
+*  Synopsis:
+*     Integer (boolean).
+
+*  Description:
+*     This attribute controls how a WcsMap with a AST__TPN projection
+*     type behaves. If the attribute value is non-zero (the default),
+*     the complete projection is performed as described in the draft
+*     version of FITS-WCS paper II. If it is zero, then the TAN
+*     projection from (psi,theta) to (xi,eta) is replaced by a unit
+*     transformation, so that the WcsMap implements the polynomial
+*     transformation only, without any preceding TAN projection.
+*
+*     In addition if the TPNTan value is zero, then the WcsMap
+*     assumes that the input and output values are in degrees rather
+*     than radians.
+
+*  Applicability:
+*     WcsMap
+*        All Frames have this attribute.
+*att-
+*/
+astMAKE_GET(WcsMap,TPNTan,int,1,( ( this->tpn_tan != -INT_MAX ) ?
+                                       this->tpn_tan : 1 ))
+astMAKE_TEST(WcsMap,TPNTan,( this->tpn_tan != -INT_MAX ))
+
+/* ProjP. */
+/* ------ */
+/*
+*att++
+*  Name:
+*     ProjP(m)
+
+*  Purpose:
+*     FITS-WCS projection parameters.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute provides aliases for the PV attributes, which
+*     specifies the projection parameter values to be used by a WcsMap
+*     when implementing a FITS-WCS sky projection. ProjP is retained for
+*     compatibility with previous versions of FITS-WCS and AST. New
+*     applications should use the PV attibute instead.
+*
+*     Attributes ProjP(0) to ProjP(9) correspond to attributes PV<axlat>_0
+*     to PV<axlat>_9, where <axlat> is replaced by the index of the
+*     latitude axis (given by attribute WcsAxis(2)). See PV for further
+*     details.
+*
+*     Note, the value of this attribute may changed only if the WcsMap
+*     has no more than one reference. That is, an error is reported if the
+*     WcsMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+
+*  Applicability:
+*     WcsMap
+*        All WcsMaps have this attribute.
+
+*att--
+*/
+
+/* PV. */
+/* --- */
+/*
+*att++
+*  Name:
+*     PVi_m
+
+*  Purpose:
+*     FITS-WCS projection parameters.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point.
+
+*  Description:
+*     This attribute specifies the projection parameter values to be
+*     used by a WcsMap when implementing a FITS-WCS sky projection.
+*     Each PV attribute name should include two integers, i and m,
+*     separated by an underscore. The axis index is specified
+*     by i, and should be in the range 1 to 99. The parameter number
+*     is specified by m, and should be in the range 0 to 99. For
+*     example, "PV2_1=45.0" would specify a value for projection
+*     parameter 1 of axis 2 in a WcsMap.
+*
+*     These projection parameters correspond exactly to the values
+*     stored using the FITS-WCS keywords "PV1_1", "PV1_2", etc. This
+*     means that projection parameters which correspond to angles must
+*     be given in degrees (despite the fact that the angular
+*     coordinates and other attributes used by a WcsMap are in
+*     radians).
+*
+*     The set of projection parameters used by a WcsMap depends on the
+*     type of projection, which is determined by its WcsType
+*     parameter.  Most projections either do not require projection
+*     parameters, or use parameters 1 and 2 associated with the latitude
+*     axis. You should consult the FITS-WCS paper for details.
+*
+*     Some projection parameters have default values (as defined in
+*     the FITS-WCS paper) which apply if no explicit value is given.
+*     You may omit setting a value for these "optional" parameters and the
+*     default will apply. Some projection parameters, however, have no
+*     default and a value must be explicitly supplied.  This is most
+*     conveniently
+c     done using the "options" argument of astWcsMap (q.v.) when a WcsMap
+f     done using the OPTIONS argument of AST_WCSMAP (q.v.) when a WcsMap
+*     is first created. An error will result when a WcsMap is used to
+*     transform coordinates if any of its required projection
+*     parameters has not been set and lacks a default value.
+
+*     A "get" operation for a parameter which has not been assigned a value
+*     will return the default value defined in the FITS-WCS paper, or
+*     AST__BAD if the paper indicates that the parameter has no default.
+*     A default value of zero is returned for parameters which are not
+*     accessed by the projection.
+*
+*     Note, the FITS-WCS paper reserves parameters 1 and 2 on the longitude
+*     axis to hold the native longitude and latitude of the fiducial
+*     point of the projection, in degrees. The default values for these
+*     parameters are determined by the projection type. The AST-specific
+*     TPN projection does not use this convention - all projection
+*     parameters for both axes are used to represent polynomical correction
+*     terms, and the native longitude and latitude at the fiducial point may
+*     not be changed from the default values of zero and 90 degrees.
+
+*  Applicability:
+*     WcsMap
+*        All WcsMaps have this attribute.
+
+*  Notes:
+*     - The value of this attribute may changed only if the WcsMap
+*     has no more than one reference. That is, an error is reported if the
+*     WcsMap has been cloned, either by including it within another object
+*     such as a CmpMap or FrameSet or by calling the
+c     astClone
+f     AST_CLONE
+*     function.
+*     - If the projection parameter values given for a WcsMap do not
+*     satisfy all the required constraints (as defined in the FITS-WCS
+*     paper), then an error will result when the WcsMap is used to
+*     transform coordinates.
+*att--
+*/
+
+/* PVMax. */
+/* ------ */
+/*
+*att++
+*  Name:
+*     PVMax(i)
+
+*  Purpose:
+*     Maximum number of FITS-WCS projection parameters.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute specifies the largest legal index for a PV projection
+*     parameter attached to a specified axis of the WcsMap (i.e. the
+*     largest legal value for "m" when accessing the "PVi_m" attribute).
+*     The axis index is specified by i, and should be in the range 1 to 99.
+*     The value for each axis is determined by the projection type specified
+*     when the WcsMap
+c     is first created using astWcsMap and cannot subsequently be
+f     is first created using AST_WCSMAP and cannot subsequently be
+*     changed.
+
+*  Applicability:
+*     WcsMap
+*        All WcsMaps have this attribute.
+*att--
+*/
+
+/* WcsType. */
+/* -------- */
+/*
+*att++
+*  Name:
+*     WcsType
+
+*  Purpose:
+*     FITS-WCS projection type.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute specifies which type of FITS-WCS projection will
+*     be performed by a WcsMap. The value is specified when a WcsMap
+c     is first created using astWcsMap and cannot subsequently be
+f     is first created using AST_WCSMAP and cannot subsequently be
+*     changed.
+*
+c     The values used are represented by macros with names of
+f     The values used are represented by symbolic constants with names of
+*     the form "AST__XXX", where "XXX" is the (upper case) 3-character
+*     code used by the FITS-WCS "CTYPEi" keyword to identify the
+*     projection. For example, possible values are AST__TAN (for the
+*     tangent plane or gnomonic projection) and AST__AIT (for the
+*     Hammer-Aitoff projection). AST__TPN is an exception in that it
+*     is not part of the FITS-WCS standard (it represents a TAN
+*     projection with polynomial correction terms as defined in an early
+*     draft of the FITS-WCS paper).
+
+*  Applicability:
+*     WcsMap
+*        All WcsMaps have this attribute.
+
+*  Notes:
+*     - For a list of available projections, see the FITS-WCS paper.
+*att--
+*/
+
+/* Type of FITS-WCS projection. Read only. */
+astMAKE_GET(WcsMap,WcsType,int,AST__WCSBAD,this->type)
+
+/* NatLat. */
+/* ------- */
+/*
+*att++
+*  Name:
+*     NatLat
+
+*  Purpose:
+*     Native latitude of the reference point of a FITS-WCS projection.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point, read-only.
+
+*  Description:
+*     This attribute gives the latitude of the reference point of the
+*     FITS-WCS projection implemented by a WcsMap. The value is in
+*     radians in the "native spherical" coordinate system. This value is
+*     fixed for most projections, for instance it is PI/2 (90 degrees)
+*     for all zenithal projections. For some projections (e.g. the conics)
+*     the value is not fixed, but is specified by parameter one on the
+*     latitude axis.
+*
+*     FITS-WCS paper II introduces the concept of a "fiducial point"
+*     which is logical distinct from the projection reference point.
+*     It is easy to confuse the use of these two points. The fiducial
+*     point is the point which has celestial coordinates given by the
+*     CRVAL FITS keywords. The native spherical coordinates for this point
+*     default to the values of the NatLat and NatLon, but these defaults
+*     mey be over-ridden by values stored in the PVi_j keywords. Put
+*     another way, the CRVAL keywords will by default give the celestial
+*     coordinates of the projection reference point, but may refer to
+*     some other point if alternative native longitude and latitude values
+*     are provided through the PVi_j keywords.
+*
+*     The NatLat attribute is read-only.
+
+*  Applicability:
+*     WcsMap
+*        All WcsMaps have this attribute.
+
+*  Notes:
+*     - A default value of AST__BAD is used if no latitude value is available.
+*att--
+*/
+
+/*
+*att++
+*  Name:
+*     NatLon
+
+*  Purpose:
+*     Native longitude of the reference point of a FITS-WCS projection.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Floating point, read-only.
+
+*  Description:
+*     This attribute gives the longitude of the reference point of the
+*     FITS-WCS projection implemented by a WcsMap. The value is in
+*     radians in the "native spherical" coordinate system, and will
+*     usually be zero. See the description of attribute NatLat for further
+*     information.
+*
+*     The NatLon attribute is read-only.
+
+*  Applicability:
+*     WcsMap
+*        All WcsMaps have this attribute.
+
+*  Notes:
+*att--
+*/
+
+/* WcsAxis. */
+/* -------- */
+/*
+*att++
+*  Name:
+*     WcsAxis(lonlat)
+
+*  Purpose:
+*     FITS-WCS projection axes.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer, read-only.
+
+*  Description:
+*     This attribute gives the indices of the longitude and latitude
+*     coordinates of the FITS-WCS projection within the coordinate
+*     space used by a WcsMap. These indices are defined when the
+c     WcsMap is first created using astWcsMap and cannot
+f     WcsMap is first created using AST_WCSMAP and cannot
+*     subsequently be altered.
+*
+*     If "lonlat" is 1, the index of the longitude axis is
+*     returned. Otherwise, if it is 2, the index of the latitude axis
+*     is returned.
+
+*  Applicability:
+*     WcsMap
+*        All WcsMaps have this attribute.
+*att--
+*/
+
+/* Index of the latitude or longitude axis. */
+MAKE_GET(WcsAxis,int,0,this->wcsaxis[ axis ],2)
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for WcsMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for WcsMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     -  This constructor makes a deep copy, including a copy of the
+*     projection parameter values associated with the input WcsMap.
+*/
+
+
+/* Local Variables: */
+   AstWcsMap *in;                /* Pointer to input WcsMap */
+   AstWcsMap *out;               /* Pointer to output WcsMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output WcsMaps. */
+   in = (AstWcsMap *) objin;
+   out = (AstWcsMap *) objout;
+
+/* Allocate memory to hold the projection parameters within the AstPrjPrm
+   structure used by WCSLIB. */
+   (out->params).p = astMalloc( astSizeOf( (in->params).p ) );
+   (out->params).p2 = astMalloc( astSizeOf( (in->params).p2 ) );
+
+/* Copy the projection parameter information. */
+   CopyPV( in, out, status );
+
+   return;
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for WcsMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for WcsMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     void
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstWcsMap *this;            /* Pointer to WcsMap */
+
+/* Obtain a pointer to the WcsMap structure. */
+   this = (AstWcsMap *) obj;
+
+/* Free the arrays used to store projection parameters. */
+   FreePV( this, status );
+
+/* Free memory used to hold the projection parameters within the AstPrjPrm
+   structure used by WCSLIB. */
+   this->params.p = astFree( this->params.p );
+   this->params.p2 = astFree( this->params.p2 );
+
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for WcsMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the WcsMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the WcsMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+#define COMMENT_LEN 150          /* Maximum length of a keyword comment */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstWcsMap *this;              /* Pointer to the WcsMap structure */
+   char *comment;                /* Pointer to comment string */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   char comment_buff[ COMMENT_LEN + 1 ]; /* Buffer for keyword comment */
+   const PrjData *prjdata;       /* Information about the projection */
+   double dval;                  /* Double precision value */
+   int axis;                     /* Zero based axis index */
+   int i;                        /* Axis index */
+   int m;                        /* Parameter index */
+   int ival;                     /* Integer value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the WcsMap structure. */
+   this = (AstWcsMap *) this_object;
+
+/* Write out values representing the instance variables for the
+   WcsMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out. Note, all read-only attributes are considered to be set.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* WcsType. */
+/* -------- */
+   ival = GetWcsType( this, status );
+   prjdata = FindPrjData( ival, status );
+   (void) sprintf( comment_buff, "%s projection", prjdata->desc );
+   comment_buff[ 0 ] = toupper( comment_buff[ 0 ] );
+   astWriteString( channel, "Type", 1, 1, prjdata->ctype + 1, comment_buff );
+
+/* FITSProj */
+/* -------- */
+   set = TestFITSProj( this, status );
+   ival = set ? GetFITSProj( this, status ) : astGetFITSProj( this );
+   astWriteInt( channel, "FitsPrj", set, 0, ival,
+                ival ? "Defines the FITS-WCS projection" :
+                       "Does not define the FITS-WCS projection" );
+
+/* TPNTan */
+/* ------ */
+   set = TestTPNTan( this, status );
+   ival = set ? GetTPNTan( this, status ) : astGetTPNTan( this );
+   astWriteInt( channel, "TpnTan", set, 0, ival,
+                ival ? "Include TAN projection in TPN mapping" :
+                       "Exclude TAN projection from TPN mapping" );
+
+/* PVi_m. */
+/* ------ */
+   for( i = 0; i < astGetNin( this ); i++ ){
+      if( this->np ) {
+         for( m = 0; m < this->np[ i ]; m++ ){
+            set = TestPV( this, i, m, status );
+            if( set ) {
+               dval = set ? GetPV( this, i, m, status ) : astGetPV( this, i, m );
+               (void) sprintf( buff, "PV%d_%d", i + 1, m );
+               (void) sprintf( comment_buff, "Projection parameter %d for axis %d", m, i + 1 );
+               astWriteDouble( channel, buff, set, 0, dval, comment_buff );
+            }
+         }
+      }
+   }
+
+/* WcsAxis(axis). */
+/* -------------- */
+   for( axis = 0; axis < 2; axis++ ){
+      ival =  GetWcsAxis( this, axis, status );
+      (void) sprintf( buff, "WcsAx%d", axis + 1 );
+      if( axis == 0 ) {
+         comment = "Index of celestial longitude axis";
+      } else {
+         comment = "Index of celestial latitude axis";
+      }
+      astWriteInt( channel, buff, (axis!=ival), 0, ival + 1, comment );
+   }
+
+/* Note, the "params" component of the AstWcsMap structure is not written out
+   because it can be re-generated from the other components. */
+
+/* Return. */
+   return;
+
+/* Undefine macros local to this function. */
+#undef COMMENT_LEN
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAWcsMap and astCheckWcsMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(WcsMap,Mapping)
+astMAKE_CHECK(WcsMap)
+
+AstWcsMap *astWcsMap_( int ncoord, int type, int lonax, int latax,
+                       const char *options, int *status, ...){
+/*
+*++
+*  Name:
+c     astWcsMap
+f     AST_WCSMAP
+
+*  Purpose:
+*     Create a WcsMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "wcsmap.h"
+c     AstWcsMap *astWcsMap( int ncoord, int type, int lonax, int latax,
+c                           const char *options, ... )
+f     RESULT = AST_WCSMAP( NCOORD, TYPE, LONAX, LATAX, OPTIONS, STATUS )
+
+*  Class Membership:
+*     WcsMap constructor.
+
+*  Description:
+*     This function creates a new WcsMap and optionally initialises its
+*     attributes.
+*
+*     A WcsMap is used to represent sky coordinate projections as
+*     described in the (draft) FITS world coordinate system (FITS-WCS)
+*     paper by E.W. Griesen and M. Calabretta (A & A, in preparation).
+*     This paper defines a set of functions, or sky projections, which
+*     transform longitude-latitude pairs representing spherical
+*     celestial coordinates into corresponding pairs of Cartesian
+*     coordinates (and vice versa).
+*
+*     A WcsMap is a specialised form of Mapping which implements these
+*     sky projections and applies them to a specified pair of coordinates.
+*     All the projections in the FITS-WCS paper are supported, plus the now
+*     deprecated "TAN with polynomial correction terms" projection which
+*     is refered to here by the code "TPN". Using the FITS-WCS terminology,
+*     the transformation is between "native spherical" and "projection
+*     plane" coordinates.  These coordinates may, optionally, be embedded in
+*     a space with more than two dimensions, the remaining coordinates being
+*     copied unchanged. Note, however, that for consistency with other AST
+*     facilities, a WcsMap handles coordinates that represent angles
+*     in radians (rather than the degrees used by FITS-WCS).
+*
+*     The type of FITS-WCS projection to be used and the coordinates
+*     (axes) to which it applies are specified when a WcsMap is first
+*     created. The projection type may subsequently be determined
+*     using the WcsType attribute and the coordinates on which it acts
+*     may be determined using the WcsAxis(lonlat) attribute.
+*
+*     Each WcsMap also allows up to 100 "projection parameters" to be
+*     associated with each axis. These specify the precise form of the
+*     projection, and are accessed using PVi_m attribute, where "i" is
+*     the integer axis index (starting at 1), and m is an integer
+*     "parameter index" in the range 0 to 99. The number of projection
+*     parameters required by each projection, and their meanings, are
+*     dependent upon the projection type (most projections either do not
+*     use any projection parameters, or use parameters 1 and 2 associated
+*     with the latitude axis). Before creating a WcsMap you should consult
+*     the FITS-WCS paper for details of which projection parameters are
+*     required, and which have defaults. When creating the WcsMap, you must
+*     explicitly set values for all those required projection parameters
+*     which do not have defaults defined in this paper.
+
+*  Parameters:
+c     ncoord
+f     NCOORD = INTEGER (Given)
+*        The number of coordinate values for each point to be
+*        transformed (i.e. the number of dimensions of the space in
+*        which the points will reside). This must be at least 2. The
+*        same number is applicable to both input and output points.
+c     type
+f     TYPE = INTEGER (Given)
+*        The type of FITS-WCS projection to apply. This should be
+c        given using a macro value such as AST__TAN (for a tangent
+f        given as a symbolic value such as AST__TAN (for a tangent
+*        plane projection), where the characters following the double
+*        underscore give the projection type code (in upper case) as
+*        used in the FITS-WCS "CTYPEi" keyword. You should consult the
+*        FITS-WCS paper for a list of the available projections. The
+*        additional code of AST__TPN can be supplied which represents a
+*        TAN projection with polynomial correction terms as defined in an
+*        early draft of the FITS-WCS paper.
+c     lonax
+f     LONAX = INTEGER (Given)
+*        The index of the longitude axis. This should lie in the range
+c        1 to "ncoord".
+f        1 to NCOORD.
+c     latax
+f     LATAX = INTEGER (Given)
+*        The index of the latitude axis. This should lie in the range
+c        1 to "ncoord" and be distinct from "lonax".
+f        1 to NCOORD and be distinct from LONAX.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new WcsMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new WcsMap. The syntax used is identical to that for the
+f        AST_SET routine.
+*
+*        If the sky projection to be implemented requires projection
+*        parameter values to be set, then this should normally be done
+*        here via the PVi_m attribute (see the "Examples"
+*        section). Setting values for these parameters is mandatory if
+*        they do not have default values (as defined in the FITS-WCS
+*        paper).
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astWcsMap()
+f     AST_WCSMAP = INTEGER
+*        A pointer to the new WcsMap.
+
+*  Examples:
+c     wcsmap = astWcsMap( 2, AST__MER, 1, 2, "" );
+f     WCSMAP = AST_WCSMAP( 2, AST__MER, 1, 2, ' ', STATUS )
+*        Creates a WcsMap that implements a FITS-WCS Mercator
+*        projection on pairs of coordinates, with coordinates 1 and 2
+*        representing the longitude and latitude respectively. Note
+*        that the FITS-WCS Mercator projection does not require any
+*        projection parameters.
+c     wcsmap = astWcsMap( 3, AST__COE, 2, 3, "PV3_1=40.0" );
+f     WCSMAP = AST_WCSMAP( 3, AST__COE, 2, 3, 'PV3_1=40.0', STATUS )
+*        Creates a WcsMap that implements a FITS-WCS conical equal
+*        area projection. The WcsMap acts on points in a 3-dimensional
+*        space; coordinates 2 and 3 represent longitude and latitude
+*        respectively, while the values of coordinate 1 are copied
+*        unchanged.  Projection parameter 1 associatyed with the latitude
+*        axis (corresponding to FITS keyword "PV3_1") is required and has
+*        no default, so is set explicitly to 40.0 degrees. Projection
+*        parameter 2 (corresponding to FITS keyword "PV3_2") is required
+*        but has a default of zero, so need not be specified.
+
+*  Notes:
+*     - The forward transformation of a WcsMap converts between
+*     FITS-WCS "native spherical" and "relative physical" coordinates,
+*     while the inverse transformation converts in the opposite
+*     direction. This arrangement may be reversed, if required, by
+c     using astInvert or by setting the Invert attribute to a non-zero
+f     using AST_INVERT or by setting the Invert attribute to a non-zero
+*     value.
+*     - If any set of coordinates cannot be transformed (for example,
+*     many projections do not cover the entire celestial sphere), then
+*     a WcsMap will yield coordinate values of AST__BAD.
+*     - The validity of any projection parameters given via the PVi_m
+c     parameter in the "options" string is not checked by this
+f     parameter in the OPTIONS string is not checked by this
+*     function. However, their validity is checked when the resulting
+*     WcsMap is used to transform coordinates, and an error will
+*     result if the projection parameters do not satisfy all the
+*     required constraints (as defined in the FITS-WCS paper).
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstWcsMap *new;               /* Pointer to new WcsMap */
+   va_list args;                 /* Variable argument list */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise the WcsMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitWcsMap( NULL, sizeof( AstWcsMap ), !class_init, &class_vtab,
+                        "WcsMap", ncoord, type, lonax - 1, latax - 1 );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new WcsMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new WcsMap. */
+   return new;
+}
+
+AstWcsMap *astWcsMapId_( int ncoord, int type, int lonax, int latax,
+                         const char *options, ... ){
+/*
+*  Name:
+*     astWcsMapId_
+
+*  Purpose:
+*     Create a WcsMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     AstWcsMap *astWcsMap_( int ncoord, int type, int lonax, int latax,
+*                            const char *options, ... )
+
+*  Class Membership:
+*     WcsMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astWcsMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astWcsMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astWcsMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astWcsMap_.
+
+*  Returned Value:
+*     The ID value associated with the new WcsMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstWcsMap *new;               /* Pointer to new WcsMap */
+   va_list args;                 /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the WcsMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitWcsMap( NULL, sizeof( AstWcsMap ), !class_init, &class_vtab,
+                        "WcsMap", ncoord, type, lonax - 1, latax - 1 );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new WcsMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new WcsMap. */
+   return astMakeId( new );
+}
+
+AstWcsMap *astInitWcsMap_( void *mem, size_t size, int init,
+                           AstWcsMapVtab *vtab, const char *name,
+                           int ncin, int type, int lonax, int latax, int *status ) {
+/*
+*+
+*  Name:
+*     astInitWcsMap
+
+*  Purpose:
+*     Initialise a WcsMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     AstWcsMap *astInitWcsMap( void *mem, size_t size, int init,
+*                               AstWcsMapVtab *vtab, const char *name,
+*                               int ncin, int type, int lonax, int latax )
+
+*  Class Membership:
+*     WcsMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new WcsMap object. It allocates memory (if necessary) to accommodate
+*     the WcsMap plus any additional data associated with the derived class.
+*     It then initialises a WcsMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a WcsMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the WcsMap is to be initialised.
+*        This must be of sufficient size to accommodate the WcsMap data
+*        (sizeof(WcsMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the WcsMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the WcsMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the WcsMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new WcsMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     ncin
+*        The number of coordinate values per point.
+*     type
+*        The type of projection to use, choosen from the list available
+*        in the FITS celestial coordinates system. These are specified by
+*        symbolic values such as AST__TAN (specifying a tangent plane
+*        projection), where the characters following the double underscore
+*        gives the FITS projection type (in upper case) as used in the FITS
+*        "CTYPEn" keyword.
+*     lonax
+*        The index of the longitude axis. The first axis has index 0.
+*     latax
+*        The index of the latitude axis. The first axis has index 0.
+
+*  Returned Value:
+*     A pointer to the new WcsMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   const PrjData *prjdata;      /* Information about the projection */
+   AstWcsMap *new;              /* Pointer to new WcsMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitWcsMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* If a genuine FITS-WCS projection has been specified, check the initialisation
+   value(s) for validity, reporting an error if necessary. Prefix the error
+   report with the name of the function and the class of object being
+   processed. First check that at least two dimensions are to be mapped. */
+   if( type != AST__WCSBAD ){
+      if ( ncin < 2 ){
+         astError( AST__WCSNC, "astInitWcsMap(%s): Too few axes (%d) "
+                   "specified. Must be at least 2.", status, name, ncin );
+
+/* Report an error if either the longitude or latitude axes are out of
+   bounds. */
+      } else if ( lonax < 0 || lonax >= ncin ){
+         astError( AST__WCSAX, "astInitWcsMap(%s): Specified longitude axis (%d) "
+                   "does not exist within a %d dimensional coordinate system. ", status,
+                   name, lonax + 1, ncin );
+
+      } else if ( latax < 0 || latax >= ncin ){
+         astError( AST__WCSAX, "astInitWcsMap(%s): Specified latitude axis (%d) "
+                   "does not exist within a %d dimensional coordinate system. ", status,
+                   name, latax + 1, ncin );
+
+/* Report an error if the longitude or latitude axes are the same. */
+      } else if ( lonax == latax ){
+         astError( AST__WCSAX, "astInitWcsMap(%s): The same axis (%d) has been "
+                   "given for both the longitude and the latitude axis.", status, name,
+                   lonax + 1 );
+
+/* Report an error if projection type is unknown. */
+      } else if ( type < 1 || type >= AST__WCSBAD ){
+         astError( AST__WCSTY, "astInitWcsMap(%s): Projection type %d is "
+                   "undefined. Projection types must be in the range 1 to %d.", status,
+                   name, type, AST__WCSBAD - 1 );
+      }
+   }
+
+/* Get a description of the requeste dprojection type. */
+   prjdata = FindPrjData( type, status );
+
+/* If all the above checks have been passed succesfully... */
+   if( astOK ){
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the WcsMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+      new = (AstWcsMap *) astInitMapping( mem, size, 0,
+                                          (AstMappingVtab *) vtab, name,
+                                          ncin, ncin, 1, 1 );
+
+      if ( astOK ) {
+
+/* Initialise the WcsMap data. */
+/* ---------------------------- */
+/* Store the projection type. */
+         new->type = type;
+
+/* Store the "use as FITS-WCS projection" flag. */
+         new->fits_proj = -INT_MAX;
+
+/* Store the "include TAN component in TPN Mapping" flag. */
+         new->tpn_tan = -INT_MAX;
+
+/* Store the axes associated with longitude and latitude. */
+         new->wcsaxis[0] = lonax;
+         new->wcsaxis[1] = latax;
+
+/* Store NULL pointers for the arrays holding projection parameters. */
+         new->p = NULL;
+         new->np = NULL;
+
+/* Allocate memory of the right size to hold the maximum number of
+   projection parameters needed by the projection. */
+         new->params.p = astMalloc( sizeof( double ) * (prjdata->mxpar + 1) );
+         new->params.p2 = astMalloc( sizeof( double ) * (prjdata->mxpar2 + 1) );
+
+/* Initialise the "AstPrjPrm" structure (defined in proj.h). */
+         InitPrjPrm( new, status );
+
+/* If an error occurred, clean up by deleting the new WcsMap. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new WcsMap. */
+   return new;
+}
+
+AstWcsMap *astLoadWcsMap_( void *mem, size_t size,
+                           AstWcsMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadWcsMap
+
+*  Purpose:
+*     Load a WcsMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "wcsmap.h"
+*     AstWcsMap *astLoadWcsMap( void *mem, size_t size,
+*                               AstWcsMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     WcsMap loader.
+
+*  Description:
+*     This function is provided to load a new WcsMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     WcsMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a WcsMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the WcsMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        WcsMap data (sizeof(WcsMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the WcsMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the WcsMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstWcsMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new WcsMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the WcsMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "WcsMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new WcsMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+/* Local Variables: */
+   const PrjData *prjdata;      /* Information about the projection */
+   AstWcsMap *new;              /* Pointer to the new WcsMap */
+   char *text;                  /* Textual form of an integer value */
+   char buff[ KEY_LEN + 1 ];    /* Buffer for keyword string */
+   double pv;                   /* Projection parameter */
+   int axis;                    /* Axis index */
+   int i;                       /* Axis index */
+   int m;                       /* Parameter index */
+   int mxpar;                   /* Maximum number of PVi_m values */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this WcsMap. In this case the
+   WcsMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstWcsMap );
+      vtab = &class_vtab;
+      name = "WcsMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitWcsMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built WcsMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "WcsMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. Note, this is only
+   done for read/write attributes, not read-only ones. */
+
+/* FITSProj */
+/* -------- */
+      new->fits_proj = astReadInt( channel, "fitsprj", -INT_MAX );
+      if ( TestFITSProj( new, status ) ) {
+         SetFITSProj( new, new->fits_proj, status );
+      }
+
+/* TPNTan */
+/* -------- */
+      new->tpn_tan = astReadInt( channel, "tpntan", -INT_MAX );
+      if ( TestTPNTan( new, status ) ) {
+         SetTPNTan( new, new->tpn_tan, status );
+      }
+
+/* WcsType */
+/* ------- */
+      text = astReadString( channel, "type", " " );
+      if( strcmp( text, " " ) ){
+         char tmp[ 10 ];
+         (void) sprintf( tmp, "-%.8s", text );
+         new->type = astWcsPrjType( tmp );
+      } else {
+         new->type = AST__WCSBAD;
+      }
+      text = astFree( text );
+      prjdata = FindPrjData( new->type, status );
+
+/* WcsAxis(axis). */
+/* -------------- */
+      for( axis = 0; axis < 2; axis++ ){
+         (void) sprintf( buff, "wcsax%d", axis + 1 );
+         new->wcsaxis[ axis ] = astReadInt( channel, buff, axis + 1 ) - 1;
+      }
+
+/* Initialise the pointers to the projection parameter information. */
+      new->p = NULL;
+      new->np = NULL;
+      new->params.p = astMalloc( sizeof( double ) * (prjdata->mxpar + 1) );
+      new->params.p2 = astMalloc( sizeof( double ) * (prjdata->mxpar2 + 1) );
+
+/* Initialise the structure used by WCSLIB to hold intermediate values,
+   so that the values will be re-calculated on the first invocation of a
+   mapping function. */
+      InitPrjPrm( new, status );
+
+/* ProjP(m). */
+/* --------- */
+      for( m = 0; m < AST__WCSMX; m++ ){
+         (void) sprintf( buff, "projp%d", m );
+         pv = astReadDouble( channel, buff, AST__BAD );
+         if( pv != AST__BAD ) SetPV( new, new->wcsaxis[ 1 ], m, pv, status );
+      }
+
+/* PVi_m. */
+/* -------*/
+      for( i = 0; i < astGetNin( new ); i++ ){
+
+         if( i == new->wcsaxis[ 0 ] ) {
+            mxpar = prjdata->mxpar2;
+         } else if( i == new->wcsaxis[ 1 ] ) {
+            mxpar = prjdata->mxpar;
+         } else {
+            mxpar = 0;
+         }
+
+         for( m = 0; m <= mxpar; m++ ){
+            (void) sprintf( buff, "pv%d_%d", i + 1, m );
+            pv = astReadDouble( channel, buff, AST__BAD );
+            if( pv != AST__BAD ) SetPV( new, i, m, pv, status );
+         }
+      }
+
+/* If an error occurred, clean up by deleting the new WcsMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new WcsMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+void astClearPV_( AstWcsMap *this, int i, int m, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,WcsMap,ClearPV))( this, i, m, status );
+}
+
+void astClearTPNTan_( AstWcsMap *this, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,WcsMap,ClearTPNTan))( this, status );
+}
+
+double astGetPV_( AstWcsMap *this, int i, int m, int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,WcsMap,GetPV))( this, i, m, status );
+}
+
+void astSetPV_( AstWcsMap *this, int i, int m, double val, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,WcsMap,SetPV))( this, i, m, val, status );
+}
+
+void astSetTPNTan_( AstWcsMap *this, int val, int *status ) {
+   if ( !astOK ) return;
+   (**astMEMBER(this,WcsMap,SetTPNTan))( this, val, status );
+}
+
+int astTestPV_( AstWcsMap *this, int i, int m, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,WcsMap,TestPV))( this, i, m, status );
+}
+
+int astIsZenithal_( AstWcsMap *this, int *status ) {
+   if ( !astOK ) return 0;
+   return (**astMEMBER(this,WcsMap,IsZenithal))( this, status );
+}
+
+double astGetNatLat_( AstWcsMap *this, int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,WcsMap,GetNatLat))( this, status );
+}
+
+double astGetNatLon_( AstWcsMap *this, int *status ) {
+   if ( !astOK ) return AST__BAD;
+   return (**astMEMBER(this,WcsMap,GetNatLon))( this, status );
+}
+
+int astGetPVMax_( AstWcsMap *this, int i, int *status ) {
+   if ( !astOK ) return -1;
+   return (**astMEMBER(this,WcsMap,GetPVMax))( this, i, status );
+}
+
+
+
+
+
+
+
diff --git a/ast/wcsmap.h b/ast/wcsmap.h
new file mode 100644
index 0000000..16ec3a7
--- /dev/null
+++ b/ast/wcsmap.h
@@ -0,0 +1,591 @@
+#if !defined( WCSMAP_INCLUDED ) /* Include this file only once */
+#define WCSMAP_INCLUDED
+/*
+*+
+*  Name:
+*     wcsmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the WcsMap class.
+
+*  Invocation:
+*     #include "wcsmap.h"
+
+*  Description:
+*     This include file defines the interface to the WcsMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The WcsMap class implements Mappings that transform a pair of
+*     longitude/latitude values into a pair of projected Cartesian
+*     coordinates. All the projections included in FITS WCS are included.
+*     For more information about these projections, see the appropriate
+*     FITS document.
+
+*  Inheritance:
+*     The WcsMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     NatLat (double)
+*        This attribute gives the latitude of the reference point of
+*        a FITS WCS projection, in the native coordinate system. The value
+*        returned is in radians. A value of AST__BAD is returned if
+*        no value is defined. This attribute is read only, and may change
+*        if new values are assigned to the projection parameters.
+*     NatLon (double)
+*        This attribute gives the longitude of the reference point of
+*        a FITS WCS projection, in the native coordinate system. The value
+*        returned is in radians. A value of AST__BAD is returned if
+*        no value is defined. This attribute is read only, and may change
+*        if new values are assigned to the projection parameters.
+*     ProjP(i) (double)
+*        This attribute provides aliases for the PV attributes, which
+*        specifies the projection parameter values to be used by a WcsMap
+*        when implementing a FITS-WCS sky projection. ProjP is retained for
+*        compatibility with previous versions of FITS-WCS and AST. New
+*        applications should use the PV attibute instead.
+*     PVj_m (double)
+*        This attribute gives the parameter values used by a FITS WCS
+*        projection. The index j is the axis index in the range 1 to 99, and
+*        the index m is the parameter index in the range 0 to 99. They will
+*        have the value AST__BAD if undefined. By default, no projection
+*        parameters are defined. These should be assigned appropriate values
+*        before using a WcsMap to transform points.
+*     WcsAxis(lonlat) (int)
+*        This attribute gives the indices of the longitude and latitude axes
+*        of a FITS WCS projection within the coordinate system used by a
+*        WcsMap. If "lonlat" is 1 then the index of the longitude axis is
+*        returned. If it is 2 the index of the latitude axis is returned.
+*        The first axis in the coordinate system is axis 1. This is a
+*        read-only attribute.
+*     WcsType (int)
+*        This attribute gives the FITS WCS projection type implemented by a
+*        WcsMap. Macros giving the integer value associated with supported
+*        projections are defined. They have the general form "AST__xxx" where
+*        "xxx" is the 3-character code used to represent the projection in the
+*        FITS CTYPE keyword.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearAttrib
+*           Clear an attribute value for a WcsMap.
+*        astGetAttrib
+*           Get an attribute value for a WcsMap.
+*        astSetAttrib
+*           Set an attribute value for a WcsMap.
+*        astTestAttrib
+*           Test if an attribute value has been set for a WcsMap.
+*        astTransform
+*           Apply a WcsMap to transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearPV
+*           Clear a PVi_j attribute value for a WcsMap.
+*        astGetNatLat
+*           Get the NatLat attribute value for a WcsMap.
+*        astGetNatLon
+*           Get the NatLon attribute value for a WcsMap.
+*        astGetPV
+*           Get a PVi_j attribute value for a WcsMap.
+*        astGetWcsAxis
+*           Get a WcsAxis attribute value for a WcsMap.
+*        astGetWcsType
+*           Get the WcsType attribute value for a WcsMap.
+*        astIsZenithal
+*           Is the projection zenithal?
+*        astSetPV
+*           Set a PVi_j attribute value for a WcsMap.
+*        astTestPV
+*           Test if a PVi_j attribute value has been set for a WcsMap.
+*        astWcsPrjName
+*           Return the FITS CTYPE keyword value for a given projection type.
+*        astWcsPrjDesc
+*           Return a textual description for a given projection type.
+*        astWcsPrjType
+*           Return the projection type given a FITS CTYPE keyword value.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAWcsMap
+*           Test class membership.
+*        astWcsMap
+*           Create a WcsMap.
+*
+*     Protected:
+*        astCheckWcsMap
+*           Validate class membership.
+*        astInitWcsMap
+*           Initialise a WcsMap.
+*        astInitWcsMapVtab
+*           Initialise the virtual function table for the WcsMap class.
+*        astLoadWcsMap
+*           Load a WcsMap.
+
+*  Macros:
+*     Public:
+*        AST__WCSMX
+*           Maximum number of parameters associated with a projection.
+*        AST__DPI
+*           180 degrees in radians.
+*        AST__DPIBY2
+*           90 degrees in radians.
+*        AST__DD2R
+*           Factor for converting degrees to radians.
+*        AST__DR2D
+*           Factor for converting radians to degrees.
+*        AST__AZP
+*           An integer identifier for the FITS AZP projection.
+*        AST__TAN
+*           An integer identifier for the FITS TAN projection.
+*        AST__SIN
+*           An integer identifier for the FITS SIN projection.
+*        AST__STG
+*           An integer identifier for the FITS STG projection.
+*        AST__ARC
+*           An integer identifier for the FITS ARC projection.
+*        AST__ZPN
+*           An integer identifier for the FITS ZPN projection.
+*        AST__ZEA
+*           An integer identifier for the FITS ZEA projection.
+*        AST__AIR
+*           An integer identifier for the FITS AIR projection.
+*        AST__CYP
+*           An integer identifier for the FITS CYP projection.
+*        AST__CAR
+*           An integer identifier for the FITS CAR projection.
+*        AST__MER
+*           An integer identifier for the FITS MER projection.
+*        AST__CEA
+*           An integer identifier for the FITS CEA projection.
+*        AST__COP
+*           An integer identifier for the FITS COP projection.
+*        AST__COD
+*           An integer identifier for the FITS COD projection.
+*        AST__COE
+*           An integer identifier for the FITS COE projection.
+*        AST__COO
+*           An integer identifier for the FITS COO projection.
+*        AST__BON
+*           An integer identifier for the FITS BON projection.
+*        AST__PCO
+*           An integer identifier for the FITS PCO projection.
+*        AST__GLS
+*           A depracated integer identifier for the FITS SFL projection.
+*        AST__SFL
+*           An integer identifier for the FITS SFL projection.
+*        AST__PAR
+*           An integer identifier for the FITS PAR projection.
+*        AST__AIT
+*           An integer identifier for the FITS AIT projection.
+*        AST__MOL
+*           An integer identifier for the FITS MOL projection.
+*        AST__CSC
+*           An integer identifier for the FITS CSC projection.
+*        AST__QSC
+*           An integer identifier for the FITS QSC projection.
+*        AST__TSC
+*           An integer identifier for the FITS TSC projection
+*        AST__HPX
+*           An integer identifier for the FITS HPX projection.
+*        AST__XPH
+*           An integer identifier for the FITS XPH projection.
+*        AST__TPN
+*           An integer identifier for a "TAN with correction terms" projection.
+*        AST__WCSBAD
+*           An integer identifier for a "null" projection.
+*
+*     Protected:
+*        None.
+
+*  Type Definitions:
+*     Public:
+*        AstWcsMap
+*           WcsMap object type.
+*
+*     Protected:
+*        AstWcsMapVtab
+*           WcsMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     15-Feb-1996 (DSB):
+*        Original version.
+*     23-MAR-1996 (DSB):
+*        Support for PointSets with more than 2 axes added.
+*     18-NOV-1996 (DSB):
+*        Updated to include attributes, etc.
+*     26-SEP-1997 (DSB):
+*        Included new protected function, astPrjDesc.
+*     11-FEB-2000 (DSB):
+*        Replaced wcsmap component projp by pointers p and np.
+*     20-OCT-2002 (DSB):
+*        Added astIsZenithal
+*     8-JAN-2003 (DSB):
+*        Added protected astInitWcsMapVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "proj.h"                /* Mark Calabretta's WCSLIB library header
+                                    file */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros. */
+/* ------- */
+/* Max. number of parameters for a WCS projection */
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+#define AST__WCSMX 10
+
+/* pi:  180 degrees in radians - from SLALIB file slamac.h. */
+#define AST__DPI 3.1415926535897932384626433832795028841971693993751
+
+/* pi/2:  90 degrees in radians - from SLALIB file slamac.h. */
+#define AST__DPIBY2 1.5707963267948966192313216916397514420985846996876
+
+/* pi/180:  degrees to radians - from SLALIB file slamac.h. */
+#define AST__DD2R 0.017453292519943295769236907684886127134428718885417
+
+/* 180/pi:  radians to degrees - from SLALIB file slamac.h. */
+#define AST__DR2D 57.295779513082320876798154814105170332405472466564
+
+/* Projection Types: (note, WCSBAD must be the last in this list) */
+
+#define AST__AZP 1
+#define AST__SZP 2
+#define AST__TAN 3
+#define AST__STG 4
+#define AST__SIN 5
+#define AST__ARC 6
+#define AST__ZPN 7
+#define AST__ZEA 8
+#define AST__AIR 9
+#define AST__CYP 10
+#define AST__CEA 11
+#define AST__CAR 12
+#define AST__MER 13
+#define AST__SFL 14
+#define AST__PAR 15
+#define AST__MOL 16
+#define AST__AIT 17
+#define AST__COP 18
+#define AST__COE 19
+#define AST__COD 20
+#define AST__COO 21
+#define AST__BON 22
+#define AST__PCO 23
+#define AST__TSC 24
+#define AST__CSC 25
+#define AST__QSC 26
+#define AST__NCP 27
+#define AST__GLS 28
+#define AST__TPN 29
+#define AST__HPX 30
+#define AST__XPH 31
+#define AST__WCSBAD 32   /* A bad projection type */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* WcsMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstWcsMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   int type;                     /* Projection type */
+   int wcsaxis[2];               /* Indices of lon and lat. axes */
+   double **p;                   /* Pointer to array of projection parameter arrays */
+   int *np;                      /* Pointer to array of projection parameter counts */
+   struct AstPrjPrm params;      /* WCS structure holding projection
+                                    parameters, etc. Defined in proj.h */
+   int fits_proj;                /* Use as FITS-WCS projection? */
+   int tpn_tan;                  /* Include TAN projection in TPN transformation? */
+} AstWcsMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstWcsMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   double (* GetNatLat)( AstWcsMap *, int * );
+   double (* GetNatLon)( AstWcsMap *, int * );
+   double (* GetPV)( AstWcsMap *, int, int, int * );
+   int (* GetWcsAxis)( AstWcsMap *, int, int * );
+   int (* GetWcsType)( AstWcsMap *, int * );
+   int (* GetPVMax)( AstWcsMap *, int, int * );
+   int (* TestPV)( AstWcsMap *, int, int, int * );
+   void (* ClearPV)( AstWcsMap *, int, int, int * );
+   void (* SetPV)( AstWcsMap *, int, int, double, int * );
+   int (* IsZenithal)( AstWcsMap *, int * );
+
+   int (* GetFITSProj)( AstWcsMap *, int * );
+   int (* TestFITSProj)( AstWcsMap *, int * );
+   void (* ClearFITSProj)( AstWcsMap *, int * );
+   void (* SetFITSProj)( AstWcsMap *, int, int * );
+
+   int (* GetTPNTan)( AstWcsMap *, int * );
+   int (* TestTPNTan)( AstWcsMap *, int * );
+   void (* ClearTPNTan)( AstWcsMap *, int * );
+   void (* SetTPNTan)( AstWcsMap *, int, int * );
+
+} AstWcsMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstWcsMapGlobals {
+   AstWcsMapVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 101 ];
+} AstWcsMapGlobals;
+
+#endif
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(WcsMap)          /* Check class membership */
+astPROTO_ISA(WcsMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstWcsMap *astWcsMap_( int, int, int, int, const char *, int *, ...);
+#else
+AstWcsMap *astWcsMapId_( int, int, int, int, const char *, ... )__attribute__((format(printf,5,6)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstWcsMap *astInitWcsMap_( void *, size_t, int, AstWcsMapVtab *,
+                           const char *, int, int, int, int, int * );
+
+/* Vtab initialiser. */
+void astInitWcsMapVtab_( AstWcsMapVtab *, const char *, int * );
+
+/* Loader. */
+AstWcsMap *astLoadWcsMap_( void *, size_t, AstWcsMapVtab *,
+                           const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitWcsMapGlobals_( AstWcsMapGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+   const char *astWcsPrjDesc_( int, int * );
+   const char *astWcsPrjName_( int, int * );
+   double astGetNatLat_( AstWcsMap *, int * );
+   double astGetNatLon_( AstWcsMap *, int * );
+   double astGetPV_( AstWcsMap *, int, int, int * );
+   int astWcsPrjType_( const char *, int * );
+   int astGetWcsAxis_( AstWcsMap *, int, int * );
+   int astGetWcsType_( AstWcsMap *, int * );
+   int astGetPVMax_( AstWcsMap *, int, int * );
+   int astTestPV_( AstWcsMap *, int, int, int * );
+   int astIsZenithal_( AstWcsMap *, int * );
+   void astClearPV_( AstWcsMap *, int, int, int * );
+   void astSetPV_( AstWcsMap *, int, int, double, int * );
+
+   int astGetFITSProj_( AstWcsMap *, int * );
+   int astTestFITSProj_( AstWcsMap *, int * );
+   void astClearFITSProj_( AstWcsMap *, int * );
+   void astSetFITSProj_( AstWcsMap *, int, int * );
+
+   int astGetTPNTan_( AstWcsMap *, int * );
+   int astTestTPNTan_( AstWcsMap *, int * );
+   void astClearTPNTan_( AstWcsMap *, int * );
+   void astSetTPNTan_( AstWcsMap *, int, int * );
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckWcsMap(this) astINVOKE_CHECK(WcsMap,this,0)
+#define astVerifyWcsMap(this) astINVOKE_CHECK(WcsMap,this,1)
+
+/* Test class membership. */
+#define astIsAWcsMap(this) astINVOKE_ISA(WcsMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astWcsMap astINVOKE(F,astWcsMap_)
+#else
+#define astWcsMap astINVOKE(F,astWcsMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitWcsMap(mem,size,init,vtab,name,ncoord,type,lon,lat) \
+astINVOKE(O,astInitWcsMap_(mem,size,init,vtab,name,ncoord,type,lon,lat,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitWcsMapVtab(vtab,name) astINVOKE(V,astInitWcsMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadWcsMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadWcsMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckWcsMap to validate WcsMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+
+#define astWcsPrjType(ctype) astWcsPrjType_(ctype,STATUS_PTR)
+#define astWcsPrjName(type) astWcsPrjName_(type,STATUS_PTR)
+#define astWcsPrjDesc(type) astWcsPrjDesc_(type,STATUS_PTR)
+
+#define astClearPV(this,i,j) \
+astINVOKE(V,astClearPV_(astCheckWcsMap(this),i,j,STATUS_PTR))
+#define astGetPV(this,i,j) \
+astINVOKE(V,astGetPV_(astCheckWcsMap(this),i,j,STATUS_PTR))
+#define astSetPV(this,i,j,par) \
+astINVOKE(V,astSetPV_(astCheckWcsMap(this),i,j,par,STATUS_PTR))
+#define astTestPV(this,i,j) \
+astINVOKE(V,astTestPV_(astCheckWcsMap(this),i,j,STATUS_PTR))
+
+#define astClearFITSProj(this) \
+astINVOKE(V,astClearFITSProj_(astCheckWcsMap(this),STATUS_PTR))
+#define astGetFITSProj(this) \
+astINVOKE(V,astGetFITSProj_(astCheckWcsMap(this),STATUS_PTR))
+#define astSetFITSProj(this,value) \
+astINVOKE(V,astSetFITSProj_(astCheckWcsMap(this),value,STATUS_PTR))
+#define astTestFITSProj(this) \
+astINVOKE(V,astTestFITSProj_(astCheckWcsMap(this),STATUS_PTR))
+
+#define astClearTPNTan(this) \
+astINVOKE(V,astClearTPNTan_(astCheckWcsMap(this),STATUS_PTR))
+#define astGetTPNTan(this) \
+astINVOKE(V,astGetTPNTan_(astCheckWcsMap(this),STATUS_PTR))
+#define astSetTPNTan(this,value) \
+astINVOKE(V,astSetTPNTan_(astCheckWcsMap(this),value,STATUS_PTR))
+#define astTestTPNTan(this) \
+astINVOKE(V,astTestTPNTan_(astCheckWcsMap(this),STATUS_PTR))
+
+#define astGetWcsType(this) \
+astINVOKE(V,astGetWcsType_(astCheckWcsMap(this),STATUS_PTR))
+
+#define astGetPVMax(this,i) \
+astINVOKE(V,astGetPVMax_(astCheckWcsMap(this),i,STATUS_PTR))
+
+#define astGetNatLat(this) \
+astINVOKE(V,astGetNatLat_(astCheckWcsMap(this),STATUS_PTR))
+
+#define astGetNatLon(this) \
+astINVOKE(V,astGetNatLon_(astCheckWcsMap(this),STATUS_PTR))
+
+#define astGetWcsAxis(this,index) \
+astINVOKE(V,astGetWcsAxis_(astCheckWcsMap(this),index,STATUS_PTR))
+
+#define astIsZenithal(this) \
+astINVOKE(V,astIsZenithal_(astCheckWcsMap(this),STATUS_PTR))
+
+#endif
+#endif
+
+
+
+
+
diff --git a/ast/wcsmath.h b/ast/wcsmath.h
new file mode 100644
index 0000000..1f8977a
--- /dev/null
+++ b/ast/wcsmath.h
@@ -0,0 +1,67 @@
+/*=============================================================================
+*
+*   WCSLIB - an implementation of the FITS WCS proposal.
+*   Copyright (C) 1995-2002, Mark Calabretta
+*
+*   This library is free software; you can redistribute it and/or modify it
+*   under the terms of the GNU Library General Public License as published
+*   by the Free Software Foundation; either version 2 of the License, or (at
+*   your option) any later version.
+*
+*   This library is distributed in the hope that it will be useful, but
+*   WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library
+*   General Public License for more details.
+*
+*   You should have received a copy of the GNU Library General Public License
+*   along with this library; if not, write to the Free Software Foundation,
+*   Inc., 51 Franklin Street,Fifth Floor, Boston, MA 02110-1301, USA
+*
+*   Correspondence concerning WCSLIB may be directed to:
+*      Internet email: [email-masked]
+*      Postal address: Dr. Mark Calabretta,
+*                      Australia Telescope National Facility,
+*                      P.O. Box 76,
+*                      Epping, NSW, 2121,
+*                      AUSTRALIA
+*
+*   Author: Mark Calabretta, Australia Telescope National Facility
+*   $Id$
+*=============================================================================
+*
+*  This version of wcstrig.h is based on the version in wcslib-2.9, but has
+*  been modified in the following ways by the Starlink project (e-mail:
+*  [email-masked]):
+*     -  Changed the name of the WCSLIB_MATH macro to WCSLIB_MATH_INCLUDED
+*===========================================================================*/
+
+#ifndef WCSLIB_MATH_INCLUDED
+#define WCSLIB_MATH_INCLUDED
+
+#ifdef PI
+#undef PI
+#endif
+
+#ifdef D2R
+#undef D2R
+#endif
+
+#ifdef R2D
+#undef R2D
+#endif
+
+#ifdef SQRT2
+#undef SQRT2
+#endif
+
+#ifdef SQRT2INV
+#undef SQRT2INV
+#endif
+
+#define PI 3.141592653589793238462643
+#define D2R PI/180.0
+#define R2D 180.0/PI
+#define SQRT2 1.4142135623730950488
+#define SQRT2INV 1.0/SQRT2
+
+#endif /* WCSLIB_MATH_INCLUDED */
diff --git a/ast/wcstrig.c b/ast/wcstrig.c
new file mode 100644
index 0000000..d3ba400
--- /dev/null
+++ b/ast/wcstrig.c
@@ -0,0 +1,189 @@
+/*============================================================================
+*
+*   WCSLIB - an implementation of the FITS WCS proposal.
+*   Copyright (C) 1995-2002, Mark Calabretta
+*
+*   This library is free software; you can redistribute it and/or modify it
+*   under the terms of the GNU Library General Public License as published
+*   by the Free Software Foundation; either version 2 of the License, or (at
+*   your option) any later version.
+*
+*   This library is distributed in the hope that it will be useful, but
+*   WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library
+*   General Public License for more details.
+*
+*   You should have received a copy of the GNU Library General Public License
+*   along with this library; if not, write to the Free Software Foundation, 
+*   Inc., 51 Franklin Street,Fifth Floor, Boston, MA 02110-1301, USA
+*
+*   Correspondence concerning WCSLIB may be directed to:
+*      Internet email: [email-masked]
+*      Postal address: Dr. Mark Calabretta,
+*                      Australia Telescope National Facility,
+*                      P.O. Box 76,
+*                      Epping, NSW, 2121,
+*                      AUSTRALIA
+*
+*=============================================================================
+*
+*  This version of wcstrig.c is based on the version in wcslib-2.9, but has
+*  been modified in the following ways by the Starlink project (e-mail:
+*  [email-masked]):
+*     -  Support for non-ANSI C "const" class removed
+*     -  Changed names of projection functions and degrees trig functions
+*        to avoid clashes with wcslib.
+*=============================================================================
+*
+*   The functions defined herein are trigonometric or inverse trigonometric
+*   functions which take or return angular arguments in decimal degrees.
+*
+*   $Id$
+*---------------------------------------------------------------------------*/
+
+#include <math.h>
+#include "wcsmath.h"
+#include "wcstrig.h"
+
+double astCosd(angle)
+
+const double angle;
+
+{
+   double resid;
+
+   resid = fabs(fmod(angle,360.0));
+   if (resid == 0.0) {
+      return 1.0;
+   } else if (resid == 90.0) {
+      return 0.0;
+   } else if (resid == 180.0) {
+      return -1.0;
+   } else if (resid == 270.0) {
+      return 0.0;
+   }
+
+   return cos(angle*D2R);
+}
+
+/*--------------------------------------------------------------------------*/
+
+double astSind(angle)
+
+const double angle;
+
+{
+   double resid;
+
+   resid = fmod(angle-90.0,360.0);
+   if (resid == 0.0) {
+      return 1.0;
+   } else if (resid == 90.0) {
+      return 0.0;
+   } else if (resid == 180.0) {
+      return -1.0;
+   } else if (resid == 270.0) {
+      return 0.0;
+   }
+
+   return sin(angle*D2R);
+}
+
+/*--------------------------------------------------------------------------*/
+
+double astTand(angle)
+
+const double angle;
+
+{
+   double resid;
+
+   resid = fmod(angle,360.0);
+   if (resid == 0.0 || fabs(resid) == 180.0) {
+      return 0.0;
+   } else if (resid == 45.0 || resid == 225.0) {
+      return 1.0;
+   } else if (resid == -135.0 || resid == -315.0) {
+      return -1.0;
+   }
+
+   return tan(angle*D2R);
+}
+
+/*--------------------------------------------------------------------------*/
+
+double astACosd(v)
+
+const double v;
+
+{
+   if (v >= 1.0) {
+      if (v-1.0 <  WCSTRIG_TOL) return 0.0;
+   } else if (v == 0.0) {
+      return 90.0;
+   } else if (v <= -1.0) {
+      if (v+1.0 > -WCSTRIG_TOL) return 180.0;
+   }
+
+   return acos(v)*R2D;
+}
+
+/*--------------------------------------------------------------------------*/
+
+double astASind(v)
+
+const double v;
+
+{
+   if (v <= -1.0) {
+      if (v+1.0 > -WCSTRIG_TOL) return -90.0;
+   } else if (v == 0.0) {
+      return 0.0;
+   } else if (v >= 1.0) {
+      if (v-1.0 <  WCSTRIG_TOL) return 90.0;
+   }
+
+   return asin(v)*R2D;
+}
+
+/*--------------------------------------------------------------------------*/
+
+double astATand(v)
+
+const double v;
+
+{
+   if (v == -1.0) {
+      return -45.0;
+   } else if (v == 0.0) {
+      return 0.0;
+   } else if (v == 1.0) {
+      return 45.0;
+   }
+
+   return atan(v)*R2D;
+}
+
+/*--------------------------------------------------------------------------*/
+
+double astATan2d(y, x)
+
+const double x, y;
+
+{
+   if (y == 0.0) {
+      if (x >= 0.0) {
+         return 0.0;
+      } else if (x < 0.0) {
+         return 180.0;
+      }
+   } else if (x == 0.0) {
+      if (y > 0.0) {
+         return 90.0;
+      } else if (y < 0.0) {
+         return -90.0;
+      }
+   }
+
+   return atan2(y,x)*R2D;
+}
diff --git a/ast/wcstrig.h b/ast/wcstrig.h
new file mode 100644
index 0000000..36463bb
--- /dev/null
+++ b/ast/wcstrig.h
@@ -0,0 +1,63 @@
+/*=============================================================================
+*
+*   WCSLIB - an implementation of the FITS WCS proposal.
+*   Copyright (C) 1995-2002, Mark Calabretta
+*
+*   This library is free software; you can redistribute it and/or modify it
+*   under the terms of the GNU Library General Public License as published
+*   by the Free Software Foundation; either version 2 of the License, or (at
+*   your option) any later version.
+*
+*   This library is distributed in the hope that it will be useful, but
+*   WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library
+*   General Public License for more details.
+*
+*   You should have received a copy of the GNU Library General Public License
+*   along with this library; if not, write to the Free Software Foundation,
+*   Inc., 51 Franklin Street,Fifth Floor, Boston, MA 02110-1301, USA
+*
+*   Correspondence concerning WCSLIB may be directed to:
+*      Internet email: [email-masked]
+*      Postal address: Dr. Mark Calabretta,
+*                      Australia Telescope National Facility,
+*                      P.O. Box 76,
+*                      Epping, NSW, 2121,
+*                      AUSTRALIA
+*
+*   Author: Mark Calabretta, Australia Telescope National Facility
+*   $Id$
+*=============================================================================
+*
+*  This version of wcstrig.h is based on the version in wcslib-2.9, but has
+*  been modified in the following ways by the Starlink project (e-mail:
+*  [email-masked]):
+*     -  Support for non-ANSI C prototypes removed
+*     -  Changed the name of the WCSLIB_TRIG macro to WCSLIB_TRIG_INCLUDED
+*     -  Changed names of degrees trig functions to avoid clashes with
+*        wcslib.
+*===========================================================================*/
+
+#ifndef WCSLIB_TRIG_INCLUDED
+#define WCSLIB_TRIG_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+double astCosd(const double);
+double astSind(const double);
+double astTand(const double);
+double astACosd(const double);
+double astASind(const double);
+double astATand(const double);
+double astATan2d(const double, const double);
+
+/* Domain tolerance for asin and acos functions. */
+#define WCSTRIG_TOL 1e-10
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* WCSLIB_TRIG_INCLUDED */
diff --git a/ast/winmap.c b/ast/winmap.c
new file mode 100644
index 0000000..5ba97d5
--- /dev/null
+++ b/ast/winmap.c
@@ -0,0 +1,4389 @@
+/*
+*class++
+*  Name:
+*     WinMap
+
+*  Purpose:
+*     Map one window on to another by scaling and shifting each axis.
+
+*  Constructor Function:
+c     astWinMap
+f     AST_WINMAP
+
+*  Description:
+*     A Winmap is a linear Mapping which transforms a rectangular
+*     window in one coordinate system into a similar window in another
+*     coordinate system by scaling and shifting each axis (the window
+*     edges being parallel to the coordinate axes).
+*
+*     A WinMap is specified by giving the coordinates of two opposite
+*     corners (A and B) of the window in both the input and output
+*     coordinate systems.
+
+*  Inheritance:
+*     The WinMap class inherits from the Mapping class.
+
+*  Attributes:
+*     The WinMap class does not define any new attributes beyond those
+*     which are applicable to all Mappings.
+
+*  Functions:
+c     The WinMap class does not define any new functions beyond those
+f     The WinMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David Berry (Starlink)
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     23-OCT-1996 (DSB):
+*        Original version.
+*     4-MAR-1997 (RFWS):
+*        Tidied public prologues.
+*     11-MAR-1997 (DSB):
+*        Added MapMerge method and associated bits.
+*     30-JUN-1997 (DSB):
+*        Bug fixed which caused the MapMerge method to generate a
+*        segmentation violation.
+*     24-MAR-1998 (RFWS):
+*        Improved output format from Dump.
+*     9-APR-1998 (DSB):
+*        MapMerge modified to allow merging of WinMaps with ZoomMaps and
+*        and UnitMaps in parallel.
+*     4-SEP-1998 (DSB):
+*        Improved MapMerge so that WinMaps can change places with a wider
+*        range of PermMaps, allowing them to approach closer to a Mapping
+*        with which they can merge.
+*     22-FEB-1999 (DSB):
+*        Corrected logic of MapMerge method to avoid infinite looping.
+*     5-MAY-1999 (DSB):
+*        More corrections to MapMerge: Cleared up errors in the use of the
+*        supplied invert flags, and corrected logic for deciding which
+*        neighbouring Mapping to swap with.
+*     16-JUL-1999 (DSB):
+*        Fixed memory leaks in WinMat and MapMerge.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitWinMapVtab
+*        method.
+*     8-SEP-2003 (DSB):
+*        Allow WinMaps to swap with WcsMaps if possible.
+*     10-NOV-2003 (DSB):
+*        Modified functions which swap a WinMap with another Mapping
+*        (e.g. WinPerm, etc), to simplify the returned Mappings.
+*     23-APR-2004 (DSB):
+*        Changes to simplification algorithm.
+*     1-SEP-2004 (DSB):
+*        Ensure do1 and do2 are initialised before use in MapMerge.
+*     7-SEP-2005 (DSB):
+*        Take account of the Invert flag when using the soom factor from
+*        a ZoomMap.
+*     14-FEB-2006 (DSB):
+*        Override astGetObjSize.
+*     15-MAR-2006 (DSB):
+*        Override astEqual.
+*     23-AUG-2006 (DSB):
+*        Correct initialisation of "result" in the Equal function.
+*     19-JAN-2007 (DSB):
+*        Fix memory leak.
+*     3-MAY-2013 (DSB):
+*        Improve simplification by adding check for inverse pairs of
+*        WinMaps in function WinWin.
+*     23-APR-2015 (DSB):
+*        Improve MapMerge. If a WinMap can merge with its next-but-one
+*        neighbour, then swap the WinMap with its neighbour, so that
+*        it is then next its next-but-one neighbour, and then merge the
+*        two Mappings into a single Mapping. Previously, only the swap
+*        was performed - not the merger. And the swap was only performed
+*        if the intervening neighbour could not itself merge. This could
+*        result in an infinite simplification loop, which was detected by
+*        CmpMap and and aborted, resulting in no useful simplification.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS WinMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory management facilities */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "matrixmap.h"           /* Linear mappings */
+#include "unitmap.h"             /* Unit mappings */
+#include "zoommap.h"             /* Zoom mappings */
+#include "permmap.h"             /* Axis permutations */
+#include "cmpmap.h"              /* Compound mappings */
+#include "wcsmap.h"              /* Celestial projections */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "winmap.h"              /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static int (* parent_getobjsize)( AstObject *, int * );
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+
+#ifdef THREAD_SAFE
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(WinMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(WinMap,Class_Init)
+#define class_vtab astGLOBAL(WinMap,Class_Vtab)
+
+
+#include <pthread.h>
+
+
+#else
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstWinMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstWinMap *astWinMapId_( int, const double [], const double [],
+                         const double [], const double [], const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static AstWinMap *WinUnit( AstWinMap *, AstUnitMap *, int, int, int * );
+static AstWinMap *WinWin( AstMapping *, AstMapping *, int, int, int, int * );
+static AstWinMap *WinZoom( AstWinMap *, AstZoomMap *, int, int, int, int, int * );
+static int GetObjSize( AstObject *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int CanSwap( AstMapping *, AstMapping *, int, int, int *, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetIsLinear( AstMapping *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int WinTerms( AstWinMap *, double **, double **, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void PermGet( AstPermMap *, int **, int **, double **, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void WinMat( AstMapping **, int *, int, int * );
+static void WinPerm( AstMapping **, int *, int, int * );
+static void WinWcs( AstMapping **, int *, int, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+
+/* Member functions. */
+/* ================= */
+static int CanSwap( AstMapping *map1, AstMapping *map2, int inv1, int inv2,
+                    int *simpler, int *status ){
+/*
+*  Name:
+*     CanSwap
+
+*  Purpose:
+*     Determine if two Mappings could be swapped.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     int CanSwap( AstMapping *map1, AstMapping *map2, int inv1, int inv2,
+*                  int *simpler, int *status )
+
+*  Class Membership:
+*     WinMap member function
+
+*  Description:
+*     This function returns a flag indicating if the pair of supplied
+*     Mappings could be replaced by an equivalent pair of Mappings from the
+*     same classes as the supplied pair, but in reversed order. Each pair
+*     of Mappings is considered to be compunded in series. The supplied
+*     Mapings are not changed in any way.
+
+*  Parameters:
+*     map1
+*        The Mapping to be applied first.
+*     map2
+*        The Mapping to be applied second.
+*     inv1
+*        The invert flag to use with map1. A value of zero causes the forward
+*        mapping to be used, and a non-zero value causes the inverse
+*        mapping to be used.
+*     inv2
+*        The invert flag to use with map2.
+*     simpler
+*        Addresss of a location at which to return a flag indicating if
+*        the swapped Mappings would be intrinsically simpler than the
+*        original Mappings.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     1 if the Mappings could be swapped, 0 otherwise.
+
+*  Notes:
+*     -  One of the supplied pair of Mappings must be a WinMap.
+*     -  A value of 0 is returned if the two Mappings could be merged into
+*     a single Mapping.
+*     -  A value of 0 is returned if an error has already occurred, or if
+*     this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstMapping *nowin;        /* Pointer to non-WinMap Mapping */
+   AstWinMap *win;           /* Pointer to the WinMap */
+   const char *class1;       /* Pointer to map1 class string */
+   const char *class2;       /* Pointer to map2 class string */
+   const char *nowin_class;  /* Pointer to non-WinMap class string */
+   double *consts;           /* Pointer to constants array */
+   int *inperm;              /* Pointer to input axis permutation array */
+   int *outperm;             /* Pointer to output axis permutation array */
+   int axlat;                /* Latitude axis in WcsMap */
+   int axlon;                /* Longitude axis in WcsMap */
+   int i;                    /* Loop count */
+   int invert[ 2 ];          /* Original invert flags */
+   int nin;                  /* No. of input coordinates for the PermMap */
+   int nout;                 /* No. of output coordinates for the PermMap */
+   int ret;                  /* Returned flag */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise */
+   ret = 0;
+   *simpler = 0;
+
+/* Temporarily set the Invert attributes of both Mappings to the supplied
+   values. */
+   invert[ 0 ] = astGetInvert( map1 );
+   astSetInvert( map1, inv1 );
+
+   invert[ 1 ] = astGetInvert( map2 );
+   astSetInvert( map2, inv2 );
+
+/* Get the classes of the two mappings. */
+   class1 = astGetClass( map1 );
+   class2 = astGetClass( map2 );
+   if( astOK ){
+
+/* Get a pointer to the non-WinMap Mapping. */
+      if( !strcmp( class1, "WinMap" ) ){
+         nowin = map2;
+         nowin_class = class2;
+         win = (AstWinMap *) map1;
+      } else {
+         nowin = map1;
+         nowin_class = class1;
+         win = (AstWinMap *) map2;
+      }
+
+/* If it is a MatrixMap, the Mappings can be swapped. */
+      if( !strcmp( nowin_class, "MatrixMap" ) ){
+         ret = 1;
+
+/* If it is a WcsMap, the Mappings can be swapped if the WinMap is
+   equivalent to a unit transformation on the celestial axes of the
+   WcsMap. */
+      } else if( !strcmp( nowin_class, "WcsMap" ) ){
+
+/* Get the indices of the celestial coordinates inthe WcsMap. */
+         axlat = astGetWcsAxis( (AstWcsMap *) nowin, 1 );
+         axlon = astGetWcsAxis( (AstWcsMap *) nowin, 0 );
+
+/* Check the shift and scale for these axes. */
+         ret = ( win->a[ axlon ] == 0.0 && win->b[ axlon ] == 1.0 &&
+                 win->a[ axlat ] == 0.0 && win->b[ axlat ] == 1.0 );
+
+/* If it is a PermMap, the Mappings can be swapped so long as all links
+   between input and output axes in the PermMap are bi-directional. This
+   does not preclude the existence of unconnected axes, which do not
+   have links (bi-directional or otherwise). */
+      } else if( !strcmp( nowin_class, "PermMap" ) ){
+
+/* Get the number of input and output coordinates. */
+         nin = astGetNin( nowin );
+         nout = astGetNout( nowin );
+
+/* We need to know the axis permutation arrays and constants array for
+   the PermMap. */
+         PermGet( (AstPermMap *) nowin, &outperm, &inperm, &consts, status );
+         if( astOK ) {
+
+/* Indicate we can swap with the PermMap. */
+            ret = 1;
+
+/* Check each output axis. If any links between axes are found which are
+   not bi-directional, indicate that we cannot swap with the PermMap. */
+            for( i = 0; i < nout; i++ ){
+               if( outperm[ i ] >= 0 && outperm[ i ] < nin ) {
+                  if( inperm[ outperm[ i ] ] != i ) {
+                     ret = 0;
+                     break;
+                  }
+               }
+            }
+
+/* Check each input axis. If any links between axes are found which are
+   not bi-directional, indicate that we cannot swap with the PermMap. */
+            for( i = 0; i < nin; i++ ){
+               if( inperm[ i ] >= 0 && inperm[ i ] < nout ) {
+                  if( outperm[ inperm[ i ] ] != i ) {
+                     ret = 0;
+                     break;
+                  }
+               }
+            }
+
+/* If we can swap with the PermMap, the swapped Mappings may be
+   intrinsically simpler than the original mappings. */
+            if( ret ) {
+
+/* If the PermMap precedes the WinMap, this will be the case if the PermMap
+   has more outputs than inputs. If the WinMap precedes the PermMap, this
+   will be the case if the PermMap has more inputs than outputs. */
+               *simpler = ( nowin == map1 ) ? nout > nin : nin > nout;
+            }
+
+/* Free the axis permutation and constants arrays. */
+            outperm = (int *) astFree( (void *) outperm );
+            inperm = (int *) astFree( (void *) inperm );
+            consts = (double *) astFree( (void *) consts );
+         }
+      }
+   }
+
+/* Re-instate the original settings of the Invert attributes for the
+   supplied MatrixMaps. */
+   astSetInvert( map1, invert[ 0 ] );
+   astSetInvert( map2, invert[ 1 ] );
+
+/* Return the answer. */
+   return astOK ? ret : 0;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a WinMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     WinMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     WinMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the WinMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* At the moment the WinMap class has no attributes, so pass it on to the
+   parent method for further interpretation. */
+   (*parent_clearattrib)( this_object, attrib, status );
+
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two WinMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     WinMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two WinMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a WinMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the WinMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstWinMap *that;
+   AstWinMap *this;
+   double *a_that;
+   double *a_this;
+   double *b_that;
+   double *b_this;
+   int i;
+   int nin;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two WinMap structures. */
+   this = (AstWinMap *) this_object;
+   that = (AstWinMap *) that_object;
+
+/* Check the second object is a WinMap. We know the first is a
+   WinMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAWinMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      if( astGetNin( that ) == nin ) {
+
+/* Assume the WinMaps are equivalent. */
+         result = 1;
+
+/* Compare the shift and scale terms from both WinMaps ignoring the
+   setting of the Invert flag for the moment. */
+         for( i = 0; i < nin; i++ ) {
+            if( !astEQUAL( this->a[ i ], that->a[ i ] ) ||
+                !astEQUAL( this->b[ i ], that->b[ i ] ) ) {
+               result = 0;
+               break;
+            }
+         }
+
+/* If the scale and shifts are equal, check the Invert flags are equal. */
+         if( result ) {
+            result= ( astGetInvert( this ) == astGetInvert( that ) );
+
+/* If the scale and shifts differ, there is still a chance that the
+   WinMaps may be equivalent if their Invert flags differ. */
+         } else if( astGetInvert( this ) != astGetInvert( that ) ) {
+
+/* Create copies of the scale and shift terms from the two WinMaps, taking
+   into account the setting of the Invert attribute. Finding the inverted
+   terms involves arithmetic which introduces rounding errors, so this
+   test is not as reliable as the above direct comparison of terms. */
+            astWinTerms( this, &a_this, &b_this );
+            astWinTerms( that, &a_that, &b_that );
+            result = 1;
+
+            for( i = 0; i < nin; i++ ) {
+               if( !astEQUAL( a_this[ i ], a_that[ i ] ) ||
+                   !astEQUAL( b_this[ i ], b_that[ i ] ) ) {
+                  result = 0;
+                  break;
+               }
+            }
+
+/* Free resources */
+            a_this = astFree( a_this );
+            a_that = astFree( a_that );
+            b_this = astFree( b_this );
+            b_that = astFree( b_that );
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static int GetIsLinear( AstMapping *this_mapping, int *status ){
+/*
+*  Name:
+*     GetIsLinear
+
+*  Purpose:
+*     Return the value of the IsLinear attribute for a WinMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GetIsLinear( AstMapping *this, int *status )
+
+*  Class Membership:
+*     WinMap member function (over-rides the protected astGetIsLinear
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns the value of the IsLinear attribute for a
+*     Frame, which is always one.
+
+*  Parameters:
+*     this
+*        Pointer to the WinMap.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   return 1;
+}
+
+static int GetObjSize( AstObject *this_object, int *status ) {
+/*
+*  Name:
+*     GetObjSize
+
+*  Purpose:
+*     Return the in-memory size of an Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     int GetObjSize( AstObject *this, int *status )
+
+*  Class Membership:
+*     WinMap member function (over-rides the astGetObjSize protected
+*     method inherited from the parent class).
+
+*  Description:
+*     This function returns the in-memory size of the supplied WinMap,
+*     in bytes.
+
+*  Parameters:
+*     this
+*        Pointer to the WinMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The Object size, in bytes.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstWinMap *this;         /* Pointer to WinMap structure */
+   int result;                /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointers to the WinMap structure. */
+   this = (AstWinMap *) this_object;
+
+/* Invoke the GetObjSize method inherited from the parent class, and then
+   add on any components of the class structure defined by thsi class
+   which are stored in dynamically allocated memory. */
+   result = (*parent_getobjsize)( this_object, status );
+   result += astTSizeOf( this->a );
+   result += astTSizeOf( this->b );
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a WinMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     WinMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a WinMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the WinMap.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the WinMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the WinMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Constants: */
+#define BUFF_LEN 50              /* Max. characters in result buffer */
+
+/* Local Variables: */
+   const char *result;           /* Pointer value to return */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* At the moment the WinMap class has no attributes, so pass it on to the
+   parent method for further interpretation. */
+   result = (*parent_getattrib)( this_object, attrib, status );
+
+/* Return the result. */
+   return result;
+
+/* Undefine macros local to this function. */
+#undef BUFF_LEN
+}
+
+void astInitWinMapVtab_(  AstWinMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitWinMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a WinMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     void astInitWinMapVtab( AstWinMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     WinMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the WinMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAWinMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->WinTerms = WinTerms;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+   parent_getobjsize = object->GetObjSize;
+   object->GetObjSize = GetObjSize;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+   mapping->MapSplit = MapSplit;
+   mapping->Rate = Rate;
+   mapping->GetIsLinear = GetIsLinear;
+
+/* Declare the class dump, copy and delete functions.*/
+   astSetDump( vtab, Dump, "WinMap", "Map one window on to another" );
+   astSetCopy( (AstObjectVtab *) vtab, Copy );
+   astSetDelete( (AstObjectVtab *) vtab, Delete );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a WinMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     WinMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated WinMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated WinMap with a Mapping which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated WinMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the WinMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        WinMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated WinMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstCmpMap *cm;        /* Pointer to neighbouring CmpMap */
+   AstMapping **maplt;   /* New mappings list pointer */
+   AstMapping *map2;     /* Pointer to replacement Mapping */
+   AstMapping *mc[2];    /* Copies of supplied Mappings to swap */
+   AstMapping *nc[2];    /* Copies of neighbouring Mappings to merge */
+   AstMapping *smc0;     /* Simplified Mapping */
+   AstMapping *smc1;     /* Simplified Mapping */
+   AstMapping *simp1;    /* Simplified Mapping */
+   AstMapping *simp2;    /* Simplified Mapping */
+   AstMatrixMap *mtr;    /* Pointer to replacement MatrixMap */
+   AstWinMap *newwm2;    /* Second component WinMap */
+   AstWinMap *newwm;     /* Pointer to replacement WinMap */
+   AstWinMap *oldwm;     /* Pointer to supplied WinMap */
+   const char *class1;   /* Pointer to first Mapping class string */
+   const char *class2;   /* Pointer to second Mapping class string */
+   const char *nclass;   /* Pointer to neighbouring Mapping class */
+   double *a;            /* Pointer to zero terms */
+   double *b;            /* Pointer to scale terms */
+   int *invlt;           /* New invert flags list pointer */
+   int cmlow;            /* Is lower neighbour a CmpMap? */
+   int diag;             /* Is WinMap equivalent to a diagonal matrix? */
+   int do1;              /* Would a backward swap make a simplification? */
+   int do2;              /* Would a forward swap make a simplification? */
+   int i1;               /* Index of first WinMap to merge */
+   int i2;               /* Index of last WinMap to merge */
+   int i;                /* Loop counter */
+   int ic[2];            /* Copies of supplied invert flags to swap */
+   int inc[4];           /* Copies of supplied invert flags to merge */
+   int invert;           /* Should the inverted Mapping be used? */
+   int nin2;             /* No. of inputs for second component WinMap */
+   int nin;              /* Number of coordinates for WinMap */
+   int nmapt;            /* No. of Mappings in list */
+   int nstep1;           /* No. of Mappings backwards to next mergable Mapping */
+   int nstep2;           /* No. of Mappings forward to next mergable Mapping */
+   int old_winv;         /* original Invert value for supplied WinMap */
+   int result;           /* Result value to return */
+   int ser;              /* Are Mappings applied in series? */
+   int simpler;          /* Is the resulting Mapping simpler than original? */
+   int swap;             /* Is there an advantage in swapping mappings? */
+   int swaphi;           /* Can WinMap be swapped with higher neighbour? */
+   int swaplo;           /* Can WinMap be swapped with lower neighbour? */
+
+/* Initialise. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   i1 = 0;
+   i2 = 0;
+
+/* Get the number of axes for the WinMap. */
+   nin = astGetNin( ( *map_list )[ where ] );
+
+/* Get a pointer to the WinMap. */
+   oldwm = (AstWinMap *) this;
+
+/* First of all, see if the WinMap can be replaced by a simpler Mapping,
+   without reference to the neighbouring Mappings in the list.           */
+/* ======================================================================*/
+/* If the shift terms in the WinMap are all zero, the WinMap can be
+   replaced by a diagonal MatrixMap (which is faster to compute). Check the
+   shift terms. */
+   diag = 1;
+   newwm = (AstWinMap *) ( *map_list )[ where ];
+   for( i = 0; i < nin; i++ ){
+      if( !astEQUAL( ( newwm->a )[ i ], 0.0 ) ){
+         diag = 0;
+         break;
+      }
+   }
+
+/* If all the shift terms are zero... */
+   if( diag ){
+
+/* Temporarily set the Invert attribute of the WinMap to the supplied
+   value. */
+      old_winv = astGetInvert( newwm );
+      astSetInvert( newwm, ( *invert_list )[ where ] );
+
+/* Get a copy of the scale terms from the WinMap. */
+      astWinTerms( newwm, NULL, &b );
+
+/* Create a diagonal MatrixMap holding the scale terms. */
+      mtr = astMatrixMap( nin, nin, 1, b, "", status );
+
+/* Restore the Invert attribute of the supplied WinMap. */
+      astSetInvert( newwm, old_winv );
+
+/* Free the memory used to hold the scale terms. */
+      b = (double *) astFree( (void *) b );
+
+/* Annul the WinMap pointer in the list and replace it with the MatrixMap
+   pointer, and indicate that the forward transformation of the returned
+   MatrixMap should be used. */
+      (void) astAnnul( ( *map_list )[ where ] );
+      ( *map_list )[ where ] = (AstMapping *) mtr;
+      ( *invert_list )[ where ] = 0;
+
+/* Return the index of the first modified element. */
+      result = where;
+
+/* If the WinMap itself could not be simplified, see if it can be merged
+   with the Mappings on either side of it in the list. */
+   } else {
+
+/* Store the classes of the neighbouring Mappings in the list. */
+       class1 = ( where > 0 ) ? astGetClass( ( *map_list )[ where - 1 ] ) : NULL;
+       class2 = ( where < *nmap - 1 ) ? astGetClass( ( *map_list )[ where + 1 ] ) : NULL;
+
+/* In series. */
+/* ========== */
+      if ( series ) {
+
+/* We first look to see if the WinMap can be merged with one of its
+   neighbours, resulting in a reduction of one in the number of Mappings
+   in the list. WinMaps can only merge directly with another WinMap, a
+   ZoomMap, or a UnitMap. */
+         if( class1 && ( !strcmp( class1, "WinMap" ) ||
+                         !strcmp( class1, "ZoomMap" ) ||
+                         !strcmp( class1, "UnitMap" ) ) ){
+            nclass = class1;
+            i1 = where - 1;
+            i2 = where;
+
+         } else if( class2 && ( !strcmp( class2, "WinMap" ) ||
+                                !strcmp( class2, "ZoomMap" ) ||
+                                !strcmp( class2, "UnitMap" ) ) ){
+            nclass = class2;
+            i1 = where;
+            i2 = where + 1;
+
+         } else {
+            nclass = NULL;
+         }
+
+/* If the WinMap can merge with one of its neighbours, create the merged
+   Mapping. */
+         if( nclass ){
+
+            if( !strcmp( nclass, "WinMap" ) ){
+               newwm = WinWin( ( *map_list )[ i1 ], ( *map_list )[ i2 ],
+                               ( *invert_list )[ i1 ], ( *invert_list )[ i2 ],
+                               1, status );
+               invert = 0;
+
+            } else if( !strcmp( nclass, "ZoomMap" ) ){
+               if( i1 == where ){
+                  newwm = WinZoom( (AstWinMap *)( *map_list )[ i1 ],
+                                   (AstZoomMap *)( *map_list )[ i2 ],
+                           ( *invert_list )[ i1 ], ( *invert_list )[ i2 ], 1, 1, status );
+               } else {
+                  newwm = WinZoom( (AstWinMap *)( *map_list )[ i2 ],
+                                   (AstZoomMap *)( *map_list )[ i1 ],
+                           ( *invert_list )[ i2 ], ( *invert_list )[ i1 ], 0, 1, status );
+               }
+               invert = 0;
+
+            } else {
+               newwm = astClone( ( *map_list )[ where ] );
+               invert = ( *invert_list )[ where ];
+            }
+
+/* If succesfull... */
+            if( astOK ){
+
+/* Annul the first of the two Mappings, and replace it with the merged
+   WinMap. Also set the invert flag. */
+               (void) astAnnul( ( *map_list )[ i1 ] );
+               ( *map_list )[ i1 ] = (AstMapping *) newwm;
+               ( *invert_list )[ i1 ] = invert;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+               (void) astAnnul( ( *map_list )[ i2 ] );
+               for ( i = i2 + 1; i < *nmap; i++ ) {
+                  ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+                  ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+               }
+
+/* Clear the vacated element at the end. */
+               ( *map_list )[ *nmap - 1 ] = NULL;
+               ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+               ( *nmap )--;
+               result = i1;
+
+            }
+
+/* If one of the neighbours is a (parallel) CmpMap, we convert the WinMap
+   into an equivalent parallel CmpMap, and then merge this parallel
+   CmpMap with the neighbouring parallel CmpMap to create a parallel CmpMap
+   containing two series CmpMaps. */
+         } else if( ( class1 && !strcmp( "CmpMap", class1 ) ) ||
+                    ( class2 && !strcmp( "CmpMap", class2 ) ) ) {
+
+/* Identify the WinMap and the CmpMap. */
+            if( class1 && !strcmp( "CmpMap", class1 ) ) {
+               i1 = where - 1;
+               i2 = where;
+               cm = (AstCmpMap *) ( *map_list )[ where - 1 ];
+               cmlow = 1;
+
+            } else {
+               i1 = where;
+               i2 = where + 1;
+               cm = (AstCmpMap *) ( *map_list )[ where + 1 ];
+               cmlow = 0;
+
+            }
+
+/* Temporarily set the required Invert attributes in the two Mappings. */
+            inc[ 0 ] = astGetInvert( ( *map_list )[ i1 ] );
+            astSetInvert( ( *map_list )[ i1 ], ( *invert_list )[ i1 ] );
+
+            inc[ 1 ] = astGetInvert( ( *map_list )[ i2 ] );
+            astSetInvert( ( *map_list )[ i2 ], ( *invert_list )[ i2 ] );
+
+/* Now get pointers to the scale and zero terms of the nominated WinMap
+   (these describe the forward transformation, taking into account the
+   setting of the Invert flag). */
+            (void) astWinTerms( oldwm , &a, &b );
+
+/* Get pointers to the two components of the parallel CmpMap. */
+            astDecompose( cm, mc, mc + 1, &ser, ic, ic + 1 );
+
+/* Check component Mappings are combined in parallel. */
+            map2 = NULL;
+            if( astOK && !ser ) {
+
+/* Temporarily set the required Invert attributes in the two component
+   Mappings to the indicated values. */
+               inc[ 2 ] = astGetInvert( mc[ 0 ] );
+               astSetInvert( mc[ 0 ], ic[ 0 ] );
+
+               inc[ 3 ] = astGetInvert( mc[ 1 ] );
+               astSetInvert( mc[ 1 ], ic[ 1 ] );
+
+/* Create the first of two corresponding WinMaps, initially with undefined
+   corners. These could be combined into a parallel CmpMap which would be
+   equivalent to the nominated WinMap. The number of inputs for each WinMap
+   is equal to either the number of outputs or inputs of the corresponding
+   component of the CmpMap, depending on whether the CmpMap is upper or lower
+   neighbour. */
+               nin = cmlow ? astGetNout( mc[ 0 ] ):astGetNin( mc[ 0 ] );
+               newwm = astWinMap( nin, NULL, NULL, NULL, NULL, "", status );
+               if( astOK ) {
+
+/* Store the first "nin" scale and zero terms from the nominated WinMap
+   in the new WinMap. */
+                  for( i = 0; i < nin; i++ ) {
+                     (newwm->a)[ i ] = a[ i ];
+                     (newwm->b)[ i ] = b[ i ];
+                  }
+               }
+
+/* Now create the second WinMap in the same way, which transforms the
+   remaining outputs of the CmpMap. */
+               nin2 = cmlow ? astGetNout( mc[ 1 ] ):astGetNin( mc[ 1 ] );
+               newwm2 = astWinMap( nin2, NULL, NULL, NULL, NULL, "", status );
+               if( astOK ) {
+
+/* Store the remaining scale and zero terms from the nominated WinMap
+   in the new WinMap. */
+                  for( i = 0; i < nin2; i++ ) {
+                     (newwm2->a)[ i ] = a[ i + nin ];
+                     (newwm2->b)[ i ] = b[ i + nin ];
+                  }
+               }
+
+/* Combine the two corresponding lower component Mappings into a series
+   CmpMap, and likewise combine the two corresponding upper component
+   Mappings into a series CmpMap. */
+               if( cmlow ) {
+                  nc[ 0 ] = (AstMapping *) astCmpMap( mc[ 0 ], newwm, 1, "", status );
+                  nc[ 1 ] = (AstMapping *) astCmpMap( mc[ 1 ], newwm2, 1, "", status );
+               } else {
+                  nc[ 0 ] = (AstMapping *) astCmpMap( newwm, mc[ 0 ], 1, "", status );
+                  nc[ 1 ] = (AstMapping *) astCmpMap( newwm2, mc[ 1 ], 1, "", status );
+               }
+               newwm = astAnnul( newwm );
+               newwm2 = astAnnul( newwm2 );
+
+/* Attempt to simplify each of the two new series CmpMaps. If neither of
+   them simplify then there is no point in doing the current merger. In fact
+   it would be dangerous to do so since we may end up in an infinite loop
+   where the resulting parallel CmpMap gets converted back into the
+   existing series CmpMap by the CmpMap MapMerge method, and then back
+   again by this method, etc. */
+               simp1 = astSimplify( nc[ 0 ] );
+               simp2 = astSimplify( nc[ 1 ] );
+
+/* Test if either could be simplified by checking if its pointer value
+   has changed. */
+               simpler = ( simp1 != nc[ 0 ] ) || ( simp2 != nc[ 1 ] );
+
+/* If either CmpMap was simplified, then combine the two series CmpMap into
+   a single parallel CmpMap. */
+               if( simpler ) {
+                  map2 = (AstMapping *) astCmpMap( simp1, simp2, 0, "", status );
+               }
+
+/* Re-instate the original Invert attributes in the two component Mappings. */
+               astSetInvert( mc[ 0 ], inc[ 2 ] );
+               astSetInvert( mc[ 1 ], inc[ 3 ] );
+
+/* Free resources. */
+               simp1 = astAnnul( simp1 );
+               simp2 = astAnnul( simp2 );
+               nc[ 0 ] = astAnnul( nc[ 0 ] );
+               nc[ 1 ] = astAnnul( nc[ 1 ] );
+
+            }
+
+/* Free resources. */
+            mc[ 0 ] = astAnnul( mc[ 0 ] );
+            mc[ 1 ] = astAnnul( mc[ 1 ] );
+            a = astFree( a );
+            b = astFree( b );
+
+/* Re-instate the original Invert attributes. */
+            astSetInvert( ( *map_list )[ i1 ], inc[ 0 ] );
+            astSetInvert( ( *map_list )[ i2 ], inc[ 1 ] );
+
+/* If the above produced a new Mapping, annul the supplied pointers for
+   the two merged Mappings, store the pointer for the new merged Mapping,
+   and shuffle the remaining Mappings down to fill the space left. Nullify
+   the end slot which is no longer used, reduce the number of Mappings in
+   the list by 1, and return the index of the first modified Mapping. */
+            if( map2 ) {
+               (void) astAnnul( ( *map_list )[ i1 ] );
+               (void) astAnnul( ( *map_list )[ i2 ] );
+               ( *map_list )[ i1 ] = map2;
+               ( *invert_list )[ i1 ] = 0;
+               for( i = i2 + 1; i < *nmap; i++ ){
+                  ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+                  ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+               }
+               ( *map_list )[ *nmap - 1 ] = NULL;
+               (*nmap)--;
+               result = i1;
+            }
+
+/* If the WinMap could not merge directly with either of its neighbours,
+   we consider whether it would be worthwhile to swap the WinMap with
+   either of its neighbours. This can only be done for certain classes
+   of Mapping (MatrixMap & some PermMaps & WcsMaps), and will usually require both
+   Mappings to be modified (unless they are commutative). The advantage of
+   swapping the order of the Mappings is that it may result in the WinMap
+   being adjacent to a Mapping with which it can merge directly on the next
+   invocation of this function, thus reducing the number of Mappings
+   in the list. */
+         } else {
+
+/* Set a flag if we could swap the WinMap with its higher neighbour. "do2"
+   is returned if swapping the Mappings would simplify either of the
+   Mappings. */
+            if( where + 1 < *nmap ){
+               swaphi = CanSwap(  ( *map_list )[ where ],
+                                  ( *map_list )[ where + 1 ],
+                                  ( *invert_list )[ where ],
+                                  ( *invert_list )[ where + 1 ], &do2, status );
+            } else {
+               swaphi = 0;
+               do2 = 0;
+            }
+
+/* If so, step through each of the Mappings which follow the WinMap,
+   looking for a Mapping with which the WinMap could merge directly. Stop
+   when such a Mapping is found, or if a Mapping is found with which the
+   WinMap could definitely not swap. Note the number of Mappings which
+   separate the WinMap from the Mapping with which it could merge (if
+   any). */
+            nstep2 = -1;
+            if( swaphi ){
+               for( i2 = where + 1; i2 < *nmap; i2++ ){
+
+/* See if we can merge with this Mapping. If so, note the number of steps
+   between the two Mappings and leave the loop. */
+                  nclass = astGetClass( ( *map_list )[ i2 ] );
+                  if( !strcmp( nclass, "WinMap" ) ||
+                      !strcmp( nclass, "ZoomMap" ) ||
+                      !strcmp( nclass, "UnitMap" ) ) {
+                     nstep2 = i2 - where - 1;
+                     break;
+                  }
+
+/* If there is no chance that we can swap with this Mapping, leave the loop
+   with -1 for the number of steps to indicate that no merging is possible.
+   WinMaps can swap with MatrixMaps and some PermMaps. */
+                  if( strcmp( nclass, "MatrixMap" ) &&
+                      strcmp( nclass, "WcsMap" ) &&
+                      strcmp( nclass, "PermMap" ) ) {
+                     break;
+                  }
+
+               }
+
+            }
+
+/* Do the same working forward from the WinMap towards the start of the map
+   list. */
+            if( where > 0 ){
+               swaplo = CanSwap(  ( *map_list )[ where - 1 ],
+                                  ( *map_list )[ where ],
+                                  ( *invert_list )[ where - 1 ],
+                                  ( *invert_list )[ where ], &do1, status );
+            } else {
+               swaplo = 0;
+               do1 = 0;
+            }
+
+            nstep1 = -1;
+            if( swaplo ){
+               for( i1 = where - 1; i1 >= 0; i1-- ){
+
+                  nclass = astGetClass( ( *map_list )[ i1 ] );
+                  if( !strcmp( nclass, "WinMap" ) ||
+                      !strcmp( nclass, "ZoomMap" ) ||
+                      !strcmp( nclass, "UnitMap" ) ) {
+                     nstep1 = where - 1 - i1;
+                     break;
+                  }
+
+                  if( strcmp( nclass, "MatrixMap" ) &&
+                      strcmp( nclass, "WcsMap" ) &&
+                      strcmp( nclass, "PermMap" ) ) {
+                     break;
+                  }
+
+               }
+
+            }
+
+/* Choose which neighbour to swap with so that the WinMap moves towards the
+   nearest Mapping with which it can merge. */
+            if( do1 || (
+                nstep1 != -1 && ( nstep2 == -1 || nstep2 > nstep1 ) ) ){
+               nclass = class1;
+               i1 = where - 1;
+               i2 = where;
+            } else if( do2 || nstep2 != -1 ){
+               nclass = class2;
+               i1 = where;
+               i2 = where + 1;
+            } else {
+               nclass = NULL;
+            }
+
+/* If there is a target Mapping in the list with which the WinMap could
+   merge, replace the supplied Mappings with swapped Mappings to bring a
+   WinMap closer to the target Mapping. */
+            if( nclass ){
+
+/* Swap the Mappings. */
+               if( !strcmp( nclass, "MatrixMap" ) ){
+                  WinMat( (*map_list) + i1, (*invert_list) + i1, where - i1, status );
+
+               } else if( !strcmp( nclass, "PermMap" ) ){
+                  WinPerm( (*map_list) + i1, (*invert_list) + i1, where - i1, status );
+
+               } else if( !strcmp( nclass, "WcsMap" ) ){
+                  WinWcs( (*map_list) + i1, (*invert_list) + i1, where - i1, status );
+               }
+
+/* And then merge them if possible. */
+               if( where == i1 && where + 1 < *nmap ) {    /* Merging upwards */
+                  map2 = astClone( (*map_list)[ where + 1 ] );
+                  nmapt = *nmap - where - 1;
+                  maplt = *map_list + where + 1;
+                  invlt = *invert_list + where + 1;
+
+                  (void) astMapMerge( map2, 0, series, &nmapt, &maplt, &invlt );
+                  map2 = astAnnul( map2 );
+                  *nmap = where + 1 + nmapt;
+
+               } else if( where - 2 >= 0 ) {               /* Merging downwards */
+                  map2 = astClone( (*map_list)[ where - 2 ] );
+                  nmapt = *nmap - where + 2;
+                  maplt = *map_list + where - 2 ;
+                  invlt = *invert_list + where - 2;
+
+                  (void) astMapMerge( map2, 0, series, &nmapt, &maplt, &invlt );
+                  map2 = astAnnul( map2 );
+                  *nmap = where - 2 + nmapt;
+               }
+
+               result = i1;
+
+/* If there is no Mapping available for merging, it may still be
+   advantageous to swap with a neighbour because the swapped Mapping may
+   be simpler than the original Mappings. For instance, a PermMap may
+   strip axes of the WinMap leaving only a UnitMap. Also, the two neighbours
+   may be able to merge. */
+            } else if( swaphi || swaplo ) {
+
+/* Try swapping with each possible neighbour in turn. */
+               for( i = 0; i < 2; i++ ) {
+
+/*  Set up the class and pointers for the mappings to be swapped, first
+    the lower neighbour, then the upper neighbour. */
+                  if( i == 0 && swaplo ){
+                     nclass = class1;
+                     i1 = where - 1;
+                     i2 = where;
+
+                  } else if( i == 1 && swaphi ){
+                     nclass = class2;
+                     i1 = where;
+                     i2 = where + 1;
+
+                  } else {
+                     nclass = NULL;
+                  }
+
+/* If we have a Mapping to swap with... */
+                  if( nclass ) {
+
+/* Take copies of the Mapping and Invert flag arrays so we do not change
+   the supplied values. */
+                     mc[ 0 ] = (AstMapping *) astCopy( ( (*map_list) + i1 )[0] );
+                     mc[ 1 ] = (AstMapping *) astCopy( ( (*map_list) + i1 )[1] );
+                     ic[ 0 ] = ( (*invert_list) + i1 )[0];
+                     ic[ 1 ] = ( (*invert_list) + i1 )[1];
+
+/* Swap these Mappings. */
+                     if( !strcmp( nclass, "MatrixMap" ) ){
+                        WinMat( mc, ic, where - i1, status );
+                     } else if( !strcmp( nclass, "PermMap" ) ){
+                        WinPerm( mc, ic, where - i1, status );
+                     } else if( !strcmp( nclass, "WcsMap" ) ){
+                        WinWcs( mc, ic, where - i1, status );
+                     }
+
+/* See if the two neighbouring Mappings can merge now that the nominated
+   Mapping is no longer in between them. First get a list of Mapping
+   pointers containing the two Mappings to be merged, and associated
+   invert flags. */
+                     if( i == 0 && where != *nmap - 1 ) {
+                        nc[ 0 ] = astClone( mc[ 1 ] );
+                        nc[ 1 ] = astClone( (*map_list)[ where + 1 ] );
+                        inc[ 0 ] = ic[ 1 ];
+                        inc[ 1 ] = (*invert_list)[ where + 1 ];
+
+                     } else if( i == 1 && where > 0 ) {
+                        nc[ 0 ] = astClone( (*map_list)[ where - 1 ] );
+                        nc[ 1 ] = astClone( mc[ 0 ] );
+                        inc[ 0 ] = (*invert_list)[ where - 1 ];
+                        inc[ 1 ] = ic[ 0 ];
+
+                     } else {
+                        nc[ 0 ] = NULL;
+                        nc[ 1 ] = NULL;
+                     }
+
+/* If both neighbours are available, use astMapMerge to see if it is
+   possible to merge the two Mappings. */
+                     swap = 0;
+                     if( nc[ 0 ] && nc[ 1 ] ) {
+                        nmapt = 2;
+                        maplt = nc;
+                        invlt = inc;
+                        map2 = astClone( nc[ 0 ] );
+                        swap = astMapMerge( map2, 0, series, &nmapt, &maplt, &invlt );
+                        map2 = astAnnul( map2 );
+                        if( swap == -1 ) {
+                           map2 = astClone( nc[ 1 ] );
+                           swap = astMapMerge( map2, 1, series, &nmapt, &maplt, &invlt );
+                           map2 = astAnnul( map2 );
+                        }
+                        swap = ( nmapt < 2 ) ? 1 : 0;
+                     }
+
+/* Free resources. */
+                     if( nc[ 0 ] ) nc[ 0 ] = astAnnul( nc[ 0 ] );
+                     if( nc[ 1 ] ) nc[ 1 ] = astAnnul( nc[ 1 ] );
+
+/* If the neighbours could not merge, see if either swapped Mapping can
+   be simplified. */
+                     if( !swap ) {
+                        smc0 = astSimplify( mc[0] );
+                        if(  smc0 != mc[0] ) {
+                           swap = 1;
+                        } else {
+                           smc1 = astSimplify( mc[1] );
+                           swap = ( smc1 != mc[1] );
+                           smc1 = astAnnul( smc1 );
+                        }
+                        smc0 = astAnnul( smc0 );
+                     }
+
+/* If there is some point in swapping the Mappings, swap them in the
+   supplied lists. Otherwise annul the swapped Mappings. */
+                     if( swap ) {
+                        (*map_list)[ i1 ] = astAnnul( (*map_list)[ i1 ] );
+                        (*map_list)[ i2 ] = astAnnul( (*map_list)[ i2 ] );
+                        (*map_list)[ i1 ] = mc[ 0 ];
+                        (*map_list)[ i2 ] = mc[ 1 ];
+                        (*invert_list)[ i1 ] = ic[ 0 ];
+                        (*invert_list)[ i2 ] = ic[ 1 ];
+                        result = i1;
+                        break;
+
+                     } else {
+                        mc[ 0 ] = astAnnul( mc[ 0 ] );
+                        mc[ 1 ] = astAnnul( mc[ 1 ] );
+                     }
+                  }
+               }
+            }
+         }
+
+/* In parallel. */
+/* ============ */
+/* WinMaps are combined in parallel with neighbouring WinMaps, ZoomMaps and
+   UnitMaps. */
+      } else {
+
+/* We first look to see if the WinMap can be merged with one of its
+   neighbours, resulting in a reduction of one in the number of Mappings
+   in the list. WinMaps can only merge directly with another WinMap, a
+   ZoomMap, or a UnitMap. */
+         if( class1 && ( !strcmp( class1, "WinMap" ) ||
+                         !strcmp( class1, "ZoomMap" ) ||
+                         !strcmp( class1, "UnitMap" ) ) ){
+            nclass = class1;
+            i1 = where - 1;
+            i2 = where;
+
+         } else if( class2 && ( !strcmp( class2, "WinMap" ) ||
+                                !strcmp( class2, "ZoomMap" ) ||
+                                !strcmp( class2, "UnitMap" ) ) ){
+            nclass = class2;
+            i1 = where;
+            i2 = where + 1;
+
+         } else {
+            nclass = NULL;
+         }
+
+/* If the WinMap can merge with one of its neighbours, create the merged
+   Mapping. */
+         if( nclass ){
+
+            if( !strcmp( nclass, "WinMap" ) ){
+               newwm = WinWin( ( *map_list )[ i1 ], ( *map_list )[ i2 ],
+                               ( *invert_list )[ i1 ], ( *invert_list )[ i2 ],
+                               0, status );
+               invert = 0;
+
+            } else if( !strcmp( nclass, "ZoomMap" ) ){
+               if( i1 == where ){
+                  newwm = WinZoom( (AstWinMap *)( *map_list )[ i1 ],
+                                   (AstZoomMap *)( *map_list )[ i2 ],
+                           ( *invert_list )[ i1 ], ( *invert_list )[ i2 ], 1, 0, status );
+               } else {
+                  newwm = WinZoom( (AstWinMap *)( *map_list )[ i2 ],
+                                   (AstZoomMap *)( *map_list )[ i1 ],
+                           ( *invert_list )[ i2 ], ( *invert_list )[ i1 ], 0, 0, status );
+               }
+               invert = 0;
+
+            } else {
+               if( i1 == where ){
+                  newwm = WinUnit( (AstWinMap *)( *map_list )[ i1 ],
+                                   (AstUnitMap *)( *map_list )[ i2 ],
+                                   ( *invert_list )[ i1 ], 1, status );
+               } else {
+                  newwm = WinUnit( (AstWinMap *)( *map_list )[ i2 ],
+                                   (AstUnitMap *)( *map_list )[ i1 ],
+                                   ( *invert_list )[ i2 ], 0, status );
+               }
+               invert = 0;
+
+            }
+
+/* If succesfull... */
+            if( astOK ){
+
+/* Annul the first of the two Mappings, and replace it with the merged
+   WinMap. Also set the invert flag. */
+               (void) astAnnul( ( *map_list )[ i1 ] );
+               ( *map_list )[ i1 ] = (AstMapping *) newwm;
+               ( *invert_list )[ i1 ] = invert;
+
+/* Annul the second of the two Mappings, and shuffle down the rest of the
+   list to fill the gap. */
+               (void) astAnnul( ( *map_list )[ i2 ] );
+               for ( i = i2 + 1; i < *nmap; i++ ) {
+                  ( *map_list )[ i - 1 ] = ( *map_list )[ i ];
+                  ( *invert_list )[ i - 1 ] = ( *invert_list )[ i ];
+               }
+
+/* Clear the vacated element at the end. */
+               ( *map_list )[ *nmap - 1 ] = NULL;
+               ( *invert_list )[ *nmap - 1 ] = 0;
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+               ( *nmap )--;
+               result = i1;
+
+            }
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     WinMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     WinMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing WinMap. This is only possible if the specified inputs
+*     correspond to some subset of the WinMap outputs. That is, there
+*     must exist a subset of the WinMap outputs for which each output
+*     depends only on the selected WinMap inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied WinMap, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the WinMap to be split (the WinMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied WinMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied WinMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied WinMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstWinMap *newwm;          /* Pointer to returned WinMap */
+   AstWinMap *this;           /* Pointer to WinMap structure */
+   double *a;                 /* Pointer to zero terms */
+   double *b;                 /* Pointer to scale terms */
+   int *result;               /* Pointer to returned array */
+   int i;                     /* Loop count */
+   int iin;                   /* Mapping input index */
+   int mnin;                  /* No. of Mapping inputs */
+   int ok;                    /* Are input indices OK? */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the WinMap structure. */
+   this = (AstWinMap *) this_map;
+
+/* Allocate memory for the returned array and create a WinMap with the
+   required number of axes and undefined corners. */
+   result = astMalloc( sizeof( int )*(size_t) nin );
+   newwm = astWinMap( nin, NULL, NULL, NULL, NULL, "", status );
+   *map = (AstMapping *) newwm;
+
+/* Now get pointers to the scale and zero terms of the supplied WinMap
+   (these describe the forward transformation, taking into account the
+   setting of the Invert flag). */
+   (void) astWinTerms( this , &a, &b );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Store the required scale and zero terms from the supplied WinMap
+   in the new WinMap. At the same time check that each axis is valid. */
+      mnin = astGetNin( this );
+      ok = 1;
+      for( i = 0; i < nin; i++ ) {
+         iin = in[ i ];
+         if( iin >= 0 && iin < mnin ) {
+            (newwm->a)[ i ] = a[ iin ];
+            (newwm->b)[ i ] = b[ iin ];
+            result[ i ] = iin;
+         } else {
+            ok = 0;
+            break;
+         }
+      }
+
+/* If the "in" array contained any invalid values, free the returned
+   resources. */
+      if( !ok ) {
+         result = astFree( result );
+         *map = astAnnul( *map );
+      }
+   }
+
+/* Free resources. */
+   a = astFree( a );
+   b = astFree( b );
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static void PermGet( AstPermMap *map, int **outperm, int **inperm,
+                     double **consts, int *status ){
+/*
+*  Name:
+*     PermGet
+
+*  Purpose:
+*     Get the axis permutation and constants array for a PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     void PermGet( AstPermMap *map, int **outperm, int **inperm,
+*                   double **const, int *status )
+
+*  Class Membership:
+*     WinMap member function
+
+*  Description:
+*     This function returns axis permutation and constants arrays which can
+*     be used to create a PermMap which is equivalent to the supplied PermMap.
+
+*  Parameters:
+*     map
+*        The PermMap.
+*     outperm
+*        An address at which to return a popinter to an array of ints
+*        holding the output axis permutation array. The array should be
+*        released using astFree when no longer needed.
+*     inperm
+*        An address at which to return a popinter to an array of ints
+*        holding the input axis permutation array. The array should be
+*        released using astFree when no longer needed.
+*     consts
+*        An address at which to return a popinter to an array of doubles
+*        holding the constants array. The array should be released using
+*        astFree when no longer needed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  NULL pointers are returned if an error has already occurred, or if
+*     this function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstPointSet *pset1;       /* PointSet holding input positions for PermMap */
+   AstPointSet *pset2;       /* PointSet holding output positions for PermMap */
+   double **ptr1;            /* Pointer to pset1 data */
+   double **ptr2;            /* Pointer to pset2 data */
+   double *cnst;             /* Pointer to constants array */
+   double cn;                /* Potential new constant value */
+   double ip;                /* Potential output axis index */
+   double op;                /* Potential input axis index */
+   int *inprm;               /* Pointer to input axis permutation array */
+   int *outprm;              /* Pointer to output axis permutation array */
+   int i;                    /* Axis count */
+   int nc;                   /* Number of constants stored so far */
+   int nin;                  /* No. of input coordinates for the PermMap */
+   int nout;                 /* No. of output coordinates for the PermMap */
+
+/* Initialise. */
+   if( outperm ) *outperm = NULL;
+   if( inperm ) *inperm = NULL;
+   if( consts ) *consts = NULL;
+
+/* Check the global error status and the supplied pointers. */
+   if ( !astOK || !outperm || !inperm || !consts ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   nc = 0;
+
+/* Get the number of input and output axes for the supplied PermMap. */
+   nin = astGetNin( map );
+   nout = astGetNout( map );
+
+/* Allocate the memory for the returned arrays. */
+   outprm = (int *) astMalloc( sizeof( int )* (size_t) nout );
+   inprm = (int *) astMalloc( sizeof( int )* (size_t) nin );
+   cnst = (double *) astMalloc( sizeof( double )* (size_t) ( nout + nin ) );
+
+/* Returned the pointers to these arrays.*/
+   *outperm = outprm;
+   *inperm = inprm;
+   *consts = cnst;
+
+/* Create two PointSets, each holding two points, which can be used for
+   input and output positions with the PermMap. */
+   pset1 = astPointSet( 2, nin, "", status );
+   pset2 = astPointSet( 2, nout, "", status );
+
+/* Set up the two input positions to be [0,1,2...] and [-1,-1,-1,...]. The
+   first position is used to enumerate the axes, and the second is used to
+   check for constant axis values. */
+   ptr1 = astGetPoints( pset1 );
+   if( astOK ){
+      for( i = 0; i < nin; i++ ){
+         ptr1[ i ][ 0 ] = ( double ) i;
+         ptr1[ i ][ 1 ] = -1.0;
+      }
+   }
+
+/* Use the PermMap to transform these positions in the forward direction. */
+   (void) astTransform( map, pset1, 1, pset2 );
+
+/* Look at the mapped positions to determine the output axis permutation
+   array. */
+   ptr2 = astGetPoints( pset2 );
+   if( astOK ){
+
+/* No constant axis valeus found yet. */
+      nc = 0;
+
+/* Do each output axis. */
+      for( i = 0; i < nout; i++ ){
+
+/* If the output axis value is copied from an input axis value, the index
+   of the appropriate input axis will be in the mapped first position. */
+         op = ptr2[ i ][ 0 ];
+
+/* If the output axis value is assigned a constant value, the result of
+   mapping the two different input axis values will be the same. */
+         cn = ptr2[ i ][ 1 ];
+         if( op == cn ) {
+
+/* We have found another constant. Store it in the constants array, and
+   store the index of the constant in the output axis permutation array. */
+            cnst[ nc ] = cn;
+            outprm[ i ] = -( nc + 1 );
+            nc++;
+
+/* If the output axis values are different, then the output axis value
+   must be copied from the input axis value. */
+         } else {
+            outprm[ i ] = (int) ( op + 0.5 );
+         }
+      }
+   }
+
+/* Now do the same thing to determine the input permutation array. */
+   if( astOK ){
+      for( i = 0; i < nout; i++ ){
+         ptr2[ i ][ 0 ] = ( double ) i;
+         ptr2[ i ][ 1 ] = -1.0;
+      }
+   }
+
+   (void) astTransform( map, pset2, 0, pset1 );
+
+   if( astOK ){
+
+      for( i = 0; i < nin; i++ ){
+
+         ip = ptr1[ i ][ 0 ];
+         cn = ptr1[ i ][ 1 ];
+         if( ip == cn ) {
+
+            cnst[ nc ] = cn;
+            inprm[ i ] = -( nc + 1 );
+            nc++;
+
+         } else {
+            inprm[ i ] = (int) ( ip + 0.5 );
+         }
+      }
+   }
+
+/* Annul the PointSets. */
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* If an error has occurred, attempt to free the returned arrays. */
+   if( !astOK ) {
+      *outperm = (int *) astFree( (void *) *outperm );
+      *inperm = (int *) astFree( (void *) *inperm );
+      *consts = (double *) astFree( (void *) *consts );
+   }
+
+/* Return. */
+   return;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     WinMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*/
+
+/* Local Variables: */
+   AstWinMap *map;
+   double result;
+
+/* Check inherited status */
+   if( !astOK ) return AST__BAD;
+
+/* Get a pointer to the WinMap structure. */
+   map = (AstWinMap *) this;
+
+/* If the input and output axes are not equal the result is zero. */
+   if( ax1 != ax2 ) {
+      result = 0.0;
+
+/* Otherwise, return the scale factor for the axis, taking the reciprocal
+   if the WinMap has been inverted. */
+   } else {
+      result = ( map->b )[ ax1 ];
+      if( astGetInvert( map ) ) {
+         if( result != 0.0 && result != AST__BAD ) {
+            result = 1.0/result;
+         } else {
+            result = AST__BAD;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a WinMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     WinMap member function (over-rides the astSetAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a WinMap, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the WinMap.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* The WinMap class currently has no attributes, so pass it on to the parent
+   method for further interpretation. */
+   (*parent_setattrib)( this_object, setting, status );
+
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a WinMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     WinMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a WinMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the WinMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* The WinMap class currently has no attributes, so pass it on to the parent
+   method for further interpretation. */
+   result = (*parent_testattrib)( this_object, attrib, status );
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a WinMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     WinMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a WinMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to map them into the
+*     required window.
+
+*  Parameters:
+*     this
+*        Pointer to the WinMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the WinMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstWinMap *map;               /* Pointer to WinMap to be applied */
+   const char *class;            /* Object class */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double *axin;                 /* Pointer to next input axis value */
+   double *axout;                /* Pointer to next output axis value */
+   double *a;                    /* Pointer to next constant term */
+   double *b;                    /* Pointer to next multiplicative term */
+   double aa;                    /* Constant term */
+   double bb;                    /* Multiplicative term */
+   int coord;                    /* Loop counter for coordinates */
+   int def;                      /* Is mapping defined? */
+   int ncoord;                   /* Number of coordinates per point */
+   int npoint;                   /* Number of points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   aa = 0.0;
+   bb = 0.0;
+
+/* Obtain a pointer to the WinMap. */
+   map = (AstWinMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   ncoord = astGetNcoord( in );
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse mapping, according to the
+   direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* Report an error if the WinMap does not contain any scales or shifts. */
+   if( !(map->a && map->b) && astOK ){
+      class = astGetClass( this );
+      astError( AST__BADWM, "astTransform(%s): The supplied %s does not "
+                "contain any window information.", status, class, class );
+   }
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+   if( astOK ){
+
+/* Store pointers to the shift and scale for the next axis. */
+      a = map->a;
+      b = map->b;
+
+/* Apply the mapping to each axis. */
+      for( coord = 0; coord < ncoord; coord++ ){
+
+/* If either the scale or shift is bad indicate that the mapping is
+   not defined on this axis. */
+         if( *a == AST__BAD || *b == AST__BAD ){
+            def = 0;
+
+/* Otherwise, get the scale and offset factors for this axis, taking account of
+   whether the mapping is inverted or not. If the mapping is undefined, set
+   the "def" flag to indicate this. */
+         } else {
+            aa = *a;
+            bb = *b;
+
+            if( forward ){
+               def = 1;
+
+            } else if( bb != 0.0 ){
+               bb = 1.0/bb;
+               aa = -aa*bb;
+               def = 1;
+
+            } else {
+               def = 0;
+            }
+
+         }
+
+/* Store pointers to the first inpout and output values on this axis. */
+         axin = ptr_in[ coord ];
+         axout = ptr_out[ coord ];
+
+/* If the mapping is defined, apply it to the supplied points. */
+         if( def ){
+
+            for( point = 0; point < npoint; point++ ){
+               if( *axin != AST__BAD ){
+                  *(axout++) = aa + bb*(*axin);
+               } else {
+                  *(axout++) = AST__BAD;
+               }
+               axin++;
+            }
+
+/* If the mapping is not defined, store bad values on this axis in the
+   returned points. */
+         } else {
+            for( point = 0; point < npoint; point++ ) *(axout++) = AST__BAD;
+         }
+
+/* Point to the scale and shift for the next axis. */
+         a++;
+         b++;
+      }
+
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+static void WinMat( AstMapping **maps, int *inverts, int iwm, int *status ){
+/*
+*  Name:
+*     WinMat
+
+*  Purpose:
+*     Swap a WinMap and a MatrixMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     void WinMat( AstMapping **maps, int *inverts, int iwm, int *status )
+
+*  Class Membership:
+*     WinMap member function
+
+*  Description:
+*     A list of two Mappings is supplied containing a WinMap and a
+*     MatrixMap. These Mappings are annulled, and replaced with
+*     another pair of Mappings consisting of a WinMap and a MatrixMap
+*     in the opposite order. These Mappings are chosen so that their
+*     combined effect is the same as the original pair of Mappings.
+*     The scale factors in the returned WinMap are always unity (i.e.
+*     the differences in scaling get absorbed into the returned
+*     MatrixMap).
+
+*  Parameters:
+*     maps
+*        A pointer to an array of two Mapping pointers.
+*     inverts
+*        A pointer to an array of two invert flags.
+*     iwm
+*        The index within "maps" of the WinMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMatrixMap *m1;             /* Pointer to Diagonal scale factor MatrixMap */
+   AstMatrixMap *m2;             /* Pointer to returned MatrixMap */
+   AstMatrixMap *sm2;            /* Pointer to simplified returned MatrixMap */
+   AstMatrixMap *mm;             /* Pointer to the supplied MatrixMap */
+   AstPointSet *pset1;           /* Shift terms from supplied WinMap */
+   AstPointSet *pset2;           /* Shift terms for returned WinMap */
+   AstWinMap *w1;                /* Pointer to the returned WinMap */
+   AstWinMap *sw1;               /* Pointer to the simplified returned WinMap */
+   AstWinMap *wm;                /* Pointer to the supplied WinMap */
+   double **ptr1;                /* Pointer to pset1 data */
+   double **ptr2;                /* Pointer to pset2 data */
+   double *a;                    /* Array of shift terms from supplied WinMap */
+   double *aa;                   /* Pointer to next shift term */
+   double *b;                    /* Array of scale terms from supplied WinMap */
+   double *bb;                   /* Pointer to next scale term */
+   int i;                        /* Axis count */
+   int nin;                      /* No. of axes in supplied WinMap */
+   int nout;                     /* No. of axes in returned WinMap */
+   int old_minv;                 /* Invert value for the supplied MatrixMap */
+   int old_winv;                 /* Invert value for the supplied WinMap */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Store pointers to the supplied WinMap and the MatrixMap. */
+   wm = (AstWinMap *) maps[ iwm ];
+   mm = (AstMatrixMap *) maps[ 1 - iwm ];
+
+/* Temporarily set the Invert attribute of the supplied Mappings to the
+   supplied values. */
+   old_winv = astGetInvert( wm );
+   astSetInvert( wm, inverts[ iwm ] );
+
+   old_minv = astGetInvert( mm );
+   astSetInvert( mm, inverts[ 1 - iwm ] );
+
+/* Get copies of the shift and scale terms used by the WinMap. This
+   also returns the number of axes in the WinMap. */
+   nin = astWinTerms( wm, &a, &b );
+
+/* Create a diagonal MatrixMap holding the scale factors from the
+   supplied WinMap. */
+   m1 = astMatrixMap( nin, nin, 1, b, "", status );
+
+/* Create a PointSet holding a single position given by the shift terms
+   in the supplied WinMap. */
+   pset1 = astPointSet( 1, nin, "", status );
+   ptr1 = astGetPoints( pset1 );
+   if( astOK ){
+      aa = a;
+      for( i = 0; i < nin; i++ ) ptr1[ i ][ 0 ] = *(aa++);
+   }
+
+/* First deal with cases when the WinMap is applied first, followed by
+   the MatrixMap. */
+   if( iwm == 0 ){
+
+/* Multiply the diagonal matrix holding the WinMap scale factors by the
+   supplied matrix. The resulting MatrixMap is the one to return in the
+   map list. */
+      m2 = astMtrMult( m1, mm );
+
+/* Transform the position given by the shift terms from the supplied
+   WinMap using the supplied MatrixMap to get the shift terms for
+   the returned WinMap. */
+      pset2 = astTransform( mm, pset1, 1, NULL );
+
+/* Now deal with cases when the MatrixMap is applied first, followed by
+   the WinMap. */
+   } else {
+
+/* Multiply the supplied MatrixMap by the diagonal matrix holding scale
+   factors from the supplied WinMap. The resulting MatrixMap is the one to
+   return in the map list. */
+      m2 = astMtrMult( mm, m1 );
+
+/* Transform the position given by the shift terms from the supplied
+   WinMap using the inverse of the returned MatrixMap to get the shift
+   terms for the returned WinMap. */
+      pset2 = astTransform( m2, pset1, 0, NULL );
+
+   }
+
+/* Re-instate the original value of the Invert attributes of the supplied
+   Mappings. */
+   astSetInvert( wm, old_winv );
+   astSetInvert( mm, old_minv );
+
+/* Get pointers to the shift terms for the returned WinMap. */
+   ptr2 = astGetPoints( pset2 );
+
+/* Create the returned WinMap, initially with undefined corners. The number of
+   axes in the WinMap must equal the number of shift terms. */
+   nout = astGetNcoord( pset2 );
+   w1 = astWinMap( nout, NULL, NULL, NULL, NULL, "", status );
+
+/* If succesful, store the scale and shift terms in the WinMap. The scale
+   terms are always unity. */
+   if( astOK ){
+      bb = w1->b;
+      aa = w1->a;
+      for( i = 0; i < nout; i++ ) {
+         *(bb++) = 1.0;
+         *(aa++) = ptr2[ i ][ 0 ];
+      }
+
+/* Replace the supplied Mappings and invert flags with the ones found
+   above. Remember that the order of the Mappings is now swapped */
+      (void) astAnnul( maps[ 0 ] );
+      (void) astAnnul( maps[ 1 ] );
+
+      sw1 = astSimplify( w1 );
+      w1 = astAnnul( w1 );
+
+      maps[ 1 - iwm ] = (AstMapping *) sw1;
+      inverts[ 1 - iwm  ] = astGetInvert( sw1 );
+
+      sm2 = astSimplify( m2 );
+      m2 = astAnnul( m2 );
+
+      maps[ iwm ] = (AstMapping *) sm2;
+      inverts[ iwm  ] = astGetInvert( sm2 );
+
+   }
+
+/* Annul the MatrixMap and PointSet holding the scale and shift terms from the
+   supplied WinMap. */
+   m1 = astAnnul( m1 );
+   pset1 = astAnnul( pset1 );
+   pset2 = astAnnul( pset2 );
+
+/* Free the copies of the scale and shift terms from the supplied WinMap. */
+   b = (double *) astFree( (void *) b );
+   a = (double *) astFree( (void *) a );
+
+/* Return. */
+   return;
+}
+
+static void WinWcs( AstMapping **maps, int *inverts, int iwm, int *status ){
+/*
+*  Name:
+*     WinWcs
+
+*  Purpose:
+*     Swap a WinMap and a WcsMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     void WinWcs( AstMapping **maps, int *inverts, int iwm, int *status )
+
+*  Class Membership:
+*     WinMap member function
+
+*  Description:
+*     A list of two Mappings is supplied containing a WinMap and a
+*     WcsMap. These Mappings are swapped.
+
+*  Parameters:
+*     maps
+*        A pointer to an array of two Mapping pointers.
+*     inverts
+*        A pointer to an array of two invert flags.
+*     iwm
+*        The index within "maps" of the WinMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstMapping *m1;               /* Pointer to a Mapping */
+   int inv;                      /* Invert value */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Simply swap the values (the CanSwap function will have checked that
+   the WcsMap and WinMap can simply be swapped). */
+   m1 = maps[ 0 ];
+   maps[ 0 ] = maps[ 1 ];
+   maps[ 1 ] = m1;
+
+   inv = inverts[ 0 ];
+   inverts[ 0 ] = inverts[ 1 ];
+   inverts[ 1 ] = inv;
+
+/* Return. */
+   return;
+}
+
+static void WinPerm( AstMapping **maps, int *inverts, int iwm, int *status ){
+/*
+*  Name:
+*     WinPerm
+
+*  Purpose:
+*     Swap a WinMap and a PermMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     void WinPerm( AstMapping **maps, int *inverts, int iwm, int *status )
+
+*  Class Membership:
+*     WinMap member function
+
+*  Description:
+*     A list of two Mappings is supplied containing a WinMap and a
+*     PermMap. These Mappings are annulled, and replaced with
+*     another pair of Mappings consisting of a WinMap and a PermMap
+*     in the opposite order. These Mappings are chosen so that their
+*     combined effect is the same as the original pair of Mappings.
+
+*  Parameters:
+*     maps
+*        A pointer to an array of two Mapping pointers.
+*     inverts
+*        A pointer to an array of two invert flags.
+*     iwm
+*        The index within "maps" of the WinMap.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  All links between input and output axes in the PermMap must
+*     be bi-directional, but there can be unconnected axes, and there
+*     need not be the same number of input and output axes.
+
+*/
+
+/* Local Variables: */
+   AstPermMap *pm;               /* Pointer to the supplied PermMap */
+   AstPermMap *p1;               /* Pointer to the returned PermMap */
+   AstPermMap *sp1;              /* Pointer to the simplified returned PermMap */
+   AstWinMap *w1;                /* Pointer to the returned WinMap */
+   AstWinMap *sw1;               /* Pointer to the simplified returned PermMap */
+   AstWinMap *wm;                /* Pointer to the supplied WinMap */
+   double *a;                    /* Array of shift terms from supplied WinMap */
+   double *aa;                   /* Pointer to next shift term */
+   double *b;                    /* Array of scale terms from supplied WinMap */
+   double *bb;                   /* Pointer to next scale term */
+   double *consts;               /* Pointer to constants array */
+   double c;                     /* A constant value */
+   int *inperm;                  /* Pointer to input axis permutation array */
+   int *outperm;                 /* Pointer to output axis permutation array */
+   int i;                        /* Axis count */
+   int j;                        /* Axis index */
+   int nin;                      /* No. of axes in supplied WinMap */
+   int npin;                     /* No. of input axes in supplied PermMap */
+   int npout;                    /* No. of output axes in supplied PermMap */
+   int old_pinv;                 /* Invert value for the supplied PermMap */
+   int old_winv;                 /* Invert value for the supplied WinMap */
+
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Initialise variables to avoid "used of uninitialised variable"
+   messages from dumb compilers. */
+   p1 = NULL;
+   w1 = NULL;
+
+/* Store pointers to the supplied WinMap and the PermMap. */
+   wm = (AstWinMap *) maps[ iwm ];
+   pm = (AstPermMap *) maps[ 1 - iwm ];
+
+/* Temporarily set the Invert attribute of the supplied Mappings to the
+   supplied values. */
+   old_winv = astGetInvert( wm );
+   astSetInvert( wm, inverts[ iwm ] );
+
+   old_pinv = astGetInvert( pm );
+   astSetInvert( pm, inverts[ 1 - iwm ] );
+
+/* Get copies of the shift and scale terms used by the WinMap. This
+   also returns the number of axes in the WinMap. */
+   nin = astWinTerms( wm, &a, &b );
+
+/* Get the axis permutation and constants arrays representing the
+   PermMap. Note, no constants are used more than once in the returned
+   arrays (i.e. duplicate constants are returned in "consts" if more than
+   one axis uses a given constant). */
+   PermGet( pm, &outperm, &inperm, &consts, status );
+
+   if( astOK ) {
+
+/* Get the number of input and output axes in the PermMap. */
+      npin = astGetNin( pm );
+      npout = astGetNout( pm );
+
+/* First consider cases where the WinMap is applied first, followed by the
+   PermMap. */
+      if( iwm == 0 ) {
+
+/* Create the new WinMap, initially with undefined corners. Its number
+   of axes will equal the number of output axes of the PermMap. */
+         w1 = astWinMap( npout, NULL, NULL, NULL, NULL, "", status );
+
+/* Get pointers to the scale and shift terms for the new WinMap. */
+         bb = w1->b;
+         aa = w1->a;
+
+/* Thinking of the forward CmpMap first, consider each of the output axes of
+   the PermMap. */
+         for( i = 0; i < npout; i++ ){
+
+/* If the value for this output axis is derived from an input axis, copy the
+   scale and shift terms from the corresponding input axis to the new
+   WinMap. */
+            j = outperm[ i ];
+            if( j >= 0 && j < nin ) {
+               aa[ i ] = a[ j ];
+               bb[ i ] = b[ j ];
+
+/* If this output axis is assigned a constant value, use zero and one for
+   the shift and scale in order to preserve the constant value produced
+   by the PermMap. */
+            } else {
+               aa[ i ] = 0.0;
+               bb[ i ] = 1.0;
+            }
+
+         }
+
+/* Now consider the inverse CmpMap. Any constants produced by the inverse
+   PermMap would previously have been scaled by the inverse WinMap. Since
+   there will be no inverse WinMap to perform this scaling in the returned
+   Mappings, we need to change the constant values to be the values after
+   the scaling which would have been applied by the WinMap. Consider each
+   of the input axes of the PermMap.*/
+         for( i = 0; i < npin; i++ ){
+
+/* Skip axes which are not assigned a constant value. */
+            if( inperm[ i ] < 0 ) {
+
+/* Scale the constant term associated with this input axis using the
+   inverse WinMap unless it is AST__BAD. */
+               c = consts[ -inperm[ i ] - 1 ];
+               if( c != AST__BAD ) {
+
+                  if( a[ i ] != AST__BAD && b[ i ] != AST__BAD &&
+                      b[ i ] != 0.0 ) {
+                     consts[ -inperm[ i ] - 1 ] = ( c - a[ i ] )/b[ i ];
+                  } else {
+                     consts[ -inperm[ i ] - 1 ] = AST__BAD;
+                  }
+
+               }
+
+            }
+
+         }
+
+/* Now consider cases where the PermMap is applied first, followed by the
+   WinMap. */
+      } else {
+
+/* Create the new WinMap, initially with undefined corners. Its number
+   of axes will equal the number of input axes of the PermMap. */
+         w1 = astWinMap( npin, NULL, NULL, NULL, NULL, "", status );
+
+/* Get pointers to the scale and shift terms for the new WinMap. */
+         bb = w1->b;
+         aa = w1->a;
+
+/* Thinking first about the inverse WinMap, consider each of the input axes
+   of the PermMap. */
+         for( i = 0; i < npin; i++ ){
+
+/* If the value for this input axis is derived from an output axis, copy the
+   scale and shift terms from the corresponding output axis to the new
+   WinMap. */
+            j = inperm[ i ];
+            if( j >= 0 && j < nin ) {
+               aa[ i ] = a[ j ];
+               bb[ i ] = b[ j ];
+
+/* If this input axis is assigned a constant value, use zero and one for
+   the shift and scale in order to preserve the constant value produced
+   by the PermMap. */
+            } else {
+               aa[ i ] = 0.0;
+               bb[ i ] = 1.0;
+            }
+
+         }
+
+/* Now consider the forward WinMap. Any constants produced by the forward
+   PermMap would previously have been scaled by the forward WinMap. Since
+   there will be no forward WinMap to perform this scaling in the returned
+   Mappings, we need to change the constant values to be the values after
+   the scaling which would have been applied by the WinMap. Consider each
+   of the output axes of the PermMap.*/
+         for( i = 0; i < npout; i++ ){
+
+/* Skip axes which are not assigned a constant value. */
+            if( outperm[ i ] < 0 ) {
+
+/* Scale the constant term associated with this input axis using the
+   forward WinMap unless it is AST__BAD. */
+               c = consts[ -outperm[ i ] - 1 ];
+               if( c != AST__BAD ) {
+
+                  if( a[ i ] != AST__BAD && b[ i ] != AST__BAD ) {
+                     consts[ -outperm[ i ] - 1 ] = a[ i ] + c*b[ i ];
+                  } else {
+                     consts[ -outperm[ i ] - 1 ] = AST__BAD;
+                  }
+
+               }
+
+            }
+
+         }
+
+      }
+
+/* Create a new PermMap (since the constants may have changed). */
+      p1 = astPermMap( npin, inperm, npout, outperm, consts, "", status );
+
+/* Free the axis permutation and constants arrays. */
+      outperm = (int *) astFree( (void *) outperm );
+      inperm = (int *) astFree( (void *) inperm );
+      consts = (double *) astFree( (void *) consts );
+   }
+
+/* Re-instate the original value of the Invert attributes of the supplied
+   Mappings. */
+   astSetInvert( wm, old_winv );
+   astSetInvert( pm, old_pinv );
+
+/* Replace the supplied Mappings with the ones created above, swapping the
+   order. */
+   if( astOK ){
+      (void) astAnnul( wm );
+      (void) astAnnul( pm );
+
+      sp1 = astSimplify( p1 );
+      p1 = astAnnul( p1 );
+
+      sw1 = astSimplify( w1 );
+      w1 = astAnnul( w1 );
+
+      maps[ iwm ] = (AstMapping *) sp1;
+      inverts[ iwm ] = 0;
+
+      maps[ 1 - iwm ] = (AstMapping *) sw1;
+      inverts[ 1 - iwm  ] = astGetInvert( sw1 );
+   }
+
+/* Free the copies of the scale and shift terms from the supplied WinMap. */
+   b = (double *) astFree( (void *) b );
+   a = (double *) astFree( (void *) a );
+
+/* Return. */
+   return;
+}
+
+static int WinTerms( AstWinMap *this, double **shift, double **scale, int *status ){
+/*
+*+
+*  Name:
+*     astWinTerms
+
+*  Purpose:
+*     Obtain the scale and shift terms used by a WinMap.
+
+*  Type:
+*     Protected virtual function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     int astWinTerms( AstWinMap *this, double **shift, double **scale )
+
+*  Class Membership:
+*     WinMap mewthod.
+
+*  Description:
+*     This function returns copies of the scale and shift terms used by a
+*     WinMap when transforming points. Each axis of the WinMap has a scale
+*     term B, and a shift term A, and the transformation of a point is done
+*     by applying these to each input axis value X in turn, to get the
+*     output axis value B.X + A. The returned terms take into account the
+*     current setting of the Invert attribute of the WinMap.
+
+*  Parameters:
+*     this
+*        Pointer to the WinMap.
+*     shift
+*        The address of a location at which to return a pointer to the
+*        start of a dynamically allocated array holding the shift terms
+*        for each axis.
+*     scale
+*        The address of a location at which to return a pointer to the
+*        start of a dynamically allocated array holding the scale terms
+*        for each axis.
+
+*  Returned Value:
+*     The number of axes in the WinMap. This is the same as the number of
+*     elements in the returned arrays.
+
+*  Notes:
+*     -  The returned arrays should be released using astFree when no
+*     longer needed.
+*     -  NULL pointers can be supplied for "scale" or "shift" if the
+*     corresponding arrays are not required.
+*     -  A value of zero will be returned, together with NULL pointers
+*     for "scale" and "shift" if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   double *a;             /* Pointer to a copy of the shift term array */
+   double *aa;            /* Pointer to the next shift term */
+   double *b;             /* Pointer to a copy of the scale term array */
+   double *bb;            /* Pointer to the next scale term */
+   int i;                 /* Axis count */
+   int result;            /* The returned number of axes */
+   size_t absize;         /* Size of shift and scale arrays */
+
+/* Initialise. */
+   result = 0;
+   if( scale ) *scale = NULL;
+   if( shift ) *shift = NULL;
+
+/* Check the global status. */
+   if ( !astOK ) return result;
+
+/* Get the number of axes in the WinMap. */
+   result = astGetNin( this );
+
+/* Create copies of the scale and shift terms from the WinMap. */
+   absize = sizeof( double )*(size_t) result;
+   b = (double *) astStore( NULL, (void *) this->b, absize );
+   a = (double *) astStore( NULL, (void *) this->a, absize );
+
+/* Check the pointers can be used. */
+   if( astOK ){
+
+/* If the WinMap is inverted, replace the scale and shift terms
+   by the corresponding values for the inverted mapping. */
+      if( astGetInvert( this ) ){
+         bb = b;
+         aa = a;
+
+         for( i = 0; i < result; i++ ){
+            if( *aa != AST__BAD && *bb != 0.0 && *bb != AST__BAD ){
+               *bb = 1.0/(*bb);
+               *aa *= -(*bb);
+            } else {
+               *bb = AST__BAD;
+               *aa = AST__BAD;
+            }
+
+            aa++;
+            bb++;
+
+         }
+      }
+
+/* Store the required pointers, and free arrays which are not required. */
+      if( scale ){
+         *scale = b;
+      } else {
+         b = (double *) astFree( (void *) b );
+      }
+
+      if( shift ){
+         *shift = a;
+      } else {
+         a = (double *) astFree( (void *) a );
+      }
+
+   }
+
+/* If an error has occurred, free the arrays and return zero. */
+   if( !astOK ){
+      if( scale ) *scale = (double *) astFree( (void *) *scale );
+      if( shift ) *shift = (double *) astFree( (void *) *shift );
+      result = 0;
+   }
+
+/* Return the answer. */
+   return result;
+
+}
+
+static AstWinMap *WinUnit( AstWinMap *wm, AstUnitMap *um, int winv,
+                           int win1, int *status ){
+/*
+*  Name:
+*     WinUnit
+
+*  Purpose:
+*     Create a WinMap by merging a WinMap and a UnitMap in parallel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     AstWinMap *WinUnit( AstWinMap *wm, AstUnitMap *um, int winv, int win1, int *status )
+
+*  Class Membership:
+*     WinMap member function
+
+*  Description:
+*     This function creates a new WinMap which performs a mapping
+*     equivalent to applying the two supplied Mappings in parallel in
+*     the directions specified by the "invert" flag (the Invert
+*     attribute of the supplied WinMap is ignored).
+
+*  Parameters:
+*     wm
+*        A pointer to the WinMap.
+*     um
+*        A pointer to the UnitMap.
+*     winv
+*        The invert flag to use with wm. A value of zero causes the forward
+*        mapping to be used, and a non-zero value causes the inverse
+*        mapping to be used.
+*     win1
+*        Indicates the order in which the Mappings should be applied.
+*
+*        If win1 is non-zero:
+*           "wm" applies to the lower axis indices and "um" to the upper
+*           axis indices.
+*
+*        If win1 is zero:
+*           "um" applies to the lower axis indices and "wm" to the upper
+*           axis indices.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the new WinMap.
+
+*  Notes:
+*     -  The forward direction of the returned WinMap is equivalent to the
+*     combined effect of the two supplied Mappings, operating in the
+*     directions specified by "winv".
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstWinMap *result;            /* Pointer to output WinMap */
+   double *a;                    /* Pointer to shift term array */
+   double *aa;                   /* Pointer to next shift term */
+   double *ar;                   /* Pointer to next shift term in result */
+   double *b;                    /* Pointer to scale term array */
+   double *bb;                   /* Pointer to next scale term */
+   double *br;                   /* Pointer to next scale term in result */
+   int i;                        /* Axis index */
+   int ninw;                     /* No. of axes in the WinMap */
+   int ninu;                     /* No. of axes in the UnitMap */
+   int old_winv;                 /* Original setting of WinMap Invert attribute */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the returned pointer. */
+   result = NULL;
+
+/* Temporarily set the Invert attribute of the WinMap to the supplied
+   value. */
+   old_winv = astGetInvert( wm );
+   astSetInvert( wm, winv );
+
+/* Create copies of the scale and shift terms from the WinMap, and store the
+   number of axes in it. */
+   ninw = astWinTerms( wm, &a, &b );
+
+/* Get the number of axes in the UnitMap. */
+   ninu = astGetNin( um );
+
+/* Create the merged WinMap with unspecified corners. */
+   result = astWinMap( ninw + ninu, NULL, NULL, NULL, NULL, "", status );
+
+/* Check the pointers can be used. */
+   if( astOK ){
+
+/* If the WinMap applies to the lower axis indices... */
+      if( win1 ){
+
+/* Use the scale and shift terms from the WinMap for the lower axes of
+   the new WinMap. */
+         aa = a;
+         bb = b;
+         ar = result->a;
+         br = result->b;
+
+         for( i = 0; i < ninw; i++ ){
+            *(ar++) = *(aa++);
+            *(br++) = *(bb++);
+         }
+
+/* Use the scale factor to 1.0 and the shift term to zero for the upper axes
+   of the new WinMap. */
+         for( i = 0; i < ninu; i++ ){
+            *(ar++) = 0.0;
+            *(br++) = 1.0;
+         }
+
+/* If the WinMap applies to the upper axis indices... */
+      } else {
+
+/* Use the scale factor to 1.0 and the shift term to zero for the lower axes
+   of the new WinMap. */
+         ar = result->a;
+         br = result->b;
+
+         for( i = 0; i < ninu; i++ ){
+            *(ar++) = 0.0;
+            *(br++) = 1.0;
+         }
+
+/* Use the scale and shift terms from the WinMap for the upper axes of
+   the new WinMap. */
+         aa = a;
+         bb = b;
+
+         for( i = 0; i < ninw; i++ ){
+            *(ar++) = *(aa++);
+            *(br++) = *(bb++);
+         }
+      }
+   }
+
+/* Free the copies of the scale and shift terms from the supplied WinMap. */
+   b = (double *) astFree( (void *) b );
+   a = (double *) astFree( (void *) a );
+
+/* Re-instate the original setting of the Invert attribute for the
+   supplied WinMap. */
+   astSetInvert( wm, old_winv );
+
+/* If an error has occurred, annull the returned WinMap. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output WinMap. */
+   return result;
+}
+
+static AstWinMap *WinWin( AstMapping *map1, AstMapping *map2, int inv1,
+                          int inv2, int series, int *status ){
+/*
+*  Name:
+*     WinWin
+
+*  Purpose:
+*     Create a merged WinMap from two supplied WinMaps.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     AstWinMap *WinWin( AstMapping *map1, AstMapping *map2, int inv1,
+*                        int inv2, int series, int *status )
+
+*  Class Membership:
+*     WinMap member function
+
+*  Description:
+*     This function creates a new WinMap which performs a mapping
+*     equivalent to applying the two supplied WinMaps either in series
+*     or parallel in the directions specified by the "invert" flags
+*     (the Invert attributes of the supplied WinMaps are ignored).
+
+*  Parameters:
+*     map1
+*        A pointer to the WinMap to apply first (if in series), or to the
+*        lower axis indices (if in parallel)
+*     map2
+*        A pointer to the WinMap to apply second (if in series), or to the
+*        upper axis indices (if in parallel)
+*     inv1
+*        The invert flag to use with map1. A value of zero causes the forward
+*        mapping to be used, and a non-zero value causes the inverse
+*        mapping to be used.
+*     inv2
+*        The invert flag to use with map2.
+*     series
+*        If non-zero, then the supplied WinMaps are combined in series.
+*        Otherwise, they are combined in parallel.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the new WinMap.
+
+*  Notes:
+*     -  The forward direction of the returned WinMap is equivalent to the
+*     combined effect of the two supplied WinMap, operating in the
+*     directions specified by "inv1" and "inv2".
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstWinMap *result;            /* Pointer to output WinMap */
+   AstWinMap *wm1;               /* Pointer to the first supplied WinMap */
+   AstWinMap *wm2;               /* Pointer to the second supplied WinMap */
+   double *a[ 2 ];               /* Pointers to shift term arrays */
+   double *a0;                   /* Pointer to next shift term from WinMap 1 */
+   double *a1;                   /* Pointer to next shift term from WinMap 2 */
+   double *ar;                   /* Pointer to next shift term in result */
+   double *b[ 2 ];               /* Pointers to scale term arrays */
+   double *b0;                   /* Pointer to next scale term from WinMap 1 */
+   double *b1;                   /* Pointer to next scale term from WinMap 2 */
+   double *br;                   /* Pointer to next scale term in result */
+   double amean;                 /* Geometric mean of the offset terms */
+   int cancel;                   /* Do the two WinMaps cancel out? */
+   int i;                        /* Axis index */
+   int invert[ 2 ];              /* Array of invert flags */
+   int nin[ 2 ];                 /* No. of axes in the two WinMaps */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the returned pointer. */
+   result = NULL;
+
+/* Store pointers to the WinMaps. */
+   wm1 = (AstWinMap *) map1;
+   wm2 = (AstWinMap *) map2;
+
+/* Temporarily set their Invert attributes to the supplied values. */
+   invert[ 0 ] = astGetInvert( wm1 );
+   astSetInvert( wm1, inv1 );
+
+   invert[ 1 ] = astGetInvert( wm2 );
+   astSetInvert( wm2, inv2 );
+
+/* Create copies of the scale and shift terms from the two WinMaps,
+   and store the number of axes in each WinMap. The scale and shift terms
+   returned take into account the setting of the Invert attribute. */
+   nin[ 0 ] = astWinTerms( wm1, a, b );
+   nin[ 1 ] = astWinTerms( wm2, a + 1, b + 1 );
+
+/* Check the pointers can be used. */
+   if( astOK ){
+
+/* Series */
+/* ====== */
+      if( series ){
+
+/* Check for equal and opposite WinMaps. Do this explicitly using the
+   supplied Mappings rather than the values returned by astWinTerms to
+   avoid the affects of rounding errors in the inversions performed by
+   astWinTerms. */
+         if( ( inv1 == 0 ) != ( inv2 == 0 ) ) {
+            cancel = 1;
+            for( i = 0; i < nin[ 0 ]; i++ ){
+               if( !astEQUAL( (wm1->a)[ i ], (wm2->a)[ i ] ) ||
+                   !astEQUAL( (wm1->b)[ i ], (wm2->b)[ i ] ) ) {
+                  cancel = 0;
+                  break;
+               }
+            }
+         } else {
+            cancel = 0;
+         }
+
+/* If they cancel, just put unit values into the WinMap. */
+         if( cancel ) {
+            a0 = a[ 0 ];
+            b0 = b[ 0 ];
+            for( i = 0; i < nin[ 0 ]; i++ ){
+               *(a0++) = 0.0;
+               *(b0++) = 1.0;
+            }
+
+/* Otherwise, merge the scale and shift terms for the two WinMaps, overwriting
+   the terms for the first WinMap. To be merged in series, both WinMaps must
+   have the same number of axes, so it matters not whether we use nin[ 0 ]
+   or nin[ 1 ] to specify the number of axes. Include rounding checks for values
+   close to a unit mapping. */
+         } else {
+            a0 = a[ 0 ];
+            b0 = b[ 0 ];
+            a1 = a[ 1 ];
+            b1 = b[ 1 ];
+            for( i = 0; i < nin[ 0 ]; i++ ){
+
+               if( *a0 != AST__BAD && *b0 != AST__BAD &&
+                   *a1 != AST__BAD && *b1 != AST__BAD ){
+
+                  amean = sqrt(fabs((*a0)*(*a1)));
+
+                  *a0 *= (*b1);
+                  *a0 += (*a1);
+                  *b0 *= (*b1);
+
+                  if( fabs( *a0 ) < amean*1E-15 ) *a0 = 0.0;
+                  if( fabs( *b0 - 1.0 ) < 1E-15 ) *b0 = 1.0;
+
+               } else {
+                  *a0 = AST__BAD;
+                  *b0 = AST__BAD;
+                  *a1 = AST__BAD;
+                  *b1 = AST__BAD;
+               }
+
+/* Move on to the next axis. */
+               a0++;
+               b0++;
+               a1++;
+               b1++;
+            }
+         }
+
+/* Create the merged WinMap with unspecified corners. */
+         result = astWinMap( nin[ 0 ], NULL, NULL, NULL, NULL, "", status );
+
+/* Store the merged scale and shift terms in the new WinMap. The forward
+   transformation of this WinMap then corresponds to the combination of the
+   two supplied WinMaps, taking into account their invert flags. */
+         a0 = a[ 0 ];
+         b0 = b[ 0 ];
+         ar = result->a;
+         br = result->b;
+         for( i = 0; i < nin[ 0 ]; i++ ){
+            *(ar++) = *(a0++);
+            *(br++) = *(b0++);
+         }
+
+/* Parallel */
+/* ======== */
+      } else {
+
+/* Create the merged WinMap with unspecified corners. */
+         result = astWinMap( nin[ 0 ] + nin[ 1 ], NULL, NULL, NULL, NULL, "", status );
+
+/* Copy the scale and shift terms into the new WinMap. */
+         a0 = a[ 0 ];
+         b0 = b[ 0 ];
+         a1 = a[ 1 ];
+         b1 = b[ 1 ];
+         ar = result->a;
+         br = result->b;
+
+         for( i = 0; i < nin[ 0 ]; i++ ){
+            *(ar++) = *(a0++);
+            *(br++) = *(b0++);
+         }
+
+         for( i = 0; i < nin[ 1 ]; i++ ){
+            *(ar++) = *(a1++);
+            *(br++) = *(b1++);
+         }
+      }
+   }
+
+/* Re-instate the original settings of the Invert attributes for the
+   supplied WinMaps. */
+   astSetInvert( wm1, invert[ 0 ] );
+   astSetInvert( wm2, invert[ 1 ] );
+
+/* Free the memory. */
+   a[ 0 ] = (double *) astFree( (void *) a[ 0 ] );
+   b[ 0 ] = (double *) astFree( (void *) b[ 0 ] );
+   a[ 1 ] = (double *) astFree( (void *) a[ 1 ] );
+   b[ 1 ] = (double *) astFree( (void *) b[ 1 ] );
+
+/* If an error has occurred, annull the returned WinMap. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output WinMap. */
+   return result;
+}
+
+static AstWinMap *WinZoom( AstWinMap *wm, AstZoomMap *zm, int winv,
+                           int zinv, int win1, int series, int *status ){
+/*
+*  Name:
+*     WinZoom
+
+*  Purpose:
+*     Create a WinMap by merging a WinMap and a ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     AstWinMap *WinZoom( AstWinMap *wm, AstZoomMap *zm, int winv,
+*                         int zinv, int win1, int series, int *status )
+
+*  Class Membership:
+*     WinMap member function
+
+*  Description:
+*     This function creates a new WinMap which performs a mapping
+*     equivalent to applying the two supplied Mappings in series or
+*     parallel in the directions specified by the "invert" flags (the
+*     Invert attributes of the supplied WinMaps are ignored).
+
+*  Parameters:
+*     wm
+*        A pointer to the WinMap.
+*     zm
+*        A pointer to the ZoomMap.
+*     winv
+*        The invert flag to use with wm. A value of zero causes the forward
+*        mapping to be used, and a non-zero value causes the inverse
+*        mapping to be used.
+*     zinv
+*        The invert flag to use with zm.
+*     win1
+*        Indicates the order in which the Mappings should be applied.
+*
+*        If win1 is non-zero:
+*           If in series:
+*              "wm" is applied first followed by "zm".
+*           If in parallel:
+*              "wm" applies to the lower axis indices and "zm" to the upper
+*              axis indices.
+*
+*        If win1 is zero:
+*           If in series:
+*              "zm" is applied first followed by "wm".
+*           If in parallel:
+*              "zm" applies to the lower axis indices and "wm" to the upper
+*              axis indices.
+*     series
+*        Should be supplied non-zero if the Mappings are to be combined in
+*        series.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the new WinMap.
+
+*  Notes:
+*     -  The forward direction of the returned WinMap is equivalent to the
+*     combined effect of the two supplied Mappings, operating in the
+*     directions specified by "zinv" and "winv".
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstWinMap *result;            /* Pointer to output WinMap */
+   double *a;                    /* Pointer to shift term array */
+   double *aa;                   /* Pointer to next shift term */
+   double *ar;                   /* Pointer to next shift term in result */
+   double *b;                    /* Pointer to scale term array */
+   double *bb;                   /* Pointer to next scale term */
+   double *br;                   /* Pointer to next scale term in result */
+   double zfac;                  /* Zoom factor */
+   int i;                        /* Axis index */
+   int ninw;                     /* No. of axes in the WinMap */
+   int ninz;                     /* No. of axes in the ZoomMap */
+   int old_winv;                 /* Original setting of WinMap Invert attribute */
+   int old_zinv;                 /* Original setting of ZoomMap Invert attribute */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the returned pointer. */
+   result = NULL;
+
+/* Temporarily set the Invert attributes of both Mappings to the supplied
+   values. */
+   old_winv = astGetInvert( wm );
+   astSetInvert( wm, winv );
+
+   old_zinv = astGetInvert( zm );
+   astSetInvert( zm, zinv );
+
+/* Get the zoom factor implemented by the ZoomMap. Invert it if necessary
+   since astGetZoom does not take account of the Invert setting.  */
+   zfac = astGetZoom( zm );
+   if( zinv ) zfac = 1.0 / zfac;
+
+/* Create copies of the scale and shift terms from the WinMap, and store the
+   number of axes in it. */
+   ninw = astWinTerms( wm, &a, &b );
+
+/* Check the pointers can be used. */
+   if( astOK ){
+
+/* First do series mode... */
+      if( series ) {
+
+/* Modify the WinMap scale and shift terms by the zoom factor. How this is
+   done depends on which way round the Mappings are applied. */
+         bb = b;
+         aa = a;
+
+         for( i = 0; i < ninw; i++ ){
+
+            if( *aa != AST__BAD && *bb != AST__BAD && zfac != AST__BAD ){
+               *bb *= zfac;
+               if( win1 ) *aa *= zfac;
+            } else {
+               *bb = AST__BAD;
+               *aa = AST__BAD;
+            }
+
+            aa++;
+            bb++;
+         }
+
+/* Create the merged WinMap with unspecified corners. */
+         result = astWinMap( ninw, NULL, NULL, NULL, NULL, "", status );
+
+/* Store the merged scale and shift terms in the new WinMap. The forward
+   transformation of this WinMap then corresponds to the combination of the
+   two supplied Mappings, taking into account their invert flags. */
+         aa = a;
+         bb = b;
+         ar = result->a;
+         br = result->b;
+         for( i = 0; i < ninw; i++ ){
+            *(ar++) = *(aa++);
+            *(br++) = *(bb++);
+         }
+
+/* Now do parallel mode... */
+      } else {
+
+/* Get the number of axes in the ZoomMap. */
+         ninz = astGetNin( zm );
+
+/* Create the merged WinMap with unspecified corners. */
+         result = astWinMap( ninw + ninz, NULL, NULL, NULL, NULL, "", status );
+
+/* If the WinMap applies to the lower axis indices... */
+         if( win1 ) {
+
+/* Use the scale and shift terms from the WinMap for the lower axes of
+   the new WinMap. */
+            aa = a;
+            bb = b;
+            ar = result->a;
+            br = result->b;
+
+            for( i = 0; i < ninw; i++ ){
+               *(ar++) = *(aa++);
+               *(br++) = *(bb++);
+            }
+
+/* Use the scale factor (with zero shift) from the ZoomMap for the upper axes
+   of the new WinMap. */
+            for( i = 0; i < ninz; i++ ){
+               *(ar++) = 0.0;
+               *(br++) = zfac;
+            }
+
+/* If the WinMap applies to the upper axis indices... */
+         } else {
+
+/* Use the scale factor (with zero shift) from the ZoomMap for the lower axes
+   of the new WinMap. */
+            ar = result->a;
+            br = result->b;
+
+            for( i = 0; i < ninz; i++ ){
+               *(ar++) = 0.0;
+               *(br++) = zfac;
+            }
+
+/* Use the scale and shift terms from the WinMap for the upper axes of
+   the new WinMap. */
+            aa = a;
+            bb = b;
+
+            for( i = 0; i < ninw; i++ ){
+               *(ar++) = *(aa++);
+               *(br++) = *(bb++);
+            }
+         }
+      }
+   }
+
+/* Free the copies of the scale and shift terms from the supplied WinMap. */
+   b = (double *) astFree( (void *) b );
+   a = (double *) astFree( (void *) a );
+
+/* Re-instate the original settings of the Invert attribute for the
+   supplied Mappings. */
+   astSetInvert( wm, old_winv );
+   astSetInvert( zm, old_zinv );
+
+/* If an error has occurred, annull the returned WinMap. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return a pointer to the output WinMap. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for WinMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for WinMap objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the WinMap to be copied.
+*     objout
+*        Pointer to the WinMap being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   AstWinMap *out;              /* Pointer to output WinMap */
+   AstWinMap *in;               /* Pointer to input WinMap */
+   int ncoord;                  /* No. of axes for the mapping */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the input and output WinMaps. */
+   in= (AstWinMap *) objin;
+   out = (AstWinMap *) objout;
+
+/* Get the number of coordinates mapped by the WinMap. */
+   ncoord = astGetNin( in );
+
+/* Allocate memory holding copies of the scales and shifts window defining the
+   mapping. */
+   out->a = (double *) astStore( NULL, (void *) in->a,
+                                  sizeof(double)*(size_t)ncoord );
+   out->b = (double *) astStore( NULL, (void *) in->b,
+                                  sizeof(double)*(size_t)ncoord );
+
+/* If an error occurred, free any allocated memory. */
+   if ( !astOK ) {
+      out->a = (double *) astFree( (void *) out->a );
+      out->b = (double *) astFree( (void *) out->b );
+   }
+
+}
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for WinMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for WinMap objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the WinMap to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - This destructor does nothing and exists only to maintain a
+*     one-to-one correspondence between destructors and copy
+*     constructors.
+*/
+
+/* Local Variables: */
+   AstWinMap *this;              /* Pointer to WinMap */
+
+/* Obtain a pointer to the WinMap structure. */
+   this = (AstWinMap *) obj;
+
+/* Free the memory holding the scales and shifts. */
+   this->a = (double *) astFree( (void *) this->a );
+   this->b = (double *) astFree( (void *) this->b );
+
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for WinMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the WinMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the WinMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define COMMENT_LEN 50           /* Maximum length of a comment string */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstWinMap *this;              /* Pointer to the WinMap structure */
+   char buff[ KEY_LEN + 1 ];     /* Buffer for keyword string */
+   char comment[ COMMENT_LEN + 1 ]; /* Buffer for comment string */
+   int axis;                     /* Axis index */
+   int ncoord;                   /* No. of axes for mapping */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the WinMap structure. */
+   this = (AstWinMap *) this_object;
+
+/* Get the number of coordinates to be mapped. */
+   ncoord = astGetNin( this );
+
+/* Write out values representing the instance variables for the
+   WinMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* The scales and shifts. */
+   for( axis = 0; axis < ncoord; axis++ ){
+      (void) sprintf( buff, "Sft%d", axis + 1 );
+      (void) sprintf( comment, "Shift for axis %d", axis + 1 );
+      astWriteDouble( channel, buff, (this->a)[ axis ] != 0.0, 0,
+                      (this->a)[ axis ], comment );
+      (void) sprintf( buff, "Scl%d", axis + 1 );
+      (void) sprintf( comment, "Scale factor for axis %d", axis + 1 );
+      astWriteDouble( channel, buff, (this->b)[ axis ] != 1.0, 0,
+                      (this->b)[ axis ], comment );
+   }
+
+/* Undefine macros local to this function. */
+#undef COMMENT_LEN
+#undef KEY_LEN
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAWinMap and astCheckWinMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(WinMap,Mapping)
+astMAKE_CHECK(WinMap)
+
+AstWinMap *astWinMap_( int ncoord, const double c1_in[], const double c2_in[],
+                       const double c1_out[], const double c2_out[],
+                       const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astWinMap
+f     AST_WINMAP
+
+*  Purpose:
+*     Create a WinMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "winmap.h"
+c     AstWinMap *astWinMap( int ncoord,
+c                           const double ina[],  const double inb[],
+c                           const double outa[], const double outb[],
+c                           const char *options, ... )
+f     RESULT = AST_WINMAP( NCOORD, INA, INB, OUTA, OUTB, OPTIONS, STATUS )
+
+*  Class Membership:
+*     WinMap constructor.
+
+*  Description:
+*     This function creates a new WinMap and optionally initialises its
+*     attributes.
+*
+*     A Winmap is a linear Mapping which transforms a rectangular
+*     window in one coordinate system into a similar window in another
+*     coordinate system by scaling and shifting each axis (the window
+*     edges being parallel to the coordinate axes).
+*
+*     A WinMap is specified by giving the coordinates of two opposite
+*     corners (A and B) of the window in both the input and output
+*     coordinate systems.
+
+*  Parameters:
+c     ncoord
+f     NCOORD = INTEGER (Given)
+*        The number of coordinate values for each point to be
+*        transformed (i.e. the number of dimensions of the space in
+*        which the points will reside). The same number is applicable
+*        to both input and output points.
+c     ina
+f     INA( NCOORD ) = DOUBLE PRECISION (Given)
+c        An array containing the "ncoord"
+f        An array containing the
+*        coordinates of corner A of the window in the input coordinate
+*        system.
+c     inb
+f     INB( NCOORD ) = DOUBLE PRECISION (Given)
+c        An array containing the "ncoord"
+f        An array containing the
+*        coordinates of corner B of the window in the input coordinate
+*        system.
+c     outa
+f     OUTA( NCOORD ) = DOUBLE PRECISION (Given)
+c        An array containing the "ncoord"
+f        An array containing the
+*        coordinates of corner A of the window in the output coordinate
+*        system.
+c     outb
+f     OUTB( NCOORD ) = DOUBLE PRECISION (Given)
+c        An array containing the "ncoord"
+f        An array containing the
+*        coordinates of corner B of the window in the output coordinate
+*        system.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new WinMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new WinMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astWinMap()
+f     AST_WINMAP = INTEGER
+*        A pointer to the new WinMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstWinMap *new;              /* Pointer to new WinMap */
+   va_list args;                /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the WinMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitWinMap( NULL, sizeof( AstWinMap ), !class_init, &class_vtab,
+                        "WinMap", ncoord, c1_in, c2_in, c1_out, c2_out );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new WinMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new WinMap. */
+   return new;
+}
+
+AstWinMap *astWinMapId_( int ncoord, const double c1_in[], const double c2_in[],
+                         const double c1_out[], const double c2_out[],
+                         const char *options, ... ) {
+/*
+*  Name:
+*     astWinMapId_
+
+*  Purpose:
+*     Create a WinMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     AstWinMap *astWinMapId_( int ncoord, const double c1_in[],
+*                              const double c2_in[], const double c1_out[],
+*                              const double c2_out[],
+*                              const char *options, ... )
+
+*  Class Membership:
+*     WinMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astWinMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astWinMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astWinMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astWinMap_.
+
+*  Returned Value:
+*     The ID value associated with the new WinMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstWinMap *new;              /* Pointer to new WinMap */
+   va_list args;                /* Variable argument list */
+   int *status;                 /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the WinMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitWinMap( NULL, sizeof( AstWinMap ), !class_init, &class_vtab,
+                        "WinMap", ncoord, c1_in, c2_in, c1_out, c2_out );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new WinMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new WinMap. */
+   return astMakeId( new );
+}
+
+AstWinMap *astInitWinMap_( void *mem, size_t size, int init,
+                           AstWinMapVtab *vtab, const char *name,
+                           int ncoord, const double *c1_in,
+                           const double *c2_in, const double *c1_out,
+                           const double *c2_out, int *status ) {
+/*
+*+
+*  Name:
+*     astInitWinMap
+
+*  Purpose:
+*     Initialise a WinMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     AstWinMap *astInitWinMap( void *mem, size_t size, int init,
+*                               AstWinMapVtab *vtab, const char *name,
+*                               int ncoord, const double *c1_in,
+*                               const double *c2_in,
+*                               const double *c1_out, const double *c2_out )
+
+*  Class Membership:
+*     WinMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new WinMap object. It allocates memory (if necessary) to accommodate
+*     the WinMap plus any additional data associated with the derived class.
+*     It then initialises a WinMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a WinMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the WinMap is to be initialised.
+*        This must be of sufficient size to accommodate the WinMap data
+*        (sizeof(WinMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the WinMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the WinMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the WinMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new WinMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     ncoord
+*        The number of coordinate values per point.
+*     c1_in
+*        The input coordinates of corner C1 of the window.
+*     c2_in
+*        The input coordinates of corner C2 of the window.
+*     c1_out
+*        The output coordinates of corner C1 of the window.
+*     c2_out
+*        The output coordinates of corner C2 of the window.
+
+*  Returned Value:
+*     A pointer to the new WinMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstWinMap *new;              /* Pointer to new WinMap */
+   double denom;                /* Denominotor */
+   int axis;                    /* Axis index */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitWinMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the WinMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+   new = (AstWinMap *) astInitMapping( mem, size, 0,
+                                       (AstMappingVtab *) vtab, name,
+                                       ncoord, ncoord, 1, 1 );
+
+   if ( astOK ) {
+
+/* Initialise the WinMap data. */
+/* ---------------------------- */
+/* Allocate memory to hold the shift and scale for each axis. */
+      new->a = (double *) astMalloc( sizeof(double)*(size_t)ncoord );
+      new->b = (double *) astMalloc( sizeof(double)*(size_t)ncoord );
+
+/* Check the pointers can be used */
+      if( astOK ){
+
+/* Calculater and store the shift and scale for each axis. */
+         for( axis = 0; axis < ncoord; axis++ ){
+
+/* If any of the corners have not been provided, store bad values. */
+            if( !c1_in || !c1_out || !c2_in || !c2_out ) {
+               (new->b)[ axis ] = AST__BAD;
+               (new->a)[ axis ] = AST__BAD;
+
+/* Otherwise, check the corners are good (not AST__BAD or NaN)... */
+            } else if( astISGOOD(c2_in[ axis ]) && astISGOOD(c1_in[ axis ]) &&
+                       astISGOOD(c2_out[ axis ]) && astISGOOD(c1_out[ axis ]) ){
+
+               denom = c2_in[ axis ] - c1_in[ axis ];
+               if( denom != 0.0 ){
+                  (new->b)[ axis ] = ( c2_out[ axis ] - c1_out[ axis ] )/denom;
+                  (new->a)[ axis ] = c1_out[ axis ] - (new->b)[ axis ]*c1_in[ axis ];
+               } else {
+                  (new->b)[ axis ] = AST__BAD;
+                  (new->a)[ axis ] = AST__BAD;
+               }
+
+            } else {
+               (new->b)[ axis ] = AST__BAD;
+               (new->a)[ axis ] = AST__BAD;
+            }
+
+         }
+
+      }
+
+/* If an error occurred, clean up by deleting the new WinMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new WinMap. */
+   return new;
+}
+
+AstWinMap *astLoadWinMap_( void *mem, size_t size,
+                           AstWinMapVtab *vtab, const char *name,
+                           AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadWinMap
+
+*  Purpose:
+*     Load a WinMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "winmap.h"
+*     AstWinMap *astLoadWinMap( void *mem, size_t size,
+*                               AstWinMapVtab *vtab, const char *name,
+*                               AstChannel *channel )
+
+*  Class Membership:
+*     WinMap loader.
+
+*  Description:
+*     This function is provided to load a new WinMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     WinMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a WinMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the WinMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        WinMap data (sizeof(WinMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the WinMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the WinMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstWinMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new WinMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the WinMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "WinMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new WinMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Constants. */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+#define KEY_LEN 50               /* Maximum length of a keyword */
+
+/* Local Variables: */
+   AstWinMap *new;              /* Pointer to the new WinMap */
+   char buff[ KEY_LEN + 1 ];    /* Buffer for keyword string */
+   int axis;                    /* Axis index */
+   int ncoord;                  /* The number of coordinate axes */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this WinMap. In this case the
+   WinMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstWinMap );
+      vtab = &class_vtab;
+      name = "WinMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitWinMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built WinMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Get the number of axis for the mapping. */
+      ncoord = astGetNin( (AstMapping *) new );
+
+/* Allocate memory to hold the scales and shifts. */
+      new->a = (double *) astMalloc( sizeof(double)*(size_t)ncoord );
+      new->b = (double *) astMalloc( sizeof(double)*(size_t)ncoord );
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "WinMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* The scales and shifts. */
+      for( axis = 0; axis < ncoord; axis++ ){
+         (void) sprintf( buff, "sft%d", axis + 1 );
+         (new->a)[ axis ] = astReadDouble( channel, buff, 0.0 );
+         (void) sprintf( buff, "scl%d", axis + 1 );
+         (new->b)[ axis ] = astReadDouble( channel, buff, 1.0 );
+      }
+   }
+
+/* If an error occurred, clean up by deleting the new WinMap. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the new WinMap pointer. */
+   return new;
+
+/* Undefine macros local to this function. */
+#undef KEY_LEN
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+int astWinTerms_( AstWinMap *this, double **scale, double **shift, int *status ){
+   if( !astOK ) return 0;
+   return (**astMEMBER(this,WinMap,WinTerms))( this, scale, shift, status );
+}
+
+
+
+
diff --git a/ast/winmap.h b/ast/winmap.h
new file mode 100644
index 0000000..fd05066
--- /dev/null
+++ b/ast/winmap.h
@@ -0,0 +1,300 @@
+#if !defined( WINMAP_INCLUDED ) /* Include this file only once */
+#define WINMAP_INCLUDED
+/*
+*+
+*  Name:
+*     winmap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the WinMap class.
+
+*  Invocation:
+*     #include "winmap.h"
+
+*  Description:
+*     This include file defines the interface to the WinMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The WinMap class implements Mappings which maps one window onto
+*     another window by scaling and shifting the values on each axis.
+
+*  Inheritance:
+*     The WinMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     None.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        ClearAttrib
+*           Clear an attribute value for a WinMap.
+*        GetAttrib
+*           Get an attribute value for a WinMap.
+*        SetAttrib
+*           Set an attribute value for a WinMap.
+*        TestAttrib
+*           Test if an attribute value has been set for a WinMap.
+*        astMapMerge
+*           Simplify a sequence of Mappings containing a WinMap.
+*        astTransform
+*           Apply a WinMap to transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astWinTerms
+*           Obtain copies of the shift and scale terms used by a WinMap.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAWinMap
+*           Test class membership.
+*        astWinMap
+*           Create a WinMap.
+*
+*     Protected:
+*        astCheckWinMap
+*           Validate class membership.
+*        astInitWinMap
+*           Initialise a WinMap.
+*        astInitWinMapVtab
+*           Initialise the virtual function table for the WinMap class.
+*        astLoadWinMap
+*           Load a WinMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstWinMap
+*           WinMap object type.
+*
+*     Protected:
+*        AstWinMapVtab
+*           WinMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     23-OCT-1996 (DSB):
+*        Original version.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitWinMapVtab
+*        method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* WinMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstWinMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double *a;                   /* Pointer to array of shifts */
+   double *b;                   /* Pointer to array of scale factors */
+
+} AstWinMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstWinMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   int (* WinTerms)( AstWinMap *, double **, double **, int * );
+
+} AstWinMapVtab;
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within the
+   object.c file. */
+
+typedef struct AstWinMapGlobals {
+   AstWinMapVtab Class_Vtab;
+   int Class_Init;
+} AstWinMapGlobals;
+
+
+/* Thread-safe initialiser for all global data used by this module. */
+void astInitWinMapGlobals_( AstWinMapGlobals * );
+
+#endif
+
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(WinMap)          /* Check class membership */
+astPROTO_ISA(WinMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstWinMap *astWinMap_( int, const double [], const double [], const double [], const double [], const char *, int *, ...);
+#else
+AstWinMap *astWinMapId_( int, const double [], const double [], const double [], const double [], const char *, ... )__attribute__((format(printf,6,7)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstWinMap *astInitWinMap_( void *, size_t, int, AstWinMapVtab *,
+                           const char *, int, const double *, const double *,
+                           const double *, const double *, int * );
+
+/* Vtab initialiser. */
+void astInitWinMapVtab_( AstWinMapVtab *, const char *, int * );
+
+/* Loader. */
+AstWinMap *astLoadWinMap_( void *, size_t, AstWinMapVtab *,
+                           const char *, AstChannel *, int * );
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+int astWinTerms_( AstWinMap *, double **, double **, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckWinMap(this) astINVOKE_CHECK(WinMap,this,0)
+#define astVerifyWinMap(this) astINVOKE_CHECK(WinMap,this,1)
+
+/* Test class membership. */
+#define astIsAWinMap(this) astINVOKE_ISA(WinMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astWinMap astINVOKE(F,astWinMap_)
+#else
+#define astWinMap astINVOKE(F,astWinMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define \
+astInitWinMap(mem,size,init,vtab,name,ncoord,c1_in,c2_in,c1_out,c2_out) \
+astINVOKE(O,astInitWinMap_(mem,size,init,vtab,name,ncoord,c1_in,c2_in,c1_out,c2_out,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitWinMapVtab(vtab,name) astINVOKE(V,astInitWinMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadWinMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadWinMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckWinMap to validate WinMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astWinTerms(this,scale,shift) \
+astINVOKE(V,astWinTerms_(astCheckWinMap(this),scale,shift,STATUS_PTR))
+#endif
+
+#endif
+
+
+
+
+
diff --git a/ast/xml.c b/ast/xml.c
new file mode 100644
index 0000000..f14cb22
--- /dev/null
+++ b/ast/xml.c
@@ -0,0 +1,7119 @@
+/*
+*  Name:
+*     xml.c
+
+*  Purpose:
+*     Implement XML functions for AST.
+
+*  Description:
+*     This file implements the Xml module which provides generic XML
+*     reading and writing functions for the XmlChan class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     22-OCT-2003 (DSB):
+*        Original version.
+*     12-JAN-2004 (DSB):
+*        Major revisions.
+*     10-FEB-2004 (DSB):
+*        - Added debug conditional code to keep track of memory leaks.
+*        - Other minor bug fixes.
+*     6-FEB-2004 (DSB):
+*        DefaultURI and astXmlAddURI modified to allow a blank URI to be
+*        used to ignore a default namespace URI provided by an enclosing
+*        element.
+*     29-NOV-2004 (DSB):
+*        Added astXmlGetType method.
+*     27-JAN-2005 (DSB):
+*        - Move astXmlTrace and associated code into conditional
+*        compilation blokc (included if DEBUG macro is defined). This
+*        speeds up the create and destruction of XmlObjects in non-DEBUG code.
+*        - Renamed the private Delete function as astXmlDelete and gave
+*        it protected access.
+*        - Modify astXmlDelete so that it can succesfully annul objects
+*        which have no parent.
+*        - Include extra info in some error messages.
+*     1-MAR-2006 (DSB):
+*        Replace astSetPermMap within DEBUG blocks by astBeginPM/astEndPM.
+*     10-DEC-2008 (DSB):
+*        Allow a prefix to be included with the attribute name in
+*        astXmlGetAttributeValue.
+*/
+
+
+/* Module Constants. */
+/* ----------------- */
+/* Set the name of the module we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. NB, this module is not a proper AST
+   class, but it defines this macro sanyway in order to get the protected
+   symbols defined in memory.h */
+#define astCLASS Xml
+
+#define IND_INC 3
+
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "memory.h"            /* Interface to the memory management module */
+#include "error.h"             /* Interface to the error module */
+#include "xml.h"               /* Interface to this module */
+#include "globals.h"           /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"           /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+
+/*
+*  Name:
+*     MAKE_CHECK
+
+*  Type:
+*     Private macro.
+
+*  Purpose:
+*     Implement the astXmlCheck<type>_ function for XML structures.
+
+*  Synopsis:
+*     #include "xml.h"
+*     MAKE_CHECK(type,id)
+
+*  Class Membership:
+*     Defined by the xml module.
+
+*  Description:
+*     This macro expands to an implementation of the protected
+*     astXmlCheck<type>_ function (q.v.) which validates membership of
+*     a specified XML data type.
+
+*  Parameters:
+*     type
+*        The type whose membership is to be validated (e.g. "Element" not
+*        "XmlElement").
+*     id
+*        The constant (e.g. "AST__XMLELEM") defining the data type.
+
+*  Notes:
+*     -  To avoid problems with some compilers, you should not leave any white
+*     space around the macro arguments.
+*/
+
+/* Define the macro. */
+#define MAKE_CHECK(type,id) \
+\
+/* Declare the function */ \
+AstXml##type *astXmlCheck##type##_( void *this, int nullok, int *status ) { \
+\
+/* Local Variables: */\
+   AstXml##type *result;    /* The returned pointer */\
+\
+/* Check the global error status. If an error has already occurred just\
+   return the supplied pointer. This is so that functions such as\
+   astXmlAnnul which do not check the inherited status receive the\
+   supplied pointer. */\
+   if( !astOK ) return this;\
+\
+/* Initialise */\
+   result = NULL;\
+\
+/* If the pointer is NULL issue an error if nullok is zero. */\
+   if( !this ) {\
+      if( !nullok ) astError( AST__PTRIN, "astXmlCheck"#type": Invalid "\
+                              "NULL pointer supplied." , status);\
+\
+/* Otherwise get the "type" component which holds a magic value for each\
+   different class of structure. Compare this value against all valid \
+   classes of structure. If no match is found, the pointer does not \
+   identify an suitable structure, and so report an error and return \
+   NULL. */\
+   } else {\
+      if( !astXmlCheckType( ( AstXmlObject * ) this, id ) ) {\
+         astError( AST__PTRIN, "astXmlCheck"#type": Invalid pointer "\
+                   "supplied; pointer to AstXml"#type" required." , status);\
+      } else {\
+         result = (AstXml##type *) this;\
+      }\
+   }\
+\
+/* Return the result. */\
+   return result;\
+}
+
+
+/* Module variables. */
+/* ================= */
+
+/* Define macros for accessing all items of thread-safe global data
+   used by this module. */
+#ifdef THREAD_SAFE
+
+#define next_id astGLOBAL(Xml,Next_ID)
+#define gettag_buff astGLOBAL(Xml,GetTag_Buff)
+#define GLOBAL_inits globals->Next_ID = 0;
+astMAKE_INITGLOBALS(Xml)
+
+/* Set up mutexes */
+static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
+#define LOCK_MUTEX1 pthread_mutex_lock( &mutex1 );
+#define UNLOCK_MUTEX1 pthread_mutex_unlock( &mutex1 );
+
+/* If thread safety is not needed, declare globals at static variables. */
+#else
+
+static int next_id = 0;
+static char gettag_buff[ AST__XML_GETTAG_BUFF_LEN + 1 ];
+
+#define LOCK_MUTEX1
+#define UNLOCK_MUTEX1
+
+#ifdef DEBUG   /* Not available in thread-safe compilations */
+static int nobj = 0;
+static AstXmlObject **existing_objects = NULL;
+#endif
+
+#endif
+
+
+/* Function prototypes. */
+/* ==================== */
+
+/* Private member functions. */
+/* ------------------------- */
+static AstXmlAttribute *FindAttribute( AstXmlElement *, const char *, int * );
+static AstXmlAttribute *NewAttribute( const char *, const char *, const char *, int * );
+static AstXmlDocument *NewDocument( int * );
+static AstXmlPrologue *NewPrologue( AstXmlDocument *, int * );
+static AstXmlNamespace *NewNamespace( const char *, const char *, int * );
+static char *AppendChar( char *, int *, char, int * );
+static char *AppendLine( char *, int *, const char *, int, int * );
+static char *RemoveEscapes( const char *, int * );
+static char *CleanText( const char *, int * );
+static const char *AddEscapes( const char *, int * );
+static const char *DefaultURI( AstXmlElement *, int * );
+static const char *Format( AstXmlObject *, int, int * );
+static char *FormatTag( AstXmlObject *, int, int * );
+static const char *ResolvePrefix( const char *, AstXmlElement *, int * );
+static int CheckType( long int, long int, int * );
+static int MatchName( AstXmlElement *, const char *, int * );
+static int Ustrcmp( const char *, const char *, int * );
+static void AddContent( AstXmlParent *, int, AstXmlContentItem *, int * );
+static void CheckName( const char *, const char *, const char *, int, int * );
+static void CheckPrefName( char *, const char *, const char *, int * );
+static void CleanXml( AstXmlObject *, long int, int * );
+static void InitXmlAttribute( AstXmlAttribute *, int, const char *, const char *, const char *, int * );
+static void InitXmlCDataSection( AstXmlCDataSection *, int, const char *, int * );
+static void InitXmlWhite( AstXmlWhite *, int, const char *, int * );
+static void InitXmlBlack( AstXmlBlack *, int, const char *, int * );
+static void InitXmlComment( AstXmlComment *, int, const char *, int * );
+static void InitXmlDocument( AstXmlDocument *, int, int * );
+static void InitXmlPrologue( AstXmlPrologue *, int, int * );
+static void InitXmlDeclPI( AstXmlDeclPI *, int, const char *, int * );
+static void InitXmlDTDec( AstXmlDTDec *, int, const char *, const char *, const char *, int * );
+static void InitXmlElement( AstXmlElement *, int, const char *, const char *, int * );
+static void InitXmlNamespace( AstXmlNamespace *, int, const char *, const char *, int * );
+static void InitXmlObject( AstXmlObject *, long int, int * );
+static void InitXmlPI( AstXmlPI *, int, const char *, const char *, int * );
+static AstXmlElement *ReadContent( AstXmlDocument **, int, int (*)( AstXmlElement *, int * ), int, char (*)( void *, int * ), void *, int, int * );
+
+#ifdef DEBUG
+static void AddObjectToList( AstXmlObject * );
+static void RemoveObjectFromList( AstXmlObject * );
+#endif
+
+/* Function implementations. */
+/* ========================= */
+
+/* Create the astXmlCheck... functiosn which check a pointer identifies
+   an XML structure of a given type. */
+
+MAKE_CHECK(Document,AST__XMLDOC)
+MAKE_CHECK(Object,AST__XMLOBJECT)
+MAKE_CHECK(Element,AST__XMLELEM)
+MAKE_CHECK(Attribute,AST__XMLATTR)
+MAKE_CHECK(CDataSection,AST__XMLCDATA)
+MAKE_CHECK(Comment,AST__XMLCOM)
+MAKE_CHECK(PI,AST__XMLPI)
+MAKE_CHECK(Namespace,AST__XMLNAME)
+MAKE_CHECK(Prologue,AST__XMLPRO)
+MAKE_CHECK(DeclPI,AST__XMLDEC)
+MAKE_CHECK(DTDec,AST__XMLDTD)
+MAKE_CHECK(White,AST__XMLWHITE)
+MAKE_CHECK(Black,AST__XMLBLACK)
+MAKE_CHECK(CharData,AST__XMLCHAR)
+MAKE_CHECK(ContentItem,AST__XMLCONT)
+MAKE_CHECK(MiscItem,AST__XMLMISC)
+MAKE_CHECK(Parent,AST__XMLPAR)
+
+
+static void AddContent( AstXmlParent *this, int where, AstXmlContentItem *item, int *status ){
+/*
+*  Name:
+*     AddContent
+
+*  Purpose:
+*     Add a content item to an XmlElement.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void AddContent( AstXmlParent *this, int where, AstXmlContentItem *item, int *status )
+
+*  Description:
+*     This function adds a supplied item to a specified XmlElement or
+*     XmlDocument. An error is reported if the item is not appropriate.
+
+*  Parameters:
+*     this
+*        The pointer to the element or document to be modified.
+*     where
+*        Ignored if "this" is an XmlElement pointer. Otherwise, "where"
+*        indicates where the item should be added to the document:
+*          1 - In the prologue, after the XML declaration but before the DTD.
+*          2 - In the prologue, after the DTD but before the root element.
+*          3 - In the epilogue, after the root element.
+*     item
+*        Pointer to the content item to be added to the element. If
+*        "this" is an XmlElement, this can be a pointer to any of the
+*        following types: AstXmlElement, AstXmlWhite, AstXmlBlack,
+*        AstXmlCDataSection, AstXmlComment, AstXmlPI. If "this" is a
+*        document, the list is restricted to: AstXmlWhite, AstXmlComment,
+*        AstXmlPI.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstXmlDocument *doc;   /* Document pointer */
+   AstXmlElement *elem;   /* Element pointer */
+   AstXmlPrologue *pro;   /* Prologue pointer */
+   int nitem;             /* Number of items in the parent */
+
+/* Check the global error status and the supplied pointers. */
+   if( !astOK || !this || !item ) return;
+
+/* Split for the two forms of parent. */
+   if( astXmlCheckType( this, AST__XMLELEM ) ) {
+      elem = (AstXmlElement *) this;
+
+/* Save the number of content items currently stored in the element. */
+      nitem = ( elem->items ) ? elem->nitem : 0;
+
+/* Attempt to extend the array to hold an extra item. */
+      elem->items = astGrow( elem->items, nitem + 1,
+                             sizeof( AstXmlContentItem * ) );
+
+/* Check the memory was allocated succesfully. */
+      if( astOK ) {
+
+/* Store the supplied pointer in the array of content items. */
+         elem->items[ nitem ] = item;
+
+/* Increment the number of content items in this element */
+         elem->nitem = nitem + 1;
+
+/* Indicate that the item is owned by the element. */
+         ( (AstXmlObject *) item )->parent = this;
+      }
+
+/* Now deal with cases where we are adding an item to the prologue or
+   epilogue of the document. */
+   } else {
+      if( !astXmlCheckType( item, AST__XMLMISC ) ){
+         astError( AST__INTER, "AddContent(xml): Inappropriate attempt to "
+                   "add an item of type %ld to an XML document (internal "
+                   "AST programming error).", status, ( (AstXmlObject *) item)->type );
+
+      } else if( !astXmlCheckType( this, AST__XMLDOC ) ){
+         astError( AST__INTER, "AddContent(xml): Inappropriate attempt to "
+                   "add an item of type %ld to an XML object of type %ld "
+                   "(internal AST programming error).", status,
+                   ( (AstXmlObject *) item)->type,
+                   ( (AstXmlObject *) this)->type );
+
+      } else {
+         doc = (AstXmlDocument *) this;
+
+/* Create a prologue if necessary. */
+         if( where < 3 && !doc->prolog ) doc->prolog = NewPrologue( doc, status );
+         pro = doc->prolog;
+
+         if( where < 2 ) {
+            nitem = ( pro->misc1 ) ? pro->nmisc1 : 0;
+            pro->misc1 = astGrow( pro->misc1, nitem + 1, sizeof( AstXmlMiscItem * ) );
+            if( astOK ) {
+               pro->misc1[ nitem ] = item;
+               pro->nmisc1 = nitem + 1;
+               ( (AstXmlObject *) item )->parent = (AstXmlParent *) pro;
+            }
+
+         } else if( where == 2 ) {
+            nitem = ( pro->misc2 ) ? pro->nmisc2 : 0;
+            pro->misc2 = astGrow( pro->misc2, nitem + 1, sizeof( AstXmlMiscItem * ) );
+            if( astOK ) {
+               pro->misc2[ nitem ] = item;
+               pro->nmisc2 = nitem + 1;
+               ( (AstXmlObject *) item )->parent = (AstXmlParent *) pro;
+            }
+
+         } else {
+            nitem = ( doc->epilog ) ? doc->nepi : 0;
+            doc->epilog = astGrow( doc->epilog, nitem + 1, sizeof( AstXmlMiscItem * ) );
+            if( astOK ) {
+               doc->epilog[ nitem ] = item;
+               doc->nepi = nitem + 1;
+               ( (AstXmlObject *) item )->parent = this;
+            }
+         }
+      }
+   }
+}
+
+static const char *AddEscapes( const char *text, int *status ){
+/*
+*  Name:
+*     AddEscapes
+
+*  Purpose:
+*     Replaces characters by corresponding entity references.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *AddEscapes( const char *text, int *status )
+
+*  Description:
+*     This function produces a dynamic copy of the supplied text in which
+*     occurrences of "&", "<", ">", and "\"" are replaced by the corresponding
+*     XML entity reference.
+*
+*     The "&" character is only replaced by an entity reference if it is
+*     followed by a non-name character (i.e. anything except a letter
+*     underscore or colon). If it is followed by a name character, it is
+*     assumed to mark the start of an entity reference.
+
+*  Parameters:
+*     text
+*        A pointer to a text string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated string containing the required
+*     copy.
+
+*  Notes:
+*     - NULL is returned if this function is called with the global error
+*     status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   char *result;             /* Returned pointer */
+   const char *c;            /* Pointer to next supplied character */
+   char *d;                  /* Pointer to next returned character */
+
+/* Initialise */
+   result = NULL;
+
+/* Return if the pointer is NULL or if an error has occurred. */
+   if( !astOK || !text ) return result;
+
+/* Allocate the maximum possible amount of memory that may be needed to
+   store the returned string. */
+   result = astMalloc( 6*strlen( text ) + 1 );
+
+/* Check the pointer can be used safely. */
+   if( astOK ) {
+
+/* Loop round every character in the supplied text. */
+      c = text - 1;
+      d = result;
+      while( *(++c) ) {
+
+/* We replace this character if it is a <, >, ', &, or ". */
+         if( *c == '<' ) {
+            strcpy( d, "&lt;" );
+            d += 4;
+
+         } else if( *c == '>' ) {
+            strcpy( d, "&gt;" );
+            d += 4;
+
+         } else if( *c == '"' ) {
+            strcpy( d, "&quot;" );
+            d += 6;
+
+         } else if( *c == '\'' ) {
+            strcpy( d, "&apos;" );
+            d += 6;
+
+         } else if( *c == '&' ) {
+            strcpy( d, "&amp;" );
+            d += 5;
+
+/* Otherwise just append the supplied character. */
+         } else {
+            *(d++) = *c;
+         }
+      }
+
+/* Terminate the returned string. */
+      *d = 0;
+
+/* Reallocate the string to free up any unused space. */
+      result = astRealloc( result, d - result + 1 );
+   }
+
+/* Return the result. */
+   return (const char *) result;
+}
+
+
+#ifdef DEBUG
+static void AddObjectToList( AstXmlObject *obj ){
+/*
+*  Name:
+*     AddObjectToList
+
+*  Purpose:
+*     Adds an XmlObject to a static list of all currently active XmlObjects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void AddObjectToList( AstXmlObject *obj )
+
+*  Description:
+*     This function adds the supplied pointer to a static list of pointers,
+*     and increments the number of elements in the list. This list holds
+*     pointers to all the XmlObjects which currently exist.
+
+*  Parameters:
+*     this
+*        A pointer to a new XmlObject.
+*/
+
+/* Return if the pointer is NULL or if an error has occurred. */
+   if( !astOK || !obj ) return;
+
+/* Increment the number of objects in the list and increase the size of
+   the list. */
+   astBeginPM;
+   existing_objects = astGrow( existing_objects, ++nobj, sizeof( AstXmlObject *) );
+   astEndPM;
+
+/* Add the new pointer to the end of the list. */
+   existing_objects[ nobj - 1 ] = obj;
+}
+#endif
+
+static char *AppendChar( char *str1, int *nc, char ch, int *status ) {
+/*
+*  Name:
+*     AppendChar
+
+*  Purpose:
+*     Append a character to a string which grows dynamically.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     char *AppendChar( char *str1, int *nc, char ch, int *status )
+
+*  Description:
+*     This function appends a character to a dynamically
+*     allocated string, extending the dynamic string as necessary to
+*     accommodate the new character (plus the final null).
+
+*  Parameters:
+*     str1
+*        Pointer to the null-terminated dynamic string, whose memory
+*        has been allocated using the AST memory allocation functions
+*        defined in "memory.h". If no space has yet been allocated for
+*        this string, a NULL pointer may be given and fresh space will
+*        be allocated by this function.
+*     nc
+*        Pointer to an integer containing the number of characters in
+*        the dynamic string (excluding the final null). This is used
+*        to save repeated searching of this string to determine its
+*        length and it defines the point where the new string will be
+*        appended. Its value is updated by this function to include
+*        the extra characters appended.
+*
+*        If "str1" is NULL, the initial value supplied for "*nc" will
+*        be ignored and zero will be used.
+*     ch
+*        The character which is to be appended to "str1".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A possibly new pointer to the dynamic string with the new character
+*     appended (its location in memory may have to change if it has to
+*     be extended, in which case the original memory is automatically
+*     freed by this function). When the string is no longer required,
+*     its memory should be freed using astFree.
+
+*  Notes:
+*     - If this function is invoked with the global error status set
+*     or if it should fail for any reason, then the returned pointer
+*     will be equal to "str1" and the dynamic string contents will be
+*     unchanged.
+*/
+
+/* Local Variables: */
+   char *result;                 /* Pointer value to return */
+   int len;                      /* Length of new string */
+
+/* Initialise. */
+   result = str1;
+
+/* If the first string pointer is NULL, also initialise the character
+   count to zero. */
+   if ( !str1 ) *nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Calculate the total string length once the character has been added. */
+   len = *nc + 1;
+
+/* Extend the dynamic string to the required length, including
+   a final null. Save the resulting pointer, which will be
+   returned. */
+   result = astGrow( str1, len + 1, sizeof( char ) );
+
+/* If OK, append the second string and update the total character
+   count. */
+   if ( astOK ) {
+      result[ *nc ] = ch;
+      *nc = len;
+      result[ *nc ] = 0;
+   }
+
+/* Return the result pointer. */
+   return result;
+}
+
+static char *AppendLine( char *str1, int *nc, const char *str2, int ind, int *status ) {
+/*
+*  Name:
+*     AppendLine
+
+*  Purpose:
+*     Append an indented new line to another string which grows dynamically.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     char *AppendLine( char *str1, int *nc, const char *str2, int ind, int *status )
+
+*  Description:
+*     This function appends one string to another dynamically
+*     allocated string, extending the dynamic string as necessary to
+*     accommodate the new characters (plus the final null).
+*
+*     A newline character is inserted if necessary to ensure that the "str2"
+*     string starts on a newline. If "ind" is positive, spaces are added
+*     as necessary to ensure that "str2" begins with the specified number of
+*     spaces.
+
+*  Parameters:
+*     str1
+*        Pointer to the null-terminated dynamic string, whose memory
+*        has been allocated using the AST memory allocation functions
+*        defined in "memory.h". If no space has yet been allocated for
+*        this string, a NULL pointer may be given and fresh space will
+*        be allocated by this function.
+*     nc
+*        Pointer to an integer containing the number of characters in
+*        the dynamic string (excluding the final null). This is used
+*        to save repeated searching of this string to determine its
+*        length and it defines the point where the new string will be
+*        appended. Its value is updated by this function to include
+*        the extra characters appended.
+*
+*        If "str1" is NULL, the initial value supplied for "*nc" will
+*        be ignored and zero will be used.
+*     str2
+*        Pointer to a constant null-terminated string, a copy of which
+*        is to be appended to "str1".
+*     ind
+*        The number of spaces to use as the indentation string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A possibly new pointer to the dynamic string with the new string
+*     appended (its location in memory may have to change if it has to
+*     be extended, in which case the original memory is automatically
+*     freed by this function). When the string is no longer required,
+*     its memory should be freed using astFree.
+
+*  Notes:
+*     - If this function is invoked with the global error status set
+*     or if it should fail for any reason, then the returned pointer
+*     will be equal to "str1" and the dynamic string contents will be
+*     unchanged.
+*/
+
+/* Local Variables: */
+   char *c;                      /* Point to next character */
+   char *result;                 /* Pointer value to return */
+   char *temp;                   /* Pointer to modified string */
+   int j;                        /* Loop count */
+
+/* Initialise. */
+   result = str1;
+
+/* If the first string pointer is NULL, also initialise the character
+   count to zero. */
+   if ( !str1 ) *nc = 0;
+
+/* Check the global error status. */
+   if ( !astOK || !str2 ) return result;
+
+/* Remove any trailing white space (except for newlines) from the supplied
+   string. */
+   if( *nc > 0 ) {
+      c = str1 + *nc - 1;
+      while( isspace( *c ) && *c != '\n' ) {
+         *(c--) = 0;
+         (*nc)--;
+      }
+
+/* If the last character in the returned string is not now a newline,
+   append a newline, so long as the new item does not start with a newline. */
+      if( str1[ *nc - 1 ] != '\n' ) {
+         temp = AppendChar( str1, nc, '\n', status );
+      } else {
+         temp = str1;
+      }
+
+   } else {
+      temp = str1;
+   }
+
+/* If a fixed indentation is specified, skip over any leading spaces in
+   the second string. */
+   if( str2 ) {
+      if( ind > 0 ) {
+         while( isspace( *str2 ) ) str2++;
+      }
+
+/* If the first character of the second string is a newline, ignore it. */
+      if( str2[ 0 ] == '\n' ) str2++;
+   }
+
+/* Append the indentation string. */
+   for( j = 0; j < ind; j++ ) temp = AppendChar( temp, nc, ' ', status );
+
+/* Append the supplied string. */
+   return astAppendString( temp, nc, str2 );
+}
+
+void astXmlAddAttr_( AstXmlElement *this, const char *name, const char *value,
+                     const char *prefix, int *status ){
+/*
+*+
+*  Name:
+*     astXmlAddAttr
+
+*  Purpose:
+*     Add an attribute to an XmlElement.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlAddAttr( AstXmlElement *this, const char *name,
+*                         const char *value, const char *prefix )
+
+*  Description:
+*     This function adds an attribute to a specified XmlElement. If the
+*     element already contains an attribute with the given name amd prefix,
+*     then the value of the attribute is changed to be the supplied value.
+
+*  Parameters:
+*     this
+*        The pointer to the element to be modified.
+*     name
+*        Pointer to a null terminated string containing the attribute name.
+*     value
+*        Pointer to a null terminated string containing the attribute value.
+*     prefix
+*        The namespace prefix for the attribute. May be NULL or blank, in
+*        which case any prefix at the start of "name" is used.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlAttribute *attr;    /* The new attribute. */
+   AstXmlAttribute *oldattr; /* Pointer to existing attribute */
+   int i;                    /* Loop index */
+   int nattr;                /* Number of attributes in the element */
+   int oldi;                 /* Index of existing attribute */
+   char *my_value;           /* Cleaned value text */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Initialise */
+   oldattr = NULL;
+
+/* Clean the value text. */
+   my_value = CleanText( value, status );
+
+/* Create a new XmlAttribute. */
+   attr = NewAttribute( name, my_value, prefix, status );
+
+/* Free the memory */
+   my_value = astFree( my_value );
+
+/* If OK, indicate that the attribute is owned by the element. */
+   if( astOK ) {
+      ( (AstXmlObject *) attr )->parent = (AstXmlParent *) this;
+
+/* Save the number of attributes currently stored in the element. */
+      nattr = ( this->attrs ) ? this->nattr : 0;
+
+/* Search the existing attributes to see if an attribute with the given
+   name and prefix already exists. */
+      oldi = -1;
+      for( i = 0; i < nattr; i++ ) {
+         oldattr = this->attrs[ i ];
+         if( !strcmp( oldattr->name, attr->name ) ) {
+            if( !oldattr->prefix && !attr->prefix ) {
+               oldi = i;
+               break;
+            } else if( oldattr->prefix && attr->prefix &&
+                       !strcmp( oldattr->prefix, attr->prefix ) ){
+               oldi = i;
+               break;
+            }
+         }
+      }
+
+/* If there is an existing attribute with the same name and prefix,
+   replace the old attribute with the new one created above. */
+      if( oldi > -1 ){
+         ((AstXmlObject *)oldattr)->parent = NULL;
+         oldattr = astXmlAnnul( oldattr );
+         this->attrs[ oldi ] = attr;
+
+/* Otherwise, attempt to extend the array to hold an extra attribute. */
+      } else {
+         this->attrs = astGrow( this->attrs, nattr + 1,
+                                sizeof( AstXmlAttribute * ) );
+
+/* Check all has gone OK. */
+         if( astOK ) {
+
+/* Store the attribute pointer in the array of attribute pointers. */
+            this->attrs[ nattr ] = attr;
+
+/* Increment the number of content items in this element */
+            this->nattr = nattr + 1;
+
+         }
+      }
+   }
+}
+
+void astXmlAddCDataSection_( AstXmlElement *this, const char *text, int *status ){
+/*
+*+
+*  Name:
+*     astXmlAddCDataSection
+
+*  Purpose:
+*     Create a new XmlCDataSection and add it to an XmlElement.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlAddCDataSection( AstXmlElement *this, const char *text )
+
+*  Description:
+*     This function creates a new XmlCDataSection structure representing
+*     an unparsed character data (CDATA) section, and adds it into an
+*     existing element.
+
+*  Parameters:
+*     this
+*        A pointer to the element to be modified.
+*     text
+*        Pointer to a null terminated string containing the character data.
+
+*-
+*/
+
+/* Local Variables: */
+   AstXmlCDataSection *new;  /* Pointer to new structure */
+   char *my_text;            /* Cleaned text */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Allocate space for the new structure. */
+   new = (AstXmlCDataSection *) astMalloc( sizeof( AstXmlCDataSection ) );
+
+/* Clean the text. */
+   my_text = CleanText( text, status );
+
+/* Initialise it. */
+   InitXmlCDataSection( new, AST__XMLCDATA, my_text, status );
+
+/* Free the memory */
+   my_text = astFree( my_text );
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) {
+      new = astXmlDelete( new );
+
+/* Otherwise, add the content item to the element. */
+   } else {
+      AddContent( (AstXmlParent *) this, 0, (AstXmlContentItem *) new, status );
+   }
+}
+
+void astXmlAddCharData_( AstXmlParent *this, int where, const char *text, int *status ){
+/*
+*+
+*  Name:
+*     astXmlAddCharData
+
+*  Purpose:
+*     Create a new XmlCharData and add it to an XmlElement or XmlDocument.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlAddCharData( AstXmlParent *this, int where, const char *text )
+
+*  Description:
+*     This function creates a new XmlCharData structure representing
+*     parsed character data, and adds it into an existing element or
+*     document.
+
+*  Parameters:
+*     this
+*        Pointer to the element or document to be modified.
+*     where
+*        Ignored if "this" is an XmlElement pointer. Otherwise, "where"
+*        indicates where the item should be added to the document:
+*          1 - In the prologue, after the XML declaration but before the DTD.
+*          2 - In the prologue, after the DTD but before the root element.
+*          3 - In the epilogue, after the root element.
+*     text
+*        Pointer to a null terminated string containing the character data.
+*        If "this" is a document, the text must consist entirely of white
+*        space.
+
+*-
+*/
+
+/* Local Variables: */
+   AstXmlCharData *new;        /* Pointer to the new structure */
+   char *my_text;              /* Pointer to cleaned text */
+   char *c;                    /* Pointer to next character */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Initialise */
+   new = NULL;
+
+/* Clean the text by replacing "\r\n" by "\n". */
+   my_text = CleanText( text, status );
+
+/* See if the text is all white. */
+   c = my_text - 1;
+   while( *(++c) && isspace( *c ) );
+
+/* If the string contains a non-white character, allocate memory for
+   a XmlBlack structure, and initialise it to hold the supplied text.
+   Otherwise, allocate memory for a XmlWhite structure, and initialise it
+   to hold the supplied text. */
+   if( *c ) {
+      if( astXmlCheckType( this, AST__XMLDOC ) ) {
+         astError( AST__XMLCM, "astXmlAddCharData(xml): Illegal attempt "
+                   "to add non-white character data to the prologue or "
+                   "epilogue of an XML document: \"%s\".", status, my_text );
+      } else {
+         new = (AstXmlCharData *) astMalloc( sizeof( AstXmlBlack ) );
+         InitXmlBlack( (AstXmlBlack *) new, AST__XMLBLACK, my_text, status );
+      }
+
+   } else {
+      new = (AstXmlCharData *) astMalloc( sizeof( AstXmlWhite ) );
+      InitXmlWhite( (AstXmlWhite *) new, AST__XMLWHITE, my_text, status );
+   }
+
+/* Free the memory holding the cleaned text */
+   my_text = astFree( my_text );
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) {
+      new = astXmlDelete( new );
+
+/* Otherwise, add the content item to the element. */
+   } else {
+      AddContent( this, where, (AstXmlContentItem *) new, status );
+   }
+}
+
+void astXmlAddComment_( AstXmlParent *this, int where, const char *text, int *status ){
+/*
+*+
+*  Name:
+*     astXmlAddComment
+
+*  Purpose:
+*     Create a new XmlComment and add it to an XmlElement or XmlDocument.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlAddComment( AstXmlParent *this, int where, const char *text )
+
+*  Description:
+*     This function creates a new XmlComment structure representing
+*     an XML comment, and adds it into an existing element or document.
+
+*  Parameters:
+*     this
+*        Pointer to the element or document to be modified.
+*     where
+*        Ignored if "this" is an XmlElement pointer. Otherwise, "where"
+*        indicates where the item should be added to the document:
+*          1 - In the prologue, after the XML declaration but before the DTD.
+*          2 - In the prologue, after the DTD but before the root element.
+*          3 - In the epilogue, after the root element.
+*     text
+*        Pointer to a null terminated string containing the comment text.
+
+*-
+*/
+
+/* Local Variables: */
+   AstXmlComment *new;        /* Pointer to the new structure */
+   char *my_text;            /* Cleaned text */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Allocate space for the new structure. */
+   new = (AstXmlComment *) astMalloc( sizeof( AstXmlComment ) );
+
+/* Clean the text. */
+   my_text = CleanText( text, status );
+
+/* Initialise it. */
+   InitXmlComment( new, AST__XMLCOM, my_text, status );
+
+/* Free the memory */
+   my_text = astFree( my_text );
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) {
+      new = astXmlDelete( new );
+
+/* Otherwise, add the content item to the element. */
+   } else {
+      AddContent( this, where, (AstXmlContentItem *) new, status );
+   }
+
+}
+
+AstXmlElement *astXmlAddElement_( AstXmlElement *this, const char *name,
+                                  const char *prefix, int *status ){
+/*
+*+
+*  Name:
+*     astXmlAddElement
+
+*  Purpose:
+*     Create a new empty XmlElement and adds it to an XmlElement.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlElement *astXmlAddElement( AstXmlElement *this, const char *name,
+*                                      const char *prefix )
+
+*  Description:
+*     This function creates a new XmlElement structure representing an
+*     empty XML element with the given name and namespace prefix, and
+*     adds it into an existing element.
+
+*  Parameters:
+*     this
+*        A pointer to the element to be modified. This may be NULL.
+*     name
+*        The name for the element.
+*     prefix
+*        The namespace prefix for the element. May be NULL or blank, in
+*        which case any prefix at the start of "name" is used.
+
+*  Returned Value:
+*     A pointer to the new structure is returned. This pointer should be
+*     freed using astXmlAnnul when no longer needed.
+
+*  Notes:
+*     - A NULL pointer is returned if the inherited status value
+*     indicates an error has occurred on entry, or if this function
+*     should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlElement *new;        /* The returned pointer */
+
+/* Initialise */
+   new = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return new;
+
+/* Allocate space for the new structure. */
+   new = (AstXmlElement *) astMalloc( sizeof( AstXmlElement ) );
+
+/* Initialise it. */
+   InitXmlElement( new, AST__XMLELEM, name, prefix, status );
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) {
+      new = astXmlDelete( new );
+
+/* Otherwise, add the content item to the element. */
+   } else {
+      AddContent( (AstXmlParent *) this, 0, (AstXmlContentItem *) new, status );
+   }
+
+/* Return the result. */
+   return new;
+
+}
+
+void astXmlAddPI_( AstXmlParent *this, int where, const char *target, const char *text, int *status ){
+/*
+*+
+*  Name:
+*     astXmlAddPI
+
+*  Purpose:
+*     Create a new XmlPI and add it to an element or document.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlAddPI( AstXmlParent *this, int where, const char *target,
+*                       const char *text )
+
+*  Description:
+*     This function creates a new XmlPI structure representing an
+*     XML "programming instruction", and adds it into an existing element
+*     or document.
+
+*  Parameters:
+*     this
+*        Pointer to the element or document to be modified. This should
+*        be a pointer to an XmlElement or an XmlDocument.
+*     where
+*        Ignored if "this" is an XmlElement pointer. Otherwise, "where"
+*        indicates where the PI should be added to the document:
+*          1 - In the prologue, after the XML declaration but before the DTD.
+*          2 - In the prologue, after the DTD but before the root element.
+*          3 - In the epilogue, after the root element.
+*     target
+*        Pointer to a null terminated string containing the PI target.
+*     text
+*        Pointer to a null terminated string containing the PI text.
+
+*-
+*/
+
+/* Local Variables: */
+   AstXmlPI *new;        /* Pointer to the new structure */
+   char *my_text;            /* Cleaned text */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Allocate space for the new structure. */
+   new = (AstXmlPI *) astMalloc( sizeof( AstXmlPI ) );
+
+/* Clean the text. */
+   my_text = CleanText( text, status );
+
+/* Initialise it. */
+   InitXmlPI( new, AST__XMLPI, target, my_text, status );
+
+/* Free the memory */
+   my_text = astFree( my_text );
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) {
+      new = astXmlDelete( new );
+
+/* Otherwise, add the content item to the element. */
+   } else {
+      AddContent( this, where, (AstXmlContentItem *) new, status );
+   }
+}
+
+void astXmlAddURI_( AstXmlElement *this, const char *prefix, const char *uri, int *status ){
+/*
+*+
+*  Name:
+*     astXmlAddURI
+
+*  Purpose:
+*     Add a namespace prefix definition to an XmlElement, or change the
+*     default namespace.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlAddURI( AstXmlElement *this, const char *prefix,
+*                        const char *uri )
+
+*  Description:
+*     This function adds a namespace prefix definition to a specified
+*     XmlElement, or changes the default namespace. If the suppliedprefix
+*     is already defined in the element, the associated URI is changed to
+*     the supplied URI.
+
+*  Parameters:
+*     this
+*        The pointer to the element to be modified.
+*     prefix
+*        Pointer to a null terminated string containing the namespace
+*        prefix. If this is NULL or blank, then the supplied URI is used
+*        as the default namespace for this element and all child elements
+*        (except for child elements which define their own default
+*        namespace).
+*     uri
+*        Pointer to a null terminated string containing the namespace URI.
+*        If this is NULL or blank, and "prefix" is also NULL or blank, then
+*        this has the same effect of there being no default namespace within
+*        the supplied element.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlNamespace *ns;    /* The new namespace definition */
+   AstXmlNamespace *oldns; /* The existing namespace definition */
+   int i;                  /* Loop index */
+   int nc;                 /* Length of namespace prefix */
+   int nnspref;            /* Number of namespace defintions in the element */
+   int oldi;               /* Index of existing attribute */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Initialise */
+   oldns = NULL;
+
+/* Store the used length of the namespace prefix. */
+   nc = prefix ? astChrLen( prefix ) : 0;
+
+/* If no namespace prefix has been supplied, just change the default
+   namespace URI. */
+   if( !nc ) {
+      if( uri ) {
+         this->defns = astStore( this->defns, uri, strlen( uri ) + 1 );
+      } else {
+         this->defns = astStore( this->defns, "", 1 );
+      }
+
+/* Otherwise, add the namespace definition to the element. */
+   } else {
+
+/* Create a new XmlNamespace. */
+      ns = NewNamespace( prefix, uri, status );
+
+/* If OK, indicate that the namespace is owned by the element. */
+      if( astOK ) {
+         ( (AstXmlObject *) ns )->parent = (AstXmlParent *) this;
+
+/* Save the number of namespace definitions currently stored in the element. */
+         nnspref = ( this->nsprefs ) ? this->nnspref : 0;
+
+/* Search the existing prefixes to see if a namespace with the given
+   prefix already exists. */
+         oldi = -1;
+         for( i = 0; i < nnspref; i++ ) {
+            oldns = this->nsprefs[ i ];
+            if( !strcmp( oldns->prefix, ns->prefix ) ) {
+               oldi = i;
+               break;
+            }
+         }
+
+/* If there is an existing namespace with the same prefix, replace the old
+   namespace with the new one created above. */
+         if( oldi > -1 ){
+            ((AstXmlObject *)oldns)->parent = NULL;
+            oldns = astXmlAnnul( oldns );
+            this->nsprefs[ oldi ] = ns;
+
+/* Otherwise, attempt to extend the array to hold an extra namespace definition. */
+         } else {
+            this->nsprefs = astGrow( this->nsprefs, nnspref + 1,
+                                     sizeof( AstXmlNamespace * ) );
+
+/* Check all has gone OK. */
+            if( astOK ) {
+
+/* Store the Namespace pointer in the array of Namespace pointers. */
+               this->nsprefs[ nnspref ] = ns;
+
+/* Increment the number of namespaces in this element */
+               this->nnspref = nnspref + 1;
+            }
+         }
+      }
+   }
+}
+
+void *astXmlAnnul_( AstXmlObject *this, int *status ){
+/*
+*+
+*  Name:
+*     astXmlAnnul
+
+*  Purpose:
+*     Free the resources used by an XmlObject.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void *astXmlAnnul( AstXmlObject *this )
+
+*  Description:
+*     This function frees the resources used to hold the XmlObject, together
+*     with any child objects contained within the supplied XmlObject. A NULL
+*     pointer is always returned. If the supplied object is still in use
+*     (that is, if its parent XmlElement still exists) then the resources
+*     are not freed, and a copy of the supplied pointer is returned.
+
+*  Parameters:
+*     this
+*        pointer to the XmlObject to be freed.
+
+*  Returned Value:
+*     A NULL pointer, or the supplied pointer if the XmlObject is still
+*     in use.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*-
+*/
+
+/* Return if a NULL pointer has been suppplied. */
+   if( !this ) return NULL;
+
+/* Return the supplied pointer if the objects parent still exists. */
+   if( this->parent &&
+       astXmlCheckType( this->parent, AST__XMLPAR ) ) return this;
+
+#ifdef DEBUG
+/* Remove the supplied object from the list of currently active XmlObjects. */
+   RemoveObjectFromList( this );
+#endif
+
+/* Clean the objects contents, and free the memory holding the XmlObject. */
+   CleanXml( this, this->type, status );
+   astFree( this );
+
+/* Return a NULL pointer. */
+   return NULL;
+}
+
+void *astXmlAnnulTree_( AstXmlObject *this, int *status ){
+/*
+*+
+*  Name:
+*     astXmlAnnulTree
+
+*  Purpose:
+*     Free the resources used by a tree of XmlObjects.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void *astXmlAnnulTree( AstXmlObject *this )
+
+*  Description:
+*     This function finds the head of the tree containing the supplied
+*     XmlObject (either an XmlElement or an XmlDocument), and frees the
+*     resources associated with all members of the tree. A NULL pointer
+*     is always returned.
+
+*  Parameters:
+*     this
+*        Pointer to a member of the tree of XmlObjects to be freed.
+
+*  Returned Value:
+*     A NULL pointer.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*-
+*/
+
+/* Return if a NULL pointer has been suppplied. */
+   if( !this ) return NULL;
+
+/* Find the root and annull it. This will free all children (i.e.
+   the entire tree). */
+   return astXmlAnnul( astXmlGetRoot( this ) );
+}
+
+AstXmlObject *astXmlCopy_( AstXmlObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astXmlCopy
+
+*  Purpose:
+*     Produce a deep copy of a supplied XmlObject.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlObject *astXmlCopy( AstXmlObject *this )
+
+*  Description:
+*     This function returns a pointer to a deep copy of the supplied
+*     XmlObject.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlObject to copy.
+
+*  Returned Value:
+*     Pointer to the new copy.
+
+*  Notes:
+*     - NULL is returned if NULL pointer is supplied.
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+
+/* Local Variables: */
+   AstXmlAttribute *attr;
+   AstXmlBlack *black;
+   AstXmlCDataSection *cdata;
+   AstXmlComment *comm;
+   AstXmlDTDec *dtd;
+   AstXmlDeclPI *dec;
+   AstXmlDocument *doc, *newdoc;
+   AstXmlElement *elem, *newelem;
+   AstXmlNamespace *ns;
+   AstXmlObject *new;
+   AstXmlPI *pi;
+   AstXmlPrologue *pro, *newpro;
+   AstXmlWhite *white;
+   int i, type;
+
+/* Initialise */
+   new = NULL;
+
+/* Check the global error status. */
+   if( !astOK || !this ) return new;
+
+/* Initialise a new XmlObject of the required class, and copy any
+   sub-objects. */
+   type = this->type;
+   if( type == AST__XMLELEM  ){
+      elem = (AstXmlElement *) this;
+      new = astMalloc( sizeof( AstXmlElement ) );
+      InitXmlElement( (AstXmlElement *) new, AST__XMLELEM,
+                      elem->name, elem->prefix, status );
+
+      newelem = (AstXmlElement *) new;
+
+      newelem->attrs = astMalloc( sizeof( AstXmlAttribute *) * (size_t)elem->nattr );
+      newelem->nattr = elem->nattr;
+      for( i = 0; i < elem->nattr; i++ ) {
+         newelem->attrs[ i ] = (AstXmlAttribute *) astXmlCopy( elem->attrs[ i ] );
+         ((AstXmlObject *) newelem->attrs[ i ])->parent = (AstXmlParent *) newelem;
+      }
+
+      newelem->items = astMalloc( sizeof( AstXmlContentItem *) * (size_t)elem->nitem );
+      newelem->nitem = elem->nitem;
+      for( i = 0; i < elem->nitem; i++ ) {
+         newelem->items[ i ] = (AstXmlContentItem *) astXmlCopy( elem->items[ i ] );
+         ((AstXmlObject *) newelem->items[ i ])->parent = (AstXmlParent *) newelem;
+      }
+
+      newelem->nsprefs = astMalloc( sizeof( AstXmlNamespace *) * (size_t)elem->nnspref );
+      newelem->nnspref = elem->nnspref;
+      for( i = 0; i < elem->nnspref; i++ ) {
+         newelem->nsprefs[ i ] = (AstXmlNamespace *) astXmlCopy( elem->nsprefs[ i ] );
+         ((AstXmlObject *) newelem->nsprefs[ i ])->parent = (AstXmlParent *) newelem;
+      }
+
+      if( elem->defns ) {
+         newelem->defns = astStore( NULL, elem->defns,
+                                    strlen( elem->defns ) + 1 );
+      }
+
+      newelem->complete = elem->complete;
+
+
+   } else if( type == AST__XMLATTR ){
+      attr = (AstXmlAttribute *) this;
+      new = astMalloc( sizeof( AstXmlAttribute ) );
+      InitXmlAttribute( (AstXmlAttribute *) new, AST__XMLATTR,
+                        attr->name, attr->value, attr->prefix, status );
+
+   } else if( type == AST__XMLBLACK ){
+      black = (AstXmlBlack *) this;
+      new = astMalloc( sizeof( AstXmlBlack ) );
+      InitXmlBlack( (AstXmlBlack *) new, AST__XMLBLACK,
+                    black->text, status );
+
+   } else if( type == AST__XMLWHITE ){
+      white = (AstXmlWhite *) this;
+      new = astMalloc( sizeof( AstXmlWhite ) );
+      InitXmlWhite( (AstXmlWhite *) new, AST__XMLWHITE,
+                    white->text, status );
+
+   } else if( type == AST__XMLCDATA ){
+      cdata = (AstXmlCDataSection *) this;
+      new = astMalloc( sizeof( AstXmlCDataSection ) );
+      InitXmlCDataSection( (AstXmlCDataSection *) new, AST__XMLCDATA,
+                           cdata->text, status );
+
+   } else if( type == AST__XMLCOM ){
+      comm = (AstXmlComment *) this;
+      new = astMalloc( sizeof( AstXmlComment ) );
+      InitXmlComment( (AstXmlComment *) new, AST__XMLCOM,
+                      comm->text, status );
+
+   } else if( type == AST__XMLPI ){
+      pi = (AstXmlPI *) this;
+      new = astMalloc( sizeof( AstXmlPI ) );
+      InitXmlPI( (AstXmlPI *) new, AST__XMLPI, pi->target, pi->text, status );
+
+   } else if( type == AST__XMLNAME ){
+      ns = (AstXmlNamespace *) this;
+      new = astMalloc( sizeof( AstXmlNamespace ) );
+      InitXmlNamespace( (AstXmlNamespace *) new, AST__XMLNAME, ns->prefix,
+                         ns->uri, status );
+
+   } else if( type == AST__XMLDOC ){
+      doc = (AstXmlDocument *) this;
+      new = astMalloc( sizeof( AstXmlDocument ) );
+      InitXmlDocument( (AstXmlDocument *) new, AST__XMLDOC, status );
+
+      newdoc = (AstXmlDocument *) new;
+
+      if( doc->prolog ) {
+         newdoc->prolog = (AstXmlPrologue *) astXmlCopy( doc->prolog );
+         ((AstXmlObject *) newdoc->prolog)->parent = (AstXmlParent *) newdoc;
+      }
+
+      if( doc->root ) {
+         newdoc->root = (AstXmlElement *) astXmlCopy( doc->root );
+         ((AstXmlObject *) newdoc->root)->parent = (AstXmlParent *) newdoc;
+      }
+
+      newdoc->epilog = astMalloc( sizeof( AstXmlMiscItem *) * (size_t)doc->nepi );
+      newdoc->nepi = doc->nepi;
+      for( i = 0; i < doc->nepi; i++ ) {
+         newdoc->epilog[ i ] = (AstXmlMiscItem *) astXmlCopy( doc->epilog[ i ] );
+         ((AstXmlObject *) newdoc->epilog[ i ])->parent = (AstXmlParent *) newdoc;
+      }
+
+      newdoc->current = NULL;
+
+   } else if( type == AST__XMLPRO ){
+      pro = (AstXmlPrologue *) this;
+      new = astMalloc( sizeof( AstXmlPrologue ) );
+      InitXmlPrologue( (AstXmlPrologue *) new, AST__XMLPRO, status );
+
+      newpro = (AstXmlPrologue *) new;
+
+      if( pro->xmldecl ) {
+         newpro->xmldecl = (AstXmlDeclPI *) astXmlCopy( pro->xmldecl );
+         ((AstXmlObject *) newpro->xmldecl)->parent = (AstXmlParent *) newpro;
+      }
+
+      if( pro->dtdec ) {
+         newpro->dtdec = (AstXmlDTDec *) astXmlCopy( pro->dtdec );
+         ((AstXmlObject *) newpro->dtdec)->parent = (AstXmlParent *) newpro;
+      }
+
+      newpro->misc1 = astMalloc( sizeof( AstXmlMiscItem *) * (size_t)pro->nmisc1 );
+      newpro->nmisc1 = pro->nmisc1;
+      for( i = 0; i < pro->nmisc1; i++ ) {
+         newpro->misc1[ i ] = (AstXmlMiscItem *) astXmlCopy( pro->misc1[ i ] );
+         ((AstXmlObject *) newpro->misc1[ i ])->parent = (AstXmlParent *) newpro;
+      }
+
+      newpro->misc2 = astMalloc( sizeof( AstXmlMiscItem *) * (size_t)pro->nmisc2 );
+      newpro->nmisc2 = pro->nmisc2;
+      for( i = 0; i < pro->nmisc2; i++ ) {
+         newpro->misc2[ i ] = (AstXmlMiscItem *) astXmlCopy( pro->misc2[ i ] );
+         ((AstXmlObject *) newpro->misc2[ i ])->parent = (AstXmlParent *) newpro;
+      }
+
+   } else if( type == AST__XMLDEC ){
+      dec = (AstXmlDeclPI *) this;
+      new = astMalloc( sizeof( AstXmlDeclPI ) );
+      InitXmlDeclPI( (AstXmlDeclPI *) new, AST__XMLDEC, dec->text, status );
+
+   } else if( type == AST__XMLDTD ){
+      dtd = (AstXmlDTDec *) this;
+      new = astMalloc( sizeof( AstXmlDTDec ) );
+      InitXmlDTDec( (AstXmlDTDec *) new, AST__XMLDTD, dtd->name,
+                    dtd->external, dtd->internal, status );
+
+   } else if( astOK ) {
+      astError( AST__INTER, "CopyXml: Invalid object type (%d) supplied "
+                "(internal AST programming error).", status, type );
+   }
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) new = astXmlDelete( new );
+
+/* Return the result. */
+   return new;
+}
+
+const char *astXmlFormat_( AstXmlObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astXmlFormat
+
+*  Purpose:
+*     Converts an XmlObject into a character string.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *astXmlFormat( AstXmlObject *this )
+
+*  Description:
+*     This function returns a pointer to a dynamically allocated string
+*     containing a textual representation of the supplied XmlObject.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlObject to format.
+
+*  Returned Value:
+*     Pointer to a null terminated string holding the formated XmlObject.
+*     This string should be freed when no longer needed using astFree.
+
+*  Notes:
+*     - No newlines or indentation strings are added to the returned string.
+*     - NULL is returned if NULL pointer is supplied.
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+   return Format( this, -1, status );
+}
+
+const char *astXmlGetAttributeValue_( AstXmlElement *this, const char *name, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetAttributeValue
+
+*  Purpose:
+*     Return a pointer to a string holding the value of a named attribute.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *astXmlGetAttributeValue( AstXmlElement *this, const char *name )
+
+*  Description:
+*     This function returns a pointer to a constant string holding the
+*     value of a named attribute of a supplied element. If the element
+*     does not have the named attribute, a NULL pointer is returned but
+*     no error is reported.
+
+*  Parameters:
+*     this
+*        The pointer to the XmlElement.
+*     name
+*        Pointer to a string holding the name of the attribute. The name
+*        may be preceded with a "prefix:" string, in which case the
+*        prefix will also be matched. If no prefix is included, the first
+*        attribute with the specified name is returned, regardless of
+*        its prefix.
+
+*  Returned Value:
+*     Pointer to a string holding the value of the attribute within the
+*     supplied element, or NULL if the attribute was not found.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   const char *result;     /* Returned pointer */
+   AstXmlAttribute *attr;  /* Pointer to the attribute */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* Find the attribute. */
+   attr = FindAttribute( this, name, status );
+
+/* Get its value. */
+   if( attr ) result = attr->value;
+
+/* Return the result. */
+   return result;
+}
+
+AstXmlContentItem *astXmlGetItem_( AstXmlElement *this, int item, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetItem
+
+*  Purpose:
+*     Return a specified item of the content of an element.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlContentItem *astXmlGetItem( AstXmlElement *this, int item )
+
+*  Description:
+*     This function returns a pointer to an item of the content of the
+*     specified element.
+
+*  Parameters:
+*     this
+*        The pointer to the XmlElement.
+*     item
+*        The index of the required item, in the range zero to "nitem-1",
+*        where "nitem" is the number of items in the element as returned
+*        by astXmlGetNitem. An error is reported if the specified index
+*        is out of bounds.
+
+*  Returned Value:
+*     A pointer to the requested item.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlContentItem *result;     /* The returned pointer */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* Report an error if the supplie dindex is bad. */
+   if( this->nitem == 0 ) {
+      astError( AST__XMLIT, "astXmlGetItem(xml): The supplied item index (%d) "
+                "is out of bounds. The supplied XmlObject has no content.", status,
+                item );
+
+   } else if( item < 0 || item >= this->nitem ) {
+      astError( AST__XMLIT, "astXmlGetItem(xml): The supplied item index (%d) "
+                "is out of bounds. Should be in the range 0 to %d.", status,
+                item, this->nitem-1 );
+   } else {
+      result = this->items[ item ];
+   }
+
+/* Return the result. */
+   return result;
+}
+
+const char *astXmlGetName_( AstXmlObject *this, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetName
+
+*  Purpose:
+*     Return a pointer to a string holding the name of an XmlObject.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *astXmlGetName( AstXmlObject *this )
+
+*  Description:
+*     This function returns a pointer to a constant string holding the
+*     name associated with an XmlObject. For elements and attributes, the
+*     "name" value is returned. For PI elements, the "target" value is
+*     returned. For namespace definitions, the "prefix" value is returned.
+*     An error is reported if the supplied XmlObject is of any other class.
+
+*  Parameters:
+*     this
+*        The pointer to the XmlObject.
+
+*  Returned Value:
+*     Pointer to the name string within the XML object.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   const char *result;     /* Returned pointer */
+   int type;               /* Object type */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* Return the relevant component of the structure, depending on its type. */
+   type = this->type;
+   if( type == AST__XMLELEM  ){
+      result = ( (AstXmlElement *) this )->name;
+
+   } else if( type == AST__XMLATTR ){
+      result = ( (AstXmlAttribute *) this )->name;
+
+   } else if( type == AST__XMLPI ){
+      result = ( (AstXmlPI *) this )->target;
+
+   } else if( type == AST__XMLNAME ){
+      result = ( (AstXmlNamespace *) this )->prefix;
+
+   } else {
+      astError( AST__INTER, "astXmlGetName: Inappropriate object type (%d) supplied "
+                "(internal AST programming error).", status, type );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+int astXmlGetNattr_( AstXmlElement *this, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetNattr
+
+*  Purpose:
+*     Return the number of attributes held by an element.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     int astXmlGetNattr( AstXmlElement *this )
+
+*  Description:
+*     This function returns the number of  attributes held by an element.
+
+*  Parameters:
+*     this
+*        The pointer to the XmlElement.
+
+*  Returned Value:
+*     The number of  attributes held by the supplied element.
+
+*  Notes:
+*     - Zero is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return 0;
+
+/* Return the result. */
+   return ( this->attrs ) ? this->nattr : 0;
+}
+
+int astXmlGetNitem_( AstXmlElement *this, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetNitem
+
+*  Purpose:
+*     Return the number of items within the content of an element.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     int astXmlGetNitem( AstXmlElement *this )
+
+*  Description:
+*     This function returns the number of items within the content of an
+*     XmlElement.
+
+*  Parameters:
+*     this
+*        The pointer to the XmlElement.
+
+*  Returned Value:
+*     The number of items in the content of the supplied element.
+
+*  Notes:
+*     - Zero is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return 0;
+
+/* Return the result. */
+   return this->nitem;
+}
+
+AstXmlParent *astXmlGetParent_( AstXmlObject *this, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetParent
+
+*  Purpose:
+*     Return a pointer to the object which contains the supplied XmlObject.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlParent *astXmlGetParent( AstXmlObject *this )
+
+*  Description:
+*     This function returns a pointer to the XmlParent object (either an
+*     XmlElement or an XmlDocument) which contains the specified XmlObject.
+*     The object can be a content item (an element, a comment, a CDATA
+*     section, a PI, or character data) in which case the enclosing
+*     XmlElement is returned, or an attribute or namespace definition in
+*     which case the XmlElement to which object refers is returned.
+*     If "this" is the root element of a document, a pointer to the
+*     XmlDocument is returned.
+
+
+*  Parameters:
+*     this
+*        The pointer to check.
+
+*  Returned Value:
+*     Pointer to the parent, or NULL if the object does not have a parent.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return NULL;
+
+/* Return the result. */
+   return this->parent;
+}
+
+AstXmlObject *astXmlGetRoot_( AstXmlObject *this, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetRoot
+
+*  Purpose:
+*     Return a pointer to the root XmlObject which contains the supplied
+*     XmlObject.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlObject *astXmlGetRoot( AstXmlObject *this )
+
+*  Description:
+*     This function returns a pointer to the XmlObject which is the root of
+*     the tree containing the specified XmlObject. A pointer to the
+*     supplied XmlObject is returned if it has no parent.
+
+*  Parameters:
+*     this
+*        The pointer to check.
+
+*  Returned Value:
+*     Pointer to the root XmlObject, or a copy of the supplied pointer if
+*     the supplied XmlObject is the root.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Local Variables:  */
+   AstXmlObject *result;
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* If "this" is a document, check it has no parent. If not, return a
+   pointer to it. */
+   if( astXmlCheckType( this, AST__XMLDOC ) ) {
+      if( this->parent ) {
+         astError( AST__INTER, "astXmlGetRoot(xml): An XmlDocument has a "
+                   "non-null parent of type %ld (internal AST programming "
+                   "error).", status, this->type );
+      } else {
+         result = (AstXmlObject *) this;
+      }
+
+/* Otherwise... */
+   } else if( this->parent ) {
+      result = astXmlGetRoot( this->parent );
+
+   } else {
+      result = this;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+const char *astXmlGetTag_( AstXmlObject *this, int opening, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetTag
+
+*  Purpose:
+*     Returns a string holding an XML tag describing the given XmlObject.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *astXmlGetTag( AstXmlObject *this, int opening )
+
+*  Description:
+*     This function returns a pointer to a static string containing an
+*     XML tag describing the given XmlObject.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlObject.
+*     opening
+*        Indicates which tag is to be returned; the start tag or the end
+*        tag. If non-zero the start tag is returned. Otherwise, the
+*        end tag is returned. If the supplied XmlObject has no end
+*        tag (i.e. if it is an empty element, or if it is not an element),
+*        then NULL is returned but no error is reported.
+
+*  Returned Value:
+*     Pointer to a null terminated string holding the tag. If the tag
+*     exceeds 200 characters, only the first 197 characters are returned
+*     and "..." is appended to the end.
+
+*  Notes:
+*     - Subsequent invocations of this function will over-write the
+*     buffer which used to hold the returned string.
+*     - Empty elements are represented as an start tag of the form <.../>,
+*     with no corresponding end tag.
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS        /* Pointer to thread-specific global data */
+   char *result;             /* The returned pointer */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Get a dynamic string holding the formatted tag. */
+   result = FormatTag( this, opening, status );
+
+/* If OK, copy the result into the static buffer. */
+   gettag_buff[ 0 ] = 0;
+   if( result ) {
+      if( astOK ) {
+
+         if( strlen( result ) > AST__XML_GETTAG_BUFF_LEN ) {
+            strncpy( gettag_buff, result, AST__XML_GETTAG_BUFF_LEN -3 );
+            strcpy( gettag_buff + AST__XML_GETTAG_BUFF_LEN - 3, "..." );
+         } else {
+            strncpy( gettag_buff, result, AST__XML_GETTAG_BUFF_LEN );
+         }
+
+         gettag_buff[ AST__XML_GETTAG_BUFF_LEN ] = 0;
+         astFree( result );
+         result = gettag_buff;
+      } else {
+         result = astFree( result );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+const char *astXmlGetType_( AstXmlObject *this, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetType
+
+*  Purpose:
+*     Returns a string holding the type of the given XmlObject.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *astXmlGetType( AstXmlObject *this )
+
+*  Description:
+*     This function returns a pointer to a static string containing the
+*     type of the given XmlObject.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlObject.
+
+*  Returned Value:
+*     Pointer to a null terminated string holding the type string.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   const char *result;             /* The returned pointer */
+   int type;                       /* Element type */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+   type = this->type;
+   if( type == AST__XMLELEM ) {
+      result = "element";
+
+   } else if( type == AST__XMLATTR ) {
+      result = "attribute";
+
+   } else if( type == AST__XMLCDATA ) {
+      result = "CDATA section";
+
+   } else if( type == AST__XMLCOM ) {
+      result = "comment";
+
+   } else if( type == AST__XMLPI ) {
+      result = "processing instruction";
+
+   } else if( type == AST__XMLNAME ) {
+      result = "namespace";
+
+   } else if( type == AST__XMLDOC ) {
+      result = "document";
+
+   } else if( type == AST__XMLPRO ) {
+      result = "prologue";
+
+   } else if( type == AST__XMLDEC ) {
+      result = "XML delaration PI";
+
+   } else if( type == AST__XMLDTD ) {
+      result = "DTD";
+
+   } else if( type == AST__XMLWHITE ) {
+      result = "white-space character data ";
+
+   } else if( type == AST__XMLBLACK ) {
+      result = "non-blank character data";
+
+   } else {
+      result = "unknown XML object";
+   }
+
+/* Return the result. */
+   return result;
+}
+
+const char *astXmlGetURI_( AstXmlObject *this, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetURI
+
+*  Purpose:
+*     Return a pointer to a string holding the namespace URI of an XmlObject.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *astXmlGetURI( AstXmlObject *this )
+
+*  Description:
+*     This function returns a pointer to a constant string holding the
+*     namespace URI associated with an XmlObject. Only attributes,
+*     elements and namespaces have associated URIs, so a NULL pointer is
+*     returned for any other class of XmlObject. A NULL pointer is also
+*     returned if XmlObject does not belong to any namespace, or if it
+*     belongs to a unknown namespace (i.e. one for which no URI is
+*     available). Any namespace prefix attached to the supplied object is
+*     resolved first using any "xmlns" attributes contained in the same
+*     element, then using any "xmlns" attributes contained in the parent
+*     element, etc.
+
+*  Parameters:
+*     this
+*        The pointer to the XmlObject.
+
+*  Returned Value:
+*     Pointer to a string holding the namespace URI, or NULL.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   const char *prefix;     /* Namespace prefix */
+   const char *result;     /* Returned pointer */
+   int type;               /* Object type */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* Do each type of object separately. */
+   type = this->type;
+   if( type == AST__XMLATTR ){
+      prefix = ( (AstXmlAttribute *) this )->prefix;
+
+/* Attributes have no default name space. Therefore if there is no prefix,
+   return NULL. If there is a prefix, resolve it within the context of
+   the attributes parent element. */
+      if( prefix ) {
+         result = ResolvePrefix( prefix, (AstXmlElement *) this->parent, status );
+      }
+
+   } else if( type == AST__XMLELEM ){
+      prefix = ( (AstXmlElement *) this )->prefix;
+
+/* If there is a prefix, resolve it within the context of this element. */
+      if( prefix ) {
+         result = ResolvePrefix( prefix, (AstXmlElement *) this, status );
+
+/* Elements do have a default name space. Therefore if there is no prefix,
+   return the default name space within the context of this element. */
+      } else {
+         result = DefaultURI( (AstXmlElement *) this, status );
+      }
+
+/* If the supplied object is a namespace, just return the associated URI. */
+   } else if( type == AST__XMLNAME ){
+      result = ( (AstXmlNamespace *) this )->uri;
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+const char *astXmlGetValue_( AstXmlObject *this, int report, int *status ){
+/*
+*+
+*  Name:
+*     astXmlGetValue
+
+*  Purpose:
+*     Return a pointer to a string holding the value of an XmlObject.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *astXmlGetValue( AstXmlObject *this, int report )
+
+*  Description:
+*     This function returns a pointer to a constant string holding the
+*     value associated with an XmlObject. For attributes, the attribute value
+*     is returned. For PI elements, the "text" value is returned. For
+*     namespace definitions, the "URI" value is returned. For character
+*     data, the character data is returned. For CDATA sections the "text"
+*     value is returned. For comments, the "text" value is returned.
+*     If the XmlObject is an element, then a non-NULL value is returned
+*     only if the element contains a single content item holding character
+*     data. In this case a pointer to the character data is returned.
+*     A null value is returned in all other cases (but no error is
+*     reported unless "report" is non-zero).
+
+*  Parameters:
+*     this
+*        The pointer to the XmlObject.
+*     report
+*        Report an error if the supplied XmlObject does not have a value?
+
+*  Returned Value:
+*     Pointer to a string holding the value of the XML object.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlContentItem *item;/* Element content */
+   const char *result;     /* Returned pointer */
+   int type;               /* Object type */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* Return the relevant component of the structure, depending on its type. */
+   type = this->type;
+   if( type == AST__XMLATTR ){
+      result = ( (AstXmlAttribute *) this )->value;
+
+   } else if( type == AST__XMLBLACK ){
+      result = ( (AstXmlBlack *) this )->text;
+
+   } else if( type == AST__XMLWHITE ){
+      result = ( (AstXmlWhite *) this )->text;
+
+   } else if( type == AST__XMLCDATA ){
+      result = ( (AstXmlCDataSection *) this )->text;
+
+   } else if( type == AST__XMLCOM ){
+      result = ( (AstXmlComment *) this )->text;
+
+   } else if( type == AST__XMLPI ){
+      result = ( (AstXmlPI *) this )->text;
+
+   } else if( type == AST__XMLNAME ){
+      result = ( (AstXmlNamespace *) this )->uri;
+
+   } else if( type == AST__XMLELEM ){
+      if( astXmlGetNitem( (AstXmlElement *) this ) == 1 ) {
+         item = astXmlGetItem( (AstXmlElement *) this, 0 );
+         if( astXmlCheckType( item, AST__XMLCHAR ) ) {
+            result = astXmlGetValue( item, report );
+         }
+      }
+
+      if( !result && astOK && report ) {
+         astError( AST__BADIN, "astRead(xml): Cannot get the value of "
+                   "element \"<%s>\": its contents are not pure character "
+                   "data.", status, astXmlGetName( this ) );
+      }
+
+   } else if( report ) {
+      astError( AST__INTER, "astXmlGetValue(xml): Cannot get the value of "
+                "an XmlObject of type %d (internal AST programming "
+                "error).", status, type );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+void astXmlInsertElement_( AstXmlElement *this, AstXmlElement *elem, int *status ){
+/*
+*+
+*  Name:
+*     astXmlInsertElement
+
+*  Purpose:
+*     Inserts an existing XmlElement into another XmlElement.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlInsertElement( AstXmlElement *this, AstXmlElement *elem )
+
+*  Description:
+*     This function inserts a given XmlElement "elem" into another given
+*     XmlElement "this". An error is reported if "elem" already has a
+*     parent.
+
+*  Parameters:
+*     this
+*        A pointer to the element to be modified.
+*     elem
+*        The element to be inserted into "this".
+
+*-
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Report AN error if "elem" has already been inserted into
+   another element. */
+   if( ((AstXmlObject *) elem)->parent ) {
+      astError( AST__INTER, "astXmlInsertElement(xml): Cannot insert \"%s\" "
+                "into \"%s\" because it already has a parent (\"%s\") "
+                "(internal AST programming error).", status,
+                astXmlGetTag( elem, 1 ), astXmlGetTag( this, 1 ),
+                astXmlGetTag( ((AstXmlObject *) elem)->parent, 1 ) );
+
+/* Otherwise, add the content item to the element. */
+   } else {
+      AddContent( (AstXmlParent *) this, 0, (AstXmlContentItem *) elem, status );
+   }
+}
+
+void astXmlPurge_( AstXmlParent *this, int *status ) {
+/*
+*+
+*  Name:
+*     astXmlPurge
+
+*  Purpose:
+*     Remove blank content from a parent object.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlPurge( AstXmlParent *this )
+
+*  Description:
+*     This function removes all character data containing only whitespace
+*     from the supplied document or element. It is recursive, in that it also
+*     removes white space from all children elements.
+
+*  Parameters:
+*     this
+*        Pointer to the document or element.
+
+*-
+*/
+
+/* Local Variables: */
+   int i;                    /* Content item index */
+   AstXmlContentItem *item;  /* Next content item */
+   AstXmlMiscItem *misc;     /* Nest miscalleneous item */
+   AstXmlDocument *doc;      /* This document */
+   AstXmlPrologue *pro;      /* This document prologue */
+   AstXmlElement *elem;      /* This element */
+
+/* Check the global error status. */
+   if( !astOK || !this ) return;
+
+/* If this is a a document.. */
+   if( astXmlCheckType( this, AST__XMLDOC ) ) {
+      doc = (AstXmlDocument *) this;
+      astXmlPurge( doc->prolog );
+      astXmlPurge( doc->root );
+
+      i = -1;
+      while( ++i < doc->nepi ) {
+         misc = doc->epilog[ i ];
+         if( astXmlCheckType( misc, AST__XMLWHITE ) ) {
+            misc = astXmlDelete( misc );
+            i--;
+         }
+      }
+
+/* If this is a prologue.. */
+   } else if( astXmlCheckType( this, AST__XMLPRO ) ) {
+      pro = (AstXmlPrologue *) this;
+
+      i = -1;
+      while( ++i < pro->nmisc1 ) {
+         misc = pro->misc1[ i ];
+         if( astXmlCheckType( misc, AST__XMLWHITE ) ) {
+            misc = astXmlDelete( misc );
+            i--;
+         }
+      }
+
+      i = -1;
+      while( ++i < pro->nmisc2 ) {
+         misc = pro->misc2[ i ];
+         if( astXmlCheckType( misc, AST__XMLWHITE ) ) {
+            misc = astXmlDelete( misc );
+            i--;
+         }
+      }
+
+
+/* If this is an element */
+   } else if( astXmlCheckType( this, AST__XMLELEM ) ) {
+      elem = (AstXmlElement *) this;
+
+      i = -1;
+      while( ++i < elem->nitem ) {
+         item = elem->items[ i ];
+
+         if( astXmlCheckType( item, AST__XMLWHITE ) ) {
+            item = astXmlDelete( item );
+            i--;
+
+         } else if( astXmlCheckType( item, AST__XMLELEM ) ) {
+            astXmlPurge( (AstXmlParent *) item );
+         }
+      }
+   }
+}
+
+void astXmlRemoveAttr_( AstXmlElement *this, const char *name,
+                        const char *prefix, int *status ){
+/*
+*+
+*  Name:
+*     astXmlRemoveAttr
+
+*  Purpose:
+*     Removes an attribute from its parent element.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlRemoveAttr( AstXmlElement *this, const char *name,
+*                            const char *prefix )
+
+*  Description:
+*     This function removes a named attribute from its parent element.
+
+*  Parameters:
+*     this
+*        The pointer to the element containing the attribute to be removed.
+*     name
+*        Pointer to a null terminated string containing the attribute name.
+*     prefix
+*        The namespace prefix for the attribute. May be NULL or blank, in
+*        which case any prefix at the start of "name" is used.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlAttribute *attr;           /* Pointer to temporary attribute structure */
+   AstXmlAttribute *oldattr;        /* Pointer to existing attribute */
+   int i;                           /* Attribute index */
+   int nattr;                       /* Number of attributes in parent */
+   int oldi;                        /* Indexof existing attribute */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Initialise */
+   oldattr = NULL;
+
+/* Create a new XmlAttribute with blank value. */
+   attr = NewAttribute( name, "", prefix, status );
+   if( astOK ) {
+
+/* Get the number of attributes currently stored in the element. */
+      nattr = ( this->attrs ) ? this->nattr : 0;
+
+/* Search the existing attributes to see if an attribute with the given
+   name and prefix already exists. */
+      oldi = -1;
+      for( i = 0; i < nattr; i++ ) {
+         oldattr = this->attrs[ i ];
+         if( !strcmp( oldattr->name, attr->name ) ) {
+            if( !oldattr->prefix && !attr->prefix ) {
+               oldi = i;
+               break;
+            } else if( oldattr->prefix && attr->prefix &&
+                       !strcmp( oldattr->prefix, attr->prefix ) ){
+               oldi = i;
+               break;
+            }
+         }
+      }
+
+/* If there is an existing attribute with the same name and prefix,
+   delete it. */
+      if( oldi > -1 ) astXmlDelete( oldattr );
+
+/* Delete the temporary attribute structure. */
+      attr = astXmlDelete( attr );
+
+   }
+}
+
+void astXmlRemoveItem_( AstXmlContentItem *this, int *status ){
+/*
+*+
+*  Name:
+*     astXmlRemoveItem
+
+*  Purpose:
+*     Removes an item of content from its parent element or document.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlRemoveItem( AstXmlContentItem *this )
+
+*  Description:
+*     This function removes an item of content from its parent element,
+*     or removes the root element from a document. The removed item is not
+*     annulled and may be subsequently added into another element.
+
+*  Parameters:
+*     this
+*        The pointer to the item to be removed form its parent.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlDocument *doc;             /* Pointer to parent document */
+   AstXmlElement *elem;             /* Pointer to parent element */
+   AstXmlParent *parent;            /* Pointer to parent */
+   int found;                       /* Was the item found within its parent? */
+   int i;                           /* Item index */
+   int j;                           /* Item index */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Get a pointer to the items parent element, and check it is not null. */
+   parent = ( (AstXmlObject *) this )->parent;
+   if( parent && astXmlCheckType( parent, AST__XMLELEM ) ) {
+      elem = (AstXmlElement *) parent;
+
+/* Search through all the items within the parent element looking for the
+   supplied item. */
+      found = 0;
+      for( i = 0; i < elem->nitem; i++ ) {
+         if( elem->items[ i ] == this ) {
+
+/* When found, decrement the number of items in the element, and shuffle
+   all the remaining item pointers down one slot to over-write it, then
+   nullify the parent pointer in the supplied object and leave the loop. */
+            (elem->nitem)--;
+            for( j = i; j < elem->nitem; j++ ) {
+               elem->items[ j ] = elem->items[ j + 1 ];
+            }
+            ( (AstXmlObject *) this )->parent = NULL;
+            found = 1;
+            break;
+         }
+      }
+
+/* Report an error if the item was not found. */
+      if( !found ) {
+         astError( AST__INTER, "astXmlRemoveItem: The parent of the supplied "
+                   "item does not contain the item (internal AST programming "
+                   "error)." , status);
+      }
+
+/* If the parent is an XmlDocument, check the item being removed is the
+   root element. */
+   } else if( parent && astXmlCheckType( parent, AST__XMLDOC ) ) {
+      doc = (AstXmlDocument *) parent;
+      if( (AstXmlElement *) this == doc->root ) {
+         ( (AstXmlObject *) this )->parent = NULL;
+         doc->root = NULL;
+      }
+   }
+}
+
+void astXmlRemoveURI_( AstXmlElement *this, const char *prefix, int *status ){
+/*
+*+
+*  Name:
+*     astXmlRemoveURI
+
+*  Purpose:
+*     Removes an namespace prefix from its parent element.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlRemoveURI( AstXmlElement *this, const char *prefix )
+
+*  Description:
+*     This function removes a named namespace prefix from its parent element.
+
+*  Parameters:
+*     this
+*        The pointer to the element containing the namespace prefix to be
+*        removed.
+*     prefix
+*        The namespace prefix to remove.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlNamespace *ns;             /* Temporary namespace structure */
+   AstXmlNamespace *oldns;          /* Pointer to existing namespace */
+   int oldi;                        /* Index of namespace within its parent */
+   int i;                           /* Namespace index */
+   int nns;                         /* Number of existing namespaces */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Initialise */
+    oldns = NULL;
+
+/* Create a new XmlNamespace with blank URI. */
+   ns = NewNamespace( prefix, "", status );
+   if( astOK ) {
+
+/* Get the number of namespace prefixes currently stored in the element. */
+      nns = ( this->nsprefs ) ? this->nnspref : 0;
+
+/* Search the list of existing namespace prefixes to see if the given prefix
+   is included. */
+      oldi = -1;
+      for( i = 0; i < nns; i++ ) {
+         oldns = this->nsprefs[ i ];
+         if( !strcmp( oldns->prefix, ns->prefix ) ){
+             oldi = i;
+             break;
+         }
+      }
+
+/* If the supplied namespace prefix was found in the list, delete it. */
+      if( oldi > -1 ) astXmlDelete( oldns );
+
+/* Delete the temporary namespace structure. */
+      ns = astXmlDelete( ns );
+
+   }
+}
+
+void astXmlSetDTDec_( AstXmlDocument *this, const char *text1,
+                      const char *text2, const char *text3, int *status ){
+/*
+*+
+*  Name:
+*     astXmlSetDTDec
+
+*  Purpose:
+*     Set the Document Type declaration for a document.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlSetDTDEC( AstXmlDocument *this, const char *text1,
+*                          const char *text2, const char *text3 )
+
+*  Description:
+*     This function stores an Document Type declaration of the form
+*
+*        <!DOCTYPE text1 text2 [text3]>
+*
+*     in the supplied document. Any previous DTD is removed.
+
+*  Parameters:
+*     this
+*        The pointer to the document.
+*     text1
+*        The document type name.
+*     text2
+*        The text defining the external elements of the document type
+*        (may be NULL).
+*     text3
+*        The text defining the internal elements of the document type
+*        (may be NULL). Do not include delimiting "[" and "]" characters.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlDTDec *new;             /* Pointer to new DT declaration */
+   AstXmlPrologue *pro;          /* Pointer to prologue */
+   char *my_text2;               /* Cleaned text2 */
+   char *my_text3;               /* Cleaned text3 */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Allocate space for the new structure. */
+   new = (AstXmlDTDec *) astMalloc( sizeof( AstXmlDTDec ) );
+
+/* Clean the text. */
+   my_text2 = CleanText( text2, status );
+   my_text3 = CleanText( text3, status );
+
+/* Initialise it. */
+   InitXmlDTDec( new, AST__XMLDTD, text1, my_text2, my_text3, status );
+
+/* Free the memory */
+   my_text2 = astFree( my_text2 );
+   my_text3 = astFree( my_text3 );
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) {
+      new = astXmlDelete( new );
+
+/* Otherwise, store it in the document, deleting any existing declaration
+   first. */
+   } else {
+
+/* Create a prologue if necessary. */
+      if( !this->prolog ) this->prolog = NewPrologue( this, status );
+
+      pro = this->prolog;
+      if( pro->dtdec ) astXmlDelete( pro->dtdec );
+      pro->dtdec = new;
+   }
+}
+
+void astXmlSetXmlDec_( AstXmlDocument *this, const char *text, int *status ){
+/*
+*+
+*  Name:
+*     astXmlSetXmlDec
+
+*  Purpose:
+*     Set the XML declaration for a document.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void astXmlSetXmlDec( AstXmlDocument *this, const char *text )
+
+*  Description:
+*     This function stores an XML declaration of the form
+*
+*        <?xml [text]?>
+*
+*     in the supplied document. Any previous XML declaration is removed.
+
+*  Parameters:
+*     this
+*        The pointer to the document.
+*     text
+*        The text to include in the XML declaration tag.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlDeclPI *new;             /* Pointer to new XML delcaration */
+   AstXmlPrologue *pro;           /* Pointer to prologue */
+   char *my_text;                 /* Cleaned text */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Allocate space for the new structure. */
+   new = (AstXmlDeclPI *) astMalloc( sizeof( AstXmlDeclPI ) );
+
+/* Clean the text. */
+   my_text = CleanText( text, status );
+
+/* Initialise it. */
+   InitXmlDeclPI( new, AST__XMLDEC, my_text, status );
+
+/* Free the memory */
+   my_text = astFree( my_text );
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) {
+      new = astXmlDelete( new );
+
+/* Otherwise, store it in the document, deleting any existing declaration
+   first. */
+   } else {
+
+/* Create a prologue if necessary. */
+      if( !this->prolog ) this->prolog = NewPrologue( this, status );
+
+      pro = this->prolog;
+      if( pro->xmldecl ) astXmlDelete( pro->xmldecl );
+      pro->xmldecl = new;
+   }
+}
+
+const char *astXmlShow_( AstXmlObject *this, int *status ) {
+/*
+*+
+*  Name:
+*     astXmlShow
+
+*  Purpose:
+*     Converts an XmlObject into a character string with indentation.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *astXmlShow( AstXmlObject *this )
+
+*  Description:
+*     This function returns a pointer to a dynamically allocated string
+*     containing a textual representation of the supplied XmlObject.
+*     Newline characters are added to the string if needed to ensure that
+*     each item of content within an element starts on a new line, and all
+*     tags are preceded by an indentation string consisting of a number
+*     of spaces.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlObject to format.
+
+*  Returned Value:
+*     Pointer to a null terminated string holding the formated XmlObject.
+*     This string should be freed when no longer needed using astFree.
+
+*  Notes:
+*     - NULL is returned if a NULL pointer is supplied.
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+   return Format( this, 0, status );
+}
+
+static void CheckName( const char *name, const char *noun, const char *method,
+                       int nullok, int *status ){
+/*
+*  Name:
+*     CheckName
+
+*  Purpose:
+*     Checks the supplied string is a valid XML name.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void CheckName( const char *name, const char *noun, const char *method,
+*                     int nullok, int *status )
+
+*  Description:
+*     This function checks that the supplied string is a valid XML name,
+*     and reports an error otherwise.
+
+*  Parameters:
+*     name
+*        The name string to check
+*     noun
+*        A word to describe the object which the name applies to - for use in
+*        error messages only.
+*     method
+*        The name of the calling method - for use in error messages only.
+*     nullok
+*        If non-zero, then a null or empty name is assumed to be
+*        acceptable.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   const char *c;       /* Pointer to next character to check */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the string is not null. */
+   if( !name ) {
+      if( !nullok ) astError( AST__XMLNM, "%s: A NULL pointer was supplied "
+                              "instead of an XML %s name.", status, method, noun );
+   } else {
+
+      c = name;
+      if( *c == 0 ) {
+         if( !nullok ) astError( AST__XMLNM, "%s: An empty string was supplied "
+                                 "instead of an XML %s name.", status, method, noun );
+      } else {
+
+         if( !isalpha( *c ) && *c != '_' ) {
+            astError( AST__XMLNM, "%s: The illegal XML %s name \"%s\" was "
+                      "encountered.", status, method, noun, name );
+
+         } else {
+            while( *(++c) ) {
+               if( !isalnum( *c ) && *c != '_' && *c != '-' && *c != '.' ){
+                  astError( AST__XMLNM, "%s: The illegal XML %s name \"%s\" was "
+                            "encountered.", status, method, noun, name );
+                  break;
+               }
+            }
+         }
+      }
+   }
+}
+
+static void CheckPrefName( char *name, const char *noun, const char *method, int *status ){
+/*
+*  Name:
+*     CheckPrefName
+
+*  Purpose:
+*     Checks the supplied string is a valid XML (prefix:)name.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void CheckPrefName( char *name, const char *noun, const char *method, int *status )
+
+*  Description:
+*     This function checks that the supplied string is a valid XML
+*     (prefix:)name combination and reports an error otherwise.
+
+*  Parameters:
+*     name
+*        The string to check
+*     noun
+*        A word to describe the object which the name applies to - for use in
+*        error messages only.
+*     method
+*        The name of the calling method - for use in error messages only.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   char *colon;             /* Pointer to first colon */
+   char *temp;              /* Pointer to temporary string */
+   int nc;                  /* Length of temporary string */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Search for a ":" character. */
+   colon = strchr( name, ':' );
+
+/* If found, temporarily convert the colon into a null so that it
+   terminates the prefix string. */
+   if( colon ) {
+      *colon = 0;
+
+/* Check the string before the colon is a valid name. */
+      temp = NULL;
+      temp = astAppendString( temp, &nc, noun );
+      temp = astAppendString( temp, &nc, " prefix" );
+      CheckName( name, temp, method, 0, status );
+      temp = astFree( temp );
+
+/* Restore the colon. */
+      *colon = ':';
+
+/* Check the string following the colon is a valid name. */
+      CheckName( colon + 1, noun, method, 0, status );
+
+/* If not found, the whole supplied string must be a name. */
+   } else {
+      CheckName( name, noun, method, 0, status );
+   }
+}
+
+static int CheckType( long int given, long int want, int *status ){
+/*
+*  Name:
+*     CheckType
+
+*  Purpose:
+*     Check that the supplied type identifies an object of a given class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     int CheckType( long int given, long int want, int *status )
+
+*  Description:
+*     This function checks that the supplied type identifier identifies
+*     a specified class of XML object, or a derived class. A flag is
+*     returned indicating if the check succeeds. No error is reported if
+*     the check fails.
+
+*  Parameters:
+*     given
+*        The type value to be checked.
+*     want
+*        The type of the required class.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if the check is passed, zero if not of if an error has
+*     already occurred.
+
+*  Notes:
+*     - This function attempts to execute even if the error status is set.
+*/
+
+/* Local Variables: */
+   int result;              /* Returned value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the wanted type is recognised. Report an error if not. */
+   if( want != AST__XMLOBJECT &&
+       want != AST__XMLELEM  &&
+       want != AST__XMLATTR  &&
+       want != AST__XMLCHAR  &&
+       want != AST__XMLCDATA &&
+       want != AST__XMLCOM   &&
+       want != AST__XMLPI    &&
+       want != AST__XMLNAME  &&
+       want != AST__XMLCONT  &&
+       want != AST__XMLPRO   &&
+       want != AST__XMLDEC   &&
+       want != AST__XMLDTD   &&
+       want != AST__XMLMISC  &&
+       want != AST__XMLBLACK &&
+       want != AST__XMLWHITE &&
+       want != AST__XMLPAR   &&
+       want != AST__XMLDOC ) {
+      if( astOK ) {
+         astError( AST__INTER, "CheckType(Xml): Unsupported XML object "
+                   "type (%ld) supplied for parameter \"want\" (internal "
+                   "AST programming error). ", status, want );
+      }
+
+/* You should never be given a generic "interface" type since the
+   "wanted" value comes from the "type" component of an XmlObject (an explicit
+   class type should always be given). */
+   } else if( given == AST__XMLPAR ||
+              given == AST__XMLMISC ||
+              given == AST__XMLCONT ||
+              given == AST__XMLCHAR ) {
+      if( astOK ) {
+         astError( AST__INTER, "CheckType(Xml): Generic type (%ld) supplied for "
+                   "parameter \"given\" (internal AST programming error).", status,
+                   given );
+      }
+
+/* If the above is OK, return a non-zero value if the type to be tested
+   equals the wanted type. */
+   } else if( want == given ) {
+      result = 1;
+
+/* If any class of XmlObject is acceptable, check that he given class
+   type is a valid XML class type. */
+   } else if( want == AST__XMLOBJECT ) {
+      result = ( given == AST__XMLELEM  ||
+                 given == AST__XMLATTR  ||
+                 given == AST__XMLCDATA ||
+                 given == AST__XMLCOM   ||
+                 given == AST__XMLPI    ||
+                 given == AST__XMLNAME  ||
+                 given == AST__XMLPRO   ||
+                 given == AST__XMLDEC   ||
+                 given == AST__XMLDTD   ||
+                 given == AST__XMLWHITE ||
+                 given == AST__XMLBLACK ||
+                 given == AST__XMLDOC );
+
+/* Otherwise, for "interface" types, check if the given class "implements
+   the interface". */
+   } else if( want == AST__XMLCONT ) {
+      result = ( given == AST__XMLELEM  ||
+                 given == AST__XMLBLACK ||
+                 given == AST__XMLWHITE ||
+                 given == AST__XMLCDATA ||
+                 given == AST__XMLCOM   ||
+                 given == AST__XMLPI    );
+
+   } else if( want == AST__XMLMISC ) {
+      result = ( given == AST__XMLWHITE ||
+                 given == AST__XMLCOM   ||
+                 given == AST__XMLPI    );
+
+   } else if( want == AST__XMLCHAR ) {
+      result = ( given == AST__XMLWHITE ||
+                 given == AST__XMLBLACK );
+
+   } else if( want == AST__XMLPAR ) {
+      result = ( given == AST__XMLDOC ||
+                 given == AST__XMLPRO ||
+                 given == AST__XMLELEM );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+int astXmlCheckType_( void *this, long int want, int *status ){
+/*
+*+
+*  Name:
+*     astXmlCheckType
+
+*  Purpose:
+*     Check that the supplied object is of a given class.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     int astXmlCheckType( void *this, long int want )
+
+*  Description:
+*     This function checks that the supplied XmlObject is of a specified
+*     class of XML object, or a derived class. A flag is returned indicating
+*     if the check succeeds. No error is reported if the check fails.
+
+*  Parameters:
+*     this
+*        The object to check.
+*     want
+*        The type of the required class.
+
+*  Returned Value:
+*     Non-zero if the check is passed, zero if not of if an error has
+*     already occurred.
+
+*  Notes:
+*     - This function attempts to execute even if the error status is set.
+*-
+*/
+
+   if( this ) {
+      return CheckType( ((AstXmlObject *) this)->type, want, status );
+   } else {
+      return 0;
+   }
+}
+
+static char *CleanText( const char *text, int *status ){
+/*
+*  Name:
+*     CleanText
+
+*  Purpose:
+*     Normalise end-of-lines in the supplied text.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     char *CleanText( const char *text, int *status )
+
+*  Description:
+*     This function returns a copy of "text in which "\r\n" has been
+*     replaced by "\n" and any remaining "\r" characters have been
+*     replaced by "\n".
+
+*  Parameters:
+*     text
+*        A pointer to a text string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated string containing the required
+*     copy.
+
+*  Notes:
+*     - NULL is returned if this function is called with the global error
+*     status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   char *d;                  /* Pointer to next returned character */
+   char *result;             /* Returned pointer */
+   char *c;                  /* Pointer to next supplied character */
+   char lc;                  /* Previous character */
+
+/* Initialise */
+   result = NULL;
+
+/* Return if the pointer is NULL or if an error has occurred. */
+   if( !astOK || !text ) return result;
+
+/* Take a copy of the supplied text */
+   result = astStore( NULL, text, strlen( text ) + 1 );
+
+/* Clean the text by replacing "\r\n" by "\n". */
+   c = result - 1;
+   d = c;
+   lc = 0;
+   while( *(++c) ) {
+      if( *c != '\n' || lc != '\r' ) d++;
+      *d = ( lc = *c );
+   }
+   *(++d) = 0;
+
+/* Now further clean it by replacing "\r" by "\n". */
+   c = result - 1;
+   while( *(++c) ) {
+      if( *c == '\r' ) *c = '\n';
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void CleanXml( AstXmlObject *this, long int type, int *status ){
+/*
+*  Name:
+*     CleanXml
+
+*  Purpose:
+*     Free the resources used within an XmlObject.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void CleanXml( AstXmlObject *this, long int type, int *status )
+
+*  Description:
+*     This function frees the resources used internally within the
+*     supplied XmlObject.
+
+*  Parameters:
+*     this
+*        pointer to the XmlObject to be cleaned.
+*     type
+*        The type of XmlObject being cleaned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if an error has already
+*     occurred.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlAttribute *attr;
+   AstXmlBlack *black;
+   AstXmlCDataSection *cdatasec;
+   AstXmlComment *comm;
+   AstXmlDTDec *dtd;
+   AstXmlDeclPI *dec;
+   AstXmlDocument *doc;
+   AstXmlElement *elem;
+   AstXmlNamespace *ns;
+   AstXmlPI *pi;
+   AstXmlPrologue *pro;
+   AstXmlWhite *white;
+
+/* Return if a NULL pointer has been suppplied. */
+   if( !this ) return;
+
+/* For the base XmlObject class, clear the object type, etc. */
+   if( type == AST__XMLOBJECT  ){
+      this->type = AST__XMLBAD;
+      this->parent = NULL;
+
+/* For each derived class of XmlObject, first clean the parent component,
+   then clean any further resources. */
+   } else if( type == AST__XMLELEM  ){
+
+      elem = (AstXmlElement *) this;
+
+      elem->name = astFree( elem->name );
+      elem->defns = astFree( elem->defns );
+      elem->prefix = astFree( elem->prefix );
+
+      while( elem->nattr > 0 ) astXmlDelete( elem->attrs[ 0 ] );
+      elem->attrs = astFree( elem->attrs );
+
+      while( elem->nitem > 0 ) astXmlDelete( elem->items[ 0 ] );
+      elem->items = astFree( elem->items );
+
+      while( elem->nnspref > 0 ) astXmlDelete( elem->nsprefs[ 0 ] );
+      elem->nsprefs = astFree( elem->nsprefs );
+
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLATTR ){
+      attr = (AstXmlAttribute *) this;
+      attr->name = astFree( attr->name );
+      attr->value = astFree( attr->value );
+      attr->prefix = astFree( attr->prefix );
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLBLACK ){
+      black = (AstXmlBlack *) this;
+      black->text = astFree( black->text );
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLWHITE ){
+      white = (AstXmlWhite *) this;
+      white->text = astFree( white->text );
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLCDATA ){
+      cdatasec = (AstXmlCDataSection *) this;
+      cdatasec->text = astFree( cdatasec->text );
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLCOM ){
+      comm = (AstXmlComment *) this;
+      comm->text = astFree( comm->text );
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLPI ){
+      pi = (AstXmlPI *) this;
+      pi->target = astFree( pi->target );
+      pi->text = astFree( pi->text );
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLNAME ){
+      ns = (AstXmlNamespace *) this;
+      ns->prefix = astFree( ns->prefix );
+      ns->uri = astFree( ns->uri );
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLDOC ){
+      doc = (AstXmlDocument *) this;
+      doc->prolog = astXmlDelete( doc->prolog );
+      doc->root = astXmlDelete( doc->root );
+      while( doc->nepi > 0 ) astXmlDelete( doc->epilog[ 0 ] );
+      doc->epilog = astFree( doc->epilog );
+      doc->current = NULL;
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLPRO ){
+      pro = (AstXmlPrologue *) this;
+      pro->xmldecl = astXmlDelete( pro->xmldecl );
+      while( pro->nmisc1 > 0 ) astXmlDelete( pro->misc1[ 0 ] );
+      pro->misc1 = astFree( pro->misc1 );
+      pro->dtdec = astXmlDelete( pro->dtdec );
+      while( pro->nmisc2 > 0 ) astXmlDelete( pro->misc2[ 0 ] );
+      pro->misc2 = astFree( pro->misc2 );
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLDEC ){
+      dec = (AstXmlDeclPI *) this;
+      dec->text = astFree( dec->text );
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( type == AST__XMLDTD ){
+      dtd = (AstXmlDTDec *) this;
+      dtd->name = astFree( dtd->name );
+      dtd->external = astFree( dtd->external );
+      dtd->internal = astFree( dtd->internal );
+      CleanXml( this, AST__XMLOBJECT, status );
+
+   } else if( astOK ) {
+      astError( AST__INTER, "CleanXml: Invalid object type (%ld) supplied "
+                "(internal AST programming error).", status, type );
+   }
+
+}
+
+static const char *DefaultURI( AstXmlElement *elem, int *status ){
+/*
+*  Name:
+*     DefaultURI
+
+*  Purpose:
+*     Find the URI associated with the default namespace.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *DefaultURI( AstXmlElement *elem, int *status )
+
+*  Description:
+*     This function returns the default namespace URI defined within the
+*     given element. If the element does not define a default namespace URI,
+*     then this function is called recursively on the parent element. If
+*     there is no parent element, NULL is returned.
+
+*  Parameters:
+*     elem
+*        The pointer to the XmlElement.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a string holding the URI, or NULL if not found.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstXmlParent *parent;        /* Parent of "this" */
+   const char *result;          /* Returned pointer */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status, and the supplied element. */
+   if( !astOK || !elem ) return result;
+
+/* If the supplied element defines a default namespace URI, return it.
+   Otherwise, call this function to get the default namespace URI from the
+   parent element. */
+   result = elem->defns;
+   if( !result ) {
+      parent = ( (AstXmlObject *) elem )->parent;
+      if( astXmlCheckType( parent, AST__XMLELEM ) ) {
+         result = DefaultURI( (AstXmlElement *) parent, status );
+      }
+   }
+
+/* If the element has a blank default namespace URI, then return NULL
+   since the XML namespaces specification says that "The default
+   namespace can be set to the empty string. This has the same effect,
+   within the scope of the declaration, of there being no default
+   namespace". */
+   if( result && astChrLen( result ) == 0 ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+void *astXmlDelete_( void *obj_ptr, int *status ){
+/*
+*+
+*  Name:
+*     astXmlDelete
+
+*  Purpose:
+*     Remove the supplied XmlObject from its parent and delete it.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void *astXmlDelete( void *obj )
+
+*  Description:
+*     This function removes the supplied XmlObject from its parent and
+*     deletes it using astXmlAnnul.
+
+*  Parameters:
+*     obj
+*        The pointer to the XmlObject to be deleted.
+
+*  Returned Value:
+*     NULL
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlDocument *doc;         /* Pointer to XM document */
+   AstXmlElement *elem;         /* Pointer to XML element */
+   AstXmlObject *obj;           /* Pointer to XmlObject */
+   AstXmlParent *parent;        /* Pointer to parent */
+   AstXmlPrologue *pro;         /* Pointer to XML prologue */
+   int i;                       /* Loop counter */
+   int j;                       /* Loop counter */
+   int n;                       /* Number of values in list */
+   int ok;                      /* Is obj a child of its parent? */
+   void *result;                /* Returned pointer */
+
+/* Initialise */
+   result = NULL;
+   ok = 0;
+
+/* Check we have an XmlObject. */
+   if( !astXmlCheckType( obj_ptr, AST__XMLOBJECT ) ) return result;
+
+/* Get the parent of the supplied object. */
+   obj = (AstXmlObject *) obj_ptr;
+   parent = obj->parent;
+   if( parent ) {
+
+/* First deal with cases where we are deleting items from a document. */
+      if( astXmlCheckType( parent, AST__XMLDOC ) ) {
+         doc = (AstXmlDocument *) parent;
+
+         if( astXmlCheckType( obj, AST__XMLPRO ) ) {
+            if( (AstXmlPrologue *) obj == doc->prolog ) {
+               doc->prolog = NULL;
+               ok = 1;
+            }
+
+         } else if( astXmlCheckType( obj, AST__XMLELEM ) ) {
+            if( (AstXmlElement *) obj == doc->root ) {
+               doc->root = NULL;
+               ok = 1;
+            }
+
+         } else if( astXmlCheckType( obj, AST__XMLMISC ) ) {
+            n = doc->nepi;
+            for( i = 0; i < n; i++ ) {
+               if( doc->epilog[ i ] == (AstXmlMiscItem *) obj ) {
+                  for( j = i + 1; j < n; j++ ) {
+                     doc->epilog[ j - 1 ] =  doc->epilog[ j ];
+                  }
+                  doc->epilog[ --doc->nepi ] = NULL;
+                  ok = 1;
+                  break;
+               }
+            }
+
+         } else if( astOK ) {
+            astError( AST__INTER, "astXmlDelete(xml): XmlObject of type %ld has "
+                      "inappropriate parent of type %ld (internal AST "
+                      "programming error).", status, obj->type, parent->type );
+         }
+
+/* Now deal with cases where we are deleting items from a prologue. */
+      } else if( astXmlCheckType( parent, AST__XMLPRO ) ) {
+         pro = (AstXmlPrologue *) parent;
+
+         if( astXmlCheckType( obj, AST__XMLDEC ) ) {
+            if( (AstXmlDeclPI *) obj == pro->xmldecl ) {
+               pro->xmldecl = NULL;
+               ok = 1;
+            }
+
+         } else if( astXmlCheckType( obj, AST__XMLDTD ) ) {
+            if( (AstXmlDTDec *) obj == pro->dtdec ) {
+               pro->dtdec = NULL;
+               ok = 1;
+            }
+
+         } else if( astXmlCheckType( obj, AST__XMLMISC ) ) {
+            n = pro->nmisc1;
+            for( i = 0; i < n; i++ ) {
+               if( pro->misc1[ i ] == (AstXmlMiscItem *) obj ) {
+                  for( j = i + 1; j < n; j++ ) {
+                     pro->misc1[ j - 1 ] =  pro->misc1[ j ];
+                  }
+                  pro->misc1[ --pro->nmisc1 ] = NULL;
+                  ok = 1;
+                  break;
+               }
+            }
+
+            if( !ok ) {
+               n = pro->nmisc2;
+               for( i = 0; i < n; i++ ) {
+                  if( pro->misc2[ i ] == (AstXmlMiscItem *) obj ) {
+                     for( j = i + 1; j < n; j++ ) {
+                        pro->misc2[ j - 1 ] =  pro->misc2[ j ];
+                     }
+                     pro->misc2[ --pro->nmisc2 ] = NULL;
+                     ok = 1;
+                     break;
+                  }
+               }
+            }
+
+         } else if( astOK ) {
+            astError( AST__INTER, "astXmlDelete(xml): XmlObject of type %ld has "
+                      "inappropriate parent of type %ld (internal AST "
+                      "programming error).", status, obj->type, parent->type );
+         }
+
+/* Now deal with cases where we are deleting items from an element. */
+      } else if( astXmlCheckType( parent, AST__XMLELEM ) ) {
+         elem = (AstXmlElement *) parent;
+
+/* Remove the object form the appropriate list in the parent, and
+   then shuffle down the remaining entries in the list and decrement the
+   size of the list. */
+         if( astXmlCheckType( obj, AST__XMLATTR ) ) {
+            n = elem->nattr;
+            for( i = 0; i < n; i++ ) {
+               if( elem->attrs[ i ] == (AstXmlAttribute *) obj ) {
+                  for( j = i + 1; j < n; j++ ) {
+                     elem->attrs[ j - 1 ] =  elem->attrs[ j ];
+                  }
+                  elem->attrs[ --elem->nattr ] = NULL;
+                  ok = 1;
+                  break;
+               }
+            }
+
+         } else if( astXmlCheckType( obj, AST__XMLNAME ) ) {
+            n = elem->nnspref;
+            for( i = 0; i < n; i++ ) {
+               if( elem->nsprefs[ i ] == (AstXmlNamespace *) obj ) {
+                  for( j = i + 1; j < n; j++ ) {
+                     elem->nsprefs[ j - 1 ] =  elem->nsprefs[ j ];
+                  }
+                  elem->nsprefs[ --elem->nnspref ] = NULL;
+                  ok = 1;
+                  break;
+               }
+            }
+
+         } else if( astXmlCheckType( obj, AST__XMLCONT ) ) {
+            n = elem->nitem;
+            for( i = 0; i < n; i++ ) {
+               if( elem->items[ i ] == (AstXmlContentItem *) obj ) {
+                  for( j = i + 1; j < n; j++ ) {
+                     elem->items[ j - 1 ] =  elem->items[ j ];
+                  }
+                  elem->items[ --elem->nitem ] = NULL;
+                  ok = 1;
+                  break;
+               }
+            }
+         }
+
+      } else if( astOK ) {
+         astError( AST__INTER, "astXmlDelete(xml): XmlObject of type %ld has "
+                   "inappropriate parent of type %ld (internal AST "
+                   "programming error).", status, obj->type, parent->type );
+      }
+
+/* Nullify the parent pointer so that astXmlAnnul will delete the object. */
+      obj->parent = NULL;
+
+/* If the supplied object has no parent, we can continue to annul it. */
+   } else {
+      ok = 1;
+   }
+
+/* Report an error if required. */
+   if( !ok && astOK ) {
+      astError( AST__INTER, "astXmlDelete(xml): Supplied XmlObject (type %ld) "
+                "is not owned by its own parent (internal AST "
+                "programming error).", status, obj->type );
+   }
+
+/* Delete the object. */
+   result = astXmlAnnul( obj );
+
+/* Annul the object and return the resulting NULL pointer. */
+   return result;
+}
+
+static AstXmlAttribute *FindAttribute( AstXmlElement *this, const char *name0,
+                                       int *status ){
+/*
+*  Name:
+*     FindAttribute
+
+*  Purpose:
+*     Search an XmlElement for a named attribute
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlAttribute *FindAttribute( AstXmlElement *this, const char *name0,
+*                                     int *status )
+
+*  Description:
+*     This function searches the supplied XmlElement for an attribute
+*     with the given name. If found, a pointer to the XmlAttribute is
+*     returned. Otherwise NULL is returned.
+
+*  Parameters:
+*     this
+*        The pointer to the XmlElement.
+*     name0
+*        Pointer to a string holding the name of the attribute. The name
+*        may be preceded with a "prefix:" string, in which case the
+*        prefix will also be matched. If no prefix is included, the first
+*        attribute with the specified name is returned, regardless of
+*        its prefix.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the XmlAttribute, or NULL if not found.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstXmlAttribute *result;     /* Returned pointer */
+   char name_buffer[ 50 ];      /* Buffer for name */
+   char prefix_buffer[ 50 ];    /* Buffer for prefix */
+   const char *colon;           /* Pointer to colon in supplied string */
+   const char *name1;           /* Pointer to name to be checked */
+   const char *name;            /* Pointer to name to be searched for */
+   const char *prefix1;         /* Pointer to prefix to be checked */
+   const char *prefix;          /* Pointer to prefix to be searched for */
+   int i;                       /* Loop count */
+   size_t len;                  /* Length of string */
+
+/* Initialise */
+   result = NULL;
+   name = name0;
+   prefix = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* If the supplied name string contains a colon, split it up into prefix
+   and name. */
+   if( ( colon = strchr( name0, ':' ) ) ) {
+      len = colon - name0;
+
+      if( len > 49 ) {
+         astError( AST__XMLNM, "FindAttribute: The XML prefix in \"%s\" "
+                   "is too long (> 49 characters).", status, name0 );
+      } else {
+         strncpy( prefix_buffer, name0, len );
+         prefix_buffer[ len ] = 0;
+         prefix = prefix_buffer;
+         len = strlen( colon + 1 );
+
+         if( len > 49 ) {
+            astError( AST__XMLNM, "FindAttribute: The XML attribute name "
+                      "in \"%s\" is too long (> 49 characters).", status, name0 );
+         } else {
+            strcpy( name_buffer, colon + 1 );
+            name = name_buffer;
+         }
+
+      }
+
+   }
+
+/* Loop round all the attributes in the element. */
+   for( i = 0; i < this->nattr; i++ ) {
+      name1 = this->attrs[ i ]->name;
+      prefix1 = this->attrs[ i ]->prefix;
+
+/* Compare the attribute name (and prefix) with the supplied name (and
+   prefix). Leave the loop if they match. */
+      if( !strcmp( name1, name ) &&
+          ( !prefix || ( prefix1 && !strcmp( prefix1, prefix ) ) ) ) {
+         result = this->attrs[ i ];
+         break;
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static const char *Format( AstXmlObject *this, int ind, int *status ){
+/*
+*  Name:
+*     Format
+
+*  Purpose:
+*     Converts an XmlObject into a character string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *Format( AstXmlObject *this, int ind, int *status )
+
+*  Description:
+*     This function returns a pointer to a dynamically allocated string
+*     containing a textual representation of the supplied XmlObject.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlObject to format.
+*     ind
+*        If the XmlObject is an element, then each content item within
+*        the element will be prefixed by a string containing "ind" spaces
+*        (indenting the returned element itself is the responsibility of
+*        the caller and so "this" is not itself indented within this function).
+*        In addition, a newline character will be included at the start
+*        of the prefix if required, to ensure that each new item starts
+*        on a new line. If "ind" is less than zero, then no prefixes are
+*        added.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a null terminated string holding the formated XmlObject.
+*     This string should be freed when no longer needed using astFree.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstXmlPrologue *pro;      /* Pointer to XML prologue */
+   AstXmlDocument *doc;      /* Pointer to XML document */
+   AstXmlAttribute *attrib;  /* Pointer to XML attribute */
+   AstXmlWhite *white;       /* Pointer to character data */
+   AstXmlBlack *black;       /* Pointer to character data */
+   AstXmlElement *elem;      /* Pointer to XML element */
+   AstXmlNamespace *ns;      /* Pointer to XML namespace instruction */
+   char *result;             /* The returned pointer */
+   const char *temp;         /* A temporary string pointer */
+   int i;                    /* Loop count */
+   int nc;                   /* Length of returned string */
+   int type;                 /* Object type */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK || !this ) return result;
+
+/* Get the object type */
+   type = this->type;
+
+/* If this is an element... */
+   if( this->type == AST__XMLELEM ) {
+      temp = FormatTag( this, 1, status );
+      result = astAppendString( result, &nc, temp );
+      temp = astFree( (void *) temp );
+
+      elem = (AstXmlElement *) this;
+      if( elem->nitem > 0 ) {
+
+/* Go round all the items of content. */
+         for( i = 0; i < elem->nitem; i++ ) {
+
+/* Ignore whitespace elements unless we are not producing indentation. */
+            if( !astXmlCheckType( elem->items[ i ], AST__XMLWHITE ) ||
+                ind < 0 ) {
+
+/* Format the item */
+               temp = Format( (AstXmlObject *) elem->items[ i ], ( ( ind > -1 ) ? ind + IND_INC : -1 ), status );
+               if( temp ) {
+
+/* Now append the next item of content, and free its memory. */
+                  if( ind > -1 ) {
+                     result = AppendLine( result, &nc, temp,
+                                          ( (ind > -1) ? ind + IND_INC : -1 ), status );
+                  } else {
+                     result = astAppendString( result, &nc, temp );
+                  }
+                  temp = astFree( (void *) temp );
+               }
+            }
+         }
+
+/* Finally append the end tag. */
+         temp = FormatTag( this, 0, status );
+         if( ind > -1 ) {
+            result = AppendLine( result, &nc, temp, ind, status );
+         } else {
+            result = astAppendString( result, &nc, temp );
+         }
+         temp = astFree( (void *) temp );
+
+      }
+
+/* If this is an attribute... */
+   } else if( type == AST__XMLATTR ){
+      attrib = (AstXmlAttribute *) this;
+
+      if( attrib->prefix ) {
+         result = astAppendString( result, &nc, attrib->prefix );
+         result = astAppendString( result, &nc, ":" );
+      }
+
+      temp = AddEscapes( attrib->value, status );
+      result = astAppendString( result, &nc, attrib->name );
+      result = astAppendString( result, &nc, "=\"" );
+      result = astAppendString( result, &nc, temp );
+      result = astAppendString( result, &nc, "\"" );
+      temp = astFree( (void *) temp );
+
+   } else if( type == AST__XMLWHITE ){
+      white = (AstXmlWhite *) this;
+      temp = AddEscapes( white->text, status );
+      result = astAppendString( result, &nc, temp );
+      temp = astFree( (void *) temp );
+
+   } else if( type == AST__XMLBLACK ){
+      black = (AstXmlBlack *) this;
+      temp = AddEscapes( black->text, status );
+      result = astAppendString( result, &nc, temp );
+      temp = astFree( (void *) temp );
+
+   } else if( type == AST__XMLCDATA ||
+              type == AST__XMLCOM ||
+              type == AST__XMLPI ||
+              type == AST__XMLDEC ||
+              type == AST__XMLDTD ){
+
+      temp = FormatTag( this, 1, status );
+      result = astAppendString( result, &nc, temp );
+      temp = astFree( (void *) temp );
+
+   } else if( type == AST__XMLNAME ){
+      ns = (AstXmlNamespace *) this;
+      result = astAppendString( result, &nc, "xmlns:" );
+      result = astAppendString( result, &nc, ns->prefix );
+      result = astAppendString( result, &nc, "=\"" );
+      result = astAppendString( result, &nc, ns->uri );
+      result = astAppendString( result, &nc, "\"" );
+
+   } else if( type == AST__XMLPRO ){
+      pro = (AstXmlPrologue *) this;
+      result = astAppendString( result, &nc,
+                             Format( (AstXmlObject *) pro->xmldecl, ind, status ) );
+
+/* Append all the miscalleneous items before the DTD. */
+      for( i = 0; i < pro->nmisc1; i++ ) {
+         temp = Format( (AstXmlObject *) pro->misc1[ i ], ind, status );
+         if( temp ) {
+            if( ind > -1 ) {
+               result = AppendLine( result, &nc, temp, ind, status );
+            } else {
+               result = astAppendString( result, &nc, temp );
+            }
+            temp = astFree( (void *) temp );
+         }
+      }
+
+/* Append the DTD. */
+      temp = Format( (AstXmlObject *) pro->dtdec, ind, status );
+      if( temp ) {
+         if( ind > -1 ) {
+            result = AppendLine( result, &nc, temp, ind, status );
+         } else {
+            result = astAppendString( result, &nc, temp );
+         }
+         temp = astFree( (void *) temp );
+      }
+
+/* Append all the miscalleneous items after the DTD. */
+      for( i = 0; i < pro->nmisc2; i++ ) {
+         temp = Format( (AstXmlObject *) pro->misc2[ i ], ind, status );
+         if( temp ) {
+            if( ind > -1 ) {
+               result = AppendLine( result, &nc, temp, ind, status );
+            } else {
+               result = astAppendString( result, &nc, temp );
+            }
+            temp = astFree( (void *) temp );
+         }
+      }
+
+   } else if( type == AST__XMLDOC ){
+      doc = (AstXmlDocument *) this;
+
+/* Format the prologue. */
+      result = astAppendString( result, &nc,
+                             Format( (AstXmlObject *) doc->prolog, ind, status ) );
+
+/* Append the root element. */
+      temp = Format( (AstXmlObject *) doc->root, ind, status );
+      if( temp ) {
+         if( ind > -1 ) {
+            result = AppendLine( result, &nc, temp, ind, status );
+         } else {
+            result = astAppendString( result, &nc, temp );
+         }
+         temp = astFree( (void *) temp );
+      }
+
+/* Append all the miscalleneous items in the epilogue. */
+      for( i = 0; i < doc->nepi; i++ ) {
+         temp = Format( (AstXmlObject *) doc->epilog[ i ], ind, status );
+         if( temp ) {
+            if( ind > -1 ) {
+               result = AppendLine( result, &nc, temp, ind, status );
+            } else {
+               result = astAppendString( result, &nc, temp );
+            }
+            temp = astFree( (void *) temp );
+         }
+      }
+
+   } else if( astOK ) {
+      astError( AST__INTER, "Format(xml): Invalid object type (%d) supplied "
+                "(internal AST programming error).", status, type );
+   }
+
+/* Free the returned string if an error has occurred. */
+   if( !astOK ) result = astFree( result );
+
+/* Return the result. */
+   return result;
+}
+
+static char *FormatTag( AstXmlObject *this, int opening, int *status ){
+/*
+*  Name:
+*     FormatTag
+
+*  Purpose:
+*     Returns a string holding an XML tag describing the given XmlObject.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     char *FormatTag( AstXmlObject *this, int opening, int *status )
+
+*  Description:
+*     This function returns a pointer to a dynamic string containing an
+*     XML tag describing the given XmlObject.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlObject.
+*     opening
+*        Indicates which tag is to be returned; the start tag or the end
+*        tag. If non-zero the start tag is returned. Otherwise, the
+*        end tag is returned. If the supplied XmlObject has no end
+*        tag (i.e. if it is an empty element, or if it is not an element),
+*        then NULL is returned but no error is reported.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a dynamically allocated string holding the tag.
+
+*  Notes:
+*     - Empty elements are represented as an start tag of the form <.../>,
+*     with no corresponding end tag.
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+
+/* Local Variables: */
+   AstXmlCDataSection *cdata;/* Pointer to XML CDATA section */
+   AstXmlElement *elem;      /* Pointer to XML element */
+   AstXmlComment *com;       /* Pointer to XML comment */
+   AstXmlPI *pi;             /* Pointer to XML processing instruction */
+   AstXmlDTDec *dtd;         /* Pointer to XML data type declaration */
+   AstXmlDeclPI *xmlpi;      /* XML version declaration */
+   char *result;             /* The returned pointer */
+   const char *temp;         /* A temporary string pointer */
+   int i;                    /* Loop count */
+   int nc;                   /* Length of returned string */
+   int type;                 /* Object type */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* Get the object type */
+   type = this->type;
+
+/* If this is an element... */
+   if( this->type == AST__XMLELEM ) {
+      elem = (AstXmlElement *) this;
+
+      if( opening ) {
+         result = astAppendString( result, &nc, "<" );
+         if( elem->prefix ) {
+            result = astAppendString( result, &nc, elem->prefix );
+            result = astAppendString( result, &nc, ":" );
+         }
+         result = astAppendString( result, &nc, elem->name );
+
+         if( elem->defns ) {
+            result = astAppendString( result, &nc, " xmlns=\"" );
+            result = astAppendString( result, &nc, elem->defns );
+            result = astAppendString( result, &nc, "\"" );
+         }
+
+         for( i = 0; i < elem->nnspref; i++ ) {
+            temp = Format( (AstXmlObject *) elem->nsprefs[ i ], -1, status );
+            if( temp ) {
+               result = AppendChar( result, &nc, ' ', status );
+               result = astAppendString( result, &nc, temp );
+               temp = astFree( (void *) temp );
+            }
+         }
+
+         for( i = 0; i < elem->nattr; i++ ) {
+            temp = Format( (AstXmlObject *) elem->attrs[ i ], -1, status );
+            if( temp ){
+               result = AppendChar( result, &nc, ' ', status );
+               result = astAppendString( result, &nc, temp );
+               temp = astFree( (void *) temp );
+            }
+         }
+
+         if( elem->nitem == 0 ) result = astAppendString( result, &nc, "/" );
+         result = astAppendString( result, &nc, ">" );
+
+      } else if( elem->nitem > 0 ) {
+         result = astAppendString( result, &nc, "</" );
+         if( elem->prefix ) {
+            result = astAppendString( result, &nc, elem->prefix );
+            result = astAppendString( result, &nc, ":" );
+         }
+         result = astAppendString( result, &nc, elem->name );
+         result = astAppendString( result, &nc, ">" );
+      }
+
+   } else if( type == AST__XMLDTD ){
+      dtd = (AstXmlDTDec *) this;
+      if( opening && dtd->name && dtd->name[0] ) {
+         result = astAppendString( result, &nc, "<!DOCTYPE " );
+         result = astAppendString( result, &nc, dtd->name );
+         if( dtd->external && dtd->external[ 0 ] ) {
+            result = astAppendString( result, &nc, " " );
+            result = astAppendString( result, &nc, dtd->external );
+         }
+         if( dtd->internal && dtd->internal[ 0 ] ) {
+            result = astAppendString( result, &nc, " [" );
+            result = astAppendString( result, &nc, dtd->internal );
+            result = astAppendString( result, &nc, "]" );
+         }
+         result = astAppendString( result, &nc, ">" );
+      }
+
+   } else if( type == AST__XMLCDATA ){
+      if( opening ) {
+         cdata = (AstXmlCDataSection *) this;
+         result = astAppendString( result, &nc, "<![CDATA[" );
+         result = astAppendString( result, &nc, cdata->text );
+         result = astAppendString( result, &nc, "]]>" );
+      }
+
+   } else if( type == AST__XMLCOM ){
+      if( opening ) {
+         com = (AstXmlComment *) this;
+         result = astAppendString( result, &nc, "<!--" );
+         result = astAppendString( result, &nc, com->text );
+         result = astAppendString( result, &nc, "-->" );
+      }
+
+   } else if( type == AST__XMLPI ){
+      pi = (AstXmlPI *) this;
+      if( opening ) {
+         result = astAppendString( result, &nc, "<?" );
+         result = astAppendString( result, &nc, pi->target );
+         if( pi->text && pi->text[0] ) {
+            result = astAppendString( result, &nc, " " );
+            result = astAppendString( result, &nc, pi->text );
+         }
+         result = astAppendString( result, &nc, "?>" );
+      }
+
+   } else if( type == AST__XMLDEC ){
+      xmlpi = (AstXmlDeclPI *) this;
+      if( opening && xmlpi->text && xmlpi->text[0] ) {
+         result = astAppendString( result, &nc, "<?xml" );
+         if( xmlpi->text && xmlpi->text[0] ) {
+            result = astAppendString( result, &nc, " " );
+            result = astAppendString( result, &nc, xmlpi->text );
+         }
+         result = astAppendString( result, &nc, "?>" );
+      }
+   }
+
+/* If notOK, free the rteurned string. */
+   if( !astOK ) result = astFree( result );
+
+/* Return the result. */
+   return result;
+}
+
+static void InitXmlAttribute( AstXmlAttribute *new, int type, const char *name,
+                              const char *value, const char *prefix, int *status ){
+/*
+*  Name:
+*     InitXmlAttribute
+
+*  Purpose:
+*     Initialise a new XmlAttribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlAttribute( AstXmlAttribute *new, int type, const char *name,
+*                       const char *value, const char *prefix, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlAttribute
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     name
+*        The name for the attribute.
+*     value
+*        The value for the attribute
+*     prefix
+*        The namespace prefix for the attribute. May be NULL or blank, in
+*        which case any prefix at the start of "name" is used.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   const char *colon;         /* Pointer to colon within supplied name */
+   char *newname;             /* Pointer to name string (no prefix) */
+   char *newpref;             /* Pointer to name string */
+   int nc;                    /* Length of prefix string */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLATTR, status ) ){
+      astError( AST__INTER, "InitXmlAttribute: Supplied object type (%d) "
+                "does not represent an XmlAttribute", status, type );
+   }
+
+/* Ensure we have non-NULL pointers. */
+   if( !name ) name = "";
+   if( !value ) value = "";
+
+/* If no prefix was supplied, extract any prefix from the start of the
+   supplied name. */
+   newname = (char *) name;
+   newpref = (char *) prefix;
+   colon = NULL;
+
+   if( !prefix || astChrLen( prefix ) == 0 ){
+      colon = strchr( name, ':' );
+      if( colon ) {
+         nc = colon - name;
+         newpref = astStore( NULL, name, nc + 1 );
+         newpref[ nc ] = 0;
+
+         nc = strlen( name ) - ( colon - name ) - 1;
+         newname = astStore( NULL, colon + 1, nc + 1 );
+         newname[ nc ] = 0;
+      }
+   }
+
+/* Check the supplied name and prefix are valid XML 'names'. */
+   CheckName( newname, "attribute", "InitXmlAttribute", 0, status );
+   CheckName( newpref, "attribute", "InitXmlAttribute", 1, status );
+
+/* Initialise the parent XmlObject component. */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Initialise the items specific to this class of structure. */
+   new->name = astStore( NULL, newname, strlen( newname ) + 1 );
+   new->value = astStore( NULL, value, strlen( value ) + 1 );
+   new->prefix = NULL;
+   if( newpref ) {
+      nc = strlen( newpref );
+      if( nc > 0 ) new->prefix = astStore( NULL, newpref, nc + 1 );
+   }
+
+/* Free any name and prefix extracted from the supplied name string */
+   if( colon ) {
+      newname = astFree( newname );
+      newpref = astFree( newpref );
+   }
+}
+
+static void InitXmlCDataSection( AstXmlCDataSection *new, int type,
+                                 const char *text, int *status ){
+/*
+*  Name:
+*     InitXmlCDataSection
+
+*  Purpose:
+*     Initialise a new XmlCDataSection.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlCDataSection( AstXmlCDataSection *new, int type,
+*                          const char *text, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlCDataSection
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     text
+*        Pointer to a null terminated string holding the text.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLCDATA, status ) ){
+      astError( AST__INTER, "InitXmlCDataSection: Supplied object type (%d) "
+                "does not represent an XmlCDataSection", status, type );
+   }
+
+/* Initialise the parent XmlObject component. */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Ensure we have non-NULL pointers. */
+   if( !text ) text = "";
+
+/* Initialise the items specific to this class of structure. */
+   new->text = astStore( NULL, text, strlen( text ) + 1 );
+}
+
+static void InitXmlWhite( AstXmlWhite *new, int type, const char *text, int *status ){
+/*
+*  Name:
+*     InitXmlWhite
+
+*  Purpose:
+*     Initialise a new XmlWhite.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlWhite( AstXmlWhite *new, int type, const char *text, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlWhite
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     text
+*        Pointer to a null terminated string holding the text.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables:  */
+   const char *c;          /* Pointer to next character */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLWHITE, status ) ){
+      astError( AST__INTER, "InitXmlWhite: Supplied object type (%d) "
+                "does not represent an XmlWhite", status, type );
+   }
+
+/* Initialise the parent XmlObject component. */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Ensure we have non-NULL pointers. */
+   if( !text ) text = "";
+
+/* Report an error if the text is not white. */
+   c = text - 1;
+   while( *(++c) ) {
+      if( !isspace( *c ) ) {
+         astError( AST__XMLCM, "InitXmlWhite(xml): Illegal XML whitespace "
+                   "string supplied \"%s\" - not all characters are white.", status,
+                    text );
+         break;
+      }
+   }
+
+/* Initialise the items specific to this class of structure. */
+   new->text = astStore( NULL, text, strlen( text ) + 1 );
+}
+
+static void InitXmlBlack( AstXmlBlack *new, int type, const char *text, int *status ){
+/*
+*  Name:
+*     InitXmlBlack
+
+*  Purpose:
+*     Initialise a new XmlBlack.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlBlack( AstXmlBlack *new, int type, const char *text, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlBlack
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     text
+*        Pointer to a null terminated string holding the text.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLBLACK, status ) ){
+      astError( AST__INTER, "InitXmlBlack: Supplied object type (%d) "
+                "does not represent an XmlBlack", status, type );
+   }
+
+/* Initialise the parent XmlObject component. */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Ensure we have non-NULL pointers. */
+   if( !text ) text = "";
+
+/* Initialise the items specific to this class of structure. */
+   new->text = astStore( NULL, text, strlen( text ) + 1 );
+}
+
+static void InitXmlComment( AstXmlComment *new, int type, const char *text, int *status ){
+/*
+*  Name:
+*     InitXmlComment
+
+*  Purpose:
+*     Initialise a new XmlComment.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlComment( AstXmlComment *new, int type, const char *text, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlComment
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     text
+*        Pointer to a null terminated string holding the text.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLCOM, status ) ){
+      astError( AST__INTER, "InitXmlComment: Supplied object type (%d) "
+                "does not represent an XmlComment", status, type );
+   }
+
+/* Initialise the parent XmlObject component. */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Ensure we have non-NULL pointers. */
+   if( !text ) text = "";
+
+/* Initialise the items specific to this class of structure. Report an error
+   if the comment is illegal. */
+   if( strstr( text, "--" ) && astOK ) {
+      astError( AST__XMLCM, "InitXmlCom(xml): Illegal XML comment "
+                "supplied \"%s\" - comments may not contain the "
+                "string \"--\".", status, text );
+      new->text = NULL;
+   } else {
+      new->text = astStore( NULL, text, strlen( text ) + 1 );
+   }
+}
+
+static void InitXmlDeclPI( AstXmlDeclPI *new, int type, const char *text, int *status ){
+/*
+*  Name:
+*     InitXmlDeclPI
+
+*  Purpose:
+*     Initialise a new XmlDeclPI.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlDeclPI( AstXmlDeclPI *new, int type, const char *text, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlDeclPI
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     text
+*        Pointer to a null terminated string holding the text.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLDEC, status ) ){
+      astError( AST__INTER, "InitXmlDeclPI: Supplied object type (%d) "
+                "does not represent an XmlDeclPI", status, type );
+   }
+
+/* Initialise the parent XmlObject component. */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Ensure we have non-NULL pointers. */
+   if( !text ) text = "";
+
+/* Initialise the items specific to this class of structure. */
+   new->text = astStore( NULL, text, strlen( text ) + 1 );
+}
+
+static void InitXmlDocument( AstXmlDocument *new, int type, int *status ){
+/*
+*  Name:
+*     InitXmlDocument
+
+*  Purpose:
+*     Initialise a new XmlDocument.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlDocument( AstXmlDocument *new, int type, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlDocument
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLDOC, status ) ){
+      astError( AST__INTER, "InitXmlDocument: Supplied object type (%d) "
+                "does not represent an XmlDocument", status, type );
+   }
+
+/* Initialise the parent XmlObject */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Initialise the items specific to this class of structure. */
+   new->prolog = NULL;
+   new->root = NULL;
+   new->epilog = NULL;
+   new->nepi = 0;
+   new->current = NULL;
+}
+
+static void InitXmlDTDec( AstXmlDTDec *new, int type, const char *name,
+                          const char *external, const char *internal, int *status ){
+/*
+*  Name:
+*     InitXmlDTDec
+
+*  Purpose:
+*     Initialise a new XmlDTDec.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void InitXmlDTDec( AstXmlDTDec *new, int type, const char *name,
+*                        const char *external, const char *internal, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlDTDec
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     name
+*        The document type name
+*     external
+*        The external SYSTEM id.
+*     internal
+*        The internal declaration markup text (this is not checked or
+*        parsed).
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLDTD, status ) ){
+      astError( AST__INTER, "InitXmlDTDec: Supplied object type (%d) "
+                "does not represent an XmlDTDec", status, type );
+   }
+
+/* Initialise the parent XmlObject */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Ensure we have non-NULL pointers. */
+   if( !name ) name = "";
+   if( !external ) external = "";
+   if( !internal ) internal = "";
+
+/* Initialise the items specific to this class of structure. */
+   new->name = astStore( NULL, name, strlen( name ) + 1 );
+   new->external = astStore( NULL, external, strlen( external ) + 1 );
+   new->internal = astStore( NULL, internal, strlen( internal ) + 1 );
+}
+
+static void InitXmlElement( AstXmlElement *new, int type, const char *name,
+                            const char *prefix, int *status ){
+/*
+*  Name:
+*     InitXmlElement
+
+*  Purpose:
+*     Initialise a new XmlElement.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlElement( AstXmlElement *new, int type, const char *name,
+*                     const char *prefix, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlElement
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     name
+*        The name for the element.
+*     prefix
+*        The namespace prefix for the element. May be NULL or blank, in
+*        which case any prefix at the start of "name" is used.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   const char *colon;         /* Pointer to colon within supplied name */
+   char *newname;             /* Pointer to name string (no prefix) */
+   char *newpref;             /* Pointer to name string */
+   int nc;                    /* Length of prefix string */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLELEM, status ) ){
+      astError( AST__INTER, "InitXmlElement: Supplied object type (%d) "
+                "does not represent an XmlElement", status, type );
+   }
+
+/* Ensure we have non-NULL pointers. */
+   if( !name ) name = "";
+
+/* If no prefix was supplied, extract any prefix from the start of the
+   supplied name. */
+   newname = (char *) name;
+   newpref = (char *) prefix;
+   colon = NULL;
+
+   if( !prefix || astChrLen( prefix ) == 0 ){
+      colon = strchr( name, ':' );
+      if( colon ) {
+         nc = colon - name;
+         newpref = astStore( NULL, name, nc + 1 );
+         newpref[ nc ] = 0;
+
+         nc = strlen( name ) - ( colon - name ) - 1;
+         newname = astStore( NULL, colon + 1, nc + 1 );
+         newname[ nc ] = 0;
+      }
+   }
+
+/* Check the supplied name and prefix are valid XML 'names'. */
+   CheckName( newname, "element", "InitXmlElement", 0, status );
+   CheckName( newpref, "element", "InitXmlElement", 1, status );
+
+/* Initialise the parent XmlObject component. */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Initialise the items specific to this class of structure. */
+   new->name = astStore( NULL, newname, strlen( newname ) + 1 );
+   new->attrs = NULL;
+   new->nattr = 0;
+   new->items = NULL;
+   new->nitem = 0;
+   new->defns = NULL;
+   new->nsprefs = NULL;
+   new->nnspref = 0;
+   new->complete = 0;
+
+   new->prefix = NULL;
+   if( newpref ) {
+      nc = strlen( newpref );
+      if( nc > 0 ) new->prefix = astStore( NULL, newpref, nc + 1 );
+   }
+
+/* Free any name and prefix extracted from the supplied name string */
+   if( colon ) {
+      newname = astFree( newname );
+      newpref = astFree( newpref );
+   }
+}
+
+static void InitXmlNamespace( AstXmlNamespace *new, int type, const char *prefix,
+                              const char *uri, int *status ){
+/*
+*  Name:
+*     InitXmlNamespace
+
+*  Purpose:
+*     Initialise a new XmlNamespace.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlNamespace( AstXmlNamespace *new, int type, const char *prefix,
+*                       const char *uri, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlNamespace
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     prefix
+*        Pointer to a null terminated string holding the namespace prefix.
+*     uri
+*        Pointer to a null terminated string holding the namespace URI.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLNAME, status ) ){
+      astError( AST__INTER, "InitXmlNamespace: Supplied object type (%d) "
+                "does not represent an XmlNamespace", status, type );
+   }
+
+/* Ensure we have non-NULL pointers. */
+   if( !prefix ) prefix = "";
+   if( !uri ) uri = "";
+
+/* Check the supplied prefix is a valid XML 'name'. */
+   CheckName( prefix, "namespace prefix", "InitXmlNamespace", 0, status );
+
+/* Initialise the parent XmlObject component. */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Initialise the items specific to this class of structure. */
+   new->prefix = astStore( NULL, prefix, strlen( prefix ) + 1 );
+   new->uri = astStore( NULL, uri, strlen( uri ) + 1 );
+}
+
+static void InitXmlObject( AstXmlObject *new, long int type, int *status ){
+/*
+*  Name:
+*     InitXmlObject
+
+*  Purpose:
+*     Initialise a new XmlObject.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlObject( AstXmlObject *new, long int type, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlObject
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS        /* Pointer to thread-specific global data */
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* If needed, get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the supplied object type is OK. Report an error if not. */
+   if( !CheckType( type, AST__XMLOBJECT, status ) ){
+      astError( AST__INTER, "InitXmlObject: Supplied object type (%ld) "
+                "is not appropriate for an XmlObject", status, type );
+   }
+
+/* This class of structure is the base class for XML objects so it has no
+   parent class to be initialised. So just initialise the items specific to
+   this class of structure. */
+   new->parent = NULL;
+   new->type = type;
+   new->id = next_id++;
+
+#ifdef DEBUG
+/* Add the new XmlObject to the list of all XmlObjects. */
+   AddObjectToList( new );
+#endif
+
+}
+
+static void InitXmlPI( AstXmlPI *new, int type, const char *target,
+                       const char *text, int *status ){
+/*
+*  Name:
+*     InitXmlPI
+
+*  Purpose:
+*     Initialise a new XmlPI.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlPI( AstXmlPI *new, int type, const char *target,
+*                const char *text, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlPI
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     target
+*        Pointer to a null terminated string holding the PI target.
+*     text
+*        Pointer to a null terminated string holding the PI text.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLPI, status ) ){
+      astError( AST__INTER, "InitXmlPI: Supplied object type (%d) "
+                "does not represent an XmlPI", status, type );
+   }
+
+/* Initialise the parent XmlObject component. */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Ensure we have non-NULL pointers. */
+   if( !target ) target = "";
+   if( !text ) text = "";
+
+/* Initialise the items specific to this class of structure. Report an error
+   if anything is illegal. */
+   new->target = NULL;
+   new->text = NULL;
+
+   if( !Ustrcmp( target, "XML", status ) && astOK ) {
+      astError( AST__XMLPT, "InitXmlPI(xml): Illegal XML PI target \"%s\""
+                " supplied.", status, target );
+   } else {
+      new->target = astStore( NULL, target, strlen( target ) + 1 );
+      new->text = astStore( NULL, text, strlen( text ) + 1 );
+   }
+}
+
+static void InitXmlPrologue( AstXmlPrologue *new, int type, int *status ){
+/*
+*  Name:
+*     InitXmlPrologue
+
+*  Purpose:
+*     Initialise a new XmlPrologue.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     InitXmlPrologue( AstXmlPrologue *new, int type, int *status )
+
+*  Description:
+*     This function initialises supplied memory to hold an XmlPrologue
+*     structure.
+
+*  Parameters:
+*     new
+*        The memory in which to initialise the structure.
+*     type
+*        An identifier for the structure type.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if( !astOK ) return;
+
+/* Check the supplied object type is appropriate for the class of
+   structure being initialised. If not report an error. */
+   if( !CheckType( type, AST__XMLPRO, status ) ){
+      astError( AST__INTER, "InitXmlPrologue: Supplied object type (%d) "
+                "does not represent an XmlPrologue", status, type );
+   }
+
+/* Initialise the parent XmlObject */
+   InitXmlObject( (AstXmlObject *) new, type, status );
+
+/* Initialise the items specific to this class of structure. */
+   new->xmldecl = NULL;
+   new->misc1 = NULL;
+   new->nmisc1 = 0;
+   new->dtdec = NULL;
+   new->misc2 = NULL;
+   new->nmisc2 = 0;
+}
+
+static int MatchName( AstXmlElement *this, const char *name, int *status ){
+/*
+*  Name:
+*     MatchName
+
+*  Purpose:
+*     Check that an element has a specified name and/or prefix.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     int MatchName( AstXmlElement *this, const char *name, int *status )
+
+*  Description:
+*     This function checks that an element has a specified name and/or prefix.
+
+*  Parameters:
+*     this
+*        The XmlElement to check.
+*     name
+*        The name for the element (may include a namespace prefix).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the supplied element has the supplie dname/prefix. Zero
+*     otherwise.
+
+*/
+
+
+/* Local Variables: */
+   const char *colon;         /* Pointer to colon within supplied name */
+   char *newname;             /* Pointer to name string (no prefix) */
+   char *newpref;             /* Pointer to name string */
+   int nc;                    /* Length of prefix string */
+   int result;                /* Returned value */
+
+/* Initialise */
+   result = 0;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* Extract any prefix from the start of the supplied name. */
+   newpref = NULL;
+   newname = (char *) name;
+   colon = strchr( name, ':' );
+   if( colon ) {
+      nc = colon - name;
+      newpref = astStore( NULL, name, nc + 1 );
+      newpref[ nc ] = 0;
+
+      nc = strlen( name ) - ( colon - name ) - 1;
+      newname = astStore( NULL, colon + 1, nc + 1 );
+      newname[ nc ] = 0;
+   }
+
+/* Compare the prefix. */
+   if( newpref && this->prefix ) {
+      result = !strcmp( newpref, this->prefix );
+
+   } else if( !newpref && !this->prefix ) {
+      result = 1;
+
+   } else {
+      result = 0;
+   }
+
+/* If the prefixes matches, compare the names */
+   if( result ) {
+      if( newname && this->name ) {
+         result = !strcmp( newname, this->name );
+
+      } else if( !newname && !this->name ) {
+         result = 1;
+
+      } else {
+         result = 0;
+      }
+   }
+
+/* Free any name and prefix extracted from the supplied name string */
+   if( colon ) {
+      newname = astFree( newname );
+      newpref = astFree( newpref );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstXmlAttribute *NewAttribute( const char *name, const char *value,
+                                      const char *prefix, int *status ){
+/*
+*  Name:
+*     NewAttribute
+
+*  Purpose:
+*     Create a new XmlAttribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlAttribute *NewAttribute( const char *name, const char *value,
+*                                    const char *prefix, int *status )
+
+*  Description:
+*     This function creates a new XmlAttribute structure representing an
+*     XML attribute with the given name, value and namespace prefix.
+
+*  Parameters:
+*     name
+*        Pointer to a null terminated string containing the attribute name.
+*     value
+*        Pointer to a null terminated string containing the attribute value.
+*     prefix
+*        Pointer to a null terminated string containing the attribute
+*        namespace prefix (may be NULL or blank).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new structure is returned.
+
+*  Notes:
+*     - A NULL pointer is returned if the inherited status value
+*     indicates an error has occurred on entry, or if this function
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstXmlAttribute *new;        /* The returned pointer */
+
+/* Initialise */
+   new = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return new;
+
+/* Allocate space for the new structure. */
+   new = (AstXmlAttribute *) astMalloc( sizeof( AstXmlAttribute ) );
+
+/* Initialise it. */
+   InitXmlAttribute( new, AST__XMLATTR, name, value, prefix, status );
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) new = astXmlDelete( new );
+
+/* Return the result. */
+   return new;
+
+}
+
+static AstXmlDocument *NewDocument( int *status ){
+/*
+*  Name:
+*     NewDocument
+
+*  Purpose:
+*     Create a new empty XmlDocument.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlDocument *NewDocument( int *status )
+
+*  Description:
+*     This function creates a new empty XmlDocument structure representing
+*     an entire XML Document.
+
+*  Parameters:
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new structure is returned.
+
+*  Notes:
+*     - A NULL pointer is returned if the inherited status value
+*     indicates an error has occurred on entry, or if this function
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstXmlDocument *new;        /* The returned pointer */
+
+/* Initialise */
+   new = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return new;
+
+/* Allocate space for the new structure. */
+   new = (AstXmlDocument *) astMalloc( sizeof( AstXmlDocument ) );
+
+/* Initialise it. */
+   InitXmlDocument( new, AST__XMLDOC, status );
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) new = astXmlDelete( new );
+
+/* Return the result. */
+   return new;
+
+}
+
+static AstXmlNamespace *NewNamespace( const char *prefix, const char *uri, int *status ){
+/*
+*  Name:
+*     NewNamespace
+
+*  Purpose:
+*     Create a new XmlNamespace.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlNamespace *NewNamespace( const char *prefix,
+*                                    const char *uri, int *status )
+
+*  Description:
+*     This function creates a new XmlNamespace structure representing an
+*     XML namespace with the given prefix and uri.
+
+*  Parameters:
+*     prefix
+*        Pointer to a null terminated string containing the namespace prefix.
+*     uri
+*        Pointer to a null terminated string containing the associated URI.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new structure is returned.
+
+*  Notes:
+*     - A NULL pointer is returned if the inherited status value
+*     indicates an error has occurred on entry, or if this function
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstXmlNamespace *new;        /* The returned pointer */
+
+/* Initialise */
+   new = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return new;
+
+/* Allocate space for the new structure. */
+   new = (AstXmlNamespace *) astMalloc( sizeof( AstXmlNamespace ) );
+
+/* Initialise it. */
+   InitXmlNamespace( new, AST__XMLNAME, prefix, uri, status );
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) new = astXmlDelete( new );
+
+/* Return the result. */
+   return new;
+
+}
+
+static AstXmlPrologue *NewPrologue( AstXmlDocument *doc, int *status ){
+/*
+*  Name:
+*     NewPrologue
+
+*  Purpose:
+*     Create a new empty XmlPrologue.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlPrologue *NewPrologue( AstXmlDocument *doc, int *status )
+
+*  Description:
+*     This function creates a new empty XmlPrologue structure representing
+*     an entire prologue.
+
+*  Parameters:
+*     doc
+*        A pointer to the XmlDocument to add the XmlPrologue to, or NULL.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new structure is returned.
+
+*  Notes:
+*     - A NULL pointer is returned if the inherited status value
+*     indicates an error has occurred on entry, or if this function
+*     should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstXmlPrologue *new;        /* The returned pointer */
+
+/* Initialise */
+   new = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return new;
+
+/* Allocate space for the new structure. */
+   new = (AstXmlPrologue *) astMalloc( sizeof( AstXmlPrologue ) );
+
+/* Initialise it. */
+   InitXmlPrologue( new, AST__XMLPRO, status );
+
+/* Set its parent. */
+   ((AstXmlObject *) new )->parent = (AstXmlParent *) doc;
+
+/* If an error occurred, delete the new structure. */
+   if( !astOK ) new = astXmlDelete( new );
+
+/* Return the result. */
+   return new;
+
+}
+
+static char *RemoveEscapes( const char *text, int *status ){
+/*
+*  Name:
+*     RemoveEscapes
+
+*  Purpose:
+*     Replaces entity references by corresponding ascii characters.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     char *RemoveEscapes( const char *text, int *status )
+
+*  Description:
+*     This function produces a dynamic copy of the supplied text in which
+*     occurrences of XML entity references are replaced by the corresponding
+*     ASCII text.
+
+*  Parameters:
+*     text
+*        A pointer to a text string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated string containing the required
+*     copy.
+
+*  Notes:
+*     - NULL is returned if this function is called with the global error
+*     status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   char *d;                  /* Pointer to next returned character */
+   char *result;             /* Returned pointer */
+   char rc;                  /* Replacement character */
+   const char *c;            /* Pointer to next supplied character */
+   int nc;                   /* Number of characters to skip */
+
+/* Initialise */
+   result = NULL;
+   nc = 0;
+
+/* Return if the pointer is NULL or if an error has occurred. */
+   if( !astOK || !text ) return result;
+
+/* Allocate memory to hold a copy of the supplied text. */
+   result = astMalloc( strlen( text ) + 1 );
+
+/* Check the pointer can be used safely. */
+   if( astOK ) {
+
+/* Loop round every character in the supplied text. */
+      c = text - 1;
+      d = result;
+      while( *(++c) ) {
+
+/* If this character marks the start of a entity reference, replace it by
+   the corresponding ascii character and shuffle the remaining text down. */
+         if( !strncmp( c, "&amp;", 5 ) ) {
+            rc = '&';
+            nc= 4;
+
+         } else if( !strncmp( c, "&lt;", 4 ) ) {
+            rc = '<';
+            nc= 3;
+
+         } else if( !strncmp( c, "&gt;", 4 ) ) {
+            rc = '>';
+            nc= 3;
+
+         } else if( !strncmp( c, "&apos;", 6 ) ) {
+            rc = '\'';
+            nc= 5;
+
+         } else if( !strncmp( c, "&quot;", 6 ) ) {
+            rc = '"';
+            nc= 5;
+
+         } else {
+            rc = 0;
+         }
+
+         if( rc ) {
+            *(d++) = rc;
+            c += nc;
+         } else {
+            *(d++) = *c;
+         }
+
+      }
+
+/* Terminate the returned string. */
+      *d = 0;
+
+/* Reallocate the string to free up any unused space. */
+      result = astRealloc( result, d - result + 1 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+#ifdef DEBUG
+static void RemoveObjectFromList( AstXmlObject *obj ){
+/*
+*  Name:
+*     RemoveObjectFromList
+
+*  Purpose:
+*     Removes an XmlObject from a static list of all currently active XmlObjects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     void RemoveObjectFromList( AstXmlObject *obj )
+
+*  Description:
+*     This function removes the supplied pointer from a static list of
+*     pointers, and decrements the number of elements in the list. This list
+*     holds pointers to all the XmlObjects which currently exist. If the
+*     supplied pointer is not found in the list, this function returns
+*     without action.
+
+*  Parameters:
+*     this
+*        A pointer to the XmlObject.
+
+*  Notes:
+*     - This function attempts to execute even if an error has already
+*     occurred.
+*/
+
+/* Local Variavles: */
+   int i;
+   int ii;
+
+/* Locate the supplied pointer within the list of pointers to all
+   currently active XmlObjects. */
+   ii = -1;
+   for( i = 0; i < nobj; i++ ){
+      if( existing_objects[ i ]->id == obj->id ) {
+         ii = i;
+         break;
+      }
+   }
+
+/* Check the pointer was found. */
+   if( ii != -1 ) {
+
+/* Shuffle all higher index pointers down one in the list in order to fill
+   the gap left by removing the supplied pointer. */
+      for( ii++; ii < nobj; ii++ ){
+         existing_objects[ ii - 1 ] = existing_objects[ ii ];
+      }
+
+/* Decrement the number of pointers in the list. */
+      nobj--;
+
+/* Nullify the pointer at the end of the list which is no longer used. */
+      existing_objects[ nobj ] = NULL;
+   }
+}
+#endif
+
+static const char *ResolvePrefix( const char *prefix, AstXmlElement *elem, int *status ){
+/*
+*  Name:
+*     ResolvePrefix
+
+*  Purpose:
+*     Find the URI associated with a namespace prefix.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     const char *ResolvePrefix( const char *prefix, AstXmlElement *elem, int *status)
+
+*  Description:
+*     This function searches the namespaces defined within the supplied
+*     element for a prefix which matches the supplied prefix. If found,
+*     it returns a pointer to the URI string associated with the prefix.
+*     If not found, it calls this function recursively on the parent
+*     element. If there is no parent element, NULL is returned.
+
+*  Parameters:
+*     prefix
+*        Pointer to a string holding the namespace prefix.
+*     elem
+*        The pointer to the XmlElement.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a string holding the URI, or NULL if not found.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstXmlParent *parent;        /* Parent object */
+   AstXmlNamespace *ns;         /* Next namespace */
+   const char *result;          /* Returned pointer */
+   int i;                       /* Loop count */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status, and the supplied element. */
+   if( !astOK || !elem ) return result;
+
+/* Loop round all the namespace definitions in the element. */
+   for( i = 0; i < elem->nnspref; i++ ) {
+      ns = elem->nsprefs[ i ];
+
+/* Compare the namespace prefix with the supplied prefix (case sensitive).
+   Store a pointer to the associated URI if they match, and leave the
+   loop. */
+      if( !strcmp( ns->prefix, prefix ) ) {
+         result = ns->uri;
+         break;
+      }
+   }
+
+/* If no matching namespace was found, attempt to resolve the prefix
+   within the context of the parent element. */
+   if( !result ) {
+      parent = ((AstXmlObject *) elem )->parent;
+      if( astXmlCheckType( parent, AST__XMLELEM ) ) {
+         result = ResolvePrefix( prefix, (AstXmlElement *) parent, status );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int Ustrcmp( const char *a, const char *b, int *status ){
+/*
+*  Name:
+*     Ustrncmp
+
+*  Purpose:
+*     A case blind version of strcmp.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     int Ustrcmp( const char *a, const char *b )
+
+*  Description:
+*     Returns 0 if there are no differences between the two strings, and 1
+*     otherwise. Comparisons are case blind.
+
+*  Parameters:
+*     a
+*        Pointer to first string.
+*     b
+*        Pointer to second string.
+
+*  Returned Value:
+*     Zero if the strings match, otherwise one.
+
+*  Notes:
+*     -  This function does not consider the sign of the difference between
+*     the two strings, whereas "strcmp" does.
+*     -  This function attempts to execute even if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   const char *aa;         /* Pointer to next "a" character */
+   const char *bb;         /* Pointer to next "b" character */
+   int ret;                /* Returned value */
+
+/* Initialise the returned value to indicate that the strings match. */
+   ret = 0;
+
+/* Initialise pointers to the start of each string. */
+   aa = a;
+   bb = b;
+
+/* Loop round each character. */
+   while( 1 ){
+
+/* We leave the loop if either of the strings has been exhausted. */
+      if( !(*aa ) || !(*bb) ){
+
+/* If one of the strings has not been exhausted, indicate that the
+   strings are different. */
+         if( *aa || *bb ) ret = 1;
+
+/* Break out of the loop. */
+         break;
+
+/* If neither string has been exhausted, convert the next characters to
+   upper case and compare them, incrementing the pointers to the next
+   characters at the same time. If they are different, break out of the
+   loop. */
+      } else {
+
+         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
+            ret = 1;
+            break;
+         }
+
+      }
+
+   }
+
+/* Return the result. */
+   return ret;
+
+}
+
+#ifdef DEBUG
+int astXmlTrace( int show ){
+/*
+*+
+*  Name:
+*     astXmlTrace
+
+*  Purpose:
+*     List details of XML objects currently in existence.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     int astXmlTrace( int show )
+
+*  Description:
+*     Lists details of XML objects currently in existence. Details are
+*     written to standard output.
+
+*  Parameters:
+*     show
+*        - 0, the ID values of all currently active XmlObjects are
+*        listed. The objects themselves are unchanged.
+*        - 1, each object is displayed using astXmlShow unless it has
+*        already been included in the display of a previous object, and then
+*        annulled. Consequently, this mode is destructive, and none of the
+*        displayed XmlObjects will be acessable afterwards.
+*        - 2, each object is displayed using astXmlShow whether or not it
+*        has already been included in the display of a previous object.
+*        The objects are left unchanged.
+*        - 3, nothing is written to standard output, but the number of
+*        active XmlObjects is still returned.
+
+*  Returned Value:
+*     The number of XMLObjects which are in existence on entry to this
+*     function.
+
+*-
+*/
+
+/* Local Variables: */
+   AstXmlObject *root;
+   int i, result, old_status;
+
+   old_status = astStatus;
+   astClearStatus;
+
+   result = nobj;
+
+   if( show == 0 ) {
+      printf( "Current list of active XmlObject identifiers: " );
+      for( i = 0; i < nobj; i++ ) printf( "%d ", existing_objects[ i ]->id );
+      printf("\n");
+
+   } else if( show ==1 ){
+      while( nobj > 0 ) {
+         root = astXmlGetRoot( existing_objects[0] );
+         printf( "ID = %d (type %ld)\n%s\n------------\n",
+                 root->id, root->type, astXmlShow( root ) );
+         root = astXmlAnnulTree( root );
+      }
+
+   } else if( show == 2 ) {
+      for( i = 0; i < nobj; i++ ) printf( "%d\n%s\n------------\n",
+                                          existing_objects[ i ]->id,
+                                          astXmlShow(existing_objects[ i ]) );
+      printf("\n");
+   }
+
+   astSetStatus( old_status );
+
+   return result;
+}
+#endif
+
+AstXmlElement *astXmlReadDocument_( AstXmlDocument **doc,
+                                   int (*is_wanted)( AstXmlElement *, int * ),
+                                   int skip, char (*source)( void *, int * ),
+                                   void *data, int *status ){
+/*
+*+
+*  Name:
+*     astXmlReadDocument
+
+*  Purpose:
+*     Read and parse an XML document.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlElement *astXmlReadDocument( AstXmlDocument **doc,
+*                                       int (*is_wanted)( AstXmlElement *, int * ),
+*                                       int skip, char (*source)( void *, int * ),
+*                                       void *data )
+
+*  Description:
+*     This function reads and parses text from an XML source. The text is
+*     obtained by calling the supplied "source" function, which returns
+*     the next character read from the external source on each invocation.
+*
+*     The reading scheme combines elements of the SAX and DOM schemes in
+*     an attempt to minimise memory requirements (a potential problem with
+*     DOM) whilst retaining a simple interface for accessing the XML
+*     elements of interest to the client (a potential problem for SAX).
+*
+*     When an element start tag is encountered in the source, the client
+*     is asked to indicate whether the element is of interest. This is
+*     done by calling the supplied "is_wanted" function. If the client
+*     indicates that the element is of interest, its contents are read
+*     and a pointer to a corresponding XmlElement structure is returned.
+*     Reading stops when the element has been read. If the client
+*     indicates that the element is not of interest, then (if "skip" is
+*     non-zero) the contents of the element are skipped over, and reading
+*     continues following the element end tag. When the next element is
+*     encountered the client will again be asked to indicate its interest
+*     in the element. This continues until either the client indicates that
+*     an element is of interest, or the end of the source is reached. If
+*     "skip" is zero, then an error is reported if the first element in
+*     the document is not of interest.
+*
+*     The client has an option to reply that an element is not itself of
+*     interest, but may possibly contain interesting elements. In this case,
+*     the sub-elements within the element are read and checked in the same
+*     way.
+*
+*     This function returns, and no more characters are read from the
+*     source, once the contents of the first "interesting" element has been
+*     read.
+*
+*     The function thus returns a pointer to an XmlElement containing the
+*     entire contents of the first interesting element encountered in the
+*     source. This function can then be invoked again to read further
+*     interesting elements from the source. In this case, the XmlDocument
+*     structure created by the initial invocation (see parameter "doc")
+*     must be supplied, as this indicates the point in the total document
+*     structure at which the previous "interesting" element was located.
+
+*  Parameters:
+*     doc
+*        Address of a location holding a pointer to an AstXmlDocument
+*        structure. The AstXmlDocument pointer should be supplied as NULL
+*        when invoking this function for the first time on a document
+*        source (i.e. when reading from the beginning of the document).
+*        In this case a new AstXmlDocument structure will be created and a
+*        pointer to it stored at the supplied address. This structure
+*        holds the context which enables subsequent invocations of this
+*        function to determine the point in the document structure at
+*        which to store any further text read from the source. It also
+*        holds the document prologue, root element, and epilogue.
+*     is_wanted
+*        Pointer to a function which is called to decide if the client is
+*        interested in each element start tag which has just been read.
+*        It has a single argument which is a pointer to the (empty) XmlElement
+*        corresponding to the element start tag which has just been read.
+*        It returns an integer:
+*           -1 : the element is not itself of interest but it may contain
+*                an interesting element, so look through the content and
+*                ask the client again about any elements found inside it.
+*            0 : the element definately contains nothing of interest to
+*                the client. kip its content and continue looking for new
+*                elements.
+*            1 : the element is definately of interest to the client so
+*                read its contents and return a pointer to it.
+*        If NULL is supplied, a value of "+1" is assumed.
+*     skip
+*        Indicates if any uninteresting elements may proceed the first
+*        element of interest. If zero, then an error is reported if the
+*        first element read from the source is not of interest to the client.
+*        If non-zero, then any uninteresting elements are simply skipped
+*        over until an interesting element is found or the document ends.
+*     source
+*        Pointer to a function which is called to return the next
+*        character from the source. It has a single argument which is
+*        used to pass any supplied data to it. It should return zero when
+*        the end of the source is reached.
+*     data
+*        Pointer to a structure to pass to the source function. This
+*        structure may contain any data needed by the source function.
+
+*  Returned Value:
+*     A pointer to the first element of interest, or NULL if there are no
+*     interesting elements in the source. The returned element will be a
+*     descendant of "*doc". For this reason, the returned pointer need not
+*     be annulled explicitly since it will be freed when the XmlDocument
+*     is annulled. However, if required (e.g. to save memory) it may be
+*     annulled before the document is annulled by using astXmlRemoveItem to
+*     remove it from its parent, and then using astXmlAnnul to annul it.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*     - It is assumed that the read commences outside any tag (i.e.
+*     in between tags or within character data).
+*-
+*/
+
+/* Local Variables: */
+   AstXmlElement *result;
+
+/* Check any supplied pointer is for an XmlDocument. */
+   astXmlCheckDocument( *doc, 1 );
+
+/* Read and parse the source text. Indicate that the element being read
+   *may* contain items of interest to the client. Surround with a mutex
+   since the supplied functions may not be thread-safe. */
+   LOCK_MUTEX1;
+   result = ReadContent( doc, -1, is_wanted, skip, source, data, 0, status );
+   UNLOCK_MUTEX1;
+
+/* Return the result. */
+   return result;
+}
+
+
+static AstXmlElement *ReadContent( AstXmlDocument **doc, int wanted,
+                                   int (*is_wanted)( AstXmlElement *, int * ),
+                                   int skip, char (*source)( void *, int * ),
+                                   void *data, int depth, int *status ){
+/*
+*  Name:
+*     ReadContent
+
+*  Purpose:
+*     Read and parse an XML document.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xml.h"
+*     AstXmlElement *ReadContent( AstXmlDocument **doc, int wanted,
+*                                 int (*is_wanted)( AstXmlElement *, int * ),
+*                                 int skip, char (*source)( void *, int * ),
+*                                 void *data, int depth, int *status )
+
+*  Description:
+*     This function reads and parses text from an XML source. The text is
+*     obtained by calling the supplied "source" function, which returns
+*     the next character read from the external source on each invocation.
+*
+*     See astXmlReadDocument for more details.
+
+*  Parameters:
+*     doc
+*        Address of a location holding a pointer to an AstXmlDocument
+*        structure. The AstXmlDocument pointer should be supplied as NULL
+*        when invoking this function for the first time on a document
+*        source (i.e. when reading from the beginning of the document).
+*        In this case a new AstXmlDocument structure will be created and a
+*        pointer to it stored at the supplied address. This structure
+*        holds the context which enables subsequent invocations of this
+*        function to determine the point in the document structure at
+*        which to store any further text read from the source. It also
+*        holds the document prologue, root element, and epilogue.
+*     wanted
+*        Indicates if the content read from the XML source is of interest
+*        to the client. If a positive value is supplied, all content read
+*        from the source (up to the end tag which corresponds to the
+*        supplied "parent") is added to the "parent" element (if supplied).
+*        If zero is supplied, then all content read from the source is
+*        discarded. If a negative value is supplied, then all content up
+*        to the first element start tag is discarded. When the first
+*        element start tag is encountered, it is passed back to the client
+*        by invoking the supplied "is_wanted" function. If this function
+*        returns a non-zero value, then the contents of the new element
+*        is read (by calling this function recursively) and a pointer to
+*        the new element is returned as the function value (reading then
+*        stops and the function returns). If the "is_wanted" function returns
+*        zero, then the contents of the new element is skipped over, and
+*        reading continues until the next element start tag is encountered,
+*        when the "is_wanted" function is again invoked.
+*     is_wanted
+*        Pointer to a function which is called to decide if the client is
+*        interested in the element start tag which has just been read.
+*        It has a single argument which is a pointer to the (empty) XmlElement
+*        corresponding to the element start tag which has just been read.
+*        It returns an integer:
+*           -1 : the element is not itself of interest but it may contain
+*                an interesting element, so look through the content and
+*                ask the client again about any elements found inside it.
+*            0 : the element definately contains nothing of interest to
+*                the client. kip its content and continue looking for new
+*                elements.
+*            1 : the element is definately of interest to the client so
+*                read its contents and return a pointer to it.
+*        If NULL is supplied, a value of "+1" is assumed.
+*     skip
+*        Indicates if any uninteresting elements may proceed the first
+*        element of interest. If zero, then an error is reported if the
+*        first element read from the source is not of interest to the client.
+*        If non-zero, then any uninteresting elements are simply skipped
+*        over until an interesting element is found or the document ends.
+*     source
+*        Pointer to a function which is called to return the next
+*        character from the source. It has a single argument which is
+*        used to pass any supplied data to it. It should return zero when
+*        the end of the source is reached.
+*     data
+*        Pointer to a structure to pass to the source function. This
+*        structure may contain any data needed by the source function.
+*     depth
+*        Depth of nesting (i.e. zero if this function was invoked from
+*        astXmlReadDocument, and a positive value if it was invoked
+*        recursively from within itself).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the first element of interest, or NULL if there are no
+*     interesting elements in the source. If the first element of
+*     interest has already been found (as indicated by "wanted" being +1)
+*     then NULL is returned. The returned element may be a child of a
+*     parent element containing namespace definitions (which may itself
+*     have a parent, etc). For this reason, the returned pointer should be
+*     freed using astXmlAnnulTree rather than astXmlAnnul.
+
+*  Notes:
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*     - It is assumed that the read commences outside any tag (i.e.
+*     in between tags or within character data).
+*/
+
+/* Local Variables; */
+   AstXmlElement *answer;       /* Result of reading a sub-element */
+   AstXmlElement *parent;       /* Pointer to current parent element */
+   AstXmlElement *elem;         /* A new element to be read */
+   AstXmlElement *result;       /* The returned pointer */
+   char *cc;                    /* Pointer to next character */
+   char *msg;                   /* Pointer to message buffer */
+   char *text1;                 /* Pointer to dynamic string */
+   char *text2;                 /* Pointer to another dynamic string */
+   char *text3;                 /* Pointer to another dynamic string */
+   char *text4;                 /* Pointer to another dynamic string */
+   char c;                      /* Current character read from source */
+   char lc2;                    /* Last but one character read */
+   char lc;                     /* Last character read */
+   char quoted;                 /* Character which opened current quote */
+   int nc1;                     /* No. of characters stored in text1 */
+   int nc2;                     /* No. of characters stored in text2 */
+   int nc3;                     /* No. of characters stored in text2 */
+   int ncmsg;                   /* Length of "msg" */
+   int newwanted;               /* Is the new element wanted? */
+   int state;                   /* Current action being performed */
+   int prolog_ok;               /* OK for source to start with a prolog? */
+   int where;                   /* Where to add the item within the document */
+
+/* Initialise */
+   result = NULL;
+   elem = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return result;
+
+/* If no XmlDocument was supplied, assume we are commencing to read a new
+   document from the begining. Create a new XmlDocument to store the
+   prologue, root element and epilogue, together with a pointer to the
+   "current" element, i.e. the element whose content is currently being
+   read. Also, since we have not yet asked the client if it is interested
+   in anything, ignore the supplied "wanted" value and use -1 to ensure
+   that we ask the client when the first element start tag is encountered. */
+   if( !*doc ){
+      prolog_ok = 1;
+      *doc = NewDocument( status );
+      wanted = -1;
+   } else {
+      prolog_ok = 0;
+   }
+
+/* Any content read from the source (except for prologue and epilogue)
+   will be placed into the "parent" element. A pointer to this element is
+   stored in the XmlDocument structure. The parent element will always be
+   a descendant of the root element, or the root element itself. */
+   parent = (*doc)->current;
+
+/* If the supplied parent has already been completed (typically because
+   it was read from an empty element tag), then just return without
+   action. */
+   if( parent && parent->complete ) {
+
+/* If an error has occurred, or if this invocation of ReadContent was
+   made recursively (rather than by the client), or if we have something
+   to return, return. */
+      if( !astOK || depth > 0 || result ) {
+         return result;
+
+/* Otherwise, returning would result in returning a null pointer to the
+   client even though the end of the document may not have been reached.
+   Revert to state 0 and search for further interesting elements. */
+      } else {
+         if( parent != (*doc)->root ) {
+            (*doc)->current = (AstXmlElement *) ( (AstXmlObject *) parent )->parent;
+         } else {
+            (*doc)->current = NULL;
+         }
+         parent = (*doc)->current;
+         state = 0;
+      }
+   }
+
+/* Initialise the previous two characters read. */
+   lc = 0;
+   lc2 = 0;
+
+/* Initialise pointer to dynamically allocated strings. */
+   text1 = NULL;
+   text2 = NULL;
+   text3 = NULL;
+   msg = NULL;
+
+/* We are not in a quote. */
+   quoted = 0;
+
+/* Initialise the "state" variable which indicates what we are currently
+   looking for. */
+   state = 0;
+
+/* Loop round reading characters from the source. */
+   while( 1 ) {
+      c = (*source)( data, status );
+
+/* Leave the loop if an error occurred whilst reading the character. */
+      if( !astOK ) break;
+
+/* If a parent element has been supplied, (i.e. if we are currently
+   reading the content of an element), or if we are not in state zero,
+   report an error and leave the loop if the end of the text has been
+   reached. If no parent was supplied, just leave the loop. */
+      if( !c ) {
+         if( parent ) {
+            astError( AST__XMLWF, "astRead(XmlChan): End of XML input text "
+                      "reached whilst reading the content of element %s.", status,
+                       astXmlGetTag( parent, 1 ) );
+
+         } else if( state > 1 ) {
+            if( msg ) {
+               astError( AST__XMLWF, "astRead(XmlChan): End of XML input text "
+                         "reached whilst reading the document epilogue "
+                         "(\"%s\").", status, msg );
+            } else {
+               astError( AST__XMLWF, "astRead(XmlChan): End of XML input text "
+                         "reached whilst reading the document epilogue." , status);
+            }
+         }
+         break;
+      }
+
+/* Save text which is not character data for use in error messages. */
+      if( state < 2 ) {
+         if( msg ) msg = astFree( msg );
+      } else {
+         msg = AppendChar( msg, &ncmsg, c, status );
+      }
+
+/* State 0: Use the first character to decide what sort of content item
+   follows (character data or a tag of some form). */
+      if( state == 0 ) {
+         if( c != '<' ) {
+            state = 1;
+            text1 = AppendChar( text1, &nc1, c, status );
+         } else {
+            msg = AppendChar( msg, &ncmsg, '<', status );
+            state = 2;
+         }
+
+/* State 1: We are reading character data. The character data ends at the
+   first occurrence of "<", at which point the character data is added to
+   the parent if required and we continue to state 2.*/
+      } else if( state == 1 ) {
+         if( c != '<' ) {
+            text1 = AppendChar( text1, &nc1, c, status );
+         } else {
+            msg = AppendChar( msg, &ncmsg, '<', status );
+            if( text1 ){
+
+/* If we have a parent element, just add it to the element. */
+               if( parent ) {
+                  if( wanted > 0 ) {
+                     text4 = RemoveEscapes( text1, status );
+                     astXmlAddCharData( (AstXmlParent *) parent, 0, text4 );
+                     text4 = astFree( text4 );
+
+
+/* If we are not allowed to skip over non-blank content, report an
+   error if the text is not blank. */
+                  } else if( !skip ) {
+                     cc = text1 - 1;
+                     while( *(++cc) ) {
+                        if( !isspace( *cc ) ) {
+                           if( parent ) {
+                              astError( AST__BADIN, "astRead(XmlChan): Cannot interpret "
+                                        "the input data \"%s\" within element %s.", status,
+                                        text1, astXmlGetTag( parent, 1 ) );
+                           } else {
+                              astError( AST__BADIN, "astRead(XmlChan): Cannot interpret "
+                                        "the input data: \"%s\".", status, text1 );
+                           }
+                           break;
+                        }
+                     }
+                  }
+
+/* Otherwise, add it to the document prologue or epilogue. */
+               } else {
+                  if( (*doc)->root ) {
+                     where = 3;
+                  } else if( (*doc)->prolog && (*doc)->prolog->dtdec ){
+                     where = 2;
+                  } else {
+                     where = 1;
+                  }
+
+                  text4 = RemoveEscapes( text1, status );
+                  astXmlAddCharData( (AstXmlParent *) *doc, where, text4 );
+                  text4 = astFree( text4 );
+               }
+
+               text1 = astFree( text1 );
+            }
+            state = 2;
+         }
+
+/* State 2: We are using the character following a "<" to determine what
+   type of tag is commencing. */
+      } else if( state == 2 ) {
+
+/* If the character is a ">", report an error. */
+         if( c == '>' ) {
+            if( parent ) {
+               astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"<>\" "
+                         "encountered within element %s.", status, astXmlGetTag( parent, 1 ) );
+            } else {
+               astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"<>\" "
+                         "encountered." , status);
+            }
+            break;
+
+/* If the character is a "?", this must be a PI tag. */
+         } else if( c == '?' ) {
+            state = 3;
+
+/* If the character is a "!", it must be a comment or a CDATA section
+   or a DTD. */
+         } else if( c == '!' ) {
+            state = 4;
+
+/* If the character is a "/", it must be an element end tag. */
+         } else if( c == '/' ) {
+            state = 5;
+
+/* Otherwise, this must be an element start tag. Append the character
+   to "text1". */
+         } else {
+            state = 6;
+            text1 = AppendChar( text1, &nc1, c, status );
+         }
+
+/* State 3: We are reading the initial text following the opening "<?" string
+   of a PI tag. The characters between the initial "<?" string and the first
+   space or closing "?>" string is the target text. */
+      } else if( state == 3 ) {
+         if( c == '>' && lc == '?' ) {
+            if( text1 ) text1[ --nc1 ] = 0;
+            state = 100;
+         } else if( isspace( c ) ) {
+            state = 7;
+         } else {
+            text1 = AppendChar( text1, &nc1, c, status );
+         }
+
+/* State 4: We are using the characters following the opening "<!" text to
+   determine if the tag is a comment, DTD or CDATA section. */
+      } else if( state == 4 ) {
+         if( c == '-' ) {
+            state = 8;
+         } else if( c == 'D' ){
+            state = 16;
+            text1 = astAppendString( text1, &nc1, "<!D" );
+         } else if( c == '[' ){
+            state = 9;
+            text1 = astAppendString( text1, &nc1, "<![" );
+         } else {
+            if( parent ) {
+               astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                         "starting with \"<!%c...\" encountered within "
+                         "element %s.", status, c, astXmlGetTag( parent, 1 ) );
+            } else {
+               astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                         "starting with \"<!%c...\" encountered.", status, c );
+            }
+            break;
+         }
+
+/* State 5: We are looking for the end of an element end tag. */
+      } else if( state == 5 ) {
+         if( c == '>' ) {
+            state = 101;
+         } else {
+            text1 = AppendChar( text1, &nc1, c, status );
+         }
+
+/* State 6: We are looking for the (prefix:)name combination at the start of
+   an element start tag. */
+      } else if( state == 6 ) {
+         if( c == '>' ) {
+            state = ( lc != '/' ) ? 102 : 103;
+         } else if( isspace( c ) ) {
+            state = 104;
+         } else if( c != '/' ){
+            text1 = AppendChar( text1, &nc1, c, status );
+         }
+
+/* State 7: We are reading the remaining text in a PI tag following the target
+   text. */
+      } else if( state == 7 ) {
+         if( c == '>' && lc == '?' ) {
+            if( text2 ) text2[ --nc2 ] = 0;
+            state = 100;
+         } else if( text2 || !isspace( c ) ) {
+            text2 = AppendChar( text2, &nc2, c, status );
+         }
+
+/* State 8: We are looking for the start of the text within a comment tag. */
+      } else if( state == 8 ) {
+         if( c == '-' ) {
+            state = 10;
+         } else {
+            if( parent ) {
+               astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                         "starting with \"<!-%c...\" encountered within "
+                         "element %s.", status, c, astXmlGetTag( parent, 1 ) );
+            } else {
+               astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                         "starting with \"<!-%c...\" encountered.", status, c );
+            }
+            break;
+         }
+
+/* State 9: We are looking for the start of the text within a CDATA tag. */
+      } else if( state == 9 ) {
+         if( c == '[' ) {
+            if( !strcmp( text1, "<![CDATA" ) ) {
+               state = 11;
+               text1 = astFree( text1 );
+            } else {
+               if( parent ) {
+                  astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                            "starting with \"%s%c...\" encountered within "
+                            "element %s.", status, text1, c, astXmlGetTag( parent, 1 )  );
+               } else {
+                  astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                            "starting with \"%s%c...\" encountered.", status, text1, c );
+               }
+               text1 = astFree( text1 );
+               break;
+            }
+
+         } else if( nc1 < 10 ) {
+            text1 = AppendChar( text1, &nc1, c, status );
+
+         } else {
+            if( parent ) {
+               astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                         "starting with \"%s%c...\" encountered within "
+                         "element %s.", status, text1, c, astXmlGetTag( parent, 1 )  );
+            } else {
+               astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                         "starting with \"%s%c...\" encountered.", status, text1, c );
+            }
+            text1 = astFree( text1 );
+            break;
+         }
+
+/* State 10: We are reading the remaining text in a comment tag. When the end
+   ">" is reached, check the previous 2 characters are "--" and then terminate
+   the text1 string in order to remove these two characters from the comment
+   text.  */
+      } else if( state == 10 ) {
+         if( c == '>' && lc == '-' && lc2 == '-' ) {
+            text1[ nc1 - 2 ] = 0;
+            state = 105;
+         } else {
+            text1 = AppendChar( text1, &nc1, c, status );
+         }
+
+/* State 11: We are reading the remaining text in a CDATA tag. */
+      } else if( state == 11 ) {
+         if( c == '>' && lc == ']' && lc2 == ']' ) {
+            text1[ nc1 - 2 ] = 0;
+            state = 106;
+         } else {
+            text1 = AppendChar( text1, &nc1, c, status );
+         }
+
+/* State 12: We are looking for an equals sign marking the end of an
+   attribute name within an element start tag. */
+      } else if( state == 12 ) {
+         if( c == '=' ) {
+            state = 13;
+
+         } else if( c == '>' ) {
+            if( text1 ) {
+               if( parent ) {
+                  astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                            " \"%s...\" encountered within element %s.", status, msg,
+                            astXmlGetTag( parent, 1 )  );
+               } else {
+                  astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"%s...\" "
+                            "encountered.", status, msg );
+               }
+               break;
+            } else {
+               if( lc == '/' ) {
+                  state = 108;
+               } else {
+                  state = 200;
+               }
+            }
+
+         } else if( text1 || !isspace( c ) ) {
+            if( c != '/' ) text1 = AppendChar( text1, &nc1, c, status );
+         }
+
+/* State 13: We are looking for a '"' or ''' marking the start of an attribute
+   value within an element start tag. */
+      } else if( state == 13 ) {
+         if( c == '"' ) {
+            state = 14;
+
+         } else if( c == '\'' ) {
+            state = 15;
+
+         } else if( c == '>' ) {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal value for attribute "
+                      "\"%s\" in XML tag \"%s...\".", status, text1, msg );
+            break;
+         }
+
+/* State 14: We are looking for a '"' marking the end of an attribute value
+   within an element start tag. */
+      } else if( state == 14 ) {
+         if( c == '"' ) {
+            state = 107;
+
+         } else if( c == '>' ) {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal value for attribute "
+                      "\"%s\" in XML tag \"%s...\".", status, text1, msg );
+            break;
+
+         } else {
+            text2 = AppendChar( text2, &nc2, c, status );
+         }
+
+/* State 15: We are looking for a ''' marking the end of an attribute value
+   within an element start tag. */
+      } else if( state == 15 ) {
+         if( c == '\'' ) {
+            state = 107;
+
+         } else if( c == '>' ) {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal value for attribute "
+                      "\"%s\" in XML tag \"%s...\".", status, text1, msg );
+            break;
+
+         } else {
+            text2 = AppendChar( text2, &nc2, c, status );
+         }
+
+/* State 16: We are looking for the end of a DOCTYPE string. */
+      } else if( state == 16 ) {
+         if( isspace( c ) ) {
+            if( !strcmp( text1, "<!DOCTYPE" ) ) {
+               state = 17;
+               text1 = astFree( text1 );
+            } else {
+               if( parent ) {
+                  astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                            "starting with \"%s%c...\" encountered within "
+                            "element %s.", status, text1, c, astXmlGetTag( parent, 1 )  );
+               } else {
+                  astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                            "starting with \"%s%c...\" encountered.", status, text1, c );
+               }
+               text1 = astFree( text1 );
+               break;
+            }
+
+         } else if( nc1 < 15 ) {
+            text1 = AppendChar( text1, &nc1, c, status );
+
+         } else {
+            if( parent ) {
+               astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                         "starting with \"%s%c...\" encountered within "
+                         "element %s.", status, text1, c, astXmlGetTag( parent, 1 )  );
+            } else {
+               astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag "
+                         "starting with \"%s%c...\" encountered.", status, text1, c );
+            }
+            text1 = astFree( text1 );
+            break;
+         }
+
+/* State 17: We are looking for the start of a DOCTYPE name string. */
+      } else if( state == 17 ) {
+         if( !isspace( c ) ) {
+            text1 = AppendChar( text1, &nc1, c, status );
+            state = 18;
+         }
+
+/* State 18: We are looking for the end of a DOCTYPE name string. */
+      } else if( state == 18 ) {
+         if( isspace( c ) ) {
+            state = 19;
+         } else if( c == '>' ) {
+            state = 109;
+         } else {
+            text1 = AppendChar( text1, &nc1, c, status );
+         }
+
+/* State 19: We are looking for the start of a string following a DOCTYPE
+   name string. */
+      } else if( state == 19 ) {
+         if( !isspace( c ) ) {
+            if( c == '[' ) {
+               state = 20;
+            } else if( c == '>' ) {
+               state = 109;
+            } else {
+               state = 21;
+               text2 = AppendChar( text2, &nc2, c, status );
+            }
+         }
+
+/* State 20: We are looking for the "]" marking the end of the internal
+   markup of a DOCTYPE element. Avoid the contents of quoted strings (such
+   as #FIXED attribute values). */
+      } else if( state == 20 ) {
+         text3 = AppendChar( text3, &nc3, c, status );
+         if( c == '\'' ) {
+            if( quoted == '\'' ) {
+               quoted = 0;
+            } else if( !quoted ) {
+               quoted = '\'';
+            }
+
+         } else if( c == '"' ) {
+            if( quoted == '"' ) {
+               quoted = 0;
+            } else if( !quoted ) {
+               quoted = '"';
+            }
+
+         } else if( !quoted && c == ']' ) {
+            text3[ --nc3 ] = 0;
+            state = 22;
+         }
+
+/* State 21: We are looking for the start of a DOCTYPE internal section. */
+      } else if( state == 21 ) {
+         if( c == '[' ) {
+            state = 20;
+         } else if( c == '>' ) {
+            state = 109;
+         } else {
+            text2 = AppendChar( text2, &nc2, c, status );
+         }
+
+/* State 22: We are looking for the ">" at the end of a DOCTYPE. */
+      } else if( state == 22 ) {
+         if( !isspace( c ) ) {
+            if( c == '>' ) {
+               state = 109;
+            } else {
+               astError( AST__XMLWF, "astRead(XmlChan): Extra text found "
+                         "at end of XML DOCTYPE tag \"%s\".", status, msg );
+            }
+         }
+
+      } else {
+         astError( AST__INTER, "ReadContent(xml): Illegal state (%d) encountered "
+                   "(AST internal programming error).", status, state );
+      }
+
+/* The following states perform actions consequent on the decisons made
+   above, but which must be performed before reading the next character. */
+
+/* In most cases there will be no actions to perform. Therefore check for
+   this first (to avoid the time spent doing all the following usually
+   irrelevant checks). */
+      if( state < 23 ) {
+
+/* State 100: We have just reached the end of a PI tag. Create a new XmlPI and
+   store it in the parent (if required). */
+      } else if( state == 100 ) {
+         if( text1 ){
+
+/* First deal with XML declaration PI's. These must be the first item in
+   the source. */
+            if( !strcmp( text1, "xml" ) ) {
+               if( (*doc)->root || (*doc)->prolog || (*doc)->nepi > 0 ) {
+                  astError( AST__XMLWF, "astRead(XmlChan): An XML "
+                            "declaration \"%s\" was encountered within the "
+                            "body of the document.", status, msg );
+               } else {
+                  astXmlSetXmlDec( *doc, text2 );
+               }
+
+/* Now deal with other PI's. */
+            } else {
+
+/* If we have a parent element, just add it to the element. */
+               if( parent ) {
+                  if( wanted > 0 ) {
+                     astXmlAddPI( (AstXmlParent *) parent, 0, text1, text2 );
+                  } else if( !skip ) {
+                     if( parent ) {
+                        astError( AST__BADIN, "astRead(XmlChan): Cannot interpret "
+                                  "the input data \"%s\" within element %s.", status,
+                                  msg, astXmlGetTag( parent, 1 ) );
+                     } else {
+                        astError( AST__BADIN, "astRead(XmlChan): Cannot interpret "
+                                  "the input data: \"%s\".", status, msg );
+                     }
+                     break;
+                  }
+
+/* Otherwise, add it to the document prologue or epilogue. */
+               } else {
+                  if( (*doc)->root ) {
+                     where = 3;
+                  } else if( (*doc)->prolog->dtdec ){
+                     where = 2;
+                  } else {
+                     where = 1;
+                  }
+                  astXmlAddPI( (AstXmlParent *) *doc, where, text1, text2 );
+
+               }
+            }
+            text1 = astFree( text1 );
+            if( text2 ) text2 = astFree( text2 );
+         } else {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"%s\" "
+                      "encountered.", status, msg );
+            break;
+         }
+         state = 0;
+
+/* State 101: We have just reached the end of an element end tag. Check that
+   the (prefix:)name is legal, and matches that of the current parent,
+   re-instate the parent's parent as the current element in the document,
+   and leave the loop if appropriate. */
+      } else if( state == 101 ) {
+         if( text1 ){
+            CheckPrefName( text1, "element", "astRead(XmlChan)", status );
+            if( parent ) {
+               if( MatchName( parent, text1, status ) ) {
+                  parent->complete = 1;
+                  if( parent != (*doc)->root ) {
+                     (*doc)->current = (AstXmlElement *) ( (AstXmlObject *) parent )->parent;
+                  } else {
+                     (*doc)->current = NULL;
+                  }
+               } else {
+                  astError( AST__XMLWF, "astRead(XmlChan): Start tag \"%s\" "
+                            "closed by end tag \"%s\".", status, astXmlGetTag( parent, 1 ),
+                            msg );
+               }
+
+            } else {
+               (*doc)->current = NULL;
+               astError( AST__XMLWF, "astRead(XmlChan): Unmatched end tag "
+                         "\"%s\" encountered.", status, msg );
+            }
+
+            text1 = astFree( text1 );
+
+         } else {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"%s\" "
+                      "encountered.", status, msg );
+         }
+
+/* If an error has occurred, or if this invocation of ReadContent was
+   made recursively (rather tnan by the client), or if we have something
+   to return, break out of the loop. */
+         if( !astOK || depth > 0 || result ) {
+            break;
+
+/* Otherwise, breaking would result in returning a null pointer to the
+   client even though the end of the document may not have been reached.
+   Revert to state 0 and search for further intersting elements. */
+         } else {
+            parent = (*doc)->current;
+            state = 0;
+         }
+
+/* State 102: We have just got the (prefix:)name for an element start tag, and
+   the start tag contains no attributes, etc. Create a new XmlElement, adding
+   it to the supplied parent, and then proceed to state 200 to read the
+   content of the element. */
+      } else if( state == 102 ) {
+         if( text1 ){
+            elem = astXmlAddElement( parent, text1, NULL );
+            text1 = astFree( text1 );
+            state = 200;
+
+         } else {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"%s\" "
+                      "encountered.", status, msg );
+            break;
+         }
+
+/* State 103: We have just got the (prefix:)name for an empty element tag, and
+   the tag does not contain further attributes, etc. Create a new XmlElement
+   and store it in the container (if any). Indicate that there is no
+   content to read, and then go on to state 200. */
+      } else if( state == 103 ) {
+         if( text1 ){
+            elem = astXmlAddElement( parent, text1, NULL );
+            elem->complete = 1;
+            text1 = astFree( text1 );
+            state = 200;
+
+         } else {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"%s\" "
+                      "encountered.", status, msg );
+            break;
+         }
+
+/* State 104: We have just got the (prefix:)name for an element start tag, but
+   the start tag may contain further attributes, etc. Create a new XmlElement
+   and store it in the container (if any). Then go to state 12 in which we
+   look for further attributes, etc. */
+      } else if( state == 104 ) {
+         if( text1 ){
+            elem = astXmlAddElement( parent, text1, NULL );
+            text1 = astFree( text1 );
+            state = 12;
+
+         } else {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"%s\" "
+                      "encountered.", status, msg );
+            break;
+         }
+
+/* State 105: We have just reached the end of a comment tag. Create a new
+   XmlComment and  store it in the parent. */
+      } else if( state == 105 ) {
+         if( text1 ){
+
+/* If we have a parent element, just add it to the element. */
+            if( parent ) {
+               if( wanted > 0 ) {
+                  astXmlAddComment( (AstXmlParent *) parent, 0, text1 );
+               } else if( !skip ) {
+                  if( parent ) {
+                     astError( AST__BADIN, "astRead(XmlChan): Cannot interpret "
+                               "the input data \"%s\" within element %s.", status,
+                               msg, astXmlGetTag( parent, 1 ) );
+                  } else {
+                     astError( AST__BADIN, "astRead(XmlChan): Cannot interpret "
+                               "the input data: \"%s\".", status, msg );
+                  }
+                  break;
+               }
+
+/* Otherwise, add it to the document prologue or epilogue. */
+            } else {
+               if( (*doc)->root ) {
+                  where = 3;
+               } else if( (*doc)->prolog->dtdec ){
+                  where = 2;
+               } else {
+                  where = 1;
+               }
+               astXmlAddComment( (AstXmlParent *) *doc, where, text1 );
+            }
+
+            text1 = astFree( text1 );
+
+         } else {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"%s\" "
+                      "encountered.", status, msg );
+            break;
+         }
+         state = 0;
+
+/* State 106: We have just reached the end of a CDATA tag. Create a new
+   XmlCDATASection and store it in the container (if any). */
+      } else if( state == 106 ) {
+         if( text1 ){
+            if( parent && wanted > 0 ) {
+               astXmlAddCDataSection( parent, text1 );
+            } else if( !skip ) {
+               if( parent ) {
+                  astError( AST__BADIN, "astRead(XmlChan): Cannot interpret "
+                            "the input data \"%s\" within element %s.", status,
+                            msg, astXmlGetTag( parent, 1 ) );
+               } else {
+                  astError( AST__BADIN, "astRead(XmlChan): Cannot interpret "
+                            "the input data: \"%s\".", status, msg );
+               }
+               break;
+            }
+            text1 = astFree( text1 );
+         } else {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"%s\" "
+                      "encountered.", status, msg );
+            break;
+         }
+         state = 0;
+
+/* State 107: We have just reached the end of an attribute or namespace
+   setting. Create a new object and store it in the element created
+   earlier. */
+      } else if( state == 107 ) {
+         if( text1 ){
+            if( !elem ) {
+               astError( AST__INTER, "ReadContent(xml): Container lost at state "
+                         "107 (AST internal programming error).", status );
+               break;
+            }
+
+            if( !strcmp( text1, "xmlns" ) ) {
+               astXmlAddURI( elem, NULL, text2 );
+
+            } else if( !strncmp( text1, "xmlns:", 6 ) ) {
+               astXmlAddURI( elem, text1+6, text2 );
+
+            } else {
+               text4 = RemoveEscapes( text2, status );
+               astXmlAddAttr( elem, text1, text4, NULL );
+               text4 = astFree( text4 );
+            }
+
+            text1 = astFree( text1 );
+            text2 = astFree( text2 );
+
+         } else {
+            astError( AST__XMLWF, "astRead(XmlChan): Illegal XML tag \"%s\" "
+                      "encountered.", status, msg );
+            break;
+         }
+         state = 12;
+
+/* State 108: We have just reached the end of an empty element tag to which
+   we have been adding attributes, etc. */
+      } else if( state == 108 ) {
+         if( elem ) {
+            elem->complete = 1;
+            state = 200;
+         } else {
+            astError( AST__INTER, "Parse(xml): No container in state 108 "
+                      "(AST internal programming error).", status );
+            break;
+         }
+
+/* State 109: We have just reached the end of a DOCTYPE tag. */
+      } else if( state == 109 ) {
+
+         if( (*doc)->root ){
+            astError( AST__XMLWF, "astRead(XmlChan): An DOCTYPE tag "
+                      "\"%s\" was encountered within the body of the "
+                      "document.", status, msg );
+            break;
+
+         } else if( (*doc)->prolog->dtdec ){
+            astError( AST__XMLWF, "astRead(XmlChan): Multiple DOCTYPE tags "
+                      "encountered." , status);
+            break;
+
+         } else {
+            astXmlSetDTDec( *doc, text1, text2, text3 );
+            text1 = astFree( text1 );
+            text2 = astFree( text2 );
+            text3 = astFree( text3 );
+            state = 0;
+         }
+
+      } else if( state != 200 ) {
+         astError( AST__INTER, "ReadContent(xml): Illegal state (%d) encountered "
+                   "(AST internal programming error).", status, state );
+      }
+
+
+
+/* State 200: We now have now read a complete element start tag and have
+   a corresponding XmlElement ("elem"), with all attributes and namespaces,
+   etc (but no content). Call the "is_wanted" function to see if the client
+   is interested in the element. */
+      if( state == 200 ) {
+
+/* If this element is found at the root level of the document, store a
+   pointer to it as the root element. Report an error if there is already
+   a root element. */
+         if( !parent ) {
+            if( (*doc)->root ){
+               if( astOK ) {
+                  astError( AST__XMLWF, "astRead(XmlChan): Multiple root "
+                            "elements encountered." , status);
+                  elem = astXmlDelete( elem );
+               }
+               break;
+            } else {
+               (*doc)->root = elem;
+               ((AstXmlObject *) elem )->parent = (AstXmlParent *) (*doc);
+            }
+         }
+
+/* If we do not already know, ask the caller if it is interested in this new
+   element. If no "is_wanted" function was supplied, assume all elements
+   are interesting. */
+         if( wanted == -1 ) {
+            newwanted = is_wanted ? (*is_wanted)( elem, status ) : 1;
+         } else {
+            newwanted = wanted;
+         }
+
+/* If it is not interested, report an error if skip is zero. */
+         if( newwanted != 1 && !skip ) {
+            if( parent ) {
+               astError( AST__BADIN, "astRead(XmlChan): Cannot interpret "
+                         "the input data \"%s\" within element %s.", status,
+                         msg, astXmlGetTag( parent, 1 ) );
+            } else {
+               astError( AST__BADIN, "astRead(XmlChan): Cannot interpret "
+                         "the input data: \"%s\".", status, msg );
+            }
+            break;
+         }
+
+/* Make the new element the "current" element in the document. */
+         (*doc)->current = elem;
+
+/* Read the contents of the new element from the source. If the client is
+   interested in the element, the read contents will be added to the
+   element, otherwise they will be discarded after being read. */
+         answer = ReadContent( doc, newwanted, is_wanted, skip, source,
+                               data, depth + 1, status );
+
+/* If the first interesting element was found inside "elem", then
+   return it. If "elem" is not interesting and did not contain anything
+   of interest, delete it and return the initialised NULL pointer. */
+         if( newwanted < 0 ) {
+            if( answer ) {
+               result = answer;
+            } else {
+               elem = astXmlDelete( elem );
+            }
+
+/* If the elem is of no interest, delete it and return the initialised
+   NULL pointer. */
+         } else if( newwanted == 0 ) {
+            elem = astXmlDelete( elem );
+
+/* Otherwise, "elem" itself is definitely of interest. If "elem" is
+   the first item of interest, return it. */
+         } else if( wanted < 0 ) {
+            result = elem;
+         }
+
+/* If we have an answer to return, leave the loop, otherwise re-instate the
+   original current element in the document and continue to read any text
+   following the element. */
+         if( result ) {
+            break;
+         } else {
+            (*doc)->current = parent;
+            state = 0;
+         }
+
+      } if( state > 22 ) {
+         astError( AST__INTER, "ReadContent(xml): Illegal state (%d) encountered "
+                   "(AST internal programming error).", status, state );
+      }
+
+/* Remember the previous two character */
+      lc2 = lc;
+      lc = c;
+   }
+
+/* Free any dynamic strings */
+   text1 = astFree( text1 );
+   text2 = astFree( text2 );
+   text3 = astFree( text3 );
+   if( msg ) msg = astFree( msg );
+
+/* Delete the returned object if an error occurred. */
+   if( !astOK ) result = astXmlDelete( result );
+
+/* Return the result. */
+   return result;
+}
+
+
+
diff --git a/ast/xml.h b/ast/xml.h
new file mode 100644
index 0000000..bbd0c89
--- /dev/null
+++ b/ast/xml.h
@@ -0,0 +1,392 @@
+#if !defined( XML_INCLUDED ) /* Include this file only once */
+#define XML_INCLUDED
+/*
+*+
+*  Name:
+*     xml.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the AST xml module
+
+*  Invocation:
+*     #include "xml.h"
+
+*  Description:
+*     This include file defines the interface to the internal xml module
+*     used by the AST library and provides the type definitions, function
+*     prototypes and macros, etc. needed to use this module.
+
+*  Inheritance:
+*     The xml module is not a class and does not inherit.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     23-OCT-2003 (DSB):
+*        Original version.
+*     12-JAN-2004 (DSB):
+*        Major revisions.
+*/
+
+
+
+/* Constant Values. */
+/* ================ */
+
+/* These constants are used as identifiers for the different classes of
+   XML object defined in this file. They are purposefully obscure to reduce
+   the possibility of random integer values being incorrectly interpreted
+   as valid XML types */
+#define AST__XMLBAD    0            /* Id for an uninitialised XmlObject */
+#define AST__XMLOBJECT 198263577    /* Id for XmlObject structure */
+#define AST__XMLELEM   182874779    /* Id for XmlElement structure */
+#define AST__XMLATTR   837746634    /* Id for XmlAttribute structure */
+#define AST__XMLCDATA  293854662    /* Id for XmlCDdataSection structure */
+#define AST__XMLCOM    748737648    /* Id for XmlComment structure */
+#define AST__XMLPI     983763553    /* Id for XmlPI structure */
+#define AST__XMLNAME   236756469    /* Id for XmlNamespace structure */
+#define AST__XMLDOC    356274395    /* Id for XmlDocument structure */
+#define AST__XMLPRO    743682474    /* Id for XmlPrologue structure */
+#define AST__XMLDEC    987546328    /* Id for XmlDeclPI structure */
+#define AST__XMLDTD    874673747    /* Id for XmlDTDec structure */
+#define AST__XMLWHITE  675849952    /* Id for XmlWhite structure */
+#define AST__XMLBLACK  347657863    /* Id for XmlBlack structure */
+
+/* The following constants refer to "interfaces", not "classes". */
+
+#define AST__XMLCHAR   456739289    /* Id for XmlCharData structure */
+#define AST__XMLCONT   673882993    /* Id for XmlContentItem structure */
+#define AST__XMLMISC   358768954    /* Id for XmlMiscItem structure */
+#define AST__XMLPAR    874366235    /* Id for XmlParent structure */
+
+/* Define constants used to size global arrays in this module. */
+#define AST__XML_GETTAG_BUFF_LEN 200
+
+/* Type Definitions. */
+/* ================= */
+
+/* Pre-define types so they can be used within structure definitions. */
+typedef struct AstXmlObject AstXmlObject;
+typedef struct AstXmlAttribute AstXmlAttribute;
+typedef struct AstXmlNamespace AstXmlNamespace;
+typedef struct AstXmlElement AstXmlElement;
+typedef struct AstXmlBlack AstXmlBlack;
+typedef struct AstXmlWhite AstXmlWhite;
+typedef struct AstXmlCDataSection AstXmlCDataSection;
+typedef struct AstXmlComment AstXmlComment;
+typedef struct AstXmlPI AstXmlPI;
+typedef struct AstXmlDocument AstXmlDocument;
+typedef struct AstXmlPrologue AstXmlPrologue;
+typedef struct AstXmlDeclPI AstXmlDeclPI;
+typedef struct AstXmlDTDec AstXmlDTDec;
+
+/* The following data types define "interfaces". That is each data type
+   corresponds to a subset of the above classes. */
+
+/* Marks a class as "character data" */
+typedef AstXmlObject AstXmlCharData;
+
+/* Marks a class as a "content item" */
+typedef AstXmlObject AstXmlContentItem;
+
+/* Marks a class as a "miscalleneous item" */
+typedef AstXmlObject AstXmlMiscItem;
+
+/* Marks a class as being able to own a child */
+typedef AstXmlObject AstXmlParent;
+
+/* XmlObject structure. */
+/* -------------------- */
+/* Contains data common to all other structures */
+struct AstXmlObject {
+   AstXmlParent *parent;        /* The parent which contains this XmlObject */
+   long int type;               /* An ID giving the type of structure */
+   int id;                      /* A unique id for this object. */
+};
+
+/* XmlAttribute structure. */
+/* ----------------------- */
+/* Describes an XML attribute */
+struct AstXmlAttribute {
+   AstXmlObject obj;          /* General information for this XmlObject */
+   char *name;                /* The name of the attribute */
+   char *value;               /* Attribute value */
+   char *prefix;              /* Namespace prefix for this attribute */
+};
+
+/* XmlNamespace structure. */
+/* ----------------------- */
+/* Describes an XML namespace definition */
+struct AstXmlNamespace {
+   AstXmlObject obj;          /* General information for this XmlObject */
+   char *prefix;              /* Namespace prefix */
+   char *uri;                 /* Namespace URI */
+};
+
+/* XmlElement structure. */
+/* --------------------- */
+/* Describes an XML element */
+struct AstXmlElement {
+   AstXmlObject obj;          /* General information for this XmlObject */
+   char *name;                /* The type (name) of the element */
+   AstXmlAttribute **attrs;   /* Ptr. to list of attributes of the element */
+   int nattr;                 /* Number of attributes in the above list */
+   AstXmlContentItem **items; /* Ptr. to list of items in the element's content */
+   int nitem;                 /* Number of items in above list */
+   char *defns;               /* Default Namespace URI for element content */
+   char *prefix;              /* Namespace prefix for this element */
+   AstXmlNamespace **nsprefs; /* Ptr. to list of new Namespaces defined by this element */
+   int nnspref;               /* Number of Namespaces in above list */
+   int complete;              /* Have the contents of the element been read? */
+};
+
+/* XmlBlack structure. */
+/* ---------------------- */
+/* Describes character data containing at least one non-blank character. */
+struct AstXmlBlack {
+   AstXmlObject obj;          /* General information for this XmlObject */
+   char *text;                /* The character data */
+};
+
+/* XmlWhite structure. */
+/* ------------------- */
+/* Describes character data containing no one non-blank characters. */
+struct AstXmlWhite {
+   AstXmlObject obj;          /* General information for this XmlObject */
+   char *text;                /* The white character data */
+};
+
+/* XmlCDataSection structure. */
+/* ----------------------- */
+/* Describes an XML CDATA section */
+struct AstXmlCDataSection {
+   AstXmlObject obj;          /* General information for this XmlObject */
+   char *text;                /* The text of the cdata section */
+};
+
+/* XmlComment structure. */
+/* --------------------- */
+/* Describes an XML CDATA section */
+struct AstXmlComment {
+   AstXmlObject obj;          /* General information for this XmlObject */
+   char *text;                /* The text of the comment */
+};
+
+/* XmlPI structure. */
+/* ---------------- */
+/* Describes an XML processing instruction */
+struct AstXmlPI {
+   AstXmlObject obj;          /* General information for this XmlObject */
+   char *target;              /* The target of the processing instruction */
+   char *text;                /* The text of the processing instruction */
+};
+
+/* XmlDocument structure. */
+/* ---------------------- */
+/* Describes an entire XML document */
+struct AstXmlDocument {
+   AstXmlObject obj;        /* General information for this XmlObject */
+   AstXmlPrologue *prolog;  /* Pointer to document prologue */
+   AstXmlElement *root;     /* Pointer to root element */
+   AstXmlMiscItem **epilog; /* List of XmlObjects forming the document epilogue */
+   int nepi;                /* No of XmlObjects pointers in "epilogue" */
+   AstXmlElement *current;  /* Pointer to element being read */
+};
+
+/* XmlPrologue structure. */
+/* ---------------------- */
+/* Describes an XML document prologue */
+struct AstXmlPrologue {
+   AstXmlObject obj;        /* General information for this XmlObject */
+   AstXmlDeclPI *xmldecl;   /* Pointer to XML declaration PI */
+   AstXmlMiscItem **misc1;  /* Group of of miscalleneous XmlObjects pointers */
+   int nmisc1;              /* No of XmlObjects pointers in "misc1" */
+   AstXmlDTDec *dtdec;      /* Pointer to Document Type Declaration */
+   AstXmlMiscItem **misc2;  /* Group of of miscalleneous XmlObjects pointers */
+   int nmisc2;              /* No of XmlObjects pointers in "misc2" */
+};
+
+/* XmlDecPI structure. */
+/* ------------------- */
+/* Describes an XML declaration PI */
+struct AstXmlDeclPI {
+   AstXmlObject obj;        /* General information for this XmlObject */
+   char *text;              /* The text of the XML declaration */
+};
+
+/* XmlDTDec structure. */
+/* ------------------- */
+/* Describes a data type declaration */
+struct AstXmlDTDec {
+   AstXmlObject obj;        /* General information for this XmlObject */
+   char *name;              /* Document type name */
+   char *external;          /* External ID */
+   char *internal;          /* Internal declarations */
+};
+
+
+#if defined(THREAD_SAFE) && defined(astCLASS)
+
+/* Define a structure holding all data items that are global within the
+   xml.c file. */
+typedef struct AstXmlGlobals {
+   int Next_ID;
+   char GetTag_Buff[ AST__XML_GETTAG_BUFF_LEN + 1 ];
+} AstXmlGlobals;
+
+#endif
+
+
+
+
+/* Function prototypes. */
+/* ==================== */
+AstXmlAttribute *astXmlCheckAttribute_( void *, int, int * );
+AstXmlBlack *astXmlCheckBlack_( void *, int, int * );
+AstXmlCDataSection *astXmlCheckCDataSection_( void *, int, int * );
+AstXmlComment *astXmlCheckComment_( void *, int, int * );
+AstXmlContentItem *astXmlGetItem_( AstXmlElement *, int, int * );
+AstXmlDTDec *astXmlCheckDTDec_( void *, int, int * );
+AstXmlDeclPI *astXmlCheckDeclPI_( void *, int, int * );
+AstXmlDocument *astXmlCheckDocument_( void *, int, int * );
+AstXmlElement *astXmlAddElement_( AstXmlElement *, const char *, const char *, int * );
+AstXmlElement *astXmlCheckElement_( void *, int, int * );
+AstXmlParent *astXmlGetParent_( AstXmlObject *, int * );
+AstXmlObject *astXmlGetRoot_( AstXmlObject *, int * );
+AstXmlElement *astXmlReadDocument_( AstXmlDocument **, int (*)( AstXmlElement *, int * ), int, char (*)( void *, int * ), void *, int * );
+AstXmlNamespace *astXmlCheckNamespace_( void *, int, int * );
+AstXmlObject *astXmlCopy_( AstXmlObject *, int * );
+AstXmlObject *astXmlCheckObject_( void *, int, int * );
+AstXmlPI *astXmlCheckPI_( void *, int, int * );
+AstXmlPrologue *astXmlCheckPrologue_( void *, int, int * );
+AstXmlWhite *astXmlCheckWhite_( void *, int, int * );
+AstXmlCharData *astXmlCheckCharData_( void *, int, int * );
+AstXmlContentItem *astXmlCheckContentItem_( void *, int, int * );
+AstXmlMiscItem *astXmlCheckMiscItem_( void *, int, int * );
+AstXmlParent *astXmlCheckParent_( void *, int, int * );
+const char *astXmlFormat_( AstXmlObject *, int * );
+const char *astXmlGetAttributeValue_( AstXmlElement *, const char *, int * );
+const char *astXmlGetName_( AstXmlObject *, int * );
+const char *astXmlGetTag_( AstXmlObject *, int, int * );
+const char *astXmlGetType_( AstXmlObject *, int * );
+const char *astXmlGetURI_( AstXmlObject *, int * );
+const char *astXmlGetValue_( AstXmlObject *, int, int * );
+const char *astXmlShow_( AstXmlObject *, int * );
+int astXmlCheckType_( void *, long int, int * );
+int astXmlGetNattr_( AstXmlElement *, int * );
+int astXmlGetNitem_( AstXmlElement *, int * );
+void *astXmlAnnulTree_( AstXmlObject *, int * );
+void *astXmlAnnul_( AstXmlObject *, int * );
+void *astXmlDelete_( void *, int * );
+void astXmlAddAttr_( AstXmlElement *, const char *, const char *, const char *, int * );
+void astXmlAddCDataSection_( AstXmlElement *, const char *, int * );
+void astXmlAddCharData_( AstXmlParent *, int, const char *, int * );
+void astXmlAddComment_( AstXmlParent *, int, const char *, int * );
+void astXmlAddPI_( AstXmlParent *, int, const char *, const char *, int * );
+void astXmlAddURI_( AstXmlElement *, const char *, const char *, int * );
+void astXmlInsertElement_( AstXmlElement *, AstXmlElement *, int * );
+void astXmlPurge_( AstXmlParent *, int * );
+void astXmlRemoveAttr_( AstXmlElement *, const char *, const char *, int * );
+void astXmlRemoveItem_( AstXmlContentItem *, int * );
+void astXmlRemoveURI_( AstXmlElement *, const char *, int * );
+void astXmlSetXmlDec_( AstXmlDocument *, const char *, int * );
+void astXmlSetDTDec_( AstXmlDocument *, const char *, const char *, const char *, int * );
+
+#if defined(THREAD_SAFE) && defined(astCLASS)
+void astInitXmlGlobals_( AstXmlGlobals * );
+#else
+
+#ifdef DEBUG
+int astXmlTrace_( int );
+#endif
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These wrap up the functions defined by this module. */
+#define astXmlGetType(this) astXmlGetType_(this,STATUS_PTR)
+#define astXmlCheckAttribute(this,nullok) astXmlCheckAttribute_(this,nullok,STATUS_PTR)
+#define astXmlCheckBlack(this,nullok) astXmlCheckBlack_(this,nullok,STATUS_PTR)
+#define astXmlCheckCDataSection(this,nullok) astXmlCheckCDataSection_(this,nullok,STATUS_PTR)
+#define astXmlCheckCharData(this,nullok) astXmlCheckCharData_(this,nullok,STATUS_PTR)
+#define astXmlCheckComment(this,nullok) astXmlCheckComment_(this,nullok,STATUS_PTR)
+#define astXmlCheckContentItem(this,nullok) astXmlCheckContentItem_(this,nullok,STATUS_PTR)
+#define astXmlCheckDTDec(this,nullok) astXmlCheckDTDec_(this,nullok,STATUS_PTR)
+#define astXmlCheckDeclPI(this,nullok) astXmlCheckDeclPI_(this,nullok,STATUS_PTR)
+#define astXmlCheckDocument(this,nullok) astXmlCheckDocument_(this,nullok,STATUS_PTR)
+#define astXmlCheckElement(this,nullok) astXmlCheckElement_(this,nullok,STATUS_PTR)
+#define astXmlCheckMiscItem(this,nullok) astXmlCheckMiscItem_(this,nullok,STATUS_PTR)
+#define astXmlCheckNamespace(this,nullok) astXmlCheckNamespace_(this,nullok,STATUS_PTR)
+#define astXmlCheckObject(this,nullok) astXmlCheckObject_(this,nullok,STATUS_PTR)
+#define astXmlCheckPI(this,nullok) astXmlCheckPI_(this,nullok,STATUS_PTR)
+#define astXmlCheckParent(this,nullok) astXmlCheckParent_(this,nullok,STATUS_PTR)
+#define astXmlCheckPrologue(this,nullok) astXmlCheckPrologue_(this,nullok,STATUS_PTR)
+#define astXmlCheckWhite(this,nullok) astXmlCheckWhite_(this,nullok,STATUS_PTR)
+
+#define astXmlAddAttr(elem,name,value,prefix) astXmlAddAttr_(astXmlCheckElement(elem,0),name,value,prefix,STATUS_PTR)
+#define astXmlAddURI(elem,prefix,uri) astXmlAddURI_(astXmlCheckElement(elem,0),prefix,uri,STATUS_PTR)
+#define astXmlAnnul(this) astXmlAnnul_(astXmlCheckObject(this,1),STATUS_PTR)
+#define astXmlDelete(this) astXmlDelete_(this,STATUS_PTR)
+#define astXmlAnnulTree(this) astXmlAnnulTree_(astXmlCheckObject(this,1),STATUS_PTR)
+#define astXmlAddCDataSection(this,text) astXmlAddCDataSection_(astXmlCheckElement(this,0),text,STATUS_PTR)
+#define astXmlAddCharData(this,where,text) astXmlAddCharData_(astXmlCheckParent(this,0),where,text,STATUS_PTR)
+#define astXmlAddComment(this,where,text) astXmlAddComment_(astXmlCheckParent(this,0),where,text,STATUS_PTR)
+#define astXmlAddElement(this,name,prefix) astXmlAddElement_(astXmlCheckElement(this,1),name,prefix,STATUS_PTR)
+#define astXmlAddPI(this,where,target,text) astXmlAddPI_(astXmlCheckParent(this,0),where,target,text,STATUS_PTR)
+#define astXmlGetParent(this) astXmlGetParent_(astXmlCheckObject(this,0),STATUS_PTR)
+#define astXmlGetRoot(this) astXmlGetRoot_(astXmlCheckObject(this,0),STATUS_PTR)
+#define astXmlGetName(this) astXmlGetName_(astXmlCheckObject(this,0),STATUS_PTR)
+#define astXmlGetValue(this,report) astXmlGetValue_(astXmlCheckObject(this,0),report,STATUS_PTR)
+#define astXmlGetAttributeValue(this,name) astXmlGetAttributeValue_(astXmlCheckElement(this,0),name,STATUS_PTR)
+#define astXmlGetNattr(this) astXmlGetNattr_(astXmlCheckElement(this,0),STATUS_PTR)
+#define astXmlGetNitem(this) astXmlGetNitem_(astXmlCheckElement(this,0),STATUS_PTR)
+#define astXmlGetItem(this,item) astXmlGetItem_(astXmlCheckElement(this,0),item,STATUS_PTR)
+#define astXmlGetAttributeValue(this,name) astXmlGetAttributeValue_(astXmlCheckElement(this,0),name,STATUS_PTR)
+#define astXmlGetTag(this,opening) astXmlGetTag_(astXmlCheckObject(this,0),opening,STATUS_PTR)
+#define astXmlGetURI(this) astXmlGetURI_(astXmlCheckObject(this,0),STATUS_PTR)
+#define astXmlFormat(this) astXmlFormat_(astXmlCheckObject(this,0),STATUS_PTR)
+#define astXmlShow(this) astXmlShow_(astXmlCheckObject(this,0),STATUS_PTR)
+#define astXmlRemoveItem(this) astXmlRemoveItem_(astXmlCheckContentItem(this,0),STATUS_PTR)
+#define astXmlRemoveAttr(this,name,prefix) astXmlRemoveAttr_(astXmlCheckElement(this,0),name,prefix,STATUS_PTR)
+#define astXmlRemoveURI(this,prefix) astXmlRemoveURI_(astXmlCheckElement(this,0),prefix,STATUS_PTR)
+#define astXmlReadDocument(doc,is_wanted,skip,source,data) astXmlReadDocument_(doc,is_wanted,skip,source,data,STATUS_PTR)
+#define astXmlInsertElement(this,elem) astXmlInsertElement_(astXmlCheckElement(this,0),astXmlCheckElement(elem,0),STATUS_PTR)
+#define astXmlPurge(this) astXmlPurge_(astXmlCheckParent(this,1),STATUS_PTR)
+#define astXmlSetXmlDec(this,text) astXmlSetXmlDec_(astXmlCheckDocument(this,0),text,STATUS_PTR)
+#define astXmlSetDTDec(this,text1,text2,text3) astXmlSetDTDec_(astXmlCheckDocument(this,0),text1,text2,text3,STATUS_PTR)
+#define astXmlCheckType(this,type) astXmlCheckType_(this,type,STATUS_PTR)
+#define astXmlCopy(this) astXmlCopy_(astXmlCheckObject(this,1),STATUS_PTR)
+
+#ifdef DEBUG
+#define astXmlTrace(show) astXmlTrace_(show)
+#endif
+
+#endif
+
+
+
diff --git a/ast/xmlchan.c b/ast/xmlchan.c
new file mode 100644
index 0000000..9dc2653
--- /dev/null
+++ b/ast/xmlchan.c
@@ -0,0 +1,14120 @@
+/*
+*class++
+*  Name:
+*     XmlChan
+
+*  Purpose:
+*     I/O Channel using XML to represent Objects.
+
+*  Constructor Function:
+c     astXmlChan
+f     AST_XMLCHAN
+
+*  Description:
+*     A XmlChan is a specialised form of Channel which supports XML I/O
+*     operations. Writing an Object to an XmlChan (using
+c     astWrite) will, if the Object is suitable, generate an
+f     AST_WRITE) will, if the Object is suitable, generate an
+*     XML description of that Object, and reading from an XmlChan will
+*     create a new Object from its XML description.
+*
+*     Normally, when you use an XmlChan, you should provide "source"
+c     and "sink" functions which connect it to an external data store
+c     by reading and writing the resulting XML text. These functions
+f     and "sink" routines which connect it to an external data store
+f     by reading and writing the resulting XML text. These routines
+*     should perform any conversions needed between external character
+c     encodings and the internal ASCII encoding. If no such functions
+f     encodings and the internal ASCII encoding. If no such routines
+*     are supplied, a Channel will read from standard input and write
+*     to standard output.
+*
+*     Alternatively, an XmlChan can be told to read or write from
+*     specific text files using the SinkFile and SourceFile attributes,
+*     in which case no sink or source function need be supplied.
+
+*  Inheritance:
+*     The XmlChan class inherits from the Channel class.
+
+*  Attributes:
+*     In addition to those attributes common to all Channels, every
+*     XmlChan also has the following attributes:
+*
+*     - XmlFormat: System for formatting Objects as XML
+*     - XmlLength: Controls output buffer length
+*     - XmlPrefix: The namespace prefix to use when writing
+
+*  Functions:
+c     The XmlChan class does not define any new functions beyond those
+f     The XmlChan class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: David Berry (Starlink)
+
+*  History:
+*     10-OCT-2003 (DSB):
+*        Original version.
+*     6-FEB-2004 (DSB):
+*        Added XmlPrefix and XmlFormat attributes.
+*     10-FEB-2004 (DSB):
+*        - Added debug conditional code to keep track of memory leaks.
+*        - Fixed bug which prevented more than 1 object being read from
+*        an XmlChan.
+*     7-DEC-2005 (DSB):
+*        Free memory allocated by calls to astReadString.
+*     12-FEB-2010 (DSB):
+*        Represent AST__BAD externally using the string "<bad>".
+*class--
+
+* Further STC work:
+*     - Speed up general STC processing (a lot of time seems to be spent
+*     simplifying things)
+*     - Document (including a complete description of what is and is not
+*     supported in the reference docs for the XmlFormat attribute).
+*     - Produce a schema describing the format which can in fact be read by
+*     AST.
+*     - Look at Jonathan McDowell's mini-STC schema (also STC stuff in
+*     spectral data model)
+*     - Web services. Read only: test STCs for overlap, test points for
+*     inclusion/exclusion, plot a mask over an image, verification (can AST
+*     read it & does it generate warnings?). Read/Write: convert FITS to STC,
+*     transform STC into a new coord system.
+*     - Add support for writing as well as reading
+*     - Modify Stc... constructors to check that the supplied Frame is suitable.
+*     - What about multiple AstroCoordFrames and AstroCoordAreas in a STC?
+*     - Add support for generic CoordFrames
+*     - What should be done with pixel coords info within STC?
+*     - Extend coverage (e.g. to 3D space frames, etc)
+
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS XmlChan
+
+/* The XML element name used to store an AST attribute setting */
+#define ATTR "_attribute"
+
+/* The XML element name used for an AST "isa" element */
+#define ISA "_isa"
+
+/* The XML attribute name which holds the name of the AST class which
+   defines the item contained in the element. */
+#define DEFINEDBY "definedby"
+
+/* The XML attribute name which holds the name of the AST attribute */
+#define NAME "name"
+
+/* The XML attribute name which holds the value of the AST attribute */
+#define VALUE "value"
+
+/* The XML attribute name which indicates if the AST attribute value is a
+   default value. */
+#define DEFAULT "default"
+
+/* The XML attribute name which indicates if the AST attribute value was
+   originally a string value. */
+#define QUOTED "quoted"
+
+/* The XML attribute name which holds a description of the AST attribute. */
+#define DESC "desc"
+
+/* The XML attribute name which holds the label associated with an AST
+   Object (if any). */
+#define LABEL "label"
+
+/* A string used to indicate atrue attribute value */
+#define TRUE "true"
+
+/* Format identifiers and strings */
+#define UNKNOWN_FORMAT  -1
+#define NATIVE_FORMAT    0
+#define QUOTED_FORMAT    1
+#define IVOA_FORMAT      2
+#define MAX_FORMAT       2
+#define UNKNOWN_STRING   "UNKNOWN"
+#define NATIVE_STRING    "NATIVE"
+#define QUOTED_STRING    "QUOTED"
+#define IVOA_STRING      "IVOA"
+
+/* Values representing message severities. */
+#define WARNING 0
+#define FAILURE 1
+#define RESET 2
+
+/* Known IVOA namespaces. When a new name is added, update the FindIVOAClass
+   function. */
+#define STC_URI "urn:nvo-stc"
+
+/* Known IVOA Classes and attributes. When a new name is added, it may be
+   necessary to update the FindIVOAClass function. */
+#define STC_RESOURCE_PROFILE     "STCResourceProfile"
+#define SEARCH_LOCATION          "SearchLocation"
+#define OBSERVATION_LOCATION     "ObservationLocation"
+#define OBSERVATORY_LOCATION     "ObservatoryLocation"
+#define CATALOG_ENTRY_LOCATION   "CatalogEntryLocation"
+#define OBS_DATA_LOCATION        "ObsDataLocation"
+#define ASTRO_COORD_SYSTEM       "AstroCoordSystem"
+#define ASTRO_COORD_AREA         "AstroCoordArea"
+#define ASTRO_COORDS             "AstroCoords"
+#define TIME_FRAME               "TimeFrame"
+#define SPACE_FRAME              "SpaceFrame"
+#define SPECTRAL_FRAME           "SpectralFrame"
+#define REDSHIFT_FRAME           "RedshiftFrame"
+#define DOPPLER_DEFINITION       "DopplerDefinition"
+
+/* Returns string "an" or "a" depending on whether the first character of
+   the supplied string is a vowel or not. */
+#define ANA(t) (t?(strchr("AaEeIiOoUu",t[0])?"an":"a"):"")
+
+/* String used to represent AST__BAD externally. */
+#define BAD_STRING "<bad>"
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "object.h"              /* Base Object class */
+#include "frame.h"               /* Coordinate Frames */
+#include "timeframe.h"           /* Time coordinate Frames */
+#include "cmpframe.h"            /* Coordinate Frames */
+#include "skyframe.h"            /* Celestial coordinate Frames */
+#include "specframe.h"           /* Spectral coordinate Frames */
+#include "region.h"              /* Regions within coordinate Frames */
+#include "ellipse.h"             /* Ellipses within coordinate Frames */
+#include "pointlist.h"           /* Points within coordinate Frames */
+#include "polygon.h"             /* Polygons within coordinate Frames */
+#include "circle.h"              /* Circles within coordinate Frames */
+#include "keymap.h"              /* Mapping of keys to values */
+#include "channel.h"             /* Interface for parent class */
+#include "xmlchan.h"             /* Interface definition for this class */
+#include "loader.h"              /* Interface to the global loader */
+#include "object.h"              /* Base Object class */
+#include "wcsmap.h"              /* Angular conversion constants */
+#include "xml.h"                 /* AST XML facilities */
+#include "erfa.h"                /* ERFA functions */
+#include "stcresourceprofile.h"  /* IVOA StcResourceProfile class */
+#include "stcsearchlocation.h"   /* IVOA SearchLocation class */
+#include "stccatalogentrylocation.h"/* IVOA CatalogEntryLocation class */
+#include "stcobsdatalocation.h"  /* IVOA ObsDataLocation class */
+#include "nullregion.h"          /* Null regions */
+#include "interval.h"            /* Axis intervals */
+#include "box.h"                 /* Box regions */
+#include "cmpregion.h"           /* Compound regions */
+#include "prism.h"               /* Prism regions */
+#include "unitmap.h"             /* Unit Mappings */
+#include "unit.h"                /* Unit handling utilities */
+#include "pal.h"                 /* slalib functions */
+#include "globals.h"             /* Thread-safe global data access */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <ctype.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Type Definitions */
+/* ================ */
+
+/* A type for functions which read an IVOA element and return a
+   corresponding AST Object. */
+typedef AstObject *(*IVOAReader)( AstXmlChan *, AstXmlElement *, int * );
+
+/* A structure to hold the result of scanning the content of an IVOA
+   element.*/
+typedef struct IVOAScan {
+   int n;           /* Number of element names described by this structure */
+   int *count;      /* Array holding number of each element name found */
+   AstXmlElement ***el;  /* Array holding pointers to each element found */
+} IVOAScan;
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+static int (* parent_getfull)( AstChannel *, int * );
+static int (* parent_getcomment)( AstChannel *, int * );
+static int (* parent_getindent)( AstChannel *, int * );
+
+/* Text values used to represent XmlFormat values externally. These
+   should be in the order defined by the associated constants above. */
+static const char *xformat[3] = { NATIVE_STRING, QUOTED_STRING, IVOA_STRING };
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->IsUsable_This = NULL; \
+   globals->GetAttrib_Buff[ 0 ] = 0; \
+   globals->GetNextChar_C = NULL; \
+   globals->GetNextChar_Buf = NULL;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(XmlChan)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(XmlChan,Class_Init)
+#define class_vtab astGLOBAL(XmlChan,Class_Vtab)
+#define isusable_this  astGLOBAL(XmlChan,IsUsable_This)
+#define getattrib_buff astGLOBAL(XmlChan,GetAttrib_Buff)
+#define getnextchar_c astGLOBAL(XmlChan,GetNextChar_C)
+#define getnextchar_buf astGLOBAL(XmlChan,GetNextChar_Buf)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+/* An XmlChan pointer use to communicate with the IsUsable function. */
+static AstXmlChan *isusable_this = NULL;
+
+/* Buffer returned by GetAttrib. */
+static char getattrib_buff[ 51 ];
+
+/* Variables used in GetNextChar */
+static char *getnextchar_c = NULL;    /* Pointer to next character to read */
+static char *getnextchar_buf = NULL;  /* Pointer to previously read text */
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstXmlChanVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstXmlChan *astXmlChanForId_( const char *(*)( void ),
+                           char *(*)( const char *(*)( void ), int * ),
+                           void (*)( const char * ),
+                           void (*)( void (*)( const char * ), const char *, int * ),
+                           const char *, ... );
+AstXmlChan *astXmlChanId_( const char *(* source)( void ),
+                             void (* sink)( const char * ),
+                             const char *options, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstObject *AstroCoordSystemReader( AstXmlChan *, AstXmlElement *, int * );
+static AstObject *MakeAstFromXml( AstXmlChan *, AstXmlElement *, int * );
+static AstObject *ObsDataLocationReader( AstXmlChan *, AstXmlElement *, int * );
+static AstObject *Read( AstChannel *, int * );
+static AstObject *ReadObject( AstChannel *, const char *, AstObject *, int * );
+static AstObject *RedshiftFrameReader( AstXmlChan *, AstXmlElement *, int * );
+static AstObject *SpaceFrameReader( AstXmlChan *, AstXmlElement *, int * );
+static AstObject *SpectralFrameReader( AstXmlChan *, AstXmlElement *, int * );
+static AstObject *StcMetadataReader( AstXmlChan *, AstXmlElement *, int * );
+static AstObject *TimeFrameReader( AstXmlChan *, AstXmlElement *, int * );
+static AstPointList *ObservatoryLocationReader( AstXmlChan *, AstXmlElement *, AstStcObsDataLocation *, int * );
+static AstRegion *AllSkyReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *AstroCoordAreaReader( AstXmlChan *, AstXmlElement *, AstFrame *, AstRegion *[4], int, AstKeyMap **, int * );
+static AstRegion *BoxReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *CircleReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *ConstraintReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *ConvexReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *Coord2VecIntervalReader( AstXmlChan *, AstXmlElement *, const char *, AstFrame *, int * );
+static AstRegion *Coord3VecIntervalReader( AstXmlChan *, AstXmlElement *, const char *, AstFrame *, int * );
+static AstRegion *CoordScalarIntervalReader( AstXmlChan *, AstXmlElement *, const char *, AstFrame *, int * );
+static AstRegion *EllipseReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *IntersectionReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *NegationReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *PolygonReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *Position2DReader( AstXmlChan *, AstXmlElement *, AstFrame *, double *, AstKeyMap **, int * );
+static AstRegion *PositionIntervalReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *RedshiftIntervalReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *RedshiftReader( AstXmlChan *, AstXmlElement *, AstFrame *, AstKeyMap **, int * );
+static AstRegion *StcRegionReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *RegionReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *SpectralIntervalReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *SpectralReader( AstXmlChan *, AstXmlElement *, AstFrame *, double *, AstKeyMap **, int * );
+static AstRegion *SphereReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstRegion *TimeIntervalReader( AstXmlChan *, AstXmlElement *, AstTimeFrame *, int * );
+static AstRegion *TimeReader( AstXmlChan *, AstXmlElement *, AstTimeFrame *, double *, AstKeyMap **, int * );
+static AstRegion *UnionReader( AstXmlChan *, AstXmlElement *, AstFrame *, int * );
+static AstSystemType RedshiftSys( AstXmlChan *, AstXmlElement *, char **, int, int * );
+static AstSystemType SpecSys( AstXmlChan *, AstXmlElement *, const char *, int, int * );
+static AstXmlElement *FindAttribute( AstXmlChan *, const char *, int * );
+static AstXmlElement *FindElement( AstXmlChan *, AstXmlElement *, const char *, int * );
+static AstXmlElement *FindObject( AstXmlChan *, const char *, int * );
+static AstXmlElement *MakePos2D( AstXmlChan *, AstXmlElement *, int * );
+static AstXmlElement *ReadXmlText( AstXmlChan *, int * );
+static AstXmlElement *Remove( AstXmlChan *, AstXmlElement *, int * );
+static IVOAReader FindIVOAClass( AstXmlElement *, int *, int * );
+static IVOAScan *FreeIVOAScan( IVOAScan *, int * );
+static IVOAScan *ScanIVOAElement( AstXmlChan *, AstXmlElement *, int, const char *[], int[], int[], int * );
+static char *ReadString( AstChannel *, const char *, const char *, int * );
+static char *SourceWrap( const char *(*)( void ), int * );
+static char GetNextChar( void *, int * );
+static const char *FindNextIsA( AstXmlElement *, int, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );
+static const char *GetTag( AstXmlObject *, int, int * );
+static double  AstronTimeReader( AstXmlChan *, AstXmlElement *, AstTimeFrame *, int * );
+static double AttrValueD( AstXmlChan *, AstXmlElement *, const char *, double, int * );
+static double ElemValueD( AstXmlChan *, AstXmlElement *, double, int * );
+static double Error2PAReader( AstXmlChan *, AstXmlElement *, double *, int * );
+static double MakeMJD( AstTimeFrame *, double, int * );
+static double PosAngleReader( AstXmlChan *, AstXmlElement *, int * );
+static double ReadDouble( AstChannel *, const char *, double, int * );
+static int AstroCoordsReader( AstXmlChan *, AstXmlElement *, AstFrame *, AstRegion *[4], AstKeyMap **, int * );
+static int AttrValueB( AstXmlChan *, AstXmlElement *, const char *, int, int * );
+static int AttrValueI( AstXmlChan *, AstXmlElement *, const char *, int, int * );
+static int ElemListD( AstXmlChan *, AstXmlElement *, int, double *, int * );
+static int FindString( int, const char *[], const char *, const char *, const char *, const char *, int * );
+static int GetComment( AstChannel *, int * );
+static int GetFull( AstChannel *, int * );
+static int GetIndent( AstChannel *, int * );
+static int IsUsable( AstXmlElement *, int * );
+static int ReadInt( AstChannel *, const char *, int, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int Use( AstXmlChan *, int, int, int * );
+static int Ustrcmp( const char *, const char *, int * );
+static int Ustrncmp( const char *, const char *, size_t, int * );
+static int VertexReader( AstXmlChan *, AstXmlElement *, double *, double *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void Copy( const AstObject *, AstObject *, int * );
+static void Delete( AstObject *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void FillAndLims( AstXmlChan *, AstXmlElement *, AstRegion *, int * );
+static void OutputText( AstXmlChan *, const char *, int, int * );
+static void ReCentreAnc( AstRegion *, int, AstKeyMap **, int * );
+static void ReadClassData( AstChannel *, const char *, int * );
+static void Report( AstXmlChan *, AstXmlElement *, int, const char *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SinkWrap( void (*)( const char * ), const char *, int * );
+static void WriteBegin( AstChannel *, const char *, const char *, int * );
+static void WriteDouble( AstChannel *, const char *, int, int, double, const char *, int * );
+static void WriteEnd( AstChannel *, const char *, int * );
+static void WriteInt( AstChannel *, const char *, int, int, int, const char *, int * );
+static void WriteIsA( AstChannel *, const char *, const char *, int * );
+static void WriteObject( AstChannel *, const char *, int, int, AstObject *, const char *, int * );
+static void WriteString( AstChannel *, const char *, int, int, const char *, const char *, int * );
+static AstTimeScaleType TimeScaleReader( AstXmlChan *, AstXmlElement *, int * );
+
+static int TestXmlLength( AstXmlChan *, int * );
+static void ClearXmlLength( AstXmlChan *, int * );
+static void SetXmlLength( AstXmlChan *, int, int * );
+static int GetXmlLength( AstXmlChan *, int * );
+
+static int TestXmlFormat( AstXmlChan *, int * );
+static void ClearXmlFormat( AstXmlChan *, int * );
+static void SetXmlFormat( AstXmlChan *, int, int * );
+static int GetXmlFormat( AstXmlChan *, int * );
+
+static int TestXmlPrefix( AstXmlChan *, int * );
+static void ClearXmlPrefix( AstXmlChan *, int * );
+static void SetXmlPrefix( AstXmlChan *, const char *, int * );
+static const char * GetXmlPrefix( AstXmlChan *, int * );
+
+/* Member functions. */
+/* ================= */
+
+static AstRegion *AllSkyReader( AstXmlChan *this, AstXmlElement *elem,
+                                AstFrame *frm, int *status ){
+/*
+*  Name:
+*     AllSkyReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA AllSky element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *AllSkyReader( AstXmlChan *this, AstXmlElement *elem,
+*                              AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     AllSky element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA AllSky element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstRegion *new;               /* Pointer to returned Region */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Create a negated NullRegion (this is a boundless Region which includes
+   all points in the Frame). */
+   new = (AstRegion *) astNullRegion( frm, NULL, "negated=1", status );
+
+/* Get any fill factor from the element and assign to the returned Region. */
+   FillAndLims( this, elem, new, status );
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstRegion *AstroCoordAreaReader( AstXmlChan *this, AstXmlElement *elem,
+                                        AstFrame *frm, AstRegion *uncs[4],
+                                        int nanc, AstKeyMap **ancs, int *status ) {
+/*
+*  Name:
+*     AstroCoordAreaReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA AstroCoordArea element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *AstroCoordAreaReader( AstXmlChan *this, AstXmlElement *elem,
+*                                      AstFrame *frm, AstRegion *uncs[4],
+*                                      int nanc, AstKeyMap **ancs, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     AstroCoordArea element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA AstroCoordArea element. May be NULL, in
+*        which case a NullRegion is returned.
+*     frm
+*        The Frame in which the returned Region is to be defined. If
+*        Units or reference values (Epoch, RestFreq, RefRA, etc) are not set
+*        for any axes, then they will be set by this function if possible.
+*     uncs
+*        Array holding pointers to the uncertainty Regions to be associated
+*        with each of the four STC domains (space, time, spectral, redshift).
+*        NULL should be suppied in any element for which no uncertainty is
+*        available.
+*     nanc
+*        Number of KeyMap pointers stored in "ancs"
+*     ancs
+*        Pointer to an array of "nanc" elements, each being a pointer to
+*        a KeyMap. Each one describes the ancilary information in an
+*        AstroCoords element associated with the AstroCoordsArea decribed
+*        by "region". Each KeyMap has elements with keys AST__STCERROR,
+*        AST__STCRES, AST__STCSIZE, AST__STCPIXSZ, AST__STCVALUE each of
+*        which holds a pointer to a Region.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+*/
+
+/* Local Variables: */
+   AstRegion *r;
+   AstFrame *cfrm;
+   AstFrame *fr;
+   AstFrame *pfrm;
+   AstFrame *red_frame;
+   AstFrame *space_frame;
+   AstFrame *spec_frame;
+   AstFrameSet *fs;
+   AstMapping *map;
+   AstObject *o;
+   AstRegion **red_list;
+   AstRegion **spec_list;
+   AstRegion **space_list;
+   AstRegion **time_list;
+   AstRegion *new;
+   AstRegion *reg;
+   AstRegion *rred;
+   AstRegion *rspec;
+   AstRegion *rspace;
+   AstRegion *rtime;
+   AstRegion *sum;
+   AstRegion *tmp;
+   AstTimeFrame *time_frame;
+   IVOAScan *scan;
+   char *decset;
+   char *raset;
+   char buff[ DBL_DIG + 30 ];
+   char setting[ 100 ];
+   const char *dom;
+   const char *id;
+   const char *names[4];
+   const char *name;
+   const char *old_units;
+   const char *text;
+   double decref;
+   double lbnd[2];
+   double raref;
+   double space_val[2];
+   double spec_val;
+   double time_val;
+   double ubnd[2];
+   int i;
+   int ianc;
+   int ired;
+   int ispace;
+   int ispec;
+   int itime;
+   int k;
+   int l;
+   int max[4];
+   int min[4];
+   int nax;
+   int nred;
+   int nspace;
+   int nspec;
+   int ntime;
+   int paxis;
+
+   static const char *key[ 5 ] = { AST__STCERROR,
+                                   AST__STCRES,
+                                   AST__STCSIZE,
+                                   AST__STCPIXSZ,
+                                   AST__STCVALUE };
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If null AstroCoordArea element has been supplied, return a NullRegion. */
+   if( !elem ) {
+      new = (AstRegion *) astNullRegion( frm, NULL, "", status );
+
+/* Otherwise, create a Region of suitable class. */
+   } else {
+
+/* First identify the individual Frames within the supplied Frame. Current
+   implementation for spatial axes is limited to celestial longitude and
+   latitude. */
+      space_frame = NULL;
+      spec_frame = NULL;
+      red_frame = NULL;
+      time_frame = NULL;
+
+      nax = astGetNaxes( frm );
+      for( i = 0; i < nax; i++ ) {
+         astPrimaryFrame( frm, i, &pfrm, &paxis );
+         dom = astGetDomain( pfrm );
+         if( !strcmp( dom, "SKY" ) ) {
+            if( !space_frame ) {
+               space_frame = astClone( pfrm );
+            } else if( pfrm != space_frame) {
+               Report( this, elem, FAILURE, "contains more than 2 spatial axes", status );
+            }
+
+         } else if( !strcmp( dom, "TIME" ) ) {
+            if( !time_frame ) {
+               if( astIsATimeFrame( pfrm ) ) {
+                  time_frame = (AstTimeFrame *) astClone( pfrm );
+               } else if( astOK ) {
+                  astError( AST__INTER, "AstroCoordAreaReader(XmlChan): %s "
+                            "supplied where TimeFrame expected (internal "
+                            "AST programming error).", status, astGetClass( pfrm ) );
+               }
+            } else {
+               Report( this, elem, FAILURE, "contains more than 1 time axis", status );
+            }
+
+         } else if( !strcmp( dom, "SPECTRUM" ) ) {
+            if( !spec_frame ) {
+               spec_frame = astClone( pfrm );
+            } else {
+               Report( this, elem, FAILURE, "contains more than 1 spectral axis", status );
+            }
+
+         } else if( !strcmp( dom, "REDSHIFT" ) ) {
+            if( !red_frame ) {
+               red_frame = astClone( pfrm );
+            } else {
+               Report( this, elem, FAILURE, "contains more than 1 redshift axis", status );
+            }
+
+         } else {
+            Report( this, elem, FAILURE, "contains axes for an unsupported domain", status );
+         }
+         pfrm = astAnnul( pfrm );
+      }
+
+/* Search the supplied element for the required sub-elements. */
+      names[ 0 ] = "Sphere|PositionInterval|Region";
+      names[ 1 ] = "TimeInterval";
+      names[ 2 ] = "SpectralInterval";
+      names[ 3 ] = "RedshiftInterval";
+      min[ 0 ] = 0;
+      min[ 1 ] = 0;
+      min[ 2 ] = 0;
+      min[ 3 ] = 0;
+      max[ 0 ] = INT_MAX;
+      max[ 1 ] = INT_MAX;
+      max[ 2 ] = INT_MAX;
+      max[ 3 ] = INT_MAX;
+      scan = ScanIVOAElement( this, elem, 4, names, min, max, status );
+
+/* If succesfull.. */
+      if( scan ) {
+
+/* Create Regions for all the SpatialIntervals found in the supplied element. */
+         space_val[ 0 ] = AST__BAD;
+         space_val[ 1 ] = AST__BAD;
+         nspace = scan->count[ 0 ];
+         space_list = astMalloc( sizeof(AstRegion *)*(size_t)nspace );
+         if( space_list ) {
+            for( ispace = 0; ispace < nspace; ispace++ ) {
+               name = astXmlGetName( scan->el[ 0 ][ ispace ] );
+               if( !strcmp( name, "Sphere" ) ) {
+                  space_list[ ispace ] = SphereReader( this,
+                                                       scan->el[ 0 ][ ispace ],
+                                                       space_frame, status );
+               } else if( !strcmp( name, "PositionInterval" ) ) {
+                  space_list[ ispace ] = PositionIntervalReader( this,
+                                                       scan->el[ 0 ][ ispace ],
+                                                       space_frame, status );
+               } else if( !strcmp( name, "Region" ) ) {
+                  space_list[ ispace ] = StcRegionReader( this,
+                                                       scan->el[ 0 ][ ispace ],
+                                                       space_frame, status );
+               } else if( astOK ) {
+                  astError( AST__INTER, "AstroCoordAreaReader(XmlChan): "
+                            "SpatialInterval type %s not yet supported "
+                            "(AST internal programming error).", status, name );
+                  break;
+               }
+
+/* Store any uncertainty region.*/
+               if( uncs[ 0 ] ) astSetUnc( space_list[ ispace ], uncs[ 0 ] );
+
+            }
+
+/* If the spatial region is a single point we will use the point as the
+   reference position for any SpecFrames which are created. If there is
+   just one spatial interval, and if it is bounded. and if the bounds are
+   equal on both axes, note the mean position. */
+            if( nspace == 1 ){
+               if( astGetBounded( space_list[ 0 ] ) ) {
+                  astGetRegionBounds( space_list[ 0 ], lbnd, ubnd );
+                  if( astEQUAL( lbnd[ 0 ], ubnd[ 0 ] ) &&
+                      astEQUAL( lbnd[ 1 ], ubnd[ 1 ] ) ) {
+                     space_val[ 0 ] = 0.5*( lbnd[ 0 ] + ubnd[ 0 ] );
+                     space_val[ 1 ] = 0.5*( lbnd[ 1 ] + ubnd[ 1 ] );
+                  }
+               }
+            }
+         }
+
+/* Create Regions for all the TimeIntervals found in the supplied element. */
+         time_val = AST__BAD;
+         ntime = scan->count[ 1 ];
+         time_list = astMalloc( sizeof(AstRegion *)*(size_t)ntime );
+         if( time_list ) {
+            for( itime = 0; itime < ntime; itime++ ) {
+               time_list[ itime ] = TimeIntervalReader( this,
+                                                       scan->el[ 1 ][ itime ],
+                                                       time_frame, status );
+
+/* Store any uncertainty region. Transfer the System and TimeOrigin
+   values from the time region to the time uncertainty, if set. */
+               if( uncs[ 1 ] ) {
+
+                  if( astTestSystem( time_frame ) &&
+                      astTestTimeOrigin( time_frame ) ) {
+
+                     sprintf( setting, "System=%s",
+                              astGetC( time_frame, "System" ) );
+                     astRegSetAttrib( uncs[ 1 ], setting, NULL );
+
+
+                     if( astTestUnit( time_frame, 0 ) ) {
+                        old_units = astGetUnit( time_frame, 0 );
+                        old_units = astStore( NULL, old_units,
+                                              strlen( old_units ) + 1 );
+                     } else {
+                        old_units = NULL;
+                     }
+
+                     astSetUnit( time_frame, 0, astGetUnit( uncs[ 1 ], 0 ) );
+
+                     sprintf( setting, "TimeOrigin=%s",
+                              astGetC( time_frame, "TimeOrigin" ) );
+                     astRegSetAttrib( uncs[ 1 ], setting, NULL );
+
+                     if( old_units ) {
+                        astSetUnit( time_frame, 0, old_units );
+                        old_units = astFree( (void *) old_units );
+                     } else {
+                        astClearUnit( time_frame, 0 );
+                     }
+
+                  }
+
+                  astSetUnc( time_list[ itime ], uncs[ 1 ] );
+               }
+            }
+
+/* Use the mid point as the Epoch for all Frames which are created. If
+   either limit is not specified, use the specified limit. */
+            if( ntime > 0 ){
+               astGetRegionBounds( time_list[ 0 ], lbnd, ubnd );
+               if( fabs( lbnd[ 0 ] ) != DBL_MAX && lbnd[ 0 ] != AST__BAD ){
+                  if( fabs( ubnd[ 0 ] ) != DBL_MAX && ubnd[ 0 ] != AST__BAD ){
+                     time_val = 0.5*( lbnd[ 0 ] + ubnd[ 0 ] );
+                  } else {
+                     time_val = lbnd[ 0 ];
+                  }
+               } else if( fabs( ubnd[ 0 ] ) != DBL_MAX && ubnd[ 0 ] != AST__BAD ){
+                  time_val = ubnd[ 0 ];
+               }
+            }
+         }
+
+/* Create Regions for all the SpectralIntervals found in the supplied element. */
+         spec_val = AST__BAD;
+         nspec = scan->count[ 2 ];
+         spec_list = astMalloc( sizeof(AstRegion *)*(size_t)nspec );
+         if( spec_list ) {
+            for( ispec = 0; ispec < nspec; ispec++ ) {
+               spec_list[ ispec ] = SpectralIntervalReader( this,
+                                                       scan->el[ 2 ][ ispec ],
+                                                       spec_frame, status );
+/* Store any uncertainty region.*/
+               if( uncs[ 2 ] ) astSetUnc( spec_list[ ispec ], uncs[ 2 ] );
+            }
+
+/* If the spectral region is a single point we will use the point as the
+   rest frequency for all RedShift Frames which are created. If there is just
+   one spectral interval, and if it is bounded. and if the bounds are equal,
+   note the mean spectral value. */
+            if( nspec == 1 ){
+               if( astGetBounded( spec_list[ 0 ] ) ) {
+                  astGetRegionBounds( spec_list[ 0 ], lbnd, ubnd );
+                  if( astEQUAL( lbnd[ 0 ], ubnd[ 0 ] ) ) {
+                     spec_val = 0.5*( lbnd[ 0 ] + ubnd[ 0 ] );
+                  }
+               }
+            }
+         }
+
+/* Create Regions for all the RedshiftIntervals found in the supplied element. */
+         nred = scan->count[ 3 ];
+         red_list = astMalloc( sizeof(AstRegion *)*(size_t)nred );
+         if( red_list ) {
+            for( ired = 0; ired < nred; ired++ ) {
+               red_list[ ired ] = RedshiftIntervalReader( this,
+                                                       scan->el[ 3 ][ ired ],
+                                                       red_frame, status );
+/* Store any uncertainty region.*/
+               if( uncs[ 3 ] ) astSetUnc( red_list[ ired ], uncs[ 3 ] );
+            }
+         }
+
+/* Free the can result structure.*/
+         scan = FreeIVOAScan( scan, status );
+
+/* If the spatial regions cover only a single point, convert it to FK5
+   J2000 and use it as the reference position for any SpecFrames (spectral or
+   redshift) unless values were inherited from the supplied Frame. If the
+   supplied Frame did not contain set values for these attributes, set them
+   now. Use astRegSetAttrib which applies the attribute setting to both
+   base and current Frame of the Region's FrameSet, and avoids re-mapping
+   the current Frame. */
+         if( astOK ) {
+            if( space_val[ 0 ] != AST__BAD && space_val[ 1 ] != AST__BAD ) {
+
+/* First need to convert to FK5 J2000 and format into a string for use with
+   astRegSetAttrib. Need to ensure that the Format and Digits attributes
+   are set to values which will result in no loss of precision in the
+   formatting and unformatting steps. */
+               fr = astCopy( space_frame );
+               astClear( fr, "Format(1),Format(2),Digits(1),Digits(2)" );
+               astSet( fr, "digits=%d,system=FK5,equinox=J2000", status, DBL_DIG);
+               fs = astConvert( space_frame, fr, "" );
+               fr = astAnnul( fr );
+               if( fs ) {
+                  astTran2( fs, 1, space_val, space_val + 1, 1, &raref, &decref );
+
+                  text = astFormat( fs, raref, 0 );
+                  l = text ? strlen( text ) : 0;
+                  raset = astMalloc( l + 10 );
+                  if( raset ) sprintf( raset, "refra=%s", text );
+
+                  text = astFormat( fs, decref, 1 );
+                  l = text ? strlen( text ) : 0;
+                  decset = astMalloc( l + 10 );
+                  if( decset ) sprintf( decset, "refdec=%s", text );
+
+                  fs = astAnnul( fs );
+
+/* Now set the FK5 J2000 values in the required Frames and Regions. */
+                  if( !spec_frame || !astTestRefRA( spec_frame ) ||
+                                     !astTestRefDec( spec_frame ) ) {
+                     for( ispec = 0; ispec < nspec; ispec++ ) {
+                        astRegSetAttrib( spec_list[ ispec ], raset, NULL );
+                        astRegSetAttrib( spec_list[ ispec ], decset, NULL );
+                     }
+
+                     if( spec_frame ) {
+                        astSetRefRA( (AstSpecFrame *) spec_frame, raref );
+                        astSetRefDec( (AstSpecFrame *) spec_frame, decref );
+                     }
+                  }
+
+                  if( !red_frame || !astTestRefRA( red_frame ) ||
+                                    !astTestRefDec( red_frame ) ) {
+                     for( ired = 0; ired < nred; ired++ ) {
+                        astRegSetAttrib( red_list[ ired ], raset, NULL );
+                        astRegSetAttrib( red_list[ ired ], decset, NULL );
+                     }
+
+                     if( red_frame ) {
+                        astSetRefRA( (AstSpecFrame *) red_frame, raref );
+                        astSetRefDec( (AstSpecFrame *) red_frame, decref );
+                     }
+                  }
+
+                  for( ianc = 0; ianc < nanc; ianc++ ) {
+                     for( k = 0; k < 5; k++ ) {
+                        if( astMapGet0A( ancs[ ianc ], key[ k ], &o ) ) {
+                           r = (AstRegion *) o;
+                           astRegSetAttrib( r, raset, NULL );
+                           astRegSetAttrib( r, decset, NULL );
+                           r = astAnnul( r );
+                        }
+                     }
+                  }
+
+/* Free resources. */
+                  if( raset ) raset = astFree( raset );
+                  if( decset ) decset = astFree( decset );
+
+               } else if( astOK ) {
+                  astError( AST__INTER, "AstroCoordAreaReader(XmlChan):"
+                            " Cannot convert spatial position to FK5 J2000" , status);
+               }
+            }
+
+/* If a time region was specified, use a typical value as the epoch for
+   all Frames. Call MakeMJD to convert "time_val" from the system of the
+   TimeFrame to an MJD (as required by the Frame Epoch attribute). Set
+   the value in both the returned Region and the supplied Frame. */
+            if( time_val != AST__BAD ) {
+               fr = astRegFrame( time_list[ 0 ] );
+               if( astIsATimeFrame( fr ) ) {
+                  time_val = MakeMJD( (AstTimeFrame *) fr, time_val, status );
+               } else if( astOK ) {
+                  astError( AST__INTER, "AstroCoordAreaReader(XmlChan): %s "
+                            "supplied where TimeFrame expected (internal "
+                            "AST programming error).", status, astGetClass( fr ) );
+               }
+               fr = astAnnul( fr );
+
+               sprintf( buff, "epoch= MJD %.*g", DBL_DIG, time_val );
+
+               if( !space_frame || !astTestEpoch( space_frame ) ) {
+                  for( ispace = 0; ispace < nspace; ispace++ ) {
+                     astRegSetAttrib( space_list[ ispace ], buff, NULL );
+                  }
+                  if( space_frame ) astSetEpoch( space_frame, time_val );
+               }
+
+               if( !spec_frame || !astTestEpoch( spec_frame ) ) {
+                  for( ispec = 0; ispec < nspec; ispec++ ) {
+                     astRegSetAttrib( spec_list[ ispec ], buff, NULL );
+                  }
+                  if( spec_frame ) astSetEpoch( spec_frame, time_val );
+               }
+
+               if( !red_frame || !astTestEpoch( red_frame ) ) {
+                  for( ired = 0; ired < nred; ired++ ) {
+                     astRegSetAttrib( red_list[ ired ], buff, NULL );
+                  }
+                  if( red_frame ) astSetEpoch( red_frame, time_val );
+               }
+
+               for( ianc = 0; ianc < nanc; ianc++ ) {
+                  for( k = 0; k < 5; k++ ) {
+                     if( astMapGet0A( ancs[ ianc ], key[ k ], &o ) ) {
+                        r = (AstRegion *) o;
+                        astRegSetAttrib( r, buff, NULL );
+                        r = astAnnul( r );
+                     }
+                  }
+               }
+
+            }
+
+/* If the spectral regions cover only a single point, format it with its
+   units so that the astSetAttrib function can convert it to Hz and use
+   it as the rest frequency for any redshift Frames. */
+            if( spec_val != AST__BAD && nred > 0 ) {
+
+               text = astGetUnit( spec_frame, 0 );
+               if( text ) sprintf( buff, "restfreq= %.*g %s", DBL_DIG,
+                                   spec_val, text );
+
+               if( !red_frame || !astTestRestFreq( red_frame ) ) {
+                  for( ired = 0; ired < nred; ired++ ) {
+                     astRegSetAttrib( red_list[ ired ], buff, NULL );
+                  }
+                  if( red_frame ) astSetAttrib( red_frame, buff );
+               }
+
+               for( ianc = 0; ianc < nanc; ianc++ ) {
+                  for( k = 0; k < 5; k++ ) {
+                     if( astMapGet0A( ancs[ ianc ], key[ k ], &o ) ) {
+                        r = (AstRegion *) o;
+                        astRegSetAttrib( r, buff, NULL );
+                        r = astAnnul( r );
+                     }
+                  }
+               }
+            }
+
+/* Create Regions corresponding to every possible combination of interval
+   on each axis type, and assemble the union of these into a CmpRegion (if
+   there is more than one). */
+            sum = NULL;
+
+/* Initialise indices of the sub-Frame intervals to use. */
+            ispace = 0;
+            itime = 0;
+            ispec = 0;
+            ired = 0;
+
+/* Loop over all possible combinations of time+space+spec+red intervals. */
+            while( 1 ) {
+               rspace = ( ispace < nspace ) ? space_list[ ispace ] : NULL;
+               rtime = ( itime < ntime ) ? time_list[ itime ] : NULL;
+               rspec = ( ispec < nspec ) ? spec_list[ ispec ] : NULL;
+               rred = ( ired < nred ) ? red_list[ ired ] : NULL;
+
+/* Prism Regions extrude a Region into higher dimensions, and the
+   extrusion is defined by an Interval. Spatial Regions are not
+   restricted to Intervals and so any spatial Region must be the first
+   Region to be included in the Prism (all the other axis types *are*
+   restricted to Intervals and so can be used to extrude the spatial
+   region). */
+               reg = rspace ? astClone( rspace ) : NULL;
+
+/* Now extrude this region (if any) into the time axis. */
+               if( rtime ) {
+                  if( reg ) {
+                     tmp = (AstRegion *) astPrism( reg, rtime, "", status );
+                     (void) astAnnul( reg );
+                     reg = tmp;
+                  } else {
+                     reg = astClone( rtime );
+                  }
+               }
+
+/* Now extrude this region (if any) into the spectral axis. */
+               if( rspec ) {
+                  if( reg ) {
+                     tmp = (AstRegion *) astPrism( reg, rspec, "", status );
+                     (void) astAnnul( reg );
+                     reg = tmp;
+                  } else {
+                     reg = astClone( rspec );
+                  }
+               }
+
+/* Now extrude this region (if any) into the redshift axis. */
+               if( rred ) {
+                  if( reg ) {
+                     tmp = (AstRegion *) astPrism( reg, rred, "", status );
+                     (void) astAnnul( reg );
+                     reg = tmp;
+                  } else {
+                     reg = astClone( rred );
+                  }
+               }
+
+
+/* If a Prism was created, add it into the CmpRegion which holds the
+   running sum of the union of all Prisms created so far. */
+               if( reg ) {
+                  if( !sum ) {
+                     sum = astClone( reg );
+                  } else {
+                     tmp = (AstRegion *) astCmpRegion( sum, reg, AST__OR, "", status );
+                     (void) astAnnul( sum );
+                     sum = tmp;
+                  }
+                  reg = astAnnul( reg );
+               }
+
+/* Increment the indices of the next set of sub-Frame Intervals to use.
+   Leave the while loop when all combinations have been done. */
+               if( ++ired >= nred ) {
+                  ired = 0;
+                  if( ++ispec >= nspec ) {
+                     ispec = 0;
+                     if( ++itime >= ntime ) {
+                        itime = 0;
+                        if( ++ispace >= nspace ) break;
+                     }
+                  }
+               }
+            }
+
+/* Simplify the total sum Region. */
+            tmp = astSimplify( sum );
+            (void) astAnnul( sum );
+            sum = tmp;
+
+/* The axes in this sum Region may not be in the correct order or units (i.e
+   in the order and units specified in the supplied Frame). So use
+   astConvert to get a Mapping from the Frame represented by the sum
+   Region to the supplied Frame. */
+            fs = astConvert( sum, frm, "" );
+            if( fs ) {
+
+/* Unless the Mapping is a UnitMap, remap the sum Region into the
+   supplied Frame using this Mapping. */
+               map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+               if( !astIsAUnitMap( map ) ) {
+                  new = astMapRegion( sum, map, frm );
+               } else {
+                  new = astClone( sum );
+               }
+
+               map = astAnnul( map );
+               fs = astAnnul( fs );
+
+            } else if( astOK ) {
+               astError( AST__INTER, "AstroCoordAreaReader(%s): Cannot "
+                         "convert from supplied Frame to internal Frame (AST "
+                         "internal programming error).", status, astGetClass( this ) );
+            }
+
+/* Transfer selected properties from the supplied Frame to the current Frame
+   of the returned Region. */
+            cfrm = astRegFrame( new );
+            if( astTestIdent( frm ) ) astSetIdent( cfrm, astGetIdent( frm ) );
+            if( astTestTitle( frm ) ) astSetTitle( cfrm, astGetTitle( frm ) );
+
+/* Ensure the Epoch is set correctly in the Region */
+            if( time_val != AST__BAD ) {
+               sprintf( buff, "epoch= MJD %.*g", DBL_DIG, time_val );
+               astRegSetAttrib( new, buff, NULL );
+            }
+
+/* Free resources. */
+            cfrm = astAnnul( cfrm );
+            sum = astAnnul( sum );
+         }
+
+         if( space_list ) {
+            for( i = 0; i < nspace; i++ ) space_list[ i ] = astAnnul( space_list[ i ]  );
+            space_list = astFree( space_list );
+         }
+
+         if( time_list ) {
+            for( i = 0; i < ntime; i++ ) time_list[ i ] = astAnnul( time_list[ i ]  );
+            time_list = astFree( time_list );
+         }
+
+         if( spec_list ) {
+            for( i = 0; i < nspec; i++ ) spec_list[ i ] = astAnnul( spec_list[ i ]  );
+            spec_list = astFree( spec_list );
+         }
+
+         if( red_list ) {
+            for( i = 0; i < nred; i++ ) red_list[ i ] = astAnnul( red_list[ i ]  );
+            red_list = astFree( red_list );
+         }
+
+      }
+
+      if( space_frame ) space_frame = astAnnul( space_frame );
+      if( time_frame ) time_frame = astAnnul( time_frame );
+      if( spec_frame ) spec_frame = astAnnul( spec_frame );
+      if( red_frame ) red_frame = astAnnul( red_frame );
+
+/* Get the ID attribute from the AstroCoordArea element and store in the
+   returned Region. */
+      id = astXmlGetAttributeValue( elem, "ID" );
+      if( id ) astSetIdent( new, id );
+
+   }
+
+/* If an error has occurred,annul the returned pointer. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static int AstroCoordsReader( AstXmlChan *this, AstXmlElement *elem,
+                              AstFrame *frm, AstRegion *uncs[4],
+                              AstKeyMap **anc, int *status ) {
+/*
+*  Name:
+*     AstroCoordsReader
+
+*  Purpose:
+*     Modify a Frame to take account of an IVOA AstroCoords element, and
+*     return an coordinate uncertainties.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int AstroCoordsReader( AstXmlChan *this, AstXmlElement *elem,
+*                            AstFrame *frm, AstRegion *uncs[4],
+*                            AstKeyMap **anc, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function modifies the supplied Frame object to incorporate the
+*     effects of the supplied AstroCoords element. It may also return
+*     Regions representing the bounds of the uncertainties in the four
+*     component coordinate Frames, depending on the contents of the
+*     AstroCoords element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA AstroCoords element.
+*     frm
+*        The Frame object to modify.
+*     uncs
+*        Array in which to return pointers to the uncertainty Regions to
+*        be associated with each of the four STC domains (space, time,
+*        spectral, redshift). NULL is returned in any element for which
+*        no uncertainty is specified within the supplied AstroCoords element.
+*     anc
+*        Address of a location at which to store the pointer to a newly
+*        created KeyMap holding ancillary information describing the
+*        AstroCoords element in the form required by constructors of AST
+*        Stc objects. A NULL pointer is returned if no usable ancillary
+*        information is found in the AstroCoords.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Non-zero if any non-NULL values have been returned in the "uncs"
+*     array. Zero otherwise.
+
+*/
+
+/* Local Variables: */
+   AstFrame *afrm;               /* Pointer to axis Frame */
+   AstFrame *gfrm;                /* Pointer to generic Frame */
+   AstFrame *pfrm;               /* Pointer to position Frame */
+   AstFrame *rfrm;               /* Pointer to redshift Frame */
+   AstFrame *sfrm;               /* Pointer to spectral Frame */
+   AstTimeFrame *tfrm;           /* Pointer to time Frame */
+   AstKeyMap *panc;              /* KeyMap holding spatial ancillary data */
+   AstKeyMap *ranc;              /* KeyMap holding redshift ancillary data */
+   AstKeyMap *sanc;              /* KeyMap holding spectral ancillary data */
+   AstKeyMap *tanc;              /* KeyMap holding temporal ancillary data */
+   AstObject *o;                 /* Pointer to object retrieved from KeyMap */
+   AstRegion *r;                 /* Individual ancillary Region */
+   AstRegion *t;                 /* Total extruded ancillary Region */
+   AstRegion *tt;                /* Temporary Region pointer */
+   AstXmlElement *el;            /* Pointer to Position2D element */
+   IVOAScan *scan;               /* Structure holding scan results */
+   char **anames;                /* Pointer to list of ancillary name pointers */
+   const char *dom;              /* Pointer to Domain attribute value */
+   const char *nam;              /* Pointer to ancillary Name string */
+   const char *names[4];         /* Names of the subelements to be searched for */
+   char buff[100];               /* Message buffer */
+   double epoch;                 /* Epoch */
+   double hi;                    /* High limit for zero-width interval */
+   double lo;                    /* Low limit for zero-width interval */
+   double pos[2];                /* Reference spatial position */
+   double rf;                    /* Rest frequency */
+   int axes[2];                  /* Indices of position axes */
+   int axis;                     /* Index of next axis to use */
+   int empty;                    /* Is returned KeyMap empty? */
+   int i;                        /* Loop count */
+   int isearth;                  /* Does the SkyFrame represent terrestrial lon/lat? */
+   int junk;                     /* Unused integer value */
+   int max[4];                   /* Max allowed occurrences of each name */
+   int min[4];                   /* Min allowed occurrences of each name */
+   int nax;                      /* Number of axes in supplied Frame */
+   int unc;                      /* Any uncertainty Regions found? */
+   int use;                      /* Use ancillary information? */
+
+   static const char *key[ 5 ] = { AST__STCERROR,
+                                   AST__STCRES,
+                                   AST__STCSIZE,
+                                   AST__STCPIXSZ,
+                                   AST__STCVALUE };
+/* Initialise */
+   unc = 0;
+   uncs[ 0 ] = NULL;
+   uncs[ 1 ] = NULL;
+   uncs[ 2 ] = NULL;
+   uncs[ 3 ] = NULL;
+   *anc = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return unc;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "Position2D|Position3D";
+   names[ 1 ] = "Time";
+   names[ 2 ] = "Spectral";
+   names[ 3 ] = "Redshift";
+   min[ 0 ] = 0;
+   min[ 1 ] = 0;
+   min[ 2 ] = 0;
+   min[ 3 ] = 0;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   max[ 2 ] = 1;
+   max[ 3 ] = 1;
+   scan = ScanIVOAElement( this, elem, 4, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Initialise pointers to component Frames */
+      pfrm = NULL;
+      tfrm = NULL;
+      sfrm = NULL;
+      rfrm = NULL;
+
+/* Initialise pointers to KeyMaps holding ancillary data. */
+      panc = NULL;
+      tanc = NULL;
+      sanc = NULL;
+      ranc = NULL;
+
+/* Allocate storage for an array of pointers to strings holding the Name
+   value for each axis. Initialise them to a null string. */
+      nax = astGetNaxes( frm );
+      anames = astMalloc( sizeof( char * )*(size_t)nax );
+      for( i = 0; i < nax; i++ ) anames[ i ] = NULL;
+
+/* Initialise the index of the next Frame axis to use. */
+      axis = 0;
+
+/* Check to see if the next 2 axes describe positions on the sky or earth
+   (see SpaceFrameReader). */
+      axes[ 0 ] = 0;
+      axes[ 1 ] = 1;
+      afrm = astPickAxes( frm, 2, axes, NULL );
+      dom = astGetDomain( afrm );
+      isearth = dom && ( !strcmp( dom, "GEO_D" ) ||
+                         !strcmp( dom, "GEO_C" ) );
+
+      if( isearth || ( dom && !strcmp( dom, "SKY" ) ) ){
+         astPrimaryFrame( frm, axis, &pfrm, &junk );
+         if( scan->count[ 0 ] ) {
+
+/* We currently also use SkyFrames to represent geographical long/lat used to
+   describe observatory positions. These may have 3D positions, in which
+   case we convert the 3D position to a 2D position by ignoring the 3rd axis
+   value (height). See SpaceFrameReader. */
+            el = MakePos2D( this, scan->el[ 0 ][ 0 ], status );
+
+/* Use the Position2D to create a Region describing the uncertainty in
+   the space axes of the Frame. Also create a KeyMap holding Regions
+   describing any ancillary information stored in the Position2D. */
+            uncs[ 0 ] = Position2DReader( this, el, pfrm, pos, &panc, status );
+            if( uncs[ 0 ] ) unc = 1;
+            el = astXmlDelete( el );
+
+/* If ancillary information was returned, extract the Name element, and
+   store it twice (once for each axis) in the "names" array. */
+            if( panc && astMapGet0C( panc, AST__STCNAME, &nam ) ) {
+               anames[ axis ] = astStore( NULL, nam, strlen( nam ) + 1 );
+               anames[ axis + 1 ] = astStore( NULL, nam, strlen( nam ) + 1 );
+            }
+         }
+
+/* Increment the axis index. */
+         axis += 2;
+
+/* If the supplied Frame has no sky frame, but we found a Position2D, then
+   report a warning and ignore the Position2D. */
+      } else if( scan->count[ 0 ] ) {
+         sprintf( buff, "contains a <%s> which is not being used.",
+                  astXmlGetName( scan->el[ 0 ][ 0 ] ) );
+         Report( this, elem, WARNING, buff, status );
+      }
+      afrm = astAnnul( afrm );
+
+/* Indicate we do not yet have an epoch to use. */
+      epoch = AST__BAD;
+
+/* Check to see if the Frame contains a time frame. It will be the next
+   axis if it does. */
+      afrm = astPickAxes( frm, 1, &axis, NULL );
+      dom = astGetDomain( afrm );
+      if( dom && !strcmp( dom, "TIME" ) ){
+         astPrimaryFrame( frm, axis, &gfrm, &junk );
+
+/* Report an error if it is not an AST TimeFrame. */
+         if( !astIsATimeFrame( gfrm ) && astOK ) {
+            astError( AST__INTER, "AstroCoordAreaReader(XmlChan): %s "
+                      "supplied where TimeFrame expected (internal "
+                      "AST programming error).", status, astGetClass( pfrm ) );
+         } else {
+            tfrm = (AstTimeFrame *) gfrm;
+         }
+
+/* Use any Time element to create a Region describing the uncertainty in the
+   time axis of the Frame. Also create a KeyMap holding Regions describing
+   any ancillary information stored in the Time element. */
+         if( scan->count[ 1 ] ) {
+            uncs[ 1 ] = TimeReader( this, scan->el[ 1 ][ 0 ], tfrm, &epoch,
+                                    &tanc, status );
+            if( uncs[ 1 ] ) unc = 1;
+
+/* If ancillary information was returned, extract the Name element, and
+   store it in the "names" array. */
+            if( tanc && astMapGet0C( tanc, AST__STCNAME, &nam ) ) {
+               anames[ axis ] = astStore( NULL, nam, strlen( nam ) + 1 );
+            }
+         }
+
+/* Increment the index of the next axis to use. */
+         axis++;
+
+/* If the supplied Frame has no time frame, but we found a Time element, then
+   report a warning and ignore the Time element. */
+      } else if( scan->count[ 1 ] ) {
+         Report( this, elem, WARNING, "contains a <Time> which is not needed", status );
+      }
+      afrm = astAnnul( afrm );
+
+/* Indicate we do not yet have a rest frequency to use with any redshift
+   axis. */
+      rf = AST__BAD;
+
+/* Check to see if the Frame contains a spectral frame. It will be the next
+   axis if it does. */
+      afrm = astPickAxes( frm, 1, &axis, NULL );
+      dom = astGetDomain( afrm );
+      if( dom && !strcmp( dom, "SPECTRUM" ) ){
+         astPrimaryFrame( frm, axis, &sfrm, &junk );
+
+/* Use any Spectral to create a Region describing the uncertainty in the
+   spectral axis of the Frame. If the Spectral contains a spectral value, the
+   first value will be returned so that it can be used  as the rest frequency
+   for any Redshift axis. It will be in units of Hz and will be AST__BAD if
+   the Spectral did not contain any spectral values. Also create a KeyMap
+   holding Regions describing any ancillary information stored in the
+   Spectral element. */
+         if( scan->count[ 2 ] ) {
+            uncs[ 2 ] = SpectralReader( this, scan->el[ 2 ][ 0 ], sfrm, &rf,
+                                        &sanc, status );
+            if( uncs[ 2 ] ) unc = 1;
+
+/* If ancillary information was returned, extract the Name element, and
+   store it in the "names" array. */
+            if( sanc && astMapGet0C( sanc, AST__STCNAME, &nam ) ) {
+               anames[ axis ] = astStore( NULL, nam, strlen( nam ) + 1 );
+            }
+         }
+
+/* Increment the index of the next axis to use. */
+         axis++;
+
+/* If the supplied Frame has no spectral frame, but we found a Spectral
+   element, then report a warning and ignore the Spectral element. */
+      } else if( scan->count[ 2 ] ) {
+         Report( this, elem, WARNING, "contains a <Spectral> which is not needed", status );
+      }
+      afrm = astAnnul( afrm );
+
+/* Check to see if the Frame contains a redshift frame. It will be the next
+   axis if it does. */
+      afrm = astPickAxes( frm, 1, &axis, NULL );
+      dom = astGetDomain( afrm );
+      if( dom && !strcmp( dom, "REDSHIFT" ) ){
+         astPrimaryFrame( frm, axis, &rfrm, &junk );
+
+/* Use any Redshift to create a Region describing the uncertainty in the
+   redshift axis of the Frame. Also create a KeyMap holding Regions describing
+   any ancillary information stored in the Redshift element. */
+         if( scan->count[ 3 ] ) {
+            uncs[ 3 ] = RedshiftReader( this, scan->el[ 3 ][ 0 ], rfrm,
+                                        &ranc, status );
+            if( uncs[ 3 ] ) unc = 1;
+
+/* If ancillary information was returned, extract the Name element, and
+   store it in the "names" array. */
+            if( ranc && astMapGet0C( ranc, AST__STCNAME, &nam ) ) {
+               anames[ axis ] = astStore( NULL, nam, strlen( nam ) + 1 );
+            }
+         }
+
+/* Increment the index of the next axis to use. */
+         axis++;
+
+/* If the supplied Frame has no redshift frame, but we found a Redshift
+   element, then report a warning and ignore the Redshift element. */
+      } else if( scan->count[ 3 ] ) {
+         Report( this, elem, WARNING, "contains a <Redshift> which is not needed", status );
+      }
+      afrm = astAnnul( afrm );
+
+/* Now assign fixed axis values (Epoch, RestFreq, etc) to the component
+   Frames of the supplied Frame. */
+      if( epoch != AST__BAD ) {
+         if( pfrm ) astSetEpoch( pfrm, epoch );
+         if( tfrm ) astSetEpoch( tfrm, epoch );
+         if( sfrm ) astSetEpoch( sfrm, epoch );
+         if( rfrm ) astSetEpoch( rfrm, epoch );
+         astSetEpoch( frm, epoch );
+      }
+
+      if( sfrm && pfrm && astIsASpecFrame( sfrm ) && astIsASkyFrame( pfrm ) &&
+          !isearth && pos[ 0 ] != AST__BAD && pos[ 1 ] != AST__BAD ) {
+         astSetRefPos( sfrm, pfrm, pos[ 0 ], pos[ 1 ] );
+      }
+
+      if( rfrm && astIsASpecFrame( rfrm ) && rf != AST__BAD ) {
+         astSetRestFreq( rfrm, rf );
+         if( pfrm && astIsASkyFrame( pfrm ) && !isearth &&
+             pos[ 0 ] != AST__BAD && pos[ 1 ] != AST__BAD ) {
+            astSetRefPos( rfrm, pfrm, pos[ 0 ], pos[ 1 ] );
+         }
+      }
+
+/* Now combine ancillary data for each component Frame into the total
+   Frame. */
+      *anc = astKeyMap( "", status );
+      if( *anc ) {
+         empty = 1;
+
+/* Store the Names element if at least one axis has a Name item. */
+         for( i = 0; i < nax; i++ ) {
+            if( !anames[ i ] ) anames[ i ] = astStore( NULL, "", 1 );
+         }
+
+         for( i = 0; i < nax; i++ ) {
+            if( empty && strlen( anames[ i ] ) > 0 ) {
+               astMapPut1C( *anc, AST__STCNAME, nax, (const char **) anames, NULL );
+               empty = 0;
+            }
+            anames[ i ] = astFree( anames[ i ] );
+         }
+
+/* Do each of the other items, all of which are described by a Region. */
+         lo = 0.0;
+         hi = 0.0;
+         for( i = 0; i < 5; i++ ) {
+
+/* Initialise a flag indicating that we have not yet found any non-null
+   information to store for this item. */
+            use = 0;
+
+/* Initialise a pointer to the Region describing the item extruded into
+   all axes. */
+            t = NULL;
+
+/* If there is a positional Frame, determine the Region describing the
+   intersection of the total Region with the position Frame. If none is
+   supplied use a zero width Interval as a flag that no information is
+   available. */
+            if( pfrm ) {
+               if( panc && astMapGet0A( panc, key[ i ], &o ) ) {
+                  t = (AstRegion *) o;
+                  use = 1;
+               } else {
+                  t = (AstRegion *) astInterval( pfrm, &lo, &hi, NULL, "", status );
+               }
+            }
+
+/* If there is a time Frame, determine the Region describing the intersection
+   of the total Region with the time Frame. If none is supplied use a zero
+   width Interval as a flag that no information is available. */
+            if( tfrm ) {
+               if( tanc && astMapGet0A( tanc, key[ i ], &o ) ) {
+                  r = (AstRegion *) o;
+                  use = 1;
+               } else {
+                  r = (AstRegion *) astInterval( tfrm, &lo, &hi, NULL, "", status );
+               }
+
+/* If there were earlier axes, extrude the current total region into the
+   time axis, and use the extruded region as the new total region.*/
+               if( t ) {
+                  tt = (AstRegion *) astPrism( t, r, "", status );
+                  r = astAnnul( r );
+                  (void) astAnnul( t );
+                  t = tt;
+
+/* If this is the first axis, use the region determined for this axis as
+   the total Region.*/
+               } else {
+                  t = r;
+               }
+            }
+
+/* Do the same for any spectral axis. */
+            if( sfrm ) {
+               if( sanc && astMapGet0A( sanc, key[ i ], &o ) ) {
+                  r = (AstRegion *) o;
+                  use = 1;
+               } else {
+                  r = (AstRegion *) astInterval( sfrm, &lo, &hi, NULL, "", status );
+               }
+
+               if( t ) {
+                  tt = (AstRegion *) astPrism( t, r, "", status );
+                  r = astAnnul( r );
+                  (void) astAnnul( t );
+                  t = tt;
+               } else {
+                  t = r;
+               }
+
+            }
+
+/* Do the same for any redshift axis. */
+            if( rfrm ) {
+               if( ranc && astMapGet0A( ranc, key[ i ], &o ) ) {
+                  r = (AstRegion *) o;
+                  use = 1;
+               } else {
+                  r = (AstRegion *) astInterval( rfrm, &lo, &hi, NULL, "", status );
+               }
+
+               if( t ) {
+                  tt = (AstRegion *) astPrism( t, r, "", status );
+                  r = astAnnul( r );
+                  (void) astAnnul( t );
+                  t = tt;
+               } else {
+                  t = r;
+               }
+            }
+
+/* If there is some non-null information for this item, replace the
+   stored Frame with the Frame which has set Epoch/RefLat/etc, simplify the
+   total Region and store it in the returned KeyMap. */
+            if( use ) {
+               astSetRegFS( t, frm );
+               tt = astSimplify( t );
+               astMapPut0A( *anc, key[ i ], tt, NULL );
+               tt = astAnnul( tt );
+               empty = 0;
+            }
+            if( t ) t = astAnnul( t );
+         }
+
+/* Return a NULL KeyMap pointer if the KeyMap is empty. */
+         if( empty ) *anc = astAnnul( *anc );
+      }
+
+/* Free resources. */
+      if( panc ) panc = astAnnul( panc );
+      if( tanc ) tanc = astAnnul( tanc );
+      if( sanc ) sanc = astAnnul( sanc );
+      if( ranc ) ranc = astAnnul( ranc );
+      if( pfrm ) pfrm = astAnnul( pfrm );
+      if( tfrm ) tfrm = astAnnul( tfrm );
+      if( sfrm ) sfrm = astAnnul( sfrm );
+      if( rfrm ) rfrm = astAnnul( rfrm );
+      scan = FreeIVOAScan( scan, status );
+      anames = astFree( anames );
+   }
+
+/* Annull any returned Regions if an error occurred.*/
+   if( !astOK ) {
+      uncs[ 0 ] = astAnnul( uncs[ 0 ] );
+      uncs[ 1 ] = astAnnul( uncs[ 1 ] );
+      uncs[ 2 ] = astAnnul( uncs[ 2 ] );
+      uncs[ 3 ] = astAnnul( uncs[ 3 ] );
+      unc = 0;
+      *anc = astAnnul( *anc );
+   }
+
+/* Return the result. */
+   return unc;
+}
+
+static AstObject *AstroCoordSystemReader( AstXmlChan *this,
+                                          AstXmlElement *elem, int *status ) {
+/*
+*  Name:
+*     AstroCoordSystemReader
+
+*  Purpose:
+*     Make an AST Object from an IVOA AstroCoordSystem element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstObject *AstroCoordSystemReader( AstXmlChan *this,
+*                                        AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Object from the supplied IVOA
+*     AstroCoordSystem element. This will be a Frame of some kind.
+*     If the AstroCoordSystem element contains only one sub-frame
+*     element, then the returned Frame will be of a suitable class
+*     to describe that sub-frame (SkyFrame, SpecFrame or TimeFrame).
+*     If the AstroCoordSystem element contains more than one sub-frame
+*     element, then the returned Frame will be a CmpFrame in which the
+*     component Frames are in the order SpaceFrame, TimeFrame,
+*     SpectralFrame, RedshiftFrame.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA AstroCoordSystem element.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object.
+
+*  Notes:
+*     - GenericCoordFrame sub-elements are currently ignored since it is not
+*     clear how they relate to the other sub-elements.
+
+*/
+
+/* Local Variables: */
+   AstCmpFrame *tmp;             /* Pointer to intermediate CmpFrame */
+   AstFrame *comp[ 4 ];          /* Pointers to component Frames */
+   AstObject *new;               /* Pointer to returned Object */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *id;               /* Pointer to ID attribute value */
+   const char *names[4];         /* Names of the subelements to be searched for */
+   int i;                        /* Index of current content item */
+   int j;                        /* Index to store Frame pointer at */
+   int max[4];                   /* Max allowed occurrences of each name */
+   int min[4];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = SPACE_FRAME;
+   names[ 1 ] = TIME_FRAME;
+   names[ 2 ] = SPECTRAL_FRAME;
+   names[ 3 ] = REDSHIFT_FRAME;
+   min[ 0 ] = 0;
+   min[ 1 ] = 0;
+   min[ 2 ] = 0;
+   min[ 3 ] = 0;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   max[ 2 ] = 1;
+   max[ 3 ] = 1;
+   scan = ScanIVOAElement( this, elem, 4, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Create Frames from the found sub-elements */
+      comp[ 0 ] = scan->count[0] ? (AstFrame *) SpaceFrameReader( this,
+                                                   scan->el[ 0 ][ 0 ], status ) : NULL;
+      comp[ 1 ] = scan->count[1] ? (AstFrame *) TimeFrameReader( this,
+                                                   scan->el[ 1 ][ 0 ], status ) : NULL;
+      comp[ 2 ] = scan->count[2] ? (AstFrame *) SpectralFrameReader( this,
+                                                   scan->el[ 2 ][ 0 ], status ) : NULL;
+      comp[ 3 ] = scan->count[3] ? (AstFrame *) RedshiftFrameReader( this,
+                                                   scan->el[ 3 ][ 0 ], status ) : NULL;
+
+/* If more than one frame was obtained combine them into a CmpFrame. If
+   present, the Frames are stored in the order SpaceFrame, TimeFrame,
+   SpectralFrame, RedshiftFrame. Shuffle the the higher elements of the
+   "comp" array down to fill any NULL elements. */
+      j = 0;
+      for( i = 0; i < 4; i++ ) {
+         if( comp[ i ] ) {
+            comp[ j++ ] = comp[ i ];
+         }
+      }
+
+/* Fill any unused elements at the end with NULL. */
+      for( ; j < 4; j++ ) comp[ j ] = NULL;
+
+/* If no Frames were read issue a fatal error. */
+      if( !comp[ 0 ] ) {
+         Report( this, elem, FAILURE, "contains no usable coordinate axes", status );
+
+/* If only one Frame was read return a clone of its pointer. */
+      } else if( !comp[ 1 ] ) {
+         new = astClone( comp[ 0 ] );
+
+/* If two or more Frames were read, create a CmpFrame holding the Frames. */
+      } else if( !comp[ 2 ] ) {
+         new = (AstObject *) astCmpFrame( comp[ 0 ], comp[ 1 ], "", status );
+
+      } else if( !comp[ 3 ] ) {
+         tmp = astCmpFrame( comp[ 0 ], comp[ 1 ], "", status );
+         new = (AstObject *) astCmpFrame( tmp, comp[ 2 ], "", status );
+         tmp = astAnnul( tmp );
+
+      } else {
+         tmp = astCmpFrame( comp[ 0 ], comp[ 1 ], "", status );
+         (void) astAnnul( comp[ 0 ] );
+         comp[ 0 ] = (AstFrame *) tmp;
+         tmp = astCmpFrame( comp[ 0 ], comp[ 2 ], "", status );
+         new = (AstObject *) astCmpFrame( tmp, comp[ 3 ], "", status );
+         tmp = astAnnul( tmp );
+      }
+
+/* Get the ID attribute from the AstroCoordSystem element and store in the
+   returned Frame. */
+      id = astXmlGetAttributeValue( elem, "ID" );
+      if( id ) astSetIdent( new, id );
+
+/* Free resources */
+      for( i = 0; i < 4; i++ ) {
+         if( comp[ i ] ) comp[ i ] = astAnnul( comp[ i ] );
+      }
+      scan = FreeIVOAScan( scan, status );
+
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Object. */
+   return new;
+}
+
+static double AstronTimeReader( AstXmlChan *this, AstXmlElement *elem,
+                                AstTimeFrame *frm, int *status ){
+/*
+*  Name:
+*     AstronTimeReader
+
+*  Purpose:
+*     Read a time value from an IVOA AstronTime element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     double AstronTimeReader( AstXmlChan *this, AstXmlElement *elem,
+*                              AstTimeFrame *frm )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function returns a double representing the time specified by
+*     the supplied IVOA AstronTime element, converted into the system
+*     represented by the supplied Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA AstronTime element.
+*     frm
+*        Pointer to the TimeFrame in which the returned value should be
+*        defined. Relevant attributes which are not set will be set by
+*        this function if possible.
+
+*  Returned Value:
+*     The time value, in the system described by "frm".
+
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;              /* FrameSet connecting two TimeFrames */
+   AstTimeFrame *cfrm;           /* TimeFrame describing XML time system */
+   AstTimeScaleType ts;          /* TimeScale */
+   IVOAScan *scan;               /* Structure holding scan results */
+   char buff[ 200 ];             /* Message buffer */
+   const char *iso;              /* Pointer to ISO date string */
+   const char *names[3];         /* Names of the subelements to be searched for */
+   const char *time_type;        /* Pointer to time type string */
+   const char *unit;             /* Pointer to Unit string */
+   double fval;                  /* Value converted to supplied TimeFrame */
+   double offset;                /* Time offset */
+   double result;                /* Time offset converted to required TimeFrame */
+   double val;                   /* Value read from element */
+   int max[3];                   /* Max allowed occurrences of each name */
+   int min[3];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   offset = 0.0;
+   result = AST__BAD;
+   val = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return offset;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "JDTime|MJDTime|ISOTime";
+   names[ 1 ] = "TimeOffset";
+   names[ 2 ] = "TimeScale|Timescale";
+   min[ 0 ] = 1;
+   min[ 1 ] = 0;
+   min[ 2 ] = 0;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   max[ 2 ] = 1;
+   scan = ScanIVOAElement( this, elem, 3, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* The supplied TimeFrame describes the system in which the caller wants
+   the time values to be returned. This may not be the same as the system
+   in which the value is stored in the XML. We create a TimeFrame
+   describing the XML system, and later transform time values from the XML
+   system to the system required by the caller. Any attributes of the XML
+   system which are not specified in the XML are assumed to be equal to
+   the values of the corresponding attributes in the supplied TimeFrame.
+   If the XML system specifies values for attributes which have not been
+   set in the supplied TimeFrame, then the values read fomr the XML are
+   assigned to the attributes of the supplied TimeFrame. */
+      cfrm = astCopy( frm );
+
+      if( scan->count[2] ) {
+         ts = TimeScaleReader( this, scan->el[2][0], status );
+         astSetTimeScale( cfrm, ts );
+         if( !astTestTimeScale( frm ) ) astSetTimeScale( frm, ts );
+      }
+
+/* If a JDTime element was found, get its value and set the TimeFrame System
+   values. */
+      time_type = astXmlGetName( scan->el[0][0] );
+      if( !strcmp( "JDTime", time_type ) ) {
+         val = ElemValueD( this, scan->el[0][0], 2400000.5, status );
+         astSetSystem( cfrm, AST__JD );
+         if( !astTestSystem( frm ) ) astSetSystem( frm, AST__JD );
+
+/* If a ISOTime element was found, get its value and set the TimeFrame
+   System attribute to MJD (the choice of AST System for an ISOTime is
+   arbitrary - JD or JEPOCH could also have been used). */
+      } else if( !strcmp( "ISOTime", time_type ) ) {
+         astSetSystem( cfrm, AST__MJD );
+         if( !astTestSystem( frm ) ) astSetSystem( frm, AST__MJD );
+         iso = astXmlGetValue( scan->el[0][0], 0 );
+         astClearTimeOrigin( cfrm );
+         if( iso && astUnformat( cfrm, 0, iso, &val ) != strlen( iso ) ) {
+            sprintf( buff, "contains unsupported ISO time format \"%s\"",
+                     iso );
+            Report( this, elem, FAILURE, buff, status );
+         }
+
+/* If an MJDTime was found, get its value and set System attributes. */
+      } else {
+         val = ElemValueD( this, scan->el[0][0], 2400000.5, status );
+         astSetSystem( cfrm, AST__MJD );
+         if( !astTestSystem( frm ) ) astSetSystem( frm, AST__MJD );
+      }
+
+/* Use this value as the TimeFrame's TimeOrigin value. Use the public
+   astSetD rather than astSetTimeOrigin since the later requires the
+   value to be supplied in the default units for the TimeFrame's System. */
+      astSetD( cfrm, "TimeOrigin", val );
+
+/* If the supplied Frame has no set TimeOrigin, also use the value
+   obtained above as the TimeOrigin in "frm". Convert it into the supplied
+   TimeFrame, and set it. Note zero is used as the axis value in cfrm
+   because the relevant epoch is zero distance away from the cfrm
+   TimeOrigin (set above). */
+      if( !astTestTimeOrigin( frm ) ) {
+
+         fs = astConvert( cfrm, frm, "" );
+         if( fs ){
+            val = 0.0;
+            astTran1( fs, 1, &val, 1, &fval );
+            astSetD( frm, "TimeOrigin", fval );
+            fs = astAnnul( fs );
+         } else if( astOK ) {
+            sprintf( buff, "contains inconsistent timescale (%s)",
+                     astGetC( cfrm, "timescale" ) );
+            Report( this, elem, FAILURE, buff, status );
+         }
+      }
+
+/* If an TimeOffset element was found, get its value and the value of its
+   unit attribute (assume a default of days). Set the units in the
+   TimeFrames. */
+      if( scan->count[1] ) {
+         offset = ElemValueD( this, scan->el[1][0], 0.0, status );
+         unit = astXmlGetAttributeValue( scan->el[1][0], "unit" );
+         if( !unit ) unit = "d";
+         astSetUnit( cfrm, 0, unit );
+         if( !astTestUnit( frm, 0 ) ) astSetUnit( frm, 0, unit );
+
+/* If no offset was given, use zero. */
+      } else {
+         offset = 0.0;
+      }
+
+/* Convert the offset from the system in which it is stored in the XML to
+   the system required by the caller. */
+      fs = astConvert( cfrm, frm, "" );
+      if( fs ){
+         astTran1( fs, 1, &offset, 1, &result );
+         fs = astAnnul( fs );
+      } else if( astOK ) {
+         sprintf( buff, "contains inconsistent timescale (%s)",
+                  astGetC( cfrm, "timescale" ) );
+         Report( this, elem, FAILURE, buff, status );
+      }
+
+/* Free resources. */
+      cfrm = astAnnul( cfrm );
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Return the time value. */
+   return result;
+}
+
+void astInitXmlChanVtab_(  AstXmlChanVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitXmlChanVtab
+
+*  Purpose:
+*     Initialise a virtual function table for an XmlChan.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void astInitXmlChanVtab( AstXmlChanVtab *vtab, const char *name )
+
+*  Class Membership:
+*     XmlChan vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the XmlChan class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstChannelVtab *channel;      /* Pointer to Channel component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitChannelVtab( (AstChannelVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAXmlChan) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstChannelVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+
+   vtab->SetXmlLength = SetXmlLength;
+   vtab->ClearXmlLength = ClearXmlLength;
+   vtab->TestXmlLength = TestXmlLength;
+   vtab->GetXmlLength = GetXmlLength;
+
+   vtab->SetXmlFormat = SetXmlFormat;
+   vtab->ClearXmlFormat = ClearXmlFormat;
+   vtab->TestXmlFormat = TestXmlFormat;
+   vtab->GetXmlFormat = GetXmlFormat;
+
+   vtab->SetXmlPrefix = SetXmlPrefix;
+   vtab->ClearXmlPrefix = ClearXmlPrefix;
+   vtab->TestXmlPrefix = TestXmlPrefix;
+   vtab->GetXmlPrefix = GetXmlPrefix;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   channel = (AstChannelVtab *) vtab;
+
+   channel->WriteBegin = WriteBegin;
+   channel->WriteIsA = WriteIsA;
+   channel->WriteEnd = WriteEnd;
+   channel->WriteInt = WriteInt;
+   channel->WriteDouble = WriteDouble;
+   channel->WriteString = WriteString;
+   channel->WriteObject = WriteObject;
+
+   channel->Read = Read;
+   channel->ReadClassData = ReadClassData;
+   channel->ReadDouble = ReadDouble;
+   channel->ReadInt = ReadInt;
+   channel->ReadObject = ReadObject;
+   channel->ReadString = ReadString;
+
+   parent_getindent = channel->GetIndent;
+   channel->GetIndent = GetIndent;
+
+   parent_getfull = channel->GetFull;
+   channel->GetFull = GetFull;
+
+   parent_getcomment = channel->GetComment;
+   channel->GetComment = GetComment;
+
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+/* Declare the class dump, copy and delete functions.*/
+   astSetCopy( vtab, Copy );
+   astSetDump( vtab, Dump, "XmlChan", "XML I/O channel" );
+   astSetDelete( vtab, Delete );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static double AttrValueD( AstXmlChan *this, AstXmlElement *elem,
+                          const char *name, double def, int *status ) {
+/*
+*  Name:
+*     AttrValueD
+
+*  Purpose:
+*     Read a floating point XML element attribute value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     double AttrValueD( AstXmlChan *this, AstXmlElement *elem,
+*                        const char *name, double def, int *status )
+
+*  Class Membership:
+*     XmlChan member function
+
+*  Description:
+*     This function returns the value of a named attribute of an XML
+*     element as a floating point value. A report is made if the
+*     attribute value is not floating point.The supplied default value is
+*     returned if the attribute is not present.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the XmlElement.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required attribute value.
+*     def
+*        If the supplied element does not have the requried attribute, then
+*        this value will be returned instead.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The required attribute value, or the default if the value was not found.
+
+*/
+
+/* Local Variables: */
+   char buff[ 200 ];             /* Msg buffer */
+   const char *value;            /* Pointer to attribute value */
+   double result;                /* Value to be returned */
+   int nc;                       /* Number of characters read by astSscanf */
+   int nf;                       /* Number of matching fields */
+   int len;                      /* Length of attribute string */
+
+/* Initialise. */
+   result = def;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the attribute value as a string. */
+   value = astXmlGetAttributeValue( elem, name );
+
+/* If the attribute exists, attempt to decode the string to give a double
+   value, checking that the entire string is read. */
+   if( value ) {
+      nc = 0;
+      nf = astSscanf( value, " %lf %n", &result, &nc );
+      len = strlen( value );
+
+      if ( nf != 1 || nc < len ) {
+         sprintf( buff, "contains a bad <%s> value: \"%s\"", name, value );
+         Report( this, elem, WARNING, buff, status );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int AttrValueI( AstXmlChan *this, AstXmlElement *elem, const char *name,
+                       int def, int *status ) {
+/*
+*  Name:
+*     AttrValueI
+
+*  Purpose:
+*     Read an integer XML element attribute value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int AttrValueI( AstXmlChan *this, AstXmlElement *elem, const char *name,
+*                     int def )
+
+*  Class Membership:
+*     XmlChan member function
+
+*  Description:
+*     This function returns the value of a named attribute of an XML element
+*     as an integer value. A report is made if the attribute value is not
+*     integer. The supplied default value is returned if the attribute is not
+*     present.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the XmlElement.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required attribute value.
+*     def
+*        If the supplied element does not have the requried attribute, then
+*        this value will be returned instead.
+
+*  Returned Value:
+*     The required attribute value, or the default if the value was not found.
+
+*/
+
+/* Local Variables: */
+   char buff[ 200 ];             /* Msg buffer */
+   const char *value;            /* Pointer to attribute value */
+   int result;                   /* Value to be returned */
+   int nc;                       /* Number of characters read by astSscanf */
+   int nf;                       /* Number of matching fields */
+   int len;                      /* Length of attribute string */
+
+/* Initialise. */
+   result = def;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the attribute value as a string. */
+   value = astXmlGetAttributeValue( elem, name );
+
+/* If the attribute exists, attempt to decode the string to give an integer
+   value, checking that the entire string is read. */
+   if( value ) {
+      nc = 0;
+      nf = astSscanf( value, " %d %n", &result, &nc );
+      len = strlen( value );
+
+      if ( nf != 1 || nc < len ) {
+         sprintf( buff, "contains a bad <%s> value: \"%s\"", name, value );
+         Report( this, elem, WARNING, buff, status );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int AttrValueB( AstXmlChan *this, AstXmlElement *elem, const char *name,
+                       int def, int *status ) {
+/*
+*  Name:
+*     AttrValueB
+
+*  Purpose:
+*     Read a boolean XML element attribute value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int AttrValueB( AstXmlChan *this, AstXmlElement *elem, const char *name,
+*                     int def, int *status )
+
+*  Class Membership:
+*     XmlChan member function
+
+*  Description:
+*     This function returns the value of a named attribute of an XML element
+*     as a boolean. A report is made if the attribute value is not
+*     boolean. The supplied default value is returned if the attribute is not
+*     present.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the XmlElement.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required attribute value.
+*     def
+*        If the supplied element does not have the requried attribute, then
+*        this value will be returned instead.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The required attribute value, or the default if the value was not found.
+
+*/
+
+/* Local Variables: */
+   char buff[ 200 ];             /* Msg buffer */
+   const char *value;            /* Pointer to attribute value */
+   int result;                   /* Value to be returned */
+   int i;                        /* Loop count */
+
+/* Define the recognised true and false strings. */
+   const char *true[ 5 ] = { "true", "TRUE", "yes", "YES", "1" };
+   const char *false[ 5 ] = { "false", "FALSE", "no", "NO", "0" };
+
+/* Initialise. */
+   result = def;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the attribute value as a string. */
+   value = astXmlGetAttributeValue( elem, name );
+
+/* If the attribute exists, attempt to decode the string to give a boolean
+   value. */
+   if( value ) {
+
+/* Indicate the result has not yet been determined. */
+      result = -1;
+
+/* See if the attribute value is equal to (or an abbreviation of) any of
+   the true strings. */
+      for( i = 0; i < 5; i++ ) {
+         if( strstr( true[ i ], value ) == true[ i ] ) {
+            result = 1;
+            break;
+         }
+      }
+
+/* If not, see if the attribute value is equal to (or an abbreviation of) any
+   of the false strings. */
+      if( result == -1 ) {
+         for( i = 0; i < 5; i++ ) {
+            if( strstr( false[ i ], value ) == false[ i ] ) {
+               result = 0;
+               break;
+            }
+         }
+      }
+
+/* If not, report a warning and return the default. */
+      if( result == -1 ) {
+         result = def;
+         sprintf( buff, "contains a bad <%s> value: \"%s\"", name, value );
+         Report( this, elem, WARNING, buff, status );
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstRegion *BoxReader( AstXmlChan *this, AstXmlElement *elem,
+                             AstFrame *frm, int *status ){
+/*
+*  Name:
+*     BoxReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Box element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *BoxReader( AstXmlChan *this, AstXmlElement *elem,
+*                           AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Box element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Box element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Frame used to define returned Region */
+   AstMapping *map;              /* Mapping between units */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *funit;            /* Unit string from supplied Frame */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   const char *unit;             /* Centre and radii unit string */
+   double cen[2];                /* Centre */
+   double size[2];               /* Axis sizes */
+   double pos[8];                /* Polygon vertex axis values */
+   double *x;                    /* Pointer to first vertex X axis value */
+   double *y;                    /* Pointer to first vertex Y axis value */
+   int i;                        /* Axis count */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Scan the supplied element for the required sub-elements */
+   names[ 0 ] = "Center";
+   names[ 1 ] = "Size";
+   min[ 0 ] = 1;
+   min[ 1 ] = 1;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the centre. */
+      cen[0] = 0.0;
+      cen[1] = 0.0;
+      ElemListD( this, scan->el[0][0], 2, cen, status );
+
+/* Get the size. */
+      size[0] = 0.0;
+      size[1] = 0.0;
+      ElemListD( this, scan->el[1][0], 2, size, status );
+
+/* Get the units attribute from the supplied element. These are the units
+   of the centre and size values. */
+      unit = astXmlGetAttributeValue( elem, "unit" );
+      if( !unit ) {
+         Report( this, elem, FAILURE, "contains no unit attribute", status );
+         unit = "";
+      }
+
+/* Since the SkyFrame class does not have active Units we must handle it
+   separately. */
+      if( astIsASkyFrame( frm ) ) {
+
+/* Create the anti-clockwise list of (x,y) at the four corners of the box. */
+         x = pos;
+         y = pos+ 4;
+         x[ 3 ] = cen[ 0 ] + 0.5*size[ 0 ];
+         y[ 3 ] = cen[ 1 ] - 0.5*size[ 1 ];
+         x[ 2 ] = cen[ 0 ] + 0.5*size[ 0 ];
+         y[ 2 ] = cen[ 1 ] + 0.5*size[ 1 ];
+         x[ 1 ] = cen[ 0 ] - 0.5*size[ 0 ];
+         y[ 1 ] = cen[ 1 ] + 0.5*size[ 1 ];
+         x[ 0 ] = cen[ 0 ] - 0.5*size[ 0 ];
+         y[ 0 ] = cen[ 1 ] - 0.5*size[ 1 ];
+
+/* Convert the axis values to radians. */
+         map = astUnitMapper( unit, "rad", NULL, NULL );
+         if( map ) {
+            astTran1( map, 8, pos, 1, pos );
+            map = astAnnul( map );
+         } else if( astOK ) {
+            Report( this, elem, FAILURE, "contains unusable units", status );
+         }
+
+/* Create the Polygon. */
+         new = (AstRegion *) astPolygon( frm, 4, 4, pos, NULL, "", status );
+
+/* Now handles Frames other than SkyFrames. */
+      } else {
+
+/* Create the anti-clockwise list of (x,y) at the four corners of the box. */
+         x = pos;
+         y = pos+ 4;
+         x[ 0 ] = cen[ 0 ] + 0.5*size[ 0 ];
+         y[ 0 ] = cen[ 1 ] - 0.5*size[ 1 ];
+         x[ 1 ] = cen[ 0 ] + 0.5*size[ 0 ];
+         y[ 1 ] = cen[ 1 ] + 0.5*size[ 1 ];
+         x[ 2 ] = cen[ 0 ] - 0.5*size[ 0 ];
+         y[ 2 ] = cen[ 1 ] + 0.5*size[ 1 ];
+         x[ 3 ] = cen[ 0 ] - 0.5*size[ 0 ];
+         y[ 3 ] = cen[ 1 ] - 0.5*size[ 1 ];
+
+/* Take a copy of the supplied Frame and set its Units to the value
+   obtained from the supplied element. */
+         cfrm = astCopy( frm );
+         astSetUnit( cfrm, 0, unit );
+         astSetUnit( cfrm, 1, unit );
+
+/* Create a Polygon within this modified Frame. */
+         new = (AstRegion *) astPolygon( frm, 4, 4, pos, NULL, "", status );
+
+/* If the Unit of this Region differs from that of the supplied Frame,
+   set it to the Unit of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new Unit. If the supplied
+   Frame had no set Unit, set it to the units obtained from the supplied
+   element. */
+         for( i = 0; i < 2; i++ ) {
+            if( astTestUnit( frm, i ) ) {
+               funit = astGetUnit( frm, i );
+               if( strcmp( funit, unit ) ) astSetUnit( new, i, funit );
+            } else {
+               astSetUnit( frm, i, unit );
+            }
+         }
+
+/* Free resources */
+         cfrm = astAnnul( cfrm );
+      }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstRegion *CircleReader( AstXmlChan *this, AstXmlElement *elem,
+                                AstFrame *frm, int *status ){
+/*
+*  Name:
+*     CircleReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Circle element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *CircleReader( AstXmlChan *this, AstXmlElement *elem,
+*                              AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Circle element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Circle element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Frame used to define returned Region */
+   AstMapping *map;              /* Mapping between units */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *funit;            /* Unit string from supplied Frame */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   const char *unit;             /* Centre unit string from supplied element */
+   double cen[2];                /* Centre */
+   double rad;                   /* Radius */
+   int i;                        /* Axis count */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Scan the supplied element for the required sub-elements */
+   names[ 0 ] = "Radius";
+   names[ 1 ] = "Center";
+   min[ 0 ] = 1;
+   min[ 1 ] = 1;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the radius. */
+      rad = ElemValueD( this, scan->el[0][0], 0.0, status );
+
+/* Get the centre. */
+      cen[0] = 0.0;
+      cen[1] = 0.0;
+      ElemListD( this, scan->el[1][0], 2, cen, status );
+
+/* Get the units attribute from the supplied element. */
+      unit = astXmlGetAttributeValue( elem, "unit" );
+      if( !unit ) {
+         Report( this, elem, FAILURE, "contains no unit attribute", status );
+         unit = "";
+      }
+
+/* Since the SkyFrame class does not have active Units we must handle it
+   separately. */
+      if( astIsASkyFrame( frm ) ) {
+
+/* Convert the axis values and radius to radians. */
+         map = astUnitMapper( unit, "rad", NULL, NULL );
+         if( map ) {
+            astTran1( map, 2, cen, 1, cen );
+            astTran1( map, 1, &rad, 1, &rad );
+            map = astAnnul( map );
+         } else if( astOK ) {
+            Report( this, elem, FAILURE, "contains unusable units", status );
+         }
+
+/* Create the Circle. */
+         new = (AstRegion *) astCircle( frm, 1, cen, &rad, NULL, "", status );
+
+/* Now handles Frames other than SkyFrames. */
+      } else {
+
+/* Take a copy of the supplied Frame and set its Units to the value
+   obtained from the supplied element. */
+         cfrm = astCopy( frm );
+         astSetUnit( cfrm, 0, unit );
+         astSetUnit( cfrm, 1, unit );
+
+/* Create a Circle within this modified Frame. */
+         new = (AstRegion *) astCircle( cfrm, 1, cen, &rad, NULL, "", status );
+
+/* If the Unit of this Region differs from that of the supplied Frame,
+   set it to the Unit of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new Unit. If the supplied
+   Frame had no set Unit, set it to the units obtained from the supplied
+   element. */
+         for( i = 0; i < 2; i++ ) {
+            if( astTestUnit( frm, i ) ) {
+               funit = astGetUnit( frm, i );
+               if( strcmp( funit, unit ) ) astSetUnit( new, i, funit );
+            } else {
+               astSetUnit( frm, i, unit );
+            }
+         }
+
+/* Free resources */
+         cfrm = astAnnul( cfrm );
+
+      }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a XmlChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     Channel member function (over-rides the astClearAttrib protected
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     XmlChan so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;              /* Pointer to the XmlChan structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* XmlLength */
+/* --------- */
+   if ( !strcmp( attrib, "xmllength" ) ) {
+      astClearXmlLength( this );
+
+/* XmlFormat */
+/* --------- */
+   } else if ( !strcmp( attrib, "xmlformat" ) ) {
+      astClearXmlFormat( this );
+
+/* XmlPrefix */
+/* --------- */
+   } else if ( !strcmp( attrib, "xmlprefix" ) ) {
+      astClearXmlPrefix( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static AstRegion *ConstraintReader( AstXmlChan *this, AstXmlElement *elem,
+                                    AstFrame *frm, int *status ){
+/*
+*  Name:
+*     ConstraintReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Constraint element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *ConstraintReader( AstXmlChan *this, AstXmlElement *elem,
+*                                  AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Constraint element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Constraint element.
+*     frm
+*        Pointer to the Frame in which the returned Region should be
+*        defined. The Unit attribute is assumed to be set to "rad".
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   double cen[2];                /* Centre long/lat values */
+   double vec[3];                /* Cartesian centre vector */
+   double rad;                   /* Radius */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Scan the supplied element for the required sub-elements */
+   names[ 0 ] = "Vector";
+   names[ 1 ] = "Offset";
+   min[ 0 ] = 1;
+   min[ 1 ] = 1;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the vector and convert from 3D cartesian to a 2D long/lat centre
+   position, in radians. */
+      vec[0] = 1.0;
+      vec[1] = 0.0;
+      vec[2] = 0.0;
+      ElemListD( this, scan->el[0][0], 3, vec, status );
+      palDcc2s( vec, cen, cen + 1 );
+
+/* Get the offset, and convert to a radial distance in radians. */
+      rad = acos( ElemValueD( this, scan->el[1][0], 1.0, status ) );
+
+/* Create the Circle. */
+      new = (AstRegion *) astCircle( frm, 1, cen, &rad, NULL, "", status );
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstRegion *ConvexReader( AstXmlChan *this, AstXmlElement *elem,
+                                AstFrame *frm, int *status ){
+/*
+*  Name:
+*     ConvexReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Convex element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *ConvexReader( AstXmlChan *this, AstXmlElement *elem,
+*                              AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Convex element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Convex element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Frame to use  when building Constraints */
+   AstRegion *new;               /* Pointer to returned Region */
+   AstRegion *reg;               /* Pointer to component Region */
+   AstRegion *tmp;               /* Pointer to new Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[1];         /* Names of the subelements to be searched for */
+   const char *unit;             /* Unit attribute in element tag */
+   int i;                        /* Loop count */
+   int issky;                    /* Is supplied Frame a SkyFrame? */
+   int max[1];                   /* Max allowed occurrences of each name */
+   int min[1];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for a Region sub-element. */
+   names[ 0 ] = "Constraint";
+   min[ 0 ] = 1;
+   max[ 0 ] = INT_MAX;
+   scan = ScanIVOAElement( this, elem, 1, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Convex needs no units since all values are normalised to a unit sphere */
+      unit = astXmlGetAttributeValue( elem, "unit" );
+      if( unit ) {
+         Report( this, elem, WARNING, "contains unnecessary unit attribute", status );
+      }
+
+/* Unless the supplied Frame is a SkyFrame (which handles the Unit
+   attribute unusually), take a copy of the supplied Frame and set its
+   units to radians. */
+      issky = astIsASkyFrame( frm );
+      if( issky ) {
+         cfrm = astCopy( frm );
+         astSetUnit( cfrm, 0, "rad" );
+         astSetUnit( cfrm, 1, "rad" );
+      } else {
+         cfrm = astClone( frm );
+      }
+
+/* Create Regions from all the component Constraint elements, and combine
+   them into nested CmpRegions, using the boolean AND operator to combine
+   them. */
+      new = ConstraintReader( this, scan->el[0][0], cfrm, status );
+      for( i = 1; i < scan->count[0]; i++ ) {
+         reg = ConstraintReader( this, scan->el[0][i], cfrm, status );
+         tmp = (AstRegion *) astCmpRegion( new, reg, AST__AND, "", status );
+         reg = astAnnul( reg );
+         (void) astAnnul( new );
+         new = tmp;
+      }
+
+/* If required, modify the units back to their original values This
+   will cause the axis values defining the returned Region to be re-mapped
+   into the new units. Do not do this if the supplied Frame is a SkyFrame. */
+      if( !issky ) {
+         if( astTestUnit( frm, 0 ) ) astSetUnit( new, 0, astGetUnit( frm, 0 ) );
+         if( astTestUnit( frm, 1 ) ) astSetUnit( new, 1, astGetUnit( frm, 1 ) );
+      }
+
+/* Get any fill factor from the element and assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      cfrm = astAnnul( cfrm );
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+
+static AstRegion *Coord2VecIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+                                           const char *unit, AstFrame *frm, int *status ){
+/*
+*  Name:
+*     Coord2VecIntervalReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Coord2VecInterval element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *Coord2VecIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+*                                         const char *unit, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Coord2VecInterval element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Coord2VecInterval element.
+*     unit
+*        A string holding the units in which the axis values are stored
+*        in the supplied element.
+*     frm
+*        Pointer to the Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Frame used to define returned Region */
+   AstMapping *map;              /* Mapping from supplied units to rads */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *funit;            /* Unit string from supplied Frame */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   double hilimit[2];            /* Upper limits */
+   double lolimit[2];            /* Lower limits */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "LoLimit2Vec";
+   names[ 1 ] = "HiLimit2Vec";
+   min[ 0 ] = 0;
+   min[ 1 ] = 0;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the limits. */
+      lolimit[0] = AST__BAD;
+      lolimit[1] = AST__BAD;
+      if( scan->count[0] ) ElemListD( this, scan->el[0][0], 2, lolimit, status );
+
+      hilimit[0] = AST__BAD;
+      hilimit[1] = AST__BAD;
+      if( scan->count[1] ) ElemListD( this, scan->el[1][0], 2, hilimit, status );
+
+/* Since the SkyFrame class does not have active Units we must handle it
+   separately. */
+      if( astIsASkyFrame( frm ) ) {
+
+/* Convert the limit values to radians. */
+         map = astUnitMapper( unit, "rad", NULL, NULL );
+         if( map ) {
+            astTran1( map, 2, lolimit, 1, lolimit );
+            astTran1( map, 2, hilimit, 1, hilimit );
+            map = astAnnul( map );
+         } else if( astOK ) {
+            Report( this, elem, FAILURE, "contains unusable units", status );
+         }
+
+/* If at least one limit was found, create an Interval within the supplied
+   Frame. Otherwise create a negated NullRegion. */
+         if( lolimit[ 0 ] != AST__BAD || lolimit[ 1 ] != AST__BAD ||
+             hilimit[ 0 ] != AST__BAD || hilimit[ 1 ] != AST__BAD ) {
+            new = (AstRegion *) astInterval( frm, lolimit, hilimit, NULL, "", status );
+         } else {
+            new = (AstRegion *) astNullRegion( frm, NULL, "negated=1", status );
+         }
+
+/* Now handles Frames other than SkyFrames. */
+      } else {
+
+/* Take a copy of the supplied Frame and set its Unit attribute to the
+   supplied value. */
+         cfrm = astCopy( frm );
+         astSetUnit( cfrm, 0, unit );
+
+/* If at least one limit was found, create an Interval within this
+   modified Frame. Otherwise create a negated NullRegion. */
+         if( lolimit[ 0 ] != AST__BAD || lolimit[ 1 ] != AST__BAD ||
+             hilimit[ 0 ] != AST__BAD || hilimit[ 1 ] != AST__BAD ) {
+            new = (AstRegion *) astInterval( cfrm, lolimit, hilimit, NULL, "", status );
+         } else {
+            new = (AstRegion *) astNullRegion( cfrm, NULL, "negated=1", status );
+         }
+
+/* If the supplied units differ from that of the supplied Frame, set the
+   units in the Region to those of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the required units. If the supplied
+   Frame had no set Units, set it to the supplied units. */
+         if( astTestUnit( frm, 0 ) ) {
+            funit = astGetUnit( frm, 0 );
+            if( strcmp( funit, unit ) ) astSetUnit( new, 0, funit );
+         } else {
+            astSetUnit( frm, 0, unit );
+         }
+
+/* Free resources */
+         cfrm = astAnnul( cfrm );
+      }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstRegion *Coord3VecIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+                                           const char *unit, AstFrame *frm, int *status ){
+/*
+*  Name:
+*     Coord3VecIntervalReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Coord3VecInterval element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *Coord3VecIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+*                                         const char *unit, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Coord3VecInterval element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Coord3VecInterval element.
+*     unit
+*        A string holding the units in which the axis values are stored
+*        in the supplied element.
+*     frm
+*        Pointer to the Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Frame used to define returned Region */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *funit;            /* Unit string from supplied Frame */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   double hilimit[3];            /* Upper limits */
+   double lolimit[3];            /* Lower limits */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "LoLimit3Vec";
+   names[ 1 ] = "HiLimit3Vec";
+   min[ 0 ] = 0;
+   min[ 1 ] = 0;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the limits. */
+      lolimit[0] = AST__BAD;
+      lolimit[1] = AST__BAD;
+      lolimit[2] = AST__BAD;
+      if( scan->count[0] ) ElemListD( this, scan->el[0][0], 3, lolimit, status );
+
+      hilimit[0] = AST__BAD;
+      hilimit[1] = AST__BAD;
+      hilimit[2] = AST__BAD;
+      if( scan->count[1] ) ElemListD( this, scan->el[1][0], 3, hilimit, status );
+
+/* Take a copy of the supplied Frame and set its Unit attribute to the
+   supplied value. */
+      cfrm = astCopy( frm );
+      astSetUnit( cfrm, 0, unit );
+
+/* If at least one limit was found, create an Interval within this
+   modified Frame. Otherwise create a negated NullRegion. */
+      if( lolimit[ 0 ] != AST__BAD || lolimit[ 1 ] != AST__BAD ||
+          lolimit[ 2 ] != AST__BAD ||
+          hilimit[ 0 ] != AST__BAD || hilimit[ 1 ] != AST__BAD ||
+          hilimit[ 2 ] != AST__BAD ) {
+         new = (AstRegion *) astInterval( cfrm, lolimit, hilimit, NULL, "", status );
+      } else {
+         new = (AstRegion *) astNullRegion( cfrm, NULL, "negated=1", status );
+      }
+
+/* If the supplied units differ from that of the supplied Frame, set the
+   units in the Region to those of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the required units. If the supplied
+   Frame had no set Units, set it to the supplied units. */
+      if( astTestUnit( frm, 0 ) ) {
+         funit = astGetUnit( frm, 0 );
+         if( strcmp( funit, unit ) ) astSetUnit( new, 0, funit );
+      } else {
+         astSetUnit( frm, 0, unit );
+      }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      cfrm = astAnnul( cfrm );
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstRegion *CoordScalarIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+                                             const char *unit, AstFrame *frm, int *status ){
+/*
+*  Name:
+*     CoordScalarIntervalReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA CoordScalarInterval element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *CoordScalarIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+*                                           const char *unit, AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     CoordScalarInterval element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA CoordScalarInterval element.
+*     unit
+*        A string holding the units in which the axis values are stored
+*        in the supplied element.
+*     frm
+*        Pointer to the Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Frame used to define returned Region */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *funit;            /* Unit string from supplied Frame */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   double hilimit;               /* Upper limit */
+   double lolimit;               /* Lower limit */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "LoLimit";
+   names[ 1 ] = "HiLimit";
+   min[ 0 ] = 0;
+   min[ 1 ] = 0;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the limits. */
+      lolimit = scan->count[0] ? ElemValueD( this, scan->el[0][0], 0.0, status ) : AST__BAD;
+      hilimit = scan->count[1] ? ElemValueD( this, scan->el[1][0], 0.0, status ) : AST__BAD;
+
+/* Take a copy of the supplied Frame and set its Unit attribute to the
+   supplied value. */
+      cfrm = astCopy( frm );
+      astSetUnit( cfrm, 0, unit );
+
+/* If at least one limit was found, create an Interval within this
+   modified Frame. Otherwise create a negated NullRegion. */
+      if( lolimit != AST__BAD || hilimit != AST__BAD ) {
+         new = (AstRegion *) astInterval( cfrm, &lolimit, &hilimit, NULL, "", status );
+      } else {
+         new = (AstRegion *) astNullRegion( cfrm, NULL, "negated=1", status );
+      }
+
+/* If the supplied units differ from that of the supplied Frame, set the
+   units in the Region to those of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the required units. If the supplied
+   Frame had no set Units, set it to the supplied units. */
+      if( astTestUnit( frm, 0 ) ) {
+         funit = astGetUnit( frm, 0 );
+         if( strcmp( funit, unit ) ) astSetUnit( new, 0, funit );
+      } else {
+         astSetUnit( frm, 0, unit );
+      }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      cfrm = astAnnul( cfrm );
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static int ElemListD( AstXmlChan *this, AstXmlElement *elem, int n,
+                      double *vals, int *status ) {
+/*
+*  Name:
+*     ElemListD
+
+*  Purpose:
+*     Read a floating point XML element value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int ElemListD( AstXmlChan *this, AstXmlElement *elem, int n,
+*                    double *vals, int *status )
+
+*  Class Membership:
+*     XmlChan member function
+
+*  Description:
+*     This function reads the content of the supplied element, converts its
+*     contents to a list of floating point values and returns these
+*     values in "values". A report is made if the element value is not a
+*     space separated list of floating point values, or if it contains
+*     more than "n" values. The number of values stored in "values" is
+*     returned as the function value.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the XmlElement.
+*     n
+*        The maximum number of floating point values to read from the supplied
+*        element.
+*     values
+*        Pointer to an array to hold the values read. This should have at
+*        least "n" elements. Any unused elements are left unchanged.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The number of values stored in "values".
+
+*/
+
+/* Local Variables: */
+   AstXmlContentItem *item;  /* Item no. "i" */
+   char *text;               /* Pointer to string holding formatted item */
+   char buff[200];           /* Message buffer */
+   const char *p;            /* Pointer to start of remaining text */
+   const char *value;        /* Pointer to element value */
+   double dval;              /* Value read from string */
+   int i;                    /* Index of current item */
+   int l;                    /* Used length of string */
+   int nc;                   /* Number of characters read by astSscanf */
+   int nitem;                /* Number of items in the element */
+   int rep;                  /* Has a warning about excess values been made? */
+   int result;               /* Value to be returned */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* No warning has yet been made avbout excess values */
+   rep = 0;
+
+/* Loop through all content items within the supplied element. */
+   nitem = astXmlGetNitem( elem );
+   for( i = 0; i < nitem; i++ ) {
+      item = astXmlGetItem( elem, i );
+
+/* If this is non-blank character data, attempt to read values from it. */
+      if( astXmlCheckType( item, AST__XMLBLACK ) ) {
+
+/* Get the element value as a string. */
+         value = astXmlGetValue( item, 0 );
+         if( value ) {
+
+/* Loop round reading floating point values from the text until the
+   end of the string is reached. */
+            l = astChrLen( value );
+            p = value;
+            while( p < value + l ){
+
+/* Read a floating point value from the start of the remaining string,
+   storing the result in the supplied array. If succesful, increment the
+   number of values read, and increment the pointer to the start of the
+   remaining string. Abort if too many values are found. */
+               if( astSscanf( p, " %lf %n", &dval, &nc ) == 1 ) {
+                  if( result < n ) {
+                     vals[ result++ ] = dval;
+                     p += nc;
+                  } else {
+                     if( !rep ) {
+                        rep = 1;
+                        if( n > 1 ) {
+                           sprintf( buff, "contains more than %d values - "
+                                    "extra values will be ignored", n );
+                        } else {
+                           sprintf( buff, "contains more than 1 value - "
+                                    "extra values will be ignored" );
+                        }
+                        Report( this, elem, WARNING, buff, status );
+                     }
+                     break;
+                  }
+
+/* If the remaing text is not a floating point value, then issue a report. */
+               } else {
+                  Report( this, elem, FAILURE, "contains a non-numerical value", status );
+                  break;
+               }
+            }
+         }
+
+/* If this is not character data, nor a comment, issue a warning. */
+      } else if( !astXmlCheckType( item, AST__XMLWHITE ) &&
+                 !astXmlCheckType( item, AST__XMLCOM ) ) {
+         text = (char *) astXmlFormat( item );
+         if( text ) {
+            if( strlen( text ) > 30 ) text[ 30 ] = 0;
+            sprintf( buff, "contains the following which is being ignored: \"%s\"",
+                     text );
+            text = astFree( text );
+            Report( this, elem, WARNING, buff, status );
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static double ElemValueD( AstXmlChan *this, AstXmlElement *elem, double def, int *status ) {
+/*
+*  Name:
+*     ElemValueD
+
+*  Purpose:
+*     Read a floating point XML element value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     double ElemValueD( AstXmlChan *this, AstXmlElement *elem, double def )
+
+*  Class Membership:
+*     XmlChan member function
+
+*  Description:
+*     This function reads the content of the supplied element, converts its
+*     contents to a floating point value and returns this value. A report is
+*     made if the element value is not floating point. The supplied default
+*     value is returned if the element is not present.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the XmlElement.
+*     def
+*        If the content of the supplied element is not a flaoting point
+*        value, then this value will be returned instead.
+
+*  Returned Value:
+*     The required element value, or the default if the value was not found.
+
+*/
+
+/* Local Variables: */
+   const char *value;            /* Pointer to element value */
+   double result;                /* Value to be returned */
+   int nc;                       /* Number of characters read by astSscanf */
+   int ok;                       /* Value read OK? */
+
+/* Initialise. */
+   result = def;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Assume the value cannot be read. */
+   ok = 0;
+
+/* Get the element value as a string. */
+   value = astXmlGetValue( elem, 0 );
+
+/* If succesful, convert the value to floating point. */
+   if( value ) {
+      nc = 0;
+      ok = ( ( 1 == astSscanf( value, " %lf %n", &result, &nc ) )
+               && ( nc >= (int) strlen( value ) ) );
+   }
+
+/* Give a warning if not OK, and use default value. */
+   if( !ok ) {
+      Report( this, elem, FAILURE, "does not contain a floating point value", status );
+      result = def;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstRegion *EllipseReader( AstXmlChan *this, AstXmlElement *elem,
+                                 AstFrame *frm, int *status ){
+/*
+*  Name:
+*     EllipseReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Ellipse element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *EllipseReader( AstXmlChan *this, AstXmlElement *elem,
+*                               AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Ellipse element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Ellipse element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Frame used to define returned Region */
+   AstMapping *map;              /* Mapping between units */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *funit;            /* Unit string from supplied Frame */
+   const char *names[4];         /* Names of the subelements to be searched for */
+   const char *unit;             /* Centre and radii unit string */
+   double cen[2];                /* Centre */
+   double pa;                    /* Major axis position angle */
+   double rad[2];                /* Major and minor radii */
+   int i;                        /* Axis count */
+   int max[4];                   /* Max allowed occurrences of each name */
+   int min[4];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Scan the supplied element for the required sub-elements */
+   names[ 0 ] = "Radius";
+   names[ 1 ] = "Center";
+   names[ 2 ] = "MinorRadius";
+   names[ 3 ] = "PosAngle";
+   min[ 0 ] = 1;
+   min[ 1 ] = 1;
+   min[ 2 ] = 1;
+   min[ 3 ] = 1;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   max[ 2 ] = 1;
+   max[ 3 ] = 1;
+   scan = ScanIVOAElement( this, elem, 4, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the major radius */
+      rad[ 0 ] = ElemValueD( this, scan->el[0][0], 0.0, status );
+
+/* Get the minor radius. */
+      rad[ 1 ] = ElemValueD( this, scan->el[2][0], 0.0, status );
+
+/* Get the centre. */
+      cen[0] = 0.0;
+      cen[1] = 0.0;
+      ElemListD( this, scan->el[1][0], 2, cen, status );
+
+/* Get the position angle. This is returned in the AST convention, i.e.
+   measured in radians from from +ve second axis through positive first
+   axis. */
+      pa = PosAngleReader( this, scan->el[3][0], status );
+
+/* Get the units attribute from the supplied element. These are the units
+   of the centre and radii value. */
+      unit = astXmlGetAttributeValue( elem, "unit" );
+      if( !unit ) {
+         Report( this, elem, FAILURE, "contains no unit attribute", status );
+         unit = "";
+      }
+
+/* Since the SkyFrame class does not have active Units we must handle it
+   separately. */
+      if( astIsASkyFrame( frm ) ) {
+
+/* Convert the axis values and radii to radians. */
+         map = astUnitMapper( unit, "rad", NULL, NULL );
+         if( map ) {
+            astTran1( map, 2, cen, 1, cen );
+            astTran1( map, 2, rad, 1, rad );
+            map = astAnnul( map );
+         } else if( astOK ) {
+            Report( this, elem, FAILURE, "contains unusable units", status );
+         }
+
+/* Create the Ellipse. */
+         new = (AstRegion *) astEllipse( frm, 1, cen, rad, &pa, NULL, "", status );
+
+/* Now handles Frames other than SkyFrames. */
+      } else {
+
+/* Take a copy of the supplied Frame and set its Units to the value
+   obtained from the supplied element. */
+         cfrm = astCopy( frm );
+         astSetUnit( cfrm, 0, unit );
+         astSetUnit( cfrm, 1, unit );
+
+/* Create a Ellipse within this modified Frame. */
+         new = (AstRegion *) astEllipse( cfrm, 1, cen, rad, &pa, NULL, "", status );
+
+/* If the Unit of this Region differs from that of the supplied Frame,
+   set it to the Unit of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new Unit. If the supplied
+   Frame had no set Unit, set it to the units obtained from the supplied
+   element. */
+         for( i = 0; i < 2; i++ ) {
+            if( astTestUnit( frm, i ) ) {
+               funit = astGetUnit( frm, i );
+               if( strcmp( funit, unit ) ) astSetUnit( new, i, funit );
+            } else {
+               astSetUnit( frm, i, unit );
+            }
+         }
+
+/* Free resources */
+         cfrm = astAnnul( cfrm );
+      }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static double Error2PAReader( AstXmlChan *this, AstXmlElement *elem,
+                              double *size, int *status ){
+/*
+*  Name:
+*     Error2PAReader
+
+*  Purpose:
+*     Read the contents of an Stc Error2PA element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     double Error2PAReader( AstXmlChan *this, AstXmlElement *elem,
+*                            double *size, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function reads the contents of an Stc Error2PA element. It can
+*     also be used to read Resolution2PA, Size2PAand PixSize2PA which
+*     have exactly the same structure as a Error2PA element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Ellipse element.
+*     size
+*        Pointer to an array to receive the 2 error sizes.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The position angle of the first error size, in radians, from positive
+*     second axis to positive first axis.
+
+*/
+
+/* Local Variables: */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[4];         /* Names of the subelements to be searched for */
+   double pa;                    /* Major axis position angle */
+   int max[4];                   /* Max allowed occurrences of each name */
+   int min[4];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   pa = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return pa;
+
+/* Scan the supplied element for the required sub-elements */
+   names[ 0 ] = "Size";
+   names[ 1 ] = "PosAngle";
+   min[ 0 ] = 1;
+   min[ 1 ] = 0;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the sizes */
+      ElemListD( this, scan->el[0][0], 2, size, status );
+
+/* Get the position angle. This is returned in the AST convention, i.e.
+   measured in radians from from +ve second axis through positive first
+   axis. */
+      pa = PosAngleReader( this, scan->el[1][0], status );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Return the position angle. */
+   return pa;
+}
+
+static void FillAndLims( AstXmlChan *this, AstXmlElement *elem, AstRegion *new, int *status ){
+/*
+*  Name:
+*     FillAndLims
+
+*  Purpose:
+*     Get fill factor and limit inclusion flags from IVOA element and
+*     assign to an AST Region.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void FillAndLims( AstXmlChan *this, AstXmlElement *elem, AstRegion *new, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function gets attributes from the supplied element describing
+*     fill factor and limit inclusion flags, and assigns suitable values
+*     to the supplied Region. Default values are used if the supplied
+*     element does not have the required attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan in which to store warnings.
+*     elem
+*        Pointer to the AstXmlElement to search.
+*     new
+*        Pointer to the Region in which to store the values.
+*     status
+*        Pointer to the inherited status variable.
+
+*/
+
+/* Local Variables: */
+   const char *text;        /* Attribute text */
+   double ff;               /* Fill factor */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Get any fill factor attribute from the element and assign to the
+   returned Region. */
+   ff = AttrValueD( this, elem, "fill_factor", AST__BAD, status );
+   if( ff != AST__BAD ) astSetFillFactor( new, ff );
+
+/* Get the flags indicating if the limits are included in the interval.
+   If either of the limits is not included in the interval, then make the
+   Region open. Assume a default of true ("included") if the attribute is
+   missing. */
+   text = astXmlGetAttributeValue( elem, "lo_include" );
+   if( text && !strcmp( text, "false" ) ) astSetClosed( new, 0 );
+
+   text = astXmlGetAttributeValue( elem, "hi_include" );
+   if( text && !strcmp( text, "false" ) ) astSetClosed( new, 0 );
+
+}
+
+static AstXmlElement *FindAttribute( AstXmlChan *this, const char *name, int *status ) {
+/*
+*  Name:
+*     FindAttribute
+
+*  Purpose:
+*     Find an XML element representing a named AST attribute.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstXmlElement *FindAttribute( AstXmlChan *this, const char *name, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function searches the content of the current container element
+*     of the supplied XmlChan looking for an element which represents a
+*     named AST attribute. No error is reported if the attribute is not
+*     found. Attributes which represent defaul values are ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     name
+*        Pointer to a string holding the required AST attribute name
+*        (case-insensitive).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the XmlElement if found, and NULL otherwise.
+
+*/
+
+/* Local Variables: */
+   AstXmlContentItem *item; /* Item no. "i" */
+   AstXmlElement *result;   /* Returned pointer */
+   const char *def;         /* Value from XML DEFAULT attribute */
+   const char *definedby;   /* Name of class which defines the item */
+   const char *xmlname;     /* Value from XML NAME attribute */
+   int i;                   /* Index of current item */
+   int nitem;               /* Number of items still in the element */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Report an error if the class being loaded has not been set. */
+   if( !this->isa_class ) {
+      astError( AST__INTER, "astRead(XmlChan): astReadNextData not called "
+                "before reading values for a %s (internal AST programming "
+                "error).", status, astXmlGetName( this->container ) );
+   }
+
+/* Check we have a container to search. */
+   if( !this->container ) {
+      astError( AST__INTER, "astRead(XmlChan): No container before reading "
+                "values for a %s (internal AST programming error).", status,
+                astXmlGetName( this->container ) );
+   }
+
+/* Check all is OK. */
+   if( astOK ) {
+
+/* Loop round all items in the elements contents. */
+      nitem = astXmlGetNitem( this->container );
+      for( i = 0; i < nitem; i++ ) {
+         item = astXmlGetItem( this->container, i );
+
+/* Ignore this item if it is not an element. */
+         if( astXmlCheckType( item, AST__XMLELEM ) ) {
+
+/* Ignore this element if its name is not ATTR. */
+            if( !astOK ) break;
+            if( !strcmp( astXmlGetName( item ), ATTR ) ){
+
+/* Ignore this element if it represents a default value. */
+               def = astXmlGetAttributeValue( item, DEFAULT );
+               if( !def || strcmp( def, TRUE ) ) {
+
+/* If this ATTR element has an XML attribute called NAME with
+   the required value (case-insensitive), we may have found a matching
+   element. */
+                   xmlname = astXmlGetAttributeValue( item, NAME );
+                   if( xmlname && !Ustrcmp( xmlname, name, status ) ) {
+
+/* Ignore the attribute if it does not belong to the correct part of the
+   object's class hierarchy. If it does, we have found the required
+   attribute. */
+                      definedby = astXmlGetAttributeValue( item, DEFINEDBY );
+                      if( definedby && !strcmp( definedby, this->isa_class ) ) {
+                         result = (AstXmlElement *) item;
+                         break;
+                      }
+                   }
+               }
+            }
+         }
+      }
+   }
+
+/* Return the pointer. */
+   return result;
+}
+
+static AstXmlElement *FindElement( AstXmlChan *this, AstXmlElement *elem,
+                                   const char *name, int *status ) {
+/*
+*  Name:
+*     FindElement
+
+*  Purpose:
+*     Find a named element within a supplied element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstXmlElement *FindElement( AstXmlChan *this, AstXmlElement *elem,
+*                                 const char *name, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function searches the content of the supplied element looking for
+*     an element with the supplied Name. No error is reported if the element
+*     is not found, but a Warning is issued if it found more than once.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan in which to store warnings.
+*     elem
+*        Pointer to the AstXmlElement to search.
+*     name
+*        Pointer to a string holding the required element name.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the XmlElement if found, and NULL otherwise.
+
+* Notes:
+*     - If the supplied element contains more than one element with the
+*     given name, the returned pointer locates the first element
+*     encountered with the required name, and a WARNING is issued that the
+*     second and subsequent elements will be ignored.
+
+*/
+
+/* Local Variables: */
+   AstXmlContentItem *item; /* Item no. "i" */
+   AstXmlElement *result;   /* Returned pointer */
+   char buff[ 200 ];        /* Message buffer */
+   int i;                   /* Index of current item */
+   int nitem;               /* Number of items still in the element */
+   int warned;              /* Has a warning been issued? */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Loop round all items in the elements contents. */
+   warned = 0;
+   nitem = astXmlGetNitem( elem );
+   for( i = 0; i < nitem; i++ ) {
+      item = astXmlGetItem( elem, i );
+
+/* Ignore this item if it is not an element. */
+      if( astXmlCheckType( item, AST__XMLELEM ) ) {
+
+/* If this element's name is the given name. */
+         if( !strcmp( astXmlGetName( item ), name ) ){
+
+/* If this is the first element with the required name, store its
+   pointer. */
+            if( !result ) {
+               result = (AstXmlElement *) item;
+
+/* Otherwise add a Warning (unles a Warning has already been issued). */
+            } else if( !warned ) {
+               warned = 1;
+               sprintf( buff, "contains more than one %s element. The "
+                        "second and subsequent such elements will be "
+                        "ignored", name );
+               Report( this, elem, WARNING, buff, status );
+            }
+         }
+      }
+   }
+
+/* Return the pointer. */
+   return result;
+}
+
+static IVOAReader FindIVOAClass( AstXmlElement *elem, int *is_ivoa, int *status ) {
+/*
+*  Name:
+*     FindIVOAClass
+
+*  Purpose:
+*     Return a pointer to a function which will create an AST Object from
+*     an IVOA element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     IVOAReader FindIVOAClass( AstXmlElement *elem, int *is_ivoa, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function checks the namespace of the supplied element to see if
+*     it is a known IVOA namespace. If it is, it returns the "is_ivoa"
+*     flag set to non-zero (otherwise it is returned as zero). It then
+*     checks to see if an AST object can be created from the IVOA
+*     element. If so, a pointer to the function which will do the
+*     conversion is returned. Otherwise a NULL pointer is returned.
+
+*  Parameters:
+*     elem
+*        Pointer to the element to check.
+*     is_ivoa
+*        Pointer to an int in which to return a flag indicating if the
+*        supplied element belongs to a known IVOA namespace.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the function (if any) which can produce an AST Object
+*     from the supplied element, or NULL if conversion is not possible.
+
+*  Notes:
+*     - NULL is returned if this function is invoked with the error
+*     status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   IVOAReader result;      /* Returned pointer */
+   const char *ivoa;       /* Pointer to "ivoa" substring */
+   const char *name;       /* Pointer to string holding element name */
+   const char *stc;        /* Pointer to "stc" substring */
+   const char *uri;        /* Pointer to string holding element namespace URI */
+
+/* Initialise */
+   *is_ivoa = 0;
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the element name.*/
+   name = astXmlGetName( elem );
+
+/* Get the namespace URI for the element, and see if it contains
+   sub-strings "STC" (or "stc") and "IVOA" (or "ivoa"). */
+   uri = astXmlGetURI( elem );
+   if( uri ) {
+      stc = strstr( uri, "STC" );
+      if( !stc ) stc = strstr( uri, "stc" );
+      ivoa = strstr( uri, "IVOA" );
+      if( !ivoa ) ivoa = strstr( uri, "ivoa" );
+
+   } else {
+      stc = NULL;
+      ivoa = NULL;
+   }
+
+/* If it is a known IVOA namespace, proceed. */
+   if( name && ( stc || ivoa ) ){
+      *is_ivoa = 1;
+
+/* Look for element types which can be converted to AST objects, and
+   return a pointer to the corresponding reader function. */
+      if( !strcmp( name, STC_RESOURCE_PROFILE ) ) {
+         result = StcMetadataReader;
+
+      } else if( !strcmp( name, SEARCH_LOCATION ) ) {
+         result = StcMetadataReader;
+
+      } else if( !strcmp( name, CATALOG_ENTRY_LOCATION ) ) {
+         result = StcMetadataReader;
+
+      } else if( !strcmp( name, OBSERVATION_LOCATION ) ) {
+         result = StcMetadataReader;
+
+      } else if( !strcmp( name, OBS_DATA_LOCATION ) ) {
+         result = ObsDataLocationReader;
+
+      } else if( !strcmp( name, ASTRO_COORD_SYSTEM ) ) {
+         result = AstroCoordSystemReader;
+
+      } else if( !strcmp( name, TIME_FRAME ) ) {
+         result = TimeFrameReader;
+
+      } else if( !strcmp( name, SPACE_FRAME ) ) {
+         result = SpaceFrameReader;
+
+      } else if( !strcmp( name, SPECTRAL_FRAME ) ) {
+         result = SpectralFrameReader;
+
+      } else if( !strcmp( name, REDSHIFT_FRAME ) ) {
+         result = RedshiftFrameReader;
+
+      } else if( !strcmp( name, REDSHIFT_FRAME ) ) {
+         result = RedshiftFrameReader;
+
+      }
+   }
+
+/* Return null if an error occurred. */
+   if( !astOK ) result = NULL;
+
+/* Return the result. */
+   return result;
+}
+
+static const char *FindNextIsA( AstXmlElement *elem, int start, int *status ) {
+/*
+*  Name:
+*     FindNextIsA
+
+*  Purpose:
+*     Find the next "isa" element within an XML element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     const char *FindNextIsA( AstXmlElement *elem, int start, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function searches the content of the specified element,
+*     starting at the item with the speicfied index, until it finds the
+*     next "isa" element. It returns the value of the "class" attribute
+*     of the found "isa" element, or the name of the supplied element
+*     if no "isa" element is found.
+
+*  Parameters:
+*     elem
+*        Pointer to the XmlElement (an element describing an AST Object).
+*     start
+*        The index of the first content item to check.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the class string.
+
+*/
+
+/* Local Variables: */
+   AstXmlContentItem *item; /* Item no. "i" */
+   const char *result;      /* Returned string */
+   int i;                   /* Index of current item */
+   int nitem;               /* Number of items still i nthe element */
+
+/* Initialise */
+   result = astXmlGetName( elem );
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Loop round all items contained in the element, starting at the given
+   index. */
+   nitem = astXmlGetNitem( elem );
+   for( i = start; i < nitem; i++ ) {
+      item = astXmlGetItem( elem, i );
+
+/* Check this item is an XmlElement with name ISA. */
+      if( astXmlCheckType( item, AST__XMLELEM ) ) {
+         if( astOK && !strcmp( astXmlGetName( item ), ISA ) ) {
+
+/* The returned string is the value of the "class" attribute of this
+   element. */
+            result = astXmlGetAttributeValue( item, "class" );
+
+/* Report an error if the element does not have a class attribute. */
+            if( !result && astOK ) {
+               astError( AST__BADIN, "astRead(XmlChan): The tag %s "
+                         "does not include a \"class\" attribute.", status,
+                         GetTag( (AstXmlObject *) item, 1, status ) );
+            }
+
+            break;
+
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstXmlElement *FindObject( AstXmlChan *this, const char *name, int *status ) {
+/*
+*  Name:
+*     FindObject
+
+*  Purpose:
+*     Find an XML element representing a named AST Object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstXmlElement *FindObject( AstXmlChan *this, const char *name, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function searches the content of the current container element
+*     of the supplied XmlChan looking for an element which represents a
+*     named AST Object. No error is reported if the object is not
+*     found. Objects which represent default values are ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     name
+*        Pointer to a string holding the required AST object name
+*        (case-insensitive).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the XmlElement if found, and NULL otherwise.
+
+*/
+
+/* Local Variables: */
+   AstXmlContentItem *item; /* Item */
+   AstXmlElement *result;   /* Returned pointer */
+   const char *def;         /* Value from XML DEFAULT attribute */
+   const char *definedby;   /* Name of class which defines the item */
+   const char *xmlname;     /* Value from XML LABEL attribute */
+   int i;                   /* Index of current item */
+   int nitem;               /* Number of items still i nthe element */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Report an error if the class being loaded has not been set. */
+   if( !this->isa_class ) {
+      astError( AST__INTER, "astRead(XmlChan): astReadNextData not called "
+                "before reading values for a %s (internal AST programming "
+                "error).", status, astXmlGetName( this->container ) );
+   }
+
+/* Check we have a container to search. */
+   if( !this->container ) {
+      astError( AST__INTER, "astRead(XmlChan): No container before reading "
+                "values for a %s (internal AST programming error).", status,
+                astXmlGetName( this->container ) );
+   }
+
+/* Check all is OK. */
+   if( astOK ) {
+
+/* Loop round all items in the elements contents. */
+      nitem = astXmlGetNitem( this->container );
+      for( i = 0; i < nitem; i++ ) {
+         item = astXmlGetItem( this->container, i );
+
+/* Ignore this item if it is not an element. */
+         if( astXmlCheckType( item, AST__XMLELEM ) ) {
+
+/* Ignore this element if its name is ATTR. */
+            if( astOK && strcmp( astXmlGetName( item ), ATTR ) ){
+
+/* Ignore this element if it represents a default value. */
+               def = astXmlGetAttributeValue( item, DEFAULT );
+               if( !def || strcmp( def, TRUE ) ) {
+
+/* If this non-ATTR element has an XML attribute called LABEL with
+   the required value (case-insensitive), we may have found a matching element. */
+                   xmlname = astXmlGetAttributeValue( item, LABEL );
+                   if( xmlname && !Ustrcmp( xmlname, name, status ) ) {
+
+/* Ignore the element if it does not belong to the correct part of the
+   object's class hierarchy. If it does, we have found the required
+   object. */
+                      definedby = astXmlGetAttributeValue( item, DEFINEDBY );
+                      if( definedby && !strcmp( definedby, this->isa_class ) ) {
+                         result = (AstXmlElement *) item;
+                         break;
+                      }
+                   }
+               }
+            }
+         }
+      }
+   }
+
+/* Return the pointer. */
+   return result;
+}
+
+static int FindString( int n, const char *list[], const char *test,
+                       const char *text, const char *method,
+                       const char *class, int *status ){
+/*
+*  Name:
+*     FindString
+
+*  Purpose:
+*     Find a given string within an array of character strings.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int FindString( int n, const char *list[], const char *test,
+*                     const char *text, const char *method, const char *class, int *status )
+
+*  Class Membership:
+*     XmlChan method.
+
+*  Description:
+*     This function identifies a supplied string within a supplied
+*     array of valid strings, and returns the index of the string within
+*     the array. The test option may not be abbreviated, but case is
+*     insignificant.
+
+*  Parameters:
+*     n
+*        The number of strings in the array pointed to be "list".
+*     list
+*        A pointer to an array of legal character strings.
+*     test
+*        A candidate string.
+*     text
+*        A string giving a description of the object, parameter,
+*        attribute, etc, to which the test value refers.
+*        This is only for use in constructing error messages. It should
+*        start with a lower case letter.
+*     method
+*        Pointer to a string holding the name of the calling method.
+*        This is only for use in constructing error messages.
+*     class
+*        Pointer to a string holding the name of the supplied object class.
+*        This is only for use in constructing error messages.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The index of the identified string within the supplied array, starting
+*     at zero.
+
+*  Notes:
+*     -  A value of -1 is returned if an error has already occurred, or
+*     if this function should fail for any reason (for instance if the
+*     supplied option is not specified in the supplied list).
+
+*/
+
+/* Local Variables: */
+   int ret;                /* The returned index */
+
+/* Check global status. */
+   if( !astOK ) return -1;
+
+/* Compare the test string with each element of the supplied list. Leave
+   the loop when a match is found. */
+   for( ret = 0; ret < n; ret++ ) {
+      if( !Ustrcmp( test, list[ ret ], status ) ) break;
+   }
+
+/* Report an error if the supplied test string does not match any element
+   in the supplied list. */
+   if( ret >= n && astOK ) {
+      astError( AST__RDERR, "%s(%s): Illegal value '%s' supplied for %s.", status,
+                method, class, test, text );
+      ret = -1;
+   }
+
+/* Return the answer. */
+   return ret;
+}
+
+static IVOAScan *FreeIVOAScan( IVOAScan *in, int *status ){
+/*
+*  Name:
+*     FreeIVOAScan
+
+*  Purpose:
+*     Free resources used by an IVOAScan structure.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     IVOAScan *FreeIVOAScan( IVOAScan *in )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function frees resources used by an IVOAScan structure (such
+*     as returned by the ScanIVOAElement function).
+
+*  Parameters:
+*     in
+*        Pointer to the IVOAScan structure.
+
+*  Returned Value:
+*     A NULL pointer.
+
+*/
+
+/* Local Variables: */
+   int j;                        /* Index of current name */
+
+/* Check the supplied pointer can be used safely. */
+   if( in ) {
+
+/* Free the arrays holding the element pointers. */
+      for( j = 0; j < in->n; j++ ) {
+         in->count[ j ] = 0;
+         in->el[ j ] = astFree( in->el[ j ] );
+      }
+
+/* Free the array holding the pointers to the arrays holding the element
+   pointers. */
+      in->el = astFree( in->el );
+
+/* Free the array holding the number of element pointers stored. */
+      in->count = astFree( in->count );
+
+/* For safety, put a zero in the name count. */
+      in->n = 0;
+
+/* Free the whole structure. */
+      in = astFree( in );
+
+   }
+
+   return NULL;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a XmlChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected astGetAttrib
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a XmlChan, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     attrib
+*        Pointer to a null terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the XmlChan, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the XmlChan. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   AstXmlChan *this;             /* Pointer to the XmlChan structure */
+   const char *result;           /* Pointer value to return */
+   int ival;                     /* Integer attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* XmlLength */
+/* --------- */
+   if ( !strcmp( attrib, "xmllength" ) ) {
+      ival = astGetXmlLength( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%d", ival );
+         result = getattrib_buff;
+      }
+
+/* XmlFormat */
+/* --------- */
+   } else if ( !strcmp( attrib, "xmlformat" ) ) {
+      ival = astGetXmlFormat( this );
+      if ( astOK ) {
+         if( ival == NATIVE_FORMAT ){
+            result = NATIVE_STRING;
+
+         } else if( ival == QUOTED_FORMAT ){
+            result = QUOTED_STRING;
+
+         } else if( ival == IVOA_FORMAT ){
+            result = IVOA_STRING;
+
+         } else {
+            result = UNKNOWN_STRING;
+         }
+      }
+
+/* XmlPrefix */
+/* --------- */
+   } else if ( !strcmp( attrib, "xmlprefix" ) ) {
+      result = astGetXmlPrefix( this );
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+
+static int GetComment( AstChannel *this, int *status ) {
+/*
+*  Name:
+*     GetComment
+
+*  Purpose:
+*     Get the value of the Comment attribute of a Channel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int GetComment( AstChannel *this, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected astGetComment
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function returns the value of the Comment attribute of the XmlChan.
+*     It changs the default value from 1 (provided by the parent Channel
+*     class) to zero.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - The Comment value.
+*/
+
+   return astTestComment( this ) ? (*parent_getcomment)( this, status ) : 0;
+}
+
+static int GetFull( AstChannel *this, int *status ) {
+/*
+*  Name:
+*     GetFull
+
+*  Purpose:
+*     Get the value of the Full attribute of a Channel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int GetFull( AstChannel *this, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected astGetFull
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function returns the value of the Full attribute of the XmlChan.
+*     It changs the default value from zero (provided by the parent Channel
+*     class) to -1.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - The Full value.
+*/
+
+   return astTestFull( this ) ? (*parent_getfull)( this, status ) : -1;
+}
+
+static int GetIndent( AstChannel *this, int *status ) {
+/*
+*  Name:
+*     GetIndent
+
+*  Purpose:
+*     Get the value of the Indent attribute for an XmlChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "XmlChan.h"
+*     int GetIndent( AstChannel *this, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected astGetIndent
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function returns the value of the Indent attribute, supplying
+*     a default value appropriate to an XmlChan.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - The Indent value to use.
+
+*/
+
+/* If the attribute is set, return its value. Otherwise return a value of
+   zero. */
+   return astTestIndent( this ) ? (*parent_getindent)( this, status ) : 0;
+}
+
+
+static char GetNextChar( void *data, int *status ) {
+/*
+*  Name:
+*     GetNextChar
+
+*  Purpose:
+*     Get the next character from the XML source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     char GetNextChar( void *data, int *status )
+
+*  Class Membership:
+*     XmlChan member function
+
+*  Description:
+*     This function returns the next character from the XML source,
+*     getting a new string if necessary.
+
+*  Parameters:
+*     data
+*        Pointer to a structure holding data needed to perform the read.
+*        This should be a pointer to the XmlChan being read. If NULL is
+*        supplied, then any internal resources are freed and a value of
+*        zero is returned.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - The next source character, or zero if NULL is supplied for "data".
+
+*  Notes:
+*     - Zero is returned if there is no more text to read.
+*     - Zero is returned if an error has already occurred, or if this
+*     function should failed for any reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS        /* Declare the thread specific global data */
+   AstXmlChan *this;         /* Pointer to the XmlChan */
+   char result;              /* The returned character */
+
+/* Initiialise */
+   result = 0;
+
+/* Get a pointer to the XmlChan. */
+   this = (AstXmlChan *) data;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* If a NULL pointer is supplied free any memory holding text already
+   read from the source, and return zero. */
+   if( !data ) {
+      getnextchar_buf = astFree( getnextchar_buf );
+      return 0;
+   }
+
+/* Check the global status */
+   if( !astOK ) return result;
+
+/* We read a new line from the source if: 1) the reset flag is set in the
+   XmlChan, 2) we have reached the terminating null in the previous line,
+   or 3) we do not yet have a line of text. */
+   if( this->reset_source || *getnextchar_c == 0 || !getnextchar_buf ) {
+      this->reset_source = 0;
+
+/* Free the memory used to hold any previous text. */
+      if( getnextchar_buf ) getnextchar_buf = astFree( getnextchar_buf );
+
+/* Read a new line of text from the source. */
+      getnextchar_buf = astGetNextText( this );
+
+/* Read a new line if the previous line was empty. */
+      while( getnextchar_buf && !getnextchar_buf[ 0 ] ) {
+         astFree( getnextchar_buf );
+         getnextchar_buf = astGetNextText( this );
+      }
+
+/* Reset the pointer to the next character to the start of the new
+   string. */
+      getnextchar_c = getnextchar_buf;
+
+/* If all has gone OK, return the first character and then increment getnextchar_c to
+   point to the next character. */
+      if( getnextchar_c && astOK ) result = *(getnextchar_c++);
+
+/* If we are reading a previously read line, return the character located
+   by getnextchar_c and increment getnextchar_c. */
+   } else {
+      result = *(getnextchar_c++);
+   }
+
+/* Return the result */
+   return result;
+
+}
+
+static const char *GetTag( AstXmlObject *this, int opening, int *status ){
+/*
+*  Name:
+*     GetTag
+
+*  Purpose:
+*     Returns a string holding an XML tag describing the given XmlObject.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     const char *GetTag( AstXmlObject *this, int opening, int *status )
+
+*  Description:
+*     This function returns a pointer to static string
+*     containing an XML tag describing the given XmlObject. It is a
+*     wrapper for the astXmlGetTag function defined in xml.h, but it
+*     additionally removes any "definedby" attribute before formating the
+*     tag (the "definedby" attribute is added by the ReadClassData
+*     function and is not part of the XML text read from the source).
+
+*  Parameters:
+*     this
+*        Pointer to the XmlObject.
+*     opening
+*        Indicates which tag is to be returned; the start tag or the end
+*        tag. If non-zero the start tag is returned. Otherwise, the
+*        end tag is returned. If the supplied XmlObject has no end
+*        tag (i.e. if it is an empty element, or if it is not an element),
+*        then NULL is returned but no error is reported.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to a null terminated static string holding the tag.
+
+*  Notes:
+*     - Empty elements are represented as an start tag of the form <.../>,
+*     with no corresponding end tag.
+*     - NULL is returned if an error has already occurred, or if this
+*     function should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlElement *elem;      /* Pointer to XML element */
+   const char *result;       /* The returned pointer */
+   const char *ptr;          /* The value of the "definedby" attribute */
+   const char *class;        /* Copy of the value of the "definedby" attribute */
+
+/* Initialise */
+   result = NULL;
+
+/* If the object is an element, check for the "definedby" attribute. */
+   if( astXmlCheckType( this, AST__XMLELEM ) ) {
+      elem = (AstXmlElement *) this;
+
+/* See if the element contains a "definedby" attribute. */
+      ptr = astXmlGetAttributeValue( elem, DEFINEDBY );
+
+/* If so, temporarily remove it, format the tag and then put it back. */
+      if( ptr ) {
+         class = astStore( NULL, ptr, strlen( ptr ) + 1 );
+         astXmlRemoveAttr( elem, DEFINEDBY, NULL );
+         result = astXmlGetTag( elem, opening );
+         astXmlAddAttr( elem, DEFINEDBY, class, NULL );
+         class = astFree( (void *) class );
+
+/* If not, just use astXmlGetTag. */
+      } else {
+         result = astXmlGetTag( this, opening );
+      }
+
+/* If the object is not an element, just use astXmlGetTag. */
+   } else {
+      result = astXmlGetTag( this, opening );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstRegion *IntersectionReader( AstXmlChan *this, AstXmlElement *elem,
+                                      AstFrame *frm, int *status ){
+/*
+*  Name:
+*     IntersectionReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Intersection region element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *IntersectionReader( AstXmlChan *this, AstXmlElement *elem,
+*                                    AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Intersection region element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Intersection region element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstRegion *new;               /* Pointer to returned Region */
+   AstRegion *reg;               /* Pointer to component Region */
+   AstRegion *tmp;               /* Pointer to new Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[1];         /* Names of the subelements to be searched for */
+   int i;                        /* Loop count */
+   int max[1];                   /* Max allowed occurrences of each name */
+   int min[1];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for a Region sub-element. */
+   names[ 0 ] = "Intersection|Union|Negation|AllSky|Circle|Ellipse|Polygon|"
+                "Convex|Box";
+   min[ 0 ] = 2;
+   max[ 0 ] = INT_MAX;
+   scan = ScanIVOAElement( this, elem, 1, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Create Regions from all the component region elements, and combine
+   them into nested CmpRegions, using the boolean AND operator to combine
+   them. */
+      new = RegionReader( this, scan->el[0][0], frm, status );
+      for( i = 1; i < scan->count[0]; i++ ) {
+         reg = RegionReader( this, scan->el[0][i], frm, status );
+         tmp = (AstRegion *) astCmpRegion( new, reg, AST__AND, "", status );
+         reg = astAnnul( reg );
+         (void) astAnnul( new );
+         new = tmp;
+      }
+
+/* Get any fill factor from the element and assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static int IsUsable( AstXmlElement *elem, int *status ){
+/*
+*  Name:
+*     IsUsable
+
+*  Purpose:
+*     See if an XmlElement could describe an AST object.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     int IsUsable( AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function checks if an instance of an AST class could be
+*     created from the supplied XmlElement.
+
+*  Parameters:
+*     elem
+*        A pointer to the XmlElement, or NULL.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If an AST Object could be created from the supplied element, +1 is
+*     returned. Otherwise, -1 is returned. Zero is returned if the supplied
+*     pointer is NULL.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Declare the thread specific global data */
+   const char *class;            /* Pointer to element name */
+   const char *uri;              /* Pointer to namespace URI */
+   IVOAReader reader;            /* Pointer to reader function */
+   int is_ivoa;                  /* Element belongs to an IVOA namespace? */
+   int oldrep;                   /* Original value of the Reporting flag */
+   int result;                   /* Result value to be returned */
+
+/* Check the global error status, and the supplied pointer. */
+   if ( !astOK || !elem ) return 0;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise */
+   result = -1;
+
+/* See if the element is in a supported IVOA namespace, and has a reader
+   function for converting it to an AST object. If so, set the default
+   XmlFormat to IVOA, and set the result non-zero. */
+   reader = FindIVOAClass( elem, &is_ivoa, status );
+   if( is_ivoa ){
+      if( reader ) result = 1;
+      if( isusable_this ) isusable_this->formatdef = IVOA_FORMAT;
+   }
+
+/* If the element is not an IVOA class, only proceed if the URI is not
+   defined, or if it the AST URI. */
+   uri = astXmlGetURI( elem );
+   if( result == -1 && ( !uri || !strcmp( uri, AST__XMLNS ) ) ) {
+
+/* Get the element name. This will be an AST class name if the element
+   describes an AST Object. */
+      class = astXmlGetName( elem );
+
+/* Attempt to get the loader for a class of this name. If no loader exists an
+   error would normally be reported. Therefore we switch off error reporting
+   before making this call. After the class we clear any error status and
+   switch error reporting back on. If no error occurs whilst getting the
+   loader, then the class name must be a valid AST class name and so return
+   a non-zero result value. */
+      if( astOK ) {
+         oldrep = astReporting( 0 );
+         astGetLoader( class, status );
+         if( astOK ) {
+            result = 1;
+         } else {
+            astClearStatus;
+         }
+         astReporting( oldrep );
+      }
+
+/* If the element is in no namespace, use the AST URI as the default
+   namespace for it and its children. */
+      if( !uri ) astXmlAddURI( elem, NULL, AST__XMLNS );
+
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstObject *MakeAstFromXml( AstXmlChan *this, AstXmlElement *elem, int *status ) {
+/*
+*  Name:
+*     MakeAstFromXml
+
+*  Purpose:
+*     Make an AST Object from an XML element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstObject *MakeAstFromXml( AstXmlChan *this, AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Object from the supplied XML element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the XML element containing a description of the AST
+*        object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object.
+*/
+
+/* Local Variables: */
+   AstLoaderType *loader;        /* Pointer to loader for Object */
+   AstObject *new;               /* Pointer to returned Object */
+   AstXmlParent *old_container;  /* Element from which items are being read */
+   IVOAReader reader;            /* Pointer to reader function */
+   const char *class;            /* Pointer to Object class name string */
+   int is_ivoa;                  /* Element belongs to an IVOA namespace? */
+   int i;                        /* Index of content item */
+   int nitem;                    /* No. of items of content within element */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* First deal with IVOA format. */
+/* ---------------------------- */
+   if( astGetXmlFormat( this ) == IVOA_FORMAT ) {
+
+/* Get a pointer to a function which will produce an AST object from
+   elements of the class of the supplied element. */
+      reader = FindIVOAClass( elem, &is_ivoa, status );
+
+/* If found, invoke the function to create the new AST object. */
+      if( is_ivoa && reader ) {
+         new = ( *reader )( this, elem, status );
+
+/* IVOA reader functions do not remove used content as they are read
+   from the element (unlike AST native readers). Therefore empty the
+   element of all content now to indicate that the element contained no
+   unrecognised content. This prevents an error being reported. If there
+   was in fact any unrecognised content, then an error will already have
+   been reported. */
+         nitem = astXmlGetNitem( elem );
+         for( i = nitem - 1; i >= 0; i-- ) astXmlDelete( astXmlGetItem( elem, i ) );
+
+/* If not found, report an error. This should not happen since the IsUsable
+   function should already have checked that the element is usable. */
+      } else if( astOK ){
+         astError( AST__INTER, "astRead(XmlChan): MakeAstFromIVOA does not "
+                   "support IVOA class \"%s\" (internal AST programming error).", status,
+                   astXmlGetName( elem ) );
+      }
+
+/* Now deal with other (i.e. NATIVE and QUOTED) format. */
+/* ---------------------------------------------------- */
+   } else {
+
+/* Get the AST class name. This is the name of the XML element. */
+      class = astXmlGetName( elem );
+
+/* Use the associated class name to locate the loader for that
+   class. This function will then be used to build the Object. */
+      loader = astGetLoader( class, status );
+
+/* If OK, save the pointer to the current container element, and indicate
+   that the supplied element is now to be used as the current container.
+   The "current container" is the XML element from which values are being
+   read. */
+      if( astOK ) {
+         old_container = this->container;
+         this->container = (AstXmlParent *) elem;
+
+/* The "isa_class" item in the XmlChan structure contains a pointer to
+   the name of the class whose loader is currently being invoked. It is set
+   by the loader itself as a side effect of calling the astReadClassData
+   function. Initialise it to NULL to indicate that astReadClassData has
+   not yet been called. */
+         this->isa_class = NULL;
+
+/* Invoke the loader, which reads the Object definition from the
+   current XML container (i.e. the supplied XML element) and builds the
+   Object. Supply NULL/zero values to the loader so that it will substitute
+   values appropriate to its own class. */
+         new = (*loader)( NULL, (size_t) 0, NULL, NULL, (AstChannel *)
+                          this, status );
+
+/* Re-instate the original container. */
+         this->container = old_container;
+      }
+   }
+
+/* If an error occurred, clean up by deleting the new Object and
+   return a NULL pointer. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the pointer to the new Object. */
+   return new;
+}
+
+static double MakeMJD( AstTimeFrame *frm, double time, int *status ) {
+/*
+*  Name:
+*     MakeMJD
+
+*  Purpose:
+*     Create an MJD value from a TimeFrame axis value.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     double MakeMJD( AstTimeFrame *frm, double time, int *status )
+
+*  Class Membership:
+*     XmlChan member function
+
+*  Description:
+*     This function converts the supplied time value from the system
+*     represented by the supplied TimeFrame into an absolute TBD MJD,
+*     in units of days.
+
+*  Parameters:
+*     frm
+*        Pointer to the TimeFrame defining the system in which "time" is
+*        supplied.
+*     time
+*        The time value to convert.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The equivalent MJD value.
+
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;
+   AstTimeFrame *cfrm;
+   double result;
+
+/* Initialise. */
+   result = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Create a copy of the supplied TimeFrame, and set its attributes to
+   describe the required MJD system. */
+   cfrm = astCopy( frm );
+   astSetSystem( cfrm, AST__MJD );
+   astSetUnit( cfrm, 0, "d" );
+   astSetTimeScale( cfrm, AST__TDB );
+   astSetTimeOrigin( cfrm, 0.0 );
+
+/* Find the Mapping from the supplied TimeFrame to this TimeFrame. Use it to
+   transform the supplied time value */
+   fs = astConvert( frm, cfrm, "" );
+   if( fs ) {
+      astTran1( fs, 1, &time, 1, &result );
+
+/* Free resources */
+      fs = astAnnul( fs );
+   }
+   cfrm = astAnnul( cfrm );
+
+/* Result */
+   return result;
+
+}
+
+static AstXmlElement *MakePos2D( AstXmlChan *this, AstXmlElement *elem, int *status ){
+/*
+*  Name:
+*     MakePos2D
+
+*  Purpose:
+*     Create an STC Position2D element from the supplied Position3D.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstXmlElement *MakePos2D(  AstXmlChan *this, AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function returns a pointer to a Position2D element by throwing
+*     away the last axis in the supplied Position3D element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the Position3D element.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Position2D element.
+
+
+*/
+
+/* Local Variables: */
+   AstXmlElement *el;            /* Pointer to subelement */
+   AstXmlElement *new;           /* Pointer to returned XmlElement */
+   IVOAScan *scan;               /* Structure holding scan results */
+   char **words;                 /* Array of words read from string */
+   char *unit2;                  /* New Unit string */
+   char buff[100];               /* Text buffer */
+   const char *names[3];         /* Names of the subelements to be searched for */
+   const char *unit;             /* Unit string */
+   double pos[3];                /* Values read from Position3D */
+   int i;                        /* Loop count */
+   int l1;                       /* Length of word 1 */
+   int l2;                       /* Length of word 2 */
+   int max[3];                   /* Max allowed occurrences of each name */
+   int min[3];                   /* Min allowed occurrences of each name */
+   int n;                        /* Number of words read from string */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* If the supplied element is not a Position3D just copy it. */
+   if( strcmp( astXmlGetName( elem ), "Position3D" ) ) {
+      new = (AstXmlElement *) astXmlCopy( elem );
+
+/* Otherwise, we create a new Position2D and add required content to it. */
+   } else {
+
+/* Search the supplied element for the required sub-elements. */
+      names[ 0 ] = "Name";
+      names[ 1 ] = "Error3";
+      names[ 2 ] = "Value3";
+      max[ 0 ] = 1;
+      max[ 1 ] = 2;
+      max[ 2 ] = 1;
+      min[ 0 ] = 1;
+      min[ 1 ] = 0;
+      min[ 2 ] = 0;
+      scan = ScanIVOAElement( this, elem, 3, names, min, max, status );
+
+/* If succesfull.. */
+      if( scan ) {
+
+/* Create an empty XML element with name "Position2D". */
+         new = astXmlAddElement( NULL, "Position2D", NULL );
+
+/* Get the units attribute from the supplied element. These are the units
+   of the positional axis values. Copy the first 2 words to the unit
+   attribute of the new element. */
+         unit = astXmlGetAttributeValue( elem, "unit" );
+         if( unit ) {
+            words = astChrSplit( unit, &n );
+            if( words ) {
+               if( n > 2 ) {
+                  l1 = strlen( words[ 0 ] );
+                  l2 = strlen( words[ 1 ] );
+                  unit2 = astMalloc( l1 + l2 + 2 );
+                  if( unit2 ) {
+                     strcpy( unit2, words[ 0 ] );
+                     unit2[ l1 ] = ' ';
+                     strcpy( unit2 + l1 + 1, words[ 1 ] );
+                     unit2[ l1 + l2 + 1 ] = 0;
+                     astXmlAddAttr( new, "unit", unit2, NULL );
+                     unit2 = astFree( unit2 );
+                  }
+               } else {
+                  astXmlAddAttr( new, "unit", unit, NULL );
+               }
+
+               if( words ) {
+                  for( i = 0; i < n; i++ ) words[ i ] = astFree( words[ i ] );
+                  words = astFree( words );
+               }
+            }
+         }
+
+/* If this Position3D contains a Name which can be read, obtain it
+   and store it in the returned Position2D. */
+         if( scan->count[ 0 ] > 0 ) {
+            el = astXmlAddElement( new, "Name", NULL );
+            astXmlAddCharData( el, 0, astXmlGetValue( scan->el[ 0 ][ 0 ], 0 ) );
+         }
+
+/* If this Position3D contains a Value which can be read, obtain it,
+   format the first 2 values and store in the returned Position2D. */
+         if( scan->count[ 2 ] > 0 ) {
+            ElemListD( this, scan->el[ 2 ][ 0 ], 3, pos, status );
+            el = astXmlAddElement( new, "Value2", NULL );
+            sprintf( buff, "%.*g %.*g", DBL_DIG, pos[0], DBL_DIG, pos[1] );
+            astXmlAddCharData( el, 0, buff );
+         }
+
+/* If this Position3D contains an Error which can be read, obtain it,
+   format the first 2 values and store in the returned Position2D. */
+         if( scan->count[ 1 ] > 0 ) {
+            ElemListD( this, scan->el[ 1 ][ 0 ], 3, pos, status );
+            el = astXmlAddElement( new, "Error2", NULL );
+            sprintf( buff, "%.*g %.*g", DBL_DIG, pos[0], DBL_DIG, pos[1] );
+            astXmlAddCharData( el, 0, buff );
+         }
+
+/* Free resources */
+         scan = FreeIVOAScan( scan, status );
+      }
+   }
+
+/* Return the result.*/
+   return new;
+
+}
+
+static AstRegion *NegationReader( AstXmlChan *this, AstXmlElement *elem,
+                                  AstFrame *frm, int *status ){
+/*
+*  Name:
+*     NegationReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Negation region element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *NegationReader( AstXmlChan *this, AstXmlElement *elem,
+*                                AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Negation region element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Negation region element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[1];         /* Names of the subelements to be searched for */
+   int max[1];                   /* Max allowed occurrences of each name */
+   int min[1];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for a Region sub-element. */
+   names[ 0 ] = "Intersection|Union|Negation|AllSky|Circle|Ellipse|Polygon|"
+                "Convex|Box";
+   min[ 0 ] = 1;
+   max[ 0 ] = 1;
+   scan = ScanIVOAElement( this, elem, 1, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Create a Region from the component region element, and negate it. */
+      new = RegionReader( this, scan->el[0][0], frm, status );
+      astNegate( new );
+
+/* Get any fill factor from the element and assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstObject *ObsDataLocationReader( AstXmlChan *this,
+                                         AstXmlElement *elem, int *status ) {
+/*
+*  Name:
+*     ObsDataLocationReader
+
+*  Purpose:
+*     Make an AST Object from an IVOA ObsDataLocationReader element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstObject *ObsDataLocationReader( AstXmlChan *this,
+*                                       AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Object from the supplied IVOA
+*     ObsDataLocationReader element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA ObsDataLocationReader element.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object.
+*/
+
+/* Local Variables: */
+   AstPointList *obs;            /* PointList defining the observatory position */
+   AstStcObsDataLocation *stc;   /* Pointer to returned Object */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   stc = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return (AstObject *) stc;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "ObservatoryLocation";
+   names[ 1 ] = "ObservationLocation";
+   min[ 0 ] = 1;
+   min[ 1 ] = 1;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Read the observation location. */
+      stc = (AstStcObsDataLocation *) StcMetadataReader( this, scan->el[ 1 ][ 0 ], status );
+
+/* Read the observatory location, returning a Pointlist describing the
+   observatory position (if possible), and modifiying the observation
+   Region by (if possible) assigning the observatory location to the
+   ObsLon and ObsLat attributes of any SpecFrames in the Region, and the
+   ObsLon and ObsLat attributes of any TimeFrames in the Region. */
+      obs = ObservatoryLocationReader( this, scan->el[ 0 ][ 0 ], stc, status );
+      if( obs ) {
+         astStcSetObs( stc, obs );
+         obs = astAnnul( obs );
+      }
+
+/* Free resources. */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Return the pointer to the new Object. */
+   return (AstObject *) stc;
+}
+
+static AstPointList *ObservatoryLocationReader( AstXmlChan *this,
+                                                AstXmlElement *elem,
+                                                AstStcObsDataLocation *obs, int *status ){
+/*
+*  Name:
+*     ObservatoryLocationReader
+
+*  Purpose:
+*     Make an AST PointList from an IVOA ObservatoryLocationReader element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstPointList *ObservatoryLocationReader( AstXmlChan *this,
+*                                              AstXmlElement *elem,
+*                                              AstStcObsDataLocation *obs, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST PointList from the supplied IVOA
+*     ObservatoryLocationReader element, and also modifies the supplied
+*     StcObsDataLocation so that the ObsLon and ObsLat attributes hold
+*     the observatory position (if appropriate).
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA ObservatoryLocation element.
+*     obs
+*        Pointer to the StcObsDataLocation in which to store the
+*        observatory position (if terrestrial).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new PointList.
+
+*/
+
+/* Local Constants: */
+#define A0  6378140.0            /* Earth equatorial radius (metres) */
+
+/* Local Variables: */
+   AstFrame *frm;                /* Pointer to obsvtory lon/lat Frame */
+   AstFrame *obs_frm;            /* Pointer to obsvation lon/lat Frame */
+   AstFrame *pfrm;               /* Pointer to axis primary Frame */
+   AstKeyMap *km;                /* KeyMap holding AstroCoords info */
+   AstObject *new;               /* Pointer to returned Region */
+   AstObject *o;                 /* Pointer to retrieved Region */
+   AstPointSet *ps;              /* Pointer to PointSet holding obs lon/lat */
+   AstRegion *err;               /* Pointer to error Region */
+   AstStc *stc;                  /* Pointer to Observatory location stc */
+   char setting[ 100 ];          /* Attribute setting string */
+   const char *dom;              /* Domain string */
+   double **ptr;                 /* Pointers to axis values for obs lon/lat */
+   double lambda;                /* Geodetic longitude radians */
+   double phi;                   /* Geodetic latitude radians */
+   double lon;                   /* Geocentric longitude radians */
+   double lat;                   /* Geocentric latitude radians */
+   int i;                        /* Index of Frame axis */
+   int nax;                      /* Number of Frame axes */
+   int paxis;                    /* Index of primary Frame axis */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise */
+   new = NULL;
+
+/* Read the ObservatoryLocation as an StcMetadata element (it will be
+   represented by a NullRegion). */
+   stc = (AstStc *) StcMetadataReader( this, elem, status );
+
+/* Extract the first AstroCoords KeyMap from the stc. */
+   if( astGetStcNCoord( stc ) == 0 ) {
+      Report( this, elem, FAILURE, "contains no observatory position", status );
+   } else {
+      km = astGetStcCoord( stc, 1 );
+
+/* Extract the PointList holding the axis values from the KeyMap. */
+      if( !astMapGet0A( km, AST__STCVALUE, &new ) ){
+         Report( this, elem, FAILURE, "contains no observatory position", status );
+
+/* Extract any position uncertainty, and store as the uncertainty in the
+   value PointList. */
+      } else if( astMapGet0A( km, AST__STCERROR, &o ) ){
+         err = (AstRegion *) o;
+         astSetUnc( new, err );
+
+/* Free resources */
+         err = astAnnul( err );
+      }
+      km = astAnnul( km );
+   }
+   stc = astAnnul( stc );
+
+/* Check the Region is a PointList. */
+   if( !astIsAPointList( new ) && astOK ) {
+      astError( AST__INTER, "ObservatoryLocationReader(XmlChan): The "
+                "observatory location is described by a %s rather than "
+                "a PointList (internal AST programming error).", status,
+                astGetClass( new ) );
+   }
+
+/* If possible, we use the observatory location to set the value of the
+   ObsLon and ObsLat attributes of any SpecFrames, and the ObsLon and
+   ObsLat attributes of any TimeFrames, in the supplied ObsDataLocation.
+   For this to be possible, the PointList being returned must represent
+   either geodetic or geocentric longitude/latitude. If it is geocentric,
+   the values need to be converted to geodetic. */
+   ps = astRegTransform( new, NULL, 1, NULL, &frm );
+   ptr = astGetPoints( ps );
+   if( ptr ){
+      nax = astGetNaxes( frm );
+      lon = AST__BAD;
+      lat = AST__BAD;
+      lambda = AST__BAD;
+      phi = AST__BAD;
+      for( i = 0; i < nax; i++ ) {
+         astPrimaryFrame( frm, i, &pfrm, &paxis );
+         dom = astGetDomain( pfrm );
+         if( dom ) {
+            if( !strcmp( dom, "GEO_C" ) ){
+               if( lon == AST__BAD ) {
+                  lon = ptr[i][0];
+                  astSetLabel( pfrm, 0, "Geodetic longitude" );
+               } else {
+                  lat = ptr[i][0];
+                  astSetLabel( pfrm, 1, "Geodetic latitude" );
+                  astSetDomain( pfrm, "GEO_D" );
+               }
+
+            } else if( !strcmp( dom, "GEO_D" ) ){
+               if( lambda == AST__BAD ) {
+                  lambda = ptr[i][0];
+               } else {
+                  phi = ptr[i][0];
+               }
+            }
+         }
+         pfrm = astAnnul( pfrm );
+      }
+
+/* If required, convert from geocentric lon/lat to geodetic lon/lat. */
+      if( lon != AST__BAD ) {
+         double pos[ 3 ], height;
+
+/* Convert the supplied geocentric lon/lat to terrestrial Cartesian
+   (x,y,z) values. The (x,y,z) system has origin at the centre of the
+   earth, Z axis going through the north pole, X axis at (long,lat)=(0,0),
+   and Y axis at (long,lat) = (E90,0). Assume an equatorial sea level
+   position. */
+         palDcs2c( lon, lat, pos );
+         pos[ 0 ] *= A0;
+         pos[ 1 ] *= A0;
+         pos[ 2 ] *= A0;
+
+/* Get the corresponding geodetic lon/lat. */
+         eraGc2gd( 1, pos, &lambda, &phi, &height );
+      }
+
+      if( lambda != AST__BAD ) {
+         obs_frm = astGetFrame( ((AstRegion *) obs)->frameset, AST__CURRENT );
+         nax = astGetNaxes( obs );
+         for( i = 0; i < nax; i++ ) {
+            astPrimaryFrame( obs_frm, i, &pfrm, &paxis );
+            if( astIsASpecFrame( pfrm ) ) {
+               sprintf( setting, "ObsLon(%d)=%.*g", i + 1, DBL_DIG, lambda*AST__DR2D );
+               astRegSetAttrib( obs, setting, NULL );
+               sprintf( setting, "ObsLat(%d)=%.*g", i + 1, DBL_DIG, phi*AST__DR2D );
+               astRegSetAttrib( obs, setting, NULL );
+            } else if( astIsATimeFrame( pfrm ) ) {
+               sprintf( setting, "ObsLon(%d)=%.*g", i + 1, DBL_DIG, lambda*AST__DR2D );
+               astRegSetAttrib( obs, setting, NULL );
+               sprintf( setting, "ObsLat(%d)=%.*g", i + 1, DBL_DIG, phi*AST__DR2D );
+               astRegSetAttrib( obs, setting, NULL );
+            }
+            pfrm = astAnnul( pfrm );
+         }
+         obs_frm = astAnnul( obs_frm );
+      }
+   }
+
+/* Free resources */
+   frm = astAnnul( frm );
+   ps = astAnnul( ps );
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return (AstPointList *) new;
+}
+#undef A0
+
+static void OutputText( AstXmlChan *this, const char *text, int mxlen, int *status ) {
+/*
+*  Name:
+*     OutputText
+
+*  Purpose:
+*     Write a string to the sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void OutputText( AstXmlChan *this, const char *text, int mxlen, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function writes the supplied text to the output sink,
+*     splitting it into lines of no more than "mxlen" characters, if
+*     required.
+
+*  Parameters:
+*     this
+*        A pointer to the XmlChan.
+*     text
+*        Pointer to the (potentially long) null terminated string to write
+*        out to the sink.
+*     mxlen
+*        The maximum allowed output line length. If zero, no limit is
+*        placed on the output line length and the supplied text is always
+*        written out as a single string.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   char *breakat;                /* Pointer to terminating character */
+   char *c;                      /* Pointer to start of next chunk */
+   char *lastend;                /* Pointer to last closing quote */
+   char *lastspace;              /* Pointer to last whitespace character */
+   char *linestart;              /* Pointer to start of current line */
+   char quote;                   /* Opening quote character */
+   char tt;                      /* Character temporarily replaced by 0 */
+   int len;                      /* Length of current line */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* If "mxlen" is zero, just output the string as supplied. */
+   if( mxlen < 1 ) {
+      astPutNextText( this, text );
+
+/* Otherwise, output the string split up into lines */
+   } else {
+      c = (char *) text - 1;
+      quote = 0;
+      lastend = NULL;
+      lastspace = NULL;
+      len = 0;
+      linestart = (char *) text;
+
+/* Loop round each character in the text */
+      while( *(++c ) ){
+
+/* Note if we are currently inside a quoted string. Remember the quote
+   character (" or ') so that we can look out for the corresponding
+   closing quote. Note the position of the previous quote end. */
+         if( !quote ) {
+            if( *c == '\"' || *c == '\'' ) quote = *c;
+         } else {
+            if( *c == quote ) {
+               quote = 0;
+               lastend = c;
+            }
+         }
+
+/* Note the position of hte previous space. */
+         if( isspace( *c ) ) lastspace = c;
+
+/* If we have exceeded the maximum allowed line length, split it. If we
+   are inside a quote, we split it at the last quote end (if any). If we
+   are not in a quote, we split it at the last space, or the last quote
+   end (which ever is closest). To cover the case where the end quote is
+   first character beyoind the limit, reduce the limit a bit. */
+         len++;
+         if( len >= mxlen - 2 ) {
+            if( !quote || !lastend ) {
+               if( lastend && lastspace ){
+                  breakat = ( lastend > lastspace ) ? lastend + 1: lastspace;
+               } else if( lastend ){
+                  breakat = lastend + 1;
+               } else if( lastspace ){
+                  breakat = lastspace;
+               } else {
+                  breakat = c;
+               }
+            } else {
+               breakat = lastend + 1;
+            }
+         } else {
+            breakat = NULL;
+         }
+
+/* If we have a line break, output the current line. */
+         if( breakat ) {
+
+/* Terminate the string, first saving the character which is replaced by the
+   terminating zero so that it can be re-instated later. */
+            tt = *breakat;
+            *breakat = 0;
+
+/* Write out the newly terminated chunk. */
+            astPutNextText( this, linestart );
+
+/* Move on to ths start of the next chunk, decrement the number of characters
+   remaining, and re-instate the character previously over-written by
+   zero. */
+            c = breakat;
+            linestart = c;
+            *c = tt;
+            len = 0;
+            quote = 0;
+         }
+      }
+
+/* Write out any remaining text (this will be less than "mxlen"
+   characters long)*/
+      if( linestart && *linestart ) astPutNextText( this, linestart );
+   }
+}
+
+static AstRegion *PolygonReader( AstXmlChan *this, AstXmlElement *elem,
+                                 AstFrame *frm, int *status ){
+/*
+*  Name:
+*     PolygonReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Polygon element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *PolygonReader( AstXmlChan *this, AstXmlElement *elem,
+*                               AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Polygon element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Polygon element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;
+   AstMapping *map;
+   AstRegion *new;
+   IVOAScan *scan;
+   const char *names[1];
+   const char *unit;
+   const char *funit;
+   double *pos;
+   double *x0;
+   double *y0;
+   double lbnd[2];
+   double ubnd[2];
+   int axcon;
+   int axlon;
+   int i;
+   int is_box;
+   int is_sky;
+   int laxcon;
+   int max[1];
+   int min[1];
+   int nv;
+   int small[ 4 ];
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Scan the supplied element for the required sub-elements */
+   names[ 0 ] = "Vertex";
+   min[ 0 ] = 1;
+   max[ 0 ] = INT_MAX;
+   scan = ScanIVOAElement( this, elem, 1, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* See if the Frame is a SkyFrame, and if so, get the index of the
+   longitude axis. */
+      is_sky = astIsASkyFrame( frm );
+      axlon = is_sky ? astGetLonAxis( (AstSkyFrame *) frm ) : -1;
+
+/* Get the units attribute from the supplied element. These are the units
+   of the vertex axis values. */
+      unit = astXmlGetAttributeValue( elem, "unit" );
+      if( !unit ) {
+         Report( this, elem, FAILURE, "contains no unit attribute", status );
+         unit = "";
+      }
+
+/* Create an array to hold the axis values at the vertices. */
+      nv = scan->count[0];
+      pos = astMalloc( sizeof( double )*(size_t) (2*nv) );
+
+/* Read each vertex element in turn. Record whether or not the first 4
+   vertices have <SmallCircles>. */
+      x0 = pos;
+      y0 = x0 + nv;
+      for( i = 0; i < nv; i++, x0++, y0++ ) {
+         small [ i % 4 ] = VertexReader( this, scan->el[0][i], x0, y0, status );
+      }
+
+/* Reset the pointers so that they point to the first x and y values. */
+      x0 = pos;
+      y0 = x0 + nv;
+
+/* Since the SkyFrame class does not have active Units we must handle it
+   separately. Convert the axis values from the supplied units (e.g.
+   degrees) to radians. */
+      if( is_sky ) {
+         map = astUnitMapper( unit, "rad", NULL, NULL );
+         if( map ) {
+            astTran1( map, nv*2, pos, 1, pos );
+            map = astAnnul( map );
+         } else if( astOK ) {
+            Report( this, elem, FAILURE, "contains unusable units", status );
+         }
+      }
+
+/* If there are 4 vertices we may be able to create an AST Box (not the
+   same as an STC Box) instead of a Polygon.*/
+      is_box = 0;
+      ubnd[0] = x0[ 0 ];
+      lbnd[0] = ubnd[0];
+      ubnd[1] = y0[ 0 ];
+      lbnd[1] = ubnd[1];
+
+      if( nv == 4 ) {
+
+/* See if the edge which ends at the 1st vertex has a constant value on
+   either axis. Is so, note the index of the axis which is held constant. */
+         is_box = 1;
+         if( is_sky && small[ 0 ] ) {
+            laxcon = 1 - axlon;
+
+         } else if( astEQUAL( x0[ 0 ], x0[ 3 ] ) ) {
+            laxcon = 0;
+
+         } else if( astEQUAL( y0[ 0 ], y0[ 3 ] ) ) {
+            laxcon = 1;
+
+         } else {
+            is_box = 0;
+         }
+
+/* If the first edge represents a constant axis value, see if the others
+   do too (ensuring that the axes which are held constant alternate). Also
+   find the bounds of the box.*/
+         if( is_box ) {
+            for( i = 1; i < 4; i++ ) {
+               if( is_sky && small[ i ] ) {
+                  axcon = 1 - axlon;
+
+               } else if( astEQUAL( x0[ i ], x0[ i - 1 ] ) ) {
+                  axcon = 0;
+
+               } else if( astEQUAL( y0[ i ], y0[ i - 1 ] ) ) {
+                  axcon = 1;
+
+               } else {
+                  is_box = 0;
+                  break;
+               }
+
+               if( axcon != 1 - laxcon ) {
+                  is_box = 0;
+                  break;
+               }
+
+               if( x0[ i ] > ubnd[0] ) {
+                  ubnd[0] = x0[ i ];
+
+               } else if( x0[ i ] < lbnd[0] ) {
+                  lbnd[0] = x0[ i ];
+               }
+
+               if( y0[ i ] > ubnd[1] ) {
+                  ubnd[1] = y0[ i ];
+
+               } else if( y0[ i ] < lbnd[1] ) {
+                  lbnd[1] = y0[ i ];
+               }
+
+               laxcon = axcon;
+            }
+         }
+      }
+
+/* Since the SkyFrame class does not have active Units we must handle it
+   separately. The axis values have already been converted from the supplied
+   units (e.g. degrees) to radians. */
+      if( is_sky ) {
+
+/* Create the Polygon or Box within the SkyFrame. */
+         if( is_box ) {
+            new = (AstRegion *) astBox( frm, 1, lbnd, ubnd, NULL, "", status );
+         } else {
+            new = (AstRegion *) astPolygon( frm, nv, nv, pos, NULL, "", status );
+         }
+
+/* Now handles Polygons in Frames other than SkyFrames. */
+      } else {
+
+/* Take a copy of the supplied Frame and set its Units to the value
+   obtained from the supplied element. */
+         cfrm = astCopy( frm );
+         astSetUnit( cfrm, 0, unit );
+         astSetUnit( cfrm, 1, unit );
+
+/* Create the Polygon or Box within the SkyFrame. */
+         if( is_box ) {
+            new = (AstRegion *) astBox( cfrm, 1, lbnd, ubnd, NULL, "", status );
+         } else {
+            new = (AstRegion *) astPolygon( cfrm, nv, nv, pos, NULL, "", status );
+         }
+
+/* If the Unit of this Region differs from that of the supplied Frame,
+   set it to the Unit of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new Unit. If the supplied
+   Frame had no set Unit, set it to the units obtained from the supplied
+   element. */
+         for( i = 0; i < 2; i++ ) {
+            if( astTestUnit( frm, i ) ) {
+               funit = astGetUnit( frm, i );
+               if( strcmp( funit, unit ) ) astSetUnit( new, i, funit );
+            } else {
+               astSetUnit( frm, i, unit );
+            }
+         }
+
+/* Free resources */
+         cfrm = astAnnul( cfrm );
+      }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      pos = astFree( pos );
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static double PosAngleReader( AstXmlChan *this, AstXmlElement *elem, int *status ){
+/*
+*  Name:
+*     PosAngleReader
+
+*  Purpose:
+*     Read an AST position angle value from an IVOA PosAngle element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     double PosAngleReader( AstXmlChan *this, AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function reads the supplied PosANgle element and returns a value
+*     representing a position angle in the AST convention (radians from
+*     +ve second axis to +ve first axis).
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Ellipse element.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The position angle.
+
+*/
+
+/* Local Variables: */
+   char buff[ 200 ];             /* Message buffer */
+   const char *paunit;           /* Position angle unit string */
+   const char *ref;              /* Reference axis string */
+   double result;                /* Position angle value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0.0;
+
+/* Get the numerical position angle stored in the supplied PosAngle
+   element. */
+   result = ElemValueD( this, elem, 0.0, status );
+
+/* Get the units attribute from the supplied element. These are the units
+   of the above value. Default is degrees. */
+   paunit = astXmlGetAttributeValue( elem, "unit" );
+
+/* Convert the position angle to radians. */
+   if( !paunit || !strcmp( paunit, "deg" ) ) {
+      result *= AST__DD2R;
+
+   } else if( !strcmp( paunit, "h" ) ) {
+      result *= 15*AST__DD2R;
+
+   } else if( !strcmp( paunit, "arcmin" ) ) {
+      result *= AST__DD2R/60.0;
+
+   } else if( !strcmp( paunit, "arcsec" ) ) {
+      result *= AST__DD2R/3600.0;
+
+   } else {
+      sprintf( buff, "contains unusable angle units \"%s\"", paunit );
+      Report( this, elem, FAILURE, buff, status );
+   }
+
+/* Get the reference attribute from the supplied element. This indicates
+   the sense and origin of the stored angle value. Convert the result
+   to the AST convention, which is the equivalent of "Y" (which is the same
+   as "North"). "X" means "from X to Y", "Y" means "from Y to X". Default
+   is "X". */
+   ref = astXmlGetAttributeValue( elem, "reference" );
+   if( !ref || !Ustrcmp( ref, "X", status ) ) {
+      result = AST__DPIBY2 - result;
+
+   } else if( Ustrcmp( ref, "Y", status ) && Ustrcmp( ref, "North", status ) ) {
+      sprintf( buff, "contains unusable reference attribute \"%s\" "
+               "(will assume \"Y\" instead)", ref );
+      Report( this, elem, WARNING, buff, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstRegion *Position2DReader( AstXmlChan *this, AstXmlElement *elem,
+                                    AstFrame *frm, double *pos,
+                                    AstKeyMap **anc, int *status ){
+/*
+*  Name:
+*     Position2DReader
+
+*  Purpose:
+*     Modify a Frame to take account of an STC <Position2D> element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *Position2DReader( AstXmlChan *this, AstXmlElement *elem,
+*                                  AstFrame *frm, double *pos, int axis,
+*                                  AstKeyMap **anc, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function reads the supplied STC <Position2D> element, and uses it,
+*     if possible, to create the uncertainty associated with the spatial
+*     axis in the supplied Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Position2D element.
+*     frm
+*        Pointer to the 2D spatial Frame.
+*     pos
+*        Pointer to an array in which to return the spatial axis values
+*        represented by the supplied Position2D element. This array is
+*        returned filled with AST__BAD If the supplied Position2D element
+*        does not contain any axis values.
+*     anc
+*        Address of a location at which to put a pointer to a newly
+*        created KeyMap. This KeyMap will contain ancillary information
+*        from the Position2D. The keys identify the item of ancillary
+*        information (Name, Value, Error, Resolution, Size, Pixel Size).
+*        The value associated with the Name key is string containing
+*        the Name item from the Position2D. The value
+*        associated with each of the other keys is a pointer to a 2D Region
+*        within the supplied Frame, corresponding to the value, error,
+*        resolution, etc. Keys will not be present in the returned KeyMap
+*        if the corresponding item of ancillary information is not present
+*        in the Position2D. A NULL pointer is returned if there is no
+*        ancillary information at all.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned:
+*     The uncertainty Region, or NULL if the supplied Position2D element
+*     does not specify an uncertainty.
+
+*/
+
+/* Local Variables: */
+   AstMapping *map1;        /* Mapping from first axis units to radians */
+   AstMapping *map2;        /* Mapping from second axis units to radians */
+   AstPointSet *pset;       /* Pointset holding Position2D axis values */
+   AstRegion *r;            /* Region to store in ancillary KeyMap */
+   AstRegion *result;       /* Returned uncertainty Region */
+   IVOAScan *scan;          /* Structure holding scan results */
+   char **words;            /* Array of words extracted from string */
+   const char *name;        /* Pointer to XML element name */
+   const char *names[6];    /* Names of the subelements to be searched for */
+   const char *unit1;       /* Pointer to axis 1 unit attribute string */
+   const char *unit2;       /* Pointer to axis 2 unit attribute string */
+   const char *unit;        /* Pointer to Position2D's unit attribute string */
+   double **ptr;            /* Arrays holding Position2D axis values */
+   double cen[ 2 ];         /* Centre values */
+   double hw[ 2 ];          /* Half widths values */
+   double pa;               /* Error position angle */
+   int i;                   /* Loop count */
+   int max[6];              /* Max allowed occurrences of each name */
+   int min[6];              /* Min allowed occurrences of each name */
+   int nword;               /* Number of words extracted from string */
+
+/* Initialise */
+   result = NULL;
+   pos[ 0 ] = AST__BAD;
+   pos[ 1 ] = AST__BAD;
+   *anc = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "Name";
+   names[ 1 ] = "Error2|Error2PA";
+   names[ 2 ] = "Value2";
+   names[ 3 ] = "Resolution2|Resolution2PA";
+   names[ 4 ] = "Size2|Size2PA";
+   names[ 5 ] = "PixSize2|PixSize2PA";
+   max[ 0 ] = 1;
+   max[ 1 ] = 2;
+   max[ 2 ] = 1;
+   max[ 3 ] = 2;
+   max[ 4 ] = 2;
+   max[ 5 ] = 2;
+   min[ 0 ] = 1;
+   min[ 1 ] = 0;
+   min[ 2 ] = 0;
+   min[ 3 ] = 0;
+   min[ 4 ] = 0;
+   min[ 5 ] = 0;
+   scan = ScanIVOAElement( this, elem, 6, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Create a KeyMap to return holding ancilary info, and put the Name into
+   it. */
+      *anc = astKeyMap( "", status );
+      if( scan->count[0] > 0 ) astMapPut0C( *anc, AST__STCNAME,
+                                  astXmlGetValue( scan->el[0][0], 0 ), NULL );
+
+/* Get the units attribute from the supplied element. These are the units
+   of the positional axis values. Split into units for each of the two
+   axes. */
+      unit1 = "";
+      unit2 = "";
+      words = NULL;
+      unit = astXmlGetAttributeValue( elem, "unit" );
+
+      if( !unit ) {
+         Report( this, elem, FAILURE, "contains no unit attribute", status );
+
+      } else {
+         words = astChrSplit( unit, &nword );
+         if( words ) {
+            unit1 = words[ 0 ];
+            unit2 = nword > 1 ? words[ 1 ] : words[ 0 ];
+         }
+      }
+
+/* Since the SkyFrame class does not have active Units we must convert the
+   axis values from the supplied units (e.g. degrees) to radians. Allow
+   for different units on the two axes. */
+      map1 = astUnitMapper( unit1, "rad", NULL, NULL );
+      if( !map1 ) Report( this, elem, FAILURE, "contains unusable units for axis 1", status );
+
+      if( unit1 && unit2 && strcmp( unit1, unit2 ) ) {
+         map2 = astUnitMapper( unit2, "rad", NULL, NULL );
+         if( !map2 ) Report( this, elem, FAILURE, "contains unusable units for axis 2", status );
+      } else {
+         map2 = astClone( map1 );
+      }
+
+/* If this Position2D contains a Value which can be read, obtain it. Otherwise,
+   issue a warning. */
+      if( scan->count[ 2 ] > 0 ) {
+         ElemListD( this, scan->el[ 2 ][ 0 ], 2, pos, status );
+
+/* Convert to radians. */
+         if( map1 == map2 ) {
+            astTran1( map1, 2, pos, 1, pos );
+         } else {
+            astTran1( map1, 1, pos, 1, pos );
+            astTran1( map2, 1, pos + 1, 1, pos + 1 );
+         }
+
+/* If this Position2D contains a value which cannot be used, issue a warning. */
+         if( pos[ 1 ] == AST__BAD ) {
+            Report( this, elem, WARNING, "contains an unreadable <Value>", status );
+         }
+
+/* Create a PointList from it and store in the returned ancillary KeyMap. */
+         pset = astPointSet( 1, 2, "", status );
+         ptr = astGetPoints( pset );
+         if( astOK ) {
+            ptr[ 0 ][ 0 ] = pos[ 0 ];
+            ptr[ 1 ][ 0 ] = pos[ 1 ];
+            r = (AstRegion *) astPointList( frm, pset, NULL, "", status );
+            astMapPut0A( *anc, AST__STCVALUE, r, NULL );
+            r = astAnnul( r );
+         }
+         pset = astAnnul( pset );
+      }
+
+/* Does this Position2D contain any Error? */
+      if( scan->count[ 1 ] > 0 && map1 ) {
+
+/* Issue a warning if more than 1 Error value was found. */
+         if( scan->count[ 1 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Error>"
+                    " element. AST can only use the first", status );
+         }
+
+/* If the error has no position angle, just read it as a list of double.
+   Otherwise, read the Error2PA structure. */
+         name = astXmlGetName( scan->el[ 1 ][ 0 ] );
+         if( name ) {
+            if( !strcmp( name, "Error2" ) ) {
+               ElemListD( this, scan->el[ 1 ][ 0 ], 2, hw, status );
+               pa = AST__BAD;
+            } else {
+               pa = Error2PAReader( this, scan->el[ 1 ][ 0 ], hw, status );
+            }
+
+/* Convert to radians, and halve to get the half-width. */
+            if( map1 == map2 ) {
+               astTran1( map1, 2, hw, 1, hw );
+            } else {
+               astTran1( map1, 1, hw, 1, hw );
+               astTran1( map2, 1, hw + 1, 1, hw + 1 );
+            }
+
+            if( hw[ 0 ] != AST__BAD ) hw[ 0 ] *= 0.5;
+            if( hw[ 1 ] != AST__BAD ) hw[ 1 ] *= 0.5;
+
+/* Create an Ellipse or Box to describe the error */
+            cen[ 0 ] = 0.0;
+            cen[ 1 ] = 0.0;
+            if( pa != AST__BAD ) {
+               result = (AstRegion *) astEllipse( frm, 1, cen, hw, &pa,
+                                                  NULL, "", status );
+            } else {
+               result = (AstRegion *) astBox( frm, 0, cen, hw, NULL, "", status );
+            }
+
+/* Store in the returned ancillary KeyMap. */
+            astMapPut0A( *anc, AST__STCERROR, result, NULL );
+         }
+      }
+
+/* Does this Position2D contain any Resolution? */
+      if( scan->count[ 3 ] > 0 && map1 ) {
+
+/* Issue a warning if more than 1 Resolution value was found. */
+         if( scan->count[ 3 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Resolution>"
+                    " element. AST can only use the first", status );
+         }
+
+/* If the resolution has no position angle, just read it as a list of double.
+   Otherwise, read the Resolution2PA structure (which is exactly the same
+   as an Error2PA structure). */
+         name = astXmlGetName( scan->el[ 3 ][ 0 ] );
+         if( name ) {
+            if( !strcmp( name, "Resolution2" ) ) {
+               ElemListD( this, scan->el[ 3 ][ 0 ], 2, hw, status );
+               pa = AST__BAD;
+            } else {
+               pa = Error2PAReader( this, scan->el[ 3 ][ 0 ], hw, status );
+            }
+
+/* Convert to radians, and halve to get the half-width. */
+            if( map1 == map2 ) {
+               astTran1( map1, 2, hw, 1, hw );
+            } else {
+               astTran1( map1, 1, hw, 1, hw );
+               astTran1( map2, 1, hw + 1, 1, hw + 1 );
+            }
+
+            if( hw[ 0 ] != AST__BAD ) hw[ 0 ] *= 0.5;
+            if( hw[ 1 ] != AST__BAD ) hw[ 1 ] *= 0.5;
+
+/* Create an Ellipse or Box to describe the resolution */
+            cen[ 0 ] = 0.0;
+            cen[ 1 ] = 0.0;
+            if( pa != AST__BAD ) {
+               r = (AstRegion *) astEllipse( frm, 1, cen, hw, &pa,
+                                                  NULL, "", status );
+            } else {
+               r = (AstRegion *) astBox( frm, 0, cen, hw, NULL, "", status );
+            }
+
+/* Store in the returned ancillary KeyMap. */
+            astMapPut0A( *anc, AST__STCRES, r, NULL );
+            r = astAnnul( r );
+         }
+      }
+
+/* Does this Position2D contain any Size? */
+      if( scan->count[ 4 ] > 0 && map1 ) {
+
+/* Issue a warning if more than 1 Size value was found. */
+         if( scan->count[ 4 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Size>"
+                    " element. AST can only use the first", status );
+         }
+
+/* If the size has no position angle, just read it as a list of double.
+   Otherwise, read the Size2PA structure (which is exactly the same
+   as an Error2PA structure). */
+         name = astXmlGetName( scan->el[ 4 ][ 0 ] );
+         if( name ) {
+            if( !strcmp( name, "Size2" ) ) {
+               ElemListD( this, scan->el[ 4 ][ 0 ], 2, hw, status );
+               pa = AST__BAD;
+            } else {
+               pa = Error2PAReader( this, scan->el[ 4 ][ 0 ], hw, status );
+            }
+
+/* Convert to radians, and halve to get the half-width. */
+            if( map1 == map2 ) {
+               astTran1( map1, 2, hw, 1, hw );
+            } else {
+               astTran1( map1, 1, hw, 1, hw );
+               astTran1( map2, 1, hw + 1, 1, hw + 1 );
+            }
+
+            if( hw[ 0 ] != AST__BAD ) hw[ 0 ] *= 0.5;
+            if( hw[ 1 ] != AST__BAD ) hw[ 1 ] *= 0.5;
+
+/* Create an Ellipse or Box to describe the size */
+            cen[ 0 ] = 0.0;
+            cen[ 1 ] = 0.0;
+            if( pa != AST__BAD ) {
+               r = (AstRegion *) astEllipse( frm, 1, cen, hw, &pa,
+                                                  NULL, "", status );
+            } else {
+               r = (AstRegion *) astBox( frm, 0, cen, hw, NULL, "", status );
+            }
+
+/* Store in the returned ancillary KeyMap. */
+            astMapPut0A( *anc, AST__STCSIZE, r, NULL );
+            r = astAnnul( r );
+         }
+      }
+
+/* Does this Position2D contain any PixSize? */
+      if( scan->count[ 5 ] > 0 && map1 ) {
+
+/* Issue a warning if more than 1 PixSize value was found. */
+         if( scan->count[ 5 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <PixSize>"
+                    " element. AST can only use the first", status );
+         }
+
+/* If the pixsize has no position angle, just read it as a list of double.
+   Otherwise, read the PixSize2PA structure (which is exactly the same
+   as an Error2PA structure). */
+         name = astXmlGetName( scan->el[ 5 ][ 0 ] );
+         if( name ) {
+            if( !strcmp( name, "PixSize2" ) ) {
+               ElemListD( this, scan->el[ 5 ][ 0 ], 2, hw, status );
+               pa = AST__BAD;
+            } else {
+               pa = Error2PAReader( this, scan->el[ 5 ][ 0 ], hw, status );
+            }
+
+/* Convert to radians, and halve to get the half-width. */
+            if( map1 == map2 ) {
+               astTran1( map1, 2, hw, 1, hw );
+            } else {
+               astTran1( map1, 1, hw, 1, hw );
+               astTran1( map2, 1, hw + 1, 1, hw + 1 );
+            }
+
+            if( hw[ 0 ] != AST__BAD ) hw[ 0 ] *= 0.5;
+            if( hw[ 1 ] != AST__BAD ) hw[ 1 ] *= 0.5;
+
+/* Create an Ellipse or Box to describe the pixsize */
+            cen[ 0 ] = 0.0;
+            cen[ 1 ] = 0.0;
+            if( pa != AST__BAD ) {
+               r = (AstRegion *) astEllipse( frm, 1, cen, hw, &pa,
+                                             NULL, "", status );
+            } else {
+               r = (AstRegion *) astBox( frm, 0, cen, hw, NULL, "", status );
+            }
+
+/* Store in the returned ancillary KeyMap. */
+            astMapPut0A( *anc, AST__STCPIXSZ, r, NULL );
+            r = astAnnul( r );
+         }
+      }
+
+/* Free resources */
+      if( map1 ) map1 = astAnnul( map1 );
+      if( map2 ) map2 = astAnnul( map2 );
+      scan = FreeIVOAScan( scan, status );
+      if( words ) {
+         for( i = 0; i < nword; i++ ) words[ i ] = astFree( words[ i ] );
+         words = astFree( words );
+      }
+
+   }
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result */
+   return result;
+
+}
+
+static AstRegion *PositionIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+                                          AstFrame *frm, int *status ){
+/*
+*  Name:
+*     PositionIntervalReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA PositionInterval element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *PositionIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+*                                        AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     PositionInterval element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA PositionInterval element.
+*     frm
+*        Pointer to the Frame in which the returned Region should be
+*        defined. If the Unit or System attribute is not set, this
+*        function will decide on the values to be used, and set these
+*        values in the supplied Frame before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[1];         /* Names of the subelements to be searched for */
+   const char *unit;             /* Unit string from supplied element */
+   int max[1];                   /* Max allowed occurrences of each name */
+   int min[1];                   /* Min allowed occurrences of each name */
+   int ndim;                     /* Number of axes in supplied Frame */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for the required sub-element. */
+   ndim = astGetNaxes( frm );
+   if( ndim == 1 ) {
+      names[ 0 ] = "CoordScalarInterval";
+   } else if( ndim == 2 ) {
+      names[ 0 ] = "Coord2VecInterval";
+   } else if( ndim == 3 ) {
+      names[ 0 ] = "Coord3VecInterval";
+   } else if( astOK ) {
+      astError( AST__INTER, "PositionIntervalReader(XmlChan): Supplied "
+                "Frame has more than 3 axes (internal AST programming error )." , status);
+   }
+   min[ 0 ] = 1;
+   max[ 0 ] = 1;
+   scan = ScanIVOAElement( this, elem, 1, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the units attribute from the supplied element. */
+      unit = astXmlGetAttributeValue( elem, "unit" );
+      if( !unit ) {
+         Report( this, elem, FAILURE, "contains no unit attribute", status );
+         unit = "";
+      }
+
+/* Read 1-d intervals */
+      if( ndim == 1 ) {
+         new = CoordScalarIntervalReader( this, scan->el[0][0], unit, frm, status );
+
+/* Read 2-d intervals */
+      } else if( ndim == 2 ) {
+         new = Coord2VecIntervalReader( this, scan->el[0][0], unit, frm, status );
+
+/* Read 3-d intervals */
+      } else if( ndim == 3 ) {
+         new = Coord3VecIntervalReader( this, scan->el[0][0], unit, frm, status );
+
+/* Report error for other dimensionalities */
+      } else if( astOK ) {
+         astError( AST__INTER, "PositionIntervalReader(XmlChan): Supplied "
+                   "Frame has more than 3 axes (internal AST programming error )." , status);
+      }
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstObject *Read( AstChannel *this_channel, int *status ) {
+/*
+*  Name:
+*     Read
+
+*  Purpose:
+*     Read an Object from a Channel.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstObject *Read( AstChannel *this_channel, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the astRead method
+*     inherited from the Channel class).
+
+*  Description:
+*     This function reads an Object from an XmlChan.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object.
+*/
+
+/* Local Variables: */
+   AstObject *new;               /* Pointer to returned Object */
+   AstXmlChan *this;             /* Pointer to the XmlChan structure */
+   AstXmlElement *elem;          /* XML element holding AST Object */
+   int def_fmt;                  /* Original default format */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* Save the current default format, and then reset it to NATIVE */
+   def_fmt = this->formatdef;
+   this->formatdef = NATIVE_FORMAT;
+
+/* First we construct an in-memory XML representation of the data source,
+   by reading text up to the end of the first element encountered from
+   which an AST Object could be created. If the Skip attribute is zero, then
+   an error is reported if there is any text prior to the start of the first
+   usable element. If Skip is non-zero any initial text prior to the start
+   of the first usable element is ignored. */
+   elem = ReadXmlText( this, status );
+
+/* Check a usable element was found. */
+   if( elem ) {
+
+/* The "current container element" is the XML element from which items
+   are currently being read. Indicate that we are currently not reading
+   any element (not used for IVOA formats). */
+      this->container = NULL;
+
+/* Next we create a new AST Object from this in-memory XML representation
+   of the source. */
+      new = MakeAstFromXml( this, elem, status );
+
+/* Remove the element. This will cause an error to be reported if
+   the element contains any items which have not been used. */
+      elem = Remove( this, elem, status );
+   }
+
+/* If an error has occurred, annul the document. */
+   if( !astOK ) this->readcontext = astXmlAnnul( this->readcontext );
+
+/* If an error occurred, clean up by deleting the new Object and
+   return a NULL pointer, and re-instate original default format. */
+   if ( !astOK ) {
+      new = astDelete( new );
+      this->formatdef = def_fmt;
+   }
+
+/* Return the pointer to the new Object. */
+   return new;
+}
+
+static void ReadClassData( AstChannel *this_channel, const char *class, int *status ) {
+/*
+*  Name:
+*     ReadClassData
+
+*  Purpose:
+*     Read values from a data source for a class loader.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void ReadClassData( AstChannel *this, const char *class, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the astReadClassData method
+*     inherited from the Channel class).
+
+*  Description:
+*     This function reads the data for a class from the data source
+*     associated with a Channel, so as to provide values for
+*     initialising the instance variables of that class as part of
+*     building a complete Object. This function should be invoked by
+*     the loader for each class immediately before it attempts to read
+*     these values.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     class
+*        A pointer to a constant null-terminated string containing the
+*        name of the class whose loader is requesting the data (note
+*        this is not usually the same as the class name of the Object
+*        being built). This value allows the class structure of the
+*        input data to be validated.
+*-
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* Pointer to the XmlChan structure */
+   AstXmlContentItem *item;      /* Pointer to next item of content */
+   const char *definedby;        /* Class defining current content items */
+   int nitem;                    /* Number of items in container */
+   int i;                        /* Loop counter */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* Check we have a container, and then store the name of the class being
+   loaded. */
+   if( !this->container ){
+      astError( AST__INTER, "astRead(XmlChan): Invalid attempt to read "
+                "%s data - there is currently no container element "
+                "(internal AST programming error).", status, class );
+
+   } else {
+      this->isa_class = class;
+
+/* Go through all the content elements within the current container and
+   give them an extra attribute named "definedby" the value of which is
+   the name of the class which defines the associated AST attribute or
+   object. This is determined by the the "isa" elements - an element is
+   "definedby" the class noted in the next following "isa" element, or by
+   the class being loaded if there is no following "isa" element. */
+
+/* Find the first "isa" element and get the value of its "class" attribute.
+   If none is found the name of the class being loaded is used. */
+      definedby = FindNextIsA( (AstXmlElement *) this->container, 0, status );
+
+/* Loop round all elements within the container. */
+      nitem = astXmlGetNitem( this->container );
+      for( i = 0; astOK && i < nitem; i++ ) {
+         item = astXmlGetItem( this->container, i );
+         if( astXmlCheckType( item, AST__XMLELEM ) ) {
+
+/* If this is an "ISA" element, then we have ended the scope of the
+   current "isa" class. All subsequent items will be defined by the class
+   mentioned in the next following "ISA" element. Find the next ISA
+   element and get its class. */
+            if( astOK && !strcmp( astXmlGetName( item ), ISA ) ) {
+               definedby = FindNextIsA( (AstXmlElement *) this->container, i + 1, status );
+
+/* For other element types, add a "definedby" attribute holding the name
+   of the class defined by the current ISA element. */
+            } else {
+               astXmlAddAttr( item, DEFINEDBY, definedby, NULL );
+            }
+         }
+      }
+   }
+}
+
+static double ReadDouble( AstChannel *this_channel, const char *name, double def, int *status ) {
+/*
+*  Name:
+*     ReadDouble
+
+*  Purpose:
+*     Read a double value as part of loading a class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     double ReadDouble( AstChannel *this, const char *name, double def, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the astReadDouble method
+*     inherited from the Channel class).
+
+*  Description:
+*     This function searches the current container element of an XmlChan to
+*     identify a double value with a specified name. If such a value
+*     is found, it is returned, otherwise a default value is returned
+*     instead.
+*
+*     This function should only be invoked from within the loader
+*     function associated with a class, in order to return a double
+*     value to be assigned to an instance variable. It must be
+*     preceded by a call to the astReadClassData function.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required value. This must be in
+*        lower case with no surrounding white space. Note that names
+*        longer than 6 characters will not match any value.
+*     def
+*        If no suitable value can be found (e.g. it is absent from the
+*        data stream being read), then this value will be returned
+*        instead.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The required value, or the default if the value was not found.
+
+*  Notes:
+*     - A value of 0.0 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* Pointer to the XmlChan structure */
+   AstXmlElement *element;       /* Pointer to element holding required value */
+   const char *value;            /* Pointer to attribute value */
+   double result;                /* Value to be returned */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Initialise. */
+   result = 0.0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* Search the current container element for an ATTR element
+   describing an AST attribute of the specified name. This call ignores
+   ATTR elements which represent default values. No error is
+   reported if an ATTR element with the given name cannot be
+   found. */
+   element = FindAttribute( this, name, status );
+
+/* If an element was found, attempt to decode the string to give a double
+   value, checking that the entire string is read (and checking for the
+   magic string used to represent bad values). If this fails, then the
+   wrong name has probably been given, or the input data are corrupt,
+   so report an error. */
+   if( element ) {
+      value = astXmlGetAttributeValue( element, VALUE );
+      if( value ) {
+         nc = 0;
+         if ( ( 0 == astSscanf( value, " " BAD_STRING " %n",
+                                                   &nc ) )
+                 && ( nc >= (int) strlen( value ) ) ) {
+            result = AST__BAD;
+
+         } else if ( !( ( 1 == astSscanf( value, " %lf %n", &result, &nc ) )
+                 && ( nc >= (int) strlen( value ) ) ) ) {
+            astError( AST__BADIN, "astRead(XmlChan): The value \"%s = %s\" "
+                      "cannot be read as a double precision floating point "
+                      "number.", status, name, value );
+
+/* If the value was succesfully read, remove the ATTR element
+   from the container. */
+         } else {
+            element = Remove( this, element, status );
+         }
+
+/* Report an error if the attribute does not have a value. */
+      } else {
+         astError( AST__BADIN, "astRead(XmlChan): No value for attribute "
+                   "\"%s\" within element \"%s\".", status, name,
+                   GetTag( (AstXmlObject *) element, 1, status ) );
+      }
+
+/* If no suitable element was found, then use the default value instead. */
+   } else {
+      result = def;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int ReadInt( AstChannel *this_channel, const char *name, int def, int *status ) {
+/*
+*  Name:
+*     ReadInt
+
+*  Purpose:
+*     Read a int value as part of loading a class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int ReadInt( AstChannel *this, const char *name, int def )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the astReadInt method
+*     inherited from the Channel class).
+
+*  Description:
+*     This function searches the current container element of an XmlChan to
+*     identify a int value with a specified name. If such a value
+*     is found, it is returned, otherwise a default value is returned
+*     instead.
+*
+*     This function should only be invoked from within the loader
+*     function associated with a class, in order to return a int
+*     value to be assigned to an instance variable. It must be
+*     preceded by a call to the astReadClassData function.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required value. This must be in
+*        lower case with no surrounding white space. Note that names
+*        longer than 6 characters will not match any value.
+*     def
+*        If no suitable value can be found (e.g. it is absent from the
+*        data stream being read), then this value will be returned
+*        instead.
+
+*  Returned Value:
+*     The required value, or the default if the value was not found.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* Pointer to the XmlChan structure */
+   AstXmlElement *element;       /* Pointer to element holding required value */
+   const char *value;            /* Pointer to attribute value */
+   int result;                   /* Value to be returned */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* Search the current container element for an ATTR element
+   describing an AST attribute of the specified name. This call ignores
+   ATTR elements which represent default values. No error is
+   reported if an ATTR element with the given name cannot be
+   found. */
+   element = FindAttribute( this, name, status );
+
+/* If an element was found, attempt to decode the string to give a int
+   value, checking that the entire string is read. If this fails, then the
+   wrong name has probably been given, or the input data are corrupt,
+   so report an error. */
+   if( element ) {
+      value = astXmlGetAttributeValue( element, VALUE );
+      if( value ) {
+         nc = 0;
+         if ( !( ( 1 == astSscanf( value, " %d %n", &result, &nc ) )
+                 && ( nc >= (int) strlen( value ) ) ) ) {
+            astError( AST__BADIN,
+                      "astRead(XmlChan): The value \"%s = %s\" cannot "
+                      "be read as an integer.", status, name, value );
+
+/* If the value was succesfully read, remove the ATTR element
+   from the container. */
+         } else {
+            element = Remove( this, element, status );
+         }
+
+/* Report an error if the attribute does not have a value. */
+      } else {
+         astError( AST__BADIN, "astRead(XmlChan): No value for attribute "
+                   "\"%s\" within element \"%s\".", status, name,
+                   GetTag( (AstXmlObject *) element, 1, status ) );
+      }
+
+/* If no suitable element was found, then use the default value instead. */
+   } else {
+      result = def;
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstObject *ReadObject( AstChannel *this_channel, const char *name,
+                              AstObject *def, int *status ) {
+/*
+*  Name:
+*     ReadObject
+
+*  Purpose:
+*     Read a (sub)Object as part of loading a class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     char *ReadObject( AstChannel *this, const char *name, AstObject *def )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the astReadObject method
+*     inherited from the Channel class).
+
+*  Description:
+*     This function searches the current container element of an XmlChan to
+*     identify an Object with a specified name. If such an Object
+*     is found, a pointer to it is returned, otherwise a default pointer
+*     is returned instead.
+*
+*     This function should only be invoked from within the loader
+*     function associated with a class, in order to return a int
+*     value to be assigned to an instance variable. It must be
+*     preceded by a call to the astReadClassData function.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required value. This must be in
+*        lower case with no surrounding white space. Note that names
+*        longer than 6 characters will not match any value.
+*     def
+*        If no suitable value can be found (e.g. it is absent from the
+*        data stream being read), then this value will be returned
+*        instead.
+
+*  Returned Value:
+*     A pointer to the Object, or a clone of the default pointer if
+*     the Object was not found.
+
+*  Notes:
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* Pointer to the XmlChan structure */
+   AstXmlElement *element;       /* Pointer to element holding required value */
+   AstObject *result;            /* Value to be returned */
+   const char *isa_class;        /* Class currently being loaded */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* Search the current container element for an element with a name which
+   is not ATTR and with the specified LABEL. This call ignores
+   elements which represent default values. No error is reported if an
+   element with the given label cannot be found. */
+   element = FindObject( this, name, status );
+
+/* If an element was found, make an AST object from it. First remember
+   the class currently being loaded so that it can be re-instated. */
+   if( element ) {
+      isa_class = this->isa_class;
+      result = MakeAstFromXml( this, element, status );
+      this->isa_class = isa_class;
+
+/* Remove the element from the container. */
+      element = Remove( this, element, status );
+
+/* If no suitable Value structure was found, clone the default
+   pointer, if given. */
+   } else if ( def ) {
+      result = astClone( def );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static char *ReadString( AstChannel *this_channel, const char *name, const char *def, int *status ) {
+/*
+*  Name:
+*     ReadString
+
+*  Purpose:
+*     Read a string value as part of loading a class.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     char *ReadString( AstChannel *this, const char *name, const char *def )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the astReadString method
+*     inherited from the Channel class).
+
+*  Description:
+*     This function searches the current container element of an XmlChan to
+*     identify a string value with a specified name. If such a value
+*     is found, it is returned, otherwise a default value is returned
+*     instead.
+*
+*     This function should only be invoked from within the loader
+*     function associated with a class, in order to return a int
+*     value to be assigned to an instance variable. It must be
+*     preceded by a call to the astReadClassData function.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated character string
+*        containing the name of the required value. This must be in
+*        lower case with no surrounding white space. Note that names
+*        longer than 6 characters will not match any value.
+*     def
+*        If no suitable value can be found (e.g. it is absent from the
+*        data stream being read), then this value will be returned
+*        instead.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated null-terminated string
+*     containing the value required, or to a copy of the default
+*     string if the value was not found (or NULL if the "def" pointer
+*     was NULL).
+
+*  Notes:
+*     - It is the caller's responsibility to arrange for the memory
+*     holding the returned string to be freed (using astFree) when it
+*     is no longer required.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* Pointer to the XmlChan structure */
+   AstXmlElement *element;       /* Pointer to element holding required value */
+   char *result;                 /* Value to be returned */
+   const char *value;            /* Pointer to attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* Search the current container element for an ATTR element
+   describing an AST attribute of the specified name. This call ignores
+   ATTR elements which represent default values. No error is
+   reported if an ATTR element with the given name cannot be
+   found. */
+   element = FindAttribute( this, name, status );
+
+/* If an element was found, return a copy of the "value" string. */
+   if( element ) {
+      value = astXmlGetAttributeValue( element, VALUE );
+      if( value ) {
+         result = astStore( NULL, value, strlen( value ) + 1 );
+
+/* If the new default for XmlFormat has not yet been set, note if this
+   element contained a "quoted" attribute. */
+         if( this->formatdef == NATIVE_FORMAT ) {
+            if( astXmlGetAttributeValue( element, QUOTED ) ) {
+               this->formatdef = QUOTED_FORMAT;
+            }
+         }
+
+/* Remove the ATTR element from the container. */
+         element = Remove( this, element, status );
+
+/* Report an error if the attribute does not have a value. */
+      } else {
+         astError( AST__BADIN, "astRead(XmlChan): No value for attribute "
+                   "\"%s\" within element \"%s\".", status, name,
+                   GetTag( (AstXmlObject *) element, 1, status ) );
+      }
+
+/* If no suitable Value structure was found, then make a dynamic copy
+   of the default string (if given) and return a pointer to this. */
+   } else if ( def ) {
+      result = astStore( NULL, def, strlen( def ) + (size_t) 1 );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static AstXmlElement *ReadXmlText( AstXmlChan *this, int *status ){
+/*
+*  Name:
+*     ReadXmlText
+
+*  Purpose:
+*     Create an in-memory XML tree from an XML text source.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstXmlElement *ReadXmlText( AstXmlChan *this, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function constructs an in-memory XML representation of the data
+*     source by reading text up to the end of the first element encountered
+*     from which an AST Object could be constructed. If the Skip attribute is
+*     zero, then an error is reported if there is any text prior to the start
+*     of the first AST Object. If Skip is non-zero any initial text prior to
+*     the start of the first usable element is ignored.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the returned XmlElement. This should be annuled using
+*     astXmlAnnul when no longer needed. NULL is returned if the end of
+*     the source text is reached without finding a en element from which
+*     an AST Object could be read.
+
+*  Notes:
+*     - A NULL pointer is returned if an error has already occurred, of
+*     if this function should fail for any reason.
+
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS       /* Declare the thread specific global data */
+   AstXmlElement *result;   /* Returned pointer */
+   int skip;                /* Skip over initial irrelevant markup? */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the structure holding thread-specific global data. */
+   astGET_GLOBALS(this);
+
+/* Get the value of the Skip attribute. This indicates if we should skip
+   over any irrelevant markup prior to the first element from which an
+   AST object could be created. */
+   skip = astGetSkip( this );
+
+/* Store a pointer to the XmlChan in a module variable so that the IsUsable function
+   can access its properties. */
+   isusable_this = this;
+
+/* Read characters from the XML source and return an XmlElement structure
+   containing the first usable element encountered. */
+   result = astXmlReadDocument( &(this->readcontext), IsUsable, skip,
+                               GetNextChar, this );
+
+/* Nullify the module variable for safety. */
+   isusable_this = NULL;
+
+/* If no usable element was found, annul the document. */
+   if( !result ) this->readcontext = astXmlAnnul( this->readcontext );
+
+/* Delete the returned element if an error has occurred. */
+   if( !astOK ) result = astXmlAnnulTree( result );
+
+/* Return the result. */
+   return result;
+
+}
+
+static void ReCentreAnc( AstRegion *region, int nanc, AstKeyMap **ancs, int *status ){
+/*
+*  Name:
+*     ReCentreAnc
+
+*  Purpose:
+*     Re-centre the Regions describing ancillary information extracted
+*     from an AstroCoords elements.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     ReCentreAnc( AstRegion *region, int nanc, AstKeyMap **ancs, int *status )
+
+*  Class Membership:
+*     XmlChan member function
+
+*  Description:
+*     This function recentres the Regions which describe ancillary
+*     information from an AstroCoords element so that it is centred at
+*     the centre of the associated AstroCoordsArea element.
+
+*  Parameters:
+*     region
+*        Pointer to the Region defining the associated AstroCoordsArea.
+*     nanc
+*        Number of KeyMap pointers stored in "ancs"
+*     ancs
+*        Pointer to an array of "nanc" elements, each being a pointer to
+*        a KeyMap. Each one describes the ancilary information in an
+*        AstroCoords element associated with the AstroCoordsArea decribed
+*        by "region". Each KeyMap has elements with keys AST__STCERROR,
+*        AST__STCRES, AST__STCSIZE, AST__STCPIXSZ, AST__STCVALUE each of
+*        which holds a pointer to a Region. These Regions are modified on
+*        exit so that they are centred on a point which inside the supplied
+*        Region.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;
+   AstFrame *pfrm;
+   AstFrameSet *fs;
+   AstMapping *map;
+   AstMapping *smap;
+   AstObject *o;
+   AstRegion *r2;
+   AstRegion *r;
+   char orgatt[ 20 ];
+   char orgset[ 80 ];
+   char setting[ 80 ];
+   char sysatt[ 20 ];
+   char sysset[ 80 ];
+   const char *old_unit;
+   const char *time_unit;
+   double *lbnd;
+   double *mid;
+   double *ubnd;
+   double epoch;
+   int i;
+   int j;
+   int k;
+   int ndim;
+   int paxis;
+   int time_axis;
+
+   static const char *key[ 5 ] = { AST__STCERROR,
+                                   AST__STCRES,
+                                   AST__STCSIZE,
+                                   AST__STCPIXSZ,
+                                   AST__STCVALUE };
+
+/* Check the inherited status. Also return if no Keymaps supplied. */
+   if( !nanc || !astOK ) return;
+
+/* Get the Frame  encapsulated by the suipplied Region. */
+   frm = astRegFrame( region );
+
+/* Get the bounds of the supplied Region. */
+   ndim = astGetNaxes( frm );
+   lbnd = astMalloc( sizeof( double )*(size_t) ndim );
+   ubnd = astMalloc( sizeof( double )*(size_t) ndim );
+   mid = astMalloc( sizeof( double )*(size_t) ndim );
+   if( mid ) {
+      astGetRegionBounds( region, lbnd, ubnd );
+
+/* Get a mid point, taking account of unbounded axes. Also find the index of
+   the time axis (if any) in the supplied Region, and get the System and
+   TimeOrigin values for the time axis. */
+      time_axis = -1;
+      time_unit = NULL;
+      orgatt[ 0 ] = 0;
+      sysatt[ 0 ] = 0;
+      for( i = 0; i < ndim; i++ ) {
+         if( lbnd[ i ] > -0.5*DBL_MAX ) {
+            if( ubnd[ i ] < 0.5*DBL_MAX ) {
+               mid[ i ] = 0.5*( lbnd[ i ] + ubnd[ i ] );
+            } else {
+               mid[ i ] = lbnd[ i ];
+            }
+         } else {
+            if( ubnd[ i ] < 0.5*DBL_MAX ) {
+               mid[ i ] = ubnd[ i ];
+            } else {
+               mid[ i ] = 0.0;
+            }
+         }
+
+/* If we have not found a time axis, see if the current axis is a time axis. */
+         if( time_axis == -1 ) {
+            astPrimaryFrame( frm, i, &pfrm, &paxis );
+            if( astIsATimeFrame( pfrm ) ) {
+
+/* If so, record its index. */
+               time_axis = i;
+
+/* If the TimeOrigin attribute is set, save its value. Create strings
+   holding the attribute name and appropriate setting string for use with
+   the ancillary regions. */
+               if( astTestTimeOrigin( (AstTimeFrame *) pfrm ) ) {
+                  sprintf( orgatt, "TimeOrigin(%d)", i + 1 );
+                  sprintf( orgset, "TimeOrigin(%d)=%s", i + 1,
+                           astGetC( pfrm, "TimeOrigin" ) );
+               }
+
+/* If the System attribute is set, save its value. Create strings
+   holding the attribute name and appropriate setting string for use with
+   the ancillary regions. */
+               if( astTestSystem( pfrm ) ) {
+                  sprintf( sysatt, "System(%d)", i + 1 );
+                  sprintf( sysset, "System(%d)=%s", i + 1,
+                           astGetC( pfrm, "System" ) );
+               }
+
+               time_unit = astGetUnit( pfrm, 0 );
+            }
+            pfrm = astAnnul( pfrm );
+         }
+
+      }
+
+/* Get the Region Epoch. */
+      if( astTestEpoch( frm ) ){
+         epoch = astGetEpoch( frm );
+         sprintf( setting, "Epoch=MJD %.*g", DBL_DIG, epoch );
+      } else {
+         setting[ 0 ] = 0;
+         epoch = AST__BAD;
+      }
+
+/* Loop round each KeyMap. */
+      for( j = 0; j < nanc; j++ ) {
+
+/* Loop round each of the relevant KeyMap elements (skip the "Value"
+   element since this should not be re-centred). */
+         for( k = 0; k < 5; k++ ) {
+            if( astMapGet0A( ancs[ j ], key[ k ], &o ) ) {
+               r = (AstRegion *) o;
+
+/* The System and TimeOrigin attributes of the STC Region are set when the
+   AstroCoordArea is read. This occurs after the ancillary Coords Regions are
+   created. Consequently, the ancillary Coords Regions may not have set
+   System and/or TimeOrigin values.  So, for System and TimeOrigin, if
+   the attribute is set in the supplied Region but not set in the ancillary
+   Region, transfer the set value to the ancillary Region. */
+               if( strlen( sysatt ) && strlen( orgatt ) ) {
+                  if( !astTest( r, sysatt ) && !astTest( r, orgatt ) ) {
+                     astRegSetAttrib( r, sysset, NULL );
+
+                     old_unit = astGetUnit( r, time_axis );
+                     if( old_unit && time_unit &&
+                         strcmp( old_unit, time_unit ) ) {
+                        if( !astTestUnit( r, time_axis ) ) {
+                           old_unit = NULL;
+                        } else {
+                           old_unit = astStore( NULL, old_unit,
+                                                strlen( old_unit ) + 1 );
+                        }
+                        astSetUnit( r, time_axis, time_unit );
+                     }
+                     astRegSetAttrib( r, orgset, NULL );
+                     if( !old_unit ) {
+                        astClearUnit( r, time_axis );
+                     } else if( strcmp( old_unit, time_unit ) ) {
+                        astSetUnit( r, time_axis, old_unit );
+                        old_unit = astFree( (void *) old_unit );
+                     }
+                  }
+               }
+
+/* Re-centre the Regions held in this element of the KeyMap, and set
+   its Epoch (do not re-centre the "Value" element). */
+               if( strcmp( key[ k ], AST__STCVALUE ) ) {
+
+/* First ensure the ancillary Region refers to the supplied Frame. */
+                  fs = astConvert( r, frm, "" );
+                  if( fs ) {
+                     map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+                     smap = astSimplify( map );
+                     if( !astIsAUnitMap( smap ) ) {
+                        r2 = astMapRegion( r, smap, frm );
+                        astMapPut0A( ancs[ j ], key[ k ], r2, NULL );
+                        (void) astAnnul( r );
+                        r = r2;
+                     }
+                     map = astAnnul( map );
+                     smap = astAnnul( smap );
+                     fs = astAnnul( fs );
+
+/* Now set the epoch and re-centre.*/
+                     if( epoch != AST__BAD ) astRegSetAttrib( r, setting, NULL );
+                     astRegCentre( r, mid, NULL, 0, AST__CURRENT );
+                  }
+               }
+               r = astAnnul( r );
+            }
+         }
+      }
+   }
+
+/* Free resources. */
+   lbnd = astFree( lbnd );
+   ubnd = astFree( ubnd );
+   mid = astFree( mid );
+   frm = astAnnul( frm );
+}
+
+static AstObject *RedshiftFrameReader( AstXmlChan *this,
+                                       AstXmlElement *elem, int *status ) {
+/*
+*  Name:
+*     RedshiftFrameReader
+
+*  Purpose:
+*     Make an AST Object from an IVOA RedshiftFrame element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstObject *RedshiftFrameReader( AstXmlChan *this, AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Object from the supplied IVOA
+*     RedshiftFrame element. The returned Object is a SpecFrame in which
+*     the Domain is set explicitly to REDSHIFT.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA RedshiftFrame element.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object.
+
+*/
+
+/* Local Variables: */
+   AstSpecFrame *new;            /* Pointer to returned Object */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[3];         /* Names of the subelements to be searched for */
+   const char *sor;              /* StdOfRest for returned Frame */
+   const char *type;             /* Doppler type (velocity or redshift) */
+   const char *sys;              /* Spectral system */
+   int max[3];                   /* Max allowed occurrences of each name */
+   int min[3];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return (AstObject *) new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "TOPOCENTER|BARYCENTER|HELIOCENTER|GEOCENTER|LSR|"
+                "LSRK|GALACTIC_CENTER|LOCAL_GROUP_CENTER|LSRD";
+   names[ 1 ] = DOPPLER_DEFINITION;
+   names[ 2 ] = "Name";
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   max[ 2 ] = 1;
+   min[ 0 ] = 1;
+   min[ 1 ] = 1;
+   min[ 2 ] = 0;
+   scan = ScanIVOAElement( this, elem, 3, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the name of the Element specifying the reference position and find
+   the corresponding AST name.*/
+      sor = astXmlGetName( scan->el[0][0] );
+      if( !strcmp( sor, "TOPOCENTER" ) ) {
+         sor = "Topo";
+      } else if( !strcmp( sor, "BARYCENTER" ) ){
+         sor = "Bary";
+      } else if( !strcmp( sor, "GEOCENTER" ) ){
+         sor = "Geo";
+      } else if( !strcmp( sor, "LSR" ) || !strcmp( sor, "LSRK" ) ) {
+         sor = "LSRK";
+      } else if( !strcmp( sor, "LSRD" ) ) {
+         sor = "LSRD";
+      } else if( !strcmp( sor, "GALACTIC_CENTER" ) ) {
+         sor = "Galactic";
+      } else if( !strcmp( sor, "LOCAL_GROUP_CENTER" ) ) {
+         sor = "Local_group";
+      } else if( !strcmp( sor, "HELIOCENTER" ) ) {
+         sor = "Helio";
+      } else if( astOK ){
+         astError( AST__INTER, "RedshiftFrameReader(XmlChan): Unknown "
+                   "standard of rest %s (internal AST programming error).", status,
+                   sor );
+      }
+
+/* Issue a warning if the reference position includes an ephemeris. */
+      if( FindElement( this, scan->el[0][0], "PlanetaryEphem", status ) ) {
+         Report( this, scan->el[0][0], WARNING, "contains a <PlanetaryEphem> "
+                 "element which will be ignored", status );
+      }
+
+/* Get the value of the value_type attribute from the element. */
+      type = astXmlGetAttributeValue( elem, "value_type" );
+      if( !type ) type = "VELOCITY";
+
+/* If the type is REDSHIFT, set the system to redshift. Also check that
+   any <DopplerDefinition> element is "OPTICAL". */
+      if( !strcmp( type, "REDSHIFT" ) ) {
+         sys = astXmlGetValue( scan->el[1][0], 0 );
+         if( sys && !strcmp( sys, "OPTICAL" ) ) {
+            sys = "REDSHIFT";
+         } else {
+            Report( this, elem, FAILURE, "specifies dimensionless "
+                    "redshift (z) but has non-optical <DopplerDefinition>", status );
+         }
+
+/* Otherwise, get the value of the Doppler definition element, and translate
+   it to an AST value.*/
+      } else {
+         sys = astXmlGetValue( scan->el[1][0], 0 );
+         if( !sys ) {
+            Report( this, elem, FAILURE, "contains a <DopplerDefinition> "
+                    "element which is not simply character data", status );
+
+         } else if( !strcmp( sys, "OPTICAL" ) ) {
+            sys = "VOPT";
+
+         } else if( !strcmp( sys, "RADIO" ) ) {
+            sys = "VRAD";
+
+         } else if( !strcmp( sys, "RELATIVISTIC" ) ) {
+            sys = "VREL";
+
+         } else {
+            Report( this, elem, FAILURE, "contains unsupported Doppler definition", status );
+         }
+      }
+
+/* Create a suitable SpecFrame. */
+      new = astSpecFrame( "Domain=REDSHIFT,System=%s,StdOfRest=%s", status, sys, sor);
+
+/* If the SpectralFrame has a <Name> element use it as the SpecFrame title. */
+      if( scan->count[2] ) astSetTitle( new, astXmlGetValue( scan->el[2][0], 0 ) );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Object. */
+   return (AstObject *) new;
+}
+
+static AstRegion *RedshiftIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+                                          AstFrame *frm, int *status ){
+/*
+*  Name:
+*     RedshiftIntervalReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA RedshiftInterval element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *RedshiftIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+*                                        AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     RedshiftInterval element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA RedshiftInterval element.
+*     frm
+*        Pointer to the Frame in which the returned Region should be
+*        defined. If the Unit or System attribute is not set, this
+*        function will decide on the values to be used, and set these
+*        values in the supplied Frame before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Frame used to define returned Region */
+   AstRegion *new;               /* Pointer to returned Region */
+   AstSystemType sys;            /* Redshift system */
+   IVOAScan *scan;               /* Structure holding scan results */
+   char *unit;                   /* Unit string from supplied element */
+   const char *funit;            /* Unit string from supplied Frame */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   double hilimit;               /* Upper spectral limit */
+   double lolimit;               /* Lower spectral limit */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "LoLimit";
+   names[ 1 ] = "HiLimit";
+   min[ 0 ] = 0;
+   min[ 1 ] = 0;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the limits. */
+      lolimit = scan->count[0] ? ElemValueD( this, scan->el[0][0], 0.0, status ) : AST__BAD;
+      hilimit = scan->count[1] ? ElemValueD( this, scan->el[1][0], 0.0, status ) : AST__BAD;
+
+/* Use any unit and vel_time_unit attributes in the supplied element to
+   determine the system and units for the redshift Frame. */
+      sys = RedshiftSys( this, elem, &unit, 1, status );
+
+/* If no system has been set in the supplied Frame, set a default system
+   now (radio velocity if both units are present, dimensionaless redshift
+   otherwise). */
+      if( !astTestSystem( frm ) ) {
+         astSetSystem( frm, sys );
+
+/* The ReddshiftSys function always returns AST__VRADIO if the velocity
+   is not dimensionless. In this case, if the supplied Frame has system
+   explicitly set AST__VOPTICAL, we use the supplied Frame preference of
+   optical/radio instead of the default returned by RedshiftSys. */
+      } else if( sys != AST__REDSHIFT ) {
+         sys = astGetSystem( frm );
+         if( sys == AST__REDSHIFT ) sys = AST__VRADIO;
+      }
+
+/* Take a copy of the supplied Frame and set its Units to the value found
+   above. */
+      cfrm = astCopy( frm );
+      astSetUnit( cfrm, 0, unit );
+
+/* If at least one limit was found, create an Interval within this
+   modified Frame. Otherwise create a negated NullRegion. */
+      if( lolimit != AST__BAD || hilimit != AST__BAD ) {
+         new = (AstRegion *) astInterval( cfrm, &lolimit, &hilimit, NULL, "", status );
+      } else {
+         new = (AstRegion *) astNullRegion( cfrm, NULL, "negated=1", status );
+      }
+
+/* If the Units of this Region differs from that of the supplied Frame,
+   set it to the Units of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new Units. If the supplied
+   Frame had no set Units, set it to the units implied by the supplied
+   XML element. */
+      if( astTestUnit( frm, 0 ) ) {
+         funit = astGetUnit( frm, 0 );
+         if( strcmp( funit, unit ) ) astSetUnit( new, 0, funit );
+      } else {
+         astSetUnit( frm, 0, unit );
+      }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      cfrm = astAnnul( cfrm );
+      if( unit ) unit = astFree( unit );
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstRegion *RedshiftReader( AstXmlChan *this, AstXmlElement *elem,
+                                  AstFrame *frm, AstKeyMap **anc, int *status ){
+/*
+*  Name:
+*     RedshiftReader
+
+*  Purpose:
+*     Modify a Frame to take account of an STC <Redshift> element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *RedshiftReader( AstXmlChan *this, AstXmlElement *elem,
+*                                AstFrame *frm, AstKeyMap **anc, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function reads the supplied STC <Redshift> element, and uses it,
+*     if possible, to create the uncertainty associated with the redshift
+*     axis in the supplied Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Redshift element.
+*     frm
+*        Pointer to the 1D redshift Frame.
+*     anc
+*        Address of a location at which to put a pointer to a newly
+*        created KeyMap. This KeyMap will contain ancillary information
+*        from the Redshift. The keys identify the item of ancillary
+*        information (Name, Value, Error, Resolution, Size, Pixel Size).
+*        The value associated with the Name key is string containing
+*        the Name item from the Redshift. The value associated with each of
+*        the other keys is a pointer to a 1D Region within the supplied
+*        Frame, corresponding to the value, error, resolution, etc. Keys
+*        will not be present in the returned KeyMap if the corresponding
+*        item of ancillary information is not present in the Redshift. A
+*        NULL pointer is returned if there is no ancillary information at all.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned:
+*     The uncertainty Region, or NULL if the supplied Redshift element
+*     does not specify an uncertainty.
+
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;         /* FrameSet connecting "sf1" and "sf2" */
+   AstMapping *map;         /* Mapping from <Redshift> Frame to supplied Frame */
+   AstRegion *r2;           /* Region mapped into returned Frame */
+   AstRegion *r3;           /* Simplified Region mapped into returned Frame */
+   AstRegion *r;            /* Original Region */
+   AstRegion *result;       /* Returned uncertainty Region */
+   AstSpecFrame *sf1;       /* SpecFrame describing value element */
+   AstSystemType fsys;      /* Redshift system from supplied Stc */
+   IVOAScan *scan;          /* Structure holding scan results */
+   const char *name;        /* Pointer to XML element name */
+   const char *names[6];    /* Names of the subelements to be searched for */
+   char *unit;              /* Pointer to Redshift's unit attribute string */
+   double lbnd[ 1 ] ;       /* Lower interval bounds */
+   double tmp;              /* Temporary storage */
+   double ubnd[ 1 ] ;       /* Upper interval bounds */
+   double v;                /* Axis value */
+   int max[6];              /* Max allowed occurrences of each name */
+   int min[6];              /* Min allowed occurrences of each name */
+
+/* Initialise */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "Name";
+   names[ 1 ] = "Error";
+   names[ 2 ] = "Value";
+   names[ 3 ] = "Resolution";
+   names[ 4 ] = "Size";
+   names[ 5 ] = "PixSize";
+   max[ 0 ] = 1;
+   max[ 1 ] = 2;
+   max[ 2 ] = 1;
+   max[ 3 ] = 2;
+   max[ 4 ] = 2;
+   max[ 5 ] = 2;
+   min[ 0 ] = 1;
+   min[ 1 ] = 0;
+   min[ 2 ] = 0;
+   min[ 3 ] = 0;
+   min[ 4 ] = 0;
+   min[ 5 ] = 0;
+   scan = ScanIVOAElement( this, elem, 6, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Create a KeyMap to return holding ancilary info, and put the Name into
+   it. */
+      *anc = astKeyMap( "", status );
+      if( scan->count[0] > 0 ) astMapPut0C( *anc, AST__STCNAME,
+                                  astXmlGetValue( scan->el[0][0], 0 ), NULL );
+
+/* Determine the units and system implied by the <Redshift> element.
+   The returned system is AST__REDSHIFT if there is no unit attribute in
+   the <Redshift> element, and is AST__VRADIO otherwise. */
+      fsys = RedshiftSys( this, elem, &unit, 1, status );
+
+/* If no system has been set in the supplied Frame, set it now to the system
+   determined above. */
+      if( !astTestSystem( frm ) ) {
+         astSetSystem( frm, fsys );
+
+/* The ReddshiftSys function above always returns AST__VRADIO if the velocity
+   is not dimensionless. However, the supplied Frame may have System set
+   explicitly to AST__VOPTICAL. In this case change the "fsys" value to use
+   AST__VOPTICAL. */
+      } else if( fsys != AST__REDSHIFT ) {
+         fsys = astGetSystem( frm );
+         if( fsys == AST__REDSHIFT ) fsys = AST__VRADIO;
+      }
+
+/* If the supplied Frame has no set units, set them now to the units of
+   the Redshift element (if any, and if the redshift is not dimensionless). */
+      if( unit && fsys != AST__REDSHIFT &&
+          astGetSystem( frm ) != AST__REDSHIFT && !astTestUnit( frm, 0 ) ) {
+         astSetUnit( frm, 0, unit );
+      }
+
+/* The values represented by the <Redshift> element may not be in the same
+   system, units, etc as the supplied SpecFrame. We will need to be able to
+   convert from one to the other, so create a SpecFrame describing the
+   system and units used by the <Redshift> element. */
+      sf1 = astCopy( frm );
+      astSetSystem( sf1, fsys );
+      if( unit ) {
+         astSetUnit( sf1, 0, unit );
+         unit = astFree( unit );
+      }
+
+/* Find the Mapping from Redshift value to the supplied SpecFrame value */
+      fs = astConvert( sf1, frm, "" );
+      if( fs ) {
+         map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+         fs = astAnnul( fs );
+      } else {
+         map = NULL;
+         Report( this, elem, FAILURE, "connot convert AstroCoords "
+                 "redshift values to the required redshift system", status );
+      }
+
+/* If this Redshift contains a Value which can be read, obtain it. */
+      if( scan->count[ 2 ] > 0 ) {
+         name = astXmlGetName( scan->el[ 2 ][ 0 ] );
+         if( name && !strcmp( name, "Value" ) ) {
+            v = ElemValueD( this, scan->el[ 2 ][ 0 ], AST__BAD, status );
+
+/* Convert the value into the supplied SpecFrame system. Create an
+   Interval describing it and store it in the returned ancillary keyMap.
+   Note we create an Interval rather than a PintList since the Prism
+   class can only extrude using Intervals. */
+            astTran1( map, 1, &v, 1, &tmp );
+            r = (AstRegion *) astInterval( frm, &tmp, &tmp, NULL, "", status ) ;
+            astMapPut0A( *anc, AST__STCVALUE, r, NULL );
+            r = astAnnul( r );
+         }
+      }
+
+/* Check for Error values in the Redshift. */
+      if( scan->count[ 1 ] > 0 ) {
+
+/* Issue a warning if more than 1 Error value was found. */
+         if( scan->count[ 1 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Error>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first Error value. */
+         v = ElemValueD( this, scan->el[1][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of an error bar centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the Frame represented by the Redshift element.
+   Map it into the supplied Frame. Simplify it. Store in the returned
+   ancillary KeyMap. */
+            r = (AstRegion *) astInterval( sf1, lbnd, ubnd, NULL, "", status );
+            r2 = astMapRegion( r, map, frm );
+            result = astSimplify( r2 );
+            astMapPut0A( *anc, AST__STCERROR, result, NULL );
+            r2 = astAnnul( r2 );
+            r = astAnnul( r );
+         }
+      }
+
+/* Check for Resolution values in the Redshift. */
+      if( scan->count[ 3 ] > 0 ) {
+
+/* Issue a warning if more than 1 value was found. */
+         if( scan->count[ 3 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Resolution>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first value. */
+         v = ElemValueD( this, scan->el[3][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of an interval centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the Frame represented by the Redshift element.
+   Map it into the supplied Frame. Simplify it. Store in the returned
+   ancillary KeyMap. */
+            r = (AstRegion *) astInterval( sf1, lbnd, ubnd, NULL, "", status );
+            r2 = astMapRegion( r, map, frm );
+            r3 = astSimplify( r2 );
+            astMapPut0A( *anc, AST__STCRES, r3, NULL );
+            r3 = astAnnul( r3 );
+            r2 = astAnnul( r2 );
+            r = astAnnul( r );
+         }
+      }
+
+/* Check for Size values in the Redshift. */
+      if( scan->count[ 4 ] > 0 ) {
+
+/* Issue a warning if more than 1 value was found. */
+         if( scan->count[ 4 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Size>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first value. */
+         v = ElemValueD( this, scan->el[4][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of an interval centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the Frame represented by the Redshift element.
+   Map it into the supplied Frame. Simplify it. Store in the returned
+   ancillary KeyMap. */
+            r = (AstRegion *) astInterval( sf1, lbnd, ubnd, NULL, "", status );
+            r2 = astMapRegion( r, map, frm );
+            r3 = astSimplify( r2 );
+            astMapPut0A( *anc, AST__STCSIZE, r3, NULL );
+            r3 = astAnnul( r3 );
+            r2 = astAnnul( r2 );
+            r = astAnnul( r );
+         }
+      }
+
+/* Check for PixSize values in the Redshift. */
+      if( scan->count[ 5] > 0 ) {
+
+/* Issue a warning if more than 1 value was found. */
+         if( scan->count[ 5 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <PixSize>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first value. */
+         v = ElemValueD( this, scan->el[5][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of an interval centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the Frame represented by the Redshift element.
+   Map it into the supplied Frame. Simplify it. Store in the returned
+   ancillary KeyMap. */
+            r = (AstRegion *) astInterval( sf1, lbnd, ubnd, NULL, "", status );
+            r2 = astMapRegion( r, map, frm );
+            r3 = astSimplify( r2 );
+            astMapPut0A( *anc, AST__STCPIXSZ, r3, NULL );
+            r3 = astAnnul( r3 );
+            r2 = astAnnul( r2 );
+            r = astAnnul( r );
+         }
+      }
+
+/* Free resources. */
+      scan = FreeIVOAScan( scan, status );
+      sf1 = astAnnul( sf1 );
+      map = astAnnul( map );
+   }
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result */
+   return result;
+
+}
+
+static AstSystemType RedshiftSys( AstXmlChan *this, AstXmlElement *elem,
+                                  char **unit, int report, int *status ){
+/*
+*  Name:
+*     RedshiftSys
+
+*  Purpose:
+*     Determine the redshift system described by the attributes in a
+*     given element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstSystemType RedshiftSys( AstXmlChan *this, AstXmlElement *elem,
+*                                char **unit, int report, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function determines the redshift system described by the unit and
+*     vel_time_unit attributes in the supplied element. It optionally reports
+*     an error if the units are not recognised.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA element containing the unit attribute to be used.
+*     unit
+*        Pointer to a location at which to return a pointer to a
+*        dynamically allocated string in which is stored the total units string
+*        implied by the unit and vel_time_unit attributes. This string
+*        should be freed when no longer needed using astFree. A NULL
+*        pointer is returned if either of the two attributes (unit and
+*        vel_time_unit) is not found in the supplied element, or if an error
+*        occurs.
+*     report
+*        If non-zero, then a failure is reported if the spectral system
+*        cannot be determined from the supplied string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned:
+*     The redshift system (radio velocity if both unit and vel_time_unit
+*     attributes are present in the supplied element, or dimensionaless
+*     redshift otherwise).
+
+*/
+
+/* Local Variables: */
+   const char *punit;            /* Pointer to positional unit string */
+   const char *tunit;            /* Pointer to time unit string */
+   int pl;                       /* Length of punit string */
+   int tl;                       /* Length of tunit string */
+
+/* Initialise. */
+   *unit = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return AST__BADSYSTEM;
+
+/* Get the Unit attribute from the element (this describes units of position) */
+   punit = astXmlGetAttributeValue( elem, "unit" );
+   if( punit ) {
+
+/* Check it is a linear measure (not angular). */
+      if( strstr( "m", punit ) &&
+          strstr( "km", punit ) &&
+          strstr( "mm", punit ) &&
+          strstr( "AU", punit ) &&
+          strstr( "kpc", punit ) &&
+          strstr( "Mpc", punit ) &&
+          strstr( "lyr", punit ) ) {
+         if( report ) Report( this, elem, FAILURE, "contains an angular unit attribute", status );
+      }
+   }
+
+/* Get the vel_time_unit attribute from the element (this describes units of
+   time). If OK, construct the total unit string (eg "km/h") . */
+   tunit = astXmlGetAttributeValue( elem, "vel_time_unit" );
+   if( tunit ) {
+      if( !punit ) {
+         if( report ) Report( this, elem, FAILURE, "contains time units but not position units - assuming Z", status );
+      } else {
+         pl = strlen( punit );
+         tl = strlen( tunit );
+         *unit = astMalloc( (size_t)( pl + tl + 2 ) );
+         if( *unit ) {
+            strcpy( *unit, punit );
+            (*unit)[ pl ] = '/';
+            strcpy( *unit + pl + 1, tunit );
+         }
+      }
+
+   } else if( punit ) {
+      if( report ) Report( this, elem, FAILURE, "contains position units but not time units - assuming Z", status );
+   }
+
+/* Return a default system (radio velocity if both units are present,
+   dimensionless redshift otherwise). */
+   return ( punit && tunit ) ? AST__VRADIO : AST__REDSHIFT;
+}
+
+static AstRegion *RegionReader( AstXmlChan *this, AstXmlElement *elem,
+                                AstFrame *frm, int *status ){
+/*
+*  Name:
+*     RegionReader
+
+*  Purpose:
+*     Make an AST Region from any subclass of IVOA Region element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *RegionReader( AstXmlChan *this, AstXmlElement *elem,
+*                              AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     element which can be of any subclass of Region.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstRegion *new;               /* Pointer to returned Region */
+   const char *name;             /* Region type */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Process each supported type of STC Region. */
+   name = astXmlGetName( elem );
+   if( !strcmp( name, "Intersection" ) ) {
+      new = IntersectionReader( this, elem, frm, status );
+
+   } else if( !strcmp( name, "Union" ) ) {
+      new = UnionReader( this, elem, frm, status );
+
+   } else if( !strcmp( name, "Negation" ) ) {
+      new = NegationReader( this, elem, frm, status );
+
+   } else if( !strcmp( name, "AllSky" ) ) {
+      new = AllSkyReader( this, elem, frm, status );
+
+   } else if( !strcmp( name, "Circle" ) ) {
+      new = CircleReader( this, elem, frm, status );
+
+   } else if( !strcmp( name, "Ellipse" ) ) {
+      new = EllipseReader( this, elem, frm, status );
+
+   } else if( !strcmp( name, "Polygon" ) ) {
+      new = PolygonReader( this, elem, frm, status );
+
+   } else if( !strcmp( name, "Box" ) ) {
+      new = BoxReader( this, elem, frm, status );
+
+   } else if( !strcmp( name, "Convex" ) ) {
+      new = ConvexReader( this, elem, frm, status );
+
+   } else {
+      astError( AST__INTER, "RegionReader(XmlChan): Does not yet "
+                "support \"%s\" regions (internal AST programming "
+                "error).", status, name );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstXmlElement *Remove( AstXmlChan *this, AstXmlElement *element, int *status ) {
+/*
+*  Name:
+*     Remove
+
+*  Purpose:
+*     Remove an element from the current container element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstXmlElement *Remove( AstXmlChan *this, AstXmlElement *element, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function removes the specified element from the current
+*     container element, and then annuls the removed element. An error is
+*     reported if the element being removed contains anything other than
+*     comments, "isa" elements and blank character data (all contents should
+*     have been consumed by the process of reading the object).
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     element
+*        Pointer to the XML element to be removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A NULL pointer is returned.
+
+*/
+
+/* Local Variables: */
+   AstXmlContentItem *item; /* Item */
+   const char *def;         /* Pointer to default attribute value */
+   int i;                   /* Index of current item */
+   int nitem;               /* Number of items still in the element */
+
+/* Check the global error status, and the supplied element. */
+   if ( !astOK || !element ) return NULL;
+
+/* Check we have a container from which to remove the element. If so,
+   check that the container is the elements parent. If so, remove the
+   element from its parent container. */
+   if( this->container ) {
+      if( (AstXmlParent *) this->container != astXmlGetParent( element ) ){
+         astError( AST__INTER, "Remove(XmlChan): Supplied element is not "
+                   "contained within the current container element (internal "
+                   "AST programming error)." , status);
+      } else {
+         astXmlRemoveItem( element );
+      }
+   }
+
+/* Check that the element being removed is empty (apart from comments,
+   defaulted values and "isa" elements). */
+   nitem = astXmlGetNitem( element );
+   for( i = 0; i < nitem; i++ ) {
+      item = astXmlGetItem( element, i );
+      if( astXmlCheckType( item, AST__XMLELEM ) ) {
+
+/* See if this element represents a default value */
+         def = astXmlGetAttributeValue( item, DEFAULT );
+
+/* Default values and "isa" elements are OK. */
+         if( ( !def || strcmp( def, TRUE ) ) && astOK &&
+             strcmp( astXmlGetName( item ), ISA ) ) {
+
+/* Remove any "definedby" attribute (added by ReadClassData) so that it
+   does not appear in the error message. */
+            if( astXmlGetAttributeValue( item, DEFINEDBY ) ) {
+               astXmlRemoveAttr( item, DEFINEDBY, NULL );
+            }
+
+/* Report the error. */
+            if( astOK ) astError( AST__BADIN, "astRead(XmlChan): The following "
+                                  "tag was not recognised as valid input within "
+                                  "a %s: %s", status, astXmlGetName( element ),
+                                  GetTag( (AstXmlObject *) item, 1, status ) );
+            break;
+         }
+
+/* Character data is OK so long as it contains only white space */
+      } else if( astXmlCheckType( item, AST__XMLBLACK ) ) {
+         astError( AST__BADIN, "astRead(XmlChan): The following character "
+                   "data was not recognised as valid input within a %s: %s", status,
+                   astXmlGetName( element ), astXmlGetValue( item, 0 ) );
+         break;
+
+      } else if( astXmlCheckType( item, AST__XMLCDATA ) ) {
+         astError( AST__BADIN, "astRead(XmlChan): The following CDATA section "
+                   "data was not recognised as valid input within a %s: %s", status,
+                   astXmlGetName( element ), astXmlGetValue( item, 0 ) );
+         break;
+
+      } else if( astXmlCheckType( item, AST__XMLPI ) ) {
+         astError( AST__BADIN, "astRead(XmlChan): The following processing "
+                   "instruction was not recognised as valid input within "
+                   "a %s: %s", status, astXmlGetName( element ), GetTag( (AstXmlObject *) item, 1, status ) );
+         break;
+      }
+   }
+
+/* Remove the element from its parent and the annul it. */
+   astXmlRemoveItem( element );
+   astXmlAnnul( element );
+
+/* Return a NULL pointer. */
+   return NULL;
+}
+
+static void Report( AstXmlChan *this, AstXmlElement *elem, int severity,
+                    const char *msg, int *status ){
+/*
+*  Name:
+*     Report
+
+*  Purpose:
+*     Handle problems reading supplied XML.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void Report( AstXmlChan *this, AstXmlElement *elem, int severity,
+*                  const char *msg, int *status )
+
+*  Class Membership:
+*     XmlChan member function
+
+*  Description:
+*     This function handles conditions which arise whilst interpreting
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the XmlElement which cound not be interpreted.
+*     severity
+*        WARNING (in which case the message is added to a list of
+*        warnings, but execution continues), or FAILURE, in which case
+*        an error is reported using astError, or RESET in which case any
+*        warnings stored in the XmlChan are removed ("elem" and "msg" are
+*        ignored).
+*     msg
+*        A message describing the condition.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   char *text;                /* Pointer to tformatted element text */
+   const char *name;          /* Element name */
+
+   if( severity == RESET ) astAddWarning( this, 0, NULL, NULL, status );
+
+   if( !astOK ) return;
+
+   if( severity == WARNING && !astGetStrict( this ) ) {
+      name = astXmlGetName( elem );
+      astAddWarning( this, 1, "astRead(%s): Warning whilst reading %s %s "
+                     "element: %s", "astRead", status, astGetClass( this ),
+                     ANA(name), name, msg );
+   } else {
+      text = (char *) astXmlGetTag( elem, 1 );
+      astError( AST__BADIN, "astRead(%s): Failed to read %s element: %s", status,
+                astGetClass( this ), text, msg );
+      text = astFree( text );
+   }
+}
+
+static IVOAScan *ScanIVOAElement( AstXmlChan *this, AstXmlElement *elem, int n,
+                                  const char *names[], int min[], int max[], int *status ){
+/*
+*  Name:
+*     ScanIVOAElement
+
+*  Purpose:
+*     Identify required sub-elements within an IVOA element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     IVOAScan *ScanIVOAElement( AstXmlChan *this, AstXmlElement *elem, int n,
+*                                const char *names[], int min[], int max[], int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function searches the supplied XML element for named sub-elements.
+*     A structure is returned containing the number of sub-elements found
+*     with each name, and pointers to the sub-elements. This structure
+*     should be freed using FreeIVOAScan when no longer needed.
+*
+*     Reports are made about any content in the supplied element which is
+*     not specified in the list of known sub-element names (excepting
+*     comments and white space).
+*
+*     Reports are also made if the number of sub-elements found with each
+*     known name is inappropriate (the minimum and maximum allowed
+*     occurrences of each name is specified by the caller).
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the XML element to be searched.
+*     n
+*        The number of names supplied in "names"
+*     names
+*        An array holding pointers to strings giving the names of the known
+*        sub-elements. Each string may be either a single element name,
+*        or a set of element names separated by "|" (the string must
+*        also start and end with a "|"). If a set is supplied, then the
+*        associated "min" and "max" values specify the minimum and maximum
+*        total number of occurrences of all names in the set, and the
+*        occurrence count stored in the returned structure gives the total
+*        number of occurrences of all names in the set.
+*     min
+*        An array holding the mimimum number of occrrences of each name within
+*        the element being searched. Supplied in the same order as the names
+*        themselves.
+*     max
+*        An array holding the maximum number of occrrences of each name within
+*        the element being searched. Supplied in the same order as the names
+*        themselves.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the IVOAScan structure holding the results of the
+*     scan. A NULL pointer is returned unless all names have at least
+*     their minimum number of occurrences in the supplied element. A NULL
+*     pointer is returned if an error occurs.
+
+*/
+
+/* Local Variables: */
+   AstXmlContentItem *item;      /* Current content item */
+   IVOAScan *result;             /* Pointer to returned structure */
+   char *text;                   /* Pointer formatted item string */
+   char buff[ 200 ];             /* Message buffer */
+   const char *name;             /* Pointer to element name string */
+   const char *w1;               /* Pointer to word to use in message */
+   const char *w2;               /* Pointer to word to use in message */
+   const char *p;                /* Pointer to start of name in string */
+   int i;                        /* Index of current content item */
+   int j;                        /* Index of current name */
+   int k;                        /* Index of current occurrence of name */
+   int l;                        /* Length of element name */
+   int known;                    /* Was content item known? */
+   int nitem;                    /* No. of content items in supplied element */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Initialise a structure to hold the results of the scan. */
+   result = astMalloc( sizeof(IVOAScan) );
+   if( result ) {
+      result->n = n;
+      result->count = astMalloc( sizeof(int)*(size_t)n);
+      result->el = astMalloc( sizeof(AstXmlElement **)*(size_t)n);
+      if( result->el ) {
+         for( j = 0; j < n; j++ ) {
+            result->count[ j ] = 0;
+            result->el[ j ] = NULL;
+         }
+      }
+   }
+
+/* Loop round all items in the elements contents. */
+   if( astOK ) {
+      nitem = astXmlGetNitem( elem );
+      for( i = 0; i < nitem; i++ ) {
+         item = astXmlGetItem( elem, i );
+         known = 1;
+
+/* If it is not an XML element, it is not known. */
+         if( !astXmlCheckType( item, AST__XMLELEM ) ) {
+            known = 0;
+
+/* If it is an element, get the name of the element. */
+         } else {
+            name = astXmlGetName( item );
+
+/* See if this name is in the supplied list of known names. */
+            known = 0;
+            j = 0;
+            if( name ) {
+               l = strlen( name );
+               for( j = 0; j < n; j++ ) {
+                  p = strstr(  names[ j ], name );
+                  if( p ){
+                     if( p == names[ j ] ) {
+                        if( p[ l ] == 0 || p[ l ] == '|' ) {
+                           known = 1;
+                           break;
+                        }
+                     } else {
+                        if( p[ -1 ] == '|' && ( p[ l ] == 0 || p[ l ] == '|' ) ) {
+                           known = 1;
+                           break;
+                        }
+                     }
+                  }
+               }
+            }
+
+/* If it is known, store the element in the results structure */
+            if( known ) {
+               k = ( result->count[ j ] )++;
+               result->el[ j ]= astGrow( result->el[ j ], k + 1,
+                                         sizeof( AstXmlElement * ) );
+               if(  result->el[ j ] ) {
+                  result->el[ j ][ k ] = (AstXmlElement *) item;
+               } else {
+                  break;
+               }
+            }
+         }
+
+/* If this content item was not known, issue a warning unless it is a comment
+   or white space. */
+         if( !known && !astXmlCheckType( item, AST__XMLCOM ) &&
+                       !astXmlCheckType( item, AST__XMLWHITE ) ) {
+            text = (char *) astXmlFormat( item );
+            if( text ) {
+               if( strlen( text ) > 30 ) text[ 30 ] = 0;
+               sprintf( buff, "contains the following which is being ignored: \"%s\"",
+                        text );
+               text = astFree( text );
+               Report( this, elem, WARNING, buff, status );
+            }
+         }
+      }
+
+/* Now check that the number of instances of each element found is OK.
+   Report warnings or failures if not. */
+      if( astOK ) {
+         for( j = 0; j < n; j++ ) {
+            if( result->count[ j ] < min[ j ] ) {
+               w1 = ( result->count[ j ] == 1 ) ? "element" : "elements";
+               w2 = ( min[ j ] == 1 ) ? "is" : "are";
+               sprintf( buff, "contains %d <%s> %s but at least %d %s needed",
+                        result->count[ j ], names[ j ], w1, min[ j ], w2 );
+               Report( this, elem, FAILURE, buff, status );
+
+            } else if ( result->count[ j ] > max[ j ] ) {
+               w1 = ( result->count[ j ] == 1 ) ? "element" : "elements";
+               w2 = ( max[ j ] == 1 ) ? "is" : "are";
+               sprintf( buff, "contains %d <%s> %s but no more than %d %s "
+                        "allowed (only the first will be used)",
+                        result->count[ j ], names[ j ], w1, max[ j ], w2 );
+               Report( this, elem, WARNING, buff, status );
+            }
+         }
+      }
+   }
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = FreeIVOAScan( result, status );
+
+/* Return the results structure.*/
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a XmlChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the astSetAttrib protected
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function assigns an attribute value for a XmlChan, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     setting
+*        Pointer to a null terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* Pointer to the XmlChan structure */
+   int ival;                     /* Integer attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by "astSscanf" */
+   int pr;                       /* Offset to start of string */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* XmlLength */
+/* ----------*/
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "xmllength= %d %n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+      astSetXmlLength( this, ival );
+
+/* XmlFormat */
+/* ----------*/
+   } else if( nc = 0,
+        ( 0 == astSscanf( setting, "xmlformat=%n%*[^\n]%n", &ival, &nc ) )
+        && ( nc >= len ) ) {
+
+      nc = astChrLen( setting + ival );
+
+      if( !Ustrncmp( setting + ival, NATIVE_STRING, nc, status ) ){
+         astSetXmlFormat( this, NATIVE_FORMAT );
+
+      } else if( !Ustrncmp( setting + ival, QUOTED_STRING, nc, status ) ){
+         astSetXmlFormat( this, QUOTED_FORMAT );
+
+      } else if( !Ustrncmp( setting + ival, IVOA_STRING, nc, status ) ){
+         astSetXmlFormat( this, IVOA_FORMAT );
+
+      } else {
+         astError( AST__BADAT, "astSet(%s): Unknown XML format '%s' "
+                   "requested for a %s.", status, astGetClass( this ), setting + ival,
+                   astGetClass( this ) );
+      }
+
+/* XmlPrefix */
+/* ----------*/
+   } else if ( nc = 0, ( 0 == astSscanf( setting, "xmlprefix=%n%*[^\n]%n", &pr, &nc ) )
+                && ( nc >= len ) ) {
+      astSetXmlPrefix( this, setting + pr );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static void SinkWrap( void (* sink)( const char * ), const char *line, int *status ) {
+/*
+*  Name:
+*     SinkWrap
+
+*  Purpose:
+*     Wrapper function to invoke a C XmlChan sink function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     void SinkWrap( void (* sink)( const char * ), const char *line, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function invokes the sink function whose pointer is
+*     supplied in order to write an output line to an external data
+*     store.
+
+*  Parameters:
+*     sink
+*        Pointer to a sink function, whose single parameter is a
+*        pointer to a const, null-terminated string containing the
+*        text to be written, and which returns void. This is the form
+*        of XmlChan sink function employed by the C language interface
+*        to the AST library.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Invoke the sink function. */
+   ( *sink )( line );
+}
+
+static char *SourceWrap( const char *(* source)( void ), int *status ) {
+/*
+*  Name:
+*     SourceWrap
+
+*  Purpose:
+*     Wrapper function to invoke a C XmlChan source function.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     char *SourceWrap( const char *, int *status(* source)( void ) )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function invokes the source function whose pointer is
+*     supplied in order to read the next input line from an external
+*     data store. It then returns a pointer to a dynamic string
+*     containing a copy of the text that was read.
+
+*  Parameters:
+*     source
+*        Pointer to a source function, with no parameters, that
+*        returns a pointer to a const, null-terminated string
+*        containing the text that it read. This is the form of XmlChan
+*        source function employed by the C language interface to the
+*        AST library.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated, null terminated string
+*     containing a copy of the text that was read. This string must be
+*     freed by the caller (using astFree) when no longer required.
+*
+*     A NULL pointer will be returned if there is no more input text
+*     to read.
+
+*  Notes:
+*     - A NULL pointer value will be returned if this function is
+*     invoked with the global error status set or if it should fail
+*     for any reason.
+*/
+
+/* Local Variables: */
+   char *result;                 /* Pointer value to return */
+   const char *line;             /* Pointer to input line */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Invoke the source function to read the next input line and return a
+   pointer to the resulting string. */
+   line = ( *source )();
+
+/* If a string was obtained, make a dynamic copy of it and save the
+   resulting pointer. */
+   if ( line ) result = astString( line, (int) strlen( line ) );
+
+/* Return the result. */
+   return result;
+}
+
+static AstObject *SpaceFrameReader( AstXmlChan *this,
+                                    AstXmlElement *elem, int *status ) {
+/*
+*  Name:
+*     SpaceFrameReader
+
+*  Purpose:
+*     Make an AST Object from an IVOA SpaceFrame element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstObject *SpaceFrameReader( AstXmlChan *this, AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Object from the supplied IVOA
+*     SpaceFrame element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA SpaceFrame element.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object.
+
+*/
+
+/* Local Variables: */
+   AstObject *new;               /* Pointer to returned Object */
+   AstXmlElement *el;            /* Pointer to sub-element */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *dom;              /* Domain string for returned SkyFrame */
+   const char *eq;               /* Equinox string for returned SkyFrame */
+   const char *names[4];         /* Names of the subelements to be searched for */
+   const char *sys;              /* System for returned Frame */
+   int ignore_h;                 /* Ignore 3rd spherical axis? */
+   int max[4];                   /* Max allowed occurrences of each name */
+   int min[4];                   /* Min allowed occurrences of each name */
+   int isgeod;                   /* Is the system geodetic lon/lat? */
+   int isgeoc;                   /* Is the system geocentric lon/lat? */
+   int need_eq;                  /* Does system need an equinox? */
+
+/* Initialise */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "ICRS|GALACTIC_II|SUPER_GALACTIC|HEE|FK4|FK5|ECLIPTIC|GEO_C|GEO_D";
+   names[ 1 ] = "TOPOCENTER";
+   names[ 2 ] = "Name";
+   names[ 3 ] = "SPHERICAL|CARTESIAN|UNITSPHERE|POLAR";
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   max[ 2 ] = 1;
+   max[ 3 ] = 1;
+   min[ 0 ] = 1;
+   min[ 1 ] = 1;
+   min[ 2 ] = 0;
+   min[ 3 ] = 1;
+   scan = ScanIVOAElement( this, elem, 4, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the sky coordinate system specified in the element. */
+      sys = astXmlGetName( scan->el[0][0] );
+      need_eq = 0;
+      dom = NULL;
+
+/* If the system is geodetic or geocentric, ignore height information if
+   supplied. This is so we can get an approximation to an observatory
+   position given in 3D, for use with SpecFrame. */
+      ignore_h = 0;
+      isgeod = sys && !strcmp( sys, "GEO_D" );
+      isgeoc = sys && !strcmp( sys, "GEO_C" );
+      if( isgeod || isgeoc ){
+         if( AttrValueI( this, scan->el[3][0], "coord_naxes", 2, status ) != 2 ) {
+            Report( this, elem, WARNING, "contains 3D spherical spatial "
+                    "coords (unsupported by AST - height information will "
+                    "be ignored)", status );
+            ignore_h = 1;
+         }
+
+/* If the system is geodetic ignore any attributes specifying a reference
+   spheroid. */
+         if( isgeod && astXmlGetNattr( scan->el[0][0] ) > 0 ) {
+            Report( this, elem, WARNING, "contains reference spheroid "
+                    "(unsupported by AST - default values will be used)", status );
+         }
+      }
+
+/* Check that the spatial axes are longitude/latitude */
+      if( strcmp( "SPHERICAL", astXmlGetName( scan->el[3][0] ) ) ){
+         Report( this, elem, FAILURE, "contains non-spherical spatial "
+                 "coords (currently unsupported by AST)", status );
+
+      } else if( !ignore_h && AttrValueI( this, scan->el[3][0], "coord_naxes", 2, status ) != 2 ) {
+         Report( this, elem, FAILURE, "contains 3D spherical spatial "
+                 "coords (currently unsupported by AST)", status );
+
+      } else if( AttrValueB( this, scan->el[3][0], "coord_vel", 0, status ) ) {
+         Report( this, elem, FAILURE, "contains velocity coords", status );
+
+/* Now check for the supported sky coordinate systems and translate to the
+   equivalent AST value. Note if the system needs an equinox to qualify it. */
+      } else if( !strcmp( sys, "GALACTIC_II" ) ){
+         sys = "GALACTIC";
+         need_eq = 0;
+
+      } else if( !strcmp( sys, "SUPER_GALACTIC" ) ){
+         sys = "SUPERGALACTIC";
+         need_eq = 0;
+
+      } else if( !strcmp( sys, "HEE" ) ){
+         sys = "HELIOECLIPTIC";
+         need_eq = 0;
+
+      } else if( !strcmp( sys, "FK4" ) ) {
+         sys = "FK4";
+         need_eq = 1;
+
+      } else if( !strcmp( sys, "FK5" ) ) {
+         sys = "FK5";
+         need_eq = 1;
+
+      } else if( !strcmp( sys, "ECLIPTIC" ) ) {
+         sys = "ECLIPTIC";
+         need_eq = 1;
+
+      } else if( isgeoc ) {
+         dom = "GEO_C";
+         sys = "UNKNOWN";
+         need_eq = 0;
+
+      } else if( isgeod ) {
+         dom = "GEO_D";
+         sys = "UNKNOWN";
+         need_eq = 0;
+
+      } else {
+         sys = "ICRS";
+         need_eq = 0;
+      }
+
+/* Extract the equinox if required. */
+      if( need_eq ) {
+         el = FindElement( this, scan->el[0][0], "Equinox", status );
+         if( el ) {
+            eq = astXmlGetValue( el, 0 );
+            if( !eq ) Report( this, scan->el[0][0], WARNING, "contains an "
+                              "<Equinox> element which is not simply "
+                              "character data. The AST default (B1950 "
+                              "or J2000) will be used", status );
+         } else {
+            eq = NULL;
+            Report( this, scan->el[0][0], WARNING, "contains no <Equinox> element. "
+                              "The AST default (B1950 or J2000) will be used", status );
+         }
+
+      } else {
+         eq = NULL;
+      }
+
+/* Create a suitable SkyFrame. */
+      new = (AstObject *) astSkyFrame( "system=%s", status, sys);
+      if( eq ) astSetC( new, "Equinox", eq );
+      if( dom ) astSetDomain( new, dom );
+
+      if( isgeod ){
+         astSetLabel( new, 0, "Geodetic longitude" );
+         astSetLabel( new, 1, "Geodetic latitude" );
+
+      } else if( isgeoc ){
+         astSetLabel( new, 0, "Geocentric longitude" );
+         astSetLabel( new, 1, "Geocentric latitude" );
+      }
+
+/* If the SpaceFrame has a <Name> element use it as the Frame title. */
+      if( scan->count[2] ) astSetTitle( new, astXmlGetValue( scan->el[2][0], 0 ) );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Object. */
+   return (AstObject *) new;
+}
+
+static AstSystemType SpecSys( AstXmlChan *this, AstXmlElement *elem,
+                              const char *unit, int report, int *status ) {
+/*
+*  Name:
+*     SpecSys
+
+*  Purpose:
+*     Determine the spectral system described by a given units string.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstSystemType SpecSys( AstXmlChan *this, AstXmlElement *elem,
+*                            const char *unit, int report, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function determines the spectral system described by a given units
+*     string. It optionally reports an error if the string is not
+*     recognised.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan. Only used if "report" is non-zero.
+*     elem
+*        Pointer to the IVOA element to which the unit relates. Only used
+*        if "report" is non-zero.
+*     unit
+*        Pointer to the units string.
+*     report
+*        If non-zero, then a failure is reported if the spectral system
+*        cannot be determined from the supplied string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned:
+*     The spectral system, or AST__BADSYSTEM if an error occurs.
+
+*/
+
+/* Local Variables: */
+   AstMapping *map;         /* Mapping from supplied unit to default unitl */
+   AstSystemType sys;       /* System value corresponding to "unit" */
+   char buff[200];          /* Buffer for failure message */
+
+/* Initialise */
+   sys = AST__BADSYSTEM;
+
+/* Check inherited status */
+   if( !astOK ) return sys;
+
+/* See if a Mapping can be found from the supplied units to "Hz". If
+   so, the supplied units are assumed to describe frequency. */
+   map = astUnitMapper( unit, "Hz", NULL, NULL );
+   if( map ) {
+      sys = AST__FREQ;
+
+/* Otherwise, see if a Mapping can be found from the supplied units to
+   "m" (metre). If so, the supplied units are assumed to describe wavelength. */
+   } else {
+      map = astUnitMapper( unit, "m", NULL, NULL );
+      if( map ) {
+         sys = AST__WAVELEN;
+
+/* Otherwise, see if a Mapping can be found from the supplied units to
+   "J" (Joule). If so, the supplied units are assumed to describe energy. */
+      } else {
+         map = astUnitMapper( unit, "J", NULL, NULL );
+         if( map ) {
+            sys = AST__ENERGY;
+
+/* Otherwise, see if a Mapping can be found from the supplied units to
+   "m^-1" (per metre). If so, the supplied units are assumed to describe
+   wave number. */
+         } else {
+            map = astUnitMapper( unit, "m^-1", NULL, NULL );
+            if( map ) {
+               sys = AST__WAVENUM;
+
+/* Otherwise, report an error if requested. */
+            } else if( report ){
+               sprintf( buff, "contains unsupported spectral units \"%s\"", unit );
+               Report( this, elem, FAILURE, buff, status );
+            }
+         }
+      }
+   }
+
+/* Free resources */
+   if( map ) map = astAnnul( map );
+
+/* Return the result. */
+   return sys;
+}
+
+static AstRegion *SpectralReader( AstXmlChan *this, AstXmlElement *elem,
+                                  AstFrame *frm, double *rf,
+                                  AstKeyMap **anc, int *status ){
+/*
+*  Name:
+*     SpectralReader
+
+*  Purpose:
+*     Modify a Frame to take account of an STC <Spectral> element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *SpectralReader( AstXmlChan *this, AstXmlElement *elem,
+*                                AstFrame *frm, double *rf,
+*                                AstKeyMap **anc, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function reads the supplied STC <Spectral> element, and uses it,
+*     if possible, to create the uncertainty associated with the spectral
+*     axis in the supplied Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Spectral element.
+*     frm
+*        Pointer to the 1D spectral Frame.
+*     rf
+*        Point to double in which to return the rest frequency to be used
+*        with any redshift axis. Value is returned in Hz. AST__BAD will
+*        be returned if no rest frequency is found.
+*     anc
+*        Address of a location at which to put a pointer to a newly
+*        created KeyMap. This KeyMap will contain ancillary information
+*        from the Spectral. The keys identify the item of ancillary
+*        information (Name, Value, Error, Resolution, Size, Pixel Size).
+*        The value associated with the Name key is string containing
+*        the Name item from the Spectral. The value associated with each of
+*        the other keys is a pointer to a 1D Region within the supplied
+*        Frame, corresponding to the value, error, resolution, etc. Keys
+*        will not be present in the returned KeyMap if the corresponding
+*        item of ancillary information is not present in the Spectral. A
+*        NULL pointer is returned if there is no ancillary information at all.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned:
+*     The uncertainty Region, or NULL if the supplied Spectral element
+*     does not specify an uncertainty.
+
+*/
+
+/* Local Variables: */
+   AstFrameSet *fs;         /* FrameSet connecting "sf1" and "sf2" */
+   AstMapping *map;         /* Mapping from <Spectral> Frame to supplied Frame */
+   AstRegion *r2;           /* Region mapped into returned Frame */
+   AstRegion *r3;           /* Simplified Region mapped into returned Frame */
+   AstRegion *r;            /* Original Region */
+   AstRegion *result;       /* Returned uncertainty Region */
+   AstSpecFrame *sf1;       /* SpecFrame describing value element */
+   AstSpecFrame *sf2;       /* SpecFrame describing returned "rf" value */
+   AstSystemType fsys;      /* Spectral system from supplied Stc */
+   IVOAScan *scan;          /* Structure holding scan results */
+   const char *name;        /* Pointer to XML element name */
+   const char *names[6];    /* Names of the subelements to be searched for */
+   const char *unit;        /* Pointer to Spectral's unit attribute string */
+   double lbnd[ 1 ] ;       /* Lower interval bounds */
+   double ubnd[ 1 ] ;       /* Upper interval bounds */
+   double tmp;              /* Mapped value */
+   double v;                /* Axis value */
+   int max[6];              /* Max allowed occurrences of each name */
+   int min[6];              /* Min allowed occurrences of each name */
+
+/* Initialise */
+   result = NULL;
+   *rf = AST__BAD;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "Name";
+   names[ 1 ] = "Error";
+   names[ 2 ] = "Value";
+   names[ 3 ] = "Resolution";
+   names[ 4 ] = "Size";
+   names[ 5 ] = "PixSize";
+   max[ 0 ] = 1;
+   max[ 1 ] = 2;
+   max[ 2 ] = 1;
+   max[ 3 ] = 2;
+   max[ 4 ] = 2;
+   max[ 5 ] = 2;
+   min[ 0 ] = 1;
+   min[ 1 ] = 0;
+   min[ 2 ] = 0;
+   min[ 3 ] = 0;
+   min[ 4 ] = 0;
+   min[ 5 ] = 0;
+   scan = ScanIVOAElement( this, elem, 6, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Create a KeyMap to return holding ancilary info, and put the Name into
+   it. */
+      *anc = astKeyMap( "", status );
+      if( scan->count[0] > 0 ) astMapPut0C( *anc, AST__STCNAME,
+                                  astXmlGetValue( scan->el[0][0], 0 ), NULL );
+
+/* The values represented by the <Spectral> element may not be in the same
+   system,units, etc as the supplied SpecFrame. We will need to be able to
+   convert from one to the other, so create a SpecFrame describing the
+   system and units used by the <Spectral> element. If the element does not
+   have a unit attribute, assume the values are in the supplied SpecFrame
+   system and units. */
+      unit = astXmlGetAttributeValue( elem, "unit" );
+      if( unit ) {
+         sf1 = astCopy( frm );
+         fsys = SpecSys( this, elem, unit, 1, status );
+         astSetSystem( sf1, fsys );
+         astSetUnit( sf1, 0, unit );
+
+/* If the supplied Frame did not have any set System, use the values from
+   the <Spectral> Frame. */
+         if( !astTestSystem( frm ) ) {
+            astSetSystem( frm, fsys );
+            astSetUnit( frm, 0, unit );
+         } else if( astGetSystem( frm ) == fsys && !astTestUnit( frm, 0 ) ) {
+            astSetUnit( frm, 0, unit );
+         }
+
+      } else {
+         sf1 = astClone( frm );
+      }
+
+/* Find the Mapping from Spectral value to the supplied SpecFrame value */
+      fs = astConvert( sf1, frm, "" );
+      if( fs ) {
+         map = astGetMapping( fs, AST__BASE, AST__CURRENT );
+         fs = astAnnul( fs );
+      } else {
+         map = NULL;
+         Report( this, elem, FAILURE, "connot convert AstroCoords "
+                 "spectral values to the required spectral system", status );
+      }
+
+/* If this Spectral contains a frequency Value which can be read, obtain
+   it. We will use the value to calculate the returned rest frequency. */
+      if( scan->count[ 2 ] > 0 ) {
+         name = astXmlGetName( scan->el[ 2 ][ 0 ] );
+         if( name && !strcmp( name, "Value" ) ) {
+            v = ElemValueD( this, scan->el[ 2 ][ 0 ], AST__BAD, status );
+
+/* Convert the value into the supplied SpecFrame system. Create an
+   Interval describing it and store it in the returned ancillary keyMap.
+   Use an Interval rather than a PointList since an Interval can be used
+   within a Prism to extrude another Region, but a PointList cannot. */
+            astTran1( map, 1, &v, 1, &tmp );
+            r = (AstRegion *) astInterval( frm, &tmp, &tmp, NULL, "", status ) ;
+            astMapPut0A( *anc, AST__STCVALUE, r, NULL );
+            r = astAnnul( r );
+
+/* We also want the rest frequency in Hz. Create a SpecFrame describing Hz. */
+            sf2 = astCopy( sf1 );
+            astSet( sf2, "system=freq,unit=Hz", status );
+
+/* Find the Mapping from the supplied value to frequency in Hz. Use it to
+   convert the rf value into Hz. */
+            fs = astConvert( sf1, sf2, "" );
+            if( fs ) {
+               astTran1( fs, 1, &v, 1, rf );
+               fs = astAnnul( fs );
+            } else if( astOK ) {
+               Report( this, elem, FAILURE, "Cannot convert spectral value"
+                         "to frequency in Hz.", status );
+            }
+            sf2 = astAnnul( sf2 );
+         }
+      }
+
+/* Check for Error values in the Spectral. */
+      if( scan->count[ 1 ] > 0 ) {
+
+/* Issue a warning if more than 1 Error value was found. */
+         if( scan->count[ 1 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Error>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first Error value. */
+         v = ElemValueD( this, scan->el[1][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of an error bar centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the Frame represented by the Spectral element.
+   Map it into the supplied Frame. Simplify it. Store in the returned
+   ancillary KeyMap. */
+            r = (AstRegion *) astInterval( sf1, lbnd, ubnd, NULL, "", status );
+            r2 = astMapRegion( r, map, frm );
+            result = astSimplify( r2 );
+            astMapPut0A( *anc, AST__STCERROR, result, NULL );
+            r2 = astAnnul( r2 );
+            r = astAnnul( r );
+         }
+      }
+
+/* Check for Resolution values in the Spectral. */
+      if( scan->count[ 3 ] > 0 ) {
+
+/* Issue a warning if more than 1 value was found. */
+         if( scan->count[ 3 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Resolution>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first value. */
+         v = ElemValueD( this, scan->el[3][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of an interval centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the Frame represented by the Spectral element.
+   Map it into the supplied Frame. Simplify it. Store in the returned
+   ancillary KeyMap. */
+            r = (AstRegion *) astInterval( sf1, lbnd, ubnd, NULL, "", status );
+            r2 = astMapRegion( r, map, frm );
+            r3 = astSimplify( r2 );
+            astMapPut0A( *anc, AST__STCRES, r3, NULL );
+            r3 = astAnnul( r3 );
+            r2 = astAnnul( r2 );
+            r = astAnnul( r );
+         }
+      }
+
+/* Check for Size values in the Spectral. */
+      if( scan->count[ 4 ] > 0 ) {
+
+/* Issue a warning if more than 1 value was found. */
+         if( scan->count[ 4 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Size>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first value. */
+         v = ElemValueD( this, scan->el[4][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of an interval centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the Frame represented by the Spectral element.
+   Map it into the supplied Frame. Simplify it. Store in the returned
+   ancillary KeyMap. */
+            r = (AstRegion *) astInterval( sf1, lbnd, ubnd, NULL, "", status );
+            r2 = astMapRegion( r, map, frm );
+            r3 = astSimplify( r2 );
+            astMapPut0A( *anc, AST__STCSIZE, r3, NULL );
+            r3 = astAnnul( r3 );
+            r2 = astAnnul( r2 );
+            r = astAnnul( r );
+         }
+      }
+
+/* Check for PixSize values in the Spectral. */
+      if( scan->count[ 5] > 0 ) {
+
+/* Issue a warning if more than 1 value was found. */
+         if( scan->count[ 5 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <PixSize>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first value. */
+         v = ElemValueD( this, scan->el[5][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of an interval centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the Frame represented by the Spectral element.
+   Map it into the supplied Frame. Simplify it. Store in the returned
+   ancillary KeyMap. */
+            r = (AstRegion *) astInterval( sf1, lbnd, ubnd, NULL, "", status );
+            r2 = astMapRegion( r, map, frm );
+            r3 = astSimplify( r2 );
+            astMapPut0A( *anc, AST__STCPIXSZ, r3, NULL );
+            r3 = astAnnul( r3 );
+            r2 = astAnnul( r2 );
+            r = astAnnul( r );
+         }
+      }
+
+/* Free resources. */
+      scan = FreeIVOAScan( scan, status );
+      sf1 = astAnnul( sf1 );
+      map = astAnnul( map );
+   }
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result */
+   return result;
+
+}
+
+static AstObject *SpectralFrameReader( AstXmlChan *this,
+                                       AstXmlElement *elem, int *status ) {
+/*
+*  Name:
+*     SpectralFrameReader
+
+*  Purpose:
+*     Make an AST Object from an IVOA SpectralFrame element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstObject *SpectralFrameReader( AstXmlChan *this, AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Object from the supplied IVOA
+*     SpectralFrame element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA SpectralFrame element.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object.
+
+*/
+
+/* Local Variables: */
+   AstSpecFrame *new;            /* Pointer to returned Object */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   const char *sor;              /* StdOfRest for returned Frame */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return (AstObject *) new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "TOPOCENTER|BARYCENTER|HELIOCENTER|GEOCENTER|LSR|"
+                "LSRK|GALACTIC_CENTER|LOCAL_GROUP|LSRD";
+   names[ 1 ] = "Name";
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   min[ 0 ] = 1;
+   min[ 1 ] = 0;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the name of the Element specifying the reference position and find
+   the corresponding AST name.*/
+      sor = astXmlGetName( scan->el[0][0] );
+      if( !strcmp( sor, "TOPOCENTER" ) ) {
+         sor = "Topo";
+      } else if( !strcmp( sor, "BARYCENTER" ) ){
+         sor = "Bary";
+      } else if( !strcmp( sor, "GEOCENTER" ) ){
+         sor = "Geo";
+      } else if( !strcmp( sor, "LSR" ) || !strcmp( sor, "LSRK" ) ) {
+         sor = "LSRK";
+      } else if( !strcmp( sor, "LSRD" ) ) {
+         sor = "LSRD";
+      } else if( !strcmp( sor, "GALACTIC_CENTER" ) ) {
+         sor = "Galactic";
+      } else if( !strcmp( sor, "LOCAL_GROUP" ) ) {
+         sor = "Local_group";
+      } else if( !strcmp( sor, "HELIOCENTER" ) ) {
+         sor = "Helio";
+      } else if( astOK ){
+         astError( AST__INTER, "SpectralFrameReader(XmlChan): Unknown "
+                   "standard of rest %s (internal AST programming error).", status,
+                   sor );
+      }
+
+/* Issue a warning if the reference position includes an ephemeris. */
+      if( FindElement( this, scan->el[0][0], "PlanetaryEphem", status ) ) {
+         Report( this, scan->el[0][0], WARNING, "contains a <PlanetaryEphem> "
+                 "element which will be ignored", status );
+      }
+
+/* Create a suitable SpecFrame. */
+      new = astSpecFrame( "StdOfRest=%s", status, sor);
+
+/* If the SpectralFrame has a <Name> element use it as the SpecFrame title. */
+      if( scan->count[1] ) astSetTitle( new, astXmlGetValue( scan->el[1][0], 0 ) );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Object. */
+   return (AstObject *) new;
+}
+
+static AstRegion *SpectralIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+                                          AstFrame *frm, int *status ){
+/*
+*  Name:
+*     SpectralIntervalReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA SpectralInterval element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *SpectralIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+*                                        AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     SpectralInterval element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA SpectralInterval element.
+*     frm
+*        Pointer to the Frame in which the returned Region should be
+*        defined. If the Unit or System attribute is not set, this
+*        function will decide on the values to be used, and set these
+*        values in the supplied Frame before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Frame used to define returned Region */
+   AstRegion *new;               /* Pointer to returned Region */
+   AstSystemType fsys;           /* System value from supplied Frame */
+   AstSystemType sys;            /* System value corresponding to "unit" */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *funit;            /* Unit string from supplied Frame */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   char *title;                  /* Title string */
+   const char *unit;             /* Unit string from supplied element */
+   double hilimit;               /* Upper spectral limit */
+   double lolimit;               /* Lower spectral limit */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "LoLimit";
+   names[ 1 ] = "HiLimit";
+   min[ 0 ] = 0;
+   min[ 1 ] = 0;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the limits. */
+      lolimit = scan->count[0] ? ElemValueD( this, scan->el[0][0], 0.0, status ) : AST__BAD;
+      hilimit = scan->count[1] ? ElemValueD( this, scan->el[1][0], 0.0, status ) : AST__BAD;
+
+/* Get the Unit attribute from the element. */
+      unit = astXmlGetAttributeValue( elem, "unit" );
+      if( !unit ) {
+         Report( this, elem, FAILURE, "contains no unit attribute", status );
+         unit = "";
+
+/* Find the spectral system corresponding to these units. */
+      } else {
+         sys = SpecSys( this, elem, unit, 1, status );
+
+/* Take a copy of the supplied Frame and set its System and Units to
+   these values. Ensure the title is preserved. */
+         cfrm = astCopy( frm );
+         if( astTestTitle( frm ) ) {
+            title = (char *) astGetTitle( frm );
+            if( title ) title = astStore( NULL, title, strlen( title ) + 1 );
+         } else {
+            title = NULL;
+         }
+         astSetSystem( cfrm, sys );
+         astSetUnit( cfrm, 0, unit );
+         if( title ) astSetTitle( cfrm, title );
+
+/* If at least one limit was found, create an Interval within this
+   modified Frame. Otherwise create a negated NullRegion. */
+         if( lolimit != AST__BAD || hilimit != AST__BAD ) {
+            new = (AstRegion *) astInterval( cfrm, &lolimit, &hilimit, NULL, "", status );
+         } else {
+            new = (AstRegion *) astNullRegion( cfrm, NULL, "negated=1", status );
+         }
+
+/* If the System of this Region differs from that of the supplied Frame,
+   set it to the System of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new System. If the supplied
+   Frame had no set system, set it to the system implied by th eunits in the
+   supplied XML element. */
+         if( astTestSystem( frm ) ) {
+            fsys = astGetSystem( frm );
+            if( fsys != sys ) astSetSystem( new, fsys );
+         } else {
+            astSetSystem( frm, sys );
+         }
+
+/* Do the same with the Units. */
+         if( astTestUnit( frm, 0 ) ) {
+            funit = astGetUnit( frm, 0 );
+            if( strcmp( funit, unit ) ) astSetUnit( new, 0, funit );
+         } else {
+            astSetUnit( frm, 0, unit );
+         }
+
+/* Ensure the original titleis preserved. */
+         if( title ) {
+            astSetTitle( new, title );
+            astSetTitle( frm, title );
+         }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+         FillAndLims( this, elem, new, status );
+
+/* Free resources */
+         cfrm = astAnnul( cfrm );
+         title = astFree( title );
+      }
+
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstRegion *SphereReader( AstXmlChan *this, AstXmlElement *elem,
+                                AstFrame *frm, int *status ){
+/*
+*  Name:
+*     SphereReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Sphere element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *SphereReader( AstXmlChan *this, AstXmlElement *elem,
+*                              AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Sphere element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Sphere element.
+*     frm
+*        Pointer to the Frame in which the returned Region should be
+*        defined. If the Unit or System attribute is not set, this
+*        function will decide on the values to be used, and set these
+*        values in the supplied Frame before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstFrame *cfrm;               /* Frame used to define returned Region */
+   AstMapping *map;              /* Mapping between units */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   char buff[200];               /* Message buffer */
+   const char *funit;            /* Unit string from supplied Frame */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   const char *runit;            /* Radius unit string from supplied element */
+   const char *unit;             /* Centre unit string from supplied element */
+   double cen[3];                /* Centre */
+   double rad;                   /* Radius */
+   double tmp;                   /* New radius value */
+   int i;                        /* Axis count */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Check the supplied Frame has the correct number of axes. */
+   if( astGetNaxes( frm ) != 3 && astOK ) {
+      astError( AST__INTER, "SphereReader(XmlChan): Supplied "
+                "Frame does not have 3 axes (internal AST programming error )." , status);
+   }
+
+/* Scan the supplied element for the required sub-elements */
+   names[ 0 ] = "Radius";
+   names[ 1 ] = "Center";
+   min[ 0 ] = 1;
+   min[ 1 ] = 1;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the radius. */
+      rad = ElemValueD( this, scan->el[0][0], 0.0, status );
+
+/* Get the centre. */
+      cen[0] = 0.0;
+      cen[1] = 0.0;
+      cen[2] = 0.0;
+      ElemListD( this, scan->el[1][0], 3, cen, status );
+
+/* Get the units attribute from the supplied element. This applies to the
+   values describing the centre position. */
+      unit = astXmlGetAttributeValue( elem, "unit" );
+      if( !unit ) {
+         Report( this, elem, FAILURE, "contains no unit attribute", status );
+         unit = "";
+      }
+
+/* Get the radius units attribute from the supplied element. */
+      runit = astXmlGetAttributeValue( elem, "radius_unit" );
+
+/* If necessary, convert the radius to the same units as the centre. */
+      if( runit && strcmp( unit, runit ) ) {
+         map = astUnitMapper( runit, unit, NULL, NULL );
+         if( map ) {
+            astTran1( map, 1, &rad, 1, &tmp );
+            rad = tmp;
+            map = astAnnul( map );
+
+         } else if( astOK ) {
+            sprintf( buff, "has inconsistent units attributes \"%s\" and "
+                     "\"%s\"", unit, runit );
+            Report( this, elem, FAILURE, buff, status );
+         }
+      }
+
+/* Take a copy of the supplied Frame and set its Units to the value
+   obtained from the supplied element. */
+      cfrm = astCopy( frm );
+      astSetUnit( cfrm, 0, unit );
+      astSetUnit( cfrm, 1, unit );
+      astSetUnit( cfrm, 2, unit );
+
+/* Create a Circle within this modified Frame. */
+      new = (AstRegion *) astCircle( cfrm, 1, cen, &rad, NULL, "", status );
+
+/* If the Unit of this Region differs from that of the supplied Frame,
+   set it to the Unit of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new Unit. If the supplied
+   Frame had no set Unit, set it to the units obtained from the supplied
+   element. */
+      for( i = 0; i < 3; i++ ) {
+         if( astTestUnit( frm, i ) ) {
+            funit = astGetUnit( frm, i );
+            if( strcmp( funit, unit ) ) astSetUnit( new, i, funit );
+         } else {
+            astSetUnit( frm, i, unit );
+         }
+      }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      cfrm = astAnnul( cfrm );
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstObject *StcMetadataReader( AstXmlChan *this,
+                                     AstXmlElement *elem, int *status ) {
+/*
+*  Name:
+*     StcMetadataReader
+
+*  Purpose:
+*     Make an AST Object from an IVOA STCMetadata element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstObject *StcMetadataReader( AstXmlChan *this,
+*                                   AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Object from the supplied IVOA
+*     STCMetadata element. The STCMetadata object can be of any subclass
+*     (e.g. STCResourceProfile, SearchLocation, CatalogEntryLocation,
+*     ObservationLocation, ObservatoryLocation).
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA STCMetadata element.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object.
+*/
+
+/* Local Variables: */
+   AstFrame *frm;                /* Frame representing the STC object */
+   AstKeyMap *anc;               /* Map holding AstroCoords ancillary data */
+   AstKeyMap **ancs;             /* List of KeyMaps holding ancillary data */
+   AstKeyMap *map1;              /* Map holding AstroCoordSystem elements */
+   AstKeyMap *map2;              /* Map holding AstroCoordArea elements */
+   AstKeyMap *map3;              /* Map holding CoordSpec elements */
+   AstKeyMap *map;               /* Map to use */
+   AstRegion *region;            /* Region representing the STC object */
+   AstRegion *tuncs[ 4 ];        /* Temporary uncertainty Regions */
+   AstRegion *uncs[ 4 ];         /* Uncertainty Regions for returned STC */
+   AstStc *stc;                  /* Pointer to returned Object (an Stc) */
+   AstXmlContentItem *item;      /* Pointer to content item */
+   AstXmlElement *aca;           /* Pointer to AstroCoordArea element to use */
+   AstXmlElement *aco;           /* Pointer to AstroCoords element to use */
+   AstXmlElement *acs;           /* Pointer to AstroCoordSystem element to use */
+   char *text;                   /* Formatted item text */
+   char buff[ 200 ];             /* Message buffer */
+   const char *id;               /* Value of ID attribute */
+   const char *ido;              /* Value of ID attribute */
+   const char *name;             /* Element name */
+   const char *stc_class;        /* STC subclass name */
+   int gotunc;                   /* Have any uncertainty Regions been obtained? */
+   int i;                        /* Index of content item within element */
+   int j;                        /* Index into list of map keys */
+   int narea;                    /* Number of AstroCoordArea elements found */
+   int ncoord;                   /* Number of CoordSpec elements found */
+   int nanc;                     /* No.of KeyMaps in "ancs" array */
+   int nitem;                    /* No. of items of content in element */
+   int nsys;                     /* Number of AstroCoordSystem elements found */
+   int reported;                 /* Have multiple uncertainies been reported? */
+   int used;                     /* Was the content item used? */
+
+/* Initialise. */
+   stc = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return (AstObject *) stc;
+
+/* Avoid compiler warnings. */
+   id = "";
+
+/* Get name of the the STCMetadata subclass represented by the supplied
+   element. */
+   stc_class = astXmlGetName( elem );
+
+/* Create KeyMaps to hold the required sub-elements. We will store the
+   integer indices of the requried elements in these keymaps, using the
+   associated Xml ID attribute values as the keys. */
+   map1 = astKeyMap( "", status );
+   map2 = astKeyMap( "", status );
+   map3 = astKeyMap( "", status );
+
+/* Loop round all items in the elements contents. */
+   nitem = astXmlGetNitem( elem );
+   for( i = 0; i < nitem; i++ ) {
+      item = astXmlGetItem( elem, i );
+      used = 1;
+
+/* Ignore this item if it is not an element. */
+      if( astXmlCheckType( item, AST__XMLELEM ) ) {
+
+/* Choose the KeyMap in which to save this item. */
+         name = astXmlGetName( item );
+         if( !strcmp( name, ASTRO_COORD_SYSTEM ) ){
+            map = map1;
+
+         } else if( !strcmp( name, ASTRO_COORD_AREA ) ){
+            map = map2;
+
+         } else if( !strcmp( name, ASTRO_COORDS ) ){
+            map = map3;
+
+         } else {
+            map = NULL;
+            used = 0;
+         }
+
+/* If we are going to save the item, get the value of the ID attribute
+   and check it. */
+         if( map ) {
+            id = astXmlGetAttributeValue( (AstXmlElement *) item, "ID" );
+            if( !id ) {
+               id = "";
+               if( map != map3 ) {
+                  Report( this, elem, WARNING, "has no ID attribute. Assuming"
+                          "a null ID value", status );
+               }
+            }
+
+/* If the KeyMap already contains an object with this ID, issue a
+   warning and skip the element. */
+            if( astMapHasKey( map, id ) ) {
+               if( map != map3 ) {
+                  sprintf( buff, "contains two or more %s elements with the "
+                           "same ID (\"%s\"). Only the first one will be used",
+                           name, id );
+                  Report( this, elem, WARNING, buff, status );
+               } else {
+                  Report( this, elem, WARNING, "contains two or more AstroCoords "
+                          "elements. Only the first one will be used", status );
+               }
+
+/* Otherwise, save the index of the item in the KeyMap, using the ID as the
+   key. */
+            } else {
+               astMapPut0I( map, id, i, "" );
+            }
+         }
+
+      } else {
+         used = 0;
+      }
+
+/* If this content item was not used, issue a warning unless it is a comment
+   or white space. */
+      if( !used && !astXmlCheckType( item, AST__XMLCOM ) &&
+                   !astXmlCheckType( item, AST__XMLWHITE ) ) {
+         text = (char *) astXmlFormat( item );
+         if( strlen( text ) > 30 ) text[ 30 ] = 0;
+         sprintf( buff, "contains the following which is being ignored: \"%s\"",
+                  text );
+         text = astFree( text );
+         Report( this, elem, WARNING, buff, status );
+      }
+
+   }
+
+/* Note the number of each type of element found with unique ID values. */
+   nsys = astMapSize( map1 );
+   narea = astMapSize( map2 );
+   ncoord = astMapSize( map3 );
+
+/* If any CoordArea elements were found, find the first one for which the
+   coordesponding AstroCoordSystem is available. */
+   acs = NULL;
+   aca = NULL;
+   for( j = 0; j < narea; j++ ) {
+
+/* Get the j'th key from "map2" (the ID associated with the j'th
+   AstroCoordArea found in the supplied element) and retrieve the value
+   associated with this key (the index "i" into the content of the
+   supplied element at which the j'th AstroCoordArea is stored). */
+      astMapGet0I( map2, astMapKey( map2, j ), &i );
+
+/* Get the i'th element in the supplied element. This will be the j'th
+   AstroCoordArea. */
+      aca = (AstXmlElement *) astXmlGetItem( elem, i );
+
+/* Get the "coord_system_id" attribute from this AstroCoordArea. Use null
+   if not available. */
+      id = astXmlGetAttributeValue( aca, "coord_system_id" );
+      if( !id ) {
+         id = "";
+         Report( this, aca, WARNING, "has no coord_system_id attribute. "
+                 "Assuming a null coord_system_id value", status );
+      }
+
+/* Get the index within the supplied element of the AstroCoordSystem with this
+   ID. Jump forward if no AstroCoordSystem with this id is available. */
+      if( astMapGet0I( map1, id, &i ) ) {
+
+/* Get a pointer to the AstroCoordSystem element with the required ID. */
+         acs = (AstXmlElement *) astXmlGetItem( elem, i );
+
+/* Leave the AstroCoordArea loop. */
+         break;
+
+/* Report a warning if no AstroCoordSystem with this id is available. */
+      } else {
+         sprintf( buff, "refers to an AstroCoordSystem with "
+                 "ID \"%s\", but no such AstroCoordSystem is available "
+                 "within the parent %s", id, stc_class );
+         Report( this, aca, WARNING, buff, status );
+      }
+   }
+
+/* If we did not find a corresponding pair of AstroCoordSystem and
+   AstroCoordArea, we either report a failure (if there were any
+   AstroCoordAreas), or get a pointer the AstroCoordSystem referred to by
+   the first AstroCoords element (we will create a Frame from this later). */
+   if( !acs ) {
+      aca = NULL;
+
+/* Report a warning if there were some AstroCoordArea tags but no matching
+   AstroCoordSystem was found. */
+      if( narea > 0 ) {
+         Report( this, elem, WARNING, "does not contain a pair of "
+                 "matching AstroCoordArea and AstroCoordSystem tags", status );
+
+/* If there are no AstroCoordAreas in the supplied element, look for a
+   pair of matching AstroCoords and AstroCoordSystem. The returned Region
+   will represent a NullRegion within this system. */
+      } else if( ncoord > 0 ) {
+
+/* Get the 1st key from "map3" (the ID associated with the 1st
+   AstroCoords found in the supplied element) and retrieve the value
+   associated with this key (the index "i" into the content of the
+   supplied element at which the 1st AstroCoords is stored). */
+         astMapGet0I( map3, astMapKey( map3, 0 ), &i );
+
+/* Get the i'th element in the supplied element. This will be the 1st
+   AstroCoord. */
+         aco = (AstXmlElement *) astXmlGetItem( elem, i );
+
+/* Get the "coord_system_id" attribute from this AstroCoords. Use null
+   if not available. */
+         id = astXmlGetAttributeValue( aco, "coord_system_id" );
+         if( !id ) {
+            id = "";
+            Report( this, aco, WARNING, "has no coord_system_id attribute. "
+                    "Assuming a null coord_system_id value", status );
+         }
+
+/* Get the index within the supplied element of the AstroCoordSystem with this
+   ID. Jump forward if no AstroCoordSystem with this id is available. */
+         if( astMapGet0I( map1, id, &i ) ) {
+
+/* Get a pointer to the AstroCoordSystem element with the required ID. */
+            acs = (AstXmlElement *) astXmlGetItem( elem, i );
+
+         } else {
+            Report( this, aco, FAILURE, "no corresponding AstroCoordSystem found", status );
+         }
+
+/* If there are no AstroCoords in the supplied element we create a
+   NullRegion within the first supplied AstroCoordSystem. */
+      } else if( nsys > 0 ) {
+         if( astMapGet0I( map1, astMapKey( map1, 0 ), &i ) ) {
+            acs = (AstXmlElement *) astXmlGetItem( elem, i );
+         }
+
+      } else {
+         Report( this, elem, FAILURE, "no usable content found", status );
+      }
+   }
+
+/* Report failure if we still have no AstroCoordSystem. */
+   if( !acs ) {
+      Report( this, elem, FAILURE, "does not contain a usable AstroCoordSystem", status );
+
+/* Issue a warning if more than one AstroCoordArea was found. */
+   } else {
+      if( narea > 1 ) Report( this, elem, WARNING, "contains more than one "
+                             "AstroCoordArea. Only one will be used", status );
+
+/* Create a Frame from the ASTRO_COORD_SYSTEM. */
+      frm = (AstFrame *) AstroCoordSystemReader( this, acs, status );
+
+/* Loop round all AstroCoords elements in the supplied element. */
+      gotunc = 0;
+      reported = 0;
+      uncs[ 0 ] = NULL;
+      uncs[ 1 ] = NULL;
+      uncs[ 2 ] = NULL;
+      uncs[ 3 ] = NULL;
+      nanc = 0;
+      ancs = NULL;
+      for( j = 0; j < ncoord; j++ ) {
+
+/* Get the j'th key from "map3" (the ID associated with the j'th
+   AstroCoords found in the supplied element) and retrieve the value
+   associated with this key (the index "i" into the content of the
+   supplied element at which the j'th AstroCoords is stored). */
+         astMapGet0I( map3, astMapKey( map3, j ), &i );
+
+/* Get the i'th element in the supplied element. This will be the j'th
+   AstroCoords. */
+         aco = (AstXmlElement *) astXmlGetItem( elem, i );
+
+/* Get the "coord_system_id" attribute from this AstroCoords and compare it
+   with the ID of the AstrocCoordSys being used. If they match, incorporate
+   the effects of the AstroCoords into the "frm" Frame and get a set of 4
+   Regions representing the uncertainty within each of the 4 STC domains
+   (space, time, spectral, redshift). */
+         ido = astXmlGetAttributeValue( aco, "coord_system_id" );
+         if( ido && !strcmp( id, ido ) ) {
+            if( AstroCoordsReader( this, aco, frm, tuncs, &anc, status ) ) {
+               if( !gotunc ) {
+                  uncs[ 0 ] = tuncs[ 0 ];
+                  uncs[ 1 ] = tuncs[ 1 ];
+                  uncs[ 2 ] = tuncs[ 2 ];
+                  uncs[ 3 ] = tuncs[ 3 ];
+                  gotunc = 1;
+               } else {
+                  if( tuncs[ 0 ] ) tuncs[ 0 ] = astAnnul( tuncs[ 0 ] );
+                  if( tuncs[ 1 ] ) tuncs[ 1 ] = astAnnul( tuncs[ 1 ] );
+                  if( tuncs[ 2 ] ) tuncs[ 2 ] = astAnnul( tuncs[ 2 ] );
+                  if( tuncs[ 3 ] ) tuncs[ 3 ] = astAnnul( tuncs[ 3 ] );
+                  if( !reported ) {
+                     Report( this, elem, WARNING, "contains more than one "
+                             "specification of the coordinate uncertainties. "
+                             "Only the first will be used", status );
+                     reported= 1;
+                  }
+               }
+            }
+
+/* If any ancillary information was read from the AstroCoords, add it to
+   the list of ancillary information to be stored in the Stc structure. */
+            if( anc ) {
+               ancs = astGrow( ancs, nanc + 1, sizeof( AstKeyMap * ) );
+               if( ancs ) ancs[ nanc++ ] = anc;
+            }
+         }
+      }
+
+/* Now create a Region from this Frame and the ASTRO_COORD_AREA. Note,
+   "aca" may be NULL in which case the returned Region will be NullRegion. */
+      region = AstroCoordAreaReader( this, aca, frm, uncs, nanc, ancs, status );
+
+/* Re-centre the Regions describing ancillary information extracted from
+   the AstroCoords elements. */
+      ReCentreAnc( region, nanc, ancs, status );
+
+/* Now create a Stc object of the appropriate sub-class. */
+      if( !strcmp( stc_class, STC_RESOURCE_PROFILE ) ) {
+         stc = (AstStc *) astStcResourceProfile( region, nanc, ancs, "", status );
+
+      } else if( !strcmp( stc_class, SEARCH_LOCATION ) ) {
+         stc = (AstStc *) astStcSearchLocation( region, nanc, ancs, "", status );
+
+      } else if( !strcmp( stc_class, CATALOG_ENTRY_LOCATION ) ) {
+         stc = (AstStc *) astStcCatalogEntryLocation( region, nanc, ancs, "", status );
+
+      } else if( !strcmp( stc_class, OBSERVATION_LOCATION ) ||
+                 !strcmp( stc_class, OBSERVATORY_LOCATION ) ) {
+         stc = (AstStc *) astStcObsDataLocation( region, nanc, ancs, "", status );
+
+      } else if( astOK ){
+         astError( AST__INTER, "astRead(XmlChan): StcMetadataReader knows "
+                   "nothing about the %s class (internal AST programming "
+                   "error).", status, stc_class );
+      }
+
+/* Get the ID attribute from the supplied element and store in the
+   returned Object. */
+      id = astXmlGetAttributeValue( elem, "ID" );
+      if( id ) astSetIdent( stc, id );
+
+/* Free resources. */
+      if( uncs[ 0 ] ) uncs[ 0 ] = astAnnul( uncs[ 0 ] );
+      if( uncs[ 1 ] ) uncs[ 1 ] = astAnnul( uncs[ 1 ] );
+      if( uncs[ 2 ] ) uncs[ 2 ] = astAnnul( uncs[ 2 ] );
+      if( uncs[ 3 ] ) uncs[ 3 ] = astAnnul( uncs[ 3 ] );
+      frm = astAnnul( frm );
+      region = astAnnul( region );
+      if( ancs ) {
+         for( i = 0; i < nanc; i++ ) ancs[ i ] = astAnnul( ancs[ i ] );
+         ancs = astFree( ancs );
+      }
+   }
+
+   map1 = astAnnul( map1 );
+   map2 = astAnnul( map2 );
+   map3 = astAnnul( map3 );
+
+/* Return the pointer to the new Object. */
+   return (AstObject *) stc;
+}
+
+static AstRegion *StcRegionReader( AstXmlChan *this, AstXmlElement *elem,
+                                AstFrame *frm, int *status ){
+/*
+*  Name:
+*     StcRegionReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Region element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *StcRegionReader( AstXmlChan *this, AstXmlElement *elem,
+*                              AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Region element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Region element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[1];         /* Names of the subelements to be searched for */
+   int max[1];                   /* Max allowed occurrences of each name */
+   int min[1];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for a Region sub-element. */
+   names[ 0 ] = "Intersection|Union|Negation|AllSky|Circle|Ellipse|Polygon|"
+                "Convex|Box";
+   min[ 0 ] = 1;
+   max[ 0 ] = 1;
+   scan = ScanIVOAElement( this, elem, 1, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Create the Region */
+      new = RegionReader( this, scan->el[0][0], frm, status );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a XmlChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the astTestAttrib protected
+*     method inherited from the Channel class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a XmlChan's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     attrib
+*        Pointer to a null terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* Pointer to the XmlChan structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* XmlLength */
+/* --------- */
+   if ( !strcmp( attrib, "xmllength" ) ) {
+      result = astTestXmlLength( this );
+
+/* XmlFormat */
+/* --------- */
+   } else if ( !strcmp( attrib, "xmlformat" ) ) {
+      result = astTestXmlFormat( this );
+
+/* XmlPrefix */
+/* --------- */
+   } else if ( !strcmp( attrib, "xmlprefix" ) ) {
+      result = astTestXmlPrefix( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstObject *TimeFrameReader( AstXmlChan *this,
+                                   AstXmlElement *elem, int *status ) {
+/*
+*  Name:
+*     TimeFrameReader
+
+*  Purpose:
+*     Make an AST Object from an IVOA TimeFrame element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstObject *TimeFrameReader( AstXmlChan *this, AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Object from the supplied IVOA
+*     TimeFrame element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA TimeFrame element.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Object.
+
+*/
+
+/* Local Variables: */
+   AstTimeFrame *new;         /* Pointer to returned Frame */
+   IVOAScan *scan;            /* Structure holding scan results */
+   const char *names[3];      /* Names of the subelements to be searched for */
+   const char *text;          /* Pointer to Name value */
+   int max[3];                /* Max allowed occurrences of each name */
+   int min[3];                /* Min allowed occurrences of each name */
+
+/* Initialise */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return (AstObject *) new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "Name";
+   names[ 1 ] = "TOPOCENTER";
+   names[ 2 ] = "TimeScale|Timescale";
+   min[ 0 ] = 0;
+   max[ 0 ] = 1;
+   min[ 1 ] = 0;
+   max[ 1 ] = 1;
+   min[ 2 ] = 1;
+   max[ 2 ] = 1;
+   scan = ScanIVOAElement( this, elem, 3, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Create a suitable TimeFrame. Set the timescale, but leave the other
+   attributes unset since they will not be known until an AstronTimeType
+   is read. Except for unit. We set unit to "d" (day) because all the
+   time form,ats supported by STC have "d" as the default unit. This
+   avoids bad publicity which arises from presentin (say) MJD values in
+   units of "s" - which people will think is wrong until they have it
+   explained. */
+      new = astTimeFrame( "unit=d", status );
+      astSetTimeScale( new, TimeScaleReader( this, scan->el[ 2 ][ 0 ], status ) );
+
+/* If the STC TimeFrame has a <Name> element use it as the AST TimeFrame title. */
+      if( scan->count[ 0 ] > 0 ) {
+         text = astXmlGetValue( scan->el[ 0 ][ 0 ], 0 );
+         if( text ) astSetTitle( new, text );
+      }
+
+/* Free resources. */
+      scan = FreeIVOAScan( scan, status );
+
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new= astAnnul( new );
+
+/* Return the pointer to the new Object. */
+   return (AstObject *) new;
+}
+
+static AstRegion *TimeIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+                                      AstTimeFrame *frm, int *status ){
+/*
+*  Name:
+*     TimeIntervalReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA TimeInterval element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *TimeIntervalReader( AstXmlChan *this, AstXmlElement *elem,
+*                                    AstTimeFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     TimeInterval element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA TimeInterval element.
+*     frm
+*        Pointer to the TimeFrame in which the returned Region should be
+*        defined.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstRegion *new;               /* Pointer to returned Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   double start;                 /* Start time */
+   double stop;                  /* Stop time */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "StartTime";
+   names[ 1 ] = "StopTime";
+   min[ 0 ] = 0;
+   min[ 1 ] = 0;
+   max[ 0 ] = 1;
+   max[ 1 ] = 1;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the limits. */
+      start = scan->count[0] ? AstronTimeReader( this, scan->el[0][0], frm, status ) : AST__BAD;
+      stop = scan->count[1] ? AstronTimeReader( this, scan->el[1][0], frm, status ) : AST__BAD;
+
+/* If at least one limit was found, create an Interval. Otherwise create
+   a negated NullRegion. */
+      if( start != AST__BAD || stop != AST__BAD ) {
+
+/* Use the stop or start time (converted to an MJD) as the Epoch within the
+   Frame. */
+         if( start != AST__BAD ) {
+            astSetEpoch( frm, MakeMJD( frm, start, status ) );
+         } else if( stop != AST__BAD ) {
+            astSetEpoch( frm, MakeMJD( frm, stop, status ) );
+         }
+         new = (AstRegion *) astInterval( frm, &start, &stop, NULL, "", status );
+      } else {
+         new = (AstRegion *) astNullRegion( frm, NULL, "negated=1", status );
+      }
+
+/* Get any fill factor and lo/hi_include attributes from the element and
+   assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources. */
+      scan = FreeIVOAScan( scan, status );
+
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static AstRegion *TimeReader( AstXmlChan *this, AstXmlElement *elem,
+                              AstTimeFrame *frm, double *epoch,
+                              AstKeyMap **anc, int *status ){
+/*
+*  Name:
+*     TimeReader
+
+*  Purpose:
+*     Modify a Frame to take account of an STC <Time> element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *TimeReader( AstXmlChan *this, AstXmlElement *elem,
+*                            AstTimeFrame *frm, double *epoch,
+*                            AstKeyMap **anc, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function reads the supplied STC <Time> element, and uses it,
+*     if possible, to create the uncertainty associated with the time
+*     axis in the supplied Frame.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Time element.
+*     frm
+*        Pointer to the TimeFrame.
+*     epoch
+*        Pointer to double in which to return the epoch to be used
+*        with other axes. Value is returned as an Modified Julian Date
+*        in the barycentric dynamical timescale (TDB). AST__BAD will
+*        be returned if the supplied Time element has no value.
+*     anc
+*        Address of a location at which to put a pointer to a newly
+*        created KeyMap. This KeyMap will contain ancillary information
+*        from the Time. The keys identify the item of ancillary
+*        information (Name, Value, Error, Resolution, Size, Pixel Size).
+*        The value associated with the Name key is string containing
+*        the Name item from the Time. The value
+*        associated with each of the other keys is a pointer to a 1D Region
+*        within the supplied Frame, corresponding to the value, error,
+*        resolution, etc. Keys will not be present in the returned KeyMap
+*        if the corresponding item of ancillary information is not present
+*        in the Time. A NULL pointer is returned if there is no
+*        ancillary information at all.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned:
+*     The uncertainty Region, or NULL if the supplied Time element
+*     does not specify an uncertainty.
+
+*/
+
+/* Local Variables: */
+   AstTimeFrame *cfrm;      /* Pointer to copy of time axis */
+   AstRegion *result;       /* Returned uncertainty Region */
+   AstRegion *r;            /* Ancillary Region */
+   IVOAScan *scan;          /* Structure holding scan results */
+   const char *funit;       /* Pointer to Frame's unit attribute string */
+   const char *names[6];    /* Names of the subelements to be searched for */
+   const char *title;       /* Pointer to Frame title string */
+   const char *unit;        /* Pointer to Time's unit attribute string */
+   double lbnd[ 1 ] ;       /* Lower interval bounds */
+   double ubnd[ 1 ] ;       /* Upper interval bounds */
+   double value;            /* Time value */
+   double v;                /* Ancillary value */
+   int max[6];              /* Max allowed occurrences of each name */
+   int min[6];              /* Min allowed occurrences of each name */
+
+/* Initialise */
+   result = NULL;
+   *epoch = AST__BAD;
+   *anc = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "Name";
+   names[ 1 ] = "Error";
+   names[ 2 ] = "TimeInstant";
+   names[ 3 ] = "Resolution";
+   names[ 4 ] = "Size";
+   names[ 5 ] = "PixSize";
+   max[ 0 ] = 1;
+   max[ 1 ] = 2;
+   max[ 2 ] = 1;
+   max[ 3 ] = 2;
+   max[ 4 ] = 2;
+   max[ 5 ] = 2;
+   min[ 0 ] = 1;
+   min[ 1 ] = 0;
+   min[ 2 ] = 0;
+   min[ 3 ] = 0;
+   min[ 4 ] = 0;
+   min[ 5 ] = 0;
+   scan = ScanIVOAElement( this, elem, 6, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Create a KeyMap to return holding ancilary info, and put the Name into
+   it. */
+      *anc = astKeyMap( "", status );
+      if( scan->count[0] > 0 ) astMapPut0C( *anc, AST__STCNAME,
+                                  astXmlGetValue( scan->el[0][0], 0 ), NULL );
+
+/* Get any Unit attribute from the Time element. */
+      unit = astXmlGetAttributeValue( elem, "unit" );
+
+/* We need to ensure the returned regions are mapped into units of "funit".
+   If this is NULL it means that the returned regions are already in the
+   required units. */
+      funit = NULL;
+
+/* If the Time element has a unit attribute, we use it in preference to any
+   units values in the supplied Frame. Take a copy of the time Frame and set
+   its Units to this values. Ensure the title is preserved. */
+      if( unit && astChrLen( unit ) ) {
+         cfrm = astCopy( frm );
+         if( astTestTitle( frm ) ) {
+            title = (char *) astGetTitle( frm );
+            if( title ) title = astStore( NULL, title, strlen( title ) + 1 );
+         } else {
+            title = NULL;
+         }
+         astSetUnit( cfrm, 0, unit );
+         if( title ) astSetTitle( cfrm, title );
+
+         if( astTestUnit( frm, 0 ) ) {
+            funit = astGetUnit( frm, 0 );
+            if( !strcmp( funit, unit ) ) {
+               funit = NULL;
+            } else {
+               funit = astStore( NULL, funit, strlen( funit ) + 1 );
+            }
+         } else {
+            astSetUnit( frm, 0, unit );
+         }
+
+      } else {
+         cfrm = astClone( frm );
+         title = NULL;
+      }
+
+/* If this Time contains a Value which can be read, obtain it. Otherwise,
+   issue a warning. We will use the value to calculate the returned epoch. */
+      if( scan->count[ 2 ] > 0 ) {
+         value = AstronTimeReader( this, scan->el[ 2 ][ 0 ], cfrm, status );
+         *epoch = MakeMJD( cfrm, value, status );
+
+/* Ensure any relevant attribute values which were set by AstronTimeReader
+   within "cfrm" are transferred to "frm". */
+         if( astTestTimeScale( cfrm ) ) astSetTimeScale( frm, astGetTimeScale( cfrm ) );
+         if( astTestSystem( cfrm ) ) astSetSystem( frm, astGetSystem( cfrm ) );
+         if( astTestUnit( cfrm, 0 ) ) astSetUnit( frm, 0, astGetUnit( cfrm, 0 ) );
+         if( astTestTimeOrigin( cfrm ) ) astSetTimeOrigin( frm, astGetTimeOrigin( cfrm ) );
+
+/* Create a Interval from it and store in the returned ancillary KeyMap. If
+   the units of this Frame differs from that of the supplied Frame, set it
+   to the units of the supplied Frame. This will cause the encapsulated
+   limits to be mapped into the new units. Ensure the original title is
+   preserved. Use an Interval rather than a PointList since an Interval
+   can be used within a Prism to extrude another Region, but a PointList
+   cannot. */
+         r = (AstRegion *) astInterval( cfrm, &value, &value, NULL, "", status ) ;
+         if( funit ) astSetUnit( r, 0, funit );
+         if( title ) astSetTitle( r, title );
+         astMapPut0A( *anc, AST__STCVALUE, r, NULL );
+         r = astAnnul( r );
+      }
+
+/* Does this Time contain any Error? */
+      if( scan->count[ 1 ] > 0 ) {
+
+/* Issue a warning if more than 1 Error value was found. */
+         if( scan->count[ 1 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Error>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first Error value. */
+         v = ElemValueD( this, scan->el[1][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of an error bar centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the time Frame. */
+            result = (AstRegion *) astInterval( cfrm, lbnd, ubnd, NULL, "", status );
+
+/* If the units of this Frame differs from that of the supplied Frame,
+   set it to the units of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new units. */
+            if( funit ) astSetUnit( result, 0, funit );
+
+/* Ensure the original title is preserved. */
+            if( title ) astSetTitle( result, title );
+
+/* Store in the returned ancillary KeyMap. */
+            astMapPut0A( *anc, AST__STCERROR, result, NULL );
+
+         }
+      }
+
+/* Does this Time contain any Resolution? */
+      if( scan->count[ 3 ] > 0 ) {
+
+/* Issue a warning if more than 1 Resolution value was found. */
+         if( scan->count[ 3 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Resolution>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first Resolution value. */
+         v = ElemValueD( this, scan->el[3][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of a bar centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the time Frame. */
+            r = (AstRegion *) astInterval( cfrm, lbnd, ubnd, NULL, "", status );
+
+/* If the units of this Frame differs from that of the supplied Frame,
+   set it to the units of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new units. */
+            if( funit ) astSetUnit( r, 0, funit );
+
+/* Ensure the original title is preserved. */
+            if( title ) astSetTitle( r, title );
+
+/* Store in the returned ancillary KeyMap. */
+            astMapPut0A( *anc, AST__STCRES, r, NULL );
+            r = astAnnul( r );
+         }
+      }
+
+/* Does this Time contain any Size? */
+      if( scan->count[ 4 ] > 0 ) {
+
+/* Issue a warning if more than 1 Size value was found. */
+         if( scan->count[ 4 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <Size>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first Size value. */
+         v = ElemValueD( this, scan->el[4][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of a bar centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the time Frame. */
+            r = (AstRegion *) astInterval( cfrm, lbnd, ubnd, NULL, "", status );
+
+/* If the units of this Frame differs from that of the supplied Frame,
+   set it to the units of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new units. */
+            if( funit ) astSetUnit( r, 0, funit );
+
+/* Ensure the original title is preserved. */
+            if( title ) astSetTitle( r, title );
+
+/* Store in the returned ancillary KeyMap. */
+            astMapPut0A( *anc, AST__STCSIZE, r, NULL );
+            r = astAnnul( r );
+         }
+      }
+
+/* Does this Time contain any PixSize? */
+      if( scan->count[ 5 ] > 0 ) {
+
+/* Issue a warning if more than 1 PixSize value was found. */
+         if( scan->count[ 5 ] > 1 ) {
+            Report( this, elem, WARNING, "contains more than one <PixSize>"
+                    " element. AST can only use the first", status );
+         }
+
+/* Get the first PixSize value. */
+         v = ElemValueD( this, scan->el[5][0], AST__BAD, status );
+         if( v != AST__BAD ) {
+
+/* Create the upper and lower limits of a bar centred on zero. */
+            ubnd[ 0 ] = 0.5*fabs( v );
+            lbnd[ 0 ] = -ubnd[ 0 ];
+
+/* Create an Interval within the time Frame. */
+            r = (AstRegion *) astInterval( cfrm, lbnd, ubnd, NULL, "", status );
+
+/* If the units of this Frame differs from that of the supplied Frame,
+   set it to the units of the supplied Frame. This will cause the
+   encapsulated limits to be mapped into the new units. */
+            if( funit ) astSetUnit( r, 0, funit );
+
+/* Ensure the original title is preserved. */
+            if( title ) astSetTitle( r, title );
+
+/* Store in the returned ancillary KeyMap. */
+            astMapPut0A( *anc, AST__STCPIXSZ, r, NULL );
+            r = astAnnul( r );
+         }
+      }
+
+/* Free resources */
+      if( funit ) funit = astFree( (void *) funit );
+      cfrm = astAnnul( cfrm );
+      if( title ) title = astFree( (void *) title );
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Return NULL if an error occurred. */
+   if( !astOK ) result = astAnnul( result );
+
+/* Return the result */
+   return result;
+
+}
+
+static AstTimeScaleType TimeScaleReader( AstXmlChan *this, AstXmlElement *elem, int *status ){
+/*
+*  Name:
+*     TimeScaleReader
+
+*  Purpose:
+*     Read a time value from an IVOA TimeScale element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstTimeScaleType TimeScaleReader( AstXmlChan *this, AstXmlElement *elem, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function returns a value representing the timescale specified by
+*     the supplied IVOA TimeScale element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA TimeScale element.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The timescale value (values are defined in timeframe.h).
+
+*/
+
+/* Local Variables: */
+   AstTimeScaleType result;
+   char buff[ 80 ];
+   const char *tstxt;
+
+/* Initialise */
+   result = AST__BADTS;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get the timescale string from the element, and find the corresponding
+   AST timescale value (if any). */
+   tstxt = astXmlGetValue( elem, 0 );
+   if( tstxt ) {
+
+      if( !strcmp( tstxt, "TT" ) ) {
+         result = AST__TT;
+
+      } else if( !strcmp( tstxt, "TDT" ) ) {
+         result = AST__TT;
+
+      } else if( !strcmp( tstxt, "ET" ) ) {
+         Report( this, elem, WARNING, "TT will be used in place of ET", status );
+         result = AST__TT;
+
+      } else if( !strcmp( tstxt, "TDB" ) ) {
+         result = AST__TDB;
+
+      } else if( !strcmp( tstxt, "TCG" ) ) {
+         result = AST__TCG;
+
+      } else if( !strcmp( tstxt, "TCB" ) ) {
+         result = AST__TCB;
+
+      } else if( !strcmp( tstxt, "TAI" ) ) {
+         result = AST__TAI;
+
+      } else if( !strcmp( tstxt, "IAT" ) ) {
+         result = AST__TAI;
+
+      } else if( !strcmp( tstxt, "UTC" ) ) {
+         result = AST__UTC;
+
+      } else if( !strcmp( tstxt, "LST" ) ) {
+         result = AST__LMST;
+
+      } else {
+         sprintf( buff, "contains unsupported timescale %s", tstxt );
+         Report( this, elem, FAILURE, buff, status );
+         result = AST__BADTS;
+      }
+   }
+
+/* Return the time value. */
+   return result;
+}
+
+static AstRegion *UnionReader( AstXmlChan *this, AstXmlElement *elem,
+                               AstFrame *frm, int *status ){
+/*
+*  Name:
+*     UnionReader
+
+*  Purpose:
+*     Make an AST Region from an IVOA Union region element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstRegion *UnionReader( AstXmlChan *this, AstXmlElement *elem,
+*                             AstFrame *frm, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function makes a new AST Region from the supplied IVOA
+*     Union region element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Union region element.
+*     frm
+*        Pointer to the 2D Frame in which the returned Region should be
+*        defined. If the Unit attribute is not set, this function will
+*        set it to the value supplied in "unit" before returning.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to the new Region.
+
+*/
+
+/* Local Variables: */
+   AstRegion *new;               /* Pointer to returned Region */
+   AstRegion *reg;               /* Pointer to component Region */
+   AstRegion *tmp;               /* Pointer to new Region */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[1];         /* Names of the subelements to be searched for */
+   int i;                        /* Loop count */
+   int max[1];                   /* Max allowed occurrences of each name */
+   int min[1];                   /* Min allowed occurrences of each name */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Search the supplied element for a Region sub-element. */
+   names[ 0 ] = "Intersection|Union|Negation|AllSky|Circle|Ellipse|Polygon|"
+                "Convex|Box";
+   min[ 0 ] = 2;
+   max[ 0 ] = INT_MAX;
+   scan = ScanIVOAElement( this, elem, 1, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Create Regions from all the component region elements, and combine
+   them into nested CmpRegions, using the boolean OR operator to combine
+   them. */
+      new = RegionReader( this, scan->el[0][0], frm, status );
+      for( i = 1; i < scan->count[0]; i++ ) {
+         reg = RegionReader( this, scan->el[0][i], frm, status );
+         tmp = (AstRegion *) astCmpRegion( new, reg, AST__OR, "", status );
+         reg = astAnnul( reg );
+         (void) astAnnul( new );
+         new = tmp;
+      }
+
+/* Get any fill factor from the element and assign to the returned Region. */
+      FillAndLims( this, elem, new, status );
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+/* Annul any returned Frame if an error has occurred. */
+   if( !astOK ) new = astAnnul( new );
+
+/* Return the pointer to the new Region. */
+   return new;
+}
+
+static int Use( AstXmlChan *this, int set, int helpful, int *status ) {
+/*
+*  Name:
+*     Use
+
+*  Purpose:
+*     Decide whether to write a value to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "channel.h"
+*     int Use( AstXmlChan *this, int set, int helpful, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function decides whether a value supplied by a class "Dump"
+*     function, via a call to one of the astWrite... protected
+*     methods, should actually be written to the data sink associated
+*     with a XmlChan.
+*
+*     This decision is based on the settings of the "set" and
+*     "helpful" flags supplied to the astWrite... method, plus the
+*     attribute settings of the XmlChan.
+
+*  Parameters:
+*     this
+*        A pointer to the XmlChan.
+*     set
+*        The "set" flag supplied.
+*     helpful
+*        The "helpful" value supplied.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the value should be written out, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global error status set or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   int full;                     /* Full attribute value */
+   int result;                   /* Result value to be returned */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* If "set" is non-zero, then so is the result ("set" values must
+   always be written out). */
+   result = ( set != 0 );
+
+/* Otherwise, obtain the value of the XmlChan's Full attribute. */
+   if ( !set ) {
+      full = astGetFull( this );
+
+/* If Full is positive, display all values, if zero, display only
+   "helpful" values, if negative, display no (un-"set") values. */
+      if ( astOK ) result = ( ( helpful && ( full > -1 ) ) || ( full > 0 ) );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int Ustrcmp( const char *a, const char *b, int *status ){
+/*
+*  Name:
+*     Ustrcmp
+
+*  Purpose:
+*     A case blind version of strcmp.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int Ustrcmp( const char *a, const char *b, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     Returns 0 if there are no differences between the two strings, and 1
+*     otherwise. Comparisons are case blind.
+
+*  Parameters:
+*     a
+*        Pointer to first string.
+*     b
+*        Pointer to second string.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the strings match, otherwise one.
+
+*  Notes:
+*     -  This function does not consider the sign of the difference between
+*     the two strings, whereas "strcmp" does.
+*     -  This function attempts to execute even if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   const char *aa;         /* Pointer to next "a" character */
+   const char *bb;         /* Pointer to next "b" character */
+   int ret;                /* Returned value */
+
+/* Initialise the returned value to indicate that the strings match. */
+   ret = 0;
+
+/* Initialise pointers to the start of each string. */
+   aa = a;
+   bb = b;
+
+/* Loop round each character. */
+   while( 1 ){
+
+/* We leave the loop if either of the strings has been exhausted. */
+      if( !(*aa ) || !(*bb) ){
+
+/* If one of the strings has not been exhausted, indicate that the
+   strings are different. */
+         if( *aa || *bb ) ret = 1;
+
+/* Break out of the loop. */
+         break;
+
+/* If neither string has been exhausted, convert the next characters to
+   upper case and compare them, incrementing the pointers to the next
+   characters at the same time. If they are different, break out of the
+   loop. */
+      } else {
+
+         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
+            ret = 1;
+            break;
+         }
+
+      }
+
+   }
+
+/* Return the result. */
+   return ret;
+
+}
+
+static int Ustrncmp( const char *a, const char *b, size_t n, int *status ){
+/*
+*  Name:
+*     Ustrncmp
+
+*  Purpose:
+*     A case blind version of strncmp.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int Ustrncmp( const char *a, const char *b, size_t n, int *status )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     Returns 0 if there are no differences between the first "n"
+*     characters of the two strings, and 1 otherwise. Comparisons are
+*     case blind.
+
+*  Parameters:
+*     a
+*        Pointer to first string.
+*     b
+*        Pointer to second string.
+*     n
+*        The maximum number of characters to compare.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Zero if the strings match, otherwise one.
+
+*  Notes:
+*     -  This function does not consider the sign of the difference between
+*     the two strings, whereas "strncmp" does.
+*     -  This function attempts to execute even if an error has occurred.
+
+*/
+
+/* Local Variables: */
+   const char *aa;         /* Pointer to next "a" character */
+   const char *bb;         /* Pointer to next "b" character */
+   int i;                  /* Character index */
+   int ret;                /* Returned value */
+
+/* Initialise the returned value to indicate that the strings match. */
+   ret = 0;
+
+/* Initialise pointers to the start of each string. */
+   aa = a;
+   bb = b;
+
+/* Compare up to "n" characters. */
+   for( i = 0; i < (int) n; i++ ){
+
+/* We leave the loop if either of the strings has been exhausted. */
+      if( !(*aa ) || !(*bb) ){
+
+/* If one of the strings has not been exhausted, indicate that the
+   strings are different. */
+         if( *aa || *bb ) ret = 1;
+
+/* Break out of the loop. */
+         break;
+
+/* If neither string has been exhausted, convert the next characters to
+   upper case and compare them, incrementing the pointers to the next
+   characters at the same time. If they are different, break out of the
+   loop. */
+      } else {
+
+         if( toupper( (int) *(aa++) ) != toupper( (int) *(bb++) ) ){
+            ret = 1;
+            break;
+         }
+
+      }
+
+   }
+
+/* Return the result. */
+   return ret;
+
+}
+
+static int VertexReader( AstXmlChan *this, AstXmlElement *elem, double *x,
+                         double *y, int *status ){
+/*
+*  Name:
+*     VertexReader
+
+*  Purpose:
+*     Read a position from an IVOA Vertex element.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     int VertexReader( AstXmlChan *this, AstXmlElement *elem, double *x,
+*                       double *y )
+
+*  Class Membership:
+*     XmlChan member function.
+
+*  Description:
+*     This function reads a 2D position from the supplied IVOA Vertex
+*     element.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     elem
+*        Pointer to the IVOA Vertex element.
+*     x
+*        Pointer to the double in which to put the returned X value.
+*     y
+*        Pointer to the double in which to put the returned Y value.
+
+*  Returned Value:
+*     Non-zero if the <Vertex> contains a <pole> tag.
+
+*/
+
+/* Local Variables: */
+   IVOAScan *scan;               /* Structure holding scan results */
+   const char *names[2];         /* Names of the subelements to be searched for */
+   double xy[ 2 ];               /* Axis values read from Position */
+   int max[2];                   /* Max allowed occurrences of each name */
+   int min[2];                   /* Min allowed occurrences of each name */
+   int result;                   /* Returned value */
+
+/* Check the global error status. */
+   if ( !astOK ) return 0;
+
+/* Initialise */
+   result = 0;
+   *x = AST__BAD;
+   *y = AST__BAD;
+
+/* Search the supplied element for the required sub-elements. */
+   names[ 0 ] = "Position";
+   max[ 0 ] = 1;
+   min[ 0 ] = 1;
+   names[ 1 ] = "SmallCircle";
+   max[ 1 ] = 1;
+   min[ 1 ] = 0;
+   scan = ScanIVOAElement( this, elem, 2, names, min, max, status );
+
+/* If succesfull.. */
+   if( scan ) {
+
+/* Get the axis values from the Position element. */
+      xy[ 0 ] = AST__BAD;
+      xy[ 1 ] = AST__BAD;
+      ElemListD( this, scan->el[0][0], 2, xy, status );
+      *x = xy[ 0 ];
+      *y = xy[ 1 ];
+
+/* Get any SmallCircle element. If it has a Pole issue a warning. */
+      result = scan->count[ 1 ];
+      if( result ) {
+         if( FindElement( this, scan->el[1][0], "Pole", status ) ) {
+            Report( this, scan->el[1][0], WARNING, "contains a <Pole> "
+                    "tag (poles are not supported by AST)", status );
+         }
+      }
+
+/* Free resources */
+      scan = FreeIVOAScan( scan, status );
+   }
+
+   return result;
+}
+
+static void WriteBegin( AstChannel *this_channel, const char *class,
+                        const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteBegin
+
+*  Purpose:
+*     Write a "Begin" data item to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void WriteBegin( AstChannel *this_channel, const char *class,
+*                      const char *comment, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected
+*     astWriteBegin method inherited from the Channel class).
+
+*  Description:
+*     This function writes a "Begin" data item to the data sink
+*     associated with a Channel, so as to begin the output of a new
+*     Object definition.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     class
+*        Pointer to a constant null-terminated string containing the
+*        name of the class to which the Object belongs.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the "Begin"
+*        item. Normally, this will describe the purpose of the Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The comment supplied may not actually be used, depending on
+*     the nature of the Channel supplied.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;         /* A pointer to the XmlChan structure. */
+   AstXmlElement *elem;      /* The XML element to hodl the new AST object */
+   const char *pref;         /* XML namespace prefix to use */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* If this is a top level object (i.e. if there is no container element),
+   reset all the other values in the XmlChan for safety. */
+   if( !this->container ) {
+      this->objectname = NULL;
+      this->objectset = 1;
+      this->objectcomment = NULL;
+   }
+
+/* Initialise a flag to indicate that the next "IsA" item should not be
+   written. This flag will be changed if and when an item is added which
+   related to the class described by the "IsA" item. Save the old value
+   first. */
+   this->write_isa = 0;
+
+/* Store the namespace prefix. */
+   pref = astGetXmlPrefix( this );
+
+/* Create a new XmlElement with a name equal to the AST class name of the
+   object being dumped (and no namespace prefix), and add it into the
+   current container (i.e. parent) element. */
+   elem = astXmlAddElement( this->container, class, pref );
+
+/* If this is a top level container, store the namespace URI for
+   the element, either default or named depending on the value of
+   XmlPrefix. */
+   if( !this->container ) astXmlAddURI( elem, pref, AST__XMLNS );
+
+/* If non-blank, append a "Label" atttribute to the element holding the
+   name of the object (stored in the XmlChan structure). */
+   if( this->objectname ) astXmlAddAttr( elem, LABEL, this->objectname, NULL );
+
+/* If the object has all default values, store a true value for the
+   DEFAULT attribute. */
+   if( !this->objectset ) astXmlAddAttr( elem, DEFAULT, TRUE, NULL );
+
+/* Add commments if required. */
+   if( astGetComment( this_channel ) ) {
+
+/* If we are adding comments, and if a comment was supplied as a
+   parameter to this function, then store the commment as an attribute of
+   the element. This comment describes the class function as a whole, not
+   the specific usage of this instance of the class (this is given by the
+   comment in this->objectcomment). */
+      if( comment && *comment ) astXmlAddComment( elem, 0, comment );
+
+/* If the object has a usage comment, add it to the content of the
+   element if required. */
+      if( this->objectcomment ) astXmlAddAttr( elem, DESC, this->objectcomment, NULL );
+   }
+
+/* Make the new element the current container. */
+   this->container = (AstXmlParent *) elem;
+
+/* If an error has occurred, annul the container element in the XmlChan. */
+   if( !astOK ) this->container = astXmlAnnulTree( this->container );
+
+}
+
+static void WriteDouble( AstChannel *this_channel, const char *name,
+                         int set, int helpful,
+                         double value, const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteDouble
+
+*  Purpose:
+*     Write a double value to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void WriteDouble( AstChannel *this, const char *name,
+*                       int set, int helpful,
+*                       double value, const char *comment, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected
+*     astWriteDouble method inherited from the Channel class).
+
+*  Description:
+*     This function writes a named double value, representing the
+*     value of a class instance variable, to the data sink associated
+*     with a Channel. It is intended for use by class "Dump" functions
+*     when writing out class information which will subsequently be
+*     re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        Channel's Full attribute is set - either to permit all values
+*        to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        The value to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the Channel supplied and the setting of its
+*        Comment attribute.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define BUFF_LEN 100             /* Size of local formatting buffer */
+
+/* Local Variables: */
+   AstXmlChan *this;             /* A pointer to the XmlChan structure. */
+   AstXmlElement *elem;          /* Pointer to new element */
+   char buff[ BUFF_LEN + 1 ];    /* Local formatting buffer */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* If the object to be written is a component of a default AST object (i.e.
+   an object which is "not set"), then we do not write out this item. */
+   if( this->objectset ) {
+
+/* Use the "set" and "helpful" flags, along with the Channel's
+   attributes to decide whether this value should actually be
+   written. */
+      if( Use( this, set, helpful, status ) ) {
+
+/* Create a new XmlElement with a name of ATTR (and no namespace
+   prefix), and add it into the current container (i.e. parent) element. */
+         elem = astXmlAddElement( this->container, ATTR,
+                                  astGetXmlPrefix( this ) );
+
+/* Add a NAME attribute to this element containing the item name. */
+         astXmlAddAttr( elem, NAME, name, NULL );
+
+/* Format the value as a string and store it as the VALUE attribute.
+   Make sure "-0" isn't produced. Use a magic string to represent bad
+   values. */
+         if( value != AST__BAD ) {
+            (void) sprintf( buff, "%.*g", DBL_DIG, value );
+            if ( !strcmp( buff, "-0" ) ) {
+               buff[ 0 ] = '0';
+               buff[ 1 ] = '\0';
+            }
+         } else {
+            strcpy( buff, BAD_STRING );
+         }
+         astXmlAddAttr( elem, VALUE, buff, NULL );
+
+/* If we are adding comments, and if a comment was supplied as a
+   parameter to this function, then store the commment as an attribute of
+   the element. */
+         if( comment && *comment && astGetComment( this_channel ) ) {
+            astXmlAddAttr( elem, DESC, comment, NULL );
+         }
+
+/* If the object has all default values, store a true value for the
+   DEFAULT attribute. */
+         if( !set ) astXmlAddAttr( elem, DEFAULT, TRUE, NULL );
+
+/* Initialise a flag to indicate that the next "IsA" item should be
+   written. */
+         this->write_isa = 1;
+      }
+   }
+
+/* If an error has occurred, annul the container element in the XmlChan. */
+   if( !astOK ) this->container = astXmlAnnulTree( this->container );
+
+/* Undefine macros local to this function. */
+#undef BUFF_LEN
+}
+
+static void WriteEnd( AstChannel *this_channel, const char *class, int *status ) {
+/*
+*  Name:
+*     WriteEnd
+
+*  Purpose:
+*     Write an "End" data item to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void WriteEnd( AstChannel *this, const char *class, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected
+*     astWriteEnd method inherited from the Channel class).
+
+*  Description:
+*     This function writes an "End" data item to the data sink
+*     associated with a Channel. This item delimits the end of an
+*     Object definition.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     class
+*        Pointer to a constant null-terminated string containing the
+*        class name of the Object whose definition is being terminated
+*        by the "End" item.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* Pointer to the XmlChan structure */
+   AstXmlParent *parent;         /* Pointer to parent element */
+   char *d;                      /* Pointer to end of next sub-string */
+   char *c;                      /* Pointer to start of next sub-string */
+   char *text;                   /* Pointer to complete string */
+   int mxlen;                    /* Max allowed length of text */
+
+#ifdef DEBUG
+   int nobj;                     /* No. of XmlObjects in existence */
+#endif
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+#ifdef DEBUG
+/* Save the number of XmlObjects currently in existenece. */
+   nobj = astXmlTrace(3);
+#endif
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* Get the parent of the current container element. */
+   if( this->container ) {
+      parent = astXmlGetParent( this->container );
+
+/* If the current container element has no parent, we have completed the
+   construction of the in-memory XML representation of the AST object being
+   written out. In this case, we convert the in-memory representation
+   into a set of strings and write them out using the supplied sink
+   function. */
+      if( !parent ) {
+
+/* First get a single string holding the complete formatted XML
+   representation of the AST object. */
+         if( astGetIndent( this ) ) {
+            text = (char *) astXmlShow( this->container );
+         } else {
+            text = (char *) astXmlFormat( this->container );
+         }
+
+/* Now, if we have any text, split it into separate lines. The end of a line
+   is indicated by a "\n" character in the text returned by astXmlFormat. */
+         if( text ) {
+
+/* Get the maximum allowed line length. */
+            mxlen = astGetXmlLength( this );
+
+/* Loop round locating each '\n' character in the string. Replace the
+   '\n' character by 0, so that the previous part of the string is then
+   null terminated, and write it out using the astPutNextText method
+   (splitting the text up into lines no longer than "mxlen"). */
+            c = text;
+            d = strchr( c, '\n' );
+            while( d ) {
+               *d = 0;
+               OutputText( this, c, mxlen, status );
+               c = d + 1;
+               d = strchr( c, '\n' );
+            }
+
+/* Write out any text following the last '\n' character. */
+            if( *c ) OutputText( this, c, mxlen, status );
+
+/* Free the memory holding the text and in-memory representations of the AST
+   Object. */
+            text = astFree( (void *) text );
+            astXmlRemoveItem( this->container );
+            this->container = astXmlAnnul( this->container );
+
+#ifdef DEBUG
+/* Report an error if there is a memory leak. */
+            if( astXmlTrace(3) > nobj && astOK ) {
+               astError( AST__INTER, "astWriteEnd(XmlChan): %d XmlObjects "
+                         "remain in existence - should be %d (internal AST "
+                         "programming error).", status, astXmlTrace(3), nobj );
+            }
+#endif
+
+         }
+      }
+
+/* Reset the current container element to be the parent found above. */
+      if( !parent || astXmlCheckType( parent, AST__XMLELEM ) ) {
+         this->container = parent;
+      } else if( astOK ) {
+         astError( AST__INTER, "astWriteEnd(XmlChan): Cannot update "
+                   "container: parent is not an XmlElement (internal "
+                   "AST programming error)." , status);
+      }
+   }
+
+/* If an error has occurred, annul the container element in the XmlChan. */
+   if( !astOK ) this->container = astXmlAnnulTree( this->container );
+
+}
+
+static void WriteInt( AstChannel *this_channel, const char *name, int set, int helpful,
+                      int value, const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteInt
+
+*  Purpose:
+*     Write an integer value to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void WriteInt( AstChannel *this, const char *name, int set, int helpful,
+*                    int value, const char *comment, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected
+*     astWriteInt method inherited from the Channel class).
+
+*  Description:
+*     This function writes a named integer value, representing the
+*     value of a class instance variable, to the data sink associated
+*     with a Channel. It is intended for use by class "Dump" functions
+*     when writing out class information which will subsequently be
+*     re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        Channel's Full attribute is set - either to permit all values
+*        to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        The value to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the Channel supplied and the setting of its
+*        Comment attribute.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Constants: */
+#define BUFF_LEN 50             /* Size of local formatting buffer */
+
+/* Local Variables: */
+   AstXmlChan *this;             /* A pointer to the XmlChan structure. */
+   AstXmlElement *elem;          /* Pointer to new element */
+   char buff[ BUFF_LEN + 1 ];    /* Local formatting buffer */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* If the object to be written is a component of a default AST object (i.e.
+   an object which is "not set"), then we do not write out this item. */
+   if( this->objectset ) {
+
+/* Use the "set" and "helpful" flags, along with the Channel's
+   attributes to decide whether this value should actually be
+   written. */
+      if( Use( this, set, helpful, status ) ) {
+
+/* Create a new XmlElement with a name of ATTR (and no namespace
+   prefix), and add it into the current container (i.e. parent) element. */
+         elem = astXmlAddElement( this->container, ATTR,
+                                  astGetXmlPrefix( this ) );
+
+/* Add a NAME attribute to this element containing the item name. */
+         astXmlAddAttr( elem, NAME, name, NULL );
+
+/* Format the value as a decimal string and add it to the element as the
+   VALUE attribute. */
+         (void) sprintf( buff, "%d", value );
+         astXmlAddAttr( elem, VALUE, buff, NULL );
+
+/* If we are adding comments, and if a comment was supplied as a
+   parameter to this function, then store the commment as an attribute of
+   the element. */
+         if( comment && *comment && astGetComment( this_channel ) ) {
+            astXmlAddAttr( elem, DESC, comment, NULL );
+         }
+
+/* If the object has all default values, store a true value for the
+   DEFAULT attribute. */
+         if( !set ) astXmlAddAttr( elem, DEFAULT, TRUE, NULL );
+
+/* Initialise a flag to indicate that the next "IsA" item should be
+   written. */
+         this->write_isa = 1;
+      }
+   }
+
+/* If an error has occurred, annul the container element in the XmlChan. */
+   if( !astOK ) this->container = astXmlAnnulTree( this->container );
+
+/* Undefine macros local to this function. */
+#undef BUFF_LEN
+}
+
+static void WriteIsA( AstChannel *this_channel, const char *class,
+                      const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteIsA
+
+*  Purpose:
+*     Write an "IsA" data item to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void WriteIsA( AstChannel *this, const char *class,
+*                    const char *comment, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected
+*     astWriteIsA method inherited from the Channel class).
+
+*  Description:
+*     This function writes an "IsA" data item to the data sink
+*     associated with a Channel. This item delimits the end of the
+*     data associated with the instance variables of a class, as part
+*     of an overall Object definition.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     class
+*        Pointer to a constant null-terminated string containing the
+*        name of the class whose data are terminated by the "IsA"
+*        item.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the "IsA"
+*        item. Normally, this will describe the purpose of the class
+*        whose data are being terminated.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     - The comment supplied may not actually be used, depending on
+*     the nature of the Channel supplied.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* A pointer to the XmlChan structure. */
+   AstXmlElement *elem;          /* Pointer to new element */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* If the object to be written is a component of a default AST object (i.e.
+   an object which is "not set"), then we do not write out this item. */
+   if( this->objectset ) {
+
+/* Output an "IsA" item only if there has been at least one item
+   written since the last "Begin" or "IsA" item, or if the Full
+   attribute for the Channel is greater than zero (requesting maximum
+   information). */
+      if ( this->write_isa || astGetFull( this ) > 0 ) {
+
+/* Create a new XmlElement with a name of "_isa" (and no namespace prefix),
+   and add it into the current container (i.e. parent) element. */
+         elem = astXmlAddElement( this->container, ISA,
+                                  astGetXmlPrefix( this ) );
+
+/* Add a "class" attribute to this element containing the class name. */
+         astXmlAddAttr( elem, "class", class, NULL );
+
+/* If we are adding comments, and if a comment was supplied as a
+   parameter to this function, then store the commment as an attribute of
+   the element. This comment describes the class function as a whole, not
+   the specific usage of this instance of the class. */
+         if( comment && *comment && astGetComment( this_channel ) ) {
+            astXmlAddAttr( elem, DESC, comment, NULL );
+         }
+      }
+   }
+
+/* Initialise a flag to indicate that the next "IsA" item should not be
+   written. This flag will be changed if and when an item is added which
+   related to the class described by the "IsA" item. */
+   this->write_isa = 0;
+
+/* If an error has occurred, annul the container element in the XmlChan. */
+   if( !astOK ) this->container = astXmlAnnulTree( this->container );
+}
+
+static void WriteObject( AstChannel *this_channel, const char *name,
+                         int set, int helpful,
+                         AstObject *value, const char *comment, int *status ) {
+/*
+*  Name:
+*     WriteObject
+
+*  Purpose:
+*     Write an Object as a value to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void WriteObject( AstChannel *this_channel, const char *name,
+*                       int set, int helpful,
+*                       AstObject *value, const char *comment, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected
+*     astWriteObject method inherited from the Channel class).
+
+*  Description:
+*     This function writes an Object as a named value, representing
+*     the value of a class instance variable, to the data sink
+*     associated with an XmlChan. It is intended for use by class
+*     "Dump" functions when writing out class information which will
+*     subsequently be re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations.
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        Channel's Full attribute is set - either to permit all values
+*        to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        A Pointer to the Object to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the Channel supplied and the setting of its
+*        Comment attribute.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;         /* A pointer to the XmlChan structure. */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* If the object to be written is a component of a default AST object (i.e.
+   an object which is "not set"), then we do not write out the object. */
+   if( this->objectset ) {
+
+/* Use the "set" and "helpful" flags, along with the Channel's
+   attributes to decide whether this value should actually be
+   written. */
+      if ( Use( this, set, helpful, status ) ) {
+
+/* Save the supplied name associated with the object being written so
+   that it is available for use within the following invocation of the
+   WriteBegin method. The name is stored within the XmlChan structure
+   (NULL is used to indicate "no name supplied"). */
+         this->objectname = ( name && strlen( name ) ) ? name : NULL;
+
+/* Also save the supplied comment and a flag indicating if the object is
+   set. These will be used by the WriteBegin method. They are stored within
+   the XmlChan structure. */
+         this->objectset = set;
+         this->objectcomment = comment;
+
+/* Write the object to the XmlChan. */
+         (void) astWrite( this, value );
+
+/* Nullify the components of the XmlChan set above. */
+         this->objectname = NULL;
+         this->objectset = 1;
+         this->objectcomment = NULL;
+
+/* Initialise a flag to indicate that the next "IsA" item should be
+   written. */
+         this->write_isa = 1;
+      }
+   }
+
+/* If an error has occurred, annul the container element in the XmlChan. */
+   if( !astOK ) this->container = astXmlAnnulTree( this->container );
+
+}
+
+static void WriteString( AstChannel *this_channel, const char *name, int set,
+                         int helpful, const char *value, const char *comment, int *status ){
+/*
+*  Name:
+*     WriteString
+
+*  Purpose:
+*     Write a string value to a data sink.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     void WriteString( AstChannel *this, const char *name, int set, int helpful,
+*                       const char *value, const char *comment, int *status )
+
+*  Class Membership:
+*     XmlChan member function (over-rides the protected
+*     astWriteString method inherited from the Channel class).
+
+*  Description:
+*     This function writes a named string value, representing the
+*     value of a class instance variable, to the data sink associated
+*     with a Channel. It is intended for use by class "Dump" functions
+*     when writing out class information which will subsequently be
+*     re-read.
+
+*  Parameters:
+*     this
+*        Pointer to the Channel.
+*     name
+*        Pointer to a constant null-terminated string containing the
+*        name to be used to identify the value in the external
+*        representation. This will form the key for identifying it
+*        again when it is re-read. The name supplied should be unique
+*        within its class.
+*
+*        Mixed case may be used and will be preserved in the external
+*        representation (where possible) for cosmetic effect. However,
+*        case is not significant when re-reading values.
+*
+*        It is recommended that a maximum of 6 alphanumeric characters
+*        (starting with an alphabetic character) be used. This permits
+*        maximum flexibility in adapting to standard external data
+*        representations (e.g. FITS).
+*     set
+*        If this is zero, it indicates that the value being written is
+*        a default value (or can be re-generated from other values) so
+*        need not necessarily be written out. Such values will
+*        typically be included in the external representation with
+*        (e.g.) a comment character so that they are available to
+*        human readers but will be ignored when re-read. They may also
+*        be completely omitted in some circumstances.
+*
+*        If "set" is non-zero, the value will always be explicitly
+*        included in the external representation so that it can be
+*        re-read.
+*     helpful
+*        This flag provides a hint about whether a value whose "set"
+*        flag is zero (above) should actually appear at all in the
+*        external representaton.
+*
+*        If the external representation allows values to be "commented
+*        out" then, by default, values will be included in this form
+*        only if their "helpful" flag is non-zero. Otherwise, they
+*        will be omitted entirely. When possible, omitting the more
+*        obscure values associated with a class is recommended in
+*        order to improve readability.
+*
+*        This default behaviour may be further modified if the
+*        Channel's Full attribute is set - either to permit all values
+*        to be shown, or to suppress non-essential information
+*        entirely.
+*     value
+*        Pointer to a constant null-terminated string containing the
+*        value to be written.
+*     comment
+*        Pointer to a constant null-terminated string containing a
+*        textual comment to be associated with the value.
+*
+*        Note that this comment may not actually be used, depending on
+*        the nature of the Channel supplied and the setting of its
+*        Comment attribute.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* A pointer to the XmlChan structure. */
+   AstXmlElement *elem;          /* Pointer to new element */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_channel;
+
+/* If the object to be written is a component of a default AST object (i.e.
+   an object which is "not set"), then we do not write out this item. */
+   if( this->objectset ) {
+
+/* Use the "set" and "helpful" flags, along with the Channel's
+   attributes to decide whether this value should actually be
+   written. */
+      if( Use( this, set, helpful, status ) ) {
+
+/* Create a new XmlElement with a name of ATTR (and no namespace
+   prefix), and add it into the current container (i.e. parent) element. */
+         elem = astXmlAddElement( this->container, ATTR,
+                                  astGetXmlPrefix( this ) );
+
+/* Add a NAME attribute to this element containing the item name. */
+         astXmlAddAttr( elem, NAME, name, NULL );
+
+/* If we are using QUOTED format, add an attribute to indicate that this is a
+   string value (mainly included for compatibility with JNIAST). */
+         if( astGetXmlFormat( this ) == QUOTED_FORMAT ) {
+            astXmlAddAttr( elem, QUOTED, TRUE, NULL );
+         }
+
+/* Add it the value to the element as the VALUE attribute. */
+         astXmlAddAttr( elem, VALUE, value, NULL );
+
+/* If we are adding comments, and if a comment was supplied as a
+   parameter to this function, then store the commment as an attribute of
+   the element. */
+         if( comment && *comment && astGetComment( this_channel ) ) {
+            astXmlAddAttr( elem, DESC, comment, NULL );
+         }
+
+/* If the object has all default values, store a true value for the
+   DEFAULT attribute. */
+         if( !set ) astXmlAddAttr( elem, DEFAULT, TRUE, NULL );
+
+/* Initialise a flag to indicate that the next "IsA" item should be
+   written. */
+         this->write_isa = 1;
+      }
+   }
+
+/* If an error has occurred, annul the container element in the XmlChan. */
+   if( !astOK ) this->container = astXmlAnnulTree( this->container );
+
+}
+
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/* XmlFormat */
+/* ========= */
+/*
+*att++
+*  Name:
+*     XmlFormat
+
+*  Purpose:
+*     System for formatting Objects as XML.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute specifies the formatting system to use when AST
+*     Objects are written out as XML through an XmlChan. It
+c     affects the behaviour of the astWrite function when
+f     affects the behaviour of the AST_WRITE routine  when
+*     they are used to transfer any AST Object to or from an external
+*     XML representation.
+*
+*     The XmlChan class allows AST objects to be represented in the form
+*     of XML in several ways (conventions) and the XmlFormat attribute is
+*     used to specify which of these should be used. The formatting options
+*     available are outlined in the "Formats Available" section below.
+*
+*     By default, an XmlChan will attempt to determine which format system
+*     is already in use, and will set the default XmlFormat value
+*     accordingly (so that subsequent I/O operations adopt the same
+*     conventions). It does this by looking for certain critical items
+*     which only occur in particular formats. For details of how this
+*     works, see the "Choice of Default Format" section below. If you wish
+*     to ensure that a particular format system is used, independently of
+*     any XML already read, you should set an explicit XmlFormat value
+*     yourself.
+*
+*  Formats Available:
+*     The XmlFormat attribute can take any of the following (case
+*     insensitive) string values to select the corresponding formatting
+*     system:
+*
+*     - "NATIVE": This is a direct conversion to XML of the heirarchical
+*     format used by a standard XML channel (and also by the NATIVE
+*     encoding of a FitsChan).
+*
+*     - "QUOTED": This is the same as NATIVE format except that extra
+*     information is included which allows client code to convert the
+*     XML into a form which can be read by a standard AST Channel. This
+*     extra information indicates which AST attribute values should be
+*     enclosed in quotes before being passed to a Channel.
+*
+*     - "IVOA": This is a format that uses an early draft of the STC-X schema
+*     developed by the International Virtual Observatory Alliance (IVOA -
+*     see "http://www.ivoa.net/") to describe coordinate systems, regions,
+*     mappings, etc. Support is limited to V1.20 described at
+*     "http://www.ivoa.net/Documents/WD/STC/STC-20050225.html". Since the
+*     version of STC-X finally adopted by the IVOA differs in several
+*     significant respects from V1.20, this format is now mainly of
+*     historical interest. Note, the alternative "STC-S" format (a
+*     simpler non-XML encoding of the STC metadata) is supported by the
+*     StcsChan class.
+
+*  Choice of Default Format;
+*     If the XmlFormat attribute of an XmlChan is not set, the default
+*     value it takes is determined by the presence of certain critical
+*     items within the document most recently read using
+c     astRead.
+f     AST_READ.
+*     The sequence of decision used to arrive at the default value is as
+*     follows:
+*
+*     - If the previous document read contained any elements in any of the STC
+*     namespaces ("urn:nvo-stc", "urn:nvo-coords" or "urn:nvo-region"), then
+*     the default value is IVOA.
+*     - If the previous document read contained any elements in the AST
+*     namespace which had an associated XML attribute called "quoted", then
+*     the default value is QUOTED.
+*     - Otherwise, if none of these conditions is met (as would be the
+*     case if no document had yet been read), then NATIVE format is
+*     used.
+*
+*     Setting an explicit value for the XmlFormat attribute always
+*     over-rides this default behaviour.
+
+*  The IVOA Format:
+*     The IVOA support caters only for certain parts of V1.20 of the
+*     draft Space-Time Coordinate (STC) schema (see
+*     http://www.ivoa.net/Documents/WD/STC/STC-20050225.html). Note, this
+*     draft has now been superceded by an officially adopted version that
+*     differs in several significant respects from V1.20. Consequently,
+*     the "IVOA" XmlChan format is of historical interest only.
+*
+*     The following points should be noted when using an XmlChan to read
+*     or write STC information (note, this list is currently incomplete):
+*
+*     - Objects can currently only be read using this format, not written.
+*     - The AST object generated by reading an <STCMetadata> element will
+*     be an instance of one of the AST "Stc" classes: StcResourceProfile,
+*     StcSearchLocation, StcCatalogEntryLocation, StcObsDataLocation.
+*     - When reading an <STCMetadata> element, the axes in the returned
+*     AST Object will be in the order space, time, spectral, redshift,
+*     irrespective of the order in which the axes occur in the <STCMetadata>
+*     element. If the supplied <STCMetadata> element does not contain all of
+*     these axes, the returned AST Object will also omit them, but the
+*     ordering of those axes which are present will be as stated above. If
+*     the spatial frame represents a celestial coordinate system the
+*     spatial axes will be in the order (longitude, latitude).
+*     - Until such time as the AST TimeFrame is complete, a simple
+*     1-dimensional Frame (with Domain set to TIME) will be used to
+*     represent the STC <TimeFrame> element. Consequently, most of the
+*     information within a <TimeFrame> element is currently ignored.
+*     - <SpaceFrame> elements can only be read if they describe a celestial
+*     longitude and latitude axes supported by the AST SkyFrame class. The
+*     space axes will be returned in the order (longitude, latitude).
+*     - Velocities associated with SpaceFrames cannot be read.
+*     - Any <GenericCoordFrame> elements within an <AstroCoordSystem> element
+*     are currently ignored.
+*     - Any second or subsequent <AstroCoordSystem> found within an
+*     STCMetaData element is ignored.
+*     - Any second or subsequent <AstroCoordArea> found within an
+*     STCMetaData element is ignored.
+*     - Any <OffsetCenter> found within a <SpaceFrame> is ignored.
+*     - Any CoordFlavor element found within a <SpaceFrame> is ignored.
+*     - <SpaceFrame> elements can only be read if they refer to
+*     one of the following space reference frames: ICRS, GALACTIC_II,
+*     SUPER_GALACTIC, HEE, FK4, FK5, ECLIPTIC.
+*     - <SpaceFrame> elements can only be read if the reference
+*     position is TOPOCENTER. Also, any planetary ephemeris is ignored.
+*     - Regions: there is currently no support for STC regions of type
+*     Sector, ConvexHull or SkyIndex.
+*     - The AST Region read from a CoordInterval element is considered to
+*     be open if either the lo_include or the hi_include attribute is
+*     set to false.
+*     - <RegionFile> elements are not supported.
+*     - Vertices within <Polygon> elements are always considered to be
+*     joined using great circles (that is, <SmallCircle> elements are
+*     ignored).
+
+*  Applicability:
+*     XmlChan
+*        All XmlChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(XmlChan,XmlFormat,xmlformat,UNKNOWN_FORMAT)
+astMAKE_SET(XmlChan,XmlFormat,int,xmlformat,(
+   value == NATIVE_FORMAT ||
+   value == IVOA_FORMAT ||
+   value == QUOTED_FORMAT ? value :
+   (astError( AST__BADAT, "astSetXmlFormat: Unknown XML formatting system %d "
+              "supplied.", status, value ), UNKNOWN_FORMAT )))
+astMAKE_TEST(XmlChan,XmlFormat,( this->xmlformat != UNKNOWN_FORMAT ))
+astMAKE_GET(XmlChan,XmlFormat,int,0,(this->xmlformat == UNKNOWN_FORMAT ?
+                                this->formatdef : this->xmlformat))
+
+/*
+*att++
+*  Name:
+*     XmlLength
+
+*  Purpose:
+*     Controls output buffer length.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Integer.
+
+*  Description:
+*     This attribute specifies the maximum length to use when writing out
+*     text through the sink function supplied when the XmlChan was created.
+*
+*     The number of characters in each string written out through the sink
+*     function will not be greater than the value of this attribute (but
+*     may be less). A value of zero (the default) means there is no limit -
+*     each string can be of any length.
+*
+f     Note, the default value of zero is unlikely to be appropriate when
+f     an XmlChan is used within Fortran code. In this case, XmlLength
+f     should usually be set to the size of the CHARACTER variable used to
+f     receive the text returned by AST_GETLINE within the sink function.
+f     This avoids the possibility of long lines being truncated invisibly
+f     within AST_GETLINE.
+
+*  Applicability:
+*     XmlChan
+*        All XmlChans have this attribute.
+*att--
+*/
+astMAKE_CLEAR(XmlChan,XmlLength,xmllength,-INT_MAX)
+astMAKE_GET(XmlChan,XmlLength,int,0,( ( this->xmllength != -INT_MAX ) ? this->xmllength : 0 ))
+astMAKE_SET(XmlChan,XmlLength,int,xmllength,(value<0?0:value))
+astMAKE_TEST(XmlChan,XmlLength,( this->xmllength != -INT_MAX ))
+
+/*
+*att++
+*  Name:
+*     XmlPrefix
+
+*  Purpose:
+*     The namespace prefix to use when writing.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     String.
+
+*  Description:
+*     This attribute is a string which is to be used as the namespace
+*     prefix for all XML elements created as a result of writing an AST
+*     Object out through an XmlChan. The URI associated with the namespace
+*     prefix is given by the symbolic constant AST__XMLNS defined in
+f     AST_PAR.
+c     ast.h.
+*     A definition of the namespace prefix is included in each top-level
+*     element produced by the XmlChan.
+*
+*     The default value is a blank string which causes no prefix to be
+*     used. In this case each top-level element will set the default
+*     namespace to be the value of AST__XMLNS.
+
+*  Applicability:
+*     Object
+*        All Objects have this attribute.
+
+*att--
+*/
+astMAKE_CLEAR(XmlChan,XmlPrefix,xmlprefix,astFree( this->xmlprefix ))
+astMAKE_GET(XmlChan,XmlPrefix,const char *,NULL,( this->xmlprefix ? this->xmlprefix : "" ))
+astMAKE_SET(XmlChan,XmlPrefix,const char *,xmlprefix,astStore( this->xmlprefix, value,
+                                                strlen( value ) + (size_t) 1 ))
+astMAKE_TEST(XmlChan,XmlPrefix,( this->xmlprefix != NULL ))
+
+
+/* Copy constructor. */
+/* ----------------- */
+static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
+/*
+*  Name:
+*     Copy
+
+*  Purpose:
+*     Copy constructor for XmlChan objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Copy( const AstObject *objin, AstObject *objout, int *status )
+
+*  Description:
+*     This function implements the copy constructor for XmlChan objects.
+
+*  Parameters:
+*     objin
+*        Pointer to the object to be copied.
+*     objout
+*        Pointer to the object being constructed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     -  This constructor makes a deep copy.
+*/
+
+/* Local Variables: */
+   AstXmlChan *in;               /* Pointer to input XmlChan */
+   AstXmlChan *out;              /* Pointer to output XmlChan */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain pointers to the input and output XmlChans. */
+   in = (AstXmlChan *) objin;
+   out = (AstXmlChan *) objout;
+
+/* Clear the non-persistent values in the new XmlChan. */
+   out->objectname = NULL;   /* Name of object being written */
+   out->objectset = 1;       /* Is object being written set?*/
+   out->objectcomment = NULL;/* Comment for object class being written */
+   out->readcontext = NULL;  /* XmlElement giving context for current read */
+   out->container = NULL;    /* XmlElement to which content will be added */
+   out->write_isa = 0;       /* Write out the next "IsA" item? */
+   out->reset_source = 1;    /* A new line should be read from the source */
+   out->isa_class = NULL;    /* Class being loaded */
+
+/* Store a copy of the prefix string.*/
+   if ( in->xmlprefix ) out->xmlprefix = astStore( NULL, in->xmlprefix,
+                                           strlen( in->xmlprefix ) + (size_t) 1 );
+}
+
+
+/* Destructor. */
+/* ----------- */
+static void Delete( AstObject *obj, int *status ) {
+/*
+*  Name:
+*     Delete
+
+*  Purpose:
+*     Destructor for XmlChan objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Delete( AstObject *obj, int *status )
+
+*  Description:
+*     This function implements the destructor for XmlChan objects.
+
+*  Parameters:
+*     obj
+*        Pointer to the object to be deleted.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Notes:
+*     This function attempts to execute even if the global error status is
+*     set.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;             /* Pointer to XmlChan */
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) obj;
+
+/* Free any unread part of the document. */
+   this->readcontext = astXmlAnnul( this->readcontext );
+
+/* Free the memory used for the XmlPrefix string if necessary. */
+   this->xmlprefix = astFree( this->xmlprefix );
+
+/* Free any memory used to store text read from the source */
+   GetNextChar( NULL, status );
+
+}
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for XmlChan objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the XmlChan class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the XmlChan whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstXmlChan *this;            /* Pointer to the XmlChan structure */
+   const char *sval;            /* String attribute value */
+   int ival;                    /* Integer attribute value */
+   int set;                     /* Has the attribute got a set value? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the XmlChan structure. */
+   this = (AstXmlChan *) this_object;
+
+/* Write out values representing the instance variables for the
+   XmlChan class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+
+/* Now do instance variables which are not attributes. */
+/* =================================================== */
+
+/* XmlLength */
+/* --------- */
+      set = TestXmlLength( this, status );
+      ival = set ? GetXmlLength( this, status ) : astGetXmlLength( this );
+      astWriteInt( channel, "XmlLen", set, 0, ival, "XML buffer length" );
+
+/* XmlFormat. */
+/* --------- */
+   set = TestXmlFormat( this, status );
+   ival = set ? GetXmlFormat( this, status ) : astGetXmlFormat( this );
+   if( ival > UNKNOWN_FORMAT && ival <= MAX_FORMAT ) {
+      astWriteString( channel, "XmlFmt", set, 1, xformat[ival], "Formatting system" );
+   } else {
+      astWriteString( channel, "XmlFmt", set, 1, UNKNOWN_STRING, "Formatting system" );
+   }
+
+/* XmlPrefix */
+/* --------- */
+      set = TestXmlPrefix( this, status );
+      sval = set ? GetXmlPrefix( this, status ) : astGetXmlPrefix( this );
+      astWriteString( channel, "XmlPrf", set, 1, sval,
+                      "Namespace prefix" );
+}
+
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAXmlChan and astCheckXmlChan functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(XmlChan,Channel)
+astMAKE_CHECK(XmlChan)
+
+AstXmlChan *astXmlChan_( const char *(* source)( void ),
+                         void (* sink)( const char * ),
+                         const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astXmlChan
+f     AST_XMLCHAN
+
+*  Purpose:
+*     Create an XmlChan.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "xmlchan.h"
+c     AstXmlChan *astXmlChan( const char *(* source)( void ),
+c                             void (* sink)( const char * ),
+c                             const char *options, ... )
+f     RESULT = AST_XMLCHAN( SOURCE, SINK, OPTIONS, STATUS )
+
+*  Class Membership:
+*     XmlChan constructor.
+
+*  Description:
+*     This function creates a new XmlChan and optionally initialises
+*     its attributes.
+*
+*     A XmlChan is a specialised form of Channel which supports XML I/O
+*     operations. Writing an Object to an XmlChan (using
+c     astWrite) will, if the Object is suitable, generate an
+f     AST_WRITE) will, if the Object is suitable, generate an
+*     XML description of that Object, and reading from an XmlChan will
+*     create a new Object from its XML description.
+*
+*     Normally, when you use an XmlChan, you should provide "source"
+c     and "sink" functions which connect it to an external data store
+f     and "sink" routines which connect it to an external data store
+*     by reading and writing the resulting XML text. By default, however,
+*     an XmlChan will read from standard input and write to standard
+*     output.
+*
+*     Alternatively, an XmlChan can be told to read or write from
+*     specific text files using the SinkFile and SourceFile attributes,
+*     in which case no sink or source function need be supplied.
+
+*  Parameters:
+c     source
+f     SOURCE = SUBROUTINE (Given)
+c        Pointer to a source function that takes no arguments and
+c        returns a pointer to a null-terminated string.  If no value
+c        has been set for the SourceFile attribute, this function
+c        will be used by the XmlChan to obtain lines of input text. On
+c        each invocation, it should return a pointer to the next input
+c        line read from some external data store, and a NULL pointer
+c        when there are no more lines to read.
+c
+c        If "source" is NULL and no value has been set for the SourceFile
+c        attribute, the XmlChan will read from standard input instead.
+f        A source routine, which is a subroutine which takes a single
+f        integer error status argument.   If no value has been set
+f        for the SourceFile attribute, this routine will be used by
+f        the XmlChan to obtain lines of input text. On each
+f        invocation, it should read the next input line from some
+f        external data store, and then return the resulting text to
+f        the AST library by calling AST_PUTLINE. It should supply a
+f        negative line length when there are no more lines to read.
+f        If an error occurs, it should set its own error status
+f        argument to an error value before returning.
+f
+f        If the null routine AST_NULL is suppied as the SOURCE value,
+f        and no value has been set for the SourceFile attribute,
+f        the XmlChan will read from standard input instead.
+c     sink
+f     SINK = SUBROUTINE (Given)
+c        Pointer to a sink function that takes a pointer to a
+c        null-terminated string as an argument and returns void.
+c        If no value has been set for the SinkFile attribute, this
+c        function will be used by the XmlChan to deliver lines of
+c        output text. On each invocation, it should deliver the
+c        contents of the string supplied to some external data store.
+c
+c        If "sink" is NULL, and no value has been set for the SinkFile
+c        attribute, the XmlChan will write to standard output instead.
+f        A sink routine, which is a subroutine which takes a single
+f        integer error status argument.  If no value has been set
+f        for the SinkFile attribute, this routine will be used by
+f        the XmlChan to deliver lines of output text. On each
+f        invocation, it should obtain the next output line from the
+f        AST library by calling AST_GETLINE, and then deliver the
+f        resulting text to some external data store.  If an error
+f        occurs, it should set its own error status argument to an
+f        error value before returning.
+f
+f        If the null routine AST_NULL is suppied as the SINK value,
+f        and no value has been set for the SinkFile attribute,
+f        the XmlChan will write to standard output instead.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new XmlChan. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new XmlChan. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astXmlChan()
+f     AST_XMLCHAN = INTEGER
+*        A pointer to the new XmlChan.
+
+*  Notes:
+f     - The names of the routines supplied for the SOURCE and SINK
+f     arguments should appear in EXTERNAL statements in the Fortran
+f     routine which invokes AST_XMLCHAN. However, this is not generally
+f     necessary for the null routine AST_NULL (so long as the AST_PAR
+f     include file has been used).
+*     - If the external data source or sink uses a character encoding
+*     other than ASCII, the supplied source and sink functions should
+*     translate between the external character encoding and the internal
+*     ASCII encoding used by AST.
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the AST error status set, or if it
+*     should fail for any reason.
+f     - Note that the null routine AST_NULL (one underscore) is
+f     different to AST__NULL (two underscores), which is the null Object
+f     pointer.
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstXmlChan *new;             /* Pointer to new XmlChan */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the XmlChan, allocating memory and initialising the
+   virtual function table as well if necessary. This interface is for
+   use by other C functions within AST, and uses the standard "wrapper"
+   functions included in this class. */
+   new = astInitXmlChan( NULL, sizeof( AstXmlChan ), !class_init,
+                          &class_vtab, "XmlChan", source, SourceWrap,
+                          sink, SinkWrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   XmlChan's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new XmlChan. */
+   return new;
+}
+
+AstXmlChan *astXmlChanId_( const char *(* source)( void ),
+                           void (* sink)( const char * ),
+                           const char *options, ... ) {
+/*
+*  Name:
+*     astXmlChanId_
+
+*  Purpose:
+*     Create an XmlChan.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstXmlChan *astXmlChanId_( const char *(* source)( void ),
+*                                void (* sink)( const char * ),
+*                                const char *options, ... )
+
+*  Class Membership:
+*     XmlChan constructor.
+
+*  Description:
+*     This function implements the external (public) C interface to the
+*     astXmlChan constructor function. Another function (astXmlChanForId)
+*     should be called to create an XmlChan for use within other languages.
+*     Both functions return an ID value (instead of a true C pointer) to
+*     external users, and must be provided because astXmlChan_ has a variable
+*     argument list which cannot be encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astXmlChan_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astXmlChan_.
+
+*  Returned Value:
+*     The ID value associated with the new XmlChan.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstXmlChan *new;             /* Pointer to new XmlChan */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the XmlChan, allocating memory and initialising the
+   virtual function table as well if necessary. This interface is for
+   use by external C functions and uses the standard "wrapper"
+   functions included in this class. */
+   new = astInitXmlChan( NULL, sizeof( AstXmlChan ), !class_init,
+                         &class_vtab, "XmlChan", source, SourceWrap,
+                         sink, SinkWrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   XmlChan's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new XmlChan. */
+   return astMakeId( new );
+}
+
+AstXmlChan *astXmlChanForId_( const char *(* source)( void ),
+                              char *(* source_wrap)( const char *(*)( void ), int * ),
+                              void (* sink)( const char * ),
+                              void (* sink_wrap)( void (*)( const char * ),
+                                                  const char *, int * ),
+                              const char *options, ... ) {
+/*
+*+
+*  Name:
+*     astXmlChanFor
+
+*  Purpose:
+*     Initialise an XmlChan from a foreign language interface.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstXmlChan *astXmlChanFor( const char *(* source)( void ),
+*                                char *(* source_wrap)( const char *(*)
+*                                                       ( void ), int * ),
+*                                void (* sink)( const char * ),
+*                                void (* sink_wrap)( void (*)( const char * ),
+*                                                    const char *, int * ),
+*                                const char *options, ... )
+
+*  Class Membership:
+*     XmlChan constructor.
+
+*  Description:
+*     This function creates a new XmlChan from a foreign language
+*     interface and optionally initialises its attributes.
+*
+*     A XmlChan implements low-level XML input/output for the AST library.
+*     Writing an Object to an XmlChan (using astWrite) will generate a
+*     XML representation of that Object, and reading from a
+*     XmlChan (using astRead) will create a new Object from its
+*     XML representation.
+*
+*     Normally, when you use an XmlChan, you should provide "source"
+*     and "sink" functions which connect it to an external data store
+*     by reading and writing the resulting text. This function also
+*     requires you to provide "wrapper" functions which will invoke
+*     the source and sink functions. By default, however, an XmlChan
+*     will read from standard input and write to standard output.
+
+*  Parameters:
+*     source
+*        Pointer to a "source" function which will be used to obtain
+*        lines of input text. Generally, this will be obtained by
+*        casting a pointer to a source function which is compatible
+*        with the "source_wrap" wrapper function (below). The pointer
+*        should later be cast back to its original type by the
+*        "source_wrap" function before the function is invoked.
+*
+*        If "source" is NULL, the XmlChan will read from standard
+*        input instead.
+*     source_wrap
+*        Pointer to a function which can be used to invoke the
+*        "source" function supplied (above). This wrapper function is
+*        necessary in order to hide variations in the nature of the
+*        source function, such as may arise when it is supplied by a
+*        foreign (non-C) language interface.
+*
+*        The single parameter of the "source_wrap" function is a
+*        pointer to the "source" function, and it should cast this
+*        function pointer (as necessary) and invoke the function with
+*        appropriate arguments to obtain the next line of input
+*        text. The "source_wrap" function should then return a pointer
+*        to a dynamically allocated, null terminated string containing
+*        the text that was read. The string will be freed (using
+*        astFree) when no longer required and the "source_wrap"
+*        function need not concern itself with this. A NULL pointer
+*        should be returned if there is no more input to read.
+*
+*        If "source_wrap" is NULL, the XmlChan will read from standard
+*        input instead.
+*     sink
+*        Pointer to a "sink" function which will be used to deliver
+*        lines of output text. Generally, this will be obtained by
+*        casting a pointer to a sink function which is compatible with
+*        the "sink_wrap" wrapper function (below). The pointer should
+*        later be cast back to its original type by the "sink_wrap"
+*        function before the function is invoked.
+*
+*        If "sink" is NULL, the XmlChan will write to standard output
+*        instead.
+*     sink_wrap
+*        Pointer to a function which can be used to invoke the "sink"
+*        function supplied (above). This wrapper function is necessary
+*        in order to hide variations in the nature of the sink
+*        function, such as may arise when it is supplied by a foreign
+*        (non-C) language interface.
+*
+*        The first parameter of the "sink_wrap" function is a pointer
+*        to the "sink" function, and the second parameter is a pointer
+*        to a const, null-terminated character string containing the
+*        text to be written.  The "sink_wrap" function should cast the
+*        "sink" function pointer (as necessary) and invoke the
+*        function with appropriate arguments to deliver the line of
+*        output text. The "sink_wrap" function then returns void.
+*
+*        If "sink_wrap" is NULL, the Channel will write to standard
+*        output instead.
+*     options
+*        Pointer to a null-terminated string containing an optional
+*        comma-separated list of attribute assignments to be used for
+*        initialising the new XmlChan. The syntax used is identical to
+*        that for the astSet function and may include "printf" format
+*        specifiers identified by "%" symbols in the normal way.
+*     ...
+*        If the "options" string contains "%" format specifiers, then
+*        an optional list of additional arguments may follow it in
+*        order to supply values to be substituted for these
+*        specifiers. The rules for supplying these are identical to
+*        those for the astSet function (and for the C "printf"
+*        function).
+
+*  Returned Value:
+*     astXmlChanFor()
+*        A pointer to the new XmlChan.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+*     function is invoked with the global error status set, or if it
+*     should fail for any reason.
+*     - This function is only available through the public interface
+*     to the XmlChan class (not the protected interface) and is
+*     intended solely for use in implementing foreign language
+*     interfaces to this class.
+*-
+
+*  Implememtation Notes:
+*     - This function behaves exactly like astXmlChanId_, in that it
+*     returns ID values and not true C pointers, but it has two
+*     additional arguments. These are pointers to the "wrapper
+*     functions" which are needed to accommodate foreign language
+*     interfaces.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstXmlChan *new;              /* Pointer to new XmlChan */
+   va_list args;                 /* Variable argument list */
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialise the XmlChan, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitXmlChan( NULL, sizeof( AstXmlChan ), !class_init,
+                         &class_vtab, "XmlChan", source, source_wrap,
+                         sink, sink_wrap );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the
+   options string to the astVSet method to initialise the new
+   XmlChan's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new XmlChan. */
+   return astMakeId( new );
+}
+
+AstXmlChan *astInitXmlChan_( void *mem, size_t size, int init,
+                             AstXmlChanVtab *vtab, const char *name,
+                             const char *(* source)( void ),
+                             char *(* source_wrap)( const char *(*)( void ), int * ),
+                             void (* sink)( const char * ),
+                             void (* sink_wrap)( void (*)( const char * ),
+                                                 const char *, int * ), int *status ) {
+/*
+*+
+*  Name:
+*     astInitXmlChan
+
+*  Purpose:
+*     Initialise an XmlChan.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstXmlChan *astInitXmlChan( void *mem, size_t size, int init,
+*                                 AstXmlChanVtab *vtab, const char *name,
+*                                 const char *(* source)( void ),
+*                                 char *(* source_wrap)( const char *(*)( void ), int * ),
+*                                 void (* sink)( const char * ),
+*                                 void (* sink_wrap)( void (*)( const char * ),
+*                                                     const char *, int * ) )
+
+*  Class Membership:
+*     XmlChan initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to
+*     initialise a new XmlChan object. It allocates memory (if
+*     necessary) to accommodate the XmlChan plus any additional data
+*     associated with the derived class.  It then initialises a
+*     XmlChan structure at the start of this memory. If the "init"
+*     flag is set, it also initialises the contents of a virtual
+*     function table for an XmlChan at the start of the memory passed
+*     via the "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the XmlChan is to be
+*        initialised.  This must be of sufficient size to accommodate
+*        the XmlChan data (sizeof(XmlChan)) plus any data used by the
+*        derived class. If a value of NULL is given, this function
+*        will allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the XmlChan (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the XmlChan structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*     init
+*        A boolean flag indicating if the XmlChan's virtual function
+*        table is to be initialised. If this value is non-zero, the
+*        virtual function table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new XmlChan.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*     source
+*        Pointer to a "source" function which will be used to obtain
+*        lines of text. Generally, this will be obtained by
+*        casting a pointer to a source function which is compatible
+*        with the "source_wrap" wrapper function (below). The pointer
+*        should later be cast back to its original type by the
+*        "source_wrap" function before the function is invoked.
+*
+*        If "source" is NULL, the Channel will read from standard
+*        input instead.
+*     source_wrap
+*        Pointer to a function which can be used to invoke the
+*        "source" function supplied (above). This wrapper function is
+*        necessary in order to hide variations in the nature of the
+*        source function, such as may arise when it is supplied by a
+*        foreign (non-C) language interface.
+*
+*        The single parameter of the "source_wrap" function is a
+*        pointer to the "source" function, and it should cast this
+*        function pointer (as necessary) and invoke the function with
+*        appropriate arguments to obtain the next line of input
+*        text. The "source_wrap" function should then return a pointer
+*        to a dynamically allocated, null terminated string containing
+*        the text that was read. The string will be freed (using
+*        astFree) when no longer required and the "source_wrap"
+*        function need not concern itself with this. A NULL pointer
+*        should be returned if there is no more input to read.
+*
+*        If "source_wrap" is NULL, the Channel will read from standard
+*        input instead.
+*     sink
+*        Pointer to a "sink" function which will be used to deliver
+*        lines of text. Generally, this will be obtained by
+*        casting a pointer to a sink function which is compatible with
+*        the "sink_wrap" wrapper function (below). The pointer should
+*        later be cast back to its original type by the "sink_wrap"
+*        function before the function is invoked.
+*
+*        If "sink" is NULL, the contents of the XmlChan will not be
+*        written out before being deleted.
+*     sink_wrap
+*        Pointer to a function which can be used to invoke the "sink"
+*        function supplied (above). This wrapper function is necessary
+*        in order to hide variations in the nature of the sink
+*        function, such as may arise when it is supplied by a foreign
+*        (non-C) language interface.
+*
+*        The first parameter of the "sink_wrap" function is a pointer
+*        to the "sink" function, and the second parameter is a pointer
+*        to a const, null-terminated character string containing the
+*        text to be written.  The "sink_wrap" function should cast the
+*        "sink" function pointer (as necessary) and invoke the
+*        function with appropriate arguments to deliver the line of
+*        output text. The "sink_wrap" function then returns void.
+*
+*        If "sink_wrap" is NULL, the Channel will write to standard
+*        output instead.
+
+*  Returned Value:
+*     A pointer to the new XmlChan.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   AstXmlChan *new;              /* Pointer to new XmlChan */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitXmlChanVtab( vtab, name );
+
+/* Initialise a Channel structure (the parent class) as the first
+   component within the XmlChan structure, allocating memory if
+   necessary. */
+   new = (AstXmlChan *) astInitChannel( mem, size, 0,
+                                       (AstChannelVtab *) vtab, name,
+                                        source, source_wrap, sink,
+                                        sink_wrap );
+
+   if ( astOK ) {
+
+/* Initialise the XmlChan data. */
+/* ---------------------------- */
+      new->objectname = NULL;   /* Name of object being written */
+      new->objectset = 1;       /* Is object being written set?*/
+      new->objectcomment = NULL;/* Comment for object class being written */
+      new->container = NULL;    /* XmlElement to which content will be added */
+      new->readcontext = NULL;  /* XmlElement giving context for current read */
+      new->write_isa = 0;       /* Write out the next "IsA" item? */
+      new->xmllength = -INT_MAX;/* Buffer length */
+      new->xmlprefix = NULL;    /* Xml prefix */
+      new->xmlformat = UNKNOWN_FORMAT; /* Xml format */
+      new->formatdef = NATIVE_FORMAT;  /* Default Xml format */
+      new->reset_source = 1;    /* A new line should be read from the source */
+      new->isa_class = NULL;    /* Class being loaded */
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new object. */
+   return new;
+}
+
+AstXmlChan *astLoadXmlChan_( void *mem, size_t size,
+                             AstXmlChanVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadXmlChan
+
+*  Purpose:
+*     Load an XmlChan.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "xmlchan.h"
+*     AstXmlChan *astLoadXmlChan( void *mem, size_t size,
+*                                 AstXmlChanVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     XmlChan loader.
+
+*  Description:
+*     This function is provided to load a new XmlChan using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     XmlChan structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for an XmlChan at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the XmlChan is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        XmlChan data (sizeof(XmlChan)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the XmlChan (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the XmlChan structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstXmlChan) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new XmlChan. If this is NULL, a pointer
+*        to the (static) virtual function table for the XmlChan class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "XmlChan" is used instead.
+
+*  Returned Value:
+*     A pointer to the new XmlChan.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstXmlChan *new;            /* Pointer to the new XmlChan */
+   char *text;                 /* Textual version of integer value */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this XmlChan. In this case the
+   XmlChan belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstXmlChan );
+      vtab = &class_vtab;
+      name = "XmlChan";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitXmlChanVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built XmlChan. */
+   new = astLoadChannel( mem, size, (AstChannelVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "XmlChan" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* Ensure other items in the XmlChan structure are initialised properly. */
+      new->objectname = NULL;   /* Name of object being written */
+      new->objectset = 1;       /* Is object being written set?*/
+      new->objectcomment = NULL;/* Comment for object class being written */
+      new->container = NULL;    /* XmlElement to which content will be added */
+      new->readcontext = NULL;  /* XmlElement giving context for current read */
+      new->write_isa = 0;       /* Write out the next "IsA" item? */
+      new->xmllength = -INT_MAX;/* Buffer length */
+      new->xmlprefix = NULL;    /* Xml prefix */
+      new->reset_source = 1;    /* A new line should be read from the source */
+      new->isa_class = NULL;    /* Class being loaded */
+      new->formatdef = NATIVE_FORMAT;  /* Default Xml format */
+
+/* Now restore presistent values. */
+
+/* XmlLength */
+/* --------- */
+      new->xmllength = astReadInt( channel, "xmllen", -INT_MAX );
+
+/* XmlPrefix */
+/* --------- */
+      new->xmlprefix = astReadString( channel, "xmlprf", NULL );
+
+/* XmlFormat. */
+/* --------- */
+      text = astReadString( channel, "xmlfmt", UNKNOWN_STRING );
+      if( strcmp( text, UNKNOWN_STRING ) ) {
+         new->xmlformat = FindString( MAX_FORMAT + 1, xformat, text,
+                                     "the XmlChan component 'XmlFmt'",
+                                     "astRead", astGetClass( channel ), status );
+      } else {
+         new->xmlformat = UNKNOWN_FORMAT;
+      }
+      if ( TestXmlFormat( new, status ) ) SetXmlFormat( new, new->xmlformat, status );
+      text = astFree( text );
+   }
+
+/* If an error occurred, clean up by deleting the new XmlChan. */
+   if ( !astOK ) new = astDelete( new );
+
+/* Return the new XmlChan pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ast/xmlchan.h b/ast/xmlchan.h
new file mode 100644
index 0000000..87559b8
--- /dev/null
+++ b/ast/xmlchan.h
@@ -0,0 +1,300 @@
+#if !defined( XMLCHAN_INCLUDED ) /* Include this file only once */
+#define XMLCHAN_INCLUDED
+/*
+*+
+*  Name:
+*     xmlchan.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the XmlChan class.
+
+*  Invocation:
+*     #include "xmlchan.h"
+
+*  Description:
+*     This include file defines the interface to the XmlChan class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The XmlChan class provides facilities for reading and writing AST
+*     Objects in the form of XML.
+
+*  Inheritance:
+*     The XmlChan class inherits from the Channel class.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+*     Copyright (C) 2009 Science & Technology Facilities Council.
+*     All Rights Reserved.
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     DSB: D.S. Berry (Starlink)
+
+*  History:
+*     10-OCT-2003 (DSB):
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "channel.h"             /* I/O channels (parent class) */
+#include "keymap.h"              /* Mappings of keys to values */
+#include "xml.h"                 /* AST XML facilities */
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros. */
+/* ------- */
+
+#if defined(astCLASS) || defined(astFORTRAN77)
+#define STATUS_PTR status
+#else
+#define STATUS_PTR astGetStatusPtr
+#endif
+
+/* URI defining the starlink AST XML namespace */
+#define AST__XMLNS  "http://www.starlink.ac.uk/ast/xml/"
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+
+/* XmlChan structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstXmlChan {
+
+/* Attributes inherited from the parent class. */
+   AstChannel channel;             /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   const char *objectname;     /* Name of Object currently being written. */
+   const char *objectcomment;  /* Comment for Object currently being written. */
+   int objectset;              /* Is the Object currently being written set? */
+   AstXmlParent *container;    /* XmlParent to which content will be added */
+   AstXmlDocument *readcontext;/* XmlDocument giving context for current read */
+   int write_isa;              /* Is the next "isA" really needed? */
+   int xmllength;              /* Buffer length */
+   int xmlformat;              /* Output format to use when writing */
+   int formatdef;              /* Default format */
+   char *xmlprefix;            /* Namespace prefix */
+   int reset_source;           /* Read a new line from the source ? */
+   const char *isa_class;      /* Class being loaded */
+} AstXmlChan;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstXmlChanVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstChannelVtab channel_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   int (* GetXmlLength)( AstXmlChan *, int * );
+   int (* TestXmlLength)( AstXmlChan *, int * );
+   void (* ClearXmlLength)( AstXmlChan *, int * );
+   void (* SetXmlLength)( AstXmlChan *, int, int * );
+
+   int (* GetXmlFormat)( AstXmlChan *, int * );
+   int (* TestXmlFormat)( AstXmlChan *, int * );
+   void (* ClearXmlFormat)( AstXmlChan *, int * );
+   void (* SetXmlFormat)( AstXmlChan *, int, int * );
+
+   const char * (* GetXmlPrefix)( AstXmlChan *, int * );
+   int (* TestXmlPrefix)( AstXmlChan *, int * );
+   void (* ClearXmlPrefix)( AstXmlChan *, int * );
+   void (* SetXmlPrefix)( AstXmlChan *, const char *, int * );
+
+} AstXmlChanVtab;
+
+#if defined(THREAD_SAFE)
+typedef struct AstXmlChanGlobals {
+   AstXmlChanVtab Class_Vtab;
+   int Class_Init;
+   AstXmlChan *IsUsable_This;
+   char GetAttrib_Buff[ 51 ];
+   char *GetNextChar_C;
+   char *GetNextChar_Buf;
+} AstXmlChanGlobals;
+
+#endif
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(XmlChan)          /* Check class membership */
+astPROTO_ISA(XmlChan)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstXmlChan *astXmlChan_( const char *(*)( void ), void (*)( const char * ),
+                          const char *, int *, ...);
+#else
+AstXmlChan *astXmlChanId_( const char *(*)( void ), void (*)( const char * ),
+                            const char *, ... )__attribute__((format(printf,3,4)));
+AstXmlChan *astXmlChanForId_( const char *(*)( void ),
+                              char *(*)( const char *(*)( void ), int * ),
+                              void (*)( const char * ),
+                              void (*)( void (*)( const char * ),
+                                        const char *, int * ),
+                              const char *, ... );
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstXmlChan *astInitXmlChan_( void *, size_t, int, AstXmlChanVtab *,
+                             const char *, const char *(*)( void ),
+                             char *(*)( const char *(*)( void ), int * ),
+                             void (*)( const char * ),
+                             void (*)( void (*)( const char * ),
+                             const char *, int * ), int * );
+
+/* Vtab initialiser. */
+void astInitXmlChanVtab_( AstXmlChanVtab *, const char *, int * );
+
+
+
+/* Loader. */
+AstXmlChan *astLoadXmlChan_( void *, size_t, AstXmlChanVtab *,
+                               const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitXmlChanGlobals_( AstXmlChanGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+int astGetXmlLength_( AstXmlChan *, int * );
+int astTestXmlLength_( AstXmlChan *, int * );
+void astClearXmlLength_( AstXmlChan *, int * );
+void astSetXmlLength_( AstXmlChan *, int, int * );
+
+int astGetXmlFormat_( AstXmlChan *, int * );
+int astTestXmlFormat_( AstXmlChan *, int * );
+void astClearXmlFormat_( AstXmlChan *, int * );
+void astSetXmlFormat_( AstXmlChan *, int, int * );
+
+const char * astGetXmlPrefix_( AstXmlChan *, int * );
+int astTestXmlPrefix_( AstXmlChan *, int * );
+void astClearXmlPrefix_( AstXmlChan *, int * );
+void astSetXmlPrefix_( AstXmlChan *, const char *, int * );
+
+
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckXmlChan(this) astINVOKE_CHECK(XmlChan,this,0)
+#define astVerifyXmlChan(this) astINVOKE_CHECK(XmlChan,this,1)
+
+/* Test class membership. */
+#define astIsAXmlChan(this) astINVOKE_ISA(XmlChan,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astXmlChan astINVOKE(F,astXmlChan_)
+#else
+#define astXmlChan astINVOKE(F,astXmlChanId_)
+#define astXmlChanFor astINVOKE(F,astXmlChanForId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitXmlChan(mem,size,init,vtab,name,source,source_wrap,sink,sink_wrap) \
+astINVOKE(O,astInitXmlChan_(mem,size,init,vtab,name,source,source_wrap,sink,sink_wrap,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitXmlChanVtab(vtab,name) astINVOKE(V,astInitXmlChanVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadXmlChan(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadXmlChan_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckXmlChan to validate XmlChan pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+
+#define astClearXmlLength(this) astINVOKE(V,astClearXmlLength_(astCheckXmlChan(this),STATUS_PTR))
+#define astGetXmlLength(this) astINVOKE(V,astGetXmlLength_(astCheckXmlChan(this),STATUS_PTR))
+#define astSetXmlLength(this,xmllength) astINVOKE(V,astSetXmlLength_(astCheckXmlChan(this),xmllength,STATUS_PTR))
+#define astTestXmlLength(this) astINVOKE(V,astTestXmlLength_(astCheckXmlChan(this),STATUS_PTR))
+
+#define astClearXmlFormat(this) astINVOKE(V,astClearXmlFormat_(astCheckXmlChan(this),STATUS_PTR))
+#define astGetXmlFormat(this) astINVOKE(V,astGetXmlFormat_(astCheckXmlChan(this),STATUS_PTR))
+#define astSetXmlFormat(this,xmlformat) astINVOKE(V,astSetXmlFormat_(astCheckXmlChan(this),xmlformat,STATUS_PTR))
+#define astTestXmlFormat(this) astINVOKE(V,astTestXmlFormat_(astCheckXmlChan(this),STATUS_PTR))
+
+#define astClearXmlPrefix(this) astINVOKE(V,astClearXmlPrefix_(astCheckXmlChan(this),STATUS_PTR))
+#define astGetXmlPrefix(this) astINVOKE(V,astGetXmlPrefix_(astCheckXmlChan(this),STATUS_PTR))
+#define astSetXmlPrefix(this,xmlpref) astINVOKE(V,astSetXmlPrefix_(astCheckXmlChan(this),xmlpref,STATUS_PTR))
+#define astTestXmlPrefix(this) astINVOKE(V,astTestXmlPrefix_(astCheckXmlChan(this),STATUS_PTR))
+
+#endif
+
+#endif
+
+
+
+
+
diff --git a/ast/zoommap.c b/ast/zoommap.c
new file mode 100644
index 0000000..44177d1
--- /dev/null
+++ b/ast/zoommap.c
@@ -0,0 +1,2074 @@
+/*
+*class++
+*  Name:
+*     ZoomMap
+
+*  Purpose:
+*     Zoom coordinates about the origin.
+
+*  Constructor Function:
+c     astZoomMap
+f     AST_ZOOMMAP
+
+*  Description:
+*     The ZoomMap class implements a Mapping which performs a "zoom"
+*     transformation by multiplying all coordinate values by the same
+*     scale factor (the inverse transformation is performed by
+*     dividing by this scale factor). The number of coordinate values
+*     representing each point is unchanged.
+
+*  Inheritance:
+*     The ZoomMap class inherits from the Mapping class.
+
+*  Attributes:
+*     In addition to those attributes common to all Mappings, every
+*     ZoomMap also has the following attributes:
+*
+*     - Zoom: ZoomMap scale factor
+
+*  Functions:
+c     The ZoomMap class does not define any new functions beyond those
+f     The ZoomMap class does not define any new routines beyond those
+*     which are applicable to all Mappings.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+*     DSB: David S. Berry (Starlink)
+
+*  History:
+*     1-FEB-1996 (RFWS):
+*        Original version.
+*     18-JUL-1996 (RFWS):
+*        Updated to support attributes and an external interface.
+*     10-SEP-1996 (RFWS):
+*        Added I/O facilities.
+*     4-JUN-1997 (RFWS):
+*        Over-ride the MapMerge method to provide ZoomMap
+*        simplification facilities.
+*     8-JAN-2003 (DSB):
+*        Changed private InitVtab method to protected astInitZoomMapVtab
+*        method.
+*     10-MAY-2006 (DSB):
+*        Override astEqual.
+*class--
+*/
+
+/* Module Macros. */
+/* ============== */
+/* Set the name of the class we are implementing. This indicates to
+   the header files that define class interfaces that they should make
+   "protected" symbols available. */
+#define astCLASS ZoomMap
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+
+#include "globals.h"             /* Thread-safe global data access */
+#include "error.h"               /* Error reporting facilities */
+#include "memory.h"              /* Memory allocation facilities */
+#include "globals.h"             /* Thread-safe global data access */
+#include "object.h"              /* Base Object class */
+#include "pointset.h"            /* Sets of points/coordinates */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+#include "channel.h"             /* I/O channels */
+#include "unitmap.h"             /* Unit Mappings */
+#include "matrixmap.h"           /* Matrix Mappings */
+#include "zoommap.h"             /* Interface definition for this class */
+
+/* Error code definitions. */
+/* ----------------------- */
+#include "ast_err.h"             /* AST error codes */
+
+/* C header files. */
+/* --------------- */
+#include <float.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Module Variables. */
+/* ================= */
+
+/* Address of this static variable is used as a unique identifier for
+   member of this class. */
+static int class_check;
+
+/* Pointers to parent class methods which are extended by this class. */
+static AstPointSet *(* parent_transform)( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *(* parent_getattrib)( AstObject *, const char *, int * );
+static int (* parent_testattrib)( AstObject *, const char *, int * );
+static void (* parent_clearattrib)( AstObject *, const char *, int * );
+static void (* parent_setattrib)( AstObject *, const char *, int * );
+
+/* Define macros for accessing each item of thread specific global data. */
+#ifdef THREAD_SAFE
+
+/* Define how to initialise thread-specific globals. */
+#define GLOBAL_inits \
+   globals->Class_Init = 0; \
+   globals->GetAttrib_Buff[ 0 ] = 0;
+
+/* Create the function that initialises global data for this module. */
+astMAKE_INITGLOBALS(ZoomMap)
+
+/* Define macros for accessing each item of thread specific global data. */
+#define class_init astGLOBAL(ZoomMap,Class_Init)
+#define class_vtab astGLOBAL(ZoomMap,Class_Vtab)
+#define getattrib_buff astGLOBAL(ZoomMap,GetAttrib_Buff)
+
+
+
+/* If thread safety is not needed, declare and initialise globals at static
+   variables. */
+#else
+
+static char getattrib_buff[ 101 ];
+
+
+/* Define the class virtual function table and its initialisation flag
+   as static variables. */
+static AstZoomMapVtab class_vtab;   /* Virtual function table */
+static int class_init = 0;       /* Virtual function table initialised? */
+
+#endif
+
+/* External Interface Function Prototypes. */
+/* ======================================= */
+/* The following functions have public prototypes only (i.e. no
+   protected prototypes), so we must provide local prototypes for use
+   within this module. */
+AstZoomMap *astZoomMapId_( int, double, const char *, ... );
+
+/* Prototypes for Private Member Functions. */
+/* ======================================== */
+static AstPointSet *Transform( AstMapping *, AstPointSet *, int, AstPointSet *, int * );
+static const char *GetAttrib( AstObject *, const char *, int * );static double GetZoom( AstZoomMap *, int * );
+static double Rate( AstMapping *, double *, int, int, int * );
+static int *MapSplit( AstMapping *, int, const int *, AstMapping **, int * );
+static int Equal( AstObject *, AstObject *, int * );
+static int GetIsLinear( AstMapping *, int * );
+static int MapMerge( AstMapping *, int, int, int *, AstMapping ***, int **, int * );
+static int TestAttrib( AstObject *, const char *, int * );
+static int TestZoom( AstZoomMap *, int * );
+static void ClearAttrib( AstObject *, const char *, int * );
+static void ClearZoom( AstZoomMap *, int * );
+static void Dump( AstObject *, AstChannel *, int * );
+static void SetAttrib( AstObject *, const char *, int * );
+static void SetZoom( AstZoomMap *, double, int * );
+
+/* Member functions. */
+/* ================= */
+static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     ClearAttrib
+
+*  Purpose:
+*     Clear an attribute value for a ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     void ClearAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     ZoomMap member function (over-rides the astClearAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function clears the value of a specified attribute for a
+*     ZoomMap, so that the default value will subsequently be used.
+
+*  Parameters:
+*     this
+*        Pointer to the ZoomMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstZoomMap *this;             /* Pointer to the ZoomMap structure */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the ZoomMap structure. */
+   this = (AstZoomMap *) this_object;
+
+/* Check the attribute name and clear the appropriate attribute. */
+
+/* Zoom. */
+/* ----- */
+   if ( !strcmp( attrib, "zoom" ) ) {
+      astClearZoom( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_clearattrib)( this_object, attrib, status );
+   }
+}
+
+static int Equal( AstObject *this_object, AstObject *that_object, int *status ) {
+/*
+*  Name:
+*     Equal
+
+*  Purpose:
+*     Test if two ZoomMaps are equivalent.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     int Equal( AstObject *this, AstObject *that, int *status )
+
+*  Class Membership:
+*     ZoomMap member function (over-rides the astEqual protected
+*     method inherited from the astMapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     two ZoomMaps are equivalent.
+
+*  Parameters:
+*     this
+*        Pointer to the first Object (a ZoomMap).
+*     that
+*        Pointer to the second Object.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if the ZoomMaps are equivalent, zero otherwise.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstZoomMap *that;
+   AstZoomMap *this;
+   int nin;
+   int nout;
+   int result;
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain pointers to the two ZoomMap structures. */
+   this = (AstZoomMap *) this_object;
+   that = (AstZoomMap *) that_object;
+
+/* Check the second object is a ZoomMap. We know the first is a
+   ZoomMap since we have arrived at this implementation of the virtual
+   function. */
+   if( astIsAZoomMap( that ) ) {
+
+/* Get the number of inputs and outputs and check they are the same for both. */
+      nin = astGetNin( this );
+      nout = astGetNout( this );
+      if( astGetNin( that ) == nin && astGetNout( that ) == nout ) {
+
+/* If the Invert flags for the two ZoomMaps differ, it may still be possible
+   for them to be equivalent. First compare the ZoomMaps if their Invert
+   flags are the same. In this case all the attributes of the two ZoomMaps
+   must be identical. */
+         if( astGetInvert( this ) == astGetInvert( that ) ) {
+            result = ( astEQUAL( this->zoom, that->zoom ) );
+
+/* If the Invert flags for the two ZoomMaps differ, the attributes of the two
+   ZoomMaps must be inversely related to each other. */
+         } else {
+            result = ( astEQUAL( this->zoom, 1.0/that->zoom ) );
+
+         }
+      }
+   }
+
+/* If an error occurred, clear the result value. */
+   if ( !astOK ) result = 0;
+
+/* Return the result, */
+   return result;
+}
+
+static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     GetAttrib
+
+*  Purpose:
+*     Get the value of a specified attribute for a ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     ZoomMap member function (over-rides the protected astGetAttrib
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a pointer to the value of a specified
+*     attribute for a ZoomMap, formatted as a character string.
+
+*  Parameters:
+*     this
+*        Pointer to the ZoomMap.
+*     attrib
+*        Pointer to a null-terminated string containing the name of
+*        the attribute whose value is required. This name should be in
+*        lower case, with all white space removed.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     - Pointer to a null-terminated string containing the attribute
+*     value.
+
+*  Notes:
+*     - The returned string pointer may point at memory allocated
+*     within the ZoomMap, or at static memory. The contents of the
+*     string may be over-written or the pointer may become invalid
+*     following a further invocation of the same function or any
+*     modification of the ZoomMap. A copy of the string should
+*     therefore be made if necessary.
+*     - A NULL pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS           /* Pointer to thread-specific global data */
+   AstZoomMap *this;             /* Pointer to the ZoomMap structure */
+   const char *result;           /* Pointer value to return */
+   double zoom;                  /* Zoom attribute value */
+
+/* Initialise. */
+   result = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(this_object);
+
+/* Obtain a pointer to the ZoomMap structure. */
+   this = (AstZoomMap *) this_object;
+
+/* Compare "attrib" with each recognised attribute name in turn,
+   obtaining the value of the required attribute. If necessary, write
+   the value into "getattrib_buff" as a null-terminated string in an appropriate
+   format.  Set "result" to point at the result string. */
+
+/* Zoom. */
+/* ----- */
+   if ( !strcmp( attrib, "zoom" ) ) {
+      zoom = astGetZoom( this );
+      if ( astOK ) {
+         (void) sprintf( getattrib_buff, "%.*g", DBL_DIG, zoom );
+         result = getattrib_buff;
+      }
+
+/* If the attribute name was not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_getattrib)( this_object, attrib, status );
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static int GetIsLinear( AstMapping *this_mapping, int *status ){
+/*
+*  Name:
+*     GetIsLinear
+
+*  Purpose:
+*     Return the value of the IsLinear attribute for a ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     void GetIsLinear( AstMapping *this, int *status )
+
+*  Class Membership:
+*     ZoomMap member function (over-rides the protected astGetIsLinear
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns the value of the IsLinear attribute for a
+*     Frame, which is always one.
+
+*  Parameters:
+*     this
+*        Pointer to the ZoomMap.
+*     status
+*        Pointer to the inherited status variable.
+*/
+   return 1;
+}
+
+void astInitZoomMapVtab_(  AstZoomMapVtab *vtab, const char *name, int *status ) {
+/*
+*+
+*  Name:
+*     astInitZoomMapVtab
+
+*  Purpose:
+*     Initialise a virtual function table for a ZoomMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     void astInitZoomMapVtab( AstZoomMapVtab *vtab, const char *name )
+
+*  Class Membership:
+*     ZoomMap vtab initialiser.
+
+*  Description:
+*     This function initialises the component of a virtual function
+*     table which is used by the ZoomMap class.
+
+*  Parameters:
+*     vtab
+*        Pointer to the virtual function table. The components used by
+*        all ancestral classes will be initialised if they have not already
+*        been initialised.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the virtual function table belongs (it
+*        is this pointer value that will subsequently be returned by the Object
+*        astClass function).
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstObjectVtab *object;        /* Pointer to Object component of Vtab */
+   AstMappingVtab *mapping;      /* Pointer to Mapping component of Vtab */
+
+/* Check the local error status. */
+   if ( !astOK ) return;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Initialize the component of the virtual function table used by the
+   parent class. */
+   astInitMappingVtab( (AstMappingVtab *) vtab, name );
+
+/* Store a unique "magic" value in the virtual function table. This
+   will be used (by astIsAZoomMap) to determine if an object belongs
+   to this class.  We can conveniently use the address of the (static)
+   class_check variable to generate this unique value. */
+   vtab->id.check = &class_check;
+   vtab->id.parent = &(((AstMappingVtab *) vtab)->id);
+
+/* Initialise member function pointers. */
+/* ------------------------------------ */
+/* Store pointers to the member functions (implemented here) that provide
+   virtual methods for this class. */
+   vtab->ClearZoom = ClearZoom;
+   vtab->GetZoom = GetZoom;
+   vtab->SetZoom = SetZoom;
+   vtab->TestZoom = TestZoom;
+
+/* Save the inherited pointers to methods that will be extended, and
+   replace them with pointers to the new member functions. */
+   object = (AstObjectVtab *) vtab;
+   mapping = (AstMappingVtab *) vtab;
+
+   parent_clearattrib = object->ClearAttrib;
+   object->ClearAttrib = ClearAttrib;
+   parent_getattrib = object->GetAttrib;
+   object->GetAttrib = GetAttrib;
+   parent_setattrib = object->SetAttrib;
+   object->SetAttrib = SetAttrib;
+   parent_testattrib = object->TestAttrib;
+   object->TestAttrib = TestAttrib;
+
+   parent_transform = mapping->Transform;
+   mapping->Transform = Transform;
+
+/* Store replacement pointers for methods which will be over-ridden by
+   new member functions implemented here. */
+   object->Equal = Equal;
+   mapping->MapMerge = MapMerge;
+   mapping->MapSplit = MapSplit;
+   mapping->Rate = Rate;
+   mapping->GetIsLinear = GetIsLinear;
+
+/* Declare the class dump function. There is no copy constructor or
+   destructor. */
+   astSetDump( vtab, Dump, "ZoomMap", "Zoom about the origin" );
+
+/* If we have just initialised the vtab for the current class, indicate
+   that the vtab is now initialised, and store a pointer to the class
+   identifier in the base "object" level of the vtab. */
+   if( vtab == &class_vtab ) {
+      class_init = 1;
+      astSetVtabClassIdentifier( vtab, &(vtab->id) );
+   }
+}
+
+static int MapMerge( AstMapping *this, int where, int series, int *nmap,
+                     AstMapping ***map_list, int **invert_list, int *status ) {
+/*
+*  Name:
+*     MapMerge
+
+*  Purpose:
+*     Simplify a sequence of Mappings containing a ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "mapping.h"
+*     int MapMerge( AstMapping *this, int where, int series, int *nmap,
+*                   AstMapping ***map_list, int **invert_list, int *status )
+
+*  Class Membership:
+*     ZoomMap method (over-rides the protected astMapMerge method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function attempts to simplify a sequence of Mappings by
+*     merging a nominated ZoomMap in the sequence with its neighbours,
+*     so as to shorten the sequence if possible.
+*
+*     In many cases, simplification will not be possible and the
+*     function will return -1 to indicate this, without further
+*     action.
+*
+*     In most cases of interest, however, this function will either
+*     attempt to replace the nominated ZoomMap with one which it
+*     considers simpler, or to merge it with the Mappings which
+*     immediately precede it or follow it in the sequence (both will
+*     normally be considered). This is sufficient to ensure the
+*     eventual simplification of most Mapping sequences by repeated
+*     application of this function.
+*
+*     In some cases, the function may attempt more elaborate
+*     simplification, involving any number of other Mappings in the
+*     sequence. It is not restricted in the type or scope of
+*     simplification it may perform, but will normally only attempt
+*     elaborate simplification in cases where a more straightforward
+*     approach is not adequate.
+
+*  Parameters:
+*     this
+*        Pointer to the nominated ZoomMap which is to be merged with
+*        its neighbours. This should be a cloned copy of the ZoomMap
+*        pointer contained in the array element "(*map_list)[where]"
+*        (see below). This pointer will not be annulled, and the
+*        ZoomMap it identifies will not be modified by this function.
+*     where
+*        Index in the "*map_list" array (below) at which the pointer
+*        to the nominated ZoomMap resides.
+*     series
+*        A non-zero value indicates that the sequence of Mappings to
+*        be simplified will be applied in series (i.e. one after the
+*        other), whereas a zero value indicates that they will be
+*        applied in parallel (i.e. on successive sub-sets of the
+*        input/output coordinates).
+*     nmap
+*        Address of an int which counts the number of Mappings in the
+*        sequence. On entry this should be set to the initial number
+*        of Mappings. On exit it will be updated to record the number
+*        of Mappings remaining after simplification.
+*     map_list
+*        Address of a pointer to a dynamically allocated array of
+*        Mapping pointers (produced, for example, by the astMapList
+*        method) which identifies the sequence of Mappings. On entry,
+*        the initial sequence of Mappings to be simplified should be
+*        supplied.
+*
+*        On exit, the contents of this array will be modified to
+*        reflect any simplification carried out. Any form of
+*        simplification may be performed. This may involve any of: (a)
+*        removing Mappings by annulling any of the pointers supplied,
+*        (b) replacing them with pointers to new Mappings, (c)
+*        inserting additional Mappings and (d) changing their order.
+*
+*        The intention is to reduce the number of Mappings in the
+*        sequence, if possible, and any reduction will be reflected in
+*        the value of "*nmap" returned. However, simplifications which
+*        do not reduce the length of the sequence (but improve its
+*        execution time, for example) may also be performed, and the
+*        sequence might conceivably increase in length (but normally
+*        only in order to split up a Mapping into pieces that can be
+*        more easily merged with their neighbours on subsequent
+*        invocations of this function).
+*
+*        If Mappings are removed from the sequence, any gaps that
+*        remain will be closed up, by moving subsequent Mapping
+*        pointers along in the array, so that vacated elements occur
+*        at the end. If the sequence increases in length, the array
+*        will be extended (and its pointer updated) if necessary to
+*        accommodate any new elements.
+*
+*        Note that any (or all) of the Mapping pointers supplied in
+*        this array may be annulled by this function, but the Mappings
+*        to which they refer are not modified in any way (although
+*        they may, of course, be deleted if the annulled pointer is
+*        the final one).
+*     invert_list
+*        Address of a pointer to a dynamically allocated array which,
+*        on entry, should contain values to be assigned to the Invert
+*        attributes of the Mappings identified in the "*map_list"
+*        array before they are applied (this array might have been
+*        produced, for example, by the astMapList method). These
+*        values will be used by this function instead of the actual
+*        Invert attributes of the Mappings supplied, which are
+*        ignored.
+*
+*        On exit, the contents of this array will be updated to
+*        correspond with the possibly modified contents of the
+*        "*map_list" array.  If the Mapping sequence increases in
+*        length, the "*invert_list" array will be extended (and its
+*        pointer updated) if necessary to accommodate any new
+*        elements.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     If simplification was possible, the function returns the index
+*     in the "map_list" array of the first element which was
+*     modified. Otherwise, it returns -1 (and makes no changes to the
+*     arrays supplied).
+
+*  Notes:
+*     - A value of -1 will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*/
+
+/* Local Variables: */
+   AstMapping *map;              /* Pointer to Mapping */
+   AstMapping *new;              /* Pointer to replacement Mapping */
+   const char *class;            /* Pointer to Mapping class string */
+   double *zooms;                /* Pointer to zoom factor array */
+   double maxzoom;               /* Maximum zoom factor */
+   double minzoom;               /* Minimum zoom factor */
+   double zoom;                  /* Zoom factor */
+   int coord;                    /* Loop counter for coordinates */
+   int imap1;                    /* Index of first ZoomMap */
+   int imap2;                    /* Index of last ZoomMap */
+   int imap;                     /* Loop counter for Mappings */
+   int ncoord;                   /* Number of input/output coordinates */
+   int ngone;                    /* Number of Mappings eliminated */
+   int nin;                      /* Total number of input coordinates */
+   int nzoom;                    /* Number of zoom factors */
+   int result;                   /* Result value to return */
+   int simpler;                  /* Mapping(s) simplified? */
+   int single;                   /* Replacement is a single ZoomMap? */
+   int unit;                     /* Replacement Mapping is a UnitMap? */
+
+/* Initialise the returned result. */
+   result = -1;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Further initialisation. */
+   new = NULL;
+   ngone = 0;
+   simpler = 0;
+
+/* In series. */
+/* ---------- */
+/* Handle the case where the Mappings are connected in series. */
+   if ( series ) {
+
+/* Obtain the effective zoom factor of the nominated Mapping (which is
+   a ZoomMap) when its Invert attribute is set to the value given. */
+      zoom = astGetZoom( ( *map_list )[ where ] );
+      if ( ( *invert_list )[ where ] ) zoom = 1.0 / zoom;
+
+/* Search adjacent lower-numbered Mappings and identify the class to
+   which they belong. */
+      imap1 = where;
+      while ( astOK && ( ( imap1 - 1 ) >= 0 ) ) {
+         map = ( *map_list )[ imap1 - 1 ];
+         class = astGetClass( map );
+         if ( astOK ) {
+
+/* For each ZoomMap found, obtain the effective zoom factor (taking
+   account of the associated invert flag value), and accumulate the
+   overall zoom factor to be used for the simplified Mapping. */
+            if ( !strcmp( class, "ZoomMap" ) ) {
+               if ( ( *invert_list )[ imap1 - 1 ] ) {
+                  zoom /= astGetZoom( map );
+               } else {
+                  zoom *= astGetZoom( map );
+               }
+
+/* UnitMaps have an effective zoom factor of unity, so we include them
+   but they so have no effect. Quit looping when the first Mapping is
+   found which is not a ZoomMap or a UnitMap. */
+            } else if ( strcmp( class, "UnitMap" ) ) {
+               break;
+            }
+            imap1--;
+         }
+      }
+
+/* Similarly search adjacent higher-numbered ZoomMaps and UnitMaps and
+   accumulate their effective zoom factors. */
+      imap2 = where;
+      while ( astOK && ( ( imap2 + 1 ) < *nmap ) ) {
+         map = ( *map_list )[ imap2 + 1 ];
+         class = astGetClass( map );
+         if ( astOK ) {
+            if ( !strcmp( class, "ZoomMap" ) ) {
+               if ( ( *invert_list )[ imap2 + 1 ] ) {
+                  zoom /= astGetZoom( map );
+               } else {
+                  zoom *= astGetZoom( map );
+               }
+            } else if ( strcmp( class, "UnitMap" ) ) {
+               break;
+            }
+            imap2++;
+         }
+      }
+
+/* Determine how many Mappings can be eliminated by condensing those
+   found above into a single Mapping. */
+      ngone = imap2 - imap1;
+
+/* Determine if the replacement Mapping can be a UnitMap. This will be
+   the case only if the accumulated zoom factor is unity (within some
+   tolerable error). */
+      unit = ( fabs( zoom - 1.0 ) <= ( 8.0 * DBL_EPSILON ) );
+
+/* Determine if simplification is possible. This will be so if (a)
+   Mappings can be eliminated ("ngone" is non-zero), or (b) the
+   replacement can be a UnitMap, or (c) where there was initially only
+   one ZoomMap, its invert flag was set (it will always be cleared in
+   the replacement Mapping). */
+      simpler = ngone || unit || ( *invert_list )[ where ];
+
+/* Do nothing more unless simplification is possible. */
+      if ( simpler ) {
+
+/* Obtain the number of input/output coordinates for the replacement
+   Mapping and create the appropriate replacement. */
+         nin = astGetNin( ( *map_list )[ where ] );
+         if ( unit ) {
+            new = (AstMapping *) astUnitMap( nin, "", status );
+         } else {
+            new = (AstMapping *) astZoomMap( nin, zoom, "", status );
+         }
+      }
+
+/* In parallel. */
+/* ------------ */
+/* Handle the case where the Mappings are connected in parallel. */
+   } else {
+
+/* Obtain the number of input/output coordinates for the nominated Mapping. */
+      nin = astGetNin( ( *map_list )[ where ] );
+
+/* Search adjacent lower-numbered Mappings and identify the class to
+   which they belong. */
+      imap1 = where;
+      while ( astOK && ( ( imap1 - 1 ) >= 0 ) ) {
+         map = ( *map_list )[ imap1 - 1 ];
+         class = astGetClass( map );
+         if ( astOK ) {
+
+/* Quit looping when the first Mapping is found which is not a ZoomMap
+   or a UnitMap. */
+            if ( strcmp( class, "ZoomMap" ) &&
+                 strcmp( class, "UnitMap" ) ) break;
+
+/* Accumulate the total number of input/output coordinates for each
+   adjacent ZoomMap and UnitMap found. */
+            nin += astGetNin( map );
+            imap1--;
+         }
+      }
+
+/* Similarly search adjacent higher-numbered Mappings and accumulate
+   the number of input/output coordinates.*/
+      imap2 = where;
+      while ( astOK && ( ( imap2 + 1 ) < *nmap ) ) {
+         map = ( *map_list )[ imap2 + 1 ];
+         class = astGetClass( map );
+         if ( astOK ) {
+            if ( strcmp( class, "ZoomMap" ) &&
+                 strcmp( class, "UnitMap" ) ) break;
+            nin += astGetNin( map );
+            imap2++;
+         }
+      }
+
+/* Allocate memory for an array of zoom factors, one for each
+   input/output coordinate in the replacement Mapping. */
+      zooms = astMalloc( sizeof( double ) * (size_t) nin );
+      if ( astOK ) {
+
+/* Initialise. */
+         nzoom = 0;
+         minzoom = DBL_MAX;
+         maxzoom = -DBL_MAX;
+
+/* Loop through all the ZoomMaps and UnitMaps being merged and again
+   identify the class to which they belong. */
+         for ( imap = imap1; astOK && ( imap <= imap2 ); imap++ ) {
+            map = ( *map_list )[ imap ];
+            class = astGetClass( map );
+            if ( astOK ) {
+
+/* Set a default zoom factor of unity (for UnitMaps). If the Mapping
+   is a ZoomMap, replace this with the effective zoom factor, taking
+   account of the associated invert flag. */
+               zoom = 1.0;
+               if ( !strcmp( class, "ZoomMap" ) ) {
+                  if ( ( *invert_list )[ imap ] ) {
+                     zoom = 1.0 / astGetZoom( map );
+                  } else {
+                     zoom = astGetZoom( map );
+                  }
+               }
+
+/* Replicate the zoom factor in the zoom factor array, once for each
+   input/output coordinate associated with the current Mapping. */
+               ncoord = astGetNin( map );
+               for ( coord = 0; coord < ncoord; coord++ ) {
+                  zooms[ nzoom++ ] = zoom;
+               }
+
+/* Store the minimum and maximum zoom factors encountered. */
+               if ( zoom < minzoom ) minzoom = zoom;
+               if ( zoom > maxzoom ) maxzoom = zoom;
+            }
+         }
+
+/* Determine how many Mappings can be eliminated by condensing those
+   found above into a single Mapping. */
+         ngone = imap2 - imap1;
+
+/* Determine whether the replacement Mapping can be a single
+   ZoomMap. This will be so if all the effective zoom factors were the
+   same. */
+         single = ( minzoom == maxzoom );
+
+/* Determine if the replacement Mapping can be a UnitMap. This will be
+   so if all the effective zoom factors were equal to unity. */
+         unit = single && ( minzoom == 1.0 );
+
+/* Determine if simplification is possible. This will be so if (a)
+   Mappings can be eliminated ("ngone" is non-zero), or (b) the
+   replacement can be a UnitMap, or (c) where there was initially only
+   one ZoomMap, its invert flag was set (it will always be cleared in
+   the replacement Mapping). */
+         simpler = ngone || unit || ( *invert_list )[ where ];
+
+/* Do nothing more unless simplification is possible. */
+         if ( simpler ) {
+
+/* Create a replacement UnitMap or ZoomMap if appropriate. */
+            if ( unit ) {
+               new = (AstMapping *) astUnitMap( nin, "", status );
+            } else if ( single ) {
+               new = (AstMapping *) astZoomMap( nin, minzoom, "", status );
+
+/* Otherwise, replace the original ZoomMaps and UnitMaps with a
+   diagonal MatrixMap containing the zoom factors as its diagonal
+   elements. */
+            } else {
+               new = (AstMapping *) astMatrixMap( nin, nin, 1, zooms, "", status );
+            }
+         }
+      }
+
+/* Free the array of zoom factors. */
+      zooms = astFree( zooms );
+   }
+
+/* If OK, and simplification is possible, we now have a replacement
+   Mapping and can insert it into the Mapping list. */
+   if ( astOK ) {
+      if ( simpler ) {
+
+/* Annul the pointers to all the ZoomMaps that are being replaced. */
+         for ( imap = imap1; imap <= imap2; imap++ ) {
+            ( *map_list )[ imap ] = astAnnul( ( *map_list )[ imap ] );
+         }
+
+/* Insert the new pointer and the associated invert flag. */
+         ( *map_list )[ imap1 ] = new;
+         ( *invert_list )[ imap1 ] = 0;
+
+/* Loop to close the resulting gap by moving subsequent elements down
+   in the arrays. */
+         for ( imap = imap2 + 1; imap < *nmap; imap++ ) {
+            ( *map_list )[ imap - ngone ] = ( *map_list )[ imap ];
+            ( *invert_list )[ imap - ngone ] = ( *invert_list )[ imap ];
+         }
+
+/* Clear the vacated elements at the end. */
+         for ( imap = *nmap - ngone; imap < *nmap; imap++ ) {
+            ( *map_list )[ imap ] = NULL;
+            ( *invert_list )[ imap ] = 0;
+         }
+
+/* Decrement the Mapping count and return the index of the first
+   modified element. */
+         ( *nmap ) -= ngone;
+         result = imap1;
+      }
+
+/* If an error occurred, but a new Mapping has been created, then
+   annul it. */
+   } else {
+      if ( new ) new = astAnnul( new );
+   }
+
+/* If an error occurred, clear the returned result. */
+   if ( !astOK ) result = -1;
+
+/* Return the result. */
+   return result;
+}
+
+static int *MapSplit( AstMapping *this_map, int nin, const int *in, AstMapping **map, int *status ){
+/*
+*  Name:
+*     MapSplit
+
+*  Purpose:
+*     Create a Mapping representing a subset of the inputs of an existing
+*     ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     int *MapSplit( AstMapping *this, int nin, const int *in, AstMapping **map, int *status )
+
+*  Class Membership:
+*     ZoomMap method (over-rides the protected astMapSplit method
+*     inherited from the Mapping class).
+
+*  Description:
+*     This function creates a new Mapping by picking specified inputs from
+*     an existing ZoomMap. This is only possible if the specified inputs
+*     correspond to some subset of the ZoomMap outputs. That is, there
+*     must exist a subset of the ZoomMap outputs for which each output
+*     depends only on the selected ZoomMap inputs, and not on any of the
+*     inputs which have not been selected. If this condition is not met
+*     by the supplied ZoomMap, then a NULL Mapping is returned.
+
+*  Parameters:
+*     this
+*        Pointer to the ZoomMap to be split (the ZoomMap is not actually
+*        modified by this function).
+*     nin
+*        The number of inputs to pick from "this".
+*     in
+*        Pointer to an array of indices (zero based) for the inputs which
+*        are to be picked. This array should have "nin" elements. If "Nin"
+*        is the number of inputs of the supplied ZoomMap, then each element
+*        should have a value in the range zero to Nin-1.
+*     map
+*        Address of a location at which to return a pointer to the new
+*        Mapping. This Mapping will have "nin" inputs (the number of
+*        outputs may be different to "nin"). A NULL pointer will be
+*        returned if the supplied ZoomMap has no subset of outputs which
+*        depend only on the selected inputs.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     A pointer to a dynamically allocated array of ints. The number of
+*     elements in this array will equal the number of outputs for the
+*     returned Mapping. Each element will hold the index of the
+*     corresponding output in the supplied ZoomMap. The array should be
+*     freed using astFree when no longer needed. A NULL pointer will
+*     be returned if no output Mapping can be created.
+
+*  Notes:
+*     - If this function is invoked with the global error status set,
+*     or if it should fail for any reason, then NULL values will be
+*     returned as the function value and for the "map" pointer.
+*/
+
+/* Local Variables: */
+   AstZoomMap *this;          /* Pointer to ZoomMap structure */
+   int *result;               /* Pointer to returned array */
+   int i;                     /* Loop count */
+   int iin;                   /* Mapping input index */
+   int mnin;                  /* No. of Mapping inputs */
+   int ok;                    /* Are input indices OK? */
+
+/* Initialise */
+   result = NULL;
+   *map = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Get a pointer to the ZoomMap structure. */
+   this = (AstZoomMap *) this_map;
+
+/* Allocate memory for the returned array and create a ZoomMap with the
+   required number of axes. */
+   result = astMalloc( sizeof( int )*(size_t) nin );
+   *map = (AstMapping *) astZoomMap( nin, astGetZoom( this ), "", status );
+
+/* Set its Invert attribute to be like the supplied ZoomMap. */
+   astSetInvert( *map, astGetInvert( this ) );
+
+/* Check pointers can be used safely. */
+   if( astOK ) {
+
+/* Store the required output axis indices. At the same time check that each
+   axis is valid. */
+      mnin = astGetNin( this );
+      ok = 1;
+      for( i = 0; i < nin; i++ ) {
+         iin = in[ i ];
+         if( iin >= 0 && iin < mnin ) {
+            result[ i ] = iin;
+         } else {
+            ok = 0;
+            break;
+         }
+      }
+
+/* If the "in" array contained any invalid values, free the returned
+   resources. */
+      if( !ok ) {
+         result = astFree( result );
+         *map = astAnnul( *map );
+      }
+   }
+
+/* Free returned resources if an error has occurred. */
+   if( !astOK ) {
+      result = astFree( result );
+      *map = astAnnul( *map );
+   }
+
+/* Return the list of output indices. */
+   return result;
+}
+
+static double Rate( AstMapping *this, double *at, int ax1, int ax2, int *status ){
+/*
+*  Name:
+*     Rate
+
+*  Purpose:
+*     Calculate the rate of change of a Mapping output.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     result = Rate( AstMapping *this, double *at, int ax1, int ax2, int *status )
+
+*  Class Membership:
+*     ZoomMap member function (overrides the astRate method inherited
+*     from the Mapping class ).
+
+*  Description:
+*     This function returns the rate of change of a specified output of
+*     the supplied Mapping with respect to a specified input, at a
+*     specified input position.
+
+*  Parameters:
+*     this
+*        Pointer to the Mapping to be applied.
+*     at
+*        The address of an array holding the axis values at the position
+*        at which the rate of change is to be evaluated. The number of
+*        elements in this array should equal the number of inputs to the
+*        Mapping.
+*     ax1
+*        The index of the Mapping output for which the rate of change is to
+*        be found (output numbering starts at 0 for the first output).
+*     ax2
+*        The index of the Mapping input which is to be varied in order to
+*        find the rate of change (input numbering starts at 0 for the first
+*        input).
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     The rate of change of Mapping output "ax1" with respect to input
+*     "ax2", evaluated at "at", or AST__BAD if the value cannot be
+*     calculated.
+
+*/
+
+/* Local Variables: */
+   double result;
+
+/* Check inherited status */
+   if( !astOK ) return AST__BAD;
+
+/* Result is zero if the axes differ */
+   if( ax1 != ax2 ) {
+      result = 0.0;
+
+/* Otherwise, get the zoom factor. */
+   } else {
+      result = astGetZoom( (AstZoomMap *) this );
+
+/* If the ZoomMap has been inverted, return the reciprocal of the zoom
+   factor. */
+      if ( astGetInvert( this ) ) {
+         if( result != AST__BAD && result != 0.0 ) {
+            result = 1.0/result;
+         } else {
+            result = AST__BAD;
+         }
+      }
+   }
+
+/* Return the result. */
+   return result;
+}
+
+static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
+/*
+*  Name:
+*     SetAttrib
+
+*  Purpose:
+*     Set an attribute value for a ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     void SetAttrib( AstObject *this, const char *setting )
+
+*  Class Membership:
+*     ZoomMap member function (over-rides the astSetAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function assigns an attribute value for a ZoomMap, the
+*     attribute and its value being specified by means of a string of
+*     the form:
+*
+*        "attribute= value "
+*
+*     Here, "attribute" specifies the attribute name and should be in
+*     lower case with no white space present. The value to the right
+*     of the "=" should be a suitable textual representation of the
+*     value to be assigned and this will be interpreted according to
+*     the attribute's data type.  White space surrounding the value is
+*     only significant for string attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the ZoomMap.
+*     setting
+*        Pointer to a null-terminated string specifying the new attribute
+*        value.
+*/
+
+/* Local Variables: */
+   AstZoomMap *this;             /* Pointer to the ZoomMap structure */
+   double zoom;                  /* Zoom attribute value */
+   int len;                      /* Length of setting string */
+   int nc;                       /* Number of characters read by astSscanf */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the ZoomMap structure. */
+   this = (AstZoomMap *) this_object;
+
+/* Obtain the length of the setting string. */
+   len = (int) strlen( setting );
+
+/* Test for each recognised attribute in turn, using "astSscanf" to parse
+   the setting string and extract the attribute value (or an offset to
+   it in the case of string values). In each case, use the value set
+   in "nc" to check that the entire string was matched. Once a value
+   has been obtained, use the appropriate method to set it. */
+
+/* Zoom. */
+/* ----- */
+   if ( nc = 0,
+        ( 1 == astSscanf( setting, "zoom= %lf %n", &zoom, &nc ) )
+        && ( nc >= len ) ) {
+      astSetZoom( this, zoom );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      (*parent_setattrib)( this_object, setting, status );
+   }
+}
+
+static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
+/*
+*  Name:
+*     TestAttrib
+
+*  Purpose:
+*     Test if a specified attribute value is set for a ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     int TestAttrib( AstObject *this, const char *attrib, int *status )
+
+*  Class Membership:
+*     ZoomMap member function (over-rides the astTestAttrib protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function returns a boolean result (0 or 1) to indicate whether
+*     a value has been set for one of a ZoomMap's attributes.
+
+*  Parameters:
+*     this
+*        Pointer to the ZoomMap.
+*     attrib
+*        Pointer to a null-terminated string specifying the attribute
+*        name.  This should be in lower case with no surrounding white
+*        space.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     One if a value has been set, otherwise zero.
+
+*  Notes:
+*     - A value of zero will be returned if this function is invoked
+*     with the global status set, or if it should fail for any reason.
+*/
+
+/* Local Variables: */
+   AstZoomMap *this;             /* Pointer to the ZoomMap structure */
+   int result;                   /* Result value to return */
+
+/* Initialise. */
+   result = 0;
+
+/* Check the global error status. */
+   if ( !astOK ) return result;
+
+/* Obtain a pointer to the ZoomMap structure. */
+   this = (AstZoomMap *) this_object;
+
+/* Check the attribute name and test the appropriate attribute. */
+
+/* Zoom. */
+/* ----- */
+   if ( !strcmp( attrib, "zoom" ) ) {
+      result = astTestZoom( this );
+
+/* If the attribute is still not recognised, pass it on to the parent
+   method for further interpretation. */
+   } else {
+      result = (*parent_testattrib)( this_object, attrib, status );
+   }
+
+/* Return the result, */
+   return result;
+}
+
+static AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+                               int forward, AstPointSet *out, int *status ) {
+/*
+*  Name:
+*     Transform
+
+*  Purpose:
+*     Apply a ZoomMap to transform a set of points.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     AstPointSet *Transform( AstMapping *this, AstPointSet *in,
+*                             int forward, AstPointSet *out, int *status )
+
+*  Class Membership:
+*     ZoomMap member function (over-rides the astTransform protected
+*     method inherited from the Mapping class).
+
+*  Description:
+*     This function takes a ZoomMap and a set of points encapsulated in a
+*     PointSet and transforms the points so as to apply the required zoom
+*     factor.
+
+*  Parameters:
+*     this
+*        Pointer to the ZoomMap.
+*     in
+*        Pointer to the PointSet holding the input coordinate data.
+*     forward
+*        A non-zero value indicates that the forward coordinate transformation
+*        should be applied, while a zero value requests the inverse
+*        transformation.
+*     out
+*        Pointer to a PointSet which will hold the transformed (output)
+*        coordinate values. A NULL value may also be given, in which case a
+*        new PointSet will be created by this function.
+*     status
+*        Pointer to the inherited status variable.
+
+*  Returned Value:
+*     Pointer to the output (possibly new) PointSet.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*     -  The number of coordinate values per point in the input PointSet must
+*     match the number of coordinates for the ZoomMap being applied.
+*     -  If an output PointSet is supplied, it must have space for sufficient
+*     number of points and coordinate values per point to accommodate the
+*     result. Any excess space will be ignored.
+*/
+
+/* Local Variables: */
+   AstPointSet *result;          /* Pointer to output PointSet */
+   AstZoomMap *map;              /* Pointer to ZoomMap to be applied */
+   double **ptr_in;              /* Pointer to input coordinate data */
+   double **ptr_out;             /* Pointer to output coordinate data */
+   double scale;                 /* Scale factor to implement zoom */
+   int coord;                    /* Loop counter for coordinates */
+   int ncoord_in;                /* Number of coordinates per input point */
+   int npoint;                   /* Number of points */
+   int point;                    /* Loop counter for points */
+
+/* Check the global error status. */
+   if ( !astOK ) return NULL;
+
+/* Obtain a pointer to the ZoomMap. */
+   map = (AstZoomMap *) this;
+
+/* Apply the parent mapping using the stored pointer to the Transform member
+   function inherited from the parent Mapping class. This function validates
+   all arguments and generates an output PointSet if necessary, but does not
+   actually transform any coordinate values. */
+   result = (*parent_transform)( this, in, forward, out, status );
+
+/* We will now extend the parent astTransform method by performing the
+   calculations needed to generate the output coordinate values. */
+
+/* Determine the numbers of points and coordinates per point from the input
+   PointSet and obtain pointers for accessing the input and output coordinate
+   values. */
+   ncoord_in = astGetNcoord( in );
+   npoint = astGetNpoint( in );
+   ptr_in = astGetPoints( in );
+   ptr_out = astGetPoints( result );
+
+/* Determine whether to apply the forward or inverse mapping, according to the
+   direction specified and whether the mapping has been inverted. */
+   if ( astGetInvert( map ) ) forward = !forward;
+
+/* Perform coordinate arithmetic. */
+/* ------------------------------ */
+/* Generate the coordinate scale factor, according to the direction
+   of mapping required. */
+   scale = astGetZoom( map );
+   if ( !forward && astOK ) scale = 1.0 / scale;
+
+/* Loop to apply the scale factor to all the coordinate values, checking for
+   (and propagating) bad values in the process. */
+   if ( astOK ) {
+      for ( coord = 0; coord < ncoord_in; coord++ ) {
+         for ( point = 0; point < npoint; point++ ) {
+            if ( ptr_in[ coord ][ point ] == AST__BAD ) {
+               ptr_out[ coord ][ point ] = AST__BAD;
+            } else {
+               ptr_out[ coord ][ point ] = ptr_in[ coord ][ point ] * scale;
+            }
+         }
+      }
+   }
+
+/* Return a pointer to the output PointSet. */
+   return result;
+}
+
+/* Functions which access class attributes. */
+/* ---------------------------------------- */
+/* Implement member functions to access the attributes associated with
+   this class using the macros defined for this purpose in the
+   "object.h" file. For a description of each attribute, see the class
+   interface (in the associated .h file). */
+
+/*
+*att++
+*  Name:
+*     Zoom
+
+*  Purpose:
+*     ZoomMap scale factor.
+
+*  Type:
+*     Public attribute.
+
+*  Synopsis:
+*     Double precision.
+
+*  Description:
+*     This attribute holds the ZoomMap scale factor, by which
+*     coordinate values are multiplied (by the forward transformation)
+*     or divided (by the inverse transformation). The default value
+*     is unity.
+*
+c     Note that if a ZoomMap is inverted (e.g. by using astInvert),
+f     Note that if a ZoomMap is inverted (e.g. by using AST_INVERT),
+*     then the reciprocal of this zoom factor will, in effect, be
+*     used.
+*
+*     In general, Mapping attributes cannot be changed after the Mapping
+*     has been created (the exception to this is the Invert attribute,
+*     which can be changed at any time). However, several of the oldest
+*     Mapping classes - including the ZoomMap class - were introduced
+*     into the AST library before this restriction was enforced. To
+*     reduce the chances of breaking existing software, the attributes of
+*     such Mappings may still be changed, but only for Mapping instances
+*     that have exactly one active reference. In other words, an error will
+*     be reported if an attempt is made to set or clear an attribute of a
+*     Mapping (other than the Invert attribute) if that Mapping has been
+*     cloned. Mappings are cloned when they are incorporated into another
+*     object such as a CmpMap or FrameSet, or when the
+c     astClone
+f     AST_CLONE
+*     function is used.
+
+*  Applicability:
+*     ZoomMap
+*        All ZoomMaps have this attribute.
+
+*  Notes:
+*     - The Zoom attribute may not be set to zero.
+*att--
+*/
+/* This ia a double value with a value of 0.0 when undefined but
+   yielding a default of 1.0. Setting it explicitly to 0.0 is not
+   permitted except via astClearZoom. */
+astMAKE_CLEAR1(ZoomMap,Zoom,zoom,0.0)
+astMAKE_GET(ZoomMap,Zoom,double,1.0,( ( this->zoom == 0.0 ) ?
+                                      1.0 : this->zoom ))
+
+/* Check for an attempt to set a value of zero and report an error if
+   necessary (leaving the Zoom value unchanged). */
+astMAKE_SET1(ZoomMap,Zoom,double,zoom,(
+            ( value != 0.0 ) ?
+            value :
+            ( astError( AST__ZOOMI,
+                       "astSetZoom(%s): A zoom factor of zero is not allowed.", status,
+                        astGetClass( this ) ),
+              this->zoom ) ))
+astMAKE_TEST(ZoomMap,Zoom,( this->zoom != 0.0 ))
+
+/* Copy constructor. */
+/* ----------------- */
+/* No copy constructor is needed, as a byte-by-byte copy suffices. */
+
+/* Destructor. */
+/* ----------- */
+/* No destructor is needed as no memory, etc. needs freeing. */
+
+/* Dump function. */
+/* -------------- */
+static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
+/*
+*  Name:
+*     Dump
+
+*  Purpose:
+*     Dump function for ZoomMap objects.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     void Dump( AstObject *this, AstChannel *channel, int *status )
+
+*  Description:
+*     This function implements the Dump function which writes out data
+*     for the ZoomMap class to an output Channel.
+
+*  Parameters:
+*     this
+*        Pointer to the ZoomMap whose data are being written.
+*     channel
+*        Pointer to the Channel to which the data are being written.
+*     status
+*        Pointer to the inherited status variable.
+*/
+
+/* Local Variables: */
+   AstZoomMap *this;             /* Pointer to the ZoomMap structure */
+   double dval;                  /* Double value */
+   int set;                      /* Attribute value set? */
+
+/* Check the global error status. */
+   if ( !astOK ) return;
+
+/* Obtain a pointer to the ZoomMap structure. */
+   this = (AstZoomMap *) this_object;
+
+/* Write out values representing the instance variables for the
+   ZoomMap class.  Accompany these with appropriate comment strings,
+   possibly depending on the values being written.*/
+
+/* In the case of attributes, we first use the appropriate (private)
+   Test...  member function to see if they are set. If so, we then use
+   the (private) Get... function to obtain the value to be written
+   out.
+
+   For attributes which are not set, we use the astGet... method to
+   obtain the value instead. This will supply a default value
+   (possibly provided by a derived class which over-rides this method)
+   which is more useful to a human reader as it corresponds to the
+   actual default attribute value.  Since "set" will be zero, these
+   values are for information only and will not be read back. */
+
+/* Zoom. */
+/* ----- */
+   set = TestZoom( this, status );
+   dval = set ? GetZoom( this, status ) : astGetZoom( this );
+   astWriteDouble( channel, "Zoom", set, 1, dval, "Zoom factor" );
+}
+
+/* Standard class functions. */
+/* ========================= */
+/* Implement the astIsAZoomMap and astCheckZoomMap functions using the macros
+   defined for this purpose in the "object.h" header file. */
+astMAKE_ISA(ZoomMap,Mapping)
+astMAKE_CHECK(ZoomMap)
+
+AstZoomMap *astZoomMap_( int ncoord, double zoom, const char *options, int *status, ...) {
+/*
+*++
+*  Name:
+c     astZoomMap
+f     AST_ZOOMMAP
+
+*  Purpose:
+*     Create a ZoomMap.
+
+*  Type:
+*     Public function.
+
+*  Synopsis:
+c     #include "zoommap.h"
+c     AstZoomMap *astZoomMap( int ncoord, double zoom,
+c                             const char *options, ... )
+f     RESULT = AST_ZOOMMAP( NCOORD, ZOOM, OPTIONS, STATUS )
+
+*  Class Membership:
+*     ZoomMap constructor.
+
+*  Description:
+*     This function creates a new ZoomMap and optionally initialises its
+*     attributes.
+*
+*     A ZoomMap is a Mapping which "zooms" a set of points about the
+*     origin by multiplying all coordinate values by the same scale
+*     factor (the inverse transformation is performed by dividing by
+*     this scale factor).
+
+*  Parameters:
+c     ncoord
+f     NCOORD = INTEGER (Given)
+*        The number of coordinate values for each point to be
+*        transformed (i.e. the number of dimensions of the space in
+*        which the points will reside). The same number is applicable
+*        to both input and output points.
+c     zoom
+f     ZOOM = DOUBLE PRECISION (Given)
+*        Initial scale factor by which coordinate values should be
+*        multiplied (by the forward transformation) or divided (by the
+*        inverse transformation). This factor may subsequently be
+*        changed via the ZoomMap's Zoom attribute. It may be positive
+*        or negative, but should not be zero.
+c     options
+f     OPTIONS = CHARACTER * ( * ) (Given)
+c        Pointer to a null-terminated string containing an optional
+c        comma-separated list of attribute assignments to be used for
+c        initialising the new ZoomMap. The syntax used is identical to
+c        that for the astSet function and may include "printf" format
+c        specifiers identified by "%" symbols in the normal way.
+f        A character string containing an optional comma-separated
+f        list of attribute assignments to be used for initialising the
+f        new ZoomMap. The syntax used is identical to that for the
+f        AST_SET routine.
+c     ...
+c        If the "options" string contains "%" format specifiers, then
+c        an optional list of additional arguments may follow it in
+c        order to supply values to be substituted for these
+c        specifiers. The rules for supplying these are identical to
+c        those for the astSet function (and for the C "printf"
+c        function).
+f     STATUS = INTEGER (Given and Returned)
+f        The global status.
+
+*  Returned Value:
+c     astZoomMap()
+f     AST_ZOOMMAP = INTEGER
+*        A pointer to the new ZoomMap.
+
+*  Notes:
+*     - A null Object pointer (AST__NULL) will be returned if this
+c     function is invoked with the AST error status set, or if it
+f     function is invoked with STATUS set to an error value, or if it
+*     should fail for any reason.
+
+*  Status Handling:
+*     The protected interface to this function includes an extra
+*     parameter at the end of the parameter list descirbed above. This
+*     parameter is a pointer to the integer inherited status
+*     variable: "int *status".
+
+*--
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstZoomMap *new;              /* Pointer to new ZoomMap */
+   va_list args;                 /* Variable argument list */
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the ZoomMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitZoomMap( NULL, sizeof( AstZoomMap ), !class_init, &class_vtab,
+                         "ZoomMap", ncoord, zoom );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new ZoomMap's attributes. */
+      va_start( args, status );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return a pointer to the new ZoomMap. */
+   return new;
+}
+
+AstZoomMap *astZoomMapId_( int ncoord, double zoom,
+                           const char *options, ... ) {
+/*
+*  Name:
+*     astZoomMapId_
+
+*  Purpose:
+*     Create a ZoomMap.
+
+*  Type:
+*     Private function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     AstZoomMap *astZoomMapId_( int ncoord, double zoom,
+*                                const char *options, ... )
+
+*  Class Membership:
+*     ZoomMap constructor.
+
+*  Description:
+*     This function implements the external (public) interface to the
+*     astZoomMap constructor function. It returns an ID value (instead
+*     of a true C pointer) to external users, and must be provided
+*     because astZoomMap_ has a variable argument list which cannot be
+*     encapsulated in a macro (where this conversion would otherwise
+*     occur).
+*
+*     The variable argument list also prevents this function from
+*     invoking astZoomMap_ directly, so it must be a re-implementation
+*     of it in all respects, except for the final conversion of the
+*     result to an ID value.
+
+*  Parameters:
+*     As for astZoomMap_.
+
+*  Returned Value:
+*     The ID value associated with the new ZoomMap.
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstZoomMap *new;              /* Pointer to new ZoomMap */
+   va_list args;                 /* Variable argument list */
+
+   int *status;                  /* Pointer to inherited status value */
+
+/* Get a pointer to the inherited status value. */
+   status = astGetStatusPtr;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(NULL);
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* Initialise the ZoomMap, allocating memory and initialising the
+   virtual function table as well if necessary. */
+   new = astInitZoomMap( NULL, sizeof( AstZoomMap ), !class_init, &class_vtab,
+                         "ZoomMap", ncoord, zoom );
+
+/* If successful, note that the virtual function table has been
+   initialised. */
+   if ( astOK ) {
+      class_init = 1;
+
+/* Obtain the variable argument list and pass it along with the options string
+   to the astVSet method to initialise the new ZoomMap's attributes. */
+      va_start( args, options );
+      astVSet( new, options, NULL, args );
+      va_end( args );
+
+/* If an error occurred, clean up by deleting the new object. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return an ID value for the new ZoomMap. */
+   return astMakeId( new );
+}
+
+AstZoomMap *astInitZoomMap_( void *mem, size_t size, int init,
+                             AstZoomMapVtab *vtab, const char *name,
+                             int ncoord, double zoom, int *status ) {
+/*
+*+
+*  Name:
+*     astInitZoomMap
+
+*  Purpose:
+*     Initialise a ZoomMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     AstZoomMap *astInitZoomMap( void *mem, size_t size, int init,
+*                                 AstZoomMapVtab *vtab, const char *name,
+*                                 int ncoord, double zoom )
+
+*  Class Membership:
+*     ZoomMap initialiser.
+
+*  Description:
+*     This function is provided for use by class implementations to initialise
+*     a new ZoomMap object. It allocates memory (if necessary) to accommodate
+*     the ZoomMap plus any additional data associated with the derived class.
+*     It then initialises a ZoomMap structure at the start of this memory. If
+*     the "init" flag is set, it also initialises the contents of a virtual
+*     function table for a ZoomMap at the start of the memory passed via the
+*     "vtab" parameter.
+
+*  Parameters:
+*     mem
+*        A pointer to the memory in which the ZoomMap is to be initialised.
+*        This must be of sufficient size to accommodate the ZoomMap data
+*        (sizeof(ZoomMap)) plus any data used by the derived class. If a value
+*        of NULL is given, this function will allocate the memory itself using
+*        the "size" parameter to determine its size.
+*     size
+*        The amount of memory used by the ZoomMap (plus derived class data).
+*        This will be used to allocate memory if a value of NULL is given for
+*        the "mem" parameter. This value is also stored in the ZoomMap
+*        structure, so a valid value must be supplied even if not required for
+*        allocating memory.
+*     init
+*        A logical flag indicating if the ZoomMap's virtual function table is
+*        to be initialised. If this value is non-zero, the virtual function
+*        table will be initialised by this function.
+*     vtab
+*        Pointer to the start of the virtual function table to be associated
+*        with the new ZoomMap.
+*     name
+*        Pointer to a constant null-terminated character string which contains
+*        the name of the class to which the new object belongs (it is this
+*        pointer value that will subsequently be returned by the astGetClass
+*        method).
+*     ncoord
+*        The number of coordinate values per point.
+*     zoom
+*        The scale factor by which coordinate values are multiplied (by the
+*        forward mapping) or divided (by the inverse mapping). This factor may
+*        not be zero.
+
+*  Returned Value:
+*     A pointer to the new ZoomMap.
+
+*  Notes:
+*     -  A null pointer will be returned if this function is invoked with the
+*     global error status set, or if it should fail for any reason.
+*-
+*/
+
+/* Local Variables: */
+   AstZoomMap *new;              /* Pointer to new ZoomMap */
+
+/* Check the global status. */
+   if ( !astOK ) return NULL;
+
+/* If necessary, initialise the virtual function table. */
+   if ( init ) astInitZoomMapVtab( vtab, name );
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the initialisation value(s) for validity, reporting an error if
+   necessary. Prefix the error report with the name of the function and the
+   class of object being processed. */
+   if ( zoom == 0.0 ) {
+      astError( AST__ZOOMI, "astInitZoomMap(%s): A zoom factor of zero is not "
+                "allowed.", status, name );
+   } else {
+
+/* Initialise a Mapping structure (the parent class) as the first component
+   within the ZoomMap structure, allocating memory if necessary. Specify that
+   the Mapping should be defined in both the forward and inverse directions. */
+      new = (AstZoomMap *) astInitMapping( mem, size, 0,
+                                           (AstMappingVtab *) vtab, name,
+                                           ncoord, ncoord, 1, 1 );
+
+      if ( astOK ) {
+
+/* Initialise the ZoomMap data. */
+/* ---------------------------- */
+/* Store the zoom factor. */
+         new->zoom = zoom;
+
+/* If an error occurred, clean up by deleting the new ZoomMap. */
+         if ( !astOK ) new = astDelete( new );
+      }
+   }
+
+/* Return a pointer to the new ZoomMap. */
+   return new;
+}
+
+AstZoomMap *astLoadZoomMap_( void *mem, size_t size,
+                             AstZoomMapVtab *vtab, const char *name,
+                             AstChannel *channel, int *status ) {
+/*
+*+
+*  Name:
+*     astLoadZoomMap
+
+*  Purpose:
+*     Load a ZoomMap.
+
+*  Type:
+*     Protected function.
+
+*  Synopsis:
+*     #include "zoommap.h"
+*     AstZoomMap *astLoadZoomMap( void *mem, size_t size,
+*                                 AstZoomMapVtab *vtab, const char *name,
+*                                 AstChannel *channel )
+
+*  Class Membership:
+*     ZoomMap loader.
+
+*  Description:
+*     This function is provided to load a new ZoomMap using data read
+*     from a Channel. It first loads the data used by the parent class
+*     (which allocates memory if necessary) and then initialises a
+*     ZoomMap structure in this memory, using data read from the input
+*     Channel.
+*
+*     If the "init" flag is set, it also initialises the contents of a
+*     virtual function table for a ZoomMap at the start of the memory
+*     passed via the "vtab" parameter.
+
+
+*  Parameters:
+*     mem
+*        A pointer to the memory into which the ZoomMap is to be
+*        loaded.  This must be of sufficient size to accommodate the
+*        ZoomMap data (sizeof(ZoomMap)) plus any data used by derived
+*        classes. If a value of NULL is given, this function will
+*        allocate the memory itself using the "size" parameter to
+*        determine its size.
+*     size
+*        The amount of memory used by the ZoomMap (plus derived class
+*        data).  This will be used to allocate memory if a value of
+*        NULL is given for the "mem" parameter. This value is also
+*        stored in the ZoomMap structure, so a valid value must be
+*        supplied even if not required for allocating memory.
+*
+*        If the "vtab" parameter is NULL, the "size" value is ignored
+*        and sizeof(AstZoomMap) is used instead.
+*     vtab
+*        Pointer to the start of the virtual function table to be
+*        associated with the new ZoomMap. If this is NULL, a pointer
+*        to the (static) virtual function table for the ZoomMap class
+*        is used instead.
+*     name
+*        Pointer to a constant null-terminated character string which
+*        contains the name of the class to which the new object
+*        belongs (it is this pointer value that will subsequently be
+*        returned by the astGetClass method).
+*
+*        If the "vtab" parameter is NULL, the "name" value is ignored
+*        and a pointer to the string "ZoomMap" is used instead.
+
+*  Returned Value:
+*     A pointer to the new ZoomMap.
+
+*  Notes:
+*     - A null pointer will be returned if this function is invoked
+*     with the global error status set, or if it should fail for any
+*     reason.
+*-
+*/
+
+/* Local Variables: */
+   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
+   AstZoomMap *new;              /* Pointer to the new ZoomMap */
+
+/* Initialise. */
+   new = NULL;
+
+/* Check the global error status. */
+   if ( !astOK ) return new;
+
+/* Get a pointer to the thread specific global data structure. */
+   astGET_GLOBALS(channel);
+
+/* If a NULL virtual function table has been supplied, then this is
+   the first loader to be invoked for this ZoomMap. In this case the
+   ZoomMap belongs to this class, so supply appropriate values to be
+   passed to the parent class loader (and its parent, etc.). */
+   if ( !vtab ) {
+      size = sizeof( AstZoomMap );
+      vtab = &class_vtab;
+      name = "ZoomMap";
+
+/* If required, initialise the virtual function table for this class. */
+      if ( !class_init ) {
+         astInitZoomMapVtab( vtab, name );
+         class_init = 1;
+      }
+   }
+
+/* Invoke the parent class loader to load data for all the ancestral
+   classes of the current one, returning a pointer to the resulting
+   partly-built ZoomMap. */
+   new = astLoadMapping( mem, size, (AstMappingVtab *) vtab, name,
+                         channel );
+
+   if ( astOK ) {
+
+/* Read input data. */
+/* ================ */
+/* Request the input Channel to read all the input data appropriate to
+   this class into the internal "values list". */
+      astReadClassData( channel, "ZoomMap" );
+
+/* Now read each individual data item from this list and use it to
+   initialise the appropriate instance variable(s) for this class. */
+
+/* In the case of attributes, we first read the "raw" input value,
+   supplying the "unset" value as the default. If a "set" value is
+   obtained, we then use the appropriate (private) Set... member
+   function to validate and set the value properly. */
+
+/* Zoom. */
+/* ----- */
+      new->zoom = astReadDouble( channel, "zoom", 0.0 );
+      if ( TestZoom( new, status ) ) SetZoom( new, new->zoom, status );
+
+/* If an error occurred, clean up by deleting the new ZoomMap. */
+      if ( !astOK ) new = astDelete( new );
+   }
+
+/* Return the new ZoomMap pointer. */
+   return new;
+}
+
+/* Virtual function interfaces. */
+/* ============================ */
+/* These provide the external interface to the virtual functions defined by
+   this class. Each simply checks the global error status and then locates and
+   executes the appropriate member function, using the function pointer stored
+   in the object's virtual function table (this pointer is located using the
+   astMEMBER macro defined in "object.h").
+
+   Note that the member function may not be the one defined here, as it may
+   have been over-ridden by a derived class. However, it should still have the
+   same interface. */
+
+
+
+
+
diff --git a/ast/zoommap.h b/ast/zoommap.h
new file mode 100644
index 0000000..1f5b06e
--- /dev/null
+++ b/ast/zoommap.h
@@ -0,0 +1,322 @@
+#if !defined( ZOOMMAP_INCLUDED ) /* Include this file only once */
+#define ZOOMMAP_INCLUDED
+/*
+*+
+*  Name:
+*     zoommap.h
+
+*  Type:
+*     C include file.
+
+*  Purpose:
+*     Define the interface to the ZoomMap class.
+
+*  Invocation:
+*     #include "zoommap.h"
+
+*  Description:
+*     This include file defines the interface to the ZoomMap class and
+*     provides the type definitions, function prototypes and macros,
+*     etc.  needed to use this class.
+*
+*     The ZoomMap class implements Mappings which perform a "zoom"
+*     transformation by multiplying all coordinate values by the same
+*     scale factor (the inverse transformation is performed by
+*     dividing by this scale factor).
+
+*  Inheritance:
+*     The ZoomMap class inherits from the Mapping class.
+
+*  Attributes Over-Ridden:
+*     None.
+
+*  New Attributes Defined:
+*     Zoom (double)
+*        The ZoomMap scale factor, by which coordinate values are
+*        multiplied (by the forward transformation) or divided (by the
+*        inverse transformation). This factor is set when a ZoomMap is
+*        created, but may later be modified. It may not be set to
+*        zero. The default value (if cleared) is one.
+*
+*        Note that if the ZoomMap is inverted (by using astInvert or
+*        setting a non-zero value for its Invert attribute), then the
+*        reciprocal of this Zoom value will, in effect, be used.
+
+*  Methods Over-Ridden:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearAttrib
+*           Clear an attribute value for a ZoomMap.
+*        astGetAttrib
+*           Get an attribute value for a ZoomMap.
+*        astSetAttrib
+*           Set an attribute value for a ZoomMap.
+*        astTestAttrib
+*           Test if an attribute value has been set for a ZoomMap.
+*        astTransform
+*           Apply a ZoomMap to transform a set of points.
+
+*  New Methods Defined:
+*     Public:
+*        None.
+*
+*     Protected:
+*        astClearZoom
+*           Clear the Zoom attribute value for a ZoomMap.
+*        astGetZoom
+*           Get the Zoom attribute value for a ZoomMap.
+*        astSetZoom
+*           Set the Zoom attribute value for a ZoomMap.
+*        astTestZoom
+*           Test if a Zoom attribute value has been set for a ZoomMap.
+
+*  Other Class Functions:
+*     Public:
+*        astIsAZoomMap
+*           Test class membership.
+*        astZoomMap
+*           Create a ZoomMap.
+*
+*     Protected:
+*        astCheckZoomMap
+*           Validate class membership.
+*        astInitZoomMap
+*           Initialise a ZoomMap.
+*        astInitZoomMapVtab
+*           Initialise the virtual function table for the ZoomMap class.
+*        astLoadZoomMap
+*           Load a ZoomMap.
+
+*  Macros:
+*     None.
+
+*  Type Definitions:
+*     Public:
+*        AstZoomMap
+*           ZoomMap object type.
+*
+*     Protected:
+*        AstZoomMapVtab
+*           ZoomMap virtual function table type.
+
+*  Feature Test Macros:
+*     astCLASS
+*        If the astCLASS macro is undefined, only public symbols are
+*        made available, otherwise protected symbols (for use in other
+*        class implementations) are defined. This macro also affects
+*        the reporting of error context information, which is only
+*        provided for external calls to the AST library.
+
+*  Copyright:
+*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
+*     Research Councils
+
+*  Licence:
+*     This program is free software: you can redistribute it and/or
+*     modify it under the terms of the GNU Lesser General Public
+*     License as published by the Free Software Foundation, either
+*     version 3 of the License, or (at your option) any later
+*     version.
+*     
+*     This program is distributed in the hope that it will be useful,
+*     but WITHOUT ANY WARRANTY; without even the implied warranty of
+*     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*     GNU Lesser General Public License for more details.
+*     
+*     You should have received a copy of the GNU Lesser General
+*     License along with this program.  If not, see
+*     <http://www.gnu.org/licenses/>.
+
+*  Authors:
+*     RFWS: R.F. Warren-Smith (Starlink)
+
+*  History:
+*     30-JAN-1996 (RFWS):
+*        Original version.
+*     18-JUL-1996 (RFWS):
+*        Updated to provide an external interface.
+*     10-SEP-1996 (RFWS):
+*        Added I/O facilities.
+*     8-JAN-2003 (DSB):
+*        Added protected astInitZoomMapVtab method.
+*-
+*/
+
+/* Include files. */
+/* ============== */
+/* Interface definitions. */
+/* ---------------------- */
+#include "mapping.h"             /* Coordinate mappings (parent class) */
+
+#if defined(astCLASS)            /* Protected */
+#include "channel.h"             /* I/O channels */
+#endif
+
+/* C header files. */
+/* --------------- */
+#if defined(astCLASS)            /* Protected */
+#include <stddef.h>
+#endif
+
+/* Macros */
+/* ====== */
+
+/* Define a dummy __attribute__ macro for use on non-GNU compilers. */
+#ifndef __GNUC__
+#  define  __attribute__(x)  /*NOTHING*/
+#endif
+
+/* Type Definitions. */
+/* ================= */
+/* ZoomMap structure. */
+/* ------------------ */
+/* This structure contains all information that is unique to each object in
+   the class (e.g. its instance variables). */
+typedef struct AstZoomMap {
+
+/* Attributes inherited from the parent class. */
+   AstMapping mapping;           /* Parent class structure */
+
+/* Attributes specific to objects in this class. */
+   double zoom;                  /* Zoom factor */
+} AstZoomMap;
+
+/* Virtual function table. */
+/* ----------------------- */
+/* This table contains all information that is the same for all
+   objects in the class (e.g. pointers to its virtual functions). */
+#if defined(astCLASS)            /* Protected */
+typedef struct AstZoomMapVtab {
+
+/* Properties (e.g. methods) inherited from the parent class. */
+   AstMappingVtab mapping_vtab;  /* Parent class virtual function table */
+
+/* A Unique identifier to determine class membership. */
+   AstClassIdentifier id;
+
+/* Properties (e.g. methods) specific to this class. */
+   double (*GetZoom)( AstZoomMap *, int * );
+   int (* TestZoom)( AstZoomMap *, int * );
+   void (* ClearZoom)( AstZoomMap *, int * );
+   void (* SetZoom)( AstZoomMap *, double, int * );
+} AstZoomMapVtab;
+
+
+#if defined(THREAD_SAFE)
+
+/* Define a structure holding all data items that are global within this
+   class. */
+typedef struct AstZoomMapGlobals {
+   AstZoomMapVtab Class_Vtab;
+   int Class_Init;
+   char GetAttrib_Buff[ 101 ];
+} AstZoomMapGlobals;
+
+#endif
+
+#endif
+
+/* Function prototypes. */
+/* ==================== */
+/* Prototypes for standard class functions. */
+/* ---------------------------------------- */
+astPROTO_CHECK(ZoomMap)          /* Check class membership */
+astPROTO_ISA(ZoomMap)            /* Test class membership */
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+AstZoomMap *astZoomMap_( int, double, const char *, int *, ...);
+#else
+AstZoomMap *astZoomMapId_( int, double, const char *, ... )__attribute__((format(printf,3,4)));
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+AstZoomMap *astInitZoomMap_( void *, size_t, int, AstZoomMapVtab *,
+                             const char *, int, double, int * );
+
+/* Vtab initialiser. */
+void astInitZoomMapVtab_( AstZoomMapVtab *, const char *, int * );
+
+/* Loader. */
+AstZoomMap *astLoadZoomMap_( void *, size_t, AstZoomMapVtab *,
+                             const char *, AstChannel *, int * );
+
+/* Thread-safe initialiser for all global data used by this module. */
+#if defined(THREAD_SAFE)
+void astInitZoomMapGlobals_( AstZoomMapGlobals * );
+#endif
+
+#endif
+
+/* Prototypes for member functions. */
+/* -------------------------------- */
+# if defined(astCLASS)           /* Protected */
+double astGetZoom_( AstZoomMap *, int * );
+int astTestZoom_( AstZoomMap *, int * );
+void astClearZoom_( AstZoomMap *, int * );
+void astSetZoom_( AstZoomMap *, double, int * );
+#endif
+
+/* Function interfaces. */
+/* ==================== */
+/* These macros are wrap-ups for the functions defined by this class
+   to make them easier to invoke (e.g. to avoid type mis-matches when
+   passing pointers to objects from derived classes). */
+
+/* Interfaces to standard class functions. */
+/* --------------------------------------- */
+/* Some of these functions provide validation, so we cannot use them
+   to validate their own arguments. We must use a cast when passing
+   object pointers (so that they can accept objects from derived
+   classes). */
+
+/* Check class membership. */
+#define astCheckZoomMap(this) astINVOKE_CHECK(ZoomMap,this,0)
+#define astVerifyZoomMap(this) astINVOKE_CHECK(ZoomMap,this,1)
+
+/* Test class membership. */
+#define astIsAZoomMap(this) astINVOKE_ISA(ZoomMap,this)
+
+/* Constructor. */
+#if defined(astCLASS)            /* Protected. */
+#define astZoomMap astINVOKE(F,astZoomMap_)
+#else
+#define astZoomMap astINVOKE(F,astZoomMapId_)
+#endif
+
+#if defined(astCLASS)            /* Protected */
+
+/* Initialiser. */
+#define astInitZoomMap(mem,size,init,vtab,name,ncoord,zoom) \
+astINVOKE(O,astInitZoomMap_(mem,size,init,vtab,name,ncoord,zoom,STATUS_PTR))
+
+/* Vtab Initialiser. */
+#define astInitZoomMapVtab(vtab,name) astINVOKE(V,astInitZoomMapVtab_(vtab,name,STATUS_PTR))
+/* Loader. */
+#define astLoadZoomMap(mem,size,vtab,name,channel) \
+astINVOKE(O,astLoadZoomMap_(mem,size,vtab,name,astCheckChannel(channel),STATUS_PTR))
+#endif
+
+/* Interfaces to public member functions. */
+/* -------------------------------------- */
+/* Here we make use of astCheckZoomMap to validate ZoomMap pointers
+   before use.  This provides a contextual error report if a pointer
+   to the wrong sort of Object is supplied. */
+
+#if defined(astCLASS)            /* Protected */
+#define astClearZoom(this) astINVOKE(V,astClearZoom_(astCheckZoomMap(this),STATUS_PTR))
+#define astGetZoom(this) astINVOKE(V,astGetZoom_(astCheckZoomMap(this),STATUS_PTR))
+#define astSetZoom(this,value) \
+astINVOKE(V,astSetZoom_(astCheckZoomMap(this),value,STATUS_PTR))
+#define astTestZoom(this) astINVOKE(V,astTestZoom_(astCheckZoomMap(this),STATUS_PTR))
+#endif
+#endif
+
+
+
+
+
-- 
cgit v0.12